From 73e2f02fd606e8101b50a00e66344a36adae9254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 19 Sep 2022 10:50:38 +0200 Subject: [PATCH 0001/1551] Avoid free call in interpreted mode (#12496) --- src/regex.cr | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/regex.cr b/src/regex.cr index 6ed5891fb921..b3ccd7bf0bec 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -262,7 +262,9 @@ class Regex raise ArgumentError.new("#{String.new(errptr)} at #{erroffset}") if @re.null? @extra = LibPCRE.study(@re, LibPCRE::STUDY_JIT_COMPILE, out studyerrptr) if @extra.null? && studyerrptr - LibPCRE.free.call @re.as(Void*) + {% unless flag?(:interpreted) %} + LibPCRE.free.call @re.as(Void*) + {% end %} raise ArgumentError.new("#{String.new(studyerrptr)}") end LibPCRE.full_info(@re, nil, LibPCRE::INFO_CAPTURECOUNT, out @captures) @@ -270,7 +272,9 @@ class Regex def finalize LibPCRE.free_study @extra - LibPCRE.free.call @re.as(Void*) + {% unless flag?(:interpreted) %} + LibPCRE.free.call @re.as(Void*) + {% end %} end # Determines Regex's source validity. If it is, `nil` is returned. @@ -283,7 +287,9 @@ class Regex def self.error?(source) : String? re = LibPCRE.compile(source, (Options::UTF_8 | Options::NO_UTF8_CHECK | Options::DUPNAMES), out errptr, out erroffset, nil) if re - LibPCRE.free.call re.as(Void*) + {% unless flag?(:interpreted) %} + LibPCRE.free.call re.as(Void*) + {% end %} nil else "#{String.new(errptr)} at #{erroffset}" From 5fe3a6041397a0162cef99f12295d63a9caa34c9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 19 Sep 2022 19:00:53 +0800 Subject: [PATCH 0002/1551] `bin/crystal`: Ensure `sh` compatibility (#12486) Co-authored-by: Oleh Prypin --- bin/crystal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/crystal b/bin/crystal index 0468693b9986..a3f193983a61 100755 --- a/bin/crystal +++ b/bin/crystal @@ -101,7 +101,7 @@ remove_path_item() { path="$1" item="$2" - echo -n $path | awk -v RS=: -v ORS=: '$0 != "'$item'"' | sed 's/:$//' + printf "%s" "$path" | awk -v RS=: -v ORS=: '$0 != "'$item'"' | sed 's/:$//' } ############################################################################## From fe693510efba9a146a355b6f524a135d43fce8c8 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 19 Sep 2022 08:01:33 -0300 Subject: [PATCH 0003/1551] Compiler optimization: avoid intermediate array when matching call arg types (#12485) --- src/compiler/crystal/semantic/method_lookup.cr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/compiler/crystal/semantic/method_lookup.cr b/src/compiler/crystal/semantic/method_lookup.cr index 1fb5861f1360..c7f1e8087507 100644 --- a/src/compiler/crystal/semantic/method_lookup.cr +++ b/src/compiler/crystal/semantic/method_lookup.cr @@ -250,6 +250,12 @@ module Crystal match_arg_type = arg_type.restrict(arg, context) if match_arg_type + if !named_args && !splat_arg_types && match_arg_type.same?(arg_type) && arg_types.size == 1 + # Optimization: no need to create matched_arg_types if + # the call has a single argument and it exactly matches the restriction + break + end + matched_arg_types ||= [] of Type matched_arg_types.push match_arg_type mandatory_args[arg_index] = true if mandatory_args From 6c3305e7d9760a4bd35c0df5f28cf017bbbb4641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 19 Sep 2022 13:26:27 +0200 Subject: [PATCH 0004/1551] Replace deprecated `Socket.ip?` with `IPAddress.valid?` (#12489) --- src/openssl/ssl/hostname_validation.cr | 2 +- src/openssl/ssl/socket.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openssl/ssl/hostname_validation.cr b/src/openssl/ssl/hostname_validation.cr index ad77a63d0e20..9f09dfc9d4eb 100644 --- a/src/openssl/ssl/hostname_validation.cr +++ b/src/openssl/ssl/hostname_validation.cr @@ -135,7 +135,7 @@ module OpenSSL::SSL::HostnameValidation end # fail match when hostname is an IP address - if ::Socket.ip?(hostname) + if ::Socket::IPAddress.valid?(hostname) return false end diff --git a/src/openssl/ssl/socket.cr b/src/openssl/ssl/socket.cr index c0a3a6e5597b..742ad8f78eea 100644 --- a/src/openssl/ssl/socket.cr +++ b/src/openssl/ssl/socket.cr @@ -15,7 +15,7 @@ abstract class OpenSSL::SSL::Socket < IO {% if LibSSL.has_method?(:ssl_get0_param) %} param = LibSSL.ssl_get0_param(@ssl) - if ::Socket.ip?(hostname) + if ::Socket::IPAddress.valid?(hostname) unless LibCrypto.x509_verify_param_set1_ip_asc(param, hostname) == 1 raise OpenSSL::Error.new("X509_VERIFY_PARAM_set1_ip_asc") end From 9577ba726add29734efdd927e35eaeb7d2b8f26f Mon Sep 17 00:00:00 2001 From: "Billy.Zheng" Date: Tue, 20 Sep 2022 14:58:58 +0800 Subject: [PATCH 0005/1551] Fix Colorize doc example (#12492) --- src/colorize.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/colorize.cr b/src/colorize.cr index d9803ae5bdf5..429795087779 100644 --- a/src/colorize.cr +++ b/src/colorize.cr @@ -146,7 +146,7 @@ module Colorize # io = IO::Memory.new # Colorize.with.green.surround(io) do # io << "green" - # Colorize.reset + # Colorize.reset(io) # io << " default" # end # ``` From c7d90c562c700bd9d9d8e12cacfd5d5348cb43a9 Mon Sep 17 00:00:00 2001 From: "Billy.Zheng" Date: Tue, 20 Sep 2022 14:59:21 +0800 Subject: [PATCH 0006/1551] Fix docs error for File.match? `**` globbing pattern. (#12343) --- src/file.cr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/file.cr b/src/file.cr index 9e3a4a7e88cc..599228058435 100644 --- a/src/file.cr +++ b/src/file.cr @@ -412,7 +412,8 @@ class File < IO::FileDescriptor # * `"c*"` matches all files beginning with `c`. # * `"*c"` matches all files ending with `c`. # * `"*c*"` matches all files that have `c` in them (including at the beginning or end). - # * `**` matches an unlimited number of arbitrary characters including `/`. + # * `**` matches directories recursively if followed by `/`. + # If this path segment contains any other characters, it is the same as the usual `*`. # * `?` matches any one character excluding `/`. # * character sets: # * `[abc]` matches any one of these character. From 853233986fce1d553b393b6688d911729e5caa09 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Tue, 20 Sep 2022 02:59:43 -0400 Subject: [PATCH 0007/1551] Update contact section of CODE of CONDUCT (#9219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Beta Ziliani Co-authored-by: Johannes Müller --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index cfd619f88ac9..1197f026cc81 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,7 +55,7 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by opening an issue or contacting the project maintainers. All +reported by contacting the project maintainers in private or by sending an email to [crystal@manas.tech](mailto:crystal@manas.tech). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. From f0a33039073206fd6d5ed4936556eb8392671d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 20 Sep 2022 14:23:14 +0200 Subject: [PATCH 0008/1551] Update nixpkgs 22.05 and LLVM 11 (#12498) --- shell.nix | 16 ++++++++++------ spec/std/kernel_spec.cr | 4 ++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/shell.nix b/shell.nix index b03d3c7632d8..e550a46489c4 100644 --- a/shell.nix +++ b/shell.nix @@ -19,13 +19,13 @@ # $ nix-shell --pure --arg musl true # -{llvm ? 10, musl ? false, system ? builtins.currentSystem}: +{llvm ? 11, musl ? false, system ? builtins.currentSystem}: let nixpkgs = import (builtins.fetchTarball { - name = "nixpkgs-20.03"; - url = "https://github.com/NixOS/nixpkgs/archive/2d580cd2793a7b5f4b8b6b88fb2ccec700ee1ae6.tar.gz"; - sha256 = "1nbanzrir1y0yi2mv70h60sars9scwmm0hsxnify2ldpczir9n37"; + name = "nixpkgs-22.05"; + url = "https://github.com/NixOS/nixpkgs/archive/22.05.tar.gz"; + sha256 = "0d643wp3l77hv2pmg2fi7vyxn4rwy0iyr8djcw1h5x72315ck9ik"; }) { inherit system; }; @@ -65,6 +65,10 @@ let pkgconfig = pkgs.pkgconfig; llvm_suite = ({ + llvm_11 = { + llvm = pkgs.llvm_11; + extra = [ pkgs.lld_11 pkgs.lldb_11 ]; + }; llvm_10 = { llvm = pkgs.llvm_10; extra = [ pkgs.lld_10 pkgs.lldb_10 ]; @@ -108,7 +112,7 @@ let stdLibDeps = with pkgs; [ boehmgc gmp libevent libiconv libxml2 libyaml openssl pcre zlib - ] ++ stdenv.lib.optionals stdenv.isDarwin [ libiconv ]; + ] ++ lib.optionals stdenv.isDarwin [ libiconv ]; tools = [ pkgs.hostname pkgs.git llvm_suite.extra ]; in @@ -123,7 +127,7 @@ pkgs.stdenv.mkDerivation rec { pkgs.libffi ]; - LLVM_CONFIG = "${llvm_suite.llvm}/bin/llvm-config"; + LLVM_CONFIG = "${llvm_suite.llvm.dev}/bin/llvm-config"; MACOSX_DEPLOYMENT_TARGET = "10.11"; } diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index aac14fb58351..d79c816f5ff4 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -4,6 +4,10 @@ require "./spec_helper" describe "PROGRAM_NAME" do it "works for UTF-8 name" do with_tempfile("source_file") do |source_file| + if ENV["IN_NIX_SHELL"]? + pending! "Example is broken in Nix shell (#12332)" + end + File.write(source_file, "File.basename(PROGRAM_NAME).inspect(STDOUT)") compile_file(source_file, bin_name: "×‽😂") do |executable_file| From 292661eb7b110e17954bc12461a281fe970b62b0 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 21 Sep 2022 05:07:45 +1000 Subject: [PATCH 0009/1551] Fix: Parse DWARF5 Data16 values (#12497) --- src/crystal/dwarf/info.cr | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/crystal/dwarf/info.cr b/src/crystal/dwarf/info.cr index 71b00fe94ec3..884236a1286b 100644 --- a/src/crystal/dwarf/info.cr +++ b/src/crystal/dwarf/info.cr @@ -48,7 +48,7 @@ module Crystal end end - alias Value = Bool | Int32 | Int64 | Slice(UInt8) | String | UInt16 | UInt32 | UInt64 | UInt8 + alias Value = Bool | Int32 | Int64 | Slice(UInt8) | String | UInt16 | UInt32 | UInt64 | UInt8 | UInt128 def read_abbreviations(io) @abbreviations = Abbrev.read(io, debug_abbrev_offset) @@ -106,6 +106,8 @@ module Crystal @io.read_bytes(UInt32) when FORM::Data8 @io.read_bytes(UInt64) + when FORM::Data16 + @io.read_bytes(UInt128) when FORM::Sdata DWARF.read_signed_leb128(@io) when FORM::Udata From c3d5e91002dca8ba0bf8810745ec3efc65d92185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 20 Sep 2022 21:08:24 +0200 Subject: [PATCH 0010/1551] [Makefile] Use `EXPORT_CC` for `make crystal` (#11760) --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index db6ebdd10977..3b648a8f96ff 100644 --- a/Makefile +++ b/Makefile @@ -37,11 +37,15 @@ CRYSTAL_CONFIG_LIBRARY_PATH := '$$ORIGIN/../lib/crystal' CRYSTAL_CONFIG_BUILD_COMMIT := $(shell git rev-parse --short HEAD 2> /dev/null) CRYSTAL_CONFIG_PATH := '$$ORIGIN/../share/crystal/src' SOURCE_DATE_EPOCH := $(shell (git show -s --format=%ct HEAD || stat -c "%Y" Makefile || stat -f "%m" Makefile) 2> /dev/null) +ifeq ($(shell command -v ld.lld >/dev/null && uname -s),Linux) + EXPORT_CC ?= CC="$(CC) -fuse-ld=lld" +endif EXPORTS := \ CRYSTAL_CONFIG_BUILD_COMMIT="$(CRYSTAL_CONFIG_BUILD_COMMIT)" \ CRYSTAL_CONFIG_PATH=$(CRYSTAL_CONFIG_PATH) \ SOURCE_DATE_EPOCH="$(SOURCE_DATE_EPOCH)" EXPORTS_BUILD := \ + $(EXPORT_CC) \ CRYSTAL_CONFIG_LIBRARY_PATH=$(CRYSTAL_CONFIG_LIBRARY_PATH) SHELL = sh LLVM_CONFIG := $(shell src/llvm/ext/find-llvm-config) @@ -60,10 +64,6 @@ LIBDIR ?= $(DESTDIR)$(PREFIX)/lib DATADIR ?= $(DESTDIR)$(PREFIX)/share/crystal INSTALL ?= /usr/bin/install -ifeq ($(shell command -v ld.lld >/dev/null && uname -s),Linux) - EXPORT_CC ?= CC="$(CC) -fuse-ld=lld" -endif - ifeq ($(or $(TERM),$(TERM),dumb),dumb) colorize = $(shell printf >&2 "$1") else From 24360482d20dfd955fb03b9f1305556f7ee0000d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 20 Sep 2022 21:08:54 +0200 Subject: [PATCH 0011/1551] Update distribution-scripts (#12502) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0d3d46508f3f..ead891c0a2ee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "e9cafa1a93a487169e561759e9a2d065c35e0770" + default: "1ee8983d46c594f97b6fe70a711c5dbefe83ab45" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From a9ab64f00e6aaacfabb7183de43a7d42a2f91c86 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 21 Sep 2022 12:22:35 -0300 Subject: [PATCH 0012/1551] Compiler: better error message for symbol against enum (#12478) Co-authored-by: Sijawusz Pur Rahnama --- spec/compiler/semantic/automatic_cast_spec.cr | 2 +- spec/compiler/semantic/call_error_spec.cr | 50 +++++++++++++++++++ src/compiler/crystal/semantic/call_error.cr | 34 +++++++++++-- 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/spec/compiler/semantic/automatic_cast_spec.cr b/spec/compiler/semantic/automatic_cast_spec.cr index 28e97f7421c3..19273cce90b3 100644 --- a/spec/compiler/semantic/automatic_cast_spec.cr +++ b/spec/compiler/semantic/automatic_cast_spec.cr @@ -208,7 +208,7 @@ describe "Semantic: automatic cast" do foo(:four) ), - "expected argument #1 to 'foo' to be Foo, not Symbol" + "expected argument #1 to 'foo' to match a member of enum Foo" end it "says ambiguous call for symbol" do diff --git a/spec/compiler/semantic/call_error_spec.cr b/spec/compiler/semantic/call_error_spec.cr index 78450742acb6..9c4040671dbd 100644 --- a/spec/compiler/semantic/call_error_spec.cr +++ b/spec/compiler/semantic/call_error_spec.cr @@ -204,4 +204,54 @@ describe "Call errors" do ), "expected argument #1 to 'foo' to be Char or Int32, not String" end + + it "says type mismatch for symbol against enum (did you mean)" do + assert_error %( + enum Color + Red + Green + Blue + end + + def foo(x : Color) + end + + foo(:rred) + ), + "expected argument #1 to 'foo' to match a member of enum Color.\n\nDid you mean :red?" + end + + it "says type mismatch for symbol against enum (list all possibilities when 10 or less)" do + assert_error %( + enum Color + Red + Green + Blue + Violet + Purple + end + + def foo(x : Color) + end + + foo(:hello_world) + ), + "expected argument #1 to 'foo' to match a member of enum Color.\n\nOptions are: :red, :green, :blue, :violet and :purple" + end + + it "says type mismatch for symbol against enum, named argument case" do + assert_error %( + enum Color + Red + Green + Blue + end + + def foo(x : Color) + end + + foo(x: :rred) + ), + "expected argument 'x' to 'foo' to match a member of enum Color.\n\nDid you mean :red?" + end end diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index ef840be2d25d..de8ccac18b1e 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -288,6 +288,17 @@ class Crystal::Call named_args.try &.find(&.name.==(index_or_name)) end + enum_types = expected_types.select(EnumType) + if actual_type.is_a?(SymbolType) && enum_types.size == 1 + enum_type = enum_types.first + + if arg.is_a?(SymbolLiteral) + symbol = arg.value + elsif arg.is_a?(NamedArgument) && (named_arg_value = arg.value).is_a?(SymbolLiteral) + symbol = named_arg_value.value + end + end + raise_no_overload_matches(arg || self, defs, arg_types, inner_exception) do |str| argument_description = case index_or_name @@ -297,9 +308,24 @@ class Crystal::Call "'#{index_or_name}'" end - str << "expected argument #{argument_description} to '#{full_name(owner, def_name)}' to be " - to_sentence(str, expected_types, " or ") - str << ", not #{actual_type}" + if symbol && enum_type + str << "expected argument #{argument_description} to '#{full_name(owner, def_name)}' to match a member of enum #{enum_type}." + str.puts + str.puts + + options = enum_type.types.map(&.[1].name.underscore) + similar_name = Levenshtein.find(symbol.underscore, options) + if similar_name + str << "Did you mean :#{similar_name}?" + elsif options.size <= 10 + str << "Options are: " + to_sentence(str, options.map { |o| ":#{o}" }, " and ") + end + else + str << "expected argument #{argument_description} to '#{full_name(owner, def_name)}' to be " + to_sentence(str, expected_types, " or ") + str << ", not #{actual_type}" + end end end @@ -449,7 +475,7 @@ class Crystal::Call arguments_type_mismatch << ArgumentTypeMismatch.new( index_or_name: index_or_name, expected_type: expected_type, - actual_type: arg_type, + actual_type: arg_type.remove_literal, ) end end From 7f7442e6721d985e0cd2a6bebba08ab13f38c734 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 21 Sep 2022 12:22:59 -0300 Subject: [PATCH 0013/1551] Formatter: format comment after select (#12506) --- spec/compiler/formatter/formatter_spec.cr | 16 ++++++++++++++++ src/compiler/crystal/tools/formatter.cr | 3 +-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 26c3b8e12811..62081303d287 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -2209,4 +2209,20 @@ describe Crystal::Formatter do # comment end CODE + + # #12493 + assert_format <<-CODE + select + # when foo + when bar + break + end + CODE + + assert_format <<-CODE + select # some comment + when bar + break + end + CODE end diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index eef9ac5364d1..1722dcad9c36 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -3843,9 +3843,8 @@ module Crystal def visit(node : Select) slash_is_regex! write_keyword :select + skip_space_write_line skip_space_or_newline - skip_semicolon - write_line node.whens.each do |a_when| needs_indent = false From bc26852532e010eeb07321b44e700f14c86fe5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 21 Sep 2022 17:23:31 +0200 Subject: [PATCH 0014/1551] Fix and enhance `scripts/update-distribution-scripts.sh` (#12503) Co-authored-by: Oleh Prypin --- scripts/update-distribution-scripts.sh | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/scripts/update-distribution-scripts.sh b/scripts/update-distribution-scripts.sh index e8a298fc79f5..a0aa96deff90 100755 --- a/scripts/update-distribution-scripts.sh +++ b/scripts/update-distribution-scripts.sh @@ -5,7 +5,11 @@ # # Usage: # -# scripts/update-distribution_scripts.sh [REF] +# scripts/update-distribution_scripts.sh [REF [BRANCH]] +# +# Parameters: +# * REF: Git commit SHA in distribution-scripts (default: HEAD) +# * BRANCH: Branch name for CI branch in crystal (default: ci/update-distribution-scripts) # # Requirements: # * packages: git gh sed @@ -20,13 +24,13 @@ GIT_DS="git --git-dir=$DISTRIBUTION_SCRIPTS_WORK_DIR" $GIT_DS fetch origin master -if [ -z "${1:-}"]; then +if [ "${1:-"HEAD"}" = "HEAD" ]; then reference=$($GIT_DS rev-list origin/master | head -1) else reference=${1} fi -branch="ci/update-distribution-scripts" +branch="${2:-"ci/update-distribution-scripts"}" git switch -C "$branch" master @@ -38,8 +42,8 @@ sed -i -E "/distribution-scripts-version:/{n;n;n;s/default: \".*\"/default: \"$r git add .circleci/config.yml message="Updates \`distribution-scripts\` dependency to https://github.com/crystal-lang/distribution-scripts/commit/$reference" -log=$($GIT_DS log $old_reference..$reference --format="%s" | sed "s/.*(/\* crystal-lang\/distribution-scripts/;s/.$//") -message="$message\n\nThis includes the following changes:\n\n$log" +log=$($GIT_DS log $old_reference..$reference --format="%s" | sed "s/.*(/crystal-lang\/distribution-scripts/;s/^/* /;s/.$//") +message=$(printf "%s\n\nThis includes the following changes:\n\n%s" "$message" "$log") git commit -m "Update distribution-scripts" -m "$message" @@ -47,6 +51,10 @@ git show git push -u upstream "$branch" +# Confirm creating pull request +echo "Create pull request for branch $branch? [y/N]" +read -r REPLY -# Create pull request -gh pr create -R crystal-lang/crystal --fill --label "topic:infrastructure" --assignee "@me" +if [ "$REPLY" = "y" ]; then + gh pr create -R crystal-lang/crystal --fill --label "topic:infrastructure" --assignee "@me" +fi From fabf79dd59adf383eaa3a220e04024de7a80d54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 22 Sep 2022 09:34:57 +0200 Subject: [PATCH 0015/1551] [CI] Upgrade GitHub Actions to macos-11 (#12500) * [CI] Update GHA cachix/install-nix-action@v17 * [CI] Upgrade GitHub Actions to macos-11 --- .github/workflows/macos.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f32c40f43191..a89fe2e44e17 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -8,14 +8,14 @@ env: jobs: x86_64-darwin-test: - runs-on: macos-10.15 + runs-on: macos-11 steps: - name: Download Crystal source uses: actions/checkout@v2 - - uses: cachix/install-nix-action@v14 + - uses: cachix/install-nix-action@v17 with: - install_url: https://releases.nixos.org/nix/nix-2.4/install + install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | experimental-features = nix-command - uses: cachix/cachix-action@v10 From 282e6e257e57f99143435ab39e5aefd3c07fc761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 22 Sep 2022 09:37:17 +0200 Subject: [PATCH 0016/1551] Introduce `IO::DEFAULT_BUFFER_SIZE` (#12507) * Refactor HTTP buffer spec decoupling from buffer size * Introduce `IO::DEFAULT_BUFFER_SIZE` * Increase `IO::DEFAULT_BUFFER_SIZE` to 32K --- spec/std/http/server/response_spec.cr | 10 ++++++---- spec/std/socket/unix_socket_spec.cr | 2 +- src/io.cr | 15 +++++++++------ src/io/buffered.cr | 2 +- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/spec/std/http/server/response_spec.cr b/spec/std/http/server/response_spec.cr index 803d6558aae3..a78a1720d7f5 100644 --- a/spec/std/http/server/response_spec.cr +++ b/spec/std/http/server/response_spec.cr @@ -96,13 +96,15 @@ describe HTTP::Server::Response do io = IO::Memory.new response = Response.new(io) str = "1234567890" - 1000.times do + slices = (IO::DEFAULT_BUFFER_SIZE // 10) + slices.times do response.print(str) end + response.print(str) response.close - first_chunk = str * 819 - second_chunk = str * 181 - io.to_s.should eq("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n1ffe\r\n#{first_chunk}\r\n712\r\n#{second_chunk}\r\n0\r\n\r\n") + first_chunk = str * slices + second_chunk = str + io.to_s.should eq("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n#{(first_chunk.bytesize).to_s(16)}\r\n#{first_chunk}\r\n#{(second_chunk.bytesize).to_s(16)}\r\n#{second_chunk}\r\n0\r\n\r\n") end it "prints with content length" do diff --git a/spec/std/socket/unix_socket_spec.cr b/spec/std/socket/unix_socket_spec.cr index d48769c318b2..700308c1c248 100644 --- a/spec/std/socket/unix_socket_spec.cr +++ b/spec/std/socket/unix_socket_spec.cr @@ -76,7 +76,7 @@ describe UNIXSocket do # BUG: shrink the socket buffers first left.write_timeout = 0.0001 right.read_timeout = 0.0001 - buf = ("a" * 4096).to_slice + buf = ("a" * IO::DEFAULT_BUFFER_SIZE).to_slice expect_raises(IO::TimeoutError, "Write timed out") do loop { left.write buf } diff --git a/src/io.cr b/src/io.cr index 782f0eb31b5a..54162972af1f 100644 --- a/src/io.cr +++ b/src/io.cr @@ -58,6 +58,9 @@ require "c/errno" # avoided, as string operations might need to read extra bytes in order to get characters # in the given encoding. abstract class IO + # Default size used for generic stream buffers. + DEFAULT_BUFFER_SIZE = 32768 + # Argument to a `seek` operation. enum Seek # Seeks to an absolute location @@ -558,7 +561,7 @@ abstract class IO decoder.write(str) end else - buffer = uninitialized UInt8[4096] + buffer = uninitialized UInt8[DEFAULT_BUFFER_SIZE] while (read_bytes = read(buffer.to_slice)) > 0 str.write buffer.to_slice[0, read_bytes] end @@ -848,9 +851,9 @@ abstract class IO # io.skip(1) # raises IO::EOFError # ``` def skip(bytes_count : Int) : Nil - buffer = uninitialized UInt8[4096] + buffer = uninitialized UInt8[DEFAULT_BUFFER_SIZE] while bytes_count > 0 - read_count = read(buffer.to_slice[0, Math.min(bytes_count, 4096)]) + read_count = read(buffer.to_slice[0, Math.min(bytes_count, buffer.size)]) raise IO::EOFError.new if read_count == 0 bytes_count -= read_count @@ -860,7 +863,7 @@ abstract class IO # Reads and discards bytes from `self` until there # are no more bytes. def skip_to_end : Nil - buffer = uninitialized UInt8[4096] + buffer = uninitialized UInt8[DEFAULT_BUFFER_SIZE] while read(buffer.to_slice) > 0 end end @@ -1152,7 +1155,7 @@ abstract class IO # io2.to_s # => "hello" # ``` def self.copy(src, dst) : Int64 - buffer = uninitialized UInt8[4096] + buffer = uninitialized UInt8[DEFAULT_BUFFER_SIZE] count = 0_i64 while (len = src.read(buffer.to_slice).to_i32) > 0 dst.write buffer.to_slice[0, len] @@ -1176,7 +1179,7 @@ abstract class IO limit = limit.to_i64 - buffer = uninitialized UInt8[4096] + buffer = uninitialized UInt8[DEFAULT_BUFFER_SIZE] remaining = limit while (len = src.read(buffer.to_slice[0, Math.min(buffer.size, Math.max(remaining, 0))])) > 0 dst.write buffer.to_slice[0, len] diff --git a/src/io/buffered.cr b/src/io/buffered.cr index 9e6294efef67..bb5a0bb35fe3 100644 --- a/src/io/buffered.cr +++ b/src/io/buffered.cr @@ -11,7 +11,7 @@ module IO::Buffered @sync = false @read_buffering = true @flush_on_newline = false - @buffer_size = 8192 + @buffer_size = IO::DEFAULT_BUFFER_SIZE # Reads at most *slice.size* bytes from the wrapped `IO` into *slice*. # Returns the number of bytes read. From 764d7c050126f8f34cc62f82003f6c289c27bda0 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 22 Sep 2022 02:37:59 -0500 Subject: [PATCH 0017/1551] Add support for `IO::FileDescriptor` staying open on finalize (#12367) * Test IO::FileDescriptor close on finalize * Add support for IO::FileDescriptor staying open on finalize This is particularly useful for APIs where you're given a file descriptor that you don't actually "own". --- spec/std/io/file_descriptor_spec.cr | 34 +++++++++++++++++++++++++++++ src/io/file_descriptor.cr | 11 ++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/spec/std/io/file_descriptor_spec.cr b/spec/std/io/file_descriptor_spec.cr index b1ce0c33cdfd..d6fd920dfe30 100644 --- a/spec/std/io/file_descriptor_spec.cr +++ b/spec/std/io/file_descriptor_spec.cr @@ -1,4 +1,9 @@ require "../spec_helper" +require "../../support/finalize" + +class IO::FileDescriptor + include FinalizeCounter +end describe IO::FileDescriptor do pending_win32 "reopen STDIN with the right mode" do @@ -8,4 +13,33 @@ describe IO::FileDescriptor do `echo "" | #{Process.quote(binpath)}`.chomp.should eq("false Pipe") end end + + it "closes on finalize" do + pipes = [] of IO::FileDescriptor + assert_finalizes("fd") do + a, b = IO.pipe + pipes << b + a + end + + expect_raises(IO::Error) do + pipes.each do |p| + p.puts "123" + end + end + end + + it "does not close if close_on_finalize is false" do + pipes = [] of IO::FileDescriptor + assert_finalizes("fd") do + a, b = IO.pipe + a.close_on_finalize = false + pipes << b + a + end + + pipes.each do |p| + p.puts "123" + end + end end diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 444c5a20bbc5..6da5b859f77b 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -11,7 +11,14 @@ class IO::FileDescriptor < IO @volatile_fd.get end - def initialize(fd, blocking = nil) + # Whether or not to close the file descriptor when this object is finalized. + # Disabling this is useful in order to create an IO wrapper over a file + # descriptor returned from a C API that keeps ownership of the descriptor. Do + # note that, if the fd is closed by its owner at any point, any IO operations + # will then fail. + property? close_on_finalize : Bool + + def initialize(fd, blocking = nil, *, @close_on_finalize = true) @volatile_fd = Atomic.new(fd) @closed = system_closed? @@ -206,7 +213,7 @@ class IO::FileDescriptor < IO end def finalize - return if closed? + return if closed? || !close_on_finalize? close rescue nil end From bd6dca1de1dc90edc97a77c7969ff6104007394c Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 22 Sep 2022 10:31:09 -0300 Subject: [PATCH 0018/1551] Parser: declare local vars of indirect type declarations in call args (#11983) --- spec/compiler/parser/parser_spec.cr | 30 +++++++++++++++++++++++ src/compiler/crystal/syntax/parser.cr | 34 +++++++++++++++++---------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index d454d213da23..b2a0a200646a 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -617,6 +617,36 @@ module Crystal it_parses "x : *T -> R", TypeDeclaration.new("x".var, ProcNotation.new(["T".path.splat] of ASTNode, "R".path)) it_parses "def foo(x : *T -> R); end", Def.new("foo", args: [Arg.new("x", restriction: ProcNotation.new(["T".path.splat] of ASTNode, "R".path))]) + it_parses "foo result : Int32; result", Expressions.new([ + Call.new(nil, "foo", TypeDeclaration.new("result".var, "Int32".path)), + Call.new(nil, "result"), + ] of ASTNode) + + it_parses "foo(x: result : Int32); result", Expressions.new([ + Call.new(nil, "foo", named_args: [NamedArgument.new("x", TypeDeclaration.new("result".var, "Int32".path))]), + Call.new(nil, "result"), + ] of ASTNode) + + it_parses "foo( + begin + result : Int32 = 1 + result + end + )", Call.new(nil, "foo", Expressions.new([ + TypeDeclaration.new("result".var, "Int32".path, 1.int32), + "result".var, + ] of ASTNode)) + + it_parses "foo(x: + begin + result : Int32 = 1 + result + end + )", Call.new(nil, "foo", named_args: [NamedArgument.new("x", Expressions.new([ + TypeDeclaration.new("result".var, "Int32".path, 1.int32), + "result".var, + ] of ASTNode))]) + it_parses "struct Foo; end", ClassDef.new("Foo".path, struct: true) it_parses "Foo()", Generic.new("Foo".path, [] of ASTNode) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index ead4e2c9ed5d..216aaa698d46 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -36,7 +36,11 @@ module Crystal @def_nest = 0 @fun_nest = 0 @type_nest = 0 - @call_args_nest = 0 + + # Keeps track of current call args starting locations, + # so if we parse a type declaration exactly at those points we + # know we don't need to declare those as local variables in those scopes. + @call_args_start_locations = [] of Location @temp_arg_count = 0 @in_macro_expression = false @stop_on_yield = 0 @@ -4250,7 +4254,11 @@ module Crystal else if @no_type_declaration == 0 && @token.type.op_colon? declare_var = parse_type_declaration(Var.new(name).at(location)) - push_var declare_var if @call_args_nest == 0 + + # Don't declare a local variable if it happens directly as an argument + # of a call, like `property foo : Int32` (we don't want `foo` to be a + # local variable afterwards.) + push_var declare_var unless @call_args_start_locations.includes?(location) declare_var elsif (!force_call && is_var) if @block_arg_name && !@uses_block_arg && name == @block_arg_name @@ -4466,8 +4474,6 @@ module Crystal has_parentheses : Bool def parse_call_args(stop_on_do_after_space = false, allow_curly = false, control = false) - @call_args_nest += 1 - case @token.type when .op_lcurly? nil @@ -4534,16 +4540,10 @@ module Crystal else nil end - ensure - @call_args_nest -= 1 end def parse_call_args_space_consumed(check_plus_and_minus = true, allow_curly = false, control = false, end_token : Token::Kind = :OP_RPAREN, allow_beginless_range = false) - # This method is called by `parse_call_args`, so it increments once too much in this case. - # But it is no problem, because it decrements once too much. - @call_args_nest += 1 - if @token.keyword?(:end) && !next_comes_colon_space? return nil end @@ -4636,8 +4636,6 @@ module Crystal end CallArgs.new args, nil, nil, nil, false, end_location, has_parentheses: false - ensure - @call_args_nest -= 1 end def parse_call_args_named_args(location, args, first_name, allow_newline) @@ -4690,7 +4688,13 @@ module Crystal if @token.keyword?(:out) value = parse_out else - value = parse_op_assign + @call_args_start_locations << @token.location + value = + begin + parse_op_assign + ensure + @call_args_start_locations.pop + end end named_args << NamedArgument.new(name, value).at(location) @@ -4713,6 +4717,8 @@ module Crystal end def parse_call_arg(found_double_splat = false) + @call_args_start_locations.push @token.location + if @token.keyword?(:out) if found_double_splat raise "out argument not allowed after double splat" @@ -4757,6 +4763,8 @@ module Crystal arg end + ensure + @call_args_start_locations.pop end def parse_out From 7501dfdc80674f256c5dcf5d5d14e4dd651bbe29 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 22 Sep 2022 21:31:40 +0800 Subject: [PATCH 0019/1551] Use more robust ordering between def overloads (#10711) --- spec/compiler/semantic/def_overload_spec.cr | 527 ++++++++++++++++++ spec/compiler/semantic/double_splat_spec.cr | 56 -- spec/compiler/semantic/splat_spec.cr | 30 +- src/compiler/crystal/semantic/ast.cr | 11 +- src/compiler/crystal/semantic/restrictions.cr | 341 +++++++++++- src/compiler/crystal/tools/doc/method.cr | 3 +- src/compiler/crystal/types.cr | 27 +- 7 files changed, 867 insertions(+), 128 deletions(-) diff --git a/spec/compiler/semantic/def_overload_spec.cr b/spec/compiler/semantic/def_overload_spec.cr index aa902b3ed02b..21e034787b92 100644 --- a/spec/compiler/semantic/def_overload_spec.cr +++ b/spec/compiler/semantic/def_overload_spec.cr @@ -1,6 +1,533 @@ require "../../spec_helper" +def assert_stricter(params1, params2, args, *, file = __FILE__, line = __LINE__) + assert_type(<<-CR, file: file, line: line, flags: "preview_overload_order") { tuple_of([int32, int32]) } + def foo(#{params1}); 1; end + def foo(#{params2}); 'x'; end + + def bar(#{params2}); 'x'; end + def bar(#{params1}); 1; end + + a = foo(#{args}) + b = bar(#{args}) + {a, b} + CR +end + +def assert_unordered(params1, params2, args, *, file = __FILE__, line = __LINE__) + assert_type(<<-CR, file: file, line: line, flags: "preview_overload_order") { tuple_of([int32, int32]) } + def foo(#{params1}); 1; end + def foo(#{params2}); 'x'; end + + def bar(#{params2}); 1; end + def bar(#{params1}); 'x'; end + + a = foo(#{args}) + b = bar(#{args}) + {a, b} + CR +end + describe "Semantic: def overload" do + describe "compare_strictness" do + context "positional parameters" do + it "specificity" do + signatures = [ + {2, 2, "x0, x1"}, + {2, 3, "x0, x1, x2 = 0"}, + {2, 4, "x0, x1, x2 = 0, x3 = 0"}, + {2, 9, "x0, x1, x2 = 0, x3 = 0, *xs"}, + {2, 9, "x0, x1, x2 = 0, *xs"}, + {2, 9, "x0, x1, *xs"}, + {1, 1, "x0"}, # incompatible with 6 defs above + {1, 2, "x0, x1 = 0"}, + {1, 3, "x0, x1 = 0, x2 = 0"}, + {1, 9, "x0, x1 = 0, x2 = 0, *xs"}, + {1, 9, "x0, x1 = 0, *xs"}, + {1, 9, "x0, *xs"}, + {0, 0, ""}, # incompatible with 12 defs above + {0, 1, "x0 = 0"}, # incompatible with 6 defs above + {0, 2, "x0 = 0, x1 = 0"}, + {0, 9, "x0 = 0, x1 = 0, *xs"}, + {0, 9, "x0 = 0, *xs"}, + {0, 9, "*xs"}, + ] + + signatures.each_combination(2, reuse: true) do |(x, y)| + min_count1, max_count1, params1 = x + min_count2, max_count2, params2 = y + next if min_count1 > max_count2 || min_count2 > max_count1 + args = Array.new({min_count1, min_count2}.max) { "0" }.join(", ") + + assert_stricter(params1, params2, args) + end + end + + it "single splat vs single splat with restriction (#3134)" do + assert_stricter( + "*args : Int32", + "*args", + "1") + end + + it "single splat restriction vs single splat with stricter restriction" do + assert_stricter( + "*args : Int32", + "*args : Int", + "1") + end + + it "positional parameter with restriction vs single splat" do + assert_stricter( + "x : Int32", + "*args", + "1") + + assert_stricter( + "x : Int32 = 0", + "*args", + "") + end + + it "positional parameter vs single splat with restriction" do + assert_stricter( + "*args : Int32", + "x", + "1") + end + + it "positional parameter with stricter restriction vs single splat with restriction" do + assert_stricter( + "x : Int32", + "*args : Int", + "1") + end + + it "positional parameter with restriction vs single splat with stricter restriction" do + assert_stricter( + "*args : Int32", + "x : Int", + "1") + end + end + + context "named parameters" do + it "specificity" do + signatures = [ + {1, 1, "*, n"}, + {1, 9, "*, n, **ns"}, + {0, 0, ""}, + {0, 1, "*, n = 0"}, + {0, 9, "*, n = 0, **ns"}, + {0, 9, "**ns"}, + ] + + signatures.each_combination(2, reuse: true) do |(x, y)| + min_count1, max_count1, params1 = x + min_count2, max_count2, params2 = y + next if min_count1 > max_count2 || min_count2 > max_count1 + args = Array.new({min_count1, min_count2}.max) { "n: 0" }.join(", ") + + assert_stricter(params1, params2, args) + end + end + + it "double splat vs double splat with restriction" do + assert_stricter( + "**args : Int32", + "**args", + "x: 1") + end + + it "double splat restriction vs double splat with stricter restriction" do + assert_stricter( + "**args : Int32", + "**args : Int", + "x: 1") + end + + it "named parameter with restriction vs double splat (#5328)" do + assert_stricter( + "*, x : Int32", + "**opts", + "x: 1") + + assert_stricter( + "*, x : Int32 = 0", + "**opts", + "") + end + + it "named parameter vs double splat with restriction" do + assert_stricter( + "**opts : Int32", + "*, x", + "x: 1") + end + + it "named parameter with stricter restriction vs double splat with restriction" do + assert_stricter( + "*, x : Int32", + "**opts : Int", + "x: 1") + end + + it "named parameter with restriction vs double splat with stricter restriction" do + assert_stricter( + "**opts : Int32", + "*, x : Int", + "x: 1") + end + end + + context "subsumption conflicts" do + it "positional vs positional" do + assert_unordered( + "x : Int32, y : Int", + "x : Int, y : Int32", + "1, 2") + end + + it "positional vs single splat" do + assert_unordered( + "x : Int32, *args : Int", + "x : Int, *args : Int32", + "1, 2, 3, 4") + + assert_unordered( + "x : Int32, *args : Number", + "*args : Int", + "1, 2, 3, 4") + end + + it "positional vs named" do + assert_unordered( + "x : Int32, *, y : Int", + "x : Int, *, y : Int32", + "1, y: 2") + end + + it "positional vs double splat" do + assert_unordered( + "x : Int32, **opts : Int", + "x : Int, **opts : Int32", + "1, y: 2, z: 3, w: 4") + end + + it "single splat vs named" do + assert_unordered( + "*args : Int32, y : Int", + "*args : Int, y : Int32", + "1, 2, 3, y: 4") + end + + it "single splat vs double splat" do + assert_unordered( + "*args : Int32, **opts : Int", + "*args : Int, **opts : Int32", + "1, 2, 3, y: 4, z: 5, w: 6") + end + + it "named vs named" do + assert_unordered( + "*, x : Int32, y : Int", + "*, x : Int, y : Int32", + "x: 1, y: 2") + end + + it "named vs double splat" do + assert_unordered( + "*, x : Int32, **opts : Int", + "*, x : Int, **opts : Int32", + "x: 1, y: 2, z: 3, w: 4") + + assert_unordered( + "*, x : Int32, **opts : Number", + "**opts : Int", + "x: 1, y: 2, z: 3, w: 4") + end + end + + context "subsumption has higher precedence over specificity" do + it "same positional parameter, required > optional" do + assert_stricter( + "x : Int32 = 0", + "x : Int", + "1") + end + + it "same positional parameter, required > single splat" do + assert_stricter( + "*x : Int32", + "x : Int", + "1") + end + + it "same positional parameter, optional > single splat" do + assert_stricter( + "*x : Int32", + "x : Int = 0", + "1") + end + + it "positional vs (required positional > optional positional)" do + assert_stricter( + "x : Int32, y = 0", + "x : Int, y", + "1, 2") + end + + it "positional vs (required positional > single splat)" do + assert_stricter( + "x : Int32, *args", + "x : Int, y", + "1, 2") + end + + it "positional vs (optional positional > single splat)" do + assert_stricter( + "x : Int32, *args", + "x : Int, y = 0", + "1, 2") + end + + it "positional vs (required named > optional named)" do + assert_stricter( + "x : Int32, *, y = 0", + "x : Int, *, y", + "1, y: 2") + end + + it "positional vs (required named > double splat)" do + assert_stricter( + "x : Int32, **opts", + "x : Int, *, y", + "1, y: 2") + end + + it "positional vs (optional named > double splat)" do + assert_stricter( + "x : Int32, **opts", + "x : Int, *, y = 0", + "1, y: 2") + end + + it "single splat vs (required named > optional named)" do + assert_stricter( + "*args : Int32, y = 0", + "*args : Int, y", + "1, 2, 3, y: 4") + end + + it "single splat vs (required named > double splat)" do + assert_stricter( + "*args : Int32, **opts", + "*args : Int, y", + "1, 2, 3, y: 4") + end + + it "single splat vs (optional named > double splat)" do + assert_stricter( + "*args : Int32, **opts", + "*args : Int, y = 0", + "1, 2, 3, y: 4") + end + + it "same named parameter, required > optional" do + assert_stricter( + "*, x : Int32 = 0", + "*, x : Int", + "x: 1") + end + + it "same named parameter, required > double splat" do + assert_stricter( + "**opts : Int32", + "*, x : Int", + "x: 1") + end + + it "same named parameter, optional > double splat" do + assert_stricter( + "**opts : Int32", + "*, x : Int = 0", + "x: 1") + end + + it "named vs (required positional > optional positional)" do + assert_stricter( + "x = 0, *, y : Int32", + "x, *, y : Int", + "1, y: 2") + end + + it "named vs (required positional > single splat)" do + assert_stricter( + "*args, y : Int32", + "x, *, y : Int", + "1, y: 2") + end + + it "named vs (optional positional > single splat)" do + assert_stricter( + "*args, y : Int32", + "x = 0, *, y : Int", + "1, y: 2") + end + + it "named vs (required named > optional named)" do + assert_stricter( + "*, x : Int32, y = 0", + "*, x : Int, y", + "x: 1, y: 2") + end + + it "named vs (required named > double splat)" do + assert_stricter( + "*, x : Int32, **opts", + "*, x : Int, y", + "x: 1, y: 2") + end + + it "named vs (optional named > double splat)" do + assert_stricter( + "*, x : Int32, **opts", + "*, x : Int, y = 0", + "x: 1, y: 2") + end + + it "double splat vs (required positional > optional positional)" do + assert_stricter( + "x = 0, **opts : Int32", + "x, **opts : Int", + "1, y: 2, z: 3, w: 4") + end + + it "double splat vs (required positional > single splat)" do + assert_stricter( + "*args, **opts : Int32", + "x, **opts : Int", + "1, y: 2, z: 3, w: 4") + end + + it "double splat vs (optional positional > single splat)" do + assert_stricter( + "*args, **opts : Int32", + "x = 0, **opts : Int", + "1, y: 2, z: 3, w: 4") + end + end + + context "specificity conflicts, positional vs named" do + it "(required > optional) vs (required > optional)" do + assert_unordered( + "x, *, y = 0", + "x = 0, *, y", + "1, y: 2") + end + + it "(required > optional) vs (required > splat)" do + assert_unordered( + "x, **opts", + "x = 0, *, y", + "1, y: 2") + end + + it "(required > optional) vs (optional > splat)" do + assert_unordered( + "x, **opts", + "x = 0, *, y = 0", + "1, y: 2") + end + + it "(required > splat) vs (required > optional)" do + assert_unordered( + "x, *, y = 0", + "*x, y", + "1, y: 2") + end + + it "(required > splat) vs (required > splat)" do + assert_unordered( + "x, **opts", + "*x, y", + "1, y: 2") + end + + it "(required > splat) vs (optional > splat)" do + assert_unordered( + "x, **opts", + "*x, y = 0", + "1, y: 2") + end + + it "(optional > splat) vs (required > optional)" do + assert_unordered( + "x = 0, *, y = 0", + "*x, y", + "1, y: 2") + end + + it "(optional > splat) vs (required > splat)" do + assert_unordered( + "x = 0, **opts", + "*x, y", + "1, y: 2") + end + + it "(optional > splat) vs (optional > splat)" do + assert_unordered( + "x = 0, **opts", + "*x, y = 0", + "1, y: 2") + end + end + + context "specificity conflicts, named vs named" do + it "(required > optional) vs (required > optional)" do + assert_unordered( + "*, x, y = 0", + "*, y, x = 0", + "x: 1, y: 2") + end + + it "(required > optional) vs (required > splat)" do + assert_unordered( + "*, x, **opts", + "*, y, x = 0", + "x: 1, y: 2") + end + + it "(required > optional) vs (optional > splat)" do + assert_unordered( + "*, x, **opts", + "*, y = 0, x = 0", + "x: 1, y: 2") + end + + it "(required > splat) vs (required > splat)" do + assert_unordered( + "*, x, **opts", + "*, y, **opts", + "x: 1, y: 2") + end + + it "(required > splat) vs (optional > splat)" do + assert_unordered( + "*, x, **opts", + "*, y = 0, **opts", + "x: 1, y: 2") + end + + it "(optional > splat) vs (optional > splat)" do + assert_unordered( + "*, x = 0, **opts", + "*, y = 0, **opts", + "x: 1, y: 2") + end + end + end + it "types a call with overload" do assert_type("def foo; 1; end; def foo(x); 2.5; end; foo") { int32 } end diff --git a/spec/compiler/semantic/double_splat_spec.cr b/spec/compiler/semantic/double_splat_spec.cr index 8d721eef40a8..3e08ba9ed605 100644 --- a/spec/compiler/semantic/double_splat_spec.cr +++ b/spec/compiler/semantic/double_splat_spec.cr @@ -202,60 +202,4 @@ describe "Semantic: double splat" do test(x: 7) )) { named_tuple_of({} of String => Type) } end - - it "matches typed before non-typed (1) (#3134)" do - assert_type(%( - def bar(**args) - "free" - end - - def bar(**args : Int32) - 1 - end - - {bar(x: 1, y: 2), bar(x: 'a', y: 1)} - )) { tuple_of([int32, string]) } - end - - it "matches typed before non-typed (1) (#3134)" do - assert_type(%( - def bar(**args : Int32) - 1 - end - - def bar(**args) - "free" - end - - {bar(x: 1, y: 2), bar(x: 'a', y: 1)} - )) { tuple_of([int32, string]) } - end - - it "orders overloads: unnamed splat vs double splat (1) (#5328)" do - assert_type(%( - def foo(*, x : Nil) - 1 - end - - def foo(**options) - :symbol - end - - foo(x: nil) - )) { int32 } - end - - it "orders overloads: unnamed splat vs double splat (2) (#5328)" do - assert_type(%( - def foo(**options) - :symbol - end - - def foo(*, x : Nil) - 1 - end - - foo(x: nil) - )) { int32 } - end end diff --git a/spec/compiler/semantic/splat_spec.cr b/spec/compiler/semantic/splat_spec.cr index 67374ab9452e..ec7a829558c4 100644 --- a/spec/compiler/semantic/splat_spec.cr +++ b/spec/compiler/semantic/splat_spec.cr @@ -167,7 +167,7 @@ describe "Semantic: splat" do foo ), - "no overload matches" + "wrong number of arguments for 'foo' (given 0, expected 1+)" end it "overloads with type restriction and splat (3)" do @@ -749,34 +749,6 @@ describe "Semantic: splat" do "expected argument #1 to 'foo' to be Union(*T), not Int32" end - it "matches typed before non-typed (1) (#3134)" do - assert_type(%( - def bar(*args) - "free" - end - - def bar(*args : Int32) - 1 - end - - {bar(1, 2), bar('a', 1)} - )) { tuple_of([int32, string]) } - end - - it "matches typed before non-typed (1) (#3134)" do - assert_type(%( - def bar(*args : Int32) - 1 - end - - def bar(*args) - "free" - end - - {bar(1, 2), bar('a', 1)} - )) { tuple_of([int32, string]) } - end - describe Splat do it "without splat" do a_def = Def.new("foo", args: [Arg.new("x"), Arg.new("y")]) diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index 9c2339658d19..72d936cd88af 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -182,19 +182,22 @@ module Crystal special_vars << name end - # Returns the minimum and maximum number of arguments that must - # be passed to this method. + # Returns the minimum and maximum number of positional arguments that must + # be passed to this method, assuming positional parameters are not matched + # by named arguments. def min_max_args_sizes max_size = args.size default_value_index = args.index(&.default_value) min_size = default_value_index || max_size splat_index = self.splat_index if splat_index + min_size = {default_value_index || splat_index, splat_index}.min if args[splat_index].name.empty? - min_size = {default_value_index || splat_index, splat_index}.min max_size = splat_index else - min_size -= 1 unless default_value_index && default_value_index < splat_index + if splat_restriction = args[splat_index].restriction + min_size += 1 unless splat_restriction.is_a?(Splat) || default_value_index.try(&.< splat_index) + end max_size = Int32::MAX end end diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 94b40ab51ec0..0599c8daa820 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -3,33 +3,27 @@ require "../types" # Here is the logic for deciding two things: # -# 1. Whether a method should come before another one -# when considering overloads. -# This is what `restriction_of?` is for. -# 2. What's the resulting type of filtering a type -# by a restriction. +# 1. Whether a method should come before another one when considering overloads. +# This is what `compare_strictness` and `restriction_of?` are for. +# 2. What's the resulting type of filtering a type by a restriction. # This is what `restrict` is for. # -# If `a.restriction_of?(b)` is true, it means that -# `a` should come before `b` when considering restrictions. -# This applies almost always to AST nodes, which are -# sometimes resolved to see if a type inherits another -# one (and so it should be considered before that type), -# but can apply to types when arguments have a fixed -# type (mostly for primitive methods, though we should +# If `a.restriction_of?(b)` is true, it means that `a` should come before `b` +# when considering restrictions. This applies almost always to AST nodes, which +# are sometimes resolved to see if a type inherits another one (and so it should +# be considered before that type), but can apply to types when arguments have a +# fixed type (mostly for primitive methods and abstract defs, though we should # get rid of this to simplify things). -# A similar logic applies to a `Def`, where this logic -# is applied for each of the arguments, though here -# the number of arguments, splat index and other factors -# are considered. -# If `a.restriction_of?(b) == true` and `b.restriction_of?(a) == true`, -# for `a` and `b` being `Def`s, then it means `a` and `b` are equivalent, -# and so when adding `b` to a types methods it will replace `a`. # -# The method `restrict` is different in that the return -# value is not a boolean, but a type, and computing it -# might be a bit more expensive. For example when restricting -# `Int32 | String` against `Int32`, the result is `Int32`. +# A similar logic applies to a `Def`, where this logic is applied for each of +# the arguments, though here the number of arguments, splat index and other +# factors are considered. If `a.compare_strictness(b) == 0`, for `a` and `b` +# being `Def`s, then it means `a` and `b` are equivalent, and so when adding `b` +# to a type's methods it will replace `a`. +# +# The method `restrict` is different in that the return value is not a boolean, +# but a type, and computing it might be a bit more expensive. For example when +# restricting `Int32 | String` against `Int32`, the result is `Int32`. module Crystal class ASTNode @@ -65,7 +59,306 @@ module Crystal end struct DefWithMetadata - def restriction_of?(other : DefWithMetadata, owner) + # Compares two defs based on overload order. Has a return value similar to + # `Comparable`; that is, `self.compare_strictness(other)` returns: + # + # * `-1` if `self` is a stricter def than *other*; + # * `1` if *other* is a stricter def than `self`; + # * `0` if `self` and *other* are equivalent defs; + # * `nil` if neither def is stricter than the other. + def compare_strictness(other : DefWithMetadata, self_owner, *, other_owner = self_owner) + unless self_owner.program.has_flag?("preview_overload_order") + return compare_strictness_old(other, self_owner, other_owner: other_owner) + end + + # If one yields and the other doesn't, neither is stricter than the other + return nil unless self.yields == other.yields + + # We don't check for incompatible defs from positional parameters here, + # because doing so would break transitivity, e.g. + # + # def f(x = 0); end + # def f(x, y, z = 0); end + # + # The two defs are indeed incompatible, but their order can be defined + # through the intermediate overload `def f(x, y = 0); end`. + + self_named_args = self.named_arguments + other_named_args = other.named_arguments + + # Required named parameter in self, no corresponding named parameter in + # other; neither is stricter than the other + unless other.def.double_splat + self_named_args.try &.each do |self_arg| + unless self_arg.default_value + unless other_named_args.try &.any?(&.external_name.== self_arg.external_name) + return nil + end + end + end + end + + unless self.def.double_splat + other_named_args.try &.each do |other_arg| + unless other_arg.default_value + unless self_named_args.try &.any?(&.external_name.== other_arg.external_name) + return nil + end + end + end + end + + self_stricter = true + other_stricter = true + + # Compare all corresponding parameters based on subsumption order + each_corresponding_param(other, self_named_args, other_named_args) do |self_arg, other_arg| + self_restriction = self_arg.type? || self_arg.restriction + other_restriction = other_arg.type? || other_arg.restriction + + case {self_restriction, other_restriction} + when {nil, nil} + # Check other corresponding parameters + when {nil, _} + self_is_not_stricter + when {_, nil} + other_is_not_stricter + else + self_is_not_stricter unless self_restriction.restriction_of?(other_restriction, self_owner) + other_is_not_stricter unless other_restriction.restriction_of?(self_restriction, other_owner) + end + end + + # The overload order is fully defined at this point if either def already + # isn't stricter than the other + return stricter_pair_to_num(self_stricter, other_stricter) if !self_stricter || !other_stricter + + # Combine the specificities from positional and all named signatures + self_stricter, other_stricter = compare_specific_positional(other) + + if self_named_args || other_named_args + self_named_args.try &.each do |self_arg| + other_arg = other_named_args.try &.find(&.external_name.== self_arg.external_name) + self_n, other_n = compare_specific_named(other, self_arg, other_arg) + self_is_not_stricter if !self_n + other_is_not_stricter if !other_n + end + + other_named_args.try &.each do |other_arg| + next if self_named_args.try &.any?(&.external_name.== other_arg.external_name) + self_n, other_n = compare_specific_named(other, nil, other_arg) + self_is_not_stricter if !self_n + other_is_not_stricter if !other_n + end + else + # If there are no named parameters at all, `(**ns)` is less specific than `()` + if self.def.double_splat && !other.def.double_splat + self_is_not_stricter + elsif other.def.double_splat && !self.def.double_splat + other_is_not_stricter + end + end + + stricter_pair_to_num(self_stricter, other_stricter) + end + + private macro self_is_not_stricter + self_stricter = false + return nil if !other_stricter + end + + private macro other_is_not_stricter + other_stricter = false + return nil if !self_stricter + end + + # Compares two defs based on whether one def's positional parameters are + # more specific than the other's. + # + # Required parameters are more specific than optional parameters, and single + # splat parameters are the least specific. + def compare_specific_positional(other : DefWithMetadata) + # If self has more required positional parameters than other, the last + # one in self must correspond to an optional or splat parameter in other, + # otherwise other has no corresponding parameter and `compare_strictness` + # would have already returned; hence, self is stricter than other in this + # case. + if self.min_size > other.min_size + self_is_stricter + elsif other.min_size > self.min_size + other_is_stricter + end + + # Bare splats aren't single splat parameters + if self_splat_index = self.def.splat_index + self_splat_index = nil if self.def.args[self_splat_index].name.empty? + end + if other_splat_index = other.def.splat_index + other_splat_index = nil if other.def.args[other_splat_index].name.empty? + end + + case {self_splat_index, other_splat_index} + in {nil, nil} + # Consider `(x0, x1 = 0, x2 = 0)` and `(y0, y1 = 0)`; both overloads can + # take 1 or 2 arguments, but only self could take 3, so other is stricter + # than self. + if self.max_size > other.max_size + other_is_stricter + elsif other.max_size > self.max_size + self_is_stricter + end + in {nil, Int32} + # other has a splat parameter, self doesn't; self is stricter than the other + self_is_stricter + in {Int32, nil} + # self has a splat parameter, other doesn't; other is stricter than self + other_is_stricter + in {Int32, Int32} + # Consider `(x0, *xs)` and `(y0, y1 = 0, *ys)`; here `y1` corresponds to + # `xs`, and splat parameter is less specific than optional parameter, so + # other is stricter than self. + if self_splat_index < other_splat_index + other_is_stricter + elsif other_splat_index < self_splat_index + self_is_stricter + end + end + + no_differences + end + + # Compares two defs based on whether one def's given named parameter is more + # specific than the other's. + def compare_specific_named(other : DefWithMetadata, self_arg : Arg?, other_arg : Arg?) + self_arg_required = self_arg && !self_arg.default_value + other_arg_required = other_arg && !other_arg.default_value + + # `n` is required in self, but not required in other; `n`'s corresponding + # parameter in other must be optional or splat, so self is stricter than + # the other + if self_arg_required && !other_arg_required + self_is_stricter + elsif other_arg_required && !self_arg_required + other_is_stricter + end + + self_arg_optional = self_arg && self_arg.default_value + other_arg_optional = other_arg && other_arg.default_value + + case {self.def.double_splat, other.def.double_splat} + in {nil, nil} + # Consider `(*, n = 0)` and `()`; both overloads can take no named + # arguments, but only self could take `n`, so other is stricter than + # self. + if self_arg_optional && !other_arg_optional + other_is_stricter + elsif other_arg_optional && !self_arg_optional + self_is_stricter + end + in {nil, Arg} + # other has a splat parameter, self doesn't; self is stricter than the other + self_is_stricter + in {Arg, nil} + # self has a splat parameter, other doesn't; other is stricter than self + other_is_stricter + in {Arg, Arg} + # Consider `(*, **ms)` and `(*, n = 0, **ns)`; here `n` corresponds to + # `ms`, and splat parameter is less specific than optional parameter, so + # other is stricter than self. + if self_arg_optional && !other_arg_optional + self_is_stricter + elsif other_arg_optional && !self_arg_optional + other_is_stricter + end + end + + no_differences + end + + private macro self_is_stricter + return {true, false} + end + + private macro other_is_stricter + return {false, true} + end + + private macro no_differences + return {true, true} + end + + # Yields each pair of corresponding parameters between `self` and *other*. + def each_corresponding_param(other : DefWithMetadata, self_named_args, other_named_args) + self_arg_index = 0 + other_arg_index = 0 + + # Traverse through positional parameters, including single splats + while self_arg_index < self.def.args.size && other_arg_index < other.def.args.size + self_arg = self.def.args[self_arg_index] + self_splatting = (self_arg_index == self.def.splat_index) + break if self_splatting && self_arg.name.empty? # Start of named parameters + + other_arg = other.def.args[other_arg_index] + other_splatting = (other_arg_index == other.def.splat_index) + break if other_splatting && other_arg.name.empty? # Start of named parameters + + yield self_arg, other_arg + + break if self_splatting && other_splatting # Both are splat parameters + + self_arg_index += 1 unless self_splatting + other_arg_index += 1 unless other_splatting + end + + # Traverse through named parameters + self_double_splat = self.def.double_splat + other_double_splat = other.def.double_splat + + self_named_args.try &.each do |self_arg| + other_arg = other_named_args.try &.find(&.external_name.== self_arg.external_name) + other_arg ||= other_double_splat + next unless other_arg + + yield self_arg, other_arg + end + + if self_double_splat + # Pair self's double splat with any remaining named parameters in other + other_named_args.try &.each do |other_arg| + next if self_named_args.try &.any?(&.external_name.== other_arg.external_name) + + yield self_double_splat, other_arg + end + + # Double splats themselves are also corresponding named parameters + if other_double_splat + yield self_double_splat, other_double_splat + end + end + end + + def stricter_pair_to_num(self_stricter, other_stricter) + case {self_stricter, other_stricter} + in {true, true} then 0 + in {true, false} then -1 + in {false, true} then 1 + in {false, false} then nil + end + end + + def named_arguments + if (splat_index = self.def.splat_index) && splat_index != self.def.args.size - 1 + self.def.args[splat_index + 1..] + end + end + + def compare_strictness_old(other : DefWithMetadata, self_owner, *, other_owner = self_owner) + self_stricter = old_restriction_of?(other, self_owner) + other_stricter = other.old_restriction_of?(self, other_owner) + stricter_pair_to_num(self_stricter, other_stricter) + end + + def old_restriction_of?(other : DefWithMetadata, owner) # This is how multiple defs are sorted by 'restrictions' (?) # If one yields and the other doesn't, none is stricter than the other diff --git a/src/compiler/crystal/tools/doc/method.cr b/src/compiler/crystal/tools/doc/method.cr index bb58997042e4..44bebd389220 100644 --- a/src/compiler/crystal/tools/doc/method.cr +++ b/src/compiler/crystal/tools/doc/method.cr @@ -104,8 +104,7 @@ class Crystal::Doc::Method other_defs_with_metadata = ancestor.defs.try &.[@def.name]? other_defs_with_metadata.try &.each do |other_def_with_metadata| # If we find an ancestor method with the same signature - if def_with_metadata.restriction_of?(other_def_with_metadata, type.type) && - other_def_with_metadata.restriction_of?(def_with_metadata, ancestor) + if def_with_metadata.compare_strictness(other_def_with_metadata, self_owner: type.type, other_owner: ancestor) == 0 other_def = other_def_with_metadata.def doc = other_def.doc return DocInfo.new(doc, @generator.type(ancestor)) if doc diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 9fdf7a02eae2..1cea37d5bb01 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -912,19 +912,20 @@ module Crystal defs = (@defs ||= {} of String => Array(DefWithMetadata)) list = defs[a_def.name] ||= [] of DefWithMetadata list.each_with_index do |ex_item, i| - if item.restriction_of?(ex_item, self) - if ex_item.restriction_of?(item, self) - # The two defs have the same signature so item overrides ex_item. - list[i] = item - a_def.previous = ex_item - a_def.doc ||= ex_item.def.doc - ex_item.def.next = a_def - return ex_item.def - else - # item has a new signature, stricter than ex_item. - list.insert(i, item) - return nil - end + case item.compare_strictness(ex_item, self) + when nil + # Incompatible defs; do nothing + when 0 + # The two defs have the same signature so item overrides ex_item. + list[i] = item + a_def.previous = ex_item + a_def.doc ||= ex_item.def.doc + ex_item.def.next = a_def + return ex_item.def + when .< 0 + # item has a new signature, stricter than ex_item. + list.insert(i, item) + return nil end end From b5a03cfe8dbb69c2f80e37bde09408ab8c909ebb Mon Sep 17 00:00:00 2001 From: David Keller Date: Thu, 22 Sep 2022 15:32:39 +0200 Subject: [PATCH 0020/1551] Allow using U/Int128 in random (#11977) --- spec/std/random/secure_spec.cr | 2 +- spec/std/random_spec.cr | 22 ++++++++++++++++++++++ src/random.cr | 20 +++++++++++--------- src/random/secure.cr | 4 ++-- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/spec/std/random/secure_spec.cr b/spec/std/random/secure_spec.cr index a1ca1b014e13..677f4ece0aa5 100644 --- a/spec/std/random/secure_spec.cr +++ b/spec/std/random/secure_spec.cr @@ -19,7 +19,7 @@ describe "Random::Secure" do end it "returns a random integer in range (#8219)" do - {% for type in %w(Int8 UInt8 Int16 UInt16 Int32 UInt32 Int64 UInt64).map(&.id) %} + {% for type in %w(Int8 UInt8 Int16 UInt16 Int32 UInt32 Int64 UInt64 Int128 UInt128).map(&.id) %} value = Random::Secure.rand({{type}}::MIN..{{type}}::MAX) typeof(value).should eq({{type}}) {% end %} diff --git a/spec/std/random_spec.cr b/spec/std/random_spec.cr index 37b951c89def..f9c8d5309075 100644 --- a/spec/std/random_spec.cr +++ b/spec/std/random_spec.cr @@ -1,6 +1,11 @@ require "./spec_helper" require "big" +# Copied here from compiler-rt's spec-helper +def make_tu(a : UInt128, b : UInt128) + (a.to_u128! << 64) + b +end + private class TestRNG(T) include Random @@ -24,6 +29,10 @@ private RNG_DATA_32 = [31541451u32, 0u32, 1u32, 234u32, 342475672u32, 863u32, 0x private RNG_DATA_64 = [148763248732657823u64, 18446744073709551615u64, 0u64, 32456325635673576u64, 2456245614625u64, 32452456246u64, 3956529762u64, 9823674982364u64, 234253464546456u64, 14345435645646u64] +private RNG_DATA_128 = [make_tu(1101449320907785530u128, 6979810955129606772u128), make_tu(12369252765162302003u128, 15575142252968787416u128), + make_tu(13441498024203764035u128, 7030046732498433846u128), make_tu(6222142093523402848u128, 17154318143782852173u128), + make_tu(9998452933988559113u128, 7429084970167000187u128), make_tu(14848715232994466871u128, 16710636589464800780u128), + make_tu(16766909356445913125u128, 789971150692021876u128), make_tu(13481304315125825423u128, 16449966857661152872u128)] describe "Random" do it "limited number" do @@ -246,6 +255,19 @@ describe "Random" do end end + it "works using U/Int128" do + rng = TestRNG.new(RNG_DATA_128) + RNG_DATA_128.each do |a| + rng.rand(UInt128::MIN..UInt128::MAX).should eq a + end + + # (234_u128 << 96) + (1_u128 << 64) + 31541451_u128 + TestRNG.new(RNG_DATA_32).rand(UInt128).should eq make_tu(1005022347265u128, 31541451u128) + + rand_in_range = TestRNG.new(RNG_DATA_32).rand(600_u128..700_u128) + (600..700).should contain(rand_in_range) + end + describe "random_bytes" do it "generates random bytes" do rng = TestRNG.new([0xfa19443eu32, 1u32, 0x12345678u32]) diff --git a/src/random.cr b/src/random.cr index 918843007367..0cfc013d01ca 100644 --- a/src/random.cr +++ b/src/random.cr @@ -112,7 +112,7 @@ module Random rand_int(max) end - {% for size in [8, 16, 32, 64] %} + {% for size in [8, 16, 32, 64, 128] %} {% utype = "UInt#{size}".id %} {% for type in ["Int#{size}".id, utype] %} private def rand_int(max : {{type}}) : {{type}} @@ -299,14 +299,16 @@ module Random end {% for type, values in { - "Int8".id => %w(20 -66 89 19), - "UInt8".id => %w(186 221 127 245), - "Int16".id => %w(-32554 32169 -20152 -7686), - "UInt16".id => %w(39546 44091 2874 17348), - "Int32".id => %w(1870830079 -1043532158 -867180637 -1216773590), - "UInt32".id => %w(3147957137 4245108745 2207809043 3184391838), - "Int64".id => %w(4438449217673515190 8514493061600538358 -4874671083204037318 -7825896160729246667), - "UInt64".id => %w(15004487597684511003 12027825265648206103 11303949506191212698 6228566501671148658), + "Int8".id => %w(20 -66 89 19), + "UInt8".id => %w(186 221 127 245), + "Int16".id => %w(-32554 32169 -20152 -7686), + "UInt16".id => %w(39546 44091 2874 17348), + "Int32".id => %w(1870830079 -1043532158 -867180637 -1216773590), + "UInt32".id => %w(3147957137 4245108745 2207809043 3184391838), + "Int64".id => %w(4438449217673515190 8514493061600538358 -4874671083204037318 -7825896160729246667), + "UInt64".id => %w(15004487597684511003 12027825265648206103 11303949506191212698 6228566501671148658), + "Int128".id => %w(-33248638598154624979861619415313153263 7715345987200799268985566794637461715 51883986405785085023723116953594906714 -63505201678563022521901409748929046368), + "UInt128".id => %w(209016375821699277802308597707088869733 168739091726124084850659068882871627438 293712757766410232411790495845165436283 15480005665598870938163293877660434201), } %} # Returns a random {{type}} # diff --git a/src/random/secure.cr b/src/random/secure.cr index e0fe7c74001c..3c839e488f04 100644 --- a/src/random/secure.cr +++ b/src/random/secure.cr @@ -27,7 +27,7 @@ module Random::Secure Crystal::System::Random.random_bytes(buf) end - {% for type in [UInt8, UInt16, UInt32, UInt64] %} + {% for type in [UInt8, UInt16, UInt32, UInt64, UInt128] %} # Generates a random integer of a given type. The number of bytes to # generate can be limited; by default it will generate as many bytes as # needed to fill the integer size. @@ -55,7 +55,7 @@ module Random::Secure end {% end %} - {% for type in [Int8, Int16, Int32, Int64] %} + {% for type in [Int8, Int16, Int32, Int64, Int128] %} private def rand_type(type : {{type}}.class, needed_bytes = sizeof({{type}})) : {{type}} result = rand_type({{"U#{type}".id}}, needed_bytes) {{type}}.new!(result) From a4704af213cb4b1cb6d36934fb41b49400a4c04e Mon Sep 17 00:00:00 2001 From: I3oris <37157434+I3oris@users.noreply.github.com> Date: Thu, 22 Sep 2022 15:34:42 +0200 Subject: [PATCH 0021/1551] Improve syntax highlighter (#12409) * Fix 1/2 treated as regex. * Fix missing dollar in global match data index. * Fix wrong context in string interpolation. * Colorize all operators (&**, &+=, &&=, ...). * Fix wrong coloration of def operators (def +, def []=). * Fix coloration for def //. --- .../syntax_highlighter/colorize_spec.cr | 39 +++++++++++++-- .../crystal/syntax_highlighter/html_spec.cr | 33 ++++++++++++- src/crystal/syntax_highlighter.cr | 49 +++++++++++++++++-- src/crystal/syntax_highlighter/colorize.cr | 4 +- 4 files changed, 112 insertions(+), 13 deletions(-) diff --git a/spec/std/crystal/syntax_highlighter/colorize_spec.cr b/spec/std/crystal/syntax_highlighter/colorize_spec.cr index 9ef6175f718b..5cd04a7a156c 100644 --- a/spec/std/crystal/syntax_highlighter/colorize_spec.cr +++ b/spec/std/crystal/syntax_highlighter/colorize_spec.cr @@ -27,7 +27,7 @@ end describe Crystal::SyntaxHighlighter::Colorize do describe ".highlight" do - it_highlights %(foo = bar("baz\#{PI + 1}") # comment), "foo \e[91m=\e[0m bar(\e[93m\"baz\#{\e[0;36mPI\e[0;93m \e[0;91m+\e[0;93m \e[0;35m1\e[0;93m}\"\e[0m) \e[90m# comment\e[0m" + it_highlights %(foo = bar("baz\#{PI + 1}") # comment), %(foo \e[91m=\e[0m bar(\e[93m"baz\#{\e[0m\e[36mPI\e[0m \e[91m+\e[0m \e[35m1\e[0m\e[93m}"\e[0m) \e[90m# comment\e[0m) it_highlights "foo", "foo" it_highlights "foo bar", "foo bar" @@ -73,15 +73,44 @@ describe Crystal::SyntaxHighlighter::Colorize do it_highlights "def foo", %(\e[91mdef\e[0m \e[92mfoo\e[0m) %w( - + - * &+ &- &* / // = == < <= > >= ! != =~ !~ & | ^ ~ ** - >> << % [] []? []= <=> === + [] []? []= <=> + + - * / + == < <= > >= != =~ !~ + & | ^ ~ ** >> << % + ).each do |op| + it_highlights %(def #{op}), %(\e[91mdef\e[0m \e[92m#{op}\e[0m) + end + + it_highlights %(def //), %(\e[91mdef\e[0m \e[92m/\e[0m\e[92m/\e[0m) + + %w( + + - * &+ &- &* &** / // = == < <= > >= ! != =~ !~ & | ^ ~ ** + >> << % [] []? []= <=> === && || + += -= *= /= //= &= |= ^= **= >>= <<= %= &+= &-= &*= &&= ||= ).each do |op| it_highlights "1 #{op} 2", %(\e[35m1\e[0m \e[91m#{op}\e[0m \e[35m2\e[0m) end + it_highlights %(1/2), %(\e[35m1\e[0m\e[91m/\e[0m\e[35m2\e[0m) + it_highlights %(1 /2), %(\e[35m1\e[0m \e[91m/\e[0m\e[35m2\e[0m) + it_highlights %(1/ 2), %(\e[35m1\e[0m\e[91m/\e[0m \e[35m2\e[0m) + + it_highlights %(a/b), %(a\e[91m/\e[0mb) + it_highlights %(a/ b), %(a\e[91m/\e[0m b) + it_highlights %(a / b), %(a \e[91m/\e[0m b) + + it_highlights %(a /b/), %(a \e[93m/b/\e[0m) + + it_highlights %($1), %($1) + it_highlights %($2?), %($2?) + it_highlights %($?), %($?) + it_highlights %($~), %($~) + it_highlights %("foo"), %(\e[93m"foo"\e[0m) it_highlights %("<>"), %(\e[93m"<>"\e[0m) - it_highlights %("foo\#{bar}baz"), %(\e[93m"foo\#{bar}baz"\e[0m) + it_highlights %("foo\#{bar}baz"), %(\e[93m"foo\#{\e[0mbar\e[93m}baz"\e[0m) + it_highlights %("foo\#{[1, bar, "str"]}baz"), %(\e[93m"foo\#{\e[0m[\e[35m1\e[0m, bar, \e[93m"str"\e[0m]\e[93m}baz"\e[0m) + it_highlights %("nest1\#{foo + "nest2\#{1 + 1}bar"}baz"), %(\e[93m"nest1\#{\e[0mfoo \e[91m+\e[0m \e[93m"nest2\#{\e[0m\e[35m1\e[0m \e[91m+\e[0m \e[35m1\e[0m\e[93m}bar"\e[0m\e[93m}baz"\e[0m) it_highlights "/foo/xim", %(\e[93m/foo/\e[0mxim) it_highlights "`foo`", %(\e[93m`foo`\e[0m) it_highlights "%(foo)", %(\e[93m%(foo)\e[0m) @@ -114,7 +143,7 @@ describe Crystal::SyntaxHighlighter::Colorize do end describe ".highlight!" do - it_highlights! %(foo = bar("baz\#{PI + 1}") # comment), "foo \e[91m=\e[0m bar(\e[93m\"baz\#{\e[0;36mPI\e[0;93m \e[0;91m+\e[0;93m \e[0;35m1\e[0;93m}\"\e[0m) \e[90m# comment\e[0m" + it_highlights! %(foo = bar("baz\#{PI + 1}") # comment), %(foo \e[91m=\e[0m bar(\e[93m"baz\#{\e[0m\e[36mPI\e[0m \e[91m+\e[0m \e[35m1\e[0m\e[93m}"\e[0m) \e[90m# comment\e[0m) it_highlights! <<-CR foo, bar = <<-FOO, <<-BAR diff --git a/spec/std/crystal/syntax_highlighter/html_spec.cr b/spec/std/crystal/syntax_highlighter/html_spec.cr index ee2706ce734a..a30c6f77a9dc 100644 --- a/spec/std/crystal/syntax_highlighter/html_spec.cr +++ b/spec/std/crystal/syntax_highlighter/html_spec.cr @@ -68,15 +68,44 @@ describe Crystal::SyntaxHighlighter::HTML do it_highlights "def foo", %(def foo) %w( - + - * &+ &- &* / // = == < <= > >= ! != =~ !~ & | ^ ~ ** - >> << % [] []? []= <=> === + [] []? []= <=> + + - * / + == < <= > >= != =~ !~ + & | ^ ~ ** >> << % + ).each do |op| + it_highlights %(def #{op}), %(def #{op}) + end + + it_highlights %(def //), %(def //) + + %w( + + - * &+ &- &* &** / // = == < <= > >= ! != =~ !~ & | ^ ~ ** + >> << % [] []? []= <=> === && || + += -= *= /= //= &= |= ^= **= >>= <<= %= &+= &-= &*= &&= ||= ).each do |op| it_highlights "1 #{op} 2", %(1 #{HTML.escape(op)} 2) end + it_highlights %(1/2), %(1/2) + it_highlights %(1 /2), %(1 /2) + it_highlights %(1/ 2), %(1/ 2) + + it_highlights %(a/b), %(a/b) + it_highlights %(a/ b), %(a/ b) + it_highlights %(a / b), %(a / b) + + it_highlights %(a /b/), %(a /b/) + + it_highlights %($1), %($1) + it_highlights %($2?), %($2?) + it_highlights %($?), %($?) + it_highlights %($~), %($~) + it_highlights %("foo"), %("foo") it_highlights %("<>"), %("<>") it_highlights %("foo\#{bar}baz"), %("foo\#{bar}baz") + it_highlights %("foo\#{[1, bar, "str"]}baz"), %("foo\#{[1, bar, "str"]}baz") + it_highlights %("nest1\#{foo + "nest2\#{1 + 1}bar"}baz"), %("nest1\#{foo + "nest2\#{1 + 1}bar"}baz") it_highlights "/foo/xim", %(/foo/xim) it_highlights "`foo`", %(`foo`) it_highlights "%(foo)", %(%(foo)) diff --git a/src/crystal/syntax_highlighter.cr b/src/crystal/syntax_highlighter.cr index 60db6a71dbac..f23109a6f43e 100644 --- a/src/crystal/syntax_highlighter.cr +++ b/src/crystal/syntax_highlighter.cr @@ -69,16 +69,38 @@ abstract class Crystal::SyntaxHighlighter end end + private def slash_is_not_regex(last_token_type type, space_before) + return nil if type.nil? + + type.number? || type.const? || type.instance_var? || + type.class_var? || type.op_rparen? || + type.op_rsquare? || type.op_rcurly? || !space_before + end + private def highlight_normal_state(lexer, break_on_rcurly = false) last_is_def = false heredoc_stack = [] of Token + last_token_type = nil + space_before = false while true token = lexer.next_token case token.type when .delimiter_start? - if token.delimiter_state.kind.heredoc? + case + when last_is_def && token.raw == "`" + render :IDENT, token.raw # colorize 'def `' + when last_is_def && token.raw == "/" + render :IDENT, token.raw # colorize 'def /' + + if lexer.current_char == '/' + render :IDENT, "/" # colorize 'def //' + lexer.reader.next_char if lexer.reader.has_next? + end + when token.raw == "/" && slash_is_not_regex(last_token_type, space_before) + render :OPERATOR, token.raw + when token.delimiter_state.kind.heredoc? heredoc_stack << token.dup highlight_token token, last_is_def else @@ -120,8 +142,10 @@ abstract class Crystal::SyntaxHighlighter end unless token.type.space? + last_token_type = token.type last_is_def = token.keyword? :def end + space_before = token.type.space? end end @@ -158,13 +182,28 @@ abstract class Crystal::SyntaxHighlighter render :UNKNOWN, token.to_s end end - when .op_plus?, .op_minus?, .op_star?, .op_amp_plus?, .op_amp_minus?, .op_amp_star?, .op_slash?, .op_slash_slash?, # + - * &+ &- &* / // - .op_eq?, .op_eq_eq?, .op_lt?, .op_lt_eq?, .op_gt?, .op_gt_eq?, .op_bang?, .op_bang_eq?, .op_eq_tilde?, .op_bang_tilde?, # = == < <= > >= ! != =~ !~ - .op_amp?, .op_bar?, .op_caret?, .op_tilde?, .op_star_star?, .op_gt_gt?, .op_lt_lt?, .op_percent?, # & | ^ ~ ** >> << % - .op_lsquare_rsquare?, .op_lsquare_rsquare_question?, .op_lsquare_rsquare_eq?, .op_lt_eq_gt?, .op_eq_eq_eq? # [] []? []= <=> === + when .op_lparen?, .op_rparen?, .op_lsquare?, .op_rsquare?, .op_lcurly?, .op_rcurly?, .op_at_lsquare?, # ( ) { } [ ] @[ + .op_comma?, .op_period?, .op_period_period?, .op_period_period_period?, # , . .. ... + .op_colon?, .op_semicolon?, .op_question?, .op_dollar_question?, .op_dollar_tilde? # : ; ? $? $~ + # Operators that should not be colorized + render :UNKNOWN, token.to_s + when .op_lsquare_rsquare?, .op_lsquare_rsquare_question?, .op_lsquare_rsquare_eq?, .op_lt_eq_gt?, # [] []? []= <=> + .op_plus?, .op_minus?, .op_star?, .op_slash?, .op_slash_slash?, # + - * / // + .op_eq_eq?, .op_lt?, .op_lt_eq?, .op_gt?, .op_gt_eq?, .op_bang_eq?, .op_eq_tilde?, .op_bang_tilde?, # == < <= > >= != =~ !~ + .op_amp?, .op_bar?, .op_caret?, .op_tilde?, .op_star_star?, .op_gt_gt?, .op_lt_lt?, .op_percent? # & | ^ ~ ** >> << % + # Operators acceptable in def + if last_is_def + render :IDENT, token.to_s + else + render :OPERATOR, token.to_s + end + when .operator? + # Colorize any other operator render :OPERATOR, token.to_s when .underscore? render :UNDERSCORE, "_" + when .global_match_data_index? + render :UNKNOWN, "$" + token.value.to_s else render :UNKNOWN, token.to_s end diff --git a/src/crystal/syntax_highlighter/colorize.cr b/src/crystal/syntax_highlighter/colorize.cr index a708b19734bd..10ed041a766b 100644 --- a/src/crystal/syntax_highlighter/colorize.cr +++ b/src/crystal/syntax_highlighter/colorize.cr @@ -65,7 +65,9 @@ class Crystal::SyntaxHighlighter::Colorize < Crystal::SyntaxHighlighter def render_interpolation(&) colorize :INTERPOLATION, "\#{" - yield + @colorize.fore(:default).surround(@io) do + yield + end colorize :INTERPOLATION, "}" end From c3fddac1e3bf328e09277c837384a73d5454a4b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 22 Sep 2022 23:57:03 +0200 Subject: [PATCH 0022/1551] Fix `HTTP::Client#exec` to abort retry when client was closed (#12465) --- spec/std/http/client/client_spec.cr | 32 +++++++++++++++++++++++++++++ src/http/client.cr | 7 ++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index 3d6c24ec714b..ef951917ae2c 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -238,6 +238,38 @@ module HTTP end end + it "will not retry when closed (non-block) (#12464)" do + requests = 0 + server_channel = Channel(Nil).new + + client = HTTP::Client.new("127.0.0.1", 0) + client.before_request do + requests += 1 + raise IO::Error.new("foobar") + end + + expect_raises(IO::Error, "foobar") do + client.not_nil!.get(path: "/") + end + requests.should eq 1 + end + + it "will not retry when closed (block) (#12464)" do + requests = 0 + server_channel = Channel(Nil).new + + client = HTTP::Client.new("127.0.0.1", 0) + client.before_request do + requests += 1 + raise IO::Error.new("foobar") + end + + expect_raises(IO::Error, "foobar") do + client.not_nil!.get(path: "/") { } + end + requests.should eq 1 + end + it "doesn't read the body if request was HEAD" do resp_get = test_server("localhost", 0, 0) do |server| client = Client.new("localhost", server.local_address.port) diff --git a/src/http/client.cr b/src/http/client.cr index dfedce519434..a819741b728b 100644 --- a/src/http/client.cr +++ b/src/http/client.cr @@ -585,12 +585,13 @@ class HTTP::Client private def exec_internal(request) begin response = exec_internal_single(request) - rescue IO::Error + rescue exc : IO::Error + raise exc if @io.nil? # do not retry if client was closed response = nil end return handle_response(response) if response - # Server probably closed the connection, so retry one + # Server probably closed the connection, so retry once close request.body.try &.rewind response = exec_internal_single(request) @@ -650,7 +651,7 @@ class HTTP::Client begin decompress = send_request(request) rescue ex : IO::Error - return yield nil if ignore_io_error + return yield nil if ignore_io_error && !@io.nil? # ignore_io_error only if client was not closed raise ex end HTTP::Client::Response.from_io?(io, ignore_body: request.ignore_body?, decompress: decompress) do |response| From abe4d344cc1807223982a7f629563487a0faabb5 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Thu, 22 Sep 2022 18:57:17 -0300 Subject: [PATCH 0023/1551] Add 'wasm_import_module' option to the @[Link] annotation (#11935) Co-authored-by: Sijawusz Pur Rahnama --- spec/compiler/semantic/lib_spec.cr | 2 +- src/compiler/crystal/codegen/fun.cr | 11 +++++++++++ src/compiler/crystal/codegen/link.cr | 11 ++++++++--- src/compiler/crystal/semantic/ast.cr | 1 + src/compiler/crystal/semantic/top_level_visitor.cr | 12 ++++++++++++ src/compiler/crystal/types.cr | 4 ++++ 6 files changed, 37 insertions(+), 4 deletions(-) diff --git a/spec/compiler/semantic/lib_spec.cr b/spec/compiler/semantic/lib_spec.cr index e017ada7d511..5908a341d999 100644 --- a/spec/compiler/semantic/lib_spec.cr +++ b/spec/compiler/semantic/lib_spec.cr @@ -345,7 +345,7 @@ describe "Semantic: lib" do lib LibFoo end ), - "unknown link argument: 'boo' (valid arguments are 'lib', 'ldflags', 'static', 'pkg_config' and 'framework')" + "unknown link argument: 'boo' (valid arguments are 'lib', 'ldflags', 'static', 'pkg_config', 'framework', and 'wasm_import_module')" end it "errors if lib already specified with positional argument" do diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index cbdc9589cbc9..16d950f49bd3 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -202,6 +202,17 @@ class Crystal::CodeGenVisitor end end + if @program.has_flag?("wasm32") + if target_def.is_a?(External) && (wasm_import_module = target_def.wasm_import_module) + context.fun.add_target_dependent_attribute("wasm-import-name", target_def.real_name) + context.fun.add_target_dependent_attribute("wasm-import-module", wasm_import_module) + end + + if is_exported_fun + context.fun.add_target_dependent_attribute("wasm-export-name", mangled_name) + end + end + context.fun end end diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index cc764678b990..dbda33033695 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -4,8 +4,9 @@ module Crystal getter pkg_config : String? getter ldflags : String? getter framework : String? + getter wasm_import_module : String? - def initialize(@lib = nil, @pkg_config = @lib, @ldflags = nil, @static = false, @framework = nil) + def initialize(@lib = nil, @pkg_config = @lib, @ldflags = nil, @static = false, @framework = nil, @wasm_import_module = nil) end def static? @@ -25,6 +26,7 @@ module Crystal lib_static = false lib_pkg_config = nil lib_framework = nil + lib_wasm_import_module = nil count = 0 args.each do |arg| @@ -71,12 +73,15 @@ module Crystal when "pkg_config" named_arg.raise "'pkg_config' link argument must be a String" unless value.is_a?(StringLiteral) lib_pkg_config = value.value + when "wasm_import_module" + named_arg.raise "'wasm_import_module' link argument must be a String" unless value.is_a?(StringLiteral) + lib_wasm_import_module = value.value else - named_arg.raise "unknown link argument: '#{named_arg.name}' (valid arguments are 'lib', 'ldflags', 'static', 'pkg_config' and 'framework')" + named_arg.raise "unknown link argument: '#{named_arg.name}' (valid arguments are 'lib', 'ldflags', 'static', 'pkg_config', 'framework', and 'wasm_import_module')" end end - new(lib_name, lib_pkg_config, lib_ldflags, lib_static, lib_framework) + new(lib_name, lib_pkg_config, lib_ldflags, lib_static, lib_framework, lib_wasm_import_module) end end diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index 72d936cd88af..60ec3b56e7d9 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -682,6 +682,7 @@ module Crystal property real_name : String property! fun_def : FunDef property call_convention : LLVM::CallConvention? + property wasm_import_module : String? property? dead = false property? used = false diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index fa031aa30228..508d15b32366 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -501,6 +501,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor type.private = true if node.visibility.private? + wasm_import_module = nil + process_annotations(annotations) do |annotation_type, ann| case annotation_type when @program.link_annotation @@ -514,6 +516,12 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor @program.warnings.add_warning(ann, "using non-named arguments for Link annotations is deprecated") end + if wasm_import_module && link_annotation.wasm_import_module + ann.raise "multiple wasm import modules specified for lib #{node.name}" + end + + wasm_import_module = link_annotation.wasm_import_module + type.add_link_annotation(link_annotation) when @program.call_convention_annotation type.call_convention = parse_call_convention(ann, type.call_convention) @@ -921,6 +929,10 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor call_convention = scope.call_convention end + if scope.is_a?(LibType) + external.wasm_import_module = scope.wasm_import_module + end + # We fill the arguments and return type in TypeDeclarationVisitor external.doc = node.doc external.call_convention = call_convention diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 1cea37d5bb01..2e0b522c5b98 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -2681,6 +2681,10 @@ module Crystal def type_desc "lib" end + + def wasm_import_module + (@link_annotations.try &.find &.wasm_import_module).try &.wasm_import_module + end end # A `type` (typedef) type inside a `lib` declaration. From abff4f7d89af82ec83139e172a0b7558d54009f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 24 Sep 2022 12:20:01 +0200 Subject: [PATCH 0024/1551] Add `HTTP::Server::Response#redirect` (#10412) Co-authored-by: Sijawusz Pur Rahnama --- spec/std/http/server/response_spec.cr | 52 +++++++++++++++++++ .../server/handlers/static_file_handler.cr | 5 +- src/http/server/response.cr | 22 ++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/spec/std/http/server/response_spec.cr b/spec/std/http/server/response_spec.cr index a78a1720d7f5..718eac89ebe5 100644 --- a/spec/std/http/server/response_spec.cr +++ b/spec/std/http/server/response_spec.cr @@ -348,4 +348,56 @@ describe HTTP::Server::Response do end end end + + describe "#redirect" do + ["/path", URI.parse("/path"), Path.posix("/path")].each do |location| + it "#{location.class} location" do + io = IO::Memory.new + response = Response.new(io) + response.redirect(location) + io.to_s.should eq("HTTP/1.1 302 Found\r\nLocation: /path\r\nContent-Length: 0\r\n\r\n") + end + end + + it "encodes special characters" do + io = IO::Memory.new + response = Response.new(io) + response.redirect("https://example.com/path\nfoo bar") + io.to_s.should eq("HTTP/1.1 302 Found\r\nLocation: https://example.com/path%0Afoo%20bar\r\nContent-Length: 0\r\n\r\n") + end + + it "permanent redirect" do + io = IO::Memory.new + response = Response.new(io) + response.redirect("/path", status: :moved_permanently) + io.to_s.should eq("HTTP/1.1 301 Moved Permanently\r\nLocation: /path\r\nContent-Length: 0\r\n\r\n") + end + + it "with header" do + io = IO::Memory.new + response = Response.new(io) + response.headers["Foo"] = "Bar" + response.redirect("/path", status: :moved_permanently) + io.to_s.should eq("HTTP/1.1 301 Moved Permanently\r\nFoo: Bar\r\nLocation: /path\r\nContent-Length: 0\r\n\r\n") + end + + it "fails if headers already sent" do + io = IO::Memory.new + response = Response.new(io) + response.puts "foo" + response.flush + expect_raises(IO::Error, "Headers already sent") do + response.redirect("/path") + end + end + + it "fails if closed" do + io = IO::Memory.new + response = Response.new(io) + response.close + expect_raises(IO::Error, "Closed stream") do + response.redirect("/path") + end + end + end end diff --git a/src/http/server/handlers/static_file_handler.cr b/src/http/server/handlers/static_file_handler.cr index 22584e3aa9f7..ce3d8efc055a 100644 --- a/src/http/server/handlers/static_file_handler.cr +++ b/src/http/server/handlers/static_file_handler.cr @@ -115,10 +115,7 @@ class HTTP::StaticFileHandler end private def redirect_to(context, url) - context.response.status = :found - - url = URI.encode_path(url.to_s) - context.response.headers.add "Location", url + context.response.redirect url end private def add_cache_headers(response_headers : HTTP::Headers, last_modified : Time) : Nil diff --git a/src/http/server/response.cr b/src/http/server/response.cr index 4803a8f6fb80..de3312a81e57 100644 --- a/src/http/server/response.cr +++ b/src/http/server/response.cr @@ -170,6 +170,28 @@ class HTTP::Server respond_with_status(HTTP::Status.new(status), message) end + # Sends a redirect to *location*. + # + # The value of *location* gets encoded with `URI.encode`. + # + # The *status* determines the HTTP status code which can be + # `HTTP::Status::FOUND` (`302`) for a temporary redirect or + # `HTTP::Status::MOVED_PERMANENTLY` (`301`) for a permanent redirect. + # + # The response gets closed. + # + # Raises `IO::Error` if the response is closed or headers were already + # sent. + def redirect(location : String | URI, status : HTTP::Status = :found) + check_headers + + self.status = status + headers["Location"] = String.build do |io| + URI.encode(location.to_s, io) { |byte| URI.reserved?(byte) || URI.unreserved?(byte) } + end + close + end + private def check_headers raise IO::Error.new "Closed stream" if @original_output.closed? if wrote_headers? From f2599414a3b6a81c5297f75a8d5a403015f2a3c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 25 Sep 2022 03:29:09 +0200 Subject: [PATCH 0025/1551] Revert "Add `HTTP::Server::Response#redirect` (#10412)" (#12517) This reverts commit abff4f7d89af82ec83139e172a0b7558d54009f9. --- spec/std/http/server/response_spec.cr | 52 ------------------- .../server/handlers/static_file_handler.cr | 5 +- src/http/server/response.cr | 22 -------- 3 files changed, 4 insertions(+), 75 deletions(-) diff --git a/spec/std/http/server/response_spec.cr b/spec/std/http/server/response_spec.cr index 718eac89ebe5..a78a1720d7f5 100644 --- a/spec/std/http/server/response_spec.cr +++ b/spec/std/http/server/response_spec.cr @@ -348,56 +348,4 @@ describe HTTP::Server::Response do end end end - - describe "#redirect" do - ["/path", URI.parse("/path"), Path.posix("/path")].each do |location| - it "#{location.class} location" do - io = IO::Memory.new - response = Response.new(io) - response.redirect(location) - io.to_s.should eq("HTTP/1.1 302 Found\r\nLocation: /path\r\nContent-Length: 0\r\n\r\n") - end - end - - it "encodes special characters" do - io = IO::Memory.new - response = Response.new(io) - response.redirect("https://example.com/path\nfoo bar") - io.to_s.should eq("HTTP/1.1 302 Found\r\nLocation: https://example.com/path%0Afoo%20bar\r\nContent-Length: 0\r\n\r\n") - end - - it "permanent redirect" do - io = IO::Memory.new - response = Response.new(io) - response.redirect("/path", status: :moved_permanently) - io.to_s.should eq("HTTP/1.1 301 Moved Permanently\r\nLocation: /path\r\nContent-Length: 0\r\n\r\n") - end - - it "with header" do - io = IO::Memory.new - response = Response.new(io) - response.headers["Foo"] = "Bar" - response.redirect("/path", status: :moved_permanently) - io.to_s.should eq("HTTP/1.1 301 Moved Permanently\r\nFoo: Bar\r\nLocation: /path\r\nContent-Length: 0\r\n\r\n") - end - - it "fails if headers already sent" do - io = IO::Memory.new - response = Response.new(io) - response.puts "foo" - response.flush - expect_raises(IO::Error, "Headers already sent") do - response.redirect("/path") - end - end - - it "fails if closed" do - io = IO::Memory.new - response = Response.new(io) - response.close - expect_raises(IO::Error, "Closed stream") do - response.redirect("/path") - end - end - end end diff --git a/src/http/server/handlers/static_file_handler.cr b/src/http/server/handlers/static_file_handler.cr index ce3d8efc055a..22584e3aa9f7 100644 --- a/src/http/server/handlers/static_file_handler.cr +++ b/src/http/server/handlers/static_file_handler.cr @@ -115,7 +115,10 @@ class HTTP::StaticFileHandler end private def redirect_to(context, url) - context.response.redirect url + context.response.status = :found + + url = URI.encode_path(url.to_s) + context.response.headers.add "Location", url end private def add_cache_headers(response_headers : HTTP::Headers, last_modified : Time) : Nil diff --git a/src/http/server/response.cr b/src/http/server/response.cr index de3312a81e57..4803a8f6fb80 100644 --- a/src/http/server/response.cr +++ b/src/http/server/response.cr @@ -170,28 +170,6 @@ class HTTP::Server respond_with_status(HTTP::Status.new(status), message) end - # Sends a redirect to *location*. - # - # The value of *location* gets encoded with `URI.encode`. - # - # The *status* determines the HTTP status code which can be - # `HTTP::Status::FOUND` (`302`) for a temporary redirect or - # `HTTP::Status::MOVED_PERMANENTLY` (`301`) for a permanent redirect. - # - # The response gets closed. - # - # Raises `IO::Error` if the response is closed or headers were already - # sent. - def redirect(location : String | URI, status : HTTP::Status = :found) - check_headers - - self.status = status - headers["Location"] = String.build do |io| - URI.encode(location.to_s, io) { |byte| URI.reserved?(byte) || URI.unreserved?(byte) } - end - close - end - private def check_headers raise IO::Error.new "Closed stream" if @original_output.closed? if wrote_headers? From 0335ba3a52039cfccb3f196eb7bbccb1b1d94c61 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 26 Sep 2022 04:58:58 -0300 Subject: [PATCH 0026/1551] Interpreter (repl): use new MainVisitor each time we need to interpret code (#12512) --- .../crystal/interpreter/interpreter.cr | 2 ++ src/compiler/crystal/interpreter/repl.cr | 2 ++ src/compiler/crystal/semantic/main_visitor.cr | 21 +++++++++++-------- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index bf9fbef71fdd..f44c3af756ae 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -1281,6 +1281,8 @@ class Crystal::Repl::Interpreter ) next unless line_node + main_visitor = MainVisitor.new(from_main_visitor: main_visitor) + vars_size_before_semantic = main_visitor.vars.size line_node = @context.program.normalize(line_node) diff --git a/src/compiler/crystal/interpreter/repl.cr b/src/compiler/crystal/interpreter/repl.cr index 1a07333064a7..6a776f66c22f 100644 --- a/src/compiler/crystal/interpreter/repl.cr +++ b/src/compiler/crystal/interpreter/repl.cr @@ -95,6 +95,8 @@ class Crystal::Repl end private def interpret(node : ASTNode) + @main_visitor = MainVisitor.new(from_main_visitor: @main_visitor) + node = @program.normalize(node) node = @program.semantic(node, main_visitor: @main_visitor) @interpreter.interpret(node, @main_visitor.meta_vars) diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 34bbd236b07f..3dfa321b7407 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -98,12 +98,13 @@ module Crystal @unreachable = false @is_initialize = false + @inside_is_a = false @in_type_args = 0 - @while_stack : Array(While) + @while_stack = [] of While @type_filters : TypeFilters? - @needs_type_filters : Int32 - @typeof_nest : Int32 + @needs_type_filters = 0 + @typeof_nest = 0 @found_self_in_initialize_call : Array(ASTNode)? @used_ivars_in_calls_in_initialize : Hash(String, Array(ASTNode))? @block_context : Block? @@ -114,16 +115,10 @@ module Crystal def initialize(program, vars = MetaVars.new, @typed_def = nil, meta_vars = nil) super(program, vars) - @while_stack = [] of While - @needs_type_filters = 0 - @typeof_nest = 0 @is_initialize = !!(typed_def && ( typed_def.name == "initialize" || typed_def.name.starts_with?("initialize:") # Because of expanded methods from named args )) - @found_self_in_initialize_call = nil - @used_ivars_in_calls_in_initialize = nil - @inside_is_a = false # We initialize meta_vars from vars given in the constructor. # We store those meta vars either in the typed def or in the program @@ -144,6 +139,14 @@ module Crystal @meta_vars = meta_vars end + def initialize(*, from_main_visitor : MainVisitor) + super(from_main_visitor.@program, from_main_visitor.@vars) + @meta_vars = from_main_visitor.@meta_vars + @typed_def = from_main_visitor.@typed_def + @scope = from_main_visitor.@scope + @path_lookup = from_main_visitor.@path_lookup + end + def visit_any(node) @unreachable = false super From 278819e4e08be6b96932fed5cb6155721dceb683 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 26 Sep 2022 15:59:29 +0800 Subject: [PATCH 0027/1551] macOS: Fix call stack when executable path contains symlinks (#12504) --- src/exception/call_stack/mach_o.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exception/call_stack/mach_o.cr b/src/exception/call_stack/mach_o.cr index 7f6bbc0d3756..fb1dee202d48 100644 --- a/src/exception/call_stack/mach_o.cr +++ b/src/exception/call_stack/mach_o.cr @@ -101,10 +101,10 @@ struct Exception::CallStack end end - program = String.new(buffer) + program = File.real_path(String.new(buffer)) LibC._dyld_image_count.times do |i| - if program == String.new(LibC._dyld_get_image_name(i)) + if program == File.real_path(String.new(LibC._dyld_get_image_name(i))) return LibC._dyld_get_image_vmaddr_slide(i) end end From 78fb6e0f7187b148b42f03adce4676a7ed63d550 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 26 Sep 2022 15:59:39 +0800 Subject: [PATCH 0028/1551] Fix restriction comparison between `Metaclass` and `Path` (#12523) --- spec/compiler/semantic/restrictions_spec.cr | 66 +++++++++++++++---- src/compiler/crystal/semantic/restrictions.cr | 10 +-- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/spec/compiler/semantic/restrictions_spec.cr b/spec/compiler/semantic/restrictions_spec.cr index 88523c28a373..11414769cd1d 100644 --- a/spec/compiler/semantic/restrictions_spec.cr +++ b/spec/compiler/semantic/restrictions_spec.cr @@ -217,6 +217,58 @@ describe "Restrictions" do end end + describe "Metaclass vs Path" do + {% for type in [Object, Value, Class] %} + it "inserts metaclass before {{ type }}" do + assert_type(%( + def foo(a : {{ type }}) + 1 + end + + def foo(a : Int32.class) + true + end + + foo(Int32) + )) { bool } + end + + it "keeps metaclass before {{ type }}" do + assert_type(%( + def foo(a : Int32.class) + true + end + + def foo(a : {{ type }}) + 1 + end + + foo(Int32) + )) { bool } + end + {% end %} + + it "doesn't error if path is undefined and method is not called (1) (#12516)" do + assert_no_errors <<-CR + def foo(a : Int32.class) + end + + def foo(a : Foo) + end + CR + end + + it "doesn't error if path is undefined and method is not called (2) (#12516)" do + assert_no_errors <<-CR + def foo(a : Foo) + end + + def foo(a : Int32.class) + end + CR + end + end + describe "Path vs Path" do it "inserts typed Path before untyped Path" do assert_type(%( @@ -1011,20 +1063,6 @@ describe "Restrictions" do "expected argument #2 to 'foo' to be StaticArray(UInt8, 10), not StaticArray(UInt8, 11)" end - it "gives precedence to T.class over Class (#7392)" do - assert_type(%( - def foo(x : Class) - 'a' - end - - def foo(x : Int32.class) - 1 - end - - foo(Int32) - )) { int32 } - end - it "restricts aliased typedef type (#9474)" do assert_type(%( lib A diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 0599c8daa820..73ddeb477655 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -702,11 +702,11 @@ module Crystal end def restriction_of?(other : Path, owner, self_free_vars = nil, other_free_vars = nil) - other_type = owner.lookup_type(other) - - # Special case: when comparing Foo.class to Class, Foo.class has precedence - if other_type == other_type.program.class_type - return true + if other_type = owner.lookup_type?(other) + # Special case: all metaclasses are subtypes of Class + if other_type.program.class_type.implements?(other_type) + return true + end end super From e05516f0005ce95bab0b20bc5632d3f5a3f88920 Mon Sep 17 00:00:00 2001 From: Giovanni Cappellotto Date: Mon, 26 Sep 2022 08:32:53 -0400 Subject: [PATCH 0029/1551] Parser: Rename `arg*` to `param*` (#12235) --- spec/compiler/parser/parser_spec.cr | 9 +- src/compiler/crystal/syntax/parser.cr | 230 +++++++++++++------------- 2 files changed, 120 insertions(+), 119 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index b2a0a200646a..aad28afddf1b 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -348,8 +348,8 @@ module Crystal assert_syntax_error "def foo(x, *); 1; end", "named parameters must follow bare *" it_parses "def foo(x, *, y, &); 1; end", Def.new("foo", args: ["x".arg, "".arg, "y".arg], body: 1.int32, splat_index: 1, block_arg: Arg.new(""), yields: 0) - assert_syntax_error "def foo(var = 1 : Int32); end", "the syntax for a parameter with a default value V and type T is `arg : T = V`" - assert_syntax_error "def foo(var = x : Int); end", "the syntax for a parameter with a default value V and type T is `arg : T = V`" + assert_syntax_error "def foo(var = 1 : Int32); end", "the syntax for a parameter with a default value V and type T is `param : T = V`" + assert_syntax_error "def foo(var = x : Int); end", "the syntax for a parameter with a default value V and type T is `param : T = V`" it_parses "def foo(**args)\n1\nend", Def.new("foo", body: 1.int32, double_splat: "args".arg) it_parses "def foo(x, **args)\n1\nend", Def.new("foo", body: 1.int32, args: ["x".arg], double_splat: "args".arg) @@ -1915,8 +1915,9 @@ module Crystal assert_syntax_error "/foo)/", "invalid regex" assert_syntax_error "def =\nend" assert_syntax_error "def foo; A = 1; end", "dynamic constant assignment. Constants can only be declared at the top level or inside other types." - assert_syntax_error "{1, ->{ |x| x } }", %(unexpected token: "|") - assert_syntax_error "{1, ->do\n|x| x\end }", %(unexpected token: "|") + assert_syntax_error "{1, ->{ |x| x } }", "unexpected token: \"|\", proc literals specify their parameters like this: ->(x : Type) { ... }" + assert_syntax_error "{1, ->do\n|x| x\end }", "unexpected token: \"|\", proc literals specify their parameters like this: ->(x : Type) { ... }" + assert_syntax_error "{1, ->{ |_| x } }", "unexpected token: \"|\", proc literals specify their parameters like this: ->(param : Type) { ... }" assert_syntax_error "1 while 3", "trailing `while` is not supported" assert_syntax_error "1 until 3", "trailing `until` is not supported" diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 216aaa698d46..ce3a1cc9b38d 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -1829,17 +1829,17 @@ module Crystal return parse_fun_pointer unless @token.keyword?(:do) end - args = [] of Arg + params = [] of Arg if @token.type.op_lparen? next_token_skip_space_or_newline while !@token.type.op_rparen? param_location = @token.location - arg = parse_fun_literal_arg.at(param_location) - if args.any? &.name.==(arg.name) - raise "duplicated proc literal parameter name: #{arg.name}", param_location + param = parse_fun_literal_param.at(param_location) + if params.any? &.name.==(param.name) + raise "duplicated proc literal parameter name: #{param.name}", param_location end - args << arg + params << param end next_token_skip_space_or_newline end @@ -1855,7 +1855,7 @@ module Crystal end with_lexical_var_scope do - push_vars args + push_vars params end_location = nil @@ -1875,7 +1875,7 @@ module Crystal unexpected_token end - a_def = Def.new("->", args, body, return_type: return_type).at(location).at_end(end_location) + a_def = Def.new("->", params, body, return_type: return_type).at(location).at_end(end_location) ProcLiteral.new(a_def).at(location).at_end(end_location) end end @@ -1892,7 +1892,7 @@ module Crystal next_token_skip_space_or_newline msg << ", ..." if @token.type.op_comma? else - msg << "arg : Type" + msg << "param : Type" end msg << ") { ... }" end @@ -1901,7 +1901,7 @@ module Crystal end end - def parse_fun_literal_arg + def parse_fun_literal_param name = check_ident next_token_skip_space_or_newline @@ -3049,7 +3049,7 @@ module Crystal next_token_skip_space end - args = [] of Arg + params = [] of Arg found_default_value = false found_splat = false @@ -3063,7 +3063,7 @@ module Crystal when .op_lparen? next_token_skip_space_or_newline while !@token.type.op_rparen? - extras = parse_arg(args, + extras = parse_param(params, extra_assigns: nil, parentheses: true, found_default_value: found_default_value, @@ -3078,10 +3078,10 @@ module Crystal found_splat = true end if extras.double_splat - double_splat = args.pop + double_splat = params.pop found_double_splat = double_splat end - if block_arg = extras.block_arg + if block_param = extras.block_arg check :OP_RPAREN break elsif @token.type.op_comma? @@ -3093,8 +3093,8 @@ module Crystal index += 1 end - if splat_index == args.size - 1 && args.last.name.empty? - raise "named parameters must follow bare *", args.last.location.not_nil! + if splat_index == params.size - 1 && params.last.name.empty? + raise "named parameters must follow bare *", params.last.location.not_nil! end next_token @@ -3122,7 +3122,7 @@ module Crystal body, end_location = parse_macro_body(name_location) end - node = Macro.new name, args, body, block_arg, splat_index, double_splat: double_splat + node = Macro.new name, params, body, block_param, splat_index, double_splat: double_splat node.name_location = name_location node.doc = doc node.end_location = end_location @@ -3491,7 +3491,7 @@ module Crystal next_token_skip_space end - args = [] of Arg + params = [] of Arg extra_assigns = [] of ASTNode if @token.type.op_period? @@ -3541,7 +3541,7 @@ module Crystal when .op_lparen? next_token_skip_space_or_newline while !@token.type.op_rparen? - extras = parse_arg(args, + extras = parse_param(params, extra_assigns: extra_assigns, parentheses: true, found_default_value: found_default_value, @@ -3557,11 +3557,11 @@ module Crystal found_splat = true end if extras.double_splat - double_splat = args.pop + double_splat = params.pop found_double_splat = double_splat end - if block_arg = extras.block_arg - compute_block_arg_yields block_arg + if block_param = extras.block_arg + compute_block_arg_yields block_param check :OP_RPAREN found_block = true break @@ -3575,15 +3575,15 @@ module Crystal end if name.ends_with?('=') - if name != "[]=" && (args.size > 1 || found_splat || found_double_splat) + if name != "[]=" && (params.size > 1 || found_splat || found_double_splat) raise "setter method '#{name}' cannot have more than one parameter" elsif found_block raise "setter method '#{name}' cannot have a block" end end - if splat_index == args.size - 1 && args.last.name.empty? - raise "named parameters must follow bare *", args.last.location.not_nil! + if splat_index == params.size - 1 && params.last.name.empty? + raise "named parameters must follow bare *", params.last.location.not_nil! end next_token_skip_space @@ -3654,7 +3654,7 @@ module Crystal @def_nest -= 1 @doc_enabled = !!@wants_doc - node = Def.new name, args, body, receiver, block_arg, return_type, @is_macro_def, @yields, is_abstract, splat_index, double_splat: double_splat, free_vars: free_vars + node = Def.new name, params, body, receiver, block_param, return_type, @is_macro_def, @yields, is_abstract, splat_index, double_splat: double_splat, free_vars: free_vars node.name_location = name_location set_visibility node node.end_location = end_location @@ -3707,10 +3707,10 @@ module Crystal splat : Bool, double_splat : Bool - def parse_arg(args, extra_assigns, parentheses, found_default_value, found_splat, found_double_splat, allow_restrictions) + def parse_param(params, extra_assigns, parentheses, found_default_value, found_splat, found_double_splat, allow_restrictions) annotations = nil - # Parse annotations first since they would be before any actual arg tokens. + # Parse annotations first since they would be before any actual param tokens. # Do this in a loop to account for multiple annotations. while @token.type.op_at_lsquare? (annotations ||= Array(Annotation).new) << parse_annotation @@ -3719,20 +3719,20 @@ module Crystal if @token.type.op_amp? next_token_skip_space_or_newline - block_arg = parse_block_arg(extra_assigns, annotations) + block_param = parse_block_param(extra_assigns, annotations) skip_space_or_newline - # When block_arg.name is empty, this is an anonymous parameter. + # When block_param.name is empty, this is an anonymous parameter. # An anonymous parameter should not conflict other parameters names. - # (In fact `args` may contain anonymous splat parameter. See #9108). + # (In fact `params` may contain anonymous splat parameter. See #9108). # So check is skipped. - unless block_arg.name.empty? - conflict_arg = args.any?(&.name.==(block_arg.name)) - conflict_double_splat = found_double_splat && found_double_splat.name == block_arg.name - if conflict_arg || conflict_double_splat - raise "duplicated def parameter name: #{block_arg.name}", block_arg.location.not_nil! + unless block_param.name.empty? + conflict_param = params.any?(&.name.==(block_param.name)) + conflict_double_splat = found_double_splat && found_double_splat.name == block_param.name + if conflict_param || conflict_double_splat + raise "duplicated def parameter name: #{block_param.name}", block_param.location.not_nil! end end - return ArgExtras.new(block_arg, false, false, false) + return ArgExtras.new(block_param, false, false, false) end if found_double_splat @@ -3741,7 +3741,7 @@ module Crystal splat = false double_splat = false - arg_location = @token.location + param_location = @token.location allow_external_name = true case @token.type @@ -3764,20 +3764,20 @@ module Crystal found_space = false if splat && (@token.type.op_comma? || @token.type.op_rparen?) - arg_name = "" - uses_arg = false + param_name = "" + uses_param = false allow_restrictions = false else - arg_location = @token.location - arg_name, external_name, found_space, uses_arg = parse_arg_name(arg_location, extra_assigns, allow_external_name: allow_external_name) + param_location = @token.location + param_name, external_name, found_space, uses_param = parse_param_name(param_location, extra_assigns, allow_external_name: allow_external_name) - args.each do |arg| - if arg.name == arg_name - raise "duplicated def parameter name: #{arg_name}", arg_location + params.each do |param| + if param.name == param_name + raise "duplicated def parameter name: #{param_name}", param_location end - if arg.external_name == external_name - raise "duplicated def parameter external name: #{external_name}", arg_location + if param.external_name == external_name + raise "duplicated def parameter external name: #{external_name}", param_location end end @@ -3834,37 +3834,37 @@ module Crystal skip_space else if found_default_value && !found_splat && !splat && !double_splat - raise "parameter must have a default value", arg_location + raise "parameter must have a default value", param_location end end unless found_colon if @token.type.symbol? - raise "the syntax for a parameter with a default value V and type T is `arg : T = V`", @token + raise "the syntax for a parameter with a default value V and type T is `param : T = V`", @token end if allow_restrictions && @token.type.op_colon? - raise "the syntax for a parameter with a default value V and type T is `arg : T = V`", @token + raise "the syntax for a parameter with a default value V and type T is `param : T = V`", @token end end - raise "BUG: arg_name is nil" unless arg_name + raise "BUG: param_name is nil" unless param_name - arg = Arg.new(arg_name, default_value, restriction, external_name: external_name, parsed_annotations: annotations).at(arg_location) - args << arg - push_var arg + param = Arg.new(param_name, default_value, restriction, external_name: external_name, parsed_annotations: annotations).at(param_location) + params << param + push_var param ArgExtras.new(nil, !!default_value, splat, !!double_splat) end - def parse_block_arg(extra_assigns, annotations) + def parse_block_param(extra_assigns, annotations) name_location = @token.location if @token.type.op_rparen? || @token.type.newline? || @token.type.op_colon? - arg_name = "" + param_name = "" else - arg_name, external_name, found_space, uses_arg = parse_arg_name(name_location, extra_assigns, allow_external_name: false) - @uses_block_arg = true if uses_arg + param_name, external_name, found_space, uses_param = parse_param_name(name_location, extra_assigns, allow_external_name: false) + @uses_block_arg = true if uses_param end inputs = nil @@ -3878,16 +3878,16 @@ module Crystal type_spec = parse_bare_proc_type end - block_arg = Arg.new(arg_name, restriction: type_spec, parsed_annotations: annotations).at(name_location) + block_param = Arg.new(param_name, restriction: type_spec, parsed_annotations: annotations).at(name_location) - push_var block_arg + push_var block_param - @block_arg_name = block_arg.name + @block_arg_name = block_param.name - block_arg + block_param end - def parse_arg_name(location, extra_assigns, allow_external_name) + def parse_param_name(location, extra_assigns, allow_external_name) do_next_token = true found_string_literal = false invalid_internal_name = nil @@ -3920,17 +3920,17 @@ module Crystal raise "cannot use '#{@token}' as a parameter name", @token end - arg_name = @token.value.to_s - if arg_name == external_name + param_name = @token.value.to_s + if param_name == external_name raise "when specified, external name must be different than internal name", @token end - uses_arg = false + uses_param = false do_next_token = true when .instance_var? # Transform `def foo(@x); end` to `def foo(x); @x = x; end` - arg_name = @token.value.to_s[1..-1] - if arg_name == external_name + param_name = @token.value.to_s[1..-1] + if param_name == external_name raise "when specified, external name must be different than internal name", @token end @@ -3944,40 +3944,40 @@ module Crystal # def method(select __arg0) # @select = __arg0 # end - if !external_name && invalid_internal_name?(arg_name) - arg_name, external_name = temp_arg_name, arg_name + if !external_name && invalid_internal_name?(param_name) + param_name, external_name = temp_arg_name, param_name end ivar = InstanceVar.new(@token.value.to_s).at(location) - var = Var.new(arg_name).at(location) + var = Var.new(param_name).at(location) assign = Assign.new(ivar, var).at(location) if extra_assigns extra_assigns.push assign else raise "can't use @instance_variable here" end - uses_arg = true + uses_param = true do_next_token = true when .class_var? - arg_name = @token.value.to_s[2..-1] - if arg_name == external_name + param_name = @token.value.to_s[2..-1] + if param_name == external_name raise "when specified, external name must be different than internal name", @token end # Same case as :INSTANCE_VAR for things like @select - if !external_name && invalid_internal_name?(arg_name) - arg_name, external_name = temp_arg_name, arg_name + if !external_name && invalid_internal_name?(param_name) + param_name, external_name = temp_arg_name, param_name end cvar = ClassVar.new(@token.value.to_s).at(location) - var = Var.new(arg_name).at(location) + var = Var.new(param_name).at(location) assign = Assign.new(cvar, var).at(location) if extra_assigns extra_assigns.push assign else raise "can't use @@class_var here" end - uses_arg = true + uses_param = true do_next_token = true else if external_name @@ -3987,7 +3987,7 @@ module Crystal if invalid_internal_name raise "cannot use '#{invalid_internal_name}' as a parameter name", invalid_internal_name end - arg_name = external_name + param_name = external_name else unexpected_token end @@ -4000,7 +4000,7 @@ module Crystal skip_space - {arg_name, external_name, found_space, uses_arg} + {param_name, external_name, found_space, uses_param} end def invalid_internal_name?(keyword) @@ -4332,11 +4332,11 @@ module Crystal def parse_block2 location = @token.location - block_args = [] of Var + block_params = [] of Var all_names = [] of String extra_assigns = nil block_body = nil - arg_index = 0 + param_index = 0 splat_index = nil slash_is_regex! @@ -4348,7 +4348,7 @@ module Crystal if splat_index raise "splat block parameter already specified", @token end - splat_index = arg_index + splat_index = param_index next_token end @@ -4358,16 +4358,16 @@ module Crystal raise "cannot use '#{@token}' as a block parameter name", @token end - arg_name = @token.value.to_s + param_name = @token.value.to_s - if all_names.includes?(arg_name) - raise "duplicated block parameter name: #{arg_name}", @token + if all_names.includes?(param_name) + raise "duplicated block parameter name: #{param_name}", @token end - all_names << arg_name + all_names << param_name when .underscore? - arg_name = "_" + param_name = "_" when .op_lparen? - block_arg_name = temp_arg_name + block_param_name = temp_arg_name next_token_skip_space_or_newline @@ -4379,26 +4379,26 @@ module Crystal raise "cannot use '#{@token}' as a block parameter name", @token end - sub_arg_name = @token.value.to_s + sub_param_name = @token.value.to_s - if all_names.includes?(sub_arg_name) - raise "duplicated block parameter name: #{sub_arg_name}", @token + if all_names.includes?(sub_param_name) + raise "duplicated block parameter name: #{sub_param_name}", @token end - all_names << sub_arg_name + all_names << sub_param_name when .underscore? - sub_arg_name = "_" + sub_param_name = "_" else raise "expecting block parameter name, not #{@token.type}", @token end - push_var_name sub_arg_name + push_var_name sub_param_name location = @token.location - unless sub_arg_name == "_" + unless sub_param_name == "_" extra_assigns ||= [] of ASTNode extra_assigns << Assign.new( - Var.new(sub_arg_name).at(location), - Call.new(Var.new(block_arg_name).at(location), "[]", NumberLiteral.new(i)).at(location) + Var.new(sub_param_name).at(location), + Call.new(Var.new(block_param_name).at(location), "[]", NumberLiteral.new(i)).at(location) ).at(location) end @@ -4416,13 +4416,13 @@ module Crystal i += 1 end - arg_name = block_arg_name + param_name = block_param_name else raise "expecting block parameter name, not #{@token.type}", @token end - var = Var.new(arg_name).at(@token.location) - block_args << var + var = Var.new(param_name).at(@token.location) + block_params << var next_token_skip_space_or_newline @@ -4436,7 +4436,7 @@ module Crystal raise "expecting ',' or '|', not #{@token}", @token end - arg_index += 1 + param_index += 1 end next_token_skip_statement_end else @@ -4444,7 +4444,7 @@ module Crystal end with_lexical_var_scope do - push_vars block_args + push_vars block_params block_body = parse_expressions @@ -4460,7 +4460,7 @@ module Crystal end block_body, end_location = yield block_body - Block.new(block_args, block_body, splat_index).at(location).at_end(end_location) + Block.new(block_params, block_body, splat_index).at(location).at_end(end_location) end end @@ -5620,7 +5620,7 @@ module Crystal real_name = name end - args = [] of Arg + params = [] of Arg varargs = false if @token.type.op_lparen? @@ -5634,30 +5634,30 @@ module Crystal end if @token.type.ident? - arg_name = @token.value.to_s - arg_location = @token.location + param_name = @token.value.to_s + param_location = @token.location next_token_skip_space_or_newline check :OP_COLON next_token_skip_space_or_newline - arg_type = parse_bare_proc_type + param_type = parse_bare_proc_type skip_space_or_newline - args.each do |arg| - if arg.name == arg_name - raise "duplicated fun parameter name: #{arg_name}", arg_location + params.each do |param| + if param.name == param_name + raise "duplicated fun parameter name: #{param_name}", param_location end end - args << Arg.new(arg_name, nil, arg_type).at(arg_location) + params << Arg.new(param_name, nil, param_type).at(param_location) - push_var_name arg_name if require_body + push_var_name param_name if require_body else if top_level raise "top-level fun parameter must have a name", @token end - arg_type = parse_union_type - args << Arg.new("", nil, arg_type).at(arg_type.location) + param_type = parse_union_type + params << Arg.new("", nil, param_type).at(param_type.location) end if @token.type.op_comma? @@ -5696,7 +5696,7 @@ module Crystal end_location = token_end_location end - fun_def = FunDef.new name, args, return_type, varargs, body, real_name + fun_def = FunDef.new name, params, return_type, varargs, body, real_name fun_def.doc = doc fun_def.at(location).at_end(end_location) end From a6704abe60b065611fb0c51f7a842f21e692dc4b Mon Sep 17 00:00:00 2001 From: Giovanni Cappellotto Date: Mon, 26 Sep 2022 08:33:46 -0400 Subject: [PATCH 0030/1551] Fix test cases (#12508) --- spec/compiler/parser/parser_spec.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index aad28afddf1b..b5e0a0301fc6 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -249,8 +249,8 @@ module Crystal it_parses "def foo(@#{kw}); end", Def.new("foo", [Arg.new("__arg0", external_name: kw.to_s)], [Assign.new("@#{kw}".instance_var, "__arg0".var)] of ASTNode) it_parses "def foo(@@#{kw}); end", Def.new("foo", [Arg.new("__arg0", external_name: kw.to_s)], [Assign.new("@@#{kw}".class_var, "__arg0".var)] of ASTNode) - assert_syntax_error "foo { |#{kw})| }", "cannot use '#{kw}' as a block parameter name", 1, 8 - assert_syntax_error "foo { |(#{kw}))| }", "cannot use '#{kw}' as a block parameter name", 1, 9 + assert_syntax_error "foo { |#{kw}| }", "cannot use '#{kw}' as a block parameter name", 1, 8 + assert_syntax_error "foo { |(#{kw})| }", "cannot use '#{kw}' as a block parameter name", 1, 9 end it_parses "def self.foo\n1\nend", Def.new("foo", body: 1.int32, receiver: "self".var) From 3e13d0e00c0ec68bea7ee5e23a5eb4934ba32683 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Mon, 26 Sep 2022 08:34:09 -0400 Subject: [PATCH 0031/1551] Document how to change base type of an enum (#9803) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/enum.cr | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/enum.cr b/src/enum.cr index 0f2fd022e0cf..c3438d7812dd 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -86,6 +86,20 @@ # puts "Got blue" # end # ``` +# +# ### Changing the Base Type +# +# The type of the underlying enum value is `Int32` by default, but it can be changed to any type in `Int::Primitive`. +# +# ``` +# enum Color : UInt8 +# Red +# Green +# Blue +# end +# +# Color::Red.value # : UInt8 +# ``` struct Enum include Comparable(self) From b7e28a82742a3bc17c701be07effdb4c9cbaa937 Mon Sep 17 00:00:00 2001 From: David Keller Date: Tue, 27 Sep 2022 11:59:26 +0200 Subject: [PATCH 0032/1551] Add U/Int128 to isqrt spec (#11976) --- spec/std/math_spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/std/math_spec.cr b/spec/std/math_spec.cr index df2fdbe67efd..51c945ca3453 100644 --- a/spec/std/math_spec.cr +++ b/spec/std/math_spec.cr @@ -46,7 +46,7 @@ describe "Math" do Math.isqrt(9).should eq(3) Math.isqrt(8).should eq(2) Math.isqrt(4).should eq(2) - {% for type in [UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64] %} + {% for type in [UInt8, UInt16, UInt32, UInt64, UInt128, Int8, Int16, Int32, Int64, Int128] %} %val = {{type}}.new 42 %exp = {{type}}.new 6 Math.isqrt(%val).should eq(%exp) From 0d285366c17ea815681c3d0e54a19435c8f60bc2 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Tue, 27 Sep 2022 07:00:15 -0300 Subject: [PATCH 0033/1551] WASM: Add support for `wasi-sdk 16`: don't rely on `__original_main` (#12450) These two functions used to be provided by libc in the past: WebAssembly/wasi-libc@5d8a140/libc-bottom-half/sources/__main_argc_argv.c WebAssembly/wasi-libc@5d8a140/libc-bottom-half/sources/__original_main.c They were permanently removed. --- src/crystal/system/wasi/main.cr | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/crystal/system/wasi/main.cr b/src/crystal/system/wasi/main.cr index 0d4482e6bcab..57ffd5f3f43c 100644 --- a/src/crystal/system/wasi/main.cr +++ b/src/crystal/system/wasi/main.cr @@ -10,9 +10,8 @@ lib LibC fun __wasm_call_ctors fun __wasm_call_dtors - # Generated by the compiler or by wasi-lib (depending on the version) to call the actual main function. - # It handles calling WASI's `args_get` to get ARGV. - fun __original_main : Int32 + # Provided by wasi-libc to obtain argc/argv and call into `__main_argc_argv`. + fun __main_void : Int32 end # As part of the WASI Application ABI, a "command" program must export a `_start` function that takes no @@ -23,7 +22,12 @@ end # alive and ready until they are unloaded from memory. fun _start LibC.__wasm_call_ctors - status = LibC.__original_main + status = LibC.__main_void LibC.__wasm_call_dtors LibWasi.proc_exit(status) if status != 0 end + +# `__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 From 543acb079b5e4731644840958ac0aee65e1358ef Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 27 Sep 2022 07:40:54 -0300 Subject: [PATCH 0034/1551] Interpreter: allow inspecting block vars without affecting program (#12520) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Interpreter: allow inspecting block vars without affecting program * Better check for existing var inside pry * Restore stack from original local vars max bytesize, not modified one * Update src/compiler/crystal/interpreter/interpreter.cr Co-authored-by: Johannes Müller --- .../crystal/interpreter/interpreter.cr | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index f44c3af756ae..c9d3cac0a72f 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -167,8 +167,10 @@ class Crystal::Repl::Interpreter compiled_def = @compiled_def # Declare or migrate local variables + # TODO: we should also migrate variables if we are outside of a block + # in a pry session, but that's tricky so we'll leave it for later. if !compiled_def || in_pry - migrate_local_vars(@local_vars, meta_vars) + migrate_local_vars(@local_vars, meta_vars) if @local_vars.block_level == 0 # TODO: is it okay to assume this is always the program? Probably not. # Check if we need a local variable for the closure context @@ -187,7 +189,13 @@ class Crystal::Repl::Interpreter # Closured vars don't belong in the local variables table next if meta_var.closured? - existing_type = @local_vars.type?(name, 0) + # Check if the var already exists from the current block upwards + existing_type = nil + @local_vars.block_level.downto(0) do |level| + existing_type = @local_vars.type?(name, level) + break if existing_type + end + if existing_type if existing_type != meta_var.type raise "BUG: can't change type of local variable #{name} from #{existing_type} to #{meta_var.type} yet" @@ -1177,9 +1185,10 @@ class Crystal::Repl::Interpreter # Remember the portion from stack_bottom + local_vars.max_bytesize up to stack # because it might happen that the child interpreter will overwrite some # of that if we already have some values in the stack past the local vars - data_size = stack - (stack_bottom + local_vars.max_bytesize) + original_local_vars_max_bytesize = local_vars.max_bytesize + data_size = stack - (stack_bottom + original_local_vars_max_bytesize) data = Pointer(Void).malloc(data_size).as(UInt8*) - data.copy_from(stack_bottom + local_vars.max_bytesize, data_size) + data.copy_from(stack_bottom + original_local_vars_max_bytesize, data_size) gatherer = LocalVarsGatherer.new(location, a_def) gatherer.gather @@ -1324,7 +1333,7 @@ class Crystal::Repl::Interpreter end # Restore the stack data in case it tas overwritten - (stack_bottom + local_vars.max_bytesize).copy_from(data, data_size) + (stack_bottom + original_local_vars_max_bytesize).copy_from(data, data_size) end private def whereami(a_def : Def, location : Location) From 98492339fa361d8c5f91a12d2c8cfd6385fbaa88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 28 Sep 2022 11:01:16 +0200 Subject: [PATCH 0035/1551] Add warning about concurrent requests in `HTTP::Client` (#12527) --- src/http/client.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/http/client.cr b/src/http/client.cr index a819741b728b..2fb0cd9d7c45 100644 --- a/src/http/client.cr +++ b/src/http/client.cr @@ -58,6 +58,8 @@ # client.close # ``` # +# WARNING: A single `HTTP::Client` instance is not safe for concurrent use by multiple fibers. +# # ### Compression # # If `compress` isn't set to `false`, and no `Accept-Encoding` header is explicitly specified, From b262838dee4d4a61a2b0c5d1a50a007f1dca1385 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 28 Sep 2022 08:28:11 -0300 Subject: [PATCH 0036/1551] Interpreter: check upcast in nilable cast (#12533) --- spec/compiler/interpreter/casts_spec.cr | 30 ++++++++++++++++++++ src/compiler/crystal/interpreter/compiler.cr | 6 ++++ 2 files changed, 36 insertions(+) diff --git a/spec/compiler/interpreter/casts_spec.cr b/spec/compiler/interpreter/casts_spec.cr index a4ec2fb9b776..57707bc80d9a 100644 --- a/spec/compiler/interpreter/casts_spec.cr +++ b/spec/compiler/interpreter/casts_spec.cr @@ -452,5 +452,35 @@ describe Crystal::Repl::Interpreter do end CODE end + + it "upcasts in nilable cast (#12532)" do + interpret(<<-CODE).should eq(2) + struct Nil + def foo + 0 + end + end + + module A + def foo + 1 + end + end + + class B + include A + + def foo + 2 + end + end + + class C + include A + end + + B.new.as?(A).foo + CODE + end end end diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index dc8e1fec68a6..39296b49ad6b 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -1649,6 +1649,12 @@ class Crystal::Repl::Compiler < Crystal::Visitor node.obj.accept self + if node.upcast? + upcast node.obj, obj_type, node.non_nilable_type + upcast node.obj, node.non_nilable_type, node.type + return + end + # Check if obj is a `to_type` dup aligned_sizeof_type(node.obj), node: nil filter_type(node, obj_type, filtered_type) From 7036927b3efb6f455cf5c278fe78fdc7edfae899 Mon Sep 17 00:00:00 2001 From: Hugo Parente Lima Date: Fri, 30 Sep 2022 07:21:43 -0300 Subject: [PATCH 0037/1551] Fix YAML serialization class name ambiguity (#12537) Co-authored-by: Ary Borenszweig --- spec/std/yaml/serializable_spec.cr | 15 +++++++++++++++ src/yaml/serialization.cr | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index f3a51cc3c3da..d04c8fccfa26 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -317,6 +317,17 @@ class YAMLAttrModuleTest2 < YAMLAttrModuleTest end end +module YAMLAttrModuleWithSameNameClass + class YAMLAttrModuleWithSameNameClass + end + + class Test + include YAML::Serializable + + property foo = 42 + end +end + abstract class YAMLShape include YAML::Serializable @@ -953,6 +964,10 @@ describe "YAML::Serializable" do it { YAMLAttrModuleTest2.from_yaml(%({"bar": 30, "moo": 40})).to_tuple.should eq({40, 15, 30}) } end + describe "work with inned class using same module name" do + it { YAMLAttrModuleWithSameNameClass::Test.from_yaml(%({"foo": 42})).foo.should eq(42) } + end + describe "use_yaml_discriminator" do it "deserializes with discriminator" do point = YAMLShape.from_yaml(%({"type": "point", "x": 1, "y": 2})).as(YAMLPoint) diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index d451a1f0e390..f87eae8c39f1 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -138,7 +138,7 @@ module YAML end private def self.new_from_yaml_node(ctx : YAML::ParseContext, node : YAML::Nodes::Node) - ctx.read_alias(node, \{{@type}}) do |obj| + ctx.read_alias(node, self) do |obj| return obj end From 2d6a36ee1c1a64ddf29eb1d82384572f702eb5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 30 Sep 2022 12:22:11 +0200 Subject: [PATCH 0038/1551] Fix specs with side effects (#12539) --- spec/std/uri_spec.cr | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/spec/std/uri_spec.cr b/spec/std/uri_spec.cr index 9c10b5377ff9..5e8bb02f7835 100644 --- a/spec/std/uri_spec.cr +++ b/spec/std/uri_spec.cr @@ -393,16 +393,25 @@ describe "URI" do it "registers port for scheme" do URI.set_default_port("ponzi", 9999) URI.default_port("ponzi").should eq(9999) + ensure + URI.set_default_port("ponzi", nil) end it "unregisters port for scheme" do - URI.set_default_port("ftp", nil) - URI.default_port("ftp").should eq(nil) + old_port = URI.default_port("ftp") + begin + URI.set_default_port("ftp", nil) + URI.default_port("ftp").should eq(nil) + ensure + URI.set_default_port("ftp", old_port) + end end it "treats scheme case insensitively" do URI.set_default_port("UNKNOWN", 1234) URI.default_port("unknown").should eq(1234) + ensure + URI.set_default_port("UNKNOWN", nil) end end From 98f7792954e51fa19c9dd4fc0dc36bfb6c674d12 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 30 Sep 2022 18:24:37 +0800 Subject: [PATCH 0039/1551] Add icon and metadata to Windows Crystal compiler binary (#12494) --- Makefile.win | 56 +++++++++++++++++- .../tools/playground/public/favicon.ico | Bin 73070 -> 21600 bytes 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/Makefile.win b/Makefile.win index 94264f81eaed..f4d93769700d 100644 --- a/Makefile.win +++ b/Makefile.win @@ -32,6 +32,7 @@ MAKEFLAGS += --no-builtin-rules SHELL := cmd.exe CXX := cl.exe +RC := rc.exe GLOB = $(shell dir $1 /B /S) MKDIR = if not exist $1 mkdir $1 @@ -173,15 +174,23 @@ $(O)\primitives_spec.exe: $(O)\crystal.exe $(DEPS) $(SOURCES) $(SPEC_SOURCES) @$(call MKDIR,"$(O)") .\bin\crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o "$@" spec\primitives_spec.cr -$(O)\crystal.exe: $(DEPS) $(SOURCES) +$(O)\crystal.exe: $(DEPS) $(SOURCES) $(O)\crystal.res $(call check_llvm_config) @$(call MKDIR,"$(O)") $(call export_vars) $(call export_build_vars) - .\bin\crystal build $(FLAGS) -o "$(O)\crystal-next.exe" src\compiler\crystal.cr -D without_openssl -D without_zlib -D without_playground --link-flags=/PDBALTPATH:crystal.pdb + .\bin\crystal build $(FLAGS) -o "$(O)\crystal-next.exe" src\compiler\crystal.cr -D without_openssl -D without_zlib -D without_playground --link-flags=/PDBALTPATH:crystal.pdb "--link-flags=$(realpath $(O)\crystal.res)" $(call MV,"$(O)\crystal-next.exe","$@") $(call MV,"$(O)\crystal-next.pdb","$(O)\crystal.pdb") +$(O)\crystal.res: $(O)\crystal.rc + @$(call MKDIR,"$(O)") + $(RC) /Fo "$@" "$<" + +$(O)\crystal.rc: $(MAKEFILE_LIST) + @$(call MKDIR,"$(O)") + $(MAKE) -s -f $(MAKEFILE_LIST) rc > "$@" + $(LLVM_EXT_OBJ): $(LLVM_EXT_DIR)\llvm_ext.cc $(call check_llvm_config) $(CXX) /c $(CXXFLAGS) "/Fo$@" "$<" $(shell $(LLVM_CONFIG) --cxxflags) @@ -199,6 +208,49 @@ clean_crystal: ## Clean up crystal built files clean_cache: ## Clean up CRYSTAL_CACHE_DIR files $(call RMDIR,"$(shell .\bin\crystal env CRYSTAL_CACHE_DIR)") +.PHONY: rc +rc: ## Write compiler resource script to standard output + $(eval comma := ,) + $(eval ver_comma := $(subst .,$(comma),$(subst -dev,,$(CRYSTAL_VERSION)))) + $(eval source_date := $(shell git show -s --format=%cs HEAD)) + $(eval source_year := $(firstword $(subst -, ,$(source_date)))) + $(eval ver_full := $(CRYSTAL_VERSION)$(if $(CRYSTAL_CONFIG_BUILD_COMMIT), [$(CRYSTAL_CONFIG_BUILD_COMMIT)])$(if $(source_date), ($(source_date)))) + @setlocal EnableDelayedExpansion &\ + echo #include ^&\ + echo.&\ + set "_dir=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))" &\ + echo 1 ICON ^"!_dir:/=\\!src\\compiler\\crystal\\tools\\playground\\public\\favicon.ico^"&\ + echo.&\ + echo VS_VERSION_INFO VERSIONINFO&\ + echo PRODUCTVERSION $(ver_comma),0&\ + echo FILEVERSION $(ver_comma),1&\ + echo FILEFLAGSMASK VS_FFI_FILEFLAGSMASK&\ + echo FILEFLAGS $(if $(findstring -dev,$(CRYSTAL_VERSION)),VS_FF_PRERELEASE,0)&\ + echo FILEOS VOS_NT_WINDOWS32&\ + echo FILETYPE VFT_APP&\ + echo FILESUBTYPE VFT2_UNKNOWN&\ + echo BEGIN&\ + echo BLOCK "StringFileInfo"&\ + echo BEGIN&\ + echo BLOCK "040904B0"&\ + echo BEGIN&\ + echo VALUE "ProductName", "Crystal"&\ + echo VALUE "ProductVersion", "$(ver_full)"&\ + echo VALUE "FileVersion", "$(ver_full)"&\ + echo VALUE "FileDescription", "Crystal$(if $(LLVM_CONFIG), $(shell $(LLVM_CONFIG) --host-target))"&\ + echo VALUE "Comments", "$(if $(LLVM_VERSION),LLVM $(LLVM_VERSION))"&\ + echo VALUE "InternalName", "crystal.exe"&\ + echo VALUE "OriginalFilename", "crystal.exe"&\ + echo VALUE "CompanyName", "Manas Technology Solutions"&\ + echo VALUE "LegalCopyright", "Copyright 2012-$(source_year) Manas Technology Solutions"&\ + echo END&\ + echo END&\ + echo BLOCK "VarFileInfo"&\ + echo BEGIN&\ + echo VALUE "Translation", 0x0409, 0x04B0&\ + echo END&\ + echo END + .PHONY: help help: ## Show this help @setlocal EnableDelayedExpansion &\ diff --git a/src/compiler/crystal/tools/playground/public/favicon.ico b/src/compiler/crystal/tools/playground/public/favicon.ico index b8f86e3a09efafd47f86c977e0c78230eb3943c0..8be6c93a3cb721ee9cac65c505f930767c3f9f5b 100644 GIT binary patch literal 21600 zcmd5^3piBi8~lE8m%YIO4(xwP6QOM`2=#?J zm_p0Pc2EcbP6rNiussH{2c%~Q{BYQ0khdTrvAhNy1p?W$dSG_*K%Rq0{G@d?^aX-Y zLCA+IRBnPu+qYJ~3bt$ze86aa=GQEvO&SZ^;6ojR-1i>pQ$esMx63MnNy|a*OA)sv zfhM=hDvM1&**%2@oDV)QFCK_2KS>+5!-u5q_p$ovUr$n>#D=UThPdrK2u0FB(x;$I z_92%gW_YZuBo!&$2kK2AWIst+Vk)V3R7?D)K%ugg7_u<$fh`^z@*&Uli?8j4AWCFM zsj{s5=MC5-g5W-R4>K@40J0xMJVSi$Ds(`o0p<*j0zw2ndtzA)8p{Td6p(!&xE~9) z#Xi^<&oeMpA)6Lg7W@l`jJsSU5+Qj75L7|Nf#7+h0^|gUvb_G%cHm0|84R)R^kn%Q+y2LFzzcT_@6Qa>W3wayjv&@o^pBZ>^3M^f9}DEs57^9)LZ& z8}M-)nK7XKP%HQi(0;#_J_&DW{kO3KUwoa&dj9aC-428Rk;Z7R{wWXe9U{XY^B#_` zGdS#J^tXqP`%j4dS4jD{@RnS|(1+(J+$Iku+2)EE{|w3ZZ`h<5xg;AhMq6b(x8ZwU zAoi7sE#|PO6ZkIRgXa^>p;c@#zGVmTo4_8=czCXA6Pq+fybi=D5c4_@K6Vm}wlvu7 z>j3_8F+RSpPJ=7~8300QEe7;W18M7glKTdW@$ob7ILWUT%6RU?b10J>3+e_S@~pw| zyi1k%LRmTnna2a@-vRP1h!)82Amn~oHpaCgK1j2PuG1S{^C{flw?+90!7d zXo4`g6yyM+41z$27JCKoR{$Jkm@GAs5CN{TsAV0-LLXmh@Oxm(!T6}i|8|J8j5Qlz z(LwNd;&HtKat=hJtGI3X13r1J^$P0J*JWvUasVv6q{Z_Do-ZDONXL^^?=%O%R|AoZ zDZXaqnftnh1H{Sz!S{+Ryw0?h0G3?W1GWQs*Nw3yeC0Y95ZB`i#Fm=_#NPY`<6|Ce zjsx`HZYTZ+=zrRdjU?aJw9M-PFpb*5#rMEmkjQrIT8m3=|7;upGpG&Rme_bsf&;%d zlCe5l{u>9tJkSa*9`j(yd!-XW@x}Idoz|In(mof(cs4Td zWX2ZT&z4~?jV(`owHPl#3a{1JVtb~Py*#mG*|5cUzmf3TjE(KZJ{^6xm5$#CG4VPE zKfm#Q67H8Pwm5#8*hd;y-uh`V4t~ycD7M(otBdiMh;b4-k~j7d`{C7;unQaph;i_m zX|4pfBO3fJb~64m@N0RW1C8I|IUqlR3lBf27Dozd*?L@{~=;IC5|xW0K!n@!o!32jkz8 zjxDJJjyee5S0Kl0tBm(&WbKc&<+Di=PnHI}w$4RcxbpV7%Zf{G`+^+sy1EBBR%>Oj z>*%>?FMk~S(|y-kTylFij05>v)goidTkaqSd@V}uRhGOl+Oune1AcB6fGh{W>%vca zPwioLv>&kX^}qqa&kOv_!p}{)f1hYQ9!&QFcoYyw1UGX`1A;9Eyww0(4j{@P3LJP= zaNudjfmIq22?B<(AaU^YQx+nMCI}P6p&3z>n_)8ogy_-Yhy9fSQyI*`6luZ&gDJv~ z9zs(j;)q0j(Z}D40zrS;r!a?#)X_SB`F}@}2Lr+?g4lx)ASEETK#b+_{kLp@A9+8d z9_rF_K^3%h+x^hhJb{ZIh-5#73uS4(vg-d@4!}5A~^uRo**+o@cdl|A}^oL*mYYDz;h1B{V&W{R*v@|W|!wB zz_{}-aF^uMrSpLE{};J;IS1e#{2$%Y}0KD-WS^FR2-=Q3Ux9xw7 zJLZo?u6YUU@UuzcBd;z4pYJ>2CojIlt_otbxn6?)!VdDl-0}N5tAl)<^b{O zAd)>ld2m~}OV)khi{IHb4rN{|apmSN z=C1@eo!L8XkFRw9QPSAs?H1{NT<;Kf5?+2Qc(P)2wk?ztZ=sBMo#HM&2mi!c!i>aU z`di3Oa~Ja;C}TW2JI=EFxFqg)UyM(R*Cp;^{;mD)EQ?EL+IEuokL*tFB-{fe+|F=p z%g2WlCsrJ%%i}J=#2SvEFT@`|WA!Alpp1EUD|az}qc6n&e-iHEI5!~9U62$IHxPAl ze^<(YbsMCu>!34z31m)yZwP|-vL#q}{e|~3@t!8$+f*Xkcccuyn?WSF-KCwSIY12Q z9LTZovlQ=j;`d6t_u2CMwls#UI@nGEA!Bv5jQ3krT8Y(G4&=Dx-<{js=Yv??{oUMe zvbamyAfCKAkn_RsjQG6^@B4rD8z{8lcO9~QSIWyITwBu+t1~%BIK2md^1VZYIsqc- z@2a+KjW@X+;^Dau&q-a4hu63j-GGC6;NM{7{v8x-yK)}7nztX$Wd}Ln zc?rJ*;dL5*7sc<>o&FoDPVj=Sg?@z+(LW3dKY1+i8Z!D|@QHN~2VHN;oOiGfBjA2k6VR^d7af1(XM zx%eLmU(hXifyOU&I3KWT(ISZ^paA)bIAYBO4V@yOiN_1mX&F}`RB;{tIITsCTDCq> z;$)9;8mhfi!AoPjtBYq#GLR4*?+U$%3VZ=#H2f01@ zlM-vpo?@!7^g_eK2@dhWcE|q8c^yEv@-GOlcKZJ5Sc65Q#-*q+oUcD}q{n3{rd)DT zU+ps9Ny#jDBJ1(b^)KujC*K?@Jbd{13AcyO@8#7-J7h;kynk3b(>VJ4Fy8I$CB~ln zh3XOWg=^kgBEOX4d^1sff$K)zie{~d0%7q4%2-}KpVque*K!8$yF-4@G<&N~){faX zso@*&yLQ96ky5XixxVPyT;nn8LIiVbhZQ7;P+d!^YIl8=O!uzv-=RHna@?RdZm%*pUp5lW^r#9^G?fxoFIT`F?f-iJOA=C!g?g zYp9IUstlSz+8%wv4q!_khY0T7(D*&2m};jJv+t-)A!nfHa2MuPRc@eg)T-a|>oQS~ z@~VT&7vFiFx4}b|J4U!NH|esnV0>--kaLcdkx4}R*YH_}A&T^o&Al-L{^9;r6PCCT+h8oH zJy`gAaeOAq7Va(}$JN*em5!zS%?Y%IQL|Ovh;Fal& z+=#_Hya~e?jXUbqEK&Ba2U)tTvVpE)@qv$`#?PXb2!656eYVw=Ff5|tcyE=z@fh0& z?J6-|WEROw&5u9$w&~_1XBJ!d{C0`nQQp4Hz59a;ZrVmKwBjsk-0Z8*D$CwFwb9F0 z4+=Y6KIrN{MYho|!Y3Ynh|2HM88yR^7gNDX1q!Qkk&hcYxyq2qRXR1PhjlU-#x3w( zIeWbt^d|HC9E*X@`V2cYn{Sj8_ZKS8@R)@BE;V>94|rp+D;_${W7sF4TU%GL0>f=ytmAOAF1FV==rdhk zjurye__U93*c6optLAqF zjULJj2OG^l1ncYKdeOP=LSEDJTRE3*sc5TFBN;K-my)WE2Nd`7vj}D7J~HO;1x62i z*1PxFz0zs;<%cVM=P8B{YhF-%yK1;_qA;lT$$ay3*Bde4V5o=E_rp$l5tPjJOvO6(Abk%=3P1hku^&Tt2Y`iJOIHBtPi_Omc z;M36~)6sRol`8hAE%VVRZK3OvhPxxs6ydhhlLOVlwI&>Gx@w+wd{fMT=*TN{HY3L8 zu~##*|Ft)h1NjPS$92}0XlCnj<_YigD1^J%dQ`yJPJu$2rz*pUJrnaoc;(lMt`Wn^U#Ul@g3|A`uCfegK?k4TPA>V?cbO9NpLN=hh>jfI^Snsq5m{p;hAm!0c=N=ir%89ku*zBOb8`sbylu{dCgv?a#u?4LW?8$niXHw6#^6|<$Z->FXQ&vXZ-pJsM;Z`>%s(};KYdb(c zrg~Msn4qL^Eww{_LAvLr(<~}AwijpVjoUIH^VQA=vE}<-^b`1h2+|QO951w3Ot+lD z5c!_#?=%oiWv)^59sFwZgze7jwus_>`!IHH9CG@@KR$pL^;R^x)aoH4O7v{-0_&^9 zl)UqHP6evVQhNx)RL<%|3(+S15>1=BEQicEXG7u0XXD@K)GqT&*PyX;_L)TM#~8gE zZ=@TVgs3No9yV|E&U5qv14`H5SeR2X)+cI3wkuJgNB__#$2-6tZ3q<3-(lLnVjGop z<8}{@L-7$>k%~9vz{zv#mQOssvw%5!_&Tc@u8afJrs^MRzxJKg^JVtvbT`6AFZ2&} zlxa=ffx>t{UaT+nN_QWOVw;yCBA__wmvM@}pd%*QaW&CCQ(yfsJn7gp55rAVFUR}Z zp7APmhu%d@BI34MsEms}tw-D)y`09i2rqnP{!`zI)0Tk?_q~5lyOCw7$)=qCJ<(|T zohS2;@~-FQBh$VWWkoUQTc%nQ%`W*}7*%kLZ4`orez;2GrZrf^daXE$bl$CUNi{$E zmzqsnlv3h@sbvAQvxyov+*vbCURu51T8NtaRov={CQngRqD7ie)l`t+FxxK#ZC%M^ z5CP{VoVWPPgK`=bWZq+6=)G(@e_(u*#+Lk|-OE+$IG(j%Gja_z^+hv%32(0VN}*@^ z6>6(Vz(FEx%4wLMU^#J?Lvcd z><(6#ie+&juij~c8Y`xJ22(dQih|tcP|T>wSvuOqD`|H}(-hsB>1BB-X#=T5jcShj z);vzAT3)VSSxn{xl&43Z8^YF8EjzcEvhJ2jV)ZfVaWpG|J~`LJRoy}_&}5=l-Z`x| zxyK9bX={gCQ;A)w3ycahJp87v$;xoJZcQYqmQX7~>h`cVqQ}oymxQ~=Mrx#At7JOI zB4Mv5y8FsZX_ZcO>5Jl=>t-dz$5dF3ocPk~T%Y?>0uA&J?K3yj=lxX8)Kv?g%=M1Q z|Cw!tijxa_uc6*J2JAIxJl*i^8*gWLE!lq8KDPHG)tsA^PWPx!D=)=A)ZL{=%!u88 za_z!j;&xPv^vy8iX|$OX!;oh~Ew$L;dr}RzsGf|lo~-Bm2j>;V^E)kcK{qh3X3n~& z5xdjD{P4#yv`jC7mcF}??oH=ZZS%J+7HItJMsh9y_F(SjB9`xl3ylhp0r+-jdX^-SvJ(fY@9(%)ipOGXH4 zG-qcaH^{>#gOmKua#e_aCX+JJfWjfDbw=BVJe()mF>86gDG^QM*&|zREsFPTmBgRE zi?vf=IJ+PXaI83h{1^^O`m=UkF?f6R==whBnHM+KrwZ=0+4+-PkO{WQfV0Fir&1f5 z_><;q+`YEfr+&++Tkoy~&%FHg0zC?fbaSUH8};&3<+Q4){k|sN%?s+e*R!AE;O#kg zr|lcDCM-!MJq%7xzooIh$YsQuxzYZvC<}l?kFg8s=4F->U)S_8ST$(DRqCK#mYhZB z7vA!#HG3eKuAjbx%C8StJu-SRWk&HhMUAcBv96-po(TcIC|Yme<{>_Alrmm9=ahQs zz|qtST?*SHHp_Ry4YvTp#42UVEZu~`#JnQEvdt=H$NPPB|vVmU_zehOD^u}kX)VpNh zaO&iCftJRW`;$DXcfEX>D# zNAbxWylle8gn($p@I!PoiuUrHTW!2uoVPHvL2q83B6>6U*xO@eqnB#`A)LNL+2-x# z_jP;n4e~Nvn;R9}-H51Qwt0r5@QPyk2VXnSi5Y@jTZzYZDJ!mPCT-szBwRlht%`8k zIImZE)ltQgsf1tO5+<#I`HR5l3Iz>gKIQAx?j4tI98;`CEZ8%7mf>T;u5E;`qCeYN zI0eQ0t{qc60-+qQ{MdIxLv45R!r)9C)0t;~m-n3(OP|tjNLjWg^;Y%w_qW8NEPnrF zB(!RF`!MY!;|l-C7D8|+edWpW?629oX_cjHq3TjZSeKRNkH{FBT#`M6YInjTQ^Rmw zK$c=jpFp)>{y55yoIs%)SVe^EA5odHX;{#Z=u$1t@dzi=nRd}PwHZmo)a3GPUn)AS zVo`Udw{@|T@RAvE^TX=9Yqkn*PGDP6=j7YQM)G9{JRlb+XI_F`v|IcmTG0MVR3E`up28OnI zrGD7s8LD%I@w5y3W@#Kq?yD2gczhTOWki+Yn5FT zoY7;2ogHY9X0pC2it^i@@DIG)6MW@9?1wuZmd?&QoAxMN7^{-HeBP*KBbF|{^QS;T zk5%tK%sEo`@`at;NMWp6#S5O|Y3|}WGr^2auladSMB$Mmw(CoHrL#48VO!CPv=ir3 z1K0e1kl&}-N44TbWPMh~j-<Yw?cSBx%O<+M_iZPHwS&hocWH?roIi71Rv^!1cI{@dXl9gQF3 L;d0C=aMk|-ujc8r literal 73070 zcmeI536LCB8OLYYkmcS0;f~A#qJaV;Dk7i^hhU^EK|n#FxDVwNsEC3xtAdz%F8Dlw6Nc;@w zCf8=*1n_sT0Qik#5W5=V7C8V3G057Cu|K#1%mqQ>7N+f;_)_EmB!n!w#@Yo;2Ty`9 zV-|K_ci>-<1CS6vda>2A$H|}u7J;zi6lH(6;ct-xkkEy!Pus)6pTRpI%J{_5_pQuf zSx|^O11nR!2bcj~0CB`5?lE*ds_!F(uuKgJ+k#(#2f+}CJ03}lcMJ3E152W8%%qP6 ze*^PD68xuO%$u3J@;~pg1{2%|%mlB4G;p65A8vw=yybwdad!r1gU7*QkQTnv=trJ% zK-ac!74hym-*1GoJe&t~+^6vv%YsigUl3+M8nKI(qw(jZHs=P82DyRdI@crAR6%TNMbRK!2@{J#cHweFP#c?la^T?<#NRwSqjTWg6!_!!7!JcO z&Ky_;u9IHxZGU!7@BW3sW0w2)JsiYwJ+Mc@T)RC#ePF^qr6q69k0A$UB*^tGjNJ#? zWfBExi9ynS{0=UnJP#!GepaXVr=BTtpm_}ZCZZe|NQh%E?@#L7m-Mlhrt$TDeAsdb z9mYLJukTOqmVj?@bJ6X1=Z2gBvts7>4#v}aMP8(zMnj-mj`H6A953W~XrncUIN$NP zlkvR9-nc)ToxI1>&-~6Z1O?cBE=F!^jMZ)IHMLWLAAXhun=?dE0GWnu96a64IDTTU zsa0a~F%BN07~=qu&XTO|1q@f!7d@4bw% zf)C%?O>}>-i1{Zu(EIqSwUgyh-#%ha@1y?}J^IqA*$EN*n$_IWZ!-9m(=d!`-eLcK z`jiNLjf#Uf`aRok@Ui^04D z$UcWL?2b?nVNpAXURu;!msKU*47y4;pSgEO448)4Yu<6JTc$Y&^!w|6WIRTG*3qBD zE-kS)^VYM&X^v4;zG=O8O+!ML+lmj)p2BAJF2{+Z`7|GMi7F;>_N&ieztZhr?G-O`>nOPPW_vDy?5jHJQH!~t?rlN?_0q5yQFM)@kwq!c3#(e zpQY_uKF5jEY!-tpDRI|4KLUQArQ6jbS6UZZ8)QKa#mOZp{&xom%K09+%2Mn_#ej!7 zcG()>eVKTav@HS#aUkJ-eaEQ zdF?6RYL z65fN|T+r^%OO&nav(L1d>G`wv`Q=UD*eB_mxN!3_Wo4iJrP1sRmpSGs{;vlh{kS%6 z>gWR?Px3V;lc#vM&*icJ!Me%8%`5D$7oe}Snl6K5AAok=@}lqSllY~voA2P|2==vh zo=3TzN#!Np?ep+^FMee>nb*e40`yzJ@nBQX1M(mzGKqHH@}TS4iy*QU#_c)aqm$;jGd^4l~ zY(qc)?UJ8Gw7(0S1hxQv-#5WD7P*E|3~zbZe#Csj`uz+A;48p+J=iTr?_hr|I1Hrm zd&J@R)$N?p-2Oi16SjPn4&Mi1%vxOVlI%<8>!_qc$6@DylPi7B+z5^V8-cjwA_j8$k=1=&t3`bb zZO<4{=75v?*BEOi_&$j8dGfvJ-#Knp_jT;VAv-ciTsdH3v6wOS4(VB7XRs^?OD57$ z9@c|@6c!;K`q_y#ap!=^1#LeBx_|p)usP6o`5>a)a`vCaeXZNZDLM|*vWD^|zFZ9s z0)C!PITO1q#@zV)Z~RX}zRsXJ7L6Zi**=3$=YeklFZV-~%W3i5j~|!ge^N4)ffoWl ze#c?+F}~dkP61lm7zf%#*Ar=;Lnog0Z=X=i`ZpL)_kKJ1nTC3;ZCwYB02=}$ce0ZP z|NQtc$xI@_?JkU+l^k$#{4#!D40Ju|EYHgRQ}~@0(b`iMb090RcJou$IcXAIa}_xd z-?>D5r%iUlp&|#O&jGEW^rtQO4S%%8SL8snIWRk=u{Un*!H%9uaQoaU%7XKvwbQiA zo;axQ;6=Q{=V05k2fyLpcZwVcJ^%Gg&iIVPUv4S~biGoX2g2dtW|5jP`7ZtzIS@|# zkIPu>jk|jnIS|S@a5F<_2Df^qr#KJzKM%YCe_4MIYu_RVe4hud%HT2Y=JI|;4*1Fe zT}$Uh4qzh1XTjK?1wo&0N_(yxh=X}}1~aVhwqZZtnD+R~KRvs*7=*=Pmip1Wqk6uX zt}`PCOnpbMScvNXz@p>5gz`?~bHNn$H7i|9ykb45z=v(J9ISJt zDaiV|8dJ!%oWSgY@#p8Qy47= zMtjFI`?D@);AG&ZjU3SP<@bV9z*fNR9dC*Y$sq%J4$jMOJmhfd#gqyL3I+-Wf-x}c zRfeh~Th=Qh^u?v}2)$nFAEEPA%c*mX*6Y-nM2|z)C0xm&4@$2#3-oI(rBbD-4_bQJ z=vsV3nhw+~wdp{ky6HgOt~L(nR--L;e|4ia9H=$*iq)W{m)msPfmXfEz+ltfW}v0p z477Bc0f%1I08R4-0}j31Rxbxy)3HO~)a8Ivmjg~+4j8tqVXAFi^4Yqx6U4?85KbgPk6a-yzG zYu5Kk?{(@m(X^1Jmh=tRWYpCArI*|Eic=p{qBcqEA4YeOQbuQz&3fsO&?LP~cp+(o z4v9^@LOmq6boK~BGbt+QRfk?hM{U-lTUl0WNOj0Q4DiwtbcP?6*OuU^mBkLdJfeP~ zeHhm4<|UY@s=lH3pi6b=*6?CoLL}^ipl+$st>H~Qk+Ftv>GiVJUn79M!;RBWLnoY7 zXFa;5ngA0{TW10My$xJ%e;>r-r>}sa9-i z_OA7p=9UAdNP6>Fp-_5*8Z~iJ(bOALYw8WxTKb@NEwN)?YiKreP6zDzmIJolasVQc z2G{gr>n#WD*tQ&~TJ4+3Z8vWwr=7VpsoM^jYQsRqwl)md&Zx|GpjmAP&(0&;ME#ZR zUF+{)gE~`GQ)W4&ZZq91nK!bU5Hzc-ouQ_ycKPD!$oes~$+$KEwgWxZUa2%V0VO(F zimtx^R*G(jPxM@-(DAn`I3GL*G@cvuoBd&Yf7LyYZNMPVygmkQ zxbi2N)-*6kPKR@Mn)8BHytNfLQe#RNgrbeG$ ze9*H8??y1be5-*@K5Xe>Y@LV4n(J6~cXseW&tQyYuYCGhhc;u`tL_dk8V|KG`ZmXt z?oD+Xqf`BDE(Q@j9ln z-PPDSrH=9_SlLzK}VWzzxMi9qA~c_!*K z?4132*?%6Jc3GW*r+~3v#Qt5N(=&M$hmEoAw4H&Co2)_M)4cfY$y$wtQ zn*-;09@UrV^Y%sH=7T;r9|LEBoxn063R#y;CCbUkVGcGt`~Y{Y zxaYwQGKkbYkt(Qwa-Hh{&ZBjkoOj=%;@n;Kb8U|GL3d9UkPqMvZ$JbBBjD8M-YE0CS&WyKyMq<YvBisK2evpK9 From 12a9b5fc63b3285cc55e21d84705736d0c2ad4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 30 Sep 2022 12:24:50 +0200 Subject: [PATCH 0040/1551] Fix `HTTP::Client` implicit compression with retry (#12536) --- spec/std/http/client/client_spec.cr | 23 +++++++++++++++++ src/http/client.cr | 39 ++++++++++++++++------------- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index ef951917ae2c..94b86fd3ed0e 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -270,6 +270,29 @@ module HTTP requests.should eq 1 end + it "retry does not affect implicit compression (#11354)" do + server = HTTP::Server.new do |context| + context.response.headers["Content-Encoding"] = "gzip" + context.response.output.print "\u001F\x8B\b\u0000\u0000\u0000\u0000\u0000\u0004\u0003+\xCFH,I-K-\u0002\u0000\xB3C\u0011N\b\u0000\u0000\u0000" + context.response.output.close + io = context.response.@io.as(Socket) + io.linger = 0 # with linger 0 the socket will be RST on close + io.close + end + address = server.bind_unused_port "127.0.0.1" + + run_server(server) do + client = HTTP::Client.new("127.0.0.1", address.port) + # First request establishes the server connection, but the server + # immediately closes it after sending the response. + client.get(path: "/") + + # Second request tries to re-use the connection which fails (due to the + # server's hang up) and then it retries by establishing a new connection. + client.get(path: "/").body.should eq "whatever" + end + end + it "doesn't read the body if request was HEAD" do resp_get = test_server("localhost", 0, 0) do |server| client = Client.new("localhost", server.local_address.port) diff --git a/src/http/client.cr b/src/http/client.cr index 2fb0cd9d7c45..a9acff3027f7 100644 --- a/src/http/client.cr +++ b/src/http/client.cr @@ -585,8 +585,9 @@ class HTTP::Client end private def exec_internal(request) + implicit_compression = implicit_compression?(request) begin - response = exec_internal_single(request) + response = exec_internal_single(request, implicit_compression: implicit_compression) rescue exc : IO::Error raise exc if @io.nil? # do not retry if client was closed response = nil @@ -596,15 +597,15 @@ class HTTP::Client # Server probably closed the connection, so retry once close request.body.try &.rewind - response = exec_internal_single(request) + response = exec_internal_single(request, implicit_compression: implicit_compression) return handle_response(response) if response raise IO::EOFError.new("Unexpected end of http response") end - private def exec_internal_single(request) - decompress = send_request(request) - HTTP::Client::Response.from_io?(io, ignore_body: request.ignore_body?, decompress: decompress) + private def exec_internal_single(request, implicit_compression = false) + send_request(request) + HTTP::Client::Response.from_io?(io, ignore_body: request.ignore_body?, decompress: implicit_compression) end private def handle_response(response) @@ -632,7 +633,8 @@ class HTTP::Client end private def exec_internal(request, &block : Response -> T) : T forall T - exec_internal_single(request, ignore_io_error: true) do |response| + implicit_compression = implicit_compression?(request) + exec_internal_single(request, ignore_io_error: true, implicit_compression: implicit_compression) do |response| if response return handle_response(response) { yield response } end @@ -641,7 +643,7 @@ class HTTP::Client # Server probably closed the connection, so retry once close request.body.try &.rewind - exec_internal_single(request) do |response| + exec_internal_single(request, implicit_compression: implicit_compression) do |response| if response return handle_response(response) { yield response } end @@ -649,14 +651,14 @@ class HTTP::Client raise IO::EOFError.new("Unexpected end of http response") end - private def exec_internal_single(request, ignore_io_error = false) + private def exec_internal_single(request, ignore_io_error = false, implicit_compression = false) begin - decompress = send_request(request) + send_request(request) rescue ex : IO::Error return yield nil if ignore_io_error && !@io.nil? # ignore_io_error only if client was not closed raise ex end - HTTP::Client::Response.from_io?(io, ignore_body: request.ignore_body?, decompress: decompress) do |response| + HTTP::Client::Response.from_io?(io, ignore_body: request.ignore_body?, decompress: implicit_compression) do |response| yield response end end @@ -669,25 +671,26 @@ class HTTP::Client end private def send_request(request) - decompress = set_defaults request + set_defaults request run_before_request_callbacks(request) request.to_io(io) io.flush - decompress end private def set_defaults(request) request.headers["Host"] ||= host_header request.headers["User-Agent"] ||= "Crystal" + + if implicit_compression?(request) + request.headers["Accept-Encoding"] = "gzip, deflate" + end + end + + private def implicit_compression?(request) {% if flag?(:without_zlib) %} false {% else %} - if compress? && !request.headers.has_key?("Accept-Encoding") - request.headers["Accept-Encoding"] = "gzip, deflate" - true - else - false - end + compress? && !request.headers.has_key?("Accept-Encoding") {% end %} end From d72dd15fc5e7af84876e0c4fc0aa308878a08fae Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 30 Sep 2022 21:54:10 +0800 Subject: [PATCH 0041/1551] Remove `spec/win32_std_spec.cr` (#12282) --- spec/std/llvm/x86_abi_spec.cr | 2 + spec/std/signal_spec.cr | 2 +- spec/std/system/group_spec.cr | 2 + spec/std/system/user_spec.cr | 2 + spec/std/thread/condition_variable_spec.cr | 2 + spec/std/thread/mutex_spec.cr | 2 + spec/std/thread_spec.cr | 2 + spec/std/va_list_spec.cr | 2 + spec/std_spec.cr | 11 +- spec/win32_std_spec.cr | 263 --------------------- 10 files changed, 17 insertions(+), 273 deletions(-) delete mode 100644 spec/win32_std_spec.cr diff --git a/spec/std/llvm/x86_abi_spec.cr b/spec/std/llvm/x86_abi_spec.cr index 5f69be12df87..27d387820298 100644 --- a/spec/std/llvm/x86_abi_spec.cr +++ b/spec/std/llvm/x86_abi_spec.cr @@ -1,3 +1,5 @@ +{% skip_file if flag?(:win32) %} # 32-bit windows is not supported + require "spec" require "llvm" diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr index e70278aedb3e..60e741e0b44c 100644 --- a/spec/std/signal_spec.cr +++ b/spec/std/signal_spec.cr @@ -1,4 +1,4 @@ -{% skip_file if flag?(:wasm32) %} +{% skip_file if flag?(:win32) || flag?(:wasm32) %} require "spec" require "signal" diff --git a/spec/std/system/group_spec.cr b/spec/std/system/group_spec.cr index 5464d5136827..dfe6b476fb9f 100644 --- a/spec/std/system/group_spec.cr +++ b/spec/std/system/group_spec.cr @@ -1,3 +1,5 @@ +{% skip_file if flag?(:win32) %} + require "spec" require "system/group" diff --git a/spec/std/system/user_spec.cr b/spec/std/system/user_spec.cr index a3236130d27a..2d0089e9c3fc 100644 --- a/spec/std/system/user_spec.cr +++ b/spec/std/system/user_spec.cr @@ -1,3 +1,5 @@ +{% skip_file if flag?(:win32) %} + require "spec" require "system/user" diff --git a/spec/std/thread/condition_variable_spec.cr b/spec/std/thread/condition_variable_spec.cr index 36e1963794b3..568f2ea35c31 100644 --- a/spec/std/thread/condition_variable_spec.cr +++ b/spec/std/thread/condition_variable_spec.cr @@ -1,3 +1,5 @@ +{% skip_file if flag?(:win32) %} # FIXME: enable after #11647 + {% if flag?(:musl) %} # FIXME: These thread specs occasionally fail on musl/alpine based ci, so # they're disabled for now to reduce noise. diff --git a/spec/std/thread/mutex_spec.cr b/spec/std/thread/mutex_spec.cr index 037c735b38e9..3346a1575615 100644 --- a/spec/std/thread/mutex_spec.cr +++ b/spec/std/thread/mutex_spec.cr @@ -1,3 +1,5 @@ +{% skip_file if flag?(:win32) %} # FIXME: enable after #11647 + {% if flag?(:musl) %} # FIXME: These thread specs occasionally fail on musl/alpine based ci, so # they're disabled for now to reduce noise. diff --git a/spec/std/thread_spec.cr b/spec/std/thread_spec.cr index 599a1968f52f..7d3e51a92972 100644 --- a/spec/std/thread_spec.cr +++ b/spec/std/thread_spec.cr @@ -1,3 +1,5 @@ +{% skip_file if flag?(:win32) %} # FIXME: enable after #11647 + require "spec" {% if flag?(:musl) %} diff --git a/spec/std/va_list_spec.cr b/spec/std/va_list_spec.cr index 79347c0638fe..4a8e4e172e23 100644 --- a/spec/std/va_list_spec.cr +++ b/spec/std/va_list_spec.cr @@ -1,3 +1,5 @@ +{% skip_file if flag?(:win32) %} + require "./spec_helper" describe VaList do diff --git a/spec/std_spec.cr b/spec/std_spec.cr index 93b05c4a300d..3081785dbe2c 100644 --- a/spec/std_spec.cr +++ b/spec/std_spec.cr @@ -1,9 +1,2 @@ -{% unless flag?(:win32) %} - require "./support/mt_abort_timeout" - require "./std/**" -{% else %} - # This list gives an overview over which specs are currently working on win32. - # - # See spec/generate_windows_spec.sh for details. - require "./win32_std_spec.cr" -{% end %} +require "./support/mt_abort_timeout" +require "./std/**" diff --git a/spec/win32_std_spec.cr b/spec/win32_std_spec.cr deleted file mode 100644 index 48d2e503bf78..000000000000 --- a/spec/win32_std_spec.cr +++ /dev/null @@ -1,263 +0,0 @@ -# This file is autogenerated by `spec/generate_windows_spec.sh` -# 2021-11-11 18:02:01+08:00 - -require "./std/array_spec.cr" -require "./std/atomic_spec.cr" -require "./std/base64_spec.cr" -require "./std/benchmark_spec.cr" -require "./std/big/big_decimal_spec.cr" -require "./std/big/big_float_spec.cr" -require "./std/big/big_int_spec.cr" -require "./std/big/big_rational_spec.cr" -require "./std/big/number_spec.cr" -require "./std/bit_array_spec.cr" -require "./std/bool_spec.cr" -require "./std/box_spec.cr" -require "./std/channel_spec.cr" -require "./std/char/reader_spec.cr" -require "./std/char_spec.cr" -require "./std/class_spec.cr" -require "./std/colorize_spec.cr" -require "./std/comparable_spec.cr" -require "./std/complex_spec.cr" -require "./std/compress/deflate/deflate_spec.cr" -require "./std/compress/gzip/gzip_spec.cr" -require "./std/compress/zip/zip_file_spec.cr" -require "./std/compress/zip/zip_spec.cr" -require "./std/compress/zlib/reader_spec.cr" -require "./std/compress/zlib/stress_spec.cr" -require "./std/compress/zlib/writer_spec.cr" -require "./std/concurrent/select_spec.cr" -require "./std/concurrent_spec.cr" -require "./std/crypto/bcrypt/base64_spec.cr" -require "./std/crypto/bcrypt/password_spec.cr" -require "./std/crypto/bcrypt_spec.cr" -require "./std/crypto/blowfish_spec.cr" -require "./std/crypto/subtle_spec.cr" -require "./std/crystal/compiler_rt/divmod128_spec.cr" -require "./std/crystal/compiler_rt/fixint_spec.cr" -require "./std/crystal/compiler_rt/float_spec.cr" -require "./std/crystal/compiler_rt/mulodi4_spec.cr" -require "./std/crystal/compiler_rt/mulosi4_spec.cr" -require "./std/crystal/compiler_rt/muloti4_spec.cr" -require "./std/crystal/compiler_rt/multi3_spec.cr" -require "./std/crystal/digest/md5_spec.cr" -require "./std/crystal/digest/sha1_spec.cr" -require "./std/crystal/hasher_spec.cr" -require "./std/crystal/pointer_linked_list_spec.cr" -require "./std/crystal/syntax_highlighter/colorize_spec.cr" -require "./std/crystal/syntax_highlighter/html_spec.cr" -require "./std/csv/csv_build_spec.cr" -require "./std/csv/csv_lex_spec.cr" -require "./std/csv/csv_parse_spec.cr" -require "./std/csv/csv_spec.cr" -require "./std/deque_spec.cr" -require "./std/digest/adler32_spec.cr" -require "./std/digest/crc32_spec.cr" -require "./std/digest/io_digest_spec.cr" -require "./std/digest/md5_spec.cr" -require "./std/digest/sha1_spec.cr" -require "./std/digest/sha256_spec.cr" -require "./std/digest/sha512_spec.cr" -require "./std/dir_spec.cr" -require "./std/double_spec.cr" -require "./std/ecr/ecr_lexer_spec.cr" -require "./std/ecr/ecr_spec.cr" -require "./std/enum_spec.cr" -require "./std/enumerable_spec.cr" -require "./std/env_spec.cr" -require "./std/errno_spec.cr" -require "./std/exception/call_stack_spec.cr" -require "./std/exception_spec.cr" -require "./std/file/tempfile_spec.cr" -require "./std/file_spec.cr" -require "./std/file_utils_spec.cr" -require "./std/float_printer/diy_fp_spec.cr" -require "./std/float_printer/grisu3_spec.cr" -require "./std/float_printer/ieee_spec.cr" -require "./std/float_printer_spec.cr" -require "./std/float_spec.cr" -require "./std/gc_spec.cr" -require "./std/hash_spec.cr" -require "./std/html_spec.cr" -require "./std/http/chunked_content_spec.cr" -require "./std/http/client/client_spec.cr" -require "./std/http/client/response_spec.cr" -require "./std/http/cookie_spec.cr" -require "./std/http/formdata/builder_spec.cr" -require "./std/http/formdata/parser_spec.cr" -require "./std/http/formdata_spec.cr" -require "./std/http/headers_spec.cr" -require "./std/http/http_spec.cr" -require "./std/http/params_spec.cr" -require "./std/http/request_spec.cr" -require "./std/http/server/handlers/compress_handler_spec.cr" -require "./std/http/server/handlers/error_handler_spec.cr" -require "./std/http/server/handlers/handler_spec.cr" -require "./std/http/server/handlers/log_handler_spec.cr" -require "./std/http/server/handlers/static_file_handler_spec.cr" -require "./std/http/server/handlers/websocket_handler_spec.cr" -require "./std/http/server/request_processor_spec.cr" -require "./std/http/server/response_spec.cr" -require "./std/http/server/server_spec.cr" -require "./std/http/status_spec.cr" -require "./std/http/web_socket_spec.cr" -require "./std/humanize_spec.cr" -require "./std/indexable/mutable_spec.cr" -require "./std/indexable_spec.cr" -require "./std/ini_spec.cr" -require "./std/int_spec.cr" -require "./std/io/argf_spec.cr" -require "./std/io/buffered_spec.cr" -require "./std/io/byte_format_spec.cr" -require "./std/io/delimited_spec.cr" -require "./std/io/file_descriptor_spec.cr" -require "./std/io/hexdump_spec.cr" -require "./std/io/io_spec.cr" -require "./std/io/memory_spec.cr" -require "./std/io/multi_writer_spec.cr" -require "./std/io/sized_spec.cr" -require "./std/io/stapled_spec.cr" -require "./std/iterator_spec.cr" -require "./std/json/any_spec.cr" -require "./std/json/builder_spec.cr" -require "./std/json/lexer_spec.cr" -require "./std/json/parser_spec.cr" -require "./std/json/pull_parser_spec.cr" -require "./std/json/serializable_spec.cr" -require "./std/json/serialization_spec.cr" -require "./std/kernel_spec.cr" -require "./std/levenshtein_spec.cr" -require "./std/llvm/aarch64_spec.cr" -require "./std/llvm/arm_abi_spec.cr" -require "./std/llvm/llvm_spec.cr" -require "./std/llvm/type_spec.cr" -require "./std/llvm/x86_64_abi_spec.cr" -# require "./std/llvm/x86_abi_spec.cr" (failed to run) -require "./std/log/broadcast_backend_spec.cr" -require "./std/log/builder_spec.cr" -require "./std/log/context_spec.cr" -require "./std/log/dispatch_spec.cr" -require "./std/log/env_config_spec.cr" -require "./std/log/format_spec.cr" -require "./std/log/io_backend_spec.cr" -require "./std/log/log_spec.cr" -require "./std/log/main_spec.cr" -require "./std/log/metadata_spec.cr" -require "./std/log/spec_spec.cr" -require "./std/match_data_spec.cr" -require "./std/math_spec.cr" -require "./std/mime/media_type_spec.cr" -require "./std/mime/multipart/builder_spec.cr" -require "./std/mime/multipart/parser_spec.cr" -require "./std/mime/multipart_spec.cr" -require "./std/mime_spec.cr" -require "./std/mutex_spec.cr" -require "./std/named_tuple_spec.cr" -require "./std/number_spec.cr" -require "./std/oauth/access_token_spec.cr" -require "./std/oauth/authorization_header_spec.cr" -require "./std/oauth/consumer_spec.cr" -require "./std/oauth/params_spec.cr" -require "./std/oauth/request_token_spec.cr" -require "./std/oauth/signature_spec.cr" -require "./std/oauth2/access_token_spec.cr" -require "./std/oauth2/client_spec.cr" -require "./std/oauth2/session_spec.cr" -require "./std/object_spec.cr" -require "./std/openssl/cipher_spec.cr" -require "./std/openssl/digest_spec.cr" -require "./std/openssl/hmac_spec.cr" -require "./std/openssl/pkcs5_spec.cr" -require "./std/openssl/ssl/context_spec.cr" -require "./std/openssl/ssl/hostname_validation_spec.cr" -require "./std/openssl/ssl/server_spec.cr" -require "./std/openssl/ssl/socket_spec.cr" -require "./std/openssl/x509/certificate_spec.cr" -require "./std/openssl/x509/name_spec.cr" -require "./std/option_parser_spec.cr" -require "./std/path_spec.cr" -require "./std/pointer_spec.cr" -require "./std/pp_spec.cr" -require "./std/pretty_print_spec.cr" -require "./std/proc_spec.cr" -require "./std/process/find_executable_spec.cr" -require "./std/process_spec.cr" -require "./std/raise_spec.cr" -require "./std/random/isaac_spec.cr" -require "./std/random/pcg32_spec.cr" -require "./std/random/secure_spec.cr" -require "./std/random_spec.cr" -require "./std/range_spec.cr" -require "./std/record_spec.cr" -require "./std/reference_spec.cr" -require "./std/regex_spec.cr" -require "./std/semantic_version_spec.cr" -require "./std/set_spec.cr" -# require "./std/signal_spec.cr" (failed codegen) -require "./std/slice_spec.cr" -require "./std/socket/address_spec.cr" -require "./std/socket/addrinfo_spec.cr" -require "./std/socket/socket_spec.cr" -require "./std/socket/tcp_server_spec.cr" -require "./std/socket/tcp_socket_spec.cr" -require "./std/socket/udp_socket_spec.cr" -require "./std/socket/unix_server_spec.cr" -require "./std/socket/unix_socket_spec.cr" -require "./std/spec/context_spec.cr" -require "./std/spec/expectations_spec.cr" -require "./std/spec/filters_spec.cr" -require "./std/spec/helpers/iterate_spec.cr" -require "./std/spec/hooks_spec.cr" -require "./std/spec/junit_formatter_spec.cr" -require "./std/spec/tap_formatter_spec.cr" -require "./std/spec_spec.cr" -require "./std/sprintf_spec.cr" -require "./std/static_array_spec.cr" -require "./std/string/grapheme_break_spec.cr" -require "./std/string/grapheme_spec.cr" -require "./std/string/utf16_spec.cr" -require "./std/string_builder_spec.cr" -require "./std/string_pool_spec.cr" -require "./std/string_scanner_spec.cr" -require "./std/string_spec.cr" -require "./std/struct_spec.cr" -require "./std/symbol_spec.cr" -require "./std/syscall_spec.cr" -# require "./std/system/group_spec.cr" (failed codegen) -# require "./std/system/user_spec.cr" (failed codegen) -require "./std/system_error_spec.cr" -require "./std/system_spec.cr" -# require "./std/thread/condition_variable_spec.cr" (failed codegen) -# require "./std/thread/mutex_spec.cr" (failed codegen) -# require "./std/thread_spec.cr" (failed codegen) -require "./std/time/custom_formats_spec.cr" -require "./std/time/format_spec.cr" -require "./std/time/location_spec.cr" -require "./std/time/span_spec.cr" -require "./std/time/time_spec.cr" -require "./std/tuple_spec.cr" -require "./std/uint_spec.cr" -require "./std/uri/params_spec.cr" -require "./std/uri/punycode_spec.cr" -require "./std/uri_spec.cr" -require "./std/uuid/json_spec.cr" -require "./std/uuid/yaml_spec.cr" -require "./std/uuid_spec.cr" -# require "./std/va_list_spec.cr" -require "./std/weak_ref_spec.cr" -require "./std/winerror_spec.cr" -require "./std/xml/builder_spec.cr" -require "./std/xml/html_spec.cr" -require "./std/xml/reader_spec.cr" -require "./std/xml/xml_spec.cr" -require "./std/xml/xpath_spec.cr" -require "./std/yaml/any_spec.cr" -require "./std/yaml/builder_spec.cr" -require "./std/yaml/nodes/builder_spec.cr" -require "./std/yaml/schema/core_spec.cr" -require "./std/yaml/schema/fail_safe_spec.cr" -require "./std/yaml/serializable_spec.cr" -require "./std/yaml/serialization_spec.cr" -require "./std/yaml/yaml_pull_parser_spec.cr" -require "./std/yaml/yaml_spec.cr" From 395fda7e1e448f120d80552d6e06c4d32b2886c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 2 Oct 2022 12:24:51 +0200 Subject: [PATCH 0042/1551] Remove `spec/generate_windows_spec.sh` (#12549) --- spec/generate_windows_spec.sh | 58 ----------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 spec/generate_windows_spec.sh diff --git a/spec/generate_windows_spec.sh b/spec/generate_windows_spec.sh deleted file mode 100644 index 1b179ec4789a..000000000000 --- a/spec/generate_windows_spec.sh +++ /dev/null @@ -1,58 +0,0 @@ -#! /usr/bin/env bash -set +x - -# This script iterates through each spec file and tries to build and run it. -# -# * `failed codegen` annotates specs that error in the compiler. -# This is mostly caused by some API not being ported to win32 (either the spec -# target itself or some tools used by the spec). -# * `failed linking` annotates specs that compile but don't link (at least not on -# basis of the libraries from *Porting to Windows* guide). -# Most failures are caused by missing libraries (libxml2, libyaml, libgmp, -# libllvm, libz, libssl), but there also seem to be some incompatibilities -# with the existing libraries. -# * `failed to run` annotates specs that compile and link but don't properly -# execute. -# -# PREREQUISITES: -# -# This script requires a working win32 build environment as described in -# the [*Porting to Windows* guide](https://github.com/crystal-lang/crystal/wiki/Porting-to-Windows) -# -# USAGE: -# -# For std spec: -# $ spec/generate_windows_spec.cr > spec/win32_std_spec.cr -# For compiler spec: -# $ spec/generate_windows_spec.cr compiler > spec/win32_compiler_spec.cr - -SPEC_SUITE=${1:-std} -CRYSTAL_BIN=${CRYSTAL_BIN:-./crystal.exe} - -command="$0 $*" -echo "# This file is autogenerated by \`${command% }\`" -echo "# $(date --rfc-3339 seconds)" -echo - -for spec in $(find "spec/$SPEC_SUITE" -type f -iname "*_spec.cr" | LC_ALL=C sort); do - require="require \"./${spec##spec/}\"" - - if ! output=$($CRYSTAL_BIN build "$spec" -Di_know_what_im_doing -Dwithout_openssl 2>&1); then - if [[ "$output" =~ "execution of command failed" ]]; then - echo "# $require (failed linking)" - else - echo "# $require (failed codegen)" - fi - continue - fi - - binary_path="./$(basename $spec .cr).exe" - - "$binary_path" > /dev/null; exit=$? - - if [ $exit -eq 0 ] || [ $exit -eq 1 ]; then - echo "$require" - else - echo "# $require (failed to run)" - fi -done From 4ca9f933aff3fddcaf0e864ecb9bfe796a092900 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 2 Oct 2022 18:25:04 +0800 Subject: [PATCH 0043/1551] Fix overflow for `rand(Range(Int, Int))` when signed span is too large (#12545) --- spec/std/random_spec.cr | 5 +++++ src/random.cr | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/std/random_spec.cr b/spec/std/random_spec.cr index f9c8d5309075..4c937bb8865b 100644 --- a/spec/std/random_spec.cr +++ b/spec/std/random_spec.cr @@ -255,6 +255,11 @@ describe "Random" do end end + it "works with span exceeding int type's range" do + rng = TestRNG.new(RNG_DATA_8) + rng.rand(-100_i8..100_i8).should eq(53_i8) + end + it "works using U/Int128" do rng = TestRNG.new(RNG_DATA_128) RNG_DATA_128.each do |a| diff --git a/src/random.cr b/src/random.cr index 0cfc013d01ca..61fe9df09d6b 100644 --- a/src/random.cr +++ b/src/random.cr @@ -226,7 +226,9 @@ module Random end span += 1 end - range.begin + {{type}}.new!(rand_int(span)) + # this addition never overflows because `rand_int` is unsigned and + # `rand_int <= range.end - range.begin <= type::MAX - range.begin` + range.begin &+ rand_int(span) end # Generates a random integer in range `{{type}}::MIN..{{type}}::MAX`. From fd8801247d47820dfbbe4bcd4cb0ad65dd5ac8a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 3 Oct 2022 23:13:00 +0200 Subject: [PATCH 0044/1551] Update distribution-scripts (#12555) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ead891c0a2ee..209f4706d6ec 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "1ee8983d46c594f97b6fe70a711c5dbefe83ab45" + default: "f567925d4d36be64b7e211d0c166af9bdd92c75f" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From 43f0a6455e6fd57152c43125969fdf2c503e6fea Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 4 Oct 2022 07:09:40 -0300 Subject: [PATCH 0045/1551] Codegen: fix how unions are represented to not miss bytes (#12551) --- spec/compiler/codegen/c_union_spec.cr | 26 +++++++++++++++++ src/compiler/crystal/codegen/llvm_typer.cr | 33 ++++++++++++++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/spec/compiler/codegen/c_union_spec.cr b/spec/compiler/codegen/c_union_spec.cr index 2e23d860ef93..2870efd5833f 100644 --- a/spec/compiler/codegen/c_union_spec.cr +++ b/spec/compiler/codegen/c_union_spec.cr @@ -214,4 +214,30 @@ describe "Code gen: c union" do foo.read_int )).to_i.should eq(42) end + + it "moves unions around correctly (#12550)" do + run(%( + require "prelude" + + lib Lib + struct Foo + x : UInt8 + y : UInt16 + end + + union Bar + foo : Foo + padding : UInt8[6] # larger than `Foo` + end + end + + def foo + a = uninitialized Lib::Bar + a.padding.fill { |i| 1_u8 + i } + a + end + + foo.padding.sum + )).to_i.should eq((1..6).sum) + end end diff --git a/src/compiler/crystal/codegen/llvm_typer.cr b/src/compiler/crystal/codegen/llvm_typer.cr index 8d1eedbe026c..edc65e60e20d 100644 --- a/src/compiler/crystal/codegen/llvm_typer.cr +++ b/src/compiler/crystal/codegen/llvm_typer.cr @@ -327,6 +327,33 @@ module Crystal @structs[llvm_name] = a_struct end + # We are going to represent the union like this: + # 1. Find out what's the type with the largest alignment + # 2. Find out what's the type's size + # 3. Have the first member of the union be an array + # of ints that match that alignemnt, up to that type's + # size, followed by another member that is just bytes + # to fill the rest of the union's size. + # + # So for example if we have this: + # + # struct Foo + # x : Int8 + # y : Int32 + # end + # + # union Bar + # foo : Foo + # padding : UInt8[24] + # end + # + # We have that for Bar, the largest alignment of its types + # is 4 (for Foo's Int32). Foo's size is 8 bytes (4 for x, 4 for y). + # Then for the first union member we'll have [2 x i32]. + # The total size of the union is 24 bytes. We already filled + # 8 bytes so we still need 16 bytes. + # The resulting union is { [2 x i32], [16 x i8] }. + max_size = 0 max_align = 0 max_align_type = nil @@ -351,8 +378,10 @@ module Crystal end end - max_align_type = max_align_type.not_nil! - union_fill = [max_align_type] of LLVM::Type + filler = @llvm_context.int(max_align * 8) + filler_size = max_align_type_size // max_align + + union_fill = [filler.array(filler_size)] of LLVM::Type if max_align_type_size < max_size union_fill << @llvm_context.int8.array(max_size - max_align_type_size) end From 6d02749cabbbf7aee8cd906c505524c6c1dedbdc Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 4 Oct 2022 19:25:22 -0300 Subject: [PATCH 0046/1551] Interpreter: implement variable autocast (#12563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/compiler/interpreter/autocast_spec.cr | 33 +++++++++++++++++++ src/compiler/crystal/interpreter/compiler.cr | 11 +++++-- .../crystal/interpreter/primitives.cr | 2 +- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/spec/compiler/interpreter/autocast_spec.cr b/spec/compiler/interpreter/autocast_spec.cr index 9efe9fda3582..0aa9e93c250c 100644 --- a/spec/compiler/interpreter/autocast_spec.cr +++ b/spec/compiler/interpreter/autocast_spec.cr @@ -102,5 +102,38 @@ describe Crystal::Repl::Interpreter do foo.color.value CODE end + + it "autocasts integer var to integer (#12560)" do + interpret(<<-CODE).should eq(1) + def foo(x : Int64) + x + end + + x = 1_i32 + foo(x) + CODE + end + + it "autocasts integer var to float (#12560)" do + interpret(<<-CODE).should eq(1) + def foo(x : Float64) + x + end + + x = 1_i32 + foo(x) + CODE + end + + it "autocasts float32 var to float64 (#12560)" do + interpret(<<-CODE).should eq(1) + def foo(x : Float64) + x + end + + x = 1.0_f32 + foo(x) + CODE + end end end diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 39296b49ad6b..1ec8d115b170 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -2341,9 +2341,14 @@ class Crystal::Repl::Compiler < Crystal::Visitor request_value(arg) - # We first cast the argument to the def's arg type, - # which is the external methods' type. - downcast arg, arg_type, target_def_arg_type + # Check number autocast but for non-literals + if arg_type != target_def_arg_type && arg_type.is_a?(IntegerType | FloatType) && target_def_arg_type.is_a?(IntegerType | FloatType) + primitive_convert(arg, arg_type, target_def_arg_type, checked: false) + else + # We first cast the argument to the def's arg type, + # which is the external methods' type. + downcast arg, arg_type, target_def_arg_type + end # Then we need to cast the argument to the target_def variable # corresponding to the argument. If for example we have this: diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr index 3980ff1d060a..34cc22b2dbab 100644 --- a/src/compiler/crystal/interpreter/primitives.cr +++ b/src/compiler/crystal/interpreter/primitives.cr @@ -646,7 +646,7 @@ class Crystal::Repl::Compiler to_kind = integer_or_float_kind(to_type) unless from_kind && to_kind - node.raise "BUG: missing handling of unchecked_convert for #{from_type} (#{node.name})" + node.raise "BUG: missing handling of unchecked_convert for #{from_type} (#{node})" end primitive_convert(node, from_kind, to_kind, checked: checked) From 7b7d28e4006edd06f74cfe4c341e271ed521b744 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 4 Oct 2022 19:26:07 -0300 Subject: [PATCH 0047/1551] Interpreter: handle missing upcast from GenericClassInstanceMetaclassType to VirtualMetaclassType (#12562) --- spec/compiler/interpreter/casts_spec.cr | 14 ++++++++++++++ src/compiler/crystal/interpreter/cast.cr | 7 +------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/spec/compiler/interpreter/casts_spec.cr b/spec/compiler/interpreter/casts_spec.cr index 57707bc80d9a..f8c59faca2d6 100644 --- a/spec/compiler/interpreter/casts_spec.cr +++ b/spec/compiler/interpreter/casts_spec.cr @@ -482,5 +482,19 @@ describe Crystal::Repl::Interpreter do B.new.as?(A).foo CODE end + + it "upcasts GenericClassInstanceMetaclassType to VirtualMetaclassType" do + interpret(<<-CODE).should eq(2) + class Foo + def self.foo; 1; end + end + + class Gen(T) < Foo + def self.foo; 2; end + end + + Gen(Int32).as(Foo.class).foo + CODE + end end end diff --git a/src/compiler/crystal/interpreter/cast.cr b/src/compiler/crystal/interpreter/cast.cr index 4fe9b81dd8b0..8fed0e3d5d5d 100644 --- a/src/compiler/crystal/interpreter/cast.cr +++ b/src/compiler/crystal/interpreter/cast.cr @@ -315,12 +315,7 @@ class Crystal::Repl::Compiler # Nothing to do end - private def upcast_distinct(node : ASTNode, from : MetaclassType, to : VirtualMetaclassType) - # TODO: not tested - end - - private def upcast_distinct(node : ASTNode, from : VirtualMetaclassType, to : VirtualMetaclassType) - # TODO: not tested + private def upcast_distinct(node : ASTNode, from : MetaclassType | VirtualMetaclassType | GenericClassInstanceMetaclassType, to : VirtualMetaclassType) end private def upcast_distinct(node : ASTNode, from : Type, to : Type) From 268aafd8e069ee1c14cfe5250868bc71c3e21245 Mon Sep 17 00:00:00 2001 From: Michael Wagner Date: Wed, 5 Oct 2022 07:14:27 -0400 Subject: [PATCH 0048/1551] chore: fix alignment typo in compiler comments (#12564) --- src/compiler/crystal/codegen/llvm_typer.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/codegen/llvm_typer.cr b/src/compiler/crystal/codegen/llvm_typer.cr index edc65e60e20d..1563c7b502c0 100644 --- a/src/compiler/crystal/codegen/llvm_typer.cr +++ b/src/compiler/crystal/codegen/llvm_typer.cr @@ -331,7 +331,7 @@ module Crystal # 1. Find out what's the type with the largest alignment # 2. Find out what's the type's size # 3. Have the first member of the union be an array - # of ints that match that alignemnt, up to that type's + # of ints that match that alignment, up to that type's # size, followed by another member that is just bytes # to fill the rest of the union's size. # From 826a631d468ba119ec0cc31c9f173fba40ed7716 Mon Sep 17 00:00:00 2001 From: analogsalad Date: Wed, 5 Oct 2022 14:15:52 +0300 Subject: [PATCH 0049/1551] Document `after_initialize` method for `yaml` and `json` serializers (#12530) --- src/json/serialization.cr | 22 ++++++++++++++++++++++ src/yaml/serialization.cr | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/json/serialization.cr b/src/json/serialization.cr index 450a15287236..0e7ff1782737 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -126,6 +126,28 @@ module JSON # field, and the rest of the fields, and their meaning, depend on its value. # # You can use `JSON::Serializable.use_json_discriminator` for this use case. + # + # ### `after_initialize` method + # + # `#after_initialize` is a method that runs after an instance is deserialized + # from JSON. It can be used as a hook to post-process the initialized object. + # + # Example: + # ``` + # require "json" + # + # class Person + # include JSON::Serializable + # getter name : String + # + # def after_initialize + # @name = @name.upcase + # end + # end + # + # person = Person.from_json %({"name": "Jane"}) + # person.name # => "JANE" + # ``` module Serializable annotation Options end diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index f87eae8c39f1..6b08d525d171 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -125,6 +125,28 @@ module YAML # field, and the rest of the fields, and their meaning, depend on its value. # # You can use `YAML::Serializable.use_yaml_discriminator` for this use case. + # + # ### `after_initialize` method + # + # `#after_initialize` is a method that runs after an instance is deserialized + # from YAML. It can be used as a hook to post-process the initialized object. + # + # Example: + # ``` + # require "yaml" + # + # class Person + # include YAML::Serializable + # getter name : String + # + # def after_initialize + # @name = @name.upcase + # end + # end + # + # person = Person.from_yaml "---\nname: Jane\n" + # person.name # => "JANE" + # ``` module Serializable annotation Options end From 41573fadba11fecc43c03b838c27bdb816e1b928 Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Thu, 6 Oct 2022 17:13:58 -0300 Subject: [PATCH 0050/1551] Add CHANGELOG for 1.6.0 (#12568) --- CHANGELOG.md | 250 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 251 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f2e1a4d34c..7bc5fb808b80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,253 @@ +# 1.6.0 (2022-10-06) + +## Language + +- Add 'wasm_import_module' option to the `@[Link]` annotation ([#11935](https://github.com/crystal-lang/crystal/pull/11935), thanks @lbguilherme) + +## Standard Library + +- Use `GC.malloc_atomic` with `GC.realloc`, not `Pointer#realloc` ([#12391](https://github.com/crystal-lang/crystal/pull/12391), thanks @HertzDevil) +- Improve syntax highlighter ([#12409](https://github.com/crystal-lang/crystal/pull/12409), thanks @I3oris) +- Enable miscellaneous parts of the standard library on Windows ([#12281](https://github.com/crystal-lang/crystal/pull/12281), thanks @HertzDevil) +- Use interpreter to run std spec tests ([#12355](https://github.com/crystal-lang/crystal/pull/12355), thanks @cyangle) +- Remove most uses of `Symbol` variables in standard library specs ([#12462](https://github.com/crystal-lang/crystal/pull/12462), thanks @HertzDevil) +- Use `@[::Primitive]` and `@[::Flags]` where necessary ([#11900](https://github.com/crystal-lang/crystal/pull/11900), thanks @HertzDevil) +- Document how to change base type of an enum ([#9803](https://github.com/crystal-lang/crystal/pull/9803), thanks @Blacksmoke16) +- Spec: bump and document timeouts in interpreted mode ([#12430](https://github.com/crystal-lang/crystal/pull/12430), thanks @asterite) + +### Collection + +- Refactor and improve `NamedTuple` deserialization from JSON and YAML ([#12008](https://github.com/crystal-lang/crystal/pull/12008), thanks @HertzDevil) +- **(performance)** Optimize `BitArray#tally(hash)` ([#11909](https://github.com/crystal-lang/crystal/pull/11909), thanks @HertzDevil) +- Use `Slice#unsafe_slice_of` and `#to_unsafe_bytes` in the standard library and compiler ([#12280](https://github.com/crystal-lang/crystal/pull/12280), thanks @HertzDevil) +- **(performance)** Optimize block-less overloads of `BitArray#index` and `#rindex` ([#12087](https://github.com/crystal-lang/crystal/pull/12087), thanks @HertzDevil) +- Support tuple metaclass indexers with non-literal arguments ([#11834](https://github.com/crystal-lang/crystal/pull/11834), thanks @HertzDevil) +- Add `Indexable#index!` overloads with `offset` parameter ([#12089](https://github.com/crystal-lang/crystal/pull/12089), thanks @HertzDevil) + +### Concurrency + +- Fix fiber clean loop on Windows ([#12300](https://github.com/crystal-lang/crystal/pull/12300), thanks @HertzDevil) +- Enable `Mutex` on Windows ([#12213](https://github.com/crystal-lang/crystal/pull/12213), thanks @HertzDevil) + +### Crypto + +- Add support for Bcrypt algorithm version 2y ([#12447](https://github.com/crystal-lang/crystal/pull/12447), thanks @docelic) +- Allow using `U/Int128` in `Random` ([#11977](https://github.com/crystal-lang/crystal/pull/11977), thanks @BlobCodes) + +### Files + +- **(breaking-change)** Define `#system_echo` and `#system_raw` on all systems ([#12352](https://github.com/crystal-lang/crystal/pull/12352), thanks @HertzDevil) +- **(breaking-change)** Do not expose `Crystal::System::FileInfo` through `File::Info` ([#12385](https://github.com/crystal-lang/crystal/pull/12385), thanks @HertzDevil) +- Fix `IO.pipe` spec on FreeBSD ([#12324](https://github.com/crystal-lang/crystal/pull/12324), thanks @dmgk) +- Fix docs error for `File.match?` `**` globbing pattern. ([#12343](https://github.com/crystal-lang/crystal/pull/12343), thanks @zw963) +- Add `Dir#info` ([#11991](https://github.com/crystal-lang/crystal/pull/11991), thanks @didactic-drunk) +- Implement `IO::FileDescriptor`'s console methods on Windows ([#12294](https://github.com/crystal-lang/crystal/pull/12294), thanks @HertzDevil) +- Fix typo: `LibC::DT_LINK` -> `DT_LNK` ([#11954](https://github.com/crystal-lang/crystal/pull/11954), thanks @HertzDevil) +- Document `IO::FileDescriptor#info` ([#12384](https://github.com/crystal-lang/crystal/pull/12384), thanks @HertzDevil) +- **(performance)** Introduce `IO::DEFAULT_BUFFER_SIZE` ([#12507](https://github.com/crystal-lang/crystal/pull/12507), thanks @straight-shoota) +- Add support for `IO::FileDescriptor` staying open on finalize ([#12367](https://github.com/crystal-lang/crystal/pull/12367), thanks @refi64) + +### Macros + +- Enhance `record` documentation ([#12334](https://github.com/crystal-lang/crystal/pull/12334), thanks @straight-shoota) + +### Networking + +- Add `Socket::IPAddress.valid?` ([#12489](https://github.com/crystal-lang/crystal/pull/12489), [#10492](https://github.com/crystal-lang/crystal/pull/10492), thanks @straight-shoota) +- Fix `HTTP::Client#exec` to abort retry when client was closed ([#12465](https://github.com/crystal-lang/crystal/pull/12465), thanks @straight-shoota) +- Fix specs with side effects ([#12539](https://github.com/crystal-lang/crystal/pull/12539), thanks @straight-shoota) +- Fix `HTTP::Client` implicit compression with retry ([#12536](https://github.com/crystal-lang/crystal/pull/12536), thanks @straight-shoota) +- `HTTP::StaticFileHandler`: Reduce max stat calls from 6 to 2 ([#12310](https://github.com/crystal-lang/crystal/pull/12310), thanks @didactic-drunk) +- Add warning about concurrent requests in `HTTP::Client` ([#12527](https://github.com/crystal-lang/crystal/pull/12527), thanks @straight-shoota) + +### Numeric + +- Add full integer support to `sprintf` and `String#%` ([#10973](https://github.com/crystal-lang/crystal/pull/10973), thanks @HertzDevil) +- Make `Float#to_s` ignore NaN sign bit ([#12399](https://github.com/crystal-lang/crystal/pull/12399), thanks @HertzDevil) +- Make `sprintf` and `String#%` ignore NaN sign bit ([#12400](https://github.com/crystal-lang/crystal/pull/12400), thanks @HertzDevil) +- Fix `Complex#to_s` imaginary component sign for certain values ([#12244](https://github.com/crystal-lang/crystal/pull/12244), thanks @HertzDevil) +- More accurate definition of `Complex#sign` ([#12242](https://github.com/crystal-lang/crystal/pull/12242), thanks @HertzDevil) +- Fix overflow for `rand(Range(Int, Int))` when signed span is too large ([#12545](https://github.com/crystal-lang/crystal/pull/12545), thanks @HertzDevil) +- **(performance)** Add `#rotate_left` and `#rotate_right` for primitive integers ([#12307](https://github.com/crystal-lang/crystal/pull/12307), thanks @HertzDevil) +- **(performance)** Optimize `BigDecimal#div` for inexact divisions ([#10803](https://github.com/crystal-lang/crystal/pull/10803), thanks @HertzDevil) +- Implement the Dragonbox algorithm for `Float#to_s` ([#10913](https://github.com/crystal-lang/crystal/pull/10913), thanks @HertzDevil) +- Add `U/Int128` to `isqrt` spec ([#11976](https://github.com/crystal-lang/crystal/pull/11976), thanks @BlobCodes) + +### Runtime + +- Fix: Parse DWARF5 Data16 values ([#12497](https://github.com/crystal-lang/crystal/pull/12497), thanks @stakach) +- macOS: Fix call stack when executable path contains symlinks ([#12504](https://github.com/crystal-lang/crystal/pull/12504), thanks @HertzDevil) +- WASM: Add support for `wasi-sdk 16`: don't rely on `__original_main` ([#12450](https://github.com/crystal-lang/crystal/pull/12450), thanks @lbguilherme) + +### Serialization + +- Fix YAML serialization class name ambiguity ([#12537](https://github.com/crystal-lang/crystal/pull/12537), thanks @hugopl) +- Allow non-type converter instances in `ArrayConverter` and `HashValueConverter` ([#10638](https://github.com/crystal-lang/crystal/pull/10638), thanks @HertzDevil) +- Document `after_initialize` method for `yaml` and `json` serializers ([#12530](https://github.com/crystal-lang/crystal/pull/12530), thanks @analogsalad) + +### System + +- Add missing fields to `LibC::Passwd` on FreeBSD ([#12315](https://github.com/crystal-lang/crystal/pull/12315), thanks @dmgk) +- Add platform-specific variants of `Process.parse_arguments` ([#12278](https://github.com/crystal-lang/crystal/pull/12278), thanks @HertzDevil) +- Make `Dir.current` respect `$PWD` ([#12471](https://github.com/crystal-lang/crystal/pull/12471), thanks @straight-shoota) + +### Text + +- Fix `String` shift state specs on FreeBSD ([#12339](https://github.com/crystal-lang/crystal/pull/12339), thanks @dmgk) +- Disallow mixing of sequential and named `sprintf` parameters ([#12402](https://github.com/crystal-lang/crystal/pull/12402), thanks @HertzDevil) +- Fix `Colorize` doc example ([#12492](https://github.com/crystal-lang/crystal/pull/12492), thanks @zw963) +- **(performance)** Optimize `String#downcase` and `String#upcase` for single byte optimizable case ([#12389](https://github.com/crystal-lang/crystal/pull/12389), thanks @asterite) +- **(performance)** Optimize `String#valid_encoding?` ([#12145](https://github.com/crystal-lang/crystal/pull/12145), thanks @HertzDevil) +- Implement `String#unicode_normalize` and `String#unicode_normalized?` ([#11226](https://github.com/crystal-lang/crystal/pull/11226), thanks @HertzDevil) +- Support parameter numbers in `sprintf` ([#12448](https://github.com/crystal-lang/crystal/pull/12448), thanks @HertzDevil) +- Use `LibC.malloc` instead of `GC.malloc` for LibPCRE allocations ([#12456](https://github.com/crystal-lang/crystal/pull/12456), thanks @lbguilherme) +- Unicode: Update to version 15.0.0 ([#12479](https://github.com/crystal-lang/crystal/pull/12479), thanks @HertzDevil) +- Avoid free call in interpreted mode ([#12496](https://github.com/crystal-lang/crystal/pull/12496), thanks @straight-shoota) + +## Compiler + +- Improve recursive splat expansion detection ([#11790](https://github.com/crystal-lang/crystal/pull/11790), thanks @asterite) +- Compiler: fix `#to_s` for empty parameters of lib funs ([#12368](https://github.com/crystal-lang/crystal/pull/12368), thanks @HertzDevil) +- Compiler: transform `Proc(*T, Void)` to `Proc(*T, Nil)` ([#12388](https://github.com/crystal-lang/crystal/pull/12388), thanks @asterite) +- Compiler: indent `begin` `Expression`s that are direct node children ([#12362](https://github.com/crystal-lang/crystal/pull/12362), thanks @HertzDevil) +- Compiler: add missing location to node on literal expander for array ([#12403](https://github.com/crystal-lang/crystal/pull/12403), thanks @asterite) +- Compiler: a generic class type can also be reference-like ([#12347](https://github.com/crystal-lang/crystal/pull/12347), thanks @asterite) +- Hoist complex element expressions outside container literals ([#12366](https://github.com/crystal-lang/crystal/pull/12366), thanks @HertzDevil) +- **(performance)** Compiler: bind to tuple, not array ([#12423](https://github.com/crystal-lang/crystal/pull/12423), thanks @asterite) +- Use `Path.new(string)` instead of `Path.new([string])` ([#12419](https://github.com/crystal-lang/crystal/pull/12419), thanks @asterite) +- Decouple warning detection from program instances ([#12293](https://github.com/crystal-lang/crystal/pull/12293), thanks @HertzDevil) +- **(performance)** Compiler: only have `freeze_type` in select AST nodes ([#12428](https://github.com/crystal-lang/crystal/pull/12428), thanks @asterite) +- Correctly display codegen when cross-compiling ([#12414](https://github.com/crystal-lang/crystal/pull/12414), thanks @luislavena) +- Compiler: simplify some calls ([#12417](https://github.com/crystal-lang/crystal/pull/12417), thanks @asterite) +- **(performance)** Compiler: optimizations in `merge_if_vars` ([#12432](https://github.com/crystal-lang/crystal/pull/12432), [#12433](https://github.com/crystal-lang/crystal/pull/12433), thanks @asterite) +- Compiler refactor: extract `type_from_dependencies` ([#12437](https://github.com/crystal-lang/crystal/pull/12437), thanks @asterite) +- **(performance)** Compiler: refactor and slightly optimize merging two types ([#12436](https://github.com/crystal-lang/crystal/pull/12436), thanks @asterite) +- **(performance)** Compiler optimization: don't create call for hook unless needed ([#12452](https://github.com/crystal-lang/crystal/pull/12452), thanks @asterite) +- **(performance)** CrystalPath: Cache `Dir.current` to avoid thousands of allocations ([#12455](https://github.com/crystal-lang/crystal/pull/12455), thanks @yxhuvud) +- Better call error messages ([#12469](https://github.com/crystal-lang/crystal/pull/12469), thanks @asterite) +- **(performance)** Compiler optimization: avoid intermediate array when matching call arg types ([#12485](https://github.com/crystal-lang/crystal/pull/12485), thanks @asterite) + +### Codegen + +- Codegen: fix how unions are represented to not miss bytes ([#12551](https://github.com/crystal-lang/crystal/pull/12551), thanks @asterite) +- Fix alignment typo in compiler comments ([#12564](https://github.com/crystal-lang/crystal/pull/12564), thanks @mdwagner) +- Remove redundant code from x86_64 abi ([#12443](https://github.com/crystal-lang/crystal/pull/12443), thanks @mattrberry) +- Codegen: use var pointer for `out` instead of an extra variable ([#10952](https://github.com/crystal-lang/crystal/pull/10952), thanks @asterite) + +### Debugger + +- Basic GDB formatter support ([#12209](https://github.com/crystal-lang/crystal/pull/12209), thanks @HertzDevil) +- Add Visual Studio formatters for `String`, `Array`, and `Hash` ([#12212](https://github.com/crystal-lang/crystal/pull/12212), thanks @HertzDevil) + +### Interpreter + +- Interpreter: handle the case of a def's body with no type ([#12220](https://github.com/crystal-lang/crystal/pull/12220), thanks @asterite) +- Interpreter: simplify ivar initialization ([#12222](https://github.com/crystal-lang/crystal/pull/12222), thanks @asterite) +- Interpreter: fix autocasting in multidispatch ([#12223](https://github.com/crystal-lang/crystal/pull/12223), thanks @asterite) +- Interpreter: handle `next` inside captured block ([#12237](https://github.com/crystal-lang/crystal/pull/12237), thanks @asterite) +- Interpreter: fix `crystal_type_id` for virtual metaclass type ([#12246](https://github.com/crystal-lang/crystal/pull/12246), thanks @asterite) +- Interpreter: handle yield with splat combined with tuple unpacking ([#12247](https://github.com/crystal-lang/crystal/pull/12247), thanks @asterite) +- Interpreter: handle inlined call that returns self for structs ([#12259](https://github.com/crystal-lang/crystal/pull/12259), thanks @asterite) +- Interpreter: implement `Int128`/`UInt128` intrinsics ([#12258](https://github.com/crystal-lang/crystal/pull/12258), thanks @asterite) +- Interpreter: fix some conversion primitives ([#12257](https://github.com/crystal-lang/crystal/pull/12257), thanks @asterite) +- Interpreter: don't override special vars inside block ([#12251](https://github.com/crystal-lang/crystal/pull/12251), thanks @asterite) +- Interpreter: add missing cast from tuple to other tuple inside union ([#12249](https://github.com/crystal-lang/crystal/pull/12249), thanks @asterite) +- Interpreter: allow declaring local vars during a pry session ([#12180](https://github.com/crystal-lang/crystal/pull/12180), thanks @asterite) +- Interpreter: handle bitreverse intrinsics ([#12273](https://github.com/crystal-lang/crystal/pull/12273), thanks @asterite) +- Interpreter: cache methods with captured block ([#12285](https://github.com/crystal-lang/crystal/pull/12285), thanks @asterite) +- Interpreter: missing downcast from `MixedUnionType` to `NilableProcType` ([#12286](https://github.com/crystal-lang/crystal/pull/12286), thanks @asterite) +- Interpreter: fix `with ... yield` with extra arguments ([#12301](https://github.com/crystal-lang/crystal/pull/12301), thanks @asterite) +- Interpreter: consider nodes without a type as `NoReturn` ([#12275](https://github.com/crystal-lang/crystal/pull/12275), thanks @asterite) +- Interpreter: take `with ... yield` scope into account for args bytesize ([#12317](https://github.com/crystal-lang/crystal/pull/12317), thanks @asterite) +- Fix loader spec on FreeBSD ([#12323](https://github.com/crystal-lang/crystal/pull/12323), thanks @dmgk) +- Interpreter: inline ivar access for virtual call with a single child ([#12321](https://github.com/crystal-lang/crystal/pull/12321), thanks @asterite) +- Interpreter: fix `as?` when there's no resulting type ([#12328](https://github.com/crystal-lang/crystal/pull/12328), thanks @asterite) +- Interpreter: handle missing closured struct self ([#12345](https://github.com/crystal-lang/crystal/pull/12345), thanks @asterite) +- Interpreter: use `non_nilable_type` in NilableCast ([#12348](https://github.com/crystal-lang/crystal/pull/12348), thanks @asterite) +- Interpreter: implement mixed union cast with compatible tuple types ([#12349](https://github.com/crystal-lang/crystal/pull/12349), thanks @asterite) +- Interpreter: fix missing `upcast_distinct` from `A+` to `B` (`Crystal::VirtualType` to `Crystal::NonGenericClassType`) ([#12374](https://github.com/crystal-lang/crystal/pull/12374), thanks @asterite) +- Interpreter: discard tuple and named tuple ([#12387](https://github.com/crystal-lang/crystal/pull/12387), thanks @asterite) +- Interpreter: cast proc call arguments to proc arg types ([#12375](https://github.com/crystal-lang/crystal/pull/12375), thanks @asterite) +- Interpreter: set correct scope for class var initializer ([#12441](https://github.com/crystal-lang/crystal/pull/12441), thanks @asterite) +- Interpreter (repl): use new `MainVisitor` each time we need to interpret code ([#12512](https://github.com/crystal-lang/crystal/pull/12512), thanks @asterite) +- Interpreter: allow inspecting block vars without affecting program ([#12520](https://github.com/crystal-lang/crystal/pull/12520), thanks @asterite) +- Interpreter: check upcast in nilable cast ([#12533](https://github.com/crystal-lang/crystal/pull/12533), thanks @asterite) +- Interpreter: implement variable autocast ([#12563](https://github.com/crystal-lang/crystal/pull/12563), thanks @asterite) +- Interpreter: handle missing upcast from `GenericClassInstanceMetaclassType` to `VirtualMetaclassType` ([#12562](https://github.com/crystal-lang/crystal/pull/12562), thanks @asterite) +- Interpreter: let local vars be seen by macros in repl and pry ([#12240](https://github.com/crystal-lang/crystal/pull/12240), thanks @asterite) +- Interpreter: handle local variable type declaration ([#12239](https://github.com/crystal-lang/crystal/pull/12239), thanks @asterite) +- Support libffi on Windows ([#12200](https://github.com/crystal-lang/crystal/pull/12200), thanks @HertzDevil) +- Add `$CRYSTAL_INTERPRETER_LOADER_INFO` to show loaded libraries ([#12221](https://github.com/crystal-lang/crystal/pull/12221), thanks @straight-shoota) +- Interpreter: node override ([#12287](https://github.com/crystal-lang/crystal/pull/12287), thanks @asterite) +- Interpreter: introduce a `Prompt` type ([#12288](https://github.com/crystal-lang/crystal/pull/12288), thanks @asterite) +- Interpreter: missing `i += 1` ([#12381](https://github.com/crystal-lang/crystal/pull/12381), thanks @asterite) +- Support building the interpreter on Windows ([#12397](https://github.com/crystal-lang/crystal/pull/12397), thanks @HertzDevil) +- Don't exit in interpreter spec and change type from `Nil` to `NoReturn` in `FixMissingTypes` ([#12230](https://github.com/crystal-lang/crystal/pull/12230), thanks @asterite) +- Interpreter: fix multidispatch with captured block ([#12236](https://github.com/crystal-lang/crystal/pull/12236), thanks @asterite) +- Interpreter: don't change compiled mode logic ([#12252](https://github.com/crystal-lang/crystal/pull/12252), thanks @asterite) +- Wait more in `HTTP::Server` specs in interpreted mode ([#12420](https://github.com/crystal-lang/crystal/pull/12420), thanks @asterite) + +### Parser + +- Lexer: fix index out of bounds when scanning numbers ([#12482](https://github.com/crystal-lang/crystal/pull/12482), thanks @asterite) +- Fix parser to never create doc from trailing comment ([#11268](https://github.com/crystal-lang/crystal/pull/11268), thanks @straight-shoota) +- Parser: declare local vars of indirect type declarations in call args ([#11983](https://github.com/crystal-lang/crystal/pull/11983), thanks @asterite) +- Remove redundant conditional ([#12196](https://github.com/crystal-lang/crystal/pull/12196), thanks @potomak) +- Warn on suffix-less integer literals outside `Int64`'s range ([#12427](https://github.com/crystal-lang/crystal/pull/12427), thanks @HertzDevil) +- Use enum instead of symbols for keywords in the lexer ([#11871](https://github.com/crystal-lang/crystal/pull/11871), thanks @HertzDevil) +- Parser: Rename `arg*` to `param*` ([#12235](https://github.com/crystal-lang/crystal/pull/12235), thanks @potomak) +- Fix test cases ([#12508](https://github.com/crystal-lang/crystal/pull/12508), thanks @potomak) + +### Semantic + +- **(breaking-change)** Allow `Union` restrictions to be ordered before all other restrictions ([#12335](https://github.com/crystal-lang/crystal/pull/12335), thanks @HertzDevil) +- **(breaking-change)** Use more robust ordering between def overloads ([#10711](https://github.com/crystal-lang/crystal/pull/10711), thanks @HertzDevil) +- Fix: Instance vars should not be allowed on `Class`, `Tuple`, `NamedTuple`, `Enum`, `Pointer` , `Proc`, `StaticArray` and `Union`. ([#12160](https://github.com/crystal-lang/crystal/pull/12160), thanks @I3oris) +- Compiler and interpreter: fix `is_a?` from virtual metaclass to generic metaclass ([#12306](https://github.com/crystal-lang/crystal/pull/12306), thanks @asterite) +- Compiler: fix type descendent for union metaclass ([#12308](https://github.com/crystal-lang/crystal/pull/12308), thanks @asterite) +- Compiler: fix `is_a?` from generic class against generic class instance type ([#12312](https://github.com/crystal-lang/crystal/pull/12312), thanks @asterite) +- Fix `self` in restrictions when instantiating macro def in subtypes ([#10954](https://github.com/crystal-lang/crystal/pull/10954), thanks @HertzDevil) +- Never resolve free variables as types during overload ordering ([#11973](https://github.com/crystal-lang/crystal/pull/11973), thanks @HertzDevil) +- Use instantiated type as `self` when inferring instance variable types ([#12466](https://github.com/crystal-lang/crystal/pull/12466), thanks @HertzDevil) +- Fix restriction comparison between `Metaclass` and `Path` ([#12523](https://github.com/crystal-lang/crystal/pull/12523), thanks @HertzDevil) +- **(performance)** Compiler: don't always use Array for node dependencies and observers ([#12405](https://github.com/crystal-lang/crystal/pull/12405), thanks @asterite) +- Compiler: better error message for symbol against enum ([#12478](https://github.com/crystal-lang/crystal/pull/12478), thanks @asterite) + +## Tools + +### Docs-generator + +- Fix docs generator search constants id ([#12262](https://github.com/crystal-lang/crystal/pull/12262), thanks @GeopJr) + +### Formatter + +- Formatter: format comment after select ([#12506](https://github.com/crystal-lang/crystal/pull/12506), thanks @asterite) +- Formatter: try to format macros that don't interpolate content ([#12378](https://github.com/crystal-lang/crystal/pull/12378), thanks @asterite) + +### Playground + +- Playground: Fix pass bound hostname to run sessions ([#12356](https://github.com/crystal-lang/crystal/pull/12356), thanks @orangeSi) +- Don't show stacktrace when playground port is already in use. ([#11844](https://github.com/crystal-lang/crystal/pull/11844), thanks @hugopl) +- Indent playground code using spaces ([#12231](https://github.com/crystal-lang/crystal/pull/12231), thanks @potomak) + +## Other + +- `bin/crystal`: Ensure `sh` compatibility ([#12486](https://github.com/crystal-lang/crystal/pull/12486), thanks @HertzDevil) +- bumping version 1.6.0-dev ([#12263](https://github.com/crystal-lang/crystal/pull/12263), thanks @beta-ziliani) +- updating CI to 1.5.0 ([#12260](https://github.com/crystal-lang/crystal/pull/12260), thanks @beta-ziliani) +- Add fish shell completion ([#12026](https://github.com/crystal-lang/crystal/pull/12026), thanks @TunkShif) +- Execute `compopt` only when it's present ([#12248](https://github.com/crystal-lang/crystal/pull/12248), thanks @potomak) +- Use `Makefile.win` and wrapper script on Windows CI ([#12344](https://github.com/crystal-lang/crystal/pull/12344), thanks @HertzDevil) +- [Makefile] Add format target ([#11420](https://github.com/crystal-lang/crystal/pull/11420), thanks @straight-shoota) +- Update contact section of CODE of CONDUCT ([#9219](https://github.com/crystal-lang/crystal/pull/9219), thanks @paulcsmith) +- Update nixpkgs 22.05 and LLVM 11 ([#12498](https://github.com/crystal-lang/crystal/pull/12498), thanks @straight-shoota) +- [Makefile] Use `EXPORT_CC` for `make crystal` ([#11760](https://github.com/crystal-lang/crystal/pull/11760), thanks @straight-shoota) +- Update distribution-scripts ([#12502](https://github.com/crystal-lang/crystal/pull/12502), [#12555](https://github.com/crystal-lang/crystal/pull/12555), thanks @straight-shoota) +- Fix and enhance `scripts/update-distribution-scripts.sh` ([#12503](https://github.com/crystal-lang/crystal/pull/12503), thanks @straight-shoota) +- [CI] Upgrade GitHub Actions to macos-11 ([#12500](https://github.com/crystal-lang/crystal/pull/12500), thanks @straight-shoota) +- Add icon and metadata to Windows Crystal compiler binary ([#12494](https://github.com/crystal-lang/crystal/pull/12494), thanks @HertzDevil) +- Remove `spec/win32_std_spec.cr` and `spec/generate_windows_spec.sh` ([#12282](https://github.com/crystal-lang/crystal/pull/12282), [#12549](https://github.com/crystal-lang/crystal/pull/12549), thanks @HertzDevil and @straight-shoota) + # 1.5.1 (2022-09-07) ## Standard Library diff --git a/src/VERSION b/src/VERSION index bcfbf475d5e0..dc1e644a1014 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.6.0-dev +1.6.0 From 619fa67017007f529fb7f2bd854b1a5355b81ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 10 Oct 2022 16:11:29 +0200 Subject: [PATCH 0051/1551] Bump version to 1.6.1-dev (#12588) --- src/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VERSION b/src/VERSION index dc1e644a1014..06bb45291686 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.6.0 +1.6.1-dev From 1a386a732b750c4bdbc1373014f051ecc45e72ee Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 10 Oct 2022 22:11:41 +0800 Subject: [PATCH 0052/1551] Disable failing specs on Windows CI (#12585) --- spec/std/http/client/client_spec.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index 94b86fd3ed0e..ff08639d7802 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -339,7 +339,7 @@ module HTTP end end - it "tests write_timeout" do + pending_win32 "tests write_timeout" do # Here we don't want to write a response on the server side because # it doesn't make sense to try to write because the client will already # timeout on read. Writing a response could lead on an exception in @@ -353,7 +353,7 @@ module HTTP end end - it "tests connect_timeout" do + pending_win32 "tests connect_timeout" do test_server("localhost", 0, 0) do |server| client = Client.new("localhost", server.local_address.port) client.connect_timeout = 0.5 From b01d9ebb925be8e43ef5fdbfedbece122a7f4e4b Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Mon, 10 Oct 2022 13:43:03 -0400 Subject: [PATCH 0053/1551] Fix doc typos in `Socket::IPAddress` (#12583) --- src/socket/address.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socket/address.cr b/src/socket/address.cr index d29aa1327783..5ce5f40bf4d4 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -169,7 +169,7 @@ class Socket valid_v4?(address) || valid_v6?(address) end - # Returns `true` if *address* is a valid IPv4 address. + # Returns `true` if *address* is a valid IPv6 address. def self.valid_v6?(address : String) : Bool !address_v6?(address).nil? end @@ -180,7 +180,7 @@ class Socket addr if LibC.inet_pton(LibC::AF_INET6, address, pointerof(addr)) == 1 end - # Returns `true` if *address* is a valid IPv5 address. + # Returns `true` if *address* is a valid IPv4 address. def self.valid_v4?(address : String) : Bool !address_v4?(address).nil? end From 5cf5e5ab960ebc3ced90974940dac5a034b9f12f Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 11 Oct 2022 06:40:05 -0300 Subject: [PATCH 0054/1551] Formatter: escape backslashes in macro literals when subformatting (#12582) --- spec/compiler/formatter/formatter_spec.cr | 11 +++++++++++ src/compiler/crystal/tools/formatter.cr | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 62081303d287..f8f8d5fddd0e 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -2225,4 +2225,15 @@ describe Crystal::Formatter do break end CODE + + # #12378 + assert_format <<-CODE + macro foo + macro bar + \\{% begin %} + \\\\{% puts %} + \\{% end %} + end + end + CODE end diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 1722dcad9c36..2894031251ef 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1817,7 +1817,16 @@ module Crystal @no_rstrip_lines.add line end - write @token.raw + raw = @token.raw + + # If the macro literal has a backlash, but we are subformatting + # a macro, we need to escape it (if it got here unescaped it was escaped to + # begin with.) + if @subformat_nesting > 0 + raw = raw.gsub("\\", "\\" * (@subformat_nesting + 1)) + end + + write raw next_macro_token false end From 809c49ecb2b043d0fde69f49c952cdc46195a282 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 11 Oct 2022 17:40:53 +0800 Subject: [PATCH 0055/1551] Treat single splats with same restriction as equivalent (#12584) --- spec/compiler/semantic/def_overload_spec.cr | 28 +++++++++++++++++++ src/compiler/crystal/semantic/restrictions.cr | 25 +++++++++++++++-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/spec/compiler/semantic/def_overload_spec.cr b/spec/compiler/semantic/def_overload_spec.cr index 21e034787b92..44149dfc1b94 100644 --- a/spec/compiler/semantic/def_overload_spec.cr +++ b/spec/compiler/semantic/def_overload_spec.cr @@ -1644,6 +1644,34 @@ describe "Semantic: def overload" do ), "no overload matches" end + + it "treats single splats with same restriction as equivalent (#12579)" do + assert_type(<<-CR) { int32 } + def foo(*x : Int32) + 'a' + end + + def foo(*x : Int32) + 1 + end + + foo(1) + CR + end + + it "treats single splats with same restriction as equivalent (2) (#12579)" do + assert_type(<<-CR) { int32 } + def foo(*x : Int32) + 'a' + end + + def foo(*y : Int32) + 1 + end + + foo(1) + CR + end end private def each_union_variant(t1, t2) diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 73ddeb477655..26d6fade3817 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -358,16 +358,35 @@ module Crystal stricter_pair_to_num(self_stricter, other_stricter) end + # this is part of `Crystal::Def#min_max_args_sizes` before #10711, provided + # that `-Dpreview_overload_order` is not in effect + # TODO: figure out if this can be derived from `self.min_size` + def old_min_args_size + if splat_index = self.def.splat_index + args = self.def.args + unless args[splat_index].name.empty? + default_value_index = args.index(&.default_value) + min_size = default_value_index || args.size + min_size -= 1 unless default_value_index.try(&.< splat_index) + return min_size + end + end + self.min_size + end + def old_restriction_of?(other : DefWithMetadata, owner) # This is how multiple defs are sorted by 'restrictions' (?) # If one yields and the other doesn't, none is stricter than the other return false unless yields == other.yields + self_min_size = old_min_args_size + other_min_size = other.old_min_args_size + # A def with more required arguments than the other comes first - if min_size > other.max_size + if self_min_size > other.max_size return true - elsif other.min_size > max_size + elsif other_min_size > max_size return false end @@ -395,7 +414,7 @@ module Crystal end if self_splat_index && other_splat_index - min = Math.min(min_size, other.min_size) + min = Math.min(self_min_size, other_min_size) else min = Math.min(max_size, other.max_size) end From e22a23ee4d589920350311bc64ea9434a78e41ac Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 11 Oct 2022 19:52:20 -0300 Subject: [PATCH 0056/1551] Interpreter (repl): migrate types even if their size remains the same (#12581) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/compiler/crystal/interpreter/interpreter.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index c9d3cac0a72f..ba507e95fdb0 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -419,7 +419,7 @@ class Crystal::Repl::Interpreter end private def migrate_local_vars(current_local_vars, next_meta_vars) - # Check if any existing local variable size changed. + # Check if any existing local variable type changed. # If so, it means we need to put them inside a union, # or make the union bigger. current_names = current_local_vars.names_at_block_level_zero @@ -435,7 +435,7 @@ class Crystal::Repl::Interpreter current_type = current_local_vars.type(current_name, 0) next_type = next_meta_vars[current_name].type - aligned_sizeof_type(current_type) != aligned_sizeof_type(next_type) + current_type != next_type end return unless needs_migration From dd1c6d5d177fc683b0a3364f47b385b20b6bde56 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Tue, 11 Oct 2022 19:52:43 -0300 Subject: [PATCH 0057/1551] Fix building Wasm32 on Crystal 1.6 (Regression) (#12580) --- src/crystal/system/wasi/file_descriptor.cr | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/crystal/system/wasi/file_descriptor.cr b/src/crystal/system/wasi/file_descriptor.cr index 7be117b03791..df25c9557ffe 100644 --- a/src/crystal/system/wasi/file_descriptor.cr +++ b/src/crystal/system/wasi/file_descriptor.cr @@ -11,10 +11,6 @@ module Crystal::System::FileDescriptor raise NotImplementedError.new "Crystal::System::FileDescriptor.pipe" end - def self.system_info(fd) - raise NotImplementedError.new "Crystal::System::FileDescriptor.system_info" - end - private def system_reopen(other : IO::FileDescriptor) raise NotImplementedError.new "Crystal::System::FileDescriptor#system_reopen" end From 5a22e692d8de89f3bdbf34b3435a7e59dc98c4a9 Mon Sep 17 00:00:00 2001 From: Dmitri Goutnik Date: Thu, 13 Oct 2022 09:08:57 -0500 Subject: [PATCH 0058/1551] Unbreak the interpreter on FreeBSD (#12600) --- src/lib_c/x86_64-freebsd/c/iconv.cr | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib_c/x86_64-freebsd/c/iconv.cr b/src/lib_c/x86_64-freebsd/c/iconv.cr index 7614fc00fe7d..da8707cc1cc2 100644 --- a/src/lib_c/x86_64-freebsd/c/iconv.cr +++ b/src/lib_c/x86_64-freebsd/c/iconv.cr @@ -3,10 +3,13 @@ require "./stddef" lib LibC type IconvT = Void* - fun iconv(x0 : IconvT, x1 : Char**, x2 : SizeT*, x3 : Char**, x4 : SizeT*) : SizeT - fun iconv_close(x0 : IconvT) : Int - fun iconv_open(x0 : Char*, x1 : Char*) : IconvT + # Although FreeBSD libc provides iconv_XXXX functions, they are just compatibility symbols, + # not directly visible via dlsym(3). Use FreeBSD-specific exported symbols so iconv also + # works in the interpreter. + fun iconv = __bsd_iconv(x0 : IconvT, x1 : Char**, x2 : SizeT*, x3 : Char**, x4 : SizeT*) : SizeT + fun iconv_close = __bsd_iconv_close(x0 : IconvT) : Int + fun iconv_open = __bsd_iconv_open(x0 : Char*, x1 : Char*) : IconvT ICONV_F_HIDE_INVALID = 0x0001 - fun __iconv(x0 : IconvT, x1 : Char**, x2 : SizeT*, x3 : Char**, x4 : SizeT*, flags : UInt32, invalids : SizeT*) : SizeT + fun __iconv = __bsd___iconv(x0 : IconvT, x1 : Char**, x2 : SizeT*, x3 : Char**, x4 : SizeT*, flags : UInt32, invalids : SizeT*) : SizeT end From 7bede60ccdc58c1f92b91c2a81ff46a801c96e1c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 14 Oct 2022 00:34:53 +0800 Subject: [PATCH 0059/1551] Detect `llvm-configXX` while building compiler (#12602) --- src/llvm/ext/find-llvm-config | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/llvm/ext/find-llvm-config b/src/llvm/ext/find-llvm-config index 3190e2f68722..82bd0120caf5 100755 --- a/src/llvm/ext/find-llvm-config +++ b/src/llvm/ext/find-llvm-config @@ -7,6 +7,8 @@ if ! LLVM_CONFIG=$(command -v "$LLVM_CONFIG"); then command -v llvm-config-${version%.*} || \ command -v llvm-config-$version || \ command -v llvm-config${version%.*}${version#*.} || \ + command -v llvm-config${version%.*} || \ + command -v llvm-config$version || \ command -v llvm${version%.*}-config || \ [ "${llvm_config_version#$version}" = "$llvm_config_version" ] || command -v llvm-config) [ "$LLVM_CONFIG" ] && break From 0fac5d5b0c0053e24e6b3c97a35a2886e855687d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 14 Oct 2022 19:53:55 +0200 Subject: [PATCH 0060/1551] Fix origin validation in playground server for localhost (#12599) --- src/compiler/crystal/tools/playground/server.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr index 228e1e5b1d70..e0f7482d4e92 100644 --- a/src/compiler/crystal/tools/playground/server.cr +++ b/src/compiler/crystal/tools/playground/server.cr @@ -537,7 +537,7 @@ module Crystal::Playground private def accept_request?(origin) case @host - when nil + when nil, "localhost", "127.0.0.1" origin == "http://127.0.0.1:#{@port}" || origin == "http://localhost:#{@port}" when "0.0.0.0" true From 41f861e8e6cc4c047ba7fc246f1a29d91d33dad4 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 18 Oct 2022 02:07:19 +0800 Subject: [PATCH 0061/1551] Fix FFI specs on release builds (#12601) --- spec/compiler/data/ffi/sum.c | 53 +++++++++++++++------------- spec/compiler/ffi/ffi_spec.cr | 65 ++++++++++++++++++----------------- 2 files changed, 63 insertions(+), 55 deletions(-) diff --git a/spec/compiler/data/ffi/sum.c b/spec/compiler/data/ffi/sum.c index 4d9f82717682..940830770d15 100644 --- a/spec/compiler/data/ffi/sum.c +++ b/spec/compiler/data/ffi/sum.c @@ -1,71 +1,76 @@ #include +#include #include "../visibility.h" -EXPORT int answer() +// all the integral return types must be at least as large as the register size +// to avoid integer promotion by FFI! + +EXPORT int64_t answer() { return 42; } -EXPORT int sum(int a, int b, int c) +EXPORT int64_t sum(int32_t a, int32_t b, int32_t c) { return a + b + c; } EXPORT void sum_primitive_types( - unsigned char a, signed char b, - unsigned short c, signed short d, - unsigned long e, signed long f, - unsigned long long g, signed long long h, + uint8_t a, int8_t b, + uint16_t c, int16_t d, + uint32_t e, int32_t f, + uint64_t g, int64_t h, float i, double j, - long *k) + int64_t *k) { - *k = a + b + c + d + e + f + g + h + (long)i + (long)j + *k; + *k = a + b + c + d + e + f + g + h + (int64_t)i + (int64_t)j + *k; } struct test_struct { - char b; - short s; - int i; - long long j; + int8_t b; + int16_t s; + int32_t i; + int64_t j; float f; double d; - int *p; + void *p; }; -EXPORT int sum_struct(struct test_struct s) +EXPORT int64_t sum_struct(struct test_struct s) { - *s.p = s.b + s.s + s.i + s.j + s.f + s.d + *(s.p); - return *s.p; + int64_t *p = (int64_t *)s.p; + *p = s.b + s.s + s.i + s.j + s.f + s.d + *p; + return *p; } -EXPORT int sum_array(int ary[4]) +EXPORT int64_t sum_array(int32_t ary[4]) { - int sum = 0; - for (int i = 0; i < 4; i++) + int64_t sum = 0; + for (int32_t i = 0; i < 4; i++) { sum += ary[i]; } return sum; } -EXPORT int sum_variadic(int count, ...) +EXPORT int64_t sum_variadic(int32_t count, ...) { va_list ap; - int j; - int sum = 0; + int32_t j; + int64_t sum = 0; va_start(ap, count); /* Requires the last fixed parameter (to get the address) */ for (j = 0; j < count; j++) { - sum += va_arg(ap, int); /* Increments ap to the next argument. */ + sum += va_arg(ap, int32_t); /* Increments ap to the next argument. */ } va_end(ap); return sum; } -EXPORT struct test_struct make_struct(char b, short s, int i, long long j, float f, double d, void *p) +EXPORT struct test_struct make_struct(int8_t b, int16_t s, int32_t i, int64_t j, float f, double d, void *p) { struct test_struct t; t.b = b; diff --git a/spec/compiler/ffi/ffi_spec.cr b/spec/compiler/ffi/ffi_spec.cr index 5bf1b3cc97f2..ac16cfc425f2 100644 --- a/spec/compiler/ffi/ffi_spec.cr +++ b/spec/compiler/ffi/ffi_spec.cr @@ -6,14 +6,17 @@ require "compiler/crystal/ffi" require "compiler/crystal/loader" require "../loader/spec_helper" +# all the integral return types must be at least as large as the register size +# to avoid integer promotion by FFI! + @[Extern] private record TestStruct, - b : LibC::Char, - s : LibC::Short, - i : LibC::Int, - j : LibC::LongLong, - f : LibC::Float, - d : LibC::Double, + b : Int8, + s : Int16, + i : Int32, + j : Int64, + f : Float32, + d : Float64, p : Pointer(Void) describe Crystal::FFI::CallInterface do @@ -28,20 +31,20 @@ describe Crystal::FFI::CallInterface do describe ".new" do it "simple call" do - call_interface = Crystal::FFI::CallInterface.new Crystal::FFI::Type.sint16, [] of Crystal::FFI::Type + call_interface = Crystal::FFI::CallInterface.new Crystal::FFI::Type.sint64, [] of Crystal::FFI::Type loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH]) loader.load_library "sum" function_pointer = loader.find_symbol("answer") - return_value = 0_i32 + return_value = 0_i64 call_interface.call(function_pointer, Pointer(Pointer(Void)).null, pointerof(return_value).as(Void*)) - return_value.should eq 42 + return_value.should eq 42_i64 ensure loader.try &.close_all end it "with args" do - call_interface = Crystal::FFI::CallInterface.new Crystal::FFI::Type.sint16, [ + call_interface = Crystal::FFI::CallInterface.new Crystal::FFI::Type.sint64, [ Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32, ] of Crystal::FFI::Type @@ -49,11 +52,11 @@ describe Crystal::FFI::CallInterface do loader.load_library "sum" function_pointer = loader.find_symbol("sum") - return_value = 0_i32 + return_value = 0_i64 args = Int32[1, 3, 5] arg_pointers = StaticArray(Pointer(Void), 3).new { |i| (args.to_unsafe + i).as(Void*) } call_interface.call(function_pointer, arg_pointers.to_unsafe, pointerof(return_value).as(Void*)) - return_value.should eq 9 + return_value.should eq 9_i64 ensure loader.try &.close_all end @@ -72,7 +75,7 @@ describe Crystal::FFI::CallInterface do loader.load_library "sum" function_pointer = loader.find_symbol("sum_primitive_types") - pointer_value = 11_i32 + pointer_value = 11_i64 arg_pointers = StaticArray[ Pointer(UInt8).malloc(1, 1).as(Void*), Pointer(Int8).malloc(1, 2).as(Void*), @@ -84,7 +87,7 @@ describe Crystal::FFI::CallInterface do Pointer(Int64).malloc(1, 8).as(Void*), Pointer(Float32).malloc(1, 9.0).as(Void*), Pointer(Float64).malloc(1, 10.0).as(Void*), - Pointer(Int32*).malloc(1, pointerof(pointer_value)).as(Void*), + Pointer(Int64*).malloc(1, pointerof(pointer_value)).as(Void*), Pointer(Void).null, ] @@ -130,7 +133,7 @@ describe Crystal::FFI::CallInterface do end it "sum struct" do - call_interface = Crystal::FFI::CallInterface.new Crystal::FFI::Type.sint32, [ + call_interface = Crystal::FFI::CallInterface.new Crystal::FFI::Type.sint64, [ Crystal::FFI::Type.struct([ Crystal::FFI::Type.sint8, Crystal::FFI::Type.sint16, @@ -146,7 +149,7 @@ describe Crystal::FFI::CallInterface do loader.load_library "sum" function_pointer = loader.find_symbol("sum_struct") - pointer_value = 11_i32 + pointer_value = 11_i64 arg_pointers = StaticArray[ Pointer(TestStruct).malloc(1, TestStruct.new( b: 2, @@ -160,10 +163,10 @@ describe Crystal::FFI::CallInterface do Pointer(Void).null, ] - return_value = 0i32 + return_value = 0_i64 call_interface.call(function_pointer, arg_pointers.to_unsafe, pointerof(return_value).as(Void*)) - return_value.should eq 50 - pointer_value.should eq 50 + return_value.should eq 50_i64 + pointer_value.should eq 50_i64 ensure loader.try &.close_all end @@ -171,7 +174,7 @@ describe Crystal::FFI::CallInterface do # passing C array by value is not supported everywhere {% unless flag?(:win32) %} it "array" do - call_interface = Crystal::FFI::CallInterface.new Crystal::FFI::Type.sint32, [ + call_interface = Crystal::FFI::CallInterface.new Crystal::FFI::Type.sint64, [ Crystal::FFI::Type.struct([ Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32, @@ -184,7 +187,7 @@ describe Crystal::FFI::CallInterface do loader.load_library "sum" function_pointer = loader.find_symbol("sum_array") - return_value = 0_i32 + return_value = 0_i64 ary = [1, 2, 3, 4] @@ -194,7 +197,7 @@ describe Crystal::FFI::CallInterface do ] call_interface.call(function_pointer, arg_pointers.to_unsafe, pointerof(return_value).as(Void*)) - return_value.should eq 10 + return_value.should eq 10_i64 ensure loader.try &.close_all end @@ -203,43 +206,43 @@ describe Crystal::FFI::CallInterface do describe ".variadic" do it "basic" do - call_interface = Crystal::FFI::CallInterface.variadic Crystal::FFI::Type.sint16, [Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32] of Crystal::FFI::Type, 1 + call_interface = Crystal::FFI::CallInterface.variadic Crystal::FFI::Type.sint64, [Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32] of Crystal::FFI::Type, 1 loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH]) loader.load_library "sum" function_pointer = loader.find_symbol("sum_variadic") - return_value = 0_i32 + return_value = 0_i64 args = Int32[3, 1, 3, 5] arg_pointers = StaticArray(Pointer(Void), 4).new { |i| (args.to_unsafe + i).as(Void*) } call_interface.call(function_pointer, arg_pointers.to_unsafe, pointerof(return_value).as(Void*)) - return_value.should eq 9 + return_value.should eq 9_i64 ensure loader.try &.close_all end it "zero varargs" do - call_interface = Crystal::FFI::CallInterface.variadic Crystal::FFI::Type.sint16, [Crystal::FFI::Type.sint32] of Crystal::FFI::Type, 1 + call_interface = Crystal::FFI::CallInterface.variadic Crystal::FFI::Type.sint64, [Crystal::FFI::Type.sint32] of Crystal::FFI::Type, 1 loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH]) loader.load_library "sum" function_pointer = loader.find_symbol("sum_variadic") - return_value = 1_i32 - count = 0 + return_value = 1_i64 + count = 0_i32 arg_pointer = pointerof(count).as(Void*) call_interface.call(function_pointer, pointerof(arg_pointer), pointerof(return_value).as(Void*)) - return_value.should eq 0 + return_value.should eq 0_i64 ensure loader.try &.close_all end it "validates args size" do expect_raises Exception, "invalid value for fixed_args" do - Crystal::FFI::CallInterface.variadic Crystal::FFI::Type.sint32, [] of Crystal::FFI::Type, 1 + Crystal::FFI::CallInterface.variadic Crystal::FFI::Type.sint64, [] of Crystal::FFI::Type, 1 end expect_raises Exception, "invalid value for fixed_args" do - Crystal::FFI::CallInterface.variadic Crystal::FFI::Type.sint32, [] of Crystal::FFI::Type, -1 + Crystal::FFI::CallInterface.variadic Crystal::FFI::Type.sint64, [] of Crystal::FFI::Type, -1 end end end From ef1cd3c1c33dad55ef1a05f79b43624c0747489f Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Tue, 18 Oct 2022 07:29:40 -0300 Subject: [PATCH 0062/1551] Adding welcome message to the interpreter (#12511) --- src/compiler/crystal/command/repl.cr | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/compiler/crystal/command/repl.cr b/src/compiler/crystal/command/repl.cr index 1c0c91889bd1..d277332125a8 100644 --- a/src/compiler/crystal/command/repl.cr +++ b/src/compiler/crystal/command/repl.cr @@ -1,4 +1,5 @@ {% skip_file if flag?(:without_interpreter) %} +require "../config" # Implementation of the `crystal repl` command @@ -34,6 +35,7 @@ class Crystal::Command end if options.empty? + show_banner repl.run else filename = options.shift @@ -41,7 +43,17 @@ class Crystal::Command error "File '#{filename}' doesn't exist" end + show_banner repl.run_file(filename, options) end end + + private def show_banner + return unless STDERR.tty? && !ENV.has_key?("CRYSTAL_INTERPRETER_SKIP_BANNER") + + formatted_sha = "[#{Config.build_commit}] " if Config.build_commit + STDERR.puts "Crystal interpreter #{Config.version} #{formatted_sha}(#{Config.date}).\n" \ + "EXPERIMENTAL SOFTWARE: if you find a bug, please consider opening an issue in\n" \ + "https://github.com/crystal-lang/crystal/issues/new/" + end end From fc61bd678afb97a82107e6577516cb27ff9fc1c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 21 Oct 2022 13:45:02 +0200 Subject: [PATCH 0063/1551] Add Changelog for 1.6.1 (#12626) --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bc5fb808b80..50582aed333c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +# 1.6.1 (2022-10-21) + +## Compiler + +### Interpreter + +- Interpreter (repl): migrate types even if their size remains the same ([#12581](https://github.com/crystal-lang/crystal/pull/12581), thanks @asterite) +- Unbreak the interpreter on FreeBSD ([#12600](https://github.com/crystal-lang/crystal/pull/12600), thanks @dmgk) +- Fix FFI specs on release builds ([#12601](https://github.com/crystal-lang/crystal/pull/12601), thanks @HertzDevil) +- Adding welcome message to the interpreter ([#12511](https://github.com/crystal-lang/crystal/pull/12511), thanks @beta-ziliani) + +### Semantic + +- Treat single splats with same restriction as equivalent ([#12584](https://github.com/crystal-lang/crystal/pull/12584), thanks @HertzDevil) + +## Tools + +### Formatter + +- Formatter: escape backslashes in macro literals when subformatting ([#12582](https://github.com/crystal-lang/crystal/pull/12582), thanks @asterite) + +### Playground + +- Fix origin validation in playground server for localhost ([#12599](https://github.com/crystal-lang/crystal/pull/12599), thanks @straight-shoota) + +## Other + +- Fix doc typos in `Socket::IPAddress` ([#12583](https://github.com/crystal-lang/crystal/pull/12583), thanks @Blacksmoke16) +- Fix building Wasm32 on Crystal 1.6 (Regression) ([#12580](https://github.com/crystal-lang/crystal/pull/12580), thanks @lbguilherme) +- Bump version to 1.6.1-dev ([#12588](https://github.com/crystal-lang/crystal/pull/12588), thanks @straight-shoota) +- Disable failing specs on Windows CI ([#12585](https://github.com/crystal-lang/crystal/pull/12585), thanks @HertzDevil) +- Detect `llvm-configXX` while building compiler ([#12602](https://github.com/crystal-lang/crystal/pull/12602), thanks @HertzDevil) + # 1.6.0 (2022-10-06) ## Language diff --git a/src/VERSION b/src/VERSION index 06bb45291686..9c6d6293b1a8 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.6.1-dev +1.6.1 From afc69ab7797120ac04b6d269a185a8b6e4a0bb17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 22 Oct 2022 15:52:36 +0200 Subject: [PATCH 0064/1551] [CI] Drop Alpine libreSSL 3.1 test (#12641) --- .github/workflows/openssl.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 23a33a9f09f6..a3439ecc3d56 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -33,21 +33,6 @@ jobs: run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs run: bin/crystal spec spec/std/openssl/ - libressl31: - runs-on: ubuntu-latest - name: "LibreSSL 3.1" - container: crystallang/crystal:1.5.0-alpine - steps: - - name: Download Crystal source - uses: actions/checkout@v2 - - name: Uninstall openssl - run: apk del openssl-dev openssl-libs-static - - name: Install libressl 3.1 - run: apk add "libressl-dev=~3.1" - - name: Check LibSSL version - run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - - name: Run OpenSSL specs - run: bin/crystal spec spec/std/openssl/ libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" From fabedd43ac85c7add28cfac86fdfc2797d014f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 22 Oct 2022 17:40:33 +0200 Subject: [PATCH 0065/1551] Bump version to 1.7.0-dev (#12640) --- .circleci/config.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/win.yml | 2 +- bin/ci | 6 +++--- shell.nix | 8 ++++---- src/VERSION | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 209f4706d6ec..547fc8592e8f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.5.0/crystal-1.5.0-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1" defaults: environment: &env diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 289f5fe0c67c..a2308db16125 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.0] + crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.1, 1.6.1] include: # libffi is only available starting from the 1.2.2 build images - crystal_bootstrap_version: 1.0.0 diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index a3439ecc3d56..ac59bd448f9a 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -6,7 +6,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.5.0-alpine + container: crystallang/crystal:1.6.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v2 @@ -23,7 +23,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.5.0-alpine + container: crystallang/crystal:1.6.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v2 @@ -36,7 +36,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.5.0-alpine + container: crystallang/crystal:1.6.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v2 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 5dbc07fa15bc..1b425e545135 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: x86_64-linux-job: runs-on: ubuntu-latest - container: crystallang/crystal:1.5.0-build + container: crystallang/crystal:1.6.1-build steps: - name: Download Crystal source uses: actions/checkout@v2 diff --git a/bin/ci b/bin/ci index a2ee08a3ccea..f59ef755cd10 100755 --- a/bin/ci +++ b/bin/ci @@ -134,8 +134,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.5.0/crystal-1.5.0-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.5.0-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.6.1-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -188,7 +188,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.5.0}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.6.1}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index e550a46489c4..32d5621cdb88 100644 --- a/shell.nix +++ b/shell.nix @@ -52,13 +52,13 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.5.0/crystal-1.5.0-1-darwin-universal.tar.gz"; - sha256 = "sha256:15h1qg4wa3zbp8p9vkvcqmy7ba1sc995r5z6d0yhx0h7nkgaw607"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:0pnakhi4hc50fw6dz0n110zpibgwjb91mf6n63fhys8hby7fg73p"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.5.0/crystal-1.5.0-1-linux-x86_64.tar.gz"; - sha256 = "sha256:1xp1bbljwbff5zf585bz1cbkcb1whswl4fljakfxr7cbaw31jg9y"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1-linux-x86_64.tar.gz"; + sha256 = "sha256:00ckw3nlr800i25mwv4qwz1lv42qq1kp3xsaxbd3l40ck5gml7c3"; }; }.${pkgs.stdenv.system}); diff --git a/src/VERSION b/src/VERSION index 9c6d6293b1a8..de023c91b16b 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.6.1 +1.7.0-dev From 5b8ff6dd892c9e96a8f12f4eaabff1d40e34d8a0 Mon Sep 17 00:00:00 2001 From: Gabriel Holodak Date: Sat, 22 Oct 2022 11:45:32 -0400 Subject: [PATCH 0066/1551] Add missing docs for `Indexable` combinations methods (#10548) --- src/indexable.cr | 116 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 11 deletions(-) diff --git a/src/indexable.cr b/src/indexable.cr index c05b6baf8c88..8ff5ddec14f4 100644 --- a/src/indexable.cr +++ b/src/indexable.cr @@ -973,7 +973,7 @@ module Indexable(T) {start_index, count} end - # Returns an `Array` with all possible permutations of *size*. + # Returns an `Array` with all possible permutations of *size* of `self`. # # ``` # a = [1, 2, 3] @@ -1066,6 +1066,17 @@ module Indexable(T) PermutationIterator(self, T).new(self, size.to_i, Indexable(T).check_reuse(reuse, size)) end + # Returns an `Array` with all possible combinations of *size* of `self`. + # + # ``` + # a = [1, 2, 3] + # a.combinations # => [[1, 2, 3]] + # a.combinations(1) # => [[1], [2], [3]] + # a.combinations(2) # => [[1, 2], [1, 3], [2, 3]] + # a.combinations(3) # => [[1, 2, 3]] + # a.combinations(0) # => [[]] + # a.combinations(4) # => [] + # ``` def combinations(size : Int = self.size) ary = [] of Array(T) each_combination(size) do |a| @@ -1074,6 +1085,21 @@ module Indexable(T) ary end + # Yields each possible combination of *size* of `self`. + # + # ``` + # a = [1, 2, 3] + # sums = [] of Int32 + # a.each_combination(2) { |p| sums << p.sum } # => nil + # sums # => [3, 4, 5] + # ``` + # + # By default, a new array is created and yielded for each combination. + # If *reuse* is given, the array can be reused: if *reuse* is + # an `Array`, this array will be reused; if *reuse* if truthy, + # the method will create a new array and reuse it. This can be + # used to prevent many memory allocations when each slice of + # interest is to be used in a read-only fashion. def each_combination(size : Int = self.size, reuse = false) : Nil n = self.size return if size > n @@ -1112,22 +1138,54 @@ module Indexable(T) end end - private def each_combination_piece(pool, size, reuse) - if reuse - reuse.clear - size.times { |i| reuse << pool[i] } - reuse - else - pool[0, size] - end - end - + # Returns an `Iterator` over each possible combination of *size* of `self`. + # + # ``` + # iter = [1, 2, 3, 4].each_combination(3) + # iter.next # => [1, 2, 3] + # iter.next # => [1, 2, 4] + # iter.next # => [1, 3, 4] + # iter.next # => [2, 3, 4] + # iter.next # => # + # ``` + # + # By default, a new array is created and returned for each combination. + # If *reuse* is given, the array can be reused: if *reuse* is + # an `Array`, this array will be reused; if *reuse* if truthy, + # the method will create a new array and reuse it. This can be + # used to prevent many memory allocations when each slice of + # interest is to be used in a read-only fashion. def each_combination(size : Int = self.size, reuse = false) raise ArgumentError.new("Size must be positive") if size < 0 CombinationIterator(self, T).new(self, size.to_i, Indexable(T).check_reuse(reuse, size)) end + # Returns an `Array` with all possible combinations with repeated elements of + # *size* of `self`. + # + # ``` + # a = [1, 2, 3] + # + # pp a.repeated_combinations + # pp a.repeated_combinations(2) + # ``` + # + # produces: + # + # ```text + # [[1, 1, 1], + # [1, 1, 2], + # [1, 1, 3], + # [1, 2, 2], + # [1, 2, 3], + # [1, 3, 3], + # [2, 2, 2], + # [2, 2, 3], + # [2, 3, 3], + # [3, 3, 3]] + # [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]] + # ``` def repeated_combinations(size : Int = self.size) : Array(Array(T)) ary = [] of Array(T) each_repeated_combination(size) do |a| @@ -1136,6 +1194,22 @@ module Indexable(T) ary end + # Yields each possible combination with repeated elements of *size* of + # `self`. + # + # ``` + # a = [1, 2, 3] + # sums = [] of Int32 + # a.each_repeated_combination(2) { |p| sums << p.sum } # => nil + # sums # => [2, 3, 4, 4, 5, 6] + # ``` + # + # By default, a new array is created and yielded for each combination. + # If *reuse* is given, the array can be reused: if *reuse* is + # an `Array`, this array will be reused; if *reuse* if truthy, + # the method will create a new array and reuse it. This can be + # used to prevent many memory allocations when each slice of + # interest is to be used in a read-only fashion. def each_repeated_combination(size : Int = self.size, reuse = false) : Nil n = self.size return if size > n && n == 0 @@ -1170,6 +1244,26 @@ module Indexable(T) end end + # Returns an `Iterator` over each possible combination with repeated elements + # of *size* of `self`. + # + # ``` + # iter = [1, 2, 3].each_repeated_combination(2) + # iter.next # => [1, 1] + # iter.next # => [1, 2] + # iter.next # => [1, 3] + # iter.next # => [2, 2] + # iter.next # => [2, 3] + # iter.next # => [3, 3] + # iter.next # => # + # ``` + # + # By default, a new array is created and returned for each combination. + # If *reuse* is given, the array can be reused: if *reuse* is + # an `Array`, this array will be reused; if *reuse* if truthy, + # the method will create a new array and reuse it. This can be + # used to prevent many memory allocations when each slice of + # interest is to be used in a read-only fashion. def each_repeated_combination(size : Int = self.size, reuse = false) raise ArgumentError.new("Size must be positive") if size < 0 From e6de9e343320b1d2d476cc19ea1082a98333b68f Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sun, 23 Oct 2022 08:42:14 -0300 Subject: [PATCH 0067/1551] [WASM] Add missing `__powisf2` and `__powidf2` compiler-rt functions (#12569) --- spec/std/crystal/compiler_rt/powidf2_spec.cr | 94 ++++++++++++++++++++ spec/std/crystal/compiler_rt/powisf2_spec.cr | 94 ++++++++++++++++++++ src/crystal/compiler_rt.cr | 5 +- src/crystal/compiler_rt/pow.cr | 20 +++++ 4 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 spec/std/crystal/compiler_rt/powidf2_spec.cr create mode 100644 spec/std/crystal/compiler_rt/powisf2_spec.cr create mode 100644 src/crystal/compiler_rt/pow.cr diff --git a/spec/std/crystal/compiler_rt/powidf2_spec.cr b/spec/std/crystal/compiler_rt/powidf2_spec.cr new file mode 100644 index 000000000000..832c7a4bfa69 --- /dev/null +++ b/spec/std/crystal/compiler_rt/powidf2_spec.cr @@ -0,0 +1,94 @@ +require "spec" +require "crystal/compiler_rt/pow" + +# Ported from https://github.com/llvm/llvm-project/blob/2e9df860468425645dcd1b241c5dbf76c072e314/compiler-rt/test/builtins/Unit/powidf2_test.c + +it ".__powidf2" do + __powidf2(0, 0).should eq 1 + __powidf2(1, 0).should eq 1 + __powidf2(1.5, 0).should eq 1 + __powidf2(2, 0).should eq 1 + __powidf2(Float64::INFINITY, 0).should eq 1 + __powidf2(-0.0, 0).should eq 1 + __powidf2(-1, 0).should eq 1 + __powidf2(-1.5, 0).should eq 1 + __powidf2(-2, 0).should eq 1 + __powidf2(-Float64::INFINITY, 0).should eq 1 + __powidf2(0, 1).should eq 0 + __powidf2(0, 2).should eq 0 + __powidf2(0, 3).should eq 0 + __powidf2(0, 4).should eq 0 + __powidf2(0, Int32::MAX - 1).should eq 0 + __powidf2(0, Int32::MAX).should eq 0 + __powidf2(-0.0, 1).should eq -0.0 + __powidf2(-0.0, 2).should eq 0 + __powidf2(-0.0, 3).should eq -0.0 + __powidf2(-0.0, 4).should eq 0 + __powidf2(-0.0, Int32::MAX - 1).should eq 0 + __powidf2(-0.0, Int32::MAX).should eq -0.0 + __powidf2(1, 1).should eq 1 + __powidf2(1, 2).should eq 1 + __powidf2(1, 3).should eq 1 + __powidf2(1, 4).should eq 1 + __powidf2(1, Int32::MAX - 1).should eq 1 + __powidf2(1, Int32::MAX).should eq 1 + __powidf2(Float64::INFINITY, 1).should eq Float64::INFINITY + __powidf2(Float64::INFINITY, 2).should eq Float64::INFINITY + __powidf2(Float64::INFINITY, 3).should eq Float64::INFINITY + __powidf2(Float64::INFINITY, 4).should eq Float64::INFINITY + __powidf2(Float64::INFINITY, Int32::MAX - 1).should eq Float64::INFINITY + __powidf2(Float64::INFINITY, Int32::MAX).should eq Float64::INFINITY + __powidf2(-Float64::INFINITY, 1).should eq -Float64::INFINITY + __powidf2(-Float64::INFINITY, 2).should eq Float64::INFINITY + __powidf2(-Float64::INFINITY, 3).should eq -Float64::INFINITY + __powidf2(-Float64::INFINITY, 4).should eq Float64::INFINITY + __powidf2(-Float64::INFINITY, Int32::MAX - 1).should eq Float64::INFINITY + __powidf2(-Float64::INFINITY, Int32::MAX).should eq -Float64::INFINITY + __powidf2(0, -1).should eq Float64::INFINITY + __powidf2(0, -2).should eq Float64::INFINITY + __powidf2(0, -3).should eq Float64::INFINITY + __powidf2(0, -4).should eq Float64::INFINITY + __powidf2(0, Int32::MIN + 2).should eq Float64::INFINITY + __powidf2(0, Int32::MIN + 1).should eq Float64::INFINITY + __powidf2(0, Int32::MIN).should eq Float64::INFINITY + __powidf2(-0.0, -1).should eq -Float64::INFINITY + __powidf2(-0.0, -2).should eq Float64::INFINITY + __powidf2(-0.0, -3).should eq -Float64::INFINITY + __powidf2(-0.0, -4).should eq Float64::INFINITY + __powidf2(-0.0, Int32::MIN + 2).should eq Float64::INFINITY + __powidf2(-0.0, Int32::MIN + 1).should eq -Float64::INFINITY + __powidf2(-0.0, Int32::MIN).should eq Float64::INFINITY + __powidf2(1, -1).should eq 1 + __powidf2(1, -2).should eq 1 + __powidf2(1, -3).should eq 1 + __powidf2(1, -4).should eq 1 + __powidf2(1, Int32::MIN + 2).should eq 1 + __powidf2(1, Int32::MIN + 1).should eq 1 + __powidf2(1, Int32::MIN).should eq 1 + __powidf2(Float64::INFINITY, -1).should eq 0 + __powidf2(Float64::INFINITY, -2).should eq 0 + __powidf2(Float64::INFINITY, -3).should eq 0 + __powidf2(Float64::INFINITY, -4).should eq 0 + __powidf2(Float64::INFINITY, Int32::MIN + 2).should eq 0 + __powidf2(Float64::INFINITY, Int32::MIN + 1).should eq 0 + __powidf2(Float64::INFINITY, Int32::MIN).should eq 0 + __powidf2(-Float64::INFINITY, -1).should eq -0.0 + __powidf2(-Float64::INFINITY, -2).should eq 0 + __powidf2(-Float64::INFINITY, -3).should eq -0.0 + __powidf2(-Float64::INFINITY, -4).should eq 0 + __powidf2(-Float64::INFINITY, Int32::MIN + 2).should eq 0 + __powidf2(-Float64::INFINITY, Int32::MIN + 1).should eq -0.0 + __powidf2(-Float64::INFINITY, Int32::MIN).should eq 0 + __powidf2(2, 10).should eq 1024.0 + __powidf2(-2, 10).should eq 1024.0 + __powidf2(2, -10).should eq 1/1024.0 + __powidf2(-2, -10).should eq 1/1024.0 + __powidf2(2, 19).should eq 524288.0 + __powidf2(-2, 19).should eq -524288.0 + __powidf2(2, -19).should eq 1/524288.0 + __powidf2(-2, -19).should eq -1/524288.0 + __powidf2(2, 31).should eq 2147483648.0 + __powidf2(-2, 31).should eq -2147483648.0 + __powidf2(2, -31).should eq 1/2147483648.0 + __powidf2(-2, -31).should eq -1/2147483648.0 +end diff --git a/spec/std/crystal/compiler_rt/powisf2_spec.cr b/spec/std/crystal/compiler_rt/powisf2_spec.cr new file mode 100644 index 000000000000..aaad225be2d2 --- /dev/null +++ b/spec/std/crystal/compiler_rt/powisf2_spec.cr @@ -0,0 +1,94 @@ +require "spec" +require "crystal/compiler_rt/pow" + +# Ported from https://github.com/llvm/llvm-project/blob/2e9df860468425645dcd1b241c5dbf76c072e314/compiler-rt/test/builtins/Unit/powisf2_test.c + +it ".__powisf2" do + __powisf2(0, 0).should eq 1 + __powisf2(1, 0).should eq 1 + __powisf2(1.5, 0).should eq 1 + __powisf2(2, 0).should eq 1 + __powisf2(Float32::INFINITY, 0).should eq 1 + __powisf2(-0.0, 0).should eq 1 + __powisf2(-1, 0).should eq 1 + __powisf2(-1.5, 0).should eq 1 + __powisf2(-2, 0).should eq 1 + __powisf2(-Float32::INFINITY, 0).should eq 1 + __powisf2(0, 1).should eq 0 + __powisf2(0, 2).should eq 0 + __powisf2(0, 3).should eq 0 + __powisf2(0, 4).should eq 0 + __powisf2(0, Int32::MAX - 1).should eq 0 + __powisf2(0, Int32::MAX).should eq 0 + __powisf2(-0.0, 1).should eq -0.0 + __powisf2(-0.0, 2).should eq 0 + __powisf2(-0.0, 3).should eq -0.0 + __powisf2(-0.0, 4).should eq 0 + __powisf2(-0.0, Int32::MAX - 1).should eq 0 + __powisf2(-0.0, Int32::MAX).should eq -0.0 + __powisf2(1, 1).should eq 1 + __powisf2(1, 2).should eq 1 + __powisf2(1, 3).should eq 1 + __powisf2(1, 4).should eq 1 + __powisf2(1, Int32::MAX - 1).should eq 1 + __powisf2(1, Int32::MAX).should eq 1 + __powisf2(Float32::INFINITY, 1).should eq Float32::INFINITY + __powisf2(Float32::INFINITY, 2).should eq Float32::INFINITY + __powisf2(Float32::INFINITY, 3).should eq Float32::INFINITY + __powisf2(Float32::INFINITY, 4).should eq Float32::INFINITY + __powisf2(Float32::INFINITY, Int32::MAX - 1).should eq Float32::INFINITY + __powisf2(Float32::INFINITY, Int32::MAX).should eq Float32::INFINITY + __powisf2(-Float32::INFINITY, 1).should eq -Float32::INFINITY + __powisf2(-Float32::INFINITY, 2).should eq Float32::INFINITY + __powisf2(-Float32::INFINITY, 3).should eq -Float32::INFINITY + __powisf2(-Float32::INFINITY, 4).should eq Float32::INFINITY + __powisf2(-Float32::INFINITY, Int32::MAX - 1).should eq Float32::INFINITY + __powisf2(-Float32::INFINITY, Int32::MAX).should eq -Float32::INFINITY + __powisf2(0, -1).should eq Float32::INFINITY + __powisf2(0, -2).should eq Float32::INFINITY + __powisf2(0, -3).should eq Float32::INFINITY + __powisf2(0, -4).should eq Float32::INFINITY + __powisf2(0, Int32::MIN + 2).should eq Float32::INFINITY + __powisf2(0, Int32::MIN + 1).should eq Float32::INFINITY + __powisf2(0, Int32::MIN).should eq Float32::INFINITY + __powisf2(-0.0, -1).should eq -Float32::INFINITY + __powisf2(-0.0, -2).should eq Float32::INFINITY + __powisf2(-0.0, -3).should eq -Float32::INFINITY + __powisf2(-0.0, -4).should eq Float32::INFINITY + __powisf2(-0.0, Int32::MIN + 2).should eq Float32::INFINITY + __powisf2(-0.0, Int32::MIN + 1).should eq -Float32::INFINITY + __powisf2(-0.0, Int32::MIN).should eq Float32::INFINITY + __powisf2(1, -1).should eq 1 + __powisf2(1, -2).should eq 1 + __powisf2(1, -3).should eq 1 + __powisf2(1, -4).should eq 1 + __powisf2(1, Int32::MIN + 2).should eq 1 + __powisf2(1, Int32::MIN + 1).should eq 1 + __powisf2(1, Int32::MIN).should eq 1 + __powisf2(Float32::INFINITY, -1).should eq 0 + __powisf2(Float32::INFINITY, -2).should eq 0 + __powisf2(Float32::INFINITY, -3).should eq 0 + __powisf2(Float32::INFINITY, -4).should eq 0 + __powisf2(Float32::INFINITY, Int32::MIN + 2).should eq 0 + __powisf2(Float32::INFINITY, Int32::MIN + 1).should eq 0 + __powisf2(Float32::INFINITY, Int32::MIN).should eq 0 + __powisf2(-Float32::INFINITY, -1).should eq -0.0 + __powisf2(-Float32::INFINITY, -2).should eq 0 + __powisf2(-Float32::INFINITY, -3).should eq -0.0 + __powisf2(-Float32::INFINITY, -4).should eq 0 + __powisf2(-Float32::INFINITY, Int32::MIN + 2).should eq 0 + __powisf2(-Float32::INFINITY, Int32::MIN + 1).should eq -0.0 + __powisf2(-Float32::INFINITY, Int32::MIN).should eq 0 + __powisf2(2, 10).should eq 1024.0 + __powisf2(-2, 10).should eq 1024.0 + __powisf2(2, -10).should eq 1/1024.0 + __powisf2(-2, -10).should eq 1/1024.0 + __powisf2(2, 19).should eq 524288.0 + __powisf2(-2, 19).should eq -524288.0 + __powisf2(2, -19).should eq 1/524288.0 + __powisf2(-2, -19).should eq -1/524288.0 + __powisf2(2, 31).should eq 2147483648.0 + __powisf2(-2, 31).should eq -2147483648.0 + __powisf2(2, -31).should eq 1/2147483648.0 + __powisf2(-2, -31).should eq -1/2147483648.0 +end diff --git a/src/crystal/compiler_rt.cr b/src/crystal/compiler_rt.cr index b96a61e9fe1f..e1681dac4186 100644 --- a/src/crystal/compiler_rt.cr +++ b/src/crystal/compiler_rt.cr @@ -11,8 +11,11 @@ require "./compiler_rt/divmod128.cr" {% end %} {% if flag?(:wasm32) %} - # __ashlti3, __ashrti3 and __lshrti3 were only missing on wasm32 + # __ashlti3, __ashrti3 and __lshrti3 are missing on wasm32 require "./compiler_rt/shift.cr" + + # __powisf2 and __powidf2 are missing on wasm32 + require "./compiler_rt/pow.cr" {% end %} {% if flag?(:win32) && flag?(:bits64) %} diff --git a/src/crystal/compiler_rt/pow.cr b/src/crystal/compiler_rt/pow.cr new file mode 100644 index 000000000000..dcbc29cf3211 --- /dev/null +++ b/src/crystal/compiler_rt/pow.cr @@ -0,0 +1,20 @@ +private macro __pow_impl(name, one, float_type) + # :nodoc: + # Ported from https://github.com/llvm/llvm-project/blob/2e9df860468425645dcd1b241c5dbf76c072e314/compiler-rt/lib/builtins + fun {{name}}(a : {{float_type}}, b : Int32) : {{float_type}} + recip = b < 0 + r = {{one}} + + loop do + r *= a if b & 1 != 0 + b = b.unsafe_div 2 + break if b == 0 + a *= a + end + + recip ? 1 / r : r + end +end + +__pow_impl(__powisf2, 1f32, Float32) +__pow_impl(__powidf2, 1f64, Float64) From bd83c558bca17bbdd994b89d5fbf07c6c43fd605 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Sun, 23 Oct 2022 19:44:04 +0800 Subject: [PATCH 0068/1551] Parser: Fix restrict grammar for name and supertype in type def (#12622) --- spec/compiler/parser/parser_spec.cr | 11 +++++++++++ src/compiler/crystal/syntax/parser.cr | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index b5e0a0301fc6..581b1e9dbe15 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -253,6 +253,17 @@ module Crystal assert_syntax_error "foo { |(#{kw})| }", "cannot use '#{kw}' as a block parameter name", 1, 9 end + describe "literals in class definitions" do + # #11209 + %w("a" 'a' [1] {1} {|a|a} ->{} ->(x : Bar){} :Bar :bar %x() %w() %()).each do |invalid| + assert_syntax_error "class Foo#{invalid}; end" + assert_syntax_error "class Foo#{invalid} < Baz; end" + assert_syntax_error "class Foo#{invalid} < self; end" + assert_syntax_error "class Foo < Baz#{invalid}; end" + assert_syntax_error "class Foo < self#{invalid}; end" + end + end + it_parses "def self.foo\n1\nend", Def.new("foo", body: 1.int32, receiver: "self".var) it_parses "def self.foo()\n1\nend", Def.new("foo", body: 1.int32, receiver: "self".var) it_parses "def self.foo=\n1\nend", Def.new("foo=", body: 1.int32, receiver: "self".var) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index ce3a1cc9b38d..4c41220d4234 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -1637,6 +1637,10 @@ module Crystal name = parse_path skip_space + unexpected_token unless @token.type.op_lt? || # Inheritance + @token.type.op_lparen? || # Generic Arguments + is_statement_end? + type_vars, splat_index = parse_type_vars superclass = nil @@ -1649,6 +1653,8 @@ module Crystal else superclass = parse_generic end + + unexpected_token unless @token.type.space? || is_statement_end? end skip_statement_end @@ -1668,6 +1674,10 @@ module Crystal class_def end + def is_statement_end? + @token.type.newline? || @token.type.op_semicolon? || @token.keyword?(:end) + end + def parse_type_vars type_vars = nil splat_index = nil From bcf1fa87133cc38cf22fe7abd57000991c78cf36 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 23 Oct 2022 19:44:44 +0800 Subject: [PATCH 0069/1551] Fix `TypeNode#nilable?` for root types (#12354) --- spec/compiler/macro/macro_methods_spec.cr | 44 +++++++++++++++++++++++ src/compiler/crystal/macros.cr | 4 ++- src/compiler/crystal/types.cr | 2 +- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 7d949ae0932b..8a6f9eb1438e 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2070,17 +2070,61 @@ module Crystal end end end + describe "#nilable?" do it false do assert_macro("{{x.nilable?}}", "false") do |program| {x: TypeNode.new(program.string)} end + + assert_macro("{{x.nilable?}}", "false") do |program| + {x: TypeNode.new(program.union_of(program.string, program.int32))} + end + + assert_macro("{{x.nilable?}}", "false") do |program| + {x: TypeNode.new(program.no_return)} + end + + assert_macro("{{x.nilable?}}", "false") do |program| + {x: TypeNode.new(program.class_type)} + end + + assert_macro("{{x.nilable?}}", "false") do |program| + {x: TypeNode.new(program.reference)} + end end it true do + assert_macro("{{x.nilable?}}", "true") do |program| + {x: TypeNode.new(program.nil_type)} + end + assert_macro("{{x.nilable?}}", "true") do |program| {x: TypeNode.new(program.union_of(program.string, program.nil))} end + + assert_macro("{{x.nilable?}}", "true") do |program| + {x: TypeNode.new(program.value)} + end + + assert_macro("{{x.nilable?}}", "true") do |program| + {x: TypeNode.new(program.object)} + end + + assert_macro("{{x.nilable?}}", "true") do |program| + mod = NonGenericModuleType.new(program, program, "SomeModule") + program.nil_type.include mod + {x: TypeNode.new(mod)} + end + + assert_type(<<-CR) { int32 } + class Foo(T) + end + + alias Bar = Foo(Bar)? + + {{ Bar.nilable? ? 1 : 'a' }} + CR end end diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index d4ce2d4b995c..91ade699fa8b 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1829,12 +1829,14 @@ module Crystal::Macros def union? : BoolLiteral end - # Returns `true` if `self` is nilable (if it has `Nil` amongst its types), otherwise `false`. + # Returns `true` if `nil` is an instance of `self`, otherwise `false`. # # ``` # {{String.nilable?}} # => false # {{String?.nilable?}} # => true # {{Union(String, Bool, Nil).nilable?}} # => true + # {{NoReturn.nilable?}} # => false + # {{Value.nilable?}} # => true # ``` def nilable? : BoolLiteral end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 2e0b522c5b98..8ff8df3c6f13 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -194,7 +194,7 @@ module Crystal end def nilable? - self.is_a?(NilType) || (self.is_a?(UnionType) && self.union_types.any?(&.nil_type?)) + nil_type? || program.nil_type.implements?(self) end def bool_type? From 5763f56f96c97b2a9cc4ab385d0346935252b45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 23 Oct 2022 13:45:04 +0200 Subject: [PATCH 0070/1551] Refactor: use helper method instead of duplicate code in lexer (#12590) --- src/compiler/crystal/syntax/lexer.cr | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index ea537baaf99e..ae4aa73590e8 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -1362,9 +1362,7 @@ module Crystal start_pos = current_pos end - while char != '\n' && char != '\0' - char = next_char_no_column_increment - end + skip_comment if doc_buffer = @token.doc_buffer doc_buffer << '\n' From 6d0bc1c9e7116b8d5db2e37d40c60df79c21076e Mon Sep 17 00:00:00 2001 From: David Keller Date: Sun, 23 Oct 2022 18:27:08 +0200 Subject: [PATCH 0071/1551] Implement `Digest` class in `Digest::CRC32` and `Digest::Adler32` (#11535) --- spec/std/digest/adler32_spec.cr | 60 ++++++++++++++++++++++++++++++++- spec/std/digest/crc32_spec.cr | 60 ++++++++++++++++++++++++++++++++- src/digest/adler32.cr | 33 +++++++++++++++++- src/digest/crc32.cr | 33 +++++++++++++++++- 4 files changed, 182 insertions(+), 4 deletions(-) diff --git a/spec/std/digest/adler32_spec.cr b/spec/std/digest/adler32_spec.cr index 46277ce5b7cd..65511569283b 100644 --- a/spec/std/digest/adler32_spec.cr +++ b/spec/std/digest/adler32_spec.cr @@ -1,7 +1,65 @@ -require "spec" +require "../spec_helper" +require "./spec_helper" require "digest/adler32" describe Digest::Adler32 do + it_acts_as_digest_algorithm Digest::Adler32 + + it "calculates digest from string" do + Digest::Adler32.digest("foo").to_slice.should eq Bytes[0x02, 0x82, 0x01, 0x45] + end + + it "calculates hash from string" do + Digest::Adler32.hexdigest("foo").should eq("02820145") + end + + it "calculates hash from unicode string" do + Digest::Adler32.hexdigest("fooø").should eq("074a02c0") + end + + it "calculates hash from UInt8 slices" do + s = Bytes[0x66, 0x6f, 0x6f] # f,o,o + Digest::Adler32.hexdigest(s).should eq("02820145") + end + + it "calculates hash of #to_slice" do + buffer = StaticArray(UInt8, 1).new(1_u8) + Digest::Adler32.hexdigest(buffer).should eq("00020002") + end + + it "can take a block" do + Digest::Adler32.hexdigest do |ctx| + ctx.update "f" + ctx.update Bytes[0x6f, 0x6f] + end.should eq("02820145") + end + + it "calculates base64'd hash from string" do + Digest::Adler32.base64digest("foo").should eq("AoIBRQ==") + end + + it "resets" do + digest = Digest::Adler32.new + digest.update "foo" + digest.final.hexstring.should eq("02820145") + + digest.reset + digest.update "foo" + digest.final.hexstring.should eq("02820145") + end + + it "can't call final twice" do + digest = Digest::Adler32.new + digest.final + expect_raises(Digest::FinalizedError) do + digest.final + end + end + + it "return the digest size" do + Digest::Adler32.new.digest_size.should eq 4 + end + it "should be able to calculate adler32" do adler = Digest::Adler32.checksum("foo").to_s(16) adler.should eq("2820145") diff --git a/spec/std/digest/crc32_spec.cr b/spec/std/digest/crc32_spec.cr index 7e23a266cd74..fbd2997f9e26 100644 --- a/spec/std/digest/crc32_spec.cr +++ b/spec/std/digest/crc32_spec.cr @@ -1,7 +1,65 @@ -require "spec" +require "../spec_helper" +require "./spec_helper" require "digest/crc32" describe Digest::CRC32 do + it_acts_as_digest_algorithm Digest::CRC32 + + it "calculates digest from string" do + Digest::CRC32.digest("foo").to_slice.should eq Bytes[0x8c, 0x73, 0x65, 0x21] + end + + it "calculates hash from string" do + Digest::CRC32.hexdigest("foo").should eq("8c736521") + end + + it "calculates hash from unicode string" do + Digest::CRC32.hexdigest("fooø").should eq("d5f3a18b") + end + + it "calculates hash from UInt8 slices" do + s = Bytes[0x66, 0x6f, 0x6f] # f,o,o + Digest::CRC32.hexdigest(s).should eq("8c736521") + end + + it "calculates hash of #to_slice" do + buffer = StaticArray(UInt8, 1).new(1_u8) + Digest::CRC32.hexdigest(buffer).should eq("a505df1b") + end + + it "can take a block" do + Digest::CRC32.hexdigest do |ctx| + ctx.update "f" + ctx.update Bytes[0x6f, 0x6f] + end.should eq("8c736521") + end + + it "calculates base64'd hash from string" do + Digest::CRC32.base64digest("foo").should eq("jHNlIQ==") + end + + it "resets" do + digest = Digest::CRC32.new + digest.update "foo" + digest.final.hexstring.should eq("8c736521") + + digest.reset + digest.update "foo" + digest.final.hexstring.should eq("8c736521") + end + + it "can't call final twice" do + digest = Digest::CRC32.new + digest.final + expect_raises(Digest::FinalizedError) do + digest.final + end + end + + it "return the digest size" do + Digest::CRC32.new.digest_size.should eq 4 + end + it "should be able to calculate crc32" do crc = Digest::CRC32.checksum("foo").to_s(16) crc.should eq("8c736521") diff --git a/src/digest/adler32.cr b/src/digest/adler32.cr index f54dd8e2f354..110eb9a8684e 100644 --- a/src/digest/adler32.cr +++ b/src/digest/adler32.cr @@ -2,7 +2,15 @@ require "lib_z" require "./digest" # Implements the Adler32 checksum algorithm. -module Digest::Adler32 +class Digest::Adler32 < ::Digest + extend ClassMethods + + @digest : UInt32 + + def initialize + @digest = Adler32.initial + end + def self.initial : UInt32 LibZ.adler32(0, nil, 0).to_u32 end @@ -19,4 +27,27 @@ module Digest::Adler32 def self.combine(adler1 : UInt32, adler2 : UInt32, len) : UInt32 LibZ.adler32_combine(adler1, adler2, len).to_u32 end + + # :nodoc: + def update_impl(data : Bytes) : Nil + @digest = Adler32.update(data, @digest) + end + + # :nodoc: + def final_impl(dst : Bytes) : Nil + dst[0] = (@digest >> 24).to_u8! + dst[1] = (@digest >> 16).to_u8! + dst[2] = (@digest >> 8).to_u8! + dst[3] = (@digest).to_u8! + end + + # :nodoc: + def reset_impl : Nil + @digest = Adler32.initial + end + + # :nodoc: + def digest_size : Int32 + 4 + end end diff --git a/src/digest/crc32.cr b/src/digest/crc32.cr index 17e00a28c732..07432f455a69 100644 --- a/src/digest/crc32.cr +++ b/src/digest/crc32.cr @@ -2,7 +2,15 @@ require "lib_z" require "./digest" # Implements the CRC32 checksum algorithm. -module Digest::CRC32 +class Digest::CRC32 < ::Digest + extend ClassMethods + + @digest : UInt32 + + def initialize + @digest = CRC32.initial + end + def self.initial : UInt32 LibZ.crc32(0, nil, 0).to_u32 end @@ -19,4 +27,27 @@ module Digest::CRC32 def self.combine(crc1 : UInt32, crc2 : UInt32, len) : UInt32 LibZ.crc32_combine(crc1, crc2, len).to_u32 end + + # :nodoc: + def update_impl(data : Bytes) : Nil + @digest = CRC32.update(data, @digest) + end + + # :nodoc: + def final_impl(dst : Bytes) : Nil + dst[0] = (@digest >> 24).to_u8! + dst[1] = (@digest >> 16).to_u8! + dst[2] = (@digest >> 8).to_u8! + dst[3] = (@digest).to_u8! + end + + # :nodoc: + def reset_impl : Nil + @digest = CRC32.initial + end + + # :nodoc: + def digest_size : Int32 + 4 + end end From 300f7481575b89830bf8693aecc9a45529b74daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 23 Oct 2022 18:28:00 +0200 Subject: [PATCH 0072/1551] Add `HTTP::Server::Response#redirect` (#12526) Co-authored-by: Sijawusz Pur Rahnama --- spec/std/http/server/response_spec.cr | 52 +++++++++++++++++++ .../server/handlers/static_file_handler.cr | 5 +- src/http/server/response.cr | 22 ++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/spec/std/http/server/response_spec.cr b/spec/std/http/server/response_spec.cr index a78a1720d7f5..0322c1a00f51 100644 --- a/spec/std/http/server/response_spec.cr +++ b/spec/std/http/server/response_spec.cr @@ -348,4 +348,56 @@ describe HTTP::Server::Response do end end end + + describe "#redirect" do + ["/path", URI.parse("/path")].each do |location| + it "#{location.class} location" do + io = IO::Memory.new + response = Response.new(io) + response.redirect(location) + io.to_s.should eq("HTTP/1.1 302 Found\r\nLocation: /path\r\nContent-Length: 0\r\n\r\n") + end + end + + it "encodes special characters" do + io = IO::Memory.new + response = Response.new(io) + response.redirect("https://example.com/path\nfoo bar") + io.to_s.should eq("HTTP/1.1 302 Found\r\nLocation: https://example.com/path%0Afoo%20bar\r\nContent-Length: 0\r\n\r\n") + end + + it "permanent redirect" do + io = IO::Memory.new + response = Response.new(io) + response.redirect("/path", status: :moved_permanently) + io.to_s.should eq("HTTP/1.1 301 Moved Permanently\r\nLocation: /path\r\nContent-Length: 0\r\n\r\n") + end + + it "with header" do + io = IO::Memory.new + response = Response.new(io) + response.headers["Foo"] = "Bar" + response.redirect("/path", status: :moved_permanently) + io.to_s.should eq("HTTP/1.1 301 Moved Permanently\r\nFoo: Bar\r\nLocation: /path\r\nContent-Length: 0\r\n\r\n") + end + + it "fails if headers already sent" do + io = IO::Memory.new + response = Response.new(io) + response.puts "foo" + response.flush + expect_raises(IO::Error, "Headers already sent") do + response.redirect("/path") + end + end + + it "fails if closed" do + io = IO::Memory.new + response = Response.new(io) + response.close + expect_raises(IO::Error, "Closed stream") do + response.redirect("/path") + end + end + end end diff --git a/src/http/server/handlers/static_file_handler.cr b/src/http/server/handlers/static_file_handler.cr index 22584e3aa9f7..8b4041903aed 100644 --- a/src/http/server/handlers/static_file_handler.cr +++ b/src/http/server/handlers/static_file_handler.cr @@ -115,10 +115,7 @@ class HTTP::StaticFileHandler end private def redirect_to(context, url) - context.response.status = :found - - url = URI.encode_path(url.to_s) - context.response.headers.add "Location", url + context.response.redirect url.to_s end private def add_cache_headers(response_headers : HTTP::Headers, last_modified : Time) : Nil diff --git a/src/http/server/response.cr b/src/http/server/response.cr index 4803a8f6fb80..de3312a81e57 100644 --- a/src/http/server/response.cr +++ b/src/http/server/response.cr @@ -170,6 +170,28 @@ class HTTP::Server respond_with_status(HTTP::Status.new(status), message) end + # Sends a redirect to *location*. + # + # The value of *location* gets encoded with `URI.encode`. + # + # The *status* determines the HTTP status code which can be + # `HTTP::Status::FOUND` (`302`) for a temporary redirect or + # `HTTP::Status::MOVED_PERMANENTLY` (`301`) for a permanent redirect. + # + # The response gets closed. + # + # Raises `IO::Error` if the response is closed or headers were already + # sent. + def redirect(location : String | URI, status : HTTP::Status = :found) + check_headers + + self.status = status + headers["Location"] = String.build do |io| + URI.encode(location.to_s, io) { |byte| URI.reserved?(byte) || URI.unreserved?(byte) } + end + close + end + private def check_headers raise IO::Error.new "Closed stream" if @original_output.closed? if wrote_headers? From 1124706bf6c28b33650eb7180fcff22285ab28a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 23 Oct 2022 18:28:22 +0200 Subject: [PATCH 0073/1551] Rename `File.real_path` to `.realpath` (#12552) --- src/crystal/system/unix/file.cr | 8 ++++---- src/crystal/system/wasi/file.cr | 4 ++-- src/crystal/system/win32/file.cr | 8 ++++---- src/exception/call_stack/mach_o.cr | 4 ++-- src/file.cr | 8 +++++++- src/process/executable_path.cr | 2 +- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index 5638d6b62ded..a73a190f45a5 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -143,10 +143,10 @@ module Crystal::System::File end end - def self.real_path(path) - real_path_ptr = LibC.realpath(path, nil) - raise ::File::Error.from_errno("Error resolving real path", file: path) unless real_path_ptr - String.new(real_path_ptr).tap { LibC.free(real_path_ptr.as(Void*)) } + def self.realpath(path) + realpath_ptr = LibC.realpath(path, nil) + raise ::File::Error.from_errno("Error resolving real path", file: path) unless realpath_ptr + String.new(realpath_ptr).tap { LibC.free(realpath_ptr.as(Void*)) } end def self.link(old_path, new_path) diff --git a/src/crystal/system/wasi/file.cr b/src/crystal/system/wasi/file.cr index ef8d90458728..3f4a0baa06fe 100644 --- a/src/crystal/system/wasi/file.cr +++ b/src/crystal/system/wasi/file.cr @@ -10,8 +10,8 @@ module Crystal::System::File raise NotImplementedError.new "Crystal::System::File.chown" end - def self.real_path(path) - raise NotImplementedError.new "Crystal::System::File.real_path" + def self.realpath(path) + raise NotImplementedError.new "Crystal::System::File.realpath" end def self.utime(atime : ::Time, mtime : ::Time, filename : String) : Nil diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 9957978b9dda..c36ab3480297 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -177,11 +177,11 @@ module Crystal::System::File end end - def self.real_path(path : String) : String + def self.realpath(path : String) : String # TODO: read links using https://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx win_path = to_windows_path(path) - real_path = System.retry_wstr_buffer do |buffer, small_buf| + realpath = System.retry_wstr_buffer do |buffer, small_buf| len = LibC.GetFullPathNameW(win_path, buffer.size, buffer, nil) if 0 < len < buffer.size break String.from_utf16(buffer[0, len]) @@ -192,11 +192,11 @@ module Crystal::System::File end end - unless exists? real_path + unless exists? realpath raise ::File::Error.from_os_error("Error resolving real path", Errno::ENOENT, file: path) end - real_path + realpath end def self.link(old_path : String, new_path : String) : Nil diff --git a/src/exception/call_stack/mach_o.cr b/src/exception/call_stack/mach_o.cr index fb1dee202d48..5646511541a7 100644 --- a/src/exception/call_stack/mach_o.cr +++ b/src/exception/call_stack/mach_o.cr @@ -101,10 +101,10 @@ struct Exception::CallStack end end - program = File.real_path(String.new(buffer)) + program = File.realpath(String.new(buffer)) LibC._dyld_image_count.times do |i| - if program == File.real_path(String.new(LibC._dyld_get_image_name(i))) + if program == File.realpath(String.new(LibC._dyld_get_image_name(i))) return LibC._dyld_get_image_vmaddr_slide(i) end end diff --git a/src/file.cr b/src/file.cr index 599228058435..cec57c03efae 100644 --- a/src/file.cr +++ b/src/file.cr @@ -611,8 +611,14 @@ class File < IO::FileDescriptor end # Resolves the real path of *path* by following symbolic links. + def self.realpath(path : Path | String) : String + Crystal::System::File.realpath(path.to_s) + end + + # :ditto: + @[Deprecated("Use `.realpath` instead.")] def self.real_path(path : Path | String) : String - Crystal::System::File.real_path(path.to_s) + realpath(path) end # Creates a new link (also known as a hard link) at *new_path* to an existing file diff --git a/src/process/executable_path.cr b/src/process/executable_path.cr index 5d8062a0d8d6..75f8dbf621cf 100644 --- a/src/process/executable_path.cr +++ b/src/process/executable_path.cr @@ -31,7 +31,7 @@ class Process def self.executable_path : String? if executable = executable_path_impl begin - File.real_path(executable) + File.realpath(executable) rescue File::Error end end From 7fbf2f6f7694a089db59276dfe34bee428ab2d44 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sun, 23 Oct 2022 15:47:44 -0300 Subject: [PATCH 0074/1551] Trap when trying to raise wasm32 exceptions (#12572) --- src/raise.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/raise.cr b/src/raise.cr index de89151ff6c1..17f1eea2b121 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -216,6 +216,7 @@ end {% if flag?(:wasm32) %} def raise(exception : Exception) : NoReturn LibC.printf("EXITING: Attempting to raise:\n#{exception.inspect_with_backtrace}") + LibIntrinsics.debugtrap LibC.exit(1) end {% else %} From 5c2c696e1e1249547b2e73a904023776fa2d92da Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 23 Oct 2022 15:48:09 -0300 Subject: [PATCH 0075/1551] Interpreter: fix class var initializer that needs an upcast (#12635) --- spec/compiler/interpreter/class_vars_spec.cr | 41 ++++++++++++++++++++ src/compiler/crystal/interpreter/compiler.cr | 15 ++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/spec/compiler/interpreter/class_vars_spec.cr b/spec/compiler/interpreter/class_vars_spec.cr index 4664fba37a78..a70529d2325d 100644 --- a/spec/compiler/interpreter/class_vars_spec.cr +++ b/spec/compiler/interpreter/class_vars_spec.cr @@ -144,5 +144,46 @@ describe Crystal::Repl::Interpreter do Foo.value CODE end + + it "does class var initializer with union (#12633)" do + interpret(<<-CODE).should eq("hello") + class MyClass + @@a : String | Int32 = "hello" + + def self.a + @@a + end + end + + x = MyClass.a + case x + in String + x + in Int32 + "bye" + end + CODE + end + + it "reads class var initializer with union (#12633)" do + interpret(<<-CODE).should eq(2) + class MyClass + @@a : Char | Int32 = 1 + + def self.foo(a) + @@a = a + b = @@a + case b + in Char + 3 + in Int32 + b + end + end + end + + MyClass.foo(2) + CODE + end end end diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 1ec8d115b170..cb3ef35da3cb 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -1155,8 +1155,19 @@ class Crystal::Repl::Compiler < Crystal::Visitor fake_def = Def.new(def_name) fake_def.owner = var.owner.metaclass fake_def.vars = initializer.meta_vars - fake_def.body = value - fake_def.bind_to(value) + + # Check if we need to upcast the value to the class var's type + fake_def.body = + if value.type? == var.type + value + else + cast = Cast.new(value, TypeNode.new(var.type)) + cast.upcast = true + cast.type = var.type + cast + end + + fake_def.bind_to(fake_def.body) compiled_def = CompiledDef.new(@context, fake_def, fake_def.owner, 0) From 7d08b49069c5ce6f8065626b6763676e2323d130 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 23 Oct 2022 19:19:32 -0300 Subject: [PATCH 0076/1551] Websocket: write masked data to temporary buffer before sending it (#12613) --- spec/std/http/web_socket_spec.cr | 12 ++++++------ src/http/web_socket/protocol.cr | 29 ++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/spec/std/http/web_socket_spec.cr b/spec/std/http/web_socket_spec.cr index 456748896727..024ab06e150f 100644 --- a/spec/std/http/web_socket_spec.cr +++ b/spec/std/http/web_socket_spec.cr @@ -182,8 +182,8 @@ describe HTTP::WebSocket do describe "send" do it "sends long data with correct header" do - size = UInt16::MAX.to_u64 + 1 - big_string = "a" * size + big_string = "abcdefghijklmnopqrstuvwxyz" * (IO::DEFAULT_BUFFER_SIZE // 4) + size = big_string.size io = IO::Memory.new ws = HTTP::WebSocket::Protocol.new(io) ws.send(big_string) @@ -194,7 +194,7 @@ describe HTTP::WebSocket do 8.times { |i| received_size <<= 8; received_size += bytes[2 + i] } received_size.should eq(size) size.times do |i| - bytes[10 + i].should eq('a'.ord) + bytes[10 + i].should eq(big_string[i].ord) end end @@ -285,8 +285,8 @@ describe HTTP::WebSocket do end it "sends long data with correct header" do - size = UInt16::MAX.to_u64 + 1 - big_string = "a" * size + big_string = "abcdefghijklmnopqrstuvwxyz" * (IO::DEFAULT_BUFFER_SIZE // 4) + size = big_string.size io = IO::Memory.new ws = HTTP::WebSocket::Protocol.new(io, masked: true) ws.send(big_string) @@ -298,7 +298,7 @@ describe HTTP::WebSocket do 8.times { |i| received_size <<= 8; received_size += bytes[2 + i] } received_size.should eq(size) size.times do |i| - (bytes[14 + i] ^ bytes[10 + (i % 4)]).should eq('a'.ord) + (bytes[14 + i] ^ bytes[10 + (i % 4)]).should eq(big_string[i].ord) end end end diff --git a/src/http/web_socket/protocol.cr b/src/http/web_socket/protocol.cr index 3dc8e62c94cf..307ff74be24b 100644 --- a/src/http/web_socket/protocol.cr +++ b/src/http/web_socket/protocol.cr @@ -140,9 +140,32 @@ class HTTP::WebSocket::Protocol mask_array = key.unsafe_as(StaticArray(UInt8, 4)) @io.write mask_array.to_slice - data.each_with_index do |byte, index| - mask = mask_array[index & 0b11] # x & 0b11 == x % 4 - @io.write_byte(byte ^ mask) + write_masked_data(data, mask_array) + end + + private def write_masked_data(data, mask_array) + # We are going to write the data, masked, into a temporary buffer. + masked_data = uninitialized UInt8[IO::DEFAULT_BUFFER_SIZE] + + # We'll do it by chunks of at most IO::DEFAULT_BUFFER_SIZE + remaining_data = data + until remaining_data.empty? + # How much data can we write? + # Either IO::DEFAULT_BUFFER_SIZE or whatever remains. + chunk_size = Math.min(remaining_data.size, IO::DEFAULT_BUFFER_SIZE) + + # Mask the data + chunk = remaining_data[0, chunk_size] + chunk.each_with_index do |byte, index| + mask = mask_array[index & 0b11] # x & 0b11 == x % 4 + masked_data[index] = byte ^ mask + end + + # Write the masked data + @io.write(masked_data.to_slice[0, chunk_size]) + + # Discard the written data + remaining_data = remaining_data[chunk_size..] end end From 4ff95d58cd39b828345a10770937c55e35e9417f Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Mon, 24 Oct 2022 06:19:50 +0800 Subject: [PATCH 0077/1551] Use mutating collection methods (#12644) --- src/channel.cr | 4 ++-- src/compiler/crystal/semantic/call_error.cr | 6 +++--- src/compiler/crystal/semantic/exhaustiveness_checker.cr | 4 ++-- src/compiler/crystal/semantic/restrictions.cr | 4 ++-- src/http/headers.cr | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/channel.cr b/src/channel.cr index 04a7b8b67a3a..59fe55e38cf4 100644 --- a/src/channel.cr +++ b/src/channel.cr @@ -425,8 +425,8 @@ class Channel(T) # This is to avoid deadlocks between concurrent `select` calls ops_locks = ops .to_a - .uniq(&.lock_object_id) - .sort_by(&.lock_object_id) + .uniq!(&.lock_object_id) + .sort_by!(&.lock_object_id) ops_locks.each &.lock diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index de8ccac18b1e..1abe22db6e58 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -244,7 +244,7 @@ class Crystal::Call return unless call_errors.all?(T) call_errors = call_errors.map &.as(T) - all_names = call_errors.flat_map(&.names).uniq + all_names = call_errors.flat_map(&.names).uniq! names_in_all_overloads = all_names.select do |missing_name| call_errors.all? &.names.includes?(missing_name) end @@ -266,7 +266,7 @@ class Crystal::Call call_errors = call_errors.map &.as(ArgumentsTypeMismatch) argument_type_mismatches = call_errors.flat_map(&.errors) - all_indexes_or_names = argument_type_mismatches.map(&.index_or_name).uniq + all_indexes_or_names = argument_type_mismatches.map(&.index_or_name).uniq! indexes_or_names_in_all_overloads = all_indexes_or_names.select do |index_or_name| call_errors.all? &.errors.any? &.index_or_name.==(index_or_name) end @@ -277,7 +277,7 @@ class Crystal::Call index_or_name = indexes_or_names_in_all_overloads.first mismatches = argument_type_mismatches.select(&.index_or_name.==(index_or_name)) - expected_types = mismatches.map(&.expected_type).uniq.sort_by(&.to_s) + expected_types = mismatches.map(&.expected_type).uniq!.sort_by!(&.to_s) actual_type = mismatches.first.actual_type arg = diff --git a/src/compiler/crystal/semantic/exhaustiveness_checker.cr b/src/compiler/crystal/semantic/exhaustiveness_checker.cr index 15bd7297a5a3..630fbcf1b36c 100644 --- a/src/compiler/crystal/semantic/exhaustiveness_checker.cr +++ b/src/compiler/crystal/semantic/exhaustiveness_checker.cr @@ -395,7 +395,7 @@ struct Crystal::ExhaustivenessChecker gathered_missing_cases = [] of String # See if a case is missing for both false and true: show it as Bool in that case - missing_cases_per_bool.values.flatten.uniq.each do |missing_case| + missing_cases_per_bool.values.flatten.uniq!.each do |missing_case| if {true, false}.all? { |bool| missing_cases_per_bool[bool]?.try &.includes?(missing_case) } gathered_missing_cases << "Bool, #{missing_case}" {true, false}.each { |bool| missing_cases_per_bool[bool]?.try &.delete(missing_case) } @@ -526,7 +526,7 @@ struct Crystal::ExhaustivenessChecker gathered_missing_cases = [] of String # See if a case is missing for all members: show it as the enum name in that case - missing_cases_per_member.values.flatten.uniq.each do |missing_case| + missing_cases_per_member.values.flatten.uniq!.each do |missing_case| if @members.all? { |member| missing_cases_per_member[member]?.try &.includes?(missing_case) } gathered_missing_cases << "#{@type}, #{missing_case}" @members.each { |member| missing_cases_per_member[member]?.try &.delete(missing_case) } diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 26d6fade3817..f3c1bca256e7 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -511,7 +511,7 @@ module Crystal def required_named_arguments if (splat_index = self.def.splat_index) && splat_index != self.def.args.size - 1 - self.def.args[splat_index + 1..-1].select { |arg| !arg.default_value }.sort_by &.external_name + self.def.args[splat_index + 1..-1].select { |arg| !arg.default_value }.sort_by! &.external_name else nil end @@ -548,7 +548,7 @@ module Crystal def required_named_arguments if (splat_index = self.splat_index) && splat_index != args.size - 1 - args[splat_index + 1..-1].select { |arg| !arg.default_value }.sort_by &.external_name + args[splat_index + 1..-1].select { |arg| !arg.default_value }.sort_by! &.external_name else nil end diff --git a/src/http/headers.cr b/src/http/headers.cr index d8823a9aa0b2..f3f4cd3f12a8 100644 --- a/src/http/headers.cr +++ b/src/http/headers.cr @@ -282,7 +282,7 @@ struct HTTP::Headers end def pretty_print(pp) - pp.list("HTTP::Headers{", @hash.keys.sort_by(&.name), "}") do |key| + pp.list("HTTP::Headers{", @hash.keys.sort_by!(&.name), "}") do |key| pp.group do key.name.pretty_print(pp) pp.text " =>" From 09868bb1a8206dc7b0ebfb6a238f27023f3d0151 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Sun, 23 Oct 2022 18:20:26 -0400 Subject: [PATCH 0078/1551] Add argless `#annotations` overload (#9326) Co-authored-by: Sijawusz Pur Rahnama --- spec/compiler/semantic/annotation_spec.cr | 809 ++++++++++++++-------- src/compiler/crystal/annotatable.cr | 5 + src/compiler/crystal/macros.cr | 28 +- src/compiler/crystal/macros/methods.cr | 36 +- src/compiler/crystal/types.cr | 2 +- 5 files changed, 581 insertions(+), 299 deletions(-) diff --git a/spec/compiler/semantic/annotation_spec.cr b/spec/compiler/semantic/annotation_spec.cr index 89dd5e734243..94ea352e4177 100644 --- a/spec/compiler/semantic/annotation_spec.cr +++ b/spec/compiler/semantic/annotation_spec.cr @@ -104,368 +104,605 @@ describe "Semantic: annotation" do end describe "#annotations" do - it "returns an empty array if there are none defined" do - assert_type(%( - annotation Foo - end + describe "all types" do + it "returns an empty array if there are none defined" do + assert_type(%( + annotation Foo; end - module Moo - end + module Moo + end - {% if Moo.annotations(Foo).size == 0 %} - 1 - {% else %} - 'a' - {% end %} - )) { int32 } - end + {% if Moo.annotations.empty? %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end - it "finds annotations on a module" do - assert_type(%( - annotation Foo - end + it "finds annotations on a module" do + assert_type(%( + annotation Foo; end + annotation Bar; end - @[Foo] - @[Foo] - module Moo - end + @[Foo] + @[Bar] + module Moo + end - {% if Moo.annotations(Foo).size == 2 %} - 1 - {% else %} - 'a' - {% end %} - )) { int32 } - end + {% if Moo.annotations.map(&.name.id) == [Foo.id, Bar.id] %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end - it "uses annotations value, positional" do - assert_type(%( - annotation Foo - end + it "finds annotations on a class" do + assert_type(%( + annotation Foo; end + annotation Bar; end - @[Foo(1)] - @[Foo(2)] - module Moo - end + @[Foo] + @[Bar] + class Moo + end - {% if Moo.annotations(Foo)[0][0] == 1 && Moo.annotations(Foo)[1][0] == 2 %} - 1 - {% else %} - 'a' - {% end %} - )) { int32 } - end + {% if Moo.annotations.map(&.name.id) == [Foo.id, Bar.id] %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end - it "uses annotations value, keyword" do - assert_type(%( - annotation Foo - end + it "finds annotations on a struct" do + assert_type(%( + annotation Foo; end + annotation Bar; end - @[Foo(x: 1)] - @[Foo(x: 2)] - module Moo - end + @[Foo] + @[Bar] + struct Moo + end - {% if Moo.annotations(Foo)[0][:x] == 1 && Moo.annotations(Foo)[1][:x] == 2 %} - 1 - {% else %} - 'a' - {% end %} - )) { int32 } - end + {% if Moo.annotations.map(&.name.id) == [Foo.id, Bar.id] %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end - it "finds annotations in class" do - assert_type(%( - annotation Foo - end + it "finds annotations on a enum" do + assert_type(%( + annotation Foo; end + annotation Bar; end - @[Foo] - @[Foo] - @[Foo] - class Moo - end + @[Foo] + @[Bar] + enum Moo + A = 1 + end - {% if Moo.annotations(Foo).size == 3 %} - 1 - {% else %} - 'a' - {% end %} - )) { int32 } - end + {% if Moo.annotations.map(&.name.id) == [Foo.id, Bar.id] %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end - it "finds annotations in struct" do - assert_type(%( - annotation Foo - end + it "finds annotations on a lib" do + assert_type(%( + annotation Foo; end + annotation Bar; end - @[Foo] - @[Foo] - @[Foo] - @[Foo] - struct Moo - end + @[Foo] + @[Bar] + lib Moo + A = 1 + end - {% if Moo.annotations(Foo).size == 4 %} - 1 - {% else %} - 'a' - {% end %} - )) { int32 } - end + {% if Moo.annotations.map(&.name.id) == [Foo.id, Bar.id] %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end - it "finds annotations in enum" do - assert_type(%( - annotation Foo - end + it "finds annotations in instance var (declaration)" do + assert_type(%( + annotation Foo; end + annotation Bar; end + + class Moo + @[Foo] + @[Bar] + @x : Int32 = 1 + + def foo + {% if @type.instance_vars.first.annotations.size == 2 %} + 1 + {% else %} + 'a' + {% end %} + end + end - @[Foo] - enum Moo - A = 1 - end + Moo.new.foo + )) { int32 } + end - {% if Moo.annotations(Foo).size == 1 %} - 1 - {% else %} - 'a' - {% end %} - )) { int32 } - end + it "finds annotations in instance var (declaration, generic)" do + assert_type(%( + annotation Foo; end + annotation Bar; end + + class Moo(T) + @[Foo] + @[Bar] + @x : T + + def initialize(@x : T) + end + + def foo + {% if @type.instance_vars.first.annotations.size == 2 %} + 1 + {% else %} + 'a' + {% end %} + end + end - it "finds annotations in lib" do - assert_type(%( - annotation Foo - end + Moo.new(1).foo + )) { int32 } + end - @[Foo] - @[Foo] - lib Moo - A = 1 - end + it "adds annotations on def" do + assert_type(%( + annotation Foo; end + annotation Bar; end + + class Moo + @[Foo] + @[Bar] + def foo + end + end - {% if Moo.annotations(Foo).size == 2 %} - 1 - {% else %} - 'a' - {% end %} - )) { int32 } + {% if Moo.methods.first.annotations.size == 2 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "finds annotations in generic parent (#7885)" do + assert_type(%( + annotation Foo; end + annotation Bar; end + + @[Foo(1)] + @[Bar(2)] + class Parent(T) + end + + class Child < Parent(Int32) + end + + {% if Child.superclass.annotations.map(&.[0]) == [1, 2] %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "find annotations on method parameters" do + assert_type(%( + annotation Foo; end + annotation Bar; end + + class Moo + def foo(@[Foo] @[Bar] value) + end + end + + {% if Moo.methods.first.args.first.annotations.size == 2 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end end - it "can't find annotations in instance var" do - assert_type(%( - annotation Foo - end + describe "of a specific type" do + it "returns an empty array if there are none defined" do + assert_type(%( + annotation Foo + end + + module Moo + end + + {% if Moo.annotations(Foo).size == 0 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "finds annotations on a module" do + assert_type(%( + annotation Foo + end + + @[Foo] + @[Foo] + module Moo + end + + {% if Moo.annotations(Foo).size == 2 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "uses annotations value, positional" do + assert_type(%( + annotation Foo + end + + @[Foo(1)] + @[Foo(2)] + module Moo + end + + {% if Moo.annotations(Foo)[0][0] == 1 && Moo.annotations(Foo)[1][0] == 2 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "uses annotations value, keyword" do + assert_type(%( + annotation Foo + end + + @[Foo(x: 1)] + @[Foo(x: 2)] + module Moo + end + + {% if Moo.annotations(Foo)[0][:x] == 1 && Moo.annotations(Foo)[1][:x] == 2 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "finds annotations in class" do + assert_type(%( + annotation Foo + end + + @[Foo] + @[Foo] + @[Foo] + class Moo + end + + {% if Moo.annotations(Foo).size == 3 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "finds annotations in struct" do + assert_type(%( + annotation Foo + end + + @[Foo] + @[Foo] + @[Foo] + @[Foo] + struct Moo + end - class Moo - @x : Int32 = 1 + {% if Moo.annotations(Foo).size == 4 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end - def foo - {% unless @type.instance_vars.first.annotations(Foo).empty? %} - 1 - {% else %} - 'a' - {% end %} + it "finds annotations in enum" do + assert_type(%( + annotation Foo end - end - Moo.new.foo - )) { char } - end + @[Foo] + enum Moo + A = 1 + end - it "can't find annotations in instance var, when other annotations are present" do - assert_type(%( - annotation Foo - end + {% if Moo.annotations(Foo).size == 1 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end - annotation Bar - end + it "finds annotations in lib" do + assert_type(%( + annotation Foo + end - class Moo - @[Bar] - @x : Int32 = 1 + @[Foo] + @[Foo] + lib Moo + A = 1 + end - def foo - {% unless @type.instance_vars.first.annotations(Foo).empty? %} - 1 - {% else %} - 'a' - {% end %} + {% if Moo.annotations(Foo).size == 2 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "can't find annotations in instance var" do + assert_type(%( + annotation Foo end - end - Moo.new.foo - )) { char } - end + class Moo + @x : Int32 = 1 - it "finds annotations in instance var (declaration)" do - assert_type(%( - annotation Foo - end + def foo + {% unless @type.instance_vars.first.annotations(Foo).empty? %} + 1 + {% else %} + 'a' + {% end %} + end + end - class Moo - @[Foo] - @[Foo] - @x : Int32 = 1 + Moo.new.foo + )) { char } + end - def foo - {% if @type.instance_vars.first.annotations(Foo).size == 2 %} - 1 - {% else %} - 'a' - {% end %} + it "can't find annotations in instance var, when other annotations are present" do + assert_type(%( + annotation Foo end - end - Moo.new.foo - )) { int32 } - end + annotation Bar + end - it "finds annotations in instance var (declaration, generic)" do - assert_type(%( - annotation Foo - end + class Moo + @[Bar] + @x : Int32 = 1 + + def foo + {% unless @type.instance_vars.first.annotations(Foo).empty? %} + 1 + {% else %} + 'a' + {% end %} + end + end - class Moo(T) - @[Foo] - @x : T + Moo.new.foo + )) { char } + end - def initialize(@x : T) + it "finds annotations in instance var (declaration)" do + assert_type(%( + annotation Foo end - def foo - {% if @type.instance_vars.first.annotations(Foo).size == 1 %} - 1 - {% else %} - 'a' - {% end %} + class Moo + @[Foo] + @[Foo] + @x : Int32 = 1 + + def foo + {% if @type.instance_vars.first.annotations(Foo).size == 2 %} + 1 + {% else %} + 'a' + {% end %} + end end - end - Moo.new(1).foo - )) { int32 } - end + Moo.new.foo + )) { int32 } + end - it "collects annotations values in type" do - assert_type(%( - annotation Foo - end + it "finds annotations in instance var (declaration, generic)" do + assert_type(%( + annotation Foo + end - @[Foo(1)] - module Moo - end + class Moo(T) + @[Foo] + @x : T - @[Foo(2)] - module Moo - end + def initialize(@x : T) + end - {% if Moo.annotations(Foo)[0][0] == 1 && Moo.annotations(Foo)[1][0] == 2 %} - 1 - {% else %} - 'a' - {% end %} - )) { int32 } - end + def foo + {% if @type.instance_vars.first.annotations(Foo).size == 1 %} + 1 + {% else %} + 'a' + {% end %} + end + end - it "overrides annotations value in type" do - assert_type(%( - annotation Foo - end + Moo.new(1).foo + )) { int32 } + end + + it "collects annotations values in type" do + assert_type(%( + annotation Foo + end - class Moo @[Foo(1)] - @x : Int32 = 1 - end + module Moo + end - class Moo @[Foo(2)] - @x : Int32 = 1 + module Moo + end - def foo - {% if @type.instance_vars.first.annotations(Foo).size == 1 && @type.instance_vars.first.annotations(Foo)[0][0] == 2 %} - 1 - {% else %} - 'a' - {% end %} + {% if Moo.annotations(Foo)[0][0] == 1 && Moo.annotations(Foo)[1][0] == 2 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "overrides annotations value in type" do + assert_type(%( + annotation Foo end - end - Moo.new.foo - )) { int32 } - end + class Moo + @[Foo(1)] + @x : Int32 = 1 + end - it "adds annotations on def" do - assert_type(%( - annotation Foo - end + class Moo + @[Foo(2)] + @x : Int32 = 1 + + def foo + {% if @type.instance_vars.first.annotations(Foo).size == 1 && @type.instance_vars.first.annotations(Foo)[0][0] == 2 %} + 1 + {% else %} + 'a' + {% end %} + end + end - class Moo - @[Foo] - @[Foo] - def foo + Moo.new.foo + )) { int32 } + end + + it "adds annotations on def" do + assert_type(%( + annotation Foo end - end - {% if Moo.methods.first.annotations(Foo).size == 2 %} - 1 - {% else %} - 'a' - {% end %} - )) { int32 } - end + class Moo + @[Foo] + @[Foo] + def foo + end + end - it "can't find annotations on def" do - assert_type(%( - annotation Foo - end + {% if Moo.methods.first.annotations(Foo).size == 2 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end - class Moo - def foo + it "can't find annotations on def" do + assert_type(%( + annotation Foo end - end - {% unless Moo.methods.first.annotations(Foo).empty? %} - 1 - {% else %} - 'a' - {% end %} - )) { char } - end + class Moo + def foo + end + end - it "can't find annotations on def, when other annotations are present" do - assert_type(%( - annotation Foo - end + {% unless Moo.methods.first.annotations(Foo).empty? %} + 1 + {% else %} + 'a' + {% end %} + )) { char } + end - annotation Bar - end + it "can't find annotations on def, when other annotations are present" do + assert_type(%( + annotation Foo + end - class Moo - @[Bar] - def foo + annotation Bar end - end - {% unless Moo.methods.first.annotations(Foo).empty? %} - 1 - {% else %} - 'a' - {% end %} - )) { char } - end + class Moo + @[Bar] + def foo + end + end - it "finds annotations in generic parent (#7885)" do - assert_type(%( - annotation Ann - end + {% unless Moo.methods.first.annotations(Foo).empty? %} + 1 + {% else %} + 'a' + {% end %} + )) { char } + end - @[Ann(1)] - class Parent(T) - end + it "finds annotations in generic parent (#7885)" do + assert_type(%( + annotation Ann + end - class Child < Parent(Int32) - end + @[Ann(1)] + class Parent(T) + end - {{ Child.superclass.annotations(Ann)[0][0] }} - )) { int32 } + class Child < Parent(Int32) + end + + {{ Child.superclass.annotations(Ann)[0][0] }} + )) { int32 } + end + + it "find annotations on method parameters" do + assert_type(%( + annotation Foo; end + annotation Bar; end + + class Moo + def foo(@[Foo] @[Bar] value) + end + end + + {% if Moo.methods.first.args.first.annotations(Foo).size == 1 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end end end diff --git a/src/compiler/crystal/annotatable.cr b/src/compiler/crystal/annotatable.cr index f5adc5127684..ca12790335f9 100644 --- a/src/compiler/crystal/annotatable.cr +++ b/src/compiler/crystal/annotatable.cr @@ -19,5 +19,10 @@ module Crystal def annotations(annotation_type : AnnotationType) : Array(Annotation)? @annotations.try &.[annotation_type]? end + + # Returns all annotations on this type, if any, or `nil` otherwise + def all_annotations : Array(Annotation)? + @annotations.try &.values.flatten + end end end diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 91ade699fa8b..9dadf48af06c 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -918,6 +918,11 @@ module Crystal::Macros # attached to this variable, or an empty `ArrayLiteral` if there are none. def annotations(type : TypeNode) : ArrayLiteral(Annotation) end + + # Returns an array of all annotations attached to this + # variable, or an empty `ArrayLiteral` if there are none. + def annotations : ArrayLiteral(Annotation) + end end # An annotation on top of a type or variable. @@ -1124,6 +1129,11 @@ module Crystal::Macros def annotations(type : TypeNode) : ArrayLiteral(Annotation) end + # Returns an array of all annotations attached to this + # arg, or an empty `ArrayLiteral` if there are none. + def annotations : ArrayLiteral(Annotation) + end + # Returns the external name of this argument. # # For example, for `def write(to file)` returns `to`. @@ -1219,14 +1229,19 @@ module Crystal::Macros end # Returns the last `Annotation` with the given `type` - # attached to this variable or `NilLiteral` if there are none. + # attached to this method or `NilLiteral` if there are none. def annotation(type : TypeNode) : Annotation | NilLiteral end # Returns an array of annotations with the given `type` - # attached to this variable, or an empty `ArrayLiteral` if there are none. + # attached to this method, or an empty `ArrayLiteral` if there are none. def annotations(type : TypeNode) : ArrayLiteral(Annotation) end + + # Returns an array of all annotations attached to this + # method, or an empty `ArrayLiteral` if there are none. + def annotations : ArrayLiteral(Annotation) + end end # A macro definition. @@ -1974,15 +1989,20 @@ module Crystal::Macros end # Returns the last `Annotation` with the given `type` - # attached to this variable or `NilLiteral` if there are none. + # attached to this type or `NilLiteral` if there are none. def annotation(type : TypeNode) : Annotation | NilLiteral end # Returns an array of annotations with the given `type` - # attached to this variable, or an empty `ArrayLiteral` if there are none. + # attached to this type, or an empty `ArrayLiteral` if there are none. def annotations(type : TypeNode) : ArrayLiteral(Annotation) end + # Returns an array of all annotations attached to this + # type, or an empty `ArrayLiteral` if there are none. + def annotations : ArrayLiteral(Annotation) + end + # Returns the number of elements in this tuple type or tuple metaclass type. # Gives a compile error if this is not one of those types. def size : NumberLiteral diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 115b771a399a..de453f0ea4f5 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1173,8 +1173,8 @@ module Crystal self.var.annotation(type) end when "annotations" - fetch_annotation(self, method, args, named_args, block) do |type| - annotations = self.var.annotations(type) + fetch_annotations(self, method, args, named_args, block) do |type| + annotations = type ? self.var.annotations(type) : self.var.all_annotations return ArrayLiteral.new if annotations.nil? ArrayLiteral.map(annotations, &.itself) end @@ -1347,8 +1347,8 @@ module Crystal self.annotation(type) end when "annotations" - fetch_annotation(self, method, args, named_args, block) do |type| - annotations = self.annotations(type) + fetch_annotations(self, method, args, named_args, block) do |type| + annotations = type ? self.annotations(type) : self.all_annotations return ArrayLiteral.new if annotations.nil? ArrayLiteral.map(annotations, &.itself) end @@ -1400,8 +1400,8 @@ module Crystal self.annotation(type) end when "annotations" - fetch_annotation(self, method, args, named_args, block) do |type| - annotations = self.annotations(type) + fetch_annotations(self, method, args, named_args, block) do |type| + annotations = type ? self.annotations(type) : self.all_annotations return ArrayLiteral.new if annotations.nil? ArrayLiteral.map(annotations, &.itself) end @@ -1658,8 +1658,8 @@ module Crystal self.type.annotation(type) end when "annotations" - fetch_annotation(self, method, args, named_args, block) do |type| - annotations = self.type.annotations(type) + fetch_annotations(self, method, args, named_args, block) do |type| + annotations = type ? self.type.annotations(type) : self.type.all_annotations return ArrayLiteral.new if annotations.nil? ArrayLiteral.map(annotations, &.itself) end @@ -2691,6 +2691,26 @@ private def fetch_annotation(node, method, args, named_args, block) end end +private def fetch_annotations(node, method, args, named_args, block) + interpret_check_args(node: node, min_count: 0) do |arg| + unless arg + return yield(nil) || Crystal::NilLiteral.new + end + + unless arg.is_a?(Crystal::TypeNode) + args[0].raise "argument to '#{node.class_desc}#annotation' must be a TypeNode, not #{arg.class_desc}" + end + + type = arg.type + unless type.is_a?(Crystal::AnnotationType) + args[0].raise "argument to '#{node.class_desc}#annotation' must be an annotation type, not #{type} (#{type.type_desc})" + end + + value = yield type + value || Crystal::NilLiteral.new + end +end + private def sort_by(object, klass, block, interpreter) block_arg = block.args.first? diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 8ff8df3c6f13..94ee4af77598 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -1968,7 +1968,7 @@ module Crystal getter generic_type : GenericType getter type_vars : Hash(String, ASTNode) - delegate :annotation, :annotations, to: generic_type + delegate :annotation, :annotations, :all_annotations, to: generic_type def initialize(program, @generic_type, @type_vars) super(program) From 8ac2216bb05e28820f1aad8aba5454cdf942ba04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 24 Oct 2022 11:48:02 +0200 Subject: [PATCH 0079/1551] Optimize `Range#sample(n)` for integers and floats (#12535) Co-authored-by: Ary Borenszweig --- spec/std/range_spec.cr | 152 +++++++++++++++++++++++++++++++++++++++++ src/range.cr | 77 +++++++++++++++++++-- 2 files changed, 225 insertions(+), 4 deletions(-) diff --git a/spec/std/range_spec.cr b/spec/std/range_spec.cr index b44e7dd0de46..c3307ddf3997 100644 --- a/spec/std/range_spec.cr +++ b/spec/std/range_spec.cr @@ -430,6 +430,158 @@ describe "Range" do x = r.sample r.should contain(x) end + + it "samples with n = 0" do + (1..3).sample(0).empty?.should be_true + end + + context "for an integer range" do + it "samples an inclusive range without n" do + value = (1..3).sample + (1 <= value <= 3).should be_true + end + + it "samples an exclusive range without n" do + value = (1...3).sample + (1 <= value <= 2).should be_true + end + + it "samples an inclusive range with n = 1" do + values = (1..3).sample(1) + values.size.should eq(1) + (1 <= values.first <= 3).should be_true + end + + it "samples an exclusive range with n = 1" do + values = (1...3).sample(1) + values.size.should eq(1) + (1 <= values.first <= 2).should be_true + end + + it "samples an inclusive range with n > 1" do + values = (1..10).sample(5) + values.size.should eq(5) + values.uniq.size.should eq(5) + values.all? { |value| 1 <= value <= 10 }.should be_true + end + + it "samples an exclusive range with n > 1" do + values = (1...10).sample(5) + values.size.should eq(5) + values.uniq.size.should eq(5) + values.all? { |value| 1 <= value <= 9 }.should be_true + end + + it "samples an inclusive range with n > 16" do + values = (1..1000).sample(100) + values.size.should eq(100) + values.uniq.size.should eq(100) + values.all? { |value| 1 <= value <= 1000 }.should be_true + end + + it "samples an inclusive range with n equal to or bigger than the available values" do + values = (1..10).sample(20) + values.size.should eq(10) + values.uniq.size.should eq(10) + values.all? { |value| 1 <= value <= 10 }.should be_true + end + + it "raises on invalid range without n" do + expect_raises ArgumentError do + (1..0).sample + end + end + + it "raises on invalid range with n = 0" do + expect_raises ArgumentError do + (1..0).sample(0) + end + end + + it "raises on invalid range with n = 1" do + expect_raises ArgumentError do + (1..0).sample(1) + end + end + + it "raises on invalid range with n > 1" do + expect_raises ArgumentError do + (1..0).sample(10) + end + end + + it "raises on exclusive range that would underflow" do + expect_raises ArgumentError do + (1_u8...0_u8).sample(10) + end + end + end + + context "for a float range" do + it "samples an inclusive range without n" do + value = (1.0..2.0).sample + (1.0 <= value <= 2.0).should be_true + end + + it "samples an exclusive range without n" do + value = (1.0...2.0).sample + (1.0 <= value < 2.0).should be_true + end + + it "samples an inclusive range with n = 1" do + values = (1.0..2.0).sample(1) + values.size.should eq(1) + (1.0 <= values.first <= 2.0).should be_true + end + + it "samples an exclusive range with n = 1" do + values = (1.0..2.0).sample(1) + values.size.should eq(1) + (1.0 <= values.first < 2.0).should be_true + end + + it "samples an inclusive range with n > 1" do + values = (1.0..2.0).sample(10) + values.size.should eq(10) + values.all? { |value| 1.0 <= value <= 2.0 }.should be_true + end + + it "samples an exclusive range with n > 1" do + values = (1.0...2.0).sample(10) + values.size.should eq(10) + values.all? { |value| 1.0 <= value < 2.0 }.should be_true + end + + it "samples an inclusive range with n >= 1 and begin == end" do + values = (1.0..1.0).sample(3) + values.size.should eq(1) + values.first.should eq(1.0) + end + + it "samples an inclusive range with n > 16" do + values = (1.0..2.0).sample(100) + values.size.should eq(100) + values.all? { |value| 1.0 <= value <= 2.0 }.should be_true + end + + it "raises on invalid range with n = 0" do + expect_raises ArgumentError do + (1.0..0.0).sample(0) + end + end + + it "raises on invalid range with n = 1" do + expect_raises ArgumentError do + (1.0..0.0).sample(1) + end + end + + it "raises on invalid range with n > 1" do + expect_raises ArgumentError do + (1.0..0.0).sample(10) + end + end + end end describe "#step" do diff --git a/src/range.cr b/src/range.cr index 33a88987aaa2..bb1aaaa3982d 100644 --- a/src/range.cr +++ b/src/range.cr @@ -399,12 +399,81 @@ struct Range(B, E) raise ArgumentError.new("Can't sample an open range") end - return super unless n == 1 + if n < 0 + raise ArgumentError.new "Can't sample negative number of elements" + end + + # For a range of integers we can do much better + {% if B < Int && E < Int %} + min = self.begin + max = self.end + + if exclusive? ? max <= min : max < min + raise ArgumentError.new "Invalid range for rand: #{self}" + end + + max -= 1 if exclusive? + + available = max - min + 1 + + # When a big chunk of elements is going to be needed, it's + # faster to just traverse the entire range than hitting + # a lot of duplicates because or random. + if n >= available // 4 + return super + end + + possible = Math.min(n, available) + + # If we must return all values in the range... + if possible == available + result = Array(B).new(possible) { |i| min + i } + result.shuffle!(random) + return result + end + + range_sample(n, random) + {% elsif B < Float && E < Float %} + min = self.begin + max = self.end - if empty? - [] of B + if exclusive? ? max <= min : max < min + raise ArgumentError.new "Invalid range for rand: #{self}" + end + + if min == max + return [min] + end + + range_sample(n, random) + {% else %} + case n + when 0 + [] of B + when 1 + [sample(random)] + else + super + end + {% end %} + end + + private def range_sample(n, random) + if n <= 16 + # For a small requested amount doing a linear lookup is faster + result = Array(B).new(n) + until result.size == n + value = sample(random) + result << value unless result.includes?(value) + end + result else - [sample(random)] + # Otherwise using a Set is faster + result = Set(B).new(n) + until result.size == n + result << sample(random) + end + result.to_a end end From 0fd43b9188abecc20704d66d72a41dcf983f29a6 Mon Sep 17 00:00:00 2001 From: Dmitri Goutnik Date: Mon, 24 Oct 2022 04:48:20 -0500 Subject: [PATCH 0080/1551] Drop FreeBSD 11 compatibility code (#12612) FreeBSD 11 went EOL on September 30, 2021. --- src/lib_c/x86_64-freebsd/c/dirent.cr | 18 ++++---------- src/lib_c/x86_64-freebsd/c/sys/stat.cr | 31 ++++++------------------- src/lib_c/x86_64-freebsd/c/sys/types.cr | 12 ++-------- 3 files changed, 14 insertions(+), 47 deletions(-) diff --git a/src/lib_c/x86_64-freebsd/c/dirent.cr b/src/lib_c/x86_64-freebsd/c/dirent.cr index ff73c04b8490..170e31d08917 100644 --- a/src/lib_c/x86_64-freebsd/c/dirent.cr +++ b/src/lib_c/x86_64-freebsd/c/dirent.cr @@ -8,21 +8,13 @@ lib LibC DT_LNK = 10 struct Dirent - {% if flag?(:freebsd11) %} - d_fileno : UInt - {% else %} - d_fileno : ULong - d_off : ULong - {% end %} + d_fileno : ULong + d_off : ULong d_reclen : UShort d_type : UChar - {% if flag?(:freebsd11) %} - d_namlen : UChar - {% else %} - d_pad0 : UChar - d_namlen : UShort - d_pad1 : UShort - {% end %} + d_pad0 : UChar + d_namlen : UShort + d_pad1 : UShort d_name : StaticArray(Char, 256) end diff --git a/src/lib_c/x86_64-freebsd/c/sys/stat.cr b/src/lib_c/x86_64-freebsd/c/sys/stat.cr index 55d51f4e4358..32334987cdb0 100644 --- a/src/lib_c/x86_64-freebsd/c/sys/stat.cr +++ b/src/lib_c/x86_64-freebsd/c/sys/stat.cr @@ -31,40 +31,23 @@ lib LibC struct Stat st_dev : DevT st_ino : InoT - {% if flag?(:freebsd11) %} - st_mode : ModeT - st_nlink : NlinkT - {% else %} - st_nlink : NlinkT - st_mode : ModeT - st_pad0 : UShort - {% end %} + st_nlink : NlinkT + st_mode : ModeT + st_pad0 : UShort st_uid : UidT st_gid : GidT - {% if !flag?(:freebsd11) %} - st_pad1 : UInt - {% end %} + st_pad1 : UInt st_rdev : DevT st_atim : Timespec st_mtim : Timespec st_ctim : Timespec - {% if !flag?(:freebsd11) %} - st_birthtim : Timespec - {% end %} + st_birthtim : Timespec st_size : OffT st_blocks : BlkcntT st_blksize : BlksizeT st_flags : FflagsT - {% if flag?(:freebsd11) %} - st_gen : UInt - st_lspare : Int - st_birthtim : Timespec - __reserved_17 : UInt - __reserved_18 : UInt - {% else %} - st_gen : ULong - st_spare : StaticArray(ULong, 10) - {% end %} + st_gen : ULong + st_spare : StaticArray(ULong, 10) end fun chmod(x0 : Char*, x1 : ModeT) : Int diff --git a/src/lib_c/x86_64-freebsd/c/sys/types.cr b/src/lib_c/x86_64-freebsd/c/sys/types.cr index a3743b2a4552..63126a905d93 100644 --- a/src/lib_c/x86_64-freebsd/c/sys/types.cr +++ b/src/lib_c/x86_64-freebsd/c/sys/types.cr @@ -9,17 +9,9 @@ lib LibC alias DevT = UInt alias GidT = UInt alias IdT = Long - {% if flag?(:freebsd11) %} - alias InoT = UInt - {% else %} - alias InoT = ULong - {% end %} + alias InoT = ULong alias ModeT = UShort - {% if flag?(:freebsd11) %} - alias NlinkT = UShort - {% else %} - alias NlinkT = ULong - {% end %} + alias NlinkT = ULong alias OffT = Long alias PidT = Int type PthreadAttrT = Void* From 0b10d133db1ad1d9f607c6d4c9fd384517f4c7af Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Mon, 24 Oct 2022 17:48:36 +0800 Subject: [PATCH 0081/1551] Style: Remove redundant begin blocks (#12638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/spec/tap_formatter_spec.cr | 8 ++-- src/compiler/crystal/macros/macros.cr | 22 ++++----- src/compiler/crystal/semantic/call.cr | 24 +++++----- src/compiler/crystal/tools/formatter.cr | 48 +++++++++---------- src/compiler/crystal/tools/init.cr | 26 +++++----- .../crystal/tools/playground/server.cr | 34 ++++++------- src/file_utils.cr | 6 +-- src/http/server/handlers/error_handler.cr | 30 ++++++------ src/json/token.cr | 16 +++---- 9 files changed, 96 insertions(+), 118 deletions(-) diff --git a/spec/std/spec/tap_formatter_spec.cr b/spec/std/spec/tap_formatter_spec.cr index 8a1b28bb29d6..daf8e04a08c8 100644 --- a/spec/std/spec/tap_formatter_spec.cr +++ b/spec/std/spec/tap_formatter_spec.cr @@ -9,11 +9,9 @@ private def build_report end private def exception_with_backtrace(msg) - begin - raise Exception.new(msg) - rescue e - e - end + raise Exception.new(msg) +rescue e + e end describe Spec::TAPFormatter do diff --git a/src/compiler/crystal/macros/macros.cr b/src/compiler/crystal/macros/macros.cr index e088f477f803..17388305f8be 100644 --- a/src/compiler/crystal/macros/macros.cr +++ b/src/compiler/crystal/macros/macros.cr @@ -35,18 +35,16 @@ class Crystal::Program end def parse_macro_source(generated_source, macro_expansion_pragmas, the_macro, node, vars, current_def = nil, inside_type = false, inside_exp = false, visibility : Visibility = :public) - begin - parser = @program.new_parser(generated_source, var_scopes: [vars.dup]) - parser.filename = VirtualFile.new(the_macro, generated_source, node.location) - parser.macro_expansion_pragmas = macro_expansion_pragmas - parser.visibility = visibility - parser.def_nest = 1 if current_def && !current_def.is_a?(External) - parser.fun_nest = 1 if current_def && current_def.is_a?(External) - parser.type_nest = 1 if inside_type - parser.wants_doc = @program.wants_doc? - generated_node = yield parser - normalize(generated_node, inside_exp: inside_exp, current_def: current_def) - end + parser = @program.new_parser(generated_source, var_scopes: [vars.dup]) + parser.filename = VirtualFile.new(the_macro, generated_source, node.location) + parser.macro_expansion_pragmas = macro_expansion_pragmas + parser.visibility = visibility + parser.def_nest = 1 if current_def && !current_def.is_a?(External) + parser.fun_nest = 1 if current_def && current_def.is_a?(External) + parser.type_nest = 1 if inside_type + parser.wants_doc = @program.wants_doc? + generated_node = yield parser + normalize(generated_node, inside_exp: inside_exp, current_def: current_def) end record MacroRunResult, stdout : String, stderr : String, status : Process::Status diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index ba4b97f82485..d2536e2dfc44 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -1101,21 +1101,19 @@ class Crystal::Call end def bubbling_exception - begin - yield - rescue ex : Crystal::CodeError - if obj = @obj - if name == "initialize" - # Avoid putting 'initialize' in the error trace - # because it's most likely that this is happening - # inside a generated 'new' method - ::raise ex - else - raise "instantiating '#{obj.type}##{name}(#{args.map(&.type).join ", "})'", ex - end + yield + rescue ex : Crystal::CodeError + if obj = @obj + if name == "initialize" + # Avoid putting 'initialize' in the error trace + # because it's most likely that this is happening + # inside a generated 'new' method + ::raise ex else - raise "instantiating '#{name}(#{args.map(&.type).join ", "})'", ex + raise "instantiating '#{obj.type}##{name}(#{args.map(&.type).join ", "})'", ex end + else + raise "instantiating '#{name}(#{args.map(&.type).join ", "})'", ex end end diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 2894031251ef..e0da41225c91 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1738,37 +1738,35 @@ module Crystal end private def format_macro_literal_only(node, source, macro_node_line) - begin - # Only format the macro contents if it's valid Crystal code - Parser.new(source).parse + # Only format the macro contents if it's valid Crystal code + Parser.new(source).parse - formatter, value = subformat(source) + formatter, value = subformat(source) - # The formatted contents might have heredocs for which we must preserve - # trailing spaces, so here we copy those from the formatter we used - # to format the contents to this formatter (we add one because we insert - # a newline before the contents). - formatter.no_rstrip_lines.each do |line| - @no_rstrip_lines.add(macro_node_line + line + 1) - end - - write_line - write value + # The formatted contents might have heredocs for which we must preserve + # trailing spaces, so here we copy those from the formatter we used + # to format the contents to this formatter (we add one because we insert + # a newline before the contents). + formatter.no_rstrip_lines.each do |line| + @no_rstrip_lines.add(macro_node_line + line + 1) + end - next_macro_token + write_line + write value - until @token.type.macro_end? - next_macro_token - end + next_macro_token - skip_space_or_newline - check :MACRO_END - write_indent - write "end" - next_token - rescue ex : Crystal::SyntaxException - format_macro_body node + until @token.type.macro_end? + next_macro_token end + + skip_space_or_newline + check :MACRO_END + write_indent + write "end" + next_token + rescue ex : Crystal::SyntaxException + format_macro_body node end def format_macro_body(node) diff --git a/src/compiler/crystal/tools/init.cr b/src/compiler/crystal/tools/init.cr index 08c16de4f3ee..5ea15c66484f 100644 --- a/src/compiler/crystal/tools/init.cr +++ b/src/compiler/crystal/tools/init.cr @@ -21,21 +21,19 @@ module Crystal end def self.run(args) - begin - config = parse_args(args) - InitProject.new(config).run - rescue ex : Init::FilesConflictError - STDERR.puts "Cannot initialize Crystal project, the following files would be overwritten:" - ex.conflicting_files.each do |path| - STDERR.puts " #{"file".colorize(:red)} #{path} #{"already exist".colorize(:red)}" - end - STDERR.puts "You can use --force to overwrite those files," - STDERR.puts "or --skip-existing to skip existing files and generate the others." - exit 1 - rescue ex : Init::Error - STDERR.puts "Cannot initialize Crystal project: #{ex}" - exit 1 + config = parse_args(args) + InitProject.new(config).run + rescue ex : Init::FilesConflictError + STDERR.puts "Cannot initialize Crystal project, the following files would be overwritten:" + ex.conflicting_files.each do |path| + STDERR.puts " #{"file".colorize(:red)} #{path} #{"already exist".colorize(:red)}" end + STDERR.puts "You can use --force to overwrite those files," + STDERR.puts "or --skip-existing to skip existing files and generate the others." + exit 1 + rescue ex : Init::Error + STDERR.puts "Cannot initialize Crystal project: #{ex}" + exit 1 end def self.parse_args(args) diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr index e0f7482d4e92..c4f316ca53b7 100644 --- a/src/compiler/crystal/tools/playground/server.cr +++ b/src/compiler/crystal/tools/playground/server.cr @@ -119,11 +119,9 @@ module Crystal::Playground end def send(message) - begin - @ws.send(message) - rescue ex : IO::Error - Log.warn { "Unable to send message (session=#{@session_key})." } - end + @ws.send(message) + rescue ex : IO::Error + Log.warn { "Unable to send message (session=#{@session_key})." } end def send_with_json_builder @@ -226,21 +224,19 @@ module Crystal::Playground end def content - begin - extname = File.extname(@filename) - content = if extname == ".cr" - crystal_source_to_markdown(@filename) - else - File.read(@filename) - end - - if extname == ".md" || extname == ".cr" - content = Markd.to_html(content) - end - content - rescue e - e.message || "Error: generating content for #{@filename}" + extname = File.extname(@filename) + content = if extname == ".cr" + crystal_source_to_markdown(@filename) + else + File.read(@filename) + end + + if extname == ".md" || extname == ".cr" + content = Markd.to_html(content) end + content + rescue e + e.message || "Error: generating content for #{@filename}" end def to_s(io : IO) : Nil diff --git a/src/file_utils.cr b/src/file_utils.cr index e82e3c1e15f5..d416ed05bff7 100644 --- a/src/file_utils.cr +++ b/src/file_utils.cr @@ -425,10 +425,8 @@ module FileUtils # FileUtils.rm_rf("non_existent_file") # ``` def rm_rf(path : Path | String) : Nil - begin - rm_r(path) - rescue File::Error - end + rm_r(path) + rescue File::Error end # Deletes a list of files or directories *paths*. diff --git a/src/http/server/handlers/error_handler.cr b/src/http/server/handlers/error_handler.cr index 9468917674ee..6036b0d9536e 100644 --- a/src/http/server/handlers/error_handler.cr +++ b/src/http/server/handlers/error_handler.cr @@ -13,22 +13,20 @@ class HTTP::ErrorHandler end def call(context) : Nil - begin - call_next(context) - rescue ex : HTTP::Server::ClientError - @log.debug(exception: ex.cause) { ex.message } - rescue ex : Exception - @log.error(exception: ex) { "Unhandled exception" } - unless context.response.closed? || context.response.wrote_headers? - if @verbose - context.response.reset - context.response.status = :internal_server_error - context.response.content_type = "text/plain" - context.response.print("ERROR: ") - context.response.puts(ex.inspect_with_backtrace) - else - context.response.respond_with_status(:internal_server_error) - end + call_next(context) + rescue ex : HTTP::Server::ClientError + @log.debug(exception: ex.cause) { ex.message } + rescue ex : Exception + @log.error(exception: ex) { "Unhandled exception" } + unless context.response.closed? || context.response.wrote_headers? + if @verbose + context.response.reset + context.response.status = :internal_server_error + context.response.content_type = "text/plain" + context.response.print("ERROR: ") + context.response.puts(ex.inspect_with_backtrace) + else + context.response.respond_with_status(:internal_server_error) end end end diff --git a/src/json/token.cr b/src/json/token.cr index 95394f2c7f3d..436709aec233 100644 --- a/src/json/token.cr +++ b/src/json/token.cr @@ -19,19 +19,15 @@ class JSON::Token property string_value : String def int_value : Int64 - begin - raw_value.to_i64 - rescue exc : ArgumentError - raise ParseException.new(exc.message, line_number, column_number) - end + raw_value.to_i64 + rescue exc : ArgumentError + raise ParseException.new(exc.message, line_number, column_number) end def float_value : Float64 - begin - raw_value.to_f64 - rescue exc : ArgumentError - raise ParseException.new(exc.message, line_number, column_number) - end + raw_value.to_f64 + rescue exc : ArgumentError + raise ParseException.new(exc.message, line_number, column_number) end property line_number : Int32 From c60bccac221d4e14af75cc256026caec8b3f5cda Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 24 Oct 2022 13:44:36 -0300 Subject: [PATCH 0082/1551] [Experimental] Compiler: try to solve string interpolation exps at compile time (#12524) Co-authored-by: Matthew Berry --- .../normalize/string_interpolation_spec.cr | 40 ++++++++++++++++ .../crystal/semantic/cleanup_transformer.cr | 48 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/spec/compiler/normalize/string_interpolation_spec.cr b/spec/compiler/normalize/string_interpolation_spec.cr index 2e596a0c910c..f92e6c2a200f 100644 --- a/spec/compiler/normalize/string_interpolation_spec.cr +++ b/spec/compiler/normalize/string_interpolation_spec.cr @@ -12,4 +12,44 @@ describe "Normalize: string interpolation" do it "normalizes heredoc" do assert_normalize "<<-FOO\nhello\nFOO", %("hello") end + + it "replaces string constant" do + result = semantic(%( + def String.interpolation(*args); ""; end + + OBJ = "world" + + "hello \#{OBJ}" + )) + node = result.node.as(Expressions).last + string = node.should be_a(StringLiteral) + string.value.should eq("hello world") + end + + it "replaces string constant that results from macro expansion" do + result = semantic(%( + def String.interpolation(*args); ""; end + + OBJ = {% if 1 + 1 == 2 %} "world" {% else %} "bug" {% end %} + + "hello \#{OBJ}" + )) + node = result.node.as(Expressions).last + string = node.should be_a(StringLiteral) + string.value.should eq("hello world") + end + + it "replaces through multiple levels" do + result = semantic(%( + def String.interpolation(*args); ""; end + + OBJ1 = "ld" + OBJ2 = "wor\#{OBJ1}" + + "hello \#{OBJ2}" + )) + node = result.node.as(Expressions).last + string = node.should be_a(StringLiteral) + string.value.should eq("hello world") + end end diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 3c4d0cd81e2a..167efe45d4d7 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -249,6 +249,54 @@ module Crystal nil end + def transform(node : StringInterpolation) + # See if we can solve all the pieces to string literals. + # If that's the case, we can replace the entire interpolation + # with a single string literal. + pieces = node.expressions.dup + solve_string_interpolation_expressions(pieces) + + if pieces.all?(StringLiteral) + string = pieces.join(&.as(StringLiteral).value) + string_literal = StringLiteral.new(string).at(node) + string_literal.type = @program.string + return string_literal + end + + if expanded = node.expanded + return expanded.transform(self) + end + node + end + + private def solve_string_interpolation_expressions(pieces : Array(ASTNode)) + pieces.each_with_index do |piece, i| + replacement = solve_string_interpolation_expression(piece) + next unless replacement + + pieces[i] = replacement + end + end + + private def solve_string_interpolation_expression(piece : ASTNode) : StringLiteral? + if piece.is_a?(ExpandableNode) + if expanded = piece.expanded + return solve_string_interpolation_expression(expanded) + end + end + + case piece + when Path + if target_const = piece.target_const + return solve_string_interpolation_expression(target_const.value) + end + when StringLiteral + return piece + end + + nil + end + def transform(node : ExpandableNode) if expanded = node.expanded return expanded.transform(self) From ccb006007ee99b7227d7dec507ec1a262060a04f Mon Sep 17 00:00:00 2001 From: maiha Date: Thu, 27 Oct 2022 18:34:47 +0900 Subject: [PATCH 0083/1551] examples: fix (2022-10) (#12665) --- src/digest/digest.cr | 2 +- src/kernel.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/digest/digest.cr b/src/digest/digest.cr index 62ca3878c8c8..765f940ca47b 100644 --- a/src/digest/digest.cr +++ b/src/digest/digest.cr @@ -37,7 +37,7 @@ abstract class Digest # ctx.update "f" # ctx.update "oo" # end - # digest.hexfinal # => "acbd18db4cc2f85cedef654fccc4a4d8" + # digest.hexstring # => "acbd18db4cc2f85cedef654fccc4a4d8" # ``` def digest(& : self ->) : Bytes context = new diff --git a/src/kernel.cr b/src/kernel.cr index 6e90457578a1..c8c511cecb8e 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -298,7 +298,7 @@ end # sprintf "%b", -123 # => "-1111011" # sprintf "%#b", 0 # => "0" # sprintf "% b", 123 # => " 1111011" -# sprintf "%+ b", 123 # => "+ 1111011" +# sprintf "%+ b", 123 # => "+1111011" # sprintf "% b", -123 # => "-1111011" # sprintf "%+ b", -123 # => "-1111011" # sprintf "%#b", 123 # => "0b1111011" From 36059d3ce9cb42e7c7e2a4cd09bf8612770a5041 Mon Sep 17 00:00:00 2001 From: Dmitri Goutnik Date: Thu, 27 Oct 2022 12:33:16 -0500 Subject: [PATCH 0084/1551] Update FreeBSD LibC types (#12651) --- src/lib_c/x86_64-freebsd/c/dirent.cr | 4 ++-- src/lib_c/x86_64-freebsd/c/sys/types.cr | 4 ++-- src/lib_c/x86_64-freebsd/c/sys/un.cr | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib_c/x86_64-freebsd/c/dirent.cr b/src/lib_c/x86_64-freebsd/c/dirent.cr index 170e31d08917..426f68954cf1 100644 --- a/src/lib_c/x86_64-freebsd/c/dirent.cr +++ b/src/lib_c/x86_64-freebsd/c/dirent.cr @@ -8,8 +8,8 @@ lib LibC DT_LNK = 10 struct Dirent - d_fileno : ULong - d_off : ULong + d_fileno : InoT + d_off : OffT d_reclen : UShort d_type : UChar d_pad0 : UChar diff --git a/src/lib_c/x86_64-freebsd/c/sys/types.cr b/src/lib_c/x86_64-freebsd/c/sys/types.cr index 63126a905d93..7e0ffad4cd28 100644 --- a/src/lib_c/x86_64-freebsd/c/sys/types.cr +++ b/src/lib_c/x86_64-freebsd/c/sys/types.cr @@ -3,10 +3,10 @@ require "../stdint" lib LibC alias BlkcntT = Long - alias BlksizeT = UInt + alias BlksizeT = Int alias ClockT = Int alias ClockidT = Int - alias DevT = UInt + alias DevT = ULong alias GidT = UInt alias IdT = Long alias InoT = ULong diff --git a/src/lib_c/x86_64-freebsd/c/sys/un.cr b/src/lib_c/x86_64-freebsd/c/sys/un.cr index 8d64bd7e2f6e..c2fd2e747ed3 100644 --- a/src/lib_c/x86_64-freebsd/c/sys/un.cr +++ b/src/lib_c/x86_64-freebsd/c/sys/un.cr @@ -2,7 +2,7 @@ require "./socket" lib LibC struct SockaddrUn - sun_len : Char + sun_len : UChar sun_family : SaFamilyT sun_path : StaticArray(Char, 104) end From 5f5b468f5e224fafa637adaad8007b162e080b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 27 Oct 2022 19:33:26 +0200 Subject: [PATCH 0085/1551] Remove unused `Program#cache_dir` property (#12669) --- src/compiler/crystal/compiler.cr | 1 - src/compiler/crystal/program.cr | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 0d99635497a7..2fec35411223 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -194,7 +194,6 @@ module Crystal @program = program = Program.new program.compiler = self program.filename = sources.first.filename - program.cache_dir = CacheDir.instance.directory_for(sources) program.codegen_target = codegen_target program.target_machine = target_machine program.flags << "release" if release? diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 55673ecc38f8..f2b75022fa61 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -84,9 +84,6 @@ module Crystal # This pool is passed to the parser, macro expander, etc. getter string_pool = StringPool.new - # The cache directory where temporary files are placed. - setter cache_dir : String? - # Here we store constants, in the # order that they are used. They will be initialized as soon # as the program starts, before the main code. From 27f9a3050d9d84c997c9f4f81328daf231095651 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sat, 29 Oct 2022 16:05:35 +0200 Subject: [PATCH 0086/1551] Lint: Fix variable name casing (#12674) --- samples/havlak.cr | 4 +- samples/sdl/raytracer.cr | 6 +-- scripts/github-changelog.cr | 9 ++-- .../crystal/tools/playground/server.cr | 44 ++++++++++--------- src/crystal/digest/md5.cr | 14 +++--- 5 files changed, 41 insertions(+), 36 deletions(-) diff --git a/samples/havlak.cr b/samples/havlak.cr index 32f788e51ceb..5d01b3cdaf1d 100644 --- a/samples/havlak.cr +++ b/samples/havlak.cr @@ -267,8 +267,8 @@ class HavlakLoopFinder node_w = nodes[w].bb if node_w - node_w.in_edges.each do |nodeV| - v = number[nodeV] + node_w.in_edges.each do |node_v| + v = number[node_v] if v != UNVISITED if ancestor?(w, v, last) back_preds[w] << v diff --git a/samples/sdl/raytracer.cr b/samples/sdl/raytracer.cr index 8ca0d7ac8952..f98e576be54b 100644 --- a/samples/sdl/raytracer.cr +++ b/samples/sdl/raytracer.cr @@ -122,11 +122,11 @@ def trace(ray, scene, depth) end reflection_ratio = obj.reflection - normE5 = normal * 1.0e-5 + norm_e5 = normal * 1.0e-5 scene.lights.each do |lgt| light_direction = (lgt.position - point_of_hit).normalize - r = Ray.new(point_of_hit + normE5, light_direction) + r = Ray.new(point_of_hit + norm_e5, light_direction) # go through the scene check whether we're blocked from the lights blocked = scene.objects.any? &.intersects? r @@ -146,7 +146,7 @@ def trace(ray, scene, depth) # compute reflection if depth < MAX_DEPTH && reflection_ratio > 0 reflection_direction = ray.dir - normal * 2.0 * dot_normal_ray - reflection = trace(Ray.new(point_of_hit + normE5, reflection_direction), scene, depth + 1) + reflection = trace(Ray.new(point_of_hit + norm_e5, reflection_direction), scene, depth + 1) result += reflection * fresneleffect end diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index fa46a03fe290..292ede110d74 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -77,13 +77,16 @@ end record PullRequest, number : Int32, title : String, - mergedAt : Time?, + merged_at : Time?, permalink : String, author : String?, labels : Array(String) do include JSON::Serializable include Comparable(self) + @[JSON::Field(key: "mergedAt")] + @merged_at : Time? + @[JSON::Field(root: "login")] @author : String? @@ -117,7 +120,7 @@ record PullRequest, labels.includes?("security") ? 0 : 1, labels.includes?("breaking-change") ? 0 : 1, labels.includes?("kind:bug") ? 0 : 1, - mergedAt || Time.unix(0), + merged_at || Time.unix(0), } end end @@ -141,7 +144,7 @@ array = parser.on_key! "data" do end changelog = File.read("CHANGELOG.md") -array.select! { |pr| pr.mergedAt && !changelog.index(pr.permalink) } +array.select! { |pr| pr.merged_at && !changelog.index(pr.permalink) } sections = array.group_by { |pr| pr.labels.each do |label| case label diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr index c4f316ca53b7..c163afc35208 100644 --- a/src/compiler/crystal/tools/playground/server.cr +++ b/src/compiler/crystal/tools/playground/server.cr @@ -390,6 +390,22 @@ module Crystal::Playground class EnvironmentHandler include HTTP::Handler + DEFAULT_SOURCE = <<-CR + def find_string(text, word) + (0..text.size-word.size).each do |i| + { i, text[i..i+word.size-1] } + if text[i..i+word.size-1] == word + return i + end + end + + nil + end + + find_string "Crystal is awesome!", "awesome" + find_string "Crystal is awesome!", "not sure" + CR + def initialize(@server : Playground::Server) end @@ -397,31 +413,17 @@ module Crystal::Playground case {context.request.method, context.request.resource} when {"GET", "/environment.js"} context.response.headers["Content-Type"] = "application/javascript" - context.response.puts %(Environment = {}) - - context.response.puts %(Environment.version = #{Crystal::Config.description.inspect}) - - defaultSource = <<-CR - def find_string(text, word) - (0..text.size-word.size).each do |i| - { i, text[i..i+word.size-1] } - if text[i..i+word.size-1] == word - return i - end - end - - nil - end - find_string "Crystal is awesome!", "awesome" - find_string "Crystal is awesome!", "not sure" - CR - context.response.puts "Environment.defaultSource = #{defaultSource.inspect}" + context.response.puts <<-JS + Environment = {} + Environment.version = #{Crystal::Config.description.inspect} + Environment.defaultSource = #{DEFAULT_SOURCE.inspect} + JS if source = @server.source - context.response.puts "Environment.source = #{source.code.inspect};" + context.response.puts "Environment.source = #{source.code.inspect}" else - context.response.puts "Environment.source = null;" + context.response.puts "Environment.source = null" end else call_next(context) diff --git a/src/crystal/digest/md5.cr b/src/crystal/digest/md5.cr index 79099db81e9f..90aa8d89baf9 100644 --- a/src/crystal/digest/md5.cr +++ b/src/crystal/digest/md5.cr @@ -30,22 +30,22 @@ class Crystal::Digest::MD5 < ::Digest update(data.to_unsafe, data.bytesize.to_u32) end - private def update(inBuf, inLen) + private def update(in_buf, in_len) tmp_in = uninitialized UInt32[16] # compute number of bytes mod 64 mdi = (@i[0] >> 3) & 0x3F # update number of bits - @i[1] &+= 1 if (@i[0] &+ (inLen << 3)) < @i[0] - @i[0] &+= (inLen << 3) - @i[1] &+= (inLen >> 29) + @i[1] &+= 1 if (@i[0] &+ (in_len << 3)) < @i[0] + @i[0] &+= (in_len << 3) + @i[1] &+= (in_len >> 29) - inLen.times do + in_len.times do # add new character to buffer, increment mdi - @in[mdi] = inBuf.value + @in[mdi] = in_buf.value mdi += 1 - inBuf += 1 + in_buf += 1 # transform if necessary if mdi == 0x40 From fb11b54f6428229d0ddd12ce8b082679dd96a43f Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sat, 29 Oct 2022 16:05:44 +0200 Subject: [PATCH 0087/1551] Lint: Remove comparisons with boolean literals (#12673) --- src/compiler/crystal/semantic/call_error.cr | 2 +- src/fiber.cr | 2 +- src/iterator.cr | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index 1abe22db6e58..ee54c34edc04 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -100,7 +100,7 @@ class Crystal::Call # If we made a lookup without the special rule for literals, # and we have literals in the call, try again with that special rule. - if with_autocast == false && (args.any?(&.supports_autocast? number_autocast) || + if !with_autocast && (args.any?(&.supports_autocast? number_autocast) || named_args.try &.any? &.value.supports_autocast? number_autocast) ::raise RetryLookupWithLiterals.new end diff --git a/src/fiber.cr b/src/fiber.cr index 89aaee7987ce..5b3f08b5c576 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -193,7 +193,7 @@ class Fiber # The fiber's proc has terminated, and the fiber is now considered dead. The # fiber is impossible to resume, ever. def dead? : Bool - @alive == false + !@alive end # Immediately resumes execution of this fiber. diff --git a/src/iterator.cr b/src/iterator.cr index 339cdb0351ed..91f872f3f16a 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -1065,7 +1065,7 @@ module Iterator(T) def next while true value = wrapped_next - return value if @returned_false == true + return value if @returned_false unless @func.call(value) @returned_false = true return value @@ -1209,7 +1209,7 @@ module Iterator(T) end def next - return stop if @returned_false == true + return stop if @returned_false value = wrapped_next if @func.call(value) value From 157d78827c47dcb66cedbc22308fc1b12fb28075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 29 Oct 2022 16:06:01 +0200 Subject: [PATCH 0088/1551] [CI] Update GHA actions (#12501) --- .github/workflows/aarch64.yml | 24 ++++++++++++------------ .github/workflows/linux.yml | 14 +++++++------- .github/workflows/macos.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/smoke.yml | 2 +- .github/workflows/win.yml | 32 ++++++++++++++++---------------- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index c441eea50f84..1e8afae7672e 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -8,13 +8,13 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Build Crystal uses: docker://jhass/crystal:1.0.0-alpine-build with: args: make crystal - name: Upload Crystal executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: crystal-aarch64-musl path: | @@ -26,9 +26,9 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download Crystal executable - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: crystal-aarch64-musl - name: Mark downloaded compiler as executable @@ -43,9 +43,9 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download Crystal executable - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: crystal-aarch64-musl - name: Mark downloaded compiler as executable @@ -59,13 +59,13 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Build Crystal uses: docker://jhass/crystal:1.0.0-build with: args: make crystal - name: Upload Crystal executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: crystal-aarch64-gnu path: | @@ -77,9 +77,9 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download Crystal executable - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: crystal-aarch64-gnu - name: Mark downloaded compiler as executable @@ -94,9 +94,9 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download Crystal executable - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: crystal-aarch64-gnu - name: Mark downloaded compiler as executable diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a2308db16125..e9dc5f9fc38f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -24,7 +24,7 @@ jobs: flags: -Dwithout_ffi steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Prepare System run: bin/ci prepare_system @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Prepare System run: bin/ci prepare_system @@ -71,7 +71,7 @@ jobs: name: "Test LLVM ${{ matrix.llvm_version }} (${{ matrix.base_image }})" steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Prepare System run: bin/ci prepare_system @@ -92,7 +92,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Prepare System run: bin/ci prepare_system @@ -117,7 +117,7 @@ jobs: - x86_64 steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Prepare System run: bin/ci prepare_system @@ -141,7 +141,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Prepare System run: bin/ci prepare_system @@ -160,7 +160,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Prepare System run: bin/ci prepare_system diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index a89fe2e44e17..1b607e96441b 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -11,7 +11,7 @@ jobs: runs-on: macos-11 steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - uses: cachix/install-nix-action@v17 with: diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index ac59bd448f9a..7ab7d4bfe351 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -9,7 +9,7 @@ jobs: container: crystallang/crystal:1.6.1-alpine steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Uninstall openssl 1.1 run: apk del openssl-dev - name: Upgrade alpine-keys @@ -26,7 +26,7 @@ jobs: container: crystallang/crystal:1.6.1-alpine steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install openssl 1.1.1 run: apk add "openssl-dev=~1.1.1" - name: Check LibSSL version @@ -39,7 +39,7 @@ jobs: container: crystallang/crystal:1.6.1-alpine steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Uninstall openssl run: apk del openssl-dev openssl-libs-static - name: Upgrade alpine-keys diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index cce52e7c4faa..0e65788721e3 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -55,7 +55,7 @@ jobs: steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Build fresh compiler run: bin/ci with_build_env make diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 1b425e545135..785e774fbc10 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -8,7 +8,7 @@ jobs: container: crystallang/crystal:1.6.1-build steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Build C extensions run: | @@ -18,7 +18,7 @@ jobs: LLVM_TARGETS=X86 bin/crystal build --cross-compile --target x86_64-pc-windows-msvc src/compiler/crystal.cr -Dwithout_playground -Dwithout_iconv -Dwithout_interpreter - name: Upload Crystal object file - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: objs path: crystal.obj @@ -34,11 +34,11 @@ jobs: uses: ilammy/msvc-dev-cmd@ed94116c4d30d2091601b81f339a2eaa1c2ba0a6 - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Cache libraries id: cache-libs - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | # openssl and llvm take much longer to build so they are cached separately libs/pcre.lib @@ -52,14 +52,14 @@ jobs: key: win-libs-${{ hashFiles('.github/workflows/win.yml') }}-msvc-${{ env.VSCMD_VER }} - name: Download libgc if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: ivmai/bdwgc ref: v8.2.0 path: bdwgc - name: Download libatomic_ops if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: ivmai/libatomic_ops ref: v7.6.10 @@ -85,7 +85,7 @@ jobs: cmake --build . --config Release - name: Download libiconv if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: pffang/libiconv-for-Windows ref: 9b7aba8da6e125ef33912fa4412779279f204003 # master @ 2021-08-24 @@ -113,7 +113,7 @@ jobs: MSBuild.exe /p:Platform=x64 /p:Configuration=ReleaseStatic libiconv.vcxproj - name: Download libffi if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: winlibs/libffi ref: libffi-3.3 @@ -157,7 +157,7 @@ jobs: cmake --build . --config Release - name: Download mpir if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: BrianGladman/mpir ref: d9c9a842be6475bef74324f367ce2c5a78c55d06 # master @ 2021-10-14 @@ -199,7 +199,7 @@ jobs: cmake --build . --config Release - name: Download libxml2 if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: GNOME/libxml2 ref: a230b728f1289dd24c1666856ac4fb55579c6dfb # master @ 2020-05-04 @@ -240,7 +240,7 @@ jobs: - name: Cache OpenSSL id: cache-openssl - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | libs/crypto.lib @@ -275,7 +275,7 @@ jobs: - name: Cache LLVM id: cache-llvm - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: llvm key: llvm-libs-13.0.0-msvc-${{ env.VSCMD_VER }} @@ -311,7 +311,7 @@ jobs: echo "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" >> ${env:GITHUB_ENV} - name: Download Crystal object file - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: objs - name: Build LLVM extensions @@ -328,14 +328,14 @@ jobs: make -f Makefile.win -B - name: Download shards release - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: crystal-lang/shards ref: v0.17.0 path: shards - name: Download molinillo release - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: crystal-lang/crystal-molinillo ref: v0.2.0 @@ -356,7 +356,7 @@ jobs: rm crystal/src/llvm/ext/llvm_ext.obj - name: Upload Crystal binaries - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: crystal path: crystal From e66fe1d7259dc6efd3aace139e76868537e6e9b5 Mon Sep 17 00:00:00 2001 From: TheEEs Date: Sun, 30 Oct 2022 01:29:46 +0700 Subject: [PATCH 0089/1551] Fix documentation for Pointer#move_to (#12677) --- src/pointer.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pointer.cr b/src/pointer.cr index b6d9146bbb84..11ee89c0101c 100644 --- a/src/pointer.cr +++ b/src/pointer.cr @@ -215,8 +215,8 @@ struct Pointer(T) self end - # Copies *count* elements from `self` into *source*. - # *source* and `self` may overlap; the copy is always done in a non-destructive manner. + # Copies *count* elements from `self` into *target*. + # *target* and `self` may overlap; the copy is always done in a non-destructive manner. # # ``` # ptr1 = Pointer.malloc(4) { |i| i + 1 } # ptr1 -> [1, 2, 3, 4] From 56dc735e352d41cd2c3f0f76b3ce3f2ae07d04dd Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sat, 29 Oct 2022 15:30:02 -0300 Subject: [PATCH 0090/1551] Allow the EventLoop implementation to be detected at runtime (#12656) --- src/crystal/scheduler.cr | 8 ++- src/crystal/system/event_loop.cr | 29 +++++---- src/crystal/system/unix/event_libevent.cr | 30 ++++------ .../system/unix/event_loop_libevent.cr | 28 ++++----- src/crystal/system/wasi/event_loop.cr | 43 +++++++------ src/crystal/system/win32/event_loop_iocp.cr | 60 +++++++++++-------- src/crystal/system/win32/socket.cr | 2 +- src/fiber.cr | 12 ++-- src/io/evented.cr | 8 +-- src/io/overlapped.cr | 10 ++-- src/kernel.cr | 2 +- 11 files changed, 127 insertions(+), 105 deletions(-) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index 59399e95e846..2344dbc5f2f7 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -12,6 +12,12 @@ require "crystal/system/thread" # Only the class methods are public and safe to use. Instance methods are # protected and must never be called directly. class Crystal::Scheduler + @event_loop = Crystal::EventLoop.create + + def self.event_loop + Thread.current.scheduler.@event_loop + end + def self.current_fiber : Fiber Thread.current.scheduler.@current end @@ -154,7 +160,7 @@ class Crystal::Scheduler end break else - Crystal::EventLoop.run_once + @event_loop.run_once end end diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index d7dc351c6ae2..056aaf7ad77f 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -1,31 +1,34 @@ -module Crystal::EventLoop +abstract class Crystal::EventLoop + # Creates an event loop instance + # def self.create : Crystal::EventLoop + # Runs the event loop. - # def self.run_once : Nil + abstract def run_once : Nil {% unless flag?(:preview_mt) %} # Reinitializes the event loop after a fork. - # def self.after_fork : Nil + abstract def after_fork : Nil {% end %} # Create a new resume event for a fiber. - # def self.create_resume_event(fiber : Fiber) : Crystal::Event + abstract def create_resume_event(fiber : Fiber) : Event # Creates a timeout_event. - # def self.create_timeout_event(fiber) : Crystal::Event + abstract def create_timeout_event(fiber : Fiber) : Event # Creates a write event for a file descriptor. - # def self.create_fd_write_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::Event + abstract def create_fd_write_event(io : IO::Evented, edge_triggered : Bool = false) : Event # Creates a read event for a file descriptor. - # def self.create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::Event -end + abstract def create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Event -struct Crystal::Event - # Frees the event. - # def free : Nil + abstract struct Event + # Frees the event. + abstract def free : Nil - # Adds a new timeout to this event. - # def add(time_span : Time::Span?) : Nil + # Adds a new timeout to this event. + abstract def add(timeout : Time::Span?) : Nil + end end {% if flag?(:wasi) %} diff --git a/src/crystal/system/unix/event_libevent.cr b/src/crystal/system/unix/event_libevent.cr index 2d9da9e1e949..62fbff94f555 100644 --- a/src/crystal/system/unix/event_libevent.cr +++ b/src/crystal/system/unix/event_libevent.cr @@ -5,7 +5,7 @@ require "./lib_event2" {% end %} # :nodoc: -struct Crystal::Event +struct Crystal::LibEvent::Event < Crystal::EventLoop::Event VERSION = String.new(LibEvent2.event_get_version) def self.callback(&block : Int32, LibEvent2::EventFlags, Void* ->) @@ -16,23 +16,19 @@ struct Crystal::Event @freed = false end - def add(timeout : LibC::Timeval? = nil) + def add(timeout : Time::Span?) : Nil if timeout - timeout_copy = timeout - LibEvent2.event_add(@event, pointerof(timeout_copy)) + timeval = LibC::Timeval.new( + tv_sec: LibC::TimeT.new(timeout.total_seconds), + tv_usec: timeout.nanoseconds // 1_000 + ) + LibEvent2.event_add(@event, pointerof(timeval)) else LibEvent2.event_add(@event, nil) end end - def add(timeout : Time::Span) - add LibC::Timeval.new( - tv_sec: LibC::TimeT.new(timeout.total_seconds), - tv_usec: timeout.nanoseconds // 1_000 - ) - end - - def free + def free : Nil LibEvent2.event_free(@event) unless @freed @freed = true end @@ -49,7 +45,7 @@ struct Crystal::Event @base = LibEvent2.event_base_new end - def reinit + def reinit : Nil unless LibEvent2.event_reinit(@base) == 0 raise "Error reinitializing libevent" end @@ -57,18 +53,18 @@ struct Crystal::Event def new_event(s : Int32, flags : LibEvent2::EventFlags, data, &callback : LibEvent2::Callback) event = LibEvent2.event_new(@base, s, flags, callback, data.as(Void*)) - Event.new(event) + Crystal::LibEvent::Event.new(event) end - def run_loop + def run_loop : Nil LibEvent2.event_base_loop(@base, LibEvent2::EventLoopFlags::None) end - def run_once + def run_once : Nil LibEvent2.event_base_loop(@base, LibEvent2::EventLoopFlags::Once) end - def loop_break + def loop_break : Nil LibEvent2.event_base_loopbreak(@base) end diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr index fb9f3721b44d..a928294bb262 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/system/unix/event_loop_libevent.cr @@ -1,37 +1,37 @@ require "./event_libevent" -class Thread - # :nodoc: - getter(event_base) { Crystal::Event::Base.new } +# :nodoc: +abstract class Crystal::EventLoop + def self.create + Crystal::LibEvent::EventLoop.new + end end # :nodoc: -module Crystal::EventLoop +class Crystal::LibEvent::EventLoop < Crystal::EventLoop + private getter(event_base) { Crystal::LibEvent::Event::Base.new } + {% unless flag?(:preview_mt) %} # Reinitializes the event loop after a fork. - def self.after_fork : Nil + def after_fork : Nil event_base.reinit end {% end %} # Runs the event loop. - def self.run_once + def run_once : Nil event_base.run_once end - private def self.event_base - Thread.current.event_base - end - # Create a new resume event for a fiber. - def self.create_resume_event(fiber : Fiber) : Crystal::Event + def create_resume_event(fiber : Fiber) : Crystal::EventLoop::Event event_base.new_event(-1, LibEvent2::EventFlags::None, fiber) do |s, flags, data| Crystal::Scheduler.enqueue data.as(Fiber) end end # Creates a timeout_event. - def self.create_timeout_event(fiber) : Crystal::Event + def create_timeout_event(fiber) : Crystal::EventLoop::Event event_base.new_event(-1, LibEvent2::EventFlags::None, fiber) do |s, flags, data| f = data.as(Fiber) if (select_action = f.timeout_select_action) @@ -44,7 +44,7 @@ module Crystal::EventLoop end # Creates a write event for a file descriptor. - def self.create_fd_write_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::Event + def create_fd_write_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::EventLoop::Event flags = LibEvent2::EventFlags::Write flags |= LibEvent2::EventFlags::Persist | LibEvent2::EventFlags::ET if edge_triggered @@ -59,7 +59,7 @@ module Crystal::EventLoop end # Creates a read event for a file descriptor. - def self.create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::Event + def create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::EventLoop::Event flags = LibEvent2::EventFlags::Read flags |= LibEvent2::EventFlags::Persist | LibEvent2::EventFlags::ET if edge_triggered diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr index 0d8eaf6f780a..8152c0c545b2 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/system/wasi/event_loop.cr @@ -1,39 +1,48 @@ # :nodoc: -module Crystal::EventLoop +abstract class Crystal::EventLoop + def self.create + Crystal::Wasi::EventLoop.new + end +end + +# :nodoc: +class Crystal::Wasi::EventLoop < Crystal::EventLoop + {% unless flag?(:preview_mt) %} + def after_fork : Nil + end + {% end %} + # Runs the event loop. - def self.run_once - raise NotImplementedError.new("Crystal::EventLoop.run_once") + def run_once : Nil + raise NotImplementedError.new("Crystal::Wasi::EventLoop.run_once") end # Create a new resume event for a fiber. - def self.create_resume_event(fiber : Fiber) : Crystal::Event - raise NotImplementedError.new("Crystal::EventLoop.create_resume_event") + def create_resume_event(fiber : Fiber) : Crystal::EventLoop::Event + raise NotImplementedError.new("Crystal::Wasi::EventLoop.create_resume_event") end # Creates a timeout_event. - def self.create_timeout_event(fiber) : Crystal::Event - raise NotImplementedError.new("Crystal::EventLoop.create_timeout_event") + def create_timeout_event(fiber) : Crystal::EventLoop::Event + raise NotImplementedError.new("Crystal::Wasi::EventLoop.create_timeout_event") end # Creates a write event for a file descriptor. - def self.create_fd_write_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::Event - raise NotImplementedError.new("Crystal::EventLoop.create_fd_write_event") + def create_fd_write_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::EventLoop::Event + raise NotImplementedError.new("Crystal::Wasi::EventLoop.create_fd_write_event") end # Creates a read event for a file descriptor. - def self.create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::Event - raise NotImplementedError.new("Crystal::EventLoop.create_fd_read_event") + def create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::EventLoop::Event + raise NotImplementedError.new("Crystal::Wasi::EventLoop.create_fd_read_event") end end -struct Crystal::Event - def add(timeout : LibC::Timeval? = nil) - end - - def add(timeout : Time::Span) +struct Crystal::Wasi::Event < Crystal::EventLoop::Event + def add(timeout : Time::Span?) : Nil end - def free + def free : Nil end def delete diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 4da70e22b494..30fc385de759 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -1,15 +1,23 @@ require "c/ioapiset" require "crystal/system/print_error" -module Crystal::EventLoop - @@queue = Deque(Event).new +# :nodoc: +abstract class Crystal::EventLoop + def self.create + Crystal::Iocp::EventLoop.new + end +end + +# :nodoc: +class Crystal::Iocp::EventLoop < Crystal::EventLoop + @queue = Deque(Crystal::Iocp::Event).new # Returns the base IO Completion Port - class_getter iocp : LibC::HANDLE do + getter iocp : LibC::HANDLE do create_completion_port(LibC::INVALID_HANDLE_VALUE, nil) end - def self.create_completion_port(handle : LibC::HANDLE, parent : LibC::HANDLE? = iocp) + def create_completion_port(handle : LibC::HANDLE, parent : LibC::HANDLE? = iocp) iocp = LibC.CreateIoCompletionPort(handle, parent, nil, 0) if iocp.null? raise IO::Error.from_winerror("CreateIoCompletionPort") @@ -18,7 +26,7 @@ module Crystal::EventLoop end # This is a temporary stub as a stand in for fiber swapping required for concurrency - def self.wait_completion(timeout = nil) + def wait_completion(timeout = nil) result = LibC.GetQueuedCompletionStatusEx(iocp, out io_entry, 1, out removed, timeout, false) if result == 0 error = WinError.value @@ -33,8 +41,8 @@ module Crystal::EventLoop end # Runs the event loop. - def self.run_once : Nil - next_event = @@queue.min_by { |e| e.wake_at } + def run_once : Nil + next_event = @queue.min_by { |e| e.wake_at } if next_event now = Time.monotonic @@ -67,40 +75,40 @@ module Crystal::EventLoop end # Reinitializes the event loop after a fork. - def self.after_fork : Nil + def after_fork : Nil end - def self.enqueue(event : Event) - unless @@queue.includes?(event) - @@queue << event + def enqueue(event : Crystal::Iocp::Event) + unless @queue.includes?(event) + @queue << event end end - def self.dequeue(event : Event) - @@queue.delete(event) + def dequeue(event : Crystal::Iocp::Event) + @queue.delete(event) end # Create a new resume event for a fiber. - def self.create_resume_event(fiber : Fiber) : Crystal::Event - Crystal::Event.new(fiber) + def create_resume_event(fiber : Fiber) : Crystal::EventLoop::Event + Crystal::Iocp::Event.new(fiber) end # Creates a write event for a file descriptor. - def self.create_fd_write_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::Event - Crystal::Event.new(Fiber.current) + def create_fd_write_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::EventLoop::Event + Crystal::Iocp::Event.new(Fiber.current) end # Creates a read event for a file descriptor. - def self.create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::Event - Crystal::Event.new(Fiber.current) + def create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::EventLoop::Event + Crystal::Iocp::Event.new(Fiber.current) end - def self.create_timeout_event(fiber) - Crystal::Event.new(fiber, timeout: true) + def create_timeout_event(fiber) : Crystal::EventLoop::Event + Crystal::Iocp::Event.new(fiber, timeout: true) end end -struct Crystal::Event +struct Crystal::Iocp::Event < Crystal::EventLoop::Event getter fiber getter wake_at getter? timeout @@ -110,15 +118,15 @@ struct Crystal::Event # Frees the event def free : Nil - Crystal::EventLoop.dequeue(self) + Crystal::Scheduler.event_loop.dequeue(self) end def delete free end - def add(time_span : Time::Span) : Nil - @wake_at = Time.monotonic + time_span - Crystal::EventLoop.enqueue(self) + def add(timeout : Time::Span?) : Nil + @wake_at = timeout ? Time.monotonic + timeout : Time.monotonic + Crystal::Scheduler.event_loop.enqueue(self) end end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 334c98a894f3..8b2adfea6164 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -79,7 +79,7 @@ module Crystal::System::Socket raise ::Socket::Error.from_wsa_error("WSASocketW") end - Crystal::EventLoop.create_completion_port LibC::HANDLE.new(socket) + Crystal::Scheduler.event_loop.create_completion_port LibC::HANDLE.new(socket) socket end diff --git a/src/fiber.cr b/src/fiber.cr index 5b3f08b5c576..156a318dc58f 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -52,8 +52,8 @@ class Fiber @context : Context @stack : Void* - @resume_event : Crystal::Event? - @timeout_event : Crystal::Event? + @resume_event : Crystal::EventLoop::Event? + @timeout_event : Crystal::EventLoop::Event? # :nodoc: property timeout_select_action : Channel::TimeoutAction? protected property stack_bottom : Void* @@ -224,13 +224,13 @@ class Fiber end # :nodoc: - def resume_event : Crystal::Event - @resume_event ||= Crystal::EventLoop.create_resume_event(self) + def resume_event : Crystal::EventLoop::Event + @resume_event ||= Crystal::Scheduler.event_loop.create_resume_event(self) end # :nodoc: - def timeout_event : Crystal::Event - @timeout_event ||= Crystal::EventLoop.create_timeout_event(self) + def timeout_event : Crystal::EventLoop::Event + @timeout_event ||= Crystal::Scheduler.event_loop.create_timeout_event(self) end # :nodoc: diff --git a/src/io/evented.cr b/src/io/evented.cr index c2f12bfebff1..c9ba8fa273e7 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -12,8 +12,8 @@ module IO::Evented @readers = Crystal::ThreadLocalValue(Deque(Fiber)).new @writers = Crystal::ThreadLocalValue(Deque(Fiber)).new - @read_event = Crystal::ThreadLocalValue(Crystal::Event).new - @write_event = Crystal::ThreadLocalValue(Crystal::Event).new + @read_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new + @write_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new # Returns the time to wait when reading before raising an `IO::TimeoutError`. def read_timeout : Time::Span? @@ -136,7 +136,7 @@ module IO::Evented end private def add_read_event(timeout = @read_timeout) : Nil - event = @read_event.get { Crystal::EventLoop.create_fd_read_event(self) } + event = @read_event.get { Crystal::Scheduler.event_loop.create_fd_read_event(self) } event.add timeout end @@ -161,7 +161,7 @@ module IO::Evented end private def add_write_event(timeout = @write_timeout) : Nil - event = @write_event.get { Crystal::EventLoop.create_fd_write_event(self) } + event = @write_event.get { Crystal::Scheduler.event_loop.create_fd_write_event(self) } event.add timeout end diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index 79dba7897615..75570feae1a8 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -58,7 +58,7 @@ module IO::Overlapped else timeout = timeout.to_u64 end - result = LibC.GetQueuedCompletionStatusEx(Crystal::EventLoop.iocp, overlapped_entries, overlapped_entries.size, out removed, timeout, false) + result = LibC.GetQueuedCompletionStatusEx(Crystal::Scheduler.event_loop.iocp, overlapped_entries, overlapped_entries.size, out removed, timeout, false) if result == 0 error = WinError.value if timeout && error.wait_timeout? @@ -158,16 +158,16 @@ module IO::Overlapped # Returns `false` if the operation timed out. def schedule_overlapped(timeout : Time::Span?, line = __LINE__) : Bool if timeout - timeout_event = Crystal::Event.new(Fiber.current) + timeout_event = Crystal::Iocp::Event.new(Fiber.current) timeout_event.add(timeout) else - timeout_event = Crystal::Event.new(Fiber.current, Time::Span::MAX) + timeout_event = Crystal::Iocp::Event.new(Fiber.current, Time::Span::MAX) end - Crystal::EventLoop.enqueue(timeout_event) + Crystal::Scheduler.event_loop.enqueue(timeout_event) Crystal::Scheduler.reschedule - Crystal::EventLoop.dequeue(timeout_event) + Crystal::Scheduler.event_loop.dequeue(timeout_event) end def overlapped_operation(socket, method, timeout, connreset_is_error = true) diff --git a/src/kernel.cr b/src/kernel.cr index c8c511cecb8e..154ef5df3d84 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -540,7 +540,7 @@ end ->Crystal::SignalChildHandler.after_fork, # reinit event loop: - ->Crystal::EventLoop.after_fork, + ->{ Crystal::Scheduler.event_loop.after_fork }, # more clean ups (may depend on event loop): ->Random::DEFAULT.new_seed, From 3a23157cb88bdb15d68f82306e3510702133655a Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Sun, 30 Oct 2022 02:30:11 +0800 Subject: [PATCH 0091/1551] Macros: Test addition between `ArrayLiteral` and `TupleLiteral` (#12639) --- spec/compiler/macro/macro_methods_spec.cr | 28 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 8a6f9eb1438e..9988addd6664 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -865,8 +865,18 @@ module Crystal assert_macro %({{ [1, 2, 3].includes?(4) }}), %(false) end - it "executes +" do - assert_macro %({{ [1, 2] + [3, 4, 5] }}), %([1, 2, 3, 4, 5]) + describe "#+" do + context "with TupleLiteral argument" do + it "concatenates the literals into an ArrayLiteral" do + assert_macro %({{ [1, 2] + {3, 4, 5} }}), %([1, 2, 3, 4, 5]) + end + end + + context "with ArrayLiteral argument" do + it "concatenates the literals into an ArrayLiteral" do + assert_macro %({{ [1, 2] + [3, 4, 5] }}), %([1, 2, 3, 4, 5]) + end + end end it "executes [] with range" do @@ -1372,8 +1382,18 @@ module Crystal assert_macro %({{ {1, 2, 3}.includes?(4) }}), %(false) end - it "executes +" do - assert_macro %({{ {1, 2} + {3, 4, 5} }}), %({1, 2, 3, 4, 5}) + describe "#+" do + context "with TupleLiteral argument" do + it "concatenates the literals into a TupleLiteral" do + assert_macro %({{ {1, 2} + {3, 4, 5} }}), %({1, 2, 3, 4, 5}) + end + end + + context "with ArrayLiteral argument" do + it "concatenates the literals into a TupleLiteral" do + assert_macro %({{ {1, 2} + [3, 4, 5] }}), %({1, 2, 3, 4, 5}) + end + end end end From 1c33a0b1d14a1e2c8835bb1b7381789d648624ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 29 Oct 2022 20:30:36 +0200 Subject: [PATCH 0092/1551] Fix `VirtualMetaclassType#implements?` to ignore base_type (#12632) --- spec/compiler/codegen/cast_spec.cr | 12 ++++++++++++ spec/compiler/codegen/is_a_spec.cr | 12 ++++++++++++ spec/compiler/semantic/metaclass_spec.cr | 12 ++++++++++++ src/compiler/crystal/types.cr | 2 +- 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/spec/compiler/codegen/cast_spec.cr b/spec/compiler/codegen/cast_spec.cr index 0ddf65f5b919..1260ab2ffe43 100644 --- a/spec/compiler/codegen/cast_spec.cr +++ b/spec/compiler/codegen/cast_spec.cr @@ -423,4 +423,16 @@ describe "Code gen: cast" do A.as(A.class) )) end + + it "cast virtual metaclass type to nilable virtual instance type (#12628)" do + run(<<-CR).to_b.should be_true + abstract struct Base + end + + struct Impl < Base + end + + Base.as(Base | Base.class).as?(Base | Impl).nil? + CR + end end diff --git a/spec/compiler/codegen/is_a_spec.cr b/spec/compiler/codegen/is_a_spec.cr index e2e9deb89605..2d751bc81c15 100644 --- a/spec/compiler/codegen/is_a_spec.cr +++ b/spec/compiler/codegen/is_a_spec.cr @@ -922,4 +922,16 @@ describe "Codegen: is_a?" do b.is_a?(B(Int32)) )).to_b.should be_true end + + it "virtual metaclass type is not virtual instance type (#12628)" do + run(<<-CR).to_b.should be_false + abstract struct Base + end + + struct Impl < Base + end + + Base.as(Base | Base.class).is_a?(Base | Impl) + CR + end end diff --git a/spec/compiler/semantic/metaclass_spec.cr b/spec/compiler/semantic/metaclass_spec.cr index ebf1b2817599..aa2457a3e255 100644 --- a/spec/compiler/semantic/metaclass_spec.cr +++ b/spec/compiler/semantic/metaclass_spec.cr @@ -134,6 +134,18 @@ describe "Semantic: metaclass" do bar_class.should be_a_proper_subtype_of(foo_class) end + it "virtual metaclass type with virtual type (#12628)" do + mod = semantic(%( + class Base; end + class Impl < Base; end + )).program + + base = mod.types["Base"] + base.virtual_type!.metaclass.implements?(base).should be_false + base.virtual_type!.metaclass.implements?(base.metaclass).should be_true + base.virtual_type!.metaclass.implements?(base.metaclass.virtual_type!).should be_true + end + it "generic classes (1)" do mod = semantic(%( class Foo(T); end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 94ee4af77598..c99f4b6cf512 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -3513,7 +3513,7 @@ module Crystal end def implements?(other_type) - super || base_type.implements?(other_type) + base_type.metaclass.implements?(other_type) end def to_s_with_options(io : IO, skip_union_parens : Bool = false, generic_args : Bool = true, codegen : Bool = false) : Nil From 777ea47102b38d97483b00ec306da9b834a473c2 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 30 Oct 2022 06:27:26 -0300 Subject: [PATCH 0093/1551] Compiler: handle yield exps without a type (#12679) --- spec/compiler/codegen/block_spec.cr | 17 +++++++++++++++++ .../crystal/semantic/cleanup_transformer.cr | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/spec/compiler/codegen/block_spec.cr b/spec/compiler/codegen/block_spec.cr index aaad134a9ee7..d62b49f03042 100644 --- a/spec/compiler/codegen/block_spec.cr +++ b/spec/compiler/codegen/block_spec.cr @@ -1587,4 +1587,21 @@ describe "Code gen: block" do end )).to_i.should eq(1) end + + it "doesn't crash if yield exp has no type (#12670)" do + codegen(%( + def foo : String? + end + + def bar + while res = foo + yield res + end + end + + bar do |res| + res + end + )) + end end diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 167efe45d4d7..fc195a75a750 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -771,7 +771,7 @@ module Crystal # If the yield has a no-return expression, the yield never happens: # replace it with a series of expressions up to the one that no-returns. - no_return_index = node.exps.index &.no_returns? + no_return_index = node.exps.index { |exp| !exp.type? || exp.no_returns? } if no_return_index exps = Expressions.new(node.exps[0, no_return_index + 1]) exps.bind_to(exps.expressions.last) From a4db8e7568e001508a8b5664bc201894c8544b11 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Sun, 30 Oct 2022 17:27:47 +0800 Subject: [PATCH 0094/1551] Add `ArrayLiteral#-(other)` and `TupleLiteral#-(other)` (#12646) Co-authored-by: Vladislav Zarakovsky Co-authored-by: Beta Ziliani --- spec/compiler/macro/macro_methods_spec.cr | 28 +++++++++++++++++++++++ src/compiler/crystal/macros/methods.cr | 16 +++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 9988addd6664..68f63a39db0a 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -879,6 +879,20 @@ module Crystal end end + describe "#-" do + context "with TupleLiteral argument" do + it "removes the elements in RHS from LHS into an ArrayLiteral" do + assert_macro %({{ [1, 2, 3, 4] - {1, 3, 5} }}), %([2, 4]) + end + end + + context "with ArrayLiteral argument" do + it "removes the elements in RHS from LHS into an ArrayLiteral" do + assert_macro %({{ [1, 2, 3, 4] - [1, 3, 5] }}), %([2, 4]) + end + end + end + it "executes [] with range" do assert_macro %({{ [1, 2, 3, 4][1...-1] }}), %([2, 3]) end @@ -1395,6 +1409,20 @@ module Crystal end end end + + describe "#-" do + context "with TupleLiteral argument" do + it "removes the elements in RHS from LHS into a TupleLiteral" do + assert_macro %({{ {1, 2, 3, 4} - {1, 3, 5} }}), %({2, 4}) + end + end + + context "with ArrayLiteral argument" do + it "removes the elements in RHS from LHS into a TupleLiteral" do + assert_macro %({{ {1, 2, 3, 4} - [1, 3, 5] }}), %({2, 4}) + end + end + end end describe "regex methods" do diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index de453f0ea4f5..743170ebfddc 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -2542,15 +2542,23 @@ private def interpret_array_or_tuple_method(object, klass, method, args, named_a when "+" interpret_check_args(node: object) do |arg| case arg - when Crystal::TupleLiteral - other_elements = arg.elements - when Crystal::ArrayLiteral - other_elements = arg.elements + when Crystal::TupleLiteral then other_elements = arg.elements + when Crystal::ArrayLiteral then other_elements = arg.elements else arg.raise "argument to `#{klass}#+` must be a tuple or array, not #{arg.class_desc}:\n\n#{arg}" end klass.new(object.elements + other_elements) end + when "-" + interpret_check_args(node: object) do |arg| + case arg + when Crystal::TupleLiteral then other_elements = arg.elements + when Crystal::ArrayLiteral then other_elements = arg.elements + else + arg.raise "argument to `#{klass}#-` must be a tuple or array, not #{arg.class_desc}:\n\n#{arg}" + end + klass.new(object.elements - other_elements) + end else nil end From 60ca566b322da315b6d05801bbf81c68a0f7868a Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sun, 30 Oct 2022 10:28:04 +0100 Subject: [PATCH 0095/1551] Lint: Use `Object#in?` instead of multiple comparisons (#12675) --- samples/sdl/raytracer.cr | 29 +++++++++++-------- samples/sdl/tv.cr | 2 +- spec/compiler/codegen/union_type_spec.cr | 2 +- spec/std/string_spec.cr | 6 ++-- src/base64.cr | 2 +- src/compiler/crystal/command.cr | 2 +- src/compiler/crystal/semantic/call.cr | 2 +- src/compiler/crystal/semantic/call_error.cr | 4 +-- .../crystal/semantic/type_guess_visitor.cr | 2 +- src/compiler/crystal/syntax/ast.cr | 2 +- src/compiler/crystal/syntax/lexer.cr | 10 +++---- src/compiler/crystal/syntax/parser.cr | 6 ++-- src/compiler/crystal/syntax/to_s.cr | 6 ++-- src/compiler/crystal/tools/formatter.cr | 6 ++-- src/compiler/crystal/tools/init.cr | 2 +- .../crystal/tools/playground/server.cr | 4 +-- src/crystal/mach_o.cr | 4 +-- src/crystal/system/unix/getrandom.cr | 2 +- src/csv/lexer.cr | 2 +- src/dir/glob.cr | 4 +-- src/enumerable.cr | 2 +- src/http/request.cr | 2 +- src/json/lexer.cr | 2 +- src/llvm/abi/x86_64.cr | 4 +-- src/mime/multipart/parser.cr | 2 +- src/process.cr | 6 ++-- src/socket/addrinfo.cr | 2 +- src/spec/cli.cr | 2 +- src/string.cr | 2 +- src/time/format/custom/iso_8601.cr | 4 +-- src/time/format/formatter.cr | 2 +- src/uri.cr | 2 +- src/xml/builder.cr | 2 +- 33 files changed, 68 insertions(+), 65 deletions(-) diff --git a/samples/sdl/raytracer.cr b/samples/sdl/raytracer.cr index f98e576be54b..0528272d8cba 100644 --- a/samples/sdl/raytracer.cr +++ b/samples/sdl/raytracer.cr @@ -222,19 +222,24 @@ SDL.hide_cursor surface = SDL.set_video_mode WIDTH, HEIGHT, 32, LibSDL::DOUBLEBUF | LibSDL::HWSURFACE | LibSDL::ASYNCBLIT first = true -while true - SDL.poll_events do |event| - if event.type == LibSDL::QUIT || event.type == LibSDL::KEYDOWN - SDL.quit - exit + +begin + while true + SDL.poll_events do |event| + if event.type.in?(LibSDL::QUIT, LibSDL::KEYDOWN) + SDL.quit + exit + end end - end - if first - start = SDL.ticks - render scene, surface - ms = SDL.ticks - start - puts "Rendered in #{ms} ms" - first = false + if first + start = SDL.ticks + render scene, surface + ms = SDL.ticks - start + puts "Rendered in #{ms} ms" + first = false + end end +ensure + SDL.quit end diff --git a/samples/sdl/tv.cr b/samples/sdl/tv.cr index 0cd491113074..ca01ab902fce 100644 --- a/samples/sdl/tv.cr +++ b/samples/sdl/tv.cr @@ -133,7 +133,7 @@ puts "Rects: #{rects.size}" begin while true SDL.poll_events do |event| - if event.type == LibSDL::QUIT || event.type == LibSDL::KEYDOWN + if event.type.in?(LibSDL::QUIT, LibSDL::KEYDOWN) ms = SDL.ticks - start puts "#{frames} frames in #{ms} ms" puts "Average FPS: #{frames / (ms * 0.001)}" diff --git a/spec/compiler/codegen/union_type_spec.cr b/spec/compiler/codegen/union_type_spec.cr index e6637f8c7850..eb561a92dbdd 100644 --- a/spec/compiler/codegen/union_type_spec.cr +++ b/spec/compiler/codegen/union_type_spec.cr @@ -202,7 +202,7 @@ describe "Code gen: union type" do a = 1 || 1.5 foo(a) )).to_string - (str == "(Int32 | Float64)" || str == "(Float64 | Int32)").should be_true + str.in?("(Int32 | Float64)", "(Float64 | Int32)").should be_true end it "provides T as a tuple literal" do diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 55023929d00b..056bcad1a82d 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -824,7 +824,7 @@ describe "String" do it { "bcdaaa".strip('a').should eq("bcd") } it { "aaabcd".strip('a').should eq("bcd") } - it { "ababcdaba".strip { |c| c == 'a' || c == 'b' }.should eq("cd") } + it { "ababcdaba".strip(&.in?('a', 'b')).should eq("cd") } end describe "rstrip" do @@ -845,7 +845,7 @@ describe "String" do it { "foobarrrr".rstrip('r').should eq("fooba") } it { "foobar".rstrip('x').should eq("foobar") } - it { "foobar".rstrip { |c| c == 'a' || c == 'r' }.should eq("foob") } + it { "foobar".rstrip(&.in?('a', 'r')).should eq("foob") } it "does not touch invalid code units in an otherwise ascii string" do " \xA0 ".rstrip.should eq(" \xA0") @@ -869,7 +869,7 @@ describe "String" do it { "bbbbarfoo".lstrip('b').should eq("arfoo") } it { "barfoo".lstrip('x').should eq("barfoo") } - it { "barfoo".lstrip { |c| c == 'a' || c == 'b' }.should eq("rfoo") } + it { "barfoo".lstrip(&.in?('a', 'b')).should eq("rfoo") } it "does not touch invalid code units in an otherwise ascii string" do " \xA0 ".lstrip.should eq("\xA0 ") diff --git a/src/base64.cr b/src/base64.cr index 717d2e7de2ff..2457768daa00 100644 --- a/src/base64.cr +++ b/src/base64.cr @@ -252,7 +252,7 @@ module Base64 bytes_begin = bytes # Get the position of the last valid base64 character (rstrip '\n', '\r' and '=') - while (size > 0) && (sym = bytes[size - 1]) && (sym == NL || sym == NR || sym == PAD) + while (size > 0) && (sym = bytes[size - 1]) && sym.in?(NL, NR, PAD) size -= 1 end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index bb7f76464443..a3ad34ba0967 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -95,7 +95,7 @@ class Crystal::Command when command == "eval" options.shift eval - when command == "i" || command == "interactive" + when command.in?("i", "interactive") options.shift {% if flag?(:without_interpreter) %} STDERR.puts "Crystal was compiled without interpreter support" diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index d2536e2dfc44..e9e299d25dbd 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -1167,7 +1167,7 @@ class Crystal::Call args["self"] = MetaVar.new("self", self_type) end - strict_check = body.is_a?(Primitive) && (body.name == "proc_call" || body.name == "pointer_set") + strict_check = body.is_a?(Primitive) && body.name.in?("proc_call", "pointer_set") arg_types.each_index do |index| arg = typed_def.args[index] diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index ee54c34edc04..aa975e8b06c5 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -67,7 +67,7 @@ class Crystal::Call # Another special case: `new` and `initialize` are only looked up one level, # so we must find the first one defined. new_owner = owner - while defs.empty? && (def_name == "initialize" || def_name == "new") + while defs.empty? && def_name.in?("initialize", "new") new_owner = new_owner.superclass if new_owner defs = new_owner.lookup_defs(def_name) @@ -106,7 +106,7 @@ class Crystal::Call end # If it's on an initialize method and there's a similar method name, it's probably a typo - if (def_name == "initialize" || def_name == "new") && (similar_def = owner.instance_type.lookup_similar_def("initialize", self.args.size, block)) + if def_name.in?("initialize", "new") && (similar_def = owner.instance_type.lookup_similar_def("initialize", self.args.size, block)) inner_msg = colorize("do you maybe have a typo in this '#{similar_def.name}' method?").yellow.bold.to_s inner_exception = TypeException.for_node(similar_def, inner_msg) end diff --git a/src/compiler/crystal/semantic/type_guess_visitor.cr b/src/compiler/crystal/semantic/type_guess_visitor.cr index 8c1f40bffa86..994e5e357977 100644 --- a/src/compiler/crystal/semantic/type_guess_visitor.cr +++ b/src/compiler/crystal/semantic/type_guess_visitor.cr @@ -667,7 +667,7 @@ module Crystal # If it's Pointer(T).malloc or Pointer(T).null, guess it to Pointer(T) if obj.is_a?(Generic) && (name = obj.name).is_a?(Path) && name.single?("Pointer") && - (node.name == "malloc" || node.name == "null") + node.name.in?("malloc", "null") type = lookup_type?(obj) return type if type.is_a?(PointerInstanceType) end diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 8b40225a02db..c434869d36a9 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -295,7 +295,7 @@ module Crystal end def has_sign? - @value[0] == '+' || @value[0] == '-' + @value[0].in?('+', '-') end def integer_value diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index ae4aa73590e8..733aa6d775ac 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -630,7 +630,7 @@ module Crystal while ident_part?(next_char) # Nothing to do end - if current_char == '?' || ((current_char == '!' || current_char == '=') && peek_next_char != '=') + if current_char == '?' || (current_char.in?('!', '=') && peek_next_char != '=') next_char end @token.type = :SYMBOL @@ -1450,7 +1450,7 @@ module Crystal while ident_part?(current_char) next_char end - if (current_char == '?' || current_char == '!') && peek_next_char != '=' + if current_char.in?('?', '!') && peek_next_char != '=' next_char end @token.type = :IDENT @@ -1896,7 +1896,7 @@ module Crystal end if reached_end && - (current_char == '\n' || current_char == '\0' || + (current_char.in?('\n', '\0') || (current_char == '\r' && peek_next_char == '\n' && next_char)) @token.type = :DELIMITER_END @token.delimiter_state = delimiter_state.with_heredoc_indent(indent) @@ -2251,7 +2251,7 @@ module Crystal whitespace = false beginning_of_line = true else - whitespace = char.ascii_whitespace? || char == ';' || char == '(' || char == '[' || char == '{' + whitespace = char.ascii_whitespace? || char.in?(';', '(', '[', '{') if beginning_of_line && !whitespace beginning_of_line = false end @@ -2907,7 +2907,7 @@ module Crystal private delegate ident_start?, ident_part?, to: Lexer def ident_part_or_end?(char) - ident_part?(char) || char == '?' || char == '!' + ident_part?(char) || char.in?('?', '!') end def peek_not_ident_part_or_end_next_char diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 4c41220d4234..770cd92324dd 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -241,7 +241,7 @@ module Crystal !exp.has_parentheses? && ( (exp.args.empty? && !exp.named_args) || Lexer.setter?(exp.name) || - exp.name == "[]" || exp.name == "[]=" + exp.name.in?("[]", "[]=") ) else false @@ -2218,7 +2218,7 @@ module Crystal this_piece_is_in_new_line = line_number != previous_line_number next_piece_is_in_new_line = i == pieces.size - 1 || pieces[i + 1].line_number != line_number if value.is_a?(String) - if value == "\n" || value == "\r\n" + if value.in?("\n", "\r\n") current_line << value if this_piece_is_in_new_line || next_piece_is_in_new_line line = current_line.to_s @@ -4298,7 +4298,7 @@ module Crystal while current_char.ascii_whitespace? next_char_no_column_increment end - comes_plus_or_minus = current_char == '+' || current_char == '-' + comes_plus_or_minus = current_char.in?('+', '-') self.current_pos = pos comes_plus_or_minus end diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 7eda37fcff98..7359b40bc209 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -73,9 +73,7 @@ module Crystal # If there's no '.' nor 'e', for example in `1_f64`, # we need to include it (#3315) node.value.each_char do |char| - if char == '.' || char == 'e' - return false - end + return false if char.in?('.', 'e') end true @@ -347,7 +345,7 @@ module Crystal node_obj = nil end - if node_obj && (node.name == "[]" || node.name == "[]?") && !block + if node_obj && node.name.in?("[]", "[]?") && !block in_parenthesis(need_parens, node_obj) @str << "[" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index e0da41225c91..2224514360e2 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -2508,7 +2508,7 @@ module Crystal base_indent = @indent # Special case: $1, $2, ... - if @token.type.global_match_data_index? && (node.name == "[]" || node.name == "[]?") && obj.is_a?(Global) + if @token.type.global_match_data_index? && node.name.in?("[]", "[]?") && obj.is_a?(Global) write "$" write @token.value next_token @@ -2663,7 +2663,7 @@ module Crystal end # This is for foo &.[bar] and &.[bar]?, or foo.[bar] and foo.[bar]? - if (node.name == "[]" || node.name == "[]?") && @token.type.op_lsquare? + if node.name.in?("[]", "[]?") && @token.type.op_lsquare? write "[" next_token_skip_space_or_newline format_call_args(node, false, base_indent) @@ -4741,7 +4741,7 @@ module Crystal @current_doc_comment = nil else # Normalize crystal language tag - if language == "cr" || language == "crystal" + if language.in?("cr", "crystal") value = value.rchop(language) language = "" end diff --git a/src/compiler/crystal/tools/init.cr b/src/compiler/crystal/tools/init.cr index 5ea15c66484f..456386f5b14e 100644 --- a/src/compiler/crystal/tools/init.cr +++ b/src/compiler/crystal/tools/init.cr @@ -130,7 +130,7 @@ module Crystal when !name[0].ascii_letter? then raise Error.new("NAME must start with a letter") when name.index("--") then raise Error.new("NAME must not have consecutive dashes") when name.index("__") then raise Error.new("NAME must not have consecutive underscores") - when !name.each_char.all? { |c| c.alphanumeric? || c == '-' || c == '_' } + when !name.each_char.all? { |c| c.alphanumeric? || c.in?('-', '_') } raise Error.new("NAME must only contain alphanumerical characters, underscores or dashes") else # name is valid diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr index c163afc35208..dfd8cce734b3 100644 --- a/src/compiler/crystal/tools/playground/server.cr +++ b/src/compiler/crystal/tools/playground/server.cr @@ -231,7 +231,7 @@ module Crystal::Playground File.read(@filename) end - if extname == ".md" || extname == ".cr" + if extname.in?(".md", ".cr") content = Markd.to_html(content) end content @@ -536,7 +536,7 @@ module Crystal::Playground private def accept_request?(origin) case @host when nil, "localhost", "127.0.0.1" - origin == "http://127.0.0.1:#{@port}" || origin == "http://localhost:#{@port}" + origin.in?("http://localhost:#{@port}", "http://127.0.0.1:#{@port}") when "0.0.0.0" true else diff --git a/src/crystal/mach_o.cr b/src/crystal/mach_o.cr index 92020e5e3a05..8a5d3a105549 100644 --- a/src/crystal/mach_o.cr +++ b/src/crystal/mach_o.cr @@ -112,7 +112,7 @@ module Crystal private def read_magic magic = @io.read_bytes(UInt32) - unless magic == MAGIC_64 || magic == CIGAM_64 || magic == MAGIC || magic == CIGAM + unless magic.in?(MAGIC_64, CIGAM_64, MAGIC, CIGAM) raise Error.new("Invalid magic number") end magic @@ -123,7 +123,7 @@ module Crystal end def endianness - if @magic == MAGIC_64 || @magic == MAGIC + if @magic.in?(MAGIC_64, MAGIC) IO::ByteFormat::SystemEndian elsif IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian IO::ByteFormat::BigEndian diff --git a/src/crystal/system/unix/getrandom.cr b/src/crystal/system/unix/getrandom.cr index 0008c80c1f4e..6b4b6de5cc09 100644 --- a/src/crystal/system/unix/getrandom.cr +++ b/src/crystal/system/unix/getrandom.cr @@ -103,7 +103,7 @@ module Crystal::System::Random read_bytes = Syscall.getrandom(buf.to_unsafe, LibC::SizeT.new(buf.size), Syscall::GRND_NONBLOCK) if read_bytes < 0 err = Errno.new(-read_bytes.to_i) - if err == Errno::EINTR || err == Errno::EAGAIN + if err.in?(Errno::EINTR, Errno::EAGAIN) ::Fiber.yield else raise RuntimeError.from_os_error("getrandom", err) diff --git a/src/csv/lexer.cr b/src/csv/lexer.cr index f4d8aab7c6d8..9d3d04c68c0f 100644 --- a/src/csv/lexer.cr +++ b/src/csv/lexer.cr @@ -138,7 +138,7 @@ abstract class CSV::Lexer private def next_char @column_number += 1 char = next_char_no_column_increment - if char == '\n' || char == '\r' + if char.in?('\n', '\r') @column_number = 0 @line_number += 1 end diff --git a/src/dir/glob.cr b/src/dir/glob.cr index 1218f82d6719..5d39de127866 100644 --- a/src/dir/glob.cr +++ b/src/dir/glob.cr @@ -147,7 +147,7 @@ class Dir private def self.constant_entry?(file) file.each_char do |char| - return false if char == '*' || char == '?' + return false if char.in?('*', '?') end true @@ -325,7 +325,7 @@ class Dir private def self.each_child(path) Dir.open(path || Dir.current) do |dir| while entry = read_entry(dir) - next if entry.name == "." || entry.name == ".." + next if entry.name.in?(".", "..") yield entry end end diff --git a/src/enumerable.cr b/src/enumerable.cr index f282b6141a53..26590eb92370 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -190,7 +190,7 @@ module Enumerable(T) def same_as?(key) : Bool return false unless @initialized - return false if key == Alone || key == Drop + return false if key.in?(Alone, Drop) @key == key end diff --git a/src/http/request.cr b/src/http/request.cr index e0eb4ebc961a..9a01daeab437 100644 --- a/src/http/request.cr +++ b/src/http/request.cr @@ -113,7 +113,7 @@ class HTTP::Request end def body=(@body : Nil) - @headers["Content-Length"] = "0" if @method == "POST" || @method == "PUT" + @headers["Content-Length"] = "0" if @method.in?("POST", "PUT") end def to_io(io) diff --git a/src/json/lexer.cr b/src/json/lexer.cr index bc3ba9b58662..6769a0f91657 100644 --- a/src/json/lexer.cr +++ b/src/json/lexer.cr @@ -275,7 +275,7 @@ abstract class JSON::Lexer char = next_char end - if char == 'e' || char == 'E' + if char.in?('e', 'E') consume_exponent else @token.kind = :float diff --git a/src/llvm/abi/x86_64.cr b/src/llvm/abi/x86_64.cr index f6cc0d0ddeb6..e7219127638d 100644 --- a/src/llvm/abi/x86_64.cr +++ b/src/llvm/abi/x86_64.cr @@ -89,7 +89,7 @@ class LLVM::ABI::X86_64 < LLVM::ABI return false if cls.empty? cl = cls.first - cl == RegClass::Memory || cl == RegClass::X87 || cl == RegClass::ComplexX87 + cl.in?(RegClass::Memory, RegClass::X87, RegClass::ComplexX87) end def sret?(cls) : Bool @@ -161,7 +161,7 @@ class LLVM::ABI::X86_64 < LLVM::ABI i = 0 ty_kind = ty.kind e = cls.size - if e > 2 && (ty_kind == Type::Kind::Struct || ty_kind == Type::Kind::Array) + if e > 2 && ty_kind.in?(Type::Kind::Struct, Type::Kind::Array) if cls[i].sse? i += 1 while i < e diff --git a/src/mime/multipart/parser.cr b/src/mime/multipart/parser.cr index 734f056507ad..ec3123b4c602 100644 --- a/src/mime/multipart/parser.cr +++ b/src/mime/multipart/parser.cr @@ -115,7 +115,7 @@ module MIME::Multipart 0.upto(transport_padding_crlf.bytesize - 3) do |i| # 3 constant to ignore "\r\n" at end byte = transport_padding_crlf.to_unsafe[i] - fail("padding contained non-whitespace character") unless byte == ' '.ord || byte == '\t'.ord + fail("padding contained non-whitespace character") unless byte.in?(' '.ord, '\t'.ord) end end diff --git a/src/process.cr b/src/process.cr index 69c03bf8961a..44365f6fe715 100644 --- a/src/process.cr +++ b/src/process.cr @@ -229,9 +229,9 @@ class Process pid = Crystal::System::Process.spawn(command_args, env, clear_env, fork_input, fork_output, fork_error, chdir) @process_info = Crystal::System::Process.new(pid) - fork_input.close unless fork_input == input || fork_input == STDIN - fork_output.close unless fork_output == output || fork_output == STDOUT - fork_error.close unless fork_error == error || fork_error == STDERR + fork_input.close unless fork_input.in?(input, STDIN) + fork_output.close unless fork_output.in?(output, STDOUT) + fork_error.close unless fork_error.in?(error, STDERR) end def finalize diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr index adda532b3d4f..1667ebb7ae91 100644 --- a/src/socket/addrinfo.cr +++ b/src/socket/addrinfo.cr @@ -151,7 +151,7 @@ class Socket # On OS X < 10.12, the libsystem implementation of getaddrinfo segfaults # if AI_NUMERICSERV is set, and servname is NULL or 0. {% if flag?(:darwin) %} - if (service == 0 || service == nil) && (hints.ai_flags & LibC::AI_NUMERICSERV) + if service.in?(0, nil) && (hints.ai_flags & LibC::AI_NUMERICSERV) hints.ai_flags |= LibC::AI_NUMERICSERV service = "00" end diff --git a/src/spec/cli.cr b/src/spec/cli.cr index f0d36f45348d..03847d45bb09 100644 --- a/src/spec/cli.cr +++ b/src/spec/cli.cr @@ -88,7 +88,7 @@ module Spec Spec.add_tag tag end opts.on("--order MODE", "run examples in random order by passing MODE as 'random' or to a specific seed by passing MODE as the seed value") do |mode| - if mode == "default" || mode == "random" + if mode.in?("default", "random") Spec.order = mode elsif seed = mode.to_u64? Spec.order = seed diff --git a/src/string.cr b/src/string.cr index a9eba0dc9d1b..974f3fd1a3a3 100644 --- a/src/string.cr +++ b/src/string.cr @@ -736,7 +736,7 @@ class String end private def to_f_impl(whitespace : Bool = true, strict : Bool = true) - return unless whitespace || '0' <= self[0] <= '9' || self[0] == '-' || self[0] == '+' + return unless whitespace || '0' <= self[0] <= '9' || self[0].in?('-', '+') v, endptr = yield diff --git a/src/time/format/custom/iso_8601.cr b/src/time/format/custom/iso_8601.cr index bdf3ed8b51a4..b6e634c7a4de 100644 --- a/src/time/format/custom/iso_8601.cr +++ b/src/time/format/custom/iso_8601.cr @@ -77,7 +77,7 @@ struct Time::Format second - if current_char == '.' || current_char == ',' + if current_char.in?('.', ',') next_char second_fraction end @@ -86,7 +86,7 @@ struct Time::Format end end - if current_char == '.' || current_char == ',' + if current_char.in?('.', ',') next_char pos = @reader.pos diff --git a/src/time/format/formatter.cr b/src/time/format/formatter.cr index 982183673e1e..b2138c11495f 100644 --- a/src/time/format/formatter.cr +++ b/src/time/format/formatter.cr @@ -211,7 +211,7 @@ struct Time::Format end def time_zone_gmt_or_rfc2822(**options) : Nil - if time.utc? || time.location.name == "UT" || time.location.name == "GMT" + if time.utc? || time.location.name.in?("UT", "GMT") time_zone_gmt else time_zone_rfc2822 diff --git a/src/uri.cr b/src/uri.cr index d96bee865ee0..996c1953d461 100644 --- a/src/uri.cr +++ b/src/uri.cr @@ -608,7 +608,7 @@ class URI result.pop if result.size > 0 # D. if the input buffer consists only of "." or "..", then remove # that from the input buffer; otherwise, - elsif path == ".." || path == "." + elsif path.in?("..", ".") path = "" # E. move the first path segment in the input buffer to the end of # the output buffer, including the initial "/" character (if diff --git a/src/xml/builder.cr b/src/xml/builder.cr index 7eead7fbc0fd..5265a07feb9f 100644 --- a/src/xml/builder.cr +++ b/src/xml/builder.cr @@ -275,7 +275,7 @@ class XML::Builder # Sets the quote char to use, either `'` or `"`. def quote_char=(char : Char) - unless char == '\'' || char == '"' + unless char.in?('\'', '"') raise ArgumentError.new("Quote char must be ' or \", not #{char}") end From 7da26e0e9f3f43dfdde859faf82fb9d054f166dd Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Mon, 31 Oct 2022 18:45:28 +0800 Subject: [PATCH 0096/1551] Fix range literals causing method lookups in docs generator (#12680) --- spec/compiler/crystal/tools/doc/doc_renderer_spec.cr | 9 +++++++++ src/compiler/crystal/tools/doc/markd_doc_renderer.cr | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr b/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr index 65b625311096..64b69773e708 100644 --- a/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr +++ b/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr @@ -81,6 +81,15 @@ describe Doc::MarkdDocRenderer do end end + it "doesn't spuriously match range literals" do + {base, base_foo}.each do |obj| + assert_code_link(obj, "(0..baz)") + assert_code_link(obj, "(0...baz)") + assert_code_link(obj, "0..baz") + assert_code_link(obj, "0...baz") + end + end + it "doesn't find substrings for methods" do assert_code_link(base_foo, "not bar") assert_code_link(base_foo, "bazzy") diff --git a/src/compiler/crystal/tools/doc/markd_doc_renderer.cr b/src/compiler/crystal/tools/doc/markd_doc_renderer.cr index 161b857ff9ea..c3def230323a 100644 --- a/src/compiler/crystal/tools/doc/markd_doc_renderer.cr +++ b/src/compiler/crystal/tools/doc/markd_doc_renderer.cr @@ -74,7 +74,7 @@ class Crystal::Doc::MarkdDocRenderer < Markd::HTMLRenderer # Check Type#method(...) or Type or #method(...) text.gsub %r( - ((?:\B::)?\b[A-Z]\w+(?:\:\:[A-Z]\w+)*|\B|(?<=\bself))([#.])([\w<=>+\-*\/\[\]&|?!^~]+[?!]?)(?:\((.*?)\))? + ((?:\B::)?\b[A-Z]\w+(?:\:\:[A-Z]\w+)*|\B|(?<=\bself))(?+\-*\/\[\]&|?!^~]+[?!]?)(?:\((.*?)\))? | ((?:\B::)?\b[A-Z]\w+(?:\:\:[A-Z]\w+)*) )x do |match_text| From 36bd91e62a2471b4c6f10eb53c03ef1443f4a2db Mon Sep 17 00:00:00 2001 From: Iain Barnett Date: Mon, 31 Oct 2022 19:45:55 +0900 Subject: [PATCH 0097/1551] Add docs for `Int#downto` (#12468) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/int.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/int.cr b/src/int.cr index 7ad4c39daaef..e124c4caa872 100644 --- a/src/int.cr +++ b/src/int.cr @@ -550,6 +550,7 @@ struct Int UptoIterator(typeof(self), typeof(to)).new(self, to) end + # Calls the given block with each integer value from self down to `to`. def downto(to, &block : self ->) : Nil return unless self >= to x = self @@ -560,6 +561,7 @@ struct Int end end + # Get an iterator for counting down from self to `to`. def downto(to) DowntoIterator(typeof(self), typeof(to)).new(self, to) end From 9ae8024f45a9d4d05c1437abd9bd4a00ff90bfe2 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 31 Oct 2022 11:46:21 +0100 Subject: [PATCH 0098/1551] Lint: Remove useless assignments (#12648) --- samples/binary-trees.cr | 1 - samples/mt_gc_test.cr | 2 +- samples/red_black_tree.cr | 4 ++-- samples/wordcount.cr | 2 +- spec/compiler/macro/macro_methods_spec.cr | 16 ++++++++-------- spec/compiler/parser/warnings_spec.cr | 2 +- spec/compiler/semantic/class_spec.cr | 2 +- spec/compiler/semantic/instance_var_spec.cr | 4 ++-- spec/compiler/semantic/virtual_spec.cr | 4 ++-- spec/compiler/semantic/warnings_spec.cr | 4 ++-- spec/spec_helper.cr | 1 - spec/std/deque_spec.cr | 2 +- spec/std/hash_spec.cr | 4 ++-- spec/std/http/client/client_spec.cr | 2 -- spec/std/http/headers_spec.cr | 2 +- spec/std/log/context_spec.cr | 2 +- spec/std/log/dispatch_spec.cr | 10 +++++----- spec/std/oauth2/session_spec.cr | 4 ++-- spec/std/proc_spec.cr | 2 -- spec/std/socket/tcp_socket_spec.cr | 2 +- spec/std/string_spec.cr | 2 +- src/compiler/crystal/codegen/codegen.cr | 1 - src/compiler/crystal/codegen/fun.cr | 3 --- src/compiler/crystal/codegen/primitives.cr | 2 +- src/compiler/crystal/command.cr | 2 +- src/compiler/crystal/command/cursor.cr | 15 +++++++-------- src/compiler/crystal/command/repl.cr | 2 +- src/compiler/crystal/command/spec.cr | 2 +- src/compiler/crystal/exception.cr | 2 +- src/compiler/crystal/interpreter/compiler.cr | 10 ++-------- src/compiler/crystal/macros/methods.cr | 2 +- src/compiler/crystal/program.cr | 2 +- src/compiler/crystal/semantic/ast.cr | 1 - src/compiler/crystal/semantic/bindings.cr | 2 +- src/compiler/crystal/semantic/call_error.cr | 9 +++------ src/compiler/crystal/semantic/exception.cr | 2 +- src/compiler/crystal/semantic/main_visitor.cr | 2 +- src/compiler/crystal/semantic/restrictions.cr | 2 +- .../crystal/semantic/top_level_visitor.cr | 2 +- .../semantic/type_declaration_processor.cr | 1 - .../crystal/semantic/type_guess_visitor.cr | 1 - .../crystal/semantic/type_to_restriction.cr | 1 - src/compiler/crystal/syntax/lexer.cr | 2 -- src/compiler/crystal/syntax/parser.cr | 8 -------- src/compiler/crystal/tools/formatter.cr | 10 +--------- src/exception/call_stack/stackwalk.cr | 2 +- src/float/printer/grisu3.cr | 2 -- src/float/printer/ieee.cr | 2 -- src/iterator.cr | 2 +- src/llvm/abi/x86.cr | 10 +++++----- src/llvm/abi/x86_64.cr | 1 - src/path.cr | 4 +++- 52 files changed, 69 insertions(+), 114 deletions(-) diff --git a/samples/binary-trees.cr b/samples/binary-trees.cr index af3b3726c56b..b06de4c2c39a 100644 --- a/samples/binary-trees.cr +++ b/samples/binary-trees.cr @@ -31,7 +31,6 @@ stretch_depth = max_depth + 1 stretch_tree = bottom_up_tree(0, stretch_depth) puts "stretch tree of depth #{stretch_depth}\t check: #{item_check(stretch_tree)}" -stretch_tree = nil long_lived_tree = bottom_up_tree(0, max_depth) diff --git a/samples/mt_gc_test.cr b/samples/mt_gc_test.cr index ba2bd03bfdd0..4c666930ba46 100644 --- a/samples/mt_gc_test.cr +++ b/samples/mt_gc_test.cr @@ -110,7 +110,7 @@ class Context @threads_state.set(State::Wait) # spin wait for threads to finish the round - while (c = @threads_reached.get) < @threads + while @threads_reached.get < @threads end log "All threads_reached!" end diff --git a/samples/red_black_tree.cr b/samples/red_black_tree.cr index f872c5cf2c6a..fb56828a5b76 100644 --- a/samples/red_black_tree.cr +++ b/samples/red_black_tree.cr @@ -328,10 +328,10 @@ class RedBlackTreeRunner @n = n random = Random.new(1234) # repeatable random seq - @a1 = Array(Int32).new(n) { rand(99_999) } + @a1 = Array(Int32).new(n) { random.rand(99_999) } random = Random.new(4321) # repeatable random seq - @a2 = Array(Int32).new(n) { rand(99_999) } + @a2 = Array(Int32).new(n) { random.rand(99_999) } @tree = RedBlackTree.new end diff --git a/samples/wordcount.cr b/samples/wordcount.cr index fc1825d297ee..b2de1158564e 100644 --- a/samples/wordcount.cr +++ b/samples/wordcount.cr @@ -50,4 +50,4 @@ end in_filenames = ARGV -do_work ARGV, output_filename, ignore_case +do_work in_filenames, output_filename, ignore_case diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 68f63a39db0a..94e7dd63bb59 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -3100,13 +3100,13 @@ module Crystal it "returns true if file exists" do run(%q< {{file_exists?("#{__DIR__}/../data/build")}} ? 10 : 20 - >, filename = __FILE__).to_i.should eq(10) + >, filename: __FILE__).to_i.should eq(10) end it "returns false if file doesn't exist" do run(%q< {{file_exists?("#{__DIR__}/../data/build_foo")}} ? 10 : 20 - >, filename = __FILE__).to_i.should eq(20) + >, filename: __FILE__).to_i.should eq(20) end end @@ -3114,13 +3114,13 @@ module Crystal it "reads file (exists)" do run(%q< {{file_exists?("spec/compiler/data/build")}} ? 10 : 20 - >, filename = __FILE__).to_i.should eq(10) + >, filename: __FILE__).to_i.should eq(10) end it "reads file (doesn't exist)" do run(%q< {{file_exists?("spec/compiler/data/build_foo")}} ? 10 : 20 - >, filename = __FILE__).to_i.should eq(20) + >, filename: __FILE__).to_i.should eq(20) end end end @@ -3130,7 +3130,7 @@ module Crystal it "reads file (exists)" do run(%q< {{read_file("#{__DIR__}/../data/build")}} - >, filename = __FILE__).to_string.should eq(File.read("#{__DIR__}/../data/build")) + >, filename: __FILE__).to_string.should eq(File.read("#{__DIR__}/../data/build")) end it "reads file (doesn't exist)" do @@ -3145,7 +3145,7 @@ module Crystal it "reads file (exists)" do run(%q< {{read_file("spec/compiler/data/build")}} - >, filename = __FILE__).to_string.should eq(File.read("spec/compiler/data/build")) + >, filename: __FILE__).to_string.should eq(File.read("spec/compiler/data/build")) end it "reads file (doesn't exist)" do @@ -3162,7 +3162,7 @@ module Crystal it "reads file (doesn't exist)" do run(%q< {{read_file?("#{__DIR__}/../data/build_foo")}} ? 10 : 20 - >, filename = __FILE__).to_i.should eq(20) + >, filename: __FILE__).to_i.should eq(20) end end @@ -3170,7 +3170,7 @@ module Crystal it "reads file (doesn't exist)" do run(%q< {{read_file?("spec/compiler/data/build_foo")}} ? 10 : 20 - >, filename = __FILE__).to_i.should eq(20) + >, filename: __FILE__).to_i.should eq(20) end end end diff --git a/spec/compiler/parser/warnings_spec.cr b/spec/compiler/parser/warnings_spec.cr index 2bfb4ff937bc..be08adf0dba6 100644 --- a/spec/compiler/parser/warnings_spec.cr +++ b/spec/compiler/parser/warnings_spec.cr @@ -3,7 +3,7 @@ require "../../support/syntax" private def assert_parser_warning(source, message, *, file = __FILE__, line = __LINE__) parser = Parser.new(source) parser.filename = "/foo/bar/baz.cr" - node = parser.parse + parser.parse warnings = parser.warnings.infos warnings.size.should eq(1), file: file, line: line diff --git a/spec/compiler/semantic/class_spec.cr b/spec/compiler/semantic/class_spec.cr index bd8a473e57ce..584d89041310 100644 --- a/spec/compiler/semantic/class_spec.cr +++ b/spec/compiler/semantic/class_spec.cr @@ -35,7 +35,7 @@ describe "Semantic: class" do end it "types generic of generic type" do - result = assert_type(" + assert_type(" class Foo(T) def set @coco = 2 diff --git a/spec/compiler/semantic/instance_var_spec.cr b/spec/compiler/semantic/instance_var_spec.cr index b2fe8db46f50..08827918646c 100644 --- a/spec/compiler/semantic/instance_var_spec.cr +++ b/spec/compiler/semantic/instance_var_spec.cr @@ -4367,7 +4367,7 @@ describe "Semantic: instance var" do end it "declares instance var of generic class" do - result = assert_type(" + assert_type(" class Foo(T) @x : T @@ -4385,7 +4385,7 @@ describe "Semantic: instance var" do end it "declares instance var of generic class after reopen" do - result = assert_type(" + assert_type(" class Foo(T) end diff --git a/spec/compiler/semantic/virtual_spec.cr b/spec/compiler/semantic/virtual_spec.cr index 821178533c16..642a235503a7 100644 --- a/spec/compiler/semantic/virtual_spec.cr +++ b/spec/compiler/semantic/virtual_spec.cr @@ -607,7 +607,7 @@ describe "Semantic: virtual" do end it "uses virtual type as generic type if class is abstract" do - result = assert_type(" + assert_type(" abstract class Foo end @@ -619,7 +619,7 @@ describe "Semantic: virtual" do end it "uses virtual type as generic type if class is abstract even in union" do - result = assert_type(" + assert_type(" abstract class Foo end diff --git a/spec/compiler/semantic/warnings_spec.cr b/spec/compiler/semantic/warnings_spec.cr index 91634f95e1ec..9e34e0ad286e 100644 --- a/spec/compiler/semantic/warnings_spec.cr +++ b/spec/compiler/semantic/warnings_spec.cr @@ -230,7 +230,7 @@ describe "Semantic: warnings" do compiler.warnings.level = :all compiler.warnings.exclude_lib_path = true compiler.prelude = "empty" - result = compiler.compile Compiler::Source.new(main_filename, File.read(main_filename)), output_filename + compiler.compile Compiler::Source.new(main_filename, File.read(main_filename)), output_filename compiler.warnings.infos.size.should eq(1) end @@ -399,7 +399,7 @@ describe "Semantic: warnings" do compiler.warnings.level = :all compiler.warnings.exclude_lib_path = true compiler.prelude = "empty" - result = compiler.compile Compiler::Source.new(main_filename, File.read(main_filename)), output_filename + compiler.compile Compiler::Source.new(main_filename, File.read(main_filename)), output_filename compiler.warnings.infos.size.should eq(1) end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 69e3566e73e0..b3f1a13e7444 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -80,7 +80,6 @@ end def assert_normalize(from, to, flags = nil, *, file = __FILE__, line = __LINE__) program = new_program program.flags.concat(flags.split) if flags - normalizer = Normalizer.new(program) from_nodes = Parser.parse(from) to_nodes = program.normalize(from_nodes) to_nodes.to_s.strip.should eq(to.strip), file: file, line: line diff --git a/spec/std/deque_spec.cr b/spec/std/deque_spec.cr index 0dce7118a913..62012587ccbf 100644 --- a/spec/std/deque_spec.cr +++ b/spec/std/deque_spec.cr @@ -637,7 +637,7 @@ describe "Deque" do a = Deque{1, 2, 3} count = 0 it = a.each_index - a.each_index.each do + it.each do count += 1 a.clear end diff --git a/spec/std/hash_spec.cr b/spec/std/hash_spec.cr index 30f66e7c5235..d168a20ecfea 100644 --- a/spec/std/hash_spec.cr +++ b/spec/std/hash_spec.cr @@ -1257,7 +1257,7 @@ describe "Hash" do it { {"a" => 2, "b" => 3}.reject(["b", "a"]).should eq({} of String => Int32) } it "does not change current hash" do h = {"a" => 3, "b" => 6, "c" => 9} - h2 = h.reject("b", "c") + h.reject("b", "c") h.should eq({"a" => 3, "b" => 6, "c" => 9}) end end @@ -1282,7 +1282,7 @@ describe "Hash" do it { {"a" => 2, "b" => 3}.select(Set{"b", "a"}).should eq({"a" => 2, "b" => 3}) } it "does not change current hash" do h = {"a" => 3, "b" => 6, "c" => 9} - h2 = h.select("b", "c") + h.select("b", "c") h.should eq({"a" => 3, "b" => 6, "c" => 9}) end end diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index ff08639d7802..f49b98afc3df 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -240,7 +240,6 @@ module HTTP it "will not retry when closed (non-block) (#12464)" do requests = 0 - server_channel = Channel(Nil).new client = HTTP::Client.new("127.0.0.1", 0) client.before_request do @@ -256,7 +255,6 @@ module HTTP it "will not retry when closed (block) (#12464)" do requests = 0 - server_channel = Channel(Nil).new client = HTTP::Client.new("127.0.0.1", 0) client.before_request do diff --git a/spec/std/http/headers_spec.cr b/spec/std/http/headers_spec.cr index 0960ab3dd1cd..7b83d06cfa94 100644 --- a/spec/std/http/headers_spec.cr +++ b/spec/std/http/headers_spec.cr @@ -20,7 +20,7 @@ describe HTTP::Headers do it "raises an error if header value contains invalid character" do expect_raises ArgumentError do - headers = HTTP::Headers{"invalid-header" => "\r\nLocation: http://example.com"} + HTTP::Headers{"invalid-header" => "\r\nLocation: http://example.com"} end end diff --git a/spec/std/log/context_spec.cr b/spec/std/log/context_spec.cr index a8bd618d1a12..3d813af1abe5 100644 --- a/spec/std/log/context_spec.cr +++ b/spec/std/log/context_spec.cr @@ -41,7 +41,7 @@ describe Log do Log.context.set a: 1 done = Channel(Nil).new - f = spawn do + spawn do Log.context.metadata.should eq(Log::Metadata.new) Log.context.set b: 2 Log.context.metadata.should eq(m({b: 2})) diff --git a/spec/std/log/dispatch_spec.cr b/spec/std/log/dispatch_spec.cr index 1bf5c2260fd2..ff6b656d325d 100644 --- a/spec/std/log/dispatch_spec.cr +++ b/spec/std/log/dispatch_spec.cr @@ -15,7 +15,7 @@ class Log it "dispatches entry" do backend = Log::MemoryBackend.new backend.dispatcher = DirectDispatcher - backend.dispatch entry = Entry.new("source", :info, "message", Log::Metadata.empty, nil) + backend.dispatch Entry.new("source", :info, "message", Log::Metadata.empty, nil) backend.entries.size.should eq(1) end end @@ -24,7 +24,7 @@ class Log it "dispatches entry" do backend = Log::MemoryBackend.new backend.dispatcher = SyncDispatcher.new - backend.dispatch entry = Entry.new("source", :info, "message", Log::Metadata.empty, nil) + backend.dispatch Entry.new("source", :info, "message", Log::Metadata.empty, nil) backend.entries.size.should eq(1) end end @@ -33,14 +33,14 @@ class Log it "dispatches entry" do backend = Log::MemoryBackend.new backend.dispatcher = AsyncDispatcher.new - backend.dispatch entry = Entry.new("source", :info, "message", Log::Metadata.empty, nil) + backend.dispatch Entry.new("source", :info, "message", Log::Metadata.empty, nil) retry { backend.entries.size.should eq(1) } end it "wait for entries to flush before closing" do backend = Log::MemoryBackend.new backend.dispatcher = AsyncDispatcher.new - backend.dispatch entry = Entry.new("source", :info, "message", Log::Metadata.empty, nil) + backend.dispatch Entry.new("source", :info, "message", Log::Metadata.empty, nil) backend.close backend.entries.size.should eq(1) end @@ -48,7 +48,7 @@ class Log it "can be closed twice" do backend = Log::MemoryBackend.new backend.dispatcher = AsyncDispatcher.new - backend.dispatch entry = Entry.new("source", :info, "message", Log::Metadata.empty, nil) + backend.dispatch Entry.new("source", :info, "message", Log::Metadata.empty, nil) backend.close backend.close backend.entries.size.should eq(1) diff --git a/spec/std/oauth2/session_spec.cr b/spec/std/oauth2/session_spec.cr index 27fcf48d637f..27ec848542a8 100644 --- a/spec/std/oauth2/session_spec.cr +++ b/spec/std/oauth2/session_spec.cr @@ -5,8 +5,8 @@ module OAuth2 typeof(begin client = Client.new "localhost", "client_id", "client_secret", redirect_uri: "uri", authorize_uri: "/baz" token = OAuth2::AccessToken::Bearer.new("token", 3600) - session = Session.new(client, token) { |s| } - session = Session.new(client, token, Time.utc) { |s| } + session = Session.new(client, token) { |_| } + session = Session.new(client, token, Time.utc) { |_| } session.authenticate(HTTP::Client.new("localhost")) end) end diff --git a/spec/std/proc_spec.cr b/spec/std/proc_spec.cr index 0fd2ea55c9d3..87bea44c0422 100644 --- a/spec/std/proc_spec.cr +++ b/spec/std/proc_spec.cr @@ -17,13 +17,11 @@ describe "Proc" do end it "does to_s" do - str = IO::Memory.new f = ->(x : Int32) { x.to_f } f.to_s.should eq("#") end it "does to_s when closured" do - str = IO::Memory.new a = 1.5 f = ->(x : Int32) { x + a } f.to_s.should eq("#") diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index 3a3448bc495a..b0746ef914c8 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -58,7 +58,7 @@ describe TCPSocket, tags: "network" do TCPServer.open("localhost", port) do |server| TCPSocket.open("localhost", port) do |client| - sock = server.accept + server.accept end end end diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 056bcad1a82d..2fb8abf15ee2 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -1332,7 +1332,7 @@ describe "String" do end it "subs char with string" do - replaced = "foobar".sub do |char| + "foobar".sub do |char| char.should eq 'f' "some" end.should eq("someoobar") diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 81edacfd0dda..058108a25710 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -1499,7 +1499,6 @@ module Crystal end # First accept all yield expressions and assign them to block vars - i = 0 unless node.exps.empty? exp_values = Array({LLVM::Value, Type}).new(node.exps.size) diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 16d950f49bd3..03310b305ad3 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -38,8 +38,6 @@ class Crystal::CodeGenVisitor func.varargs? ) - p2 = new_fun.params.to_a - func.params.to_a.each_with_index do |p1, index| attrs = new_fun.attributes(index + 1) new_fun.add_attribute(attrs, index + 1) unless attrs.value == 0 @@ -125,7 +123,6 @@ class Crystal::CodeGenVisitor if @debug.variables? && !target_def.naked? in_alloca_block do - args_offset = !is_fun_literal && self_type.passed_as_self? ? 2 : 1 location = target_def.location context.vars.each do |name, var| next if var.debug_variable_created diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index 278c22324efe..a7f6125079d7 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -748,7 +748,7 @@ class Crystal::CodeGenVisitor old_debug_location = @current_debug_location if @debug.line_numbers? && (location = node.location) - set_current_debug_location(node.location) + set_current_debug_location(location) end if type.element_type.has_inner_pointers? diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index a3ad34ba0967..29bcbb471e42 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -210,7 +210,7 @@ class Crystal::Command output_filename = Crystal.temp_executable(config.output_filename) - result = config.compile output_filename + config.compile output_filename unless config.compiler.no_codegen? report_warnings diff --git a/src/compiler/crystal/command/cursor.cr b/src/compiler/crystal/command/cursor.cr index dc4666aa6585..6ea2d3be0317 100644 --- a/src/compiler/crystal/command/cursor.cr +++ b/src/compiler/crystal/command/cursor.cr @@ -7,31 +7,30 @@ class Crystal::Command private def implementations cursor_command("tool implementations") do |location, config, result| - result = ImplementationsVisitor.new(location).process(result) + ImplementationsVisitor.new(location).process(result) end end private def context cursor_command("tool context") do |location, config, result| - result = ContextVisitor.new(location).process(result) + ContextVisitor.new(location).process(result) end end private def expand cursor_command("tool expand", no_cleanup: true, wants_doc: true) do |location, config, result| - result = ExpandVisitor.new(location).process(result) + ExpandVisitor.new(location).process(result) end end private def cursor_command(command, no_cleanup = false, wants_doc = false) - config, result = compile_no_codegen command, cursor_command: true, no_cleanup: no_cleanup, wants_doc: wants_doc + config, result = compile_no_codegen command, + cursor_command: true, + no_cleanup: no_cleanup, + wants_doc: wants_doc format = config.output_format - file = "" - line = "" - col = "" - loc = config.cursor_location.not_nil!.split(':') if loc.size != 3 error "cursor location must be file:line:column" diff --git a/src/compiler/crystal/command/repl.cr b/src/compiler/crystal/command/repl.cr index d277332125a8..6148f9c020ac 100644 --- a/src/compiler/crystal/command/repl.cr +++ b/src/compiler/crystal/command/repl.cr @@ -7,7 +7,7 @@ class Crystal::Command private def repl repl = Repl.new - option_parser = parse_with_crystal_opts do |opts| + parse_with_crystal_opts do |opts| opts.banner = "Usage: crystal i [options] [programfile] [arguments]\n\nOptions:" opts.on("-D FLAG", "--define FLAG", "Define a compile-time flag") do |flag| diff --git a/src/compiler/crystal/command/spec.cr b/src/compiler/crystal/command/spec.cr index e9535e9bcd4b..ab5aaad9caa3 100644 --- a/src/compiler/crystal/command/spec.cr +++ b/src/compiler/crystal/command/spec.cr @@ -95,7 +95,7 @@ class Crystal::Command output_filename = Crystal.temp_executable "spec" ENV["CRYSTAL_SPEC_COMPILER_BIN"] ||= Process.executable_path - result = compiler.compile sources, output_filename + compiler.compile sources, output_filename report_warnings execute output_filename, options, compiler, error_on_exit: warnings_fail_on_exit? end diff --git a/src/compiler/crystal/exception.cr b/src/compiler/crystal/exception.cr index 40652d7c3c30..1d45c44ce3f8 100644 --- a/src/compiler/crystal/exception.cr +++ b/src/compiler/crystal/exception.cr @@ -261,7 +261,7 @@ module Crystal source_slice = source.lines[from_index...to_index] source_slice, spaces_removed = minimize_indentation(source_slice) - io << Crystal.with_line_numbers(source_slice, line_number, @color, line_number_start = from_index + 1) + io << Crystal.with_line_numbers(source_slice, line_number, @color, from_index + 1) offset = OFFSET_FROM_LINE_NUMBER_DECORATOR + line_number.to_s.chars.size - spaces_removed append_error_indicator(io, offset, @column_number, @size) end diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index cb3ef35da3cb..f0bb9739ec03 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -981,7 +981,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor end private def read_closured_var_pointer(closured_var : ClosuredVar, *, node : ASTNode?) - indexes, type = closured_var.indexes, closured_var.type + indexes = closured_var.indexes # First load the closure pointer closure_var_index = get_closure_var_index @@ -2474,9 +2474,6 @@ class Crystal::Repl::Compiler < Crystal::Visitor # We don't want pointer.value to return a copy of something # if we are calling through it call_obj = call_obj.not_nil! - - element_type = call_obj.type.as(PointerInstanceType).element_type - request_value(call_obj) return end @@ -2687,8 +2684,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor local_var = lookup_local_var_or_closured_var(exp.name) case local_var in LocalVar - index, type = local_var.index, local_var.type - pointerof_var(index, node: node) + pointerof_var(local_var.index, node: node) in ClosuredVar node.raise "BUG: missing interpreter out closured var" end @@ -2754,8 +2750,6 @@ class Crystal::Repl::Compiler < Crystal::Visitor compiled_def.local_vars.declare(arg.name, var_type) end - a_def = @def - needs_closure_context = (target_def.vars.try &.any? { |name, var| var.type? && var.closure_in?(target_def) }) # Declare the closure context arg and var, if any diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 743170ebfddc..e55f194cd8f7 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -2500,7 +2500,7 @@ private def interpret_array_or_tuple_method(object, klass, method, args, named_a case arg = from when Crystal::NumberLiteral index = arg.to_number.to_i - value = object.elements[index]? || Crystal::NilLiteral.new + object.elements[index]? || Crystal::NilLiteral.new when Crystal::RangeLiteral range = arg.interpret_to_nilable_range(interpreter) begin diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index f2b75022fa61..1c0a70430965 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -138,7 +138,7 @@ module Crystal types["NoReturn"] = @no_return = NoReturnType.new self, self, "NoReturn" types["Void"] = @void = VoidType.new self, self, "Void" - types["Nil"] = nil_t = @nil = NilType.new self, self, "Nil", value, 1 + types["Nil"] = @nil = NilType.new self, self, "Nil", value, 1 types["Bool"] = @bool = BoolType.new self, self, "Bool", value, 1 types["Char"] = @char = CharType.new self, self, "Char", value, 4 diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index 60ec3b56e7d9..a9e3585ab320 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -471,7 +471,6 @@ module Crystal # bind all previously related local vars to it so that # they get all types assigned to it. local_vars.each &.bind_to self - local_vars = nil end # True if this variable belongs to the given context diff --git a/src/compiler/crystal/semantic/bindings.cr b/src/compiler/crystal/semantic/bindings.cr index fbc0983b5251..1705b4688f63 100644 --- a/src/compiler/crystal/semantic/bindings.cr +++ b/src/compiler/crystal/semantic/bindings.cr @@ -576,7 +576,7 @@ module Crystal node_type = node.type? return unless node_type - if node.is_a?(Path) && (target_const = node.target_const) + if node.is_a?(Path) && node.target_const node.raise "can't use constant as type for NamedTuple" end diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index aa975e8b06c5..e36f5953ea4e 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -730,7 +730,7 @@ class Crystal::Call end def def_full_name(owner, a_def, arg_types = nil) - Call.def_full_name(owner, a_def, arg_types = nil) + Call.def_full_name(owner, a_def, arg_types) end def self.def_full_name(owner, a_def, arg_types = nil) @@ -778,7 +778,7 @@ class Crystal::Call end if arg_default = arg.default_value str << " = " - str << arg.default_value + str << arg_default end printed = true end @@ -805,8 +805,6 @@ class Crystal::Call end def raise_matches_not_found_for_virtual_metaclass_new(owner) - arg_types = args.map &.type - owner.each_concrete_type do |concrete_type| defs = concrete_type.instance_type.lookup_defs_with_modules("initialize") defs = defs.select { |a_def| a_def.args.size != args.size } @@ -819,8 +817,7 @@ class Crystal::Call end def check_macro_wrong_number_of_arguments(def_name) - obj = self.obj - return if obj && !obj.is_a?(Path) + return if (obj = self.obj) && !obj.is_a?(Path) macros = in_macro_target &.lookup_macros(def_name) return unless macros.is_a?(Array(Macro)) diff --git a/src/compiler/crystal/semantic/exception.cr b/src/compiler/crystal/semantic/exception.cr index 213a1239bf96..09a29438fbe0 100644 --- a/src/compiler/crystal/semantic/exception.cr +++ b/src/compiler/crystal/semantic/exception.cr @@ -140,7 +140,7 @@ module Crystal def default_message if line_number = @line_number - "#{@warning ? "warning" : "error"} in line #{@line_number}" + "#{@warning ? "warning" : "error"} in line #{line_number}" end end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 3dfa321b7407..734204742b85 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -934,7 +934,7 @@ module Crystal node.raise "can't use `yield` outside a method" end - if ctx = @fun_literal_context + if @fun_literal_context node.raise <<-MSG can't use `yield` inside a proc literal or captured block diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index f3c1bca256e7..79af0dc431bb 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -1537,7 +1537,7 @@ module Crystal output = other.output # Consider the case of a splat in the type vars - if inputs && (splat_given = inputs.any?(Splat)) + if inputs && inputs.any?(Splat) i = 0 inputs.each do |input| if input.is_a?(Splat) diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 508d15b32366..02aa925a3dbf 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -857,7 +857,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor node.raise "can only use 'private' for types" end when Assign - if (target = exp.target).is_a?(Path) + if exp.target.is_a?(Path) if node.modifier.private? return false else diff --git a/src/compiler/crystal/semantic/type_declaration_processor.cr b/src/compiler/crystal/semantic/type_declaration_processor.cr index 79ef150f908d..65451741fac3 100644 --- a/src/compiler/crystal/semantic/type_declaration_processor.cr +++ b/src/compiler/crystal/semantic/type_declaration_processor.cr @@ -561,7 +561,6 @@ struct Crystal::TypeDeclarationProcessor next if info.def.calls_super? && ancestor_non_nilable.try(&.includes?(instance_var)) unless info.try(&.instance_vars.try(&.includes?(instance_var))) - all_assigned = false # Remember that this variable wasn't initialized here, and later error # if it turns out to be non-nilable nilable_vars = @nilable_instance_vars[owner] ||= {} of String => InitializeInfo diff --git a/src/compiler/crystal/semantic/type_guess_visitor.cr b/src/compiler/crystal/semantic/type_guess_visitor.cr index 994e5e357977..64c84ebcb888 100644 --- a/src/compiler/crystal/semantic/type_guess_visitor.cr +++ b/src/compiler/crystal/semantic/type_guess_visitor.cr @@ -813,7 +813,6 @@ module Crystal # Try to guess from the method's body, but now # the current lookup type is obj_type - type = nil old_type_override = @type_override @type_override = obj_type diff --git a/src/compiler/crystal/semantic/type_to_restriction.cr b/src/compiler/crystal/semantic/type_to_restriction.cr index 0a08019f79fc..910d31f44ea1 100644 --- a/src/compiler/crystal/semantic/type_to_restriction.cr +++ b/src/compiler/crystal/semantic/type_to_restriction.cr @@ -93,7 +93,6 @@ module Crystal end def convert(type : GenericInstanceType) - generic_type = type.generic_type path = type_to_path(type.generic_type) type_vars = type.type_vars.map do |name, type_var| if type_var.is_a?(NumberLiteral) diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 733aa6d775ac..dc689059d3ba 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -2293,10 +2293,8 @@ module Crystal def skip_macro_whitespace start = current_pos while current_char.ascii_whitespace? - whitespace = true if current_char == '\n' incr_line_number 0 - beginning_of_line = true end next_char end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 770cd92324dd..4ee7871f06ae 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -701,7 +701,6 @@ module Crystal end_location = token_end_location @wants_regex = false - has_parentheses = false next_token space_consumed = false @@ -2607,8 +2606,6 @@ module Crystal first_value = parse_op_assign skip_space_or_newline - end_location = nil - entries = [] of NamedTupleLiteral::Entry entries << NamedTupleLiteral::Entry.new(first_key, first_value) @@ -3483,7 +3480,6 @@ module Crystal receiver = nil @yields = nil - name_line_number = @token.line_number name_location = @token.location receiver_location = @token.location end_location = token_end_location @@ -3775,7 +3771,6 @@ module Crystal if splat && (@token.type.op_comma? || @token.type.op_rparen?) param_name = "" - uses_param = false allow_restrictions = false else param_location = @token.location @@ -3877,9 +3872,6 @@ module Crystal @uses_block_arg = true if uses_param end - inputs = nil - output = nil - if @token.type.op_colon? next_token_skip_space_or_newline diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 2224514360e2..417854fcf925 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -202,7 +202,6 @@ module Crystal end old_indent = @indent - base_indent = old_indent next_needs_indent = false has_newline = false @@ -220,7 +219,6 @@ module Crystal @indent += 2 write_line unless wrote_newline next_token_skip_space_or_newline - base_indent = @indent next_needs_indent = true has_newline = true end @@ -238,7 +236,6 @@ module Crystal end end has_begin = true - base_indent = @indent next_needs_indent = true has_newline = true end @@ -555,7 +552,6 @@ module Crystal @last_is_heredoc = is_heredoc heredoc_line = @line - heredoc_end = @line # To detect the first content of interpolation of string literal correctly, # we should consume the first string token if this token contains only removed indentation of heredoc. @@ -851,7 +847,6 @@ module Crystal write_space_at_end = true end - start_line = @line if next_needs_indent write_indent(offset, element) else @@ -1475,7 +1470,7 @@ module Crystal skip_space write_token " ", :OP_COLON, " " skip_space_or_newline - accept node.return_type.not_nil! + accept return_type end if free_vars = node.free_vars @@ -2071,7 +2066,6 @@ module Crystal write_macro_slashes write "{% " - macro_state = @macro_state next_token_skip_space_or_newline write_keyword :for, " " @@ -2714,7 +2708,6 @@ module Crystal next_token if @token.type.op_lparen? write "=(" - has_parentheses = true slash_is_regex! next_token format_call_args(node, true, base_indent) @@ -2733,7 +2726,6 @@ module Crystal ends_with_newline = false has_args = !node.args.empty? || node.named_args - column = @indent has_newlines = false found_comment = false diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index f45b87acffd6..000cbffba2f8 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -34,7 +34,7 @@ struct Exception::CallStack def self.setup_crash_handler LibC.AddVectoredExceptionHandler(1, ->(exception_info) do - case status = exception_info.value.exceptionRecord.value.exceptionCode + case exception_info.value.exceptionRecord.value.exceptionCode when LibC::EXCEPTION_ACCESS_VIOLATION addr = exception_info.value.exceptionRecord.value.exceptionInformation[1] Crystal::System.print_error "Invalid memory access (C0000005) at address 0x%llx\n", addr diff --git a/src/float/printer/grisu3.cr b/src/float/printer/grisu3.cr index 1daa1be65775..7d82d03d2f40 100644 --- a/src/float/printer/grisu3.cr +++ b/src/float/printer/grisu3.cr @@ -315,8 +315,6 @@ module Float::Printer::Grisu3 # digits might correctly yield *v* when read again, the closest will be # computed. def grisu3(v : Float64 | Float32, buffer_p) : {Bool, Int32, Int32} - buffer = buffer_p.to_slice(128) - w = DiyFP.from_f_normalized(v) # boundary_minus and boundary_plus are the boundaries between v and its diff --git a/src/float/printer/ieee.cr b/src/float/printer/ieee.cr index f050cc6a8c18..0270d5977982 100644 --- a/src/float/printer/ieee.cr +++ b/src/float/printer/ieee.cr @@ -113,7 +113,6 @@ module Float::Printer::IEEE physical_significand_is_zero = (d64 & SIGNIFICAND_MASK_64) == 0 lower_bound_closer = physical_significand_is_zero && (exponent(d64) != DENORMAL_EXPONENT_64) - calc_denormal = denormal?(d64) f, e = if lower_bound_closer {(w.frac << 2) - 1, w.exp - 2} else @@ -132,7 +131,6 @@ module Float::Printer::IEEE physical_significand_is_zero = (d32 & SIGNIFICAND_MASK_32) == 0 lower_bound_closer = physical_significand_is_zero && (exponent(d32) != DENORMAL_EXPONENT_32) - calc_denormal = denormal?(d32) f, e = if lower_bound_closer {(w.frac << 2) - 1, w.exp - 2} else diff --git a/src/iterator.cr b/src/iterator.cr index 91f872f3f16a..d306555238ec 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -456,7 +456,7 @@ module Iterator(T) self.next else value = {last_elem, elem} - @last_elem, elem = elem, @last_elem + @last_elem = elem value end end diff --git a/src/llvm/abi/x86.cr b/src/llvm/abi/x86.cr index abc67c4a0471..7d9b04e41042 100644 --- a/src/llvm/abi/x86.cr +++ b/src/llvm/abi/x86.cr @@ -30,11 +30,11 @@ class LLVM::ABI::X86 < LLVM::ABI if osx? || windows? case target_data.abi_size(rty) - when 1 then ret_ty = ret_value(rty, context.int8) - when 2 then ret_ty = ret_value(rty, context.int16) - when 4 then ret_ty = ret_value(rty, context.int32) - when 8 then ret_ty = ret_value(rty, context.int64) - else ret_ty = ret_pointer(rty) + when 1 then ret_value(rty, context.int8) + when 2 then ret_value(rty, context.int16) + when 4 then ret_value(rty, context.int32) + when 8 then ret_value(rty, context.int64) + else ret_pointer(rty) end else ret_pointer(rty) diff --git a/src/llvm/abi/x86_64.cr b/src/llvm/abi/x86_64.cr index e7219127638d..418c8f672eea 100644 --- a/src/llvm/abi/x86_64.cr +++ b/src/llvm/abi/x86_64.cr @@ -23,7 +23,6 @@ class LLVM::ABI::X86_64 < LLVM::ABI ret_ty = ArgType.direct(context.void) end - arg_tys = Array(LLVM::Type).new(atys.size) arg_tys = atys.map do |arg_type| abi_type, needed_int_regs, needed_sse_regs = x86_64_type(arg_type, Attribute::ByVal, context) { |cls| pass_by_val?(cls) } if available_int_regs >= needed_int_regs && available_sse_regs >= needed_sse_regs diff --git a/src/path.cr b/src/path.cr index f7509acf171b..ba8517d6e56a 100644 --- a/src/path.cr +++ b/src/path.cr @@ -225,7 +225,9 @@ struct Path "." else # Path has a parent (ex. "a/a", "/home/user//", "C://Users/mmm") return String.new(slice[0, 1]) if pos == -1 - return anchor.to_s if windows? && pos == 1 && slice.unsafe_fetch(pos) === ':' && (anchor = self.anchor) + if windows? && pos == 1 && slice.unsafe_fetch(pos) === ':' && (anchor = self.anchor) + return anchor.to_s + end String.new(slice[0, pos + 1]) end end From 5fa604325d934cd3ba91ebde721e888bdaee3dc7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 31 Oct 2022 19:29:32 +0800 Subject: [PATCH 0099/1551] Eliminate `nil` from many predicate methods --- src/compiler/crystal/codegen/ast.cr | 2 +- src/compiler/crystal/codegen/types.cr | 4 ++-- src/compiler/crystal/semantic/ast.cr | 2 ++ .../crystal/semantic/cleanup_transformer.cr | 8 ++++---- src/compiler/crystal/semantic/conversions.cr | 2 +- src/compiler/crystal/semantic/restrictions.cr | 8 ++++---- .../crystal/semantic/top_level_visitor.cr | 2 +- src/compiler/crystal/tools/doc/generator.cr | 4 ++-- src/compiler/crystal/tools/doc/type.cr | 2 +- src/compiler/crystal/types.cr | 8 ++++---- src/time/format/custom/http_date.cr | 4 ++-- src/time/format/custom/yaml_date.cr | 18 +++++++++--------- src/uri.cr | 6 +++++- 13 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/compiler/crystal/codegen/ast.cr b/src/compiler/crystal/codegen/ast.cr index c36f55c30987..bae46133d4bd 100644 --- a/src/compiler/crystal/codegen/ast.cr +++ b/src/compiler/crystal/codegen/ast.cr @@ -3,7 +3,7 @@ require "../syntax/ast" module Crystal class ASTNode def no_returns? - type?.try &.no_return? + !!type?.try &.no_return? end end diff --git a/src/compiler/crystal/codegen/types.cr b/src/compiler/crystal/codegen/types.cr index df81ea966471..17f6551f7cd1 100644 --- a/src/compiler/crystal/codegen/types.cr +++ b/src/compiler/crystal/codegen/types.cr @@ -29,9 +29,9 @@ module Crystal when VirtualType self.struct? when NonGenericModuleType - self.including_types.try &.passed_by_value? + !!self.including_types.try &.passed_by_value? when GenericModuleInstanceType - self.including_types.try &.passed_by_value? + !!self.including_types.try &.passed_by_value? when GenericClassInstanceType self.generic_type.passed_by_value? when TypeDefType diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index a9e3585ab320..b7e3e3936ea9 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -34,6 +34,8 @@ module Crystal self_type.kind.bytesize <= 64 when FloatType self_type.kind.f32? + else + false end else false diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index fc195a75a750..2169c38475ba 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -434,15 +434,15 @@ module Crystal end private def void_lib_call?(node) - return unless node.is_a?(Call) + return false unless node.is_a?(Call) obj = node.obj - return unless obj.is_a?(Path) + return false unless obj.is_a?(Path) type = obj.type? - return unless type.is_a?(LibType) + return false unless type.is_a?(LibType) - node.type?.try &.nil_type? + !!node.type?.try &.nil_type? end def transform(node : Global) diff --git a/src/compiler/crystal/semantic/conversions.cr b/src/compiler/crystal/semantic/conversions.cr index 2239cce5edb3..98e014bdb0ec 100644 --- a/src/compiler/crystal/semantic/conversions.cr +++ b/src/compiler/crystal/semantic/conversions.cr @@ -44,6 +44,6 @@ module Crystal::Conversions end def self.to_unsafe_lookup_failed?(ex) - ex.message.try(&.includes?("undefined method 'to_unsafe'")) + !!ex.message.try(&.includes?("undefined method 'to_unsafe'")) end end diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 79af0dc431bb..1bb477bd26e5 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -945,7 +945,7 @@ module Crystal return true end - parents.try &.any? &.restriction_of?(other, owner, self_free_vars, other_free_vars) + !!parents.try &.any? &.restriction_of?(other, owner, self_free_vars, other_free_vars) end def restriction_of?(other : AliasType, owner, self_free_vars = nil, other_free_vars = nil) @@ -1055,9 +1055,9 @@ module Crystal # to e.g. AbstractDefChecker; generic instances shall behave like AST # nodes when def restrictions are considered, i.e. all generic type # variables are covariant. - return nil unless type_var.type.implements?(other_type_var.type) + return false unless type_var.type.implements?(other_type_var.type) else - return nil unless type_var == other_type_var + return false unless type_var == other_type_var end end @@ -1669,7 +1669,7 @@ module Crystal end # Disallow casting a function to another one accepting different argument count - return nil if arg_types.size != other.arg_types.size + return false if arg_types.size != other.arg_types.size arg_types.zip(other.arg_types) do |arg_type, other_arg_type| return false unless arg_type == other_arg_type diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 02aa925a3dbf..e0793cae61e1 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -1057,7 +1057,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def has_hooks?(type_with_hooks) hooks = type_with_hooks.as?(ModuleType).try &.hooks - hooks && !hooks.empty? + !hooks.nil? && !hooks.empty? end def run_hooks(type_with_hooks, current_type, kind : HookKind, node, call = nil) diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index b02a85abc86d..9e2a22a25506 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -146,7 +146,7 @@ class Crystal::Doc::Generator # Don't include lib types or types inside a lib type return false if type.is_a?(Crystal::LibType) || type.namespace.is_a?(LibType) - type.locations.try &.any? do |type_location| + !!type.locations.try &.any? do |type_location| must_include? type_location end end @@ -179,7 +179,7 @@ class Crystal::Doc::Generator return false if nodoc? const return true if crystal_builtin?(const) - const.locations.try &.any? { |location| must_include? location } + !!const.locations.try &.any? { |location| must_include? location } end def must_include?(location : Crystal::Location) diff --git a/src/compiler/crystal/tools/doc/type.cr b/src/compiler/crystal/tools/doc/type.cr index 51558aeb1a48..d8748ef898e3 100644 --- a/src/compiler/crystal/tools/doc/type.cr +++ b/src/compiler/crystal/tools/doc/type.cr @@ -571,7 +571,7 @@ class Crystal::Doc::Type return false unless node.is_a?(Path) match = lookup_path(node) - match && match.type == @generator.program.nil_type + !!match.try &.type == @generator.program.nil_type end def node_to_html(node, io, html : HTMLOption = :all) diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index c99f4b6cf512..fbeea4b4e73a 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -292,7 +292,7 @@ module Crystal when VirtualMetaclassType implements?(other_type.base_type.metaclass) else - parents.try &.any? &.implements?(other_type) + !!parents.try &.any? &.implements?(other_type) end end @@ -339,7 +339,7 @@ module Crystal # Returns true if `self` and *other* are in the same namespace. def same_namespace?(other) top_namespace(self) == top_namespace(other) || - parents.try &.any? { |parent| parent.same_namespace?(other) } + !!parents.try &.any? { |parent| parent.same_namespace?(other) } end private def top_namespace(type) @@ -462,11 +462,11 @@ module Crystal end def has_def?(name) - has_def_without_parents?(name) || parents.try(&.any?(&.has_def?(name))) + has_def_without_parents?(name) || !!parents.try(&.any?(&.has_def?(name))) end def has_def_without_parents?(name) - defs.try(&.has_key?(name)) + !!defs.try(&.has_key?(name)) end record DefInMacroLookup diff --git a/src/time/format/custom/http_date.cr b/src/time/format/custom/http_date.cr index 70ca4dba7193..d9ca38b9d7e5 100644 --- a/src/time/format/custom/http_date.cr +++ b/src/time/format/custom/http_date.cr @@ -94,8 +94,8 @@ struct Time::Format !ansi_c_format && current_char.ascii_whitespace? end - def http_date_short_day_name_with_comma? : Bool? - return unless current_char.ascii_letter? + def http_date_short_day_name_with_comma? : Bool + return false unless current_char.ascii_letter? short_day_name diff --git a/src/time/format/custom/yaml_date.cr b/src/time/format/custom/yaml_date.cr index 636778e5ea1f..ea5e081ecb1d 100644 --- a/src/time/format/custom/yaml_date.cr +++ b/src/time/format/custom/yaml_date.cr @@ -44,19 +44,19 @@ struct Time::Format if (year = consume_number?(4)) && char?('-') @year = year else - return nil + return false end if (month = consume_number?(2)) && char?('-') @month = month else - return nil + return false end if day = consume_number?(2) @day = day else - return nil + return false end case current_char @@ -71,7 +71,7 @@ struct Time::Format end else if @reader.has_next? - return nil + return false end end @@ -82,19 +82,19 @@ struct Time::Format if (hour = consume_number?(2)) && char?(':') @hour = hour else - return nil + return false end if (minute = consume_number?(2)) && char?(':') @minute = minute else - return nil + return false end if second = consume_number?(2) @second = second else - return nil + return false end second_fraction? @@ -105,10 +105,10 @@ struct Time::Format begin time_zone_z_or_offset(force_zero_padding: false, force_minutes: false) rescue Time::Format::Error - return nil + return false end - return nil if @reader.has_next? + return false if @reader.has_next? end true diff --git a/src/uri.cr b/src/uri.cr index 996c1953d461..36a407e1d35b 100644 --- a/src/uri.cr +++ b/src/uri.cr @@ -718,6 +718,10 @@ class URI # Returns `true` if this URI's *port* is the default port for # its *scheme*. private def default_port? - (scheme = @scheme) && (port = @port) && port == URI.default_port(scheme) + if (scheme = @scheme) && (port = @port) + port == URI.default_port(scheme) + else + false + end end end From b31b04bce75305907ab43418f08ccd6e45e5f154 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 31 Oct 2022 10:56:03 -0300 Subject: [PATCH 0100/1551] Compiler: ignore type filters when accepting cast obj and to (#12668) --- spec/compiler/semantic/nilable_cast_spec.cr | 12 ++++++++++++ src/compiler/crystal/semantic/main_visitor.cr | 8 ++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/spec/compiler/semantic/nilable_cast_spec.cr b/spec/compiler/semantic/nilable_cast_spec.cr index 526ff6e7c581..f2b46110f415 100644 --- a/spec/compiler/semantic/nilable_cast_spec.cr +++ b/spec/compiler/semantic/nilable_cast_spec.cr @@ -74,4 +74,16 @@ describe "Semantic: nilable cast" do base.as?(Moo) )) { union_of([types["Foo"], types["Bar"], nil_type] of Type) } end + + it "doesn't introduce type filter for nilable cast object (#12661)" do + assert_type(%( + val = 1 || false + + if val.as?(Char) + true + else + val + end + )) { union_of(int32, bool) } + end end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 734204742b85..014a2bec64fa 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -1766,10 +1766,14 @@ module Crystal typed_def.raises = true end - node.obj.accept self + ignoring_type_filters do + node.obj.accept self + end @in_type_args += 1 - node.to.accept self + ignoring_type_filters do + node.to.accept self + end @in_type_args -= 1 node.obj.add_observer node From 4cbcf762243aaa286f66d5f002e000c699f4bae5 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 31 Oct 2022 21:58:22 +0800 Subject: [PATCH 0101/1551] Leverage `GC.malloc_atomic` for XML (#12692) --- src/xml/libxml2.cr | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/xml/libxml2.cr b/src/xml/libxml2.cr index d30281ad733b..f2e4574e94f1 100644 --- a/src/xml/libxml2.cr +++ b/src/xml/libxml2.cr @@ -317,7 +317,12 @@ end LibXML.xmlGcMemSetup( ->GC.free, ->GC.malloc(LibC::SizeT), - ->GC.malloc(LibC::SizeT), + # TODO(interpreted): remove this condition + {% if flag?(:interpreted) %} + ->GC.malloc(LibC::SizeT) + {% else %} + ->GC.malloc_atomic(LibC::SizeT) + {% end %}, ->GC.realloc(Void*, LibC::SizeT), ->(str) { len = LibC.strlen(str) + 1 From 42a3f91335852613824a6a2587da6e590b540518 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 1 Nov 2022 18:32:33 +0800 Subject: [PATCH 0102/1551] Use single helper method to pass UTF-16 strings to Windows (#12695) --- src/compiler/crystal/loader/msvc.cr | 2 +- src/crystal/system/win32/dir.cr | 12 ++++------- src/crystal/system/win32/env.cr | 20 ++++++++++-------- src/crystal/system/win32/file.cr | 30 ++++++++++++--------------- src/crystal/system/win32/file_info.cr | 4 ---- src/crystal/system/win32/process.cr | 4 ++-- src/crystal/system/windows.cr | 4 ++++ src/exception/call_stack/stackwalk.cr | 2 +- 8 files changed, 36 insertions(+), 42 deletions(-) diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index 69358ce6afd0..41cbba0397e3 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -89,7 +89,7 @@ class Crystal::Loader end private def open_library(path : String) - LibC.LoadLibraryExW(path.check_no_null_byte.to_utf16, nil, 0) + LibC.LoadLibraryExW(System.to_wstr(path), nil, 0) end def load_current_program_handle diff --git a/src/crystal/system/win32/dir.cr b/src/crystal/system/win32/dir.cr index db8a375d5c7c..88d1babe2cd4 100644 --- a/src/crystal/system/win32/dir.cr +++ b/src/crystal/system/win32/dir.cr @@ -18,7 +18,7 @@ module Crystal::System::Dir raise ::File::Error.from_os_error("Error opening directory", Errno::ENOENT, file: path) end - DirHandle.new(LibC::INVALID_HANDLE_VALUE, to_windows_path(path + "\\*")) + DirHandle.new(LibC::INVALID_HANDLE_VALUE, System.to_wstr(path + "\\*")) end def self.next_entry(dir : DirHandle, path : String) : Entry? @@ -101,7 +101,7 @@ module Crystal::System::Dir end def self.current=(path : String) : String - if LibC.SetCurrentDirectoryW(to_windows_path(path)) == 0 + if LibC.SetCurrentDirectoryW(System.to_wstr(path)) == 0 raise ::File::Error.from_winerror("Error while changing directory", file: path) end @@ -124,13 +124,13 @@ module Crystal::System::Dir end def self.create(path : String, mode : Int32) : Nil - if LibC._wmkdir(to_windows_path(path)) == -1 + if LibC._wmkdir(System.to_wstr(path)) == -1 raise ::File::Error.from_errno("Unable to create directory", file: path) end end def self.delete(path : String, *, raise_on_missing : Bool) : Bool - return true if LibC._wrmdir(to_windows_path(path)) == 0 + return true if LibC._wrmdir(System.to_wstr(path)) == 0 if !raise_on_missing && Errno.value == Errno::ENOENT false @@ -138,8 +138,4 @@ module Crystal::System::Dir raise ::File::Error.from_errno("Unable to remove directory", file: path) end end - - private def self.to_windows_path(path : String) : LibC::LPWSTR - path.check_no_null_byte.to_utf16.to_unsafe - end end diff --git a/src/crystal/system/win32/env.cr b/src/crystal/system/win32/env.cr index 6306cfb8a634..490ad70a1ea1 100644 --- a/src/crystal/system/win32/env.cr +++ b/src/crystal/system/win32/env.cr @@ -5,33 +5,33 @@ require "c/processenv" module Crystal::System::Env # Sets an environment variable or unsets it if *value* is `nil`. def self.set(key : String, value : String) : Nil - key.check_no_null_byte("key") - value.check_no_null_byte("value") + key = System.to_wstr(key, "key") + value = System.to_wstr(value, "value") - if LibC.SetEnvironmentVariableW(key.to_utf16, value.to_utf16) == 0 + if LibC.SetEnvironmentVariableW(key, value) == 0 raise RuntimeError.from_winerror("SetEnvironmentVariableW") end end # Unsets an environment variable. def self.set(key : String, value : Nil) : Nil - key.check_no_null_byte("key") + key = System.to_wstr(key, "key") - if LibC.SetEnvironmentVariableW(key.to_utf16, nil) == 0 + if LibC.SetEnvironmentVariableW(key, nil) == 0 raise RuntimeError.from_winerror("SetEnvironmentVariableW") end end # Gets an environment variable. def self.get(key : String) : String? - key.check_no_null_byte("key") + key = System.to_wstr(key, "key") System.retry_wstr_buffer do |buffer, small_buf| # `GetEnvironmentVariableW` doesn't set last error on success but we need # a success message in order to identify if length == 0 means not found or # the value is an empty string. LibC.SetLastError(WinError::ERROR_SUCCESS) - length = LibC.GetEnvironmentVariableW(key.to_utf16, buffer, buffer.size) + length = LibC.GetEnvironmentVariableW(key, buffer, buffer.size) if 0 < length < buffer.size return String.from_utf16(buffer[0, length]) @@ -52,10 +52,10 @@ module Crystal::System::Env # Returns `true` if environment variable is set. def self.has_key?(key : String) : Bool - key.check_no_null_byte("key") + key = System.to_wstr(key, "key") buffer = uninitialized UInt16[1] - LibC.GetEnvironmentVariableW(key.to_utf16, buffer, buffer.size) != 0 + LibC.GetEnvironmentVariableW(key, buffer, buffer.size) != 0 end # Iterates all environment variables. @@ -79,6 +79,8 @@ module Crystal::System::Env # Used internally to create an input for `CreateProcess` `lpEnvironment`. def self.make_env_block(env : Enumerable({String, String})) + # NOTE: the entire string contains embedded null bytes so we can't use + # `System.to_wstr` here String.build do |io| env.each do |(key, value)| if key.includes?('=') || key.empty? diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index c36ab3480297..6cd51cd529f6 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -19,7 +19,7 @@ module Crystal::System::File perm = LibC::S_IREAD end - fd = LibC._wopen(to_windows_path(filename), oflag, perm) + fd = LibC._wopen(System.to_wstr(filename), oflag, perm) if fd == -1 raise ::File::Error.from_errno("Error opening file with mode '#{mode}'", file: filename) end @@ -31,7 +31,7 @@ module Crystal::System::File path = "#{dir}#{::File::SEPARATOR}#{prefix}.#{::Random::Secure.hex}#{suffix}" mode = LibC::O_RDWR | LibC::O_CREAT | LibC::O_EXCL | LibC::O_BINARY | LibC::O_NOINHERIT - fd = LibC._wopen(to_windows_path(path), mode, ::File::DEFAULT_CREATE_PERMISSIONS) + fd = LibC._wopen(System.to_wstr(path), mode, ::File::DEFAULT_CREATE_PERMISSIONS) if fd == -1 raise ::File::Error.from_errno("Error creating temporary file", file: path) end @@ -57,7 +57,7 @@ module Crystal::System::File end def self.info?(path : String, follow_symlinks : Bool) : ::File::Info? - winpath = to_windows_path(path) + winpath = System.to_wstr(path) unless follow_symlinks # First try using GetFileAttributes to check if it's a reparse point @@ -85,7 +85,7 @@ module Crystal::System::File end handle = LibC.CreateFileW( - to_windows_path(path), + System.to_wstr(path), LibC::FILE_READ_ATTRIBUTES, LibC::FILE_SHARE_READ | LibC::FILE_SHARE_WRITE | LibC::FILE_SHARE_DELETE, nil, @@ -128,7 +128,7 @@ module Crystal::System::File end private def self.accessible?(path, mode) - LibC._waccess_s(to_windows_path(path), mode) == 0 + LibC._waccess_s(System.to_wstr(path), mode) == 0 end def self.chown(path : String, uid : Int32, gid : Int32, follow_symlinks : Bool) : Nil @@ -144,7 +144,7 @@ module Crystal::System::File # TODO: dereference symlinks - attributes = LibC.GetFileAttributesW(to_windows_path(path)) + attributes = LibC.GetFileAttributesW(System.to_wstr(path)) if attributes == LibC::INVALID_FILE_ATTRIBUTES raise ::File::Error.from_winerror("Error changing permissions", file: path) end @@ -157,7 +157,7 @@ module Crystal::System::File attributes |= LibC::FILE_ATTRIBUTE_READONLY end - if LibC.SetFileAttributesW(to_windows_path(path), attributes) == 0 + if LibC.SetFileAttributesW(System.to_wstr(path), attributes) == 0 raise ::File::Error.from_winerror("Error changing permissions", file: path) end end @@ -168,7 +168,7 @@ module Crystal::System::File end def self.delete(path : String, *, raise_on_missing : Bool) : Bool - if LibC._wunlink(to_windows_path(path)) == 0 + if LibC._wunlink(System.to_wstr(path)) == 0 true elsif !raise_on_missing && Errno.value == Errno::ENOENT false @@ -179,7 +179,7 @@ module Crystal::System::File def self.realpath(path : String) : String # TODO: read links using https://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx - win_path = to_windows_path(path) + win_path = System.to_wstr(path) realpath = System.retry_wstr_buffer do |buffer, small_buf| len = LibC.GetFullPathNameW(win_path, buffer.size, buffer, nil) @@ -200,14 +200,14 @@ module Crystal::System::File end def self.link(old_path : String, new_path : String) : Nil - if LibC.CreateHardLinkW(to_windows_path(new_path), to_windows_path(old_path), nil) == 0 + if LibC.CreateHardLinkW(System.to_wstr(new_path), System.to_wstr(old_path), nil) == 0 raise ::File::Error.from_winerror("Error creating link", file: old_path, other: new_path) end end def self.symlink(old_path : String, new_path : String) : Nil # TODO: support directory symlinks (copy Go's stdlib logic here) - if LibC.CreateSymbolicLinkW(to_windows_path(new_path), to_windows_path(old_path), LibC::SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) == 0 + if LibC.CreateSymbolicLinkW(System.to_wstr(new_path), System.to_wstr(old_path), LibC::SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) == 0 raise ::File::Error.from_winerror("Error creating symlink", file: old_path, other: new_path) end end @@ -217,7 +217,7 @@ module Crystal::System::File end def self.rename(old_path : String, new_path : String) : ::File::Error? - if LibC.MoveFileExW(to_windows_path(old_path), to_windows_path(new_path), LibC::MOVEFILE_REPLACE_EXISTING) == 0 + if LibC.MoveFileExW(System.to_wstr(old_path), System.to_wstr(new_path), LibC::MOVEFILE_REPLACE_EXISTING) == 0 ::File::Error.from_winerror("Error renaming file", file: old_path, other: new_path) end end @@ -226,7 +226,7 @@ module Crystal::System::File atime = Crystal::System::Time.to_filetime(access_time) mtime = Crystal::System::Time.to_filetime(modification_time) handle = LibC.CreateFileW( - to_windows_path(path), + System.to_wstr(path), LibC::FILE_WRITE_ATTRIBUTES, LibC::FILE_SHARE_READ | LibC::FILE_SHARE_WRITE | LibC::FILE_SHARE_DELETE, nil, @@ -269,10 +269,6 @@ module Crystal::System::File raise NotImplementedError.new("File#flock_unlock") end - private def self.to_windows_path(path : String) : LibC::LPWSTR - path.check_no_null_byte.to_utf16.to_unsafe - end - private def system_fsync(flush_metadata = true) : Nil if LibC._commit(fd) != 0 raise IO::Error.from_errno("Error syncing file") diff --git a/src/crystal/system/win32/file_info.cr b/src/crystal/system/win32/file_info.cr index bc588bc38e8f..10a163c44644 100644 --- a/src/crystal/system/win32/file_info.cr +++ b/src/crystal/system/win32/file_info.cr @@ -89,8 +89,4 @@ module Crystal::System::FileInfo @file_attributes.nFileIndexHigh == other.file_attributes.nFileIndexHigh && @file_attributes.nFileIndexLow == other.file_attributes.nFileIndexLow end - - private def to_windows_path(path : String) : LibC::LPWSTR - path.check_no_null_byte.to_utf16.to_unsafe - end end diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 75c7e581e425..291a66d228e6 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -125,8 +125,8 @@ struct Crystal::System::Process process_info = LibC::PROCESS_INFORMATION.new if LibC.CreateProcessW( - nil, command_args.check_no_null_byte.to_utf16, nil, nil, true, LibC::CREATE_UNICODE_ENVIRONMENT, - make_env_block(env, clear_env), chdir.try &.check_no_null_byte.to_utf16, + nil, System.to_wstr(command_args), nil, nil, true, LibC::CREATE_UNICODE_ENVIRONMENT, + make_env_block(env, clear_env), chdir.try { |str| System.to_wstr(str) }, pointerof(startup_info), pointerof(process_info) ) == 0 error = WinError.value diff --git a/src/crystal/system/windows.cr b/src/crystal/system/windows.cr index a2bdd28226f0..5fcbf7a96503 100644 --- a/src/crystal/system/windows.cr +++ b/src/crystal/system/windows.cr @@ -9,4 +9,8 @@ module Crystal::System yield buffer, false raise "BUG: retry_wstr_buffer returned" end + + def self.to_wstr(str : String, name : String? = nil) : LibC::LPWSTR + str.check_no_null_byte(name).to_utf16.to_unsafe + end end diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index 000cbffba2f8..2a580b498eff 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -25,7 +25,7 @@ struct Exception::CallStack # `at_exit` because unhandled exceptions in `main_user_code` are printed # after those handlers) executable_path = Process.executable_path - executable_path_ptr = executable_path ? File.dirname(executable_path).to_utf16.to_unsafe : Pointer(LibC::WCHAR).null + executable_path_ptr = executable_path ? Crystal::System.to_wstr(File.dirname(executable_path)) : Pointer(LibC::WCHAR).null if LibC.SymInitializeW(LibC.GetCurrentProcess, executable_path_ptr, 1) == 0 raise RuntimeError.from_winerror("SymInitializeW") end From 47ae3d75465cda5e571f3dcd137d8d4b9817103d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Tue, 1 Nov 2022 03:34:41 -0700 Subject: [PATCH 0103/1551] Lexer: fix global capture vars ending with zero, e.g. `$10?` (#12701) --- spec/compiler/lexer/lexer_spec.cr | 4 +++- src/compiler/crystal/syntax/lexer.cr | 11 +++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/spec/compiler/lexer/lexer_spec.cr b/spec/compiler/lexer/lexer_spec.cr index d6d7868c52c1..7cc0478a89f5 100644 --- a/spec/compiler/lexer/lexer_spec.cr +++ b/spec/compiler/lexer/lexer_spec.cr @@ -284,7 +284,9 @@ describe "Lexer" do ":^", ":~", ":**", ":>>", ":<<", ":%", ":[]", ":[]?", ":[]=", ":<=>", ":===", ":&+", ":&-", ":&*", ":&**"] - it_lexes_global_match_data_index ["$1", "$10", "$1?", "$23?"] + it_lexes_global_match_data_index ["$1", "$10", "$1?", "$10?", "$23?"] + assert_syntax_error "$01", %(unexpected token: "1") + assert_syntax_error "$0?" it_lexes "$~", :OP_DOLLAR_TILDE it_lexes "$?", :OP_DOLLAR_QUESTION diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index dc689059d3ba..de74db370247 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -827,14 +827,13 @@ module Crystal @token.type = :OP_DOLLAR_QUESTION when .ascii_number? start = current_pos - char = next_char - if char == '0' - char = next_char + if current_char == '0' + next_char else - while char.ascii_number? - char = next_char + while next_char.ascii_number? + # Nothing to do end - char = next_char if char == '?' + next_char if current_char == '?' end @token.type = :GLOBAL_MATCH_DATA_INDEX @token.value = string_range_from_pool(start) From 916adf70bfd184b67c7731f529de04c54337751c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 1 Nov 2022 20:55:27 +0800 Subject: [PATCH 0104/1551] Opt in to new overload ordering behavior in Makefile (#12703) --- Makefile | 2 +- Makefile.win | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3b648a8f96ff..cbd4e42445cf 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ check ?= ## Enable only check when running format O := .build SOURCES := $(shell find src -name '*.cr') SPEC_SOURCES := $(shell find spec -name '*.cr') -override FLAGS += -D strict_multi_assign $(if $(release),--release )$(if $(stats),--stats )$(if $(progress),--progress )$(if $(threads),--threads $(threads) )$(if $(debug),-d )$(if $(static),--static )$(if $(LDFLAGS),--link-flags="$(LDFLAGS)" )$(if $(target),--cross-compile --target $(target) )$(if $(interpreter),,-Dwithout_interpreter ) +override FLAGS += -D strict_multi_assign -D preview_overload_order $(if $(release),--release )$(if $(stats),--stats )$(if $(progress),--progress )$(if $(threads),--threads $(threads) )$(if $(debug),-d )$(if $(static),--static )$(if $(LDFLAGS),--link-flags="$(LDFLAGS)" )$(if $(target),--cross-compile --target $(target) )$(if $(interpreter),,-Dwithout_interpreter ) SPEC_WARNINGS_OFF := --exclude-warnings spec/std --exclude-warnings spec/compiler --exclude-warnings spec/primitives SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) ) CRYSTAL_CONFIG_LIBRARY_PATH := '$$ORIGIN/../lib/crystal' diff --git a/Makefile.win b/Makefile.win index f4d93769700d..b05c3056eb8a 100644 --- a/Makefile.win +++ b/Makefile.win @@ -47,7 +47,7 @@ RMDIR = if exist $1 rd /S /Q $1 O := .build SOURCES := $(call GLOB,src\\*.cr) SPEC_SOURCES := $(call GLOB,spec\\*.cr) -override FLAGS += -D strict_multi_assign $(if $(release),--release )$(if $(stats),--stats )$(if $(progress),--progress )$(if $(threads),--threads $(threads) )$(if $(debug),-d )$(if $(static),--static )$(if $(LDFLAGS),--link-flags="$(LDFLAGS)" )$(if $(target),--cross-compile --target $(target) )$(if $(interpreter),,-Dwithout_interpreter ) +override FLAGS += -D strict_multi_assign -D preview_overload_order $(if $(release),--release )$(if $(stats),--stats )$(if $(progress),--progress )$(if $(threads),--threads $(threads) )$(if $(debug),-d )$(if $(static),--static )$(if $(LDFLAGS),--link-flags="$(LDFLAGS)" )$(if $(target),--cross-compile --target $(target) )$(if $(interpreter),,-Dwithout_interpreter ) SPEC_WARNINGS_OFF := --exclude-warnings spec\std --exclude-warnings spec\compiler --exclude-warnings spec\primitives SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) ) CRYSTAL_CONFIG_LIBRARY_PATH := $$ORIGIN\lib From 7f870d988d92f676bc95695fcb5d0f3f5ce34efb Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Wed, 2 Nov 2022 03:56:04 +0800 Subject: [PATCH 0105/1551] Fix method lookup for single char class names (#12683) --- .../crystal/tools/doc/doc_renderer_spec.cr | 15 +++++++++++++++ .../crystal/tools/doc/markd_doc_renderer.cr | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr b/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr index 64b69773e708..d8d179a05d51 100644 --- a/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr +++ b/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr @@ -57,6 +57,12 @@ describe Doc::MarkdDocRenderer do def foo end end + + class A + def foo; end + def bar; end + def self.baz; end + end ", wants_doc: true).program generator = Doc::Generator.new(program, [""]) @@ -66,6 +72,8 @@ describe Doc::MarkdDocRenderer do sub_foo = sub.lookup_method("foo").not_nil! nested = generator.type(program.types["Base"].types["Nested"]) nested_foo = nested.lookup_method("foo").not_nil! + single_char_class = generator.type(program.types["A"]) + single_char_class_foo = single_char_class.lookup_method("foo").not_nil! it "finds sibling methods" do {base, base_foo}.each do |obj| @@ -81,6 +89,13 @@ describe Doc::MarkdDocRenderer do end end + it "matches methods on single-character class names" do + {single_char_class, single_char_class_foo}.each do |obj| + assert_code_link(obj, "#bar", %(#bar)) + assert_code_link(obj, ".baz", %(.baz)) + end + end + it "doesn't spuriously match range literals" do {base, base_foo}.each do |obj| assert_code_link(obj, "(0..baz)") diff --git a/src/compiler/crystal/tools/doc/markd_doc_renderer.cr b/src/compiler/crystal/tools/doc/markd_doc_renderer.cr index c3def230323a..f703d7ed787b 100644 --- a/src/compiler/crystal/tools/doc/markd_doc_renderer.cr +++ b/src/compiler/crystal/tools/doc/markd_doc_renderer.cr @@ -74,9 +74,9 @@ class Crystal::Doc::MarkdDocRenderer < Markd::HTMLRenderer # Check Type#method(...) or Type or #method(...) text.gsub %r( - ((?:\B::)?\b[A-Z]\w+(?:\:\:[A-Z]\w+)*|\B|(?<=\bself))(?+\-*\/\[\]&|?!^~]+[?!]?)(?:\((.*?)\))? + ((?:\B::)?\b[A-Z]\w*(?:\:\:[A-Z]\w*)*|\B|(?<=\bself))(?+\-*\/\[\]&|?!^~]+[?!]?)(?:\((.*?)\))? | - ((?:\B::)?\b[A-Z]\w+(?:\:\:[A-Z]\w+)*) + ((?:\B::)?\b[A-Z]\w*(?:\:\:[A-Z]\w*)*) )x do |match_text| if $5? # Type From 85ef4af470c2958063cf4f43c8c5fd33ccacd4d2 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Wed, 2 Nov 2022 03:56:52 +0800 Subject: [PATCH 0106/1551] Simplify sequential character checks in Crystal lexer (#12699) --- src/compiler/crystal/syntax/lexer.cr | 219 ++++++++++++++------------- 1 file changed, 115 insertions(+), 104 deletions(-) diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index de74db370247..a163238c1271 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -114,11 +114,7 @@ module Crystal start = current_pos # Check # pragma comment - if next_char_no_column_increment == '<' && - next_char_no_column_increment == 'l' && - next_char_no_column_increment == 'o' && - next_char_no_column_increment == 'c' && - next_char_no_column_increment == ':' + if char_sequence?('<', 'l', 'o', 'c', ':', column_increment: false) next_char_no_column_increment consume_loc_pragma start = current_pos @@ -851,11 +847,11 @@ module Crystal when 'a' case next_char when 'b' - if next_char == 's' && next_char == 't' && next_char == 'r' && next_char == 'a' && next_char == 'c' && next_char == 't' + if char_sequence?('s', 't', 'r', 'a', 'c', 't') return check_ident_or_keyword(:abstract, start) end when 'l' - if next_char == 'i' && next_char == 'a' && next_char == 's' + if char_sequence?('i', 'a', 's') return check_ident_or_keyword(:alias, start) end when 's' @@ -874,7 +870,7 @@ module Crystal return check_ident_or_keyword(:as, start) end when 'n' - if next_char == 'n' && next_char == 'o' && next_char == 't' && next_char == 'a' && next_char == 't' && next_char == 'i' && next_char == 'o' && next_char == 'n' + if char_sequence?('n', 'o', 't', 'a', 't', 'i', 'o', 'n') return check_ident_or_keyword(:annotation, start) end else @@ -884,11 +880,11 @@ module Crystal when 'b' case next_char when 'e' - if next_char == 'g' && next_char == 'i' && next_char == 'n' + if char_sequence?('g', 'i', 'n') return check_ident_or_keyword(:begin, start) end when 'r' - if next_char == 'e' && next_char == 'a' && next_char == 'k' + if char_sequence?('e', 'a', 'k') return check_ident_or_keyword(:break, start) end else @@ -898,11 +894,11 @@ module Crystal when 'c' case next_char when 'a' - if next_char == 's' && next_char == 'e' + if char_sequence?('s', 'e') return check_ident_or_keyword(:case, start) end when 'l' - if next_char == 'a' && next_char == 's' && next_char == 's' + if char_sequence?('a', 's', 's') return check_ident_or_keyword(:class, start) end else @@ -942,7 +938,7 @@ module Crystal when 'd' return check_ident_or_keyword(:end, start) when 's' - if next_char == 'u' && next_char == 'r' && next_char == 'e' + if char_sequence?('u', 'r', 'e') return check_ident_or_keyword(:ensure, start) end when 'u' @@ -953,7 +949,7 @@ module Crystal # scan_ident end when 'x' - if next_char == 't' && next_char == 'e' && next_char == 'n' && next_char == 'd' + if char_sequence?('t', 'e', 'n', 'd') return check_ident_or_keyword(:extend, start) end else @@ -963,7 +959,7 @@ module Crystal when 'f' case next_char when 'a' - if next_char == 'l' && next_char == 's' && next_char == 'e' + if char_sequence?('l', 's', 'e') return check_ident_or_keyword(:false, start) end when 'o' @@ -986,11 +982,11 @@ module Crystal if ident_part_or_end?(peek_next_char) case next_char when 'c' - if next_char == 'l' && next_char == 'u' && next_char == 'd' && next_char == 'e' + if char_sequence?('l', 'u', 'd', 'e') return check_ident_or_keyword(:include, start) end when 's' - if next_char == 't' && next_char == 'a' && next_char == 'n' && next_char == 'c' && next_char == 'e' && next_char == '_' && next_char == 's' && next_char == 'i' && next_char == 'z' && next_char == 'e' && next_char == 'o' && next_char == 'f' + if char_sequence?('t', 'a', 'n', 'c', 'e', '_', 's', 'i', 'z', 'e', 'o', 'f') return check_ident_or_keyword(:instance_sizeof, start) end else @@ -1003,7 +999,7 @@ module Crystal return @token end when 's' - if next_char == '_' && next_char == 'a' && next_char == '?' + if char_sequence?('_', 'a', '?') return check_ident_or_keyword(:is_a_question, start) end else @@ -1023,13 +1019,13 @@ module Crystal when 'm' case next_char when 'a' - if next_char == 'c' && next_char == 'r' && next_char == 'o' + if char_sequence?('c', 'r', 'o') return check_ident_or_keyword(:macro, start) end when 'o' case next_char when 'd' - if next_char == 'u' && next_char == 'l' && next_char == 'e' + if char_sequence?('u', 'l', 'e') return check_ident_or_keyword(:module, start) end else @@ -1042,7 +1038,7 @@ module Crystal when 'n' case next_char when 'e' - if next_char == 'x' && next_char == 't' + if char_sequence?('x', 't') return check_ident_or_keyword(:next, start) end when 'i' @@ -1066,7 +1062,7 @@ module Crystal when 'f' if peek_next_char == 'f' next_char - if next_char == 's' && next_char == 'e' && next_char == 't' && next_char == 'o' && next_char == 'f' + if char_sequence?('s', 'e', 't', 'o', 'f') return check_ident_or_keyword(:offsetof, start) end else @@ -1083,17 +1079,17 @@ module Crystal when 'p' case next_char when 'o' - if next_char == 'i' && next_char == 'n' && next_char == 't' && next_char == 'e' && next_char == 'r' && next_char == 'o' && next_char == 'f' + if char_sequence?('i', 'n', 't', 'e', 'r', 'o', 'f') return check_ident_or_keyword(:pointerof, start) end when 'r' case next_char when 'i' - if next_char == 'v' && next_char == 'a' && next_char == 't' && next_char == 'e' + if char_sequence?('v', 'a', 't', 'e') return check_ident_or_keyword(:private, start) end when 'o' - if next_char == 't' && next_char == 'e' && next_char == 'c' && next_char == 't' && next_char == 'e' && next_char == 'd' + if char_sequence?('t', 'e', 'c', 't', 'e', 'd') return check_ident_or_keyword(:protected, start) end else @@ -1110,22 +1106,22 @@ module Crystal when 's' case next_char when 'c' - if next_char == 'u' && next_char == 'e' + if char_sequence?('u', 'e') return check_ident_or_keyword(:rescue, start) end when 'p' - if next_char == 'o' && next_char == 'n' && next_char == 'd' && next_char == 's' && next_char == '_' && next_char == 't' && next_char == 'o' && next_char == '?' + if char_sequence?('o', 'n', 'd', 's', '_', 't', 'o', '?') return check_ident_or_keyword(:responds_to_question, start) end else # scan_ident end when 't' - if next_char == 'u' && next_char == 'r' && next_char == 'n' + if char_sequence?('u', 'r', 'n') return check_ident_or_keyword(:return, start) end when 'q' - if next_char == 'u' && next_char == 'i' && next_char == 'r' && next_char == 'e' + if char_sequence?('u', 'i', 'r', 'e') return check_ident_or_keyword(:require, start) end else @@ -1141,7 +1137,7 @@ module Crystal if next_char == 'l' case next_char when 'e' - if next_char == 'c' && next_char == 't' + if char_sequence?('c', 't') return check_ident_or_keyword(:select, start) end when 'f' @@ -1151,15 +1147,15 @@ module Crystal end end when 'i' - if next_char == 'z' && next_char == 'e' && next_char == 'o' && next_char == 'f' + if char_sequence?('z', 'e', 'o', 'f') return check_ident_or_keyword(:sizeof, start) end when 't' - if next_char == 'r' && next_char == 'u' && next_char == 'c' && next_char == 't' + if char_sequence?('r', 'u', 'c', 't') return check_ident_or_keyword(:struct, start) end when 'u' - if next_char == 'p' && next_char == 'e' && next_char == 'r' + if char_sequence?('p', 'e', 'r') return check_ident_or_keyword(:super, start) end else @@ -1169,15 +1165,15 @@ module Crystal when 't' case next_char when 'h' - if next_char == 'e' && next_char == 'n' + if char_sequence?('e', 'n') return check_ident_or_keyword(:then, start) end when 'r' - if next_char == 'u' && next_char == 'e' + if char_sequence?('u', 'e') return check_ident_or_keyword(:true, start) end when 'y' - if next_char == 'p' && next_char == 'e' + if char_sequence?('p', 'e') if peek_next_char == 'o' next_char if next_char == 'f' @@ -1201,18 +1197,18 @@ module Crystal return check_ident_or_keyword(:union, start) end when 'n' - if next_char == 'i' && next_char == 't' && next_char == 'i' && next_char == 'a' && next_char == 'l' && next_char == 'i' && next_char == 'z' && next_char == 'e' && next_char == 'd' + if char_sequence?('i', 't', 'i', 'a', 'l', 'i', 'z', 'e', 'd') return check_ident_or_keyword(:uninitialized, start) end else # scan_ident end when 'l' - if next_char == 'e' && next_char == 's' && next_char == 's' + if char_sequence?('e', 's', 's') return check_ident_or_keyword(:unless, start) end when 't' - if next_char == 'i' && next_char == 'l' + if char_sequence?('i', 'l') return check_ident_or_keyword(:until, start) end else @@ -1221,7 +1217,7 @@ module Crystal end scan_ident(start) when 'v' - if next_char == 'e' && next_char == 'r' && next_char == 'b' && next_char == 'a' && next_char == 't' && next_char == 'i' && next_char == 'm' + if char_sequence?('e', 'r', 'b', 'a', 't', 'i', 'm') return check_ident_or_keyword(:verbatim, start) end scan_ident(start) @@ -1234,14 +1230,14 @@ module Crystal return check_ident_or_keyword(:when, start) end when 'i' - if next_char == 'l' && next_char == 'e' + if char_sequence?('l', 'e') return check_ident_or_keyword(:while, start) end else # scan_ident end when 'i' - if next_char == 't' && next_char == 'h' + if char_sequence?('t', 'h') return check_ident_or_keyword(:with, start) end else @@ -1249,7 +1245,7 @@ module Crystal end scan_ident(start) when 'y' - if next_char == 'i' && next_char == 'e' && next_char == 'l' && next_char == 'd' + if char_sequence?('i', 'e', 'l', 'd') return check_ident_or_keyword(:yield, start) end scan_ident(start) @@ -1258,7 +1254,7 @@ module Crystal when '_' case next_char when 'D' - if next_char == 'I' && next_char == 'R' && next_char == '_' && next_char == '_' + if char_sequence?('I', 'R', '_', '_') if ident_part_or_end?(peek_next_char) scan_ident(start) else @@ -1268,7 +1264,7 @@ module Crystal end end when 'E' - if next_char == 'N' && next_char == 'D' && next_char == '_' && next_char == 'L' && next_char == 'I' && next_char == 'N' && next_char == 'E' && next_char == '_' && next_char == '_' + if char_sequence?('N', 'D', '_', 'L', 'I', 'N', 'E', '_', '_') if ident_part_or_end?(peek_next_char) scan_ident(start) else @@ -1278,7 +1274,7 @@ module Crystal end end when 'F' - if next_char == 'I' && next_char == 'L' && next_char == 'E' && next_char == '_' && next_char == '_' + if char_sequence?('I', 'L', 'E', '_', '_') if ident_part_or_end?(peek_next_char) scan_ident(start) else @@ -1288,7 +1284,7 @@ module Crystal end end when 'L' - if next_char == 'I' && next_char == 'N' && next_char == 'E' && next_char == '_' && next_char == '_' + if char_sequence?('I', 'N', 'E', '_', '_') if ident_part_or_end?(peek_next_char) scan_ident(start) else @@ -1374,7 +1370,7 @@ module Crystal def skip_comment char = current_char - while char != '\n' && char != '\0' + while !char.in?('\n', '\0') char = next_char_no_column_increment end end @@ -1388,8 +1384,7 @@ module Crystal when ' ', '\t' next_char when '\\' - case next_char - when '\r', '\n' + if next_char.in?('\r', '\n') handle_slash_r_slash_n_or_slash_n next_char incr_line_number @@ -1858,13 +1853,7 @@ module Crystal string_end = delimiter_state.end string_nest = delimiter_state.nest - while current_char != string_end && - current_char != string_nest && - current_char != '\0' && - current_char != '\\' && - current_char != '#' && - current_char != '\r' && - current_char != '\n' + while !current_char.in?(string_end, string_nest, '\0', '\\', '#', '\r', '\n') next_char end @@ -1876,7 +1865,7 @@ module Crystal old_pos = current_pos old_column = @column_number - while current_char == ' ' || current_char == '\t' + while current_char.in?(' ', '\t') next_char end @@ -1960,12 +1949,12 @@ module Crystal case char when 'e' - if next_char == 'n' && next_char == 'd' && !ident_part_or_end?(peek_next_char) + if char_sequence?('n', 'd') && !ident_part_or_end?(peek_next_char) next_char nest -= 1 end when 'f' - if next_char == 'o' && next_char == 'r' && !ident_part_or_end?(peek_next_char) + if char_sequence?('o', 'r') && !ident_part_or_end?(peek_next_char) next_char nest += 1 end @@ -1975,7 +1964,7 @@ module Crystal nest += 1 end when 'u' - if next_char == 'n' && next_char == 'l' && next_char == 'e' && next_char == 's' && next_char == 's' && !ident_part_or_end?(peek_next_char) + if char_sequence?('n', 'l', 'e', 's', 's') && !ident_part_or_end?(peek_next_char) next_char nest += 1 end @@ -2060,27 +2049,27 @@ module Crystal if !delimiter_state && current_char == '%' && ident_start?(peek_next_char) char = next_char - if char == 'q' && (peek = peek_next_char) && peek.in?('(', '<', '[', '{', '|') + if char == 'q' && peek_next_char.in?('(', '<', '[', '{', '|') next_char delimiter_state = Token::DelimiterState.new(:string, char, closing_char, 1) next_char - elsif char == 'Q' && (peek = peek_next_char) && peek.in?('(', '<', '[', '{', '|') + elsif char == 'Q' && peek_next_char.in?('(', '<', '[', '{', '|') next_char delimiter_state = Token::DelimiterState.new(:string, char, closing_char, 1) next_char - elsif char == 'i' && (peek = peek_next_char) && peek.in?('(', '<', '[', '{', '|') + elsif char == 'i' && peek_next_char.in?('(', '<', '[', '{', '|') next_char delimiter_state = Token::DelimiterState.new(:symbol_array, char, closing_char, 1) next_char - elsif char == 'w' && (peek = peek_next_char) && peek.in?('(', '<', '[', '{', '|') + elsif char == 'w' && peek_next_char.in?('(', '<', '[', '{', '|') next_char delimiter_state = Token::DelimiterState.new(:string_array, char, closing_char, 1) next_char - elsif char == 'x' && (peek = peek_next_char) && peek.in?('(', '<', '[', '{', '|') + elsif char == 'x' && peek_next_char.in?('(', '<', '[', '{', '|') next_char delimiter_state = Token::DelimiterState.new(:command, char, closing_char, 1) next_char - elsif char == 'r' && (peek = peek_next_char) && peek.in?('(', '<', '[', '{', '|') + elsif char == 'r' && peek_next_char.in?('(', '<', '[', '{', '|') next_char delimiter_state = Token::DelimiterState.new(:regex, char, closing_char, 1) next_char @@ -2131,7 +2120,9 @@ module Crystal char = current_char - until char == '{' || char == '\0' || (char == '\\' && ((peek = peek_next_char) == '{' || peek == '%')) || (whitespace && !delimiter_state && char == 'e') + until char.in?('{', '\0') || + (char == '\\' && peek_next_char.in?('{', '%')) || + (whitespace && !delimiter_state && char == 'e') case char when '\n' incr_line_number 0 @@ -2218,7 +2209,7 @@ module Crystal next end else - if !delimiter_state && whitespace && lookahead { char == 'y' && next_char == 'i' && next_char == 'e' && next_char == 'l' && next_char == 'd' && !ident_part_or_end?(peek_next_char) } + if !delimiter_state && whitespace && lookahead { char == 'y' && char_sequence?('i', 'e', 'l', 'd') && !ident_part_or_end?(peek_next_char) } yields = true char = current_char whitespace = true @@ -2309,70 +2300,87 @@ module Crystal when 'a' case next_char when 'b' - if next_char == 's' && next_char == 't' && next_char == 'r' && next_char == 'a' && next_char == 'c' && next_char == 't' && next_char.whitespace? + if char_sequence?('s', 't', 'r', 'a', 'c', 't') && next_char.whitespace? case next_char when 'd' - next_char == 'e' && next_char == 'f' && peek_not_ident_part_or_end_next_char && :abstract_def + char_sequence?('e', 'f') && peek_not_ident_part_or_end_next_char && :abstract_def when 'c' - next_char == 'l' && next_char == 'a' && next_char == 's' && next_char == 's' && peek_not_ident_part_or_end_next_char && :abstract_class + char_sequence?('l', 'a', 's', 's') && peek_not_ident_part_or_end_next_char && :abstract_class when 's' - next_char == 't' && next_char == 'r' && next_char == 'u' && next_char == 'c' && next_char == 't' && peek_not_ident_part_or_end_next_char && :abstract_struct + char_sequence?('t', 'r', 'u', 'c', 't') && peek_not_ident_part_or_end_next_char && :abstract_struct else false end end when 'n' - next_char == 'n' && next_char == 'o' && next_char == 't' && next_char == 'a' && next_char == 't' && next_char == 'i' && next_char == 'o' && next_char == 'n' && peek_not_ident_part_or_end_next_char && :annotation + char_sequence?('n', 'o', 't', 'a', 't', 'i', 'o', 'n') && peek_not_ident_part_or_end_next_char && :annotation else false end when 'b' - next_char == 'e' && next_char == 'g' && next_char == 'i' && next_char == 'n' && peek_not_ident_part_or_end_next_char && :begin + char_sequence?('e', 'g', 'i', 'n') && peek_not_ident_part_or_end_next_char && :begin when 'c' - (char = next_char) && ( - (char == 'a' && next_char == 's' && next_char == 'e' && peek_not_ident_part_or_end_next_char && :case) || - (char == 'l' && next_char == 'a' && next_char == 's' && next_char == 's' && peek_not_ident_part_or_end_next_char && :class) - ) + case next_char + when 'a' + char_sequence?('s', 'e') && peek_not_ident_part_or_end_next_char && :case + when 'l' + char_sequence?('a', 's', 's') && peek_not_ident_part_or_end_next_char && :class + else + false + end when 'd' - (char = next_char) && - ((char == 'o' && peek_not_ident_part_or_end_next_char && :do) || - (char == 'e' && next_char == 'f' && peek_not_ident_part_or_end_next_char && :def)) + case next_char + when 'o' + peek_not_ident_part_or_end_next_char && :do + when 'e' + next_char == 'f' && peek_not_ident_part_or_end_next_char && :def + else + false + end when 'f' - next_char == 'u' && next_char == 'n' && peek_not_ident_part_or_end_next_char && :fun + char_sequence?('u', 'n') && peek_not_ident_part_or_end_next_char && :fun when 'i' - beginning_of_line && next_char == 'f' && - (char = next_char) && (!ident_part_or_end?(char) && :if) + beginning_of_line && next_char == 'f' && (char = next_char) && (!ident_part_or_end?(char) && :if) when 'l' - next_char == 'i' && next_char == 'b' && peek_not_ident_part_or_end_next_char && :lib + char_sequence?('i', 'b') && peek_not_ident_part_or_end_next_char && :lib when 'm' - (char = next_char) && ( - (char == 'a' && next_char == 'c' && next_char == 'r' && next_char == 'o' && peek_not_ident_part_or_end_next_char && :macro) || - (char == 'o' && next_char == 'd' && next_char == 'u' && next_char == 'l' && next_char == 'e' && peek_not_ident_part_or_end_next_char && :module) - ) + case next_char + when 'a' + char_sequence?('c', 'r', 'o') && peek_not_ident_part_or_end_next_char && :macro + when 'o' + char_sequence?('d', 'u', 'l', 'e') && peek_not_ident_part_or_end_next_char && :module + else + false + end when 's' case next_char when 'e' - next_char == 'l' && next_char == 'e' && next_char == 'c' && next_char == 't' && !ident_part_or_end?(peek_next_char) && next_char && :select + char_sequence?('l', 'e', 'c', 't') && !ident_part_or_end?(peek_next_char) && next_char && :select when 't' - next_char == 'r' && next_char == 'u' && next_char == 'c' && next_char == 't' && !ident_part_or_end?(peek_next_char) && next_char && :struct + char_sequence?('r', 'u', 'c', 't') && !ident_part_or_end?(peek_next_char) && next_char && :struct else false end when 'u' - next_char == 'n' && (char = next_char) && ( - (char == 'i' && next_char == 'o' && next_char == 'n' && peek_not_ident_part_or_end_next_char && :union) || - (beginning_of_line && char == 'l' && next_char == 'e' && next_char == 's' && next_char == 's' && peek_not_ident_part_or_end_next_char && :unless) || - (beginning_of_line && char == 't' && next_char == 'i' && next_char == 'l' && peek_not_ident_part_or_end_next_char && :until) - ) + next_char == 'n' && case next_char + when 'i' + char_sequence?('o', 'n') && peek_not_ident_part_or_end_next_char && :union + when 'l' + beginning_of_line && char_sequence?('e', 's', 's') && peek_not_ident_part_or_end_next_char && :unless + when 't' + beginning_of_line && char_sequence?('i', 'l') && peek_not_ident_part_or_end_next_char && :until + else + false + end when 'w' - beginning_of_line && next_char == 'h' && next_char == 'i' && next_char == 'l' && next_char == 'e' && peek_not_ident_part_or_end_next_char && :while + beginning_of_line && char_sequence?('h', 'i', 'l', 'e') && peek_not_ident_part_or_end_next_char && :while else false end end def check_heredoc_start - return nil unless current_char == '<' && next_char == '<' && next_char == '-' + return nil unless current_char == '<' && char_sequence?('<', '-') has_single_quote = false found_closing_single_quote = false @@ -2711,8 +2719,7 @@ module Crystal case current_char when 'o' - unless next_char_no_column_increment == 'p' && - next_char_no_column_increment == '>' + unless char_sequence?('p', '>', column_increment: false) raise %(expected #, # or #) end @@ -2723,9 +2730,7 @@ module Crystal pop_location when 'u' - unless next_char_no_column_increment == 's' && - next_char_no_column_increment == 'h' && - next_char_no_column_increment == '>' + unless char_sequence?('s', 'h', '>', column_increment: false) raise %(expected #, # or #) end @@ -2949,6 +2954,12 @@ module Crystal is_slash_r end + private def char_sequence?(*tokens, column_increment : Bool = true) + tokens.all? do |token| + token == (column_increment ? next_char : next_char_no_column_increment) + end + end + def unknown_token raise "unknown token: #{current_char.inspect}", @line_number, @column_number end From a14c46f34ee894f44a50934909ddb7d2074ad95a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Wed, 2 Nov 2022 06:56:00 -0700 Subject: [PATCH 0107/1551] Lexer: allow regex after CRLF --- spec/compiler/lexer/lexer_spec.cr | 20 ++++++++++++++++++++ src/compiler/crystal/syntax/lexer.cr | 1 + 2 files changed, 21 insertions(+) diff --git a/spec/compiler/lexer/lexer_spec.cr b/spec/compiler/lexer/lexer_spec.cr index 7cc0478a89f5..bcb47588a685 100644 --- a/spec/compiler/lexer/lexer_spec.cr +++ b/spec/compiler/lexer/lexer_spec.cr @@ -664,4 +664,24 @@ describe "Lexer" do assert_syntax_error %("\\x1z"), "invalid hex escape" assert_syntax_error %("hi\\) + + it "lexes regex after \\n" do + lexer = Lexer.new("\n/=/") + lexer.slash_is_regex = true + token = lexer.next_token + token.type.should eq(t :NEWLINE) + token = lexer.next_token + token.type.should eq(t :DELIMITER_START) + token.delimiter_state.kind.should eq(Token::DelimiterKind::REGEX) + end + + it "lexes regex after \\r\\n" do + lexer = Lexer.new("\r\n/=/") + lexer.slash_is_regex = true + token = lexer.next_token + token.type.should eq(t :NEWLINE) + token = lexer.next_token + token.type.should eq(t :DELIMITER_START) + token.delimiter_state.kind.should eq(Token::DelimiterKind::REGEX) + end end diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index a163238c1271..ad87830b361c 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -175,6 +175,7 @@ module Crystal next_char @token.type = :NEWLINE incr_line_number + reset_regex_flags = false consume_newlines else raise "expected '\\n' after '\\r'" From 66d4244f7aae6d520255ee9cabd18c07cdc886be Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Thu, 3 Nov 2022 02:14:55 +0800 Subject: [PATCH 0108/1551] Partial revert "Compiler: refactor and slightly optimize merging two types (#12436)" (#12709) This reverts part of commit bfc33dbda41ed4c3590d02bcdd781fcea5368fd3 The reverted part is 8d19346f5ca3394ed2e8f0367fe0d01734151d8f (Generalize the case of a non-union type merged with a union type) from #12436 --- src/compiler/crystal/semantic/type_merge.cr | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compiler/crystal/semantic/type_merge.cr b/src/compiler/crystal/semantic/type_merge.cr index 027af4fcfa23..9b2fe4e236f5 100644 --- a/src/compiler/crystal/semantic/type_merge.cr +++ b/src/compiler/crystal/semantic/type_merge.cr @@ -46,12 +46,11 @@ module Crystal return second if first.no_return? return first if second.no_return? - # Check if a non-union type is part of a union type - if !first.is_a?(UnionType) && second.is_a?(UnionType) && second.union_types.includes?(first) + if first.nil_type? && second.is_a?(UnionType) && second.union_types.includes?(first) return second end - if !second.is_a?(UnionType) && first.is_a?(UnionType) && first.union_types.includes?(second) + if second.nil_type? && first.is_a?(UnionType) && first.union_types.includes?(second) return first end From 23b5100f9b6e8eab20abaae6fa2b6b334731424f Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Thu, 3 Nov 2022 06:40:37 -0300 Subject: [PATCH 0109/1551] CI: Update to OpenSSL 3.0.7 for bundled lib on Windows (#12712) --- .github/workflows/win.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 785e774fbc10..12f27d75edca 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -246,15 +246,15 @@ jobs: libs/crypto.lib libs/ssl.lib libs/openssl_VERSION - key: win-openssl-libs-3.0.0-msvc-${{ env.VSCMD_VER }} + key: win-openssl-libs-3.0.7-msvc-${{ env.VSCMD_VER }} - name: Set up NASM if: steps.cache-openssl.outputs.cache-hit != 'true' uses: ilammy/setup-nasm@e2335e5fc95548c09cd2deea2768793e0e8f0941 # v1.2.1 - name: Download OpenSSL if: steps.cache-openssl.outputs.cache-hit != 'true' run: | - iwr https://www.openssl.org/source/openssl-3.0.0.tar.gz -OutFile openssl.tar.gz - (Get-FileHash -Algorithm SHA256 .\openssl.tar.gz).hash -eq "59eedfcb46c25214c9bd37ed6078297b4df01d012267fe9e9eee31f61bc70536" + iwr https://www.openssl.org/source/openssl-3.0.7.tar.gz -OutFile openssl.tar.gz + (Get-FileHash -Algorithm SHA256 .\openssl.tar.gz).hash -eq "83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e" 7z x openssl.tar.gz 7z x openssl.tar mv openssl-* openssl @@ -271,7 +271,7 @@ jobs: run: | cp openssl/libcrypto.lib libs/crypto.lib cp openssl/libssl.lib libs/ssl.lib - [IO.File]::WriteAllLines("libs/openssl_VERSION", "3.0.0") + [IO.File]::WriteAllLines("libs/openssl_VERSION", "3.0.7") - name: Cache LLVM id: cache-llvm From a96fefdb91150835fe378d9d2c22e91bf99e766d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 29 Oct 2022 20:30:36 +0200 Subject: [PATCH 0110/1551] Fix `VirtualMetaclassType#implements?` to ignore base_type (#12632) --- spec/compiler/codegen/cast_spec.cr | 12 ++++++++++++ spec/compiler/codegen/is_a_spec.cr | 12 ++++++++++++ spec/compiler/semantic/metaclass_spec.cr | 12 ++++++++++++ src/compiler/crystal/types.cr | 2 +- 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/spec/compiler/codegen/cast_spec.cr b/spec/compiler/codegen/cast_spec.cr index 0ddf65f5b919..1260ab2ffe43 100644 --- a/spec/compiler/codegen/cast_spec.cr +++ b/spec/compiler/codegen/cast_spec.cr @@ -423,4 +423,16 @@ describe "Code gen: cast" do A.as(A.class) )) end + + it "cast virtual metaclass type to nilable virtual instance type (#12628)" do + run(<<-CR).to_b.should be_true + abstract struct Base + end + + struct Impl < Base + end + + Base.as(Base | Base.class).as?(Base | Impl).nil? + CR + end end diff --git a/spec/compiler/codegen/is_a_spec.cr b/spec/compiler/codegen/is_a_spec.cr index e2e9deb89605..2d751bc81c15 100644 --- a/spec/compiler/codegen/is_a_spec.cr +++ b/spec/compiler/codegen/is_a_spec.cr @@ -922,4 +922,16 @@ describe "Codegen: is_a?" do b.is_a?(B(Int32)) )).to_b.should be_true end + + it "virtual metaclass type is not virtual instance type (#12628)" do + run(<<-CR).to_b.should be_false + abstract struct Base + end + + struct Impl < Base + end + + Base.as(Base | Base.class).is_a?(Base | Impl) + CR + end end diff --git a/spec/compiler/semantic/metaclass_spec.cr b/spec/compiler/semantic/metaclass_spec.cr index ebf1b2817599..aa2457a3e255 100644 --- a/spec/compiler/semantic/metaclass_spec.cr +++ b/spec/compiler/semantic/metaclass_spec.cr @@ -134,6 +134,18 @@ describe "Semantic: metaclass" do bar_class.should be_a_proper_subtype_of(foo_class) end + it "virtual metaclass type with virtual type (#12628)" do + mod = semantic(%( + class Base; end + class Impl < Base; end + )).program + + base = mod.types["Base"] + base.virtual_type!.metaclass.implements?(base).should be_false + base.virtual_type!.metaclass.implements?(base.metaclass).should be_true + base.virtual_type!.metaclass.implements?(base.metaclass.virtual_type!).should be_true + end + it "generic classes (1)" do mod = semantic(%( class Foo(T); end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 2e0b522c5b98..b0b334addda0 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -3513,7 +3513,7 @@ module Crystal end def implements?(other_type) - super || base_type.implements?(other_type) + base_type.metaclass.implements?(other_type) end def to_s_with_options(io : IO, skip_union_parens : Bool = false, generic_args : Bool = true, codegen : Bool = false) : Nil From 39986700f8c600e83c55171bb65f38f849303154 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 30 Oct 2022 06:27:26 -0300 Subject: [PATCH 0111/1551] Compiler: handle yield exps without a type (#12679) --- spec/compiler/codegen/block_spec.cr | 17 +++++++++++++++++ .../crystal/semantic/cleanup_transformer.cr | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/spec/compiler/codegen/block_spec.cr b/spec/compiler/codegen/block_spec.cr index aaad134a9ee7..d62b49f03042 100644 --- a/spec/compiler/codegen/block_spec.cr +++ b/spec/compiler/codegen/block_spec.cr @@ -1587,4 +1587,21 @@ describe "Code gen: block" do end )).to_i.should eq(1) end + + it "doesn't crash if yield exp has no type (#12670)" do + codegen(%( + def foo : String? + end + + def bar + while res = foo + yield res + end + end + + bar do |res| + res + end + )) + end end diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 3c4d0cd81e2a..bdff752d02e4 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -723,7 +723,7 @@ module Crystal # If the yield has a no-return expression, the yield never happens: # replace it with a series of expressions up to the one that no-returns. - no_return_index = node.exps.index &.no_returns? + no_return_index = node.exps.index { |exp| !exp.type? || exp.no_returns? } if no_return_index exps = Expressions.new(node.exps[0, no_return_index + 1]) exps.bind_to(exps.expressions.last) From d76cdeea2cd8207c359a9a71913295554cece70a Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 31 Oct 2022 10:56:03 -0300 Subject: [PATCH 0112/1551] Compiler: ignore type filters when accepting cast obj and to (#12668) --- spec/compiler/semantic/nilable_cast_spec.cr | 12 ++++++++++++ src/compiler/crystal/semantic/main_visitor.cr | 8 ++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/spec/compiler/semantic/nilable_cast_spec.cr b/spec/compiler/semantic/nilable_cast_spec.cr index 526ff6e7c581..f2b46110f415 100644 --- a/spec/compiler/semantic/nilable_cast_spec.cr +++ b/spec/compiler/semantic/nilable_cast_spec.cr @@ -74,4 +74,16 @@ describe "Semantic: nilable cast" do base.as?(Moo) )) { union_of([types["Foo"], types["Bar"], nil_type] of Type) } end + + it "doesn't introduce type filter for nilable cast object (#12661)" do + assert_type(%( + val = 1 || false + + if val.as?(Char) + true + else + val + end + )) { union_of(int32, bool) } + end end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 3dfa321b7407..a6c021e84653 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -1766,10 +1766,14 @@ module Crystal typed_def.raises = true end - node.obj.accept self + ignoring_type_filters do + node.obj.accept self + end @in_type_args += 1 - node.to.accept self + ignoring_type_filters do + node.to.accept self + end @in_type_args -= 1 node.obj.add_observer node From 1e6e235dca69555cd3734409dea7850522170456 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Thu, 3 Nov 2022 02:14:55 +0800 Subject: [PATCH 0113/1551] Partial revert "Compiler: refactor and slightly optimize merging two types (#12436)" (#12709) This reverts part of commit bfc33dbda41ed4c3590d02bcdd781fcea5368fd3 The reverted part is 8d19346f5ca3394ed2e8f0367fe0d01734151d8f (Generalize the case of a non-union type merged with a union type) from #12436 --- src/compiler/crystal/semantic/type_merge.cr | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compiler/crystal/semantic/type_merge.cr b/src/compiler/crystal/semantic/type_merge.cr index 027af4fcfa23..9b2fe4e236f5 100644 --- a/src/compiler/crystal/semantic/type_merge.cr +++ b/src/compiler/crystal/semantic/type_merge.cr @@ -46,12 +46,11 @@ module Crystal return second if first.no_return? return first if second.no_return? - # Check if a non-union type is part of a union type - if !first.is_a?(UnionType) && second.is_a?(UnionType) && second.union_types.includes?(first) + if first.nil_type? && second.is_a?(UnionType) && second.union_types.includes?(first) return second end - if !second.is_a?(UnionType) && first.is_a?(UnionType) && first.union_types.includes?(second) + if second.nil_type? && first.is_a?(UnionType) && first.union_types.includes?(second) return first end From a3fed29bcef0dd6812eece5d03dde848028a7f20 Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Thu, 3 Nov 2022 06:40:37 -0300 Subject: [PATCH 0114/1551] CI: Update to OpenSSL 3.0.7 for bundled lib on Windows (#12712) --- .github/workflows/win.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 5dbc07fa15bc..f30939a8e53e 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -246,15 +246,15 @@ jobs: libs/crypto.lib libs/ssl.lib libs/openssl_VERSION - key: win-openssl-libs-3.0.0-msvc-${{ env.VSCMD_VER }} + key: win-openssl-libs-3.0.7-msvc-${{ env.VSCMD_VER }} - name: Set up NASM if: steps.cache-openssl.outputs.cache-hit != 'true' uses: ilammy/setup-nasm@e2335e5fc95548c09cd2deea2768793e0e8f0941 # v1.2.1 - name: Download OpenSSL if: steps.cache-openssl.outputs.cache-hit != 'true' run: | - iwr https://www.openssl.org/source/openssl-3.0.0.tar.gz -OutFile openssl.tar.gz - (Get-FileHash -Algorithm SHA256 .\openssl.tar.gz).hash -eq "59eedfcb46c25214c9bd37ed6078297b4df01d012267fe9e9eee31f61bc70536" + iwr https://www.openssl.org/source/openssl-3.0.7.tar.gz -OutFile openssl.tar.gz + (Get-FileHash -Algorithm SHA256 .\openssl.tar.gz).hash -eq "83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e" 7z x openssl.tar.gz 7z x openssl.tar mv openssl-* openssl @@ -271,7 +271,7 @@ jobs: run: | cp openssl/libcrypto.lib libs/crypto.lib cp openssl/libssl.lib libs/ssl.lib - [IO.File]::WriteAllLines("libs/openssl_VERSION", "3.0.0") + [IO.File]::WriteAllLines("libs/openssl_VERSION", "3.0.7") - name: Cache LLVM id: cache-llvm From 879691b2e3268ab290a2a0951bd1d6032f0d90f3 Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Thu, 3 Nov 2022 14:18:49 -0300 Subject: [PATCH 0115/1551] Add Changelog 1.6.2 (#12716) --- CHANGELOG.md | 19 +++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50582aed333c..2426820c56ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# 1.6.2 (2022-11-03) + +## Language + +- Fix `VirtualMetaclassType#implements?` to ignore base type ([#12632](https://github.com/crystal-lang/crystal/pull/12632), thanks @straight-shoota) + +## Compiler + +- Compiler: handle yield expressions without a type ([#12679](https://github.com/crystal-lang/crystal/pull/12679), thanks @asterite) +- Partial revert "Compiler: refactor and slightly optimize merging two types (#12436)" ([#12709](https://github.com/crystal-lang/crystal/pull/12709), thanks @caspiano) + +### Semantic + +- Compiler: ignore type filters when accepting cast for `obj` and `to` ([#12668](https://github.com/crystal-lang/crystal/pull/12668), thanks @asterite) + +## Other + +- **(security)** CI: Update to OpenSSL 3.0.7 for bundled lib on Windows ([#12712](https://github.com/crystal-lang/crystal/pull/12712), thanks @beta-ziliani) + # 1.6.1 (2022-10-21) ## Compiler diff --git a/src/VERSION b/src/VERSION index 9c6d6293b1a8..fdd3be6df54a 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.6.1 +1.6.2 From 216b39f5bb7621429037fe52c7d2646107232a1e Mon Sep 17 00:00:00 2001 From: TSUYUSATO Kitsune Date: Fri, 4 Nov 2022 21:56:42 +0900 Subject: [PATCH 0116/1551] Add missing specs for `->var.foo` semantics with assignments (#9419) Co-authored-by: Ary Borenszweig --- spec/compiler/codegen/proc_spec.cr | 70 ++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/spec/compiler/codegen/proc_spec.cr b/spec/compiler/codegen/proc_spec.cr index 2c4998d116ce..05f9650a7e42 100644 --- a/spec/compiler/codegen/proc_spec.cr +++ b/spec/compiler/codegen/proc_spec.cr @@ -896,6 +896,76 @@ describe "Code gen: proc" do )).to_i.should eq(1) end + it "saves receiver value of proc pointer `->var.foo`" do + run(%( + class Foo + def initialize(@foo : Int32) + end + + def foo + @foo + end + end + + var = Foo.new(1) + proc = ->var.foo + var = Foo.new(2) + proc.call + )).to_i.should eq(1) + end + + it "saves receiver value of proc pointer `->@ivar.foo`" do + run(%( + class Foo + def initialize(@foo : Int32) + end + + def foo + @foo + end + end + + class Test + @ivar = Foo.new(1) + + def test + proc = ->@ivar.foo + @ivar = Foo.new(2) + proc.call + end + end + + Test.new.test + )).to_i.should eq(1) + end + + it "saves receiver value of proc pointer `->@@cvar.foo`" do + run(%( + require "prelude" + + class Foo + def initialize(@foo : Int32) + end + + def foo + @foo + end + end + + class Test + @@cvar = Foo.new(1) + + def self.test + proc = ->@@cvar.foo + @@cvar = Foo.new(2) + proc.call + end + end + + Test.test + )).to_i.should eq(1) + end + # FIXME: JIT compilation of this spec is broken, forcing normal compilation (#10961) it "doesn't crash when taking a proc pointer to a virtual type (#9823)" do run(%( From e447761abb888eb029f9e574c65c9d1a0ea091d4 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Thu, 10 Nov 2022 17:31:33 +0700 Subject: [PATCH 0117/1551] Add `Iterable#each_cons_pair` (#12726) --- src/iterable.cr | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/iterable.cr b/src/iterable.cr index d02120914c92..35683fba330d 100644 --- a/src/iterable.cr +++ b/src/iterable.cr @@ -36,6 +36,13 @@ module Iterable(T) each.cons(count, reuse) end + # Same as `each.cons_pair`. + # + # See also: `Iterator#cons_pair`. + def each_cons_pair + each.cons_pair + end + # Same as `each.with_index(offset)`. def each_with_index(offset = 0) each.with_index(offset) From 4d0d0928af0b19e3679437b3856b3954d960f537 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 12:41:05 +0100 Subject: [PATCH 0118/1551] Configure Renovate (#12678) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oleh Prypin --- .github/renovate.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/renovate.json diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 000000000000..4e18234dc0ec --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:base"], + "separateMajorMinor": false, + "packageRules": [ + { + "matchDatasources": ["docker"], + "enabled": false + }, + { + "groupName": "GH Actions", + "matchManagers": ["github-actions"], + "schedule": ["after 5am and before 8am on Wednesday"] + } + ] +} From 33343691fc2adc98cfba13ad112eb8b60831848a Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Fri, 11 Nov 2022 18:41:53 +0700 Subject: [PATCH 0119/1551] Assignment to global regex match data is not allowed (#12714) --- spec/compiler/macro/macro_methods_spec.cr | 8 ++--- spec/compiler/parser/parser_spec.cr | 36 +++++++++++++---------- src/compiler/crystal/syntax/parser.cr | 26 ++++++++++++---- 3 files changed, 45 insertions(+), 25 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 94e7dd63bb59..b39fa26e9f15 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2754,15 +2754,15 @@ module Crystal end end - describe "multiassign methods" do - multiassign_node = MultiAssign.new(["foo".var, "bar".var] of ASTNode, [2.int32, "a".string] of ASTNode) + describe "multi_assign methods" do + multi_assign_node = MultiAssign.new(["foo".var, "bar".var] of ASTNode, [2.int32, "a".string] of ASTNode) it "executes targets" do - assert_macro %({{x.targets}}), %([foo, bar]), {x: multiassign_node} + assert_macro %({{x.targets}}), %([foo, bar]), {x: multi_assign_node} end it "executes values" do - assert_macro %({{x.values}}), %([2, "a"]), {x: multiassign_node} + assert_macro %({{x.values}}), %([2, "a"]), {x: multi_assign_node} end end diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 581b1e9dbe15..1c6693d64780 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1208,16 +1208,27 @@ module Crystal it_parses "1.=~(2)", Call.new(1.int32, "=~", 2.int32) it_parses "def =~; end", Def.new("=~", [] of Arg) - it_parses "$~", Global.new("$~") - it_parses "$~.foo", Call.new(Global.new("$~"), "foo") - it_parses "$0", Call.new(Global.new("$~"), "[]", 0.int32) - it_parses "$1", Call.new(Global.new("$~"), "[]", 1.int32) - it_parses "$1?", Call.new(Global.new("$~"), "[]?", 1.int32) - it_parses "foo $1", Call.new(nil, "foo", Call.new(Global.new("$~"), "[]", 1.int32)) - it_parses "$~ = 1", Assign.new("$~".var, 1.int32) - - assert_syntax_error "$2147483648" - assert_syntax_error "$99999999999999999999999?", "Index $99999999999999999999999 doesn't fit in an Int32" + describe "global regex match data" do + it_parses "$~", Global.new("$~") + it_parses "$~.foo", Call.new(Global.new("$~"), "foo") + it_parses "$0", Call.new(Global.new("$~"), "[]", 0.int32) + it_parses "$1", Call.new(Global.new("$~"), "[]", 1.int32) + it_parses "$1?", Call.new(Global.new("$~"), "[]?", 1.int32) + it_parses "foo $1", Call.new(nil, "foo", Call.new(Global.new("$~"), "[]", 1.int32)) + it_parses "$~ = 1", Assign.new("$~".var, 1.int32) + + assert_syntax_error "$0 = 1", "global match data cannot be assigned to" + assert_syntax_error "$0, $1 = [1, 2]", "global match data cannot be assigned to" + assert_syntax_error "$0, a = {1, 2}", "global match data cannot be assigned to" + + assert_syntax_error "$2147483648" + assert_syntax_error "$99999999999999999999999?", "Index $99999999999999999999999 doesn't fit in an Int32" + + it_parses "$?", Global.new("$?") + it_parses "$?.foo", Call.new(Global.new("$?"), "foo") + it_parses "foo $?", Call.new(nil, "foo", Global.new("$?")) + it_parses "$? = 1", Assign.new("$?".var, 1.int32) + end it_parses "foo /a/", Call.new(nil, "foo", regex("a")) it_parses "foo(/a/)", Call.new(nil, "foo", regex("a")) @@ -1229,11 +1240,6 @@ module Crystal it_parses "foo a, / /", Call.new(nil, "foo", ["a".call, regex(" ")] of ASTNode) it_parses "foo /;/", Call.new(nil, "foo", regex(";")) - it_parses "$?", Global.new("$?") - it_parses "$?.foo", Call.new(Global.new("$?"), "foo") - it_parses "foo $?", Call.new(nil, "foo", Global.new("$?")) - it_parses "$? = 1", Assign.new("$?".var, 1.int32) - it_parses "foo out x; x", [Call.new(nil, "foo", Out.new("x".var)), "x".var] it_parses "foo(out x); x", [Call.new(nil, "foo", Out.new("x".var)), "x".var] it_parses "foo out @x; @x", [Call.new(nil, "foo", Out.new("@x".instance_var)), "@x".instance_var] diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 4ee7871f06ae..98f2b9367b96 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -199,21 +199,21 @@ module Crystal unexpected_token end - targets = exps[0...assign_index].map { |exp| multiassign_left_hand(exp) } + targets = exps[0...assign_index].map { |exp| multi_assign_left_hand(exp) } assign = exps[assign_index] values = [] of ASTNode case assign when Assign - targets << multiassign_left_hand(assign.target) + targets << multi_assign_left_hand(assign.target) values << assign.value when Call assign.name = assign.name.byte_slice(0, assign.name.bytesize - 1) targets << assign values << assign.args.pop else - raise "BUG: multiassign index expression can only be Assign or Call" + raise "BUG: multi_assign index expression can only be Assign or Call" end if lhs_splat_index @@ -259,14 +259,24 @@ module Crystal end end - def multiassign_left_hand(exp) + def multi_assign_left_hand(exp) if exp.is_a?(Path) raise "can't assign to constant in multiple assignment", exp.location.not_nil! end - if exp.is_a?(Call) && !exp.obj && exp.args.empty? - exp = Var.new(exp.name).at(exp) + if exp.is_a?(Call) + case obj = exp.obj + when Nil + if exp.args.empty? + exp = Var.new(exp.name).at(exp) + end + when Global + if obj.name == "$~" && exp.name == "[]" + raise "global match data cannot be assigned to", obj.location.not_nil! + end + end end + if exp.is_a?(Var) if exp.name == "self" raise "can't change the value of self", exp.location.not_nil! @@ -995,6 +1005,10 @@ module Crystal node_and_next_token Global.new(var.name).at(location) end when .global_match_data_index? + if peek_ahead { next_token_skip_space; @token.type.op_eq? } + raise "global match data cannot be assigned to" + end + value = @token.value.to_s if value_prefix = value.rchop? '?' method = "[]?" From 1b43f3723d9fc39500b2c22ee58a309b7c44ca89 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Fri, 11 Nov 2022 18:42:11 +0700 Subject: [PATCH 0120/1551] Add links to equivalent `Iterator` methods in `Iterable` (#12727) --- src/iterable.cr | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/iterable.cr b/src/iterable.cr index 35683fba330d..86351bba0e70 100644 --- a/src/iterable.cr +++ b/src/iterable.cr @@ -5,11 +5,15 @@ module Iterable(T) abstract def each # Same as `each.cycle`. + # + # See also: `Iterator#cycle`. def cycle each.cycle end # Same as `each.cycle(n)`. + # + # See also: `Iterator#cycle(n)`. def cycle(n) each.cycle(n) end @@ -21,17 +25,21 @@ module Iterable(T) # (0..7).chunk(&.//(3)).to_a # => [{0, [0, 1, 2]}, {1, [3, 4, 5]}, {2, [6, 7]}] # ``` # - # See also: `Iterator#chunks`. + # See also: `Iterator#chunk`. def chunk(reuse = false, &block : T -> U) forall U each.chunk reuse, &block end # Same as `each.slice(count, reuse)`. + # + # See also: `Iterator#slice(count, reuse)`. def each_slice(count : Int, reuse = false) each.slice(count, reuse) end - # Same as `each.cons(count)`. + # Same as `each.cons(count, reuse)`. + # + # See also: `Iterator#cons(count, reuse)`. def each_cons(count : Int, reuse = false) each.cons(count, reuse) end @@ -44,41 +52,57 @@ module Iterable(T) end # Same as `each.with_index(offset)`. + # + # See also: `Iterator#with_index(offset)`. def each_with_index(offset = 0) each.with_index(offset) end # Same as `each.with_object(obj)`. + # + # See also: `Iterator#with_object(obj)`. def each_with_object(obj) each.with_object(obj) end # Same as `each.slice_after(reuse, &block)`. + # + # See also: `Iterator#slice_after(reuse, &block)`. def slice_after(reuse : Bool | Array(T) = false, &block : T -> B) forall B each.slice_after(reuse, &block) end # Same as `each.slice_after(pattern, reuse)`. + # + # See also: `Iterator#slice_after(pattern, reuse)`. def slice_after(pattern, reuse : Bool | Array(T) = false) each.slice_after(pattern, reuse) end # Same as `each.slice_before(reuse, &block)`. + # + # See also: `Iterator#slice_before(reuse, &block)`. def slice_before(reuse : Bool | Array(T) = false, &block : T -> B) forall B each.slice_before(reuse, &block) end # Same as `each.slice_before(pattern, reuse)`. + # + # See also: `Iterator#slice_before(pattern, reuse)`. def slice_before(pattern, reuse : Bool | Array(T) = false) each.slice_before(pattern, reuse) end # Same as `each.slice_when(reuse, &block)`. + # + # See also: `Iterator#slice_when`. def slice_when(reuse : Bool | Array(T) = false, &block : T, T -> B) forall B each.slice_when(reuse, &block) end # Same as `each.chunk_while(reuse, &block)`. + # + # See also: `Iterator#chunk_while`. def chunk_while(reuse : Bool | Array(T) = false, &block : T, T -> B) forall B each.chunk_while(reuse, &block) end From 1da0e338b4f3350e67af228b65a46f8613a60209 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Sat, 12 Nov 2022 18:29:54 +0700 Subject: [PATCH 0121/1551] Use `Object#in?` in place of multiple comparisons (#12700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/http/cookie_spec.cr | 60 +++++++++++++++--------------------- src/base64.cr | 4 +-- src/http/cookie.cr | 8 +++-- 3 files changed, 32 insertions(+), 40 deletions(-) diff --git a/spec/std/http/cookie_spec.cr b/spec/std/http/cookie_spec.cr index 28174021f908..08d2e91c7b16 100644 --- a/spec/std/http/cookie_spec.cr +++ b/spec/std/http/cookie_spec.cr @@ -38,34 +38,33 @@ module HTTP expect_raises IO::Error, "Invalid cookie name" do HTTP::Cookie.new("\t", "") end - # more extensive specs on #name= end it "raises on invalid value" do expect_raises IO::Error, "Invalid cookie value" do HTTP::Cookie.new("x", %(foo\rbar)) end - # more extensive specs on #value= end end describe "#name=" do it "raises on invalid name" do cookie = HTTP::Cookie.new("x", "") - expect_raises IO::Error, "Invalid cookie name" do - cookie.name = "" - end - expect_raises IO::Error, "Invalid cookie name" do - cookie.name = "\t" - end - expect_raises IO::Error, "Invalid cookie name" do - cookie.name = "\r" - end - expect_raises IO::Error, "Invalid cookie name" do - cookie.name = "a\nb" - end - expect_raises IO::Error, "Invalid cookie name" do - cookie.name = "a\rb" + invalid_names = [ + '"', '(', ')', ',', '/', + ' ', '\r', '\t', '\n', + '{', '}', + (':'..'@').each, + ('['..']').each, + ].flat_map { |c| "a#{c}b" } + + # name cannot be empty + invalid_names << "" + + invalid_names.each do |invalid_name| + expect_raises IO::Error, "Invalid cookie name" do + cookie.name = invalid_name + end end end end @@ -73,26 +72,15 @@ module HTTP describe "#value=" do it "raises on invalid value" do cookie = HTTP::Cookie.new("x", "") - expect_raises IO::Error, "Invalid cookie value" do - cookie.value = %(foo\rbar) - end - expect_raises IO::Error, "Invalid cookie value" do - cookie.value = %(foo"bar) - end - expect_raises IO::Error, "Invalid cookie value" do - cookie.value = "foo;bar" - end - expect_raises IO::Error, "Invalid cookie value" do - cookie.value = "foo\\bar" - end - expect_raises IO::Error, "Invalid cookie value" do - cookie.value = "foo\\bar" - end - expect_raises IO::Error, "Invalid cookie value" do - cookie.value = "foo bar" - end - expect_raises IO::Error, "Invalid cookie value" do - cookie.value = "foo,bar" + invalid_values = { + '"', ',', ';', '\\', # invalid printable ascii characters + ' ', '\r', '\t', '\n', # non-printable ascii characters + }.map { |c| "foo#{c}bar" } + + invalid_values.each do |invalid_value| + expect_raises IO::Error, "Invalid cookie value" do + cookie.value = invalid_value + end end end end diff --git a/src/base64.cr b/src/base64.cr index 2457768daa00..ddb783dfe49e 100644 --- a/src/base64.cr +++ b/src/base64.cr @@ -262,7 +262,7 @@ module Base64 break if bytes > fin # Move the pointer by one byte until there is a valid base64 character - while bytes.value == NL || bytes.value == NR + while bytes.value.in?(NL, NR) bytes += 1 end break if bytes > fin @@ -272,7 +272,7 @@ module Base64 end # Move the pointer by one byte until there is a valid base64 character or the end of `bytes` was reached - while (bytes < fin + 4) && (bytes.value == NL || bytes.value == NR) + while (bytes < fin + 4) && bytes.value.in?(NL, NR) bytes += 1 end diff --git a/src/http/cookie.cr b/src/http/cookie.cr index d22fdf1419d0..9dc5517fa05d 100644 --- a/src/http/cookie.cr +++ b/src/http/cookie.cr @@ -58,7 +58,11 @@ module HTTP # valid characters for cookie-name per https://tools.ietf.org/html/rfc6265#section-4.1.1 # and https://tools.ietf.org/html/rfc2616#section-2.2 # "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~" - unless (0x21...0x7f).includes?(byte) && byte != 0x22 && byte != 0x28 && byte != 0x29 && byte != 0x2c && byte != 0x2f && !(0x3a..0x40).includes?(byte) && !(0x5b..0x5d).includes?(byte) && byte != 0x7b && byte != 0x7d + if !byte.in?(0x21...0x7f) || # Non-printable ASCII character + byte.in?(0x22, 0x28, 0x29, 0x2c, 0x2f) || # '"', '(', ')', ',', '/' + byte.in?(0x3a..0x40) || # ':', ';', '<', '=', '>', '?', '@' + byte.in?(0x5b..0x5d) || # '[', '\\', ']' + byte.in?(0x7b, 0x7d) # '{', '}' raise IO::Error.new("Invalid cookie name") end end @@ -76,7 +80,7 @@ module HTTP value.each_byte do |byte| # valid characters for cookie-value per https://tools.ietf.org/html/rfc6265#section-4.1.1 # all printable ASCII characters except ' ', ',', '"', ';' and '\\' - unless (0x21...0x7f).includes?(byte) && byte != 0x22 && byte != 0x2c && byte != 0x3b && byte != 0x5c + if !byte.in?(0x21...0x7f) || byte.in?(0x22, 0x2c, 0x3b, 0x5c) raise IO::Error.new("Invalid cookie value") end end From 6edc9e3920ab6e676bd1a27da084c8e1013cdfc1 Mon Sep 17 00:00:00 2001 From: Dmitri Goutnik Date: Sat, 12 Nov 2022 06:30:55 -0500 Subject: [PATCH 0122/1551] `Exception::CallStack`: avoid allocations in `LibC.dl_iterate_phdr` (#12625) Calling `self.read_dwarf_sections` directly from the C callback may lead to reallocations and deadlocks due to the internal lock held by `dl_iterate_phdr` (#10084). Work around this by storing object base address and passing it to `self.read_dwarf_sections` later when `dl_iterate_phdr` returns. --- src/exception/call_stack/elf.cr | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/exception/call_stack/elf.cr b/src/exception/call_stack/elf.cr index cdedae66dd42..6bcaf5eec2b7 100644 --- a/src/exception/call_stack/elf.cr +++ b/src/exception/call_stack/elf.cr @@ -5,16 +5,19 @@ require "crystal/elf" struct Exception::CallStack protected def self.load_debug_info_impl + base_address : LibC::Elf_Addr = 0 phdr_callback = LibC::DlPhdrCallback.new do |info, size, data| - # The first entry is the header for the current program - read_dwarf_sections(info.value.addr) + # The first entry is the header for the current program. + # Note that we avoid allocating here and just store the base address + # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns. + # Calling self.read_dwarf_sections from this callback may lead to reallocations + # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084). + data.as(Pointer(LibC::Elf_Addr)).value = info.value.addr 1 end - # GC needs to be disabled around dl_iterate_phdr in freebsd (#10084) - {% if flag?(:freebsd) %} GC.disable {% end %} - LibC.dl_iterate_phdr(phdr_callback, nil) - {% if flag?(:freebsd) %} GC.enable {% end %} + LibC.dl_iterate_phdr(phdr_callback, pointerof(base_address)) + self.read_dwarf_sections(base_address) end protected def self.read_dwarf_sections(base_address = 0) From 18fd4c3bbe0699bb43df85a1a012b2415cd68c62 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Sat, 12 Nov 2022 18:32:49 +0700 Subject: [PATCH 0123/1551] Style: Remove explicit returns from the codebase (#12637) --- samples/spectral-norm.cr | 2 +- src/bit_array.cr | 2 +- src/compiler/crystal/codegen/call.cr | 2 +- src/compiler/crystal/codegen/class_var.cr | 2 +- src/compiler/crystal/codegen/match.cr | 2 +- src/compiler/crystal/codegen/primitives.cr | 18 +++++++++--------- src/compiler/crystal/exception.cr | 8 ++++---- src/compiler/crystal/interpreter/compiler.cr | 2 +- src/compiler/crystal/macros/methods.cr | 6 +++--- src/compiler/crystal/semantic/call.cr | 2 +- src/compiler/crystal/semantic/filters.cr | 4 ++-- src/compiler/crystal/semantic/main_visitor.cr | 3 ++- src/compiler/crystal/semantic/restrictions.cr | 2 +- .../crystal/semantic/type_intersect.cr | 2 +- src/compiler/crystal/syntax/parser.cr | 4 ++-- src/compiler/crystal/tools/context.cr | 6 +++--- src/compiler/crystal/tools/expand.cr | 4 ++-- src/compiler/crystal/tools/formatter.cr | 17 +++++++---------- src/compiler/crystal/tools/git.cr | 3 +-- src/compiler/crystal/tools/implementations.cr | 4 ++-- src/crystal/system/unix/file.cr | 2 +- src/crystal/system/unix/socket.cr | 2 +- src/crystal/system/win32/dir.cr | 8 ++++---- src/crystal/system/win32/file.cr | 2 +- src/env.cr | 2 +- src/float/printer/grisu3.cr | 2 +- src/float/printer/ieee.cr | 4 ++-- src/http/common.cr | 2 +- src/http/headers.cr | 2 +- src/log/spec.cr | 2 +- src/named_tuple.cr | 3 +-- src/path.cr | 4 ++-- src/raise.cr | 2 +- src/random/pcg32.cr | 2 +- src/range.cr | 2 +- src/regex/match_data.cr | 2 +- src/spec/expectations.cr | 8 ++++---- src/string.cr | 7 +++---- src/struct.cr | 4 ++-- src/time/location/loader.cr | 2 +- 40 files changed, 77 insertions(+), 82 deletions(-) diff --git a/samples/spectral-norm.cr b/samples/spectral-norm.cr index 249b745a01ea..a45ba2a3a36e 100644 --- a/samples/spectral-norm.cr +++ b/samples/spectral-norm.cr @@ -1,7 +1,7 @@ # Copied with little modifications from: https://github.com/wmoxam/Ruby-Benchmarks-Game/blob/master/benchmarks/spectral-norm.rb def eval_A(i, j) - return 1.0_f64 / ((i + j) * (i + j + 1.0) / 2.0 + i + 1.0) + 1.0_f64 / ((i + j) * (i + j + 1.0) / 2.0 + i + 1.0) end def eval_A_times_u(u) diff --git a/src/bit_array.cr b/src/bit_array.cr index 2506c6b01db2..4fd4703b7eb8 100644 --- a/src/bit_array.cr +++ b/src/bit_array.cr @@ -54,7 +54,7 @@ struct BitArray # NOTE: If BitArray implements resizing, there may be more than 1 binary # representation and their hashes for equivalent BitArrays after a downsize as the # discarded bits may not have been zeroed. - return LibC.memcmp(@bits, other.@bits, bytesize) == 0 + LibC.memcmp(@bits, other.@bits, bytesize) == 0 end def unsafe_fetch(index : Int) : Bool diff --git a/src/compiler/crystal/codegen/call.cr b/src/compiler/crystal/codegen/call.cr index 75f0a1833920..598204154ef0 100644 --- a/src/compiler/crystal/codegen/call.cr +++ b/src/compiler/crystal/codegen/call.cr @@ -462,7 +462,7 @@ class Crystal::CodeGenVisitor @last = self_type.passed_as_self? ? call_args.first : type_id(self_type) inline_call_return_value target_def, body - return true + true else false end diff --git a/src/compiler/crystal/codegen/class_var.cr b/src/compiler/crystal/codegen/class_var.cr index a16b68e3e416..1e3df6bc1358 100644 --- a/src/compiler/crystal/codegen/class_var.cr +++ b/src/compiler/crystal/codegen/class_var.cr @@ -210,7 +210,7 @@ class Crystal::CodeGenVisitor global_name = class_var_global_name(class_var) global = get_global global_name, class_var.type, class_var global = ensure_class_var_in_this_module(global, class_var) - return global + global end def read_virtual_class_var_ptr(class_var, owner) diff --git a/src/compiler/crystal/codegen/match.cr b/src/compiler/crystal/codegen/match.cr index 483ed398b76e..1a50bdb8fd0a 100644 --- a/src/compiler/crystal/codegen/match.cr +++ b/src/compiler/crystal/codegen/match.cr @@ -41,7 +41,7 @@ class Crystal::CodeGenVisitor match_fun_name = "~match<#{type}>" func = @main_mod.functions[match_fun_name]? || create_match_fun(match_fun_name, type) func = check_main_fun match_fun_name, func - return call func, [type_id] of LLVM::Value + call func, [type_id] of LLVM::Value end private def create_match_fun(name, type) diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index a7f6125079d7..bc71b573e889 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -100,20 +100,20 @@ class Crystal::CodeGenVisitor def codegen_binary_op(op, t1 : CharType, t2 : CharType, p1, p2) case op - when "==" then return builder.icmp LLVM::IntPredicate::EQ, p1, p2 - when "!=" then return builder.icmp LLVM::IntPredicate::NE, p1, p2 - when "<" then return builder.icmp LLVM::IntPredicate::ULT, p1, p2 - when "<=" then return builder.icmp LLVM::IntPredicate::ULE, p1, p2 - when ">" then return builder.icmp LLVM::IntPredicate::UGT, p1, p2 - when ">=" then return builder.icmp LLVM::IntPredicate::UGE, p1, p2 + when "==" then builder.icmp LLVM::IntPredicate::EQ, p1, p2 + when "!=" then builder.icmp LLVM::IntPredicate::NE, p1, p2 + when "<" then builder.icmp LLVM::IntPredicate::ULT, p1, p2 + when "<=" then builder.icmp LLVM::IntPredicate::ULE, p1, p2 + when ">" then builder.icmp LLVM::IntPredicate::UGT, p1, p2 + when ">=" then builder.icmp LLVM::IntPredicate::UGE, p1, p2 else raise "BUG: trying to codegen #{t1} #{op} #{t2}" end end def codegen_binary_op(op, t1 : SymbolType, t2 : SymbolType, p1, p2) case op - when "==" then return builder.icmp LLVM::IntPredicate::EQ, p1, p2 - when "!=" then return builder.icmp LLVM::IntPredicate::NE, p1, p2 + when "==" then builder.icmp LLVM::IntPredicate::EQ, p1, p2 + when "!=" then builder.icmp LLVM::IntPredicate::NE, p1, p2 else raise "BUG: trying to codegen #{t1} #{op} #{t2}" end end @@ -203,7 +203,7 @@ class Crystal::CodeGenVisitor ) codegen_raise_overflow_cond overflow - return codegen_binary_op_with_overflow("*", t1, @program.int_type(false, t2.bytes), p1, p2) + codegen_binary_op_with_overflow("*", t1, @program.int_type(false, t2.bytes), p1, p2) end def codegen_mul_signed_unsigned_with_overflow(t1, t2, p1, p2) diff --git a/src/compiler/crystal/exception.cr b/src/compiler/crystal/exception.cr index 1d45c44ce3f8..c40f9a39051a 100644 --- a/src/compiler/crystal/exception.cr +++ b/src/compiler/crystal/exception.cr @@ -30,15 +30,15 @@ module Crystal if filename.is_a? VirtualFile loc = filename.expanded_location if loc - return true_filename loc.filename + true_filename loc.filename else - return "" + "" end else if filename - return filename + filename else - return "" + "" end end end diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index f0bb9739ec03..bb6cadd88fc7 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -1992,7 +1992,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor pop aligned_sizeof_type(node), node: nil end - return false + false end private def create_compiled_def(node : Call, target_def : Def) diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index e55f194cd8f7..aa6a40cf8138 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -342,9 +342,9 @@ module Crystal def to_string(context) case self - when StringLiteral then return self.value - when SymbolLiteral then return self.value - when MacroId then return self.value + when StringLiteral then self.value + when SymbolLiteral then self.value + when MacroId then self.value else raise "expected #{context} to be a StringLiteral, SymbolLiteral or MacroId, not #{class_desc}" end diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index e9e299d25dbd..3a80d50515cb 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -579,7 +579,7 @@ class Crystal::Call if index || nilable indexer_def = yield instance_type, (index || -1) indexer_match = Match.new(indexer_def, arg_types, MatchContext.new(owner, owner)) - return Matches.new(ZeroOneOrMany(Match).new(indexer_match), true) + Matches.new(ZeroOneOrMany(Match).new(indexer_match), true) else raise "missing key '#{name}' for named tuple #{owner}" end diff --git a/src/compiler/crystal/semantic/filters.cr b/src/compiler/crystal/semantic/filters.cr index 79185617d59e..8612c956aae7 100644 --- a/src/compiler/crystal/semantic/filters.cr +++ b/src/compiler/crystal/semantic/filters.cr @@ -142,9 +142,9 @@ module Crystal case other when NilType - return nil + nil when UnionType - return Type.merge(other.union_types.reject &.nil_type?) + Type.merge(other.union_types.reject &.nil_type?) else other end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 014a2bec64fa..44da862b2a1d 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -1398,7 +1398,8 @@ module Crystal expansion.accept self node.expanded = expansion node.bind_to(expanded) - return false + + false end # If it's a super or previous_def call inside an initialize we treat diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 1bb477bd26e5..5e157e3ef200 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -1474,7 +1474,7 @@ module Crystal restricted = typedef.restrict(other, context) if restricted == typedef - return self + self elsif restricted.is_a?(UnionType) program.type_merge(restricted.union_types.map { |t| t == typedef ? self : t }) else diff --git a/src/compiler/crystal/semantic/type_intersect.cr b/src/compiler/crystal/semantic/type_intersect.cr index 500c214a2cfb..490503dbc4c1 100644 --- a/src/compiler/crystal/semantic/type_intersect.cr +++ b/src/compiler/crystal/semantic/type_intersect.cr @@ -154,7 +154,7 @@ module Crystal restricted = common_descendent(type1.typedef, type2) if restricted == type1.typedef - return type1 + type1 elsif restricted.is_a?(UnionType) type1.program.type_merge(restricted.union_types.map { |t| t == type1.typedef ? type1 : t }) else diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 98f2b9367b96..515eeb2f2059 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3446,7 +3446,7 @@ module Crystal end a_then, a_else = a_else, a_then if is_unless - return MacroIf.new(cond, a_then, a_else).at_end(token_end_location) + MacroIf.new(cond, a_then, a_else).at_end(token_end_location) end def parse_expression_inside_macro @@ -4669,7 +4669,7 @@ module Crystal else skip_space end - return CallArgs.new args, nil, nil, named_args, false, end_location, has_parentheses: allow_newline + CallArgs.new args, nil, nil, named_args, false, end_location, has_parentheses: allow_newline end def parse_named_args(location, first_name = nil, allow_newline = false) diff --git a/src/compiler/crystal/tools/context.cr b/src/compiler/crystal/tools/context.cr index 004b17f36d90..0d7a0a98d55a 100644 --- a/src/compiler/crystal/tools/context.cr +++ b/src/compiler/crystal/tools/context.cr @@ -161,14 +161,14 @@ module Crystal if @contexts.empty? if @found_untyped_def - return ContextResult.new("failed", "no context information found (methods which are never called don't have a context)") + ContextResult.new("failed", "no context information found (methods which are never called don't have a context)") else - return ContextResult.new("failed", "no context information found") + ContextResult.new("failed", "no context information found") end else res = ContextResult.new("ok", "#{@contexts.size} possible context#{@contexts.size > 1 ? "s" : ""} found") res.contexts = @contexts - return res + res end end diff --git a/src/compiler/crystal/tools/expand.cr b/src/compiler/crystal/tools/expand.cr index 93035de47875..365fc2b3f865 100644 --- a/src/compiler/crystal/tools/expand.cr +++ b/src/compiler/crystal/tools/expand.cr @@ -116,11 +116,11 @@ module Crystal result.node.accept(self) if @found_nodes.empty? - return ExpandResult.new("failed", @message) + ExpandResult.new("failed", @message) else res = ExpandResult.new("ok", "#{@found_nodes.size} expansion#{@found_nodes.size > 1 ? "s" : ""} found") res.expansions = @found_nodes.map { |node| ExpandResult::Expansion.build(node) } - return res + res end end diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 417854fcf925..c2bc17cec2ea 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1800,7 +1800,7 @@ module Crystal write_indent write "end" next_token - return false + false end def visit(node : MacroLiteral) @@ -3479,18 +3479,15 @@ module Crystal skip_semicolon_or_space_or_newline check_end write "; end" - next_token - return false else skip_space_or_newline check_end write_line write_indent write "end" - next_token - return false end + next_token false end @@ -4309,25 +4306,25 @@ module Crystal def visit(node : Block) # Handled in format_block - return false + false end def visit(node : When) # Handled in format_when - return false + false end def visit(node : Rescue) # Handled in visit(node : ExceptionHandler) - return false + false end def visit(node : MacroId) - return false + false end def visit(node : MetaVar) - return false + false end def visit(node : Asm) diff --git a/src/compiler/crystal/tools/git.cr b/src/compiler/crystal/tools/git.cr index f06aa20b5f07..a79d7c3c730f 100644 --- a/src/compiler/crystal/tools/git.cr +++ b/src/compiler/crystal/tools/git.cr @@ -4,8 +4,7 @@ module Crystal::Git # Tries to run git command with args. # Yields block if exec fails or process status is not success. def self.git_command(args, output : Process::Stdio = Process::Redirect::Close) - status = Process.run(executable, args, output: output) - return status.success? + Process.run(executable, args, output: output).success? rescue IO::Error false end diff --git a/src/compiler/crystal/tools/implementations.cr b/src/compiler/crystal/tools/implementations.cr index 8b2763d835d3..4e49f18c4712 100644 --- a/src/compiler/crystal/tools/implementations.cr +++ b/src/compiler/crystal/tools/implementations.cr @@ -98,11 +98,11 @@ module Crystal result.node.accept(self) if @locations.empty? - return ImplementationResult.new("failed", "no implementations or method call found") + ImplementationResult.new("failed", "no implementations or method call found") else res = ImplementationResult.new("ok", "#{@locations.size} implementation#{@locations.size > 1 ? "s" : ""} found") res.implementations = @locations.map { |loc| ImplementationTrace.build(loc) } - return res + res end end diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index a73a190f45a5..711f1a4bfcf3 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -43,7 +43,7 @@ module Crystal::System::File ::File::Info.new(stat) else if Errno.value.in?(Errno::ENOENT, Errno::ENOTDIR) - return nil + nil else raise ::File::Error.from_errno("Unable to get file info", file: path) end diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 6007b3b524da..b48f383d5226 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -126,7 +126,7 @@ module Crystal::System::Socket end if Errno.value == Errno::ENOPROTOOPT - return false + false else raise ::Socket::Error.from_errno("getsockopt") end diff --git a/src/crystal/system/win32/dir.cr b/src/crystal/system/win32/dir.cr index 88d1babe2cd4..80189b9f030b 100644 --- a/src/crystal/system/win32/dir.cr +++ b/src/crystal/system/win32/dir.cr @@ -27,11 +27,11 @@ module Crystal::System::Dir handle = LibC.FindFirstFileW(dir.query, out data) if handle != LibC::INVALID_HANDLE_VALUE dir.handle = handle - return data_to_entry(data) + data_to_entry(data) else error = WinError.value if error == WinError::ERROR_FILE_NOT_FOUND - return nil + nil else raise ::File::Error.from_os_error("Error reading directory entries", error, file: path) end @@ -39,11 +39,11 @@ module Crystal::System::Dir else # Use FindNextFile if LibC.FindNextFileW(dir.handle, out data_) != 0 - return data_to_entry(data_) + data_to_entry(data_) else error = WinError.value if error == WinError::ERROR_NO_MORE_FILES - return nil + nil else raise ::File::Error.from_os_error("Error reading directory entries", error, file: path) end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 6cd51cd529f6..7a44bfdee207 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -50,7 +50,7 @@ module Crystal::System::File private def self.check_not_found_error(message, path) error = WinError.value if NOT_FOUND_ERRORS.includes? error - return nil + nil else raise ::File::Error.from_os_error(message, error, file: path) end diff --git a/src/env.cr b/src/env.cr index 75cee635989b..e16e60a67fcf 100644 --- a/src/env.cr +++ b/src/env.cr @@ -64,7 +64,7 @@ module ENV # the *key* does not exist. def self.fetch(key : String, &block : String -> T) : String | T forall T if value = Crystal::System::Env.get(key) - return value + value else yield key end diff --git a/src/float/printer/grisu3.cr b/src/float/printer/grisu3.cr index 7d82d03d2f40..d68cf2b7b287 100644 --- a/src/float/printer/grisu3.cr +++ b/src/float/printer/grisu3.cr @@ -156,7 +156,7 @@ module Float::Printer::Grisu3 # Since too_low = too_high - unsafe_interval this is equivalent to # [too_high - unsafe_interval + 4 ulp; too_high - 2 ulp] # Conceptually we have: rest ~= too_high - buffer - return (2 &* unit <= rest) && (rest <= unsafe_interval &- 4 &* unit) + (2 &* unit <= rest) && (rest <= unsafe_interval &- 4 &* unit) end # Generates the digits of input number *w*. diff --git a/src/float/printer/ieee.cr b/src/float/printer/ieee.cr index 0270d5977982..b56b3d5bb454 100644 --- a/src/float/printer/ieee.cr +++ b/src/float/printer/ieee.cr @@ -119,7 +119,7 @@ module Float::Printer::IEEE {(w.frac << 1) - 1, w.exp - 1} end m_minus = DiyFP.new(f << (e - m_plus.exp), m_plus.exp) - return {minus: m_minus, plus: m_plus} + {minus: m_minus, plus: m_plus} end def normalized_boundaries(v : Float32) : {minus: DiyFP, plus: DiyFP} @@ -137,7 +137,7 @@ module Float::Printer::IEEE {(w.frac << 1) - 1, w.exp - 1} end m_minus = DiyFP.new(f << (e - m_plus.exp), m_plus.exp) - return {minus: m_minus, plus: m_plus} + {minus: m_minus, plus: m_plus} end def frac_and_exp(v : Float64) : {UInt64, Int32} diff --git a/src/http/common.cr b/src/http/common.cr index 1a3691de8e4c..2e9da53c46ff 100644 --- a/src/http/common.cr +++ b/src/http/common.cr @@ -128,7 +128,7 @@ module HTTP end name, value = parse_header(line) - return HeaderLine.new name: name, value: value, bytesize: line.bytesize + HeaderLine.new name: name, value: value, bytesize: line.bytesize end private def self.check_content_type_charset(body, headers) diff --git a/src/http/headers.cr b/src/http/headers.cr index f3f4cd3f12a8..ef756d545beb 100644 --- a/src/http/headers.cr +++ b/src/http/headers.cr @@ -300,7 +300,7 @@ struct HTTP::Headers end def valid_value?(value) : Bool - return invalid_value_char(value).nil? + invalid_value_char(value).nil? end forward_missing_to @hash diff --git a/src/log/spec.cr b/src/log/spec.cr index e1d19af3597d..e776705dc359 100644 --- a/src/log/spec.cr +++ b/src/log/spec.cr @@ -112,7 +112,7 @@ class Log matches = yield entry if matches @entry = entry - return self + self else fail("No matching entries found, expected #{description}, but got #{entry.severity} with #{entry.message.inspect}", file, line) end diff --git a/src/named_tuple.cr b/src/named_tuple.cr index 2417ce1490b8..0c8c52e5d0a1 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -676,8 +676,7 @@ struct NamedTuple {% for key in T %} return false unless self[{{key.symbolize}}] == other[{{key.symbolize}}]? {% end %} - - return true + true end # Returns a named tuple with the same keys but with cloned values, using the `clone` method. diff --git a/src/path.cr b/src/path.cr index ba8517d6e56a..e7397079f3f7 100644 --- a/src/path.cr +++ b/src/path.cr @@ -625,7 +625,7 @@ struct Path end @reader = reader - return 0 + 0 end end @@ -1033,7 +1033,7 @@ struct Path end end - return path + path end # :ditto: diff --git a/src/raise.cr b/src/raise.cr index 17f1eea2b121..f3d505ebe94e 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -86,7 +86,7 @@ private def traverse_eh_table(leb, start, ip, actions) end end - return nil + nil end {% if flag?(:interpreted) %} diff --git a/src/random/pcg32.cr b/src/random/pcg32.cr index 5fd4755929d7..bc34f736b282 100644 --- a/src/random/pcg32.cr +++ b/src/random/pcg32.cr @@ -67,7 +67,7 @@ class Random::PCG32 @state = oldstate &* PCG_DEFAULT_MULTIPLIER_64 &+ @inc xorshifted = UInt32.new!(((oldstate >> 18) ^ oldstate) >> 27) rot = UInt32.new!(oldstate >> 59) - return UInt32.new!(xorshifted.rotate_right(rot)) + UInt32.new!(xorshifted.rotate_right(rot)) end def jump(delta) diff --git a/src/range.cr b/src/range.cr index bb1aaaa3982d..9d409b260156 100644 --- a/src/range.cr +++ b/src/range.cr @@ -576,7 +576,7 @@ struct Range(B, E) begin_value = @range.begin return stop if !begin_value.nil? && @current <= begin_value - return @current = @current.pred + @current = @current.pred end end diff --git a/src/regex/match_data.cr b/src/regex/match_data.cr index ce489b797a5f..89e12204986e 100644 --- a/src/regex/match_data.cr +++ b/src/regex/match_data.cr @@ -397,7 +397,7 @@ class Regex return false unless regex == other.regex return false unless string == other.string - return @ovector.memcmp(other.@ovector, size * 2) == 0 + @ovector.memcmp(other.@ovector, size * 2) == 0 end # See `Object#hash(hasher)` diff --git a/src/spec/expectations.cr b/src/spec/expectations.cr index 453d796e5fe6..399f5be6f253 100644 --- a/src/spec/expectations.cr +++ b/src/spec/expectations.cr @@ -35,10 +35,10 @@ module Spec MSG end - return <<-MSG - Expected size: #{expected_value.size} - got size: #{actual_value.size} - MSG + <<-MSG + Expected size: #{expected_value.size} + got size: #{actual_value.size} + MSG else expected = expected_value.inspect got = actual_value.inspect diff --git a/src/string.cr b/src/string.cr index 974f3fd1a3a3..7ea2e2348a55 100644 --- a/src/string.cr +++ b/src/string.cr @@ -987,8 +987,7 @@ class String byte_index = char_index_to_byte_index(index) if byte_index && byte_index < @bytesize - reader = Char::Reader.new(self, pos: byte_index) - return reader.current_char + Char::Reader.new(self, pos: byte_index).current_char else yield end @@ -1088,9 +1087,9 @@ class String case count when 0 - return self + self when size - return "" + "" else if single_byte_optimizable? byte_delete_at(start, count, count) diff --git a/src/struct.cr b/src/struct.cr index 4c7f6ff30ef6..068b5198dd12 100644 --- a/src/struct.cr +++ b/src/struct.cr @@ -69,9 +69,9 @@ struct Struct {% for ivar in @type.instance_vars %} return false unless @{{ivar.id}} == other.@{{ivar.id}} {% end %} - return true + true else - return false + false end end diff --git a/src/time/location/loader.cr b/src/time/location/loader.cr index fc780927be6d..5386f1570ef0 100644 --- a/src/time/location/loader.cr +++ b/src/time/location/loader.cr @@ -46,7 +46,7 @@ class Time::Location mtime = File.info(path).modification_time if (cache = @@location_cache[name]?) && cache[:time] == mtime - return cache[:location] + cache[:location] else File.open(path) do |file| location = yield file From ed211835f35e354a9047a36aa634a32c01844864 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 15 Nov 2022 03:08:09 +0800 Subject: [PATCH 0124/1551] Optimize `Hash#select(Enumerable)` and `#merge!(Hash, &)` (#12737) --- src/hash.cr | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/hash.cr b/src/hash.cr index 0accf433ec4c..9c6e751e27bb 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -1466,11 +1466,8 @@ class Hash(K, V) {% end %} each do |k, v| - if other.has_key?(k) - other[k] = yield k, other[k], v - else - other[k] = v - end + entry = other.find_entry(k) + other[k] = entry ? yield(k, entry.value, v) : v end end @@ -1551,9 +1548,10 @@ class Hash(K, V) # {"a" => 1, "b" => 2, "c" => 3, "d" => 4}.select(Set{"a", "c"}) # => {"a" => 1, "c" => 3} # ``` def select(keys : Enumerable) : Hash(K, V) - hash = {} of K => V - keys.each { |k| hash[k] = self[k] if has_key?(k) } - hash + keys.each_with_object({} of K => V) do |k, memo| + entry = find_entry(k) + memo[k] = entry.value if entry + end end # :ditto: From c91fafafa1b270a8055aac39ac8ffb45a6084c8c Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 15 Nov 2022 17:35:03 -0300 Subject: [PATCH 0125/1551] Improve error message when there are extra types (#12734) --- spec/compiler/semantic/abstract_def_spec.cr | 7 +- spec/compiler/semantic/call_error_spec.cr | 39 +++++++ spec/compiler/semantic/def_overload_spec.cr | 6 +- spec/compiler/semantic/def_spec.cr | 4 +- spec/compiler/semantic/initialize_spec.cr | 3 +- spec/compiler/semantic/restrictions_spec.cr | 2 +- spec/compiler/semantic/virtual_spec.cr | 4 +- src/compiler/crystal/semantic/call_error.cr | 112 +++++++++++++++++--- 8 files changed, 146 insertions(+), 31 deletions(-) diff --git a/spec/compiler/semantic/abstract_def_spec.cr b/spec/compiler/semantic/abstract_def_spec.cr index 643ccc108037..0cbff757099f 100644 --- a/spec/compiler/semantic/abstract_def_spec.cr +++ b/spec/compiler/semantic/abstract_def_spec.cr @@ -94,12 +94,7 @@ describe "Semantic: abstract def" do Bar.new.foo(1 || 'a') ), - <<-MSG - Overloads are: - - Bar#foo(x : Int32) - Couldn't find overloads for these types: - - Bar#foo(x : Char) - MSG + "expected argument #1 to 'Bar#foo' to be Int32, not (Char | Int32)" end it "errors if using abstract def on non-abstract class" do diff --git a/spec/compiler/semantic/call_error_spec.cr b/spec/compiler/semantic/call_error_spec.cr index 9c4040671dbd..ea4fcf85234e 100644 --- a/spec/compiler/semantic/call_error_spec.cr +++ b/spec/compiler/semantic/call_error_spec.cr @@ -254,4 +254,43 @@ describe "Call errors" do ), "expected argument 'x' to 'foo' to match a member of enum Color.\n\nDid you mean :red?" end + + it "errors on argument if more types are given than expected" do + assert_error %( + def foo(x : Int32) + end + + def foo(x : Char) + end + + foo(1 || nil) + ), + "expected argument #1 to 'foo' to be Int32, not (Int32 | Nil)" + end + + it "errors on argument if more types are given than expected, shows all expected types" do + assert_error %( + def foo(x : Int32) + end + + def foo(x : Char) + end + + foo(1 ? nil : (1 || 'a')) + ), + "expected argument #1 to 'foo' to be Char or Int32, not (Char | Int32 | Nil)" + end + + it "errors on argument if argument matches in all overloads but with different types in other arguments" do + assert_error %( + def foo(x : String, y : Int32, w : Int32) + end + + def foo(x : String, y : Nil, w : Char) + end + + foo("a", 1 || nil, 1) + ), + "expected argument #2 to 'foo' to be Int32, not (Int32 | Nil)" + end end diff --git a/spec/compiler/semantic/def_overload_spec.cr b/spec/compiler/semantic/def_overload_spec.cr index 44149dfc1b94..439830c9bfc7 100644 --- a/spec/compiler/semantic/def_overload_spec.cr +++ b/spec/compiler/semantic/def_overload_spec.cr @@ -1437,7 +1437,7 @@ describe "Semantic: def overload" do a = 1 || nil f(a: a) ), - "no overload matches" + "expected argument 'a' to 'f' to be Int32, not (Int32 | Nil)" end it "errors if no overload matches on union against named arg with external param name (#10516)" do @@ -1448,7 +1448,7 @@ describe "Semantic: def overload" do a = 1 || nil f(a: a) ), - "no overload matches" + "expected argument 'a' to 'f' to be Int32, not (Int32 | Nil)" end it "dispatches with named arg" do @@ -1642,7 +1642,7 @@ describe "Semantic: def overload" do x = uninitialized Foo foo(x) ), - "no overload matches" + "expected argument #1 to 'foo' to be Bar, not Foo" end it "treats single splats with same restriction as equivalent (#12579)" do diff --git a/spec/compiler/semantic/def_spec.cr b/spec/compiler/semantic/def_spec.cr index 45ec41de6249..bf758e298dd4 100644 --- a/spec/compiler/semantic/def_spec.cr +++ b/spec/compiler/semantic/def_spec.cr @@ -121,7 +121,7 @@ describe "Semantic: def" do foo 1 || 1.5 ", - "no overload matches" + "expected argument #1 to 'foo' to be Int, not (Float64 | Int32)" end it "reports no overload matches 2" do @@ -134,7 +134,7 @@ describe "Semantic: def" do foo(1 || 'a', 1 || 1.5) ", - "no overload matches" + "expected argument #1 to 'foo' to be Int, not (Char | Int32)" end it "reports no block given" do diff --git a/spec/compiler/semantic/initialize_spec.cr b/spec/compiler/semantic/initialize_spec.cr index 674d7a59e769..e10be70edf08 100644 --- a/spec/compiler/semantic/initialize_spec.cr +++ b/spec/compiler/semantic/initialize_spec.cr @@ -640,7 +640,8 @@ describe "Semantic: initialize" do a = 1 > 0 ? nil : 1 Foo.new(a) ", - "no overload matches", inject_primitives: true + "expected argument #1 to 'Foo.new' to be Int32, not (Int32 | Nil)", + inject_primitives: true end it "doesn't mark instance variable as nilable when using self.class" do diff --git a/spec/compiler/semantic/restrictions_spec.cr b/spec/compiler/semantic/restrictions_spec.cr index 11414769cd1d..139f881cc447 100644 --- a/spec/compiler/semantic/restrictions_spec.cr +++ b/spec/compiler/semantic/restrictions_spec.cr @@ -750,7 +750,7 @@ describe "Restrictions" do bar(1 || "") ), - "no overload matches" + "expected argument #1 to 'bar' to be String, not (Int32 | String)" end it "errors on T::Type that's union when used from type restriction" do diff --git a/spec/compiler/semantic/virtual_spec.cr b/spec/compiler/semantic/virtual_spec.cr index 642a235503a7..76a8d6834f87 100644 --- a/spec/compiler/semantic/virtual_spec.cr +++ b/spec/compiler/semantic/virtual_spec.cr @@ -384,7 +384,7 @@ describe "Semantic: virtual" do f = Bar1.new || Bar2.new || Baz.new foo(f) ", - "no overload matches" + "expected argument #1 to 'foo' to be Bar1 or Baz, not Foo" end it "checks cover in every concrete subclass" do @@ -446,7 +446,7 @@ describe "Semantic: virtual" do f = Bar1.new || Bar2.new || Baz.new f.foo(f) ", - "no overload matches" + "expected argument #1 to 'Baz#foo' to be Bar1 or Baz, not Foo" end it "checks cover in every concrete subclass 3" do diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index e36f5953ea4e..6433cb101392 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -123,6 +123,7 @@ class Crystal::Call check_extra_named_arguments(call_errors, owner, defs, arg_types, inner_exception) check_arguments_already_specified(call_errors, owner, defs, arg_types, inner_exception) check_wrong_number_of_arguments(call_errors, owner, defs, def_name, arg_types, named_args_types, inner_exception) + check_extra_types_arguments_mismatch(call_errors, owner, defs, def_name, arg_types, named_args_types, inner_exception) check_arguments_type_mismatch(call_errors, owner, defs, def_name, arg_types, named_args_types, inner_exception) if args.size == 1 && args.first.type.includes_type?(program.nil) @@ -259,6 +260,40 @@ class Crystal::Call raise_matches_not_found_named_args(owner, def_name, defs, arg_types, named_args_types, inner_exception) end + private def check_extra_types_arguments_mismatch(call_errors, owner, defs, def_name, arg_types, named_args_types, inner_exception) + call_errors = call_errors.select(ArgumentsTypeMismatch) + return if call_errors.empty? + + call_errors = call_errors.map &.as(ArgumentsTypeMismatch) + argument_type_mismatches = call_errors.flat_map(&.errors) + + argument_type_mismatches.select!(&.extra_types) + return if argument_type_mismatches.empty? + + argument_type_mismatches.each do |target_error| + index_or_name = target_error.index_or_name + + mismatches = argument_type_mismatches.select(&.index_or_name.==(index_or_name)) + expected_types = mismatches.map(&.expected_type).uniq! + actual_type = mismatches.first.actual_type + + actual_types = + if actual_type.is_a?(UnionType) + actual_type.union_types + else + [actual_type] of Type + end + + # It could happen that a type that's missing in one overload is actually + # covered in another overload, and eventually all overloads are covered + if expected_types.to_set == actual_types.to_set + expected_types = [target_error.expected_type] + end + + raise_argument_type_mismatch(index_or_name, actual_type, expected_types.sort_by!(&.to_s), owner, defs, def_name, arg_types, inner_exception) + end + end + private def check_arguments_type_mismatch(call_errors, owner, defs, def_name, arg_types, named_args_types, inner_exception) call_errors = call_errors.select(ArgumentsTypeMismatch) return if call_errors.empty? @@ -280,6 +315,10 @@ class Crystal::Call expected_types = mismatches.map(&.expected_type).uniq!.sort_by!(&.to_s) actual_type = mismatches.first.actual_type + raise_argument_type_mismatch(index_or_name, actual_type, expected_types, owner, defs, def_name, arg_types, inner_exception) + end + + private def raise_argument_type_mismatch(index_or_name, actual_type, expected_types, owner, defs, def_name, arg_types, inner_exception) arg = case index_or_name in Int32 @@ -324,7 +363,7 @@ class Crystal::Call else str << "expected argument #{argument_description} to '#{full_name(owner, def_name)}' to be " to_sentence(str, expected_types, " or ") - str << ", not #{actual_type}" + str << ", not #{actual_type.devirtualize}" end end end @@ -359,7 +398,11 @@ class Crystal::Call record ExtraNamedArguments, names : Array(String), similar_names : Array(String?) record ArgumentsAlreadySpecified, names : Array(String) record ArgumentsTypeMismatch, errors : Array(ArgumentTypeMismatch) - record ArgumentTypeMismatch, index_or_name : (Int32 | String), expected_type : Type | ASTNode, actual_type : Type + record ArgumentTypeMismatch, + index_or_name : (Int32 | String), + expected_type : Type | ASTNode, + actual_type : Type, + extra_types : Array(Type)? private def compute_call_error_reason(owner, a_def, arg_types, named_args_types) if (block && !a_def.yields) || (!block && a_def.yields) @@ -461,23 +504,60 @@ class Crystal::Call private def check_argument_type_mismatch(def_arg, index_or_name, arg_type, match_context, arguments_type_mismatch) restricted = arg_type.restrict(def_arg, match_context) - unless restricted - expected_type = def_arg.type? - unless expected_type - restriction = def_arg.restriction - if restriction - expected_type = match_context.instantiated_type.lookup_type?(restriction, free_vars: match_context.free_vars) - end + + arg_type = arg_type.remove_literal + return if restricted == arg_type + + expected_type = compute_expected_type(def_arg, match_context) + + extra_types = + if restricted + # This was a partial match + compute_extra_types(arg_type, expected_type) + else + # This wasn't a match + nil end - expected_type ||= def_arg.restriction.not_nil! - expected_type = expected_type.devirtualize if expected_type.is_a?(Type) - arguments_type_mismatch << ArgumentTypeMismatch.new( - index_or_name: index_or_name, - expected_type: expected_type, - actual_type: arg_type.remove_literal, - ) + arguments_type_mismatch << ArgumentTypeMismatch.new( + index_or_name: index_or_name, + expected_type: expected_type, + actual_type: arg_type.remove_literal, + extra_types: extra_types, + ) + end + + private def compute_expected_type(def_arg, match_context) + expected_type = def_arg.type? + unless expected_type + restriction = def_arg.restriction + if restriction + expected_type = match_context.instantiated_type.lookup_type?(restriction, free_vars: match_context.free_vars) + end end + expected_type ||= def_arg.restriction.not_nil! + expected_type = expected_type.devirtualize if expected_type.is_a?(Type) + expected_type + end + + private def compute_extra_types(actual_type, expected_type) + expected_types = + if expected_type.is_a?(UnionType) + expected_type.union_types + elsif expected_type.is_a?(Type) + [expected_type] of Type + else + return + end + + actual_types = + if actual_type.is_a?(UnionType) + actual_type.union_types + else + [actual_type] of Type + end + + actual_types - expected_types end private def no_overload_matches_message(io, full_name, defs, args, arg_types, named_args_types) From d447f5825ac6f71c1f7bc17c20b0518f4ec68738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 16 Nov 2022 10:57:56 +0100 Subject: [PATCH 0126/1551] [CI] Add version pin for ilammy/msvc-dev-cmd in windows CI (#12746) --- .github/workflows/win.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 12f27d75edca..eed3e1ee41ee 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -31,7 +31,7 @@ jobs: run: | git config --global core.autocrlf false - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@ed94116c4d30d2091601b81f339a2eaa1c2ba0a6 + uses: ilammy/msvc-dev-cmd@ed94116c4d30d2091601b81f339a2eaa1c2ba0a6 # v1.4.1 - name: Download Crystal source uses: actions/checkout@v3 From 3973f44d51dd4f38625683df58bb63624e653459 Mon Sep 17 00:00:00 2001 From: Dmitri Goutnik Date: Wed, 16 Nov 2022 05:19:52 -0500 Subject: [PATCH 0127/1551] Enable `arc4random(3)` on all supported BSDs and macOS/Darwin (#12608) Co-authored-by: Jamie Gaskins --- src/crystal/system/random.cr | 2 +- src/crystal/system/unix/arc4random.cr | 2 +- src/lib_c/aarch64-darwin/c/stdlib.cr | 3 +++ src/lib_c/x86_64-darwin/c/stdlib.cr | 3 +++ src/lib_c/x86_64-dragonfly/c/stdlib.cr | 3 +++ src/lib_c/x86_64-freebsd/c/stdlib.cr | 3 +++ src/lib_c/x86_64-netbsd/c/stdlib.cr | 1 + src/lib_c/x86_64-openbsd/c/stdlib.cr | 1 + src/random/secure.cr | 2 +- 9 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/crystal/system/random.cr b/src/crystal/system/random.cr index becb879cedbb..1a5b3c8f4677 100644 --- a/src/crystal/system/random.cr +++ b/src/crystal/system/random.cr @@ -14,7 +14,7 @@ end require "./wasi/random" {% elsif flag?(:linux) %} require "./unix/getrandom" -{% elsif flag?(:openbsd) || flag?(:netbsd) %} +{% elsif flag?(:bsd) || flag?(:darwin) %} require "./unix/arc4random" {% elsif flag?(:unix) %} require "./unix/urandom" diff --git a/src/crystal/system/unix/arc4random.cr b/src/crystal/system/unix/arc4random.cr index 4338e3341974..53ead51228db 100644 --- a/src/crystal/system/unix/arc4random.cr +++ b/src/crystal/system/unix/arc4random.cr @@ -1,4 +1,4 @@ -{% skip_file unless flag?(:openbsd) || flag?(:netbsd) %} +{% skip_file unless flag?(:bsd) || flag?(:darwin) %} require "c/stdlib" diff --git a/src/lib_c/aarch64-darwin/c/stdlib.cr b/src/lib_c/aarch64-darwin/c/stdlib.cr index e7d1e35d2ecb..82e7c165d6f5 100644 --- a/src/lib_c/aarch64-darwin/c/stdlib.cr +++ b/src/lib_c/aarch64-darwin/c/stdlib.cr @@ -7,6 +7,9 @@ lib LibC rem : Int end + fun arc4random : UInt32 + fun arc4random_buf(x0 : Void*, x1 : SizeT) : Void + fun arc4random_uniform(x0 : UInt32T) : UInt32T fun atof(x0 : Char*) : Double fun div(x0 : Int, x1 : Int) : DivT fun exit(x0 : Int) : NoReturn diff --git a/src/lib_c/x86_64-darwin/c/stdlib.cr b/src/lib_c/x86_64-darwin/c/stdlib.cr index e7d1e35d2ecb..82e7c165d6f5 100644 --- a/src/lib_c/x86_64-darwin/c/stdlib.cr +++ b/src/lib_c/x86_64-darwin/c/stdlib.cr @@ -7,6 +7,9 @@ lib LibC rem : Int end + fun arc4random : UInt32 + fun arc4random_buf(x0 : Void*, x1 : SizeT) : Void + fun arc4random_uniform(x0 : UInt32T) : UInt32T fun atof(x0 : Char*) : Double fun div(x0 : Int, x1 : Int) : DivT fun exit(x0 : Int) : NoReturn diff --git a/src/lib_c/x86_64-dragonfly/c/stdlib.cr b/src/lib_c/x86_64-dragonfly/c/stdlib.cr index d3325e2defd0..81dbfd85291b 100644 --- a/src/lib_c/x86_64-dragonfly/c/stdlib.cr +++ b/src/lib_c/x86_64-dragonfly/c/stdlib.cr @@ -7,6 +7,9 @@ lib LibC rem : Int end + fun arc4random : UInt32 + fun arc4random_buf(x0 : Void*, x1 : SizeT) : Void + fun arc4random_uniform(x0 : UInt32T) : UInt32T fun atof(x0 : Char*) : Double fun div(x0 : Int, x1 : Int) : DivT fun exit(x0 : Int) : NoReturn diff --git a/src/lib_c/x86_64-freebsd/c/stdlib.cr b/src/lib_c/x86_64-freebsd/c/stdlib.cr index d3325e2defd0..81dbfd85291b 100644 --- a/src/lib_c/x86_64-freebsd/c/stdlib.cr +++ b/src/lib_c/x86_64-freebsd/c/stdlib.cr @@ -7,6 +7,9 @@ lib LibC rem : Int end + fun arc4random : UInt32 + fun arc4random_buf(x0 : Void*, x1 : SizeT) : Void + fun arc4random_uniform(x0 : UInt32T) : UInt32T fun atof(x0 : Char*) : Double fun div(x0 : Int, x1 : Int) : DivT fun exit(x0 : Int) : NoReturn diff --git a/src/lib_c/x86_64-netbsd/c/stdlib.cr b/src/lib_c/x86_64-netbsd/c/stdlib.cr index 49aada507901..fc374b5a2861 100644 --- a/src/lib_c/x86_64-netbsd/c/stdlib.cr +++ b/src/lib_c/x86_64-netbsd/c/stdlib.cr @@ -9,6 +9,7 @@ lib LibC fun arc4random : UInt32 fun arc4random_buf(x0 : Void*, x1 : SizeT) : Void + fun arc4random_uniform(x0 : UInt32T) : UInt32T fun atof(x0 : Char*) : Double fun div(x0 : Int, x1 : Int) : DivT fun exit(x0 : Int) : NoReturn diff --git a/src/lib_c/x86_64-openbsd/c/stdlib.cr b/src/lib_c/x86_64-openbsd/c/stdlib.cr index f5d053f7b156..81dbfd85291b 100644 --- a/src/lib_c/x86_64-openbsd/c/stdlib.cr +++ b/src/lib_c/x86_64-openbsd/c/stdlib.cr @@ -9,6 +9,7 @@ lib LibC fun arc4random : UInt32 fun arc4random_buf(x0 : Void*, x1 : SizeT) : Void + fun arc4random_uniform(x0 : UInt32T) : UInt32T fun atof(x0 : Char*) : Double fun div(x0 : Int, x1 : Int) : DivT fun exit(x0 : Int) : NoReturn diff --git a/src/random/secure.cr b/src/random/secure.cr index 3c839e488f04..1722b5e6e884 100644 --- a/src/random/secure.cr +++ b/src/random/secure.cr @@ -11,7 +11,7 @@ require "crystal/system/random" # [1, 5, 6].shuffle(Random::Secure) # => [6, 1, 5] # ``` # -# On OpenBSD, it uses [`arc4random`](https://man.openbsd.org/arc4random), +# On BSD-based systems and macOS/Darwin, it uses [`arc4random`](https://man.openbsd.org/arc4random), # on Linux [`getrandom`](http://man7.org/linux/man-pages/man2/getrandom.2.html) (if the kernel supports it), # on Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom), # and falls back to reading from `/dev/urandom` on UNIX systems. From 1d431cd33ece2b241eb4180155effe9c834dbb9a Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Wed, 16 Nov 2022 11:20:13 +0100 Subject: [PATCH 0128/1551] Lint: Use `Enumerable#find!/#index!` variants (#12686) --- spec/std/xml/html_spec.cr | 8 ++++---- spec/std/xml/xml_spec.cr | 6 +++--- src/compiler/crystal/codegen/cast.cr | 12 ++++++------ src/compiler/crystal/crystal_path.cr | 2 +- src/compiler/crystal/semantic/call.cr | 6 +++--- src/compiler/crystal/semantic/call_error.cr | 2 +- src/compiler/crystal/tools/playground/server.cr | 6 +++--- src/compiler/crystal/types.cr | 4 ++-- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/spec/std/xml/html_spec.cr b/spec/std/xml/html_spec.cr index b91d552c0b54..49cb14056c0d 100644 --- a/spec/std/xml/html_spec.cr +++ b/spec/std/xml/html_spec.cr @@ -18,15 +18,15 @@ describe XML do html = doc.children[1] html.name.should eq("html") - head = html.children.find { |node| node.name == "head" }.not_nil! + head = html.children.find! { |node| node.name == "head" } head.name.should eq("head") - title = head.children.find { |node| node.name == "title" }.not_nil! + title = head.children.find! { |node| node.name == "title" } title.text.should eq("Samantha") - body = html.children.find { |node| node.name == "body" }.not_nil! + body = html.children.find! { |node| node.name == "body" } - h1 = body.children.find { |node| node.name == "h1" }.not_nil! + h1 = body.children.find! { |node| node.name == "h1" } attrs = h1.attributes attrs.should_not be_empty diff --git a/spec/std/xml/xml_spec.cr b/spec/std/xml/xml_spec.cr index d003b79122ad..cb5db774e32f 100644 --- a/spec/std/xml/xml_spec.cr +++ b/spec/std/xml/xml_spec.cr @@ -71,7 +71,7 @@ describe XML do person["id3"]?.should be_nil expect_raises(KeyError) { person["id3"] } - name = person.children.find { |node| node.name == "name" }.not_nil! + name = person.children.find! { |node| node.name == "name" } name.content.should eq("John") name.parent.should eq(person) @@ -92,8 +92,8 @@ describe XML do doc.document.should eq(doc) doc.name.should eq("document") - people = doc.children.find { |node| node.name == "people" }.not_nil! - person = people.children.find { |node| node.name == "person" }.not_nil! + people = doc.children.find! { |node| node.name == "people" } + person = people.children.find! { |node| node.name == "person" } person["id"].should eq("1") end diff --git a/src/compiler/crystal/codegen/cast.cr b/src/compiler/crystal/codegen/cast.cr index f4f391115a8a..a409f5bc1b02 100644 --- a/src/compiler/crystal/codegen/cast.cr +++ b/src/compiler/crystal/codegen/cast.cr @@ -125,7 +125,7 @@ class Crystal::CodeGenVisitor types_needing_cast.each_with_index do |type_needing_cast, i| # Find compatible type - compatible_type = target_type.union_types.find { |ut| type_needing_cast.implements?(ut) }.not_nil! + compatible_type = target_type.union_types.find! { |ut| type_needing_cast.implements?(ut) } matches_label, doesnt_match_label = new_blocks "matches", "doesnt_match_label" cmp_result = equal?(value_type_id, type_id(type_needing_cast)) @@ -184,7 +184,7 @@ class Crystal::CodeGenVisitor # It might happen that `value_type` is not of the union but it's compatible with one of them. # We need to first cast the value to the compatible type and then store it in the value. unless target_type.union_types.any? &.==(value_type) - compatible_type = target_type.union_types.find { |ut| value_type.implements?(ut) }.not_nil! + compatible_type = target_type.union_types.find! { |ut| value_type.implements?(ut) } value = upcast(value, compatible_type, value_type) return assign(target_pointer, target_type, compatible_type, value) end @@ -380,7 +380,7 @@ class Crystal::CodeGenVisitor Phi.open(self, to_type, @needs_value) do |phi| types_needing_cast.each_with_index do |type_needing_cast, i| # Find compatible type - compatible_type = to_type.union_types.find { |ut| ut.implements?(type_needing_cast) }.not_nil! + compatible_type = to_type.union_types.find! { |ut| ut.implements?(type_needing_cast) } matches_label, doesnt_match_label = new_blocks "matches", "doesnt_match_label" cmp_result = equal?(from_type_id, type_id(type_needing_cast)) @@ -422,7 +422,7 @@ class Crystal::CodeGenVisitor case to_type when TupleInstanceType, NamedTupleInstanceType unless from_type.union_types.any? &.==(to_type) - compatible_type = from_type.union_types.find { |ut| to_type.implements?(ut) }.not_nil! + compatible_type = from_type.union_types.find! { |ut| to_type.implements?(ut) } value = downcast(value, compatible_type, from_type, true) value = downcast(value, to_type, compatible_type, true) return value @@ -577,7 +577,7 @@ class Crystal::CodeGenVisitor Phi.open(self, to_type, @needs_value) do |phi| types_needing_cast.each_with_index do |type_needing_cast, i| # Find compatible type - compatible_type = to_type.union_types.find { |ut| type_needing_cast.implements?(ut) }.not_nil! + compatible_type = to_type.union_types.find! { |ut| type_needing_cast.implements?(ut) } matches_label, doesnt_match_label = new_blocks "matches", "doesnt_match_label" cmp_result = equal?(from_type_id, type_id(type_needing_cast)) @@ -625,7 +625,7 @@ class Crystal::CodeGenVisitor case from_type when TupleInstanceType, NamedTupleInstanceType unless to_type.union_types.any? &.==(from_type) - compatible_type = to_type.union_types.find { |ut| from_type.implements?(ut) }.not_nil! + compatible_type = to_type.union_types.find! { |ut| from_type.implements?(ut) } value = upcast(value, compatible_type, from_type) return upcast(value, to_type, compatible_type) end diff --git a/src/compiler/crystal/crystal_path.cr b/src/compiler/crystal/crystal_path.cr index 1b11fbd3fde1..0e2da64781cd 100644 --- a/src/compiler/crystal/crystal_path.cr +++ b/src/compiler/crystal/crystal_path.cr @@ -84,7 +84,7 @@ module Crystal end end - def find(filename, relative_to = nil) : Array(String)? + def find(filename, relative_to = nil) : Array(String) relative_to = File.dirname(relative_to) if relative_to.is_a?(String) if filename.starts_with? '.' diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index 3a80d50515cb..e3b0dcf47012 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -665,11 +665,11 @@ class Crystal::Call parents = lookup.base_type.ancestors when NonGenericModuleType ancestors = parent_visitor.scope.ancestors - index_of_ancestor = ancestors.index(lookup).not_nil! + index_of_ancestor = ancestors.index!(lookup) parents = ancestors[index_of_ancestor + 1..-1] when GenericModuleType ancestors = parent_visitor.scope.ancestors - index_of_ancestor = ancestors.index { |ancestor| ancestor.is_a?(GenericModuleInstanceType) && ancestor.generic_type == lookup }.not_nil! + index_of_ancestor = ancestors.index! { |ancestor| ancestor.is_a?(GenericModuleInstanceType) && ancestor.generic_type == lookup } parents = ancestors[index_of_ancestor + 1..-1] when GenericType ancestors = parent_visitor.scope.ancestors @@ -1216,7 +1216,7 @@ class Crystal::Call end named_args_types.try &.each do |named_arg| - arg = typed_def.args.find { |arg| arg.external_name == named_arg.name }.not_nil! + arg = typed_def.args.find! { |arg| arg.external_name == named_arg.name } type = named_arg.type var = MetaVar.new(arg.name, type) diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index 6433cb101392..0271724b5b0a 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -216,7 +216,7 @@ class Crystal::Call # Show did you mean for the simplest case for now if names.size == 1 && call_errors.size == 1 extra_name = names.first - name_index = call_errors.first.names.index(extra_name).not_nil! + name_index = call_errors.first.names.index!(extra_name) similar_name = call_errors.first.similar_names[name_index] if similar_name str.puts diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr index dfd8cce734b3..12914e593305 100644 --- a/src/compiler/crystal/tools/playground/server.cr +++ b/src/compiler/crystal/tools/playground/server.cr @@ -22,7 +22,7 @@ module Crystal::Playground instrumented = Playground::AgentInstrumentorTransformer.transform(ast).to_s Log.info { "Code instrumentation (session=#{session_key}, tag=#{tag}).\n#{instrumented}" } - prelude = %( + prelude = <<-CR require "compiler/crystal/tools/playground/agent" class Crystal::Playground::Agent @@ -36,7 +36,7 @@ module Crystal::Playground def _p Crystal::Playground::Agent.instance end - ) + CR [ Compiler::Source.new("playground_prelude", prelude), @@ -449,7 +449,7 @@ module Crystal::Playground end def start - playground_dir = File.dirname(CrystalPath.new.find("compiler/crystal/tools/playground/server.cr").not_nil![0]) + playground_dir = File.dirname(__FILE__) views_dir = File.join(playground_dir, "views") public_dir = File.join(playground_dir, "public") diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index fbeea4b4e73a..f27447775da7 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -2555,11 +2555,11 @@ module Crystal end def name_index(name) - @entries.index &.name.==(name) + @entries.index(&.name.==(name)) end def name_type(name) - @entries.find(&.name.==(name)).not_nil!.type + @entries.find!(&.name.==(name)).type end def tuple_indexer(index) From 002e065263663c6faa47eff11c10a881d27553f4 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Wed, 16 Nov 2022 18:21:03 +0800 Subject: [PATCH 0129/1551] Error when declaring a constant within another constant declaration (#12566) Co-authored-by: Sijawusz Pur Rahnama --- spec/compiler/parser/parser_spec.cr | 10 ++++++++++ src/compiler/crystal/syntax/parser.cr | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 1c6693d64780..311b0c3e0344 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1936,6 +1936,16 @@ module Crystal assert_syntax_error "{1, ->do\n|x| x\end }", "unexpected token: \"|\", proc literals specify their parameters like this: ->(x : Type) { ... }" assert_syntax_error "{1, ->{ |_| x } }", "unexpected token: \"|\", proc literals specify their parameters like this: ->(param : Type) { ... }" + # #2874 + assert_syntax_error "A = B = 1", "dynamic constant assignment" + assert_syntax_error "A = (B = 1)", "dynamic constant assignment" + assert_syntax_error "A = foo(B = 1)", "dynamic constant assignment" + assert_syntax_error "A = foo { B = 1 }", "dynamic constant assignment" + assert_syntax_error "A = begin; B = 1; end", "dynamic constant assignment" + assert_syntax_error "A = begin; 1; rescue; B = 1; end", "dynamic constant assignment" + assert_syntax_error "A = begin; 1; rescue; 1; else; B = 1; end", "dynamic constant assignment" + assert_syntax_error "A = begin; 1; ensure; B = 1; end", "dynamic constant assignment" + assert_syntax_error "1 while 3", "trailing `while` is not supported" assert_syntax_error "1 until 3", "trailing `until` is not supported" assert_syntax_error "x++", "postfix increment is not supported, use `exp += 1`" diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 515eeb2f2059..adcbcabeece5 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -36,6 +36,7 @@ module Crystal @def_nest = 0 @fun_nest = 0 @type_nest = 0 + @is_constant_assignment = false # Keeps track of current call args starting locations, # so if we parse a type declaration exactly at those points we @@ -380,10 +381,12 @@ module Crystal else break unless can_be_assigned?(atomic) - if atomic.is_a?(Path) && (inside_def? || inside_fun?) + if atomic.is_a?(Path) && (inside_def? || inside_fun? || @is_constant_assignment) raise "dynamic constant assignment. Constants can only be declared at the top level or inside other types." end + @is_constant_assignment = true if atomic.is_a?(Path) + if atomic.is_a?(Var) && atomic.name == "self" raise "can't change the value of self", location end @@ -432,6 +435,8 @@ module Crystal end end + @is_constant_assignment = false if atomic.is_a?(Path) + push_var atomic atomic = Assign.new(atomic, atomic_value).at(location) From 9f4fed0e70050d33f1a32c907dd64e708f7cf31b Mon Sep 17 00:00:00 2001 From: Denis Maslennikov Date: Wed, 16 Nov 2022 20:13:28 +0300 Subject: [PATCH 0130/1551] Add `File.executable?` for Windows (#9677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/file_spec.cr | 7 +++---- src/crystal/system/win32/file.cr | 2 +- src/lib_c/x86_64-windows-msvc/c/winbase.cr | 2 ++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 54993835500d..746ce26fbff3 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -132,17 +132,16 @@ describe "File" do end end - # TODO: implement this for win32 describe "executable?" do - pending_win32 "gives false" do + it "gives false" do File.executable?(datapath("test_file.txt")).should be_false end - pending_win32 "gives false when the file doesn't exist" do + it "gives false when the file doesn't exist" do File.executable?(datapath("non_existing_file.txt")).should be_false end - pending_win32 "gives false when a component of the path is a file" do + it "gives false when a component of the path is a file" do File.executable?(datapath("dir", "test_file.txt", "")).should be_false end end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 7a44bfdee207..07b91f3828fa 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -124,7 +124,7 @@ module Crystal::System::File end def self.executable?(path) : Bool - raise NotImplementedError.new("File.executable?") + LibC.GetBinaryTypeW(to_windows_path(path), out result) != 0 end private def self.accessible?(path, mode) diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index a5d2ccbe72ae..b213335b0d14 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -41,4 +41,6 @@ lib LibC MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x20_u32 fun MoveFileExW(lpExistingFileName : LPWSTR, lpNewFileName : LPWSTR, dwFlags : DWORD) : BOOL + + fun GetBinaryTypeW(lpApplicationName : LPWSTR, lpBinaryType : DWORD*) : BOOL end From 4a0e38bda3f706b3f690bcbf39f85cc56b4cc093 Mon Sep 17 00:00:00 2001 From: James Clarke Date: Wed, 16 Nov 2022 17:14:20 +0000 Subject: [PATCH 0131/1551] Fix `OpenSSL::SSL::Context::Client#alpn_protocol=` (#12724) The current `OpenSSL::SSL::Context#alpn_protocol=` setter calls `LibSSL.ssl_ctx_set_alpn_select_cb`, which only works for server SSL contexts. This PR moves this setter into the `Context::Server` subclass, and adds a `Context::Client#alpn_protocol=` setter that calls the correct `LibSSL.ssl_ctx_set_alpn_protos` method for client contexts. --- spec/std/openssl/ssl/socket_spec.cr | 20 +++++++++++++ src/openssl/lib_ssl.cr | 1 + src/openssl/ssl/context.cr | 44 +++++++++++++++++------------ 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/spec/std/openssl/ssl/socket_spec.cr b/spec/std/openssl/ssl/socket_spec.cr index f4265cd0a1b8..ccf1046b6949 100644 --- a/spec/std/openssl/ssl/socket_spec.cr +++ b/spec/std/openssl/ssl/socket_spec.cr @@ -93,6 +93,26 @@ describe OpenSSL::SSL::Socket do ) end + it "returns selected alpn protocol" do + tcp_server = TCPServer.new("127.0.0.1", 0) + server_context, client_context = ssl_context_pair + + server_context.alpn_protocol = "h2" + client_context.alpn_protocol = "h2" + + OpenSSL::SSL::Server.open(tcp_server, server_context) do |server| + spawn do + Client.open(TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port), client_context, hostname: "example.com") do |socket| + socket.alpn_protocol.should eq("h2") + end + end + + client = server.accept + client.alpn_protocol.should eq("h2") + client.close + end + end + it "accepts clients that only write then close the connection" do tcp_server = TCPServer.new("127.0.0.1", 0) server_context, client_context = ssl_context_pair diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr index 2e781ea09919..e6faefa52d8e 100644 --- a/src/openssl/lib_ssl.cr +++ b/src/openssl/lib_ssl.cr @@ -261,6 +261,7 @@ lib LibSSL fun ssl_get0_alpn_selected = SSL_get0_alpn_selected(handle : SSL, data : Char**, len : LibC::UInt*) : Void fun ssl_ctx_set_alpn_select_cb = SSL_CTX_set_alpn_select_cb(ctx : SSLContext, cb : ALPNCallback, arg : Void*) : Void + fun ssl_ctx_set_alpn_protos = SSL_CTX_set_alpn_protos(ctx : SSLContext, protos : Char*, protos_len : Int) : Int {% end %} {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 %} diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr index 0bf32ae7f959..37c54ca77eb2 100644 --- a/src/openssl/ssl/context.cr +++ b/src/openssl/ssl/context.cr @@ -98,6 +98,14 @@ abstract class OpenSSL::SSL::Context end }, hostname.as(Void*)) end + + private def alpn_protocol=(protocol : Bytes) + {% if LibSSL.has_method?(:ssl_ctx_set_alpn_protos) %} + LibSSL.ssl_ctx_set_alpn_protos(@handle, protocol, protocol.size) + {% else %} + raise NotImplementedError.new("LibSSL.ssl_ctx_set_alpn_protos") + {% end %} + end end class Server < Context @@ -173,6 +181,24 @@ abstract class OpenSSL::SSL::Context raise OpenSSL::Error.new("SSL_CTX_set_num_tickets") if ret != 1 {% end %} end + + private def alpn_protocol=(protocol : Bytes) + {% if LibSSL.has_method?(:ssl_ctx_set_alpn_select_cb) %} + alpn_cb = ->(ssl : LibSSL::SSL, o : LibC::Char**, olen : LibC::Char*, i : LibC::Char*, ilen : LibC::Int, data : Void*) { + proto = Box(Bytes).unbox(data) + ret = LibSSL.ssl_select_next_proto(o, olen, proto, 2, i, ilen) + if ret != LibSSL::OPENSSL_NPN_NEGOTIATED + LibSSL::SSL_TLSEXT_ERR_NOACK + else + LibSSL::SSL_TLSEXT_ERR_OK + end + } + @alpn_protocol = alpn_protocol = Box.box(protocol) + LibSSL.ssl_ctx_set_alpn_select_cb(@handle, alpn_cb, alpn_protocol) + {% else %} + raise NotImplementedError.new("LibSSL.ssl_ctx_set_alpn_select_cb") + {% end %} + end end protected def initialize(method : LibSSL::SSLMethod) @@ -429,24 +455,6 @@ abstract class OpenSSL::SSL::Context self.alpn_protocol = proto end - private def alpn_protocol=(protocol : Bytes) - {% if LibSSL.has_method?(:ssl_ctx_set_alpn_select_cb) %} - alpn_cb = ->(ssl : LibSSL::SSL, o : LibC::Char**, olen : LibC::Char*, i : LibC::Char*, ilen : LibC::Int, data : Void*) { - proto = Box(Bytes).unbox(data) - ret = LibSSL.ssl_select_next_proto(o, olen, proto, 2, i, ilen) - if ret != LibSSL::OPENSSL_NPN_NEGOTIATED - LibSSL::SSL_TLSEXT_ERR_NOACK - else - LibSSL::SSL_TLSEXT_ERR_OK - end - } - @alpn_protocol = alpn_protocol = Box.box(protocol) - LibSSL.ssl_ctx_set_alpn_select_cb(@handle, alpn_cb, alpn_protocol) - {% else %} - raise NotImplementedError.new("LibSSL.ssl_ctx_set_alpn_select_cb") - {% end %} - end - # Sets this context verify param to the default one of the given name. # # Depending on the OpenSSL version, the available defaults are From 47b02e62eb1d8d83b2cd69fe8aff1f1dce8939f0 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Thu, 17 Nov 2022 01:14:37 +0800 Subject: [PATCH 0132/1551] Fix missed elements in `Hash#select!(keys : Enumerable)` (#12739) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/hash_spec.cr | 6 ++++++ src/hash.cr | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/std/hash_spec.cr b/spec/std/hash_spec.cr index d168a20ecfea..2954446f69ce 100644 --- a/spec/std/hash_spec.cr +++ b/spec/std/hash_spec.cr @@ -1293,11 +1293,17 @@ describe "Hash" do it { {"a" => 2, "b" => 3}.select!("b", "a").should eq({"a" => 2, "b" => 3}) } it { {"a" => 2, "b" => 3}.select!(["b", "a"]).should eq({"a" => 2, "b" => 3}) } it { {"a" => 2, "b" => 3}.select!(Set{"b", "a"}).should eq({"a" => 2, "b" => 3}) } + it "does change current hash" do h = {"a" => 3, "b" => 6, "c" => 9} h.select!("b", "c") h.should eq({"b" => 6, "c" => 9}) end + + it "does not skip elements with an exhaustable enumerable argument (#12736)" do + h = {1 => 'a', 2 => 'b', 3 => 'c'}.select!({1, 2, 3}.each) + h.should eq({1 => 'a', 2 => 'b', 3 => 'c'}) + end end it "doesn't generate a negative index for the bucket index (#2321)" do diff --git a/src/hash.cr b/src/hash.cr index 9c6e751e27bb..63064498c55f 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -1569,8 +1569,16 @@ class Hash(K, V) # h1 == h2 == h3 == h4 # => true # h1 # => {"a" => 1, "c" => 3} # ``` + def select!(keys : Indexable) : self + each_key { |k| delete(k) unless k.in?(keys) } + self + end + + # :ditto: def select!(keys : Enumerable) : self - each { |k, v| delete(k) unless keys.includes?(k) } + # Convert enumerable to a set to prevent exhaustion of elements + key_set = keys.to_set + each_key { |k| delete(k) unless k.in?(key_set) } self end From 7c9f674a3e8c1673d2cac8dd9ae3aa170579d5a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 17 Nov 2022 01:00:44 +0100 Subject: [PATCH 0133/1551] Fix call to transform windows string using `System.to_wstr` (#12747) --- src/crystal/system/win32/file.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 07b91f3828fa..0df1926cc047 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -124,7 +124,7 @@ module Crystal::System::File end def self.executable?(path) : Bool - LibC.GetBinaryTypeW(to_windows_path(path), out result) != 0 + LibC.GetBinaryTypeW(System.to_wstr(path), out result) != 0 end private def self.accessible?(path, mode) From d5e7b850e87f7ec46deee1d3648a14e75cbd30a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 17 Nov 2022 01:01:30 +0100 Subject: [PATCH 0134/1551] [CI] Update dependencies for windows CI (#12745) --- .github/workflows/win.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index eed3e1ee41ee..50b906678014 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -25,7 +25,7 @@ jobs: x86_64-windows-job: needs: x86_64-linux-job - runs-on: windows-latest + runs-on: windows-2022 steps: - name: Disable CRLF line ending substitution run: | @@ -55,14 +55,14 @@ jobs: uses: actions/checkout@v3 with: repository: ivmai/bdwgc - ref: v8.2.0 + ref: v8.2.2 path: bdwgc - name: Download libatomic_ops if: steps.cache-libs.outputs.cache-hit != 'true' uses: actions/checkout@v3 with: repository: ivmai/libatomic_ops - ref: v7.6.10 + ref: v7.6.14 path: bdwgc/libatomic_ops - name: Build libgc if: steps.cache-libs.outputs.cache-hit != 'true' @@ -88,7 +88,7 @@ jobs: uses: actions/checkout@v3 with: repository: pffang/libiconv-for-Windows - ref: 9b7aba8da6e125ef33912fa4412779279f204003 # master @ 2021-08-24 + ref: 1353455a6c4e15c9db6865fd9c2bf7203b59c0ec # master@{2022-10-11} path: libiconv - name: Build libiconv if: steps.cache-libs.outputs.cache-hit != 'true' @@ -145,8 +145,8 @@ jobs: - name: Download zlib if: steps.cache-libs.outputs.cache-hit != 'true' run: | - iwr https://github.com/madler/zlib/archive/v1.2.11.zip -OutFile zlib.zip - (Get-FileHash -Algorithm SHA256 .\zlib.zip).hash -eq "f5cc4ab910db99b2bdbba39ebbdc225ffc2aa04b4057bc2817f1b94b6978cfc3" + iwr https://github.com/madler/zlib/archive/v1.2.13.zip -OutFile zlib.zip + (Get-FileHash -Algorithm SHA256 .\zlib.zip).hash -eq "C2856951BBF30E30861ACE3765595D86BA13F2CF01279D901F6C62258C57F4FF" 7z x zlib.zip mv zlib-* zlib - name: Build zlib @@ -160,7 +160,7 @@ jobs: uses: actions/checkout@v3 with: repository: BrianGladman/mpir - ref: d9c9a842be6475bef74324f367ce2c5a78c55d06 # master @ 2021-10-14 + ref: 28d01062f62de1218d511c4574da4006f92be3bd # master@{2022-10-12} path: mpir - name: Build mpir if: steps.cache-libs.outputs.cache-hit != 'true' @@ -187,8 +187,8 @@ jobs: - name: Download libyaml if: steps.cache-libs.outputs.cache-hit != 'true' run: | - iwr https://github.com/yaml/libyaml/archive/0.2.4.zip -OutFile libyaml.zip - (Get-FileHash -Algorithm SHA256 .\libyaml.zip).hash -eq "5882285b8265096d045ecebbee651b73bef6cead34e439b63e86bf393c936793" + iwr https://github.com/yaml/libyaml/archive/0.2.5.zip -OutFile libyaml.zip + (Get-FileHash -Algorithm SHA256 .\libyaml.zip).hash -eq "14605BAA6DFC0C4D3AB943A46A627413C0388736E453B67FE4E90C9683C8CBC8" 7z x libyaml.zip mv libyaml-* libyaml - name: Build libyaml @@ -202,13 +202,13 @@ jobs: uses: actions/checkout@v3 with: repository: GNOME/libxml2 - ref: a230b728f1289dd24c1666856ac4fb55579c6dfb # master @ 2020-05-04 + ref: f507d167f1755b7eaea09fb1a44d29aab828b6d1 # v2.10.3 path: libxml2 - name: Build libxml2 if: steps.cache-libs.outputs.cache-hit != 'true' working-directory: ./libxml2 run: | - cmake . -DBUILD_SHARED_LIBS=OFF -DLIBXML2_WITH_PROGRAMS=OFF -DLIBXML2_WITH_HTTP=OFF -DLIBXML2_WITH_FTP=OFF -DLIBXML2_WITH_TESTS=OFF -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF + cmake . -DBUILD_SHARED_LIBS=OFF -DLIBXML2_WITH_PROGRAMS=OFF -DLIBXML2_WITH_HTTP=OFF -DLIBXML2_WITH_FTP=OFF -DLIBXML2_WITH_TESTS=OFF -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLIBXML2_WITH_ICONV=OFF -DLIBXML2_WITH_LZMA=OFF -DLIBXML2_WITH_PYTHON=OFF -DLIBXML2_WITH_ZLIB=OFF echo ' @@ -230,7 +230,7 @@ jobs: run: | mkdir libs mv pcre/Release/pcre.lib libs/ - mv libiconv/lib64/libiconvStatic.lib libs/iconv.lib + mv libiconv/output/x64/ReleaseStatic/libiconvStatic.lib libs/iconv.lib mv bdwgc/Release/gc.lib libs/ mv libffi/win32/vs16_x64/x64/Release/libffi.lib libs/ffi.lib mv zlib/Release/zlibstatic.lib libs/z.lib @@ -278,12 +278,12 @@ jobs: uses: actions/cache@v3 with: path: llvm - key: llvm-libs-13.0.0-msvc-${{ env.VSCMD_VER }} + key: llvm-libs-13.0.1-msvc-${{ env.VSCMD_VER }} - name: Download LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' run: | - iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/llvm-13.0.0.src.tar.xz -OutFile llvm.tar.xz - (Get-FileHash -Algorithm SHA256 .\llvm.tar.xz).hash -eq "408d11708643ea826f519ff79761fcdfc12d641a2510229eec459e72f8163020" + iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.1/llvm-13.0.1.src.tar.xz -OutFile llvm.tar.xz + (Get-FileHash -Algorithm SHA256 .\llvm.tar.xz).hash -eq "EC6B80D82C384ACAD2DC192903A6CF2CDBAFFB889B84BFB98DA9D71E630FC834" 7z x llvm.tar.xz 7z x llvm.tar mv llvm-* llvm-src @@ -331,7 +331,7 @@ jobs: uses: actions/checkout@v3 with: repository: crystal-lang/shards - ref: v0.17.0 + ref: v0.17.1 path: shards - name: Download molinillo release From 7a04fdd0fac9d25225183624d1306c3072e877cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 17 Nov 2022 01:02:04 +0100 Subject: [PATCH 0135/1551] Refactor libXML error handling to remove global state (#12663) libXML error handlers are now set right before calling a lib function collecting errors directly in that context. Afterwards the error handler is reset. There is no longer a global array collecting errors at `XML::Error.errors`. Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Beta Ziliani --- spec/std/xml/reader_spec.cr | 20 ++++++++++++ spec/std/xml/xml_spec.cr | 9 ++---- src/xml.cr | 21 +++++++------ src/xml/error.cr | 62 +++++++++++++++++++++++-------------- src/xml/node.cr | 12 ++----- src/xml/reader.cr | 38 ++++++++++++++++++++--- src/xml/xpath_context.cr | 4 ++- 7 files changed, 110 insertions(+), 56 deletions(-) diff --git a/spec/std/xml/reader_spec.cr b/spec/std/xml/reader_spec.cr index d8c2946a201b..54183dd8742d 100644 --- a/spec/std/xml/reader_spec.cr +++ b/spec/std/xml/reader_spec.cr @@ -553,4 +553,24 @@ module XML end end end + + describe "#errors" do + it "makes errors accessible" do + reader = XML::Reader.new(%()) + reader.read + reader.expand? + + reader.errors.map(&.to_s).should eq ["Opening and ending tag mismatch: people line 1 and foo"] + end + + it "adds errors to `XML::Error.errors` (deprecated)" do + XML::Error.errors # clear class error list + + reader = XML::Reader.new(%()) + reader.read + reader.expand? + + XML::Error.errors.try(&.map(&.to_s)).should eq ["Opening and ending tag mismatch: people line 1 and foo"] + end + end end diff --git a/spec/std/xml/xml_spec.cr b/spec/std/xml/xml_spec.cr index cb5db774e32f..4c5e18c97249 100644 --- a/spec/std/xml/xml_spec.cr +++ b/spec/std/xml/xml_spec.cr @@ -158,15 +158,10 @@ describe XML do person2.previous_element.should eq(person) end - it "handles errors" do + it "#errors" do xml = XML.parse(%()) xml.root.not_nil!.name.should eq("people") - errors = xml.errors.not_nil! - errors.size.should eq(1) - - errors[0].message.should eq("Opening and ending tag mismatch: people line 1 and foo") - errors[0].line_number.should eq(1) - errors[0].to_s.should eq("Opening and ending tag mismatch: people line 1 and foo") + xml.errors.try(&.map(&.to_s)).should eq ["Opening and ending tag mismatch: people line 1 and foo"] end describe "#namespace" do diff --git a/src/xml.cr b/src/xml.cr index f07bb6b5bee0..9e1a19e4cc7c 100644 --- a/src/xml.cr +++ b/src/xml.cr @@ -52,14 +52,14 @@ module XML # See `ParserOptions.default` for default options. def self.parse(string : String, options : ParserOptions = ParserOptions.default) : Node raise XML::Error.new("Document is empty", 0) if string.empty? - from_ptr LibXML.xmlReadMemory(string, string.bytesize, nil, nil, options) + from_ptr { LibXML.xmlReadMemory(string, string.bytesize, nil, nil, options) } end # Parses an XML document from *io* with *options* into an `XML::Node`. # # See `ParserOptions.default` for default options. def self.parse(io : IO, options : ParserOptions = ParserOptions.default) : Node - from_ptr LibXML.xmlReadIO( + from_ptr { LibXML.xmlReadIO( ->(ctx, buffer, len) { LibC::Int.new(Box(IO).unbox(ctx).read Slice.new(buffer, len)) }, @@ -68,7 +68,7 @@ module XML nil, nil, options, - ) + ) } end # Parses an HTML document from *string* with *options* into an `XML::Node`. @@ -76,14 +76,14 @@ module XML # See `HTMLParserOptions.default` for default options. def self.parse_html(string : String, options : HTMLParserOptions = HTMLParserOptions.default) : Node raise XML::Error.new("Document is empty", 0) if string.empty? - from_ptr LibXML.htmlReadMemory(string, string.bytesize, nil, nil, options) + from_ptr { LibXML.htmlReadMemory(string, string.bytesize, nil, nil, options) } end # Parses an HTML document from *io* with *options* into an `XML::Node`. # # See `HTMLParserOptions.default` for default options. def self.parse_html(io : IO, options : HTMLParserOptions = HTMLParserOptions.default) : Node - from_ptr LibXML.htmlReadIO( + from_ptr { LibXML.htmlReadIO( ->(ctx, buffer, len) { LibC::Int.new(Box(IO).unbox(ctx).read Slice.new(buffer, len)) }, @@ -92,15 +92,16 @@ module XML nil, nil, options, - ) + ) } end - protected def self.from_ptr(doc : LibXML::Doc*) + protected def self.from_ptr(& : -> LibXML::Doc*) + errors = [] of XML::Error + doc = XML::Error.collect(errors) { yield } + raise Error.new(LibXML.xmlGetLastError) unless doc - node = Node.new(doc) - XML::Error.set_errors(node) - node + Node.new(doc, errors) end end diff --git a/src/xml/error.cr b/src/xml/error.cr index 23b0229858df..868dfeb4bd00 100644 --- a/src/xml/error.cr +++ b/src/xml/error.cr @@ -11,35 +11,14 @@ class XML::Error < Exception super(message) end - # TODO: this logic isn't thread/fiber safe, but error checking is less needed than - # the ability to parse HTML5 and malformed documents. In any case, fix this. @@errors = [] of self - LibXML.xmlSetStructuredErrorFunc nil, ->(ctx, error) { - @@errors << XML::Error.new(error) - } - - LibXML.xmlSetGenericErrorFunc nil, ->(ctx, fmt) { - # TODO: use va_start and va_end to - message = String.new(fmt).chomp - error = XML::Error.new(message, 0) - - {% if flag?(:arm) || flag?(:aarch64) %} - # libxml2 is likely missing ARM unwind tables (.ARM.extab and .ARM.exidx - # sections) which prevent raising from a libxml2 context. - @@errors << error - {% else %} - raise error - {% end %} - } - # :nodoc: - def self.set_errors(node) - if errors = self.errors - node.errors = errors - end + protected def self.add_errors(errors) + @@errors.concat(errors) end + @[Deprecated("This class accessor is deprecated. XML errors are accessible directly in the respective context via `XML::Reader#errors` and `XML::Node#errors`.")] def self.errors : Array(XML::Error)? if @@errors.empty? nil @@ -49,4 +28,39 @@ class XML::Error < Exception errors end end + + def self.collect(errors, &) + LibXML.xmlSetStructuredErrorFunc Box.box(errors), ->(ctx, error) { + Box(Array(XML::Error)).unbox(ctx) << XML::Error.new(error) + } + begin + yield + ensure + LibXML.xmlSetStructuredErrorFunc nil, nil + end + end + + def self.collect_generic(errors, &) + LibXML.xmlSetGenericErrorFunc Box.box(errors), ->(ctx, fmt) { + # TODO: use va_start and va_end to + message = String.new(fmt).chomp + error = XML::Error.new(message, 0) + + {% if flag?(:arm) || flag?(:aarch64) %} + # libxml2 is likely missing ARM unwind tables (.ARM.extab and .ARM.exidx + # sections) which prevent raising from a libxml2 context. + Box(Array(XML::Error)).unbox(ctx) << error + {% else %} + raise error + {% end %} + } + + begin + collect(errors) do + yield + end + ensure + LibXML.xmlSetGenericErrorFunc nil, nil + end + end end diff --git a/src/xml/node.cr b/src/xml/node.cr index cb5313f3e702..218897b5d025 100644 --- a/src/xml/node.cr +++ b/src/xml/node.cr @@ -7,7 +7,7 @@ class XML::Node end # :ditto: - def initialize(node : LibXML::Doc*) + def initialize(node : LibXML::Doc*, @errors = nil) initialize(node.as(LibXML::Node*)) end @@ -576,17 +576,9 @@ class XML::Node xpath(path, namespaces, variables).as(String) end - # :nodoc: - def errors=(errors) - @node.value._private = errors.as(Void*) - end - # Returns the list of `XML::Error` found when parsing this document. # Returns `nil` if no errors were found. - def errors : Array(XML::Error)? - ptr = @node.value._private - ptr ? (ptr.as(Array(XML::Error))) : nil - end + getter errors : Array(XML::Error)? private def check_no_null_byte(string) if string.includes? Char::ZERO diff --git a/src/xml/reader.cr b/src/xml/reader.cr index b6877f3c6030..684d3ed28871 100644 --- a/src/xml/reader.cr +++ b/src/xml/reader.cr @@ -1,7 +1,31 @@ require "./libxml2" require "./parser_options" +# `XML::Reader` is a parser for XML that iterates a XML document. +# +# ``` +# require "xml" +# +# reader = XML::Reader.new(<<-XML) +# Hello XML! +# XML +# reader.read +# reader.name # => "message" +# reader.read +# reader.value # => "Hello XML!" +# ``` +# +# This is an alternative approach to `XML.parse` which parses an entire document +# into an XML data structure. +# `XML::Reader` offers more control and does not need to store the XML document +# in memory entirely. The latter is especially useful for large documents with +# the `IO`-based constructor. +# +# WARNING: This type is not concurrency-safe. class XML::Reader + # Returns the errors reported while parsing. + getter errors = [] of XML::Error + # Creates a new reader from a string. # # See `XML::ParserOptions.default` for default options. @@ -30,7 +54,7 @@ class XML::Reader # Moves the reader to the next node. def read : Bool - LibXML.xmlTextReaderRead(@reader) == 1 + collect_errors { LibXML.xmlTextReaderRead(@reader) == 1 } end # Moves the reader to the next node while skipping subtrees. @@ -46,7 +70,7 @@ class XML::Reader if result == -1 node = LibXML.xmlTextReaderCurrentNode(@reader) if node.null? - LibXML.xmlTextReaderRead(@reader) == 1 + collect_errors { LibXML.xmlTextReaderRead(@reader) == 1 } elsif !node.value.next.null? LibXML.xmlTextReaderNext(@reader) == 1 else @@ -123,7 +147,7 @@ class XML::Reader # Returns the node's XML content including subtrees. def read_inner_xml : String - xml = LibXML.xmlTextReaderReadInnerXml(@reader) + xml = collect_errors { LibXML.xmlTextReaderReadInnerXml(@reader) } xml ? String.new(xml) : "" end @@ -139,7 +163,7 @@ class XML::Reader # to avoid doing an extra C call each time. return "" if node_type.none? - xml = LibXML.xmlTextReaderReadOuterXml(@reader) + xml = collect_errors { LibXML.xmlTextReaderReadOuterXml(@reader) } xml ? String.new(xml) : "" end @@ -170,4 +194,10 @@ class XML::Reader def to_unsafe @reader end + + private def collect_errors + Error.collect(@errors) { yield }.tap do + Error.add_errors(@errors) + end + end end diff --git a/src/xml/xpath_context.cr b/src/xml/xpath_context.cr index dc96887a39db..33ae9335c742 100644 --- a/src/xml/xpath_context.cr +++ b/src/xml/xpath_context.cr @@ -1,11 +1,13 @@ class XML::XPathContext + getter errors = [] of XML::Error + def initialize(node : Node) @ctx = LibXML.xmlXPathNewContext(node.to_unsafe.value.doc) @ctx.value.node = node.to_unsafe end def evaluate(search_path : String) - xpath = LibXML.xmlXPathEvalExpression(search_path, self) + xpath = XML::Error.collect_generic(@errors) { LibXML.xmlXPathEvalExpression(search_path, self) } unless xpath {% if flag?(:arm) || flag?(:aarch64) %} if errors = XML::Error.errors From d5aa1f771baa818774ac3fbb23b9d49437c626a2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 17 Nov 2022 12:26:53 +0100 Subject: [PATCH 0136/1551] Update GH Actions (#12742) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/macos.yml | 4 ++-- .github/workflows/win.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 1b607e96441b..2156939c142c 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -13,12 +13,12 @@ jobs: - name: Download Crystal source uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v17 + - uses: cachix/install-nix-action@v18 with: install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | experimental-features = nix-command - - uses: cachix/cachix-action@v10 + - uses: cachix/cachix-action@v12 with: name: crystal-ci signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 50b906678014..af96cde4114f 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -31,7 +31,7 @@ jobs: run: | git config --global core.autocrlf false - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@ed94116c4d30d2091601b81f339a2eaa1c2ba0a6 # v1.4.1 + uses: ilammy/msvc-dev-cmd@7315a94840631165970262a99c72cfb48a65d25d # v1.12.0 - name: Download Crystal source uses: actions/checkout@v3 @@ -249,7 +249,7 @@ jobs: key: win-openssl-libs-3.0.7-msvc-${{ env.VSCMD_VER }} - name: Set up NASM if: steps.cache-openssl.outputs.cache-hit != 'true' - uses: ilammy/setup-nasm@e2335e5fc95548c09cd2deea2768793e0e8f0941 # v1.2.1 + uses: ilammy/setup-nasm@321e6ed62a1fc77024a3bd853deb33645e8b22c4 # v1.4.0 - name: Download OpenSSL if: steps.cache-openssl.outputs.cache-hit != 'true' run: | From 70673cf1ac32f2858a500fa1382744894b750b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 17 Nov 2022 12:27:11 +0100 Subject: [PATCH 0137/1551] [CI] Run specs in random order by default (#12541) --- .github/workflows/macos.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- Makefile | 3 ++- Makefile.win | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 2156939c142c..357eac5f5821 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -33,4 +33,4 @@ jobs: run: bin/ci build - name: Test interpreter - run: bin/ci with_build_env 'bin/crystal spec spec/compiler/interpreter_spec.cr' + run: bin/ci with_build_env 'bin/crystal spec --order=random spec/compiler/interpreter_spec.cr' diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 7ab7d4bfe351..1f45def014a9 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -19,7 +19,7 @@ jobs: - name: Check LibSSL version run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs - run: bin/crystal spec spec/std/openssl/ + run: bin/crystal spec --order=random spec/std/openssl/ openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" @@ -32,7 +32,7 @@ jobs: - name: Check LibSSL version run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs - run: bin/crystal spec spec/std/openssl/ + run: bin/crystal spec --order=random spec/std/openssl/ libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" @@ -52,4 +52,4 @@ jobs: - name: Check LibSSL version run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs - run: bin/crystal spec spec/std/openssl/ + run: bin/crystal spec --order=random spec/std/openssl/ diff --git a/Makefile b/Makefile index cbd4e42445cf..b2dac554aa95 100644 --- a/Makefile +++ b/Makefile @@ -26,13 +26,14 @@ junit_output ?= ## Path to output junit results static ?= ## Enable static linking interpreter ?= ## Enable interpreter feature check ?= ## Enable only check when running format +order ?=random ## Enable order for spec execution (values: "default" | "random" | seed number) O := .build SOURCES := $(shell find src -name '*.cr') SPEC_SOURCES := $(shell find spec -name '*.cr') override FLAGS += -D strict_multi_assign -D preview_overload_order $(if $(release),--release )$(if $(stats),--stats )$(if $(progress),--progress )$(if $(threads),--threads $(threads) )$(if $(debug),-d )$(if $(static),--static )$(if $(LDFLAGS),--link-flags="$(LDFLAGS)" )$(if $(target),--cross-compile --target $(target) )$(if $(interpreter),,-Dwithout_interpreter ) SPEC_WARNINGS_OFF := --exclude-warnings spec/std --exclude-warnings spec/compiler --exclude-warnings spec/primitives -SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) ) +SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) )$(if $(order),--order=$(order) ) CRYSTAL_CONFIG_LIBRARY_PATH := '$$ORIGIN/../lib/crystal' CRYSTAL_CONFIG_BUILD_COMMIT := $(shell git rev-parse --short HEAD 2> /dev/null) CRYSTAL_CONFIG_PATH := '$$ORIGIN/../share/crystal/src' diff --git a/Makefile.win b/Makefile.win index b05c3056eb8a..17e35ecaa6d7 100644 --- a/Makefile.win +++ b/Makefile.win @@ -26,6 +26,7 @@ junit_output ?= ## Path to output junit results static ?= ## Enable static linking interpreter ?= ## Enable interpreter feature check ?= ## Enable only check when running format +order ?= ## Enable order for spec execution (values: "default" | "random" | seed number) MAKEFLAGS += --no-builtin-rules .SUFFIXES: @@ -49,7 +50,7 @@ SOURCES := $(call GLOB,src\\*.cr) SPEC_SOURCES := $(call GLOB,spec\\*.cr) override FLAGS += -D strict_multi_assign -D preview_overload_order $(if $(release),--release )$(if $(stats),--stats )$(if $(progress),--progress )$(if $(threads),--threads $(threads) )$(if $(debug),-d )$(if $(static),--static )$(if $(LDFLAGS),--link-flags="$(LDFLAGS)" )$(if $(target),--cross-compile --target $(target) )$(if $(interpreter),,-Dwithout_interpreter ) SPEC_WARNINGS_OFF := --exclude-warnings spec\std --exclude-warnings spec\compiler --exclude-warnings spec\primitives -SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) ) +SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) )$(if $(order),--order=$(order) ) CRYSTAL_CONFIG_LIBRARY_PATH := $$ORIGIN\lib CRYSTAL_CONFIG_BUILD_COMMIT := $(shell git rev-parse --short HEAD) CRYSTAL_CONFIG_PATH := $$ORIGIN\src From 3b2e25b5b8d86571558f616641540a26bccca6e5 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 18 Nov 2022 04:32:35 +0800 Subject: [PATCH 0138/1551] Update `shell.nix` for newer LLVM versions and aarch64-darwin (#12591) --- shell.nix | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/shell.nix b/shell.nix index 32d5621cdb88..27a9a347f54d 100644 --- a/shell.nix +++ b/shell.nix @@ -56,6 +56,11 @@ let sha256 = "sha256:0pnakhi4hc50fw6dz0n110zpibgwjb91mf6n63fhys8hby7fg73p"; }; + aarch64-darwin = { + url = "https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:0pnakhi4hc50fw6dz0n110zpibgwjb91mf6n63fhys8hby7fg73p"; + }; + x86_64-linux = { url = "https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1-linux-x86_64.tar.gz"; sha256 = "sha256:00ckw3nlr800i25mwv4qwz1lv42qq1kp3xsaxbd3l40ck5gml7c3"; @@ -65,6 +70,18 @@ let pkgconfig = pkgs.pkgconfig; llvm_suite = ({ + llvm_14 = { + llvm = pkgs.llvm_14; + extra = [ pkgs.lld_14 ]; # lldb marked as broken + }; + llvm_13 = { + llvm = pkgs.llvm_13; + extra = [ pkgs.lld_13 ]; # lldb marked as broken + }; + llvm_12 = { + llvm = pkgs.llvm_12; + extra = [ pkgs.lld_12 pkgs.lldb_12 ]; + }; llvm_11 = { llvm = pkgs.llvm_11; extra = [ pkgs.lld_11 pkgs.lldb_11 ]; @@ -75,19 +92,19 @@ let }; llvm_9 = { llvm = pkgs.llvm_9; - extra = [ ]; # lldb it fails to compile on Darwin + extra = [ pkgs.lld_9 ]; # lldb marked as broken }; llvm_8 = { llvm = pkgs.llvm_8; - extra = [ ]; # lldb it fails to compile on Darwin + extra = [ pkgs.lld_8 ]; # lldb marked as broken }; llvm_7 = { - llvm = pkgs.llvm; - extra = [ pkgs.lldb ]; + llvm = pkgs.llvm_7; + extra = [ pkgs.lld_7 ]; # lldb it fails to compile on Darwin }; llvm_6 = { llvm = pkgs.llvm_6; - extra = [ ]; # lldb it fails to compile on Darwin + extra = [ pkgs.lld_6 ]; # lldb it fails to compile on Darwin }; }."llvm_${toString llvm}"); From 1cb02e35125d72d8909793d78b8f7aee4bcda298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 18 Nov 2022 14:32:41 +0100 Subject: [PATCH 0139/1551] Update previous Crystal release - 1.6.2 (#12750) --- .circleci/config.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/win.yml | 2 +- bin/ci | 6 +++--- shell.nix | 8 ++++---- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 547fc8592e8f..4ade2df927d8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1" defaults: environment: &env diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e9dc5f9fc38f..ba107bad0e12 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.1, 1.6.1] + crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.1, 1.6.2] include: # libffi is only available starting from the 1.2.2 build images - crystal_bootstrap_version: 1.0.0 diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 1f45def014a9..d59e3b968d7b 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -6,7 +6,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.6.1-alpine + container: crystallang/crystal:1.6.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -23,7 +23,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.6.1-alpine + container: crystallang/crystal:1.6.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -36,7 +36,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.6.1-alpine + container: crystallang/crystal:1.6.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index af96cde4114f..357bc76dacc7 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: x86_64-linux-job: runs-on: ubuntu-latest - container: crystallang/crystal:1.6.1-build + container: crystallang/crystal:1.6.2-build steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/bin/ci b/bin/ci index f59ef755cd10..84a8b8c8307f 100755 --- a/bin/ci +++ b/bin/ci @@ -134,8 +134,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.6.1-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.6.2-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -188,7 +188,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.6.1}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.6.2}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 27a9a347f54d..6adf9f98346e 100644 --- a/shell.nix +++ b/shell.nix @@ -52,8 +52,8 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:0pnakhi4hc50fw6dz0n110zpibgwjb91mf6n63fhys8hby7fg73p"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:1f3946iq8df2sfr8nk7ydxwld1prvpql03fsn7cj31cjj09hsjzp"; }; aarch64-darwin = { @@ -62,8 +62,8 @@ let }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1-linux-x86_64.tar.gz"; - sha256 = "sha256:00ckw3nlr800i25mwv4qwz1lv42qq1kp3xsaxbd3l40ck5gml7c3"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-linux-x86_64.tar.gz"; + sha256 = "sha256:0f3nrd59b9yajvrhh24i9kbs6nmgc7dpw134jj3iyqh9wz83pyqx"; }; }.${pkgs.stdenv.system}); From dbed0d8d39a55e23f973635e40306e8b814af5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 19 Nov 2022 13:24:34 +0100 Subject: [PATCH 0140/1551] Ensure `HTTP::Client` closes response when breaking out of block (#12749) --- spec/std/http/client/client_spec.cr | 20 ++++++++++++++++++++ src/http/client.cr | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index f49b98afc3df..10bc63f2e7af 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -180,6 +180,26 @@ module HTTP end end + it "ensures closing the response when breaking out of block" do + server = HTTP::Server.new { } + address = server.bind_unused_port "127.0.0.1" + + run_server(server) do + client = HTTP::Client.new(address.address, address.port) + response = nil + + exc = Exception.new("") + expect_raises Exception do + client.get("/") do |r| + response = r + raise exc + end + end.should be exc + + response.try(&.body_io?.try(&.closed?)).should be_true + end + end + pending_win32 "will retry a broken socket" do server = HTTP::Server.new do |context| context.response.output.print "foo" diff --git a/src/http/client.cr b/src/http/client.cr index a9acff3027f7..80f3506b60a3 100644 --- a/src/http/client.cr +++ b/src/http/client.cr @@ -664,10 +664,10 @@ class HTTP::Client end private def handle_response(response) - value = yield + yield + ensure response.body_io?.try &.close close unless response.keep_alive? - value end private def send_request(request) From 6eb21ea956da205cb84b74e4671acf5d1a27ad9a Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Sat, 19 Nov 2022 20:25:22 +0800 Subject: [PATCH 0141/1551] Support `@[Deprecated]` on `annotation` (#12557) Co-authored-by: George Dietrich --- spec/compiler/semantic/warnings_spec.cr | 30 +++++++++++++++++++ src/compiler/crystal/semantic/ast.cr | 6 ++++ .../crystal/semantic/cleanup_transformer.cr | 6 ++++ .../crystal/semantic/top_level_visitor.cr | 9 +++++- src/compiler/crystal/semantic/warnings.cr | 13 ++++++++ 5 files changed, 63 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/warnings_spec.cr b/spec/compiler/semantic/warnings_spec.cr index 9e34e0ad286e..26b509d14aad 100644 --- a/spec/compiler/semantic/warnings_spec.cr +++ b/spec/compiler/semantic/warnings_spec.cr @@ -1,6 +1,36 @@ require "../spec_helper" describe "Semantic: warnings" do + describe "deprecated annotations" do + it "detects deprecated annotations" do + assert_warning <<-CR, + @[Deprecated] + annotation Foo; end + + @[Foo] + def bar; end + + bar + CR + "warning in line 2\nWarning: Deprecated annotation Foo." + end + + it "detects deprecated namespaced annotations" do + assert_warning <<-CR, + module MyNamespace + @[Deprecated] + annotation Foo; end + end + + @[MyNamespace::Foo] + def bar; end + + bar + CR + "warning in line 3\nWarning: Deprecated annotation MyNamespace::Foo." + end + end + describe "deprecated methods" do it "detects top-level deprecated methods" do assert_warning <<-CR, diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index b7e3e3936ea9..79e02d1ccf00 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -679,6 +679,12 @@ module Crystal property! resolved_type : AliasType end + class AnnotationDef + include Annotatable + + property! resolved_type : AnnotationType + end + class External < Def property real_name : String property! fun_def : FunDef diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 2169c38475ba..dd3c4e03bc67 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -126,6 +126,12 @@ module Crystal {@last_is_truthy, @last_is_falsey} end + def transform(node : AnnotationDef) + @program.check_call_to_deprecated_annotation node + + node + end + def transform(node : Def) node.hook_expansions.try &.map! &.transform self node diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index e0793cae61e1..a3e4fbdd4a2c 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -275,6 +275,11 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def visit(node : AnnotationDef) check_outside_exp node, "declare annotation" + annotations = read_annotations + process_annotations(annotations) do |annotation_type, ann| + node.add_annotation(annotation_type, ann) + end + scope, name, type = lookup_type_def(node) if type @@ -286,7 +291,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor scope.types[name] = type end - attach_doc type, node, annotations: nil + node.resolved_type = type + + attach_doc type, node, annotations false end diff --git a/src/compiler/crystal/semantic/warnings.cr b/src/compiler/crystal/semantic/warnings.cr index 8e1a74f11721..437632bc9953 100644 --- a/src/compiler/crystal/semantic/warnings.cr +++ b/src/compiler/crystal/semantic/warnings.cr @@ -8,6 +8,7 @@ module Crystal @deprecated_constants_detected = Set(String).new @deprecated_methods_detected = Set(String).new @deprecated_macros_detected = Set(String).new + @deprecated_annotations_detected = Set(String).new def check_deprecated_constant(const : Const, node : Path) return unless @warnings.level.all? @@ -30,6 +31,12 @@ module Crystal end end + def check_call_to_deprecated_annotation(node : AnnotationDef) : Nil + return unless @warnings.level.all? + + check_deprecation(node, node.name, @deprecated_annotations_detected) + end + private def check_deprecation(object, use_site, detects) if (ann = object.annotation(self.deprecated_annotation)) && (deprecated_annotation = DeprecatedAnnotation.from(ann)) @@ -59,6 +66,12 @@ module Crystal end end + class AnnotationDef + def short_reference + "annotation #{resolved_type}" + end + end + class Macro def short_reference case owner From 949a8a981bb57194c191c5f80bd146bb391a3a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 19 Nov 2022 20:47:51 +0100 Subject: [PATCH 0142/1551] Implement `flock_*` fiber-aware, without blocking the thread (#12728) --- spec/std/file_spec.cr | 38 +++++++++++++++++++++++++++++++-- src/crystal/system/unix/file.cr | 25 +++++++++++++++++----- src/io/file_descriptor.cr | 1 - 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 746ce26fbff3..a393049f596a 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -939,10 +939,10 @@ describe "File" do File.open(datapath("test_file.txt")) do |file1| File.open(datapath("test_file.txt")) do |file2| file1.flock_exclusive do - # BUG: check for EWOULDBLOCK when exception filters are implemented - expect_raises(IO::Error, "Error applying or removing file lock") do + exc = expect_raises(IO::Error, "Error applying file lock: file is already locked") do file2.flock_exclusive(blocking: false) { } end + exc.os_error.should eq Errno::EWOULDBLOCK end end end @@ -957,6 +957,40 @@ describe "File" do end end end + + pending_win32 "#flock_shared soft blocking fiber" do + File.open(datapath("test_file.txt")) do |file1| + File.open(datapath("test_file.txt")) do |file2| + done = Channel(Nil).new + file1.flock_exclusive + + spawn do + file1.flock_unlock + done.send nil + end + + file2.flock_shared + done.receive + end + end + end + + pending_win32 "#flock_exclusive soft blocking fiber" do + File.open(datapath("test_file.txt")) do |file1| + File.open(datapath("test_file.txt")) do |file2| + done = Channel(Nil).new + file1.flock_exclusive + + spawn do + file1.flock_unlock + done.send nil + end + + file2.flock_exclusive + done.receive + end + end + end end it "reads at offset" do diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index 711f1a4bfcf3..36b54ea305a0 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -252,14 +252,29 @@ module Crystal::System::File flock LibC::FlockOp::UN end - private def flock(op : LibC::FlockOp, blocking : Bool = true) - op |= LibC::FlockOp::NB unless blocking + private def flock(op : LibC::FlockOp, retry : Bool) : Nil + op |= LibC::FlockOp::NB - if LibC.flock(fd, op) != 0 - raise IO::Error.from_errno("Error applying or removing file lock") + if retry + until flock(op) + ::Fiber.yield + end + else + flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked") end + end - nil + private def flock(op) : Bool + if 0 == LibC.flock(fd, op) + true + else + errno = Errno.value + if errno.in?(Errno::EAGAIN, Errno::EWOULDBLOCK) + false + else + raise IO::Error.from_os_error("Error applying or removing file lock", errno) + end + end end private def system_fsync(flush_metadata = true) : Nil diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 6da5b859f77b..ecbcc6cebf88 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -175,7 +175,6 @@ class IO::FileDescriptor < IO end # TODO: use fcntl/lockf instead of flock (which doesn't lock over NFS) - # TODO: always use non-blocking locks, yield fiber until resource becomes available def flock_shared(blocking = true) flock_shared blocking From 1f9e24f9ede8aa120fec3b7676b6866dfd5373fc Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sat, 19 Nov 2022 21:09:54 +0100 Subject: [PATCH 0143/1551] Style: Use short block notation for simple one-liners (#12676) --- samples/sdl/fire.cr | 4 ++-- samples/sdl/tv.cr | 4 ++-- samples/sudoku.cr | 2 +- spec/compiler/crystal/tools/context_spec.cr | 2 +- spec/std/enumerable_spec.cr | 16 ++++++++-------- spec/std/hash_spec.cr | 8 ++++---- spec/std/http/web_socket_spec.cr | 4 ++-- spec/std/json/serialization_spec.cr | 2 +- spec/std/mime/multipart/parser_spec.cr | 2 +- src/benchmark/ips.cr | 2 +- src/compiler/crystal/codegen/cast.cr | 2 +- src/compiler/crystal/command.cr | 2 +- src/compiler/crystal/syntax/transformer.cr | 2 +- src/compiler/crystal/types.cr | 2 +- src/crystal/system/win32/event_loop_iocp.cr | 2 +- src/http/cookie.cr | 6 +++--- src/io/multi_writer.cr | 4 ++-- src/mime/multipart/builder.cr | 6 +++--- src/option_parser.cr | 2 +- src/string.cr | 6 +++--- 20 files changed, 40 insertions(+), 40 deletions(-) diff --git a/samples/sdl/fire.cr b/samples/sdl/fire.cr index 8f92564af5b0..c3af69631398 100644 --- a/samples/sdl/fire.cr +++ b/samples/sdl/fire.cr @@ -257,7 +257,7 @@ def parse_rectangles(filename) rects = [] of Rectangle lines = File.read(filename) - lines = lines.split('\n').map { |line| line.rstrip } + lines = lines.split('\n').map(&.rstrip) lines.each_with_index do |line, y| x = 0 line.each_char do |c| @@ -294,7 +294,7 @@ class Screen background_intensity = intensity(@background[offset]) color_intensity = intensity(color) - if color_intensity >= background_intensity && @rects.any? { |rect| rect.contains?(point) } + if color_intensity >= background_intensity && @rects.any?(&.contains?(point)) @background[offset] = color end end diff --git a/samples/sdl/tv.cr b/samples/sdl/tv.cr index ca01ab902fce..734be1c8b89b 100644 --- a/samples/sdl/tv.cr +++ b/samples/sdl/tv.cr @@ -98,7 +98,7 @@ end def parse_rectangles rects = [] of Rectangle - lines = File.read("#{__DIR__}/tv.txt").split('\n').map { |line| line.rstrip } + lines = File.read("#{__DIR__}/tv.txt").split('\n').map(&.rstrip) lines.each_with_index do |line, y| x = 0 line.each_char do |c| @@ -146,7 +146,7 @@ begin (height // 10).times do |h| (width // 10).times do |w| - rect = rects.find { |rect| rect.contains?(w, h) } + rect = rects.find(&.contains?(w, h)) 10.times do |y| 10.times do |x| surface[x + 10 * w, y + 10 * h] = rect ? (rect.light? ? color_maker.light_color : color_maker.dark_color) : color_maker.black_color diff --git a/samples/sudoku.cr b/samples/sudoku.cr index 96c28fd9b94c..992796e905f2 100644 --- a/samples/sudoku.cr +++ b/samples/sudoku.cr @@ -151,7 +151,7 @@ def solve_all(sudoku) sudoku.split('\n').map do |line| if line.size >= 81 ret = sd_solve(mr, mc, line) - ret.map { |s2| s2.join } + ret.map(&.join) end end.compact end diff --git a/spec/compiler/crystal/tools/context_spec.cr b/spec/compiler/crystal/tools/context_spec.cr index 0724c378b58a..c3f839033ac9 100644 --- a/spec/compiler/crystal/tools/context_spec.cr +++ b/spec/compiler/crystal/tools/context_spec.cr @@ -43,7 +43,7 @@ end private def assert_context_includes(code, variable, var_types) run_context_tool(code) do |result| result.contexts.should_not be_nil - result.contexts.not_nil!.map { |h| h[variable].to_s }.should eq(var_types) + result.contexts.not_nil!.map(&.[variable].to_s).should eq(var_types) end end diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 2bcc7d92ccb3..a7bac45abd48 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -523,8 +523,8 @@ describe "Enumerable" do it { [1, 2, 2, 3].group_by { |x| x == 2 }.should eq({true => [2, 2], false => [1, 3]}) } it "groups can group by size (like the doc example)" do - %w(Alice Bob Ary).group_by { |e| e.size }.should eq({3 => ["Bob", "Ary"], - 5 => ["Alice"]}) + %w(Alice Bob Ary).group_by(&.size).should eq({3 => ["Bob", "Ary"], + 5 => ["Alice"]}) end end @@ -625,12 +625,12 @@ describe "Enumerable" do describe "index_by" do it "creates a hash indexed by the value returned by the block" do - hash = ["Anna", "Ary", "Alice"].index_by { |e| e.size } + hash = ["Anna", "Ary", "Alice"].index_by(&.size) hash.should eq({4 => "Anna", 3 => "Ary", 5 => "Alice"}) end it "overrides values if a value is returned twice" do - hash = ["Anna", "Ary", "Alice", "Bob"].index_by { |e| e.size } + hash = ["Anna", "Ary", "Alice", "Bob"].index_by(&.size) hash.should eq({4 => "Anna", 3 => "Bob", 5 => "Alice"}) end end @@ -1242,7 +1242,7 @@ describe "Enumerable" do it "returns a hash with counts according to the value" do hash = {} of Char => Int32 words = ["crystal", "ruby"] - words.each { |word| word.chars.tally(hash) } + words.each(&.chars.tally(hash)) hash.should eq( {'c' => 1, 'r' => 2, 'y' => 2, 's' => 1, 't' => 1, 'a' => 1, 'l' => 1, 'u' => 1, 'b' => 1} @@ -1252,7 +1252,7 @@ describe "Enumerable" do it "updates existing hash with counts according to the value" do hash = {'a' => 1, 'b' => 1, 'c' => 1, 'd' => 1} words = ["crystal", "ruby"] - words.each { |word| word.chars.tally(hash) } + words.each(&.chars.tally(hash)) hash.should eq( {'a' => 2, 'b' => 2, 'c' => 2, 'd' => 1, 'r' => 2, 'y' => 2, 's' => 1, 't' => 1, 'l' => 1, 'u' => 1} @@ -1262,7 +1262,7 @@ describe "Enumerable" do it "ignores the default value" do hash = Hash(Char, Int32).new(100) words = ["crystal", "ruby"] - words.each { |word| word.chars.tally(hash) } + words.each(&.chars.tally(hash)) hash.should eq( {'c' => 1, 'r' => 2, 'y' => 2, 's' => 1, 't' => 1, 'a' => 1, 'l' => 1, 'u' => 1, 'b' => 1} @@ -1272,7 +1272,7 @@ describe "Enumerable" do it "returns a hash with Int64 counts according to the value" do hash = {} of Char => Int64 words = ["crystal", "ruby"] - words.each { |word| word.chars.tally(hash) } + words.each(&.chars.tally(hash)) hash.should eq( {'c' => 1, 'r' => 2, 'y' => 2, 's' => 1, 't' => 1, 'a' => 1, 'l' => 1, 'u' => 1, 'b' => 1} diff --git a/spec/std/hash_spec.cr b/spec/std/hash_spec.cr index 2954446f69ce..8bb8eca096bc 100644 --- a/spec/std/hash_spec.cr +++ b/spec/std/hash_spec.cr @@ -404,12 +404,12 @@ describe "Hash" do describe "if block is given," do it "returns the first key with the given value" do hash = {"foo" => "bar", "baz" => "bar"} - hash.key_for("bar") { |value| value.upcase }.should eq("foo") + hash.key_for("bar", &.upcase).should eq("foo") end it "yields the argument if no hash key pairs with the value" do hash = {"foo" => "bar"} - hash.key_for("qux") { |value| value.upcase }.should eq("QUX") + hash.key_for("qux", &.upcase).should eq("QUX") end end end @@ -842,7 +842,7 @@ describe "Hash" do it "transforms keys with type casting" do h1 = {"a" => 1, "b" => 2, "c" => 3} - h2 = h1.transform_keys { |x| x.to_s.upcase } + h2 = h1.transform_keys(&.to_s.upcase) h2.should be_a(Hash(String, Int32)) h2.should eq({"A" => 1, "B" => 2, "C" => 3}) end @@ -865,7 +865,7 @@ describe "Hash" do it "transforms values with type casting values" do h1 = {"a" => 1, "b" => 2, "c" => 3} - h2 = h1.transform_values { |x| x.to_s } + h2 = h1.transform_values(&.to_s) h2.should be_a(Hash(String, String)) h2.should eq({"a" => "1", "b" => "2", "c" => "3"}) end diff --git a/spec/std/http/web_socket_spec.cr b/spec/std/http/web_socket_spec.cr index 024ab06e150f..3610e524d56e 100644 --- a/spec/std/http/web_socket_spec.cr +++ b/spec/std/http/web_socket_spec.cr @@ -588,7 +588,7 @@ describe "Websocket integration tests" do it "streams less than the buffer frame size" do integration_setup do |wsoc, bin_ch, _| bytes = "hello test world".to_slice - wsoc.stream(frame_size: 1024) { |io| io.write bytes } + wsoc.stream(frame_size: 1024, &.write(bytes)) received = bin_ch.receive received.should eq bytes end @@ -598,7 +598,7 @@ describe "Websocket integration tests" do integration_setup do |wsoc, bin_ch, _| bytes = ("hello test world" * 80).to_slice bytes.size.should be > 1024 - wsoc.stream(frame_size: 1024) { |io| io.write bytes } + wsoc.stream(frame_size: 1024, &.write(bytes)) received = bin_ch.receive received.should eq bytes end diff --git a/spec/std/json/serialization_spec.cr b/spec/std/json/serialization_spec.cr index dcb46348c05a..be0a5efe1713 100644 --- a/spec/std/json/serialization_spec.cr +++ b/spec/std/json/serialization_spec.cr @@ -443,7 +443,7 @@ describe "JSON serialization" do it "deserializes unions of the same kind and remains stable" do str = [Int32::MAX, Int64::MAX].to_json value = Array(Int32 | Int64).from_json(str) - value.all? { |x| x.should be_a(Int64) } + value.all?(&.should(be_a(Int64))) end it "deserializes Time" do diff --git a/spec/std/mime/multipart/parser_spec.cr b/spec/std/mime/multipart/parser_spec.cr index e29781f6b79a..052ce61ea0e6 100644 --- a/spec/std/mime/multipart/parser_spec.cr +++ b/spec/std/mime/multipart/parser_spec.cr @@ -145,6 +145,6 @@ describe MIME::Multipart::Parser do end parser.@state.finished?.should be_true - ios.each { |io| io.closed?.should eq(true) } + ios.each(&.closed?.should(eq(true))) end end diff --git a/src/benchmark/ips.cr b/src/benchmark/ips.cr index 47229a1536a1..85d1df46723e 100644 --- a/src/benchmark/ips.cr +++ b/src/benchmark/ips.cr @@ -115,7 +115,7 @@ module Benchmark end private def run_comparison - fastest = ran_items.max_by { |i| i.mean } + fastest = ran_items.max_by(&.mean) ran_items.each do |item| item.slower = (fastest.mean / item.mean).to_f end diff --git a/src/compiler/crystal/codegen/cast.cr b/src/compiler/crystal/codegen/cast.cr index a409f5bc1b02..e069e8f6559d 100644 --- a/src/compiler/crystal/codegen/cast.cr +++ b/src/compiler/crystal/codegen/cast.cr @@ -380,7 +380,7 @@ class Crystal::CodeGenVisitor Phi.open(self, to_type, @needs_value) do |phi| types_needing_cast.each_with_index do |type_needing_cast, i| # Find compatible type - compatible_type = to_type.union_types.find! { |ut| ut.implements?(type_needing_cast) } + compatible_type = to_type.union_types.find!(&.implements?(type_needing_cast)) matches_label, doesnt_match_label = new_blocks "matches", "doesnt_match_label" cmp_result = equal?(from_type_id, type_id(type_needing_cast)) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 29bcbb471e42..7f9ea58148be 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -353,7 +353,7 @@ class Crystal::Command unless no_codegen valid_emit_values = Compiler::EmitTarget.names - valid_emit_values.map! { |v| v.gsub('_', '-').downcase } + valid_emit_values.map!(&.gsub('_', '-').downcase) opts.on("--emit [#{valid_emit_values.join('|')}]", "Comma separated list of types of output for the compiler to emit") do |emit_values| compiler.emit_targets |= validate_emit_values(emit_values.split(',').map(&.strip)) diff --git a/src/compiler/crystal/syntax/transformer.cr b/src/compiler/crystal/syntax/transformer.cr index 40427a491459..299e1c53c6ad 100644 --- a/src/compiler/crystal/syntax/transformer.cr +++ b/src/compiler/crystal/syntax/transformer.cr @@ -598,7 +598,7 @@ module Crystal end def transform_many(exps) - exps.map! { |exp| exp.transform(self) } if exps + exps.map!(&.transform(self)) if exps end end end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index f27447775da7..d60c5fbff879 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -339,7 +339,7 @@ module Crystal # Returns true if `self` and *other* are in the same namespace. def same_namespace?(other) top_namespace(self) == top_namespace(other) || - !!parents.try &.any? { |parent| parent.same_namespace?(other) } + !!parents.try &.any?(&.same_namespace?(other)) end private def top_namespace(type) diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 30fc385de759..353c50a35974 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -42,7 +42,7 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop # Runs the event loop. def run_once : Nil - next_event = @queue.min_by { |e| e.wake_at } + next_event = @queue.min_by(&.wake_at) if next_event now = Time.monotonic diff --git a/src/http/cookie.cr b/src/http/cookie.cr index 9dc5517fa05d..23f853a5a47c 100644 --- a/src/http/cookie.cr +++ b/src/http/cookie.cr @@ -232,7 +232,7 @@ module HTTP # See `HTTP::Request#cookies` and `HTTP::Client::Response#cookies`. @[Deprecated("Use `.from_client_headers` or `.from_server_headers` instead.")] def self.from_headers(headers) : self - new.tap { |cookies| cookies.fill_from_headers(headers) } + new.tap(&.fill_from_headers(headers)) end # Filling cookies by parsing the `Cookie` and `Set-Cookie` @@ -248,7 +248,7 @@ module HTTP # # See `HTTP::Client::Response#cookies`. def self.from_client_headers(headers) : self - new.tap { |cookies| cookies.fill_from_client_headers(headers) } + new.tap(&.fill_from_client_headers(headers)) end # Filling cookies by parsing the `Cookie` headers in the given `HTTP::Headers`. @@ -265,7 +265,7 @@ module HTTP # # See `HTTP::Request#cookies`. def self.from_server_headers(headers) : self - new.tap { |cookies| cookies.fill_from_server_headers(headers) } + new.tap(&.fill_from_server_headers(headers)) end # Filling cookies by parsing the `Set-Cookie` headers in the given `HTTP::Headers`. diff --git a/src/io/multi_writer.cr b/src/io/multi_writer.cr index 2b03b7f90ce8..84bf5df5210f 100644 --- a/src/io/multi_writer.cr +++ b/src/io/multi_writer.cr @@ -34,7 +34,7 @@ class IO::MultiWriter < IO return if slice.empty? - @writers.each { |writer| writer.write(slice) } + @writers.each(&.write(slice)) end def read(slice : Bytes) : NoReturn @@ -45,7 +45,7 @@ class IO::MultiWriter < IO return if @closed @closed = true - @writers.each { |writer| writer.close } if sync_close? + @writers.each(&.close) if sync_close? end def flush : Nil diff --git a/src/mime/multipart/builder.cr b/src/mime/multipart/builder.cr index 6a7a0c07465a..3f3f4d3aeeae 100644 --- a/src/mime/multipart/builder.cr +++ b/src/mime/multipart/builder.cr @@ -50,7 +50,7 @@ module MIME::Multipart # # Can be called multiple times to append to the preamble multiple times. def preamble(data : Bytes) : Nil - preamble { |io| io.write data } + preamble(&.write(data)) end # Appends *preamble_io* to the preamble segment of the multipart message. @@ -82,7 +82,7 @@ module MIME::Multipart # and *data*. Throws if `#finish` or `#epilogue` is called before this # method. def body_part(headers : HTTP::Headers, data : Bytes) : Nil - body_part_impl(headers) { |io| io.write data } + body_part_impl(headers, &.write(data)) end # Appends a body part to the multipart message with the given *headers* @@ -141,7 +141,7 @@ module MIME::Multipart # # Can be called multiple times to append to the epilogue multiple times. def epilogue(data : Bytes) : Nil - epilogue { |io| io.write data } + epilogue(&.write(data)) end # Appends *preamble_io* to the epilogue segment of the multipart message. diff --git a/src/option_parser.cr b/src/option_parser.cr index 30312a55ac2e..f82a8a42d607 100644 --- a/src/option_parser.cr +++ b/src/option_parser.cr @@ -439,7 +439,7 @@ class OptionParser # subcommands since they are no longer valid. unless flag.starts_with?('-') @handlers.select! { |k, _| k.starts_with?('-') } - @flags.select! { |flag| flag.starts_with?(" -") } + @flags.select!(&.starts_with?(" -")) end handler.block.call(value || "") diff --git a/src/string.cr b/src/string.cr index 7ea2e2348a55..f5f9fd0e2abe 100644 --- a/src/string.cr +++ b/src/string.cr @@ -2832,7 +2832,7 @@ class String # described at `Char#in_set?`. Returns the number of characters # in this string that match the given set. def count(*sets) : Int32 - count { |char| char.in_set?(*sets) } + count(&.in_set?(*sets)) end # Yields each char in this string to the block. @@ -2867,7 +2867,7 @@ class String # "aabbccdd".delete("a-c") # => "dd" # ``` def delete(*sets) : String - delete { |char| char.in_set?(*sets) } + delete(&.in_set?(*sets)) end # Yields each char in this string to the block. @@ -2910,7 +2910,7 @@ class String # "a bbb".squeeze # => "a b" # ``` def squeeze(*sets : String) : String - squeeze { |char| char.in_set?(*sets) } + squeeze(&.in_set?(*sets)) end # Returns a new `String`, that has all characters removed, From 762cdd66d1bee03b701aafb62fda6365bfe653ac Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sun, 20 Nov 2022 14:02:03 +0100 Subject: [PATCH 0144/1551] Add `String#index/rindex!` methods (#12730) --- spec/std/string_spec.cr | 136 ++++++++++++++++++++++++++++++++++++++++ src/string.cr | 24 +++++++ 2 files changed, 160 insertions(+) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 2fb8abf15ee2..a8d047b77977 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -967,6 +967,78 @@ describe "String" do end end + describe "#index!" do + describe "by char" do + it { "foo".index!('o').should eq(1) } + it do + expect_raises(Enumerable::NotFoundError) do + "foo".index!('g') + end + end + + describe "with offset" do + it { "foobarbaz".index!('a', 5).should eq(7) } + it { "foobarbaz".index!('a', -4).should eq(7) } + it do + expect_raises(Enumerable::NotFoundError) do + "foo".index!('f', 1) + end + end + it do + expect_raises(Enumerable::NotFoundError) do + "foo".index!('g', -20) + end + end + end + end + + describe "by string" do + it { "foo bar".index!("o b").should eq(2) } + it { "foo".index!("").should eq(0) } + it { "foo".index!("foo").should eq(0) } + it do + expect_raises(Enumerable::NotFoundError) do + "foo".index!("fg") + end + end + + describe "with offset" do + it { "foobarbaz".index!("ba", 4).should eq(6) } + it { "foobarbaz".index!("ba", -5).should eq(6) } + it do + expect_raises(Enumerable::NotFoundError) do + "foo".index!("ba", 1) + end + end + it do + expect_raises(Enumerable::NotFoundError) do + "foo".index!("ba", -20) + end + end + end + end + + describe "by regex" do + it { "string 12345".index!(/\d+/).should eq(7) } + it { "12345".index!(/\d/).should eq(0) } + it do + expect_raises(Enumerable::NotFoundError) do + "Hello, world!".index!(/\d/) + end + end + + describe "with offset" do + it { "abcDef".index!(/[A-Z]/).should eq(3) } + it { "foobarbaz".index!(/ba/, -5).should eq(6) } + it do + expect_raises(Enumerable::NotFoundError) do + "Foo".index!(/[A-Z]/, 1) + end + end + end + end + end + describe "#rindex" do describe "by char" do it { "bbbb".rindex('b').should eq(3) } @@ -1047,6 +1119,70 @@ describe "String" do end end + describe "#rindex!" do + describe "by char" do + it { "bbbb".rindex!('b').should eq(3) } + it { "foobar".rindex!('a').should eq(4) } + it do + expect_raises(Enumerable::NotFoundError) do + "foobar".rindex!('g') + end + end + + describe "with offset" do + it { "bbbb".rindex!('b', 2).should eq(2) } + it do + expect_raises(Enumerable::NotFoundError) do + "abbbb".rindex!('b', 0) + end + end + end + end + + describe "by string" do + it { "bbbb".rindex!("b").should eq(3) } + it { "foo baro baz".rindex!("o b").should eq(7) } + it do + expect_raises(Enumerable::NotFoundError) do + "foo baro baz".rindex!("fg") + end + end + + describe "with offset" do + it { "bbbb".rindex!("b", 2).should eq(2) } + it do + expect_raises(Enumerable::NotFoundError) do + "abbbb".rindex!("b", 0) + end + end + it do + expect_raises(Enumerable::NotFoundError) do + "bbbb".rindex!("b", -5) + end + end + end + end + + describe "by regex" do + it { "bbbb".rindex!(/b/).should eq(3) } + it { "a43b53".rindex!(/\d+/).should eq(4) } + it do + expect_raises(Enumerable::NotFoundError) do + "bbbb".rindex!(/\d/) + end + end + + describe "with offset" do + it { "bbbb".rindex!(/b/, 2).should eq(2) } + it do + expect_raises(Enumerable::NotFoundError) do + "abbbb".rindex!(/b/, 0) + end + end + end + end + end + describe "partition" do describe "by char" do it { "hello".partition('h').should eq({"", "h", "ello"}) } diff --git a/src/string.cr b/src/string.cr index f5f9fd0e2abe..dd3918252002 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3281,6 +3281,13 @@ class String self.match(search, offset).try &.begin end + # :ditto: + # + # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`. + def index!(search, offset = 0) : Int32 + index(search, offset) || raise Enumerable::NotFoundError.new + end + # Returns the index of the _last_ appearance of *search* in the string, # If *offset* is present, it defines the position to _end_ the search # (characters beyond this point are ignored). @@ -3387,6 +3394,23 @@ class String match_result.try &.begin end + # :ditto: + # + # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`. + def rindex!(search : Regex, offset = size) : Int32 + rindex(search, offset) || raise Enumerable::NotFoundError.new + end + + # :ditto: + def rindex!(search : String, offset = size - search.size) : Int32 + rindex(search, offset) || raise Enumerable::NotFoundError.new + end + + # :ditto: + def rindex!(search : Char, offset = size - 1) : Int32 + rindex(search, offset) || raise Enumerable::NotFoundError.new + end + # Searches separator or pattern (`Regex`) in the string, and returns # a `Tuple` with the part before it, the match, and the part after it. # If it is not found, returns str followed by two empty strings. From 63618ff56abc1603d31b84200e18e4611f93566a Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sun, 20 Nov 2022 14:02:41 +0100 Subject: [PATCH 0145/1551] Couple of ameba lint issues fixed (#12685) --- samples/2048.cr | 2 +- samples/sudoku.cr | 4 ++-- spec/compiler/crystal/tools/implementations_spec.cr | 6 ++++-- spec/std/channel_spec.cr | 2 +- spec/std/csv/csv_parse_spec.cr | 4 ++-- spec/std/int_spec.cr | 10 +++++----- spec/std/number_spec.cr | 6 +++--- spec/std/weak_ref_spec.cr | 2 +- src/enumerable.cr | 2 +- src/env.cr | 2 +- src/iterator.cr | 2 +- src/process/executable_path.cr | 6 ++---- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/samples/2048.cr b/samples/2048.cr index 6e3d294e864c..ef5ef3111d8f 100644 --- a/samples/2048.cr +++ b/samples/2048.cr @@ -225,7 +225,7 @@ class Game def insert_tile value = rand > 0.8 ? 4 : 2 - empty_cells = @grid.map(&.count &.nil?).sum + empty_cells = @grid.sum(&.count &.nil?) fill_cell = empty_cells > 1 ? rand(empty_cells - 1) + 1 : 1 diff --git a/samples/sudoku.cr b/samples/sudoku.cr index 992796e905f2..2c3adf64dce0 100644 --- a/samples/sudoku.cr +++ b/samples/sudoku.cr @@ -148,12 +148,12 @@ SUDOKU def solve_all(sudoku) mr, mc = sd_genmat() - sudoku.split('\n').map do |line| + sudoku.split('\n').compact_map do |line| if line.size >= 81 ret = sd_solve(mr, mc, line) ret.map(&.join) end - end.compact + end end 10.times do |i| diff --git a/spec/compiler/crystal/tools/implementations_spec.cr b/spec/compiler/crystal/tools/implementations_spec.cr index 53af55b5e583..7d35659de2bb 100644 --- a/spec/compiler/crystal/tools/implementations_spec.cr +++ b/spec/compiler/crystal/tools/implementations_spec.cr @@ -30,9 +30,11 @@ private def assert_implementations(code) if cursor_location visitor, result = processed_implementation_visitor(code, cursor_location) - result_location = result.implementations.not_nil!.map { |e| Location.new(e.filename.not_nil!, e.line.not_nil!, e.column.not_nil!).to_s }.sort + result_locations = result.implementations.not_nil!.map do |e| + Location.new(e.filename.not_nil!, e.line.not_nil!, e.column.not_nil!).to_s + end.sort! - result_location.should eq(expected_locations.map(&.to_s)) + result_locations.should eq(expected_locations.map(&.to_s)) else raise "no cursor found in spec" end diff --git a/spec/std/channel_spec.cr b/spec/std/channel_spec.cr index dff88b48eddf..4bf7a7c5c80a 100644 --- a/spec/std/channel_spec.cr +++ b/spec/std/channel_spec.cr @@ -608,7 +608,7 @@ describe "unbuffered" do spawn { ch.send 2; ch.send 5 } spawn { ch.send 3; ch.send 6 } - (1..6).map { ch.receive }.sort.should eq([1, 2, 3, 4, 5, 6]) + (1..6).map { ch.receive }.sort!.should eq([1, 2, 3, 4, 5, 6]) end it "works with select" do diff --git a/spec/std/csv/csv_parse_spec.cr b/spec/std/csv/csv_parse_spec.cr index 98192cbf413b..5f9906f35333 100644 --- a/spec/std/csv/csv_parse_spec.cr +++ b/spec/std/csv/csv_parse_spec.cr @@ -90,7 +90,7 @@ describe CSV do it "does CSV.each_row" do sum = 0 CSV.each_row("1,2\n3,4\n") do |row| - sum += row.map(&.to_i).sum + sum += row.sum(&.to_i) end.should be_nil sum.should eq(10) end @@ -98,7 +98,7 @@ describe CSV do it "does CSV.each_row with separator and quotes" do sum = 0 CSV.each_row("1\t'2'\n3\t4\n", '\t', '\'') do |row| - sum += row.map(&.to_i).sum + sum += row.sum(&.to_i) end.should be_nil sum.should eq(10) end diff --git a/spec/std/int_spec.cr b/spec/std/int_spec.cr index 8fb67df975dd..b8029e3a0911 100644 --- a/spec/std/int_spec.cr +++ b/spec/std/int_spec.cr @@ -905,11 +905,11 @@ describe "Int" do end it "works for maximums" do - Int32::MAX.digits.should eq(Int32::MAX.to_s.chars.map(&.to_i).reverse) - Int64::MAX.digits.should eq(Int64::MAX.to_s.chars.map(&.to_i).reverse) - UInt64::MAX.digits.should eq(UInt64::MAX.to_s.chars.map(&.to_i).reverse) - Int128::MAX.digits.should eq(Int128::MAX.to_s.chars.map(&.to_i).reverse) - UInt128::MAX.digits.should eq(UInt128::MAX.to_s.chars.map(&.to_i).reverse) + Int32::MAX.digits.should eq(Int32::MAX.to_s.chars.map(&.to_i).reverse!) + Int64::MAX.digits.should eq(Int64::MAX.to_s.chars.map(&.to_i).reverse!) + UInt64::MAX.digits.should eq(UInt64::MAX.to_s.chars.map(&.to_i).reverse!) + Int128::MAX.digits.should eq(Int128::MAX.to_s.chars.map(&.to_i).reverse!) + UInt128::MAX.digits.should eq(UInt128::MAX.to_s.chars.map(&.to_i).reverse!) end it "works for non-Int32" do diff --git a/spec/std/number_spec.cr b/spec/std/number_spec.cr index 51f6cfc9e933..0b703d7199d0 100644 --- a/spec/std/number_spec.cr +++ b/spec/std/number_spec.cr @@ -574,15 +574,15 @@ describe "Number" do describe "whole range" do it { (UInt8::MIN..UInt8::MAX).each.count { true }.should eq(256) } it_iterates "UInt8 upwards", (UInt8::MIN.to_i..UInt8::MAX.to_i).map(&.to_u8), (UInt8::MIN..UInt8::MAX).step(by: 1) - it_iterates "UInt8 downwards", (UInt8::MIN.to_i..UInt8::MAX.to_i).map(&.to_u8).reverse, (UInt8::MAX..UInt8::MIN).step(by: -1) + it_iterates "UInt8 downwards", (UInt8::MIN.to_i..UInt8::MAX.to_i).map(&.to_u8).reverse!, (UInt8::MAX..UInt8::MIN).step(by: -1) it { (Int8::MIN..Int8::MAX).each.count { true }.should eq(256) } it_iterates "Int8 upwards", (Int8::MIN.to_i..Int8::MAX.to_i).map(&.to_i8), (Int8::MIN..Int8::MAX).step(by: 1) - it_iterates "Int8 downwards", (Int8::MIN.to_i..Int8::MAX.to_i).map(&.to_i8).reverse, (Int8::MAX..Int8::MIN).step(by: -1) + it_iterates "Int8 downwards", (Int8::MIN.to_i..Int8::MAX.to_i).map(&.to_i8).reverse!, (Int8::MAX..Int8::MIN).step(by: -1) it { (Int16::MIN..Int16::MAX).each.count { true }.should eq(65536) } it_iterates "Int16 upwards", (Int16::MIN.to_i..Int16::MAX.to_i).map(&.to_i16), (Int16::MIN..Int16::MAX).step(by: 1) - it_iterates "Int16 downwards", (Int16::MIN.to_i..Int16::MAX.to_i).map(&.to_i16).reverse, (Int16::MAX..Int16::MIN).step(by: -1) + it_iterates "Int16 downwards", (Int16::MIN.to_i..Int16::MAX.to_i).map(&.to_i16).reverse!, (Int16::MAX..Int16::MIN).step(by: -1) end it_iterates "towards limit [max-4, max-2, max]", [Int32::MAX - 4, Int32::MAX - 2, Int32::MAX], (Int32::MAX - 4).step(to: Int32::MAX, by: 2) diff --git a/spec/std/weak_ref_spec.cr b/spec/std/weak_ref_spec.cr index 76f610956ef5..9cd7f70389ab 100644 --- a/spec/std/weak_ref_spec.cr +++ b/spec/std/weak_ref_spec.cr @@ -60,7 +60,7 @@ describe WeakRef do end GC.collect FinalizeState.count("weak_foo_ref").should be > 0 - instances.select { |wr| wr.value.nil? }.size.should be > 0 + instances.count { |wr| wr.value.nil? }.should be > 0 instances[-1].value.should_not be_nil # Use `last` to stop the variable from being optimised away in release mode. diff --git a/src/enumerable.cr b/src/enumerable.cr index 26590eb92370..9bbb49c997e7 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -231,7 +231,7 @@ module Enumerable(T) ary = [] of typeof((yield Enumerable.element_type(self)).not_nil!) each do |e| v = yield e - unless v.is_a?(Nil) + unless v.nil? ary << v end end diff --git a/src/env.cr b/src/env.cr index e16e60a67fcf..6e5b47104288 100644 --- a/src/env.cr +++ b/src/env.cr @@ -128,7 +128,7 @@ module ENV end def self.pretty_print(pp) - pp.list("{", keys.sort, "}") do |key| + pp.list("{", keys.sort!, "}") do |key| pp.group do key.pretty_print(pp) pp.text " =>" diff --git a/src/iterator.cr b/src/iterator.cr index d306555238ec..3cc614a84194 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -357,7 +357,7 @@ module Iterator(T) value = wrapped_next mapped_value = @func.call(value) - return mapped_value unless mapped_value.is_a?(Nil) + return mapped_value unless mapped_value.nil? end end end diff --git a/src/process/executable_path.cr b/src/process/executable_path.cr index 75f8dbf621cf..67f253cdd291 100644 --- a/src/process/executable_path.cr +++ b/src/process/executable_path.cr @@ -37,7 +37,7 @@ class Process end end - private def self.is_executable_file?(path) + private def self.file_executable?(path) unless File.info?(path, follow_symlinks: true).try &.file? return false end @@ -55,9 +55,7 @@ class Process # in *path*. def self.find_executable(name : Path | String, path : String? = ENV["PATH"]?, pwd : Path | String = Dir.current) : String? find_executable_possibilities(Path.new(name), path, pwd) do |p| - if is_executable_file?(p) - return p.to_s - end + return p.to_s if file_executable?(p) end nil end From 66379f523fb9e7e999813afec72918d267613b23 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 21 Nov 2022 19:28:35 +0800 Subject: [PATCH 0146/1551] Support new SI prefixes in `Number#humanize` (#12761) --- spec/std/humanize_spec.cr | 93 +++++++++++++++++++++++++++++++++++++++ src/humanize.cr | 27 ++++++------ 2 files changed, 107 insertions(+), 13 deletions(-) diff --git a/spec/std/humanize_spec.cr b/spec/std/humanize_spec.cr index eb82ff832c73..a7342b495947 100644 --- a/spec/std/humanize_spec.cr +++ b/spec/std/humanize_spec.cr @@ -12,6 +12,8 @@ private LENGTH_UNITS = ->(magnitude : Int32, number : Float64) do end end +private CUSTOM_PREFIXES = [['a'], ['b', 'c', 'd']] + describe Number do describe "#format" do it { assert_prints 1.format, "1" } @@ -117,6 +119,78 @@ describe Number do it { assert_prints 0.123_456_78.humanize, "123m" } it { assert_prints 0.123_456_78.humanize(5), "123.46m" } + it { assert_prints 1.0e-35.humanize, "0.00001q" } + it { assert_prints 1.0e-34.humanize, "0.0001q" } + it { assert_prints 1.0e-33.humanize, "0.001q" } + it { assert_prints 1.0e-32.humanize, "0.01q" } + it { assert_prints 1.0e-31.humanize, "0.1q" } + it { assert_prints 1.0e-30.humanize, "1.0q" } + it { assert_prints 1.0e-29.humanize, "10.0q" } + it { assert_prints 1.0e-28.humanize, "100q" } + it { assert_prints 1.0e-27.humanize, "1.0r" } + it { assert_prints 1.0e-26.humanize, "10.0r" } + it { assert_prints 1.0e-25.humanize, "100r" } + it { assert_prints 1.0e-24.humanize, "1.0y" } + it { assert_prints 1.0e-23.humanize, "10.0y" } + it { assert_prints 1.0e-22.humanize, "100y" } + it { assert_prints 1.0e-21.humanize, "1.0z" } + it { assert_prints 1.0e-20.humanize, "10.0z" } + it { assert_prints 1.0e-19.humanize, "100z" } + it { assert_prints 1.0e-18.humanize, "1.0a" } + it { assert_prints 1.0e-17.humanize, "10.0a" } + it { assert_prints 1.0e-16.humanize, "100a" } + it { assert_prints 1.0e-15.humanize, "1.0f" } + it { assert_prints 1.0e-14.humanize, "10.0f" } + it { assert_prints 1.0e-13.humanize, "100f" } + it { assert_prints 1.0e-12.humanize, "1.0p" } + it { assert_prints 1.0e-11.humanize, "10.0p" } + it { assert_prints 1.0e-10.humanize, "100p" } + it { assert_prints 1.0e-9.humanize, "1.0n" } + it { assert_prints 1.0e-8.humanize, "10.0n" } + it { assert_prints 1.0e-7.humanize, "100n" } + it { assert_prints 1.0e-6.humanize, "1.0µ" } + it { assert_prints 1.0e-5.humanize, "10.0µ" } + it { assert_prints 1.0e-4.humanize, "100µ" } + it { assert_prints 1.0e-3.humanize, "1.0m" } + it { assert_prints 1.0e-2.humanize, "10.0m" } + it { assert_prints 1.0e-1.humanize, "100m" } + it { assert_prints 1.0e+0.humanize, "1.0" } + it { assert_prints 1.0e+1.humanize, "10.0" } + it { assert_prints 1.0e+2.humanize, "100" } + it { assert_prints 1.0e+3.humanize, "1.0k" } + it { assert_prints 1.0e+4.humanize, "10.0k" } + it { assert_prints 1.0e+5.humanize, "100k" } + it { assert_prints 1.0e+6.humanize, "1.0M" } + it { assert_prints 1.0e+7.humanize, "10.0M" } + it { assert_prints 1.0e+8.humanize, "100M" } + it { assert_prints 1.0e+9.humanize, "1.0G" } + it { assert_prints 1.0e+10.humanize, "10.0G" } + it { assert_prints 1.0e+11.humanize, "100G" } + it { assert_prints 1.0e+12.humanize, "1.0T" } + it { assert_prints 1.0e+13.humanize, "10.0T" } + it { assert_prints 1.0e+14.humanize, "100T" } + it { assert_prints 1.0e+15.humanize, "1.0P" } + it { assert_prints 1.0e+16.humanize, "10.0P" } + it { assert_prints 1.0e+17.humanize, "100P" } + it { assert_prints 1.0e+18.humanize, "1.0E" } + it { assert_prints 1.0e+19.humanize, "10.0E" } + it { assert_prints 1.0e+20.humanize, "100E" } + it { assert_prints 1.0e+21.humanize, "1.0Z" } + it { assert_prints 1.0e+22.humanize, "10.0Z" } + it { assert_prints 1.0e+23.humanize, "100Z" } + it { assert_prints 1.0e+24.humanize, "1.0Y" } + it { assert_prints 1.0e+25.humanize, "10.0Y" } + it { assert_prints 1.0e+26.humanize, "100Y" } + it { assert_prints 1.0e+27.humanize, "1.0R" } + it { assert_prints 1.0e+28.humanize, "10.0R" } + it { assert_prints 1.0e+29.humanize, "100R" } + it { assert_prints 1.0e+30.humanize, "1.0Q" } + it { assert_prints 1.0e+31.humanize, "10.0Q" } + it { assert_prints 1.0e+32.humanize, "100Q" } + it { assert_prints 1.0e+33.humanize, "1,000Q" } + it { assert_prints 1.0e+34.humanize, "10,000Q" } + it { assert_prints 1.0e+35.humanize, "100,000Q" } + it { assert_prints 1_234.567_890_123.humanize(precision: 2, significant: false), "1.23k" } it { assert_prints 123.456_789_012_3.humanize(precision: 2, significant: false), "123.46" } it { assert_prints 12.345_678_901_23.humanize(precision: 2, significant: false), "12.35" } @@ -144,6 +218,25 @@ describe Number do it { assert_prints 0.001.humanize(prefixes: LENGTH_UNITS), "1.0 mm" } it { assert_prints 0.000_01.humanize(prefixes: LENGTH_UNITS), "10.0 µm" } it { assert_prints 0.000_000_001.humanize(prefixes: LENGTH_UNITS), "1.0 nm" } + + it { assert_prints 1.0e-7.humanize(prefixes: CUSTOM_PREFIXES), "0.0001a" } + it { assert_prints 1.0e-6.humanize(prefixes: CUSTOM_PREFIXES), "0.001a" } + it { assert_prints 1.0e-5.humanize(prefixes: CUSTOM_PREFIXES), "0.01a" } + it { assert_prints 1.0e-4.humanize(prefixes: CUSTOM_PREFIXES), "0.1a" } + it { assert_prints 1.0e-3.humanize(prefixes: CUSTOM_PREFIXES), "1.0a" } + it { assert_prints 1.0e-2.humanize(prefixes: CUSTOM_PREFIXES), "10.0a" } + it { assert_prints 1.0e-1.humanize(prefixes: CUSTOM_PREFIXES), "100a" } + it { assert_prints 1.0e+0.humanize(prefixes: CUSTOM_PREFIXES), "1.0b" } + it { assert_prints 1.0e+1.humanize(prefixes: CUSTOM_PREFIXES), "10.0b" } + it { assert_prints 1.0e+2.humanize(prefixes: CUSTOM_PREFIXES), "100b" } + it { assert_prints 1.0e+3.humanize(prefixes: CUSTOM_PREFIXES), "1.0c" } + it { assert_prints 1.0e+4.humanize(prefixes: CUSTOM_PREFIXES), "10.0c" } + it { assert_prints 1.0e+5.humanize(prefixes: CUSTOM_PREFIXES), "100c" } + it { assert_prints 1.0e+6.humanize(prefixes: CUSTOM_PREFIXES), "1.0d" } + it { assert_prints 1.0e+7.humanize(prefixes: CUSTOM_PREFIXES), "10.0d" } + it { assert_prints 1.0e+8.humanize(prefixes: CUSTOM_PREFIXES), "100d" } + it { assert_prints 1.0e+9.humanize(prefixes: CUSTOM_PREFIXES), "1,000d" } + it { assert_prints 1.0e+10.humanize(prefixes: CUSTOM_PREFIXES), "10,000d" } end end end diff --git a/src/humanize.cr b/src/humanize.cr index 0fa5dfaca4f4..73974b6448df 100644 --- a/src/humanize.cr +++ b/src/humanize.cr @@ -96,7 +96,7 @@ struct Number end # Default SI prefixes ordered by magnitude. - SI_PREFIXES = { {'y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm'}, {nil, 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'} } + SI_PREFIXES = { {'q', 'r', 'y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm'}, {nil, 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q'} } # SI prefixes used by `#humanize`. Equal to `SI_PREFIXES` but prepends the # prefix with a space character. @@ -113,12 +113,13 @@ struct Number def self.si_prefix(magnitude : Int, prefixes = SI_PREFIXES) : Char? index = (magnitude // 3) prefixes = prefixes[magnitude < 0 ? 0 : 1] - prefixes[index.clamp((-prefixes.size + 1)..(prefixes.size - 1))] + prefixes[index.clamp((-prefixes.size)..(prefixes.size - 1))] end # :nodoc: - def self.prefix_index(i : Int32, group : Int32 = 3) : Int32 - ((i - (i > 0 ? 1 : 0)) // group) * group + def self.prefix_index(i : Int32, *, group : Int32 = 3, prefixes = SI_PREFIXES) : Int32 + prefixes = prefixes[i < 0 ? 0 : 1] + ((i - (i > 0 ? 1 : 0)) // group).clamp((-prefixes.size)..(prefixes.size - 1)) * group end # Pretty prints this number as a `String` in a human-readable format. @@ -150,7 +151,7 @@ struct Number # See `Int#humanize_bytes` to format a file size. def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, prefixes : Indexable = SI_PREFIXES) : Nil humanize(io, precision, separator, delimiter, base: base, significant: significant) do |magnitude, _| - magnitude = Number.prefix_index(magnitude) + magnitude = Number.prefix_index(magnitude, prefixes: prefixes) {magnitude, Number.si_prefix(magnitude, prefixes)} end end @@ -284,13 +285,13 @@ end struct Int enum BinaryPrefixFormat - # The IEC standard prefixes (`Ki`, `Mi`, `Gi`, `Ti`, `Pi`, `Ei`, `Zi`, `Yi`) + # The IEC standard prefixes (`Ki`, `Mi`, `Gi`, `Ti`, `Pi`, `Ei`, `Zi`, `Yi`, `Ri`, `Qi`) # based on powers of 1000. IEC - # Extended range of the JEDEC units (`K`, `M`, `G`, `T`, `P`, `E`, `Z`, `Y`) which equals to - # the prefixes of the SI system except for uppercase `K` and is based on - # powers of 1024. + # Extended range of the JEDEC units (`K`, `M`, `G`, `T`, `P`, `E`, `Z`, `Y`, `R`, `Q`) + # which equals to the prefixes of the SI system except for uppercase `K` and + # is based on powers of 1024. JEDEC end @@ -300,12 +301,12 @@ struct Int # Values with binary measurements such as computer storage (e.g. RAM size) are # typically expressed using unit prefixes based on 1024 (instead of multiples # of 1000 as per SI standard). This method by default uses the IEC standard - # prefixes (`Ki`, `Mi`, `Gi`, `Ti`, `Pi`, `Ei`, `Zi`, `Yi`) based on powers of - # 1000 (see `BinaryPrefixFormat::IEC`). + # prefixes (`Ki`, `Mi`, `Gi`, `Ti`, `Pi`, `Ei`, `Zi`, `Yi`, `Ri`, `Qi`) based + # on powers of 1000 (see `BinaryPrefixFormat::IEC`). # # *format* can be set to use the extended range of JEDEC units (`K`, `M`, `G`, - # `T`, `P`, `E`, `Z`, `Y`) which equals to the prefixes of the SI system - # except for uppercase `K` and is based on powers of 1024 (see + # `T`, `P`, `E`, `Z`, `Y`, `R`, `Q`) which equals to the prefixes of the SI + # system except for uppercase `K` and is based on powers of 1024 (see # `BinaryPrefixFormat::JEDEC`). # # ``` From 5397f1e6fc82f014076a7806dac583736bfee05a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 21 Nov 2022 19:29:19 +0800 Subject: [PATCH 0147/1551] [CI] Update PCRE 8.45 for Windows CI (#12762) --- .github/workflows/win.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 357bc76dacc7..5e714918dc6b 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -73,8 +73,8 @@ jobs: - name: Download libpcre if: steps.cache-libs.outputs.cache-hit != 'true' run: | - iwr https://cs.stanford.edu/pub/exim/pcre/pcre-8.43.zip -OutFile pcre.zip - (Get-FileHash -Algorithm SHA256 .\pcre.zip).hash -eq "ae236dc25d7e0e738a94e103218e0085eb02ff9bd98f637b6e061a48decdb433" + iwr https://cs.stanford.edu/pub/exim/pcre/pcre-8.45.zip -OutFile pcre.zip + (Get-FileHash -Algorithm SHA256 .\pcre.zip).hash -eq "5b709aa45ea3b8bb73052947200ad187f651a2049158fb5bbfed329e4322a977" 7z x pcre.zip mv pcre-* pcre - name: Build libpcre From 58293f32a3847606228db1307c4b1f43e2368cf0 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 22 Nov 2022 18:27:46 +0800 Subject: [PATCH 0148/1551] Upgrade the Dragonbox algorithm (#12767) --- src/float/printer/dragonbox.cr | 290 ++++++-------- src/float/printer/dragonbox_cache.cr | 580 +++++++++++++-------------- 2 files changed, 420 insertions(+), 450 deletions(-) diff --git a/src/float/printer/dragonbox.cr b/src/float/printer/dragonbox.cr index 437ab881e008..cb857379266f 100644 --- a/src/float/printer/dragonbox.cr +++ b/src/float/printer/dragonbox.cr @@ -19,7 +19,7 @@ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. module Float::Printer::Dragonbox - # Current revision: https://github.com/jk-jeon/dragonbox/tree/b5b4f65a83da14019bcec7ae31965216215a3e10 + # Current revision: https://github.com/jk-jeon/dragonbox/tree/33a9e021290d529bcb41773be2c7c3c91726a9cb # # Assumes the following policies: # @@ -80,34 +80,32 @@ module Float::Printer::Dragonbox ac &+ (intermediate >> 32) &+ (ad >> 32) &+ (bc >> 32) end - # Get upper 64-bits of multiplication of a 64-bit unsigned integer and a 128-bit unsigned integer. - def self.umul192_upper64(x : UInt64, y : UInt128) : UInt64 - g0 = umul128(x, y.high) - g0.unsafe_add! umul128_upper64(x, y.low) - g0.high + # Get upper 128-bits of multiplication of a 64-bit unsigned integer and a 128-bit unsigned integer. + def self.umul192_upper128(x : UInt64, y : UInt128) : UInt128 + r = umul128(x, y.high) + r.unsafe_add!(umul128_upper64(x, y.low)) + r end - # Get upper 32-bits of multiplication of a 32-bit unsigned integer and a 64-bit unsigned integer. - def self.umul96_upper32(x : UInt32, y : UInt64) : UInt32 - # a = 0_u32 - b = x - c = (y >> 32).to_u32! - d = y.to_u32! + # Get upper 64-bits of multiplication of a 32-bit unsigned integer and a 64-bit unsigned integer. + def self.umul96_upper64(x : UInt32, y : UInt64) : UInt64 + yh = (y >> 32).to_u32! + yl = y.to_u32! - # ac = 0_u64 - bc = umul64(b, c) - # ad = 0_u64 - bd = umul64(b, d) + xyh = umul64(x, yh) + xyl = umul64(x, yl) - intermediate = (bd >> 32) &+ bc - (intermediate >> 32).to_u32! + xyh &+ (xyl >> 32) end - # Get middle 64-bits of multiplication of a 64-bit unsigned integer and a 128-bit unsigned integer. - def self.umul192_middle64(x : UInt64, y : UInt128) : UInt64 - g01 = x &* y.high - g10 = umul128_upper64(x, y.low) - g01 &+ g10 + # Get lower 128-bits of multiplication of a 64-bit unsigned integer and a 128-bit unsigned integer. + def self.umul192_lower128(x : UInt64, y : UInt128) : UInt128 + high = x &* y.high + high_low = umul128(x, y.low) + UInt128.new( + high: high &+ high_low.high, + low: high_low.low, + ) end # Get lower 64-bits of multiplication of a 32-bit unsigned integer and a 64-bit unsigned integer. @@ -119,8 +117,8 @@ module Float::Printer::Dragonbox # Utilities for fast log computation. private module Log def self.floor_log10_pow2(e : Int) - # Precondition: `-1700 <= e <= 1700` - (e &* 1262611) >> 22 + # Precondition: `-2620 <= e <= 2620` + (e &* 315653) >> 20 end def self.floor_log2_pow10(e : Int) @@ -129,8 +127,8 @@ module Float::Printer::Dragonbox end def self.floor_log10_pow2_minus_log10_4_over_3(e : Int) - # Precondition: `-1700 <= e <= 1700` - (e &* 1262611 &- 524031) >> 22 + # Precondition: `-2985 <= e <= 2936` + (e &* 631305 &- 261663) >> 21 end end @@ -190,52 +188,40 @@ module Float::Printer::Dragonbox {0xdcd618596be30fe5_u64, 0x000000000000060b_u64}, ] - module CHECK_DIVISIBILITY_AND_DIVIDE_BY_POW10_INFO_F32 - MAGIC_NUMBER = 0xcccd_u32 - BITS_FOR_COMPARISON = 16 - THRESHOLD = 0x3333_u32 - SHIFT_AMOUNT = 19 + module DIVIDE_BY_POW10_INFO_F32 + MAGIC_NUMBER = 6554_u32 + SHIFT_AMOUNT = 16 end - module CHECK_DIVISIBILITY_AND_DIVIDE_BY_POW10_INFO_F64 - MAGIC_NUMBER = 0x147c29_u32 - BITS_FOR_COMPARISON = 12 - THRESHOLD = 0xa3_u32 - SHIFT_AMOUNT = 27 - end - - def self.divisible_by_power_of_5?(x : UInt32, exp : Int) - mod_inv, max_quotients = CACHED_POWERS_OF_5_TABLE_U32[exp] - x &* mod_inv <= max_quotients - end - - def self.divisible_by_power_of_5?(x : UInt64, exp : Int) - mod_inv, max_quotients = CACHED_POWERS_OF_5_TABLE_U64[exp] - x &* mod_inv <= max_quotients - end - - def self.divisible_by_power_of_2?(x : Int::Unsigned, exp : Int) - x.trailing_zeros_count >= exp + module DIVIDE_BY_POW10_INFO_F64 + MAGIC_NUMBER = 656_u32 + SHIFT_AMOUNT = 16 end + # N == 1 def self.check_divisibility_and_divide_by_pow10_k1(n : UInt32) - bits_for_comparison = CHECK_DIVISIBILITY_AND_DIVIDE_BY_POW10_INFO_F32::BITS_FOR_COMPARISON - comparison_mask = ~(UInt32::MAX << bits_for_comparison) + n &*= DIVIDE_BY_POW10_INFO_F32::MAGIC_NUMBER + + # Mask for the lowest (divisibility_check_bits)-bits. + divisibility_check_bits = DIVIDE_BY_POW10_INFO_F32::SHIFT_AMOUNT + comparison_mask = ~(UInt32::MAX << divisibility_check_bits) + result = n & comparison_mask < DIVIDE_BY_POW10_INFO_F32::MAGIC_NUMBER - n &*= CHECK_DIVISIBILITY_AND_DIVIDE_BY_POW10_INFO_F32::MAGIC_NUMBER - c = ((n >> 1) | (n << (bits_for_comparison - 1))) & comparison_mask - n >>= CHECK_DIVISIBILITY_AND_DIVIDE_BY_POW10_INFO_F32::SHIFT_AMOUNT - {n, c <= CHECK_DIVISIBILITY_AND_DIVIDE_BY_POW10_INFO_F32::THRESHOLD} + n >>= divisibility_check_bits + {n, result} end + # N == 2 def self.check_divisibility_and_divide_by_pow10_k2(n : UInt32) - bits_for_comparison = CHECK_DIVISIBILITY_AND_DIVIDE_BY_POW10_INFO_F64::BITS_FOR_COMPARISON - comparison_mask = ~(UInt32::MAX << bits_for_comparison) + n &*= DIVIDE_BY_POW10_INFO_F64::MAGIC_NUMBER + + # Mask for the lowest (divisibility_check_bits)-bits. + divisibility_check_bits = DIVIDE_BY_POW10_INFO_F64::SHIFT_AMOUNT + comparison_mask = ~(UInt32::MAX << divisibility_check_bits) + result = n & comparison_mask < DIVIDE_BY_POW10_INFO_F64::MAGIC_NUMBER - n &*= CHECK_DIVISIBILITY_AND_DIVIDE_BY_POW10_INFO_F64::MAGIC_NUMBER - c = ((n >> 2) | (n << (bits_for_comparison - 2))) & comparison_mask - n >>= CHECK_DIVISIBILITY_AND_DIVIDE_BY_POW10_INFO_F64::SHIFT_AMOUNT - {n, c <= CHECK_DIVISIBILITY_AND_DIVIDE_BY_POW10_INFO_F64::THRESHOLD} + n >>= divisibility_check_bits + {n, result} end end @@ -246,11 +232,11 @@ module Float::Printer::Dragonbox end def remove_exponent_bits(u : D::CarrierUInt, exponent_bits) - D::SignedSignificand.new!(u ^ (D::CarrierUInt.new!(exponent_bits) << D::SIGNIFICAND_BITS)) + u ^ (D::CarrierUInt.new!(exponent_bits) << D::SIGNIFICAND_BITS) end - def remove_sign_bit_and_shift(s : D::SignedSignificand) - D::CarrierUInt.new!(s) << 1 + def remove_sign_bit_and_shift(u : D::CarrierUInt) + u << 1 end def check_divisibility_and_divide_by_pow10(n : UInt32) @@ -275,7 +261,6 @@ module Float::Printer::Dragonbox DECIMAL_DIGITS = 9 alias CarrierUInt = UInt32 - alias SignedSignificand = Int32 CARRIER_BITS = 32 KAPPA = 1 @@ -283,11 +268,6 @@ module Float::Printer::Dragonbox # MAX_K = 46 CACHE_BITS = 64 - DIVISIBILITY_CHECK_BY_5_THRESHOLD = 39 - CASE_FC_PM_HALF_LOWER_THRESHOLD = -1 - CASE_FC_PM_HALF_UPPER_THRESHOLD = 6 - CASE_FC_LOWER_THRESHOLD = -2 - CASE_FC_UPPER_THRESHOLD = 6 SHORTER_INTERVAL_TIE_LOWER_THRESHOLD = -35 SHORTER_INTERVAL_TIE_UPPER_THRESHOLD = -35 CASE_SHORTER_INTERVAL_LEFT_ENDPOINT_LOWER_THRESHOLD = 2 @@ -308,7 +288,6 @@ module Float::Printer::Dragonbox DECIMAL_DIGITS = 17 alias CarrierUInt = UInt64 - alias SignedSignificand = Int64 CARRIER_BITS = 64 KAPPA = 2 @@ -316,11 +295,6 @@ module Float::Printer::Dragonbox # MAX_K = 326 CACHE_BITS = 128 - DIVISIBILITY_CHECK_BY_5_THRESHOLD = 86 - CASE_FC_PM_HALF_LOWER_THRESHOLD = -2 - CASE_FC_PM_HALF_UPPER_THRESHOLD = 9 - CASE_FC_LOWER_THRESHOLD = -4 - CASE_FC_UPPER_THRESHOLD = 9 SHORTER_INTERVAL_TIE_LOWER_THRESHOLD = -77 SHORTER_INTERVAL_TIE_UPPER_THRESHOLD = -77 CASE_SHORTER_INTERVAL_LEFT_ENDPOINT_LOWER_THRESHOLD = 2 @@ -331,8 +305,8 @@ module Float::Printer::Dragonbox end private module Impl(F, ImplInfo) - def self.break_rounding_tie(significand) - significand % 2 == 0 ? significand : significand - 1 + def self.prefer_round_down?(significand) + significand % 2 != 0 end def self.compute_nearest_normal(two_fc, exponent, is_closed) @@ -341,13 +315,21 @@ module Float::Printer::Dragonbox # Compute k and beta. minus_k = Log.floor_log10_pow2(exponent) - ImplInfo::KAPPA cache = ImplInfo.get_cache(-minus_k) - beta_minus_1 = exponent + Log.floor_log2_pow10(-minus_k) + beta = exponent + Log.floor_log2_pow10(-minus_k) # Compute zi and deltai. # 10^kappa <= deltai < 10^(kappa + 1) - deltai = compute_delta(cache, beta_minus_1) - two_fr = two_fc | 1 - zi = compute_mul(two_fr << beta_minus_1, cache) + deltai = compute_delta(cache, beta) + # For the case of binary32, the result of integer check is not correct for + # 29711844 * 2^-82 + # = 6.1442653300000000008655037797566933477355632930994033813476... * 10^-18 + # and 29711844 * 2^-81 + # = 1.2288530660000000001731007559513386695471126586198806762695... * 10^-17, + # and they are the unique counterexamples. However, since 29711844 is even, + # this does not cause any problem for the endpoints calculations; it can only + # cause a problem when we need to perform integer check for the center. + # Fortunately, with these inputs, that branch is never executed, so we are fine. + zi, is_z_integer = compute_mul((two_fc | 1) << beta, cache) # Step 2: Try larger divisor big_divisor = ImplInfo::BIG_DIVISOR @@ -361,7 +343,7 @@ module Float::Printer::Dragonbox # do nothing when .<(deltai) # Exclude the right endpoint if necessary. - if r == 0 && !is_closed && is_product_integer_pm_half?(two_fr, exponent, minus_k) + if r == 0 && is_z_integer && !is_closed significand -= 1 r = big_divisor else @@ -370,10 +352,9 @@ module Float::Printer::Dragonbox end else # r == deltai; compare fractional parts. - # Check conditions in the order different from the paper - # to take advantage of short-circuiting. - two_fl = two_fc - 1 - unless (!is_closed || !is_product_integer_pm_half?(two_fl, exponent, minus_k)) && !compute_mul_parity(two_fl, cache, beta_minus_1) + xi_parity, x_is_integer = compute_mul_parity(two_fc - 1, cache, beta) + + if xi_parity || (x_is_integer && is_closed) ret_exponent = minus_k + ImplInfo::KAPPA + 1 return {significand, ret_exponent} end @@ -387,25 +368,26 @@ module Float::Printer::Dragonbox approx_y_parity = ((dist ^ (small_divisor // 2)) & 1) != 0 # Is dist divisible by 10^kappa? - dist, divisible_by_10_to_the_kappa = ImplInfo.check_divisibility_and_divide_by_pow10(dist) + dist, divisible_by_small_divisor = ImplInfo.check_divisibility_and_divide_by_pow10(dist) # Add dist / 10^kappa to the significand. significand += dist - if divisible_by_10_to_the_kappa - # Check z^(f) >= epsilon^(f) + if divisible_by_small_divisor + # Check z^(f) >= epsilon^(f). # We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, - # where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f) + # where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f). # Since there are only 2 possibilities, we only need to care about the parity. # Also, zi and r should have the same parity since the divisor # is an even number. - if compute_mul_parity(two_fc, cache, beta_minus_1) != approx_y_parity + yi_parity, is_y_integer = compute_mul_parity(two_fc, cache, beta) + if yi_parity != approx_y_parity significand -= 1 - elsif is_product_integer?(two_fc, exponent, minus_k) + elsif prefer_round_down?(significand) && is_y_integer # If z^(f) >= epsilon^(f), we might have a tie # when z^(f) == epsilon^(f), or equivalently, when y is an integer. # For tie-to-up case, we can just choose the upper one. - significand = break_rounding_tie(significand) + significand -= 1 end end @@ -415,13 +397,13 @@ module Float::Printer::Dragonbox def self.compute_nearest_shorter(exponent) # Compute k and beta. minus_k = Log.floor_log10_pow2_minus_log10_4_over_3(exponent) - beta_minus_1 = exponent + Log.floor_log2_pow10(-minus_k) + beta = exponent + Log.floor_log2_pow10(-minus_k) # Compute xi and zi. cache = ImplInfo.get_cache(-minus_k) - xi = compute_left_endpoint_for_shorter_interval_case(cache, beta_minus_1) - zi = compute_right_endpoint_for_shorter_interval_case(cache, beta_minus_1) + xi = compute_left_endpoint_for_shorter_interval_case(cache, beta) + zi = compute_right_endpoint_for_shorter_interval_case(cache, beta) # If we don't accept the left endpoint or # if the left endpoint is not an integer, increase it. @@ -437,12 +419,12 @@ module Float::Printer::Dragonbox end # Otherwise, compute the round-up of y - significand = compute_round_up_for_shorter_interval_case(cache, beta_minus_1) + significand = compute_round_up_for_shorter_interval_case(cache, beta) ret_exponent = minus_k # When tie occurs, choose one of them according to the rule. - if ImplInfo::SHORTER_INTERVAL_TIE_LOWER_THRESHOLD <= exponent <= ImplInfo::SHORTER_INTERVAL_TIE_UPPER_THRESHOLD - significand = break_rounding_tie(significand) + if prefer_round_down?(significand) && (ImplInfo::SHORTER_INTERVAL_TIE_LOWER_THRESHOLD <= exponent <= ImplInfo::SHORTER_INTERVAL_TIE_UPPER_THRESHOLD) + significand -= 1 elsif significand < xi significand += 1 end @@ -450,67 +432,80 @@ module Float::Printer::Dragonbox {significand, ret_exponent} end - def self.compute_mul(u, cache) + def self.compute_mul(u, cache) # : {result: ImplInfo::CarrierUInt, is_integer: Bool} {% if F == Float32 %} - WUInt.umul96_upper32(u, cache) + r = WUInt.umul96_upper64(u, cache) + { + ImplInfo::CarrierUInt.new!(r >> 32), + ImplInfo::CarrierUInt.new!(r) == 0, + } {% else %} # F == Float64 - WUInt.umul192_upper64(u, cache) + r = WUInt.umul192_upper128(u, cache) + {r.high, r.low == 0} {% end %} end - def self.compute_delta(cache, beta_minus_1) : UInt32 + def self.compute_delta(cache, beta) : UInt32 {% if F == Float32 %} - (cache >> (ImplInfo::CACHE_BITS - 1 - beta_minus_1)).to_u32! + (cache >> (ImplInfo::CACHE_BITS - 1 - beta)).to_u32! {% else %} # F == Float64 - (cache.high >> (ImplInfo::CARRIER_BITS - 1 - beta_minus_1)).to_u32! + (cache.high >> (ImplInfo::CARRIER_BITS - 1 - beta)).to_u32! {% end %} end - def self.compute_mul_parity(two_f, cache, beta_minus_1) : Bool + def self.compute_mul_parity(two_f, cache, beta) # : {parity: Bool, is_integer: Bool} {% if F == Float32 %} - ((WUInt.umul96_lower64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0 + r = WUInt.umul96_lower64(two_f, cache) + { + ((r >> (64 - beta)) & 1) != 0, + UInt32.new!(r >> (32 - beta)) == 0, + } {% else %} # F == Float64 - ((WUInt.umul192_middle64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0 + r = WUInt.umul192_lower128(two_f, cache) + { + ((r.high >> (64 - beta)) & 1) != 0, + (r.high << beta) | (r.low >> (64 - beta)) == 0, + } {% end %} end - def self.compute_left_endpoint_for_shorter_interval_case(cache, beta_minus_1) + def self.compute_left_endpoint_for_shorter_interval_case(cache, beta) significand_bits = ImplInfo::SIGNIFICAND_BITS ImplInfo::CarrierUInt.new!( {% if F == Float32 %} - (cache - (cache >> (significand_bits + 2))) >> (ImplInfo::CACHE_BITS - significand_bits - 1 - beta_minus_1) + (cache - (cache >> (significand_bits + 2))) >> (ImplInfo::CACHE_BITS - significand_bits - 1 - beta) {% else %} # F == Float64 - (cache.high - (cache.high >> (significand_bits + 2))) >> (ImplInfo::CARRIER_BITS - significand_bits - 1 - beta_minus_1) + (cache.high - (cache.high >> (significand_bits + 2))) >> (ImplInfo::CARRIER_BITS - significand_bits - 1 - beta) {% end %} ) end - def self.compute_right_endpoint_for_shorter_interval_case(cache, beta_minus_1) + def self.compute_right_endpoint_for_shorter_interval_case(cache, beta) significand_bits = ImplInfo::SIGNIFICAND_BITS ImplInfo::CarrierUInt.new!( {% if F == Float32 %} - (cache + (cache >> (significand_bits + 1))) >> (ImplInfo::CACHE_BITS - significand_bits - 1 - beta_minus_1) + (cache + (cache >> (significand_bits + 1))) >> (ImplInfo::CACHE_BITS - significand_bits - 1 - beta) {% else %} # F == Float64 - (cache.high + (cache.high >> (significand_bits + 1))) >> (ImplInfo::CARRIER_BITS - significand_bits - 1 - beta_minus_1) + (cache.high + (cache.high >> (significand_bits + 1))) >> (ImplInfo::CARRIER_BITS - significand_bits - 1 - beta) {% end %} ) end - def self.compute_round_up_for_shorter_interval_case(cache, beta_minus_1) + def self.compute_round_up_for_shorter_interval_case(cache, beta) significand_bits = ImplInfo::SIGNIFICAND_BITS {% if F == Float32 %} - (ImplInfo::CarrierUInt.new!(cache >> (ImplInfo::CACHE_BITS - significand_bits - 2 - beta_minus_1)) + 1) // 2 + (ImplInfo::CarrierUInt.new!(cache >> (ImplInfo::CACHE_BITS - significand_bits - 2 - beta)) + 1) // 2 {% else %} # F == Float64 - ((cache.high >> (ImplInfo::CARRIER_BITS - significand_bits - 2 - beta_minus_1)) + 1) // 2 + ((cache.high >> (ImplInfo::CARRIER_BITS - significand_bits - 2 - beta)) + 1) // 2 {% end %} end @@ -519,30 +514,6 @@ module Float::Printer::Dragonbox exponent <= ImplInfo::CASE_SHORTER_INTERVAL_LEFT_ENDPOINT_UPPER_THRESHOLD end - def self.is_product_integer_pm_half?(two_f, exponent, minus_k) - # Case I: f = fc +- 1/2 - - return false if exponent < ImplInfo::CASE_FC_PM_HALF_LOWER_THRESHOLD - # For k >= 0 - return true if exponent <= ImplInfo::CASE_FC_PM_HALF_UPPER_THRESHOLD - # For k < 0 - return false if exponent > ImplInfo::DIVISIBILITY_CHECK_BY_5_THRESHOLD - Div.divisible_by_power_of_5?(two_f, minus_k) - end - - def self.is_product_integer?(two_f, exponent, minus_k) - # Case II: f = fc + 1 - # Case III: f = fc - - # Exponent for 5 is negative - return false if exponent > ImplInfo::DIVISIBILITY_CHECK_BY_5_THRESHOLD - return Div.divisible_by_power_of_5?(two_f, minus_k) if exponent > ImplInfo::CASE_FC_UPPER_THRESHOLD - # Both exponents are nonnegative - return true if exponent >= ImplInfo::CASE_FC_LOWER_THRESHOLD - # Exponent for 2 is negative - Div.divisible_by_power_of_2?(two_f, minus_k - exponent + 1) - end - def self.to_decimal(signed_significand_bits, exponent_bits) two_fc = ImplInfo.remove_sign_bit_and_shift(signed_significand_bits) exponent = exponent_bits.to_i @@ -552,28 +523,27 @@ module Float::Printer::Dragonbox exponent += ImplInfo::EXPONENT_BIAS - ImplInfo::SIGNIFICAND_BITS # Shorter interval case; proceed like Schubfach. - # One might think this condition is wrong, - # since when exponent_bits == 1 and two_fc == 0, - # the interval is actullay regular. - # However, it turns out that this seemingly wrong condition - # is actually fine, because the end result is anyway the same. + # One might think this condition is wrong, since when exponent_bits == 1 + # and two_fc == 0, the interval is actually regular. However, it turns out + # that this seemingly wrong condition is actually fine, because the end + # result is anyway the same. # # [binary32] - # floor( (fc-1/2) * 2^e ) = 1.175'494'28... * 10^-38 - # floor( (fc-1/4) * 2^e ) = 1.175'494'31... * 10^-38 - # floor( fc * 2^e ) = 1.175'494'35... * 10^-38 - # floor( (fc+1/2) * 2^e ) = 1.175'494'42... * 10^-38 + # (fc-1/2) * 2^e = 1.175'494'28... * 10^-38 + # (fc-1/4) * 2^e = 1.175'494'31... * 10^-38 + # fc * 2^e = 1.175'494'35... * 10^-38 + # (fc+1/2) * 2^e = 1.175'494'42... * 10^-38 # # Hence, shorter_interval_case will return 1.175'494'4 * 10^-38. - # 1.175'494'3 * 10^-38 is also a correct shortest representation - # that will be rejected if we assume shorter interval, - # but 1.175'494'4 * 10^-38 is closer to the true value so it doesn't matter. + # 1.175'494'3 * 10^-38 is also a correct shortest representation that will + # be rejected if we assume shorter interval, but 1.175'494'4 * 10^-38 is + # closer to the true value so it doesn't matter. # # [binary64] - # floor( (fc-1/2) * 2^e ) = 2.225'073'858'507'201'13... * 10^-308 - # floor( (fc-1/4) * 2^e ) = 2.225'073'858'507'201'25... * 10^-308 - # floor( fc * 2^e ) = 2.225'073'858'507'201'38... * 10^-308 - # floor( (fc+1/2) * 2^e ) = 2.225'073'858'507'201'63... * 10^-308 + # (fc-1/2) * 2^e = 2.225'073'858'507'201'13... * 10^-308 + # (fc-1/4) * 2^e = 2.225'073'858'507'201'25... * 10^-308 + # fc * 2^e = 2.225'073'858'507'201'38... * 10^-308 + # (fc+1/2) * 2^e = 2.225'073'858'507'201'63... * 10^-308 # # Hence, shorter_interval_case will return 2.225'073'858'507'201'4 * 10^-308. # This is indeed of the shortest length, and it is the unique one diff --git a/src/float/printer/dragonbox_cache.cr b/src/float/printer/dragonbox_cache.cr index 211cd3fd4d18..eec330246d19 100644 --- a/src/float/printer/dragonbox_cache.cr +++ b/src/float/printer/dragonbox_cache.cr @@ -67,25 +67,25 @@ module Float::Printer::Dragonbox 0x84595161401484a0_u64, 0xa56fa5b99019a5c8_u64, 0xcecb8f27f4200f3a_u64, - 0x813f3978f8940984_u64, - 0xa18f07d736b90be5_u64, - 0xc9f2c9cd04674ede_u64, - 0xfc6f7c4045812296_u64, - 0x9dc5ada82b70b59d_u64, - 0xc5371912364ce305_u64, - 0xf684df56c3e01bc6_u64, - 0x9a130b963a6c115c_u64, - 0xc097ce7bc90715b3_u64, - 0xf0bdc21abb48db20_u64, - 0x96769950b50d88f4_u64, - 0xbc143fa4e250eb31_u64, - 0xeb194f8e1ae525fd_u64, - 0x92efd1b8d0cf37be_u64, - 0xb7abc627050305ad_u64, - 0xe596b7b0c643c719_u64, - 0x8f7e32ce7bea5c6f_u64, - 0xb35dbf821ae4f38b_u64, - 0xe0352f62a19e306e_u64, + 0x813f3978f8940985_u64, + 0xa18f07d736b90be6_u64, + 0xc9f2c9cd04674edf_u64, + 0xfc6f7c4045812297_u64, + 0x9dc5ada82b70b59e_u64, + 0xc5371912364ce306_u64, + 0xf684df56c3e01bc7_u64, + 0x9a130b963a6c115d_u64, + 0xc097ce7bc90715b4_u64, + 0xf0bdc21abb48db21_u64, + 0x96769950b50d88f5_u64, + 0xbc143fa4e250eb32_u64, + 0xeb194f8e1ae525fe_u64, + 0x92efd1b8d0cf37bf_u64, + 0xb7abc627050305ae_u64, + 0xe596b7b0c643c71a_u64, + 0x8f7e32ce7bea5c70_u64, + 0xb35dbf821ae4f38c_u64, + 0xe0352f62a19e306f_u64, ] end @@ -448,277 +448,277 @@ module Float::Printer::Dragonbox put(cache, 0x85a36366eb71f041_u64, 0x47a6da2b7f864750_u64) put(cache, 0xa70c3c40a64e6c51_u64, 0x999090b65f67d924_u64) put(cache, 0xd0cf4b50cfe20765_u64, 0xfff4b4e3f741cf6d_u64) - put(cache, 0x82818f1281ed449f_u64, 0xbff8f10e7a8921a4_u64) - put(cache, 0xa321f2d7226895c7_u64, 0xaff72d52192b6a0d_u64) - put(cache, 0xcbea6f8ceb02bb39_u64, 0x9bf4f8a69f764490_u64) - put(cache, 0xfee50b7025c36a08_u64, 0x02f236d04753d5b4_u64) - put(cache, 0x9f4f2726179a2245_u64, 0x01d762422c946590_u64) - put(cache, 0xc722f0ef9d80aad6_u64, 0x424d3ad2b7b97ef5_u64) - put(cache, 0xf8ebad2b84e0d58b_u64, 0xd2e0898765a7deb2_u64) - put(cache, 0x9b934c3b330c8577_u64, 0x63cc55f49f88eb2f_u64) - put(cache, 0xc2781f49ffcfa6d5_u64, 0x3cbf6b71c76b25fb_u64) - put(cache, 0xf316271c7fc3908a_u64, 0x8bef464e3945ef7a_u64) - put(cache, 0x97edd871cfda3a56_u64, 0x97758bf0e3cbb5ac_u64) - put(cache, 0xbde94e8e43d0c8ec_u64, 0x3d52eeed1cbea317_u64) - put(cache, 0xed63a231d4c4fb27_u64, 0x4ca7aaa863ee4bdd_u64) - put(cache, 0x945e455f24fb1cf8_u64, 0x8fe8caa93e74ef6a_u64) - put(cache, 0xb975d6b6ee39e436_u64, 0xb3e2fd538e122b44_u64) - put(cache, 0xe7d34c64a9c85d44_u64, 0x60dbbca87196b616_u64) - put(cache, 0x90e40fbeea1d3a4a_u64, 0xbc8955e946fe31cd_u64) - put(cache, 0xb51d13aea4a488dd_u64, 0x6babab6398bdbe41_u64) - put(cache, 0xe264589a4dcdab14_u64, 0xc696963c7eed2dd1_u64) - put(cache, 0x8d7eb76070a08aec_u64, 0xfc1e1de5cf543ca2_u64) - put(cache, 0xb0de65388cc8ada8_u64, 0x3b25a55f43294bcb_u64) - put(cache, 0xdd15fe86affad912_u64, 0x49ef0eb713f39ebe_u64) - put(cache, 0x8a2dbf142dfcc7ab_u64, 0x6e3569326c784337_u64) - put(cache, 0xacb92ed9397bf996_u64, 0x49c2c37f07965404_u64) - put(cache, 0xd7e77a8f87daf7fb_u64, 0xdc33745ec97be906_u64) - put(cache, 0x86f0ac99b4e8dafd_u64, 0x69a028bb3ded71a3_u64) - put(cache, 0xa8acd7c0222311bc_u64, 0xc40832ea0d68ce0c_u64) - put(cache, 0xd2d80db02aabd62b_u64, 0xf50a3fa490c30190_u64) - put(cache, 0x83c7088e1aab65db_u64, 0x792667c6da79e0fa_u64) - put(cache, 0xa4b8cab1a1563f52_u64, 0x577001b891185938_u64) - put(cache, 0xcde6fd5e09abcf26_u64, 0xed4c0226b55e6f86_u64) - put(cache, 0x80b05e5ac60b6178_u64, 0x544f8158315b05b4_u64) - put(cache, 0xa0dc75f1778e39d6_u64, 0x696361ae3db1c721_u64) - put(cache, 0xc913936dd571c84c_u64, 0x03bc3a19cd1e38e9_u64) - put(cache, 0xfb5878494ace3a5f_u64, 0x04ab48a04065c723_u64) - put(cache, 0x9d174b2dcec0e47b_u64, 0x62eb0d64283f9c76_u64) - put(cache, 0xc45d1df942711d9a_u64, 0x3ba5d0bd324f8394_u64) - put(cache, 0xf5746577930d6500_u64, 0xca8f44ec7ee36479_u64) - put(cache, 0x9968bf6abbe85f20_u64, 0x7e998b13cf4e1ecb_u64) - put(cache, 0xbfc2ef456ae276e8_u64, 0x9e3fedd8c321a67e_u64) - put(cache, 0xefb3ab16c59b14a2_u64, 0xc5cfe94ef3ea101e_u64) - put(cache, 0x95d04aee3b80ece5_u64, 0xbba1f1d158724a12_u64) - put(cache, 0xbb445da9ca61281f_u64, 0x2a8a6e45ae8edc97_u64) - put(cache, 0xea1575143cf97226_u64, 0xf52d09d71a3293bd_u64) - put(cache, 0x924d692ca61be758_u64, 0x593c2626705f9c56_u64) - put(cache, 0xb6e0c377cfa2e12e_u64, 0x6f8b2fb00c77836c_u64) - put(cache, 0xe498f455c38b997a_u64, 0x0b6dfb9c0f956447_u64) - put(cache, 0x8edf98b59a373fec_u64, 0x4724bd4189bd5eac_u64) - put(cache, 0xb2977ee300c50fe7_u64, 0x58edec91ec2cb657_u64) - put(cache, 0xdf3d5e9bc0f653e1_u64, 0x2f2967b66737e3ed_u64) - put(cache, 0x8b865b215899f46c_u64, 0xbd79e0d20082ee74_u64) - put(cache, 0xae67f1e9aec07187_u64, 0xecd8590680a3aa11_u64) - put(cache, 0xda01ee641a708de9_u64, 0xe80e6f4820cc9495_u64) - put(cache, 0x884134fe908658b2_u64, 0x3109058d147fdcdd_u64) - put(cache, 0xaa51823e34a7eede_u64, 0xbd4b46f0599fd415_u64) - put(cache, 0xd4e5e2cdc1d1ea96_u64, 0x6c9e18ac7007c91a_u64) - put(cache, 0x850fadc09923329e_u64, 0x03e2cf6bc604ddb0_u64) - put(cache, 0xa6539930bf6bff45_u64, 0x84db8346b786151c_u64) - put(cache, 0xcfe87f7cef46ff16_u64, 0xe612641865679a63_u64) - put(cache, 0x81f14fae158c5f6e_u64, 0x4fcb7e8f3f60c07e_u64) - put(cache, 0xa26da3999aef7749_u64, 0xe3be5e330f38f09d_u64) - put(cache, 0xcb090c8001ab551c_u64, 0x5cadf5bfd3072cc5_u64) - put(cache, 0xfdcb4fa002162a63_u64, 0x73d9732fc7c8f7f6_u64) - put(cache, 0x9e9f11c4014dda7e_u64, 0x2867e7fddcdd9afa_u64) - put(cache, 0xc646d63501a1511d_u64, 0xb281e1fd541501b8_u64) - put(cache, 0xf7d88bc24209a565_u64, 0x1f225a7ca91a4226_u64) - put(cache, 0x9ae757596946075f_u64, 0x3375788de9b06958_u64) - put(cache, 0xc1a12d2fc3978937_u64, 0x0052d6b1641c83ae_u64) - put(cache, 0xf209787bb47d6b84_u64, 0xc0678c5dbd23a49a_u64) - put(cache, 0x9745eb4d50ce6332_u64, 0xf840b7ba963646e0_u64) - put(cache, 0xbd176620a501fbff_u64, 0xb650e5a93bc3d898_u64) - put(cache, 0xec5d3fa8ce427aff_u64, 0xa3e51f138ab4cebe_u64) - put(cache, 0x93ba47c980e98cdf_u64, 0xc66f336c36b10137_u64) - put(cache, 0xb8a8d9bbe123f017_u64, 0xb80b0047445d4184_u64) - put(cache, 0xe6d3102ad96cec1d_u64, 0xa60dc059157491e5_u64) - put(cache, 0x9043ea1ac7e41392_u64, 0x87c89837ad68db2f_u64) - put(cache, 0xb454e4a179dd1877_u64, 0x29babe4598c311fb_u64) - put(cache, 0xe16a1dc9d8545e94_u64, 0xf4296dd6fef3d67a_u64) - put(cache, 0x8ce2529e2734bb1d_u64, 0x1899e4a65f58660c_u64) - put(cache, 0xb01ae745b101e9e4_u64, 0x5ec05dcff72e7f8f_u64) - put(cache, 0xdc21a1171d42645d_u64, 0x76707543f4fa1f73_u64) - put(cache, 0x899504ae72497eba_u64, 0x6a06494a791c53a8_u64) - put(cache, 0xabfa45da0edbde69_u64, 0x0487db9d17636892_u64) - put(cache, 0xd6f8d7509292d603_u64, 0x45a9d2845d3c42b6_u64) - put(cache, 0x865b86925b9bc5c2_u64, 0x0b8a2392ba45a9b2_u64) - put(cache, 0xa7f26836f282b732_u64, 0x8e6cac7768d7141e_u64) - put(cache, 0xd1ef0244af2364ff_u64, 0x3207d795430cd926_u64) - put(cache, 0x8335616aed761f1f_u64, 0x7f44e6bd49e807b8_u64) - put(cache, 0xa402b9c5a8d3a6e7_u64, 0x5f16206c9c6209a6_u64) - put(cache, 0xcd036837130890a1_u64, 0x36dba887c37a8c0f_u64) - put(cache, 0x802221226be55a64_u64, 0xc2494954da2c9789_u64) - put(cache, 0xa02aa96b06deb0fd_u64, 0xf2db9baa10b7bd6c_u64) - put(cache, 0xc83553c5c8965d3d_u64, 0x6f92829494e5acc7_u64) - put(cache, 0xfa42a8b73abbf48c_u64, 0xcb772339ba1f17f9_u64) - put(cache, 0x9c69a97284b578d7_u64, 0xff2a760414536efb_u64) - put(cache, 0xc38413cf25e2d70d_u64, 0xfef5138519684aba_u64) - put(cache, 0xf46518c2ef5b8cd1_u64, 0x7eb258665fc25d69_u64) - put(cache, 0x98bf2f79d5993802_u64, 0xef2f773ffbd97a61_u64) - put(cache, 0xbeeefb584aff8603_u64, 0xaafb550ffacfd8fa_u64) - put(cache, 0xeeaaba2e5dbf6784_u64, 0x95ba2a53f983cf38_u64) - put(cache, 0x952ab45cfa97a0b2_u64, 0xdd945a747bf26183_u64) - put(cache, 0xba756174393d88df_u64, 0x94f971119aeef9e4_u64) - put(cache, 0xe912b9d1478ceb17_u64, 0x7a37cd5601aab85d_u64) - put(cache, 0x91abb422ccb812ee_u64, 0xac62e055c10ab33a_u64) - put(cache, 0xb616a12b7fe617aa_u64, 0x577b986b314d6009_u64) - put(cache, 0xe39c49765fdf9d94_u64, 0xed5a7e85fda0b80b_u64) - put(cache, 0x8e41ade9fbebc27d_u64, 0x14588f13be847307_u64) - put(cache, 0xb1d219647ae6b31c_u64, 0x596eb2d8ae258fc8_u64) - put(cache, 0xde469fbd99a05fe3_u64, 0x6fca5f8ed9aef3bb_u64) - put(cache, 0x8aec23d680043bee_u64, 0x25de7bb9480d5854_u64) - put(cache, 0xada72ccc20054ae9_u64, 0xaf561aa79a10ae6a_u64) - put(cache, 0xd910f7ff28069da4_u64, 0x1b2ba1518094da04_u64) - put(cache, 0x87aa9aff79042286_u64, 0x90fb44d2f05d0842_u64) - put(cache, 0xa99541bf57452b28_u64, 0x353a1607ac744a53_u64) - put(cache, 0xd3fa922f2d1675f2_u64, 0x42889b8997915ce8_u64) - put(cache, 0x847c9b5d7c2e09b7_u64, 0x69956135febada11_u64) - put(cache, 0xa59bc234db398c25_u64, 0x43fab9837e699095_u64) - put(cache, 0xcf02b2c21207ef2e_u64, 0x94f967e45e03f4bb_u64) - put(cache, 0x8161afb94b44f57d_u64, 0x1d1be0eebac278f5_u64) - put(cache, 0xa1ba1ba79e1632dc_u64, 0x6462d92a69731732_u64) - put(cache, 0xca28a291859bbf93_u64, 0x7d7b8f7503cfdcfe_u64) - put(cache, 0xfcb2cb35e702af78_u64, 0x5cda735244c3d43e_u64) - put(cache, 0x9defbf01b061adab_u64, 0x3a0888136afa64a7_u64) - put(cache, 0xc56baec21c7a1916_u64, 0x088aaa1845b8fdd0_u64) - put(cache, 0xf6c69a72a3989f5b_u64, 0x8aad549e57273d45_u64) - put(cache, 0x9a3c2087a63f6399_u64, 0x36ac54e2f678864b_u64) - put(cache, 0xc0cb28a98fcf3c7f_u64, 0x84576a1bb416a7dd_u64) - put(cache, 0xf0fdf2d3f3c30b9f_u64, 0x656d44a2a11c51d5_u64) - put(cache, 0x969eb7c47859e743_u64, 0x9f644ae5a4b1b325_u64) - put(cache, 0xbc4665b596706114_u64, 0x873d5d9f0dde1fee_u64) - put(cache, 0xeb57ff22fc0c7959_u64, 0xa90cb506d155a7ea_u64) - put(cache, 0x9316ff75dd87cbd8_u64, 0x09a7f12442d588f2_u64) - put(cache, 0xb7dcbf5354e9bece_u64, 0x0c11ed6d538aeb2f_u64) - put(cache, 0xe5d3ef282a242e81_u64, 0x8f1668c8a86da5fa_u64) - put(cache, 0x8fa475791a569d10_u64, 0xf96e017d694487bc_u64) - put(cache, 0xb38d92d760ec4455_u64, 0x37c981dcc395a9ac_u64) - put(cache, 0xe070f78d3927556a_u64, 0x85bbe253f47b1417_u64) - put(cache, 0x8c469ab843b89562_u64, 0x93956d7478ccec8e_u64) - put(cache, 0xaf58416654a6babb_u64, 0x387ac8d1970027b2_u64) - put(cache, 0xdb2e51bfe9d0696a_u64, 0x06997b05fcc0319e_u64) - put(cache, 0x88fcf317f22241e2_u64, 0x441fece3bdf81f03_u64) - put(cache, 0xab3c2fddeeaad25a_u64, 0xd527e81cad7626c3_u64) - put(cache, 0xd60b3bd56a5586f1_u64, 0x8a71e223d8d3b074_u64) - put(cache, 0x85c7056562757456_u64, 0xf6872d5667844e49_u64) - put(cache, 0xa738c6bebb12d16c_u64, 0xb428f8ac016561db_u64) - put(cache, 0xd106f86e69d785c7_u64, 0xe13336d701beba52_u64) - put(cache, 0x82a45b450226b39c_u64, 0xecc0024661173473_u64) - put(cache, 0xa34d721642b06084_u64, 0x27f002d7f95d0190_u64) - put(cache, 0xcc20ce9bd35c78a5_u64, 0x31ec038df7b441f4_u64) - put(cache, 0xff290242c83396ce_u64, 0x7e67047175a15271_u64) - put(cache, 0x9f79a169bd203e41_u64, 0x0f0062c6e984d386_u64) - put(cache, 0xc75809c42c684dd1_u64, 0x52c07b78a3e60868_u64) - put(cache, 0xf92e0c3537826145_u64, 0xa7709a56ccdf8a82_u64) - put(cache, 0x9bbcc7a142b17ccb_u64, 0x88a66076400bb691_u64) - put(cache, 0xc2abf989935ddbfe_u64, 0x6acff893d00ea435_u64) - put(cache, 0xf356f7ebf83552fe_u64, 0x0583f6b8c4124d43_u64) - put(cache, 0x98165af37b2153de_u64, 0xc3727a337a8b704a_u64) - put(cache, 0xbe1bf1b059e9a8d6_u64, 0x744f18c0592e4c5c_u64) - put(cache, 0xeda2ee1c7064130c_u64, 0x1162def06f79df73_u64) - put(cache, 0x9485d4d1c63e8be7_u64, 0x8addcb5645ac2ba8_u64) - put(cache, 0xb9a74a0637ce2ee1_u64, 0x6d953e2bd7173692_u64) - put(cache, 0xe8111c87c5c1ba99_u64, 0xc8fa8db6ccdd0437_u64) - put(cache, 0x910ab1d4db9914a0_u64, 0x1d9c9892400a22a2_u64) - put(cache, 0xb54d5e4a127f59c8_u64, 0x2503beb6d00cab4b_u64) - put(cache, 0xe2a0b5dc971f303a_u64, 0x2e44ae64840fd61d_u64) - put(cache, 0x8da471a9de737e24_u64, 0x5ceaecfed289e5d2_u64) - put(cache, 0xb10d8e1456105dad_u64, 0x7425a83e872c5f47_u64) - put(cache, 0xdd50f1996b947518_u64, 0xd12f124e28f77719_u64) - put(cache, 0x8a5296ffe33cc92f_u64, 0x82bd6b70d99aaa6f_u64) - put(cache, 0xace73cbfdc0bfb7b_u64, 0x636cc64d1001550b_u64) - put(cache, 0xd8210befd30efa5a_u64, 0x3c47f7e05401aa4e_u64) - put(cache, 0x8714a775e3e95c78_u64, 0x65acfaec34810a71_u64) - put(cache, 0xa8d9d1535ce3b396_u64, 0x7f1839a741a14d0d_u64) - put(cache, 0xd31045a8341ca07c_u64, 0x1ede48111209a050_u64) - put(cache, 0x83ea2b892091e44d_u64, 0x934aed0aab460432_u64) - put(cache, 0xa4e4b66b68b65d60_u64, 0xf81da84d5617853f_u64) - put(cache, 0xce1de40642e3f4b9_u64, 0x36251260ab9d668e_u64) - put(cache, 0x80d2ae83e9ce78f3_u64, 0xc1d72b7c6b426019_u64) - put(cache, 0xa1075a24e4421730_u64, 0xb24cf65b8612f81f_u64) - put(cache, 0xc94930ae1d529cfc_u64, 0xdee033f26797b627_u64) - put(cache, 0xfb9b7cd9a4a7443c_u64, 0x169840ef017da3b1_u64) - put(cache, 0x9d412e0806e88aa5_u64, 0x8e1f289560ee864e_u64) - put(cache, 0xc491798a08a2ad4e_u64, 0xf1a6f2bab92a27e2_u64) - put(cache, 0xf5b5d7ec8acb58a2_u64, 0xae10af696774b1db_u64) - put(cache, 0x9991a6f3d6bf1765_u64, 0xacca6da1e0a8ef29_u64) - put(cache, 0xbff610b0cc6edd3f_u64, 0x17fd090a58d32af3_u64) - put(cache, 0xeff394dcff8a948e_u64, 0xddfc4b4cef07f5b0_u64) - put(cache, 0x95f83d0a1fb69cd9_u64, 0x4abdaf101564f98e_u64) - put(cache, 0xbb764c4ca7a4440f_u64, 0x9d6d1ad41abe37f1_u64) - put(cache, 0xea53df5fd18d5513_u64, 0x84c86189216dc5ed_u64) - put(cache, 0x92746b9be2f8552c_u64, 0x32fd3cf5b4e49bb4_u64) - put(cache, 0xb7118682dbb66a77_u64, 0x3fbc8c33221dc2a1_u64) - put(cache, 0xe4d5e82392a40515_u64, 0x0fabaf3feaa5334a_u64) - put(cache, 0x8f05b1163ba6832d_u64, 0x29cb4d87f2a7400e_u64) - put(cache, 0xb2c71d5bca9023f8_u64, 0x743e20e9ef511012_u64) - put(cache, 0xdf78e4b2bd342cf6_u64, 0x914da9246b255416_u64) - put(cache, 0x8bab8eefb6409c1a_u64, 0x1ad089b6c2f7548e_u64) - put(cache, 0xae9672aba3d0c320_u64, 0xa184ac2473b529b1_u64) - put(cache, 0xda3c0f568cc4f3e8_u64, 0xc9e5d72d90a2741e_u64) - put(cache, 0x8865899617fb1871_u64, 0x7e2fa67c7a658892_u64) - put(cache, 0xaa7eebfb9df9de8d_u64, 0xddbb901b98feeab7_u64) - put(cache, 0xd51ea6fa85785631_u64, 0x552a74227f3ea565_u64) - put(cache, 0x8533285c936b35de_u64, 0xd53a88958f87275f_u64) - put(cache, 0xa67ff273b8460356_u64, 0x8a892abaf368f137_u64) - put(cache, 0xd01fef10a657842c_u64, 0x2d2b7569b0432d85_u64) - put(cache, 0x8213f56a67f6b29b_u64, 0x9c3b29620e29fc73_u64) - put(cache, 0xa298f2c501f45f42_u64, 0x8349f3ba91b47b8f_u64) - put(cache, 0xcb3f2f7642717713_u64, 0x241c70a936219a73_u64) - put(cache, 0xfe0efb53d30dd4d7_u64, 0xed238cd383aa0110_u64) - put(cache, 0x9ec95d1463e8a506_u64, 0xf4363804324a40aa_u64) - put(cache, 0xc67bb4597ce2ce48_u64, 0xb143c6053edcd0d5_u64) - put(cache, 0xf81aa16fdc1b81da_u64, 0xdd94b7868e94050a_u64) - put(cache, 0x9b10a4e5e9913128_u64, 0xca7cf2b4191c8326_u64) - put(cache, 0xc1d4ce1f63f57d72_u64, 0xfd1c2f611f63a3f0_u64) - put(cache, 0xf24a01a73cf2dccf_u64, 0xbc633b39673c8cec_u64) - put(cache, 0x976e41088617ca01_u64, 0xd5be0503e085d813_u64) - put(cache, 0xbd49d14aa79dbc82_u64, 0x4b2d8644d8a74e18_u64) - put(cache, 0xec9c459d51852ba2_u64, 0xddf8e7d60ed1219e_u64) - put(cache, 0x93e1ab8252f33b45_u64, 0xcabb90e5c942b503_u64) - put(cache, 0xb8da1662e7b00a17_u64, 0x3d6a751f3b936243_u64) - put(cache, 0xe7109bfba19c0c9d_u64, 0x0cc512670a783ad4_u64) - put(cache, 0x906a617d450187e2_u64, 0x27fb2b80668b24c5_u64) - put(cache, 0xb484f9dc9641e9da_u64, 0xb1f9f660802dedf6_u64) - put(cache, 0xe1a63853bbd26451_u64, 0x5e7873f8a0396973_u64) - put(cache, 0x8d07e33455637eb2_u64, 0xdb0b487b6423e1e8_u64) - put(cache, 0xb049dc016abc5e5f_u64, 0x91ce1a9a3d2cda62_u64) - put(cache, 0xdc5c5301c56b75f7_u64, 0x7641a140cc7810fb_u64) - put(cache, 0x89b9b3e11b6329ba_u64, 0xa9e904c87fcb0a9d_u64) - put(cache, 0xac2820d9623bf429_u64, 0x546345fa9fbdcd44_u64) - put(cache, 0xd732290fbacaf133_u64, 0xa97c177947ad4095_u64) - put(cache, 0x867f59a9d4bed6c0_u64, 0x49ed8eabcccc485d_u64) - put(cache, 0xa81f301449ee8c70_u64, 0x5c68f256bfff5a74_u64) - put(cache, 0xd226fc195c6a2f8c_u64, 0x73832eec6fff3111_u64) - put(cache, 0x83585d8fd9c25db7_u64, 0xc831fd53c5ff7eab_u64) - put(cache, 0xa42e74f3d032f525_u64, 0xba3e7ca8b77f5e55_u64) - put(cache, 0xcd3a1230c43fb26f_u64, 0x28ce1bd2e55f35eb_u64) - put(cache, 0x80444b5e7aa7cf85_u64, 0x7980d163cf5b81b3_u64) - put(cache, 0xa0555e361951c366_u64, 0xd7e105bcc332621f_u64) - put(cache, 0xc86ab5c39fa63440_u64, 0x8dd9472bf3fefaa7_u64) - put(cache, 0xfa856334878fc150_u64, 0xb14f98f6f0feb951_u64) - put(cache, 0x9c935e00d4b9d8d2_u64, 0x6ed1bf9a569f33d3_u64) - put(cache, 0xc3b8358109e84f07_u64, 0x0a862f80ec4700c8_u64) - put(cache, 0xf4a642e14c6262c8_u64, 0xcd27bb612758c0fa_u64) - put(cache, 0x98e7e9cccfbd7dbd_u64, 0x8038d51cb897789c_u64) - put(cache, 0xbf21e44003acdd2c_u64, 0xe0470a63e6bd56c3_u64) - put(cache, 0xeeea5d5004981478_u64, 0x1858ccfce06cac74_u64) - put(cache, 0x95527a5202df0ccb_u64, 0x0f37801e0c43ebc8_u64) - put(cache, 0xbaa718e68396cffd_u64, 0xd30560258f54e6ba_u64) - put(cache, 0xe950df20247c83fd_u64, 0x47c6b82ef32a2069_u64) - put(cache, 0x91d28b7416cdd27e_u64, 0x4cdc331d57fa5441_u64) - put(cache, 0xb6472e511c81471d_u64, 0xe0133fe4adf8e952_u64) - put(cache, 0xe3d8f9e563a198e5_u64, 0x58180fddd97723a6_u64) - put(cache, 0x8e679c2f5e44ff8f_u64, 0x570f09eaa7ea7648_u64) - put(cache, 0xb201833b35d63f73_u64, 0x2cd2cc6551e513da_u64) - put(cache, 0xde81e40a034bcf4f_u64, 0xf8077f7ea65e58d1_u64) - put(cache, 0x8b112e86420f6191_u64, 0xfb04afaf27faf782_u64) - put(cache, 0xadd57a27d29339f6_u64, 0x79c5db9af1f9b563_u64) - put(cache, 0xd94ad8b1c7380874_u64, 0x18375281ae7822bc_u64) - put(cache, 0x87cec76f1c830548_u64, 0x8f2293910d0b15b5_u64) - put(cache, 0xa9c2794ae3a3c69a_u64, 0xb2eb3875504ddb22_u64) - put(cache, 0xd433179d9c8cb841_u64, 0x5fa60692a46151eb_u64) - put(cache, 0x849feec281d7f328_u64, 0xdbc7c41ba6bcd333_u64) - put(cache, 0xa5c7ea73224deff3_u64, 0x12b9b522906c0800_u64) - put(cache, 0xcf39e50feae16bef_u64, 0xd768226b34870a00_u64) - put(cache, 0x81842f29f2cce375_u64, 0xe6a1158300d46640_u64) - put(cache, 0xa1e53af46f801c53_u64, 0x60495ae3c1097fd0_u64) - put(cache, 0xca5e89b18b602368_u64, 0x385bb19cb14bdfc4_u64) - put(cache, 0xfcf62c1dee382c42_u64, 0x46729e03dd9ed7b5_u64) - put(cache, 0x9e19db92b4e31ba9_u64, 0x6c07a2c26a8346d1_u64) - put(cache, 0xc5a05277621be293_u64, 0xc7098b7305241885_u64) - put(cache, 0xf70867153aa2db38_u64, 0xb8cbee4fc66d1ea7_u64) + put(cache, 0x82818f1281ed449f_u64, 0xbff8f10e7a8921a5_u64) + put(cache, 0xa321f2d7226895c7_u64, 0xaff72d52192b6a0e_u64) + put(cache, 0xcbea6f8ceb02bb39_u64, 0x9bf4f8a69f764491_u64) + put(cache, 0xfee50b7025c36a08_u64, 0x02f236d04753d5b5_u64) + put(cache, 0x9f4f2726179a2245_u64, 0x01d762422c946591_u64) + put(cache, 0xc722f0ef9d80aad6_u64, 0x424d3ad2b7b97ef6_u64) + put(cache, 0xf8ebad2b84e0d58b_u64, 0xd2e0898765a7deb3_u64) + put(cache, 0x9b934c3b330c8577_u64, 0x63cc55f49f88eb30_u64) + put(cache, 0xc2781f49ffcfa6d5_u64, 0x3cbf6b71c76b25fc_u64) + put(cache, 0xf316271c7fc3908a_u64, 0x8bef464e3945ef7b_u64) + put(cache, 0x97edd871cfda3a56_u64, 0x97758bf0e3cbb5ad_u64) + put(cache, 0xbde94e8e43d0c8ec_u64, 0x3d52eeed1cbea318_u64) + put(cache, 0xed63a231d4c4fb27_u64, 0x4ca7aaa863ee4bde_u64) + put(cache, 0x945e455f24fb1cf8_u64, 0x8fe8caa93e74ef6b_u64) + put(cache, 0xb975d6b6ee39e436_u64, 0xb3e2fd538e122b45_u64) + put(cache, 0xe7d34c64a9c85d44_u64, 0x60dbbca87196b617_u64) + put(cache, 0x90e40fbeea1d3a4a_u64, 0xbc8955e946fe31ce_u64) + put(cache, 0xb51d13aea4a488dd_u64, 0x6babab6398bdbe42_u64) + put(cache, 0xe264589a4dcdab14_u64, 0xc696963c7eed2dd2_u64) + put(cache, 0x8d7eb76070a08aec_u64, 0xfc1e1de5cf543ca3_u64) + put(cache, 0xb0de65388cc8ada8_u64, 0x3b25a55f43294bcc_u64) + put(cache, 0xdd15fe86affad912_u64, 0x49ef0eb713f39ebf_u64) + put(cache, 0x8a2dbf142dfcc7ab_u64, 0x6e3569326c784338_u64) + put(cache, 0xacb92ed9397bf996_u64, 0x49c2c37f07965405_u64) + put(cache, 0xd7e77a8f87daf7fb_u64, 0xdc33745ec97be907_u64) + put(cache, 0x86f0ac99b4e8dafd_u64, 0x69a028bb3ded71a4_u64) + put(cache, 0xa8acd7c0222311bc_u64, 0xc40832ea0d68ce0d_u64) + put(cache, 0xd2d80db02aabd62b_u64, 0xf50a3fa490c30191_u64) + put(cache, 0x83c7088e1aab65db_u64, 0x792667c6da79e0fb_u64) + put(cache, 0xa4b8cab1a1563f52_u64, 0x577001b891185939_u64) + put(cache, 0xcde6fd5e09abcf26_u64, 0xed4c0226b55e6f87_u64) + put(cache, 0x80b05e5ac60b6178_u64, 0x544f8158315b05b5_u64) + put(cache, 0xa0dc75f1778e39d6_u64, 0x696361ae3db1c722_u64) + put(cache, 0xc913936dd571c84c_u64, 0x03bc3a19cd1e38ea_u64) + put(cache, 0xfb5878494ace3a5f_u64, 0x04ab48a04065c724_u64) + put(cache, 0x9d174b2dcec0e47b_u64, 0x62eb0d64283f9c77_u64) + put(cache, 0xc45d1df942711d9a_u64, 0x3ba5d0bd324f8395_u64) + put(cache, 0xf5746577930d6500_u64, 0xca8f44ec7ee3647a_u64) + put(cache, 0x9968bf6abbe85f20_u64, 0x7e998b13cf4e1ecc_u64) + put(cache, 0xbfc2ef456ae276e8_u64, 0x9e3fedd8c321a67f_u64) + put(cache, 0xefb3ab16c59b14a2_u64, 0xc5cfe94ef3ea101f_u64) + put(cache, 0x95d04aee3b80ece5_u64, 0xbba1f1d158724a13_u64) + put(cache, 0xbb445da9ca61281f_u64, 0x2a8a6e45ae8edc98_u64) + put(cache, 0xea1575143cf97226_u64, 0xf52d09d71a3293be_u64) + put(cache, 0x924d692ca61be758_u64, 0x593c2626705f9c57_u64) + put(cache, 0xb6e0c377cfa2e12e_u64, 0x6f8b2fb00c77836d_u64) + put(cache, 0xe498f455c38b997a_u64, 0x0b6dfb9c0f956448_u64) + put(cache, 0x8edf98b59a373fec_u64, 0x4724bd4189bd5ead_u64) + put(cache, 0xb2977ee300c50fe7_u64, 0x58edec91ec2cb658_u64) + put(cache, 0xdf3d5e9bc0f653e1_u64, 0x2f2967b66737e3ee_u64) + put(cache, 0x8b865b215899f46c_u64, 0xbd79e0d20082ee75_u64) + put(cache, 0xae67f1e9aec07187_u64, 0xecd8590680a3aa12_u64) + put(cache, 0xda01ee641a708de9_u64, 0xe80e6f4820cc9496_u64) + put(cache, 0x884134fe908658b2_u64, 0x3109058d147fdcde_u64) + put(cache, 0xaa51823e34a7eede_u64, 0xbd4b46f0599fd416_u64) + put(cache, 0xd4e5e2cdc1d1ea96_u64, 0x6c9e18ac7007c91b_u64) + put(cache, 0x850fadc09923329e_u64, 0x03e2cf6bc604ddb1_u64) + put(cache, 0xa6539930bf6bff45_u64, 0x84db8346b786151d_u64) + put(cache, 0xcfe87f7cef46ff16_u64, 0xe612641865679a64_u64) + put(cache, 0x81f14fae158c5f6e_u64, 0x4fcb7e8f3f60c07f_u64) + put(cache, 0xa26da3999aef7749_u64, 0xe3be5e330f38f09e_u64) + put(cache, 0xcb090c8001ab551c_u64, 0x5cadf5bfd3072cc6_u64) + put(cache, 0xfdcb4fa002162a63_u64, 0x73d9732fc7c8f7f7_u64) + put(cache, 0x9e9f11c4014dda7e_u64, 0x2867e7fddcdd9afb_u64) + put(cache, 0xc646d63501a1511d_u64, 0xb281e1fd541501b9_u64) + put(cache, 0xf7d88bc24209a565_u64, 0x1f225a7ca91a4227_u64) + put(cache, 0x9ae757596946075f_u64, 0x3375788de9b06959_u64) + put(cache, 0xc1a12d2fc3978937_u64, 0x0052d6b1641c83af_u64) + put(cache, 0xf209787bb47d6b84_u64, 0xc0678c5dbd23a49b_u64) + put(cache, 0x9745eb4d50ce6332_u64, 0xf840b7ba963646e1_u64) + put(cache, 0xbd176620a501fbff_u64, 0xb650e5a93bc3d899_u64) + put(cache, 0xec5d3fa8ce427aff_u64, 0xa3e51f138ab4cebf_u64) + put(cache, 0x93ba47c980e98cdf_u64, 0xc66f336c36b10138_u64) + put(cache, 0xb8a8d9bbe123f017_u64, 0xb80b0047445d4185_u64) + put(cache, 0xe6d3102ad96cec1d_u64, 0xa60dc059157491e6_u64) + put(cache, 0x9043ea1ac7e41392_u64, 0x87c89837ad68db30_u64) + put(cache, 0xb454e4a179dd1877_u64, 0x29babe4598c311fc_u64) + put(cache, 0xe16a1dc9d8545e94_u64, 0xf4296dd6fef3d67b_u64) + put(cache, 0x8ce2529e2734bb1d_u64, 0x1899e4a65f58660d_u64) + put(cache, 0xb01ae745b101e9e4_u64, 0x5ec05dcff72e7f90_u64) + put(cache, 0xdc21a1171d42645d_u64, 0x76707543f4fa1f74_u64) + put(cache, 0x899504ae72497eba_u64, 0x6a06494a791c53a9_u64) + put(cache, 0xabfa45da0edbde69_u64, 0x0487db9d17636893_u64) + put(cache, 0xd6f8d7509292d603_u64, 0x45a9d2845d3c42b7_u64) + put(cache, 0x865b86925b9bc5c2_u64, 0x0b8a2392ba45a9b3_u64) + put(cache, 0xa7f26836f282b732_u64, 0x8e6cac7768d7141f_u64) + put(cache, 0xd1ef0244af2364ff_u64, 0x3207d795430cd927_u64) + put(cache, 0x8335616aed761f1f_u64, 0x7f44e6bd49e807b9_u64) + put(cache, 0xa402b9c5a8d3a6e7_u64, 0x5f16206c9c6209a7_u64) + put(cache, 0xcd036837130890a1_u64, 0x36dba887c37a8c10_u64) + put(cache, 0x802221226be55a64_u64, 0xc2494954da2c978a_u64) + put(cache, 0xa02aa96b06deb0fd_u64, 0xf2db9baa10b7bd6d_u64) + put(cache, 0xc83553c5c8965d3d_u64, 0x6f92829494e5acc8_u64) + put(cache, 0xfa42a8b73abbf48c_u64, 0xcb772339ba1f17fa_u64) + put(cache, 0x9c69a97284b578d7_u64, 0xff2a760414536efc_u64) + put(cache, 0xc38413cf25e2d70d_u64, 0xfef5138519684abb_u64) + put(cache, 0xf46518c2ef5b8cd1_u64, 0x7eb258665fc25d6a_u64) + put(cache, 0x98bf2f79d5993802_u64, 0xef2f773ffbd97a62_u64) + put(cache, 0xbeeefb584aff8603_u64, 0xaafb550ffacfd8fb_u64) + put(cache, 0xeeaaba2e5dbf6784_u64, 0x95ba2a53f983cf39_u64) + put(cache, 0x952ab45cfa97a0b2_u64, 0xdd945a747bf26184_u64) + put(cache, 0xba756174393d88df_u64, 0x94f971119aeef9e5_u64) + put(cache, 0xe912b9d1478ceb17_u64, 0x7a37cd5601aab85e_u64) + put(cache, 0x91abb422ccb812ee_u64, 0xac62e055c10ab33b_u64) + put(cache, 0xb616a12b7fe617aa_u64, 0x577b986b314d600a_u64) + put(cache, 0xe39c49765fdf9d94_u64, 0xed5a7e85fda0b80c_u64) + put(cache, 0x8e41ade9fbebc27d_u64, 0x14588f13be847308_u64) + put(cache, 0xb1d219647ae6b31c_u64, 0x596eb2d8ae258fc9_u64) + put(cache, 0xde469fbd99a05fe3_u64, 0x6fca5f8ed9aef3bc_u64) + put(cache, 0x8aec23d680043bee_u64, 0x25de7bb9480d5855_u64) + put(cache, 0xada72ccc20054ae9_u64, 0xaf561aa79a10ae6b_u64) + put(cache, 0xd910f7ff28069da4_u64, 0x1b2ba1518094da05_u64) + put(cache, 0x87aa9aff79042286_u64, 0x90fb44d2f05d0843_u64) + put(cache, 0xa99541bf57452b28_u64, 0x353a1607ac744a54_u64) + put(cache, 0xd3fa922f2d1675f2_u64, 0x42889b8997915ce9_u64) + put(cache, 0x847c9b5d7c2e09b7_u64, 0x69956135febada12_u64) + put(cache, 0xa59bc234db398c25_u64, 0x43fab9837e699096_u64) + put(cache, 0xcf02b2c21207ef2e_u64, 0x94f967e45e03f4bc_u64) + put(cache, 0x8161afb94b44f57d_u64, 0x1d1be0eebac278f6_u64) + put(cache, 0xa1ba1ba79e1632dc_u64, 0x6462d92a69731733_u64) + put(cache, 0xca28a291859bbf93_u64, 0x7d7b8f7503cfdcff_u64) + put(cache, 0xfcb2cb35e702af78_u64, 0x5cda735244c3d43f_u64) + put(cache, 0x9defbf01b061adab_u64, 0x3a0888136afa64a8_u64) + put(cache, 0xc56baec21c7a1916_u64, 0x088aaa1845b8fdd1_u64) + put(cache, 0xf6c69a72a3989f5b_u64, 0x8aad549e57273d46_u64) + put(cache, 0x9a3c2087a63f6399_u64, 0x36ac54e2f678864c_u64) + put(cache, 0xc0cb28a98fcf3c7f_u64, 0x84576a1bb416a7de_u64) + put(cache, 0xf0fdf2d3f3c30b9f_u64, 0x656d44a2a11c51d6_u64) + put(cache, 0x969eb7c47859e743_u64, 0x9f644ae5a4b1b326_u64) + put(cache, 0xbc4665b596706114_u64, 0x873d5d9f0dde1fef_u64) + put(cache, 0xeb57ff22fc0c7959_u64, 0xa90cb506d155a7eb_u64) + put(cache, 0x9316ff75dd87cbd8_u64, 0x09a7f12442d588f3_u64) + put(cache, 0xb7dcbf5354e9bece_u64, 0x0c11ed6d538aeb30_u64) + put(cache, 0xe5d3ef282a242e81_u64, 0x8f1668c8a86da5fb_u64) + put(cache, 0x8fa475791a569d10_u64, 0xf96e017d694487bd_u64) + put(cache, 0xb38d92d760ec4455_u64, 0x37c981dcc395a9ad_u64) + put(cache, 0xe070f78d3927556a_u64, 0x85bbe253f47b1418_u64) + put(cache, 0x8c469ab843b89562_u64, 0x93956d7478ccec8f_u64) + put(cache, 0xaf58416654a6babb_u64, 0x387ac8d1970027b3_u64) + put(cache, 0xdb2e51bfe9d0696a_u64, 0x06997b05fcc0319f_u64) + put(cache, 0x88fcf317f22241e2_u64, 0x441fece3bdf81f04_u64) + put(cache, 0xab3c2fddeeaad25a_u64, 0xd527e81cad7626c4_u64) + put(cache, 0xd60b3bd56a5586f1_u64, 0x8a71e223d8d3b075_u64) + put(cache, 0x85c7056562757456_u64, 0xf6872d5667844e4a_u64) + put(cache, 0xa738c6bebb12d16c_u64, 0xb428f8ac016561dc_u64) + put(cache, 0xd106f86e69d785c7_u64, 0xe13336d701beba53_u64) + put(cache, 0x82a45b450226b39c_u64, 0xecc0024661173474_u64) + put(cache, 0xa34d721642b06084_u64, 0x27f002d7f95d0191_u64) + put(cache, 0xcc20ce9bd35c78a5_u64, 0x31ec038df7b441f5_u64) + put(cache, 0xff290242c83396ce_u64, 0x7e67047175a15272_u64) + put(cache, 0x9f79a169bd203e41_u64, 0x0f0062c6e984d387_u64) + put(cache, 0xc75809c42c684dd1_u64, 0x52c07b78a3e60869_u64) + put(cache, 0xf92e0c3537826145_u64, 0xa7709a56ccdf8a83_u64) + put(cache, 0x9bbcc7a142b17ccb_u64, 0x88a66076400bb692_u64) + put(cache, 0xc2abf989935ddbfe_u64, 0x6acff893d00ea436_u64) + put(cache, 0xf356f7ebf83552fe_u64, 0x0583f6b8c4124d44_u64) + put(cache, 0x98165af37b2153de_u64, 0xc3727a337a8b704b_u64) + put(cache, 0xbe1bf1b059e9a8d6_u64, 0x744f18c0592e4c5d_u64) + put(cache, 0xeda2ee1c7064130c_u64, 0x1162def06f79df74_u64) + put(cache, 0x9485d4d1c63e8be7_u64, 0x8addcb5645ac2ba9_u64) + put(cache, 0xb9a74a0637ce2ee1_u64, 0x6d953e2bd7173693_u64) + put(cache, 0xe8111c87c5c1ba99_u64, 0xc8fa8db6ccdd0438_u64) + put(cache, 0x910ab1d4db9914a0_u64, 0x1d9c9892400a22a3_u64) + put(cache, 0xb54d5e4a127f59c8_u64, 0x2503beb6d00cab4c_u64) + put(cache, 0xe2a0b5dc971f303a_u64, 0x2e44ae64840fd61e_u64) + put(cache, 0x8da471a9de737e24_u64, 0x5ceaecfed289e5d3_u64) + put(cache, 0xb10d8e1456105dad_u64, 0x7425a83e872c5f48_u64) + put(cache, 0xdd50f1996b947518_u64, 0xd12f124e28f7771a_u64) + put(cache, 0x8a5296ffe33cc92f_u64, 0x82bd6b70d99aaa70_u64) + put(cache, 0xace73cbfdc0bfb7b_u64, 0x636cc64d1001550c_u64) + put(cache, 0xd8210befd30efa5a_u64, 0x3c47f7e05401aa4f_u64) + put(cache, 0x8714a775e3e95c78_u64, 0x65acfaec34810a72_u64) + put(cache, 0xa8d9d1535ce3b396_u64, 0x7f1839a741a14d0e_u64) + put(cache, 0xd31045a8341ca07c_u64, 0x1ede48111209a051_u64) + put(cache, 0x83ea2b892091e44d_u64, 0x934aed0aab460433_u64) + put(cache, 0xa4e4b66b68b65d60_u64, 0xf81da84d56178540_u64) + put(cache, 0xce1de40642e3f4b9_u64, 0x36251260ab9d668f_u64) + put(cache, 0x80d2ae83e9ce78f3_u64, 0xc1d72b7c6b42601a_u64) + put(cache, 0xa1075a24e4421730_u64, 0xb24cf65b8612f820_u64) + put(cache, 0xc94930ae1d529cfc_u64, 0xdee033f26797b628_u64) + put(cache, 0xfb9b7cd9a4a7443c_u64, 0x169840ef017da3b2_u64) + put(cache, 0x9d412e0806e88aa5_u64, 0x8e1f289560ee864f_u64) + put(cache, 0xc491798a08a2ad4e_u64, 0xf1a6f2bab92a27e3_u64) + put(cache, 0xf5b5d7ec8acb58a2_u64, 0xae10af696774b1dc_u64) + put(cache, 0x9991a6f3d6bf1765_u64, 0xacca6da1e0a8ef2a_u64) + put(cache, 0xbff610b0cc6edd3f_u64, 0x17fd090a58d32af4_u64) + put(cache, 0xeff394dcff8a948e_u64, 0xddfc4b4cef07f5b1_u64) + put(cache, 0x95f83d0a1fb69cd9_u64, 0x4abdaf101564f98f_u64) + put(cache, 0xbb764c4ca7a4440f_u64, 0x9d6d1ad41abe37f2_u64) + put(cache, 0xea53df5fd18d5513_u64, 0x84c86189216dc5ee_u64) + put(cache, 0x92746b9be2f8552c_u64, 0x32fd3cf5b4e49bb5_u64) + put(cache, 0xb7118682dbb66a77_u64, 0x3fbc8c33221dc2a2_u64) + put(cache, 0xe4d5e82392a40515_u64, 0x0fabaf3feaa5334b_u64) + put(cache, 0x8f05b1163ba6832d_u64, 0x29cb4d87f2a7400f_u64) + put(cache, 0xb2c71d5bca9023f8_u64, 0x743e20e9ef511013_u64) + put(cache, 0xdf78e4b2bd342cf6_u64, 0x914da9246b255417_u64) + put(cache, 0x8bab8eefb6409c1a_u64, 0x1ad089b6c2f7548f_u64) + put(cache, 0xae9672aba3d0c320_u64, 0xa184ac2473b529b2_u64) + put(cache, 0xda3c0f568cc4f3e8_u64, 0xc9e5d72d90a2741f_u64) + put(cache, 0x8865899617fb1871_u64, 0x7e2fa67c7a658893_u64) + put(cache, 0xaa7eebfb9df9de8d_u64, 0xddbb901b98feeab8_u64) + put(cache, 0xd51ea6fa85785631_u64, 0x552a74227f3ea566_u64) + put(cache, 0x8533285c936b35de_u64, 0xd53a88958f872760_u64) + put(cache, 0xa67ff273b8460356_u64, 0x8a892abaf368f138_u64) + put(cache, 0xd01fef10a657842c_u64, 0x2d2b7569b0432d86_u64) + put(cache, 0x8213f56a67f6b29b_u64, 0x9c3b29620e29fc74_u64) + put(cache, 0xa298f2c501f45f42_u64, 0x8349f3ba91b47b90_u64) + put(cache, 0xcb3f2f7642717713_u64, 0x241c70a936219a74_u64) + put(cache, 0xfe0efb53d30dd4d7_u64, 0xed238cd383aa0111_u64) + put(cache, 0x9ec95d1463e8a506_u64, 0xf4363804324a40ab_u64) + put(cache, 0xc67bb4597ce2ce48_u64, 0xb143c6053edcd0d6_u64) + put(cache, 0xf81aa16fdc1b81da_u64, 0xdd94b7868e94050b_u64) + put(cache, 0x9b10a4e5e9913128_u64, 0xca7cf2b4191c8327_u64) + put(cache, 0xc1d4ce1f63f57d72_u64, 0xfd1c2f611f63a3f1_u64) + put(cache, 0xf24a01a73cf2dccf_u64, 0xbc633b39673c8ced_u64) + put(cache, 0x976e41088617ca01_u64, 0xd5be0503e085d814_u64) + put(cache, 0xbd49d14aa79dbc82_u64, 0x4b2d8644d8a74e19_u64) + put(cache, 0xec9c459d51852ba2_u64, 0xddf8e7d60ed1219f_u64) + put(cache, 0x93e1ab8252f33b45_u64, 0xcabb90e5c942b504_u64) + put(cache, 0xb8da1662e7b00a17_u64, 0x3d6a751f3b936244_u64) + put(cache, 0xe7109bfba19c0c9d_u64, 0x0cc512670a783ad5_u64) + put(cache, 0x906a617d450187e2_u64, 0x27fb2b80668b24c6_u64) + put(cache, 0xb484f9dc9641e9da_u64, 0xb1f9f660802dedf7_u64) + put(cache, 0xe1a63853bbd26451_u64, 0x5e7873f8a0396974_u64) + put(cache, 0x8d07e33455637eb2_u64, 0xdb0b487b6423e1e9_u64) + put(cache, 0xb049dc016abc5e5f_u64, 0x91ce1a9a3d2cda63_u64) + put(cache, 0xdc5c5301c56b75f7_u64, 0x7641a140cc7810fc_u64) + put(cache, 0x89b9b3e11b6329ba_u64, 0xa9e904c87fcb0a9e_u64) + put(cache, 0xac2820d9623bf429_u64, 0x546345fa9fbdcd45_u64) + put(cache, 0xd732290fbacaf133_u64, 0xa97c177947ad4096_u64) + put(cache, 0x867f59a9d4bed6c0_u64, 0x49ed8eabcccc485e_u64) + put(cache, 0xa81f301449ee8c70_u64, 0x5c68f256bfff5a75_u64) + put(cache, 0xd226fc195c6a2f8c_u64, 0x73832eec6fff3112_u64) + put(cache, 0x83585d8fd9c25db7_u64, 0xc831fd53c5ff7eac_u64) + put(cache, 0xa42e74f3d032f525_u64, 0xba3e7ca8b77f5e56_u64) + put(cache, 0xcd3a1230c43fb26f_u64, 0x28ce1bd2e55f35ec_u64) + put(cache, 0x80444b5e7aa7cf85_u64, 0x7980d163cf5b81b4_u64) + put(cache, 0xa0555e361951c366_u64, 0xd7e105bcc3326220_u64) + put(cache, 0xc86ab5c39fa63440_u64, 0x8dd9472bf3fefaa8_u64) + put(cache, 0xfa856334878fc150_u64, 0xb14f98f6f0feb952_u64) + put(cache, 0x9c935e00d4b9d8d2_u64, 0x6ed1bf9a569f33d4_u64) + put(cache, 0xc3b8358109e84f07_u64, 0x0a862f80ec4700c9_u64) + put(cache, 0xf4a642e14c6262c8_u64, 0xcd27bb612758c0fb_u64) + put(cache, 0x98e7e9cccfbd7dbd_u64, 0x8038d51cb897789d_u64) + put(cache, 0xbf21e44003acdd2c_u64, 0xe0470a63e6bd56c4_u64) + put(cache, 0xeeea5d5004981478_u64, 0x1858ccfce06cac75_u64) + put(cache, 0x95527a5202df0ccb_u64, 0x0f37801e0c43ebc9_u64) + put(cache, 0xbaa718e68396cffd_u64, 0xd30560258f54e6bb_u64) + put(cache, 0xe950df20247c83fd_u64, 0x47c6b82ef32a206a_u64) + put(cache, 0x91d28b7416cdd27e_u64, 0x4cdc331d57fa5442_u64) + put(cache, 0xb6472e511c81471d_u64, 0xe0133fe4adf8e953_u64) + put(cache, 0xe3d8f9e563a198e5_u64, 0x58180fddd97723a7_u64) + put(cache, 0x8e679c2f5e44ff8f_u64, 0x570f09eaa7ea7649_u64) + put(cache, 0xb201833b35d63f73_u64, 0x2cd2cc6551e513db_u64) + put(cache, 0xde81e40a034bcf4f_u64, 0xf8077f7ea65e58d2_u64) + put(cache, 0x8b112e86420f6191_u64, 0xfb04afaf27faf783_u64) + put(cache, 0xadd57a27d29339f6_u64, 0x79c5db9af1f9b564_u64) + put(cache, 0xd94ad8b1c7380874_u64, 0x18375281ae7822bd_u64) + put(cache, 0x87cec76f1c830548_u64, 0x8f2293910d0b15b6_u64) + put(cache, 0xa9c2794ae3a3c69a_u64, 0xb2eb3875504ddb23_u64) + put(cache, 0xd433179d9c8cb841_u64, 0x5fa60692a46151ec_u64) + put(cache, 0x849feec281d7f328_u64, 0xdbc7c41ba6bcd334_u64) + put(cache, 0xa5c7ea73224deff3_u64, 0x12b9b522906c0801_u64) + put(cache, 0xcf39e50feae16bef_u64, 0xd768226b34870a01_u64) + put(cache, 0x81842f29f2cce375_u64, 0xe6a1158300d46641_u64) + put(cache, 0xa1e53af46f801c53_u64, 0x60495ae3c1097fd1_u64) + put(cache, 0xca5e89b18b602368_u64, 0x385bb19cb14bdfc5_u64) + put(cache, 0xfcf62c1dee382c42_u64, 0x46729e03dd9ed7b6_u64) + put(cache, 0x9e19db92b4e31ba9_u64, 0x6c07a2c26a8346d2_u64) + put(cache, 0xc5a05277621be293_u64, 0xc7098b7305241886_u64) + put(cache, 0xf70867153aa2db38_u64, 0xb8cbee4fc66d1ea8_u64) cache end end From 2512abc4fbef6df268f2241cda252ade33a2c288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Wed, 23 Nov 2022 07:00:48 -0800 Subject: [PATCH 0149/1551] Lexer: delete redundant `scan_ident` calls (#12691) --- spec/compiler/lexer/lexer_spec.cr | 9 + src/compiler/crystal/syntax/lexer.cr | 552 +++++++++++++-------------- 2 files changed, 277 insertions(+), 284 deletions(-) diff --git a/spec/compiler/lexer/lexer_spec.cr b/spec/compiler/lexer/lexer_spec.cr index bcb47588a685..aa9eaf3e06c8 100644 --- a/spec/compiler/lexer/lexer_spec.cr +++ b/spec/compiler/lexer/lexer_spec.cr @@ -684,4 +684,13 @@ describe "Lexer" do token.type.should eq(t :DELIMITER_START) token.delimiter_state.kind.should eq(Token::DelimiterKind::REGEX) end + + it "lexes heredoc start" do + lexer = Lexer.new("<<-EOS\n") + lexer.wants_raw = true + token = lexer.next_token + token.type.should eq(t :DELIMITER_START) + token.delimiter_state.kind.should eq(Token::DelimiterKind::HEREDOC) + token.raw.should eq "<<-EOS" + end end diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index ad87830b361c..c8f65187a448 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -219,64 +219,7 @@ module Crystal when '=' next_char :OP_LT_LT_EQ when '-' - has_single_quote = false - found_closing_single_quote = false - - char = next_char - start_here = current_pos - - if char == '\'' - has_single_quote = true - char = next_char - start_here = current_pos - end - - unless ident_part?(char) - raise "heredoc identifier starts with invalid character" - end - - end_here = 0 - - while true - char = next_char - case - when char == '\r' - if peek_next_char == '\n' - end_here = current_pos - next_char - break - else - raise "expecting '\\n' after '\\r'" - end - when char == '\n' - end_here = current_pos - break - when ident_part?(char) - # ok - when char == '\0' - raise "Unexpected EOF on heredoc identifier" - else - if char == '\'' && has_single_quote - found_closing_single_quote = true - end_here = current_pos - next_char - break - elsif has_single_quote - # wait until another quote - else - end_here = current_pos - break - end - end - end - - if has_single_quote && !found_closing_single_quote - raise "expecting closing single quote" - end - - here = string_range(start_here, end_here) - - delimited_pair :heredoc, here, here, start, allow_escapes: !has_single_quote, advance: false + consume_heredoc_start else @token.type = :OP_LT_LT end @@ -338,8 +281,6 @@ module Crystal @token.type = :OP_STAR end when '/' - line = @line_number - column = @column_number char = next_char if (@wants_def_or_macro_name || !@slash_is_regex) && char == '/' case next_char @@ -353,15 +294,11 @@ module Crystal elsif @wants_def_or_macro_name @token.type = :OP_SLASH elsif @slash_is_regex - @token.type = :DELIMITER_START - @token.delimiter_state = Token::DelimiterState.new(:regex, '/', '/') - @token.raw = "/" + delimited_pair :regex, '/', '/', start, advance: false elsif char.ascii_whitespace? || char == '\0' @token.type = :OP_SLASH elsif @wants_regex - @token.type = :DELIMITER_START - @token.delimiter_state = Token::DelimiterState.new(:regex, '/', '/') - @token.raw = "/" + delimited_pair :regex, '/', '/', start, advance: false else @token.type = :OP_SLASH end @@ -464,186 +401,12 @@ module Crystal reset_regex_flags = false next_char :OP_SEMICOLON when ':' - char = next_char - - if @wants_symbol - case char - when ':' - next_char :OP_COLON_COLON - when '+' - next_char_and_symbol "+" - when '-' - next_char_and_symbol "-" - when '*' - if next_char == '*' - next_char_and_symbol "**" - else - symbol "*" - end - when '/' - case next_char - when '/' - next_char_and_symbol "//" - else - symbol "/" - end - when '=' - case next_char - when '=' - if next_char == '=' - next_char_and_symbol "===" - else - symbol "==" - end - when '~' - next_char_and_symbol "=~" - else - unknown_token - end - when '!' - case next_char - when '=' - next_char_and_symbol "!=" - when '~' - next_char_and_symbol "!~" - else - symbol "!" - end - when '<' - case next_char - when '=' - if next_char == '>' - next_char_and_symbol "<=>" - else - symbol "<=" - end - when '<' - next_char_and_symbol "<<" - else - symbol "<" - end - when '>' - case next_char - when '=' - next_char_and_symbol ">=" - when '>' - next_char_and_symbol ">>" - else - symbol ">" - end - when '&' - case next_char - when '+' - next_char_and_symbol "&+" - when '-' - next_char_and_symbol "&-" - when '*' - case next_char - when '*' - next_char_and_symbol "&**" - else - symbol "&*" - end - else - symbol "&" - end - when '|' - next_char_and_symbol "|" - when '^' - next_char_and_symbol "^" - when '~' - next_char_and_symbol "~" - when '%' - next_char_and_symbol "%" - when '[' - if next_char == ']' - case next_char - when '=' - next_char_and_symbol "[]=" - when '?' - next_char_and_symbol "[]?" - else - symbol "[]" - end - else - unknown_token - end - when '"' - line = @line_number - column = @column_number - start = current_pos + 1 - io = IO::Memory.new - while true - char = next_char - case char - when '\\' - case char = next_char - when 'a' - io << '\a' - when 'b' - io << '\b' - when 'n' - io << '\n' - when 'r' - io << '\r' - when 't' - io << '\t' - when 'v' - io << '\v' - when 'f' - io << '\f' - when 'e' - io << '\e' - when 'x' - io.write_byte consume_string_hex_escape - when 'u' - io << consume_string_unicode_escape - when '0', '1', '2', '3', '4', '5', '6', '7' - io.write_byte consume_octal_escape(char) - when '\n' - incr_line_number nil - io << '\n' - when '\0' - raise "unterminated quoted symbol", line, column - else - io << char - end - when '"' - break - when '\0' - raise "unterminated quoted symbol", line, column - else - io << char - end - end - - @token.type = :SYMBOL - @token.value = io.to_s - next_char - set_token_raw_from_start(start - 2) - else - if ident_start?(char) - start = current_pos - while ident_part?(next_char) - # Nothing to do - end - if current_char == '?' || (current_char.in?('!', '=') && peek_next_char != '=') - next_char - end - @token.type = :SYMBOL - @token.value = string_range_from_pool(start) - set_token_raw_from_start(start - 1) - else - @token.type = :OP_COLON - end - end + if next_char == ':' + next_char :OP_COLON_COLON + elsif @wants_symbol + consume_symbol else - case char - when ':' - next_char :OP_COLON_COLON - else - @token.type = :OP_COLON - end + @token.type = :OP_COLON end when '~' next_char :OP_TILDE @@ -778,17 +541,14 @@ module Crystal end next_char set_token_raw_from_start(start) - when '"', '`' - delimiter = current_char - if delimiter == '`' && @wants_def_or_macro_name + when '`' + if @wants_def_or_macro_name next_char :OP_GRAVE else - next_char - @token.type = :DELIMITER_START - delimiter_kind = delimiter == '`' ? Token::DelimiterKind::COMMAND : Token::DelimiterKind::STRING - @token.delimiter_state = Token::DelimiterState.new(delimiter_kind, delimiter, delimiter) - set_token_raw_from_start(start) + delimited_pair :command, '`', '`', start end + when '"' + delimited_pair :string, '"', '"', start when '0'..'9' scan_number start when '@' @@ -797,19 +557,11 @@ module Crystal when '[' next_char :OP_AT_LSQUARE else - class_var = false if current_char == '@' - class_var = true next_char - end - if ident_start?(current_char) - while ident_part?(next_char) - # Nothing to do - end - @token.type = class_var ? Token::Kind::CLASS_VAR : Token::Kind::INSTANCE_VAR - @token.value = string_range_from_pool(start) + consume_variable :CLASS_VAR, start else - unknown_token + consume_variable :INSTANCE_VAR, start end end when '$' @@ -835,15 +587,7 @@ module Crystal @token.type = :GLOBAL_MATCH_DATA_INDEX @token.value = string_range_from_pool(start) else - if ident_start?(current_char) - while ident_part?(next_char) - # Nothing to do - end - @token.type = :GLOBAL - @token.value = string_range_from_pool(start) - else - unknown_token - end + consume_variable :GLOBAL, start end when 'a' case next_char @@ -1256,9 +1000,7 @@ module Crystal case next_char when 'D' if char_sequence?('I', 'R', '_', '_') - if ident_part_or_end?(peek_next_char) - scan_ident(start) - else + unless ident_part_or_end?(peek_next_char) next_char @token.type = :MAGIC_DIR return @token @@ -1266,9 +1008,7 @@ module Crystal end when 'E' if char_sequence?('N', 'D', '_', 'L', 'I', 'N', 'E', '_', '_') - if ident_part_or_end?(peek_next_char) - scan_ident(start) - else + unless ident_part_or_end?(peek_next_char) next_char @token.type = :MAGIC_END_LINE return @token @@ -1276,9 +1016,7 @@ module Crystal end when 'F' if char_sequence?('I', 'L', 'E', '_', '_') - if ident_part_or_end?(peek_next_char) - scan_ident(start) - else + unless ident_part_or_end?(peek_next_char) next_char @token.type = :MAGIC_FILE return @token @@ -1286,9 +1024,7 @@ module Crystal end when 'L' if char_sequence?('I', 'N', 'E', '_', '_') - if ident_part_or_end?(peek_next_char) - scan_ident(start) - else + unless ident_part_or_end?(peek_next_char) next_char @token.type = :MAGIC_LINE return @token @@ -2751,6 +2487,254 @@ module Crystal end end + private def consume_heredoc_start + start = current_pos - 2 + + has_single_quote = false + found_closing_single_quote = false + + char = next_char + start_here = current_pos + + if char == '\'' + has_single_quote = true + char = next_char + start_here = current_pos + end + + unless ident_part?(char) + raise "heredoc identifier starts with invalid character" + end + + end_here = 0 + + while true + char = next_char + case + when char == '\r' + if peek_next_char == '\n' + end_here = current_pos + next_char + break + else + raise "expecting '\\n' after '\\r'" + end + when char == '\n' + end_here = current_pos + break + when ident_part?(char) + # ok + when char == '\0' + raise "Unexpected EOF on heredoc identifier" + else + if char == '\'' && has_single_quote + found_closing_single_quote = true + end_here = current_pos + next_char + break + elsif has_single_quote + # wait until another quote + else + end_here = current_pos + break + end + end + end + + if has_single_quote && !found_closing_single_quote + raise "expecting closing single quote" + end + + here = string_range(start_here, end_here) + + delimited_pair :heredoc, here, here, start, allow_escapes: !has_single_quote, advance: false + end + + private def consume_symbol + case char = current_char + when ':' + next_char :OP_COLON_COLON + when '+' + next_char_and_symbol "+" + when '-' + next_char_and_symbol "-" + when '*' + if next_char == '*' + next_char_and_symbol "**" + else + symbol "*" + end + when '/' + case next_char + when '/' + next_char_and_symbol "//" + else + symbol "/" + end + when '=' + case next_char + when '=' + if next_char == '=' + next_char_and_symbol "===" + else + symbol "==" + end + when '~' + next_char_and_symbol "=~" + else + unknown_token + end + when '!' + case next_char + when '=' + next_char_and_symbol "!=" + when '~' + next_char_and_symbol "!~" + else + symbol "!" + end + when '<' + case next_char + when '=' + if next_char == '>' + next_char_and_symbol "<=>" + else + symbol "<=" + end + when '<' + next_char_and_symbol "<<" + else + symbol "<" + end + when '>' + case next_char + when '=' + next_char_and_symbol ">=" + when '>' + next_char_and_symbol ">>" + else + symbol ">" + end + when '&' + case next_char + when '+' + next_char_and_symbol "&+" + when '-' + next_char_and_symbol "&-" + when '*' + case next_char + when '*' + next_char_and_symbol "&**" + else + symbol "&*" + end + else + symbol "&" + end + when '|' + next_char_and_symbol "|" + when '^' + next_char_and_symbol "^" + when '~' + next_char_and_symbol "~" + when '%' + next_char_and_symbol "%" + when '[' + if next_char == ']' + case next_char + when '=' + next_char_and_symbol "[]=" + when '?' + next_char_and_symbol "[]?" + else + symbol "[]" + end + else + unknown_token + end + when '"' + line = @line_number + column = @column_number + start = current_pos + 1 + io = IO::Memory.new + while true + char = next_char + case char + when '\\' + case char = next_char + when 'a' + io << '\a' + when 'b' + io << '\b' + when 'n' + io << '\n' + when 'r' + io << '\r' + when 't' + io << '\t' + when 'v' + io << '\v' + when 'f' + io << '\f' + when 'e' + io << '\e' + when 'x' + io.write_byte consume_string_hex_escape + when 'u' + io << consume_string_unicode_escape + when '0', '1', '2', '3', '4', '5', '6', '7' + io.write_byte consume_octal_escape(char) + when '\n' + incr_line_number nil + io << '\n' + when '\0' + raise "unterminated quoted symbol", line, column + else + io << char + end + when '"' + break + when '\0' + raise "unterminated quoted symbol", line, column + else + io << char + end + end + + @token.type = :SYMBOL + @token.value = io.to_s + next_char + set_token_raw_from_start(start - 2) + else + if ident_start?(char) + start = current_pos + while ident_part?(next_char) + # Nothing to do + end + if current_char == '?' || (current_char.in?('!', '=') && peek_next_char != '=') + next_char + end + @token.type = :SYMBOL + @token.value = string_range_from_pool(start) + set_token_raw_from_start(start - 1) + else + @token.type = :OP_COLON + end + end + end + + private def consume_variable(token_type : Token::Kind, start) + if ident_start?(current_char) + while ident_part?(next_char) + # Nothing to do + end + @token.type = token_type + @token.value = string_range_from_pool(start) + else + unknown_token + end + end + def set_location(filename, line_number, column_number) @token.filename = @filename = filename @token.line_number = @line_number = line_number From dee1c5af568d516607c3ca6174ebc7964a47a537 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Wed, 23 Nov 2022 23:01:58 +0800 Subject: [PATCH 0150/1551] Formatter: document stdin filename argument (`-`) (#12620) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Will Rigney Co-authored-by: Johannes Müller --- src/compiler/crystal/command/format.cr | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/compiler/crystal/command/format.cr b/src/compiler/crystal/command/format.cr index 76ebe9c3ef34..41dbe0157dab 100644 --- a/src/compiler/crystal/command/format.cr +++ b/src/compiler/crystal/command/format.cr @@ -11,7 +11,18 @@ class Crystal::Command show_backtrace = false OptionParser.parse(@options) do |opts| - opts.banner = "Usage: crystal tool format [options] [file or directory]\n\nOptions:" + opts.banner = <<-USAGE + Usage: crystal tool format [options] [- | file or directory ...] + + Formats Crystal code in place. + + If a file or directory is omitted, + Crystal source files beneath the working directory are formatted. + + To format STDIN to STDOUT, use '-' in place of any path arguments. + + Options: + USAGE opts.on("--check", "Checks that formatting code produces no changes") do |f| check = true From 524fb888515ddac7685eb2d34327b9122717bcf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 23 Nov 2022 23:22:53 +0100 Subject: [PATCH 0151/1551] Print seed info at start and end of spec output (#12755) --- src/spec/context.cr | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/spec/context.cr b/src/spec/context.cr index 77b30b2bcb3e..e2d90e7686de 100644 --- a/src/spec/context.cr +++ b/src/spec/context.cr @@ -153,6 +153,8 @@ module Spec end def run + print_order_message + internal_run end @@ -255,10 +257,6 @@ module Spec puts Spec.color("#{total} examples, #{failures.size} failures, #{errors.size} errors, #{pendings.size} pending", final_status) puts Spec.color("Only running `focus: true`", :focus) if Spec.focus? - if randomizer_seed = Spec.randomizer_seed - puts Spec.color("Randomized with seed: #{randomizer_seed}", :order) - end - unless failures_and_errors.empty? puts puts "Failed examples:" @@ -268,6 +266,14 @@ module Spec puts Spec.color(" # #{fail.description}", :comment) end end + + print_order_message + end + + def print_order_message + if randomizer_seed = Spec.randomizer_seed + puts Spec.color("Randomized with seed: #{randomizer_seed}", :order) + end end def describe(description, file, line, end_line, focus, tags, &block) From 33c1c2efa9bfe75155f31eb1468c669a7286eb16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 23 Nov 2022 23:23:29 +0100 Subject: [PATCH 0152/1551] Fix: Do not merge union types in truthy filter (#12752) --- spec/compiler/semantic/if_spec.cr | 28 ++++++++++++++++++++++++ src/compiler/crystal/semantic/filters.cr | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/if_spec.cr b/spec/compiler/semantic/if_spec.cr index 9ed5199a21ba..38b14e24499a 100644 --- a/spec/compiler/semantic/if_spec.cr +++ b/spec/compiler/semantic/if_spec.cr @@ -235,6 +235,34 @@ describe "Semantic: if" do )) { int32 } end + it "restricts and doesn't unify union types" do + assert_type(%( + class Foo + end + + module M + def m + 1 + end + end + + class Bar < Foo + include M + end + + class Baz < Foo + include M + end + + a = Bar.new.as(Foo) + if b = a.as?(M) + b.m + else + nil + end + )) { union_of(nil_type, int32) } + end + it "types variable after unreachable else of && (#3360)" do assert_type(%( def test diff --git a/src/compiler/crystal/semantic/filters.cr b/src/compiler/crystal/semantic/filters.cr index 8612c956aae7..ebc77d01e675 100644 --- a/src/compiler/crystal/semantic/filters.cr +++ b/src/compiler/crystal/semantic/filters.cr @@ -144,7 +144,7 @@ module Crystal when NilType nil when UnionType - Type.merge(other.union_types.reject &.nil_type?) + other.program.union_of(other.union_types.reject &.nil_type?) else other end From 022e1f9fe3d57b93f841789ea0bc477938bc8abe Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 24 Nov 2022 18:40:48 +0800 Subject: [PATCH 0153/1551] Support scientific notation in `BigDecimal#to_s` (#10805) Co-authored-by: Beta Ziliani --- spec/std/big/big_decimal_spec.cr | 70 +++++++++++++++++------------- src/big/big_decimal.cr | 74 +++++++++++++++++++++++--------- 2 files changed, 95 insertions(+), 49 deletions(-) diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index 3f4f11aca033..e783d2f5bb10 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -1,5 +1,6 @@ require "spec" require "big" +require "../../support/string" describe BigDecimal do it "initializes from valid input" do @@ -382,34 +383,45 @@ describe BigDecimal do end it "converts to string" do - BigDecimal.new.to_s.should eq "0" - BigDecimal.new(0).to_s.should eq "0" - BigDecimal.new(1).to_s.should eq "1" - BigDecimal.new(-1).to_s.should eq "-1" - BigDecimal.new("8.5").to_s.should eq "8.5" - BigDecimal.new("-0.35").to_s.should eq "-0.35" - BigDecimal.new("-.35").to_s.should eq "-0.35" - BigDecimal.new("0.01").to_s.should eq "0.01" - BigDecimal.new("-0.01").to_s.should eq "-0.01" - BigDecimal.new("0.00123").to_s.should eq "0.00123" - BigDecimal.new("-0.00123").to_s.should eq "-0.00123" - BigDecimal.new("1.0").to_s.should eq "1" - BigDecimal.new("-1.0").to_s.should eq "-1" - BigDecimal.new("1.000").to_s.should eq "1" - BigDecimal.new("-1.000").to_s.should eq "-1" - BigDecimal.new("1.0001").to_s.should eq "1.0001" - BigDecimal.new("-1.0001").to_s.should eq "-1.0001" - - (BigDecimal.new(1).div(BigDecimal.new(3), 9)).to_s.should eq "0.333333333" - (BigDecimal.new(1000).div(BigDecimal.new(3000), 9)).to_s.should eq "0.333333333" - (BigDecimal.new(1).div(BigDecimal.new(3000), 9)).to_s.should eq "0.000333333" - - (BigDecimal.new("112839719283").div(BigDecimal.new("3123779"), 9)).to_s.should eq "36122.824080384" - (BigDecimal.new("112839719283").div(BigDecimal.new("3123779"), 14)).to_s.should eq "36122.8240803846879" - (BigDecimal.new("-0.4098").div(BigDecimal.new("0.2229011193"), 20)).to_s.should eq "-1.83848336557007141059" - - BigDecimal.new(1, 2).to_s.should eq "0.01" - BigDecimal.new(100, 4).to_s.should eq "0.01" + assert_prints BigDecimal.new.to_s, "0.0" + assert_prints BigDecimal.new(0).to_s, "0.0" + assert_prints BigDecimal.new(1).to_s, "1.0" + assert_prints BigDecimal.new(-1).to_s, "-1.0" + assert_prints BigDecimal.new("8.5").to_s, "8.5" + assert_prints BigDecimal.new("-0.35").to_s, "-0.35" + assert_prints BigDecimal.new("-.35").to_s, "-0.35" + assert_prints BigDecimal.new("0.01").to_s, "0.01" + assert_prints BigDecimal.new("-0.01").to_s, "-0.01" + assert_prints BigDecimal.new("0.00123").to_s, "0.00123" + assert_prints BigDecimal.new("-0.00123").to_s, "-0.00123" + assert_prints BigDecimal.new("1.0").to_s, "1.0" + assert_prints BigDecimal.new("-1.0").to_s, "-1.0" + assert_prints BigDecimal.new("1.000").to_s, "1.0" + assert_prints BigDecimal.new("-1.000").to_s, "-1.0" + assert_prints BigDecimal.new("1.0001").to_s, "1.0001" + assert_prints BigDecimal.new("-1.0001").to_s, "-1.0001" + + assert_prints BigDecimal.new(1).div(BigDecimal.new(3), 9).to_s, "0.333333333" + assert_prints BigDecimal.new(1000).div(BigDecimal.new(3000), 9).to_s, "0.333333333" + assert_prints BigDecimal.new(1).div(BigDecimal.new(3000), 9).to_s, "0.000333333" + + assert_prints BigDecimal.new("112839719283").div(BigDecimal.new("3123779"), 9).to_s, "36122.824080384" + assert_prints BigDecimal.new("112839719283").div(BigDecimal.new("3123779"), 14).to_s, "36122.8240803846879" + assert_prints BigDecimal.new("-0.4098").div(BigDecimal.new("0.2229011193"), 20).to_s, "-1.83848336557007141059" + + assert_prints BigDecimal.new(1, 2).to_s, "0.01" + assert_prints BigDecimal.new(100, 4).to_s, "0.01" + + assert_prints "12345678901234567".to_big_d.to_s, "1.2345678901234567e+16" + assert_prints "1234567890123456789".to_big_d.to_s, "1.234567890123456789e+18" + + assert_prints BigDecimal.new(1_000_000_000_000_000_i64, 0).to_s, "1.0e+15" + assert_prints BigDecimal.new(100_000_000_000_000_i64, 0).to_s, "100000000000000.0" + assert_prints BigDecimal.new(1, 4).to_s, "0.0001" + assert_prints BigDecimal.new(1, 5).to_s, "1.0e-5" + + assert_prints "1.23e45".to_big_d.to_s, "1.23e+45" + assert_prints "1e-234".to_big_d.to_s, "1.0e-234" end it "converts to other number types" do @@ -781,6 +793,6 @@ describe BigDecimal do end describe "#inspect" do - it { "123".to_big_d.inspect.should eq("123") } + it { "123".to_big_d.inspect.should eq("123.0") } end end diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index 66945bcc6899..1cb44c0b1657 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -443,28 +443,62 @@ struct BigDecimal < Number def to_s(io : IO) : Nil factor_powers_of_ten - s = @value.to_s - if @scale == 0 - io << s - return + cstr = LibGMP.get_str(nil, 10, @value) + length = LibC.strlen(cstr) + buffer = Slice.new(cstr, length) + + # add negative sign + if buffer[0]? == 45 # '-' + io << '-' + buffer = buffer[1..] + length -= 1 end - if @scale >= s.size && @value >= 0 - io << "0." - (@scale - s.size).times do - io << '0' - end - io << s - elsif @scale >= s.size && @value < 0 - io << "-0.0" - (@scale - s.size).times do - io << '0' - end - io << s[1..-1] - elsif (offset = s.size - @scale) == 1 && @value < 0 - io << "-0." << s[offset..-1] - else - io << s[0...offset] << '.' << s[offset..-1] + decimal_exponent = length.to_i - @scale + point = decimal_exponent + exp = point + exp_mode = point > 15 || point < -3 + point = 1 if exp_mode + + # add leading zero + io << '0' if point < 1 + + # add integer part digits + if decimal_exponent > 0 && !exp_mode + # whole number but not big enough to be exp form + io.write_string buffer[0, {decimal_exponent, length}.min] + buffer = buffer[{decimal_exponent, length}.min...] + (point - length).times { io << '0' } + elsif point > 0 + io.write_string buffer[0, point] + buffer = buffer[point...] + end + + io << '.' + + # add leading zeros after point + if point < 0 + (-point).times { io << '0' } + end + + # remove trailing zeroes + while buffer.size > 1 && buffer.last === '0' + buffer = buffer[0..-2] + end + + # add fractional part digits + io.write_string buffer + + # print trailing 0 if whole number or exp notation of power of ten + if (decimal_exponent >= length && !exp_mode) || (exp != point && length == 1) + io << '0' + end + + # exp notation + if exp != point + io << 'e' + io << '+' if exp > 0 + (exp - 1).to_s(io) end end From c1ed068ac19ce7cc52585c825f87606e73d487e5 Mon Sep 17 00:00:00 2001 From: GeopJr Date: Mon, 28 Nov 2022 16:29:33 +0200 Subject: [PATCH 0154/1551] Handle triples without libc (#12594) Co-authored-by: Jason Frey --- spec/compiler/codegen/target_spec.cr | 1 + src/compiler/crystal/codegen/target.cr | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/spec/compiler/codegen/target_spec.cr b/spec/compiler/codegen/target_spec.cr index c5639d0bb217..db22ad7334bd 100644 --- a/spec/compiler/codegen/target_spec.cr +++ b/spec/compiler/codegen/target_spec.cr @@ -17,6 +17,7 @@ describe Crystal::Codegen::Target do Target.new("i686-unknown-linux-gnu").to_s.should eq("i386-unknown-linux-gnu") Target.new("amd64-unknown-openbsd").to_s.should eq("x86_64-unknown-openbsd") Target.new("arm64-apple-darwin20.2.0").to_s.should eq("aarch64-apple-darwin20.2.0") + Target.new("x86_64-suse-linux").to_s.should eq("x86_64-suse-linux-gnu") end it "parses freebsd version" do diff --git a/src/compiler/crystal/codegen/target.cr b/src/compiler/crystal/codegen/target.cr index c89880deabda..cb0c62d4f080 100644 --- a/src/compiler/crystal/codegen/target.cr +++ b/src/compiler/crystal/codegen/target.cr @@ -32,6 +32,17 @@ class Crystal::Codegen::Target else # no need to tweak the architecture end + + if linux? && environment_parts.size == 1 + case @vendor + when "suse", "redhat", "slackware", "amazon", "unknown", "montavista", "mti" + # Build string instead of setting it as "linux-gnu" + # since "linux6E" & "linuxspe" are available. + @environment = "#{@environment}-gnu" + else + # no need to tweak the environment + end + end end def environment_parts From d06e5a374c20ccfb3421491096bef7a3af27a3fa Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Mon, 28 Nov 2022 06:30:33 -0800 Subject: [PATCH 0155/1551] Add WebAssembly specs (#12571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Ary Borenszweig Co-authored-by: Beta Ziliani Co-authored-by: Quinton Miller Co-authored-by: George Dietrich Co-authored-by: Dmitri Goutnik Co-authored-by: Gabriel Holodak Co-authored-by: Caspian Baska Co-authored-by: David Keller --- .github/workflows/smoke.yml | 1 - .github/workflows/wasm32.yml | 45 ++++++ spec/generate_wasm32_spec.sh | 61 ++++++++ spec/wasm32_std_spec.cr | 268 +++++++++++++++++++++++++++++++++++ src/crystal/scheduler.cr | 5 +- src/spec/expectations.cr | 74 +++++----- 6 files changed, 418 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/wasm32.yml create mode 100755 spec/generate_wasm32_spec.sh create mode 100644 spec/wasm32_std_spec.cr diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 0e65788721e3..9fd272badbb3 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -47,7 +47,6 @@ jobs: - arm-linux-gnueabihf - i386-linux-gnu - i386-linux-musl - - wasm32-unknown-wasi - x86_64-dragonfly - x86_64-freebsd - x86_64-netbsd diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml new file mode 100644 index 000000000000..e2b0fbf49bbe --- /dev/null +++ b/.github/workflows/wasm32.yml @@ -0,0 +1,45 @@ +name: WebAssembly CI + +on: [push, pull_request] + +env: + SPEC_SPLIT_DOTS: 160 + +jobs: + wasm32-test: + runs-on: ubuntu-latest + container: crystallang/crystal:1.6.1-build + steps: + - name: Download Crystal source + uses: actions/checkout@v2 + + - name: Install wasmtime + uses: mwilliamson/setup-wasmtime-action@v1 + with: + wasmtime-version: "2.0.0" + + - name: Install LLVM 13 + run: | + apt-get update + apt-get install -y curl lsb-release wget software-properties-common gnupg + curl -O https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + ./llvm.sh 13 + ln -s $(which wasm-ld-13) /usr/bin/wasm-ld + + - name: Download wasm32 libs + run: | + mkdir wasm32-wasi-libs + curl -LO https://github.com/lbguilherme/wasm-libs/releases/download/0.0.2/wasm32-wasi-libs.tar.gz + echo "114dd08b776c92e15b4ec83178fa486dc436e24b7f662c3241a8cdf2506fe426 wasm32-wasi-libs.tar.gz" | sha256sum -c - + tar -f wasm32-wasi-libs.tar.gz -C wasm32-wasi-libs -xz + rm wasm32-wasi-libs.tar.gz + + - name: Build spec/wasm32_std_spec.cr + run: bin/crystal build spec/wasm32_std_spec.cr -o wasm32_std_spec.wasm --target wasm32-wasi + env: + CRYSTAL_LIBRARY_PATH: ${{ github.workspace }}/wasm32-wasi-libs + + - name: Run wasm32_std_spec.wasm + run: | + wasmtime run wasm32_std_spec.wasm diff --git a/spec/generate_wasm32_spec.sh b/spec/generate_wasm32_spec.sh new file mode 100755 index 000000000000..a6388e0cc8e4 --- /dev/null +++ b/spec/generate_wasm32_spec.sh @@ -0,0 +1,61 @@ +#! /usr/bin/env bash +set +x + +# This script iterates through each spec file and tries to build and run it. +# +# * `failed codegen` annotates specs that error in the compiler. +# This is mostly caused by some API not being ported to wasm32 (either the spec +# target itself or some tools used by the spec). +# * `failed linking` annotates specs that compile but don't link. +# Most failures are caused by missing libraries (libxml2, libyaml, libgmp, +# libllvm, libz, libssl). +# * `failed to run` annotates specs that compile and link but don't properly +# execute. +# +# PREREQUISITES: +# +# - wasmtime (https://wasmtime.dev/) +# +# Note: Libs are downloaded from https://github.com/lbguilherme/wasm-libs +# +# USAGE: +# +# $ spec/generate_wasm32_spec.sh > spec/wasm32_std_spec.cr + +WORK_DIR=$(mktemp -d) +function cleanup { + rm -rf $WORK_DIR +} +trap cleanup EXIT + +mkdir "$WORK_DIR"/wasm32-wasi-libs +curl -L https://github.com/lbguilherme/wasm-libs/releases/download/0.0.2/wasm32-wasi-libs.tar.gz | tar -C "$WORK_DIR"/wasm32-wasi-libs -xz +export CRYSTAL_LIBRARY_PATH="$WORK_DIR"/wasm32-wasi-libs + +command="$0 $*" +echo "# This file is autogenerated by \`${command% }\`" +echo "# $(date --rfc-3339 seconds)" +echo + +for spec in $(find "spec/std" -type f -iname "*_spec.cr" | LC_ALL=C sort); do + require="require \"./${spec##spec/}\"" + target=$WORK_DIR"/"$spec".wasm" + mkdir -p $(dirname "$target") + + if ! output=$(bin/crystal build "$spec" -o "$target" --target wasm32-wasi 2>&1); then + if [[ "$output" =~ "execution of command failed" ]]; then + echo "# $require (failed linking)" + else + echo "# $require (failed codegen)" + fi + continue + fi + + wasmtime run "$target" > /dev/null; exit=$? + + if [ $exit -eq 0 ]; then + echo "$require" + else + echo "# $require (failed to run)" + fi +done diff --git a/spec/wasm32_std_spec.cr b/spec/wasm32_std_spec.cr new file mode 100644 index 000000000000..4e8e9cf7a0fd --- /dev/null +++ b/spec/wasm32_std_spec.cr @@ -0,0 +1,268 @@ +# This file is autogenerated by `spec/generate_wasm32_spec.sh` +# 2022-10-06 08:12:57-03:00 + +require "./std/array_spec.cr" +require "./std/atomic_spec.cr" +require "./std/base64_spec.cr" +# require "./std/benchmark_spec.cr" (failed to run) +# require "./std/big/big_decimal_spec.cr" (failed linking) +# require "./std/big/big_float_spec.cr" (failed linking) +# require "./std/big/big_int_spec.cr" (failed linking) +# require "./std/big/big_rational_spec.cr" (failed linking) +require "./std/big/number_spec.cr" +require "./std/bit_array_spec.cr" +require "./std/bool_spec.cr" +require "./std/box_spec.cr" +# require "./std/channel_spec.cr" (failed to run) +require "./std/char/reader_spec.cr" +# require "./std/char_spec.cr" (failed to run) +require "./std/class_spec.cr" +require "./std/colorize_spec.cr" +require "./std/comparable_spec.cr" +require "./std/complex_spec.cr" +# require "./std/compress/deflate/deflate_spec.cr" (failed linking) +# require "./std/compress/gzip/gzip_spec.cr" (failed linking) +# require "./std/compress/zip/zip_file_spec.cr" (failed linking) +# require "./std/compress/zip/zip_spec.cr" (failed linking) +# require "./std/compress/zlib/reader_spec.cr" (failed linking) +# require "./std/compress/zlib/stress_spec.cr" (failed linking) +# require "./std/compress/zlib/writer_spec.cr" (failed linking) +# require "./std/concurrent/select_spec.cr" (failed to run) +# require "./std/concurrent_spec.cr" (failed to run) +require "./std/crypto/bcrypt/base64_spec.cr" +require "./std/crypto/bcrypt/password_spec.cr" +require "./std/crypto/bcrypt_spec.cr" +require "./std/crypto/blowfish_spec.cr" +require "./std/crypto/subtle_spec.cr" +require "./std/crystal/compiler_rt/ashlti3_spec.cr" +require "./std/crystal/compiler_rt/ashrti3_spec.cr" +require "./std/crystal/compiler_rt/divmod128_spec.cr" +require "./std/crystal/compiler_rt/fixint_spec.cr" +require "./std/crystal/compiler_rt/float_spec.cr" +require "./std/crystal/compiler_rt/lshrti3_spec.cr" +require "./std/crystal/compiler_rt/mulodi4_spec.cr" +require "./std/crystal/compiler_rt/mulosi4_spec.cr" +require "./std/crystal/compiler_rt/muloti4_spec.cr" +require "./std/crystal/compiler_rt/multi3_spec.cr" +require "./std/crystal/compiler_rt/powidf2_spec.cr" +require "./std/crystal/compiler_rt/powisf2_spec.cr" +# require "./std/crystal/digest/md5_spec.cr" (failed to run) +# require "./std/crystal/digest/sha1_spec.cr" (failed to run) +# require "./std/crystal/hasher_spec.cr" (failed linking) +require "./std/crystal/pointer_linked_list_spec.cr" +# require "./std/crystal/syntax_highlighter/colorize_spec.cr" (failed to run) +# require "./std/crystal/syntax_highlighter/html_spec.cr" (failed linking) +require "./std/csv/csv_build_spec.cr" +require "./std/csv/csv_lex_spec.cr" +require "./std/csv/csv_parse_spec.cr" +require "./std/csv/csv_spec.cr" +require "./std/deque_spec.cr" +# require "./std/digest/adler32_spec.cr" (failed linking) +# require "./std/digest/crc32_spec.cr" (failed linking) +# require "./std/digest/io_digest_spec.cr" (failed linking) +# require "./std/digest/md5_spec.cr" (failed linking) +# require "./std/digest/sha1_spec.cr" (failed linking) +# require "./std/digest/sha256_spec.cr" (failed linking) +# require "./std/digest/sha512_spec.cr" (failed linking) +# require "./std/dir_spec.cr" (failed to run) +require "./std/double_spec.cr" +require "./std/ecr/ecr_lexer_spec.cr" +# require "./std/ecr/ecr_spec.cr" (failed linking) +require "./std/enum_spec.cr" +require "./std/enumerable_spec.cr" +# require "./std/env_spec.cr" (failed to run) +# require "./std/errno_spec.cr" (failed to run) +# require "./std/exception/call_stack_spec.cr" (failed to run) +# require "./std/exception_spec.cr" (failed codegen) +# require "./std/file/tempfile_spec.cr" (failed to run) +# require "./std/file_spec.cr" (failed linking) +# require "./std/file_utils_spec.cr" (failed linking) +require "./std/float_printer/diy_fp_spec.cr" +require "./std/float_printer/grisu3_spec.cr" +require "./std/float_printer/ieee_spec.cr" +# require "./std/float_printer_spec.cr" (failed to run) +require "./std/float_spec.cr" +require "./std/gc_spec.cr" +require "./std/hash_spec.cr" +require "./std/html_spec.cr" +# require "./std/http/chunked_content_spec.cr" (failed linking) +# require "./std/http/client/client_spec.cr" (failed linking) +# require "./std/http/client/response_spec.cr" (failed linking) +# require "./std/http/cookie_spec.cr" (failed linking) +# require "./std/http/formdata/builder_spec.cr" (failed linking) +# require "./std/http/formdata/parser_spec.cr" (failed linking) +# require "./std/http/formdata_spec.cr" (failed linking) +require "./std/http/headers_spec.cr" +# require "./std/http/http_spec.cr" (failed linking) +require "./std/http/params_spec.cr" +# require "./std/http/request_spec.cr" (failed linking) +# require "./std/http/server/handlers/compress_handler_spec.cr" (failed linking) +# require "./std/http/server/handlers/error_handler_spec.cr" (failed linking) +# require "./std/http/server/handlers/handler_spec.cr" (failed linking) +# require "./std/http/server/handlers/log_handler_spec.cr" (failed linking) +# require "./std/http/server/handlers/static_file_handler_spec.cr" (failed linking) +# require "./std/http/server/handlers/websocket_handler_spec.cr" (failed linking) +# require "./std/http/server/request_processor_spec.cr" (failed linking) +# require "./std/http/server/response_spec.cr" (failed linking) +# require "./std/http/server/server_spec.cr" (failed linking) +# require "./std/http/status_spec.cr" (failed linking) +# require "./std/http/web_socket_spec.cr" (failed linking) +# require "./std/humanize_spec.cr" (failed to run) +require "./std/indexable/mutable_spec.cr" +require "./std/indexable_spec.cr" +# require "./std/ini_spec.cr" (failed to run) +# require "./std/int_spec.cr" (failed linking) +# require "./std/io/argf_spec.cr" (failed to run) +# require "./std/io/buffered_spec.cr" (failed to run) +require "./std/io/byte_format_spec.cr" +require "./std/io/delimited_spec.cr" +# require "./std/io/file_descriptor_spec.cr" (failed to run) +# require "./std/io/hexdump_spec.cr" (failed to run) +# require "./std/io/io_spec.cr" (failed linking) +# require "./std/io/memory_spec.cr" (failed to run) +# require "./std/io/multi_writer_spec.cr" (failed to run) +require "./std/io/sized_spec.cr" +# require "./std/io/stapled_spec.cr" (failed to run) +require "./std/iterator_spec.cr" +# require "./std/json/any_spec.cr" (failed linking) +require "./std/json/builder_spec.cr" +require "./std/json/lexer_spec.cr" +require "./std/json/parser_spec.cr" +# require "./std/json/pull_parser_spec.cr" (failed to run) +# require "./std/json/serializable_spec.cr" (failed codegen) +# require "./std/json/serialization_spec.cr" (failed codegen) +# require "./std/kernel_spec.cr" (failed to run) +require "./std/levenshtein_spec.cr" +# require "./std/llvm/aarch64_spec.cr" (failed linking) +# require "./std/llvm/arm_abi_spec.cr" (failed linking) +# require "./std/llvm/llvm_spec.cr" (failed linking) +# require "./std/llvm/type_spec.cr" (failed linking) +# require "./std/llvm/x86_64_abi_spec.cr" (failed linking) +# require "./std/llvm/x86_abi_spec.cr" (failed linking) +# require "./std/log/broadcast_backend_spec.cr" (failed to run) +# require "./std/log/builder_spec.cr" (failed to run) +# require "./std/log/context_spec.cr" (failed to run) +# require "./std/log/dispatch_spec.cr" (failed to run) +require "./std/log/env_config_spec.cr" +# require "./std/log/format_spec.cr" (failed codegen) +# require "./std/log/io_backend_spec.cr" (failed to run) +# require "./std/log/log_spec.cr" (failed to run) +require "./std/log/main_spec.cr" +# require "./std/log/metadata_spec.cr" (failed to run) +# require "./std/log/spec_spec.cr" (failed to run) +require "./std/match_data_spec.cr" +# require "./std/math_spec.cr" (failed to run) +# require "./std/mime/media_type_spec.cr" (failed linking) +# require "./std/mime/multipart/builder_spec.cr" (failed linking) +# require "./std/mime/multipart/parser_spec.cr" (failed linking) +# require "./std/mime/multipart_spec.cr" (failed linking) +# require "./std/mime_spec.cr" (failed to run) +# require "./std/mutex_spec.cr" (failed to run) +require "./std/named_tuple_spec.cr" +# require "./std/number_spec.cr" (failed linking) +# require "./std/oauth/access_token_spec.cr" (failed linking) +# require "./std/oauth/authorization_header_spec.cr" (failed linking) +# require "./std/oauth/consumer_spec.cr" (failed linking) +# require "./std/oauth/params_spec.cr" (failed linking) +# require "./std/oauth/request_token_spec.cr" (failed linking) +# require "./std/oauth/signature_spec.cr" (failed linking) +# require "./std/oauth2/access_token_spec.cr" (failed linking) +# require "./std/oauth2/client_spec.cr" (failed linking) +# require "./std/oauth2/session_spec.cr" (failed linking) +# require "./std/object_spec.cr" (failed to run) +# require "./std/openssl/cipher_spec.cr" (failed linking) +# require "./std/openssl/digest_spec.cr" (failed linking) +# require "./std/openssl/hmac_spec.cr" (failed linking) +# require "./std/openssl/pkcs5_spec.cr" (failed linking) +# require "./std/openssl/ssl/context_spec.cr" (failed linking) +# require "./std/openssl/ssl/hostname_validation_spec.cr" (failed linking) +# require "./std/openssl/ssl/server_spec.cr" (failed linking) +# require "./std/openssl/ssl/socket_spec.cr" (failed linking) +# require "./std/openssl/x509/certificate_spec.cr" (failed linking) +# require "./std/openssl/x509/name_spec.cr" (failed linking) +require "./std/option_parser_spec.cr" +# require "./std/path_spec.cr" (failed to run) +require "./std/pointer_spec.cr" +require "./std/pp_spec.cr" +require "./std/pretty_print_spec.cr" +require "./std/proc_spec.cr" +# require "./std/process/find_executable_spec.cr" (failed to run) +require "./std/process_spec.cr" +# require "./std/raise_spec.cr" (failed codegen) +require "./std/random/isaac_spec.cr" +require "./std/random/pcg32_spec.cr" +require "./std/random/secure_spec.cr" +# require "./std/random_spec.cr" (failed linking) +# require "./std/range_spec.cr" (failed linking) +require "./std/record_spec.cr" +# require "./std/reference_spec.cr" (failed to run) +# require "./std/regex_spec.cr" (failed to run) +require "./std/semantic_version_spec.cr" +require "./std/set_spec.cr" +require "./std/signal_spec.cr" +require "./std/slice_spec.cr" +# require "./std/socket/address_spec.cr" (failed to run) +# require "./std/socket/addrinfo_spec.cr" (failed to run) +# require "./std/socket/socket_spec.cr" (failed to run) +require "./std/socket/tcp_server_spec.cr" +require "./std/socket/tcp_socket_spec.cr" +# require "./std/socket/udp_socket_spec.cr" (failed to run) +# require "./std/socket/unix_server_spec.cr" (failed to run) +# require "./std/socket/unix_socket_spec.cr" (failed to run) +# require "./std/spec/context_spec.cr" (failed to run) +require "./std/spec/expectations_spec.cr" +# require "./std/spec/filters_spec.cr" (failed to run) +require "./std/spec/helpers/iterate_spec.cr" +# require "./std/spec/hooks_spec.cr" (failed to run) +# require "./std/spec/junit_formatter_spec.cr" (failed linking) +require "./std/spec/tap_formatter_spec.cr" +# require "./std/spec_spec.cr" (failed codegen) +# require "./std/sprintf_spec.cr" (failed linking) +require "./std/static_array_spec.cr" +require "./std/string/grapheme_break_spec.cr" +require "./std/string/grapheme_spec.cr" +require "./std/string/utf16_spec.cr" +require "./std/string_builder_spec.cr" +require "./std/string_pool_spec.cr" +require "./std/string_scanner_spec.cr" +# require "./std/string_spec.cr" (failed to run) +# require "./std/struct_spec.cr" (failed linking) +require "./std/symbol_spec.cr" +require "./std/syscall_spec.cr" +# require "./std/system/group_spec.cr" (failed to run) +# require "./std/system/user_spec.cr" (failed to run) +# require "./std/system_error_spec.cr" (failed to run) +# require "./std/system_spec.cr" (failed to run) +# require "./std/thread/condition_variable_spec.cr" (failed to run) +# require "./std/thread/mutex_spec.cr" (failed to run) +# require "./std/thread_spec.cr" (failed to run) +require "./std/time/custom_formats_spec.cr" +# require "./std/time/format_spec.cr" (failed to run) +# require "./std/time/location_spec.cr" (failed to run) +require "./std/time/span_spec.cr" +# require "./std/time/time_spec.cr" (failed to run) +require "./std/tuple_spec.cr" +require "./std/uint_spec.cr" +require "./std/uri/params_spec.cr" +require "./std/uri/punycode_spec.cr" +# require "./std/uri_spec.cr" (failed linking) +require "./std/uuid/json_spec.cr" +# require "./std/uuid/yaml_spec.cr" (failed linking) +require "./std/uuid_spec.cr" +# require "./std/va_list_spec.cr" (failed to run) +# require "./std/weak_ref_spec.cr" (failed to run) +require "./std/winerror_spec.cr" +# require "./std/xml/builder_spec.cr" (failed linking) +# require "./std/xml/html_spec.cr" (failed linking) +# require "./std/xml/reader_spec.cr" (failed linking) +# require "./std/xml/xml_spec.cr" (failed linking) +# require "./std/xml/xpath_spec.cr" (failed linking) +# require "./std/yaml/any_spec.cr" (failed linking) +# require "./std/yaml/builder_spec.cr" (failed codegen) +# require "./std/yaml/nodes/builder_spec.cr" (failed codegen) +# require "./std/yaml/schema/core_spec.cr" (failed codegen) +# require "./std/yaml/schema/fail_safe_spec.cr" (failed codegen) +# require "./std/yaml/serializable_spec.cr" (failed codegen) +# require "./std/yaml/serialization_spec.cr" (failed codegen) +# require "./std/yaml/yaml_pull_parser_spec.cr" (failed codegen) +# require "./std/yaml/yaml_spec.cr" (failed codegen) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index 2344dbc5f2f7..61df03294875 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -175,7 +175,10 @@ class Crystal::Scheduler end protected def yield : Nil - sleep(0.seconds) + # TODO: Fiber switching and libevent for wasm32 + {% unless flag?(:wasm32) %} + sleep(0.seconds) + {% end %} end protected def yield(fiber : Fiber) : Nil diff --git a/src/spec/expectations.cr b/src/spec/expectations.cr index 399f5be6f253..fa37ca4def5c 100644 --- a/src/spec/expectations.cr +++ b/src/spec/expectations.cr @@ -382,45 +382,51 @@ module Spec # If *message* is a regular expression, it is used to match the error message. # # It returns the rescued exception. - def expect_raises(klass : T.class, message : String | Regex | Nil = nil, file = __FILE__, line = __LINE__) forall T - yield - rescue ex : T - # We usually bubble Spec::AssertionFailed, unless this is the expected exception - if ex.is_a?(Spec::AssertionFailed) && klass != Spec::AssertionFailed - raise ex - end - - # `NestingSpecError` is treated as the same above. - if ex.is_a?(Spec::NestingSpecError) && klass != Spec::NestingSpecError - raise ex + {% if flag?(:wasm32) %} + def expect_raises(klass : T.class, message : String | Regex | Nil = nil, file = __FILE__, line = __LINE__, &) forall T + # TODO: Enable "expect_raises" for wasm32 after exceptions are working. end + {% else %} + def expect_raises(klass : T.class, message : String | Regex | Nil = nil, file = __FILE__, line = __LINE__) forall T + yield + rescue ex : T + # We usually bubble Spec::AssertionFailed, unless this is the expected exception + if ex.is_a?(Spec::AssertionFailed) && klass != Spec::AssertionFailed + raise ex + end - ex_to_s = ex.to_s - case message - when Regex - unless (ex_to_s =~ message) - backtrace = ex.backtrace.join('\n') { |f| " # #{f}" } - fail "Expected #{klass} with message matching #{message.inspect}, " \ - "got #<#{ex.class}: #{ex_to_s}> with backtrace:\n#{backtrace}", file, line + # `NestingSpecError` is treated as the same above. + if ex.is_a?(Spec::NestingSpecError) && klass != Spec::NestingSpecError + raise ex end - when String - unless ex_to_s.includes?(message) - backtrace = ex.backtrace.join('\n') { |f| " # #{f}" } - fail "Expected #{klass} with #{message.inspect}, got #<#{ex.class}: " \ - "#{ex_to_s}> with backtrace:\n#{backtrace}", file, line + + ex_to_s = ex.to_s + case message + when Regex + unless (ex_to_s =~ message) + backtrace = ex.backtrace.join('\n') { |f| " # #{f}" } + fail "Expected #{klass} with message matching #{message.inspect}, " \ + "got #<#{ex.class}: #{ex_to_s}> with backtrace:\n#{backtrace}", file, line + end + when String + unless ex_to_s.includes?(message) + backtrace = ex.backtrace.join('\n') { |f| " # #{f}" } + fail "Expected #{klass} with #{message.inspect}, got #<#{ex.class}: " \ + "#{ex_to_s}> with backtrace:\n#{backtrace}", file, line + end + when Nil + # No need to check the message end - when Nil - # No need to check the message - end - ex - rescue ex - backtrace = ex.backtrace.join('\n') { |f| " # #{f}" } - fail "Expected #{klass}, got #<#{ex.class}: #{ex}> with backtrace:\n" \ - "#{backtrace}", file, line - else - fail "Expected #{klass} but nothing was raised", file, line - end + ex + rescue ex + backtrace = ex.backtrace.join('\n') { |f| " # #{f}" } + fail "Expected #{klass}, got #<#{ex.class}: #{ex}> with backtrace:\n" \ + "#{backtrace}", file, line + else + fail "Expected #{klass} but nothing was raised", file, line + end + {% end %} end module ObjectExtensions From c18b00e3e4933318a2ffea9c72abf037fef0d28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 28 Nov 2022 15:32:06 +0100 Subject: [PATCH 0156/1551] Implement `flock_*` for Win32 (#12766) --- spec/std/file_spec.cr | 11 +++-- src/crystal/system/win32/file.cr | 45 +++++++++++++++++-- src/lib_c/x86_64-windows-msvc/c/fileapi.cr | 15 +++++++ src/lib_c/x86_64-windows-msvc/c/minwinbase.cr | 3 ++ 4 files changed, 65 insertions(+), 9 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index a393049f596a..e9270a48b483 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -933,22 +933,21 @@ describe "File" do end end - # TODO: implement flock on windows describe "flock" do - pending_win32 "exclusively locks a file" do + it "#flock_exclusive" do File.open(datapath("test_file.txt")) do |file1| File.open(datapath("test_file.txt")) do |file2| file1.flock_exclusive do exc = expect_raises(IO::Error, "Error applying file lock: file is already locked") do file2.flock_exclusive(blocking: false) { } end - exc.os_error.should eq Errno::EWOULDBLOCK + exc.os_error.should eq({% if flag?(:win32) %}WinError::ERROR_LOCK_VIOLATION{% else %}Errno::EWOULDBLOCK{% end %}) end end end end - pending_win32 "shared locks a file" do + it "#flock_shared" do File.open(datapath("test_file.txt")) do |file1| File.open(datapath("test_file.txt")) do |file2| file1.flock_shared do @@ -958,7 +957,7 @@ describe "File" do end end - pending_win32 "#flock_shared soft blocking fiber" do + it "#flock_shared soft blocking fiber" do File.open(datapath("test_file.txt")) do |file1| File.open(datapath("test_file.txt")) do |file2| done = Channel(Nil).new @@ -975,7 +974,7 @@ describe "File" do end end - pending_win32 "#flock_exclusive soft blocking fiber" do + it "#flock_exclusive soft blocking fiber" do File.open(datapath("test_file.txt")) do |file1| File.open(datapath("test_file.txt")) do |file2| done = Channel(Nil).new diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 0df1926cc047..bda41cde8b69 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -258,15 +258,54 @@ module Crystal::System::File end private def system_flock_shared(blocking : Bool) : Nil - raise NotImplementedError.new("File#flock_shared") + flock(false, blocking) end private def system_flock_exclusive(blocking : Bool) : Nil - raise NotImplementedError.new("File#flock_exclusive") + flock(true, blocking) end private def system_flock_unlock : Nil - raise NotImplementedError.new("File#flock_unlock") + unlock_file(windows_handle) + end + + private def flock(exclusive, retry) + flags = LibC::LOCKFILE_FAIL_IMMEDIATELY + flags |= LibC::LOCKFILE_EXCLUSIVE_LOCK if exclusive + + handle = windows_handle + if retry + until lock_file(handle, flags) + ::Fiber.yield + end + else + lock_file(handle, flags) || raise IO::Error.from_winerror("Error applying file lock: file is already locked") + end + end + + private def lock_file(handle, flags) + # lpOverlapped must be provided despite the synchronous use of this method. + overlapped = LibC::OVERLAPPED.new + # lock the entire file with offset 0 in overlapped and number of bytes set to max value + if 0 != LibC.LockFileEx(handle, flags, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped)) + true + else + winerror = WinError.value + if winerror == WinError::ERROR_LOCK_VIOLATION + false + else + raise IO::Error.from_winerror("LockFileEx", winerror) + end + end + end + + private def unlock_file(handle) + # lpOverlapped must be provided despite the synchronous use of this method. + overlapped = LibC::OVERLAPPED.new + # unlock the entire file with offset 0 in overlapped and number of bytes set to max value + if 0 == LibC.UnlockFileEx(handle, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped)) + raise IO::Error.from_winerror("UnLockFileEx") + end end private def system_fsync(flush_metadata = true) : Nil diff --git a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr index 42533d369411..701a9590ca22 100644 --- a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr @@ -75,6 +75,21 @@ lib LibC fun FindNextFileW(hFindFile : HANDLE, lpFindFileData : WIN32_FIND_DATAW*) : BOOL fun FindClose(hFindFile : HANDLE) : BOOL + fun LockFileEx( + hFile : HANDLE, + dwFlags : DWORD, + dwReserved : DWORD, + nNumberOfBytesToLockLow : DWORD, + nNumberOfBytesToLockHigh : DWORD, + lpOverlapped : OVERLAPPED* + ) : BOOL + fun UnlockFileEx( + hFile : HANDLE, + dwReserved : DWORD, + nNumberOfBytesToUnlockLow : DWORD, + nNumberOfBytesToUnlockHigh : DWORD, + lpOverlapped : OVERLAPPED* + ) : BOOL fun SetFileTime(hFile : HANDLE, lpCreationTime : FILETIME*, lpLastAccessTime : FILETIME*, lpLastWriteTime : FILETIME*) : BOOL end diff --git a/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr b/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr index d01b5232e387..cb56e3269de2 100644 --- a/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr @@ -45,6 +45,9 @@ lib LibC GetFileExMaxInfoLevel end + LOCKFILE_FAIL_IMMEDIATELY = DWORD.new(0x00000001) + LOCKFILE_EXCLUSIVE_LOCK = DWORD.new(0x00000002) + STATUS_PENDING = 0x103 STILL_ACTIVE = STATUS_PENDING end From 424df1efcec25da22054704756622a2d5bc9b861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 28 Nov 2022 21:55:49 +0100 Subject: [PATCH 0157/1551] Add docs to `ENV#has_key?` (#12781) Co-authored-by: Jack Thorne --- src/env.cr | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/env.cr b/src/env.cr index 6e5b47104288..b28e4014ea22 100644 --- a/src/env.cr +++ b/src/env.cr @@ -40,8 +40,12 @@ module ENV value end - # Returns `true` if the environment variable named *key* exists and `false` - # if it doesn't. + # Returns `true` if the environment variable named *key* exists and `false` if it doesn't. + # + # ``` + # ENV.has_key?("NOT_A_REAL_KEY") # => false + # ENV.has_key?("PATH") # => true + # ``` def self.has_key?(key : String) : Bool Crystal::System::Env.has_key?(key) end From 37eaa39a35e2d9ce2957adb2bdb6c0e1d5b8064e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 29 Nov 2022 11:50:46 +0100 Subject: [PATCH 0158/1551] Improve specs by removing absolute path references (#12776) --- spec/std/file_spec.cr | 55 +++++++++++++--------------------------- spec/std/process_spec.cr | 26 ++++++++++--------- 2 files changed, 31 insertions(+), 50 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index e9270a48b483..ea71a9e2497f 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -1,24 +1,5 @@ require "./spec_helper" -private def base - Dir.current -end - -private def tmpdir - "/tmp" -end - -private def rootdir - "/" -end - -private def home - home = ENV["HOME"] - return home if home == "/" - - home.chomp('/') -end - private def it_raises_on_null_byte(operation, file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block) it "errors on #{operation}", file, line, end_line do expect_raises(ArgumentError, "String contains null byte") do @@ -354,8 +335,8 @@ describe "File" do it "chown" do # changing owners requires special privileges, so we test that method calls do compile - typeof(File.chown("/tmp/test")) - typeof(File.chown("/tmp/test", uid: 1001, gid: 100, follow_symlinks: true)) + typeof(File.chown(".")) + typeof(File.chown(".", uid: 1001, gid: 100, follow_symlinks: true)) File.open(File::NULL, "w") do |file| typeof(file.chown) @@ -596,30 +577,27 @@ describe "File" do end end - describe "real_path" do + describe "#realpath" do it "expands paths for normal files" do - {% if flag?(:win32) %} - File.real_path("C:\\Windows").should eq(File.real_path("C:\\Windows")) - File.real_path("C:\\Windows\\..").should eq(File.real_path("C:\\")) - {% else %} - File.real_path("/usr/share").should eq(File.real_path("/usr/share")) - File.real_path("/usr/share/..").should eq(File.real_path("/usr")) - {% end %} + path = File.join(File.realpath("."), datapath("dir")) + File.realpath(path).should eq(path) + File.realpath(File.join(path, "..")).should eq(File.dirname(path)) end it "raises if file doesn't exist" do - expect_raises(File::NotFoundError, "Error resolving real path: '/usr/share/foo/bar'") do - File.real_path("/usr/share/foo/bar") + path = datapath("doesnotexist") + expect_raises(File::NotFoundError, "Error resolving real path: '#{path.inspect_unquoted}'") do + File.realpath(path) end end - # TODO: see Crystal::System::File.real_path TODO + # TODO: see Crystal::System::File.realpath TODO pending_win32 "expands paths of symlinks" do file_path = File.expand_path(datapath("test_file.txt")) with_tempfile("symlink.txt") do |symlink_path| File.symlink(file_path, symlink_path) - real_symlink_path = File.real_path(symlink_path) - real_file_path = File.real_path(file_path) + real_symlink_path = File.realpath(symlink_path) + real_file_path = File.realpath(file_path) real_symlink_path.should eq(real_file_path) end end @@ -1318,10 +1296,11 @@ describe "File" do end end - # TODO: there is no file which is reliably nonwriteable on windows - pending_win32 "raises if file cannot be accessed" do - expect_raises(File::Error, "Error setting time on file: '/bin/ls'") do - File.touch("/bin/ls") + it "raises if file cannot be accessed" do + # This path is invalid because it represents a file path as a directory path + path = File.join(datapath("test_file.txt"), "doesnotexist") + expect_raises(File::Error, path.inspect_unquoted) do + File.touch(path) end end end diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 2d40623f4738..3655dad0a3f7 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -166,19 +166,21 @@ describe Process do $?.exit_code.should eq(0) end - pending_win32 "chroot raises when unprivileged" do - status, output, _ = compile_and_run_source <<-'CODE' - begin - Process.chroot("/usr") - puts "FAIL" - rescue ex - puts ex.inspect - end - CODE + {% if flag?(:unix) %} + it "chroot raises when unprivileged" do + status, output, _ = compile_and_run_source <<-'CODE' + begin + Process.chroot(".") + puts "FAIL" + rescue ex + puts ex.inspect + end + CODE - status.success?.should be_true - output.should eq("#\n") - end + status.success?.should be_true + output.should eq("#\n") + end + {% end %} it "sets working directory" do parent = File.dirname(Dir.current) From 1a2d6801807be44f1377255c99b55bc59c0e382e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 29 Nov 2022 17:41:44 +0100 Subject: [PATCH 0159/1551] Re-organize and enhance specs for `Regex` (#12788) --- spec/std/regex_spec.cr | 452 ++++++++++++++++++++++++++++++----------- 1 file changed, 329 insertions(+), 123 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 918222241f5a..24ac68b35f9a 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -1,116 +1,200 @@ require "./spec_helper" describe "Regex" do - it "compare to other instances" do - Regex.new("foo").should eq(Regex.new("foo")) - Regex.new("foo").should_not eq(Regex.new("bar")) + describe ".new" do + it "doesn't crash when PCRE tries to free some memory (#771)" do + expect_raises(ArgumentError) { Regex.new("foo)") } + end + + it "raises exception with invalid regex" do + expect_raises(ArgumentError) { Regex.new("+") } + end end - it "does =~" do - (/foo/ =~ "bar foo baz").should eq(4) - $~.group_size.should eq(0) + it "#options" do + /cat/.options.ignore_case?.should be_false + /cat/i.options.ignore_case?.should be_true + /cat/.options.multiline?.should be_false + /cat/m.options.multiline?.should be_true + /cat/.options.extended?.should be_false + /cat/x.options.extended?.should be_true + /cat/mx.options.multiline?.should be_true + /cat/mx.options.extended?.should be_true + /cat/mx.options.ignore_case?.should be_false + /cat/xi.options.ignore_case?.should be_true + /cat/xi.options.extended?.should be_true + /cat/xi.options.multiline?.should be_false end - it "does inspect" do - /foo/.inspect.should eq("/foo/") - /foo/.inspect.should eq("/foo/") - /foo/imx.inspect.should eq("/foo/imx") + it "#source" do + /foo/.source.should eq "foo" + /(foo|bar)*/.source.should eq "(foo|bar)*" + /foo\x96/.source.should eq "foo\\x96" + Regex.new("").source.should eq "" end - it "does to_s" do - /foo/.to_s.should eq("(?-imsx:foo)") - /foo/im.to_s.should eq("(?ims-x:foo)") - /foo/imx.to_s.should eq("(?imsx-:foo)") + describe "#match" do + it "returns matchdata" do + md = /(?.)(?.)/.match("Crystal").should_not be_nil + md[0].should eq "Cr" + md.captures.should eq [] of String + md.named_captures.should eq({"bar" => "C", "foo" => "r"}) + end - "Crystal".match(/(?C)#{/(?R)/i}/).should be_truthy - "Crystal".match(/(?C)#{/(?R)/}/i).should be_falsey + it "assigns captures" do + matchdata = /foo/.match("foo") + $~.should eq(matchdata) - md = "Crystal".match(/(?.)#{/(?.)/}/).not_nil! - md[0].should eq("Cr") - md["bar"].should eq("C") - md["foo"].should eq("r") - end + /foo/.match("bar") + expect_raises(NilAssertionError) { $~ } + end - it "does inspect with slash" do - %r(/).inspect.should eq("/\\//") - %r(\/).inspect.should eq("/\\//") - end + it "returns nil on non-match" do + /Crystal/.match("foo").should be_nil + end - it "does to_s with slash" do - %r(/).to_s.should eq("(?-imsx:\\/)") - %r(\/).to_s.should eq("(?-imsx:\\/)") - end + describe "with pos" do + it "positive" do + /foo/.match("foo", 0).should_not be_nil + /foo/.match("foo", 1).should be_nil + /foo/.match(".foo", 1).should_not be_nil + /foo/.match("..foo", 1).should_not be_nil + + /foo/.match("bar", 0).should be_nil + /foo/.match("bar", 1).should be_nil + end + + it "char index" do + /foo/.match("öfoo", 1).should_not be_nil + end + + pending "negative" do + /foo/.match("..foo", -3).should_not be_nil + /foo/.match("..foo", -2).should be_nil + end + end - it "doesn't crash when PCRE tries to free some memory (#771)" do - expect_raises(ArgumentError) { Regex.new("foo)") } + it "with options" do + /foo/.match(".foo", options: Regex::Options::ANCHORED).should be_nil + end end - it "checks if Char need to be escaped" do - Regex.needs_escape?('*').should be_true - Regex.needs_escape?('|').should be_true - Regex.needs_escape?('@').should be_false - end + describe "#match_at_byte_index" do + it "assigns captures" do + matchdata = /foo/.match_at_byte_index("..foo", 1) + $~.should eq(matchdata) - it "checks if String need to be escaped" do - Regex.needs_escape?("10$").should be_true - Regex.needs_escape?("foo").should be_false - end + /foo/.match_at_byte_index("foo", 1) + expect_raises(NilAssertionError) { $~ } - it "escapes" do - Regex.escape(" .\\+*?[^]$(){}=!<>|:-hello").should eq("\\ \\.\\\\\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:\\-hello") - end + /foo/.match("foo") # make sure $~ is assigned + $~.should_not be_nil - it "matches ignore case" do - ("HeLlO" =~ /hello/).should be_nil - ("HeLlO" =~ /hello/i).should eq(0) - end + /foo/.match_at_byte_index("foo", 5) + expect_raises(NilAssertionError) { $~ } + end - it "matches lines beginnings on ^ in multiline mode" do - ("foo\nbar" =~ /^bar/).should be_nil - ("foo\nbar" =~ /^bar/m).should eq(4) - end + it "positive index" do + md = /foo/.match_at_byte_index("foo", 0).should_not be_nil + md.begin.should eq 0 + /foo/.match_at_byte_index("foo", 1).should be_nil + md = /foo/.match_at_byte_index(".foo", 1).should_not be_nil + md.begin.should eq 1 + md = /foo/.match_at_byte_index("..foo", 1).should_not be_nil + md.begin.should eq 2 + /foo/.match_at_byte_index("foo", 5).should be_nil + + /foo/.match_at_byte_index("bar", 0).should be_nil + /foo/.match_at_byte_index("bar", 1).should be_nil + end - it "matches multiline" do - ("foo\nbaz" =~ //).should be_nil - ("foo\nbaz" =~ //m).should eq(4) - end + it "multibyte index" do + md = /foo/.match_at_byte_index("öfoo", 1).should_not be_nil + md.begin.should eq 1 + md.byte_begin.should eq 2 - it "matches unicode char against [[:print:]] (#11262)" do - ("\n☃" =~ /[[:print:]]/).should eq(1) - end + md = /foo/.match_at_byte_index("öfoo", 2).should_not be_nil + md.begin.should eq 1 + md.byte_begin.should eq 2 + end - it "matches unicode char against [[:alnum:]] (#4704)" do - /[[:alnum:]]/x.match("à").should_not be_nil - end + pending "negative" do + md = /foo/.match_at_byte_index("..foo", -3).should_not be_nil + md.begin.should eq 0 + /foo/.match_at_byte_index("..foo", -2).should be_nil + end - it "matches with =~ and captures" do - ("fooba" =~ /f(o+)(bar?)/).should eq(0) - $~.group_size.should eq(2) - $1.should eq("oo") - $2.should eq("ba") + it "with options" do + /foo/.match_at_byte_index("..foo", 1, options: Regex::Options::ANCHORED).should be_nil + end end - it "matches with =~ and gets utf-8 codepoint index" do - index = "こんに" =~ /ん/ - index.should eq(1) - end + describe "#matches?" do + it "basic" do + /foo/.matches?("foo").should be_true + expect_raises(NilAssertionError) { $~ } + /foo/.matches?("bar").should be_false + expect_raises(NilAssertionError) { $~ } + end - it "matches with === and captures" do - "foo" =~ /foo/ - (/f(o+)(bar?)/ === "fooba").should be_true - $~.group_size.should eq(2) - $1.should eq("oo") - $2.should eq("ba") - end + describe "options" do + it "ignore case" do + /hello/.matches?("HeLlO").should be_false + /hello/i.matches?("HeLlO").should be_true + end + + describe "multiline" do + it "anchor" do + /^bar/.matches?("foo\nbar").should be_false + /^bar/m.matches?("foo\nbar").should be_true + end + + it "span" do + //.matches?("foo\nbaz").should be_false + //m.matches?("foo\nbaz").should be_true + end + end + + describe "extended" do + it "ignores white space" do + /foo bar/.matches?("foobar").should be_false + /foo bar/x.matches?("foobar").should be_true + end + + it "ignores comments" do + /foo#comment\nbar/.matches?("foobar").should be_false + /foo#comment\nbar/x.matches?("foobar").should be_true + end + end + + it "anchored" do + Regex.new("foo", Regex::Options::ANCHORED).matches?("foo").should be_true + Regex.new("foo", Regex::Options::ANCHORED).matches?(".foo").should be_false + end + end - describe "#matches?" do - it "matches but create no MatchData" do - /f(o+)(bar?)/.matches?("fooba").should be_true - /f(o+)(bar?)/.matches?("barfo").should be_false + describe "unicode" do + it "unicode support" do + /ん/.matches?("こんに").should be_true + end + + it "matches unicode char against [[:alnum:]] (#4704)" do + /[[:alnum:]]/.matches?("à").should be_true + end + + it "matches unicode char against [[:print:]] (#11262)" do + /[[:print:]]/.matches?("\n☃").should be_true + end + + it "invalid codepoint" do + /foo/.matches?("f\x96o").should be_false + /f\x96o/.matches?("f\x96o").should be_false + /f.o/.matches?("f\x96o").should be_true + end end - it "can specify initial position of matching" do - /f(o+)(bar?)/.matches?("fooba", 1).should be_false + it "with options" do + /foo/.matches?(".foo", options: Regex::Options::ANCHORED).should be_false end it "matches a large single line string" do @@ -122,33 +206,172 @@ describe "Regex" do end end - describe "name_table" do + describe "#matches_at_byte_index?" do + it "positive index" do + /foo/.matches_at_byte_index?("foo", 0).should be_true + /foo/.matches_at_byte_index?("foo", 1).should be_false + /foo/.matches_at_byte_index?(".foo", 1).should be_true + /foo/.matches_at_byte_index?("..foo", 1).should be_true + /foo/.matches_at_byte_index?("foo", 5).should be_false + + /foo/.matches_at_byte_index?("bar", 0).should be_false + /foo/.matches_at_byte_index?("bar", 1).should be_false + end + + it "multibyte index" do + /foo/.matches_at_byte_index?("öfoo", 1).should be_true + end + + pending "negative" do + /foo/.matches_at_byte_index?("..foo", -3).should be_true + /foo/.matches_at_byte_index?("..foo", -2).should be_false + end + + it "with options" do + /foo/.matches_at_byte_index?("..foo", 1, options: Regex::Options::ANCHORED).should be_false + end + end + + describe "#===" do + it "basic" do + (/f(o+)(bar?)/ === "fooba").should be_true + (/f(o+)(bar?)/ === "pooba").should be_false + end + + it "assigns captures" do + /f(o+)(bar?)/ === "fooba" + $~.group_size.should eq(2) + $1.should eq("oo") + $2.should eq("ba") + + /f(o+)(bar?)/ === "pooba" + expect_raises(NilAssertionError) { $~ } + end + end + + describe "#=~" do + it "returns match index or nil" do + (/foo/ =~ "bar foo baz").should eq(4) + (/foo/ =~ "bar boo baz").should be_nil + end + + it "assigns captures" do + "fooba" =~ /f(o+)(bar?)/ + $~.group_size.should eq(2) + $1.should eq("oo") + $2.should eq("ba") + + /foo/ =~ "bar boo baz" + expect_raises(NilAssertionError) { $~ } + end + + it "accepts any type" do + (/foo/ =~ nil).should be_nil + (/foo/ =~ 1).should be_nil + (/foo/ =~ [1, 2]).should be_nil + (/foo/ =~ true).should be_nil + end + end + + describe "#name_table" do it "is a map of capture group number to name" do - table = (/(? (?(\d\d)?\d\d) - (?\d\d) - (?\d\d) )/x).name_table - table[1].should eq("date") - table[2].should eq("year") - table[3]?.should be_nil - table[4].should eq("month") - table[5].should eq("day") + (/(? (?(\d\d)?\d\d) - (?\d\d) - (?\d\d) )/x).name_table.should eq({ + 1 => "date", + 2 => "year", + 4 => "month", + 5 => "day", + }) end + + it "alpanumeric" do + /(?)/.name_table.should eq({1 => "f1"}) + end + + it "duplicate name" do + /(?)(?)/.name_table.should eq({1 => "foo", 2 => "foo"}) + end + end + + it "#capture_count" do + /(?:.)/x.capture_count.should eq(0) + /(?.+)/.capture_count.should eq(1) + /(.)?/x.capture_count.should eq(1) + /(.)|(.)/x.capture_count.should eq(2) end - describe "capture_count" do - it "returns the number of (named & non-named) capture groups" do - /(?:.)/x.capture_count.should eq(0) - /(?.+)/.capture_count.should eq(1) - /(.)?/x.capture_count.should eq(1) - /(.)|(.)/x.capture_count.should eq(2) + describe "#inspect" do + it "with options" do + /foo/.inspect.should eq("/foo/") + /foo/im.inspect.should eq("/foo/im") + /foo/imx.inspect.should eq("/foo/imx") + end + + it "escapes" do + %r(/).inspect.should eq("/\\//") + %r(\/).inspect.should eq("/\\//") end end - it "raises exception with invalid regex" do - expect_raises(ArgumentError) { Regex.new("+") } + describe "#to_s" do + it "with options" do + /foo/.to_s.should eq("(?-imsx:foo)") + /foo/im.to_s.should eq("(?ims-x:foo)") + /foo/imx.to_s.should eq("(?imsx-:foo)") + end + + it "with slash" do + %r(/).to_s.should eq("(?-imsx:\\/)") + %r(\/).to_s.should eq("(?-imsx:\\/)") + end + + it "interpolation" do + regex = /(?R)/i + /(?C)#{regex}/.should eq /(?C)(?i-msx:(?R))/ + /(?C)#{regex}/i.should eq /(?C)(?i-msx:(?R))/i + end + end + + it "#==" do + regex = Regex.new("foo", Regex::Options::IGNORE_CASE) + (regex == Regex.new("foo", Regex::Options::IGNORE_CASE)).should be_true + (regex == Regex.new("foo")).should be_false + (regex == Regex.new("bar", Regex::Options::IGNORE_CASE)).should be_false + (regex == Regex.new("bar")).should be_false + end + + it "#hash" do + hash = Regex.new("foo", Regex::Options::IGNORE_CASE).hash + hash.should eq(Regex.new("foo", Regex::Options::IGNORE_CASE).hash) + hash.should_not eq(Regex.new("foo").hash) + hash.should_not eq(Regex.new("bar", Regex::Options::IGNORE_CASE).hash) + hash.should_not eq(Regex.new("bar").hash) + end + + it "#dup" do + regex = /foo/ + regex.dup.should be(regex) + end + + it "#clone" do + regex = /foo/ + regex.clone.should be(regex) end - it "raises if outside match range with []" do - "foo" =~ /foo/ - expect_raises(IndexError) { $1 } + describe ".needs_escape?" do + it "Char" do + Regex.needs_escape?('*').should be_true + Regex.needs_escape?('|').should be_true + Regex.needs_escape?('@').should be_false + end + + it "String" do + Regex.needs_escape?("10$").should be_true + Regex.needs_escape?("foo").should be_false + end + end + + it ".escape" do + Regex.escape(" .\\+*?[^]$(){}=!<>|:-hello").should eq("\\ \\.\\\\\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:\\-hello") end describe ".union" do @@ -189,29 +412,12 @@ describe "Regex" do end end - it "dups" do - regex = /foo/ - regex.dup.should be(regex) + it "#+" do + (/dogs/ + /cats/i).should eq /(?-imsx:dogs)|(?i-msx:cats)/ end - it "clones" do - regex = /foo/ - regex.clone.should be(regex) - end - - it "checks equality by ==" do - regex = Regex.new("foo", Regex::Options::IGNORE_CASE) - (regex == Regex.new("foo", Regex::Options::IGNORE_CASE)).should be_true - (regex == Regex.new("foo")).should be_false - (regex == Regex.new("bar", Regex::Options::IGNORE_CASE)).should be_false - (regex == Regex.new("bar")).should be_false - end - - it "hashes" do - hash = Regex.new("foo", Regex::Options::IGNORE_CASE).hash - hash.should eq(Regex.new("foo", Regex::Options::IGNORE_CASE).hash) - hash.should_not eq(Regex.new("foo").hash) - hash.should_not eq(Regex.new("bar", Regex::Options::IGNORE_CASE).hash) - hash.should_not eq(Regex.new("bar").hash) + it ".error?" do + Regex.error?("(foo|bar)").should be_nil + Regex.error?("(foo|bar").should eq "missing ) at 8" end end From 5cc9efffff4a74b9d4d1a636a6d0196ce7264506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 29 Nov 2022 17:42:19 +0100 Subject: [PATCH 0160/1551] Re-organize and enhance specs for `Regex::MatchData` (#12789) --- spec/interpreter_std_spec.cr | 2 +- spec/std/match_data_spec.cr | 303 ---------------------- spec/std/regex/match_data_spec.cr | 402 ++++++++++++++++++++++++++++++ spec/wasm32_std_spec.cr | 2 +- 4 files changed, 404 insertions(+), 305 deletions(-) delete mode 100644 spec/std/match_data_spec.cr create mode 100644 spec/std/regex/match_data_spec.cr diff --git a/spec/interpreter_std_spec.cr b/spec/interpreter_std_spec.cr index 44c7a0ae1d78..cb47c8dc1027 100644 --- a/spec/interpreter_std_spec.cr +++ b/spec/interpreter_std_spec.cr @@ -146,7 +146,7 @@ require "./std/log/log_spec.cr" require "./std/log/main_spec.cr" require "./std/log/metadata_spec.cr" require "./std/log/spec_spec.cr" -require "./std/match_data_spec.cr" +require "./std/regex/match_data_spec.cr" require "./std/math_spec.cr" require "./std/mime/media_type_spec.cr" require "./std/mime/multipart/builder_spec.cr" diff --git a/spec/std/match_data_spec.cr b/spec/std/match_data_spec.cr deleted file mode 100644 index 367eb76dee89..000000000000 --- a/spec/std/match_data_spec.cr +++ /dev/null @@ -1,303 +0,0 @@ -require "spec" - -describe "Regex::MatchData" do - it "does inspect" do - /f(o)(x)/.match("the fox").inspect.should eq(%(Regex::MatchData("fox" 1:"o" 2:"x"))) - /f(o)(x)?/.match("the fort").inspect.should eq(%(Regex::MatchData("fo" 1:"o" 2:nil))) - /fox/.match("the fox").inspect.should eq(%(Regex::MatchData("fox"))) - end - - it "does to_s" do - /f(o)(x)/.match("the fox").to_s.should eq(%(Regex::MatchData("fox" 1:"o" 2:"x"))) - /f(?o)(?x)/.match("the fox").to_s.should eq(%(Regex::MatchData("fox" lettero:"o" letterx:"x"))) - /fox/.match("the fox").to_s.should eq(%(Regex::MatchData("fox"))) - end - - it "does pretty_print" do - /f(o)(x)?/.match("the fo").pretty_inspect.should eq(%(Regex::MatchData("fo" 1:"o" 2:nil))) - - expected = <<-REGEX - Regex::MatchData("foooo" - first:"f" - second:"oooo" - third:"ooo" - fourth:"oo" - fifth:"o") - REGEX - - /(?f)(?o(?o(?o(?o))))/.match("fooooo").pretty_inspect.should eq(expected) - end - - it "does size" do - "Crystal".match(/[p-s]/).not_nil!.size.should eq(1) - "Crystal".match(/r(ys)/).not_nil!.size.should eq(2) - "Crystal".match(/r(ys)(?ta)/).not_nil!.size.should eq(3) - end - - describe "#[]" do - it "captures empty group" do - ("foo" =~ /(?z?)foo/).should eq(0) - $~[1].should eq("") - $~["g1"].should eq("") - end - - it "capture named group" do - ("fooba" =~ /f(?o+)(?bar?)/).should eq(0) - $~["g1"].should eq("oo") - $~["g2"].should eq("ba") - end - - it "captures duplicated named group" do - re = /(?:(?foo)|(?bar))*/ - - ("foo" =~ re).should eq(0) - $~["g1"].should eq("foo") - - ("bar" =~ re).should eq(0) - $~["g1"].should eq("bar") - - ("foobar" =~ re).should eq(0) - $~["g1"].should eq("bar") - - ("barfoo" =~ re).should eq(0) - $~["g1"].should eq("foo") - end - - it "can use negative index" do - "foo" =~ /(f)(oo)/ - $~[-1].should eq("oo") - $~[-2].should eq("f") - $~[-3].should eq("foo") - expect_raises(IndexError, "Invalid capture group index: -4") { $~[-4] } - end - - it "raises exception when named group doesn't exist" do - ("foo" =~ /foo/).should eq(0) - expect_raises(KeyError, "Capture group 'group' does not exist") { $~["group"] } - end - - it "raises exception on optional empty group" do - ("foo" =~ /(?z)?foo/).should eq(0) - expect_raises(IndexError, "Capture group 1 was not matched") { $~[1] } - expect_raises(KeyError, "Capture group 'g1' was not matched") { $~["g1"] } - end - - it "raises if outside match range with []" do - "foo" =~ /foo/ - expect_raises(IndexError, "Invalid capture group index: 1") { $~[1] } - end - - it "raises if special variable accessed on invalid capture group" do - "spice" =~ /spice(s)?/ - expect_raises(IndexError, "Capture group 1 was not matched") { $1 } - expect_raises(IndexError, "Invalid capture group index: 3") { $3 } - end - - it "can use range" do - "ab" =~ /(a)(b)/ - $~[1..2].should eq(["a", "b"]) - $~[1..].should eq(["a", "b"]) - $~[..].should eq(["ab", "a", "b"]) - expect_raises(IndexError) { $~[4..] } - end - - it "can use start and count" do - "ab" =~ /(a)(b)/ - $~[1, 2].should eq(["a", "b"]) - expect_raises(IndexError) { $~[4, 1] } - end - end - - describe "#[]?" do - it "capture empty group" do - ("foo" =~ /(?z?)foo/).should eq(0) - $~[1]?.should eq("") - $~["g1"]?.should eq("") - end - - it "capture optional empty group" do - ("foo" =~ /(?z)?foo/).should eq(0) - $~[1]?.should be_nil - $~["g1"]?.should be_nil - end - - it "capture named group" do - ("fooba" =~ /f(?o+)(?bar?)/).should eq(0) - $~["g1"]?.should eq("oo") - $~["g2"]?.should eq("ba") - end - - it "captures duplicated named group" do - re = /(?:(?foo)|(?bar))*/ - - ("foo" =~ re).should eq(0) - $~["g1"]?.should eq("foo") - - ("bar" =~ re).should eq(0) - $~["g1"]?.should eq("bar") - - ("foobar" =~ re).should eq(0) - $~["g1"]?.should eq("bar") - - ("barfoo" =~ re).should eq(0) - $~["g1"]?.should eq("foo") - end - - it "can use negative index" do - "foo" =~ /(b)?(f)(oo)/ - $~[-1]?.should eq("oo") - $~[-2]?.should eq("f") - $~[-3]?.should be_nil - $~[-4]?.should eq("foo") - end - - it "returns nil exception when named group doesn't exist" do - ("foo" =~ /foo/).should eq(0) - $~["group"]?.should be_nil - end - - it "returns nil if outside match range with []" do - "foo" =~ /foo/ - $~[1]?.should be_nil - end - - it "can use range" do - "ab" =~ /(a)(b)/ - $~[1..2]?.should eq(["a", "b"]) - $~[1..]?.should eq(["a", "b"]) - $~[..]?.should eq(["ab", "a", "b"]) - $~[4..]?.should be_nil - end - - it "can use start and count" do - "ab" =~ /(a)(b)/ - $~[1, 2]?.should eq(["a", "b"]) - $~[4, 1]?.should be_nil - end - end - - describe "#post_match" do - it "returns an empty string when there's nothing after" do - "Crystal".match(/ystal/).not_nil!.post_match.should eq "" - end - - it "returns the part of the string after the match" do - "Crystal".match(/yst/).not_nil!.post_match.should eq "al" - end - - it "works with unicode" do - "há日本語".match(/本/).not_nil!.post_match.should eq "語" - end - end - - describe "#pre_match" do - it "returns an empty string when there's nothing before" do - "Crystal".match(/Cryst/).not_nil!.pre_match.should eq "" - end - - it "returns the part of the string before the match" do - "Crystal".match(/yst/).not_nil!.pre_match.should eq "Cr" - end - - it "works with unicode" do - "há日本語".match(/本/).not_nil!.pre_match.should eq "há日" - end - end - - describe "#captures" do - it "gets an array of unnamed captures" do - "Crystal".match(/(Cr)y/).not_nil!.captures.should eq(["Cr"]) - "Crystal".match(/(Cr)(?y)(st)(?al)/).not_nil!.captures.should eq(["Cr", "st"]) - end - - it "gets an array of unnamed captures with optional" do - "Crystal".match(/(Cr)(s)?/).not_nil!.captures.should eq(["Cr", nil]) - "Crystal".match(/(Cr)(?s)?(tal)?/).not_nil!.captures.should eq(["Cr", nil]) - end - end - - describe "#named_captures" do - it "gets a hash of named captures" do - "Crystal".match(/(?Cr)y/).not_nil!.named_captures.should eq({"name1" => "Cr"}) - "Crystal".match(/(Cr)(?y)(st)(?al)/).not_nil!.named_captures.should eq({"name1" => "y", "name2" => "al"}) - end - - it "gets a hash of named captures with optional" do - "Crystal".match(/(?Cr)(?s)?/).not_nil!.named_captures.should eq({"name1" => "Cr", "name2" => nil}) - "Crystal".match(/(Cr)(?s)?(t)?(?al)?/).not_nil!.named_captures.should eq({"name1" => nil, "name2" => nil}) - end - - it "gets a hash of named captures with duplicated name" do - "Crystal".match(/(?Cr)y(?s)/).not_nil!.named_captures.should eq({"name" => "s"}) - end - end - - describe "#to_a" do - it "converts into an array" do - "Crystal".match(/(?Cr)(y)/).not_nil!.to_a.should eq(["Cry", "Cr", "y"]) - "Crystal".match(/(Cr)(?y)(st)(?al)/).not_nil!.to_a.should eq(["Crystal", "Cr", "y", "st", "al"]) - end - - it "converts into an array having nil" do - "Crystal".match(/(?Cr)(s)?/).not_nil!.to_a.should eq(["Cr", "Cr", nil]) - "Crystal".match(/(Cr)(?s)?(yst)?(?al)?/).not_nil!.to_a.should eq(["Crystal", "Cr", nil, "yst", "al"]) - end - end - - describe "#to_h" do - it "converts into a hash" do - "Crystal".match(/(?Cr)(y)/).not_nil!.to_h.should eq({ - 0 => "Cry", - "name1" => "Cr", - 2 => "y", - }) - "Crystal".match(/(Cr)(?y)(st)(?al)/).not_nil!.to_h.should eq({ - 0 => "Crystal", - 1 => "Cr", - "name1" => "y", - 3 => "st", - "name2" => "al", - }) - end - - it "converts into a hash having nil" do - "Crystal".match(/(?Cr)(s)?/).not_nil!.to_h.should eq({ - 0 => "Cr", - "name1" => "Cr", - 2 => nil, - }) - "Crystal".match(/(Cr)(?s)?(yst)?(?al)?/).not_nil!.to_h.should eq({ - 0 => "Crystal", - 1 => "Cr", - "name1" => nil, - 3 => "yst", - "name2" => "al", - }) - end - - it "converts into a hash with duplicated names" do - "Crystal".match(/(Cr)(?s)?(yst)?(?al)?/).not_nil!.to_h.should eq({ - 0 => "Crystal", - 1 => "Cr", - "name" => "al", - 3 => "yst", - }) - end - end - - it "can check equality" do - re = /((?he)llo)/ - m1 = re.match("hello") - m2 = re.match("hello") - m1.should be_truthy - m2.should be_truthy - m1.should eq(m2) - end - - it "hashes" do - re = /(a|b)/ - hash = re.match("a").hash - hash.should eq(re.match("a").hash) - hash.should_not eq(re.match("b").hash) - end -end diff --git a/spec/std/regex/match_data_spec.cr b/spec/std/regex/match_data_spec.cr new file mode 100644 index 000000000000..1079dff145e1 --- /dev/null +++ b/spec/std/regex/match_data_spec.cr @@ -0,0 +1,402 @@ +require "spec" + +private def matchdata(re, string) + re.match(string).should_not be_nil +end + +describe "Regex::MatchData" do + it "#regex" do + regex = /foo/ + matchdata(regex, "foo").regex.should be(regex) + end + + it "#string" do + string = "foo" + matchdata(/foo/, string).string.should be(string) + end + + it "#inspect" do + matchdata(/f(o)(x)/, "the fox").inspect.should eq(%(Regex::MatchData("fox" 1:"o" 2:"x"))) + matchdata(/f(o)(x)?/, "the fort").inspect.should eq(%(Regex::MatchData("fo" 1:"o" 2:nil))) + matchdata(/fox/, "the fox").inspect.should eq(%(Regex::MatchData("fox"))) + end + + it "#to_s" do + matchdata(/f(o)(x)/, "the fox").to_s.should eq(%(Regex::MatchData("fox" 1:"o" 2:"x"))) + matchdata(/f(?o)(?x)/, "the fox").to_s.should eq(%(Regex::MatchData("fox" lettero:"o" letterx:"x"))) + matchdata(/fox/, "the fox").to_s.should eq(%(Regex::MatchData("fox"))) + end + + it "#pretty_print" do + matchdata(/f(o)(x)?/, "the fo").pretty_inspect.should eq(%(Regex::MatchData("fo" 1:"o" 2:nil))) + + expected = <<-REGEX + Regex::MatchData("foooo" + first:"f" + second:"oooo" + third:"ooo" + fourth:"oo" + fifth:"o") + REGEX + + matchdata(/(?f)(?o(?o(?o(?o))))/, "fooooo").pretty_inspect.should eq(expected) + end + + it "#size" do + matchdata(/[p-s]/, "Crystal").size.should eq(1) + matchdata(/r(ys)/, "Crystal").size.should eq(2) + matchdata(/r(ys)(?ta)/, "Crystal").size.should eq(3) + matchdata(/foo(bar)?/, "foo").size.should eq(2) + matchdata(/foo(bar)?/, "foobar").size.should eq(2) + end + + describe "#begin" do + it "no captures" do + matchdata(/foo/, "foo").begin.should eq 0 + matchdata(/foo/, "foo").begin(-1).should eq 0 + matchdata(/foo/, ".foo.").begin.should eq 1 + matchdata(/foo/, ".foo.").begin(-1).should eq 1 + end + + it "out of range" do + expect_raises(IndexError) do + matchdata(/foo/, "foo").begin(1) + end + end + + it "with capture" do + matchdata(/f(o)o/, "foo").begin.should eq 0 + matchdata(/f(o)o/, "foo").begin(1).should eq 1 + matchdata(/f(o)o/, "foo").begin(-1).should eq 1 + matchdata(/f(o)o/, ".foo.").begin.should eq 1 + matchdata(/f(o)o/, ".foo.").begin(1).should eq 2 + matchdata(/f(o)o/, ".foo.").begin(-1).should eq 2 + end + + it "char index" do + matchdata(/foo/, "öfoo").begin.should eq 1 + end + end + + describe "#byte_begin" do + it "char index" do + matchdata(/foo/, "öfoo").byte_begin.should eq 2 + end + end + + describe "#end" do + it "no captures" do + matchdata(/foo/, "foo").end.should eq 3 + matchdata(/foo/, "foo").end(-1).should eq 3 + matchdata(/foo/, ".foo.").end.should eq 4 + matchdata(/foo/, ".foo.").end(-1).should eq 4 + end + + it "out of range" do + expect_raises(IndexError) do + matchdata(/foo/, "foo").end(1) + end + end + + it "with capture" do + matchdata(/f(o)o/, "foo").end.should eq 3 + matchdata(/f(o)o/, "foo").end(1).should eq 2 + matchdata(/f(o)o/, "foo").end(-1).should eq 2 + matchdata(/f(o)o/, ".foo.").end.should eq 4 + matchdata(/f(o)o/, ".foo.").end(1).should eq 3 + matchdata(/f(o)o/, ".foo.").end(-1).should eq 3 + end + + it "char index" do + matchdata(/foo/, "öfoo").end.should eq 4 + end + end + + describe "#byte_end" do + it "char index" do + matchdata(/foo/, "öfoo").byte_end.should eq 5 + end + end + + describe "#[]" do + describe "String" do + it "capture named group" do + md = matchdata(/f(?o+)(?bar?)/, "fooba") + md["g1"].should eq("oo") + md["g2"].should eq("ba") + end + + it "captures duplicated named group" do + re = /(?:(?foo)|(?bar))*/ + + matchdata(re, "foo")["g1"].should eq("foo") + matchdata(re, "bar")["g1"].should eq("bar") + matchdata(re, "foobar")["g1"].should eq("bar") + matchdata(re, "barfoo")["g1"].should eq("foo") + end + + it "raises exception when named group doesn't exist" do + md = matchdata(/foo/, "foo") + expect_raises(KeyError, "Capture group 'group' does not exist") { md["group"] } + end + + it "captures empty group" do + matchdata(/(?z?)foo/, "foo")["g1"].should eq("") + end + + it "raises exception on optional empty group" do + md = matchdata(/(?z)?foo/, "foo") + expect_raises(KeyError, "Capture group 'g1' was not matched") { md["g1"] } + end + end + + describe "Int" do + it "can use negative index" do + md = matchdata(/(f)(oo)/, "foo") + md[-1].should eq("oo") + md[-2].should eq("f") + md[-3].should eq("foo") + expect_raises(IndexError, "Invalid capture group index: -4") { md[-4] } + end + + it "raises if outside match range with []" do + md = matchdata(/foo/, "foo") + expect_raises(IndexError, "Invalid capture group index: 1") { md[1] } + end + + it "raises if special variable accessed on invalid capture group" do + md = matchdata(/spice(s)?/, "spice") + expect_raises(IndexError, "Capture group 1 was not matched") { md[1] } + expect_raises(IndexError, "Invalid capture group index: 3") { md[3] } + end + + it "captures empty group" do + matchdata(/(?z?)foo/, "foo")[1].should eq("") + end + + it "raises exception on optional empty group" do + md = matchdata(/(?z)?foo/, "foo") + expect_raises(IndexError, "Capture group 1 was not matched") { md[1] } + end + end + + describe "Range" do + it "can use range" do + md = matchdata(/(a)(b)/, "ab") + md[1..2].should eq(["a", "b"]) + md[1..].should eq(["a", "b"]) + md[..].should eq(["ab", "a", "b"]) + expect_raises(IndexError) { md[4..] } + end + + it "can use start and count" do + md = matchdata(/(a)(b)/, "ab") + md[1, 2].should eq(["a", "b"]) + expect_raises(IndexError) { md[4, 1] } + end + end + end + + describe "#[]?" do + describe "String" do + it "capture named group" do + md = matchdata(/f(?o+)(?bar?)/, "fooba") + md["g1"]?.should eq("oo") + md["g2"]?.should eq("ba") + end + + it "captures duplicated named group" do + re = /(?:(?foo)|(?bar))*/ + + md = matchdata(re, "foo") + md["g1"]?.should eq("foo") + + md = matchdata(re, "bar") + md["g1"]?.should eq("bar") + + md = matchdata(re, "foobar") + md["g1"]?.should eq("bar") + + md = matchdata(re, "barfoo") + md["g1"]?.should eq("foo") + end + + it "returns nil exception when named group doesn't exist" do + md = matchdata(/foo/, "foo") + md["group"]?.should be_nil + end + + it "capture empty group" do + matchdata(/(?z?)foo/, "foo")["g1"]?.should eq("") + end + + it "capture optional empty group" do + matchdata(/(?z)?foo/, "foo")["g1"]?.should be_nil + end + end + + describe "Int" do + it "can use negative index" do + md = matchdata(/(b)?(f)(oo)/, "foo") + md[-1]?.should eq("oo") + md[-2]?.should eq("f") + md[-3]?.should be_nil + md[-4]?.should eq("foo") + end + + it "returns nil if outside match range with []" do + md = matchdata(/foo/, "foo") + md[1]?.should be_nil + end + + it "capture empty group" do + matchdata(/(?z?)foo/, "foo")[1]?.should eq("") + end + + it "capture optional empty group" do + matchdata(/(?z)?foo/, "foo")[1]?.should be_nil + end + end + + describe "Range" do + it "can use range" do + md = matchdata(/(a)(b)/, "ab") + md[1..2]?.should eq(["a", "b"]) + md[1..]?.should eq(["a", "b"]) + md[..]?.should eq(["ab", "a", "b"]) + md[4..]?.should be_nil + end + + it "can use start and count" do + md = matchdata(/(a)(b)/, "ab") + md[1, 2]?.should eq(["a", "b"]) + md[4, 1]?.should be_nil + end + end + end + + describe "#post_match" do + it "returns an empty string when there's nothing after" do + matchdata(/ystal/, "Crystal").post_match.should eq "" + end + + it "returns the part of the string after the match" do + matchdata(/yst/, "Crystal").post_match.should eq "al" + end + + it "works with unicode" do + matchdata(/本/, "há日本語").post_match.should eq "語" + end + end + + describe "#pre_match" do + it "returns an empty string when there's nothing before" do + matchdata(/Cryst/, "Crystal").pre_match.should eq "" + end + + it "returns the part of the string before the match" do + matchdata(/yst/, "Crystal").pre_match.should eq "Cr" + end + + it "works with unicode" do + matchdata(/本/, "há日本語").pre_match.should eq "há日" + end + end + + describe "#captures" do + it "gets an array of unnamed captures" do + matchdata(/(Cr)y/, "Crystal").captures.should eq(["Cr"]) + matchdata(/(Cr)(?y)(st)(?al)/, "Crystal").captures.should eq(["Cr", "st"]) + end + + it "gets an array of unnamed captures with optional" do + matchdata(/(Cr)(s)?/, "Crystal").captures.should eq(["Cr", nil]) + matchdata(/(Cr)(?s)?(tal)?/, "Crystal").captures.should eq(["Cr", nil]) + end + end + + describe "#named_captures" do + it "gets a hash of named captures" do + matchdata(/(?Cr)y/, "Crystal").named_captures.should eq({"name1" => "Cr"}) + matchdata(/(Cr)(?y)(st)(?al)/, "Crystal").named_captures.should eq({"name1" => "y", "name2" => "al"}) + end + + it "gets a hash of named captures with optional" do + matchdata(/(?Cr)(?s)?/, "Crystal").named_captures.should eq({"name1" => "Cr", "name2" => nil}) + matchdata(/(Cr)(?s)?(t)?(?al)?/, "Crystal").named_captures.should eq({"name1" => nil, "name2" => nil}) + end + + it "gets a hash of named captures with duplicated name" do + matchdata(/(?Cr)y(?s)/, "Crystal").named_captures.should eq({"name" => "s"}) + end + end + + describe "#to_a" do + it "converts into an array" do + matchdata(/(?Cr)(y)/, "Crystal").to_a.should eq(["Cry", "Cr", "y"]) + matchdata(/(Cr)(?y)(st)(?al)/, "Crystal").to_a.should eq(["Crystal", "Cr", "y", "st", "al"]) + end + + it "converts into an array having nil" do + matchdata(/(?Cr)(s)?/, "Crystal").to_a.should eq(["Cr", "Cr", nil]) + matchdata(/(Cr)(?s)?(yst)?(?al)?/, "Crystal").to_a.should eq(["Crystal", "Cr", nil, "yst", "al"]) + end + end + + describe "#to_h" do + it "converts into a hash" do + matchdata(/(?Cr)(y)/, "Crystal").to_h.should eq({ + 0 => "Cry", + "name1" => "Cr", + 2 => "y", + }) + matchdata(/(Cr)(?y)(st)(?al)/, "Crystal").to_h.should eq({ + 0 => "Crystal", + 1 => "Cr", + "name1" => "y", + 3 => "st", + "name2" => "al", + }) + end + + it "converts into a hash having nil" do + matchdata(/(?Cr)(s)?/, "Crystal").to_h.should eq({ + 0 => "Cr", + "name1" => "Cr", + 2 => nil, + }) + matchdata(/(Cr)(?s)?(yst)?(?al)?/, "Crystal").to_h.should eq({ + 0 => "Crystal", + 1 => "Cr", + "name1" => nil, + 3 => "yst", + "name2" => "al", + }) + end + + it "converts into a hash with duplicated names" do + matchdata(/(Cr)(?s)?(yst)?(?al)?/, "Crystal").to_h.should eq({ + 0 => "Crystal", + 1 => "Cr", + "name" => "al", + 3 => "yst", + }) + end + end + + it "#==" do + re = /((?he)llo)/ + m1 = re.match("hello") + m2 = re.match("hello") + m1.should be_truthy + m2.should be_truthy + m1.should eq(m2) + end + + it "#hash" do + re = /(a|b)/ + hash = re.match("a").hash + hash.should eq(re.match("a").hash) + hash.should_not eq(re.match("b").hash) + end +end diff --git a/spec/wasm32_std_spec.cr b/spec/wasm32_std_spec.cr index 4e8e9cf7a0fd..cd0e0c1507f1 100644 --- a/spec/wasm32_std_spec.cr +++ b/spec/wasm32_std_spec.cr @@ -150,7 +150,7 @@ require "./std/log/env_config_spec.cr" require "./std/log/main_spec.cr" # require "./std/log/metadata_spec.cr" (failed to run) # require "./std/log/spec_spec.cr" (failed to run) -require "./std/match_data_spec.cr" +require "./std/regex/match_data_spec.cr" # require "./std/math_spec.cr" (failed to run) # require "./std/mime/media_type_spec.cr" (failed linking) # require "./std/mime/multipart/builder_spec.cr" (failed linking) From a83537ef5f5f0448a7d7bba4bdb10a2aa6f084c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 30 Nov 2022 13:11:52 +0100 Subject: [PATCH 0161/1551] Fix `XML::Node#errors` to return `nil` when empty (#12795) --- spec/std/xml/xml_spec.cr | 3 +++ src/xml/node.cr | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/std/xml/xml_spec.cr b/spec/std/xml/xml_spec.cr index 4c5e18c97249..3c695f517497 100644 --- a/spec/std/xml/xml_spec.cr +++ b/spec/std/xml/xml_spec.cr @@ -162,6 +162,9 @@ describe XML do xml = XML.parse(%()) xml.root.not_nil!.name.should eq("people") xml.errors.try(&.map(&.to_s)).should eq ["Opening and ending tag mismatch: people line 1 and foo"] + + xml = XML.parse(%()) + xml.errors.should be_nil end describe "#namespace" do diff --git a/src/xml/node.cr b/src/xml/node.cr index 218897b5d025..89451839dc85 100644 --- a/src/xml/node.cr +++ b/src/xml/node.cr @@ -7,7 +7,7 @@ class XML::Node end # :ditto: - def initialize(node : LibXML::Doc*, @errors = nil) + def initialize(node : LibXML::Doc*, @errors : Array(XML::Error)? = nil) initialize(node.as(LibXML::Node*)) end @@ -578,7 +578,9 @@ class XML::Node # Returns the list of `XML::Error` found when parsing this document. # Returns `nil` if no errors were found. - getter errors : Array(XML::Error)? + def errors : Array(XML::Error)? + return @errors unless @errors.try &.empty? + end private def check_no_null_byte(string) if string.includes? Char::ZERO From 1b932187607adf7abb41968c8a36e62e2c2dbebd Mon Sep 17 00:00:00 2001 From: Gabriel Holodak Date: Wed, 30 Nov 2022 07:12:14 -0500 Subject: [PATCH 0162/1551] Fix crash when using `sizeof`, `instance_sizeof`, or `offsetof` as a type arg (#12577) --- spec/compiler/semantic/static_array_spec.cr | 24 ++++++++++++++++++++ src/compiler/crystal/semantic/type_lookup.cr | 4 ++++ src/compiler/crystal/syntax/parser.cr | 6 +++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/spec/compiler/semantic/static_array_spec.cr b/spec/compiler/semantic/static_array_spec.cr index cd2db6e88159..0112584753c7 100644 --- a/spec/compiler/semantic/static_array_spec.cr +++ b/spec/compiler/semantic/static_array_spec.cr @@ -155,4 +155,28 @@ describe "Semantic: static array" do ), "expected argument #1 to 'fn' to be StaticArray(Int32, 11), not StaticArray(Int32, 10)" end + + it "doesn't crash on sizeof (#8858)" do + assert_error %( + alias BadArray = Int32[sizeof(Int32)] + ), + "can't use sizeof(Int32) as a generic type argument" + end + + it "doesn't crash on instance_sizeof (#8858)" do + assert_error %( + alias BadArray = Int32[instance_sizeof(String)] + ), + "can't use instance_sizeof(String) as a generic type argument" + end + + it "doesn't crash on offsetof (#8858)" do + assert_error %( + class Foo + @foo : Int32 = 0 + end + alias BadArray = Int32[offsetof(Foo, @foo)] + ), + "can't use offsetof(Foo, @foo) as a generic type argument" + end end diff --git a/src/compiler/crystal/semantic/type_lookup.cr b/src/compiler/crystal/semantic/type_lookup.cr index 7a98450cc72b..64c3b319cf6e 100644 --- a/src/compiler/crystal/semantic/type_lookup.cr +++ b/src/compiler/crystal/semantic/type_lookup.cr @@ -247,6 +247,10 @@ class Crystal::Type type_var.raise "can only splat tuple type, not #{splat_type}" end next + when SizeOf, InstanceSizeOf, OffsetOf + next unless @raise + + type_var.raise "can't use #{type_var} as a generic type argument" end # Check the case of T resolving to a number diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index adcbcabeece5..e554f500b738 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -5771,6 +5771,7 @@ module Crystal end def parse_sizeof(klass) + sizeof_location = @token.location next_token_skip_space check :OP_LPAREN @@ -5785,10 +5786,11 @@ module Crystal check :OP_RPAREN next_token_skip_space - klass.new(exp).at_end(end_location) + klass.new(exp).at(sizeof_location).at_end(end_location) end def parse_offsetof + offsetof_location = @token.location next_token_skip_space check :OP_LPAREN @@ -5817,7 +5819,7 @@ module Crystal check :OP_RPAREN next_token_skip_space - OffsetOf.new(type, offset).at_end(end_location) + OffsetOf.new(type, offset).at(offsetof_location).at_end(end_location) end def parse_type_def From 312e369f74edc3f2785a3959595892dbc2396092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 1 Dec 2022 00:30:47 +0100 Subject: [PATCH 0163/1551] Add missing positive spec for `Regex#match` with option (#12804) --- spec/std/regex_spec.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 24ac68b35f9a..c38194bc8785 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -76,6 +76,7 @@ describe "Regex" do it "with options" do /foo/.match(".foo", options: Regex::Options::ANCHORED).should be_nil + /foo/.match("foo", options: Regex::Options::ANCHORED).should_not be_nil end end @@ -126,6 +127,7 @@ describe "Regex" do it "with options" do /foo/.match_at_byte_index("..foo", 1, options: Regex::Options::ANCHORED).should be_nil + /foo/.match_at_byte_index(".foo", 1, options: Regex::Options::ANCHORED).should_not be_nil end end @@ -195,6 +197,7 @@ describe "Regex" do it "with options" do /foo/.matches?(".foo", options: Regex::Options::ANCHORED).should be_false + /foo/.matches?("foo", options: Regex::Options::ANCHORED).should be_true end it "matches a large single line string" do @@ -229,6 +232,7 @@ describe "Regex" do it "with options" do /foo/.matches_at_byte_index?("..foo", 1, options: Regex::Options::ANCHORED).should be_false + /foo/.matches_at_byte_index?(".foo", 1, options: Regex::Options::ANCHORED).should be_true end end From 926e89cc025c7ee1ffe4ee1179526188119e1ae0 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 1 Dec 2022 19:57:35 +0800 Subject: [PATCH 0164/1551] Redefine defs when constant and number in generic arguments are equal (#12785) --- spec/compiler/semantic/restrictions_spec.cr | 77 +++++++++++++++++++ src/compiler/crystal/semantic/restrictions.cr | 48 ++++++++++++ 2 files changed, 125 insertions(+) diff --git a/spec/compiler/semantic/restrictions_spec.cr b/spec/compiler/semantic/restrictions_spec.cr index 139f881cc447..4336b142b8ca 100644 --- a/spec/compiler/semantic/restrictions_spec.cr +++ b/spec/compiler/semantic/restrictions_spec.cr @@ -548,6 +548,46 @@ describe "Restrictions" do end end + describe "Path vs NumberLiteral" do + it "inserts constant before number literal of same value with generic arguments" do + assert_type(<<-CR) { bool } + X = 1 + + class Foo(N) + end + + def foo(a : Foo(1)) + 'a' + end + + def foo(a : Foo(X)) + true + end + + foo(Foo(1).new) + CR + end + + it "inserts number literal before constant of same value with generic arguments" do + assert_type(<<-CR) { bool } + X = 1 + + class Foo(N) + end + + def foo(a : Foo(X)) + 'a' + end + + def foo(a : Foo(1)) + true + end + + foo(Foo(1).new) + CR + end + end + describe "free variables" do it "inserts path before free variable with same name" do assert_type(<<-CR) { tuple_of([char, bool]) } @@ -577,6 +617,43 @@ describe "Restrictions" do CR end + # TODO: enable in #12784 + pending "inserts constant before free variable with same name" do + assert_type(<<-CR) { tuple_of([char, bool]) } + class Foo(T); end + + X = 1 + + def foo(x : Foo(X)) forall X + true + end + + def foo(x : Foo(X)) + 'a' + end + + {foo(Foo(1).new), foo(Foo(2).new)} + CR + end + + pending "keeps constant before free variable with same name" do + assert_type(<<-CR) { tuple_of([char, bool]) } + class Foo(T); end + + X = 1 + + def foo(x : Foo(X)) + 'a' + end + + def foo(x : Foo(X)) forall X + true + end + + {foo(Foo(1).new), foo(Foo(2).new)} + CR + end + it "inserts path before free variable even if free var resolves to a more specialized type" do assert_type(<<-CR) { tuple_of([int32, int32, bool]) } class Foo diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 5e157e3ef200..523030444d2e 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -605,11 +605,59 @@ module Crystal false end + def restriction_of?(other : NumberLiteral, owner, self_free_vars = nil, other_free_vars = nil) + return false if self_free_vars && self.single_name?.try { |name| self_free_vars.includes?(name) } + + # this happens when `self` and `other` are generic arguments: + # + # ``` + # X = 1 + # + # def foo(param : StaticArray(Int32, X)) + # end + # + # def foo(param : StaticArray(Int32, 1)) + # end + # ``` + case self_type = owner.lookup_path(self) + when Const + self_type.value == other + when NumberLiteral + self_type == other + else + false + end + end + def restriction_of?(other, owner, self_free_vars = nil, other_free_vars = nil) false end end + class NumberLiteral + def restriction_of?(other : Path, owner, self_free_vars = nil, other_free_vars = nil) + # this happens when `self` and `other` are generic arguments: + # + # ``` + # X = 1 + # + # def foo(param : StaticArray(Int32, 1)) + # end + # + # def foo(param : StaticArray(Int32, X)) + # end + # ``` + case other_type = owner.lookup_path(other) + when Const + other_type.value == self + when NumberLiteral + other_type == self + else + false + end + end + end + class Union def restriction_of?(other, owner, self_free_vars = nil, other_free_vars = nil) # For a union to be considered before another restriction, From d40b44b66df1bea01e8deb4289a65d7aea362501 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 1 Dec 2022 12:57:50 +0100 Subject: [PATCH 0165/1551] Update actions/checkout action to v3 (#12805) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/wasm32.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index e2b0fbf49bbe..835c2ddbcccd 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -11,7 +11,7 @@ jobs: container: crystallang/crystal:1.6.1-build steps: - name: Download Crystal source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install wasmtime uses: mwilliamson/setup-wasmtime-action@v1 From 251bd55afd9db5547945808e2e9bcd45a4e73e11 Mon Sep 17 00:00:00 2001 From: "Billy.Zheng" Date: Fri, 2 Dec 2022 02:25:49 +0800 Subject: [PATCH 0166/1551] Use qualified type reference `YAML::Any` (#12688) --- src/json/any.cr | 14 +++++++------- src/yaml/any.cr | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/json/any.cr b/src/json/any.cr index 5a9a96c78265..59a11b852c5d 100644 --- a/src/json/any.cr +++ b/src/json/any.cr @@ -18,7 +18,7 @@ # which return `nil` when the underlying value type won't match. struct JSON::Any # All possible JSON types. - alias Type = Nil | Bool | Int64 | Float64 | String | Array(Any) | Hash(String, Any) + alias Type = Nil | Bool | Int64 | Float64 | String | Array(JSON::Any) | Hash(String, JSON::Any) # Reads a `JSON::Any` value from the given pull parser. def self.new(pull : JSON::PullParser) @@ -224,25 +224,25 @@ struct JSON::Any # Checks that the underlying value is `Array`, and returns its value. # Raises otherwise. - def as_a : Array(Any) + def as_a : Array(JSON::Any) @raw.as(Array) end # Checks that the underlying value is `Array`, and returns its value. # Returns `nil` otherwise. - def as_a? : Array(Any)? + def as_a? : Array(JSON::Any)? as_a if @raw.is_a?(Array) end # Checks that the underlying value is `Hash`, and returns its value. # Raises otherwise. - def as_h : Hash(String, Any) + def as_h : Hash(String, JSON::Any) @raw.as(Hash) end # Checks that the underlying value is `Hash`, and returns its value. # Returns `nil` otherwise. - def as_h? : Hash(String, Any)? + def as_h? : Hash(String, JSON::Any)? as_h if @raw.is_a?(Hash) end @@ -283,12 +283,12 @@ struct JSON::Any # Returns a new JSON::Any instance with the `raw` value `dup`ed. def dup - Any.new(raw.dup) + JSON::Any.new(raw.dup) end # Returns a new JSON::Any instance with the `raw` value `clone`ed. def clone - Any.new(raw.clone) + JSON::Any.new(raw.clone) end end diff --git a/src/yaml/any.cr b/src/yaml/any.cr index 284e6ad0e33d..f0a7978369b9 100644 --- a/src/yaml/any.cr +++ b/src/yaml/any.cr @@ -26,14 +26,14 @@ # which return `nil` when the underlying value type won't match. struct YAML::Any # All valid YAML core schema types. - alias Type = Nil | Bool | Int64 | Float64 | String | Time | Bytes | Array(Any) | Hash(Any, Any) | Set(Any) + alias Type = Nil | Bool | Int64 | Float64 | String | Time | Bytes | Array(YAML::Any) | Hash(YAML::Any, YAML::Any) | Set(YAML::Any) def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) case node when YAML::Nodes::Scalar new YAML::Schema::Core.parse_scalar(node.value) when YAML::Nodes::Sequence - ary = [] of Any + ary = [] of YAML::Any node.each do |value| ary << new(ctx, value) @@ -41,7 +41,7 @@ struct YAML::Any new ary when YAML::Nodes::Mapping - hash = {} of Any => Any + hash = {} of YAML::Any => YAML::Any node.each do |key, value| hash[new(ctx, key)] = new(ctx, value) @@ -62,7 +62,7 @@ struct YAML::Any # Returns the raw underlying value, a `Type`. getter raw : Type - # Creates a `Any` that wraps the given `Type`. + # Creates a `YAML::Any` that wraps the given `Type`. def initialize(@raw : Type) end @@ -236,25 +236,25 @@ struct YAML::Any # Checks that the underlying value is `Array`, and returns its value. # Raises otherwise. - def as_a : Array(Any) + def as_a : Array(YAML::Any) @raw.as(Array) end # Checks that the underlying value is `Array`, and returns its value. # Returns `nil` otherwise. - def as_a? : Array(Any)? + def as_a? : Array(YAML::Any)? @raw.as?(Array) end # Checks that the underlying value is `Hash`, and returns its value. # Raises otherwise. - def as_h : Hash(Any, Any) + def as_h : Hash(YAML::Any, YAML::Any) @raw.as(Hash) end # Checks that the underlying value is `Hash`, and returns its value. # Returns `nil` otherwise. - def as_h? : Hash(Any, Any)? + def as_h? : Hash(YAML::Any, YAML::Any)? @raw.as?(Hash) end @@ -311,12 +311,12 @@ struct YAML::Any # Returns a new YAML::Any instance with the `raw` value `dup`ed. def dup - Any.new(raw.dup) + YAML::Any.new(raw.dup) end # Returns a new YAML::Any instance with the `raw` value `clone`ed. def clone - Any.new(raw.clone) + YAML::Any.new(raw.clone) end # Forwards `to_json_object_key` to `raw` if it responds to that method, From 49edc0bdf73f9c60b83c349f1930f33ffdee563e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 2 Dec 2022 11:17:55 +0100 Subject: [PATCH 0167/1551] Raise `IndexError` on unmatched subpattern for `MatchData#begin` and `#end` (#12810) Resolves https://github.com/crystal-lang/crystal/issues/12806 --- spec/std/regex/match_data_spec.cr | 102 ++++++++++++++++++++++++++---- src/regex/match_data.cr | 28 +++++++- 2 files changed, 116 insertions(+), 14 deletions(-) diff --git a/spec/std/regex/match_data_spec.cr b/spec/std/regex/match_data_spec.cr index 1079dff145e1..7fc9c3219faa 100644 --- a/spec/std/regex/match_data_spec.cr +++ b/spec/std/regex/match_data_spec.cr @@ -65,12 +65,33 @@ describe "Regex::MatchData" do end it "with capture" do - matchdata(/f(o)o/, "foo").begin.should eq 0 - matchdata(/f(o)o/, "foo").begin(1).should eq 1 - matchdata(/f(o)o/, "foo").begin(-1).should eq 1 - matchdata(/f(o)o/, ".foo.").begin.should eq 1 - matchdata(/f(o)o/, ".foo.").begin(1).should eq 2 - matchdata(/f(o)o/, ".foo.").begin(-1).should eq 2 + md = matchdata(/f(o)o/, "foo") + md.begin.should eq 0 + md.begin(1).should eq 1 + md.begin(-1).should eq 1 + + md = matchdata(/f(o)o/, ".foo.") + md.begin.should eq 1 + md.begin(1).should eq 2 + md.begin(-1).should eq 2 + end + + it "with unmatched capture" do + md = matchdata(/f(x)?o/, "foo") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.begin(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.begin(-1) + end + + md = matchdata(/f(x)?o/, ".foo.") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.begin(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.begin(-1) + end end it "char index" do @@ -82,6 +103,24 @@ describe "Regex::MatchData" do it "char index" do matchdata(/foo/, "öfoo").byte_begin.should eq 2 end + + it "with unmatched capture" do + md = matchdata(/f(x)?o/, "foo") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_begin(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_begin(-1) + end + + md = matchdata(/f(x)?o/, ".foo.") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_begin(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_begin(-1) + end + end end describe "#end" do @@ -99,12 +138,33 @@ describe "Regex::MatchData" do end it "with capture" do - matchdata(/f(o)o/, "foo").end.should eq 3 - matchdata(/f(o)o/, "foo").end(1).should eq 2 - matchdata(/f(o)o/, "foo").end(-1).should eq 2 - matchdata(/f(o)o/, ".foo.").end.should eq 4 - matchdata(/f(o)o/, ".foo.").end(1).should eq 3 - matchdata(/f(o)o/, ".foo.").end(-1).should eq 3 + md = matchdata(/f(o)o/, "foo") + md.end.should eq 3 + md.end(1).should eq 2 + md.end(-1).should eq 2 + + md = matchdata(/f(o)o/, ".foo.") + md.end.should eq 4 + md.end(1).should eq 3 + md.end(-1).should eq 3 + end + + it "with unmatched capture" do + md = matchdata(/f(x)?o/, "foo") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.end(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.end(-1) + end + + md = matchdata(/f(x)?o/, ".foo.") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.end(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.end(-1) + end end it "char index" do @@ -116,6 +176,24 @@ describe "Regex::MatchData" do it "char index" do matchdata(/foo/, "öfoo").byte_end.should eq 5 end + + it "with unmatched capture" do + md = matchdata(/f(x)?o/, "foo") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_end(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_end(-1) + end + + md = matchdata(/f(x)?o/, ".foo.") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_end(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_end(-1) + end + end end describe "#[]" do diff --git a/src/regex/match_data.cr b/src/regex/match_data.cr index 89e12204986e..085c829073e8 100644 --- a/src/regex/match_data.cr +++ b/src/regex/match_data.cr @@ -59,10 +59,15 @@ class Regex # When *n* is `0` or not given, uses the match of the entire `Regex`. # Otherwise, uses the match of the *n*th capture group. # + # Raises `IndexError` if the index is out of range or the respective + # subpattern is unused. + # # ``` # "Crystal".match(/r/).not_nil!.begin(0) # => 1 # "Crystal".match(/r(ys)/).not_nil!.begin(1) # => 2 # "クリスタル".match(/リ(ス)/).not_nil!.begin(0) # => 1 + # "Crystal".match(/r/).not_nil!.begin(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match(/r(x)?/).not_nil!.begin(1) # IndexError: Capture group 1 was not matched # ``` def begin(n = 0) : Int32 @string.byte_index_to_char_index(byte_begin(n)).not_nil! @@ -73,10 +78,15 @@ class Regex # When *n* is `0` or not given, uses the match of the entire `Regex`. # Otherwise, uses the match of the *n*th capture group. # + # Raises `IndexError` if the index is out of range or the respective + # subpattern is unused. + # # ``` # "Crystal".match(/r/).not_nil!.end(0) # => 2 # "Crystal".match(/r(ys)/).not_nil!.end(1) # => 4 # "クリスタル".match(/リ(ス)/).not_nil!.end(0) # => 3 + # "Crystal".match(/r/).not_nil!.end(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match(/r(x)?/).not_nil!.end(1) # IndexError: Capture group 1 was not matched # ``` def end(n = 0) : Int32 @string.byte_index_to_char_index(byte_end(n)).not_nil! @@ -87,15 +97,22 @@ class Regex # When *n* is `0` or not given, uses the match of the entire `Regex`. # Otherwise, uses the match of the *n*th capture group. # + # Raises `IndexError` if the index is out of range or the respective + # subpattern is unused. + # # ``` # "Crystal".match(/r/).not_nil!.byte_begin(0) # => 1 # "Crystal".match(/r(ys)/).not_nil!.byte_begin(1) # => 2 # "クリスタル".match(/リ(ス)/).not_nil!.byte_begin(0) # => 3 + # "Crystal".match(/r/).not_nil!.byte_begin(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match(/r(x)?/).not_nil!.byte_begin(1) # IndexError: Capture group 1 was not matched # ``` def byte_begin(n = 0) : Int32 check_index_out_of_bounds n n += size if n < 0 - @ovector[n * 2] + value = @ovector[n * 2] + raise_capture_group_was_not_matched(n) if value < 0 + value end # Returns the position of the next byte after the match. @@ -103,15 +120,22 @@ class Regex # When *n* is `0` or not given, uses the match of the entire `Regex`. # Otherwise, uses the match of the *n*th capture group. # + # Raises `IndexError` if the index is out of range or the respective + # subpattern is unused. + # # ``` # "Crystal".match(/r/).not_nil!.byte_end(0) # => 2 # "Crystal".match(/r(ys)/).not_nil!.byte_end(1) # => 4 # "クリスタル".match(/リ(ス)/).not_nil!.byte_end(0) # => 9 + # "Crystal".match(/r/).not_nil!.byte_end(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match(/r(x)?/).not_nil!.byte_end(1) # IndexError: Capture group 1 was not matched # ``` def byte_end(n = 0) : Int32 check_index_out_of_bounds n n += size if n < 0 - @ovector[n * 2 + 1] + value = @ovector[n * 2 + 1] + raise_capture_group_was_not_matched(n) if value < 0 + value end # Returns the match of the *n*th capture group, or `nil` if there isn't From 49f34536ad9c9f40aa4d900f5169c3850298cbbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 4 Dec 2022 12:38:23 +0100 Subject: [PATCH 0168/1551] Use context-specific heredoc deliminators (#12816) --- scripts/generate_grapheme_break_specs.cr | 4 +- scripts/generate_ssl_server_defaults.cr | 8 +- spec/compiler/codegen/cast_spec.cr | 4 +- spec/compiler/codegen/is_a_spec.cr | 4 +- spec/compiler/codegen/multi_assign_spec.cr | 92 ++-- spec/compiler/codegen/pointer_spec.cr | 4 +- spec/compiler/codegen/primitives_spec.cr | 12 +- spec/compiler/codegen/private_spec.cr | 16 +- spec/compiler/codegen/proc_spec.cr | 4 +- .../compiler/crystal/tools/doc/method_spec.cr | 8 +- spec/compiler/crystal/tools/doc/type_spec.cr | 44 +- spec/compiler/crystal/tools/doc_spec.cr | 4 +- spec/compiler/crystal/tools/expand_spec.cr | 140 +++--- spec/compiler/crystal/tools/hierarchy_spec.cr | 8 +- spec/compiler/crystal/tools/init_spec.cr | 24 +- .../compiler/crystal/tools/playground_spec.cr | 94 ++-- spec/compiler/formatter/formatter_spec.cr | 326 +++++++------- spec/compiler/interpreter/autocast_spec.cr | 36 +- spec/compiler/interpreter/blocks_spec.cr | 144 +++--- spec/compiler/interpreter/bugs_spec.cr | 36 +- spec/compiler/interpreter/calls_spec.cr | 152 +++---- spec/compiler/interpreter/casts_spec.cr | 124 +++--- spec/compiler/interpreter/class_vars_spec.cr | 28 +- spec/compiler/interpreter/classes_spec.cr | 36 +- spec/compiler/interpreter/closures_spec.cr | 112 ++--- spec/compiler/interpreter/constants_spec.cr | 24 +- .../compiler/interpreter/control_flow_spec.cr | 56 +-- spec/compiler/interpreter/enum_spec.cr | 8 +- spec/compiler/interpreter/exceptions_spec.cr | 64 +-- spec/compiler/interpreter/extern_spec.cr | 28 +- spec/compiler/interpreter/integration_spec.cr | 56 +-- spec/compiler/interpreter/is_a_spec.cr | 40 +- spec/compiler/interpreter/lib_spec.cr | 16 +- .../interpreter/multidispatch_spec.cr | 76 ++-- spec/compiler/interpreter/named_tuple_spec.cr | 12 +- spec/compiler/interpreter/pointers_spec.cr | 104 ++--- spec/compiler/interpreter/primitives_spec.cr | 88 ++-- spec/compiler/interpreter/procs_spec.cr | 32 +- spec/compiler/interpreter/responds_to_spec.cr | 8 +- .../compiler/interpreter/special_vars_spec.cr | 24 +- spec/compiler/interpreter/structs_spec.cr | 60 +-- spec/compiler/interpreter/symbol_spec.cr | 12 +- spec/compiler/interpreter/tuple_spec.cr | 48 +- spec/compiler/interpreter/typeof_spec.cr | 4 +- spec/compiler/interpreter/types_spec.cr | 28 +- spec/compiler/interpreter/unions_spec.cr | 32 +- spec/compiler/macro/macro_methods_spec.cr | 12 +- spec/compiler/normalize/array_literal_spec.cr | 36 +- spec/compiler/normalize/hash_literal_spec.cr | 20 +- spec/compiler/normalize/multi_assign_spec.cr | 96 ++-- spec/compiler/normalize/proc_pointer_spec.cr | 32 +- spec/compiler/normalize/select_spec.cr | 20 +- spec/compiler/parser/parser_spec.cr | 24 +- spec/compiler/semantic/abstract_def_spec.cr | 168 +++---- spec/compiler/semantic/annotation_spec.cr | 8 +- spec/compiler/semantic/automatic_cast_spec.cr | 4 +- spec/compiler/semantic/block_spec.cr | 24 +- spec/compiler/semantic/case_spec.cr | 8 +- spec/compiler/semantic/class_spec.cr | 4 +- spec/compiler/semantic/closure_spec.cr | 4 +- spec/compiler/semantic/const_spec.cr | 12 +- spec/compiler/semantic/def_overload_spec.cr | 24 +- spec/compiler/semantic/def_spec.cr | 4 +- spec/compiler/semantic/did_you_mean_spec.cr | 4 +- spec/compiler/semantic/doc_spec.cr | 4 +- spec/compiler/semantic/enum_spec.cr | 8 +- spec/compiler/semantic/exception_spec.cr | 4 +- spec/compiler/semantic/generic_class_spec.cr | 4 +- spec/compiler/semantic/instance_var_spec.cr | 40 +- spec/compiler/semantic/lib_spec.cr | 8 +- spec/compiler/semantic/macro_spec.cr | 412 +++++++++--------- spec/compiler/semantic/metaclass_spec.cr | 8 +- spec/compiler/semantic/multi_assign_spec.cr | 20 +- spec/compiler/semantic/pointer_spec.cr | 16 +- spec/compiler/semantic/private_spec.cr | 4 +- spec/compiler/semantic/proc_spec.cr | 28 +- .../semantic/recursive_struct_check_spec.cr | 4 +- .../semantic/restrictions_augmenter_spec.cr | 120 ++--- spec/compiler/semantic/restrictions_spec.cr | 68 +-- spec/compiler/semantic/return_spec.cr | 12 +- spec/compiler/semantic/sizeof_spec.cr | 4 +- spec/compiler/semantic/splat_spec.cr | 4 +- .../semantic/virtual_metaclass_spec.cr | 4 +- spec/compiler/semantic/virtual_spec.cr | 4 +- .../semantic/visibility_modifiers_spec.cr | 4 +- spec/compiler/semantic/warnings_spec.cr | 184 ++++---- .../syntax_highlighter/colorize_spec.cr | 12 +- .../crystal/syntax_highlighter/html_spec.cr | 12 +- spec/std/exception/call_stack_spec.cr | 4 +- spec/std/http/client/client_spec.cr | 4 +- spec/std/http/request_spec.cr | 8 +- .../std/http/server/request_processor_spec.cr | 52 +-- spec/std/http/server/server_spec.cr | 8 +- spec/std/kernel_spec.cr | 56 +-- spec/std/process_spec.cr | 4 +- spec/std/spec/hooks_spec.cr | 4 +- spec/std/string/grapheme_break_spec.cr | 134 +++--- spec/std/yaml/yaml_spec.cr | 4 +- src/compiler/crystal/tools/doc/templates.cr | 8 +- .../crystal/tools/playground/server.cr | 8 +- src/yaml.cr | 4 +- src/yaml/any.cr | 4 +- 102 files changed, 2038 insertions(+), 2040 deletions(-) diff --git a/scripts/generate_grapheme_break_specs.cr b/scripts/generate_grapheme_break_specs.cr index 7e800a15a349..e31c602cb38f 100644 --- a/scripts/generate_grapheme_break_specs.cr +++ b/scripts/generate_grapheme_break_specs.cr @@ -22,7 +22,7 @@ def string_or_char(string) end File.open(path, "w") do |file| - file.puts <<-CR + file.puts <<-CRYSTAL # This file was automatically generated by running: # # scripts/generate_grapheme_break_spec.cr @@ -33,7 +33,7 @@ File.open(path, "w") do |file| require "./spec_helper" describe "String#each_grapheme" do - CR + CRYSTAL HTTP::Client.get(url).body.each_line do |line| next if line.starts_with?('#') diff --git a/scripts/generate_ssl_server_defaults.cr b/scripts/generate_ssl_server_defaults.cr index a0a87647d788..bab12ad2bb0e 100755 --- a/scripts/generate_ssl_server_defaults.cr +++ b/scripts/generate_ssl_server_defaults.cr @@ -13,12 +13,12 @@ DEFAULTS_FILE = "src/openssl/ssl/defaults.cr" json = JSON.parse(HTTP::Client.get(url).body) File.open(DEFAULTS_FILE, "w") do |file| - file.print <<-CR + file.print <<-CRYSTAL # THIS FILE WAS AUTOMATICALLY GENERATED BY script/ssl_server_defaults.cr # on #{Time.utc}. abstract class OpenSSL::SSL::Context - CR + CRYSTAL configuration = json["configurations"].as_h.each do |level, configuration| clients = configuration["oldest_clients"].as_a @@ -27,7 +27,7 @@ File.open(DEFAULTS_FILE, "w") do |file| disabled_ciphers = %w(!RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS) all_ciphers = ciphersuites + ciphers + disabled_ciphers - file.puts <<-CR + file.puts <<-CRYSTAL # The list of secure ciphers on **#{level}** compatibility level as per Mozilla # recommendations. @@ -52,7 +52,7 @@ File.open(DEFAULTS_FILE, "w") do |file| # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. CIPHER_SUITES_#{level.upcase} = "#{ciphersuites.join(":")}" - CR + CRYSTAL end file.puts "end" end diff --git a/spec/compiler/codegen/cast_spec.cr b/spec/compiler/codegen/cast_spec.cr index 1260ab2ffe43..b4ad3d4bdc3e 100644 --- a/spec/compiler/codegen/cast_spec.cr +++ b/spec/compiler/codegen/cast_spec.cr @@ -425,7 +425,7 @@ describe "Code gen: cast" do end it "cast virtual metaclass type to nilable virtual instance type (#12628)" do - run(<<-CR).to_b.should be_true + run(<<-CRYSTAL).to_b.should be_true abstract struct Base end @@ -433,6 +433,6 @@ describe "Code gen: cast" do end Base.as(Base | Base.class).as?(Base | Impl).nil? - CR + CRYSTAL end end diff --git a/spec/compiler/codegen/is_a_spec.cr b/spec/compiler/codegen/is_a_spec.cr index 2d751bc81c15..97d72378be42 100644 --- a/spec/compiler/codegen/is_a_spec.cr +++ b/spec/compiler/codegen/is_a_spec.cr @@ -924,7 +924,7 @@ describe "Codegen: is_a?" do end it "virtual metaclass type is not virtual instance type (#12628)" do - run(<<-CR).to_b.should be_false + run(<<-CRYSTAL).to_b.should be_false abstract struct Base end @@ -932,6 +932,6 @@ describe "Codegen: is_a?" do end Base.as(Base | Base.class).is_a?(Base | Impl) - CR + CRYSTAL end end diff --git a/spec/compiler/codegen/multi_assign_spec.cr b/spec/compiler/codegen/multi_assign_spec.cr index faffb8fb34f5..e9075e8c16bd 100644 --- a/spec/compiler/codegen/multi_assign_spec.cr +++ b/spec/compiler/codegen/multi_assign_spec.cr @@ -2,15 +2,15 @@ require "../../spec_helper" describe "Code gen: multi assign" do it "supports n to n assignment" do - run(<<-CR).to_i.should eq(123) + run(<<-CRYSTAL).to_i.should eq(123) a, b, c = 1, 2, 3 a &* 100 &+ b &* 10 &+ c - CR + CRYSTAL end context "without strict_multi_assign" do it "supports 1 to n assignment" do - run(<<-CR).to_i.should eq(123) + run(<<-CRYSTAL).to_i.should eq(123) class Foo def [](index) index &+ 1 @@ -19,11 +19,11 @@ describe "Code gen: multi assign" do a, b, c = Foo.new a &* 100 &+ b &* 10 &+ c - CR + CRYSTAL end it "doesn't raise if value size in 1 to n assignment doesn't match target count" do - run(<<-CR).to_i.should eq(4) + run(<<-CRYSTAL).to_i.should eq(4) require "prelude" begin @@ -33,13 +33,13 @@ describe "Code gen: multi assign" do raise ex unless ex.message == "Multiple assignment count mismatch" 5 end - CR + CRYSTAL end end context "strict_multi_assign" do it "supports 1 to n assignment" do - run(<<-CR, flags: %w(strict_multi_assign)).to_i.should eq(123) + run(<<-CRYSTAL, flags: %w(strict_multi_assign)).to_i.should eq(123) require "prelude" class Foo @@ -56,11 +56,11 @@ describe "Code gen: multi assign" do a, b, c = Foo.new a &* 100 &+ b &* 10 &+ c - CR + CRYSTAL end it "raises if value size in 1 to n assignment doesn't match target count" do - run(<<-CR, flags: %w(strict_multi_assign)).to_i.should eq(5) + run(<<-CRYSTAL, flags: %w(strict_multi_assign)).to_i.should eq(5) require "prelude" begin @@ -70,126 +70,126 @@ describe "Code gen: multi assign" do raise ex unless ex.message == "Multiple assignment count mismatch" 5 end - CR + CRYSTAL end end it "supports m to n assignment, with splat on left-hand side (1)" do - run(<<-CR).to_i.should eq(12345) + run(<<-CRYSTAL).to_i.should eq(12345) #{tuple_new} a, *b, c = 1, 2, 3, 4, 5 a &* 10000 &+ b[0] &* 1000 &+ b[1] &* 100 &+ b[2] &* 10 &+ c - CR + CRYSTAL end it "supports m to n assignment, with splat on left-hand side (2)" do - run(<<-CR).to_i.should eq(12345) + run(<<-CRYSTAL).to_i.should eq(12345) #{tuple_new} *a, b, c = 1, 2, 3, 4, 5 a[0] &* 10000 &+ a[1] &* 1000 &+ a[2] &* 100 &+ b &* 10 &+ c - CR + CRYSTAL end it "supports m to n assignment, with splat on left-hand side (3)" do - run(<<-CR).to_i.should eq(12345) + run(<<-CRYSTAL).to_i.should eq(12345) #{tuple_new} a, b, *c = 1, 2, 3, 4, 5 a &* 10000 &+ b &* 1000 &+ c[0] &* 100 &+ c[1] &* 10 &+ c[2] - CR + CRYSTAL end it "supports m to n assignment, splat is empty tuple (1)" do - run(<<-CR).to_b.should be_true + run(<<-CRYSTAL).to_b.should be_true #{tuple_new} _, *x, _ = 1, 2 x.is_a?(Tuple()) - CR + CRYSTAL end it "supports m to n assignment, splat is empty tuple (2)" do - run(<<-CR).to_b.should be_true + run(<<-CRYSTAL).to_b.should be_true #{tuple_new} *x, _, _ = 1, 2 x.is_a?(Tuple()) - CR + CRYSTAL end it "supports m to n assignment, splat is empty tuple (3)" do - run(<<-CR).to_b.should be_true + run(<<-CRYSTAL).to_b.should be_true #{tuple_new} _, _, *x = 1, 2 x.is_a?(Tuple()) - CR + CRYSTAL end it "supports 1 to n assignment, with splat on left-hand side (1)" do - run(<<-CR).to_i.should eq(12345) + run(<<-CRYSTAL).to_i.should eq(12345) require "prelude" a, *b, c = {1, 2, 3, 4, 5} a &* 10000 &+ b[0] &* 1000 &+ b[1] &* 100 &+ b[2] &* 10 &+ c - CR + CRYSTAL end it "supports 1 to n assignment, with splat on left-hand side (2)" do - run(<<-CR).to_i.should eq(12345) + run(<<-CRYSTAL).to_i.should eq(12345) #{range_new} #{include_indexable} *a, b, c = {1, 2, 3, 4, 5} a[0] &* 10000 &+ a[1] &* 1000 &+ a[2] &* 100 &+ b &* 10 &+ c - CR + CRYSTAL end it "supports 1 to n assignment, with splat on left-hand side (3)" do - run(<<-CR).to_i.should eq(12345) + run(<<-CRYSTAL).to_i.should eq(12345) #{range_new} #{include_indexable} a, b, *c = {1, 2, 3, 4, 5} a &* 10000 &+ b &* 1000 &+ c[0] &* 100 &+ c[1] &* 10 &+ c[2] - CR + CRYSTAL end it "supports 1 to n assignment, splat is empty (1)" do - run(<<-CR).to_b.should be_true + run(<<-CRYSTAL).to_b.should be_true require "prelude" _, *x, _ = {1, 2} x.is_a?(Tuple()) - CR + CRYSTAL end it "supports 1 to n assignment, splat is empty (2)" do - run(<<-CR).to_b.should be_true + run(<<-CRYSTAL).to_b.should be_true #{tuple_new} #{range_new} #{include_indexable} *x, _, _ = {1, 2} x.is_a?(Tuple()) - CR + CRYSTAL end it "supports 1 to n assignment, splat is empty (3)" do - run(<<-CR).to_b.should be_true + run(<<-CRYSTAL).to_b.should be_true #{tuple_new} #{range_new} #{include_indexable} _, _, *x = {1, 2} x.is_a?(Tuple()) - CR + CRYSTAL end it "supports 1 to n assignment, raises if too short" do - run(<<-CR).to_b.should be_true + run(<<-CRYSTAL).to_b.should be_true require "prelude" begin @@ -198,11 +198,11 @@ describe "Code gen: multi assign" do rescue ex : IndexError ex.message == "Multiple assignment count mismatch" end - CR + CRYSTAL end it "supports 1 to n assignment, raises if out of bounds (1)" do - run(<<-CR).to_b.should be_true + run(<<-CRYSTAL).to_b.should be_true require "prelude" begin @@ -211,11 +211,11 @@ describe "Code gen: multi assign" do rescue ex : IndexError true end - CR + CRYSTAL end it "supports 1 to n assignment, raises if out of bounds (2)" do - run(<<-CR).to_b.should be_true + run(<<-CRYSTAL).to_b.should be_true require "prelude" begin @@ -224,33 +224,33 @@ describe "Code gen: multi assign" do rescue ex : IndexError true end - CR + CRYSTAL end end private def tuple_new - <<-CR + <<-CRYSTAL struct Tuple def self.new(*args) args end end - CR + CRYSTAL end private def range_new - <<-CR + <<-CRYSTAL struct Range(B, E) def initialize(@begin : B, @end : E, @exclusive : Bool = false) end end - CR + CRYSTAL end private def include_indexable - <<-CR + <<-CRYSTAL struct Tuple(*T) include Indexable(Union(*T)) end - CR + CRYSTAL end diff --git a/spec/compiler/codegen/pointer_spec.cr b/spec/compiler/codegen/pointer_spec.cr index 592420ec2248..8e5cd488fd52 100644 --- a/spec/compiler/codegen/pointer_spec.cr +++ b/spec/compiler/codegen/pointer_spec.cr @@ -439,7 +439,7 @@ describe "Code gen: pointer" do end it "passes arguments correctly for typedef metaclass (#8544)" do - run <<-CR + run <<-CRYSTAL lib LibFoo type Foo = Void* end @@ -453,7 +453,7 @@ describe "Code gen: pointer" do x = 1 LibFoo::Foo.foo(x) Pointer(Void).foo(x) - CR + CRYSTAL end it "generates correct code for Pointer.malloc(0) (#2905)" do diff --git a/spec/compiler/codegen/primitives_spec.cr b/spec/compiler/codegen/primitives_spec.cr index 442228eabfa7..cbe5695beb4f 100644 --- a/spec/compiler/codegen/primitives_spec.cr +++ b/spec/compiler/codegen/primitives_spec.cr @@ -312,7 +312,7 @@ describe "Code gen: primitives" do describe "atomicrmw" do it "codegens atomicrmw with enums" do - run(<<-CR).to_i.should eq(3) + run(<<-CRYSTAL).to_i.should eq(3) enum RMWBinOp Add = 1 end @@ -328,11 +328,11 @@ describe "Code gen: primitives" do x = 1 atomicrmw(:add, pointerof(x), 2, :sequentially_consistent, false) x - CR + CRYSTAL end it "codegens atomicrmw with enums" do - run(<<-CR).to_i.should eq(3) + run(<<-CRYSTAL).to_i.should eq(3) enum RMWBinOp Add = 1 end @@ -348,12 +348,12 @@ describe "Code gen: primitives" do x = 1 atomicrmw(RMWBinOp::Add, pointerof(x), 2, Ordering::SequentiallyConsistent, false) x - CR + CRYSTAL end # TODO: remove once support for 1.4 is dropped it "codegens atomicrmw with symbols" do - run(<<-CR).to_i.should eq(3) + run(<<-CRYSTAL).to_i.should eq(3) @[Primitive(:atomicrmw)] def atomicrmw(op : Symbol, ptr : Int32*, val : Int32, ordering : Symbol, singlethread : Bool) : Int32 end @@ -361,7 +361,7 @@ describe "Code gen: primitives" do x = 1 atomicrmw(:add, pointerof(x), 2, :sequentially_consistent, false) x - CR + CRYSTAL end end diff --git a/spec/compiler/codegen/private_spec.cr b/spec/compiler/codegen/private_spec.cr index 1a5ebc1c8ea2..b2e3a0e11b75 100644 --- a/spec/compiler/codegen/private_spec.cr +++ b/spec/compiler/codegen/private_spec.cr @@ -43,17 +43,17 @@ describe "Codegen: private" do end it "codegens class var of private type with same name as public type (#11620)" do - src1 = Compiler::Source.new("foo.cr", <<-CR) + src1 = Compiler::Source.new("foo.cr", <<-CRYSTAL) module Foo @@x = true end - CR + CRYSTAL - src2 = Compiler::Source.new("foo_private.cr", <<-CR) + src2 = Compiler::Source.new("foo_private.cr", <<-CRYSTAL) private module Foo @@x = 1 end - CR + CRYSTAL compiler = create_spec_compiler compiler.prelude = "empty" @@ -63,17 +63,17 @@ describe "Codegen: private" do end it "codegens class vars of private types with same name (#11620)" do - src1 = Compiler::Source.new("foo1.cr", <<-CR) + src1 = Compiler::Source.new("foo1.cr", <<-CRYSTAL) private module Foo @@x = true end - CR + CRYSTAL - src2 = Compiler::Source.new("foo2.cr", <<-CR) + src2 = Compiler::Source.new("foo2.cr", <<-CRYSTAL) private module Foo @@x = 1 end - CR + CRYSTAL compiler = create_spec_compiler compiler.prelude = "empty" diff --git a/spec/compiler/codegen/proc_spec.cr b/spec/compiler/codegen/proc_spec.cr index 05f9650a7e42..217f2b8ba9a5 100644 --- a/spec/compiler/codegen/proc_spec.cr +++ b/spec/compiler/codegen/proc_spec.cr @@ -10,11 +10,11 @@ describe "Code gen: proc" do end it "call proc literal with return type" do - run(<<-CR).to_b.should be_true + run(<<-CRYSTAL).to_b.should be_true f = -> : Int32 | Float64 { 1 } x = f.call x.is_a?(Int32) && x == 1 - CR + CRYSTAL end it "call proc pointer" do diff --git a/spec/compiler/crystal/tools/doc/method_spec.cr b/spec/compiler/crystal/tools/doc/method_spec.cr index 52069555d6e2..7a605c4c0ef2 100644 --- a/spec/compiler/crystal/tools/doc/method_spec.cr +++ b/spec/compiler/crystal/tools/doc/method_spec.cr @@ -158,23 +158,23 @@ describe Doc::Method do end it "trailing comment is not a doc comment" do - program = semantic(<<-CR, inject_primitives: false, wants_doc: true).program + program = semantic(<<-CRYSTAL, inject_primitives: false, wants_doc: true).program nil # trailing comment def foo end - CR + CRYSTAL generator = Doc::Generator.new program, [""] method = generator.type(program).lookup_class_method("foo").not_nil! method.doc.should be_nil end it "trailing comment is not part of a doc comment" do - program = semantic(<<-CR, inject_primitives: false, wants_doc: true).program + program = semantic(<<-CRYSTAL, inject_primitives: false, wants_doc: true).program nil # trailing comment # doc comment def foo end - CR + CRYSTAL generator = Doc::Generator.new program, [""] method = generator.type(program).lookup_class_method("foo").not_nil! method.doc.should eq("doc comment") diff --git a/spec/compiler/crystal/tools/doc/type_spec.cr b/spec/compiler/crystal/tools/doc/type_spec.cr index 5f2dfca77fa0..73184dff873b 100644 --- a/spec/compiler/crystal/tools/doc/type_spec.cr +++ b/spec/compiler/crystal/tools/doc/type_spec.cr @@ -47,12 +47,12 @@ describe Doc::Type do describe "#node_to_html" do it "shows relative path" do - program = semantic(<<-CODE).program + program = semantic(<<-CRYSTAL).program class Foo class Bar end end - CODE + CRYSTAL generator = Doc::Generator.new program, [""] foo = generator.type(program.types["Foo"]) @@ -60,12 +60,12 @@ describe Doc::Type do end it "shows relative generic" do - program = semantic(<<-CODE).program + program = semantic(<<-CRYSTAL).program class Foo class Bar(T) end end - CODE + CRYSTAL generator = Doc::Generator.new program, [""] foo = generator.type(program.types["Foo"]) @@ -73,12 +73,12 @@ describe Doc::Type do end it "shows generic path with necessary colons" do - program = semantic(<<-CODE).program + program = semantic(<<-CRYSTAL).program class Foo class Foo end end - CODE + CRYSTAL generator = Doc::Generator.new program, [""] foo = generator.type(program.types["Foo"]) @@ -86,12 +86,12 @@ describe Doc::Type do end it "shows generic path with unnecessary colons" do - program = semantic(<<-CODE).program + program = semantic(<<-CRYSTAL).program class Foo class Bar end end - CODE + CRYSTAL generator = Doc::Generator.new program, [""] foo = generator.type(program.types["Foo"]) @@ -99,13 +99,13 @@ describe Doc::Type do end it "shows tuples" do - program = semantic(<<-CODE).program + program = semantic(<<-CRYSTAL).program class Foo end class Bar end - CODE + CRYSTAL generator = Doc::Generator.new program, [""] foo = generator.type(program.types["Foo"]) @@ -114,13 +114,13 @@ describe Doc::Type do end it "shows named tuples" do - program = semantic(<<-CODE).program + program = semantic(<<-CRYSTAL).program class Foo end class Bar end - CODE + CRYSTAL generator = Doc::Generator.new program, [""] foo = generator.type(program.types["Foo"]) @@ -130,7 +130,7 @@ describe Doc::Type do end it "ASTNode has no superclass" do - program = semantic(<<-CODE).program + program = semantic(<<-CRYSTAL).program module Crystal module Macros class ASTNode @@ -139,7 +139,7 @@ describe Doc::Type do end end end - CODE + CRYSTAL generator = Doc::Generator.new program, [""] macros_module = program.types["Crystal"].types["Macros"] @@ -150,7 +150,7 @@ describe Doc::Type do end it "ASTNode has no ancestors" do - program = semantic(<<-CODE).program + program = semantic(<<-CRYSTAL).program module Crystal module Macros class ASTNode @@ -159,7 +159,7 @@ describe Doc::Type do end end end - CODE + CRYSTAL generator = Doc::Generator.new program, [""] macros_module = program.types["Crystal"].types["Macros"] @@ -171,13 +171,13 @@ describe Doc::Type do describe "#instance_methods" do it "sorts operators first" do - program = semantic(<<-CODE).program + program = semantic(<<-CRYSTAL).program class Foo def foo; end def ~; end def +; end end - CODE + CRYSTAL generator = Doc::Generator.new program, [""] type = generator.type(program.types["Foo"]) @@ -187,13 +187,13 @@ describe Doc::Type do describe "#class_methods" do it "sorts operators first" do - program = semantic(<<-CODE).program + program = semantic(<<-CRYSTAL).program class Foo def self.foo; end def self.~; end def self.+; end end - CODE + CRYSTAL generator = Doc::Generator.new program, [""] type = generator.type(program.types["Foo"]) @@ -203,13 +203,13 @@ describe Doc::Type do describe "#macros" do it "sorts operators first" do - program = semantic(<<-CODE).program + program = semantic(<<-CRYSTAL).program class Foo macro foo; end macro ~; end macro +; end end - CODE + CRYSTAL generator = Doc::Generator.new program, [""] type = generator.type(program.types["Foo"]) diff --git a/spec/compiler/crystal/tools/doc_spec.cr b/spec/compiler/crystal/tools/doc_spec.cr index d6acc34b9f29..541a6867d97b 100644 --- a/spec/compiler/crystal/tools/doc_spec.cr +++ b/spec/compiler/crystal/tools/doc_spec.cr @@ -4,13 +4,13 @@ describe Crystal::Doc::Generator do describe ".anchor_link" do it "generates the correct anchor link" do Crystal::Doc.anchor_link("anchor").should eq( - <<-ANCHOR + <<-HTML - ANCHOR + HTML ) end end diff --git a/spec/compiler/crystal/tools/expand_spec.cr b/spec/compiler/crystal/tools/expand_spec.cr index 9d29afc1911a..a4eccdea49d4 100644 --- a/spec/compiler/crystal/tools/expand_spec.cr +++ b/spec/compiler/crystal/tools/expand_spec.cr @@ -102,109 +102,109 @@ describe "expand" do end it "expands macro control {% if %}" do - code = <<-CODE + code = <<-CRYSTAL {%‸ if 1 == 1 %} true {% end %} - CODE + CRYSTAL assert_expand_simple code, "true" end it "expands macro control {% if %} with cursor inside it" do - code = <<-CODE + code = <<-CRYSTAL {% if 1 == 1 %} tr‸ue {% end %} - CODE + CRYSTAL assert_expand_simple code, "true" end it "expands macro control {% if %} with cursor at end of it" do - code = <<-CODE + code = <<-CRYSTAL {% if 1 == 1 %} true {% end ‸%} - CODE + CRYSTAL assert_expand_simple code, "true" end it "expands macro control {% if %} with indent" do - code = <<-CODE + code = <<-CRYSTAL begin {% if 1 == 1 %} t‸rue {% end %} end - CODE + CRYSTAL - original = <<-CODE + original = <<-CRYSTAL {% if 1 == 1 %} true {% end %} - CODE + CRYSTAL assert_expand_simple code, original: original, expanded: "true" end it "expands macro control {% for %}" do - code = <<-CODE + code = <<-CRYSTAL {% f‸or x in 1..3 %} {{ x }} {% end %} - CODE + CRYSTAL assert_expand_simple code, "1\n2\n3\n" end it "expands macro control {% for %} with cursor inside it" do - code = <<-CODE + code = <<-CRYSTAL {% for x in 1..3 %} ‸ {{ x }} {% end %} - CODE + CRYSTAL assert_expand_simple code, "1\n2\n3\n" end it "expands macro control {% for %} with cursor at end of it" do - code = <<-CODE + code = <<-CRYSTAL {% for x in 1..3 %} {{ x }} ‸{% end %} - CODE + CRYSTAL assert_expand_simple code, "1\n2\n3\n" end it "expands macro control {% for %} with indent" do - code = <<-CODE + code = <<-CRYSTAL begin {% f‸or x in 1..3 %} {{ x }} {% end %} end - CODE + CRYSTAL - original = <<-CODE + original = <<-CRYSTAL {% for x in 1..3 %} {{ x }} {% end %} - CODE + CRYSTAL assert_expand_simple code, original: original, expanded: "1\n2\n3\n" end it "expands simple macro" do - code = <<-CODE + code = <<-CRYSTAL macro foo 1 end ‸foo - CODE + CRYSTAL assert_expand_simple code, original: "foo", expanded: "1" do |expansion| expansion.expanded_macros.size.should eq(1) @@ -220,31 +220,31 @@ describe "expand" do end it "expands simple macro with cursor inside it" do - code = <<-CODE + code = <<-CRYSTAL macro foo 1 end f‸oo - CODE + CRYSTAL assert_expand_simple code, original: "foo", expanded: "1" end it "expands simple macro with cursor at end of it" do - code = <<-CODE + code = <<-CRYSTAL macro foo 1 end fo‸o - CODE + CRYSTAL assert_expand_simple code, original: "foo", expanded: "1" end it "expands complex macro" do - code = <<-CODE + code = <<-CRYSTAL macro foo {% if true %} "if true" @@ -255,13 +255,13 @@ describe "expand" do end ‸foo - CODE + CRYSTAL assert_expand_simple code, original: "foo", expanded: %("if true"\n"1"\n"2"\n"3"\n) end it "expands macros with 2 level" do - code = <<-CODE + code = <<-CRYSTAL macro foo :foo end @@ -272,7 +272,7 @@ describe "expand" do end b‸ar - CODE + CRYSTAL assert_expand code, [["bar", "foo\n:bar\n", ":foo\n:bar\n"]] do |result| expansion = result.expansions.not_nil![0] @@ -297,7 +297,7 @@ describe "expand" do end it "expands macros with 3 level" do - code = <<-CODE + code = <<-CRYSTAL macro foo :foo end @@ -314,7 +314,7 @@ describe "expand" do end ba‸z - CODE + CRYSTAL assert_expand code, [["baz", "foo\nbar\n:baz\n", ":foo\nfoo\n:bar\n:baz\n", ":foo\n:foo\n:bar\n:baz\n"]] do |result| expansion = result.expansions.not_nil![0] @@ -352,7 +352,7 @@ describe "expand" do end it "expands macro of module" do - code = <<-CODE + code = <<-CRYSTAL module Foo macro foo :Foo @@ -361,7 +361,7 @@ describe "expand" do end Foo.f‸oo - CODE + CRYSTAL assert_expand_simple code, original: "Foo.foo", expanded: ":Foo\n:foo\n" do |expansion| expansion.expanded_macros.size.should eq(1) @@ -377,7 +377,7 @@ describe "expand" do end it "expands macro of module with cursor at module name" do - code = <<-CODE + code = <<-CRYSTAL module Foo macro foo :Foo @@ -386,13 +386,13 @@ describe "expand" do end F‸oo.foo - CODE + CRYSTAL assert_expand_simple code, original: "Foo.foo", expanded: ":Foo\n:foo\n" end it "expands macro of module with cursor at dot" do - code = <<-CODE + code = <<-CRYSTAL module Foo macro foo :Foo @@ -401,13 +401,13 @@ describe "expand" do end Foo‸.foo - CODE + CRYSTAL assert_expand_simple code, original: "Foo.foo", expanded: ":Foo\n:foo\n" end it "expands macro of module inside module" do - code = <<-CODE + code = <<-CRYSTAL module Foo macro foo :Foo @@ -416,35 +416,35 @@ describe "expand" do f‸oo end - CODE + CRYSTAL assert_expand_simple code, original: "foo", expanded: ":Foo\n:foo\n" end %w(module class struct enum lib).each do |keyword| it "expands macro expression inside #{keyword}" do - code = <<-CODE + code = <<-CRYSTAL #{keyword} Foo ‸{{ "Foo = 1".id }} end - CODE + CRYSTAL assert_expand_simple code, original: %({{ "Foo = 1".id }}), expanded: "Foo = 1" end it "expands macro expression inside private #{keyword}" do - code = <<-CODE + code = <<-CRYSTAL private #{keyword} Foo ‸{{ "Foo = 1".id }} end - CODE + CRYSTAL assert_expand_simple code, original: %({{ "Foo = 1".id }}), expanded: "Foo = 1" end unless keyword == "lib" it "expands macro expression inside def of private #{keyword}" do - code = <<-CODE + code = <<-CRYSTAL private #{keyword} Foo Foo = 1 def self.foo @@ -453,7 +453,7 @@ describe "expand" do end Foo.foo - CODE + CRYSTAL assert_expand_simple code, original: "{{ :foo }}", expanded: ":foo" end @@ -462,25 +462,25 @@ describe "expand" do %w(struct union).each do |keyword| it "expands macro expression inside C #{keyword}" do - code = <<-CODE + code = <<-CRYSTAL lib Foo #{keyword} Foo ‸{{ "x : Int32".id }} end end - CODE + CRYSTAL assert_expand_simple code, original: %({{ "x : Int32".id }}), expanded: "x : Int32" end it "expands macro expression inside C #{keyword} of private lib" do - code = <<-CODE + code = <<-CRYSTAL private lib Foo #{keyword} Foo ‸{{ "x : Int32".id }} end end - CODE + CRYSTAL assert_expand_simple code, original: %({{ "x : Int32".id }}), expanded: "x : Int32" end @@ -488,14 +488,14 @@ describe "expand" do ["", "private "].each do |prefix| it "expands macro expression inside #{prefix}def" do - code = <<-CODE + code = <<-CRYSTAL #{prefix}def foo(x : T) forall T ‸{{ T }} end foo 1 foo "bar" - CODE + CRYSTAL assert_expand code, [ ["{{ T }}", "Int32"], @@ -504,7 +504,7 @@ describe "expand" do end it "expands macro expression inside def of #{prefix}module" do - code = <<-CODE + code = <<-CRYSTAL #{prefix}module Foo(T) def self.foo {{ ‸T }} @@ -514,7 +514,7 @@ describe "expand" do Foo(Int32).foo Foo(String).foo Foo(1).foo - CODE + CRYSTAL assert_expand code, [ ["{{ T }}", "Int32"], @@ -524,7 +524,7 @@ describe "expand" do end it "expands macro expression inside def of nested #{prefix}module" do - code = <<-CODE + code = <<-CRYSTAL #{prefix}module Foo #{prefix}module Bar(T) def self.foo @@ -536,7 +536,7 @@ describe "expand" do Bar(String).foo Bar(1).foo end - CODE + CRYSTAL assert_expand code, [ ["{{ T }}", "Int32"], @@ -547,54 +547,54 @@ describe "expand" do end it "expands macro expression inside fun" do - code = <<-CODE + code = <<-CRYSTAL fun foo {{ :foo‸ }} end - CODE + CRYSTAL assert_expand_simple code, original: "{{ :foo }}", expanded: ":foo" end it "doesn't expand macro expression" do - code = <<-CODE + code = <<-CRYSTAL {{ 1 + 2 }} ‸ - CODE + CRYSTAL assert_expand_fail code end it "doesn't expand macro expression with cursor out of end" do - code = <<-CODE + code = <<-CRYSTAL {{ 1 + 2 }}‸ - CODE + CRYSTAL assert_expand_fail code end it "doesn't expand macro expression" do - code = <<-CODE + code = <<-CRYSTAL ‸ {{ 1 + 2 }} - CODE + CRYSTAL assert_expand_fail code end it "doesn't expand normal call" do - code = <<-CODE + code = <<-CRYSTAL def foo 1 end ‸foo - CODE + CRYSTAL assert_expand_fail code, "no expansion found: foo may not be a macro" end it "expands macro with doc" do - code = <<-CODE + code = <<-CRYSTAL macro foo(x) # string of {{ x }} def {{ x }}_str @@ -607,9 +607,9 @@ describe "expand" do end ‸foo(hello) - CODE + CRYSTAL - expanded = <<-CODE + expanded = <<-CRYSTAL # string of hello def hello_str "hello" @@ -618,7 +618,7 @@ describe "expand" do def hello_sym :hello end - CODE + CRYSTAL assert_expand_simple code, original: "foo(hello)", expanded: expanded + '\n' end diff --git a/spec/compiler/crystal/tools/hierarchy_spec.cr b/spec/compiler/crystal/tools/hierarchy_spec.cr index b2c80fa6e018..9e01acded7ec 100644 --- a/spec/compiler/crystal/tools/hierarchy_spec.cr +++ b/spec/compiler/crystal/tools/hierarchy_spec.cr @@ -2,13 +2,13 @@ require "../../../spec_helper" describe Crystal::TextHierarchyPrinter do it "works" do - program = semantic(<<-CR).program + program = semantic(<<-CRYSTAL).program class Foo end class Bar < Foo end - CR + CRYSTAL output = String.build { |io| Crystal.print_hierarchy(program, io, "ar$", "text") } output.should eq(<<-EOS) @@ -25,13 +25,13 @@ end describe Crystal::JSONHierarchyPrinter do it "works" do - program = semantic(<<-CR).program + program = semantic(<<-CRYSTAL).program class Foo end class Bar < Foo end - CR + CRYSTAL output = String.build { |io| Crystal.print_hierarchy(program, io, "ar$", "json") } JSON.parse(output).should eq(JSON.parse(<<-EOS)) diff --git a/spec/compiler/crystal/tools/init_spec.cr b/spec/compiler/crystal/tools/init_spec.cr index 05e52ff10da7..1408b0d4e0af 100644 --- a/spec/compiler/crystal/tools/init_spec.cr +++ b/spec/compiler/crystal/tools/init_spec.cr @@ -45,11 +45,11 @@ module Crystal describe Init::InitProject do it "correctly uses git config" do within_temporary_directory do - File.write(".gitconfig", <<-CONTENT) + File.write(".gitconfig", <<-INI) [user] email = dorian@dorianmarie.fr name = Dorian Marié - CONTENT + INI with_env("GIT_CONFIG": "#{FileUtils.pwd}/.gitconfig") do exec_init("example", "example", "app") @@ -118,7 +118,7 @@ module Crystal readme.should contain("# example") readme.should contain(%{1. Add the dependency to your `shard.yml`:}) - readme.should contain(<<-EOF + readme.should contain(<<-MARKDOWN ```yaml dependencies: @@ -126,7 +126,7 @@ module Crystal github: jsmith/example ``` - EOF + MARKDOWN ) readme.should contain(%{2. Run `shards install`}) readme.should contain(%{TODO: Write a description here}) @@ -142,7 +142,7 @@ module Crystal readme.should contain(%{TODO: Write a description here}) readme.should_not contain(%{1. Add the dependency to your `shard.yml`:}) - readme.should_not contain(<<-EOF + readme.should_not contain(<<-MARKDOWN ```yaml dependencies: @@ -150,7 +150,7 @@ module Crystal github: jsmith/example ``` - EOF + MARKDOWN ) readme.should_not contain(%{2. Run `shards install`}) readme.should contain(%{TODO: Write installation instructions here}) @@ -175,7 +175,7 @@ module Crystal end with_file "example/src/example.cr" do |example| - example.should eq(<<-EOF + example.should eq(<<-CRYSTAL # TODO: Write documentation for `Example` module Example VERSION = "0.1.0" @@ -183,21 +183,21 @@ module Crystal # TODO: Put your code here end - EOF + CRYSTAL ) end with_file "example/spec/spec_helper.cr" do |example| - example.should eq(<<-EOF + example.should eq(<<-CRYSTAL require "spec" require "../src/example" - EOF + CRYSTAL ) end with_file "example/spec/example_spec.cr" do |example| - example.should eq(<<-EOF + example.should eq(<<-CRYSTAL require "./spec_helper" describe Example do @@ -208,7 +208,7 @@ module Crystal end end - EOF + CRYSTAL ) end diff --git a/spec/compiler/crystal/tools/playground_spec.cr b/spec/compiler/crystal/tools/playground_spec.cr index ae784db3335b..2fe1a9b722f7 100644 --- a/spec/compiler/crystal/tools/playground_spec.cr +++ b/spec/compiler/crystal/tools/playground_spec.cr @@ -141,22 +141,22 @@ describe Playground::AgentInstrumentorTransformer do assert_agent %( def foo 4 - end), <<-CR + end), <<-CRYSTAL def foo _p.i(3) { 4 } end - CR + CRYSTAL end it "instrument single statement var def" do assert_agent %( def foo(x) x - end), <<-CR + end), <<-CRYSTAL def foo(x) _p.i(3) { x } end - CR + CRYSTAL end it "instrument multi statement def" do @@ -164,23 +164,23 @@ describe Playground::AgentInstrumentorTransformer do def foo 2 6 - end), <<-CR + end), <<-CRYSTAL def foo _p.i(3) { 2 } _p.i(4) { 6 } end - CR + CRYSTAL end it "instrument returns inside def" do assert_agent %( def foo return 4 - end), <<-CR + end), <<-CRYSTAL def foo return _p.i(3) { 4 } end - CR + CRYSTAL end it "instrument class defs" do @@ -196,7 +196,7 @@ describe Playground::AgentInstrumentorTransformer do def self.bar(x, y) x+y end - end), <<-CR + end), <<-CRYSTAL class Foo def initialize @x = _p.i(4) { 3 }.as(typeof(3)) @@ -209,7 +209,7 @@ describe Playground::AgentInstrumentorTransformer do _p.i(11) { x + y } end end - CR + CRYSTAL end it "instrument instance variable and class variables reads and writes" do @@ -225,7 +225,7 @@ describe Playground::AgentInstrumentorTransformer do def self.bar @@x end - end), <<-CR + end), <<-CRYSTAL class Foo def initialize @x = _p.i(4) { 3 }.as(typeof(3)) @@ -238,7 +238,7 @@ describe Playground::AgentInstrumentorTransformer do _p.i(11) { @@x } end end - CR + CRYSTAL end it "do not instrument class initializing arguments" do @@ -248,7 +248,7 @@ describe Playground::AgentInstrumentorTransformer do @z = @x + @y end end - ), <<-CR + ), <<-CRYSTAL class Foo def initialize(x, y) @x = x @@ -256,7 +256,7 @@ describe Playground::AgentInstrumentorTransformer do @z = _p.i(4) { @x + @y }.as(typeof(@x + @y)) end end - CR + CRYSTAL end it "allow visibility modifiers" do @@ -268,7 +268,7 @@ describe Playground::AgentInstrumentorTransformer do protected def self.bar 2 end - end), <<-CR + end), <<-CRYSTAL class Foo private def bar _p.i(4) { 1 } @@ -277,18 +277,18 @@ describe Playground::AgentInstrumentorTransformer do _p.i(7) { 2 } end end - CR + CRYSTAL end it "do not instrument macro calls in class" do assert_agent %( class Foo property foo - end), <<-CR + end), <<-CRYSTAL class Foo property foo end - CR + CRYSTAL end it "instrument nested class defs" do @@ -299,7 +299,7 @@ describe Playground::AgentInstrumentorTransformer do @x = 3 end end - end), <<-CR + end), <<-CRYSTAL class Bar class Foo def initialize @@ -307,19 +307,19 @@ describe Playground::AgentInstrumentorTransformer do end end end - CR + CRYSTAL end it "do not instrument records class" do assert_agent %( record Foo, x, y - ), <<-CR + ), <<-CRYSTAL record Foo, x, y - CR + CRYSTAL end it "do not instrument top level macro calls" do - assert_agent(<<-FROM, <<-TO + assert_agent(<<-CRYSTAL, <<-CRYSTAL) macro bar def foo 4 @@ -327,7 +327,7 @@ describe Playground::AgentInstrumentorTransformer do end bar foo - FROM + CRYSTAL macro bar def foo 4 @@ -335,12 +335,11 @@ describe Playground::AgentInstrumentorTransformer do end bar _p.i(7) { foo } - TO - ) + CRYSTAL end it "do not instrument class/module declared macro" do - assert_agent(<<-FROM, <<-TO + assert_agent(<<-CRYSTAL, <<-CRYSTAL) module Bar macro bar 4 @@ -354,7 +353,7 @@ describe Playground::AgentInstrumentorTransformer do 8 end end - FROM + CRYSTAL module Bar macro bar 4 @@ -368,8 +367,7 @@ describe Playground::AgentInstrumentorTransformer do _p.i(11) { 8 } end end - TO - ) + CRYSTAL end it "instrument inside modules" do @@ -382,7 +380,7 @@ describe Playground::AgentInstrumentorTransformer do end end end - end), <<-CR + end), <<-CRYSTAL module Bar class Baz class Foo @@ -392,7 +390,7 @@ describe Playground::AgentInstrumentorTransformer do end end end - CR + CRYSTAL end it "instrument if statement" do @@ -402,13 +400,13 @@ describe Playground::AgentInstrumentorTransformer do else c end - ), <<-CR + ), <<-CRYSTAL if a _p.i(3) { b } else _p.i(5) { c } end - CR + CRYSTAL end it "instrument unless statement" do @@ -418,13 +416,13 @@ describe Playground::AgentInstrumentorTransformer do else c end - ), <<-CR + ), <<-CRYSTAL unless a _p.i(3) { b } else _p.i(5) { c } end - CR + CRYSTAL end it "instrument while statement" do @@ -433,12 +431,12 @@ describe Playground::AgentInstrumentorTransformer do b c end - ), <<-CR + ), <<-CRYSTAL while a _p.i(3) { b } _p.i(4) { c } end - CR + CRYSTAL end it "instrument case statement" do @@ -452,7 +450,7 @@ describe Playground::AgentInstrumentorTransformer do else d end - ), <<-CR + ), <<-CRYSTAL case a when 0 _p.i(4) { b } @@ -461,7 +459,7 @@ describe Playground::AgentInstrumentorTransformer do else _p.i(8) { d } end - CR + CRYSTAL end it "instrument blocks and single yields" do @@ -472,7 +470,7 @@ describe Playground::AgentInstrumentorTransformer do foo do |a| a end - ), <<-CR + ), <<-CRYSTAL def foo(x) yield _p.i(3) { x } end @@ -481,7 +479,7 @@ describe Playground::AgentInstrumentorTransformer do _p.i(6) { a } end end - CR + CRYSTAL end it "instrument blocks and but non multi yields" do @@ -492,7 +490,7 @@ describe Playground::AgentInstrumentorTransformer do foo do |a, i| a end - ), <<-CR + ), <<-CRYSTAL def foo(x) yield x, 1 end @@ -501,7 +499,7 @@ describe Playground::AgentInstrumentorTransformer do _p.i(6) { a } end end - CR + CRYSTAL end it "instrument nested blocks unless in same line" do @@ -513,7 +511,7 @@ describe Playground::AgentInstrumentorTransformer do end baz { 'c' } end - ), <<-CR + ), <<-CRYSTAL a = _p.i(2) do foo do _p.i(3) { 'a' } @@ -529,7 +527,7 @@ describe Playground::AgentInstrumentorTransformer do end end end - CR + CRYSTAL end it "instrument typeof" do @@ -554,7 +552,7 @@ describe Playground::AgentInstrumentorTransformer do rescue 0 end - ), <<-CR + ), <<-CRYSTAL begin raise(_p.i(3) { "The exception" }) rescue ex : String @@ -573,7 +571,7 @@ describe Playground::AgentInstrumentorTransformer do _p.i(16) { 0 } end end - CR + CRYSTAL end end diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index f8f8d5fddd0e..5731cbe68815 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -308,15 +308,15 @@ describe Crystal::Formatter do ); end CRYSTAL - assert_format <<-BEFORE, <<-AFTER + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( @[MyAnn] bar ); end - BEFORE + CRYSTAL def foo( @[MyAnn] bar ); end - AFTER + CRYSTAL assert_format <<-CRYSTAL def foo( @@ -352,31 +352,31 @@ describe Crystal::Formatter do ); end CRYSTAL - assert_format <<-BEFORE, <<-AFTER + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( @[MyAnn] bar ); end - BEFORE + CRYSTAL def foo( @[MyAnn] bar ); end - AFTER + CRYSTAL - assert_format <<-BEFORE, <<-AFTER + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( - @[MyAnn] + @[MyAnn] bar ); end - BEFORE + CRYSTAL def foo( @[MyAnn] bar ); end - AFTER + CRYSTAL - assert_format <<-BEFORE + assert_format <<-CRYSTAL def foo( @[MyAnn] @[MyAnn] @@ -386,9 +386,9 @@ describe Crystal::Formatter do @[MyAnn] biz ); end - BEFORE + CRYSTAL - assert_format <<-BEFORE + assert_format <<-CRYSTAL def foo( @[MyAnn] @[MyAnn] @@ -400,12 +400,12 @@ describe Crystal::Formatter do @[MyAnn] biz ); end - BEFORE + CRYSTAL - assert_format <<-BEFORE, <<-AFTER + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( - @[MyAnn] - @[MyAnn] + @[MyAnn] + @[MyAnn] bar, @[MyAnn] @[MyAnn] baz, @@ -416,7 +416,7 @@ describe Crystal::Formatter do biz ); end - BEFORE + CRYSTAL def foo( @[MyAnn] @[MyAnn] @@ -428,7 +428,7 @@ describe Crystal::Formatter do @[MyAnn] biz ); end - AFTER + CRYSTAL assert_format "loop do\n 1\nrescue\n 2\nend" assert_format "loop do\n 1\n loop do\n 2\n rescue\n 3\n end\n 4\nend" @@ -770,12 +770,12 @@ describe Crystal::Formatter do assert_format "case 1 \n in Int32 \n 3 \n end", "case 1\nin Int32\n 3\nend" - assert_format <<-CODE + assert_format <<-CRYSTAL case 0 when 0 then 1; 2 # Comments end - CODE + CRYSTAL assert_format "select \n when foo \n 2 \n end", "select\nwhen foo\n 2\nend" assert_format "select \n when foo \n 2 \n when bar \n 3 \n end", "select\nwhen foo\n 2\nwhen bar\n 3\nend" @@ -916,12 +916,12 @@ describe Crystal::Formatter do assert_format "alias Foo::Bar=Baz", "alias Foo::Bar = Baz" assert_format "alias Foo::Bar= Baz", "alias Foo::Bar = Baz" assert_format "alias Foo::Bar =Baz", "alias Foo::Bar = Baz" - assert_format <<-BEFORE, <<-AFTER + assert_format <<-CRYSTAL, <<-CRYSTAL alias Foo= Bar - BEFORE + CRYSTAL alias Foo = Bar - AFTER + CRYSTAL assert_format "lib Foo\nend" assert_format "lib Foo\ntype Foo = Bar\nend", "lib Foo\n type Foo = Bar\nend" assert_format "lib Foo\nfun foo\nend", "lib Foo\n fun foo\nend" @@ -938,81 +938,81 @@ describe Crystal::Formatter do assert_format "lib Foo\n fun foo(Int32) : Int32\nend" assert_format "fun foo(x : Int32) : Int32\n 1\nend" assert_format "fun foo(\n x : Int32,\n ...\n) : Int32\n 1\nend" - assert_format <<-CODE + assert_format <<-CRYSTAL lib Foo fun foo = bar(Int32) : Int32 end - CODE - assert_format <<-CODE + CRYSTAL + assert_format <<-CRYSTAL lib Foo fun foo = bar : Void end - CODE - assert_format <<-BEFORE, <<-AFTER + CRYSTAL + assert_format <<-CRYSTAL, <<-CRYSTAL lib Foo fun foo = bar : Void end - BEFORE + CRYSTAL lib Foo fun foo = bar : Void end - AFTER - assert_format <<-CODE + CRYSTAL + assert_format <<-CRYSTAL lib Foo fun foo = bar(Int32) : Int32 end - CODE - assert_format <<-BEFORE, <<-AFTER + CRYSTAL + assert_format <<-CRYSTAL, <<-CRYSTAL lib Foo fun foo = bar(Int32) : Int32 end - BEFORE + CRYSTAL lib Foo fun foo = bar(Int32) : Int32 end - AFTER - assert_format <<-BEFORE, <<-AFTER + CRYSTAL + assert_format <<-CRYSTAL, <<-CRYSTAL lib Foo fun foo = bar(Int32, Int32) : Int32 end - BEFORE + CRYSTAL lib Foo fun foo = bar(Int32, Int32) : Int32 end - AFTER + CRYSTAL assert_format "lib Foo\n fun foo = bar(Int32) : Int32\nend" - assert_format <<-CODE + assert_format <<-CRYSTAL lib Foo fun foo = "bar"(Int32) : Int32 end - CODE - assert_format <<-CODE + CRYSTAL + assert_format <<-CRYSTAL lib Foo fun foo = "bar"(Int32) : Int32 end - CODE - assert_format <<-CODE + CRYSTAL + assert_format <<-CRYSTAL lib Foo fun foo = "bar"(Int32) : Int32 # comment end - CODE + CRYSTAL assert_format "lib Foo\n $foo : Int32 \nend", "lib Foo\n $foo : Int32\nend" assert_format "lib Foo\n $foo = hello : Int32 \nend", "lib Foo\n $foo = hello : Int32\nend" assert_format "lib Foo\nalias Foo = Bar -> \n$a : Int32\nend", "lib Foo\n alias Foo = Bar ->\n $a : Int32\nend" @@ -1642,13 +1642,13 @@ describe Crystal::Formatter do assert_format "{%\n if 1\n 2\n end\n%}" - assert_format <<-CODE + assert_format <<-CRYSTAL # ```text # 1 + 2 # ``` - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL # ```text # 1 + 2 # ``` @@ -1656,19 +1656,19 @@ describe Crystal::Formatter do # ``` # 3 + 4 # ``` - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL X(typeof(begin e.is_a?(Y) ? 1 : 2 end)) - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL X(typeof(begin e.is_a?(Y) end)) - CODE + CRYSTAL # Keep trailing spaces in macros. assert_format( @@ -1762,7 +1762,7 @@ describe Crystal::Formatter do assert_format "foo\n .bar\n .baz(\n 1\n )" assert_format "foo.bar\n .baz(\n 1\n )" - assert_format <<-BEFORE, + assert_format <<-CRYSTAL, def foo {% if flag?(:foo) %} foo + bar @@ -1770,8 +1770,8 @@ describe Crystal::Formatter do baz + qux {% end %} end - BEFORE - <<-AFTER + CRYSTAL + <<-CRYSTAL def foo {% if flag?(:foo) %} foo + bar @@ -1779,39 +1779,39 @@ describe Crystal::Formatter do baz + qux {% end %} end - AFTER + CRYSTAL - assert_format <<-BEFORE, + assert_format <<-CRYSTAL, def foo {% for x in y %} foo + bar {% end %} end - BEFORE - <<-AFTER + CRYSTAL + <<-CRYSTAL def foo {% for x in y %} foo + bar {% end %} end - AFTER + CRYSTAL - assert_format <<-BEFORE, + assert_format <<-CRYSTAL, x = {% if flag?(:foo) %} foo + bar {% else %} baz + qux {% end %} - BEFORE - <<-AFTER + CRYSTAL + <<-CRYSTAL x = {% if flag?(:foo) %} foo + bar {% else %} baz + qux {% end %} - AFTER + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL {% if flag?(:freebsd) %} 1 + 2 {% end %} @@ -1820,9 +1820,9 @@ describe Crystal::Formatter do when 1234 then 1 else x end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL {% if z %} 1 {% end %} @@ -1834,17 +1834,17 @@ describe Crystal::Formatter do 1 end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL lib LibFoo {% begin %} fun foo : Int32 {% end %} end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL lib LibFoo struct Bar {% begin %} @@ -1852,9 +1852,9 @@ describe Crystal::Formatter do {% end %} end end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL enum Foo {% begin %} A @@ -1862,9 +1862,9 @@ describe Crystal::Formatter do C {% end %} end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL a = 1 b, c = 2, 3 {% begin %} @@ -1872,50 +1872,50 @@ describe Crystal::Formatter do b |= 2 c |= 3 {% end %} - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL lib LibFoo {% begin %} fun x = y(Int32) {% end %} end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL {% begin %} " foo" {% end %} - CODE + CRYSTAL - assert_format <<-BEFORE, + assert_format <<-CRYSTAL, {% if z %} class Foo end {% end %} - BEFORE - <<-AFTER + CRYSTAL + <<-CRYSTAL {% if z %} class Foo end {% end %} - AFTER + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL {% if true %} # x {% end %} - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL {% if true %} # x # y {% end %} - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL {% if true %} # x # @@ -1924,17 +1924,17 @@ describe Crystal::Formatter do # ``` # x # ``` - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL def foo(x) {% if true %} x = x + 2 {% end %} end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL def foo(x) {% if true %} # comment @@ -1942,9 +1942,9 @@ describe Crystal::Formatter do B = 2 {% end %} end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL def foo(x) {% if true %} \\{% if true %} @@ -1959,17 +1959,17 @@ describe Crystal::Formatter do \\{% x %} {% end %} end - CODE + CRYSTAL it "gives proper line number in syntax error inside macro" do - source = <<-CODE + source = <<-CRYSTAL a = 1 b = 2 {% begin %} c |= 3 {% end %} - CODE + CRYSTAL ex = expect_raises(Crystal::SyntaxException) do Crystal.format(source) @@ -1978,52 +1978,52 @@ describe Crystal::Formatter do end # #8197 - assert_format <<-CODE + assert_format <<-CRYSTAL foo .foo1(bar .bar1 .bar2) - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL foo.foo1( bar .bar1 .bar2) - CODE + CRYSTAL assert_format "[] of (Array(T))" assert_format "[] of (((Array(T))))" - assert_format <<-CODE + assert_format <<-CRYSTAL macro foo # bar baz end - CODE + CRYSTAL assert_format "a.!" assert_format "a &.!" assert_format "a &.a.!" assert_format "a &.!.!" - assert_format <<-CODE + assert_format <<-CRYSTAL ->{ # first comment puts "hi" # second comment } - CODE + CRYSTAL # #9014 - assert_format <<-CODE + assert_format <<-CRYSTAL {% unless true 1 end %} - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL {% unless true 1 @@ -2031,9 +2031,9 @@ describe Crystal::Formatter do 2 end %} - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL {% if true 1 @@ -2041,48 +2041,48 @@ describe Crystal::Formatter do 2 end %} - CODE + CRYSTAL # #4626 - assert_format <<-CODE + assert_format <<-CRYSTAL 1 # foo / 1 / - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL 1 # foo / #{1} / - CODE + CRYSTAL - assert_format <<-BEFORE, + assert_format <<-CRYSTAL, def foo # Comment end - BEFORE - <<-AFTER + CRYSTAL + <<-CRYSTAL def foo # Comment end - AFTER + CRYSTAL - assert_format <<-BEFORE, + assert_format <<-CRYSTAL, def foo 1 # Comment end - BEFORE - <<-AFTER + CRYSTAL + <<-CRYSTAL def foo 1 # Comment end - AFTER + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL def foo 1 end @@ -2092,142 +2092,142 @@ describe Crystal::Formatter do def bar 2 end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL require "foo" @x : Int32 class Bar end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL x = <<-FOO hello FOO def bar end - CODE + CRYSTAL - assert_format <<-BEFORE, <<-AFTER + assert_format <<-CRYSTAL, <<-CRYSTAL begin 1 # Comment end - BEFORE + CRYSTAL begin 1 # Comment end - AFTER + CRYSTAL - assert_format <<-BEFORE, <<-AFTER + assert_format <<-CRYSTAL, <<-CRYSTAL begin # Comment end - BEFORE + CRYSTAL begin # Comment end - AFTER + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL foo 1, # comment do end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL foo 1, # comment # bar do end - CODE + CRYSTAL # #10190 - assert_format <<-CODE + assert_format <<-CRYSTAL foo( 1, ) do 2 end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL foo( 1, ) { 2 } - CODE + CRYSTAL # #11079 - assert_format <<-CODE + assert_format <<-CRYSTAL foo = [1, [2, 3], 4] - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL foo = {1, {2, 3}, 4} - CODE + CRYSTAL # #10817 - assert_format <<-CODE + assert_format <<-CRYSTAL def func # comment (1 + 2) / 3 end - CODE + CRYSTAL # #10943 - assert_format <<-CODE + assert_format <<-CRYSTAL foo do # a # b bar end - CODE + CRYSTAL # #10499 - assert_format <<-CODE + assert_format <<-CRYSTAL case nil else nil; nil # comment end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL case nil else nil; nil # comment end - CODE + CRYSTAL # #12493 - assert_format <<-CODE + assert_format <<-CRYSTAL select # when foo when bar break end - CODE + CRYSTAL - assert_format <<-CODE + assert_format <<-CRYSTAL select # some comment when bar break end - CODE + CRYSTAL # #12378 - assert_format <<-CODE + assert_format <<-CRYSTAL macro foo macro bar \\{% begin %} @@ -2235,5 +2235,5 @@ describe Crystal::Formatter do \\{% end %} end end - CODE + CRYSTAL end diff --git a/spec/compiler/interpreter/autocast_spec.cr b/spec/compiler/interpreter/autocast_spec.cr index 0aa9e93c250c..701ceeefd0bd 100644 --- a/spec/compiler/interpreter/autocast_spec.cr +++ b/spec/compiler/interpreter/autocast_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "autocast" do it "autocasts symbol to enum" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) enum Color Red Green @@ -17,31 +17,31 @@ describe Crystal::Repl::Interpreter do c = foo :green c.value - CODE + CRYSTAL end it "autocasts number literal to integer" do - interpret(<<-CODE).should eq(12) + interpret(<<-CRYSTAL).should eq(12) def foo(x : UInt8) x end foo(12) - CODE + CRYSTAL end it "autocasts number literal to float" do - interpret(<<-CODE).should eq(12.0) + interpret(<<-CRYSTAL).should eq(12.0) def foo(x : Float64) x end foo(12) - CODE + CRYSTAL end it "autocasts symbol to enum in multidispatch (#11782)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) enum Color Red Green @@ -61,11 +61,11 @@ describe Crystal::Repl::Interpreter do end (Foo.new || Bar.new).foo(:green).value - CODE + CRYSTAL end it "autocasts int in multidispatch" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) class Foo def foo(x : Int64) x @@ -79,11 +79,11 @@ describe Crystal::Repl::Interpreter do end (Foo.new || Bar.new).foo(1) - CODE + CRYSTAL end it "autocasts symbol to enum in ivar initializer (#12216)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) enum Color Red Green @@ -100,40 +100,40 @@ describe Crystal::Repl::Interpreter do foo = Foo.new foo.color.value - CODE + CRYSTAL end it "autocasts integer var to integer (#12560)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) def foo(x : Int64) x end x = 1_i32 foo(x) - CODE + CRYSTAL end it "autocasts integer var to float (#12560)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) def foo(x : Float64) x end x = 1_i32 foo(x) - CODE + CRYSTAL end it "autocasts float32 var to float64 (#12560)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) def foo(x : Float64) x end x = 1.0_f32 foo(x) - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/blocks_spec.cr b/spec/compiler/interpreter/blocks_spec.cr index 4261242d03a6..227979288a67 100644 --- a/spec/compiler/interpreter/blocks_spec.cr +++ b/spec/compiler/interpreter/blocks_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "blocks" do it "interprets simplest block" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) def foo yield end @@ -14,11 +14,11 @@ describe Crystal::Repl::Interpreter do a += 1 end a - CODE + CRYSTAL end it "interprets block with multiple yields" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) def foo yield yield @@ -29,11 +29,11 @@ describe Crystal::Repl::Interpreter do a += 1 end a - CODE + CRYSTAL end it "interprets yield return value" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) def foo yield end @@ -42,11 +42,11 @@ describe Crystal::Repl::Interpreter do 1 end z - CODE + CRYSTAL end it "interprets yield inside another block" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) def foo bar do yield @@ -62,11 +62,11 @@ describe Crystal::Repl::Interpreter do a += 1 end a - CODE + CRYSTAL end it "interprets yield inside def with arguments" do - interpret(<<-CODE).should eq(18) + interpret(<<-CRYSTAL).should eq(18) def foo(x) a = yield a + x @@ -76,11 +76,11 @@ describe Crystal::Repl::Interpreter do 8 end a - CODE + CRYSTAL end it "interprets yield expression" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) def foo yield 1 end @@ -90,11 +90,11 @@ describe Crystal::Repl::Interpreter do a += x end a - CODE + CRYSTAL end it "interprets yield expressions" do - interpret(<<-CODE).should eq(2 + 2*3 + 4*5) + interpret(<<-CRYSTAL).should eq(2 + 2*3 + 4*5) def foo yield 3, 4, 5 end @@ -104,11 +104,11 @@ describe Crystal::Repl::Interpreter do a += a * x + y * z end a - CODE + CRYSTAL end it "discards yield expression" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) def foo yield 1 end @@ -118,11 +118,11 @@ describe Crystal::Repl::Interpreter do a = 3 end a - CODE + CRYSTAL end it "yields different values to form a union" do - interpret(<<-CODE).should eq(5) + interpret(<<-CRYSTAL).should eq(5) def foo yield 1 yield 'a' @@ -139,11 +139,11 @@ describe Crystal::Repl::Interpreter do end end a - CODE + CRYSTAL end it "returns from block" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def foo baz do yield @@ -165,11 +165,11 @@ describe Crystal::Repl::Interpreter do end bar - CODE + CRYSTAL end it "interprets next inside block" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) def foo yield end @@ -181,11 +181,11 @@ describe Crystal::Repl::Interpreter do end 20 end - CODE + CRYSTAL end it "interprets next inside block (union, through next)" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) def foo yield end @@ -203,11 +203,11 @@ describe Crystal::Repl::Interpreter do else 20 end - CODE + CRYSTAL end it "interprets next inside block (union, through normal exit)" do - interpret(<<-CODE).should eq('a') + interpret(<<-CRYSTAL).should eq('a') def foo yield end @@ -225,11 +225,11 @@ describe Crystal::Repl::Interpreter do else 'b' end - CODE + CRYSTAL end it "interprets break inside block" do - interpret(<<-CODE).should eq(20) + interpret(<<-CRYSTAL).should eq(20) def baz yield end @@ -248,11 +248,11 @@ describe Crystal::Repl::Interpreter do end 20 end - CODE + CRYSTAL end it "interprets break inside block (union, through break)" do - interpret(<<-CODE).should eq(20) + interpret(<<-CRYSTAL).should eq(20) def foo yield 'a' @@ -270,11 +270,11 @@ describe Crystal::Repl::Interpreter do else 30 end - CODE + CRYSTAL end it "interprets break inside block (union, through normal flow)" do - interpret(<<-CODE).should eq('a') + interpret(<<-CRYSTAL).should eq('a') def foo yield 'a' @@ -292,11 +292,11 @@ describe Crystal::Repl::Interpreter do else 'b' end - CODE + CRYSTAL end it "interprets break inside block (union, through return)" do - interpret(<<-CODE).should eq('a') + interpret(<<-CRYSTAL).should eq('a') def foo yield return 'a' @@ -314,11 +314,11 @@ describe Crystal::Repl::Interpreter do else 'b' end - CODE + CRYSTAL end it "interprets block with args that conflict with a local var" do - interpret(<<-CODE).should eq(201) + interpret(<<-CRYSTAL).should eq(201) def foo yield 1 end @@ -331,11 +331,11 @@ describe Crystal::Repl::Interpreter do end x + a - CODE + CRYSTAL end it "interprets block with args that conflict with a local var" do - interpret(<<-CODE).should eq(216) + interpret(<<-CRYSTAL).should eq(216) def foo yield 1 end @@ -375,11 +375,11 @@ describe Crystal::Repl::Interpreter do x += a end x + a - CODE + CRYSTAL end it "clears block local variables when calling block" do - interpret(<<-CODE).should eq(20) + interpret(<<-CRYSTAL).should eq(20) def foo yield 1 end @@ -406,11 +406,11 @@ describe Crystal::Repl::Interpreter do else z end - CODE + CRYSTAL end it "clears block local variables when calling block (2)" do - interpret(<<-CODE).should eq(20) + interpret(<<-CRYSTAL).should eq(20) def foo yield end @@ -433,11 +433,11 @@ describe Crystal::Repl::Interpreter do else 20 end - CODE + CRYSTAL end it "captures non-closure block" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def capture(&block : Int32 -> Int32) block end @@ -447,11 +447,11 @@ describe Crystal::Repl::Interpreter do a = 100 b = capture { |x| x + 1 } b.call(41) - CODE + CRYSTAL end it "casts yield expression to block var type (not block arg type)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def foo yield 42 end @@ -466,11 +466,11 @@ describe Crystal::Repl::Interpreter do a = 0 bar { |z| a = z } a - CODE + CRYSTAL end it "interprets with ... yield" do - interpret(<<-CODE).should eq(31) + interpret(<<-CRYSTAL).should eq(31) struct Int32 def plus(x : Int32) self + x @@ -484,11 +484,11 @@ describe Crystal::Repl::Interpreter do foo do |x| 1 + (plus x) end - CODE + CRYSTAL end it "interprets with ... yield with struct" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) struct Foo def initialize @x = 1 @@ -511,11 +511,11 @@ describe Crystal::Repl::Interpreter do inc x end - CODE + CRYSTAL end it "interprets with ... yield with extra arguments (#12296)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) class Object def itself self @@ -529,11 +529,11 @@ describe Crystal::Repl::Interpreter do build do |t| itself end - CODE + CRYSTAL end it "counts with ... yield scope in block args bytesize (#12316)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) class Object def itself self @@ -551,11 +551,11 @@ describe Crystal::Repl::Interpreter do foo do |x| itself &- x end - CODE + CRYSTAL end it "interprets yield with splat (1)" do - interpret(<<-CODE).should eq((2 - 3) * 4) + interpret(<<-CRYSTAL).should eq((2 - 3) * 4) def foo t = {2, 3, 4} yield *t @@ -566,11 +566,11 @@ describe Crystal::Repl::Interpreter do a = (x1 - x2) * x3 end a - CODE + CRYSTAL end it "interprets yield with splat (2)" do - interpret(<<-CODE).should eq((((1 - 2) * 3) - 4) * 5) + interpret(<<-CRYSTAL).should eq((((1 - 2) * 3) - 4) * 5) def foo t = {2, 3, 4} yield 1, *t, 5 @@ -581,11 +581,11 @@ describe Crystal::Repl::Interpreter do a = (((x1 - x2) * x3) - x4) * x5 end a - CODE + CRYSTAL end it "interprets yield with splat, less block arguments" do - interpret(<<-CODE).should eq(2 - 3) + interpret(<<-CRYSTAL).should eq(2 - 3) def foo t = {2, 3, 4} yield *t @@ -596,11 +596,11 @@ describe Crystal::Repl::Interpreter do a = x1 - x2 end a - CODE + CRYSTAL end it "interprets block with splat" do - interpret(<<-CODE).should eq((((1 - 2) * 3) - 4) * 5) + interpret(<<-CRYSTAL).should eq((((1 - 2) * 3) - 4) * 5) def foo yield 1, 2, 3, 4, 5 end @@ -610,11 +610,11 @@ describe Crystal::Repl::Interpreter do a = (((x1 - x[0]) * x[1]) - x[2]) * x5 end a - CODE + CRYSTAL end it "interprets yield with splat, block with splat" do - interpret(<<-CODE).should eq((((1 - 2) * 3) - 4) * 5) + interpret(<<-CRYSTAL).should eq((((1 - 2) * 3) - 4) * 5) def foo t = {1, 2, 3} yield *t, 4, 5 @@ -625,11 +625,11 @@ describe Crystal::Repl::Interpreter do a = (((x1 - x[0]) * x[1]) - x[2]) * x5 end a - CODE + CRYSTAL end it "interprets yield with splat, block with splat (#12227)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) def foo yield *{ {3, 2} } end @@ -637,11 +637,11 @@ describe Crystal::Repl::Interpreter do foo do |x, y| x &- y end - CODE + CRYSTAL end it "considers block arg without type as having NoReturn type (#12270)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def bar if ptr = nil yield ptr @@ -657,11 +657,11 @@ describe Crystal::Repl::Interpreter do end foo - CODE + CRYSTAL end it "considers block arg without type as having NoReturn type (2) (#12270)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def bar if ptr = nil yield ptr @@ -677,11 +677,11 @@ describe Crystal::Repl::Interpreter do end foo - CODE + CRYSTAL end it "caches method with captured block (#12276)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def execute(x, &block : -> Int32) if x execute(false) do @@ -695,7 +695,7 @@ describe Crystal::Repl::Interpreter do execute(true) do 42 end - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/bugs_spec.cr b/spec/compiler/interpreter/bugs_spec.cr index 6e08de990162..1e51d04a1f98 100644 --- a/spec/compiler/interpreter/bugs_spec.cr +++ b/spec/compiler/interpreter/bugs_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "bugs" do it "doesn't pass self to top-level method" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) struct Int32 def foo(x) self @@ -22,11 +22,11 @@ describe Crystal::Repl::Interpreter do end Moo.moo - CODE + CRYSTAL end it "doesn't pass self to top-level method (FileNode)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) enum Color Red Green @@ -50,11 +50,11 @@ describe Crystal::Repl::Interpreter do other = 2 e = Color::Green.should eq(t :green) e.value - CODE + CRYSTAL end it "breaks from current block, not from outer block" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) def twice # index: 1, block_caller: 0 @@ -92,21 +92,21 @@ describe Crystal::Repl::Interpreter do end x - CODE + CRYSTAL end it "doesn't incorrectly consider a non-closure as closure" do - interpret(<<-CODE, prelude: "prelude").should eq("false") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("false") c = 0 ->{ c ->{}.closure? }.call - CODE + CRYSTAL end it "doesn't override local variable value with block var with the same name" do - interpret(<<-CODE).should eq(0) + interpret(<<-CRYSTAL).should eq(0) def block yield 1 end @@ -126,17 +126,17 @@ describe Crystal::Repl::Interpreter do end foo - CODE + CRYSTAL end it "does leading zeros" do - interpret(<<-CODE, prelude: "prelude").should eq("8") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("8") 0_i8.leading_zeros_count - CODE + CRYSTAL end it "does multidispatch on virtual struct" do - interpret(<<-CODE).should eq(true) + interpret(<<-CRYSTAL).should eq(true) abstract struct Base end @@ -160,11 +160,11 @@ describe Crystal::Repl::Interpreter do address = Foo.new.as(Base) address.foo - CODE + CRYSTAL end it "correctly puts virtual metaclass type in union" do - interpret(<<-CODE).should eq("Bar") + interpret(<<-CRYSTAL).should eq("Bar") abstract struct Foo end @@ -183,11 +183,11 @@ describe Crystal::Repl::Interpreter do foo = Bar.new.as(Foo) foo2 = foo || nil foo2.class.name - CODE + CRYSTAL end it "does multidispatch on virtual struct union nil" do - interpret(<<-CODE).should eq(true) + interpret(<<-CRYSTAL).should eq(true) abstract struct Foo @value = 1 end @@ -208,7 +208,7 @@ describe Crystal::Repl::Interpreter do foo = Bar.new.as(Foo) bar = (foo || nil).itself bar.is_a?(Bar) - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/calls_spec.cr b/spec/compiler/interpreter/calls_spec.cr index 2f246f610d34..43f027341ff1 100644 --- a/spec/compiler/interpreter/calls_spec.cr +++ b/spec/compiler/interpreter/calls_spec.cr @@ -4,17 +4,17 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "calls" do it "calls a top-level method without arguments and no local vars" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) def foo 1 + 2 end foo - CODE + CRYSTAL end it "calls a top-level method without arguments but with local vars" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) def foo x = 1 y = 2 @@ -23,42 +23,42 @@ describe Crystal::Repl::Interpreter do x = foo x - CODE + CRYSTAL end it "calls a top-level method with two arguments" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) def foo(x, y) x + y end x = foo(1, 2) x - CODE + CRYSTAL end it "interprets call with default values" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) def foo(x = 1, y = 2) x + y end foo - CODE + CRYSTAL end it "interprets call with named arguments" do - interpret(<<-CODE).should eq(-15) + interpret(<<-CRYSTAL).should eq(-15) def foo(x, y) x - y end foo(y: 25, x: 10) - CODE + CRYSTAL end it "interprets self for primitive types" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Int32 def foo self @@ -66,11 +66,11 @@ describe Crystal::Repl::Interpreter do end 42.foo - CODE + CRYSTAL end it "interprets explicit self call for primitive types" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Int32 def foo self.bar @@ -82,11 +82,11 @@ describe Crystal::Repl::Interpreter do end 42.foo - CODE + CRYSTAL end it "interprets implicit self call for pointer" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) struct Pointer(T) def plus1 self + 1_i64 @@ -96,21 +96,21 @@ describe Crystal::Repl::Interpreter do ptr = Pointer(UInt8).malloc(1_u64) ptr2 = ptr.plus1 (ptr2 - ptr) - CODE + CRYSTAL end it "interprets call with if" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) def foo 1 == 1 ? 2 : 3 end foo - CODE + CRYSTAL end it "does call with struct as obj" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) struct Foo def initialize(@x : Int64) end @@ -129,11 +129,11 @@ describe Crystal::Repl::Interpreter do end foo.x - CODE + CRYSTAL end it "does call with struct as obj (2)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) struct Foo def two 2 @@ -141,11 +141,11 @@ describe Crystal::Repl::Interpreter do end Foo.new.two - CODE + CRYSTAL end it "does call on instance var that's a struct, from a class" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) class Foo def initialize @x = 0_i64 @@ -174,11 +174,11 @@ describe Crystal::Repl::Interpreter do end Foo.new.foo - CODE + CRYSTAL end it "does call on instance var that's a struct, from a struct" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize @x = 0_i64 @@ -207,11 +207,11 @@ describe Crystal::Repl::Interpreter do end Foo.new.foo - CODE + CRYSTAL end it "discards call with struct as obj" do - interpret(<<-CODE).should eq(4) + interpret(<<-CRYSTAL).should eq(4) struct Foo def initialize(@x : Int64) end @@ -231,11 +231,11 @@ describe Crystal::Repl::Interpreter do foo.x 4 - CODE + CRYSTAL end it "does call on constant that's a struct, takes a pointer to instance var" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize @x = 42 @@ -252,11 +252,11 @@ describe Crystal::Repl::Interpreter do CONST = Foo.new CONST.to_unsafe.value - CODE + CRYSTAL end it "does call on constant that's a struct, takes a pointer to instance var, inside if" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize @x = 42 @@ -274,11 +274,11 @@ describe Crystal::Repl::Interpreter do CONST = Foo.new c = (1 == 1 ? CONST : CONST).to_unsafe c.value - CODE + CRYSTAL end it "does call on var that's a struct, takes a pointer to instance var, inside if" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize @x = 42 @@ -296,11 +296,11 @@ describe Crystal::Repl::Interpreter do a = Foo.new c = (1 == 1 ? a : a).to_unsafe c.value - CODE + CRYSTAL end it "does call on ivar that's a struct, takes a pointer to instance var, inside if" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize @x = 42 @@ -327,11 +327,11 @@ describe Crystal::Repl::Interpreter do end Bar.new.do_it - CODE + CRYSTAL end it "does call on self that's a struct, takes a pointer to instance var, inside if" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize @x = 42 @@ -352,11 +352,11 @@ describe Crystal::Repl::Interpreter do end Foo.new.do_it - CODE + CRYSTAL end it "does call on Pointer#value that's a struct, takes a pointer to instance var" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize @x = 42 @@ -375,11 +375,11 @@ describe Crystal::Repl::Interpreter do ptr = pointerof(foo) c = ptr.value.to_unsafe c.value - CODE + CRYSTAL end it "does call on read instance var that's a struct, takes a pointer to instance var" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize @x = 42 @@ -403,11 +403,11 @@ describe Crystal::Repl::Interpreter do bar = Bar.new(foo) c = bar.@foo.to_unsafe c.value - CODE + CRYSTAL end it "does ReadInstanceVar with wants_struct_pointer" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize @x = 1 @@ -432,11 +432,11 @@ describe Crystal::Repl::Interpreter do entry.value = Foo.new ptr = entry.value.@bar.to_unsafe ptr.value - CODE + CRYSTAL end it "does Assign var with wants_struct_pointer" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Bar def initialize @x = 1 @@ -452,11 +452,11 @@ describe Crystal::Repl::Interpreter do bar = Bar.new ptr = (x = bar).to_unsafe ptr.value - CODE + CRYSTAL end it "does Assign instance var with wants_struct_pointer" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Bar def initialize @x = 1 @@ -480,11 +480,11 @@ describe Crystal::Repl::Interpreter do end Foo.new.foo - CODE + CRYSTAL end it "does Assign class var with wants_struct_pointer" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Bar def initialize @x = 1 @@ -508,11 +508,11 @@ describe Crystal::Repl::Interpreter do end Foo.new.foo - CODE + CRYSTAL end it "inlines method that just reads an instance var" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize @x = 1 @@ -541,11 +541,11 @@ describe Crystal::Repl::Interpreter do entry.value = Foo.new ptr = entry.value.bar.to_unsafe ptr.value - CODE + CRYSTAL end it "inlines method that just reads an instance var, but produces side effects of args" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize @x = 1 @@ -575,11 +575,11 @@ describe Crystal::Repl::Interpreter do a = 1 ptr = entry.value.bar(a = 10).to_unsafe ptr.value + a - CODE + CRYSTAL end it "inlines method that just reads an instance var (2)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) abstract class Abstract end @@ -594,11 +594,11 @@ describe Crystal::Repl::Interpreter do original = Concrete.new(2).as(Abstract) original.x - CODE + CRYSTAL end it "puts struct pointer after tuple indexer" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) struct Point def initialize(@x : Int64) end @@ -611,11 +611,11 @@ describe Crystal::Repl::Interpreter do a = Point.new(1_u64) t = {a} t[0].x - CODE + CRYSTAL end it "mutates call argument" do - interpret(<<-CODE).should eq(9000) + interpret(<<-CRYSTAL).should eq(9000) def foo(x) if 1 == 0 x = "hello" @@ -629,11 +629,11 @@ describe Crystal::Repl::Interpreter do end foo 9000 - CODE + CRYSTAL end it "inlines call that returns self" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize @x = 0 @@ -659,11 +659,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.new foo.mutate_itself foo.x - CODE + CRYSTAL end it "inlines call that returns self (2)" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize @x = 0 @@ -689,11 +689,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.new foo.mutate_itself foo.x - CODE + CRYSTAL end it "mutates through pointer (1)" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize @x = 0 @@ -718,11 +718,11 @@ describe Crystal::Repl::Interpreter do end foo.mutate.ptr.value - CODE + CRYSTAL end it "mutates through pointer (2)" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize @x = 0 @@ -748,11 +748,11 @@ describe Crystal::Repl::Interpreter do x = foo.mutate.ptr x.value - CODE + CRYSTAL end it "mutates through pointer (3)" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize @x = 0 @@ -771,11 +771,11 @@ describe Crystal::Repl::Interpreter do ptr.value = Foo.new ptr.value.mutate ptr.value.x - CODE + CRYSTAL end it "mutates through read instance var" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize @bar = Bar.new @@ -802,11 +802,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.new foo.@bar.z = 10 foo.bar.z - CODE + CRYSTAL end it "mutates through inlined instance var with receiver" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize @bar = Bar.new @@ -833,11 +833,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.new foo.bar.z = 10 foo.bar.z - CODE + CRYSTAL end it "mutates through inlined instance var without receiver" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize @bar = Bar.new @@ -868,7 +868,7 @@ describe Crystal::Repl::Interpreter do foo = Foo.new foo.mutate foo.bar.z - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/casts_spec.cr b/spec/compiler/interpreter/casts_spec.cr index f8c59faca2d6..ac28197ce534 100644 --- a/spec/compiler/interpreter/casts_spec.cr +++ b/spec/compiler/interpreter/casts_spec.cr @@ -4,16 +4,16 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "casts" do it "casts from reference to pointer and back" do - interpret(<<-CODE).should eq("hello") + interpret(<<-CRYSTAL).should eq("hello") x = "hello" p = x.as(UInt8*) y = p.as(String) y - CODE + CRYSTAL end it "casts from reference to nilable reference" do - interpret(<<-CODE).should eq("hello") + interpret(<<-CRYSTAL).should eq("hello") x = "hello" y = x.as(String | Nil) if y @@ -21,22 +21,22 @@ describe Crystal::Repl::Interpreter do else "bye" end - CODE + CRYSTAL end it "casts from mixed union type to another mixed union type for caller" do - interpret(<<-CODE).should eq(true) + interpret(<<-CRYSTAL).should eq(true) a = 1 == 1 ? 1 : (1 == 1 ? 20_i16 : nil) if a a < 2 else false end - CODE + CRYSTAL end it "casts from nilable type to mixed union type" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) ascii = true delimiter = 1 == 1 ? nil : "foo" @@ -45,43 +45,43 @@ describe Crystal::Repl::Interpreter do else 2 end - CODE + CRYSTAL end it "casts from nilable type to mixed union type (2)" do - interpret(<<-CODE).should eq(true) + interpret(<<-CRYSTAL).should eq(true) y = 1 == 1 ? "a" : nil x = true x = y x.is_a?(String) - CODE + CRYSTAL end it "casts from mixed union type to primitive type" do - interpret(<<-CODE, prelude: "prelude").should eq("2") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("2") x = 1 == 1 ? 2 : nil x.as(Int32) - CODE + CRYSTAL end it "casts nilable from mixed union type to primitive type (non-nil case)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) x = 1 == 1 ? 2 : nil y = x.as?(Int32) y ? y : 20 - CODE + CRYSTAL end it "casts nilable from mixed union type to primitive type (nil case)" do - interpret(<<-CODE).should eq(20) + interpret(<<-CRYSTAL).should eq(20) x = 1 == 1 ? nil : 2 y = x.as?(Int32) y ? y : 20 - CODE + CRYSTAL end it "upcasts between tuple types" do - interpret(<<-CODE, prelude: "prelude").should eq((1 + 'a'.ord).to_s) + interpret(<<-CRYSTAL, prelude: "prelude").should eq((1 + 'a'.ord).to_s) a = if 1 == 1 {1, 'a'} @@ -90,11 +90,11 @@ describe Crystal::Repl::Interpreter do end a[0].as(Int32) + a[1].as(Char).ord - CODE + CRYSTAL end it "upcasts between named tuple types, same order" do - interpret(<<-CODE, prelude: "prelude").should eq((1 + 'a'.ord).to_s) + interpret(<<-CRYSTAL, prelude: "prelude").should eq((1 + 'a'.ord).to_s) a = if 1 == 1 {a: 1, b: 'a'} @@ -103,11 +103,11 @@ describe Crystal::Repl::Interpreter do end a[:a].as(Int32) + a[:b].as(Char).ord - CODE + CRYSTAL end it "upcasts between named tuple types, different order" do - interpret(<<-CODE, prelude: "prelude").should eq((1 + 'a'.ord).to_s) + interpret(<<-CRYSTAL, prelude: "prelude").should eq((1 + 'a'.ord).to_s) a = if 1 == 1 {a: 1, b: 'a'} @@ -116,11 +116,11 @@ describe Crystal::Repl::Interpreter do end a[:a].as(Int32) + a[:b].as(Char).ord - CODE + CRYSTAL end it "upcasts to module type" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) module Moo end @@ -146,11 +146,11 @@ describe Crystal::Repl::Interpreter do else 10 end - CODE + CRYSTAL end it "upcasts virtual type to union" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Foo def foo 1 @@ -170,11 +170,11 @@ describe Crystal::Repl::Interpreter do else 20 end - CODE + CRYSTAL end it "casts nil to Void*" do - interpret(<<-CODE).should eq(0) + interpret(<<-CRYSTAL).should eq(0) module Moo def self.moo(r) r.as(Void*) @@ -182,11 +182,11 @@ describe Crystal::Repl::Interpreter do end Moo.moo(nil).address - CODE + CRYSTAL end it "does is_a? with virtual metaclass" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) class A def self.a 2 @@ -210,19 +210,19 @@ describe Crystal::Repl::Interpreter do else 0 end - CODE + CRYSTAL end it "discards cast" do - interpret(<<-CODE, prelude: "prelude").should eq("10") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("10") x = 1 || 'a' x.as(Int32) 10 - CODE + CRYSTAL end it "raises when as fails" do - interpret(<<-CODE, prelude: "prelude").to_s.should contain("cast from Int32 to Char failed") + interpret(<<-CRYSTAL, prelude: "prelude").to_s.should contain("cast from Int32 to Char failed") x = 1 || 'a' begin x.as(Char) @@ -230,17 +230,17 @@ describe Crystal::Repl::Interpreter do rescue ex : TypeCastError ex.message.not_nil! end - CODE + CRYSTAL end it "casts to filtered type, not type in as(...)" do - interpret(<<-CODE, prelude: "prelude").should eq("1") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("1") ({1} || 2).as(Tuple)[0] - CODE + CRYSTAL end it "does is_a? with virtual type (struct)" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) abstract struct Foo end @@ -271,11 +271,11 @@ describe Crystal::Repl::Interpreter do else 0 end - CODE + CRYSTAL end it "puts virtual metaclass into union (#12162)" do - interpret(<<-CODE, prelude: "prelude").should eq(%("ActionA")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("ActionA")) class Action end @@ -288,11 +288,11 @@ describe Crystal::Repl::Interpreter do x = ActionA || ActionB y = x || Nil y.to_s - CODE + CRYSTAL end it "puts tuple type inside union of different tuple type (#12243)" do - interpret(<<-CODE, prelude: "prelude").should eq(%("{180}")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("{180}")) class A def initialize(@x : {Char | Int32}?) end @@ -304,11 +304,11 @@ describe Crystal::Repl::Interpreter do x = A.new({180}).x x.to_s - CODE + CRYSTAL end it "puts named tuple type inside union of different named tuple type (#12243)" do - interpret(<<-CODE, prelude: "prelude").should eq(%("{v: 180}")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("{v: 180}")) class A def initialize(@x : {v: Char | Int32}?) end @@ -320,11 +320,11 @@ describe Crystal::Repl::Interpreter do x = A.new({v: 180}).x x.to_s - CODE + CRYSTAL end it "casts from mixed union type to nilable proc type (#12283)" do - interpret(<<-CODE).should eq("b") + interpret(<<-CRYSTAL).should eq("b") message = ->{ "b" }.as(String | Proc(String) | Nil) if message.is_a?(String) "a" @@ -333,31 +333,31 @@ describe Crystal::Repl::Interpreter do else "c" end - CODE + CRYSTAL end it "does as? with no resulting type (#12327)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) if nil.as?(Int32) 0 else 42 end - CODE + CRYSTAL end it "does as? with no resulting type, not from nil (#12327)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) if 1.as?(String) 0 else 42 end - CODE + CRYSTAL end it "does as? with a type that can't match (#12346)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) abstract class A end @@ -369,11 +369,11 @@ describe Crystal::Repl::Interpreter do a = B.new || C.new a.as?(B | Int32) ? 1 : 2 - CODE + CRYSTAL end it "upcasts mixed union with tuple to mixed union with compatible tuple (1) (#12331)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) class Foo def initialize(@tuple : Tuple(Int32?) | Tuple(Int32, Int32)) end @@ -396,11 +396,11 @@ describe Crystal::Repl::Interpreter do else 3 end - CODE + CRYSTAL end it "upcasts mixed union with tuple to mixed union with compatible tuple (2) (#12331)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Foo def initialize(@tuple : Tuple(Int32?) | Tuple(Int32, Int32)) end @@ -423,11 +423,11 @@ describe Crystal::Repl::Interpreter do else 3 end - CODE + CRYSTAL end it "upcasts mixed union with tuple to mixed union with compatible tuple (3) (#12331)" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) class Foo def initialize(@tuple : Tuple(Int32?) | Tuple(Int32, Int32)) end @@ -450,11 +450,11 @@ describe Crystal::Repl::Interpreter do else 3 end - CODE + CRYSTAL end it "upcasts in nilable cast (#12532)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) struct Nil def foo 0 @@ -480,11 +480,11 @@ describe Crystal::Repl::Interpreter do end B.new.as?(A).foo - CODE + CRYSTAL end it "upcasts GenericClassInstanceMetaclassType to VirtualMetaclassType" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Foo def self.foo; 1; end end @@ -494,7 +494,7 @@ describe Crystal::Repl::Interpreter do end Gen(Int32).as(Foo.class).foo - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/class_vars_spec.cr b/spec/compiler/interpreter/class_vars_spec.cr index a70529d2325d..ff15ab345b21 100644 --- a/spec/compiler/interpreter/class_vars_spec.cr +++ b/spec/compiler/interpreter/class_vars_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "class vars" do it "interprets class var without initializer" do - interpret(<<-CODE).should eq(41) + interpret(<<-CRYSTAL).should eq(41) class Foo @@x : Int32? @@ -30,11 +30,11 @@ describe Crystal::Repl::Interpreter do a += x if x a - CODE + CRYSTAL end it "interprets class var with initializer" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) class Foo @@x = 10 @@ -60,11 +60,11 @@ describe Crystal::Repl::Interpreter do a += x if x a - CODE + CRYSTAL end it "interprets class var for virtual type" do - interpret(<<-CODE).should eq(30) + interpret(<<-CRYSTAL).should eq(30) class Foo @@x = 1 @@ -92,11 +92,11 @@ describe Crystal::Repl::Interpreter do a += foobar.get a += barfoo.get a - CODE + CRYSTAL end it "interprets class var for virtual metaclass type" do - interpret(<<-CODE).should eq(30) + interpret(<<-CRYSTAL).should eq(30) class Foo @@x = 1 @@ -124,11 +124,11 @@ describe Crystal::Repl::Interpreter do a += foobar.get a += barfoo.get a - CODE + CRYSTAL end it "finds self in class var initializer (#12439)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) class Foo @@value : Int32 = self.int @@ -142,11 +142,11 @@ describe Crystal::Repl::Interpreter do end Foo.value - CODE + CRYSTAL end it "does class var initializer with union (#12633)" do - interpret(<<-CODE).should eq("hello") + interpret(<<-CRYSTAL).should eq("hello") class MyClass @@a : String | Int32 = "hello" @@ -162,11 +162,11 @@ describe Crystal::Repl::Interpreter do in Int32 "bye" end - CODE + CRYSTAL end it "reads class var initializer with union (#12633)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class MyClass @@a : Char | Int32 = 1 @@ -183,7 +183,7 @@ describe Crystal::Repl::Interpreter do end MyClass.foo(2) - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/classes_spec.cr b/spec/compiler/interpreter/classes_spec.cr index ea54f0b98fb2..6a5ce23ae22b 100644 --- a/spec/compiler/interpreter/classes_spec.cr +++ b/spec/compiler/interpreter/classes_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "classes" do it "does allocate, set instance var and get instance var" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) class Foo @x = 0 @@ -19,11 +19,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.allocate foo.x = 42 foo.x - CODE + CRYSTAL end it "does constructor" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) class Foo def initialize(@x : Int32) end @@ -35,7 +35,7 @@ describe Crystal::Repl::Interpreter do foo = Foo.new(42) foo.x - CODE + CRYSTAL end it "interprets read instance var" do @@ -43,17 +43,17 @@ describe Crystal::Repl::Interpreter do end it "discards allocate" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) class Foo end Foo.allocate 3 - CODE + CRYSTAL end it "calls implicit class self method" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) class Foo def initialize @x = 10 @@ -70,11 +70,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.new foo.foo - CODE + CRYSTAL end it "calls explicit struct self method" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize @x = 10 @@ -91,11 +91,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.new foo.foo - CODE + CRYSTAL end it "calls implicit struct self method" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize @x = 10 @@ -112,11 +112,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.new foo.foo - CODE + CRYSTAL end it "does object_id" do - interpret(<<-CODE).should be_true + interpret(<<-CRYSTAL).should be_true class Foo end @@ -124,12 +124,12 @@ describe Crystal::Repl::Interpreter do object_id = foo.object_id address = foo.as(Void*).address object_id == address - CODE + CRYSTAL end end it "inlines instance var access from virtual type with a single type (#39520)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) struct Int32 def foo 1 @@ -157,11 +157,11 @@ describe Crystal::Repl::Interpreter do expression = ValueExpression.new.as(Expression) expression.value.foo - CODE + CRYSTAL end it "downcasts virtual type to its only type (#12351)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) abstract class A end @@ -177,6 +177,6 @@ describe Crystal::Repl::Interpreter do b = B.new.as(A) foo(b) - CODE + CRYSTAL end end diff --git a/spec/compiler/interpreter/closures_spec.cr b/spec/compiler/interpreter/closures_spec.cr index 5487ea21b1e3..d0c51359f122 100644 --- a/spec/compiler/interpreter/closures_spec.cr +++ b/spec/compiler/interpreter/closures_spec.cr @@ -4,16 +4,16 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "closures" do it "does closure without args that captures and modifies one local variable" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) a = 0 proc = -> { a = 42 } proc.call a - CODE + CRYSTAL end it "does closure without args that captures and modifies two local variables" do - interpret(<<-CODE).should eq(7) + interpret(<<-CRYSTAL).should eq(7) a = 0 b = 0 proc = ->{ @@ -22,11 +22,11 @@ describe Crystal::Repl::Interpreter do } proc.call a - b - CODE + CRYSTAL end it "does closure with two args that captures and modifies two local variables" do - interpret(<<-CODE).should eq(7) + interpret(<<-CRYSTAL).should eq(7) a = 0 b = 0 proc = ->(x : Int32, y : Int32) { @@ -35,11 +35,11 @@ describe Crystal::Repl::Interpreter do } proc.call(10, 3) a - b - CODE + CRYSTAL end it "does closure and accesses it inside block" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def foo yield end @@ -53,11 +53,11 @@ describe Crystal::Repl::Interpreter do end x - CODE + CRYSTAL end it "does closure inside def" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def foo a = 0 proc = -> { a = 42 } @@ -66,11 +66,11 @@ describe Crystal::Repl::Interpreter do end foo - CODE + CRYSTAL end it "closures def arguments" do - interpret(<<-CODE).should eq((41 + 1) - (10 + 2)) + interpret(<<-CRYSTAL).should eq((41 + 1) - (10 + 2)) def foo(a, b) proc = -> { a += 1; b += 2 } proc.call @@ -78,11 +78,11 @@ describe Crystal::Repl::Interpreter do end foo(41, 10) - CODE + CRYSTAL end it "does closure inside proc" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) proc = ->{ a = 0 proc2 = -> { a = 42 } @@ -91,11 +91,11 @@ describe Crystal::Repl::Interpreter do } proc.call - CODE + CRYSTAL end it "does closure inside proc, capture proc argument" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) proc = ->(a : Int32) { proc2 = -> { a += 1 } proc2.call @@ -103,11 +103,11 @@ describe Crystal::Repl::Interpreter do } proc.call(41) - CODE + CRYSTAL end it "does closure inside const" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) FOO = begin a = 0 @@ -117,11 +117,11 @@ describe Crystal::Repl::Interpreter do end FOO - CODE + CRYSTAL end it "does closure inside class variable initializer" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) class Foo @@foo : Int32 = begin @@ -137,11 +137,11 @@ describe Crystal::Repl::Interpreter do end Foo.foo - CODE + CRYSTAL end it "does closure inside block" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def foo yield end @@ -152,11 +152,11 @@ describe Crystal::Repl::Interpreter do proc.call a end - CODE + CRYSTAL end it "does closure inside block, capture block arg" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def foo yield 21 end @@ -166,11 +166,11 @@ describe Crystal::Repl::Interpreter do proc.call a end - CODE + CRYSTAL end it "does nested closure inside proc" do - interpret(<<-CODE).should eq(21) + interpret(<<-CRYSTAL).should eq(21) a = 0 proc1 = ->{ @@ -191,11 +191,11 @@ describe Crystal::Repl::Interpreter do y = a y - x - CODE + CRYSTAL end it "does nested closure inside captured blocks" do - interpret(<<-CODE).should eq(21) + interpret(<<-CRYSTAL).should eq(21) def capture(&block : -> _) block end @@ -220,11 +220,11 @@ describe Crystal::Repl::Interpreter do y = a y - x - CODE + CRYSTAL end it "does nested closure inside methods and blocks" do - interpret(<<-CODE).should eq(12) + interpret(<<-CRYSTAL).should eq(12) def foo yield end @@ -241,11 +241,11 @@ describe Crystal::Repl::Interpreter do b end - CODE + CRYSTAL end it "does closure with pointerof local var" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) a = 0 proc = ->do ptr = pointerof(a) @@ -253,11 +253,11 @@ describe Crystal::Repl::Interpreter do end proc.call a - CODE + CRYSTAL end it "closures self in proc literal" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) class Foo def initialize @x = 1 @@ -281,11 +281,11 @@ describe Crystal::Repl::Interpreter do proc.call proc.call foo.x - CODE + CRYSTAL end it "closures self in proc literal (implicit self)" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) class Foo def initialize @x = 1 @@ -309,11 +309,11 @@ describe Crystal::Repl::Interpreter do proc.call proc.call foo.x - CODE + CRYSTAL end it "closures self and modifies instance var" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) class Foo def initialize @x = 1 @@ -333,11 +333,11 @@ describe Crystal::Repl::Interpreter do proc.call proc.call foo.x - CODE + CRYSTAL end it "closures struct and calls method on it" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) struct Foo def initialize @x = 1 @@ -356,11 +356,11 @@ describe Crystal::Repl::Interpreter do proc = ->{ foo.inc } proc.call foo.x - CODE + CRYSTAL end it "doesn't mix local vars with closured vars" do - interpret(<<-CODE).should eq(20) + interpret(<<-CRYSTAL).should eq(20) def foo(x) yield x end @@ -373,11 +373,11 @@ describe Crystal::Repl::Interpreter do end }.call end - CODE + CRYSTAL end it "closures closured block arg" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) def foo(&block : -> Int32) ->{ block.call }.call end @@ -385,11 +385,11 @@ describe Crystal::Repl::Interpreter do foo do 1 end - CODE + CRYSTAL end it "closures block args after 8 bytes (the closure var)" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) def foo yield({1, 2, 3}) end @@ -397,11 +397,11 @@ describe Crystal::Repl::Interpreter do foo do |x, y, z| ->{ x + y + z }.call end - CODE + CRYSTAL end it "passes closured struct instance var as self" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Bar def bar 10 @@ -419,11 +419,11 @@ describe Crystal::Repl::Interpreter do end Foo.new.foo - CODE + CRYSTAL end it "does next inside captured block (#12226)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) def foo(&block : -> _) block.call end @@ -432,11 +432,11 @@ describe Crystal::Repl::Interpreter do next 1 2 end - CODE + CRYSTAL end it "gets ivar of self closured struct (#12341)" do - interpret(<<-CODE).should eq(860) + interpret(<<-CRYSTAL).should eq(860) def closure(&block : -> Int32) block end @@ -457,11 +457,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.new foo.foo - CODE + CRYSTAL end it "sets ivar of self closured struct (#12341)" do - interpret(<<-CODE).should eq(1) # Yes, not 2. A closured struct's value can't change. + interpret(<<-CRYSTAL).should eq(1) # Yes, not 2. A closured struct's value can't change. def closure(&block) block end @@ -483,11 +483,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.new foo.foo foo.count - CODE + CRYSTAL end it "reads self closured struct (#12341)" do - interpret(<<-CODE).should eq(860) + interpret(<<-CRYSTAL).should eq(860) def closure(&block : -> _) block end @@ -512,7 +512,7 @@ describe Crystal::Repl::Interpreter do foo = Foo.new bar = foo.foo bar.value - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/constants_spec.cr b/spec/compiler/interpreter/constants_spec.cr index aeed305f68fe..5758100d7aea 100644 --- a/spec/compiler/interpreter/constants_spec.cr +++ b/spec/compiler/interpreter/constants_spec.cr @@ -4,31 +4,31 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "constants" do it "returns nil in the assignment" do - interpret(<<-CODE).should eq(nil) + interpret(<<-CRYSTAL).should eq(nil) A = 123 - CODE + CRYSTAL end it "interprets constant literal" do - interpret(<<-CODE).should eq(123) + interpret(<<-CRYSTAL).should eq(123) A = 123 A - CODE + CRYSTAL end it "interprets complex constant" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) A = begin a = 1 b = 2 a + b end A + A - CODE + CRYSTAL end it "hoists constants" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) x = A + A A = begin @@ -38,11 +38,11 @@ describe Crystal::Repl::Interpreter do end x - CODE + CRYSTAL end it "interprets self inside constant inside class" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) class Foo X = self.foo @@ -56,19 +56,19 @@ describe Crystal::Repl::Interpreter do end Foo::X - CODE + CRYSTAL end end context "magic constants" do it "does line number" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) def foo(x, line = __LINE__) x + line end foo(1) - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/control_flow_spec.cr b/spec/compiler/interpreter/control_flow_spec.cr index b8c44ce61329..91c6128b09e5 100644 --- a/spec/compiler/interpreter/control_flow_spec.cr +++ b/spec/compiler/interpreter/control_flow_spec.cr @@ -60,47 +60,47 @@ describe Crystal::Repl::Interpreter do end it "interprets while" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) a = 0 while a < 10 a = a + 1 end a - CODE + CRYSTAL end it "interprets while, returns nil" do - interpret(<<-CODE).should eq(nil) + interpret(<<-CRYSTAL).should eq(nil) a = 0 while a < 10 a = a + 1 end - CODE + CRYSTAL end it "interprets until" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) a = 0 until a == 10 a = a + 1 end a - CODE + CRYSTAL end it "interprets break inside while" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) a = 0 while a < 10 a += 1 break if a == 3 end a - CODE + CRYSTAL end it "interprets break inside nested while" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) a = 0 b = 0 c = 0 @@ -118,11 +118,11 @@ describe Crystal::Repl::Interpreter do end c - CODE + CRYSTAL end it "interprets break inside while inside block" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) def foo yield 20 @@ -136,33 +136,33 @@ describe Crystal::Repl::Interpreter do end end a - CODE + CRYSTAL end it "interprets break with value inside while (through break)" do - interpret(<<-CODE).should eq(8) + interpret(<<-CRYSTAL).should eq(8) a = 0 x = while a < 10 a += 1 break 8 if a == 3 end x || 10 - CODE + CRYSTAL end it "interprets break with value inside while (through normal flow)" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) a = 0 x = while a < 10 a += 1 break 8 if a == 20 end x || 10 - CODE + CRYSTAL end it "interprets next inside while" do - interpret(<<-CODE).should eq(1 + 2 + 8 + 9 + 10) + interpret(<<-CRYSTAL).should eq(1 + 2 + 8 + 9 + 10) a = 0 x = 0 while a < 10 @@ -173,11 +173,11 @@ describe Crystal::Repl::Interpreter do x += a end x - CODE + CRYSTAL end it "interprets next inside while inside block" do - interpret(<<-CODE).should eq(1 + 2 + 8 + 9 + 10) + interpret(<<-CRYSTAL).should eq(1 + 2 + 8 + 9 + 10) def foo yield 10 @@ -195,7 +195,7 @@ describe Crystal::Repl::Interpreter do end end x - CODE + CRYSTAL end it "discards while" do @@ -203,7 +203,7 @@ describe Crystal::Repl::Interpreter do end it "interprets return" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) def foo(x) if x == 1 return 2 @@ -213,31 +213,31 @@ describe Crystal::Repl::Interpreter do end foo(1) - CODE + CRYSTAL end it "interprets return Nil" do - interpret(<<-CODE).should be_nil + interpret(<<-CRYSTAL).should be_nil def foo : Nil 1 end foo - CODE + CRYSTAL end it "interprets return Nil with explicit return (#12178)" do - interpret(<<-CODE).should be_nil + interpret(<<-CRYSTAL).should be_nil def foo : Nil return 1 end foo - CODE + CRYSTAL end it "interprets return implicit nil and Int32" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) def foo(x) if x == 1 return @@ -252,7 +252,7 @@ describe Crystal::Repl::Interpreter do else 10 end - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/enum_spec.cr b/spec/compiler/interpreter/enum_spec.cr index ac3bc1efa358..f8e5e3d0ae2d 100644 --- a/spec/compiler/interpreter/enum_spec.cr +++ b/spec/compiler/interpreter/enum_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "enum" do it "does enum value" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) enum Color Red Green @@ -12,11 +12,11 @@ describe Crystal::Repl::Interpreter do end Color::Blue.value - CODE + CRYSTAL end it "does enum new" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) enum Color Red Green @@ -25,7 +25,7 @@ describe Crystal::Repl::Interpreter do blue = Color.new(2) blue.value - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/exceptions_spec.cr b/spec/compiler/interpreter/exceptions_spec.cr index 9506325af1d6..f35cf54fdd6c 100644 --- a/spec/compiler/interpreter/exceptions_spec.cr +++ b/spec/compiler/interpreter/exceptions_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "exception handling" do it "does ensure without rescue/raise" do - interpret(<<-CODE).should eq(12) + interpret(<<-CRYSTAL).should eq(12) x = 1 y = begin @@ -13,11 +13,11 @@ describe Crystal::Repl::Interpreter do x = 2 end x + y - CODE + CRYSTAL end it "does rescue when nothing is raised" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) a = begin 1 rescue @@ -29,11 +29,11 @@ describe Crystal::Repl::Interpreter do else 10 end - CODE + CRYSTAL end it "raises and rescues anything" do - interpret(<<-CODE, prelude: "prelude").should eq("2") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("2") a = begin if 1 == 1 raise "OH NO" @@ -49,11 +49,11 @@ describe Crystal::Repl::Interpreter do else 10 end - CODE + CRYSTAL end it "raises and rescues anything, does ensure when an exception is rescued" do - interpret(<<-CODE, prelude: "prelude").should eq("3") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("3") a = 0 b = 0 @@ -66,11 +66,11 @@ describe Crystal::Repl::Interpreter do end a + b - CODE + CRYSTAL end it "raises and rescues specific exception type" do - interpret(<<-CODE, prelude: "prelude").should eq("2") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("2") class Ex1 < Exception; end class Ex2 < Exception; end @@ -85,11 +85,11 @@ describe Crystal::Repl::Interpreter do end a - CODE + CRYSTAL end it "captures exception in variable" do - interpret(<<-CODE, prelude: "prelude").should eq("10") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("10") class Ex1 < Exception getter value @@ -106,11 +106,11 @@ describe Crystal::Repl::Interpreter do end a - CODE + CRYSTAL end it "executes ensure when exception is raised in body" do - interpret(<<-CODE, prelude: "prelude").should eq("10") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("10") a = 0 begin @@ -123,11 +123,11 @@ describe Crystal::Repl::Interpreter do end a - CODE + CRYSTAL end it "executes ensure when exception is raised in rescue" do - interpret(<<-CODE, prelude: "prelude").should eq("10") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("10") a = 0 begin @@ -142,11 +142,11 @@ describe Crystal::Repl::Interpreter do end a - CODE + CRYSTAL end it "does else" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) a = begin 'a' @@ -157,11 +157,11 @@ describe Crystal::Repl::Interpreter do end a + 1 - CODE + CRYSTAL end it "does ensure for else" do - interpret(<<-CODE).should eq(2 + ((1 * 2) + 3)) + interpret(<<-CRYSTAL).should eq(2 + ((1 * 2) + 3)) x = 1 a = @@ -177,11 +177,11 @@ describe Crystal::Repl::Interpreter do end a + x - CODE + CRYSTAL end it "does ensure for else when else raises" do - interpret(<<-CODE, prelude: "prelude").should eq("2") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("2") x = 1 begin @@ -198,11 +198,11 @@ describe Crystal::Repl::Interpreter do end x - CODE + CRYSTAL end it "does ensure with explicit return" do - interpret(<<-CODE).should eq(22) + interpret(<<-CRYSTAL).should eq(22) module Global @@property = 0 @@ -235,11 +235,11 @@ describe Crystal::Repl::Interpreter do x = foo Global.property + x - CODE + CRYSTAL end it "executes ensure when returning from a block" do - interpret(<<-CODE).should eq(21) + interpret(<<-CRYSTAL).should eq(21) module Global @@property = 0 @@ -269,11 +269,11 @@ describe Crystal::Repl::Interpreter do x = foo Global.property + x - CODE + CRYSTAL end it "executes ensure when returning from a block (2)" do - interpret(<<-CODE).should eq(21) + interpret(<<-CRYSTAL).should eq(21) module Global @@property = 0 @@ -314,11 +314,11 @@ describe Crystal::Repl::Interpreter do x = foo Global.property + x - CODE + CRYSTAL end it "executes ensure when breaking from a block" do - interpret(<<-CODE).should eq(18) + interpret(<<-CRYSTAL).should eq(18) module Global @@property = 0 @@ -348,11 +348,11 @@ describe Crystal::Repl::Interpreter do x = foo Global.property + x - CODE + CRYSTAL end it "executes ensure when returning a big value from a block" do - interpret(<<-CODE, prelude: "prelude").should eq("32405") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("32405") module Global @@property = 0 @@ -387,7 +387,7 @@ describe Crystal::Repl::Interpreter do else 0 end - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/extern_spec.cr b/spec/compiler/interpreter/extern_spec.cr index b46b23ed3947..bbd860bd3cb2 100644 --- a/spec/compiler/interpreter/extern_spec.cr +++ b/spec/compiler/interpreter/extern_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "extern" do it "interprets primitive struct_or_union_set and get (struct)" do - interpret(<<-CODE).should eq(30) + interpret(<<-CRYSTAL).should eq(30) lib LibFoo struct Foo x : Int32 @@ -16,11 +16,11 @@ describe Crystal::Repl::Interpreter do foo.x = 10 foo.y = 20 foo.x + foo.y - CODE + CRYSTAL end it "discards primitive struct_or_union_set and get (struct)" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) lib LibFoo struct Foo x : Int32 @@ -30,11 +30,11 @@ describe Crystal::Repl::Interpreter do foo = LibFoo::Foo.new foo.y = 10 - CODE + CRYSTAL end it "discards primitive struct_or_union_set because it's a copy" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) lib LibFoo struct Foo x : Int32 @@ -47,11 +47,11 @@ describe Crystal::Repl::Interpreter do end copy.y = 10 - CODE + CRYSTAL end it "interprets primitive struct_or_union_set and get (union)" do - interpret(<<-CODE).should eq(-2045911175) + interpret(<<-CRYSTAL).should eq(-2045911175) lib LibFoo union Foo a : Bool @@ -63,11 +63,11 @@ describe Crystal::Repl::Interpreter do foo = LibFoo::Foo.new foo.x = 123456789012345 foo.y - CODE + CRYSTAL end it "sets extern struct proc field" do - interpret(<<-CODE).should eq(13) + interpret(<<-CRYSTAL).should eq(13) lib LibFoo struct Foo proc : Int32 -> Int32 @@ -79,11 +79,11 @@ describe Crystal::Repl::Interpreter do foo.field = 10 foo.proc = ->(x : Int32) { x + 1 } foo.proc.call(2) + foo.field - CODE + CRYSTAL end it "sets struct field through pointer" do - interpret(<<-CODE).should eq(20) + interpret(<<-CRYSTAL).should eq(20) lib LibFoo struct Foo x : Int32 @@ -94,11 +94,11 @@ describe Crystal::Repl::Interpreter do ptr = pointerof(foo) ptr.value.x = 20 foo.x - CODE + CRYSTAL end it "does automatic C cast" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) lib LibFoo struct Foo x : UInt8 @@ -108,7 +108,7 @@ describe Crystal::Repl::Interpreter do foo = LibFoo::Foo.new foo.x = 257 foo.x - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/integration_spec.cr b/spec/compiler/interpreter/integration_spec.cr index 6a2f1027197a..742a5740d22f 100644 --- a/spec/compiler/interpreter/integration_spec.cr +++ b/spec/compiler/interpreter/integration_spec.cr @@ -4,41 +4,41 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "integration" do it "does Int32#to_s" do - interpret(<<-CODE, prelude: "prelude").should eq(%("123456789")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("123456789")) 123456789.to_s - CODE + CRYSTAL end it "does Float64#to_s (simple)" do - interpret(<<-CODE, prelude: "prelude").should eq(%("1.5")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("1.5")) 1.5.to_s - CODE + CRYSTAL end it "does Float64#to_s (complex)" do - interpret(<<-CODE, prelude: "prelude").should eq(%("123456789.12345")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("123456789.12345")) 123456789.12345.to_s - CODE + CRYSTAL end it "does Range#to_a, Array#to_s" do - interpret(<<-CODE, prelude: "prelude").should eq(%("[1, 2, 3, 4, 5]")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("[1, 2, 3, 4, 5]")) (1..5).to_a.to_s - CODE + CRYSTAL end it "does some Hash methods" do - interpret(<<-CODE, prelude: "prelude").should eq("90") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("90") h = {} of Int32 => Int32 10.times do |i| h[i] = i * 2 end h.values.sum - CODE + CRYSTAL end it "does CSV" do - interpret(<<-CODE, prelude: "prelude").should eq((1..6).sum.to_s) + interpret(<<-CRYSTAL, prelude: "prelude").should eq((1..6).sum.to_s) require "csv" csv = CSV.new <<-CSV, headers: true @@ -54,22 +54,22 @@ describe Crystal::Repl::Interpreter do end end sum - CODE + CRYSTAL end it "does JSON" do - interpret(<<-CODE, prelude: "prelude").should eq("6") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("6") require "json" json = JSON.parse <<-JSON {"a": [1, 2, 3]} JSON json.as_h["a"].as_a.sum(&.as_i) - CODE + CRYSTAL end it "does JSON::Serializable" do - interpret(<<-CODE, prelude: "prelude").should eq("3") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("3") require "json" record Point, x : Int32, y : Int32 do @@ -80,11 +80,11 @@ describe Crystal::Repl::Interpreter do {"x": 1, "y": 2} JSON point.x + point.y - CODE + CRYSTAL end it "does YAML" do - interpret(<<-CODE, prelude: "prelude").should eq("6") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("6") require "yaml" yaml = YAML.parse <<-YAML @@ -94,11 +94,11 @@ describe Crystal::Repl::Interpreter do - 3 YAML yaml.as_h["a"].as_a.sum(&.as_i) - CODE + CRYSTAL end it "does YAML::Serializable" do - interpret(<<-CODE, prelude: "prelude").should eq("3") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("3") require "yaml" record Point, x : Int32, y : Int32 do @@ -110,11 +110,11 @@ describe Crystal::Repl::Interpreter do y: 2 YAML point.x + point.y - CODE + CRYSTAL end pending "does XML" do - interpret(<<-CODE, prelude: "prelude").should eq("3") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("3") require "xml" doc = XML.parse(<<-XML @@ -130,28 +130,28 @@ describe Crystal::Repl::Interpreter do id = attrs["id"].content.to_i id2 = attrs["id2"].content.to_i id + id2 - CODE + CRYSTAL end it "does String#includes?" do - interpret(<<-CODE, prelude: "prelude").should eq("true") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("true") a = "Negative array size: -1" b = "Negative array size" a.includes?(b) - CODE + CRYSTAL end it "does IO.pipe (checks that StaticArray is passed correctly to C calls)" do - interpret(<<-CODE, prelude: "prelude").should eq(%("hello")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("hello")) IO.pipe do |r, w| w.puts "hello" r.gets.not_nil! end - CODE + CRYSTAL end it "does caller" do - interpret(<<-CODE, prelude: "prelude").should eq(%(":6:5 in 'bar'")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%(":6:5 in 'bar'")) def foo bar end @@ -161,7 +161,7 @@ describe Crystal::Repl::Interpreter do end foo - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/is_a_spec.cr b/spec/compiler/interpreter/is_a_spec.cr index e1cf0e28196f..5138ae7cdb8c 100644 --- a/spec/compiler/interpreter/is_a_spec.cr +++ b/spec/compiler/interpreter/is_a_spec.cr @@ -4,18 +4,18 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "is_a?" do it "does is_a? from NilableType to NonGenericClassType (true)" do - interpret(<<-CODE).should eq("hello") + interpret(<<-CRYSTAL).should eq("hello") a = "hello" || nil if a.is_a?(String) a else "bar" end - CODE + CRYSTAL end it "does is_a? from NilableType to NonGenericClassType (false)" do - interpret(<<-CODE).should eq("bar") + interpret(<<-CRYSTAL).should eq("bar") a = 1 == 1 ? nil : "hello" if a.is_a?(String) a @@ -23,11 +23,11 @@ describe Crystal::Repl::Interpreter do z = a "bar" end - CODE + CRYSTAL end it "does is_a? from NilableType to GenericClassInstanceType (true)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) class Foo(T) def initialize(@x : T) end @@ -43,11 +43,11 @@ describe Crystal::Repl::Interpreter do else 2 end - CODE + CRYSTAL end it "does is_a? from NilableType to GenericClassInstanceType (false)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Foo(T) def initialize(@x : T) end @@ -64,11 +64,11 @@ describe Crystal::Repl::Interpreter do z = a 2 end - CODE + CRYSTAL end it "does is_a? from NilableReferenceUnionType to NonGenericClassType (true)" do - interpret(<<-CODE).should eq("hello") + interpret(<<-CRYSTAL).should eq("hello") class Foo end @@ -78,11 +78,11 @@ describe Crystal::Repl::Interpreter do else "bar" end - CODE + CRYSTAL end it "does is_a? from NilableReferenceUnionType to NonGenericClassType (false)" do - interpret(<<-CODE).should eq("baz") + interpret(<<-CRYSTAL).should eq("baz") class Foo end @@ -92,11 +92,11 @@ describe Crystal::Repl::Interpreter do else "baz" end - CODE + CRYSTAL end it "does is_a? from VirtualType to NonGenericClassType (true)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Foo def x 1 @@ -115,11 +115,11 @@ describe Crystal::Repl::Interpreter do else 20 end - CODE + CRYSTAL end it "does is_a? from VirtualType to NonGenericClassType (false)" do - interpret(<<-CODE).should eq(20) + interpret(<<-CRYSTAL).should eq(20) class Foo def x 1 @@ -138,29 +138,29 @@ describe Crystal::Repl::Interpreter do else 20 end - CODE + CRYSTAL end it "does is_a? from NilableProcType to Nil" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) proc = 1 == 1 ? nil : ->{ 1 } if proc.nil? 10 else 20 end - CODE + CRYSTAL end it "does is_a? from NilableProcType to non-Nil" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) proc = 1 == 2 ? nil : ->{ 10 } if proc.is_a?(Proc) proc.call else 20 end - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/lib_spec.cr b/spec/compiler/interpreter/lib_spec.cr index 7b99c1555e99..98e1da7fd294 100644 --- a/spec/compiler/interpreter/lib_spec.cr +++ b/spec/compiler/interpreter/lib_spec.cr @@ -10,29 +10,29 @@ describe Crystal::Repl::Interpreter do end it "promotes float" do - interpret(<<-CR).should eq 3.5 + interpret(<<-CRYSTAL).should eq 3.5 @[Link(ldflags: "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -lsum")] lib LibSum fun sum_float(count : Int32, ...) : Float32 end LibSum.sum_float(2, 1.2_f32, 2.3_f32) - CR + CRYSTAL end it "promotes int" do - interpret(<<-CR).should eq 5 + interpret(<<-CRYSTAL).should eq 5 @[Link(ldflags: "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -lsum")] lib LibSum fun sum_int(count : Int32, ...) : Int32 end LibSum.sum_int(2, 1_u8, 4_i16) - CR + CRYSTAL end it "promotes enum" do - interpret(<<-CR).should eq 5 + interpret(<<-CRYSTAL).should eq 5 @[Link(ldflags: "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -lsum")] lib LibSum fun sum_int(count : Int32, ...) : Int32 @@ -47,7 +47,7 @@ describe Crystal::Repl::Interpreter do end LibSum.sum_int(2, E::ONE, F::FOUR) - CR + CRYSTAL end after_all do @@ -62,14 +62,14 @@ describe Crystal::Repl::Interpreter do end it "expands ldflags" do - interpret(<<-CR).should eq 4 + interpret(<<-CRYSTAL).should eq 4 @[Link(ldflags: "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -l`echo sum`")] lib LibSum fun simple_sum_int(a : Int32, b : Int32) : Int32 end LibSum.simple_sum_int(2, 2) - CR + CRYSTAL end after_all do diff --git a/spec/compiler/interpreter/multidispatch_spec.cr b/spec/compiler/interpreter/multidispatch_spec.cr index ebd16a44f720..345228cc4710 100644 --- a/spec/compiler/interpreter/multidispatch_spec.cr +++ b/spec/compiler/interpreter/multidispatch_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "multidispatch" do it "does dispatch on one argument" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def foo(x : Char) x.ord.to_i32 end @@ -15,11 +15,11 @@ describe Crystal::Repl::Interpreter do a = 42 || 'a' foo(a) - CODE + CRYSTAL end it "does dispatch on one argument inside module with implicit self" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) module Moo def self.foo(x : Char) x.ord.to_i32 @@ -36,11 +36,11 @@ describe Crystal::Repl::Interpreter do end Moo.bar - CODE + CRYSTAL end it "does dispatch on one argument inside module with explicit receiver" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) module Moo def self.foo(x : Char) x.ord.to_i32 @@ -56,11 +56,11 @@ describe Crystal::Repl::Interpreter do a = 42 || 'a' Moo.foo(a) - CODE + CRYSTAL end it "does dispatch on receiver type" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Char def foo self.ord.to_i32 @@ -75,11 +75,11 @@ describe Crystal::Repl::Interpreter do a = 42 || 'a' a.foo - CODE + CRYSTAL end it "does dispatch on receiver type and argument type" do - interpret(<<-CODE).should eq(42 + 'b'.ord) + interpret(<<-CRYSTAL).should eq(42 + 'b'.ord) struct Char def foo(x : Int32) self.ord.to_i32 + x @@ -103,11 +103,11 @@ describe Crystal::Repl::Interpreter do a = 42 || 'a' b = 'b' || 43 a.foo(b) - CODE + CRYSTAL end it "does dispatch on receiver type and argument type, multiple times" do - interpret(<<-CODE).should eq(2 * (42 + 'b'.ord)) + interpret(<<-CRYSTAL).should eq(2 * (42 + 'b'.ord)) struct Char def foo(x : Int32) self.ord.to_i32 + x @@ -133,11 +133,11 @@ describe Crystal::Repl::Interpreter do x = a.foo(b) y = a.foo(b) x + y - CODE + CRYSTAL end it "does dispatch on one argument with struct receiver, and modifies it" do - interpret(<<-CODE).should eq(32) + interpret(<<-CRYSTAL).should eq(32) struct Foo def initialize @x = 2_i64 @@ -165,11 +165,11 @@ describe Crystal::Repl::Interpreter do a = 20 || 'a' b = foo.foo(a) b + foo.x - CODE + CRYSTAL end it "downcasts self from union to struct (pass pointer to self)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Foo def initialize @x = 1_i64 @@ -192,11 +192,11 @@ describe Crystal::Repl::Interpreter do obj = Point.new || Foo.new obj.x - CODE + CRYSTAL end it "does dispatch on virtual type" do - interpret(<<-CODE).should eq(4) + interpret(<<-CRYSTAL).should eq(4) abstract class Foo def foo 1 @@ -222,11 +222,11 @@ describe Crystal::Repl::Interpreter do y = foo.foo x + y - CODE + CRYSTAL end it "does dispatch on one argument with block" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) def foo(x : Char) yield x.ord.to_i32 end @@ -239,11 +239,11 @@ describe Crystal::Repl::Interpreter do foo(a) do |x| x + 10 end - CODE + CRYSTAL end it "doesn't compile block if it's not used (no yield)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Object def try yield self @@ -259,11 +259,11 @@ describe Crystal::Repl::Interpreter do a = 1 || nil b = a.try { |x| x + 1 } b || 10 - CODE + CRYSTAL end it "does multidispatch on virtual metaclass type (1)" do - interpret(<<-CODE).should eq("BB") + interpret(<<-CRYSTAL).should eq("BB") class Class def lt(other : T.class) : String forall T {% @type %} @@ -283,11 +283,11 @@ describe Crystal::Repl::Interpreter do t = B || A t.lt(t) - CODE + CRYSTAL end it "does multidispatch on virtual metaclass type (2)" do - interpret(<<-CODE).should eq("BB") + interpret(<<-CRYSTAL).should eq("BB") class Class def lt(other : T.class) : String forall T {% @type %} @@ -310,11 +310,11 @@ describe Crystal::Repl::Interpreter do t = B || A t.lt(t) - CODE + CRYSTAL end it "passes self as pointer when doing multidispatch" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) struct Foo def initialize(@x : Int32) end @@ -344,11 +344,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.new(0) || Bar.new(1) foo.to_unsafe.value = 10 foo.x - CODE + CRYSTAL end it "passes self as pointer when doing multidispatch (2)" do - interpret(<<-CODE).should be_true + interpret(<<-CRYSTAL).should be_true struct Tuple def ==(other) false @@ -357,11 +357,11 @@ describe Crystal::Repl::Interpreter do a = 1.as(Int32 | Tuple(Int64, Int64)) a == 1 - CODE + CRYSTAL end it "initialize multidispatch" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) struct Foo def initialize(x : Int64) initialize(x, 1 || 'a') @@ -379,11 +379,11 @@ describe Crystal::Repl::Interpreter do end Foo.new(1_i64).x - CODE + CRYSTAL end it "does multidispatch with mandatory named arguments" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) class Object def foo(obj, *, file = "") obj @@ -391,11 +391,11 @@ describe Crystal::Repl::Interpreter do end ("" || nil).foo 1, file: "" - CODE + CRYSTAL end it "does multidispatch with captured block (#12217)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) class A def then(&callback : Int32 -> Int32) callback.call(70) @@ -420,11 +420,11 @@ describe Crystal::Repl::Interpreter do end a_value - b_value - CODE + CRYSTAL end it "casts multidispatch argument to the def's arg type" do - interpret(<<-CODE) + interpret(<<-CRYSTAL) def foo(a : String) forall T end @@ -433,7 +433,7 @@ describe Crystal::Repl::Interpreter do end foo("b" || nil) - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/named_tuple_spec.cr b/spec/compiler/interpreter/named_tuple_spec.cr index 4ec9dfc3d264..e97cb4bccef4 100644 --- a/spec/compiler/interpreter/named_tuple_spec.cr +++ b/spec/compiler/interpreter/named_tuple_spec.cr @@ -4,14 +4,14 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "named tuple" do it "interprets named tuple literal and access by known index" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) a = {a: 1, b: 2, c: 3} a[:a] + a[:b] + a[:c] - CODE + CRYSTAL end it "interprets named tuple metaclass indexer" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) struct Int32 def self.foo 2 @@ -20,13 +20,13 @@ describe Crystal::Repl::Interpreter do a = {a: 1, b: 'a'} a.class[:a].foo - CODE + CRYSTAL end it "discards named tuple (#12383)" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) 1 + ({a: 1, b: 2, c: 3, d: 4}; 2) - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/pointers_spec.cr b/spec/compiler/interpreter/pointers_spec.cr index 9064956c210f..571e63843925 100644 --- a/spec/compiler/interpreter/pointers_spec.cr +++ b/spec/compiler/interpreter/pointers_spec.cr @@ -4,59 +4,59 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "pointers" do it "interprets pointer set and get (int)" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) ptr = Pointer(Int32).malloc(1_u64) ptr.value = 10 ptr.value - CODE + CRYSTAL end it "interprets pointer set and get (bool)" do - interpret(<<-CODE).should be_true + interpret(<<-CRYSTAL).should be_true ptr = Pointer(Bool).malloc(1_u64) ptr.value = true ptr.value - CODE + CRYSTAL end it "interprets pointer set and get (clear stack)" do - interpret(<<-CODE).should eq(50.unsafe_chr) + interpret(<<-CRYSTAL).should eq(50.unsafe_chr) ptr = Pointer(UInt8).malloc(1_u64) ptr.value = 50_u8 ptr.value.unsafe_chr - CODE + CRYSTAL end it "interprets pointerof, mutates pointer, read var" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) a = 1 ptr = pointerof(a) ptr.value = 2 a - CODE + CRYSTAL end it "interprets pointerof, mutates var, read pointer" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) a = 1 ptr = pointerof(a) a = 2 ptr.value - CODE + CRYSTAL end it "interprets pointerof and mutates memory (there are more variables)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) x = 42 a = 1 ptr = pointerof(a) ptr.value = 2 a - CODE + CRYSTAL end it "pointerof instance var" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Foo def initialize(@x : Int32) end @@ -74,11 +74,11 @@ describe Crystal::Repl::Interpreter do ptr = foo.x_ptr ptr.value = 2 foo.x - CODE + CRYSTAL end it "pointerof class var" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Foo @@x : Int32? @@ -96,11 +96,11 @@ describe Crystal::Repl::Interpreter do ptr.value = 2 x = Foo.x x || 0 - CODE + CRYSTAL end it "pointerof read instance var" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Foo def initialize(@x : Int32) end @@ -118,11 +118,11 @@ describe Crystal::Repl::Interpreter do ptr = pointerof(foo.@x) ptr.value = 2 foo.x - CODE + CRYSTAL end it "interprets pointer set and get (union type)" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) ptr = Pointer(Int32 | Bool).malloc(1_u64) ptr.value = 10 value = ptr.value @@ -131,119 +131,119 @@ describe Crystal::Repl::Interpreter do else 20 end - CODE + CRYSTAL end it "interprets pointer set and get (union type, setter value)" do - interpret(<<-CODE).should eq(10) + interpret(<<-CRYSTAL).should eq(10) ptr = Pointer(Int32 | Bool).malloc(1_u64) ptr.value = 10 - CODE + CRYSTAL end it "interprets pointer new and pointer address" do - interpret(<<-CODE).should eq(123_u64) + interpret(<<-CRYSTAL).should eq(123_u64) ptr = Pointer(Int32 | Bool).new(123_u64) ptr.address - CODE + CRYSTAL end it "interprets pointer diff" do - interpret(<<-CODE).should eq(8_i64) + interpret(<<-CRYSTAL).should eq(8_i64) ptr1 = Pointer(Int32).new(133_u64) ptr2 = Pointer(Int32).new(100_u64) ptr1 - ptr2 - CODE + CRYSTAL end it "interprets pointer diff, negative" do - interpret(<<-CODE).should eq(-8_i64) + interpret(<<-CRYSTAL).should eq(-8_i64) ptr1 = Pointer(Int32).new(133_u64) ptr2 = Pointer(Int32).new(100_u64) ptr2 - ptr1 - CODE + CRYSTAL end it "discards pointer malloc" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) Pointer(Int32).malloc(1_u64) 1 - CODE + CRYSTAL end it "discards pointer get" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) ptr = Pointer(Int32).malloc(1_u64) ptr.value 1 - CODE + CRYSTAL end it "discards pointer set" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) ptr = Pointer(Int32).malloc(1_u64) ptr.value = 1 - CODE + CRYSTAL end it "discards pointer new" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) Pointer(Int32).new(1_u64) 1 - CODE + CRYSTAL end it "discards pointer diff" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) ptr1 = Pointer(Int32).new(133_u64) ptr2 = Pointer(Int32).new(100_u64) ptr1 - ptr2 1 - CODE + CRYSTAL end it "discards pointerof" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) a = 1 pointerof(a) 3 - CODE + CRYSTAL end it "interprets pointer add" do - interpret(<<-CODE).should eq(9) + interpret(<<-CRYSTAL).should eq(9) ptr = Pointer(Int32).new(1_u64) ptr2 = ptr + 2_i64 ptr2.address - CODE + CRYSTAL end it "discards pointer add" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) ptr = Pointer(Int32).new(1_u64) ptr + 2_i64 3 - CODE + CRYSTAL end it "interprets pointer realloc" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) ptr = Pointer(Int32).malloc(1_u64) ptr2 = ptr.realloc(2_u64) 3 - CODE + CRYSTAL end it "discards pointer realloc" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) ptr = Pointer(Int32).malloc(1_u64) ptr.realloc(2_u64) 3 - CODE + CRYSTAL end it "interprets pointer realloc wrapper" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) struct Pointer(T) def realloc(n) realloc(n.to_u64) @@ -253,18 +253,18 @@ describe Crystal::Repl::Interpreter do ptr = Pointer(Int32).malloc(1_u64) ptr2 = ptr.realloc(2) 3 - CODE + CRYSTAL end it "interprets nilable pointer truthiness" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) ptr = 1 == 1 ? Pointer(UInt8).malloc(1) : nil if ptr 1 else 2 end - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/primitives_spec.cr b/spec/compiler/interpreter/primitives_spec.cr index 21aad383a01c..f003ebdb92ee 100644 --- a/spec/compiler/interpreter/primitives_spec.cr +++ b/spec/compiler/interpreter/primitives_spec.cr @@ -95,68 +95,68 @@ describe Crystal::Repl::Interpreter do end it "uses a string pool" do - interpret(<<-CODE).should eq(true) + interpret(<<-CRYSTAL).should eq(true) "a".object_id == "a".object_id - CODE + CRYSTAL end it "precomputes string literal length" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) "旅".@length - CODE + CRYSTAL end end context "local variables" do it "interprets variable set" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) a = 1 - CODE + CRYSTAL end it "interprets variable set and get" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) a = 1 a - CODE + CRYSTAL end it "interprets variable set and get, second local var" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) x = 10 a = 1 a - CODE + CRYSTAL end it "interprets variable set and get with operations" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) a = 1 b = 2 c = 3 a + b + c - CODE + CRYSTAL end it "interprets uninitialized" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) a = uninitialized Int32 a = 3 a - CODE + CRYSTAL end it "doesn't declare variable with no type" do - interpret(<<-CODE).should eq(nil) + interpret(<<-CRYSTAL).should eq(nil) x = nil if x y = x end - CODE + CRYSTAL end it "doesn't declare variable with no type inside method" do - interpret(<<-CODE).should eq(nil) + interpret(<<-CRYSTAL).should eq(nil) def foo(x) if x y = x @@ -164,25 +164,25 @@ describe Crystal::Repl::Interpreter do end foo(nil) - CODE + CRYSTAL end it "assigns to underscore" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) _ = (a = 1) a - CODE + CRYSTAL end it "doesn't discard underscore right hand side" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) a = (_ = 1) a - CODE + CRYSTAL end it "interprets at the class level" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) x = 0 class Foo @@ -198,14 +198,14 @@ describe Crystal::Repl::Interpreter do end x - CODE + CRYSTAL end it "interprets local variable declaration (#12229)" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) a : Int32 = 1 a - CODE + CRYSTAL end end @@ -293,18 +293,18 @@ describe Crystal::Repl::Interpreter do end it "discards conversion" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) 1.to_i8! 3 - CODE + CRYSTAL end it "discards conversion with local var" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) x = 1 x.to_i8! 3 - CODE + CRYSTAL end end @@ -489,7 +489,7 @@ describe Crystal::Repl::Interpreter do end it "interprets Int32.unsafe_shl(Int32) with self" do - interpret(<<-CODE).should eq(4) + interpret(<<-CRYSTAL).should eq(4) struct Int32 def shl2 unsafe_shl(2) @@ -498,7 +498,7 @@ describe Crystal::Repl::Interpreter do a = 1 a.shl2 - CODE + CRYSTAL end end @@ -680,11 +680,11 @@ describe Crystal::Repl::Interpreter do end it "interprets UInt64.unsafe_mod(UInt64)" do - interpret(<<-CODE).should eq(906272454103984) + interpret(<<-CRYSTAL).should eq(906272454103984) a = 10097976637018756016_u64 b = 9007199254740992_u64 a.unsafe_mod(b) - CODE + CRYSTAL end it "discards comparison" do @@ -746,7 +746,7 @@ describe Crystal::Repl::Interpreter do end it "interprets not for nilable proc type (true)" do - interpret(<<-CODE).should eq(true) + interpret(<<-CRYSTAL).should eq(true) a = if 1 == 1 nil @@ -754,11 +754,11 @@ describe Crystal::Repl::Interpreter do ->{ 1 } end !a - CODE + CRYSTAL end it "interprets not for nilable proc type (false)" do - interpret(<<-CODE).should eq(false) + interpret(<<-CRYSTAL).should eq(false) a = if 1 == 1 ->{ 1 } @@ -766,21 +766,21 @@ describe Crystal::Repl::Interpreter do nil end !a - CODE + CRYSTAL end it "interprets not for generic class instance type" do - interpret(<<-CODE).should eq(false) + interpret(<<-CRYSTAL).should eq(false) class Foo(T) end foo = Foo(Int32).new !foo - CODE + CRYSTAL end it "interprets not for nilable type (false)" do - interpret(<<-CODE).should eq(false) + interpret(<<-CRYSTAL).should eq(false) class Foo end @@ -793,11 +793,11 @@ describe Crystal::Repl::Interpreter do nil end !a - CODE + CRYSTAL end it "interprets not for nilable type (true)" do - interpret(<<-CODE).should eq(true) + interpret(<<-CRYSTAL).should eq(true) class Foo end @@ -810,7 +810,7 @@ describe Crystal::Repl::Interpreter do "a" end !a - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/procs_spec.cr b/spec/compiler/interpreter/procs_spec.cr index 863f8cf1fa93..2eca72820607 100644 --- a/spec/compiler/interpreter/procs_spec.cr +++ b/spec/compiler/interpreter/procs_spec.cr @@ -4,21 +4,21 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "procs" do it "interprets no args proc literal" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) proc = ->{ 40 } proc.call + 2 - CODE + CRYSTAL end it "interprets proc literal with args" do - interpret(<<-CODE).should eq(30) + interpret(<<-CRYSTAL).should eq(30) proc = ->(x : Int32, y : Int32) { x + y } proc.call(10, 20) - CODE + CRYSTAL end it "interprets call inside Proc type" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Proc def call2 call @@ -27,11 +27,11 @@ describe Crystal::Repl::Interpreter do proc = ->{ 40 } proc.call2 + 2 - CODE + CRYSTAL end it "casts from nilable proc type to proc type" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) proc = if 1 == 1 ->{ 42 } @@ -44,19 +44,19 @@ describe Crystal::Repl::Interpreter do else 1 end - CODE + CRYSTAL end it "discards proc call" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) proc = ->{ 40 } proc.call 2 - CODE + CRYSTAL end it "can downcast Proc(T) to Proc(Nil)" do - interpret(<<-CODE) + interpret(<<-CRYSTAL) class Foo def initialize(@proc : ->) end @@ -67,12 +67,12 @@ describe Crystal::Repl::Interpreter do end Foo.new(->{ 1 }).call - CODE + CRYSTAL end end it "casts proc call arguments to proc arg types (#12350)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) abstract struct Base end @@ -98,11 +98,11 @@ describe Crystal::Repl::Interpreter do bar = Foo.new(42) proc.call(bar) - CODE + CRYSTAL end it "does call without receiver inside closure" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Proc def foo ->{ @@ -112,6 +112,6 @@ describe Crystal::Repl::Interpreter do end ->{ 42 }.foo.call - CODE + CRYSTAL end end diff --git a/spec/compiler/interpreter/responds_to_spec.cr b/spec/compiler/interpreter/responds_to_spec.cr index be04637bbf5f..f972f0ae8d65 100644 --- a/spec/compiler/interpreter/responds_to_spec.cr +++ b/spec/compiler/interpreter/responds_to_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "responds_to?" do it "does responds_to?" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) class Foo def initialize @x = 1 @@ -38,11 +38,11 @@ describe Crystal::Repl::Interpreter do end a - CODE + CRYSTAL end it "doesn't crash if def body ends up with no type (#12219)" do - interpret(<<-CODE, prelude: "prelude").should eq("1") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("1") class Base def foo raise "OH NO" @@ -69,7 +69,7 @@ describe Crystal::Repl::Interpreter do rescue 1 end - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/special_vars_spec.cr b/spec/compiler/interpreter/special_vars_spec.cr index 13738bcf31ca..131190ffee43 100644 --- a/spec/compiler/interpreter/special_vars_spec.cr +++ b/spec/compiler/interpreter/special_vars_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "special vars" do it "does special var that's a reference" do - interpret(<<-CODE).should eq("hey") + interpret(<<-CRYSTAL).should eq("hey") class Object; def not_nil!; self; end; end def foo(x) @@ -13,11 +13,11 @@ describe Crystal::Repl::Interpreter do foo(2) $? || "oops" - CODE + CRYSTAL end it "does special var that's a struct" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) class Object; def not_nil!; self; end; end def foo(x) @@ -26,11 +26,11 @@ describe Crystal::Repl::Interpreter do foo(2) $? || 4 - CODE + CRYSTAL end it "does special var that's a reference inside block" do - interpret(<<-CODE).should eq("hey") + interpret(<<-CRYSTAL).should eq("hey") class Object; def not_nil!; self; end; end def bar @@ -45,11 +45,11 @@ describe Crystal::Repl::Interpreter do foo(2) $? || "oops" - CODE + CRYSTAL end it "does special var that's a reference when there are optional arguments" do - interpret(<<-CODE).should eq("hey") + interpret(<<-CRYSTAL).should eq("hey") class Object; def not_nil!; self; end; end def foo(x = 1) @@ -58,11 +58,11 @@ describe Crystal::Repl::Interpreter do foo $? || "oops" - CODE + CRYSTAL end it "does special var that's a reference for multidispatch" do - interpret(<<-CODE).should eq("hey") + interpret(<<-CRYSTAL).should eq("hey") class Object; def not_nil!; self; end; end def foo(x : Int32) @@ -76,11 +76,11 @@ describe Crystal::Repl::Interpreter do a = 1 || "a" foo(a) $? || "oops" - CODE + CRYSTAL end it "sets special var inside call inside block (#12250)" do - interpret(<<-CODE).should eq("hey") + interpret(<<-CRYSTAL).should eq("hey") class Object; def not_nil!; self; end; end def foo @@ -93,7 +93,7 @@ describe Crystal::Repl::Interpreter do bar { foo } $? || "oops" - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/structs_spec.cr b/spec/compiler/interpreter/structs_spec.cr index a573866d1e84..1e969f60d1b2 100644 --- a/spec/compiler/interpreter/structs_spec.cr +++ b/spec/compiler/interpreter/structs_spec.cr @@ -4,7 +4,7 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "structs" do it "does allocate, set instance var and get instance var" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo @x = 0_i64 @y = 0_i64 @@ -28,11 +28,11 @@ describe Crystal::Repl::Interpreter do foo.x = 22_i64 foo.y = 20_i64 foo.x + foo.y - CODE + CRYSTAL end it "does constructor" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize(@x : Int32) end @@ -44,11 +44,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.new(42) foo.x - CODE + CRYSTAL end it "interprets read instance var of struct" do - interpret(<<-CODE).should eq(20) + interpret(<<-CRYSTAL).should eq(20) struct Foo @x = 0_i64 @y = 0_i64 @@ -64,11 +64,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.allocate foo.y = 20_i64 foo.@y - CODE + CRYSTAL end it "casts def body to def type" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) struct Foo def foo return nil if 1 == 2 @@ -79,21 +79,21 @@ describe Crystal::Repl::Interpreter do value = Foo.new.foo value ? 1 : 2 - CODE + CRYSTAL end it "discards allocate" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) struct Foo end Foo.allocate 3 - CODE + CRYSTAL end it "mutates struct inside union" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) struct Foo def initialize @x = 1 @@ -118,11 +118,11 @@ describe Crystal::Repl::Interpreter do else 0 end - CODE + CRYSTAL end it "mutates struct stored in class var" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) struct Foo def initialize @x = 1 @@ -153,11 +153,11 @@ describe Crystal::Repl::Interpreter do Moo.mutate after = Moo.foo.x before + after - CODE + CRYSTAL end it "does simple class instance var initializer" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) class Foo @x = 42 @@ -168,11 +168,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.allocate foo.x - CODE + CRYSTAL end it "does complex class instance var initializer" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) class Foo @x : Int32 = begin a = 20 @@ -187,11 +187,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.allocate foo.x - CODE + CRYSTAL end it "does class instance var initializer inheritance" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) module Moo @z = 3 @@ -220,11 +220,11 @@ describe Crystal::Repl::Interpreter do bar = Bar.allocate bar.x + bar.y + bar.z - CODE + CRYSTAL end it "does simple struct instance var initializer" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo @x = 42 @@ -235,11 +235,11 @@ describe Crystal::Repl::Interpreter do foo = Foo.allocate foo.x - CODE + CRYSTAL end it "does call receiver by value from VirtualType abstract struct to concrete struct (#12190)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) abstract struct Base end @@ -262,11 +262,11 @@ describe Crystal::Repl::Interpreter do else 1 end - CODE + CRYSTAL end it "does call receiver by value from VirtualType abstract struct to union" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) abstract struct Base end @@ -298,11 +298,11 @@ describe Crystal::Repl::Interpreter do else 1 end - CODE + CRYSTAL end it "sets multiple instance vars in virtual abstract struct call (#12187)" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) abstract struct Foo @x = 0 @y = 0 @@ -336,11 +336,11 @@ describe Crystal::Repl::Interpreter do f = Bar.new || Baz.new f.set f.x + f.y + f.z - CODE + CRYSTAL end it "inlines struct method that returns self (#12253)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) struct Foo def initialize(@x : Int32) end @@ -361,7 +361,7 @@ describe Crystal::Repl::Interpreter do a = Foo.new(42) b = a.foo b.x - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/symbol_spec.cr b/spec/compiler/interpreter/symbol_spec.cr index bd2be22a1654..56000d5dd640 100644 --- a/spec/compiler/interpreter/symbol_spec.cr +++ b/spec/compiler/interpreter/symbol_spec.cr @@ -4,23 +4,23 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "symbol" do it "Symbol#to_s" do - interpret(<<-CODE).should eq("hello") + interpret(<<-CRYSTAL).should eq("hello") x = :hello x.to_s - CODE + CRYSTAL end it "Symbol#to_i" do - interpret(<<-CODE).should eq(0 + 1 + 2) + interpret(<<-CRYSTAL).should eq(0 + 1 + 2) x = :hello y = :bye z = :foo x.to_i + y.to_i + z.to_i - CODE + CRYSTAL end it "symbol equality" do - interpret(<<-CODE).should eq(9) + interpret(<<-CRYSTAL).should eq(9) s1 = :foo s2 = :bar @@ -30,7 +30,7 @@ describe Crystal::Repl::Interpreter do a += 4 if s1 != s1 a += 8 if s1 != s2 a - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/tuple_spec.cr b/spec/compiler/interpreter/tuple_spec.cr index 75c58d983d67..15ea8d5dea00 100644 --- a/spec/compiler/interpreter/tuple_spec.cr +++ b/spec/compiler/interpreter/tuple_spec.cr @@ -4,57 +4,57 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "tuple" do it "interprets tuple literal and access by known index" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) a = {1, 2, 3} a[0] + a[1] + a[2] - CODE + CRYSTAL end it "interprets tuple range indexer" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) #{range_new} a = {1, 2, 4, 8, 16} b = a[1...-2] b[0] + b[1] - CODE + CRYSTAL end it "interprets tuple range indexer (2)" do - interpret(<<-CODE).should eq(24) + interpret(<<-CRYSTAL).should eq(24) #{range_new} a = {1_i8, 2_i8, 4_i8, 8_i8, 16_i32} b = a[3..] b[1] + b[0] - CODE + CRYSTAL end it "interprets tuple literal of different types (1)" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) a = {1, true} a[0] + (a[1] ? 2 : 3) - CODE + CRYSTAL end it "interprets tuple literal of different types (2)" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) a = {true, 1} a[1] + (a[0] ? 2 : 3) - CODE + CRYSTAL end it "discards tuple access" do - interpret(<<-CODE).should eq(1) + interpret(<<-CRYSTAL).should eq(1) foo = {1, 2} a = foo[0] foo[1] a - CODE + CRYSTAL end it "interprets tuple self" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) struct Tuple def itself self @@ -64,19 +64,19 @@ describe Crystal::Repl::Interpreter do a = {1, 2, 3} b = a.itself b[0] + b[1] + b[2] - CODE + CRYSTAL end it "extends sign when doing to_i32" do - interpret(<<-CODE).should eq(-50) + interpret(<<-CRYSTAL).should eq(-50) t = {-50_i16} exp = t[0] z = exp.to_i32 - CODE + CRYSTAL end it "unpacks tuple in block arguments" do - interpret(<<-CODE).should eq(6) + interpret(<<-CRYSTAL).should eq(6) def foo t = {1, 2, 3} yield t @@ -85,11 +85,11 @@ describe Crystal::Repl::Interpreter do foo do |x, y, z| x + y + z end - CODE + CRYSTAL end it "interprets tuple metaclass indexer" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) struct Int32 def self.foo 2 @@ -98,11 +98,11 @@ describe Crystal::Repl::Interpreter do a = {1, 'a'} a.class[0].foo - CODE + CRYSTAL end it "interprets tuple metaclass range indexer" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) #{range_new} struct Int32 @@ -120,13 +120,13 @@ describe Crystal::Repl::Interpreter do a = {true, 1, "a", 'a', 1.0} b = a.class[1...-2] b[0].foo + b[1].bar - CODE + CRYSTAL end it "discards tuple (#12383)" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) 1 + ({1, 2, 3, 4}; 2) - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/typeof_spec.cr b/spec/compiler/interpreter/typeof_spec.cr index 5b1c230cc216..90cbdbeda91f 100644 --- a/spec/compiler/interpreter/typeof_spec.cr +++ b/spec/compiler/interpreter/typeof_spec.cr @@ -14,7 +14,7 @@ describe Crystal::Repl::Interpreter do end it "interprets typeof virtual type" do - interpret(<<-CODE, prelude: "prelude").should eq(%("Foo")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("Foo")) abstract class Foo end @@ -26,7 +26,7 @@ describe Crystal::Repl::Interpreter do foo = Baz.new.as(Foo) typeof(foo).to_s - CODE + CRYSTAL end end end diff --git a/spec/compiler/interpreter/types_spec.cr b/spec/compiler/interpreter/types_spec.cr index 02bae1be40a1..0158561cd197 100644 --- a/spec/compiler/interpreter/types_spec.cr +++ b/spec/compiler/interpreter/types_spec.cr @@ -18,28 +18,28 @@ describe Crystal::Repl::Interpreter do end it "interprets class for virtual_type type" do - interpret(<<-CODE, prelude: "prelude").should eq(%("Bar")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("Bar")) class Foo; end class Bar < Foo; end bar = Bar.new || Foo.new bar.class.to_s - CODE + CRYSTAL end it "interprets class for virtual_type type (struct)" do - interpret(<<-CODE, prelude: "prelude").should eq(%("Baz")) + interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("Baz")) abstract struct Foo; end struct Bar < Foo; end struct Baz < Foo; end baz = Baz.new || Bar.new baz.class.to_s - CODE + CRYSTAL end it "does class method on virtual metaclass casted to generic metaclass (#12302)" do - interpret(<<-CODE).should eq(42) + interpret(<<-CRYSTAL).should eq(42) class A def self.foo 1 @@ -54,22 +54,22 @@ describe Crystal::Repl::Interpreter do b = B(String).new.as(A) b.class.foo - CODE + CRYSTAL end it "discards class for virtual_type type" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Foo; end class Bar < Foo; end bar = Bar.new || Foo.new bar.class 2 - CODE + CRYSTAL end it "interprets class for module type (#12203)" do - interpret(<<-CODE).should eq("A") + interpret(<<-CRYSTAL).should eq("A") class Class def name : String {{ @type.name.stringify }} @@ -94,7 +94,7 @@ describe Crystal::Repl::Interpreter do e = E.new(A.new) base = e.@base base.class.name - CODE + CRYSTAL end it "interprets crystal_type_id for nil" do @@ -107,7 +107,7 @@ describe Crystal::Repl::Interpreter do end it "interprets crystal_type_id for virtual metaclass type (#12228)" do - interpret(<<-CODE).should eq(true) + interpret(<<-CRYSTAL).should eq(true) class P end @@ -116,16 +116,16 @@ describe Crystal::Repl::Interpreter do p = A.as(P.class) p.crystal_type_id == A.crystal_type_id - CODE + CRYSTAL end it "interprets class_crystal_instance_type_id" do - interpret(<<-CODE, prelude: "prelude").should eq("true") + interpret(<<-CRYSTAL, prelude: "prelude").should eq("true") class Foo end Foo.new.crystal_type_id == Foo.crystal_instance_type_id - CODE + CRYSTAL end it "discards Path" do diff --git a/spec/compiler/interpreter/unions_spec.cr b/spec/compiler/interpreter/unions_spec.cr index 5839f05f0dac..11bde229b44d 100644 --- a/spec/compiler/interpreter/unions_spec.cr +++ b/spec/compiler/interpreter/unions_spec.cr @@ -4,21 +4,21 @@ require "./spec_helper" describe Crystal::Repl::Interpreter do context "unions" do it "put and remove from union, together with is_a? (truthy case)" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) a = 1 == 1 ? 2 : true a.is_a?(Int32) ? a : 4 - CODE + CRYSTAL end it "put and remove from union, together with is_a? (falsey case)" do - interpret(<<-CODE).should eq(true) + interpret(<<-CRYSTAL).should eq(true) a = 1 == 2 ? 2 : true a.is_a?(Int32) ? true : a - CODE + CRYSTAL end it "returns union type" do - interpret(<<-CODE).should eq('a') + interpret(<<-CRYSTAL).should eq('a') def foo if 1 == 1 return 'a' @@ -33,19 +33,19 @@ describe Crystal::Repl::Interpreter do else 'b' end - CODE + CRYSTAL end it "put and remove from union in local var" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) a = 1 == 1 ? 2 : true a = 3 a.is_a?(Int32) ? a : 4 - CODE + CRYSTAL end it "put and remove from union in instance var" do - interpret(<<-CODE).should eq(2) + interpret(<<-CRYSTAL).should eq(2) class Foo @x : Int32 | Char @@ -69,26 +69,26 @@ describe Crystal::Repl::Interpreter do else 10 end - CODE + CRYSTAL end it "discards is_a?" do - interpret(<<-CODE).should eq(3) + interpret(<<-CRYSTAL).should eq(3) a = 1 == 1 ? 2 : true a.is_a?(Int32) 3 - CODE + CRYSTAL end it "converts from NilableType to NonGenericClassType" do - interpret(<<-CODE).should eq("a") + interpret(<<-CRYSTAL).should eq("a") a = 1 == 1 ? "a" : nil a || "b" - CODE + CRYSTAL end it "puts union inside union" do - interpret(<<-CODE).should eq('a'.ord) + interpret(<<-CRYSTAL).should eq('a'.ord) a = 'a' || 1 || true case a in Char @@ -98,7 +98,7 @@ describe Crystal::Repl::Interpreter do in Bool 20 end - CODE + CRYSTAL end end end diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index b39fa26e9f15..d711b93ccd2e 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2165,14 +2165,14 @@ module Crystal {x: TypeNode.new(mod)} end - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } class Foo(T) end alias Bar = Foo(Bar)? {{ Bar.nilable? ? 1 : 'a' }} - CR + CRYSTAL end end @@ -3134,9 +3134,9 @@ module Crystal end it "reads file (doesn't exist)" do - assert_error <<-CR, + assert_error <<-CRYSTAL, {{read_file("#{__DIR__}/../data/build_foo")}} - CR + CRYSTAL "No such file or directory" end end @@ -3149,9 +3149,9 @@ module Crystal end it "reads file (doesn't exist)" do - assert_error <<-CR, + assert_error <<-CRYSTAL, {{read_file("spec/compiler/data/build_foo")}} - CR + CRYSTAL "No such file or directory" end end diff --git a/spec/compiler/normalize/array_literal_spec.cr b/spec/compiler/normalize/array_literal_spec.cr index ab247427e144..9c180349bcac 100644 --- a/spec/compiler/normalize/array_literal_spec.cr +++ b/spec/compiler/normalize/array_literal_spec.cr @@ -6,27 +6,27 @@ describe "Normalize: array literal" do end it "normalizes non-empty with of" do - assert_expand "[1, 2] of Int8", <<-CR + assert_expand "[1, 2] of Int8", <<-CRYSTAL __temp_1 = ::Array(Int8).unsafe_build(2) __temp_2 = __temp_1.to_unsafe __temp_2[0] = 1 __temp_2[1] = 2 __temp_1 - CR + CRYSTAL end it "normalizes non-empty without of" do - assert_expand "[1, 2]", <<-CR + assert_expand "[1, 2]", <<-CRYSTAL __temp_1 = ::Array(typeof(1, 2)).unsafe_build(2) __temp_2 = __temp_1.to_unsafe __temp_2[0] = 1 __temp_2[1] = 2 __temp_1 - CR + CRYSTAL end it "normalizes non-empty with of, with splat" do - assert_expand "[1, *2, *3, 4, 5] of Int8", <<-CR + assert_expand "[1, *2, *3, 4, 5] of Int8", <<-CRYSTAL __temp_1 = ::Array(Int8).new(3) __temp_1 << 1 __temp_1.concat(2) @@ -34,11 +34,11 @@ describe "Normalize: array literal" do __temp_1 << 4 __temp_1 << 5 __temp_1 - CR + CRYSTAL end it "normalizes non-empty without of, with splat" do - assert_expand "[1, *2, *3, 4, 5]", <<-CR + assert_expand "[1, *2, *3, 4, 5]", <<-CRYSTAL __temp_1 = ::Array(typeof(1, ::Enumerable.element_type(2), ::Enumerable.element_type(3), 4, 5)).new(3) __temp_1 << 1 __temp_1.concat(2) @@ -46,38 +46,38 @@ describe "Normalize: array literal" do __temp_1 << 4 __temp_1 << 5 __temp_1 - CR + CRYSTAL end it "normalizes non-empty without of, with splat only" do - assert_expand "[*1]", <<-CR + assert_expand "[*1]", <<-CRYSTAL __temp_1 = ::Array(typeof(::Enumerable.element_type(1))).new(0) __temp_1.concat(1) __temp_1 - CR + CRYSTAL end it "hoists complex element expressions" do - assert_expand "[[1]]", <<-CR + assert_expand "[[1]]", <<-CRYSTAL __temp_1 = [1] __temp_2 = ::Array(typeof(__temp_1)).unsafe_build(1) __temp_3 = __temp_2.to_unsafe __temp_3[0] = __temp_1 __temp_2 - CR + CRYSTAL end it "hoists complex element expressions, with splat" do - assert_expand "[*[1]]", <<-CR + assert_expand "[*[1]]", <<-CRYSTAL __temp_1 = [1] __temp_2 = ::Array(typeof(::Enumerable.element_type(__temp_1))).new(0) __temp_2.concat(__temp_1) __temp_2 - CR + CRYSTAL end it "hoists complex element expressions, array-like" do - assert_expand_named "Foo{[1], *[2]}", <<-CR + assert_expand_named "Foo{[1], *[2]}", <<-CRYSTAL __temp_1 = [1] __temp_2 = [2] __temp_3 = Foo.new @@ -86,11 +86,11 @@ describe "Normalize: array literal" do __temp_3 << __temp_4 end __temp_3 - CR + CRYSTAL end it "hoists complex element expressions, array-like generic" do - assert_expand_named "Foo{[1], *[2]}", <<-CR, generic: "Foo" + assert_expand_named "Foo{[1], *[2]}", <<-CRYSTAL, generic: "Foo" __temp_1 = [1] __temp_2 = [2] __temp_3 = Foo(typeof(__temp_1, ::Enumerable.element_type(__temp_2))).new @@ -99,6 +99,6 @@ describe "Normalize: array literal" do __temp_3 << __temp_4 end __temp_3 - CR + CRYSTAL end end diff --git a/spec/compiler/normalize/hash_literal_spec.cr b/spec/compiler/normalize/hash_literal_spec.cr index 67f95424368a..3701733957a4 100644 --- a/spec/compiler/normalize/hash_literal_spec.cr +++ b/spec/compiler/normalize/hash_literal_spec.cr @@ -6,53 +6,53 @@ describe "Normalize: hash literal" do end it "normalizes non-empty with of" do - assert_expand "{1 => 2, 3 => 4} of Int => Float", <<-CR + assert_expand "{1 => 2, 3 => 4} of Int => Float", <<-CRYSTAL __temp_1 = ::Hash(Int, Float).new __temp_1[1] = 2 __temp_1[3] = 4 __temp_1 - CR + CRYSTAL end it "normalizes non-empty without of" do - assert_expand "{1 => 2, 3 => 4}", <<-CR + assert_expand "{1 => 2, 3 => 4}", <<-CRYSTAL __temp_1 = ::Hash(typeof(1, 3), typeof(2, 4)).new __temp_1[1] = 2 __temp_1[3] = 4 __temp_1 - CR + CRYSTAL end it "hoists complex element expressions" do - assert_expand "{[1] => 2, 3 => [4]}", <<-CR + assert_expand "{[1] => 2, 3 => [4]}", <<-CRYSTAL __temp_1 = [1] __temp_2 = [4] __temp_3 = ::Hash(typeof(__temp_1, 3), typeof(2, __temp_2)).new __temp_3[__temp_1] = 2 __temp_3[3] = __temp_2 __temp_3 - CR + CRYSTAL end it "hoists complex element expressions, hash-like" do - assert_expand_named "Foo{[1] => 2, 3 => [4]}", <<-CR + assert_expand_named "Foo{[1] => 2, 3 => [4]}", <<-CRYSTAL __temp_1 = [1] __temp_2 = [4] __temp_3 = Foo.new __temp_3[__temp_1] = 2 __temp_3[3] = __temp_2 __temp_3 - CR + CRYSTAL end it "hoists complex element expressions, hash-like generic" do - assert_expand_named "Foo{[1] => 2, 3 => [4]}", <<-CR, generic: "Foo" + assert_expand_named "Foo{[1] => 2, 3 => [4]}", <<-CRYSTAL, generic: "Foo" __temp_1 = [1] __temp_2 = [4] __temp_3 = Foo(typeof(__temp_1, 3), typeof(2, __temp_2)).new __temp_3[__temp_1] = 2 __temp_3[3] = __temp_2 __temp_3 - CR + CRYSTAL end end diff --git a/spec/compiler/normalize/multi_assign_spec.cr b/spec/compiler/normalize/multi_assign_spec.cr index 08c8fb44f0ce..3757ee9d45d9 100644 --- a/spec/compiler/normalize/multi_assign_spec.cr +++ b/spec/compiler/normalize/multi_assign_spec.cr @@ -2,64 +2,64 @@ require "../../spec_helper" describe "Normalize: multi assign" do it "normalizes n to n" do - assert_expand "a, b, c = 1, 2, 3", <<-CR + assert_expand "a, b, c = 1, 2, 3", <<-CRYSTAL __temp_1 = 1 __temp_2 = 2 __temp_3 = 3 a = __temp_1 b = __temp_2 c = __temp_3 - CR + CRYSTAL end it "normalizes n to n with []" do - assert_expand_third "a = 1; b = 2; a[0], b[1] = 2, 3", <<-CR + assert_expand_third "a = 1; b = 2; a[0], b[1] = 2, 3", <<-CRYSTAL __temp_1 = 2 __temp_2 = 3 a[0] = __temp_1 b[1] = __temp_2 - CR + CRYSTAL end it "normalizes n to n with call" do - assert_expand_third "a = 1; b = 2; a.foo, b.bar = 2, 3", <<-CR + assert_expand_third "a = 1; b = 2; a.foo, b.bar = 2, 3", <<-CRYSTAL __temp_1 = 2 __temp_2 = 3 a.foo = __temp_1 b.bar = __temp_2 - CR + CRYSTAL end context "without strict_multi_assign" do it "normalizes 1 to n" do - assert_expand_second "d = 1; a, b, c = d", <<-CR + assert_expand_second "d = 1; a, b, c = d", <<-CRYSTAL __temp_1 = d a = __temp_1[0] b = __temp_1[1] c = __temp_1[2] - CR + CRYSTAL end it "normalizes 1 to n with []" do - assert_expand_third "a = 1; b = 2; a[0], b[1] = 2", <<-CR + assert_expand_third "a = 1; b = 2; a[0], b[1] = 2", <<-CRYSTAL __temp_1 = 2 a[0] = __temp_1[0] b[1] = __temp_1[1] - CR + CRYSTAL end it "normalizes 1 to n with call" do - assert_expand_third "a = 1; b = 2; a.foo, b.bar = 2", <<-CR + assert_expand_third "a = 1; b = 2; a.foo, b.bar = 2", <<-CRYSTAL __temp_1 = 2 a.foo = __temp_1[0] b.bar = __temp_1[1] - CR + CRYSTAL end end context "strict_multi_assign" do it "normalizes 1 to n" do - assert_expand_second "d = 1; a, b, c = d", <<-CR, flags: "strict_multi_assign" + assert_expand_second "d = 1; a, b, c = d", <<-CRYSTAL, flags: "strict_multi_assign" __temp_1 = d if __temp_1.size != 3 ::raise(::IndexError.new("Multiple assignment count mismatch")) @@ -67,56 +67,56 @@ describe "Normalize: multi assign" do a = __temp_1[0] b = __temp_1[1] c = __temp_1[2] - CR + CRYSTAL end it "normalizes 1 to n with []" do - assert_expand_third "a = 1; b = 2; a[0], b[1] = 2", <<-CR, flags: "strict_multi_assign" + assert_expand_third "a = 1; b = 2; a[0], b[1] = 2", <<-CRYSTAL, flags: "strict_multi_assign" __temp_1 = 2 if __temp_1.size != 2 ::raise(::IndexError.new("Multiple assignment count mismatch")) end a[0] = __temp_1[0] b[1] = __temp_1[1] - CR + CRYSTAL end it "normalizes 1 to n with call" do - assert_expand_third "a = 1; b = 2; a.foo, b.bar = 2", <<-CR, flags: "strict_multi_assign" + assert_expand_third "a = 1; b = 2; a.foo, b.bar = 2", <<-CRYSTAL, flags: "strict_multi_assign" __temp_1 = 2 if __temp_1.size != 2 ::raise(::IndexError.new("Multiple assignment count mismatch")) end a.foo = __temp_1[0] b.bar = __temp_1[1] - CR + CRYSTAL end end it "normalizes m to n, with splat on left-hand side, splat is empty" do - assert_expand_third "a = 1; b = 2; *a[0], b.foo, c = 3, 4", <<-CR + assert_expand_third "a = 1; b = 2; *a[0], b.foo, c = 3, 4", <<-CRYSTAL __temp_1 = ::Tuple.new __temp_2 = 3 __temp_3 = 4 a[0] = __temp_1 b.foo = __temp_2 c = __temp_3 - CR + CRYSTAL end it "normalizes m to n, with splat on left-hand side, splat is non-empty" do - assert_expand_third "a = 1; b = 2; a[0], *b.foo, c = 3, 4, 5, 6, 7", <<-CR + assert_expand_third "a = 1; b = 2; a[0], *b.foo, c = 3, 4, 5, 6, 7", <<-CRYSTAL __temp_1 = 3 __temp_2 = ::Tuple.new(4, 5, 6) __temp_3 = 7 a[0] = __temp_1 b.foo = __temp_2 c = __temp_3 - CR + CRYSTAL end it "normalizes m to n, with *_ on left-hand side (1)" do - assert_expand "a, *_, b, c = 1, 2, 3, 4, 5", <<-CR + assert_expand "a, *_, b, c = 1, 2, 3, 4, 5", <<-CRYSTAL __temp_1 = 1 2 3 @@ -125,11 +125,11 @@ describe "Normalize: multi assign" do a = __temp_1 b = __temp_2 c = __temp_3 - CR + CRYSTAL end it "normalizes m to n, with *_ on left-hand side (2)" do - assert_expand "*_, a, b, c = 1, 2, 3, 4, 5", <<-CR + assert_expand "*_, a, b, c = 1, 2, 3, 4, 5", <<-CRYSTAL 1 2 __temp_1 = 3 @@ -138,11 +138,11 @@ describe "Normalize: multi assign" do a = __temp_1 b = __temp_2 c = __temp_3 - CR + CRYSTAL end it "normalizes m to n, with *_ on left-hand side (3)" do - assert_expand "a, b, c, *_ = 1, 2, 3, 4, 5", <<-CR + assert_expand "a, b, c, *_ = 1, 2, 3, 4, 5", <<-CRYSTAL __temp_1 = 1 __temp_2 = 2 __temp_3 = 3 @@ -151,11 +151,11 @@ describe "Normalize: multi assign" do a = __temp_1 b = __temp_2 c = __temp_3 - CR + CRYSTAL end it "normalizes 1 to n, with splat on left-hand side" do - assert_expand_third "c = 1; d = 2; a, b, *c.foo, d[0], e, f = 3", <<-CR + assert_expand_third "c = 1; d = 2; a, b, *c.foo, d[0], e, f = 3", <<-CRYSTAL __temp_1 = 3 if __temp_1.size < 5 ::raise(::IndexError.new("Multiple assignment count mismatch")) @@ -166,31 +166,31 @@ describe "Normalize: multi assign" do d[0] = __temp_1[-3] e = __temp_1[-2] f = __temp_1[-1] - CR + CRYSTAL end it "normalizes 1 to n, with splat on left-hand side, splat before other targets" do - assert_expand "*a, b, c, d = 3", <<-CR + assert_expand "*a, b, c, d = 3", <<-CRYSTAL __temp_1 = 3 a = __temp_1[0..-4] b = __temp_1[-3] c = __temp_1[-2] d = __temp_1[-1] - CR + CRYSTAL end it "normalizes 1 to n, with splat on left-hand side, splat after other targets" do - assert_expand "a, b, c, *d = 3", <<-CR + assert_expand "a, b, c, *d = 3", <<-CRYSTAL __temp_1 = 3 a = __temp_1[0] b = __temp_1[1] c = __temp_1[2] d = __temp_1[3..-1] - CR + CRYSTAL end it "normalizes 1 to n, with *_ on left-hand side (1)" do - assert_expand "a, *_, b, c = 1", <<-CR + assert_expand "a, *_, b, c = 1", <<-CRYSTAL __temp_1 = 1 if __temp_1.size < 3 ::raise(::IndexError.new("Multiple assignment count mismatch")) @@ -198,53 +198,53 @@ describe "Normalize: multi assign" do a = __temp_1[0] b = __temp_1[-2] c = __temp_1[-1] - CR + CRYSTAL end it "normalizes 1 to n, with *_ on left-hand side (2)" do - assert_expand "*_, a, b, c = 1", <<-CR + assert_expand "*_, a, b, c = 1", <<-CRYSTAL __temp_1 = 1 a = __temp_1[-3] b = __temp_1[-2] c = __temp_1[-1] - CR + CRYSTAL end it "normalizes 1 to n, with *_ on left-hand side (3)" do - assert_expand "a, b, c, *_ = 1", <<-CR + assert_expand "a, b, c, *_ = 1", <<-CRYSTAL __temp_1 = 1 a = __temp_1[0] b = __temp_1[1] c = __temp_1[2] - CR + CRYSTAL end it "normalizes n to splat on left-hand side" do - assert_expand "*a = 1, 2, 3, 4", <<-CR + assert_expand "*a = 1, 2, 3, 4", <<-CRYSTAL __temp_1 = ::Tuple.new(1, 2, 3, 4) a = __temp_1 - CR + CRYSTAL end it "normalizes n to *_ on left-hand side" do - assert_expand "*_ = 1, 2, 3, 4", <<-CR + assert_expand "*_ = 1, 2, 3, 4", <<-CRYSTAL 1 2 3 4 - CR + CRYSTAL end it "normalizes 1 to splat on left-hand side" do - assert_expand "*a = 1", <<-CR + assert_expand "*a = 1", <<-CRYSTAL __temp_1 = 1 a = __temp_1[0..-1] - CR + CRYSTAL end it "normalizes 1 to *_ on left-hand side" do - assert_expand "*_ = 1", <<-CR + assert_expand "*_ = 1", <<-CRYSTAL __temp_1 = 1 - CR + CRYSTAL end end diff --git a/spec/compiler/normalize/proc_pointer_spec.cr b/spec/compiler/normalize/proc_pointer_spec.cr index 0c11bf5d0abc..bd91332f23f6 100644 --- a/spec/compiler/normalize/proc_pointer_spec.cr +++ b/spec/compiler/normalize/proc_pointer_spec.cr @@ -2,69 +2,69 @@ require "../../spec_helper" describe "Normalize: proc pointer" do it "normalizes proc pointer without object" do - assert_expand "->foo", <<-CR + assert_expand "->foo", <<-CRYSTAL -> do foo end - CR + CRYSTAL end it "normalizes proc pointer with parameters, without object" do - assert_expand "->foo(Int32, String)", <<-CR + assert_expand "->foo(Int32, String)", <<-CRYSTAL ->(__temp_1 : Int32, __temp_2 : String) do foo(__temp_1, __temp_2) end - CR + CRYSTAL end it "normalizes proc pointer of global call" do - assert_expand "->::foo(Int32)", <<-CR + assert_expand "->::foo(Int32)", <<-CRYSTAL ->(__temp_1 : Int32) do ::foo(__temp_1) end - CR + CRYSTAL end it "normalizes proc pointer with const receiver" do - assert_expand "->Foo.foo(Int32)", <<-CR + assert_expand "->Foo.foo(Int32)", <<-CRYSTAL ->(__temp_1 : Int32) do Foo.foo(__temp_1) end - CR + CRYSTAL end it "normalizes proc pointer with global const receiver" do - assert_expand "->::Foo.foo(Int32)", <<-CR + assert_expand "->::Foo.foo(Int32)", <<-CRYSTAL ->(__temp_1 : Int32) do ::Foo.foo(__temp_1) end - CR + CRYSTAL end it "normalizes proc pointer with variable receiver" do - assert_expand_second "foo = 1; ->foo.bar(Int32)", <<-CR + assert_expand_second "foo = 1; ->foo.bar(Int32)", <<-CRYSTAL __temp_1 = foo ->(__temp_2 : Int32) do __temp_1.bar(__temp_2) end - CR + CRYSTAL end it "normalizes proc pointer with ivar receiver" do - assert_expand "->@foo.bar(Int32)", <<-CR + assert_expand "->@foo.bar(Int32)", <<-CRYSTAL __temp_1 = @foo ->(__temp_2 : Int32) do __temp_1.bar(__temp_2) end - CR + CRYSTAL end it "normalizes proc pointer with cvar receiver" do - assert_expand "->@@foo.bar(Int32)", <<-CR + assert_expand "->@@foo.bar(Int32)", <<-CRYSTAL __temp_1 = @@foo ->(__temp_2 : Int32) do __temp_1.bar(__temp_2) end - CR + CRYSTAL end end diff --git a/spec/compiler/normalize/select_spec.cr b/spec/compiler/normalize/select_spec.cr index 236ca0eae311..cd7231de4375 100644 --- a/spec/compiler/normalize/select_spec.cr +++ b/spec/compiler/normalize/select_spec.cr @@ -2,7 +2,7 @@ require "../../spec_helper" describe "Normalize: case" do it "normalizes select with call" do - assert_expand "select; when foo; body; when bar; baz; end", <<-CODE + assert_expand "select; when foo; body; when bar; baz; end", <<-CRYSTAL __temp_1, __temp_2 = ::Channel.select({foo_select_action, bar_select_action}) case __temp_1 when 0 @@ -12,11 +12,11 @@ describe "Normalize: case" do else ::raise("BUG: invalid select index") end - CODE + CRYSTAL end it "normalizes select with assign" do - assert_expand "select; when x = foo; x + 1; end", <<-CODE + assert_expand "select; when x = foo; x + 1; end", <<-CRYSTAL __temp_1, __temp_2 = ::Channel.select({foo_select_action}) case __temp_1 when 0 @@ -25,11 +25,11 @@ describe "Normalize: case" do else ::raise("BUG: invalid select index") end - CODE + CRYSTAL end it "normalizes select with else" do - assert_expand "select; when foo; body; else; baz; end", <<-CODE + assert_expand "select; when foo; body; else; baz; end", <<-CRYSTAL __temp_1, __temp_2 = ::Channel.non_blocking_select({foo_select_action}) case __temp_1 when 0 @@ -37,11 +37,11 @@ describe "Normalize: case" do else baz end - CODE + CRYSTAL end it "normalizes select with assign and question method" do - assert_expand "select; when x = foo?; x + 1; end", <<-CODE + assert_expand "select; when x = foo?; x + 1; end", <<-CRYSTAL __temp_1, __temp_2 = ::Channel.select({foo_select_action?}) case __temp_1 when 0 @@ -50,11 +50,11 @@ describe "Normalize: case" do else ::raise("BUG: invalid select index") end - CODE + CRYSTAL end it "normalizes select with assign and bang method" do - assert_expand "select; when x = foo!; x + 1; end", <<-CODE + assert_expand "select; when x = foo!; x + 1; end", <<-CRYSTAL __temp_1, __temp_2 = ::Channel.select({foo_select_action!}) case __temp_1 when 0 @@ -63,6 +63,6 @@ describe "Normalize: case" do else ::raise("BUG: invalid select index") end - CODE + CRYSTAL end end diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 311b0c3e0344..61f1a369154f 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -339,13 +339,13 @@ module Crystal it_parses "def foo(@[Foo] &@block); end", Def.new("foo", body: Assign.new("@block".instance_var, "block".var), block_arg: "block".arg(annotations: ["Foo".ann]), yields: 0) it_parses "def foo(@[Foo] *args); end", Def.new("foo", args: ["args".arg(annotations: ["Foo".ann])], splat_index: 0) it_parses "def foo(@[Foo] **args); end", Def.new("foo", double_splat: "args".arg(annotations: ["Foo".ann])) - it_parses <<-CR, Def.new("foo", ["id".arg(restriction: "Int32".path, annotations: ["Foo".ann]), "name".arg(restriction: "String".path, annotations: ["Bar".ann])]) + it_parses <<-CRYSTAL, Def.new("foo", ["id".arg(restriction: "Int32".path, annotations: ["Foo".ann]), "name".arg(restriction: "String".path, annotations: ["Bar".ann])]) def foo( @[Foo] id : Int32, @[Bar] name : String ); end - CR + CRYSTAL it_parses "def foo(\n&block\n); end", Def.new("foo", block_arg: Arg.new("block"), yields: 0) it_parses "def foo(&block :\n Int ->); end", Def.new("foo", block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path] of ASTNode)), yields: 1) @@ -1052,13 +1052,13 @@ module Crystal it_parses "macro foo(a, @[Foo] &block);end", Macro.new("foo", ["a".arg], Expressions.new, block_arg: "block".arg(annotations: ["Foo".ann])) it_parses "macro foo(@[Foo] *args);end", Macro.new("foo", ["args".arg(annotations: ["Foo".ann])], Expressions.new, splat_index: 0) it_parses "macro foo(@[Foo] **args);end", Macro.new("foo", body: Expressions.new, double_splat: "args".arg(annotations: ["Foo".ann])) - it_parses <<-CR, Macro.new("foo", ["id".arg(annotations: ["Foo".ann]), "name".arg(annotations: ["Bar".ann])], Expressions.new) + it_parses <<-CRYSTAL, Macro.new("foo", ["id".arg(annotations: ["Foo".ann]), "name".arg(annotations: ["Bar".ann])], Expressions.new) macro foo( @[Foo] id, @[Bar] name );end - CR + CRYSTAL assert_syntax_error "macro foo; {% foo = 1 }; end" assert_syntax_error "macro def foo : String; 1; end" @@ -2069,29 +2069,29 @@ module Crystal it_parses "{[] of Foo, ::foo}", TupleLiteral.new([ArrayLiteral.new([] of ASTNode, "Foo".path), Call.new(nil, "foo", global: true)] of ASTNode) it_parses "{[] of Foo, self.foo}", TupleLiteral.new([ArrayLiteral.new([] of ASTNode, "Foo".path), Call.new("self".var, "foo")] of ASTNode) - it_parses <<-'CR', Macro.new("foo", body: Expressions.new([MacroLiteral.new(" <<-FOO\n \#{ "), MacroVar.new("var"), MacroLiteral.new(" }\n FOO\n")] of ASTNode)) + it_parses <<-'CRYSTAL', Macro.new("foo", body: Expressions.new([MacroLiteral.new(" <<-FOO\n \#{ "), MacroVar.new("var"), MacroLiteral.new(" }\n FOO\n")] of ASTNode)) macro foo <<-FOO #{ %var } FOO end - CR + CRYSTAL - it_parses <<-'CR', Macro.new("foo", body: MacroLiteral.new(" <<-FOO, <<-BAR + \"\"\n FOO\n BAR\n")) + it_parses <<-'CRYSTAL', Macro.new("foo", body: MacroLiteral.new(" <<-FOO, <<-BAR + \"\"\n FOO\n BAR\n")) macro foo <<-FOO, <<-BAR + "" FOO BAR end - CR + CRYSTAL - it_parses <<-'CR', Macro.new("foo", body: MacroLiteral.new(" <<-FOO\n %foo\n FOO\n")) + it_parses <<-'CRYSTAL', Macro.new("foo", body: MacroLiteral.new(" <<-FOO\n %foo\n FOO\n")) macro foo <<-FOO %foo FOO end - CR + CRYSTAL it_parses "macro foo; bar class: 1; end", Macro.new("foo", body: MacroLiteral.new(" bar class: 1; ")) @@ -2406,7 +2406,7 @@ module Crystal end it "correctly computes line number after `\\{%\n` (#9857)" do - code = <<-CODE + code = <<-CRYSTAL macro foo \\{% 1 @@ -2414,7 +2414,7 @@ module Crystal end 1 - CODE + CRYSTAL exps = Parser.parse(code).as(Expressions) exps.expressions[1].location.not_nil!.line_number.should eq(7) diff --git a/spec/compiler/semantic/abstract_def_spec.cr b/spec/compiler/semantic/abstract_def_spec.cr index 0cbff757099f..b7a7da5d431f 100644 --- a/spec/compiler/semantic/abstract_def_spec.cr +++ b/spec/compiler/semantic/abstract_def_spec.cr @@ -116,14 +116,14 @@ describe "Semantic: abstract def" do end it "errors if abstract method is not implemented by subclass" do - exc = assert_error <<-CR, + exc = assert_error <<-CRYSTAL, abstract class Foo abstract def foo end class Bar < Foo end - CR + CRYSTAL "abstract `def Foo#foo()` must be implemented by Bar" exc.line_number.should eq 5 exc.column_number.should eq 1 @@ -185,7 +185,7 @@ describe "Semantic: abstract def" do end it "doesn't error if abstract method is implemented by subclass" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo end @@ -194,11 +194,11 @@ describe "Semantic: abstract def" do def foo end end - CR + CRYSTAL end it "doesn't error if abstract method with args is implemented by subclass" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(x, y) end @@ -207,11 +207,11 @@ describe "Semantic: abstract def" do def foo(x, y) end end - CR + CRYSTAL end it "doesn't error if abstract method with args is implemented by subclass (restriction -> no restriction)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(x, y : Int32) end @@ -220,11 +220,11 @@ describe "Semantic: abstract def" do def foo(x, y) end end - CR + CRYSTAL end it "doesn't error if abstract method with args is implemented by subclass (don't check subclasses)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo end @@ -236,18 +236,18 @@ describe "Semantic: abstract def" do class Baz < Bar end - CR + CRYSTAL end it "errors if abstract method of private type is not implemented by subclass" do - assert_error <<-CR, "abstract `def Foo#foo()` must be implemented by Bar" + assert_error <<-CRYSTAL, "abstract `def Foo#foo()` must be implemented by Bar" private abstract class Foo abstract def foo end class Bar < Foo end - CR + CRYSTAL end it "errors if abstract method is not implemented by subclass of subclass" do @@ -266,7 +266,7 @@ describe "Semantic: abstract def" do end it "doesn't error if abstract method is implemented by subclass via module inclusion" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo end @@ -279,7 +279,7 @@ describe "Semantic: abstract def" do class Bar < Foo include Moo end - CR + CRYSTAL end it "errors if abstract method is not implemented by including class" do @@ -296,7 +296,7 @@ describe "Semantic: abstract def" do end it "doesn't error if abstract method is implemented by including class" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL module Foo abstract def foo end @@ -307,11 +307,11 @@ describe "Semantic: abstract def" do def foo end end - CR + CRYSTAL end it "errors if abstract method of private type is not implemented by including class" do - assert_error <<-CR, "abstract `def Foo#foo()` must be implemented by Bar" + assert_error <<-CRYSTAL, "abstract `def Foo#foo()` must be implemented by Bar" private module Foo abstract def foo end @@ -319,11 +319,11 @@ describe "Semantic: abstract def" do class Bar include Foo end - CR + CRYSTAL end it "doesn't error if abstract method is not implemented by including module" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL module Foo abstract def foo end @@ -331,7 +331,7 @@ describe "Semantic: abstract def" do module Bar include Foo end - CR + CRYSTAL end it "errors if abstract method is not implemented by subclass (nested in module)" do @@ -349,7 +349,7 @@ describe "Semantic: abstract def" do end it "doesn't error if abstract method with args is implemented by subclass (with one default arg)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(x) end @@ -358,7 +358,7 @@ describe "Semantic: abstract def" do def foo(x, y = 1) end end - CR + CRYSTAL end it "doesn't error if implements with parent class" do @@ -471,7 +471,7 @@ describe "Semantic: abstract def" do end it "finds implements in included module in disorder (#4052)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL module B abstract def x end @@ -486,11 +486,11 @@ describe "Semantic: abstract def" do include C include B end - CR + CRYSTAL end it "errors if missing return type" do - assert_error <<-CR, + assert_error <<-CRYSTAL, abstract class Foo abstract def foo : Int32 end @@ -500,12 +500,12 @@ describe "Semantic: abstract def" do 1 end end - CR + CRYSTAL "this method overrides Foo#foo() which has an explicit return type of Int32.\n\nPlease add an explicit return type (Int32 or a subtype of it) to this method as well." end it "errors if different return type" do - assert_error <<-CR, + assert_error <<-CRYSTAL, abstract class Foo abstract def foo : Int32 end @@ -518,7 +518,7 @@ describe "Semantic: abstract def" do 1 end end - CR + CRYSTAL "this method must return Int32, which is the return type of the overridden method Foo#foo(), or a subtype of it, not Bar::Int32" end @@ -546,7 +546,7 @@ describe "Semantic: abstract def" do end it "matches instantiated generic types" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo(T) abstract def foo(x : T) end @@ -558,11 +558,11 @@ describe "Semantic: abstract def" do def foo(x : Int32) end end - CR + CRYSTAL end it "matches generic types" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo(T) abstract def foo(x : T) end @@ -571,11 +571,11 @@ describe "Semantic: abstract def" do def foo(x : U) end end - CR + CRYSTAL end it "matches instantiated generic module" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL module Foo(T) abstract def foo(x : T) end @@ -586,11 +586,11 @@ describe "Semantic: abstract def" do def foo(x : Int32) end end - CR + CRYSTAL end it "matches generic module" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL module Foo(T) abstract def foo(x : T) end @@ -601,11 +601,11 @@ describe "Semantic: abstract def" do def foo(x : U) end end - CR + CRYSTAL end it "matches generic module (a bit more complex)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL class Gen(T) end @@ -619,11 +619,11 @@ describe "Semantic: abstract def" do def foo(x : Gen(Int32)) end end - CR + CRYSTAL end it "matches generic return type" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo(T) abstract def foo : T end @@ -633,11 +633,11 @@ describe "Semantic: abstract def" do 1 end end - CR + CRYSTAL end it "errors if missing a return type in subclass of generic subclass" do - assert_error <<-CR, + assert_error <<-CRYSTAL, abstract class Foo(T) abstract def foo : T end @@ -646,12 +646,12 @@ describe "Semantic: abstract def" do def foo end end - CR + CRYSTAL "this method overrides Foo(T)#foo() which has an explicit return type of T.\n\nPlease add an explicit return type (Int32 or a subtype of it) to this method as well." end it "errors if can't find parent return type" do - assert_error <<-CR, + assert_error <<-CRYSTAL, abstract class Foo abstract def foo : Unknown end @@ -660,12 +660,12 @@ describe "Semantic: abstract def" do def foo end end - CR + CRYSTAL "can't resolve return type Unknown" end it "errors if can't find child return type" do - assert_error <<-CR, + assert_error <<-CRYSTAL, abstract class Foo abstract def foo : Int32 end @@ -674,12 +674,12 @@ describe "Semantic: abstract def" do def foo : Unknown end end - CR + CRYSTAL "can't resolve return type Unknown" end it "implements through extend (considers original type for generic lookup) (#8096)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL module ICallable(T) abstract def call(foo : T) end @@ -693,11 +693,11 @@ describe "Semantic: abstract def" do extend ICallable(Int32) extend Moo end - CR + CRYSTAL end it "implements through extend (considers original type for generic lookup) (2) (#8096)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL module ICallable(T) abstract def call(foo : T) end @@ -709,11 +709,11 @@ describe "Semantic: abstract def" do def call(foo : Int32) end end - CR + CRYSTAL end it "can implement even if yield comes later in macro code" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL module Moo abstract def each(& : Int32 -> _) end @@ -729,11 +729,11 @@ describe "Semantic: abstract def" do {% end %} end end - CR + CRYSTAL end it "can implement by block signature even if yield comes later in macro code" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL module Moo abstract def each(& : Int32 -> _) end @@ -747,11 +747,11 @@ describe "Semantic: abstract def" do {% end %} end end - CR + CRYSTAL end it "error shows full signature of block parameter" do - assert_error(<<-CR, "abstract `def Moo#each(& : (Int32 -> _))` must be implemented by Foo") + assert_error(<<-CRYSTAL, "abstract `def Moo#each(& : (Int32 -> _))` must be implemented by Foo") module Moo abstract def each(& : Int32 -> _) end @@ -759,11 +759,11 @@ describe "Semantic: abstract def" do class Foo include Moo end - CR + CRYSTAL end it "doesn't error if implementation have default value" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(x) end @@ -772,7 +772,7 @@ describe "Semantic: abstract def" do def foo(x = 1) end end - CR + CRYSTAL end it "errors if implementation doesn't have default value" do @@ -845,7 +845,7 @@ describe "Semantic: abstract def" do end it "doesn't error if implementation matches keyword argument" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(*, x) end @@ -854,7 +854,7 @@ describe "Semantic: abstract def" do def foo(*, x) end end - CR + CRYSTAL end it "errors if implementation doesn't match keyword argument type" do @@ -872,7 +872,7 @@ describe "Semantic: abstract def" do end it "doesn't error if implementation have keyword arguments in different order" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(*, x : Int32, y : String) end @@ -881,7 +881,7 @@ describe "Semantic: abstract def" do def foo(*, y : String, x : Int32) end end - CR + CRYSTAL end it "errors if implementation has more keyword arguments" do @@ -899,7 +899,7 @@ describe "Semantic: abstract def" do end it "doesn't error if implementation has more keyword arguments with default values" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(*, x) end @@ -908,7 +908,7 @@ describe "Semantic: abstract def" do def foo(*, x, y = 1) end end - CR + CRYSTAL end it "errors if implementation doesn't have a splat" do @@ -940,7 +940,7 @@ describe "Semantic: abstract def" do end it "doesn't error with splat" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(*args) end @@ -949,11 +949,11 @@ describe "Semantic: abstract def" do def foo(*args) end end - CR + CRYSTAL end it "doesn't error with splat and args with default value" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(*args) end @@ -962,11 +962,11 @@ describe "Semantic: abstract def" do def foo(a = 1, *args) end end - CR + CRYSTAL end it "allows arguments to be collapsed into splat" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(a : Int32, b : String) end @@ -975,7 +975,7 @@ describe "Semantic: abstract def" do def foo(*args : Int32 | String) end end - CR + CRYSTAL end it "errors if keyword argument doesn't have the same default value" do @@ -992,7 +992,7 @@ describe "Semantic: abstract def" do end it "allow double splat argument" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(**kargs) end @@ -1001,11 +1001,11 @@ describe "Semantic: abstract def" do def foo(**kargs) end end - CR + CRYSTAL end it "allow double splat when abstract doesn't have it" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo end @@ -1014,7 +1014,7 @@ describe "Semantic: abstract def" do def foo(**kargs) end end - CR + CRYSTAL end it "errors if implementation misses the double splat" do @@ -1044,7 +1044,7 @@ describe "Semantic: abstract def" do end it "allow splat instead of keyword argument" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL abstract class Foo abstract def foo(*, foo) end @@ -1053,7 +1053,7 @@ describe "Semantic: abstract def" do def foo(**kargs) end end - CR + CRYSTAL end it "extra keyword arguments must have compatible type to double splat" do @@ -1103,7 +1103,7 @@ describe "Semantic: abstract def" do describe "implementation is not inherited from supertype" do it "nongeneric class" do - assert_error <<-CR, "abstract `def Abstract#foo()` must be implemented by Concrete" + assert_error <<-CRYSTAL, "abstract `def Abstract#foo()` must be implemented by Concrete" class Supertype def foo; end end @@ -1114,11 +1114,11 @@ describe "Semantic: abstract def" do class Concrete < Abstract end - CR + CRYSTAL end it "generic class" do - assert_error <<-CR, "abstract `def Abstract(T)#foo()` must be implemented by Concrete" + assert_error <<-CRYSTAL, "abstract `def Abstract(T)#foo()` must be implemented by Concrete" class Supertype(T) def foo; end end @@ -1129,11 +1129,11 @@ describe "Semantic: abstract def" do class Concrete(T) < Abstract(T) end - CR + CRYSTAL end it "nongeneric module" do - assert_error <<-CR, "abstract `def Abstract#size()` must be implemented by Concrete" + assert_error <<-CRYSTAL, "abstract `def Abstract#size()` must be implemented by Concrete" module Supertype def size end @@ -1148,11 +1148,11 @@ describe "Semantic: abstract def" do class Concrete include Abstract end - CR + CRYSTAL end it "generic module" do - assert_error <<-CR, "abstract `def Abstract(T)#size()` must be implemented by Concrete(T)" + assert_error <<-CRYSTAL, "abstract `def Abstract(T)#size()` must be implemented by Concrete(T)" module Supertype(T) def size end @@ -1167,7 +1167,7 @@ describe "Semantic: abstract def" do class Concrete(T) include Abstract(T) end - CR + CRYSTAL end end end diff --git a/spec/compiler/semantic/annotation_spec.cr b/spec/compiler/semantic/annotation_spec.cr index 94ea352e4177..249a23c149a0 100644 --- a/spec/compiler/semantic/annotation_spec.cr +++ b/spec/compiler/semantic/annotation_spec.cr @@ -1130,12 +1130,12 @@ describe "Semantic: annotation" do end it "doesn't carry link annotation from lib to fun" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL @[Link("foo")] lib LibFoo fun foo end - CR + CRYSTAL end it "finds annotation in generic parent (#7885)" do @@ -1271,13 +1271,13 @@ describe "Semantic: annotation" do end it "doesn't bleed annotation from class into class variable (#8314)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL annotation Attr; end @[Attr] class Bar @@x = 0 end - CR + CRYSTAL end end diff --git a/spec/compiler/semantic/automatic_cast_spec.cr b/spec/compiler/semantic/automatic_cast_spec.cr index 19273cce90b3..c10eea7e9e19 100644 --- a/spec/compiler/semantic/automatic_cast_spec.cr +++ b/spec/compiler/semantic/automatic_cast_spec.cr @@ -730,7 +730,7 @@ describe "Semantic: automatic cast" do end it "errors when autocast default value doesn't match enum member" do - assert_error <<-CR, + assert_error <<-CRYSTAL, enum Foo FOO end @@ -739,7 +739,7 @@ describe "Semantic: automatic cast" do end foo - CR + CRYSTAL "can't autocast :bar to Foo: no matching enum member" end end diff --git a/spec/compiler/semantic/block_spec.cr b/spec/compiler/semantic/block_spec.cr index 74b2c272d8d0..021ba2a6738a 100644 --- a/spec/compiler/semantic/block_spec.cr +++ b/spec/compiler/semantic/block_spec.cr @@ -1318,7 +1318,7 @@ describe "Block inference" do end it "auto-unpacks tuple, captured empty block" do - assert_no_errors <<-CR, inject_primitives: true + assert_no_errors <<-CRYSTAL, inject_primitives: true def foo(&block : {Int32, Char} -> _) tup = {1, 'a'} block.call tup @@ -1326,7 +1326,7 @@ describe "Block inference" do foo do |x, y| end - CR + CRYSTAL end it "auto-unpacks tuple, captured block with multiple statements" do @@ -1457,18 +1457,18 @@ describe "Block inference" do end it "reports mismatch with generic argument type in output type" do - assert_error(<<-CR, "expected block to return String, not Int32") + assert_error(<<-CRYSTAL, "expected block to return String, not Int32") class Foo(T) def foo(&block : -> T) end end Foo(String).new.foo { 1 } - CR + CRYSTAL end it "reports mismatch with generic argument type in input type" do - assert_error(<<-CR, "argument #1 of yield expected to be String, not Int32") + assert_error(<<-CRYSTAL, "argument #1 of yield expected to be String, not Int32") class Foo(T) def foo(&block : T -> ) yield 1 @@ -1476,7 +1476,7 @@ describe "Block inference" do end Foo(String).new.foo {} - CR + CRYSTAL end it "correctly types unpacked tuple block arg after block (#3339)" do @@ -1522,14 +1522,14 @@ describe "Block inference" do end it "doesn't crash on cleaning up typeof node without dependencies (#8669)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL def foo(&) end foo do typeof(bar) end - CR + CRYSTAL end it "respects block arg restriction when block has a splat parameter (#6473)" do @@ -1565,7 +1565,7 @@ describe "Block inference" do end it "allows underscore in block return type even if the return type can't be computed" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL def foo(& : -> _) yield end @@ -1577,11 +1577,11 @@ describe "Block inference" do end recursive - CR + CRYSTAL end it "doesn't fail with 'already had enclosing call' (#11200)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL def capture(&block) block end @@ -1606,6 +1606,6 @@ describe "Block inference" do foo = Bar(Bool).new.as(Foo) foo.foo - CR + CRYSTAL end end diff --git a/spec/compiler/semantic/case_spec.cr b/spec/compiler/semantic/case_spec.cr index 9cd3f5d2a3e4..e9eff0a92ca4 100644 --- a/spec/compiler/semantic/case_spec.cr +++ b/spec/compiler/semantic/case_spec.cr @@ -648,17 +648,17 @@ describe "semantic: case" do end private def bool_case_eq - <<-CODE + <<-CRYSTAL struct Bool def ===(other) true end end - CODE + CRYSTAL end private def enum_eq - <<-CODE + <<-CRYSTAL struct Enum def ==(other : self) value == other.value @@ -668,5 +668,5 @@ private def enum_eq true end end - CODE + CRYSTAL end diff --git a/spec/compiler/semantic/class_spec.cr b/spec/compiler/semantic/class_spec.cr index 584d89041310..88c3dd474a3f 100644 --- a/spec/compiler/semantic/class_spec.cr +++ b/spec/compiler/semantic/class_spec.cr @@ -1116,7 +1116,7 @@ describe "Semantic: class" do end it "errors if inherits from metaclass" do - assert_error <<-CR, "Foo.class is not a class, it's a metaclass" + assert_error <<-CRYSTAL, "Foo.class is not a class, it's a metaclass" class Foo end @@ -1124,7 +1124,7 @@ describe "Semantic: class" do class Bar < FooClass end - CR + CRYSTAL end it "can use short name for top-level type" do diff --git a/spec/compiler/semantic/closure_spec.cr b/spec/compiler/semantic/closure_spec.cr index f73e1c83d3a0..9fb359f0bb36 100644 --- a/spec/compiler/semantic/closure_spec.cr +++ b/spec/compiler/semantic/closure_spec.cr @@ -617,7 +617,7 @@ describe "Semantic: closure" do end it "doesn't assign all types to metavar if closured but only assigned to once" do - assert_no_errors <<-CR, inject_primitives: true + assert_no_errors <<-CRYSTAL, inject_primitives: true def capture(&block) block end @@ -627,7 +627,7 @@ describe "Semantic: closure" do x &+ 1 end end - CR + CRYSTAL end it "does assign all types to metavar if closured but only assigned to once in a loop" do diff --git a/spec/compiler/semantic/const_spec.cr b/spec/compiler/semantic/const_spec.cr index 5815a11f4123..164a412756e4 100644 --- a/spec/compiler/semantic/const_spec.cr +++ b/spec/compiler/semantic/const_spec.cr @@ -319,7 +319,7 @@ describe "Semantic: const" do "1 + 2", "1 + ZED", "ZED - 1", "ZED * 2", "ZED // 2", "1 &+ ZED", "ZED &- 1", "ZED &* 2"].each do |node| it "doesn't errors if constant depends on another one defined later through method, but constant is simple (#{node})" do - assert_no_errors <<-CR, inject_primitives: true + assert_no_errors <<-CRYSTAL, inject_primitives: true ZED = 10 struct Int32 @@ -337,7 +337,7 @@ describe "Semantic: const" do end CONST1 - CR + CRYSTAL end end @@ -452,19 +452,19 @@ describe "Semantic: const" do end it "errors if using const in proc notation parameter type" do - assert_error <<-CR, "A is not a type, it's a constant" + assert_error <<-CRYSTAL, "A is not a type, it's a constant" A = 1 x : A -> - CR + CRYSTAL end it "errors if using const in proc notation return type" do - assert_error <<-CR, "A is not a type, it's a constant" + assert_error <<-CRYSTAL, "A is not a type, it's a constant" A = 1 x : -> A - CR + CRYSTAL end it "errors if using return inside constant value (#5391)" do diff --git a/spec/compiler/semantic/def_overload_spec.cr b/spec/compiler/semantic/def_overload_spec.cr index 439830c9bfc7..f0e2a93864dc 100644 --- a/spec/compiler/semantic/def_overload_spec.cr +++ b/spec/compiler/semantic/def_overload_spec.cr @@ -1,7 +1,7 @@ require "../../spec_helper" def assert_stricter(params1, params2, args, *, file = __FILE__, line = __LINE__) - assert_type(<<-CR, file: file, line: line, flags: "preview_overload_order") { tuple_of([int32, int32]) } + assert_type(<<-CRYSTAL, file: file, line: line, flags: "preview_overload_order") { tuple_of([int32, int32]) } def foo(#{params1}); 1; end def foo(#{params2}); 'x'; end @@ -11,11 +11,11 @@ def assert_stricter(params1, params2, args, *, file = __FILE__, line = __LINE__) a = foo(#{args}) b = bar(#{args}) {a, b} - CR + CRYSTAL end def assert_unordered(params1, params2, args, *, file = __FILE__, line = __LINE__) - assert_type(<<-CR, file: file, line: line, flags: "preview_overload_order") { tuple_of([int32, int32]) } + assert_type(<<-CRYSTAL, file: file, line: line, flags: "preview_overload_order") { tuple_of([int32, int32]) } def foo(#{params1}); 1; end def foo(#{params2}); 'x'; end @@ -25,7 +25,7 @@ def assert_unordered(params1, params2, args, *, file = __FILE__, line = __LINE__ a = foo(#{args}) b = bar(#{args}) {a, b} - CR + CRYSTAL end describe "Semantic: def overload" do @@ -763,16 +763,16 @@ describe "Semantic: def overload" do end it "does not consider global paths as free variables (1)" do - assert_error <<-CR, "undefined constant ::Foo" + assert_error <<-CRYSTAL, "undefined constant ::Foo" def foo(x : ::Foo) forall Foo end foo(1) - CR + CRYSTAL end it "does not consider global paths as free variables (2)" do - assert_error <<-CR, "expected argument #1 to 'foo' to be Foo, not Int32" + assert_error <<-CRYSTAL, "expected argument #1 to 'foo' to be Foo, not Int32" class Foo end @@ -780,7 +780,7 @@ describe "Semantic: def overload" do end foo(1) - CR + CRYSTAL end it "prefers more specific overload than one with free variables" do @@ -1646,7 +1646,7 @@ describe "Semantic: def overload" do end it "treats single splats with same restriction as equivalent (#12579)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } def foo(*x : Int32) 'a' end @@ -1656,11 +1656,11 @@ describe "Semantic: def overload" do end foo(1) - CR + CRYSTAL end it "treats single splats with same restriction as equivalent (2) (#12579)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } def foo(*x : Int32) 'a' end @@ -1670,7 +1670,7 @@ describe "Semantic: def overload" do end foo(1) - CR + CRYSTAL end end diff --git a/spec/compiler/semantic/def_spec.cr b/spec/compiler/semantic/def_spec.cr index bf758e298dd4..fee65d2f3b5d 100644 --- a/spec/compiler/semantic/def_spec.cr +++ b/spec/compiler/semantic/def_spec.cr @@ -557,10 +557,10 @@ describe "Semantic: def" do end it "points error at name (#6937)" do - ex = assert_error <<-CODE, + ex = assert_error <<-CRYSTAL, 1. foobar - CODE + CRYSTAL "undefined method" ex.line_number.should eq(2) ex.column_number.should eq(3) diff --git a/spec/compiler/semantic/did_you_mean_spec.cr b/spec/compiler/semantic/did_you_mean_spec.cr index 593dcd24160c..cd3f0856ebcb 100644 --- a/spec/compiler/semantic/did_you_mean_spec.cr +++ b/spec/compiler/semantic/did_you_mean_spec.cr @@ -102,14 +102,14 @@ describe "Semantic: did you mean" do end it "doesn't suggest for operator" do - error = assert_error <<-CR + error = assert_error <<-CRYSTAL class Foo def + end end Foo.new.a - CR + CRYSTAL error.to_s.should_not contain("Did you mean") end diff --git a/spec/compiler/semantic/doc_spec.cr b/spec/compiler/semantic/doc_spec.cr index e46f7a9df640..9b38339b6485 100644 --- a/spec/compiler/semantic/doc_spec.cr +++ b/spec/compiler/semantic/doc_spec.cr @@ -419,7 +419,7 @@ describe "Semantic: doc" do end it "stores doc for macro defined in macro call" do - result = semantic <<-CR, wants_doc: true + result = semantic <<-CRYSTAL, wants_doc: true macro def_foo macro foo end @@ -427,7 +427,7 @@ describe "Semantic: doc" do # Hello def_foo - CR + CRYSTAL program = result.program foo = program.macros.not_nil!["foo"].first foo.doc.should eq("Hello") diff --git a/spec/compiler/semantic/enum_spec.cr b/spec/compiler/semantic/enum_spec.cr index fd9083c888f4..ee4ac5a7a0e5 100644 --- a/spec/compiler/semantic/enum_spec.cr +++ b/spec/compiler/semantic/enum_spec.cr @@ -476,23 +476,23 @@ describe "Semantic: enum" do end it "doesn't overflow when going from negative to zero (#7874)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL enum Nums Zero = -2 One Two end - CR + CRYSTAL end it "doesn't overflow on flags member (#7877)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL @[Flags] enum Filter A = 1 << 29 B end - CR + CRYSTAL end it "doesn't visit enum members generated by macros twice (#10104)" do diff --git a/spec/compiler/semantic/exception_spec.cr b/spec/compiler/semantic/exception_spec.cr index 776af95c7121..1b5328e5edeb 100644 --- a/spec/compiler/semantic/exception_spec.cr +++ b/spec/compiler/semantic/exception_spec.cr @@ -695,7 +695,7 @@ describe "Semantic: exception" do end it "gets a non-nilable type if all rescue are unreachable (#8751)" do - assert_no_errors <<-CR, inject_primitives: true + assert_no_errors <<-CRYSTAL, inject_primitives: true while true begin foo = 1 @@ -707,7 +707,7 @@ describe "Semantic: exception" do foo &+ 2 end - CR + CRYSTAL end it "correctly types variable assigned inside nested exception handler (#9769)" do diff --git a/spec/compiler/semantic/generic_class_spec.cr b/spec/compiler/semantic/generic_class_spec.cr index bb2e27e62a0b..5f809229f96e 100644 --- a/spec/compiler/semantic/generic_class_spec.cr +++ b/spec/compiler/semantic/generic_class_spec.cr @@ -655,14 +655,14 @@ describe "Semantic: generic class" do end it "doesn't duplicate overload on generic class with class method (#2385)" do - error = assert_error <<-CR + error = assert_error <<-CRYSTAL class Foo(T) def self.foo(x : Int32) end end Foo(String).foo(35.7) - CR + CRYSTAL error.to_s.lines.count(" - Foo(T).foo(x : Int32)").should eq(1) end diff --git a/spec/compiler/semantic/instance_var_spec.cr b/spec/compiler/semantic/instance_var_spec.cr index 08827918646c..3e268bf7dd8e 100644 --- a/spec/compiler/semantic/instance_var_spec.cr +++ b/spec/compiler/semantic/instance_var_spec.cr @@ -776,7 +776,7 @@ describe "Semantic: instance var" do end it "infers type from proc literal with return type" do - assert_type(<<-CR) { proc_of([int32, bool, string]) } + assert_type(<<-CRYSTAL) { proc_of([int32, bool, string]) } class Foo def initialize @x = ->(x : Int32, y : Bool) : String { "" } @@ -788,7 +788,7 @@ describe "Semantic: instance var" do end Foo.new.x - CR + CRYSTAL end it "infers type from new expression" do @@ -5482,7 +5482,7 @@ describe "Semantic: instance var" do end it "errors when overriding inherited instance variable with incompatible type" do - assert_error <<-CR, "instance variable '@a' of A must be Int32, not (Char | Int32)" + assert_error <<-CRYSTAL, "instance variable '@a' of A must be Int32, not (Char | Int32)" class A @a = 1 end @@ -5490,11 +5490,11 @@ describe "Semantic: instance var" do class B < A @a = 'a' end - CR + CRYSTAL end it "accepts overriding inherited instance variable with compatible type" do - semantic <<-CR + semantic <<-CRYSTAL class A @a = 1 end @@ -5502,7 +5502,7 @@ describe "Semantic: instance var" do class B < A @a = 2 end - CR + CRYSTAL end it "looks up return type restriction in defining type, not instantiated type (#11961)" do @@ -5537,7 +5537,7 @@ describe "Semantic: instance var" do end it "looks up self restriction in instantiated type, not defined type" do - assert_type(<<-CR) { types["Foo2"] } + assert_type(<<-CRYSTAL) { types["Foo2"] } class Foo1 def foo : self self @@ -5558,7 +5558,7 @@ describe "Semantic: instance var" do end Bar.new.bar - CR + CRYSTAL end it "inferrs Proc(Void) to Proc(Nil)" do @@ -5680,7 +5680,7 @@ describe "Semantic: instance var" do end it "accepts module and module, with definitions" do - semantic <<-CR + semantic <<-CRYSTAL module M @a = 1 end @@ -5693,11 +5693,11 @@ describe "Semantic: instance var" do include N include M end - CR + CRYSTAL end it "accepts module and module, with declarations" do - semantic <<-CR + semantic <<-CRYSTAL module M @a : Int32? end @@ -5710,13 +5710,13 @@ describe "Semantic: instance var" do include N include M end - CR + CRYSTAL end end context "with incompatible type" do it "module and class, with definitions" do - assert_error <<-CR, "instance variable '@a' of A, with B < A, is already declared as Int32 (trying to re-declare it in B as Char)" + assert_error <<-CRYSTAL, "instance variable '@a' of A, with B < A, is already declared as Int32 (trying to re-declare it in B as Char)" module M @a = 'a' end @@ -5728,11 +5728,11 @@ describe "Semantic: instance var" do class B < A include M end - CR + CRYSTAL end it "module and class, with declarations" do - assert_error <<-CR, "instance variable '@a' of A, with B < A, is already declared as Int32 (trying to re-declare it in B as Char)" + assert_error <<-CRYSTAL, "instance variable '@a' of A, with B < A, is already declared as Int32 (trying to re-declare it in B as Char)" module M @a : Char = 'a' end @@ -5744,11 +5744,11 @@ describe "Semantic: instance var" do class B < A include M end - CR + CRYSTAL end it "errors module and module, with definitions" do - assert_error <<-CR, "instance variable '@a' of B must be Char, not (Char | Int32)" + assert_error <<-CRYSTAL, "instance variable '@a' of B must be Char, not (Char | Int32)" module M @a = 'c' end @@ -5761,11 +5761,11 @@ describe "Semantic: instance var" do include N include M end - CR + CRYSTAL end it "errors module and module, with declarations" do - assert_error <<-CR, "instance variable '@a' of B must be Int32, not (Char | Int32)" + assert_error <<-CRYSTAL, "instance variable '@a' of B must be Int32, not (Char | Int32)" module M @a : Char = 'c' end @@ -5778,7 +5778,7 @@ describe "Semantic: instance var" do include N include M end - CR + CRYSTAL end end end diff --git a/spec/compiler/semantic/lib_spec.cr b/spec/compiler/semantic/lib_spec.cr index 5908a341d999..70c2a7b11819 100644 --- a/spec/compiler/semantic/lib_spec.cr +++ b/spec/compiler/semantic/lib_spec.cr @@ -377,20 +377,20 @@ describe "Semantic: lib" do end it "warns if @[Link(static: true)] is specified" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, @[Link("foo", static: true)] lib Foo end - CR + CRYSTAL "warning in line 1\nWarning: specifying static linking for individual libraries is deprecated" end it "warns if Link annotations use positional arguments" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, @[Link("foo", "bar")] lib Foo end - CR + CRYSTAL "warning in line 1\nWarning: using non-named arguments for Link annotations is deprecated" end diff --git a/spec/compiler/semantic/macro_spec.cr b/spec/compiler/semantic/macro_spec.cr index f7bd191801ea..4e75f621387b 100644 --- a/spec/compiler/semantic/macro_spec.cr +++ b/spec/compiler/semantic/macro_spec.cr @@ -2,13 +2,13 @@ require "../../spec_helper" describe "Semantic: macro" do it "types macro" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo 1 end foo - CR + CRYSTAL end it "errors if macro uses undefined variable" do @@ -17,7 +17,7 @@ describe "Semantic: macro" do end it "types macro def" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } class Foo def foo : Int32 {{ @type }} @@ -26,11 +26,11 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "errors if macro def type not found" do - assert_error <<-CR, "undefined constant Foo" + assert_error <<-CRYSTAL, "undefined constant Foo" class Baz def foo : Foo {{ @type }} @@ -38,11 +38,11 @@ describe "Semantic: macro" do end Baz.new.foo - CR + CRYSTAL end it "errors if macro def type doesn't match found" do - assert_error <<-CR, "method Foo#foo must return Int32 but it is returning Char" + assert_error <<-CRYSTAL, "method Foo#foo must return Int32 but it is returning Char" class Foo def foo : Int32 {{ @type}} @@ -51,7 +51,7 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "allows subclasses of return type for macro def" do @@ -127,7 +127,7 @@ describe "Semantic: macro" do Baz.new.foobar.foo }).to_i.should eq(2) - assert_error(<<-CR, "method Bar#bar must return Foo(String) but it is returning Foo(Int32)") + assert_error(<<-CRYSTAL, "method Bar#bar must return Foo(String) but it is returning Foo(Int32)") class Foo(T) def initialize(@foo : T) end @@ -141,11 +141,11 @@ describe "Semantic: macro" do end Bar.new.bar - CR + CRYSTAL end it "allows union return types for macro def" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } class Foo def foo : String | Int32 {{ @type }} @@ -154,11 +154,11 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "types macro def that calls another method" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } def bar_baz 1 end @@ -173,11 +173,11 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "types macro def that calls another method inside a class" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } class Foo def bar_baz 1 @@ -192,11 +192,11 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "types macro def that calls another method inside a class" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } class Foo def foo : Int32 {{ @type }} @@ -213,11 +213,11 @@ describe "Semantic: macro" do end Bar.new.foo - CR + CRYSTAL end it "types macro def with argument" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } class Foo def foo(x) : Int32 {{ @type }} @@ -226,11 +226,11 @@ describe "Semantic: macro" do end Foo.new.foo(1) - CR + CRYSTAL end it "expands macro with block" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo {{yield}} end @@ -242,11 +242,11 @@ describe "Semantic: macro" do end bar - CR + CRYSTAL end it "expands macro with block and argument to yield" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo {{yield 1}} end @@ -258,56 +258,56 @@ describe "Semantic: macro" do end bar - CR + CRYSTAL end it "errors if find macros but wrong arguments" do - assert_error(<<-CR, "wrong number of arguments for macro 'foo' (given 1, expected 0)", inject_primitives: true) + assert_error(<<-CRYSTAL, "wrong number of arguments for macro 'foo' (given 1, expected 0)", inject_primitives: true) macro foo 1 end foo(1) - CR + CRYSTAL end it "executes raise inside macro" do - ex = assert_error(<<-CR, "OH NO") + ex = assert_error(<<-CRYSTAL, "OH NO") macro foo {{ raise "OH NO" }} end foo - CR + CRYSTAL ex.to_s.should_not contain("expanding macro") end it "executes raise inside macro, with node (#5669)" do - ex = assert_error(<<-CR, "OH") + ex = assert_error(<<-CRYSTAL, "OH") macro foo(x) {{ x.raise "OH\nNO" }} end foo(1) - CR + CRYSTAL ex.to_s.should contain "NO" ex.to_s.should_not contain("expanding macro") end it "executes raise inside macro, with empty message (#8631)" do - assert_error(<<-CR, "") + assert_error(<<-CRYSTAL, "") macro foo {{ raise "" }} end foo - CR + CRYSTAL end it "can specify tuple as return type" do - assert_type(<<-CR) { tuple_of([int32, int32] of Type) } + assert_type(<<-CRYSTAL) { tuple_of([int32, int32] of Type) } class Foo def foo : {Int32, Int32} {{ @type }} @@ -316,11 +316,11 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "allows specifying self as macro def return type" do - assert_type(<<-CR) { types["Foo"] } + assert_type(<<-CRYSTAL) { types["Foo"] } class Foo def foo : self {{ @type }} @@ -329,11 +329,11 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "allows specifying self as macro def return type (2)" do - assert_type(<<-CR) { types["Bar"] } + assert_type(<<-CRYSTAL) { types["Bar"] } class Foo def foo : self {{ @type }} @@ -345,7 +345,7 @@ describe "Semantic: macro" do end Bar.new.foo - CR + CRYSTAL end it "preserves correct self in restriction when macro def is to be instantiated in subtypes (#5044)" do @@ -407,27 +407,27 @@ describe "Semantic: macro" do end it "errors if non-existent named arg" do - assert_error(<<-CR, "no parameter named 'y'") + assert_error(<<-CRYSTAL, "no parameter named 'y'") macro foo(x = 1) {{x}} + 1 end foo y: 2 - CR + CRYSTAL end it "errors if named arg already specified" do - assert_error(<<-CR, "argument for parameter 'x' already specified") + assert_error(<<-CRYSTAL, "argument for parameter 'x' already specified") macro foo(x = 1) {{x}} + 1 end foo 2, x: 2 - CR + CRYSTAL end it "finds macro in included module" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } module Moo macro bar 1 @@ -443,11 +443,11 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "errors when trying to define def inside def with macro expansion" do - assert_error(<<-CR, "can't define def inside def") + assert_error(<<-CRYSTAL, "can't define def inside def") macro foo def bar; end end @@ -457,11 +457,11 @@ describe "Semantic: macro" do end baz - CR + CRYSTAL end it "gives precise location info when doing yield inside macro" do - assert_error(<<-CR, "in line 6") + assert_error(<<-CRYSTAL, "in line 6") macro foo {{yield}} end @@ -469,11 +469,11 @@ describe "Semantic: macro" do foo do 1 + 'a' end - CR + CRYSTAL end it "transforms with {{yield}} and call" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo bar({{yield}}) end @@ -489,11 +489,11 @@ describe "Semantic: macro" do foo do baz end - CR + CRYSTAL end it "can return class type in macro def" do - assert_type(<<-CR) { types["Int32"].metaclass } + assert_type(<<-CRYSTAL) { types["Int32"].metaclass } class Foo def foo : Int32.class {{ @type }} @@ -502,11 +502,11 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "can return virtual class type in macro def" do - assert_type(<<-CR, inject_primitives: true) { types["Foo"].metaclass.virtual_type } + assert_type(<<-CRYSTAL, inject_primitives: true) { types["Foo"].metaclass.virtual_type } class Foo end @@ -521,24 +521,24 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "can't define new variables (#466)" do - error = assert_error <<-CR + error = assert_error <<-CRYSTAL macro foo hello = 1 end foo hello - CR + CRYSTAL error.to_s.should_not contain("did you mean") end it "finds macro in included generic module" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } module Moo(T) macro moo 1 @@ -554,11 +554,11 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "finds macro in inherited generic class" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } class Moo(T) macro moo 1 @@ -572,21 +572,21 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "doesn't die on && inside if (bug)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo 1 && 2 end foo ? 3 : 4 - CR + CRYSTAL end it "checks if macro expansion returns (#821)" do - assert_type(<<-CR) { nilable symbol } + assert_type(<<-CRYSTAL) { nilable symbol } macro pass return :pass end @@ -597,43 +597,43 @@ describe "Semantic: macro" do end me - CR + CRYSTAL end it "errors if declares macro inside if" do - assert_error(<<-CR, "can't declare macro dynamically") + assert_error(<<-CRYSTAL, "can't declare macro dynamically") if 1 == 2 macro foo; end end - CR + CRYSTAL end it "allows declaring class with macro if" do - assert_type(<<-CR) { types["Foo"] } + assert_type(<<-CRYSTAL) { types["Foo"] } {% if true %} class Foo; end {% end %} Foo.new - CR + CRYSTAL end it "allows declaring class with macro for" do - assert_type(<<-CR) { types["Foo"] } + assert_type(<<-CRYSTAL) { types["Foo"] } {% for i in 0..0 %} class Foo; end {% end %} Foo.new - CR + CRYSTAL end it "allows declaring class with inline macro expression (#1333)" do - assert_type(<<-CR) { types["Foo"] } + assert_type(<<-CRYSTAL) { types["Foo"] } {{ "class Foo; end".id }} Foo.new - CR + CRYSTAL end it "errors if requires inside class through macro expansion" do @@ -652,7 +652,7 @@ describe "Semantic: macro" do end it "errors if requires inside if through macro expansion" do - assert_error(<<-CR, "can't require dynamically") + assert_error(<<-CRYSTAL, "can't require dynamically") macro req require "bar" end @@ -660,11 +660,11 @@ describe "Semantic: macro" do if 1 == 2 req end - CR + CRYSTAL end it "can define constant via macro included" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } module Mod macro included CONST = 1 @@ -674,11 +674,11 @@ describe "Semantic: macro" do include Mod CONST - CR + CRYSTAL end it "errors if applying protected modifier to macro" do - assert_error(<<-CR, "can only use 'private' for macros") + assert_error(<<-CRYSTAL, "can only use 'private' for macros") class Foo protected macro foo 1 @@ -686,11 +686,11 @@ describe "Semantic: macro" do end Foo.foo - CR + CRYSTAL end it "expands macro with break inside while (#1852)" do - assert_type(<<-CR) { nil_type } + assert_type(<<-CRYSTAL) { nil_type } macro test foo = "bar" break @@ -699,11 +699,11 @@ describe "Semantic: macro" do while true test end - CR + CRYSTAL end it "can access variable inside macro expansion (#2057)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo x end @@ -715,11 +715,11 @@ describe "Semantic: macro" do method do |x| foo end - CR + CRYSTAL end it "declares variable for macro with out" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } lib LibFoo fun foo(x : Int32*) end @@ -730,44 +730,44 @@ describe "Semantic: macro" do LibFoo.foo(out z) some_macro - CR + CRYSTAL end it "show macro trace in errors (1)" do - ex = assert_error(<<-CR, "Error: expanding macro") + ex = assert_error(<<-CRYSTAL, "Error: expanding macro") macro foo Bar end foo - CR + CRYSTAL ex.to_s.should contain "error in line 5" end it "show macro trace in errors (2)" do - ex = assert_error(<<-CR, "Error: expanding macro") + ex = assert_error(<<-CRYSTAL, "Error: expanding macro") {% begin %} Bar {% end %} - CR + CRYSTAL ex.to_s.should contain "error in line 1" end it "errors if using macro that is defined later" do - assert_error(<<-CR, "macro 'foo' must be defined before this point but is defined later") + assert_error(<<-CRYSTAL, "macro 'foo' must be defined before this point but is defined later") class Bar foo end macro foo end - CR + CRYSTAL end it "looks up argument types in macro owner, not in subclass (#2395)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } struct Nil def method(x : Problem) 0 @@ -796,11 +796,11 @@ describe "Semantic: macro" do end Moo::Bar.new.method(Problem.new) - CR + CRYSTAL end it "doesn't error when adding macro call to constant (#2457)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo end @@ -814,75 +814,75 @@ describe "Semantic: macro" do coco do foo end - CR + CRYSTAL end it "errors if named arg matches single splat parameter" do - assert_error(<<-CR, "no parameter named 'x'") + assert_error(<<-CRYSTAL, "no parameter named 'x'") macro foo(*y) end foo x: 1, y: 2 - CR + CRYSTAL end it "errors if named arg matches splat parameter" do - assert_error(<<-CR, "wrong number of arguments for macro 'foo' (given 0, expected 1+)") + assert_error(<<-CRYSTAL, "wrong number of arguments for macro 'foo' (given 0, expected 1+)") macro foo(x, *y) end foo x: 1, y: 2 - CR + CRYSTAL end it "says missing argument because positional args don't match past splat" do - assert_error(<<-CR, "missing argument: z") + assert_error(<<-CRYSTAL, "missing argument: z") macro foo(x, *y, z) end foo 1, 2 - CR + CRYSTAL end it "allows named args after splat" do - assert_type(<<-CR) { tuple_of([tuple_of([int32]), char]) } + assert_type(<<-CRYSTAL) { tuple_of([tuple_of([int32]), char]) } macro foo(*y, x) { {{y}}, {{x}} } end foo 1, x: 'a' - CR + CRYSTAL end it "errors if missing one argument" do - assert_error(<<-CR, "missing argument: z") + assert_error(<<-CRYSTAL, "missing argument: z") macro foo(x, y, z) end foo x: 1, y: 2 - CR + CRYSTAL end it "errors if missing two arguments" do - assert_error(<<-CR, "missing arguments: x, z") + assert_error(<<-CRYSTAL, "missing arguments: x, z") macro foo(x, y, z) end foo y: 2 - CR + CRYSTAL end it "doesn't include parameters with default values in missing arguments error" do - assert_error(<<-CR, "missing argument: z") + assert_error(<<-CRYSTAL, "missing argument: z") macro foo(x, z, y = 1) end foo(x: 1) - CR + CRYSTAL end it "solves macro expression arguments before macro expansion (type)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo(x) {% if x.is_a?(TypeNode) && x.name == "String" %} 1 @@ -892,11 +892,11 @@ describe "Semantic: macro" do end foo({{ String }}) - CR + CRYSTAL end it "solves macro expression arguments before macro expansion (constant)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo(x) {% if x.is_a?(NumberLiteral) && x == 1 %} 1 @@ -907,11 +907,11 @@ describe "Semantic: macro" do CONST = 1 foo({{ CONST }}) - CR + CRYSTAL end it "solves named macro expression arguments before macro expansion (type) (#2423)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo(x) {% if x.is_a?(TypeNode) && x.name == "String" %} 1 @@ -921,11 +921,11 @@ describe "Semantic: macro" do end foo(x: {{ String }}) - CR + CRYSTAL end it "solves named macro expression arguments before macro expansion (constant) (#2423)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo(x) {% if x.is_a?(NumberLiteral) && x == 1 %} 1 @@ -936,11 +936,11 @@ describe "Semantic: macro" do CONST = 1 foo(x: {{ CONST }}) - CR + CRYSTAL end it "finds generic type argument of included module" do - assert_type(<<-CR) { int32.metaclass } + assert_type(<<-CRYSTAL) { int32.metaclass } module Bar(T) def t {{ T }} @@ -952,11 +952,11 @@ describe "Semantic: macro" do end Foo(Int32).new.t - CR + CRYSTAL end it "finds generic type argument of included module with self" do - assert_type(<<-CR) { generic_class("Foo", int32).metaclass } + assert_type(<<-CRYSTAL) { generic_class("Foo", int32).metaclass } module Bar(T) def t {{ T }} @@ -968,11 +968,11 @@ describe "Semantic: macro" do end Foo(Int32).new.t - CR + CRYSTAL end it "finds free type vars" do - assert_type(<<-CR) { tuple_of([int32.metaclass, string.metaclass]) } + assert_type(<<-CRYSTAL) { tuple_of([int32.metaclass, string.metaclass]) } module Foo(T) def self.foo(foo : U) forall U { {{ T }}, {{ U }} } @@ -980,41 +980,41 @@ describe "Semantic: macro" do end Foo(Int32).foo("foo") - CR + CRYSTAL end it "gets named arguments in double splat" do - assert_type(<<-CR) { named_tuple_of({"x": string, "y": bool}) } + assert_type(<<-CRYSTAL) { named_tuple_of({"x": string, "y": bool}) } macro foo(**options) {{options}} end foo x: "foo", y: true - CR + CRYSTAL end it "uses splat and double splat" do - assert_type(<<-CR) { tuple_of([tuple_of([int32, char]), named_tuple_of({"x": string, "y": bool})]) } + assert_type(<<-CRYSTAL) { tuple_of([tuple_of([int32, char]), named_tuple_of({"x": string, "y": bool})]) } macro foo(*args, **options) { {{args}}, {{options}} } end foo 1, 'a', x: "foo", y: true - CR + CRYSTAL end it "double splat and regular args" do - assert_type(<<-CR) { tuple_of([int32, bool, named_tuple_of({"w": char, "z": string})]) } + assert_type(<<-CRYSTAL) { tuple_of([int32, bool, named_tuple_of({"w": char, "z": string})]) } macro foo(x, y, **options) { {{x}}, {{y}}, {{options}} } end foo 1, w: 'a', y: true, z: "z" - CR + CRYSTAL end it "declares multi-assign vars for macro" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro id(x, y) {{x}} {{y}} @@ -1023,11 +1023,11 @@ describe "Semantic: macro" do a, b = 1, 2 id(a, b) 1 - CR + CRYSTAL end it "declares rescue variable inside for macro" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro id(x) {{x}} end @@ -1038,49 +1038,49 @@ describe "Semantic: macro" do end 1 - CR + CRYSTAL end it "matches with default value after splat" do - assert_type(<<-CR) { tuple_of([int32, tuple_of([char]), bool]) } + assert_type(<<-CRYSTAL) { tuple_of([int32, tuple_of([char]), bool]) } macro foo(x, *y, z = true) { {{x}}, {{y}}, {{z}} } end foo 1, 'a' - CR + CRYSTAL end it "uses bare *" do - assert_type(<<-CR) { tuple_of([int32, char]) } + assert_type(<<-CRYSTAL) { tuple_of([int32, char]) } macro foo(x, *, y) { {{x}}, {{y}} } end foo 10, y: 'a' - CR + CRYSTAL end it "uses bare *, doesn't let more args" do - assert_error(<<-CR, "wrong number of arguments for macro 'foo' (given 2, expected 1)") + assert_error(<<-CRYSTAL, "wrong number of arguments for macro 'foo' (given 2, expected 1)") macro foo(x, *, y) end foo 10, 20, y: 30 - CR + CRYSTAL end it "uses bare *, doesn't let more args" do - assert_error(<<-CR, "no overload matches") + assert_error(<<-CRYSTAL, "no overload matches") def foo(x, *, y) end foo 10, 20, y: 30 - CR + CRYSTAL end it "finds macro through alias (#2706)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } module Moo macro bar 1 @@ -1090,11 +1090,11 @@ describe "Semantic: macro" do alias Foo = Moo Foo.bar - CR + CRYSTAL end it "can override macro (#2773)" do - assert_type(<<-CR) { char } + assert_type(<<-CRYSTAL) { char } macro foo 1 end @@ -1104,31 +1104,31 @@ describe "Semantic: macro" do end foo - CR + CRYSTAL end it "works inside proc literal (#2984)" do - assert_type(<<-CR, inject_primitives: true) { int32 } + assert_type(<<-CRYSTAL, inject_primitives: true) { int32 } macro foo 1 end ->{ foo }.call - CR + CRYSTAL end it "finds var in proc for macros" do - assert_type(<<-CR, inject_primitives: true) { int32 } + assert_type(<<-CRYSTAL, inject_primitives: true) { int32 } macro foo(x) {{x}} end ->(x : Int32) { foo(x) }.call(1) - CR + CRYSTAL end it "applies visibility modifier only to first level" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo class Foo def self.foo @@ -1140,11 +1140,11 @@ describe "Semantic: macro" do private foo Foo.foo - CR + CRYSTAL end it "gives correct error when method is invoked but macro exists at the same scope" do - assert_error(<<-CR, "undefined method 'foo'") + assert_error(<<-CRYSTAL, "undefined method 'foo'") macro foo(x) end @@ -1152,23 +1152,23 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "uses uninitialized variable with macros" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } macro foo(x) {{x}} end a = uninitialized Int32 foo(a) - CR + CRYSTAL end describe "skip_file macro directive" do it "skips expanding the rest of the current file" do - res = semantic(<<-CR) + res = semantic(<<-CRYSTAL) class A end @@ -1176,14 +1176,14 @@ describe "Semantic: macro" do class B end - CR + CRYSTAL res.program.types.has_key?("A").should be_true res.program.types.has_key?("B").should be_false end it "skips file inside an if macro expression" do - res = semantic(<<-CR) + res = semantic(<<-CRYSTAL) class A end @@ -1195,7 +1195,7 @@ describe "Semantic: macro" do class B end - CR + CRYSTAL res.program.types.has_key?("A").should be_true res.program.types.has_key?("B").should be_false @@ -1205,7 +1205,7 @@ describe "Semantic: macro" do end it "finds method before macro (#236)" do - assert_type(<<-CR) { char } + assert_type(<<-CRYSTAL) { char } macro global 1 end @@ -1221,11 +1221,11 @@ describe "Semantic: macro" do end Foo.new.bar - CR + CRYSTAL end it "finds macro and method at the same scope" do - assert_type(<<-CR) { tuple_of [int32, char] } + assert_type(<<-CRYSTAL) { tuple_of [int32, char] } macro global(x) 1 end @@ -1235,11 +1235,11 @@ describe "Semantic: macro" do end {global(1), global(1, 2)} - CR + CRYSTAL end it "finds macro and method at the same scope inside included module" do - assert_type(<<-CR) { tuple_of [int32, char] } + assert_type(<<-CRYSTAL) { tuple_of [int32, char] } module Moo macro global(x) 1 @@ -1259,11 +1259,11 @@ describe "Semantic: macro" do end Foo.new.main - CR + CRYSTAL end it "finds macro in included module at class level (#4639)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } module Moo macro foo def self.bar @@ -1279,11 +1279,11 @@ describe "Semantic: macro" do end Foo.bar - CR + CRYSTAL end it "finds macro in module in Object" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } class Object macro foo def self.bar @@ -1297,11 +1297,11 @@ describe "Semantic: macro" do end Moo.bar - CR + CRYSTAL end it "finds metaclass instance of instance method (#4739)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } class Parent macro foo def self.bar @@ -1320,11 +1320,11 @@ describe "Semantic: macro" do end GrandChild.bar - CR + CRYSTAL end it "finds metaclass instance of instance method (#4639)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } module Include macro foo def foo @@ -1344,11 +1344,11 @@ describe "Semantic: macro" do end Foo.new.foo - CR + CRYSTAL end it "can lookup type parameter when macro is called inside class (#5343)" do - assert_type(<<-CR) { int32.metaclass } + assert_type(<<-CRYSTAL) { int32.metaclass } class Foo(T) macro foo {{T}} @@ -1364,11 +1364,11 @@ describe "Semantic: macro" do end Bar.foo - CR + CRYSTAL end it "cannot lookup type defined in caller class" do - assert_error(<<-CR, "undefined constant Baz") + assert_error(<<-CRYSTAL, "undefined constant Baz") class Foo macro foo {{Baz}} @@ -1385,11 +1385,11 @@ describe "Semantic: macro" do end Bar.foo - CR + CRYSTAL end it "clones default value before expanding" do - assert_type(<<-CR) { nil_type } + assert_type(<<-CRYSTAL) { nil_type } FOO = {} of String => String? macro foo(x = {} of String => String) @@ -1400,11 +1400,11 @@ describe "Semantic: macro" do foo foo {{ FOO["foo"] }} - CR + CRYSTAL end it "does macro verbatim inside macro" do - assert_type(<<-CR) { types["Bar"].metaclass } + assert_type(<<-CRYSTAL) { types["Bar"].metaclass } class Foo macro inherited {% verbatim do %} @@ -1419,19 +1419,19 @@ describe "Semantic: macro" do end Bar.new.foo - CR + CRYSTAL end it "does macro verbatim outside macro" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } {% verbatim do %} 1 {% end %} - CR + CRYSTAL end it "evaluates yield expression (#2924)" do - assert_type(<<-CR) { string } + assert_type(<<-CRYSTAL) { string } macro a(b) {{yield b}} end @@ -1439,19 +1439,19 @@ describe "Semantic: macro" do a("foo") do |c| {{c}} end - CR + CRYSTAL end it "finds generic in macro code" do - assert_type(<<-CR) { array_of(string).metaclass } + assert_type(<<-CRYSTAL) { array_of(string).metaclass } {% begin %} {{ Array(String) }} {% end %} - CR + CRYSTAL end it "finds generic in macro code using free var" do - assert_type(<<-CR) { array_of(int32).metaclass } + assert_type(<<-CRYSTAL) { array_of(int32).metaclass } class Foo(T) def self.foo {% begin %} @@ -1461,11 +1461,11 @@ describe "Semantic: macro" do end Foo(Int32).foo - CR + CRYSTAL end it "expands multiline macro expression in verbatim (#6643)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } {% verbatim do %} {{ if true @@ -1475,11 +1475,11 @@ describe "Semantic: macro" do end }} {% end %} - CR + CRYSTAL end it "can use macro in instance var initializer (#7666)" do - assert_type(<<-CR) { string } + assert_type(<<-CRYSTAL) { string } class Foo macro m "test" @@ -1493,11 +1493,11 @@ describe "Semantic: macro" do end Foo.new.x - CR + CRYSTAL end it "can use macro in instance var initializer (just assignment) (#7666)" do - assert_type(<<-CR) { string } + assert_type(<<-CRYSTAL) { string } class Foo macro m "test" @@ -1511,11 +1511,11 @@ describe "Semantic: macro" do end Foo.new.x - CR + CRYSTAL end it "shows correct error message in macro expansion (#7083)" do - assert_error(<<-CR, "can't instantiate abstract class Foo") + assert_error(<<-CRYSTAL, "can't instantiate abstract class Foo") abstract class Foo {% begin %} def self.new @@ -1525,19 +1525,19 @@ describe "Semantic: macro" do end Foo.new - CR + CRYSTAL end it "doesn't crash on syntax error inside macro (regression, #8038)" do expect_raises(Crystal::SyntaxException, "unterminated array literal") do - semantic(<<-CR) + semantic(<<-CRYSTAL) {% begin %}[{% end %} - CR + CRYSTAL end end it "has correct location after expanding assignment after instance var" do - result = semantic <<-CR + result = semantic <<-CRYSTAL macro foo(x) # 1 @{{x}} # 2 # 3 @@ -1548,14 +1548,14 @@ describe "Semantic: macro" do class Foo # 8 foo(x = 1) # 9 end - CR + CRYSTAL method = result.program.types["Foo"].lookup_first_def("bar", false).not_nil! method.location.not_nil!.expanded_location.not_nil!.line_number.should eq(9) end it "executes OpAssign (#9356)" do - assert_type(<<-CR) { int32 } + assert_type(<<-CRYSTAL) { int32 } {% begin %} {% a = nil %} {% a ||= 1 %} @@ -1565,25 +1565,25 @@ describe "Semantic: macro" do 'a' {% end %} {% end %} - CR + CRYSTAL end it "executes MultiAssign" do - assert_type(<<-CR) { tuple_of([int32, int32] of Type) } + assert_type(<<-CRYSTAL) { tuple_of([int32, int32] of Type) } {% begin %} {% a, b = 1, 2 %} { {{a}}, {{b}} } {% end %} - CR + CRYSTAL end it "executes MultiAssign with ArrayLiteral value" do - assert_type(<<-CR) { tuple_of([int32, int32] of Type) } + assert_type(<<-CRYSTAL) { tuple_of([int32, int32] of Type) } {% begin %} {% xs = [1, 2] %} {% a, b = xs %} { {{a}}, {{b}} } {% end %} - CR + CRYSTAL end end diff --git a/spec/compiler/semantic/metaclass_spec.cr b/spec/compiler/semantic/metaclass_spec.cr index aa2457a3e255..d38b454a80f3 100644 --- a/spec/compiler/semantic/metaclass_spec.cr +++ b/spec/compiler/semantic/metaclass_spec.cr @@ -260,7 +260,7 @@ describe "Semantic: metaclass" do end it "can't reopen as struct" do - assert_error <<-CR, "Bar is not a struct, it's a metaclass" + assert_error <<-CRYSTAL, "Bar is not a struct, it's a metaclass" class Foo end @@ -268,11 +268,11 @@ describe "Semantic: metaclass" do struct Bar end - CR + CRYSTAL end it "can't reopen as module" do - assert_error <<-CR, "Bar is not a module, it's a metaclass" + assert_error <<-CRYSTAL, "Bar is not a module, it's a metaclass" class Foo end @@ -280,6 +280,6 @@ describe "Semantic: metaclass" do module Bar end - CR + CRYSTAL end end diff --git a/spec/compiler/semantic/multi_assign_spec.cr b/spec/compiler/semantic/multi_assign_spec.cr index 58549c8900c2..9a4fb430af9c 100644 --- a/spec/compiler/semantic/multi_assign_spec.cr +++ b/spec/compiler/semantic/multi_assign_spec.cr @@ -13,7 +13,7 @@ describe "Semantic: multi assign" do end it "doesn't error if assigning non-Indexable (#11414)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL class Foo def [](index) end @@ -24,11 +24,11 @@ describe "Semantic: multi assign" do end a, b, c = Foo.new - CR + CRYSTAL end it "errors if assigning non-Indexable to splat (#11414)" do - assert_error <<-CR, "right-hand side of one-to-many assignment must be an Indexable, not Foo" + assert_error <<-CRYSTAL, "right-hand side of one-to-many assignment must be an Indexable, not Foo" require "prelude" class Foo @@ -41,7 +41,7 @@ describe "Semantic: multi assign" do end a, *b, c = Foo.new - CR + CRYSTAL end end @@ -94,7 +94,7 @@ describe "Semantic: multi assign" do end it "errors if assigning non-Indexable (#11414)" do - assert_error <<-CR, "right-hand side of one-to-many assignment must be an Indexable, not Foo", flags: "strict_multi_assign" + assert_error <<-CRYSTAL, "right-hand side of one-to-many assignment must be an Indexable, not Foo", flags: "strict_multi_assign" require "prelude" class Foo @@ -107,11 +107,11 @@ describe "Semantic: multi assign" do end a, b, c = Foo.new - CR + CRYSTAL end it "errors if assigning non-Indexable to splat (#11414)" do - assert_error <<-CR, "right-hand side of one-to-many assignment must be an Indexable, not Foo", flags: "strict_multi_assign" + assert_error <<-CRYSTAL, "right-hand side of one-to-many assignment must be an Indexable, not Foo", flags: "strict_multi_assign" require "prelude" class Foo @@ -124,12 +124,12 @@ describe "Semantic: multi assign" do end a, *b, c = Foo.new - CR + CRYSTAL end end it "can pass splat variable at top-level to macros (#11596)" do - assert_type(<<-CR) { tuple_of [int32, int32, int32] } + assert_type(<<-CRYSTAL) { tuple_of [int32, int32, int32] } struct Tuple def self.new(*args) args @@ -142,6 +142,6 @@ describe "Semantic: multi assign" do a, *b, c = 1, 2, 3, 4, 5 foo(b) - CR + CRYSTAL end end diff --git a/spec/compiler/semantic/pointer_spec.cr b/spec/compiler/semantic/pointer_spec.cr index 978c20992c0e..6ca9b320235e 100644 --- a/spec/compiler/semantic/pointer_spec.cr +++ b/spec/compiler/semantic/pointer_spec.cr @@ -99,21 +99,21 @@ describe "Semantic: pointer" do end it "detects recursive pointerof expansion (3)" do - assert_error <<-CR, "recursive pointerof expansion" + assert_error <<-CRYSTAL, "recursive pointerof expansion" x = {1} x = pointerof(x) - CR + CRYSTAL end it "detects recursive pointerof expansion (4)" do - assert_error <<-CR, "recursive pointerof expansion" + assert_error <<-CRYSTAL, "recursive pointerof expansion" x = 1 x = {pointerof(x)} - CR + CRYSTAL end it "doesn't crash if pointerof expansion type has generic splat parameter (#11808)" do - assert_type(<<-CR) { pointer_of(union_of int32, generic_class("Foo", string)) } + assert_type(<<-CRYSTAL) { pointer_of(union_of int32, generic_class("Foo", string)) } class Foo(*T) end @@ -121,7 +121,7 @@ describe "Semantic: pointer" do pointer = pointerof(x) x = Foo(String).new pointer - CR + CRYSTAL end it "can assign nil to void pointer" do @@ -182,7 +182,7 @@ describe "Semantic: pointer" do end it "can assign pointerof virtual type (#8216)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL class Base end @@ -193,7 +193,7 @@ describe "Semantic: pointer" do x : Pointer(Base) x = pointerof(u) - CR + CRYSTAL end it "errors with non-matching generic value with value= (#10211)" do diff --git a/spec/compiler/semantic/private_spec.cr b/spec/compiler/semantic/private_spec.cr index e2c589e97f58..4f339370b02f 100644 --- a/spec/compiler/semantic/private_spec.cr +++ b/spec/compiler/semantic/private_spec.cr @@ -517,7 +517,7 @@ describe "Semantic: private" do end it "doesn't inherit visibility from class node in macro hook (#8794)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL module M1 macro included include M2 @@ -559,6 +559,6 @@ describe "Semantic: private" do end Foo.new(1) - CR + CRYSTAL end end diff --git a/spec/compiler/semantic/proc_spec.cr b/spec/compiler/semantic/proc_spec.cr index b1673df3b60b..9e4179477ba6 100644 --- a/spec/compiler/semantic/proc_spec.cr +++ b/spec/compiler/semantic/proc_spec.cr @@ -834,25 +834,25 @@ describe "Semantic: proc" do end it "allows metaclass in procs" do - assert_type(<<-CR) { proc_of(types["Foo"].metaclass, types["Foo"]) } + assert_type(<<-CRYSTAL) { proc_of(types["Foo"].metaclass, types["Foo"]) } class Foo end ->(x : Foo.class) { x.new } - CR + CRYSTAL end it "allows metaclass in proc return types" do - assert_type(<<-CR) { proc_of(types["Foo"].metaclass) } + assert_type(<<-CRYSTAL) { proc_of(types["Foo"].metaclass) } class Foo end -> : Foo.class { Foo } - CR + CRYSTAL end it "allows metaclass in captured block" do - assert_type(<<-CR) { proc_of(types["Foo"].metaclass, types["Foo"]) } + assert_type(<<-CRYSTAL) { proc_of(types["Foo"].metaclass, types["Foo"]) } class Foo end @@ -861,11 +861,11 @@ describe "Semantic: proc" do end foo { |x| x.new } - CR + CRYSTAL end it "allows metaclass in proc pointer" do - assert_type(<<-CR) { proc_of(types["Foo"].metaclass, types["Foo"]) } + assert_type(<<-CRYSTAL) { proc_of(types["Foo"].metaclass, types["Foo"]) } class Foo end @@ -874,11 +874,11 @@ describe "Semantic: proc" do end ->foo(Foo.class) - CR + CRYSTAL end it "allows metaclass in proc notation parameter type" do - assert_type(<<-CR) { proc_of(types["Foo"].metaclass, nil_type) } + assert_type(<<-CRYSTAL) { proc_of(types["Foo"].metaclass, nil_type) } class Foo end @@ -886,16 +886,16 @@ describe "Semantic: proc" do x : Foo.class -> = Proc(Foo.class, Nil).new { } x - CR + CRYSTAL end it "allows metaclass in proc notation return type" do - assert_type(<<-CR) { proc_of(types["Foo"].metaclass) } + assert_type(<<-CRYSTAL) { proc_of(types["Foo"].metaclass) } class Foo end x : -> Foo.class = ->{ Foo } x - CR + CRYSTAL end it "..." do @@ -1318,11 +1318,11 @@ describe "Semantic: proc" do end private def proc_new - <<-CODE + <<-CRYSTAL struct Proc def self.new(&block : self) block end end - CODE + CRYSTAL end diff --git a/spec/compiler/semantic/recursive_struct_check_spec.cr b/spec/compiler/semantic/recursive_struct_check_spec.cr index 29535516b24d..fd8f70541e72 100644 --- a/spec/compiler/semantic/recursive_struct_check_spec.cr +++ b/spec/compiler/semantic/recursive_struct_check_spec.cr @@ -186,13 +186,13 @@ describe "Semantic: recursive struct check" do end it "errors on private recursive type" do - assert_error <<-CR, "recursive struct Test detected" + assert_error <<-CRYSTAL, "recursive struct Test detected" private struct Test def initialize(@test : Test?) end end Test.new(Test.new(nil)) - CR + CRYSTAL end end diff --git a/spec/compiler/semantic/restrictions_augmenter_spec.cr b/spec/compiler/semantic/restrictions_augmenter_spec.cr index 8fb358295e06..7d55c7cf1258 100644 --- a/spec/compiler/semantic/restrictions_augmenter_spec.cr +++ b/spec/compiler/semantic/restrictions_augmenter_spec.cr @@ -12,23 +12,23 @@ end private def it_augments_for_ivar(ivar_type : String, expected_type : String, file = __FILE__, line = __LINE__) it "augments #{ivar_type}", file, line do - before = <<-BEFORE + before = <<-CRYSTAL class Foo @x : #{ivar_type} def initialize(value) @x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL class Foo @x : #{ivar_type} def initialize(value : #{expected_type}) @x = value end end - AFTER + CRYSTAL expect_augment before, after end @@ -57,7 +57,7 @@ describe "Semantic: restrictions augmenter" do it_augments_for_ivar "Enumerable(Int32).class", "::Enumerable(::Int32).class" it "augments relative public type" do - before = <<-BEFORE + before = <<-CRYSTAL class Foo class Bar class Baz @@ -70,9 +70,9 @@ describe "Semantic: restrictions augmenter" do @x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL class Foo class Bar class Baz @@ -83,13 +83,13 @@ describe "Semantic: restrictions augmenter" do @x = value end end - AFTER + CRYSTAL expect_augment before, after end it "augments relative private type" do - before = <<-BEFORE + before = <<-CRYSTAL class Foo private class Bar class Baz @@ -102,9 +102,9 @@ describe "Semantic: restrictions augmenter" do @x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL class Foo private class Bar class Baz @@ -115,13 +115,13 @@ describe "Semantic: restrictions augmenter" do @x = value end end - AFTER + CRYSTAL expect_augment before, after end it "augments relative private type in same namespace" do - before = <<-BEFORE + before = <<-CRYSTAL class Foo private class Bar end @@ -132,9 +132,9 @@ describe "Semantic: restrictions augmenter" do end end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL class Foo private class Bar end @@ -145,57 +145,57 @@ describe "Semantic: restrictions augmenter" do end end end - AFTER + CRYSTAL expect_augment before, after end it "augments generic uninstantiated type" do - before = <<-BEFORE + before = <<-CRYSTAL class Foo(T) @x : Array(T) def initialize(value) @x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL class Foo(T) @x : Array(T) def initialize(value : ::Array(T)) @x = value end end - AFTER + CRYSTAL expect_augment before, after end it "augments for class var" do - before = <<-BEFORE + before = <<-CRYSTAL class Foo @@x = 1 def self.set(value) @@x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL class Foo @@x = 1 def self.set(value : ::Int32) @@x = value end end - AFTER + CRYSTAL expect_augment before, after end it "doesn't augment if assigned inside if" do - expect_no_augment <<-CODE + expect_no_augment <<-CRYSTAL class Foo @x : Int32 def initialize(value) @@ -204,11 +204,11 @@ describe "Semantic: restrictions augmenter" do end end end - CODE + CRYSTAL end it "doesn't augment if assigned inside while" do - expect_no_augment <<-CODE + expect_no_augment <<-CRYSTAL class Foo @x : Int32 def initialize(value) @@ -217,11 +217,11 @@ describe "Semantic: restrictions augmenter" do end end end - CODE + CRYSTAL end it "doesn't augment if assigned inside block" do - expect_no_augment <<-CODE + expect_no_augment <<-CRYSTAL def foo yield end @@ -233,44 +233,44 @@ describe "Semantic: restrictions augmenter" do end end end - CODE + CRYSTAL end it "doesn't augment if the no_restrictions_augmenter flag is present" do - expect_no_augment <<-CODE, flags: "no_restrictions_augmenter" + expect_no_augment <<-CRYSTAL, flags: "no_restrictions_augmenter" class Foo @x : Int32 def initialize(value) @x = value end end - CODE + CRYSTAL end it "augments recursive alias type (#12134)" do - before = <<-BEFORE + before = <<-CRYSTAL alias BasicObject = Array(BasicObject) | Hash(String, BasicObject) class Foo def initialize(value = Hash(String, BasicObject).new) @x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL alias BasicObject = Array(BasicObject) | Hash(String, BasicObject) class Foo def initialize(value : ::Hash(::String, ::BasicObject) = Hash(String, BasicObject).new) @x = value end end - AFTER + CRYSTAL expect_augment before, after end it "augments typedef" do - before = <<-BEFORE + before = <<-CRYSTAL lib LibFoo type X = Void* end @@ -280,9 +280,9 @@ describe "Semantic: restrictions augmenter" do @x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL lib LibFoo type X = Void* end @@ -292,13 +292,13 @@ describe "Semantic: restrictions augmenter" do @x = value end end - AFTER + CRYSTAL expect_augment before, after end it "augments virtual type" do - before = <<-BEFORE + before = <<-CRYSTAL class A end class B < A @@ -309,9 +309,9 @@ describe "Semantic: restrictions augmenter" do @x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL class A end class B < A @@ -322,13 +322,13 @@ describe "Semantic: restrictions augmenter" do @x = value end end - AFTER + CRYSTAL expect_augment before, after end it "augments virtual metaclass type" do - before = <<-BEFORE + before = <<-CRYSTAL class A end class B < A @@ -339,9 +339,9 @@ describe "Semantic: restrictions augmenter" do @x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL class A end class B < A @@ -352,35 +352,35 @@ describe "Semantic: restrictions augmenter" do @x = value end end - AFTER + CRYSTAL expect_augment before, after end it "augments type splat" do - before = <<-BEFORE + before = <<-CRYSTAL class Foo(T) @x : Array(*T) def initialize(value) @x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL class Foo(T) @x : Array(*T) def initialize(value : ::Array(*T)) @x = value end end - AFTER + CRYSTAL expect_augment before, after end it "doesn't crash on macro that yields and defines class (#12142)" do - before = <<-BEFORE + before = <<-CRYSTAL macro foo {{yield}} end @@ -394,9 +394,9 @@ describe "Semantic: restrictions augmenter" do @x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL macro foo {{ yield }} end @@ -408,29 +408,29 @@ describe "Semantic: restrictions augmenter" do @x = value end end - AFTER + CRYSTAL expect_augment before, after end it "augments for Union(*T) (#12435)" do - before = <<-BEFORE + before = <<-CRYSTAL class Foo(*T) @x : Union(*T) def initialize(value) @x = value end end - BEFORE + CRYSTAL - after = <<-AFTER + after = <<-CRYSTAL class Foo(*T) @x : Union(*T) def initialize(value : ::Union(*T)) @x = value end end - AFTER + CRYSTAL expect_augment before, after end diff --git a/spec/compiler/semantic/restrictions_spec.cr b/spec/compiler/semantic/restrictions_spec.cr index 4336b142b8ca..baf69551da21 100644 --- a/spec/compiler/semantic/restrictions_spec.cr +++ b/spec/compiler/semantic/restrictions_spec.cr @@ -249,23 +249,23 @@ describe "Restrictions" do {% end %} it "doesn't error if path is undefined and method is not called (1) (#12516)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL def foo(a : Int32.class) end def foo(a : Foo) end - CR + CRYSTAL end it "doesn't error if path is undefined and method is not called (2) (#12516)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL def foo(a : Foo) end def foo(a : Int32.class) end - CR + CRYSTAL end end @@ -550,7 +550,7 @@ describe "Restrictions" do describe "Path vs NumberLiteral" do it "inserts constant before number literal of same value with generic arguments" do - assert_type(<<-CR) { bool } + assert_type(<<-CRYSTAL) { bool } X = 1 class Foo(N) @@ -565,11 +565,11 @@ describe "Restrictions" do end foo(Foo(1).new) - CR + CRYSTAL end it "inserts number literal before constant of same value with generic arguments" do - assert_type(<<-CR) { bool } + assert_type(<<-CRYSTAL) { bool } X = 1 class Foo(N) @@ -584,13 +584,13 @@ describe "Restrictions" do end foo(Foo(1).new) - CR + CRYSTAL end end describe "free variables" do it "inserts path before free variable with same name" do - assert_type(<<-CR) { tuple_of([char, bool]) } + assert_type(<<-CRYSTAL) { tuple_of([char, bool]) } def foo(x : Int32) forall Int32 true end @@ -600,11 +600,11 @@ describe "Restrictions" do end {foo(1), foo("")} - CR + CRYSTAL end it "keeps path before free variable with same name" do - assert_type(<<-CR) { tuple_of([char, bool]) } + assert_type(<<-CRYSTAL) { tuple_of([char, bool]) } def foo(x : Int32) 'a' end @@ -614,12 +614,12 @@ describe "Restrictions" do end {foo(1), foo("")} - CR + CRYSTAL end # TODO: enable in #12784 pending "inserts constant before free variable with same name" do - assert_type(<<-CR) { tuple_of([char, bool]) } + assert_type(<<-CRYSTAL) { tuple_of([char, bool]) } class Foo(T); end X = 1 @@ -633,11 +633,11 @@ describe "Restrictions" do end {foo(Foo(1).new), foo(Foo(2).new)} - CR + CRYSTAL end pending "keeps constant before free variable with same name" do - assert_type(<<-CR) { tuple_of([char, bool]) } + assert_type(<<-CRYSTAL) { tuple_of([char, bool]) } class Foo(T); end X = 1 @@ -651,11 +651,11 @@ describe "Restrictions" do end {foo(Foo(1).new), foo(Foo(2).new)} - CR + CRYSTAL end it "inserts path before free variable even if free var resolves to a more specialized type" do - assert_type(<<-CR) { tuple_of([int32, int32, bool]) } + assert_type(<<-CRYSTAL) { tuple_of([int32, int32, bool]) } class Foo end @@ -671,11 +671,11 @@ describe "Restrictions" do end {foo(Foo.new), foo(Bar.new), foo('a')} - CR + CRYSTAL end it "keeps path before free variable even if free var resolves to a more specialized type" do - assert_type(<<-CR) { tuple_of([int32, int32, bool]) } + assert_type(<<-CRYSTAL) { tuple_of([int32, int32, bool]) } class Foo end @@ -691,13 +691,13 @@ describe "Restrictions" do end {foo(Foo.new), foo(Bar.new), foo('a')} - CR + CRYSTAL end end describe "Union" do it "handles redefinitions (1) (#12330)" do - assert_type(<<-CR) { bool } + assert_type(<<-CRYSTAL) { bool } def foo(x : Int32 | String) 'a' end @@ -707,11 +707,11 @@ describe "Restrictions" do end foo(1) - CR + CRYSTAL end it "handles redefinitions (2) (#12330)" do - assert_type(<<-CR) { bool } + assert_type(<<-CRYSTAL) { bool } def foo(x : Int32 | String) 'a' end @@ -721,11 +721,11 @@ describe "Restrictions" do end foo(1) - CR + CRYSTAL end it "orders union before generic (#12330)" do - assert_type(<<-CR) { bool } + assert_type(<<-CRYSTAL) { bool } module Foo(T) end @@ -746,7 +746,7 @@ describe "Restrictions" do end foo(Bar1.new) - CR + CRYSTAL end end end @@ -1158,16 +1158,16 @@ describe "Restrictions" do end it "errors if using Tuple with named args" do - assert_error <<-CR, "can only instantiate NamedTuple with named arguments" + assert_error <<-CRYSTAL, "can only instantiate NamedTuple with named arguments" def foo(x : Tuple(a: Int32)) end foo({1}) - CR + CRYSTAL end it "doesn't error if using Tuple with no args" do - assert_type(<<-CR) { tuple_of([] of Type) } + assert_type(<<-CRYSTAL) { tuple_of([] of Type) } def foo(x : Tuple()) x end @@ -1177,20 +1177,20 @@ describe "Restrictions" do end foo(bar) - CR + CRYSTAL end it "errors if using NamedTuple with positional args" do - assert_error <<-CR, "can only instantiate NamedTuple with named arguments" + assert_error <<-CRYSTAL, "can only instantiate NamedTuple with named arguments" def foo(x : NamedTuple(Int32)) end foo({a: 1}) - CR + CRYSTAL end it "doesn't error if using NamedTuple with no args" do - assert_type(<<-CR) { named_tuple_of({} of String => Type) } + assert_type(<<-CRYSTAL) { named_tuple_of({} of String => Type) } def foo(x : NamedTuple()) x end @@ -1200,6 +1200,6 @@ describe "Restrictions" do end foo(bar) - CR + CRYSTAL end end diff --git a/spec/compiler/semantic/return_spec.cr b/spec/compiler/semantic/return_spec.cr index e23f3c4c503a..ddf1b4b9281e 100644 --- a/spec/compiler/semantic/return_spec.cr +++ b/spec/compiler/semantic/return_spec.cr @@ -183,7 +183,7 @@ describe "Semantic: return" do end it "can use non-type free var in return type (#6543)" do - assert_type(<<-CR) { generic_class "Foo", 1.int32 } + assert_type(<<-CRYSTAL) { generic_class "Foo", 1.int32 } class Foo(A) end @@ -192,11 +192,11 @@ describe "Semantic: return" do end foo(Foo(1).new) - CR + CRYSTAL end it "can use non-type free var in return type (2) (#6543)" do - assert_type(<<-CR) { generic_class "Matrix", 3.int32, 4.int32 } + assert_type(<<-CRYSTAL) { generic_class "Matrix", 3.int32, 4.int32 } class Matrix(N, M) def *(other : Matrix(M, P)) : Matrix(N, P) forall P Matrix(N, P).new @@ -204,11 +204,11 @@ describe "Semantic: return" do end Matrix(3, 2).new * Matrix(2, 4).new - CR + CRYSTAL end it "errors if non-type free var cannot be inferred" do - assert_error <<-CR, "undefined constant P" + assert_error <<-CRYSTAL, "undefined constant P" class Foo(A) end @@ -217,7 +217,7 @@ describe "Semantic: return" do end foo(Foo(1).new) - CR + CRYSTAL end it "forms a tuple from multiple return values" do diff --git a/spec/compiler/semantic/sizeof_spec.cr b/spec/compiler/semantic/sizeof_spec.cr index 3828099bade0..1cdab6a6ed3b 100644 --- a/spec/compiler/semantic/sizeof_spec.cr +++ b/spec/compiler/semantic/sizeof_spec.cr @@ -75,12 +75,12 @@ describe "Semantic: sizeof" do end it "gives error if using instance_sizeof on a metaclass" do - assert_error <<-CR, "instance_sizeof can only be used with a class, but Foo.class is a metaclass" + assert_error <<-CRYSTAL, "instance_sizeof can only be used with a class, but Foo.class is a metaclass" class Foo end instance_sizeof(Foo.class) - CR + CRYSTAL end it "gives error if using instance_sizeof on a generic type without type vars" do diff --git a/spec/compiler/semantic/splat_spec.cr b/spec/compiler/semantic/splat_spec.cr index ec7a829558c4..f4582d622be9 100644 --- a/spec/compiler/semantic/splat_spec.cr +++ b/spec/compiler/semantic/splat_spec.cr @@ -812,14 +812,14 @@ describe "Semantic: splat" do end it "doesn't shift a call's location" do - result = semantic <<-CR + result = semantic <<-CRYSTAL class Foo def bar(x) bar(*{"test"}) end end Foo.new.bar("test") - CR + CRYSTAL program = result.program a_typ = program.types["Foo"].as(NonGenericClassType) a_def = a_typ.def_instances.values[0] diff --git a/spec/compiler/semantic/virtual_metaclass_spec.cr b/spec/compiler/semantic/virtual_metaclass_spec.cr index db65875ef487..b2e003a094c9 100644 --- a/spec/compiler/semantic/virtual_metaclass_spec.cr +++ b/spec/compiler/semantic/virtual_metaclass_spec.cr @@ -152,7 +152,7 @@ describe "Semantic: virtual metaclass" do end it "restricts virtual metaclass to Class (#11376)" do - assert_type(<<-CR) { nilable types["Foo"].virtual_type.metaclass } + assert_type(<<-CRYSTAL) { nilable types["Foo"].virtual_type.metaclass } class Foo end @@ -161,6 +161,6 @@ describe "Semantic: virtual metaclass" do x = Foo || Bar x if x.is_a?(Class) - CR + CRYSTAL end end diff --git a/spec/compiler/semantic/virtual_spec.cr b/spec/compiler/semantic/virtual_spec.cr index 76a8d6834f87..15a87f7ab62a 100644 --- a/spec/compiler/semantic/virtual_spec.cr +++ b/spec/compiler/semantic/virtual_spec.cr @@ -135,7 +135,7 @@ describe "Semantic: virtual" do end it "works with restriction alpha" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL require "prelude" class Foo @@ -154,7 +154,7 @@ describe "Semantic: virtual" do a = [nil, Foo.new, Bar.new, Baz.new] a.push(Baz.new || Ban.new) - CR + CRYSTAL end it "doesn't check cover for subclasses" do diff --git a/spec/compiler/semantic/visibility_modifiers_spec.cr b/spec/compiler/semantic/visibility_modifiers_spec.cr index b3a509db9be2..e6ce6ae9317f 100644 --- a/spec/compiler/semantic/visibility_modifiers_spec.cr +++ b/spec/compiler/semantic/visibility_modifiers_spec.cr @@ -414,7 +414,7 @@ describe "Visibility modifiers" do end it "handles virtual types (#8561)" do - assert_no_errors <<-CR + assert_no_errors <<-CRYSTAL module Namespace class Foo protected def foo @@ -439,6 +439,6 @@ describe "Visibility modifiers" do end Namespace::Baz.new.bar - CR + CRYSTAL end end diff --git a/spec/compiler/semantic/warnings_spec.cr b/spec/compiler/semantic/warnings_spec.cr index 26b509d14aad..c9b606b36a35 100644 --- a/spec/compiler/semantic/warnings_spec.cr +++ b/spec/compiler/semantic/warnings_spec.cr @@ -3,7 +3,7 @@ require "../spec_helper" describe "Semantic: warnings" do describe "deprecated annotations" do it "detects deprecated annotations" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, @[Deprecated] annotation Foo; end @@ -11,12 +11,12 @@ describe "Semantic: warnings" do def bar; end bar - CR + CRYSTAL "warning in line 2\nWarning: Deprecated annotation Foo." end it "detects deprecated namespaced annotations" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, module MyNamespace @[Deprecated] annotation Foo; end @@ -26,36 +26,36 @@ describe "Semantic: warnings" do def bar; end bar - CR + CRYSTAL "warning in line 3\nWarning: Deprecated annotation MyNamespace::Foo." end end describe "deprecated methods" do it "detects top-level deprecated methods" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] def foo end foo - CR + CRYSTAL "warning in line 5\nWarning: Deprecated top-level foo. Do not use me" end it "deprecation reason is optional" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, @[Deprecated] def foo end foo - CR + CRYSTAL "warning in line 5\nWarning: Deprecated top-level foo." end it "detects deprecated instance methods" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, class Foo @[Deprecated("Do not use me")] def m @@ -63,12 +63,12 @@ describe "Semantic: warnings" do end Foo.new.m - CR + CRYSTAL "warning in line 7\nWarning: Deprecated Foo#m. Do not use me" end it "detects deprecated class methods" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, class Foo @[Deprecated("Do not use me")] def self.m @@ -76,12 +76,12 @@ describe "Semantic: warnings" do end Foo.m - CR + CRYSTAL "warning in line 7\nWarning: Deprecated Foo.m. Do not use me" end it "detects deprecated generic instance methods" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, class Foo(T) @[Deprecated("Do not use me")] def m @@ -89,12 +89,12 @@ describe "Semantic: warnings" do end Foo(Int32).new.m - CR + CRYSTAL "warning in line 7\nWarning: Deprecated Foo(Int32)#m. Do not use me" end it "detects deprecated generic class methods" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, class Foo(T) @[Deprecated("Do not use me")] def self.m @@ -102,12 +102,12 @@ describe "Semantic: warnings" do end Foo(Int32).m - CR + CRYSTAL "warning in line 7\nWarning: Deprecated Foo(Int32).m. Do not use me" end it "detects deprecated module methods" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, module Foo @[Deprecated("Do not use me")] def self.m @@ -115,23 +115,23 @@ describe "Semantic: warnings" do end Foo.m - CR + CRYSTAL "warning in line 7\nWarning: Deprecated Foo.m. Do not use me" end it "detects deprecated methods with named arguments" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, @[Deprecated] def foo(*, a) end foo(a: 2) - CR + CRYSTAL "warning in line 5\nWarning: Deprecated top-level foo:a." end it "detects deprecated initialize" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, class Foo @[Deprecated] def initialize @@ -139,12 +139,12 @@ describe "Semantic: warnings" do end Foo.new - CR + CRYSTAL "warning in line 7\nWarning: Deprecated Foo.new." end it "detects deprecated initialize with named arguments" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, class Foo @[Deprecated] def initialize(*, a) @@ -152,12 +152,12 @@ describe "Semantic: warnings" do end Foo.new(a: 2) - CR + CRYSTAL "warning in line 7\nWarning: Deprecated Foo.new:a." end it "informs warnings once per call site location (a)" do - warning_failures = warnings_result <<-CR + warning_failures = warnings_result <<-CRYSTAL class Foo @[Deprecated("Do not use me")] def m @@ -170,12 +170,12 @@ describe "Semantic: warnings" do Foo.new.b Foo.new.b - CR + CRYSTAL warning_failures.size.should eq(1) end it "informs warnings once per call site location (b)" do - warning_failures = warnings_result <<-CR + warning_failures = warnings_result <<-CRYSTAL class Foo @[Deprecated("Do not use me")] def m @@ -184,13 +184,13 @@ describe "Semantic: warnings" do Foo.new.m Foo.new.m - CR + CRYSTAL warning_failures.size.should eq(2) end it "informs warnings once per yield" do - warning_failures = warnings_result <<-CR + warning_failures = warnings_result <<-CRYSTAL class Foo @[Deprecated("Do not use me")] def m @@ -203,13 +203,13 @@ describe "Semantic: warnings" do end twice { Foo.new.m } - CR + CRYSTAL warning_failures.size.should eq(1) end it "informs warnings once per target type" do - warning_failures = warnings_result <<-CR + warning_failures = warnings_result <<-CRYSTAL class Foo(T) @[Deprecated("Do not use me")] def m @@ -222,7 +222,7 @@ describe "Semantic: warnings" do Foo(Int32).new.b Foo(Int64).new.b - CR + CRYSTAL warning_failures.size.should eq(2) end @@ -240,13 +240,13 @@ describe "Semantic: warnings" do output_filename = File.join(path, "main") Dir.cd(path) do - File.write main_filename, <<-CR + File.write main_filename, <<-CRYSTAL require "./lib/foo" bar foo - CR - File.write File.join(path, "lib", "foo.cr"), <<-CR + CRYSTAL + File.write File.join(path, "lib", "foo.cr"), <<-CRYSTAL @[Deprecated("Do not use me")] def foo end @@ -254,7 +254,7 @@ describe "Semantic: warnings" do def bar foo end - CR + CRYSTAL compiler = create_spec_compiler compiler.warnings.level = :all @@ -268,29 +268,29 @@ describe "Semantic: warnings" do end it "errors if invalid argument type" do - assert_error <<-CR, + assert_error <<-CRYSTAL, @[Deprecated(42)] def foo end - CR + CRYSTAL "first argument must be a String" end it "errors if too many arguments" do - assert_error <<-CR, + assert_error <<-CRYSTAL, @[Deprecated("Do not use me", "extra arg")] def foo end - CR + CRYSTAL "wrong number of deprecated annotation arguments (given 2, expected 1)" end it "errors if invalid named arguments" do - assert_error <<-CR, + assert_error <<-CRYSTAL, @[Deprecated(invalid: "Do not use me")] def foo end - CR + CRYSTAL "too many named arguments (given 1, expected maximum 0)" end end @@ -466,27 +466,27 @@ describe "Semantic: warnings" do describe "deprecated constants" do it "detects deprecated constants" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] FOO = 1 FOO - CR + CRYSTAL "warning in line 4\nWarning: Deprecated FOO. Do not use me" end it "detects deprecated constants inside macros" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] FOO = 1 {% FOO %} - CR + CRYSTAL "warning in line 4\nWarning: Deprecated FOO. Do not use me" end it "detects deprecated constants in type declarations (1)" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] FOO = 1 @@ -495,12 +495,12 @@ describe "Semantic: warnings" do class Bar < Foo(FOO) end - CR + CRYSTAL "warning in line 7\nWarning: Deprecated FOO. Do not use me" end it "detects deprecated constants in type declarations (2)" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] FOO = 1 @@ -510,12 +510,12 @@ describe "Semantic: warnings" do class Bar include Foo(FOO) end - CR + CRYSTAL "warning in line 8\nWarning: Deprecated FOO. Do not use me" end it "detects deprecated constants in type declarations (3)" do - assert_warning <<-CR, + assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] FOO = 1 @@ -523,14 +523,14 @@ describe "Semantic: warnings" do end alias Bar = Foo(FOO) - CR + CRYSTAL "warning in line 7\nWarning: Deprecated FOO. Do not use me" end end describe "abstract def positional parameter name mismatch" do it "detects mismatch with single parameter" do - assert_warning <<-CR, "warning in line 6\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" + assert_warning <<-CRYSTAL, "warning in line 6\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" abstract class Foo abstract def foo(x) end @@ -538,11 +538,11 @@ describe "Semantic: warnings" do class Bar < Foo def foo(y); end end - CR + CRYSTAL end it "detects mismatch within many parameters" do - assert_warning <<-CR, "warning in line 6\nWarning: positional parameter 'e' corresponds to parameter 'c' of the overridden method" + assert_warning <<-CRYSTAL, "warning in line 6\nWarning: positional parameter 'e' corresponds to parameter 'c' of the overridden method" abstract class Foo abstract def foo(a, b, c, d) end @@ -550,11 +550,11 @@ describe "Semantic: warnings" do class Bar < Foo def foo(a, b, e, d); end end - CR + CRYSTAL end it "detects multiple mismatches" do - warnings_result(<<-CR).size.should eq(2) + warnings_result(<<-CRYSTAL).size.should eq(2) abstract class Foo abstract def foo(src, dst) end @@ -562,11 +562,11 @@ describe "Semantic: warnings" do class Bar < Foo def foo(dst, src); end end - CR + CRYSTAL end it "respects external names of positional parameters (1)" do - assert_warning <<-CR, "warning in line 6\nWarning: positional parameter 'a' corresponds to parameter 'b' of the overridden method" + assert_warning <<-CRYSTAL, "warning in line 6\nWarning: positional parameter 'a' corresponds to parameter 'b' of the overridden method" abstract class Foo abstract def foo(b) end @@ -574,11 +574,11 @@ describe "Semantic: warnings" do class Bar < Foo def foo(a b); end end - CR + CRYSTAL end it "respects external names of positional parameters (2)" do - assert_warning <<-CR, "warning in line 6\nWarning: positional parameter 'b' corresponds to parameter 'a' of the overridden method" + assert_warning <<-CRYSTAL, "warning in line 6\nWarning: positional parameter 'b' corresponds to parameter 'a' of the overridden method" abstract class Foo abstract def foo(a b) end @@ -586,11 +586,11 @@ describe "Semantic: warnings" do class Bar < Foo def foo(b); end end - CR + CRYSTAL end it "doesn't warn if external parameter name matches (1)" do - warnings_result(<<-CR).should be_empty + warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(a) end @@ -598,11 +598,11 @@ describe "Semantic: warnings" do class Bar < Foo def foo(a b); end end - CR + CRYSTAL end it "doesn't warn if external parameter name matches (2)" do - warnings_result(<<-CR).should be_empty + warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(a b) end @@ -610,11 +610,11 @@ describe "Semantic: warnings" do class Bar < Foo def foo(a c); end end - CR + CRYSTAL end it "doesn't compare positional parameters to single splat" do - warnings_result(<<-CR).should be_empty + warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(x) end @@ -622,11 +622,11 @@ describe "Semantic: warnings" do class Bar < Foo def foo(*y); end end - CR + CRYSTAL end it "doesn't compare single splats" do - warnings_result(<<-CR).should be_empty + warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(*x) end @@ -634,11 +634,11 @@ describe "Semantic: warnings" do class Bar < Foo def foo(*y); end end - CR + CRYSTAL end it "informs warnings once per matching overload (1)" do - assert_warning <<-CR, "warning in line 6\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" + assert_warning <<-CRYSTAL, "warning in line 6\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" abstract class Foo abstract def foo(x : Int32) end @@ -647,11 +647,11 @@ describe "Semantic: warnings" do def foo(y : Int32 | Char); end def foo(x : Int32 | String); end end - CR + CRYSTAL end it "informs warnings once per matching overload (2)" do - warnings_result(<<-CR).size.should eq(2) + warnings_result(<<-CRYSTAL).size.should eq(2) abstract class Foo abstract def foo(x : Int32) end @@ -660,12 +660,12 @@ describe "Semantic: warnings" do def foo(y : Int32 | Char); end def foo(z : Int32 | String); end end - CR + CRYSTAL end describe "stops warning after implementation with matching parameters is found (#12150)" do it "exact match" do - warnings_result(<<-CR).should be_empty + warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(x : Int32) end @@ -674,11 +674,11 @@ describe "Semantic: warnings" do def foo(x : Int32); end def foo(y : Int32 | String); end end - CR + CRYSTAL end it "contravariant restrictions" do - warnings_result(<<-CR).should be_empty + warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(x : Int32, y : Int32) end @@ -687,11 +687,11 @@ describe "Semantic: warnings" do def foo(x : Int32 | Char, y : Int); end def foo(y : Int32 | String, z : Int32); end end - CR + CRYSTAL end it "different single splats" do - warnings_result(<<-CR).should be_empty + warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(x : Int32, *y) end @@ -700,11 +700,11 @@ describe "Semantic: warnings" do def foo(x : Int32, *z); end def foo(y : Int32 | String, *z); end end - CR + CRYSTAL end it "reordered named parameters" do - warnings_result(<<-CR).should be_empty + warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(x : Int32, *, y : Int32, z : Int32) end @@ -713,13 +713,13 @@ describe "Semantic: warnings" do def foo(x : Int32, *, z : Int32, y : Int32); end def foo(w : Int, *, y : Int32, z : Int32); end end - CR + CRYSTAL end end describe "continues warning if implementation with matching parameters is not found (#12150)" do it "not a full implementation" do - assert_warning <<-CR, "warning in line 8\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" + assert_warning <<-CRYSTAL, "warning in line 8\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" abstract class Foo abstract def foo(x : Int32 | String) end @@ -729,11 +729,11 @@ describe "Semantic: warnings" do def foo(x : String); end def foo(y : Int32 | String); end end - CR + CRYSTAL end it "single splat" do - assert_warning <<-CR, "warning in line 7\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" + assert_warning <<-CRYSTAL, "warning in line 7\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" abstract class Foo abstract def foo(x : Int32) end @@ -742,11 +742,11 @@ describe "Semantic: warnings" do def foo(x : Int32, *y); end def foo(y : Int32 | String); end end - CR + CRYSTAL end it "double splat" do - assert_warning <<-CR, "warning in line 7\nWarning: positional parameter 'z' corresponds to parameter 'x' of the overridden method" + assert_warning <<-CRYSTAL, "warning in line 7\nWarning: positional parameter 'z' corresponds to parameter 'x' of the overridden method" abstract class Foo abstract def foo(x : Int32, *, y) end @@ -755,12 +755,12 @@ describe "Semantic: warnings" do def foo(x : Int32, **opts); end def foo(z : Int32, *, y); end end - CR + CRYSTAL end end it "doesn't warn if current type is abstract (#12266)" do - warnings_result(<<-CR).should be_empty + warnings_result(<<-CRYSTAL).should be_empty class Foo def foo(x); end end @@ -771,11 +771,11 @@ describe "Semantic: warnings" do abstract class Baz < Bar end - CR + CRYSTAL end it "doesn't warn if current type is a module (#12266)" do - warnings_result(<<-CR).should be_empty + warnings_result(<<-CRYSTAL).should be_empty module Foo def foo(x); end # Warning: positional parameter 'x' corresponds to parameter 'y' of the overridden method Bar#foo(y), which has a different name and may affect named argument passing end @@ -788,7 +788,7 @@ describe "Semantic: warnings" do module Baz include Bar end - CR + CRYSTAL end end diff --git a/spec/std/crystal/syntax_highlighter/colorize_spec.cr b/spec/std/crystal/syntax_highlighter/colorize_spec.cr index 5cd04a7a156c..c989112264fc 100644 --- a/spec/std/crystal/syntax_highlighter/colorize_spec.cr +++ b/spec/std/crystal/syntax_highlighter/colorize_spec.cr @@ -127,13 +127,13 @@ describe Crystal::SyntaxHighlighter::Colorize do it_highlights "Set{1, 2, 3}", %(\e[36mSet\e[0m{\e[35m1\e[0m, \e[35m2\e[0m, \e[35m3\e[0m}) - it_highlights <<-CR, <<-ANSI + it_highlights <<-CRYSTAL, <<-ANSI foo, bar = <<-FOO, <<-BAR foo FOO bar BAR - CR + CRYSTAL foo, bar \e[91m=\e[0m \e[93m<<-FOO\e[0m, \e[93m<<-BAR\e[0m \e[93m foo FOO\e[0m @@ -145,16 +145,16 @@ describe Crystal::SyntaxHighlighter::Colorize do describe ".highlight!" do it_highlights! %(foo = bar("baz\#{PI + 1}") # comment), %(foo \e[91m=\e[0m bar(\e[93m"baz\#{\e[0m\e[36mPI\e[0m \e[91m+\e[0m \e[35m1\e[0m\e[93m}"\e[0m) \e[90m# comment\e[0m) - it_highlights! <<-CR + it_highlights! <<-CRYSTAL foo, bar = <<-FOO, <<-BAR foo FOO - CR + CRYSTAL - it_highlights! <<-CR + it_highlights! <<-CRYSTAL foo, bar = <<-FOO, <<-BAR foo - CR + CRYSTAL it_highlights! "\"foo" it_highlights! "%w[foo" diff --git a/spec/std/crystal/syntax_highlighter/html_spec.cr b/spec/std/crystal/syntax_highlighter/html_spec.cr index a30c6f77a9dc..fc1a3d25672c 100644 --- a/spec/std/crystal/syntax_highlighter/html_spec.cr +++ b/spec/std/crystal/syntax_highlighter/html_spec.cr @@ -122,13 +122,13 @@ describe Crystal::SyntaxHighlighter::HTML do it_highlights "Set{1, 2, 3}", %(Set{1, 2, 3}) - it_highlights <<-CR, <<-HTML + it_highlights <<-CRYSTAL, <<-HTML foo, bar = <<-FOO, <<-BAR foo FOO bar BAR - CR + CRYSTAL foo, bar = <<-FOO, <<-BAR foo FOO @@ -140,20 +140,20 @@ describe Crystal::SyntaxHighlighter::HTML do describe "#highlight!" do it_highlights! %(foo = bar("baz\#{PI + 1}") # comment), "foo = bar("baz\#{PI + 1}") # comment" - it_highlights! <<-CR, <<-HTML + it_highlights! <<-CRYSTAL, <<-HTML foo, bar = <<-FOO, <<-BAR foo FOO - CR + CRYSTAL foo, bar = <<-FOO, <<-BAR foo FOO HTML - it_highlights! <<-CR, <<-HTML + it_highlights! <<-CRYSTAL, <<-HTML foo, bar = <<-FOO, <<-BAR foo - CR + CRYSTAL foo, bar = <<-FOO, <<-BAR foo HTML diff --git a/spec/std/exception/call_stack_spec.cr b/spec/std/exception/call_stack_spec.cr index fa3565b0ed79..b5bf92597dc2 100644 --- a/spec/std/exception/call_stack_spec.cr +++ b/spec/std/exception/call_stack_spec.cr @@ -33,13 +33,13 @@ describe "Backtrace" do source_path = Path.new(source_file) source_path.absolute?.should be_true - File.write source_file, <<-EOF + File.write source_file, <<-CRYSTAL def callee1 puts caller.join('\n') end callee1 - EOF + CRYSTAL _, output, _ = compile_and_run_file(source_file) output.should match /\A(#{Regex.escape(source_path.to_s)}):/ diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index 10bc63f2e7af..1f35c086e9af 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -400,13 +400,13 @@ module HTTP end it "works with IO" do - io_response = IO::Memory.new <<-RESPONSE.gsub('\n', "\r\n") + io_response = IO::Memory.new <<-HTTP.gsub('\n', "\r\n") HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 3 Hi! - RESPONSE + HTTP io_request = IO::Memory.new io = IO::Stapled.new(io_response, io_request) client = Client.new(io) diff --git a/spec/std/http/request_spec.cr b/spec/std/http/request_spec.cr index 78e6b72994f2..b030692fc842 100644 --- a/spec/std/http/request_spec.cr +++ b/spec/std/http/request_spec.cr @@ -480,7 +480,7 @@ module HTTP end it "doesn't raise on request with multiple Content_length headers" do - io = IO::Memory.new <<-REQ + io = IO::Memory.new <<-HTTP GET / HTTP/1.1 Host: host Content-Length: 5 @@ -488,12 +488,12 @@ module HTTP Content-Type: text/plain abcde - REQ + HTTP HTTP::Request.from_io(io) end it "raises if request has multiple and differing content-length headers" do - io = IO::Memory.new <<-REQ + io = IO::Memory.new <<-HTTP GET / HTTP/1.1 Host: host Content-Length: 5 @@ -501,7 +501,7 @@ module HTTP Content-Type: text/plain abcde - REQ + HTTP expect_raises(ArgumentError) do HTTP::Request.from_io(io) end diff --git a/spec/std/http/server/request_processor_spec.cr b/spec/std/http/server/request_processor_spec.cr index 677580d961cc..42a6ff7c7f70 100644 --- a/spec/std/http/server/request_processor_spec.cr +++ b/spec/std/http/server/request_processor_spec.cr @@ -18,14 +18,14 @@ describe HTTP::Server::RequestProcessor do output = IO::Memory.new processor.process(input, output) output.rewind - output.gets_to_end.should eq(requestize(<<-RESPONSE + output.gets_to_end.should eq(requestize(<<-HTTP HTTP/1.1 200 OK Connection: keep-alive Content-Type: text/plain Content-Length: 11 Hello world - RESPONSE + HTTP )) end @@ -37,7 +37,7 @@ describe HTTP::Server::RequestProcessor do context.response << "\r\n" end - input = IO::Memory.new(requestize(<<-REQUEST + input = IO::Memory.new(requestize(<<-HTTP POST / HTTP/1.1 Content-Length: 7 @@ -46,12 +46,12 @@ describe HTTP::Server::RequestProcessor do Content-Length: 7 hello - REQUEST + HTTP )) output = IO::Memory.new processor.process(input, output) output.rewind - output.gets_to_end.should eq(requestize(<<-RESPONSE + output.gets_to_end.should eq(requestize(<<-HTTP HTTP/1.1 200 OK Connection: keep-alive Content-Type: text/plain @@ -65,7 +65,7 @@ describe HTTP::Server::RequestProcessor do hello - RESPONSE + HTTP )) end @@ -75,19 +75,19 @@ describe HTTP::Server::RequestProcessor do context.response.puts "Hello world\r" end - input = IO::Memory.new(requestize(<<-REQUEST + input = IO::Memory.new(requestize(<<-HTTP POST / HTTP/1.1 POST / HTTP/1.1 Content-Length: 7 hello - REQUEST + HTTP )) output = IO::Memory.new processor.process(input, output) output.rewind - output.gets_to_end.should eq(requestize(<<-RESPONSE + output.gets_to_end.should eq(requestize(<<-HTTP HTTP/1.1 200 OK Connection: keep-alive Content-Type: text/plain @@ -101,7 +101,7 @@ describe HTTP::Server::RequestProcessor do Hello world - RESPONSE + HTTP )) end @@ -111,7 +111,7 @@ describe HTTP::Server::RequestProcessor do context.response.puts "Hello world\r" end - input = IO::Memory.new(requestize(<<-REQUEST + input = IO::Memory.new(requestize(<<-HTTP POST / HTTP/1.1 hello @@ -119,12 +119,12 @@ describe HTTP::Server::RequestProcessor do Content-Length: 7 hello - REQUEST + HTTP )) output = IO::Memory.new processor.process(input, output) output.rewind - output.gets_to_end.should eq(requestize(<<-RESPONSE + output.gets_to_end.should eq(requestize(<<-HTTP HTTP/1.1 200 OK Connection: keep-alive Content-Type: text/plain @@ -136,7 +136,7 @@ describe HTTP::Server::RequestProcessor do Content-Length: 16 400 Bad Request\\n - RESPONSE + HTTP ).gsub("\\n", "\n")) end @@ -145,7 +145,7 @@ describe HTTP::Server::RequestProcessor do context.response.headers["Connection"] = "close" end - input = IO::Memory.new(requestize(<<-REQUEST + input = IO::Memory.new(requestize(<<-HTTP POST / HTTP/1.1 Content-Length: 7 @@ -154,18 +154,18 @@ describe HTTP::Server::RequestProcessor do Content-Length: 7 hello - REQUEST + HTTP )) output = IO::Memory.new processor.process(input, output) output.rewind - output.gets_to_end.should eq(requestize(<<-RESPONSE + output.gets_to_end.should eq(requestize(<<-HTTP HTTP/1.1 200 OK Connection: close Content-Length: 0 - RESPONSE + HTTP )) end @@ -173,7 +173,7 @@ describe HTTP::Server::RequestProcessor do processor = HTTP::Server::RequestProcessor.new do |context| end - input = IO::Memory.new(requestize(<<-REQUEST + input = IO::Memory.new(requestize(<<-HTTP POST / HTTP/1.1 Content-Length: 4 @@ -182,18 +182,18 @@ describe HTTP::Server::RequestProcessor do Content-Length: 7 hello - REQUEST + HTTP )) output = IO::Memory.new processor.process(input, output) output.rewind - output.gets_to_end.should eq(requestize(<<-RESPONSE + output.gets_to_end.should eq(requestize(<<-HTTP HTTP/1.1 200 OK Connection: keep-alive Content-Length: 0 - RESPONSE + HTTP )) end @@ -203,7 +203,7 @@ describe HTTP::Server::RequestProcessor do io.gets_to_end end - input = IO::Memory.new(requestize(<<-REQUEST + input = IO::Memory.new(requestize(<<-HTTP POST / HTTP/1.1 Content-Length: 16387 @@ -212,12 +212,12 @@ describe HTTP::Server::RequestProcessor do Content-Length: 7 hello - REQUEST + HTTP )) output = IO::Memory.new processor.process(input, output) output.rewind - output.gets_to_end.should eq(requestize(<<-RESPONSE + output.gets_to_end.should eq(requestize(<<-HTTP HTTP/1.1 200 OK Connection: keep-alive Content-Length: 0 @@ -227,7 +227,7 @@ describe HTTP::Server::RequestProcessor do Content-Length: 0 - RESPONSE + HTTP )) end end diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index 040d3b5f8afe..1cc5b20f666b 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -121,12 +121,12 @@ describe HTTP::Server do run_server(server) do TCPSocket.open(address.address, address.port) do |socket| - socket << requestize(<<-REQUEST + socket << requestize(<<-HTTP POST / HTTP/1.1 Expect: 100-continue Content-Length: 5 - REQUEST + HTTP ) socket << "\r\n" socket.flush @@ -153,12 +153,12 @@ describe HTTP::Server do run_server(server) do TCPSocket.open(address.address, address.port) do |socket| - socket << requestize(<<-REQUEST + socket << requestize(<<-HTTP POST / HTTP/1.1 Expect: 100-continue Content-Length: 5 - REQUEST + HTTP ) socket << "\r\n" socket.flush diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index d79c816f5ff4..fefe880d9350 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -49,31 +49,31 @@ end describe "at_exit" do it "runs handlers on normal program ending" do - status, output, _ = compile_and_run_source <<-CODE + status, output, _ = compile_and_run_source <<-CRYSTAL at_exit do print "handler code." end - CODE + CRYSTAL status.success?.should be_true output.should eq("handler code.") end it "runs handlers on explicit program ending" do - status, output, _ = compile_and_run_source <<-'CODE' + status, output, _ = compile_and_run_source <<-'CRYSTAL' at_exit do |exit_code| print "handler code, exit code: #{exit_code}." end exit 42 - CODE + CRYSTAL status.exit_code.should eq(42) output.should eq("handler code, exit code: 42.") end it "runs handlers in reverse order" do - status, output, _ = compile_and_run_source <<-CODE + status, output, _ = compile_and_run_source <<-CRYSTAL at_exit do print "first handler code." end @@ -81,14 +81,14 @@ describe "at_exit" do at_exit do print "second handler code." end - CODE + CRYSTAL status.success?.should be_true output.should eq("second handler code.first handler code.") end it "runs all handlers maximum once" do - status, output, _ = compile_and_run_source <<-CODE + status, output, _ = compile_and_run_source <<-CRYSTAL at_exit do print "first handler code." end @@ -103,14 +103,14 @@ describe "at_exit" do at_exit do print "third handler code." end - CODE + CRYSTAL status.success?.should be_true output.should eq("third handler code.second handler code, explicit exit!first handler code.") end it "allows handlers to change the exit code with explicit `exit` call" do - status, output, _ = compile_and_run_source <<-'CODE' + status, output, _ = compile_and_run_source <<-'CRYSTAL' at_exit do |exit_code| print "first handler code, exit code: #{exit_code}." end @@ -125,7 +125,7 @@ describe "at_exit" do at_exit do |exit_code| print "third handler code, exit code: #{exit_code}." end - CODE + CRYSTAL status.success?.should be_false status.exit_code.should eq(42) @@ -133,7 +133,7 @@ describe "at_exit" do end it "allows handlers to change the exit code with explicit `exit` call (2)" do - status, output, _ = compile_and_run_source <<-'CODE' + status, output, _ = compile_and_run_source <<-'CRYSTAL' at_exit do |exit_code| print "first handler code, exit code: #{exit_code}." end @@ -150,7 +150,7 @@ describe "at_exit" do end exit 21 - CODE + CRYSTAL status.success?.should be_false status.exit_code.should eq(42) @@ -158,7 +158,7 @@ describe "at_exit" do end it "changes final exit code when an handler raises an error" do - status, output, error = compile_and_run_source <<-'CODE' + status, output, error = compile_and_run_source <<-'CRYSTAL' at_exit do |exit_code| print "first handler code, exit code: #{exit_code}." end @@ -173,7 +173,7 @@ describe "at_exit" do at_exit do |exit_code| print "third handler code, exit code: #{exit_code}." end - CODE + CRYSTAL status.success?.should be_false status.exit_code.should eq(1) @@ -182,7 +182,7 @@ describe "at_exit" do end it "shows unhandled exceptions after at_exit handlers" do - status, _, error = compile_and_run_source <<-CODE + status, _, error = compile_and_run_source <<-CRYSTAL at_exit do STDERR.print "first handler code." end @@ -192,27 +192,27 @@ describe "at_exit" do end raise "Kaboom!" - CODE + CRYSTAL status.success?.should be_false error.should contain("second handler code.first handler code.Unhandled exception: Kaboom!") end it "can get unhandled exception in at_exit handler" do - status, _, error = compile_and_run_source <<-CODE + status, _, error = compile_and_run_source <<-CRYSTAL at_exit do |_, ex| STDERR.print ex.try &.message end raise "Kaboom!" - CODE + CRYSTAL status.success?.should be_false error.should contain("Kaboom!Unhandled exception: Kaboom!") end it "allows at_exit inside at_exit" do - status, output, _ = compile_and_run_source <<-CODE + status, output, _ = compile_and_run_source <<-CRYSTAL at_exit do print "1" at_exit do @@ -226,16 +226,16 @@ describe "at_exit" do print "4" end end - CODE + CRYSTAL status.success?.should be_true output.should eq("3412") end it "prints unhandled exception with cause" do - status, _, error = compile_and_run_source <<-CODE + status, _, error = compile_and_run_source <<-CRYSTAL raise Exception.new("secondary", cause: Exception.new("primary")) - CODE + CRYSTAL status.success?.should be_false error.should contain "Unhandled exception: secondary" @@ -245,9 +245,9 @@ end describe "hardware exception" do it "reports invalid memory access" do - status, _, error = compile_and_run_source <<-'CODE' + status, _, error = compile_and_run_source <<-'CRYSTAL' puts Pointer(Int64).null.value - CODE + CRYSTAL status.success?.should be_false error.should contain("Invalid memory access") @@ -263,13 +263,13 @@ describe "hardware exception" do # the default stack size is 0.5G. Setting a # smaller stack size with `ulimit -s 8192` # will address this. - status, _, error = compile_and_run_source <<-'CODE' + status, _, error = compile_and_run_source <<-'CRYSTAL' def foo y = StaticArray(Int8, 512).new(0) foo end foo - CODE + CRYSTAL status.success?.should be_false error.should contain("Stack overflow") @@ -277,7 +277,7 @@ describe "hardware exception" do {% end %} pending_win32 "detects stack overflow on a fiber stack" do - status, _, error = compile_and_run_source <<-'CODE' + status, _, error = compile_and_run_source <<-'CRYSTAL' def foo y = StaticArray(Int8, 512).new(0) foo @@ -288,7 +288,7 @@ describe "hardware exception" do end sleep 60.seconds - CODE + CRYSTAL status.success?.should be_false error.should contain("Stack overflow") diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 3655dad0a3f7..e051e328c66e 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -168,14 +168,14 @@ describe Process do {% if flag?(:unix) %} it "chroot raises when unprivileged" do - status, output, _ = compile_and_run_source <<-'CODE' + status, output, _ = compile_and_run_source <<-'CRYSTAL' begin Process.chroot(".") puts "FAIL" rescue ex puts ex.inspect end - CODE + CRYSTAL status.success?.should be_true output.should eq("#\n") diff --git a/spec/std/spec/hooks_spec.cr b/spec/std/spec/hooks_spec.cr index 105f5bd9db0e..a0667acbf51f 100644 --- a/spec/std/spec/hooks_spec.cr +++ b/spec/std/spec/hooks_spec.cr @@ -3,7 +3,7 @@ require "./spec_helper" describe Spec do describe "hooks" do it "runs in correct order" do - compile_and_run_source(<<-CR, flags: %w(--no-debug))[1].lines[..-5].should eq <<-OUT.lines + compile_and_run_source(<<-CRYSTAL, flags: %w(--no-debug))[1].lines[..-5].should eq <<-OUT.lines require "prelude" require "spec" @@ -90,7 +90,7 @@ describe Spec do it {} end - CR + CRYSTAL Can't call `before_all` outside of a describe/context Can't call `before_each` outside of a describe/context Can't call `after_all` outside of a describe/context diff --git a/spec/std/string/grapheme_break_spec.cr b/spec/std/string/grapheme_break_spec.cr index 9ff17889fe05..1e757064d43f 100644 --- a/spec/std/string/grapheme_break_spec.cr +++ b/spec/std/string/grapheme_break_spec.cr @@ -10,8 +10,8 @@ require "./spec_helper" describe "String#each_grapheme" do it_iterates_graphemes " ", [' ', ' '] # ÷ [0.2] SPACE (Other) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes " \u0308 ", [" \u0308", ' '] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes " \r", [' ', '\r'] # ÷ [0.2] SPACE (Other) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes " \u0308\r", [" \u0308", '\r'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes " \r", [' ', '\r'] # ÷ [0.2] SPACE (Other) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes " \u0308\r", [" \u0308", '\r'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes " \n", [' ', '\n'] # ÷ [0.2] SPACE (Other) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes " \u0308\n", [" \u0308", '\n'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes " \u0001", [' ', '\u0001'] # ÷ [0.2] SPACE (Other) ÷ [5.0] (Control) ÷ [0.3] @@ -42,44 +42,44 @@ describe "String#each_grapheme" do it_iterates_graphemes " \u0308\u200D", [" \u0308\u200D"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u0378", [' ', '\u0378'] # ÷ [0.2] SPACE (Other) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes " \u0308\u0378", [" \u0308", '\u0378'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\r ", ['\r', ' '] # ÷ [0.2] (CR) ÷ [4.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\r\u0308 ", ['\r', '\u0308', ' '] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\r\r", ['\r', '\r'] # ÷ [0.2] (CR) ÷ [4.0] (CR) ÷ [0.3] - it_iterates_graphemes "\r\u0308\r", ['\r', '\u0308', '\r'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\r\n", ["\r\n"] # ÷ [0.2] (CR) × [3.0] (LF) ÷ [0.3] - it_iterates_graphemes "\r\u0308\n", ['\r', '\u0308', '\n'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] - it_iterates_graphemes "\r\u0001", ['\r', '\u0001'] # ÷ [0.2] (CR) ÷ [4.0] (Control) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0001", ['\r', '\u0308', '\u0001'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\r\u034F", ['\r', '\u034F'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u034F", ['\r', "\u0308\u034F"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\r\u{1F1E6}", ['\r', '\u{1F1E6}'] # ÷ [0.2] (CR) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u{1F1E6}", ['\r', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\r\u0600", ['\r', '\u0600'] # ÷ [0.2] (CR) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0600", ['\r', '\u0308', '\u0600'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\r\u0903", ['\r', '\u0903'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0903", ['\r', "\u0308\u0903"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\r\u1100", ['\r', '\u1100'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u1100", ['\r', '\u0308', '\u1100'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\r\u1160", ['\r', '\u1160'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u1160", ['\r', '\u0308', '\u1160'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\r\u11A8", ['\r', '\u11A8'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u11A8", ['\r', '\u0308', '\u11A8'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\r\uAC00", ['\r', '\uAC00'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\r\u0308\uAC00", ['\r', '\u0308', '\uAC00'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\r\uAC01", ['\r', '\uAC01'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\r\u0308\uAC01", ['\r', '\u0308', '\uAC01'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\r\u231A", ['\r', '\u231A'] # ÷ [0.2] (CR) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u231A", ['\r', '\u0308', '\u231A'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\r\u0300", ['\r', '\u0300'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0300", ['\r', "\u0308\u0300"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\r\u200D", ['\r', '\u200D'] # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u200D", ['\r', "\u0308\u200D"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\r\u0378", ['\r', '\u0378'] # ÷ [0.2] (CR) ÷ [4.0] (Other) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0378", ['\r', '\u0308', '\u0378'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\r ", ['\r', ' '] # ÷ [0.2] (CRYSTAL) ÷ [4.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\r\u0308 ", ['\r', '\u0308', ' '] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\r\r", ['\r', '\r'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\r\u0308\r", ['\r', '\u0308', '\r'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\r\n", ["\r\n"] # ÷ [0.2] (CRYSTAL) × [3.0] (LF) ÷ [0.3] + it_iterates_graphemes "\r\u0308\n", ['\r', '\u0308', '\n'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\r\u0001", ['\r', '\u0001'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] (Control) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0001", ['\r', '\u0308', '\u0001'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\r\u034F", ['\r', '\u034F'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u034F", ['\r', "\u0308\u034F"] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\r\u{1F1E6}", ['\r', '\u{1F1E6}'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u{1F1E6}", ['\r', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\r\u0600", ['\r', '\u0600'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0600", ['\r', '\u0308', '\u0600'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\r\u0903", ['\r', '\u0903'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0903", ['\r', "\u0308\u0903"] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\r\u1100", ['\r', '\u1100'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u1100", ['\r', '\u0308', '\u1100'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\r\u1160", ['\r', '\u1160'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u1160", ['\r', '\u0308', '\u1160'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\r\u11A8", ['\r', '\u11A8'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u11A8", ['\r', '\u0308', '\u11A8'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\r\uAC00", ['\r', '\uAC00'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\r\u0308\uAC00", ['\r', '\u0308', '\uAC00'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\r\uAC01", ['\r', '\uAC01'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\r\u0308\uAC01", ['\r', '\u0308', '\uAC01'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\r\u231A", ['\r', '\u231A'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u231A", ['\r', '\u0308', '\u231A'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\r\u0300", ['\r', '\u0300'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0300", ['\r', "\u0308\u0300"] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u200D", ['\r', '\u200D'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u200D", ['\r', "\u0308\u200D"] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u0378", ['\r', '\u0378'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] (Other) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0378", ['\r', '\u0308', '\u0378'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\n ", ['\n', ' '] # ÷ [0.2] (LF) ÷ [4.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\n\u0308 ", ['\n', '\u0308', ' '] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\n\r", ['\n', '\r'] # ÷ [0.2] (LF) ÷ [4.0] (CR) ÷ [0.3] - it_iterates_graphemes "\n\u0308\r", ['\n', '\u0308', '\r'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\n\r", ['\n', '\r'] # ÷ [0.2] (LF) ÷ [4.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\n\u0308\r", ['\n', '\u0308', '\r'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\n\n", ['\n', '\n'] # ÷ [0.2] (LF) ÷ [4.0] (LF) ÷ [0.3] it_iterates_graphemes "\n\u0308\n", ['\n', '\u0308', '\n'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\n\u0001", ['\n', '\u0001'] # ÷ [0.2] (LF) ÷ [4.0] (Control) ÷ [0.3] @@ -112,8 +112,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\n\u0308\u0378", ['\n', '\u0308', '\u0378'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0001 ", ['\u0001', ' '] # ÷ [0.2] (Control) ÷ [4.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0001\u0308 ", ['\u0001', '\u0308', ' '] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0001\r", ['\u0001', '\r'] # ÷ [0.2] (Control) ÷ [4.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u0001\u0308\r", ['\u0001', '\u0308', '\r'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0001\r", ['\u0001', '\r'] # ÷ [0.2] (Control) ÷ [4.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\r", ['\u0001', '\u0308', '\r'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u0001\n", ['\u0001', '\n'] # ÷ [0.2] (Control) ÷ [4.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\n", ['\u0001', '\u0308', '\n'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0001\u0001", ['\u0001', '\u0001'] # ÷ [0.2] (Control) ÷ [4.0] (Control) ÷ [0.3] @@ -146,8 +146,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0001\u0308\u0378", ['\u0001', '\u0308', '\u0378'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u034F ", ['\u034F', ' '] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u034F\u0308 ", ["\u034F\u0308", ' '] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u034F\r", ['\u034F', '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\r", ["\u034F\u0308", '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u034F\r", ['\u034F', '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u034F\u0308\r", ["\u034F\u0308", '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u034F\n", ['\u034F', '\n'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u034F\u0308\n", ["\u034F\u0308", '\n'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u034F\u0001", ['\u034F', '\u0001'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (Control) ÷ [0.3] @@ -180,8 +180,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u034F\u0308\u0378", ["\u034F\u0308", '\u0378'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u{1F1E6} ", ['\u{1F1E6}', ' '] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308 ", ["\u{1F1E6}\u0308", ' '] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\r", ['\u{1F1E6}', '\r'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\u0308\r", ["\u{1F1E6}\u0308", '\r'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\r", ['\u{1F1E6}', '\r'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\r", ["\u{1F1E6}\u0308", '\r'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\n", ['\u{1F1E6}', '\n'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\n", ["\u{1F1E6}\u0308", '\n'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0001", ['\u{1F1E6}', '\u0001'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (Control) ÷ [0.3] @@ -214,8 +214,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u{1F1E6}\u0308\u0378", ["\u{1F1E6}\u0308", '\u0378'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0600 ", ["\u0600 "] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0600\u0308 ", ["\u0600\u0308", ' '] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0600\r", ['\u0600', '\r'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u0600\u0308\r", ["\u0600\u0308", '\r'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0600\r", ['\u0600', '\r'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\r", ["\u0600\u0308", '\r'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u0600\n", ['\u0600', '\n'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\n", ["\u0600\u0308", '\n'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0600\u0001", ['\u0600', '\u0001'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (Control) ÷ [0.3] @@ -248,8 +248,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0600\u0308\u0378", ["\u0600\u0308", '\u0378'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0903 ", ['\u0903', ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0903\u0308 ", ["\u0903\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0903\r", ['\u0903', '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\r", ["\u0903\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0903\r", ['\u0903', '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\r", ["\u0903\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u0903\n", ['\u0903', '\n'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0903\u0308\n", ["\u0903\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0903\u0001", ['\u0903', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (Control) ÷ [0.3] @@ -282,8 +282,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0903\u0308\u0378", ["\u0903\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u1100 ", ['\u1100', ' '] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u1100\u0308 ", ["\u1100\u0308", ' '] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u1100\r", ['\u1100', '\r'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u1100\u0308\r", ["\u1100\u0308", '\r'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u1100\r", ['\u1100', '\r'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\r", ["\u1100\u0308", '\r'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u1100\n", ['\u1100', '\n'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\n", ["\u1100\u0308", '\n'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u1100\u0001", ['\u1100', '\u0001'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (Control) ÷ [0.3] @@ -316,8 +316,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1100\u0308\u0378", ["\u1100\u0308", '\u0378'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u1160 ", ['\u1160', ' '] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u1160\u0308 ", ["\u1160\u0308", ' '] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u1160\r", ['\u1160', '\r'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u1160\u0308\r", ["\u1160\u0308", '\r'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u1160\r", ['\u1160', '\r'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\r", ["\u1160\u0308", '\r'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u1160\n", ['\u1160', '\n'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\n", ["\u1160\u0308", '\n'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u1160\u0001", ['\u1160', '\u0001'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (Control) ÷ [0.3] @@ -350,8 +350,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1160\u0308\u0378", ["\u1160\u0308", '\u0378'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u11A8 ", ['\u11A8', ' '] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308 ", ["\u11A8\u0308", ' '] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u11A8\r", ['\u11A8', '\r'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u11A8\u0308\r", ["\u11A8\u0308", '\r'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u11A8\r", ['\u11A8', '\r'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\r", ["\u11A8\u0308", '\r'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u11A8\n", ['\u11A8', '\n'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\n", ["\u11A8\u0308", '\n'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u11A8\u0001", ['\u11A8', '\u0001'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (Control) ÷ [0.3] @@ -384,8 +384,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u11A8\u0308\u0378", ["\u11A8\u0308", '\u0378'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\uAC00 ", ['\uAC00', ' '] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308 ", ["\uAC00\u0308", ' '] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\uAC00\r", ['\uAC00', '\r'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\uAC00\u0308\r", ["\uAC00\u0308", '\r'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\uAC00\r", ['\uAC00', '\r'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\r", ["\uAC00\u0308", '\r'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\uAC00\n", ['\uAC00', '\n'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\n", ["\uAC00\u0308", '\n'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\uAC00\u0001", ['\uAC00', '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (Control) ÷ [0.3] @@ -418,8 +418,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC00\u0308\u0378", ["\uAC00\u0308", '\u0378'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\uAC01 ", ['\uAC01', ' '] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308 ", ["\uAC01\u0308", ' '] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\uAC01\r", ['\uAC01', '\r'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\uAC01\u0308\r", ["\uAC01\u0308", '\r'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\uAC01\r", ['\uAC01', '\r'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\r", ["\uAC01\u0308", '\r'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\uAC01\n", ['\uAC01', '\n'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\n", ["\uAC01\u0308", '\n'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\uAC01\u0001", ['\uAC01', '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (Control) ÷ [0.3] @@ -452,8 +452,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC01\u0308\u0378", ["\uAC01\u0308", '\u0378'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u231A ", ['\u231A', ' '] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u231A\u0308 ", ["\u231A\u0308", ' '] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u231A\r", ['\u231A', '\r'] # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u231A\u0308\r", ["\u231A\u0308", '\r'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u231A\r", ['\u231A', '\r'] # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\r", ["\u231A\u0308", '\r'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u231A\n", ['\u231A', '\n'] # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\n", ["\u231A\u0308", '\n'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u231A\u0001", ['\u231A', '\u0001'] # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (Control) ÷ [0.3] @@ -486,8 +486,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u231A\u0308\u0378", ["\u231A\u0308", '\u0378'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0300 ", ['\u0300', ' '] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0300\u0308 ", ["\u0300\u0308", ' '] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0300\r", ['\u0300', '\r'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u0300\u0308\r", ["\u0300\u0308", '\r'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0300\r", ['\u0300', '\r'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\r", ["\u0300\u0308", '\r'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u0300\n", ['\u0300', '\n'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\n", ["\u0300\u0308", '\n'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0300\u0001", ['\u0300', '\u0001'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] @@ -520,8 +520,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0300\u0308\u0378", ["\u0300\u0308", '\u0378'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u200D ", ['\u200D', ' '] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u200D\u0308 ", ["\u200D\u0308", ' '] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u200D\r", ['\u200D', '\r'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u200D\u0308\r", ["\u200D\u0308", '\r'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u200D\r", ['\u200D', '\r'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\r", ["\u200D\u0308", '\r'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u200D\n", ['\u200D', '\n'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\n", ["\u200D\u0308", '\n'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u200D\u0001", ['\u200D', '\u0001'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] @@ -554,8 +554,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u200D\u0308\u0378", ["\u200D\u0308", '\u0378'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0378 ", ['\u0378', ' '] # ÷ [0.2] (Other) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0378\u0308 ", ["\u0378\u0308", ' '] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0378\r", ['\u0378', '\r'] # ÷ [0.2] (Other) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u0378\u0308\r", ["\u0378\u0308", '\r'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0378\r", ['\u0378', '\r'] # ÷ [0.2] (Other) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\r", ["\u0378\u0308", '\r'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] it_iterates_graphemes "\u0378\n", ['\u0378', '\n'] # ÷ [0.2] (Other) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\n", ["\u0378\u0308", '\n'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0378\u0001", ['\u0378', '\u0001'] # ÷ [0.2] (Other) ÷ [5.0] (Control) ÷ [0.3] @@ -586,7 +586,7 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0378\u0308\u200D", ["\u0378\u0308\u200D"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0378\u0378", ['\u0378', '\u0378'] # ÷ [0.2] (Other) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u0378", ["\u0378\u0308", '\u0378'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\r\na\n\u0308", ["\r\n", 'a', '\n', '\u0308'] # ÷ [0.2] (CR) × [3.0] (LF) ÷ [4.0] LATIN SMALL LETTER A (Other) ÷ [5.0] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\na\n\u0308", ["\r\n", 'a', '\n', '\u0308'] # ÷ [0.2] (CRYSTAL) × [3.0] (LF) ÷ [4.0] LATIN SMALL LETTER A (Other) ÷ [5.0] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "a\u0308", ["a\u0308"] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u200D\u0646", [" \u200D", '\u0646'] # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC LETTER NOON (Other) ÷ [0.3] it_iterates_graphemes "\u0646\u200D ", ["\u0646\u200D", ' '] # ÷ [0.2] ARABIC LETTER NOON (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] diff --git a/spec/std/yaml/yaml_spec.cr b/spec/std/yaml/yaml_spec.cr index 4eb6a345d9bd..996b74f06716 100644 --- a/spec/std/yaml/yaml_spec.cr +++ b/spec/std/yaml/yaml_spec.cr @@ -137,11 +137,11 @@ describe "YAML" do it "has correct message (#4006)" do expect_raises YAML::ParseException, "could not find expected ':' at line 4, column 1, while scanning a simple key at line 3, column 5" do - YAML.parse <<-END + YAML.parse <<-YAML a: - "b": > c - END + YAML end end diff --git a/src/compiler/crystal/tools/doc/templates.cr b/src/compiler/crystal/tools/doc/templates.cr index 14e7f4daba62..77496edb1fd4 100644 --- a/src/compiler/crystal/tools/doc/templates.cr +++ b/src/compiler/crystal/tools/doc/templates.cr @@ -1,24 +1,24 @@ require "ecr/macros" module Crystal::Doc - SVG_DEFS = <<-SVGS + SVG_DEFS = <<-SVG - SVGS + SVG def self.anchor_link(anchor : String) anchor = anchor.downcase.gsub(' ', '-') - <<-ANCHOR + <<-HTML - ANCHOR + HTML end record TypeTemplate, type : Type, types : Array(Type), project_info : ProjectInfo do diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr index 12914e593305..654d0863faba 100644 --- a/src/compiler/crystal/tools/playground/server.cr +++ b/src/compiler/crystal/tools/playground/server.cr @@ -22,7 +22,7 @@ module Crystal::Playground instrumented = Playground::AgentInstrumentorTransformer.transform(ast).to_s Log.info { "Code instrumentation (session=#{session_key}, tag=#{tag}).\n#{instrumented}" } - prelude = <<-CR + prelude = <<-CRYSTAL require "compiler/crystal/tools/playground/agent" class Crystal::Playground::Agent @@ -36,7 +36,7 @@ module Crystal::Playground def _p Crystal::Playground::Agent.instance end - CR + CRYSTAL [ Compiler::Source.new("playground_prelude", prelude), @@ -390,7 +390,7 @@ module Crystal::Playground class EnvironmentHandler include HTTP::Handler - DEFAULT_SOURCE = <<-CR + DEFAULT_SOURCE = <<-CRYSTAL def find_string(text, word) (0..text.size-word.size).each do |i| { i, text[i..i+word.size-1] } @@ -404,7 +404,7 @@ module Crystal::Playground find_string "Crystal is awesome!", "awesome" find_string "Crystal is awesome!", "not sure" - CR + CRYSTAL def initialize(@server : Playground::Server) end diff --git a/src/yaml.cr b/src/yaml.cr index 813b456c084f..23a5843dfecb 100644 --- a/src/yaml.cr +++ b/src/yaml.cr @@ -19,14 +19,14 @@ require "base64" # ``` # require "yaml" # -# data = YAML.parse <<-END +# data = YAML.parse <<-YAML # --- # foo: # bar: # baz: # - qux # - fox -# END +# YAML # data["foo"]["bar"]["baz"][1].as_s # => "fox" # ``` # diff --git a/src/yaml/any.cr b/src/yaml/any.cr index f0a7978369b9..e8e3a82e58b3 100644 --- a/src/yaml/any.cr +++ b/src/yaml/any.cr @@ -5,14 +5,14 @@ # ``` # require "yaml" # -# data = YAML.parse <<-END +# data = YAML.parse <<-YAML # --- # foo: # bar: # baz: # - qux # - fox -# END +# YAML # data["foo"]["bar"]["baz"][0].as_s # => "qux" # data["foo"]["bar"]["baz"].as_a # => ["qux", "fox"] # ``` From 87813d1f282f940a30c6b964fad5b06c65b80787 Mon Sep 17 00:00:00 2001 From: Hugo Parente Lima Date: Sun, 4 Dec 2022 08:38:45 -0300 Subject: [PATCH 0169/1551] Swap documentation for `String#split` array and block versions. (#12808) --- src/string.cr | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/string.cr b/src/string.cr index dd3918252002..4e2fe6064108 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3947,7 +3947,7 @@ class String yield String.new(to_unsafe + byte_offset, piece_bytesize, piece_size) end - # Splits the string after each regex *separator* and yields each part to a block. + # Makes an `Array` by splitting the string on *separator* (and removing instances of *separator*). # # If *limit* is present, the array will be limited to *limit* items and # the final item will contain the remainder of the string. @@ -3957,15 +3957,9 @@ class String # If *remove_empty* is `true`, any empty strings are removed from the result. # # ``` - # ary = [] of String # long_river_name = "Mississippi" - # - # long_river_name.split(/s+/) { |s| ary << s } - # ary # => ["Mi", "i", "ippi"] - # ary.clear - # - # long_river_name.split(//) { |s| ary << s } - # ary # => ["M", "i", "s", "s", "i", "s", "s", "i", "p", "p", "i"] + # long_river_name.split(/s+/) # => ["Mi", "i", "ippi"] + # long_river_name.split(//) # => ["M", "i", "s", "s", "i", "s", "s", "i", "p", "p", "i"] # ``` def split(separator : Regex, limit = nil, *, remove_empty = false) : Array(String) ary = Array(String).new @@ -3975,7 +3969,7 @@ class String ary end - # Makes an `Array` by splitting the string on *separator* (and removing instances of *separator*). + # Splits the string after each regex *separator* and yields each part to a block. # # If *limit* is present, the array will be limited to *limit* items and # the final item will contain the remainder of the string. @@ -3985,9 +3979,15 @@ class String # If *remove_empty* is `true`, any empty strings are removed from the result. # # ``` + # ary = [] of String # long_river_name = "Mississippi" - # long_river_name.split(/s+/) # => ["Mi", "i", "ippi"] - # long_river_name.split(//) # => ["M", "i", "s", "s", "i", "s", "s", "i", "p", "p", "i"] + # + # long_river_name.split(/s+/) { |s| ary << s } + # ary # => ["Mi", "i", "ippi"] + # ary.clear + # + # long_river_name.split(//) { |s| ary << s } + # ary # => ["M", "i", "s", "s", "i", "s", "s", "i", "p", "p", "i"] # ``` def split(separator : Regex, limit = nil, *, remove_empty = false, &block : String -> _) if empty? From 5a33430465bd0a6f1731f98bfe7d7a543c02bad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 4 Dec 2022 23:41:20 +0100 Subject: [PATCH 0170/1551] Fix `BigInt#%` for unsigned integers (#12773) --- spec/std/big/big_int_spec.cr | 1 + src/big/big_int.cr | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr index 4aa7aaadcd6a..69be160b848f 100644 --- a/spec/std/big/big_int_spec.cr +++ b/spec/std/big/big_int_spec.cr @@ -228,6 +228,7 @@ describe "BigInt" do it "does modulo" do (10.to_big_i % 3.to_big_i).should eq(1.to_big_i) (10.to_big_i % 3).should eq(1.to_big_i) + (10.to_big_i % 3u8).should eq(1.to_big_i) (10 % 3.to_big_i).should eq(1.to_big_i) end diff --git a/src/big/big_int.cr b/src/big/big_int.cr index 1f8e95f1aa7c..f703a6854126 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -253,7 +253,7 @@ struct BigInt < Int check_division_by_zero other if other < 0 - -(-self).unsafe_floored_mod(-other) + -(-self).unsafe_floored_mod(other.abs) else unsafe_floored_mod(other) end From 6b19d661430c3a04bda315dc26b0364958e16852 Mon Sep 17 00:00:00 2001 From: Vlad Zarakovsky Date: Mon, 5 Dec 2022 01:41:58 +0300 Subject: [PATCH 0171/1551] Replace `if !blank?` with `unless blank?` (#12800) --- src/string.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/string.cr b/src/string.cr index 4e2fe6064108..c741f004334d 100644 --- a/src/string.cr +++ b/src/string.cr @@ -2958,7 +2958,7 @@ class String # # See also: `Nil#presence`. def presence : self? - self if !blank? + self unless blank? end # Returns `true` if this string is equal to `*other*. From 33b1920a553980487bd801120264d881ebddfa90 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 6 Dec 2022 05:01:22 +0800 Subject: [PATCH 0172/1551] Fix restriction of numeral generic argument against non-free variable `Path` (#12784) --- spec/compiler/semantic/restrictions_spec.cr | 30 +++++++++++++++-- src/compiler/crystal/semantic/restrictions.cr | 33 ++++++++++++++----- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/spec/compiler/semantic/restrictions_spec.cr b/spec/compiler/semantic/restrictions_spec.cr index baf69551da21..7201bb816f69 100644 --- a/spec/compiler/semantic/restrictions_spec.cr +++ b/spec/compiler/semantic/restrictions_spec.cr @@ -617,8 +617,7 @@ describe "Restrictions" do CRYSTAL end - # TODO: enable in #12784 - pending "inserts constant before free variable with same name" do + it "inserts constant before free variable with same name" do assert_type(<<-CRYSTAL) { tuple_of([char, bool]) } class Foo(T); end @@ -636,7 +635,7 @@ describe "Restrictions" do CRYSTAL end - pending "keeps constant before free variable with same name" do + it "keeps constant before free variable with same name" do assert_type(<<-CRYSTAL) { tuple_of([char, bool]) } class Foo(T); end @@ -1140,6 +1139,31 @@ describe "Restrictions" do "expected argument #2 to 'foo' to be StaticArray(UInt8, 10), not StaticArray(UInt8, 11)" end + it "does not treat single path as free variable when given number (1) (#11859)" do + assert_error <<-CR, "expected argument #1 to 'Foo(1)#foo' to be Foo(1), not Foo(2)" + class Foo(T) + def foo(x : Foo(T)) + end + end + + Foo(1).new.foo(Foo(2).new) + CR + end + + it "does not treat single path as free variable when given number (2) (#11859)" do + assert_error <<-CR, "expected argument #1 to 'foo' to be Foo(1), not Foo(2)" + X = 1 + + class Foo(T) + end + + def foo(x : Foo(X)) + end + + foo(Foo(2).new) + CR + end + it "restricts aliased typedef type (#9474)" do assert_type(%( lib A diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 523030444d2e..c391dfdb4ee6 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -705,7 +705,20 @@ module Crystal end def restriction_of?(other : Generic, owner, self_free_vars = nil, other_free_vars = nil) - return true if self == other + # The two `Foo(X)`s below are not equal because only one of them is bound + # and the other one is unbound, so we compare the free variables too: + # (`X` is an alias or a numeric constant) + # + # ``` + # def foo(x : Foo(X)) forall X + # end + # + # def foo(x : Foo(X)) + # end + # ``` + # + # See also the todo in `Path#restriction_of?(Path)` + return true if self == other && self_free_vars == other_free_vars return false unless name == other.name && type_vars.size == other.type_vars.size # Special case: NamedTuple against NamedTuple @@ -1242,15 +1255,17 @@ module Crystal end when Path if first_name = other_type_var.single_name? - # If the free variable is already set to another - # number, there's no match - existing = context.get_free_var(first_name) - if existing && existing != type_var - return nil - end + if context.has_def_free_var?(first_name) + # If the free variable is already set to another + # number, there's no match + existing = context.get_free_var(first_name) + if existing && existing != type_var + return nil + end - context.set_free_var(first_name, type_var) - return type_var + context.set_free_var(first_name, type_var) + return type_var + end end else # Restriction is not possible (maybe return nil here?) From ae173ee134b62f291f721a3b8fea001564668495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 5 Dec 2022 22:01:42 +0100 Subject: [PATCH 0173/1551] Fix explicit type conversion to u64 for `GC::Stats` (#12779) Resolves https://github.com/crystal-lang/crystal/pull/12634 --- spec/std/gc_spec.cr | 8 ++++++++ src/gc/boehm.cr | 30 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/spec/std/gc_spec.cr b/spec/std/gc_spec.cr index 7fd19817128b..4ceb1ad173f9 100644 --- a/spec/std/gc_spec.cr +++ b/spec/std/gc_spec.cr @@ -10,4 +10,12 @@ describe "GC" do GC.enable end end + + it ".stats" do + GC.stats.should be_a(GC::Stats) + end + + it ".prof_stats" do + GC.prof_stats.should be_a(GC::ProfStats) + end end diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 2a76eb950a4b..f9a93aef154b 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -217,11 +217,11 @@ module GC Stats.new( # collections: collections, # bytes_found: bytes_found, - heap_size: heap_size, - free_bytes: free_bytes, - unmapped_bytes: unmapped_bytes, - bytes_since_gc: bytes_since_gc, - total_bytes: total_bytes + heap_size: heap_size.to_u64!, + free_bytes: free_bytes.to_u64!, + unmapped_bytes: unmapped_bytes.to_u64!, + bytes_since_gc: bytes_since_gc.to_u64!, + total_bytes: total_bytes.to_u64! ) end @@ -229,16 +229,16 @@ module GC LibGC.get_prof_stats(out stats, sizeof(LibGC::ProfStats)) ProfStats.new( - heap_size: stats.heap_size, - free_bytes: stats.free_bytes, - unmapped_bytes: stats.unmapped_bytes, - bytes_since_gc: stats.bytes_since_gc, - bytes_before_gc: stats.bytes_before_gc, - non_gc_bytes: stats.non_gc_bytes, - gc_no: stats.gc_no, - markers_m1: stats.markers_m1, - bytes_reclaimed_since_gc: stats.bytes_reclaimed_since_gc, - reclaimed_bytes_before_gc: stats.reclaimed_bytes_before_gc) + heap_size: stats.heap_size.to_u64!, + free_bytes: stats.free_bytes.to_u64!, + unmapped_bytes: stats.unmapped_bytes.to_u64!, + bytes_since_gc: stats.bytes_since_gc.to_u64!, + bytes_before_gc: stats.bytes_before_gc.to_u64!, + non_gc_bytes: stats.non_gc_bytes.to_u64!, + gc_no: stats.gc_no.to_u64!, + markers_m1: stats.markers_m1.to_u64!, + bytes_reclaimed_since_gc: stats.bytes_reclaimed_since_gc.to_u64!, + reclaimed_bytes_before_gc: stats.reclaimed_bytes_before_gc.to_u64!) end {% unless flag?(:win32) %} From 5e3ea1cac9d815c088aa1beb5f646283fe69c390 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Thu, 8 Dec 2022 03:34:35 +0800 Subject: [PATCH 0174/1551] Fix calls with do-end blocks within index operators (#12824) --- spec/compiler/parser/parser_spec.cr | 9 +++++++++ src/compiler/crystal/syntax/parser.cr | 3 +++ 2 files changed, 12 insertions(+) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 61f1a369154f..b11874676354 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -681,6 +681,15 @@ module Crystal it_parses "[] of {String, ->}", ArrayLiteral.new([] of ASTNode, Generic.new(Path.global("Tuple"), ["String".path, ProcNotation.new] of ASTNode)) it_parses "x([] of Foo, Bar.new)", Call.new(nil, "x", ArrayLiteral.new([] of ASTNode, "Foo".path), Call.new("Bar".path, "new")) + context "calls with blocks within index operator (#12818)" do + it_parses "foo[bar { 1 }]", Call.new("foo".call, "[]", Call.new(nil, "bar", block: Block.new(body: 1.int32))) + it_parses "foo.[bar { 1 }]", Call.new("foo".call, "[]", Call.new(nil, "bar", block: Block.new(body: 1.int32))) + it_parses "foo.[](bar { 1 })", Call.new("foo".call, "[]", Call.new(nil, "bar", block: Block.new(body: 1.int32))) + it_parses "foo[bar do; 1; end]", Call.new("foo".call, "[]", Call.new(nil, "bar", block: Block.new(body: 1.int32))) + it_parses "foo.[bar do; 1; end]", Call.new("foo".call, "[]", Call.new(nil, "bar", block: Block.new(body: 1.int32))) + it_parses "foo.[](bar do; 1; end)", Call.new("foo".call, "[]", Call.new(nil, "bar", block: Block.new(body: 1.int32))) + end + it_parses "Foo(x: U)", Generic.new("Foo".path, [] of ASTNode, named_args: [NamedArgument.new("x", "U".path)]) it_parses "Foo(x: U, y: V)", Generic.new("Foo".path, [] of ASTNode, named_args: [NamedArgument.new("x", "U".path), NamedArgument.new("y", "V".path)]) it_parses "Foo(X: U, Y: V)", Generic.new("Foo".path, [] of ASTNode, named_args: [NamedArgument.new("X", "U".path), NamedArgument.new("Y", "V".path)]) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index e554f500b738..5b772962c98d 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -804,14 +804,17 @@ module Crystal name_location = @token.location next_token_skip_space_or_newline + call_args = preserve_stop_on_do do parse_call_args_space_consumed( check_plus_and_minus: false, allow_curly: true, end_token: :OP_RSQUARE, allow_beginless_range: true, + control: true, ) end + skip_space_or_newline check :OP_RSQUARE end_location = token_end_location From 65a17bfaad1ad7c531cc95c5c71761692c0feec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 7 Dec 2022 20:34:59 +0100 Subject: [PATCH 0175/1551] Improve `Benchmark` docs (#12782) Co-authored-by: r00ster --- src/benchmark.cr | 6 ++---- src/benchmark/bm.cr | 4 ++-- src/benchmark/ips.cr | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/benchmark.cr b/src/benchmark.cr index c836ddf18dd6..da2a10be9def 100644 --- a/src/benchmark.cr +++ b/src/benchmark.cr @@ -33,8 +33,6 @@ require "./benchmark/**" # end # ``` # -# Make sure to always benchmark code by compiling with the `--release` flag. -# # ### Measure the time to construct the string given by the expression: `"a"*1_000_000_000` # # ``` @@ -81,7 +79,7 @@ require "./benchmark/**" # upto: 0.010000 0.000000 0.010000 ( 0.010466) # ``` # -# Make sure to always benchmark code by compiling with the `--release` flag. +# NOTE: Make sure to always benchmark code by compiling with the `--release` flag. module Benchmark extend self @@ -104,7 +102,7 @@ module Benchmark # The optional parameters *calculation* and *warmup* set the duration of # those stages in seconds. For more detail on these stages see # `Benchmark::IPS`. When the *interactive* parameter is `true`, results are - # displayed and updated as they are calculated, otherwise all at once. + # displayed and updated as they are calculated, otherwise all at once after they finished. def ips(calculation = 5, warmup = 2, interactive = STDOUT.tty?) {% if !flag?(:release) %} puts "Warning: benchmarking without the `--release` flag won't yield useful results" diff --git a/src/benchmark/bm.cr b/src/benchmark/bm.cr index ab0f87704d6e..4b94e4ca6eee 100644 --- a/src/benchmark/bm.cr +++ b/src/benchmark/bm.cr @@ -24,12 +24,12 @@ module Benchmark def initialize(@utime, @stime, @cutime, @cstime, @real, @label) end - # Total time, that is utime + stime + cutime + cstime + # Total time, that is `utime` + `stime` + `cutime` + `cstime` def total : Float64 utime + stime + cutime + cstime end - # Prints *utime*, *stime*, *total* and *real* to the given IO. + # Prints `utime`, `stime`, `total` and `real` to *io*. def to_s(io : IO) : Nil io.printf " %.6f %.6f %.6f ( %.6f)", utime, stime, total, real end diff --git a/src/benchmark/ips.cr b/src/benchmark/ips.cr index 85d1df46723e..cb952325eca0 100644 --- a/src/benchmark/ips.cr +++ b/src/benchmark/ips.cr @@ -14,7 +14,7 @@ module Benchmark module IPS class Job # List of all entries in the benchmark. - # After #execute, these are populated with the resulting statistics. + # After `#execute`, these are populated with the resulting statistics. property items : Array(Entry) @warmup_time : Time::Span @@ -129,7 +129,7 @@ module Benchmark # Code to be benchmarked property action : -> - # Number of cycles needed to run for approx 100ms + # Number of cycles needed to run `action` for approximately 100ms. # Calculated during the warmup stage property! cycles : Int32 From a37e97d02f369a8bec32897f1fa8c668727d0016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 8 Dec 2022 23:15:46 +0100 Subject: [PATCH 0176/1551] Optimize uniqueness filter in `Channel.select_impl` (#12814) --- src/channel.cr | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/channel.cr b/src/channel.cr index 59fe55e38cf4..5a7a5957c786 100644 --- a/src/channel.cr +++ b/src/channel.cr @@ -425,20 +425,19 @@ class Channel(T) # This is to avoid deadlocks between concurrent `select` calls ops_locks = ops .to_a - .uniq!(&.lock_object_id) - .sort_by!(&.lock_object_id) + .unstable_sort_by!(&.lock_object_id) - ops_locks.each &.lock + each_skip_duplicates(ops_locks, &.lock) ops.each_with_index do |op, index| state = op.execute case state in .delivered? - ops_locks.each &.unlock + each_skip_duplicates(ops_locks, &.unlock) return index, op.result in .closed? - ops_locks.each &.unlock + each_skip_duplicates(ops_locks, &.unlock) return index, op.default_result in .none? # do nothing @@ -446,7 +445,7 @@ class Channel(T) end if non_blocking - ops_locks.each &.unlock + each_skip_duplicates(ops_locks, &.unlock) return ops.size, NotReady.new end @@ -456,7 +455,7 @@ class Channel(T) shared_state = SelectContextSharedState.new(SelectState::Active) contexts = ops.map &.create_context_and_wait(shared_state) - ops_locks.each &.unlock + each_skip_duplicates(ops_locks, &.unlock) Crystal::Scheduler.reschedule contexts.each_with_index do |context, index| @@ -475,6 +474,19 @@ class Channel(T) raise "BUG: Fiber was awaken from select but no action was activated" end + private def self.each_skip_duplicates(ops_locks) + # Avoid deadlocks from trying to lock the same lock twice. + # `ops_lock` is sorted by `lock_object_id`, so identical onces will be in + # a row and we skip repeats while iterating. + last_lock_id = nil + ops_locks.each do |op| + if op.lock_object_id != last_lock_id + last_lock_id = op.lock_object_id + yield op + end + end + end + # :nodoc: def send_select_action(value : T) SendAction.new(self, value) From 6acf0319f5850bf5ccf65028105640e46fa17a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 11 Dec 2022 21:07:53 +0100 Subject: [PATCH 0177/1551] Add references between String equality, comparison methods (#10531) Co-authored-by: Vladislav Zarakovsky --- src/string.cr | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/string.cr b/src/string.cr index c741f004334d..06df4ce97834 100644 --- a/src/string.cr +++ b/src/string.cr @@ -2963,12 +2963,22 @@ class String # Returns `true` if this string is equal to `*other*. # - # Comparison is done byte-per-byte: if a byte is different from the corresponding - # byte, `false` is returned and so on. This means two strings containing invalid + # Equality is checked byte-per-byte: if any byte is different from the corresponding + # byte, it returns `false`. This means two strings containing invalid # UTF-8 byte sequences may compare unequal, even when they both produce the # Unicode replacement character at the same string indices. # - # See `#compare` for more comparison options. + # Thus equality is case-sensitive, as it is with the comparison operator (`#<=>`). + # `#compare` offers a case-insensitive alternative. + # + # ``` + # "abcdef" == "abcde" # => false + # "abcdef" == "abcdef" # => true + # "abcdef" == "abcdefg" # => false + # "abcdef" == "ABCDEF" # => false + # + # "abcdef".compare("ABCDEF", case_sensitive: false) == 0 # => true + # ``` def ==(other : self) : Bool return true if same?(other) return false unless bytesize == other.bytesize @@ -2995,6 +3005,8 @@ class String # "abcdef" <=> "abcdefg" # => -1 # "abcdef" <=> "ABCDEF" # => 1 # ``` + # + # The comparison is case-sensitive. `#compare` is a case-insensitive alternative. def <=>(other : self) : Int32 return 0 if same?(other) min_bytesize = Math.min(bytesize, other.bytesize) @@ -3021,6 +3033,8 @@ class String # # "heIIo".compare("heııo", case_insensitive: true, options: Unicode::CaseOptions::Turkic) # => 0 # ``` + # + # Case-sensitive only comparison is provided by the comparison operator `#<=>`. def compare(other : String, case_insensitive = false, options = Unicode::CaseOptions::None) : Int32 return self <=> other unless case_insensitive From a3f9199b8aed928d91c797e0aa9656f302804e18 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 12 Dec 2022 04:08:36 +0800 Subject: [PATCH 0178/1551] Implement multithreading primitives on Windows (#11647) --- spec/std/thread/mutex_spec.cr | 9 +- src/crystal/system/thread.cr | 2 +- .../system/thread_condition_variable.cr | 31 +++++++ src/crystal/system/thread_mutex.cr | 2 +- .../system/unix/pthread_condition_variable.cr | 2 +- src/crystal/system/wasi/thread.cr | 16 ---- .../system/wasi/thread_condition_variable.cr | 16 ++++ src/crystal/system/win32/process.cr | 2 +- src/crystal/system/win32/thread.cr | 85 +++++++++++++++---- .../system/win32/thread_condition_variable.cr | 41 +++++++++ src/crystal/system/win32/thread_mutex.cr | 57 ++++++++++++- src/gc/boehm.cr | 16 +++- src/gc/none.cr | 13 ++- src/lib_c/x86_64-windows-msvc/c/process.cr | 5 ++ .../c/processthreadsapi.cr | 2 + src/lib_c/x86_64-windows-msvc/c/synchapi.cr | 28 ++++++ src/lib_c/x86_64-windows-msvc/c/winbase.cr | 5 ++ src/lib_c/x86_64-windows-msvc/c/winsock2.cr | 6 +- 18 files changed, 286 insertions(+), 52 deletions(-) create mode 100644 src/crystal/system/thread_condition_variable.cr create mode 100644 src/crystal/system/wasi/thread_condition_variable.cr create mode 100644 src/crystal/system/win32/thread_condition_variable.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/process.cr diff --git a/spec/std/thread/mutex_spec.cr b/spec/std/thread/mutex_spec.cr index 3346a1575615..405c812b0888 100644 --- a/spec/std/thread/mutex_spec.cr +++ b/spec/std/thread/mutex_spec.cr @@ -15,11 +15,11 @@ describe Thread::Mutex do a = 0 mutex = Thread::Mutex.new - threads = 10.times.map do + threads = Array.new(10) do Thread.new do mutex.synchronize { a += 1 } end - end.to_a + end threads.each(&.join) a.should eq(10) @@ -29,15 +29,16 @@ describe Thread::Mutex do mutex = Thread::Mutex.new mutex.try_lock.should be_true mutex.try_lock.should be_false - expect_raises(RuntimeError, "pthread_mutex_lock: ") { mutex.lock } + expect_raises(RuntimeError) { mutex.lock } mutex.unlock + Thread.new { mutex.synchronize { } }.join end it "won't unlock from another thread" do mutex = Thread::Mutex.new mutex.lock - expect_raises(RuntimeError, "pthread_mutex_unlock: ") do + expect_raises(RuntimeError) do Thread.new { mutex.unlock }.join end diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index 71f6286d9d36..3666b7ad512a 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -27,12 +27,12 @@ class Thread end require "./thread_linked_list" +require "./thread_condition_variable" {% if flag?(:wasi) %} require "./wasi/thread" {% elsif flag?(:unix) %} require "./unix/pthread" - require "./unix/pthread_condition_variable" {% elsif flag?(:win32) %} require "./win32/thread" {% else %} diff --git a/src/crystal/system/thread_condition_variable.cr b/src/crystal/system/thread_condition_variable.cr new file mode 100644 index 000000000000..ea5923601aec --- /dev/null +++ b/src/crystal/system/thread_condition_variable.cr @@ -0,0 +1,31 @@ +class Thread + class ConditionVariable + # Creates a new condition variable. + # def initialize + + # Unblocks one thread that is waiting on `self`. + # def signal : Nil + + # Unblocks all threads that are waiting on `self`. + # def broadcast : Nil + + # Causes the calling thread to wait on `self` and unlock the given *mutex* + # atomically. + # def wait(mutex : Thread::Mutex) : Nil + + # Causes the calling thread to wait on `self` and unlock the given *mutex* + # atomically within the given *time* span. Yields to the given block if a + # timeout occurs. + # def wait(mutex : Thread::Mutex, time : Time::Span, & : ->) + end +end + +{% if flag?(:wasi) %} + require "./wasi/thread_condition_variable" +{% elsif flag?(:unix) %} + require "./unix/pthread_condition_variable" +{% elsif flag?(:win32) %} + require "./win32/thread_condition_variable" +{% else %} + {% raise "thread condition variable not supported" %} +{% end %} diff --git a/src/crystal/system/thread_mutex.cr b/src/crystal/system/thread_mutex.cr index 1a5f3006d6eb..e3cf9ffeb6cb 100644 --- a/src/crystal/system/thread_mutex.cr +++ b/src/crystal/system/thread_mutex.cr @@ -24,5 +24,5 @@ end {% elsif flag?(:win32) %} require "./win32/thread_mutex" {% else %} - {% raise "thread not supported" %} + {% raise "thread mutex not supported" %} {% end %} diff --git a/src/crystal/system/unix/pthread_condition_variable.cr b/src/crystal/system/unix/pthread_condition_variable.cr index 225aad1c7105..a09811c79281 100644 --- a/src/crystal/system/unix/pthread_condition_variable.cr +++ b/src/crystal/system/unix/pthread_condition_variable.cr @@ -33,7 +33,7 @@ class Thread raise RuntimeError.from_os_error("pthread_cond_wait", Errno.new(ret)) unless ret == 0 end - def wait(mutex : Thread::Mutex, time : Time::Span) + def wait(mutex : Thread::Mutex, time : Time::Span, & : ->) ret = {% if flag?(:darwin) %} ts = uninitialized LibC::Timespec diff --git a/src/crystal/system/wasi/thread.cr b/src/crystal/system/wasi/thread.cr index b10439852f55..805c7fbb77a6 100644 --- a/src/crystal/system/wasi/thread.cr +++ b/src/crystal/system/wasi/thread.cr @@ -54,20 +54,4 @@ class Thread # TODO: Implement Pointer(Void).null end - - # :nodoc: - # TODO: Implement - class ConditionVariable - def signal : Nil - end - - def broadcast : Nil - end - - def wait(mutex : Thread::Mutex) : Nil - end - - def wait(mutex : Thread::Mutex, time : Time::Span, &) - end - end end diff --git a/src/crystal/system/wasi/thread_condition_variable.cr b/src/crystal/system/wasi/thread_condition_variable.cr new file mode 100644 index 000000000000..eb205333acdd --- /dev/null +++ b/src/crystal/system/wasi/thread_condition_variable.cr @@ -0,0 +1,16 @@ +# TODO: Implement +class Thread + class ConditionVariable + def signal : Nil + end + + def broadcast : Nil + end + + def wait(mutex : Thread::Mutex) : Nil + end + + def wait(mutex : Thread::Mutex, time : Time::Span, &) + end + end +end diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 291a66d228e6..ef9bd87dcb75 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -21,7 +21,7 @@ struct Crystal::System::Process end def wait - if LibC.WaitForSingleObject(@process_handle, LibC::INFINITE) != 0 + if LibC.WaitForSingleObject(@process_handle, LibC::INFINITE) != LibC::WAIT_OBJECT_0 raise RuntimeError.from_winerror("WaitForSingleObject") end diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr index 1af2ee9660f2..5e7ae49b30fc 100644 --- a/src/crystal/system/win32/thread.cr +++ b/src/crystal/system/win32/thread.cr @@ -1,10 +1,11 @@ require "c/processthreadsapi" +require "c/synchapi" -# TODO: Implement for multithreading. class Thread # all thread objects, so the GC can see them (it doesn't scan thread locals) - @@threads = Thread::LinkedList(Thread).new + protected class_getter(threads) { Thread::LinkedList(Thread).new } + @th : LibC::HANDLE @exception : Exception? @detached = Atomic(UInt8).new(0) @main_fiber : Fiber? @@ -16,42 +17,87 @@ class Thread property previous : Thread? def self.unsafe_each - @@threads.unsafe_each { |thread| yield thread } + threads.unsafe_each { |thread| yield thread } end + # Starts a new system thread. + def initialize(&@func : ->) + @th = uninitialized LibC::HANDLE + + @th = GC.beginthreadex( + security: Pointer(Void).null, + stack_size: LibC::UInt.zero, + start_address: ->(data : Void*) { data.as(Thread).start; LibC::UInt.zero }, + arglist: self.as(Void*), + initflag: LibC::UInt.zero, + thrdaddr: Pointer(LibC::UInt).null) + end + + # Used once to initialize the thread object representing the main thread of + # the process (that already exists). def initialize + # `GetCurrentThread` returns a _constant_ and is only meaningful as an + # argument to Win32 APIs; to uniquely identify it we must duplicate the handle + @th = uninitialized LibC::HANDLE + cur_proc = LibC.GetCurrentProcess + LibC.DuplicateHandle(cur_proc, LibC.GetCurrentThread, cur_proc, pointerof(@th), 0, true, LibC::DUPLICATE_SAME_ACCESS) + + @func = ->{} @main_fiber = Fiber.new(stack_address, self) - @@threads.push(self) + + Thread.threads.push(self) + end + + private def detach + if @detached.compare_and_set(0, 1).last + yield + end end - @@current : Thread? = nil + # Suspends the current thread until this thread terminates. + def join : Nil + detach do + if LibC.WaitForSingleObject(@th, LibC::INFINITE) != LibC::WAIT_OBJECT_0 + @exception ||= RuntimeError.from_winerror("WaitForSingleObject") + end + if LibC.CloseHandle(@th) == 0 + @exception ||= RuntimeError.from_winerror("CloseHandle") + end + end - # Associates the Thread object to the running system thread. - protected def self.current=(@@current : Thread) : Thread + if exception = @exception + raise exception + end end + @[ThreadLocal] + @@current : Thread? + # Returns the Thread object associated to the running system thread. def self.current : Thread - @@current || raise "BUG: Thread.current returned NULL" + @@current ||= new + end + + # Associates the Thread object to the running system thread. + protected def self.current=(@@current : Thread) : Thread end - # Create the thread object for the current thread (aka the main thread of the - # process). - # - # TODO: consider moving to `kernel.cr` or `crystal/main.cr` - self.current = new + def self.yield : Nil + LibC.SwitchToThread + end # Returns the Fiber representing the thread's main stack. - def main_fiber + def main_fiber : Fiber @main_fiber.not_nil! end # :nodoc: - def scheduler + def scheduler : Crystal::Scheduler @scheduler ||= Crystal::Scheduler.new(main_fiber) end protected def start + Thread.threads.push(self) Thread.current = self @main_fiber = fiber = Fiber.new(stack_address, self) @@ -60,9 +106,9 @@ class Thread rescue ex @exception = ex ensure - @@threads.delete(self) + Thread.threads.delete(self) Fiber.inactive(fiber) - detach_self + detach { LibC.CloseHandle(@th) } end end @@ -71,4 +117,9 @@ class Thread Pointer(Void).new(low_limit) end + + # :nodoc: + def to_unsafe + @th + end end diff --git a/src/crystal/system/win32/thread_condition_variable.cr b/src/crystal/system/win32/thread_condition_variable.cr new file mode 100644 index 000000000000..423de9dc57f3 --- /dev/null +++ b/src/crystal/system/win32/thread_condition_variable.cr @@ -0,0 +1,41 @@ +require "c/synchapi" + +# :nodoc: +class Thread + # :nodoc: + class ConditionVariable + def initialize + @cond = uninitialized LibC::CONDITION_VARIABLE + LibC.InitializeConditionVariable(self) + end + + def signal : Nil + LibC.WakeConditionVariable(self) + end + + def broadcast : Nil + LibC.WakeAllConditionVariable(self) + end + + def wait(mutex : Thread::Mutex) : Nil + ret = LibC.SleepConditionVariableCS(self, mutex, LibC::INFINITE) + raise RuntimeError.from_winerror("SleepConditionVariableCS") if ret == 0 + end + + def wait(mutex : Thread::Mutex, time : Time::Span, & : ->) + ret = LibC.SleepConditionVariableCS(self, mutex, time.total_milliseconds) + return if ret != 0 + + error = WinError.value + if error == WinError::ERROR_TIMEOUT + yield + else + raise RuntimeError.from_os_error("SleepConditionVariableCS", error) + end + end + + def to_unsafe + pointerof(@cond) + end + end +end diff --git a/src/crystal/system/win32/thread_mutex.cr b/src/crystal/system/win32/thread_mutex.cr index be681c31398a..afd4cb1fbdcb 100644 --- a/src/crystal/system/win32/thread_mutex.cr +++ b/src/crystal/system/win32/thread_mutex.cr @@ -1,8 +1,61 @@ -# TODO: Implement +require "c/synchapi" + +# :nodoc: class Thread + # :nodoc: + # for Win32 condition variable interop we must use either a critical section + # or a slim reader/writer lock, not a Win32 mutex + # also note critical sections are reentrant; to match the behaviour in + # `../unix/pthread_mutex.cr` we must do extra housekeeping ourselves class Mutex + def initialize + @cs = uninitialized LibC::CRITICAL_SECTION + LibC.InitializeCriticalSectionAndSpinCount(self, 1000) + end + + def lock : Nil + LibC.EnterCriticalSection(self) + if @cs.recursionCount > 1 + LibC.LeaveCriticalSection(self) + raise RuntimeError.new "Attempt to lock a mutex recursively (deadlock)" + end + end + + def try_lock : Bool + if LibC.TryEnterCriticalSection(self) != 0 + if @cs.recursionCount > 1 + LibC.LeaveCriticalSection(self) + false + else + true + end + else + false + end + end + + def unlock : Nil + # `owningThread` is declared as `LibC::HANDLE` for historical reasons, so + # the following comparison is correct + unless @cs.owningThread == LibC::HANDLE.new(LibC.GetCurrentThreadId) + raise RuntimeError.new "Attempt to unlock a mutex locked by another thread" + end + LibC.LeaveCriticalSection(self) + end + def synchronize - yield + lock + yield self + ensure + unlock + end + + def finalize + LibC.DeleteCriticalSection(self) + end + + def to_unsafe + pointerof(@cs) end end end diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index f9a93aef154b..325fa82f31b0 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -107,8 +107,11 @@ lib LibGC fun size = GC_size(addr : Void*) : LibC::SizeT - {% unless flag?(:win32) || flag?(:wasm32) %} - # Boehm GC requires to use GC_pthread_create and GC_pthread_join instead of pthread_create and pthread_join + # Boehm GC requires to use its own thread manipulation routines instead of pthread's or Win32's + {% if flag?(:win32) %} + fun beginthreadex = GC_beginthreadex(security : Void*, stack_size : LibC::UInt, start_address : Void* -> LibC::UInt, + arglist : Void*, initflag : LibC::UInt, thrdaddr : LibC::UInt*) : Void* + {% elsif !flag?(:wasm32) %} fun pthread_create = GC_pthread_create(thread : LibC::PthreadT*, attr : LibC::PthreadAttrT*, start : Void* -> Void*, arg : Void*) : LibC::Int fun pthread_join = GC_pthread_join(thread : LibC::PthreadT, value : Void**) : LibC::Int fun pthread_detach = GC_pthread_detach(thread : LibC::PthreadT) : LibC::Int @@ -241,7 +244,14 @@ module GC reclaimed_bytes_before_gc: stats.reclaimed_bytes_before_gc.to_u64!) end - {% unless flag?(:win32) %} + {% if flag?(:win32) %} + # :nodoc: + def self.beginthreadex(security : Void*, stack_size : LibC::UInt, start_address : Void* -> LibC::UInt, arglist : Void*, initflag : LibC::UInt, thrdaddr : LibC::UInt*) : LibC::HANDLE + ret = LibGC.beginthreadex(security, stack_size, start_address, arglist, initflag, thrdaddr) + raise RuntimeError.from_errno("GC_beginthreadex") if ret.null? + ret.as(LibC::HANDLE) + end + {% else %} # :nodoc: def self.pthread_create(thread : LibC::PthreadT*, attr : LibC::PthreadAttrT*, start : Void* -> Void*, arg : Void*) LibGC.pthread_create(thread, attr, start, arg) diff --git a/src/gc/none.cr b/src/gc/none.cr index c243a071d3b9..2c0530e7599d 100644 --- a/src/gc/none.cr +++ b/src/gc/none.cr @@ -1,3 +1,7 @@ +{% if flag?(:win32) %} + require "c/process" +{% end %} + module GC def self.init end @@ -65,7 +69,14 @@ module GC reclaimed_bytes_before_gc: 0) end - {% unless flag?(:win32) || flag?(:wasm32) %} + {% if flag?(:win32) %} + # :nodoc: + def self.beginthreadex(security : Void*, stack_size : LibC::UInt, start_address : Void* -> LibC::UInt, arglist : Void*, initflag : LibC::UInt, thrdaddr : LibC::UInt*) : LibC::HANDLE + ret = LibC._beginthreadex(security, stack_size, start_address, arglist, initflag, thrdaddr) + raise RuntimeError.from_errno("_beginthreadex") if ret.null? + ret.as(LibC::HANDLE) + end + {% elsif !flag?(:wasm32) %} # :nodoc: def self.pthread_create(thread : LibC::PthreadT*, attr : LibC::PthreadAttrT*, start : Void* -> Void*, arg : Void*) LibC.pthread_create(thread, attr, start, arg) diff --git a/src/lib_c/x86_64-windows-msvc/c/process.cr b/src/lib_c/x86_64-windows-msvc/c/process.cr new file mode 100644 index 000000000000..e567068fb1e7 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/process.cr @@ -0,0 +1,5 @@ +require "lib_c" + +lib LibC + fun _beginthreadex(security : Void*, stack_size : UInt, start_address : Void* -> UInt, arglist : Void*, initflag : UInt, thrdaddr : UInt*) : Void* +end diff --git a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr index 6d0336a37e16..441d9acac697 100644 --- a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr @@ -33,6 +33,7 @@ lib LibC end fun GetCurrentThread : HANDLE + fun GetCurrentThreadId : DWORD fun GetCurrentThreadStackLimits(lowLimit : ULONG_PTR*, highLimit : ULONG_PTR*) : Void fun GetCurrentProcess : HANDLE fun GetCurrentProcessId : DWORD @@ -46,6 +47,7 @@ lib LibC fun SetThreadStackGuarantee(stackSizeInBytes : DWORD*) : BOOL fun GetProcessTimes(hProcess : HANDLE, lpCreationTime : FILETIME*, lpExitTime : FILETIME*, lpKernelTime : FILETIME*, lpUserTime : FILETIME*) : BOOL + fun SwitchToThread : BOOL PROCESS_QUERY_INFORMATION = 0x0400 end diff --git a/src/lib_c/x86_64-windows-msvc/c/synchapi.cr b/src/lib_c/x86_64-windows-msvc/c/synchapi.cr index 23804d0f3aaf..e101b7f6284b 100644 --- a/src/lib_c/x86_64-windows-msvc/c/synchapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/synchapi.cr @@ -1,7 +1,35 @@ require "c/basetsd" require "c/int_safe" +require "c/winbase" +require "c/wtypesbase" lib LibC + # the meanings of these fields are documented not in the Win32 API docs but in + # https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/displaying-a-critical-section + struct CRITICAL_SECTION + debugInfo : Void* # PRTL_CRITICAL_SECTION_DEBUG + lockCount : LONG + recursionCount : LONG + owningThread : HANDLE + lockSemaphore : HANDLE + spinCount : UInt64 + end + + struct CONDITION_VARIABLE + ptr : Void* + end + + fun InitializeCriticalSectionAndSpinCount(lpCriticalSection : CRITICAL_SECTION*, dwSpinCount : DWORD) : BOOL + fun DeleteCriticalSection(lpCriticalSection : CRITICAL_SECTION*) + fun EnterCriticalSection(lpCriticalSection : CRITICAL_SECTION*) + fun TryEnterCriticalSection(lpCriticalSection : CRITICAL_SECTION*) : BOOL + fun LeaveCriticalSection(lpCriticalSection : CRITICAL_SECTION*) + + fun InitializeConditionVariable(conditionVariable : CONDITION_VARIABLE*) + fun SleepConditionVariableCS(conditionVariable : CONDITION_VARIABLE*, criticalSection : CRITICAL_SECTION*, dwMilliseconds : DWORD) : BOOL + fun WakeConditionVariable(conditionVariable : CONDITION_VARIABLE*) + fun WakeAllConditionVariable(conditionVariable : CONDITION_VARIABLE*) + fun Sleep(dwMilliseconds : DWORD) fun WaitForSingleObject(hHandle : HANDLE, dwMilliseconds : DWORD) : DWORD end diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index b213335b0d14..f8b27e4850df 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -31,6 +31,11 @@ lib LibC INFINITE = 0xFFFFFFFF + WAIT_OBJECT_0 = 0x00000000_u32 + WAIT_IO_COMPLETION = 0x000000C0_u32 + WAIT_TIMEOUT = 0x00000102_u32 + WAIT_FAILED = 0xFFFFFFFF_u32 + STARTF_USESTDHANDLES = 0x00000100 MOVEFILE_REPLACE_EXISTING = 0x1_u32 diff --git a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr index 4cf807c18622..223c2366b072 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr @@ -1,6 +1,7 @@ require "./ws2def" require "./basetsd" require "./guiddef" +require "./winbase" @[Link("WS2_32")] lib LibC @@ -65,13 +66,8 @@ lib LibC WSA_INVALID_EVENT = Pointer(WSAEVENT).null WSA_MAXIMUM_WAIT_EVENTS = MAXIMUM_WAIT_OBJECTS WSA_WAIT_FAILED = WAIT_FAILED - STATUS_WAIT_0 = 0_i64 - WAIT_OBJECT_0 = ((STATUS_WAIT_0) + 0) WSA_WAIT_EVENT_0 = WAIT_OBJECT_0 - STATUS_USER_APC = 0xc0 - WAIT_IO_COMPLETION = STATUS_USER_APC WSA_WAIT_IO_COMPLETION = WAIT_IO_COMPLETION - WAIT_TIMEOUT = 258_i64 WSA_WAIT_TIMEOUT = WAIT_TIMEOUT WSA_INFINITE = INFINITE From eb1ebb4400e05497ca94ebc2fb66f24261b0bf08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 13 Dec 2022 11:36:02 +0100 Subject: [PATCH 0179/1551] Rename `Def#yields` to `Def#block_arity` (#12833) --- .../compiler/crystal/tools/doc/method_spec.cr | 2 +- spec/compiler/macro/macro_methods_spec.cr | 2 +- spec/compiler/parser/parser_spec.cr | 58 +++++++++---------- .../crystal/interpreter/multidispatch.cr | 2 +- src/compiler/crystal/macros/methods.cr | 2 +- .../crystal/semantic/abstract_def_checker.cr | 2 +- src/compiler/crystal/semantic/call.cr | 2 +- src/compiler/crystal/semantic/call_error.cr | 6 +- .../crystal/semantic/default_arguments.cr | 6 +- .../crystal/semantic/method_missing.cr | 2 +- src/compiler/crystal/semantic/new.cr | 10 ++-- .../crystal/semantic/type_guess_visitor.cr | 2 +- src/compiler/crystal/syntax/ast.cr | 10 ++-- src/compiler/crystal/syntax/parser.cr | 18 +++--- src/compiler/crystal/tools/context.cr | 2 +- src/compiler/crystal/tools/doc/method.cr | 4 +- src/compiler/crystal/tools/doc/to_json.cr | 3 +- src/compiler/crystal/types.cr | 2 +- 18 files changed, 69 insertions(+), 66 deletions(-) diff --git a/spec/compiler/crystal/tools/doc/method_spec.cr b/spec/compiler/crystal/tools/doc/method_spec.cr index 7a605c4c0ef2..6353115da23e 100644 --- a/spec/compiler/crystal/tools/doc/method_spec.cr +++ b/spec/compiler/crystal/tools/doc/method_spec.cr @@ -72,7 +72,7 @@ describe Doc::Method do generator = Doc::Generator.new program, ["."] doc_type = Doc::Type.new generator, program - a_def = Def.new "foo", yields: 1 + a_def = Def.new "foo", block_arity: 1 doc_method = Doc::Method.new generator, doc_type, a_def, false assert_args_to_s(doc_method, "(&)") end diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index d711b93ccd2e..562185f0ed8e 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2337,7 +2337,7 @@ module Crystal end it "executes accepts_block?" do - assert_macro %({{x.accepts_block?}}), "true", {x: Def.new("some_def", ["x".arg, "y".arg], yields: 1)} + assert_macro %({{x.accepts_block?}}), "true", {x: Def.new("some_def", ["x".arg, "y".arg], block_arity: 1)} assert_macro %({{x.accepts_block?}}), "false", {x: Def.new("some_def")} end diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index b11874676354..3f0e70358a52 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -299,44 +299,44 @@ module Crystal it_parses "def foo(var : Char[N]); end", Def.new("foo", [Arg.new("var", restriction: "Char".static_array_of("N".path))]) it_parses "def foo(var : Int32 = 1); end", Def.new("foo", [Arg.new("var", 1.int32, "Int32".path)]) it_parses "def foo(var : Int32 -> = 1); end", Def.new("foo", [Arg.new("var", 1.int32, ProcNotation.new(["Int32".path] of ASTNode))]) - it_parses "def foo; yield; end", Def.new("foo", body: Yield.new, yields: 0) - it_parses "def foo; yield 1; end", Def.new("foo", body: Yield.new([1.int32] of ASTNode), yields: 1) - it_parses "def foo; yield 1; yield; end", Def.new("foo", body: [Yield.new([1.int32] of ASTNode), Yield.new] of ASTNode, yields: 1) - it_parses "def foo; yield(1); end", Def.new("foo", body: [Yield.new([1.int32] of ASTNode, has_parentheses: true)] of ASTNode, yields: 1) + it_parses "def foo; yield; end", Def.new("foo", body: Yield.new, block_arity: 0) + it_parses "def foo; yield 1; end", Def.new("foo", body: Yield.new([1.int32] of ASTNode), block_arity: 1) + it_parses "def foo; yield 1; yield; end", Def.new("foo", body: [Yield.new([1.int32] of ASTNode), Yield.new] of ASTNode, block_arity: 1) + it_parses "def foo; yield(1); end", Def.new("foo", body: [Yield.new([1.int32] of ASTNode, has_parentheses: true)] of ASTNode, block_arity: 1) it_parses "def foo(a, b = a); end", Def.new("foo", [Arg.new("a"), Arg.new("b", "a".var)]) - it_parses "def foo(&block); end", Def.new("foo", block_arg: Arg.new("block"), yields: 0) - it_parses "def foo(&); end", Def.new("foo", block_arg: Arg.new(""), yields: 0) - it_parses "def foo(&\n); end", Def.new("foo", block_arg: Arg.new(""), yields: 0) - it_parses "def foo(a, &block); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block"), yields: 0) - it_parses "def foo(a, &block : Int -> Double); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path] of ASTNode, "Double".path)), yields: 1) - it_parses "def foo(a, & : Int -> Double); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("", restriction: ProcNotation.new(["Int".path] of ASTNode, "Double".path)), yields: 1) - it_parses "def foo(a, &block : Int, Float -> Double); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path, "Float".path] of ASTNode, "Double".path)), yields: 2) - it_parses "def foo(a, &block : Int, self -> Double); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path, Self.new] of ASTNode, "Double".path)), yields: 2) - it_parses "def foo(a, &block : -> Double); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(nil, "Double".path)), yields: 0) - it_parses "def foo(a, &block : Int -> ); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path] of ASTNode)), yields: 1) - it_parses "def foo(a, &block : self -> self); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new([Self.new] of ASTNode, Self.new)), yields: 1) - it_parses "def foo(a, &block : Foo); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: Path.new("Foo")), yields: 0) - it_parses "def foo; with a yield; end", Def.new("foo", body: Yield.new(scope: "a".call), yields: 1) - it_parses "def foo; with a yield 1; end", Def.new("foo", body: Yield.new([1.int32] of ASTNode, "a".call), yields: 1) - it_parses "def foo; a = 1; with a yield a; end", Def.new("foo", body: [Assign.new("a".var, 1.int32), Yield.new(["a".var] of ASTNode, "a".var)] of ASTNode, yields: 1) + it_parses "def foo(&block); end", Def.new("foo", block_arg: Arg.new("block"), block_arity: 0) + it_parses "def foo(&); end", Def.new("foo", block_arg: Arg.new(""), block_arity: 0) + it_parses "def foo(&\n); end", Def.new("foo", block_arg: Arg.new(""), block_arity: 0) + it_parses "def foo(a, &block); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block"), block_arity: 0) + it_parses "def foo(a, &block : Int -> Double); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path] of ASTNode, "Double".path)), block_arity: 1) + it_parses "def foo(a, & : Int -> Double); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("", restriction: ProcNotation.new(["Int".path] of ASTNode, "Double".path)), block_arity: 1) + it_parses "def foo(a, &block : Int, Float -> Double); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path, "Float".path] of ASTNode, "Double".path)), block_arity: 2) + it_parses "def foo(a, &block : Int, self -> Double); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path, Self.new] of ASTNode, "Double".path)), block_arity: 2) + it_parses "def foo(a, &block : -> Double); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(nil, "Double".path)), block_arity: 0) + it_parses "def foo(a, &block : Int -> ); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path] of ASTNode)), block_arity: 1) + it_parses "def foo(a, &block : self -> self); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new([Self.new] of ASTNode, Self.new)), block_arity: 1) + it_parses "def foo(a, &block : Foo); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: Path.new("Foo")), block_arity: 0) + it_parses "def foo; with a yield; end", Def.new("foo", body: Yield.new(scope: "a".call), block_arity: 1) + it_parses "def foo; with a yield 1; end", Def.new("foo", body: Yield.new([1.int32] of ASTNode, "a".call), block_arity: 1) + it_parses "def foo; a = 1; with a yield a; end", Def.new("foo", body: [Assign.new("a".var, 1.int32), Yield.new(["a".var] of ASTNode, "a".var)] of ASTNode, block_arity: 1) it_parses "def foo(@var); end", Def.new("foo", [Arg.new("var")], [Assign.new("@var".instance_var, "var".var)] of ASTNode) it_parses "def foo(@var); 1; end", Def.new("foo", [Arg.new("var")], [Assign.new("@var".instance_var, "var".var), 1.int32] of ASTNode) it_parses "def foo(@var = 1); 1; end", Def.new("foo", [Arg.new("var", 1.int32)], [Assign.new("@var".instance_var, "var".var), 1.int32] of ASTNode) it_parses "def foo(@@var); end", Def.new("foo", [Arg.new("var")], [Assign.new("@@var".class_var, "var".var)] of ASTNode) it_parses "def foo(@@var); 1; end", Def.new("foo", [Arg.new("var")], [Assign.new("@@var".class_var, "var".var), 1.int32] of ASTNode) it_parses "def foo(@@var = 1); 1; end", Def.new("foo", [Arg.new("var", 1.int32)], [Assign.new("@@var".class_var, "var".var), 1.int32] of ASTNode) - it_parses "def foo(&@block); end", Def.new("foo", body: Assign.new("@block".instance_var, "block".var), block_arg: Arg.new("block"), yields: 0) + it_parses "def foo(&@block); end", Def.new("foo", body: Assign.new("@block".instance_var, "block".var), block_arg: Arg.new("block"), block_arity: 0) # Defs with annotated parameters it_parses "def foo(@[Foo] var); end", Def.new("foo", ["var".arg(annotations: ["Foo".ann])]) it_parses "def foo(@[Foo] outer inner); end", Def.new("foo", ["inner".arg(annotations: ["Foo".ann], external_name: "outer")]) it_parses "def foo(@[Foo] var); end", Def.new("foo", ["var".arg(annotations: ["Foo".ann])]) it_parses "def foo(a, @[Foo] var); end", Def.new("foo", ["a".arg, "var".arg(annotations: ["Foo".ann])]) - it_parses "def foo(a, @[Foo] &block); end", Def.new("foo", ["a".arg], block_arg: "block".arg(annotations: ["Foo".ann]), yields: 0) + it_parses "def foo(a, @[Foo] &block); end", Def.new("foo", ["a".arg], block_arg: "block".arg(annotations: ["Foo".ann]), block_arity: 0) it_parses "def foo(@[Foo] @var); end", Def.new("foo", ["var".arg(annotations: ["Foo".ann])], [Assign.new("@var".instance_var, "var".var)] of ASTNode) it_parses "def foo(@[Foo] var : Int32); end", Def.new("foo", ["var".arg(restriction: "Int32".path, annotations: ["Foo".ann])]) it_parses "def foo(@[Foo] @[Bar] var : Int32); end", Def.new("foo", ["var".arg(restriction: "Int32".path, annotations: ["Foo".ann, "Bar".ann])]) - it_parses "def foo(@[Foo] &@block); end", Def.new("foo", body: Assign.new("@block".instance_var, "block".var), block_arg: "block".arg(annotations: ["Foo".ann]), yields: 0) + it_parses "def foo(@[Foo] &@block); end", Def.new("foo", body: Assign.new("@block".instance_var, "block".var), block_arg: "block".arg(annotations: ["Foo".ann]), block_arity: 0) it_parses "def foo(@[Foo] *args); end", Def.new("foo", args: ["args".arg(annotations: ["Foo".ann])], splat_index: 0) it_parses "def foo(@[Foo] **args); end", Def.new("foo", double_splat: "args".arg(annotations: ["Foo".ann])) it_parses <<-CRYSTAL, Def.new("foo", ["id".arg(restriction: "Int32".path, annotations: ["Foo".ann]), "name".arg(restriction: "String".path, annotations: ["Bar".ann])]) @@ -347,24 +347,24 @@ module Crystal ); end CRYSTAL - it_parses "def foo(\n&block\n); end", Def.new("foo", block_arg: Arg.new("block"), yields: 0) - it_parses "def foo(&block :\n Int ->); end", Def.new("foo", block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path] of ASTNode)), yields: 1) - it_parses "def foo(&block : Int ->\n); end", Def.new("foo", block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path] of ASTNode)), yields: 1) + it_parses "def foo(\n&block\n); end", Def.new("foo", block_arg: Arg.new("block"), block_arity: 0) + it_parses "def foo(&block :\n Int ->); end", Def.new("foo", block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path] of ASTNode)), block_arity: 1) + it_parses "def foo(&block : Int ->\n); end", Def.new("foo", block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path] of ASTNode)), block_arity: 1) - it_parses "def foo(a, &block : *Int -> ); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path.splat] of ASTNode)), yields: 1) + it_parses "def foo(a, &block : *Int -> ); end", Def.new("foo", [Arg.new("a")], block_arg: Arg.new("block", restriction: ProcNotation.new(["Int".path.splat] of ASTNode)), block_arity: 1) it_parses "def foo(x, *args, y = 2); 1; end", Def.new("foo", args: ["x".arg, "args".arg, Arg.new("y", default_value: 2.int32)], body: 1.int32, splat_index: 1) it_parses "def foo(x, *args, y = 2, w, z = 3); 1; end", Def.new("foo", args: ["x".arg, "args".arg, Arg.new("y", default_value: 2.int32), "w".arg, Arg.new("z", default_value: 3.int32)], body: 1.int32, splat_index: 1) it_parses "def foo(x, *, y); 1; end", Def.new("foo", args: ["x".arg, "".arg, "y".arg], body: 1.int32, splat_index: 1) assert_syntax_error "def foo(x, *); 1; end", "named parameters must follow bare *" - it_parses "def foo(x, *, y, &); 1; end", Def.new("foo", args: ["x".arg, "".arg, "y".arg], body: 1.int32, splat_index: 1, block_arg: Arg.new(""), yields: 0) + it_parses "def foo(x, *, y, &); 1; end", Def.new("foo", args: ["x".arg, "".arg, "y".arg], body: 1.int32, splat_index: 1, block_arg: Arg.new(""), block_arity: 0) assert_syntax_error "def foo(var = 1 : Int32); end", "the syntax for a parameter with a default value V and type T is `param : T = V`" assert_syntax_error "def foo(var = x : Int); end", "the syntax for a parameter with a default value V and type T is `param : T = V`" it_parses "def foo(**args)\n1\nend", Def.new("foo", body: 1.int32, double_splat: "args".arg) it_parses "def foo(x, **args)\n1\nend", Def.new("foo", body: 1.int32, args: ["x".arg], double_splat: "args".arg) - it_parses "def foo(x, **args, &block)\n1\nend", Def.new("foo", body: 1.int32, args: ["x".arg], double_splat: "args".arg, block_arg: "block".arg, yields: 0) + it_parses "def foo(x, **args, &block)\n1\nend", Def.new("foo", body: 1.int32, args: ["x".arg], double_splat: "args".arg, block_arg: "block".arg, block_arity: 0) it_parses "def foo(**args)\nargs\nend", Def.new("foo", body: "args".var, double_splat: "args".arg) it_parses "def foo(x = 1, **args)\n1\nend", Def.new("foo", body: 1.int32, args: [Arg.new("x", default_value: 1.int32)], double_splat: "args".arg) it_parses "def foo(**args : Foo)\n1\nend", Def.new("foo", body: 1.int32, double_splat: Arg.new("args", restriction: "Foo".path)) @@ -2411,7 +2411,7 @@ module Crystal it "doesn't override yield with macro yield" do parser = Parser.new("def foo; yield 1; {% begin %} yield 1 {% end %}; end") a_def = parser.parse.as(Def) - a_def.yields.should eq(1) + a_def.block_arity.should eq(1) end it "correctly computes line number after `\\{%\n` (#9857)" do diff --git a/src/compiler/crystal/interpreter/multidispatch.cr b/src/compiler/crystal/interpreter/multidispatch.cr index 5e74aee2bbe8..005c67dca47e 100644 --- a/src/compiler/crystal/interpreter/multidispatch.cr +++ b/src/compiler/crystal/interpreter/multidispatch.cr @@ -133,7 +133,7 @@ module Crystal::Repl::Multidispatch a_def.uses_block_arg = true else a_def.block_arg = Arg.new("") - a_def.yields = block.args.size + a_def.block_arity = block.args.size end end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index aa6a40cf8138..2ecdd2c141c8 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1374,7 +1374,7 @@ module Crystal when "block_arg" interpret_check_args { @block_arg || Nop.new } when "accepts_block?" - interpret_check_args { BoolLiteral.new(@yields != nil) } + interpret_check_args { BoolLiteral.new(@block_arity != nil) } when "return_type" interpret_check_args { @return_type || Nop.new } when "free_vars" diff --git a/src/compiler/crystal/semantic/abstract_def_checker.cr b/src/compiler/crystal/semantic/abstract_def_checker.cr index c2357cff7c65..2a7ccdc05d2a 100644 --- a/src/compiler/crystal/semantic/abstract_def_checker.cr +++ b/src/compiler/crystal/semantic/abstract_def_checker.cr @@ -140,7 +140,7 @@ class Crystal::AbstractDefChecker def implements?(target_type : Type, t1 : Type, m1 : Def, free_vars1, t2 : Type, m2 : Def, free_vars2) return false if m1.abstract? return false unless m1.name == m2.name - return false unless m1.yields == m2.yields + return false unless m1.block_arity == m2.block_arity m1_args, m1_kargs = def_arg_ranges(m1) m2_args, m2_kargs = def_arg_ranges(m2) diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index e3b0dcf47012..314db053c75f 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -789,7 +789,7 @@ class Crystal::Call def match_block_arg(match) block_arg = match.def.block_arg return nil, nil unless block_arg - return nil, nil unless match.def.yields || match.def.uses_block_arg? + return nil, nil unless match.def.block_arity || match.def.uses_block_arg? yield_vars = nil block_arg_type = nil diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index 0271724b5b0a..2984ed5fd4c2 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -405,7 +405,7 @@ class Crystal::Call extra_types : Array(Type)? private def compute_call_error_reason(owner, a_def, arg_types, named_args_types) - if (block && !a_def.yields) || (!block && a_def.yields) + if (block && !a_def.block_arity) || (!block && a_def.block_arity) return BlockMismatch.new end @@ -660,7 +660,7 @@ class Crystal::Call all_arguments_sizes = [] of Int32 min_splat = Int32::MAX defs.each do |a_def| - next if (block && !a_def.yields) || (!block && a_def.yields) + next if (block && !a_def.block_arity) || (!block && a_def.block_arity) min_size, max_size = a_def.min_max_args_sizes if max_size == Int32::MAX @@ -869,7 +869,7 @@ class Crystal::Call printed = true end - if a_def.yields + if a_def.block_arity str << ", " if printed str << '&' if block_arg = a_def.block_arg diff --git a/src/compiler/crystal/semantic/default_arguments.cr b/src/compiler/crystal/semantic/default_arguments.cr index a4f9dcc67e4d..013caab3c3aa 100644 --- a/src/compiler/crystal/semantic/default_arguments.cr +++ b/src/compiler/crystal/semantic/default_arguments.cr @@ -34,7 +34,7 @@ class Crystal::Def end end - retain_body = yields || splat_index || double_splat || assigns_special_var? || macro_def? || args.any? { |arg| arg.default_value && arg.restriction } + retain_body = block_arity || splat_index || double_splat || assigns_special_var? || macro_def? || args.any? { |arg| arg.default_value && arg.restriction } splat_index = self.splat_index double_splat = self.double_splat @@ -94,13 +94,13 @@ class Crystal::Def new_name = name end - expansion = Def.new(new_name, new_args, nil, receiver.clone, block_arg.clone, return_type.clone, macro_def?, yields).at(self) + expansion = Def.new(new_name, new_args, nil, receiver.clone, block_arg.clone, return_type.clone, macro_def?, block_arity).at(self) expansion.args.each { |arg| arg.default_value = nil } expansion.calls_super = calls_super? expansion.calls_initialize = calls_initialize? expansion.calls_previous_def = calls_previous_def? expansion.uses_block_arg = uses_block_arg? - expansion.yields = yields + expansion.block_arity = block_arity expansion.raises = raises? expansion.free_vars = free_vars expansion.annotations = annotations diff --git a/src/compiler/crystal/semantic/method_missing.cr b/src/compiler/crystal/semantic/method_missing.cr index b48af9b6d4a2..54a15355f712 100644 --- a/src/compiler/crystal/semantic/method_missing.cr +++ b/src/compiler/crystal/semantic/method_missing.cr @@ -93,7 +93,7 @@ module Crystal end a_def.body = generated_nodes - a_def.yields = block.try &.args.size + a_def.block_arity = block.try &.args.size end owner = self diff --git a/src/compiler/crystal/semantic/new.cr b/src/compiler/crystal/semantic/new.cr index 91943fab94f9..de8ae55312a0 100644 --- a/src/compiler/crystal/semantic/new.cr +++ b/src/compiler/crystal/semantic/new.cr @@ -73,7 +73,7 @@ module Crystal inherits_from_generic = type.ancestors.any?(GenericClassInstanceType) if is_generic || inherits_from_generic has_default_self_new = self_new_methods.any? do |a_def| - a_def.args.empty? && !a_def.yields + a_def.args.empty? && !a_def.block_arity end # For a generic class type we need to define `new` even @@ -92,7 +92,7 @@ module Crystal initialize_methods.each do |initialize| # If the type has `self.new()`, don't override it - if initialize.args.empty? && !initialize.yields && has_default_self_new + if initialize.args.empty? && !initialize.block_arity && has_default_self_new next end @@ -137,7 +137,7 @@ module Crystal new_def = Def.new("new", def_args, Nop.new).at(self) new_def.splat_index = splat_index new_def.double_splat = double_splat.clone - new_def.yields = yields + new_def.block_arity = block_arity new_def.visibility = visibility new_def.new = true new_def.doc = doc @@ -204,7 +204,7 @@ module Crystal # If the initialize yields, call it with a block # that yields those arguments. - if block_args_count = self.yields + if block_args_count = self.block_arity block_args = Array.new(block_args_count) { |i| Var.new("_arg#{i}") } vars = Array.new(block_args_count) { |i| Var.new("_arg#{i}").at(self).as(ASTNode) } init.block = Block.new(block_args, Yield.new(vars).at(self)).at(self) @@ -289,7 +289,7 @@ module Crystal end expansion = Def.new(name, def_args, Nop.new, splat_index: splat_index).at(self) - expansion.yields = yields + expansion.block_arity = block_arity expansion.visibility = visibility expansion.annotations = annotations diff --git a/src/compiler/crystal/semantic/type_guess_visitor.cr b/src/compiler/crystal/semantic/type_guess_visitor.cr index 64c84ebcb888..4eb13804f64d 100644 --- a/src/compiler/crystal/semantic/type_guess_visitor.cr +++ b/src/compiler/crystal/semantic/type_guess_visitor.cr @@ -769,7 +769,7 @@ module Crystal defs = metaclass.lookup_defs(node.name) defs = defs.select do |a_def| - a_def_has_block = !!a_def.yields + a_def_has_block = !!a_def.block_arity call_has_block = !!(node.block || node.block_arg) next unless a_def_has_block == call_has_block diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index c434869d36a9..cf8339ceec85 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -1056,7 +1056,9 @@ module Crystal property body : ASTNode property block_arg : Arg? property return_type : ASTNode? - property yields : Int32? + # Number of block arguments accepted by this method. + # `nil` if it does not receive a block. + property block_arity : Int32? property name_location : Location? property splat_index : Int32? property doc : String? @@ -1070,7 +1072,7 @@ module Crystal property? assigns_special_var = false property? abstract : Bool - def initialize(@name, @args = [] of Arg, body = nil, @receiver = nil, @block_arg = nil, @return_type = nil, @macro_def = false, @yields = nil, @abstract = false, @splat_index = nil, @double_splat = nil, @free_vars = nil) + def initialize(@name, @args = [] of Arg, body = nil, @receiver = nil, @block_arg = nil, @return_type = nil, @macro_def = false, @block_arity = nil, @abstract = false, @splat_index = nil, @double_splat = nil, @free_vars = nil) @body = Expressions.from body end @@ -1088,7 +1090,7 @@ module Crystal end def clone_without_location - a_def = Def.new(@name, @args.clone, @body.clone, @receiver.clone, @block_arg.clone, @return_type.clone, @macro_def, @yields, @abstract, @splat_index, @double_splat.clone, @free_vars) + a_def = Def.new(@name, @args.clone, @body.clone, @receiver.clone, @block_arg.clone, @return_type.clone, @macro_def, @block_arity, @abstract, @splat_index, @double_splat.clone, @free_vars) a_def.calls_super = calls_super? a_def.calls_initialize = calls_initialize? a_def.calls_previous_def = calls_previous_def? @@ -1099,7 +1101,7 @@ module Crystal a_def end - def_equals_and_hash @name, @args, @body, @receiver, @block_arg, @return_type, @macro_def, @yields, @abstract, @splat_index, @double_splat + def_equals_and_hash @name, @args, @body, @receiver, @block_arg, @return_type, @macro_def, @block_arity, @abstract, @splat_index, @double_splat end class Macro < ASTNode diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 5b772962c98d..df754e7dd7e8 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3170,7 +3170,7 @@ module Crystal next_macro_token macro_state, skip_whitespace macro_state = @token.macro_state if macro_state.yields - @yields ||= 0 + @block_arity ||= 0 end skip_whitespace = false @@ -3501,7 +3501,7 @@ module Crystal consume_def_or_macro_name receiver = nil - @yields = nil + @block_arity = nil name_location = @token.location receiver_location = @token.location end_location = token_end_location @@ -3682,7 +3682,7 @@ module Crystal @def_nest -= 1 @doc_enabled = !!@wants_doc - node = Def.new name, params, body, receiver, block_param, return_type, @is_macro_def, @yields, is_abstract, splat_index, double_splat: double_splat, free_vars: free_vars + node = Def.new name, params, body, receiver, block_param, return_type, @is_macro_def, @block_arity, is_abstract, splat_index, double_splat: double_splat, free_vars: free_vars node.name_location = name_location set_visibility node node.end_location = end_location @@ -3723,9 +3723,9 @@ module Crystal def compute_block_arg_yields(block_arg) block_arg_restriction = block_arg.restriction if block_arg_restriction.is_a?(ProcNotation) - @yields = block_arg_restriction.inputs.try(&.size) || 0 + @block_arity = block_arg_restriction.inputs.try(&.size) || 0 else - @yields = 0 + @block_arity = 0 end end @@ -5450,7 +5450,7 @@ module Crystal location = @token.location next_token_skip_space @stop_on_yield += 1 - @yields ||= 1 + @block_arity ||= 1 scope = parse_op_assign @stop_on_yield -= 1 skip_space @@ -5469,9 +5469,9 @@ module Crystal end_location = nil end - yields = (@yields ||= 0) - if args && args.size > yields - @yields = args.size + block_arity = (@block_arity ||= 0) + if args && args.size > block_arity + @block_arity = args.size end Yield.new(args || [] of ASTNode, scope, !!call_args.try(&.has_parentheses)).at(location).at_end(end_location) diff --git a/src/compiler/crystal/tools/context.cr b/src/compiler/crystal/tools/context.cr index 0d7a0a98d55a..7bf49397fd7c 100644 --- a/src/compiler/crystal/tools/context.cr +++ b/src/compiler/crystal/tools/context.cr @@ -175,7 +175,7 @@ module Crystal def visit(node : Def) return false unless contains_target(node) - if @def_with_yield.nil? && !node.yields.nil? + if @def_with_yield.nil? && !node.block_arity.nil? @def_with_yield = node return false end diff --git a/src/compiler/crystal/tools/doc/method.cr b/src/compiler/crystal/tools/doc/method.cr index 44bebd389220..dee37cb6d1b8 100644 --- a/src/compiler/crystal/tools/doc/method.cr +++ b/src/compiler/crystal/tools/doc/method.cr @@ -241,7 +241,7 @@ class Crystal::Doc::Method io << ", " if printed io << '&' arg_to_html block_arg, io, html: html - elsif @def.yields + elsif @def.block_arity io << ", " if printed io << '&' end @@ -314,7 +314,7 @@ class Crystal::Doc::Method end def has_args? - !@def.args.empty? || @def.double_splat || @def.block_arg || @def.yields + !@def.args.empty? || @def.double_splat || @def.block_arg || @def.block_arity end def to_json(builder : JSON::Builder) diff --git a/src/compiler/crystal/tools/doc/to_json.cr b/src/compiler/crystal/tools/doc/to_json.cr index 846fb39d1700..aa1ead27e275 100644 --- a/src/compiler/crystal/tools/doc/to_json.cr +++ b/src/compiler/crystal/tools/doc/to_json.cr @@ -17,7 +17,8 @@ class Crystal::Def builder.field "args", args unless args.empty? builder.field "double_splat", double_splat unless double_splat.nil? builder.field "splat_index", splat_index unless splat_index.nil? - builder.field "yields", yields unless yields.nil? + builder.field "yields", block_arity unless block_arity.nil? + builder.field "block_arity", block_arity unless block_arity.nil? builder.field "block_arg", block_arg unless block_arg.nil? builder.field "return_type", return_type.to_s unless return_type.nil? builder.field "visibility", visibility.to_s diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index d60c5fbff879..a1cdb71c9d7f 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -843,7 +843,7 @@ module Crystal def : Def do def self.new(a_def : Def) min_size, max_size = a_def.min_max_args_sizes - new min_size, max_size, !!a_def.yields, a_def + new min_size, max_size, !!a_def.block_arity, a_def end end From c8f1af546abe005bb70cdda325b4037da2fb7511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 14 Dec 2022 12:07:01 +0100 Subject: [PATCH 0180/1551] Extract internal Regex API for PCRE backend (#12802) --- src/regex.cr | 90 ++++------------------- src/regex/engine.cr | 4 ++ src/regex/lib_pcre.cr | 10 +++ src/regex/match_data.cr | 65 ++++------------- src/regex/pcre.cr | 156 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 199 insertions(+), 126 deletions(-) create mode 100644 src/regex/engine.cr create mode 100644 src/regex/pcre.cr diff --git a/src/regex.cr b/src/regex.cr index b3ccd7bf0bec..136671e2f65c 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -1,4 +1,5 @@ -require "./regex/*" +require "./regex/engine" +require "./regex/match_data" # A `Regex` represents a regular expression, a pattern that describes the # contents of strings. A `Regex` can determine whether or not a string matches @@ -195,6 +196,8 @@ require "./regex/*" # `Hash` of `String` => `Int32`, and therefore requires named capture groups to have # unique names within a single `Regex`. class Regex + include Regex::Engine + # List of metacharacters that need to be escaped. # # See `Regex.needs_escape?` and `Regex.escape`. @@ -253,28 +256,8 @@ class Regex # options = Regex::Options::IGNORE_CASE | Regex::Options::EXTENDED # Regex.new("dog", options) # => /dog/ix # ``` - def initialize(source : String, @options : Options = Options::None) - # PCRE's pattern must have their null characters escaped - source = source.gsub('\u{0}', "\\0") - @source = source - - @re = LibPCRE.compile(@source, (options | Options::UTF_8 | Options::NO_UTF8_CHECK | Options::DUPNAMES | Options::UCP), out errptr, out erroffset, nil) - raise ArgumentError.new("#{String.new(errptr)} at #{erroffset}") if @re.null? - @extra = LibPCRE.study(@re, LibPCRE::STUDY_JIT_COMPILE, out studyerrptr) - if @extra.null? && studyerrptr - {% unless flag?(:interpreted) %} - LibPCRE.free.call @re.as(Void*) - {% end %} - raise ArgumentError.new("#{String.new(studyerrptr)}") - end - LibPCRE.full_info(@re, nil, LibPCRE::INFO_CAPTURECOUNT, out @captures) - end - - def finalize - LibPCRE.free_study @extra - {% unless flag?(:interpreted) %} - LibPCRE.free.call @re.as(Void*) - {% end %} + def self.new(source : String, options : Options = Options::None) + new(_source: source, _options: options) end # Determines Regex's source validity. If it is, `nil` is returned. @@ -285,15 +268,7 @@ class Regex # Regex.error?("(foo|bar") # => "missing ) at 8" # ``` def self.error?(source) : String? - re = LibPCRE.compile(source, (Options::UTF_8 | Options::NO_UTF8_CHECK | Options::DUPNAMES), out errptr, out erroffset, nil) - if re - {% unless flag?(:interpreted) %} - LibPCRE.free.call re.as(Void*) - {% end %} - nil - else - "#{String.new(errptr)} at #{erroffset}" - end + Engine.error_impl(source) end # Returns `true` if *char* need to be escaped, `false` otherwise. @@ -485,12 +460,10 @@ class Regex # ``` def match(str, pos = 0, options = Regex::Options::None) : MatchData? if byte_index = str.char_index_to_byte_index(pos) - match = match_at_byte_index(str, byte_index, options) + $~ = match_at_byte_index(str, byte_index, options) else - match = nil + $~ = nil end - - $~ = match end # Match at byte index. Matches a regular expression against `String` @@ -504,17 +477,11 @@ class Regex # /(.)(.)/.match_at_byte_index("クリスタル", 3).try &.[2] # => "ス" # ``` def match_at_byte_index(str, byte_index = 0, options = Regex::Options::None) : MatchData? - return ($~ = nil) if byte_index > str.bytesize - - ovector_size = (@captures + 1) * 3 - ovector = Pointer(Int32).malloc(ovector_size) - if internal_matches?(str, byte_index, options, ovector, ovector_size) - match = MatchData.new(self, @re, str, byte_index, ovector, @captures) + if byte_index > str.bytesize + $~ = nil else - match = nil + $~ = match_impl(str, byte_index, options) end - - $~ = match end # Match at character index. It behaves like `#match`, however it returns `Bool` value. @@ -540,14 +507,7 @@ class Regex def matches_at_byte_index?(str, byte_index = 0, options = Regex::Options::None) : Bool return false if byte_index > str.bytesize - internal_matches?(str, byte_index, options, nil, 0) - end - - # Calls `pcre_exec` C function, and handles returning value. - private def internal_matches?(str, byte_index, options, ovector, ovector_size) - ret = LibPCRE.exec(@re, @extra, str, str.bytesize, byte_index, (options | Options::NO_UTF8_CHECK), ovector, ovector_size) - # TODO: when `ret < -1`, it means PCRE error. It should handle correctly. - ret >= 0 + matches_impl(str, byte_index, options) end # Returns a `Hash` where the values are the names of capture groups and the @@ -561,26 +521,7 @@ class Regex # /(.)(?.)(.)(?.)(.)/.name_table # => {4 => "bar", 2 => "foo"} # ``` def name_table : Hash(Int32, String) - LibPCRE.full_info(@re, @extra, LibPCRE::INFO_NAMECOUNT, out name_count) - LibPCRE.full_info(@re, @extra, LibPCRE::INFO_NAMEENTRYSIZE, out name_entry_size) - table_pointer = Pointer(UInt8).null - LibPCRE.full_info(@re, @extra, LibPCRE::INFO_NAMETABLE, pointerof(table_pointer).as(Pointer(Int32))) - name_table = table_pointer.to_slice(name_entry_size*name_count) - - lookup = Hash(Int32, String).new - - name_count.times do |i| - capture_offset = i * name_entry_size - capture_number = ((name_table[capture_offset].to_u16 << 8)).to_i32 | name_table[capture_offset + 1] - - name_offset = capture_offset + 2 - checked = name_table[name_offset, name_entry_size - 3] - name = String.new(checked.to_unsafe) - - lookup[capture_number] = name - end - - lookup + name_table_impl end # Returns the number of (named & non-named) capture groups. @@ -592,8 +533,7 @@ class Regex # /(.)|(.)/.capture_count # => 2 # ``` def capture_count : Int32 - LibPCRE.full_info(@re, @extra, LibPCRE::INFO_CAPTURECOUNT, out capture_count) - capture_count + capture_count_impl end # Convert to `String` in subpattern format. Produces a `String` which can be diff --git a/src/regex/engine.cr b/src/regex/engine.cr new file mode 100644 index 000000000000..ad69e5d034bf --- /dev/null +++ b/src/regex/engine.cr @@ -0,0 +1,4 @@ +require "./pcre" + +# :nodoc: +alias Regex::Engine = PCRE diff --git a/src/regex/lib_pcre.cr b/src/regex/lib_pcre.cr index 2182153870f0..cf32142c5358 100644 --- a/src/regex/lib_pcre.cr +++ b/src/regex/lib_pcre.cr @@ -2,6 +2,16 @@ lib LibPCRE alias Int = LibC::Int + CASELESS = 0x00000001 + MULTILINE = 0x00000002 + DOTALL = 0x00000004 + EXTENDED = 0x00000008 + ANCHORED = 0x00000010 + UTF8 = 0x00000800 + NO_UTF8_CHECK = 0x00002000 + DUPNAMES = 0x00080000 + UCP = 0x20000000 + type Pcre = Void* type PcreExtra = Void* fun compile = pcre_compile(pattern : UInt8*, options : Int, errptr : UInt8**, erroffset : Int*, tableptr : Void*) : Pcre diff --git a/src/regex/match_data.cr b/src/regex/match_data.cr index 085c829073e8..949bce29e603 100644 --- a/src/regex/match_data.cr +++ b/src/regex/match_data.cr @@ -16,6 +16,8 @@ class Regex # starting from `1`, so that `0` can be used to refer to the entire regular # expression without needing to capture it explicitly. struct MatchData + include Engine::MatchData + # Returns the original regular expression. # # ``` @@ -39,10 +41,6 @@ class Regex # ``` getter string : String - # :nodoc: - def initialize(@regex : Regex, @code : LibPCRE::Pcre, @string : String, @pos : Int32, @ovector : Int32*, @group_size : Int32) - end - # Returns the number of elements in this match object. # # ``` @@ -109,10 +107,7 @@ class Regex # ``` def byte_begin(n = 0) : Int32 check_index_out_of_bounds n - n += size if n < 0 - value = @ovector[n * 2] - raise_capture_group_was_not_matched(n) if value < 0 - value + byte_range(n) { |normalized_n| raise_capture_group_was_not_matched(normalized_n) }.begin end # Returns the position of the next byte after the match. @@ -132,10 +127,7 @@ class Regex # ``` def byte_end(n = 0) : Int32 check_index_out_of_bounds n - n += size if n < 0 - value = @ovector[n * 2 + 1] - raise_capture_group_was_not_matched(n) if value < 0 - value + byte_range(n) { |normalized_n| raise_capture_group_was_not_matched(normalized_n) }.end end # Returns the match of the *n*th capture group, or `nil` if there isn't @@ -151,11 +143,8 @@ class Regex def []?(n : Int) : String? return unless valid_group?(n) - n += size if n < 0 - start = @ovector[n * 2] - finish = @ovector[n * 2 + 1] - return if start < 0 - @string.byte_slice(start, finish - start) + range = byte_range(n) { return nil } + @string.byte_slice(range.begin, range.end - range.begin) end # Returns the match of the *n*th capture group, or raises an `IndexError` @@ -167,11 +156,9 @@ class Regex # ``` def [](n : Int) : String check_index_out_of_bounds n - n += size if n < 0 - value = self[n]? - raise_capture_group_was_not_matched n if value.nil? - value + range = byte_range(n) { |normalized_n| raise_capture_group_was_not_matched(normalized_n) } + @string.byte_slice(range.begin, range.end - range.begin) end # Returns the match of the capture group named by *group_name*, or @@ -189,16 +176,7 @@ class Regex # "Crystal".match(/(?Cr).*(?al)/).not_nil!["ok"]? # => "al" # ``` def []?(group_name : String) : String? - max_start = -1 - match = nil - named_capture_number(group_name) do |n| - start = @ovector[n * 2] - if start > max_start - max_start = start - match = self[n]? - end - end - match + fetch_impl(group_name) { nil } end # Returns the match of the capture group named by *group_name*, or @@ -216,14 +194,13 @@ class Regex # "Crystal".match(/(?Cr).*(?al)/).not_nil!["ok"] # => "al" # ``` def [](group_name : String) : String - match = self[group_name]? - unless match - named_capture_number(group_name) do + fetch_impl(group_name) { |exists| + if exists raise KeyError.new("Capture group '#{group_name}' was not matched") + else + raise KeyError.new("Capture group '#{group_name}' does not exist") end - raise KeyError.new("Capture group '#{group_name}' does not exist") - end - match + } end # Returns all matches that are within the given range. @@ -249,20 +226,6 @@ class Regex Array(String).new(count) { |i| self[start + i] } end - private def named_capture_number(group_name) - name_entry_size = LibPCRE.get_stringtable_entries(@code, group_name, out first, out last) - return if name_entry_size < 0 - - while first <= last - capture_number = (first[0].to_u16 << 8) | first[1].to_u16 - yield capture_number - - first += name_entry_size - end - - nil - end - # Returns the part of the original string before the match. If the match # starts at the start of the string, returns the empty string. # diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr new file mode 100644 index 000000000000..ff68509bed9e --- /dev/null +++ b/src/regex/pcre.cr @@ -0,0 +1,156 @@ +require "./lib_pcre" + +# :nodoc: +module Regex::PCRE + private def initialize(*, _source source, _options @options) + # PCRE's pattern must have their null characters escaped + source = source.gsub('\u{0}', "\\0") + @source = source + + @re = LibPCRE.compile(@source, pcre_options(options) | LibPCRE::UTF8 | LibPCRE::NO_UTF8_CHECK | LibPCRE::DUPNAMES | LibPCRE::UCP, out errptr, out erroffset, nil) + raise ArgumentError.new("#{String.new(errptr)} at #{erroffset}") if @re.null? + @extra = LibPCRE.study(@re, LibPCRE::STUDY_JIT_COMPILE, out studyerrptr) + if @extra.null? && studyerrptr + {% unless flag?(:interpreted) %} + LibPCRE.free.call @re.as(Void*) + {% end %} + raise ArgumentError.new("#{String.new(studyerrptr)}") + end + LibPCRE.full_info(@re, nil, LibPCRE::INFO_CAPTURECOUNT, out @captures) + end + + private def pcre_options(options) + flag = 0 + options.each do |option| + flag |= case option + when .ignore_case? then LibPCRE::CASELESS + when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE + when .extended? then LibPCRE::EXTENDED + when .anchored? then LibPCRE::ANCHORED + when .utf_8? then LibPCRE::UTF8 + when .no_utf8_check? then LibPCRE::NO_UTF8_CHECK + when .dupnames? then LibPCRE::DUPNAMES + when .ucp? then LibPCRE::UCP + else + # Unnamed values are explicitly used PCRE options, just pass them through: + option.value + end + end + flag + end + + def finalize + LibPCRE.free_study @extra + {% unless flag?(:interpreted) %} + LibPCRE.free.call @re.as(Void*) + {% end %} + end + + protected def self.error_impl(source) + re = LibPCRE.compile(source, LibPCRE::UTF8 | LibPCRE::NO_UTF8_CHECK | LibPCRE::DUPNAMES, out errptr, out erroffset, nil) + if re + {% unless flag?(:interpreted) %} + LibPCRE.free.call re.as(Void*) + {% end %} + nil + else + "#{String.new(errptr)} at #{erroffset}" + end + end + + private def name_table_impl + LibPCRE.full_info(@re, @extra, LibPCRE::INFO_NAMECOUNT, out name_count) + LibPCRE.full_info(@re, @extra, LibPCRE::INFO_NAMEENTRYSIZE, out name_entry_size) + table_pointer = Pointer(UInt8).null + LibPCRE.full_info(@re, @extra, LibPCRE::INFO_NAMETABLE, pointerof(table_pointer).as(Pointer(Int32))) + name_table = table_pointer.to_slice(name_entry_size*name_count) + + lookup = Hash(Int32, String).new + + name_count.times do |i| + capture_offset = i * name_entry_size + capture_number = ((name_table[capture_offset].to_u16 << 8)).to_i32 | name_table[capture_offset + 1] + + name_offset = capture_offset + 2 + checked = name_table[name_offset, name_entry_size - 3] + name = String.new(checked.to_unsafe) + + lookup[capture_number] = name + end + + lookup + end + + private def capture_count_impl + LibPCRE.full_info(@re, @extra, LibPCRE::INFO_CAPTURECOUNT, out capture_count) + capture_count + end + + private def match_impl(str, byte_index, options) + ovector_size = (@captures + 1) * 3 + ovector = Pointer(Int32).malloc(ovector_size) + if internal_matches?(str, byte_index, options, ovector, ovector_size) + Regex::MatchData.new(self, @re, str, byte_index, ovector, @captures) + end + end + + private def matches_impl(str, byte_index, options) + internal_matches?(str, byte_index, options, nil, 0) + end + + # Calls `pcre_exec` C function, and handles returning value. + private def internal_matches?(str, byte_index, options, ovector, ovector_size) + ret = LibPCRE.exec(@re, @extra, str, str.bytesize, byte_index, pcre_options(options) | LibPCRE::NO_UTF8_CHECK, ovector, ovector_size) + # TODO: when `ret < -1`, it means PCRE error. It should handle correctly. + ret >= 0 + end + + module MatchData + # :nodoc: + def initialize(@regex : ::Regex, @code : LibPCRE::Pcre, @string : String, @pos : Int32, @ovector : Int32*, @group_size : Int32) + end + + private def byte_range(n, &) + n += size if n < 0 + range = Range.new(@ovector[n * 2], @ovector[n * 2 + 1], exclusive: true) + if range.begin < 0 || range.end < 0 + yield n + else + range + end + end + + private def fetch_impl(group_name : String) + max_start = -1 + match = nil + exists = false + each_named_capture_number(group_name) do |n| + exists = true + start = byte_range(n) { nil }.try(&.begin) || next + if start > max_start + max_start = start + match = self[n]? + end + end + if match + match + else + yield exists + end + end + + private def each_named_capture_number(group_name) + name_entry_size = LibPCRE.get_stringtable_entries(@code, group_name, out first, out last) + return if name_entry_size < 0 + + while first <= last + capture_number = (first[0].to_u16 << 8) | first[1].to_u16 + yield capture_number + + first += name_entry_size + end + + nil + end + end +end From ce28902ad5d5b7d1a660146a04158330aafad8e7 Mon Sep 17 00:00:00 2001 From: Mike Robbins Date: Wed, 14 Dec 2022 06:07:33 -0500 Subject: [PATCH 0181/1551] Automatically cast Int to Float for `{JSON,YAML}::Any#as_f` (#12835) --- spec/std/json/any_spec.cr | 22 +++++++++++++++++ spec/std/json/serializable_spec.cr | 11 +++++++++ spec/std/yaml/any_spec.cr | 24 +++++++++++++++++++ src/json/any.cr | 38 +++++++++++++++++++++++------- src/yaml/any.cr | 38 +++++++++++++++++++++++------- 5 files changed, 117 insertions(+), 16 deletions(-) diff --git a/spec/std/json/any_spec.cr b/spec/std/json/any_spec.cr index 9758bbad0022..6601a53378bd 100644 --- a/spec/std/json/any_spec.cr +++ b/spec/std/json/any_spec.cr @@ -30,16 +30,38 @@ describe JSON::Any do it "gets float32" do JSON.parse("123.45").as_f32.should eq(123.45_f32) + expect_raises(TypeCastError) { JSON.parse("true").as_f32 } JSON.parse("123.45").as_f32?.should eq(123.45_f32) JSON.parse("true").as_f32?.should be_nil end + it "gets float32 from JSON integer (#8618)" do + value = JSON.parse("123").as_f32 + value.should eq(123.0) + value.should be_a(Float32) + + value = JSON.parse("123").as_f32? + value.should eq(123.0) + value.should be_a(Float32) + end + it "gets float64" do JSON.parse("123.45").as_f.should eq(123.45) + expect_raises(TypeCastError) { JSON.parse("true").as_f } JSON.parse("123.45").as_f?.should eq(123.45) JSON.parse("true").as_f?.should be_nil end + it "gets float64 from JSON integer (#8618)" do + value = JSON.parse("123").as_f + value.should eq(123.0) + value.should be_a(Float64) + + value = JSON.parse("123").as_f? + value.should eq(123.0) + value.should be_a(Float64) + end + it "gets string" do JSON.parse(%("hello")).as_s.should eq("hello") JSON.parse(%("hello")).as_s?.should eq("hello") diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index 2c2295d3f26b..cceb161c6f12 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -88,6 +88,12 @@ class JSONAttrWithBool property value : Bool end +class JSONAttrWithFloat + include JSON::Serializable + + property value : Float64 +end + class JSONAttrWithUUID include JSON::Serializable @@ -631,6 +637,11 @@ describe "JSON mapping" do json.value.should be_false end + it "parses JSON integer into a float property (#8618)" do + json = JSONAttrWithFloat.from_json(%({"value": 123})) + json.value.should eq(123.0) + end + it "parses UUID" do uuid = JSONAttrWithUUID.from_json(%({"value": "ba714f86-cac6-42c7-8956-bcf5105e1b81"})) uuid.should be_a(JSONAttrWithUUID) diff --git a/spec/std/yaml/any_spec.cr b/spec/std/yaml/any_spec.cr index e215ba5084e7..c2a030dcc0f4 100644 --- a/spec/std/yaml/any_spec.cr +++ b/spec/std/yaml/any_spec.cr @@ -65,6 +65,8 @@ describe YAML::Any do value.should eq(1.2_f32) value.should be_a(Float32) + expect_raises(TypeCastError) { YAML.parse("true").as_f32 } + value = YAML.parse("1.2").as_f32? value.should eq(1.2_f32) value.should be_a(Float32) @@ -73,11 +75,23 @@ describe YAML::Any do value.should be_nil end + it "gets float32 from JSON integer (#8618)" do + value = YAML.parse("123").as_f32 + value.should eq(123.0) + value.should be_a(Float32) + + value = YAML.parse("123").as_f32? + value.should eq(123.0) + value.should be_a(Float32) + end + it "gets float64" do value = YAML.parse("1.2").as_f value.should eq(1.2) value.should be_a(Float64) + expect_raises(TypeCastError) { YAML.parse("true").as_f } + value = YAML.parse("1.2").as_f? value.should eq(1.2) value.should be_a(Float64) @@ -86,6 +100,16 @@ describe YAML::Any do value.should be_nil end + it "gets float64 from JSON integer (#8618)" do + value = YAML.parse("123").as_f + value.should eq(123.0) + value.should be_a(Float64) + + value = YAML.parse("123").as_f? + value.should eq(123.0) + value.should be_a(Float64) + end + it "gets time" do value = YAML.parse("2010-01-02").as_time value.should eq(Time.utc(2010, 1, 2)) diff --git a/src/json/any.cr b/src/json/any.cr index 59a11b852c5d..d64b7859bc53 100644 --- a/src/json/any.cr +++ b/src/json/any.cr @@ -186,28 +186,50 @@ struct JSON::Any as_i64 if @raw.is_a?(Int64) end - # Checks that the underlying value is `Float`, and returns its value as an `Float64`. + # Checks that the underlying value is `Float` (or `Int`), and returns its value as an `Float64`. # Raises otherwise. def as_f : Float64 - @raw.as(Float64) + case raw = @raw + when Int + raw.to_f + else + raw.as(Float64) + end end - # Checks that the underlying value is `Float`, and returns its value as an `Float64`. + # Checks that the underlying value is `Float` (or `Int`), and returns its value as an `Float64`. # Returns `nil` otherwise. def as_f? : Float64? - @raw.as?(Float64) + case raw = @raw + when Int + raw.to_f + else + raw.as?(Float64) + end end - # Checks that the underlying value is `Float`, and returns its value as an `Float32`. + # Checks that the underlying value is `Float` (or `Int`), and returns its value as an `Float32`. # Raises otherwise. def as_f32 : Float32 - @raw.as(Float).to_f32 + case raw = @raw + when Int + raw.to_f32 + else + raw.as(Float).to_f32 + end end - # Checks that the underlying value is `Float`, and returns its value as an `Float32`. + # Checks that the underlying value is `Float` (or `Int`), and returns its value as an `Float32`. # Returns `nil` otherwise. def as_f32? : Float32? - as_f32 if @raw.is_a?(Float) + case raw = @raw + when Int + raw.to_f32 + when Float + raw.to_f32 + else + nil + end end # Checks that the underlying value is `String`, and returns its value. diff --git a/src/yaml/any.cr b/src/yaml/any.cr index e8e3a82e58b3..2ed8f242999c 100644 --- a/src/yaml/any.cr +++ b/src/yaml/any.cr @@ -198,28 +198,50 @@ struct YAML::Any as_i if @raw.is_a?(Int) end - # Checks that the underlying value is `Float64`, and returns its value. + # Checks that the underlying value is `Float` (or `Int`), and returns its value. # Raises otherwise. def as_f : Float64 - @raw.as(Float64) + case raw = @raw + when Int + raw.to_f + else + raw.as(Float64) + end end - # Checks that the underlying value is `Float64`, and returns its value. + # Checks that the underlying value is `Float` (or `Int`), and returns its value. # Returns `nil` otherwise. def as_f? : Float64? - @raw.as?(Float64) + case raw = @raw + when Int + raw.to_f + else + raw.as?(Float64) + end end - # Checks that the underlying value is `Float`, and returns its value as an `Float32`. + # Checks that the underlying value is `Float` (or `Int`), and returns its value as an `Float32`. # Raises otherwise. def as_f32 : Float32 - @raw.as(Float).to_f32 + case raw = @raw + when Int + raw.to_f32 + else + raw.as(Float).to_f32 + end end - # Checks that the underlying value is `Float`, and returns its value as an `Float32`. + # Checks that the underlying value is `Float` (or `Int`), and returns its value as an `Float32`. # Returns `nil` otherwise. def as_f32? : Float32? - as_f32 if @raw.is_a?(Float) + case raw = @raw + when Int + raw.to_f32 + when Float + raw.to_f32 + else + nil + end end # Checks that the underlying value is `Time`, and returns its value. From dab949e885522c260637bdffbc4015071c8b2f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 15 Dec 2022 13:25:38 +0100 Subject: [PATCH 0182/1551] Improve documentation for `Object#to_s` and `#inspect` (#9974) Co-authored-by: Beta Ziliani --- src/array.cr | 6 ++++ src/object.cr | 78 +++++++++++++++++++++++---------------------------- 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/src/array.cr b/src/array.cr index 51779b62788d..a93f974ab650 100644 --- a/src/array.cr +++ b/src/array.cr @@ -1717,6 +1717,12 @@ class Array(T) self end + # Prints a nicely readable and concise string representation of this array + # to *io*. + # + # The result resembles an array literal but it does not necessarily compile. + # + # Each element is presented using its `#inspect(io)` result to avoid ambiguity. def to_s(io : IO) : Nil executed = exec_recursive(:to_s) do io << '[' diff --git a/src/object.cr b/src/object.cr index 7fe746d3457e..b49159b10fe5 100644 --- a/src/object.cr +++ b/src/object.cr @@ -86,72 +86,64 @@ class Object hash(Crystal::Hasher.new).result end - # Returns a string representation of this object. + # Returns a nicely readable and concise string representation of this object, + # typically intended for users. # - # Descendants must usually **not** override this method. Instead, - # they must override `to_s(io)`, which must append to the given - # IO object. + # This method should usually **not** be overridden. It delegates to + # `#to_s(IO)` which can be overridden for custom implementations. + # + # Also see `#inspect`. def to_s : String String.build do |io| to_s io end end - # Appends a `String` representation of this object - # to the given `IO` object. - # - # An object must never append itself to the io argument, - # as this will in turn call `to_s(io)` on it. - abstract def to_s(io : IO) : Nil - - # Returns a `String` representation of this object suitable - # to be embedded inside other expressions, sometimes providing - # more information about this object. - # - # `#inspect` (and `#inspect(io)`) are the methods used when - # you invoke `#to_s` or `#inspect` on an object that holds - # other objects and wants to show them. For example when you - # invoke `Array#to_s`, `#inspect` will be invoked on each element: + # Prints a nicely readable and concise string representation of this object, + # typically intended for users, to *io*. # + # This method is called when an object is interpolated in a string literal: # ``` - # ary = ["one", "two", "three, etc."] - # ary.inspect # => ["one", "two", "three, etc."] + # "foo #{bar} baz" # calls bar.to_io with the builder for this string # ``` # - # Note that if Array invoked `#to_s` on each of the elements - # above, the output would have been this: - # + # `IO#<<` calls this method to append an object to itself: # ``` - # ary = ["one", "two", "three, etc."] - # # If inspect invoked to_s on each element... - # ary.inspect # => [one, two, three, etc.] + # io << bar # calls bar.to_s(io) # ``` # - # Note that it's not clear how many elements the array has, - # or which are they, because `#to_s` doesn't guarantee that - # the string representation is clearly delimited (in the case - # of `String` the quotes are not shown). + # Thus implementations must not interpolate `self` in a string literal or call + # `io << self` which both would lead to an endless loop. # - # Also note that sometimes the output of `#inspect` will look - # like a Crystal expression that will compile, but this isn't - # always the case, nor is it necessary. Notably, `Reference#inspect` - # and `Struct#inspect` return values that don't compile. + # Also see `#inspect(IO)`. + abstract def to_s(io : IO) : Nil + + # Returns an unambiguous and information-rich string representation of this + # object, typically intended for developers. + # + # This method should usually **not** be overridden. It delegates to + # `#inspect(IO)` which can be overridden for custom implementations. # - # Classes must usually **not** override this method. Instead, - # they must override `inspect(io)`, which must append to the - # given `IO` object. + # Also see `#to_s`. def inspect : String String.build do |io| inspect io end end - # Appends a string representation of this object - # to the given `IO` object. + # Prints to *io* an unambiguous and information-rich string representation of this + # object, typically intended for developers. + # + # It is similar to `#to_s(IO)`, but often provides more information. Ideally, it should + # contain sufficient information to be able to recreate an object with the same value + # (given an identical environment). + # + # For types that don't provide a custom implementation of this method, + # default implementation delegates to `#to_s(IO)`. This said, it is advisable to + # have an appropriate `#inspect` implementation on every type. Default + # implementations are provided by `Struct#inspect` and `Reference#inspect`. # - # Similar to `to_s(io)`, but usually appends more information - # about this object. - # See `#inspect`. + # `::p` and `::p!` use this method to print an object in `STDOUT`. def inspect(io : IO) : Nil to_s io end From 649f0614f09be92253afe6110e6e63a116150d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 15 Dec 2022 13:25:56 +0100 Subject: [PATCH 0183/1551] Add custom `message` parameter to `#not_nil!` (#12797) --- spec/std/object_spec.cr | 31 +++++++++++++++++++++++++++++++ src/nil.cr | 10 ++++++++-- src/object.cr | 2 +- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/spec/std/object_spec.cr b/spec/std/object_spec.cr index 0775a9c24b89..57c0ff48f703 100644 --- a/spec/std/object_spec.cr +++ b/spec/std/object_spec.cr @@ -513,4 +513,35 @@ describe Object do (x == y).should be_false end end + + describe "#not_nil!" do + it "basic" do + 1.not_nil! + expect_raises(NilAssertionError, "Nil assertion failed") do + nil.not_nil! + end + end + + it "removes Nil type" do + x = TestObject.new.as(TestObject?) + typeof(x.not_nil!).should eq TestObject + x.not_nil!.should be x + end + + it "raises NilAssertionError" do + x = nil.as(TestObject?) + typeof(x.not_nil!).should eq TestObject + expect_raises(NilAssertionError, "Nil assertion failed") do + x.not_nil! + end + end + + it "with message" do + x = TestObject.new + x.not_nil!("custom message").should be x + expect_raises(NilAssertionError, "custom message") do + nil.not_nil!("custom message") + end + end + end end diff --git a/src/nil.cr b/src/nil.cr index a27eff27f8fb..7b97b5f00d3c 100644 --- a/src/nil.cr +++ b/src/nil.cr @@ -103,9 +103,15 @@ struct Nil # Raises `NilAssertionError`. # + # If *message* is given, it is forwarded as error message of `NilAssertionError`. + # # See also: `Object#not_nil!`. - def not_nil! : NoReturn - raise NilAssertionError.new + def not_nil!(message = nil) : NoReturn + if message + raise NilAssertionError.new(message) + else + raise NilAssertionError.new + end end # Returns `self`. diff --git a/src/object.cr b/src/object.cr index b49159b10fe5..608dcf618bda 100644 --- a/src/object.cr +++ b/src/object.cr @@ -220,7 +220,7 @@ class Object # for example using [`if var`](https://crystal-lang.org/reference/syntax_and_semantics/if_var.html). # `not_nil!` is only meant as a last resort when there's no other way to explain this to the compiler. # Either way, consider instead raising a concrete exception with a descriptive message. - def not_nil! + def not_nil!(message = nil) self end From 1788b67e826996aa84f4bfc9e888d567c2db0386 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Thu, 15 Dec 2022 20:26:11 +0800 Subject: [PATCH 0184/1551] Resolve type of free variable on block return type mismatch (#12754) --- spec/compiler/semantic/block_spec.cr | 11 +++++++++++ src/compiler/crystal/semantic/call.cr | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/block_spec.cr b/spec/compiler/semantic/block_spec.cr index 021ba2a6738a..8dc82443018b 100644 --- a/spec/compiler/semantic/block_spec.cr +++ b/spec/compiler/semantic/block_spec.cr @@ -1608,4 +1608,15 @@ describe "Block inference" do foo.foo CRYSTAL end + + it "renders expected block return type of a free variable on mismatch" do + assert_error(<<-CR, "expected block to return Int64, not String") + struct Foo + def bar(arg : U, &block : -> U) forall U + end + end + + Foo.new.bar(1_i64) { "hi" } + CR + end end diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index 314db053c75f..ca0f4cfb3cbb 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -1041,7 +1041,7 @@ class Crystal::Call when Self match.context.instantiated_type when Crystal::Path - match.context.defining_type.lookup_path(output) + match.context.defining_type.lookup_type_var(output, match.context.free_vars) else output end From c05049de9db682aa37ac744056c8bf1511be843c Mon Sep 17 00:00:00 2001 From: David Keller Date: Thu, 15 Dec 2022 18:55:16 +0100 Subject: [PATCH 0185/1551] Remove oct/bin floating point literals (#12687) --- spec/compiler/lexer/lexer_spec.cr | 3 +++ src/compiler/crystal/syntax/lexer.cr | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/spec/compiler/lexer/lexer_spec.cr b/spec/compiler/lexer/lexer_spec.cr index aa9eaf3e06c8..8575f6e1c14b 100644 --- a/spec/compiler/lexer/lexer_spec.cr +++ b/spec/compiler/lexer/lexer_spec.cr @@ -362,6 +362,9 @@ describe "Lexer" do assert_syntax_error "0o200_i8", "0o200 doesn't fit in an Int8" assert_syntax_error "0b10000000_i8", "0b10000000 doesn't fit in an Int8" + assert_syntax_error "0b11_f32", "binary float literal is not supported" + assert_syntax_error "0o73_f64", "octal float literal is not supported" + # 2**31 - 1 it_lexes_i32 [["0x7fffffff", "2147483647"], ["0o17777777777", "2147483647"], ["0b1111111111111111111111111111111", "2147483647"]] it_lexes_i32 [["0x7fffffff_i32", "2147483647"], ["0o17777777777_i32", "2147483647"], ["0b1111111111111111111111111111111_i32", "2147483647"]] diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index c8f65187a448..333fdadaa452 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -1284,6 +1284,13 @@ module Crystal raise("unexpected '_' in number", @token, (current_pos - start)) if peek_next_char == '_' break unless peek_next_char.in?('0'..'9') when 'i', 'u', 'f' + if current_char == 'f' && base != 10 + case base + when 2 then raise("binary float literal is not supported", @token, (current_pos - start)) + when 8 then raise("octal float literal is not supported", @token, (current_pos - start)) + end + break + end before_suffix_pos = current_pos @token.number_kind = consume_number_suffix next_char From f0003ed02836a123fdbf9f23f91f7f5646d4120e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 15 Dec 2022 18:55:37 +0100 Subject: [PATCH 0186/1551] Add more specific error message for uninstantiated proc type (#11219) Co-authored-by: Caspian Baska --- spec/compiler/semantic/block_spec.cr | 10 ++++++++++ src/compiler/crystal/semantic/call.cr | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/block_spec.cr b/spec/compiler/semantic/block_spec.cr index 8dc82443018b..98ab54449665 100644 --- a/spec/compiler/semantic/block_spec.cr +++ b/spec/compiler/semantic/block_spec.cr @@ -676,6 +676,16 @@ describe "Block inference" do "expected block type to be a function type, not Int32" end + it "errors if proc is not instantiated" do + assert_error <<-CR, "can't create an instance of generic class Proc(*T, R) without specifying its type vars" + def capture(&block : Proc) + block + end + + capture { } + CR + end + it "passes #262" do assert_type(%( require "prelude" diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index ca0f4cfb3cbb..a3c16f7c2a33 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -836,7 +836,11 @@ class Crystal::Call # is valid too only if Foo is an alias/typedef that refers to a FunctionType block_arg_restriction_type = lookup_node_type(match.context, block_arg_restriction).remove_typedef unless block_arg_restriction_type.is_a?(ProcInstanceType) - block_arg_restriction.raise "expected block type to be a function type, not #{block_arg_restriction_type}" + if block_arg_restriction_type.is_a?(ProcType) + block_arg_restriction.raise "can't create an instance of generic class #{block_arg_restriction_type} without specifying its type vars" + else + block_arg_restriction.raise "expected block type to be a function type, not #{block_arg_restriction_type}" + end return nil, nil end From 174bb009effbb326a40741f8927e6e48692a6133 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Fri, 16 Dec 2022 00:21:50 +0100 Subject: [PATCH 0187/1551] Add `Indexable#rindex!` method variant (#12759) --- spec/std/indexable_spec.cr | 22 ++++++++++++++++++++++ src/indexable.cr | 14 ++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/spec/std/indexable_spec.cr b/spec/std/indexable_spec.cr index e2071cef0885..f7c39f7ba485 100644 --- a/spec/std/indexable_spec.cr +++ b/spec/std/indexable_spec.cr @@ -133,6 +133,28 @@ describe Indexable do end end + describe "#rindex!" do + it "does rindex with big negative offset" do + indexable = SafeIndexable.new(3) + expect_raises Enumerable::NotFoundError do + indexable.rindex!(0, -100) + end + end + + it "does rindex with big offset" do + indexable = SafeIndexable.new(3) + expect_raises Enumerable::NotFoundError do + indexable.rindex!(0, 100) + end + end + + it "offset type" do + indexable = SafeIndexable.new(3) + indexable.rindex!(1, 2_i64).should eq 1 + indexable.rindex!(1, 2_i64).should be_a(Int64) + end + end + it "does each" do indexable = SafeIndexable.new(3) is = [] of Int32 diff --git a/src/indexable.cr b/src/indexable.cr index 8ff5ddec14f4..8ce454a341ed 100644 --- a/src/indexable.cr +++ b/src/indexable.cr @@ -849,6 +849,13 @@ module Indexable(T) rindex(offset) { |elem| elem == value } end + # :ditto: + # + # Raises `Enumerable::NotFoundError` if *value* is not in `self`. + def rindex!(value, offset = size - 1) + rindex(value, offset) || raise Enumerable::NotFoundError.new + end + # Returns the index of the first object in `self` for which the block # is truthy, starting from the last object, or `nil` if no match # is found. @@ -872,6 +879,13 @@ module Indexable(T) nil end + # :ditto: + # + # Raises `Enumerable::NotFoundError` if no match is found. + def rindex!(offset = size - 1, & : T ->) + rindex(offset) { |e| yield e } || raise Enumerable::NotFoundError.new + end + # Optimized version of `Enumerable#sample` that runs in O(1) time. # # ``` From 7b26311022cb12c120171a0511aadf77b477fa72 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 16 Dec 2022 07:22:10 +0800 Subject: [PATCH 0188/1551] Enable multithreading specs on Windows CI (#12843) --- spec/std/thread/condition_variable_spec.cr | 2 -- spec/std/thread/mutex_spec.cr | 2 -- spec/std/thread_spec.cr | 2 -- 3 files changed, 6 deletions(-) diff --git a/spec/std/thread/condition_variable_spec.cr b/spec/std/thread/condition_variable_spec.cr index 568f2ea35c31..36e1963794b3 100644 --- a/spec/std/thread/condition_variable_spec.cr +++ b/spec/std/thread/condition_variable_spec.cr @@ -1,5 +1,3 @@ -{% skip_file if flag?(:win32) %} # FIXME: enable after #11647 - {% if flag?(:musl) %} # FIXME: These thread specs occasionally fail on musl/alpine based ci, so # they're disabled for now to reduce noise. diff --git a/spec/std/thread/mutex_spec.cr b/spec/std/thread/mutex_spec.cr index 405c812b0888..d028ffdd8f8a 100644 --- a/spec/std/thread/mutex_spec.cr +++ b/spec/std/thread/mutex_spec.cr @@ -1,5 +1,3 @@ -{% skip_file if flag?(:win32) %} # FIXME: enable after #11647 - {% if flag?(:musl) %} # FIXME: These thread specs occasionally fail on musl/alpine based ci, so # they're disabled for now to reduce noise. diff --git a/spec/std/thread_spec.cr b/spec/std/thread_spec.cr index 7d3e51a92972..599a1968f52f 100644 --- a/spec/std/thread_spec.cr +++ b/spec/std/thread_spec.cr @@ -1,5 +1,3 @@ -{% skip_file if flag?(:win32) %} # FIXME: enable after #11647 - require "spec" {% if flag?(:musl) %} From a5373ade7157f940f21383e22a74656b6d264d89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 16 Dec 2022 12:07:39 +0100 Subject: [PATCH 0189/1551] Implement `Regex` engine on PCRE2 (#12840) --- spec/std/regex_spec.cr | 20 +++-- src/regex/engine.cr | 13 ++- src/regex/lib_pcre2.cr | 89 +++++++++++++++++++++ src/regex/pcre2.cr | 176 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 290 insertions(+), 8 deletions(-) create mode 100644 src/regex/lib_pcre2.cr create mode 100644 src/regex/pcre2.cr diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index c38194bc8785..5935e85060fd 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -200,12 +200,16 @@ describe "Regex" do /foo/.matches?("foo", options: Regex::Options::ANCHORED).should be_true end - it "matches a large single line string" do - LibPCRE.config LibPCRE::CONFIG_JIT, out jit_enabled - pending! "PCRE JIT mode not available." unless 1 == jit_enabled + it "doesn't crash with a large single line string" do + {% if Regex::Engine.resolve.name == "Regex::PCRE" %} + LibPCRE.config LibPCRE::CONFIG_JIT, out jit_enabled + pending! "PCRE JIT mode not available." unless 1 == jit_enabled + {% end %} str = File.read(datapath("large_single_line_string.txt")) - str.matches?(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/).should be_false + str.matches?(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/) + # We don't care whether this actually matches or not, it's just to make + # sure the engine does not stack overflow with a large string. end end @@ -422,6 +426,12 @@ describe "Regex" do it ".error?" do Regex.error?("(foo|bar)").should be_nil - Regex.error?("(foo|bar").should eq "missing ) at 8" + Regex.error?("(foo|bar").should eq( + if Regex::Engine.to_s == "Regex::PCRE2" + "missing closing parenthesis at 8" + else + "missing ) at 8" + end + ) end end diff --git a/src/regex/engine.cr b/src/regex/engine.cr index ad69e5d034bf..766917f87dd2 100644 --- a/src/regex/engine.cr +++ b/src/regex/engine.cr @@ -1,4 +1,11 @@ -require "./pcre" +{% if flag?(:use_pcre2) || (!flag?(:use_pcre) && !flag?(:win32) && `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libpcre2-8 || printf %s false` != "false") %} + require "./pcre2" -# :nodoc: -alias Regex::Engine = PCRE + # :nodoc: + alias Regex::Engine = PCRE2 +{% else %} + require "./pcre" + + # :nodoc: + alias Regex::Engine = PCRE +{% end %} diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr new file mode 100644 index 000000000000..922c492b7e1a --- /dev/null +++ b/src/regex/lib_pcre2.cr @@ -0,0 +1,89 @@ +@[Link("pcre2-8")] +lib LibPCRE2 + alias Int = LibC::Int + + UNSET = ~LibC::SizeT.new(0) + + ANCHORED = 0x80000000 + NO_UTF_CHECK = 0x40000000 + ENDANCHORED = 0x20000000 + + ALLOW_EMPTY_CLASS = 0x00000001 + ALT_BSUX = 0x00000002 + AUTO_CALLOUT = 0x00000004 + CASELESS = 0x00000008 + DOLLAR_ENDONLY = 0x00000010 + DOTALL = 0x00000020 + DUPNAMES = 0x00000040 + EXTENDED = 0x00000080 + FIRSTLINE = 0x00000100 + MATCH_UNSET_BACKREF = 0x00000200 + MULTILINE = 0x00000400 + NEVER_UCP = 0x00000800 + NEVER_UTF = 0x00001000 + NO_AUTO_CAPTURE = 0x00002000 + NO_AUTO_POSSESS = 0x00004000 + NO_DOTSTAR_ANCHOR = 0x00008000 + NO_START_OPTIMIZE = 0x00010000 + UCP = 0x00020000 + UNGREEDY = 0x00040000 + UTF = 0x00080000 + NEVER_BACKSLASH_C = 0x00100000 + ALT_CIRCUMFLEX = 0x00200000 + ALT_VERBNAMES = 0x00400000 + USE_OFFSET_LIMIT = 0x00800000 + EXTENDED_MORE = 0x01000000 + LITERAL = 0x02000000 + MATCH_INVALID_UTF = 0x04000000 + + ERROR_NOMATCH = -1 + + INFO_ALLOPTIONS = 0 + INFO_ARGOPTIONS = 1 + INFO_BACKREFMAX = 2 + INFO_BSR = 3 + INFO_CAPTURECOUNT = 4 + INFO_FIRSTCODEUNIT = 5 + INFO_FIRSTCODETYPE = 6 + INFO_FIRSTBITMAP = 7 + INFO_HASCRORLF = 8 + INFO_JCHANGED = 9 + INFO_JITSIZE = 10 + INFO_LASTCODEUNIT = 11 + INFO_LASTCODETYPE = 12 + INFO_MATCHEMPTY = 13 + INFO_MATCHLIMIT = 14 + INFO_MAXLOOKBEHIND = 15 + INFO_MINLENGTH = 16 + INFO_NAMECOUNT = 17 + INFO_NAMEENTRYSIZE = 18 + INFO_NAMETABLE = 19 + INFO_NEWLINE = 20 + INFO_DEPTHLIMIT = 21 + INFO_RECURSIONLIMIT = 21 # Obsolete synonym + INFO_SIZE = 22 + INFO_HASBACKSLASHC = 23 + INFO_FRAMESIZE = 24 + INFO_HEAPLIMIT = 25 + INFO_EXTRAOPTIONS = 26 + + type Code = Void* + type CompileContext = Void* + type MatchData = Void* + + fun get_error_message = pcre2_get_error_message_8(errorcode : Int, buffer : UInt8*, bufflen : LibC::SizeT) : Int + + fun compile = pcre2_compile_8(pattern : UInt8*, length : LibC::SizeT, options : UInt32, errorcode : LibC::SizeT*, erroroffset : Int*, ccontext : CompileContext*) : Code* + fun code_free = pcre2_code_free_8(code : Code*) : Void + + fun pattern_info = pcre2_pattern_info_8(code : Code*, what : UInt32, where : Void*) : Int + + fun match = pcre2_match_8(code : Code*, subject : UInt8*, length : LibC::SizeT, startoffset : LibC::SizeT, options : UInt32, match_data : MatchData*, mcontext : Void*) : Int + fun match_data_create_from_pattern = pcre2_match_data_create_from_pattern_8(code : Code*, gcontext : Void*) : MatchData* + fun match_data_free = pcre2_match_data_free_8(match_data : MatchData*) : Void + + fun substring_nametable_scan = pcre2_substring_nametable_scan_8(code : Code*, name : UInt8*, first : UInt8*, last : UInt8*) : Int + + fun get_ovector_pointer = pcre2_get_ovector_pointer_8(match_data : MatchData*) : LibC::SizeT* + fun get_ovector_count = pcre2_get_ovector_count_8(match_data : MatchData*) : UInt32 +end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr new file mode 100644 index 000000000000..3eea20280268 --- /dev/null +++ b/src/regex/pcre2.cr @@ -0,0 +1,176 @@ +require "./lib_pcre2" + +# :nodoc: +module Regex::PCRE2 + @re : LibPCRE2::Code* + + # :nodoc: + def initialize(*, _source @source : String, _options @options) + @re = PCRE2.compile(source, pcre2_options(options) | LibPCRE2::UTF | LibPCRE2::NO_UTF_CHECK | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| + raise ArgumentError.new(error_message) + end + end + + protected def self.compile(source, options) + if res = LibPCRE2.compile(source, source.bytesize, options, out errorcode, out erroroffset, nil) + res + else + message = String.new(256) do |buffer| + bytesize = LibPCRE2.get_error_message(errorcode, buffer, 256) + {bytesize, 0} + end + yield "#{message} at #{erroroffset}" + end + end + + private def pcre2_options(options) + flag = 0 + options.each do |option| + flag |= case option + when .ignore_case? then LibPCRE2::CASELESS + when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE + when .extended? then LibPCRE2::EXTENDED + when .anchored? then LibPCRE2::ANCHORED + when .utf_8? then LibPCRE2::UTF + when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK + when .dupnames? then LibPCRE2::DUPNAMES + when .ucp? then LibPCRE2::UCP + else + raise "unreachable" + end + end + flag + end + + def finalize + {% unless flag?(:interpreted) %} + LibPCRE2.code_free @re + {% end %} + end + + protected def self.error_impl(source) + code = PCRE2.compile(source, LibPCRE2::UTF | LibPCRE2::NO_UTF_CHECK | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| + return error_message + end + + LibPCRE2.code_free code + + nil + end + + private def pattern_info(what) + value = uninitialized UInt32 + pattern_info(what, pointerof(value)) + value + end + + private def pattern_info(what, where) + ret = LibPCRE2.pattern_info(@re, what, where) + if ret != 0 + raise "error pattern_info #{what}: #{ret}" + end + end + + private def name_table_impl + lookup = Hash(Int32, String).new + + each_capture_group do |capture_number, name_entry| + lookup[capture_number] = String.new(name_entry.to_unsafe + 2) + end + + lookup + end + + # :nodoc: + def each_capture_group + name_table = uninitialized UInt8* + pattern_info(LibPCRE2::INFO_NAMETABLE, pointerof(name_table)) + + name_entry_size = pattern_info(LibPCRE2::INFO_NAMEENTRYSIZE) + + name_count = pattern_info(LibPCRE2::INFO_NAMECOUNT) + name_count.times do + capture_number = (name_table[0] << 8) | name_table[1] + + yield capture_number, Slice.new(name_table, name_entry_size) + + name_table += name_entry_size + end + end + + private def capture_count_impl + pattern_info(LibPCRE2::INFO_CAPTURECOUNT).to_i32 + end + + private def match_impl(str, byte_index, options) + match_data = match_data(str, byte_index, options) || return + + ovector = LibPCRE2.get_ovector_pointer(match_data) + ovector_count = LibPCRE2.get_ovector_count(match_data) + LibPCRE2.match_data_free(match_data) + + ::Regex::MatchData.new(self, @re, str, byte_index, ovector, ovector_count.to_i32 - 1) + end + + private def matches_impl(str, byte_index, options) + if match_data = match_data(str, byte_index, options) + LibPCRE2.match_data_free(match_data) + true + else + false + end + end + + private def match_data(str, byte_index, options) + match_data = LibPCRE2.match_data_create_from_pattern(@re, nil) + match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, nil) + + if match_count < 0 + LibPCRE2.match_data_free(match_data) + case match_count + when LibPCRE2::ERROR_NOMATCH + return + else + raise "error!" + end + end + + match_data + end + + module MatchData + # :nodoc: + def initialize(@regex : Regex, @code : LibPCRE2::Code*, @string : String, @pos : Int32, @ovector : UInt64*, @group_size : Int32) + end + + private def byte_range(n, &) + n += size if n < 0 + range = Range.new(@ovector[n * 2].to_i32!, @ovector[n * 2 + 1].to_i32!, exclusive: true) + if range.begin < 0 || range.end < 0 + yield n + else + range + end + end + + private def fetch_impl(group_name : String) + selected_range = nil + exists = false + @regex.each_capture_group do |number, name_entry| + if name_entry[2, group_name.bytesize] == group_name.to_slice + exists = true + range = byte_range(number) { nil } + if (range && selected_range && range.begin > selected_range.begin) || !selected_range + selected_range = range + end + end + end + + if selected_range + @string.byte_slice(selected_range.begin, selected_range.end - selected_range.begin) + else + yield exists + end + end + end +end From dce9d40222898afb48c6a36f6727abecfda06d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 16 Dec 2022 18:05:06 +0100 Subject: [PATCH 0190/1551] Revert "Implement `Regex` engine on PCRE2" (#12850) --- spec/std/regex_spec.cr | 20 ++--- src/regex/engine.cr | 13 +-- src/regex/lib_pcre2.cr | 89 --------------------- src/regex/pcre2.cr | 176 ----------------------------------------- 4 files changed, 8 insertions(+), 290 deletions(-) delete mode 100644 src/regex/lib_pcre2.cr delete mode 100644 src/regex/pcre2.cr diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 5935e85060fd..c38194bc8785 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -200,16 +200,12 @@ describe "Regex" do /foo/.matches?("foo", options: Regex::Options::ANCHORED).should be_true end - it "doesn't crash with a large single line string" do - {% if Regex::Engine.resolve.name == "Regex::PCRE" %} - LibPCRE.config LibPCRE::CONFIG_JIT, out jit_enabled - pending! "PCRE JIT mode not available." unless 1 == jit_enabled - {% end %} + it "matches a large single line string" do + LibPCRE.config LibPCRE::CONFIG_JIT, out jit_enabled + pending! "PCRE JIT mode not available." unless 1 == jit_enabled str = File.read(datapath("large_single_line_string.txt")) - str.matches?(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/) - # We don't care whether this actually matches or not, it's just to make - # sure the engine does not stack overflow with a large string. + str.matches?(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/).should be_false end end @@ -426,12 +422,6 @@ describe "Regex" do it ".error?" do Regex.error?("(foo|bar)").should be_nil - Regex.error?("(foo|bar").should eq( - if Regex::Engine.to_s == "Regex::PCRE2" - "missing closing parenthesis at 8" - else - "missing ) at 8" - end - ) + Regex.error?("(foo|bar").should eq "missing ) at 8" end end diff --git a/src/regex/engine.cr b/src/regex/engine.cr index 766917f87dd2..ad69e5d034bf 100644 --- a/src/regex/engine.cr +++ b/src/regex/engine.cr @@ -1,11 +1,4 @@ -{% if flag?(:use_pcre2) || (!flag?(:use_pcre) && !flag?(:win32) && `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libpcre2-8 || printf %s false` != "false") %} - require "./pcre2" +require "./pcre" - # :nodoc: - alias Regex::Engine = PCRE2 -{% else %} - require "./pcre" - - # :nodoc: - alias Regex::Engine = PCRE -{% end %} +# :nodoc: +alias Regex::Engine = PCRE diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr deleted file mode 100644 index 922c492b7e1a..000000000000 --- a/src/regex/lib_pcre2.cr +++ /dev/null @@ -1,89 +0,0 @@ -@[Link("pcre2-8")] -lib LibPCRE2 - alias Int = LibC::Int - - UNSET = ~LibC::SizeT.new(0) - - ANCHORED = 0x80000000 - NO_UTF_CHECK = 0x40000000 - ENDANCHORED = 0x20000000 - - ALLOW_EMPTY_CLASS = 0x00000001 - ALT_BSUX = 0x00000002 - AUTO_CALLOUT = 0x00000004 - CASELESS = 0x00000008 - DOLLAR_ENDONLY = 0x00000010 - DOTALL = 0x00000020 - DUPNAMES = 0x00000040 - EXTENDED = 0x00000080 - FIRSTLINE = 0x00000100 - MATCH_UNSET_BACKREF = 0x00000200 - MULTILINE = 0x00000400 - NEVER_UCP = 0x00000800 - NEVER_UTF = 0x00001000 - NO_AUTO_CAPTURE = 0x00002000 - NO_AUTO_POSSESS = 0x00004000 - NO_DOTSTAR_ANCHOR = 0x00008000 - NO_START_OPTIMIZE = 0x00010000 - UCP = 0x00020000 - UNGREEDY = 0x00040000 - UTF = 0x00080000 - NEVER_BACKSLASH_C = 0x00100000 - ALT_CIRCUMFLEX = 0x00200000 - ALT_VERBNAMES = 0x00400000 - USE_OFFSET_LIMIT = 0x00800000 - EXTENDED_MORE = 0x01000000 - LITERAL = 0x02000000 - MATCH_INVALID_UTF = 0x04000000 - - ERROR_NOMATCH = -1 - - INFO_ALLOPTIONS = 0 - INFO_ARGOPTIONS = 1 - INFO_BACKREFMAX = 2 - INFO_BSR = 3 - INFO_CAPTURECOUNT = 4 - INFO_FIRSTCODEUNIT = 5 - INFO_FIRSTCODETYPE = 6 - INFO_FIRSTBITMAP = 7 - INFO_HASCRORLF = 8 - INFO_JCHANGED = 9 - INFO_JITSIZE = 10 - INFO_LASTCODEUNIT = 11 - INFO_LASTCODETYPE = 12 - INFO_MATCHEMPTY = 13 - INFO_MATCHLIMIT = 14 - INFO_MAXLOOKBEHIND = 15 - INFO_MINLENGTH = 16 - INFO_NAMECOUNT = 17 - INFO_NAMEENTRYSIZE = 18 - INFO_NAMETABLE = 19 - INFO_NEWLINE = 20 - INFO_DEPTHLIMIT = 21 - INFO_RECURSIONLIMIT = 21 # Obsolete synonym - INFO_SIZE = 22 - INFO_HASBACKSLASHC = 23 - INFO_FRAMESIZE = 24 - INFO_HEAPLIMIT = 25 - INFO_EXTRAOPTIONS = 26 - - type Code = Void* - type CompileContext = Void* - type MatchData = Void* - - fun get_error_message = pcre2_get_error_message_8(errorcode : Int, buffer : UInt8*, bufflen : LibC::SizeT) : Int - - fun compile = pcre2_compile_8(pattern : UInt8*, length : LibC::SizeT, options : UInt32, errorcode : LibC::SizeT*, erroroffset : Int*, ccontext : CompileContext*) : Code* - fun code_free = pcre2_code_free_8(code : Code*) : Void - - fun pattern_info = pcre2_pattern_info_8(code : Code*, what : UInt32, where : Void*) : Int - - fun match = pcre2_match_8(code : Code*, subject : UInt8*, length : LibC::SizeT, startoffset : LibC::SizeT, options : UInt32, match_data : MatchData*, mcontext : Void*) : Int - fun match_data_create_from_pattern = pcre2_match_data_create_from_pattern_8(code : Code*, gcontext : Void*) : MatchData* - fun match_data_free = pcre2_match_data_free_8(match_data : MatchData*) : Void - - fun substring_nametable_scan = pcre2_substring_nametable_scan_8(code : Code*, name : UInt8*, first : UInt8*, last : UInt8*) : Int - - fun get_ovector_pointer = pcre2_get_ovector_pointer_8(match_data : MatchData*) : LibC::SizeT* - fun get_ovector_count = pcre2_get_ovector_count_8(match_data : MatchData*) : UInt32 -end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr deleted file mode 100644 index 3eea20280268..000000000000 --- a/src/regex/pcre2.cr +++ /dev/null @@ -1,176 +0,0 @@ -require "./lib_pcre2" - -# :nodoc: -module Regex::PCRE2 - @re : LibPCRE2::Code* - - # :nodoc: - def initialize(*, _source @source : String, _options @options) - @re = PCRE2.compile(source, pcre2_options(options) | LibPCRE2::UTF | LibPCRE2::NO_UTF_CHECK | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| - raise ArgumentError.new(error_message) - end - end - - protected def self.compile(source, options) - if res = LibPCRE2.compile(source, source.bytesize, options, out errorcode, out erroroffset, nil) - res - else - message = String.new(256) do |buffer| - bytesize = LibPCRE2.get_error_message(errorcode, buffer, 256) - {bytesize, 0} - end - yield "#{message} at #{erroroffset}" - end - end - - private def pcre2_options(options) - flag = 0 - options.each do |option| - flag |= case option - when .ignore_case? then LibPCRE2::CASELESS - when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE - when .extended? then LibPCRE2::EXTENDED - when .anchored? then LibPCRE2::ANCHORED - when .utf_8? then LibPCRE2::UTF - when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK - when .dupnames? then LibPCRE2::DUPNAMES - when .ucp? then LibPCRE2::UCP - else - raise "unreachable" - end - end - flag - end - - def finalize - {% unless flag?(:interpreted) %} - LibPCRE2.code_free @re - {% end %} - end - - protected def self.error_impl(source) - code = PCRE2.compile(source, LibPCRE2::UTF | LibPCRE2::NO_UTF_CHECK | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| - return error_message - end - - LibPCRE2.code_free code - - nil - end - - private def pattern_info(what) - value = uninitialized UInt32 - pattern_info(what, pointerof(value)) - value - end - - private def pattern_info(what, where) - ret = LibPCRE2.pattern_info(@re, what, where) - if ret != 0 - raise "error pattern_info #{what}: #{ret}" - end - end - - private def name_table_impl - lookup = Hash(Int32, String).new - - each_capture_group do |capture_number, name_entry| - lookup[capture_number] = String.new(name_entry.to_unsafe + 2) - end - - lookup - end - - # :nodoc: - def each_capture_group - name_table = uninitialized UInt8* - pattern_info(LibPCRE2::INFO_NAMETABLE, pointerof(name_table)) - - name_entry_size = pattern_info(LibPCRE2::INFO_NAMEENTRYSIZE) - - name_count = pattern_info(LibPCRE2::INFO_NAMECOUNT) - name_count.times do - capture_number = (name_table[0] << 8) | name_table[1] - - yield capture_number, Slice.new(name_table, name_entry_size) - - name_table += name_entry_size - end - end - - private def capture_count_impl - pattern_info(LibPCRE2::INFO_CAPTURECOUNT).to_i32 - end - - private def match_impl(str, byte_index, options) - match_data = match_data(str, byte_index, options) || return - - ovector = LibPCRE2.get_ovector_pointer(match_data) - ovector_count = LibPCRE2.get_ovector_count(match_data) - LibPCRE2.match_data_free(match_data) - - ::Regex::MatchData.new(self, @re, str, byte_index, ovector, ovector_count.to_i32 - 1) - end - - private def matches_impl(str, byte_index, options) - if match_data = match_data(str, byte_index, options) - LibPCRE2.match_data_free(match_data) - true - else - false - end - end - - private def match_data(str, byte_index, options) - match_data = LibPCRE2.match_data_create_from_pattern(@re, nil) - match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, nil) - - if match_count < 0 - LibPCRE2.match_data_free(match_data) - case match_count - when LibPCRE2::ERROR_NOMATCH - return - else - raise "error!" - end - end - - match_data - end - - module MatchData - # :nodoc: - def initialize(@regex : Regex, @code : LibPCRE2::Code*, @string : String, @pos : Int32, @ovector : UInt64*, @group_size : Int32) - end - - private def byte_range(n, &) - n += size if n < 0 - range = Range.new(@ovector[n * 2].to_i32!, @ovector[n * 2 + 1].to_i32!, exclusive: true) - if range.begin < 0 || range.end < 0 - yield n - else - range - end - end - - private def fetch_impl(group_name : String) - selected_range = nil - exists = false - @regex.each_capture_group do |number, name_entry| - if name_entry[2, group_name.bytesize] == group_name.to_slice - exists = true - range = byte_range(number) { nil } - if (range && selected_range && range.begin > selected_range.begin) || !selected_range - selected_range = range - end - end - end - - if selected_range - @string.byte_slice(selected_range.begin, selected_range.end - selected_range.begin) - else - yield exists - end - end - end -end From 8825501f1dc9dce963afcf0dfe68f0499786627b Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Sat, 17 Dec 2022 10:07:43 -0300 Subject: [PATCH 0191/1551] Reverting #12405 Compiler: don't always use Array for node dependencies and observers (#12849) --- .../compiler/crystal/zero_one_or_many_spec.cr | 166 ------------------ src/compiler/crystal/codegen/call.cr | 4 +- src/compiler/crystal/interpreter/compiler.cr | 4 +- .../crystal/interpreter/multidispatch.cr | 6 +- src/compiler/crystal/semantic/bindings.cr | 47 ++--- src/compiler/crystal/semantic/call.cr | 73 ++++---- src/compiler/crystal/semantic/call_error.cr | 3 +- .../crystal/semantic/cleanup_transformer.cr | 64 +++---- src/compiler/crystal/semantic/cover.cr | 6 +- src/compiler/crystal/semantic/filters.cr | 2 +- .../crystal/semantic/fix_missing_types.cr | 2 +- src/compiler/crystal/semantic/lib.cr | 7 +- src/compiler/crystal/semantic/main_visitor.cr | 24 ++- src/compiler/crystal/semantic/match.cr | 19 +- .../crystal/semantic/method_lookup.cr | 16 +- src/compiler/crystal/semantic/type_merge.cr | 8 +- src/compiler/crystal/semantic/warnings.cr | 2 +- src/compiler/crystal/tools/context.cr | 12 +- src/compiler/crystal/tools/implementations.cr | 6 +- src/compiler/crystal/zero_one_or_many.cr | 117 ------------ 20 files changed, 165 insertions(+), 423 deletions(-) delete mode 100644 spec/compiler/crystal/zero_one_or_many_spec.cr delete mode 100644 src/compiler/crystal/zero_one_or_many.cr diff --git a/spec/compiler/crystal/zero_one_or_many_spec.cr b/spec/compiler/crystal/zero_one_or_many_spec.cr deleted file mode 100644 index 149a44539324..000000000000 --- a/spec/compiler/crystal/zero_one_or_many_spec.cr +++ /dev/null @@ -1,166 +0,0 @@ -require "spec" -require "../../../src/compiler/crystal/zero_one_or_many" - -describe Crystal::ZeroOneOrMany do - describe "initialize and size" do - it "creates without a value" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.size.should eq(0) - ary.value.should be_nil - end - - it "creates with a value" do - ary = Crystal::ZeroOneOrMany.new(1) - ary.size.should eq(1) - ary.to_a.should eq([1]) - ary.value.should be_a(Int32) - end - end - - describe "as Indexable" do - it "when there's no value" do - ary = Crystal::ZeroOneOrMany(Int32).new - expect_raises(IndexError) { ary[0] } - expect_raises(IndexError) { ary[1] } - end - - it "when there's a single value" do - ary = Crystal::ZeroOneOrMany.new(1) - ary[0].should eq(1) - expect_raises(IndexError) { ary[1] } - end - - it "when there's two values" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.concat([1, 2]) - ary[0].should eq(1) - ary[1].should eq(2) - expect_raises(IndexError) { ary[2] } - end - end - - describe "#each" do - it "when there's no value" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.sum.should eq(0) - end - - it "when there's a single value" do - ary = Crystal::ZeroOneOrMany.new(1) - ary.sum.should eq(1) - end - - it "when there's two values" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.concat([1, 2]) - ary.sum.should eq(3) - end - end - - describe "#push element" do - it "when there's no value" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.push 1 - ary.to_a.should eq([1]) - end - - it "when there's a single value" do - ary = Crystal::ZeroOneOrMany.new(1) - ary.push 2 - ary.to_a.should eq([1, 2]) - end - - it "when there's two values" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.push 1 - ary << 2 - ary.push 3 - ary.to_a.should eq([1, 2, 3]) - end - end - - describe "#+ elements" do - it "when there's no value and elements is empty" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.concat([] of Int32) - ary.empty?.should be_true - ary.value.should be_nil - end - - it "when there's no value and elements has a single value" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.concat([1]) - ary.to_a.should eq([1]) - ary.value.should be_a(Int32) - end - - it "when there's no value and elements has more than one value" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.concat([1, 2]) - ary.to_a.should eq([1, 2]) - ary.value.should be_a(Array(Int32)) - end - - it "when there's a single value" do - ary = Crystal::ZeroOneOrMany.new(1) - ary.concat([2, 3]) - ary.to_a.should eq([1, 2, 3]) - ary.value.should be_a(Array(Int32)) - end - - it "when there's two values" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.concat([1, 2]) - ary.concat([3, 4]) - ary.to_a.should eq([1, 2, 3, 4]) - ary.value.should be_a(Array(Int32)) - end - end - - describe "#reject" do - it "when there's no value" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.reject! { true } - ary.empty?.should be_true - ary.value.should be_nil - end - - it "when there's a single value and it matches" do - ary = Crystal::ZeroOneOrMany(Int32).new(1) - ary.reject! { |x| x == 1 } - ary.empty?.should be_true - ary.value.should be_nil - end - - it "when there's a single value and it doesn't match" do - ary = Crystal::ZeroOneOrMany(Int32).new(1) - ary.reject! { |x| x == 2 } - ary.to_a.should eq([1]) - ary.value.should be_a(Int32) - end - - it "when there are three values and none matches" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.concat([1, 2, 3]) - ary.reject! { |x| x == 4 } - ary.to_a.should eq([1, 2, 3]) - ary.value.should be_a(Array(Int32)) - end - - it "when there are three values and two match" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.concat([1, 2, 3]) - ary.reject! { |x| x < 3 } - ary.to_a.should eq([3]) - ary.value.should be_a(Int32) - end - - it "when there are three values and all match" do - ary = Crystal::ZeroOneOrMany(Int32).new - ary.concat([1, 2, 3]) - ary.reject! { |x| x < 4 } - ary.empty?.should be_true - ary.value.should be_nil - end - end -end diff --git a/src/compiler/crystal/codegen/call.cr b/src/compiler/crystal/codegen/call.cr index 598204154ef0..2f92d217ff80 100644 --- a/src/compiler/crystal/codegen/call.cr +++ b/src/compiler/crystal/codegen/call.cr @@ -7,7 +7,7 @@ class Crystal::CodeGenVisitor end target_defs = node.target_defs - if target_defs.empty? + unless target_defs node.raise "BUG: no target defs" end @@ -394,7 +394,7 @@ class Crystal::CodeGenVisitor position_at_end current_def_label # Prepare this specific call - call.target_defs = ZeroOneOrMany.new(a_def) + call.target_defs = [a_def] of Def call.obj.try &.set_type(a_def.owner) call.args.zip(a_def.args) do |call_arg, a_def_arg| call_arg.set_type(a_def_arg.type) diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index bb6cadd88fc7..5f480e7d18ab 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -1802,7 +1802,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor end target_defs = node.target_defs - if target_defs.empty? + unless target_defs node.raise "BUG: no target defs" end @@ -2458,7 +2458,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor end target_defs = call.target_defs - if target_defs.empty? + unless target_defs call.raise "BUG: no target defs" end diff --git a/src/compiler/crystal/interpreter/multidispatch.cr b/src/compiler/crystal/interpreter/multidispatch.cr index 005c67dca47e..3c22a7dec8a5 100644 --- a/src/compiler/crystal/interpreter/multidispatch.cr +++ b/src/compiler/crystal/interpreter/multidispatch.cr @@ -39,7 +39,7 @@ require "../semantic/main_visitor" # end # ``` module Crystal::Repl::Multidispatch - def self.create_def(context : Context, node : Call, target_defs : ZeroOneOrMany(Def)) + def self.create_def(context : Context, node : Call, target_defs : Array(Def)) if node.block a_def = create_def_uncached(context, node, target_defs) @@ -70,7 +70,7 @@ module Crystal::Repl::Multidispatch a_def end - private def self.create_def_uncached(context : Context, node : Call, target_defs : ZeroOneOrMany(Def)) + private def self.create_def_uncached(context : Context, node : Call, target_defs : Array(Def)) autocast_types = nil # The generated multidispatch method should handle autocasted @@ -200,7 +200,7 @@ module Crystal::Repl::Multidispatch end call = Call.new(call_obj, node.name, call_args) - call.target_defs = ZeroOneOrMany(Def).new(target_def) + call.target_defs = [target_def] call.type = target_def.type if block diff --git a/src/compiler/crystal/semantic/bindings.cr b/src/compiler/crystal/semantic/bindings.cr index 1705b4688f63..bba14d9be92b 100644 --- a/src/compiler/crystal/semantic/bindings.cr +++ b/src/compiler/crystal/semantic/bindings.cr @@ -1,7 +1,7 @@ module Crystal class ASTNode - property dependencies = ZeroOneOrMany(ASTNode).new - property observers = ZeroOneOrMany(ASTNode).new + property! dependencies : Array(ASTNode) + property observers : Array(ASTNode)? property enclosing_call : Call? @dirty = false @@ -107,17 +107,17 @@ module Crystal end def bind_to(node : ASTNode) : Nil - bind(node) do - @dependencies.push node + bind(node) do |dependencies| + dependencies.push node node.add_observer self end end - def bind_to(nodes : Indexable(ASTNode)) : Nil + def bind_to(nodes : Indexable) : Nil return if nodes.empty? - bind do - @dependencies.concat nodes + bind do |dependencies| + dependencies.concat nodes nodes.each &.add_observer self end end @@ -130,7 +130,9 @@ module Crystal raise_frozen_type freeze_type, from_type, from end - yield + dependencies = @dependencies ||= [] of ASTNode + + yield dependencies new_type = type_from_dependencies new_type = map_type(new_type) if new_type @@ -151,28 +153,27 @@ module Crystal Type.merge dependencies end - def unbind_from(nodes : Nil) : Nil + def unbind_from(nodes : Nil) # Nothing to do end - def unbind_from(node : ASTNode) : Nil - @dependencies.reject! &.same?(node) + def unbind_from(node : ASTNode) + @dependencies.try &.reject! &.same?(node) node.remove_observer self end - def unbind_from(nodes : Enumerable(ASTNode)) : Nil - @dependencies.reject! { |dependency| - nodes.any? &.same?(dependency) - } + def unbind_from(nodes : Array(ASTNode)) + @dependencies.try &.reject! { |dep| nodes.any? &.same?(dep) } nodes.each &.remove_observer self end - def add_observer(observer : ASTNode) : Nil - @observers.push observer + def add_observer(observer) + observers = @observers ||= [] of ASTNode + observers.push observer end - def remove_observer(observer : ASTNode) : Nil - @observers.reject! &.same?(observer) + def remove_observer(observer) + @observers.try &.reject! &.same?(observer) end def set_enclosing_call(enclosing_call) @@ -194,9 +195,9 @@ module Crystal end def notify_observers - @observers.each &.update self + @observers.try &.each &.update self @enclosing_call.try &.recalculate - @observers.each &.propagate + @observers.try &.each &.propagate @enclosing_call.try &.propagate end @@ -268,8 +269,8 @@ module Crystal visited = Set(ASTNode).new.compare_by_identity owner_trace << node if node.type?.try &.includes_type?(owner) visited.add node - until node.dependencies.empty? - dependencies = node.dependencies.select { |dep| dep.type? && dep.type.includes_type?(owner) && !visited.includes?(dep) } + while deps = node.dependencies? + dependencies = deps.select { |dep| dep.type? && dep.type.includes_type?(owner) && !visited.includes?(dep) } if dependencies.size > 0 node = dependencies.first nil_reason = node.nil_reason if node.is_a?(MetaTypeVar) diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index a3c16f7c2a33..83fe5f7f88e0 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -7,7 +7,7 @@ class Crystal::Call property! scope : Type property with_scope : Type? property! parent_visitor : MainVisitor - property target_defs = ZeroOneOrMany(Def).new + property target_defs : Array(Def)? property expanded : ASTNode? property expanded_macro : Macro? property? uses_with_scope = false @@ -20,14 +20,15 @@ class Crystal::Call end def target_def - case target_defs.size - when 0 - ::raise "Zero target defs for #{self}" - when 1 - target_defs.first - else - ::raise "#{target_defs.size} target defs for #{self}" + if defs = @target_defs + if defs.size == 1 + return defs.first + else + ::raise "#{defs.size} target defs for #{self}" + end end + + ::raise "Zero target defs for #{self}" end def recalculate @@ -77,10 +78,10 @@ class Crystal::Call block = @block - unbind_from target_defs unless target_defs.empty? + unbind_from @target_defs if @target_defs unbind_from block.break if block - @target_defs = ZeroOneOrMany(Def).new + @target_defs = nil if block_arg = @block_arg replace_block_arg_with_block(block_arg) @@ -91,12 +92,11 @@ class Crystal::Call # If @target_defs is set here it means there was a recalculation # fired as a result of a recalculation. We keep the last one. - return unless target_defs.empty? + return if @target_defs - # TODO: optimize zero one or many @target_defs = matches - bind_to matches unless matches.empty? + bind_to matches if matches bind_to block.break if block if (parent_visitor = @parent_visitor) && matches @@ -109,13 +109,13 @@ class Crystal::Call end end - def lookup_matches : ZeroOneOrMany(Def) + def lookup_matches lookup_matches(with_autocast: false) rescue ex : RetryLookupWithLiterals lookup_matches(with_autocast: true) end - def lookup_matches(*, with_autocast = false) : ZeroOneOrMany(Def) + def lookup_matches(*, with_autocast = false) if args.any? { |arg| arg.is_a?(Splat) || arg.is_a?(DoubleSplat) } lookup_matches_with_splat(with_autocast) else @@ -133,7 +133,7 @@ class Crystal::Call end end - def lookup_matches_with_splat(with_autocast) : ZeroOneOrMany(Def) + def lookup_matches_with_splat(with_autocast) # Check if all splat are of tuples arg_types = Array(Type).new(args.size * 2) named_args_types = nil @@ -184,7 +184,7 @@ class Crystal::Call lookup_matches_without_splat arg_types, named_args_types, with_autocast: with_autocast end - def lookup_matches_without_splat(arg_types, named_args_types, with_autocast) : ZeroOneOrMany(Def) + def lookup_matches_without_splat(arg_types, named_args_types, with_autocast) if obj = @obj lookup_matches_in(obj.type, arg_types, named_args_types, with_autocast: with_autocast) elsif name == "super" @@ -198,19 +198,15 @@ class Crystal::Call end end - def lookup_matches_in(owner : AliasType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) : ZeroOneOrMany(Def) + def lookup_matches_in(owner : AliasType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) lookup_matches_in(owner.remove_alias, arg_types, named_args_types, search_in_parents: search_in_parents, with_autocast: with_autocast) end - def lookup_matches_in(owner : UnionType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) : ZeroOneOrMany(Def) - matches = ZeroOneOrMany(Def).new - owner.union_types.each { |type| - matches.concat lookup_matches_in(type, arg_types, named_args_types, search_in_parents: search_in_parents, with_autocast: with_autocast) - } - matches + def lookup_matches_in(owner : UnionType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) + owner.union_types.flat_map { |type| lookup_matches_in(type, arg_types, named_args_types, search_in_parents: search_in_parents, with_autocast: with_autocast) } end - def lookup_matches_in(owner : Program, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) : ZeroOneOrMany(Def) + def lookup_matches_in(owner : Program, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) lookup_matches_in_type(owner, arg_types, named_args_types, self_type, def_name, search_in_parents: search_in_parents, with_autocast: with_autocast) end @@ -218,26 +214,26 @@ class Crystal::Call lookup_matches_in program, arg_types, named_args_types, search_in_parents: search_in_parents, with_autocast: with_autocast end - def lookup_matches_in(owner : NonGenericModuleType | GenericModuleInstanceType | GenericType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) : ZeroOneOrMany(Def) + def lookup_matches_in(owner : NonGenericModuleType | GenericModuleInstanceType | GenericType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) attach_subclass_observer owner including_types = owner.including_types if including_types lookup_matches_in(including_types, arg_types, named_args_types, search_in_parents: search_in_parents, with_autocast: with_autocast) else - ZeroOneOrMany(Def).new + [] of Def end end - def lookup_matches_in(owner : LibType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) : ZeroOneOrMany(Def) + def lookup_matches_in(owner : LibType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) raise "lib fun call is not supported in dispatch" end - def lookup_matches_in(owner : Type, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) : ZeroOneOrMany(Def) + def lookup_matches_in(owner : Type, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) lookup_matches_in_type(owner, arg_types, named_args_types, self_type, def_name, search_in_parents: search_in_parents, with_autocast: with_autocast) end - def lookup_matches_with_scope_in(owner, arg_types, named_args_types, with_autocast = false) : ZeroOneOrMany(Def) + def lookup_matches_with_scope_in(owner, arg_types, named_args_types, with_autocast = false) signature = CallSignature.new(name, arg_types, block, named_args_types) matches = lookup_matches_checking_expansion(owner, signature, with_autocast: with_autocast) @@ -255,7 +251,7 @@ class Crystal::Call instantiate signature, matches, owner, self_type: nil, with_autocast: with_autocast end - def lookup_matches_in_type(owner, arg_types, named_args_types, self_type, def_name, search_in_parents, search_in_toplevel = true, with_autocast = false) : ZeroOneOrMany(Def) + def lookup_matches_in_type(owner, arg_types, named_args_types, self_type, def_name, search_in_parents, search_in_toplevel = true, with_autocast = false) signature = CallSignature.new(def_name, arg_types, block, named_args_types) matches = check_tuple_indexer(owner, def_name, args, arg_types) @@ -357,13 +353,12 @@ class Crystal::Call end end - def instantiate(signature, matches, owner, self_type, with_autocast) : ZeroOneOrMany(Def) + def instantiate(signature, matches, owner, self_type, with_autocast) matches.each &.remove_literals if with_autocast block = @block - # TODO: use map - typed_defs = ZeroOneOrMany(Def).new + typed_defs = Array(Def).new(matches.size) matches.each do |match| check_visibility match @@ -458,7 +453,7 @@ class Crystal::Call typed_def.type = return_type if return_type.no_return? || return_type.nil_type? end - def check_tuple_indexer(owner, def_name, args, arg_types) : Matches? + def check_tuple_indexer(owner, def_name, args, arg_types) return unless args.size == 1 case def_name @@ -493,13 +488,13 @@ class Crystal::Call end end - def tuple_indexer_helper(args, arg_types, owner, instance_type, nilable) : Matches? + def tuple_indexer_helper(args, arg_types, owner, instance_type, nilable) index = tuple_indexer_helper_index(args.first, owner, instance_type, nilable) return unless index indexer_def = yield instance_type, index indexer_match = Match.new(indexer_def, arg_types, MatchContext.new(owner, owner)) - Matches.new(ZeroOneOrMany(Match).new(indexer_match), true) + Matches.new([indexer_match] of Match, true) end private def tuple_indexer_helper_index(arg, owner, instance_type, nilable) @@ -579,7 +574,7 @@ class Crystal::Call if index || nilable indexer_def = yield instance_type, (index || -1) indexer_match = Match.new(indexer_def, arg_types, MatchContext.new(owner, owner)) - Matches.new(ZeroOneOrMany(Match).new(indexer_match), true) + Matches.new([indexer_match] of Match, true) else raise "missing key '#{name}' for named tuple #{owner}" end @@ -710,7 +705,7 @@ class Crystal::Call signature = CallSignature.new(previous.name, arg_types, block, named_args_types) context = MatchContext.new(scope, scope, def_free_vars: previous.free_vars) match = Match.new(previous, arg_types, context, named_args_types) - matches = Matches.new(ZeroOneOrMany(Match).new(match), true) + matches = Matches.new([match] of Match, true) unless signature.match(previous_item, context) raise_matches_not_found scope, previous.name, arg_types, named_args_types, matches, with_autocast: with_autocast, number_autocast: !program.has_flag?("no_number_autocast") diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index 2984ed5fd4c2..875fd5269380 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -643,7 +643,8 @@ class Crystal::Call if obj.is_a?(InstanceVar) scope = self.scope ivar = scope.lookup_instance_var(obj.name) - if ivar.dependencies.size == 1 && ivar.dependencies.first.same?(program.nil_var) + deps = ivar.dependencies? + if deps && deps.size == 1 && deps.first.same?(program.nil_var) similar_name = scope.lookup_similar_instance_var_name(ivar.name) if similar_name msg << colorize(" (#{ivar.name} was never assigned a value, did you mean #{similar_name}?)").yellow.bold diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index dd3c4e03bc67..0d7aa41ced5b 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -562,42 +562,43 @@ module Crystal return exps end - target_defs = node.target_defs - if target_defs.size == 1 - if target_defs.first.is_a?(External) - check_args_are_not_closure node, "can't send closure to C function" - elsif obj_type && obj_type.extern? && node.name.ends_with?('=') - check_args_are_not_closure node, "can't set closure as C #{obj_type.type_desc} member" + if target_defs = node.target_defs + if target_defs.size == 1 + if target_defs[0].is_a?(External) + check_args_are_not_closure node, "can't send closure to C function" + elsif obj_type && obj_type.extern? && node.name.ends_with?('=') + check_args_are_not_closure node, "can't set closure as C #{obj_type.type_desc} member" + end end - end - current_def = @current_def + current_def = @current_def - target_defs.each do |target_def| - if @transformed.add?(target_def) - node.bubbling_exception do - @current_def = target_def - @def_nest_count += 1 - target_def.body = target_def.body.transform(self) - @def_nest_count -= 1 - @current_def = current_def + target_defs.each do |target_def| + if @transformed.add?(target_def) + node.bubbling_exception do + @current_def = target_def + @def_nest_count += 1 + target_def.body = target_def.body.transform(self) + @def_nest_count -= 1 + @current_def = current_def + end end - end - # If the current call targets a method that raises, the method - # where the call happens also raises. - current_def.raises = true if current_def && target_def.raises? - end + # If the current call targets a method that raises, the method + # where the call happens also raises. + current_def.raises = true if current_def && target_def.raises? + end - if target_defs.empty? - exps = [] of ASTNode - if obj = node.obj - exps.push obj + if node.target_defs.not_nil!.empty? + exps = [] of ASTNode + if obj = node.obj + exps.push obj + end + node.args.each { |arg| exps.push arg } + call_exps = Expressions.from exps + call_exps.set_type(exps.last.type?) unless exps.empty? + return call_exps end - node.args.each { |arg| exps.push arg } - call_exps = Expressions.from exps - call_exps.set_type(exps.last.type?) unless exps.empty? - return call_exps end node.replace_splats @@ -1068,7 +1069,10 @@ module Crystal node = super unless node.type? - node.unbind_from node.dependencies + if dependencies = node.dependencies? + node.unbind_from node.dependencies + end + node.bind_to node.expressions end diff --git a/src/compiler/crystal/semantic/cover.cr b/src/compiler/crystal/semantic/cover.cr index 1ae936cba44c..4674d0a12248 100644 --- a/src/compiler/crystal/semantic/cover.cr +++ b/src/compiler/crystal/semantic/cover.cr @@ -4,9 +4,9 @@ require "../types" module Crystal struct Cover getter signature : CallSignature - getter matches : ZeroOneOrMany(Match) + getter matches : Array(Match) - def self.create(signature : CallSignature, matches : ZeroOneOrMany(Match)) + def self.create(signature, matches) if matches matches.empty? ? false : Cover.new(signature, matches) else @@ -14,7 +14,7 @@ module Crystal end end - def initialize(@signature : CallSignature, @matches : ZeroOneOrMany(Match)) + def initialize(@signature, @matches) end def all? diff --git a/src/compiler/crystal/semantic/filters.cr b/src/compiler/crystal/semantic/filters.cr index ebc77d01e675..86938b7e7b77 100644 --- a/src/compiler/crystal/semantic/filters.cr +++ b/src/compiler/crystal/semantic/filters.cr @@ -1,7 +1,7 @@ module Crystal class TypeFilteredNode < ASTNode def initialize(@filter : TypeFilter, @node : ASTNode) - @dependencies = ZeroOneOrMany.new(@node) + @dependencies = [@node] of ASTNode node.add_observer self update(@node) end diff --git a/src/compiler/crystal/semantic/fix_missing_types.cr b/src/compiler/crystal/semantic/fix_missing_types.cr index 07a113c50d20..3a0eb5857d1d 100644 --- a/src/compiler/crystal/semantic/fix_missing_types.cr +++ b/src/compiler/crystal/semantic/fix_missing_types.cr @@ -74,7 +74,7 @@ class Crystal::FixMissingTypes < Crystal::Visitor block.type = @program.no_return end - node.target_defs.each do |target_def| + node.target_defs.try &.each do |target_def| if @fixed.add?(target_def) target_def.type = @program.no_return unless target_def.type? target_def.accept_children self diff --git a/src/compiler/crystal/semantic/lib.cr b/src/compiler/crystal/semantic/lib.cr index f81f63226795..5e0bff6ce3ad 100644 --- a/src/compiler/crystal/semantic/lib.cr +++ b/src/compiler/crystal/semantic/lib.cr @@ -23,10 +23,11 @@ class Crystal::Call obj_type.used = true external.used = true - @target_defs = ZeroOneOrMany(Def).new(external) + untyped_defs = [external] of Def + @target_defs = untyped_defs - self.unbind_from old_target_defs unless old_target_defs.empty? - self.bind_to external + self.unbind_from old_target_defs if old_target_defs + self.bind_to untyped_defs end def check_lib_call_named_args(external) diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 44da862b2a1d..e3c710134e1e 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -370,7 +370,8 @@ module Crystal var.bind_to(@program.nil_var) var.nil_if_read = false - bind_to_program_nil_var(meta_var) + meta_var.bind_to(@program.nil_var) unless meta_var.dependencies.try &.any? &.same?(@program.nil_var) + node.bind_to(@program.nil_var) end check_mutably_closured meta_var, var @@ -395,10 +396,6 @@ module Crystal end end - private def bind_to_program_nil_var(node) - node.bind_to(@program.nil_var) unless node.dependencies.any? &.same?(@program.nil_var) - end - def visit(node : TypeDeclaration) case var = node.var when Var @@ -600,6 +597,19 @@ module Crystal program.undefined_instance_variable(node, owner, similar_name) end + def first_time_accessing_meta_type_var?(var) + return false if var.uninitialized? + + if var.freeze_type + deps = var.dependencies? + # If no dependencies, it's the case of a global for a regex literal. + # If there are dependencies and it's just one, it's the same var + deps ? deps.size == 1 : false + else + !var.dependencies? + end + end + def visit(node : InstanceVar) var = lookup_instance_var node node.bind_to(var) @@ -1242,7 +1252,7 @@ module Crystal # It can happen that this call is inside an ArrayLiteral or HashLiteral, # was expanded but isn't bound to the expansion because the call (together # with its expansion) was cloned. - if (expanded = node.expanded) && (node.dependencies.empty? || !node.type?) + if (expanded = node.expanded) && (!node.dependencies? || !node.type?) node.bind_to(expanded) end @@ -3246,7 +3256,7 @@ module Crystal def define_special_var(name, value) meta_var, _ = assign_to_meta_var(name) meta_var.bind_to value - bind_to_program_nil_var(meta_var) + meta_var.bind_to program.nil_var unless meta_var.dependencies.any? &.same?(program.nil_var) meta_var.assigned_to = true check_closured meta_var diff --git a/src/compiler/crystal/semantic/match.cr b/src/compiler/crystal/semantic/match.cr index 0e282e497d42..f18572f69291 100644 --- a/src/compiler/crystal/semantic/match.cr +++ b/src/compiler/crystal/semantic/match.cr @@ -130,7 +130,7 @@ module Crystal struct Matches include Enumerable(Match) - property matches : ZeroOneOrMany(Match) + property matches : Array(Match)? property cover : Bool | Cover | Nil property owner : Type? @@ -139,25 +139,32 @@ module Crystal def cover_all? cover = @cover - @success && !@matches.empty? && (cover == true || (cover.is_a?(Cover) && cover.all?)) + matches = @matches + @success && matches && matches.size > 0 && (cover == true || (cover.is_a?(Cover) && cover.all?)) end def empty? - !@success || matches.empty? + return true unless @success + + if matches = @matches + matches.empty? + else + true + end end def each - @success && @matches.each do |match| + @success && @matches.try &.each do |match| yield match end end def size - @matches.size + @matches.try(&.size) || 0 end def [](*args) - Matches.new(@matches.[*args], @cover, @owner, @success) + Matches.new(@matches.try &.[](*args), @cover, @owner, @success) end end end diff --git a/src/compiler/crystal/semantic/method_lookup.cr b/src/compiler/crystal/semantic/method_lookup.cr index c7f1e8087507..9c5e42c12ac0 100644 --- a/src/compiler/crystal/semantic/method_lookup.cr +++ b/src/compiler/crystal/semantic/method_lookup.cr @@ -66,7 +66,7 @@ module Crystal named_args : Array(NamedArgumentType)? class Type - def lookup_matches(signature, owner = self, path_lookup = self, matches_array = ZeroOneOrMany(Match).new, analyze_all = false) + def lookup_matches(signature, owner = self, path_lookup = self, matches_array = nil, analyze_all = false) matches = lookup_matches_without_parents(signature, owner, path_lookup, matches_array, analyze_all: analyze_all) return matches if matches.cover_all? @@ -105,7 +105,7 @@ module Crystal Matches.new(matches_array, cover, owner, false) end - def lookup_matches_without_parents(signature, owner = self, path_lookup = self, matches_array = ZeroOneOrMany(Match).new, analyze_all = false) + def lookup_matches_without_parents(signature, owner = self, path_lookup = self, matches_array = nil, analyze_all = false) if defs = self.defs.try &.[signature.name]? context = MatchContext.new(owner, path_lookup) @@ -129,7 +129,8 @@ module Crystal next if exact_match if match - matches_array.push match + matches_array ||= [] of Match + matches_array << match # If the argument types are compatible with the match's argument types, # we are done. We don't just compare types with ==, there is a special case: @@ -157,7 +158,7 @@ module Crystal Matches.new(matches_array, Cover.create(signature, matches_array), owner) end - def lookup_matches_with_modules(signature, owner = self, path_lookup = self, matches_array = ZeroOneOrMany(Match).new, analyze_all = false) + def lookup_matches_with_modules(signature, owner = self, path_lookup = self, matches_array = nil, analyze_all = false) matches = lookup_matches_without_parents(signature, owner, path_lookup, matches_array, analyze_all: analyze_all) return matches unless matches.empty? @@ -439,7 +440,7 @@ module Crystal if is_new && subtype_matches.empty? other_initializers = subtype_lookup.instance_type.lookup_defs_with_modules("initialize") unless other_initializers.empty? - return Matches.new(ZeroOneOrMany(Match).new, false) + return Matches.new(nil, false) end end @@ -447,7 +448,7 @@ module Crystal # def, we need to copy it to the subclass so that @name, @instance_vars and other # macro vars resolve correctly. if subtype_matches.empty? - new_subtype_matches = ZeroOneOrMany(Match).new + new_subtype_matches = nil base_type_matches.each do |base_type_match| if base_type_match.def.macro_def? @@ -479,6 +480,7 @@ module Crystal changes << Change.new(change_owner, cloned_def) end + new_subtype_matches ||= [] of Match new_subtype_matches.push Match.new(cloned_def, full_subtype_match.arg_types, MatchContext.new(subtype_lookup, full_subtype_match.context.defining_type, full_subtype_match.context.free_vars), full_subtype_match.named_arg_types) end @@ -487,7 +489,7 @@ module Crystal end end - unless new_subtype_matches.empty? + if new_subtype_matches subtype_matches = Matches.new(new_subtype_matches, Cover.create(signature, new_subtype_matches)) end end diff --git a/src/compiler/crystal/semantic/type_merge.cr b/src/compiler/crystal/semantic/type_merge.cr index 9b2fe4e236f5..0d79faee06b1 100644 --- a/src/compiler/crystal/semantic/type_merge.cr +++ b/src/compiler/crystal/semantic/type_merge.cr @@ -2,7 +2,7 @@ require "../program" module Crystal class Program - def type_merge(types : Indexable(Type?)) : Type? + def type_merge(types : Array(Type?)) : Type? case types.size when 0 nil @@ -17,7 +17,7 @@ module Crystal end end - def type_merge(nodes : Indexable(ASTNode)) : Type? + def type_merge(nodes : Array(ASTNode)) : Type? case nodes.size when 0 nil @@ -161,11 +161,11 @@ module Crystal end class Type - def self.merge(nodes : Indexable(ASTNode)) : Type? + def self.merge(nodes : Array(ASTNode)) : Type? nodes.find(&.type?).try &.type.program.type_merge(nodes) end - def self.merge(types : Indexable(Type)) : Type? + def self.merge(types : Array(Type)) : Type? if types.size == 0 nil else diff --git a/src/compiler/crystal/semantic/warnings.cr b/src/compiler/crystal/semantic/warnings.cr index 437632bc9953..0dae71c5cd06 100644 --- a/src/compiler/crystal/semantic/warnings.cr +++ b/src/compiler/crystal/semantic/warnings.cr @@ -26,7 +26,7 @@ module Crystal return unless @warnings.level.all? return if compiler_expanded_call(node) - node.target_defs.each do |target_def| + node.target_defs.try &.each do |target_def| check_deprecation(target_def, node, @deprecated_methods_detected) end end diff --git a/src/compiler/crystal/tools/context.cr b/src/compiler/crystal/tools/context.cr index 7bf49397fd7c..5bc6dd43e0e9 100644 --- a/src/compiler/crystal/tools/context.cr +++ b/src/compiler/crystal/tools/context.cr @@ -77,11 +77,13 @@ module Crystal def visit(node : Call) return false if node.obj.nil? && node.name == "raise" - node.target_defs.each do |typed_def| - typed_def.accept(self) - next unless @context_visitor.def_with_yield.not_nil!.location == typed_def.location - @context_visitor.inside_typed_def do - typed_def.accept(@context_visitor) + node.target_defs.try do |defs| + defs.each do |typed_def| + typed_def.accept(self) + next unless @context_visitor.def_with_yield.not_nil!.location == typed_def.location + @context_visitor.inside_typed_def do + typed_def.accept(@context_visitor) + end end end true diff --git a/src/compiler/crystal/tools/implementations.cr b/src/compiler/crystal/tools/implementations.cr index 4e49f18c4712..f4f3a390f0eb 100644 --- a/src/compiler/crystal/tools/implementations.cr +++ b/src/compiler/crystal/tools/implementations.cr @@ -109,8 +109,10 @@ module Crystal def visit(node : Call) return contains_target(node) unless node.location && @target_location.between?(node.name_location, node.name_end_location) - node.target_defs.each do |target_def| - @locations << target_def.location.not_nil! + if target_defs = node.target_defs + target_defs.each do |target_def| + @locations << target_def.location.not_nil! + end end end diff --git a/src/compiler/crystal/zero_one_or_many.cr b/src/compiler/crystal/zero_one_or_many.cr deleted file mode 100644 index 80c1557e09cb..000000000000 --- a/src/compiler/crystal/zero_one_or_many.cr +++ /dev/null @@ -1,117 +0,0 @@ -# An Array(T)-like type that's optimized for the case of -# frequently having zero or one elements. -struct Crystal::ZeroOneOrMany(T) - include Indexable(T) - - getter value : Nil | T | Array(T) - - def initialize - @value = nil - end - - def initialize(@value : T) - end - - def unsafe_fetch(index : Int) - value = @value - case value - in Nil - raise IndexError.new("Called ZeroOneOrMany#unsafe_fetch but value is nil") - in T - if index != 0 - raise IndexError.new("Called ZeroOneOrMany#unsafe_fetch with index != 0 but value is not an array") - end - - value - in Array(T) - value.unsafe_fetch(index) - end - end - - def each(& : T ->) - value = @value - case value - in Nil - # Nothing to do - in T - yield value - in Array(T) - value.each do |element| - yield element - end - end - end - - def size : Int32 - value = @value - case value - in Nil - 0 - in T - 1 - in Array(T) - value.size - end - end - - def <<(element : T) : self - push(element) - end - - def push(element : T) : self - value = @value - case value - in Nil - @value = element - in T - @value = [value, element] of T - in Array(T) - value.push element - end - self - end - - def concat(elements : Indexable(T)) : self - value = @value - case value - in Nil - case elements.size - when 0 - # Nothing to do - when 1 - @value = elements.first - else - @value = elements.map(&.as(T)).to_a - end - in T - new_value = Array(T).new(elements.size + 1) - new_value.push(value) - new_value.concat(elements) - @value = new_value - in Array(T) - value.concat(elements) - end - self - end - - def reject!(&block : T -> _) : self - value = @value - case value - in Nil - # Nothing to do - in T - if yield value - @value = nil - end - in Array(T) - value.reject! { |element| yield element } - case value.size - when 0 - @value = nil - when 1 - @value = value.first - end - end - self - end -end From 49ab4252b2e778400d70c1747a7154be417c117d Mon Sep 17 00:00:00 2001 From: Pete Brumm Date: Sat, 17 Dec 2022 07:09:39 -0600 Subject: [PATCH 0192/1551] Fix: Read `UInt` in zip directory header (#12822) Co-authored-by: Sijawusz Pur Rahnama --- spec/std/compress/zip/zip_file_spec.cr | 17 +++++++++++++++++ src/compress/zip/file.cr | 8 ++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/spec/std/compress/zip/zip_file_spec.cr b/spec/std/compress/zip/zip_file_spec.cr index 89a1a7ad1606..24deddb5cb06 100644 --- a/spec/std/compress/zip/zip_file_spec.cr +++ b/spec/std/compress/zip/zip_file_spec.cr @@ -127,6 +127,23 @@ describe Compress::Zip do end end + it "writes over int16 files to make sure we can parse" do + io = IO::Memory.new + + Compress::Zip::Writer.open(io) do |zip| + 0_u16.upto(UInt16::MAX - 1).each do |index| + zip.add Compress::Zip::Writer::Entry.new("foo_#{index}.txt", comment: "some comment"), + "contents of foo" + end + end + + io.rewind + + Compress::Zip::File.open(io) do |zip| + zip.entries.size.should eq(UInt16::MAX) + end + end + typeof(Compress::Zip::File.new("file.zip")) typeof(Compress::Zip::File.open("file.zip") { }) end diff --git a/src/compress/zip/file.cr b/src/compress/zip/file.cr index ba2c133231c9..166626a0b73f 100644 --- a/src/compress/zip/file.cr +++ b/src/compress/zip/file.cr @@ -121,10 +121,10 @@ class Compress::Zip::File raise Error.new("Expected end of central directory header signature, not 0x#{signature.to_s(16)}") end - read Int16 # number of this disk - read Int16 # disk start - read Int16 # number of entries in disk - entries_size = read Int16 # number of total entries + read UInt16 # number of this disk + read UInt16 # disk start + read UInt16 # number of entries in disk + entries_size = read UInt16 # number of total entries read UInt32 # size of the central directory directory_offset = read UInt32 # offset of central directory comment_length = read UInt16 # comment length From f4ae5f033b379fb96c00b04e344e6c17994b50e7 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sat, 17 Dec 2022 13:03:49 -0300 Subject: [PATCH 0193/1551] Add lib functions earlier so that they are visible in top-level macros (#12848) --- spec/compiler/semantic/lib_spec.cr | 14 ++++++++++++++ src/compiler/crystal/semantic/top_level_visitor.cr | 9 ++++++++- .../crystal/semantic/type_declaration_visitor.cr | 8 ++++---- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/spec/compiler/semantic/lib_spec.cr b/spec/compiler/semantic/lib_spec.cr index 70c2a7b11819..1ee3c7aa99c6 100644 --- a/spec/compiler/semantic/lib_spec.cr +++ b/spec/compiler/semantic/lib_spec.cr @@ -962,4 +962,18 @@ describe "Semantic: lib" do ), "passing Void return value of lib fun call has no effect" end + + it "can list lib functions at the top level (#12395)" do + assert_type(%( + lib LibFoo + fun foo + end + + {% if LibFoo.methods.size == 1 %} + true + {% else %} + 1 + {% end %} + )) { bool } + end end diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index a3e4fbdd4a2c..dea95f0f9494 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -914,7 +914,12 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor annotations = read_annotations - external = External.new(node.name, ([] of Arg), node.body, node.real_name).at(node) + # We'll resolve the external args types later, in TypeDeclarationVisitor + external_args = node.args.map do |arg| + Arg.new(arg.name).at(arg.location) + end + + external = External.new(node.name, external_args, node.body, node.real_name).at(node) call_convention = nil process_def_annotations(external, annotations) do |annotation_type, ann| @@ -947,6 +952,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor external.fun_def = node node.external = external + current_type.add_def(external) + false end diff --git a/src/compiler/crystal/semantic/type_declaration_visitor.cr b/src/compiler/crystal/semantic/type_declaration_visitor.cr index db557ee6d634..bcb3a63e6eb6 100644 --- a/src/compiler/crystal/semantic/type_declaration_visitor.cr +++ b/src/compiler/crystal/semantic/type_declaration_visitor.cr @@ -107,14 +107,16 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor def visit(node : FunDef) external = node.external - node.args.each do |arg| + node.args.each_with_index do |arg, index| restriction = arg.restriction.not_nil! arg_type = lookup_type(restriction) arg_type = check_allowed_in_lib(restriction, arg_type) if arg_type.remove_typedef.void? restriction.raise "can't use Void as parameter type" end - external.args << Arg.new(arg.name, type: arg_type).at(arg.location) + + # The external args were added in TopLevelVisitor + external.args[index].type = arg_type end node_return_type = node.return_type @@ -131,8 +133,6 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor old_external = add_external external old_external.dead = true if old_external - current_type.add_def(external) - if current_type.is_a?(Program) key = DefInstanceKey.new external.object_id, external.args.map(&.type), nil, nil program.add_def_instance key, external From adfc4ce39f28773905ecadf429e172881d5afdac Mon Sep 17 00:00:00 2001 From: Mike Robbins Date: Tue, 20 Dec 2022 06:27:30 -0500 Subject: [PATCH 0194/1551] Fix `HTTP::Client` certificate validation error on FQDN (host with trailing dot) (#12778) Co-authored-by: Quinton Miller --- spec/manual/https_client_spec.cr | 4 ++++ spec/std/uri_spec.cr | 4 ++++ src/http/client.cr | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/spec/manual/https_client_spec.cr b/spec/manual/https_client_spec.cr index 3780a088cbc2..5c1dc11199b2 100644 --- a/spec/manual/https_client_spec.cr +++ b/spec/manual/https_client_spec.cr @@ -7,6 +7,10 @@ describe "https requests" do HTTP::Client.get("https://google.com") end + it "can fetch from google.com. FQDN with trailing dot (#12777)" do + HTTP::Client.get("https://google.com.") + end + it "can close request before consuming body" do HTTP::Client.get("https://crystal-lang.org") do break diff --git a/spec/std/uri_spec.cr b/spec/std/uri_spec.cr index 5e8bb02f7835..96aea2e16989 100644 --- a/spec/std/uri_spec.cr +++ b/spec/std/uri_spec.cr @@ -68,6 +68,10 @@ describe "URI" do assert_uri("http://[::1]:81/", scheme: "http", host: "[::1]", port: 81, path: "/") assert_uri("http://192.0.2.16:81/", scheme: "http", host: "192.0.2.16", port: 81, path: "/") + # preserves fully-qualified host with trailing dot + assert_uri("https://example.com./", scheme: "https", host: "example.com.", path: "/") + assert_uri("https://example.com.:8443/", scheme: "https", host: "example.com.", port: 8443, path: "/") + # port it { URI.parse("http://192.168.0.2:/foo").should eq URI.new(scheme: "http", host: "192.168.0.2", path: "/foo") } diff --git a/src/http/client.cr b/src/http/client.cr index 80f3506b60a3..eeb6300f4f5d 100644 --- a/src/http/client.cr +++ b/src/http/client.cr @@ -801,7 +801,7 @@ class HTTP::Client if tls = @tls tcp_socket = io begin - io = OpenSSL::SSL::Socket::Client.new(tcp_socket, context: tls, sync_close: true, hostname: @host) + io = OpenSSL::SSL::Socket::Client.new(tcp_socket, context: tls, sync_close: true, hostname: @host.rchop('.')) rescue exc # don't leak the TCP socket when the SSL connection failed tcp_socket.close From b7aa8aaa29abcf652196ea68801a823de6d6544c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Tue, 20 Dec 2022 06:34:16 -0800 Subject: [PATCH 0195/1551] Parser: fix wrong/missing locations of various AST nodes (#11798) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/compiler/parser/parser_spec.cr | 150 +++++++++++++++++- src/compiler/crystal/syntax/ast.cr | 2 +- src/compiler/crystal/syntax/lexer.cr | 4 +- src/compiler/crystal/syntax/parser.cr | 214 ++++++++++++++++---------- 4 files changed, 283 insertions(+), 87 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 3f0e70358a52..cc4b11238e41 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -22,10 +22,30 @@ private def it_parses(string, expected_node, file = __FILE__, line = __LINE__, * end end +private def location_to_index(string, location) + index = 0 + (location.line_number - 1).times do + index = string.index!('\n', index) + 1 + end + index + location.column_number - 1 +end + +private def source_between(string, loc, end_loc) + beginning = location_to_index(string, loc.not_nil!) + ending = location_to_index(string, end_loc.not_nil!) + string[beginning..ending] +end + +private def node_source(string, node) + source_between(string, node.location, node.end_location) +end + private def assert_end_location(source, line_number = 1, column_number = source.size, file = __FILE__, line = __LINE__) it "gets corrects end location for #{source.inspect}", file, line do - parser = Parser.new("#{source}; 1") + string = "#{source}; 1" + parser = Parser.new(string) node = parser.parse.as(Expressions).expressions[0] + node_source(string, node).should eq(source) end_loc = node.end_location.not_nil! end_loc.line_number.should eq(line_number) end_loc.column_number.should eq(column_number) @@ -2162,6 +2182,57 @@ module Crystal assert_end_location "extend Foo" assert_end_location "1.as(Int32)" assert_end_location "puts obj.foo" + assert_end_location "a, b = 1, 2 if 3" + assert_end_location "abstract def foo(x)" + assert_end_location "::foo" + assert_end_location "foo.[0] = 1" + assert_end_location "x : Foo(A, *B, C)" + assert_end_location "Int[8]?" + assert_end_location "[1, 2,]" + assert_end_location "foo(\n &.block\n)", line_number: 3, column_number: 1 + assert_end_location "foo.bar(x) do; end" + assert_end_location "%w(one two)" + assert_end_location "{%\nif foo\n bar\n end\n%}", line_number: 5, column_number: 2 + assert_end_location "foo bar, out baz" + assert_end_location "Foo?" + assert_end_location "foo : Foo.class" + assert_end_location "foo : Foo?" + assert_end_location "foo : Foo*" + assert_end_location "foo : Foo**" + assert_end_location "foo : Foo[42]" + assert_end_location "foo ->bar" + assert_end_location "foo ->bar=" + assert_end_location "foo ->self.bar" + assert_end_location "foo ->self.bar=" + assert_end_location "foo ->Bar.baz" + assert_end_location "foo ->Bar.baz=" + assert_end_location "foo ->@bar.baz" + assert_end_location "foo ->@bar.baz=" + assert_end_location "foo ->@@bar.baz" + assert_end_location "foo ->@@bar.baz=" + assert_end_location "foo ->bar(Baz)" + assert_end_location "foo *bar" + assert_end_location "foo **bar" + assert_end_location "Foo { 1 }" + assert_end_location "foo.!" + assert_end_location "foo.!()" + assert_end_location "f.x = foo" + assert_end_location "f.x=(*foo)" + assert_end_location "f.x=(foo).bar" + assert_end_location "x : Foo ->" + assert_end_location "x : Foo -> Bar" + assert_end_location %(require "foo") + assert_end_location "begin; 1; 2; 3; end" + assert_end_location "1.." + assert_end_location "foo.responds_to?(:foo)" + assert_end_location "foo.responds_to? :foo" + assert_end_location "foo.nil?" + assert_end_location "foo.nil?( )" + assert_end_location "@a = uninitialized Foo" + assert_end_location "@@a = uninitialized Foo" + assert_end_location "1 rescue 2" + assert_end_location "1 ensure 2" + assert_end_location "foo.bar= *baz" assert_syntax_error %({"a" : 1}), "space not allowed between named argument name and ':'" assert_syntax_error %({"a": 1, "b" : 2}), "space not allowed between named argument name and ':'" @@ -2359,6 +2430,12 @@ module Crystal node.location.not_nil!.line_number.should eq(1) node.else_location.not_nil!.line_number.should eq(2) node.end_location.not_nil!.line_number.should eq(3) + + parser = Parser.new("if foo\nend") + node = parser.parse.as(If) + node.location.not_nil!.line_number.should eq(1) + node.else_location.should be_nil + node.end_location.not_nil!.line_number.should eq(2) end it "sets correct location of `elsif` of if statement" do @@ -2408,6 +2485,77 @@ module Crystal node.end_location.not_nil!.line_number.should eq(5) end + it "sets correct location of trailing ensure" do + parser = Parser.new("foo ensure bar") + node = parser.parse.as(ExceptionHandler) + ensure_location = node.ensure_location.not_nil! + ensure_location.line_number.should eq(1) + ensure_location.column_number.should eq(5) + end + + it "sets correct location of trailing rescue" do + source = "foo rescue bar" + parser = Parser.new(source) + node = parser.parse.as(ExceptionHandler).rescues.not_nil![0] + node_source(source, node).should eq("rescue bar") + end + + it "sets correct location of call name" do + source = "foo(bar)" + node = Parser.new(source).parse.as(Call) + source_between(source, node.name_location, node.name_end_location).should eq("foo") + end + + it "sets correct location of element in array literal" do + source = "%i(foo bar)" + elements = Parser.new(source).parse.as(ArrayLiteral).elements + node_source(source, elements[0]).should eq("foo") + node_source(source, elements[1]).should eq("bar") + end + + it "sets correct location of implicit tuple literal of multi-return" do + source = "def foo; return 1, 2; end" + node = Parser.new(source).parse.as(Def).body.as(Return).exp.not_nil! + node_source(source, node).should eq("1, 2") + end + + it "sets correct location of var in type declaration" do + source = "foo : Int32" + node = Parser.new(source).parse.as(TypeDeclaration).var + node_source(source, node).should eq("foo") + + source = "begin : Int32" + node = Parser.new(source).parse.as(TypeDeclaration).var + node_source(source, node).should eq("begin") + end + + it "sets correct location of var in proc pointer" do + source = "foo : Foo; ->foo.bar" + expressions = Parser.new(source).parse.as(Expressions).expressions + node = expressions[1].as(ProcPointer).obj.not_nil! + node_source(source, node).should eq("foo") + end + + it "sets correct location of var in macro for loop" do + source = "{% for foo, bar in baz %} {% end %}" + node = Parser.new(source).parse.as(MacroFor) + node_source(source, node.vars[0]).should eq("foo") + node_source(source, node.vars[1]).should eq("bar") + end + + it "sets correct location of receiver var in method def" do + source = "def foo.bar; end" + node = Parser.new(source).parse.as(Def).receiver.not_nil! + node_source(source, node).should eq("foo") + end + + it "sets correct location of vars in C struct" do + source = "lib Foo; struct Bar; fizz, buzz : Int32; end; end" + expressions = Parser.new(source).parse.as(LibDef).body.as(CStructOrUnionDef).body.as(Expressions).expressions + node_source(source, expressions[0].as(TypeDeclaration).var).should eq("fizz") + node_source(source, expressions[1].as(TypeDeclaration).var).should eq("buzz") + end + it "doesn't override yield with macro yield" do parser = Parser.new("def foo; yield 1; {% begin %} yield 1 {% end %}; end") a_def = parser.parse.as(Def) diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index cf8339ceec85..e8fe26bc1217 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -693,7 +693,7 @@ module Crystal loc = @name_location return unless loc - Location.new(loc.filename, loc.line_number, loc.column_number + name_size) + Location.new(loc.filename, loc.line_number, loc.column_number + name_size - 1) end def_equals_and_hash obj, name, args, block, block_arg, named_args, global? diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 333fdadaa452..615d0583a1a7 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -2314,8 +2314,6 @@ module Crystal end def next_string_array_token - reset_token - while true if current_char == '\n' next_char @@ -2327,6 +2325,8 @@ module Crystal end end + reset_token + if current_char == @token.delimiter_state.end @token.raw = current_char.to_s if @wants_raw next_char diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index df754e7dd7e8..5eaf4ff4ec9b 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -231,7 +231,7 @@ module Crystal end multi = MultiAssign.new(targets, values).at(location) - parse_expression_suffix multi, @token.location + parse_expression_suffix multi, location end def multi_assign_target?(exp) @@ -309,22 +309,29 @@ module Crystal when Keyword::UNTIL raise "trailing `until` is not supported", @token when Keyword::RESCUE + rescue_location = @token.location next_token_skip_space rescue_body = parse_op_assign - rescues = [Rescue.new(rescue_body)] of Rescue + rescues = [Rescue.new(rescue_body).at(rescue_location).at_end(rescue_body)] of Rescue if atomic.is_a?(Assign) - atomic.value = ExceptionHandler.new(atomic.value, rescues).at(location).tap { |e| e.suffix = true } + atomic.value = ex = ExceptionHandler.new(atomic.value, rescues) else - atomic = ExceptionHandler.new(atomic, rescues).at(location).tap { |e| e.suffix = true } + atomic = ex = ExceptionHandler.new(atomic, rescues) end + ex.at(location).at_end(rescue_body) + ex.suffix = true when Keyword::ENSURE + ensure_location = @token.location next_token_skip_space ensure_body = parse_op_assign if atomic.is_a?(Assign) - atomic.value = ExceptionHandler.new(atomic.value, ensure: ensure_body).at(location).tap { |e| e.suffix = true } + atomic.value = ex = ExceptionHandler.new(atomic.value, ensure: ensure_body) else - atomic = ExceptionHandler.new(atomic, ensure: ensure_body).at(location).tap { |e| e.suffix = true } + atomic = ex = ExceptionHandler.new(atomic, ensure: ensure_body) end + ex.at(location).at_end(ensure_body) + ex.ensure_location = ensure_location + ex.suffix = true else break end @@ -377,7 +384,9 @@ module Crystal atomic.name = "[]=" atomic.name_size = 0 - atomic.args << parse_op_assign_no_control + arg = parse_op_assign_no_control + atomic.args << arg + atomic.end_location = arg.end_location else break unless can_be_assigned?(atomic) @@ -421,7 +430,7 @@ module Crystal push_var atomic next_token_skip_space type = parse_bare_proc_type - atomic = UninitializedVar.new(atomic, type).at(location) + atomic = UninitializedVar.new(atomic, type).at(location).at_end(type) return atomic else if atomic.is_a?(Var) && !var?(atomic.name) @@ -523,20 +532,22 @@ module Crystal end def new_range(exp, location, exclusive) + end_location = token_end_location check_void_value exp, location next_token_skip_space check_void_expression_keyword - right = if end_token? || - @token.type.op_rparen? || - @token.type.op_comma? || - @token.type.op_semicolon? || - @token.type.op_eq_gt? || - @token.type.newline? - Nop.new - else - parse_or - end - RangeLiteral.new(exp, right, exclusive).at(location).at_end(right) + if end_token? || + @token.type.op_rparen? || + @token.type.op_comma? || + @token.type.op_semicolon? || + @token.type.op_eq_gt? || + @token.type.newline? + right = Nop.new + else + right = parse_or + end_location = right.end_location + end + RangeLiteral.new(exp, right, exclusive).at(location).at_end(end_location) end macro parse_operator(name, next_operator, node, *operators, right_associative = false) @@ -740,16 +751,19 @@ module Crystal next_token_skip_space arg = parse_single_arg check :OP_RPAREN + end_location = token_end_location next_token else arg = parse_op_assign_no_control + end_location = arg.end_location end else skip_space_or_newline arg = parse_single_arg + end_location = arg.end_location end - atomic = Call.new(atomic, "#{name}=", arg).at(location) + atomic = Call.new(atomic, "#{name}=", arg).at(location).at_end(end_location) atomic.name_location = name_location next when .assignment_operator? @@ -783,7 +797,7 @@ module Crystal atomic = Call.new atomic, name, (args || [] of ASTNode), block, block_arg, named_args atomic.name_location = name_location - atomic.end_location = call_args.try(&.end_location) || block.try(&.end_location) || end_location + atomic.end_location = block.try(&.end_location) || call_args.try(&.end_location) || end_location atomic.at(location) atomic end @@ -830,6 +844,7 @@ module Crystal if @token.type.op_question? method_name = "[]?" + end_location = token_end_location next_token_skip_space else method_name = "[]" @@ -860,9 +875,10 @@ module Crystal def parse_single_arg if @token.type.op_star? + location = @token.location next_token_skip_space arg = parse_op_assign_no_control - Splat.new(arg) + Splat.new(arg).at(location).at_end(arg) else parse_op_assign_no_control end @@ -916,16 +932,18 @@ module Crystal name = parse_responds_to_name next_token_skip_space_or_newline check :OP_RPAREN + end_location = token_end_location next_token_skip_space elsif @token.type.space? next_token name = parse_responds_to_name + end_location = token_end_location next_token_skip_space else unexpected_token "expected space or '('" end - RespondsTo.new(atomic, name) + RespondsTo.new(atomic, name).at_end(end_location) end def parse_responds_to_name @@ -937,27 +955,31 @@ module Crystal end def parse_nil?(atomic) + end_location = token_end_location next_token if @token.type.op_lparen? next_token_skip_space_or_newline check :OP_RPAREN + end_location = token_end_location next_token_skip_space end - IsA.new(atomic, Path.global("Nil"), nil_check: true) + IsA.new(atomic, Path.global("Nil"), nil_check: true).at_end(end_location) end def parse_negation_suffix(atomic) + end_location = token_end_location next_token if @token.type.op_lparen? next_token_skip_space_or_newline check :OP_RPAREN + end_location = token_end_location next_token_skip_space end - Not.new(atomic) + Not.new(atomic).at_end(end_location) end def parse_atomic @@ -1208,7 +1230,7 @@ module Crystal def check_type_declaration if next_comes_colon_space? name = @token.value.to_s - var = Var.new(name).at(@token.location) + var = Var.new(name).at(@token.location).at_end(token_end_location) next_token_skip_space check :OP_COLON type_declaration = parse_type_declaration(var) @@ -1226,7 +1248,7 @@ module Crystal next_token_skip_space_or_newline value = parse_op_assign_no_control end - TypeDeclaration.new(var, var_type, value).at(var.location) + TypeDeclaration.new(var, var_type, value).at(var).at_end(value || var_type) end def next_comes_colon_space? @@ -1283,7 +1305,7 @@ module Crystal case tuple_or_hash when TupleLiteral - ary = ArrayLiteral.new(tuple_or_hash.elements, name: type).at(tuple_or_hash.location) + ary = ArrayLiteral.new(tuple_or_hash.elements, name: type).at(tuple_or_hash) return ary when HashLiteral tuple_or_hash.name = type @@ -1358,10 +1380,10 @@ module Crystal next_token_skip_statement_end exps = parse_expressions node, end_location = parse_exception_handler exps, begin_location: begin_location - node.end_location = end_location if !node.is_a?(ExceptionHandler) && (!node.is_a?(Expressions) || !node.keyword.none?) - node = Expressions.new([node]).at(begin_location).at_end(end_location) + node = Expressions.new([node]) end + node.at(begin_location).at_end(end_location) node.keyword = :begin if node.is_a?(Expressions) node end @@ -1421,13 +1443,13 @@ module Crystal next_token_skip_space if rescues || a_ensure - ex = ExceptionHandler.new(exp, rescues, a_else, a_ensure).at(begin_location).at_end(end_location) + ex = ExceptionHandler.new(exp, rescues, a_else, a_ensure) + ex.at(begin_location).at_end(end_location) ex.implicit = true if implicit ex.else_location = else_location ex.ensure_location = ensure_location {ex, end_location} else - exp {exp, end_location} end end @@ -1561,6 +1583,7 @@ module Crystal if check_paren skip_space_or_newline check :OP_RPAREN + end_location = token_end_location next_token_skip_space else skip_space @@ -1964,38 +1987,45 @@ module Crystal case @token.type when .ident? name = @token.value.to_s + var_location = @token.location + var_end_location = token_end_location global_call = global - if consume_def_equals_sign_skip_space + equals_sign, end_location = consume_def_equals_sign_skip_space + if equals_sign name = "#{name}=" elsif @token.type.op_period? raise "ProcPointer of local variable cannot be global", location if global if name != "self" && !var_in_scope?(name) raise "undefined variable '#{name}'", location end - obj = Var.new(name) + obj = Var.new(name).at(var_location).at_end(var_end_location) name = consume_def_or_macro_name - name = "#{name}=" if consume_def_equals_sign_skip_space + equals_sign, end_location = consume_def_equals_sign_skip_space + name = "#{name}=" if equals_sign end when .const? obj = parse_generic global: global, location: location, expression: false check :OP_PERIOD name = consume_def_or_macro_name - name = "#{name}=" if consume_def_equals_sign_skip_space + equals_sign, end_location = consume_def_equals_sign_skip_space + name = "#{name}=" if equals_sign when .instance_var? raise "ProcPointer of instance variable cannot be global", location if global obj = InstanceVar.new(@token.value.to_s) next_token_skip_space check :OP_PERIOD name = consume_def_or_macro_name - name = "#{name}=" if consume_def_equals_sign_skip_space + equals_sign, end_location = consume_def_equals_sign_skip_space + name = "#{name}=" if equals_sign when .class_var? raise "ProcPointer of class variable cannot be global", location if global obj = ClassVar.new(@token.value.to_s) next_token_skip_space check :OP_PERIOD name = consume_def_or_macro_name - name = "#{name}=" if consume_def_equals_sign_skip_space + equals_sign, end_location = consume_def_equals_sign_skip_space + name = "#{name}=" if equals_sign else unexpected_token end @@ -2008,12 +2038,13 @@ module Crystal next_token_skip_space types = parse_union_types(:OP_RPAREN) check :OP_RPAREN + end_location = token_end_location next_token_skip_space else types = [] of ASTNode end - ProcPointer.new(obj, name, types, !!global_call) + ProcPointer.new(obj, name, types, !!global_call).at_end(end_location) end record Piece, @@ -2324,6 +2355,10 @@ module Crystal end def parse_string_without_interpolation(context, want_skip_space = true) + parse_string_literal_without_interpolation(context, want_skip_space).value + end + + def parse_string_literal_without_interpolation(context, want_skip_space = true) location = @token.location unless string_literal_start? @@ -2332,7 +2367,7 @@ module Crystal string = parse_delimiter(want_skip_space) if string.is_a?(StringLiteral) - string.value + string else raise "interpolation not allowed in #{context}", location end @@ -2348,13 +2383,15 @@ module Crystal def parse_string_or_symbol_array(klass, elements_type) strings = [] of ASTNode + end_location = nil while true next_string_array_token case @token.type when .string? - strings << klass.new(@token.value.to_s) + strings << klass.new(@token.value.to_s).at(@token.location).at_end(token_end_location) when .string_array_end? + end_location = token_end_location next_token break else @@ -2362,7 +2399,7 @@ module Crystal end end - ArrayLiteral.new strings, Path.global(elements_type) + ArrayLiteral.new(strings, Path.global(elements_type)).at_end(end_location) end def parse_empty_array_literal @@ -2401,7 +2438,6 @@ module Crystal end exps << exp - end_location = token_end_location skip_space if @token.type.op_comma? @@ -2414,6 +2450,7 @@ module Crystal end end @wants_regex = false + end_location = token_end_location next_token_skip_space end @@ -2687,11 +2724,11 @@ module Crystal raise "can't require inside type declarations", @token if @type_nest > 0 next_token_skip_space - string = parse_string_without_interpolation("require") + string_literal = parse_string_literal_without_interpolation("require") skip_space - Require.new string + Require.new(string_literal.value).at_end(string_literal) end def parse_case @@ -3072,7 +3109,8 @@ module Crystal raise "macro can't have a receiver" when .ident? check_valid_def_name - name = "#{name}=" if consume_def_equals_sign_skip_space + equals_sign, _ = consume_def_equals_sign_skip_space + name = "#{name}=" if equals_sign else check_valid_def_op_name next_token_skip_space @@ -3324,7 +3362,7 @@ module Crystal else unexpected_token "expecting ident or underscore" end - vars << Var.new(var).at(@token.location) + vars << Var.new(var).at(@token.location).at_end(token_end_location) next_token_skip_space if @token.type.op_comma? @@ -3412,6 +3450,8 @@ module Crystal else node = parse_if_after_condition cond, location, true end + skip_space_or_newline + check :OP_PERCENT_RCURLY return MacroExpression.new(node, output: false).at_end(token_end_location) end @@ -3511,7 +3551,8 @@ module Crystal elsif @token.type.ident? check_valid_def_name name = @token.value.to_s - name = "#{name}=" if consume_def_equals_sign_skip_space + equals_sign, _ = consume_def_equals_sign_skip_space + name = "#{name}=" if equals_sign else check_valid_def_op_name name = @token.type.to_s @@ -3525,7 +3566,7 @@ module Crystal if @token.type.op_period? unless receiver if name - receiver = Var.new(name).at(receiver_location) + receiver = Var.new(name).at(receiver_location).at_end(end_location) else raise "shouldn't reach this line" end @@ -3538,7 +3579,8 @@ module Crystal name = @token.value.to_s name_location = @token.location - name = "#{name}=" if consume_def_equals_sign_skip_space + equals_sign, _ = consume_def_equals_sign_skip_space + name = "#{name}=" if equals_sign else check DefOrMacroCheck2 check_valid_def_op_name @@ -3602,6 +3644,8 @@ module Crystal index += 1 end + end_location = token_end_location + if name.ends_with?('=') if name != "[]=" && (params.size > 1 || found_splat || found_double_splat) raise "setter method '#{name}' cannot have more than one parameter" @@ -4078,14 +4122,16 @@ module Crystal a_then = parse_expressions skip_statement_end + else_location = nil a_else = nil if @token.type.ident? - else_location = @token.location case @token.value when Keyword::ELSE + else_location = @token.location next_token_skip_statement_end a_else = parse_expressions when Keyword::ELSIF + else_location = @token.location a_else = parse_if check_end: false end end @@ -4141,8 +4187,7 @@ module Crystal node end - def parse_var_or_call(global = false, force_call = false) - location = @token.location + def parse_var_or_call(global = false, force_call = false, location = @token.location) end_location = token_end_location doc = @token.doc @@ -4277,7 +4322,8 @@ module Crystal end else if @no_type_declaration == 0 && @token.type.op_colon? - declare_var = parse_type_declaration(Var.new(name).at(location)) + declare_var = parse_type_declaration(Var.new(name).at(location).at_end(end_location)) + end_location = declare_var.end_location # Don't declare a local variable if it happens directly as an argument # of a call, like `property foo : Int32` (we don't want `foo` to be a @@ -4750,6 +4796,7 @@ module Crystal parse_out else + location = @token.location splat = nil case @token.type when .op_star? @@ -4778,9 +4825,9 @@ module Crystal case splat when :single - arg = Splat.new(arg).at(arg.location) + arg = Splat.new(arg).at(location).at_end(arg) when :double - arg = DoubleSplat.new(arg).at(arg.location) + arg = DoubleSplat.new(arg).at(location).at_end(arg) else # no splat end @@ -4792,31 +4839,24 @@ module Crystal end def parse_out - next_token_skip_space_or_newline location = @token.location + next_token_skip_space_or_newline name = @token.value.to_s case @token.type when .ident? - var = Var.new(name).at(location) - var_out = Out.new(var).at(location) + var = Var.new(name) push_var var - - next_token - var_out when .instance_var? - ivar = InstanceVar.new(name).at(location) - ivar_out = Out.new(ivar).at(location) - next_token - ivar_out + var = InstanceVar.new(name) when .underscore? - underscore = Underscore.new.at(location) - var_out = Out.new(underscore).at(location) - next_token - var_out + var = Underscore.new else raise "expecting variable or instance variable after out" end + var.at(@token.location).at_end(token_end_location) + next_token + Out.new(var).at(location).at_end(var) end def parse_generic_or_global_call @@ -4825,7 +4865,7 @@ module Crystal case @token.type when .ident? - set_visibility parse_var_or_call global: true + set_visibility parse_var_or_call global: true, location: location when .const? ident = parse_generic global: true, location: location, expression: true parse_custom_literal ident @@ -4985,8 +5025,9 @@ module Crystal # is appeared in macro expression. (e.g. `{% if T <= Int32? %} ... {% end %}`) # Note that the parser cannot consume any spaces because it conflicts ternary operator. while expression && @token.type.op_question? + end_location = token_end_location next_token - type = make_nilable_expression(type) + type = make_nilable_expression(type).at_end(end_location) end skip_space @@ -5134,28 +5175,31 @@ module Crystal def parse_type_suffix(type) loop do + end_location = token_end_location case @token.type when .op_period? next_token_skip_space_or_newline check_ident :class + end_location = token_end_location next_token_skip_space - type = Metaclass.new(type).at(type) + type = Metaclass.new(type).at(type).at_end(end_location) when .op_question? next_token_skip_space - type = make_nilable_type(type) + type = make_nilable_type(type).at_end(end_location) when .op_star? next_token_skip_space - type = make_pointer_type(type) + type = make_pointer_type(type).at_end(end_location) when .op_star_star? next_token_skip_space - type = make_pointer_type(make_pointer_type(type)) + type = make_pointer_type(make_pointer_type(type)).at_end(end_location) when .op_lsquare? next_token_skip_space_or_newline size = parse_type_arg skip_space_or_newline check :OP_RSQUARE + end_location = token_end_location next_token_skip_space - type = make_static_array_type(type, size) + type = make_static_array_type(type, size).at_end(end_location) else return type end @@ -5166,14 +5210,16 @@ module Crystal has_output_type = type_start?(consume_newlines: false) check :OP_MINUS_GT + end_location = token_end_location next_token_skip_space if has_output_type skip_space_or_newline output_type = parse_union_type + end_location = output_type.end_location end - ProcNotation.new(input_types, output_type).at(location) + ProcNotation.new(input_types, output_type).at(location).at_end(end_location) end def make_nilable_type(type) @@ -5500,7 +5546,7 @@ module Crystal if args.size == 1 && !args.first.is_a?(Splat) node = klass.new(args.first) else - tuple = TupleLiteral.new(args).at(args.last) + tuple = TupleLiteral.new(args).at(args.first).at_end(args.last) node = klass.new(tuple) end else @@ -5895,13 +5941,13 @@ module Crystal end def parse_c_struct_or_union_fields(exps) - vars = [Var.new(@token.value.to_s).at(@token.location)] + vars = [Var.new(@token.value.to_s).at(@token.location).at_end(token_end_location)] next_token_skip_space_or_newline while @token.type.op_comma? next_token_skip_space_or_newline - vars << Var.new(check_ident).at(@token.location) + vars << Var.new(check_ident).at(@token.location).at_end(token_end_location) next_token_skip_space_or_newline end @@ -6096,13 +6142,15 @@ module Crystal end def consume_def_equals_sign_skip_space + end_location = token_end_location next_token if @token.type.op_eq? + end_location = token_end_location next_token_skip_space - true + {true, end_location} else skip_space - false + {false, end_location} end end From 181ec635babe776318751a55535c5614fcb028e6 Mon Sep 17 00:00:00 2001 From: "Brian J. Cardiff" Date: Tue, 20 Dec 2022 11:35:01 -0300 Subject: [PATCH 0196/1551] Match Nix loader errors in compiler spec (#12852) --- spec/compiler/loader/unix_spec.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/compiler/loader/unix_spec.cr b/spec/compiler/loader/unix_spec.cr index cfbe53c7c172..8acf48559eea 100644 --- a/spec/compiler/loader/unix_spec.cr +++ b/spec/compiler/loader/unix_spec.cr @@ -28,10 +28,10 @@ describe Crystal::Loader do end it "parses file paths" do - expect_raises(Crystal::Loader::LoadError, /#{Dir.current}\/foobar\.o.+(No such file or directory|image not found)|Cannot open "#{Dir.current}\/foobar\.o"/) do + expect_raises(Crystal::Loader::LoadError, /#{Dir.current}\/foobar\.o.+(No such file or directory|image not found|no such file)|Cannot open "#{Dir.current}\/foobar\.o"/) do Crystal::Loader.parse(["foobar.o"], search_paths: [] of String) end - expect_raises(Crystal::Loader::LoadError, /(#{Dir.current}\/foo\/bar\.o).+(No such file or directory|image not found)|Cannot open "#{Dir.current}\/foo\/bar\.o"/) do + expect_raises(Crystal::Loader::LoadError, /(#{Dir.current}\/foo\/bar\.o).+(No such file or directory|image not found|no such file)|Cannot open "#{Dir.current}\/foo\/bar\.o"/) do Crystal::Loader.parse(["-l", "foo/bar.o"], search_paths: [] of String) end end From 75a3a47013f47f5773e00a2e7768bbce96547a95 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 21 Dec 2022 07:24:29 +0800 Subject: [PATCH 0197/1551] Order `_` after any other `Path` when comparing overloads (#12855) --- spec/compiler/semantic/restrictions_spec.cr | 57 +++++++++++++++++++ src/compiler/crystal/semantic/restrictions.cr | 12 ++++ 2 files changed, 69 insertions(+) diff --git a/spec/compiler/semantic/restrictions_spec.cr b/spec/compiler/semantic/restrictions_spec.cr index 7201bb816f69..14c81e618ce2 100644 --- a/spec/compiler/semantic/restrictions_spec.cr +++ b/spec/compiler/semantic/restrictions_spec.cr @@ -748,6 +748,63 @@ describe "Restrictions" do CRYSTAL end end + + describe "Underscore vs Path" do + it "inserts Path before underscore (#12854)" do + assert_type(<<-CRYSTAL) { bool } + class Foo + end + + def foo(x : _) + 'a' + end + + def foo(x : Foo) + true + end + + foo(Foo.new) + CRYSTAL + end + + it "keeps underscore after Path (#12854)" do + assert_type(<<-CRYSTAL) { bool } + class Foo + end + + def foo(x : Foo) + true + end + + def foo(x : _) + 'a' + end + + foo(Foo.new) + CRYSTAL + end + + it "works with splats and modules, under -Dpreview_overload_order (#12854)" do + assert_type(<<-CRYSTAL, flags: "preview_overload_order") { bool } + module Foo + end + + class Bar + include Foo + end + + def foo(*x : _) + 'a' + end + + def foo(x : Foo) + true + end + + foo(Bar.new) + CRYSTAL + end + end end it "self always matches instance type in restriction" do diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index c391dfdb4ee6..9b7ac5c19613 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -629,6 +629,10 @@ module Crystal end end + def restriction_of?(other : Underscore, owner, self_free_vars = nil, other_free_vars = nil) + true + end + def restriction_of?(other, owner, self_free_vars = nil, other_free_vars = nil) false end @@ -659,6 +663,10 @@ module Crystal end class Union + def restriction_of?(other : Underscore, owner, self_free_vars = nil, other_free_vars = nil) + true + end + def restriction_of?(other, owner, self_free_vars = nil, other_free_vars = nil) # For a union to be considered before another restriction, # all types in the union must be considered before @@ -1473,6 +1481,10 @@ module Crystal end class AliasType + def restriction_of?(other : Underscore, owner, self_free_vars = nil, other_free_vars = nil) + true + end + def restriction_of?(other, owner, self_free_vars = nil, other_free_vars = nil) return true if self == other From f88c3ce3a89d1986718c8056b34b449d7d6ac96d Mon Sep 17 00:00:00 2001 From: Gabriel Silveira <26633512+gabriel-ss@users.noreply.github.com> Date: Wed, 21 Dec 2022 14:48:24 -0300 Subject: [PATCH 0198/1551] Add methods to manipulate semantic versions (#12834) --- spec/std/semantic_version_spec.cr | 24 ++++++++++++++ src/semantic_version.cr | 52 +++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/spec/std/semantic_version_spec.cr b/spec/std/semantic_version_spec.cr index 9902e1fe46ee..41403505b776 100644 --- a/spec/std/semantic_version_spec.cr +++ b/spec/std/semantic_version_spec.cr @@ -88,6 +88,30 @@ describe SemanticVersion do end end + it "copies with specified modifications" do + base_version = SemanticVersion.new(1, 2, 3, "rc", "0000") + base_version.copy_with(major: 0).should eq SemanticVersion.new(0, 2, 3, "rc", "0000") + base_version.copy_with(minor: 0).should eq SemanticVersion.new(1, 0, 3, "rc", "0000") + base_version.copy_with(patch: 0).should eq SemanticVersion.new(1, 2, 0, "rc", "0000") + base_version.copy_with(prerelease: "alpha").should eq SemanticVersion.new(1, 2, 3, "alpha", "0000") + base_version.copy_with(build: "0001").should eq SemanticVersion.new(1, 2, 3, "rc", "0001") + base_version.copy_with(prerelease: nil, build: nil).should eq SemanticVersion.new(1, 2, 3) + end + + it "bumps to the correct version" do + SemanticVersion.new(1, 1, 1).bump_minor.should eq SemanticVersion.new(1, 2, 0) + SemanticVersion.new(1, 2, 0).bump_patch.should eq SemanticVersion.new(1, 2, 1) + SemanticVersion.new(1, 2, 1).bump_major.should eq SemanticVersion.new(2, 0, 0) + SemanticVersion.new(2, 0, 0).bump_patch.should eq SemanticVersion.new(2, 0, 1) + SemanticVersion.new(2, 0, 1).bump_minor.should eq SemanticVersion.new(2, 1, 0) + SemanticVersion.new(2, 1, 0).bump_major.should eq SemanticVersion.new(3, 0, 0) + + version_with_prerelease = SemanticVersion.new(1, 2, 3, "rc", "0001") + version_with_prerelease.bump_major.should eq SemanticVersion.new(2, 0, 0) + version_with_prerelease.bump_minor.should eq SemanticVersion.new(1, 3, 0) + version_with_prerelease.bump_patch.should eq SemanticVersion.new(1, 2, 3) + end + describe SemanticVersion::Prerelease do it "compares <" do sprereleases = %w( diff --git a/src/semantic_version.cr b/src/semantic_version.cr index 1e15321a3089..8c35e9998eae 100644 --- a/src/semantic_version.cr +++ b/src/semantic_version.cr @@ -82,6 +82,58 @@ struct SemanticVersion end end + # Returns a new `SemanticVersion` created with the specified parts. The + # default for each part is its current value. + # + # ``` + # require "semantic_version" + # + # current_version = SemanticVersion.new 1, 1, 1, "rc" + # current_version.copy_with(patch: 2) # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=["rc"])) + # current_version.copy_with(prerelease: nil) # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) + def copy_with(major : Int32 = @major, minor : Int32 = @minor, patch : Int32 = @patch, prerelease : String | Prerelease | Nil = @prerelease, build : String? = @build) + SemanticVersion.new major, minor, patch, prerelease, build + end + + # Returns a copy of the current version with a major bump. + # + # ``` + # require "semantic_version" + # + # current_version = SemanticVersion.new 1, 1, 1, "rc" + # current_version.bump_major # => SemanticVersion(@build=nil, @major=2, @minor=0, @patch=0, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) + def bump_major + copy_with(major: major + 1, minor: 0, patch: 0, prerelease: nil, build: nil) + end + + # Returns a copy of the current version with a minor bump. + # + # ``` + # require "semantic_version" + # + # current_version = SemanticVersion.new 1, 1, 1, "rc" + # current_version.bump_minor # => SemanticVersion(@build=nil, @major=1, @minor=2, @patch=0, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) + def bump_minor + copy_with(minor: minor + 1, patch: 0, prerelease: nil, build: nil) + end + + # Returns a copy of the current version with a patch bump. Bumping a patch of + # a prerelease just erase the prerelease data. + # + # ``` + # require "semantic_version" + # + # current_version = SemanticVersion.new 1, 1, 1, "rc" + # next_patch = current_version.bump_patch # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=1, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) + # next_patch.bump_patch # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) + def bump_patch + if prerelease.identifiers.empty? + copy_with(patch: patch + 1, prerelease: nil, build: nil) + else + copy_with(prerelease: nil, build: nil) + end + end + # The comparison operator. # # Returns `-1`, `0` or `1` depending on whether `self`'s version is lower than *other*'s, From 8674a93a3043dd349c7082c7e7c9fd29327200b1 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Thu, 22 Dec 2022 05:15:17 +0800 Subject: [PATCH 0199/1551] Add types to methods with defaults (#12837) --- src/array.cr | 2 +- src/char.cr | 8 ++++---- src/enumerable.cr | 4 ++-- src/indexable.cr | 4 ++-- src/indexable/mutable.cr | 2 +- src/range.cr | 2 +- src/slice.cr | 2 +- src/string.cr | 2 +- src/uuid.cr | 10 +++++----- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/array.cr b/src/array.cr index a93f974ab650..31ab55075464 100644 --- a/src/array.cr +++ b/src/array.cr @@ -1542,7 +1542,7 @@ class Array(T) # Returns an array with all the elements in the collection randomized # using the given *random* number generator. - def shuffle(random = Random::DEFAULT) : Array(T) + def shuffle(random : Random = Random::DEFAULT) : Array(T) dup.shuffle!(random) end diff --git a/src/char.cr b/src/char.cr index d4f9834470a1..cb8afeb83230 100644 --- a/src/char.cr +++ b/src/char.cr @@ -400,7 +400,7 @@ struct Char # 'x'.downcase # => 'x' # '.'.downcase # => '.' # ``` - def downcase(options = Unicode::CaseOptions::None) : Char + def downcase(options : Unicode::CaseOptions = :none) : Char Unicode.downcase(self, options) end @@ -409,7 +409,7 @@ struct Char # This method takes into account the possibility that an downcase # version of a char might result in multiple chars, like for # 'İ', which results in 'i' and a dot mark. - def downcase(options = Unicode::CaseOptions::None) + def downcase(options : Unicode::CaseOptions = :none) Unicode.downcase(self, options) { |char| yield char } end @@ -427,7 +427,7 @@ struct Char # 'X'.upcase # => 'X' # '.'.upcase # => '.' # ``` - def upcase(options = Unicode::CaseOptions::None) : Char + def upcase(options : Unicode::CaseOptions = :none) : Char Unicode.upcase(self, options) end @@ -441,7 +441,7 @@ struct Char # 'z'.upcase { |v| puts v } # prints 'Z' # 'ffl'.upcase { |v| puts v } # prints 'F', 'F', 'L' # ``` - def upcase(options = Unicode::CaseOptions::None) + def upcase(options : Unicode::CaseOptions = :none) Unicode.upcase(self, options) { |char| yield char } end diff --git a/src/enumerable.cr b/src/enumerable.cr index 9bbb49c997e7..a74e39baa616 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -1389,7 +1389,7 @@ module Enumerable(T) # {1, 2, 3, 4, 5}.sample(2) # => [3, 4] # {1, 2, 3, 4, 5}.sample(2, Random.new(1)) # => [1, 5] # ``` - def sample(n : Int, random = Random::DEFAULT) : Array(T) + def sample(n : Int, random : Random = Random::DEFAULT) : Array(T) raise ArgumentError.new("Can't sample negative number of elements") if n < 0 # Unweighted reservoir sampling: @@ -1425,7 +1425,7 @@ module Enumerable(T) # a.sample # => 1 # a.sample(Random.new(1)) # => 3 # ``` - def sample(random = Random::DEFAULT) : T + def sample(random : Random = Random::DEFAULT) : T value = uninitialized T found = false diff --git a/src/indexable.cr b/src/indexable.cr index 8ce454a341ed..f47c5ab441c2 100644 --- a/src/indexable.cr +++ b/src/indexable.cr @@ -894,7 +894,7 @@ module Indexable(T) # a.sample # => 1 # a.sample(Random.new(1)) # => 2 # ``` - def sample(random = Random::DEFAULT) + def sample(random : Random = Random::DEFAULT) raise IndexError.new("Can't sample empty collection") if size == 0 unsafe_fetch(random.rand(size)) end @@ -904,7 +904,7 @@ module Indexable(T) # If `self` is not empty and `n` is equal to 1, calls `sample(random)` exactly # once. Thus, *random* will be left in a different state compared to the # implementation in `Enumerable`. - def sample(n : Int, random = Random::DEFAULT) : Array(T) + def sample(n : Int, random : Random = Random::DEFAULT) : Array(T) return super unless n == 1 if empty? diff --git a/src/indexable/mutable.cr b/src/indexable/mutable.cr index e78bd7ffe204..86331554f924 100644 --- a/src/indexable/mutable.cr +++ b/src/indexable/mutable.cr @@ -259,7 +259,7 @@ module Indexable::Mutable(T) # a.shuffle!(Random.new(42)) # => [3, 2, 4, 5, 1] # a # => [3, 2, 4, 5, 1] # ``` - def shuffle!(random = Random::DEFAULT) : self + def shuffle!(random : Random = Random::DEFAULT) : self (size - 1).downto(1) do |i| j = random.rand(i + 1) swap(i, j) diff --git a/src/range.cr b/src/range.cr index 9d409b260156..df6c51284c04 100644 --- a/src/range.cr +++ b/src/range.cr @@ -362,7 +362,7 @@ struct Range(B, E) # the method simply calls `random.rand(self)`. # # Raises `ArgumentError` if `self` is an open range. - def sample(random = Random::DEFAULT) + def sample(random : Random = Random::DEFAULT) {% if B == Nil || E == Nil %} {% raise "Can't sample an open range" %} {% end %} diff --git a/src/slice.cr b/src/slice.cr index 1efc40951ff1..c944de299560 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -301,7 +301,7 @@ struct Slice(T) # :inherit: # # Raises if this slice is read-only. - def shuffle!(random = Random::DEFAULT) : self + def shuffle!(random : Random = Random::DEFAULT) : self check_writable super end diff --git a/src/string.cr b/src/string.cr index 06df4ce97834..6df2aedb5331 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3035,7 +3035,7 @@ class String # ``` # # Case-sensitive only comparison is provided by the comparison operator `#<=>`. - def compare(other : String, case_insensitive = false, options = Unicode::CaseOptions::None) : Int32 + def compare(other : String, case_insensitive = false, options : Unicode::CaseOptions = :none) : Int32 return self <=> other unless case_insensitive if single_byte_optimizable? && other.single_byte_optimizable? diff --git a/src/uuid.cr b/src/uuid.cr index 99d079e18a1c..2146c26b43bb 100644 --- a/src/uuid.cr +++ b/src/uuid.cr @@ -60,7 +60,7 @@ struct UUID # Creates UUID from 16-bytes slice. Raises if *slice* isn't 16 bytes long. See # `#initialize` for *variant* and *version*. - def self.new(slice : Slice(UInt8), variant = nil, version = nil) + def self.new(slice : Slice(UInt8), variant : Variant? = nil, version : Version? = nil) raise ArgumentError.new "Invalid bytes length #{slice.size}, expected 16" unless slice.size == 16 bytes = uninitialized UInt8[16] @@ -71,14 +71,14 @@ struct UUID # Creates another `UUID` which is a copy of *uuid*, but allows overriding # *variant* or *version*. - def self.new(uuid : UUID, variant = nil, version = nil) + def self.new(uuid : UUID, variant : Variant? = nil, version : Version? = nil) new(uuid.bytes, variant, version) end # Creates new UUID by decoding `value` string from hyphenated (ie `ba714f86-cac6-42c7-8956-bcf5105e1b81`), # hexstring (ie `89370a4ab66440c8add39e06f2bb6af6`) or URN (ie `urn:uuid:3f9eaf9e-cdb0-45cc-8ecb-0e5b2bfb0c20`) # format, raising an `ArgumentError` if the string does not match any of these formats. - def self.new(value : String, variant = nil, version = nil) + def self.new(value : String, variant : Variant? = nil, version : Version? = nil) bytes = uninitialized UInt8[16] case value.size @@ -110,7 +110,7 @@ struct UUID # Creates new UUID by decoding `value` string from hyphenated (ie `ba714f86-cac6-42c7-8956-bcf5105e1b81`), # hexstring (ie `89370a4ab66440c8add39e06f2bb6af6`) or URN (ie `urn:uuid:3f9eaf9e-cdb0-45cc-8ecb-0e5b2bfb0c20`) # format, returning `nil` if the string does not match any of these formats. - def self.parse?(value : String, variant = nil, version = nil) : UUID? + def self.parse?(value : String, variant : Variant? = nil, version : Version? = nil) : UUID? bytes = uninitialized UInt8[16] case value.size @@ -167,7 +167,7 @@ struct UUID # # It is strongly recommended to use a cryptographically random source for # *random*, such as `Random::Secure`. - def self.random(random = Random::Secure, variant = Variant::RFC4122, version = Version::V4) : self + def self.random(random : Random = Random::Secure, variant : Variant = :rfc4122, version : Version = :v4) : self new_bytes = uninitialized UInt8[16] random.random_bytes(new_bytes.to_slice) From 9e47a2d969c8f87b797057a1089688373b6419da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 22 Dec 2022 11:14:46 +0100 Subject: [PATCH 0200/1551] Use `sleep` in flock wait loop (#12861) --- src/crystal/system/unix/file.cr | 2 +- src/crystal/system/win32/file.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index 36b54ea305a0..a976b4b08012 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -257,7 +257,7 @@ module Crystal::System::File if retry until flock(op) - ::Fiber.yield + sleep 0.1 end else flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked") diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index bda41cde8b69..3fb3998e3c48 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -276,7 +276,7 @@ module Crystal::System::File handle = windows_handle if retry until lock_file(handle, flags) - ::Fiber.yield + sleep 0.1 end else lock_file(handle, flags) || raise IO::Error.from_winerror("Error applying file lock: file is already locked") From bd2802f2a08000d8d2de1352f1d3860ef57ff06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 22 Dec 2022 21:35:41 +0100 Subject: [PATCH 0201/1551] Implement `Regex` engine on PCRE2 (#12856) --- .github/workflows/regex-engine.yml | 31 +++++ spec/std/regex/match_data_spec.cr | 3 + spec/std/regex_spec.cr | 24 +++- src/regex/engine.cr | 13 +- src/regex/lib_pcre2.cr | 200 +++++++++++++++++++++++++++++ src/regex/pcre2.cr | 183 ++++++++++++++++++++++++++ 6 files changed, 446 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/regex-engine.yml create mode 100644 src/regex/lib_pcre2.cr create mode 100644 src/regex/pcre2.cr diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml new file mode 100644 index 000000000000..6bdd09a6b533 --- /dev/null +++ b/.github/workflows/regex-engine.yml @@ -0,0 +1,31 @@ +name: Regex Engine CI + +on: [push, pull_request] + +jobs: + pcre: + runs-on: ubuntu-latest + name: "PCRE" + container: crystallang/crystal:1.6.2-alpine + steps: + - name: Download Crystal source + uses: actions/checkout@v3 + - name: Assert using PCRE + run: bin/crystal eval 'abort unless Regex::Engine == Regex::PCRE' + - name: Run Regex specs + run: bin/crystal spec --order=random spec/std/regex* + pcre2: + runs-on: ubuntu-latest + name: "PCRE2" + container: crystallang/crystal:1.6.2-alpine + steps: + - name: Download Crystal source + uses: actions/checkout@v3 + - name: Install PCRE2 + run: apk add pcre2-dev + - name: Assert using PCRE2 + run: bin/crystal eval -Duse_pcre2 'abort unless Regex::Engine == Regex::PCRE2' + - name: Assert select PCRE + run: bin/crystal eval -Duse_pcre 'abort unless Regex::Engine == Regex::PCRE' + - name: Run Regex specs + run: bin/crystal spec -Duse_pcre2 --order=random spec/std/regex* diff --git a/spec/std/regex/match_data_spec.cr b/spec/std/regex/match_data_spec.cr index 7fc9c3219faa..70b11b1104a8 100644 --- a/spec/std/regex/match_data_spec.cr +++ b/spec/std/regex/match_data_spec.cr @@ -216,6 +216,8 @@ describe "Regex::MatchData" do it "raises exception when named group doesn't exist" do md = matchdata(/foo/, "foo") expect_raises(KeyError, "Capture group 'group' does not exist") { md["group"] } + + expect_raises(KeyError, "Capture group 'groupwithlongname' does not exist") { md["groupwithlongname"] } end it "captures empty group" do @@ -302,6 +304,7 @@ describe "Regex::MatchData" do it "returns nil exception when named group doesn't exist" do md = matchdata(/foo/, "foo") md["group"]?.should be_nil + md["groupwithlongname"]?.should be_nil end it "capture empty group" do diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index c38194bc8785..d3830124aa79 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -200,12 +200,20 @@ describe "Regex" do /foo/.matches?("foo", options: Regex::Options::ANCHORED).should be_true end - it "matches a large single line string" do - LibPCRE.config LibPCRE::CONFIG_JIT, out jit_enabled - pending! "PCRE JIT mode not available." unless 1 == jit_enabled + it "doesn't crash with a large single line string" do + {% if Regex::Engine.resolve.name == "Regex::PCRE" %} + LibPCRE.config LibPCRE::CONFIG_JIT, out jit_enabled + pending! "PCRE JIT mode not available." unless 1 == jit_enabled + {% else %} + # This spec requires a fairly large depth limit. Some package builds + # have a more restrictive value which would make this test fail. + pending! "PCRE2 depth limit too low" unless Regex::PCRE2.config(LibPCRE2::CONFIG_DEPTHLIMIT, UInt32) > 8192 + {% end %} str = File.read(datapath("large_single_line_string.txt")) - str.matches?(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/).should be_false + str.matches?(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/) + # We don't care whether this actually matches or not, it's just to make + # sure the engine does not stack overflow with a large string. end end @@ -422,6 +430,12 @@ describe "Regex" do it ".error?" do Regex.error?("(foo|bar)").should be_nil - Regex.error?("(foo|bar").should eq "missing ) at 8" + Regex.error?("(foo|bar").should eq( + if Regex::Engine.to_s == "Regex::PCRE2" + "missing closing parenthesis at 8" + else + "missing ) at 8" + end + ) end end diff --git a/src/regex/engine.cr b/src/regex/engine.cr index ad69e5d034bf..c2a336accd0d 100644 --- a/src/regex/engine.cr +++ b/src/regex/engine.cr @@ -1,4 +1,11 @@ -require "./pcre" +{% if flag?(:use_pcre2) %} + require "./pcre2" -# :nodoc: -alias Regex::Engine = PCRE + # :nodoc: + alias Regex::Engine = PCRE2 +{% else %} + require "./pcre" + + # :nodoc: + alias Regex::Engine = PCRE +{% end %} diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr new file mode 100644 index 000000000000..32e3c9afa767 --- /dev/null +++ b/src/regex/lib_pcre2.cr @@ -0,0 +1,200 @@ +@[Link("pcre2-8")] +lib LibPCRE2 + alias Int = LibC::Int + + UNSET = ~LibC::SizeT.new(0) + + ANCHORED = 0x80000000 + NO_UTF_CHECK = 0x40000000 + ENDANCHORED = 0x20000000 + + ALLOW_EMPTY_CLASS = 0x00000001 + ALT_BSUX = 0x00000002 + AUTO_CALLOUT = 0x00000004 + CASELESS = 0x00000008 + DOLLAR_ENDONLY = 0x00000010 + DOTALL = 0x00000020 + DUPNAMES = 0x00000040 + EXTENDED = 0x00000080 + FIRSTLINE = 0x00000100 + MATCH_UNSET_BACKREF = 0x00000200 + MULTILINE = 0x00000400 + NEVER_UCP = 0x00000800 + NEVER_UTF = 0x00001000 + NO_AUTO_CAPTURE = 0x00002000 + NO_AUTO_POSSESS = 0x00004000 + NO_DOTSTAR_ANCHOR = 0x00008000 + NO_START_OPTIMIZE = 0x00010000 + UCP = 0x00020000 + UNGREEDY = 0x00040000 + UTF = 0x00080000 + NEVER_BACKSLASH_C = 0x00100000 + ALT_CIRCUMFLEX = 0x00200000 + ALT_VERBNAMES = 0x00400000 + USE_OFFSET_LIMIT = 0x00800000 + EXTENDED_MORE = 0x01000000 + LITERAL = 0x02000000 + MATCH_INVALID_UTF = 0x04000000 + + enum Error + # "Expected" matching error codes: no match and partial match. + + NOMATCH = -1 + PARTIAL = -2 + + # Error codes for UTF-8 validity checks + + UTF8_ERR1 = -3 + UTF8_ERR2 = -4 + UTF8_ERR3 = -5 + UTF8_ERR4 = -6 + UTF8_ERR5 = -7 + UTF8_ERR6 = -8 + UTF8_ERR7 = -9 + UTF8_ERR8 = -10 + UTF8_ERR9 = -11 + UTF8_ERR10 = -12 + UTF8_ERR11 = -13 + UTF8_ERR12 = -14 + UTF8_ERR13 = -15 + UTF8_ERR14 = -16 + UTF8_ERR15 = -17 + UTF8_ERR16 = -18 + UTF8_ERR17 = -19 + UTF8_ERR18 = -20 + UTF8_ERR19 = -21 + UTF8_ERR20 = -22 + UTF8_ERR21 = -23 + + # Error codes for UTF-16 validity checks + + UTF16_ERR1 = -24 + UTF16_ERR2 = -25 + UTF16_ERR3 = -26 + + # Error codes for UTF-32 validity checks + + UTF32_ERR1 = -27 + UTF32_ERR2 = -28 + + # Miscellaneous error codes for pcre2[_dfa]_match(), substring extraction + # functions, context functions, and serializing functions. They are in numerical + # order. Originally they were in alphabetical order too, but now that PCRE2 is + # released, the numbers must not be changed. + + BADDATA = -29 + MIXEDTABLES = -30 # Name was changed + BADMAGIC = -31 + BADMODE = -32 + BADOFFSET = -33 + BADOPTION = -34 + BADREPLACEMENT = -35 + BADUTFOFFSET = -36 + CALLOUT = -37 # Never used by PCRE2 itself + DFA_BADRESTART = -38 + DFA_RECURSE = -39 + DFA_UCOND = -40 + DFA_UFUNC = -41 + DFA_UITEM = -42 + DFA_WSSIZE = -43 + INTERNAL = -44 + JIT_BADOPTION = -45 + JIT_STACKLIMIT = -46 + MATCHLIMIT = -47 + NOMEMORY = -48 + NOSUBSTRING = -49 + NOUNIQUESUBSTRING = -50 + NULL = -51 + RECURSELOOP = -52 + DEPTHLIMIT = -53 + RECURSIONLIMIT = -53 # Obsolete synonym + UNAVAILABLE = -54 + UNSET = -55 + BADOFFSETLIMIT = -56 + BADREPESCAPE = -57 + REPMISSINGBRACE = -58 + BADSUBSTITUTION = -59 + BADSUBSPATTERN = -60 + TOOMANYREPLACE = -61 + BADSERIALIZEDDATA = -62 + HEAPLIMIT = -63 + CONVERT_SYNTAX = -64 + INTERNAL_DUPMATCH = -65 + DFA_UINVALID_UTF = -66 + end + + INFO_ALLOPTIONS = 0 + INFO_ARGOPTIONS = 1 + INFO_BACKREFMAX = 2 + INFO_BSR = 3 + INFO_CAPTURECOUNT = 4 + INFO_FIRSTCODEUNIT = 5 + INFO_FIRSTCODETYPE = 6 + INFO_FIRSTBITMAP = 7 + INFO_HASCRORLF = 8 + INFO_JCHANGED = 9 + INFO_JITSIZE = 10 + INFO_LASTCODEUNIT = 11 + INFO_LASTCODETYPE = 12 + INFO_MATCHEMPTY = 13 + INFO_MATCHLIMIT = 14 + INFO_MAXLOOKBEHIND = 15 + INFO_MINLENGTH = 16 + INFO_NAMECOUNT = 17 + INFO_NAMEENTRYSIZE = 18 + INFO_NAMETABLE = 19 + INFO_NEWLINE = 20 + INFO_DEPTHLIMIT = 21 + INFO_RECURSIONLIMIT = 21 # Obsolete synonym + INFO_SIZE = 22 + INFO_HASBACKSLASHC = 23 + INFO_FRAMESIZE = 24 + INFO_HEAPLIMIT = 25 + INFO_EXTRAOPTIONS = 26 + + # Request types for pcre2_config(). + + CONFIG_BSR = 0 + CONFIG_JIT = 1 + CONFIG_JITTARGET = 2 + CONFIG_LINKSIZE = 3 + CONFIG_MATCHLIMIT = 4 + CONFIG_NEWLINE = 5 + CONFIG_PARENSLIMIT = 6 + CONFIG_DEPTHLIMIT = 7 + CONFIG_RECURSIONLIMIT = 7 # Obsolete synonym + CONFIG_STACKRECURSE = 8 # Obsolete + CONFIG_UNICODE = 9 + CONFIG_UNICODE_VERSION = 10 + CONFIG_VERSION = 11 + CONFIG_HEAPLIMIT = 12 + CONFIG_NEVER_BACKSLASH_C = 13 + CONFIG_COMPILED_WIDTHS = 14 + CONFIG_TABLES_LENGTH = 15 + + type Code = Void* + type CompileContext = Void* + type MatchData = Void* + type GeneralContext = Void* + + fun get_error_message = pcre2_get_error_message_8(errorcode : Int, buffer : UInt8*, bufflen : LibC::SizeT) : Int + + fun compile = pcre2_compile_8(pattern : UInt8*, length : LibC::SizeT, options : UInt32, errorcode : LibC::SizeT*, erroroffset : Int*, ccontext : CompileContext*) : Code* + fun code_free = pcre2_code_free_8(code : Code*) : Void + + fun pattern_info = pcre2_pattern_info_8(code : Code*, what : UInt32, where : Void*) : Int + + fun match = pcre2_match_8(code : Code*, subject : UInt8*, length : LibC::SizeT, startoffset : LibC::SizeT, options : UInt32, match_data : MatchData*, mcontext : Void*) : Int + fun match_data_create_from_pattern = pcre2_match_data_create_from_pattern_8(code : Code*, gcontext : GeneralContext) : MatchData* + fun match_data_free = pcre2_match_data_free_8(match_data : MatchData*) : Void + + fun substring_nametable_scan = pcre2_substring_nametable_scan_8(code : Code*, name : UInt8*, first : UInt8*, last : UInt8*) : Int + + fun get_ovector_pointer = pcre2_get_ovector_pointer_8(match_data : MatchData*) : LibC::SizeT* + fun get_ovector_count = pcre2_get_ovector_count_8(match_data : MatchData*) : UInt32 + + # void *private_malloc(Int, void *); + # void private_free(void *, void *); + fun general_context_create = pcre2_general_context_create_8(private_malloc : Void*, private_free : Void*, memory_data : Void*) : GeneralContext + fun config = pcre2_config_8(what : UInt32, where : Void*) : Int +end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr new file mode 100644 index 000000000000..dffd2369ba11 --- /dev/null +++ b/src/regex/pcre2.cr @@ -0,0 +1,183 @@ +require "./lib_pcre2" + +# :nodoc: +module Regex::PCRE2 + @re : LibPCRE2::Code* + + # :nodoc: + def initialize(*, _source @source : String, _options @options) + @re = PCRE2.compile(source, pcre2_options(options) | LibPCRE2::UTF | LibPCRE2::NO_UTF_CHECK | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| + raise ArgumentError.new(error_message) + end + end + + protected def self.compile(source, options) + if res = LibPCRE2.compile(source, source.bytesize, options, out errorcode, out erroroffset, nil) + res + else + message = String.new(256) do |buffer| + bytesize = LibPCRE2.get_error_message(errorcode, buffer, 256) + {bytesize, 0} + end + yield "#{message} at #{erroroffset}" + end + end + + private def pcre2_options(options) + flag = 0 + options.each do |option| + flag |= case option + when .ignore_case? then LibPCRE2::CASELESS + when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE + when .extended? then LibPCRE2::EXTENDED + when .anchored? then LibPCRE2::ANCHORED + when .utf_8? then LibPCRE2::UTF + when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK + when .dupnames? then LibPCRE2::DUPNAMES + when .ucp? then LibPCRE2::UCP + else + raise "unreachable" + end + end + flag + end + + def finalize + {% unless flag?(:interpreted) %} + LibPCRE2.code_free @re + {% end %} + end + + protected def self.error_impl(source) + code = PCRE2.compile(source, LibPCRE2::UTF | LibPCRE2::NO_UTF_CHECK | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| + return error_message + end + + LibPCRE2.code_free code + + nil + end + + private def pattern_info(what) + value = uninitialized UInt32 + pattern_info(what, pointerof(value)) + value + end + + private def pattern_info(what, where) + ret = LibPCRE2.pattern_info(@re, what, where) + if ret != 0 + raise "error pattern_info #{what}: #{ret}" + end + end + + private def name_table_impl + lookup = Hash(Int32, String).new + + each_capture_group do |capture_number, name_entry| + lookup[capture_number] = String.new(name_entry.to_unsafe + 2) + end + + lookup + end + + # :nodoc: + def each_capture_group + name_table = uninitialized UInt8* + pattern_info(LibPCRE2::INFO_NAMETABLE, pointerof(name_table)) + + name_entry_size = pattern_info(LibPCRE2::INFO_NAMEENTRYSIZE) + + name_count = pattern_info(LibPCRE2::INFO_NAMECOUNT) + name_count.times do + capture_number = (name_table[0] << 8) | name_table[1] + + yield capture_number, Slice.new(name_table, name_entry_size) + + name_table += name_entry_size + end + end + + private def capture_count_impl + pattern_info(LibPCRE2::INFO_CAPTURECOUNT).to_i32 + end + + private def match_impl(str, byte_index, options) + match_data = match_data(str, byte_index, options) || return + + ovector = LibPCRE2.get_ovector_pointer(match_data) + ovector_count = LibPCRE2.get_ovector_count(match_data) + + ::Regex::MatchData.new(self, @re, str, byte_index, ovector, ovector_count.to_i32 - 1) + end + + private def matches_impl(str, byte_index, options) + if match_data = match_data(str, byte_index, options) + true + else + false + end + end + + class_getter general_context do + LibPCRE2.general_context_create(->(size : LibC::Int, data : Void*) { GC.malloc(size) }.pointer, ->(pointer : Void*, data : Void*) { GC.free(pointer) }.pointer, nil) + end + + private def match_data(str, byte_index, options) + match_data = LibPCRE2.match_data_create_from_pattern(@re, Regex::PCRE2.general_context) + match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, nil) + + if match_count < 0 + case error = LibPCRE2::Error.new(match_count) + when .nomatch? + return + else + raise Exception.new("Regex match error: #{error}") + end + end + + match_data + end + + def self.config(what, type : T.class) : T forall T + value = uninitialized T + LibPCRE2.config(what, pointerof(value)) + value + end + + module MatchData + # :nodoc: + def initialize(@regex : Regex, @code : LibPCRE2::Code*, @string : String, @pos : Int32, @ovector : UInt64*, @group_size : Int32) + end + + private def byte_range(n, &) + n += size if n < 0 + range = Range.new(@ovector[n * 2].to_i32!, @ovector[n * 2 + 1].to_i32!, exclusive: true) + if range.begin < 0 || range.end < 0 + yield n + else + range + end + end + + private def fetch_impl(group_name : String) + selected_range = nil + exists = false + @regex.each_capture_group do |number, name_entry| + if name_entry[2, group_name.bytesize]? == group_name.to_slice + exists = true + range = byte_range(number) { nil } + if (range && selected_range && range.begin > selected_range.begin) || !selected_range + selected_range = range + end + end + end + + if selected_range + @string.byte_slice(selected_range.begin, selected_range.end - selected_range.begin) + else + yield exists + end + end + end +end From ba5f5221c5b32fb7b2c37fa767e7db1d19842cff Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 23 Dec 2022 19:20:32 +0800 Subject: [PATCH 0202/1551] Add `#bit_reverse` and `#byte_swap` for primitive integers (#12865) --- spec/std/int_spec.cr | 46 ++++ src/base64.cr | 2 +- src/bit_array.cr | 10 +- .../crystal/interpreter/instructions.cr | 30 +-- .../crystal/interpreter/primitives.cr | 26 +- src/int.cr | 240 ++++++++++++++++++ src/intrinsics.cr | 52 +++- src/socket/address.cr | 8 +- 8 files changed, 368 insertions(+), 46 deletions(-) diff --git a/spec/std/int_spec.cr b/spec/std/int_spec.cr index b8029e3a0911..4f377b385206 100644 --- a/spec/std/int_spec.cr +++ b/spec/std/int_spec.cr @@ -770,6 +770,52 @@ describe "Int" do iter.next.should be_a(Iterator::Stop) end + describe "#bit_reverse" do + it { 0x12_u8.bit_reverse.should eq(0x48_u8) } + it { 0x1234_u16.bit_reverse.should eq(0x2C48_u16) } + it { 0x12345678_u32.bit_reverse.should eq(0x1E6A2C48_u32) } + it { 0x123456789ABCDEF0_u64.bit_reverse.should eq(0x0F7B3D591E6A2C48_u64) } + it { 1.to_u128.bit_reverse.should eq(1.to_u128 << 127) } + it { (1.to_u128 << 127).bit_reverse.should eq(0x1.to_u128) } + it { 0x12345678.to_u128.bit_reverse.should eq(0x1E6A2C48.to_u128 << 96) } + + it { 0x12_i8.bit_reverse.should eq(0x48_i8) } + it { 0x1234_i16.bit_reverse.should eq(0x2C48_i16) } + it { 0x12345678_i32.bit_reverse.should eq(0x1E6A2C48_i32) } + it { 0x123456789ABCDEF0_i64.bit_reverse.should eq(0x0F7B3D591E6A2C48_i64) } + it { 1.to_i128.bit_reverse.should eq(1.to_i128 << 127) } + it { (1.to_i128 << 127).bit_reverse.should eq(0x1.to_i128) } + it { 0x12345678.to_i128.bit_reverse.should eq(0x1E6A2C48.to_i128 << 96) } + + {% for width in %w(8 16 32 64 128).map(&.id) %} + it { 0.to_i{{width}}.bit_reverse.should be_a(Int{{width}}) } + it { 0.to_u{{width}}.bit_reverse.should be_a(UInt{{width}}) } + {% end %} + end + + describe "#byte_swap" do + it { 0x12_u8.byte_swap.should eq(0x12_u8) } + it { 0x1234_u16.byte_swap.should eq(0x3412_u16) } + it { 0x12345678_u32.byte_swap.should eq(0x78563412_u32) } + it { 0x123456789ABCDEF0_u64.byte_swap.should eq(0xF0DEBC9A78563412_u64) } + it { 1.to_u128.byte_swap.should eq(1.to_u128 << 120) } + it { (1.to_u128 << 127).byte_swap.should eq(0x80.to_u128) } + it { 0x12345678.to_u128.byte_swap.should eq(0x78563412.to_u128 << 96) } + + it { 0x12_i8.byte_swap.should eq(0x12_i8) } + it { 0x1234_i16.byte_swap.should eq(0x3412_i16) } + it { 0x12345678_i32.byte_swap.should eq(0x78563412_i32) } + it { 0x123456789ABCDEF0_i64.byte_swap.should eq(0xF0DEBC9A78563412_u64.to_i64!) } + it { 1.to_i128.byte_swap.should eq(1.to_i128 << 120) } + it { (1.to_i128 << 127).byte_swap.should eq(0x80.to_i128) } + it { 0x12345678.to_i128.byte_swap.should eq(0x78563412.to_i128 << 96) } + + {% for width in %w(8 16 32 64 128).map(&.id) %} + it { 0.to_i{{width}}.byte_swap.should be_a(Int{{width}}) } + it { 0.to_u{{width}}.byte_swap.should be_a(UInt{{width}}) } + {% end %} + end + describe "#popcount" do it { 5_i8.popcount.should eq(2) } it { 127_i8.popcount.should eq(7) } diff --git a/src/base64.cr b/src/base64.cr index ddb783dfe49e..10ef444f08f1 100644 --- a/src/base64.cr +++ b/src/base64.cr @@ -208,7 +208,7 @@ module Base64 # process bunch of full triples while cstr < endcstr - n = Intrinsics.bswap32(cstr.as(UInt32*).value) + n = cstr.as(UInt32*).value.byte_swap yield bytes[(n >> 26) & 63] yield bytes[(n >> 20) & 63] yield bytes[(n >> 14) & 63] diff --git a/src/bit_array.cr b/src/bit_array.cr index 4fd4703b7eb8..9003de5d396f 100644 --- a/src/bit_array.cr +++ b/src/bit_array.cr @@ -484,10 +484,10 @@ struct BitArray return self if size <= 1 if size <= 32 - @bits.value = Intrinsics.bitreverse32(@bits.value) >> (32 - size) + @bits.value = @bits.value.bit_reverse >> (32 - size) elsif size <= 64 more_bits = @bits.as(UInt64*) - more_bits.value = Intrinsics.bitreverse64(more_bits.value) >> (64 - size) + more_bits.value = more_bits.value.bit_reverse >> (64 - size) else # 3 or more groups of bits offset = (-size) % 32 @@ -502,17 +502,17 @@ struct BitArray # hgfedcba fghijklm nopqrstu (malloc_size - 1).downto(1) do |i| # fshl(a, b, count) = (a << count) | (b >> (N - count)) - @bits[i] = Intrinsics.bitreverse32(Intrinsics.fshl32(@bits[i], @bits[i - 1], offset)) + @bits[i] = Intrinsics.fshl32(@bits[i], @bits[i - 1], offset).bit_reverse end # last group: # # edcba000 fghijklm nopqrstu # 000abcde fghijklm nopqrstu - @bits[0] = Intrinsics.bitreverse32(@bits[0] << offset) + @bits[0] = (@bits[0] << offset).bit_reverse else # no padding; do only the bit reverses - Slice.new(@bits, malloc_size).map! { |x| Intrinsics.bitreverse32(x) } + Slice.new(@bits, malloc_size).map! &.bit_reverse end # reversing all groups themselves: diff --git a/src/compiler/crystal/interpreter/instructions.cr b/src/compiler/crystal/interpreter/instructions.cr index 52be2ab4aabc..66d5296bdba3 100644 --- a/src/compiler/crystal/interpreter/instructions.cr +++ b/src/compiler/crystal/interpreter/instructions.cr @@ -1820,22 +1820,24 @@ require "./repl" }, {% end %} - interpreter_intrinsics_bswap32: { - pop_values: [id : UInt32], - push: true, - code: LibIntrinsics.bswap32(id), - }, - interpreter_intrinsics_bswap16: { - pop_values: [id : UInt16], - push: true, - code: LibIntrinsics.bswap16(id), - }, interpreter_intrinsics_read_cycle_counter: { push: true, code: LibIntrinsics.read_cycle_counter, }, {% for n in [8, 16, 32, 64, 128] %} + interpreter_intrinsics_bitreverse{{n}}: { + pop_values: [value : UInt{{n}}], + push: true, + code: LibIntrinsics.bitreverse{{n}}(value), + }, + {% unless n == 8 %} + interpreter_intrinsics_bswap{{n}}: { + pop_values: [value : UInt{{n}}], + push: true, + code: LibIntrinsics.bswap{{n}}(value), + }, + {% end %} interpreter_intrinsics_popcount{{n}}: { pop_values: [value : Int{{n}}], push: true, @@ -1875,14 +1877,6 @@ require "./repl" }, {% end %} - {% for n in [16, 32, 64] %} - interpreter_intrinsics_bitreverse{{n}}: { - pop_values: [value : UInt{{n}}], - push: true, - code: LibIntrinsics.bitreverse{{n}}(value), - }, - {% end %} - libm_ceil_f32: { pop_values: [value : Float32], push: true, diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr index 34cc22b2dbab..e89a3c7fae36 100644 --- a/src/compiler/crystal/interpreter/primitives.cr +++ b/src/compiler/crystal/interpreter/primitives.cr @@ -274,7 +274,7 @@ class Crystal::Repl::Compiler pop(aligned_sizeof_type(node.type), node: nil) unless @wants_value when "load_atomic" - node.args.each { |arg| request_value(arg) } + accept_call_args(node) pointer_instance_type = node.args.first.type.as(PointerInstanceType) element_type = pointer_instance_type.element_type @@ -282,7 +282,7 @@ class Crystal::Repl::Compiler load_atomic(element_size, node: node) when "store_atomic" - node.args.each { |arg| request_value(arg) } + accept_call_args(node) pointer_instance_type = node.args.first.type.as(PointerInstanceType) element_type = pointer_instance_type.element_type @@ -290,7 +290,7 @@ class Crystal::Repl::Compiler store_atomic(element_size, node: node) when "atomicrmw" - node.args.each { |arg| request_value(arg) } + accept_call_args(node) pointer_instance_type = node.args[1].type.as(PointerInstanceType) element_type = pointer_instance_type.element_type @@ -298,7 +298,7 @@ class Crystal::Repl::Compiler atomicrmw(element_size, node: node) when "cmpxchg" - node.args.each { |arg| request_value(arg) } + accept_call_args(node) pointer_instance_type = node.args[0].type.as(PointerInstanceType) element_type = pointer_instance_type.element_type @@ -413,10 +413,18 @@ class Crystal::Repl::Compiler {% if flag?(:i386) || flag?(:x86_64) %} interpreter_intrinsics_pause(node: node) {% end %} - when "interpreter_intrinsics_bswap32" - interpreter_intrinsics_bswap32(node: node) when "interpreter_intrinsics_bswap16" + accept_call_args(node) interpreter_intrinsics_bswap16(node: node) + when "interpreter_intrinsics_bswap32" + accept_call_args(node) + interpreter_intrinsics_bswap32(node: node) + when "interpreter_intrinsics_bswap64" + accept_call_args(node) + interpreter_intrinsics_bswap64(node: node) + when "interpreter_intrinsics_bswap128" + accept_call_args(node) + interpreter_intrinsics_bswap128(node: node) when "interpreter_intrinsics_read_cycle_counter" interpreter_intrinsics_read_cycle_counter(node: node) when "interpreter_intrinsics_popcount8" @@ -464,6 +472,9 @@ class Crystal::Repl::Compiler when "interpreter_intrinsics_counttrailing128" accept_call_args(node) interpreter_intrinsics_counttrailing128(node: node) + when "interpreter_intrinsics_bitreverse8" + accept_call_args(node) + interpreter_intrinsics_bitreverse8(node: node) when "interpreter_intrinsics_bitreverse16" accept_call_args(node) interpreter_intrinsics_bitreverse16(node: node) @@ -473,6 +484,9 @@ class Crystal::Repl::Compiler when "interpreter_intrinsics_bitreverse64" accept_call_args(node) interpreter_intrinsics_bitreverse64(node: node) + when "interpreter_intrinsics_bitreverse128" + accept_call_args(node) + interpreter_intrinsics_bitreverse128(node: node) when "interpreter_intrinsics_fshl8" accept_call_args(node) interpreter_intrinsics_fshl8(node: node) diff --git a/src/int.cr b/src/int.cr index e124c4caa872..7de873b9c008 100644 --- a/src/int.cr +++ b/src/int.cr @@ -878,6 +878,30 @@ struct Int8 Intrinsics.popcount8(self) end + # Reverses the bits of `self`; the least significant bit becomes the most + # significant, and vice-versa. + # + # ``` + # 0b01001011_u8.bit_reverse # => 0b11010010 + # 0b1100100001100111_u16.bit_reverse # => 0b1110011000010011 + # ``` + def bit_reverse : self + Intrinsics.bitreverse8(self).to_i8! + end + + # Swaps the bytes of `self`; a little-endian value becomes a big-endian value, + # and vice-versa. The bit order within each byte is unchanged. + # + # Has no effect on 8-bit integers. + # + # ``` + # 0x1234_u16.byte_swap # => 0x3412 + # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # ``` + def byte_swap : self + self + end + # Returns the number of leading `0`-bits. def leading_zeros_count Intrinsics.countleading8(self, false) @@ -955,6 +979,30 @@ struct Int16 Intrinsics.popcount16(self) end + # Reverses the bits of `self`; the least significant bit becomes the most + # significant, and vice-versa. + # + # ``` + # 0b01001011_u8.bit_reverse # => 0b11010010 + # 0b1100100001100111_u16.bit_reverse # => 0b1110011000010011 + # ``` + def bit_reverse : self + Intrinsics.bitreverse16(self).to_i16! + end + + # Swaps the bytes of `self`; a little-endian value becomes a big-endian value, + # and vice-versa. The bit order within each byte is unchanged. + # + # Has no effect on 8-bit integers. + # + # ``` + # 0x1234_u16.byte_swap # => 0x3412 + # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # ``` + def byte_swap : self + Intrinsics.bswap16(self).to_i16! + end + # Returns the number of leading `0`-bits. def leading_zeros_count Intrinsics.countleading16(self, false) @@ -1032,6 +1080,30 @@ struct Int32 Intrinsics.popcount32(self) end + # Reverses the bits of `self`; the least significant bit becomes the most + # significant, and vice-versa. + # + # ``` + # 0b01001011_u8.bit_reverse # => 0b11010010 + # 0b1100100001100111_u16.bit_reverse # => 0b1110011000010011 + # ``` + def bit_reverse : self + Intrinsics.bitreverse32(self).to_i32! + end + + # Swaps the bytes of `self`; a little-endian value becomes a big-endian value, + # and vice-versa. The bit order within each byte is unchanged. + # + # Has no effect on 8-bit integers. + # + # ``` + # 0x1234_u16.byte_swap # => 0x3412 + # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # ``` + def byte_swap : self + Intrinsics.bswap32(self).to_i32! + end + # Returns the number of leading `0`-bits. def leading_zeros_count Intrinsics.countleading32(self, false) @@ -1109,6 +1181,30 @@ struct Int64 Intrinsics.popcount64(self) end + # Reverses the bits of `self`; the least significant bit becomes the most + # significant, and vice-versa. + # + # ``` + # 0b01001011_u8.bit_reverse # => 0b11010010 + # 0b1100100001100111_u16.bit_reverse # => 0b1110011000010011 + # ``` + def bit_reverse : self + Intrinsics.bitreverse64(self).to_i64! + end + + # Swaps the bytes of `self`; a little-endian value becomes a big-endian value, + # and vice-versa. The bit order within each byte is unchanged. + # + # Has no effect on 8-bit integers. + # + # ``` + # 0x1234_u16.byte_swap # => 0x3412 + # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # ``` + def byte_swap : self + Intrinsics.bswap64(self).to_i64! + end + # Returns the number of leading `0`-bits. def leading_zeros_count Intrinsics.countleading64(self, false) @@ -1188,6 +1284,30 @@ struct Int128 Intrinsics.popcount128(self) end + # Reverses the bits of `self`; the least significant bit becomes the most + # significant, and vice-versa. + # + # ``` + # 0b01001011_u8.bit_reverse # => 0b11010010 + # 0b1100100001100111_u16.bit_reverse # => 0b1110011000010011 + # ``` + def bit_reverse : self + Intrinsics.bitreverse128(self).to_i128! + end + + # Swaps the bytes of `self`; a little-endian value becomes a big-endian value, + # and vice-versa. The bit order within each byte is unchanged. + # + # Has no effect on 8-bit integers. + # + # ``` + # 0x1234_u16.byte_swap # => 0x3412 + # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # ``` + def byte_swap : self + Intrinsics.bswap128(self).to_i128! + end + # Returns the number of leading `0`-bits. def leading_zeros_count Intrinsics.countleading128(self, false) @@ -1269,6 +1389,30 @@ struct UInt8 Intrinsics.popcount8(self) end + # Reverses the bits of `self`; the least significant bit becomes the most + # significant, and vice-versa. + # + # ``` + # 0b01001011_u8.bit_reverse # => 0b11010010 + # 0b1100100001100111_u16.bit_reverse # => 0b1110011000010011 + # ``` + def bit_reverse : self + Intrinsics.bitreverse8(self) + end + + # Swaps the bytes of `self`; a little-endian value becomes a big-endian value, + # and vice-versa. The bit order within each byte is unchanged. + # + # Has no effect on 8-bit integers. + # + # ``` + # 0x1234_u16.byte_swap # => 0x3412 + # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # ``` + def byte_swap : self + self + end + # Returns the number of leading `0`-bits. def leading_zeros_count Intrinsics.countleading8(self, false) @@ -1350,6 +1494,30 @@ struct UInt16 Intrinsics.popcount16(self) end + # Reverses the bits of `self`; the least significant bit becomes the most + # significant, and vice-versa. + # + # ``` + # 0b01001011_u8.bit_reverse # => 0b11010010 + # 0b1100100001100111_u16.bit_reverse # => 0b1110011000010011 + # ``` + def bit_reverse : self + Intrinsics.bitreverse16(self) + end + + # Swaps the bytes of `self`; a little-endian value becomes a big-endian value, + # and vice-versa. The bit order within each byte is unchanged. + # + # Has no effect on 8-bit integers. + # + # ``` + # 0x1234_u16.byte_swap # => 0x3412 + # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # ``` + def byte_swap : self + Intrinsics.bswap16(self) + end + # Returns the number of leading `0`-bits. def leading_zeros_count Intrinsics.countleading16(self, false) @@ -1431,6 +1599,30 @@ struct UInt32 Intrinsics.popcount32(self) end + # Reverses the bits of `self`; the least significant bit becomes the most + # significant, and vice-versa. + # + # ``` + # 0b01001011_u8.bit_reverse # => 0b11010010 + # 0b1100100001100111_u16.bit_reverse # => 0b1110011000010011 + # ``` + def bit_reverse : self + Intrinsics.bitreverse32(self) + end + + # Swaps the bytes of `self`; a little-endian value becomes a big-endian value, + # and vice-versa. The bit order within each byte is unchanged. + # + # Has no effect on 8-bit integers. + # + # ``` + # 0x1234_u16.byte_swap # => 0x3412 + # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # ``` + def byte_swap : self + Intrinsics.bswap32(self) + end + # Returns the number of leading `0`-bits. def leading_zeros_count Intrinsics.countleading32(self, false) @@ -1512,6 +1704,30 @@ struct UInt64 Intrinsics.popcount64(self) end + # Reverses the bits of `self`; the least significant bit becomes the most + # significant, and vice-versa. + # + # ``` + # 0b01001011_u8.bit_reverse # => 0b11010010 + # 0b1100100001100111_u16.bit_reverse # => 0b1110011000010011 + # ``` + def bit_reverse : self + Intrinsics.bitreverse64(self) + end + + # Swaps the bytes of `self`; a little-endian value becomes a big-endian value, + # and vice-versa. The bit order within each byte is unchanged. + # + # Has no effect on 8-bit integers. + # + # ``` + # 0x1234_u16.byte_swap # => 0x3412 + # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # ``` + def byte_swap : self + Intrinsics.bswap64(self) + end + # Returns the number of leading `0`-bits. def leading_zeros_count Intrinsics.countleading64(self, false) @@ -1595,6 +1811,30 @@ struct UInt128 Intrinsics.popcount128(self) end + # Reverses the bits of `self`; the least significant bit becomes the most + # significant, and vice-versa. + # + # ``` + # 0b01001011_u8.bit_reverse # => 0b11010010 + # 0b1100100001100111_u16.bit_reverse # => 0b1110011000010011 + # ``` + def bit_reverse : self + Intrinsics.bitreverse128(self) + end + + # Swaps the bytes of `self`; a little-endian value becomes a big-endian value, + # and vice-versa. The bit order within each byte is unchanged. + # + # Has no effect on 8-bit integers. + # + # ``` + # 0x1234_u16.byte_swap # => 0x3412 + # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # ``` + def byte_swap : self + Intrinsics.bswap128(self) + end + # Returns the number of leading `0`-bits. def leading_zeros_count Intrinsics.countleading128(self, false) diff --git a/src/intrinsics.cr b/src/intrinsics.cr index ab0ab022fbe6..1bbbc8c3330b 100644 --- a/src/intrinsics.cr +++ b/src/intrinsics.cr @@ -49,21 +49,33 @@ lib LibIntrinsics {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_read_cycle_counter)] {% end %} fun read_cycle_counter = "llvm.readcyclecounter" : UInt64 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse64)] {% end %} - fun bitreverse64 = "llvm.bitreverse.i64"(id : UInt64) : UInt64 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse8)] {% end %} + fun bitreverse8 = "llvm.bitreverse.i8"(id : UInt8) : UInt8 + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse16)] {% end %} + fun bitreverse16 = "llvm.bitreverse.i16"(id : UInt16) : UInt16 {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse32)] {% end %} fun bitreverse32 = "llvm.bitreverse.i32"(id : UInt32) : UInt32 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse16)] {% end %} - fun bitreverse16 = "llvm.bitreverse.i16"(id : UInt16) : UInt16 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse64)] {% end %} + fun bitreverse64 = "llvm.bitreverse.i64"(id : UInt64) : UInt64 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bswap32)] {% end %} - fun bswap32 = "llvm.bswap.i32"(id : UInt32) : UInt32 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse128)] {% end %} + fun bitreverse128 = "llvm.bitreverse.i128"(id : UInt128) : UInt128 {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bswap16)] {% end %} fun bswap16 = "llvm.bswap.i16"(id : UInt16) : UInt16 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bswap32)] {% end %} + fun bswap32 = "llvm.bswap.i32"(id : UInt32) : UInt32 + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bswap64)] {% end %} + fun bswap64 = "llvm.bswap.i64"(id : UInt64) : UInt64 + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bswap128)] {% end %} + fun bswap128 = "llvm.bswap.i128"(id : UInt128) : UInt128 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_popcount8)] {% end %} fun popcount8 = "llvm.ctpop.i8"(src : Int8) : Int8 @@ -196,24 +208,40 @@ module Intrinsics LibIntrinsics.read_cycle_counter end - def self.bitreverse64(id) : UInt64 - LibIntrinsics.bitreverse64(id) + def self.bitreverse8(id) : UInt8 + LibIntrinsics.bitreverse8(id) + end + + def self.bitreverse16(id) : UInt16 + LibIntrinsics.bitreverse16(id) end def self.bitreverse32(id) : UInt32 LibIntrinsics.bitreverse32(id) end - def self.bitreverse16(id) : UInt16 - LibIntrinsics.bitreverse16(id) + def self.bitreverse64(id) : UInt64 + LibIntrinsics.bitreverse64(id) + end + + def self.bitreverse128(id) : UInt128 + LibIntrinsics.bitreverse128(id) + end + + def self.bswap16(id) : UInt16 + LibIntrinsics.bswap16(id) end def self.bswap32(id) : UInt32 LibIntrinsics.bswap32(id) end - def self.bswap16(id) - LibIntrinsics.bswap16(id) + def self.bswap64(id) : UInt64 + LibIntrinsics.bswap64(id) + end + + def self.bswap128(id) : UInt128 + LibIntrinsics.bswap128(id) end def self.popcount8(src) : Int8 diff --git a/src/socket/address.cr b/src/socket/address.cr index 5ce5f40bf4d4..601d4e7b7191 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -147,7 +147,7 @@ class Socket @addr = sockaddr.value.sin6_addr @port = {% if flag?(:dragonfly) %} - Intrinsics.bswap16(sockaddr.value.sin6_port).to_i + sockaddr.value.sin6_port.byte_swap.to_i {% else %} LibC.ntohs(sockaddr.value.sin6_port).to_i {% end %} @@ -158,7 +158,7 @@ class Socket @addr = sockaddr.value.sin_addr @port = {% if flag?(:dragonfly) %} - Intrinsics.bswap16(sockaddr.value.sin_port).to_i + sockaddr.value.sin_port.byte_swap.to_i {% else %} LibC.ntohs(sockaddr.value.sin_port).to_i {% end %} @@ -305,7 +305,7 @@ class Socket sockaddr = Pointer(LibC::SockaddrIn6).malloc sockaddr.value.sin6_family = family {% if flag?(:dragonfly) %} - sockaddr.value.sin6_port = Intrinsics.bswap16(port) + sockaddr.value.sin6_port = port.byte_swap {% else %} sockaddr.value.sin6_port = LibC.htons(port) {% end %} @@ -317,7 +317,7 @@ class Socket sockaddr = Pointer(LibC::SockaddrIn).malloc sockaddr.value.sin_family = family {% if flag?(:dragonfly) %} - sockaddr.value.sin_port = Intrinsics.bswap16(port) + sockaddr.value.sin_port = port.byte_swap {% else %} sockaddr.value.sin_port = LibC.htons(port) {% end %} From c4fe774f2bee985470bfee9dc1a2e844a315de43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 23 Dec 2022 12:21:36 +0100 Subject: [PATCH 0203/1551] Add `HTTP::Headers#serialize` (#12765) --- spec/std/http/headers_spec.cr | 5 +++++ src/http/common.cr | 19 ++++++++++--------- src/http/headers.cr | 26 ++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/spec/std/http/headers_spec.cr b/spec/std/http/headers_spec.cr index 7b83d06cfa94..1eb76f86ce84 100644 --- a/spec/std/http/headers_spec.cr +++ b/spec/std/http/headers_spec.cr @@ -150,6 +150,11 @@ describe HTTP::Headers do headers.to_s.should eq(%(HTTP::Headers{"Foo_quux" => "bar", "Baz-Quux" => ["a", "b"]})) end + it "#serialize" do + headers = HTTP::Headers{"Foo_quux" => "bar", "Baz-Quux" => ["a", "b"]} + headers.serialize.should eq("Foo_quux: bar\r\nBaz-Quux: a\r\nBaz-Quux: b\r\n") + end + it "merges and return self" do headers = HTTP::Headers.new headers.should be headers.merge!({"foo" => "bar"}) diff --git a/src/http/common.cr b/src/http/common.cr index 2e9da53c46ff..5cda70bbfe51 100644 --- a/src/http/common.cr +++ b/src/http/common.cr @@ -261,36 +261,37 @@ module HTTP elsif body_io content_length = content_length(headers) if content_length - serialize_headers(io, headers) + headers.serialize(io) + io << "\r\n" copied = IO.copy(body_io, io) if copied != content_length raise ArgumentError.new("Content-Length header is #{content_length} but body had #{copied} bytes") end elsif Client::Response.supports_chunked?(version) headers["Transfer-Encoding"] = "chunked" - serialize_headers(io, headers) + headers.serialize(io) + io << "\r\n" serialize_chunked_body(io, body_io) else body = body_io.gets_to_end serialize_headers_and_string_body(io, headers, body) end else - serialize_headers(io, headers) + headers.serialize(io) + io << "\r\n" end end def self.serialize_headers_and_string_body(io, headers, body) headers["Content-Length"] = body.bytesize.to_s - serialize_headers(io, headers) + headers.serialize(io) + io << "\r\n" io << body end + @[Deprecated("Use `HTTP::Headers#serialize` instead.")] def self.serialize_headers(io, headers) - headers.each do |name, values| - values.each do |value| - io << name << ": " << value << "\r\n" - end - end + headers.serialize(io) io << "\r\n" end diff --git a/src/http/headers.cr b/src/http/headers.cr index ef756d545beb..c88018082a7d 100644 --- a/src/http/headers.cr +++ b/src/http/headers.cr @@ -299,6 +299,32 @@ struct HTTP::Headers end end + # Serializes headers according to the HTTP protocol. + # + # Prints a list of HTTP header fields in the format desribed in [RFC 7230 §3.2](https://www.rfc-editor.org/rfc/rfc7230#section-3.2), + # with each field terminated by a CRLF sequence (`"\r\n"`). + # + # The serialization does *not* include a double CRLF sequence at the end. + # + # ``` + # headers = HTTP::Headers{"foo" => "bar", "baz" => %w[qux qox]}) + # headers.serialize # => "foo: bar\r\nbaz: qux\r\nbaz: qox\r\n" + # ``` + def serialize : String + String.build do |io| + serialize(io) + end + end + + # :ditto: + def serialize(io : IO) : Nil + each do |name, values| + values.each do |value| + io << name << ": " << value << "\r\n" + end + end + end + def valid_value?(value) : Bool invalid_value_char(value).nil? end From a0ca3465cdcef8bd91cc141ab5406be6caab1580 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Fri, 23 Dec 2022 06:21:50 -0500 Subject: [PATCH 0204/1551] Validate cookie name prefixes (#10648) --- spec/std/http/cookie_spec.cr | 119 +++++++++++++++++++++++++++++++++++ src/http/cookie.cr | 54 +++++++++++++++- 2 files changed, 171 insertions(+), 2 deletions(-) diff --git a/spec/std/http/cookie_spec.cr b/spec/std/http/cookie_spec.cr index 08d2e91c7b16..218bfd9c608e 100644 --- a/spec/std/http/cookie_spec.cr +++ b/spec/std/http/cookie_spec.cr @@ -45,6 +45,29 @@ module HTTP HTTP::Cookie.new("x", %(foo\rbar)) end end + + describe "with a security prefix" do + it "raises on invalid cookie with prefix" do + expect_raises ArgumentError, "Invalid cookie name. Has '__Secure-' prefix, but is not secure." do + HTTP::Cookie.new("__Secure-foo", "bar", secure: false) + end + + expect_raises ArgumentError, "Invalid cookie name. Does not meet '__Host-' prefix requirements of: secure: true, path: \"/\", domain: nil." do + HTTP::Cookie.new("__Host-foo", "bar", domain: "foo") + end + end + + it "automatically makes the cookie secure if it has the __Secure- prefix and no explicit *secure* value is provided" do + HTTP::Cookie.new("__Secure-foo", "bar").secure.should be_true + end + + it "automatically configures the cookie if it has the __Host- prefix and no explicit values provided" do + cookie = HTTP::Cookie.new "__Host-foo", "bar" + cookie.secure.should be_true + cookie.domain.should be_nil + cookie.path.should eq "/" + end + end end describe "#name=" do @@ -67,6 +90,42 @@ module HTTP end end end + + it "doesn't raise on invalid cookie with __Secure- prefix" do + cookie = HTTP::Cookie.new "x", "", secure: false + + cookie.name = "__Secure-x" + cookie.name.should eq "__Secure-x" + cookie.secure.should be_false + end + + it "doesn't raise on invalid cookie with __Host- prefix" do + cookie = HTTP::Cookie.new "x", "", path: "/foo" + + cookie.name = "__Host-x" + cookie.name.should eq "__Host-x" + cookie.secure.should be_true + cookie.path.should eq "/foo" + cookie.valid?.should be_false + end + + it "automatically configures the cookie __Secure- prefix and related properties are unset" do + cookie = HTTP::Cookie.new "x", "" + + cookie.name = "__Secure-x" + cookie.name.should eq "__Secure-x" + cookie.secure.should be_true + end + + it "automatically configures the cookie __Host- prefix and related unset properties" do + cookie = HTTP::Cookie.new "x", "" + + cookie.name = "__Host-x" + cookie.name.should eq "__Host-x" + cookie.secure.should be_true + cookie.path.should eq "/" + cookie.domain.should be_nil + end end describe "#value=" do @@ -105,6 +164,66 @@ module HTTP it { HTTP::Cookie.new("empty-value", "").to_set_cookie_header.should eq "empty-value=" } end + + describe "#valid? & #validate!" do + it "raises on invalid cookie with __Secure- prefix" do + cookie = HTTP::Cookie.new "x", "", secure: false + cookie.name = "__Secure-x" + + cookie.valid?.should be_false + + expect_raises ArgumentError, "Invalid cookie name. Has '__Secure-' prefix, but is not secure." do + cookie.validate! + end + + cookie.secure = true + cookie.valid?.should be_true + end + + it "with a __Secure- prefix, but @secure is somehow `nil`" do + cookie = HTTP::Cookie.new "__Secure-x", "" + + cookie.valid?.should be_true + + pointerof(cookie.@secure).value = nil + + cookie.valid?.should be_false + end + + it "raises on invalid cookie with __Host- prefix" do + cookie = HTTP::Cookie.new "x", "", domain: "example.com", secure: false + cookie.name = "__Host-x" + + cookie.valid?.should be_false + + # Not secure + expect_raises ArgumentError, "Invalid cookie name. Does not meet '__Host-' prefix requirements of: secure: true, path: \"/\", domain: nil." do + cookie.validate! + end + + cookie.secure = true + cookie.valid?.should be_false + + # Invalid path + expect_raises ArgumentError, "Invalid cookie name. Does not meet '__Host-' prefix requirements of: secure: true, path: \"/\", domain: nil." do + cookie.validate! + end + + cookie.path = "/" + cookie.valid?.should be_false + + # Has domain + expect_raises ArgumentError, "Invalid cookie name. Does not meet '__Host-' prefix requirements of: secure: true, path: \"/\", domain: nil." do + cookie.validate! + end + + cookie.domain = nil + + cookie.name = "__Host-x" + cookie.name.should eq "__Host-x" + cookie.valid?.should be_true + end + end end describe Cookie::Parser do diff --git a/src/http/cookie.cr b/src/http/cookie.cr index 23f853a5a47c..f6199cf42484 100644 --- a/src/http/cookie.cr +++ b/src/http/cookie.cr @@ -20,21 +20,24 @@ module HTTP property path : String? property expires : Time? property domain : String? - property secure : Bool property http_only : Bool property samesite : SameSite? property extension : String? property max_age : Time::Span? getter creation_time : Time + @secure : Bool? + def_equals_and_hash name, value, path, expires, domain, secure, http_only, samesite, extension # Creates a new `Cookie` instance. # # Raises `IO::Error` if *name* or *value* are invalid as per [RFC 6265 §4.1.1](https://tools.ietf.org/html/rfc6265#section-4.1.1). + # Raises `ArgumentError` if *name* has a security prefix but the requirements are not met as per [RFC 6265 bis §4.1.3](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.3). + # Alternatively, if *name* has a security prefix, and the related properties are `nil`, the prefix will automatically be applied to the cookie. def initialize(name : String, value : String, @path : String? = nil, @expires : Time? = nil, @domain : String? = nil, - @secure : Bool = false, @http_only : Bool = false, + @secure : Bool? = nil, @http_only : Bool = false, @samesite : SameSite? = nil, @extension : String? = nil, @max_age : Time::Span? = nil, @creation_time = Time.utc) validate_name(name) @@ -42,6 +45,17 @@ module HTTP validate_value(value) @value = value raise IO::Error.new("Invalid max_age") if @max_age.try { |max_age| max_age < Time::Span.zero } + + self.check_prefix + self.validate! + end + + # Returns `true` if this cookie has the *Secure* flag. + def secure : Bool + !!@secure + end + + def secure=(@secure : Bool) : Bool end # Sets the name of this cookie. @@ -50,6 +64,8 @@ module HTTP def name=(name : String) validate_name(name) @name = name + + self.check_prefix end private def validate_name(name) @@ -140,6 +156,40 @@ module HTTP end end + # Returns `false` if `#name` has a security prefix but the requirements are not met as per + # [RFC 6265 bis §4.1.3](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.3), + # otherwise returns `true`. + def valid? : Bool + self.valid_secure_prefix? && self.valid_host_prefix? + end + + # Raises `ArgumentError` if `self` is not `#valid?`. + def validate! : self + raise ArgumentError.new "Invalid cookie name. Has '__Secure-' prefix, but is not secure." unless self.valid_secure_prefix? + raise ArgumentError.new "Invalid cookie name. Does not meet '__Host-' prefix requirements of: secure: true, path: \"/\", domain: nil." unless self.valid_host_prefix? + + self + end + + private def valid_secure_prefix? : Bool + self.secure || !@name.starts_with?("__Secure-") + end + + private def valid_host_prefix? : Bool + !@name.starts_with?("__Host-") || (self.secure && "/" == @path && @domain.nil?) + end + + private def check_prefix : Nil + if @name.starts_with?("__Host-") + @path = "/" if @path.nil? + @secure = true if @secure.nil? + end + + if @name.starts_with?("__Secure-") + @secure = true if @secure.nil? + end + end + # :nodoc: module Parser module Regex From e578f09f7fe11ff1dfbe613b85ffaabebdb7539b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 Dec 2022 15:59:51 +0100 Subject: [PATCH 0205/1551] Update mwilliamson/setup-wasmtime-action action to v2 (#12864) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/wasm32.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 835c2ddbcccd..3dc4cf35e468 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v3 - name: Install wasmtime - uses: mwilliamson/setup-wasmtime-action@v1 + uses: mwilliamson/setup-wasmtime-action@v2 with: wasmtime-version: "2.0.0" From d3c68ec40101bed9d3e835bcda6c36ed969091e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 23 Dec 2022 16:00:07 +0100 Subject: [PATCH 0206/1551] Add missing overloads for `String#byte_slice` (#12809) Co-authored-by: Quinton Miller --- spec/std/string_spec.cr | 47 ++++++++++++++++++++++++- src/string.cr | 76 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 118 insertions(+), 5 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index a8d047b77977..b53c7479a31e 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -203,9 +203,10 @@ describe "String" do end end - describe "byte_slice" do + describe "#byte_slice" do it "gets byte_slice" do "hello".byte_slice(1, 3).should eq("ell") + "hello".byte_slice(1..3).should eq("ell") end it "gets byte_slice with negative count" do @@ -224,14 +225,19 @@ describe "String" do expect_raises(IndexError) do "hello".byte_slice(10, 3) end + expect_raises(IndexError) do + "hello".byte_slice(10..13) + end end it "gets byte_slice with large count" do "hello".byte_slice(1, 10).should eq("ello") + "hello".byte_slice(1..10).should eq("ello") end it "gets byte_slice with negative index" do "hello".byte_slice(-2, 3).should eq("lo") + "hello".byte_slice(-2..-1).should eq("lo") end it "gets byte_slice(Int) with start out of bounds" do @@ -244,6 +250,45 @@ describe "String" do end end + describe "#byte_slice?" do + it "gets byte_slice" do + "hello".byte_slice?(1, 3).should eq("ell") + "hello".byte_slice?(1..3).should eq("ell") + end + + it "gets byte_slice with negative count" do + expect_raises(ArgumentError) do + "hello".byte_slice?(1, -10) + end + end + + it "gets byte_slice with negative count at last" do + expect_raises(ArgumentError) do + "hello".byte_slice?(5, -1) + end + end + + it "gets byte_slice with start out of bounds" do + "hello".byte_slice?(10, 3).should be_nil + "hello".byte_slice?(10..13).should be_nil + end + + it "gets byte_slice with large count" do + "hello".byte_slice?(1, 10).should eq("ello") + "hello".byte_slice?(1..11).should eq("ello") + end + + it "gets byte_slice with negative index" do + "hello".byte_slice?(-2, 3).should eq("lo") + "hello".byte_slice?(-2..-1).should eq("lo") + end + + it "gets byte_slice(Int) with start out of bounds" do + "hello".byte_slice?(10).should be_nil + "hello".byte_slice?(-10).should be_nil + end + end + describe "to_i" do it { "1234".to_i.should eq(1234) } it { "-128".to_i8.should eq(-128) } diff --git a/src/string.cr b/src/string.cr index 6df2aedb5331..0b7e36fbc144 100644 --- a/src/string.cr +++ b/src/string.cr @@ -1173,9 +1173,9 @@ class String # "hello".byte_slice(-2, 5) # => "he" # "¥hello".byte_slice(0, 2) # => "¥" # "¥hello".byte_slice(2, 2) # => "he" - # "¥hello".byte_slice(0, 1) # => "�" (invalid UTF-8 character) - # "¥hello".byte_slice(1, 1) # => "�" (invalid UTF-8 character) - # "¥hello".byte_slice(1, 2) # => "�h" (invalid UTF-8 character) + # "¥hello".byte_slice(0, 1) # => "\xC2" (invalid UTF-8 character) + # "¥hello".byte_slice(1, 1) # => "\xA5" (invalid UTF-8 character) + # "¥hello".byte_slice(1, 2) # => "\xA5h" (invalid UTF-8 character) # "hello".byte_slice(6, 2) # raises IndexError # "hello".byte_slice(-6, 2) # raises IndexError # "hello".byte_slice(0, -2) # raises ArgumentError @@ -1184,6 +1184,35 @@ class String byte_slice?(start, count) || raise IndexError.new end + # Returns a new string built from byte in *range*. + # + # Byte indices can be negative to start counting from the end of the string. + # If the end index is bigger than `#bytesize`, only remaining bytes are returned. + # + # This method should be avoided, + # unless the string is proven to be ASCII-only (for example `#ascii_only?`), + # or the byte positions are known to be at character boundaries. + # Otherwise, multi-byte characters may be split, leading to an invalid UTF-8 encoding. + # + # Raises `IndexError` if the *range* begin is out of bounds. + # + # ``` + # "hello".byte_slice(0..2) # => "hel" + # "hello".byte_slice(0..100) # => "hello" + # "hello".byte_slice(-2..3) # => "l" + # "hello".byte_slice(-2..5) # => "lo" + # "¥hello".byte_slice(0...2) # => "¥" + # "¥hello".byte_slice(2...4) # => "he" + # "¥hello".byte_slice(0..0) # => "\xC2" (invalid UTF-8 character) + # "¥hello".byte_slice(1..1) # => "\xA5" (invalid UTF-8 character) + # "¥hello".byte_slice(1..2) # => "\xA5h" (invalid UTF-8 character) + # "hello".byte_slice(6..2) # raises IndexError + # "hello".byte_slice(-6..2) # raises IndexError + # ``` + def byte_slice(range : Range) : String + byte_slice(*Indexable.range_to_index_and_count(range, bytesize) || raise IndexError.new) + end + # Like `byte_slice(Int, Int)` but returns `Nil` if the *start* index is out of bounds. # # Raises `ArgumentError` if *count* is negative. @@ -1209,6 +1238,18 @@ class String end end + # Like `byte_slice(Range)` but returns `Nil` if *range* begin is out of bounds. + # + # ``` + # "hello".byte_slice?(0..2) # => "hel" + # "hello".byte_slice?(0..100) # => "hello" + # "hello".byte_slice?(6..8) # => nil + # "hello".byte_slice?(-6..2) # => nil + # ``` + def byte_slice?(range : Range) : String? + byte_slice?(*Indexable.range_to_index_and_count(range, bytesize) || return nil) + end + # Returns a substring starting from the *start* byte. # # *start* can be negative to start counting @@ -1226,7 +1267,7 @@ class String # "hello".byte_slice(2) # => "llo" # "hello".byte_slice(-2) # => "lo" # "¥hello".byte_slice(2) # => "hello" - # "¥hello".byte_slice(1) # => "�hello" (invalid UTF-8 character) + # "¥hello".byte_slice(1) # => "\xA5hello" (invalid UTF-8 character) # "hello".byte_slice(6) # raises IndexError # "hello".byte_slice(-6) # raises IndexError # ``` @@ -1236,6 +1277,33 @@ class String byte_slice start, count end + # Returns a substring starting from the *start* byte. + # + # *start* can be negative to start counting + # from the end of the string. + # + # This method should be avoided, + # unless the string is proven to be ASCII-only (for example `#ascii_only?`), + # or the byte positions are known to be at character boundaries. + # Otherwise, multi-byte characters may be split, leading to an invalid UTF-8 encoding. + # + # Returns `nil` if *start* index is out of bounds. + # + # ``` + # "hello".byte_slice?(0) # => "hello" + # "hello".byte_slice?(2) # => "llo" + # "hello".byte_slice?(-2) # => "lo" + # "¥hello".byte_slice?(2) # => "hello" + # "¥hello".byte_slice?(1) # => "\xA5hello" (invalid UTF-8 character) + # "hello".byte_slice?(6) # => nil + # "hello".byte_slice?(-6) # => nil + # ``` + def byte_slice?(start : Int) : String? + count = bytesize - start + return nil if start > 0 && count < 0 + byte_slice? start, count + end + # Returns the codepoint of the character at the given *index*. # # Negative indices can be used to start counting from the end of the string. From 63e46ebdf666e3db69786890f435c62258bcbf02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 23 Dec 2022 22:41:43 +0100 Subject: [PATCH 0207/1551] Warn on missing space before colon in type declaration/restriction (#12740) --- spec/compiler/formatter/formatter_spec.cr | 4 ++ spec/compiler/parser/warnings_spec.cr | 37 +++++++++++- src/compiler/crystal/syntax/parser.cr | 68 +++++++++++++++++------ 3 files changed, 92 insertions(+), 17 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 5731cbe68815..abcbac598c29 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -225,12 +225,15 @@ describe Crystal::Formatter do assert_format "def foo( x , & block : Int32->Float64)\nend", "def foo(x, &block : Int32 -> Float64)\nend" assert_format "def foo( x , & block : ->)\nend", "def foo(x, &block : ->)\nend" assert_format "def foo( x , & : Int32 )\nend", "def foo(x, & : Int32)\nend" + assert_format "def foo(&: Int32)\nend", "def foo(& : Int32)\nend" + assert_format "def foo(&block: Int32)\nend", "def foo(&block : Int32)\nend" assert_format "def foo( x , * y )\nend", "def foo(x, *y)\nend" assert_format "class Bar\nprotected def foo(x)\na=b(c)\nend\nend", "class Bar\n protected def foo(x)\n a = b(c)\n end\nend" assert_format "def foo=(x)\nend" assert_format "def +(x)\nend" assert_format "def foo : Int32 \n end", "def foo : Int32\nend" assert_format "def foo ( x ) : Int32 \n end", "def foo(x) : Int32\nend" + assert_format "def foo: Int32\nend", "def foo : Int32\nend" assert_format "def %(x)\n 1\nend" assert_format "def //(x)\n 1\nend" assert_format "def `(x)\n 1\nend" @@ -725,6 +728,7 @@ describe Crystal::Formatter do assert_format "x : {A, B}", "x : {A, B}" assert_format "x : { {A, B}, {C, D} }" assert_format "x : {A, B, }", "x : {A, B}" + assert_format "x: Int32", "x : Int32" assert_format "class Foo\n@x : Int32\nend", "class Foo\n @x : Int32\nend" assert_format "class Foo\n@x : Int32\nend", "class Foo\n @x : Int32\nend" assert_format "class Foo\nx = 1\nend", "class Foo\n x = 1\nend" diff --git a/spec/compiler/parser/warnings_spec.cr b/spec/compiler/parser/warnings_spec.cr index be08adf0dba6..af816e5828ab 100644 --- a/spec/compiler/parser/warnings_spec.cr +++ b/spec/compiler/parser/warnings_spec.cr @@ -2,7 +2,7 @@ require "../../support/syntax" private def assert_parser_warning(source, message, *, file = __FILE__, line = __LINE__) parser = Parser.new(source) - parser.filename = "/foo/bar/baz.cr" + parser.filename = "test.cr" parser.parse warnings = parser.warnings.infos @@ -10,6 +10,15 @@ private def assert_parser_warning(source, message, *, file = __FILE__, line = __ warnings[0].should contain(message), file: file, line: line end +private def assert_no_parser_warning(source, *, file = __FILE__, line = __LINE__) + parser = Parser.new(source) + parser.filename = "test.cr" + parser.parse + + warnings = parser.warnings.infos + warnings.should eq([] of String), file: file, line: line +end + describe "Parser warnings" do it "warns on suffix-less UInt64 literals > Int64::MAX" do values = [ @@ -27,4 +36,30 @@ describe "Parser warnings" do assert_parser_warning "{{ #{value} }}", "Warning: #{value} doesn't fit in an Int64, try using the suffix u64 or i128" end end + + describe "warns on missing space before colon" do + it "in block param type restriction" do + assert_parser_warning("def foo(&block: Foo)\nend", "warning in test.cr:1\nWarning: space required before colon in type restriction (run `crystal tool format` to fix this)") + assert_no_parser_warning("def foo(&block : Foo)\nend") + end + + it "in anonymous block param type restriction" do + assert_parser_warning("def foo(&: Foo)\nend", "warning in test.cr:1\nWarning: space required before colon in type restriction (run `crystal tool format` to fix this)") + assert_no_parser_warning("def foo(& : Foo)\nend") + end + + it "in type declaration" do + assert_parser_warning("x: Int32", "warning in test.cr:1\nWarning: space required before colon in type declaration (run `crystal tool format` to fix this)") + assert_no_parser_warning("x : Int32") + assert_parser_warning("class Foo\n@x: Int32\nend", "warning in test.cr:2\nWarning: space required before colon in type declaration (run `crystal tool format` to fix this)") + assert_no_parser_warning("class Foo\n@x : Int32\nend") + assert_parser_warning("class Foo\n@@x: Int32\nend", "warning in test.cr:2\nWarning: space required before colon in type declaration (run `crystal tool format` to fix this)") + assert_no_parser_warning("class Foo\n@@x : Int32\nend") + end + + it "in return type restriction" do + assert_parser_warning("def foo: Foo\nend", "warning in test.cr:1\nWarning: space required before colon in return type restriction (run `crystal tool format` to fix this)") + assert_no_parser_warning("def foo : Foo\nend") + end + end end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 5eaf4ff4ec9b..f8b4f7ffd901 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -1231,7 +1231,11 @@ module Crystal if next_comes_colon_space? name = @token.value.to_s var = Var.new(name).at(@token.location).at_end(token_end_location) - next_token_skip_space + next_token + unless @token.type.space? + warnings.add_warning_at(@token.location, "space required before colon in type declaration (run `crystal tool format` to fix this)") + end + skip_space check :OP_COLON type_declaration = parse_type_declaration(var) set_visibility type_declaration @@ -1267,19 +1271,19 @@ module Crystal comes_colon_space end - def new_node_check_type_declaration(klass) - new_node_check_type_declaration(klass) { } - end - def new_node_check_type_declaration(klass) name = @token.value.to_s - yield name var = klass.new(name).at(@token.location) var.end_location = token_end_location @wants_regex = false - next_token_skip_space + next_token + space_after_name = @token.type.space? + skip_space if @no_type_declaration == 0 && @token.type.op_colon? + unless space_after_name + warnings.add_warning_at(@token.location, "space required before colon in type declaration (run `crystal tool format` to fix this)") + end parse_type_declaration(var) else var @@ -3548,16 +3552,22 @@ module Crystal if @token.type.const? receiver = parse_path + last_was_space = false elsif @token.type.ident? check_valid_def_name name = @token.value.to_s - equals_sign, _ = consume_def_equals_sign_skip_space + + equals_sign, _ = consume_def_equals_sign name = "#{name}=" if equals_sign + last_was_space = @token.type.space? + skip_space else check_valid_def_op_name name = @token.type.to_s - next_token_skip_space + next_token + last_was_space = @token.type.space? + skip_space end params = [] of Arg @@ -3579,15 +3589,19 @@ module Crystal name = @token.value.to_s name_location = @token.location - equals_sign, _ = consume_def_equals_sign_skip_space + equals_sign, _ = consume_def_equals_sign name = "#{name}=" if equals_sign + last_was_space = @token.type.space? + skip_space else check DefOrMacroCheck2 check_valid_def_op_name name = @token.type.to_s name_location = @token.location - next_token_skip_space + next_token + last_was_space = @token.type.space? + skip_space end else if receiver @@ -3658,7 +3672,9 @@ module Crystal raise "named parameters must follow bare *", params.last.location.not_nil! end - next_token_skip_space + next_token + last_was_space = @token.type.space? + skip_space if @token.type.symbol? raise "a space is mandatory between ':' and return type", @token end @@ -3685,6 +3701,9 @@ module Crystal end if @token.type.op_colon? + unless last_was_space + warnings.add_warning_at @token.location, "space required before colon in return type restriction (run `crystal tool format` to fix this)" + end next_token_skip_space return_type = parse_bare_proc_type end_location = return_type.end_location @@ -3790,7 +3809,12 @@ module Crystal end if @token.type.op_amp? - next_token_skip_space_or_newline + next_token + unless @token.type.ident? || @token.type.space? + warnings.add_warning_at @token.location, "space required before colon in type restriction (run `crystal tool format` to fix this)" + end + skip_space_or_newline + block_param = parse_block_param(extra_assigns, annotations) skip_space_or_newline # When block_param.name is empty, this is an anonymous parameter. @@ -3939,6 +3963,9 @@ module Crystal end if @token.type.op_colon? + unless param_name.empty? || found_space + warnings.add_warning_at @token.location, "space required before colon in type restriction (run `crystal tool format` to fix this)" + end next_token_skip_space_or_newline location = @token.location @@ -4241,7 +4268,8 @@ module Crystal @wants_regex = false next_token - if @token.type.space? + name_followed_by_space = @token.type.space? + if name_followed_by_space # We don't want the next token to be a regex literal if the call's name is # a variable in the current scope (it's unlikely that there will be a method # with that name that accepts a regex as a first argument). @@ -4322,6 +4350,9 @@ module Crystal end else if @no_type_declaration == 0 && @token.type.op_colon? + unless name_followed_by_space + warnings.add_warning_at(@token.location, "space required before colon in type declaration (run `crystal tool format` to fix this)") + end declare_var = parse_type_declaration(Var.new(name).at(location).at_end(end_location)) end_location = declare_var.end_location @@ -6142,14 +6173,19 @@ module Crystal end def consume_def_equals_sign_skip_space + result = consume_def_equals_sign + skip_space + result + end + + def consume_def_equals_sign end_location = token_end_location next_token if @token.type.op_eq? end_location = token_end_location - next_token_skip_space + next_token {true, end_location} else - skip_space {false, end_location} end end From ee5ccf03321c33d152943876b7c4cfb4f82f0320 Mon Sep 17 00:00:00 2001 From: I3oris <37157434+I3oris@users.noreply.github.com> Date: Sat, 24 Dec 2022 15:10:30 +0100 Subject: [PATCH 0208/1551] Interpreter reply (#12738) --- lib/reply/.gitignore | 9 + lib/reply/CHANGELOG.md | 61 + lib/reply/LICENSE | 21 + lib/reply/README.md | 104 ++ lib/reply/examples/crystal_repl.cr | 130 +++ lib/reply/examples/repl.cr | 11 + lib/reply/shard.yml | 14 + lib/reply/spec/auto_completion_spec.cr | 283 +++++ lib/reply/spec/char_reader_spec.cr | 74 ++ lib/reply/spec/expression_editor_spec.cr | 434 +++++++ lib/reply/spec/history_spec.cr | 178 +++ lib/reply/spec/reader_spec.cr | 613 ++++++++++ lib/reply/spec/spec_helper.cr | 141 +++ lib/reply/src/auto_completion.cr | 263 +++++ lib/reply/src/char_reader.cr | 198 ++++ lib/reply/src/expression_editor.cr | 1002 +++++++++++++++++ lib/reply/src/history.cr | 109 ++ lib/reply/src/reader.cr | 442 ++++++++ lib/reply/src/reply.cr | 5 + lib/reply/src/term_cursor.cr | 179 +++ lib/reply/src/term_size.cr | 150 +++ .../crystal/interpreter/interpreter.cr | 28 +- src/compiler/crystal/interpreter/prompt.cr | 116 -- .../crystal/interpreter/pry_reader.cr | 34 + src/compiler/crystal/interpreter/repl.cr | 70 +- .../crystal/interpreter/repl_reader.cr | 131 +++ 26 files changed, 4633 insertions(+), 167 deletions(-) create mode 100644 lib/reply/.gitignore create mode 100644 lib/reply/CHANGELOG.md create mode 100644 lib/reply/LICENSE create mode 100644 lib/reply/README.md create mode 100644 lib/reply/examples/crystal_repl.cr create mode 100644 lib/reply/examples/repl.cr create mode 100644 lib/reply/shard.yml create mode 100644 lib/reply/spec/auto_completion_spec.cr create mode 100644 lib/reply/spec/char_reader_spec.cr create mode 100644 lib/reply/spec/expression_editor_spec.cr create mode 100644 lib/reply/spec/history_spec.cr create mode 100644 lib/reply/spec/reader_spec.cr create mode 100644 lib/reply/spec/spec_helper.cr create mode 100644 lib/reply/src/auto_completion.cr create mode 100644 lib/reply/src/char_reader.cr create mode 100644 lib/reply/src/expression_editor.cr create mode 100644 lib/reply/src/history.cr create mode 100644 lib/reply/src/reader.cr create mode 100644 lib/reply/src/reply.cr create mode 100644 lib/reply/src/term_cursor.cr create mode 100644 lib/reply/src/term_size.cr delete mode 100644 src/compiler/crystal/interpreter/prompt.cr create mode 100644 src/compiler/crystal/interpreter/pry_reader.cr create mode 100644 src/compiler/crystal/interpreter/repl_reader.cr diff --git a/lib/reply/.gitignore b/lib/reply/.gitignore new file mode 100644 index 000000000000..0bbd4a9f41e1 --- /dev/null +++ b/lib/reply/.gitignore @@ -0,0 +1,9 @@ +/docs/ +/lib/ +/bin/ +/.shards/ +*.dwarf + +# Libraries don't need dependency lock +# Dependencies will be locked in applications that use them +/shard.lock diff --git a/lib/reply/CHANGELOG.md b/lib/reply/CHANGELOG.md new file mode 100644 index 000000000000..cce13877b562 --- /dev/null +++ b/lib/reply/CHANGELOG.md @@ -0,0 +1,61 @@ +## RELPy v0.3.0 + +### New features +* Windows support: REPLy is now working on Windows 10. +All features expect to work like linux except the key binding 'alt-enter' +that becomes 'ctrl-enter' on windows. +* Implement saving history in a file. + * Add `Reader#history_file` which allow to specify the file location. + * Add `History#max_size=` which allow to change the history max size. (default: 10_000) + +### Internals +* Windows: use `GetConsoleScreenBufferInfo` for `Term::Size` and `ReadConsoleA` for +`read_char`. +* Windows: Disable some specs on windows. +* Small refactoring on `colorized_lines`. +* Refactor: Remove unneeded ivar `@max_prompt_size`. +* Improve performances for `move_cursor_to`. +* Remove unneeded ameba exception. +* Remove useless printing of `Term::Cursor.show` at exit. + +## RELPy v0.2.1 + +### Bug fixs +* Reduce blinking on ws-code (computation are now done before clearing the screen). Disallow `sync` and `flush_on_newline` during `update` which help to reduce blinking too, (ic#10), thanks @cyangle! +* Align the expression when prompt size change (e.g. line number increase), which avoid a cursor bug in this case. +* Fix wrong history index after submitting an empty entry. + +### Internal +* Write spec to avoid bug with autocompletion with '=' characters (ic#11), thanks @cyangle! + +## RELPy v0.2.0 + +### New features + +* BREAKING CHANGE: `word_delimiters` is now a `Array(Char)` property instead of `Regex` to return in a overridden function. +* `ctrl-n`, `ctrl-p` keybinding for navigate histories (#1), thanks @zw963! +* `delete_after`, `delete_before` (`ctrl-k`, `ctrl-u`) (#2), thanks @zw963! +* `move_word_forward`, `move_word_backward` (`alt-f`/`ctrl-right`, `alt-b`/`ctrl-left`) (#2), thanks @zw963! +* `delete_word`, `word_back` (`alt-backspace`/`ctrl-backspace`, `alt-d`/`ctrl-delete`) (#2), thanks @zw963! +* `delete` or `eof` on `ctrl-d` (#2), thanks @zw963! +* Bind `ctrl-b`/`ctrl-f` with move cursor backward/forward (#2), thanks @zw963! + +### Bug fixs +* Fix ioctl window size magic number on darwin and bsd (#3), thanks @shinzlet! + +### Internal +* Refactor: move word functions (`delete_word`, `move_word_forward`, etc.) from `Reader` to the `ExpressionEditor`. +* Add this CHANGELOG. + + +## RELPy v0.1.0 +First version extracted from IC. + +### New features +* Multiline input +* History +* Pasting of large expressions +* Hook for Syntax highlighting +* Hook for Auto formatting +* Hook for Auto indentation +* Hook for Auto completion (Experimental) diff --git a/lib/reply/LICENSE b/lib/reply/LICENSE new file mode 100644 index 000000000000..4fe9a4e24252 --- /dev/null +++ b/lib/reply/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 I3oris + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/reply/README.md b/lib/reply/README.md new file mode 100644 index 000000000000..ae33523e4824 --- /dev/null +++ b/lib/reply/README.md @@ -0,0 +1,104 @@ +# REPLy + +REPLy is a shard that provide a term reader for a REPL (Read Eval Print Loop). + +## Features + +It includes the following features: +* Multiline input +* History +* Pasting of large expressions +* Hook for Syntax highlighting +* Hook for Auto formatting +* Hook for Auto indentation +* Hook for Auto completion (Experimental) +* Work on Windows 10 + +It doesn't support yet: +* History reverse i-search +* Customizable hotkeys +* Unicode characters + +NOTE: REPLy was extracted from https://github.com/I3oris/ic, it was first designed to fit exactly the usecase of a crystal interpreter, so don't hesitate to open an issue to make REPLy more generic and suitable for your project if needed. + +## Installation + +1. Add the dependency to your `shard.yml`: + + ```yaml + dependencies: + reply: + github: I3oris/reply + ``` + +2. Run `shards install` + +## Usage + +### Minimal example + +```crystal +require "reply" + +reader = Reply::Reader.new +reader.read_loop do |expression| + # Eval expression here + puts " => #{expression}" +end +``` + +### Customize the Interface + +```crystal +require "reply" + +class MyReader < Reply::Reader + def prompt(io : IO, line_number : Int32, color? : Bool) : Nil + # Display a custom prompt + end + + def highlight(expression : String) : String + # Highlight the expression + end + + def continue?(expression : String) : Bool + # Return whether the interface should continue on multiline, depending of the expression + end + + def format(expression : String) : String? + # Reformat when expression is submitted + end + + def indentation_level(expression_before_cursor : String) : Int32? + # Compute the indentation from the expression + end + + def save_in_history?(expression : String) : Bool + # Return whether the expression is saved in history + end + + def auto_complete(name_filter : String, expression : String) : {String, Array(String)} + # Return the auto-completion result from expression + end +end +``` + +## Similar Project +* [fancyline](https://github.com/Papierkorb/fancyline) +* [crystal-readline](https://github.com/crystal-lang/crystal-readline) + +## Development + +Free to pull request! + +## Contributing + +1. Fork it () +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request + +## Contributors + +- [I3oris](https://github.com/I3oris) - creator and maintainer diff --git a/lib/reply/examples/crystal_repl.cr b/lib/reply/examples/crystal_repl.cr new file mode 100644 index 000000000000..97cf3a1d88e7 --- /dev/null +++ b/lib/reply/examples/crystal_repl.cr @@ -0,0 +1,130 @@ +require "../src/reply" +require "crystal/syntax_highlighter/colorize" +require "compiler/crystal/tools/formatter" + +CRYSTAL_KEYWORD = %w( + abstract alias annotation asm begin break case class + def do else elsif end ensure enum extend for fun + if in include instance_sizeof lib macro module + next of offsetof out pointerof private protected require + rescue return select sizeof struct super + then type typeof union uninitialized unless until + verbatim when while with yield +) + +CONTINUE_ERROR = [ + "expecting identifier 'end', not 'EOF'", + "expecting token 'CONST', not 'EOF'", + "expecting any of these tokens: IDENT, CONST, `, <<, <, <=, ==, ===, !=, =~, !~, >>, >, >=, +, -, *, /, //, !, ~, %, &, |, ^, **, [], []?, []=, <=>, &+, &-, &*, &** (not 'EOF')", + "expecting any of these tokens: ;, NEWLINE (not 'EOF')", + "expecting token ')', not 'EOF'", + "expecting token ']', not 'EOF'", + "expecting token '}', not 'EOF'", + "expecting token '%}', not 'EOF'", + "expecting token '}', not ','", + "expected '}' or named tuple name, not EOF", + "unexpected token: NEWLINE", + "unexpected token: EOF", + "unexpected token: EOF (expecting when, else or end)", + "unexpected token: EOF (expecting ',', ';' or '\n')", + "Unexpected EOF on heredoc identifier", + "unterminated parenthesized expression", + "unterminated call", + "Unterminated string literal", + "unterminated hash literal", + "Unterminated command literal", + "unterminated array literal", + "unterminated tuple literal", + "unterminated macro", + "Unterminated string interpolation", + "invalid trailing comma in call", + "unknown token: '\\u{0}'", +] + +# `"`, `:`, `'`, are not a delimiter because symbols and strings are treated as one word. +# '=', !', '?' are not a delimiter because they could make part of method name. +WORD_DELIMITERS = {{" \n\t+-*/,;@&%<>^\\[](){}|.~".chars}} + +class CrystalReader < Reply::Reader + def prompt(io : IO, line_number : Int32, color? : Bool) : Nil + io << "crystal".colorize.blue.toggle(color?) + io << ':' + io << sprintf("%03d", line_number) + io << "> " + end + + def highlight(expression : String) : String + Crystal::SyntaxHighlighter::Colorize.highlight!(expression) + end + + def continue?(expression : String) : Bool + Crystal::Parser.new(expression).parse + false + rescue e : Crystal::CodeError + e.message.in? CONTINUE_ERROR + end + + def format(expression : String) : String? + Crystal.format(expression).chomp rescue nil + end + + def indentation_level(expression_before_cursor : String) : Int32? + parser = Crystal::Parser.new(expression_before_cursor) + parser.parse rescue nil + + parser.type_nest + parser.def_nest + parser.fun_nest + end + + def reindent_line(line) + case line.strip + when "end", ")", "]", "}" + 0 + when "else", "elsif", "rescue", "ensure", "in", "when" + -1 + else + nil + end + end + + def save_in_history?(expression : String) : Bool + !expression.blank? + end + + def history_file : Path | String | IO | Nil + "history.txt" + end + + def auto_complete(name_filter : String, expression : String) : {String, Array(String)} + return "Keywords:", CRYSTAL_KEYWORD.dup + end + + def auto_completion_display_title(io : IO, title : String) + io << title + end + + def auto_completion_display_selected_entry(io : IO, entry : String) + io << entry.colorize.red.bright + end + + def auto_completion_display_entry(io : IO, entry_matched : String, entry_remaining : String) + io << entry_matched.colorize.red.bright << entry_remaining + end +end + +reader = CrystalReader.new +reader.word_delimiters = WORD_DELIMITERS + +reader.read_loop do |expression| + case expression + when "clear_history" + reader.clear_history + when "reset" + reader.reset + when "exit" + break + when .presence + # Eval expression here + print " => " + puts Crystal::SyntaxHighlighter::Colorize.highlight!(expression) + end +end diff --git a/lib/reply/examples/repl.cr b/lib/reply/examples/repl.cr new file mode 100644 index 000000000000..200276ff8e11 --- /dev/null +++ b/lib/reply/examples/repl.cr @@ -0,0 +1,11 @@ +require "../src/reply" + +class MyReader < Reply::Reader +end + +reader = MyReader.new + +reader.read_loop do |expression| + # Eval expression here + puts " => #{expression}" +end diff --git a/lib/reply/shard.yml b/lib/reply/shard.yml new file mode 100644 index 000000000000..ae9551f95bdb --- /dev/null +++ b/lib/reply/shard.yml @@ -0,0 +1,14 @@ +name: reply +version: 0.3.0 +description: "Shard to create a REPL interface" + +authors: + - I3oris + +crystal: 1.5.0 + +license: MIT + +development_dependencies: + ameba: + github: crystal-ameba/ameba diff --git a/lib/reply/spec/auto_completion_spec.cr b/lib/reply/spec/auto_completion_spec.cr new file mode 100644 index 000000000000..1a826d91c7c8 --- /dev/null +++ b/lib/reply/spec/auto_completion_spec.cr @@ -0,0 +1,283 @@ +require "./spec_helper" + +RESULTS = { + "Int32", [ + "abs", "abs2", "bit", "bit_length", "bits", "bits_set?", "ceil", "chr", + "clamp", "class", "clone", "crystal_type_id", "day", "days", "digits", "divisible_by?", + "divmod", "downto", "dup", "even?", "fdiv", "floor", "format", "gcd", + "hash", "hour", "hours", "humanize", "humanize_bytes", "in?", "inspect", "itself", + "lcm", "leading_zeros_count", "microsecond", "microseconds", "millisecond", "milliseconds", "minute", "minutes", + "modulo", "month", "months", "nanosecond", "nanoseconds", "negative?", "not_nil!", "odd?", + "popcount", "positive?", "pred", "pretty_inspect", "pretty_print", "remainder", "round", "round_away", + "round_even", "second", "seconds", "sign", "significant", "step", "succ", "tap", + "tdiv", "times", "to", "to_f", "to_f!", "to_f32", "to_f32!", "to_f64", + "to_f64!", "to_i", "to_i!", "to_i128", "to_i128!", "to_i16", "to_i16!", "to_i32", + "to_i32!", "to_i64", "to_i64!", "to_i8", "to_i8!", "to_io", "to_s", "to_u", + "to_u!", "to_u128", "to_u128!", "to_u16", "to_u16!", "to_u32", "to_u32!", "to_u64", + "to_u64!", "to_u8", "to_u8!", "trailing_zeros_count", "trunc", "try", "unsafe_as", "unsafe_chr", + "unsafe_div", "unsafe_mod", "unsafe_shl", "unsafe_shr", "upto", "week", "weeks", "year", + "years", "zero?", "as", "as?", "is_a?", "nil?", "responds_to?", + ], +} + +module Reply + describe AutoCompletion do + describe "displays entries" do + it "for many entries" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("", "42.") + handler.open + handler.verify_display max_height: 5, + with_width: 40, + display: "Int32:\n" \ + "abs bits clamp \n" \ + "abs2 bits_set? class \n" \ + "bit ceil clone \n" \ + "bit_length chr crystal_type_id..\n", + height: 5 + end + + it "for many entries with larger screen" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("", "42.") + handler.open + handler.verify_display max_height: 5, + with_width: 54, + display: "Int32:\n" \ + "abs bits clamp \n" \ + "abs2 bits_set? class \n" \ + "bit ceil clone \n" \ + "bit_length chr crystal_type_id..\n", + height: 5 + handler.verify_display max_height: 5, + with_width: 55, + display: "Int32:\n" \ + "abs bits clamp day \n" \ + "abs2 bits_set? class days \n" \ + "bit ceil clone digits \n" \ + "bit_length chr crystal_type_id divisible_by?..\n", + height: 5 + end + + it "for many entries with higher screen" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("", "42.") + handler.open + handler.verify_display max_height: 5, + with_width: 40, + display: "Int32:\n" \ + "abs bits clamp \n" \ + "abs2 bits_set? class \n" \ + "bit ceil clone \n" \ + "bit_length chr crystal_type_id..\n", + height: 5 + handler.verify_display max_height: 6, + with_width: 40, + display: "Int32:\n" \ + "abs bits_set? clone \n" \ + "abs2 ceil crystal_type_id \n" \ + "bit chr day \n" \ + "bit_length clamp days \n" \ + "bits class digits.. \n", + height: 6 + end + + it "for few entries" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("ab", "42.") + handler.open + handler.verify_display max_height: 5, + with_width: 40, + display: "Int32:\n" \ + "abs \n" \ + "abs2 \n", + height: 3 + end + + it "when closed" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("", "42.") + handler.close + handler.verify_display max_height: 5, + with_width: 40, + display: "", + height: 0 + end + + it "when cleared" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("", "42.") + handler.clear + handler.verify_display max_height: 5, min_height: 3, + with_width: 40, + display: "\n\n\n", + height: 3 + handler.verify_display max_height: 5, min_height: 5, + with_width: 40, + display: "\n\n\n\n\n", + height: 5 + handler.verify_display max_height: 5, + with_width: 40, + display: "", + height: 0 + end + + it "when max height is zero" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("", "42.") + handler.open + handler.verify_display max_height: 0, + with_width: 40, + display: "", + height: 0 + end + + it "for no entry" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("___nop___", "42.") + handler.open + handler.verify_display max_height: 5, + with_width: 40, + display: "Int32:\n", + height: 1 + end + end + + describe "moves selection" do + it "selection next" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("", "42.") + handler.open + handler.verify_display max_height: 4, + with_width: 20, + display: "Int32:\n" \ + "abs bit_length \n" \ + "abs2 bits \n" \ + "bit bits_set?.. \n", + height: 4 + + handler.selection_next + handler.verify_display max_height: 4, + with_width: 20, + display: "Int32:\n" \ + ">abs bit_length \n" \ + "abs2 bits \n" \ + "bit bits_set?.. \n", + height: 4 + + 3.times { handler.selection_next } + handler.verify_display max_height: 4, + with_width: 20, + display: "Int32:\n" \ + "abs >bit_length \n" \ + "abs2 bits \n" \ + "bit bits_set?.. \n", + height: 4 + end + + it "selection next on next column" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("", "42.") + handler.open + 6.times { handler.selection_next } + handler.verify_display max_height: 4, + with_width: 20, + display: "Int32:\n" \ + "abs bit_length \n" \ + "abs2 bits \n" \ + "bit >bits_set?..\n", + height: 4 + + handler.selection_next + handler.verify_display max_height: 4, + with_width: 20, + display: "Int32:\n" \ + "bit_length >ceil \n" \ + "bits chr \n" \ + "bits_set? clamp..\n", + height: 4 + end + + it "selection previous" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("", "42.") + handler.open + 2.times { handler.selection_next } + handler.verify_display max_height: 4, + with_width: 20, + display: "Int32:\n" \ + "abs bit_length \n" \ + ">abs2 bits \n" \ + "bit bits_set?.. \n", + height: 4 + + handler.selection_previous + handler.verify_display max_height: 4, + with_width: 20, + display: "Int32:\n" \ + ">abs bit_length \n" \ + "abs2 bits \n" \ + "bit bits_set?.. \n", + height: 4 + + handler.selection_previous + handler.verify_display max_height: 4, + with_width: 20, + display: "Int32:\n" \ + "nil? \n" \ + ">responds_to? \n" \ + "\n", + height: 4 + end + end + + describe "name filter" do + it "changes" do + handler = SpecHelper.auto_completion(returning: RESULTS) + handler.complete_on("", "42.") + handler.open + handler.verify_display max_height: 5, + with_width: 40, + display: "Int32:\n" \ + "abs bits clamp \n" \ + "abs2 bits_set? class \n" \ + "bit ceil clone \n" \ + "bit_length chr crystal_type_id..\n", + height: 5 + handler.name_filter = "to" + + handler.verify_display max_height: 5, + with_width: 40, + display: "Int32:\n" \ + "to to_f32! to_i! to_i16! \n" \ + "to_f to_f64 to_i128 to_i32 \n" \ + "to_f! to_f64! to_i128! to_i32! \n" \ + "to_f32 to_i to_i16 to_i64.. \n", + height: 5 + + handler.name_filter = "to_nop" + handler.verify_display max_height: 5, + with_width: 40, + display: "Int32:\n", + height: 1 + + handler.name_filter = "to_" + handler.verify_display max_height: 5, + with_width: 40, + display: "Int32:\n" \ + "to_f to_f64 to_i128 to_i32 \n" \ + "to_f! to_f64! to_i128! to_i32! \n" \ + "to_f32 to_i to_i16 to_i64 \n" \ + "to_f32! to_i! to_i16! to_i64!..\n", + height: 5 + + handler.name_filter = "to_i32!" + handler.verify_display max_height: 5, + with_width: 40, + display: "Int32:\n" \ + "to_i32! \n", + height: 2 + end + end + end +end diff --git a/lib/reply/spec/char_reader_spec.cr b/lib/reply/spec/char_reader_spec.cr new file mode 100644 index 000000000000..268a16621901 --- /dev/null +++ b/lib/reply/spec/char_reader_spec.cr @@ -0,0 +1,74 @@ +require "./spec_helper" + +module Reply + describe CharReader do + it "read chars" do + reader = SpecHelper.char_reader + + reader.verify_read('a', expect: ['a']) + reader.verify_read("Hello", expect: ["Hello"]) + end + + it "read ANSI escape sequence" do + reader = SpecHelper.char_reader + + reader.verify_read("\e[A", expect: :up) + reader.verify_read("\e[B", expect: :down) + reader.verify_read("\e[C", expect: :right) + reader.verify_read("\e[D", expect: :left) + reader.verify_read("\e[3~", expect: :delete) + reader.verify_read("\e[3;5~", expect: :ctrl_delete) + reader.verify_read("\e[1;5A", expect: :ctrl_up) + reader.verify_read("\e[1;5B", expect: :ctrl_down) + reader.verify_read("\e[1;5C", expect: :ctrl_right) + reader.verify_read("\e[1;5D", expect: :ctrl_left) + reader.verify_read("\e[H", expect: :home) + reader.verify_read("\e[F", expect: :end) + reader.verify_read("\eOH", expect: :home) + reader.verify_read("\eOF", expect: :end) + reader.verify_read("\e[1~", expect: :home) + reader.verify_read("\e[4~", expect: :end) + + reader.verify_read("\e\t", expect: :shift_tab) + reader.verify_read("\e\r", expect: :alt_enter) + reader.verify_read("\e\u007f", expect: :alt_backspace) + reader.verify_read("\eb", expect: :alt_b) + reader.verify_read("\ed", expect: :alt_d) + reader.verify_read("\ef", expect: :alt_f) + reader.verify_read("\e", expect: :escape) + reader.verify_read("\t", expect: :tab) + + reader.verify_read('\0', expect: [] of CharReader::Sequence) + reader.verify_read('\t', expect: :tab) + reader.verify_read('\b', expect: :ctrl_backspace) + reader.verify_read('\u007F', expect: :backspace) + reader.verify_read('\u0001', expect: :ctrl_a) + reader.verify_read('\u0002', expect: :ctrl_b) + reader.verify_read('\u0003', expect: :ctrl_c) + reader.verify_read('\u0004', expect: :ctrl_d) + reader.verify_read('\u0005', expect: :ctrl_e) + reader.verify_read('\u0006', expect: :ctrl_f) + reader.verify_read('\u000b', expect: :ctrl_k) + reader.verify_read('\u000e', expect: :ctrl_n) + reader.verify_read('\u0010', expect: :ctrl_p) + reader.verify_read('\u0015', expect: :ctrl_u) + reader.verify_read('\u0018', expect: :ctrl_x) + + {% if flag?(:win32) %} + reader.verify_read('\n', expect: :ctrl_enter) + reader.verify_read('\r', expect: :enter) + {% else %} + reader.verify_read('\n', expect: :enter) + {% end %} + end + + it "read large buffer" do + reader = SpecHelper.char_reader(buffer_size: 1024) + + reader.verify_read( + "a"*10_000, + expect: ["a" * 1024]*9 + ["a"*(10_000 - 9*1024)] + ) + end + end +end diff --git a/lib/reply/spec/expression_editor_spec.cr b/lib/reply/spec/expression_editor_spec.cr new file mode 100644 index 000000000000..b37354a827d0 --- /dev/null +++ b/lib/reply/spec/expression_editor_spec.cr @@ -0,0 +1,434 @@ +require "./spec_helper" + +module Reply + describe ExpressionEditor do + it "computes expression_height" do + editor = SpecHelper.expression_editor + editor.current_line = ":Hi" + editor.expression_height.should eq 1 + + editor.current_line = "print :Hel" \ + "lo" + editor.expression_height.should eq 2 + + editor.current_line = ":at_edge__" + editor.expression_height.should eq 2 + + editor << "#{""}puts :this" \ + "symbol_is_a_too" \ + "_mutch_long_sym" \ + "bol" + editor.insert_new_line(indent: 0) + editor << ":with_a_ne" \ + "line" + + editor.expression_height.should eq 4 + 2 + end + + it "gives previous, current, and next line" do + editor = SpecHelper.expression_editor + editor << "aaa" + editor.previous_line?.should be_nil + editor.current_line.should eq "aaa" + editor.next_line?.should be_nil + + editor.insert_new_line(indent: 0) + editor << "bbb" + editor.insert_new_line(indent: 0) + editor << "ccc" + editor.move_cursor_up + + editor.previous_line?.should eq "aaa" + editor.current_line.should eq "bbb" + editor.next_line?.should eq "ccc" + end + + it "tells if cursor is on last line" do + editor = SpecHelper.expression_editor + editor.cursor_on_last_line?.should be_true + + editor << "aaa" + editor.insert_new_line(indent: 0) + editor.cursor_on_last_line?.should be_true + + editor << "bbb" + 2.times { editor.move_cursor_left } + editor.cursor_on_last_line?.should be_true + + editor.move_cursor_up + editor.cursor_on_last_line?.should be_false + end + + it "gets expression_before_cursor" do + editor = SpecHelper.expression_editor + editor << "print :Hel" \ + "lo" + editor.insert_new_line(indent: 0) + editor << "puts :Bye" + + editor.expression_before_cursor.should eq "print :Hello\nputs :Bye" + editor.expression_before_cursor(x: 0, y: 0).should eq "" + editor.expression_before_cursor(x: 9, y: 0).should eq "print :He" + end + + it "modify previous, current, and next line" do + editor = SpecHelper.expression_editor + editor << "aaa" + + editor.current_line = "AAA" + editor.verify("AAA", x: 3, y: 0) + + editor.insert_new_line(indent: 0) + editor << "bbb" + editor.insert_new_line(indent: 0) + editor << "ccc" + editor.move_cursor_up + + editor.previous_line = "AAA" + editor.current_line = "BBB" + editor.next_line = "CCC" + + editor.verify("AAA\nBBB\nCCC", x: 3, y: 1) + end + + it "deletes line" do + editor = SpecHelper.expression_editor + editor << "aaa\nbbb\nccc\n" + + editor.delete_line(1) + editor.verify("aaa\nccc\n") + + editor.delete_line(0) + editor.verify("ccc\n") + + editor.delete_line(1) + editor.verify("ccc") + + editor.delete_line(0) + editor.verify("") + end + + it "clears expression" do + editor = SpecHelper.expression_editor + editor << "aaa\nbbb\nccc\n" + editor.clear_expression + editor.expression.should be_empty + end + + it "insert chars" do + editor = SpecHelper.expression_editor + editor << 'a' + editor.verify("a", x: 1, y: 0) + + editor.move_cursor_left + editor << 'b' + editor.verify("ba", x: 1, y: 0) + + editor << '\n' + editor.verify("b\na", x: 0, y: 1) + end + + it "ignores control characters" do + editor = SpecHelper.expression_editor + editor << "abc" + + editor << '\b' + editor.verify("abc", x: 3, y: 0) + + editor << '\u007F' << '\u0002' + editor.verify("abc", x: 3, y: 0) + + editor << "def\u007F\b\eghi\u0007" + editor.verify("abcdefghi", x: 9, y: 0) + end + + it "inserts new line" do + editor = SpecHelper.expression_editor + editor << "aaa" + editor.insert_new_line(indent: 1) + editor.verify("aaa\n ", x: 2, y: 1) + + editor << "bbb" + editor.move_cursor_up + editor.insert_new_line(indent: 5) + editor.insert_new_line(indent: 0) + editor.verify("aaa\n \n\n bbb", x: 0, y: 2) + end + + it "does delete & back" do + editor = SpecHelper.expression_editor + editor << "abc\n\ndef\nghi" + editor.move_cursor_up + 2.times { editor.move_cursor_left } + + editor.delete + editor.verify("abc\n\ndf\nghi", x: 1, y: 2) + + editor.back + editor.verify("abc\n\nf\nghi", x: 0, y: 2) + + editor.back + editor.verify("abc\nf\nghi", x: 0, y: 1) + + editor.back + editor.verify("abcf\nghi", x: 3, y: 0) + + editor.delete + editor.verify("abc\nghi", x: 3, y: 0) + + editor.delete + editor.verify("abcghi", x: 3, y: 0) + end + + # Empty interpolations `#{""}` are used to better show the influence of the prompt to line wrapping. '#{""}' takes 5 characters, like the prompt used for this spec: 'p:00>'. + it "moves cursor left" do + editor = SpecHelper.expression_editor + editor << "#{""}print :Hel" \ + "lo\n" + editor << "#{""}print :loo" \ + "ooooooooooooooo" \ + "oooooong\n" + editor << "#{""}:end" + + editor.verify(x: 4, y: 2) + + 4.times { editor.move_cursor_left } + editor.verify(x: 0, y: 2) + + editor.move_cursor_left + editor.verify(x: 33, y: 1) + + 34.times { editor.move_cursor_left } + editor.verify(x: 12, y: 0) + + 20.times { editor.move_cursor_left } + editor.verify(x: 0, y: 0, scroll_offset: 1) + end + + # Empty interpolations `#{""}` are used to better show the influence of the prompt to line wrapping. '#{""}' takes 5 characters, like the prompt used for this spec: 'p:00>'. + it "moves cursor right" do + editor = SpecHelper.expression_editor + editor << "#{""}print :Hel" \ + "lo\n" + editor << "#{""}print :loo" \ + "ooooooooooooooo" \ + "oooooong\n" + editor << "#{""}:end" + editor.move_cursor_to_begin + + 12.times { editor.move_cursor_right } + editor.verify(x: 12, y: 0, scroll_offset: 1) + + editor.move_cursor_right + editor.verify(x: 0, y: 1, scroll_offset: 1) + + 34.times { editor.move_cursor_right } + editor.verify(x: 0, y: 2) + + 10.times { editor.move_cursor_right } + editor.verify(x: 4, y: 2) + end + + # Empty interpolations `#{""}` are used to better show the influence of the prompt to line wrapping. '#{""}' takes 5 characters, like the prompt used for this spec: 'p:00>'. + it "moves cursor up" do + editor = SpecHelper.expression_editor + editor << "#{""}print :Hel" \ + "lo\n" + editor << "#{""}print :loo" \ + "ooooooooooooooo" \ + "oooooong\n" + editor << "#{""}:end" + + editor.verify(x: 4, y: 2) + + editor.move_cursor_up + editor.verify(x: 33, y: 1) + + editor.move_cursor_up + editor.verify(x: 18, y: 1) + + editor.move_cursor_up + editor.verify(x: 3, y: 1) + + editor.move_cursor_up + editor.verify(x: 12, y: 0) + + editor.move_cursor_up + editor.verify(x: 0, y: 0, scroll_offset: 1) + end + + # Empty interpolations `#{""}` are used to better show the influence of the prompt to line wrapping. '#{""}' takes 5 characters, like the prompt used for this spec: 'p:00>'. + it "moves cursor down" do + editor = SpecHelper.expression_editor + editor << "#{""}print :Hel" \ + "lo\n" + editor << "#{""}print :loo" \ + "ooooooooooooooo" \ + "oooooong\n" + editor << "#{""}:end" + editor.move_cursor_to_begin + + editor.move_cursor_down + editor.verify(x: 12, y: 0, scroll_offset: 1) + + editor.move_cursor_down + editor.verify(x: 0, y: 1, scroll_offset: 1) + + editor.move_cursor_down + editor.verify(x: 15, y: 1, scroll_offset: 1) + + editor.move_cursor_down + editor.verify(x: 30, y: 1, scroll_offset: 1) + + editor.move_cursor_down + editor.verify(x: 0, y: 2, scroll_offset: 0) + end + + it "moves cursor to" do + editor = SpecHelper.expression_editor + editor << "#{""}print :Hel" \ + "lo\n" + editor << "#{""}print :loo" \ + "ooooooooooooooo" \ + "oooooong\n" + editor << "#{""}:end" + editor.move_cursor_to(x: 0, y: 1, allow_scrolling: false) + editor.verify(x: 0, y: 1) + + editor.move_cursor_to(x: 3, y: 2, allow_scrolling: false) + editor.verify(x: 3, y: 2) + + editor.move_cursor_to_begin(allow_scrolling: false) + editor.verify(x: 0, y: 0) + + editor.move_cursor_to(x: 21, y: 1, allow_scrolling: false) + editor.verify(x: 21, y: 1) + + editor.move_cursor_to(x: 12, y: 0, allow_scrolling: false) + editor.verify(x: 12, y: 0) + + editor.move_cursor_to_end(allow_scrolling: false) + editor.verify(x: 4, y: 2) + + editor.move_cursor_to_end_of_line(y: 0, allow_scrolling: false) + editor.verify(x: 12, y: 0) + + editor.move_cursor_to_end_of_line(y: 1, allow_scrolling: false) + editor.verify(x: 33, y: 1) + end + + it "replaces" do + editor = SpecHelper.expression_editor + editor << "aaa\nbbb\nccc" + + editor.replace(["AAA", "BBBCCC"]) + editor.verify("AAA\nBBBCCC", x: 3, y: 1) + editor.lines.should eq ["AAA", "BBBCCC"] + + editor.replace(["aaa", "", "ccc", "ddd"]) + editor.verify("aaa\n\nccc\nddd", x: 0, y: 1) + editor.lines.should eq ["aaa", "", "ccc", "ddd"] + + editor.replace([""]) + editor.verify("", x: 0, y: 0) + editor.lines.should eq [""] + end + + it "ends editing" do + editor = SpecHelper.expression_editor + editor << "aaa" + + editor.end_editing + editor.verify("aaa", x: 3, y: 0) + + editor.end_editing(["aaa", "bbb", "ccc"]) + editor.verify("aaa\nbbb\nccc", x: 3, y: 2) + end + + it "prompts next" do + editor = SpecHelper.expression_editor + editor << "aaa" + + editor.prompt_next + editor.verify("", x: 0, y: 0) + end + + it "scroll up and down" do + editor = SpecHelper.expression_editor + editor << "#{""}print :Hel" \ + "lo\n" + editor << "#{""}print :Wor" \ + "ld\n" + editor << "#{""}print :loo" \ + "ooooooooooooooo" \ + "oooooong\n" + editor << "#{""}:end" + + 4.times { editor.move_cursor_up } + editor.expression_height.should eq 8 + + editor.verify(x: 12, y: 1, scroll_offset: 0) + + editor.scroll_up + editor.verify(x: 12, y: 1, scroll_offset: 1) + + editor.scroll_down + editor.verify(x: 12, y: 1, scroll_offset: 0) + + editor.scroll_down + editor.verify(x: 12, y: 1, scroll_offset: 0) + + editor.scroll_up + editor.scroll_up + editor.scroll_up + editor.verify(x: 12, y: 1, scroll_offset: 3) + + editor.scroll_up + editor.verify(x: 12, y: 1, scroll_offset: 3) + + editor.scroll_down + editor.verify(x: 12, y: 1, scroll_offset: 2) + end + + it "is aligned when prompt size change" do + editor = ExpressionEditor.new do |line_number, _color?| + "*" * line_number + ">" # A prompt that increase its size at each line + end + editor.output = IO::Memory.new + editor.color = false + editor.height = 50 + editor.width = 15 + + editor.update { editor << '1' } + editor.verify_output "\e[1G\e[J" + <<-END + >1 + END + + editor.update { editor << '\n' << '2' } + + editor.output = IO::Memory.new # Reset the output because we want verify only the last update + editor.update # It currently need an extra update for the alignment to be taken in account. + editor.verify_output "\e[1A\e[1G\e[J" \ + "> 1\n" \ + "*>2" + + editor.update do + editor << '\n' << '3' + editor << '\n' << '4' + editor << '\n' << '5' + end + + editor.output = IO::Memory.new + editor.update + editor.verify_output "\e[4A\e[1G\e[J" \ + "> 1\n" \ + "*> 2\n" \ + "**> 3\n" \ + "***> 4\n" \ + "****>5" + end + + # TODO: + # header + end +end diff --git a/lib/reply/spec/history_spec.cr b/lib/reply/spec/history_spec.cr new file mode 100644 index 000000000000..d553ea057e42 --- /dev/null +++ b/lib/reply/spec/history_spec.cr @@ -0,0 +1,178 @@ +require "./spec_helper" + +module Reply + ENTRIES = [ + [%(puts "Hello World")], + [%(i = 0)], + [ + %(while i < 10), + %( puts i), + %( i += 1), + %(end), + ], + ] + + describe History do + it "submits entry" do + history = SpecHelper.history + + history.verify([] of Array(String), index: 0) + + history << [%(puts "Hello World")] + history.verify(ENTRIES[0...1], index: 1) + + history << [%(i = 0)] + history.verify(ENTRIES[0...2], index: 2) + + history << [ + %(while i < 10), + %( puts i), + %( i += 1), + %(end), + ] + history.verify(ENTRIES, index: 3) + end + + it "submit dupplicate entry" do + history = SpecHelper.history(with: ENTRIES) + + history.verify(ENTRIES, index: 3) + + history << [%(i = 0)] + history.verify([ENTRIES[0], ENTRIES[2], ENTRIES[1]], index: 3) + end + + it "clears" do + history = SpecHelper.history(with: ENTRIES) + + history.clear + history.verify([] of Array(String), index: 0) + end + + it "navigates" do + history = SpecHelper.history(with: ENTRIES) + + history.verify(ENTRIES, index: 3) + + # Before down: current edition... + # After down: current edition... + history.down(["current edition..."]).should be_nil + history.verify(ENTRIES, index: 3) + + # Before up: current edition... + # After up: while i < 10 + # puts i + # i += 1 + # end + history.up(["current edition..."]).should eq ENTRIES[2] + history.verify(ENTRIES, index: 2) + + # Before up: while i < 10 + # puts i + # i += 1 + # end + # After up: i = 0 + history.up(ENTRIES[2]).should eq ENTRIES[1] + history.verify(ENTRIES, index: 1) + + # Before up (edited): edited_i = 0 + # After up: puts "Hello World" + history.up([%(edited_i = 0)]).should eq ENTRIES[0] + history.verify(ENTRIES, index: 0) + + # Before up: puts "Hello World" + # After up: puts "Hello World" + history.up(ENTRIES[0]).should be_nil + history.verify(ENTRIES, index: 0) + + # Before down: puts "Hello World" + # After down: edited_i = 0 + history.down(ENTRIES[0]).should eq [%(edited_i = 0)] + history.verify(ENTRIES, index: 1) + + # Before down down: edited_i = 0 + # After down down: current edition... + history.down([%(edited_i = 0)]).should eq ENTRIES[2] + history.down(ENTRIES[2]).should eq [%(current edition...)] + history.verify(ENTRIES, index: 3) + end + + it "saves and loads" do + entries = ([ + [%(foo)], + [%q(\)], + [ + %q(bar), + %q("baz" \), + %q("\n\\"), + %q(), + %q(\), + ], + [%q(a\\\b)], + ]) + history = SpecHelper.history(with: entries) + + io = IO::Memory.new + history.save(io) + io.to_s.should eq( + %q(foo) + "\n" + + %q(\\) + "\n" + + %q(bar\) + "\n" + + %q("baz" \\\) + "\n" + + %q("\\n\\\\"\) + "\n" + + %q(\) + "\n" + + %q(\\) + "\n" + + %q(a\\\\\\b) + ) + + io.rewind + history.load(io) + + history.verify(entries, index: 4) + end + + it "saves and loads empty" do + history = SpecHelper.history + + io = IO::Memory.new + history.save(io) + io.to_s.should be_empty + + io.rewind + history.load(io) + + history.verify([] of Array(String), index: 0) + end + + it "respects max size" do + history = SpecHelper.history + + history.max_size = 4 + + history << ["1"] + history << ["2"] + history << ["3"] + history << ["4"] + history.verify([["1"], ["2"], ["3"], ["4"]], index: 4) + + history << ["5"] + history.verify([["2"], ["3"], ["4"], ["5"]], index: 4) + + history.max_size = 2 + history << ["6"] + history.verify([["5"], ["6"]], index: 2) + + history.max_size = 0 # minimum possible is 1 + history << ["7"] + history.verify([["7"]], index: 1) + + history.max_size = -314 # minimum possible is 1 + history << ["8"] + history.verify([["8"]], index: 1) + + history.max_size = 3 + history << ["9"] + history.verify([["8"], ["9"]], index: 2) + end + end +end diff --git a/lib/reply/spec/reader_spec.cr b/lib/reply/spec/reader_spec.cr new file mode 100644 index 000000000000..4e9f446f3de0 --- /dev/null +++ b/lib/reply/spec/reader_spec.cr @@ -0,0 +1,613 @@ +{% skip_file if flag?(:win32) %} +# FIXME: We skip all these specs because the file descriptor is blocking, making +# the spec to hang out, and we cannot change it. # (`blocking=` is not implemented on window) + +require "./spec_helper" + +module Reply + describe Reader do + it "reads char" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out).should eq "a" + reader.read_next(from: pipe_out).should eq "♥💎" + end + + SpecHelper.send(pipe_in, 'a') + SpecHelper.send(pipe_in, '\n') + SpecHelper.send(pipe_in, '♥') + SpecHelper.send(pipe_in, '💎') + SpecHelper.send(pipe_in, '\n') + end + + it "reads string" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out).should eq "Hello" + reader.read_next(from: pipe_out).should eq "class Foo\n def foo\n 42\n end\nend" + end + + SpecHelper.send(pipe_in, "Hello") + SpecHelper.send(pipe_in, '\n') + + SpecHelper.send(pipe_in, <<-END) + class Foo + def foo + 42 + end + end + END + SpecHelper.send(pipe_in, '\n') + end + + it "uses directional arrows" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, <<-END) + class Foo + def foo + 42 + end + end + END + SpecHelper.send(pipe_in, "\e[A") # up + SpecHelper.send(pipe_in, "\e[C") # right + SpecHelper.send(pipe_in, "\e[B") # down + SpecHelper.send(pipe_in, "\e[D") # left + reader.editor.verify(x: 2, y: 4) + + SpecHelper.send(pipe_in, '\0') + end + + it "uses ctrl-n & ctrl-p" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + reader.read_next(from: pipe_out) + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, "x = 42") + SpecHelper.send(pipe_in, '\n') + SpecHelper.send(pipe_in, <<-END) + puts "Hello", + "World" + END + SpecHelper.send(pipe_in, '\n') + + SpecHelper.send(pipe_in, '\u0010') # ctrl-p (up) + reader.editor.verify(%(puts "Hello",\n "World")) + + SpecHelper.send(pipe_in, '\u0010') # ctrl-p (up) + SpecHelper.send(pipe_in, '\u0010') # ctrl-p (up) + reader.editor.verify("x = 42") + + SpecHelper.send(pipe_in, '\u000e') # ctrl-n (down) + reader.editor.verify(%(puts "Hello",\n "World")) + + SpecHelper.send(pipe_in, '\0') + end + + it "uses ctrl-f & ctrl-b" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, "x=42") + reader.editor.verify(x: 4, y: 0) + + SpecHelper.send(pipe_in, '\u0006') # ctrl-f (right) + reader.editor.verify(x: 4, y: 0) + + SpecHelper.send(pipe_in, '\u0002') # ctrl-b (left) + reader.editor.verify(x: 3, y: 0) + + SpecHelper.send(pipe_in, '\u0002') # ctrl-b (left) + SpecHelper.send(pipe_in, '\u0002') # ctrl-b (left) + SpecHelper.send(pipe_in, '\u0002') # ctrl-b (left) + reader.editor.verify(x: 0, y: 0) + + SpecHelper.send(pipe_in, '\u0002') # ctrl-b (left) + reader.editor.verify(x: 0, y: 0) + + SpecHelper.send(pipe_in, '\u0006') # ctrl-f (right) + reader.editor.verify(x: 1, y: 0) + + SpecHelper.send(pipe_in, '\0') + end + + it "uses back" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out).should eq "Hey" + reader.read_next(from: pipe_out).should eq "ab" + reader.read_next(from: pipe_out).should eq "" + end + + SpecHelper.send(pipe_in, "Hello") + SpecHelper.send(pipe_in, '\u{7f}') # back + SpecHelper.send(pipe_in, '\u{7f}') + SpecHelper.send(pipe_in, '\u{7f}') + SpecHelper.send(pipe_in, 'y') + SpecHelper.send(pipe_in, '\n') + + SpecHelper.send(pipe_in, "a\nb") + SpecHelper.send(pipe_in, "\e[D") # left + SpecHelper.send(pipe_in, '\u{7f}') + SpecHelper.send(pipe_in, '\n') + + SpecHelper.send(pipe_in, "") + SpecHelper.send(pipe_in, '\u{7f}') + SpecHelper.send(pipe_in, '\n') + end + + it "deletes" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out).should eq "Hey" + reader.read_next(from: pipe_out).should eq "ab" + reader.read_next(from: pipe_out).should eq "" + end + + SpecHelper.send(pipe_in, "Hello") + SpecHelper.send(pipe_in, "\e[D") # left + SpecHelper.send(pipe_in, "\e[D") + SpecHelper.send(pipe_in, "\e[D") + SpecHelper.send(pipe_in, "\e[3~") # delete + SpecHelper.send(pipe_in, "\e[3~") + SpecHelper.send(pipe_in, "\e[3~") + SpecHelper.send(pipe_in, 'y') + SpecHelper.send(pipe_in, '\n') + + SpecHelper.send(pipe_in, "a\nb") + SpecHelper.send(pipe_in, "\e[D") + SpecHelper.send(pipe_in, "\e[D") + SpecHelper.send(pipe_in, "\e[3~") + SpecHelper.send(pipe_in, '\n') + + SpecHelper.send(pipe_in, "") + SpecHelper.send(pipe_in, "\e[3~") + SpecHelper.send(pipe_in, '\n') + end + + it "deletes or eof" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + channel = Channel(Symbol).new + spawn do + reader.read_next(from: pipe_out).should be_nil + channel.send(:finished) + end + + SpecHelper.send(pipe_in, "a\nb") + SpecHelper.send(pipe_in, '\u0001') # ctrl-a (move cursor to begin) + reader.editor.verify("a\nb") + + SpecHelper.send(pipe_in, '\u0004') # ctrl-d (delete or eof) + reader.editor.verify("\nb") + + SpecHelper.send(pipe_in, '\u0004') # ctrl-d (delete or eof) + reader.editor.verify("b") + + SpecHelper.send(pipe_in, '\u0004') # ctrl-d (delete or eof) + reader.editor.verify("") + + SpecHelper.send(pipe_in, '\u0004') # ctrl-d (delete or eof) + channel.receive.should eq :finished + end + + it "uses tabulation" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, "42.") + reader.auto_completion.verify(open: false) + + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(hello world hey)) + reader.editor.verify("42.") + + SpecHelper.send(pipe_in, 'w') + reader.auto_completion.verify(open: true, entries: %w(world), name_filter: "w") + reader.editor.verify("42.w") + + SpecHelper.send(pipe_in, '\u{7f}') # back + reader.auto_completion.verify(open: true, entries: %w(hello world hey)) + reader.editor.verify("42.") + + SpecHelper.send(pipe_in, 'h') + reader.auto_completion.verify(open: true, entries: %w(hello hey), name_filter: "h") + reader.editor.verify("42.h") + + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(hello hey), name_filter: "h", selection_pos: 0) + reader.editor.verify("42.hello") + + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(hello hey), name_filter: "h", selection_pos: 1) + reader.editor.verify("42.hey") + + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(hello hey), name_filter: "h", selection_pos: 0) + reader.editor.verify("42.hello") + + SpecHelper.send(pipe_in, "\e\t") # shit_tab + reader.auto_completion.verify(open: true, entries: %w(hello hey), name_filter: "h", selection_pos: 1) + reader.editor.verify("42.hey") + + SpecHelper.send(pipe_in, '\u{7f}') # back + SpecHelper.send(pipe_in, 'l') + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(hello), name_filter: "hel", selection_pos: 0) + reader.editor.verify("42.hello") + + SpecHelper.send(pipe_in, ' ') + reader.auto_completion.verify(open: false, cleared: true) + reader.editor.verify("42.hello ") + + SpecHelper.send(pipe_in, '\0') + end + + it "roll over auto completion entries with equal" do + reader = SpecHelper.reader(SpecReaderWithEqual) + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(hello world= hey)) + reader.editor.verify("") + + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(hello world= hey), selection_pos: 0) + reader.editor.verify("hello") + + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(hello world= hey), selection_pos: 1) + reader.editor.verify("world=") + + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(hello world= hey), selection_pos: 2) + reader.editor.verify("hey") + + SpecHelper.send(pipe_in, '\0') + end + + it "uses escape" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, "42.") + reader.auto_completion.verify(open: false) + + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(hello world hey)) + + SpecHelper.send(pipe_in, '\e') # escape + reader.auto_completion.verify(open: false) + end + + it "uses alt-enter" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out).should eq "Hello\nWorld" + end + + SpecHelper.send(pipe_in, "Hello") + SpecHelper.send(pipe_in, "\e\r") # alt-enter + SpecHelper.send(pipe_in, "World") + reader.editor.verify("Hello\nWorld") + SpecHelper.send(pipe_in, "\n") + end + + it "uses ctrl-c" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out).should be_nil + end + + SpecHelper.send(pipe_in, "Hello") + SpecHelper.send(pipe_in, '\u{3}') # ctrl-c + reader.editor.verify("") + + SpecHelper.send(pipe_in, '\0') + end + + it "uses ctrl-d & ctrl-x" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out).should be_nil + reader.read_next(from: pipe_out).should be_nil + end + + SpecHelper.send(pipe_in, "Hello") + SpecHelper.send(pipe_in, '\u{4}') # ctrl-d + + SpecHelper.send(pipe_in, "World") + SpecHelper.send(pipe_in, '\u{24}') # ctrl-x + end + + it "uses ctrl-u & ctrl-k" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, <<-END) + Lorem ipsum + dolor sit + amet. + END + SpecHelper.send(pipe_in, '\u0010') # ctrl-p (up) + reader.editor.verify(x: 5, y: 1) + + SpecHelper.send(pipe_in, '\u000b') # ctrl-k (delete after) + reader.editor.verify(<<-END, x: 5, y: 1) + Lorem ipsum + dolor + amet. + END + + SpecHelper.send(pipe_in, '\u000b') # ctrl-k (delete after) + reader.editor.verify(<<-END, x: 5, y: 1) + Lorem ipsum + doloramet. + END + + SpecHelper.send(pipe_in, '\u0015') # ctrl-u (delete before) + reader.editor.verify(<<-END, x: 0, y: 1) + Lorem ipsum + amet. + END + + SpecHelper.send(pipe_in, '\u000b') # ctrl-k (delete after) + reader.editor.verify(<<-END, x: 0, y: 1) + Lorem ipsum + + END + SpecHelper.send(pipe_in, '\u0015') # ctrl-u (delete before) + SpecHelper.send(pipe_in, '\u0015') # ctrl-u (delete before) + reader.editor.verify("", x: 0, y: 0) + + SpecHelper.send(pipe_in, '\u000b') # ctrl-k (delete after) + SpecHelper.send(pipe_in, '\u0015') # ctrl-u (delete before) + reader.editor.verify("", x: 0, y: 0) + end + + it "moves word forward" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, <<-END) + lorem ipsum + +"dolor", sit: + amet() + END + + SpecHelper.send(pipe_in, '\u0001') # ctrl-a (move cursor to begin) + reader.editor.verify(x: 0, y: 0) + + SpecHelper.send(pipe_in, "\ef") # Alt-f (move_word_forward) + reader.editor.verify(x: 5, y: 0) + + SpecHelper.send(pipe_in, "\ef") # Alt-f (move_word_forward) + reader.editor.verify(x: 13, y: 0) + + SpecHelper.send(pipe_in, "\e[1;5C") # Ctrl-right (move_word_forward) + reader.editor.verify(x: 7, y: 1) + + SpecHelper.send(pipe_in, "\e[1;5C") # Ctrl-right (move_word_forward) + reader.editor.verify(x: 13, y: 1) + + SpecHelper.send(pipe_in, "\e[1;5C") # Ctrl-right (move_word_forward) + reader.editor.verify(x: 14, y: 1) + + SpecHelper.send(pipe_in, "\ef") # Alt-f (move_word_forward) + reader.editor.verify(x: 4, y: 2) + + SpecHelper.send(pipe_in, "\ef") # Alt-f (move_word_forward) + reader.editor.verify(x: 6, y: 2) + + SpecHelper.send(pipe_in, "\ef") # Alt-f (move_word_forward) + reader.editor.verify(x: 6, y: 2) + + SpecHelper.send(pipe_in, "\0") + end + + it "moves word backward" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, <<-END) + lorem ipsum + +"dolor", sit: + amet() + END + + reader.editor.verify(x: 6, y: 2) + + SpecHelper.send(pipe_in, "\eb") # Alt-b (move_word_backward) + reader.editor.verify(x: 0, y: 2) + + SpecHelper.send(pipe_in, "\eb") # Alt-b (move_word_backward) + reader.editor.verify(x: 10, y: 1) + + SpecHelper.send(pipe_in, "\e[1;5D") # Ctrl-left (move_word_backward) + reader.editor.verify(x: 2, y: 1) + + SpecHelper.send(pipe_in, "\e[1;5D") # Ctrl-left (move_word_backward) + reader.editor.verify(x: 0, y: 1) + + SpecHelper.send(pipe_in, "\e[1;5D") # Ctrl-left (move_word_backward) + reader.editor.verify(x: 8, y: 0) + + SpecHelper.send(pipe_in, "\eb") # Alt-b (move_word_backward) + reader.editor.verify(x: 0, y: 0) + + SpecHelper.send(pipe_in, "\eb") # Alt-b (move_word_backward) + reader.editor.verify(x: 0, y: 0) + + SpecHelper.send(pipe_in, "\0") + end + + it "uses delete word and word back" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, <<-END) + lorem ipsum + +"dolor", sit: + amet() + END + + SpecHelper.send(pipe_in, "\e[A") # up + reader.editor.verify(x: 6, y: 1) + + SpecHelper.send(pipe_in, '\b') # Ctrl-backspace (delete_word) + reader.editor.verify(<<-END, x: 2, y: 1) + lorem ipsum + +"r", sit: + amet() + END + + SpecHelper.send(pipe_in, '\b') # Ctrl-backspace (delete_word) + reader.editor.verify(<<-END, x: 0, y: 1) + lorem ipsum + r", sit: + amet() + END + + SpecHelper.send(pipe_in, '\b') # Ctrl-backspace (delete_word) + reader.editor.verify(<<-END, x: 8, y: 0) + lorem r", sit: + amet() + END + + SpecHelper.send(pipe_in, "\ed") # Alt-d (word_back) + reader.editor.verify(<<-END, x: 8, y: 0) + lorem ", sit: + amet() + END + + SpecHelper.send(pipe_in, "\ed") # Alt-d (word_back) + SpecHelper.send(pipe_in, "\e[3;5~") # Ctrl-delete (word_back) + SpecHelper.send(pipe_in, "\e[3;5~") # Ctrl-delete (word_back) + reader.editor.verify(<<-END, x: 8, y: 0) + lorem () + END + + SpecHelper.send(pipe_in, '\b') # Ctrl-backspace (delete_word) + SpecHelper.send(pipe_in, "\e\u007f") # Alt-backspace (delete_word) + reader.editor.verify(<<-END, x: 0, y: 0) + () + END + + SpecHelper.send(pipe_in, "\ed") # Alt-d (word_back) + SpecHelper.send(pipe_in, "\ed") # Alt-d (word_back) + reader.editor.verify("", x: 0, y: 0) + + SpecHelper.send(pipe_in, "\0") + end + + it "sets history to last after empty entry" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out).should eq "a" + reader.read_next(from: pipe_out).should eq "b" + reader.read_next(from: pipe_out).should eq "" + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, 'a') + SpecHelper.send(pipe_in, '\n') + SpecHelper.send(pipe_in, 'b') + SpecHelper.send(pipe_in, '\n') + + SpecHelper.send(pipe_in, "\e[A") # up + reader.editor.verify("b") + SpecHelper.send(pipe_in, "\e[A") # up + reader.editor.verify("a") + + SpecHelper.send(pipe_in, "\u{7f}") # back + SpecHelper.send(pipe_in, '\n') + reader.editor.verify("") + + SpecHelper.send(pipe_in, "\e[A") # up + reader.editor.verify("b") + SpecHelper.send(pipe_in, "\e[A") # up + reader.editor.verify("a") + + SpecHelper.send(pipe_in, '\0') + end + + it "resets" do + reader = SpecHelper.reader + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, "Hello\nWorld") + SpecHelper.send(pipe_in, '\n') + reader.line_number.should eq 3 + + reader.reset + reader.line_number.should eq 1 + + SpecHelper.send(pipe_in, '\0') + end + end +end diff --git a/lib/reply/spec/spec_helper.cr b/lib/reply/spec/spec_helper.cr new file mode 100644 index 000000000000..432220b98f98 --- /dev/null +++ b/lib/reply/spec/spec_helper.cr @@ -0,0 +1,141 @@ +require "spec" +require "../src/reply" + +module Reply + class AutoCompletion + def verify(open, entries = [] of String, name_filter = "", cleared = false, selection_pos = nil) + self.open?.should eq open + self.cleared?.should eq cleared + self.name_filter.should eq name_filter + self.entries.should eq entries + @selection_pos.should eq selection_pos + end + + def verify_display(max_height, min_height, with_width, display, height) + height_got = nil + + display_got = String.build do |io| + height_got = self.display_entries(io, color?: false, width: with_width, max_height: max_height, min_height: min_height) + end + display_got.should eq display + height_got.should eq height + (display_got.split("\n").size - 1).should eq height + end + + def verify_display(max_height, with_width, display, height) + verify_display(max_height, 0, with_width, display, height) + end + end + + class ExpressionEditor + def verify(expression : String) + self.expression.should eq expression + end + + def verify(x : Int32, y : Int32, scroll_offset = 0) + {self.x, self.y}.should eq({x, y}) + @scroll_offset.should eq scroll_offset + end + + def verify(expression : String, x : Int32, y : Int32, scroll_offset = 0) + self.verify(expression) + self.verify(x, y, scroll_offset) + end + + def verify_output(output) + self.output.to_s.should eq output + end + end + + class History + def verify(entries, index) + @history.should eq Deque(Array(String)).new(entries) + @index.should eq index + end + end + + struct CharReader + def verify_read(to_read, expect : CharReader::Sequence) + verify_read(to_read, [expect]) + end + + def verify_read(to_read, expect : Array) + chars = [] of Char | CharReader::Sequence | String? + io = IO::Memory.new + io << to_read + io.rewind + loop do + c = self.read_char(io) + break if c == CharReader::Sequence::EOF + chars << c + end + chars.should eq expect + end + end + + class SpecReader < Reader + def auto_complete(current_word : String, expression_before : String) + return "title", %w(hello world hey) + end + + getter auto_completion + end + + class SpecReaderWithEqual < Reader + def initialize + super + self.word_delimiters = {{" \n\t+-*/,;@&%<>^\\[](){}|.~".chars}} + end + + def auto_complete(current_word : String, expression_before : String) + return "title", %w(hello world= hey) + end + + getter auto_completion + end + + module SpecHelper + def self.auto_completion(returning results) + results = results.clone + AutoCompletion.new do + results + end + end + + def self.expression_editor + editor = ExpressionEditor.new do |line_number, _color?| + # Prompt size = 5 + "p:#{sprintf("%02d", line_number)}>" + end + editor.output = IO::Memory.new + editor.color = false + editor.height = 5 + editor.width = 15 + editor + end + + def self.history(with entries = [] of Array(String)) + history = History.new + entries.each { |e| history << e } + history + end + + def self.char_reader(buffer_size = 64) + CharReader.new(buffer_size) + end + + def self.reader(type = SpecReader) + reader = type.new + reader.output = IO::Memory.new + reader.color = false + reader.editor.height = 15 + reader.editor.width = 30 + reader + end + + def self.send(io, value) + io << value + Fiber.yield + end + end +end diff --git a/lib/reply/src/auto_completion.cr b/lib/reply/src/auto_completion.cr new file mode 100644 index 000000000000..ee4940fac71c --- /dev/null +++ b/lib/reply/src/auto_completion.cr @@ -0,0 +1,263 @@ +require "./term_cursor" +require "./term_size" +require "colorize" + +module Reply + # Interface of auto-completion. + # + # It provides following important methods: + # + # * `complete_on`: Trigger the auto-completion given a *word_on_cursor* and expression before. + # Stores the list of entries, and returns the *replacement* string. + # + # * `name_filter=`: Update the filtering of entries. + # + # * `display_entries`: Displays on screen the stored entries. + # Highlight the one selected. (initially `nil`). + # + # * `selection_next`/`selection_previous`: Increases/decrease the selected entry. + # + # * `open`/`close`: Toggle display, clear entries if close. + # + # * `clear`: Like `close`, but display a empty space instead of nothing. + private class AutoCompletion + getter? open = false + getter? cleared = false + @selection_pos : Int32? = nil + + @title = "" + @all_entries = [] of String + getter entries = [] of String + property name_filter = "" + + def initialize(&@auto_complete : String, String -> {String, Array(String)}) + @display_title = ->default_display_title(IO, String) + @display_entry = ->default_display_entry(IO, String, String) + @display_selected_entry = ->default_display_selected_entry(IO, String) + end + + def complete_on(current_word : String, expression_before : String) : String? + @title, @all_entries = @auto_complete.call(current_word, expression_before) + self.name_filter = current_word + + @entries.empty? ? nil : common_root(@entries) + end + + def name_filter=(@name_filter) + @selection_pos = nil + @entries = @all_entries.select(&.starts_with?(@name_filter)) + end + + # If open, displays completion entries by columns, minimizing the height. + # Highlight the selected entry (initially `nil`). + # + # If cleared, displays `clear_size` space. + # + # If closed, do nothing. + # + # Returns the actual displayed height. + def display_entries(io, color? = true, width = Term::Size.width, max_height = 10, min_height = 0) : Int32 # ameba:disable Metrics/CyclomaticComplexity + if cleared? + min_height.times { io.puts } + return min_height + end + + return 0 unless open? + return 0 if max_height <= 1 + + height = 0 + + # Print title: + if color? + @display_title.call(io, @title) + else + io << @title << ":" + end + io.puts + height += 1 + + if @entries.empty? + (min_height - height).times { io.puts } + return {height, min_height}.max + end + + nb_rows = compute_nb_row(@entries, max_nb_row: max_height - height, width: width) + + columns = @entries.in_groups_of(nb_rows, filled_up_with: "") + column_widths = columns.map &.max_of &.size.+(2) + + nb_cols = nb_colomns_in_width(column_widths, width) + + col_start = 0 + if pos = @selection_pos + col_end = pos // nb_rows + + if col_end >= nb_cols + nb_cols = nb_colomns_in_width(column_widths[..col_end].reverse_each, width) + + col_start = col_end - nb_cols + 1 + end + end + + nb_rows.times do |r| + nb_cols.times do |c| + c += col_start + + entry = columns[c][r] + col_width = column_widths[c] + + # `...` on the last column and row: + if (r == nb_rows - 1) && (c - col_start == nb_cols - 1) && columns[c + 1]? + entry += ".." + end + + # Entry to display: + entry_str = entry.ljust(col_width) + + if r + c*nb_rows == @selection_pos + # Colorize selection: + if color? + @display_selected_entry.call(io, entry_str) + else + io << ">" + entry_str[...-1] # if no color, remove last spaces to let place to '*'. + end + else + # Display entry_str, with @name_filter prefix in bright: + unless entry.empty? + if color? + io << @display_entry.call(io, @name_filter, entry_str.lchop(@name_filter)) + else + io << entry_str + end + end + end + end + io << Term::Cursor.clear_line_after if color? + io.puts + end + + height += nb_rows + + (min_height - height).times { io.puts } + + {height, min_height}.max + end + + # Increases selected entry. + def selection_next + return nil if @entries.empty? + + if (pos = @selection_pos).nil? + new_pos = 0 + else + new_pos = (pos + 1) % @entries.size + end + @selection_pos = new_pos + @entries[new_pos] + end + + # Decreases selected entry. + def selection_previous + return nil if @entries.empty? + + if (pos = @selection_pos).nil? + new_pos = 0 + else + new_pos = (pos - 1) % @entries.size + end + @selection_pos = new_pos + @entries[new_pos] + end + + def open + @open = true + @cleared = false + end + + def close + @selection_pos = nil + @entries.clear + @name_filter = "" + @all_entries.clear + @open = false + @cleared = false + end + + def clear + close + @cleared = true + end + + def set_display_title(&@display_title : IO, String ->) + end + + def set_display_entry(&@display_entry : IO, String, String ->) + end + + def set_display_selected_entry(&@display_selected_entry : IO, String ->) + end + + protected def default_display_title(io, title) + io << title.colorize.underline << ":" + end + + protected def default_display_entry(io, entry_matched, entry_remaining) + io << entry_matched.colorize.bright << entry_remaining + end + + protected def default_display_selected_entry(io, entry) + io << entry.colorize.bright.on_dark_gray + end + + private def nb_colomns_in_width(column_widths, width) + nb_cols = 0 + w = 0 + column_widths.each do |col_width| + w += col_width + break if w > width + nb_cols += 1 + end + nb_cols + end + + # Computes the min number of rows required to display entries: + # * if all entries cannot fit in `max_nb_row` rows, returns `max_nb_row`, + # * if there are less than 10 entries, returns `entries.size` because in this case, it's more convenient to display them in one column. + private def compute_nb_row(entries, max_nb_row, width) + if entries.size > 10 + # test possible nb rows: (1 to max_nb_row) + 1.to max_nb_row do |r| + w = 0 + # Sum the width of each given column: + entries.each_slice(r, reuse: true) do |col| + w += col.max_of &.size + 2 + end + + # If *w* goes past *width*, we found min row required: + return r if w < width + end + end + + {entries.size, max_nb_row}.min + end + + # Finds the common root text between given entries. + private def common_root(entries) + return "" if entries.empty? + return entries[0] if entries.size == 1 + + i = 0 + entry_iterators = entries.map &.each_char + + loop do + char_on_first_entry = entries[0][i]? + same = entry_iterators.all? do |entry| + entry.next == char_on_first_entry + end + i += 1 + break if !same + end + entries[0][...(i - 1)] + end + end +end diff --git a/lib/reply/src/char_reader.cr b/lib/reply/src/char_reader.cr new file mode 100644 index 000000000000..3da5ca06d804 --- /dev/null +++ b/lib/reply/src/char_reader.cr @@ -0,0 +1,198 @@ +module Reply + private struct CharReader + enum Sequence + EOF + UP + DOWN + RIGHT + LEFT + ENTER + ESCAPE + DELETE + BACKSPACE + CTRL_A + CTRL_B + CTRL_C + CTRL_D + CTRL_E + CTRL_F + CTRL_K + CTRL_N + CTRL_P + CTRL_U + CTRL_X + CTRL_UP + CTRL_DOWN + CTRL_LEFT + CTRL_RIGHT + CTRL_ENTER + CTRL_DELETE + CTRL_BACKSPACE + ALT_B + ALT_D + ALT_F + ALT_ENTER + ALT_BACKSPACE + TAB + SHIFT_TAB + HOME + END + end + + def initialize(buffer_size = 8192) + @slice_buffer = Bytes.new(buffer_size) + end + + def read_char(from io : T = STDIN) forall T + {% if flag?(:win32) && T <= IO::FileDescriptor %} + handle = LibC._get_osfhandle(io.fd) + raise RuntimeError.from_errno("_get_osfhandle") if handle == -1 + + raw(io) do + LibC.ReadConsoleA(LibC::HANDLE.new(handle), @slice_buffer, @slice_buffer.size, out nb_read, nil) + + parse_escape_sequence(@slice_buffer[0...nb_read]) + end + {% else %} + nb_read = raw(io, &.read(@slice_buffer)) + parse_escape_sequence(@slice_buffer[0...nb_read]) + {% end %} + end + + private def parse_escape_sequence(chars : Bytes) : Char | Sequence | String? + return String.new(chars) if chars.size > 6 + return Sequence::EOF if chars.empty? + + case chars[0]? + when '\e'.ord + case chars[1]? + when '['.ord + case chars[2]? + when 'A'.ord then Sequence::UP + when 'B'.ord then Sequence::DOWN + when 'C'.ord then Sequence::RIGHT + when 'D'.ord then Sequence::LEFT + when 'Z'.ord then Sequence::SHIFT_TAB + when '3'.ord + if {chars[3]?, chars[4]?} == {';'.ord, '5'.ord} + case chars[5]? + when '~'.ord then Sequence::CTRL_DELETE + end + elsif chars[3]? == '~'.ord + Sequence::DELETE + end + when '1'.ord + if {chars[3]?, chars[4]?} == {';'.ord, '5'.ord} + case chars[5]? + when 'A'.ord then Sequence::CTRL_UP + when 'B'.ord then Sequence::CTRL_DOWN + when 'C'.ord then Sequence::CTRL_RIGHT + when 'D'.ord then Sequence::CTRL_LEFT + end + elsif chars[3]? == '~'.ord # linux console HOME + Sequence::HOME + end + when '4'.ord # linux console END + if chars[3]? == '~'.ord + Sequence::END + end + when 'H'.ord # xterm HOME + Sequence::HOME + when 'F'.ord # xterm END + Sequence::END + end + when '\t'.ord + Sequence::SHIFT_TAB + when '\r'.ord + Sequence::ALT_ENTER + when 0x7f + Sequence::ALT_BACKSPACE + when 'O'.ord + if chars[2]? == 'H'.ord # gnome terminal HOME + Sequence::HOME + elsif chars[2]? == 'F'.ord # gnome terminal END + Sequence::END + end + when 'b' + Sequence::ALT_B + when 'd' + Sequence::ALT_D + when 'f' + Sequence::ALT_F + when Nil + Sequence::ESCAPE + end + when '\r'.ord + Sequence::ENTER + when '\n'.ord + {% if flag?(:win32) %} + Sequence::CTRL_ENTER + {% else %} + Sequence::ENTER + {% end %} + when '\t'.ord + Sequence::TAB + when '\b'.ord + Sequence::CTRL_BACKSPACE + when ctrl('a') + Sequence::CTRL_A + when ctrl('b') + Sequence::CTRL_B + when ctrl('c') + Sequence::CTRL_C + when ctrl('d') + Sequence::CTRL_D + when ctrl('e') + Sequence::CTRL_E + when ctrl('f') + Sequence::CTRL_F + when ctrl('k') + Sequence::CTRL_K + when ctrl('n') + Sequence::CTRL_N + when ctrl('p') + Sequence::CTRL_P + when ctrl('u') + Sequence::CTRL_U + when ctrl('x') + Sequence::CTRL_X + when '\0'.ord + Sequence::EOF + when 0x7f + Sequence::BACKSPACE + else + if chars.size == 1 + chars[0].chr + end + end || String.new(chars) + end + + private def raw(io : T, &) forall T + {% if T.has_method?(:raw) %} + if io.tty? + io.raw { yield io } + else + yield io + end + {% else %} + yield io + {% end %} + end + + private def ctrl(k) + (k.ord & 0x1f) + end + end +end + +{% if flag?(:win32) %} + lib LibC + STD_INPUT_HANDLE = -10 + + fun ReadConsoleA(hConsoleInput : Void*, + lpBuffer : Void*, + nNumberOfCharsToRead : UInt32, + lpNumberOfCharsRead : UInt32*, + pInputControl : Void*) : UInt8 + end +{% end %} diff --git a/lib/reply/src/expression_editor.cr b/lib/reply/src/expression_editor.cr new file mode 100644 index 000000000000..5c3d7aec24b9 --- /dev/null +++ b/lib/reply/src/expression_editor.cr @@ -0,0 +1,1002 @@ +require "./term_cursor" +require "./term_size" + +module Reply + # The `ExpressionEditor` allows to edit and display an expression. + # + # Its main task is to provide the display of the prompt and a multiline expression within + # the term bounds, and ensure the correspondence between the cursor on screen and the cursor on the expression. + # + # Usage example: + # ``` + # # new editor: + # @editor = ExpressionEditor.new( + # prompt: ->(expr_line_number : Int32) { "prompt>" } + # ) + # + # # edit some code: + # @editor.update do + # @editor << %(puts "World") + # + # insert_new_line(indent: 1) + # @editor << %(puts "!") + # end + # + # # move cursor: + # @editor.move_cursor_up + # 4.times { @editor.move_cursor_left } + # + # # edit: + # @editor.update do + # @editor << "Hello " + # end + # + # @editor.end_editing + # + # @editor.expression # => %(puts "Hello World"\n puts "!") + # puts "=> ok" + # + # # clear and restart edition: + # @editor.prompt_next + # ``` + # + # The above displays: + # ``` + # prompt>puts "Hello World" + # prompt> puts "!" + # => ok + # prompt> + # ``` + # + # Methods that modify the expression should be placed inside an `update` so the screen can be refreshed taking in account + # the adding or removing of lines, and doesn't boilerplate the display. + class ExpressionEditor + getter lines : Array(String) = [""] + getter expression : String? { lines.join('\n') } + getter expression_height : Int32? { lines.sum { |l| line_height(l) } } + property? color = true + property output : IO = STDOUT + + # Tracks the cursor position relatively to the expression's lines, (y=0 corresponds to the first line and x=0 the first char) + # This position is independent of text wrapping so its position will not match to real cursor on screen. + # + # `|` : cursor position + # + # ``` + # prompt>def very_looo + # ooo|ng_name <= wrapping + # prompt> bar + # prompt>end + # ``` + # For example here the cursor position is x=16, y=0, but real cursor is at x=3,y=1 from the beginning of expression. + getter x = 0 + getter y = 0 + + # The editor height, if not set (`nil`), equal to term height. + setter height : Int32? = nil + + # The editor width, if not set (`nil`), equal to term width. + setter width : Int32? = nil + + @prompt : Int32, Bool -> String + @prompt_size : Int32 + + @scroll_offset = 0 + @header_height = 0 + + @header : IO, Int32 -> Int32 = ->(io : IO, previous_height : Int32) { 0 } + @highlight = ->(code : String) { code } + + # The list of characters delimiting words. + # + # default: ` \n\t+-*/,;@&%<>"'^\\[](){}|.~:=!?` + property word_delimiters : Array(Char) = {{" \n\t+-*/,;@&%<>\"'^\\[](){}|.~:=!?".chars}} + + # Creates a new `ExpressionEditor` with the given *prompt*. + def initialize(&@prompt : Int32, Bool -> String) + @prompt_size = @prompt.call(0, false).size # uncolorized size + end + + # Sets a `Proc` allowing to display a header above the prompt. (used by auto-completion) + # + # *io*: The IO in which the header should be displayed. + # *previous_hight*: Previous header height, useful to keep a header size constant. + # Should returns the exact *height* printed in the io. + def set_header(&@header : IO, Int32 -> Int32) + end + + # Sets the `Proc` to highlight the expression. + def set_highlight(&@highlight : String -> String) + end + + private def move_cursor(x, y) + @x += x + @y += y + end + + private def move_real_cursor(x, y) + @output.print Term::Cursor.move(x, -y) + end + + private def move_abs_cursor(@x, @y) + end + + private def reset_cursor + @x = @y = 0 + end + + # Returns true is the char at *x*, *y* is a word char. + private def word_char?(x) + if x >= 0 && (ch = self.current_line[x]?) + !ch.in? self.word_delimiters + end + end + + def current_line + @lines[@y] + end + + def previous_line? + if @y > 0 + @lines[@y - 1] + end + end + + def next_line? + @lines[@y + 1]? + end + + # Returns the word under the cursor following `word_delimiters`. + def current_word + word_begin, word_end = self.current_word_begin_end + + self.current_line[word_begin..word_end] + end + + # Returns begin and end position of `current_word`. + def current_word_begin_end + return 0, 0 if self.current_line.empty? + + word_begin = {@x - 1, 0}.max + word_end = @x + while word_char?(word_begin) + word_begin -= 1 + end + + while word_char?(word_end) + word_end += 1 + end + + {word_begin + 1, word_end - 1} + end + + def empty? + @lines.empty? || (@lines.size == 1 && @lines.first.empty?) + end + + def cursor_on_last_line? + (@y == @lines.size - 1) + end + + def expression_before_cursor(x = @x, y = @y) + String.build do |io| + @lines[...y].each { |line| io << line << '\n' } + io << @lines[y][...x] + end + end + + # Following functions modifies the expression, they should be called inside + # an `update` block to see the changes in the screen: # + + # Should be called inside an `update`. + def previous_line=(line) + @lines[@y - 1] = line + @expression = @expression_height = nil + end + + # Should be called inside an `update`. + def current_line=(line) + @lines[@y] = line + @expression = @expression_height = nil + end + + # Should be called inside an `update`. + def next_line=(line) + @lines[@y + 1] = line + @expression = @expression_height = nil + end + + # Replaces the word under the cursor by *replacement*, then moves cursor at the end of *replacement*. + # Should be called inside an `update`. + def current_word=(replacement) + word_begin, word_end = self.current_word_begin_end + + if word_begin == word_end == 0 + self.current_line = replacement + else + self.current_line = self.current_line.sub(word_begin..word_end, replacement) + end + + move_abs_cursor(x: word_begin + replacement.size, y: @y) + end + + # Should be called inside an `update`. + def delete_line(y) + @lines.delete_at(y) + @expression = @expression_height = nil + end + + # Should be called inside an `update`. + def clear_expression + @lines.clear << "" + @expression = @expression_height = nil + end + + # Should be called inside an `update`. + # + # If *char* is `\n` or `\r`, inserts a new line with indent 0. + # Does nothing if the char is an `ascii_control?`. + def <<(char : Char) : self + return insert_new_line(0) if char.in? '\n', '\r' + return self if char.ascii_control? + + if @x >= current_line.size + self.current_line = current_line + char + else + self.current_line = current_line.insert(@x, char) + end + + move_cursor(x: +1, y: 0) + self + end + + # Should be called inside an `update`. + def <<(str : String) : self + str.each_char do |ch| + self << ch + end + self + end + + # Should be called inside an `update`. + def insert_new_line(indent) + case @x + when current_line.size + @lines.insert(@y + 1, " "*indent) + when .< current_line.size + @lines.insert(@y + 1, " "*indent + current_line[@x..]) + self.current_line = current_line[...@x] + end + + @expression = @expression_height = nil + move_abs_cursor(x: indent*2, y: @y + 1) + self + end + + # Should be called inside an `update`. + def delete + case @x + when current_line.size + if next_line = next_line? + self.current_line = current_line + next_line + + delete_line(@y + 1) + end + when .< current_line.size + self.current_line = current_line.delete_at(@x) + end + end + + # Should be called inside an `update`. + def back + case @x + when 0 + if prev_line = previous_line? + self.previous_line = prev_line + current_line + + move_cursor(x: prev_line.size, y: -1) + delete_line(@y + 1) + end + when .> 0 + self.current_line = current_line.delete_at(@x - 1) + move_cursor(x: -1, y: 0) + end + end + + # Should be called inside an `update`. + def delete_word + self.delete if @x == current_line.size + + word_end = self.next_word_end + self.current_line = current_line[...@x] + current_line[(word_end + 1)..] + end + + # Should be called inside an `update`. + def word_back + self.back if @x == 0 + + x = @x + + word_begin = self.previous_word_begin + move_abs_cursor(x: word_begin, y: @y) + + self.current_line = current_line[...word_begin] + current_line[x..] + end + + # Should be called inside an `update`. + def delete_after_cursor + if @x == current_line.size + self.delete + elsif !current_line.empty? + self.current_line = current_line[...@x] + end + end + + # Should be called inside an `update`. + def delete_before_cursor + if @x == 0 + self.back + elsif !current_line.empty? + self.current_line = current_line[@x..] + + move_abs_cursor(x: 0, y: @y) + end + end + + # End modifying functions. # + + # Gives the size of the last part of the line when it's wrapped + # + # prompt>def very_looo + # ooooooooong <= last part + # prompt> bar + # prompt>end + # + # e.g. here "ooooooooong".size = 10 + private def last_part_size(line_size) + (@prompt_size + line_size) % self.width + end + + # Returns the height of this line, (1 on common lines, more on wrapped lines): + private def line_height(line) + 1 + (@prompt_size + line.size) // self.width + end + + private def height_above_cursor(x = @x, y = @y) + height = @lines.each.first(@y).sum { |l| line_height(l) } + height += line_height(current_line[...@x]) - 1 + height + end + + # The editor width, if not set (`nil`), equal to term width. + def width + @width || Term::Size.width + end + + # The editor height, if not set (`nil`), equal to term height. + def height + @height || Term::Size.height + end + + # Returns the max height that could take an expression on screen. + # + # The expression scrolls if it's higher than epression_max_height. + private def epression_max_height + self.height - @header_height + end + + def move_cursor_left(allow_scrolling = true) + case @x + when 0 + # Wrap the cursor at the end of the previous line: + # + # `|`: cursor pos + # `*`: wanted pos + # + # ``` + # prompt>def very_looo + # ooooooooong* + # prompt>| bar + # prompt>end + # ``` + if prev_line = previous_line? + scroll_up_if_needed if allow_scrolling + + # Wrap real cursor: + size_of_last_part = last_part_size(prev_line.size) + move_real_cursor(x: -@prompt_size + size_of_last_part, y: -1) + + # Wrap cursor: + move_cursor(x: prev_line.size, y: -1) + end + when .> 0 + # Move the cursor left, wrap the real cursor if needed: + # + # `|`: cursor pos + # `*`: wanted pos + # + # ``` + # prompt>def very_looo* + # |oooooooong + # prompt> bar + # prompt>end + # ``` + if last_part_size(@x) == 0 + scroll_up_if_needed if allow_scrolling + move_real_cursor(x: self.width + 1, y: -1) + else + move_real_cursor(x: -1, y: 0) + end + move_cursor(x: -1, y: 0) + end + end + + def move_cursor_right(allow_scrolling = true) + case @x + when current_line.size + # Wrap the cursor at the beginning of the next line: + # + # `|`: cursor pos + # `*`: wanted pos + # + # ``` + # prompt>def very_looo + # ooooooooong| + # prompt>* bar + # prompt>end + # ``` + if next_line? + scroll_down_if_needed if allow_scrolling + + # Wrap real cursor: + size_of_last_part = last_part_size(current_line.size) + move_real_cursor(x: -size_of_last_part + @prompt_size, y: +1) + + # Wrap cursor: + move_cursor(x: -current_line.size, y: +1) + end + when .< current_line.size + # Move the cursor right, wrap the real cursor if needed: + # + # `|`: cursor pos + # `*`: wanted pos + # + # ``` + # prompt>def very_looo| + # *oooooooong + # prompt> bar + # prompt>end + # ``` + if last_part_size(@x) == (self.width - 1) + scroll_down_if_needed if allow_scrolling + + move_real_cursor(x: -self.width, y: +1) + else + move_real_cursor(x: +1, y: 0) + end + + # move cursor right + move_cursor(x: +1, y: 0) + end + end + + def move_cursor_up(allow_scrolling = true) + scroll_up_if_needed if allow_scrolling + + if (@prompt_size + @x) >= self.width + if @x >= self.width + # Here, we have: + # ``` + # prompt>def *very_looo + # ooooooooooo|ooooooooo + # ooooooooong + # prompt> bar + # prompt>end + # ``` + # So we need only to move real cursor up + # and move back @x by term-width. + # + move_real_cursor(x: 0, y: -1) + move_cursor(x: -self.width, y: 0) + else + # Here, we have: + # ``` + # prompt>*def very_looo + # ooo|ooooooooooooooooo + # ooooooooong + # prompt> bar + # prompt>end + # ``` + # + move_real_cursor(x: self.width - @x, y: -1) + move_cursor(x: 0 - @x, y: 0) + end + + true + elsif prev_line = previous_line? + # Here, there are a previous line in which we can move up, we want to + # move on the last part of the previous line: + size_of_last_part = last_part_size(prev_line.size) + + if size_of_last_part < @prompt_size + @x + # ``` + # prompt>def very_looo + # oooooooooooooooooooo + # ong* <= last part + # prompt> ba|aar + # prompt>end + # ``` + move_real_cursor(x: -@x - @prompt_size + size_of_last_part, y: -1) + move_abs_cursor(x: prev_line.size, y: @y - 1) + else + # ``` + # prompt>def very_looo + # oooooooooooooooooooo + # ooooooooooo*ooong <= last part + # prompt> ba|aar + # prompt>end + # ``` + move_real_cursor(x: 0, y: -1) + x = prev_line.size - size_of_last_part + @prompt_size + @x + move_abs_cursor(x: x, y: @y - 1) + end + true + else + false + end + end + + def move_cursor_down(allow_scrolling = true) + scroll_down_if_needed if allow_scrolling + + size_of_last_part = last_part_size(current_line.size) + real_x = last_part_size(@x) + + remaining = current_line.size - @x + + if remaining > size_of_last_part + # on middle + if remaining > self.width + # Here, there are enough remaining to just move down + # ``` + # prompt>def very|_loooo + # ooooooooooooooo*oooooo + # ong + # prompt> bar + # prompt>end + # ``` + # + move_real_cursor(x: 0, y: +1) + move_cursor(x: self.width, y: 0) + else + # Here, we goes to end of current line: + # ``` + # prompt>def very_loooo + # ooooooooooooooo|ooooo + # ong* + # prompt> bar + # prompt>end + # ``` + move_real_cursor(x: -real_x + size_of_last_part, y: +1) + move_abs_cursor(x: current_line.size, y: @y) + end + true + elsif next_line = next_line? + case real_x + when .< @prompt_size + # Here, we are behind the prompt so we want goes to the beginning of the next line: + # ``` + # prompt>def very_loooo + # ooooooooooooooooooooo + # ong| + # prompt>* bar + # prompt>end + # ``` + move_real_cursor(x: -real_x + @prompt_size, y: +1) + move_abs_cursor(x: 0, y: @y + 1) + when .< @prompt_size + next_line.size + # Here, we can just move down on the next line: + # ``` + # prompt>def very_loooo + # ooooooooooooooooooooo + # ooooooooong| + # prompt> ba*r + # prompt>end + # ``` + move_real_cursor(x: 0, y: +1) + move_abs_cursor(x: real_x - @prompt_size, y: @y + 1) + else + # Finally, here, we want to move at end of the next line: + # ``` + # prompt>def very_loooo + # ooooooooooooooooooooo + # ooooooooooooooong| + # prompt> bar* + # prompt>end + # ``` + x = real_x - (@prompt_size + next_line.size) + move_real_cursor(x: -x, y: +1) + move_abs_cursor(x: next_line.size, y: @y + 1) + end + true + else + false + end + end + + def move_cursor_to(x, y, allow_scrolling = true) + until @y == y + (@y > y) ? move_cursor_up(allow_scrolling: false) : move_cursor_down(allow_scrolling: false) + end + + until @x == x + (@x > x) ? move_cursor_left(allow_scrolling: false) : move_cursor_right(allow_scrolling: false) + end + + if allow_scrolling && update_scroll_offset + update + end + end + + def move_cursor_to_begin(allow_scrolling = true) + move_cursor_to(0, 0, allow_scrolling: allow_scrolling) + end + + def move_cursor_to_end(allow_scrolling = true) + y = @lines.size - 1 + + move_cursor_to(@lines[y].size, y, allow_scrolling: allow_scrolling) + end + + def move_cursor_to_end_of_line(y = @y, allow_scrolling = true) + move_cursor_to(@lines[y].size, y, allow_scrolling: allow_scrolling) + end + + def move_word_forward + self.move_cursor_right if @x == self.current_line.size + + word_end = self.next_word_end + self.move_cursor_to(x: word_end + 1, y: @y) + end + + def move_word_backward + self.move_cursor_left if @x == 0 + + word_begin = self.previous_word_begin + self.move_cursor_to(x: word_begin, y: @y) + end + + private def next_word_end + x = @x + while word_char?(x) == false + x += 1 + end + + while word_char?(x) + x += 1 + end + x - 1 + end + + private def previous_word_begin + x = @x - 1 + while word_char?(x) == false + x -= 1 + end + + while word_char?(x) + x -= 1 + end + x + 1 + end + + # Refresh the screen. + # + # It clears the display of the current expression, + # then yields for modifications, and displays the new expression. + # + # if *force_full_view* is true, whole expression is displayed, even if it overflow the term width, otherwise + # the expression is bound and can be scrolled. + def update(force_full_view = false, &) + height_to_clear = self.height_above_cursor + with self yield + + @expression = @expression_height = nil + + # Updated expression can be smaller so we might need to adjust the cursor: + @y = @y.clamp(0, @lines.size - 1) + @x = @x.clamp(0, @lines[@y].size) + + print_expression_and_header(height_to_clear, force_full_view) + end + + def update(force_full_view = false) + height_to_clear = self.height_above_cursor + + print_expression_and_header(height_to_clear, force_full_view) + end + + # Calls the header proc and saves the *header_height* + private def update_header : String + String.build do |io| + @header_height = @header.call(io, @header_height) + end + end + + def replace(lines : Array(String)) + update { @lines = lines } + end + + # Prints the full expression (without view bounds), and eventually replace it by *replacement*. + def end_editing(replacement : Array(String)? = nil) + if replacement + update(force_full_view: true) do + @lines = replacement + end + else + update(force_full_view: true) + end + + move_cursor_to_end(allow_scrolling: false) + @output.puts + end + + # Clears the expression and start a new prompt on a next line. + def prompt_next + @scroll_offset = 0 + @lines = [""] + @expression = @expression_height = nil + reset_cursor + @prompt_size = 0 + print_prompt(@output, 0) + end + + private def print_prompt(io, line_index) + line_prompt_size = @prompt.call(line_index, false).size # uncolorized size + @prompt_size = {line_prompt_size, @prompt_size}.max + io.print @prompt.call(line_index, color?) + + # Padding to align the lines when the prompt size change + (@prompt_size - line_prompt_size).times do + io.print ' ' + end + end + + def scroll_up + if @scroll_offset < expression_height() - epression_max_height() + @scroll_offset += 1 + update + end + end + + def scroll_down + if @scroll_offset > 0 + @scroll_offset -= 1 + update + end + end + + private def scroll_up_if_needed + if update_scroll_offset(y_shift: -1) + update + end + end + + private def scroll_down_if_needed + if update_scroll_offset(y_shift: +1) + update + end + end + + # Updates the scroll offset in a way that (cursor + y_shift) is still between the view bounds + # Returns true if the offset has been effectively modified. + private def update_scroll_offset(y_shift = 0) + start, end_ = self.view_bounds + real_y = self.height_above_cursor + y_shift + + # case 1: cursor is before view start, we need to increase the scroll by the difference. + if real_y < start + @scroll_offset += start - real_y + true + + # case 2: cursor is after view end, we need to decrease the scroll by the difference. + elsif real_y > end_ + @scroll_offset -= real_y - end_ + true + else + false + end + end + + protected def expression_scrolled? + expression_height() > epression_max_height() + end + + # Returns y-start and end positions of the expression that should be displayed on the screen. + # This take account of @scroll_offset, and the size start-end should never be greater than screen height. + private def view_bounds + end_ = expression_height() - 1 + + start = {0, end_ + 1 - epression_max_height()}.max + + @scroll_offset = @scroll_offset.clamp(0, start) # @scroll_offset could not be greater than start. + + start -= @scroll_offset + end_ -= @scroll_offset + {start, end_} + end + + private def print_line(io, colorized_line, line_index, line_size, prompt?, first?, is_last_part?) + if prompt? + io.puts unless first? + print_prompt(io, line_index) + end + io.print colorized_line + + # ``` + # prompt>begin | + # prompt> foooooooooooooooooooo| + # | <- If the line size match exactly the screen width, we need to add a + # prompt> bar | extra line feed, so computes based on `%` or `//` stay exact. + # prompt>end | + # ``` + io.puts if is_last_part? && last_part_size(line_size) == 0 + end + + private def sync_output + if (output = @output).is_a?(IO::FileDescriptor) && output.tty? + # Disallowing the synchronization reduce blinking on some terminal like vscode (#10) + output.sync = false + output.flush_on_newline = false + output.print Term::Cursor.hide + yield + output.print Term::Cursor.show + output.flush_on_newline = true + output.sync = true + output.flush + else + yield + end + end + + # Prints the colorized expression, this former is clipped if it's higher than screen. + # The `header` is print just above. + # The only displayed part of the expression is delimited by `view_bounds` and depend of the value of + # `@scroll_offset`. + # Lines that takes more than one line (if wrapped) are cut in consequence. + # + # *height_to_clear* is the height we have to clear between the previous cursor pos and the beginning of the prompt. + # if *force_full_view* is true, all expression is dumped on screen, without clipping. + private def print_expression_and_header(height_to_clear, force_full_view = false) + height_to_clear += @header_height + header = update_header() + + if force_full_view + start, end_ = 0, Int32::MAX + else + update_scroll_offset() + + start, end_ = view_bounds() + end + + first = true + + y = 0 + + colorized_lines = + if self.color? + @highlight.call(self.expression).split('\n') + else + self.lines + end + + # While printing, real cursor move, but @x/@y don't, so we track the moved cursor position to be able to + # restore real cursor at @x/@y position. + cursor_move_x = cursor_move_y = 0 + + display = String.build do |io| + # Iterate over the uncolored lines because we need to know the true size of each line: + @lines.each_with_index do |line, line_index| + line_height = line_height(line) + + break if y > end_ + if y + line_height <= start + y += line_height + next + end + + if start <= y && y + line_height - 1 <= end_ + # The line can hold entirely between the view bounds, print it: + print_line(io, colorized_lines[line_index], line_index, line.size, prompt?: true, first?: first, is_last_part?: true) + first = false + + cursor_move_x = line.size + cursor_move_y = line_index + + y += line_height + else + # The line cannot holds entirely between the view bounds. + # We need to cut the line into each part and display only parts that hold in the view + colorized_parts = parts_from_colorized(colorized_lines[line_index]) + + colorized_parts.each_with_index do |colorized_part, part_number| + if start <= y <= end_ + # The part holds on the view, we can print it. + print_line(io, colorized_part, line_index, line.size, prompt?: part_number == 0, first?: first, is_last_part?: part_number == line_height - 1) + first = false + + cursor_move_x = {line.size, (part_number + 1)*self.width - @prompt_size - 1}.min + cursor_move_y = line_index + end + y += 1 + end + end + end + end + + sync_output do + # Rewind cursor from *height_to_clear*, then print the header and the clipped expression + move_real_cursor(x: 0, y: -height_to_clear) + @output.print Term::Cursor.column(1) + @output.print Term::Cursor.clear_screen_down + @output.print header + @output.print display + + # Retrieve the real cursor at its corresponding cursor position (`@x`, `@y`) + x_save, y_save = @x, @y + @y = cursor_move_y + @x = cursor_move_x + + move_cursor_to(x_save, y_save, allow_scrolling: false) + end + end + + # Splits the given *line* (colorized) into parts delimited by wrapping. + # + # Because *line* is colorized, it's hard to know when it's wrap based on its size (colors sequence might appear anywhere in the string) + # Here we does the following: + # * Create a `String::Builder` for the first part (`part_builder`) + # * Iterate over the *line*, parsing the color sequence + # * Count cursor `x` for each char unless color sequences + # * If count goes over term width: + # reset `x` to 0, and create a new `String::Builder` for next part. + private def parts_from_colorized(line) + parts = Array(String).new + + color_sequence = "" + part_builder = String::Builder.new + + x = @prompt_size + chars = line.each_char + until (c = chars.next).is_a? Iterator::Stop + # Parse color sequence: + if c == '\e' && chars.next == '[' + color_sequence = String.build do |seq| + seq << '\e' << '[' + + until (c = chars.next) == 'm' + break if c.is_a? Iterator::Stop + seq << c + end + seq << 'm' + end + part_builder << color_sequence + else + part_builder << c + x += 1 + end + + if x >= self.width + # Wrapping: save part and create a new builder for next part + part_builder << "\e[0m" + parts << part_builder.to_s + part_builder = String::Builder.new + part_builder << color_sequence # We also add the previous color sequence because color need to be preserved. + x = 0 + end + end + parts << part_builder.to_s + parts + end + end +end diff --git a/lib/reply/src/history.cr b/lib/reply/src/history.cr new file mode 100644 index 000000000000..3f12f0f01f95 --- /dev/null +++ b/lib/reply/src/history.cr @@ -0,0 +1,109 @@ +module Reply + class History + getter history = Deque(Array(String)).new + getter max_size = 10_000 + @index = 0 + + # Hold the history lines being edited, always contains one element more than @history + # because it can also contain the current line (not yet in history) + @edited_history = [nil] of Array(String)? + + def <<(lines) + lines = lines.dup # make history elements independent + + if l = @history.delete_element(lines) + # re-insert duplicate elements at the end + @history.push(l) + else + # delete oldest entries until history size is `max_size` + while @history.size >= max_size + @history.delete_at(0) + end + + @history.push(lines) + end + set_to_last + end + + def clear + @history.clear + @edited_history.clear.push(nil) + @index = 0 + end + + def up(current_edited_lines : Array(String)) + unless @index == 0 + @edited_history[@index] = current_edited_lines + + @index -= 1 + (@edited_history[@index]? || @history[@index]).dup + end + end + + def down(current_edited_lines : Array(String)) + unless @index == @history.size + @edited_history[@index] = current_edited_lines + + @index += 1 + (@edited_history[@index]? || @history[@index]).dup + end + end + + def max_size=(max_size) + @max_size = max_size.clamp 1.. + end + + def load(file : Path | String) + File.touch(file) unless File.exists?(file) + File.open(file, "r") { |f| load(f) } + end + + def load(io : IO) + str = io.gets_to_end + if str.empty? + @history = Deque(Array(String)).new + else + history = + str.gsub(/(\\\n|\\\\)/, { + "\\\n": '\e', # replace temporary `\\n` by `\e` because we first split by `\n` but want to avoid `\\n`. (`\e` could not exist in a history line) + "\\\\": '\\', # replace `\\` by `\`. + }) + .split('\n') # split each expression + .map(&.split('\e')) # split each expression by lines + + @history = Deque(Array(String)).new(history) + end + + @edited_history.clear + (@history.size + 1).times do + @edited_history << nil + end + @index = @history.size + end + + def save(file : Path | String) + File.open(file, "w") { |f| save(f) } + end + + def save(io : IO) + @history.join(io, '\n') do |entry, io2| + entry.join(io2, "\\\n") do |line, io3| + io3 << line.gsub('\\', "\\\\") + end + end + end + + # Sets the index to last added value + protected def set_to_last + @index = @history.size + @edited_history.fill(nil).push(nil) + end + end +end + +class Deque(T) + # Add this method because unlike `Array`, `Deque#delete` return a Boolean instead of the element. + def delete_element(obj) : T? + internal_delete { |i| i == obj } + end +end diff --git a/lib/reply/src/reader.cr b/lib/reply/src/reader.cr new file mode 100644 index 000000000000..f8bb5bbb03fd --- /dev/null +++ b/lib/reply/src/reader.cr @@ -0,0 +1,442 @@ +require "./history" +require "./expression_editor" +require "./char_reader" +require "./auto_completion" + +module Reply + # Reader for your REPL. + # + # Create a subclass of it and override methods to customize behavior. + # + # ``` + # class MyReader < Reply::Reader + # def prompt(io, line_number, color?) + # io << "reply> " + # end + # end + # ``` + # + # Run the REPL with `run`: + # + # ``` + # reader = MyReader.new + # + # reader.run do |expression| + # # Eval expression here + # puts " => #{expression}" + # end + # ``` + # + # Or with `read_next`: + # ``` + # loop do + # expression = reader.read_next + # break unless expression + # + # # Eval expression here + # puts " => #{expression}" + # end + # ``` + class Reader + # General architecture: + # + # ``` + # SDTIN -> CharReader -> Reader -> ExpressionEditor -> STDOUT + # ^ ^ + # | | + # History AutoCompletion + # ``` + + getter history = History.new + getter editor : ExpressionEditor + @auto_completion : AutoCompletion + @char_reader = CharReader.new + getter line_number = 1 + + delegate :color?, :color=, :lines, :output, :output=, to: @editor + delegate :word_delimiters, :word_delimiters=, to: @editor + + def initialize + @editor = ExpressionEditor.new do |expr_line_number, color?| + String.build do |io| + prompt(io, @line_number + expr_line_number, color?) + end + end + + @auto_completion = AutoCompletion.new(&->auto_complete(String, String)) + @auto_completion.set_display_title(&->auto_completion_display_title(IO, String)) + @auto_completion.set_display_entry(&->auto_completion_display_entry(IO, String, String)) + @auto_completion.set_display_selected_entry(&->auto_completion_display_selected_entry(IO, String)) + + @editor.set_header do |io, previous_height| + @auto_completion.display_entries(io, color?, max_height: {10, Term::Size.height - 1}.min, min_height: previous_height) + end + + @editor.set_highlight(&->highlight(String)) + + if file = self.history_file + @history.load(file) + end + end + + # Override to customize the prompt. + # + # Toggle the colorization following *color?*. + # + # default: `$:001> ` + def prompt(io : IO, line_number : Int32, color? : Bool) + io << "$:" + io << sprintf("%03d", line_number) + io << "> " + end + + # Override to enable expression highlighting. + # + # default: uncolored `expression` + def highlight(expression : String) + expression + end + + # Override this method to makes the interface continue on multiline, depending of the expression. + # + # default: `false` + def continue?(expression : String) + false + end + + # Override to enable reformatting after submitting. + # + # default: unchanged `expression` + def format(expression : String) + nil + end + + # Override to return the expected indentation level in function of expression before cursor. + # + # default: `0` + def indentation_level(expression_before_cursor : String) + 0 + end + + # Override to select with expression is saved in history. + # + # default: `!expression.blank?` + def save_in_history?(expression : String) + !expression.blank? + end + + # Override to indicate the `Path|String|IO` where the history is saved. If `nil`, the history is not persistent. + # + # default: `nil` + def history_file + nil + end + + # Override to integrate auto-completion. + # + # *current_word* is picked following `word_delimiters`. + # It expects to return `Tuple` with: + # * a title : `String` + # * the auto-completion results : `Array(String)` + # + # default: `{"", [] of String}` + def auto_complete(current_word : String, expression_before : String) + return "", [] of String + end + + # Override to customize how title is displayed. + # + # default: `title` underline + `":"` + def auto_completion_display_title(io : IO, title : String) + @auto_completion.default_display_title(io, title) + end + + # Override to customize how entry is displayed. + # + # Entry is split in two (`entry_matched` + `entry_remaining`). `entry_matched` correspond + # to the part already typed when auto-completion was triggered. + # + # default: `entry_matched` bright + `entry_remaining` normal. + def auto_completion_display_entry(io : IO, entry_matched : String, entry_remaining : String) + @auto_completion.default_display_entry(io, entry_matched, entry_remaining) + end + + # Override to customize how the selected entry is displayed. + # + # default: `entry` bright on dark grey + def auto_completion_display_selected_entry(io : IO, entry : String) + @auto_completion.default_display_selected_entry(io, entry) + end + + # Override to enable line re-indenting. + # + # This methods is called each time a character is entered. + # + # You should return either: + # * `nil`: keep the line as it + # * `Int32` value: re-indent the line by an amount equal to the returned value, relatively to `indentation_level`. + # (0 to follow `indentation_level`) + # + # See `example/crystal_repl`. + # + # default: `nil` + def reindent_line(line : String) + nil + end + + def read_next(from io : IO = STDIN) : String? # ameba:disable Metrics/CyclomaticComplexity + @editor.prompt_next + + loop do + read = @char_reader.read_char(from: io) + + @editor.width, @editor.height = Term::Size.size + case read + in Char then on_char(read) + in String then on_string(read) + in .enter? then on_enter { |line| return line } + in .up? then on_up + in .ctrl_p? then on_up + in .down? then on_down + in .ctrl_n? then on_down + in .left? then on_left + in .ctrl_b? then on_left + in .right? then on_right + in .ctrl_f? then on_right + in .ctrl_up? then on_ctrl_up { |line| return line } + in .ctrl_down? then on_ctrl_down { |line| return line } + in .ctrl_left? then on_ctrl_left { |line| return line } + in .ctrl_right? then on_ctrl_right { |line| return line } + in .tab? then on_tab + in .shift_tab? then on_tab(shift_tab: true) + in .escape? then on_escape + in .alt_enter? then on_enter(alt_enter: true) { } + in .ctrl_enter? then on_enter(ctrl_enter: true) { } + in .alt_backspace? then @editor.update { word_back } + in .ctrl_backspace? then @editor.update { word_back } + in .backspace? then on_back + in .home?, .ctrl_a? then on_begin + in .end?, .ctrl_e? then on_end + in .delete? then @editor.update { delete } + in .ctrl_k? then @editor.update { delete_after_cursor } + in .ctrl_u? then @editor.update { delete_before_cursor } + in .alt_f? then @editor.move_word_forward + in .alt_b? then @editor.move_word_backward + in .ctrl_delete? then @editor.update { delete_word } + in .alt_d? then @editor.update { delete_word } + in .ctrl_c? then on_ctrl_c + in .ctrl_d? + if @editor.empty? + output.puts + return nil + else + @editor.update { delete } + end + in .eof?, .ctrl_x? + output.puts + return nil + end + + if read.is_a?(CharReader::Sequence) && (read.tab? || read.enter? || read.alt_enter? || read.shift_tab? || read.escape? || read.backspace? || read.ctrl_c?) + else + if @auto_completion.open? + auto_complete_insert_char(read) + @editor.update + end + end + end + end + + def read_loop(& : String -> _) + loop do + yield read_next || break + end + end + + # Reset the line number and close auto-completion results. + def reset + @line_number = 1 + @auto_completion.close + end + + # Clear the history and the `history_file`. + def clear_history + @history.clear + if file = self.history_file + @history.save(file) + end + end + + private def on_char(char) + @editor.update do + @editor << char + line = @editor.current_line.rstrip(' ') + + if @editor.x == line.size + # Re-indent line after typing a char. + if shift = self.reindent_line(line) + indent = self.indentation_level(@editor.expression_before_cursor) + new_indent = (indent + shift).clamp 0.. + @editor.current_line = " "*new_indent + @editor.current_line.lstrip(' ') + end + end + end + end + + private def on_string(string) + @editor.update do + @editor << string + end + end + + private def on_enter(alt_enter = false, ctrl_enter = false, &) + @auto_completion.close + if alt_enter || ctrl_enter || (@editor.cursor_on_last_line? && continue?(@editor.expression)) + @editor.update do + insert_new_line(indent: self.indentation_level(@editor.expression_before_cursor)) + end + else + submit_expr + yield @editor.expression + end + end + + private def on_up + has_moved = @editor.move_cursor_up + + if !has_moved && (new_lines = @history.up(@editor.lines)) + @editor.replace(new_lines) + @editor.move_cursor_to_end + end + end + + private def on_down + has_moved = @editor.move_cursor_down + + if !has_moved && (new_lines = @history.down(@editor.lines)) + @editor.replace(new_lines) + @editor.move_cursor_to_end_of_line(y: 0) + end + end + + private def on_left + @editor.move_cursor_left + end + + private def on_right + @editor.move_cursor_right + end + + private def on_back + auto_complete_remove_char if @auto_completion.open? + @editor.update { back } + end + + # If overridden, can yield an expression to giveback to `run`. + # This is made because the `PryInterface` in `IC` can override these hotkeys and yield + # command like `step`/`next`. + # + # TODO: It need a proper design to override hotkeys. + private def on_ctrl_up(& : String ->) + @editor.scroll_down + end + + private def on_ctrl_down(& : String ->) + @editor.scroll_up + end + + private def on_ctrl_left(& : String ->) + @editor.move_word_backward + end + + private def on_ctrl_right(& : String ->) + @editor.move_word_forward + end + + private def on_ctrl_c + @auto_completion.close + @editor.end_editing + output.puts "^C" + @history.set_to_last + @editor.prompt_next + end + + private def on_tab(shift_tab = false) + line = @editor.current_line + + # Retrieve the word under the cursor + word_begin, word_end = @editor.current_word_begin_end + current_word = line[word_begin..word_end] + + if @auto_completion.open? + if shift_tab + replacement = @auto_completion.selection_previous + else + replacement = @auto_completion.selection_next + end + else + # Get whole expression before cursor, allow auto-completion to deduce the receiver type + expr = @editor.expression_before_cursor(x: word_begin) + + # Compute auto-completion, return `replacement` (`nil` if no entry, full name if only one entry, or the begin match of entries otherwise) + replacement = @auto_completion.complete_on(current_word, expr) + + if replacement && @auto_completion.entries.size >= 2 + @auto_completion.open + end + end + + # Replace the current_word by the replacement word + if replacement + @editor.update { @editor.current_word = replacement } + end + end + + private def on_escape + @auto_completion.close + @editor.update + end + + private def on_begin + @editor.move_cursor_to_begin + end + + private def on_end + @editor.move_cursor_to_end + end + + private def auto_complete_insert_char(char) + if char.is_a? Char && !char.in?(@editor.word_delimiters) + @auto_completion.name_filter = @editor.current_word + elsif @editor.expression_scrolled? || char.is_a?(String) + @auto_completion.close + else + @auto_completion.clear + end + end + + private def auto_complete_remove_char + char = @editor.current_line[@editor.x - 1]? + if !char.in?(@editor.word_delimiters) + @auto_completion.name_filter = @editor.current_word[...-1] + else + @auto_completion.clear + end + end + + private def submit_expr(*, history = true) + formated = format(@editor.expression).try &.split('\n') + @editor.end_editing(replacement: formated) + + @line_number += @editor.lines.size + if history && save_in_history?(@editor.expression) + @history << @editor.lines + else + @history.set_to_last + end + if file = self.history_file + @history.save(file) + end + end + end +end diff --git a/lib/reply/src/reply.cr b/lib/reply/src/reply.cr new file mode 100644 index 000000000000..fa143a91deaa --- /dev/null +++ b/lib/reply/src/reply.cr @@ -0,0 +1,5 @@ +require "./reader" + +module Reply + VERSION = "0.3.0" +end diff --git a/lib/reply/src/term_cursor.cr b/lib/reply/src/term_cursor.cr new file mode 100644 index 000000000000..64eaf87db5cd --- /dev/null +++ b/lib/reply/src/term_cursor.cr @@ -0,0 +1,179 @@ +# from https://github.com/crystal-term/cursor +module Reply::Term + module Cursor + extend self + + ESC = "\e" + CSI = "\e[" + DEC_RST = "l" + DEC_SET = "h" + DEC_TCEM = "?25" + + # Make cursor visible + def show + CSI + DEC_TCEM + DEC_SET + end + + # Hide cursor + def hide + CSI + DEC_TCEM + DEC_RST + end + + # Switch off cursor for the block + def invisible(stream = STDOUT, &block) + stream.print(hide) + yield + ensure + stream.print(show) + end + + # Save current position + def save + # TODO: Should be CSI + "s" on Windows + ESC + "7" + end + + # Restore cursor position + def restore + # TODO: Should be CSI + "u" on Windows + ESC + "8" + end + + # Query cursor current position + def current + CSI + "6n" + end + + # Set the cursor absolute position + def move_to(row : Int32? = nil, column : Int32? = nil) + return CSI + "H" if row.nil? && column.nil? + CSI + "#{(column || 0) + 1};#{(row || 0) + 1}H" + end + + # Move cursor relative to its current position + def move(x, y) + (x < 0 ? backward(-x) : (x > 0 ? forward(x) : "")) + + (y < 0 ? down(-y) : (y > 0 ? up(y) : "")) + end + + # Move cursor up by n + def up(n : Int32? = nil) + CSI + "#{(n || 1)}A" + end + + # :ditto: + def cursor_up(n) + up(n) + end + + # Move the cursor down by n + def down(n : Int32? = nil) + CSI + "#{(n || 1)}B" + end + + # :ditto: + def cursor_down(n) + down(n) + end + + # Move the cursor backward by n + def backward(n : Int32? = nil) + CSI + "#{n || 1}D" + end + + # :ditto: + def cursor_backward(n) + backward(n) + end + + # Move the cursor forward by n + def forward(n : Int32? = nil) + CSI + "#{n || 1}C" + end + + # :ditto: + def cursor_forward(n) + forward(n) + end + + # Cursor moves to nth position horizontally in the current line + def column(n : Int32? = nil) + CSI + "#{n || 1}G" + end + + # Cursor moves to the nth position vertically in the current column + def row(n : Int32? = nil) + CSI + "#{n || 1}d" + end + + # Move cursor down to beginning of next line + def next_line + CSI + 'E' + column(1) + end + + # Move cursor up to beginning of previous line + def prev_line + CSI + 'A' + column(1) + end + + # Erase n characters from the current cursor position + def clear_char(n : Int32? = nil) + CSI + "#{n}X" + end + + # Erase the entire current line and return to beginning of the line + def clear_line + CSI + "2K" + column(1) + end + + # Erase from the beginning of the line up to and including + # the current cursor position. + def clear_line_before + CSI + "1K" + end + + # Erase from the current position (inclusive) to + # the end of the line + def clear_line_after + CSI + "0K" + end + + # Clear a number of lines + def clear_lines(n, direction = :up) + n.times.reduce([] of String) do |acc, i| + dir = direction == :up ? up : down + acc << clear_line + ((i == n - 1) ? "" : dir) + end.join + end + + # Clear a number of rows + def clear_rows(n, direction = :up) + clear_lines(n, direction) + end + + # Clear screen down from current position + def clear_screen_down + CSI + "J" + end + + # Clear screen up from current position + def clear_screen_up + CSI + "1J" + end + + # Clear the screen with the background colour and moves the cursor to home + def clear_screen + CSI + "2J" + end + + # Scroll display up one line + def scroll_up + ESC + "M" + end + + # Scroll display down one line + def scroll_down + ESC + "D" + end + end +end diff --git a/lib/reply/src/term_size.cr b/lib/reply/src/term_size.cr new file mode 100644 index 000000000000..d0a4c2e79699 --- /dev/null +++ b/lib/reply/src/term_size.cr @@ -0,0 +1,150 @@ +# Implementation inspired from https://github.com/crystal-term/screen/blob/master/src/term-screen.cr. +module Reply::Term::Size + extend self + + DEFAULT_SIZE = {80, 27} + + def width + self.size[0] + end + + def height + self.size[1] + end + + private def check_size(size) + if size && (cols = size[0]) && (rows = size[1]) && cols != 0 && rows != 0 + {cols, rows} + end + end + + {% if flag?(:win32) %} + def size : {Int32, Int32} + check_size(size_from_screen_buffer) || + check_size(size_from_ansicon) || + DEFAULT_SIZE + end + + # Detect terminal size Windows GetConsoleScreenBufferInfo + private def size_from_screen_buffer + LibC.GetConsoleScreenBufferInfo(LibC.GetStdHandle(LibC::STD_OUTPUT_HANDLE), out csbi) + cols = csbi.srWindow.right - csbi.srWindow.left + 1 + rows = csbi.srWindow.bottom - csbi.srWindow.top + 1 + + {cols.to_i32, rows.to_i32} + end + + # Detect terminal size from Windows ANSICON + private def size_from_ansicon + return unless ENV["ANSICON"]?.to_s =~ /\((.*)x(.*)\)/ + + rows, cols = [$2, $1].map(&.to_i) + {cols, rows} + end + {% else %} + def size : {Int32, Int32} + size_from_ioctl(0) || # STDIN + size_from_ioctl(1) || # STDOUT + size_from_ioctl(2) || # STDERR + check_size(size_from_tput) || + check_size(size_from_stty) || + check_size(size_from_env) || + DEFAULT_SIZE + end + + # Read terminal size from Unix ioctl + private def size_from_ioctl(fd) + winsize = uninitialized LibC::Winsize + ret = LibC.ioctl(fd, LibC::TIOCGWINSZ, pointerof(winsize)) + return if ret < 0 + + {winsize.ws_col.to_i32, winsize.ws_row.to_i32} + end + + # Detect terminal size from tput utility + private def size_from_tput + return unless STDOUT.tty? + + lines = `tput lines`.to_i? + cols = `tput cols`.to_i? + + {cols, lines} + rescue + nil + end + + # Detect terminal size from stty utility + private def size_from_stty + return unless STDOUT.tty? + + parts = `stty size`.split(/\s+/) + return unless parts.size > 1 + lines, cols = parts.map(&.to_i?) + + {cols, lines} + rescue + nil + end + + # Detect terminal size from environment + private def size_from_env + return unless ENV["COLUMNS"]?.to_s =~ /^\d+$/ + + rows = ENV["LINES"]? || ENV["ROWS"]? + cols = ENV["COLUMNS"]? + + {cols.try &.to_i?, rows.try &.to_i?} + end + {% end %} +end + +{% if flag?(:win32) %} + lib LibC + struct COORD + x : Int16 + y : Int16 + end + + struct SMALL_RECT + left : Int16 + top : Int16 + right : Int16 + bottom : Int16 + end + + struct CONSOLE_SCREEN_BUFFER_INFO + dwSize : COORD + dwCursorPosition : COORD + wAttributes : UInt16 + srWindow : SMALL_RECT + dwMaximumWindowSize : COORD + end + + STD_OUTPUT_HANDLE = -11 + + fun GetConsoleScreenBufferInfo(hConsoleOutput : Void*, lpConsoleScreenBufferInfo : CONSOLE_SCREEN_BUFFER_INFO*) : Void + fun GetStdHandle(nStdHandle : UInt32) : Void* + end +{% else %} + lib LibC + struct Winsize + ws_row : UShort + ws_col : UShort + ws_xpixel : UShort + ws_ypixel : UShort + end + + # TIOCGWINSZ is a magic number passed to ioctl that requests the current + # terminal window size. It is platform dependent (see + # https://stackoverflow.com/a/4286840). + {% begin %} + {% if flag?(:darwin) || flag?(:bsd) %} + TIOCGWINSZ = 0x40087468 + {% elsif flag?(:unix) %} + TIOCGWINSZ = 0x5413 + {% end %} + {% end %} + + fun ioctl(fd : Int, request : ULong, ...) : Int + end +{% end %} diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index ba507e95fdb0..ad02f5e52cd2 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -69,6 +69,9 @@ class Crystal::Repl::Interpreter # - when doing `finish`, we'd like to exit the current frame @pry_max_target_frame : Int32? + # The input reader for the pry interface, it's stored here notably to hold the history. + @pry_reader : PryReader + # The set of local variables for interpreting code. getter local_vars : LocalVars @@ -123,6 +126,9 @@ class Crystal::Repl::Interpreter @block_level = 0 @compiled_def = nil + + @pry_reader = PryReader.new + @pry_reader.color = @context.program.color? end def self.new(interpreter : Interpreter, compiled_def : CompiledDef, stack : Pointer(UInt8), block_level : Int32) @@ -141,6 +147,9 @@ class Crystal::Repl::Interpreter @call_stack_leave_index = @call_stack.size @compiled_def = compiled_def + + @pry_reader = PryReader.new + @pry_reader.color = @context.program.color? end # Interprets the give node under the given context. @@ -1230,12 +1239,8 @@ class Crystal::Repl::Interpreter interpreter = Interpreter.new(self, compiled_def, local_vars, closure_context, stack_bottom, block_level) - prompt = Prompt.new(@context, show_nest: false) - while @pry - prefix = String.build do |io| - io.print "pry" - io.print '(' + @pry_reader.prompt_info = String.build do |io| unless owner.is_a?(Program) if owner.metaclass? io.print owner.instance_type @@ -1246,10 +1251,9 @@ class Crystal::Repl::Interpreter end end io.print compiled_def.def.name - io.print ')' end - input = prompt.prompt(prefix) + input = @pry_reader.read_next unless input self.pry = false break @@ -1284,10 +1288,13 @@ class Crystal::Repl::Interpreter end begin - line_node = prompt.parse( - input: input, + parser = Parser.new( + input, + string_pool: @context.program.string_pool, var_scopes: [meta_vars.keys.to_set], ) + line_node = parser.parse + next unless line_node main_visitor = MainVisitor.new(from_main_visitor: main_visitor) @@ -1317,7 +1324,8 @@ class Crystal::Repl::Interpreter # to their new type) local_vars = interpreter.local_vars - prompt.display(value) + print " => " + puts SyntaxHighlighter::Colorize.highlight!(value.to_s) rescue ex : EscapingException print "Unhandled exception: " print ex diff --git a/src/compiler/crystal/interpreter/prompt.cr b/src/compiler/crystal/interpreter/prompt.cr deleted file mode 100644 index ea6077cc4aa5..000000000000 --- a/src/compiler/crystal/interpreter/prompt.cr +++ /dev/null @@ -1,116 +0,0 @@ -# Allows reading a prompt for the interpreter. -class Crystal::Repl::Prompt - property line_number : Int32 - - def initialize(@context : Context, @show_nest : Bool) - @buffer = "" - @nest = 0 - @incomplete = false - @line_number = 1 - end - - # Asks for a line of input, prefixed with the given prefix. - # Returns nil if the user pressed CTRL-C. - def prompt(prefix) : String? - prompt = String.build do |io| - io.print prefix - if @show_nest - io.print ':' - io.print @nest - end - io.print(@incomplete ? '*' : '>') - io.print ' ' - if @nest == 0 && @incomplete - io.print " " - else - io.print " " * @nest if @nest > 0 - end - end - - print prompt - line = gets - return unless line - - if @context.program.color? - # Go back one line to print it again colored - print "\033[F" - print prompt - - colored_line = line - begin - colored_line = Crystal::SyntaxHighlighter::Colorize.highlight(colored_line) - rescue - # Ignore highlight errors - end - - puts colored_line - end - - new_buffer = - if @buffer.empty? - line - else - "#{@buffer}\n#{line}" - end - - new_buffer - end - - # Parses the given input with the given var_scopes. - # The input must be that returned from `#prompt`. - # Returns a parsed ASTNode if the input was valid Crystal syntax. - # If the input was partial Crystal syntax, `nil` is returned - # but the partial input is remembered. Next time `#prompt` is called, - # the returned value there will be the new complete input (what there - # was before plus the new input, separated by a new line). - def parse(input : String, var_scopes : Array(Set(String))) : ASTNode? - parser = Parser.new( - input, - string_pool: @context.program.string_pool, - var_scopes: var_scopes, - ) - - begin - node = parser.parse - - @nest = 0 - @buffer = "" - @line_number += 1 - @incomplete = false - - parser.warnings.report(STDOUT) - - node - rescue ex : Crystal::SyntaxException - # TODO: improve this - case ex.message - when "unexpected token: EOF", - "expecting identifier 'end', not 'EOF'" - @nest = parser.type_nest + parser.def_nest + parser.fun_nest - @buffer = input - @line_number += 1 - @incomplete = @nest == 0 - when "expecting token ']', not 'EOF'", - "unterminated array literal", - "unterminated hash literal", - "unterminated tuple literal" - @nest = parser.type_nest + parser.def_nest + parser.fun_nest - @buffer = input - @line_number += 1 - @incomplete = true - else - puts "Error: #{ex.message}" - @nest = 0 - @buffer = "" - @incomplete = false - end - nil - end - end - - # Displays a value, preceding it with "=> ". - def display(value : Value) - print "=> " - puts value - end -end diff --git a/src/compiler/crystal/interpreter/pry_reader.cr b/src/compiler/crystal/interpreter/pry_reader.cr new file mode 100644 index 000000000000..d7b57fcfaded --- /dev/null +++ b/src/compiler/crystal/interpreter/pry_reader.cr @@ -0,0 +1,34 @@ +require "./repl_reader" + +class Crystal::PryReader < Crystal::ReplReader + property prompt_info = "" + + def prompt(io, line_number, color?) + io << "pry(" + io << @prompt_info + io << ')' + + io.print(@incomplete ? '*' : '>') + io << ' ' + end + + def continue?(expression : String) : Bool + if expression == "*s" || expression == "*d" + @incomplete = false + else + super + end + end + + def on_ctrl_down + yield "next" + end + + def on_ctrl_left + yield "finish" + end + + def on_ctrl_right + yield "step" + end +end diff --git a/src/compiler/crystal/interpreter/repl.cr b/src/compiler/crystal/interpreter/repl.cr index 6a776f66c22f..93b3a6cef65c 100644 --- a/src/compiler/crystal/interpreter/repl.cr +++ b/src/compiler/crystal/interpreter/repl.cr @@ -6,7 +6,6 @@ class Crystal::Repl def initialize @program = Program.new @context = Context.new(@program) - @line_number = 1 @main_visitor = MainVisitor.new(@program) @interpreter = Interpreter.new(@context) @@ -15,52 +14,33 @@ class Crystal::Repl def run load_prelude - prompt = Prompt.new(@context, show_nest: true) + reader = ReplReader.new(repl: self) + reader.color = @context.program.color? - while true - input = prompt.prompt("icr:#{prompt.line_number}") - unless input - # Explicitly call exit on ctrl+D so at_exit handlers run - interpret_exit + reader.read_loop do |expression| + case expression + when "exit" break - end - - if input.blank? - prompt.line_number += 1 - next - end + when .presence + parser = new_parser(expression) + parser.warnings.report(STDOUT) - node = prompt.parse( - input: input, - var_scopes: [@interpreter.local_vars.names_at_block_level_zero.to_set], - ) - next unless node + node = parser.parse + next unless node - begin value = interpret(node) - prompt.display(value) - rescue ex : EscapingException - @nest = 0 - @buffer = "" - @line_number += 1 - - print "Unhandled exception: " - print ex - rescue ex : Crystal::CodeError - @nest = 0 - @buffer = "" - @line_number += 1 - - ex.color = true - ex.error_trace = true - puts ex - rescue ex : Exception - @nest = 0 - @buffer = "" - @line_number += 1 - - ex.inspect_with_backtrace(STDOUT) + print " => " + puts SyntaxHighlighter::Colorize.highlight!(value.to_s) end + rescue ex : EscapingException + print "Unhandled exception: " + print ex + rescue ex : Crystal::CodeError + ex.color = @context.program.color? + ex.error_trace = true + puts ex + rescue ex : Exception + ex.inspect_with_backtrace(STDOUT) end end @@ -155,4 +135,12 @@ class Crystal::Repl puts "Error while calling Crystal.exit: #{ex.message}" end end + + protected def new_parser(source) + Parser.new( + source, + string_pool: @context.program.string_pool, + var_scopes: [@interpreter.local_vars.names_at_block_level_zero.to_set] + ) + end end diff --git a/src/compiler/crystal/interpreter/repl_reader.cr b/src/compiler/crystal/interpreter/repl_reader.cr new file mode 100644 index 000000000000..535ec53e64a9 --- /dev/null +++ b/src/compiler/crystal/interpreter/repl_reader.cr @@ -0,0 +1,131 @@ +require "reply" + +class Crystal::ReplReader < Reply::Reader + KEYWORDS = %w( + abstract alias annotation asm begin break case class + def do else elsif end ensure enum extend for fun + if in include instance_sizeof lib macro module + next of offsetof out pointerof private protected require + rescue return select sizeof struct super + then type typeof union uninitialized unless until + verbatim when while with yield + ) + METHOD_KEYWORDS = %w(as as? is_a? nil? responds_to?) + CONTINUE_ERROR = [ + "expecting identifier 'end', not 'EOF'", + "expecting token 'CONST', not 'EOF'", + "expecting any of these tokens: IDENT, CONST, `, <<, <, <=, ==, ===, !=, =~, !~, >>, >, >=, +, -, *, /, //, !, ~, %, &, |, ^, **, [], []?, []=, <=>, &+, &-, &*, &** (not 'EOF')", + "expecting any of these tokens: ;, NEWLINE (not 'EOF')", + "expecting token ')', not 'EOF'", + "expecting token ']', not 'EOF'", + "expecting token '}', not 'EOF'", + "expecting token '%}', not 'EOF'", + "expecting token '}', not ','", + "expected '}' or named tuple name, not EOF", + "unexpected token: NEWLINE", + "unexpected token: EOF", + "unexpected token: EOF (expecting when, else or end)", + "unexpected token: EOF (expecting ',', ';' or '\n')", + "Unexpected EOF on heredoc identifier", + "unterminated parenthesized expression", + "unterminated call", + "Unterminated string literal", + "unterminated hash literal", + "Unterminated command literal", + "unterminated array literal", + "unterminated tuple literal", + "unterminated macro", + "Unterminated string interpolation", + "invalid trailing comma in call", + "unknown token: '\\u{0}'", + ] + @incomplete = false + @repl : Repl? + + def initialize(@repl = nil) + super() + + # `"`, `:`, `'`, are not a delimiter because symbols and strings are treated as one word. + # '=', !', '?' are not a delimiter because they could make part of method name. + self.word_delimiters = {{" \n\t+-*/,;@&%<>^\\[](){}|.~".chars}} + end + + def prompt(io : IO, line_number : Int32, color? : Bool) : Nil + io << "icr:" + io << line_number + + io.print(@incomplete ? '*' : '>') + io << ' ' + end + + def highlight(expression : String) : String + SyntaxHighlighter::Colorize.highlight!(expression) + end + + def continue?(expression : String) : Bool + new_parser(expression).parse + @incomplete = false + false + rescue e : CodeError + @incomplete = e.message.in?(CONTINUE_ERROR) + if (message = e.message) && message.matches? /Unterminated heredoc: can't find ".*" anywhere before the end of file/ + @incomplete = true + end + + @incomplete + end + + def format(expression : String) : String? + Crystal.format(expression).chomp rescue nil + end + + def indentation_level(expression_before_cursor : String) : Int32? + parser = new_parser(expression_before_cursor) + parser.parse rescue nil + + parser.type_nest + parser.def_nest + parser.fun_nest + end + + def reindent_line(line) + case line.strip + when "end", ")", "]", "}" + 0 + when "else", "elsif", "rescue", "ensure", "in", "when" + -1 + else + nil + end + end + + def save_in_history?(expression : String) : Bool + !expression.blank? + end + + def auto_complete(name_filter : String, expression : String) : {String, Array(String)} + if expression.ends_with? '.' + return "Keywords:", METHOD_KEYWORDS.dup + else + return "Keywords:", KEYWORDS.dup + end + end + + def auto_completion_display_title(io : IO, title : String) + io << title + end + + def auto_completion_display_selected_entry(io : IO, entry : String) + io << entry.colorize.red.bright + end + + def auto_completion_display_entry(io : IO, entry_matched : String, entry_remaining : String) + io << entry_matched.colorize.red.bright << entry_remaining + end + + private def new_parser(source) + if repl = @repl + repl.new_parser(source) + else + Parser.new(source) + end + end +end From a0fd8fe8e917e3d9ff27b7ea14f3915b59483d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 28 Dec 2022 20:33:48 +0100 Subject: [PATCH 0209/1551] Fix warning on space before colon with anonymous block arg (#12869) --- spec/compiler/parser/warnings_spec.cr | 2 ++ src/compiler/crystal/syntax/parser.cr | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/compiler/parser/warnings_spec.cr b/spec/compiler/parser/warnings_spec.cr index af816e5828ab..8fdf527ce2c7 100644 --- a/spec/compiler/parser/warnings_spec.cr +++ b/spec/compiler/parser/warnings_spec.cr @@ -41,11 +41,13 @@ describe "Parser warnings" do it "in block param type restriction" do assert_parser_warning("def foo(&block: Foo)\nend", "warning in test.cr:1\nWarning: space required before colon in type restriction (run `crystal tool format` to fix this)") assert_no_parser_warning("def foo(&block : Foo)\nend") + assert_no_parser_warning("def foo(&@foo)\nend") end it "in anonymous block param type restriction" do assert_parser_warning("def foo(&: Foo)\nend", "warning in test.cr:1\nWarning: space required before colon in type restriction (run `crystal tool format` to fix this)") assert_no_parser_warning("def foo(& : Foo)\nend") + assert_no_parser_warning("def foo(&)\nend") end it "in type declaration" do diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index f8b4f7ffd901..1a06517d337a 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3810,10 +3810,12 @@ module Crystal if @token.type.op_amp? next_token - unless @token.type.ident? || @token.type.space? + space_after_amp = @token.type.space? + skip_space_or_newline + + if @token.type.op_colon? && !space_after_amp # anonymous block arg without space warnings.add_warning_at @token.location, "space required before colon in type restriction (run `crystal tool format` to fix this)" end - skip_space_or_newline block_param = parse_block_param(extra_assigns, annotations) skip_space_or_newline From b3d2d5fadafe8e363086d78be3d7c3e41d1084d7 Mon Sep 17 00:00:00 2001 From: maiha Date: Fri, 30 Dec 2022 07:24:46 +0900 Subject: [PATCH 0210/1551] examples: fix (2022-12) (#12870) --- src/complex.cr | 30 +++++++++++++++--------------- src/hash.cr | 8 ++++---- src/http/headers.cr | 2 +- src/http/status.cr | 4 ++-- src/json/serialization.cr | 5 +++-- src/json/to_json.cr | 8 ++++---- src/log.cr | 6 +++--- src/named_tuple.cr | 16 ++++++++-------- src/semantic_version.cr | 10 +++++++--- src/string.cr | 2 +- src/string/utf16.cr | 8 +++++--- src/time.cr | 8 ++++---- src/time/span.cr | 2 +- src/uuid/yaml.cr | 2 +- src/yaml/serialization.cr | 3 ++- 15 files changed, 61 insertions(+), 53 deletions(-) diff --git a/src/complex.cr b/src/complex.cr index 491e06892688..6fd1d2a2faf2 100644 --- a/src/complex.cr +++ b/src/complex.cr @@ -6,11 +6,11 @@ # ``` # require "complex" # -# Complex.new(1, 0) # => 1.0 + 0.0i -# Complex.new(5, -12) # => 5.0 - 12.0i +# Complex.new(1, 0) # => 1.0 + 0.0.i +# Complex.new(5, -12) # => 5.0 - 12.0.i # -# 1.to_c # => 1.0 + 0.0i -# 1.i # => 0.0 + 1.0i +# 1.to_c # => 1.0 + 0.0.i +# 1.i # => 0.0 + 1.0.i # ``` struct Complex # Returns the real part. @@ -151,9 +151,9 @@ struct Complex # ``` # require "complex" # - # Complex.new(7, -24).sign # => (0.28 - 0.96i) - # Complex.new(1.0 / 0.0, 24).sign # => (1.0 + 0.0i) - # Complex.new(-0.0, +0.0).sign # => (-0.0 + 0.0i) + # Complex.new(7, -24).sign # => (0.28 - 0.96.i) + # Complex.new(1.0 / 0.0, 24).sign # => (1.0 + 0.0.i) + # Complex.new(-0.0, +0.0).sign # => (-0.0 + 0.0.i) # ``` def sign : Complex return self if zero? @@ -199,8 +199,8 @@ struct Complex # ``` # require "complex" # - # Complex.new(42, 2).conj # => 42.0 - 2.0i - # Complex.new(42, -2).conj # => 42.0 + 2.0i + # Complex.new(42, 2).conj # => 42.0 - 2.0.i + # Complex.new(42, -2).conj # => 42.0 + 2.0.i # ``` def conj : Complex Complex.new(@real, -@imag) @@ -353,7 +353,7 @@ module Math # ``` # require "complex" # - # Math.exp(4 + 2.i) # => -22.720847417619233 + 49.645957334580565i + # Math.exp(4 + 2.i) # => -22.720847417619233 + 49.645957334580565.i # ``` def exp(value : Complex) : Complex r = exp(value.real) @@ -365,7 +365,7 @@ module Math # ``` # require "complex" # - # Math.log(4 + 2.i) # => 1.4978661367769956 + 0.4636476090008061i + # Math.log(4 + 2.i) # => 1.4978661367769956 + 0.4636476090008061.i # ``` def log(value : Complex) : Complex Complex.new(Math.log(value.abs), value.phase) @@ -376,7 +376,7 @@ module Math # ``` # require "complex" # - # Math.log2(4 + 2.i) # => 2.1609640474436813 + 0.6689021062254881i + # Math.log2(4 + 2.i) # => 2.1609640474436813 + 0.6689021062254881.i # ``` def log2(value : Complex) : Complex log(value) / LOG2 @@ -387,7 +387,7 @@ module Math # ``` # require "complex" # - # Math.log10(4 + 2.i) # => 0.6505149978319906 + 0.20135959813668655i + # Math.log10(4 + 2.i) # => 0.6505149978319906 + 0.20135959813668655.i # ``` def log10(value : Complex) : Complex log(value) / LOG10 @@ -399,7 +399,7 @@ module Math # ``` # require "complex" # - # Math.sqrt(4 + 2.i) # => 2.0581710272714924 + 0.48586827175664565i + # Math.sqrt(4 + 2.i) # => 2.0581710272714924 + 0.48586827175664565.i # ``` # # Although the imaginary number is defined as i = sqrt(-1), @@ -409,7 +409,7 @@ module Math # # ``` # Math.sqrt(-1.0) # => -NaN - # Math.sqrt(-1.0 + 0.0.i) # => 0.0 + 1.0i + # Math.sqrt(-1.0 + 0.0.i) # => 0.0 + 1.0.i # ``` def sqrt(value : Complex) : Complex r = value.abs diff --git a/src/hash.cr b/src/hash.cr index 63064498c55f..d29064f57b8c 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -1102,8 +1102,8 @@ class Hash(K, V) # # ``` # h = {"a" => {"b" => [10, 20, 30]}} - # h.dig? "a", "b" # => [10, 20, 30] - # h.dig? "a", "b", "c", "d", "e" # => nil + # h.dig? "a", "b" # => [10, 20, 30] + # h.dig? "a", "x" # => nil # ``` def dig?(key : K, *subkeys) if (value = self[key]?) && value.responds_to?(:dig?) @@ -1121,8 +1121,8 @@ class Hash(K, V) # # ``` # h = {"a" => {"b" => [10, 20, 30]}} - # h.dig "a", "b" # => [10, 20, 30] - # h.dig "a", "b", "c", "d", "e" # raises KeyError + # h.dig "a", "b" # => [10, 20, 30] + # h.dig "a", "x" # raises KeyError # ``` def dig(key : K, *subkeys) if (value = self[key]) && value.responds_to?(:dig) diff --git a/src/http/headers.cr b/src/http/headers.cr index c88018082a7d..97cdcf95ab8b 100644 --- a/src/http/headers.cr +++ b/src/http/headers.cr @@ -307,7 +307,7 @@ struct HTTP::Headers # The serialization does *not* include a double CRLF sequence at the end. # # ``` - # headers = HTTP::Headers{"foo" => "bar", "baz" => %w[qux qox]}) + # headers = HTTP::Headers{"foo" => "bar", "baz" => %w[qux qox]} # headers.serialize # => "foo: bar\r\nbaz: qux\r\nbaz: qox\r\n" # ``` def serialize : String diff --git a/src/http/status.cr b/src/http/status.cr index aa90109c8806..f3920b808e82 100644 --- a/src/http/status.cr +++ b/src/http/status.cr @@ -74,8 +74,8 @@ enum HTTP::Status # ``` # require "http/status" # - # HTTP::Status.new(100) # => CONTINUE - # HTTP::Status.new(202) # => ACCEPTED + # HTTP::Status.new(100) # => HTTP::Status::CONTINUE + # HTTP::Status.new(202) # => HTTP::Status::ACCEPTED # HTTP::Status.new(123) # => 123 # HTTP::Status.new(1000) # raises ArgumentError # ``` diff --git a/src/json/serialization.cr b/src/json/serialization.cr index 0e7ff1782737..001fe9b0bc54 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -98,8 +98,9 @@ module JSON # @a : Int32 # end # - # a = A.from_json(%({"a":1,"b":2})) # => A(@json_unmapped={"b" => 2_i64}, @a=1) - # a.to_json # => {"a":1,"b":2} + # a = A.from_json(%({"a":1,"b":2})) # => A(@json_unmapped={"b" => 2}, @a=1) + # a.json_unmapped["b"].raw.class # => Int64 + # a.to_json # => %({"a":1,"b":2}) # ``` # # diff --git a/src/json/to_json.cr b/src/json/to_json.cr index af534d790e00..18d4df75ec2f 100644 --- a/src/json/to_json.cr +++ b/src/json/to_json.cr @@ -353,8 +353,8 @@ end # end # # timestamp = TimestampHash.from_json(%({"birthdays":{"foo":1459859781,"bar":1567628762}})) -# timestamp.birthdays # => {"foo" => 2016-04-05 12:36:21 UTC, "bar" => 2019-09-04 20:26:02 UTC)} -# timestamp.to_json # => {"birthdays":{"foo":1459859781,"bar":1567628762}} +# timestamp.birthdays # => {"foo" => 2016-04-05 12:36:21 UTC, "bar" => 2019-09-04 20:26:02 UTC} +# timestamp.to_json # => %({"birthdays":{"foo":1459859781,"bar":1567628762}}) # ``` # # `JSON::HashValueConverter.new` should be used if the nested converter is also @@ -371,8 +371,8 @@ end # end # # timestamp = TimestampHash.from_json(%({"birthdays":{"foo":"Apr 5, 2016","bar":"Sep 4, 2019"}})) -# timestamp.birthdays # => {"foo" => 2016-04-05 00:00:00 UTC, "bar" => 2019-09-04 00:00:00 UTC)} -# timestamp.to_json # => {"birthdays":{"foo":"Apr 5, 2016","bar":"Sep 4, 2019"}} +# timestamp.birthdays # => {"foo" => 2016-04-05 00:00:00 UTC, "bar" => 2019-09-04 00:00:00 UTC} +# timestamp.to_json # => %({"birthdays":{"foo":"Apr 5, 2016","bar":"Sep 4, 2019"}}) # ``` # # This implies that `JSON::HashValueConverter(T)` and diff --git a/src/log.cr b/src/log.cr index 4055162d006d..428c8e19fd11 100644 --- a/src/log.cr +++ b/src/log.cr @@ -40,7 +40,7 @@ # # ``` # module DB -# Log = ::Log.for("db") # => Log for db source +# Log = ::Log.for("db") # Log for db source # # def do_something # Log.info { "this is logged in db source" } @@ -58,7 +58,7 @@ # # ``` # class DB::Pool -# Log = DB::Log.for("pool") # => Log for db.pool source +# Log = DB::Log.for("pool") # Log for db.pool source # end # ``` # @@ -70,7 +70,7 @@ # # ``` # module DB -# Log = ::Log.for(self) # => Log for db source +# Log = ::Log.for(self) # Log for db source # end # ``` # diff --git a/src/named_tuple.cr b/src/named_tuple.cr index 0c8c52e5d0a1..6497504b04ed 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -31,8 +31,8 @@ # # ``` # language = {name: "Crystal", year: 2011} -# language[:name]? # => 1 -# typeof(language[:name]?) # => Int32 +# language[:name]? # => "Crystal" +# typeof(language[:name]?) # => String # ``` # # `NamedTuple`'s own instance classes may also be indexed in a similar manner, @@ -243,9 +243,9 @@ struct NamedTuple # Returns `nil` if not found. # # ``` - # h = {a: {b: [10, 20, 30]}} - # h.dig? "a", "b" # => [10, 20, 30] - # h.dig? "a", "b", "c", "d", "e" # => nil + # h = {a: {b: {c: [10, 20]}}, x: {a: "b"}} + # h.dig? :a, :b, :c # => [10, 20] + # h.dig? "a", "x" # => nil # ``` def dig?(key : Symbol | String, *subkeys) if (value = self[key]?) && value.responds_to?(:dig?) @@ -262,9 +262,9 @@ struct NamedTuple # raises `KeyError`. # # ``` - # h = {a: {b: [10, 20, 30]}} - # h.dig "a", "b" # => [10, 20, 30] - # h.dig "a", "b", "c", "d", "e" # raises KeyError + # h = {a: {b: {c: [10, 20]}}, x: {a: "b"}} + # h.dig :a, :b, :c # => [10, 20] + # h.dig "a", "x" # raises KeyError # ``` def dig(key : Symbol | String, *subkeys) if (value = self[key]) && value.responds_to?(:dig) diff --git a/src/semantic_version.cr b/src/semantic_version.cr index 8c35e9998eae..308f914c63af 100644 --- a/src/semantic_version.cr +++ b/src/semantic_version.cr @@ -89,8 +89,9 @@ struct SemanticVersion # require "semantic_version" # # current_version = SemanticVersion.new 1, 1, 1, "rc" - # current_version.copy_with(patch: 2) # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=["rc"])) - # current_version.copy_with(prerelease: nil) # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) + # current_version.copy_with(patch: 2) # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=["rc"])) + # current_version.copy_with(prerelease: nil) # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=1, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) + # ``` def copy_with(major : Int32 = @major, minor : Int32 = @minor, patch : Int32 = @patch, prerelease : String | Prerelease | Nil = @prerelease, build : String? = @build) SemanticVersion.new major, minor, patch, prerelease, build end @@ -102,6 +103,7 @@ struct SemanticVersion # # current_version = SemanticVersion.new 1, 1, 1, "rc" # current_version.bump_major # => SemanticVersion(@build=nil, @major=2, @minor=0, @patch=0, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) + # ``` def bump_major copy_with(major: major + 1, minor: 0, patch: 0, prerelease: nil, build: nil) end @@ -113,6 +115,7 @@ struct SemanticVersion # # current_version = SemanticVersion.new 1, 1, 1, "rc" # current_version.bump_minor # => SemanticVersion(@build=nil, @major=1, @minor=2, @patch=0, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) + # ``` def bump_minor copy_with(minor: minor + 1, patch: 0, prerelease: nil, build: nil) end @@ -125,7 +128,8 @@ struct SemanticVersion # # current_version = SemanticVersion.new 1, 1, 1, "rc" # next_patch = current_version.bump_patch # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=1, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) - # next_patch.bump_patch # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) + # next_patch.bump_patch # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=[])) + # ``` def bump_patch if prerelease.identifiers.empty? copy_with(patch: patch + 1, prerelease: nil, build: nil) diff --git a/src/string.cr b/src/string.cr index 0b7e36fbc144..0d25eb667eec 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3045,7 +3045,7 @@ class String # "abcdef" == "abcdefg" # => false # "abcdef" == "ABCDEF" # => false # - # "abcdef".compare("ABCDEF", case_sensitive: false) == 0 # => true + # "abcdef".compare("ABCDEF", case_insensitive: true) == 0 # => true # ``` def ==(other : self) : Bool return true if same?(other) diff --git a/src/string/utf16.cr b/src/string/utf16.cr index c97b9436dcc6..bf79b073a2fd 100644 --- a/src/string/utf16.cr +++ b/src/string/utf16.cr @@ -96,10 +96,12 @@ class String # # ``` # slice = Slice[104_u16, 105_u16, 0_u16, 55296_u16, 56485_u16, 0_u16] - # String.from_utf16(slice) # => "hi\0000𐂥" + # String.from_utf16(slice) # => "hi\0000𐂥\u0000" # pointer = slice.to_unsafe - # string, pointer = String.from_utf16(pointer) # => "hi" - # string, pointer = String.from_utf16(pointer) # => "𐂥" + # string, pointer = String.from_utf16(pointer) + # string # => "hi" + # string, pointer = String.from_utf16(pointer) + # string # => "𐂥" # ``` # # Invalid values are encoded using the unicode replacement char with diff --git a/src/time.cr b/src/time.cr index 737ba23e8ad5..1cf7a9be7536 100644 --- a/src/time.cr +++ b/src/time.cr @@ -37,12 +37,12 @@ require "crystal/system/time" # # ``` # time = Time.utc(2016, 2, 15, 10, 20, 30) -# time.to_s # => 2016-02-15 10:20:30 UTC +# time.to_s # => "2016-02-15 10:20:30 UTC" # time = Time.local(2016, 2, 15, 10, 20, 30, location: Time::Location.load("Europe/Berlin")) -# time.to_s # => 2016-02-15 10:20:30 +01:00 Europe/Berlin +# time.to_s # => "2016-02-15 10:20:30 +01:00" # # The time-of-day can be omitted and defaults to midnight (start of day): # time = Time.utc(2016, 2, 15) -# time.to_s # => 2016-02-15 00:00:00 UTC +# time.to_s # => "2016-02-15 00:00:00 UTC" # ``` # # ### Retrieving Time Information @@ -660,7 +660,7 @@ struct Time # change: # # ``` - # start = Time.new(2017, 10, 28, 13, 37, location: Time::Location.load("Europe/Berlin")) + # start = Time.local(2017, 10, 28, 13, 37, location: Time::Location.load("Europe/Berlin")) # one_day_later = start.shift days: 1 # # one_day_later - start # => 25.hours diff --git a/src/time/span.cr b/src/time/span.cr index 6cca5ce6fe9b..d4ae3e39f1b1 100644 --- a/src/time/span.cr +++ b/src/time/span.cr @@ -105,7 +105,7 @@ struct Time::Span # # ``` # Time::Span.new(days: 1) # => 1.00:00:00 - # Time::Span.new(days: 1, hours: 2, minutes: 3) # => 01:02:03 + # Time::Span.new(days: 1, hours: 2, minutes: 3) # => 1.02:03:00 # Time::Span.new(days: 1, hours: 2, minutes: 3, seconds: 4, nanoseconds: 5) # => 1.02:03:04.000000005 # ``` def self.new(*, days : Int = 0, hours : Int = 0, minutes : Int = 0, seconds : Int = 0, nanoseconds : Int = 0) diff --git a/src/uuid/yaml.cr b/src/uuid/yaml.cr index c0e09d8b5631..2c7eec1f5a45 100644 --- a/src/uuid/yaml.cr +++ b/src/uuid/yaml.cr @@ -17,7 +17,7 @@ struct UUID # property id : UUID # end # - # example = Example.from_yaml("uuid: 50a11da6-377b-4bdf-b9f0-076f9db61c93") + # example = Example.from_yaml("id: 50a11da6-377b-4bdf-b9f0-076f9db61c93") # example.id # => UUID(50a11da6-377b-4bdf-b9f0-076f9db61c93) # ``` def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index 6b08d525d171..bbd85a24eec4 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -97,7 +97,8 @@ module YAML # @a : Int32 # end # - # a = A.from_yaml("---\na: 1\nb: 2\n") # => A(@yaml_unmapped={"b" => 2_i64}, @a=1) + # a = A.from_yaml("---\na: 1\nb: 2\n") # => A(@yaml_unmapped={"b" => 2}, @a=1) + # a.yaml_unmapped["b"].raw.class # => Int64 # a.to_yaml # => "---\na: 1\nb: 2\n" # ``` # From 6ee88d6de6df8d7189cbfeabdd6824fd3cfff0da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 3 Jan 2023 18:04:23 +0100 Subject: [PATCH 0211/1551] Add specs for `system` macro method (#12885) --- spec/compiler/macro/macro_methods_spec.cr | 57 +++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 562185f0ed8e..427304789bb2 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -10,6 +10,37 @@ private def declare_class_var(container : ClassVarContainer, name, var_type : Ty container.class_vars[name] = var end +private def exit_code_command(code) + {% if flag?(:win32) %} + %(cmd.exe /c "exit #{code}") + {% else %} + case code + when 0 + "true" + when 1 + "false" + else + "/bin/sh -c 'exit #{code}'" + end + {% end %} +end + +private def shell_command(command) + {% if flag?(:win32) %} + "cmd.exe /c #{Process.quote(command)}" + {% else %} + "/bin/sh -c #{Process.quote(command)}" + {% end %} +end + +private def newline + {% if flag?(:win32) %} + "\r\n" + {% else %} + "\n" + {% end %} +end + module Crystal describe Macro do describe "node methods" do @@ -3175,6 +3206,32 @@ module Crystal end end + describe ".system" do + it "command does not exist" do + # FIXME: This inconsistency between Windows and POSIX is tracked in #12873 + expect_raises( + {{ flag?(:win32) ? File::NotFoundError : Crystal::TypeException }}, + {{ flag?(:win32) ? "Error executing process: 'commanddoesnotexist'" : "error executing command: commanddoesnotexist" }} + ) do + semantic %({{ `commanddoesnotexist` }}) + end + end + + it "successful command" do + assert_macro %({{ `#{exit_code_command(0)}` }}), "" + end + + it "successful command with output" do + assert_macro %({{ `#{shell_command("echo foobar")}` }}), "foobar#{newline}" + end + + it "failing command" do + assert_error %({{ `#{exit_code_command(1)}` }}), "error executing command: #{exit_code_command(1)}, got exit status 1" + assert_error %({{ `#{exit_code_command(2)}` }}), "error executing command: #{exit_code_command(2)}, got exit status 2" + assert_error %({{ `#{exit_code_command(127)}` }}), "error executing command: #{exit_code_command(127)}, got exit status 127" + end + end + describe "error reporting" do it "reports wrong number of arguments" do assert_macro_error %({{[1, 2, 3].push}}), "wrong number of arguments for macro 'ArrayLiteral#push' (given 0, expected 1)" From 74a7cd2a75f982bb94ce85094d39f73f971de564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 3 Jan 2023 23:41:05 +0100 Subject: [PATCH 0212/1551] Organize `Process` specs (#12889) Co-authored-by: Beta Ziliani --- spec/std/process/utils_spec.cr | 120 +++++++ spec/std/process_spec.cr | 577 ++++++++++++++------------------- 2 files changed, 356 insertions(+), 341 deletions(-) create mode 100644 spec/std/process/utils_spec.cr diff --git a/spec/std/process/utils_spec.cr b/spec/std/process/utils_spec.cr new file mode 100644 index 000000000000..e971a0209e4a --- /dev/null +++ b/spec/std/process/utils_spec.cr @@ -0,0 +1,120 @@ +require "spec" + +describe Process do + describe ".executable_path" do + it "searches executable" do + Process.executable_path.should be_a(String | Nil) + end + end + + describe ".quote_posix" do + it { Process.quote_posix("").should eq "''" } + it { Process.quote_posix(" ").should eq "' '" } + it { Process.quote_posix("$hi").should eq "'$hi'" } + it { Process.quote_posix(orig = "aZ5+,-./:=@_").should eq orig } + it { Process.quote_posix(orig = "cafe").should eq orig } + it { Process.quote_posix("café").should eq "'café'" } + it { Process.quote_posix("I'll").should eq %('I'"'"'ll') } + it { Process.quote_posix("'").should eq %(''"'"'') } + it { Process.quote_posix("\\").should eq "'\\'" } + + context "join" do + it { Process.quote_posix([] of String).should eq "" } + it { Process.quote_posix(["my file.txt", "another.txt"]).should eq "'my file.txt' another.txt" } + it { Process.quote_posix(["foo ", "", " ", " bar"]).should eq "'foo ' '' ' ' ' bar'" } + it { Process.quote_posix(["foo'", "\"bar"]).should eq %('foo'"'"'' '"bar') } + end + end + + describe ".quote_windows" do + it { Process.quote_windows("").should eq %("") } + it { Process.quote_windows(" ").should eq %(" ") } + it { Process.quote_windows(orig = "%hi%").should eq orig } + it { Process.quote_windows(%q(C:\"foo" project.txt)).should eq %q("C:\\\"foo\" project.txt") } + it { Process.quote_windows(%q(C:\"foo"_project.txt)).should eq %q(C:\\\"foo\"_project.txt) } + it { Process.quote_windows(%q(C:\Program Files\Foo Bar\foobar.exe)).should eq %q("C:\Program Files\Foo Bar\foobar.exe") } + it { Process.quote_windows(orig = "café").should eq orig } + it { Process.quote_windows(%(")).should eq %q(\") } + it { Process.quote_windows(%q(a\\b\ c\)).should eq %q("a\\b\ c\\") } + it { Process.quote_windows(orig = %q(a\\b\c\)).should eq orig } + + context "join" do + it { Process.quote_windows([] of String).should eq "" } + it { Process.quote_windows(["my file.txt", "another.txt"]).should eq %("my file.txt" another.txt) } + it { Process.quote_windows(["foo ", "", " ", " bar"]).should eq %("foo " "" " " " bar") } + end + end + + {% if flag?(:unix) %} + describe ".parse_arguments" do + it "uses the native platform rules" do + Process.parse_arguments(%q[a\ b'c']).should eq [%q[a bc]] + end + end + {% elsif flag?(:win32) %} + describe ".parse_arguments" do + it "uses the native platform rules" do + Process.parse_arguments(%q[a\ b'c']).should eq [%q[a\], %q[b'c']] + end + end + {% else %} + pending ".parse_arguments" + {% end %} + + describe ".parse_arguments_posix" do + it { Process.parse_arguments_posix(%q[]).should eq([] of String) } + it { Process.parse_arguments_posix(%q[ ]).should eq([] of String) } + it { Process.parse_arguments_posix(%q[foo]).should eq [%q[foo]] } + it { Process.parse_arguments_posix(%q[foo bar]).should eq [%q[foo], %q[bar]] } + it { Process.parse_arguments_posix(%q["foo bar" 'foo bar' baz]).should eq [%q[foo bar], %q[foo bar], %q[baz]] } + it { Process.parse_arguments_posix(%q["foo bar"'foo bar'baz]).should eq [%q[foo barfoo barbaz]] } + it { Process.parse_arguments_posix(%q[foo\ bar]).should eq [%q[foo bar]] } + it { Process.parse_arguments_posix(%q["foo\ bar"]).should eq [%q[foo\ bar]] } + it { Process.parse_arguments_posix(%q['foo\ bar']).should eq [%q[foo\ bar]] } + it { Process.parse_arguments_posix(%q[\]).should eq [%q[\]] } + it { Process.parse_arguments_posix(%q["foo bar" '\hello/' Fizz\ Buzz]).should eq [%q[foo bar], %q[\hello/], %q[Fizz Buzz]] } + it { Process.parse_arguments_posix(%q[foo"bar"baz]).should eq [%q[foobarbaz]] } + it { Process.parse_arguments_posix(%q[foo'bar'baz]).should eq [%q[foobarbaz]] } + it { Process.parse_arguments_posix(%q[this 'is a "'very wei"rd co"m"mand please" don't do t'h'a't p"leas"e]).should eq [%q[this], %q[is a "very], %q[weird command please], %q[dont do that], %q[please]] } + + it "raises an error when double quote is unclosed" do + expect_raises ArgumentError, "Unmatched quote" do + Process.parse_arguments_posix(%q["foo]) + end + end + + it "raises an error if single quote is unclosed" do + expect_raises ArgumentError, "Unmatched quote" do + Process.parse_arguments_posix(%q['foo]) + end + end + end + + describe ".parse_arguments_windows" do + it { Process.parse_arguments_windows(%q[]).should eq([] of String) } + it { Process.parse_arguments_windows(%q[ ]).should eq([] of String) } + it { Process.parse_arguments_windows(%q[foo]).should eq [%q[foo]] } + it { Process.parse_arguments_windows(%q[foo bar]).should eq [%q[foo], %q[bar]] } + it { Process.parse_arguments_windows(%q["foo bar" 'foo bar' baz]).should eq [%q[foo bar], %q['foo], %q[bar'], %q[baz]] } + it { Process.parse_arguments_windows(%q["foo bar"baz]).should eq [%q[foo barbaz]] } + it { Process.parse_arguments_windows(%q[foo"bar baz"]).should eq [%q[foobar baz]] } + it { Process.parse_arguments_windows(%q[foo\bar]).should eq [%q[foo\bar]] } + it { Process.parse_arguments_windows(%q[foo\ bar]).should eq [%q[foo\], %q[bar]] } + it { Process.parse_arguments_windows(%q[foo\\bar]).should eq [%q[foo\\bar]] } + it { Process.parse_arguments_windows(%q[foo\\\bar]).should eq [%q[foo\\\bar]] } + it { Process.parse_arguments_windows(%q[ /LIBPATH:C:\crystal\lib ]).should eq [%q[/LIBPATH:C:\crystal\lib]] } + it { Process.parse_arguments_windows(%q[a\\\b d"e f"g h]).should eq [%q[a\\\b], %q[de fg], %q[h]] } + it { Process.parse_arguments_windows(%q[a\\\"b c d]).should eq [%q[a\"b], %q[c], %q[d]] } + it { Process.parse_arguments_windows(%q[a\\\\"b c" d e]).should eq [%q[a\\b c], %q[d], %q[e]] } + it { Process.parse_arguments_windows(%q["foo bar" '\hello/' Fizz\ Buzz]).should eq [%q[foo bar], %q['\hello/'], %q[Fizz\], %q[Buzz]] } + it { Process.parse_arguments_windows(%q[this 'is a "'very wei"rd co"m"mand please" don't do t'h'a't p"leas"e"]).should eq [%q[this], %q['is], %q[a], %q['very weird], %q[command], %q[please don't do t'h'a't please]] } + + it "raises an error if double quote is unclosed" do + expect_raises ArgumentError, "Unmatched quote" do + Process.parse_arguments_windows(%q["foo]) + Process.parse_arguments_windows(%q[\"foo]) + Process.parse_arguments_windows(%q["f\"oo\\\"]) + end + end + end +end diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index e051e328c66e..c28d74d83882 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -61,262 +61,263 @@ private def newline end describe Process do - it "runs true" do - process = Process.new(*exit_code_command(0)) - process.wait.exit_code.should eq(0) - end - - it "runs false" do - process = Process.new(*exit_code_command(1)) - process.wait.exit_code.should eq(1) - end - - it "raises if command doesn't exist" do - expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do - Process.new("foobarbaz") + describe ".new" do + it "raises if command doesn't exist" do + expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do + Process.new("foobarbaz") + end end - end - pending_win32 "raises if command is not executable" do - with_tempfile("crystal-spec-run") do |path| - File.touch path - expect_raises(File::AccessDeniedError, "Error executing process: '#{path.inspect_unquoted}'") do - Process.new(path) + pending_win32 "raises if command is not executable" do + with_tempfile("crystal-spec-run") do |path| + File.touch path + expect_raises(File::AccessDeniedError, "Error executing process: '#{path.inspect_unquoted}'") do + Process.new(path) + end end end - end - it "raises if command could not be executed" do - with_tempfile("crystal-spec-run") do |path| - File.touch path - command = File.join(path, "foo") - expect_raises(IO::Error, "Error executing process: '#{command.inspect_unquoted}'") do - Process.new(command) + it "raises if command could not be executed" do + with_tempfile("crystal-spec-run") do |path| + File.touch path + command = File.join(path, "foo") + expect_raises(IO::Error, "Error executing process: '#{command.inspect_unquoted}'") do + Process.new(command) + end end end end - it "run waits for the process" do - Process.run(*exit_code_command(0)).exit_code.should eq(0) - end - - it "runs true in block" do - Process.run(*exit_code_command(0)) { } - $?.exit_code.should eq(0) - end + describe "#wait" do + it "successful exit code" do + process = Process.new(*exit_code_command(0)) + process.wait.exit_code.should eq(0) + end - it "receives arguments in array" do - command, args = exit_code_command(123) - Process.run(command, args.to_a).exit_code.should eq(123) + it "unsuccessful exit code" do + process = Process.new(*exit_code_command(1)) + process.wait.exit_code.should eq(1) + end end - it "receives arguments in tuple" do - command, args = exit_code_command(123) - Process.run(command, args.as(Tuple)).exit_code.should eq(123) - end + describe ".run" do + it "waits for the process" do + Process.run(*exit_code_command(0)).exit_code.should eq(0) + end - it "redirects output to /dev/null" do - # This doesn't test anything but no output should be seen while running tests - command, args = {% if flag?(:win32) %} - {"cmd.exe", {"/c", "dir"}} - {% else %} - {"/bin/ls", [] of String} - {% end %} - Process.run(command, args, output: Process::Redirect::Close).exit_code.should eq(0) - end + it "runs true in block" do + Process.run(*exit_code_command(0)) { } + $?.exit_code.should eq(0) + end - it "gets output" do - value = Process.run(*shell_command("echo hello")) do |proc| - proc.output.gets_to_end + it "receives arguments in array" do + command, args = exit_code_command(123) + Process.run(command, args.to_a).exit_code.should eq(123) end - value.should eq("hello#{newline}") - end - pending_win32 "sends input in IO" do - value = Process.run(*stdin_to_stdout_command, input: IO::Memory.new("hello")) do |proc| - proc.input?.should be_nil - proc.output.gets_to_end + it "receives arguments in tuple" do + command, args = exit_code_command(123) + Process.run(command, args.as(Tuple)).exit_code.should eq(123) end - value.should eq("hello") - end - it "sends output to IO" do - output = IO::Memory.new - Process.run(*shell_command("echo hello"), output: output) - output.to_s.should eq("hello#{newline}") - end + it "redirects output to /dev/null" do + # This doesn't test anything but no output should be seen while running tests + command, args = {% if flag?(:win32) %} + {"cmd.exe", {"/c", "dir"}} + {% else %} + {"/bin/ls", [] of String} + {% end %} + Process.run(command, args, output: Process::Redirect::Close).exit_code.should eq(0) + end - it "sends error to IO" do - error = IO::Memory.new - Process.run(*shell_command("1>&2 echo hello"), error: error) - error.to_s.should eq("hello#{newline}") - end + it "gets output" do + value = Process.run(*shell_command("echo hello")) do |proc| + proc.output.gets_to_end + end + value.should eq("hello#{newline}") + end - it "controls process in block" do - value = Process.run(*stdin_to_stdout_command, error: :inherit) do |proc| - proc.input.puts "hello" - proc.input.close - proc.output.gets_to_end + pending_win32 "sends input in IO" do + value = Process.run(*stdin_to_stdout_command, input: IO::Memory.new("hello")) do |proc| + proc.input?.should be_nil + proc.output.gets_to_end + end + value.should eq("hello") end - value.should eq("hello#{newline}") - end - it "closes ios after block" do - Process.run(*stdin_to_stdout_command) { } - $?.exit_code.should eq(0) - end + it "sends output to IO" do + output = IO::Memory.new + Process.run(*shell_command("echo hello"), output: output) + output.to_s.should eq("hello#{newline}") + end - {% if flag?(:unix) %} - it "chroot raises when unprivileged" do - status, output, _ = compile_and_run_source <<-'CRYSTAL' - begin - Process.chroot(".") - puts "FAIL" - rescue ex - puts ex.inspect - end - CRYSTAL + it "sends error to IO" do + error = IO::Memory.new + Process.run(*shell_command("1>&2 echo hello"), error: error) + error.to_s.should eq("hello#{newline}") + end - status.success?.should be_true - output.should eq("#\n") + it "controls process in block" do + value = Process.run(*stdin_to_stdout_command, error: :inherit) do |proc| + proc.input.puts "hello" + proc.input.close + proc.output.gets_to_end + end + value.should eq("hello#{newline}") end - {% end %} - it "sets working directory" do - parent = File.dirname(Dir.current) - command = {% if flag?(:win32) %} - "cmd.exe /c echo %cd%" - {% else %} - "pwd" - {% end %} - value = Process.run(command, shell: true, chdir: parent, output: Process::Redirect::Pipe) do |proc| - proc.output.gets_to_end + it "closes ios after block" do + Process.run(*stdin_to_stdout_command) { } + $?.exit_code.should eq(0) end - value.should eq "#{parent}#{newline}" - end - pending_win32 "disallows passing arguments to nowhere" do - expect_raises ArgumentError, /args.+@/ do - Process.run("foo bar", {"baz"}, shell: true) + it "sets working directory" do + parent = File.dirname(Dir.current) + command = {% if flag?(:win32) %} + "cmd.exe /c echo %cd%" + {% else %} + "pwd" + {% end %} + value = Process.run(command, shell: true, chdir: parent, output: Process::Redirect::Pipe) do |proc| + proc.output.gets_to_end + end + value.should eq "#{parent}#{newline}" end - end - pending_win32 "looks up programs in the $PATH with a shell" do - proc = Process.run(*exit_code_command(0), shell: true, output: Process::Redirect::Close) - proc.exit_code.should eq(0) - end + pending_win32 "disallows passing arguments to nowhere" do + expect_raises ArgumentError, /args.+@/ do + Process.run("foo bar", {"baz"}, shell: true) + end + end - pending_win32 "allows passing huge argument lists to a shell" do - proc = Process.new(%(echo "${@}"), {"a", "b"}, shell: true, output: Process::Redirect::Pipe) - output = proc.output.gets_to_end - proc.wait - output.should eq "a b\n" - end + pending_win32 "looks up programs in the $PATH with a shell" do + proc = Process.run(*exit_code_command(0), shell: true, output: Process::Redirect::Close) + proc.exit_code.should eq(0) + end - pending_win32 "does not run shell code in the argument list" do - proc = Process.new("echo", {"`echo hi`"}, shell: true, output: Process::Redirect::Pipe) - output = proc.output.gets_to_end - proc.wait - output.should eq "`echo hi`\n" - end + pending_win32 "allows passing huge argument lists to a shell" do + proc = Process.new(%(echo "${@}"), {"a", "b"}, shell: true, output: Process::Redirect::Pipe) + output = proc.output.gets_to_end + proc.wait + output.should eq "a b\n" + end - describe "environ" do - it "clears the environment" do - value = Process.run(*print_env_command, clear_env: true) do |proc| - proc.output.gets_to_end - end - value.should eq("") + pending_win32 "does not run shell code in the argument list" do + proc = Process.new("echo", {"`echo hi`"}, shell: true, output: Process::Redirect::Pipe) + output = proc.output.gets_to_end + proc.wait + output.should eq "`echo hi`\n" end - it "clears and sets an environment variable" do - value = Process.run(*print_env_command, clear_env: true, env: {"FOO" => "bar"}) do |proc| - proc.output.gets_to_end + describe "environ" do + it "clears the environment" do + value = Process.run(*print_env_command, clear_env: true) do |proc| + proc.output.gets_to_end + end + value.should eq("") end - value.should eq("FOO=bar#{newline}") - end - it "sets an environment variable" do - value = Process.run(*print_env_command, env: {"FOO" => "bar"}) do |proc| - proc.output.gets_to_end + it "clears and sets an environment variable" do + value = Process.run(*print_env_command, clear_env: true, env: {"FOO" => "bar"}) do |proc| + proc.output.gets_to_end + end + value.should eq("FOO=bar#{newline}") end - value.should match /(*ANYCRLF)^FOO=bar$/m - end - it "sets an empty environment variable" do - value = Process.run(*print_env_command, env: {"FOO" => ""}) do |proc| - proc.output.gets_to_end + it "sets an environment variable" do + value = Process.run(*print_env_command, env: {"FOO" => "bar"}) do |proc| + proc.output.gets_to_end + end + value.should match /(*ANYCRLF)^FOO=bar$/m end - value.should match /(*ANYCRLF)^FOO=$/m - end - it "deletes existing environment variable" do - ENV["FOO"] = "bar" - value = Process.run(*print_env_command, env: {"FOO" => nil}) do |proc| - proc.output.gets_to_end + it "sets an empty environment variable" do + value = Process.run(*print_env_command, env: {"FOO" => ""}) do |proc| + proc.output.gets_to_end + end + value.should match /(*ANYCRLF)^FOO=$/m end - value.should_not match /(*ANYCRLF)^FOO=/m - ensure - ENV.delete("FOO") - end - {% if flag?(:win32) %} - it "deletes existing environment variable case-insensitive" do + it "deletes existing environment variable" do ENV["FOO"] = "bar" - value = Process.run(*print_env_command, env: {"foo" => nil}) do |proc| + value = Process.run(*print_env_command, env: {"FOO" => nil}) do |proc| proc.output.gets_to_end end - value.should_not match /(*ANYCRLF)^FOO=/mi + value.should_not match /(*ANYCRLF)^FOO=/m ensure ENV.delete("FOO") end - {% end %} - it "preserves existing environment variable" do - ENV["FOO"] = "bar" - value = Process.run(*print_env_command) do |proc| - proc.output.gets_to_end - end - value.should match /(*ANYCRLF)^FOO=bar$/m - ensure - ENV.delete("FOO") - end + {% if flag?(:win32) %} + it "deletes existing environment variable case-insensitive" do + ENV["FOO"] = "bar" + value = Process.run(*print_env_command, env: {"foo" => nil}) do |proc| + proc.output.gets_to_end + end + value.should_not match /(*ANYCRLF)^FOO=/mi + ensure + ENV.delete("FOO") + end + {% end %} - it "preserves and sets an environment variable" do - ENV["FOO"] = "bar" - value = Process.run(*print_env_command, env: {"FOO2" => "bar2"}) do |proc| - proc.output.gets_to_end + it "preserves existing environment variable" do + ENV["FOO"] = "bar" + value = Process.run(*print_env_command) do |proc| + proc.output.gets_to_end + end + value.should match /(*ANYCRLF)^FOO=bar$/m + ensure + ENV.delete("FOO") end - value.should match /(*ANYCRLF)^FOO=bar$/m - value.should match /(*ANYCRLF)^FOO2=bar2$/m - ensure - ENV.delete("FOO") - end - it "overrides existing environment variable" do - ENV["FOO"] = "bar" - value = Process.run(*print_env_command, env: {"FOO" => "different"}) do |proc| - proc.output.gets_to_end + it "preserves and sets an environment variable" do + ENV["FOO"] = "bar" + value = Process.run(*print_env_command, env: {"FOO2" => "bar2"}) do |proc| + proc.output.gets_to_end + end + value.should match /(*ANYCRLF)^FOO=bar$/m + value.should match /(*ANYCRLF)^FOO2=bar2$/m + ensure + ENV.delete("FOO") end - value.should match /(*ANYCRLF)^FOO=different$/m - ensure - ENV.delete("FOO") - end - {% if flag?(:win32) %} - it "overrides existing environment variable case-insensitive" do + it "overrides existing environment variable" do ENV["FOO"] = "bar" - value = Process.run(*print_env_command, env: {"fOo" => "different"}) do |proc| + value = Process.run(*print_env_command, env: {"FOO" => "different"}) do |proc| proc.output.gets_to_end end - value.should_not match /(*ANYCRLF)^FOO=/m - value.should match /(*ANYCRLF)^fOo=different$/m + value.should match /(*ANYCRLF)^FOO=different$/m ensure ENV.delete("FOO") end - {% end %} + + {% if flag?(:win32) %} + it "overrides existing environment variable case-insensitive" do + ENV["FOO"] = "bar" + value = Process.run(*print_env_command, env: {"fOo" => "different"}) do |proc| + proc.output.gets_to_end + end + value.should_not match /(*ANYCRLF)^FOO=/m + value.should match /(*ANYCRLF)^fOo=different$/m + ensure + ENV.delete("FOO") + end + {% end %} + end + + pending_win32 "can link processes together" do + buffer = IO::Memory.new + Process.run(*stdin_to_stdout_command) do |cat| + Process.run(*stdin_to_stdout_command, input: cat.output, output: buffer) do + 1000.times { cat.input.puts "line" } + cat.close + end + end + buffer.to_s.lines.size.should eq(1000) + end end - describe "signal" do + describe "#signal" do pending_win32 "kills a process" do process = Process.new(*standing_command) process.signal(Signal::KILL).should be_nil @@ -330,46 +331,16 @@ describe Process do end end - pending_win32 "gets the pgid of a process id" do + pending_win32 "#terminate" do process = Process.new(*standing_command) - Process.pgid(process.pid).should be_a(Int64) - process.signal(Signal::KILL) - Process.pgid.should eq(Process.pgid(Process.pid)) - end + process.exists?.should be_true + process.terminated?.should be_false - pending_win32 "can link processes together" do - buffer = IO::Memory.new - Process.run(*stdin_to_stdout_command) do |cat| - Process.run(*stdin_to_stdout_command, input: cat.output, output: buffer) do - 1000.times { cat.input.puts "line" } - cat.close - end - end - buffer.to_s.lines.size.should eq(1000) + process.terminate + process.wait end - {% unless flag?(:preview_mt) || flag?(:win32) %} - it "executes the new process with exec" do - with_tempfile("crystal-spec-exec") do |path| - File.exists?(path).should be_false - - fork = Process.fork do - Process.exec("/usr/bin/env", {"touch", path}) - end - fork.wait - - File.exists?(path).should be_true - end - end - - it "gets error from exec" do - expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do - Process.exec("foobarbaz") - end - end - {% end %} - - pending_win32 "checks for existence" do + pending_win32 ".exists?" do # We can't reliably check whether it ever returns false, since we can't predict # how PIDs are used by the system, a new process might be spawned in between # reaping the one we would spawn and checking for it, using the now available @@ -391,129 +362,53 @@ describe Process do process.terminated?.should be_true end - pending_win32 "terminates the process" do + pending_win32 ".pgid" do process = Process.new(*standing_command) - process.exists?.should be_true - process.terminated?.should be_false - - process.terminate - process.wait - end - - describe "executable_path" do - it "searches executable" do - Process.executable_path.should be_a(String | Nil) - end - end - - describe "quote_posix" do - it { Process.quote_posix("").should eq "''" } - it { Process.quote_posix(" ").should eq "' '" } - it { Process.quote_posix("$hi").should eq "'$hi'" } - it { Process.quote_posix(orig = "aZ5+,-./:=@_").should eq orig } - it { Process.quote_posix(orig = "cafe").should eq orig } - it { Process.quote_posix("café").should eq "'café'" } - it { Process.quote_posix("I'll").should eq %('I'"'"'ll') } - it { Process.quote_posix("'").should eq %(''"'"'') } - it { Process.quote_posix("\\").should eq "'\\'" } - - context "join" do - it { Process.quote_posix([] of String).should eq "" } - it { Process.quote_posix(["my file.txt", "another.txt"]).should eq "'my file.txt' another.txt" } - it { Process.quote_posix(["foo ", "", " ", " bar"]).should eq "'foo ' '' ' ' ' bar'" } - it { Process.quote_posix(["foo'", "\"bar"]).should eq %('foo'"'"'' '"bar') } - end + Process.pgid(process.pid).should be_a(Int64) + process.signal(Signal::KILL) + Process.pgid.should eq(Process.pgid(Process.pid)) end - describe "quote_windows" do - it { Process.quote_windows("").should eq %("") } - it { Process.quote_windows(" ").should eq %(" ") } - it { Process.quote_windows(orig = "%hi%").should eq orig } - it { Process.quote_windows(%q(C:\"foo" project.txt)).should eq %q("C:\\\"foo\" project.txt") } - it { Process.quote_windows(%q(C:\"foo"_project.txt)).should eq %q(C:\\\"foo\"_project.txt) } - it { Process.quote_windows(%q(C:\Program Files\Foo Bar\foobar.exe)).should eq %q("C:\Program Files\Foo Bar\foobar.exe") } - it { Process.quote_windows(orig = "café").should eq orig } - it { Process.quote_windows(%(")).should eq %q(\") } - it { Process.quote_windows(%q(a\\b\ c\)).should eq %q("a\\b\ c\\") } - it { Process.quote_windows(orig = %q(a\\b\c\)).should eq orig } - - context "join" do - it { Process.quote_windows([] of String).should eq "" } - it { Process.quote_windows(["my file.txt", "another.txt"]).should eq %("my file.txt" another.txt) } - it { Process.quote_windows(["foo ", "", " ", " bar"]).should eq %("foo " "" " " " bar") } - end - end + {% unless flag?(:preview_mt) || flag?(:win32) %} + describe ".fork" do + it "executes the new process with exec" do + with_tempfile("crystal-spec-exec") do |path| + File.exists?(path).should be_false - {% if flag?(:unix) %} - describe ".parse_arguments" do - it "uses the native platform rules" do - Process.parse_arguments(%q[a\ b'c']).should eq [%q[a bc]] - end - end - {% elsif flag?(:win32) %} - describe ".parse_arguments" do - it "uses the native platform rules" do - Process.parse_arguments(%q[a\ b'c']).should eq [%q[a\], %q[b'c']] - end - end - {% else %} - pending ".parse_arguments" - {% end %} + fork = Process.fork do + Process.exec("/usr/bin/env", {"touch", path}) + end + fork.wait - describe ".parse_arguments_posix" do - it { Process.parse_arguments_posix(%q[]).should eq([] of String) } - it { Process.parse_arguments_posix(%q[ ]).should eq([] of String) } - it { Process.parse_arguments_posix(%q[foo]).should eq [%q[foo]] } - it { Process.parse_arguments_posix(%q[foo bar]).should eq [%q[foo], %q[bar]] } - it { Process.parse_arguments_posix(%q["foo bar" 'foo bar' baz]).should eq [%q[foo bar], %q[foo bar], %q[baz]] } - it { Process.parse_arguments_posix(%q["foo bar"'foo bar'baz]).should eq [%q[foo barfoo barbaz]] } - it { Process.parse_arguments_posix(%q[foo\ bar]).should eq [%q[foo bar]] } - it { Process.parse_arguments_posix(%q["foo\ bar"]).should eq [%q[foo\ bar]] } - it { Process.parse_arguments_posix(%q['foo\ bar']).should eq [%q[foo\ bar]] } - it { Process.parse_arguments_posix(%q[\]).should eq [%q[\]] } - it { Process.parse_arguments_posix(%q["foo bar" '\hello/' Fizz\ Buzz]).should eq [%q[foo bar], %q[\hello/], %q[Fizz Buzz]] } - it { Process.parse_arguments_posix(%q[foo"bar"baz]).should eq [%q[foobarbaz]] } - it { Process.parse_arguments_posix(%q[foo'bar'baz]).should eq [%q[foobarbaz]] } - it { Process.parse_arguments_posix(%q[this 'is a "'very wei"rd co"m"mand please" don't do t'h'a't p"leas"e]).should eq [%q[this], %q[is a "very], %q[weird command please], %q[dont do that], %q[please]] } - - it "raises an error when double quote is unclosed" do - expect_raises ArgumentError, "Unmatched quote" do - Process.parse_arguments_posix(%q["foo]) + File.exists?(path).should be_true + end end end - it "raises an error if single quote is unclosed" do - expect_raises ArgumentError, "Unmatched quote" do - Process.parse_arguments_posix(%q['foo]) + describe ".exec" do + it "gets error from exec" do + expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do + Process.exec("foobarbaz") + end end end - end + {% end %} - describe ".parse_arguments_windows" do - it { Process.parse_arguments_windows(%q[]).should eq([] of String) } - it { Process.parse_arguments_windows(%q[ ]).should eq([] of String) } - it { Process.parse_arguments_windows(%q[foo]).should eq [%q[foo]] } - it { Process.parse_arguments_windows(%q[foo bar]).should eq [%q[foo], %q[bar]] } - it { Process.parse_arguments_windows(%q["foo bar" 'foo bar' baz]).should eq [%q[foo bar], %q['foo], %q[bar'], %q[baz]] } - it { Process.parse_arguments_windows(%q["foo bar"baz]).should eq [%q[foo barbaz]] } - it { Process.parse_arguments_windows(%q[foo"bar baz"]).should eq [%q[foobar baz]] } - it { Process.parse_arguments_windows(%q[foo\bar]).should eq [%q[foo\bar]] } - it { Process.parse_arguments_windows(%q[foo\ bar]).should eq [%q[foo\], %q[bar]] } - it { Process.parse_arguments_windows(%q[foo\\bar]).should eq [%q[foo\\bar]] } - it { Process.parse_arguments_windows(%q[foo\\\bar]).should eq [%q[foo\\\bar]] } - it { Process.parse_arguments_windows(%q[ /LIBPATH:C:\crystal\lib ]).should eq [%q[/LIBPATH:C:\crystal\lib]] } - it { Process.parse_arguments_windows(%q[a\\\b d"e f"g h]).should eq [%q[a\\\b], %q[de fg], %q[h]] } - it { Process.parse_arguments_windows(%q[a\\\"b c d]).should eq [%q[a\"b], %q[c], %q[d]] } - it { Process.parse_arguments_windows(%q[a\\\\"b c" d e]).should eq [%q[a\\b c], %q[d], %q[e]] } - it { Process.parse_arguments_windows(%q["foo bar" '\hello/' Fizz\ Buzz]).should eq [%q[foo bar], %q['\hello/'], %q[Fizz\], %q[Buzz]] } - it { Process.parse_arguments_windows(%q[this 'is a "'very wei"rd co"m"mand please" don't do t'h'a't p"leas"e"]).should eq [%q[this], %q['is], %q[a], %q['very weird], %q[command], %q[please don't do t'h'a't please]] } - - it "raises an error if double quote is unclosed" do - expect_raises ArgumentError, "Unmatched quote" do - Process.parse_arguments_windows(%q["foo]) - Process.parse_arguments_windows(%q[\"foo]) - Process.parse_arguments_windows(%q["f\"oo\\\"]) + describe ".chroot" do + {% if flag?(:unix) %} + it "raises when unprivileged" do + status, output, _ = compile_and_run_source <<-'CRYSTAL' + begin + Process.chroot(".") + puts "FAIL" + rescue ex + puts ex.inspect + end + CRYSTAL + + status.success?.should be_true + output.should eq("#\n") end - end + {% end %} end end From c9454476c8fbc609504d1c8a489a123bdd9a2da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 4 Jan 2023 12:30:59 +0100 Subject: [PATCH 0213/1551] Use `File#flock_exclusive` on win32 in compiler (#12876) --- src/compiler/crystal/compiler.cr | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 2fec35411223..0baaf2db0c08 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -286,16 +286,11 @@ module Crystal end private def with_file_lock(output_dir) - {% if flag?(:win32) %} - # TODO: use flock when it's supported in Windows - yield - {% else %} - File.open(File.join(output_dir, "compiler.lock"), "w") do |file| - file.flock_exclusive do - yield - end + File.open(File.join(output_dir, "compiler.lock"), "w") do |file| + file.flock_exclusive do + yield end - {% end %} + end end private def run_dsymutil(filename) From fa7876ddbffff11b39f7a06451e962395fb67937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 4 Jan 2023 12:31:13 +0100 Subject: [PATCH 0214/1551] Update distribution-scripts (#12891) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4ade2df927d8..cfacf3bcc06f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "f567925d4d36be64b7e211d0c166af9bdd92c75f" + default: "d8c44c0a2c977bfb90363923047f2c64cef3bf8e" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From d7468857236ab5c8605708381af8a8c523c177c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 4 Jan 2023 12:31:32 +0100 Subject: [PATCH 0215/1551] [CI] Update shards 0.17.2 (#12875) --- .github/workflows/win.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 5e714918dc6b..5d47f5fa0ae7 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -331,7 +331,7 @@ jobs: uses: actions/checkout@v3 with: repository: crystal-lang/shards - ref: v0.17.1 + ref: v0.17.2 path: shards - name: Download molinillo release From 68ea5d22474617fa0ad963e7e9bcaa9f7519e729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 4 Jan 2023 12:42:18 +0100 Subject: [PATCH 0216/1551] Add tests for `Process::Status` (#12881) --- spec/std/process/status_spec.cr | 65 +++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 spec/std/process/status_spec.cr diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr new file mode 100644 index 000000000000..48c7672685d6 --- /dev/null +++ b/spec/std/process/status_spec.cr @@ -0,0 +1,65 @@ +require "spec" + +private def exit_status(status) + {% if flag?(:unix) %} + status << 8 + {% else %} + status.to_u32! + {% end %} +end + +describe Process::Status do + it "#exit_code" do + Process::Status.new(exit_status(0)).exit_code.should eq 0 + Process::Status.new(exit_status(1)).exit_code.should eq 1 + Process::Status.new(exit_status(127)).exit_code.should eq 127 + Process::Status.new(exit_status(128)).exit_code.should eq 128 + Process::Status.new(exit_status(255)).exit_code.should eq 255 + end + + it "#success?" do + Process::Status.new(exit_status(0)).success?.should be_true + Process::Status.new(exit_status(1)).success?.should be_false + Process::Status.new(exit_status(127)).success?.should be_false + Process::Status.new(exit_status(128)).success?.should be_false + Process::Status.new(exit_status(255)).success?.should be_false + end + + it "#normal_exit?" do + Process::Status.new(exit_status(0)).normal_exit?.should be_true + Process::Status.new(exit_status(1)).normal_exit?.should be_true + Process::Status.new(exit_status(127)).normal_exit?.should be_true + Process::Status.new(exit_status(128)).normal_exit?.should be_true + Process::Status.new(exit_status(255)).normal_exit?.should be_true + end + + it "#signal_exit?" do + Process::Status.new(exit_status(0)).signal_exit?.should be_false + Process::Status.new(exit_status(1)).signal_exit?.should be_false + Process::Status.new(exit_status(127)).signal_exit?.should be_false + Process::Status.new(exit_status(128)).signal_exit?.should be_false + Process::Status.new(exit_status(255)).signal_exit?.should be_false + end + + {% if flag?(:unix) && !flag?(:wasi) %} + it "#exit_signal" do + Process::Status.new(Signal::HUP.value).exit_signal.should eq Signal::HUP + Process::Status.new(Signal::INT.value).exit_signal.should eq Signal::INT + last_signal = Signal.values[-1] + Process::Status.new(last_signal.value).exit_signal.should eq last_signal + end + + it "#normal_exit? with signal code" do + Process::Status.new(0x01).normal_exit?.should be_false + Process::Status.new(0x7f).normal_exit?.should be_false + end + + it "#signal_exit? with signal code" do + Process::Status.new(0x01).signal_exit?.should be_true + + # 0x7f raises arithmetic error due to overflow, but this shouldn't + # matter because actual signal values don't expand to that range + Process::Status.new(0x7e).signal_exit?.should be_true + end + {% end %} +end From 292052bb2b74697a57ef0a527de0c304dc7c9313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 4 Jan 2023 13:14:17 +0100 Subject: [PATCH 0217/1551] Refactor specs for `Enum#to_s` using `assert_prints` (#12882) --- spec/std/enum_spec.cr | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/spec/std/enum_spec.cr b/spec/std/enum_spec.cr index a4d87ffce0a2..65743937b64e 100644 --- a/spec/std/enum_spec.cr +++ b/spec/std/enum_spec.cr @@ -1,4 +1,5 @@ require "spec" +require "../support/string" enum SpecEnum : Int8 One @@ -49,26 +50,31 @@ private enum SpecEnumWithCaseSensitiveMembers end describe Enum do - describe "to_s" do + describe "#to_s" do it "for simple enum" do - SpecEnum::One.to_s.should eq("One") - SpecEnum::Two.to_s.should eq("Two") - SpecEnum::Three.to_s.should eq("Three") + assert_prints SpecEnum::One.to_s, "One" + assert_prints SpecEnum::Two.to_s, "Two" + assert_prints SpecEnum::Three.to_s, "Three" + assert_prints SpecEnum.new(127).to_s, "127" end it "for flags enum" do - SpecEnumFlags::None.to_s.should eq("None") - SpecEnumFlags::All.to_s.should eq("One | Two | Three") - (SpecEnumFlags::One | SpecEnumFlags::Two).to_s.should eq("One | Two") + assert_prints SpecEnumFlags::None.to_s, "None" + assert_prints SpecEnumFlags::All.to_s, "One | Two | Three" + assert_prints (SpecEnumFlags::One | SpecEnumFlags::Two).to_s, "One | Two" + assert_prints SpecEnumFlags.new(128).to_s, "128" + # FIXME: This should probably be something like `One | 128` + assert_prints (SpecEnumFlags::One | SpecEnumFlags.new(128)).to_s, "One" end it "for private enum" do - PrivateEnum::FOO.to_s.should eq "FOO" - PrivateFlagsEnum::FOO.to_s.should eq "FOO" - PrivateEnum::QUX.to_s.should eq "FOO" - String.build { |io| PrivateEnum::FOO.to_s(io) }.should eq "FOO" - String.build { |io| PrivateFlagsEnum::FOO.to_s(io) }.should eq "FOO" - String.build { |io| (PrivateFlagsEnum::FOO | PrivateFlagsEnum::BAZ).to_s(io) }.should eq "FOO | BAZ" + assert_prints PrivateEnum::FOO.to_s, "FOO" + assert_prints PrivateFlagsEnum::FOO.to_s, "FOO" + assert_prints PrivateEnum::QUX.to_s, "FOO" + assert_prints (PrivateFlagsEnum::FOO | PrivateFlagsEnum::BAZ).to_s, "FOO | BAZ" + assert_prints PrivateFlagsEnum.new(128).to_s, "128" + # FIXME: This should probably be something like `FOO | 128` + assert_prints (PrivateFlagsEnum::FOO | PrivateFlagsEnum.new(128)).to_s, "FOO" end end From 2354ad56a3bceac22604d61139ed8395f0c18e89 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 5 Jan 2023 04:26:27 +0800 Subject: [PATCH 0218/1551] Build PCRE2 on Windows (#12847) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- .github/workflows/win.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 5d47f5fa0ae7..aad484818f4a 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -83,6 +83,19 @@ jobs: run: | cmake . -DBUILD_SHARED_LIBS=OFF -DPCRE_SUPPORT_UNICODE_PROPERTIES=ON -DPCRE_SUPPORT_JIT=ON -DPCRE_STATIC_RUNTIME=ON -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF cmake --build . --config Release + - name: Download libpcre2 + if: steps.cache-libs.outputs.cache-hit != 'true' + uses: actions/checkout@v3 + with: + repository: PCRE2Project/pcre2 + ref: pcre2-10.42 + path: pcre2 + - name: Build libpcre2 + if: steps.cache-libs.outputs.cache-hit != 'true' + working-directory: ./pcre2 + run: | + cmake . -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DPCRE2_STATIC_RUNTIME=ON -DPCRE2_BUILD_PCRE2GREP=OFF -DPCRE2_BUILD_TESTS=OFF -DPCRE2_SUPPORT_UNICODE=ON -DPCRE2_SUPPORT_JIT=ON -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF + cmake --build . --config Release - name: Download libiconv if: steps.cache-libs.outputs.cache-hit != 'true' uses: actions/checkout@v3 @@ -230,6 +243,7 @@ jobs: run: | mkdir libs mv pcre/Release/pcre.lib libs/ + mv pcre2/Release/pcre2-8-static.lib libs/pcre2-8.lib mv libiconv/output/x64/ReleaseStatic/libiconvStatic.lib libs/iconv.lib mv bdwgc/Release/gc.lib libs/ mv libffi/win32/vs16_x64/x64/Release/libffi.lib libs/ffi.lib From 59b77bc76724351a0c556f679aa0ab8b0fb90e05 Mon Sep 17 00:00:00 2001 From: Fernando Valverde Date: Wed, 4 Jan 2023 14:26:42 -0600 Subject: [PATCH 0219/1551] Fix Number comparison operator docs (#12880) --- src/number.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/number.cr b/src/number.cr index 6a1660c2c2d4..62177e78d89c 100644 --- a/src/number.cr +++ b/src/number.cr @@ -195,7 +195,7 @@ struct Number # Returns: # - `-1` if `self` is less than *other* # - `0` if `self` is equal to *other* - # - `-1` if `self` is greater than *other* + # - `1` if `self` is greater than *other* # - `nil` if `self` is `NaN` or *other* is `NaN`, because `NaN` values are not comparable def <=>(other) : Int32? # NaN can't be compared to other numbers From 939772ee9603277a8eb8d03dc69c13926bf027b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 4 Jan 2023 21:27:10 +0100 Subject: [PATCH 0220/1551] Fix: Unwrap nested errors in error handler for `Crystal::Error` (#12888) --- src/compiler/crystal/command.cr | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 7f9ea58148be..ca66b460d83b 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -141,6 +141,14 @@ class Crystal::Command rescue ex : Crystal::Error report_warnings + # This unwraps nested errors which could be caused by `require` which wraps + # errors in order to trace the require path. The causes are listed similarly + # to `#inspect_with_backtrace` but without the backtrace. + while cause = ex.cause + error ex.message, exit_code: nil + ex = cause + end + error ex.message rescue ex : OptionParser::Exception error ex.message From 7bc2db543ea1656be3f4941b18eed8bcde11e156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 5 Jan 2023 17:14:33 +0100 Subject: [PATCH 0221/1551] Fix `Enum#to_s` for flag enum containing named and unnamed members (#12895) --- spec/std/enum_spec.cr | 8 ++++---- src/enum.cr | 11 +++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/spec/std/enum_spec.cr b/spec/std/enum_spec.cr index 65743937b64e..0fd244ead5ec 100644 --- a/spec/std/enum_spec.cr +++ b/spec/std/enum_spec.cr @@ -63,8 +63,9 @@ describe Enum do assert_prints SpecEnumFlags::All.to_s, "One | Two | Three" assert_prints (SpecEnumFlags::One | SpecEnumFlags::Two).to_s, "One | Two" assert_prints SpecEnumFlags.new(128).to_s, "128" - # FIXME: This should probably be something like `One | 128` - assert_prints (SpecEnumFlags::One | SpecEnumFlags.new(128)).to_s, "One" + assert_prints (SpecEnumFlags::One | SpecEnumFlags.new(128)).to_s, "One | 128" + assert_prints (SpecEnumFlags::One | SpecEnumFlags.new(8) | SpecEnumFlags.new(16)).to_s, "One | 24" + assert_prints (SpecEnumFlags::One | SpecEnumFlags::Two | SpecEnumFlags.new(16)).to_s, "One | Two | 16" end it "for private enum" do @@ -73,8 +74,7 @@ describe Enum do assert_prints PrivateEnum::QUX.to_s, "FOO" assert_prints (PrivateFlagsEnum::FOO | PrivateFlagsEnum::BAZ).to_s, "FOO | BAZ" assert_prints PrivateFlagsEnum.new(128).to_s, "128" - # FIXME: This should probably be something like `FOO | 128` - assert_prints (PrivateFlagsEnum::FOO | PrivateFlagsEnum.new(128)).to_s, "FOO" + assert_prints (PrivateFlagsEnum::FOO | PrivateFlagsEnum.new(128)).to_s, "FOO | 128" end end diff --git a/src/enum.cr b/src/enum.cr index c3438d7812dd..6619ad1fe9ee 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -133,17 +133,20 @@ struct Enum if value == 0 io << "None" else - found = false + remaining_value = self.value {% for member in @type.constants %} {% if member.stringify != "All" %} if {{@type.constant(member)}} != 0 && value.bits_set? {{@type.constant(member)}} - io << " | " if found + io << " | " unless remaining_value == self.value io << {{member.stringify}} - found = true + remaining_value &= ~{{@type.constant(member)}} end {% end %} {% end %} - io << value unless found + unless remaining_value.zero? + io << " | " unless remaining_value == self.value + io << remaining_value + end end {% else %} io << to_s From c9f4a6496d441721c19c3cb4d37b84cf05146b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 5 Jan 2023 19:16:05 +0100 Subject: [PATCH 0222/1551] Implement PCRE2 JIT compilation (#12866) --- spec/std/regex_spec.cr | 13 +++++++------ src/regex/lib_pcre2.cr | 16 +++++++++++++++- src/regex/pcre2.cr | 31 ++++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index d3830124aa79..72f62a3463cd 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -201,17 +201,18 @@ describe "Regex" do end it "doesn't crash with a large single line string" do + str = File.read(datapath("large_single_line_string.txt")) + {% if Regex::Engine.resolve.name == "Regex::PCRE" %} LibPCRE.config LibPCRE::CONFIG_JIT, out jit_enabled pending! "PCRE JIT mode not available." unless 1 == jit_enabled + + str.matches?(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/) {% else %} - # This spec requires a fairly large depth limit. Some package builds - # have a more restrictive value which would make this test fail. - pending! "PCRE2 depth limit too low" unless Regex::PCRE2.config(LibPCRE2::CONFIG_DEPTHLIMIT, UInt32) > 8192 + # Can't use regex literal because the *LIMIT_DEPTH verb is not supported in libpcre (only libpcre2) + # and thus the compiler doesn't recognize it. + str.matches?(Regex.new("(*LIMIT_DEPTH=8192)^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$")) {% end %} - - str = File.read(datapath("large_single_line_string.txt")) - str.matches?(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/) # We don't care whether this actually matches or not, it's just to make # sure the engine does not stack overflow with a large string. end diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index 32e3c9afa767..0df60a05499b 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -182,9 +182,23 @@ lib LibPCRE2 fun compile = pcre2_compile_8(pattern : UInt8*, length : LibC::SizeT, options : UInt32, errorcode : LibC::SizeT*, erroroffset : Int*, ccontext : CompileContext*) : Code* fun code_free = pcre2_code_free_8(code : Code*) : Void + type MatchContext = Void* + fun match_context_create = pcre2_match_context_create_8(gcontext : Void*) : MatchContext + + JIT_COMPLETE = 0x00000001_u32 # For full matching + JIT_PARTIAL_SOFT = 0x00000002_u32 + JIT_PARTIAL_HARD = 0x00000004_u32 + JIT_INVALID_UTF = 0x00000100_u32 + fun jit_compile = pcre2_jit_compile_8(code : Code*, options : UInt32) : Int + + type JITStack = Void* + + fun jit_stack_create = pcre2_jit_stack_create_8(startsize : LibC::SizeT, maxsize : LibC::SizeT, gcontext : GeneralContext) : JITStack + fun jit_stack_assign = pcre2_jit_stack_assign_8(mcontext : MatchContext, callable_function : Void*, callable_data : Void*) : Void + fun pattern_info = pcre2_pattern_info_8(code : Code*, what : UInt32, where : Void*) : Int - fun match = pcre2_match_8(code : Code*, subject : UInt8*, length : LibC::SizeT, startoffset : LibC::SizeT, options : UInt32, match_data : MatchData*, mcontext : Void*) : Int + fun match = pcre2_match_8(code : Code*, subject : UInt8*, length : LibC::SizeT, startoffset : LibC::SizeT, options : UInt32, match_data : MatchData*, mcontext : MatchContext) : Int fun match_data_create_from_pattern = pcre2_match_data_create_from_pattern_8(code : Code*, gcontext : GeneralContext) : MatchData* fun match_data_free = pcre2_match_data_free_8(match_data : MatchData*) : Void diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index dffd2369ba11..176a479bbb57 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -9,6 +9,20 @@ module Regex::PCRE2 @re = PCRE2.compile(source, pcre2_options(options) | LibPCRE2::UTF | LibPCRE2::NO_UTF_CHECK | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| raise ArgumentError.new(error_message) end + + jit_compile + end + + private def jit_compile : Nil + ret = LibPCRE2.jit_compile(@re, LibPCRE2::JIT_COMPLETE) + if ret < 0 + case error = LibPCRE2::Error.new(ret) + when .jit_badoption? + # okay + else + raise ArgumentError.new("Regex JIT compile error: #{error}") + end + end end protected def self.compile(source, options) @@ -123,9 +137,24 @@ module Regex::PCRE2 LibPCRE2.general_context_create(->(size : LibC::Int, data : Void*) { GC.malloc(size) }.pointer, ->(pointer : Void*, data : Void*) { GC.free(pointer) }.pointer, nil) end + # Returns a JIT stack that's shared in the current thread. + # + # Only a single `match` function can run per thread at any given time, so there + # can't be any concurrent access to the JIT stack. + @[ThreadLocal] + class_getter jit_stack : LibPCRE2::JITStack do + jit_stack = LibPCRE2.jit_stack_create(32_768, 1_048_576, Regex::PCRE2.general_context) + if jit_stack.null? + raise "Error allocating JIT stack" + end + jit_stack + end + private def match_data(str, byte_index, options) match_data = LibPCRE2.match_data_create_from_pattern(@re, Regex::PCRE2.general_context) - match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, nil) + match_context = LibPCRE2.match_context_create(nil) + LibPCRE2.jit_stack_assign(match_context, nil, Regex::PCRE2.jit_stack.as(Void*)) + match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, match_context) if match_count < 0 case error = LibPCRE2::Error.new(match_count) From 549f2513367d0bd7fa24714ea72f2243691d7503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Garc=C3=ADa=20Isa=C3=ADa?= Date: Thu, 5 Jan 2023 15:38:09 -0300 Subject: [PATCH 0223/1551] Rotate breached credentials in CircleCI (#12902) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cfacf3bcc06f..da22e31ed768 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -428,7 +428,7 @@ jobs: - image: manastech/s3cmd:2.2-alpine environment: <<: *env - AWS_ACCESS_KEY_ID: AKIA2EEIIRCJCMOSW2XH + AWS_ACCESS_KEY_ID: AKIA2EEIIRCJDEDGK6MQ steps: - attach_workspace: at: /tmp/workspace From 16f839ba250ad96054a68788a64cb671973427d0 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 6 Jan 2023 02:38:27 +0800 Subject: [PATCH 0224/1551] Update NOTICE.md (#12901) --- NOTICE.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index bb7b3e171975..2824f40372df 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1,6 +1,6 @@ # Crystal Programming Language -Copyright 2012-2022 Manas Technology Solutions. +Copyright 2012-2023 Manas Technology Solutions. This product includes software developed at Manas Technology Solutions (). @@ -13,7 +13,7 @@ Please see [LICENSE](/LICENSE) for additional copyright and licensing informatio Crystal compiler links the following libraries, which have their own license: * [LLVM][] - [BSD-3, effectively][] - * [PCRE][] - [BSD-3][] + * [PCRE or PCRE2][] - [BSD-3][] * [libevent2][] - [BSD-3][] * [libiconv][] - [LGPLv3][] * [bdwgc][] - [MIT][] @@ -25,7 +25,7 @@ Crystal compiler calls the following tools as external process on compiling, whi Crystal standard library uses the following libraries, which have their own licenses: * [LLVM][] - [BSD-3, effectively][] - * [PCRE][] - [BSD-3][] + * [PCRE or PCRE2][] - [BSD-3][] * [libevent2][] - [BSD-3][] * [libiconv][] - [LGPLv3][] * [bdwgc][] - [MIT][] @@ -69,6 +69,6 @@ Crystal playground includes the following libraries, which have their own licens [Materialize]: http://materializecss.com/ [Octicons]: https://octicons.github.com/ [OpenSSL]: https://www.openssl.org/ -[PCRE]: http://pcre.org/ +[PCRE or PCRE2]: http://pcre.org/ [readline]: https://tiswww.case.edu/php/chet/readline/rltop.html [Zlib]: http://www.zlib.net/ From 5402f6a38f69e45d4c20fa0ee685ffb6f1aa416b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20H=C3=B6rberg?= Date: Fri, 6 Jan 2023 10:39:48 +0100 Subject: [PATCH 0225/1551] `IPAddress#loopback?` should consider `::ffff:127.0.0.1/104` loopback too (#12783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller Co-authored-by: Beta Ziliani --- spec/std/socket/address_spec.cr | 3 +++ src/socket/address.cr | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/std/socket/address_spec.cr b/spec/std/socket/address_spec.cr index 95cb170a3477..77dc12da93a8 100644 --- a/spec/std/socket/address_spec.cr +++ b/spec/std/socket/address_spec.cr @@ -127,6 +127,9 @@ describe Socket::IPAddress do Socket::IPAddress.new("::2", 0).loopback?.should be_false Socket::IPAddress.new(Socket::IPAddress::LOOPBACK, 0).loopback?.should be_true Socket::IPAddress.new(Socket::IPAddress::LOOPBACK6, 0).loopback?.should be_true + Socket::IPAddress.new("::ffff:127.0.0.1", 0).loopback?.should be_true + Socket::IPAddress.new("::ffff:127.0.1.1", 0).loopback?.should be_true + Socket::IPAddress.new("::ffff:1.0.0.1", 0).loopback?.should be_false end it "#unspecified?" do diff --git a/src/socket/address.cr b/src/socket/address.cr index 601d4e7b7191..f29eb8f067b7 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -227,7 +227,11 @@ class Socket in LibC::InAddr addr.s_addr & 0x000000ff_u32 == 0x0000007f_u32 in LibC::In6Addr - ipv6_addr8(addr) == StaticArray[0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 1_u8] + addr8 = ipv6_addr8(addr) + num = addr8.unsafe_as(UInt128) + # TODO: Use UInt128 literals + num == (1_u128 << 120) || # "::1" + num & UInt128::MAX >> 24 == 0x7fffff_u128 << 80 # "::ffff:127.0.0.1/104" end end From 7561aca91122c58f14dd30cc430dde46b6b4a45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 9 Jan 2023 15:04:04 +0100 Subject: [PATCH 0226/1551] Add docs for `ArrayLiteral#-` (#12916) --- src/compiler/crystal/macros.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 9dadf48af06c..ae2a2e73c25a 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -707,6 +707,10 @@ module Crystal::Macros def +(other : ArrayLiteral) : ArrayLiteral end + # Similar to `Array#-`. + def -(other : ArrayLiteral) : ArrayLiteral + end + # Returns the type specified at the end of the array literal, if any. # # This refers to the part after brackets in `[] of String`. From bf0a606f5355645757f27cdec18de478a289cbe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 9 Jan 2023 15:04:44 +0100 Subject: [PATCH 0227/1551] Split pre-1.0 changelog (#12898) --- CHANGELOG.0.md | 4105 ++++++++++++++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 4103 +---------------------------------------------- 2 files changed, 4107 insertions(+), 4101 deletions(-) create mode 100644 CHANGELOG.0.md diff --git a/CHANGELOG.0.md b/CHANGELOG.0.md new file mode 100644 index 000000000000..5ed22c42b213 --- /dev/null +++ b/CHANGELOG.0.md @@ -0,0 +1,4105 @@ + +Newer entries in [CHANGELOG.md](./CHANGELOG.md) + +# 0.36.1 (2021-02-02) + +## Standard library + +- Fix `Enum.from_value?` for flag enum with non `Int32` base type. ([#10303](https://github.com/crystal-lang/crystal/pull/10303), thanks @straight-shoota) + +### Text + +- Don't raise on `String.new` with `null` pointer and bytesize 0. ([#10308](https://github.com/crystal-lang/crystal/pull/10308), thanks @asterite) + +### Collections + +- Explicitly return a `Hash` in `Hash#dup` and `Hash#clone` (reverts [#9871](https://github.com/crystal-lang/crystal/pull/9871)). ([#10331](https://github.com/crystal-lang/crystal/pull/10331), thanks @straight-shoota) + +### Serialization + +- XML: fix deprecation warning. ([#10335](https://github.com/crystal-lang/crystal/pull/10335), thanks @bcardiff) + +### Runtime + +- Eager load DWARF only if `CRYSTAL_LOAD_DWARF=1`. ([#10326](https://github.com/crystal-lang/crystal/pull/10326), thanks @bcardiff) + +## Compiler + +- **(performance)** Initialize right-away constants in a separate function. ([#10334](https://github.com/crystal-lang/crystal/pull/10334), thanks @asterite) +- Fix incorrect casting between different union types. ([#10333](https://github.com/crystal-lang/crystal/pull/10333), thanks @asterite) +- Fix a formatting error in the "missing argument" error. ([#10325](https://github.com/crystal-lang/crystal/pull/10325), thanks @BlobCodes) +- Fix while condition assignment check for Not. ([#10347](https://github.com/crystal-lang/crystal/pull/10347), thanks @asterite) +- Allow operators and setters-like macros names back. ([#10338](https://github.com/crystal-lang/crystal/pull/10338), thanks @asterite) + +### Language semantics + +- Fix type check not considering virtual types. ([#10304](https://github.com/crystal-lang/crystal/pull/10304), thanks @asterite) +- Use path lookup when looking up type for auto-cast match. ([#10318](https://github.com/crystal-lang/crystal/pull/10318), thanks @asterite) + +# 0.36.0 (2021-01-26) + +## Language changes + +- **(breaking-change)** Reject annotations on ivars defined in base class. ([#9502](https://github.com/crystal-lang/crystal/pull/9502), thanks @waj) +- **(breaking-change)** Make `**` be right associative. ([#9684](https://github.com/crystal-lang/crystal/pull/9684), thanks @asterite) + +### Macros + +- **(breaking-change)** Deprecate `TypeNode#has_attribute?` in favor of `#annotation`. ([#9950](https://github.com/crystal-lang/crystal/pull/9950), thanks @HertzDevil) +- Support heredoc literal in macro. ([#9467](https://github.com/crystal-lang/crystal/pull/9467), thanks @MakeNowJust) +- Support `%Q`, `%i`, `%w`, and `%x` literals in macros. ([#9811](https://github.com/crystal-lang/crystal/pull/9811), thanks @toddsundsted) +- Allow executing multi-assignments in macros. ([#9440](https://github.com/crystal-lang/crystal/pull/9440), thanks @MakeNowJust) + +## Standard library + +- **(breaking-change)** Drop deprecated `CRC32`, `Adler32` top-level. ([#9530](https://github.com/crystal-lang/crystal/pull/9530), thanks @bcardiff) +- **(breaking-change)** Drop deprecated `Flate`, `Gzip`, `Zip`, `Zlib` top-level. ([#9529](https://github.com/crystal-lang/crystal/pull/9529), thanks @bcardiff) +- **(breaking-change)** Drop deprecated `with_color` top-level method. ([#9531](https://github.com/crystal-lang/crystal/pull/9531), thanks @bcardiff) +- **(breaking-change)** Make `SemanticVersion` parsing more strict. ([#9868](https://github.com/crystal-lang/crystal/pull/9868), thanks @hugopl) +- Respect explicitly provided type vars for `Tuple.new` and `NamedTuple.new`. ([#10047](https://github.com/crystal-lang/crystal/pull/10047), thanks @HertzDevil) +- Fix `OptionParser` to handle sub-commands with hyphen. ([#9465](https://github.com/crystal-lang/crystal/pull/9465), thanks @erdnaxeli) +- Fix `OptionParser` to not call handler if value is given to none value handler. ([#9603](https://github.com/crystal-lang/crystal/pull/9603), thanks @MakeNowJust) +- Make `def_equals` compare first by reference. ([#9650](https://github.com/crystal-lang/crystal/pull/9650), thanks @waj) +- Remove unnecessary `def ==(other)` fallbacks. ([#9571](https://github.com/crystal-lang/crystal/pull/9571), thanks @MakeNowJust) +- Fix `OptionParser` indentation of option descriptions. ([#10183](https://github.com/crystal-lang/crystal/pull/10183), thanks @wonderix) +- Add `#hash` to `Log::Metadata`, `Path`, `SemanticVersion`, `Socket::Address`, `URI::Params`, and `UUID`. ([#10119](https://github.com/crystal-lang/crystal/pull/10119), thanks @straight-shoota) +- Refactor several `case` statements with exhaustive `case`/`in`. ([#9656](https://github.com/crystal-lang/crystal/pull/9656), thanks @Sija) +- Remove `Kernel` mentions in docs. ([#9549](https://github.com/crystal-lang/crystal/pull/9549), thanks @toddsundsted) +- Improve docs for `Object#dup`. ([#10053](https://github.com/crystal-lang/crystal/pull/10053), thanks @straight-shoota) +- Fix example codes in multiple places. ([#9818](https://github.com/crystal-lang/crystal/pull/9818), thanks @maiha) +- Fix typos, misspelling and styling. ([#9636](https://github.com/crystal-lang/crystal/pull/9636), [#9638](https://github.com/crystal-lang/crystal/pull/9638), [#9561](https://github.com/crystal-lang/crystal/pull/9561), [#9786](https://github.com/crystal-lang/crystal/pull/9786), [#9840](https://github.com/crystal-lang/crystal/pull/9840), [#9844](https://github.com/crystal-lang/crystal/pull/9844), [#9854](https://github.com/crystal-lang/crystal/pull/9854), [#9869](https://github.com/crystal-lang/crystal/pull/9869), [#10068](https://github.com/crystal-lang/crystal/pull/10068), [#10123](https://github.com/crystal-lang/crystal/pull/10123), [#10102](https://github.com/crystal-lang/crystal/pull/10102), [#10116](https://github.com/crystal-lang/crystal/pull/10116), [#10229](https://github.com/crystal-lang/crystal/pull/10229), [#10252](https://github.com/crystal-lang/crystal/pull/10252), thanks @kubo, @m-o-e, @mgomes, @philipp-classen, @dukeraphaelng, @camreeves, @docelic, @ilmanzo, @Sija, @pxeger, @oprypin) + +### Macros + +- Fix documentation for `#[]=` macro methods. ([#10025](https://github.com/crystal-lang/crystal/pull/10025), thanks @HertzDevil) + +### Numeric + +- **(breaking-change)** Move `Complex#exp`, `Complex#log`, etc. to `Math.exp`, `Math.log` overloads. ([#9739](https://github.com/crystal-lang/crystal/pull/9739), thanks @cristian-lsdb) +- **(performance)** Use unchecked arithmetics in `Int#times`. ([#9511](https://github.com/crystal-lang/crystal/pull/9511), thanks @waj) +- Check for overflow when extending signed integers to unsigned integers. ([#10000](https://github.com/crystal-lang/crystal/pull/10000), thanks @HertzDevil) +- Fix `sprintf` for zero left padding a negative number. ([#9725](https://github.com/crystal-lang/crystal/pull/9725), thanks @asterite) +- Fix `BigInt#to_64` and `BigInt#hash`. ([#10121](https://github.com/crystal-lang/crystal/pull/10121), thanks @oprypin) +- Fix `BigDecimal#to_s` for numbers between `-1` and `0`. ([#9897](https://github.com/crystal-lang/crystal/pull/9897), thanks @dukeraphaelng) +- Fix `Number#step`. ([#10130](https://github.com/crystal-lang/crystal/pull/10130), [#10295](https://github.com/crystal-lang/crystal/pull/10295), thanks @straight-shoota, @bcardiff) +- Make `Complex` unary plus returns `self`. ([#9719](https://github.com/crystal-lang/crystal/pull/9719), thanks @asterite) +- Improve `Int#lcm` and `Int#gcd`. ([#9999](https://github.com/crystal-lang/crystal/pull/9999), thanks @HertzDevil) +- Add `Math::TAU`. ([#10179](https://github.com/crystal-lang/crystal/pull/10179), thanks @j8r) +- Add wrapped unary negation to unsigned integer types to allow `&- x` expressions. ([#9947](https://github.com/crystal-lang/crystal/pull/9947), thanks @HertzDevil) +- Improve documentation of mathematical functions. ([#9994](https://github.com/crystal-lang/crystal/pull/9994), thanks @HertzDevil) + +### Text + +- **(breaking-change)** Fix `String#index` not working well for broken UTF-8 sequences. ([#9713](https://github.com/crystal-lang/crystal/pull/9713), thanks @asterite) +- **(performance)** `String`: Don't materialize `Regex` `match[0]` if not needed. ([#9615](https://github.com/crystal-lang/crystal/pull/9615), thanks @asterite) +- Fix `String#rindex` default offset for matching empty regex correctly. ([#9690](https://github.com/crystal-lang/crystal/pull/9690), thanks @MakeNowJust) +- Add various `String#delete_at` methods. ([#9398](https://github.com/crystal-lang/crystal/pull/9398), thanks @asterite) +- Raise if passing `null` Pointer to `String`. ([#9653](https://github.com/crystal-lang/crystal/pull/9653), thanks @jgaskins) +- Add `Regex#capture_count`. ([#9746](https://github.com/crystal-lang/crystal/pull/9746), thanks @Sija) +- Add `Regex::MatchData#[]` overloading to `Range`. ([#10076](https://github.com/crystal-lang/crystal/pull/10076), thanks @MakeNowJust) +- Fix duplicates in `String` documentation. ([#9987](https://github.com/crystal-lang/crystal/pull/9987), thanks @Nicolab) +- Add docs for substitution to `Kernel.sprintf`. ([#10227](https://github.com/crystal-lang/crystal/pull/10227), thanks @straight-shoota) + +### Collections + +- **(breaking-change)** Make `Hash#reject!`, `Hash#select!`, and `Hash#compact!` consistent with `Array` and return `self`. ([#9904](https://github.com/crystal-lang/crystal/pull/9904), thanks @caspiano) +- **(breaking-change)** Deprecate `Hash#delete_if` in favor of `Hash#reject!`, add `Dequeue#reject!` and `Dequeue#select!`. ([#9878](https://github.com/crystal-lang/crystal/pull/9878), thanks @caspiano) +- **(breaking-change)** Change `Set#delete` to return Bool. ([#9590](https://github.com/crystal-lang/crystal/pull/9590), thanks @j8r) +- **(breaking-change)** Drop deprecated `Enumerable#grep`. ([#9711](https://github.com/crystal-lang/crystal/pull/9711), thanks @j8r) +- **(breaking-change)** Remove `Hash#key_index` . ([#10016](https://github.com/crystal-lang/crystal/pull/10016), thanks @asterite) +- **(breaking-change)** Deprecate `StaticArray#[]=(value)` in favor of `StaticArray#fill`. ([#10027](https://github.com/crystal-lang/crystal/pull/10027), thanks @HertzDevil) +- **(breaking-change)** Rename `Set#{sub,super}set?` to `{sub,super}set_of?`. ([#10187](https://github.com/crystal-lang/crystal/pull/10187), thanks @straight-shoota) +- **(performance)** Optimize `Array#shift` and `Array#unshift`. ([#10081](https://github.com/crystal-lang/crystal/pull/10081), thanks @asterite) +- **(performance)** `Array#rotate` optimization for small arrays. ([#8516](https://github.com/crystal-lang/crystal/pull/8516), thanks @wontruefree) +- ~~Fix `Hash#dup` and `Hash#clone` to return correct type for subclasses. ([#9871](https://github.com/crystal-lang/crystal/pull/9871), thanks @vlazar)~~ (Reverted by [#10331](https://github.com/crystal-lang/crystal/pull/10331) in 0.36.1) +- Fix `Iterator#cons_pair` return type. ([#9788](https://github.com/crystal-lang/crystal/pull/9788), thanks @asterite) +- Fix key type restriction of `Hash#merge` block. ([#9495](https://github.com/crystal-lang/crystal/pull/9495), thanks @MakeNowJust) +- Fix `Range#step`. ([#10203](https://github.com/crystal-lang/crystal/pull/10203), thanks @straight-shoota) +- Let `Range#step` delegate to `begin.step` and allow float based ranges to be stepped. ([#10209](https://github.com/crystal-lang/crystal/pull/10209), thanks @straight-shoota) +- Fix `Indexable#join` with `IO`. ([#10152](https://github.com/crystal-lang/crystal/pull/10152), thanks @straight-shoota) +- Fix `Flatten` for value-type iterators. ([#10096](https://github.com/crystal-lang/crystal/pull/10096), thanks @f-fr) +- Fix `Indexable.range_to_index_and_count` to not raise `IndexError`. ([#10191](https://github.com/crystal-lang/crystal/pull/10191), thanks @straight-shoota) +- Handle recursive structures in `def_clone`, `Hash`, `Array`, `Dequeue`. ([#9800](https://github.com/crystal-lang/crystal/pull/9800), thanks @asterite) +- Add `BitArray#dup`. ([#9550](https://github.com/crystal-lang/crystal/pull/9550), thanks @andremedeiros) +- Allow `Iterator#zip` to take multiple `Iterator` arguments. ([#9944](https://github.com/crystal-lang/crystal/pull/9944), thanks @HertzDevil) +- Add `Iterator#with_object(obj, &)`. ([#9956](https://github.com/crystal-lang/crystal/pull/9956), thanks @HertzDevil) +- Always clear unused bits of `BitArray`s. ([#10008](https://github.com/crystal-lang/crystal/pull/10008), thanks @HertzDevil) +- Update `Enumerable#sum(initial, &)` docs. ([#9860](https://github.com/crystal-lang/crystal/pull/9860), thanks @jage) +- Add `Array#unsafe_build`. ([#10092](https://github.com/crystal-lang/crystal/pull/10092), thanks @straight-shoota) +- Add identity methods for `#sum` and `#product`. ([#10151](https://github.com/crystal-lang/crystal/pull/10151), thanks @straight-shoota) +- Move `combinations`, `permutations`, etc., from `Array` to `Indexable`. ([#10235](https://github.com/crystal-lang/crystal/pull/10235), thanks @wonderix) +- Move sampling methods to `Enumerable`. ([#10129](https://github.com/crystal-lang/crystal/pull/10129), thanks @HertzDevil) +- Use `Set#add` in `Set#add` documentation. ([#9441](https://github.com/crystal-lang/crystal/pull/9441), thanks @hugopl) +- Improve `Hash#values_at` docs. ([#9955](https://github.com/crystal-lang/crystal/pull/9955), thanks @j8r) + +### Serialization + +- **(breaking-change)** Drop deprecated `JSON.mapping`. ([#9527](https://github.com/crystal-lang/crystal/pull/9527), thanks @bcardiff) +- **(breaking-change)** Drop deprecated `YAML.mapping`. ([#9526](https://github.com/crystal-lang/crystal/pull/9526), thanks @bcardiff) +- Fix flag enum deserialization from `Int64`. ([#10145](https://github.com/crystal-lang/crystal/pull/10145), thanks @straight-shoota) +- Add support for selective JSON and YAML serialization. ([#9567](https://github.com/crystal-lang/crystal/pull/9567), thanks @stakach) +- YAML: correctly serialize `infinity` and `NaN`. ([#9780](https://github.com/crystal-lang/crystal/pull/9780), thanks @asterite) +- YAML: allow using discriminator in contained types. ([#9851](https://github.com/crystal-lang/crystal/pull/9851), thanks @asterite) +- Support more literal types in `JSON::Serializable.use_json_discriminator` macro. ([#9222](https://github.com/crystal-lang/crystal/pull/9222), thanks @voximity) +- Support more literal types in `YAML::Serializable.use_yaml_discriminator` macro. ([#10149](https://github.com/crystal-lang/crystal/pull/10149), thanks @straight-shoota) +- Add `JSON` serialization to big number types. ([#9898](https://github.com/crystal-lang/crystal/pull/9898), [#10054](https://github.com/crystal-lang/crystal/pull/10054), thanks @dukeraphaelng, @Sija) +- Handle namespaced defaults in `JSON::Serializable` and `YAML::Serializable`. ([#9733](https://github.com/crystal-lang/crystal/pull/9733), thanks @jgaskins) +- Standardize YAML error location. ([#9273](https://github.com/crystal-lang/crystal/pull/9273), thanks @straight-shoota) +- Update `YAML::Field(converter)` argument docs. ([#9557](https://github.com/crystal-lang/crystal/pull/9557), thanks @dscottboggs) +- Use `content` instead of `value` when inspect `XML::Attribute`. ([#9592](https://github.com/crystal-lang/crystal/pull/9592), thanks @asterite) +- Let `JSON::PullParser#on_key` yield self and return value. ([#10028](https://github.com/crystal-lang/crystal/pull/10028), thanks @straight-shoota) +- Cleanup `YAML`/`JSON` `Any#dig` methods. ([#9415](https://github.com/crystal-lang/crystal/pull/9415), thanks @j8r) +- Document `JSON::PullParser`. ([#9983](https://github.com/crystal-lang/crystal/pull/9983), thanks @erdnaxeli) +- Clarify Serialization Converter requirements and examples. ([#10202](https://github.com/crystal-lang/crystal/pull/10202), thanks @Daniel-Worrall) + +### Time + +- **(breaking-change)** Drop deprecated `Time::Span.new` variants. ([#10051](https://github.com/crystal-lang/crystal/pull/10051), thanks @Sija) +- **(breaking-change)** Deprecate `Time::Span#duration`, use `Time::Span#abs`. ([#10144](https://github.com/crystal-lang/crystal/pull/10144), thanks @straight-shoota) +- Add `%Z` time format directive. ([#10141](https://github.com/crystal-lang/crystal/pull/10141), thanks @straight-shoota) +- Improve handling edge cases for `Time::Location#load`. ([#10140](https://github.com/crystal-lang/crystal/pull/10140), thanks @straight-shoota) +- Enable pending specs. ([#10093](https://github.com/crystal-lang/crystal/pull/10093), thanks @straight-shoota) +- Improve `Time::Span` arithmetic specs. ([#10143](https://github.com/crystal-lang/crystal/pull/10143), thanks @straight-shoota) + +### Files + +- **(breaking-change)** Make `File.size` and `FileInfo#size` be `Int64` instead of `UInt64`. ([#10015](https://github.com/crystal-lang/crystal/pull/10015), thanks @asterite) +- **(breaking-change)** Fix `FileUtils.cp_r` when destination is a directory. ([#10180](https://github.com/crystal-lang/crystal/pull/10180), thanks @wonderix) +- Enable large-file support on i386-linux-gnu. ([#9478](https://github.com/crystal-lang/crystal/pull/9478), thanks @kubo) +- Fix glob not following symdir directories. ([#9910](https://github.com/crystal-lang/crystal/pull/9910), thanks @straight-shoota) +- Windows: allow creating symlinks without admin rights. ([#9767](https://github.com/crystal-lang/crystal/pull/9767), thanks @oprypin) +- Windows: allow touching directories. ([#10284](https://github.com/crystal-lang/crystal/pull/10284), thanks @straight-shoota) +- Fix `Dir.glob` when `readdir` doesn't report file type. ([#9877](https://github.com/crystal-lang/crystal/pull/9877), thanks @straight-shoota) +- Add `Path#stem`. ([#10037](https://github.com/crystal-lang/crystal/pull/10037), thanks @straight-shoota) +- Move `File#fsync`, `File#flock_exclusive`, `File#flock_shared`, and `File#flock_unlock` methods to `IO::FileDescriptor`. ([#9794](https://github.com/crystal-lang/crystal/pull/9794), thanks @naqvis) +- Call `IO#flush` on builder finish methods. ([#9321](https://github.com/crystal-lang/crystal/pull/9321), thanks @straight-shoota) +- Support using `Path` in `MIME` and `File::Error` APIs. ([#10034](https://github.com/crystal-lang/crystal/pull/10034), thanks @Blacksmoke16) +- Windows: enable `FileUtils` specs. ([#9902](https://github.com/crystal-lang/crystal/pull/9902), thanks @oprypin) + +### Networking + +- **(breaking-change)** Rename `HTTP::Params` to `URI::Params`. ([#10098](https://github.com/crystal-lang/crystal/pull/10098), thanks @straight-shoota) +- **(breaking-change)** Rename `URI#full_path` to `URI#request_target`. ([#10099](https://github.com/crystal-lang/crystal/pull/10099), thanks @straight-shoota) +- **(breaking-change)** `URI::Params#[]=` replaces all values. ([#9605](https://github.com/crystal-lang/crystal/pull/9605), thanks @asterite) +- Fix type of `HTTP::Server::Response#closed?` to `Bool`. ([#9489](https://github.com/crystal-lang/crystal/pull/9489), thanks @straight-shoota) +- Fix `Socket#accept` to obey `read_timeout`. ([#9538](https://github.com/crystal-lang/crystal/pull/9538), thanks @waj) +- Fix `Socket` to allow datagram over unix sockets. ([#9838](https://github.com/crystal-lang/crystal/pull/9838), thanks @bcardiff) +- Fix `HTTP::Headers#==` and `HTTP::Headers#hash`. ([#10186](https://github.com/crystal-lang/crystal/pull/10186), thanks @straight-shoota) +- Make `HTTP::StaticFileHandler` serve pre-gzipped content. ([#9626](https://github.com/crystal-lang/crystal/pull/9626), thanks @waj) +- Delete `Content-Encoding` and `Content-Length` headers after decompression in `HTTP::Client::Response`. ([#5212](https://github.com/crystal-lang/crystal/pull/5212), thanks @maiha) +- Delay setup of `HTTP::CompressHandler` until content is written. ([#9625](https://github.com/crystal-lang/crystal/pull/9625), thanks @waj) +- Allow `HTTP::Client` to work with any IO. ([#9543](https://github.com/crystal-lang/crystal/pull/9543), thanks @waj) +- Make `HTTP::Server` don't override `content-length` if already set. ([#9726](https://github.com/crystal-lang/crystal/pull/9726), thanks @asterite) +- Do not share underlying hash between `URI::Param`s. ([#9641](https://github.com/crystal-lang/crystal/pull/9641), thanks @jgaskins) +- Add `HTTP::Cookie::SameSite::None`. ([#9262](https://github.com/crystal-lang/crystal/pull/9262), thanks @j8r) +- Add `HTTP::Client` logging and basic instrumentation. ([#9756](https://github.com/crystal-lang/crystal/pull/9756), thanks @bcardiff) +- Add `HTTP::Request#hostname`+ utils. ([#10029](https://github.com/crystal-lang/crystal/pull/10029), thanks @straight-shoota) +- Add `URI#authority`. ([#10100](https://github.com/crystal-lang/crystal/pull/10100), thanks @straight-shoota) +- Add `Socket::IPAddress#private?`. ([#9517](https://github.com/crystal-lang/crystal/pull/9517), thanks @jgillich) +- Make `Socket::IpAddress` a bit more type-friendly. ([#9528](https://github.com/crystal-lang/crystal/pull/9528), thanks @asterite) +- Make specs pass in non-IPv6 environments. ([#9438](https://github.com/crystal-lang/crystal/pull/9438), thanks @jhass) +- Make specs pending instead of failing in no multi-cast environments. ([#9566](https://github.com/crystal-lang/crystal/pull/9566), thanks @jhass) +- Remove invalid optimization on `Encoding`. ([#9724](https://github.com/crystal-lang/crystal/pull/9724), thanks @asterite) +- Drop `RemoteAddressType` private alias. ([#9777](https://github.com/crystal-lang/crystal/pull/9777), thanks @bcardiff) +- Add documentation for `HTTP::WebSocket`. ([#9761](https://github.com/crystal-lang/crystal/pull/9761), [#9778](https://github.com/crystal-lang/crystal/pull/9778), thanks @ibraheemdev, @bcardiff) +- Fix `HTTP` docs. ([#9612](https://github.com/crystal-lang/crystal/pull/9612), [#9627](https://github.com/crystal-lang/crystal/pull/9627), [#9717](https://github.com/crystal-lang/crystal/pull/9717), thanks @RX14, @asterite, @3n-k1) +- Fix reference to TCP protocol in docs. ([#9457](https://github.com/crystal-lang/crystal/pull/9457), thanks @dprobinson) + +### Logging + +- **(breaking-change)** Drop deprecated `Logger`. ([#9525](https://github.com/crystal-lang/crystal/pull/9525), thanks @bcardiff) +- **(performance)** Add logging dispatcher to defer entry dispatch from current fiber. ([#9432](https://github.com/crystal-lang/crystal/pull/9432), thanks @waj) +- Take timestamp as an argument in `Log::Entry` initializer. ([#9570](https://github.com/crystal-lang/crystal/pull/9570), thanks @Sija) +- Improve docs regarding `Log.setup`. ([#9497](https://github.com/crystal-lang/crystal/pull/9497), [#9559](https://github.com/crystal-lang/crystal/pull/9559), thanks @caspiano, @jjlorenzo) + +### Crypto + +- **(security)** Fix `"force-peer"` `verify_mode` setup. ([#9668](https://github.com/crystal-lang/crystal/pull/9668), thanks @PhilAtWysdom) +- **(security)** Force secure renegotiation on server to prevent Secure Client-Initiated Renegotiation vulnerability attack. ([#9815](https://github.com/crystal-lang/crystal/pull/9815), thanks @bcardiff) +- **(breaking-change)** Refactor `Digest` and introduce `Digest::MD5`, `Digest::SHA1`, `Digest::SHA256`, `Digest::SHA512` backed by openssl. ([#9864](https://github.com/crystal-lang/crystal/pull/9864), thanks @bcardiff) +- Fix overflows in MD5 and SHA1. ([#9781](https://github.com/crystal-lang/crystal/pull/9781), thanks @bcardiff) +- Add `OpenSSL::SSL::Context#set_modern_ciphers` and related methods to set ciphers and cipher suites in a more portable fashion. ([#9814](https://github.com/crystal-lang/crystal/pull/9814), [#10298](https://github.com/crystal-lang/crystal/pull/10298), thanks @bcardiff) +- Add `OpenSSL::SSL::Context#security_level`. ([#9831](https://github.com/crystal-lang/crystal/pull/9831), thanks @bcardiff) +- `OpenSSL::digest` add require for `OpenSSL::Error`. ([#10173](https://github.com/crystal-lang/crystal/pull/10173), thanks @wonderix) + +### Concurrency + +- **(breaking-change)** Hide Channel internal implementation methods. ([#9564](https://github.com/crystal-lang/crystal/pull/9564), thanks @j8r) +- `Channel#close` returns `true` unless the channel was already closed. ([#9443](https://github.com/crystal-lang/crystal/pull/9443), thanks @waj) +- Support splat expressions inside `spawn` macro. ([#10234](https://github.com/crystal-lang/crystal/pull/10234), thanks @HertzDevil) +- Remove incorrect note about negative timeout not triggering. ([#10131](https://github.com/crystal-lang/crystal/pull/10131), thanks @straight-shoota) + +### System + +- Fix `Process.find_executable` to check the found path is executable and add Windows support. ([#9365](https://github.com/crystal-lang/crystal/pull/9365), thanks @oprypin) +- Add `Process.parse_arguments` and fix `CRYSTAL_OPTS` parsing. ([#9518](https://github.com/crystal-lang/crystal/pull/9518), thanks @MakeNowJust) +- Port to NetBSD. ([#9360](https://github.com/crystal-lang/crystal/pull/9360), thanks @niacat) +- Add note about raising `IO::Error` to `Process` methods. ([#9894](https://github.com/crystal-lang/crystal/pull/9894), thanks @straight-shoota) +- Handle errors in `User`/`Group` `from_name?`/`from_id?` as not found. ([#10182](https://github.com/crystal-lang/crystal/pull/10182), thanks @wonderix) +- define the `SC_PAGESIZE` constant on more platforms. ([#9821](https://github.com/crystal-lang/crystal/pull/9821), thanks @carlhoerberg) + +### Runtime + +- Fix bug with passing many args then a struct in Win64 C lib ABI. ([#9520](https://github.com/crystal-lang/crystal/pull/9520), thanks @oprypin) +- Fix C ABI for AArch64. ([#9430](https://github.com/crystal-lang/crystal/pull/9430), thanks @jhass) +- Disable LLVM Global Isel. ([#9401](https://github.com/crystal-lang/crystal/pull/9401), [#9562](https://github.com/crystal-lang/crystal/pull/9562), thanks @jhass, @bcardiff) +- Print original exception after failing to raise it. ([#9220](https://github.com/crystal-lang/crystal/pull/9220), thanks @jhass) +- Use Dwarf information on `Exception::CallStack.print_frame` and crash stacktraces. ([#9792](https://github.com/crystal-lang/crystal/pull/9792), [#9852](https://github.com/crystal-lang/crystal/pull/9852), thanks @bcardiff) +- `MachO`: Handle missing `LC_UUID`. ([#9706](https://github.com/crystal-lang/crystal/pull/9706), thanks @bcardiff) +- Allow `WeakRef` to compile with `-Dgc_none`. ([#9806](https://github.com/crystal-lang/crystal/pull/9806), thanks @bcardiff) +- Fix example in `Crystal.main` docs. ([#9736](https://github.com/crystal-lang/crystal/pull/9736), thanks @hugopl) +- Add a C ABI spec for accepting a large struct - failing on Win32, AArch64. ([#9534](https://github.com/crystal-lang/crystal/pull/9534), thanks @oprypin) +- Don't relativize paths outside of current directory in backtrace. ([#10177](https://github.com/crystal-lang/crystal/pull/10177), thanks @Sija) +- Avoid loading DWARF if env var `CRYSTAL_LOAD_DWARF=0`. ([#10261](https://github.com/crystal-lang/crystal/pull/10261), thanks @bcardiff) +- Fix DWARF loading in FreeBSD. ([#10259](https://github.com/crystal-lang/crystal/pull/10259), thanks @bcardiff) +- Print unhandled exceptions from main routine with `#inspect_with_backtrace`. ([#10086](https://github.com/crystal-lang/crystal/pull/10086), thanks @straight-shoota) + +### Spec + +- **(breaking-change)** Add support for custom failure messages in expectations. ([#10127](https://github.com/crystal-lang/crystal/pull/10127), [#10289](https://github.com/crystal-lang/crystal/pull/10289), thanks @Fryguy, @straight-shoota) +- Allow absolute file paths in `crystal spec` CLI. ([#9951](https://github.com/crystal-lang/crystal/pull/9951), thanks @KevinSjoberg) +- Allow `--link-flags` to be used. ([#6243](https://github.com/crystal-lang/crystal/pull/6243), thanks @bcardiff) +- Improve duration display in `spec --profile`. ([#10044](https://github.com/crystal-lang/crystal/pull/10044), thanks @straight-shoota) +- Add exception handler in spec `at_exit`. ([#10106](https://github.com/crystal-lang/crystal/pull/10106), thanks @straight-shoota) + +## Compiler + +- **(performance)** Don't use init check for constants that are declared before read. ([#9801](https://github.com/crystal-lang/crystal/pull/9801), thanks @asterite) +- **(performance)** Don't use init check for class vars initialized before read. ([#9995](https://github.com/crystal-lang/crystal/pull/9995), thanks @asterite) +- **(performance)** Don't use init flag for string constants. ([#9808](https://github.com/crystal-lang/crystal/pull/9808), thanks @asterite) +- Run side effects for class var with constant initializer. ([#10010](https://github.com/crystal-lang/crystal/pull/10010), thanks @asterite) +- Fix `VaList` and disable `va_arg` for AArch64. ([#9422](https://github.com/crystal-lang/crystal/pull/9422), thanks @jhass) +- Fix cache corrupted when compilation is cancelled. ([#9558](https://github.com/crystal-lang/crystal/pull/9558), thanks @waj) +- Consider `select` as an opening keyword in macros. ([#9624](https://github.com/crystal-lang/crystal/pull/9624), thanks @asterite) +- Use function attribute `frame-pointer`. ([#9361](https://github.com/crystal-lang/crystal/pull/9361), thanks @kubo39) +- Make missing `__crystal_raise_overflow` error more clear. ([#9686](https://github.com/crystal-lang/crystal/pull/9686), thanks @3n-k1) +- Forbid calls with both block arg and block. ([#10026](https://github.com/crystal-lang/crystal/pull/10026), thanks @HertzDevil) +- Add method name to frozen type error. ([#10057](https://github.com/crystal-lang/crystal/pull/10057), thanks @straight-shoota) +- Use file lock to avoid clashes between compilations. ([#10050](https://github.com/crystal-lang/crystal/pull/10050), thanks @asterite) +- Add a couple of missing end locations to some nodes. ([#10012](https://github.com/crystal-lang/crystal/pull/10012), thanks @asterite) +- Don't visit enum member expanded by macro twice. ([#10105](https://github.com/crystal-lang/crystal/pull/10105), thanks @asterite) +- Fix crash when matching arguments against splat restriction after positional parameters. ([#10172](https://github.com/crystal-lang/crystal/pull/10172), thanks @HertzDevil) +- Fix parsing of `do end` blocks inside parenthesis. ([#10189](https://github.com/crystal-lang/crystal/pull/10189), [#10207](https://github.com/crystal-lang/crystal/pull/10207), thanks @straight-shoota, @Sija) +- Fix regex parsing after then in `case/when`. ([#10274](https://github.com/crystal-lang/crystal/pull/10274), thanks @asterite) +- Fix error message for non-exhaustive case statements of 3 or more `Bool`/`Enum` values. ([#10194](https://github.com/crystal-lang/crystal/pull/10194), thanks @HertzDevil) +- Forbid reopening generic types with different type var splats. ([#10167](https://github.com/crystal-lang/crystal/pull/10167), thanks @HertzDevil) +- Apply stricter checks to macro names. ([#10069](https://github.com/crystal-lang/crystal/pull/10069), thanks @HertzDevil) +- Reword error message when including/inheriting generic type without type vars. ([#10206](https://github.com/crystal-lang/crystal/pull/10206), thanks @HertzDevil) +- Print a message pointing to the online playground if compiled without it. ([#9622](https://github.com/crystal-lang/crystal/pull/9622), thanks @deiv) +- Handle typedef type restriction against alias type correctly. ([#9490](https://github.com/crystal-lang/crystal/pull/9490), thanks @MakeNowJust) +- Initial support LLVM 11.0 with known issue regarding optimizations [#10220](https://github.com/crystal-lang/crystal/issues/10220). ([#9829](https://github.com/crystal-lang/crystal/pull/9829), [#10293](https://github.com/crystal-lang/crystal/pull/10293), thanks @bcardiff) +- Add support for `--mcpu native`. ([#10264](https://github.com/crystal-lang/crystal/pull/10264), [#10276](https://github.com/crystal-lang/crystal/pull/10276), thanks @BlobCodes, @bcardiff) +- Fix `Process.run` uses in compiler to handle exec failure. ([#9893](https://github.com/crystal-lang/crystal/pull/9893), [#9911](https://github.com/crystal-lang/crystal/pull/9911), [#9913](https://github.com/crystal-lang/crystal/pull/9913), thanks @straight-shoota, @bcardiff, @oprypin) +- Fix `Command#report_warnings` to gracefully handle missing `Compiler#program`. ([#9866](https://github.com/crystal-lang/crystal/pull/9866), thanks @straight-shoota) +- Add `Program#host_compiler`. ([#9920](https://github.com/crystal-lang/crystal/pull/9920), thanks @straight-shoota) +- Add `Crystal::Path#name_size` implementation. ([#9380](https://github.com/crystal-lang/crystal/pull/9380), thanks @straight-shoota) +- Refactor helper methods in `call_error.cr`. ([#9376](https://github.com/crystal-lang/crystal/pull/9376), thanks @straight-shoota) +- Delegate def error location to implementing type. ([#10159](https://github.com/crystal-lang/crystal/pull/10159), thanks @straight-shoota) +- Refactor `Crystal::Exception` to `Crystal::CodeError`. ([#10197](https://github.com/crystal-lang/crystal/pull/10197), thanks @straight-shoota) +- Refactor `check_single_def_error_message`. ([#10196](https://github.com/crystal-lang/crystal/pull/10196), thanks @straight-shoota) +- Code clean-ups. ([#9468](https://github.com/crystal-lang/crystal/pull/9468), thanks @MakeNowJust) +- Refactors for compiler specs. ([#9379](https://github.com/crystal-lang/crystal/pull/9379), thanks @straight-shoota) +- Windows CI: Fix and enable compiler specs. ([#9348](https://github.com/crystal-lang/crystal/pull/9348), [#9560](https://github.com/crystal-lang/crystal/pull/9560), thanks @oprypin) + +### Language semantics + +- **(breaking-change)** Handle union restrictions with free vars properly. ([#10267](https://github.com/crystal-lang/crystal/pull/10267), thanks @HertzDevil) +- **(breaking-change)** Disallow keywords as block argument name. ([#9704](https://github.com/crystal-lang/crystal/pull/9704), thanks @asterite) +- **(breaking-change)** Fix `a-b -c` incorrectly parsed as `a - (b - c)`. ([#9652](https://github.com/crystal-lang/crystal/pull/9652), [#9884](https://github.com/crystal-lang/crystal/pull/9884), thanks @asterite, @bcardiff) +- **(breaking-change)** Check abstract def implementations with splats, default values and keyword arguments. ([#9585](https://github.com/crystal-lang/crystal/pull/9585), thanks @waj) +- **(breaking-change)** Make abstract def return type warning an error. ([#9810](https://github.com/crystal-lang/crystal/pull/9810), thanks @bcardiff) +- **(breaking-change)** Error when abstract def implementation adds a type restriction. ([#9634](https://github.com/crystal-lang/crystal/pull/9634), thanks @waj) +- **(breaking-change)** Don't interpret Proc typedefs as aliases. ([#10254](https://github.com/crystal-lang/crystal/pull/10254), thanks @HertzDevil) +- **(breaking-change)** Remove the special logic of union of pointer and `nil`. ([#9872](https://github.com/crystal-lang/crystal/pull/9872), thanks @asterite) +- Check abstract def implementations with double splats. ([#9633](https://github.com/crystal-lang/crystal/pull/9633), thanks @waj) +- Make inherited hook work through generic instances. ([#9701](https://github.com/crystal-lang/crystal/pull/9701), thanks @asterite) +- Attach doc comment to annotation in macro expansion. ([#9630](https://github.com/crystal-lang/crystal/pull/9630), thanks @MakeNowJust) +- Marks else branch of exhaustive case as unreachable, improving resulting type. ([#9659](https://github.com/crystal-lang/crystal/pull/9659), thanks @Sija) +- Make `Pointer(T)#value=` stricter for generic arguments. ([#10224](https://github.com/crystal-lang/crystal/pull/10224), thanks @asterite) +- Extend type filtering of conditional clauses to arbitrary logical connectives. ([#10147](https://github.com/crystal-lang/crystal/pull/10147), thanks @HertzDevil) +- Support NamedTuple instance restrictions in redefinition checks. ([#10245](https://github.com/crystal-lang/crystal/pull/10245), thanks @HertzDevil) +- Respect block arg restriction when given block has a splat parameter. ([#10242](https://github.com/crystal-lang/crystal/pull/10242), thanks @HertzDevil) +- Make overloads without double splat more specialized than overloads with one. ([#10185](https://github.com/crystal-lang/crystal/pull/10185), thanks @HertzDevil) +- Support type splats inside explicit Unions in type restrictions. ([#10174](https://github.com/crystal-lang/crystal/pull/10174), thanks @HertzDevil) +- Fix splats on generic arguments. ([#9859](https://github.com/crystal-lang/crystal/pull/9859), thanks @asterite) +- Correctly compute line number after macro escape. ([#9858](https://github.com/crystal-lang/crystal/pull/9858), thanks @asterite) +- Fix bug for C structs assigned to a type. ([#9743](https://github.com/crystal-lang/crystal/pull/9743), thanks @matthewmcgarvey) +- Promote C variadic args as needed. ([#9747](https://github.com/crystal-lang/crystal/pull/9747), thanks @asterite) +- Fix parsing of `def self./` and `def self.%`. ([#9721](https://github.com/crystal-lang/crystal/pull/9721), thanks @asterite) +- Fix `ASTNode#to_s` for parenthesized expression in block. ([#9629](https://github.com/crystal-lang/crystal/pull/9629), thanks @MakeNowJust) +- Don't form a closure on `typeof(@ivar)`. ([#9723](https://github.com/crystal-lang/crystal/pull/9723), thanks @asterite) +- Add a few missing `expanded.transform self`. ([#9506](https://github.com/crystal-lang/crystal/pull/9506), thanks @asterite) +- Treat `super` as special only if it doesn't have a receiver (i.e.: allow `super` user defined methods). ([#10011](https://github.com/crystal-lang/crystal/pull/10011), thanks @asterite) +- Implement auto-casting in a better way. ([#9501](https://github.com/crystal-lang/crystal/pull/9501), thanks @asterite) +- Try literal type first when autocasting to unions types. ([#9610](https://github.com/crystal-lang/crystal/pull/9610), thanks @asterite) +- Correctly pass named arguments for `previous_def` and `super`. ([#9834](https://github.com/crystal-lang/crystal/pull/9834), thanks @asterite) +- Turn proc pointer into proc literal in some cases to closure needed variables. ([#9824](https://github.com/crystal-lang/crystal/pull/9824), thanks @asterite) +- Fix proc of self causing multi-dispatch. ([#9972](https://github.com/crystal-lang/crystal/pull/9972), thanks @asterite) +- Fixes and improvements to closured variables. ([#9986](https://github.com/crystal-lang/crystal/pull/9986), thanks @asterite) +- Don't merge proc types but allow assigning them if they are compatible. ([#9971](https://github.com/crystal-lang/crystal/pull/9971), thanks @asterite) +- Reset nilable vars inside block method. ([#10091](https://github.com/crystal-lang/crystal/pull/10091), thanks @asterite) +- Reset free vars before attempting each overload match. ([#10271](https://github.com/crystal-lang/crystal/pull/10271), thanks @HertzDevil) +- Let `offsetof` support `Tuples`. ([#10218](https://github.com/crystal-lang/crystal/pull/10218), thanks @hugopl) + +## Tools + +- Fetch git config correctly. ([#9640](https://github.com/crystal-lang/crystal/pull/9640), thanks @dorianmariefr) + +### Formatter + +- Avoid adding a newline after comment before end. ([#9722](https://github.com/crystal-lang/crystal/pull/9722), thanks @asterite) +- Apply string pieces combination even if heredoc has no indent. ([#9475](https://github.com/crystal-lang/crystal/pull/9475), thanks @MakeNowJust) +- Correctly format named arguments like `foo:bar`. ([#9740](https://github.com/crystal-lang/crystal/pull/9740), thanks @asterite) +- Handle comment before do in a separate line. ([#9762](https://github.com/crystal-lang/crystal/pull/9762), thanks @asterite) +- Fix indent for the first comment of `select when`/`else`. ([#10002](https://github.com/crystal-lang/crystal/pull/10002), thanks @MakeNowJust) +- Reduce redundant newlines at end of `begin ... end`. ([#9741](https://github.com/crystal-lang/crystal/pull/9741), thanks @MakeNowJust) +- Let formatter normalize crystal language tag for code blocks in doc comments. ([#10156](https://github.com/crystal-lang/crystal/pull/10156), thanks @straight-shoota) +- Fix indentation of trailing comma in call with a block. ([#10223](https://github.com/crystal-lang/crystal/pull/10223), thanks @MakeNowJust) + +### Doc generator + +- Linkification: refactor, fix edge cases and add specs. ([#9817](https://github.com/crystal-lang/crystal/pull/9817), thanks @oprypin) +- Exclude types from docs who are in nodoc namespaces. ([#9819](https://github.com/crystal-lang/crystal/pull/9819), thanks @Blacksmoke16) +- Fix link generation for lib type reference. ([#9931](https://github.com/crystal-lang/crystal/pull/9931), thanks @straight-shoota) +- Correctly render underscores in code blocks. ([#9822](https://github.com/crystal-lang/crystal/pull/9822), thanks @Blacksmoke16) +- Fix some HTML in docs generator. ([#9918](https://github.com/crystal-lang/crystal/pull/9918), thanks @straight-shoota) +- Correctly handle broken source code on syntax highlighting. ([#9416](https://github.com/crystal-lang/crystal/pull/9416), thanks @MakeNowJust) +- Re-introduce canonical URL for docs generator. ([#9917](https://github.com/crystal-lang/crystal/pull/9917), thanks @straight-shoota) +- Expose `"location"` and `"args_html"` in doc JSON. ([#10122](https://github.com/crystal-lang/crystal/pull/10122), [#10200](https://github.com/crystal-lang/crystal/pull/10200), thanks @oprypin) +- Expose `"aliased_html"` in doc JSON, make `"aliased"` field nullable. ([#10117](https://github.com/crystal-lang/crystal/pull/10117), thanks @oprypin) +- Support linking to section headings in same tab. ([#9826](https://github.com/crystal-lang/crystal/pull/9826), thanks @Blacksmoke16) +- Do not highlight `undef` as a keyword, it's not. ([#10216](https://github.com/crystal-lang/crystal/pull/10216), thanks @rhysd) + +## Others + +- CI improvements and housekeeping. ([#9507](https://github.com/crystal-lang/crystal/pull/9507), [#9513](https://github.com/crystal-lang/crystal/pull/9513), [#9512](https://github.com/crystal-lang/crystal/pull/9512), [#9515](https://github.com/crystal-lang/crystal/pull/9515), [#9514](https://github.com/crystal-lang/crystal/pull/9514), [#9609](https://github.com/crystal-lang/crystal/pull/9609), [#9642](https://github.com/crystal-lang/crystal/pull/9642), [#9758](https://github.com/crystal-lang/crystal/pull/9758), [#9763](https://github.com/crystal-lang/crystal/pull/9763), [#9850](https://github.com/crystal-lang/crystal/pull/9850), [#9906](https://github.com/crystal-lang/crystal/pull/9906), [#9907](https://github.com/crystal-lang/crystal/pull/9907), [#9912](https://github.com/crystal-lang/crystal/pull/9912), [#10078](https://github.com/crystal-lang/crystal/pull/10078), [#10217](https://github.com/crystal-lang/crystal/pull/10217), [#10255](https://github.com/crystal-lang/crystal/pull/10255), thanks @jhass, @bcardiff, @waj, @oprypin, @j8r, @Sija) +- Add timeout to individual specs on multi-threading. ([#9865](https://github.com/crystal-lang/crystal/pull/9865), thanks @bcardiff) +- Initial AArch64 CI. ([#9508](https://github.com/crystal-lang/crystal/pull/9508), [#9582](https://github.com/crystal-lang/crystal/pull/9582), thanks @jhass, @waj) +- Update distribution-scripts and use LLVM 10 on Linux packages. ([#9710](https://github.com/crystal-lang/crystal/pull/9710), thanks @bcardiff) +- Update distribution-scripts and publish nightly packages to bintray. ([#9663](https://github.com/crystal-lang/crystal/pull/9663), thanks @bcardiff) +- Update distribution-scripts to use Shards v0.13.0. ([#10280](https://github.com/crystal-lang/crystal/pull/10280), thanks @bcardiff) +- Initial Nix development environment. Use Nix for OSX CI instead of homebrew. ([#9727](https://github.com/crystal-lang/crystal/pull/9727), [#9776](https://github.com/crystal-lang/crystal/pull/9776), thanks @bcardiff) +- Prevent recursive call in `bin/crystal` wrapper. ([#9505](https://github.com/crystal-lang/crystal/pull/9505), thanks @jhass) +- Allow overriding `CRYSTAL_PATH` in `bin/crystal` wrapper . ([#9632](https://github.com/crystal-lang/crystal/pull/9632), thanks @bcardiff) +- Makefile: support changing the current crystal via env var and argument. ([#9471](https://github.com/crystal-lang/crystal/pull/9471), thanks @bcardiff) +- Makefile: improve supported LLVM versions message. ([#10221](https://github.com/crystal-lang/crystal/pull/10221), [#10269](https://github.com/crystal-lang/crystal/pull/10269), thanks @rdp, @straight-shoota) +- Update CONTRIBUTING.md. ([#9448](https://github.com/crystal-lang/crystal/pull/9448), [#10249](https://github.com/crystal-lang/crystal/pull/10249), [#10250](https://github.com/crystal-lang/crystal/pull/10250), thanks @wontruefree, @sanks64, @straight-shoota) +- Refactor `RedBlackTree` sample to use an enum instead of symbols. ([#10233](https://github.com/crystal-lang/crystal/pull/10233), thanks @Sija) +- Add security policy. ([#9984](https://github.com/crystal-lang/crystal/pull/9984), thanks @straight-shoota) +- Use `name: nightly` in docs-versions.sh for HEAD. ([#9915](https://github.com/crystal-lang/crystal/pull/9915), thanks @straight-shoota) +- Use canonical base url for generating docs. ([#10007](https://github.com/crystal-lang/crystal/pull/10007), thanks @straight-shoota) +- Remove Vagrantfile. ([#10033](https://github.com/crystal-lang/crystal/pull/10033), thanks @straight-shoota) +- Update NOTICE's copyright year to 2021. ([#10166](https://github.com/crystal-lang/crystal/pull/10166), thanks @matiasgarciaisaia) + +# 0.35.1 (2020-06-19) + +## Standard library + +### Collections + +- Remove `Hash#each` type restriction to allow working with splats. ([#9456](https://github.com/crystal-lang/crystal/pull/9456), thanks @bcardiff) + +### Networking + +- Revert `IO#write` changes in 0.35.0 and let it return Nil. ([#9469](https://github.com/crystal-lang/crystal/pull/9469), thanks @bcardiff) +- Avoid leaking logging context in HTTP request handlers. ([#9494](https://github.com/crystal-lang/crystal/pull/9494), thanks @Blacksmoke16) + +### Crypto + +- Use less strict cipher compatibility for OpenSSL client context. ([#9459](https://github.com/crystal-lang/crystal/pull/9459), thanks @straight-shoota) +- Fix `Digest::Base` block argument type restrictions. ([#9500](https://github.com/crystal-lang/crystal/pull/9500), thanks @straight-shoota) + +### Logging + +- Fix `Log.context.set` docs for hash based data. ([#9470](https://github.com/crystal-lang/crystal/pull/9470), thanks @bcardiff) + +## Compiler + +- Show warnings even if there are errors. ([#9461](https://github.com/crystal-lang/crystal/pull/9461), thanks @asterite) +- Fix parsing of `{foo: X, typeof: Y}` type. ([#9453](https://github.com/crystal-lang/crystal/pull/9453), thanks @MakeNowJust) +- Fix parsing of proc in hash `of` key type. ([#9458](https://github.com/crystal-lang/crystal/pull/9458), thanks @MakeNowJust) +- Revert debug level information changes in specs to fix 32 bits builds. ([#9466](https://github.com/crystal-lang/crystal/pull/9466), thanks @bcardiff) + +## Others + +- CI improvements and housekeeping. ([#9455](https://github.com/crystal-lang/crystal/pull/9455), thanks @bcardiff) +- Code formatting. ([#9482](https://github.com/crystal-lang/crystal/pull/9482), thanks @MakeNowJust) + +# 0.35.0 (2020-06-09) + +## Language changes + +- **(breaking-change)** Let `case when` be non-exhaustive, introduce `case in` as exhaustive. ([#9258](https://github.com/crystal-lang/crystal/pull/9258), [#9045](https://github.com/crystal-lang/crystal/pull/9045), thanks @asterite) +- Allow `->@ivar.foo` and `->@@cvar.foo` expressions. ([#9268](https://github.com/crystal-lang/crystal/pull/9268), thanks @MakeNowJust) + +### Macros + +- Allow executing OpAssign (`+=`, `||=`, etc.) inside macros. ([#9409](https://github.com/crystal-lang/crystal/pull/9409), thanks @asterite) + +## Standard library + +- **(breaking-change)** Refactor to standardize on first argument for methods receiving `IO`. ([#9134](https://github.com/crystal-lang/crystal/pull/9134), [#9289](https://github.com/crystal-lang/crystal/pull/9289), [#9303](https://github.com/crystal-lang/crystal/pull/9303), [#9318](https://github.com/crystal-lang/crystal/pull/9318), thanks @straight-shoota, @bcardiff, @oprypin) +- **(breaking-change)** Cleanup Digest and OpenSSL::Digest. ([#8426](https://github.com/crystal-lang/crystal/pull/8426), thanks @didactic-drunk) +- Fix `Enum#to_s` for private enum. ([#9126](https://github.com/crystal-lang/crystal/pull/9126), thanks @straight-shoota) +- Refactor `Benchmark::IPS::Entry` to use `UInt64` in `bytes_per_op`. ([#9081](https://github.com/crystal-lang/crystal/pull/9081), thanks @jhass) +- Add `Experimental` annotation and doc label. ([#9244](https://github.com/crystal-lang/crystal/pull/9244), thanks @bcardiff) +- Add subcommands to `OptionParser`. ([#9009](https://github.com/crystal-lang/crystal/pull/9009), [#9133](https://github.com/crystal-lang/crystal/pull/9133), thanks @RX14, @Sija) +- Make `NamedTuple#sorted_keys` public. ([#9263](https://github.com/crystal-lang/crystal/pull/9263), thanks @waj) +- Fix example codes in multiple places. ([#9203](https://github.com/crystal-lang/crystal/pull/9203), thanks @maiha) + +### Macros + +- **(breaking-change)** Remove top-level `assert_responds_to` macro. ([#9085](https://github.com/crystal-lang/crystal/pull/9085), thanks @bcardiff) +- **(breaking-change)** Drop top-level `parallel` macro. ([#9097](https://github.com/crystal-lang/crystal/pull/9097), thanks @bcardiff) +- Fix lazy property not forwarding annotations. ([#9140](https://github.com/crystal-lang/crystal/pull/9140), thanks @asterite) +- Add `host_flag?` macro method, not affected by cross-compilation. ([#9049](https://github.com/crystal-lang/crystal/pull/9049), thanks @oprypin) +- Add `.each` and `.each_with_index` to various macro types. ([#9120](https://github.com/crystal-lang/crystal/pull/9120), thanks @Blacksmoke16) +- Add `StringLiteral#titleize` macro method. ([#9269](https://github.com/crystal-lang/crystal/pull/9269), thanks @MakeNowJust) +- Add `TypeNode` methods to check what "type" the node is. ([#9270](https://github.com/crystal-lang/crystal/pull/9270), thanks @Blacksmoke16) +- Fix support `TypeNode.name(generic_args: false)` for generic instances. ([#9224](https://github.com/crystal-lang/crystal/pull/9224), thanks @Blacksmoke16) + +### Numeric + +- **(breaking-change)** Add `Int#digits`, reverse `BigInt#digits` result. ([#9383](https://github.com/crystal-lang/crystal/pull/9383), thanks @asterite) +- Fix overflow checking for operations with mixed sign. ([#9403](https://github.com/crystal-lang/crystal/pull/9403), thanks @waj) +- Add `BigInt#factorial` using GMP. ([#9132](https://github.com/crystal-lang/crystal/pull/9132), thanks @peheje) + +### Text + +- Add `String#titleize`. ([#9204](https://github.com/crystal-lang/crystal/pull/9204), thanks @hugopl) +- Add `Regex#matches?` and `String#matches?`. ([#8989](https://github.com/crystal-lang/crystal/pull/8989), thanks @MakeNowJust) +- Add `IO` overloads to various `String` case methods. ([#9236](https://github.com/crystal-lang/crystal/pull/9236), thanks @Blacksmoke16) +- Improve docs examples regarding `Regex::MatchData`. ([#9010](https://github.com/crystal-lang/crystal/pull/9010), thanks @MakeNowJust) +- Improve docs on `String` methods. ([#8447](https://github.com/crystal-lang/crystal/pull/8447), thanks @jan-zajic) + +### Collections + +- **(breaking-change)** Add `Enumerable#first` with fallback block. ([#8999](https://github.com/crystal-lang/crystal/pull/8999), thanks @MakeNowJust) +- Fix `Array#delete_at` bug with negative start index. ([#9399](https://github.com/crystal-lang/crystal/pull/9399), thanks @asterite) +- Fix `Enumerable#{zip,zip?}` when self is an `Iterator`. ([#9330](https://github.com/crystal-lang/crystal/pull/9330), thanks @mneumann) +- Make `Range#each` and `Range#reverse_each` work better with end/begin-less values. ([#9325](https://github.com/crystal-lang/crystal/pull/9325), thanks @asterite) +- Improve docs on `Hash`. ([#8887](https://github.com/crystal-lang/crystal/pull/8887), thanks @rdp) + +### Serialization + +- **(breaking-change)** Deprecate `JSON.mapping` and `YAML.mapping`. ([#9272](https://github.com/crystal-lang/crystal/pull/9272), thanks @straight-shoota) +- **(breaking-change)** Make `INI` a module. ([#9408](https://github.com/crystal-lang/crystal/pull/9408), thanks @j8r) +- Fix integration between `record` macro and `JSON::Serializable`/`YAML::Serializable` regarding default values. ([#9063](https://github.com/crystal-lang/crystal/pull/9063), thanks @Blacksmoke16) +- Fix `XML.parse` invalid mem access in multi-thread. ([#9098](https://github.com/crystal-lang/crystal/pull/9098), thanks @bcardiff, @asterite) +- Fix double string escape in `XML::Node#content=`. ([#9300](https://github.com/crystal-lang/crystal/pull/9300), thanks @straight-shoota) +- Improve xpath regarding namespaces. ([#9288](https://github.com/crystal-lang/crystal/pull/9288), thanks @asterite) +- Escape CDATA end sequences. ([#9230](https://github.com/crystal-lang/crystal/pull/9230), thanks @Blacksmoke16) +- Add `JSON` and `YAML` serialization to `Path`. ([#9156](https://github.com/crystal-lang/crystal/pull/9156), thanks @straight-shoota) +- Specify pkg-config name for `libyaml`. ([#9426](https://github.com/crystal-lang/crystal/pull/9426), thanks @jhass) +- Specify pkg-config name for `libxml2`. ([#9436](https://github.com/crystal-lang/crystal/pull/9436), thanks @Blacksmoke16) +- Make YAML specs robust against libyaml 0.2.5. ([#9427](https://github.com/crystal-lang/crystal/pull/9427), thanks @jhass) + +### Time + +- **(breaking-change)** Support different number of fraction digits for RFC3339 time format. ([#9283](https://github.com/crystal-lang/crystal/pull/9283), thanks @waj) +- Fix parsing AM/PM hours. ([#9334](https://github.com/crystal-lang/crystal/pull/9334), thanks @straight-shoota) +- Improve `File.utime` precision from second to 100-nanosecond on Windows. ([#9344](https://github.com/crystal-lang/crystal/pull/9344), thanks @kubo) + +### Files + +- **(breaking-change)** Move `Flate`, `Gzip`, `Zip`, `Zlib` to `Compress`. ([#8886](https://github.com/crystal-lang/crystal/pull/8886), thanks @bcardiff) +- **(breaking-change)** Cleanup `File` & `FileUtils`. ([#9175](https://github.com/crystal-lang/crystal/pull/9175), thanks @bcardiff) +- Fix realpath on macOS 10.15 (Catalina). ([#9296](https://github.com/crystal-lang/crystal/pull/9296), thanks @waj) +- Fix `File#pos`, `File#seek` and `File#truncate` over 2G on Windows. ([#9015](https://github.com/crystal-lang/crystal/pull/9015), thanks @kubo) +- Fix `File.rename` to overwrite the destination file on Windows, like elsewhere. ([#9038](https://github.com/crystal-lang/crystal/pull/9038), thanks @oprypin) +- Fix `File`'s specs and related exception types on Windows. ([#9037](https://github.com/crystal-lang/crystal/pull/9037), thanks @oprypin) +- Add support for `Path` arguments to multiple methods. ([#9153](https://github.com/crystal-lang/crystal/pull/9153), thanks @straight-shoota) +- Add `Path#each_part` iterator. ([#9138](https://github.com/crystal-lang/crystal/pull/9138), thanks @straight-shoota) +- Add `Path#relative_to`. ([#9169](https://github.com/crystal-lang/crystal/pull/9169), thanks @straight-shoota) +- Add support for `Path` pattern to `Dir.glob`. ([#9420](https://github.com/crystal-lang/crystal/pull/9420), thanks @straight-shoota) +- Implement `File#fsync` on Windows. ([#9257](https://github.com/crystal-lang/crystal/pull/9257), thanks @kubo) +- Refactor `Path` regarding empty and `.`. ([#9137](https://github.com/crystal-lang/crystal/pull/9137), thanks @straight-shoota) + +### Networking + +- **(breaking-change)** Make `IO#skip`, `IO#write` returns the number of bytes it skipped/written as `Int64`. ([#9233](https://github.com/crystal-lang/crystal/pull/9233), [#9363](https://github.com/crystal-lang/crystal/pull/9363), thanks @bcardiff) +- **(breaking-change)** Improve error handling and logging in `HTTP::Server`. ([#9115](https://github.com/crystal-lang/crystal/pull/9115), [#9034](https://github.com/crystal-lang/crystal/pull/9034), thanks @waj, @straight-shoota) +- **(breaking-change)** Change `HTTP::Request#remote_address` type to `Socket::Address?`. ([#9210](https://github.com/crystal-lang/crystal/pull/9210), thanks @waj) +- Fix `flush` methods to always flush underlying `IO`. ([#9320](https://github.com/crystal-lang/crystal/pull/9320), thanks @straight-shoota) +- Fix `HTTP::Server` sporadic failure in SSL handshake. ([#9177](https://github.com/crystal-lang/crystal/pull/9177), thanks @waj) +- `WebSocket` shouldn't reply with same close code. ([#9313](https://github.com/crystal-lang/crystal/pull/9313), thanks @waj) +- Ignore response body during `WebSocket` handshake. ([#9418](https://github.com/crystal-lang/crystal/pull/9418), thanks @waj) +- Treat cookies which expire in this instant as expired. ([#9061](https://github.com/crystal-lang/crystal/pull/9061), thanks @RX14) +- Set `sync` or `flush_on_newline` for standard I/O on Windows. ([#9207](https://github.com/crystal-lang/crystal/pull/9207), thanks @kubo) +- Prefer HTTP basic authentication in OAuth2 client. ([#9127](https://github.com/crystal-lang/crystal/pull/9127), thanks @crush-157) +- Defer request upgrade in `HTTP::Server` (aka: WebSockets). ([#9243](https://github.com/crystal-lang/crystal/pull/9243), thanks @waj) +- Improve `URI::Punycode`, `HTTP::WebSocketHandler`, `HTTP::Status` documentation. ([#9068](https://github.com/crystal-lang/crystal/pull/9068), [#9130](https://github.com/crystal-lang/crystal/pull/9130), [#9180](https://github.com/crystal-lang/crystal/pull/9180), thanks @Blacksmoke16, @dscottboggs, @wontruefree) +- Remove `HTTP::Params::Builder#to_s`, use underlying `IO` directly. ([#9319](https://github.com/crystal-lang/crystal/pull/9319), thanks @straight-shoota) +- Fixed some regular failing specs in multi-thread mode. ([#9412](https://github.com/crystal-lang/crystal/pull/9412), thanks @bcardiff) + +### Crypto + +- **(security)** Update SSL server secure defaults. ([#9026](https://github.com/crystal-lang/crystal/pull/9026), thanks @straight-shoota) +- Add LibSSL `NO_TLS_V1_3` option. ([#9350](https://github.com/crystal-lang/crystal/pull/9350), thanks @lun-4) + +### Logging + +- **(breaking-change)** Rename `Log::Severity::Warning` to `Warn`. Drop `Verbose`. Add `Trace` and `Notice`. ([#9293](https://github.com/crystal-lang/crystal/pull/9293), [#9107](https://github.com/crystal-lang/crystal/pull/9107), [#9316](https://github.com/crystal-lang/crystal/pull/9316), thanks @bcardiff, @paulcsmith) +- **(breaking-change)** Allow local data on entries via `Log::Metadata` and redesign `Log::Context`. ([#9118](https://github.com/crystal-lang/crystal/pull/9118), [#9227](https://github.com/crystal-lang/crystal/pull/9227), [#9150](https://github.com/crystal-lang/crystal/pull/9150), [#9157](https://github.com/crystal-lang/crystal/pull/9157), thanks @bcardiff, @waj) +- **(breaking-change)** Split top-level `Log::Metadata` from `Log::Metadata::Value`, drop immutability via clone, improve performance. ([#9295](https://github.com/crystal-lang/crystal/pull/9295), thanks @bcardiff) +- **(breaking-change)** Rework `Log.setup_from_env` and defaults. ([#9145](https://github.com/crystal-lang/crystal/pull/9145), [#9240](https://github.com/crystal-lang/crystal/pull/9240), thanks @bcardiff) +- Add `Log.capture` spec helper. ([#9201](https://github.com/crystal-lang/crystal/pull/9201), thanks @bcardiff) +- Redesign `Log::Formatter`. ([#9211](https://github.com/crystal-lang/crystal/pull/9211), thanks @waj) +- Add `to_json` for `Log::Context`. ([#9101](https://github.com/crystal-lang/crystal/pull/9101), thanks @paulcsmith) +- Add `Log::IOBackend#new` with `formatter` named argument. ([#9105](https://github.com/crystal-lang/crystal/pull/9105), [#9434](https://github.com/crystal-lang/crystal/pull/9434), thanks @paulcsmith, @bcardiff) +- Allow `nil` as context raw values. ([#9121](https://github.com/crystal-lang/crystal/pull/9121), thanks @bcardiff) +- Add missing `Log#with_context`. ([#9058](https://github.com/crystal-lang/crystal/pull/9058), thanks @bcardiff) +- Fix types referred in documentation. ([#9117](https://github.com/crystal-lang/crystal/pull/9117), thanks @bcardiff) +- Allow override context within logging calls. ([#9146](https://github.com/crystal-lang/crystal/pull/9146), thanks @bcardiff) +- Check severity before backend. ([#9400](https://github.com/crystal-lang/crystal/pull/9400), thanks @asterite) + +### Concurrency + +- **(breaking-change)** Drop `Concurrent::Future` and top-level methods `delay`, `future`, `lazy`. Use [crystal-community/future.cr](https://github.com/crystal-community/future.cr). ([#9093](https://github.com/crystal-lang/crystal/pull/9093), thanks @bcardiff) + +### System + +- **(breaking-change)** Deprecate `Process#kill`, use `Process#signal`. ([#9006](https://github.com/crystal-lang/crystal/pull/9006), thanks @oprypin, @jan-zajic) +- `Process` raises `IO::Error` (or subclasses). ([#9340](https://github.com/crystal-lang/crystal/pull/9340), thanks @waj) +- Add `Process.quote` and fix shell usages in the compiler. ([#9043](https://github.com/crystal-lang/crystal/pull/9043), [#9369](https://github.com/crystal-lang/crystal/pull/9369), thanks @oprypin, @bcardiff) +- Implement `Process` support on Windows. ([#9047](https://github.com/crystal-lang/crystal/pull/9047), [#9021](https://github.com/crystal-lang/crystal/pull/9021), [#9122](https://github.com/crystal-lang/crystal/pull/9122), [#9112](https://github.com/crystal-lang/crystal/pull/9112), [#9149](https://github.com/crystal-lang/crystal/pull/9149), [#9310](https://github.com/crystal-lang/crystal/pull/9310), thanks @oprypin, @RX14, @kubo, @jan-zajic) +- Fix compile-time checking of `dup3`/`clock_gettime` methods definition. ([#9407](https://github.com/crystal-lang/crystal/pull/9407), thanks @asterite) +- Use `Int64` as portable `Process.pid` type. ([#9019](https://github.com/crystal-lang/crystal/pull/9019), thanks @oprypin) + +### Runtime + +- **(breaking-change)** Deprecate top-level `fork`. ([#9136](https://github.com/crystal-lang/crystal/pull/9136), thanks @bcardiff) +- **(breaking-change)** Move `Debug` to `Crystal` namespace. ([#9176](https://github.com/crystal-lang/crystal/pull/9176), thanks @bcardiff) +- Fix segfaults when static linking with musl. ([#9238](https://github.com/crystal-lang/crystal/pull/9238), thanks @waj) +- Allow calling `at_exit` inside `at_exit`. ([#9388](https://github.com/crystal-lang/crystal/pull/9388), thanks @asterite) +- Rework DWARF loading and fix empty backtraces in musl. ([#9267](https://github.com/crystal-lang/crystal/pull/9267), thanks @waj) +- Add DragonFly(BSD) support. ([#9178](https://github.com/crystal-lang/crystal/pull/9178), thanks @mneumann) +- Add `Crystal::System::Process` to split out system-specific implementations. ([#9035](https://github.com/crystal-lang/crystal/pull/9035), thanks @oprypin) +- Move internal `CallStack` to `Exception::CallStack`. ([#9076](https://github.com/crystal-lang/crystal/pull/9076), thanks @bcardiff) +- Specify pkg-config name for `libevent`. ([#9395](https://github.com/crystal-lang/crystal/pull/9395), thanks @jhass) + +### Spec + +- Reference global Spec in `be_a` macro. ([#9066](https://github.com/crystal-lang/crystal/pull/9066), thanks @asterite) +- Add `-h` short flag to spec runner. ([#9164](https://github.com/crystal-lang/crystal/pull/9164), thanks @straight-shoota) +- Fix `crystal spec` file paths on Windows. ([#9234](https://github.com/crystal-lang/crystal/pull/9234), thanks @oprypin) +- Refactor spec hooks. ([#9090](https://github.com/crystal-lang/crystal/pull/9090), thanks @straight-shoota) + +## Compiler + +- **(breaking-change)** Improve compiler single-file run syntax to make it shebang-friendly `#!`. ([#9171](https://github.com/crystal-lang/crystal/pull/9171), thanks @RX14) +- **(breaking-change)** Use `Process.quote` for `crystal env` output. ([#9428](https://github.com/crystal-lang/crystal/pull/9428), thanks @MakeNowJust) +- **(breaking-change)** Simplify `Link` annotation handling. ([#8972](https://github.com/crystal-lang/crystal/pull/8972), thanks @RX14) +- Fix parsing of `foo:"bar"` inside call or named tuple. ([#9033](https://github.com/crystal-lang/crystal/pull/9033), thanks @asterite) +- Fix parsing of anonymous splat and block arg. ([#9113](https://github.com/crystal-lang/crystal/pull/9113), thanks @MakeNowJust) +- Fix parsing of `unless` inside macro. ([#9024](https://github.com/crystal-lang/crystal/pull/9024), [#9167](https://github.com/crystal-lang/crystal/pull/9167), thanks @MakeNowJust) +- Fix parsing of `\ ` (backslash + space) inside regex literal to ` ` (space). ([#9079](https://github.com/crystal-lang/crystal/pull/9079), thanks @MakeNowJust) +- Fix parsing of ambiguous '+' and '-'. ([#9194](https://github.com/crystal-lang/crystal/pull/9194), thanks @max-codeware) +- Fix parsing of capitalized named argument. ([#9232](https://github.com/crystal-lang/crystal/pull/9232), thanks @asterite) +- Fix parsing of `{[] of Foo, self.foo}` expressions. ([#9329](https://github.com/crystal-lang/crystal/pull/9329), thanks @MakeNowJust) +- Fix cast fun function pointer to Proc. ([#9287](https://github.com/crystal-lang/crystal/pull/9287), thanks @asterite) +- Make compiler warn on deprecated macros. ([#9343](https://github.com/crystal-lang/crystal/pull/9343), thanks @bcardiff) +- Basic support for Win64 C lib ABI. ([#9387](https://github.com/crystal-lang/crystal/pull/9387), thanks @oprypin) +- Make the compiler able to run on Windows and compile itself. ([#9054](https://github.com/crystal-lang/crystal/pull/9054), [#9062](https://github.com/crystal-lang/crystal/pull/9062), [#9095](https://github.com/crystal-lang/crystal/pull/9095), [#9106](https://github.com/crystal-lang/crystal/pull/9106), [#9307](https://github.com/crystal-lang/crystal/pull/9307), thanks @oprypin, @Sija) +- Add docs regarding `CRYSTAL_OPTS`. ([#9018](https://github.com/crystal-lang/crystal/pull/9018), thanks @straight-shoota) +- Remove `Process.run("which")` from compiler. ([#9141](https://github.com/crystal-lang/crystal/pull/9141), thanks @straight-shoota) +- Refactor type parser. ([#9208](https://github.com/crystal-lang/crystal/pull/9208), thanks @MakeNowJust) +- Refactor & clean-up in compiler. ([#8781](https://github.com/crystal-lang/crystal/pull/8781), [#9195](https://github.com/crystal-lang/crystal/pull/9195), thanks @rhysd, @straight-shoota) +- Refactor `CrystalPath::Error`. ([#9359](https://github.com/crystal-lang/crystal/pull/9359), thanks @straight-shoota) +- Refactor and improvements on spec_helper. ([#9367](https://github.com/crystal-lang/crystal/pull/9367), [#9059](https://github.com/crystal-lang/crystal/pull/9059), [#9393](https://github.com/crystal-lang/crystal/pull/9393), [#9351](https://github.com/crystal-lang/crystal/pull/9351), [#9402](https://github.com/crystal-lang/crystal/pull/9402), thanks @straight-shoota, @jhass, @oprypin) +- Split general ABI specs from x86_64-specific ones, run on every platform. ([#9384](https://github.com/crystal-lang/crystal/pull/9384), thanks @oprypin) + +### Language semantics + +- Fix `RegexLiteral#to_s` output when first character of literal is whitespace. ([#9017](https://github.com/crystal-lang/crystal/pull/9017), thanks @MakeNowJust) +- Fix autocasting in multidispatch. ([#9004](https://github.com/crystal-lang/crystal/pull/9004), thanks @asterite) +- Fix propagation of annotations into other scopes. ([#9125](https://github.com/crystal-lang/crystal/pull/9125), thanks @asterite) +- Fix yield computation inside macro code. ([#9324](https://github.com/crystal-lang/crystal/pull/9324), thanks @asterite) +- Fix incorrect type generated with `as?` when type is a union. ([#9417](https://github.com/crystal-lang/crystal/pull/9417), [#9435](https://github.com/crystal-lang/crystal/pull/9435), thanks @asterite) +- Don't duplicate instance var in inherited generic type. ([#9433](https://github.com/crystal-lang/crystal/pull/9433), thanks @asterite) +- Ensure `type_vars` works for generic modules. ([#9161](https://github.com/crystal-lang/crystal/pull/9161), thanks @toddsundsted) +- Make autocasting work in default values against unions. ([#9366](https://github.com/crystal-lang/crystal/pull/9366), thanks @asterite) +- Skip no closure check for non-Crystal procs. ([#9248](https://github.com/crystal-lang/crystal/pull/9248), thanks @jhass) + +### Debugger + +- Improve debugging support. ([#8538](https://github.com/crystal-lang/crystal/pull/8538), thanks @skuznetsov) +- Move recent additions to `DIBuilder` to `LLVMExt`. ([#9114](https://github.com/crystal-lang/crystal/pull/9114), thanks @bcardiff) + +## Tools + +### Formatter + +- Fix formatting of regex after some comments. ([#9109](https://github.com/crystal-lang/crystal/pull/9109), thanks @MakeNowJust) +- Fix formatting of `&.!`. ([#9391](https://github.com/crystal-lang/crystal/pull/9391), thanks @MakeNowJust) +- Avoid crash on heredoc with interpolations. ([#9382](https://github.com/crystal-lang/crystal/pull/9382), thanks @MakeNowJust) +- Refactor: code clean-up. ([#9231](https://github.com/crystal-lang/crystal/pull/9231), thanks @MakeNowJust) + +### Doc generator + +- Fix links to methods with `String` default values. ([#9200](https://github.com/crystal-lang/crystal/pull/9200), thanks @bcardiff) +- Fix syntax highlighting of heredoc. ([#9396](https://github.com/crystal-lang/crystal/pull/9396), thanks @MakeNowJust) +- Correctly attach docs before annotations to following types. ([#9332](https://github.com/crystal-lang/crystal/pull/9332), thanks @asterite) +- Allow annotations and `:ditto:` in macro. ([#9341](https://github.com/crystal-lang/crystal/pull/9341), thanks @bcardiff) +- Add project name and version to API docs. ([#8792](https://github.com/crystal-lang/crystal/pull/8792), thanks @straight-shoota) +- Add version selector to API docs. ([#9074](https://github.com/crystal-lang/crystal/pull/9074), [#9187](https://github.com/crystal-lang/crystal/pull/9187), [#9250](https://github.com/crystal-lang/crystal/pull/9250), [#9252](https://github.com/crystal-lang/crystal/pull/9252), [#9254](https://github.com/crystal-lang/crystal/pull/9254), thanks @straight-shoota, @bcardiff) +- Show input type path instead of full qualified path on generic. ([#9302](https://github.com/crystal-lang/crystal/pull/9302), thanks @MakeNowJust) +- Remove README link in API docs. ([#9082](https://github.com/crystal-lang/crystal/pull/9082), thanks @straight-shoota) +- Remove special handling for version tags in docs generator. ([#9083](https://github.com/crystal-lang/crystal/pull/9083), thanks @straight-shoota) +- Refactor `is_crystal_repo` based on project name. ([#9070](https://github.com/crystal-lang/crystal/pull/9070), thanks @straight-shoota) +- Refactor `Docs::Generator` source link generation. ([#9119](https://github.com/crystal-lang/crystal/pull/9119), [#9305](https://github.com/crystal-lang/crystal/pull/9305), thanks @straight-shoota) + +### Playground + +- Allow building compiler without 'playground', to avoid dependency on sockets. ([#9031](https://github.com/crystal-lang/crystal/pull/9031), thanks @oprypin) +- Add support to jquery version 3. ([#9028](https://github.com/crystal-lang/crystal/pull/9028), thanks @deiv) + +## Others + +- CI improvements and housekeeping. ([#9012](https://github.com/crystal-lang/crystal/pull/9012), [#9129](https://github.com/crystal-lang/crystal/pull/9129), [#9242](https://github.com/crystal-lang/crystal/pull/9242), [#9370](https://github.com/crystal-lang/crystal/pull/9370), thanks @bcardiff) +- Update to Shards 0.11.1. ([#9446](https://github.com/crystal-lang/crystal/pull/9446), thanks @bcardiff) +- Tidy up Makefile and crystal env output. ([#9423](https://github.com/crystal-lang/crystal/pull/9423), thanks @bcardiff) +- Always include `lib` directory in the `CRYSTAL_PATH`. ([#9315](https://github.com/crystal-lang/crystal/pull/9315), thanks @waj) +- Use `SOURCE_DATE_EPOCH` only to determine compiler date. ([#9088](https://github.com/crystal-lang/crystal/pull/9088), thanks @straight-shoota) +- Win CI: Bootstrap Crystal, build things on Windows, publish the binary. ([#9123](https://github.com/crystal-lang/crystal/pull/9123), [#9155](https://github.com/crystal-lang/crystal/pull/9155), [#9144](https://github.com/crystal-lang/crystal/pull/9144), [#9346](https://github.com/crystal-lang/crystal/pull/9346), thanks @oprypin) +- Regenerate implementation tool sample. ([#9003](https://github.com/crystal-lang/crystal/pull/9003), thanks @nulty) +- Avoid requiring non std-lib spec spec_helper. ([#9294](https://github.com/crystal-lang/crystal/pull/9294), thanks @bcardiff) +- Improve grammar and fix typos. ([#9087](https://github.com/crystal-lang/crystal/pull/9087), [#9212](https://github.com/crystal-lang/crystal/pull/9212), [#9368](https://github.com/crystal-lang/crystal/pull/9368), thanks @MakeNowJust, @j8r) +- Hide internal functions in docs. ([#9410](https://github.com/crystal-lang/crystal/pull/9410), thanks @bcardiff) +- Advertise full `crystal spec` command for running a particular spec. ([#9103](https://github.com/crystal-lang/crystal/pull/9103), thanks @paulcsmith) +- Update README. ([#9225](https://github.com/crystal-lang/crystal/pull/9225), [#9163](https://github.com/crystal-lang/crystal/pull/9163), thanks @danimiba, @straight-shoota) +- Fix LICENSE and add NOTICE file. ([#3903](https://github.com/crystal-lang/crystal/pull/3903), thanks @MakeNowJust) + +# 0.34.0 (2020-04-06) + +## Language changes + +- **(breaking-change)** Exhaustive `case` expression check added, for now it produces warnings. ([#8424](https://github.com/crystal-lang/crystal/pull/8424), [#8962](https://github.com/crystal-lang/crystal/pull/8962), thanks @asterite, @Sija) +- Let `Proc(T)` be used as a `Proc(Nil)`. ([#8969](https://github.com/crystal-lang/crystal/pull/8969), [#8970](https://github.com/crystal-lang/crystal/pull/8970), thanks @asterite) + +## Standard library + +- **(breaking-change)** Replace `Errno`, `WinError`, `IO::Timeout` with `RuntimeError`, `IO::TimeoutError`, `IO::Error`, `File::Error`, `Socket::Error`, and subclasses. ([#8885](https://github.com/crystal-lang/crystal/pull/8885), thanks @waj) +- **(breaking-change)** Replace `Logger` module in favor of `Log` module. ([#8847](https://github.com/crystal-lang/crystal/pull/8847), [#8976](https://github.com/crystal-lang/crystal/pull/8976), thanks @bcardiff) +- **(breaking-change)** Move `Adler32` and `CRC32` to `Digest`. ([#8881](https://github.com/crystal-lang/crystal/pull/8881), thanks @bcardiff) +- **(breaking-change)** Remove `DL` module. ([#8882](https://github.com/crystal-lang/crystal/pull/8882), thanks @bcardiff) +- Enable more win32 specs. ([#8683](https://github.com/crystal-lang/crystal/pull/8683), [#8822](https://github.com/crystal-lang/crystal/pull/8822), thanks @straight-shoota) +- Make `SemanticVersion::Prerelease` comparable. ([#8991](https://github.com/crystal-lang/crystal/pull/8991), thanks @MakeNowJust) +- Use `flag?(:i386)` instead of obsolete `flag?(:i686)`. ([#8863](https://github.com/crystal-lang/crystal/pull/8863), thanks @bcardiff) +- Remove Windows workaround using `vsnprintf`. ([#8942](https://github.com/crystal-lang/crystal/pull/8942), thanks @oprypin) +- Fixed docs broken link to ruby's prettyprint source. ([#8915](https://github.com/crystal-lang/crystal/pull/8915), thanks @matthin) +- Update `OptionParser` example to exit on `--help`. ([#8927](https://github.com/crystal-lang/crystal/pull/8927), thanks @vlazar) + +### Numeric + +- Fixed `Float32#to_s` corner-case. ([#8838](https://github.com/crystal-lang/crystal/pull/8838), thanks @toddsundsted) +- Fixed make `Big*#to_u` raise on negative values. ([#8826](https://github.com/crystal-lang/crystal/pull/8826), thanks @Sija) +- Fixed `BigDecimal#to_big_i` regression. ([#8790](https://github.com/crystal-lang/crystal/pull/8790), thanks @Sija) +- Add `Complex#round`. ([#8819](https://github.com/crystal-lang/crystal/pull/8819), thanks @miketheman) +- Add `Int#bit_length` and `BigInt#bit_length`. ([#8924](https://github.com/crystal-lang/crystal/pull/8924), [#8931](https://github.com/crystal-lang/crystal/pull/,8931), thanks @asterite) +- Fixed docs of `Int#gcd`. ([#8894](https://github.com/crystal-lang/crystal/pull/8894), thanks @nard-tech) + +### Text + +- **(breaking-change)** Deprecate top-level `with_color` in favor of `Colorize.with`. ([#8892](https://github.com/crystal-lang/crystal/pull/8892), [#8958](https://github.com/crystal-lang/crystal/pull/8958), thanks @bcardiff, @oprypin) +- Add overloads for `String#ljust`, `String#rjust` and `String#center` that take an `IO`. ([#8923](https://github.com/crystal-lang/crystal/pull/8923), thanks @asterite) +- Add missing `Regex#hash` and `Regex::MatchData#hash`. ([#8986](https://github.com/crystal-lang/crystal/pull/8986), thanks @MakeNowJust) +- Upgrade Unicode to 13.0.0. ([#8906](https://github.com/crystal-lang/crystal/pull/8906), thanks @Blacksmoke16) +- Revert deprecation of `String#codepoint_at`. ([#8902](https://github.com/crystal-lang/crystal/pull/8902), thanks @vlazar) +- Move `Iconv` to `Crystal` namespace. ([#8890](https://github.com/crystal-lang/crystal/pull/8890), thanks @bcardiff) + +### Collections + +- Fixed make `Range#size` raise on an open range. ([#8829](https://github.com/crystal-lang/crystal/pull/8829), thanks @Sija) +- Add `Enumerable#empty?`. ([#8960](https://github.com/crystal-lang/crystal/pull/8960), thanks @Sija) +- **(performance)** Optimized Implementation of `Array#fill` for zero Values. ([#8903](https://github.com/crystal-lang/crystal/pull/8903), thanks @toddsundsted) +- Refactor `Reflect` to an `Enumerable` private definition. ([#8884](https://github.com/crystal-lang/crystal/pull/8884), thanks @bcardiff) + +### Serialization + +- **(breaking-change)** Rename `YAML::Builder.new` with block to `YAML::Builder.build`. ([#8896](https://github.com/crystal-lang/crystal/pull/8896), thanks @straight-shoota) +- Add `XML.build_fragment`. ([#8813](https://github.com/crystal-lang/crystal/pull/8813), thanks @straight-shoota) +- Add `CSV#rewind`. ([#8912](https://github.com/crystal-lang/crystal/pull/8912), thanks @asterite) +- Add `Deque#from_json` and `Deque#to_json`. ([#8850](https://github.com/crystal-lang/crystal/pull/8850), thanks @carlhoerberg) +- Call to `IO#flush` on `CSV`, `INI`, `JSON`, `XML`, and `YAML` builders. ([#8876](https://github.com/crystal-lang/crystal/pull/8876), thanks @asterite) +- Add docs to `Object.from_yaml`. ([#8800](https://github.com/crystal-lang/crystal/pull/8800), thanks @wowinter13) + +### Time + +- **(breaking-change)** Improve `Time::Span` initialization API with mandatory named arguments. ([#8257](https://github.com/crystal-lang/crystal/pull/8257), [#8857](https://github.com/crystal-lang/crystal/pull/8857), thanks @dnamsons, @bcardiff) +- Add `Time::Span#total_microseconds`. ([#8966](https://github.com/crystal-lang/crystal/pull/8966), thanks @vlazar) + +### Files + +- Fixed multi-thread race condition by setting `fd` to `-1` on closed `File`/`Socket`. ([#8873](https://github.com/crystal-lang/crystal/pull/8873), thanks @bcardiff) +- Fixed `File.dirname` with unicode chars. ([#8911](https://github.com/crystal-lang/crystal/pull/8911), thanks @asterite) +- Add `IO::Buffered#flush_on_newline` back and set it to true for non-tty. ([#8935](https://github.com/crystal-lang/crystal/pull/8935), thanks @asterite) +- Forward missing methods of `IO::Hexdump` to underlying `IO`. ([#8908](https://github.com/crystal-lang/crystal/pull/8908), thanks @carlhoerberg) + +### Networking + +- **(breaking-change)** Correctly support WebSocket close codes. ([#8975](https://github.com/crystal-lang/crystal/pull/8975), [#8981](https://github.com/crystal-lang/crystal/pull/8981), thanks @RX14, @Sija) +- Make `HTTP::Client` return empty `body_io` if content-length is zero. ([#8503](https://github.com/crystal-lang/crystal/pull/8503), thanks @asterite) +- Fixed `UDP` specs in the case of a local firewall. ([#8817](https://github.com/crystal-lang/crystal/pull/8817), thanks @RX14) +- Fixed `MIME` spec examples to not collide with actual registry. ([#8795](https://github.com/crystal-lang/crystal/pull/8795), thanks @straight-shoota) +- Fixed `UNIXServer`, and `HTTP::WebSocket` specs to ensure server is accepting before closing. ([#8755](https://github.com/crystal-lang/crystal/pull/8755), [#8879](https://github.com/crystal-lang/crystal/pull/8879), thanks @bcardiff) +- Add type annotation to `tls` argument in `HTTP`. ([#8678](https://github.com/crystal-lang/crystal/pull/8678), thanks @j8r) +- Add `Location` to `HTTP::Request` common header names. ([#8992](https://github.com/crystal-lang/crystal/pull/8992), thanks @mamantoha) + +### Concurrency + +- Add docs on `Future` regarding exceptions. ([#8860](https://github.com/crystal-lang/crystal/pull/8860), thanks @rdp) +- Disable occasionally failing `Thread` specs on musl. ([#8801](https://github.com/crystal-lang/crystal/pull/8801), thanks @straight-shoota) + +### System + +- Fixed typo on `src/signal.cr`. ([#8805](https://github.com/crystal-lang/crystal/pull/8805), thanks @lbguilherme) + +### Runtime + +- Fixed exceptions not being inspectable when running binary from PATH. ([#8807](https://github.com/crystal-lang/crystal/pull/8807), thanks @willhbr) +- Move `AtExitHandlers` to `Crystal` namespace. ([#8883](https://github.com/crystal-lang/crystal/pull/8883), thanks @bcardiff) + +## Compiler + +- **(breaking-change)** Drop `disable_overflow` compiler flag. ([#8772](https://github.com/crystal-lang/crystal/pull/8772), thanks @Sija) +- Fixed url in "can't infer block return type" error message. ([#8869](https://github.com/crystal-lang/crystal/pull/8869), thanks @nilium) +- Fixed typo in math interpreter error message. ([#8941](https://github.com/crystal-lang/crystal/pull/8941), thanks @j8r) +- Use `CRYSTAL_OPTS` environment variable as default compiler options. ([#8900](https://github.com/crystal-lang/crystal/pull/8900), thanks @bcardiff) +- Avoid using the default `--exclude-warnings` value if some is specified. ([#8899](https://github.com/crystal-lang/crystal/pull/8899), thanks @bcardiff) +- Honor `LIBRARY_PATH` as default library path, and allow linking with no explicit `/usr/lib:/usr/local/lib` paths. ([#8948](https://github.com/crystal-lang/crystal/pull/8948), thanks @bcardiff) +- Fix Windows LLVM globals codegen in non-single-module mode. ([#8978](https://github.com/crystal-lang/crystal/pull/8978), thanks @oprypin) +- Add support for LLVM 10. ([#8940](https://github.com/crystal-lang/crystal/pull/8940), thanks @RX14) +- Remove redundant calls to `Object.to_s` in interpolation in compiler's code. ([#8947](https://github.com/crystal-lang/crystal/pull/8947), thanks @veelenga) + +### Language semantics + +- Type as `NoReturn` if calling method on abstract class with no concrete subclasses. ([#8870](https://github.com/crystal-lang/crystal/pull/8870), thanks @asterite) + +## Tools + +- Add `crystal init` name validation. ([#8737](https://github.com/crystal-lang/crystal/pull/8737), thanks @straight-shoota) + +### Doc generator + +- Show warnings on docs command. ([#8880](https://github.com/crystal-lang/crystal/pull/8880), thanks @bcardiff) + +## Others + +- CI improvements and housekeeping. ([#8804](https://github.com/crystal-lang/crystal/pull/8804), [#8811](https://github.com/crystal-lang/crystal/pull/8811), [#8982](https://github.com/crystal-lang/crystal/pull/8982), thanks @bcardiff, @oprypin) +- Update to Shards 0.10.0. ([#8988](https://github.com/crystal-lang/crystal/pull/8988), thanks @bcardiff) +- Fix `pretty_json` sample. ([#8816](https://github.com/crystal-lang/crystal/pull/8816), thanks @asterite) +- Fix typos throughout the codebase. ([#8971](https://github.com/crystal-lang/crystal/pull/8971), thanks @Sija) + +# 0.33.0 (2020-02-14) + +## Language changes + +- Allow `timeout` in select statements. ([#8506](https://github.com/crystal-lang/crystal/pull/8506), [#8705](https://github.com/crystal-lang/crystal/pull/8705), thanks @bcardiff, @firejox, @vlazar) + +### Macros + +- Add `TypeNode#name(generic_args : BoolLiteral)` to return `TypeNode`'s name with or without type vars. ([#8483](https://github.com/crystal-lang/crystal/pull/8483), thanks @Blacksmoke16) + +## Standard library + +- **(breaking-change)** Remove several previously deprecated methods and modules: `PartialComparable`, `Crypto::Bcrypt::Password#==`, `HTTP::Server::Response#respond_with_error`, `JSON::PullParser::Kind#==`, `Symbol#==(JSON::PullParser::Kind)`, `JSON::Token#type`, `String#at`, `Time.new`, `Time.now`, `Time.utc_now`, `URI.escape`, `URI.unescape`. ([#8646](https://github.com/crystal-lang/crystal/pull/8646), [#8596](https://github.com/crystal-lang/crystal/pull/8596), thanks @bcardiff, @Blacksmoke16) +- Fixed docs wording. ([#8606](https://github.com/crystal-lang/crystal/pull/8606), [#8784](https://github.com/crystal-lang/crystal/pull/8784), thanks @fxn) +- Add `Object#in?`. ([#8720](https://github.com/crystal-lang/crystal/pull/8720), [#8723](https://github.com/crystal-lang/crystal/pull/8723), thanks @Sija) +- Allow to create an enum from a symbol. ([#8634](https://github.com/crystal-lang/crystal/pull/8634), thanks @bew) +- Add `VaList#next` for getting the next element in a variadic argument list. ([#8535](https://github.com/crystal-lang/crystal/pull/8535), [#8688](https://github.com/crystal-lang/crystal/pull/8688), thanks @ffwff, @RX14) +- Refactor `ARGF` implementation. ([#8593](https://github.com/crystal-lang/crystal/pull/8593), thanks @arcage) +- Fixed specs of `Colorize` on dumb terminal. ([#8673](https://github.com/crystal-lang/crystal/pull/8673), thanks @oprypin) +- Fixed some specs on Win32. ([#8670](https://github.com/crystal-lang/crystal/pull/8670), thanks @straight-shoota) + +### Numeric + +- Add `BigInt#unsafe_shr`. ([#8763](https://github.com/crystal-lang/crystal/pull/8763), thanks @asterite) +- Refactor `Float#fdiv` to use binary primitive. ([#8662](https://github.com/crystal-lang/crystal/pull/8662), thanks @bcardiff) + +### Text + +- Fixed `\u0000` wrongly added on `String#sub(Hash)` replaces last char. ([#8644](https://github.com/crystal-lang/crystal/pull/8644), thanks @mimame) + +### Collections + +- Fixed `Enumerable#zip` to work with union types. ([#8621](https://github.com/crystal-lang/crystal/pull/8621), thanks @asterite) +- Fixed docs regarding `Hash`'s `initial_capacity`. ([#8569](https://github.com/crystal-lang/crystal/pull/8569), thanks @r00ster91) + +### Serialization + +- Improved JSON deserialization into union types. ([#8689](https://github.com/crystal-lang/crystal/pull/8689), thanks @KimBurgess) +- Fixed expected error message in libxml2 error spec. ([#8699](https://github.com/crystal-lang/crystal/pull/8699), thanks @straight-shoota) +- Fixed `JSON::PullParser` overflow handling. ([#8698](https://github.com/crystal-lang/crystal/pull/8698), thanks @KimBurgess) +- Fixed `JSON::Any#dig?`/`YAML::Any#dig?` on non-structure values. ([#8745](https://github.com/crystal-lang/crystal/pull/8745), thanks @Sija) + +### Time + +- Fixed `Time#shift` over date boundaries with zone offset. ([#8742](https://github.com/crystal-lang/crystal/pull/8742), thanks @straight-shoota) + +### Files + +- **(breaking-change)** Deprecate `File::Info#owner`, and `File::Info#group`; use `owner_id`, and `group_id`. ([#8007](https://github.com/crystal-lang/crystal/pull/8007), thanks @j8r) +- Fixed `Path.new` receiving `Path` as first argument. ([#8753](https://github.com/crystal-lang/crystal/pull/8753), thanks @straight-shoota) +- Fixed `File.size` and `File.info` to work with `Path` parameters. ([#8625](https://github.com/crystal-lang/crystal/pull/8625), thanks @snluu) +- Fixed `Path` specs when `ENV["HOME"]` is unset. ([#8667](https://github.com/crystal-lang/crystal/pull/8667), thanks @straight-shoota) +- Refactor `Dir.mkdir_p` to use `Path#each_parent` and make it work on Win32. ([#8668](https://github.com/crystal-lang/crystal/pull/8668), thanks @straight-shoota) +- Fixed `IO::MultiWriter` specs to close file before reading/deleting it. ([#8674](https://github.com/crystal-lang/crystal/pull/8674), thanks @oprypin) + +### Networking + +- Fixed invalid call to libevent and race conditions on closed `IO` when resuming readable/writable event. ([#8707](https://github.com/crystal-lang/crystal/pull/8707), [#8733](https://github.com/crystal-lang/crystal/pull/8733), thanks @bcardiff) +- Fixed unexpected EOF in terminated SSL connection. ([#8540](https://github.com/crystal-lang/crystal/pull/8540), thanks @rdp) +- Fixed `HTTP::Cookie` to support `Int64` max-age values. ([#8759](https://github.com/crystal-lang/crystal/pull/8759), thanks @asterite) +- Improve error message for `getaddrinfo` failure. ([#8498](https://github.com/crystal-lang/crystal/pull/8498), thanks @rdp) +- Make `IO::SysCall#wait_readable` and `IO::SysCall#wait_writable` public, yet `:nodoc:`. ([#7366](https://github.com/crystal-lang/crystal/pull/7366), thanks @stakach) +- Refactor `StaticFileHandler` to use `Path`. ([#8672](https://github.com/crystal-lang/crystal/pull/8672), thanks @straight-shoota) +- Remove fixed date in spec. ([#8640](https://github.com/crystal-lang/crystal/pull/8640), thanks @bcardiff) +- Remove non-portable error message in `TCPServer` spec. ([#8702](https://github.com/crystal-lang/crystal/pull/8702), thanks @straight-shoota) + +### Crypto + +- Add `Crypto::Bcrypt::Password` check for invalid hash value. ([#6467](https://github.com/crystal-lang/crystal/pull/6467), thanks @miketheman) +- Improve documentation for `Random::Secure`. ([#8484](https://github.com/crystal-lang/crystal/pull/8484), thanks @straight-shoota) + +### Concurrency + +- Fixed `Future(Nil)` when the block raises. ([#8650](https://github.com/crystal-lang/crystal/pull/8650), thanks @lbguilherme) +- Fixed `IO` closing in multi-thread mode. ([#8733](https://github.com/crystal-lang/crystal/pull/8733), thanks @bcardiff) +- Fixed some regular failing specs in multi-thread mode. ([#8592](https://github.com/crystal-lang/crystal/pull/8592), [#8643](https://github.com/crystal-lang/crystal/pull/8643), [#8724](https://github.com/crystal-lang/crystal/pull/8724), [#8761](https://github.com/crystal-lang/crystal/pull/8761), thanks @bcardiff) +- Add docs to `Fiber`. ([#8739](https://github.com/crystal-lang/crystal/pull/8739), thanks @straight-shoota) + +### System + +- Enable `system` module for Win32 in prelude. ([#8661](https://github.com/crystal-lang/crystal/pull/8661), thanks @straight-shoota) +- Handle exceptions raised at `__crystal_sigfault_handler`. ([#8743](https://github.com/crystal-lang/crystal/pull/8743), thanks @waj) + +### Runtime + +- Fixed wrongly collected exception object by the GC. Ensure `LibUnwind::Exception` struct is not atomic. ([#8728](https://github.com/crystal-lang/crystal/pull/8728), thanks @waj) +- Fixed reporting of non-statement rows in DWARF backtrace. ([#8499](https://github.com/crystal-lang/crystal/pull/8499), thanks @rdp) +- Add top level exception handler. ([#8735](https://github.com/crystal-lang/crystal/pull/8735), [#8791](https://github.com/crystal-lang/crystal/pull/8791), thanks @waj) +- Try to open stdio in non-blocking mode. ([#8787](https://github.com/crystal-lang/crystal/pull/8787), thanks @waj) +- Allow `Crystal::System.print_error` to use `printf` like format. ([#8786](https://github.com/crystal-lang/crystal/pull/8786), thanks @bcardiff) + +### Spec + +- **(breaking-change)** Remove previously deprecated spec method `assert`. ([#8767](https://github.com/crystal-lang/crystal/pull/8767), thanks @Blacksmoke16) +- `Spec::JUnitFormatter` output and options enhancements. ([#8599](https://github.com/crystal-lang/crystal/pull/8599), [#8692](https://github.com/crystal-lang/crystal/pull/8692), thanks @Sija, @bcardiff) + +## Compiler + +- **(breaking-change)** Drop support for previously deprecated comma separators in enums and other cleanups. ([#8657](https://github.com/crystal-lang/crystal/pull/8657), thanks @bcardiff) +- **(breaking-change)** Drop uppercase F32 and F64 float number suffixes. ([#8782](https://github.com/crystal-lang/crystal/pull/8782), thanks @rhysd) +- Fixed memory corruption issues by using LLVM's `memset` and `memcpy` that matches target machine. ([#8746](https://github.com/crystal-lang/crystal/pull/8746), thanks @bcardiff) +- Fixed ICE when trying to add type inside annotation. ([#8628](https://github.com/crystal-lang/crystal/pull/8628), thanks @asterite) +- Fixed ICE on `typeof` in an unused block. ([#8695](https://github.com/crystal-lang/crystal/pull/8695), thanks @asterite) +- Fixed ICE in case of wrong target triple. ([#8710](https://github.com/crystal-lang/crystal/pull/8710), thanks @Sija) +- Fixed ICE when raising a macro exception with empty message. ([#8654](https://github.com/crystal-lang/crystal/pull/8654), thanks @jan-zajic) +- Fixed parser bug macro with "eenum" in it. ([#8760](https://github.com/crystal-lang/crystal/pull/8760), thanks @asterite) +- Change `CRYSTAL_PATH` to allow shards to override std-lib. ([#8752](https://github.com/crystal-lang/crystal/pull/8752), thanks @bcardiff) + +### Language semantics + +- Fixed missing virtualization of `Proc` pointer. ([#8757](https://github.com/crystal-lang/crystal/pull/8757), thanks @asterite) +- Fixed type of vars after `begin`/`rescue` if all `rescue` are unreachable. ([#8758](https://github.com/crystal-lang/crystal/pull/8758), thanks @asterite) +- Fixed visibility propagation to macro expansions in all cases. ([#8762](https://github.com/crystal-lang/crystal/pull/8762), [#8796](https://github.com/crystal-lang/crystal/pull/8796), thanks @asterite) + +## Tools + +- Update `crystal init` to handle `.`. ([#8681](https://github.com/crystal-lang/crystal/pull/8681), thanks @jethrodaniel) + +### Formatter + +- Fixed indent after comment inside indexer. ([#8627](https://github.com/crystal-lang/crystal/pull/8627), thanks @asterite) +- Fixed indent of comments at the end of a proc literal. ([#8778](https://github.com/crystal-lang/crystal/pull/8778), thanks @asterite) +- Fixed crash when formatting comment after macro. ([#8697](https://github.com/crystal-lang/crystal/pull/8697), thanks @asterite) +- Fixed crash when formatting `exp.!`. ([#8768](https://github.com/crystal-lang/crystal/pull/8768), thanks @asterite) +- Removes unnecessary escape sequences. ([#8619](https://github.com/crystal-lang/crystal/pull/8619), thanks @RX14) + +### Doc generator + +- **(breaking-change)** Deprecate `ditto` and `nodoc` in favor of `:ditto:` and `:nodoc:`. ([#6362](https://github.com/crystal-lang/crystal/pull/6362), thanks @j8r) +- Skip creation of `docs/` dir when not needed. ([#8718](https://github.com/crystal-lang/crystal/pull/8718), thanks @Sija) + +## Others + +- CI improvements and housekeeping. ([#8580](https://github.com/crystal-lang/crystal/pull/8580), [#8597](https://github.com/crystal-lang/crystal/pull/8597), [#8679](https://github.com/crystal-lang/crystal/pull/8679), [#8779](https://github.com/crystal-lang/crystal/pull/8779), thanks @bcardiff, @j8r) +- Add Windows CI using GitHub Actions. ([#8676](https://github.com/crystal-lang/crystal/pull/8676), thanks @oprypin) +- Add Alpine CI using CircleCI. ([#7420](https://github.com/crystal-lang/crystal/pull/7420), thanks @straight-shoota) +- Build Alpine Docker images. ([#8708](https://github.com/crystal-lang/crystal/pull/8708), thanks @straight-shoota) +- Allow `Makefile` to use `lld` if present (Linux only). ([#8641](https://github.com/crystal-lang/crystal/pull/8641), thanks @bcardiff) +- Simplify script to determine installed LLVM version. ([#8605](https://github.com/crystal-lang/crystal/pull/8605), thanks @j8r) +- Add CircleCI test summaries. ([#8617](https://github.com/crystal-lang/crystal/pull/8617), thanks @Sija) +- Add helper scripts to identify working std-lib specs on Win32. ([#8664](https://github.com/crystal-lang/crystal/pull/8664), thanks @straight-shoota) + +# 0.32.1 (2019-12-18) + +## Standard library + +### Collections + +- Fixed docs of `Enumerable#each_cons_pair` and `Iterator#cons_pair`. ([#8585](https://github.com/crystal-lang/crystal/pull/8585), thanks @arcage) + +### Networking + +- Fixed `HTTP::WebSocket`'s `on_close` callback is called for all errors. ([#8552](https://github.com/crystal-lang/crystal/pull/8552), thanks @stakach) +- Fixed sporadic failure in specs with OpenSSL 1.1+. ([#8582](https://github.com/crystal-lang/crystal/pull/8582), thanks @rdp) + +## Compiler + +### Language semantics + +- Combine contiguous string literals before string interpolation. ([#8581](https://github.com/crystal-lang/crystal/pull/8581), thanks @asterite) + +# 0.32.0 (2019-12-11) + +## Language changes + +- Allow boolean negation to be written also as a regular method call `expr.!`. ([#8445](https://github.com/crystal-lang/crystal/pull/8445), thanks @jan-zajic) + +### Macros + +- Add `TypeNode#class_vars` to list class variables of a type in a macro. ([#8405](https://github.com/crystal-lang/crystal/pull/8405), thanks @jan-zajic) +- Add `TypeNode#includers` to get an array of types a module is directly included in. ([#8133](https://github.com/crystal-lang/crystal/pull/8133), thanks @Blacksmoke16) +- Add `ArrayLiteral#map_with_index` and `TupleLiteral#map_with_index`. ([#8049](https://github.com/crystal-lang/crystal/pull/8049), thanks @Blacksmoke16) +- Add docs for `ArrayLiteral#reduce`. ([#8379](https://github.com/crystal-lang/crystal/pull/8379), thanks @jan-zajic) +- Add `lower:` named argument to `StringLiteral#camelcase`. ([#8429](https://github.com/crystal-lang/crystal/pull/8429), thanks @Blacksmoke16) + +## Standard library + +- **(breaking-change)** Remove `Readline` from std-lib. It's now available as a shard at [crystal-lang/crystal-readline](https://www.github.com/crystal-lang/crystal-readline) ([#8364](https://github.com/crystal-lang/crystal/pull/8364), thanks @ftarulla) +- Move `Number#clamp` to `Comparable#clamp`. ([#8522](https://github.com/crystal-lang/crystal/pull/8522), thanks @wontruefree) +- Allow `abort` without arguments. ([#8214](https://github.com/crystal-lang/crystal/pull/8214), thanks @dbackeus) +- Improve error message for not-nil assertion in getters. ([#8200](https://github.com/crystal-lang/crystal/pull/8200), [#8296](https://github.com/crystal-lang/crystal/pull/8296), thanks @icy-arctic-fox) +- Add `Enum.valid?`. ([#5716](https://github.com/crystal-lang/crystal/pull/5716), thanks @MakeNowJust) +- Disable colored output if `TERM=dumb`. ([#8271](https://github.com/crystal-lang/crystal/pull/8271), thanks @ilanpillemer) +- Documentation improvements. ([#7656](https://github.com/crystal-lang/crystal/pull/7656), [#8337](https://github.com/crystal-lang/crystal/pull/8337), [#8446](https://github.com/crystal-lang/crystal/pull/8446), thanks @r00ster91, @vlazar, @cserb) +- Add docs for pseudo methods. ([#8327](https://github.com/crystal-lang/crystal/pull/8327), [#8491](https://github.com/crystal-lang/crystal/pull/8491), thanks @straight-shoota) +- Code cleanups. ([#8270](https://github.com/crystal-lang/crystal/pull/8270), [#8368](https://github.com/crystal-lang/crystal/pull/8368), [#8404](https://github.com/crystal-lang/crystal/pull/8404), thanks @asterite, @vlazar, @arcage) + +### Numeric + +- Fixed `%` and `Int#remainder` edge case of min int value against `-1`. ([#8321](https://github.com/crystal-lang/crystal/pull/8321), thanks @asterite) +- Fixed `Int#gcd` types edge case and improve performance. ([#7996](https://github.com/crystal-lang/crystal/pull/7996), [#8419](https://github.com/crystal-lang/crystal/pull/8419), thanks @yxhuvud, @j8r) +- Add `Int#bits` for accessing bit ranges. ([#8165](https://github.com/crystal-lang/crystal/pull/8165), thanks @stakach) +- Allow `Number#round` with `UInt` argument. ([#8361](https://github.com/crystal-lang/crystal/pull/8361), thanks @igor-alexandrov) + +### Text + +- **(breaking-change)** Implement string interpolation as a call to `String.interpolation`. ([#8400](https://github.com/crystal-lang/crystal/pull/8400), thanks @asterite) +- **(breaking-change)** Deprecate `String#codepoint_at`, use `char_at(index).ord`. ([#8475](https://github.com/crystal-lang/crystal/pull/8475), thanks @vlazar) +- Fixed encoding specs for musl iconv. ([#8525](https://github.com/crystal-lang/crystal/pull/8525), thanks @straight-shoota) +- Add `String#presence`. ([#8345](https://github.com/crystal-lang/crystal/pull/8345), [#8508](https://github.com/crystal-lang/crystal/pull/8508), thanks @igor-alexandrov, @Sija) +- Add `String#center`. ([#8557](https://github.com/crystal-lang/crystal/pull/8557), thanks @hutou) +- **(performance)** Refactor `String#to_utf16` optimizing for ascii-only. ([#8526](https://github.com/crystal-lang/crystal/pull/8526), thanks @straight-shoota) +- Add docs in `Levenshtein` module. ([#8386](https://github.com/crystal-lang/crystal/pull/8386), thanks @katafrakt) +- Add docs to `Regex::Options`. ([#8448](https://github.com/crystal-lang/crystal/pull/8448), thanks @jan-zajic) + +### Collections + +- **(breaking-change)** Deprecate `Enumerable#grep`, use `Enumerable#select`. ([#8452](https://github.com/crystal-lang/crystal/pull/8452), thanks @j8r) +- Fixed `Enumerable#minmax`, `#min`, `#max` for partially comparable values. ([#8490](https://github.com/crystal-lang/crystal/pull/8490), thanks @TedTran2019) +- Fixed `Hash#rehash`. ([#8450](https://github.com/crystal-lang/crystal/pull/8450), thanks @asterite) +- Fixed `Array` range assignment index out of bounds. ([#8347](https://github.com/crystal-lang/crystal/pull/8347), thanks @asterite) +- Fixed endless ranged support for `String#[]?` and `Array#[]?`. ([#8567](https://github.com/crystal-lang/crystal/pull/8567), thanks @KarthikMAM) +- Add `Hash#compare_by_identity` and `Set#compare_by_identity`. ([#8451](https://github.com/crystal-lang/crystal/pull/8451), thanks @asterite) +- Add `Enumerable#each_cons_pair` and `Iterator#cons_pair` yielding a tuple. ([#8332](https://github.com/crystal-lang/crystal/pull/8332), thanks @straight-shoota) +- Add `offset` argument to all `map_with_index` methods. ([#8264](https://github.com/crystal-lang/crystal/pull/8264), thanks @asterite) +- **(performance)** Optimized version of `Tuple#to_a`. ([#8265](https://github.com/crystal-lang/crystal/pull/8265), thanks @asterite) +- Add docs to `Hash.merge!(other : Hash, &)`. ([#8380](https://github.com/crystal-lang/crystal/pull/8380), thanks @jan-zajic) +- Add docs to `Hash.select`. ([#8391](https://github.com/crystal-lang/crystal/pull/8391), thanks @jan-zajic) +- Add docs and specs to `Enumerable.reduce`. ([#8378](https://github.com/crystal-lang/crystal/pull/8378), thanks @jan-zajic) + +### Serialization + +- **(breaking-change)** Make `XML::Reader#expand` raise, introduce `XML::Reader#expand?` for former behavior. ([#8186](https://github.com/crystal-lang/crystal/pull/8186), thanks @Blacksmoke16) +- Allow `JSON.mapping` & `YAML.mapping` converter attribute to be applied to `Array` and `Hash`. ([#8156](https://github.com/crystal-lang/crystal/pull/8156), thanks @rodrigopinto) +- Add `use_json_discriminator` and `use_yaml_discriminator` to choose type based on property value. ([#8406](https://github.com/crystal-lang/crystal/pull/8406), thanks @asterite) +- Remove return type `self` restriction from `Object.from_json` and `Object.from_yaml`. ([#8489](https://github.com/crystal-lang/crystal/pull/8489), thanks @straight-shoota) + +### Files + +- **(breaking-change)** Remove expand home (`~`) by default in `File.expand_path` and `Path#expand`, now opt-in argument. ([#7903](https://github.com/crystal-lang/crystal/pull/7903), thanks @didactic-drunk) +- Fixed bugs in `Path` regarding `#dirname`, `#each_part`, `#each_parent`. ([#8415](https://github.com/crystal-lang/crystal/pull/8415), thanks @jan-zajic) +- Fixed `GZip::Reader` and `GZip::Writer` to handle large data sizes. ([#8421](https://github.com/crystal-lang/crystal/pull/8421), thanks @straight-shoota) +- Fixed `File::Info#same_file?` by providing access to 64 bit inode numbers. ([#8355](https://github.com/crystal-lang/crystal/pull/8355), thanks @didactic-drunk) + +### Networking + +- Fixed `HTTP::Response#mime_type` returns `nil` on empty `Content-Type` header. ([#8464](https://github.com/crystal-lang/crystal/pull/8464), thanks @Sija) +- Fixed handling of unidirectional SSL servers hang. ([#8481](https://github.com/crystal-lang/crystal/pull/8481), thanks @rdp) +- Add `HTTP::Client#write_timeout`. ([#8507](https://github.com/crystal-lang/crystal/pull/8507), thanks @Sija) +- Updated mime type of `.js` files to `text/javascript` and include `image/webp`. ([#8342](https://github.com/crystal-lang/crystal/pull/8342), thanks @mamantoha) +- Refactor websocket protocol GUID string. ([#8339](https://github.com/crystal-lang/crystal/pull/8339), thanks @vlazar) + +### Crypto + +- **(breaking-change)** Enforce single-line results of `OpenSSL::DigestBase#base64digest` via `Base64.strict_encode`. ([#8215](https://github.com/crystal-lang/crystal/pull/8215), thanks @j8r) + +### Concurrency + +- Fixed `Channel` successful sent and raise behavior. ([#8284](https://github.com/crystal-lang/crystal/pull/8284), thanks @firejox) +- Fixed `Channel#close` to be thread-safe. ([#8249](https://github.com/crystal-lang/crystal/pull/8249), thanks @firejox) +- Fixed `select` with `receive?` and closed channels. ([#8304](https://github.com/crystal-lang/crystal/pull/8304), thanks @bcardiff) +- Faster `Mutex` implementation and policy checks. ([#8295](https://github.com/crystal-lang/crystal/pull/8295), [#8563](https://github.com/crystal-lang/crystal/pull/8563), thanks @waj, @ysbaddaden) +- **(performance)** Channel internals refactor and optimize. ([#8322](https://github.com/crystal-lang/crystal/pull/8322), [#8497](https://github.com/crystal-lang/crystal/pull/8497), thanks @firejox, @Sija) +- Add docs to `Channel#send` and `Channel#close`. ([#8356](https://github.com/crystal-lang/crystal/pull/8356), thanks @lbarasti) +- Fixed `Thread#gc_thread_handler` for Windows support. ([#8519](https://github.com/crystal-lang/crystal/pull/8519), thanks @straight-shoota) + +### System + +- Don't close pipes used for signal handlers in multi-thread mode. ([#8465](https://github.com/crystal-lang/crystal/pull/8465), thanks @waj) +- Fixed thread initialization on OpenBSD. ([#8293](https://github.com/crystal-lang/crystal/pull/8293), thanks @wmoxam) +- Implement fibers for win32. ([#7995](https://github.com/crystal-lang/crystal/pull/7995), [#8513](https://github.com/crystal-lang/crystal/pull/8513), thanks @straight-shoota, @firejox) + +### Runtime + +- Fixed fiber initialization on `-Dgc_none -Dpreview_mt`. ([#8280](https://github.com/crystal-lang/crystal/pull/8280), thanks @bcardiff) +- Add GC profiling stats and warning bindings. ([#8281](https://github.com/crystal-lang/crystal/pull/8281), thanks @bcardiff, @benoist) +- Refactor `callstack_spec`. ([#8308](https://github.com/crystal-lang/crystal/pull/8308), [#8395](https://github.com/crystal-lang/crystal/pull/8395), thanks @straight-shoota, @Sija) + +### Spec + +- Fixed `--fail-fast` behaviour. ([#8453](https://github.com/crystal-lang/crystal/pull/8453), thanks @asterite) +- Add before, after, and around hooks. ([#8302](https://github.com/crystal-lang/crystal/pull/8302), thanks @asterite) +- Restrict the type returned by `should_not be_nil` and others. ([#8412](https://github.com/crystal-lang/crystal/pull/8412), thanks @asterite) +- Add ability to randomize specs via `--order random|`. ([#8310](https://github.com/crystal-lang/crystal/pull/8310), thanks @Fryguy) +- Add specs for `Spec` filters. ([#8242](https://github.com/crystal-lang/crystal/pull/8242), thanks @Fryguy) +- Add ability to tag specs. ([#8068](https://github.com/crystal-lang/crystal/pull/8068), thanks @Fryguy) + +## Compiler + +- Fixed musl libc detection (Alpine 3.10 regression bug). ([#8330](https://github.com/crystal-lang/crystal/pull/8330), thanks @straight-shoota) +- Fixed pragmas handling in macros. ([#8256](https://github.com/crystal-lang/crystal/pull/8256), thanks @asterite) +- Fixed parser crash for 'alias Foo?'. ([#8282](https://github.com/crystal-lang/crystal/pull/8282), thanks @oprypin) +- Fixed parser error on newline before closing parenthesis. ([#8320](https://github.com/crystal-lang/crystal/pull/8320), thanks @MakeNowJust) +- Fixed generic subtypes edge cases triggering `no target defs` error. ([#8417](https://github.com/crystal-lang/crystal/pull/8417), thanks @asterite) +- Fixed cleanup of local vars reachable by macros. ([#8529](https://github.com/crystal-lang/crystal/pull/8529), thanks @asterite) +- Add support for LLVM 9. ([#8358](https://github.com/crystal-lang/crystal/pull/8358), thanks @RX14) +- Add `--mcmodel` option to compiler. ([#8363](https://github.com/crystal-lang/crystal/pull/8363), thanks @ffwff) +- Disallow `instance_sizeof` on union. ([#8399](https://github.com/crystal-lang/crystal/pull/8399), thanks @asterite) +- Add mention to `crystal --help` in help. ([#3628](https://github.com/crystal-lang/crystal/pull/3628), thanks @rdp) +- Improve error message when a filename is misspelled. ([#8500](https://github.com/crystal-lang/crystal/pull/8500), thanks @rdp) +- Show full path of locally compiled Crystal. ([#8486](https://github.com/crystal-lang/crystal/pull/8486), thanks @rdp) +- Code cleanups. ([#8460](https://github.com/crystal-lang/crystal/pull/8460), thanks @veelenga) + +### Language semantics + +- Fixed method lookup priority when type alias of union is used. ([#8258](https://github.com/crystal-lang/crystal/pull/8258), thanks @asterite) +- Fixed visibility modifiers in virtual types. ([#8562](https://github.com/crystal-lang/crystal/pull/8562), thanks @asterite) +- Fixed `sizeof(Bool)`. ([#8273](https://github.com/crystal-lang/crystal/pull/8273), thanks @asterite) + +## Tools + +### Formatter + +- Fixed indent in arguments. ([#8315](https://github.com/crystal-lang/crystal/pull/8315), thanks @MakeNowJust) +- Fixed crash related to parenthesis on generic types. ([#8501](https://github.com/crystal-lang/crystal/pull/8501), thanks @asterite) + +### Doc generator + +- Fixed underscore type restriction in doc generator. ([#8331](https://github.com/crystal-lang/crystal/pull/8331), thanks @straight-shoota) +- Correctly attach docs through multiple macro invocations. ([#8502](https://github.com/crystal-lang/crystal/pull/8502), thanks @asterite) +- Allow constants to use `:ditto:`. ([#8389](https://github.com/crystal-lang/crystal/pull/8389), thanks @Blacksmoke16) +- Add sitemap generator. ([#8348](https://github.com/crystal-lang/crystal/pull/8348), thanks @straight-shoota) +- Add documentation for pseudo-methods: `!`, `as`, `nil?`, etc.. ([#8327](https://github.com/crystal-lang/crystal/pull/8327), [#8371](https://github.com/crystal-lang/crystal/pull/8371), thanks @straight-shoota) +- Add clickable anchor icon next to headings. ([#8344](https://github.com/crystal-lang/crystal/pull/8344), thanks @Blacksmoke16) +- Use `&` instead of `&block` for signature of yielding method. ([#8394](https://github.com/crystal-lang/crystal/pull/8394), thanks @j8r) + +### Playground + +- Do not collapse whitespaces in playground sidebar. ([#8528](https://github.com/crystal-lang/crystal/pull/8528), thanks @hugopl) + +## Others + +- CI improvements and housekeeping. ([#8210](https://github.com/crystal-lang/crystal/pull/8210), [#8251](https://github.com/crystal-lang/crystal/pull/8251), [#8283](https://github.com/crystal-lang/crystal/pull/8283), [#8439](https://github.com/crystal-lang/crystal/pull/8439), [#8510](https://github.com/crystal-lang/crystal/pull/8510), thanks @bcardiff) +- Update base docker images to `bionic` and LLVM 8.0. ([#8442](https://github.com/crystal-lang/crystal/pull/8442), thanks @bcardiff) +- Repository clean-up. ([#8312](https://github.com/crystal-lang/crystal/pull/8312), [#8397](https://github.com/crystal-lang/crystal/pull/8397), thanks @bcardiff, @straight-shoota) + +# 0.31.1 (2019-09-30) + +## Standard library + +### Numeric + +- Fixed overflow in `Random::Secure`. ([#8224](https://github.com/crystal-lang/crystal/pull/8224), thanks @oprypin) + +### Networking + +- Workaround `IO::Evented#evented_write` invalid `IndexError` error. ([#8239](https://github.com/crystal-lang/crystal/pull/8239), thanks @bcardiff) + +### Concurrency + +- Use bdw-gc upstream patch for green threads support. ([#8225](https://github.com/crystal-lang/crystal/pull/8225), thanks @bcardiff) +- Refactor `Channel` to use records instead of tuples. ([#8227](https://github.com/crystal-lang/crystal/pull/8227), thanks @asterite) + +### Spec + +- Add `before_suite` and `after_suite` hooks. ([#8238](https://github.com/crystal-lang/crystal/pull/8238), thanks @asterite) + +## Compiler + +- Fix debug location information when emitting main code from module. ([#8234](https://github.com/crystal-lang/crystal/pull/8234), thanks @asterite) + +### Language semantics + +- Use virtual type for `uninitialized`. ([#8221](https://github.com/crystal-lang/crystal/pull/8221), thanks @asterite) + +# 0.31.0 (2019-09-23) + +## Language changes + +- Allow non-captured block args with type restriction using `& : T -> U`. ([#8117](https://github.com/crystal-lang/crystal/pull/8117), thanks @asterite) + +### Macros + +- Ensure `@type` is devirtualized inside macros. ([#8149](https://github.com/crystal-lang/crystal/pull/8149), thanks @asterite) + +## Standard library + +- **(breaking-change)** Remove `Markdown` from the std-lib. ([#8115](https://github.com/crystal-lang/crystal/pull/8115), thanks @asterite) +- **(breaking-change)** Deprecate `OptionParser#parse!`, use `OptionParser#parse`. ([#8041](https://github.com/crystal-lang/crystal/pull/8041), thanks @didactic-drunk) +- Fix example codes in multiple places. ([#8194](https://github.com/crystal-lang/crystal/pull/8194), thanks @maiha) + +### Numeric + +- **(breaking-change)** Enable overflow by default. ([#8170](https://github.com/crystal-lang/crystal/pull/8170), thanks @bcardiff) +- **(breaking-change)** Make `/` the arithmetic division for all types. ([#8120](https://github.com/crystal-lang/crystal/pull/8120), thanks @bcardiff) +- Add `BigDecimal#**` and `BigRational#**` (pow operator). ([#7860](https://github.com/crystal-lang/crystal/pull/7860), thanks @jwbuiter) +- Avoid overflow exception in `Number#round(digits, base)`. ([#8204](https://github.com/crystal-lang/crystal/pull/8204), thanks @bcardiff) +- Refactor `Int#divisible_by?` for clarity. ([#8045](https://github.com/crystal-lang/crystal/pull/8045), thanks @yxhuvud) + +### Text + +- **(performance)** Minor `String#lchop?` ASCII-only optimization. ([#8052](https://github.com/crystal-lang/crystal/pull/8052), thanks @r00ster91) + +### Collections + +- **(performance)** Array optimizations for small number of elements. ([#8048](https://github.com/crystal-lang/crystal/pull/8048), thanks @asterite) +- **(performance)** Optimize `Array#*`. ([#8087](https://github.com/crystal-lang/crystal/pull/8087), thanks @asterite) +- **(performance)** Hash now uses an open addressing algorithm. ([#8017](https://github.com/crystal-lang/crystal/pull/8017), [#8182](https://github.com/crystal-lang/crystal/pull/8182), thanks @asterite) +- **(performance)** Optimize `Hash#to_a`, `Hash#keys` and `Hash#values`. ([#8042](https://github.com/crystal-lang/crystal/pull/8042), thanks @asterite) +- **(performance)** Add `Hash#put` and optimize `Set#add?`. ([#8116](https://github.com/crystal-lang/crystal/pull/8116), thanks @asterite) +- Fixed `Slice#==` for some generic instantiations, add `Slice#<=>`. ([#8074](https://github.com/crystal-lang/crystal/pull/8074), thanks @asterite) +- Add docs on idempotence and methods involving eager evaluation in `Iterator`. ([#8053](https://github.com/crystal-lang/crystal/pull/8053), thanks @KimBurgess) +- Add `Set#+`. ([#8121](https://github.com/crystal-lang/crystal/pull/8121), thanks @sam0x17) +- Refactor `Hash` to use integer division instead of float division. ([#8104](https://github.com/crystal-lang/crystal/pull/8104), thanks @asterite) + +### Serialization + +- **(breaking-change)** Rename `XML::Type` to `XML::Node::Type`, introduce `XML::Reader::Type`. ([#8134](https://github.com/crystal-lang/crystal/pull/8134), thanks @asterite) +- Fixed JSON and YAML parsing of `NamedTuple` with nilable fields. ([#8109](https://github.com/crystal-lang/crystal/pull/8109), thanks @asterite) +- Fixed YAML to emit unicode characters as such. ([#8132](https://github.com/crystal-lang/crystal/pull/8132), thanks @asterite) +- Fixed INI generation of empty sections. ([#8106](https://github.com/crystal-lang/crystal/pull/8106), thanks @j8r) + +### Files + +- **(performance)** Optimize `Path#join` by precomputing capacity if possible. ([#8078](https://github.com/crystal-lang/crystal/pull/8078), thanks @asterite) +- **(performance)** Optimize `Path#join` for the case of joining one single part. ([#8082](https://github.com/crystal-lang/crystal/pull/8082), thanks @asterite) +- **(performance)** Optimize `Dir.glob`. ([#8081](https://github.com/crystal-lang/crystal/pull/8081), thanks @asterite) +- Fixed `File.basename` off-by-one corner-case. ([#8119](https://github.com/crystal-lang/crystal/pull/8119), thanks @ysbaddaden) +- Fixed unneeded evaluation of `Path.home` on `Path.expand`. ([#8128](https://github.com/crystal-lang/crystal/pull/8128), thanks @asterite) +- Fixed `Zip::Writer` STORED compression. ([#8142](https://github.com/crystal-lang/crystal/pull/8142), thanks @asterite) +- Fixed missing check on `ARGF` if read_count is zero. ([#8177](https://github.com/crystal-lang/crystal/pull/8177), thanks @Blacksmoke16) + +### Networking + +- **(breaking-change)** Replace `HTTP::Server::Response#respond_with_error` with `#respond_with_status`. ([#6988](https://github.com/crystal-lang/crystal/pull/6988), thanks @straight-shoota) +- **(breaking-change)** Handle too long URIs and too large header fields in `HTTP::Request.from_io` and remove `HTTP::Request::BadRequest`. ([#8013](https://github.com/crystal-lang/crystal/pull/8013), thanks @straight-shoota) +- Fixed memory leak from `SSL_new` if `ssl_accept` fails. ([#8088](https://github.com/crystal-lang/crystal/pull/8088), thanks @rdp) +- Fixed WebSocket ipv6 hostname connection. ([#8066](https://github.com/crystal-lang/crystal/pull/8066), thanks @MrSorcus) +- Add `URI#query_params` method. ([#8090](https://github.com/crystal-lang/crystal/pull/8090), thanks @rodrigopinto) +- Add `URI#resolve` and `URI#relativize`. ([#7716](https://github.com/crystal-lang/crystal/pull/7716), thanks @straight-shoota) +- Add `#clear`, `#delete`, and `#size` methods to `HTTP::Cookies`. ([#8107](https://github.com/crystal-lang/crystal/pull/8107), thanks @sam0x17) +- Refactor `http/server_spec`. ([#8056](https://github.com/crystal-lang/crystal/pull/8056), thanks @straight-shoota) +- Refactor UDP specs to use random port. ([#8139](https://github.com/crystal-lang/crystal/pull/8139), thanks @waj) + +### Concurrency + +- Multithreading. ([#8112](https://github.com/crystal-lang/crystal/pull/8112), thanks @waj) +- Delay releasing of fiber stack in multi-thread mode. ([#8138](https://github.com/crystal-lang/crystal/pull/8138), thanks @waj) +- Make `Crystal::Scheduler.init_workers` block until workers are ready. ([#8145](https://github.com/crystal-lang/crystal/pull/8145), thanks @bcardiff) +- Make `Crystal::ThreadLocalValue` thread-safe. ([#8168](https://github.com/crystal-lang/crystal/pull/8168), thanks @waj) +- Let `exec_recursive` use a thread-local data structure. ([#8146](https://github.com/crystal-lang/crystal/pull/8146), thanks @asterite) +- Add explicit return types for some channel methods. ([#8161](https://github.com/crystal-lang/crystal/pull/8161), thanks @Blacksmoke16) +- Remove the dedicated fiber to run the event loop. ([#8173](https://github.com/crystal-lang/crystal/pull/8173), thanks @waj) +- Fix corruption of thread linked list. ([#8196](https://github.com/crystal-lang/crystal/pull/8196), thanks @waj) +- Workaround compile on win32 until fibers is implemented. ([#8195](https://github.com/crystal-lang/crystal/pull/8195), thanks @straight-shoota) + +### System + +- Increase precision of `Process.times`. ([#8097](https://github.com/crystal-lang/crystal/pull/8097), thanks @jgaskins) + +### Spec + +- **(breaking-change)** Add support for `focus`. ([#8125](https://github.com/crystal-lang/crystal/pull/8125), [#8178](https://github.com/crystal-lang/crystal/pull/8178), [#8208](https://github.com/crystal-lang/crystal/pull/8208), thanks @asterite, @straight-shoota, @bcardiff) + +## Compiler + +- Fixed ICE on declarations inside fun. ([#8076](https://github.com/crystal-lang/crystal/pull/8076), thanks @asterite) +- Fixed missing `name_location` of some calls. ([#8192](https://github.com/crystal-lang/crystal/pull/8192), thanks @asterite) +- Activate compiler warnings by default. ([#8171](https://github.com/crystal-lang/crystal/pull/8171), thanks @bcardiff) +- Improve return type mismatch error. ([#8203](https://github.com/crystal-lang/crystal/pull/8203), thanks @asterite) +- Improve `for` expression error. ([#7641](https://github.com/crystal-lang/crystal/pull/7641), thanks @r00ster91) + +### Language semantics + +- Fixed abstract def check regarding generic ancestor lookup. ([#8098](https://github.com/crystal-lang/crystal/pull/8098), thanks @asterite) +- Fixed missing virtualization of type arguments in `Proc` types. ([#8159](https://github.com/crystal-lang/crystal/pull/8159), thanks @asterite) +- Fixed incorrect typing after exception handler. ([#8037](https://github.com/crystal-lang/crystal/pull/8037), thanks @asterite) +- Fixed behaviour when a yield node can't be typed. ([#8101](https://github.com/crystal-lang/crystal/pull/8101), thanks @asterite) +- Fixed `offsetof` on reference types. ([#8137](https://github.com/crystal-lang/crystal/pull/8137), thanks @mcr431) +- Allow rescue var to be closured. ([#8143](https://github.com/crystal-lang/crystal/pull/8143), thanks @asterite) +- Refactor class var and constant initialization. ([#8067](https://github.com/crystal-lang/crystal/pull/8067), [#8091](https://github.com/crystal-lang/crystal/pull/8091), thanks @waj) +- Add runtime check for recursive initialization of class variables and constants. ([#8172](https://github.com/crystal-lang/crystal/pull/8172), thanks @waj) + +## Tools + +### Doc generator + +- Fixed link to constructors of another class. ([#8110](https://github.com/crystal-lang/crystal/pull/8110), thanks @asterite) +- Enable docs from previous def and/or ancestors to be inherited. ([#6989](https://github.com/crystal-lang/crystal/pull/6989), thanks @asterite) + +## Others + +- Update CI to use 0.30.1. ([#8032](https://github.com/crystal-lang/crystal/pull/8032), thanks @bcardiff) +- Use LLVM 8.0 for Linux official packages. ([#8155](https://github.com/crystal-lang/crystal/pull/8155), thanks @bcardiff, @RX14) +- Update dependencies of the build process. ([#8205](https://github.com/crystal-lang/crystal/pull/8205), thanks @bcardiff) +- Code cleanups. ([#8033](https://github.com/crystal-lang/crystal/pull/8033), thanks @straight-shoota) + +# 0.30.1 (2019-08-12) + +## Standard library + +### Numeric + +- Fixed `Number#humanize` digits. ([#8027](https://github.com/crystal-lang/crystal/pull/8027), thanks @straight-shoota) + +### Networking + +- Fixed TCP socket leaking after failed SSL connect in `HTTP::Client#socket`. ([#8025](https://github.com/crystal-lang/crystal/pull/8025), thanks @straight-shoota) +- Honor normalized header names for HTTP requests. ([#8061](https://github.com/crystal-lang/crystal/pull/8061), thanks @asterite) + +### Concurrency + +- Don't resume fibers directly from event loop callbacks (or support for libevent 2.1.11). ([#8058](https://github.com/crystal-lang/crystal/pull/8058), thanks @waj) + +## Compiler + +- Fixed `sizeof(Nil)` and other empty types. ([#8040](https://github.com/crystal-lang/crystal/pull/8040), thanks @asterite) +- Avoid internal globals of type i128 or u128. (or workaround [a llvm 128 bits bug](https://bugs.llvm.org/show_bug.cgi?id=42932)). ([#8063](https://github.com/crystal-lang/crystal/pull/8063), thanks @bcardiff, @asterite) + +### Language semantics + +- Consider abstract method implementation in supertype for abstract method checks. ([#8035](https://github.com/crystal-lang/crystal/pull/8035), thanks @asterite) + +## Tools + +### Formatter + +- Handle consecutive macro literals when subformatting. ([#8034](https://github.com/crystal-lang/crystal/pull/8034), thanks @asterite) +- Fixed crash when formatting syntax error inside macro. ([#8055](https://github.com/crystal-lang/crystal/pull/8055), thanks @asterite) + +## Others + +- Use LLVM 6.0.1 for darwin official packages. ([#7994](https://github.com/crystal-lang/crystal/pull/7994), thanks @bcardiff) +- Split std_specs in 32 bits CI. ([#8065](https://github.com/crystal-lang/crystal/pull/8065), thanks @bcardiff) + +# 0.30.0 (2019-08-01) + +## Language changes + +- **(breaking-change)** Enforce abstract methods return types. ([#7956](https://github.com/crystal-lang/crystal/pull/7956), [#7999](https://github.com/crystal-lang/crystal/pull/7999), [#8010](https://github.com/crystal-lang/crystal/pull/8010), thanks @asterite) +- **(breaking-change)** Don't allow ranges to span across lines. ([#7888](https://github.com/crystal-lang/crystal/pull/7888), thanks @oprypin) + +### Macros + +- Add `args`/`named_args` macro methods to `Annotations`. ([#7694](https://github.com/crystal-lang/crystal/pull/7694), thanks @Blacksmoke16) +- Unify `resolve` and `types` macro methods API for `Type` and `Path` for convenience. ([#7970](https://github.com/crystal-lang/crystal/pull/7970), thanks @asterite) + +## Standard library + +- **(breaking-change)** Remove `UUID#to_slice` in favor of `UUID#bytes` to fix dangling pointer issues. ([#7901](https://github.com/crystal-lang/crystal/pull/7901), thanks @ysbaddaden) +- **(performance)** Improve `Box` of reference types. ([#8016](https://github.com/crystal-lang/crystal/pull/8016), thanks @waj) +- Fixed initial seed of `Random::ISAAC`. ([#7977](https://github.com/crystal-lang/crystal/pull/7977), thanks @asterite) +- Fixed mem intrinsics for aarch64. ([#7983](https://github.com/crystal-lang/crystal/pull/7983), thanks @drujensen) +- Add `Benchmark.memory`. ([#7835](https://github.com/crystal-lang/crystal/pull/7835), thanks @r00ster91) +- Allow setting default capacity for `StringPool`. ([#7899](https://github.com/crystal-lang/crystal/pull/7899), thanks @carlhoerberg) +- Add type restrictions to `INI`. ([#7831](https://github.com/crystal-lang/crystal/pull/7831), thanks @j8r) +- Fixed `Logger` docs. ([#7898](https://github.com/crystal-lang/crystal/pull/7898), thanks @dprobinson) +- Fix example codes in multiple places. ([#8003](https://github.com/crystal-lang/crystal/pull/8003), thanks @maiha) + +### Numeric + +- Fixed incorrect `Int#%` overflow. ([#7980](https://github.com/crystal-lang/crystal/pull/7980), thanks @asterite) +- Fixed inconsistency between `Float#to_s` and `BigFloat#to_s`, always show `.0` for whole numbers. ([#7982](https://github.com/crystal-lang/crystal/pull/7982), thanks @Lasvad) + +### Text + +- Fixed unicode alternate ranges generation. ([#7924](https://github.com/crystal-lang/crystal/pull/7924), thanks @asterite) + +### Collections + +- Add `Enumerable#tally`. ([#7921](https://github.com/crystal-lang/crystal/pull/7921), thanks @kachick) +- Add `Enumerable#reduce?` overload with not initial value. ([#7941](https://github.com/crystal-lang/crystal/pull/7941), thanks @miketheman) +- Fix specs of `Enumerable#min_by?`. ([#7919](https://github.com/crystal-lang/crystal/pull/7919), thanks @kachick) + +### Serialization + +- **(breaking-change)** JSON: use enums instead of symbols. ([#7966](https://github.com/crystal-lang/crystal/pull/7966), thanks @asterite) +- Fixed YAML deserialization of String in a union type. ([#7938](https://github.com/crystal-lang/crystal/pull/7938), thanks @asterite) +- Validate element names in `XML::Builder`. ([#7965](https://github.com/crystal-lang/crystal/pull/7965), thanks @Blacksmoke16) +- Allow numeric keys in JSON (ie: `Hash(Int32, String).from_json`). ([#7944](https://github.com/crystal-lang/crystal/pull/7944), thanks @asterite) +- Add `alias`/`merge` methods to `YAML::Builder` and `YAML::Nodes::Builder`. ([#7949](https://github.com/crystal-lang/crystal/pull/7949), thanks @Blacksmoke16) + +### Files + +- Adds `File.readlink` to match `File.symlink`. ([#7858](https://github.com/crystal-lang/crystal/pull/7858), thanks @didactic-drunk) + +### Networking + +- **(breaking-change)** Improve URL encoding. `URI.escape` and `URI.unescape` are renamed to `URI.encode_www_form` and `URI.decode_www_form`. Add `URI.encode` and `URI.decode`. ([#7997](https://github.com/crystal-lang/crystal/pull/7997), [#8021](https://github.com/crystal-lang/crystal/pull/8021), thanks @straight-shoota, @bcardiff) +- **(performance)** HTTP protocol parsing optimizations. ([#8002](https://github.com/crystal-lang/crystal/pull/8002), [#8009](https://github.com/crystal-lang/crystal/pull/8009), thanks @asterite) +- Fixed `HTTP::Server` response double-close. ([#7908](https://github.com/crystal-lang/crystal/pull/7908), thanks @asterite) +- Enforce `HTTP::Client` host argument is just a host. ([#7958](https://github.com/crystal-lang/crystal/pull/7958), thanks @asterite) +- Allow `HTTP::Params.encode` to encode an arrays of values for a key. ([#7862](https://github.com/crystal-lang/crystal/pull/7862), thanks @rodrigopinto) +- Forward `read_timeout`/`write_timeout` in ssl socket to underlaying socket. ([#7820](https://github.com/crystal-lang/crystal/pull/7820), thanks @carlhoerberg) +- Natively support [Same-site Cookies](https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1.1). ([#7864](https://github.com/crystal-lang/crystal/pull/7864), thanks @Blacksmoke16) +- Allow setting buffer size for `IO::Buffered`. ([#7930](https://github.com/crystal-lang/crystal/pull/7930), thanks @carlhoerberg) + +### Crypto + +- Require openssl algorithm in pkcs5. ([#7985](https://github.com/crystal-lang/crystal/pull/7985), thanks @will) +- Fixed cipher expectation in `OpenSSL::SSL::Socket` spec. ([#7871](https://github.com/crystal-lang/crystal/pull/7871), thanks @j8r) + +### Concurrency + +- Fixed `sysconf` call on OpenBSD. ([#7879](https://github.com/crystal-lang/crystal/pull/7879), thanks @jcs) + +### System + +- Introduce `System::User` and `System::Group`. ([#7725](https://github.com/crystal-lang/crystal/pull/7725), thanks @woodruffw, @chris-huxtable) +- Add docs for `Process::Status.exit_status` (#7873). ([#8014](https://github.com/crystal-lang/crystal/pull/8014), thanks @UlisseMini) + +## Compiler + +- Fixed codegen of `pointer.as(Nil)`. ([#8019](https://github.com/crystal-lang/crystal/pull/8019), thanks @asterite) +- Fixed edge cases in parser and stringifier. ([#7886](https://github.com/crystal-lang/crystal/pull/7886), thanks @oprypin) +- Fixed `concrete_types` for virtual metaclass and modules. ([#7951](https://github.com/crystal-lang/crystal/pull/7951), thanks @bcardiff) +- Fixed incorrect `remove_indirection` in `TypeDefType`. ([#7971](https://github.com/crystal-lang/crystal/pull/7971), thanks @bcardiff) +- Fixed missing `CRYSTAL_SPEC_COMPILER_FLAGS` usage in some more specs. ([774768](https://github.com/crystal-lang/crystal/commit/77476800836eb47c8d783e2259bf21c2992f2041), thanks @bcardiff) +- Revamp compile error formatting & output. ([#7748](https://github.com/crystal-lang/crystal/pull/7748), thanks @martimatix) +- Add support for LLVM 8. ([#7987](https://github.com/crystal-lang/crystal/pull/7987), thanks @bcardiff) +- Add support for LLVM 7. ([#7986](https://github.com/crystal-lang/crystal/pull/7986), thanks @bcardiff, @waj, @foutrelis, @wmoxam) +- Add debug log helper function for codegen. ([#7935](https://github.com/crystal-lang/crystal/pull/7935), [#7937](https://github.com/crystal-lang/crystal/pull/7937), thanks @bcardiff) +- Refactor codegen of unions. ([#7940](https://github.com/crystal-lang/crystal/pull/7940), thanks @bcardiff) +- Move `LLVMId` from `CodeGenVisitor` to `Program`. ([#7973](https://github.com/crystal-lang/crystal/pull/7973), thanks @bcardiff) +- Minor additions and refactors on for LLVM codegen. ([#7972](https://github.com/crystal-lang/crystal/pull/7972), thanks @bcardiff) +- Add `bin/check-compiler-flag` helper script. Add `make clean_cache`. ([da3892](https://github.com/crystal-lang/crystal/commit/da38927f3a00f1e6e5ea86b96ca669533f0aa438), thanks @bcardiff) + +### Language semantics + +- Fixed generic metaclass argument expansion. ([#7916](https://github.com/crystal-lang/crystal/pull/7916), thanks @asterite) +- Fixed top-level private const not being scoped. ([#7907](https://github.com/crystal-lang/crystal/pull/7907), thanks @asterite) +- Fixed enum overflow when declaring members. ([#7881](https://github.com/crystal-lang/crystal/pull/7881), thanks @asterite) +- Fixed annotation lookup on generic types. ([#7891](https://github.com/crystal-lang/crystal/pull/7891), thanks @asterite) + +## Tools + +### Formatter + +- Format top-level inline macros. ([#7889](https://github.com/crystal-lang/crystal/pull/7889), [#7992](https://github.com/crystal-lang/crystal/pull/7992), thanks @asterite) + +### Doc generator + +- Allow rendering tags on methods without any docs. ([#7952](https://github.com/crystal-lang/crystal/pull/7952), thanks @Blacksmoke16) + +## Others + +- Update CI to use 0.29.0. ([#7863](https://github.com/crystal-lang/crystal/pull/7863), thanks @bcardiff) +- Automated snap publishing. ([#7893](https://github.com/crystal-lang/crystal/pull/7893), thanks @bcardiff) +- ~~Use LLVM 6.0.1 for darwin official packages.~~ ([#7994](https://github.com/crystal-lang/crystal/pull/7994), thanks @bcardiff) + +# 0.29.0 (2019-06-05) + +## Standard library + +- Fix example codes in multiple places. ([#7718](https://github.com/crystal-lang/crystal/pull/7718), thanks @maiha) + +### Macros + +- Fix inheritance support of `record` macro. ([#7811](https://github.com/crystal-lang/crystal/pull/7811), thanks @asterite) +- Omit quotes in `puts` macro output. ([#7734](https://github.com/crystal-lang/crystal/pull/7734), thanks @asterite) + +### Numeric + +- **(performance)** Optimize `String#to_u` methods for the case of a negative number. ([#7446](https://github.com/crystal-lang/crystal/pull/7446), thanks @r00ster91) + +### Text + +- **(breaking-change)** Deprecate `String#at`, use `String#char_at`. ([#7633](https://github.com/crystal-lang/crystal/pull/7633), thanks @j8r) +- **(breaking-change)** Change `String#to_i` to parse octals with prefix `0o` (but not `0` by default). ([#7691](https://github.com/crystal-lang/crystal/pull/7691), thanks @icy-arctic-fox) +- **(breaking-change)** Restrict some `String#to_i` arguments to be `Bool`. ([#7436](https://github.com/crystal-lang/crystal/pull/7436), thanks @j8r) +- Add `downcase` option to `String#camelcase`. ([#7717](https://github.com/crystal-lang/crystal/pull/7717), thanks @wontruefree) +- Add support for unicode 12.0.0. ([#7721](https://github.com/crystal-lang/crystal/pull/7721), thanks @Blacksmoke16) +- Fix `Unicode` not showing up in the API docs. ([#7720](https://github.com/crystal-lang/crystal/pull/7720), thanks @r00ster91) + +### Collections + +- **(breaking-change)** Remove `Slice#pointer`. ([#7581](https://github.com/crystal-lang/crystal/pull/7581), thanks @Maroo-b) +- Add sort methods to `Slice`. ([#7597](https://github.com/crystal-lang/crystal/pull/7597), thanks @Maroo-b) +- Add `Slice#[]?`. ([#7701](https://github.com/crystal-lang/crystal/pull/7701), thanks @Sija) +- Improve docs for `Slice#[]`. ([#7780](https://github.com/crystal-lang/crystal/pull/7780), thanks @Sija) + +### Serialization + +- YAML: let String handle numbers too. ([#7809](https://github.com/crystal-lang/crystal/pull/7809), thanks @asterite) + +### Time + +- Fix time format RFC 3339 to not include offset seconds. ([#7492](https://github.com/crystal-lang/crystal/pull/7492), thanks @straight-shoota) + +### Files + +- **(breaking-change)** Rename `File::DEVNULL` to `File::NULL`. ([#7778](https://github.com/crystal-lang/crystal/pull/7778), thanks @r00ster91) +- Fix handling of files starting with `~` in `Path#expand`. ([#7768](https://github.com/crystal-lang/crystal/pull/7768), thanks @byroot) +- Fix `Dir.glob(match_hidden: false)` not hiding hidden files properly. ([#7774](https://github.com/crystal-lang/crystal/pull/7774), thanks @ayazhafiz) + +### Networking + +- **(breaking-change)** Let `IO#copy` return `UInt64`. ([#7660](https://github.com/crystal-lang/crystal/pull/7660), thanks @asterite) +- Add support for UDP multicast. ([#7423](https://github.com/crystal-lang/crystal/pull/7423), thanks @stakach) +- Add missing requires to `openssl.cr`. ([#7803](https://github.com/crystal-lang/crystal/pull/7803), thanks @RX14) +- Add `IO::MultiWriter#flush`. ([#7765](https://github.com/crystal-lang/crystal/pull/7765), thanks @mamantoha) +- Add `OpenSSL::SSL::Socket#cipher` and `#tls_version`. ([#7445](https://github.com/crystal-lang/crystal/pull/7445), thanks @carlhoerberg) +- Improve `URI#normalize`. ([#7635](https://github.com/crystal-lang/crystal/pull/7635), thanks @straight-shoota) +- Improve documentation of some `URI` methods. ([#7796](https://github.com/crystal-lang/crystal/pull/7796), thanks @r00ster91) +- Refactor `StaticFileHandler` specs for `Last-Modified` header. ([#7640](https://github.com/crystal-lang/crystal/pull/7640), thanks @straight-shoota) +- Refactor compression usage in handler specs. ([#7819](https://github.com/crystal-lang/crystal/pull/7819), thanks @asterite) + +### Crypto + +- **(breaking-change)** Rename `Crypto::Bcrypt::Password#==` to `#verify`. ([#7790](https://github.com/crystal-lang/crystal/pull/7790), thanks @asterite) + +### Concurrency + +- Add docs for `Channel`. ([#7673](https://github.com/crystal-lang/crystal/pull/7673), thanks @j8r) + +## Compiler + +- **(breaking-change)** Fix require relative path resolution. ([#7758](https://github.com/crystal-lang/crystal/pull/7758), thanks @asterite) +- **(breaking-change)** Disallow '!' or '?' at the end of the LHS in an assignment. ([#7582](https://github.com/crystal-lang/crystal/pull/7582), thanks @Maroo-b) +- Allow running compiler_specs with specific flags. ([#7837](https://github.com/crystal-lang/crystal/pull/7837), thanks @bcardiff) +- Fix extend from generic types. ([#7812](https://github.com/crystal-lang/crystal/pull/7812), thanks @asterite) +- Don't virtualize types in `Union(...)` and keep more accurate type information. ([#7815](https://github.com/crystal-lang/crystal/pull/7815), thanks @asterite) +- Do not generate debug metadata for arguments of naked functions. ([#7775](https://github.com/crystal-lang/crystal/pull/7775), thanks @eyusupov) +- Detect deprecation on initialize methods and methods with named args. ([#7724](https://github.com/crystal-lang/crystal/pull/7724), thanks @bcardiff) +- Fix track of AST nodes location. ([#7827](https://github.com/crystal-lang/crystal/pull/7827), thanks @asterite) +- Fix `offsetof` not being usable with macros. ([#7703](https://github.com/crystal-lang/crystal/pull/7703), thanks @malte-v) +- Allow parsing of `call &.@ivar`. ([#7754](https://github.com/crystal-lang/crystal/pull/7754), thanks @asterite) +- Fix `Def#to_s` with `**options` and `&block`. ([#7854](https://github.com/crystal-lang/crystal/pull/7854), thanks @MakeNowJust) +- Check `pointerof` inner expression for errors. ([#7755](https://github.com/crystal-lang/crystal/pull/7755), thanks @asterite) +- Fix some error messages. ([#7833](https://github.com/crystal-lang/crystal/pull/7833), thanks @asterite) +- Improve wording of `pointerof(self)` parser error. ([#7542](https://github.com/crystal-lang/crystal/pull/7542), thanks @r00ster91) +- Fix typo. ([#7828](https://github.com/crystal-lang/crystal/pull/7828), thanks @RX14) + +### Language semantics + +- **(breaking-change)** Fix new/initialize lookup regarding modules. ([#7818](https://github.com/crystal-lang/crystal/pull/7818), thanks @asterite) +- **(breaking-change)** Don't precompute `sizeof` on abstract structs and modules. ([#7801](https://github.com/crystal-lang/crystal/pull/7801), thanks @asterite) +- Consider macro calls in `@ivar` initializer. ([#7750](https://github.com/crystal-lang/crystal/pull/7750), thanks @asterite) +- Give precedence to `T.class` over `Class` in method lookup. ([#7759](https://github.com/crystal-lang/crystal/pull/7759), thanks @asterite) +- Honor enum base type on non-default values. ([#7776](https://github.com/crystal-lang/crystal/pull/7776), thanks @asterite) +- Avoid lookup of private def defined inside macro. ([#7733](https://github.com/crystal-lang/crystal/pull/7733), thanks @asterite) +- Improve type flow of var in `if` with `&&`. ([#7785](https://github.com/crystal-lang/crystal/pull/7785), thanks @asterite) +- Fix handling of `NoReturn` in `if`. ([#7792](https://github.com/crystal-lang/crystal/pull/7792), thanks @asterite) +- Improve edge issues with `while` and `rescue`. ([#7806](https://github.com/crystal-lang/crystal/pull/7806), thanks @asterite) +- Improve error on macro call in proc pointer. ([#7757](https://github.com/crystal-lang/crystal/pull/7757), thanks @asterite) +- Fix error on named args forwarding. ([#7756](https://github.com/crystal-lang/crystal/pull/7756), thanks @asterite) +- Check `NoReturn` type in named args. ([#7761](https://github.com/crystal-lang/crystal/pull/7761), thanks @asterite) +- Fix internal handling of unbound/abstract generic types. ([#7781](https://github.com/crystal-lang/crystal/pull/7781), thanks @asterite) +- Fix wrong cast to unbound generic type. ([#7793](https://github.com/crystal-lang/crystal/pull/7793), thanks @asterite) +- Fix subclass observer to handle edge case call over generic types. ([#7735](https://github.com/crystal-lang/crystal/pull/7735), thanks @asterite) +- Fix edge case of abstract struct with one subclass. ([#7787](https://github.com/crystal-lang/crystal/pull/7787), thanks @asterite) +- Make automatic cast work with `with ... yield`. ([#7746](https://github.com/crystal-lang/crystal/pull/7746), thanks @asterite) + +## Tools + +- Allow to lookup class and module implementations. ([#7742](https://github.com/crystal-lang/crystal/pull/7742), thanks @MakeNowJust) +- Refactor old duplicated 'def contains_target'. ([#7739](https://github.com/crystal-lang/crystal/pull/7739), thanks @MakeNowJust) + +### Formatter + +- Don't produce unnecessary newline before named args following heredoc. ([#7695](https://github.com/crystal-lang/crystal/pull/7695), thanks @MakeNowJust) +- Fix formatting of multiline call arguments. ([#7745](https://github.com/crystal-lang/crystal/pull/7745), thanks @MakeNowJust) +- Fix formatting of annotations with newlines and spaces. ([#7744](https://github.com/crystal-lang/crystal/pull/7744), thanks @MakeNowJust) +- Refactor code to format &.[]. ([#7699](https://github.com/crystal-lang/crystal/pull/7699), thanks @MakeNowJust) + +## Others + +- CI improvements and housekeeping. ([#7705](https://github.com/crystal-lang/crystal/pull/7705), [#7852](https://github.com/crystal-lang/crystal/pull/7852), thanks @bcardiff) +- Move VERSION inside ./src. ([#7804](https://github.com/crystal-lang/crystal/pull/7804), thanks @bcardiff) + +# 0.28.0 (2019-04-17) + +## Language changes + +- **(breaking-change)** Enum declaration members can no longer be separated by a space, only by a newline, `;` or `,`, the latter being deprecated and reformatted to a newline. ([#7607](https://github.com/crystal-lang/crystal/pull/7607), [#7618](https://github.com/crystal-lang/crystal/pull/7618), thanks @asterite, and @j8r) +- Add begin-less and end-less ranges: `array[5..]`. ([#7179](https://github.com/crystal-lang/crystal/pull/7179), thanks @asterite) +- Add `offsetof(Type, @ivar)` expression. ([#7589](https://github.com/crystal-lang/crystal/pull/7589), thanks @malte-v) + +### Macros + +- Add `Type#annotations` to list all annotations and not just the last of each kind. ([#7326](https://github.com/crystal-lang/crystal/pull/7326), thanks @Blacksmoke16) +- Add `ArrayLiteral#sort_by` macro method. ([#3947](https://github.com/crystal-lang/crystal/pull/3947), thanks @jreinert) + +## Standard library + +- **(breaking-change)** Allow creating `None` enum flag with `Enum.from_value`. ([#6516](https://github.com/crystal-lang/crystal/pull/6516), thanks @bew) +- **(breaking-change)** Add deprecation message to `PartialComparable`. Its behaviour has been fully integrated into `Comparable`. ([#7664](https://github.com/crystal-lang/crystal/pull/7664), thanks @straight-shoota) +- **(performance)** Optimize dwarf line numbers decoding. ([#7413](https://github.com/crystal-lang/crystal/pull/7413), thanks @asterite) +- Fix `Signal::CHLD.reset` not clearing previous handler. ([#7409](https://github.com/crystal-lang/crystal/pull/7409), thanks @asterite) +- Add lazy versions of `Object.getter?` and `Object.property?` macros. ([#7322](https://github.com/crystal-lang/crystal/pull/7322), thanks @Sija) +- Allow returning other values than `-1`, `0` and `1` by `Comparable#<=>`. ([#7277](https://github.com/crystal-lang/crystal/pull/7277), thanks @r00ster91) +- Add missing `require` statements to samples in the API docs. ([#7564](https://github.com/crystal-lang/crystal/pull/7564), thanks @Maroo-b) +- Fix example codes in multiple places. ([#7569](https://github.com/crystal-lang/crystal/pull/7569), thanks @maiha) +- Add documentation for `@[Flags]` and `@[Link]` annotations. ([#7665](https://github.com/crystal-lang/crystal/pull/7665), thanks @bcardiff) +- Add documentation for `Bool`. ([#7651](https://github.com/crystal-lang/crystal/pull/7651), thanks @wontruefree) +- Refactor to avoid usage of the thread-local `$errno` GLIBC_PRIVATE symbol. ([#7496](https://github.com/crystal-lang/crystal/pull/7496), thanks @felixvf) +- Refactor to have similar signatures across the stdlib for `#to_s` and `#inspect`. ([#7528](https://github.com/crystal-lang/crystal/pull/7528), thanks @wontruefree) + +### Numeric + +- **(breaking-change)** Add deprecation message to `Int#/`. It will return a `Float` in `0.29.0`. Use `Int#//` for integer division. ([#7639](https://github.com/crystal-lang/crystal/pull/7639), thanks @bcardiff) +- Change `Number#inspect` to not show the type suffixes. ([#7525](https://github.com/crystal-lang/crystal/pull/7525), thanks @asterite) +- Add `Int#leading_zeros_count` and `Int#trailing_zeros_count`. ([#7520](https://github.com/crystal-lang/crystal/pull/7520), thanks @Sija) +- Add `Big*#//`, `BigInt#&`-ops and missing `#floor`, `#ceil`, `#trunc`. ([#7638](https://github.com/crystal-lang/crystal/pull/7638), thanks @bcardiff) +- Improve `OverflowError` message. ([#7375](https://github.com/crystal-lang/crystal/pull/7375), thanks @r00ster91) + +### Text + +- **(performance)** Optimize `String#compare` in case of ASCII only. ([#7352](https://github.com/crystal-lang/crystal/pull/7352), thanks @r00ster91) +- Add methods for human-readable formatting of numbers: `Number#format`, `Number#humanize`, and `Int#humanize_bytes`. ([#6314](https://github.com/crystal-lang/crystal/pull/6314), thanks @straight-shoota) +- Add `String#rchop?` and `String#lchop?`. ([#7328](https://github.com/crystal-lang/crystal/pull/7328), thanks @j8r) +- Add `options` argument to `String#camelcase` and `String#underscore`. ([#7374](https://github.com/crystal-lang/crystal/pull/7374), thanks @r00ster91) +- Add docs to `Unicode::CaseOptions`. ([#7513](https://github.com/crystal-lang/crystal/pull/7513), thanks @r00ster91) +- Improve specs and docs for `String#each_line` and `IO#each_line`. ([#7419](https://github.com/crystal-lang/crystal/pull/7419), thanks @straight-shoota) + +### Collections + +- **(breaking-change)** Let `Array#sort` only use `<=>`, and let `<=>` return `nil` for partial comparability. ([#6611](https://github.com/crystal-lang/crystal/pull/6611), thanks @asterite) +- **(breaking-change)** Drop `Iterator#rewind`. Implement `#cycle` by storing elements in an array. ([#7440](https://github.com/crystal-lang/crystal/pull/7440), thanks @asterite) +- **(performance)** Add `Enumerable#each_cons` support for `Deque` as a reuse buffer. ([#7233](https://github.com/crystal-lang/crystal/pull/7233), thanks @yxhuvud) +- **(performance)** Change `Range#bsearch` `/ 2` to `>> 1` for faster performance. ([#7531](https://github.com/crystal-lang/crystal/pull/7531), thanks @Fryguy) +- Fix `Slice#clone` for non-primitive types and deep copy. ([#7591](https://github.com/crystal-lang/crystal/pull/7591), thanks @straight-shoota) +- Move `Indexable#zip` and `Indexable#zip?` to `Enumerable` and make it work with any number of `Indexable` or `Iterable` or `Iterator`. ([#7453](https://github.com/crystal-lang/crystal/pull/7453), thanks @asterite) +- Add `Slice#[](Range)`. ([#7439](https://github.com/crystal-lang/crystal/pull/7439), thanks @asterite) +- Add nillable range fetching `#[]?(Range)` to `Array` and `String`. ([#7338](https://github.com/crystal-lang/crystal/pull/7338), thanks @j8r) +- Add `Set#add?`. ([#7495](https://github.com/crystal-lang/crystal/pull/7495), thanks @Sija) +- Improve documentation of `Hash` regarding ordering of items. ([#7594](https://github.com/crystal-lang/crystal/pull/7594), thanks @straight-shoota) + +### Serialization + +- **(breaking-change)** Change return type of `YAML#libyaml_version` to `SemanticVersion`. ([#7555](https://github.com/crystal-lang/crystal/pull/7555), thanks @asterite) +- Fix support for libxml2 2.9.9. ([#7477](https://github.com/crystal-lang/crystal/pull/7477), thanks @asterite) +- Fix support for libyaml 0.2.2. ([#7555](https://github.com/crystal-lang/crystal/pull/7555), thanks @asterite) +- Add `BigDecimal.from_yaml`. ([#7398](https://github.com/crystal-lang/crystal/pull/7398), thanks @Sija) + +### Time + +- **(breaking-change)** Rename `Time` constructors. Deprecate `Time.now` to encourage usage `Time.utc` or `Time.local` ([#5346](https://github.com/crystal-lang/crystal/pull/5346), [#7586](https://github.com/crystal-lang/crystal/pull/7586), thanks @straight-shoota) +- **(breaking-change)** Rename `Time#add_span` to `Time#shift` for changing a time instance by calendar units and handle other units. ([#6598](https://github.com/crystal-lang/crystal/pull/6598), thanks @straight-shoota) +- **(breaking-change)** Change `Time#date` to return a `Tuple` of `{year, month, day}`. Use `Time#at_beginning_of_day` if a `Time` instance is wanted. ([#5822](https://github.com/crystal-lang/crystal/pull/5822), thanks @straight-shoota) +- Fix Windows monotonic time bug. ([#7377](https://github.com/crystal-lang/crystal/pull/7377), thanks @domgetter) +- Refactor `Time` methods. ([#6581](https://github.com/crystal-lang/crystal/pull/6581), thanks @straight-shoota) + +### Files + +- **(breaking-change)** Remove `IO#flush_on_newline` and only `sync` on `STDOUT`/`STDIN`/`STDERR` when they are TTY devices. ([#7470](https://github.com/crystal-lang/crystal/pull/7470), thanks @asterite) +- Add `Path` type. ([#5635](https://github.com/crystal-lang/crystal/pull/5635), thanks @straight-shoota) + +### Networking + +- **(breaking-change)** Move `HTTP::Multipart` to `MIME::Multipart`. ([#7085](https://github.com/crystal-lang/crystal/pull/7085), thanks @m1lt0n) +- **(breaking-change)** Stop parsing JSON in OAuth2 errors. ([#7467](https://github.com/crystal-lang/crystal/pull/7467), thanks @asterite) +- **(breaking-change)** Fix `RequestProcessor` connection reuse logic. ([#7055](https://github.com/crystal-lang/crystal/pull/7055), thanks @straight-shoota) +- **(breaking-change)** Replace `HTTP.default_status_message_for(Int)` with `HTTP::Status.new(Int).description`. ([#7247](https://github.com/crystal-lang/crystal/pull/7247), thanks @dwightwatson) +- **(breaking-change)** Fix issues in `URI` implementation. `URI#opaque` method is merged into `URI#path`, which no longer returns `Nil`. `#parse`/`#to_s` normalization and default port handling has changed. ([#6323](https://github.com/crystal-lang/crystal/pull/6323), thanks @straight-shoota) +- Fix write buffering in OpenSSL sockets. ([#7460](https://github.com/crystal-lang/crystal/pull/7460), thanks @carlhoerberg) +- Fix leaks in `HTTP::Server` `#bind_*` and specs. ([#7197](https://github.com/crystal-lang/crystal/pull/7197), thanks @straight-shoota) +- Add `HTTP::Request#remote_address`. ([#7610](https://github.com/crystal-lang/crystal/pull/7610), thanks @asterite) +- Add `HTTP::Status` and `Response#status`. ([#7247](https://github.com/crystal-lang/crystal/pull/7247), [#7682](https://github.com/crystal-lang/crystal/pull/7682), thanks @dwightwatson, and @bcardiff) +- Add support for OAuth 2.0 resource owner password credentials grant type. ([#7424](https://github.com/crystal-lang/crystal/pull/7424), thanks @Blacksmoke16) +- Add support for IIS date format in cookies. ([#7405](https://github.com/crystal-lang/crystal/pull/7405), thanks @Sija) +- Allow calls to `IO::Syscall#wait_readable` and `IO::Syscall#wait_writable`. ([#7366](https://github.com/crystal-lang/crystal/pull/7366), thanks @stakach) + +- Fix spec of `HTTP::Client` to not write server response after timeout. ([#7402](https://github.com/crystal-lang/crystal/pull/7402), thanks @asterite) +- Fix spec of `TCP::Server` for musl. ([#7484](https://github.com/crystal-lang/crystal/pull/7484), thanks @straight-shoota) + +### Crypto + +- **(breaking-change)** Use `OpenSSL::Algorithm` instead of symbols for `digest`/`hexdigest`. Expose LibCrypt's `PKCS5_PBKDF2_HMAC`. ([#7264](https://github.com/crystal-lang/crystal/pull/7264), thanks @mniak) + +### Concurrency + +- Add multi-threading ready GC when compiling with `-D preview_mt`. ([#7546](https://github.com/crystal-lang/crystal/pull/7546), thanks @bcardiff, @waj, and @ysbaddaden) +- Ship patched bdw-gc for multi-threading support. ([#7622](https://github.com/crystal-lang/crystal/pull/7622), thanks @bcardiff, and @ysbaddaden) +- Refactor to extract `Fiber::StackPool` from `Fiber`. ([#7417](https://github.com/crystal-lang/crystal/pull/7417), thanks @ysbaddaden) +- Refactor `IO::Syscall` as `IO::Evented`. ([#7505](https://github.com/crystal-lang/crystal/pull/7505), thanks @ysbaddaden) + +### System + +- Add command and args to `execvp` error message. ([#7511](https://github.com/crystal-lang/crystal/pull/7511), thanks @straight-shoota) +- Refactor signals handling in a separate fiber. ([#7469](https://github.com/crystal-lang/crystal/pull/7469), thanks @asterite) + +### Spec + +- Improve how running specs are cancelled upon `CTRL+C`. ([#7426](https://github.com/crystal-lang/crystal/pull/7426), thanks @asterite) +- Allow `pending` and `it` to accept constants. ([#7646](https://github.com/crystal-lang/crystal/pull/7646), thanks @straight-shoota) + +## Compiler + +- **(performance)** Avoid fork and spawn when `--threads=1`. ([#7397](https://github.com/crystal-lang/crystal/pull/7397), thanks @asterite) +- Fix exception type thrown on missing require. ([#7386](https://github.com/crystal-lang/crystal/pull/7386), thanks @asterite) +- Fix ICE when assigning a constant inside a multi-assign. ([#7468](https://github.com/crystal-lang/crystal/pull/7468), thanks @asterite) +- Fix parsing and behaviour of `->foo.[]` and other operators . ([#7334](https://github.com/crystal-lang/crystal/pull/7334), thanks @asterite) +- Fix parsing bug in `asm` with 3 colons and a variable. ([#7627](https://github.com/crystal-lang/crystal/pull/7627), thanks @r00ster91) +- Opt-in detection of calls to `@[Deprecated]` methods. ([#7596](https://github.com/crystal-lang/crystal/pull/7596), [#7626](https://github.com/crystal-lang/crystal/pull/7626), [#7661](https://github.com/crystal-lang/crystal/pull/7661), thanks @bcardiff) +- Add `CRYSTAL_LIBRARY_PATH` for lookup static libraries. ([#7562](https://github.com/crystal-lang/crystal/pull/7562), thanks @bcardiff) +- Improve error messages by adding the scope (and `with ... yield` scope, if any) on undefined method error. ([#7384](https://github.com/crystal-lang/crystal/pull/7384), thanks @asterite) +- Suggest `next` when trying to break from captured block . ([#7406](https://github.com/crystal-lang/crystal/pull/7406), thanks @r00ster91) +- Add detection of linux environment in compiler config. ([#7479](https://github.com/crystal-lang/crystal/pull/7479), thanks @straight-shoota) +- Pending leftovers to support `//` and `&`-ops in multiple places. ([#7628](https://github.com/crystal-lang/crystal/pull/7628), thanks @bcardiff) +- Refactor `Crystal::Config.version` to use `read_file` macro. ([#7081](https://github.com/crystal-lang/crystal/pull/7081), thanks @Sija) +- Rewrite macro spec without executing a shell command. ([#6962](https://github.com/crystal-lang/crystal/pull/6962), thanks @asterite) +- Fix typo in internals. ([#7592](https://github.com/crystal-lang/crystal/pull/7592), thanks @toshokan) + +### Language semantics + +- Fix issues with `as`, `as?` and unions and empty types. ([#7475](https://github.com/crystal-lang/crystal/pull/7475), thanks @asterite) +- Fix method lookup when restrictions of instantiated and non-instantiated generic types are used. ([#7537](https://github.com/crystal-lang/crystal/pull/7537), thanks @bew) +- Fix method lookup when free vars and explicit types are used. ([#7536](https://github.com/crystal-lang/crystal/pull/7536), [#7580](https://github.com/crystal-lang/crystal/pull/7580), thanks @bew) +- When declaring a `protected initialize`, define a protected `new`. ([#7510](https://github.com/crystal-lang/crystal/pull/7510), thanks @asterite) +- Fix named args type matching. ([#7529](https://github.com/crystal-lang/crystal/pull/7529), thanks @asterite) +- Merge procs with the same arguments type and `Nil | T` return type to `Nil` return type. ([#7527](https://github.com/crystal-lang/crystal/pull/7527), thanks @asterite) +- Fix passing recursive alias to proc. ([#7568](https://github.com/crystal-lang/crystal/pull/7568), thanks @asterite) + +## Tools + +- Suggest the user to run the formatter in `travis.yml`. ([#7138](https://github.com/crystal-lang/crystal/pull/7138), thanks @KCErb) + +### Formatter + +- Fix formatting of `1\n.as(Int32)`. ([#7347](https://github.com/crystal-lang/crystal/pull/7347), thanks @asterite) +- Fix formatting of nested array elements. ([#7450](https://github.com/crystal-lang/crystal/pull/7450), thanks @MakeNowJust) +- Fix formatting of comments and enums. ([#7605](https://github.com/crystal-lang/crystal/pull/7605), thanks @asterite) +- Fix CLI handling of absolute paths input. ([#7560](https://github.com/crystal-lang/crystal/pull/7560), thanks @RX14) + +### Doc generator + +- Don't include private constants. ([#7575](https://github.com/crystal-lang/crystal/pull/7575), thanks @r00ster91) +- Include Crystal built-in constants. ([#7623](https://github.com/crystal-lang/crystal/pull/7623), thanks @bcardiff) +- Add compile-time flag to docs generator. ([#6668](https://github.com/crystal-lang/crystal/pull/6668), [#7438](https://github.com/crystal-lang/crystal/pull/7438), thanks @straight-shoota) +- Display deprecated label when `@[Deprecated]` is used. ([#7653](https://github.com/crystal-lang/crystal/pull/7653), thanks @bcardiff) + +### Playground + +- Change the font-weight used for better readability. ([#7552](https://github.com/crystal-lang/crystal/pull/7552), thanks @Maroo-b) + +## Others + +- CI improvements and housekeeping. ([#7359](https://github.com/crystal-lang/crystal/pull/7359), [#7381](https://github.com/crystal-lang/crystal/pull/7381), [#7388](https://github.com/crystal-lang/crystal/pull/7388), [#7387](https://github.com/crystal-lang/crystal/pull/7387), [#7390](https://github.com/crystal-lang/crystal/pull/7390), [#7622](https://github.com/crystal-lang/crystal/pull/7622), thanks @bcardiff) +- Smoke test linux 64 bits package using docker image recent build. ([#7389](https://github.com/crystal-lang/crystal/pull/7389), thanks @bcardiff) +- Mention git pre-commit hook in `CONTRIBUTING.md`. ([#7617](https://github.com/crystal-lang/crystal/pull/7617), thanks @straight-shoota) +- Fix misspellings throughout the codebase. ([#7361](https://github.com/crystal-lang/crystal/pull/7361), thanks @Sija) +- Use chars instead of strings throughout the codebase. ([#6237](https://github.com/crystal-lang/crystal/pull/6237), thanks @r00ster91) +- Fix GC finalization warning in `Thread` specs. ([#7403](https://github.com/crystal-lang/crystal/pull/7403), thanks @asterite) +- Remove generated docs from linux packages. ([#7519](https://github.com/crystal-lang/crystal/issues/7519), thanks @straight-shoota) + +# 0.27.2 (2019-02-05) + +## Standard library + +- Fixed integer overflow in main thread stack base detection. ([#7373](https://github.com/crystal-lang/crystal/pull/7373), thanks @ysbaddaden) + +### Networking + +- Fixes TLS exception during shutdown. ([#7372](https://github.com/crystal-lang/crystal/pull/7372), thanks @bcardiff) +- Fixed `HTTP::Client` support exception on missing Content-Type. ([#7371](https://github.com/crystal-lang/crystal/pull/7371), thanks @bew) + +# 0.27.1 (2019-01-30) + +## Language changes + +- Allow trailing commas inside tuple types. ([#7182](https://github.com/crystal-lang/crystal/pull/7182), thanks @asterite) + +## Standard library + +- **(performance)** Optimize generating `UUID` from `String`. ([#7030](https://github.com/crystal-lang/crystal/pull/7030), thanks @jgaskins) +- **(performance)** Improve `SemanticVersion` operations. ([#7234](https://github.com/crystal-lang/crystal/pull/7234), thanks @j8r) +- Fixed markdown inline code parsing. ([#7090](https://github.com/crystal-lang/crystal/pull/7090), thanks @MakeNowJust) +- Fixed inappropriate uses of `Time.now`. ([#7155](https://github.com/crystal-lang/crystal/pull/7155), thanks @straight-shoota) +- Make `Nil#not_nil!` raise `NilAssertionError`. ([#7330](https://github.com/crystal-lang/crystal/pull/7330), thanks @r00ster91) +- Add SemanticVersion to API docs. ([#7003](https://github.com/crystal-lang/crystal/pull/7003), thanks @Blacksmoke16) +- Add docs to discourage the use of `Bool#to_unsafe` other than for C bindings. ([#7320](https://github.com/crystal-lang/crystal/pull/7320), thanks @oprypin) +- Refactor `#to_s` to be independent of the `name` method. ([#7295](https://github.com/crystal-lang/crystal/pull/7295), thanks @asterite) + +### Macros + +- Fixed docs of `ArrayLiteral#unshift`. ([#7127](https://github.com/crystal-lang/crystal/pull/7127), thanks @Blacksmoke16) +- Fixed `Annotation#[]` to accept `String` and `Symbol` as keys. ([#7153](https://github.com/crystal-lang/crystal/pull/7153), thanks @MakeNowJust) +- Fixed `NamedTupleLiteral#[]` to raise a compile error for invalid key type. ([#7158](https://github.com/crystal-lang/crystal/pull/7158), thanks @MakeNowJust) +- Fixed `getter`/`property` macros to work properly with `Bool` types. ([#7313](https://github.com/crystal-lang/crystal/pull/7313), thanks @Sija) +- Add `read_file` macro method. ([#6967](https://github.com/crystal-lang/crystal/pull/6967), [#7094](https://github.com/crystal-lang/crystal/pull/7094), thanks @Sija, @woodruffw) +- Add `StringLiteral#count`. ([#7239](https://github.com/crystal-lang/crystal/pull/7239), thanks @Blacksmoke16) + +### Numeric + +- Fixed scale issues when dividing `BigDecimal`. ([#7218](https://github.com/crystal-lang/crystal/pull/7218), thanks @Sija) +- Allow underscores in the `String` passed to `Big*` constructors. ([#7107](https://github.com/crystal-lang/crystal/pull/7107), thanks @Sija) +- Add conversion methods and docs to `Complex`. ([#5440](https://github.com/crystal-lang/crystal/pull/5440), thanks @Sija) +- Add specs for `Int128`, `UInt128`. ([#7173](https://github.com/crystal-lang/crystal/pull/7173), thanks @bcardiff) +- Add unsafe number ops `value.to_X!`/`T.new!`/`Int#&**`. ([#7226](https://github.com/crystal-lang/crystal/pull/7226), thanks @bcardiff) +- Add overflow detection with preview opt-in. ([#7206](https://github.com/crystal-lang/crystal/pull/7206), thanks @bcardiff) + +### Text + +- Fixed `ECR` location error reported. ([#7137](https://github.com/crystal-lang/crystal/pull/7137), thanks @MakeNowJust) +- Add docs to ECR. ([#7121](https://github.com/crystal-lang/crystal/pull/7121), thanks @KCErb) +- Refactor `String#to_i` to avoid future overflow. ([#7172](https://github.com/crystal-lang/crystal/pull/7172), thanks @bcardiff) + +### Collections + +- Fixed docs example in `Hash#from`. ([#7210](https://github.com/crystal-lang/crystal/pull/7210), thanks @r00ster91) +- Fixed docs links of `Enumerable#chunks` and `Iterator#chunk`. ([#6941](https://github.com/crystal-lang/crystal/pull/6941), thanks @r00ster91) +- Remove implicit null skip from `Hash` to `JSON` serialization. ([#7053](https://github.com/crystal-lang/crystal/pull/7053), thanks @MakeNowJust) +- Add `Iterator#slice_after`. ([#7146](https://github.com/crystal-lang/crystal/pull/7146), thanks @asterite) +- Add `Iterator#slice_before`. ([#7152](https://github.com/crystal-lang/crystal/pull/7152), thanks @asterite) +- Add `Iteratory#slice_when` and `Iterator#chunk_while`. ([#7159](https://github.com/crystal-lang/crystal/pull/7159), thanks @asterite) +- Add `Enumerable#to_h(&block)`. ([#7150](https://github.com/crystal-lang/crystal/pull/7150), thanks @Sija) +- Add `Enumerable#one?`. ([#7166](https://github.com/crystal-lang/crystal/pull/7166), thanks @asterite) +- Add several `Enumerable`, `Iterator` and `Array` overloads that accept a pattern. ([#7174](https://github.com/crystal-lang/crystal/pull/7174), thanks @asterite) +- Add docs to hash constructors. ([#6923](https://github.com/crystal-lang/crystal/pull/6923), thanks @KCErb) + +### Serialization + +- Add conversion between JSON and YAML. ([#7232](https://github.com/crystal-lang/crystal/pull/7232), thanks @straight-shoota) +- Standardize `#as_T`/`#as_T?` methods between `JSON::Any`/`YAML::Any`. ([#6556](https://github.com/crystal-lang/crystal/pull/6556), thanks @j8r) +- Add `Set#from_yaml`. ([#6310](https://github.com/crystal-lang/crystal/pull/6310), thanks @kostya) + +### Time + +- Fixed `Time::Span` initializer and `sleep` for big seconds. ([#7221](https://github.com/crystal-lang/crystal/pull/7221), thanks @asterite) +- Fixed docs to show proper use of parse. ([#7035](https://github.com/crystal-lang/crystal/pull/7035), thanks @jwoertink) +- Add missing `Float#weeks` method similar to `Int#weeks`. ([#7165](https://github.com/crystal-lang/crystal/pull/7165), thanks @vlazar) + +### Files + +- Fix `mkstemps` support on aarch64. ([#7300](https://github.com/crystal-lang/crystal/pull/7300), thanks @silmanduin66) +- Validate LibC error codes in specs involving Errno errors. ([#7087](https://github.com/crystal-lang/crystal/pull/7087), thanks @straight-shoota) +- Add microsecond precision to `System::File.utime` (Unix). ([#7156](https://github.com/crystal-lang/crystal/pull/7156), thanks @straight-shoota) +- Add missing tempfile cleanup in specs. ([#7250](https://github.com/crystal-lang/crystal/pull/7250), thanks @bcardiff) +- Add docs for file open modes. ([#6664](https://github.com/crystal-lang/crystal/pull/6664), thanks @r00ster91) + +### Networking + +- Fixed `HTTP::Client` edge case of exception during in TLS initialization. ([#7123](https://github.com/crystal-lang/crystal/pull/7123), thanks @asterite) +- Fixed `OpenSSL::SSL::Error.new` to not raise `Errno`. ([#7068](https://github.com/crystal-lang/crystal/pull/7068), thanks @straight-shoota) +- Fixed `URI` encoding in `StaticFileHandler::DirectoryListing`. ([#7072](https://github.com/crystal-lang/crystal/pull/7072), thanks @Sija) +- Add MIME registry. ([#5765](https://github.com/crystal-lang/crystal/pull/5765), [#7079](https://github.com/crystal-lang/crystal/pull/7079), [#7080](https://github.com/crystal-lang/crystal/pull/7080), thanks @straight-shoota, @Sija) +- Add `MIME::MediaType` for parsing mime media types. ([#7077](https://github.com/crystal-lang/crystal/pull/7077), thanks @straight-shoota) +- Add support for 100-continue in `HTTP::Server::Response`. ([#6912](https://github.com/crystal-lang/crystal/pull/6912), thanks @jreinert) +- Add support for creating sockets from raw file descriptors. ([#6894](https://github.com/crystal-lang/crystal/pull/6894), thanks @myfreeweb) +- Add SNI support for OpenSSL. ([#7291](https://github.com/crystal-lang/crystal/pull/7291), thanks @bararchy) +- Improve `HTTP::Server` docs. ([#7251](https://github.com/crystal-lang/crystal/pull/7251), thanks @straight-shoota) +- Refactor `OpenSSL` specs to reduce chances of failing. ([#7202](https://github.com/crystal-lang/crystal/pull/7202), thanks @bcardiff) + +### Crypto + +- Add `OpenSSL::Cipher#authenticated?` to see if the cipher supports aead. ([#7223](https://github.com/crystal-lang/crystal/pull/7223), thanks @danielwestendorf) + +### System + +- Fixed inline ASM when compiling for ARM. ([#7041](https://github.com/crystal-lang/crystal/pull/7041), thanks @omarroth) +- Implement `Crystal::System` for Win32. ([#6972](https://github.com/crystal-lang/crystal/pull/6972), thanks @markrjr) +- Add `Errno#errno_message` getter. ([#6702](https://github.com/crystal-lang/crystal/pull/6702), thanks @r00ster91) + +### Spec + +- Detect nesting `it` and `pending` at run-time. ([#7297](https://github.com/crystal-lang/crystal/pull/7297), thanks @MakeNowJust) + +## Compiler + +- Fixed how `LLVM::Type.const_int` emit `Int128` literals. ([#7135](https://github.com/crystal-lang/crystal/pull/7135), thanks @bcardiff) +- Fixed ICE related to named tuples. ([#7163](https://github.com/crystal-lang/crystal/pull/7163), thanks @asterite) +- Fixed automatic casting for private top-level methods. ([#7310](https://github.com/crystal-lang/crystal/pull/7310), thanks @asterite) +- Give proper error if defining initialize inside enum, allow `Enum.new`. ([#7266](https://github.com/crystal-lang/crystal/pull/7266), thanks @asterite) +- Give proper error when trying to access instance variable of union type. ([#7194](https://github.com/crystal-lang/crystal/pull/7194), thanks @asterite) +- Give proper error when trying to instantiate Module. ([#6735](https://github.com/crystal-lang/crystal/pull/6735), thanks @r00ster91) +- Give proper error related to named arguments. ([#7288](https://github.com/crystal-lang/crystal/pull/7288), thanks @asterite) +- Parse required comma between block args. ([#7343](https://github.com/crystal-lang/crystal/pull/7343), thanks @asterite) +- Improve inference in recursion that involves blocks. ([#7161](https://github.com/crystal-lang/crystal/pull/7161), thanks @asterite) +- Add locations to all expanded macro arguments. ([#7008](https://github.com/crystal-lang/crystal/pull/7008), thanks @MakeNowJust) +- Turn a not compiler specific error while requiring into ICE. ([#7208](https://github.com/crystal-lang/crystal/pull/7208), thanks @MakeNowJust) +- Remove old `nil?` error on pointer types. ([#7180](https://github.com/crystal-lang/crystal/pull/7180), thanks @asterite) +- Improve too big tuple and named tuple error message. ([#7131](https://github.com/crystal-lang/crystal/pull/7131), thanks @r00ster91) +- Workaround buggy offset debug info values. ([#7335](https://github.com/crystal-lang/crystal/pull/7335), thanks @bcardiff) +- Refactor extract helper methods to emit `Float32`, `Float64` values. ([#7134](https://github.com/crystal-lang/crystal/pull/7134), thanks @bcardiff) +- Refactor filename resolution logic out of `interpret_run`. ([#7051](https://github.com/crystal-lang/crystal/pull/7051), thanks @Sija) +- Refactor internals regarding overflow. ([#7262](https://github.com/crystal-lang/crystal/pull/7262), thanks @bcardiff) +- Refactor `Crystal::Codegen::Target` and consolidate triple handling. ([#7282](https://github.com/crystal-lang/crystal/pull/7282), [#7317](https://github.com/crystal-lang/crystal/pull/7317), thanks @RX14, @bcardiff) + +## Tools + +- Update README template. ([#7118](https://github.com/crystal-lang/crystal/pull/7118), thanks @mamantoha) +- Capitalise Crystal in CLI output. ([#7224](https://github.com/crystal-lang/crystal/pull/7224), thanks @dwightwatson) + +### Formatter + +- Fixed formatting of multiline literal elements. ([#7048](https://github.com/crystal-lang/crystal/pull/7048), thanks @MakeNowJust) +- Fixed formatting of heredoc with interpolations. ([#7184](https://github.com/crystal-lang/crystal/pull/7184), thanks @MakeNowJust) +- Fixed prevent conflict between nested tuple types and macro expressions. ([#7097](https://github.com/crystal-lang/crystal/pull/7097), thanks @MakeNowJust) +- Fixed format when `typeof` appears inside generic type. ([#7176](https://github.com/crystal-lang/crystal/pull/7176), thanks @asterite) +- Fixed format of newline after `&.foo` in call. ([#7240](https://github.com/crystal-lang/crystal/pull/7240), thanks @MakeNowJust) +- Honor same behaviour for single or multiple file arguments. ([#7144](https://github.com/crystal-lang/crystal/pull/7144), thanks @straight-shoota) +- Refactor remove quotes from overflow symbols in formatter spec. ([#6968](https://github.com/crystal-lang/crystal/pull/6968), thanks @r00ster91) +- Major rework of `crystal tool format` command. ([#7257](https://github.com/crystal-lang/crystal/pull/7257), thanks @MakeNowJust) + +### Doc generator + +- **(security)** Prevent XSS via args. ([#7056](https://github.com/crystal-lang/crystal/pull/7056), thanks @MakeNowJust) +- Fixed generation of toplevel. ([#7063](https://github.com/crystal-lang/crystal/pull/7063), thanks @MakeNowJust) +- Fixed display of double splat and block arg. ([#7029](https://github.com/crystal-lang/crystal/pull/7029), [#7031](https://github.com/crystal-lang/crystal/pull/7031), thanks @MakeNowJust) +- Fixed keep trailing spaces in macros. ([#7099](https://github.com/crystal-lang/crystal/pull/7099), thanks @MakeNowJust) +- Fixed avoid showing subtypes of aliased type. ([#7124](https://github.com/crystal-lang/crystal/pull/7124), thanks @asterite) +- Fixed style of methods when hovering. ([#7022](https://github.com/crystal-lang/crystal/pull/7022), thanks @r00ster91) +- Fixed duplicate `source_link` field. ([#7033](https://github.com/crystal-lang/crystal/pull/7033), thanks @bcardiff) +- Fixed missing keywords in `Doc::Highlighter`. ([#7054](https://github.com/crystal-lang/crystal/pull/7054), thanks @MakeNowJust) +- Add `--format` option to docs command. ([#6982](https://github.com/crystal-lang/crystal/pull/6982), thanks @mniak) + +## Others + +- CI improvements and housekeeping. ([#7018](https://github.com/crystal-lang/crystal/pull/7018), [#7043](https://github.com/crystal-lang/crystal/pull/7043), [#7133](https://github.com/crystal-lang/crystal/pull/7133), [#7139](https://github.com/crystal-lang/crystal/pull/7139), [#7230](https://github.com/crystal-lang/crystal/pull/7230), [#7227](https://github.com/crystal-lang/crystal/pull/7227), [#7263](https://github.com/crystal-lang/crystal/pull/7263), thanks @bcardiff) +- CI split formatting check. ([#7228](https://github.com/crystal-lang/crystal/pull/7228), thanks @bcardiff) +- Depend on standard variable to let the user define the build date. ([#7186](https://github.com/crystal-lang/crystal/pull/7186), thanks @eli-schwartz) +- Reorganize community section in README, add forum. ([#7235](https://github.com/crystal-lang/crystal/pull/7235), thanks @straight-shoota) +- Fixed docs grammar and typos. ([#7034](https://github.com/crystal-lang/crystal/pull/7034), [#7242](https://github.com/crystal-lang/crystal/pull/7242), [#7331](https://github.com/crystal-lang/crystal/pull/7331), thanks @r00ster91, @girng) +- Improve samples. ([#6454](https://github.com/crystal-lang/crystal/pull/6454), thanks @r00ster91) +- Fixed 0.27.0 CHANGELOG. ([#7024](https://github.com/crystal-lang/crystal/pull/7024), thanks @arcage) +- Update ISSUE_TEMPLATE to include forum. ([#7301](https://github.com/crystal-lang/crystal/pull/7301), thanks @straight-shoota) +- Update LICENSE's copyright year. ([#7246](https://github.com/crystal-lang/crystal/pull/7246), thanks @matiasgarciaisaia) + +# 0.27.0 (2018-11-01) + +## Language changes + +- **(breaking-change)** Disallow comma after newline in argument list. ([#6514](https://github.com/crystal-lang/crystal/pull/6514), thanks @asterite) + +### Macros + +- Add `Generic#resolve` and `Generic#resolve?` macro methods. ([#6617](https://github.com/crystal-lang/crystal/pull/6617), thanks @asterite) + +## Standard library + +- Fixed `v1`, `v2`, `v3`, `v4`, `v5` methods of `UUID`. ([#6952](https://github.com/crystal-lang/crystal/pull/6952), thanks @r00ster91) +- Fixed multiple docs typos and phrasing in multiple places. ([#6778](https://github.com/crystal-lang/crystal/pull/6778), [#6963](https://github.com/crystal-lang/crystal/pull/6963), thanks @r00ster91) +- Fixes `Pointer`/`UInt` subtraction. ([#6994](https://github.com/crystal-lang/crystal/pull/6994), thanks @damaxwell) +- Add stack overflow detection. ([#6928](https://github.com/crystal-lang/crystal/pull/6928), [#6995](https://github.com/crystal-lang/crystal/pull/6995), thanks @damaxwell) +- Add caller file and line to `Nil#not_nil!`. ([#6712](https://github.com/crystal-lang/crystal/pull/6712), thanks @yeeunmariakim) +- Restrict `Enum#parse`/`Enum#parse?` to `String` arguments. ([#6654](https://github.com/crystal-lang/crystal/pull/6654), thanks @vladfaust) +- Refactor and unify printing exceptions from within fibers. ([#6594](https://github.com/crystal-lang/crystal/pull/6594), thanks @Sija) +- Improve docs on properties generated by `property?`. ([#6682](https://github.com/crystal-lang/crystal/pull/6682), thanks @epergo) +- Add docs to top level namespace constants. ([#6971](https://github.com/crystal-lang/crystal/pull/6971), thanks @r00ster91) + +### Macros + +- Fix typos in `StringLiteral#gsub` and `#tr` errors. ([#6925](https://github.com/crystal-lang/crystal/pull/6925), thanks @r00ster91) + +### Numeric + +- **(breaking-change)** Disallow `rand` with zero value. ([#6686](https://github.com/crystal-lang/crystal/pull/6686), thanks @oprypin) +- **(breaking-change)** Let `==` and `!=` compare the values instead of bits when dealing with signed vs unsigned integers. ([#6689](https://github.com/crystal-lang/crystal/pull/6689), thanks @asterite) +- Fixed `Int#downto` with unsigned int. ([#6678](https://github.com/crystal-lang/crystal/pull/6678), thanks @gmarcais) +- Add wrapping arithmetics operators `&+` `&-` `&*`. ([#6890](https://github.com/crystal-lang/crystal/pull/6890), thanks @bcardiff) +- Add floor divisions operator `Int#//` and `Float#//`. ([#6891](https://github.com/crystal-lang/crystal/pull/6891), thanks @bcardiff) +- Add random support for `BigInt`. ([#6687](https://github.com/crystal-lang/crystal/pull/6687), thanks @oprypin) +- Add docs related to `Float::Printer::*`. ([#5438](https://github.com/crystal-lang/crystal/pull/5438), thanks @Sija) + +### Text + +- Add `String::Builder#chomp!` returns self. ([#6583](https://github.com/crystal-lang/crystal/pull/6583), thanks @Sija) +- Add `:default` to colorize and document `ColorRGB`, `Color256`. ([#6427](https://github.com/crystal-lang/crystal/pull/6427), thanks @r00ster91) +- Add `String::Formatter` support for `c` flag and improve docs. ([#6758](https://github.com/crystal-lang/crystal/pull/6758), thanks @r00ster91) + +### Collections + +- **(breaking-change)** Replace `Indexable#at` with `#fetch`. Remove `Hash#fetch(key)` as alias of `Hash#[]`. ([#6296](https://github.com/crystal-lang/crystal/pull/6296), thanks @AlexWayfer) +- Add `Hash/Indexable#dig/dig?`. ([#6719](https://github.com/crystal-lang/crystal/pull/6719), thanks @Sija) +- Add `Iterator.chain` to chain array of iterators. ([#6570](https://github.com/crystal-lang/crystal/pull/6570), thanks @xqyww123) +- Add `NamedTuple#to_h` over empty tuples. ([#6628](https://github.com/crystal-lang/crystal/pull/6628), thanks @icyleaf) +- Optimize `Indexable#join` when all elements are strings. ([#6635](https://github.com/crystal-lang/crystal/pull/6635), thanks @asterite) +- Optimize `Array#skip`. ([#6946](https://github.com/crystal-lang/crystal/pull/6946), thanks @asterite) + +### Serialization + +- Fixed `YAML::Schema::FailSafe.parse` and `parse_all`. ([#6790](https://github.com/crystal-lang/crystal/pull/6790), thanks @r00ster91) +- Fixed order of `xmlns` and prefix in `XML::Builder#namespace`. ([#6743](https://github.com/crystal-lang/crystal/pull/6743), thanks @yeeunmariakim) +- Fixed `CSV.build` quoting of `Char` and `Symbol`. ([#6904](https://github.com/crystal-lang/crystal/pull/6904), thanks @maiha) +- Fixed docs for `JSON::Serializable`. ([#6950](https://github.com/crystal-lang/crystal/pull/6950), thanks @Heaven31415) +- Add `XML::Attributes#delete`. ([#6910](https://github.com/crystal-lang/crystal/pull/6910), thanks @joenas) +- Add ability to quote values always in `CSV.build`. ([#6723](https://github.com/crystal-lang/crystal/pull/6723), thanks @maiha) +- Refactor how empty properties are handled in `JSON::Serializable` and `YAML::Serializable`. ([#6539](https://github.com/crystal-lang/crystal/pull/6539), thanks @r00ster91) + +### Time + +- **(breaking-change)** Rename `Time#epoch` to `Time#to_unix`. Also `#epoch_ms` to `#to_unix_ms`, and `#epoch_f` to `#to_unix_f`. ([#6662](https://github.com/crystal-lang/crystal/pull/6662), thanks @straight-shoota) +- Fixed spec for `Time::Location.load_local` with `TZ=nil`. ([#6740](https://github.com/crystal-lang/crystal/pull/6740), thanks @straight-shoota) +- Add support for ISO calendar week to `Time`. ([#6681](https://github.com/crystal-lang/crystal/pull/6681), thanks @straight-shoota) +- Add `Time::Format` support for `%G`, `%g`, `%V`. ([#6681](https://github.com/crystal-lang/crystal/pull/6681), thanks @straight-shoota) +- Add `Time::Location` loader support for Windows. ([#6363](https://github.com/crystal-lang/crystal/pull/6363), thanks @straight-shoota) +- Add `Time#to_local_in` to change time zone while keeping wall clock. ([#6572](https://github.com/crystal-lang/crystal/pull/6572), thanks @straight-shoota) +- Add `Time::UNIX_EPOCH` and drop private `UNIX_SECONDS` constant. ([#6908](https://github.com/crystal-lang/crystal/pull/6908), thanks @j8r) +- Change `Time::DayOfWeek` to ISO ordinal numbering based on `Monday = 1`. ([#6555](https://github.com/crystal-lang/crystal/pull/6555), thanks @straight-shoota) +- Refactor time specs. ([#6574](https://github.com/crystal-lang/crystal/pull/6574), thanks @straight-shoota) +- Add docs for singular method aliases, add `Int#microsecond` alias. ([#6297](https://github.com/crystal-lang/crystal/pull/6297), thanks @Sija) + +### Files + +- **(breaking-change)** Remove `Tempfile`. Use `File.tempfile` or `File.tempname`. ([#6485](https://github.com/crystal-lang/crystal/pull/6485), thanks @straight-shoota) +- Fixed missing closed status check of FDs when creating a subprocess. ([#6641](https://github.com/crystal-lang/crystal/pull/6641), thanks @Timbus) +- Fixed `ChecksumReader.write` error message. ([#6889](https://github.com/crystal-lang/crystal/pull/6889), thanks @r00ster91) +- Add `File#delete`, `Dir#tempdir` and improve `File` docs. ([#6485](https://github.com/crystal-lang/crystal/pull/6485), thanks @straight-shoota) +- Add `File#fsync` to flush all data written into the file to the disk device. ([#6793](https://github.com/crystal-lang/crystal/pull/6793), thanks @carlhoerberg) +- Add `DEVNULL` to docs. ([#6642](https://github.com/crystal-lang/crystal/pull/6642), thanks @r00ster91) +- Improve checks for FreeBSD version due to breaking API changes. ([#6629](https://github.com/crystal-lang/crystal/pull/6629), thanks @myfreeweb) +- Improve performance of `Zlib::Reader`, `Gzip::Reader` and `Flate::Reader` by including `IO::Buffered`. ([#6916](https://github.com/crystal-lang/crystal/pull/6916), thanks @asterite) +- Refactor `Crystal::System::FileDescriptor` to use `@fd` ivar directly. ([#6703](https://github.com/crystal-lang/crystal/pull/6703), thanks @straight-shoota) +- Refactor `{Zlib,Gzip,Flate}::Reader#unbuffered_rewind` to use `check_open`. ([#6958](https://github.com/crystal-lang/crystal/pull/6958), thanks @Sija) + +### Networking + +- **(breaking-change)** Remove deprecated alias `HTTP::Server#bind_ssl`. Use `HTTP::Server#bind_tls`. ([#6699](https://github.com/crystal-lang/crystal/pull/6699), thanks @straight-shoota) +- Add `Socket::Address#pretty_print` and `#inspect`. ([#6704](https://github.com/crystal-lang/crystal/pull/6704), thanks @straight-shoota) +- Add `Socket::IPAddress` loopback, unspecified and broadcast methods/constants. ([#6710](https://github.com/crystal-lang/crystal/pull/6710), thanks @straight-shoota) +- Fixed `Socket#reuse_port?` if `SO_REUSEPORT` is not supported. ([#6706](https://github.com/crystal-lang/crystal/pull/6706), thanks @straight-shoota) +- Fixed `TCPServer` handling of `reuse_port`. ([#6940](https://github.com/crystal-lang/crystal/pull/6940), thanks @RX14) +- Add docs to demonstrate parameters for `HTTP::Client`. ([#5145](https://github.com/crystal-lang/crystal/pull/5145), thanks @HCLarsen) +- Add docs examples to `Socket::Server#accept`. ([#6705](https://github.com/crystal-lang/crystal/pull/6705), thanks @straight-shoota) +- Refactor `socket_spec.cr` into separate files. ([#6700](https://github.com/crystal-lang/crystal/pull/6700), thanks @straight-shoota) +- Refactor specs of `HTTP::Client` to remove inheritance for test server. ([#6909](https://github.com/crystal-lang/crystal/pull/6909), thanks @straight-shoota) +- Improve specs for `HTTP::Server#close`. ([#5958](https://github.com/crystal-lang/crystal/pull/5958), thanks @straight-shoota) +- Improve specs for socket. ([#6711](https://github.com/crystal-lang/crystal/pull/6711), thanks @straight-shoota) + +### Crypto + +- Fixed OpenSSL bindings to work with LibreSSL. ([#6917](https://github.com/crystal-lang/crystal/pull/6917), thanks @LVMBDV) +- Add support for OpenSSL 1.1.1. ([#6738](https://github.com/crystal-lang/crystal/pull/6738), thanks @ysbaddaden) + +### Concurrency + +- Improve POSIX threads integration regarding locking, error and resource management. ([#6944](https://github.com/crystal-lang/crystal/pull/6944), thanks @ysbaddaden) +- Remove unintended public methods from `Channel`. ([#6714](https://github.com/crystal-lang/crystal/pull/6714), thanks @asterite) +- Refactor `Fiber`/`Scheduler` to isolate responsibilities. ([#6897](https://github.com/crystal-lang/crystal/pull/6897), thanks @ysbaddaden) +- Refactor specs that relied on `Fiber.yield` behavior. ([#6953](https://github.com/crystal-lang/crystal/pull/6953), thanks @ysbaddaden) + +### System + +- Fixed fork and signal child handlers. ([#6426](https://github.com/crystal-lang/crystal/pull/6426), thanks @ysbaddaden) +- Use blocking `IO` on a TTY if it can't be reopened. ([#6660](https://github.com/crystal-lang/crystal/pull/6660), thanks @Timbus) +- Refactor `Process` in preparation for Windows support. ([#6744](https://github.com/crystal-lang/crystal/pull/6744), thanks @RX14) + +### Spec + +- Allow `pending` to be used without blocks. ([#6732](https://github.com/crystal-lang/crystal/pull/6732), thanks @tswicegood) +- Add `be_empty` expectation. ([#6614](https://github.com/crystal-lang/crystal/pull/6614), thanks @mamantoha) +- Add specs for expectation methods. ([#6512](https://github.com/crystal-lang/crystal/pull/6512), thanks @rodrigopinto) + +## Compiler + +- Fixed don't "ambiguous match" if there's an exact match. ([#6618](https://github.com/crystal-lang/crystal/pull/6618), thanks @asterite) +- Fixed allow annotations inside enums. ([#6713](https://github.com/crystal-lang/crystal/pull/6713), thanks @asterite) +- Fixed `super` inside macros will honor arguments. ([#6638](https://github.com/crystal-lang/crystal/pull/6638), thanks @asterite) +- Fixed guessed ivar type from splat arguments. ([#6648](https://github.com/crystal-lang/crystal/pull/6648), thanks @MakeNowJust) +- Fixed `ASTNode#to_s` of non-unary operator call without argument. ([#6538](https://github.com/crystal-lang/crystal/pull/6538), thanks @MakeNowJust) +- Fixed `ASTNode#to_s` for multiline macro expression. ([#6666](https://github.com/crystal-lang/crystal/pull/6666), thanks @MakeNowJust) +- Fixed `ASTNode#to_s` for `{% verbatim do %} ... {% end %}`. ([#6665](https://github.com/crystal-lang/crystal/pull/6665), thanks @MakeNowJust) +- Fixed empty case statement normalization. ([#6915](https://github.com/crystal-lang/crystal/pull/6915), thanks @straight-shoota) +- Fixed codegen of tuple elements with unreachable elements. ([#6659](https://github.com/crystal-lang/crystal/pull/6659), thanks @MakeNowJust) +- Fixed parsing of `//` corner cases. ([#6927](https://github.com/crystal-lang/crystal/pull/6927), thanks @bcardiff) +- Fixed recursive block expansion check for non `ProcNotation` restriction. ([#6932](https://github.com/crystal-lang/crystal/pull/6932), thanks @MakeNowJust) +- Fixed corner case of expressions not typed on main phase but typed on cleanup phase. ([#6720](https://github.com/crystal-lang/crystal/pull/6720), thanks @MakeNowJust) +- Improve error traces regarding `return`, `next` and `break`. ([#6633](https://github.com/crystal-lang/crystal/pull/6633), thanks @asterite) +- Add resolve generics typenodes in macros. ([#6617](https://github.com/crystal-lang/crystal/pull/6617), thanks @asterite) +- Add support for multiple output values in inline asm. ([#6680](https://github.com/crystal-lang/crystal/pull/6680), thanks @RX14) +- Improve parsing of `asm` operands. ([#6688](https://github.com/crystal-lang/crystal/pull/6688), thanks @RX14) +- Refactor rescue block codegen for Windows. ([#6649](https://github.com/crystal-lang/crystal/pull/6649), thanks @RX14) + +## Tools + +- Improve installation section in README template. ([#6914](https://github.com/crystal-lang/crystal/pull/6914), [#6942](https://github.com/crystal-lang/crystal/pull/6942), thanks @r00ster91) +- Improve contributors section in README template. ([#7005](https://github.com/crystal-lang/crystal/pull/7005), thanks @r00ster91) + +### Formatter + +- Fixed formatting of `{% verbatim do %} ... {% end %}` outside macro. ([#6667](https://github.com/crystal-lang/crystal/pull/6667), thanks @MakeNowJust) +- Fixed formatting of `//` corner cases. ([#6927](https://github.com/crystal-lang/crystal/pull/6927), thanks @bcardiff) +- Improve formatting of `asm` operands. ([#6688](https://github.com/crystal-lang/crystal/pull/6688), thanks @RX14) + +### Doc generator + +- Add support for comments after `:nodoc:` marker. ([#6627](https://github.com/crystal-lang/crystal/pull/6627), thanks @Sija) +- Fixed browser performance issue with blur filter. ([#6764](https://github.com/crystal-lang/crystal/pull/6764), thanks @girng) +- Accessibility improvement in search field. ([#6926](https://github.com/crystal-lang/crystal/pull/6926), thanks @jodylecompte) + +## Others + +- CI improvements and housekeeping. ([#6658](https://github.com/crystal-lang/crystal/pull/6658), [#6739](https://github.com/crystal-lang/crystal/pull/6739), [#6930](https://github.com/crystal-lang/crystal/pull/6930), thanks @bcardiff, @RX14) +- Add `VERSION` file and support for specifying the build commit. ([#6966](https://github.com/crystal-lang/crystal/pull/6966), thanks @bcardiff) +- Add support for specifying the build date. ([#6788](https://github.com/crystal-lang/crystal/pull/6788), thanks @peterhoeg) +- Update Contributing section in `README.md`. ([#6911](https://github.com/crystal-lang/crystal/pull/6911), thanks @r00ster91) + +# 0.26.1 (2018-08-27) + +## Language changes + +- **(breaking-change)** Make `self` to be eager evaluated when including modules. ([#6557](https://github.com/crystal-lang/crystal/pull/6557), thanks @bcardiff) + +### Macros + +- Add `accepts_block?` macro method to `Def`. ([#6604](https://github.com/crystal-lang/crystal/pull/6604), thanks @willhbr) + +## Standard library + +### Macros + +- Fixed `Object#def_hash` can receive symbols. ([#6531](https://github.com/crystal-lang/crystal/pull/6531), thanks @Sija) + +### Collections + +- Add `Hash#transform_keys` and `Hash#transform_values`. ([#4385](https://github.com/crystal-lang/crystal/pull/4385), thanks @deepj) + +### Serialization + +- Fixed `JSON::Serializable` and `YAML::Serializable` clashing with custom initializers. ([#6458](https://github.com/crystal-lang/crystal/pull/6458), thanks @kostya) + +### Time + +- Fixed docs for `Time::Format`. ([#6578](https://github.com/crystal-lang/crystal/pull/6578), thanks @straight-shoota) + +### Files + +- Fixed zlib handling of buffer error. ([#6610](https://github.com/crystal-lang/crystal/pull/6610), thanks @asterite) + +### Networking + +- **(deprecate)** `HTTP::Server#bind_ssl` in favor of `HTTP::Server#bind_tls`. ([#6551](https://github.com/crystal-lang/crystal/pull/6551), thanks @bcardiff) +- Add tls scheme to `HTTP::Server#bind`. ([#6533](https://github.com/crystal-lang/crystal/pull/6533), thanks @straight-shoota) +- Fixed `HTTP::Server` crash with self-signed certificate. ([#6590](https://github.com/crystal-lang/crystal/pull/6590), thanks @bcardiff) +- Refactor `HTTP::Server` specs to use free ports. ([#6530](https://github.com/crystal-lang/crystal/pull/6530), thanks @straight-shoota) + +### System + +- Improve `STDIN`/`STDOUT`/`STDERR` handling to avoid breaking other programs. ([#6518](https://github.com/crystal-lang/crystal/pull/6518), thanks @Timbus) + +### Spec + +- Fixed `DotFormatter` to flush after every spec. ([#6562](https://github.com/crystal-lang/crystal/pull/6562), thanks @asterite) +- Add support for Windows. ([#6497](https://github.com/crystal-lang/crystal/pull/6497), thanks @RX14) + +## Compiler + +- Fixed evaluate yield expressions in macros. ([#6587](https://github.com/crystal-lang/crystal/pull/6587), thanks @asterite) +- Fixed presence check of named argument via external name. ([#6560](https://github.com/crystal-lang/crystal/pull/6560), thanks @asterite) +- Fixed parser error on `break when`. ([#6509](https://github.com/crystal-lang/crystal/pull/6509), thanks @asterite) +- Fixed `~` methods are now able to be called as `foo.~`. ([#6541](https://github.com/crystal-lang/crystal/pull/6541), thanks @MakeNowJust) +- Fixed parsing newline after macro control expression. ([#6607](https://github.com/crystal-lang/crystal/pull/6607), thanks @asterite) +- Refactor use enum instead of hardcoded string values for emit kinds. ([#6515](https://github.com/crystal-lang/crystal/pull/6515), thanks @bew) + +## Tools + +### Formatter + +- Fixed formatting of newline before `&.method` in call. ([#6535](https://github.com/crystal-lang/crystal/pull/6535), thanks @MakeNowJust) +- Fixed formatting of empty heredoc. ([#6567](https://github.com/crystal-lang/crystal/pull/6567), thanks @MakeNowJust) +- Fixed formatting of string literal in interpolation. ([#6568](https://github.com/crystal-lang/crystal/pull/6568), thanks @MakeNowJust) +- Fixed formatting of comments in case when. ([#6595](https://github.com/crystal-lang/crystal/pull/6595), thanks @asterite) + +### Doc generator + +- Add Menlo font family and fix ordering. ([#6602](https://github.com/crystal-lang/crystal/pull/6602), thanks @slice) + +### Playground + +- Fixed internal link. ([#6596](https://github.com/crystal-lang/crystal/pull/6596), thanks @omarroth) + +## Others + +- CI improvements and housekeeping. ([#6550](https://github.com/crystal-lang/crystal/pull/6550), [#6612](https://github.com/crystal-lang/crystal/pull/6612), thanks @bcardiff) +- Add `pkg-config` as Linux package dependency. ([distribution-scripts#16](https://github.com/crystal-lang/distribution-scripts/pull/16), thanks @bcardiff) + +# 0.26.0 (2018-08-09) + +## Language changes + +- **(breaking-change)** Revert do not collapse unions for sibling types. ([#6351](https://github.com/crystal-lang/crystal/pull/6351), thanks @asterite) +- **(breaking-change)** Constant lookup context in macro is now lexical. ([#5354](https://github.com/crystal-lang/crystal/pull/5354), thanks @MakeNowJust) +- **(breaking-change)** Evaluate instance var initializers at the metaclass level (ie: disallow using `self`). ([#6414](https://github.com/crystal-lang/crystal/pull/6414), thanks @asterite) +- **(breaking-change)** Add `//` operator parsing. NB: No behaviour is assigned to this operator yet. ([#6470](https://github.com/crystal-lang/crystal/pull/6470), thanks @bcardiff) +- Add `&+` `&-` `&*` `&**` operators parsing. NB: No behaviour is assigned to these operators yet. ([#6329](https://github.com/crystal-lang/crystal/pull/6329), thanks @bcardiff) +- Add support for empty `case` without `when`. ([#6367](https://github.com/crystal-lang/crystal/pull/6367), thanks @straight-shoota) + +### Macros + +- Add `pp!` and `p!` macro methods. ([#6374](https://github.com/crystal-lang/crystal/pull/6374), [#6476](https://github.com/crystal-lang/crystal/pull/6476), thanks @straight-shoota) + +## Standard library + +- Fix docs for `Pointer`. ([#6494](https://github.com/crystal-lang/crystal/pull/6494), thanks @fxn) +- Fix docs of `UUID` enums. ([#6496](https://github.com/crystal-lang/crystal/pull/6496), thanks @r00ster91) + +### Numeric + +- Fixed `Random#rand(Range(Float, Float))` to return `Float`. ([#6445](https://github.com/crystal-lang/crystal/pull/6445), thanks @straight-shoota) +- Add docs of big module overloads. ([#6336](https://github.com/crystal-lang/crystal/pull/6336), thanks @laginha87) + +### Text + +- **(breaking-change)** `String#from_utf16(pointer : Pointer(UInt16))` returns now `{String, Pointer(UInt16)}`. ([#6333](https://github.com/crystal-lang/crystal/pull/6333), thanks @straight-shoota) +- Add support for unicode 11.0.0. ([#6505](https://github.com/crystal-lang/crystal/pull/6505), thanks @asterite) +- Add an optional argument to `String#check_no_null_byte` to customize error message. ([#6333](https://github.com/crystal-lang/crystal/pull/6333), thanks @straight-shoota) +- Add `ECR.render` for rendering directly as `String`. ([#6371](https://github.com/crystal-lang/crystal/pull/6371), thanks @straight-shoota) +- Fix docs for `Char` ([#6487](https://github.com/crystal-lang/crystal/pull/6487), thanks @r00ster91) + +### Collections + +- Add docs for `StaticArray`. ([#6404](https://github.com/crystal-lang/crystal/pull/6404), [#6488](https://github.com/crystal-lang/crystal/pull/6488), thanks @straight-shoota, @r00ster91, @hinrik) +- Refactor `Array#concat`. ([#6493](https://github.com/crystal-lang/crystal/pull/6493), thanks @fxn) + +### Serialization + +- **(breaking-change)** Add a maximum nesting level to prevent stack overflow on `YAML::Builder` and `JSON::Builder`. ([#6322](https://github.com/crystal-lang/crystal/pull/6322), thanks @asterite) +- Fixed compatibility for libyaml 0.2.1 regarding document end marker `...`. ([#6287](https://github.com/crystal-lang/crystal/pull/6287), thanks @straight-shoota) +- Add methods and options for pull parsing or hybrid parsing to `XML::Reader`. ([#5740](https://github.com/crystal-lang/crystal/pull/5740), [#6332](https://github.com/crystal-lang/crystal/pull/6332), thanks @felixbuenemann) +- Fixed docs for `JSON::Any`, `JSON::Serialization` and `YAML::Serialization`. ([#6460](https://github.com/crystal-lang/crystal/pull/6460), [#6491](https://github.com/crystal-lang/crystal/pull/6491), thanks @delef, @bmulvihill) + + +### Time + +- **(breaking-change)** Make location a required argument for `Time.parse`. ([#6369](https://github.com/crystal-lang/crystal/pull/6369), thanks @straight-shoota) +- Add `Time.parse!`, `Time.parse_utc`, `Time.parse_local`. ([#6369](https://github.com/crystal-lang/crystal/pull/6369), thanks @straight-shoota) +- Fix docs comment missing ([#6387](https://github.com/crystal-lang/crystal/pull/6387), thanks @faustinoaq) + +### Files + +- **(breaking-change)** Remove `File.each_line` method that returns an iterator. Use `IO#each_line`. ([#6301](https://github.com/crystal-lang/crystal/pull/6301), thanks @asterite) +- Fixed `File.join` when path separator is a component argument. ([#6328](https://github.com/crystal-lang/crystal/pull/6328), thanks @icyleaf) +- Fixed `Dir.glob` can now list broken symlinks. ([#6466](https://github.com/crystal-lang/crystal/pull/6466), thanks @straight-shoota) +- Add `File` and `Dir` support for Windows. ([#5623](https://github.com/crystal-lang/crystal/pull/5623), thanks @RX14) + +### Networking + +- **(breaking-change)** Drop `HTTP::Server#tls` in favor of `HTTP::Server#bind_ssl`. ([#5960](https://github.com/crystal-lang/crystal/pull/5960), thanks @straight-shoota) +- **(breaking-change)** Rename alias `HTTP::Handler::Proc` to `HTTP::Handler::HandlerProc`. ([#6453](https://github.com/crystal-lang/crystal/pull/6453), thanks @jwoertink) +- Fixed `Socket#accept?` base implementation. ([#6277](https://github.com/crystal-lang/crystal/pull/6277), thanks @ysbaddaden) +- Fixed performance issue due to unbuffered `IO` read. `IO#sync` only affect writes, introduce `IO#read_buffering?`. ([#6304](https://github.com/crystal-lang/crystal/pull/6304), [#6474](https://github.com/crystal-lang/crystal/pull/6474), thanks @asterite, @bcardiff) +- Fixed handling of closed state in `HTTP::Server::Response`. ([#6477](https://github.com/crystal-lang/crystal/pull/6477), thanks @straight-shoota) +- Fixed change encoding name comparison to be case insensitive for UTF-8. ([#6355](https://github.com/crystal-lang/crystal/pull/6355), thanks @asterite) +- Fixed support for quoted charset value in HTTP. ([#6354](https://github.com/crystal-lang/crystal/pull/6354), thanks @asterite) +- Fixed docs regarding udp example on `Socket::Addrinfo`. ([#6388](https://github.com/crystal-lang/crystal/pull/6388), thanks @faustinoaq) +- Fixed `HTTP::Client` will set `connection: close` header on one-shot requests. ([#6410](https://github.com/crystal-lang/crystal/pull/6410), thanks @asterite) +- Fixed `OpenSSL::Digest` for multibyte strings. ([#6471](https://github.com/crystal-lang/crystal/pull/6471), thanks @RX14) +- Fixed missing `Host` header when using `HTTP::Client#exec`. ([#6481](https://github.com/crystal-lang/crystal/pull/6481), thanks @straight-shoota) +- Add `HTTP::Server#bind(URI|String)` that infers protocol from scheme. ([#6500](https://github.com/crystal-lang/crystal/pull/6500), thanks @straight-shoota) +- Add `HTTP::Params.new` and `HTTP::Params#empty?`. ([#6241](https://github.com/crystal-lang/crystal/pull/6241), thanks @icyleaf) +- Add support for multiple Etags in `If-None-Match` header for `HTTP::Request` and `HTTP::StaticFileHandler`. ([#6219](https://github.com/crystal-lang/crystal/pull/6219), thanks @straight-shoota) +- Add IDNs normalization to punycode in `OpenSSL::SSL::Socket`. ([#6306](https://github.com/crystal-lang/crystal/pull/6306), thanks @paulkass) +- Add `application/wasm` to the default MIME types of `HTTP::StaticFileHandler`. ([#6377](https://github.com/crystal-lang/crystal/pull/6377), thanks @MakeNowJust) +- Add `URI#absolute?` and `URI#relative?`. ([#6311](https://github.com/crystal-lang/crystal/pull/6311), thanks @mamantoha) + +### Crypto + +- Fixed `Crypto::Bcrypt::Password#==` was hiding `Reference#==(other)`. ([#6356](https://github.com/crystal-lang/crystal/pull/6356), thanks @straight-shoota) + +### Concurrency + +- Fixed `Atomic#swap` with reference types. ([#6428](https://github.com/crystal-lang/crystal/pull/6428), thanks @Exilor) + +### System + +- Fixed raise `Errno` if `Process.new` fails to exec. ([#6501](https://github.com/crystal-lang/crystal/pull/6501), thanks @straight-shoota, @lbguilherme) +- Add support for `WinError` UTF-16 string messages. ([#6442](https://github.com/crystal-lang/crystal/pull/6442), thanks @straight-shoota) +- Refactor platform specifics from `ENV` to `Crystal::System::Env` and implement for Windows. ([#6333](https://github.com/crystal-lang/crystal/pull/6333), [#6499](https://github.com/crystal-lang/crystal/pull/6499), thanks @straight-shoota) + +### Spec + +- Add [TAP](https://testanything.org/) formatter to spec suite. ([#6286](https://github.com/crystal-lang/crystal/pull/6286), thanks @straight-shoota) + +## Compiler + +- Fixed named arguments expansion from double splat clash with local variable names. ([#6378](https://github.com/crystal-lang/crystal/pull/6378), thanks @asterite) +- Fixed auto assigned ivars arguments expansions when clash with keywords. ([#6379](https://github.com/crystal-lang/crystal/pull/6379), thanks @asterite) +- Fixed resulting type of union of tuple metaclasses. ([#6342](https://github.com/crystal-lang/crystal/pull/6342), thanks @asterite) +- Fixed ICE when using unbound type parameter inside generic type. ([#6292](https://github.com/crystal-lang/crystal/pull/6292), thanks @asterite) +- Fixed ICE when using unions of metaclasses. ([#6307](https://github.com/crystal-lang/crystal/pull/6307), thanks @asterite) +- Fixed ICE related to literal type guessing and generic types hierarchy. ([#6341](https://github.com/crystal-lang/crystal/pull/6341), thanks @asterite) +- Fixed ICE related to `not` and inlinable values. ([#6452](https://github.com/crystal-lang/crystal/pull/6452), thanks @asterite) +- Fixed rebind variables type in while condition after analyzing its body. ([#6295](https://github.com/crystal-lang/crystal/pull/6295), thanks @asterite) +- Fixed corner cases regarding automatic casts and method instantiation. ([#6284](https://github.com/crystal-lang/crystal/pull/6284), thanks @asterite) +- Fixed parsing of `\A` (and others) inside `%r{...}` inside macros. ([#6282](https://github.com/crystal-lang/crystal/pull/6282), thanks @asterite) +- Fixed parsing of of named tuple inside generic type arguments. ([#6413](https://github.com/crystal-lang/crystal/pull/6413), thanks @asterite) +- Fixed disallow cast from module class to virtual metaclass. ([#6320](https://github.com/crystal-lang/crystal/pull/6320), thanks @asterite) +- Fixed disallow `return` inside a constant's value. ([#6347](https://github.com/crystal-lang/crystal/pull/6347), thanks @asterite) +- Fixed debug info for closured self. ([#6346](https://github.com/crystal-lang/crystal/pull/6346), thanks @asterite) +- Fixed parsing error of newline before closing macro. ([#6382](https://github.com/crystal-lang/crystal/pull/6382), thanks @asterite) +- Fixed missing error if constant has `NoReturn` type. ([#6411](https://github.com/crystal-lang/crystal/pull/6411), thanks @asterite) +- Fixed give proper error when doing sizeof uninstantiated generic type. ([#6418](https://github.com/crystal-lang/crystal/pull/6418), thanks @asterite) +- Fixed private aliases at top-level are now considered private. ([#6432](https://github.com/crystal-lang/crystal/pull/6432), thanks @asterite) +- Fixed setters with multiple arguments as now disallowed. ([#6324](https://github.com/crystal-lang/crystal/pull/6324), thanks @maxfierke) +- Fixed type var that resolves to number in restriction didn't work. ([#6504](https://github.com/crystal-lang/crystal/pull/6504), thanks @asterite) +- Add support for class variables in generic classes. ([#6348](https://github.com/crystal-lang/crystal/pull/6348), thanks @asterite) +- Add support for exception handling in Windows (SEH). ([#6419](https://github.com/crystal-lang/crystal/pull/6419), thanks @RX14) +- Refactor codegen of binary operators. ([#6330](https://github.com/crystal-lang/crystal/pull/6330), thanks @bcardiff) +- Refactor use `JSON::Serializable` instead of `JSON.mapping`. ([#6308](https://github.com/crystal-lang/crystal/pull/6308), thanks @kostya) +- Refactor `Crystal::Call#check_visibility` and extract type methods. ([#6484](https://github.com/crystal-lang/crystal/pull/6484), thanks @asterite, @bcardiff) +- Change how metaclasses are shown. Use `Foo.class` instead of `Foo:Class`. ([#6439](https://github.com/crystal-lang/crystal/pull/6439), thanks @RX14) + +## Tools + +- Flatten project structure created by `crystal init`. ([#6317](https://github.com/crystal-lang/crystal/pull/6317), thanks @straight-shoota) + +### Formatter + +- Fixed formatting of `{ {1}.foo, ...}` like expressions. ([#6300](https://github.com/crystal-lang/crystal/pull/6300), thanks @asterite) +- Fixed formatting of `when` with numbers. Use right alignment only if all are number literals. ([#6392](https://github.com/crystal-lang/crystal/pull/6392), thanks @MakeNowJust) +- Fixed formatting of comment in case's else. ([#6393](https://github.com/crystal-lang/crystal/pull/6393), thanks @MakeNowJust) +- Fixed code fence when language is not crystal will not be formatted. ([#6424](https://github.com/crystal-lang/crystal/pull/6424), thanks @asterite) + +### Doc generator + +- Add line numbers at link when there are duplicated filenames in "Defined in:" section. ([#6280](https://github.com/crystal-lang/crystal/pull/6280), [#6489](https://github.com/crystal-lang/crystal/pull/6489), thanks @r00ster91) +- Fix docs navigator not scrolling into open type on page load. ([#6420](https://github.com/crystal-lang/crystal/pull/6420), thanks @soanvig) + +## Others + +- Fixed `system_spec` does no longer emit errors messages on BSD platforms. ([#6289](https://github.com/crystal-lang/crystal/pull/6289), thanks @jcs) +- Fixed compilation issue when running spec against compiler and std together. ([#6312](https://github.com/crystal-lang/crystal/pull/6312), thanks @straight-shoota) +- Add support for LLVM 6.0. ([#6381](https://github.com/crystal-lang/crystal/pull/6381), [#6380](https://github.com/crystal-lang/crystal/pull/6380), [#6383](https://github.com/crystal-lang/crystal/pull/6383), thanks @felixbuenemann) +- CI improvements and housekeeping. ([#6313](https://github.com/crystal-lang/crystal/pull/6313), [#6337](https://github.com/crystal-lang/crystal/pull/6337), [#6407](https://github.com/crystal-lang/crystal/pull/6407), [#6408](https://github.com/crystal-lang/crystal/pull/6408), [#6315](https://github.com/crystal-lang/crystal/pull/6315), thanks @bcardiff, @MakeNowJust, @r00ster91, @maiha) + +# 0.25.1 (2018-06-27) + +## Standard library + +### Macros +- Fixed `Object.delegate` is now able to be used with `[]=` methods. ([#6178](https://github.com/crystal-lang/crystal/pull/6178), thanks @straight-shoota) +- Fixed `p!` `pp!` are now able to be used with tuples. ([#6244](https://github.com/crystal-lang/crystal/pull/6244), thanks @bcardiff) +- Add `#copy_with` method to structs generated by `record` macro. ([#5736](https://github.com/crystal-lang/crystal/pull/5736), thanks @chris-baynes) +- Add docs for `ArrayLiteral#push` and `#unshift`. ([#6232](https://github.com/crystal-lang/crystal/pull/6232), thanks @MakeNowJust) + +### Collections +- Add docs for `Indexable#zip` and `#zip?` methods. ([#5734](https://github.com/crystal-lang/crystal/pull/5734), thanks @rodrigopinto) + +### Serialization +- Add `#dup` and `#clone` for `JSON::Any` and `YAML::Any`. ([6266](https://github.com/crystal-lang/crystal/pull/6266), thanks @asterite) +- Add docs example of nesting mappings to `YAML.builder`. ([#6097](https://github.com/crystal-lang/crystal/pull/6097), thanks @kalinon) + +### Time +- Fixed docs regarding formatting and parsing `Time`. ([#6208](https://github.com/crystal-lang/crystal/pull/6208), [#6214](https://github.com/crystal-lang/crystal/pull/6214), thanks @r00ster91 and @straight-shoota) +- Fixed `Time` internals for future Windows support. ([#6181](https://github.com/crystal-lang/crystal/pull/6181), thanks @RX14) +- Add `Time::Span#microseconds`, `Int#microseconds` and `Float#microseconds`. ([#6272](https://github.com/crystal-lang/crystal/pull/6272), thanks @asterite) +- Add specs. ([#6174](https://github.com/crystal-lang/crystal/pull/6174), thanks @straight-shoota) + +### Files +- Fixed `File.extname` edge case. ([#6234](https://github.com/crystal-lang/crystal/pull/6234), thanks @bcardiff) +- Fixed `FileInfo#flags` return value. ([#6248](https://github.com/crystal-lang/crystal/pull/6248), thanks @fgimian) + +### Networking +- Fixed `IO#write(slice : Bytes)` won't write information if slice is empty. ([#6269](https://github.com/crystal-lang/crystal/pull/6269), thanks @asterite) +- Fixed docs regarding `HTTP::Server#bind_tcp` method. ([#6179](https://github.com/crystal-lang/crystal/pull/6179), [#6233](https://github.com/crystal-lang/crystal/pull/6233), thanks @straight-shoota and @MakeNowJust) +- Add Etag support in `HTTP::StaticFileHandler`. ([#6145](https://github.com/crystal-lang/crystal/pull/6145), thanks @emq) + +### Misc +- Fixed `mmap` usage on OpenBSD 6.3+. ([#6250](https://github.com/crystal-lang/crystal/pull/6250), thanks @jcs) +- Fixed `big/big_int`, `big/big_float`, etc are now able to be included directly. ([#6267](https://github.com/crystal-lang/crystal/pull/6267), thanks @asterite) +- Refactor dependency in `Crystal::Hasher` to avoid load order issues. ([#6184](https://github.com/crystal-lang/crystal/pull/6184), thanks @ysbaddaden) + +## Compiler +- Fixed a leakage of unbounded generic type variable and show error. ([#6128](https://github.com/crystal-lang/crystal/pull/6128), thanks @asterite) +- Fixed error message when lookup of library fails and lib's name contains non-alpha chars. ([#6187](https://github.com/crystal-lang/crystal/pull/6187), thanks @oprypin) +- Fixed integer kind deduction for very large negative numbers. ([#6182](https://github.com/crystal-lang/crystal/pull/6182), thanks @rGradeStd) +- Refactor specs tempfiles and data files usage in favor of portability ([#5951](https://github.com/crystal-lang/crystal/pull/5951), thanks @straight-shoota) +- Improve formatting and information in some compiler error messages. ([#6261](https://github.com/crystal-lang/crystal/pull/6261), thanks @RX14) + +## Tools + +### Formatter +- Fixed crash when semicolon after block paren were present. ([#6192](https://github.com/crystal-lang/crystal/pull/6192), thanks @MakeNowJust) +- Fixed invalid code produced when heredoc and comma were present. ([#6222](https://github.com/crystal-lang/crystal/pull/6222), thanks @straight-shoota and @MakeNowJust) +- Fixed crash when one-liner `begin`/`rescue` were present. ([#6274](https://github.com/crystal-lang/crystal/pull/6274), thanks @asterite) + +### Doc generator +- Fixed JSON export that prevent jumping to constant. ([#6218](https://github.com/crystal-lang/crystal/pull/6218), thanks @straight-shoota) +- Fixed crash when virtual types were reached. ([#6246](https://github.com/crystal-lang/crystal/pull/6246), thanks @bcardiff) + +## Misc + +- CI improvements and housekeeping. ([#6193](https://github.com/crystal-lang/crystal/pull/6193), [#6211](https://github.com/crystal-lang/crystal/pull/6211), [#6209](https://github.com/crystal-lang/crystal/pull/6209), [#6221](https://github.com/crystal-lang/crystal/pull/6221), [#6260](https://github.com/crystal-lang/crystal/pull/6260), thanks @bcardiff, @kostya and @r00ster91) +- Update man page. ([#6259](https://github.com/crystal-lang/crystal/pull/6259), thanks @docelic) + +# 0.25.0 (2018-06-11) + +## New features and breaking changes +- **(breaking-change)** Time zones has been added to `Time`. ([#5324](https://github.com/crystal-lang/crystal/pull/5324), [#5819](https://github.com/crystal-lang/crystal/pull/5819), thanks @straight-shoota) +- **(breaking-change)** Drop `HTTP.rfc1123_date` in favor of `HTTP.format_time` and add time format implementations for ISO-8601, RFC-3339, and RFC-2822. ([#5123](https://github.com/crystal-lang/crystal/pull/5123), thanks @straight-shoota) +- **(breaking-change)** `crystal deps` is removed, use `shards`. ([#5544](https://github.com/crystal-lang/crystal/pull/5544), thanks @asterite) +- **(breaking-change)** `Hash#key` was renamed as `Hash#key_for`. ([#5444](https://github.com/crystal-lang/crystal/pull/5444), thanks @marksiemers) +- **(breaking-change)** `JSON::Any` and `YAML::Any` have been re-implemented solving some inconsistencies and avoiding the usage of recursive aliases (`JSON::Type` and `YAML::Type` have been removed). ([#5183](https://github.com/crystal-lang/crystal/pull/5183), thanks @asterite) +- **(breaking-change)** Multiple heredocs can be used as arguments and methods can be invoked writing them in the initial delimiter, also empty heredocs are now supported. ([#5578](https://github.com/crystal-lang/crystal/pull/5578), [#5602](https://github.com/crystal-lang/crystal/pull/5602), [#6048](https://github.com/crystal-lang/crystal/pull/6048), thanks @asterite and @MakeNowJust) +- **(breaking-change)** Refactor signal handlers and avoid closing pipe at exit. ([#5730](https://github.com/crystal-lang/crystal/pull/5730), thanks @ysbaddaden) +- **(breaking-change)** Improve behaviour of `File.join` with empty path component. ([#5915](https://github.com/crystal-lang/crystal/pull/5915), thanks @straight-shoota) +- **(breaking-change)** Drop `Colorize#push` in favor of `Colorize#surround` and allow nested calls across the stack. ([#4196](https://github.com/crystal-lang/crystal/pull/4196), thanks @MakeNowJust) +- **(breaking-change)** `File.stat` was renamed to `File.info` and a more portable API was implemented. ([#5584](https://github.com/crystal-lang/crystal/pull/5584), [#6161](https://github.com/crystal-lang/crystal/pull/6161), thanks @RX14 and @bcardiff) +- **(breaking-change)** Refactor `HTTP::Server` to bind to multiple addresses. ([#5776](https://github.com/crystal-lang/crystal/pull/5776), [#5959](https://github.com/crystal-lang/crystal/pull/5959), thanks @straight-shoota) +- **(breaking-change)** Remove block argument from `loop`. ([#6026](https://github.com/crystal-lang/crystal/pull/6026), thanks @asterite) +- **(breaking-change)** Do not collapse unions for sibling types. ([#6024](https://github.com/crystal-lang/crystal/pull/6024), thanks @asterite) +- **(breaking-change)** Disallow `typeof` in type restrictions. ([#5192](https://github.com/crystal-lang/crystal/pull/5192), thanks @asterite) +- **(breaking-change)** Perform unbuffered read when `IO::Buffered#sync = true`. ([#5849](https://github.com/crystal-lang/crystal/pull/5849), thanks @RX14) +- **(breaking-change)** Drop `when _` support. ([#6150](https://github.com/crystal-lang/crystal/pull/6150), thanks @MakeNowJust) +- **(breaking-change)** The `DivisionByZero` exception was renamed to `DivisionByZeroError`. ([#5395](https://github.com/crystal-lang/crystal/pull/5395), thanks @sdogruyol) +- A bootstrap Windows port has been added to the standard library. It's not usable for real programs yet. ([#5339](https://github.com/crystal-lang/crystal/pull/5339), [#5484](https://github.com/crystal-lang/crystal/pull/5484), [#5448](https://github.com/crystal-lang/crystal/pull/5448), thanks @RX14) +- Add automatic casts on literals arguments for numbers and enums. ([#6074](https://github.com/crystal-lang/crystal/pull/6074), thanks @asterite) +- Add user defined annotations. ([#6063](https://github.com/crystal-lang/crystal/pull/6063), [#6084](https://github.com/crystal-lang/crystal/pull/6084), [#6106](https://github.com/crystal-lang/crystal/pull/6106), thanks @asterite) +- Add macro verbatim blocks to avoid nested macros. ([#6108](https://github.com/crystal-lang/crystal/pull/6108), thanks @asterite) +- Allow namespaced expressions to define constants eg: `Foo::Bar = 1`. ([#5883](https://github.com/crystal-lang/crystal/pull/5883), thanks @bew) +- Allow trailing `=` in symbol literals. ([#5969](https://github.com/crystal-lang/crystal/pull/5969), thanks @straight-shoota) +- Allow redefining `None` to `0` for `@[Flags]` enum. ([#6160](https://github.com/crystal-lang/crystal/pull/6160), thanks @bew) +- Suggest possible solutions to failing requires. ([#5487](https://github.com/crystal-lang/crystal/pull/5487), thanks @RX14) +- Allow pointers of external C library global variables. ([#4845](https://github.com/crystal-lang/crystal/pull/4845), thanks @larubujo) +- Decouple pretty-printing (`pp`) and showing the expression (`!`): `p`, `pp`, `p!`, `pp!`. ([#6044](https://github.com/crystal-lang/crystal/pull/6044), thanks @asterite) +- Add ivars default value reflection in macros. ([#5974](https://github.com/crystal-lang/crystal/pull/5974), thanks @asterite) +- Add argless overload to `Number#round` to rounds to the nearest whole number. ([#5397](https://github.com/crystal-lang/crystal/pull/5397), thanks @Sija) +- Add `Int#bits_set?` to easily check that certain bits are set. ([#5619](https://github.com/crystal-lang/crystal/pull/5619), thanks @RX14) +- Add `Float32` and `Float64` constants. ([#4787](https://github.com/crystal-lang/crystal/pull/4787), thanks @konovod) +- Add allocated bytes per operation in `Benchmark.ips`. ([#5522](https://github.com/crystal-lang/crystal/pull/5522), thanks @asterite) +- Add `String#to_utf16` and `String.from_utf16`. ([#5541](https://github.com/crystal-lang/crystal/pull/5541), [#5579](https://github.com/crystal-lang/crystal/pull/5579), [#5583](https://github.com/crystal-lang/crystal/pull/5583) thanks @asterite, @RX14 and @straight-shoota) +- Add `String#starts_with?(re: Regex)`. ([#5485](https://github.com/crystal-lang/crystal/pull/5485), thanks @MakeNowJust) +- Add `Regex.needs_escape?`. ([#5962](https://github.com/crystal-lang/crystal/pull/5962), thanks @Sija) +- Add `Hash#last_key` and `Hash#last_value`. ([#5760](https://github.com/crystal-lang/crystal/pull/5760), thanks @j8r) +- Add no-copy iteration to `Indexable`. ([#4584](https://github.com/crystal-lang/crystal/pull/4584), thanks @cjgajard) +- Add `Time#at_{beginning,end}_of_second` ([#6167](https://github.com/crystal-lang/crystal/pull/6167), thanks @straight-shoota) +- Add `IO::Stapled` to combine two unidirectional `IO`s into a single bidirectional one. ([#6017](https://github.com/crystal-lang/crystal/pull/6017), thanks @straight-shoota) +- Add context to errors in `JSON.mapping` generated code. ([#5932](https://github.com/crystal-lang/crystal/pull/5932), thanks @straight-shoota) +- Add `JSON::Serializable` and `YAML::Serializable` attribute powered mappings. ([#6082](https://github.com/crystal-lang/crystal/pull/6082), thanks @kostya) +- Add `mode` param to `File.write`. ([#5754](https://github.com/crystal-lang/crystal/pull/5754), thanks @woodruffw) +- Add Punycode/IDNA support and integrate with DNS lookup. ([#2543](https://github.com/crystal-lang/crystal/pull/2543), thanks @MakeNowJust) +- Add `HTTP::Client#options` method. ([#5824](https://github.com/crystal-lang/crystal/pull/5824), thanks @mamantoha) +- Add support for `Last-Modified` and other cache improvements to `HTTP::StaticFileHandler`. ([#2470](https://github.com/crystal-lang/crystal/pull/2470), [#5607](https://github.com/crystal-lang/crystal/pull/5607), thanks @bebac and @straight-shoota) +- Add operations and improvements related to `BigDecimal` and `BigFloat`. ([#5437](https://github.com/crystal-lang/crystal/pull/5437), [#5390](https://github.com/crystal-lang/crystal/pull/5390), [#5589](https://github.com/crystal-lang/crystal/pull/5589), [#5582](https://github.com/crystal-lang/crystal/pull/5582), [#5638](https://github.com/crystal-lang/crystal/pull/5638), [#5675](https://github.com/crystal-lang/crystal/pull/5675), thanks @Sija and @mjago) +- Add `BigDecimal` and `UUID` JSON support. ([#5525](https://github.com/crystal-lang/crystal/pull/5525), [#5551](https://github.com/crystal-lang/crystal/pull/5551), thanks @lukeasrodgers and @lachlan) +- Add missing `UUID#inspect`. ([#5574](https://github.com/crystal-lang/crystal/pull/5574), thanks @ngsankha) +- Add `Logger` configuration in initializer. ([#5618](https://github.com/crystal-lang/crystal/pull/5618), thanks @Sija) +- Add custom separators in `CSV.build`. ([#5998](https://github.com/crystal-lang/crystal/pull/5998), [#6008](https://github.com/crystal-lang/crystal/pull/6008) thanks @Sija) +- Add `INI.build` to emit `INI` files. ([#5298](https://github.com/crystal-lang/crystal/pull/5298), thanks @j8r) +- Add `Process.chroot`. ([#5577](https://github.com/crystal-lang/crystal/pull/5577), thanks @chris-huxtable) +- Add `Tempfile.tempname` to create likely nonexisting filenames. ([#5360](https://github.com/crystal-lang/crystal/pull/5360), thanks @woodruffw) +- Add `FileUtils#ln`, `ln_s`, and `ln_sf`. ([#5421](https://github.com/crystal-lang/crystal/pull/5421), thanks @woodruffw) +- Add support 8bit and true color to `Colorize`. ([#5902](https://github.com/crystal-lang/crystal/pull/5902), thanks @MakeNowJust) +- Add comparison operators between classes. ([#5645](https://github.com/crystal-lang/crystal/pull/5645), thanks @asterite) +- Add exception cause in backtrace. ([#5833](https://github.com/crystal-lang/crystal/pull/5833), thanks @RX14) +- Add unhandled exception as argument in `at_exit`. ([#5906](https://github.com/crystal-lang/crystal/pull/5906), thanks @MakeNowJust) +- Add support to target aarch64-linux-musl. ([#5861](https://github.com/crystal-lang/crystal/pull/5861), thanks @jirutka) +- Add `#clear` method to `ArrayLiteral`/`HashLiteral` for macros. ([#5265](https://github.com/crystal-lang/crystal/pull/5265), thanks @Sija) +- Add `Bool#to_unsafe` for C bindings. ([#5465](https://github.com/crystal-lang/crystal/pull/5465), thanks @woodruffw) +- Spec: Add expectations `starts_with`, `ends_with`. ([#5881](https://github.com/crystal-lang/crystal/pull/5881), thanks @kostya) +- Formatter: Add `--include` and `--exclude` options to restrict directories. ([#4635](https://github.com/crystal-lang/crystal/pull/4635), thanks @straight-shoota) +- Documentation generator: improved navigation, searching, rendering and SEO. ([#5229](https://github.com/crystal-lang/crystal/pull/5229), [#5795](https://github.com/crystal-lang/crystal/pull/5795), [#5990](https://github.com/crystal-lang/crystal/pull/5990), [#5657](https://github.com/crystal-lang/crystal/pull/5657), [#6073](https://github.com/crystal-lang/crystal/pull/6073), thanks @straight-shoota, @Sija and @j8r) +- Playground: Add button in playground to run formatter. ([#3652](https://github.com/crystal-lang/crystal/pull/3652), thanks @samueleaton) + +## Standard library bugs fixed +- Fixed `String#sub` handling of negative indexes. ([#5491](https://github.com/crystal-lang/crystal/pull/5491), thanks @MakeNowJust) +- Fixed `String#gsub` in non-ascii strings. ([#5350](https://github.com/crystal-lang/crystal/pull/5350), thanks @straight-shoota) +- Fixed `String#dump` for UTF-8 characters higher than `\uFFFF`. ([#5668](https://github.com/crystal-lang/crystal/pull/5668), thanks @straight-shoota) +- Fixed `String#tr` edge case optimization bug. ([#5913](https://github.com/crystal-lang/crystal/pull/5913), thanks @MakeNowJust) +- Fixed `String#rindex` when called with `Regex`. ([#5594](https://github.com/crystal-lang/crystal/pull/5594), thanks @straight-shoota) +- Fixed `Time::Span` precision loss and boundary check. ([#5563](https://github.com/crystal-lang/crystal/pull/5563), [#5786](https://github.com/crystal-lang/crystal/pull/5786), thanks @petoem and @straight-shoota) +- `Array#sample` was fixed to use the provided random number generator (instead of the default) in all cases. ([#5419](https://github.com/crystal-lang/crystal/pull/5419), thanks @c910335) +- Add short-circuit logic in `Deque#rotate!` for singleton and empty queues. ([#5399](https://github.com/crystal-lang/crystal/pull/5399), thanks @willcosgrove) +- `Slice#reverse!` was optimised to be up to 43% faster. ([#5401](https://github.com/crystal-lang/crystal/pull/5401), thanks @larubujo) +- Fixed `Regex#inspect` when escaping was needed. ([#5841](https://github.com/crystal-lang/crystal/pull/5841), thanks @MakeNowJust) +- Fixed `JSON.mapping` now generates type restriction on getters. ([#5935](https://github.com/crystal-lang/crystal/pull/5935), thanks @Daniel-Worrall) +- Fixed `JSON.mapping` documentation regarding unions. ([#5483](https://github.com/crystal-lang/crystal/pull/5483), thanks @RX14) +- Fixed `JSON.mapping` and `YAML.mapping` to allow `properties` property. ([#5180](https://github.com/crystal-lang/crystal/pull/5180), [#5352](https://github.com/crystal-lang/crystal/pull/5352), thanks @maxpowa and @Sija) +- Fixed `YAML` int and float parsing. ([#5699](https://github.com/crystal-lang/crystal/pull/5699), [#5774](https://github.com/crystal-lang/crystal/pull/5774), thanks @straight-shoota) +- Fixed WebSocket handshake validation. ([#5327](https://github.com/crystal-lang/crystal/pull/5327), [#6027](https://github.com/crystal-lang/crystal/pull/6027) thanks @straight-shoota) +- Fixed `HTTP::Client` is able to use ipv6 addresses. ([#6147](https://github.com/crystal-lang/crystal/pull/6147), thanks @bcardiff) +- Fixed handling some invalid responses in `HTTP::Client`. ([#5630](https://github.com/crystal-lang/crystal/pull/5630), thanks @straight-shoota) +- Fixed `HTTP::ChunkedContent` will raise on unterminated content. ([#5928](https://github.com/crystal-lang/crystal/pull/5928), [#5943](https://github.com/crystal-lang/crystal/pull/5943), thanks @straight-shoota) +- `URI#to_s` now handles default ports for lots of schemes. ([#5233](https://github.com/crystal-lang/crystal/pull/5233), thanks @lachlan) +- `HTTP::Cookies` is able to deal with spaces in cookies. ([#5408](https://github.com/crystal-lang/crystal/pull/5408), thanks @bararchy) +- Fixed MIME type of SVG images in `HTTP::StaticFileHandler`. ([#5605](https://github.com/crystal-lang/crystal/pull/5605), thanks @damianham) +- Fixed URI encoding in `StaticFileHandler#redirect_to`. ([#5628](https://github.com/crystal-lang/crystal/pull/5628), thanks @straight-shoota) +- Fixed `before_request` callbacks to be executed right before writing the request in `HTTP::Client`. ([#5626](https://github.com/crystal-lang/crystal/pull/5626), thanks @asterite) +- `Dir.glob` was re-implemented with performance improvements and edge cases fixed. ([#5179](https://github.com/crystal-lang/crystal/pull/5179), thanks @straight-shoota) +- Fixed `File.extname` edge case for '.' in path with no extension. ([#5790](https://github.com/crystal-lang/crystal/pull/5790), thanks @codyjb) +- Some ECDHE curves were incorrectly disabled in `OpenSSL` clients, this has been fixed. ([#5494](https://github.com/crystal-lang/crystal/pull/5494), thanks @jhass) +- Fixed allow bcrypt passwords up to 71 bytes. ([#5356](https://github.com/crystal-lang/crystal/pull/5356), thanks @ysbaddaden) +- Unhandled exceptions occurring inside `Process.fork` now print their backtrace correctly. ([#5431](https://github.com/crystal-lang/crystal/pull/5431), thanks @RX14) +- Fixed `Zip` no longer modifies deflate signature. ([#5376](https://github.com/crystal-lang/crystal/pull/5376), thanks @luislavena) +- Fixed `INI` parser edge cases and performance improvements. ([#5442](https://github.com/crystal-lang/crystal/pull/5442), [#5718](https://github.com/crystal-lang/crystal/pull/5718) thanks @woodruffw, @j8r) +- Fixed initialization of `LibXML`. ([#5587](https://github.com/crystal-lang/crystal/pull/5587), thanks @lbguilherme) +- Some finalizers were missing for example when the object where cloned. ([#5367](https://github.com/crystal-lang/crystal/pull/5367), thanks @alexbatalov) +- Fixed sigfault handler initialization regarding `sa_mask`. ([#5677](https://github.com/crystal-lang/crystal/pull/5677) thanks @ysbaddaden) +- Fixed missing reference symbol in ARM. ([#5640](https://github.com/crystal-lang/crystal/pull/5640), thanks @blankoworld) +- Fixed detect LLVM 5.0 by `llvm-config-5.0` command. ([#5531](https://github.com/crystal-lang/crystal/pull/5531), thanks @Vexatos) +- Restore STDIN|OUT|ERR blocking state on exit. ([#5802](https://github.com/crystal-lang/crystal/pull/5802), thanks @bew) +- Fixed multiple `at_exit` handlers chaining. ([#5413](https://github.com/crystal-lang/crystal/pull/5413), thanks @bew) +- Fixed senders were not notified when channels were closed. ([#5880](https://github.com/crystal-lang/crystal/pull/5880), thanks @carlhoerberg) +- Fixed forward unhandled exception to caller in `parallel` macro. ([#5726](https://github.com/crystal-lang/crystal/pull/5726), thanks @lipanski) +- Fixed Markdown parsing of code fences appearing on the same line. ([#5606](https://github.com/crystal-lang/crystal/pull/5606), thanks @oprypin) +- Fixed OpenSSL bindings to recognize LibreSSL. ([#5676](https://github.com/crystal-lang/crystal/pull/5676), [#6062](https://github.com/crystal-lang/crystal/pull/6062), [#5949](https://github.com/crystal-lang/crystal/pull/5949), [#5973](https://github.com/crystal-lang/crystal/pull/5973) thanks @LVMBDV and @RX14) +- Fixed path value in to `UNIXSocket` created by `UNIXServer`. ([#5869](https://github.com/crystal-lang/crystal/pull/5869), thanks @straight-shoota) +- Fixed `Object.delegate` over setters. ([#5964](https://github.com/crystal-lang/crystal/pull/5964), thanks @straight-shoota) +- Fixed `pp` will now use the same width on every line. ([#5978](https://github.com/crystal-lang/crystal/pull/5978), thanks @MakeNowJust) +- Fixes missing stdarg.cr for i686-linux-musl. ([#6120](https://github.com/crystal-lang/crystal/pull/6120), thanks @bcardiff) +- Spec: Fixed junit spec formatter to emit the correct XML. ([#5463](https://github.com/crystal-lang/crystal/pull/5463), thanks @hanneskaeufler) + +## Compiler bugs fixed +- Fixed enum generated values when a member has value 0. ([#5954](https://github.com/crystal-lang/crystal/pull/5954), thanks @bew) +- Fixed compiler issue when previous compilation was interrupted. ([#5585](https://github.com/crystal-lang/crystal/pull/5585), thanks @asterite) +- Fixed compiler error with an empty `ensure` block. ([#5396](https://github.com/crystal-lang/crystal/pull/5396), thanks @MakeNowJust) +- Fixed parsing regex in default arguments. ([#5481](https://github.com/crystal-lang/crystal/pull/5481), thanks @MakeNowJust) +- Fixed parsing error of regex literal after open parenthesis. ([#5453](https://github.com/crystal-lang/crystal/pull/5453), thanks @MakeNowJust) +- Fixed parsing of empty array with blank. ([#6107](https://github.com/crystal-lang/crystal/pull/6107), thanks @asterite) +- Static libraries are now found correctly when using the `--static` compiler flag. ([#5385](https://github.com/crystal-lang/crystal/pull/5385), thanks @jreinert) +- Improve error messages for unterminated literals. ([#5409](https://github.com/crystal-lang/crystal/pull/5409), thanks @straight-shoota) +- Fixed `ProcNotation` and `ProcLiteral` introspection in macros. ([#5206](https://github.com/crystal-lang/crystal/pull/5206), thanks @javanut13) +- Cross compilation honors `--emit` and avoid generating `bc_flags` in current directory. ([#5521](https://github.com/crystal-lang/crystal/pull/5521), thanks @asterite) +- Fixed compiler error with integer constants as generic arguments. ([#5532](https://github.com/crystal-lang/crystal/pull/5532), thanks @asterite) +- Fixed compiler error with self as base class. ([#5534](https://github.com/crystal-lang/crystal/pull/5534), thanks @asterite) +- Fixed macro expansion when mutating the argument. ([#5247](https://github.com/crystal-lang/crystal/pull/5247), thanks @MakeNowJust) +- Fixed macro expansion edge cases. ([#5680](https://github.com/crystal-lang/crystal/pull/5680), [#5842](https://github.com/crystal-lang/crystal/pull/5842), [#6163](https://github.com/crystal-lang/crystal/pull/6163), thanks @asterite, @MakeNowJust and @splattael) +- Fixed macro overload on named args. ([#5808](https://github.com/crystal-lang/crystal/pull/5808), thanks @bew) +- Fixed macro numeric types used in interpreter. ([#5972](https://github.com/crystal-lang/crystal/pull/5972), thanks @straight-shoota) +- Fixed missing debug locations in several places. ([#5597](https://github.com/crystal-lang/crystal/pull/5597), thanks @asterite) +- Fixed missing information in AST nodes needed for macro expansion. ([#5454](https://github.com/crystal-lang/crystal/pull/5454), thanks @MakeNowJust) +- Fixed multiline error messages in emitted by `ASTNode#raise` macro method. ([#5670](https://github.com/crystal-lang/crystal/pull/5670), thanks @asterite) +- Fixed nested delimiters and escaped whitespace in string/symbol array literals. ([#5667](https://github.com/crystal-lang/crystal/pull/5667), thanks @straight-shoota) +- Fixed custom array/hash-like literals in nested modules. ([#5685](https://github.com/crystal-lang/crystal/pull/5685), thanks @asterite) +- Fixed usage of static array in C externs. ([#5690](https://github.com/crystal-lang/crystal/pull/5690), thanks @asterite) +- Fixed `spawn` over expression with receivers. ([#5781](https://github.com/crystal-lang/crystal/pull/5781), thanks @straight-shoota) +- Fixed prevent heredoc inside interpolation. ([#5648](https://github.com/crystal-lang/crystal/pull/5648), thanks @MakeNowJust) +- Fixed parsing error when a newline follows block arg. ([#5737](https://github.com/crystal-lang/crystal/pull/5737), thanks @bew) +- Fixed parsing error when macro argument is followed by a newline. ([#6046](https://github.com/crystal-lang/crystal/pull/6046), thanks @asterite) +- Fixed compiler error messages wording. ([#5887](https://github.com/crystal-lang/crystal/pull/5887), thanks @r00ster91) +- Fixed recursion issues in `method_added` macro hook. ([#5159](https://github.com/crystal-lang/crystal/pull/5159), thanks @MakeNowJust) +- Fixed avoid using type of updated argument for type inference. ([#5166](https://github.com/crystal-lang/crystal/pull/5166), thanks @MakeNowJust) +- Fixed parsing error message on unbalanced end brace in macros. ([#5420](https://github.com/crystal-lang/crystal/pull/5420), thanks @MakeNowJust) +- Fixed parsing error message on keywords are used as arguments. ([#5930](https://github.com/crystal-lang/crystal/pull/5930), [#6052](https://github.com/crystal-lang/crystal/pull/6052), thanks @MakeNowJust and @esse) +- Fixed parsing error message on missing comma for named tuples. ([#5981](https://github.com/crystal-lang/crystal/pull/5981), thanks @MakeNowJust) +- Fixed missing handling of `cond` node in visitor. ([#6032](https://github.com/crystal-lang/crystal/pull/6032), thanks @veelenga) +- Fixed cli when `--threads` has invalid value. ([#6039](https://github.com/crystal-lang/crystal/pull/6039), thanks @r00ster91) +- Fixed private methods can now be called with explicit `self` receiver. ([#6075](https://github.com/crystal-lang/crystal/pull/6075), thanks @MakeNowJust) +- Fixed missing some missing rules of initializer in initializers macro methods. ([#6077](https://github.com/crystal-lang/crystal/pull/6077), thanks @asterite) +- Fixed regression bug related to unreachable code. ([#6045](https://github.com/crystal-lang/crystal/pull/6045), thanks @asterite) + +## Tools bugs fixed +- Several `crystal init` and template improvements. ([#5475](https://github.com/crystal-lang/crystal/pull/5475), [#5355](https://github.com/crystal-lang/crystal/pull/5355), [#4691](https://github.com/crystal-lang/crystal/pull/4691), [#5788](https://github.com/crystal-lang/crystal/pull/5788), [#5644](https://github.com/crystal-lang/crystal/pull/5644), [#6031](https://github.com/crystal-lang/crystal/pull/6031) thanks @woodruffw, @faustinoaq, @bew, @kostya and @MakeNowJust) +- Formatter: improve formatting of method call arguments with trailing comments. ([#5492](https://github.com/crystal-lang/crystal/pull/5492), thanks @MakeNowJust) +- Formatter: fix formatting of multiline statements. ([#5234](https://github.com/crystal-lang/crystal/pull/5234), [#5901](https://github.com/crystal-lang/crystal/pull/5901), [#6013](https://github.com/crystal-lang/crystal/pull/6013) thanks @MakeNowJust) +- Formatter: fix formatting of multi assignment. ([#5452](https://github.com/crystal-lang/crystal/pull/5452), thanks @MakeNowJust) +- Formatter: fix formatting of backslash ending statements. ([#5194](https://github.com/crystal-lang/crystal/pull/5194), thanks @asterite) +- Formatter: fix formatting of `.[]` methods. ([#5424](https://github.com/crystal-lang/crystal/pull/5424), thanks @MakeNowJust) +- Formatter: fix formatting of statements with comments. ([#5655](https://github.com/crystal-lang/crystal/pull/5655), [#5893](https://github.com/crystal-lang/crystal/pull/5893), [#5909](https://github.com/crystal-lang/crystal/pull/5909), thanks @MakeNowJust) +- Formatter: fix formatting of nested `begin`/`end`. ([#5922](https://github.com/crystal-lang/crystal/pull/5922), thanks @MakeNowJust) +- Formatter: fix formatting of trailing comma with block calls. ([#5855](https://github.com/crystal-lang/crystal/pull/5855), thanks @MakeNowJust) +- Formatter: fix formatting of ending expression after heredoc. ([#6127](https://github.com/crystal-lang/crystal/pull/6127), thanks @asterite) +- Documentation generator: references to nested types in markdown are now correctly parsed. ([#5308](https://github.com/crystal-lang/crystal/pull/5308), thanks @straight-shoota) +- Documentation generator: fix leftovers regarding default old `doc` directory. ([#5406](https://github.com/crystal-lang/crystal/pull/5406), thanks @GloverDonovan) +- Documentation generator: avoid failing on non git directory. ([#3700](https://github.com/crystal-lang/crystal/pull/3700), thanks @MakeNowJust) +- `Crystal::Doc::Highlighter` has specs now ([#5368](https://github.com/crystal-lang/crystal/pull/5368), thanks @MakeNowJust) +- Playground: can now be run with HTTPS. ([#5527](https://github.com/crystal-lang/crystal/pull/5527), thanks @opiation) +- Playground: Pretty-print objects in inspector. ([#4601](https://github.com/crystal-lang/crystal/pull/4601), thanks @jgaskins) + +## Misc +- The platform-specific parts of `File` and `IO::FileDescriptor` were moved to `Crystal::System`, as part of preparation for the Windows port. ([#5333](https://github.com/crystal-lang/crystal/pull/5333), [#5553](https://github.com/crystal-lang/crystal/pull/5553), [#5622](https://github.com/crystal-lang/crystal/pull/5622) thanks @RX14) +- The platform-specific parts of `Dir` were moved to `Crystal::System`, as part of preparation for the Windows port. ([#5447](https://github.com/crystal-lang/crystal/pull/5447), thanks @RX14) +- Incremental contributions regarding Windows support. ([#5422](https://github.com/crystal-lang/crystal/pull/5422), [#5524](https://github.com/crystal-lang/crystal/pull/5524), [#5533](https://github.com/crystal-lang/crystal/pull/5533), [#5538](https://github.com/crystal-lang/crystal/pull/5538), [#5539](https://github.com/crystal-lang/crystal/pull/5539), [#5580](https://github.com/crystal-lang/crystal/pull/5580), [#5947](https://github.com/crystal-lang/crystal/pull/5947) thanks @RX14 and @straight-shoota) +- The build on OpenBSD was fixed. ([#5387](https://github.com/crystal-lang/crystal/pull/5387), thanks @wmoxam) +- Add support for FreeBSD 12 (64-bit inodes). ([#5199](https://github.com/crystal-lang/crystal/pull/5199), thanks @myfreeweb) +- Scripts and makefiles now depend on `sh` instead of `bash` for greater portability. ([#5468](https://github.com/crystal-lang/crystal/pull/5468), thanks @j8r) +- Honor `LDFLAGS` and `EXTRA_FLAGS` in `Makefile`. ([#5423](https://github.com/crystal-lang/crystal/pull/5423), [#5860](https://github.com/crystal-lang/crystal/pull/5860), thanks @trofi, @jirutka) +- Improve message on link failure. ([#5486](https://github.com/crystal-lang/crystal/pull/5486), [#5603](https://github.com/crystal-lang/crystal/pull/5603), thanks @RX14 and @waj) +- Improve `String#to_json` when chars don't need escaping. ([#5456](https://github.com/crystal-lang/crystal/pull/5456), thanks @larubujo) +- Improve `Time#add_span` when arguments are zero. ([#5787](https://github.com/crystal-lang/crystal/pull/5787), thanks @straight-shoota) +- Improve `String#pretty_print` to output by splitting newline. ([#5750](https://github.com/crystal-lang/crystal/pull/5750), thanks @MakeNowJust) +- Add `\a` escape sequence. ([#5864](https://github.com/crystal-lang/crystal/pull/5864), thanks @r00ster91) +- Several miscellaneous minor code cleanups and refactors. ([#5499](https://github.com/crystal-lang/crystal/pull/5499), [#5502](https://github.com/crystal-lang/crystal/pull/5502), [#5507](https://github.com/crystal-lang/crystal/pull/5507), [#5516](https://github.com/crystal-lang/crystal/pull/5516), [#4915](https://github.com/crystal-lang/crystal/pull/4915), [#5526](https://github.com/crystal-lang/crystal/pull/5526), [#5529](https://github.com/crystal-lang/crystal/pull/5529), [#5535](https://github.com/crystal-lang/crystal/pull/5535), [#5537](https://github.com/crystal-lang/crystal/pull/5537), [#5540](https://github.com/crystal-lang/crystal/pull/5540), [#5435](https://github.com/crystal-lang/crystal/pull/5435), [#5520](https://github.com/crystal-lang/crystal/pull/5520), [#5530](https://github.com/crystal-lang/crystal/pull/5530), [#5547](https://github.com/crystal-lang/crystal/pull/5547), [#5543](https://github.com/crystal-lang/crystal/pull/5543), [#5561](https://github.com/crystal-lang/crystal/pull/5561), [#5599](https://github.com/crystal-lang/crystal/pull/5599), [#5493](https://github.com/crystal-lang/crystal/pull/5493), [#5546](https://github.com/crystal-lang/crystal/pull/5546), [#5624](https://github.com/crystal-lang/crystal/pull/5624), [#5701](https://github.com/crystal-lang/crystal/pull/5701), [#5733](https://github.com/crystal-lang/crystal/pull/5733), [#5646](https://github.com/crystal-lang/crystal/pull/5646), [#5729](https://github.com/crystal-lang/crystal/pull/5729), [#5791](https://github.com/crystal-lang/crystal/pull/5791), [#5859](https://github.com/crystal-lang/crystal/pull/5859), [#5882](https://github.com/crystal-lang/crystal/pull/5882), [#5899](https://github.com/crystal-lang/crystal/pull/5899), [#5918](https://github.com/crystal-lang/crystal/pull/5918), [#5896](https://github.com/crystal-lang/crystal/pull/5896), [#5810](https://github.com/crystal-lang/crystal/pull/5810), [#5575](https://github.com/crystal-lang/crystal/pull/5575), [#5785](https://github.com/crystal-lang/crystal/pull/5785), [#5866](https://github.com/crystal-lang/crystal/pull/5866), [#5816](https://github.com/crystal-lang/crystal/pull/5816), [#5945](https://github.com/crystal-lang/crystal/pull/5945), [#5963](https://github.com/crystal-lang/crystal/pull/5963), [#5968](https://github.com/crystal-lang/crystal/pull/5968), [#5977](https://github.com/crystal-lang/crystal/pull/5977), [#6004](https://github.com/crystal-lang/crystal/pull/6004), [#5794](https://github.com/crystal-lang/crystal/pull/5794), [#5858](https://github.com/crystal-lang/crystal/pull/5858), [#6033](https://github.com/crystal-lang/crystal/pull/6033), [#6036](https://github.com/crystal-lang/crystal/pull/6036), [#6079](https://github.com/crystal-lang/crystal/pull/6079), [#6111](https://github.com/crystal-lang/crystal/pull/6111), [#6118](https://github.com/crystal-lang/crystal/pull/6118), [#6141](https://github.com/crystal-lang/crystal/pull/6141), [#6142](https://github.com/crystal-lang/crystal/pull/6142), [#5380](https://github.com/crystal-lang/crystal/pull/5380), [#6071](https://github.com/crystal-lang/crystal/pull/6071), thanks @chastell, @lachlan, @bew, @RX14, @sdogruyol, @MakeNowJust, @Sija, @noriyotcp, @asterite, @splattael, @straight-shoota, @r00ster91, @jirutka, @paulcsmith, @rab, @esse, @carlhoerberg, @chris-huxtable, @luislavena) +- Several documentation fixes and additions. ([#5425](https://github.com/crystal-lang/crystal/pull/5425), [#5682](https://github.com/crystal-lang/crystal/pull/5682), [#5779](https://github.com/crystal-lang/crystal/pull/5779), [#5576](https://github.com/crystal-lang/crystal/pull/5576), [#5806](https://github.com/crystal-lang/crystal/pull/5806), [#5817](https://github.com/crystal-lang/crystal/pull/5817), [#5873](https://github.com/crystal-lang/crystal/pull/5873), [#5878](https://github.com/crystal-lang/crystal/pull/5878), [#5637](https://github.com/crystal-lang/crystal/pull/5637), [#5885](https://github.com/crystal-lang/crystal/pull/5885), [#5884](https://github.com/crystal-lang/crystal/pull/5884), [#5728](https://github.com/crystal-lang/crystal/pull/5728), [#5917](https://github.com/crystal-lang/crystal/pull/5917), [#5912](https://github.com/crystal-lang/crystal/pull/5912), [#5894](https://github.com/crystal-lang/crystal/pull/5894), [#5933](https://github.com/crystal-lang/crystal/pull/5933), [#5809](https://github.com/crystal-lang/crystal/pull/5809), [#5936](https://github.com/crystal-lang/crystal/pull/5936), [#5908](https://github.com/crystal-lang/crystal/pull/5908), [#5851](https://github.com/crystal-lang/crystal/pull/5851), [#5378](https://github.com/crystal-lang/crystal/pull/5378), [#5914](https://github.com/crystal-lang/crystal/pull/5914), [#5967](https://github.com/crystal-lang/crystal/pull/5967), [#5993](https://github.com/crystal-lang/crystal/pull/5993), [#3482](https://github.com/crystal-lang/crystal/pull/3482), [#5946](https://github.com/crystal-lang/crystal/pull/5946), [#6095](https://github.com/crystal-lang/crystal/pull/6095), [#6117](https://github.com/crystal-lang/crystal/pull/6117), [#6131](https://github.com/crystal-lang/crystal/pull/6131), [#6162](https://github.com/crystal-lang/crystal/pull/6162), thanks @MakeNowJust, @straight-shoota, @vendethiel, @bew, @Heaven31415, @marksiemers, @Willamin, @r00ster91, @maiha, @Givralix, @docelic, @CaDs, @esse, @igneus, @masukomi) +- CI housekeeping and including 32 bits automated builds. ([#5796](https://github.com/crystal-lang/crystal/pull/5796), [#5804](https://github.com/crystal-lang/crystal/pull/5804), [#5837](https://github.com/crystal-lang/crystal/pull/5837), [#6015](https://github.com/crystal-lang/crystal/pull/6015), [#6165](https://github.com/crystal-lang/crystal/pull/6165), thanks @bcardiff, @bew and @Sija) +- Sync docs in master to [https://crystal-lang.org/api/master](https://crystal-lang.org/api/master). ([#5941](https://github.com/crystal-lang/crystal/pull/5941), thanks @bcardiff) +- Enable the large heap configuration for libgc. ([#5839](https://github.com/crystal-lang/crystal/pull/5839), thanks @RX14) +- Improve Ctrl-C handling of spec. ([#5719](https://github.com/crystal-lang/crystal/pull/5719), thanks @MakeNowJust) +- Playground: Update to codemirror 5.38.0. ([#6166](https://github.com/crystal-lang/crystal/pull/6166), thanks @bcardiff) + +# 0.24.2 (2018-03-08) + +- Fixed an `Index out of bounds` raised during `at_exit` ([#5224](https://github.com/crystal-lang/crystal/issues/5224), [#5565](https://github.com/crystal-lang/crystal/issues/5565), thanks @ysbaddaden) +- Re-add `Dir#each` so it complies with `Enumerable` ([#5458](https://github.com/crystal-lang/crystal/issues/5458), thanks @bcardiff) +- Fixed `SSL::Context` bug verifying certificates ([#5266](https://github.com/crystal-lang/crystal/issues/5266), [#5601](https://github.com/crystal-lang/crystal/issues/5601), thanks @waj) +- Fixed UUID documentation that was missing ([#5478](https://github.com/crystal-lang/crystal/issues/5478), [#5542](https://github.com/crystal-lang/crystal/issues/5542), thanks @asterite) +- Fixed a bug with single expressions in parenthesis ([#5482](https://github.com/crystal-lang/crystal/issues/5482), [#5511](https://github.com/crystal-lang/crystal/issues/5511), [#5513](https://github.com/crystal-lang/crystal/issues/5513), thanks @MakeNowJust) +- Fixed `skip_file` macro docs ([#5488](https://github.com/crystal-lang/crystal/issues/5488), thanks @straight-shoota) +- Fixed CI `build` script's `LIBRARY_PATH` ([#5457](https://github.com/crystal-lang/crystal/issues/5457), [#5461](https://github.com/crystal-lang/crystal/issues/5461), thanks @bcardiff) +- Fixed formatter bug with upper-cased `fun` names ([#5432](https://github.com/crystal-lang/crystal/issues/5432), [#5434](https://github.com/crystal-lang/crystal/issues/5434), thanks @bew) + +# 0.24.1 (2017-12-23) + +## New features +- Add ThinLTO support for faster release builds in LLVM 4.0 and above. ([#4367](https://github.com/crystal-lang/crystal/issues/4367), thanks @bcardiff) +- **(breaking-change)** Add `UUID` type. `Random::Secure.uuid` has been replaced with `UUID.random`. ([#4453](https://github.com/crystal-lang/crystal/issues/4453), thanks @wontruefree) +- Add a `BigDecimal` class for arbitrary precision, exact, decimal numbers. ([#4876](https://github.com/crystal-lang/crystal/issues/4876) and [#5255](https://github.com/crystal-lang/crystal/issues/5255), thanks @vegai and @Sija) +- Allow `Set` to work as a case condition, which matches when the case variable is inside the set. ([#5269](https://github.com/crystal-lang/crystal/issues/5269), thanks @MakeNowJust) +- **(breaking-change)** Change `Time::Format` codes to allow more robust options for parsing sub-second precision times. ([#5317](https://github.com/crystal-lang/crystal/issues/5317), thanks @bcardiff) +- Add `Time.utc`, an alias of `Time.new` which shortens creating UTC times. ([#5321](https://github.com/crystal-lang/crystal/issues/5321), thanks @straight-shoota) +- Add custom extension support to `Tempfile`. ([#5264](https://github.com/crystal-lang/crystal/issues/5264), thanks @jreinert) +- Add `reduce` method to `TupleLiteral` and `ArrayLiteral` when using macros. ([#5294](https://github.com/crystal-lang/crystal/issues/5294), thanks @javanut13) +- Export a JSON representation of the documentation in the generated output. ([#4746](https://github.com/crystal-lang/crystal/issues/4746) and [#5228](https://github.com/crystal-lang/crystal/issues/5228), thanks @straight-shoota) +- Make `gc/none` garbage collection compile again and allow it to be enabled using `-Dgc_none` compiler flag. ([#5314](https://github.com/crystal-lang/crystal/issues/5314), thanks @ysbaddaden) + +## Standard library bugs fixed +- Make `String#[]` unable to read out-of-bounds when the string ends in a unicode character. ([#5257](https://github.com/crystal-lang/crystal/issues/5257), thanks @Papierkorb) +- Fix incorrect parsing of long JSON floating point values. ([#5323](https://github.com/crystal-lang/crystal/issues/5323), thanks @benoist) +- Replace the default hash function with one resistant to hash DoS. ([#5146](https://github.com/crystal-lang/crystal/issues/5146), thanks @funny-falcon) +- Ensure equal numbers always have the same hashcode. ([#5276](https://github.com/crystal-lang/crystal/issues/5276), thanks @akzhan) +- Fix struct equality when two structs descend from the same abstract struct. ([#5254](https://github.com/crystal-lang/crystal/issues/5254), thanks @hinrik) +- Fix `URI#full_path` not to append a `?` unless the query params are nonempty. ([#5340](https://github.com/crystal-lang/crystal/issues/5340), thanks @paulcsmith) +- Fix `HTTP::Params.parse` to parse `&&` correctly. ([#5274](https://github.com/crystal-lang/crystal/issues/5274), thanks @akiicat) +- Disallow null bytes in `ENV` keys and values. ([#5216](https://github.com/crystal-lang/crystal/issues/5216), thanks @Papierkorb) +- Disallow null bytes in `XML::Node` names and content. ([#5200](https://github.com/crystal-lang/crystal/issues/5200), thanks @RX14) +- Fix `IO#blocking=` on OpenBSD. ([#5283](https://github.com/crystal-lang/crystal/issues/5283), thanks @wmoxam) +- Fix linking programs in OpenBSD. ([#5282](https://github.com/crystal-lang/crystal/issues/5282), thanks @wmoxam) + +## Compiler bugs fixed +- Stop incorrectly finding top-level methods when searching for a `super` method. ([#5202](https://github.com/crystal-lang/crystal/issues/5202), thanks @lbguilherme) +- Fix parsing regex literals starting with a `;` directly after a call (ex `p /;/`). ([#5208](https://github.com/crystal-lang/crystal/issues/5208), thanks @MakeNowJust) +- Correct a case where `Expressions#to_s` could produce invalid output, causing macro expansion to fail. ([#5226](https://github.com/crystal-lang/crystal/issues/5226), thanks @asterite) +- Give error instead of crashing when `self` is used at the top level. ([#5227](https://github.com/crystal-lang/crystal/issues/5227), thanks @MakeNowJust) +- Give error instead of crashing when using `instance_sizeof` on a generic type without providing it's type arguments. ([#5209](https://github.com/crystal-lang/crystal/issues/5209), thanks @lbguilherme) +- Fix parsing calls when short block syntax (`&.foo`) is followed by a newline. ([#5237](https://github.com/crystal-lang/crystal/issues/5237), thanks @MakeNowJust) +- Give error instead of crashing when an unterminated string array literal (`%w()`) sits at the end of a file. ([#5241](https://github.com/crystal-lang/crystal/issues/5241), thanks @asterite) +- Give error when attempting to use macro yield (`{{yield}}`) outside a macro. ([#5307](https://github.com/crystal-lang/crystal/issues/5307), thanks @MakeNowJust) +- Fix error related to generic inheritance. ([#5284](https://github.com/crystal-lang/crystal/issues/5284), thanks @MakeNowJust) +- Fix compiler crash when using recursive alias and generics. ([#5330](https://github.com/crystal-lang/crystal/issues/5330), thanks @MakeNowJust) +- Fix parsing `foo(+1)` as `foo + 1` instead of `foo(1)` where `foo` was a local variable. ([#5336](https://github.com/crystal-lang/crystal/issues/5336), thanks @MakeNowJust) +- Documentation generator: Keep quoted symbol literals quoted when syntax highlighting code blocks in documentation output. ([#5238](https://github.com/crystal-lang/crystal/issues/5238), thanks @MakeNowJust) +- Documentation generator: Keep the original delimiter used when syntax highlighting string array literals. ([#5297](https://github.com/crystal-lang/crystal/issues/5297), thanks @MakeNowJust) +- Documentation generator: Fix XSS vulnerability when syntax highlighting string array literals. ([#5259](https://github.com/crystal-lang/crystal/issues/5259), thanks @MakeNowJust) +- Formatter: fix indentation of the last comment in a `begin`/`end` block. ([#5198](https://github.com/crystal-lang/crystal/issues/5198), thanks @MakeNowJust) +- Formatter: fix formatting parentheses with multiple lines in. ([#5268](https://github.com/crystal-lang/crystal/issues/5268), thanks @MakeNowJust) +- Formatter: fix formatting `$1?`. ([#5313](https://github.com/crystal-lang/crystal/issues/5313), thanks @MakeNowJust) +- Formatter: ensure to insert a space between `{` and `%` characters to avoid forming `{%` macros. ([#5278](https://github.com/crystal-lang/crystal/issues/5278), thanks @MakeNowJust) + +## Misc +- Fix `Makefile`, CI, and gitignore to use the new documentation path after [#4937](https://github.com/crystal-lang/crystal/issues/4937). ([#5217](https://github.com/crystal-lang/crystal/issues/5217), thanks @straight-shoota) +- Miscellaneous code cleanups. ([#5318](https://github.com/crystal-lang/crystal/issues/5318), [#5341](https://github.com/crystal-lang/crystal/issues/5341) and [#5366](https://github.com/crystal-lang/crystal/issues/5366), thanks @bew and @mig-hub) +- Documentation fixes. ([#5253](https://github.com/crystal-lang/crystal/issues/5253), [#5296](https://github.com/crystal-lang/crystal/issues/5296), [#5300](https://github.com/crystal-lang/crystal/issues/5300) and [#5322](https://github.com/crystal-lang/crystal/issues/5322), thanks @arcage, @icyleaf, @straight-shoota and @bew) +- Fix the in-repository changelog to include 0.24.0. ([#5331](https://github.com/crystal-lang/crystal/pull/5331), thanks @sdogruyol) + +# 0.24.0 (2017-10-30) + +- **(breaking-change)** HTTP::Client#post_form is now HTTP::Client.post(form: ...) +- **(breaking-change)** Array#reject!, Array#compact! and Array#select! now return self ([#5154](https://github.com/crystal-lang/crystal/pull/5154)) +- **(breaking-change)** Remove the possibility to require big_int, big_float or big_rational individually: use require "big" instead ([#5121](https://github.com/crystal-lang/crystal/pull/5121)) +- **(breaking-change)** Spec: remove expect_raises without type argument ([#5096](https://github.com/crystal-lang/crystal/pull/5096)) +- **(breaking-change)** IO is now a class, no longer a module ([#4901](https://github.com/crystal-lang/crystal/pull/4901)) +- **(breaking-change)** Time constructors now have nanosecond and kind as named argument ([#5072](https://github.com/crystal-lang/crystal/pull/5072)) +- **(breaking-change)** Removed XML.escape. Use HTML.escape instead ([#5046](https://github.com/crystal-lang/crystal/pull/5046)) +- **(breaking-change)** Removed macro def ([#5040](https://github.com/crystal-lang/crystal/pull/5040)) +- **(breaking-change)** SecureRandom is now Random::Secure ([#4894](https://github.com/crystal-lang/crystal/pull/4894)) +- **(breaking-change)** HTML.escape now only escapes &<>"' ([#5012](https://github.com/crystal-lang/crystal/pull/5012)) +- **(breaking-change)** To define a custom hash method you must now define hash(hasher) ([#4946](https://github.com/crystal-lang/crystal/pull/4946)) +- **(breaking-change)** Flate::Reader.new(&block) and Flate::Writer.new(&block) now use the name open ([#4887](https://github.com/crystal-lang/crystal/pull4887/)) +- **(breaking-change)** Use an Enum for Process stdio redirections ([#4445](https://github.com/crystal-lang/crystal/pull/4445)) +- **(breaking-change)** Remove '$0' special syntax +- **(breaking-change)** Remove bare array creation from multi assign (a = 1, 2, 3) ([#4824](https://github.com/crystal-lang/crystal/pull/4824)) +- **(breaking-change)** Rename skip macro method to skip_file ([#4709](https://github.com/crystal-lang/crystal/pull/4709)) +- **(breaking-change)** StaticArray#map and Slice#map now return their same type instead of Array ([#5124](https://github.com/crystal-lang/crystal/pull/5124)) +- **(breaking-change)** Tuple#map_with_index now returns a Tuple. ([#5086](https://github.com/crystal-lang/crystal/pull/5086)) +- Packages built with LLVM 3.9.1. They should (hopefully) fix [#4719](https://github.com/crystal-lang/crystal/issues/4719) +- Syntax: Allow flat rescue/ensure/else block in do/end block ([#5114](https://github.com/crystal-lang/crystal/pull/5114)) +- Syntax: fun names and lib function calls can now start with Uppercase +- Macros: Using an alias in macros will now automatically resolve it to is aliased type ([#4995](https://github.com/crystal-lang/crystal/pull/4995)) +- Macros: The flags bits32 and bits64 are now automatically defined in macros +- The YAML module has now full support for the 1.1 core schema with additional types, and properly supports aliases and merge keys ([#5007](https://github.com/crystal-lang/crystal/pull/5007)) +- Add --output option to crystal docs ([#4937](https://github.com/crystal-lang/crystal/pull/4937)) +- Add Time#days_in_year: it returns the no of days in a given year ([#5163](https://github.com/crystal-lang/crystal/pull/5163)) +- Add Time.monotonic to return monotonic clock ([#5108](https://github.com/crystal-lang/crystal/pull/5108)) +- Add remove_empty option to many String#split overloads +- Add Math.sqrt overloads for Bigs ([#5113](https://github.com/crystal-lang/crystal/pull/5113)) +- Add --stdin-filename to crystal command to compile source from STDIN ([#4571](https://github.com/crystal-lang/crystal/pull/4571)) +- Add Crystal.main to more easily redefine the main of a program ([#4998](https://github.com/crystal-lang/crystal/pull/4998)) +- Add Tuple.types that returns a tuple of types ([#4962](https://github.com/crystal-lang/crystal/pull/4962)) +- Add NamedTuple.types that returns a named tuple of types ([#4962](https://github.com/crystal-lang/crystal/pull/4962)) +- Add NamedTuple#merge(other : NamedTuple) ([#4688](https://github.com/crystal-lang/crystal/pull/4688)) +- Add YAML and JSON.mapping presence: true option ([#4843](https://github.com/crystal-lang/crystal/pull/4843)) +- Add Dir.each_child(&block) ([#4811](https://github.com/crystal-lang/crystal/pull/4811)) +- Add Dir.children ([#4808](https://github.com/crystal-lang/crystal/pull/4808)) +- HTML.unescape now supports all HTML5 named entities ([#5064](https://github.com/crystal-lang/crystal/pull/5064)) +- Regex now supports duplicated named captures ([#5061](https://github.com/crystal-lang/crystal/pull/5061)) +- rand(0) is now valid and returns 0 +- Tuple#[] now supports a negative index ([#4735](https://github.com/crystal-lang/crystal/pull/4735)) +- JSON::Builder#field now accepts non-scalar values ([#4706](https://github.com/crystal-lang/crystal/pull/4706)) +- Number#inspect now shows the number type +- Some additions to Big arithmetics ([#4653](https://github.com/crystal-lang/crystal/pull/4653)) +- Increase the precision of Time and Time::Span to nanoseconds ([#5022](https://github.com/crystal-lang/crystal/pull/5022)) +- Upgrade Unicode to 10.0.0 ([#5122](https://github.com/crystal-lang/crystal/pull/5122)) +- Support LLVM 5.0 ([#4821](https://github.com/crystal-lang/crystal/pull/4821)) +- [Lots of bugs fixed](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.24.0) + +# 0.23.1 (2017-07-01) + +* Added `Random::PCG32` generator (See [#4536](https://github.com/crystal-lang/crystal/issues/4536), thanks @konovod) +* WebSocket should compare "Upgrade" header value with case insensitive (See [#4617](https://github.com/crystal-lang/crystal/issues/4617), thanks @MakeNowJust) +* Fixed macro lookup from included module (See [#4639](https://github.com/crystal-lang/crystal/issues/4639), thanks @asterite) +* Explained "crystal tool expand" in crystal(1) man page (See [#4643](https://github.com/crystal-lang/crystal/issues/4643), thanks @MakeNowJust) +* Explained how to detect end of file in `IO` (See [#4661](https://github.com/crystal-lang/crystal/issues/4661), thanks @oprypin) + +# 0.23.0 (2017-06-27) + +* **(breaking-change)** `Logger#formatter` takes a `Severity` instead of a `String` (See [#4355](https://github.com/crystal-lang/crystal/issues/4355), [#4369](https://github.com/crystal-lang/crystal/issues/4369), thanks @Sija) +* **(breaking-change)** Removed `IO.select` (See [#4392](https://github.com/crystal-lang/crystal/issues/4392), thanks @RX14) +* Added `Crystal::System::Random` namespace (See [#4450](https://github.com/crystal-lang/crystal/issues/4450), thanks @ysbaddaden) +* Added `Path#resolve?` macro method (See [#4370](https://github.com/crystal-lang/crystal/issues/4370), [#4408](https://github.com/crystal-lang/crystal/issues/4408), thanks @RX14) +* Added range methods to `BitArray` (See [#4397](https://github.com/crystal-lang/crystal/issues/4397), [#3968](https://github.com/crystal-lang/crystal/issues/3968), thanks @RX14) +* Added some well-known HTTP Status messages (See [#4419](https://github.com/crystal-lang/crystal/issues/4419), thanks @akzhan) +* Added compiler progress indicator (See [#4182](https://github.com/crystal-lang/crystal/issues/4182), thanks @RX14) +* Added `System.cpu_cores` (See [#4449](https://github.com/crystal-lang/crystal/issues/4449), [#4226](https://github.com/crystal-lang/crystal/issues/4226), thanks @miketheman) +* Added `separator` and `quote_char` to `CSV#each_row` (See [#4448](https://github.com/crystal-lang/crystal/issues/4448), thanks @timsu) +* Added `map_with_index!` to `Pointer`, `Array` and `StaticArray` (See [#4456](https://github.com/crystal-lang/crystal/issues/4456), [#3356](https://github.com/crystal-lang/crystal/issues/3356), [#3354](https://github.com/crystal-lang/crystal/issues/3354), thanks @Nephos) +* Added `headers` parameter to `HTTP::WebSocket` constructors (See [#4227](https://github.com/crystal-lang/crystal/issues/4227), [#4222](https://github.com/crystal-lang/crystal/issues/4222), thanks @adamtrilling) +* Added `unlink` to `XML::Node` (See [#4515](https://github.com/crystal-lang/crystal/issues/4515), [#4331](https://github.com/crystal-lang/crystal/issues/4331), thanks @RX14 and @MrSorcus) +* Added `Math.frexp` (See [#4560](https://github.com/crystal-lang/crystal/issues/4560), thanks @akzhan) +* Added `Regex::MatchData` support for negative indexes (See [#4566](https://github.com/crystal-lang/crystal/issues/4566), thanks @MakeNowJust) +* Added `captures`, `named_captures`, `to_a` and `to_h` to `Regex::MatchData` (See [#3783](https://github.com/crystal-lang/crystal/issues/3783), thanks @MakeNowJust) +* Added `|` as a string delimiter to allow `q|string|` syntax (See [#3467](https://github.com/crystal-lang/crystal/issues/3467), thanks @RX14) +* Added support for Windows linker (See [#4491](https://github.com/crystal-lang/crystal/issues/4491), thanks @RX14) +* Added llvm operand bundle def and catch pad/ret/switch in order to support Windows SEH (See [#4501](https://github.com/crystal-lang/crystal/issues/4501), thanks @bcardiff) +* Added `Float::Printer` based on Grisu3 to speed up float to string conversion (See [#4333](https://github.com/crystal-lang/crystal/issues/4333), thanks @will) +* Added `Object.unsafe_as` to unsafely reinterpret the bytes of an object as being of another `type` (See [#4333](https://github.com/crystal-lang/crystal/issues/4333), thanks @asterite) +* Added `.downcase(Unicode::CaseOptions::Fold)` option which convert strings to casefolded strings for caseless matching (See [#4512](https://github.com/crystal-lang/crystal/issues/4512), thanks @akzhan) +* Added `OpenSSL::DigestIO` to wrap an IO while calculating a digest (See [#4260](https://github.com/crystal-lang/crystal/issues/4260), thanks @spalladino) +* Added `zero?` to numbers and time spans (See [#4026](https://github.com/crystal-lang/crystal/issues/4026), thanks @jellymann) +* Added `TypeNode#has_method?` method (See [#4474](https://github.com/crystal-lang/crystal/issues/4474), thanks @Sija) +* `Regex::MatchData#size` renamed to `#group_size` (See [#4565](https://github.com/crystal-lang/crystal/issues/4565), thanks @MakeNowJust) +* `HTTP::StaticFileHandler` can disable directory listing (See [#4403](https://github.com/crystal-lang/crystal/issues/4403), [#4398](https://github.com/crystal-lang/crystal/issues/4398), thanks @joaodiogocosta) +* `bin/crystal` now uses `/bin/sh` instead of `/bin/bash` (See [#3809](https://github.com/crystal-lang/crystal/issues/3809), [#4410](https://github.com/crystal-lang/crystal/issues/4410), thanks @TheLonelyGhost) +* `crystal init` generates a `.editorconfig` file (See [#4422](https://github.com/crystal-lang/crystal/issues/4422), [#297](https://github.com/crystal-lang/crystal/issues/297), thanks @akzhan) +* `man` page for `crystal` command (See [#2989](https://github.com/crystal-lang/crystal/issues/2989), [#1291](https://github.com/crystal-lang/crystal/issues/1291), thanks @dread-uo) +* Re-raising an exception doesn't overwrite its callstack (See [#4487](https://github.com/crystal-lang/crystal/issues/4487), [#4482](https://github.com/crystal-lang/crystal/issues/4482), thanks @akzhan) +* MD5 and SHA1 documentation clearly states they are not cryptographically secure anymore (See [#4426](https://github.com/crystal-lang/crystal/issues/4426), thanks @RX14) +* Documentation about constructor methods now rendered separately (See [#4216](https://github.com/crystal-lang/crystal/issues/4216), thanks @Sija) +* Turn `Random::System` into a module (See [#4542](https://github.com/crystal-lang/crystal/issues/4542), thanks @oprypin) +* `Regex::MatchData` pretty printed (See [#4574](https://github.com/crystal-lang/crystal/issues/4574), thanks @MakeNowJust) +* `String.underscore` treats digits as downcase or upcase characters depending previous characters (See [#4280](https://github.com/crystal-lang/crystal/issues/4280), thanks @MakeNowJust) +* Refactor time platform specific implementation (See [#4502](https://github.com/crystal-lang/crystal/issues/4502), thanks @bcardiff) +* Fixed Crystal not reusing .o files across builds (See [#4336](https://github.com/crystal-lang/crystal/issues/4336)) +* Fixed `SomeClass.class.is_a?(SomeConst)` causing an "already had enclosing call" exception (See [#4364](https://github.com/crystal-lang/crystal/issues/4364), [#4390](https://github.com/crystal-lang/crystal/issues/4390), thanks @rockwyc992) +* Fixed `HTTP::Params.parse` query string with two `=` gave wrong result (See [#4388](https://github.com/crystal-lang/crystal/issues/4388), [#4389](https://github.com/crystal-lang/crystal/issues/4389), thanks @akiicat) +* Fixed `Class.class.is_a?(Class.class.class.class.class)` 🎉 (See [#4375](https://github.com/crystal-lang/crystal/issues/4375), [#4374](https://github.com/crystal-lang/crystal/issues/4374), thanks @rockwyc992) +* Fixed select hanging when sending before receive (See [#3862](https://github.com/crystal-lang/crystal/issues/3862), [#3899](https://github.com/crystal-lang/crystal/issues/3899), thanks @kostya) +* Fixed "Unknown key in access token json: id_token" error in OAuth2 client (See [#4437](https://github.com/crystal-lang/crystal/issues/4437)) +* Fixed macro lookup conflicting with method lookup when including on top level (See [#236](https://github.com/crystal-lang/crystal/issues/236)) +* Fixed Vagrant images (See [#4510](https://github.com/crystal-lang/crystal/issues/4510), [#4508](https://github.com/crystal-lang/crystal/issues/4508), thanks @Val) +* Fixed `IO::FileDescriptor#seek` from current position (See [#4558](https://github.com/crystal-lang/crystal/issues/4558), thanks @ysbaddaden) +* Fixed `IO::Memory#gets_to_end` to consume the `IO` (See [#4415](https://github.com/crystal-lang/crystal/issues/4415), thanks @jhass) +* Fixed setting of XML attributes (See [#4562](https://github.com/crystal-lang/crystal/issues/4562), thanks @asterite) +* Fixed "SSL_shutdown: Operation now in progress" error by retrying (See [#3168](https://github.com/crystal-lang/crystal/issues/3168), thanks @akzhan) +* Fixed WebSocket negotiation (See [#4386](https://github.com/crystal-lang/crystal/issues/4386), thanks @RX14) + +# 0.22.0 (2017-04-20) + +* **(breaking-change)** Removed `Process.new(pid)` is now private (See [#4197](https://github.com/crystal-lang/crystal/issues/4197)) +* **(breaking-change)** IO#peek now returns an empty slice on EOF (See [#4240](https://github.com/crystal-lang/crystal/issues/4240), [#4261](https://github.com/crystal-lang/crystal/issues/4261)) +* **(breaking-change)** Rename `WeakRef#target` to `WeakRef#value` (See [#4293](https://github.com/crystal-lang/crystal/issues/4293)) +* **(breaking-change)** Rename `HTTP::Params.from_hash` to `HTTP::Params.encode` (See [#4205](https://github.com/crystal-lang/crystal/issues/4205)) +* **(breaking-change)** `'\"'` is now invalid, use `'"'` (See [#4309](https://github.com/crystal-lang/crystal/issues/4309)) +* Improved backtrace function names are now read from DWARF sections (See [#3958](https://github.com/crystal-lang/crystal/issues/3958), thanks @ysbaddaden) +* Improved sigfaults and exceptions are printed to STDERR (See [#4163](https://github.com/crystal-lang/crystal/issues/4163), thanks @Sija) +* Improved SSL Sockets are now buffered (See [#4248](https://github.com/crystal-lang/crystal/issues/4248)) +* Improved type inference on loops (See [#4242](https://github.com/crystal-lang/crystal/issues/4242), [#4243](https://github.com/crystal-lang/crystal/issues/4243)) +* Improved `pp` and `p`, the printed value is returned (See [#4285](https://github.com/crystal-lang/crystal/issues/4285), [#4283](https://github.com/crystal-lang/crystal/issues/4283), thanks @MakeNowJust) +* Added support for OpenSSL 1.1.0 (See [#4215](https://github.com/crystal-lang/crystal/issues/4215), [#4230](https://github.com/crystal-lang/crystal/issues/4230), thanks @ysbaddaden) +* Added `SecureRandom#random_bytes(Bytes)` (See [#4191](https://github.com/crystal-lang/crystal/issues/4191), thanks @konovod) +* Added setting and deleting of attributes on `XML::Node` (See [#3902](https://github.com/crystal-lang/crystal/issues/3902), thanks @bmmcginty) +* Added `File.touch` and `FileUtils.touch` methods (See [#4069](https://github.com/crystal-lang/crystal/issues/4069), thanks @Sija) +* Added `#values_at` for `CSV` (See [#4157](https://github.com/crystal-lang/crystal/issues/4157), thanks @need47) +* Added `Time#clone` (See [#4174](https://github.com/crystal-lang/crystal/issues/4174), thanks @Sija) +* Added `ancestors` macro method (See [#3875](https://github.com/crystal-lang/crystal/issues/3875), thanks @david50407) +* Added `skip` macro method ([#4237](https://github.com/crystal-lang/crystal/issues/4237), thanks @mverzilli) +* Added `Colorize.on_tty_only!` for easier toggling (See [#4075](https://github.com/crystal-lang/crystal/issues/4075), [#4271](https://github.com/crystal-lang/crystal/issues/4271), thanks @MakeNowJust) +* Added `WebSocket#on_binary` to receive binary messages (See [#2774](https://github.com/crystal-lang/crystal/issues/2774), thanks @lbguilherme) +* Fixed `Iterator.of` stops iterating when `Iterator.stop` is returned (See [#4208](https://github.com/crystal-lang/crystal/issues/4208)) +* Fixed `String#insert` for non-ascii Char (See [#4164](https://github.com/crystal-lang/crystal/issues/4164), thanks @Papierkorb) +* Fixed `File.link` now creates a hard link ([#4116](https://github.com/crystal-lang/crystal/issues/4116), thanks @KCreate) +* Fixed error message for `#to_h` over empty `NamedTuple` (See [#4076](https://github.com/crystal-lang/crystal/issues/4076), thanks @karlseguin) +* Fixed `NamedTuple#to_h` does no longer call to value's `#clone` (See [#4203](https://github.com/crystal-lang/crystal/issues/4203)) +* Fixed `Math#gamma` and `Math#lgamma` (See [#4229](https://github.com/crystal-lang/crystal/issues/4229), thanks @KCreate) +* Fixed `TCPSocket` creation for 0 port for Mac OSX (See [#4177](https://github.com/crystal-lang/crystal/issues/4177), thanks @will) +* Fixed repo name extraction from git remote in doc tool (See [#4132](https://github.com/crystal-lang/crystal/issues/4132), thanks @Sija) +* Fixed `self` resolution when including a generic module (See [#3972](https://github.com/crystal-lang/crystal/issues/3972), thanks @MakeNowJust) +* Fixed debug information was missing in some cases (See [#4166](https://github.com/crystal-lang/crystal/issues/4166), [#4202](https://github.com/crystal-lang/crystal/issues/4202), [#4254](https://github.com/crystal-lang/crystal/issues/4254)) +* Fixed use generic ARM architecture target triple for all ARM architectures (See [#4167](https://github.com/crystal-lang/crystal/issues/4167), thanks @ysbaddaden) +* Fixed macro run arguments escaping +* Fixed zsh completion (See [#4284](https://github.com/crystal-lang/crystal/issues/4284), thanks @veelenga) +* Fixed honor `--no-color` option in spec (See [#4306](https://github.com/crystal-lang/crystal/issues/4306), thanks @luislavena) +* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.22.0) + +# 0.21.1 (2017-03-06) + +* Improved lookup of abstract def implementors (see [#4052](https://github.com/crystal-lang/crystal/issues/4052)) +* Improved allocation of objects without pointer instance variables using `malloc_atomic` (see [#4081](https://github.com/crystal-lang/crystal/issues/4081)) +* Added `crystal --version` reports also the LLVM version (see [#4095](https://github.com/crystal-lang/crystal/issues/4095), thanks @matiasgarciaisaia) +* Fixed instance variables initializers corner cases (see [#3988](https://github.com/crystal-lang/crystal/issues/3988)) +* Fixed `crystal play` was broken (see [#4061](https://github.com/crystal-lang/crystal/issues/4061)) +* Fixed `Atomic` can be set to `nil` (see [#4062](https://github.com/crystal-lang/crystal/issues/4062)) +* Fixed `GZip::Header` extra byte (see [#4068](https://github.com/crystal-lang/crystal/issues/4068), thanks @crisward) +* Fixed `ASTNode#to_s` for `Attribute` (see [#4098](https://github.com/crystal-lang/crystal/issues/4098), thanks @olbat) +* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.21.1) + +# 0.21.0 (2017-02-20) + +* **(breaking-change)** The compiler now reuses previous macro run compilations so `{{ run(...) }}` is only re-run if the code changes +* **(breaking-change)** Spec: `assert { ... }` is now `it { ... }` (thanks @TheLonelyGhost) +* **(breaking-change)** Renamed `Set#merge!` to `Set#concat` +* **(breaking-change)** `Zlib` was split into `Flate`, `Gzip` and `Zlib` ([bda40f](https://github.com/crystal-lang/crystal/commit/bda40f)) +* **(breaking-change)** `Crypto::MD5` is now `Digest::MD5` +* **(breaking-change)** `String#chop` is now `String#rchop` +* **(breaking-change)** `String#to_slice` now returns a read-only Slice +* **(breaking-change)** `String` can now hold invalid UTF-8 byte sequences, and they produce a unicode replacement character when traversed +* **(breaking-change)** Removed `String#lchomp`. Use `String#lchop` +* **(breaking-change)** Octal escapes inside strings incorrectly produced a codepoint value instead of a byte value +* **(breaking-change)** Removed octal escape from char literals +* Fixed compiler performance regression related to cached files ([f69e37e](https://github.com/crystal-lang/crystal/commit/f69e37e)) +* Added `\xHH` escape sequence in string literals +* `Char::Reader` can now traverse a string backwards +* `Enum#to_s` now uses pipes instead of commas for flag enums +* `IO#read_string` is now encoding-aware +* `OAuth2::Client` now sends `application/json` Accept header, and considers the `expires_in` access token property as optional +* `Slice` can now be read-only +* `TCPServer` no longer set SO_REUSEPORT to true by default +* Added `HTTP::Multipart` and `HTTP::FormData` (thanks @RX14) +* Added `File::Stat#pipe?` +* Added `File.utime` +* Added `IO#peek` +* Added `String#strip(arg)`, `String#lstrip(arg)`, `String#rstrip(arg)` +* Added `String#lchop`, `String#lchop(prefix)`, `String#rchop` and `String#rchop(suffix)` +* Added `String#hexbytes` and `String#hexbytes?` +* Added `String#scrub` and `String#valid_encoding?` +* Added `include?` macro method for StringLiteral, SymbolLiteral and MacroId (thanks @karlseguin) +* Added "view source" links to GitLab (thanks @ezrast) +* Updated CONTRIBUTING.md guidelines +* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.21.0) + +# 0.20.5 (2017-01-20) + +* Improved performance in `String#index`, `String#rindex` due to Rabin-Karp algorithm (thanks @MakeNowJust). +* Improved performance in `Crypto::Bcrypt` (see [#3880](https://github.com/crystal-lang/crystal/issues/3880), thanks @ysbaddaden). +* `expect_raises` returns raised exception (thanks @kostya). +* Line numbers debug information is always generated (see [#3831](https://github.com/crystal-lang/crystal/issues/3831), thanks @ysbaddaden). +* Added `Zip::File`, `Zip::Reader` and `Zip::Writer`. Native readers for zip files that delegate compression to existing zlib module. +* Added `Hash#delete` with block (see [#3856](https://github.com/crystal-lang/crystal/issues/3856), thanks @bmulvihill). +* Added `String#[](char : Char)` (see [#3855](https://github.com/crystal-lang/crystal/issues/3855), thanks @Sija). +* Added `crystal tool expand` to expand macro call in a given location (see [#3732](https://github.com/crystal-lang/crystal/issues/3732), thanks @MakeNowJust). +* Fixed `crystal play` is able to show compilation errors again. +* `crystal doc` recognizes `crystal-lang/crystal` in any remote (thanks @MaxLap). +* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.5) + +# 0.20.4 (2017-01-06) + +* **(breaking change)** A type that wants to convert itself to JSON now must override `to_json(builder : JSON::Builder)` instead of `to_json(io : IO)`. The same is true for custom JSON converters. If you are using `JSON.mapping` then your code will continue to work without changes. +* **(breaking change)** Defining a `finalize` method on a struct now gives a compile error +* **(breaking change)** Default argument types now must match their restriction, if any (for example `def foo(x : Int32 = nil)` will now fail to compile if `foo` is invoked without arguments) (thanks @MakeNowJust) +* **(breaking change)** `each` methods now return `Nil` +* **(breaking change)** `IO#skip(bytes)` will now raise if there aren't at least the given amount of bytes in the `IO` (previously it would work well if there were less bytes, and it would hang if there were more) +* **(breaking change)** `MemoryIO` was removed (use `IO::Memory` instead) +* **(breaking change)** `Number#step` now requires named arguments, `to` and `by`, to avoid argument order confusion +* **(breaking change)** `YAML::Emitter` was renamed to `YAML::Builder`, and some of its methods were also renamed +* **(breaking change)** `XML::Node#[]` now always returns a `String` (previously it could also return `Nil`, which was incorrect) +* **(breaking change)** `XML::Node#content` now returns an empty `String` when no content is available +* `HTTP::Client` now automatically reconnects on a dropped keep-alive connection +* `with ... yield` now works well with `method_missing` +* Class variables can now be used in generic types (all generic instances share the same variable, and subclasses get their own copy, as usual) +* Added support for LLVM 4 (thanks @ysbaddaden) +* Added `Enum.each` and `Enum#each` (thanks @ysbaddaden) +* Added `Hash#compact` and `Hash#compact!` (thanks @MakeNowJust) +* Added `IO#read_string(bytesize)` +* Added `IO#skip_to_end` +* Added `Iterator#flat_map` (thanks @MakeNowJust) +* Added `JSON.build` and `JSON::Builder` +* Added `NamedTuple#has_key?(String)` (thanks @Sija) +* Added `p(NamedTuple)` (thanks @splattael) +* Added `Regex::MatchData#==` (thanks @MakeNowJust) +* Added `String#sub(Regex, NamedTuple)` (thanks @maiha) +* Added `XML.build` and `XML::Builder` +* Lots of improvements and applied consistencies to doc comments (thanks @Sija and @maiha) +* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.4) + +## 0.20.3 (2016-12-23) + +* **(breaking change)** `IO#gets`, `IO#each_line`, `String#lines`, `String#each_line`, etc. now chomp lines by default. You can pass `chomp: false` to prevent automatic chomping. Note that `chomp` is `true` by default for argless `IO#gets` (read line) but `false` if args are given. +* **(breaking change)** `HTTP::Handler` is now a module instead of a class (thanks @andrewhamon) +* **(breaking change)** Free variables now must be specified with `forall`, a single uppercase letter will not work anymore +* **(breaking change)** The `libs` directory is no longer in the default CRYSTAL_PATH, use `lib` (running `crystal deps` should fix this) +* Optimized compile times, specially on linux +* `private` can now be used with macros inside types (thanks @MakeNowJust) +* CLI: the `-s`/`--stats` option now also shows execution time (thanks @MakeNowJust) +* CLI: added `-t`/`--time` to show execution time (thanks @MakeNowJust) +* `Socket` now allows any family/type/protocol association, [and many other improvements](https://github.com/crystal-lang/crystal/pull/3750) (thanks @ysbaddaden) +* YAML: an `IO` can now be passed to `from_yaml` (thanks @MakeNowJust) +* Added `class_getter`, `class_setter`, `class_property`, etc. (thanks @Sija) +* Added `String#lchomp` (thanks @Sija) +* Added `IO#read_fully?` +* Added `Iterator#flatten` (thanks @MakeNowJust) +* Added `HTTP::WebSocket#ping`, `pong`, `on_ping`, `on_pong`, and now a ping message is automatically replied with a pong message (thanks @Sija) +* Added `File#empty?` and `Dir#empty?` (thanks @dylandrop) +* Added `Time::Span#/(Time::Span)` (thanks @RX14) +* Added `String#split` versions that accept a block (thanks @splattael) +* Added `URI#normalize` and `normalize!` (thanks @taylorfinnell) +* Added `reuse` optional argument to many `Array`, `Enumerable` and `Iterable` methods that allow you to reuse the yielded/return array for better performance and less memory footprint +* The `:debug` flag is now present when compiled with `--debug`, useful for doing `flag?(:debug)` in macros (thanks @luislavena) +* [Many bug fixes and performance improvements](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.3) + +## 0.20.1 (2016-12-05) + +* **(breaking change)** `Set#merge` as renamed to `Set#merge!` +* **(breaking change)** `Slice.new(size)` no longer works with non primitive integers and floats +* **(breaking change)** The macro method `argify` was renamed to `splat` +* Added pretty printing. The methods `p` and `pp` now use it. To get the old behaviour use `puts obj.inspect` +* Added `ArrayLiteral#[]=`, `TypeNode#constant`, `TypeNode#overrides?` and `HashLiteral#double_splat` in macros +* Added a `finished` macro hook that runs at the end of the program +* Added support for declaring the type of a local variable +* Added `Slice.empty` +* Flags enums now have a `none?` method +* `IO::ByteFormat` has now methods to encode/decode to/from a `Slice` +* Spec: the line number passed to run a specific `it` block can now be inside any line of that block +* The `CallConvention` attribute can now also be applied to a `lib` declaration, and all `fun`s inside it will inherit it +* The `method_missing` hook can now define a method, useful for specifying block arguments +* Support double splat in macros (`{{**...}}`) +* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.1) + +## 0.20.0 (2016-11-22) + +* **(breaking change)** Removed `ifdef` from the language +* **(breaking change)** Removed `PointerIO` +* **(breaking change)** The `body` property of `HTTP::Request` is now an `IO?` (previously it was `String`). Use `request.body.try(&.gets_to_end)` if you need the entire body as a String. +* **(breaking change)** `MemoryIO` has been renamed to `IO::Memory`. The old name can still be used but will produce a compile-time warning. `MemoryIO` will be removed immediately after 0.20.0. +* **(breaking change)** `Char#digit?` was split into `Char#ascii_number?` and `Char#number?`. The old name is still available and will produce a compile-time warning, but will be removed immediately after 0.20.0. +* **(breaking change)** `Char#alpha?` was split into `Char#ascii_letter?` and `Char#letter?`. The old name is still available and will produce a compile-time warning, but will be removed immediately after 0.20.0. +* **(breaking change)** The `Iterable` module is now generic +* Many `String` and `Char` methods are now unicode-aware, for example `String#downcase`, `String#upcase`, `Char#downcase`, `Char#upcase`, `Char#whitespace?`, etc. +* Added support for HTTP client and server streaming. +* Added support for ARM (thanks @ysbaddaden) +* Added support for AArch64 (thanks @ysbaddaden) +* Added support for LLVM 3.9 (thanks @ysbaddaden) +* Added `__END_LINE__` magic constant in method default arguments: will be the last line of a call (if the call has a block, it will be the last line of that block) +* Added `@def` inside macros that takes the value of the current method +* API docs have a nicer style now, and notes like TODO and DEPRECATED are better highlighted (thanks @samueleaton) +* Slight improvement to debugging support (thanks @ggiraldez) +* Line numbers in backtraces (linux only for now) (thanks @ysbaddaden) +* Added iteration times to `Benchmark.ips` (thanks @RX14) +* Allow `HTTP::Client` block initializer to be used when passing an URI (thanks @philnash) +* `JSON.mapping` and `YAML.mapping` getter/setter generation can now be controlled (thanks @zatherz) +* `Time` is now serializable to JSON and YAML using ISO 8601 date-time format +* Added `IO::MultiWriter` (thanks @RX14) +* Added `String#index(Regex)` and `String#rindex(Regex)` (thanks @zatherz) +* Added `String#partition` and `String#rpartition` (thanks @johnjansen) +* Added `FileUtils.cd`, `FileUtils.mkdir`, `FileUtils.mkdir_p`, `FileUtils.mv`, `FileUtils.pwd`, `FileUtils.rm`, `FileUtils.rm_r`, `FileUtils.rmdir` (thanks @ghivert) +* Added `JSON::Builder#raw_field` (thanks @kostya) +* Added `Enumerable#chunks` and `Iterator#chunk` (thanks @kostya) +* Added `Iterator#with_index` +* Several enhancements to the Random module: now works for any integer type and avoids overflows (thanks @BlaXpirit) +* Optimized `Array#sort` by using introsort (thanks @c910335) +* [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.0) + +## 0.19.4 (2016-10-07) + +* Added support for OpenBSD (thanks @wmoxam and @ysbaddaden) +* More iconv fixes for FreeBSD (thanks @ysbaddaden) +* Changed how `require` works for the upcoming `shards` release (this is backwards compatible). See https://github.com/crystal-lang/crystal/pull/2788 +* Added `Atomic` and exposed all LLVM atomic instructions to Crystal (needed to implemented multiple-thread support) +* Added `Process.executable_path` (thanks @kostya, @whereami and @ysbaddaden) +* Added `HTML.unescape` (thanks @dukex) +* Added `Char#+(Int)` and `Char#-(Int)` +* [A few bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.4) + +## 0.19.3 (2016-09-30) + +* `crystal eval` now accepts some flags like `--stats`, `--release` and `--help` +* Added `File.chown` and `File.chmod` (thanks @ysbaddaden) +* Added `Time::Span.zero` (useful for doing `sum`) (thanks @RX14) +* Added docs to `OAuth` and `OAuth2` +* [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.3) + +## 0.19.2 (2016-09-16) + +* Generic type variables no longer need to be single-letter names (for example `class Gen(Foo)` is now possible) +* Added syntax to denote free variables: `def foo(x : T) forall T`. The old rule of single-letter name still applies but will be removed in the future. +* Removed the restriction that top-level types and constants can't have single-letter names +* Added `@[Extern]` attribute to mark regular Crystal structs as being able to be used in C bindings +* Faster `Char#to_s` when it's ASCII: this improves the performance of JSON and CSV parsing +* `crystal spec`: allow passing `--release` and other options +* `crystal spec`: allow running all specs in a given directory +* `crystal playground`: support custom workbook resources (thanks @bcardiff) +* `crystal playground`: standard output now understands ANSI colors (thanks @bcardiff) +* Added many more macro methods to traverse AST nodes (thanks @BlaXpirit) +* Error messages no longer include a type trace by default, pass `--error-trace` to show the full trace (the trace is often useless and makes it harder to understand error messages) +* [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.2) + +## 0.19.1 (2016-09-09) + +* Types (class, module, etc.) can now be marked as `private`. +* Added `WeakRef` (thanks @bcardiff) +* [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.1) + +## 0.19.0 (2016-09-02) + +* **(breaking change)** Added `select` keyword +* **(breaking change)** Removed $global variables. Use @@class variables instead. +* **(breaking change)** Heredoc now ends when the matching identifier is found, either followed by a space or by a non-identifier +* **(breaking change)** Assignment to a local variable inside an assignment to that same variable is now an error +* **(breaking change)** Type names like `T`, `T1`, `U`, etc., are now disallowed at the top level, to avoid conflicts with free variables +* **(breaking change)** Type lookup (`Foo::Bar::Baz`) had some incorrect behaviour that now is fixed. This can break existing code that relied on this incorrect behaviour. The fix is to fully qualify types (`::Foo::Bar::Baz`) +* **(breaking change)** In relationships like `class Bar < Foo(Baz)` and `include Moo(Baz)`, all of `Foo`, `Moo` and `Baz` must be defined before that point (this was not always the case in previous versions) +* **(breaking change)** Removed the deprecated syntax `x as T` +* **(breaking change)** Removed block form of `String#match` +* **(breaking change)** Removed `IO#read_nonblock` +* **(breaking change)** `Int#/` now performs floored division. Use `Int#tdiv` for truncated division (see their docs to learn the difference) +* Added support for LLVM 3.8 (thanks @omarroth) +* `||` now does type filtering +* Generic inheritance should now work well, and (instantiated) generic modules can now be used as the type of instance variables +* `NamedTuple` can now be accessed with strings too (thanks @jhass) +* `Base64` can now encode and decode directly to an `IO` (thanks @kostya) +* `BigInt` now uses GMP implementation of gcd and lcm (thanks @endSly) +* `ECR` now supports removing leading and trailing whitespace (`<%-`, `-%>`) +* `HTTP::Request#path` now never returns `nil`: it fallbacks to `"/"` (thanks @jhass) +* `String#tr(..., "")` is now the same as `String#delete` +* `tool hierarchy` now supports `--format json` (thanks @bmulvihill) +* Added `Char#ascii?` +* Added `Class#nilable?` and `Union#nilable?` +* Added `Hash#has_value?` (thanks @kachick) +* Added `IO::Sized` and `IO::Delimited` (thanks @RX14) +* Added `IO::Hexdump` (thanks @ysbaddaden) +* Added `IO#noecho` and `IO#noecho!` (thanks @jhass) +* Added `Logger.new(nil)` to create a null logger +* Added `OptionParser#missing_option` and `OptionParser#invalid_option` (thanks @jhass) +* Added `Process.exists?`, `Process#exists?` and `Process#terminated?` (thanks @jhass) +* Added `Process.exec` (thanks @jhass) +* Added `Slice#copy_to`, `Slice#copy_from`, `Slice#move_to` and `Slice#move_from` (thanks @RX14) +* Added `URI#==` and `URI#hash` (thanks @timcraft) +* Added `YAML#parse(IO)` +* Added `Indexable` module that `Array`, `Slice`, `Tuple` and `StaticArray` include +* Added `indent` parameter to `to_pretty_json` +* Added lazy form of `getter` and `property` macros +* Added macro methods to access an ASTNode's location +* Unified String and Char to integer/float conversion API (thanks @jhass) +* [Lots of bug fixes](https://github.com/crystal-lang/crystal/milestone/5?closed=1) + +## 0.18.7 (2016-07-03) + +* The `compile` command was renamed back to `build`. The `compile` command is deprecated and will be removed in a future version +* Fibers now can be spawned with a name +* ECR macros can now be required with just `require "ecr"` +* [Several bugs fixes and enhancements](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.7+is%3Aclosed) + +## 0.18.6 (2016-06-28) + +* `T?` is now parsed as `Union(T, Nil)` outside the type grammar +* Added `String#sub` overloads for replacing an index or range with a char or string +* [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.6+is%3Aclosed) + +## 0.18.5 (2016-06-27) + +* Added `OpenSSL::SSL::Socket#alpn_protocol` +* Added `IO#copy(src, desc, limit)` (thanks @jreinert) +* Added `TypeNode#instance` macro method +* [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.5+is%3Aclosed) + +## 0.18.4 (2016-06-21) + +* Fixed [#2887](https://github.com/crystal-lang/crystal/issues/2887) +* Fix broken specs + +## 0.18.3 (2016-06-21) + +* `TypeNode`: added `<`, `<=`, `>` and `>=` macro methods +* [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.3+is%3Aclosed) + +## 0.18.2 (2016-06-16) + +* Fixed building Crystal from the source tarball + +## 0.18.1 (2016-06-16) + +* Spec: passing `--profile` shows the slowest 10 specs (thanks @mperham) +* Added `StringLiteral#>` and `StringLiteral#<` in macros +* [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.1+is%3Aclosed) + +## 0.18.0 (2016-06-14) + +* **(breaking change)** `IniFile` was renamed to `INI`, and its method `load` renamed to `parse` +* **(breaking change)** `Process.getpgid` was renamed to `Process.pgid` +* **(breaking change)** An `Exception`'s backtrace is now set when it's raised, not when it's created: it's `backtrace` method raises if it's not set, and there's `backtrace?` to get it as a nilable array +* **(breaking change)** `dup` is now correctly implemented in all types. `clone` is not defined by default, but some types in the standard library do. Also check `Object#def_clone` +* **(breaking change)** the `method_missing` macro only accepts a single argument: a `Call` now. The form that accepted 3 arguments was removed. +* **(breaking change)** the `delegate` macro must now be used like `delegate method1, method2, ..., methodN, to: object` +* **(breaking change)** `Hash#each_with_index` and `Hash#each_with_object` now yield a tuple (pair) and an index, because `Hash` is now `Enumerable`. Use `do |(key, value), index|` for this. +* **(breaking change)** `{"foo": 1}` denotes a named tuple literal now, not a hash literal. Use `{"foo" => 1}` instead. This also applies to, for example `HTTP::Headers{...}` +* **(breaking change)** Extra block arguments now give a compile-time error. This means that methods that yield more than once, one time with N arguments and another time with M arguments, with N < M, will always give an error. To fix this, add M - N `nil` fillers on the yield side (this makes it more explicit that `nil` was intended to be a block argument value) +* **(breaking change)** `OpenSSL::SSL::Context` and `OpenSSL::SSL::Socket` can no longer be used directly anymore. Use their respective subclasses `OpenSSL::SSL::Context::Client`, + with `OpenSSL::SSL::Socket::Client`, `OpenSSL::SSL::Context::Server` with `OpenSSL::SSL::Socket::Server`. +* **(breaking change)** TLS server and client sockets now use sane defaults, including support for hostname verification for client sockets, used by default in `HTTP::Client`. +* **(breaking change)** The `ssl` option was renamed to `tls` in `HTTP::Client`, `HTTP::Server`, `HTTP::WebSocket`, `OAuth::Consumer`, `OAuth::Signature` and `OAuth2::AccessToken`. +* The `dns_timeout` setting in a few classes like `HTTP::Client` and `TCPSocket` is now ignored until a next version supports a non-blocking `getaddrinfo` equivalent +* `OpenSSL::SSL::Socket::Client` supports server name indication now. +* The `build` command was renamed to `compile`. The `build` command is deprecated and will be removed in a future version +* The `--cross-compile` flag no longer takes arguments, use `--target` and `-D` +* Added a `Union` type that represents the type of a union, which can have class methods +* Methods, procs and lib functions that are marked as returning `Void` now return `Nil` +* Methods that are marked as returning `Nil` are not checked for a correct return type, they always return `nil` now +* When `as` fails at runtime it now includes which type couldn't be cast +* Macros can now be used inside `lib` and `enum` declarations +* Macros can now be declared inside enums +* Macro calls can now be used as enum values +* Generic types can now include a splatted type variable. This already existed in the language (`Tuple(*T)`, `Proc(*T)`) but there was no syntax to define such types. +* Class variables are now inherited (only their type, not their value). They are now similar to Ruby class instance variables. +* Splats in `yield` can now be used +* Splat in block arguments can now be used. +* Added block auto-unpacking: if a method yields a tuple and a block specifies more then one block argument, the tuple is unpacked to these arguments +* String literals are now allowed as external method arguments, to match named tuples and named arguments +* `sizeof` and `instance_sizeof` can now be used as generic type arguments (mostly useful combined with `StaticArray`) +* `Hash`, `HTTP::Headers`, `HTTP::Params` and `ENV` now include the `Enumerable` module +* `Proc` is now `Proc(*T, R)` +* `Tuple(*T).new` and `NamedTuple(**T).new` now correctly match the given `T` ([#1828](https://github.com/crystal-lang/crystal/issues/1828)) +* `Float64#to_s` now produces an ever more accurate output +* `JSON` parsing now correctly handle floats with many digits +* `JSON.mapping` and `YAML.mapping` now also accept named arguments in addition to a hash literal or named tuple literal +* `Int#chr` now raises if the integer is out of a char's range. The old non-raising behaviour is now in `Int#unsafe_chr`. +* The output of `pp x` is now `x # => ...` instead of `x = ...` +* The output of the `debug()` macro method now tries to format the code (pass `false` to disable this) +* Added `JSON` and `YAML` parsing and mapping for unions +* Added `FileUtils.cp_r` (thanks @Dreauw) +* Added `Tuple.from` and `NamedTuple.from` (thanks @jhass) +* Added `XML.escape` (thanks @juanedi) +* Added `HTTP::Server::Response#respond_with_error` (thanks @jhass) +* Added `TCPServer#accept?` +* Added optional `base` argument to `Char#digit?` and `Char#hex?` (thanks @mirek) +* Added `flag?` macro method, similar to using `ifdef`. `ifdef` is deprecated and will be removed in a future version. +* Added `YAML::PullParser#read_raw` +* Added `Proc#partial` +* Added `Socket.ip?(str)` to validate IPv4 and IPv6 addresses +* Added `Bytes` as an alias of `Slice(UInt8)` +* Added `RangeLiteral` macro methods: `begin`, `end`, `excludes_end?`, `map` and `to_a` +* Added `ArrayLiteral#[range]` and `ArrayLiteral#[from, to]` in macros (applicable for `TupleLiteral` too) +* Added `Generic` macro methods: `name`, `type_vars`, `named_args` +* Spec: added JUnit formatter output (thanks @juanedi) +* The `tls` option in `HTTP::Client` can now take a `OpenSSL::SSL::Context::Client` in addition to `true`. +* `HTTP::LogHandler` logs exceptions now (thanks @jhass) +* `HTTP::ErrorHandler` does not tell the client which exception occurred by default (can be enabled with a `verbose` flag) (thanks @jhass) +* Several bug fixes + +## 0.17.4 (2016-05-26) + +* Added string literals without interpolations nor escapes: `%q{...}` and `<<-'HEREDOC'`. Also added `%Q{...}` with the same meaning as `%{...}`. +* A method that uses `@type` inside a macro expression is now automatically detected as being a `macro def` +* `Float64#to_s` now produces a more accurate output +* Added `Crystal::VERSION` and other compiler-metadata constants +* Added `Object.from_json(string_or_io, root)` and a `root` option to `JSON.mapping` +* Added `System.hostname` (thanks @miketheman) +* The `property`, `getter` and `setter` macros now also accept assignments (`property x = 0`) +* The `record` macro now also accepts assignments (`record Point, x = 0, y = 0`) +* Comparison in macros between `MacroId` and `StringLiteral` or `SymbolLiteral` now work as expected (compares the `id` representation) +* Some bug fixes + +## 0.17.3 (2016-05-20) + +* Fixed: multiple macro runs executions didn't work well ([#2624](https://github.com/crystal-lang/crystal/issues/2624)) +* Fixed incorrect formatting of underscore in unpacked block arguments +* Fixed wrong codegen for global variable assignment in type declaration ([#2619](https://github.com/crystal-lang/crystal/issues/2619)) +* Fixed initialize default arguments where evaluated at the class scope ([#731](https://github.com/crystal-lang/crystal/issues/731)) +* The type guesser can now infer a block type from `def initialize(&@block)` +* Allow type restriction in double splat argument (similar to restriction in single splat) +* Allow splat restriction in splat argument (useful for `Tuple.new`) +* Allow double splat restriction in double splat argument (useful for `NamedTuple.new`) + +## 0.17.2 (2016-05-18) + +* Fixed crash when using pointerof of constant + +## 0.17.1 (2016-05-18) + +* Constants and class vars are no longer initialized before "main". Now their initialization order goes along with "main", similar to how it works in Ruby (much more intuitive) +* Added syntax for unpacking block arguments: `foo { |(x, y)| ... }` +* Added `NamedTupleLiteral#map` and `HashLiteral#map` in macros (thanks @jhass) +* Fixed wrong codgen for tuples/named tuples merge with pass-by-value types +* Formatter: fixed incorrect format for named tuple type + +## 0.17.0 (2016-05-17) + +* **(breaking change)** Macro defs are now parsed like regular methods. Enclose the body with `{% begin %} .. {% end %}` if you needed that behaviour +* **(breaking change)** A union of two tuples of the same size results in a tuple with the unions of the types in each position. This only affects code that later tested a tuple's type with `is_a?`, for example `tuple.is_a?({Int32, String})` +* **(breaking change)** Method arguments have now a different semantic. This only affects methods that had a splat argument followed by other arguments. +* **(breaking change)** The syntax `{foo: 1, bar: 2}` now denotes a `NamedTuple`, not a `Hash` with symbol as keys. Use `{:foo => 1, :bar => 2}` instead +* The syntax `exp as Type` is now deprecated and will be removed in the next version. Use `crystal tool format` to automatically upgrade your code +* The compiler now gives an error when trying to define a method named `!`, `is_a?`, `responds_to?`, `nil?`, `as` or `as?` +* Added the `NamedTuple` type +* Added double splatting +* Added external argument names +* Macro defs return type is no longer mandatory +* Added `as?`: similar to `as`, but returns `nil` when the type doesn't match +* Added `Number::Primitive` alias +* Added `Tuple#+(Tuple)` +* Added `ArrayLiteral#+(ArrayLiteral)` in macros +* `Crypto::MD5` now allows `Slice(UInt8)` and a block form (thanks @will) +* Added docs for XML (thanks @Hamdiakoguz) +* Many bug fixes + +## 0.16.0 (2016-05-05) + +* **(breaking change)** Instance, class and global variables types must be told to the compiler, [either explicitly or through a series of syntactic rules](http://crystal-lang.org/docs/syntax_and_semantics/type_inference.html) +* **(breaking change)** Non-abstract structs cannot be inherited anymore (abstract structs can), check the [docs](http://crystal-lang.org/docs/syntax_and_semantics/structs.html) to know why. In many cases you can use modules instead. +* **(breaking change)** Class variables are now initialized at the beginning of the program (before "main"), make sure to read the docs about [class variables](http://crystal-lang.org/docs/syntax_and_semantics/class_variables.html) and [main](http://crystal-lang.org/docs/syntax_and_semantics/the_program.html) +* **(breaking change)** Constants are now initialized at the beginning of the program (before "main"), make sure to read the docs about [constants](http://crystal-lang.org/docs/syntax_and_semantics/constants.html) and [main](http://crystal-lang.org/docs/syntax_and_semantics/the_program.html) +* **(breaking change)** When doing `crystal program.cr arg1 arg2 arg3`, `arg1`, `arg2` and `arg3` are considered arguments to pass to the program (not the compiler). Use `crystal run program.cr arg1 ...` to consider `arg1` a file to include in the compilation. +* **(breaking change)** `Int#**(Int)` now returns an integer, and raises if the argument is negative. Use a float base or exponent for negative exponents to work. +* **(breaking change)** `Slice#to_s` and `StaticArray#to_s` now include their type name in the output +* Support for FreeBSD and musl libc has landed (thanks @ysbaddaden) +* The `.crystal` directory is now created at `$HOME/.cache/crystal` or `$HOME/.crystal` (or others similar), with a fallback to the current directory +* `crystal doc` and `crystal tool hierarchy` are now much faster. Additionally, the hierarchy tool shows types for generic types, and doesn't show instantiations anymore (wasn't very useful) +* `!` now does type filtering (for example you can do `!x || x.bar`, assuming `x` can be `nil` and the non-nil type responds to `bar`) +* Named arguments can now match any argument, even if they don't have a default value. Make sure to read the [docs](http://crystal-lang.org/docs/syntax_and_semantics/default_and_named_arguments.html) +* The `as` operator can now be written as a method: `exp.as(Type)` in addition to `exp as Type`. The old syntax will be removed in a few releases. +* Added `@x : Int32 = 1` syntax (declaration + initialization) +* `new`/`initialize` logic now works more as one would expect +* Added `BigRational` (thanks @will) +* Added `BigFloat` (thanks @Exilor) +* Added `String#insert` +* Added `Time::EpochConverter` and `Time::EpochMillisConverter` +* Added `%s` (unix epoch) directive to `Time::Format` +* `Time` now honours Daylight Saving and `ENV["TZ"]` +* Added `HTTP::Server::Response#cookies` (thanks @jhass) +* Added `Array#bsearch`, `Array#bsearch_index` and `Range#bsearch` (thanks @MakeNowJust) +* Added `Range#reverse_each` iterator (thanks @omninonsense) +* `JSON::Any`: added `as_...?` methods (thanks @DougEverly) +* `JSON::Any` is now `Enumerable` +* `YAML::Any` is now `Enumerable` +* Added `JSON.parse_raw` that returns a `JSON::Type` +* `JSON::PullParser`: added `#read_raw` to read a JSON value as a raw string (useful for delayed parsing). Also added `String::RawConverter` to be used with `JSON.mapping`. +* `JSON` and `YAML`: enums, `BigInt` and `BigFloat` are now serializable +* `ENV`: allow passing `nil` as a value to delete an environment variable +* `Hash`: allow `Array | Tuple` arguments for `#select`, `#select!`, `#reject` and `#reject!` +* `Crypto::Subtle.constant_time_compare` now returns `Bool`, and it can compare two strings in addition to two slices (thanks @skunkworker) +* `HTTP::Server`: reset port zero after listening (thanks @splattael) +* Added `File#each_line` iterator +* Added `Number.slice`, `Number.static_array`, `Slice.[]` and `StaticArray.[]` to easily create slices and static arrays +* Added `Slice#hexdump` (thanks @will) +* Added `Enumerable#product` (thanks @dkhofer) +* Fix: disallow using `out` with `Void*` pointers +* Fixed bug in `XML::Node#namespace_scopes` (thanks @Hamdiakoguz) +* Added docs for `INIFile` (thanks @EvanHahn) +* Lots of bug fixes + +## 0.15.0 (2016-03-31) + +* **(breaking change)** `!` has now its meaning hardcoded in the language. If you defined it for a type it won't be invoked as a method anymore. +* **(breaking change)** `nil?` has now its meaning hardcoded in the language. If you defined it for a type it won't be invoked as a method anymore. +* **(breaking change)** `typeof` is now disallowed in `alias` declarations +* Added `crystal tool format --check` to check that source code is properly formatted +* `crystal play` (playground) added workbooks support, as well as improvements and stabilizations +* Added `Tempfile.dirname` (thanks @DougEverly) +* Added `Path#resolve` method in macros +* `{{...}}` arguments to a macro call are now expanded before macro invocation ([#2392](https://github.com/crystal-lang/crystal/issues/2392)) +* Special variables (`$~` and `$?`) are now accessible after being defined in blocks ([#2194](https://github.com/crystal-lang/crystal/issues/2194)) +* Some bugs and regressions fixed + +## 0.14.2 (2016-03-22) + +* Fixed regression with formatter ([#2348](https://github.com/crystal-lang/crystal/issues/2348)) +* Fixed regression with block return types ([#2347](https://github.com/crystal-lang/crystal/issues/2347)) +* Fixed regression with openssl (https://github.com/crystal-lang/crystal/commit/78c12caf2366b01f949046e78ad4dab65d0d80d4) + +## 0.14.1 (2016-03-21) + +* Fixed some regressions in the formatter + +## 0.14.0 (2016-03-21) + +* **(breaking change)** The syntax of a method argument with a default value and a type restriction is now `def foo(arg : Type = default_value)`. The old `def foo(arg = default_value : Type)` was removed. +* **(breaking change)** `Enumerable#take(n)` and `Iterator#take(n)` were renamed to `first(n)` +* **(breaking change)** `Socket#addr` and `Socket#peeraddr` were renamed to `local_address` and `remote_address` respectively +* **(breaking change)** Removed `Comparable#between?(a, z)`. Use `a <= x <= z` instead +* **(breaking change)** `HTTP::WebSocketHandler` callbacks can now access the `HTTP::Context`. If you had a forwarding method to it you'll need to update it. See [#2313](https://github.com/crystal-lang/crystal/issues/2313). +* New command `crystal play` that opens a playground for you to play in the browser :-) (thanks @bcardiff) +* New command `crystal env` that prints environment information +* `Spec`: you can now run multiple files with specified line numbers, as in `crystal spec file1.cr:10 file2.cr:20 ...` +* Initial support for musl-libc (thanks @ysbaddaden) +* Added `FileUtils.cp` (thanks @Dreauw) +* Added `Array#first(n)` and `Array#last(n)` (thanks @greyblake) +* Added `WebSocket#close` and properly handle disconnections +* Added `UDPSocket#send` and `UDPSocket#receive` (thanks @tatey) +* Added `Char#uppercase?` and `Char#lowercase?` (thanks @MaloJaffre`) +* Added `sync_close` property to `OpenSSL::SSL::Socket`, `Zlib::Inflate` and `Zlib::Deflate` +* Added `XML::Node#encoding` and `XML::Node#version` +* Added `HTTP::Client::Response#success?` (thanks @marceloboeira) +* Added `StaticArray#shuffle!(random)` (thanks @Nesqwik) +* Added `Splat#exp` method in macros +* Added fiber-safe `Mutex` +* All `Int` types (except `BigInt`) can now be used in `JSON` and `YAML` mappings (thanks @marceloboeira) +* Instance variable declarations/initializations now correctly work in generic classes and modules +* Lots of bug fixes + +## 0.13.0 (2016-03-07) + +* **(breaking change)** `Matrix` was moved to a separate shard: [https://github.com/Exilor/matrix](https://github.com/Exilor/Matrix) +* The syntax of a method argument with a default value and a type restriction is now `def foo(arg : Type = default_value)`. Run `crystal tool format` to automatically upgrade existing code to this new syntax. The old `def foo(arg = default_value : Type)` syntax will be removed in a next release. +* Special handling of `case` with a tuple literal. See [#2258](https://github.com/crystal-lang/crystal/pull/2258). +* Keywords can now be used for variable declaration, so `property end : Time` works as expected. +* Comparison of signed vs. unsigned integers now always give a correct result +* Allow declaring instance variables in non-generic module types (`module Moo; @x : Int32; end`) +* Allow initializing instance variables in non-generic module types (`module Moo; @x = 1; end`) +* `Spec`: allow setting multiple output formatters (thanks @marceloboeira) +* `StringScanner`: improved performance +* Added `foo.[0] = 1` and `foo.[0]` as valid syntax, similar to the one in `&.` blocks (thanks @MakeNowJust) +* `CSV`: allow separate and quote characters different than comma and doble quote (thanks @jreinert) +* `YAML`: support merge operator (`<<`) (thanks @jreinert) +* Allow redefining primitive methods like `Int32#+(other : Int32)` +* Allow defining macros with operator names like `[]` +* `Levenshtein`: improved performance (thanks @tcrouch) +* `HTTP::Client`: fixed incorrect parsing of chunked body +* `HTTP::Client`: added a constructor with an `URI` argument (thanks @plukevdh) +* `String`: `sub` and `gsub` now understand backreferences (thanks @bjmllr) +* `Random`: added `Random#rand(Float64)` and `Random#rand(Range(Float, Float))` (thanks @AlexWayfer) +* `HTML`: `HTML.escape` includes more characters (thanks @Ryuuzakis) +* Added `TypeNode.class` method in macros (thanks @waterlink) +* `run` inside macros now also work with absolute paths (useful when used with `__DIR__`) +* Added docs for `Math` and `StaticArray` (thanks @Zavydiel, @HeleneMyr) +* Many bug fixes and some micro-optimizations + +## 0.12.0 (2016-02-16) + +* **(breaking change)** When used with a type declaration, the macros `property`, `getter`, `setter`, etc., declare instance variables with those types. +* **(breaking change)** `JSON.mapping` and `YAML.mapping` declare instance variables with the given types. +* **(breaking change)** `YAML.load` was renamed to `YAML.parse`, and it now returns a `YAML::Any`. +* **(breaking change)** `embed_ecr` and `ecr_file` were renamed to `ECR.embed` and `ECR.def_to_s` (the old methods now produce a warning and will be removed in the next release). +* Added encoding support: `IO#set_encoding`, `String#encode`, and `HTTP::Client` charset check. +* Segmentation faults are now trapped and shown in a more friendlier way. +* The `record` macro can now accept type declarations (for example `record Point, x : Int32, y : Int32`) +* Added `Iterator#step` (thanks @jhass) +* `Array#push` and `Array#unshift` can now accept multiple values and add the elements in an efficient way (thanks @arktisklada) +* Added `default` option to `JSON.mapping` (thanks @kostya) +* Added `default` option to `YAML.mapping` (thanks @jreinert) +* Allow doing `case foo; when Foo.class` (and `Foo(T)` and `Foo(T).class`) in case expressions. +* Added `Class#|` so a union type can be expresses as `Int32 | Char` in regular code. +* Added `File.real_path` (thanks @jreinert) +* Added `dns_timeout` for `HTTP::Client` (thanks @kostya) +* Added dynamic width precision to `sprintf` (thanks @gtramontina) +* `Markdown` now supports blockquotes and 1 level of list nesting (thanks @SebastianSzturo) +* `p` now accepts multiple arguments +* Many bug fixes and some optimizations + +## 0.11.1 (2016-01-25) +* Fixed [#2050](https://github.com/crystal-lang/crystal/issues/2050), [#2054](https://github.com/crystal-lang/crystal/issues/2054), [#2057](https://github.com/crystal-lang/crystal/issues/2057), [#2059](https://github.com/crystal-lang/crystal/issues/2059), [#2064](https://github.com/crystal-lang/crystal/issues/2064) +* Fixed bug: HTTP::Server::Response headers weren't cleared after each request +* Formatter would incorrectly change `property x :: Int32` to `property x = uninitialized Int32` + +## 0.11.0 (2016-01-23) + +* **(breaking change)** Syntax for type declarations changed from `var :: Type` to `var : Type`. The old syntax is still allowed but will be deprecated in the next version (run `crystal tool format` to automatically fix this) +* **(breaking change)** Syntax for uninitialized variables, which used to be `var :: Type`, is now `var = uninitialized Type`. The old syntax is still allowed but will be deprecated in the next version (run `crystal tool format` to automatically fix this) +* **(breaking change)** `HTTP::Server` refactor to support streaming. Check the [docs](http://crystal-lang.org/api/HTTP/Server.html) of `HTTP::Server` for upgrade instructions +* **(breaking change)** Renamed `HTTP::WebSocketSession` to `HTTP::WebSocket`. +* **(breaking change)** Heredocs now remove indentations according to the indentation of the closing identifier (thanks @rhysd) +* **(breaking change)** Renamed `Enumerable#inject` to `Enumerable#reduce` +* **(breaking change)** `next` and `return` semantic inside captured block has been swapped ([#420](https://github.com/crystal-lang/crystal/issues/420)) +* Fibers context switch is now faster, done with inline assembly. `libpcl` is no longer used +* Allow annotating the type of class and global variables +* Support comments in ECR (thanks @ysbaddaden) +* Security improvements to `HTTP::StaticFileHandler` (thanks @MakeNowJust) +* Moved `seek`, `tell`, `pos` and `pos=` from `File` to `IO::FileDescriptor` (affects `Tempfile`) +* `URI.parse` is now faster (thanks @will) +* Many bug fixes, some really old ones involving issues with order of declaration + +## 0.10.2 (2016-01-13) + +* Fixed Directory Traversal Vulnerability in HTTP::StaticFileHandler (thanks @MakeNowJust) + +## 0.10.1 (2016-01-08) + +* Added `Int#popcount` (thanks @rmosolgo) +* Added `@[Naked]` attribute for omitting a method's prelude +* Check that abstract methods are implemented by subtypes +* Some bug fixes + +## 0.10.0 (2015-12-23) + +* **(breaking change)** `def` arguments must always be enclosed in parentheses +* **(breaking change)** A space is now required before and after def return type restriction +* **(breaking change)** Renamed `Dir.working_dir` to `Dir.current` +* **(breaking change)** Moved `HTML::Builder` to [its own shard](https://github.com/crystal-lang/html_builder) +* **(breaking change)** `String#split` now always keeps all results (never drops trailing empty strings) +* **(breaking change)** Removed `Array#buffer`, `StaticArray#buffer` and `Slice#buffer`. Use `to_unsafe` instead (so unsafe usages are easier to spot) +* **(breaking change)** Removed `String#cstr`. Use `to_unsafe` instead (so unsafe usages are easier to spot) +* Optimized Range#sum (thanks @MakeNowJust) +* Allow forward declarations for lib external vars +* Added `Int#to_s(base)` for `base = 62` (thanks @jhass) +* `JSON.parse` now returns `JSON::Any`, which allows traversal of JSON objects with less casts +* Added `OpenSSL::PKCS5` (thanks @benoist) +* MemoryIO can now be created to read/write from a Slice(UInt8). In this mode MemoryIO can't be expanded, and can optionally be written. And when creating a MemoryIO from a String, it's non-resizeable and read-only. +* Added `Object#!~` (the opposite of `=~`) +* `at_exit` now receives that exit status code in the block (thanks @MakeNowJust) +* Allow using `Set` in JSON mappings (thanks @benoist) +* Added `File.executable?`, `File.readable?` and `File.writeable?` (thanks @mverzilli) +* `Array#sort_by` and `Array#sort_by!` now use a [Schwartzian transform](https://en.wikipedia.org/wiki/Schwartzian_transform) (thanks @radarek) +* Added `Array#each_permutation`, `Array#each_combination` and `Array#each_repeated_combination` iterators +* Added optional *random* argument to `Array#sample` and `Array#shuffle` +* The `delegate` macro can now delegate multiple methods to an object (thanks @elthariel) +* Added basic YAML generation (thanks @porras) + +## 0.9.1 (2015-10-30) + +* Docs search now finds nested entries (thanks @adlerhsieh) +* Many corrections and changes to the formatter, for better consistency and less obtrusion. +* Added `OpenSSL::Cipher` and `OpenSSL::Digest` (thanks @benoist) +* Added `Char#+(String)` (thanks @hangyas) +* Added `Hash#key` and `Hash#key?` (thanks @adlerhsieh) +* Added `Time::Span#*` and `Time::Span#/` (thanks @jbaum98) +* Added `Slice#reverse_each` (thanks @omninonsense) +* Added docs for `Random` and `Tempfile` (thanks @adlerhsieh) +* Fixed some bugs. + +## 0.9.0 (2015-10-16) + +* **(breaking change)** The `CGI` module's functionality has been moved to `URI` and `HTTP::Params` +* **(breaking change)** `IO#read()` is now `IO#gets_to_end`. Removed `IO#read(count)`, added `IO#skip(count)` +* **(breaking change)** `json_mapping` is now `JSON.mapping`. `yaml_mapping` is now `YAML.mapping` +* **(breaking change)** `StringIO` is now `MemoryIO` +* Added `crystal tool format` that automatically formats your code +* `protected` methods can now be invoked between types inside a same namespace +* Removed `curses`, you can use `https://github.com/jreinert/ncurses-crystal` +* `to_unsafe` and numeric conversions are now also automatically performed in C struct and union fields +* Added `{% begin %} ... {% end %}` as an alternative to `{% if true %} ... {% end %}` +* Added `~!` operator +* Added debug metadata for char, float, bool and enums. Also for classes and structs (experimental) +* `Dir.glob` now works well with recursive patterns like `**` (thanks @pgkos) +* Added `read_timeout` and `connect_timeout` to `HTTP::Client` (thanks @benoist) +* Added `Zlib` (thanks @datanoise and @bcardiff) +* Added `HTTP::DeflateHandler` (thanks @bcardiff) +* Added `ENV#fetch` (thanks @tristil) +* `Hash#new` now accepts an initialize capacity argument +* `HTTP::Request` provides access and mutation of `query`, `path` and `query_params` (thanks @waterlink) +* Added `XML::Node#content=` and `#name=` +* Allow passing handlers and a block to an `HTTP::Server` (thanks @RX14) +* `crystal init` now tries to use your github username if available (thanks @jreinert) +* Added `Hash#select`, `Hash#reject` and their bang variant, and `Hash#each_with_object` (thanks @devdazed) +* Added `Hash#select(*keys)` and `Hash#reject(*keys)` and their bang variant (thanks @sdogruyol) +* Added `Set#-`, `Set#^`, and `Set#subtract` (thanks @js-ojus) +* Allow running specs without colors (thanks @rhysd) +* Added `TypeNode#has_constant?` and `TypeNode#type_vars` in macros (thanks @jreinert) +* Added `String#compare` that allows case insensitive comparisons +* Added `File#truncate` (thanks @porras) +* `CSV` is now a class for iterating rows, optionally with headers access +* Allow setting multiple `before_request` callbacks to an `HTTP::Client` +* Added `Dir.cd(&block)` (thanks @rhysd) +* Added `Class#cast` (thanks @will) +* Fixes and additions to WebSocket, like the possibility of streaming data (thanks @jreinert) +* Added `SemanticVersion` class (thanks @technorama) +* `loop` now yields a counter +* Added `Array#[]=(index, count, value)` and `Array#[]=(range, value)` +* Added argless `sleep` +* `IO#write(slice)` now writes the full slice or raises on error +* Added some docs for ECR, Markdown, Hash, File, Time, Time::Span, Colorize, String, SecureRandom, YAML (thanks @adlerhsieh, @chdorner, @vjdhama, @rmosolgo) +* Many bug fixes + +## 0.8.0 (2015-09-19) + +* **(breaking change)** Renamed a couple of types: `ChannelClosed` -> `Channel::ClosedError`, + `UnbufferedChannel` -> `Channel::Unbuffered`, `BufferedChannel` -> `Channel::Buffered`, + `DayOfWeek` -> `Time::DayOfWeek`, `MonthSpan` -> `Time::MonthSpan`, `TimeSpan` -> `Time::Span`, + `TimeFormat` -> `Time::Format`, `EmptyEnumerable` -> `Enumerable::EmptyError`, `SocketError` -> `Socket::Error`, + `MatchData` -> `Regex::MatchData`, `SignedInt` -> `Int::Signed`, `UnsignedInt` -> `Int::Unsigned`, + `FileDescriptorIO` -> `IO::FileDescriptor`, `BufferedIO` -> `IO::Buffered`, `CharReader` -> `Char::Reader`, + `PointerAppender` -> `Pointer::Appender`. +* **(breaking change)** All places that raised `DomainError` raise `ArgumentError` now. +* **(breaking change)** Renamed `Type.cast` to `Type.new` (for example, `Int32.new` instead of `Int32.cast`) +* **(breaking change)** Removed all macro instance variables except `@type` +* **(breaking change)** Removed `undef` +* **(breaking change)** Removed `length()` and `count()` methods from collections. The only method for this is now `size`. +* **(breaking change)** Removed the ability to invoke methods on a union class +* Improved debugger support +* `crystal deps` now delegates to [shards](https://github.com/ysbaddaden/shards). Removed `Projecfile` support. +* Automatically convert numeric types when invoking C functions +* Automatically define questions methods for enum members +* Support quotes inside quoted symbols (thanks @wolflee) +* Allow marking `initialize` as private +* Added `method_added` macro hook (thanks @MakeNowJust) +* Added `ArrayLiteral#includes?(obj)` in macros +* Added `ASTNode#symbolize` in macros (thanks @kirbyfan64) +* Added experimental `yaml_mapping` +* Added nilable variants to `Enumerable#max`, `Enumerable#min`, and others (thanks @technorama) +* Added `Iterator#flatten` (thanks @jpellerin) +* Added setting a read timeout to `HTTP::Client` (thanks @benoist) +* Added `Array#delete_at(index, count)` and `Array#delete_at(range)` (thanks @tebakane) +* Added `HTTP::Cookies` (thanks @netfeed) +* Added `Tuple#reverse` (thanks @jhass) +* Added `Number#clamp` (thanks @technorama) +* Added several socket options (thanks @technorama) +* Added `WebSocket.open` (thanks @kumpelblase2) +* Added `Enum.flags` macro +* Added support for sending chunked content in HTTP server (thanks @bcardiff) +* Added `future`, `lazy` and `delay` concurrency methods (thanks @technorama) +* `fork` now returns a `Process` (thanks @technorama) +* Documented `Set`, and added a couple of methods (thanks @will) +* Nicer formatting in `Benchmark.ips`, and interactive mode (thanks @will) +* The `-f` format output is now honored in compiler errors (thanks @kirbyfan64) +* Fixed an ambiguity with the `crystal build` command (thanks @MakeNowJust) +* Cast exceptions now raise `TypeCastError` instead of `Exception` (thanks @will) +* Many bugs fixes + +## 0.7.7 (2015-09-05) + +* **(breaking change)** Reimplemented `Process.run` to allow configuring input, output and error, as well as behaving well regarding non-blocking IO (thanks @technorama) +* **(breaking change)** Removed the `alias_method` macro. +* **(breaking change)** Disallow declaring defs, classes and other declarations "dynamically" (for example inside an `if`... this of course didn't work, but incorrectly compiled). +* **(breaking change)** `require` is now only allowed at the top-level, never inside other types or methods. +* **(breaking change)** Removed `Nil#to_i` +* **(breaking change)** Changed API of `Channel#select` toward a thread-safe one. +* **(breaking change)** The two methods that IO must implement are now `read(slice : Slice(UInt8))` and `write(slice : Slice(UInt8))`. +* New beautiful, searchable and more functional API docs. Thanks @rosylilly for the initial design, and @BlaxPirit for some improvements. +* CLI: Moved `browser`, `hierarchy` and `types` to `crystal tool ...` +* Added `crystal tool context` and `crystal tool implementations` for IDEs (thanks @bcardiff!!) +* `Int#>>(amount)` and `Int#<<(amount)` now give zero when `amount` is greater than the number of bits of the integer representation. +* Added `\%` escape sequence inside macros. +* Added aliases for the many C types (thanks @BlaxPirit) +* Added `Iterator#in_groups_of` (thanks @PragTob) +* Added optional `offset` argument to `Hash#each_with_index` (thanks @sergey-kucher) +* Added `Array#combinations`, `Array#each_combination`, `Array#repeated_combinations`, `Array#each_repeated_combination`, `Array#repeated_permutations`, `Array#each_repeated_permutation`, `Array.product` and `Array.each_product` (thanks @kostya) +* Added `Array#rotate` and `Array#rotate!` (thanks @kostya) +* Added `MatchData#pre_match` and `MatchData#post_match` (thanks @bjmllr) +* Added `Array#flatten` +* Added `Range.reverse_each`, along with `Int#pred` and `Char#pred` (thanks @BlaxPirit) +* Added `XML.parse_html` (thanks @ryanworl) +* Added `ENV.keys` and`ENV.values` (thanks @will) +* Added `StaticArray==(other : StaticArray)` (thanks @tatey) +* Added `String#sub` in many variants (thanks @jhass) +* Added `Readline.bind_key`, `Readline.unbind_key`, `Readline.done` and `Readline.done=` (thanks @daphee) +* Added `Hash#all?`, `Hash#any?` and `Hash#inject` (thanks @jreinert) +* Added `File#pos` and `File#pos=` +* Added `Enum.from_value` and `Enum.from_value?` +* Added `Deque` (thanks @BlaxPirit) +* Added lots of methods to `StringScanner`, and documented it, making it usable (thanks @will) +* `StringIO` now quacks like a `File`. +* Allow sending masked data through a `WebSocket`, and sending long data (thanks @kumpelblase2) +* `File.new` now accepts an optional `perm` argument (thanks @technorama) +* `FileDescriptorIO` now has configurable read/write timeouts (thanks @technorama) +* Signal handling is more robust and allows any kind of code (thanks @technorama) +* Correctly handle `WebSocket` close packet (thanks @bebac) +* Correctly implement `seek` and `tell` in buffered IOs (thanks @lbguilherme) +* Allow setting several options on sockets (thanks @technorama) +* Some improvements to `crystal init` for the "app" case (thanks @krisleech) +* `sleep` and IO timeouts can receive `TimeSpan` as arguments (thanks @BlaxPirit) +* Handle `HTTP::Response` without content-length (thanks @lbguilherme) +* Added docs for OptionParser, ENV, Regex, Enumerable, Iterator and some Array methods (thanks @porras, @will, @bjmllr, @PragTob, @decioferreira) +* Lots of bug fixes and small improvements + +## 0.7.6 (2015-08-13) + +* **(breaking change)** removed support for trailing `while`/`until` ([read this](https://github.com/crystal-lang/crystal/wiki/FAQ#why-trailing-whileuntil-is-not-supported-unlike-ruby)) +* **(breaking change)** Renamed `Enumerable#drop` to `Enumerable#skip` +* **(breaking change)** Renamed `Time.at` to `Time.epoch`, and `Time#to_i` and `Time#to_f` to `Time#epoch` and `Time#epoch_f` +* **(breaking change)** `inherited` macro now runs before a class' body +* Renamed `--no-build` flag to `--no-codegen` +* Allow interpolations in heredocs (thanks @jessedoyle) +* Allow hash substitutions in `String#%` and `sprintf` (thanks @zamith) +* Added `SecureRandom.base64`, `SecureRandom.urlsafe_base64` and `SecureRandom.uuid` (thanks @ysbaddaden) +* Added `File.link`, `File.symlink` and `File.symlink?` (thanks @ysbaddaden) +* Added `Enumerable#in_groups_of` (thanks @jalyna) +* Added `Array#zip?` (thanks @yui-knk) +* Added `Array#permutations` and `Array#each_permutation` (thanks @jalyna and @kostya) +* Added `IO#gets(limit : Int)` and `IO#gets(delimiter : Char, limit : Int)` +* Added `Iterator#compact_map`, `Iterator#take_while` and `Iterator#skip_while` (thanks @PragTob) +* Added `StringLiteral#to_i` macro method +* Added `Crypto::Bcrypt` (thanks @akaufmann) +* Added `Time.epoch_ms` and `Time#epoch_ms` +* Added `BitArray#toggle` and `BitArray#invert` (thanks @will) +* Fixed `IO#reopen` swapped semantic (thanks @technorama) +* Many bug fixes and improvements + +## 0.7.5 (2015-07-30) + +* **(breaking change)** `0` is not a prefix for octal numbers anymore. Use `0o` +* **(breaking change)** Renamed `MissingKey` to `KeyError` +* **(breaking change)** Renamed `IndexOutOfBounds` to `IndexError` +* Fixed all exception-handling related bugs. +* Allow nested and multiline ternary expressions (thanks @daviswahl) +* Allow assigning to `_` (underscore), give error when trying to read from it +* Macros can now also receive the following nodes: `And`, `Or`, `Case`, `RangeLiteral` and `StringInterpolation`. `And` and `Or` have `left` and `right` methods. +* Added `-e` option to `hierarchy` command to filter types by a regex +* Added `-v` as an alias of `--version` +* Added `-h` as an alias of `--help` +* Added `Array#transpose` (thanks @rhysd) +* Added `Benchmark#ips` (thanks @will) +* Added `Hash#merge(&block)` and `Hash#merge!(&block)` (thanks @yui-knk) +* Added `Hash#invert` (thanks @yui-knk) +* Added `Bool#^` (thanks @yui-knk) +* Added `Enumerable#drop`, `Enumerable#drop_while` and `Enumerable#take_while` (thanks @PragTob) +* Added `Enumerable#none?` (thanks @yui-knk) +* Added `Set#subset?`, `Set#superset?` and `Set#intersects?` (thanks @yui-knk) +* Added `Set#new(Enumerable)` (thanks @yui-knk) +* Added `String#succ` (thanks @porras and @Exilor) +* Added `Array#*` (thanks @porras) +* Added `Char#===(Int)` and `Int#===(Char)` (thanks @will) +* Added `StringLiteral#camelcase` and `StringLiteral#underscore` in macros +* Added `Expressions#expressions` in macros +* Added `Cast#obj` and `Cast#to` in macros +* Added `ASTNode#class_name` in macros (thanks @yui-knk) +* Added `Array#push`/`Array#<<` and `Array#unshift` in macros (thanks @ysbaddaden) +* Added `Def#visibility` in macros (thanks @ysbaddaden) +* Added `String#codepoints` and `String#each_codepoint` (thanks @jhass) +* `Char#to_i(base)` now supports bases from 2 to 36 +* `Set#|` now correctly accepts a set of a possible different type (thanks @yui-knk) +* Flush `STDERR` on exit (thanks @jbbarth) +* `HTTP::Client` methods accept an optional block, which will yield an `HTTP::Response` with a non-nil `body_io` property to consume the response's IO +* Document `URI`, `UDPSocket` (thanks @davydovanton) +* Improved `URI` class (thanks @will) +* Define `$~` in `String#gsub` and `String#scan` +* Define `$?` in `Process.run` +* Lots of bug fixes and small improvements + +## 0.7.4 (2015-06-23) + +* Added Float module and remainder (thanks @wmoxam) +* Show elapsed time in HTTP::LogHandler (thanks @zamith for the suggestion) +* Added `0o` as a prefix for octal numbers (thanks @asb) +* Allow spaces before the closing tag of a heredoc (thanks @zamith) +* `String#split(Regex)` now includes captures in the results +* Added `union?`, `union_types` and `type_params` in macro methods +* Improved `MatchData#to_s` to show named groups (thanks @will) +* Optimized Base64 encode/decode (thanks @kostya) +* Added basic docs for spec (thanks @PragTob) +* Added docs for Benchmark (thanks @daneb) +* Added `ARGF` +* Non-matching regex captures now return `nil` instead of an empty string (thanks @will) +* Added `$1?`, `$2?`, etc., as a nilable alternative to `$1`, `$2`, etc. +* Added user, password, fragment and opaque to URI (thanks @will) +* `HTTP::Client.exec` now honors user/password info from URI +* Set default user agent in `HTTP::Client` +* Added `String#chop` +* Fixed `crystal deps` behaviour with empty git repositories (thanks @tkrajcar) +* Optimized `HTTP::Headers` and `HTTP::Request` parsing. +* `FileDescriptorIO` (superclass of `File` and `Socket`) has now buffering capabilities (use `sync=` and `sync?` to turn on/off). That means there's no need to use `BufferedIO` for these classes anymore. +* Allow `pointerof` with class and global variables, and also `foo.@bar` access +* Optimized fibers performance. +* Added inline assembly support. +* The `.crystal` cache dir is now configurable with an ENV variable (thanks @jhass) +* Generic type variables names can now also be a single letter followed by a digit. + +## 0.7.3 (2015-06-07) + +* Added `Tuple.from_json` and `Tuple.to_json` +* The `method_missing` macro now accepts a 1 argument variant that is a Call node. The 3 arguments variant will be deprecated. +* Flush STDOUT at program exit (fixes `print` not showing any output) +* Added `Time#to_utc` and `Time#to_local` (thanks @datanoise) +* Time comparison is now correct when comparing local vs. utc times +* Support timezone offsets in Time parsing and formatting +* Added `IO#gets(delimiter : String)` +* Added `String#chomp(Char)` and `String#chomp(String)` +* Allow invoking `debug()` inside a macro to see what's being generated. +* `IO#puts` and `IO#print` now receive a splat (thanks @rhysd) +* Added `Process.kill` and `Process.getpgid` (thanks @barachy) +* `Signal` is now an enum. Use it like `Signal::INT.trap { ... }` instead of `Signal.trap(Signal::INT) { ... }` +* Added `CSV.each_row` (both in block and iterator forms) +* Important fixes to non-blocking IO logic. + +## 0.7.2 (2015-05-26) + +* Improved performance of Regex +* Fixed lexing of octal characters and strings (thanks @rhysd) +* Time.parse can return UTC times (thanks @will) +* Handle dashes in `crystal init` (thanks @niftyn8) +* Generic type variables can now only be single letters (T, U, A, B, etc.) +* Support `%x` and `%X` in `sprintf` (thanks @yyyc514) +* Optimized `Int#to_s` (thanks @yyyc514) +* Added `upcase` option to `Int#to_s`, and use downcase by default. +* Improved `String#to_i` and fixed the many variants (`to_i8`, `to_u64`, etc.) +* Added `Time.at` (thanks @jeromegn) +* Added `Int#upto`, `Int#downto`, `Int#to` iterators. +* Added `Iterator#cons` and `Enumerable#each_cons` (thanks @porras) +* Added `Iterator.of`, `Iterator#chain` and `Iterator#tap`. +* Allow top-level `private macro` (similar to top-level `private def`) +* Optimized `BufferedIO` writing performance and memory usage. +* Added `Channel#close`, `Channel#closed?`, `Channel#receive?` and allow them to send/receive nil values (thanks @datanoise). +* Fixed `Process#run` after introducing non-blocking IO (thanks @will) +* `Tuple#map` now returns a `Tuple` (previously it returned an `Array`) +* `Tuple#class` now returns a proper `Class` (previously it returned a `Tuple` of classes) +* Lots of bug fixes. + +## 0.7.1 (2015-04-30) + +* Fixed [#597](https://github.com/crystal-lang/crystal/issues/597). +* Fixed [#599](https://github.com/crystal-lang/crystal/issues/599). + +## 0.7.0 (2015-04-30) + +* Crystal has evented IO by default. Added `spawn` and `Channel`. +* Correctly support the X86_64 and X86 ABIs. Now bindings to C APIs that pass and return structs works perfectly fine. +* Added `crystal init` to quickly create a skeleton library or application (thanks @waterlink) +* Added `--emit` flag to the compiler. Now you can easily see the generated LLVM IR, LLVM bitcode, assembly and object files. +* Added `--no-color` flag to suppress color output, useful for editor tools. +* Added macro vars: `%var` and `%var{x, y}` create uniquely named variables inside macros. +* Added [typed splats](https://github.com/crystal-lang/crystal/issues/291). +* Added `Iterator` and many methods that return iterators, like `Array#each`, `Hash#each`, `Int#times`, `Int#step`, `String#each_char`, etc. +* Added `sprintf` and improved `String#%` to support floats and float formatting. +* Added more variants of `String#gsub`. +* Added `Pointer#clear` and use it to clear an `Array`'s values when doing `pop` and other shrinking methods. +* Added `BigInt#to_s(base)`, `BigInt::cast` and bit operators (thanks @Exilor) +* Allow invoking methods on a union class as long as all types in the union have it. +* Allow specifying a def's return type. The compiler checks the return type only for that def for now (not for subclasses overriding the method). The return type appears in the documentation. +* Allow constants and computed constants for a StaticArray length. +* Allow class vars in enums. +* Allow private and protected defs in enums. +* Allow reopening a `lib` and adding more `@[Link]` attributes to it, even allowing duplicated attributes. +* Allow getting a function pointer to a lib fun without specifying its types (i.e. `->LibC.getenv`) +* Allow specifying `ditto` for a doc comment to reuse the previous comment. +* Changed the semantic of `%`: previously it meant `remainder`, not it means `modulo`, similar to Ruby and Python. Added `Int#remainder`. +* `#to_s` and `#inspect` now work for a union class. +* Spec: added global `before_each` and `after_each` hooks, which will simplify the use of mocking libraries like [timecop.cr](https://github.com/waterlink/timecop.cr) and [webmock.cr](https://github.com/manastech/webmock.cr). +* `Range(T)` is now `Range(B, E)` again (much more flexible). +* Improved Regex performance. +* Better XML support. +* Support LLVM 3.6. +* Exception class is now shown on unhandled exceptions +* The following types are now disallowed in generics (for now): Object, Value, Reference, Number, Int and Float. +* Lots of bug fixes, enhancements and optimizations. + +## 0.6.1 (2015-03-04) + +* The `class` method now works in all cases. You can now compare classes with `==` and ask their `hash` value. +* Block variables can now shadow local variables. +* `Range(B, E)` is now `Range(T)`. +* Added `Number::[]`. Now you can do `Int64[1, 2, 3]` instead of `[1_i64, 2_i64, 3_u64]`. +* Better detection of nilable instance variables, and better error messages too. +* Added `Crypto::Blowfish` (thanks @akaufmann) +* Added `Matrix` (thanks @Exilor) +* Added `CallConvention` attribute for `fun`s. +* Macros: added `constants` so you can inspect a type's constants at compile time. +* Macros: added `methods`, which lists a type's methods (without including supertypes). +* Macros: added `has_attribute?` for enum types, so you can check if an enum has the Flags attribute on it. +* Many more small additions and bug fixes. + +## 0.6.0 (2015-02-12) + +* Same as 0.5.10 + +## 0.5.10 (transitional) (2015-02-12) + +* **Note**: This release makes core, breaking changes to the language, and doesn't work out of the box with its accompanying standard library. Use 0.6.0 instead. +* Improved error messages related to nilable instance variables. +* The magic variables `$~` and `$?` are now method-local and concurrent-safe. +* `Tuple` is now correctly considered a struct +* `Pointer` is now correctly considered a struct +* Renamed `Function` to `Proc` + +## 0.5.9 (2015-02-07) + +* `Random` is now a module, with static methods that default to the `Random::MT19937` class. +* Added `Random::ISAAC` engine (thanks @ysbaddaden!) +* Added `String#size` (thanks @zamith!) +* Added `limit` to all `String#split` variants (thanks @jhass!) +* Raising inside a Thread is now rescued and re-raised on join (thanks @jhass!) +* Added `path` option to Projectfile for `crystal deps` (thanks @naps62!) +* Many fixes towards making Crystal work on linux 32 bits. +* Huge refactors, additions and improvements for sockets: Socket, IPSocket, TCPSocket, TCPServer, UDPSocket, UNIXSocket, UNIXServer (thanks @ysbaddaden!) +* Allow regex with empty spaces in various places. +* Added `HTML.escape(String)` (thanks @naps62!) +* Added support for `%w[...]`, `%w{...}`, `%w<...>` as alternatives to `%w(...)`. Same goes for `%i(...)` (thanks @zamith!) +* Added `Enumerable#min_of`, `Enumerable#max_of` and `Enumerable#minmax_of`, `Enumerable#to_h`, `Dir.chdir` and `Number#fdiv` (thanks @jhass!) +* Added `String#match`, `String#[]`, `String#[]?` and `MatchData#[]? ` related to regexes (thanks @jhass!) +* Allow `T::Bar` when T is a generic type argument. +* Added `subclasses` and `all_subclasses` in macros. +* Now you can invoke `to_s` and `inspect` on C structs and unions, making debugging C bindings much easier! +* Added `#to_f` and `#to_i` to `Time` and `TimeSpan` (thanks @epitron!) +* Added `IO.select` (thanks @jhass!) +* Now you can use `ifdef` inside C structs and unions. +* Added `include` inside C structs, to include other struct fields (useful for composition and avoiding an explicit indirection). +* Added `Char#in_set?`, `String#count`, `String#delete` and `String#squeeze` (thanks @jhass!) +* Added `-D flag` option to the compiler to set compile-time flags to use in `ifdef`. +* More support for forward declarations inside C libs. +* Rewritten some `Function` primitives in Crystal itself, and added methods for obtaining the pointer and closure data, as well as for recreating a function from these. +* Added a `Logger` class (thanks @ysbaddaden!) +* Lots of bugs fixed. + +## 0.5.8 (2015-01-16) + +* Added `Random` and `Random::MT19937` (Mersenne Twister) classes (thanks @rhysd). +* Docs: removed automatic linking. To link to classes and methods surround with backticks. +* Fixed [#328](https://github.com/crystal-lang/crystal/issues/328): `!=` bug. + +## 0.5.7 (2015-01-02) + +* Fixed: `doc` command had some hardcoded paths and didn't work +* Added: `private def` at the top-level of a file is only available inside that file + +## 0.5.6 (2014-31-12) + +* Added a `crystal doc` command to automatically generate documentation for a project using [Markdown](http://daringfireball.net/projects/markdown/) syntax. The style is still ugly but it's quite functional. Now we only need to start documenting things :-) +* Removed the old `@:` attribute syntax. +* Fixed [#311](https://github.com/crystal-lang/crystal/issues/311): Issues with invoking lib functions in other ways (thanks @scidom). +* Fixed [#314](https://github.com/crystal-lang/crystal/issues/314): NoReturn information is not lazy. +* Fixed [#317](https://github.com/crystal-lang/crystal/issues/317): Fixes in UTF-8 encoding/decoding (thanks @yous). +* Fixed [#319](https://github.com/crystal-lang/crystal/issues/319): Unexpected EOF (thanks @Exilor). +* `{{yield}}` inside macros now preserve the yielded node location, leading to much better error messages. +* Added `Float#nan?`, `Float#infinite?` and `Float#finite?`. +* Many other bug fixes and improvements. + +## 0.5.5 (2014-12-12) + +* Removed `src` and crystal compiler `libs` directory from CRYSTAL_PATH. +* Several bug fixes. + +## 0.5.4 (2014-12-04) + +* **(breaking change)** `require "foo"` always looks up in `CRYSTAL_PATH`. `require "./foo"` looks up relative to the requiring file. +* **(breaking change)** Renamed `Json` to `JSON`, `Xml` to `XML` and `Yaml` to `YAML` to follow [a convention](https://github.com/crystal-lang/crystal/issues/279). +* **(breaking change)** To use HTTP types do, for example, `require "http/client"` instead of the old `require "net/http"`. +* Added `alias_method` macro (thanks @Exilor and @jtomschroeder). +* Added some `Complex` number methods and many math methods, refactors and specs (thanks @scidom). +* Inheriting generic classes is now possible. +* Creating arrays of generic types (i.e.: `[] of Thread`) is now possible. +* Allow using an alias in a block type (i.e.: `alias F = Int32 ->`, `&block : F`). +* `json_mapping` macro supports a simpler syntax: `json_mapping({key1: Type1, key2: Type2})`. +* Spec: added `be_a(type)` matcher. +* Spec: added `be > ...` and similar matchers for `>=`, `<` and `<=`. +* Added `File::file?` and `File::directory?`. +* CSV parser can parse from String or IO. +* When invoking the compiler like this: `crystal foo.cr -o foo` the `build` command is assumed instead of `run`. +* Added short symbol notation for methods that are operators (i.e. `:+`, `:*`, `:[]`, etc.). +* Added `TimeSpan#ago`, `TimeSpan#from_now`, `MonthSpan#ago` and `MonthSpan#from_now`. + +## 0.5.3 (2014-11-06) + +* Spec: when a `should` or `should_not` fail, the filename and line number, including the source's line, is included in the error message. +* Spec: added `-l` switch to be able to run a spec defined in a line. +* Added `crystal spec file:line` +* Properties (property, setter, getter) can now be restricted to a type with the syntax `property name :: Type`. +* Enums can be used outside `lib`. They inherit `Enum`, can have methods and can be marked with @[Flags]. +* Removed the distinction between `lib` enums and regular enums. +* Fixed: it was incorrectly possible to define `class`, `def`, etc. inside a call block. +* The syntax for specifying the base type of an enum, `enum Name < BaseType` has been deprecated. Use `enum Name : BaseType`. +* Added `Array#<=>` and make it comparable to other arrays. + +## 0.5.2 (2014-11-04) + +* New command line interface to the compiler (`crystal build ...`, `crystal run ...`, `crystal spec`, etc.). The default is to compiler and run a program. +* `crystal eval` without arguments reads from standard input. +* Added preliminar `crystal deps` command. +* `__FILE__`, `__DIR__` and `__LINE__`, when used as def default arguments, resolve to the caller location (similar to [D](http://dlang.org/traits.html#specialkeywords) and [Swift](https://developer.apple.com/swift/blog/?id=15)) +* Allow `as` to determine a type even if the casted value doesn't have a type yet. +* Added `is_a?` in macros. The check is against an [AST node](https://github.com/crystal-lang/crystal/blob/master/src/compiler/crystal/syntax/ast.cr) name. For example `node.is_a?(HashLiteral)`. +* Added `emit_null` property to `json_mapping`. +* Added `converter` property to `json_mapping`. +* Added `pp` in macros. +* Added `to_pretty_json`. +* Added really basic `CSV.parse`. +* Added `Regex.escape`. +* Added `String#scan`. +* Added `-e` switch to spec, to run specs that match a pattern. +* Added `--fail-fast` switch to spec. +* Added `HTTPClient#basic_auth`. +* Added `DeclareVar`, `Def` and `Arg` macro methods. +* Added `Time` and `TimeSpan` structs. `TimeWithZone` will come later. +* Added `Array#fill` (thanks @Exilor). +* Added `Array#uniq`. +* Optimized `File.read_lines`. +* Allow any expression inside `{% ... %}` so that you can interpret code without outputting the result. +* Allow `\` at the end of a line. +* Allow using `if` and `unless` inside macro expressions. +* Allow marking a `fun/def` as `@[Raises]` (useful when a function can potentially raise from a callback). +* Allow procs are now considered `@[Raises]`. +* `OAuth2::Client` supports getting an access token via authorization code or refresh token. +* Consecutive string literals are automatically concatenated by the parser as long as there is a `\` with a newline between them. +* Many bug fixes. + +## 0.5.1 (2014-10-16) + +* Added [json_mapping](https://github.com/crystal-lang/crystal/blob/master/spec/std/json/mapping_spec.cr) macro. +* Added [Signal](https://github.com/crystal-lang/crystal/blob/master/src/signal.cr) module. +* Added [Tempfile](https://github.com/crystal-lang/crystal/blob/master/src/tempfile.cr) class. +* Enhanced [HTTP::Client](https://github.com/crystal-lang/crystal/blob/master/src/net/http/client/client.cr). +* Added [OAuth::Consumer](https://github.com/crystal-lang/crystal/blob/master/libs/oauth/consumer.cr). +* Added [OAuth2::Client](https://github.com/crystal-lang/crystal/blob/master/libs/oauth2/client.cr). +* Added [OpenSSL::HMAC](https://github.com/crystal-lang/crystal/blob/master/libs/openssl/hmac.cr). +* Added [SecureRandom](https://github.com/crystal-lang/crystal/blob/master/src/secure_random.cr). +* New syntax for array/hash-like classes. For example: `Set {1, 2, 3}` and `HTTP::Headers {"content-type": "text/plain"}`. These just create the type and use `<<` or `[]=`. +* Optimized Json parsing performance. +* Added a [CSV builder](https://github.com/crystal-lang/crystal/blob/master/src/csv.cr#L13). +* XML reader can [parse from an IO](https://github.com/crystal-lang/crystal/blob/master/src/xml/reader.cr#L10). +* Added `Dir::glob` and `Dir::Entries` (thanks @jhass) +* Allow `ensure` as an expression suffix. +* Fixed [#219](https://github.com/crystal-lang/crystal/issues/219): Proc type is not inferred when passing to library fun and the return type doesn't match. +* Fixed [#224](https://github.com/crystal-lang/crystal/issues/224): Class#new doesn't pass a block. +* Fixed [#225](https://github.com/crystal-lang/crystal/issues/225): ICE when comparing void to something. +* Fixed [#227](https://github.com/crystal-lang/crystal/issues/227): Nested captured block looses scope and crashes compiler. +* Fixed [#228](https://github.com/crystal-lang/crystal/issues/228): Macro expansion doesn't retain symbol escaping as needed. +* Fixed [#229](https://github.com/crystal-lang/crystal/issues/229): Can't change block context if defined within module context. +* Fixed [#230](https://github.com/crystal-lang/crystal/issues/230): Type interference breaks equality operator. +* Fixed [#233](https://github.com/crystal-lang/crystal/issues/233): Incorrect `no block given` message with new. +* Other bug fixes. + +## 0.5.0 (2014-09-24) + +* String overhaul, and optimizations + +## 0.4.5 (2014-09-24) + +* Define backtick (`) for command execution. +* Allow string literals as keys in hash literals: `{"foo": "bar"} # :: Hash(String, String)` +* Allow `ifdef` as a suffix. +* Integer division by zero raises a `DivisionByZero` exception. +* Link attributes are now only processed if a lib function is used. +* Removed the `type Name : Type` syntax (use `type Name = Type` instead). +* Removed the `lib Lib("libname"); end` syntax. Use `@[Link]` attribute instead. +* Fixed some `require` issues. +* String representation includes length. +* Upgraded to LLVM 3.5. + +## 0.4.4 (2014-09-17) + +* Fixed [#193](https://github.com/crystal-lang/crystal/issues/193): allow initializing an enum value with another's one. +* The `record` macro is now variadic, so instead of `record Vec3, [x, y, z]` write `record Vec3, x, y, z`. +* The `def_equals`, `def_hash` and `def_equals_and_hash` macros are now variadic. +* The `property`, `getter` and `setter` macros are now variadic. +* All String methods are now UTF-8 aware. +* `String#length` returns the number of characters, while `String#bytesize` return the number of bytes (previously `length` returned the number of bytes and `bytesize` didn't exist). +* `String#[](index)` now returns a `Char` instead of an `UInt8`, where index is counted in characters. There's also `String#byte_at(index)`. +* Removed the `\x` escape sequence in char and string literals. Use `\u` instead. +* `initialize` methods are now protected. +* Added `IO#gets_to_end`. +* Added backticks (`...`) and `%x(...)` for command execution. +* Added `%r(...)` for regular expression literals. +* Allow interpolations in regular expression literals. +* Compiling with `--release` sets a `release` flag that you can test with `ifdef`. +* Allow passing splats to C functions +* A C type can now be declared like `type Name = Type` (`type Name : Type` will be deprecated). +* Now a C struct/union type can be created with named arguments. +* New attributes syntax: `@[Attr(...)`] instead of `@:Attr`. The old syntax will be deprecated in a future release. +* New link syntax for C libs: `@[Link("name")]` (uses `name` as `pkg-config name` if available or `-lname` instead), `@[Link(ldflags: "...")]` to pass raw flags to the linker, `@[Link("name", static: true)]` to try to find a static library first, and `@[Link(framework: "AppKit")]` (for Mac OSX). +* Added an `exec` method to execute shell commands. Added the `system` and `backtick` similar to Ruby ones. +* Added `be_truthy` and `be_falsey` spec matchers. Added `Array#zip` without a block. (thanks @mjgpy3) +* Added `getter?` and `property?` macros to create methods that end with `?`. +* Added a `CGI` module. +* The compiler now only depends on `cc` for compiling (removed dependency to `llc`, `opt`, `llvm-dis` and `clang`). +* Added `IO#tty?`. +* Some bug fixes. + +## 0.4.3 (2014-08-14) + +* Reverted a commit that introduced random crashes. + +## 0.4.2 (2014-08-13) + +* Fixed [#187](https://github.com/crystal-lang/crystal/issues/185): mixing `yield` and `block.call` crashes the compiler. +* Added `\u` unicode escape sequences inside strings and chars (similar to Ruby). `\x` will be deprecated as it can generate strings with invalid UTF-8 byte sequences. +* Added `String#chars`. +* Fixed: splats weren't working in `initialize`. +* Added the `private` and `protected` visibility modifiers, with the same semantics as Ruby. The difference is that you must place them before a `def` or a macro call. +* Some bug fixes. + +## 0.4.1 (2014-08-09) + +* Fixed [#185](https://github.com/crystal-lang/crystal/issues/185): `-e` flag stopped working. +* Added a `@length` compile-time variable available inside tuples that allows to do loop unrolling. +* Some bug fixes. + +## 0.4.0 (2014-08-08) + +* Support splats in macros. +* Support splats in defs and calls. +* Added named arguments. +* Renamed the `make_named_tuple` macro to `record`. +* Added `def_equals`, `def_hash` and `def_equals_and_hash` macros to generate them from a list of fields. +* Added `Slice(T)`, which is a struct having a pointer and a length. Use this in IO for a safe API. +* Some `StaticArray` fixes and enhancements. + +## 0.3.5 (2014-07-29) + +* **(breaking change)** Removed the special `->` operator for pointers of structs/unions: instead of `foo->bar` use `foo.value.bar`; instead of `foo->bar = 1` use `foo.value.bar = 1`. +* Added `colorize` file that provides methods to easily output bash colors. +* Now you can use modules as generic type arguments (for example, do `x = [] of IO`). +* Added SSL sockets. Now HTTP::Server implements HTTPS. +* Macros have access to constants and types. +* Allow iterating a range in macros with `for`. +* Use cpu cycle counter to initialize random. +* `method_missing` now works in generic types. +* Fixed [#154](https://github.com/crystal-lang/crystal/issues/154): bug, constants are initialized before global variables. +* Fixed [#168](https://github.com/crystal-lang/crystal/issues/168): incorrect type inference of instance variables if not assigned in superclass. +* Fixed [#169](https://github.com/crystal-lang/crystal/issues/169): `responds_to?` wasn't working with generic types. +* Fixed [#171](https://github.com/crystal-lang/crystal/issues/171): ensure blocks are not executed if the rescue block returns from a def. +* Fixed [#175](https://github.com/crystal-lang/crystal/issues/175): invalid code generated when using with/yield with structs. +* Fixed some parser issues and other small issues. +* Allow forward struct/union declarations in libs. +* Added `String#replace(Regex, String)` +* Added a `Box(T)` class, useful for boxing value types to pass them to C as `Void*`. + +## 0.3.4 (2014-07-21) + +* Fixed [#165](https://github.com/crystal-lang/crystal/issues/165): restrictions with generic types didn't work for hierarchy types. +* Allow using a single underscore in restrictions, useful for matching against an n-tuple or an n-function where you don't care about the types (e.g.: `def foo(x : {_, _})`. +* Added a `generate_hash` macro that generates a `hash` methods based on some AST nodes. +* Added very basic `previous_def`: similar to `super`, but uses the previous definition of a method. Useful to decorate existing methods (similar to `alias_method_chain`). For now the method's type restrictions must match for a previous definition to be found. +* Made the compiler a bit faster +* Added `env` in macros, to fetch an environment value. Returns a StringLiteral if found or NilLiteral if not. +* Make `return 1, 2` be the same as `return {1, 2}`. Same goes with `break` and `next`. +* Added `Pointer#as_enumerable(size : Int)` to create an `Enumerable` from a Pointer with an associated size, with zero overhead. Some methods removed from `Pointer`: `each`, `map`, `to_a`, `index`. +* Added `StaticArray::new`, `StaticArray::new(value)`, `StaticArray::new(&block)`, `StaticArray#shuffle!` and `StaticArray#map!`. +* Faster `Char#to_s(io : IO)` + +## 0.3.3 (2014-07-14) + +* Allow implicit conversion to C types by defining a `to_unsafe` method. This removed the hardcoded rule for converting a `String` to `UInt8*` and also allows passing an `Array(T)` to an argument expecting `Pointer(T)`. +* Fixed `.is_a?(Class)` not working ([#162](https://github.com/crystal-lang/crystal/issues/162)) +* Attributes are now associated to AST nodes in the semantic pass, not during parsing. This allows macros to generate attributes that will be attached to subsequent expressions. +* **(breaking change)** Make ENV#[] raise on missing key, and added ENV#[]? +* **(breaking change)** Macro defs are now written like `macro def name(args) : ReturnType` instead of `def name(args) : ReturnType`, which was a bit confusing. + +## 0.3.2 (2014-07-10) + +* Integer literals without a suffix are inferred to be Int32, Int64 or UInt64 depending on their value. +* Check that integer literals fit into their types. +* Put back `Int#to_s(radix : Int)` (was renamed to `to_s_in_base` in the previous release) by also specifying a restriction in `Int#to_s(io : IO)`. +* Added `expect_raises` macros in specs + +## 0.3.1 (2014-07-09) + +* **(breaking change)** Replaced `@name` inside macros with `@class_name`. +* **(breaking change)** Instance variables inside macros now don't have the `@` symbols in their names. + +## 0.3.0 (2014-07-08) + +* Added `Array#each_index` +* Optimized `String#*` for the case when the string has length one. +* Use `GC.malloc_atomic` for String and String::Buffer (as they don't contain internal pointers.) +* Added a `PointerAppender` struct to easily append to a `Pointer` while counting at the same time (thanks @kostya for the idea). +* Added a `Base64` module (thanks @kostya) +* Allow default arguments in macros +* Allow invoking `new` on a function type. For example: `alias F = Int32 -> Int32; f = F.new { |x| x + 1 }; f.call(2) #=> 3`. +* Allow omitting function argument types when invoking C functions that accept functions as arguments. +* Renamed `@name` to `@class_name` inside macros. `@name` will be deprecated in the next version. +* Added IO#read_fully +* Macro hooks: `inherited`, `included` and `extended` +* `method_missing` macro +* Added `{{ raise ... }}` inside macros to issue a compile error. +* Started JSON serialization and deserialization +* Now `at_exit` handlers are run when you invoke `exit` +* Methods can be marked as abstract +* New convention for `to_s` and `inspect`: you must override them receiving an IO object +* StringBuilder and StringBuffer have been replaced by StringIO + +## 0.2.0 (2014-06-24) + +* Removed icr (a REPL): it is abandoned for the moment because it was done in a hacky, non-reliable way +* Added very basic `String#underscore` and `String#camelcase`. +* The parser generates string literals out of strings with interpolated string literals. For example, `"#{__DIR__}/foo"` is interpolated at compile time and generates a string literal with the full path, since `__DIR__` is just a (special) string literal. +* **(breaking change)** Now macro nodes are always pasted as is. If you want to generate an id use `{{var.id}}`. + + Previously, a code like this: + + ```ruby + macro foo(name) + def {{name}}; end + end + + foo :hello + foo "hello" + foo hello + ``` + + generated this: + + ```ruby + def hello; end + def hello; end + def hello; end + ``` + + With this change, it generates this: + + ```ruby + def :hello; end + def "hello"; end + def hello; end + ``` + + Now, to get an identifier out of a symbol literal, string literal or a name, use id: + + ```ruby + macro foo(name) + def {{name.id}}; end + end + ``` + + Although it's longer to type, the implicit "id" call was sometimes confusing. Explicit is better than implicit. + + Invoking `id` on any other kind of node has no effect on the pasted result. +* Allow escaping curly braces inside macros with `\{`. This allows defining macros that, when expanded, can contain other macro expressions. +* Added a special comment-like pragma to change the lexer's filename, line number and column number. + + ```ruby + # foo.cr + a = 1 + #b = 2 + c = 3 + ``` + + In the previous example, `b = 2` (and the rest of the file) is considered as being parsed from file `bar.cr` at line 12, column 24. + +* Added a special `run` call inside macros. This compiles and executes another Crystal program and pastes its output into the current program. + + As an example, consider this program: + + ```ruby + # foo.cr + {{ run("my_program", 1, 2, 3) }} + ``` + + Compiling `foo.cr` will, at compile-time, compile `my_program.cr` and execute it with arguments `1 2 3`. The output of that execution is pasted into `foo.cr` at that location. +* Added ECR (Embedded Crystal) support. This is implemented using the special `run` macro call. + + A small example: + + ```ruby + # template.ecr + Hello <%= @msg %> + ``` + + ```ruby + # foo.cr + require "ecr/macros" + + class HelloView + def initialize(@msg) + end + + # This generates a to_s method with the contents of template.ecr + ecr_file "template.ecr" + end + + view = HelloView.new "world!" + view.to_s #=> "Hello world!" + ``` + + The nice thing about this is that, using the `#` pragma for specifying the lexer's location, if you have a syntax/semantic error in the template the error points to the template :-) + + +## 0.1.0 (2014-06-18) + +* First official release diff --git a/CHANGELOG.md b/CHANGELOG.md index 2426820c56ed..d9a97a9dc2ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1640,4105 +1640,6 @@ - Add LLVM 11.1 to the list of supported versions. ([#10523](https://github.com/crystal-lang/crystal/pull/10523), thanks @Sija) - Fix SDL examples crashes. ([#10470](https://github.com/crystal-lang/crystal/pull/10470), thanks @megatux) -# 0.36.1 (2021-02-02) +## 0.x -## Standard library - -- Fix `Enum.from_value?` for flag enum with non `Int32` base type. ([#10303](https://github.com/crystal-lang/crystal/pull/10303), thanks @straight-shoota) - -### Text - -- Don't raise on `String.new` with `null` pointer and bytesize 0. ([#10308](https://github.com/crystal-lang/crystal/pull/10308), thanks @asterite) - -### Collections - -- Explicitly return a `Hash` in `Hash#dup` and `Hash#clone` (reverts [#9871](https://github.com/crystal-lang/crystal/pull/9871)). ([#10331](https://github.com/crystal-lang/crystal/pull/10331), thanks @straight-shoota) - -### Serialization - -- XML: fix deprecation warning. ([#10335](https://github.com/crystal-lang/crystal/pull/10335), thanks @bcardiff) - -### Runtime - -- Eager load DWARF only if `CRYSTAL_LOAD_DWARF=1`. ([#10326](https://github.com/crystal-lang/crystal/pull/10326), thanks @bcardiff) - -## Compiler - -- **(performance)** Initialize right-away constants in a separate function. ([#10334](https://github.com/crystal-lang/crystal/pull/10334), thanks @asterite) -- Fix incorrect casting between different union types. ([#10333](https://github.com/crystal-lang/crystal/pull/10333), thanks @asterite) -- Fix a formatting error in the "missing argument" error. ([#10325](https://github.com/crystal-lang/crystal/pull/10325), thanks @BlobCodes) -- Fix while condition assignment check for Not. ([#10347](https://github.com/crystal-lang/crystal/pull/10347), thanks @asterite) -- Allow operators and setters-like macros names back. ([#10338](https://github.com/crystal-lang/crystal/pull/10338), thanks @asterite) - -### Language semantics - -- Fix type check not considering virtual types. ([#10304](https://github.com/crystal-lang/crystal/pull/10304), thanks @asterite) -- Use path lookup when looking up type for auto-cast match. ([#10318](https://github.com/crystal-lang/crystal/pull/10318), thanks @asterite) - -# 0.36.0 (2021-01-26) - -## Language changes - -- **(breaking-change)** Reject annotations on ivars defined in base class. ([#9502](https://github.com/crystal-lang/crystal/pull/9502), thanks @waj) -- **(breaking-change)** Make `**` be right associative. ([#9684](https://github.com/crystal-lang/crystal/pull/9684), thanks @asterite) - -### Macros - -- **(breaking-change)** Deprecate `TypeNode#has_attribute?` in favor of `#annotation`. ([#9950](https://github.com/crystal-lang/crystal/pull/9950), thanks @HertzDevil) -- Support heredoc literal in macro. ([#9467](https://github.com/crystal-lang/crystal/pull/9467), thanks @MakeNowJust) -- Support `%Q`, `%i`, `%w`, and `%x` literals in macros. ([#9811](https://github.com/crystal-lang/crystal/pull/9811), thanks @toddsundsted) -- Allow executing multi-assignments in macros. ([#9440](https://github.com/crystal-lang/crystal/pull/9440), thanks @MakeNowJust) - -## Standard library - -- **(breaking-change)** Drop deprecated `CRC32`, `Adler32` top-level. ([#9530](https://github.com/crystal-lang/crystal/pull/9530), thanks @bcardiff) -- **(breaking-change)** Drop deprecated `Flate`, `Gzip`, `Zip`, `Zlib` top-level. ([#9529](https://github.com/crystal-lang/crystal/pull/9529), thanks @bcardiff) -- **(breaking-change)** Drop deprecated `with_color` top-level method. ([#9531](https://github.com/crystal-lang/crystal/pull/9531), thanks @bcardiff) -- **(breaking-change)** Make `SemanticVersion` parsing more strict. ([#9868](https://github.com/crystal-lang/crystal/pull/9868), thanks @hugopl) -- Respect explicitly provided type vars for `Tuple.new` and `NamedTuple.new`. ([#10047](https://github.com/crystal-lang/crystal/pull/10047), thanks @HertzDevil) -- Fix `OptionParser` to handle sub-commands with hyphen. ([#9465](https://github.com/crystal-lang/crystal/pull/9465), thanks @erdnaxeli) -- Fix `OptionParser` to not call handler if value is given to none value handler. ([#9603](https://github.com/crystal-lang/crystal/pull/9603), thanks @MakeNowJust) -- Make `def_equals` compare first by reference. ([#9650](https://github.com/crystal-lang/crystal/pull/9650), thanks @waj) -- Remove unnecessary `def ==(other)` fallbacks. ([#9571](https://github.com/crystal-lang/crystal/pull/9571), thanks @MakeNowJust) -- Fix `OptionParser` indentation of option descriptions. ([#10183](https://github.com/crystal-lang/crystal/pull/10183), thanks @wonderix) -- Add `#hash` to `Log::Metadata`, `Path`, `SemanticVersion`, `Socket::Address`, `URI::Params`, and `UUID`. ([#10119](https://github.com/crystal-lang/crystal/pull/10119), thanks @straight-shoota) -- Refactor several `case` statements with exhaustive `case`/`in`. ([#9656](https://github.com/crystal-lang/crystal/pull/9656), thanks @Sija) -- Remove `Kernel` mentions in docs. ([#9549](https://github.com/crystal-lang/crystal/pull/9549), thanks @toddsundsted) -- Improve docs for `Object#dup`. ([#10053](https://github.com/crystal-lang/crystal/pull/10053), thanks @straight-shoota) -- Fix example codes in multiple places. ([#9818](https://github.com/crystal-lang/crystal/pull/9818), thanks @maiha) -- Fix typos, misspelling and styling. ([#9636](https://github.com/crystal-lang/crystal/pull/9636), [#9638](https://github.com/crystal-lang/crystal/pull/9638), [#9561](https://github.com/crystal-lang/crystal/pull/9561), [#9786](https://github.com/crystal-lang/crystal/pull/9786), [#9840](https://github.com/crystal-lang/crystal/pull/9840), [#9844](https://github.com/crystal-lang/crystal/pull/9844), [#9854](https://github.com/crystal-lang/crystal/pull/9854), [#9869](https://github.com/crystal-lang/crystal/pull/9869), [#10068](https://github.com/crystal-lang/crystal/pull/10068), [#10123](https://github.com/crystal-lang/crystal/pull/10123), [#10102](https://github.com/crystal-lang/crystal/pull/10102), [#10116](https://github.com/crystal-lang/crystal/pull/10116), [#10229](https://github.com/crystal-lang/crystal/pull/10229), [#10252](https://github.com/crystal-lang/crystal/pull/10252), thanks @kubo, @m-o-e, @mgomes, @philipp-classen, @dukeraphaelng, @camreeves, @docelic, @ilmanzo, @Sija, @pxeger, @oprypin) - -### Macros - -- Fix documentation for `#[]=` macro methods. ([#10025](https://github.com/crystal-lang/crystal/pull/10025), thanks @HertzDevil) - -### Numeric - -- **(breaking-change)** Move `Complex#exp`, `Complex#log`, etc. to `Math.exp`, `Math.log` overloads. ([#9739](https://github.com/crystal-lang/crystal/pull/9739), thanks @cristian-lsdb) -- **(performance)** Use unchecked arithmetics in `Int#times`. ([#9511](https://github.com/crystal-lang/crystal/pull/9511), thanks @waj) -- Check for overflow when extending signed integers to unsigned integers. ([#10000](https://github.com/crystal-lang/crystal/pull/10000), thanks @HertzDevil) -- Fix `sprintf` for zero left padding a negative number. ([#9725](https://github.com/crystal-lang/crystal/pull/9725), thanks @asterite) -- Fix `BigInt#to_64` and `BigInt#hash`. ([#10121](https://github.com/crystal-lang/crystal/pull/10121), thanks @oprypin) -- Fix `BigDecimal#to_s` for numbers between `-1` and `0`. ([#9897](https://github.com/crystal-lang/crystal/pull/9897), thanks @dukeraphaelng) -- Fix `Number#step`. ([#10130](https://github.com/crystal-lang/crystal/pull/10130), [#10295](https://github.com/crystal-lang/crystal/pull/10295), thanks @straight-shoota, @bcardiff) -- Make `Complex` unary plus returns `self`. ([#9719](https://github.com/crystal-lang/crystal/pull/9719), thanks @asterite) -- Improve `Int#lcm` and `Int#gcd`. ([#9999](https://github.com/crystal-lang/crystal/pull/9999), thanks @HertzDevil) -- Add `Math::TAU`. ([#10179](https://github.com/crystal-lang/crystal/pull/10179), thanks @j8r) -- Add wrapped unary negation to unsigned integer types to allow `&- x` expressions. ([#9947](https://github.com/crystal-lang/crystal/pull/9947), thanks @HertzDevil) -- Improve documentation of mathematical functions. ([#9994](https://github.com/crystal-lang/crystal/pull/9994), thanks @HertzDevil) - -### Text - -- **(breaking-change)** Fix `String#index` not working well for broken UTF-8 sequences. ([#9713](https://github.com/crystal-lang/crystal/pull/9713), thanks @asterite) -- **(performance)** `String`: Don't materialize `Regex` `match[0]` if not needed. ([#9615](https://github.com/crystal-lang/crystal/pull/9615), thanks @asterite) -- Fix `String#rindex` default offset for matching empty regex correctly. ([#9690](https://github.com/crystal-lang/crystal/pull/9690), thanks @MakeNowJust) -- Add various `String#delete_at` methods. ([#9398](https://github.com/crystal-lang/crystal/pull/9398), thanks @asterite) -- Raise if passing `null` Pointer to `String`. ([#9653](https://github.com/crystal-lang/crystal/pull/9653), thanks @jgaskins) -- Add `Regex#capture_count`. ([#9746](https://github.com/crystal-lang/crystal/pull/9746), thanks @Sija) -- Add `Regex::MatchData#[]` overloading to `Range`. ([#10076](https://github.com/crystal-lang/crystal/pull/10076), thanks @MakeNowJust) -- Fix duplicates in `String` documentation. ([#9987](https://github.com/crystal-lang/crystal/pull/9987), thanks @Nicolab) -- Add docs for substitution to `Kernel.sprintf`. ([#10227](https://github.com/crystal-lang/crystal/pull/10227), thanks @straight-shoota) - -### Collections - -- **(breaking-change)** Make `Hash#reject!`, `Hash#select!`, and `Hash#compact!` consistent with `Array` and return `self`. ([#9904](https://github.com/crystal-lang/crystal/pull/9904), thanks @caspiano) -- **(breaking-change)** Deprecate `Hash#delete_if` in favor of `Hash#reject!`, add `Dequeue#reject!` and `Dequeue#select!`. ([#9878](https://github.com/crystal-lang/crystal/pull/9878), thanks @caspiano) -- **(breaking-change)** Change `Set#delete` to return Bool. ([#9590](https://github.com/crystal-lang/crystal/pull/9590), thanks @j8r) -- **(breaking-change)** Drop deprecated `Enumerable#grep`. ([#9711](https://github.com/crystal-lang/crystal/pull/9711), thanks @j8r) -- **(breaking-change)** Remove `Hash#key_index` . ([#10016](https://github.com/crystal-lang/crystal/pull/10016), thanks @asterite) -- **(breaking-change)** Deprecate `StaticArray#[]=(value)` in favor of `StaticArray#fill`. ([#10027](https://github.com/crystal-lang/crystal/pull/10027), thanks @HertzDevil) -- **(breaking-change)** Rename `Set#{sub,super}set?` to `{sub,super}set_of?`. ([#10187](https://github.com/crystal-lang/crystal/pull/10187), thanks @straight-shoota) -- **(performance)** Optimize `Array#shift` and `Array#unshift`. ([#10081](https://github.com/crystal-lang/crystal/pull/10081), thanks @asterite) -- **(performance)** `Array#rotate` optimization for small arrays. ([#8516](https://github.com/crystal-lang/crystal/pull/8516), thanks @wontruefree) -- ~~Fix `Hash#dup` and `Hash#clone` to return correct type for subclasses. ([#9871](https://github.com/crystal-lang/crystal/pull/9871), thanks @vlazar)~~ (Reverted by [#10331](https://github.com/crystal-lang/crystal/pull/10331) in 0.36.1) -- Fix `Iterator#cons_pair` return type. ([#9788](https://github.com/crystal-lang/crystal/pull/9788), thanks @asterite) -- Fix key type restriction of `Hash#merge` block. ([#9495](https://github.com/crystal-lang/crystal/pull/9495), thanks @MakeNowJust) -- Fix `Range#step`. ([#10203](https://github.com/crystal-lang/crystal/pull/10203), thanks @straight-shoota) -- Let `Range#step` delegate to `begin.step` and allow float based ranges to be stepped. ([#10209](https://github.com/crystal-lang/crystal/pull/10209), thanks @straight-shoota) -- Fix `Indexable#join` with `IO`. ([#10152](https://github.com/crystal-lang/crystal/pull/10152), thanks @straight-shoota) -- Fix `Flatten` for value-type iterators. ([#10096](https://github.com/crystal-lang/crystal/pull/10096), thanks @f-fr) -- Fix `Indexable.range_to_index_and_count` to not raise `IndexError`. ([#10191](https://github.com/crystal-lang/crystal/pull/10191), thanks @straight-shoota) -- Handle recursive structures in `def_clone`, `Hash`, `Array`, `Dequeue`. ([#9800](https://github.com/crystal-lang/crystal/pull/9800), thanks @asterite) -- Add `BitArray#dup`. ([#9550](https://github.com/crystal-lang/crystal/pull/9550), thanks @andremedeiros) -- Allow `Iterator#zip` to take multiple `Iterator` arguments. ([#9944](https://github.com/crystal-lang/crystal/pull/9944), thanks @HertzDevil) -- Add `Iterator#with_object(obj, &)`. ([#9956](https://github.com/crystal-lang/crystal/pull/9956), thanks @HertzDevil) -- Always clear unused bits of `BitArray`s. ([#10008](https://github.com/crystal-lang/crystal/pull/10008), thanks @HertzDevil) -- Update `Enumerable#sum(initial, &)` docs. ([#9860](https://github.com/crystal-lang/crystal/pull/9860), thanks @jage) -- Add `Array#unsafe_build`. ([#10092](https://github.com/crystal-lang/crystal/pull/10092), thanks @straight-shoota) -- Add identity methods for `#sum` and `#product`. ([#10151](https://github.com/crystal-lang/crystal/pull/10151), thanks @straight-shoota) -- Move `combinations`, `permutations`, etc., from `Array` to `Indexable`. ([#10235](https://github.com/crystal-lang/crystal/pull/10235), thanks @wonderix) -- Move sampling methods to `Enumerable`. ([#10129](https://github.com/crystal-lang/crystal/pull/10129), thanks @HertzDevil) -- Use `Set#add` in `Set#add` documentation. ([#9441](https://github.com/crystal-lang/crystal/pull/9441), thanks @hugopl) -- Improve `Hash#values_at` docs. ([#9955](https://github.com/crystal-lang/crystal/pull/9955), thanks @j8r) - -### Serialization - -- **(breaking-change)** Drop deprecated `JSON.mapping`. ([#9527](https://github.com/crystal-lang/crystal/pull/9527), thanks @bcardiff) -- **(breaking-change)** Drop deprecated `YAML.mapping`. ([#9526](https://github.com/crystal-lang/crystal/pull/9526), thanks @bcardiff) -- Fix flag enum deserialization from `Int64`. ([#10145](https://github.com/crystal-lang/crystal/pull/10145), thanks @straight-shoota) -- Add support for selective JSON and YAML serialization. ([#9567](https://github.com/crystal-lang/crystal/pull/9567), thanks @stakach) -- YAML: correctly serialize `infinity` and `NaN`. ([#9780](https://github.com/crystal-lang/crystal/pull/9780), thanks @asterite) -- YAML: allow using discriminator in contained types. ([#9851](https://github.com/crystal-lang/crystal/pull/9851), thanks @asterite) -- Support more literal types in `JSON::Serializable.use_json_discriminator` macro. ([#9222](https://github.com/crystal-lang/crystal/pull/9222), thanks @voximity) -- Support more literal types in `YAML::Serializable.use_yaml_discriminator` macro. ([#10149](https://github.com/crystal-lang/crystal/pull/10149), thanks @straight-shoota) -- Add `JSON` serialization to big number types. ([#9898](https://github.com/crystal-lang/crystal/pull/9898), [#10054](https://github.com/crystal-lang/crystal/pull/10054), thanks @dukeraphaelng, @Sija) -- Handle namespaced defaults in `JSON::Serializable` and `YAML::Serializable`. ([#9733](https://github.com/crystal-lang/crystal/pull/9733), thanks @jgaskins) -- Standardize YAML error location. ([#9273](https://github.com/crystal-lang/crystal/pull/9273), thanks @straight-shoota) -- Update `YAML::Field(converter)` argument docs. ([#9557](https://github.com/crystal-lang/crystal/pull/9557), thanks @dscottboggs) -- Use `content` instead of `value` when inspect `XML::Attribute`. ([#9592](https://github.com/crystal-lang/crystal/pull/9592), thanks @asterite) -- Let `JSON::PullParser#on_key` yield self and return value. ([#10028](https://github.com/crystal-lang/crystal/pull/10028), thanks @straight-shoota) -- Cleanup `YAML`/`JSON` `Any#dig` methods. ([#9415](https://github.com/crystal-lang/crystal/pull/9415), thanks @j8r) -- Document `JSON::PullParser`. ([#9983](https://github.com/crystal-lang/crystal/pull/9983), thanks @erdnaxeli) -- Clarify Serialization Converter requirements and examples. ([#10202](https://github.com/crystal-lang/crystal/pull/10202), thanks @Daniel-Worrall) - -### Time - -- **(breaking-change)** Drop deprecated `Time::Span.new` variants. ([#10051](https://github.com/crystal-lang/crystal/pull/10051), thanks @Sija) -- **(breaking-change)** Deprecate `Time::Span#duration`, use `Time::Span#abs`. ([#10144](https://github.com/crystal-lang/crystal/pull/10144), thanks @straight-shoota) -- Add `%Z` time format directive. ([#10141](https://github.com/crystal-lang/crystal/pull/10141), thanks @straight-shoota) -- Improve handling edge cases for `Time::Location#load`. ([#10140](https://github.com/crystal-lang/crystal/pull/10140), thanks @straight-shoota) -- Enable pending specs. ([#10093](https://github.com/crystal-lang/crystal/pull/10093), thanks @straight-shoota) -- Improve `Time::Span` arithmetic specs. ([#10143](https://github.com/crystal-lang/crystal/pull/10143), thanks @straight-shoota) - -### Files - -- **(breaking-change)** Make `File.size` and `FileInfo#size` be `Int64` instead of `UInt64`. ([#10015](https://github.com/crystal-lang/crystal/pull/10015), thanks @asterite) -- **(breaking-change)** Fix `FileUtils.cp_r` when destination is a directory. ([#10180](https://github.com/crystal-lang/crystal/pull/10180), thanks @wonderix) -- Enable large-file support on i386-linux-gnu. ([#9478](https://github.com/crystal-lang/crystal/pull/9478), thanks @kubo) -- Fix glob not following symdir directories. ([#9910](https://github.com/crystal-lang/crystal/pull/9910), thanks @straight-shoota) -- Windows: allow creating symlinks without admin rights. ([#9767](https://github.com/crystal-lang/crystal/pull/9767), thanks @oprypin) -- Windows: allow touching directories. ([#10284](https://github.com/crystal-lang/crystal/pull/10284), thanks @straight-shoota) -- Fix `Dir.glob` when `readdir` doesn't report file type. ([#9877](https://github.com/crystal-lang/crystal/pull/9877), thanks @straight-shoota) -- Add `Path#stem`. ([#10037](https://github.com/crystal-lang/crystal/pull/10037), thanks @straight-shoota) -- Move `File#fsync`, `File#flock_exclusive`, `File#flock_shared`, and `File#flock_unlock` methods to `IO::FileDescriptor`. ([#9794](https://github.com/crystal-lang/crystal/pull/9794), thanks @naqvis) -- Call `IO#flush` on builder finish methods. ([#9321](https://github.com/crystal-lang/crystal/pull/9321), thanks @straight-shoota) -- Support using `Path` in `MIME` and `File::Error` APIs. ([#10034](https://github.com/crystal-lang/crystal/pull/10034), thanks @Blacksmoke16) -- Windows: enable `FileUtils` specs. ([#9902](https://github.com/crystal-lang/crystal/pull/9902), thanks @oprypin) - -### Networking - -- **(breaking-change)** Rename `HTTP::Params` to `URI::Params`. ([#10098](https://github.com/crystal-lang/crystal/pull/10098), thanks @straight-shoota) -- **(breaking-change)** Rename `URI#full_path` to `URI#request_target`. ([#10099](https://github.com/crystal-lang/crystal/pull/10099), thanks @straight-shoota) -- **(breaking-change)** `URI::Params#[]=` replaces all values. ([#9605](https://github.com/crystal-lang/crystal/pull/9605), thanks @asterite) -- Fix type of `HTTP::Server::Response#closed?` to `Bool`. ([#9489](https://github.com/crystal-lang/crystal/pull/9489), thanks @straight-shoota) -- Fix `Socket#accept` to obey `read_timeout`. ([#9538](https://github.com/crystal-lang/crystal/pull/9538), thanks @waj) -- Fix `Socket` to allow datagram over unix sockets. ([#9838](https://github.com/crystal-lang/crystal/pull/9838), thanks @bcardiff) -- Fix `HTTP::Headers#==` and `HTTP::Headers#hash`. ([#10186](https://github.com/crystal-lang/crystal/pull/10186), thanks @straight-shoota) -- Make `HTTP::StaticFileHandler` serve pre-gzipped content. ([#9626](https://github.com/crystal-lang/crystal/pull/9626), thanks @waj) -- Delete `Content-Encoding` and `Content-Length` headers after decompression in `HTTP::Client::Response`. ([#5212](https://github.com/crystal-lang/crystal/pull/5212), thanks @maiha) -- Delay setup of `HTTP::CompressHandler` until content is written. ([#9625](https://github.com/crystal-lang/crystal/pull/9625), thanks @waj) -- Allow `HTTP::Client` to work with any IO. ([#9543](https://github.com/crystal-lang/crystal/pull/9543), thanks @waj) -- Make `HTTP::Server` don't override `content-length` if already set. ([#9726](https://github.com/crystal-lang/crystal/pull/9726), thanks @asterite) -- Do not share underlying hash between `URI::Param`s. ([#9641](https://github.com/crystal-lang/crystal/pull/9641), thanks @jgaskins) -- Add `HTTP::Cookie::SameSite::None`. ([#9262](https://github.com/crystal-lang/crystal/pull/9262), thanks @j8r) -- Add `HTTP::Client` logging and basic instrumentation. ([#9756](https://github.com/crystal-lang/crystal/pull/9756), thanks @bcardiff) -- Add `HTTP::Request#hostname`+ utils. ([#10029](https://github.com/crystal-lang/crystal/pull/10029), thanks @straight-shoota) -- Add `URI#authority`. ([#10100](https://github.com/crystal-lang/crystal/pull/10100), thanks @straight-shoota) -- Add `Socket::IPAddress#private?`. ([#9517](https://github.com/crystal-lang/crystal/pull/9517), thanks @jgillich) -- Make `Socket::IpAddress` a bit more type-friendly. ([#9528](https://github.com/crystal-lang/crystal/pull/9528), thanks @asterite) -- Make specs pass in non-IPv6 environments. ([#9438](https://github.com/crystal-lang/crystal/pull/9438), thanks @jhass) -- Make specs pending instead of failing in no multi-cast environments. ([#9566](https://github.com/crystal-lang/crystal/pull/9566), thanks @jhass) -- Remove invalid optimization on `Encoding`. ([#9724](https://github.com/crystal-lang/crystal/pull/9724), thanks @asterite) -- Drop `RemoteAddressType` private alias. ([#9777](https://github.com/crystal-lang/crystal/pull/9777), thanks @bcardiff) -- Add documentation for `HTTP::WebSocket`. ([#9761](https://github.com/crystal-lang/crystal/pull/9761), [#9778](https://github.com/crystal-lang/crystal/pull/9778), thanks @ibraheemdev, @bcardiff) -- Fix `HTTP` docs. ([#9612](https://github.com/crystal-lang/crystal/pull/9612), [#9627](https://github.com/crystal-lang/crystal/pull/9627), [#9717](https://github.com/crystal-lang/crystal/pull/9717), thanks @RX14, @asterite, @3n-k1) -- Fix reference to TCP protocol in docs. ([#9457](https://github.com/crystal-lang/crystal/pull/9457), thanks @dprobinson) - -### Logging - -- **(breaking-change)** Drop deprecated `Logger`. ([#9525](https://github.com/crystal-lang/crystal/pull/9525), thanks @bcardiff) -- **(performance)** Add logging dispatcher to defer entry dispatch from current fiber. ([#9432](https://github.com/crystal-lang/crystal/pull/9432), thanks @waj) -- Take timestamp as an argument in `Log::Entry` initializer. ([#9570](https://github.com/crystal-lang/crystal/pull/9570), thanks @Sija) -- Improve docs regarding `Log.setup`. ([#9497](https://github.com/crystal-lang/crystal/pull/9497), [#9559](https://github.com/crystal-lang/crystal/pull/9559), thanks @caspiano, @jjlorenzo) - -### Crypto - -- **(security)** Fix `"force-peer"` `verify_mode` setup. ([#9668](https://github.com/crystal-lang/crystal/pull/9668), thanks @PhilAtWysdom) -- **(security)** Force secure renegotiation on server to prevent Secure Client-Initiated Renegotiation vulnerability attack. ([#9815](https://github.com/crystal-lang/crystal/pull/9815), thanks @bcardiff) -- **(breaking-change)** Refactor `Digest` and introduce `Digest::MD5`, `Digest::SHA1`, `Digest::SHA256`, `Digest::SHA512` backed by openssl. ([#9864](https://github.com/crystal-lang/crystal/pull/9864), thanks @bcardiff) -- Fix overflows in MD5 and SHA1. ([#9781](https://github.com/crystal-lang/crystal/pull/9781), thanks @bcardiff) -- Add `OpenSSL::SSL::Context#set_modern_ciphers` and related methods to set ciphers and cipher suites in a more portable fashion. ([#9814](https://github.com/crystal-lang/crystal/pull/9814), [#10298](https://github.com/crystal-lang/crystal/pull/10298), thanks @bcardiff) -- Add `OpenSSL::SSL::Context#security_level`. ([#9831](https://github.com/crystal-lang/crystal/pull/9831), thanks @bcardiff) -- `OpenSSL::digest` add require for `OpenSSL::Error`. ([#10173](https://github.com/crystal-lang/crystal/pull/10173), thanks @wonderix) - -### Concurrency - -- **(breaking-change)** Hide Channel internal implementation methods. ([#9564](https://github.com/crystal-lang/crystal/pull/9564), thanks @j8r) -- `Channel#close` returns `true` unless the channel was already closed. ([#9443](https://github.com/crystal-lang/crystal/pull/9443), thanks @waj) -- Support splat expressions inside `spawn` macro. ([#10234](https://github.com/crystal-lang/crystal/pull/10234), thanks @HertzDevil) -- Remove incorrect note about negative timeout not triggering. ([#10131](https://github.com/crystal-lang/crystal/pull/10131), thanks @straight-shoota) - -### System - -- Fix `Process.find_executable` to check the found path is executable and add Windows support. ([#9365](https://github.com/crystal-lang/crystal/pull/9365), thanks @oprypin) -- Add `Process.parse_arguments` and fix `CRYSTAL_OPTS` parsing. ([#9518](https://github.com/crystal-lang/crystal/pull/9518), thanks @MakeNowJust) -- Port to NetBSD. ([#9360](https://github.com/crystal-lang/crystal/pull/9360), thanks @niacat) -- Add note about raising `IO::Error` to `Process` methods. ([#9894](https://github.com/crystal-lang/crystal/pull/9894), thanks @straight-shoota) -- Handle errors in `User`/`Group` `from_name?`/`from_id?` as not found. ([#10182](https://github.com/crystal-lang/crystal/pull/10182), thanks @wonderix) -- define the `SC_PAGESIZE` constant on more platforms. ([#9821](https://github.com/crystal-lang/crystal/pull/9821), thanks @carlhoerberg) - -### Runtime - -- Fix bug with passing many args then a struct in Win64 C lib ABI. ([#9520](https://github.com/crystal-lang/crystal/pull/9520), thanks @oprypin) -- Fix C ABI for AArch64. ([#9430](https://github.com/crystal-lang/crystal/pull/9430), thanks @jhass) -- Disable LLVM Global Isel. ([#9401](https://github.com/crystal-lang/crystal/pull/9401), [#9562](https://github.com/crystal-lang/crystal/pull/9562), thanks @jhass, @bcardiff) -- Print original exception after failing to raise it. ([#9220](https://github.com/crystal-lang/crystal/pull/9220), thanks @jhass) -- Use Dwarf information on `Exception::CallStack.print_frame` and crash stacktraces. ([#9792](https://github.com/crystal-lang/crystal/pull/9792), [#9852](https://github.com/crystal-lang/crystal/pull/9852), thanks @bcardiff) -- `MachO`: Handle missing `LC_UUID`. ([#9706](https://github.com/crystal-lang/crystal/pull/9706), thanks @bcardiff) -- Allow `WeakRef` to compile with `-Dgc_none`. ([#9806](https://github.com/crystal-lang/crystal/pull/9806), thanks @bcardiff) -- Fix example in `Crystal.main` docs. ([#9736](https://github.com/crystal-lang/crystal/pull/9736), thanks @hugopl) -- Add a C ABI spec for accepting a large struct - failing on Win32, AArch64. ([#9534](https://github.com/crystal-lang/crystal/pull/9534), thanks @oprypin) -- Don't relativize paths outside of current directory in backtrace. ([#10177](https://github.com/crystal-lang/crystal/pull/10177), thanks @Sija) -- Avoid loading DWARF if env var `CRYSTAL_LOAD_DWARF=0`. ([#10261](https://github.com/crystal-lang/crystal/pull/10261), thanks @bcardiff) -- Fix DWARF loading in FreeBSD. ([#10259](https://github.com/crystal-lang/crystal/pull/10259), thanks @bcardiff) -- Print unhandled exceptions from main routine with `#inspect_with_backtrace`. ([#10086](https://github.com/crystal-lang/crystal/pull/10086), thanks @straight-shoota) - -### Spec - -- **(breaking-change)** Add support for custom failure messages in expectations. ([#10127](https://github.com/crystal-lang/crystal/pull/10127), [#10289](https://github.com/crystal-lang/crystal/pull/10289), thanks @Fryguy, @straight-shoota) -- Allow absolute file paths in `crystal spec` CLI. ([#9951](https://github.com/crystal-lang/crystal/pull/9951), thanks @KevinSjoberg) -- Allow `--link-flags` to be used. ([#6243](https://github.com/crystal-lang/crystal/pull/6243), thanks @bcardiff) -- Improve duration display in `spec --profile`. ([#10044](https://github.com/crystal-lang/crystal/pull/10044), thanks @straight-shoota) -- Add exception handler in spec `at_exit`. ([#10106](https://github.com/crystal-lang/crystal/pull/10106), thanks @straight-shoota) - -## Compiler - -- **(performance)** Don't use init check for constants that are declared before read. ([#9801](https://github.com/crystal-lang/crystal/pull/9801), thanks @asterite) -- **(performance)** Don't use init check for class vars initialized before read. ([#9995](https://github.com/crystal-lang/crystal/pull/9995), thanks @asterite) -- **(performance)** Don't use init flag for string constants. ([#9808](https://github.com/crystal-lang/crystal/pull/9808), thanks @asterite) -- Run side effects for class var with constant initializer. ([#10010](https://github.com/crystal-lang/crystal/pull/10010), thanks @asterite) -- Fix `VaList` and disable `va_arg` for AArch64. ([#9422](https://github.com/crystal-lang/crystal/pull/9422), thanks @jhass) -- Fix cache corrupted when compilation is cancelled. ([#9558](https://github.com/crystal-lang/crystal/pull/9558), thanks @waj) -- Consider `select` as an opening keyword in macros. ([#9624](https://github.com/crystal-lang/crystal/pull/9624), thanks @asterite) -- Use function attribute `frame-pointer`. ([#9361](https://github.com/crystal-lang/crystal/pull/9361), thanks @kubo39) -- Make missing `__crystal_raise_overflow` error more clear. ([#9686](https://github.com/crystal-lang/crystal/pull/9686), thanks @3n-k1) -- Forbid calls with both block arg and block. ([#10026](https://github.com/crystal-lang/crystal/pull/10026), thanks @HertzDevil) -- Add method name to frozen type error. ([#10057](https://github.com/crystal-lang/crystal/pull/10057), thanks @straight-shoota) -- Use file lock to avoid clashes between compilations. ([#10050](https://github.com/crystal-lang/crystal/pull/10050), thanks @asterite) -- Add a couple of missing end locations to some nodes. ([#10012](https://github.com/crystal-lang/crystal/pull/10012), thanks @asterite) -- Don't visit enum member expanded by macro twice. ([#10105](https://github.com/crystal-lang/crystal/pull/10105), thanks @asterite) -- Fix crash when matching arguments against splat restriction after positional parameters. ([#10172](https://github.com/crystal-lang/crystal/pull/10172), thanks @HertzDevil) -- Fix parsing of `do end` blocks inside parenthesis. ([#10189](https://github.com/crystal-lang/crystal/pull/10189), [#10207](https://github.com/crystal-lang/crystal/pull/10207), thanks @straight-shoota, @Sija) -- Fix regex parsing after then in `case/when`. ([#10274](https://github.com/crystal-lang/crystal/pull/10274), thanks @asterite) -- Fix error message for non-exhaustive case statements of 3 or more `Bool`/`Enum` values. ([#10194](https://github.com/crystal-lang/crystal/pull/10194), thanks @HertzDevil) -- Forbid reopening generic types with different type var splats. ([#10167](https://github.com/crystal-lang/crystal/pull/10167), thanks @HertzDevil) -- Apply stricter checks to macro names. ([#10069](https://github.com/crystal-lang/crystal/pull/10069), thanks @HertzDevil) -- Reword error message when including/inheriting generic type without type vars. ([#10206](https://github.com/crystal-lang/crystal/pull/10206), thanks @HertzDevil) -- Print a message pointing to the online playground if compiled without it. ([#9622](https://github.com/crystal-lang/crystal/pull/9622), thanks @deiv) -- Handle typedef type restriction against alias type correctly. ([#9490](https://github.com/crystal-lang/crystal/pull/9490), thanks @MakeNowJust) -- Initial support LLVM 11.0 with known issue regarding optimizations [#10220](https://github.com/crystal-lang/crystal/issues/10220). ([#9829](https://github.com/crystal-lang/crystal/pull/9829), [#10293](https://github.com/crystal-lang/crystal/pull/10293), thanks @bcardiff) -- Add support for `--mcpu native`. ([#10264](https://github.com/crystal-lang/crystal/pull/10264), [#10276](https://github.com/crystal-lang/crystal/pull/10276), thanks @BlobCodes, @bcardiff) -- Fix `Process.run` uses in compiler to handle exec failure. ([#9893](https://github.com/crystal-lang/crystal/pull/9893), [#9911](https://github.com/crystal-lang/crystal/pull/9911), [#9913](https://github.com/crystal-lang/crystal/pull/9913), thanks @straight-shoota, @bcardiff, @oprypin) -- Fix `Command#report_warnings` to gracefully handle missing `Compiler#program`. ([#9866](https://github.com/crystal-lang/crystal/pull/9866), thanks @straight-shoota) -- Add `Program#host_compiler`. ([#9920](https://github.com/crystal-lang/crystal/pull/9920), thanks @straight-shoota) -- Add `Crystal::Path#name_size` implementation. ([#9380](https://github.com/crystal-lang/crystal/pull/9380), thanks @straight-shoota) -- Refactor helper methods in `call_error.cr`. ([#9376](https://github.com/crystal-lang/crystal/pull/9376), thanks @straight-shoota) -- Delegate def error location to implementing type. ([#10159](https://github.com/crystal-lang/crystal/pull/10159), thanks @straight-shoota) -- Refactor `Crystal::Exception` to `Crystal::CodeError`. ([#10197](https://github.com/crystal-lang/crystal/pull/10197), thanks @straight-shoota) -- Refactor `check_single_def_error_message`. ([#10196](https://github.com/crystal-lang/crystal/pull/10196), thanks @straight-shoota) -- Code clean-ups. ([#9468](https://github.com/crystal-lang/crystal/pull/9468), thanks @MakeNowJust) -- Refactors for compiler specs. ([#9379](https://github.com/crystal-lang/crystal/pull/9379), thanks @straight-shoota) -- Windows CI: Fix and enable compiler specs. ([#9348](https://github.com/crystal-lang/crystal/pull/9348), [#9560](https://github.com/crystal-lang/crystal/pull/9560), thanks @oprypin) - -### Language semantics - -- **(breaking-change)** Handle union restrictions with free vars properly. ([#10267](https://github.com/crystal-lang/crystal/pull/10267), thanks @HertzDevil) -- **(breaking-change)** Disallow keywords as block argument name. ([#9704](https://github.com/crystal-lang/crystal/pull/9704), thanks @asterite) -- **(breaking-change)** Fix `a-b -c` incorrectly parsed as `a - (b - c)`. ([#9652](https://github.com/crystal-lang/crystal/pull/9652), [#9884](https://github.com/crystal-lang/crystal/pull/9884), thanks @asterite, @bcardiff) -- **(breaking-change)** Check abstract def implementations with splats, default values and keyword arguments. ([#9585](https://github.com/crystal-lang/crystal/pull/9585), thanks @waj) -- **(breaking-change)** Make abstract def return type warning an error. ([#9810](https://github.com/crystal-lang/crystal/pull/9810), thanks @bcardiff) -- **(breaking-change)** Error when abstract def implementation adds a type restriction. ([#9634](https://github.com/crystal-lang/crystal/pull/9634), thanks @waj) -- **(breaking-change)** Don't interpret Proc typedefs as aliases. ([#10254](https://github.com/crystal-lang/crystal/pull/10254), thanks @HertzDevil) -- **(breaking-change)** Remove the special logic of union of pointer and `nil`. ([#9872](https://github.com/crystal-lang/crystal/pull/9872), thanks @asterite) -- Check abstract def implementations with double splats. ([#9633](https://github.com/crystal-lang/crystal/pull/9633), thanks @waj) -- Make inherited hook work through generic instances. ([#9701](https://github.com/crystal-lang/crystal/pull/9701), thanks @asterite) -- Attach doc comment to annotation in macro expansion. ([#9630](https://github.com/crystal-lang/crystal/pull/9630), thanks @MakeNowJust) -- Marks else branch of exhaustive case as unreachable, improving resulting type. ([#9659](https://github.com/crystal-lang/crystal/pull/9659), thanks @Sija) -- Make `Pointer(T)#value=` stricter for generic arguments. ([#10224](https://github.com/crystal-lang/crystal/pull/10224), thanks @asterite) -- Extend type filtering of conditional clauses to arbitrary logical connectives. ([#10147](https://github.com/crystal-lang/crystal/pull/10147), thanks @HertzDevil) -- Support NamedTuple instance restrictions in redefinition checks. ([#10245](https://github.com/crystal-lang/crystal/pull/10245), thanks @HertzDevil) -- Respect block arg restriction when given block has a splat parameter. ([#10242](https://github.com/crystal-lang/crystal/pull/10242), thanks @HertzDevil) -- Make overloads without double splat more specialized than overloads with one. ([#10185](https://github.com/crystal-lang/crystal/pull/10185), thanks @HertzDevil) -- Support type splats inside explicit Unions in type restrictions. ([#10174](https://github.com/crystal-lang/crystal/pull/10174), thanks @HertzDevil) -- Fix splats on generic arguments. ([#9859](https://github.com/crystal-lang/crystal/pull/9859), thanks @asterite) -- Correctly compute line number after macro escape. ([#9858](https://github.com/crystal-lang/crystal/pull/9858), thanks @asterite) -- Fix bug for C structs assigned to a type. ([#9743](https://github.com/crystal-lang/crystal/pull/9743), thanks @matthewmcgarvey) -- Promote C variadic args as needed. ([#9747](https://github.com/crystal-lang/crystal/pull/9747), thanks @asterite) -- Fix parsing of `def self./` and `def self.%`. ([#9721](https://github.com/crystal-lang/crystal/pull/9721), thanks @asterite) -- Fix `ASTNode#to_s` for parenthesized expression in block. ([#9629](https://github.com/crystal-lang/crystal/pull/9629), thanks @MakeNowJust) -- Don't form a closure on `typeof(@ivar)`. ([#9723](https://github.com/crystal-lang/crystal/pull/9723), thanks @asterite) -- Add a few missing `expanded.transform self`. ([#9506](https://github.com/crystal-lang/crystal/pull/9506), thanks @asterite) -- Treat `super` as special only if it doesn't have a receiver (i.e.: allow `super` user defined methods). ([#10011](https://github.com/crystal-lang/crystal/pull/10011), thanks @asterite) -- Implement auto-casting in a better way. ([#9501](https://github.com/crystal-lang/crystal/pull/9501), thanks @asterite) -- Try literal type first when autocasting to unions types. ([#9610](https://github.com/crystal-lang/crystal/pull/9610), thanks @asterite) -- Correctly pass named arguments for `previous_def` and `super`. ([#9834](https://github.com/crystal-lang/crystal/pull/9834), thanks @asterite) -- Turn proc pointer into proc literal in some cases to closure needed variables. ([#9824](https://github.com/crystal-lang/crystal/pull/9824), thanks @asterite) -- Fix proc of self causing multi-dispatch. ([#9972](https://github.com/crystal-lang/crystal/pull/9972), thanks @asterite) -- Fixes and improvements to closured variables. ([#9986](https://github.com/crystal-lang/crystal/pull/9986), thanks @asterite) -- Don't merge proc types but allow assigning them if they are compatible. ([#9971](https://github.com/crystal-lang/crystal/pull/9971), thanks @asterite) -- Reset nilable vars inside block method. ([#10091](https://github.com/crystal-lang/crystal/pull/10091), thanks @asterite) -- Reset free vars before attempting each overload match. ([#10271](https://github.com/crystal-lang/crystal/pull/10271), thanks @HertzDevil) -- Let `offsetof` support `Tuples`. ([#10218](https://github.com/crystal-lang/crystal/pull/10218), thanks @hugopl) - -## Tools - -- Fetch git config correctly. ([#9640](https://github.com/crystal-lang/crystal/pull/9640), thanks @dorianmariefr) - -### Formatter - -- Avoid adding a newline after comment before end. ([#9722](https://github.com/crystal-lang/crystal/pull/9722), thanks @asterite) -- Apply string pieces combination even if heredoc has no indent. ([#9475](https://github.com/crystal-lang/crystal/pull/9475), thanks @MakeNowJust) -- Correctly format named arguments like `foo:bar`. ([#9740](https://github.com/crystal-lang/crystal/pull/9740), thanks @asterite) -- Handle comment before do in a separate line. ([#9762](https://github.com/crystal-lang/crystal/pull/9762), thanks @asterite) -- Fix indent for the first comment of `select when`/`else`. ([#10002](https://github.com/crystal-lang/crystal/pull/10002), thanks @MakeNowJust) -- Reduce redundant newlines at end of `begin ... end`. ([#9741](https://github.com/crystal-lang/crystal/pull/9741), thanks @MakeNowJust) -- Let formatter normalize crystal language tag for code blocks in doc comments. ([#10156](https://github.com/crystal-lang/crystal/pull/10156), thanks @straight-shoota) -- Fix indentation of trailing comma in call with a block. ([#10223](https://github.com/crystal-lang/crystal/pull/10223), thanks @MakeNowJust) - -### Doc generator - -- Linkification: refactor, fix edge cases and add specs. ([#9817](https://github.com/crystal-lang/crystal/pull/9817), thanks @oprypin) -- Exclude types from docs who are in nodoc namespaces. ([#9819](https://github.com/crystal-lang/crystal/pull/9819), thanks @Blacksmoke16) -- Fix link generation for lib type reference. ([#9931](https://github.com/crystal-lang/crystal/pull/9931), thanks @straight-shoota) -- Correctly render underscores in code blocks. ([#9822](https://github.com/crystal-lang/crystal/pull/9822), thanks @Blacksmoke16) -- Fix some HTML in docs generator. ([#9918](https://github.com/crystal-lang/crystal/pull/9918), thanks @straight-shoota) -- Correctly handle broken source code on syntax highlighting. ([#9416](https://github.com/crystal-lang/crystal/pull/9416), thanks @MakeNowJust) -- Re-introduce canonical URL for docs generator. ([#9917](https://github.com/crystal-lang/crystal/pull/9917), thanks @straight-shoota) -- Expose `"location"` and `"args_html"` in doc JSON. ([#10122](https://github.com/crystal-lang/crystal/pull/10122), [#10200](https://github.com/crystal-lang/crystal/pull/10200), thanks @oprypin) -- Expose `"aliased_html"` in doc JSON, make `"aliased"` field nullable. ([#10117](https://github.com/crystal-lang/crystal/pull/10117), thanks @oprypin) -- Support linking to section headings in same tab. ([#9826](https://github.com/crystal-lang/crystal/pull/9826), thanks @Blacksmoke16) -- Do not highlight `undef` as a keyword, it's not. ([#10216](https://github.com/crystal-lang/crystal/pull/10216), thanks @rhysd) - -## Others - -- CI improvements and housekeeping. ([#9507](https://github.com/crystal-lang/crystal/pull/9507), [#9513](https://github.com/crystal-lang/crystal/pull/9513), [#9512](https://github.com/crystal-lang/crystal/pull/9512), [#9515](https://github.com/crystal-lang/crystal/pull/9515), [#9514](https://github.com/crystal-lang/crystal/pull/9514), [#9609](https://github.com/crystal-lang/crystal/pull/9609), [#9642](https://github.com/crystal-lang/crystal/pull/9642), [#9758](https://github.com/crystal-lang/crystal/pull/9758), [#9763](https://github.com/crystal-lang/crystal/pull/9763), [#9850](https://github.com/crystal-lang/crystal/pull/9850), [#9906](https://github.com/crystal-lang/crystal/pull/9906), [#9907](https://github.com/crystal-lang/crystal/pull/9907), [#9912](https://github.com/crystal-lang/crystal/pull/9912), [#10078](https://github.com/crystal-lang/crystal/pull/10078), [#10217](https://github.com/crystal-lang/crystal/pull/10217), [#10255](https://github.com/crystal-lang/crystal/pull/10255), thanks @jhass, @bcardiff, @waj, @oprypin, @j8r, @Sija) -- Add timeout to individual specs on multi-threading. ([#9865](https://github.com/crystal-lang/crystal/pull/9865), thanks @bcardiff) -- Initial AArch64 CI. ([#9508](https://github.com/crystal-lang/crystal/pull/9508), [#9582](https://github.com/crystal-lang/crystal/pull/9582), thanks @jhass, @waj) -- Update distribution-scripts and use LLVM 10 on Linux packages. ([#9710](https://github.com/crystal-lang/crystal/pull/9710), thanks @bcardiff) -- Update distribution-scripts and publish nightly packages to bintray. ([#9663](https://github.com/crystal-lang/crystal/pull/9663), thanks @bcardiff) -- Update distribution-scripts to use Shards v0.13.0. ([#10280](https://github.com/crystal-lang/crystal/pull/10280), thanks @bcardiff) -- Initial Nix development environment. Use Nix for OSX CI instead of homebrew. ([#9727](https://github.com/crystal-lang/crystal/pull/9727), [#9776](https://github.com/crystal-lang/crystal/pull/9776), thanks @bcardiff) -- Prevent recursive call in `bin/crystal` wrapper. ([#9505](https://github.com/crystal-lang/crystal/pull/9505), thanks @jhass) -- Allow overriding `CRYSTAL_PATH` in `bin/crystal` wrapper . ([#9632](https://github.com/crystal-lang/crystal/pull/9632), thanks @bcardiff) -- Makefile: support changing the current crystal via env var and argument. ([#9471](https://github.com/crystal-lang/crystal/pull/9471), thanks @bcardiff) -- Makefile: improve supported LLVM versions message. ([#10221](https://github.com/crystal-lang/crystal/pull/10221), [#10269](https://github.com/crystal-lang/crystal/pull/10269), thanks @rdp, @straight-shoota) -- Update CONTRIBUTING.md. ([#9448](https://github.com/crystal-lang/crystal/pull/9448), [#10249](https://github.com/crystal-lang/crystal/pull/10249), [#10250](https://github.com/crystal-lang/crystal/pull/10250), thanks @wontruefree, @sanks64, @straight-shoota) -- Refactor `RedBlackTree` sample to use an enum instead of symbols. ([#10233](https://github.com/crystal-lang/crystal/pull/10233), thanks @Sija) -- Add security policy. ([#9984](https://github.com/crystal-lang/crystal/pull/9984), thanks @straight-shoota) -- Use `name: nightly` in docs-versions.sh for HEAD. ([#9915](https://github.com/crystal-lang/crystal/pull/9915), thanks @straight-shoota) -- Use canonical base url for generating docs. ([#10007](https://github.com/crystal-lang/crystal/pull/10007), thanks @straight-shoota) -- Remove Vagrantfile. ([#10033](https://github.com/crystal-lang/crystal/pull/10033), thanks @straight-shoota) -- Update NOTICE's copyright year to 2021. ([#10166](https://github.com/crystal-lang/crystal/pull/10166), thanks @matiasgarciaisaia) - -# 0.35.1 (2020-06-19) - -## Standard library - -### Collections - -- Remove `Hash#each` type restriction to allow working with splats. ([#9456](https://github.com/crystal-lang/crystal/pull/9456), thanks @bcardiff) - -### Networking - -- Revert `IO#write` changes in 0.35.0 and let it return Nil. ([#9469](https://github.com/crystal-lang/crystal/pull/9469), thanks @bcardiff) -- Avoid leaking logging context in HTTP request handlers. ([#9494](https://github.com/crystal-lang/crystal/pull/9494), thanks @Blacksmoke16) - -### Crypto - -- Use less strict cipher compatibility for OpenSSL client context. ([#9459](https://github.com/crystal-lang/crystal/pull/9459), thanks @straight-shoota) -- Fix `Digest::Base` block argument type restrictions. ([#9500](https://github.com/crystal-lang/crystal/pull/9500), thanks @straight-shoota) - -### Logging - -- Fix `Log.context.set` docs for hash based data. ([#9470](https://github.com/crystal-lang/crystal/pull/9470), thanks @bcardiff) - -## Compiler - -- Show warnings even if there are errors. ([#9461](https://github.com/crystal-lang/crystal/pull/9461), thanks @asterite) -- Fix parsing of `{foo: X, typeof: Y}` type. ([#9453](https://github.com/crystal-lang/crystal/pull/9453), thanks @MakeNowJust) -- Fix parsing of proc in hash `of` key type. ([#9458](https://github.com/crystal-lang/crystal/pull/9458), thanks @MakeNowJust) -- Revert debug level information changes in specs to fix 32 bits builds. ([#9466](https://github.com/crystal-lang/crystal/pull/9466), thanks @bcardiff) - -## Others - -- CI improvements and housekeeping. ([#9455](https://github.com/crystal-lang/crystal/pull/9455), thanks @bcardiff) -- Code formatting. ([#9482](https://github.com/crystal-lang/crystal/pull/9482), thanks @MakeNowJust) - -# 0.35.0 (2020-06-09) - -## Language changes - -- **(breaking-change)** Let `case when` be non-exhaustive, introduce `case in` as exhaustive. ([#9258](https://github.com/crystal-lang/crystal/pull/9258), [#9045](https://github.com/crystal-lang/crystal/pull/9045), thanks @asterite) -- Allow `->@ivar.foo` and `->@@cvar.foo` expressions. ([#9268](https://github.com/crystal-lang/crystal/pull/9268), thanks @MakeNowJust) - -### Macros - -- Allow executing OpAssign (`+=`, `||=`, etc.) inside macros. ([#9409](https://github.com/crystal-lang/crystal/pull/9409), thanks @asterite) - -## Standard library - -- **(breaking-change)** Refactor to standardize on first argument for methods receiving `IO`. ([#9134](https://github.com/crystal-lang/crystal/pull/9134), [#9289](https://github.com/crystal-lang/crystal/pull/9289), [#9303](https://github.com/crystal-lang/crystal/pull/9303), [#9318](https://github.com/crystal-lang/crystal/pull/9318), thanks @straight-shoota, @bcardiff, @oprypin) -- **(breaking-change)** Cleanup Digest and OpenSSL::Digest. ([#8426](https://github.com/crystal-lang/crystal/pull/8426), thanks @didactic-drunk) -- Fix `Enum#to_s` for private enum. ([#9126](https://github.com/crystal-lang/crystal/pull/9126), thanks @straight-shoota) -- Refactor `Benchmark::IPS::Entry` to use `UInt64` in `bytes_per_op`. ([#9081](https://github.com/crystal-lang/crystal/pull/9081), thanks @jhass) -- Add `Experimental` annotation and doc label. ([#9244](https://github.com/crystal-lang/crystal/pull/9244), thanks @bcardiff) -- Add subcommands to `OptionParser`. ([#9009](https://github.com/crystal-lang/crystal/pull/9009), [#9133](https://github.com/crystal-lang/crystal/pull/9133), thanks @RX14, @Sija) -- Make `NamedTuple#sorted_keys` public. ([#9263](https://github.com/crystal-lang/crystal/pull/9263), thanks @waj) -- Fix example codes in multiple places. ([#9203](https://github.com/crystal-lang/crystal/pull/9203), thanks @maiha) - -### Macros - -- **(breaking-change)** Remove top-level `assert_responds_to` macro. ([#9085](https://github.com/crystal-lang/crystal/pull/9085), thanks @bcardiff) -- **(breaking-change)** Drop top-level `parallel` macro. ([#9097](https://github.com/crystal-lang/crystal/pull/9097), thanks @bcardiff) -- Fix lazy property not forwarding annotations. ([#9140](https://github.com/crystal-lang/crystal/pull/9140), thanks @asterite) -- Add `host_flag?` macro method, not affected by cross-compilation. ([#9049](https://github.com/crystal-lang/crystal/pull/9049), thanks @oprypin) -- Add `.each` and `.each_with_index` to various macro types. ([#9120](https://github.com/crystal-lang/crystal/pull/9120), thanks @Blacksmoke16) -- Add `StringLiteral#titleize` macro method. ([#9269](https://github.com/crystal-lang/crystal/pull/9269), thanks @MakeNowJust) -- Add `TypeNode` methods to check what "type" the node is. ([#9270](https://github.com/crystal-lang/crystal/pull/9270), thanks @Blacksmoke16) -- Fix support `TypeNode.name(generic_args: false)` for generic instances. ([#9224](https://github.com/crystal-lang/crystal/pull/9224), thanks @Blacksmoke16) - -### Numeric - -- **(breaking-change)** Add `Int#digits`, reverse `BigInt#digits` result. ([#9383](https://github.com/crystal-lang/crystal/pull/9383), thanks @asterite) -- Fix overflow checking for operations with mixed sign. ([#9403](https://github.com/crystal-lang/crystal/pull/9403), thanks @waj) -- Add `BigInt#factorial` using GMP. ([#9132](https://github.com/crystal-lang/crystal/pull/9132), thanks @peheje) - -### Text - -- Add `String#titleize`. ([#9204](https://github.com/crystal-lang/crystal/pull/9204), thanks @hugopl) -- Add `Regex#matches?` and `String#matches?`. ([#8989](https://github.com/crystal-lang/crystal/pull/8989), thanks @MakeNowJust) -- Add `IO` overloads to various `String` case methods. ([#9236](https://github.com/crystal-lang/crystal/pull/9236), thanks @Blacksmoke16) -- Improve docs examples regarding `Regex::MatchData`. ([#9010](https://github.com/crystal-lang/crystal/pull/9010), thanks @MakeNowJust) -- Improve docs on `String` methods. ([#8447](https://github.com/crystal-lang/crystal/pull/8447), thanks @jan-zajic) - -### Collections - -- **(breaking-change)** Add `Enumerable#first` with fallback block. ([#8999](https://github.com/crystal-lang/crystal/pull/8999), thanks @MakeNowJust) -- Fix `Array#delete_at` bug with negative start index. ([#9399](https://github.com/crystal-lang/crystal/pull/9399), thanks @asterite) -- Fix `Enumerable#{zip,zip?}` when self is an `Iterator`. ([#9330](https://github.com/crystal-lang/crystal/pull/9330), thanks @mneumann) -- Make `Range#each` and `Range#reverse_each` work better with end/begin-less values. ([#9325](https://github.com/crystal-lang/crystal/pull/9325), thanks @asterite) -- Improve docs on `Hash`. ([#8887](https://github.com/crystal-lang/crystal/pull/8887), thanks @rdp) - -### Serialization - -- **(breaking-change)** Deprecate `JSON.mapping` and `YAML.mapping`. ([#9272](https://github.com/crystal-lang/crystal/pull/9272), thanks @straight-shoota) -- **(breaking-change)** Make `INI` a module. ([#9408](https://github.com/crystal-lang/crystal/pull/9408), thanks @j8r) -- Fix integration between `record` macro and `JSON::Serializable`/`YAML::Serializable` regarding default values. ([#9063](https://github.com/crystal-lang/crystal/pull/9063), thanks @Blacksmoke16) -- Fix `XML.parse` invalid mem access in multi-thread. ([#9098](https://github.com/crystal-lang/crystal/pull/9098), thanks @bcardiff, @asterite) -- Fix double string escape in `XML::Node#content=`. ([#9300](https://github.com/crystal-lang/crystal/pull/9300), thanks @straight-shoota) -- Improve xpath regarding namespaces. ([#9288](https://github.com/crystal-lang/crystal/pull/9288), thanks @asterite) -- Escape CDATA end sequences. ([#9230](https://github.com/crystal-lang/crystal/pull/9230), thanks @Blacksmoke16) -- Add `JSON` and `YAML` serialization to `Path`. ([#9156](https://github.com/crystal-lang/crystal/pull/9156), thanks @straight-shoota) -- Specify pkg-config name for `libyaml`. ([#9426](https://github.com/crystal-lang/crystal/pull/9426), thanks @jhass) -- Specify pkg-config name for `libxml2`. ([#9436](https://github.com/crystal-lang/crystal/pull/9436), thanks @Blacksmoke16) -- Make YAML specs robust against libyaml 0.2.5. ([#9427](https://github.com/crystal-lang/crystal/pull/9427), thanks @jhass) - -### Time - -- **(breaking-change)** Support different number of fraction digits for RFC3339 time format. ([#9283](https://github.com/crystal-lang/crystal/pull/9283), thanks @waj) -- Fix parsing AM/PM hours. ([#9334](https://github.com/crystal-lang/crystal/pull/9334), thanks @straight-shoota) -- Improve `File.utime` precision from second to 100-nanosecond on Windows. ([#9344](https://github.com/crystal-lang/crystal/pull/9344), thanks @kubo) - -### Files - -- **(breaking-change)** Move `Flate`, `Gzip`, `Zip`, `Zlib` to `Compress`. ([#8886](https://github.com/crystal-lang/crystal/pull/8886), thanks @bcardiff) -- **(breaking-change)** Cleanup `File` & `FileUtils`. ([#9175](https://github.com/crystal-lang/crystal/pull/9175), thanks @bcardiff) -- Fix realpath on macOS 10.15 (Catalina). ([#9296](https://github.com/crystal-lang/crystal/pull/9296), thanks @waj) -- Fix `File#pos`, `File#seek` and `File#truncate` over 2G on Windows. ([#9015](https://github.com/crystal-lang/crystal/pull/9015), thanks @kubo) -- Fix `File.rename` to overwrite the destination file on Windows, like elsewhere. ([#9038](https://github.com/crystal-lang/crystal/pull/9038), thanks @oprypin) -- Fix `File`'s specs and related exception types on Windows. ([#9037](https://github.com/crystal-lang/crystal/pull/9037), thanks @oprypin) -- Add support for `Path` arguments to multiple methods. ([#9153](https://github.com/crystal-lang/crystal/pull/9153), thanks @straight-shoota) -- Add `Path#each_part` iterator. ([#9138](https://github.com/crystal-lang/crystal/pull/9138), thanks @straight-shoota) -- Add `Path#relative_to`. ([#9169](https://github.com/crystal-lang/crystal/pull/9169), thanks @straight-shoota) -- Add support for `Path` pattern to `Dir.glob`. ([#9420](https://github.com/crystal-lang/crystal/pull/9420), thanks @straight-shoota) -- Implement `File#fsync` on Windows. ([#9257](https://github.com/crystal-lang/crystal/pull/9257), thanks @kubo) -- Refactor `Path` regarding empty and `.`. ([#9137](https://github.com/crystal-lang/crystal/pull/9137), thanks @straight-shoota) - -### Networking - -- **(breaking-change)** Make `IO#skip`, `IO#write` returns the number of bytes it skipped/written as `Int64`. ([#9233](https://github.com/crystal-lang/crystal/pull/9233), [#9363](https://github.com/crystal-lang/crystal/pull/9363), thanks @bcardiff) -- **(breaking-change)** Improve error handling and logging in `HTTP::Server`. ([#9115](https://github.com/crystal-lang/crystal/pull/9115), [#9034](https://github.com/crystal-lang/crystal/pull/9034), thanks @waj, @straight-shoota) -- **(breaking-change)** Change `HTTP::Request#remote_address` type to `Socket::Address?`. ([#9210](https://github.com/crystal-lang/crystal/pull/9210), thanks @waj) -- Fix `flush` methods to always flush underlying `IO`. ([#9320](https://github.com/crystal-lang/crystal/pull/9320), thanks @straight-shoota) -- Fix `HTTP::Server` sporadic failure in SSL handshake. ([#9177](https://github.com/crystal-lang/crystal/pull/9177), thanks @waj) -- `WebSocket` shouldn't reply with same close code. ([#9313](https://github.com/crystal-lang/crystal/pull/9313), thanks @waj) -- Ignore response body during `WebSocket` handshake. ([#9418](https://github.com/crystal-lang/crystal/pull/9418), thanks @waj) -- Treat cookies which expire in this instant as expired. ([#9061](https://github.com/crystal-lang/crystal/pull/9061), thanks @RX14) -- Set `sync` or `flush_on_newline` for standard I/O on Windows. ([#9207](https://github.com/crystal-lang/crystal/pull/9207), thanks @kubo) -- Prefer HTTP basic authentication in OAuth2 client. ([#9127](https://github.com/crystal-lang/crystal/pull/9127), thanks @crush-157) -- Defer request upgrade in `HTTP::Server` (aka: WebSockets). ([#9243](https://github.com/crystal-lang/crystal/pull/9243), thanks @waj) -- Improve `URI::Punycode`, `HTTP::WebSocketHandler`, `HTTP::Status` documentation. ([#9068](https://github.com/crystal-lang/crystal/pull/9068), [#9130](https://github.com/crystal-lang/crystal/pull/9130), [#9180](https://github.com/crystal-lang/crystal/pull/9180), thanks @Blacksmoke16, @dscottboggs, @wontruefree) -- Remove `HTTP::Params::Builder#to_s`, use underlying `IO` directly. ([#9319](https://github.com/crystal-lang/crystal/pull/9319), thanks @straight-shoota) -- Fixed some regular failing specs in multi-thread mode. ([#9412](https://github.com/crystal-lang/crystal/pull/9412), thanks @bcardiff) - -### Crypto - -- **(security)** Update SSL server secure defaults. ([#9026](https://github.com/crystal-lang/crystal/pull/9026), thanks @straight-shoota) -- Add LibSSL `NO_TLS_V1_3` option. ([#9350](https://github.com/crystal-lang/crystal/pull/9350), thanks @lun-4) - -### Logging - -- **(breaking-change)** Rename `Log::Severity::Warning` to `Warn`. Drop `Verbose`. Add `Trace` and `Notice`. ([#9293](https://github.com/crystal-lang/crystal/pull/9293), [#9107](https://github.com/crystal-lang/crystal/pull/9107), [#9316](https://github.com/crystal-lang/crystal/pull/9316), thanks @bcardiff, @paulcsmith) -- **(breaking-change)** Allow local data on entries via `Log::Metadata` and redesign `Log::Context`. ([#9118](https://github.com/crystal-lang/crystal/pull/9118), [#9227](https://github.com/crystal-lang/crystal/pull/9227), [#9150](https://github.com/crystal-lang/crystal/pull/9150), [#9157](https://github.com/crystal-lang/crystal/pull/9157), thanks @bcardiff, @waj) -- **(breaking-change)** Split top-level `Log::Metadata` from `Log::Metadata::Value`, drop immutability via clone, improve performance. ([#9295](https://github.com/crystal-lang/crystal/pull/9295), thanks @bcardiff) -- **(breaking-change)** Rework `Log.setup_from_env` and defaults. ([#9145](https://github.com/crystal-lang/crystal/pull/9145), [#9240](https://github.com/crystal-lang/crystal/pull/9240), thanks @bcardiff) -- Add `Log.capture` spec helper. ([#9201](https://github.com/crystal-lang/crystal/pull/9201), thanks @bcardiff) -- Redesign `Log::Formatter`. ([#9211](https://github.com/crystal-lang/crystal/pull/9211), thanks @waj) -- Add `to_json` for `Log::Context`. ([#9101](https://github.com/crystal-lang/crystal/pull/9101), thanks @paulcsmith) -- Add `Log::IOBackend#new` with `formatter` named argument. ([#9105](https://github.com/crystal-lang/crystal/pull/9105), [#9434](https://github.com/crystal-lang/crystal/pull/9434), thanks @paulcsmith, @bcardiff) -- Allow `nil` as context raw values. ([#9121](https://github.com/crystal-lang/crystal/pull/9121), thanks @bcardiff) -- Add missing `Log#with_context`. ([#9058](https://github.com/crystal-lang/crystal/pull/9058), thanks @bcardiff) -- Fix types referred in documentation. ([#9117](https://github.com/crystal-lang/crystal/pull/9117), thanks @bcardiff) -- Allow override context within logging calls. ([#9146](https://github.com/crystal-lang/crystal/pull/9146), thanks @bcardiff) -- Check severity before backend. ([#9400](https://github.com/crystal-lang/crystal/pull/9400), thanks @asterite) - -### Concurrency - -- **(breaking-change)** Drop `Concurrent::Future` and top-level methods `delay`, `future`, `lazy`. Use [crystal-community/future.cr](https://github.com/crystal-community/future.cr). ([#9093](https://github.com/crystal-lang/crystal/pull/9093), thanks @bcardiff) - -### System - -- **(breaking-change)** Deprecate `Process#kill`, use `Process#signal`. ([#9006](https://github.com/crystal-lang/crystal/pull/9006), thanks @oprypin, @jan-zajic) -- `Process` raises `IO::Error` (or subclasses). ([#9340](https://github.com/crystal-lang/crystal/pull/9340), thanks @waj) -- Add `Process.quote` and fix shell usages in the compiler. ([#9043](https://github.com/crystal-lang/crystal/pull/9043), [#9369](https://github.com/crystal-lang/crystal/pull/9369), thanks @oprypin, @bcardiff) -- Implement `Process` support on Windows. ([#9047](https://github.com/crystal-lang/crystal/pull/9047), [#9021](https://github.com/crystal-lang/crystal/pull/9021), [#9122](https://github.com/crystal-lang/crystal/pull/9122), [#9112](https://github.com/crystal-lang/crystal/pull/9112), [#9149](https://github.com/crystal-lang/crystal/pull/9149), [#9310](https://github.com/crystal-lang/crystal/pull/9310), thanks @oprypin, @RX14, @kubo, @jan-zajic) -- Fix compile-time checking of `dup3`/`clock_gettime` methods definition. ([#9407](https://github.com/crystal-lang/crystal/pull/9407), thanks @asterite) -- Use `Int64` as portable `Process.pid` type. ([#9019](https://github.com/crystal-lang/crystal/pull/9019), thanks @oprypin) - -### Runtime - -- **(breaking-change)** Deprecate top-level `fork`. ([#9136](https://github.com/crystal-lang/crystal/pull/9136), thanks @bcardiff) -- **(breaking-change)** Move `Debug` to `Crystal` namespace. ([#9176](https://github.com/crystal-lang/crystal/pull/9176), thanks @bcardiff) -- Fix segfaults when static linking with musl. ([#9238](https://github.com/crystal-lang/crystal/pull/9238), thanks @waj) -- Allow calling `at_exit` inside `at_exit`. ([#9388](https://github.com/crystal-lang/crystal/pull/9388), thanks @asterite) -- Rework DWARF loading and fix empty backtraces in musl. ([#9267](https://github.com/crystal-lang/crystal/pull/9267), thanks @waj) -- Add DragonFly(BSD) support. ([#9178](https://github.com/crystal-lang/crystal/pull/9178), thanks @mneumann) -- Add `Crystal::System::Process` to split out system-specific implementations. ([#9035](https://github.com/crystal-lang/crystal/pull/9035), thanks @oprypin) -- Move internal `CallStack` to `Exception::CallStack`. ([#9076](https://github.com/crystal-lang/crystal/pull/9076), thanks @bcardiff) -- Specify pkg-config name for `libevent`. ([#9395](https://github.com/crystal-lang/crystal/pull/9395), thanks @jhass) - -### Spec - -- Reference global Spec in `be_a` macro. ([#9066](https://github.com/crystal-lang/crystal/pull/9066), thanks @asterite) -- Add `-h` short flag to spec runner. ([#9164](https://github.com/crystal-lang/crystal/pull/9164), thanks @straight-shoota) -- Fix `crystal spec` file paths on Windows. ([#9234](https://github.com/crystal-lang/crystal/pull/9234), thanks @oprypin) -- Refactor spec hooks. ([#9090](https://github.com/crystal-lang/crystal/pull/9090), thanks @straight-shoota) - -## Compiler - -- **(breaking-change)** Improve compiler single-file run syntax to make it shebang-friendly `#!`. ([#9171](https://github.com/crystal-lang/crystal/pull/9171), thanks @RX14) -- **(breaking-change)** Use `Process.quote` for `crystal env` output. ([#9428](https://github.com/crystal-lang/crystal/pull/9428), thanks @MakeNowJust) -- **(breaking-change)** Simplify `Link` annotation handling. ([#8972](https://github.com/crystal-lang/crystal/pull/8972), thanks @RX14) -- Fix parsing of `foo:"bar"` inside call or named tuple. ([#9033](https://github.com/crystal-lang/crystal/pull/9033), thanks @asterite) -- Fix parsing of anonymous splat and block arg. ([#9113](https://github.com/crystal-lang/crystal/pull/9113), thanks @MakeNowJust) -- Fix parsing of `unless` inside macro. ([#9024](https://github.com/crystal-lang/crystal/pull/9024), [#9167](https://github.com/crystal-lang/crystal/pull/9167), thanks @MakeNowJust) -- Fix parsing of `\ ` (backslash + space) inside regex literal to ` ` (space). ([#9079](https://github.com/crystal-lang/crystal/pull/9079), thanks @MakeNowJust) -- Fix parsing of ambiguous '+' and '-'. ([#9194](https://github.com/crystal-lang/crystal/pull/9194), thanks @max-codeware) -- Fix parsing of capitalized named argument. ([#9232](https://github.com/crystal-lang/crystal/pull/9232), thanks @asterite) -- Fix parsing of `{[] of Foo, self.foo}` expressions. ([#9329](https://github.com/crystal-lang/crystal/pull/9329), thanks @MakeNowJust) -- Fix cast fun function pointer to Proc. ([#9287](https://github.com/crystal-lang/crystal/pull/9287), thanks @asterite) -- Make compiler warn on deprecated macros. ([#9343](https://github.com/crystal-lang/crystal/pull/9343), thanks @bcardiff) -- Basic support for Win64 C lib ABI. ([#9387](https://github.com/crystal-lang/crystal/pull/9387), thanks @oprypin) -- Make the compiler able to run on Windows and compile itself. ([#9054](https://github.com/crystal-lang/crystal/pull/9054), [#9062](https://github.com/crystal-lang/crystal/pull/9062), [#9095](https://github.com/crystal-lang/crystal/pull/9095), [#9106](https://github.com/crystal-lang/crystal/pull/9106), [#9307](https://github.com/crystal-lang/crystal/pull/9307), thanks @oprypin, @Sija) -- Add docs regarding `CRYSTAL_OPTS`. ([#9018](https://github.com/crystal-lang/crystal/pull/9018), thanks @straight-shoota) -- Remove `Process.run("which")` from compiler. ([#9141](https://github.com/crystal-lang/crystal/pull/9141), thanks @straight-shoota) -- Refactor type parser. ([#9208](https://github.com/crystal-lang/crystal/pull/9208), thanks @MakeNowJust) -- Refactor & clean-up in compiler. ([#8781](https://github.com/crystal-lang/crystal/pull/8781), [#9195](https://github.com/crystal-lang/crystal/pull/9195), thanks @rhysd, @straight-shoota) -- Refactor `CrystalPath::Error`. ([#9359](https://github.com/crystal-lang/crystal/pull/9359), thanks @straight-shoota) -- Refactor and improvements on spec_helper. ([#9367](https://github.com/crystal-lang/crystal/pull/9367), [#9059](https://github.com/crystal-lang/crystal/pull/9059), [#9393](https://github.com/crystal-lang/crystal/pull/9393), [#9351](https://github.com/crystal-lang/crystal/pull/9351), [#9402](https://github.com/crystal-lang/crystal/pull/9402), thanks @straight-shoota, @jhass, @oprypin) -- Split general ABI specs from x86_64-specific ones, run on every platform. ([#9384](https://github.com/crystal-lang/crystal/pull/9384), thanks @oprypin) - -### Language semantics - -- Fix `RegexLiteral#to_s` output when first character of literal is whitespace. ([#9017](https://github.com/crystal-lang/crystal/pull/9017), thanks @MakeNowJust) -- Fix autocasting in multidispatch. ([#9004](https://github.com/crystal-lang/crystal/pull/9004), thanks @asterite) -- Fix propagation of annotations into other scopes. ([#9125](https://github.com/crystal-lang/crystal/pull/9125), thanks @asterite) -- Fix yield computation inside macro code. ([#9324](https://github.com/crystal-lang/crystal/pull/9324), thanks @asterite) -- Fix incorrect type generated with `as?` when type is a union. ([#9417](https://github.com/crystal-lang/crystal/pull/9417), [#9435](https://github.com/crystal-lang/crystal/pull/9435), thanks @asterite) -- Don't duplicate instance var in inherited generic type. ([#9433](https://github.com/crystal-lang/crystal/pull/9433), thanks @asterite) -- Ensure `type_vars` works for generic modules. ([#9161](https://github.com/crystal-lang/crystal/pull/9161), thanks @toddsundsted) -- Make autocasting work in default values against unions. ([#9366](https://github.com/crystal-lang/crystal/pull/9366), thanks @asterite) -- Skip no closure check for non-Crystal procs. ([#9248](https://github.com/crystal-lang/crystal/pull/9248), thanks @jhass) - -### Debugger - -- Improve debugging support. ([#8538](https://github.com/crystal-lang/crystal/pull/8538), thanks @skuznetsov) -- Move recent additions to `DIBuilder` to `LLVMExt`. ([#9114](https://github.com/crystal-lang/crystal/pull/9114), thanks @bcardiff) - -## Tools - -### Formatter - -- Fix formatting of regex after some comments. ([#9109](https://github.com/crystal-lang/crystal/pull/9109), thanks @MakeNowJust) -- Fix formatting of `&.!`. ([#9391](https://github.com/crystal-lang/crystal/pull/9391), thanks @MakeNowJust) -- Avoid crash on heredoc with interpolations. ([#9382](https://github.com/crystal-lang/crystal/pull/9382), thanks @MakeNowJust) -- Refactor: code clean-up. ([#9231](https://github.com/crystal-lang/crystal/pull/9231), thanks @MakeNowJust) - -### Doc generator - -- Fix links to methods with `String` default values. ([#9200](https://github.com/crystal-lang/crystal/pull/9200), thanks @bcardiff) -- Fix syntax highlighting of heredoc. ([#9396](https://github.com/crystal-lang/crystal/pull/9396), thanks @MakeNowJust) -- Correctly attach docs before annotations to following types. ([#9332](https://github.com/crystal-lang/crystal/pull/9332), thanks @asterite) -- Allow annotations and `:ditto:` in macro. ([#9341](https://github.com/crystal-lang/crystal/pull/9341), thanks @bcardiff) -- Add project name and version to API docs. ([#8792](https://github.com/crystal-lang/crystal/pull/8792), thanks @straight-shoota) -- Add version selector to API docs. ([#9074](https://github.com/crystal-lang/crystal/pull/9074), [#9187](https://github.com/crystal-lang/crystal/pull/9187), [#9250](https://github.com/crystal-lang/crystal/pull/9250), [#9252](https://github.com/crystal-lang/crystal/pull/9252), [#9254](https://github.com/crystal-lang/crystal/pull/9254), thanks @straight-shoota, @bcardiff) -- Show input type path instead of full qualified path on generic. ([#9302](https://github.com/crystal-lang/crystal/pull/9302), thanks @MakeNowJust) -- Remove README link in API docs. ([#9082](https://github.com/crystal-lang/crystal/pull/9082), thanks @straight-shoota) -- Remove special handling for version tags in docs generator. ([#9083](https://github.com/crystal-lang/crystal/pull/9083), thanks @straight-shoota) -- Refactor `is_crystal_repo` based on project name. ([#9070](https://github.com/crystal-lang/crystal/pull/9070), thanks @straight-shoota) -- Refactor `Docs::Generator` source link generation. ([#9119](https://github.com/crystal-lang/crystal/pull/9119), [#9305](https://github.com/crystal-lang/crystal/pull/9305), thanks @straight-shoota) - -### Playground - -- Allow building compiler without 'playground', to avoid dependency on sockets. ([#9031](https://github.com/crystal-lang/crystal/pull/9031), thanks @oprypin) -- Add support to jquery version 3. ([#9028](https://github.com/crystal-lang/crystal/pull/9028), thanks @deiv) - -## Others - -- CI improvements and housekeeping. ([#9012](https://github.com/crystal-lang/crystal/pull/9012), [#9129](https://github.com/crystal-lang/crystal/pull/9129), [#9242](https://github.com/crystal-lang/crystal/pull/9242), [#9370](https://github.com/crystal-lang/crystal/pull/9370), thanks @bcardiff) -- Update to Shards 0.11.1. ([#9446](https://github.com/crystal-lang/crystal/pull/9446), thanks @bcardiff) -- Tidy up Makefile and crystal env output. ([#9423](https://github.com/crystal-lang/crystal/pull/9423), thanks @bcardiff) -- Always include `lib` directory in the `CRYSTAL_PATH`. ([#9315](https://github.com/crystal-lang/crystal/pull/9315), thanks @waj) -- Use `SOURCE_DATE_EPOCH` only to determine compiler date. ([#9088](https://github.com/crystal-lang/crystal/pull/9088), thanks @straight-shoota) -- Win CI: Bootstrap Crystal, build things on Windows, publish the binary. ([#9123](https://github.com/crystal-lang/crystal/pull/9123), [#9155](https://github.com/crystal-lang/crystal/pull/9155), [#9144](https://github.com/crystal-lang/crystal/pull/9144), [#9346](https://github.com/crystal-lang/crystal/pull/9346), thanks @oprypin) -- Regenerate implementation tool sample. ([#9003](https://github.com/crystal-lang/crystal/pull/9003), thanks @nulty) -- Avoid requiring non std-lib spec spec_helper. ([#9294](https://github.com/crystal-lang/crystal/pull/9294), thanks @bcardiff) -- Improve grammar and fix typos. ([#9087](https://github.com/crystal-lang/crystal/pull/9087), [#9212](https://github.com/crystal-lang/crystal/pull/9212), [#9368](https://github.com/crystal-lang/crystal/pull/9368), thanks @MakeNowJust, @j8r) -- Hide internal functions in docs. ([#9410](https://github.com/crystal-lang/crystal/pull/9410), thanks @bcardiff) -- Advertise full `crystal spec` command for running a particular spec. ([#9103](https://github.com/crystal-lang/crystal/pull/9103), thanks @paulcsmith) -- Update README. ([#9225](https://github.com/crystal-lang/crystal/pull/9225), [#9163](https://github.com/crystal-lang/crystal/pull/9163), thanks @danimiba, @straight-shoota) -- Fix LICENSE and add NOTICE file. ([#3903](https://github.com/crystal-lang/crystal/pull/3903), thanks @MakeNowJust) - -# 0.34.0 (2020-04-06) - -## Language changes - -- **(breaking-change)** Exhaustive `case` expression check added, for now it produces warnings. ([#8424](https://github.com/crystal-lang/crystal/pull/8424), [#8962](https://github.com/crystal-lang/crystal/pull/8962), thanks @asterite, @Sija) -- Let `Proc(T)` be used as a `Proc(Nil)`. ([#8969](https://github.com/crystal-lang/crystal/pull/8969), [#8970](https://github.com/crystal-lang/crystal/pull/8970), thanks @asterite) - -## Standard library - -- **(breaking-change)** Replace `Errno`, `WinError`, `IO::Timeout` with `RuntimeError`, `IO::TimeoutError`, `IO::Error`, `File::Error`, `Socket::Error`, and subclasses. ([#8885](https://github.com/crystal-lang/crystal/pull/8885), thanks @waj) -- **(breaking-change)** Replace `Logger` module in favor of `Log` module. ([#8847](https://github.com/crystal-lang/crystal/pull/8847), [#8976](https://github.com/crystal-lang/crystal/pull/8976), thanks @bcardiff) -- **(breaking-change)** Move `Adler32` and `CRC32` to `Digest`. ([#8881](https://github.com/crystal-lang/crystal/pull/8881), thanks @bcardiff) -- **(breaking-change)** Remove `DL` module. ([#8882](https://github.com/crystal-lang/crystal/pull/8882), thanks @bcardiff) -- Enable more win32 specs. ([#8683](https://github.com/crystal-lang/crystal/pull/8683), [#8822](https://github.com/crystal-lang/crystal/pull/8822), thanks @straight-shoota) -- Make `SemanticVersion::Prerelease` comparable. ([#8991](https://github.com/crystal-lang/crystal/pull/8991), thanks @MakeNowJust) -- Use `flag?(:i386)` instead of obsolete `flag?(:i686)`. ([#8863](https://github.com/crystal-lang/crystal/pull/8863), thanks @bcardiff) -- Remove Windows workaround using `vsnprintf`. ([#8942](https://github.com/crystal-lang/crystal/pull/8942), thanks @oprypin) -- Fixed docs broken link to ruby's prettyprint source. ([#8915](https://github.com/crystal-lang/crystal/pull/8915), thanks @matthin) -- Update `OptionParser` example to exit on `--help`. ([#8927](https://github.com/crystal-lang/crystal/pull/8927), thanks @vlazar) - -### Numeric - -- Fixed `Float32#to_s` corner-case. ([#8838](https://github.com/crystal-lang/crystal/pull/8838), thanks @toddsundsted) -- Fixed make `Big*#to_u` raise on negative values. ([#8826](https://github.com/crystal-lang/crystal/pull/8826), thanks @Sija) -- Fixed `BigDecimal#to_big_i` regression. ([#8790](https://github.com/crystal-lang/crystal/pull/8790), thanks @Sija) -- Add `Complex#round`. ([#8819](https://github.com/crystal-lang/crystal/pull/8819), thanks @miketheman) -- Add `Int#bit_length` and `BigInt#bit_length`. ([#8924](https://github.com/crystal-lang/crystal/pull/8924), [#8931](https://github.com/crystal-lang/crystal/pull/,8931), thanks @asterite) -- Fixed docs of `Int#gcd`. ([#8894](https://github.com/crystal-lang/crystal/pull/8894), thanks @nard-tech) - -### Text - -- **(breaking-change)** Deprecate top-level `with_color` in favor of `Colorize.with`. ([#8892](https://github.com/crystal-lang/crystal/pull/8892), [#8958](https://github.com/crystal-lang/crystal/pull/8958), thanks @bcardiff, @oprypin) -- Add overloads for `String#ljust`, `String#rjust` and `String#center` that take an `IO`. ([#8923](https://github.com/crystal-lang/crystal/pull/8923), thanks @asterite) -- Add missing `Regex#hash` and `Regex::MatchData#hash`. ([#8986](https://github.com/crystal-lang/crystal/pull/8986), thanks @MakeNowJust) -- Upgrade Unicode to 13.0.0. ([#8906](https://github.com/crystal-lang/crystal/pull/8906), thanks @Blacksmoke16) -- Revert deprecation of `String#codepoint_at`. ([#8902](https://github.com/crystal-lang/crystal/pull/8902), thanks @vlazar) -- Move `Iconv` to `Crystal` namespace. ([#8890](https://github.com/crystal-lang/crystal/pull/8890), thanks @bcardiff) - -### Collections - -- Fixed make `Range#size` raise on an open range. ([#8829](https://github.com/crystal-lang/crystal/pull/8829), thanks @Sija) -- Add `Enumerable#empty?`. ([#8960](https://github.com/crystal-lang/crystal/pull/8960), thanks @Sija) -- **(performance)** Optimized Implementation of `Array#fill` for zero Values. ([#8903](https://github.com/crystal-lang/crystal/pull/8903), thanks @toddsundsted) -- Refactor `Reflect` to an `Enumerable` private definition. ([#8884](https://github.com/crystal-lang/crystal/pull/8884), thanks @bcardiff) - -### Serialization - -- **(breaking-change)** Rename `YAML::Builder.new` with block to `YAML::Builder.build`. ([#8896](https://github.com/crystal-lang/crystal/pull/8896), thanks @straight-shoota) -- Add `XML.build_fragment`. ([#8813](https://github.com/crystal-lang/crystal/pull/8813), thanks @straight-shoota) -- Add `CSV#rewind`. ([#8912](https://github.com/crystal-lang/crystal/pull/8912), thanks @asterite) -- Add `Deque#from_json` and `Deque#to_json`. ([#8850](https://github.com/crystal-lang/crystal/pull/8850), thanks @carlhoerberg) -- Call to `IO#flush` on `CSV`, `INI`, `JSON`, `XML`, and `YAML` builders. ([#8876](https://github.com/crystal-lang/crystal/pull/8876), thanks @asterite) -- Add docs to `Object.from_yaml`. ([#8800](https://github.com/crystal-lang/crystal/pull/8800), thanks @wowinter13) - -### Time - -- **(breaking-change)** Improve `Time::Span` initialization API with mandatory named arguments. ([#8257](https://github.com/crystal-lang/crystal/pull/8257), [#8857](https://github.com/crystal-lang/crystal/pull/8857), thanks @dnamsons, @bcardiff) -- Add `Time::Span#total_microseconds`. ([#8966](https://github.com/crystal-lang/crystal/pull/8966), thanks @vlazar) - -### Files - -- Fixed multi-thread race condition by setting `fd` to `-1` on closed `File`/`Socket`. ([#8873](https://github.com/crystal-lang/crystal/pull/8873), thanks @bcardiff) -- Fixed `File.dirname` with unicode chars. ([#8911](https://github.com/crystal-lang/crystal/pull/8911), thanks @asterite) -- Add `IO::Buffered#flush_on_newline` back and set it to true for non-tty. ([#8935](https://github.com/crystal-lang/crystal/pull/8935), thanks @asterite) -- Forward missing methods of `IO::Hexdump` to underlying `IO`. ([#8908](https://github.com/crystal-lang/crystal/pull/8908), thanks @carlhoerberg) - -### Networking - -- **(breaking-change)** Correctly support WebSocket close codes. ([#8975](https://github.com/crystal-lang/crystal/pull/8975), [#8981](https://github.com/crystal-lang/crystal/pull/8981), thanks @RX14, @Sija) -- Make `HTTP::Client` return empty `body_io` if content-length is zero. ([#8503](https://github.com/crystal-lang/crystal/pull/8503), thanks @asterite) -- Fixed `UDP` specs in the case of a local firewall. ([#8817](https://github.com/crystal-lang/crystal/pull/8817), thanks @RX14) -- Fixed `MIME` spec examples to not collide with actual registry. ([#8795](https://github.com/crystal-lang/crystal/pull/8795), thanks @straight-shoota) -- Fixed `UNIXServer`, and `HTTP::WebSocket` specs to ensure server is accepting before closing. ([#8755](https://github.com/crystal-lang/crystal/pull/8755), [#8879](https://github.com/crystal-lang/crystal/pull/8879), thanks @bcardiff) -- Add type annotation to `tls` argument in `HTTP`. ([#8678](https://github.com/crystal-lang/crystal/pull/8678), thanks @j8r) -- Add `Location` to `HTTP::Request` common header names. ([#8992](https://github.com/crystal-lang/crystal/pull/8992), thanks @mamantoha) - -### Concurrency - -- Add docs on `Future` regarding exceptions. ([#8860](https://github.com/crystal-lang/crystal/pull/8860), thanks @rdp) -- Disable occasionally failing `Thread` specs on musl. ([#8801](https://github.com/crystal-lang/crystal/pull/8801), thanks @straight-shoota) - -### System - -- Fixed typo on `src/signal.cr`. ([#8805](https://github.com/crystal-lang/crystal/pull/8805), thanks @lbguilherme) - -### Runtime - -- Fixed exceptions not being inspectable when running binary from PATH. ([#8807](https://github.com/crystal-lang/crystal/pull/8807), thanks @willhbr) -- Move `AtExitHandlers` to `Crystal` namespace. ([#8883](https://github.com/crystal-lang/crystal/pull/8883), thanks @bcardiff) - -## Compiler - -- **(breaking-change)** Drop `disable_overflow` compiler flag. ([#8772](https://github.com/crystal-lang/crystal/pull/8772), thanks @Sija) -- Fixed url in "can't infer block return type" error message. ([#8869](https://github.com/crystal-lang/crystal/pull/8869), thanks @nilium) -- Fixed typo in math interpreter error message. ([#8941](https://github.com/crystal-lang/crystal/pull/8941), thanks @j8r) -- Use `CRYSTAL_OPTS` environment variable as default compiler options. ([#8900](https://github.com/crystal-lang/crystal/pull/8900), thanks @bcardiff) -- Avoid using the default `--exclude-warnings` value if some is specified. ([#8899](https://github.com/crystal-lang/crystal/pull/8899), thanks @bcardiff) -- Honor `LIBRARY_PATH` as default library path, and allow linking with no explicit `/usr/lib:/usr/local/lib` paths. ([#8948](https://github.com/crystal-lang/crystal/pull/8948), thanks @bcardiff) -- Fix Windows LLVM globals codegen in non-single-module mode. ([#8978](https://github.com/crystal-lang/crystal/pull/8978), thanks @oprypin) -- Add support for LLVM 10. ([#8940](https://github.com/crystal-lang/crystal/pull/8940), thanks @RX14) -- Remove redundant calls to `Object.to_s` in interpolation in compiler's code. ([#8947](https://github.com/crystal-lang/crystal/pull/8947), thanks @veelenga) - -### Language semantics - -- Type as `NoReturn` if calling method on abstract class with no concrete subclasses. ([#8870](https://github.com/crystal-lang/crystal/pull/8870), thanks @asterite) - -## Tools - -- Add `crystal init` name validation. ([#8737](https://github.com/crystal-lang/crystal/pull/8737), thanks @straight-shoota) - -### Doc generator - -- Show warnings on docs command. ([#8880](https://github.com/crystal-lang/crystal/pull/8880), thanks @bcardiff) - -## Others - -- CI improvements and housekeeping. ([#8804](https://github.com/crystal-lang/crystal/pull/8804), [#8811](https://github.com/crystal-lang/crystal/pull/8811), [#8982](https://github.com/crystal-lang/crystal/pull/8982), thanks @bcardiff, @oprypin) -- Update to Shards 0.10.0. ([#8988](https://github.com/crystal-lang/crystal/pull/8988), thanks @bcardiff) -- Fix `pretty_json` sample. ([#8816](https://github.com/crystal-lang/crystal/pull/8816), thanks @asterite) -- Fix typos throughout the codebase. ([#8971](https://github.com/crystal-lang/crystal/pull/8971), thanks @Sija) - -# 0.33.0 (2020-02-14) - -## Language changes - -- Allow `timeout` in select statements. ([#8506](https://github.com/crystal-lang/crystal/pull/8506), [#8705](https://github.com/crystal-lang/crystal/pull/8705), thanks @bcardiff, @firejox, @vlazar) - -### Macros - -- Add `TypeNode#name(generic_args : BoolLiteral)` to return `TypeNode`'s name with or without type vars. ([#8483](https://github.com/crystal-lang/crystal/pull/8483), thanks @Blacksmoke16) - -## Standard library - -- **(breaking-change)** Remove several previously deprecated methods and modules: `PartialComparable`, `Crypto::Bcrypt::Password#==`, `HTTP::Server::Response#respond_with_error`, `JSON::PullParser::Kind#==`, `Symbol#==(JSON::PullParser::Kind)`, `JSON::Token#type`, `String#at`, `Time.new`, `Time.now`, `Time.utc_now`, `URI.escape`, `URI.unescape`. ([#8646](https://github.com/crystal-lang/crystal/pull/8646), [#8596](https://github.com/crystal-lang/crystal/pull/8596), thanks @bcardiff, @Blacksmoke16) -- Fixed docs wording. ([#8606](https://github.com/crystal-lang/crystal/pull/8606), [#8784](https://github.com/crystal-lang/crystal/pull/8784), thanks @fxn) -- Add `Object#in?`. ([#8720](https://github.com/crystal-lang/crystal/pull/8720), [#8723](https://github.com/crystal-lang/crystal/pull/8723), thanks @Sija) -- Allow to create an enum from a symbol. ([#8634](https://github.com/crystal-lang/crystal/pull/8634), thanks @bew) -- Add `VaList#next` for getting the next element in a variadic argument list. ([#8535](https://github.com/crystal-lang/crystal/pull/8535), [#8688](https://github.com/crystal-lang/crystal/pull/8688), thanks @ffwff, @RX14) -- Refactor `ARGF` implementation. ([#8593](https://github.com/crystal-lang/crystal/pull/8593), thanks @arcage) -- Fixed specs of `Colorize` on dumb terminal. ([#8673](https://github.com/crystal-lang/crystal/pull/8673), thanks @oprypin) -- Fixed some specs on Win32. ([#8670](https://github.com/crystal-lang/crystal/pull/8670), thanks @straight-shoota) - -### Numeric - -- Add `BigInt#unsafe_shr`. ([#8763](https://github.com/crystal-lang/crystal/pull/8763), thanks @asterite) -- Refactor `Float#fdiv` to use binary primitive. ([#8662](https://github.com/crystal-lang/crystal/pull/8662), thanks @bcardiff) - -### Text - -- Fixed `\u0000` wrongly added on `String#sub(Hash)` replaces last char. ([#8644](https://github.com/crystal-lang/crystal/pull/8644), thanks @mimame) - -### Collections - -- Fixed `Enumerable#zip` to work with union types. ([#8621](https://github.com/crystal-lang/crystal/pull/8621), thanks @asterite) -- Fixed docs regarding `Hash`'s `initial_capacity`. ([#8569](https://github.com/crystal-lang/crystal/pull/8569), thanks @r00ster91) - -### Serialization - -- Improved JSON deserialization into union types. ([#8689](https://github.com/crystal-lang/crystal/pull/8689), thanks @KimBurgess) -- Fixed expected error message in libxml2 error spec. ([#8699](https://github.com/crystal-lang/crystal/pull/8699), thanks @straight-shoota) -- Fixed `JSON::PullParser` overflow handling. ([#8698](https://github.com/crystal-lang/crystal/pull/8698), thanks @KimBurgess) -- Fixed `JSON::Any#dig?`/`YAML::Any#dig?` on non-structure values. ([#8745](https://github.com/crystal-lang/crystal/pull/8745), thanks @Sija) - -### Time - -- Fixed `Time#shift` over date boundaries with zone offset. ([#8742](https://github.com/crystal-lang/crystal/pull/8742), thanks @straight-shoota) - -### Files - -- **(breaking-change)** Deprecate `File::Info#owner`, and `File::Info#group`; use `owner_id`, and `group_id`. ([#8007](https://github.com/crystal-lang/crystal/pull/8007), thanks @j8r) -- Fixed `Path.new` receiving `Path` as first argument. ([#8753](https://github.com/crystal-lang/crystal/pull/8753), thanks @straight-shoota) -- Fixed `File.size` and `File.info` to work with `Path` parameters. ([#8625](https://github.com/crystal-lang/crystal/pull/8625), thanks @snluu) -- Fixed `Path` specs when `ENV["HOME"]` is unset. ([#8667](https://github.com/crystal-lang/crystal/pull/8667), thanks @straight-shoota) -- Refactor `Dir.mkdir_p` to use `Path#each_parent` and make it work on Win32. ([#8668](https://github.com/crystal-lang/crystal/pull/8668), thanks @straight-shoota) -- Fixed `IO::MultiWriter` specs to close file before reading/deleting it. ([#8674](https://github.com/crystal-lang/crystal/pull/8674), thanks @oprypin) - -### Networking - -- Fixed invalid call to libevent and race conditions on closed `IO` when resuming readable/writable event. ([#8707](https://github.com/crystal-lang/crystal/pull/8707), [#8733](https://github.com/crystal-lang/crystal/pull/8733), thanks @bcardiff) -- Fixed unexpected EOF in terminated SSL connection. ([#8540](https://github.com/crystal-lang/crystal/pull/8540), thanks @rdp) -- Fixed `HTTP::Cookie` to support `Int64` max-age values. ([#8759](https://github.com/crystal-lang/crystal/pull/8759), thanks @asterite) -- Improve error message for `getaddrinfo` failure. ([#8498](https://github.com/crystal-lang/crystal/pull/8498), thanks @rdp) -- Make `IO::SysCall#wait_readable` and `IO::SysCall#wait_writable` public, yet `:nodoc:`. ([#7366](https://github.com/crystal-lang/crystal/pull/7366), thanks @stakach) -- Refactor `StaticFileHandler` to use `Path`. ([#8672](https://github.com/crystal-lang/crystal/pull/8672), thanks @straight-shoota) -- Remove fixed date in spec. ([#8640](https://github.com/crystal-lang/crystal/pull/8640), thanks @bcardiff) -- Remove non-portable error message in `TCPServer` spec. ([#8702](https://github.com/crystal-lang/crystal/pull/8702), thanks @straight-shoota) - -### Crypto - -- Add `Crypto::Bcrypt::Password` check for invalid hash value. ([#6467](https://github.com/crystal-lang/crystal/pull/6467), thanks @miketheman) -- Improve documentation for `Random::Secure`. ([#8484](https://github.com/crystal-lang/crystal/pull/8484), thanks @straight-shoota) - -### Concurrency - -- Fixed `Future(Nil)` when the block raises. ([#8650](https://github.com/crystal-lang/crystal/pull/8650), thanks @lbguilherme) -- Fixed `IO` closing in multi-thread mode. ([#8733](https://github.com/crystal-lang/crystal/pull/8733), thanks @bcardiff) -- Fixed some regular failing specs in multi-thread mode. ([#8592](https://github.com/crystal-lang/crystal/pull/8592), [#8643](https://github.com/crystal-lang/crystal/pull/8643), [#8724](https://github.com/crystal-lang/crystal/pull/8724), [#8761](https://github.com/crystal-lang/crystal/pull/8761), thanks @bcardiff) -- Add docs to `Fiber`. ([#8739](https://github.com/crystal-lang/crystal/pull/8739), thanks @straight-shoota) - -### System - -- Enable `system` module for Win32 in prelude. ([#8661](https://github.com/crystal-lang/crystal/pull/8661), thanks @straight-shoota) -- Handle exceptions raised at `__crystal_sigfault_handler`. ([#8743](https://github.com/crystal-lang/crystal/pull/8743), thanks @waj) - -### Runtime - -- Fixed wrongly collected exception object by the GC. Ensure `LibUnwind::Exception` struct is not atomic. ([#8728](https://github.com/crystal-lang/crystal/pull/8728), thanks @waj) -- Fixed reporting of non-statement rows in DWARF backtrace. ([#8499](https://github.com/crystal-lang/crystal/pull/8499), thanks @rdp) -- Add top level exception handler. ([#8735](https://github.com/crystal-lang/crystal/pull/8735), [#8791](https://github.com/crystal-lang/crystal/pull/8791), thanks @waj) -- Try to open stdio in non-blocking mode. ([#8787](https://github.com/crystal-lang/crystal/pull/8787), thanks @waj) -- Allow `Crystal::System.print_error` to use `printf` like format. ([#8786](https://github.com/crystal-lang/crystal/pull/8786), thanks @bcardiff) - -### Spec - -- **(breaking-change)** Remove previously deprecated spec method `assert`. ([#8767](https://github.com/crystal-lang/crystal/pull/8767), thanks @Blacksmoke16) -- `Spec::JUnitFormatter` output and options enhancements. ([#8599](https://github.com/crystal-lang/crystal/pull/8599), [#8692](https://github.com/crystal-lang/crystal/pull/8692), thanks @Sija, @bcardiff) - -## Compiler - -- **(breaking-change)** Drop support for previously deprecated comma separators in enums and other cleanups. ([#8657](https://github.com/crystal-lang/crystal/pull/8657), thanks @bcardiff) -- **(breaking-change)** Drop uppercase F32 and F64 float number suffixes. ([#8782](https://github.com/crystal-lang/crystal/pull/8782), thanks @rhysd) -- Fixed memory corruption issues by using LLVM's `memset` and `memcpy` that matches target machine. ([#8746](https://github.com/crystal-lang/crystal/pull/8746), thanks @bcardiff) -- Fixed ICE when trying to add type inside annotation. ([#8628](https://github.com/crystal-lang/crystal/pull/8628), thanks @asterite) -- Fixed ICE on `typeof` in an unused block. ([#8695](https://github.com/crystal-lang/crystal/pull/8695), thanks @asterite) -- Fixed ICE in case of wrong target triple. ([#8710](https://github.com/crystal-lang/crystal/pull/8710), thanks @Sija) -- Fixed ICE when raising a macro exception with empty message. ([#8654](https://github.com/crystal-lang/crystal/pull/8654), thanks @jan-zajic) -- Fixed parser bug macro with "eenum" in it. ([#8760](https://github.com/crystal-lang/crystal/pull/8760), thanks @asterite) -- Change `CRYSTAL_PATH` to allow shards to override std-lib. ([#8752](https://github.com/crystal-lang/crystal/pull/8752), thanks @bcardiff) - -### Language semantics - -- Fixed missing virtualization of `Proc` pointer. ([#8757](https://github.com/crystal-lang/crystal/pull/8757), thanks @asterite) -- Fixed type of vars after `begin`/`rescue` if all `rescue` are unreachable. ([#8758](https://github.com/crystal-lang/crystal/pull/8758), thanks @asterite) -- Fixed visibility propagation to macro expansions in all cases. ([#8762](https://github.com/crystal-lang/crystal/pull/8762), [#8796](https://github.com/crystal-lang/crystal/pull/8796), thanks @asterite) - -## Tools - -- Update `crystal init` to handle `.`. ([#8681](https://github.com/crystal-lang/crystal/pull/8681), thanks @jethrodaniel) - -### Formatter - -- Fixed indent after comment inside indexer. ([#8627](https://github.com/crystal-lang/crystal/pull/8627), thanks @asterite) -- Fixed indent of comments at the end of a proc literal. ([#8778](https://github.com/crystal-lang/crystal/pull/8778), thanks @asterite) -- Fixed crash when formatting comment after macro. ([#8697](https://github.com/crystal-lang/crystal/pull/8697), thanks @asterite) -- Fixed crash when formatting `exp.!`. ([#8768](https://github.com/crystal-lang/crystal/pull/8768), thanks @asterite) -- Removes unnecessary escape sequences. ([#8619](https://github.com/crystal-lang/crystal/pull/8619), thanks @RX14) - -### Doc generator - -- **(breaking-change)** Deprecate `ditto` and `nodoc` in favor of `:ditto:` and `:nodoc:`. ([#6362](https://github.com/crystal-lang/crystal/pull/6362), thanks @j8r) -- Skip creation of `docs/` dir when not needed. ([#8718](https://github.com/crystal-lang/crystal/pull/8718), thanks @Sija) - -## Others - -- CI improvements and housekeeping. ([#8580](https://github.com/crystal-lang/crystal/pull/8580), [#8597](https://github.com/crystal-lang/crystal/pull/8597), [#8679](https://github.com/crystal-lang/crystal/pull/8679), [#8779](https://github.com/crystal-lang/crystal/pull/8779), thanks @bcardiff, @j8r) -- Add Windows CI using GitHub Actions. ([#8676](https://github.com/crystal-lang/crystal/pull/8676), thanks @oprypin) -- Add Alpine CI using CircleCI. ([#7420](https://github.com/crystal-lang/crystal/pull/7420), thanks @straight-shoota) -- Build Alpine Docker images. ([#8708](https://github.com/crystal-lang/crystal/pull/8708), thanks @straight-shoota) -- Allow `Makefile` to use `lld` if present (Linux only). ([#8641](https://github.com/crystal-lang/crystal/pull/8641), thanks @bcardiff) -- Simplify script to determine installed LLVM version. ([#8605](https://github.com/crystal-lang/crystal/pull/8605), thanks @j8r) -- Add CircleCI test summaries. ([#8617](https://github.com/crystal-lang/crystal/pull/8617), thanks @Sija) -- Add helper scripts to identify working std-lib specs on Win32. ([#8664](https://github.com/crystal-lang/crystal/pull/8664), thanks @straight-shoota) - -# 0.32.1 (2019-12-18) - -## Standard library - -### Collections - -- Fixed docs of `Enumerable#each_cons_pair` and `Iterator#cons_pair`. ([#8585](https://github.com/crystal-lang/crystal/pull/8585), thanks @arcage) - -### Networking - -- Fixed `HTTP::WebSocket`'s `on_close` callback is called for all errors. ([#8552](https://github.com/crystal-lang/crystal/pull/8552), thanks @stakach) -- Fixed sporadic failure in specs with OpenSSL 1.1+. ([#8582](https://github.com/crystal-lang/crystal/pull/8582), thanks @rdp) - -## Compiler - -### Language semantics - -- Combine contiguous string literals before string interpolation. ([#8581](https://github.com/crystal-lang/crystal/pull/8581), thanks @asterite) - -# 0.32.0 (2019-12-11) - -## Language changes - -- Allow boolean negation to be written also as a regular method call `expr.!`. ([#8445](https://github.com/crystal-lang/crystal/pull/8445), thanks @jan-zajic) - -### Macros - -- Add `TypeNode#class_vars` to list class variables of a type in a macro. ([#8405](https://github.com/crystal-lang/crystal/pull/8405), thanks @jan-zajic) -- Add `TypeNode#includers` to get an array of types a module is directly included in. ([#8133](https://github.com/crystal-lang/crystal/pull/8133), thanks @Blacksmoke16) -- Add `ArrayLiteral#map_with_index` and `TupleLiteral#map_with_index`. ([#8049](https://github.com/crystal-lang/crystal/pull/8049), thanks @Blacksmoke16) -- Add docs for `ArrayLiteral#reduce`. ([#8379](https://github.com/crystal-lang/crystal/pull/8379), thanks @jan-zajic) -- Add `lower:` named argument to `StringLiteral#camelcase`. ([#8429](https://github.com/crystal-lang/crystal/pull/8429), thanks @Blacksmoke16) - -## Standard library - -- **(breaking-change)** Remove `Readline` from std-lib. It's now available as a shard at [crystal-lang/crystal-readline](https://www.github.com/crystal-lang/crystal-readline) ([#8364](https://github.com/crystal-lang/crystal/pull/8364), thanks @ftarulla) -- Move `Number#clamp` to `Comparable#clamp`. ([#8522](https://github.com/crystal-lang/crystal/pull/8522), thanks @wontruefree) -- Allow `abort` without arguments. ([#8214](https://github.com/crystal-lang/crystal/pull/8214), thanks @dbackeus) -- Improve error message for not-nil assertion in getters. ([#8200](https://github.com/crystal-lang/crystal/pull/8200), [#8296](https://github.com/crystal-lang/crystal/pull/8296), thanks @icy-arctic-fox) -- Add `Enum.valid?`. ([#5716](https://github.com/crystal-lang/crystal/pull/5716), thanks @MakeNowJust) -- Disable colored output if `TERM=dumb`. ([#8271](https://github.com/crystal-lang/crystal/pull/8271), thanks @ilanpillemer) -- Documentation improvements. ([#7656](https://github.com/crystal-lang/crystal/pull/7656), [#8337](https://github.com/crystal-lang/crystal/pull/8337), [#8446](https://github.com/crystal-lang/crystal/pull/8446), thanks @r00ster91, @vlazar, @cserb) -- Add docs for pseudo methods. ([#8327](https://github.com/crystal-lang/crystal/pull/8327), [#8491](https://github.com/crystal-lang/crystal/pull/8491), thanks @straight-shoota) -- Code cleanups. ([#8270](https://github.com/crystal-lang/crystal/pull/8270), [#8368](https://github.com/crystal-lang/crystal/pull/8368), [#8404](https://github.com/crystal-lang/crystal/pull/8404), thanks @asterite, @vlazar, @arcage) - -### Numeric - -- Fixed `%` and `Int#remainder` edge case of min int value against `-1`. ([#8321](https://github.com/crystal-lang/crystal/pull/8321), thanks @asterite) -- Fixed `Int#gcd` types edge case and improve performance. ([#7996](https://github.com/crystal-lang/crystal/pull/7996), [#8419](https://github.com/crystal-lang/crystal/pull/8419), thanks @yxhuvud, @j8r) -- Add `Int#bits` for accessing bit ranges. ([#8165](https://github.com/crystal-lang/crystal/pull/8165), thanks @stakach) -- Allow `Number#round` with `UInt` argument. ([#8361](https://github.com/crystal-lang/crystal/pull/8361), thanks @igor-alexandrov) - -### Text - -- **(breaking-change)** Implement string interpolation as a call to `String.interpolation`. ([#8400](https://github.com/crystal-lang/crystal/pull/8400), thanks @asterite) -- **(breaking-change)** Deprecate `String#codepoint_at`, use `char_at(index).ord`. ([#8475](https://github.com/crystal-lang/crystal/pull/8475), thanks @vlazar) -- Fixed encoding specs for musl iconv. ([#8525](https://github.com/crystal-lang/crystal/pull/8525), thanks @straight-shoota) -- Add `String#presence`. ([#8345](https://github.com/crystal-lang/crystal/pull/8345), [#8508](https://github.com/crystal-lang/crystal/pull/8508), thanks @igor-alexandrov, @Sija) -- Add `String#center`. ([#8557](https://github.com/crystal-lang/crystal/pull/8557), thanks @hutou) -- **(performance)** Refactor `String#to_utf16` optimizing for ascii-only. ([#8526](https://github.com/crystal-lang/crystal/pull/8526), thanks @straight-shoota) -- Add docs in `Levenshtein` module. ([#8386](https://github.com/crystal-lang/crystal/pull/8386), thanks @katafrakt) -- Add docs to `Regex::Options`. ([#8448](https://github.com/crystal-lang/crystal/pull/8448), thanks @jan-zajic) - -### Collections - -- **(breaking-change)** Deprecate `Enumerable#grep`, use `Enumerable#select`. ([#8452](https://github.com/crystal-lang/crystal/pull/8452), thanks @j8r) -- Fixed `Enumerable#minmax`, `#min`, `#max` for partially comparable values. ([#8490](https://github.com/crystal-lang/crystal/pull/8490), thanks @TedTran2019) -- Fixed `Hash#rehash`. ([#8450](https://github.com/crystal-lang/crystal/pull/8450), thanks @asterite) -- Fixed `Array` range assignment index out of bounds. ([#8347](https://github.com/crystal-lang/crystal/pull/8347), thanks @asterite) -- Fixed endless ranged support for `String#[]?` and `Array#[]?`. ([#8567](https://github.com/crystal-lang/crystal/pull/8567), thanks @KarthikMAM) -- Add `Hash#compare_by_identity` and `Set#compare_by_identity`. ([#8451](https://github.com/crystal-lang/crystal/pull/8451), thanks @asterite) -- Add `Enumerable#each_cons_pair` and `Iterator#cons_pair` yielding a tuple. ([#8332](https://github.com/crystal-lang/crystal/pull/8332), thanks @straight-shoota) -- Add `offset` argument to all `map_with_index` methods. ([#8264](https://github.com/crystal-lang/crystal/pull/8264), thanks @asterite) -- **(performance)** Optimized version of `Tuple#to_a`. ([#8265](https://github.com/crystal-lang/crystal/pull/8265), thanks @asterite) -- Add docs to `Hash.merge!(other : Hash, &)`. ([#8380](https://github.com/crystal-lang/crystal/pull/8380), thanks @jan-zajic) -- Add docs to `Hash.select`. ([#8391](https://github.com/crystal-lang/crystal/pull/8391), thanks @jan-zajic) -- Add docs and specs to `Enumerable.reduce`. ([#8378](https://github.com/crystal-lang/crystal/pull/8378), thanks @jan-zajic) - -### Serialization - -- **(breaking-change)** Make `XML::Reader#expand` raise, introduce `XML::Reader#expand?` for former behavior. ([#8186](https://github.com/crystal-lang/crystal/pull/8186), thanks @Blacksmoke16) -- Allow `JSON.mapping` & `YAML.mapping` converter attribute to be applied to `Array` and `Hash`. ([#8156](https://github.com/crystal-lang/crystal/pull/8156), thanks @rodrigopinto) -- Add `use_json_discriminator` and `use_yaml_discriminator` to choose type based on property value. ([#8406](https://github.com/crystal-lang/crystal/pull/8406), thanks @asterite) -- Remove return type `self` restriction from `Object.from_json` and `Object.from_yaml`. ([#8489](https://github.com/crystal-lang/crystal/pull/8489), thanks @straight-shoota) - -### Files - -- **(breaking-change)** Remove expand home (`~`) by default in `File.expand_path` and `Path#expand`, now opt-in argument. ([#7903](https://github.com/crystal-lang/crystal/pull/7903), thanks @didactic-drunk) -- Fixed bugs in `Path` regarding `#dirname`, `#each_part`, `#each_parent`. ([#8415](https://github.com/crystal-lang/crystal/pull/8415), thanks @jan-zajic) -- Fixed `GZip::Reader` and `GZip::Writer` to handle large data sizes. ([#8421](https://github.com/crystal-lang/crystal/pull/8421), thanks @straight-shoota) -- Fixed `File::Info#same_file?` by providing access to 64 bit inode numbers. ([#8355](https://github.com/crystal-lang/crystal/pull/8355), thanks @didactic-drunk) - -### Networking - -- Fixed `HTTP::Response#mime_type` returns `nil` on empty `Content-Type` header. ([#8464](https://github.com/crystal-lang/crystal/pull/8464), thanks @Sija) -- Fixed handling of unidirectional SSL servers hang. ([#8481](https://github.com/crystal-lang/crystal/pull/8481), thanks @rdp) -- Add `HTTP::Client#write_timeout`. ([#8507](https://github.com/crystal-lang/crystal/pull/8507), thanks @Sija) -- Updated mime type of `.js` files to `text/javascript` and include `image/webp`. ([#8342](https://github.com/crystal-lang/crystal/pull/8342), thanks @mamantoha) -- Refactor websocket protocol GUID string. ([#8339](https://github.com/crystal-lang/crystal/pull/8339), thanks @vlazar) - -### Crypto - -- **(breaking-change)** Enforce single-line results of `OpenSSL::DigestBase#base64digest` via `Base64.strict_encode`. ([#8215](https://github.com/crystal-lang/crystal/pull/8215), thanks @j8r) - -### Concurrency - -- Fixed `Channel` successful sent and raise behavior. ([#8284](https://github.com/crystal-lang/crystal/pull/8284), thanks @firejox) -- Fixed `Channel#close` to be thread-safe. ([#8249](https://github.com/crystal-lang/crystal/pull/8249), thanks @firejox) -- Fixed `select` with `receive?` and closed channels. ([#8304](https://github.com/crystal-lang/crystal/pull/8304), thanks @bcardiff) -- Faster `Mutex` implementation and policy checks. ([#8295](https://github.com/crystal-lang/crystal/pull/8295), [#8563](https://github.com/crystal-lang/crystal/pull/8563), thanks @waj, @ysbaddaden) -- **(performance)** Channel internals refactor and optimize. ([#8322](https://github.com/crystal-lang/crystal/pull/8322), [#8497](https://github.com/crystal-lang/crystal/pull/8497), thanks @firejox, @Sija) -- Add docs to `Channel#send` and `Channel#close`. ([#8356](https://github.com/crystal-lang/crystal/pull/8356), thanks @lbarasti) -- Fixed `Thread#gc_thread_handler` for Windows support. ([#8519](https://github.com/crystal-lang/crystal/pull/8519), thanks @straight-shoota) - -### System - -- Don't close pipes used for signal handlers in multi-thread mode. ([#8465](https://github.com/crystal-lang/crystal/pull/8465), thanks @waj) -- Fixed thread initialization on OpenBSD. ([#8293](https://github.com/crystal-lang/crystal/pull/8293), thanks @wmoxam) -- Implement fibers for win32. ([#7995](https://github.com/crystal-lang/crystal/pull/7995), [#8513](https://github.com/crystal-lang/crystal/pull/8513), thanks @straight-shoota, @firejox) - -### Runtime - -- Fixed fiber initialization on `-Dgc_none -Dpreview_mt`. ([#8280](https://github.com/crystal-lang/crystal/pull/8280), thanks @bcardiff) -- Add GC profiling stats and warning bindings. ([#8281](https://github.com/crystal-lang/crystal/pull/8281), thanks @bcardiff, @benoist) -- Refactor `callstack_spec`. ([#8308](https://github.com/crystal-lang/crystal/pull/8308), [#8395](https://github.com/crystal-lang/crystal/pull/8395), thanks @straight-shoota, @Sija) - -### Spec - -- Fixed `--fail-fast` behaviour. ([#8453](https://github.com/crystal-lang/crystal/pull/8453), thanks @asterite) -- Add before, after, and around hooks. ([#8302](https://github.com/crystal-lang/crystal/pull/8302), thanks @asterite) -- Restrict the type returned by `should_not be_nil` and others. ([#8412](https://github.com/crystal-lang/crystal/pull/8412), thanks @asterite) -- Add ability to randomize specs via `--order random|`. ([#8310](https://github.com/crystal-lang/crystal/pull/8310), thanks @Fryguy) -- Add specs for `Spec` filters. ([#8242](https://github.com/crystal-lang/crystal/pull/8242), thanks @Fryguy) -- Add ability to tag specs. ([#8068](https://github.com/crystal-lang/crystal/pull/8068), thanks @Fryguy) - -## Compiler - -- Fixed musl libc detection (Alpine 3.10 regression bug). ([#8330](https://github.com/crystal-lang/crystal/pull/8330), thanks @straight-shoota) -- Fixed pragmas handling in macros. ([#8256](https://github.com/crystal-lang/crystal/pull/8256), thanks @asterite) -- Fixed parser crash for 'alias Foo?'. ([#8282](https://github.com/crystal-lang/crystal/pull/8282), thanks @oprypin) -- Fixed parser error on newline before closing parenthesis. ([#8320](https://github.com/crystal-lang/crystal/pull/8320), thanks @MakeNowJust) -- Fixed generic subtypes edge cases triggering `no target defs` error. ([#8417](https://github.com/crystal-lang/crystal/pull/8417), thanks @asterite) -- Fixed cleanup of local vars reachable by macros. ([#8529](https://github.com/crystal-lang/crystal/pull/8529), thanks @asterite) -- Add support for LLVM 9. ([#8358](https://github.com/crystal-lang/crystal/pull/8358), thanks @RX14) -- Add `--mcmodel` option to compiler. ([#8363](https://github.com/crystal-lang/crystal/pull/8363), thanks @ffwff) -- Disallow `instance_sizeof` on union. ([#8399](https://github.com/crystal-lang/crystal/pull/8399), thanks @asterite) -- Add mention to `crystal --help` in help. ([#3628](https://github.com/crystal-lang/crystal/pull/3628), thanks @rdp) -- Improve error message when a filename is misspelled. ([#8500](https://github.com/crystal-lang/crystal/pull/8500), thanks @rdp) -- Show full path of locally compiled Crystal. ([#8486](https://github.com/crystal-lang/crystal/pull/8486), thanks @rdp) -- Code cleanups. ([#8460](https://github.com/crystal-lang/crystal/pull/8460), thanks @veelenga) - -### Language semantics - -- Fixed method lookup priority when type alias of union is used. ([#8258](https://github.com/crystal-lang/crystal/pull/8258), thanks @asterite) -- Fixed visibility modifiers in virtual types. ([#8562](https://github.com/crystal-lang/crystal/pull/8562), thanks @asterite) -- Fixed `sizeof(Bool)`. ([#8273](https://github.com/crystal-lang/crystal/pull/8273), thanks @asterite) - -## Tools - -### Formatter - -- Fixed indent in arguments. ([#8315](https://github.com/crystal-lang/crystal/pull/8315), thanks @MakeNowJust) -- Fixed crash related to parenthesis on generic types. ([#8501](https://github.com/crystal-lang/crystal/pull/8501), thanks @asterite) - -### Doc generator - -- Fixed underscore type restriction in doc generator. ([#8331](https://github.com/crystal-lang/crystal/pull/8331), thanks @straight-shoota) -- Correctly attach docs through multiple macro invocations. ([#8502](https://github.com/crystal-lang/crystal/pull/8502), thanks @asterite) -- Allow constants to use `:ditto:`. ([#8389](https://github.com/crystal-lang/crystal/pull/8389), thanks @Blacksmoke16) -- Add sitemap generator. ([#8348](https://github.com/crystal-lang/crystal/pull/8348), thanks @straight-shoota) -- Add documentation for pseudo-methods: `!`, `as`, `nil?`, etc.. ([#8327](https://github.com/crystal-lang/crystal/pull/8327), [#8371](https://github.com/crystal-lang/crystal/pull/8371), thanks @straight-shoota) -- Add clickable anchor icon next to headings. ([#8344](https://github.com/crystal-lang/crystal/pull/8344), thanks @Blacksmoke16) -- Use `&` instead of `&block` for signature of yielding method. ([#8394](https://github.com/crystal-lang/crystal/pull/8394), thanks @j8r) - -### Playground - -- Do not collapse whitespaces in playground sidebar. ([#8528](https://github.com/crystal-lang/crystal/pull/8528), thanks @hugopl) - -## Others - -- CI improvements and housekeeping. ([#8210](https://github.com/crystal-lang/crystal/pull/8210), [#8251](https://github.com/crystal-lang/crystal/pull/8251), [#8283](https://github.com/crystal-lang/crystal/pull/8283), [#8439](https://github.com/crystal-lang/crystal/pull/8439), [#8510](https://github.com/crystal-lang/crystal/pull/8510), thanks @bcardiff) -- Update base docker images to `bionic` and LLVM 8.0. ([#8442](https://github.com/crystal-lang/crystal/pull/8442), thanks @bcardiff) -- Repository clean-up. ([#8312](https://github.com/crystal-lang/crystal/pull/8312), [#8397](https://github.com/crystal-lang/crystal/pull/8397), thanks @bcardiff, @straight-shoota) - -# 0.31.1 (2019-09-30) - -## Standard library - -### Numeric - -- Fixed overflow in `Random::Secure`. ([#8224](https://github.com/crystal-lang/crystal/pull/8224), thanks @oprypin) - -### Networking - -- Workaround `IO::Evented#evented_write` invalid `IndexError` error. ([#8239](https://github.com/crystal-lang/crystal/pull/8239), thanks @bcardiff) - -### Concurrency - -- Use bdw-gc upstream patch for green threads support. ([#8225](https://github.com/crystal-lang/crystal/pull/8225), thanks @bcardiff) -- Refactor `Channel` to use records instead of tuples. ([#8227](https://github.com/crystal-lang/crystal/pull/8227), thanks @asterite) - -### Spec - -- Add `before_suite` and `after_suite` hooks. ([#8238](https://github.com/crystal-lang/crystal/pull/8238), thanks @asterite) - -## Compiler - -- Fix debug location information when emitting main code from module. ([#8234](https://github.com/crystal-lang/crystal/pull/8234), thanks @asterite) - -### Language semantics - -- Use virtual type for `uninitialized`. ([#8221](https://github.com/crystal-lang/crystal/pull/8221), thanks @asterite) - -# 0.31.0 (2019-09-23) - -## Language changes - -- Allow non-captured block args with type restriction using `& : T -> U`. ([#8117](https://github.com/crystal-lang/crystal/pull/8117), thanks @asterite) - -### Macros - -- Ensure `@type` is devirtualized inside macros. ([#8149](https://github.com/crystal-lang/crystal/pull/8149), thanks @asterite) - -## Standard library - -- **(breaking-change)** Remove `Markdown` from the std-lib. ([#8115](https://github.com/crystal-lang/crystal/pull/8115), thanks @asterite) -- **(breaking-change)** Deprecate `OptionParser#parse!`, use `OptionParser#parse`. ([#8041](https://github.com/crystal-lang/crystal/pull/8041), thanks @didactic-drunk) -- Fix example codes in multiple places. ([#8194](https://github.com/crystal-lang/crystal/pull/8194), thanks @maiha) - -### Numeric - -- **(breaking-change)** Enable overflow by default. ([#8170](https://github.com/crystal-lang/crystal/pull/8170), thanks @bcardiff) -- **(breaking-change)** Make `/` the arithmetic division for all types. ([#8120](https://github.com/crystal-lang/crystal/pull/8120), thanks @bcardiff) -- Add `BigDecimal#**` and `BigRational#**` (pow operator). ([#7860](https://github.com/crystal-lang/crystal/pull/7860), thanks @jwbuiter) -- Avoid overflow exception in `Number#round(digits, base)`. ([#8204](https://github.com/crystal-lang/crystal/pull/8204), thanks @bcardiff) -- Refactor `Int#divisible_by?` for clarity. ([#8045](https://github.com/crystal-lang/crystal/pull/8045), thanks @yxhuvud) - -### Text - -- **(performance)** Minor `String#lchop?` ASCII-only optimization. ([#8052](https://github.com/crystal-lang/crystal/pull/8052), thanks @r00ster91) - -### Collections - -- **(performance)** Array optimizations for small number of elements. ([#8048](https://github.com/crystal-lang/crystal/pull/8048), thanks @asterite) -- **(performance)** Optimize `Array#*`. ([#8087](https://github.com/crystal-lang/crystal/pull/8087), thanks @asterite) -- **(performance)** Hash now uses an open addressing algorithm. ([#8017](https://github.com/crystal-lang/crystal/pull/8017), [#8182](https://github.com/crystal-lang/crystal/pull/8182), thanks @asterite) -- **(performance)** Optimize `Hash#to_a`, `Hash#keys` and `Hash#values`. ([#8042](https://github.com/crystal-lang/crystal/pull/8042), thanks @asterite) -- **(performance)** Add `Hash#put` and optimize `Set#add?`. ([#8116](https://github.com/crystal-lang/crystal/pull/8116), thanks @asterite) -- Fixed `Slice#==` for some generic instantiations, add `Slice#<=>`. ([#8074](https://github.com/crystal-lang/crystal/pull/8074), thanks @asterite) -- Add docs on idempotence and methods involving eager evaluation in `Iterator`. ([#8053](https://github.com/crystal-lang/crystal/pull/8053), thanks @KimBurgess) -- Add `Set#+`. ([#8121](https://github.com/crystal-lang/crystal/pull/8121), thanks @sam0x17) -- Refactor `Hash` to use integer division instead of float division. ([#8104](https://github.com/crystal-lang/crystal/pull/8104), thanks @asterite) - -### Serialization - -- **(breaking-change)** Rename `XML::Type` to `XML::Node::Type`, introduce `XML::Reader::Type`. ([#8134](https://github.com/crystal-lang/crystal/pull/8134), thanks @asterite) -- Fixed JSON and YAML parsing of `NamedTuple` with nilable fields. ([#8109](https://github.com/crystal-lang/crystal/pull/8109), thanks @asterite) -- Fixed YAML to emit unicode characters as such. ([#8132](https://github.com/crystal-lang/crystal/pull/8132), thanks @asterite) -- Fixed INI generation of empty sections. ([#8106](https://github.com/crystal-lang/crystal/pull/8106), thanks @j8r) - -### Files - -- **(performance)** Optimize `Path#join` by precomputing capacity if possible. ([#8078](https://github.com/crystal-lang/crystal/pull/8078), thanks @asterite) -- **(performance)** Optimize `Path#join` for the case of joining one single part. ([#8082](https://github.com/crystal-lang/crystal/pull/8082), thanks @asterite) -- **(performance)** Optimize `Dir.glob`. ([#8081](https://github.com/crystal-lang/crystal/pull/8081), thanks @asterite) -- Fixed `File.basename` off-by-one corner-case. ([#8119](https://github.com/crystal-lang/crystal/pull/8119), thanks @ysbaddaden) -- Fixed unneeded evaluation of `Path.home` on `Path.expand`. ([#8128](https://github.com/crystal-lang/crystal/pull/8128), thanks @asterite) -- Fixed `Zip::Writer` STORED compression. ([#8142](https://github.com/crystal-lang/crystal/pull/8142), thanks @asterite) -- Fixed missing check on `ARGF` if read_count is zero. ([#8177](https://github.com/crystal-lang/crystal/pull/8177), thanks @Blacksmoke16) - -### Networking - -- **(breaking-change)** Replace `HTTP::Server::Response#respond_with_error` with `#respond_with_status`. ([#6988](https://github.com/crystal-lang/crystal/pull/6988), thanks @straight-shoota) -- **(breaking-change)** Handle too long URIs and too large header fields in `HTTP::Request.from_io` and remove `HTTP::Request::BadRequest`. ([#8013](https://github.com/crystal-lang/crystal/pull/8013), thanks @straight-shoota) -- Fixed memory leak from `SSL_new` if `ssl_accept` fails. ([#8088](https://github.com/crystal-lang/crystal/pull/8088), thanks @rdp) -- Fixed WebSocket ipv6 hostname connection. ([#8066](https://github.com/crystal-lang/crystal/pull/8066), thanks @MrSorcus) -- Add `URI#query_params` method. ([#8090](https://github.com/crystal-lang/crystal/pull/8090), thanks @rodrigopinto) -- Add `URI#resolve` and `URI#relativize`. ([#7716](https://github.com/crystal-lang/crystal/pull/7716), thanks @straight-shoota) -- Add `#clear`, `#delete`, and `#size` methods to `HTTP::Cookies`. ([#8107](https://github.com/crystal-lang/crystal/pull/8107), thanks @sam0x17) -- Refactor `http/server_spec`. ([#8056](https://github.com/crystal-lang/crystal/pull/8056), thanks @straight-shoota) -- Refactor UDP specs to use random port. ([#8139](https://github.com/crystal-lang/crystal/pull/8139), thanks @waj) - -### Concurrency - -- Multithreading. ([#8112](https://github.com/crystal-lang/crystal/pull/8112), thanks @waj) -- Delay releasing of fiber stack in multi-thread mode. ([#8138](https://github.com/crystal-lang/crystal/pull/8138), thanks @waj) -- Make `Crystal::Scheduler.init_workers` block until workers are ready. ([#8145](https://github.com/crystal-lang/crystal/pull/8145), thanks @bcardiff) -- Make `Crystal::ThreadLocalValue` thread-safe. ([#8168](https://github.com/crystal-lang/crystal/pull/8168), thanks @waj) -- Let `exec_recursive` use a thread-local data structure. ([#8146](https://github.com/crystal-lang/crystal/pull/8146), thanks @asterite) -- Add explicit return types for some channel methods. ([#8161](https://github.com/crystal-lang/crystal/pull/8161), thanks @Blacksmoke16) -- Remove the dedicated fiber to run the event loop. ([#8173](https://github.com/crystal-lang/crystal/pull/8173), thanks @waj) -- Fix corruption of thread linked list. ([#8196](https://github.com/crystal-lang/crystal/pull/8196), thanks @waj) -- Workaround compile on win32 until fibers is implemented. ([#8195](https://github.com/crystal-lang/crystal/pull/8195), thanks @straight-shoota) - -### System - -- Increase precision of `Process.times`. ([#8097](https://github.com/crystal-lang/crystal/pull/8097), thanks @jgaskins) - -### Spec - -- **(breaking-change)** Add support for `focus`. ([#8125](https://github.com/crystal-lang/crystal/pull/8125), [#8178](https://github.com/crystal-lang/crystal/pull/8178), [#8208](https://github.com/crystal-lang/crystal/pull/8208), thanks @asterite, @straight-shoota, @bcardiff) - -## Compiler - -- Fixed ICE on declarations inside fun. ([#8076](https://github.com/crystal-lang/crystal/pull/8076), thanks @asterite) -- Fixed missing `name_location` of some calls. ([#8192](https://github.com/crystal-lang/crystal/pull/8192), thanks @asterite) -- Activate compiler warnings by default. ([#8171](https://github.com/crystal-lang/crystal/pull/8171), thanks @bcardiff) -- Improve return type mismatch error. ([#8203](https://github.com/crystal-lang/crystal/pull/8203), thanks @asterite) -- Improve `for` expression error. ([#7641](https://github.com/crystal-lang/crystal/pull/7641), thanks @r00ster91) - -### Language semantics - -- Fixed abstract def check regarding generic ancestor lookup. ([#8098](https://github.com/crystal-lang/crystal/pull/8098), thanks @asterite) -- Fixed missing virtualization of type arguments in `Proc` types. ([#8159](https://github.com/crystal-lang/crystal/pull/8159), thanks @asterite) -- Fixed incorrect typing after exception handler. ([#8037](https://github.com/crystal-lang/crystal/pull/8037), thanks @asterite) -- Fixed behaviour when a yield node can't be typed. ([#8101](https://github.com/crystal-lang/crystal/pull/8101), thanks @asterite) -- Fixed `offsetof` on reference types. ([#8137](https://github.com/crystal-lang/crystal/pull/8137), thanks @mcr431) -- Allow rescue var to be closured. ([#8143](https://github.com/crystal-lang/crystal/pull/8143), thanks @asterite) -- Refactor class var and constant initialization. ([#8067](https://github.com/crystal-lang/crystal/pull/8067), [#8091](https://github.com/crystal-lang/crystal/pull/8091), thanks @waj) -- Add runtime check for recursive initialization of class variables and constants. ([#8172](https://github.com/crystal-lang/crystal/pull/8172), thanks @waj) - -## Tools - -### Doc generator - -- Fixed link to constructors of another class. ([#8110](https://github.com/crystal-lang/crystal/pull/8110), thanks @asterite) -- Enable docs from previous def and/or ancestors to be inherited. ([#6989](https://github.com/crystal-lang/crystal/pull/6989), thanks @asterite) - -## Others - -- Update CI to use 0.30.1. ([#8032](https://github.com/crystal-lang/crystal/pull/8032), thanks @bcardiff) -- Use LLVM 8.0 for Linux official packages. ([#8155](https://github.com/crystal-lang/crystal/pull/8155), thanks @bcardiff, @RX14) -- Update dependencies of the build process. ([#8205](https://github.com/crystal-lang/crystal/pull/8205), thanks @bcardiff) -- Code cleanups. ([#8033](https://github.com/crystal-lang/crystal/pull/8033), thanks @straight-shoota) - -# 0.30.1 (2019-08-12) - -## Standard library - -### Numeric - -- Fixed `Number#humanize` digits. ([#8027](https://github.com/crystal-lang/crystal/pull/8027), thanks @straight-shoota) - -### Networking - -- Fixed TCP socket leaking after failed SSL connect in `HTTP::Client#socket`. ([#8025](https://github.com/crystal-lang/crystal/pull/8025), thanks @straight-shoota) -- Honor normalized header names for HTTP requests. ([#8061](https://github.com/crystal-lang/crystal/pull/8061), thanks @asterite) - -### Concurrency - -- Don't resume fibers directly from event loop callbacks (or support for libevent 2.1.11). ([#8058](https://github.com/crystal-lang/crystal/pull/8058), thanks @waj) - -## Compiler - -- Fixed `sizeof(Nil)` and other empty types. ([#8040](https://github.com/crystal-lang/crystal/pull/8040), thanks @asterite) -- Avoid internal globals of type i128 or u128. (or workaround [a llvm 128 bits bug](https://bugs.llvm.org/show_bug.cgi?id=42932)). ([#8063](https://github.com/crystal-lang/crystal/pull/8063), thanks @bcardiff, @asterite) - -### Language semantics - -- Consider abstract method implementation in supertype for abstract method checks. ([#8035](https://github.com/crystal-lang/crystal/pull/8035), thanks @asterite) - -## Tools - -### Formatter - -- Handle consecutive macro literals when subformatting. ([#8034](https://github.com/crystal-lang/crystal/pull/8034), thanks @asterite) -- Fixed crash when formatting syntax error inside macro. ([#8055](https://github.com/crystal-lang/crystal/pull/8055), thanks @asterite) - -## Others - -- Use LLVM 6.0.1 for darwin official packages. ([#7994](https://github.com/crystal-lang/crystal/pull/7994), thanks @bcardiff) -- Split std_specs in 32 bits CI. ([#8065](https://github.com/crystal-lang/crystal/pull/8065), thanks @bcardiff) - -# 0.30.0 (2019-08-01) - -## Language changes - -- **(breaking-change)** Enforce abstract methods return types. ([#7956](https://github.com/crystal-lang/crystal/pull/7956), [#7999](https://github.com/crystal-lang/crystal/pull/7999), [#8010](https://github.com/crystal-lang/crystal/pull/8010), thanks @asterite) -- **(breaking-change)** Don't allow ranges to span across lines. ([#7888](https://github.com/crystal-lang/crystal/pull/7888), thanks @oprypin) - -### Macros - -- Add `args`/`named_args` macro methods to `Annotations`. ([#7694](https://github.com/crystal-lang/crystal/pull/7694), thanks @Blacksmoke16) -- Unify `resolve` and `types` macro methods API for `Type` and `Path` for convenience. ([#7970](https://github.com/crystal-lang/crystal/pull/7970), thanks @asterite) - -## Standard library - -- **(breaking-change)** Remove `UUID#to_slice` in favor of `UUID#bytes` to fix dangling pointer issues. ([#7901](https://github.com/crystal-lang/crystal/pull/7901), thanks @ysbaddaden) -- **(performance)** Improve `Box` of reference types. ([#8016](https://github.com/crystal-lang/crystal/pull/8016), thanks @waj) -- Fixed initial seed of `Random::ISAAC`. ([#7977](https://github.com/crystal-lang/crystal/pull/7977), thanks @asterite) -- Fixed mem intrinsics for aarch64. ([#7983](https://github.com/crystal-lang/crystal/pull/7983), thanks @drujensen) -- Add `Benchmark.memory`. ([#7835](https://github.com/crystal-lang/crystal/pull/7835), thanks @r00ster91) -- Allow setting default capacity for `StringPool`. ([#7899](https://github.com/crystal-lang/crystal/pull/7899), thanks @carlhoerberg) -- Add type restrictions to `INI`. ([#7831](https://github.com/crystal-lang/crystal/pull/7831), thanks @j8r) -- Fixed `Logger` docs. ([#7898](https://github.com/crystal-lang/crystal/pull/7898), thanks @dprobinson) -- Fix example codes in multiple places. ([#8003](https://github.com/crystal-lang/crystal/pull/8003), thanks @maiha) - -### Numeric - -- Fixed incorrect `Int#%` overflow. ([#7980](https://github.com/crystal-lang/crystal/pull/7980), thanks @asterite) -- Fixed inconsistency between `Float#to_s` and `BigFloat#to_s`, always show `.0` for whole numbers. ([#7982](https://github.com/crystal-lang/crystal/pull/7982), thanks @Lasvad) - -### Text - -- Fixed unicode alternate ranges generation. ([#7924](https://github.com/crystal-lang/crystal/pull/7924), thanks @asterite) - -### Collections - -- Add `Enumerable#tally`. ([#7921](https://github.com/crystal-lang/crystal/pull/7921), thanks @kachick) -- Add `Enumerable#reduce?` overload with not initial value. ([#7941](https://github.com/crystal-lang/crystal/pull/7941), thanks @miketheman) -- Fix specs of `Enumerable#min_by?`. ([#7919](https://github.com/crystal-lang/crystal/pull/7919), thanks @kachick) - -### Serialization - -- **(breaking-change)** JSON: use enums instead of symbols. ([#7966](https://github.com/crystal-lang/crystal/pull/7966), thanks @asterite) -- Fixed YAML deserialization of String in a union type. ([#7938](https://github.com/crystal-lang/crystal/pull/7938), thanks @asterite) -- Validate element names in `XML::Builder`. ([#7965](https://github.com/crystal-lang/crystal/pull/7965), thanks @Blacksmoke16) -- Allow numeric keys in JSON (ie: `Hash(Int32, String).from_json`). ([#7944](https://github.com/crystal-lang/crystal/pull/7944), thanks @asterite) -- Add `alias`/`merge` methods to `YAML::Builder` and `YAML::Nodes::Builder`. ([#7949](https://github.com/crystal-lang/crystal/pull/7949), thanks @Blacksmoke16) - -### Files - -- Adds `File.readlink` to match `File.symlink`. ([#7858](https://github.com/crystal-lang/crystal/pull/7858), thanks @didactic-drunk) - -### Networking - -- **(breaking-change)** Improve URL encoding. `URI.escape` and `URI.unescape` are renamed to `URI.encode_www_form` and `URI.decode_www_form`. Add `URI.encode` and `URI.decode`. ([#7997](https://github.com/crystal-lang/crystal/pull/7997), [#8021](https://github.com/crystal-lang/crystal/pull/8021), thanks @straight-shoota, @bcardiff) -- **(performance)** HTTP protocol parsing optimizations. ([#8002](https://github.com/crystal-lang/crystal/pull/8002), [#8009](https://github.com/crystal-lang/crystal/pull/8009), thanks @asterite) -- Fixed `HTTP::Server` response double-close. ([#7908](https://github.com/crystal-lang/crystal/pull/7908), thanks @asterite) -- Enforce `HTTP::Client` host argument is just a host. ([#7958](https://github.com/crystal-lang/crystal/pull/7958), thanks @asterite) -- Allow `HTTP::Params.encode` to encode an arrays of values for a key. ([#7862](https://github.com/crystal-lang/crystal/pull/7862), thanks @rodrigopinto) -- Forward `read_timeout`/`write_timeout` in ssl socket to underlaying socket. ([#7820](https://github.com/crystal-lang/crystal/pull/7820), thanks @carlhoerberg) -- Natively support [Same-site Cookies](https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1.1). ([#7864](https://github.com/crystal-lang/crystal/pull/7864), thanks @Blacksmoke16) -- Allow setting buffer size for `IO::Buffered`. ([#7930](https://github.com/crystal-lang/crystal/pull/7930), thanks @carlhoerberg) - -### Crypto - -- Require openssl algorithm in pkcs5. ([#7985](https://github.com/crystal-lang/crystal/pull/7985), thanks @will) -- Fixed cipher expectation in `OpenSSL::SSL::Socket` spec. ([#7871](https://github.com/crystal-lang/crystal/pull/7871), thanks @j8r) - -### Concurrency - -- Fixed `sysconf` call on OpenBSD. ([#7879](https://github.com/crystal-lang/crystal/pull/7879), thanks @jcs) - -### System - -- Introduce `System::User` and `System::Group`. ([#7725](https://github.com/crystal-lang/crystal/pull/7725), thanks @woodruffw, @chris-huxtable) -- Add docs for `Process::Status.exit_status` (#7873). ([#8014](https://github.com/crystal-lang/crystal/pull/8014), thanks @UlisseMini) - -## Compiler - -- Fixed codegen of `pointer.as(Nil)`. ([#8019](https://github.com/crystal-lang/crystal/pull/8019), thanks @asterite) -- Fixed edge cases in parser and stringifier. ([#7886](https://github.com/crystal-lang/crystal/pull/7886), thanks @oprypin) -- Fixed `concrete_types` for virtual metaclass and modules. ([#7951](https://github.com/crystal-lang/crystal/pull/7951), thanks @bcardiff) -- Fixed incorrect `remove_indirection` in `TypeDefType`. ([#7971](https://github.com/crystal-lang/crystal/pull/7971), thanks @bcardiff) -- Fixed missing `CRYSTAL_SPEC_COMPILER_FLAGS` usage in some more specs. ([774768](https://github.com/crystal-lang/crystal/commit/77476800836eb47c8d783e2259bf21c2992f2041), thanks @bcardiff) -- Revamp compile error formatting & output. ([#7748](https://github.com/crystal-lang/crystal/pull/7748), thanks @martimatix) -- Add support for LLVM 8. ([#7987](https://github.com/crystal-lang/crystal/pull/7987), thanks @bcardiff) -- Add support for LLVM 7. ([#7986](https://github.com/crystal-lang/crystal/pull/7986), thanks @bcardiff, @waj, @foutrelis, @wmoxam) -- Add debug log helper function for codegen. ([#7935](https://github.com/crystal-lang/crystal/pull/7935), [#7937](https://github.com/crystal-lang/crystal/pull/7937), thanks @bcardiff) -- Refactor codegen of unions. ([#7940](https://github.com/crystal-lang/crystal/pull/7940), thanks @bcardiff) -- Move `LLVMId` from `CodeGenVisitor` to `Program`. ([#7973](https://github.com/crystal-lang/crystal/pull/7973), thanks @bcardiff) -- Minor additions and refactors on for LLVM codegen. ([#7972](https://github.com/crystal-lang/crystal/pull/7972), thanks @bcardiff) -- Add `bin/check-compiler-flag` helper script. Add `make clean_cache`. ([da3892](https://github.com/crystal-lang/crystal/commit/da38927f3a00f1e6e5ea86b96ca669533f0aa438), thanks @bcardiff) - -### Language semantics - -- Fixed generic metaclass argument expansion. ([#7916](https://github.com/crystal-lang/crystal/pull/7916), thanks @asterite) -- Fixed top-level private const not being scoped. ([#7907](https://github.com/crystal-lang/crystal/pull/7907), thanks @asterite) -- Fixed enum overflow when declaring members. ([#7881](https://github.com/crystal-lang/crystal/pull/7881), thanks @asterite) -- Fixed annotation lookup on generic types. ([#7891](https://github.com/crystal-lang/crystal/pull/7891), thanks @asterite) - -## Tools - -### Formatter - -- Format top-level inline macros. ([#7889](https://github.com/crystal-lang/crystal/pull/7889), [#7992](https://github.com/crystal-lang/crystal/pull/7992), thanks @asterite) - -### Doc generator - -- Allow rendering tags on methods without any docs. ([#7952](https://github.com/crystal-lang/crystal/pull/7952), thanks @Blacksmoke16) - -## Others - -- Update CI to use 0.29.0. ([#7863](https://github.com/crystal-lang/crystal/pull/7863), thanks @bcardiff) -- Automated snap publishing. ([#7893](https://github.com/crystal-lang/crystal/pull/7893), thanks @bcardiff) -- ~~Use LLVM 6.0.1 for darwin official packages.~~ ([#7994](https://github.com/crystal-lang/crystal/pull/7994), thanks @bcardiff) - -# 0.29.0 (2019-06-05) - -## Standard library - -- Fix example codes in multiple places. ([#7718](https://github.com/crystal-lang/crystal/pull/7718), thanks @maiha) - -### Macros - -- Fix inheritance support of `record` macro. ([#7811](https://github.com/crystal-lang/crystal/pull/7811), thanks @asterite) -- Omit quotes in `puts` macro output. ([#7734](https://github.com/crystal-lang/crystal/pull/7734), thanks @asterite) - -### Numeric - -- **(performance)** Optimize `String#to_u` methods for the case of a negative number. ([#7446](https://github.com/crystal-lang/crystal/pull/7446), thanks @r00ster91) - -### Text - -- **(breaking-change)** Deprecate `String#at`, use `String#char_at`. ([#7633](https://github.com/crystal-lang/crystal/pull/7633), thanks @j8r) -- **(breaking-change)** Change `String#to_i` to parse octals with prefix `0o` (but not `0` by default). ([#7691](https://github.com/crystal-lang/crystal/pull/7691), thanks @icy-arctic-fox) -- **(breaking-change)** Restrict some `String#to_i` arguments to be `Bool`. ([#7436](https://github.com/crystal-lang/crystal/pull/7436), thanks @j8r) -- Add `downcase` option to `String#camelcase`. ([#7717](https://github.com/crystal-lang/crystal/pull/7717), thanks @wontruefree) -- Add support for unicode 12.0.0. ([#7721](https://github.com/crystal-lang/crystal/pull/7721), thanks @Blacksmoke16) -- Fix `Unicode` not showing up in the API docs. ([#7720](https://github.com/crystal-lang/crystal/pull/7720), thanks @r00ster91) - -### Collections - -- **(breaking-change)** Remove `Slice#pointer`. ([#7581](https://github.com/crystal-lang/crystal/pull/7581), thanks @Maroo-b) -- Add sort methods to `Slice`. ([#7597](https://github.com/crystal-lang/crystal/pull/7597), thanks @Maroo-b) -- Add `Slice#[]?`. ([#7701](https://github.com/crystal-lang/crystal/pull/7701), thanks @Sija) -- Improve docs for `Slice#[]`. ([#7780](https://github.com/crystal-lang/crystal/pull/7780), thanks @Sija) - -### Serialization - -- YAML: let String handle numbers too. ([#7809](https://github.com/crystal-lang/crystal/pull/7809), thanks @asterite) - -### Time - -- Fix time format RFC 3339 to not include offset seconds. ([#7492](https://github.com/crystal-lang/crystal/pull/7492), thanks @straight-shoota) - -### Files - -- **(breaking-change)** Rename `File::DEVNULL` to `File::NULL`. ([#7778](https://github.com/crystal-lang/crystal/pull/7778), thanks @r00ster91) -- Fix handling of files starting with `~` in `Path#expand`. ([#7768](https://github.com/crystal-lang/crystal/pull/7768), thanks @byroot) -- Fix `Dir.glob(match_hidden: false)` not hiding hidden files properly. ([#7774](https://github.com/crystal-lang/crystal/pull/7774), thanks @ayazhafiz) - -### Networking - -- **(breaking-change)** Let `IO#copy` return `UInt64`. ([#7660](https://github.com/crystal-lang/crystal/pull/7660), thanks @asterite) -- Add support for UDP multicast. ([#7423](https://github.com/crystal-lang/crystal/pull/7423), thanks @stakach) -- Add missing requires to `openssl.cr`. ([#7803](https://github.com/crystal-lang/crystal/pull/7803), thanks @RX14) -- Add `IO::MultiWriter#flush`. ([#7765](https://github.com/crystal-lang/crystal/pull/7765), thanks @mamantoha) -- Add `OpenSSL::SSL::Socket#cipher` and `#tls_version`. ([#7445](https://github.com/crystal-lang/crystal/pull/7445), thanks @carlhoerberg) -- Improve `URI#normalize`. ([#7635](https://github.com/crystal-lang/crystal/pull/7635), thanks @straight-shoota) -- Improve documentation of some `URI` methods. ([#7796](https://github.com/crystal-lang/crystal/pull/7796), thanks @r00ster91) -- Refactor `StaticFileHandler` specs for `Last-Modified` header. ([#7640](https://github.com/crystal-lang/crystal/pull/7640), thanks @straight-shoota) -- Refactor compression usage in handler specs. ([#7819](https://github.com/crystal-lang/crystal/pull/7819), thanks @asterite) - -### Crypto - -- **(breaking-change)** Rename `Crypto::Bcrypt::Password#==` to `#verify`. ([#7790](https://github.com/crystal-lang/crystal/pull/7790), thanks @asterite) - -### Concurrency - -- Add docs for `Channel`. ([#7673](https://github.com/crystal-lang/crystal/pull/7673), thanks @j8r) - -## Compiler - -- **(breaking-change)** Fix require relative path resolution. ([#7758](https://github.com/crystal-lang/crystal/pull/7758), thanks @asterite) -- **(breaking-change)** Disallow '!' or '?' at the end of the LHS in an assignment. ([#7582](https://github.com/crystal-lang/crystal/pull/7582), thanks @Maroo-b) -- Allow running compiler_specs with specific flags. ([#7837](https://github.com/crystal-lang/crystal/pull/7837), thanks @bcardiff) -- Fix extend from generic types. ([#7812](https://github.com/crystal-lang/crystal/pull/7812), thanks @asterite) -- Don't virtualize types in `Union(...)` and keep more accurate type information. ([#7815](https://github.com/crystal-lang/crystal/pull/7815), thanks @asterite) -- Do not generate debug metadata for arguments of naked functions. ([#7775](https://github.com/crystal-lang/crystal/pull/7775), thanks @eyusupov) -- Detect deprecation on initialize methods and methods with named args. ([#7724](https://github.com/crystal-lang/crystal/pull/7724), thanks @bcardiff) -- Fix track of AST nodes location. ([#7827](https://github.com/crystal-lang/crystal/pull/7827), thanks @asterite) -- Fix `offsetof` not being usable with macros. ([#7703](https://github.com/crystal-lang/crystal/pull/7703), thanks @malte-v) -- Allow parsing of `call &.@ivar`. ([#7754](https://github.com/crystal-lang/crystal/pull/7754), thanks @asterite) -- Fix `Def#to_s` with `**options` and `&block`. ([#7854](https://github.com/crystal-lang/crystal/pull/7854), thanks @MakeNowJust) -- Check `pointerof` inner expression for errors. ([#7755](https://github.com/crystal-lang/crystal/pull/7755), thanks @asterite) -- Fix some error messages. ([#7833](https://github.com/crystal-lang/crystal/pull/7833), thanks @asterite) -- Improve wording of `pointerof(self)` parser error. ([#7542](https://github.com/crystal-lang/crystal/pull/7542), thanks @r00ster91) -- Fix typo. ([#7828](https://github.com/crystal-lang/crystal/pull/7828), thanks @RX14) - -### Language semantics - -- **(breaking-change)** Fix new/initialize lookup regarding modules. ([#7818](https://github.com/crystal-lang/crystal/pull/7818), thanks @asterite) -- **(breaking-change)** Don't precompute `sizeof` on abstract structs and modules. ([#7801](https://github.com/crystal-lang/crystal/pull/7801), thanks @asterite) -- Consider macro calls in `@ivar` initializer. ([#7750](https://github.com/crystal-lang/crystal/pull/7750), thanks @asterite) -- Give precedence to `T.class` over `Class` in method lookup. ([#7759](https://github.com/crystal-lang/crystal/pull/7759), thanks @asterite) -- Honor enum base type on non-default values. ([#7776](https://github.com/crystal-lang/crystal/pull/7776), thanks @asterite) -- Avoid lookup of private def defined inside macro. ([#7733](https://github.com/crystal-lang/crystal/pull/7733), thanks @asterite) -- Improve type flow of var in `if` with `&&`. ([#7785](https://github.com/crystal-lang/crystal/pull/7785), thanks @asterite) -- Fix handling of `NoReturn` in `if`. ([#7792](https://github.com/crystal-lang/crystal/pull/7792), thanks @asterite) -- Improve edge issues with `while` and `rescue`. ([#7806](https://github.com/crystal-lang/crystal/pull/7806), thanks @asterite) -- Improve error on macro call in proc pointer. ([#7757](https://github.com/crystal-lang/crystal/pull/7757), thanks @asterite) -- Fix error on named args forwarding. ([#7756](https://github.com/crystal-lang/crystal/pull/7756), thanks @asterite) -- Check `NoReturn` type in named args. ([#7761](https://github.com/crystal-lang/crystal/pull/7761), thanks @asterite) -- Fix internal handling of unbound/abstract generic types. ([#7781](https://github.com/crystal-lang/crystal/pull/7781), thanks @asterite) -- Fix wrong cast to unbound generic type. ([#7793](https://github.com/crystal-lang/crystal/pull/7793), thanks @asterite) -- Fix subclass observer to handle edge case call over generic types. ([#7735](https://github.com/crystal-lang/crystal/pull/7735), thanks @asterite) -- Fix edge case of abstract struct with one subclass. ([#7787](https://github.com/crystal-lang/crystal/pull/7787), thanks @asterite) -- Make automatic cast work with `with ... yield`. ([#7746](https://github.com/crystal-lang/crystal/pull/7746), thanks @asterite) - -## Tools - -- Allow to lookup class and module implementations. ([#7742](https://github.com/crystal-lang/crystal/pull/7742), thanks @MakeNowJust) -- Refactor old duplicated 'def contains_target'. ([#7739](https://github.com/crystal-lang/crystal/pull/7739), thanks @MakeNowJust) - -### Formatter - -- Don't produce unnecessary newline before named args following heredoc. ([#7695](https://github.com/crystal-lang/crystal/pull/7695), thanks @MakeNowJust) -- Fix formatting of multiline call arguments. ([#7745](https://github.com/crystal-lang/crystal/pull/7745), thanks @MakeNowJust) -- Fix formatting of annotations with newlines and spaces. ([#7744](https://github.com/crystal-lang/crystal/pull/7744), thanks @MakeNowJust) -- Refactor code to format &.[]. ([#7699](https://github.com/crystal-lang/crystal/pull/7699), thanks @MakeNowJust) - -## Others - -- CI improvements and housekeeping. ([#7705](https://github.com/crystal-lang/crystal/pull/7705), [#7852](https://github.com/crystal-lang/crystal/pull/7852), thanks @bcardiff) -- Move VERSION inside ./src. ([#7804](https://github.com/crystal-lang/crystal/pull/7804), thanks @bcardiff) - -# 0.28.0 (2019-04-17) - -## Language changes - -- **(breaking-change)** Enum declaration members can no longer be separated by a space, only by a newline, `;` or `,`, the latter being deprecated and reformatted to a newline. ([#7607](https://github.com/crystal-lang/crystal/pull/7607), [#7618](https://github.com/crystal-lang/crystal/pull/7618), thanks @asterite, and @j8r) -- Add begin-less and end-less ranges: `array[5..]`. ([#7179](https://github.com/crystal-lang/crystal/pull/7179), thanks @asterite) -- Add `offsetof(Type, @ivar)` expression. ([#7589](https://github.com/crystal-lang/crystal/pull/7589), thanks @malte-v) - -### Macros - -- Add `Type#annotations` to list all annotations and not just the last of each kind. ([#7326](https://github.com/crystal-lang/crystal/pull/7326), thanks @Blacksmoke16) -- Add `ArrayLiteral#sort_by` macro method. ([#3947](https://github.com/crystal-lang/crystal/pull/3947), thanks @jreinert) - -## Standard library - -- **(breaking-change)** Allow creating `None` enum flag with `Enum.from_value`. ([#6516](https://github.com/crystal-lang/crystal/pull/6516), thanks @bew) -- **(breaking-change)** Add deprecation message to `PartialComparable`. Its behaviour has been fully integrated into `Comparable`. ([#7664](https://github.com/crystal-lang/crystal/pull/7664), thanks @straight-shoota) -- **(performance)** Optimize dwarf line numbers decoding. ([#7413](https://github.com/crystal-lang/crystal/pull/7413), thanks @asterite) -- Fix `Signal::CHLD.reset` not clearing previous handler. ([#7409](https://github.com/crystal-lang/crystal/pull/7409), thanks @asterite) -- Add lazy versions of `Object.getter?` and `Object.property?` macros. ([#7322](https://github.com/crystal-lang/crystal/pull/7322), thanks @Sija) -- Allow returning other values than `-1`, `0` and `1` by `Comparable#<=>`. ([#7277](https://github.com/crystal-lang/crystal/pull/7277), thanks @r00ster91) -- Add missing `require` statements to samples in the API docs. ([#7564](https://github.com/crystal-lang/crystal/pull/7564), thanks @Maroo-b) -- Fix example codes in multiple places. ([#7569](https://github.com/crystal-lang/crystal/pull/7569), thanks @maiha) -- Add documentation for `@[Flags]` and `@[Link]` annotations. ([#7665](https://github.com/crystal-lang/crystal/pull/7665), thanks @bcardiff) -- Add documentation for `Bool`. ([#7651](https://github.com/crystal-lang/crystal/pull/7651), thanks @wontruefree) -- Refactor to avoid usage of the thread-local `$errno` GLIBC_PRIVATE symbol. ([#7496](https://github.com/crystal-lang/crystal/pull/7496), thanks @felixvf) -- Refactor to have similar signatures across the stdlib for `#to_s` and `#inspect`. ([#7528](https://github.com/crystal-lang/crystal/pull/7528), thanks @wontruefree) - -### Numeric - -- **(breaking-change)** Add deprecation message to `Int#/`. It will return a `Float` in `0.29.0`. Use `Int#//` for integer division. ([#7639](https://github.com/crystal-lang/crystal/pull/7639), thanks @bcardiff) -- Change `Number#inspect` to not show the type suffixes. ([#7525](https://github.com/crystal-lang/crystal/pull/7525), thanks @asterite) -- Add `Int#leading_zeros_count` and `Int#trailing_zeros_count`. ([#7520](https://github.com/crystal-lang/crystal/pull/7520), thanks @Sija) -- Add `Big*#//`, `BigInt#&`-ops and missing `#floor`, `#ceil`, `#trunc`. ([#7638](https://github.com/crystal-lang/crystal/pull/7638), thanks @bcardiff) -- Improve `OverflowError` message. ([#7375](https://github.com/crystal-lang/crystal/pull/7375), thanks @r00ster91) - -### Text - -- **(performance)** Optimize `String#compare` in case of ASCII only. ([#7352](https://github.com/crystal-lang/crystal/pull/7352), thanks @r00ster91) -- Add methods for human-readable formatting of numbers: `Number#format`, `Number#humanize`, and `Int#humanize_bytes`. ([#6314](https://github.com/crystal-lang/crystal/pull/6314), thanks @straight-shoota) -- Add `String#rchop?` and `String#lchop?`. ([#7328](https://github.com/crystal-lang/crystal/pull/7328), thanks @j8r) -- Add `options` argument to `String#camelcase` and `String#underscore`. ([#7374](https://github.com/crystal-lang/crystal/pull/7374), thanks @r00ster91) -- Add docs to `Unicode::CaseOptions`. ([#7513](https://github.com/crystal-lang/crystal/pull/7513), thanks @r00ster91) -- Improve specs and docs for `String#each_line` and `IO#each_line`. ([#7419](https://github.com/crystal-lang/crystal/pull/7419), thanks @straight-shoota) - -### Collections - -- **(breaking-change)** Let `Array#sort` only use `<=>`, and let `<=>` return `nil` for partial comparability. ([#6611](https://github.com/crystal-lang/crystal/pull/6611), thanks @asterite) -- **(breaking-change)** Drop `Iterator#rewind`. Implement `#cycle` by storing elements in an array. ([#7440](https://github.com/crystal-lang/crystal/pull/7440), thanks @asterite) -- **(performance)** Add `Enumerable#each_cons` support for `Deque` as a reuse buffer. ([#7233](https://github.com/crystal-lang/crystal/pull/7233), thanks @yxhuvud) -- **(performance)** Change `Range#bsearch` `/ 2` to `>> 1` for faster performance. ([#7531](https://github.com/crystal-lang/crystal/pull/7531), thanks @Fryguy) -- Fix `Slice#clone` for non-primitive types and deep copy. ([#7591](https://github.com/crystal-lang/crystal/pull/7591), thanks @straight-shoota) -- Move `Indexable#zip` and `Indexable#zip?` to `Enumerable` and make it work with any number of `Indexable` or `Iterable` or `Iterator`. ([#7453](https://github.com/crystal-lang/crystal/pull/7453), thanks @asterite) -- Add `Slice#[](Range)`. ([#7439](https://github.com/crystal-lang/crystal/pull/7439), thanks @asterite) -- Add nillable range fetching `#[]?(Range)` to `Array` and `String`. ([#7338](https://github.com/crystal-lang/crystal/pull/7338), thanks @j8r) -- Add `Set#add?`. ([#7495](https://github.com/crystal-lang/crystal/pull/7495), thanks @Sija) -- Improve documentation of `Hash` regarding ordering of items. ([#7594](https://github.com/crystal-lang/crystal/pull/7594), thanks @straight-shoota) - -### Serialization - -- **(breaking-change)** Change return type of `YAML#libyaml_version` to `SemanticVersion`. ([#7555](https://github.com/crystal-lang/crystal/pull/7555), thanks @asterite) -- Fix support for libxml2 2.9.9. ([#7477](https://github.com/crystal-lang/crystal/pull/7477), thanks @asterite) -- Fix support for libyaml 0.2.2. ([#7555](https://github.com/crystal-lang/crystal/pull/7555), thanks @asterite) -- Add `BigDecimal.from_yaml`. ([#7398](https://github.com/crystal-lang/crystal/pull/7398), thanks @Sija) - -### Time - -- **(breaking-change)** Rename `Time` constructors. Deprecate `Time.now` to encourage usage `Time.utc` or `Time.local` ([#5346](https://github.com/crystal-lang/crystal/pull/5346), [#7586](https://github.com/crystal-lang/crystal/pull/7586), thanks @straight-shoota) -- **(breaking-change)** Rename `Time#add_span` to `Time#shift` for changing a time instance by calendar units and handle other units. ([#6598](https://github.com/crystal-lang/crystal/pull/6598), thanks @straight-shoota) -- **(breaking-change)** Change `Time#date` to return a `Tuple` of `{year, month, day}`. Use `Time#at_beginning_of_day` if a `Time` instance is wanted. ([#5822](https://github.com/crystal-lang/crystal/pull/5822), thanks @straight-shoota) -- Fix Windows monotonic time bug. ([#7377](https://github.com/crystal-lang/crystal/pull/7377), thanks @domgetter) -- Refactor `Time` methods. ([#6581](https://github.com/crystal-lang/crystal/pull/6581), thanks @straight-shoota) - -### Files - -- **(breaking-change)** Remove `IO#flush_on_newline` and only `sync` on `STDOUT`/`STDIN`/`STDERR` when they are TTY devices. ([#7470](https://github.com/crystal-lang/crystal/pull/7470), thanks @asterite) -- Add `Path` type. ([#5635](https://github.com/crystal-lang/crystal/pull/5635), thanks @straight-shoota) - -### Networking - -- **(breaking-change)** Move `HTTP::Multipart` to `MIME::Multipart`. ([#7085](https://github.com/crystal-lang/crystal/pull/7085), thanks @m1lt0n) -- **(breaking-change)** Stop parsing JSON in OAuth2 errors. ([#7467](https://github.com/crystal-lang/crystal/pull/7467), thanks @asterite) -- **(breaking-change)** Fix `RequestProcessor` connection reuse logic. ([#7055](https://github.com/crystal-lang/crystal/pull/7055), thanks @straight-shoota) -- **(breaking-change)** Replace `HTTP.default_status_message_for(Int)` with `HTTP::Status.new(Int).description`. ([#7247](https://github.com/crystal-lang/crystal/pull/7247), thanks @dwightwatson) -- **(breaking-change)** Fix issues in `URI` implementation. `URI#opaque` method is merged into `URI#path`, which no longer returns `Nil`. `#parse`/`#to_s` normalization and default port handling has changed. ([#6323](https://github.com/crystal-lang/crystal/pull/6323), thanks @straight-shoota) -- Fix write buffering in OpenSSL sockets. ([#7460](https://github.com/crystal-lang/crystal/pull/7460), thanks @carlhoerberg) -- Fix leaks in `HTTP::Server` `#bind_*` and specs. ([#7197](https://github.com/crystal-lang/crystal/pull/7197), thanks @straight-shoota) -- Add `HTTP::Request#remote_address`. ([#7610](https://github.com/crystal-lang/crystal/pull/7610), thanks @asterite) -- Add `HTTP::Status` and `Response#status`. ([#7247](https://github.com/crystal-lang/crystal/pull/7247), [#7682](https://github.com/crystal-lang/crystal/pull/7682), thanks @dwightwatson, and @bcardiff) -- Add support for OAuth 2.0 resource owner password credentials grant type. ([#7424](https://github.com/crystal-lang/crystal/pull/7424), thanks @Blacksmoke16) -- Add support for IIS date format in cookies. ([#7405](https://github.com/crystal-lang/crystal/pull/7405), thanks @Sija) -- Allow calls to `IO::Syscall#wait_readable` and `IO::Syscall#wait_writable`. ([#7366](https://github.com/crystal-lang/crystal/pull/7366), thanks @stakach) - -- Fix spec of `HTTP::Client` to not write server response after timeout. ([#7402](https://github.com/crystal-lang/crystal/pull/7402), thanks @asterite) -- Fix spec of `TCP::Server` for musl. ([#7484](https://github.com/crystal-lang/crystal/pull/7484), thanks @straight-shoota) - -### Crypto - -- **(breaking-change)** Use `OpenSSL::Algorithm` instead of symbols for `digest`/`hexdigest`. Expose LibCrypt's `PKCS5_PBKDF2_HMAC`. ([#7264](https://github.com/crystal-lang/crystal/pull/7264), thanks @mniak) - -### Concurrency - -- Add multi-threading ready GC when compiling with `-D preview_mt`. ([#7546](https://github.com/crystal-lang/crystal/pull/7546), thanks @bcardiff, @waj, and @ysbaddaden) -- Ship patched bdw-gc for multi-threading support. ([#7622](https://github.com/crystal-lang/crystal/pull/7622), thanks @bcardiff, and @ysbaddaden) -- Refactor to extract `Fiber::StackPool` from `Fiber`. ([#7417](https://github.com/crystal-lang/crystal/pull/7417), thanks @ysbaddaden) -- Refactor `IO::Syscall` as `IO::Evented`. ([#7505](https://github.com/crystal-lang/crystal/pull/7505), thanks @ysbaddaden) - -### System - -- Add command and args to `execvp` error message. ([#7511](https://github.com/crystal-lang/crystal/pull/7511), thanks @straight-shoota) -- Refactor signals handling in a separate fiber. ([#7469](https://github.com/crystal-lang/crystal/pull/7469), thanks @asterite) - -### Spec - -- Improve how running specs are cancelled upon `CTRL+C`. ([#7426](https://github.com/crystal-lang/crystal/pull/7426), thanks @asterite) -- Allow `pending` and `it` to accept constants. ([#7646](https://github.com/crystal-lang/crystal/pull/7646), thanks @straight-shoota) - -## Compiler - -- **(performance)** Avoid fork and spawn when `--threads=1`. ([#7397](https://github.com/crystal-lang/crystal/pull/7397), thanks @asterite) -- Fix exception type thrown on missing require. ([#7386](https://github.com/crystal-lang/crystal/pull/7386), thanks @asterite) -- Fix ICE when assigning a constant inside a multi-assign. ([#7468](https://github.com/crystal-lang/crystal/pull/7468), thanks @asterite) -- Fix parsing and behaviour of `->foo.[]` and other operators . ([#7334](https://github.com/crystal-lang/crystal/pull/7334), thanks @asterite) -- Fix parsing bug in `asm` with 3 colons and a variable. ([#7627](https://github.com/crystal-lang/crystal/pull/7627), thanks @r00ster91) -- Opt-in detection of calls to `@[Deprecated]` methods. ([#7596](https://github.com/crystal-lang/crystal/pull/7596), [#7626](https://github.com/crystal-lang/crystal/pull/7626), [#7661](https://github.com/crystal-lang/crystal/pull/7661), thanks @bcardiff) -- Add `CRYSTAL_LIBRARY_PATH` for lookup static libraries. ([#7562](https://github.com/crystal-lang/crystal/pull/7562), thanks @bcardiff) -- Improve error messages by adding the scope (and `with ... yield` scope, if any) on undefined method error. ([#7384](https://github.com/crystal-lang/crystal/pull/7384), thanks @asterite) -- Suggest `next` when trying to break from captured block . ([#7406](https://github.com/crystal-lang/crystal/pull/7406), thanks @r00ster91) -- Add detection of linux environment in compiler config. ([#7479](https://github.com/crystal-lang/crystal/pull/7479), thanks @straight-shoota) -- Pending leftovers to support `//` and `&`-ops in multiple places. ([#7628](https://github.com/crystal-lang/crystal/pull/7628), thanks @bcardiff) -- Refactor `Crystal::Config.version` to use `read_file` macro. ([#7081](https://github.com/crystal-lang/crystal/pull/7081), thanks @Sija) -- Rewrite macro spec without executing a shell command. ([#6962](https://github.com/crystal-lang/crystal/pull/6962), thanks @asterite) -- Fix typo in internals. ([#7592](https://github.com/crystal-lang/crystal/pull/7592), thanks @toshokan) - -### Language semantics - -- Fix issues with `as`, `as?` and unions and empty types. ([#7475](https://github.com/crystal-lang/crystal/pull/7475), thanks @asterite) -- Fix method lookup when restrictions of instantiated and non-instantiated generic types are used. ([#7537](https://github.com/crystal-lang/crystal/pull/7537), thanks @bew) -- Fix method lookup when free vars and explicit types are used. ([#7536](https://github.com/crystal-lang/crystal/pull/7536), [#7580](https://github.com/crystal-lang/crystal/pull/7580), thanks @bew) -- When declaring a `protected initialize`, define a protected `new`. ([#7510](https://github.com/crystal-lang/crystal/pull/7510), thanks @asterite) -- Fix named args type matching. ([#7529](https://github.com/crystal-lang/crystal/pull/7529), thanks @asterite) -- Merge procs with the same arguments type and `Nil | T` return type to `Nil` return type. ([#7527](https://github.com/crystal-lang/crystal/pull/7527), thanks @asterite) -- Fix passing recursive alias to proc. ([#7568](https://github.com/crystal-lang/crystal/pull/7568), thanks @asterite) - -## Tools - -- Suggest the user to run the formatter in `travis.yml`. ([#7138](https://github.com/crystal-lang/crystal/pull/7138), thanks @KCErb) - -### Formatter - -- Fix formatting of `1\n.as(Int32)`. ([#7347](https://github.com/crystal-lang/crystal/pull/7347), thanks @asterite) -- Fix formatting of nested array elements. ([#7450](https://github.com/crystal-lang/crystal/pull/7450), thanks @MakeNowJust) -- Fix formatting of comments and enums. ([#7605](https://github.com/crystal-lang/crystal/pull/7605), thanks @asterite) -- Fix CLI handling of absolute paths input. ([#7560](https://github.com/crystal-lang/crystal/pull/7560), thanks @RX14) - -### Doc generator - -- Don't include private constants. ([#7575](https://github.com/crystal-lang/crystal/pull/7575), thanks @r00ster91) -- Include Crystal built-in constants. ([#7623](https://github.com/crystal-lang/crystal/pull/7623), thanks @bcardiff) -- Add compile-time flag to docs generator. ([#6668](https://github.com/crystal-lang/crystal/pull/6668), [#7438](https://github.com/crystal-lang/crystal/pull/7438), thanks @straight-shoota) -- Display deprecated label when `@[Deprecated]` is used. ([#7653](https://github.com/crystal-lang/crystal/pull/7653), thanks @bcardiff) - -### Playground - -- Change the font-weight used for better readability. ([#7552](https://github.com/crystal-lang/crystal/pull/7552), thanks @Maroo-b) - -## Others - -- CI improvements and housekeeping. ([#7359](https://github.com/crystal-lang/crystal/pull/7359), [#7381](https://github.com/crystal-lang/crystal/pull/7381), [#7388](https://github.com/crystal-lang/crystal/pull/7388), [#7387](https://github.com/crystal-lang/crystal/pull/7387), [#7390](https://github.com/crystal-lang/crystal/pull/7390), [#7622](https://github.com/crystal-lang/crystal/pull/7622), thanks @bcardiff) -- Smoke test linux 64 bits package using docker image recent build. ([#7389](https://github.com/crystal-lang/crystal/pull/7389), thanks @bcardiff) -- Mention git pre-commit hook in `CONTRIBUTING.md`. ([#7617](https://github.com/crystal-lang/crystal/pull/7617), thanks @straight-shoota) -- Fix misspellings throughout the codebase. ([#7361](https://github.com/crystal-lang/crystal/pull/7361), thanks @Sija) -- Use chars instead of strings throughout the codebase. ([#6237](https://github.com/crystal-lang/crystal/pull/6237), thanks @r00ster91) -- Fix GC finalization warning in `Thread` specs. ([#7403](https://github.com/crystal-lang/crystal/pull/7403), thanks @asterite) -- Remove generated docs from linux packages. ([#7519](https://github.com/crystal-lang/crystal/issues/7519), thanks @straight-shoota) - -# 0.27.2 (2019-02-05) - -## Standard library - -- Fixed integer overflow in main thread stack base detection. ([#7373](https://github.com/crystal-lang/crystal/pull/7373), thanks @ysbaddaden) - -### Networking - -- Fixes TLS exception during shutdown. ([#7372](https://github.com/crystal-lang/crystal/pull/7372), thanks @bcardiff) -- Fixed `HTTP::Client` support exception on missing Content-Type. ([#7371](https://github.com/crystal-lang/crystal/pull/7371), thanks @bew) - -# 0.27.1 (2019-01-30) - -## Language changes - -- Allow trailing commas inside tuple types. ([#7182](https://github.com/crystal-lang/crystal/pull/7182), thanks @asterite) - -## Standard library - -- **(performance)** Optimize generating `UUID` from `String`. ([#7030](https://github.com/crystal-lang/crystal/pull/7030), thanks @jgaskins) -- **(performance)** Improve `SemanticVersion` operations. ([#7234](https://github.com/crystal-lang/crystal/pull/7234), thanks @j8r) -- Fixed markdown inline code parsing. ([#7090](https://github.com/crystal-lang/crystal/pull/7090), thanks @MakeNowJust) -- Fixed inappropriate uses of `Time.now`. ([#7155](https://github.com/crystal-lang/crystal/pull/7155), thanks @straight-shoota) -- Make `Nil#not_nil!` raise `NilAssertionError`. ([#7330](https://github.com/crystal-lang/crystal/pull/7330), thanks @r00ster91) -- Add SemanticVersion to API docs. ([#7003](https://github.com/crystal-lang/crystal/pull/7003), thanks @Blacksmoke16) -- Add docs to discourage the use of `Bool#to_unsafe` other than for C bindings. ([#7320](https://github.com/crystal-lang/crystal/pull/7320), thanks @oprypin) -- Refactor `#to_s` to be independent of the `name` method. ([#7295](https://github.com/crystal-lang/crystal/pull/7295), thanks @asterite) - -### Macros - -- Fixed docs of `ArrayLiteral#unshift`. ([#7127](https://github.com/crystal-lang/crystal/pull/7127), thanks @Blacksmoke16) -- Fixed `Annotation#[]` to accept `String` and `Symbol` as keys. ([#7153](https://github.com/crystal-lang/crystal/pull/7153), thanks @MakeNowJust) -- Fixed `NamedTupleLiteral#[]` to raise a compile error for invalid key type. ([#7158](https://github.com/crystal-lang/crystal/pull/7158), thanks @MakeNowJust) -- Fixed `getter`/`property` macros to work properly with `Bool` types. ([#7313](https://github.com/crystal-lang/crystal/pull/7313), thanks @Sija) -- Add `read_file` macro method. ([#6967](https://github.com/crystal-lang/crystal/pull/6967), [#7094](https://github.com/crystal-lang/crystal/pull/7094), thanks @Sija, @woodruffw) -- Add `StringLiteral#count`. ([#7239](https://github.com/crystal-lang/crystal/pull/7239), thanks @Blacksmoke16) - -### Numeric - -- Fixed scale issues when dividing `BigDecimal`. ([#7218](https://github.com/crystal-lang/crystal/pull/7218), thanks @Sija) -- Allow underscores in the `String` passed to `Big*` constructors. ([#7107](https://github.com/crystal-lang/crystal/pull/7107), thanks @Sija) -- Add conversion methods and docs to `Complex`. ([#5440](https://github.com/crystal-lang/crystal/pull/5440), thanks @Sija) -- Add specs for `Int128`, `UInt128`. ([#7173](https://github.com/crystal-lang/crystal/pull/7173), thanks @bcardiff) -- Add unsafe number ops `value.to_X!`/`T.new!`/`Int#&**`. ([#7226](https://github.com/crystal-lang/crystal/pull/7226), thanks @bcardiff) -- Add overflow detection with preview opt-in. ([#7206](https://github.com/crystal-lang/crystal/pull/7206), thanks @bcardiff) - -### Text - -- Fixed `ECR` location error reported. ([#7137](https://github.com/crystal-lang/crystal/pull/7137), thanks @MakeNowJust) -- Add docs to ECR. ([#7121](https://github.com/crystal-lang/crystal/pull/7121), thanks @KCErb) -- Refactor `String#to_i` to avoid future overflow. ([#7172](https://github.com/crystal-lang/crystal/pull/7172), thanks @bcardiff) - -### Collections - -- Fixed docs example in `Hash#from`. ([#7210](https://github.com/crystal-lang/crystal/pull/7210), thanks @r00ster91) -- Fixed docs links of `Enumerable#chunks` and `Iterator#chunk`. ([#6941](https://github.com/crystal-lang/crystal/pull/6941), thanks @r00ster91) -- Remove implicit null skip from `Hash` to `JSON` serialization. ([#7053](https://github.com/crystal-lang/crystal/pull/7053), thanks @MakeNowJust) -- Add `Iterator#slice_after`. ([#7146](https://github.com/crystal-lang/crystal/pull/7146), thanks @asterite) -- Add `Iterator#slice_before`. ([#7152](https://github.com/crystal-lang/crystal/pull/7152), thanks @asterite) -- Add `Iteratory#slice_when` and `Iterator#chunk_while`. ([#7159](https://github.com/crystal-lang/crystal/pull/7159), thanks @asterite) -- Add `Enumerable#to_h(&block)`. ([#7150](https://github.com/crystal-lang/crystal/pull/7150), thanks @Sija) -- Add `Enumerable#one?`. ([#7166](https://github.com/crystal-lang/crystal/pull/7166), thanks @asterite) -- Add several `Enumerable`, `Iterator` and `Array` overloads that accept a pattern. ([#7174](https://github.com/crystal-lang/crystal/pull/7174), thanks @asterite) -- Add docs to hash constructors. ([#6923](https://github.com/crystal-lang/crystal/pull/6923), thanks @KCErb) - -### Serialization - -- Add conversion between JSON and YAML. ([#7232](https://github.com/crystal-lang/crystal/pull/7232), thanks @straight-shoota) -- Standardize `#as_T`/`#as_T?` methods between `JSON::Any`/`YAML::Any`. ([#6556](https://github.com/crystal-lang/crystal/pull/6556), thanks @j8r) -- Add `Set#from_yaml`. ([#6310](https://github.com/crystal-lang/crystal/pull/6310), thanks @kostya) - -### Time - -- Fixed `Time::Span` initializer and `sleep` for big seconds. ([#7221](https://github.com/crystal-lang/crystal/pull/7221), thanks @asterite) -- Fixed docs to show proper use of parse. ([#7035](https://github.com/crystal-lang/crystal/pull/7035), thanks @jwoertink) -- Add missing `Float#weeks` method similar to `Int#weeks`. ([#7165](https://github.com/crystal-lang/crystal/pull/7165), thanks @vlazar) - -### Files - -- Fix `mkstemps` support on aarch64. ([#7300](https://github.com/crystal-lang/crystal/pull/7300), thanks @silmanduin66) -- Validate LibC error codes in specs involving Errno errors. ([#7087](https://github.com/crystal-lang/crystal/pull/7087), thanks @straight-shoota) -- Add microsecond precision to `System::File.utime` (Unix). ([#7156](https://github.com/crystal-lang/crystal/pull/7156), thanks @straight-shoota) -- Add missing tempfile cleanup in specs. ([#7250](https://github.com/crystal-lang/crystal/pull/7250), thanks @bcardiff) -- Add docs for file open modes. ([#6664](https://github.com/crystal-lang/crystal/pull/6664), thanks @r00ster91) - -### Networking - -- Fixed `HTTP::Client` edge case of exception during in TLS initialization. ([#7123](https://github.com/crystal-lang/crystal/pull/7123), thanks @asterite) -- Fixed `OpenSSL::SSL::Error.new` to not raise `Errno`. ([#7068](https://github.com/crystal-lang/crystal/pull/7068), thanks @straight-shoota) -- Fixed `URI` encoding in `StaticFileHandler::DirectoryListing`. ([#7072](https://github.com/crystal-lang/crystal/pull/7072), thanks @Sija) -- Add MIME registry. ([#5765](https://github.com/crystal-lang/crystal/pull/5765), [#7079](https://github.com/crystal-lang/crystal/pull/7079), [#7080](https://github.com/crystal-lang/crystal/pull/7080), thanks @straight-shoota, @Sija) -- Add `MIME::MediaType` for parsing mime media types. ([#7077](https://github.com/crystal-lang/crystal/pull/7077), thanks @straight-shoota) -- Add support for 100-continue in `HTTP::Server::Response`. ([#6912](https://github.com/crystal-lang/crystal/pull/6912), thanks @jreinert) -- Add support for creating sockets from raw file descriptors. ([#6894](https://github.com/crystal-lang/crystal/pull/6894), thanks @myfreeweb) -- Add SNI support for OpenSSL. ([#7291](https://github.com/crystal-lang/crystal/pull/7291), thanks @bararchy) -- Improve `HTTP::Server` docs. ([#7251](https://github.com/crystal-lang/crystal/pull/7251), thanks @straight-shoota) -- Refactor `OpenSSL` specs to reduce chances of failing. ([#7202](https://github.com/crystal-lang/crystal/pull/7202), thanks @bcardiff) - -### Crypto - -- Add `OpenSSL::Cipher#authenticated?` to see if the cipher supports aead. ([#7223](https://github.com/crystal-lang/crystal/pull/7223), thanks @danielwestendorf) - -### System - -- Fixed inline ASM when compiling for ARM. ([#7041](https://github.com/crystal-lang/crystal/pull/7041), thanks @omarroth) -- Implement `Crystal::System` for Win32. ([#6972](https://github.com/crystal-lang/crystal/pull/6972), thanks @markrjr) -- Add `Errno#errno_message` getter. ([#6702](https://github.com/crystal-lang/crystal/pull/6702), thanks @r00ster91) - -### Spec - -- Detect nesting `it` and `pending` at run-time. ([#7297](https://github.com/crystal-lang/crystal/pull/7297), thanks @MakeNowJust) - -## Compiler - -- Fixed how `LLVM::Type.const_int` emit `Int128` literals. ([#7135](https://github.com/crystal-lang/crystal/pull/7135), thanks @bcardiff) -- Fixed ICE related to named tuples. ([#7163](https://github.com/crystal-lang/crystal/pull/7163), thanks @asterite) -- Fixed automatic casting for private top-level methods. ([#7310](https://github.com/crystal-lang/crystal/pull/7310), thanks @asterite) -- Give proper error if defining initialize inside enum, allow `Enum.new`. ([#7266](https://github.com/crystal-lang/crystal/pull/7266), thanks @asterite) -- Give proper error when trying to access instance variable of union type. ([#7194](https://github.com/crystal-lang/crystal/pull/7194), thanks @asterite) -- Give proper error when trying to instantiate Module. ([#6735](https://github.com/crystal-lang/crystal/pull/6735), thanks @r00ster91) -- Give proper error related to named arguments. ([#7288](https://github.com/crystal-lang/crystal/pull/7288), thanks @asterite) -- Parse required comma between block args. ([#7343](https://github.com/crystal-lang/crystal/pull/7343), thanks @asterite) -- Improve inference in recursion that involves blocks. ([#7161](https://github.com/crystal-lang/crystal/pull/7161), thanks @asterite) -- Add locations to all expanded macro arguments. ([#7008](https://github.com/crystal-lang/crystal/pull/7008), thanks @MakeNowJust) -- Turn a not compiler specific error while requiring into ICE. ([#7208](https://github.com/crystal-lang/crystal/pull/7208), thanks @MakeNowJust) -- Remove old `nil?` error on pointer types. ([#7180](https://github.com/crystal-lang/crystal/pull/7180), thanks @asterite) -- Improve too big tuple and named tuple error message. ([#7131](https://github.com/crystal-lang/crystal/pull/7131), thanks @r00ster91) -- Workaround buggy offset debug info values. ([#7335](https://github.com/crystal-lang/crystal/pull/7335), thanks @bcardiff) -- Refactor extract helper methods to emit `Float32`, `Float64` values. ([#7134](https://github.com/crystal-lang/crystal/pull/7134), thanks @bcardiff) -- Refactor filename resolution logic out of `interpret_run`. ([#7051](https://github.com/crystal-lang/crystal/pull/7051), thanks @Sija) -- Refactor internals regarding overflow. ([#7262](https://github.com/crystal-lang/crystal/pull/7262), thanks @bcardiff) -- Refactor `Crystal::Codegen::Target` and consolidate triple handling. ([#7282](https://github.com/crystal-lang/crystal/pull/7282), [#7317](https://github.com/crystal-lang/crystal/pull/7317), thanks @RX14, @bcardiff) - -## Tools - -- Update README template. ([#7118](https://github.com/crystal-lang/crystal/pull/7118), thanks @mamantoha) -- Capitalise Crystal in CLI output. ([#7224](https://github.com/crystal-lang/crystal/pull/7224), thanks @dwightwatson) - -### Formatter - -- Fixed formatting of multiline literal elements. ([#7048](https://github.com/crystal-lang/crystal/pull/7048), thanks @MakeNowJust) -- Fixed formatting of heredoc with interpolations. ([#7184](https://github.com/crystal-lang/crystal/pull/7184), thanks @MakeNowJust) -- Fixed prevent conflict between nested tuple types and macro expressions. ([#7097](https://github.com/crystal-lang/crystal/pull/7097), thanks @MakeNowJust) -- Fixed format when `typeof` appears inside generic type. ([#7176](https://github.com/crystal-lang/crystal/pull/7176), thanks @asterite) -- Fixed format of newline after `&.foo` in call. ([#7240](https://github.com/crystal-lang/crystal/pull/7240), thanks @MakeNowJust) -- Honor same behaviour for single or multiple file arguments. ([#7144](https://github.com/crystal-lang/crystal/pull/7144), thanks @straight-shoota) -- Refactor remove quotes from overflow symbols in formatter spec. ([#6968](https://github.com/crystal-lang/crystal/pull/6968), thanks @r00ster91) -- Major rework of `crystal tool format` command. ([#7257](https://github.com/crystal-lang/crystal/pull/7257), thanks @MakeNowJust) - -### Doc generator - -- **(security)** Prevent XSS via args. ([#7056](https://github.com/crystal-lang/crystal/pull/7056), thanks @MakeNowJust) -- Fixed generation of toplevel. ([#7063](https://github.com/crystal-lang/crystal/pull/7063), thanks @MakeNowJust) -- Fixed display of double splat and block arg. ([#7029](https://github.com/crystal-lang/crystal/pull/7029), [#7031](https://github.com/crystal-lang/crystal/pull/7031), thanks @MakeNowJust) -- Fixed keep trailing spaces in macros. ([#7099](https://github.com/crystal-lang/crystal/pull/7099), thanks @MakeNowJust) -- Fixed avoid showing subtypes of aliased type. ([#7124](https://github.com/crystal-lang/crystal/pull/7124), thanks @asterite) -- Fixed style of methods when hovering. ([#7022](https://github.com/crystal-lang/crystal/pull/7022), thanks @r00ster91) -- Fixed duplicate `source_link` field. ([#7033](https://github.com/crystal-lang/crystal/pull/7033), thanks @bcardiff) -- Fixed missing keywords in `Doc::Highlighter`. ([#7054](https://github.com/crystal-lang/crystal/pull/7054), thanks @MakeNowJust) -- Add `--format` option to docs command. ([#6982](https://github.com/crystal-lang/crystal/pull/6982), thanks @mniak) - -## Others - -- CI improvements and housekeeping. ([#7018](https://github.com/crystal-lang/crystal/pull/7018), [#7043](https://github.com/crystal-lang/crystal/pull/7043), [#7133](https://github.com/crystal-lang/crystal/pull/7133), [#7139](https://github.com/crystal-lang/crystal/pull/7139), [#7230](https://github.com/crystal-lang/crystal/pull/7230), [#7227](https://github.com/crystal-lang/crystal/pull/7227), [#7263](https://github.com/crystal-lang/crystal/pull/7263), thanks @bcardiff) -- CI split formatting check. ([#7228](https://github.com/crystal-lang/crystal/pull/7228), thanks @bcardiff) -- Depend on standard variable to let the user define the build date. ([#7186](https://github.com/crystal-lang/crystal/pull/7186), thanks @eli-schwartz) -- Reorganize community section in README, add forum. ([#7235](https://github.com/crystal-lang/crystal/pull/7235), thanks @straight-shoota) -- Fixed docs grammar and typos. ([#7034](https://github.com/crystal-lang/crystal/pull/7034), [#7242](https://github.com/crystal-lang/crystal/pull/7242), [#7331](https://github.com/crystal-lang/crystal/pull/7331), thanks @r00ster91, @girng) -- Improve samples. ([#6454](https://github.com/crystal-lang/crystal/pull/6454), thanks @r00ster91) -- Fixed 0.27.0 CHANGELOG. ([#7024](https://github.com/crystal-lang/crystal/pull/7024), thanks @arcage) -- Update ISSUE_TEMPLATE to include forum. ([#7301](https://github.com/crystal-lang/crystal/pull/7301), thanks @straight-shoota) -- Update LICENSE's copyright year. ([#7246](https://github.com/crystal-lang/crystal/pull/7246), thanks @matiasgarciaisaia) - -# 0.27.0 (2018-11-01) - -## Language changes - -- **(breaking-change)** Disallow comma after newline in argument list. ([#6514](https://github.com/crystal-lang/crystal/pull/6514), thanks @asterite) - -### Macros - -- Add `Generic#resolve` and `Generic#resolve?` macro methods. ([#6617](https://github.com/crystal-lang/crystal/pull/6617), thanks @asterite) - -## Standard library - -- Fixed `v1`, `v2`, `v3`, `v4`, `v5` methods of `UUID`. ([#6952](https://github.com/crystal-lang/crystal/pull/6952), thanks @r00ster91) -- Fixed multiple docs typos and phrasing in multiple places. ([#6778](https://github.com/crystal-lang/crystal/pull/6778), [#6963](https://github.com/crystal-lang/crystal/pull/6963), thanks @r00ster91) -- Fixes `Pointer`/`UInt` subtraction. ([#6994](https://github.com/crystal-lang/crystal/pull/6994), thanks @damaxwell) -- Add stack overflow detection. ([#6928](https://github.com/crystal-lang/crystal/pull/6928), [#6995](https://github.com/crystal-lang/crystal/pull/6995), thanks @damaxwell) -- Add caller file and line to `Nil#not_nil!`. ([#6712](https://github.com/crystal-lang/crystal/pull/6712), thanks @yeeunmariakim) -- Restrict `Enum#parse`/`Enum#parse?` to `String` arguments. ([#6654](https://github.com/crystal-lang/crystal/pull/6654), thanks @vladfaust) -- Refactor and unify printing exceptions from within fibers. ([#6594](https://github.com/crystal-lang/crystal/pull/6594), thanks @Sija) -- Improve docs on properties generated by `property?`. ([#6682](https://github.com/crystal-lang/crystal/pull/6682), thanks @epergo) -- Add docs to top level namespace constants. ([#6971](https://github.com/crystal-lang/crystal/pull/6971), thanks @r00ster91) - -### Macros - -- Fix typos in `StringLiteral#gsub` and `#tr` errors. ([#6925](https://github.com/crystal-lang/crystal/pull/6925), thanks @r00ster91) - -### Numeric - -- **(breaking-change)** Disallow `rand` with zero value. ([#6686](https://github.com/crystal-lang/crystal/pull/6686), thanks @oprypin) -- **(breaking-change)** Let `==` and `!=` compare the values instead of bits when dealing with signed vs unsigned integers. ([#6689](https://github.com/crystal-lang/crystal/pull/6689), thanks @asterite) -- Fixed `Int#downto` with unsigned int. ([#6678](https://github.com/crystal-lang/crystal/pull/6678), thanks @gmarcais) -- Add wrapping arithmetics operators `&+` `&-` `&*`. ([#6890](https://github.com/crystal-lang/crystal/pull/6890), thanks @bcardiff) -- Add floor divisions operator `Int#//` and `Float#//`. ([#6891](https://github.com/crystal-lang/crystal/pull/6891), thanks @bcardiff) -- Add random support for `BigInt`. ([#6687](https://github.com/crystal-lang/crystal/pull/6687), thanks @oprypin) -- Add docs related to `Float::Printer::*`. ([#5438](https://github.com/crystal-lang/crystal/pull/5438), thanks @Sija) - -### Text - -- Add `String::Builder#chomp!` returns self. ([#6583](https://github.com/crystal-lang/crystal/pull/6583), thanks @Sija) -- Add `:default` to colorize and document `ColorRGB`, `Color256`. ([#6427](https://github.com/crystal-lang/crystal/pull/6427), thanks @r00ster91) -- Add `String::Formatter` support for `c` flag and improve docs. ([#6758](https://github.com/crystal-lang/crystal/pull/6758), thanks @r00ster91) - -### Collections - -- **(breaking-change)** Replace `Indexable#at` with `#fetch`. Remove `Hash#fetch(key)` as alias of `Hash#[]`. ([#6296](https://github.com/crystal-lang/crystal/pull/6296), thanks @AlexWayfer) -- Add `Hash/Indexable#dig/dig?`. ([#6719](https://github.com/crystal-lang/crystal/pull/6719), thanks @Sija) -- Add `Iterator.chain` to chain array of iterators. ([#6570](https://github.com/crystal-lang/crystal/pull/6570), thanks @xqyww123) -- Add `NamedTuple#to_h` over empty tuples. ([#6628](https://github.com/crystal-lang/crystal/pull/6628), thanks @icyleaf) -- Optimize `Indexable#join` when all elements are strings. ([#6635](https://github.com/crystal-lang/crystal/pull/6635), thanks @asterite) -- Optimize `Array#skip`. ([#6946](https://github.com/crystal-lang/crystal/pull/6946), thanks @asterite) - -### Serialization - -- Fixed `YAML::Schema::FailSafe.parse` and `parse_all`. ([#6790](https://github.com/crystal-lang/crystal/pull/6790), thanks @r00ster91) -- Fixed order of `xmlns` and prefix in `XML::Builder#namespace`. ([#6743](https://github.com/crystal-lang/crystal/pull/6743), thanks @yeeunmariakim) -- Fixed `CSV.build` quoting of `Char` and `Symbol`. ([#6904](https://github.com/crystal-lang/crystal/pull/6904), thanks @maiha) -- Fixed docs for `JSON::Serializable`. ([#6950](https://github.com/crystal-lang/crystal/pull/6950), thanks @Heaven31415) -- Add `XML::Attributes#delete`. ([#6910](https://github.com/crystal-lang/crystal/pull/6910), thanks @joenas) -- Add ability to quote values always in `CSV.build`. ([#6723](https://github.com/crystal-lang/crystal/pull/6723), thanks @maiha) -- Refactor how empty properties are handled in `JSON::Serializable` and `YAML::Serializable`. ([#6539](https://github.com/crystal-lang/crystal/pull/6539), thanks @r00ster91) - -### Time - -- **(breaking-change)** Rename `Time#epoch` to `Time#to_unix`. Also `#epoch_ms` to `#to_unix_ms`, and `#epoch_f` to `#to_unix_f`. ([#6662](https://github.com/crystal-lang/crystal/pull/6662), thanks @straight-shoota) -- Fixed spec for `Time::Location.load_local` with `TZ=nil`. ([#6740](https://github.com/crystal-lang/crystal/pull/6740), thanks @straight-shoota) -- Add support for ISO calendar week to `Time`. ([#6681](https://github.com/crystal-lang/crystal/pull/6681), thanks @straight-shoota) -- Add `Time::Format` support for `%G`, `%g`, `%V`. ([#6681](https://github.com/crystal-lang/crystal/pull/6681), thanks @straight-shoota) -- Add `Time::Location` loader support for Windows. ([#6363](https://github.com/crystal-lang/crystal/pull/6363), thanks @straight-shoota) -- Add `Time#to_local_in` to change time zone while keeping wall clock. ([#6572](https://github.com/crystal-lang/crystal/pull/6572), thanks @straight-shoota) -- Add `Time::UNIX_EPOCH` and drop private `UNIX_SECONDS` constant. ([#6908](https://github.com/crystal-lang/crystal/pull/6908), thanks @j8r) -- Change `Time::DayOfWeek` to ISO ordinal numbering based on `Monday = 1`. ([#6555](https://github.com/crystal-lang/crystal/pull/6555), thanks @straight-shoota) -- Refactor time specs. ([#6574](https://github.com/crystal-lang/crystal/pull/6574), thanks @straight-shoota) -- Add docs for singular method aliases, add `Int#microsecond` alias. ([#6297](https://github.com/crystal-lang/crystal/pull/6297), thanks @Sija) - -### Files - -- **(breaking-change)** Remove `Tempfile`. Use `File.tempfile` or `File.tempname`. ([#6485](https://github.com/crystal-lang/crystal/pull/6485), thanks @straight-shoota) -- Fixed missing closed status check of FDs when creating a subprocess. ([#6641](https://github.com/crystal-lang/crystal/pull/6641), thanks @Timbus) -- Fixed `ChecksumReader.write` error message. ([#6889](https://github.com/crystal-lang/crystal/pull/6889), thanks @r00ster91) -- Add `File#delete`, `Dir#tempdir` and improve `File` docs. ([#6485](https://github.com/crystal-lang/crystal/pull/6485), thanks @straight-shoota) -- Add `File#fsync` to flush all data written into the file to the disk device. ([#6793](https://github.com/crystal-lang/crystal/pull/6793), thanks @carlhoerberg) -- Add `DEVNULL` to docs. ([#6642](https://github.com/crystal-lang/crystal/pull/6642), thanks @r00ster91) -- Improve checks for FreeBSD version due to breaking API changes. ([#6629](https://github.com/crystal-lang/crystal/pull/6629), thanks @myfreeweb) -- Improve performance of `Zlib::Reader`, `Gzip::Reader` and `Flate::Reader` by including `IO::Buffered`. ([#6916](https://github.com/crystal-lang/crystal/pull/6916), thanks @asterite) -- Refactor `Crystal::System::FileDescriptor` to use `@fd` ivar directly. ([#6703](https://github.com/crystal-lang/crystal/pull/6703), thanks @straight-shoota) -- Refactor `{Zlib,Gzip,Flate}::Reader#unbuffered_rewind` to use `check_open`. ([#6958](https://github.com/crystal-lang/crystal/pull/6958), thanks @Sija) - -### Networking - -- **(breaking-change)** Remove deprecated alias `HTTP::Server#bind_ssl`. Use `HTTP::Server#bind_tls`. ([#6699](https://github.com/crystal-lang/crystal/pull/6699), thanks @straight-shoota) -- Add `Socket::Address#pretty_print` and `#inspect`. ([#6704](https://github.com/crystal-lang/crystal/pull/6704), thanks @straight-shoota) -- Add `Socket::IPAddress` loopback, unspecified and broadcast methods/constants. ([#6710](https://github.com/crystal-lang/crystal/pull/6710), thanks @straight-shoota) -- Fixed `Socket#reuse_port?` if `SO_REUSEPORT` is not supported. ([#6706](https://github.com/crystal-lang/crystal/pull/6706), thanks @straight-shoota) -- Fixed `TCPServer` handling of `reuse_port`. ([#6940](https://github.com/crystal-lang/crystal/pull/6940), thanks @RX14) -- Add docs to demonstrate parameters for `HTTP::Client`. ([#5145](https://github.com/crystal-lang/crystal/pull/5145), thanks @HCLarsen) -- Add docs examples to `Socket::Server#accept`. ([#6705](https://github.com/crystal-lang/crystal/pull/6705), thanks @straight-shoota) -- Refactor `socket_spec.cr` into separate files. ([#6700](https://github.com/crystal-lang/crystal/pull/6700), thanks @straight-shoota) -- Refactor specs of `HTTP::Client` to remove inheritance for test server. ([#6909](https://github.com/crystal-lang/crystal/pull/6909), thanks @straight-shoota) -- Improve specs for `HTTP::Server#close`. ([#5958](https://github.com/crystal-lang/crystal/pull/5958), thanks @straight-shoota) -- Improve specs for socket. ([#6711](https://github.com/crystal-lang/crystal/pull/6711), thanks @straight-shoota) - -### Crypto - -- Fixed OpenSSL bindings to work with LibreSSL. ([#6917](https://github.com/crystal-lang/crystal/pull/6917), thanks @LVMBDV) -- Add support for OpenSSL 1.1.1. ([#6738](https://github.com/crystal-lang/crystal/pull/6738), thanks @ysbaddaden) - -### Concurrency - -- Improve POSIX threads integration regarding locking, error and resource management. ([#6944](https://github.com/crystal-lang/crystal/pull/6944), thanks @ysbaddaden) -- Remove unintended public methods from `Channel`. ([#6714](https://github.com/crystal-lang/crystal/pull/6714), thanks @asterite) -- Refactor `Fiber`/`Scheduler` to isolate responsibilities. ([#6897](https://github.com/crystal-lang/crystal/pull/6897), thanks @ysbaddaden) -- Refactor specs that relied on `Fiber.yield` behavior. ([#6953](https://github.com/crystal-lang/crystal/pull/6953), thanks @ysbaddaden) - -### System - -- Fixed fork and signal child handlers. ([#6426](https://github.com/crystal-lang/crystal/pull/6426), thanks @ysbaddaden) -- Use blocking `IO` on a TTY if it can't be reopened. ([#6660](https://github.com/crystal-lang/crystal/pull/6660), thanks @Timbus) -- Refactor `Process` in preparation for Windows support. ([#6744](https://github.com/crystal-lang/crystal/pull/6744), thanks @RX14) - -### Spec - -- Allow `pending` to be used without blocks. ([#6732](https://github.com/crystal-lang/crystal/pull/6732), thanks @tswicegood) -- Add `be_empty` expectation. ([#6614](https://github.com/crystal-lang/crystal/pull/6614), thanks @mamantoha) -- Add specs for expectation methods. ([#6512](https://github.com/crystal-lang/crystal/pull/6512), thanks @rodrigopinto) - -## Compiler - -- Fixed don't "ambiguous match" if there's an exact match. ([#6618](https://github.com/crystal-lang/crystal/pull/6618), thanks @asterite) -- Fixed allow annotations inside enums. ([#6713](https://github.com/crystal-lang/crystal/pull/6713), thanks @asterite) -- Fixed `super` inside macros will honor arguments. ([#6638](https://github.com/crystal-lang/crystal/pull/6638), thanks @asterite) -- Fixed guessed ivar type from splat arguments. ([#6648](https://github.com/crystal-lang/crystal/pull/6648), thanks @MakeNowJust) -- Fixed `ASTNode#to_s` of non-unary operator call without argument. ([#6538](https://github.com/crystal-lang/crystal/pull/6538), thanks @MakeNowJust) -- Fixed `ASTNode#to_s` for multiline macro expression. ([#6666](https://github.com/crystal-lang/crystal/pull/6666), thanks @MakeNowJust) -- Fixed `ASTNode#to_s` for `{% verbatim do %} ... {% end %}`. ([#6665](https://github.com/crystal-lang/crystal/pull/6665), thanks @MakeNowJust) -- Fixed empty case statement normalization. ([#6915](https://github.com/crystal-lang/crystal/pull/6915), thanks @straight-shoota) -- Fixed codegen of tuple elements with unreachable elements. ([#6659](https://github.com/crystal-lang/crystal/pull/6659), thanks @MakeNowJust) -- Fixed parsing of `//` corner cases. ([#6927](https://github.com/crystal-lang/crystal/pull/6927), thanks @bcardiff) -- Fixed recursive block expansion check for non `ProcNotation` restriction. ([#6932](https://github.com/crystal-lang/crystal/pull/6932), thanks @MakeNowJust) -- Fixed corner case of expressions not typed on main phase but typed on cleanup phase. ([#6720](https://github.com/crystal-lang/crystal/pull/6720), thanks @MakeNowJust) -- Improve error traces regarding `return`, `next` and `break`. ([#6633](https://github.com/crystal-lang/crystal/pull/6633), thanks @asterite) -- Add resolve generics typenodes in macros. ([#6617](https://github.com/crystal-lang/crystal/pull/6617), thanks @asterite) -- Add support for multiple output values in inline asm. ([#6680](https://github.com/crystal-lang/crystal/pull/6680), thanks @RX14) -- Improve parsing of `asm` operands. ([#6688](https://github.com/crystal-lang/crystal/pull/6688), thanks @RX14) -- Refactor rescue block codegen for Windows. ([#6649](https://github.com/crystal-lang/crystal/pull/6649), thanks @RX14) - -## Tools - -- Improve installation section in README template. ([#6914](https://github.com/crystal-lang/crystal/pull/6914), [#6942](https://github.com/crystal-lang/crystal/pull/6942), thanks @r00ster91) -- Improve contributors section in README template. ([#7005](https://github.com/crystal-lang/crystal/pull/7005), thanks @r00ster91) - -### Formatter - -- Fixed formatting of `{% verbatim do %} ... {% end %}` outside macro. ([#6667](https://github.com/crystal-lang/crystal/pull/6667), thanks @MakeNowJust) -- Fixed formatting of `//` corner cases. ([#6927](https://github.com/crystal-lang/crystal/pull/6927), thanks @bcardiff) -- Improve formatting of `asm` operands. ([#6688](https://github.com/crystal-lang/crystal/pull/6688), thanks @RX14) - -### Doc generator - -- Add support for comments after `:nodoc:` marker. ([#6627](https://github.com/crystal-lang/crystal/pull/6627), thanks @Sija) -- Fixed browser performance issue with blur filter. ([#6764](https://github.com/crystal-lang/crystal/pull/6764), thanks @girng) -- Accessibility improvement in search field. ([#6926](https://github.com/crystal-lang/crystal/pull/6926), thanks @jodylecompte) - -## Others - -- CI improvements and housekeeping. ([#6658](https://github.com/crystal-lang/crystal/pull/6658), [#6739](https://github.com/crystal-lang/crystal/pull/6739), [#6930](https://github.com/crystal-lang/crystal/pull/6930), thanks @bcardiff, @RX14) -- Add `VERSION` file and support for specifying the build commit. ([#6966](https://github.com/crystal-lang/crystal/pull/6966), thanks @bcardiff) -- Add support for specifying the build date. ([#6788](https://github.com/crystal-lang/crystal/pull/6788), thanks @peterhoeg) -- Update Contributing section in `README.md`. ([#6911](https://github.com/crystal-lang/crystal/pull/6911), thanks @r00ster91) - -# 0.26.1 (2018-08-27) - -## Language changes - -- **(breaking-change)** Make `self` to be eager evaluated when including modules. ([#6557](https://github.com/crystal-lang/crystal/pull/6557), thanks @bcardiff) - -### Macros - -- Add `accepts_block?` macro method to `Def`. ([#6604](https://github.com/crystal-lang/crystal/pull/6604), thanks @willhbr) - -## Standard library - -### Macros - -- Fixed `Object#def_hash` can receive symbols. ([#6531](https://github.com/crystal-lang/crystal/pull/6531), thanks @Sija) - -### Collections - -- Add `Hash#transform_keys` and `Hash#transform_values`. ([#4385](https://github.com/crystal-lang/crystal/pull/4385), thanks @deepj) - -### Serialization - -- Fixed `JSON::Serializable` and `YAML::Serializable` clashing with custom initializers. ([#6458](https://github.com/crystal-lang/crystal/pull/6458), thanks @kostya) - -### Time - -- Fixed docs for `Time::Format`. ([#6578](https://github.com/crystal-lang/crystal/pull/6578), thanks @straight-shoota) - -### Files - -- Fixed zlib handling of buffer error. ([#6610](https://github.com/crystal-lang/crystal/pull/6610), thanks @asterite) - -### Networking - -- **(deprecate)** `HTTP::Server#bind_ssl` in favor of `HTTP::Server#bind_tls`. ([#6551](https://github.com/crystal-lang/crystal/pull/6551), thanks @bcardiff) -- Add tls scheme to `HTTP::Server#bind`. ([#6533](https://github.com/crystal-lang/crystal/pull/6533), thanks @straight-shoota) -- Fixed `HTTP::Server` crash with self-signed certificate. ([#6590](https://github.com/crystal-lang/crystal/pull/6590), thanks @bcardiff) -- Refactor `HTTP::Server` specs to use free ports. ([#6530](https://github.com/crystal-lang/crystal/pull/6530), thanks @straight-shoota) - -### System - -- Improve `STDIN`/`STDOUT`/`STDERR` handling to avoid breaking other programs. ([#6518](https://github.com/crystal-lang/crystal/pull/6518), thanks @Timbus) - -### Spec - -- Fixed `DotFormatter` to flush after every spec. ([#6562](https://github.com/crystal-lang/crystal/pull/6562), thanks @asterite) -- Add support for Windows. ([#6497](https://github.com/crystal-lang/crystal/pull/6497), thanks @RX14) - -## Compiler - -- Fixed evaluate yield expressions in macros. ([#6587](https://github.com/crystal-lang/crystal/pull/6587), thanks @asterite) -- Fixed presence check of named argument via external name. ([#6560](https://github.com/crystal-lang/crystal/pull/6560), thanks @asterite) -- Fixed parser error on `break when`. ([#6509](https://github.com/crystal-lang/crystal/pull/6509), thanks @asterite) -- Fixed `~` methods are now able to be called as `foo.~`. ([#6541](https://github.com/crystal-lang/crystal/pull/6541), thanks @MakeNowJust) -- Fixed parsing newline after macro control expression. ([#6607](https://github.com/crystal-lang/crystal/pull/6607), thanks @asterite) -- Refactor use enum instead of hardcoded string values for emit kinds. ([#6515](https://github.com/crystal-lang/crystal/pull/6515), thanks @bew) - -## Tools - -### Formatter - -- Fixed formatting of newline before `&.method` in call. ([#6535](https://github.com/crystal-lang/crystal/pull/6535), thanks @MakeNowJust) -- Fixed formatting of empty heredoc. ([#6567](https://github.com/crystal-lang/crystal/pull/6567), thanks @MakeNowJust) -- Fixed formatting of string literal in interpolation. ([#6568](https://github.com/crystal-lang/crystal/pull/6568), thanks @MakeNowJust) -- Fixed formatting of comments in case when. ([#6595](https://github.com/crystal-lang/crystal/pull/6595), thanks @asterite) - -### Doc generator - -- Add Menlo font family and fix ordering. ([#6602](https://github.com/crystal-lang/crystal/pull/6602), thanks @slice) - -### Playground - -- Fixed internal link. ([#6596](https://github.com/crystal-lang/crystal/pull/6596), thanks @omarroth) - -## Others - -- CI improvements and housekeeping. ([#6550](https://github.com/crystal-lang/crystal/pull/6550), [#6612](https://github.com/crystal-lang/crystal/pull/6612), thanks @bcardiff) -- Add `pkg-config` as Linux package dependency. ([distribution-scripts#16](https://github.com/crystal-lang/distribution-scripts/pull/16), thanks @bcardiff) - -# 0.26.0 (2018-08-09) - -## Language changes - -- **(breaking-change)** Revert do not collapse unions for sibling types. ([#6351](https://github.com/crystal-lang/crystal/pull/6351), thanks @asterite) -- **(breaking-change)** Constant lookup context in macro is now lexical. ([#5354](https://github.com/crystal-lang/crystal/pull/5354), thanks @MakeNowJust) -- **(breaking-change)** Evaluate instance var initializers at the metaclass level (ie: disallow using `self`). ([#6414](https://github.com/crystal-lang/crystal/pull/6414), thanks @asterite) -- **(breaking-change)** Add `//` operator parsing. NB: No behaviour is assigned to this operator yet. ([#6470](https://github.com/crystal-lang/crystal/pull/6470), thanks @bcardiff) -- Add `&+` `&-` `&*` `&**` operators parsing. NB: No behaviour is assigned to these operators yet. ([#6329](https://github.com/crystal-lang/crystal/pull/6329), thanks @bcardiff) -- Add support for empty `case` without `when`. ([#6367](https://github.com/crystal-lang/crystal/pull/6367), thanks @straight-shoota) - -### Macros - -- Add `pp!` and `p!` macro methods. ([#6374](https://github.com/crystal-lang/crystal/pull/6374), [#6476](https://github.com/crystal-lang/crystal/pull/6476), thanks @straight-shoota) - -## Standard library - -- Fix docs for `Pointer`. ([#6494](https://github.com/crystal-lang/crystal/pull/6494), thanks @fxn) -- Fix docs of `UUID` enums. ([#6496](https://github.com/crystal-lang/crystal/pull/6496), thanks @r00ster91) - -### Numeric - -- Fixed `Random#rand(Range(Float, Float))` to return `Float`. ([#6445](https://github.com/crystal-lang/crystal/pull/6445), thanks @straight-shoota) -- Add docs of big module overloads. ([#6336](https://github.com/crystal-lang/crystal/pull/6336), thanks @laginha87) - -### Text - -- **(breaking-change)** `String#from_utf16(pointer : Pointer(UInt16))` returns now `{String, Pointer(UInt16)}`. ([#6333](https://github.com/crystal-lang/crystal/pull/6333), thanks @straight-shoota) -- Add support for unicode 11.0.0. ([#6505](https://github.com/crystal-lang/crystal/pull/6505), thanks @asterite) -- Add an optional argument to `String#check_no_null_byte` to customize error message. ([#6333](https://github.com/crystal-lang/crystal/pull/6333), thanks @straight-shoota) -- Add `ECR.render` for rendering directly as `String`. ([#6371](https://github.com/crystal-lang/crystal/pull/6371), thanks @straight-shoota) -- Fix docs for `Char` ([#6487](https://github.com/crystal-lang/crystal/pull/6487), thanks @r00ster91) - -### Collections - -- Add docs for `StaticArray`. ([#6404](https://github.com/crystal-lang/crystal/pull/6404), [#6488](https://github.com/crystal-lang/crystal/pull/6488), thanks @straight-shoota, @r00ster91, @hinrik) -- Refactor `Array#concat`. ([#6493](https://github.com/crystal-lang/crystal/pull/6493), thanks @fxn) - -### Serialization - -- **(breaking-change)** Add a maximum nesting level to prevent stack overflow on `YAML::Builder` and `JSON::Builder`. ([#6322](https://github.com/crystal-lang/crystal/pull/6322), thanks @asterite) -- Fixed compatibility for libyaml 0.2.1 regarding document end marker `...`. ([#6287](https://github.com/crystal-lang/crystal/pull/6287), thanks @straight-shoota) -- Add methods and options for pull parsing or hybrid parsing to `XML::Reader`. ([#5740](https://github.com/crystal-lang/crystal/pull/5740), [#6332](https://github.com/crystal-lang/crystal/pull/6332), thanks @felixbuenemann) -- Fixed docs for `JSON::Any`, `JSON::Serialization` and `YAML::Serialization`. ([#6460](https://github.com/crystal-lang/crystal/pull/6460), [#6491](https://github.com/crystal-lang/crystal/pull/6491), thanks @delef, @bmulvihill) - - -### Time - -- **(breaking-change)** Make location a required argument for `Time.parse`. ([#6369](https://github.com/crystal-lang/crystal/pull/6369), thanks @straight-shoota) -- Add `Time.parse!`, `Time.parse_utc`, `Time.parse_local`. ([#6369](https://github.com/crystal-lang/crystal/pull/6369), thanks @straight-shoota) -- Fix docs comment missing ([#6387](https://github.com/crystal-lang/crystal/pull/6387), thanks @faustinoaq) - -### Files - -- **(breaking-change)** Remove `File.each_line` method that returns an iterator. Use `IO#each_line`. ([#6301](https://github.com/crystal-lang/crystal/pull/6301), thanks @asterite) -- Fixed `File.join` when path separator is a component argument. ([#6328](https://github.com/crystal-lang/crystal/pull/6328), thanks @icyleaf) -- Fixed `Dir.glob` can now list broken symlinks. ([#6466](https://github.com/crystal-lang/crystal/pull/6466), thanks @straight-shoota) -- Add `File` and `Dir` support for Windows. ([#5623](https://github.com/crystal-lang/crystal/pull/5623), thanks @RX14) - -### Networking - -- **(breaking-change)** Drop `HTTP::Server#tls` in favor of `HTTP::Server#bind_ssl`. ([#5960](https://github.com/crystal-lang/crystal/pull/5960), thanks @straight-shoota) -- **(breaking-change)** Rename alias `HTTP::Handler::Proc` to `HTTP::Handler::HandlerProc`. ([#6453](https://github.com/crystal-lang/crystal/pull/6453), thanks @jwoertink) -- Fixed `Socket#accept?` base implementation. ([#6277](https://github.com/crystal-lang/crystal/pull/6277), thanks @ysbaddaden) -- Fixed performance issue due to unbuffered `IO` read. `IO#sync` only affect writes, introduce `IO#read_buffering?`. ([#6304](https://github.com/crystal-lang/crystal/pull/6304), [#6474](https://github.com/crystal-lang/crystal/pull/6474), thanks @asterite, @bcardiff) -- Fixed handling of closed state in `HTTP::Server::Response`. ([#6477](https://github.com/crystal-lang/crystal/pull/6477), thanks @straight-shoota) -- Fixed change encoding name comparison to be case insensitive for UTF-8. ([#6355](https://github.com/crystal-lang/crystal/pull/6355), thanks @asterite) -- Fixed support for quoted charset value in HTTP. ([#6354](https://github.com/crystal-lang/crystal/pull/6354), thanks @asterite) -- Fixed docs regarding udp example on `Socket::Addrinfo`. ([#6388](https://github.com/crystal-lang/crystal/pull/6388), thanks @faustinoaq) -- Fixed `HTTP::Client` will set `connection: close` header on one-shot requests. ([#6410](https://github.com/crystal-lang/crystal/pull/6410), thanks @asterite) -- Fixed `OpenSSL::Digest` for multibyte strings. ([#6471](https://github.com/crystal-lang/crystal/pull/6471), thanks @RX14) -- Fixed missing `Host` header when using `HTTP::Client#exec`. ([#6481](https://github.com/crystal-lang/crystal/pull/6481), thanks @straight-shoota) -- Add `HTTP::Server#bind(URI|String)` that infers protocol from scheme. ([#6500](https://github.com/crystal-lang/crystal/pull/6500), thanks @straight-shoota) -- Add `HTTP::Params.new` and `HTTP::Params#empty?`. ([#6241](https://github.com/crystal-lang/crystal/pull/6241), thanks @icyleaf) -- Add support for multiple Etags in `If-None-Match` header for `HTTP::Request` and `HTTP::StaticFileHandler`. ([#6219](https://github.com/crystal-lang/crystal/pull/6219), thanks @straight-shoota) -- Add IDNs normalization to punycode in `OpenSSL::SSL::Socket`. ([#6306](https://github.com/crystal-lang/crystal/pull/6306), thanks @paulkass) -- Add `application/wasm` to the default MIME types of `HTTP::StaticFileHandler`. ([#6377](https://github.com/crystal-lang/crystal/pull/6377), thanks @MakeNowJust) -- Add `URI#absolute?` and `URI#relative?`. ([#6311](https://github.com/crystal-lang/crystal/pull/6311), thanks @mamantoha) - -### Crypto - -- Fixed `Crypto::Bcrypt::Password#==` was hiding `Reference#==(other)`. ([#6356](https://github.com/crystal-lang/crystal/pull/6356), thanks @straight-shoota) - -### Concurrency - -- Fixed `Atomic#swap` with reference types. ([#6428](https://github.com/crystal-lang/crystal/pull/6428), thanks @Exilor) - -### System - -- Fixed raise `Errno` if `Process.new` fails to exec. ([#6501](https://github.com/crystal-lang/crystal/pull/6501), thanks @straight-shoota, @lbguilherme) -- Add support for `WinError` UTF-16 string messages. ([#6442](https://github.com/crystal-lang/crystal/pull/6442), thanks @straight-shoota) -- Refactor platform specifics from `ENV` to `Crystal::System::Env` and implement for Windows. ([#6333](https://github.com/crystal-lang/crystal/pull/6333), [#6499](https://github.com/crystal-lang/crystal/pull/6499), thanks @straight-shoota) - -### Spec - -- Add [TAP](https://testanything.org/) formatter to spec suite. ([#6286](https://github.com/crystal-lang/crystal/pull/6286), thanks @straight-shoota) - -## Compiler - -- Fixed named arguments expansion from double splat clash with local variable names. ([#6378](https://github.com/crystal-lang/crystal/pull/6378), thanks @asterite) -- Fixed auto assigned ivars arguments expansions when clash with keywords. ([#6379](https://github.com/crystal-lang/crystal/pull/6379), thanks @asterite) -- Fixed resulting type of union of tuple metaclasses. ([#6342](https://github.com/crystal-lang/crystal/pull/6342), thanks @asterite) -- Fixed ICE when using unbound type parameter inside generic type. ([#6292](https://github.com/crystal-lang/crystal/pull/6292), thanks @asterite) -- Fixed ICE when using unions of metaclasses. ([#6307](https://github.com/crystal-lang/crystal/pull/6307), thanks @asterite) -- Fixed ICE related to literal type guessing and generic types hierarchy. ([#6341](https://github.com/crystal-lang/crystal/pull/6341), thanks @asterite) -- Fixed ICE related to `not` and inlinable values. ([#6452](https://github.com/crystal-lang/crystal/pull/6452), thanks @asterite) -- Fixed rebind variables type in while condition after analyzing its body. ([#6295](https://github.com/crystal-lang/crystal/pull/6295), thanks @asterite) -- Fixed corner cases regarding automatic casts and method instantiation. ([#6284](https://github.com/crystal-lang/crystal/pull/6284), thanks @asterite) -- Fixed parsing of `\A` (and others) inside `%r{...}` inside macros. ([#6282](https://github.com/crystal-lang/crystal/pull/6282), thanks @asterite) -- Fixed parsing of of named tuple inside generic type arguments. ([#6413](https://github.com/crystal-lang/crystal/pull/6413), thanks @asterite) -- Fixed disallow cast from module class to virtual metaclass. ([#6320](https://github.com/crystal-lang/crystal/pull/6320), thanks @asterite) -- Fixed disallow `return` inside a constant's value. ([#6347](https://github.com/crystal-lang/crystal/pull/6347), thanks @asterite) -- Fixed debug info for closured self. ([#6346](https://github.com/crystal-lang/crystal/pull/6346), thanks @asterite) -- Fixed parsing error of newline before closing macro. ([#6382](https://github.com/crystal-lang/crystal/pull/6382), thanks @asterite) -- Fixed missing error if constant has `NoReturn` type. ([#6411](https://github.com/crystal-lang/crystal/pull/6411), thanks @asterite) -- Fixed give proper error when doing sizeof uninstantiated generic type. ([#6418](https://github.com/crystal-lang/crystal/pull/6418), thanks @asterite) -- Fixed private aliases at top-level are now considered private. ([#6432](https://github.com/crystal-lang/crystal/pull/6432), thanks @asterite) -- Fixed setters with multiple arguments as now disallowed. ([#6324](https://github.com/crystal-lang/crystal/pull/6324), thanks @maxfierke) -- Fixed type var that resolves to number in restriction didn't work. ([#6504](https://github.com/crystal-lang/crystal/pull/6504), thanks @asterite) -- Add support for class variables in generic classes. ([#6348](https://github.com/crystal-lang/crystal/pull/6348), thanks @asterite) -- Add support for exception handling in Windows (SEH). ([#6419](https://github.com/crystal-lang/crystal/pull/6419), thanks @RX14) -- Refactor codegen of binary operators. ([#6330](https://github.com/crystal-lang/crystal/pull/6330), thanks @bcardiff) -- Refactor use `JSON::Serializable` instead of `JSON.mapping`. ([#6308](https://github.com/crystal-lang/crystal/pull/6308), thanks @kostya) -- Refactor `Crystal::Call#check_visibility` and extract type methods. ([#6484](https://github.com/crystal-lang/crystal/pull/6484), thanks @asterite, @bcardiff) -- Change how metaclasses are shown. Use `Foo.class` instead of `Foo:Class`. ([#6439](https://github.com/crystal-lang/crystal/pull/6439), thanks @RX14) - -## Tools - -- Flatten project structure created by `crystal init`. ([#6317](https://github.com/crystal-lang/crystal/pull/6317), thanks @straight-shoota) - -### Formatter - -- Fixed formatting of `{ {1}.foo, ...}` like expressions. ([#6300](https://github.com/crystal-lang/crystal/pull/6300), thanks @asterite) -- Fixed formatting of `when` with numbers. Use right alignment only if all are number literals. ([#6392](https://github.com/crystal-lang/crystal/pull/6392), thanks @MakeNowJust) -- Fixed formatting of comment in case's else. ([#6393](https://github.com/crystal-lang/crystal/pull/6393), thanks @MakeNowJust) -- Fixed code fence when language is not crystal will not be formatted. ([#6424](https://github.com/crystal-lang/crystal/pull/6424), thanks @asterite) - -### Doc generator - -- Add line numbers at link when there are duplicated filenames in "Defined in:" section. ([#6280](https://github.com/crystal-lang/crystal/pull/6280), [#6489](https://github.com/crystal-lang/crystal/pull/6489), thanks @r00ster91) -- Fix docs navigator not scrolling into open type on page load. ([#6420](https://github.com/crystal-lang/crystal/pull/6420), thanks @soanvig) - -## Others - -- Fixed `system_spec` does no longer emit errors messages on BSD platforms. ([#6289](https://github.com/crystal-lang/crystal/pull/6289), thanks @jcs) -- Fixed compilation issue when running spec against compiler and std together. ([#6312](https://github.com/crystal-lang/crystal/pull/6312), thanks @straight-shoota) -- Add support for LLVM 6.0. ([#6381](https://github.com/crystal-lang/crystal/pull/6381), [#6380](https://github.com/crystal-lang/crystal/pull/6380), [#6383](https://github.com/crystal-lang/crystal/pull/6383), thanks @felixbuenemann) -- CI improvements and housekeeping. ([#6313](https://github.com/crystal-lang/crystal/pull/6313), [#6337](https://github.com/crystal-lang/crystal/pull/6337), [#6407](https://github.com/crystal-lang/crystal/pull/6407), [#6408](https://github.com/crystal-lang/crystal/pull/6408), [#6315](https://github.com/crystal-lang/crystal/pull/6315), thanks @bcardiff, @MakeNowJust, @r00ster91, @maiha) - -# 0.25.1 (2018-06-27) - -## Standard library - -### Macros -- Fixed `Object.delegate` is now able to be used with `[]=` methods. ([#6178](https://github.com/crystal-lang/crystal/pull/6178), thanks @straight-shoota) -- Fixed `p!` `pp!` are now able to be used with tuples. ([#6244](https://github.com/crystal-lang/crystal/pull/6244), thanks @bcardiff) -- Add `#copy_with` method to structs generated by `record` macro. ([#5736](https://github.com/crystal-lang/crystal/pull/5736), thanks @chris-baynes) -- Add docs for `ArrayLiteral#push` and `#unshift`. ([#6232](https://github.com/crystal-lang/crystal/pull/6232), thanks @MakeNowJust) - -### Collections -- Add docs for `Indexable#zip` and `#zip?` methods. ([#5734](https://github.com/crystal-lang/crystal/pull/5734), thanks @rodrigopinto) - -### Serialization -- Add `#dup` and `#clone` for `JSON::Any` and `YAML::Any`. ([6266](https://github.com/crystal-lang/crystal/pull/6266), thanks @asterite) -- Add docs example of nesting mappings to `YAML.builder`. ([#6097](https://github.com/crystal-lang/crystal/pull/6097), thanks @kalinon) - -### Time -- Fixed docs regarding formatting and parsing `Time`. ([#6208](https://github.com/crystal-lang/crystal/pull/6208), [#6214](https://github.com/crystal-lang/crystal/pull/6214), thanks @r00ster91 and @straight-shoota) -- Fixed `Time` internals for future Windows support. ([#6181](https://github.com/crystal-lang/crystal/pull/6181), thanks @RX14) -- Add `Time::Span#microseconds`, `Int#microseconds` and `Float#microseconds`. ([#6272](https://github.com/crystal-lang/crystal/pull/6272), thanks @asterite) -- Add specs. ([#6174](https://github.com/crystal-lang/crystal/pull/6174), thanks @straight-shoota) - -### Files -- Fixed `File.extname` edge case. ([#6234](https://github.com/crystal-lang/crystal/pull/6234), thanks @bcardiff) -- Fixed `FileInfo#flags` return value. ([#6248](https://github.com/crystal-lang/crystal/pull/6248), thanks @fgimian) - -### Networking -- Fixed `IO#write(slice : Bytes)` won't write information if slice is empty. ([#6269](https://github.com/crystal-lang/crystal/pull/6269), thanks @asterite) -- Fixed docs regarding `HTTP::Server#bind_tcp` method. ([#6179](https://github.com/crystal-lang/crystal/pull/6179), [#6233](https://github.com/crystal-lang/crystal/pull/6233), thanks @straight-shoota and @MakeNowJust) -- Add Etag support in `HTTP::StaticFileHandler`. ([#6145](https://github.com/crystal-lang/crystal/pull/6145), thanks @emq) - -### Misc -- Fixed `mmap` usage on OpenBSD 6.3+. ([#6250](https://github.com/crystal-lang/crystal/pull/6250), thanks @jcs) -- Fixed `big/big_int`, `big/big_float`, etc are now able to be included directly. ([#6267](https://github.com/crystal-lang/crystal/pull/6267), thanks @asterite) -- Refactor dependency in `Crystal::Hasher` to avoid load order issues. ([#6184](https://github.com/crystal-lang/crystal/pull/6184), thanks @ysbaddaden) - -## Compiler -- Fixed a leakage of unbounded generic type variable and show error. ([#6128](https://github.com/crystal-lang/crystal/pull/6128), thanks @asterite) -- Fixed error message when lookup of library fails and lib's name contains non-alpha chars. ([#6187](https://github.com/crystal-lang/crystal/pull/6187), thanks @oprypin) -- Fixed integer kind deduction for very large negative numbers. ([#6182](https://github.com/crystal-lang/crystal/pull/6182), thanks @rGradeStd) -- Refactor specs tempfiles and data files usage in favor of portability ([#5951](https://github.com/crystal-lang/crystal/pull/5951), thanks @straight-shoota) -- Improve formatting and information in some compiler error messages. ([#6261](https://github.com/crystal-lang/crystal/pull/6261), thanks @RX14) - -## Tools - -### Formatter -- Fixed crash when semicolon after block paren were present. ([#6192](https://github.com/crystal-lang/crystal/pull/6192), thanks @MakeNowJust) -- Fixed invalid code produced when heredoc and comma were present. ([#6222](https://github.com/crystal-lang/crystal/pull/6222), thanks @straight-shoota and @MakeNowJust) -- Fixed crash when one-liner `begin`/`rescue` were present. ([#6274](https://github.com/crystal-lang/crystal/pull/6274), thanks @asterite) - -### Doc generator -- Fixed JSON export that prevent jumping to constant. ([#6218](https://github.com/crystal-lang/crystal/pull/6218), thanks @straight-shoota) -- Fixed crash when virtual types were reached. ([#6246](https://github.com/crystal-lang/crystal/pull/6246), thanks @bcardiff) - -## Misc - -- CI improvements and housekeeping. ([#6193](https://github.com/crystal-lang/crystal/pull/6193), [#6211](https://github.com/crystal-lang/crystal/pull/6211), [#6209](https://github.com/crystal-lang/crystal/pull/6209), [#6221](https://github.com/crystal-lang/crystal/pull/6221), [#6260](https://github.com/crystal-lang/crystal/pull/6260), thanks @bcardiff, @kostya and @r00ster91) -- Update man page. ([#6259](https://github.com/crystal-lang/crystal/pull/6259), thanks @docelic) - -# 0.25.0 (2018-06-11) - -## New features and breaking changes -- **(breaking-change)** Time zones has been added to `Time`. ([#5324](https://github.com/crystal-lang/crystal/pull/5324), [#5819](https://github.com/crystal-lang/crystal/pull/5819), thanks @straight-shoota) -- **(breaking-change)** Drop `HTTP.rfc1123_date` in favor of `HTTP.format_time` and add time format implementations for ISO-8601, RFC-3339, and RFC-2822. ([#5123](https://github.com/crystal-lang/crystal/pull/5123), thanks @straight-shoota) -- **(breaking-change)** `crystal deps` is removed, use `shards`. ([#5544](https://github.com/crystal-lang/crystal/pull/5544), thanks @asterite) -- **(breaking-change)** `Hash#key` was renamed as `Hash#key_for`. ([#5444](https://github.com/crystal-lang/crystal/pull/5444), thanks @marksiemers) -- **(breaking-change)** `JSON::Any` and `YAML::Any` have been re-implemented solving some inconsistencies and avoiding the usage of recursive aliases (`JSON::Type` and `YAML::Type` have been removed). ([#5183](https://github.com/crystal-lang/crystal/pull/5183), thanks @asterite) -- **(breaking-change)** Multiple heredocs can be used as arguments and methods can be invoked writing them in the initial delimiter, also empty heredocs are now supported. ([#5578](https://github.com/crystal-lang/crystal/pull/5578), [#5602](https://github.com/crystal-lang/crystal/pull/5602), [#6048](https://github.com/crystal-lang/crystal/pull/6048), thanks @asterite and @MakeNowJust) -- **(breaking-change)** Refactor signal handlers and avoid closing pipe at exit. ([#5730](https://github.com/crystal-lang/crystal/pull/5730), thanks @ysbaddaden) -- **(breaking-change)** Improve behaviour of `File.join` with empty path component. ([#5915](https://github.com/crystal-lang/crystal/pull/5915), thanks @straight-shoota) -- **(breaking-change)** Drop `Colorize#push` in favor of `Colorize#surround` and allow nested calls across the stack. ([#4196](https://github.com/crystal-lang/crystal/pull/4196), thanks @MakeNowJust) -- **(breaking-change)** `File.stat` was renamed to `File.info` and a more portable API was implemented. ([#5584](https://github.com/crystal-lang/crystal/pull/5584), [#6161](https://github.com/crystal-lang/crystal/pull/6161), thanks @RX14 and @bcardiff) -- **(breaking-change)** Refactor `HTTP::Server` to bind to multiple addresses. ([#5776](https://github.com/crystal-lang/crystal/pull/5776), [#5959](https://github.com/crystal-lang/crystal/pull/5959), thanks @straight-shoota) -- **(breaking-change)** Remove block argument from `loop`. ([#6026](https://github.com/crystal-lang/crystal/pull/6026), thanks @asterite) -- **(breaking-change)** Do not collapse unions for sibling types. ([#6024](https://github.com/crystal-lang/crystal/pull/6024), thanks @asterite) -- **(breaking-change)** Disallow `typeof` in type restrictions. ([#5192](https://github.com/crystal-lang/crystal/pull/5192), thanks @asterite) -- **(breaking-change)** Perform unbuffered read when `IO::Buffered#sync = true`. ([#5849](https://github.com/crystal-lang/crystal/pull/5849), thanks @RX14) -- **(breaking-change)** Drop `when _` support. ([#6150](https://github.com/crystal-lang/crystal/pull/6150), thanks @MakeNowJust) -- **(breaking-change)** The `DivisionByZero` exception was renamed to `DivisionByZeroError`. ([#5395](https://github.com/crystal-lang/crystal/pull/5395), thanks @sdogruyol) -- A bootstrap Windows port has been added to the standard library. It's not usable for real programs yet. ([#5339](https://github.com/crystal-lang/crystal/pull/5339), [#5484](https://github.com/crystal-lang/crystal/pull/5484), [#5448](https://github.com/crystal-lang/crystal/pull/5448), thanks @RX14) -- Add automatic casts on literals arguments for numbers and enums. ([#6074](https://github.com/crystal-lang/crystal/pull/6074), thanks @asterite) -- Add user defined annotations. ([#6063](https://github.com/crystal-lang/crystal/pull/6063), [#6084](https://github.com/crystal-lang/crystal/pull/6084), [#6106](https://github.com/crystal-lang/crystal/pull/6106), thanks @asterite) -- Add macro verbatim blocks to avoid nested macros. ([#6108](https://github.com/crystal-lang/crystal/pull/6108), thanks @asterite) -- Allow namespaced expressions to define constants eg: `Foo::Bar = 1`. ([#5883](https://github.com/crystal-lang/crystal/pull/5883), thanks @bew) -- Allow trailing `=` in symbol literals. ([#5969](https://github.com/crystal-lang/crystal/pull/5969), thanks @straight-shoota) -- Allow redefining `None` to `0` for `@[Flags]` enum. ([#6160](https://github.com/crystal-lang/crystal/pull/6160), thanks @bew) -- Suggest possible solutions to failing requires. ([#5487](https://github.com/crystal-lang/crystal/pull/5487), thanks @RX14) -- Allow pointers of external C library global variables. ([#4845](https://github.com/crystal-lang/crystal/pull/4845), thanks @larubujo) -- Decouple pretty-printing (`pp`) and showing the expression (`!`): `p`, `pp`, `p!`, `pp!`. ([#6044](https://github.com/crystal-lang/crystal/pull/6044), thanks @asterite) -- Add ivars default value reflection in macros. ([#5974](https://github.com/crystal-lang/crystal/pull/5974), thanks @asterite) -- Add argless overload to `Number#round` to rounds to the nearest whole number. ([#5397](https://github.com/crystal-lang/crystal/pull/5397), thanks @Sija) -- Add `Int#bits_set?` to easily check that certain bits are set. ([#5619](https://github.com/crystal-lang/crystal/pull/5619), thanks @RX14) -- Add `Float32` and `Float64` constants. ([#4787](https://github.com/crystal-lang/crystal/pull/4787), thanks @konovod) -- Add allocated bytes per operation in `Benchmark.ips`. ([#5522](https://github.com/crystal-lang/crystal/pull/5522), thanks @asterite) -- Add `String#to_utf16` and `String.from_utf16`. ([#5541](https://github.com/crystal-lang/crystal/pull/5541), [#5579](https://github.com/crystal-lang/crystal/pull/5579), [#5583](https://github.com/crystal-lang/crystal/pull/5583) thanks @asterite, @RX14 and @straight-shoota) -- Add `String#starts_with?(re: Regex)`. ([#5485](https://github.com/crystal-lang/crystal/pull/5485), thanks @MakeNowJust) -- Add `Regex.needs_escape?`. ([#5962](https://github.com/crystal-lang/crystal/pull/5962), thanks @Sija) -- Add `Hash#last_key` and `Hash#last_value`. ([#5760](https://github.com/crystal-lang/crystal/pull/5760), thanks @j8r) -- Add no-copy iteration to `Indexable`. ([#4584](https://github.com/crystal-lang/crystal/pull/4584), thanks @cjgajard) -- Add `Time#at_{beginning,end}_of_second` ([#6167](https://github.com/crystal-lang/crystal/pull/6167), thanks @straight-shoota) -- Add `IO::Stapled` to combine two unidirectional `IO`s into a single bidirectional one. ([#6017](https://github.com/crystal-lang/crystal/pull/6017), thanks @straight-shoota) -- Add context to errors in `JSON.mapping` generated code. ([#5932](https://github.com/crystal-lang/crystal/pull/5932), thanks @straight-shoota) -- Add `JSON::Serializable` and `YAML::Serializable` attribute powered mappings. ([#6082](https://github.com/crystal-lang/crystal/pull/6082), thanks @kostya) -- Add `mode` param to `File.write`. ([#5754](https://github.com/crystal-lang/crystal/pull/5754), thanks @woodruffw) -- Add Punycode/IDNA support and integrate with DNS lookup. ([#2543](https://github.com/crystal-lang/crystal/pull/2543), thanks @MakeNowJust) -- Add `HTTP::Client#options` method. ([#5824](https://github.com/crystal-lang/crystal/pull/5824), thanks @mamantoha) -- Add support for `Last-Modified` and other cache improvements to `HTTP::StaticFileHandler`. ([#2470](https://github.com/crystal-lang/crystal/pull/2470), [#5607](https://github.com/crystal-lang/crystal/pull/5607), thanks @bebac and @straight-shoota) -- Add operations and improvements related to `BigDecimal` and `BigFloat`. ([#5437](https://github.com/crystal-lang/crystal/pull/5437), [#5390](https://github.com/crystal-lang/crystal/pull/5390), [#5589](https://github.com/crystal-lang/crystal/pull/5589), [#5582](https://github.com/crystal-lang/crystal/pull/5582), [#5638](https://github.com/crystal-lang/crystal/pull/5638), [#5675](https://github.com/crystal-lang/crystal/pull/5675), thanks @Sija and @mjago) -- Add `BigDecimal` and `UUID` JSON support. ([#5525](https://github.com/crystal-lang/crystal/pull/5525), [#5551](https://github.com/crystal-lang/crystal/pull/5551), thanks @lukeasrodgers and @lachlan) -- Add missing `UUID#inspect`. ([#5574](https://github.com/crystal-lang/crystal/pull/5574), thanks @ngsankha) -- Add `Logger` configuration in initializer. ([#5618](https://github.com/crystal-lang/crystal/pull/5618), thanks @Sija) -- Add custom separators in `CSV.build`. ([#5998](https://github.com/crystal-lang/crystal/pull/5998), [#6008](https://github.com/crystal-lang/crystal/pull/6008) thanks @Sija) -- Add `INI.build` to emit `INI` files. ([#5298](https://github.com/crystal-lang/crystal/pull/5298), thanks @j8r) -- Add `Process.chroot`. ([#5577](https://github.com/crystal-lang/crystal/pull/5577), thanks @chris-huxtable) -- Add `Tempfile.tempname` to create likely nonexisting filenames. ([#5360](https://github.com/crystal-lang/crystal/pull/5360), thanks @woodruffw) -- Add `FileUtils#ln`, `ln_s`, and `ln_sf`. ([#5421](https://github.com/crystal-lang/crystal/pull/5421), thanks @woodruffw) -- Add support 8bit and true color to `Colorize`. ([#5902](https://github.com/crystal-lang/crystal/pull/5902), thanks @MakeNowJust) -- Add comparison operators between classes. ([#5645](https://github.com/crystal-lang/crystal/pull/5645), thanks @asterite) -- Add exception cause in backtrace. ([#5833](https://github.com/crystal-lang/crystal/pull/5833), thanks @RX14) -- Add unhandled exception as argument in `at_exit`. ([#5906](https://github.com/crystal-lang/crystal/pull/5906), thanks @MakeNowJust) -- Add support to target aarch64-linux-musl. ([#5861](https://github.com/crystal-lang/crystal/pull/5861), thanks @jirutka) -- Add `#clear` method to `ArrayLiteral`/`HashLiteral` for macros. ([#5265](https://github.com/crystal-lang/crystal/pull/5265), thanks @Sija) -- Add `Bool#to_unsafe` for C bindings. ([#5465](https://github.com/crystal-lang/crystal/pull/5465), thanks @woodruffw) -- Spec: Add expectations `starts_with`, `ends_with`. ([#5881](https://github.com/crystal-lang/crystal/pull/5881), thanks @kostya) -- Formatter: Add `--include` and `--exclude` options to restrict directories. ([#4635](https://github.com/crystal-lang/crystal/pull/4635), thanks @straight-shoota) -- Documentation generator: improved navigation, searching, rendering and SEO. ([#5229](https://github.com/crystal-lang/crystal/pull/5229), [#5795](https://github.com/crystal-lang/crystal/pull/5795), [#5990](https://github.com/crystal-lang/crystal/pull/5990), [#5657](https://github.com/crystal-lang/crystal/pull/5657), [#6073](https://github.com/crystal-lang/crystal/pull/6073), thanks @straight-shoota, @Sija and @j8r) -- Playground: Add button in playground to run formatter. ([#3652](https://github.com/crystal-lang/crystal/pull/3652), thanks @samueleaton) - -## Standard library bugs fixed -- Fixed `String#sub` handling of negative indexes. ([#5491](https://github.com/crystal-lang/crystal/pull/5491), thanks @MakeNowJust) -- Fixed `String#gsub` in non-ascii strings. ([#5350](https://github.com/crystal-lang/crystal/pull/5350), thanks @straight-shoota) -- Fixed `String#dump` for UTF-8 characters higher than `\uFFFF`. ([#5668](https://github.com/crystal-lang/crystal/pull/5668), thanks @straight-shoota) -- Fixed `String#tr` edge case optimization bug. ([#5913](https://github.com/crystal-lang/crystal/pull/5913), thanks @MakeNowJust) -- Fixed `String#rindex` when called with `Regex`. ([#5594](https://github.com/crystal-lang/crystal/pull/5594), thanks @straight-shoota) -- Fixed `Time::Span` precision loss and boundary check. ([#5563](https://github.com/crystal-lang/crystal/pull/5563), [#5786](https://github.com/crystal-lang/crystal/pull/5786), thanks @petoem and @straight-shoota) -- `Array#sample` was fixed to use the provided random number generator (instead of the default) in all cases. ([#5419](https://github.com/crystal-lang/crystal/pull/5419), thanks @c910335) -- Add short-circuit logic in `Deque#rotate!` for singleton and empty queues. ([#5399](https://github.com/crystal-lang/crystal/pull/5399), thanks @willcosgrove) -- `Slice#reverse!` was optimised to be up to 43% faster. ([#5401](https://github.com/crystal-lang/crystal/pull/5401), thanks @larubujo) -- Fixed `Regex#inspect` when escaping was needed. ([#5841](https://github.com/crystal-lang/crystal/pull/5841), thanks @MakeNowJust) -- Fixed `JSON.mapping` now generates type restriction on getters. ([#5935](https://github.com/crystal-lang/crystal/pull/5935), thanks @Daniel-Worrall) -- Fixed `JSON.mapping` documentation regarding unions. ([#5483](https://github.com/crystal-lang/crystal/pull/5483), thanks @RX14) -- Fixed `JSON.mapping` and `YAML.mapping` to allow `properties` property. ([#5180](https://github.com/crystal-lang/crystal/pull/5180), [#5352](https://github.com/crystal-lang/crystal/pull/5352), thanks @maxpowa and @Sija) -- Fixed `YAML` int and float parsing. ([#5699](https://github.com/crystal-lang/crystal/pull/5699), [#5774](https://github.com/crystal-lang/crystal/pull/5774), thanks @straight-shoota) -- Fixed WebSocket handshake validation. ([#5327](https://github.com/crystal-lang/crystal/pull/5327), [#6027](https://github.com/crystal-lang/crystal/pull/6027) thanks @straight-shoota) -- Fixed `HTTP::Client` is able to use ipv6 addresses. ([#6147](https://github.com/crystal-lang/crystal/pull/6147), thanks @bcardiff) -- Fixed handling some invalid responses in `HTTP::Client`. ([#5630](https://github.com/crystal-lang/crystal/pull/5630), thanks @straight-shoota) -- Fixed `HTTP::ChunkedContent` will raise on unterminated content. ([#5928](https://github.com/crystal-lang/crystal/pull/5928), [#5943](https://github.com/crystal-lang/crystal/pull/5943), thanks @straight-shoota) -- `URI#to_s` now handles default ports for lots of schemes. ([#5233](https://github.com/crystal-lang/crystal/pull/5233), thanks @lachlan) -- `HTTP::Cookies` is able to deal with spaces in cookies. ([#5408](https://github.com/crystal-lang/crystal/pull/5408), thanks @bararchy) -- Fixed MIME type of SVG images in `HTTP::StaticFileHandler`. ([#5605](https://github.com/crystal-lang/crystal/pull/5605), thanks @damianham) -- Fixed URI encoding in `StaticFileHandler#redirect_to`. ([#5628](https://github.com/crystal-lang/crystal/pull/5628), thanks @straight-shoota) -- Fixed `before_request` callbacks to be executed right before writing the request in `HTTP::Client`. ([#5626](https://github.com/crystal-lang/crystal/pull/5626), thanks @asterite) -- `Dir.glob` was re-implemented with performance improvements and edge cases fixed. ([#5179](https://github.com/crystal-lang/crystal/pull/5179), thanks @straight-shoota) -- Fixed `File.extname` edge case for '.' in path with no extension. ([#5790](https://github.com/crystal-lang/crystal/pull/5790), thanks @codyjb) -- Some ECDHE curves were incorrectly disabled in `OpenSSL` clients, this has been fixed. ([#5494](https://github.com/crystal-lang/crystal/pull/5494), thanks @jhass) -- Fixed allow bcrypt passwords up to 71 bytes. ([#5356](https://github.com/crystal-lang/crystal/pull/5356), thanks @ysbaddaden) -- Unhandled exceptions occurring inside `Process.fork` now print their backtrace correctly. ([#5431](https://github.com/crystal-lang/crystal/pull/5431), thanks @RX14) -- Fixed `Zip` no longer modifies deflate signature. ([#5376](https://github.com/crystal-lang/crystal/pull/5376), thanks @luislavena) -- Fixed `INI` parser edge cases and performance improvements. ([#5442](https://github.com/crystal-lang/crystal/pull/5442), [#5718](https://github.com/crystal-lang/crystal/pull/5718) thanks @woodruffw, @j8r) -- Fixed initialization of `LibXML`. ([#5587](https://github.com/crystal-lang/crystal/pull/5587), thanks @lbguilherme) -- Some finalizers were missing for example when the object where cloned. ([#5367](https://github.com/crystal-lang/crystal/pull/5367), thanks @alexbatalov) -- Fixed sigfault handler initialization regarding `sa_mask`. ([#5677](https://github.com/crystal-lang/crystal/pull/5677) thanks @ysbaddaden) -- Fixed missing reference symbol in ARM. ([#5640](https://github.com/crystal-lang/crystal/pull/5640), thanks @blankoworld) -- Fixed detect LLVM 5.0 by `llvm-config-5.0` command. ([#5531](https://github.com/crystal-lang/crystal/pull/5531), thanks @Vexatos) -- Restore STDIN|OUT|ERR blocking state on exit. ([#5802](https://github.com/crystal-lang/crystal/pull/5802), thanks @bew) -- Fixed multiple `at_exit` handlers chaining. ([#5413](https://github.com/crystal-lang/crystal/pull/5413), thanks @bew) -- Fixed senders were not notified when channels were closed. ([#5880](https://github.com/crystal-lang/crystal/pull/5880), thanks @carlhoerberg) -- Fixed forward unhandled exception to caller in `parallel` macro. ([#5726](https://github.com/crystal-lang/crystal/pull/5726), thanks @lipanski) -- Fixed Markdown parsing of code fences appearing on the same line. ([#5606](https://github.com/crystal-lang/crystal/pull/5606), thanks @oprypin) -- Fixed OpenSSL bindings to recognize LibreSSL. ([#5676](https://github.com/crystal-lang/crystal/pull/5676), [#6062](https://github.com/crystal-lang/crystal/pull/6062), [#5949](https://github.com/crystal-lang/crystal/pull/5949), [#5973](https://github.com/crystal-lang/crystal/pull/5973) thanks @LVMBDV and @RX14) -- Fixed path value in to `UNIXSocket` created by `UNIXServer`. ([#5869](https://github.com/crystal-lang/crystal/pull/5869), thanks @straight-shoota) -- Fixed `Object.delegate` over setters. ([#5964](https://github.com/crystal-lang/crystal/pull/5964), thanks @straight-shoota) -- Fixed `pp` will now use the same width on every line. ([#5978](https://github.com/crystal-lang/crystal/pull/5978), thanks @MakeNowJust) -- Fixes missing stdarg.cr for i686-linux-musl. ([#6120](https://github.com/crystal-lang/crystal/pull/6120), thanks @bcardiff) -- Spec: Fixed junit spec formatter to emit the correct XML. ([#5463](https://github.com/crystal-lang/crystal/pull/5463), thanks @hanneskaeufler) - -## Compiler bugs fixed -- Fixed enum generated values when a member has value 0. ([#5954](https://github.com/crystal-lang/crystal/pull/5954), thanks @bew) -- Fixed compiler issue when previous compilation was interrupted. ([#5585](https://github.com/crystal-lang/crystal/pull/5585), thanks @asterite) -- Fixed compiler error with an empty `ensure` block. ([#5396](https://github.com/crystal-lang/crystal/pull/5396), thanks @MakeNowJust) -- Fixed parsing regex in default arguments. ([#5481](https://github.com/crystal-lang/crystal/pull/5481), thanks @MakeNowJust) -- Fixed parsing error of regex literal after open parenthesis. ([#5453](https://github.com/crystal-lang/crystal/pull/5453), thanks @MakeNowJust) -- Fixed parsing of empty array with blank. ([#6107](https://github.com/crystal-lang/crystal/pull/6107), thanks @asterite) -- Static libraries are now found correctly when using the `--static` compiler flag. ([#5385](https://github.com/crystal-lang/crystal/pull/5385), thanks @jreinert) -- Improve error messages for unterminated literals. ([#5409](https://github.com/crystal-lang/crystal/pull/5409), thanks @straight-shoota) -- Fixed `ProcNotation` and `ProcLiteral` introspection in macros. ([#5206](https://github.com/crystal-lang/crystal/pull/5206), thanks @javanut13) -- Cross compilation honors `--emit` and avoid generating `bc_flags` in current directory. ([#5521](https://github.com/crystal-lang/crystal/pull/5521), thanks @asterite) -- Fixed compiler error with integer constants as generic arguments. ([#5532](https://github.com/crystal-lang/crystal/pull/5532), thanks @asterite) -- Fixed compiler error with self as base class. ([#5534](https://github.com/crystal-lang/crystal/pull/5534), thanks @asterite) -- Fixed macro expansion when mutating the argument. ([#5247](https://github.com/crystal-lang/crystal/pull/5247), thanks @MakeNowJust) -- Fixed macro expansion edge cases. ([#5680](https://github.com/crystal-lang/crystal/pull/5680), [#5842](https://github.com/crystal-lang/crystal/pull/5842), [#6163](https://github.com/crystal-lang/crystal/pull/6163), thanks @asterite, @MakeNowJust and @splattael) -- Fixed macro overload on named args. ([#5808](https://github.com/crystal-lang/crystal/pull/5808), thanks @bew) -- Fixed macro numeric types used in interpreter. ([#5972](https://github.com/crystal-lang/crystal/pull/5972), thanks @straight-shoota) -- Fixed missing debug locations in several places. ([#5597](https://github.com/crystal-lang/crystal/pull/5597), thanks @asterite) -- Fixed missing information in AST nodes needed for macro expansion. ([#5454](https://github.com/crystal-lang/crystal/pull/5454), thanks @MakeNowJust) -- Fixed multiline error messages in emitted by `ASTNode#raise` macro method. ([#5670](https://github.com/crystal-lang/crystal/pull/5670), thanks @asterite) -- Fixed nested delimiters and escaped whitespace in string/symbol array literals. ([#5667](https://github.com/crystal-lang/crystal/pull/5667), thanks @straight-shoota) -- Fixed custom array/hash-like literals in nested modules. ([#5685](https://github.com/crystal-lang/crystal/pull/5685), thanks @asterite) -- Fixed usage of static array in C externs. ([#5690](https://github.com/crystal-lang/crystal/pull/5690), thanks @asterite) -- Fixed `spawn` over expression with receivers. ([#5781](https://github.com/crystal-lang/crystal/pull/5781), thanks @straight-shoota) -- Fixed prevent heredoc inside interpolation. ([#5648](https://github.com/crystal-lang/crystal/pull/5648), thanks @MakeNowJust) -- Fixed parsing error when a newline follows block arg. ([#5737](https://github.com/crystal-lang/crystal/pull/5737), thanks @bew) -- Fixed parsing error when macro argument is followed by a newline. ([#6046](https://github.com/crystal-lang/crystal/pull/6046), thanks @asterite) -- Fixed compiler error messages wording. ([#5887](https://github.com/crystal-lang/crystal/pull/5887), thanks @r00ster91) -- Fixed recursion issues in `method_added` macro hook. ([#5159](https://github.com/crystal-lang/crystal/pull/5159), thanks @MakeNowJust) -- Fixed avoid using type of updated argument for type inference. ([#5166](https://github.com/crystal-lang/crystal/pull/5166), thanks @MakeNowJust) -- Fixed parsing error message on unbalanced end brace in macros. ([#5420](https://github.com/crystal-lang/crystal/pull/5420), thanks @MakeNowJust) -- Fixed parsing error message on keywords are used as arguments. ([#5930](https://github.com/crystal-lang/crystal/pull/5930), [#6052](https://github.com/crystal-lang/crystal/pull/6052), thanks @MakeNowJust and @esse) -- Fixed parsing error message on missing comma for named tuples. ([#5981](https://github.com/crystal-lang/crystal/pull/5981), thanks @MakeNowJust) -- Fixed missing handling of `cond` node in visitor. ([#6032](https://github.com/crystal-lang/crystal/pull/6032), thanks @veelenga) -- Fixed cli when `--threads` has invalid value. ([#6039](https://github.com/crystal-lang/crystal/pull/6039), thanks @r00ster91) -- Fixed private methods can now be called with explicit `self` receiver. ([#6075](https://github.com/crystal-lang/crystal/pull/6075), thanks @MakeNowJust) -- Fixed missing some missing rules of initializer in initializers macro methods. ([#6077](https://github.com/crystal-lang/crystal/pull/6077), thanks @asterite) -- Fixed regression bug related to unreachable code. ([#6045](https://github.com/crystal-lang/crystal/pull/6045), thanks @asterite) - -## Tools bugs fixed -- Several `crystal init` and template improvements. ([#5475](https://github.com/crystal-lang/crystal/pull/5475), [#5355](https://github.com/crystal-lang/crystal/pull/5355), [#4691](https://github.com/crystal-lang/crystal/pull/4691), [#5788](https://github.com/crystal-lang/crystal/pull/5788), [#5644](https://github.com/crystal-lang/crystal/pull/5644), [#6031](https://github.com/crystal-lang/crystal/pull/6031) thanks @woodruffw, @faustinoaq, @bew, @kostya and @MakeNowJust) -- Formatter: improve formatting of method call arguments with trailing comments. ([#5492](https://github.com/crystal-lang/crystal/pull/5492), thanks @MakeNowJust) -- Formatter: fix formatting of multiline statements. ([#5234](https://github.com/crystal-lang/crystal/pull/5234), [#5901](https://github.com/crystal-lang/crystal/pull/5901), [#6013](https://github.com/crystal-lang/crystal/pull/6013) thanks @MakeNowJust) -- Formatter: fix formatting of multi assignment. ([#5452](https://github.com/crystal-lang/crystal/pull/5452), thanks @MakeNowJust) -- Formatter: fix formatting of backslash ending statements. ([#5194](https://github.com/crystal-lang/crystal/pull/5194), thanks @asterite) -- Formatter: fix formatting of `.[]` methods. ([#5424](https://github.com/crystal-lang/crystal/pull/5424), thanks @MakeNowJust) -- Formatter: fix formatting of statements with comments. ([#5655](https://github.com/crystal-lang/crystal/pull/5655), [#5893](https://github.com/crystal-lang/crystal/pull/5893), [#5909](https://github.com/crystal-lang/crystal/pull/5909), thanks @MakeNowJust) -- Formatter: fix formatting of nested `begin`/`end`. ([#5922](https://github.com/crystal-lang/crystal/pull/5922), thanks @MakeNowJust) -- Formatter: fix formatting of trailing comma with block calls. ([#5855](https://github.com/crystal-lang/crystal/pull/5855), thanks @MakeNowJust) -- Formatter: fix formatting of ending expression after heredoc. ([#6127](https://github.com/crystal-lang/crystal/pull/6127), thanks @asterite) -- Documentation generator: references to nested types in markdown are now correctly parsed. ([#5308](https://github.com/crystal-lang/crystal/pull/5308), thanks @straight-shoota) -- Documentation generator: fix leftovers regarding default old `doc` directory. ([#5406](https://github.com/crystal-lang/crystal/pull/5406), thanks @GloverDonovan) -- Documentation generator: avoid failing on non git directory. ([#3700](https://github.com/crystal-lang/crystal/pull/3700), thanks @MakeNowJust) -- `Crystal::Doc::Highlighter` has specs now ([#5368](https://github.com/crystal-lang/crystal/pull/5368), thanks @MakeNowJust) -- Playground: can now be run with HTTPS. ([#5527](https://github.com/crystal-lang/crystal/pull/5527), thanks @opiation) -- Playground: Pretty-print objects in inspector. ([#4601](https://github.com/crystal-lang/crystal/pull/4601), thanks @jgaskins) - -## Misc -- The platform-specific parts of `File` and `IO::FileDescriptor` were moved to `Crystal::System`, as part of preparation for the Windows port. ([#5333](https://github.com/crystal-lang/crystal/pull/5333), [#5553](https://github.com/crystal-lang/crystal/pull/5553), [#5622](https://github.com/crystal-lang/crystal/pull/5622) thanks @RX14) -- The platform-specific parts of `Dir` were moved to `Crystal::System`, as part of preparation for the Windows port. ([#5447](https://github.com/crystal-lang/crystal/pull/5447), thanks @RX14) -- Incremental contributions regarding Windows support. ([#5422](https://github.com/crystal-lang/crystal/pull/5422), [#5524](https://github.com/crystal-lang/crystal/pull/5524), [#5533](https://github.com/crystal-lang/crystal/pull/5533), [#5538](https://github.com/crystal-lang/crystal/pull/5538), [#5539](https://github.com/crystal-lang/crystal/pull/5539), [#5580](https://github.com/crystal-lang/crystal/pull/5580), [#5947](https://github.com/crystal-lang/crystal/pull/5947) thanks @RX14 and @straight-shoota) -- The build on OpenBSD was fixed. ([#5387](https://github.com/crystal-lang/crystal/pull/5387), thanks @wmoxam) -- Add support for FreeBSD 12 (64-bit inodes). ([#5199](https://github.com/crystal-lang/crystal/pull/5199), thanks @myfreeweb) -- Scripts and makefiles now depend on `sh` instead of `bash` for greater portability. ([#5468](https://github.com/crystal-lang/crystal/pull/5468), thanks @j8r) -- Honor `LDFLAGS` and `EXTRA_FLAGS` in `Makefile`. ([#5423](https://github.com/crystal-lang/crystal/pull/5423), [#5860](https://github.com/crystal-lang/crystal/pull/5860), thanks @trofi, @jirutka) -- Improve message on link failure. ([#5486](https://github.com/crystal-lang/crystal/pull/5486), [#5603](https://github.com/crystal-lang/crystal/pull/5603), thanks @RX14 and @waj) -- Improve `String#to_json` when chars don't need escaping. ([#5456](https://github.com/crystal-lang/crystal/pull/5456), thanks @larubujo) -- Improve `Time#add_span` when arguments are zero. ([#5787](https://github.com/crystal-lang/crystal/pull/5787), thanks @straight-shoota) -- Improve `String#pretty_print` to output by splitting newline. ([#5750](https://github.com/crystal-lang/crystal/pull/5750), thanks @MakeNowJust) -- Add `\a` escape sequence. ([#5864](https://github.com/crystal-lang/crystal/pull/5864), thanks @r00ster91) -- Several miscellaneous minor code cleanups and refactors. ([#5499](https://github.com/crystal-lang/crystal/pull/5499), [#5502](https://github.com/crystal-lang/crystal/pull/5502), [#5507](https://github.com/crystal-lang/crystal/pull/5507), [#5516](https://github.com/crystal-lang/crystal/pull/5516), [#4915](https://github.com/crystal-lang/crystal/pull/4915), [#5526](https://github.com/crystal-lang/crystal/pull/5526), [#5529](https://github.com/crystal-lang/crystal/pull/5529), [#5535](https://github.com/crystal-lang/crystal/pull/5535), [#5537](https://github.com/crystal-lang/crystal/pull/5537), [#5540](https://github.com/crystal-lang/crystal/pull/5540), [#5435](https://github.com/crystal-lang/crystal/pull/5435), [#5520](https://github.com/crystal-lang/crystal/pull/5520), [#5530](https://github.com/crystal-lang/crystal/pull/5530), [#5547](https://github.com/crystal-lang/crystal/pull/5547), [#5543](https://github.com/crystal-lang/crystal/pull/5543), [#5561](https://github.com/crystal-lang/crystal/pull/5561), [#5599](https://github.com/crystal-lang/crystal/pull/5599), [#5493](https://github.com/crystal-lang/crystal/pull/5493), [#5546](https://github.com/crystal-lang/crystal/pull/5546), [#5624](https://github.com/crystal-lang/crystal/pull/5624), [#5701](https://github.com/crystal-lang/crystal/pull/5701), [#5733](https://github.com/crystal-lang/crystal/pull/5733), [#5646](https://github.com/crystal-lang/crystal/pull/5646), [#5729](https://github.com/crystal-lang/crystal/pull/5729), [#5791](https://github.com/crystal-lang/crystal/pull/5791), [#5859](https://github.com/crystal-lang/crystal/pull/5859), [#5882](https://github.com/crystal-lang/crystal/pull/5882), [#5899](https://github.com/crystal-lang/crystal/pull/5899), [#5918](https://github.com/crystal-lang/crystal/pull/5918), [#5896](https://github.com/crystal-lang/crystal/pull/5896), [#5810](https://github.com/crystal-lang/crystal/pull/5810), [#5575](https://github.com/crystal-lang/crystal/pull/5575), [#5785](https://github.com/crystal-lang/crystal/pull/5785), [#5866](https://github.com/crystal-lang/crystal/pull/5866), [#5816](https://github.com/crystal-lang/crystal/pull/5816), [#5945](https://github.com/crystal-lang/crystal/pull/5945), [#5963](https://github.com/crystal-lang/crystal/pull/5963), [#5968](https://github.com/crystal-lang/crystal/pull/5968), [#5977](https://github.com/crystal-lang/crystal/pull/5977), [#6004](https://github.com/crystal-lang/crystal/pull/6004), [#5794](https://github.com/crystal-lang/crystal/pull/5794), [#5858](https://github.com/crystal-lang/crystal/pull/5858), [#6033](https://github.com/crystal-lang/crystal/pull/6033), [#6036](https://github.com/crystal-lang/crystal/pull/6036), [#6079](https://github.com/crystal-lang/crystal/pull/6079), [#6111](https://github.com/crystal-lang/crystal/pull/6111), [#6118](https://github.com/crystal-lang/crystal/pull/6118), [#6141](https://github.com/crystal-lang/crystal/pull/6141), [#6142](https://github.com/crystal-lang/crystal/pull/6142), [#5380](https://github.com/crystal-lang/crystal/pull/5380), [#6071](https://github.com/crystal-lang/crystal/pull/6071), thanks @chastell, @lachlan, @bew, @RX14, @sdogruyol, @MakeNowJust, @Sija, @noriyotcp, @asterite, @splattael, @straight-shoota, @r00ster91, @jirutka, @paulcsmith, @rab, @esse, @carlhoerberg, @chris-huxtable, @luislavena) -- Several documentation fixes and additions. ([#5425](https://github.com/crystal-lang/crystal/pull/5425), [#5682](https://github.com/crystal-lang/crystal/pull/5682), [#5779](https://github.com/crystal-lang/crystal/pull/5779), [#5576](https://github.com/crystal-lang/crystal/pull/5576), [#5806](https://github.com/crystal-lang/crystal/pull/5806), [#5817](https://github.com/crystal-lang/crystal/pull/5817), [#5873](https://github.com/crystal-lang/crystal/pull/5873), [#5878](https://github.com/crystal-lang/crystal/pull/5878), [#5637](https://github.com/crystal-lang/crystal/pull/5637), [#5885](https://github.com/crystal-lang/crystal/pull/5885), [#5884](https://github.com/crystal-lang/crystal/pull/5884), [#5728](https://github.com/crystal-lang/crystal/pull/5728), [#5917](https://github.com/crystal-lang/crystal/pull/5917), [#5912](https://github.com/crystal-lang/crystal/pull/5912), [#5894](https://github.com/crystal-lang/crystal/pull/5894), [#5933](https://github.com/crystal-lang/crystal/pull/5933), [#5809](https://github.com/crystal-lang/crystal/pull/5809), [#5936](https://github.com/crystal-lang/crystal/pull/5936), [#5908](https://github.com/crystal-lang/crystal/pull/5908), [#5851](https://github.com/crystal-lang/crystal/pull/5851), [#5378](https://github.com/crystal-lang/crystal/pull/5378), [#5914](https://github.com/crystal-lang/crystal/pull/5914), [#5967](https://github.com/crystal-lang/crystal/pull/5967), [#5993](https://github.com/crystal-lang/crystal/pull/5993), [#3482](https://github.com/crystal-lang/crystal/pull/3482), [#5946](https://github.com/crystal-lang/crystal/pull/5946), [#6095](https://github.com/crystal-lang/crystal/pull/6095), [#6117](https://github.com/crystal-lang/crystal/pull/6117), [#6131](https://github.com/crystal-lang/crystal/pull/6131), [#6162](https://github.com/crystal-lang/crystal/pull/6162), thanks @MakeNowJust, @straight-shoota, @vendethiel, @bew, @Heaven31415, @marksiemers, @Willamin, @r00ster91, @maiha, @Givralix, @docelic, @CaDs, @esse, @igneus, @masukomi) -- CI housekeeping and including 32 bits automated builds. ([#5796](https://github.com/crystal-lang/crystal/pull/5796), [#5804](https://github.com/crystal-lang/crystal/pull/5804), [#5837](https://github.com/crystal-lang/crystal/pull/5837), [#6015](https://github.com/crystal-lang/crystal/pull/6015), [#6165](https://github.com/crystal-lang/crystal/pull/6165), thanks @bcardiff, @bew and @Sija) -- Sync docs in master to [https://crystal-lang.org/api/master](https://crystal-lang.org/api/master). ([#5941](https://github.com/crystal-lang/crystal/pull/5941), thanks @bcardiff) -- Enable the large heap configuration for libgc. ([#5839](https://github.com/crystal-lang/crystal/pull/5839), thanks @RX14) -- Improve Ctrl-C handling of spec. ([#5719](https://github.com/crystal-lang/crystal/pull/5719), thanks @MakeNowJust) -- Playground: Update to codemirror 5.38.0. ([#6166](https://github.com/crystal-lang/crystal/pull/6166), thanks @bcardiff) - -# 0.24.2 (2018-03-08) - -- Fixed an `Index out of bounds` raised during `at_exit` ([#5224](https://github.com/crystal-lang/crystal/issues/5224), [#5565](https://github.com/crystal-lang/crystal/issues/5565), thanks @ysbaddaden) -- Re-add `Dir#each` so it complies with `Enumerable` ([#5458](https://github.com/crystal-lang/crystal/issues/5458), thanks @bcardiff) -- Fixed `SSL::Context` bug verifying certificates ([#5266](https://github.com/crystal-lang/crystal/issues/5266), [#5601](https://github.com/crystal-lang/crystal/issues/5601), thanks @waj) -- Fixed UUID documentation that was missing ([#5478](https://github.com/crystal-lang/crystal/issues/5478), [#5542](https://github.com/crystal-lang/crystal/issues/5542), thanks @asterite) -- Fixed a bug with single expressions in parenthesis ([#5482](https://github.com/crystal-lang/crystal/issues/5482), [#5511](https://github.com/crystal-lang/crystal/issues/5511), [#5513](https://github.com/crystal-lang/crystal/issues/5513), thanks @MakeNowJust) -- Fixed `skip_file` macro docs ([#5488](https://github.com/crystal-lang/crystal/issues/5488), thanks @straight-shoota) -- Fixed CI `build` script's `LIBRARY_PATH` ([#5457](https://github.com/crystal-lang/crystal/issues/5457), [#5461](https://github.com/crystal-lang/crystal/issues/5461), thanks @bcardiff) -- Fixed formatter bug with upper-cased `fun` names ([#5432](https://github.com/crystal-lang/crystal/issues/5432), [#5434](https://github.com/crystal-lang/crystal/issues/5434), thanks @bew) - -# 0.24.1 (2017-12-23) - -## New features -- Add ThinLTO support for faster release builds in LLVM 4.0 and above. ([#4367](https://github.com/crystal-lang/crystal/issues/4367), thanks @bcardiff) -- **(breaking-change)** Add `UUID` type. `Random::Secure.uuid` has been replaced with `UUID.random`. ([#4453](https://github.com/crystal-lang/crystal/issues/4453), thanks @wontruefree) -- Add a `BigDecimal` class for arbitrary precision, exact, decimal numbers. ([#4876](https://github.com/crystal-lang/crystal/issues/4876) and [#5255](https://github.com/crystal-lang/crystal/issues/5255), thanks @vegai and @Sija) -- Allow `Set` to work as a case condition, which matches when the case variable is inside the set. ([#5269](https://github.com/crystal-lang/crystal/issues/5269), thanks @MakeNowJust) -- **(breaking-change)** Change `Time::Format` codes to allow more robust options for parsing sub-second precision times. ([#5317](https://github.com/crystal-lang/crystal/issues/5317), thanks @bcardiff) -- Add `Time.utc`, an alias of `Time.new` which shortens creating UTC times. ([#5321](https://github.com/crystal-lang/crystal/issues/5321), thanks @straight-shoota) -- Add custom extension support to `Tempfile`. ([#5264](https://github.com/crystal-lang/crystal/issues/5264), thanks @jreinert) -- Add `reduce` method to `TupleLiteral` and `ArrayLiteral` when using macros. ([#5294](https://github.com/crystal-lang/crystal/issues/5294), thanks @javanut13) -- Export a JSON representation of the documentation in the generated output. ([#4746](https://github.com/crystal-lang/crystal/issues/4746) and [#5228](https://github.com/crystal-lang/crystal/issues/5228), thanks @straight-shoota) -- Make `gc/none` garbage collection compile again and allow it to be enabled using `-Dgc_none` compiler flag. ([#5314](https://github.com/crystal-lang/crystal/issues/5314), thanks @ysbaddaden) - -## Standard library bugs fixed -- Make `String#[]` unable to read out-of-bounds when the string ends in a unicode character. ([#5257](https://github.com/crystal-lang/crystal/issues/5257), thanks @Papierkorb) -- Fix incorrect parsing of long JSON floating point values. ([#5323](https://github.com/crystal-lang/crystal/issues/5323), thanks @benoist) -- Replace the default hash function with one resistant to hash DoS. ([#5146](https://github.com/crystal-lang/crystal/issues/5146), thanks @funny-falcon) -- Ensure equal numbers always have the same hashcode. ([#5276](https://github.com/crystal-lang/crystal/issues/5276), thanks @akzhan) -- Fix struct equality when two structs descend from the same abstract struct. ([#5254](https://github.com/crystal-lang/crystal/issues/5254), thanks @hinrik) -- Fix `URI#full_path` not to append a `?` unless the query params are nonempty. ([#5340](https://github.com/crystal-lang/crystal/issues/5340), thanks @paulcsmith) -- Fix `HTTP::Params.parse` to parse `&&` correctly. ([#5274](https://github.com/crystal-lang/crystal/issues/5274), thanks @akiicat) -- Disallow null bytes in `ENV` keys and values. ([#5216](https://github.com/crystal-lang/crystal/issues/5216), thanks @Papierkorb) -- Disallow null bytes in `XML::Node` names and content. ([#5200](https://github.com/crystal-lang/crystal/issues/5200), thanks @RX14) -- Fix `IO#blocking=` on OpenBSD. ([#5283](https://github.com/crystal-lang/crystal/issues/5283), thanks @wmoxam) -- Fix linking programs in OpenBSD. ([#5282](https://github.com/crystal-lang/crystal/issues/5282), thanks @wmoxam) - -## Compiler bugs fixed -- Stop incorrectly finding top-level methods when searching for a `super` method. ([#5202](https://github.com/crystal-lang/crystal/issues/5202), thanks @lbguilherme) -- Fix parsing regex literals starting with a `;` directly after a call (ex `p /;/`). ([#5208](https://github.com/crystal-lang/crystal/issues/5208), thanks @MakeNowJust) -- Correct a case where `Expressions#to_s` could produce invalid output, causing macro expansion to fail. ([#5226](https://github.com/crystal-lang/crystal/issues/5226), thanks @asterite) -- Give error instead of crashing when `self` is used at the top level. ([#5227](https://github.com/crystal-lang/crystal/issues/5227), thanks @MakeNowJust) -- Give error instead of crashing when using `instance_sizeof` on a generic type without providing it's type arguments. ([#5209](https://github.com/crystal-lang/crystal/issues/5209), thanks @lbguilherme) -- Fix parsing calls when short block syntax (`&.foo`) is followed by a newline. ([#5237](https://github.com/crystal-lang/crystal/issues/5237), thanks @MakeNowJust) -- Give error instead of crashing when an unterminated string array literal (`%w()`) sits at the end of a file. ([#5241](https://github.com/crystal-lang/crystal/issues/5241), thanks @asterite) -- Give error when attempting to use macro yield (`{{yield}}`) outside a macro. ([#5307](https://github.com/crystal-lang/crystal/issues/5307), thanks @MakeNowJust) -- Fix error related to generic inheritance. ([#5284](https://github.com/crystal-lang/crystal/issues/5284), thanks @MakeNowJust) -- Fix compiler crash when using recursive alias and generics. ([#5330](https://github.com/crystal-lang/crystal/issues/5330), thanks @MakeNowJust) -- Fix parsing `foo(+1)` as `foo + 1` instead of `foo(1)` where `foo` was a local variable. ([#5336](https://github.com/crystal-lang/crystal/issues/5336), thanks @MakeNowJust) -- Documentation generator: Keep quoted symbol literals quoted when syntax highlighting code blocks in documentation output. ([#5238](https://github.com/crystal-lang/crystal/issues/5238), thanks @MakeNowJust) -- Documentation generator: Keep the original delimiter used when syntax highlighting string array literals. ([#5297](https://github.com/crystal-lang/crystal/issues/5297), thanks @MakeNowJust) -- Documentation generator: Fix XSS vulnerability when syntax highlighting string array literals. ([#5259](https://github.com/crystal-lang/crystal/issues/5259), thanks @MakeNowJust) -- Formatter: fix indentation of the last comment in a `begin`/`end` block. ([#5198](https://github.com/crystal-lang/crystal/issues/5198), thanks @MakeNowJust) -- Formatter: fix formatting parentheses with multiple lines in. ([#5268](https://github.com/crystal-lang/crystal/issues/5268), thanks @MakeNowJust) -- Formatter: fix formatting `$1?`. ([#5313](https://github.com/crystal-lang/crystal/issues/5313), thanks @MakeNowJust) -- Formatter: ensure to insert a space between `{` and `%` characters to avoid forming `{%` macros. ([#5278](https://github.com/crystal-lang/crystal/issues/5278), thanks @MakeNowJust) - -## Misc -- Fix `Makefile`, CI, and gitignore to use the new documentation path after [#4937](https://github.com/crystal-lang/crystal/issues/4937). ([#5217](https://github.com/crystal-lang/crystal/issues/5217), thanks @straight-shoota) -- Miscellaneous code cleanups. ([#5318](https://github.com/crystal-lang/crystal/issues/5318), [#5341](https://github.com/crystal-lang/crystal/issues/5341) and [#5366](https://github.com/crystal-lang/crystal/issues/5366), thanks @bew and @mig-hub) -- Documentation fixes. ([#5253](https://github.com/crystal-lang/crystal/issues/5253), [#5296](https://github.com/crystal-lang/crystal/issues/5296), [#5300](https://github.com/crystal-lang/crystal/issues/5300) and [#5322](https://github.com/crystal-lang/crystal/issues/5322), thanks @arcage, @icyleaf, @straight-shoota and @bew) -- Fix the in-repository changelog to include 0.24.0. ([#5331](https://github.com/crystal-lang/crystal/pull/5331), thanks @sdogruyol) - -# 0.24.0 (2017-10-30) - -- **(breaking-change)** HTTP::Client#post_form is now HTTP::Client.post(form: ...) -- **(breaking-change)** Array#reject!, Array#compact! and Array#select! now return self ([#5154](https://github.com/crystal-lang/crystal/pull/5154)) -- **(breaking-change)** Remove the possibility to require big_int, big_float or big_rational individually: use require "big" instead ([#5121](https://github.com/crystal-lang/crystal/pull/5121)) -- **(breaking-change)** Spec: remove expect_raises without type argument ([#5096](https://github.com/crystal-lang/crystal/pull/5096)) -- **(breaking-change)** IO is now a class, no longer a module ([#4901](https://github.com/crystal-lang/crystal/pull/4901)) -- **(breaking-change)** Time constructors now have nanosecond and kind as named argument ([#5072](https://github.com/crystal-lang/crystal/pull/5072)) -- **(breaking-change)** Removed XML.escape. Use HTML.escape instead ([#5046](https://github.com/crystal-lang/crystal/pull/5046)) -- **(breaking-change)** Removed macro def ([#5040](https://github.com/crystal-lang/crystal/pull/5040)) -- **(breaking-change)** SecureRandom is now Random::Secure ([#4894](https://github.com/crystal-lang/crystal/pull/4894)) -- **(breaking-change)** HTML.escape now only escapes &<>"' ([#5012](https://github.com/crystal-lang/crystal/pull/5012)) -- **(breaking-change)** To define a custom hash method you must now define hash(hasher) ([#4946](https://github.com/crystal-lang/crystal/pull/4946)) -- **(breaking-change)** Flate::Reader.new(&block) and Flate::Writer.new(&block) now use the name open ([#4887](https://github.com/crystal-lang/crystal/pull4887/)) -- **(breaking-change)** Use an Enum for Process stdio redirections ([#4445](https://github.com/crystal-lang/crystal/pull/4445)) -- **(breaking-change)** Remove '$0' special syntax -- **(breaking-change)** Remove bare array creation from multi assign (a = 1, 2, 3) ([#4824](https://github.com/crystal-lang/crystal/pull/4824)) -- **(breaking-change)** Rename skip macro method to skip_file ([#4709](https://github.com/crystal-lang/crystal/pull/4709)) -- **(breaking-change)** StaticArray#map and Slice#map now return their same type instead of Array ([#5124](https://github.com/crystal-lang/crystal/pull/5124)) -- **(breaking-change)** Tuple#map_with_index now returns a Tuple. ([#5086](https://github.com/crystal-lang/crystal/pull/5086)) -- Packages built with LLVM 3.9.1. They should (hopefully) fix [#4719](https://github.com/crystal-lang/crystal/issues/4719) -- Syntax: Allow flat rescue/ensure/else block in do/end block ([#5114](https://github.com/crystal-lang/crystal/pull/5114)) -- Syntax: fun names and lib function calls can now start with Uppercase -- Macros: Using an alias in macros will now automatically resolve it to is aliased type ([#4995](https://github.com/crystal-lang/crystal/pull/4995)) -- Macros: The flags bits32 and bits64 are now automatically defined in macros -- The YAML module has now full support for the 1.1 core schema with additional types, and properly supports aliases and merge keys ([#5007](https://github.com/crystal-lang/crystal/pull/5007)) -- Add --output option to crystal docs ([#4937](https://github.com/crystal-lang/crystal/pull/4937)) -- Add Time#days_in_year: it returns the no of days in a given year ([#5163](https://github.com/crystal-lang/crystal/pull/5163)) -- Add Time.monotonic to return monotonic clock ([#5108](https://github.com/crystal-lang/crystal/pull/5108)) -- Add remove_empty option to many String#split overloads -- Add Math.sqrt overloads for Bigs ([#5113](https://github.com/crystal-lang/crystal/pull/5113)) -- Add --stdin-filename to crystal command to compile source from STDIN ([#4571](https://github.com/crystal-lang/crystal/pull/4571)) -- Add Crystal.main to more easily redefine the main of a program ([#4998](https://github.com/crystal-lang/crystal/pull/4998)) -- Add Tuple.types that returns a tuple of types ([#4962](https://github.com/crystal-lang/crystal/pull/4962)) -- Add NamedTuple.types that returns a named tuple of types ([#4962](https://github.com/crystal-lang/crystal/pull/4962)) -- Add NamedTuple#merge(other : NamedTuple) ([#4688](https://github.com/crystal-lang/crystal/pull/4688)) -- Add YAML and JSON.mapping presence: true option ([#4843](https://github.com/crystal-lang/crystal/pull/4843)) -- Add Dir.each_child(&block) ([#4811](https://github.com/crystal-lang/crystal/pull/4811)) -- Add Dir.children ([#4808](https://github.com/crystal-lang/crystal/pull/4808)) -- HTML.unescape now supports all HTML5 named entities ([#5064](https://github.com/crystal-lang/crystal/pull/5064)) -- Regex now supports duplicated named captures ([#5061](https://github.com/crystal-lang/crystal/pull/5061)) -- rand(0) is now valid and returns 0 -- Tuple#[] now supports a negative index ([#4735](https://github.com/crystal-lang/crystal/pull/4735)) -- JSON::Builder#field now accepts non-scalar values ([#4706](https://github.com/crystal-lang/crystal/pull/4706)) -- Number#inspect now shows the number type -- Some additions to Big arithmetics ([#4653](https://github.com/crystal-lang/crystal/pull/4653)) -- Increase the precision of Time and Time::Span to nanoseconds ([#5022](https://github.com/crystal-lang/crystal/pull/5022)) -- Upgrade Unicode to 10.0.0 ([#5122](https://github.com/crystal-lang/crystal/pull/5122)) -- Support LLVM 5.0 ([#4821](https://github.com/crystal-lang/crystal/pull/4821)) -- [Lots of bugs fixed](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.24.0) - -# 0.23.1 (2017-07-01) - -* Added `Random::PCG32` generator (See [#4536](https://github.com/crystal-lang/crystal/issues/4536), thanks @konovod) -* WebSocket should compare "Upgrade" header value with case insensitive (See [#4617](https://github.com/crystal-lang/crystal/issues/4617), thanks @MakeNowJust) -* Fixed macro lookup from included module (See [#4639](https://github.com/crystal-lang/crystal/issues/4639), thanks @asterite) -* Explained "crystal tool expand" in crystal(1) man page (See [#4643](https://github.com/crystal-lang/crystal/issues/4643), thanks @MakeNowJust) -* Explained how to detect end of file in `IO` (See [#4661](https://github.com/crystal-lang/crystal/issues/4661), thanks @oprypin) - -# 0.23.0 (2017-06-27) - -* **(breaking-change)** `Logger#formatter` takes a `Severity` instead of a `String` (See [#4355](https://github.com/crystal-lang/crystal/issues/4355), [#4369](https://github.com/crystal-lang/crystal/issues/4369), thanks @Sija) -* **(breaking-change)** Removed `IO.select` (See [#4392](https://github.com/crystal-lang/crystal/issues/4392), thanks @RX14) -* Added `Crystal::System::Random` namespace (See [#4450](https://github.com/crystal-lang/crystal/issues/4450), thanks @ysbaddaden) -* Added `Path#resolve?` macro method (See [#4370](https://github.com/crystal-lang/crystal/issues/4370), [#4408](https://github.com/crystal-lang/crystal/issues/4408), thanks @RX14) -* Added range methods to `BitArray` (See [#4397](https://github.com/crystal-lang/crystal/issues/4397), [#3968](https://github.com/crystal-lang/crystal/issues/3968), thanks @RX14) -* Added some well-known HTTP Status messages (See [#4419](https://github.com/crystal-lang/crystal/issues/4419), thanks @akzhan) -* Added compiler progress indicator (See [#4182](https://github.com/crystal-lang/crystal/issues/4182), thanks @RX14) -* Added `System.cpu_cores` (See [#4449](https://github.com/crystal-lang/crystal/issues/4449), [#4226](https://github.com/crystal-lang/crystal/issues/4226), thanks @miketheman) -* Added `separator` and `quote_char` to `CSV#each_row` (See [#4448](https://github.com/crystal-lang/crystal/issues/4448), thanks @timsu) -* Added `map_with_index!` to `Pointer`, `Array` and `StaticArray` (See [#4456](https://github.com/crystal-lang/crystal/issues/4456), [#3356](https://github.com/crystal-lang/crystal/issues/3356), [#3354](https://github.com/crystal-lang/crystal/issues/3354), thanks @Nephos) -* Added `headers` parameter to `HTTP::WebSocket` constructors (See [#4227](https://github.com/crystal-lang/crystal/issues/4227), [#4222](https://github.com/crystal-lang/crystal/issues/4222), thanks @adamtrilling) -* Added `unlink` to `XML::Node` (See [#4515](https://github.com/crystal-lang/crystal/issues/4515), [#4331](https://github.com/crystal-lang/crystal/issues/4331), thanks @RX14 and @MrSorcus) -* Added `Math.frexp` (See [#4560](https://github.com/crystal-lang/crystal/issues/4560), thanks @akzhan) -* Added `Regex::MatchData` support for negative indexes (See [#4566](https://github.com/crystal-lang/crystal/issues/4566), thanks @MakeNowJust) -* Added `captures`, `named_captures`, `to_a` and `to_h` to `Regex::MatchData` (See [#3783](https://github.com/crystal-lang/crystal/issues/3783), thanks @MakeNowJust) -* Added `|` as a string delimiter to allow `q|string|` syntax (See [#3467](https://github.com/crystal-lang/crystal/issues/3467), thanks @RX14) -* Added support for Windows linker (See [#4491](https://github.com/crystal-lang/crystal/issues/4491), thanks @RX14) -* Added llvm operand bundle def and catch pad/ret/switch in order to support Windows SEH (See [#4501](https://github.com/crystal-lang/crystal/issues/4501), thanks @bcardiff) -* Added `Float::Printer` based on Grisu3 to speed up float to string conversion (See [#4333](https://github.com/crystal-lang/crystal/issues/4333), thanks @will) -* Added `Object.unsafe_as` to unsafely reinterpret the bytes of an object as being of another `type` (See [#4333](https://github.com/crystal-lang/crystal/issues/4333), thanks @asterite) -* Added `.downcase(Unicode::CaseOptions::Fold)` option which convert strings to casefolded strings for caseless matching (See [#4512](https://github.com/crystal-lang/crystal/issues/4512), thanks @akzhan) -* Added `OpenSSL::DigestIO` to wrap an IO while calculating a digest (See [#4260](https://github.com/crystal-lang/crystal/issues/4260), thanks @spalladino) -* Added `zero?` to numbers and time spans (See [#4026](https://github.com/crystal-lang/crystal/issues/4026), thanks @jellymann) -* Added `TypeNode#has_method?` method (See [#4474](https://github.com/crystal-lang/crystal/issues/4474), thanks @Sija) -* `Regex::MatchData#size` renamed to `#group_size` (See [#4565](https://github.com/crystal-lang/crystal/issues/4565), thanks @MakeNowJust) -* `HTTP::StaticFileHandler` can disable directory listing (See [#4403](https://github.com/crystal-lang/crystal/issues/4403), [#4398](https://github.com/crystal-lang/crystal/issues/4398), thanks @joaodiogocosta) -* `bin/crystal` now uses `/bin/sh` instead of `/bin/bash` (See [#3809](https://github.com/crystal-lang/crystal/issues/3809), [#4410](https://github.com/crystal-lang/crystal/issues/4410), thanks @TheLonelyGhost) -* `crystal init` generates a `.editorconfig` file (See [#4422](https://github.com/crystal-lang/crystal/issues/4422), [#297](https://github.com/crystal-lang/crystal/issues/297), thanks @akzhan) -* `man` page for `crystal` command (See [#2989](https://github.com/crystal-lang/crystal/issues/2989), [#1291](https://github.com/crystal-lang/crystal/issues/1291), thanks @dread-uo) -* Re-raising an exception doesn't overwrite its callstack (See [#4487](https://github.com/crystal-lang/crystal/issues/4487), [#4482](https://github.com/crystal-lang/crystal/issues/4482), thanks @akzhan) -* MD5 and SHA1 documentation clearly states they are not cryptographically secure anymore (See [#4426](https://github.com/crystal-lang/crystal/issues/4426), thanks @RX14) -* Documentation about constructor methods now rendered separately (See [#4216](https://github.com/crystal-lang/crystal/issues/4216), thanks @Sija) -* Turn `Random::System` into a module (See [#4542](https://github.com/crystal-lang/crystal/issues/4542), thanks @oprypin) -* `Regex::MatchData` pretty printed (See [#4574](https://github.com/crystal-lang/crystal/issues/4574), thanks @MakeNowJust) -* `String.underscore` treats digits as downcase or upcase characters depending previous characters (See [#4280](https://github.com/crystal-lang/crystal/issues/4280), thanks @MakeNowJust) -* Refactor time platform specific implementation (See [#4502](https://github.com/crystal-lang/crystal/issues/4502), thanks @bcardiff) -* Fixed Crystal not reusing .o files across builds (See [#4336](https://github.com/crystal-lang/crystal/issues/4336)) -* Fixed `SomeClass.class.is_a?(SomeConst)` causing an "already had enclosing call" exception (See [#4364](https://github.com/crystal-lang/crystal/issues/4364), [#4390](https://github.com/crystal-lang/crystal/issues/4390), thanks @rockwyc992) -* Fixed `HTTP::Params.parse` query string with two `=` gave wrong result (See [#4388](https://github.com/crystal-lang/crystal/issues/4388), [#4389](https://github.com/crystal-lang/crystal/issues/4389), thanks @akiicat) -* Fixed `Class.class.is_a?(Class.class.class.class.class)` 🎉 (See [#4375](https://github.com/crystal-lang/crystal/issues/4375), [#4374](https://github.com/crystal-lang/crystal/issues/4374), thanks @rockwyc992) -* Fixed select hanging when sending before receive (See [#3862](https://github.com/crystal-lang/crystal/issues/3862), [#3899](https://github.com/crystal-lang/crystal/issues/3899), thanks @kostya) -* Fixed "Unknown key in access token json: id_token" error in OAuth2 client (See [#4437](https://github.com/crystal-lang/crystal/issues/4437)) -* Fixed macro lookup conflicting with method lookup when including on top level (See [#236](https://github.com/crystal-lang/crystal/issues/236)) -* Fixed Vagrant images (See [#4510](https://github.com/crystal-lang/crystal/issues/4510), [#4508](https://github.com/crystal-lang/crystal/issues/4508), thanks @Val) -* Fixed `IO::FileDescriptor#seek` from current position (See [#4558](https://github.com/crystal-lang/crystal/issues/4558), thanks @ysbaddaden) -* Fixed `IO::Memory#gets_to_end` to consume the `IO` (See [#4415](https://github.com/crystal-lang/crystal/issues/4415), thanks @jhass) -* Fixed setting of XML attributes (See [#4562](https://github.com/crystal-lang/crystal/issues/4562), thanks @asterite) -* Fixed "SSL_shutdown: Operation now in progress" error by retrying (See [#3168](https://github.com/crystal-lang/crystal/issues/3168), thanks @akzhan) -* Fixed WebSocket negotiation (See [#4386](https://github.com/crystal-lang/crystal/issues/4386), thanks @RX14) - -# 0.22.0 (2017-04-20) - -* **(breaking-change)** Removed `Process.new(pid)` is now private (See [#4197](https://github.com/crystal-lang/crystal/issues/4197)) -* **(breaking-change)** IO#peek now returns an empty slice on EOF (See [#4240](https://github.com/crystal-lang/crystal/issues/4240), [#4261](https://github.com/crystal-lang/crystal/issues/4261)) -* **(breaking-change)** Rename `WeakRef#target` to `WeakRef#value` (See [#4293](https://github.com/crystal-lang/crystal/issues/4293)) -* **(breaking-change)** Rename `HTTP::Params.from_hash` to `HTTP::Params.encode` (See [#4205](https://github.com/crystal-lang/crystal/issues/4205)) -* **(breaking-change)** `'\"'` is now invalid, use `'"'` (See [#4309](https://github.com/crystal-lang/crystal/issues/4309)) -* Improved backtrace function names are now read from DWARF sections (See [#3958](https://github.com/crystal-lang/crystal/issues/3958), thanks @ysbaddaden) -* Improved sigfaults and exceptions are printed to STDERR (See [#4163](https://github.com/crystal-lang/crystal/issues/4163), thanks @Sija) -* Improved SSL Sockets are now buffered (See [#4248](https://github.com/crystal-lang/crystal/issues/4248)) -* Improved type inference on loops (See [#4242](https://github.com/crystal-lang/crystal/issues/4242), [#4243](https://github.com/crystal-lang/crystal/issues/4243)) -* Improved `pp` and `p`, the printed value is returned (See [#4285](https://github.com/crystal-lang/crystal/issues/4285), [#4283](https://github.com/crystal-lang/crystal/issues/4283), thanks @MakeNowJust) -* Added support for OpenSSL 1.1.0 (See [#4215](https://github.com/crystal-lang/crystal/issues/4215), [#4230](https://github.com/crystal-lang/crystal/issues/4230), thanks @ysbaddaden) -* Added `SecureRandom#random_bytes(Bytes)` (See [#4191](https://github.com/crystal-lang/crystal/issues/4191), thanks @konovod) -* Added setting and deleting of attributes on `XML::Node` (See [#3902](https://github.com/crystal-lang/crystal/issues/3902), thanks @bmmcginty) -* Added `File.touch` and `FileUtils.touch` methods (See [#4069](https://github.com/crystal-lang/crystal/issues/4069), thanks @Sija) -* Added `#values_at` for `CSV` (See [#4157](https://github.com/crystal-lang/crystal/issues/4157), thanks @need47) -* Added `Time#clone` (See [#4174](https://github.com/crystal-lang/crystal/issues/4174), thanks @Sija) -* Added `ancestors` macro method (See [#3875](https://github.com/crystal-lang/crystal/issues/3875), thanks @david50407) -* Added `skip` macro method ([#4237](https://github.com/crystal-lang/crystal/issues/4237), thanks @mverzilli) -* Added `Colorize.on_tty_only!` for easier toggling (See [#4075](https://github.com/crystal-lang/crystal/issues/4075), [#4271](https://github.com/crystal-lang/crystal/issues/4271), thanks @MakeNowJust) -* Added `WebSocket#on_binary` to receive binary messages (See [#2774](https://github.com/crystal-lang/crystal/issues/2774), thanks @lbguilherme) -* Fixed `Iterator.of` stops iterating when `Iterator.stop` is returned (See [#4208](https://github.com/crystal-lang/crystal/issues/4208)) -* Fixed `String#insert` for non-ascii Char (See [#4164](https://github.com/crystal-lang/crystal/issues/4164), thanks @Papierkorb) -* Fixed `File.link` now creates a hard link ([#4116](https://github.com/crystal-lang/crystal/issues/4116), thanks @KCreate) -* Fixed error message for `#to_h` over empty `NamedTuple` (See [#4076](https://github.com/crystal-lang/crystal/issues/4076), thanks @karlseguin) -* Fixed `NamedTuple#to_h` does no longer call to value's `#clone` (See [#4203](https://github.com/crystal-lang/crystal/issues/4203)) -* Fixed `Math#gamma` and `Math#lgamma` (See [#4229](https://github.com/crystal-lang/crystal/issues/4229), thanks @KCreate) -* Fixed `TCPSocket` creation for 0 port for Mac OSX (See [#4177](https://github.com/crystal-lang/crystal/issues/4177), thanks @will) -* Fixed repo name extraction from git remote in doc tool (See [#4132](https://github.com/crystal-lang/crystal/issues/4132), thanks @Sija) -* Fixed `self` resolution when including a generic module (See [#3972](https://github.com/crystal-lang/crystal/issues/3972), thanks @MakeNowJust) -* Fixed debug information was missing in some cases (See [#4166](https://github.com/crystal-lang/crystal/issues/4166), [#4202](https://github.com/crystal-lang/crystal/issues/4202), [#4254](https://github.com/crystal-lang/crystal/issues/4254)) -* Fixed use generic ARM architecture target triple for all ARM architectures (See [#4167](https://github.com/crystal-lang/crystal/issues/4167), thanks @ysbaddaden) -* Fixed macro run arguments escaping -* Fixed zsh completion (See [#4284](https://github.com/crystal-lang/crystal/issues/4284), thanks @veelenga) -* Fixed honor `--no-color` option in spec (See [#4306](https://github.com/crystal-lang/crystal/issues/4306), thanks @luislavena) -* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.22.0) - -# 0.21.1 (2017-03-06) - -* Improved lookup of abstract def implementors (see [#4052](https://github.com/crystal-lang/crystal/issues/4052)) -* Improved allocation of objects without pointer instance variables using `malloc_atomic` (see [#4081](https://github.com/crystal-lang/crystal/issues/4081)) -* Added `crystal --version` reports also the LLVM version (see [#4095](https://github.com/crystal-lang/crystal/issues/4095), thanks @matiasgarciaisaia) -* Fixed instance variables initializers corner cases (see [#3988](https://github.com/crystal-lang/crystal/issues/3988)) -* Fixed `crystal play` was broken (see [#4061](https://github.com/crystal-lang/crystal/issues/4061)) -* Fixed `Atomic` can be set to `nil` (see [#4062](https://github.com/crystal-lang/crystal/issues/4062)) -* Fixed `GZip::Header` extra byte (see [#4068](https://github.com/crystal-lang/crystal/issues/4068), thanks @crisward) -* Fixed `ASTNode#to_s` for `Attribute` (see [#4098](https://github.com/crystal-lang/crystal/issues/4098), thanks @olbat) -* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.21.1) - -# 0.21.0 (2017-02-20) - -* **(breaking-change)** The compiler now reuses previous macro run compilations so `{{ run(...) }}` is only re-run if the code changes -* **(breaking-change)** Spec: `assert { ... }` is now `it { ... }` (thanks @TheLonelyGhost) -* **(breaking-change)** Renamed `Set#merge!` to `Set#concat` -* **(breaking-change)** `Zlib` was split into `Flate`, `Gzip` and `Zlib` ([bda40f](https://github.com/crystal-lang/crystal/commit/bda40f)) -* **(breaking-change)** `Crypto::MD5` is now `Digest::MD5` -* **(breaking-change)** `String#chop` is now `String#rchop` -* **(breaking-change)** `String#to_slice` now returns a read-only Slice -* **(breaking-change)** `String` can now hold invalid UTF-8 byte sequences, and they produce a unicode replacement character when traversed -* **(breaking-change)** Removed `String#lchomp`. Use `String#lchop` -* **(breaking-change)** Octal escapes inside strings incorrectly produced a codepoint value instead of a byte value -* **(breaking-change)** Removed octal escape from char literals -* Fixed compiler performance regression related to cached files ([f69e37e](https://github.com/crystal-lang/crystal/commit/f69e37e)) -* Added `\xHH` escape sequence in string literals -* `Char::Reader` can now traverse a string backwards -* `Enum#to_s` now uses pipes instead of commas for flag enums -* `IO#read_string` is now encoding-aware -* `OAuth2::Client` now sends `application/json` Accept header, and considers the `expires_in` access token property as optional -* `Slice` can now be read-only -* `TCPServer` no longer set SO_REUSEPORT to true by default -* Added `HTTP::Multipart` and `HTTP::FormData` (thanks @RX14) -* Added `File::Stat#pipe?` -* Added `File.utime` -* Added `IO#peek` -* Added `String#strip(arg)`, `String#lstrip(arg)`, `String#rstrip(arg)` -* Added `String#lchop`, `String#lchop(prefix)`, `String#rchop` and `String#rchop(suffix)` -* Added `String#hexbytes` and `String#hexbytes?` -* Added `String#scrub` and `String#valid_encoding?` -* Added `include?` macro method for StringLiteral, SymbolLiteral and MacroId (thanks @karlseguin) -* Added "view source" links to GitLab (thanks @ezrast) -* Updated CONTRIBUTING.md guidelines -* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.21.0) - -# 0.20.5 (2017-01-20) - -* Improved performance in `String#index`, `String#rindex` due to Rabin-Karp algorithm (thanks @MakeNowJust). -* Improved performance in `Crypto::Bcrypt` (see [#3880](https://github.com/crystal-lang/crystal/issues/3880), thanks @ysbaddaden). -* `expect_raises` returns raised exception (thanks @kostya). -* Line numbers debug information is always generated (see [#3831](https://github.com/crystal-lang/crystal/issues/3831), thanks @ysbaddaden). -* Added `Zip::File`, `Zip::Reader` and `Zip::Writer`. Native readers for zip files that delegate compression to existing zlib module. -* Added `Hash#delete` with block (see [#3856](https://github.com/crystal-lang/crystal/issues/3856), thanks @bmulvihill). -* Added `String#[](char : Char)` (see [#3855](https://github.com/crystal-lang/crystal/issues/3855), thanks @Sija). -* Added `crystal tool expand` to expand macro call in a given location (see [#3732](https://github.com/crystal-lang/crystal/issues/3732), thanks @MakeNowJust). -* Fixed `crystal play` is able to show compilation errors again. -* `crystal doc` recognizes `crystal-lang/crystal` in any remote (thanks @MaxLap). -* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.5) - -# 0.20.4 (2017-01-06) - -* **(breaking change)** A type that wants to convert itself to JSON now must override `to_json(builder : JSON::Builder)` instead of `to_json(io : IO)`. The same is true for custom JSON converters. If you are using `JSON.mapping` then your code will continue to work without changes. -* **(breaking change)** Defining a `finalize` method on a struct now gives a compile error -* **(breaking change)** Default argument types now must match their restriction, if any (for example `def foo(x : Int32 = nil)` will now fail to compile if `foo` is invoked without arguments) (thanks @MakeNowJust) -* **(breaking change)** `each` methods now return `Nil` -* **(breaking change)** `IO#skip(bytes)` will now raise if there aren't at least the given amount of bytes in the `IO` (previously it would work well if there were less bytes, and it would hang if there were more) -* **(breaking change)** `MemoryIO` was removed (use `IO::Memory` instead) -* **(breaking change)** `Number#step` now requires named arguments, `to` and `by`, to avoid argument order confusion -* **(breaking change)** `YAML::Emitter` was renamed to `YAML::Builder`, and some of its methods were also renamed -* **(breaking change)** `XML::Node#[]` now always returns a `String` (previously it could also return `Nil`, which was incorrect) -* **(breaking change)** `XML::Node#content` now returns an empty `String` when no content is available -* `HTTP::Client` now automatically reconnects on a dropped keep-alive connection -* `with ... yield` now works well with `method_missing` -* Class variables can now be used in generic types (all generic instances share the same variable, and subclasses get their own copy, as usual) -* Added support for LLVM 4 (thanks @ysbaddaden) -* Added `Enum.each` and `Enum#each` (thanks @ysbaddaden) -* Added `Hash#compact` and `Hash#compact!` (thanks @MakeNowJust) -* Added `IO#read_string(bytesize)` -* Added `IO#skip_to_end` -* Added `Iterator#flat_map` (thanks @MakeNowJust) -* Added `JSON.build` and `JSON::Builder` -* Added `NamedTuple#has_key?(String)` (thanks @Sija) -* Added `p(NamedTuple)` (thanks @splattael) -* Added `Regex::MatchData#==` (thanks @MakeNowJust) -* Added `String#sub(Regex, NamedTuple)` (thanks @maiha) -* Added `XML.build` and `XML::Builder` -* Lots of improvements and applied consistencies to doc comments (thanks @Sija and @maiha) -* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.4) - -## 0.20.3 (2016-12-23) - -* **(breaking change)** `IO#gets`, `IO#each_line`, `String#lines`, `String#each_line`, etc. now chomp lines by default. You can pass `chomp: false` to prevent automatic chomping. Note that `chomp` is `true` by default for argless `IO#gets` (read line) but `false` if args are given. -* **(breaking change)** `HTTP::Handler` is now a module instead of a class (thanks @andrewhamon) -* **(breaking change)** Free variables now must be specified with `forall`, a single uppercase letter will not work anymore -* **(breaking change)** The `libs` directory is no longer in the default CRYSTAL_PATH, use `lib` (running `crystal deps` should fix this) -* Optimized compile times, specially on linux -* `private` can now be used with macros inside types (thanks @MakeNowJust) -* CLI: the `-s`/`--stats` option now also shows execution time (thanks @MakeNowJust) -* CLI: added `-t`/`--time` to show execution time (thanks @MakeNowJust) -* `Socket` now allows any family/type/protocol association, [and many other improvements](https://github.com/crystal-lang/crystal/pull/3750) (thanks @ysbaddaden) -* YAML: an `IO` can now be passed to `from_yaml` (thanks @MakeNowJust) -* Added `class_getter`, `class_setter`, `class_property`, etc. (thanks @Sija) -* Added `String#lchomp` (thanks @Sija) -* Added `IO#read_fully?` -* Added `Iterator#flatten` (thanks @MakeNowJust) -* Added `HTTP::WebSocket#ping`, `pong`, `on_ping`, `on_pong`, and now a ping message is automatically replied with a pong message (thanks @Sija) -* Added `File#empty?` and `Dir#empty?` (thanks @dylandrop) -* Added `Time::Span#/(Time::Span)` (thanks @RX14) -* Added `String#split` versions that accept a block (thanks @splattael) -* Added `URI#normalize` and `normalize!` (thanks @taylorfinnell) -* Added `reuse` optional argument to many `Array`, `Enumerable` and `Iterable` methods that allow you to reuse the yielded/return array for better performance and less memory footprint -* The `:debug` flag is now present when compiled with `--debug`, useful for doing `flag?(:debug)` in macros (thanks @luislavena) -* [Many bug fixes and performance improvements](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.3) - -## 0.20.1 (2016-12-05) - -* **(breaking change)** `Set#merge` as renamed to `Set#merge!` -* **(breaking change)** `Slice.new(size)` no longer works with non primitive integers and floats -* **(breaking change)** The macro method `argify` was renamed to `splat` -* Added pretty printing. The methods `p` and `pp` now use it. To get the old behaviour use `puts obj.inspect` -* Added `ArrayLiteral#[]=`, `TypeNode#constant`, `TypeNode#overrides?` and `HashLiteral#double_splat` in macros -* Added a `finished` macro hook that runs at the end of the program -* Added support for declaring the type of a local variable -* Added `Slice.empty` -* Flags enums now have a `none?` method -* `IO::ByteFormat` has now methods to encode/decode to/from a `Slice` -* Spec: the line number passed to run a specific `it` block can now be inside any line of that block -* The `CallConvention` attribute can now also be applied to a `lib` declaration, and all `fun`s inside it will inherit it -* The `method_missing` hook can now define a method, useful for specifying block arguments -* Support double splat in macros (`{{**...}}`) -* [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.1) - -## 0.20.0 (2016-11-22) - -* **(breaking change)** Removed `ifdef` from the language -* **(breaking change)** Removed `PointerIO` -* **(breaking change)** The `body` property of `HTTP::Request` is now an `IO?` (previously it was `String`). Use `request.body.try(&.gets_to_end)` if you need the entire body as a String. -* **(breaking change)** `MemoryIO` has been renamed to `IO::Memory`. The old name can still be used but will produce a compile-time warning. `MemoryIO` will be removed immediately after 0.20.0. -* **(breaking change)** `Char#digit?` was split into `Char#ascii_number?` and `Char#number?`. The old name is still available and will produce a compile-time warning, but will be removed immediately after 0.20.0. -* **(breaking change)** `Char#alpha?` was split into `Char#ascii_letter?` and `Char#letter?`. The old name is still available and will produce a compile-time warning, but will be removed immediately after 0.20.0. -* **(breaking change)** The `Iterable` module is now generic -* Many `String` and `Char` methods are now unicode-aware, for example `String#downcase`, `String#upcase`, `Char#downcase`, `Char#upcase`, `Char#whitespace?`, etc. -* Added support for HTTP client and server streaming. -* Added support for ARM (thanks @ysbaddaden) -* Added support for AArch64 (thanks @ysbaddaden) -* Added support for LLVM 3.9 (thanks @ysbaddaden) -* Added `__END_LINE__` magic constant in method default arguments: will be the last line of a call (if the call has a block, it will be the last line of that block) -* Added `@def` inside macros that takes the value of the current method -* API docs have a nicer style now, and notes like TODO and DEPRECATED are better highlighted (thanks @samueleaton) -* Slight improvement to debugging support (thanks @ggiraldez) -* Line numbers in backtraces (linux only for now) (thanks @ysbaddaden) -* Added iteration times to `Benchmark.ips` (thanks @RX14) -* Allow `HTTP::Client` block initializer to be used when passing an URI (thanks @philnash) -* `JSON.mapping` and `YAML.mapping` getter/setter generation can now be controlled (thanks @zatherz) -* `Time` is now serializable to JSON and YAML using ISO 8601 date-time format -* Added `IO::MultiWriter` (thanks @RX14) -* Added `String#index(Regex)` and `String#rindex(Regex)` (thanks @zatherz) -* Added `String#partition` and `String#rpartition` (thanks @johnjansen) -* Added `FileUtils.cd`, `FileUtils.mkdir`, `FileUtils.mkdir_p`, `FileUtils.mv`, `FileUtils.pwd`, `FileUtils.rm`, `FileUtils.rm_r`, `FileUtils.rmdir` (thanks @ghivert) -* Added `JSON::Builder#raw_field` (thanks @kostya) -* Added `Enumerable#chunks` and `Iterator#chunk` (thanks @kostya) -* Added `Iterator#with_index` -* Several enhancements to the Random module: now works for any integer type and avoids overflows (thanks @BlaXpirit) -* Optimized `Array#sort` by using introsort (thanks @c910335) -* [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.0) - -## 0.19.4 (2016-10-07) - -* Added support for OpenBSD (thanks @wmoxam and @ysbaddaden) -* More iconv fixes for FreeBSD (thanks @ysbaddaden) -* Changed how `require` works for the upcoming `shards` release (this is backwards compatible). See https://github.com/crystal-lang/crystal/pull/2788 -* Added `Atomic` and exposed all LLVM atomic instructions to Crystal (needed to implemented multiple-thread support) -* Added `Process.executable_path` (thanks @kostya, @whereami and @ysbaddaden) -* Added `HTML.unescape` (thanks @dukex) -* Added `Char#+(Int)` and `Char#-(Int)` -* [A few bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.4) - -## 0.19.3 (2016-09-30) - -* `crystal eval` now accepts some flags like `--stats`, `--release` and `--help` -* Added `File.chown` and `File.chmod` (thanks @ysbaddaden) -* Added `Time::Span.zero` (useful for doing `sum`) (thanks @RX14) -* Added docs to `OAuth` and `OAuth2` -* [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.3) - -## 0.19.2 (2016-09-16) - -* Generic type variables no longer need to be single-letter names (for example `class Gen(Foo)` is now possible) -* Added syntax to denote free variables: `def foo(x : T) forall T`. The old rule of single-letter name still applies but will be removed in the future. -* Removed the restriction that top-level types and constants can't have single-letter names -* Added `@[Extern]` attribute to mark regular Crystal structs as being able to be used in C bindings -* Faster `Char#to_s` when it's ASCII: this improves the performance of JSON and CSV parsing -* `crystal spec`: allow passing `--release` and other options -* `crystal spec`: allow running all specs in a given directory -* `crystal playground`: support custom workbook resources (thanks @bcardiff) -* `crystal playground`: standard output now understands ANSI colors (thanks @bcardiff) -* Added many more macro methods to traverse AST nodes (thanks @BlaXpirit) -* Error messages no longer include a type trace by default, pass `--error-trace` to show the full trace (the trace is often useless and makes it harder to understand error messages) -* [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.2) - -## 0.19.1 (2016-09-09) - -* Types (class, module, etc.) can now be marked as `private`. -* Added `WeakRef` (thanks @bcardiff) -* [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.1) - -## 0.19.0 (2016-09-02) - -* **(breaking change)** Added `select` keyword -* **(breaking change)** Removed $global variables. Use @@class variables instead. -* **(breaking change)** Heredoc now ends when the matching identifier is found, either followed by a space or by a non-identifier -* **(breaking change)** Assignment to a local variable inside an assignment to that same variable is now an error -* **(breaking change)** Type names like `T`, `T1`, `U`, etc., are now disallowed at the top level, to avoid conflicts with free variables -* **(breaking change)** Type lookup (`Foo::Bar::Baz`) had some incorrect behaviour that now is fixed. This can break existing code that relied on this incorrect behaviour. The fix is to fully qualify types (`::Foo::Bar::Baz`) -* **(breaking change)** In relationships like `class Bar < Foo(Baz)` and `include Moo(Baz)`, all of `Foo`, `Moo` and `Baz` must be defined before that point (this was not always the case in previous versions) -* **(breaking change)** Removed the deprecated syntax `x as T` -* **(breaking change)** Removed block form of `String#match` -* **(breaking change)** Removed `IO#read_nonblock` -* **(breaking change)** `Int#/` now performs floored division. Use `Int#tdiv` for truncated division (see their docs to learn the difference) -* Added support for LLVM 3.8 (thanks @omarroth) -* `||` now does type filtering -* Generic inheritance should now work well, and (instantiated) generic modules can now be used as the type of instance variables -* `NamedTuple` can now be accessed with strings too (thanks @jhass) -* `Base64` can now encode and decode directly to an `IO` (thanks @kostya) -* `BigInt` now uses GMP implementation of gcd and lcm (thanks @endSly) -* `ECR` now supports removing leading and trailing whitespace (`<%-`, `-%>`) -* `HTTP::Request#path` now never returns `nil`: it fallbacks to `"/"` (thanks @jhass) -* `String#tr(..., "")` is now the same as `String#delete` -* `tool hierarchy` now supports `--format json` (thanks @bmulvihill) -* Added `Char#ascii?` -* Added `Class#nilable?` and `Union#nilable?` -* Added `Hash#has_value?` (thanks @kachick) -* Added `IO::Sized` and `IO::Delimited` (thanks @RX14) -* Added `IO::Hexdump` (thanks @ysbaddaden) -* Added `IO#noecho` and `IO#noecho!` (thanks @jhass) -* Added `Logger.new(nil)` to create a null logger -* Added `OptionParser#missing_option` and `OptionParser#invalid_option` (thanks @jhass) -* Added `Process.exists?`, `Process#exists?` and `Process#terminated?` (thanks @jhass) -* Added `Process.exec` (thanks @jhass) -* Added `Slice#copy_to`, `Slice#copy_from`, `Slice#move_to` and `Slice#move_from` (thanks @RX14) -* Added `URI#==` and `URI#hash` (thanks @timcraft) -* Added `YAML#parse(IO)` -* Added `Indexable` module that `Array`, `Slice`, `Tuple` and `StaticArray` include -* Added `indent` parameter to `to_pretty_json` -* Added lazy form of `getter` and `property` macros -* Added macro methods to access an ASTNode's location -* Unified String and Char to integer/float conversion API (thanks @jhass) -* [Lots of bug fixes](https://github.com/crystal-lang/crystal/milestone/5?closed=1) - -## 0.18.7 (2016-07-03) - -* The `compile` command was renamed back to `build`. The `compile` command is deprecated and will be removed in a future version -* Fibers now can be spawned with a name -* ECR macros can now be required with just `require "ecr"` -* [Several bugs fixes and enhancements](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.7+is%3Aclosed) - -## 0.18.6 (2016-06-28) - -* `T?` is now parsed as `Union(T, Nil)` outside the type grammar -* Added `String#sub` overloads for replacing an index or range with a char or string -* [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.6+is%3Aclosed) - -## 0.18.5 (2016-06-27) - -* Added `OpenSSL::SSL::Socket#alpn_protocol` -* Added `IO#copy(src, desc, limit)` (thanks @jreinert) -* Added `TypeNode#instance` macro method -* [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.5+is%3Aclosed) - -## 0.18.4 (2016-06-21) - -* Fixed [#2887](https://github.com/crystal-lang/crystal/issues/2887) -* Fix broken specs - -## 0.18.3 (2016-06-21) - -* `TypeNode`: added `<`, `<=`, `>` and `>=` macro methods -* [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.3+is%3Aclosed) - -## 0.18.2 (2016-06-16) - -* Fixed building Crystal from the source tarball - -## 0.18.1 (2016-06-16) - -* Spec: passing `--profile` shows the slowest 10 specs (thanks @mperham) -* Added `StringLiteral#>` and `StringLiteral#<` in macros -* [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.1+is%3Aclosed) - -## 0.18.0 (2016-06-14) - -* **(breaking change)** `IniFile` was renamed to `INI`, and its method `load` renamed to `parse` -* **(breaking change)** `Process.getpgid` was renamed to `Process.pgid` -* **(breaking change)** An `Exception`'s backtrace is now set when it's raised, not when it's created: it's `backtrace` method raises if it's not set, and there's `backtrace?` to get it as a nilable array -* **(breaking change)** `dup` is now correctly implemented in all types. `clone` is not defined by default, but some types in the standard library do. Also check `Object#def_clone` -* **(breaking change)** the `method_missing` macro only accepts a single argument: a `Call` now. The form that accepted 3 arguments was removed. -* **(breaking change)** the `delegate` macro must now be used like `delegate method1, method2, ..., methodN, to: object` -* **(breaking change)** `Hash#each_with_index` and `Hash#each_with_object` now yield a tuple (pair) and an index, because `Hash` is now `Enumerable`. Use `do |(key, value), index|` for this. -* **(breaking change)** `{"foo": 1}` denotes a named tuple literal now, not a hash literal. Use `{"foo" => 1}` instead. This also applies to, for example `HTTP::Headers{...}` -* **(breaking change)** Extra block arguments now give a compile-time error. This means that methods that yield more than once, one time with N arguments and another time with M arguments, with N < M, will always give an error. To fix this, add M - N `nil` fillers on the yield side (this makes it more explicit that `nil` was intended to be a block argument value) -* **(breaking change)** `OpenSSL::SSL::Context` and `OpenSSL::SSL::Socket` can no longer be used directly anymore. Use their respective subclasses `OpenSSL::SSL::Context::Client`, - with `OpenSSL::SSL::Socket::Client`, `OpenSSL::SSL::Context::Server` with `OpenSSL::SSL::Socket::Server`. -* **(breaking change)** TLS server and client sockets now use sane defaults, including support for hostname verification for client sockets, used by default in `HTTP::Client`. -* **(breaking change)** The `ssl` option was renamed to `tls` in `HTTP::Client`, `HTTP::Server`, `HTTP::WebSocket`, `OAuth::Consumer`, `OAuth::Signature` and `OAuth2::AccessToken`. -* The `dns_timeout` setting in a few classes like `HTTP::Client` and `TCPSocket` is now ignored until a next version supports a non-blocking `getaddrinfo` equivalent -* `OpenSSL::SSL::Socket::Client` supports server name indication now. -* The `build` command was renamed to `compile`. The `build` command is deprecated and will be removed in a future version -* The `--cross-compile` flag no longer takes arguments, use `--target` and `-D` -* Added a `Union` type that represents the type of a union, which can have class methods -* Methods, procs and lib functions that are marked as returning `Void` now return `Nil` -* Methods that are marked as returning `Nil` are not checked for a correct return type, they always return `nil` now -* When `as` fails at runtime it now includes which type couldn't be cast -* Macros can now be used inside `lib` and `enum` declarations -* Macros can now be declared inside enums -* Macro calls can now be used as enum values -* Generic types can now include a splatted type variable. This already existed in the language (`Tuple(*T)`, `Proc(*T)`) but there was no syntax to define such types. -* Class variables are now inherited (only their type, not their value). They are now similar to Ruby class instance variables. -* Splats in `yield` can now be used -* Splat in block arguments can now be used. -* Added block auto-unpacking: if a method yields a tuple and a block specifies more then one block argument, the tuple is unpacked to these arguments -* String literals are now allowed as external method arguments, to match named tuples and named arguments -* `sizeof` and `instance_sizeof` can now be used as generic type arguments (mostly useful combined with `StaticArray`) -* `Hash`, `HTTP::Headers`, `HTTP::Params` and `ENV` now include the `Enumerable` module -* `Proc` is now `Proc(*T, R)` -* `Tuple(*T).new` and `NamedTuple(**T).new` now correctly match the given `T` ([#1828](https://github.com/crystal-lang/crystal/issues/1828)) -* `Float64#to_s` now produces an ever more accurate output -* `JSON` parsing now correctly handle floats with many digits -* `JSON.mapping` and `YAML.mapping` now also accept named arguments in addition to a hash literal or named tuple literal -* `Int#chr` now raises if the integer is out of a char's range. The old non-raising behaviour is now in `Int#unsafe_chr`. -* The output of `pp x` is now `x # => ...` instead of `x = ...` -* The output of the `debug()` macro method now tries to format the code (pass `false` to disable this) -* Added `JSON` and `YAML` parsing and mapping for unions -* Added `FileUtils.cp_r` (thanks @Dreauw) -* Added `Tuple.from` and `NamedTuple.from` (thanks @jhass) -* Added `XML.escape` (thanks @juanedi) -* Added `HTTP::Server::Response#respond_with_error` (thanks @jhass) -* Added `TCPServer#accept?` -* Added optional `base` argument to `Char#digit?` and `Char#hex?` (thanks @mirek) -* Added `flag?` macro method, similar to using `ifdef`. `ifdef` is deprecated and will be removed in a future version. -* Added `YAML::PullParser#read_raw` -* Added `Proc#partial` -* Added `Socket.ip?(str)` to validate IPv4 and IPv6 addresses -* Added `Bytes` as an alias of `Slice(UInt8)` -* Added `RangeLiteral` macro methods: `begin`, `end`, `excludes_end?`, `map` and `to_a` -* Added `ArrayLiteral#[range]` and `ArrayLiteral#[from, to]` in macros (applicable for `TupleLiteral` too) -* Added `Generic` macro methods: `name`, `type_vars`, `named_args` -* Spec: added JUnit formatter output (thanks @juanedi) -* The `tls` option in `HTTP::Client` can now take a `OpenSSL::SSL::Context::Client` in addition to `true`. -* `HTTP::LogHandler` logs exceptions now (thanks @jhass) -* `HTTP::ErrorHandler` does not tell the client which exception occurred by default (can be enabled with a `verbose` flag) (thanks @jhass) -* Several bug fixes - -## 0.17.4 (2016-05-26) - -* Added string literals without interpolations nor escapes: `%q{...}` and `<<-'HEREDOC'`. Also added `%Q{...}` with the same meaning as `%{...}`. -* A method that uses `@type` inside a macro expression is now automatically detected as being a `macro def` -* `Float64#to_s` now produces a more accurate output -* Added `Crystal::VERSION` and other compiler-metadata constants -* Added `Object.from_json(string_or_io, root)` and a `root` option to `JSON.mapping` -* Added `System.hostname` (thanks @miketheman) -* The `property`, `getter` and `setter` macros now also accept assignments (`property x = 0`) -* The `record` macro now also accepts assignments (`record Point, x = 0, y = 0`) -* Comparison in macros between `MacroId` and `StringLiteral` or `SymbolLiteral` now work as expected (compares the `id` representation) -* Some bug fixes - -## 0.17.3 (2016-05-20) - -* Fixed: multiple macro runs executions didn't work well ([#2624](https://github.com/crystal-lang/crystal/issues/2624)) -* Fixed incorrect formatting of underscore in unpacked block arguments -* Fixed wrong codegen for global variable assignment in type declaration ([#2619](https://github.com/crystal-lang/crystal/issues/2619)) -* Fixed initialize default arguments where evaluated at the class scope ([#731](https://github.com/crystal-lang/crystal/issues/731)) -* The type guesser can now infer a block type from `def initialize(&@block)` -* Allow type restriction in double splat argument (similar to restriction in single splat) -* Allow splat restriction in splat argument (useful for `Tuple.new`) -* Allow double splat restriction in double splat argument (useful for `NamedTuple.new`) - -## 0.17.2 (2016-05-18) - -* Fixed crash when using pointerof of constant - -## 0.17.1 (2016-05-18) - -* Constants and class vars are no longer initialized before "main". Now their initialization order goes along with "main", similar to how it works in Ruby (much more intuitive) -* Added syntax for unpacking block arguments: `foo { |(x, y)| ... }` -* Added `NamedTupleLiteral#map` and `HashLiteral#map` in macros (thanks @jhass) -* Fixed wrong codgen for tuples/named tuples merge with pass-by-value types -* Formatter: fixed incorrect format for named tuple type - -## 0.17.0 (2016-05-17) - -* **(breaking change)** Macro defs are now parsed like regular methods. Enclose the body with `{% begin %} .. {% end %}` if you needed that behaviour -* **(breaking change)** A union of two tuples of the same size results in a tuple with the unions of the types in each position. This only affects code that later tested a tuple's type with `is_a?`, for example `tuple.is_a?({Int32, String})` -* **(breaking change)** Method arguments have now a different semantic. This only affects methods that had a splat argument followed by other arguments. -* **(breaking change)** The syntax `{foo: 1, bar: 2}` now denotes a `NamedTuple`, not a `Hash` with symbol as keys. Use `{:foo => 1, :bar => 2}` instead -* The syntax `exp as Type` is now deprecated and will be removed in the next version. Use `crystal tool format` to automatically upgrade your code -* The compiler now gives an error when trying to define a method named `!`, `is_a?`, `responds_to?`, `nil?`, `as` or `as?` -* Added the `NamedTuple` type -* Added double splatting -* Added external argument names -* Macro defs return type is no longer mandatory -* Added `as?`: similar to `as`, but returns `nil` when the type doesn't match -* Added `Number::Primitive` alias -* Added `Tuple#+(Tuple)` -* Added `ArrayLiteral#+(ArrayLiteral)` in macros -* `Crypto::MD5` now allows `Slice(UInt8)` and a block form (thanks @will) -* Added docs for XML (thanks @Hamdiakoguz) -* Many bug fixes - -## 0.16.0 (2016-05-05) - -* **(breaking change)** Instance, class and global variables types must be told to the compiler, [either explicitly or through a series of syntactic rules](http://crystal-lang.org/docs/syntax_and_semantics/type_inference.html) -* **(breaking change)** Non-abstract structs cannot be inherited anymore (abstract structs can), check the [docs](http://crystal-lang.org/docs/syntax_and_semantics/structs.html) to know why. In many cases you can use modules instead. -* **(breaking change)** Class variables are now initialized at the beginning of the program (before "main"), make sure to read the docs about [class variables](http://crystal-lang.org/docs/syntax_and_semantics/class_variables.html) and [main](http://crystal-lang.org/docs/syntax_and_semantics/the_program.html) -* **(breaking change)** Constants are now initialized at the beginning of the program (before "main"), make sure to read the docs about [constants](http://crystal-lang.org/docs/syntax_and_semantics/constants.html) and [main](http://crystal-lang.org/docs/syntax_and_semantics/the_program.html) -* **(breaking change)** When doing `crystal program.cr arg1 arg2 arg3`, `arg1`, `arg2` and `arg3` are considered arguments to pass to the program (not the compiler). Use `crystal run program.cr arg1 ...` to consider `arg1` a file to include in the compilation. -* **(breaking change)** `Int#**(Int)` now returns an integer, and raises if the argument is negative. Use a float base or exponent for negative exponents to work. -* **(breaking change)** `Slice#to_s` and `StaticArray#to_s` now include their type name in the output -* Support for FreeBSD and musl libc has landed (thanks @ysbaddaden) -* The `.crystal` directory is now created at `$HOME/.cache/crystal` or `$HOME/.crystal` (or others similar), with a fallback to the current directory -* `crystal doc` and `crystal tool hierarchy` are now much faster. Additionally, the hierarchy tool shows types for generic types, and doesn't show instantiations anymore (wasn't very useful) -* `!` now does type filtering (for example you can do `!x || x.bar`, assuming `x` can be `nil` and the non-nil type responds to `bar`) -* Named arguments can now match any argument, even if they don't have a default value. Make sure to read the [docs](http://crystal-lang.org/docs/syntax_and_semantics/default_and_named_arguments.html) -* The `as` operator can now be written as a method: `exp.as(Type)` in addition to `exp as Type`. The old syntax will be removed in a few releases. -* Added `@x : Int32 = 1` syntax (declaration + initialization) -* `new`/`initialize` logic now works more as one would expect -* Added `BigRational` (thanks @will) -* Added `BigFloat` (thanks @Exilor) -* Added `String#insert` -* Added `Time::EpochConverter` and `Time::EpochMillisConverter` -* Added `%s` (unix epoch) directive to `Time::Format` -* `Time` now honours Daylight Saving and `ENV["TZ"]` -* Added `HTTP::Server::Response#cookies` (thanks @jhass) -* Added `Array#bsearch`, `Array#bsearch_index` and `Range#bsearch` (thanks @MakeNowJust) -* Added `Range#reverse_each` iterator (thanks @omninonsense) -* `JSON::Any`: added `as_...?` methods (thanks @DougEverly) -* `JSON::Any` is now `Enumerable` -* `YAML::Any` is now `Enumerable` -* Added `JSON.parse_raw` that returns a `JSON::Type` -* `JSON::PullParser`: added `#read_raw` to read a JSON value as a raw string (useful for delayed parsing). Also added `String::RawConverter` to be used with `JSON.mapping`. -* `JSON` and `YAML`: enums, `BigInt` and `BigFloat` are now serializable -* `ENV`: allow passing `nil` as a value to delete an environment variable -* `Hash`: allow `Array | Tuple` arguments for `#select`, `#select!`, `#reject` and `#reject!` -* `Crypto::Subtle.constant_time_compare` now returns `Bool`, and it can compare two strings in addition to two slices (thanks @skunkworker) -* `HTTP::Server`: reset port zero after listening (thanks @splattael) -* Added `File#each_line` iterator -* Added `Number.slice`, `Number.static_array`, `Slice.[]` and `StaticArray.[]` to easily create slices and static arrays -* Added `Slice#hexdump` (thanks @will) -* Added `Enumerable#product` (thanks @dkhofer) -* Fix: disallow using `out` with `Void*` pointers -* Fixed bug in `XML::Node#namespace_scopes` (thanks @Hamdiakoguz) -* Added docs for `INIFile` (thanks @EvanHahn) -* Lots of bug fixes - -## 0.15.0 (2016-03-31) - -* **(breaking change)** `!` has now its meaning hardcoded in the language. If you defined it for a type it won't be invoked as a method anymore. -* **(breaking change)** `nil?` has now its meaning hardcoded in the language. If you defined it for a type it won't be invoked as a method anymore. -* **(breaking change)** `typeof` is now disallowed in `alias` declarations -* Added `crystal tool format --check` to check that source code is properly formatted -* `crystal play` (playground) added workbooks support, as well as improvements and stabilizations -* Added `Tempfile.dirname` (thanks @DougEverly) -* Added `Path#resolve` method in macros -* `{{...}}` arguments to a macro call are now expanded before macro invocation ([#2392](https://github.com/crystal-lang/crystal/issues/2392)) -* Special variables (`$~` and `$?`) are now accessible after being defined in blocks ([#2194](https://github.com/crystal-lang/crystal/issues/2194)) -* Some bugs and regressions fixed - -## 0.14.2 (2016-03-22) - -* Fixed regression with formatter ([#2348](https://github.com/crystal-lang/crystal/issues/2348)) -* Fixed regression with block return types ([#2347](https://github.com/crystal-lang/crystal/issues/2347)) -* Fixed regression with openssl (https://github.com/crystal-lang/crystal/commit/78c12caf2366b01f949046e78ad4dab65d0d80d4) - -## 0.14.1 (2016-03-21) - -* Fixed some regressions in the formatter - -## 0.14.0 (2016-03-21) - -* **(breaking change)** The syntax of a method argument with a default value and a type restriction is now `def foo(arg : Type = default_value)`. The old `def foo(arg = default_value : Type)` was removed. -* **(breaking change)** `Enumerable#take(n)` and `Iterator#take(n)` were renamed to `first(n)` -* **(breaking change)** `Socket#addr` and `Socket#peeraddr` were renamed to `local_address` and `remote_address` respectively -* **(breaking change)** Removed `Comparable#between?(a, z)`. Use `a <= x <= z` instead -* **(breaking change)** `HTTP::WebSocketHandler` callbacks can now access the `HTTP::Context`. If you had a forwarding method to it you'll need to update it. See [#2313](https://github.com/crystal-lang/crystal/issues/2313). -* New command `crystal play` that opens a playground for you to play in the browser :-) (thanks @bcardiff) -* New command `crystal env` that prints environment information -* `Spec`: you can now run multiple files with specified line numbers, as in `crystal spec file1.cr:10 file2.cr:20 ...` -* Initial support for musl-libc (thanks @ysbaddaden) -* Added `FileUtils.cp` (thanks @Dreauw) -* Added `Array#first(n)` and `Array#last(n)` (thanks @greyblake) -* Added `WebSocket#close` and properly handle disconnections -* Added `UDPSocket#send` and `UDPSocket#receive` (thanks @tatey) -* Added `Char#uppercase?` and `Char#lowercase?` (thanks @MaloJaffre`) -* Added `sync_close` property to `OpenSSL::SSL::Socket`, `Zlib::Inflate` and `Zlib::Deflate` -* Added `XML::Node#encoding` and `XML::Node#version` -* Added `HTTP::Client::Response#success?` (thanks @marceloboeira) -* Added `StaticArray#shuffle!(random)` (thanks @Nesqwik) -* Added `Splat#exp` method in macros -* Added fiber-safe `Mutex` -* All `Int` types (except `BigInt`) can now be used in `JSON` and `YAML` mappings (thanks @marceloboeira) -* Instance variable declarations/initializations now correctly work in generic classes and modules -* Lots of bug fixes - -## 0.13.0 (2016-03-07) - -* **(breaking change)** `Matrix` was moved to a separate shard: [https://github.com/Exilor/matrix](https://github.com/Exilor/Matrix) -* The syntax of a method argument with a default value and a type restriction is now `def foo(arg : Type = default_value)`. Run `crystal tool format` to automatically upgrade existing code to this new syntax. The old `def foo(arg = default_value : Type)` syntax will be removed in a next release. -* Special handling of `case` with a tuple literal. See [#2258](https://github.com/crystal-lang/crystal/pull/2258). -* Keywords can now be used for variable declaration, so `property end : Time` works as expected. -* Comparison of signed vs. unsigned integers now always give a correct result -* Allow declaring instance variables in non-generic module types (`module Moo; @x : Int32; end`) -* Allow initializing instance variables in non-generic module types (`module Moo; @x = 1; end`) -* `Spec`: allow setting multiple output formatters (thanks @marceloboeira) -* `StringScanner`: improved performance -* Added `foo.[0] = 1` and `foo.[0]` as valid syntax, similar to the one in `&.` blocks (thanks @MakeNowJust) -* `CSV`: allow separate and quote characters different than comma and doble quote (thanks @jreinert) -* `YAML`: support merge operator (`<<`) (thanks @jreinert) -* Allow redefining primitive methods like `Int32#+(other : Int32)` -* Allow defining macros with operator names like `[]` -* `Levenshtein`: improved performance (thanks @tcrouch) -* `HTTP::Client`: fixed incorrect parsing of chunked body -* `HTTP::Client`: added a constructor with an `URI` argument (thanks @plukevdh) -* `String`: `sub` and `gsub` now understand backreferences (thanks @bjmllr) -* `Random`: added `Random#rand(Float64)` and `Random#rand(Range(Float, Float))` (thanks @AlexWayfer) -* `HTML`: `HTML.escape` includes more characters (thanks @Ryuuzakis) -* Added `TypeNode.class` method in macros (thanks @waterlink) -* `run` inside macros now also work with absolute paths (useful when used with `__DIR__`) -* Added docs for `Math` and `StaticArray` (thanks @Zavydiel, @HeleneMyr) -* Many bug fixes and some micro-optimizations - -## 0.12.0 (2016-02-16) - -* **(breaking change)** When used with a type declaration, the macros `property`, `getter`, `setter`, etc., declare instance variables with those types. -* **(breaking change)** `JSON.mapping` and `YAML.mapping` declare instance variables with the given types. -* **(breaking change)** `YAML.load` was renamed to `YAML.parse`, and it now returns a `YAML::Any`. -* **(breaking change)** `embed_ecr` and `ecr_file` were renamed to `ECR.embed` and `ECR.def_to_s` (the old methods now produce a warning and will be removed in the next release). -* Added encoding support: `IO#set_encoding`, `String#encode`, and `HTTP::Client` charset check. -* Segmentation faults are now trapped and shown in a more friendlier way. -* The `record` macro can now accept type declarations (for example `record Point, x : Int32, y : Int32`) -* Added `Iterator#step` (thanks @jhass) -* `Array#push` and `Array#unshift` can now accept multiple values and add the elements in an efficient way (thanks @arktisklada) -* Added `default` option to `JSON.mapping` (thanks @kostya) -* Added `default` option to `YAML.mapping` (thanks @jreinert) -* Allow doing `case foo; when Foo.class` (and `Foo(T)` and `Foo(T).class`) in case expressions. -* Added `Class#|` so a union type can be expresses as `Int32 | Char` in regular code. -* Added `File.real_path` (thanks @jreinert) -* Added `dns_timeout` for `HTTP::Client` (thanks @kostya) -* Added dynamic width precision to `sprintf` (thanks @gtramontina) -* `Markdown` now supports blockquotes and 1 level of list nesting (thanks @SebastianSzturo) -* `p` now accepts multiple arguments -* Many bug fixes and some optimizations - -## 0.11.1 (2016-01-25) -* Fixed [#2050](https://github.com/crystal-lang/crystal/issues/2050), [#2054](https://github.com/crystal-lang/crystal/issues/2054), [#2057](https://github.com/crystal-lang/crystal/issues/2057), [#2059](https://github.com/crystal-lang/crystal/issues/2059), [#2064](https://github.com/crystal-lang/crystal/issues/2064) -* Fixed bug: HTTP::Server::Response headers weren't cleared after each request -* Formatter would incorrectly change `property x :: Int32` to `property x = uninitialized Int32` - -## 0.11.0 (2016-01-23) - -* **(breaking change)** Syntax for type declarations changed from `var :: Type` to `var : Type`. The old syntax is still allowed but will be deprecated in the next version (run `crystal tool format` to automatically fix this) -* **(breaking change)** Syntax for uninitialized variables, which used to be `var :: Type`, is now `var = uninitialized Type`. The old syntax is still allowed but will be deprecated in the next version (run `crystal tool format` to automatically fix this) -* **(breaking change)** `HTTP::Server` refactor to support streaming. Check the [docs](http://crystal-lang.org/api/HTTP/Server.html) of `HTTP::Server` for upgrade instructions -* **(breaking change)** Renamed `HTTP::WebSocketSession` to `HTTP::WebSocket`. -* **(breaking change)** Heredocs now remove indentations according to the indentation of the closing identifier (thanks @rhysd) -* **(breaking change)** Renamed `Enumerable#inject` to `Enumerable#reduce` -* **(breaking change)** `next` and `return` semantic inside captured block has been swapped ([#420](https://github.com/crystal-lang/crystal/issues/420)) -* Fibers context switch is now faster, done with inline assembly. `libpcl` is no longer used -* Allow annotating the type of class and global variables -* Support comments in ECR (thanks @ysbaddaden) -* Security improvements to `HTTP::StaticFileHandler` (thanks @MakeNowJust) -* Moved `seek`, `tell`, `pos` and `pos=` from `File` to `IO::FileDescriptor` (affects `Tempfile`) -* `URI.parse` is now faster (thanks @will) -* Many bug fixes, some really old ones involving issues with order of declaration - -## 0.10.2 (2016-01-13) - -* Fixed Directory Traversal Vulnerability in HTTP::StaticFileHandler (thanks @MakeNowJust) - -## 0.10.1 (2016-01-08) - -* Added `Int#popcount` (thanks @rmosolgo) -* Added `@[Naked]` attribute for omitting a method's prelude -* Check that abstract methods are implemented by subtypes -* Some bug fixes - -## 0.10.0 (2015-12-23) - -* **(breaking change)** `def` arguments must always be enclosed in parentheses -* **(breaking change)** A space is now required before and after def return type restriction -* **(breaking change)** Renamed `Dir.working_dir` to `Dir.current` -* **(breaking change)** Moved `HTML::Builder` to [its own shard](https://github.com/crystal-lang/html_builder) -* **(breaking change)** `String#split` now always keeps all results (never drops trailing empty strings) -* **(breaking change)** Removed `Array#buffer`, `StaticArray#buffer` and `Slice#buffer`. Use `to_unsafe` instead (so unsafe usages are easier to spot) -* **(breaking change)** Removed `String#cstr`. Use `to_unsafe` instead (so unsafe usages are easier to spot) -* Optimized Range#sum (thanks @MakeNowJust) -* Allow forward declarations for lib external vars -* Added `Int#to_s(base)` for `base = 62` (thanks @jhass) -* `JSON.parse` now returns `JSON::Any`, which allows traversal of JSON objects with less casts -* Added `OpenSSL::PKCS5` (thanks @benoist) -* MemoryIO can now be created to read/write from a Slice(UInt8). In this mode MemoryIO can't be expanded, and can optionally be written. And when creating a MemoryIO from a String, it's non-resizeable and read-only. -* Added `Object#!~` (the opposite of `=~`) -* `at_exit` now receives that exit status code in the block (thanks @MakeNowJust) -* Allow using `Set` in JSON mappings (thanks @benoist) -* Added `File.executable?`, `File.readable?` and `File.writeable?` (thanks @mverzilli) -* `Array#sort_by` and `Array#sort_by!` now use a [Schwartzian transform](https://en.wikipedia.org/wiki/Schwartzian_transform) (thanks @radarek) -* Added `Array#each_permutation`, `Array#each_combination` and `Array#each_repeated_combination` iterators -* Added optional *random* argument to `Array#sample` and `Array#shuffle` -* The `delegate` macro can now delegate multiple methods to an object (thanks @elthariel) -* Added basic YAML generation (thanks @porras) - -## 0.9.1 (2015-10-30) - -* Docs search now finds nested entries (thanks @adlerhsieh) -* Many corrections and changes to the formatter, for better consistency and less obtrusion. -* Added `OpenSSL::Cipher` and `OpenSSL::Digest` (thanks @benoist) -* Added `Char#+(String)` (thanks @hangyas) -* Added `Hash#key` and `Hash#key?` (thanks @adlerhsieh) -* Added `Time::Span#*` and `Time::Span#/` (thanks @jbaum98) -* Added `Slice#reverse_each` (thanks @omninonsense) -* Added docs for `Random` and `Tempfile` (thanks @adlerhsieh) -* Fixed some bugs. - -## 0.9.0 (2015-10-16) - -* **(breaking change)** The `CGI` module's functionality has been moved to `URI` and `HTTP::Params` -* **(breaking change)** `IO#read()` is now `IO#gets_to_end`. Removed `IO#read(count)`, added `IO#skip(count)` -* **(breaking change)** `json_mapping` is now `JSON.mapping`. `yaml_mapping` is now `YAML.mapping` -* **(breaking change)** `StringIO` is now `MemoryIO` -* Added `crystal tool format` that automatically formats your code -* `protected` methods can now be invoked between types inside a same namespace -* Removed `curses`, you can use `https://github.com/jreinert/ncurses-crystal` -* `to_unsafe` and numeric conversions are now also automatically performed in C struct and union fields -* Added `{% begin %} ... {% end %}` as an alternative to `{% if true %} ... {% end %}` -* Added `~!` operator -* Added debug metadata for char, float, bool and enums. Also for classes and structs (experimental) -* `Dir.glob` now works well with recursive patterns like `**` (thanks @pgkos) -* Added `read_timeout` and `connect_timeout` to `HTTP::Client` (thanks @benoist) -* Added `Zlib` (thanks @datanoise and @bcardiff) -* Added `HTTP::DeflateHandler` (thanks @bcardiff) -* Added `ENV#fetch` (thanks @tristil) -* `Hash#new` now accepts an initialize capacity argument -* `HTTP::Request` provides access and mutation of `query`, `path` and `query_params` (thanks @waterlink) -* Added `XML::Node#content=` and `#name=` -* Allow passing handlers and a block to an `HTTP::Server` (thanks @RX14) -* `crystal init` now tries to use your github username if available (thanks @jreinert) -* Added `Hash#select`, `Hash#reject` and their bang variant, and `Hash#each_with_object` (thanks @devdazed) -* Added `Hash#select(*keys)` and `Hash#reject(*keys)` and their bang variant (thanks @sdogruyol) -* Added `Set#-`, `Set#^`, and `Set#subtract` (thanks @js-ojus) -* Allow running specs without colors (thanks @rhysd) -* Added `TypeNode#has_constant?` and `TypeNode#type_vars` in macros (thanks @jreinert) -* Added `String#compare` that allows case insensitive comparisons -* Added `File#truncate` (thanks @porras) -* `CSV` is now a class for iterating rows, optionally with headers access -* Allow setting multiple `before_request` callbacks to an `HTTP::Client` -* Added `Dir.cd(&block)` (thanks @rhysd) -* Added `Class#cast` (thanks @will) -* Fixes and additions to WebSocket, like the possibility of streaming data (thanks @jreinert) -* Added `SemanticVersion` class (thanks @technorama) -* `loop` now yields a counter -* Added `Array#[]=(index, count, value)` and `Array#[]=(range, value)` -* Added argless `sleep` -* `IO#write(slice)` now writes the full slice or raises on error -* Added some docs for ECR, Markdown, Hash, File, Time, Time::Span, Colorize, String, SecureRandom, YAML (thanks @adlerhsieh, @chdorner, @vjdhama, @rmosolgo) -* Many bug fixes - -## 0.8.0 (2015-09-19) - -* **(breaking change)** Renamed a couple of types: `ChannelClosed` -> `Channel::ClosedError`, - `UnbufferedChannel` -> `Channel::Unbuffered`, `BufferedChannel` -> `Channel::Buffered`, - `DayOfWeek` -> `Time::DayOfWeek`, `MonthSpan` -> `Time::MonthSpan`, `TimeSpan` -> `Time::Span`, - `TimeFormat` -> `Time::Format`, `EmptyEnumerable` -> `Enumerable::EmptyError`, `SocketError` -> `Socket::Error`, - `MatchData` -> `Regex::MatchData`, `SignedInt` -> `Int::Signed`, `UnsignedInt` -> `Int::Unsigned`, - `FileDescriptorIO` -> `IO::FileDescriptor`, `BufferedIO` -> `IO::Buffered`, `CharReader` -> `Char::Reader`, - `PointerAppender` -> `Pointer::Appender`. -* **(breaking change)** All places that raised `DomainError` raise `ArgumentError` now. -* **(breaking change)** Renamed `Type.cast` to `Type.new` (for example, `Int32.new` instead of `Int32.cast`) -* **(breaking change)** Removed all macro instance variables except `@type` -* **(breaking change)** Removed `undef` -* **(breaking change)** Removed `length()` and `count()` methods from collections. The only method for this is now `size`. -* **(breaking change)** Removed the ability to invoke methods on a union class -* Improved debugger support -* `crystal deps` now delegates to [shards](https://github.com/ysbaddaden/shards). Removed `Projecfile` support. -* Automatically convert numeric types when invoking C functions -* Automatically define questions methods for enum members -* Support quotes inside quoted symbols (thanks @wolflee) -* Allow marking `initialize` as private -* Added `method_added` macro hook (thanks @MakeNowJust) -* Added `ArrayLiteral#includes?(obj)` in macros -* Added `ASTNode#symbolize` in macros (thanks @kirbyfan64) -* Added experimental `yaml_mapping` -* Added nilable variants to `Enumerable#max`, `Enumerable#min`, and others (thanks @technorama) -* Added `Iterator#flatten` (thanks @jpellerin) -* Added setting a read timeout to `HTTP::Client` (thanks @benoist) -* Added `Array#delete_at(index, count)` and `Array#delete_at(range)` (thanks @tebakane) -* Added `HTTP::Cookies` (thanks @netfeed) -* Added `Tuple#reverse` (thanks @jhass) -* Added `Number#clamp` (thanks @technorama) -* Added several socket options (thanks @technorama) -* Added `WebSocket.open` (thanks @kumpelblase2) -* Added `Enum.flags` macro -* Added support for sending chunked content in HTTP server (thanks @bcardiff) -* Added `future`, `lazy` and `delay` concurrency methods (thanks @technorama) -* `fork` now returns a `Process` (thanks @technorama) -* Documented `Set`, and added a couple of methods (thanks @will) -* Nicer formatting in `Benchmark.ips`, and interactive mode (thanks @will) -* The `-f` format output is now honored in compiler errors (thanks @kirbyfan64) -* Fixed an ambiguity with the `crystal build` command (thanks @MakeNowJust) -* Cast exceptions now raise `TypeCastError` instead of `Exception` (thanks @will) -* Many bugs fixes - -## 0.7.7 (2015-09-05) - -* **(breaking change)** Reimplemented `Process.run` to allow configuring input, output and error, as well as behaving well regarding non-blocking IO (thanks @technorama) -* **(breaking change)** Removed the `alias_method` macro. -* **(breaking change)** Disallow declaring defs, classes and other declarations "dynamically" (for example inside an `if`... this of course didn't work, but incorrectly compiled). -* **(breaking change)** `require` is now only allowed at the top-level, never inside other types or methods. -* **(breaking change)** Removed `Nil#to_i` -* **(breaking change)** Changed API of `Channel#select` toward a thread-safe one. -* **(breaking change)** The two methods that IO must implement are now `read(slice : Slice(UInt8))` and `write(slice : Slice(UInt8))`. -* New beautiful, searchable and more functional API docs. Thanks @rosylilly for the initial design, and @BlaxPirit for some improvements. -* CLI: Moved `browser`, `hierarchy` and `types` to `crystal tool ...` -* Added `crystal tool context` and `crystal tool implementations` for IDEs (thanks @bcardiff!!) -* `Int#>>(amount)` and `Int#<<(amount)` now give zero when `amount` is greater than the number of bits of the integer representation. -* Added `\%` escape sequence inside macros. -* Added aliases for the many C types (thanks @BlaxPirit) -* Added `Iterator#in_groups_of` (thanks @PragTob) -* Added optional `offset` argument to `Hash#each_with_index` (thanks @sergey-kucher) -* Added `Array#combinations`, `Array#each_combination`, `Array#repeated_combinations`, `Array#each_repeated_combination`, `Array#repeated_permutations`, `Array#each_repeated_permutation`, `Array.product` and `Array.each_product` (thanks @kostya) -* Added `Array#rotate` and `Array#rotate!` (thanks @kostya) -* Added `MatchData#pre_match` and `MatchData#post_match` (thanks @bjmllr) -* Added `Array#flatten` -* Added `Range.reverse_each`, along with `Int#pred` and `Char#pred` (thanks @BlaxPirit) -* Added `XML.parse_html` (thanks @ryanworl) -* Added `ENV.keys` and`ENV.values` (thanks @will) -* Added `StaticArray==(other : StaticArray)` (thanks @tatey) -* Added `String#sub` in many variants (thanks @jhass) -* Added `Readline.bind_key`, `Readline.unbind_key`, `Readline.done` and `Readline.done=` (thanks @daphee) -* Added `Hash#all?`, `Hash#any?` and `Hash#inject` (thanks @jreinert) -* Added `File#pos` and `File#pos=` -* Added `Enum.from_value` and `Enum.from_value?` -* Added `Deque` (thanks @BlaxPirit) -* Added lots of methods to `StringScanner`, and documented it, making it usable (thanks @will) -* `StringIO` now quacks like a `File`. -* Allow sending masked data through a `WebSocket`, and sending long data (thanks @kumpelblase2) -* `File.new` now accepts an optional `perm` argument (thanks @technorama) -* `FileDescriptorIO` now has configurable read/write timeouts (thanks @technorama) -* Signal handling is more robust and allows any kind of code (thanks @technorama) -* Correctly handle `WebSocket` close packet (thanks @bebac) -* Correctly implement `seek` and `tell` in buffered IOs (thanks @lbguilherme) -* Allow setting several options on sockets (thanks @technorama) -* Some improvements to `crystal init` for the "app" case (thanks @krisleech) -* `sleep` and IO timeouts can receive `TimeSpan` as arguments (thanks @BlaxPirit) -* Handle `HTTP::Response` without content-length (thanks @lbguilherme) -* Added docs for OptionParser, ENV, Regex, Enumerable, Iterator and some Array methods (thanks @porras, @will, @bjmllr, @PragTob, @decioferreira) -* Lots of bug fixes and small improvements - -## 0.7.6 (2015-08-13) - -* **(breaking change)** removed support for trailing `while`/`until` ([read this](https://github.com/crystal-lang/crystal/wiki/FAQ#why-trailing-whileuntil-is-not-supported-unlike-ruby)) -* **(breaking change)** Renamed `Enumerable#drop` to `Enumerable#skip` -* **(breaking change)** Renamed `Time.at` to `Time.epoch`, and `Time#to_i` and `Time#to_f` to `Time#epoch` and `Time#epoch_f` -* **(breaking change)** `inherited` macro now runs before a class' body -* Renamed `--no-build` flag to `--no-codegen` -* Allow interpolations in heredocs (thanks @jessedoyle) -* Allow hash substitutions in `String#%` and `sprintf` (thanks @zamith) -* Added `SecureRandom.base64`, `SecureRandom.urlsafe_base64` and `SecureRandom.uuid` (thanks @ysbaddaden) -* Added `File.link`, `File.symlink` and `File.symlink?` (thanks @ysbaddaden) -* Added `Enumerable#in_groups_of` (thanks @jalyna) -* Added `Array#zip?` (thanks @yui-knk) -* Added `Array#permutations` and `Array#each_permutation` (thanks @jalyna and @kostya) -* Added `IO#gets(limit : Int)` and `IO#gets(delimiter : Char, limit : Int)` -* Added `Iterator#compact_map`, `Iterator#take_while` and `Iterator#skip_while` (thanks @PragTob) -* Added `StringLiteral#to_i` macro method -* Added `Crypto::Bcrypt` (thanks @akaufmann) -* Added `Time.epoch_ms` and `Time#epoch_ms` -* Added `BitArray#toggle` and `BitArray#invert` (thanks @will) -* Fixed `IO#reopen` swapped semantic (thanks @technorama) -* Many bug fixes and improvements - -## 0.7.5 (2015-07-30) - -* **(breaking change)** `0` is not a prefix for octal numbers anymore. Use `0o` -* **(breaking change)** Renamed `MissingKey` to `KeyError` -* **(breaking change)** Renamed `IndexOutOfBounds` to `IndexError` -* Fixed all exception-handling related bugs. -* Allow nested and multiline ternary expressions (thanks @daviswahl) -* Allow assigning to `_` (underscore), give error when trying to read from it -* Macros can now also receive the following nodes: `And`, `Or`, `Case`, `RangeLiteral` and `StringInterpolation`. `And` and `Or` have `left` and `right` methods. -* Added `-e` option to `hierarchy` command to filter types by a regex -* Added `-v` as an alias of `--version` -* Added `-h` as an alias of `--help` -* Added `Array#transpose` (thanks @rhysd) -* Added `Benchmark#ips` (thanks @will) -* Added `Hash#merge(&block)` and `Hash#merge!(&block)` (thanks @yui-knk) -* Added `Hash#invert` (thanks @yui-knk) -* Added `Bool#^` (thanks @yui-knk) -* Added `Enumerable#drop`, `Enumerable#drop_while` and `Enumerable#take_while` (thanks @PragTob) -* Added `Enumerable#none?` (thanks @yui-knk) -* Added `Set#subset?`, `Set#superset?` and `Set#intersects?` (thanks @yui-knk) -* Added `Set#new(Enumerable)` (thanks @yui-knk) -* Added `String#succ` (thanks @porras and @Exilor) -* Added `Array#*` (thanks @porras) -* Added `Char#===(Int)` and `Int#===(Char)` (thanks @will) -* Added `StringLiteral#camelcase` and `StringLiteral#underscore` in macros -* Added `Expressions#expressions` in macros -* Added `Cast#obj` and `Cast#to` in macros -* Added `ASTNode#class_name` in macros (thanks @yui-knk) -* Added `Array#push`/`Array#<<` and `Array#unshift` in macros (thanks @ysbaddaden) -* Added `Def#visibility` in macros (thanks @ysbaddaden) -* Added `String#codepoints` and `String#each_codepoint` (thanks @jhass) -* `Char#to_i(base)` now supports bases from 2 to 36 -* `Set#|` now correctly accepts a set of a possible different type (thanks @yui-knk) -* Flush `STDERR` on exit (thanks @jbbarth) -* `HTTP::Client` methods accept an optional block, which will yield an `HTTP::Response` with a non-nil `body_io` property to consume the response's IO -* Document `URI`, `UDPSocket` (thanks @davydovanton) -* Improved `URI` class (thanks @will) -* Define `$~` in `String#gsub` and `String#scan` -* Define `$?` in `Process.run` -* Lots of bug fixes and small improvements - -## 0.7.4 (2015-06-23) - -* Added Float module and remainder (thanks @wmoxam) -* Show elapsed time in HTTP::LogHandler (thanks @zamith for the suggestion) -* Added `0o` as a prefix for octal numbers (thanks @asb) -* Allow spaces before the closing tag of a heredoc (thanks @zamith) -* `String#split(Regex)` now includes captures in the results -* Added `union?`, `union_types` and `type_params` in macro methods -* Improved `MatchData#to_s` to show named groups (thanks @will) -* Optimized Base64 encode/decode (thanks @kostya) -* Added basic docs for spec (thanks @PragTob) -* Added docs for Benchmark (thanks @daneb) -* Added `ARGF` -* Non-matching regex captures now return `nil` instead of an empty string (thanks @will) -* Added `$1?`, `$2?`, etc., as a nilable alternative to `$1`, `$2`, etc. -* Added user, password, fragment and opaque to URI (thanks @will) -* `HTTP::Client.exec` now honors user/password info from URI -* Set default user agent in `HTTP::Client` -* Added `String#chop` -* Fixed `crystal deps` behaviour with empty git repositories (thanks @tkrajcar) -* Optimized `HTTP::Headers` and `HTTP::Request` parsing. -* `FileDescriptorIO` (superclass of `File` and `Socket`) has now buffering capabilities (use `sync=` and `sync?` to turn on/off). That means there's no need to use `BufferedIO` for these classes anymore. -* Allow `pointerof` with class and global variables, and also `foo.@bar` access -* Optimized fibers performance. -* Added inline assembly support. -* The `.crystal` cache dir is now configurable with an ENV variable (thanks @jhass) -* Generic type variables names can now also be a single letter followed by a digit. - -## 0.7.3 (2015-06-07) - -* Added `Tuple.from_json` and `Tuple.to_json` -* The `method_missing` macro now accepts a 1 argument variant that is a Call node. The 3 arguments variant will be deprecated. -* Flush STDOUT at program exit (fixes `print` not showing any output) -* Added `Time#to_utc` and `Time#to_local` (thanks @datanoise) -* Time comparison is now correct when comparing local vs. utc times -* Support timezone offsets in Time parsing and formatting -* Added `IO#gets(delimiter : String)` -* Added `String#chomp(Char)` and `String#chomp(String)` -* Allow invoking `debug()` inside a macro to see what's being generated. -* `IO#puts` and `IO#print` now receive a splat (thanks @rhysd) -* Added `Process.kill` and `Process.getpgid` (thanks @barachy) -* `Signal` is now an enum. Use it like `Signal::INT.trap { ... }` instead of `Signal.trap(Signal::INT) { ... }` -* Added `CSV.each_row` (both in block and iterator forms) -* Important fixes to non-blocking IO logic. - -## 0.7.2 (2015-05-26) - -* Improved performance of Regex -* Fixed lexing of octal characters and strings (thanks @rhysd) -* Time.parse can return UTC times (thanks @will) -* Handle dashes in `crystal init` (thanks @niftyn8) -* Generic type variables can now only be single letters (T, U, A, B, etc.) -* Support `%x` and `%X` in `sprintf` (thanks @yyyc514) -* Optimized `Int#to_s` (thanks @yyyc514) -* Added `upcase` option to `Int#to_s`, and use downcase by default. -* Improved `String#to_i` and fixed the many variants (`to_i8`, `to_u64`, etc.) -* Added `Time.at` (thanks @jeromegn) -* Added `Int#upto`, `Int#downto`, `Int#to` iterators. -* Added `Iterator#cons` and `Enumerable#each_cons` (thanks @porras) -* Added `Iterator.of`, `Iterator#chain` and `Iterator#tap`. -* Allow top-level `private macro` (similar to top-level `private def`) -* Optimized `BufferedIO` writing performance and memory usage. -* Added `Channel#close`, `Channel#closed?`, `Channel#receive?` and allow them to send/receive nil values (thanks @datanoise). -* Fixed `Process#run` after introducing non-blocking IO (thanks @will) -* `Tuple#map` now returns a `Tuple` (previously it returned an `Array`) -* `Tuple#class` now returns a proper `Class` (previously it returned a `Tuple` of classes) -* Lots of bug fixes. - -## 0.7.1 (2015-04-30) - -* Fixed [#597](https://github.com/crystal-lang/crystal/issues/597). -* Fixed [#599](https://github.com/crystal-lang/crystal/issues/599). - -## 0.7.0 (2015-04-30) - -* Crystal has evented IO by default. Added `spawn` and `Channel`. -* Correctly support the X86_64 and X86 ABIs. Now bindings to C APIs that pass and return structs works perfectly fine. -* Added `crystal init` to quickly create a skeleton library or application (thanks @waterlink) -* Added `--emit` flag to the compiler. Now you can easily see the generated LLVM IR, LLVM bitcode, assembly and object files. -* Added `--no-color` flag to suppress color output, useful for editor tools. -* Added macro vars: `%var` and `%var{x, y}` create uniquely named variables inside macros. -* Added [typed splats](https://github.com/crystal-lang/crystal/issues/291). -* Added `Iterator` and many methods that return iterators, like `Array#each`, `Hash#each`, `Int#times`, `Int#step`, `String#each_char`, etc. -* Added `sprintf` and improved `String#%` to support floats and float formatting. -* Added more variants of `String#gsub`. -* Added `Pointer#clear` and use it to clear an `Array`'s values when doing `pop` and other shrinking methods. -* Added `BigInt#to_s(base)`, `BigInt::cast` and bit operators (thanks @Exilor) -* Allow invoking methods on a union class as long as all types in the union have it. -* Allow specifying a def's return type. The compiler checks the return type only for that def for now (not for subclasses overriding the method). The return type appears in the documentation. -* Allow constants and computed constants for a StaticArray length. -* Allow class vars in enums. -* Allow private and protected defs in enums. -* Allow reopening a `lib` and adding more `@[Link]` attributes to it, even allowing duplicated attributes. -* Allow getting a function pointer to a lib fun without specifying its types (i.e. `->LibC.getenv`) -* Allow specifying `ditto` for a doc comment to reuse the previous comment. -* Changed the semantic of `%`: previously it meant `remainder`, not it means `modulo`, similar to Ruby and Python. Added `Int#remainder`. -* `#to_s` and `#inspect` now work for a union class. -* Spec: added global `before_each` and `after_each` hooks, which will simplify the use of mocking libraries like [timecop.cr](https://github.com/waterlink/timecop.cr) and [webmock.cr](https://github.com/manastech/webmock.cr). -* `Range(T)` is now `Range(B, E)` again (much more flexible). -* Improved Regex performance. -* Better XML support. -* Support LLVM 3.6. -* Exception class is now shown on unhandled exceptions -* The following types are now disallowed in generics (for now): Object, Value, Reference, Number, Int and Float. -* Lots of bug fixes, enhancements and optimizations. - -## 0.6.1 (2015-03-04) - -* The `class` method now works in all cases. You can now compare classes with `==` and ask their `hash` value. -* Block variables can now shadow local variables. -* `Range(B, E)` is now `Range(T)`. -* Added `Number::[]`. Now you can do `Int64[1, 2, 3]` instead of `[1_i64, 2_i64, 3_u64]`. -* Better detection of nilable instance variables, and better error messages too. -* Added `Crypto::Blowfish` (thanks @akaufmann) -* Added `Matrix` (thanks @Exilor) -* Added `CallConvention` attribute for `fun`s. -* Macros: added `constants` so you can inspect a type's constants at compile time. -* Macros: added `methods`, which lists a type's methods (without including supertypes). -* Macros: added `has_attribute?` for enum types, so you can check if an enum has the Flags attribute on it. -* Many more small additions and bug fixes. - -## 0.6.0 (2015-02-12) - -* Same as 0.5.10 - -## 0.5.10 (transitional) (2015-02-12) - -* **Note**: This release makes core, breaking changes to the language, and doesn't work out of the box with its accompanying standard library. Use 0.6.0 instead. -* Improved error messages related to nilable instance variables. -* The magic variables `$~` and `$?` are now method-local and concurrent-safe. -* `Tuple` is now correctly considered a struct -* `Pointer` is now correctly considered a struct -* Renamed `Function` to `Proc` - -## 0.5.9 (2015-02-07) - -* `Random` is now a module, with static methods that default to the `Random::MT19937` class. -* Added `Random::ISAAC` engine (thanks @ysbaddaden!) -* Added `String#size` (thanks @zamith!) -* Added `limit` to all `String#split` variants (thanks @jhass!) -* Raising inside a Thread is now rescued and re-raised on join (thanks @jhass!) -* Added `path` option to Projectfile for `crystal deps` (thanks @naps62!) -* Many fixes towards making Crystal work on linux 32 bits. -* Huge refactors, additions and improvements for sockets: Socket, IPSocket, TCPSocket, TCPServer, UDPSocket, UNIXSocket, UNIXServer (thanks @ysbaddaden!) -* Allow regex with empty spaces in various places. -* Added `HTML.escape(String)` (thanks @naps62!) -* Added support for `%w[...]`, `%w{...}`, `%w<...>` as alternatives to `%w(...)`. Same goes for `%i(...)` (thanks @zamith!) -* Added `Enumerable#min_of`, `Enumerable#max_of` and `Enumerable#minmax_of`, `Enumerable#to_h`, `Dir.chdir` and `Number#fdiv` (thanks @jhass!) -* Added `String#match`, `String#[]`, `String#[]?` and `MatchData#[]? ` related to regexes (thanks @jhass!) -* Allow `T::Bar` when T is a generic type argument. -* Added `subclasses` and `all_subclasses` in macros. -* Now you can invoke `to_s` and `inspect` on C structs and unions, making debugging C bindings much easier! -* Added `#to_f` and `#to_i` to `Time` and `TimeSpan` (thanks @epitron!) -* Added `IO.select` (thanks @jhass!) -* Now you can use `ifdef` inside C structs and unions. -* Added `include` inside C structs, to include other struct fields (useful for composition and avoiding an explicit indirection). -* Added `Char#in_set?`, `String#count`, `String#delete` and `String#squeeze` (thanks @jhass!) -* Added `-D flag` option to the compiler to set compile-time flags to use in `ifdef`. -* More support for forward declarations inside C libs. -* Rewritten some `Function` primitives in Crystal itself, and added methods for obtaining the pointer and closure data, as well as for recreating a function from these. -* Added a `Logger` class (thanks @ysbaddaden!) -* Lots of bugs fixed. - -## 0.5.8 (2015-01-16) - -* Added `Random` and `Random::MT19937` (Mersenne Twister) classes (thanks @rhysd). -* Docs: removed automatic linking. To link to classes and methods surround with backticks. -* Fixed [#328](https://github.com/crystal-lang/crystal/issues/328): `!=` bug. - -## 0.5.7 (2015-01-02) - -* Fixed: `doc` command had some hardcoded paths and didn't work -* Added: `private def` at the top-level of a file is only available inside that file - -## 0.5.6 (2014-31-12) - -* Added a `crystal doc` command to automatically generate documentation for a project using [Markdown](http://daringfireball.net/projects/markdown/) syntax. The style is still ugly but it's quite functional. Now we only need to start documenting things :-) -* Removed the old `@:` attribute syntax. -* Fixed [#311](https://github.com/crystal-lang/crystal/issues/311): Issues with invoking lib functions in other ways (thanks @scidom). -* Fixed [#314](https://github.com/crystal-lang/crystal/issues/314): NoReturn information is not lazy. -* Fixed [#317](https://github.com/crystal-lang/crystal/issues/317): Fixes in UTF-8 encoding/decoding (thanks @yous). -* Fixed [#319](https://github.com/crystal-lang/crystal/issues/319): Unexpected EOF (thanks @Exilor). -* `{{yield}}` inside macros now preserve the yielded node location, leading to much better error messages. -* Added `Float#nan?`, `Float#infinite?` and `Float#finite?`. -* Many other bug fixes and improvements. - -## 0.5.5 (2014-12-12) - -* Removed `src` and crystal compiler `libs` directory from CRYSTAL_PATH. -* Several bug fixes. - -## 0.5.4 (2014-12-04) - -* **(breaking change)** `require "foo"` always looks up in `CRYSTAL_PATH`. `require "./foo"` looks up relative to the requiring file. -* **(breaking change)** Renamed `Json` to `JSON`, `Xml` to `XML` and `Yaml` to `YAML` to follow [a convention](https://github.com/crystal-lang/crystal/issues/279). -* **(breaking change)** To use HTTP types do, for example, `require "http/client"` instead of the old `require "net/http"`. -* Added `alias_method` macro (thanks @Exilor and @jtomschroeder). -* Added some `Complex` number methods and many math methods, refactors and specs (thanks @scidom). -* Inheriting generic classes is now possible. -* Creating arrays of generic types (i.e.: `[] of Thread`) is now possible. -* Allow using an alias in a block type (i.e.: `alias F = Int32 ->`, `&block : F`). -* `json_mapping` macro supports a simpler syntax: `json_mapping({key1: Type1, key2: Type2})`. -* Spec: added `be_a(type)` matcher. -* Spec: added `be > ...` and similar matchers for `>=`, `<` and `<=`. -* Added `File::file?` and `File::directory?`. -* CSV parser can parse from String or IO. -* When invoking the compiler like this: `crystal foo.cr -o foo` the `build` command is assumed instead of `run`. -* Added short symbol notation for methods that are operators (i.e. `:+`, `:*`, `:[]`, etc.). -* Added `TimeSpan#ago`, `TimeSpan#from_now`, `MonthSpan#ago` and `MonthSpan#from_now`. - -## 0.5.3 (2014-11-06) - -* Spec: when a `should` or `should_not` fail, the filename and line number, including the source's line, is included in the error message. -* Spec: added `-l` switch to be able to run a spec defined in a line. -* Added `crystal spec file:line` -* Properties (property, setter, getter) can now be restricted to a type with the syntax `property name :: Type`. -* Enums can be used outside `lib`. They inherit `Enum`, can have methods and can be marked with @[Flags]. -* Removed the distinction between `lib` enums and regular enums. -* Fixed: it was incorrectly possible to define `class`, `def`, etc. inside a call block. -* The syntax for specifying the base type of an enum, `enum Name < BaseType` has been deprecated. Use `enum Name : BaseType`. -* Added `Array#<=>` and make it comparable to other arrays. - -## 0.5.2 (2014-11-04) - -* New command line interface to the compiler (`crystal build ...`, `crystal run ...`, `crystal spec`, etc.). The default is to compiler and run a program. -* `crystal eval` without arguments reads from standard input. -* Added preliminar `crystal deps` command. -* `__FILE__`, `__DIR__` and `__LINE__`, when used as def default arguments, resolve to the caller location (similar to [D](http://dlang.org/traits.html#specialkeywords) and [Swift](https://developer.apple.com/swift/blog/?id=15)) -* Allow `as` to determine a type even if the casted value doesn't have a type yet. -* Added `is_a?` in macros. The check is against an [AST node](https://github.com/crystal-lang/crystal/blob/master/src/compiler/crystal/syntax/ast.cr) name. For example `node.is_a?(HashLiteral)`. -* Added `emit_null` property to `json_mapping`. -* Added `converter` property to `json_mapping`. -* Added `pp` in macros. -* Added `to_pretty_json`. -* Added really basic `CSV.parse`. -* Added `Regex.escape`. -* Added `String#scan`. -* Added `-e` switch to spec, to run specs that match a pattern. -* Added `--fail-fast` switch to spec. -* Added `HTTPClient#basic_auth`. -* Added `DeclareVar`, `Def` and `Arg` macro methods. -* Added `Time` and `TimeSpan` structs. `TimeWithZone` will come later. -* Added `Array#fill` (thanks @Exilor). -* Added `Array#uniq`. -* Optimized `File.read_lines`. -* Allow any expression inside `{% ... %}` so that you can interpret code without outputting the result. -* Allow `\` at the end of a line. -* Allow using `if` and `unless` inside macro expressions. -* Allow marking a `fun/def` as `@[Raises]` (useful when a function can potentially raise from a callback). -* Allow procs are now considered `@[Raises]`. -* `OAuth2::Client` supports getting an access token via authorization code or refresh token. -* Consecutive string literals are automatically concatenated by the parser as long as there is a `\` with a newline between them. -* Many bug fixes. - -## 0.5.1 (2014-10-16) - -* Added [json_mapping](https://github.com/crystal-lang/crystal/blob/master/spec/std/json/mapping_spec.cr) macro. -* Added [Signal](https://github.com/crystal-lang/crystal/blob/master/src/signal.cr) module. -* Added [Tempfile](https://github.com/crystal-lang/crystal/blob/master/src/tempfile.cr) class. -* Enhanced [HTTP::Client](https://github.com/crystal-lang/crystal/blob/master/src/net/http/client/client.cr). -* Added [OAuth::Consumer](https://github.com/crystal-lang/crystal/blob/master/libs/oauth/consumer.cr). -* Added [OAuth2::Client](https://github.com/crystal-lang/crystal/blob/master/libs/oauth2/client.cr). -* Added [OpenSSL::HMAC](https://github.com/crystal-lang/crystal/blob/master/libs/openssl/hmac.cr). -* Added [SecureRandom](https://github.com/crystal-lang/crystal/blob/master/src/secure_random.cr). -* New syntax for array/hash-like classes. For example: `Set {1, 2, 3}` and `HTTP::Headers {"content-type": "text/plain"}`. These just create the type and use `<<` or `[]=`. -* Optimized Json parsing performance. -* Added a [CSV builder](https://github.com/crystal-lang/crystal/blob/master/src/csv.cr#L13). -* XML reader can [parse from an IO](https://github.com/crystal-lang/crystal/blob/master/src/xml/reader.cr#L10). -* Added `Dir::glob` and `Dir::Entries` (thanks @jhass) -* Allow `ensure` as an expression suffix. -* Fixed [#219](https://github.com/crystal-lang/crystal/issues/219): Proc type is not inferred when passing to library fun and the return type doesn't match. -* Fixed [#224](https://github.com/crystal-lang/crystal/issues/224): Class#new doesn't pass a block. -* Fixed [#225](https://github.com/crystal-lang/crystal/issues/225): ICE when comparing void to something. -* Fixed [#227](https://github.com/crystal-lang/crystal/issues/227): Nested captured block looses scope and crashes compiler. -* Fixed [#228](https://github.com/crystal-lang/crystal/issues/228): Macro expansion doesn't retain symbol escaping as needed. -* Fixed [#229](https://github.com/crystal-lang/crystal/issues/229): Can't change block context if defined within module context. -* Fixed [#230](https://github.com/crystal-lang/crystal/issues/230): Type interference breaks equality operator. -* Fixed [#233](https://github.com/crystal-lang/crystal/issues/233): Incorrect `no block given` message with new. -* Other bug fixes. - -## 0.5.0 (2014-09-24) - -* String overhaul, and optimizations - -## 0.4.5 (2014-09-24) - -* Define backtick (`) for command execution. -* Allow string literals as keys in hash literals: `{"foo": "bar"} # :: Hash(String, String)` -* Allow `ifdef` as a suffix. -* Integer division by zero raises a `DivisionByZero` exception. -* Link attributes are now only processed if a lib function is used. -* Removed the `type Name : Type` syntax (use `type Name = Type` instead). -* Removed the `lib Lib("libname"); end` syntax. Use `@[Link]` attribute instead. -* Fixed some `require` issues. -* String representation includes length. -* Upgraded to LLVM 3.5. - -## 0.4.4 (2014-09-17) - -* Fixed [#193](https://github.com/crystal-lang/crystal/issues/193): allow initializing an enum value with another's one. -* The `record` macro is now variadic, so instead of `record Vec3, [x, y, z]` write `record Vec3, x, y, z`. -* The `def_equals`, `def_hash` and `def_equals_and_hash` macros are now variadic. -* The `property`, `getter` and `setter` macros are now variadic. -* All String methods are now UTF-8 aware. -* `String#length` returns the number of characters, while `String#bytesize` return the number of bytes (previously `length` returned the number of bytes and `bytesize` didn't exist). -* `String#[](index)` now returns a `Char` instead of an `UInt8`, where index is counted in characters. There's also `String#byte_at(index)`. -* Removed the `\x` escape sequence in char and string literals. Use `\u` instead. -* `initialize` methods are now protected. -* Added `IO#gets_to_end`. -* Added backticks (`...`) and `%x(...)` for command execution. -* Added `%r(...)` for regular expression literals. -* Allow interpolations in regular expression literals. -* Compiling with `--release` sets a `release` flag that you can test with `ifdef`. -* Allow passing splats to C functions -* A C type can now be declared like `type Name = Type` (`type Name : Type` will be deprecated). -* Now a C struct/union type can be created with named arguments. -* New attributes syntax: `@[Attr(...)`] instead of `@:Attr`. The old syntax will be deprecated in a future release. -* New link syntax for C libs: `@[Link("name")]` (uses `name` as `pkg-config name` if available or `-lname` instead), `@[Link(ldflags: "...")]` to pass raw flags to the linker, `@[Link("name", static: true)]` to try to find a static library first, and `@[Link(framework: "AppKit")]` (for Mac OSX). -* Added an `exec` method to execute shell commands. Added the `system` and `backtick` similar to Ruby ones. -* Added `be_truthy` and `be_falsey` spec matchers. Added `Array#zip` without a block. (thanks @mjgpy3) -* Added `getter?` and `property?` macros to create methods that end with `?`. -* Added a `CGI` module. -* The compiler now only depends on `cc` for compiling (removed dependency to `llc`, `opt`, `llvm-dis` and `clang`). -* Added `IO#tty?`. -* Some bug fixes. - -## 0.4.3 (2014-08-14) - -* Reverted a commit that introduced random crashes. - -## 0.4.2 (2014-08-13) - -* Fixed [#187](https://github.com/crystal-lang/crystal/issues/185): mixing `yield` and `block.call` crashes the compiler. -* Added `\u` unicode escape sequences inside strings and chars (similar to Ruby). `\x` will be deprecated as it can generate strings with invalid UTF-8 byte sequences. -* Added `String#chars`. -* Fixed: splats weren't working in `initialize`. -* Added the `private` and `protected` visibility modifiers, with the same semantics as Ruby. The difference is that you must place them before a `def` or a macro call. -* Some bug fixes. - -## 0.4.1 (2014-08-09) - -* Fixed [#185](https://github.com/crystal-lang/crystal/issues/185): `-e` flag stopped working. -* Added a `@length` compile-time variable available inside tuples that allows to do loop unrolling. -* Some bug fixes. - -## 0.4.0 (2014-08-08) - -* Support splats in macros. -* Support splats in defs and calls. -* Added named arguments. -* Renamed the `make_named_tuple` macro to `record`. -* Added `def_equals`, `def_hash` and `def_equals_and_hash` macros to generate them from a list of fields. -* Added `Slice(T)`, which is a struct having a pointer and a length. Use this in IO for a safe API. -* Some `StaticArray` fixes and enhancements. - -## 0.3.5 (2014-07-29) - -* **(breaking change)** Removed the special `->` operator for pointers of structs/unions: instead of `foo->bar` use `foo.value.bar`; instead of `foo->bar = 1` use `foo.value.bar = 1`. -* Added `colorize` file that provides methods to easily output bash colors. -* Now you can use modules as generic type arguments (for example, do `x = [] of IO`). -* Added SSL sockets. Now HTTP::Server implements HTTPS. -* Macros have access to constants and types. -* Allow iterating a range in macros with `for`. -* Use cpu cycle counter to initialize random. -* `method_missing` now works in generic types. -* Fixed [#154](https://github.com/crystal-lang/crystal/issues/154): bug, constants are initialized before global variables. -* Fixed [#168](https://github.com/crystal-lang/crystal/issues/168): incorrect type inference of instance variables if not assigned in superclass. -* Fixed [#169](https://github.com/crystal-lang/crystal/issues/169): `responds_to?` wasn't working with generic types. -* Fixed [#171](https://github.com/crystal-lang/crystal/issues/171): ensure blocks are not executed if the rescue block returns from a def. -* Fixed [#175](https://github.com/crystal-lang/crystal/issues/175): invalid code generated when using with/yield with structs. -* Fixed some parser issues and other small issues. -* Allow forward struct/union declarations in libs. -* Added `String#replace(Regex, String)` -* Added a `Box(T)` class, useful for boxing value types to pass them to C as `Void*`. - -## 0.3.4 (2014-07-21) - -* Fixed [#165](https://github.com/crystal-lang/crystal/issues/165): restrictions with generic types didn't work for hierarchy types. -* Allow using a single underscore in restrictions, useful for matching against an n-tuple or an n-function where you don't care about the types (e.g.: `def foo(x : {_, _})`. -* Added a `generate_hash` macro that generates a `hash` methods based on some AST nodes. -* Added very basic `previous_def`: similar to `super`, but uses the previous definition of a method. Useful to decorate existing methods (similar to `alias_method_chain`). For now the method's type restrictions must match for a previous definition to be found. -* Made the compiler a bit faster -* Added `env` in macros, to fetch an environment value. Returns a StringLiteral if found or NilLiteral if not. -* Make `return 1, 2` be the same as `return {1, 2}`. Same goes with `break` and `next`. -* Added `Pointer#as_enumerable(size : Int)` to create an `Enumerable` from a Pointer with an associated size, with zero overhead. Some methods removed from `Pointer`: `each`, `map`, `to_a`, `index`. -* Added `StaticArray::new`, `StaticArray::new(value)`, `StaticArray::new(&block)`, `StaticArray#shuffle!` and `StaticArray#map!`. -* Faster `Char#to_s(io : IO)` - -## 0.3.3 (2014-07-14) - -* Allow implicit conversion to C types by defining a `to_unsafe` method. This removed the hardcoded rule for converting a `String` to `UInt8*` and also allows passing an `Array(T)` to an argument expecting `Pointer(T)`. -* Fixed `.is_a?(Class)` not working ([#162](https://github.com/crystal-lang/crystal/issues/162)) -* Attributes are now associated to AST nodes in the semantic pass, not during parsing. This allows macros to generate attributes that will be attached to subsequent expressions. -* **(breaking change)** Make ENV#[] raise on missing key, and added ENV#[]? -* **(breaking change)** Macro defs are now written like `macro def name(args) : ReturnType` instead of `def name(args) : ReturnType`, which was a bit confusing. - -## 0.3.2 (2014-07-10) - -* Integer literals without a suffix are inferred to be Int32, Int64 or UInt64 depending on their value. -* Check that integer literals fit into their types. -* Put back `Int#to_s(radix : Int)` (was renamed to `to_s_in_base` in the previous release) by also specifying a restriction in `Int#to_s(io : IO)`. -* Added `expect_raises` macros in specs - -## 0.3.1 (2014-07-09) - -* **(breaking change)** Replaced `@name` inside macros with `@class_name`. -* **(breaking change)** Instance variables inside macros now don't have the `@` symbols in their names. - -## 0.3.0 (2014-07-08) - -* Added `Array#each_index` -* Optimized `String#*` for the case when the string has length one. -* Use `GC.malloc_atomic` for String and String::Buffer (as they don't contain internal pointers.) -* Added a `PointerAppender` struct to easily append to a `Pointer` while counting at the same time (thanks @kostya for the idea). -* Added a `Base64` module (thanks @kostya) -* Allow default arguments in macros -* Allow invoking `new` on a function type. For example: `alias F = Int32 -> Int32; f = F.new { |x| x + 1 }; f.call(2) #=> 3`. -* Allow omitting function argument types when invoking C functions that accept functions as arguments. -* Renamed `@name` to `@class_name` inside macros. `@name` will be deprecated in the next version. -* Added IO#read_fully -* Macro hooks: `inherited`, `included` and `extended` -* `method_missing` macro -* Added `{{ raise ... }}` inside macros to issue a compile error. -* Started JSON serialization and deserialization -* Now `at_exit` handlers are run when you invoke `exit` -* Methods can be marked as abstract -* New convention for `to_s` and `inspect`: you must override them receiving an IO object -* StringBuilder and StringBuffer have been replaced by StringIO - -## 0.2.0 (2014-06-24) - -* Removed icr (a REPL): it is abandoned for the moment because it was done in a hacky, non-reliable way -* Added very basic `String#underscore` and `String#camelcase`. -* The parser generates string literals out of strings with interpolated string literals. For example, `"#{__DIR__}/foo"` is interpolated at compile time and generates a string literal with the full path, since `__DIR__` is just a (special) string literal. -* **(breaking change)** Now macro nodes are always pasted as is. If you want to generate an id use `{{var.id}}`. - - Previously, a code like this: - - ```ruby - macro foo(name) - def {{name}}; end - end - - foo :hello - foo "hello" - foo hello - ``` - - generated this: - - ```ruby - def hello; end - def hello; end - def hello; end - ``` - - With this change, it generates this: - - ```ruby - def :hello; end - def "hello"; end - def hello; end - ``` - - Now, to get an identifier out of a symbol literal, string literal or a name, use id: - - ```ruby - macro foo(name) - def {{name.id}}; end - end - ``` - - Although it's longer to type, the implicit "id" call was sometimes confusing. Explicit is better than implicit. - - Invoking `id` on any other kind of node has no effect on the pasted result. -* Allow escaping curly braces inside macros with `\{`. This allows defining macros that, when expanded, can contain other macro expressions. -* Added a special comment-like pragma to change the lexer's filename, line number and column number. - - ```ruby - # foo.cr - a = 1 - #b = 2 - c = 3 - ``` - - In the previous example, `b = 2` (and the rest of the file) is considered as being parsed from file `bar.cr` at line 12, column 24. - -* Added a special `run` call inside macros. This compiles and executes another Crystal program and pastes its output into the current program. - - As an example, consider this program: - - ```ruby - # foo.cr - {{ run("my_program", 1, 2, 3) }} - ``` - - Compiling `foo.cr` will, at compile-time, compile `my_program.cr` and execute it with arguments `1 2 3`. The output of that execution is pasted into `foo.cr` at that location. -* Added ECR (Embedded Crystal) support. This is implemented using the special `run` macro call. - - A small example: - - ```ruby - # template.ecr - Hello <%= @msg %> - ``` - - ```ruby - # foo.cr - require "ecr/macros" - - class HelloView - def initialize(@msg) - end - - # This generates a to_s method with the contents of template.ecr - ecr_file "template.ecr" - end - - view = HelloView.new "world!" - view.to_s #=> "Hello world!" - ``` - - The nice thing about this is that, using the `#` pragma for specifying the lexer's location, if you have a syntax/semantic error in the template the error points to the template :-) - - -## 0.1.0 (2014-06-18) - -* First official release +Older entries in [CHANGELOG.0.md](./CHANGELOG.0.md) From 016578f858cd08efc035a9c66a82965efc75321c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 9 Jan 2023 17:49:38 +0100 Subject: [PATCH 0228/1551] Add Changelog for 1.7.0 (#12897) Co-authored-by: Beta Ziliani --- CHANGELOG.md | 222 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 223 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9a97a9dc2ec..03d5f9685437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,225 @@ +# 1.7.0 (2023-01-09) + +## Language + +- Add lib functions earlier so that they are visible in top-level macros ([#12848](https://github.com/crystal-lang/crystal/pull/12848), thanks @asterite) + +## Standard Library + +- Improve `Benchmark` docs ([#12782](https://github.com/crystal-lang/crystal/pull/12782), thanks @r00ster91, @straight-shoota) +- Improve documentation for `Object#to_s` and `#inspect` ([#9974](https://github.com/crystal-lang/crystal/pull/9974), thanks @straight-shoota) +- Add methods to manipulate semantic versions ([#12834](https://github.com/crystal-lang/crystal/pull/12834), thanks @gabriel-ss) +- Add types to methods with defaults ([#12837](https://github.com/crystal-lang/crystal/pull/12837), thanks @caspiano) +- examples: fix (2022-10) ([#12665](https://github.com/crystal-lang/crystal/pull/12665), thanks @maiha) +- Fix documentation for `Pointer#move_to` ([#12677](https://github.com/crystal-lang/crystal/pull/12677), thanks @TheEEs) +- **(performance)** Eliminate `nil` from many predicate methods ([#12702](https://github.com/crystal-lang/crystal/pull/12702), thanks @HertzDevil) +- examples: fix (2022-12) ([#12870](https://github.com/crystal-lang/crystal/pull/12870), thanks @maiha) + +### Collection + +- Fix missed elements in `Hash#select!(keys : Enumerable)` ([#12739](https://github.com/crystal-lang/crystal/pull/12739), thanks @caspiano) +- Add missing docs for `Indexable` combinations methods ([#10548](https://github.com/crystal-lang/crystal/pull/10548), thanks @keidax) +- **(performance)** Optimize `Range#sample(n)` for integers and floats ([#12535](https://github.com/crystal-lang/crystal/pull/12535), thanks @straight-shoota) +- Add `Iterable#each_cons_pair` ([#12726](https://github.com/crystal-lang/crystal/pull/12726), thanks @caspiano) +- Add links to equivalent `Iterator` methods in `Iterable` ([#12727](https://github.com/crystal-lang/crystal/pull/12727), thanks @caspiano) +- **(performance)** Optimize `Hash#select(Enumerable)` and `#merge!(Hash, &)` ([#12737](https://github.com/crystal-lang/crystal/pull/12737), thanks @HertzDevil) +- Add `Indexable#rindex!` method variant ([#12759](https://github.com/crystal-lang/crystal/pull/12759), thanks @Sija) +- **(performance)** Use mutating collection methods ([#12644](https://github.com/crystal-lang/crystal/pull/12644), thanks @caspiano) +- Fix `Enum#to_s` for flag enum containing named and unnamed members ([#12895](https://github.com/crystal-lang/crystal/pull/12895), thanks @straight-shoota) + +### Concurrency + +- Allow the EventLoop implementation to be detected at runtime ([#12656](https://github.com/crystal-lang/crystal/pull/12656), thanks @lbguilherme) +- **(performance)** Optimize uniqueness filter in `Channel.select_impl` ([#12814](https://github.com/crystal-lang/crystal/pull/12814), thanks @straight-shoota) +- Implement multithreading primitives on Windows ([#11647](https://github.com/crystal-lang/crystal/pull/11647), thanks @HertzDevil) + +### Crypto + +- **(breaking-change)** Implement `Digest` class in `Digest::CRC32` and `Digest::Adler32` ([#11535](https://github.com/crystal-lang/crystal/pull/11535), thanks @BlobCodes) +- Fix `OpenSSL::SSL::Context::Client#alpn_protocol=` ([#12724](https://github.com/crystal-lang/crystal/pull/12724), thanks @jaclarke) +- Fix `HTTP::Client` certificate validation error on FQDN (host with trailing dot) ([#12778](https://github.com/crystal-lang/crystal/pull/12778), thanks @compumike) +- Enable `arc4random(3)` on all supported BSDs and macOS/Darwin ([#12608](https://github.com/crystal-lang/crystal/pull/12608), thanks @dmgk) + +### Files + +- Fix: Read `UInt` in zip directory header ([#12822](https://github.com/crystal-lang/crystal/pull/12822), thanks @pbrumm) +- Add `File.executable?` for Windows ([#9677](https://github.com/crystal-lang/crystal/pull/9677), thanks @nof1000) + +### Macros + +- Fix `TypeNode#nilable?` for root types ([#12354](https://github.com/crystal-lang/crystal/pull/12354), thanks @HertzDevil) +- Add argless `#annotations` overload ([#9326](https://github.com/crystal-lang/crystal/pull/9326), thanks @Blacksmoke16) +- Add specs for addition between `ArrayLiteral` and `TupleLiteral` ([#12639](https://github.com/crystal-lang/crystal/pull/12639), thanks @caspiano) +- Add `ArrayLiteral#-(other)` and `TupleLiteral#-(other)` ([#12646](https://github.com/crystal-lang/crystal/pull/12646), [#12916](https://github.com/crystal-lang/crystal/pull/12916) thanks @caspiano, @straight-shoota) + +### Networking + +- **(breaking-change)** Add `HTTP::Headers#serialize` ([#12765](https://github.com/crystal-lang/crystal/pull/12765), thanks @straight-shoota) +- Ensure `HTTP::Client` closes response when breaking out of block ([#12749](https://github.com/crystal-lang/crystal/pull/12749), thanks @straight-shoota) +- Add `HTTP::Server::Response#redirect` ([#12526](https://github.com/crystal-lang/crystal/pull/12526), thanks @straight-shoota) +- **(performance)** Websocket: write masked data to temporary buffer before sending it ([#12613](https://github.com/crystal-lang/crystal/pull/12613), thanks @asterite) +- Validate cookie name prefixes ([#10648](https://github.com/crystal-lang/crystal/pull/10648), thanks @Blacksmoke16) +- `IPAddress#loopback?` should consider `::ffff:127.0.0.1/104` loopback too ([#12783](https://github.com/crystal-lang/crystal/pull/12783), thanks @carlhoerberg) + +### Numeric + +- Support new SI prefixes in `Number#humanize` ([#12761](https://github.com/crystal-lang/crystal/pull/12761), thanks @HertzDevil) +- Fix `BigInt#%` for unsigned integers ([#12773](https://github.com/crystal-lang/crystal/pull/12773), thanks @straight-shoota) +- [WASM] Add missing `__powisf2` and `__powidf2` compiler-rt functions ([#12569](https://github.com/crystal-lang/crystal/pull/12569), thanks @lbguilherme) +- Add docs for `Int#downto` ([#12468](https://github.com/crystal-lang/crystal/pull/12468), thanks @yb66) +- Upgrade the Dragonbox algorithm ([#12767](https://github.com/crystal-lang/crystal/pull/12767), thanks @HertzDevil) +- Support scientific notation in `BigDecimal#to_s` ([#10805](https://github.com/crystal-lang/crystal/pull/10805), thanks @HertzDevil) +- Add `#bit_reverse` and `#byte_swap` for primitive integers ([#12865](https://github.com/crystal-lang/crystal/pull/12865), thanks @HertzDevil) +- Fix Number comparison operator docs ([#12880](https://github.com/crystal-lang/crystal/pull/12880), thanks @fdocr) + +### Runtime + +- `Exception::CallStack`: avoid allocations in `LibC.dl_iterate_phdr` ([#12625](https://github.com/crystal-lang/crystal/pull/12625), thanks @dmgk) +- Fix explicit type conversion to u64 for `GC::Stats` ([#12779](https://github.com/crystal-lang/crystal/pull/12779), thanks @straight-shoota) +- Add custom `message` parameter to `#not_nil!` ([#12797](https://github.com/crystal-lang/crystal/pull/12797), thanks @straight-shoota) +- Refactor specs for `Enum#to_s` using `assert_prints` ([#12882](https://github.com/crystal-lang/crystal/pull/12882), thanks @straight-shoota) + +### Serialization + +- **(performance)** Leverage `GC.malloc_atomic` for XML ([#12692](https://github.com/crystal-lang/crystal/pull/12692), thanks @HertzDevil) +- Refactor libXML error handling to remove global state ([#12663](https://github.com/crystal-lang/crystal/pull/12663), [#12795](https://github.com/crystal-lang/crystal/pull/12795), thanks @straight-shoota) +- Use qualified type reference `YAML::Any` ([#12688](https://github.com/crystal-lang/crystal/pull/12688), thanks @zw963) +- Automatically cast Int to Float for `{JSON,YAML}::Any#as_f` ([#12835](https://github.com/crystal-lang/crystal/pull/12835), thanks @compumike) + +### Specs + +- Print seed info at start and end of spec output ([#12755](https://github.com/crystal-lang/crystal/pull/12755), thanks @straight-shoota) + +### System + +- **(breaking)** Rename `File.real_path` to `.realpath` ([#12552](https://github.com/crystal-lang/crystal/pull/12552), thanks @straight-shoota) +- **(breaking-change)** Drop FreeBSD 11 compatibility code ([#12612](https://github.com/crystal-lang/crystal/pull/12612), thanks @dmgk) +- Trap when trying to raise wasm32 exceptions ([#12572](https://github.com/crystal-lang/crystal/pull/12572), thanks @lbguilherme) +- Use single helper method to pass UTF-16 strings to Windows ([#12695](https://github.com/crystal-lang/crystal/pull/12695), [#12747](https://github.com/crystal-lang/crystal/pull/12747), thanks @HertzDevil, @straight-shoota) +- Implement `flock_*` fiber-aware, without blocking the thread ([#12861](https://github.com/crystal-lang/crystal/pull/12861), [#12728](https://github.com/crystal-lang/crystal/pull/12728), thanks @straight-shoota) +- Implement `flock_*` for Win32 ([#12766](https://github.com/crystal-lang/crystal/pull/12766), thanks @straight-shoota) +- Add docs to `ENV#has_key?` ([#12781](https://github.com/crystal-lang/crystal/pull/12781), thanks @straight-shoota) +- Improve specs by removing absolute path references ([#12776](https://github.com/crystal-lang/crystal/pull/12776), thanks @straight-shoota) +- Update FreeBSD LibC types ([#12651](https://github.com/crystal-lang/crystal/pull/12651), thanks @dmgk) +- Organize `Process` specs ([#12889](https://github.com/crystal-lang/crystal/pull/12889), thanks @straight-shoota) +- Add tests for `Process::Status` ([#12881](https://github.com/crystal-lang/crystal/pull/12881), thanks @straight-shoota) + +### Text + +- Raise `IndexError` on unmatched subpattern for `MatchData#begin` and `#end` ([#12810](https://github.com/crystal-lang/crystal/pull/12810), thanks @straight-shoota) +- Swap documentation for `String#split` array and block versions ([#12808](https://github.com/crystal-lang/crystal/pull/12808), thanks @hugopl) +- Add `String#index/rindex!` methods ([#12730](https://github.com/crystal-lang/crystal/pull/12730), thanks @Sija) +- Re-organize and enhance specs for `Regex` and `Regex::MatchData` ([#12788](https://github.com/crystal-lang/crystal/pull/12788), [#12789](https://github.com/crystal-lang/crystal/pull/12789), thanks @straight-shoota) +- Add missing positive spec for `Regex#match` with option ([#12804](https://github.com/crystal-lang/crystal/pull/12804), thanks @straight-shoota) +- Replace `if !blank?` with `unless blank?` ([#12800](https://github.com/crystal-lang/crystal/pull/12800), thanks @vlazar) +- Add references between String equality, comparison methods ([#10531](https://github.com/crystal-lang/crystal/pull/10531), thanks @straight-shoota) +- Extract internal Regex API for PCRE backend ([#12802](https://github.com/crystal-lang/crystal/pull/12802), thanks @straight-shoota) +- Implement `Regex` engine on PCRE2 ([#12856](https://github.com/crystal-lang/crystal/pull/12856), [#12866](https://github.com/crystal-lang/crystal/pull/12866), [#12847](https://github.com/crystal-lang/crystal/pull/12847), thanks @straight-shoota, thanks @HertzDevil) +- Add missing overloads for `String#byte_slice` ([#12809](https://github.com/crystal-lang/crystal/pull/12809), thanks @straight-shoota) + +## Compiler + +- Improve error message when there are extra types ([#12734](https://github.com/crystal-lang/crystal/pull/12734), thanks @asterite) +- Handle triples without libc ([#12594](https://github.com/crystal-lang/crystal/pull/12594), thanks @GeopJr) +- Remove unused `Program#cache_dir` property ([#12669](https://github.com/crystal-lang/crystal/pull/12669), thanks @straight-shoota) +- Fix: Unwrap nested errors in error handler for `Crystal::Error` ([#12888](https://github.com/crystal-lang/crystal/pull/12888), thanks @straight-shoota) + +### Codegen + +- Add missing specs for `->var.foo` semantics with assignments ([#9419](https://github.com/crystal-lang/crystal/pull/9419), thanks @makenowjust) +- Use `File#flock_exclusive` on win32 in compiler ([#12876](https://github.com/crystal-lang/crystal/pull/12876), thanks @straight-shoota) + +### Generics + +- Redefine defs when constant and number in generic arguments are equal ([#12785](https://github.com/crystal-lang/crystal/pull/12785), thanks @HertzDevil) +- Fix restriction of numeral generic argument against non-free variable `Path` ([#12784](https://github.com/crystal-lang/crystal/pull/12784), thanks @HertzDevil) + +### Interpreter + +- Interpreter: fix class var initializer that needs an upcast ([#12635](https://github.com/crystal-lang/crystal/pull/12635), thanks @asterite) +- Reverting #12405 Compiler: don't always use Array for node dependencies and observers ([#12849](https://github.com/crystal-lang/crystal/pull/12849), thanks @beta-ziliani) +- Match Nix loader errors in compiler spec ([#12852](https://github.com/crystal-lang/crystal/pull/12852), thanks @bcardiff) +- Interpreter reply ([#12738](https://github.com/crystal-lang/crystal/pull/12738), thanks @I3oris) + +### Parser + +- **(breaking-change)** Parser: Fix restrict grammar for name and supertype in type def ([#12622](https://github.com/crystal-lang/crystal/pull/12622), thanks @caspiano) +- Lexer: fix global capture vars ending with zero, e.g. `$10?` ([#12701](https://github.com/crystal-lang/crystal/pull/12701), thanks @FnControlOption) +- Lexer: allow regex after CRLF ([#12713](https://github.com/crystal-lang/crystal/pull/12713), thanks @FnControlOption) +- Assignment to global regex match data is not allowed ([#12714](https://github.com/crystal-lang/crystal/pull/12714), thanks @caspiano) +- Error when declaring a constant within another constant declaration ([#12566](https://github.com/crystal-lang/crystal/pull/12566), thanks @caspiano) +- Fix calls with do-end blocks within index operators ([#12824](https://github.com/crystal-lang/crystal/pull/12824), thanks @caspiano) +- Remove oct/bin floating point literals ([#12687](https://github.com/crystal-lang/crystal/pull/12687), thanks @BlobCodes) +- Parser: fix wrong/missing locations of various AST nodes ([#11798](https://github.com/crystal-lang/crystal/pull/11798), thanks @FnControlOption) +- Refactor: use helper method instead of duplicate code in lexer ([#12590](https://github.com/crystal-lang/crystal/pull/12590), thanks @straight-shoota) +- Simplify sequential character checks in Crystal lexer ([#12699](https://github.com/crystal-lang/crystal/pull/12699), thanks @caspiano) +- Lexer: delete redundant `scan_ident` calls ([#12691](https://github.com/crystal-lang/crystal/pull/12691), thanks @FnControlOption) +- Rename `Def#yields` to `Def#block_arity` ([#12833](https://github.com/crystal-lang/crystal/pull/12833), thanks @straight-shoota) +- Fix warning on space before colon with anonymous block arg ([#12869](https://github.com/crystal-lang/crystal/pull/12869), thanks @straight-shoota) +- Warn on missing space before colon in type declaration/restriction ([#12740](https://github.com/crystal-lang/crystal/pull/12740), thanks @straight-shoota) + +### Semantic + +- Fix: Do not merge union types in truthy filter ([#12752](https://github.com/crystal-lang/crystal/pull/12752), thanks @straight-shoota) +- Fix crash when using `sizeof`, `instance_sizeof`, or `offsetof` as a type arg ([#12577](https://github.com/crystal-lang/crystal/pull/12577), thanks @keidax) +- Resolve type of free variable on block return type mismatch ([#12754](https://github.com/crystal-lang/crystal/pull/12754), thanks @caspiano) +- Order `_` after any other `Path` when comparing overloads ([#12855](https://github.com/crystal-lang/crystal/pull/12855), thanks @HertzDevil) +- [Experimental] Compiler: try to solve string interpolation exps at compile time ([#12524](https://github.com/crystal-lang/crystal/pull/12524), thanks @asterite) +- Support `@[Deprecated]` on `annotation` ([#12557](https://github.com/crystal-lang/crystal/pull/12557), thanks @caspiano) +- Add more specific error message for uninstantiated proc type ([#11219](https://github.com/crystal-lang/crystal/pull/11219), thanks @straight-shoota) +- Add specs for `system` macro method ([#12885](https://github.com/crystal-lang/crystal/pull/12885), thanks @straight-shoota) + +## Tools + +### Docs-generator + +- Fix range literals causing method lookups in docs generator ([#12680](https://github.com/crystal-lang/crystal/pull/12680), thanks @caspiano) +- Fix method lookup for single char class names ([#12683](https://github.com/crystal-lang/crystal/pull/12683), thanks @caspiano) + +### Formatter + +- Formatter: document stdin filename argument (`-`) ([#12620](https://github.com/crystal-lang/crystal/pull/12620), thanks @caspiano) + +## Other + +### Infrastructure +- [CI] Drop Alpine libreSSL 3.1 test ([#12641](https://github.com/crystal-lang/crystal/pull/12641), thanks @straight-shoota) +- Bump version to 1.7.0-dev ([#12640](https://github.com/crystal-lang/crystal/pull/12640), thanks @straight-shoota) +- [CI] Update GHA actions ([#12501](https://github.com/crystal-lang/crystal/pull/12501), thanks @straight-shoota) +- Opt in to new overload ordering behavior in Makefile ([#12703](https://github.com/crystal-lang/crystal/pull/12703), thanks @HertzDevil) +- Merge release 1.6.2 into master ([#12719](https://github.com/crystal-lang/crystal/pull/12719), thanks @beta-ziliani) +- Configure Renovate ([#12678](https://github.com/crystal-lang/crystal/pull/12678), thanks @renovate) +- [CI] Add version pin for ilammy/msvc-dev-cmd in windows CI ([#12746](https://github.com/crystal-lang/crystal/pull/12746), thanks @straight-shoota) +- [CI] Update dependencies for windows CI ([#12745](https://github.com/crystal-lang/crystal/pull/12745), thanks @straight-shoota) +- Update GH Actions ([#12742](https://github.com/crystal-lang/crystal/pull/12742), thanks @renovate) +- [CI] Run specs in random order by default ([#12541](https://github.com/crystal-lang/crystal/pull/12541), thanks @straight-shoota) +- Update `shell.nix` for newer LLVM versions and aarch64-darwin ([#12591](https://github.com/crystal-lang/crystal/pull/12591), thanks @HertzDevil) +- Update previous Crystal release - 1.6.2 ([#12750](https://github.com/crystal-lang/crystal/pull/12750), thanks @straight-shoota) +- [CI] Update PCRE 8.45 for Windows CI ([#12762](https://github.com/crystal-lang/crystal/pull/12762), thanks @HertzDevil) +- Add WebAssembly specs ([#12571](https://github.com/crystal-lang/crystal/pull/12571), thanks @lbguilherme) +- Update actions/checkout action to v3 ([#12805](https://github.com/crystal-lang/crystal/pull/12805), thanks @renovate) +- Enable multithreading specs on Windows CI ([#12843](https://github.com/crystal-lang/crystal/pull/12843), thanks @HertzDevil) +- [CI] Update mwilliamson/setup-wasmtime-action action to v2 ([#12864](https://github.com/crystal-lang/crystal/pull/12864), thanks @renovate) +- [CI] Update distribution-scripts ([#12891](https://github.com/crystal-lang/crystal/pull/12891), thanks @straight-shoota) +- [CI] Update shards 0.17.2 ([#12875](https://github.com/crystal-lang/crystal/pull/12875), thanks @straight-shoota) +- Rotate breached credentials in CircleCI ([#12902](https://github.com/crystal-lang/crystal/pull/12902), thanks @matiasgarciaisaia) +- Update `NOTICE.md` ([#12901](https://github.com/crystal-lang/crystal/pull/12901), thanks @HertzDevil) +- Split pre-1.0 changelog ([#12898](https://github.com/crystal-lang/crystal/pull/12898), thanks @straight-shoota) + +### Code Improvements + +- Style: Remove redundant begin blocks ([#12638](https://github.com/crystal-lang/crystal/pull/12638), thanks @caspiano) +- Lint: Fix variable name casing ([#12674](https://github.com/crystal-lang/crystal/pull/12674), thanks @Sija) +- Lint: Remove comparisons with boolean literals ([#12673](https://github.com/crystal-lang/crystal/pull/12673), thanks @Sija) +- Lint: Use `Object#in?` instead of multiple comparisons ([#12675](https://github.com/crystal-lang/crystal/pull/12675), thanks @Sija) +- Lint: Remove useless assignments ([#12648](https://github.com/crystal-lang/crystal/pull/12648), thanks @Sija) +- Use `Object#in?` in place of multiple comparisons ([#12700](https://github.com/crystal-lang/crystal/pull/12700), thanks @caspiano) +- Style: Remove explicit returns from the codebase ([#12637](https://github.com/crystal-lang/crystal/pull/12637), thanks @caspiano) +- Lint: Use `Enumerable#find!/#index!` variants ([#12686](https://github.com/crystal-lang/crystal/pull/12686), thanks @Sija) +- Style: Use short block notation for simple one-liners ([#12676](https://github.com/crystal-lang/crystal/pull/12676), thanks @Sija) +- Couple of ameba lint issues fixed ([#12685](https://github.com/crystal-lang/crystal/pull/12685), thanks @Sija) +- Use context-specific heredoc deliminators ([#12816](https://github.com/crystal-lang/crystal/pull/12816), thanks @straight-shoota) + # 1.6.2 (2022-11-03) ## Language diff --git a/src/VERSION b/src/VERSION index de023c91b16b..bd8bf882d061 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.7.0-dev +1.7.0 From 862919a417053d48ce99c45d11e560f5db066323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 10 Jan 2023 19:34:56 +0100 Subject: [PATCH 0229/1551] Update previous Crystal release - 1.7.0 (#12925) --- .circleci/config.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ src/VERSION | 2 +- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index da22e31ed768..496928625134 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.7.0/crystal-1.7.0-1" defaults: environment: &env diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ba107bad0e12..4cf50cffd0ea 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.1, 1.6.2] + crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.1, 1.6.2, 1.7.0] include: # libffi is only available starting from the 1.2.2 build images - crystal_bootstrap_version: 1.0.0 diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index d59e3b968d7b..2073783890c8 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -6,7 +6,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.6.2-alpine + container: crystallang/crystal:1.7.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -23,7 +23,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.6.2-alpine + container: crystallang/crystal:1.7.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -36,7 +36,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.6.2-alpine + container: crystallang/crystal:1.7.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index 6bdd09a6b533..c5262e6d3225 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -6,7 +6,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.6.2-alpine + container: crystallang/crystal:1.7.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -17,7 +17,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.6.2-alpine + container: crystallang/crystal:1.7.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 3dc4cf35e468..9d5b459447a3 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -8,7 +8,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.6.1-build + container: crystallang/crystal:1.7.0-build steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index aad484818f4a..2020681daa4c 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: x86_64-linux-job: runs-on: ubuntu-latest - container: crystallang/crystal:1.6.2-build + container: crystallang/crystal:1.7.0-build steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/bin/ci b/bin/ci index 84a8b8c8307f..5b4d93017b1b 100755 --- a/bin/ci +++ b/bin/ci @@ -134,8 +134,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.6.2-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.7.0/crystal-1.7.0-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.7.0-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -188,7 +188,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.6.2}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.7.0}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 6adf9f98346e..8b3ffaab8e84 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:1f3946iq8df2sfr8nk7ydxwld1prvpql03fsn7cj31cjj09hsjzp"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.7.0/crystal-1.7.0-1-darwin-universal.tar.gz"; + sha256 = "sha256:1wpghg24xjr27xqh3q3avpk04fxxm6salar85v672k4s3xf5rjrz"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:0pnakhi4hc50fw6dz0n110zpibgwjb91mf6n63fhys8hby7fg73p"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.7.0/crystal-1.7.0-1-darwin-universal.tar.gz"; + sha256 = "sha256:1wpghg24xjr27xqh3q3avpk04fxxm6salar85v672k4s3xf5rjrz"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-linux-x86_64.tar.gz"; - sha256 = "sha256:0f3nrd59b9yajvrhh24i9kbs6nmgc7dpw134jj3iyqh9wz83pyqx"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.7.0/crystal-1.7.0-1-linux-x86_64.tar.gz"; + sha256 = "sha256:1d4wcggd32a3h3f7fzkfwlfanwp9lljmh2x5a9gwdf6lblllmkfy"; }; }.${pkgs.stdenv.system}); diff --git a/src/VERSION b/src/VERSION index bd8bf882d061..0ef074f2eca2 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.7.0 +1.8.0-dev From 8a7da877d909177530c789e99017f3b4b6cb2143 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 11 Jan 2023 02:35:43 +0800 Subject: [PATCH 0230/1551] Allow namespaced `Path`s as type names for `lib` (#12903) Resolves https://github.com/crystal-lang/crystal/issues/12696 --- spec/compiler/formatter/formatter_spec.cr | 2 + spec/compiler/parser/parser_spec.cr | 92 ++++++++++--------- spec/compiler/parser/to_s_spec.cr | 1 + .../crystal/semantic/top_level_visitor.cr | 14 +-- src/compiler/crystal/syntax/ast.cr | 2 +- src/compiler/crystal/syntax/parser.cr | 2 +- src/compiler/crystal/syntax/to_s.cr | 2 +- src/compiler/crystal/tools/formatter.cr | 4 +- 8 files changed, 62 insertions(+), 57 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index abcbac598c29..943dea41ed14 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1039,6 +1039,8 @@ describe Crystal::Formatter do assert_format "lib Bar\n enum Foo\n A\n end\nend" assert_format "lib Bar\n enum Foo\n A = 1\n end\nend" + assert_format "lib Foo::Bar\nend" + %w(foo foo= foo? foo!).each do |method| assert_format "->#{method}" assert_format "foo = 1\n->foo.#{method}" diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index cc4b11238e41..8da2cb980dd7 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -939,55 +939,57 @@ module Crystal it_parses "Foo::Bar", ["Foo", "Bar"].path - it_parses "lib LibC\nend", LibDef.new("LibC") - it_parses "lib LibC\nfun getchar\nend", LibDef.new("LibC", [FunDef.new("getchar")] of ASTNode) - it_parses "lib LibC\nfun getchar(...)\nend", LibDef.new("LibC", [FunDef.new("getchar", varargs: true)] of ASTNode) - it_parses "lib LibC\nfun getchar : Int\nend", LibDef.new("LibC", [FunDef.new("getchar", return_type: "Int".path)] of ASTNode) - it_parses "lib LibC\nfun getchar : (->)?\nend", LibDef.new("LibC", [FunDef.new("getchar", return_type: Crystal::Union.new([ProcNotation.new, "Nil".path(true)] of ASTNode))] of ASTNode) - it_parses "lib LibC\nfun getchar(Int, Float)\nend", LibDef.new("LibC", [FunDef.new("getchar", [Arg.new("", restriction: "Int".path), Arg.new("", restriction: "Float".path)])] of ASTNode) - it_parses "lib LibC\nfun getchar(a : Int, b : Float)\nend", LibDef.new("LibC", [FunDef.new("getchar", [Arg.new("a", restriction: "Int".path), Arg.new("b", restriction: "Float".path)])] of ASTNode) - it_parses "lib LibC\nfun getchar(a : Int)\nend", LibDef.new("LibC", [FunDef.new("getchar", [Arg.new("a", restriction: "Int".path)])] of ASTNode) - it_parses "lib LibC\nfun getchar(a : Int, b : Float) : Int\nend", LibDef.new("LibC", [FunDef.new("getchar", [Arg.new("a", restriction: "Int".path), Arg.new("b", restriction: "Float".path)], "Int".path)] of ASTNode) - it_parses "lib LibC; fun getchar(a : Int, b : Float) : Int; end", LibDef.new("LibC", [FunDef.new("getchar", [Arg.new("a", restriction: "Int".path), Arg.new("b", restriction: "Float".path)], "Int".path)] of ASTNode) - it_parses "lib LibC; fun foo(a : Int*); end", LibDef.new("LibC", [FunDef.new("foo", [Arg.new("a", restriction: "Int".path.pointer_of)])] of ASTNode) - it_parses "lib LibC; fun foo(a : Int**); end", LibDef.new("LibC", [FunDef.new("foo", [Arg.new("a", restriction: "Int".path.pointer_of.pointer_of)])] of ASTNode) - it_parses "lib LibC; fun foo : Int*; end", LibDef.new("LibC", [FunDef.new("foo", return_type: "Int".path.pointer_of)] of ASTNode) - it_parses "lib LibC; fun foo : Int**; end", LibDef.new("LibC", [FunDef.new("foo", return_type: "Int".path.pointer_of.pointer_of)] of ASTNode) - it_parses "lib LibC; fun foo(a : ::B, ::C -> ::D); end", LibDef.new("LibC", [FunDef.new("foo", [Arg.new("a", restriction: ProcNotation.new([Path.global("B"), Path.global("C")] of ASTNode, Path.global("D")))])] of ASTNode) - it_parses "lib LibC; type A = B; end", LibDef.new("LibC", [TypeDef.new("A", "B".path)] of ASTNode) - it_parses "lib LibC; type A = B*; end", LibDef.new("LibC", [TypeDef.new("A", "B".path.pointer_of)] of ASTNode) - it_parses "lib LibC; type A = B**; end", LibDef.new("LibC", [TypeDef.new("A", "B".path.pointer_of.pointer_of)] of ASTNode) - it_parses "lib LibC; type A = B.class; end", LibDef.new("LibC", [TypeDef.new("A", Metaclass.new("B".path))] of ASTNode) - it_parses "lib LibC; struct Foo; end end", LibDef.new("LibC", [CStructOrUnionDef.new("Foo")] of ASTNode) - it_parses "lib LibC; struct Foo; x : Int; y : Float; end end", LibDef.new("LibC", [CStructOrUnionDef.new("Foo", [TypeDeclaration.new("x".var, "Int".path), TypeDeclaration.new("y".var, "Float".path)] of ASTNode)] of ASTNode) - it_parses "lib LibC; struct Foo; x : Int*; end end", LibDef.new("LibC", [CStructOrUnionDef.new("Foo", Expressions.from(TypeDeclaration.new("x".var, "Int".path.pointer_of)))] of ASTNode) - it_parses "lib LibC; struct Foo; x : Int**; end end", LibDef.new("LibC", [CStructOrUnionDef.new("Foo", Expressions.from(TypeDeclaration.new("x".var, "Int".path.pointer_of.pointer_of)))] of ASTNode) - it_parses "lib LibC; struct Foo; x, y, z : Int; end end", LibDef.new("LibC", [CStructOrUnionDef.new("Foo", [TypeDeclaration.new("x".var, "Int".path), TypeDeclaration.new("y".var, "Int".path), TypeDeclaration.new("z".var, "Int".path)] of ASTNode)] of ASTNode) - it_parses "lib LibC; union Foo; end end", LibDef.new("LibC", [CStructOrUnionDef.new("Foo", union: true)] of ASTNode) - it_parses "lib LibC; enum Foo; A\nB; C\nD = 1; end end", LibDef.new("LibC", [EnumDef.new("Foo".path, [Arg.new("A"), Arg.new("B"), Arg.new("C"), Arg.new("D", 1.int32)] of ASTNode)] of ASTNode) - it_parses "lib LibC; enum Foo; A = 1; B; end end", LibDef.new("LibC", [EnumDef.new("Foo".path, [Arg.new("A", 1.int32), Arg.new("B")] of ASTNode)] of ASTNode) - it_parses "lib LibC; Foo = 1; end", LibDef.new("LibC", [Assign.new("Foo".path, 1.int32)] of ASTNode) - it_parses "lib LibC\nfun getch = GetChar\nend", LibDef.new("LibC", [FunDef.new("getch", real_name: "GetChar")] of ASTNode) - it_parses %(lib LibC\nfun getch = "get.char"\nend), LibDef.new("LibC", [FunDef.new("getch", real_name: "get.char")] of ASTNode) - it_parses %(lib LibC\nfun getch = "get.char" : Int32\nend), LibDef.new("LibC", [FunDef.new("getch", return_type: "Int32".path, real_name: "get.char")] of ASTNode) - it_parses %(lib LibC\nfun getch = "get.char"(x : Int32)\nend), LibDef.new("LibC", [FunDef.new("getch", [Arg.new("x", restriction: "Int32".path)], real_name: "get.char")] of ASTNode) - it_parses "lib LibC\n$errno : Int32\n$errno2 : Int32\nend", LibDef.new("LibC", [ExternalVar.new("errno", "Int32".path), ExternalVar.new("errno2", "Int32".path)] of ASTNode) - it_parses "lib LibC\n$errno : B, C -> D\nend", LibDef.new("LibC", [ExternalVar.new("errno", ProcNotation.new(["B".path, "C".path] of ASTNode, "D".path))] of ASTNode) - it_parses "lib LibC\n$errno = Foo : Int32\nend", LibDef.new("LibC", [ExternalVar.new("errno", "Int32".path, "Foo")] of ASTNode) - it_parses "lib LibC\nalias Foo = Bar\nend", LibDef.new("LibC", [Alias.new("Foo".path, "Bar".path)] of ASTNode) - it_parses "lib LibC; struct Foo; include Bar; end; end", LibDef.new("LibC", [CStructOrUnionDef.new("Foo", Include.new("Bar".path))] of ASTNode) - - it_parses "lib LibC\nfun SomeFun\nend", LibDef.new("LibC", [FunDef.new("SomeFun")] of ASTNode) + it_parses "lib LibC\nend", LibDef.new("LibC".path) + it_parses "lib LibC\nfun getchar\nend", LibDef.new("LibC".path, [FunDef.new("getchar")] of ASTNode) + it_parses "lib LibC\nfun getchar(...)\nend", LibDef.new("LibC".path, [FunDef.new("getchar", varargs: true)] of ASTNode) + it_parses "lib LibC\nfun getchar : Int\nend", LibDef.new("LibC".path, [FunDef.new("getchar", return_type: "Int".path)] of ASTNode) + it_parses "lib LibC\nfun getchar : (->)?\nend", LibDef.new("LibC".path, [FunDef.new("getchar", return_type: Crystal::Union.new([ProcNotation.new, "Nil".path(true)] of ASTNode))] of ASTNode) + it_parses "lib LibC\nfun getchar(Int, Float)\nend", LibDef.new("LibC".path, [FunDef.new("getchar", [Arg.new("", restriction: "Int".path), Arg.new("", restriction: "Float".path)])] of ASTNode) + it_parses "lib LibC\nfun getchar(a : Int, b : Float)\nend", LibDef.new("LibC".path, [FunDef.new("getchar", [Arg.new("a", restriction: "Int".path), Arg.new("b", restriction: "Float".path)])] of ASTNode) + it_parses "lib LibC\nfun getchar(a : Int)\nend", LibDef.new("LibC".path, [FunDef.new("getchar", [Arg.new("a", restriction: "Int".path)])] of ASTNode) + it_parses "lib LibC\nfun getchar(a : Int, b : Float) : Int\nend", LibDef.new("LibC".path, [FunDef.new("getchar", [Arg.new("a", restriction: "Int".path), Arg.new("b", restriction: "Float".path)], "Int".path)] of ASTNode) + it_parses "lib LibC; fun getchar(a : Int, b : Float) : Int; end", LibDef.new("LibC".path, [FunDef.new("getchar", [Arg.new("a", restriction: "Int".path), Arg.new("b", restriction: "Float".path)], "Int".path)] of ASTNode) + it_parses "lib LibC; fun foo(a : Int*); end", LibDef.new("LibC".path, [FunDef.new("foo", [Arg.new("a", restriction: "Int".path.pointer_of)])] of ASTNode) + it_parses "lib LibC; fun foo(a : Int**); end", LibDef.new("LibC".path, [FunDef.new("foo", [Arg.new("a", restriction: "Int".path.pointer_of.pointer_of)])] of ASTNode) + it_parses "lib LibC; fun foo : Int*; end", LibDef.new("LibC".path, [FunDef.new("foo", return_type: "Int".path.pointer_of)] of ASTNode) + it_parses "lib LibC; fun foo : Int**; end", LibDef.new("LibC".path, [FunDef.new("foo", return_type: "Int".path.pointer_of.pointer_of)] of ASTNode) + it_parses "lib LibC; fun foo(a : ::B, ::C -> ::D); end", LibDef.new("LibC".path, [FunDef.new("foo", [Arg.new("a", restriction: ProcNotation.new([Path.global("B"), Path.global("C")] of ASTNode, Path.global("D")))])] of ASTNode) + it_parses "lib LibC; type A = B; end", LibDef.new("LibC".path, [TypeDef.new("A", "B".path)] of ASTNode) + it_parses "lib LibC; type A = B*; end", LibDef.new("LibC".path, [TypeDef.new("A", "B".path.pointer_of)] of ASTNode) + it_parses "lib LibC; type A = B**; end", LibDef.new("LibC".path, [TypeDef.new("A", "B".path.pointer_of.pointer_of)] of ASTNode) + it_parses "lib LibC; type A = B.class; end", LibDef.new("LibC".path, [TypeDef.new("A", Metaclass.new("B".path))] of ASTNode) + it_parses "lib LibC; struct Foo; end end", LibDef.new("LibC".path, [CStructOrUnionDef.new("Foo")] of ASTNode) + it_parses "lib LibC; struct Foo; x : Int; y : Float; end end", LibDef.new("LibC".path, [CStructOrUnionDef.new("Foo", [TypeDeclaration.new("x".var, "Int".path), TypeDeclaration.new("y".var, "Float".path)] of ASTNode)] of ASTNode) + it_parses "lib LibC; struct Foo; x : Int*; end end", LibDef.new("LibC".path, [CStructOrUnionDef.new("Foo", Expressions.from(TypeDeclaration.new("x".var, "Int".path.pointer_of)))] of ASTNode) + it_parses "lib LibC; struct Foo; x : Int**; end end", LibDef.new("LibC".path, [CStructOrUnionDef.new("Foo", Expressions.from(TypeDeclaration.new("x".var, "Int".path.pointer_of.pointer_of)))] of ASTNode) + it_parses "lib LibC; struct Foo; x, y, z : Int; end end", LibDef.new("LibC".path, [CStructOrUnionDef.new("Foo", [TypeDeclaration.new("x".var, "Int".path), TypeDeclaration.new("y".var, "Int".path), TypeDeclaration.new("z".var, "Int".path)] of ASTNode)] of ASTNode) + it_parses "lib LibC; union Foo; end end", LibDef.new("LibC".path, [CStructOrUnionDef.new("Foo", union: true)] of ASTNode) + it_parses "lib LibC; enum Foo; A\nB; C\nD = 1; end end", LibDef.new("LibC".path, [EnumDef.new("Foo".path, [Arg.new("A"), Arg.new("B"), Arg.new("C"), Arg.new("D", 1.int32)] of ASTNode)] of ASTNode) + it_parses "lib LibC; enum Foo; A = 1; B; end end", LibDef.new("LibC".path, [EnumDef.new("Foo".path, [Arg.new("A", 1.int32), Arg.new("B")] of ASTNode)] of ASTNode) + it_parses "lib LibC; Foo = 1; end", LibDef.new("LibC".path, [Assign.new("Foo".path, 1.int32)] of ASTNode) + it_parses "lib LibC\nfun getch = GetChar\nend", LibDef.new("LibC".path, [FunDef.new("getch", real_name: "GetChar")] of ASTNode) + it_parses %(lib LibC\nfun getch = "get.char"\nend), LibDef.new("LibC".path, [FunDef.new("getch", real_name: "get.char")] of ASTNode) + it_parses %(lib LibC\nfun getch = "get.char" : Int32\nend), LibDef.new("LibC".path, [FunDef.new("getch", return_type: "Int32".path, real_name: "get.char")] of ASTNode) + it_parses %(lib LibC\nfun getch = "get.char"(x : Int32)\nend), LibDef.new("LibC".path, [FunDef.new("getch", [Arg.new("x", restriction: "Int32".path)], real_name: "get.char")] of ASTNode) + it_parses "lib LibC\n$errno : Int32\n$errno2 : Int32\nend", LibDef.new("LibC".path, [ExternalVar.new("errno", "Int32".path), ExternalVar.new("errno2", "Int32".path)] of ASTNode) + it_parses "lib LibC\n$errno : B, C -> D\nend", LibDef.new("LibC".path, [ExternalVar.new("errno", ProcNotation.new(["B".path, "C".path] of ASTNode, "D".path))] of ASTNode) + it_parses "lib LibC\n$errno = Foo : Int32\nend", LibDef.new("LibC".path, [ExternalVar.new("errno", "Int32".path, "Foo")] of ASTNode) + it_parses "lib LibC\nalias Foo = Bar\nend", LibDef.new("LibC".path, [Alias.new("Foo".path, "Bar".path)] of ASTNode) + it_parses "lib LibC; struct Foo; include Bar; end; end", LibDef.new("LibC".path, [CStructOrUnionDef.new("Foo", Include.new("Bar".path))] of ASTNode) + + it_parses "lib LibC\nfun SomeFun\nend", LibDef.new("LibC".path, [FunDef.new("SomeFun")] of ASTNode) + + it_parses "lib Foo::Bar\nend", LibDef.new(Path.new("Foo", "Bar")) it_parses "fun foo(x : Int32) : Int64\nx\nend", FunDef.new("foo", [Arg.new("x", restriction: "Int32".path)], "Int64".path, body: "x".var) assert_syntax_error "fun foo(Int32); end", "top-level fun parameter must have a name" assert_syntax_error "fun Foo : Int64\nend" - it_parses "lib LibC; {{ 1 }}; end", LibDef.new("LibC", body: [MacroExpression.new(1.int32)] of ASTNode) - it_parses "lib LibC; {% if 1 %}2{% end %}; end", LibDef.new("LibC", body: [MacroIf.new(1.int32, MacroLiteral.new("2"))] of ASTNode) + it_parses "lib LibC; {{ 1 }}; end", LibDef.new("LibC".path, body: [MacroExpression.new(1.int32)] of ASTNode) + it_parses "lib LibC; {% if 1 %}2{% end %}; end", LibDef.new("LibC".path, body: [MacroIf.new(1.int32, MacroLiteral.new("2"))] of ASTNode) - it_parses "lib LibC; struct Foo; {{ 1 }}; end; end", LibDef.new("LibC", body: CStructOrUnionDef.new("Foo", Expressions.from([MacroExpression.new(1.int32)] of ASTNode))) - it_parses "lib LibC; struct Foo; {% if 1 %}2{% end %}; end; end", LibDef.new("LibC", body: CStructOrUnionDef.new("Foo", Expressions.from([MacroIf.new(1.int32, MacroLiteral.new("2"))] of ASTNode))) + it_parses "lib LibC; struct Foo; {{ 1 }}; end; end", LibDef.new("LibC".path, body: CStructOrUnionDef.new("Foo", Expressions.from([MacroExpression.new(1.int32)] of ASTNode))) + it_parses "lib LibC; struct Foo; {% if 1 %}2{% end %}; end; end", LibDef.new("LibC".path, body: CStructOrUnionDef.new("Foo", Expressions.from([MacroIf.new(1.int32, MacroLiteral.new("2"))] of ASTNode))) it_parses "1 .. 2", RangeLiteral.new(1.int32, 2.int32, false) it_parses "1 ... 2", RangeLiteral.new(1.int32, 2.int32, true) @@ -1378,7 +1380,7 @@ module Crystal # This is useful for example when interpolating __FILE__ and __DIR__ it_parses "\"foo\#{\"bar\"}baz\"", "foobarbaz".string - it_parses "lib LibFoo\nend\nif true\nend", [LibDef.new("LibFoo"), If.new(true.bool)] + it_parses "lib LibFoo\nend\nif true\nend", [LibDef.new("LibFoo".path), If.new(true.bool)] it_parses "foo(\n1\n)", Call.new(nil, "foo", 1.int32) @@ -1552,7 +1554,7 @@ module Crystal assert_syntax_error "@[Foo(\"\": 1)]" - it_parses "lib LibC\n@[Bar]; end", LibDef.new("LibC", Annotation.new("Bar".path)) + it_parses "lib LibC\n@[Bar]; end", LibDef.new("LibC".path, Annotation.new("Bar".path)) it_parses "Foo(_)", Generic.new("Foo".path, [Underscore.new] of ASTNode) diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index 6ffa19cc3f2e..aaef2b232c1b 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -148,6 +148,7 @@ describe "ASTNode#to_s" do expect_to_s %(lib Foo\n union Foo\n a : Int\n b : Int32\n end\nend) expect_to_s %(lib Foo\n FOO = 0\nend) expect_to_s %(lib LibC\n fun getch = "get.char"\nend) + expect_to_s %(lib Foo::Bar\nend) expect_to_s %(enum Foo\n A = 0\n B\nend) expect_to_s %(alias Foo = Void) expect_to_s %(alias Foo::Bar = Void) diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index dea95f0f9494..d92a2d5895d5 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -495,15 +495,17 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor annotations = read_annotations - scope = current_type_scope(node) + scope, name, type = lookup_type_def(node) - type = scope.types[node.name]? if type - node.raise "#{node.name} is not a lib" unless type.is_a?(LibType) + unless type.is_a?(LibType) + node.raise "#{type} is not a lib, it's a #{type.type_desc}" + end else - type = LibType.new @program, scope, node.name - scope.types[node.name] = type + type = LibType.new @program, scope, name + scope.types[name] = type end + node.resolved_type = type type.private = true if node.visibility.private? @@ -524,7 +526,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end if wasm_import_module && link_annotation.wasm_import_module - ann.raise "multiple wasm import modules specified for lib #{node.name}" + ann.raise "multiple wasm import modules specified for lib #{type}" end wasm_import_module = link_annotation.wasm_import_module diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index e8fe26bc1217..859d479bde48 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -1882,7 +1882,7 @@ module Crystal end class LibDef < ASTNode - property name : String + property name : Path property body : ASTNode property name_location : Location? property visibility = Visibility::Public diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 1a06517d337a..0b69138720e9 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -5593,8 +5593,8 @@ module Crystal location = @token.location next_token_skip_space_or_newline - name = check_const name_location = @token.location + name = parse_path next_token_skip_statement_end body = push_visibility { parse_lib_body_expressions } diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 7359b40bc209..405c21b32159 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -1104,7 +1104,7 @@ module Crystal def visit(node : LibDef) @str << "lib " - @str << node.name + node.name.accept self newline @inside_lib = true accept_with_indent(node.body) diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index c2bc17cec2ea..9650696f8f23 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -3562,9 +3562,7 @@ module Crystal write_keyword :lib, " " - check :CONST - write node.name - next_token + accept node.name format_nested_with_end node.body From 3db4bddf619ac3eee4895da8f64fa042a976a9aa Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 11 Jan 2023 18:16:35 +0800 Subject: [PATCH 0231/1551] Fix `x @y` and `x @@y` in def parameters when `y` is reserved (#12922) Fixes https://github.com/crystal-lang/crystal/issues/12914 --- spec/compiler/formatter/formatter_spec.cr | 2 ++ spec/compiler/parser/parser_spec.cr | 2 ++ src/compiler/crystal/syntax/parser.cr | 13 +++++++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 943dea41ed14..4d1549b45a1f 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -209,6 +209,8 @@ describe Crystal::Formatter do assert_format "def foo ( &@block) \n end", "def foo(&@block)\nend" assert_format "def foo ( @select) \n end", "def foo(@select)\nend" assert_format "def foo ( @@select) \n end", "def foo(@@select)\nend" + assert_format "def foo ( bar @select) \n end", "def foo(bar @select)\nend" + assert_format "def foo ( bar @@select) \n end", "def foo(bar @@select)\nend" assert_format "def foo(a, &@b)\nend" assert_format "def foo ( x = 1 ) \n end", "def foo(x = 1)\nend" assert_format "def foo ( x : Int32 ) \n end", "def foo(x : Int32)\nend" diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 8da2cb980dd7..2389a72af9c1 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -268,6 +268,8 @@ module Crystal it_parses "def foo(#{kw} foo); end", Def.new("foo", [Arg.new("foo", external_name: kw.to_s)]) it_parses "def foo(@#{kw}); end", Def.new("foo", [Arg.new("__arg0", external_name: kw.to_s)], [Assign.new("@#{kw}".instance_var, "__arg0".var)] of ASTNode) it_parses "def foo(@@#{kw}); end", Def.new("foo", [Arg.new("__arg0", external_name: kw.to_s)], [Assign.new("@@#{kw}".class_var, "__arg0".var)] of ASTNode) + it_parses "def foo(x @#{kw}); end", Def.new("foo", [Arg.new("__arg0", external_name: "x")], [Assign.new("@#{kw}".instance_var, "__arg0".var)] of ASTNode) + it_parses "def foo(x @@#{kw}); end", Def.new("foo", [Arg.new("__arg0", external_name: "x")], [Assign.new("@@#{kw}".class_var, "__arg0".var)] of ASTNode) assert_syntax_error "foo { |#{kw}| }", "cannot use '#{kw}' as a block parameter name", 1, 8 assert_syntax_error "foo { |(#{kw})| }", "cannot use '#{kw}' as a block parameter name", 1, 9 diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 0b69138720e9..b011edf0630d 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -4041,8 +4041,12 @@ module Crystal # def method(select __arg0) # @select = __arg0 # end - if !external_name && invalid_internal_name?(param_name) - param_name, external_name = temp_arg_name, param_name + # + # The external name defaults to the internal one unless otherwise + # specified (i.e. `def method(foo @select)`). + if invalid_internal_name?(param_name) + external_name ||= param_name + param_name = temp_arg_name end ivar = InstanceVar.new(@token.value.to_s).at(location) @@ -4062,8 +4066,9 @@ module Crystal end # Same case as :INSTANCE_VAR for things like @select - if !external_name && invalid_internal_name?(param_name) - param_name, external_name = temp_arg_name, param_name + if invalid_internal_name?(param_name) + external_name ||= param_name + param_name = temp_arg_name end cvar = ClassVar.new(@token.value.to_s).at(location) From ef79d4128c806cdbb8687fe7af197d7fc6d7295f Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 11 Jan 2023 07:17:13 -0300 Subject: [PATCH 0232/1551] Fix: Interpreter `value_to_bool` for module, generic module and generic module metaclass (#12920) --- spec/compiler/interpreter/primitives_spec.cr | 40 ++++++++++++++++++++ src/compiler/crystal/interpreter/to_bool.cr | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/spec/compiler/interpreter/primitives_spec.cr b/spec/compiler/interpreter/primitives_spec.cr index f003ebdb92ee..05abbaea5660 100644 --- a/spec/compiler/interpreter/primitives_spec.cr +++ b/spec/compiler/interpreter/primitives_spec.cr @@ -812,5 +812,45 @@ describe Crystal::Repl::Interpreter do !a CRYSTAL end + + it "interprets not for module (#12918)" do + interpret(<<-CRYSTAL).should eq(false) + module MyModule; end + + class One + include MyModule + end + + !One.new.as(MyModule) + CRYSTAL + end + + it "interprets not for generic module" do + interpret(<<-CRYSTAL).should eq(false) + module MyModule(T); end + + class One + include MyModule(Int32) + end + + !One.new.as(MyModule(Int32)) + CRYSTAL + end + + it "interprets not for generic module metaclass" do + interpret(<<-CRYSTAL).should eq(false) + module MyModule(T); end + + !MyModule(Int32) + CRYSTAL + end + + it "interprets not for generic class instance metaclass" do + interpret(<<-CRYSTAL).should eq(false) + class MyClass(T); end + + !MyClass(Int32) + CRYSTAL + end end end diff --git a/src/compiler/crystal/interpreter/to_bool.cr b/src/compiler/crystal/interpreter/to_bool.cr index 665174a9497c..56c2f5f459d1 100644 --- a/src/compiler/crystal/interpreter/to_bool.cr +++ b/src/compiler/crystal/interpreter/to_bool.cr @@ -33,7 +33,7 @@ class Crystal::Repl::Compiler union_to_bool aligned_sizeof_type(type), node: nil end - private def value_to_bool(node : ASTNode, type : NonGenericClassType | GenericClassInstanceType | VirtualType | MetaclassType | VirtualMetaclassType | ReferenceUnionType | IntegerType | CharType | SymbolType | FloatType | EnumType) + private def value_to_bool(node : ASTNode, type : NonGenericClassType | GenericClassInstanceType | VirtualType | MetaclassType | VirtualMetaclassType | ReferenceUnionType | IntegerType | CharType | SymbolType | FloatType | EnumType | NonGenericModuleType | GenericModuleInstanceType | GenericModuleInstanceMetaclassType | GenericClassInstanceMetaclassType) pop aligned_sizeof_type(type), node: nil put_true node: nil end From cde494bd7e090b615bc01a97ce292f8270aeb952 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 11 Jan 2023 18:17:26 +0800 Subject: [PATCH 0233/1551] Disallow empty exponents in number literals (#12910) --- spec/compiler/lexer/lexer_spec.cr | 9 +++++++-- src/compiler/crystal/syntax/lexer.cr | 7 ++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/spec/compiler/lexer/lexer_spec.cr b/spec/compiler/lexer/lexer_spec.cr index 8575f6e1c14b..6045635a603c 100644 --- a/spec/compiler/lexer/lexer_spec.cr +++ b/spec/compiler/lexer/lexer_spec.cr @@ -431,8 +431,13 @@ describe "Lexer" do assert_syntax_error ".42", ".1 style number literal is not supported, put 0 before dot" assert_syntax_error "-.42", ".1 style number literal is not supported, put 0 before dot" - assert_syntax_error "2e", "unexpected token: \"e\"" - assert_syntax_error "2ef32", "unexpected token: \"ef32\"" + assert_syntax_error "2e", "invalid decimal number exponent" + assert_syntax_error "2e+", "invalid decimal number exponent" + assert_syntax_error "2ef32", "invalid decimal number exponent" + assert_syntax_error "2e+@foo", "invalid decimal number exponent" + assert_syntax_error "2e+e", "invalid decimal number exponent" + assert_syntax_error "2e+f32", "invalid decimal number exponent" + assert_syntax_error "2e+-2", "invalid decimal number exponent" assert_syntax_error "2e+_2", "unexpected '_' in number" # Test for #11671 diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 615d0583a1a7..008c4ff1f9ee 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -1232,6 +1232,7 @@ module Crystal has_underscores = false last_is_underscore = false pos_after_prefix = start + pos_before_exponent = nil # Consume prefix if current_char == '0' @@ -1268,6 +1269,10 @@ module Crystal last_is_underscore = false end + if pos_before_exponent + raise("invalid decimal number exponent", @token, (current_pos - start)) unless current_pos > pos_before_exponent + end + case current_char when '_' raise("consecutive underscores in numbers aren't allowed", @token, (current_pos - start)) if last_is_underscore @@ -1282,7 +1287,7 @@ module Crystal is_e_notation = is_decimal = true next_char if peek_next_char.in?('+', '-') raise("unexpected '_' in number", @token, (current_pos - start)) if peek_next_char == '_' - break unless peek_next_char.in?('0'..'9') + pos_before_exponent = current_pos + 1 when 'i', 'u', 'f' if current_char == 'f' && base != 10 case base From 084e7345b0b825b5f8dbbb1787881584ee297749 Mon Sep 17 00:00:00 2001 From: James Good Date: Wed, 11 Jan 2023 10:30:37 -0700 Subject: [PATCH 0234/1551] Format spec results with pretty inspect (#11635) --- src/spec/expectations.cr | 58 ++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/spec/expectations.cr b/src/spec/expectations.cr index fa37ca4def5c..3313bb43aaa9 100644 --- a/src/spec/expectations.cr +++ b/src/spec/expectations.cr @@ -40,8 +40,8 @@ module Spec got size: #{actual_value.size} MSG else - expected = expected_value.inspect - got = actual_value.inspect + expected = expected_value.pretty_inspect + got = actual_value.pretty_inspect if expected == got expected += " : #{@expected_value.class}" got += " : #{actual_value.class}" @@ -51,7 +51,7 @@ module Spec end def negative_failure_message(actual_value) - "Expected: actual_value != #{@expected_value.inspect}\n got: #{actual_value.inspect}" + "Expected: actual_value != #{@expected_value.pretty_inspect}\n got: #{actual_value.pretty_inspect}" end end @@ -65,11 +65,11 @@ module Spec end def failure_message(actual_value) - "Expected: #{@expected_value.inspect} (object_id: #{@expected_value.object_id})\n got: #{actual_value.inspect} (object_id: #{actual_value.object_id})" + "Expected: #{@expected_value.pretty_inspect} (object_id: #{@expected_value.object_id})\n got: #{actual_value.pretty_inspect} (object_id: #{actual_value.object_id})" end def negative_failure_message(actual_value) - "Expected: value.same? #{@expected_value.inspect} (object_id: #{@expected_value.object_id})\n got: #{actual_value.inspect} (object_id: #{actual_value.object_id})" + "Expected: value.same? #{@expected_value.pretty_inspect} (object_id: #{@expected_value.object_id})\n got: #{actual_value.pretty_inspect} (object_id: #{actual_value.object_id})" end end @@ -80,11 +80,11 @@ module Spec end def failure_message(actual_value) - "Expected: #{actual_value.inspect} to be truthy" + "Expected: #{actual_value.pretty_inspect} to be truthy" end def negative_failure_message(actual_value) - "Expected: #{actual_value.inspect} not to be truthy" + "Expected: #{actual_value.pretty_inspect} not to be truthy" end end @@ -95,11 +95,11 @@ module Spec end def failure_message(actual_value) - "Expected: #{actual_value.inspect} to be falsey" + "Expected: #{actual_value.pretty_inspect} to be falsey" end def negative_failure_message(actual_value) - "Expected: #{actual_value.inspect} not to be falsey" + "Expected: #{actual_value.pretty_inspect} not to be falsey" end end @@ -110,11 +110,11 @@ module Spec end def failure_message(actual_value) - "Expected: #{actual_value.inspect} to be nil" + "Expected: #{actual_value.pretty_inspect} to be nil" end def negative_failure_message(actual_value) - "Expected: #{actual_value.inspect} not to be nil" + "Expected: #{actual_value.pretty_inspect} not to be nil" end end @@ -128,11 +128,11 @@ module Spec end def failure_message(actual_value) - "Expected #{actual_value.inspect} to be within #{@delta.inspect} of #{@expected_value.inspect}" + "Expected #{actual_value.pretty_inspect} to be within #{@delta} of #{@expected_value.pretty_inspect}" end def negative_failure_message(actual_value) - "Expected #{actual_value.inspect} not to be within #{@delta.inspect} of #{@expected_value.inspect}" + "Expected #{actual_value.pretty_inspect} not to be within #{@delta} of #{@expected_value.pretty_inspect}" end end @@ -143,11 +143,11 @@ module Spec end def failure_message(actual_value) - "Expected #{actual_value.inspect} (#{actual_value.class}) to be a #{T}" + "Expected #{actual_value.pretty_inspect} (#{actual_value.class}) to be a #{T}" end def negative_failure_message(actual_value) - "Expected #{actual_value.inspect} (#{actual_value.class}) not to be a #{T}" + "Expected #{actual_value.pretty_inspect} (#{actual_value.class}) not to be a #{T}" end end @@ -193,11 +193,11 @@ module Spec end def failure_message(actual_value) - "Expected #{actual_value.inspect} to be #{@op} #{@expected_value.inspect}" + "Expected #{actual_value.pretty_inspect} to be #{@op} #{@expected_value.pretty_inspect}" end def negative_failure_message(actual_value) - "Expected #{actual_value.inspect} not to be #{@op} #{@expected_value.inspect}" + "Expected #{actual_value.pretty_inspect} not to be #{@op} #{@expected_value.pretty_inspect}" end end @@ -211,11 +211,11 @@ module Spec end def failure_message(actual_value) - "Expected: #{actual_value.inspect}\nto match: #{@expected_value.inspect}" + "Expected: #{actual_value.pretty_inspect}\nto match: #{@expected_value.pretty_inspect}" end def negative_failure_message(actual_value) - "Expected: value #{actual_value.inspect}\n to not match: #{@expected_value.inspect}" + "Expected: value #{actual_value.pretty_inspect}\n to not match: #{@expected_value.pretty_inspect}" end end @@ -229,11 +229,11 @@ module Spec end def failure_message(actual_value) - "Expected: #{actual_value.inspect}\nto include: #{@expected_value.inspect}" + "Expected: #{actual_value.pretty_inspect}\nto include: #{@expected_value.pretty_inspect}" end def negative_failure_message(actual_value) - "Expected: value #{actual_value.inspect}\nto not include: #{@expected_value.inspect}" + "Expected: value #{actual_value.pretty_inspect}\nto not include: #{@expected_value.pretty_inspect}" end end @@ -247,11 +247,11 @@ module Spec end def failure_message(actual_value) - "Expected: #{actual_value.inspect}\nto start with: #{@expected_value.inspect}" + "Expected: #{actual_value.pretty_inspect}\nto start with: #{@expected_value.pretty_inspect}" end def negative_failure_message(actual_value) - "Expected: value #{actual_value.inspect}\nnot to start with: #{@expected_value.inspect}" + "Expected: value #{actual_value.pretty_inspect}\nnot to start with: #{@expected_value.pretty_inspect}" end end @@ -265,11 +265,11 @@ module Spec end def failure_message(actual_value) - "Expected: #{actual_value.inspect}\nto end with: #{@expected_value.inspect}" + "Expected: #{actual_value.pretty_inspect}\nto end with: #{@expected_value.pretty_inspect}" end def negative_failure_message(actual_value) - "Expected: value #{actual_value.inspect}\nnot to end with: #{@expected_value.inspect}" + "Expected: value #{actual_value.pretty_inspect}\nnot to end with: #{@expected_value.pretty_inspect}" end end @@ -280,11 +280,11 @@ module Spec end def failure_message(actual_value) - "Expected: #{actual_value.inspect} to be empty" + "Expected: #{actual_value.pretty_inspect} to be empty" end def negative_failure_message(actual_value) - "Expected: #{actual_value.inspect} not to be empty" + "Expected: #{actual_value.pretty_inspect} not to be empty" end end @@ -405,13 +405,13 @@ module Spec when Regex unless (ex_to_s =~ message) backtrace = ex.backtrace.join('\n') { |f| " # #{f}" } - fail "Expected #{klass} with message matching #{message.inspect}, " \ + fail "Expected #{klass} with message matching #{message.pretty_inspect}, " \ "got #<#{ex.class}: #{ex_to_s}> with backtrace:\n#{backtrace}", file, line end when String unless ex_to_s.includes?(message) backtrace = ex.backtrace.join('\n') { |f| " # #{f}" } - fail "Expected #{klass} with #{message.inspect}, got #<#{ex.class}: " \ + fail "Expected #{klass} with #{message.pretty_inspect}, got #<#{ex.class}: " \ "#{ex_to_s}> with backtrace:\n#{backtrace}", file, line end when Nil From 787d729c5dc075a5fc2031a75bc59954ac11c9dc Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 12 Jan 2023 01:36:51 +0800 Subject: [PATCH 0235/1551] Stricter checks for multiple assignment syntax (#12919) --- spec/compiler/parser/parser_spec.cr | 12 ++++++++++++ src/compiler/crystal/syntax/parser.cr | 17 ++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 2389a72af9c1..0064bc0fadcf 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -181,6 +181,7 @@ module Crystal it_parses "a, b = 1", MultiAssign.new(["a".var, "b".var] of ASTNode, [1.int32] of ASTNode) it_parses "_, _ = 1, 2", MultiAssign.new([Underscore.new, Underscore.new] of ASTNode, [1.int32, 2.int32] of ASTNode) it_parses "a[0], a[1] = 1, 2", MultiAssign.new([Call.new("a".call, "[]", 0.int32), Call.new("a".call, "[]", 1.int32)] of ASTNode, [1.int32, 2.int32] of ASTNode) + it_parses "a[], a[] = 1, 2", MultiAssign.new([Call.new("a".call, "[]"), Call.new("a".call, "[]")] of ASTNode, [1.int32, 2.int32] of ASTNode) it_parses "a.foo, a.bar = 1, 2", MultiAssign.new([Call.new("a".call, "foo"), Call.new("a".call, "bar")] of ASTNode, [1.int32, 2.int32] of ASTNode) it_parses "x = 0; a, b = x += 1", [Assign.new("x".var, 0.int32), MultiAssign.new(["a".var, "b".var] of ASTNode, [OpAssign.new("x".var, "+", 1.int32)] of ASTNode)] of ASTNode it_parses "a, b = 1, 2 if 3", If.new(3.int32, MultiAssign.new(["a".var, "b".var] of ASTNode, [1.int32, 2.int32] of ASTNode)) @@ -227,6 +228,17 @@ module Crystal assert_syntax_error "*a, b, c, d, e = 1, 2", "Multiple assignment count mismatch" assert_syntax_error "a, b, c, d, *e = 1, 2, 3", "Multiple assignment count mismatch" + # #11442, #12911 + assert_syntax_error "a, b.<=" + assert_syntax_error "*a == 1" + assert_syntax_error "*a === 1" + + assert_syntax_error "a {}, b = 1" + assert_syntax_error "a.b {}, c = 1" + + assert_syntax_error "a.b(), c.d = 1" + assert_syntax_error "a.b, c.d() = 1" + it_parses "def foo\n1\nend", Def.new("foo", body: 1.int32) it_parses "def downto(n)\n1\nend", Def.new("downto", ["n".arg], 1.int32) it_parses "def foo ; 1 ; end", Def.new("foo", body: 1.int32) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index b011edf0630d..4e85bc738f84 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -142,7 +142,7 @@ module Crystal last = parse_expression skip_space - last_is_target = multi_assign_target?(last) + last_is_target = multi_assign_target?(last) || multi_assign_middle?(last) case @token.type when .op_comma? @@ -184,7 +184,7 @@ module Crystal end last = parse_op_assign(allow_ops: false) - if assign_index == -1 && !multi_assign_target?(last) + if assign_index == -1 && !multi_assign_target?(last) && !multi_assign_middle?(last) unexpected_token end @@ -236,14 +236,10 @@ module Crystal def multi_assign_target?(exp) case exp - when Underscore, Var, InstanceVar, ClassVar, Global, Assign + when Underscore, Var, InstanceVar, ClassVar, Global true when Call - !exp.has_parentheses? && ( - (exp.args.empty? && !exp.named_args) || - Lexer.setter?(exp.name) || - exp.name.in?("[]", "[]=") - ) + !exp.has_parentheses? && !exp.block && ((exp.args.empty? && !exp.named_args) || exp.name == "[]") else false end @@ -254,7 +250,7 @@ module Crystal when Assign true when Call - exp.name.ends_with? '=' + Lexer.setter?(exp.name) || exp.name == "[]=" else false end @@ -783,8 +779,10 @@ module Crystal block = call_args.block block_arg = call_args.block_arg named_args = call_args.named_args + has_parentheses = call_args.has_parentheses else args = block = block_arg = named_args = nil + has_parentheses = false end end @@ -796,6 +794,7 @@ module Crystal end atomic = Call.new atomic, name, (args || [] of ASTNode), block, block_arg, named_args + atomic.has_parentheses = has_parentheses atomic.name_location = name_location atomic.end_location = block.try(&.end_location) || call_args.try(&.end_location) || end_location atomic.at(location) From 127385511f5803a06592ea09b9503986154affe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 12 Jan 2023 01:01:24 +0100 Subject: [PATCH 0236/1551] [CI] Remove `verbose=1` in `test_llvm` (#12931) --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4cf50cffd0ea..663ad7bcea6e 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -83,7 +83,7 @@ jobs: run: mkdir -p llvm && curl -L ${{ matrix.llvm_url }} > llvm.tar.xz && tar x --xz -C llvm --strip-components=1 -f llvm.tar.xz - name: Test - run: bin/ci with_build_env "make clean deps compiler_spec crystal std_spec LLVM_CONFIG=\$(pwd)/llvm/bin/llvm-config threads=1 verbose=1 junit_output=.junit/spec.xml" + run: bin/ci with_build_env "make clean deps compiler_spec crystal std_spec LLVM_CONFIG=\$(pwd)/llvm/bin/llvm-config threads=1 junit_output=.junit/spec.xml" x86_64-gnu-test-preview_mt: env: From c4e8c21bab012589ad2dddf8314fe9efc829cd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 13 Jan 2023 12:37:50 +0100 Subject: [PATCH 0237/1551] Fix `Process` spec to wait on started processes (#12941) --- spec/std/process_spec.cr | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index c28d74d83882..82265dd94852 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -321,6 +321,8 @@ describe Process do pending_win32 "kills a process" do process = Process.new(*standing_command) process.signal(Signal::KILL).should be_nil + ensure + process.try &.wait end pending_win32 "kills many process" do @@ -328,6 +330,9 @@ describe Process do process2 = Process.new(*standing_command) process1.signal(Signal::KILL).should be_nil process2.signal(Signal::KILL).should be_nil + ensure + process1.try &.wait + process2.try &.wait end end @@ -337,7 +342,8 @@ describe Process do process.terminated?.should be_false process.terminate - process.wait + ensure + process.try(&.wait) end pending_win32 ".exists?" do @@ -367,6 +373,8 @@ describe Process do Process.pgid(process.pid).should be_a(Int64) process.signal(Signal::KILL) Process.pgid.should eq(Process.pgid(Process.pid)) + ensure + process.try(&.wait) end {% unless flag?(:preview_mt) || flag?(:win32) %} From c6e3116df9a44a871740318ebcf486f986b79dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 13 Jan 2023 16:21:15 +0100 Subject: [PATCH 0238/1551] Add comment for `LiteralExpander` `select` (#12926) Co-authored-by: Beta Ziliani --- .../crystal/semantic/literal_expander.cr | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/compiler/crystal/semantic/literal_expander.cr b/src/compiler/crystal/semantic/literal_expander.cr index 74c59209838b..ead7901501c8 100644 --- a/src/compiler/crystal/semantic/literal_expander.cr +++ b/src/compiler/crystal/semantic/literal_expander.cr @@ -631,6 +631,48 @@ module Crystal final_exp end + # Convert a `select` statement into a `case` statement based on `Channel.select` + # + # From: + # + # select + # when foo then body + # when x = bar then x.baz + # end + # + # To: + # + # %index, %value = ::Channel.select({foo_select_action, bar_select_action}) + # case %index + # when 0 + # body + # when 1 + # x = value.as(typeof(foo)) + # x.baz + # else + # ::raise("BUG: invalid select index") + # end + # + # + # If there's an `else` branch, use `Channel.non_blocking_select`. + # + # From: + # + # select + # when foo then body + # else qux + # end + # + # To: + # + # %index, %value = ::Channel.non_blocking_select({foo_select_action}) + # case %index + # when 0 + # body + # else + # qux + # end + # def expand(node : Select) index_name = @program.new_temp_var_name value_name = @program.new_temp_var_name From 0ce950dfa439734ed9ce8ab0128b47afbc608fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 14 Jan 2023 14:23:53 +0100 Subject: [PATCH 0239/1551] Add `Tuple#to_static_array` (#12930) --- spec/std/tuple_spec.cr | 15 +++++++++++++++ src/tuple.cr | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/spec/std/tuple_spec.cr b/spec/std/tuple_spec.cr index 22d8d40933d0..c6ead7b1ccf6 100644 --- a/spec/std/tuple_spec.cr +++ b/spec/std/tuple_spec.cr @@ -341,4 +341,19 @@ describe "Tuple" do ary = Tuple.new.to_a ary.size.should eq(0) end + + it "#to_static_array" do + ary = {1, 'a', true}.to_static_array + ary.should be_a(StaticArray(Int32 | Char | Bool, 3)) + ary.should eq(StaticArray[1, 'a', true]) + ary.size.should eq(3) + + ary = Tuple.new.to_static_array + ary.should be_a(StaticArray(NoReturn, 0)) + ary.size.should eq(0) + + ary = Tuple(String | Int32).new(1).to_static_array + ary.should be_a(StaticArray(String | Int32, 1)) + ary.should eq StaticArray[1.as(String | Int32)] + end end diff --git a/src/tuple.cr b/src/tuple.cr index 16d2675243c8..a3815c856278 100644 --- a/src/tuple.cr +++ b/src/tuple.cr @@ -548,6 +548,24 @@ struct Tuple end end + # Returns a `StaticArray` with the same elements. + # + # The element type is `Union(*T)`. + # + # ``` + # {1, 'a', true}.to_static_array # => StaticArray[1, 'a', true] + # ``` + @[AlwaysInline] + def to_static_array : StaticArray + {% begin %} + ary = uninitialized StaticArray(Union(*T), {{ T.size }}) + each_with_index do |value, i| + ary.to_unsafe[i] = value + end + ary + {% end %} + end + # Appends a string representation of this tuple to the given `IO`. # # ``` From 4e08bbc00ce6ad2399f66cd0058adb32963dec3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 14 Jan 2023 14:24:02 +0100 Subject: [PATCH 0240/1551] Fix: Make sure to dup `Array` in `Channel.select_impl` (#12827) --- spec/std/channel_spec.cr | 11 +++++++++++ src/channel.cr | 18 +++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/spec/std/channel_spec.cr b/spec/std/channel_spec.cr index 4bf7a7c5c80a..945e63e6c958 100644 --- a/spec/std/channel_spec.cr +++ b/spec/std/channel_spec.cr @@ -569,6 +569,17 @@ describe Channel do end end end + + it "returns correct index for array argument", focus: true do + ch = [Channel(String).new, Channel(String).new, Channel(String).new] + channels = [ch[0], ch[2], ch[1]] # shuffle around to get non-sequential lock_object_ids + spawn_and_wait(->{ channels[0].send "foo" }) do + i, m = Channel.non_blocking_select(channels.map(&.receive_select_action)) + + i.should eq(0) + m.should eq("foo") + end + end end end diff --git a/src/channel.cr b/src/channel.cr index 5a7a5957c786..151972418012 100644 --- a/src/channel.cr +++ b/src/channel.cr @@ -421,11 +421,23 @@ class Channel(T) end private def self.select_impl(ops : Indexable(SelectAction), non_blocking) + # ops_locks is a duplicate of ops that can be sorted without disturbing the + # index positions of ops + if ops.responds_to?(:unstable_sort_by!) + # If the collection type implements `unstable_sort_by!` we can dup it. + # This applies to two types: + # * `Array`: `Array#to_a` does not dup and would return the same instance, + # thus we'd be sorting ops and messing up the index positions. + # * `StaticArray`: This avoids a heap allocation because we can dup a + # static array on the stack. + ops_locks = ops.dup + else + ops_locks = ops.to_a + end + # Sort the operations by the channel they contain # This is to avoid deadlocks between concurrent `select` calls - ops_locks = ops - .to_a - .unstable_sort_by!(&.lock_object_id) + ops_locks.unstable_sort_by!(&.lock_object_id) each_skip_duplicates(ops_locks, &.lock) From d857dddb6811dc37559332c92b5760f10420c36b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 15 Jan 2023 21:15:50 +0800 Subject: [PATCH 0241/1551] Deprecate `Termios` (#12940) --- src/crystal/system/unix/file_descriptor.cr | 19 ++++++++----------- src/termios.cr | 11 +++++++++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 6bc081085c61..d8084de32834 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -192,12 +192,9 @@ module Crystal::System::FileDescriptor private def system_echo(enable : Bool, & : ->) system_console_mode do |mode| - if enable - mode.c_lflag |= Termios::LocalMode.flags(ECHO, ECHOE, ECHOK, ECHONL).value - else - mode.c_lflag &= ~(Termios::LocalMode.flags(ECHO, ECHOE, ECHOK, ECHONL).value) - end - if LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode)) != 0 + flags = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL + mode.c_lflag = enable ? (mode.c_lflag | flags) : (mode.c_lflag & ~flags) + if LibC.tcsetattr(fd, LibC::TCSANOW, pointerof(mode)) != 0 raise IO::Error.from_errno("tcsetattr") end yield @@ -209,11 +206,11 @@ module Crystal::System::FileDescriptor if enable LibC.cfmakeraw(pointerof(mode)) else - mode.c_iflag |= Termios::InputMode.flags(BRKINT, ISTRIP, ICRNL, IXON).value - mode.c_oflag |= Termios::OutputMode::OPOST.value - mode.c_lflag |= Termios::LocalMode.flags(ECHO, ECHOE, ECHOK, ECHONL, ICANON, ISIG, IEXTEN).value + mode.c_iflag |= LibC::BRKINT | LibC::ISTRIP | LibC::ICRNL | LibC::IXON + mode.c_oflag |= LibC::OPOST + mode.c_lflag |= LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL | LibC::ICANON | LibC::ISIG | LibC::IEXTEN end - if LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode)) != 0 + if LibC.tcsetattr(fd, LibC::TCSANOW, pointerof(mode)) != 0 raise IO::Error.from_errno("tcsetattr") end yield @@ -228,7 +225,7 @@ module Crystal::System::FileDescriptor before = mode ret = yield mode - LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(before)) + LibC.tcsetattr(fd, LibC::TCSANOW, pointerof(before)) ret end end diff --git a/src/termios.cr b/src/termios.cr index db34be00c8b1..86297781c332 100644 --- a/src/termios.cr +++ b/src/termios.cr @@ -1,6 +1,8 @@ require "c/termios" +@[Deprecated] module Termios + @[Deprecated] @[Flags] enum InputMode BRKINT = LibC::BRKINT @@ -18,6 +20,7 @@ module Termios end {% if flag?(:freebsd) %} + @[Deprecated] @[Flags] enum OutputMode OPOST = LibC::OPOST @@ -31,6 +34,7 @@ module Termios end {% elsif flag?(:dragonfly) %} # FIXME: Verify + @[Deprecated] @[Flags] enum OutputMode OPOST = LibC::OPOST @@ -43,6 +47,7 @@ module Termios TAB3 = LibC::TAB3 end {% elsif flag?(:netbsd) || flag?(:openbsd) %} + @[Deprecated] @[Flags] enum OutputMode OPOST = LibC::OPOST @@ -52,6 +57,7 @@ module Termios ONLRET = LibC::ONLRET end {% else %} + @[Deprecated] @[Flags] enum OutputMode OPOST = LibC::OPOST @@ -86,6 +92,7 @@ module Termios end {% end %} + @[Deprecated] enum BaudRate B0 = LibC::B0 B50 = LibC::B50 @@ -105,6 +112,7 @@ module Termios B38400 = LibC::B38400 end + @[Deprecated] enum ControlMode CSIZE = LibC::CSIZE CS5 = LibC::CS5 @@ -119,6 +127,7 @@ module Termios CLOCAL = LibC::CLOCAL end + @[Deprecated] @[Flags] enum LocalMode : Int64 ECHO = LibC::ECHO @@ -132,6 +141,7 @@ module Termios TOSTOP = LibC::TOSTOP end + @[Deprecated] @[Flags] enum AttributeSelection TCSANOW = LibC::TCSANOW @@ -139,6 +149,7 @@ module Termios TCSAFLUSH = LibC::TCSAFLUSH end + @[Deprecated] enum LineControl TCSANOW = LibC::TCSANOW TCSADRAIN = LibC::TCSADRAIN From 440b62d579329799a39cc642dc6ad7ee8a72e6ea Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 15 Jan 2023 21:16:46 +0800 Subject: [PATCH 0242/1551] Stricter checks for enum definitions (#12945) --- spec/compiler/semantic/enum_spec.cr | 104 ++++++++++++++ .../crystal/semantic/top_level_visitor.cr | 136 ++++++++---------- src/compiler/crystal/syntax/ast.cr | 61 ++++---- src/compiler/crystal/types.cr | 6 +- 4 files changed, 204 insertions(+), 103 deletions(-) diff --git a/spec/compiler/semantic/enum_spec.cr b/spec/compiler/semantic/enum_spec.cr index ee4ac5a7a0e5..876694b99821 100644 --- a/spec/compiler/semantic/enum_spec.cr +++ b/spec/compiler/semantic/enum_spec.cr @@ -354,6 +354,72 @@ describe "Semantic: enum" do enum_type.annotation(annotation_type).should_not be_nil end + it "reopens enum without base type (1)" do + assert_no_errors <<-CRYSTAL + enum Foo + X + end + + enum Foo + end + CRYSTAL + end + + it "reopens enum without base type (2)" do + assert_no_errors <<-CRYSTAL + enum Foo : UInt8 + X + end + + enum Foo + end + CRYSTAL + end + + it "reopens enum with same base type (1)" do + assert_no_errors <<-CRYSTAL + enum Foo + X + end + + enum Foo : Int32 + end + CRYSTAL + end + + it "reopens enum with same base type (2)" do + assert_no_errors <<-CRYSTAL + enum Foo : UInt8 + X + end + + enum Foo : UInt8 + end + CRYSTAL + end + + it "errors if reopening enum with different base type (1)" do + assert_error <<-CRYSTAL, "enum Foo's base type is Int32, not UInt8" + enum Foo + X + end + + enum Foo : UInt8 + end + CRYSTAL + end + + it "errors if reopening enum with different base type (2)" do + assert_error <<-CRYSTAL, "enum Foo's base type is UInt8, not UInt16" + enum Foo : UInt8 + X + end + + enum Foo : UInt16 + end + CRYSTAL + end + it "can use macro expression inside enum" do assert_type(%( enum Foo @@ -466,6 +532,15 @@ describe "Semantic: enum" do end it "gives error on enum overflow" do + assert_error %( + enum Foo : Int8 + #{Array.new(129) { |i| "V#{i + 1}" }.join "\n"} + end + ), + "value of enum member V129 would overflow the base type Int8" + end + + it "gives error on flags enum overflow" do assert_error %( @[Flags] enum Foo : UInt8 @@ -475,6 +550,35 @@ describe "Semantic: enum" do "value of enum member V9 would overflow the base type UInt8" end + it "gives error on enum overflow after a member with value" do + assert_error <<-CRYSTAL, "value of enum member B would overflow the base type Int32" + enum Foo + A = 0x7FFFFFFF + B + end + CRYSTAL + end + + it "gives error on signed flags enum overflow after a member with value" do + assert_error <<-CRYSTAL, "value of enum member B would overflow the base type Int32" + @[Flags] + enum Foo + A = 0x40000000 + B + end + CRYSTAL + end + + it "gives error on unsigned flags enum overflow after a member with value" do + assert_error <<-CRYSTAL, "value of enum member B would overflow the base type UInt32" + @[Flags] + enum Foo : UInt32 + A = 0x80000000 + B + end + CRYSTAL + end + it "doesn't overflow when going from negative to zero (#7874)" do assert_no_errors <<-CRYSTAL enum Nums diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index d92a2d5895d5..98ea7faa80f1 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -619,13 +619,14 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor unless enum_base_type.is_a?(IntegerType) base_type.raise "enum base type must be an integer type" end - else - enum_base_type = @program.int32 + + if enum_type && enum_base_type != enum_type.base_type + base_type.raise "enum #{name}'s base type is #{enum_type.base_type}, not #{enum_base_type}" + end end - all_value = interpret_enum_value(NumberLiteral.new(0), enum_base_type) existed = !!enum_type - enum_type ||= EnumType.new(@program, scope, name, enum_base_type) + enum_type ||= EnumType.new(@program, scope, name, enum_base_type || @program.int32) enum_type.private = true if node.visibility.private? @@ -638,40 +639,32 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor attach_doc enum_type, node, annotations pushing_type(enum_type) do - counter = enum_type.flags? ? 1 : 0 - counter = interpret_enum_value(NumberLiteral.new(counter), enum_base_type) - counter, all_value, overflow = visit_enum_members(node, node.members, counter, all_value, - overflow: false, - existed: existed, - enum_type: enum_type, - enum_base_type: enum_base_type, - is_flags: enum_type.flags?) + visit_enum_members(node, node.members, existed, enum_type) end - num_members = enum_type.types.size - if num_members > 0 && enum_type.flags? - # skip None & All, they doesn't count as members for @[Flags] enums - num_members = enum_type.types.count { |(name, _)| !name.in?("None", "All") } - end + unless existed + num_members = enum_type.types.size + if num_members > 0 && enum_type.flags? + # skip None & All, they doesn't count as members for @[Flags] enums + num_members = enum_type.types.count { |(name, _)| !name.in?("None", "All") } + end - if num_members == 0 - node.raise "enum #{node.name} must have at least one member" - end + if num_members == 0 + node.raise "enum #{node.name} must have at least one member" + end - unless existed if enum_type.flags? - unless enum_type.types["None"]? - none = NumberLiteral.new("0", enum_base_type.kind) - none.type = enum_type - enum_type.add_constant Arg.new("None", default_value: none) - + unless enum_type.types.has_key?("None") + enum_type.add_constant("None", 0) define_enum_none_question_method(enum_type, node) end - unless enum_type.types["All"]? - all = NumberLiteral.new(all_value.to_s, enum_base_type.kind) - all.type = enum_type - enum_type.add_constant Arg.new("All", default_value: all) + unless enum_type.types.has_key?("All") + all_value = enum_type.base_type.kind.cast(0).as(Int::Primitive) + enum_type.types.each_value do |member| + all_value |= interpret_enum_value(member.as(Const).value, enum_type.base_type) + end + enum_type.add_constant("All", all_value) end end @@ -681,43 +674,57 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor false end - def visit_enum_members(node, members, counter, all_value, overflow, **options) - members.each do |member| - counter, all_value, overflow = - visit_enum_member(node, member, counter, all_value, overflow, **options) + def visit_enum_members(node, members, existed, enum_type, previous_counter = nil) + members.reduce(previous_counter) do |counter, member| + visit_enum_member(node, member, existed, enum_type, counter) end - {counter, all_value, overflow} end - def visit_enum_member(node, member, counter, all_value, overflow, **options) + def visit_enum_member(node, member, existed, enum_type, previous_counter = nil) case member when MacroIf expanded = expand_inline_macro(member, mode: Parser::ParseMode::Enum, accept: false) - visit_enum_member(node, expanded, counter, all_value, overflow, **options) + visit_enum_member(node, expanded, existed, enum_type, previous_counter) when MacroExpression expanded = expand_inline_macro(member, mode: Parser::ParseMode::Enum, accept: false) - visit_enum_member(node, expanded, counter, all_value, overflow, **options) + visit_enum_member(node, expanded, existed, enum_type, previous_counter) when MacroFor expanded = expand_inline_macro(member, mode: Parser::ParseMode::Enum, accept: false) - visit_enum_member(node, expanded, counter, all_value, overflow, **options) + visit_enum_member(node, expanded, existed, enum_type, previous_counter) when Expressions - visit_enum_members(node, member.expressions, counter, all_value, overflow, **options) + visit_enum_members(node, member.expressions, existed, enum_type, previous_counter) when Arg - enum_type = options[:enum_type] - base_type = options[:enum_base_type] - is_flags = options[:is_flags] - - if options[:existed] + if existed node.raise "can't reopen enum and add more constants to it" end + if enum_type.types.has_key?(member.name) + member.raise "enum '#{enum_type}' already contains a member named '#{member.name}'" + end + if default_value = member.default_value - counter = interpret_enum_value(default_value, base_type) - elsif overflow - member.raise "value of enum member #{member} would overflow the base type #{base_type}" + counter = interpret_enum_value(default_value, enum_type.base_type) + elsif previous_counter + if enum_type.flags? + if previous_counter == 0 # In case the member is set to 0 + counter = 1 + else + counter = previous_counter &* 2 + unless (counter <=> previous_counter).sign == previous_counter.sign + member.raise "value of enum member #{member} would overflow the base type #{enum_type.base_type}" + end + end + else + counter = previous_counter &+ 1 + unless counter > previous_counter + member.raise "value of enum member #{member} would overflow the base type #{enum_type.base_type}" + end + end + else + counter = enum_type.base_type.kind.cast(enum_type.flags? ? 1 : 0).as(Int::Primitive) end - if is_flags && !@in_lib + if enum_type.flags? && !@in_lib if member.name == "None" && counter != 0 member.raise "flags enum can't redefine None member to non-0" elsif member.name == "All" @@ -726,22 +733,17 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end if default_value.is_a?(Crystal::NumberLiteral) - enum_base_kind = base_type.kind + enum_base_kind = enum_type.base_type.kind if (enum_base_kind.i32?) && (enum_base_kind != default_value.kind) default_value.raise "enum value must be an Int32" end end - all_value |= counter - const_value = NumberLiteral.new(counter.to_s, base_type.kind) - member.default_value = const_value - if enum_type.types.has_key?(member.name) - member.raise "enum '#{enum_type}' already contains a member named '#{member.name}'" - end + define_enum_question_method(enum_type, member, enum_type.flags?) - define_enum_question_method(enum_type, member, is_flags) + const_member = enum_type.add_constant(member.name, counter) + member.default_value = const_member.value - const_member = enum_type.add_constant member const_member.doc = member.doc check_ditto const_member, member.location @@ -749,24 +751,10 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor const_member.add_location(member_location) end - const_value.type = enum_type - if is_flags - if counter == 0 # In case the member is set to 0 - new_counter = 1 - overflow = false - else - new_counter = counter &* 2 - overflow = !default_value && counter.sign != new_counter.sign - end - else - new_counter = counter &+ 1 - overflow = !default_value && counter > 0 && new_counter < 0 - end - new_counter = overflow ? counter : new_counter - {new_counter, all_value, overflow} + counter else member.accept self - {counter, all_value, overflow} + previous_counter end end diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 859d479bde48..81320af3b063 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -263,21 +263,37 @@ module Crystal f32? || f64? end - def self.from_number(number : Number) + def self.from_number(number : Number::Primitive) : self case number - when Int8 then I8 - when Int16 then I16 - when Int32 then I32 - when Int64 then I64 - when Int128 then I128 - when UInt8 then U8 - when UInt16 then U16 - when UInt32 then U32 - when UInt64 then U64 - when UInt128 then U128 - when Float32 then F32 - when Float64 then F64 - else raise "Unsupported Number type for NumberLiteral: #{number.class}" + in Int8 then I8 + in Int16 then I16 + in Int32 then I32 + in Int64 then I64 + in Int128 then I128 + in UInt8 then U8 + in UInt16 then U16 + in UInt32 then U32 + in UInt64 then U64 + in UInt128 then U128 + in Float32 then F32 + in Float64 then F64 + end + end + + def cast(number) : Number::Primitive + case self + in .i8? then number.to_i8 + in .i16? then number.to_i16 + in .i32? then number.to_i32 + in .i64? then number.to_i64 + in .i128? then number.to_i128 + in .u8? then number.to_u8 + in .u16? then number.to_u16 + in .u32? then number.to_u32 + in .u64? then number.to_u64 + in .u128? then number.to_u128 + in .f32? then number.to_f32 + in .f64? then number.to_f64 end end end @@ -299,20 +315,11 @@ module Crystal end def integer_value - case kind - when .i8? then value.to_i8 - when .i16? then value.to_i16 - when .i32? then value.to_i32 - when .i64? then value.to_i64 - when .i128? then value.to_i128 - when .u8? then value.to_u8 - when .u16? then value.to_u16 - when .u32? then value.to_u32 - when .u64? then value.to_u64 - when .u128? then value.to_u128 - else - raise "Bug: called 'integer_value' for non-integer literal" + unless kind.signed_int? || kind.unsigned_int? + raise "BUG: called 'integer_value' for non-integer literal" end + + kind.cast(value) end # Returns true if this literal is representable in the *other_type*. Used to diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index a1cdb71c9d7f..e25f90f1eb6a 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -2829,8 +2829,10 @@ module Crystal @parents ||= [program.enum] of Type end - def add_constant(constant) - types[constant.name] = const = Const.new(program, self, constant.name, constant.default_value.not_nil!) + def add_constant(name, value) + const_exp = NumberLiteral.new(value.to_s, base_type.kind) + const_exp.type = self + types[name] = const = Const.new(program, self, name, const_exp) program.const_initializers << const const end From bf5ffe3ddcdbcd10fa5eef01febbb39514440fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 15 Jan 2023 14:20:39 +0100 Subject: [PATCH 0243/1551] Update `VERSION` to 1.7.1-dev (#12950) --- src/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VERSION b/src/VERSION index bd8bf882d061..896d75a90354 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.7.0 +1.7.1-dev From 2b346db361d7274e17df6c7df7fbbfe393c151f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 15 Jan 2023 14:23:20 +0100 Subject: [PATCH 0244/1551] Fix baked-in path in playground to resolve at runtime (#12948) --- src/compiler/crystal/tools/playground/server.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr index 654d0863faba..691bea3fc5ef 100644 --- a/src/compiler/crystal/tools/playground/server.cr +++ b/src/compiler/crystal/tools/playground/server.cr @@ -449,7 +449,7 @@ module Crystal::Playground end def start - playground_dir = File.dirname(__FILE__) + playground_dir = File.dirname(CrystalPath.new.find("compiler/crystal/tools/playground/server.cr").not_nil![0]) views_dir = File.join(playground_dir, "views") public_dir = File.join(playground_dir, "public") From 0caa488b94e26490c341c883515ac11e922ad532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 15 Jan 2023 17:46:16 +0100 Subject: [PATCH 0245/1551] Fix: Remove `focus: true` (#12962) --- spec/std/channel_spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/std/channel_spec.cr b/spec/std/channel_spec.cr index 945e63e6c958..77be79f6d0d3 100644 --- a/spec/std/channel_spec.cr +++ b/spec/std/channel_spec.cr @@ -570,7 +570,7 @@ describe Channel do end end - it "returns correct index for array argument", focus: true do + it "returns correct index for array argument" do ch = [Channel(String).new, Channel(String).new, Channel(String).new] channels = [ch[0], ch[2], ch[1]] # shuffle around to get non-sequential lock_object_ids spawn_and_wait(->{ channels[0].send "foo" }) do From f0bd16a8dcc43f02ba118d7f0b912eab3251b927 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 16 Jan 2023 19:04:54 +0800 Subject: [PATCH 0246/1551] Formatter: add `&` to yielding methods without a block parameter (#12951) --- samples/2048.cr | 6 +- samples/meteor.cr | 4 +- samples/red_black_tree.cr | 10 +- samples/sdl/fire.cr | 2 +- samples/sdl/sdl/sdl.cr | 2 +- spec/compiler/crystal/tools/context_spec.cr | 2 +- spec/compiler/crystal/tools/expand_spec.cr | 6 +- spec/compiler/crystal/tools/init_spec.cr | 4 +- .../crystal/tools/table_print_spec.cr | 2 +- spec/compiler/crystal/types_spec.cr | 2 +- spec/compiler/formatter/formatter_spec.cr | 172 ++++++++++++++++++ spec/compiler/semantic/concrete_types_spec.cr | 2 +- spec/compiler/semantic/def_overload_spec.cr | 2 +- spec/spec_helper.cr | 6 +- spec/std/big/big_float_spec.cr | 2 +- spec/std/deque_spec.cr | 4 +- spec/std/dir_spec.cr | 2 +- spec/std/enumerable_spec.cr | 2 +- spec/std/http/client/client_spec.cr | 4 +- spec/std/http/spec_helper.cr | 6 +- spec/std/http/web_socket_spec.cr | 2 +- spec/std/io/buffered_spec.cr | 2 +- spec/std/json/builder_spec.cr | 2 +- spec/std/json/pull_parser_spec.cr | 6 +- spec/std/spec/junit_formatter_spec.cr | 4 +- spec/std/spec/tap_formatter_spec.cr | 2 +- spec/std/spec_helper.cr | 6 +- spec/std/time/span_spec.cr | 2 +- spec/std/time/spec_helper.cr | 4 +- spec/std/xml/builder_spec.cr | 2 +- spec/std/yaml/builder_spec.cr | 2 +- spec/std/yaml/nodes/builder_spec.cr | 2 +- spec/support/env.cr | 4 +- spec/support/finalize.cr | 2 +- spec/support/retry.cr | 2 +- spec/support/tempfile.cr | 6 +- src/array.cr | 12 +- src/base64.cr | 4 +- src/benchmark.cr | 10 +- src/big/big_decimal.cr | 2 +- src/big/big_float.cr | 2 +- src/big/big_int.cr | 2 +- src/big/big_rational.cr | 2 +- src/bit_array.cr | 2 +- src/channel.cr | 4 +- src/char.cr | 8 +- src/char/reader.cr | 2 +- src/colorize.cr | 6 +- src/compiler/crystal/codegen/codegen.cr | 18 +- src/compiler/crystal/codegen/context.cr | 4 +- src/compiler/crystal/codegen/debug.cr | 2 +- src/compiler/crystal/codegen/phi.cr | 2 +- src/compiler/crystal/codegen/primitives.cr | 2 +- src/compiler/crystal/command/cursor.cr | 2 +- src/compiler/crystal/compiler.cr | 4 +- .../crystal/interpreter/class_vars.cr | 2 +- src/compiler/crystal/interpreter/compiler.cr | 12 +- .../crystal/interpreter/pry_reader.cr | 6 +- src/compiler/crystal/macros/interpreter.cr | 4 +- src/compiler/crystal/macros/macros.cr | 2 +- src/compiler/crystal/macros/methods.cr | 14 +- src/compiler/crystal/progress_tracker.cr | 2 +- src/compiler/crystal/semantic/bindings.cr | 2 +- src/compiler/crystal/semantic/call.cr | 8 +- src/compiler/crystal/semantic/call_error.cr | 6 +- .../crystal/semantic/cleanup_transformer.cr | 4 +- src/compiler/crystal/semantic/conversions.cr | 2 +- src/compiler/crystal/semantic/cover.cr | 6 +- src/compiler/crystal/semantic/filters.cr | 2 +- src/compiler/crystal/semantic/main_visitor.cr | 12 +- src/compiler/crystal/semantic/match.cr | 2 +- .../semantic/recursive_struct_checker.cr | 2 +- src/compiler/crystal/semantic/restrictions.cr | 2 +- .../crystal/semantic/semantic_visitor.cr | 8 +- .../crystal/semantic/top_level_visitor.cr | 2 +- src/compiler/crystal/semantic/type_lookup.cr | 2 +- src/compiler/crystal/semantic/type_merge.cr | 2 +- src/compiler/crystal/syntax/ast.cr | 8 +- src/compiler/crystal/syntax/lexer.cr | 6 +- src/compiler/crystal/syntax/parser.cr | 20 +- src/compiler/crystal/syntax/to_s.cr | 10 +- src/compiler/crystal/tools/context.cr | 2 +- src/compiler/crystal/tools/formatter.cr | 41 +++-- .../crystal/tools/playground/agent.cr | 4 +- .../agent_instrumentor_transformer.cr | 2 +- .../crystal/tools/playground/server.cr | 2 +- src/compiler/crystal/tools/print_hierarchy.cr | 2 +- src/compiler/crystal/tools/table_print.cr | 6 +- src/compiler/crystal/types.cr | 14 +- src/compress/deflate/reader.cr | 4 +- src/compress/deflate/writer.cr | 2 +- src/compress/gzip/reader.cr | 4 +- src/compress/gzip/writer.cr | 4 +- src/compress/zip/file.cr | 6 +- src/compress/zip/reader.cr | 6 +- src/compress/zip/writer.cr | 8 +- src/compress/zlib/reader.cr | 2 +- src/compress/zlib/writer.cr | 4 +- src/crystal/dwarf/info.cr | 2 +- src/crystal/elf.cr | 4 +- src/crystal/hasher.cr | 2 +- src/crystal/iconv.cr | 2 +- src/crystal/mach_o.cr | 8 +- src/crystal/main.cr | 2 +- src/crystal/pointer_linked_list.cr | 4 +- src/crystal/spin_lock.cr | 4 +- src/crystal/system/thread_linked_list.cr | 2 +- src/crystal/system/unix.cr | 2 +- src/crystal/system/unix/pthread.cr | 4 +- src/crystal/system/unix/pthread_mutex.cr | 2 +- src/crystal/system/unix/socket.cr | 8 +- src/crystal/system/wasi/thread_mutex.cr | 2 +- src/crystal/system/win32/file_descriptor.cr | 2 +- src/crystal/system/win32/socket.cr | 12 +- src/crystal/system/win32/thread.cr | 4 +- src/crystal/system/win32/thread_mutex.cr | 2 +- src/crystal/system/win32/windows_registry.cr | 2 +- src/crystal/system/windows.cr | 2 +- src/crystal/thread_local_value.cr | 2 +- src/csv.cr | 10 +- src/csv/builder.cr | 6 +- src/csv/parser.cr | 2 +- src/deque.cr | 8 +- src/dir/glob.cr | 2 +- src/enumerable.cr | 26 +-- src/exception/call_stack/dwarf.cr | 2 +- src/exception/call_stack/mach_o.cr | 2 +- src/fiber.cr | 2 +- src/file.cr | 4 +- src/file/tempfile.cr | 4 +- src/file_utils.cr | 2 +- src/hash.cr | 14 +- src/http/client.cr | 18 +- src/http/client/response.cr | 2 +- src/http/common.cr | 2 +- src/http/cookie.cr | 2 +- src/http/formdata.cr | 8 +- src/http/formdata/parser.cr | 2 +- src/http/headers.cr | 4 +- .../server/handlers/static_file_handler.cr | 2 +- src/http/web_socket.cr | 2 +- src/http/web_socket/protocol.cr | 2 +- src/humanize.cr | 2 +- src/indexable.cr | 22 +-- src/int.cr | 2 +- src/io.cr | 6 +- src/io/evented.cr | 10 +- src/io/file_descriptor.cr | 6 +- src/io/overlapped.cr | 20 +- src/io/stapled.cr | 2 +- src/iterator.cr | 2 +- src/json/builder.cr | 14 +- src/json/from_json.cr | 8 +- src/json/lexer.cr | 2 +- src/json/parser.cr | 2 +- src/json/pull_parser.cr | 10 +- src/kernel.cr | 2 +- src/levenshtein.cr | 4 +- src/llvm/abi/x86_64.cr | 2 +- src/llvm/basic_block_collection.cr | 4 +- src/llvm/context.cr | 2 +- src/llvm/function_collection.cr | 4 +- src/llvm/function_pass_manager.cr | 2 +- src/llvm/instruction_collection.cr | 2 +- src/llvm/jit_compiler.cr | 2 +- src/llvm/module.cr | 2 +- src/llvm/pass_builder_options.cr | 2 +- src/llvm/target.cr | 2 +- src/log/builder.cr | 2 +- src/log/main.cr | 8 +- src/log/metadata.cr | 2 +- src/log/setup.cr | 2 +- src/log/spec.cr | 4 +- src/mime/media_type.cr | 2 +- src/mime/multipart.cr | 8 +- src/mime/multipart/builder.cr | 8 +- src/mime/multipart/parser.cr | 2 +- src/mutex.cr | 2 +- src/named_tuple.cr | 10 +- src/oauth/consumer.cr | 4 +- src/oauth2/client.cr | 2 +- src/object.cr | 4 +- src/openssl/ssl/server.cr | 2 +- src/openssl/ssl/socket.cr | 4 +- src/option_parser.cr | 6 +- src/path.cr | 2 +- src/pretty_print.cr | 12 +- src/process.cr | 4 +- src/process/executable_path.cr | 2 +- src/raise.cr | 2 +- src/range.cr | 4 +- src/reference.cr | 4 +- src/regex/pcre.cr | 4 +- src/regex/pcre2.cr | 6 +- src/slice.cr | 2 +- src/socket.cr | 4 +- src/socket/addrinfo.cr | 8 +- src/socket/server.cr | 4 +- src/socket/tcp_server.cr | 4 +- src/socket/tcp_socket.cr | 2 +- src/socket/unix_server.cr | 2 +- src/socket/unix_socket.cr | 4 +- src/spec/expectations.cr | 2 +- src/string.cr | 44 ++--- src/string/builder.cr | 2 +- src/string/utf16.cr | 4 +- src/time/location/loader.cr | 4 +- src/tuple.cr | 10 +- src/unicode/unicode.cr | 6 +- src/uri/encoding.cr | 2 +- src/uri/params.cr | 6 +- src/va_list.cr | 2 +- src/xml/attributes.cr | 2 +- src/xml/builder.cr | 24 +-- src/xml/node_set.cr | 2 +- src/xml/reader.cr | 2 +- src/yaml/builder.cr | 12 +- src/yaml/from_yaml.cr | 8 +- src/yaml/nodes/builder.cr | 6 +- src/yaml/nodes/nodes.cr | 4 +- src/yaml/nodes/parser.cr | 2 +- src/yaml/parse_context.cr | 4 +- src/yaml/parser.cr | 6 +- src/yaml/pull_parser.cr | 10 +- src/yaml/schema/core.cr | 8 +- src/yaml/schema/core/parser.cr | 2 +- 226 files changed, 741 insertions(+), 558 deletions(-) diff --git a/samples/2048.cr b/samples/2048.cr index ef5ef3111d8f..75f37aa17c11 100644 --- a/samples/2048.cr +++ b/samples/2048.cr @@ -32,7 +32,7 @@ module Screen 65536 => {Colorize::ColorANSI::White, Colorize::ColorANSI::Black}, } - def self.colorize_for(tile) + def self.colorize_for(tile, &) fg_color, bg_color = TILES[tile] color = Colorize.with.fore(fg_color) color = color.back(bg_color) if bg_color @@ -241,7 +241,7 @@ class Game end end - def each_cell_with_index + def each_cell_with_index(&) 0.upto(@grid.size - 1) do |row| 0.upto(@grid.size - 1) do |col| yield @grid[row][col], row, col @@ -295,7 +295,7 @@ class Game end end - def movable_tiles(direction, drow, dcol) + def movable_tiles(direction, drow, dcol, &) max = @grid.size - 1 from_row, to_row, from_column, to_column = case direction diff --git a/samples/meteor.cr b/samples/meteor.cr index e6c91281ec03..65a1b8e5cb84 100644 --- a/samples/meteor.cr +++ b/samples/meteor.cr @@ -9,7 +9,7 @@ class MyIterator(T) def initialize(@data : T, &@block : T -> T) end - def each + def each(&) while true yield @data @data = @block.call(@data) @@ -145,7 +145,7 @@ class SolutionNode getter :x getter :prev - def each + def each(&) yield @x p = prev while y = p diff --git a/samples/red_black_tree.cr b/samples/red_black_tree.cr index fb56828a5b76..6ea240448d16 100644 --- a/samples/red_black_tree.cr +++ b/samples/red_black_tree.cr @@ -167,7 +167,7 @@ class RedBlackTree y end - def inorder_walk(x = root) + def inorder_walk(x = root, &) x = self.minimum while !x.nil_node? yield x.key @@ -175,11 +175,11 @@ class RedBlackTree end end - def each(x = root) + def each(x = root, &) inorder_walk(x) { |k| yield k } end - def reverse_inorder_walk(x = root) + def reverse_inorder_walk(x = root, &) x = self.maximum while !x.nil_node? yield x.key @@ -187,7 +187,7 @@ class RedBlackTree end end - def reverse_each(x = root) + def reverse_each(x = root, &) reverse_inorder_walk(x) { |k| yield k } end @@ -382,7 +382,7 @@ class RedBlackTreeRunner end end -def bench(name, n = 1) +def bench(name, n = 1, &) start = Time.monotonic print "#{name}: " res = nil diff --git a/samples/sdl/fire.cr b/samples/sdl/fire.cr index c3af69631398..7476b190984b 100644 --- a/samples/sdl/fire.cr +++ b/samples/sdl/fire.cr @@ -233,7 +233,7 @@ class Points end end - def each + def each(&) @points.each do |point| yield point unless point.dead? end diff --git a/samples/sdl/sdl/sdl.cr b/samples/sdl/sdl/sdl.cr index e601b027ea62..eb3a14b31620 100644 --- a/samples/sdl/sdl/sdl.cr +++ b/samples/sdl/sdl/sdl.cr @@ -35,7 +35,7 @@ module SDL LibSDL.quit end - def self.poll_events + def self.poll_events(&) while LibSDL.poll_event(out event) == 1 yield event end diff --git a/spec/compiler/crystal/tools/context_spec.cr b/spec/compiler/crystal/tools/context_spec.cr index c3f839033ac9..84c711e8af53 100644 --- a/spec/compiler/crystal/tools/context_spec.cr +++ b/spec/compiler/crystal/tools/context_spec.cr @@ -11,7 +11,7 @@ private def processed_context_visitor(code, cursor_location) {visitor, process_result} end -private def run_context_tool(code) +private def run_context_tool(code, &) cursor_location = nil code.lines.each_with_index do |line, line_number_0| diff --git a/spec/compiler/crystal/tools/expand_spec.cr b/spec/compiler/crystal/tools/expand_spec.cr index a4eccdea49d4..40a122587afd 100644 --- a/spec/compiler/crystal/tools/expand_spec.cr +++ b/spec/compiler/crystal/tools/expand_spec.cr @@ -13,7 +13,7 @@ private def processed_expand_visitor(code, cursor_location) {visitor, process_result} end -private def run_expand_tool(code) +private def run_expand_tool(code, &) cursor_location = nil code.lines.each_with_index do |line, line_number_0| @@ -41,7 +41,7 @@ private def assert_expand(code, expected_result) assert_expand(code, expected_result) { } end -private def assert_expand(code, expected_result) +private def assert_expand(code, expected_result, &) run_expand_tool code do |result| result.status.should eq("ok") result.message.should eq("#{expected_result.size} expansion#{expected_result.size >= 2 ? "s" : ""} found") @@ -59,7 +59,7 @@ private def assert_expand_simple(code, expanded, original = code.delete('‸')) assert_expand_simple(code, expanded, original) { } end -private def assert_expand_simple(code, expanded, original = code.delete('‸')) +private def assert_expand_simple(code, expanded, original = code.delete('‸'), &) assert_expand(code, [[original, expanded]]) { |result| yield result.expansions.not_nil![0] } end diff --git a/spec/compiler/crystal/tools/init_spec.cr b/spec/compiler/crystal/tools/init_spec.cr index 1408b0d4e0af..80b800b62dd8 100644 --- a/spec/compiler/crystal/tools/init_spec.cr +++ b/spec/compiler/crystal/tools/init_spec.cr @@ -22,7 +22,7 @@ end # Creates a temporary directory, cd to it and run the block inside it. # The directory and its content is deleted when the block return. -private def within_temporary_directory +private def within_temporary_directory(&) with_tempfile "init_spec_tmp" do |tmp_path| Dir.mkdir_p(tmp_path) Dir.cd(tmp_path) do @@ -31,7 +31,7 @@ private def within_temporary_directory end end -private def with_file(name) +private def with_file(name, &) yield File.read(name) end diff --git a/spec/compiler/crystal/tools/table_print_spec.cr b/spec/compiler/crystal/tools/table_print_spec.cr index eda496bb6c9c..44e335296090 100644 --- a/spec/compiler/crystal/tools/table_print_spec.cr +++ b/spec/compiler/crystal/tools/table_print_spec.cr @@ -1,7 +1,7 @@ require "spec" require "compiler/crystal/tools/table_print" -private def assert_table(expected) +private def assert_table(expected, &) actual = String::Builder.build do |builder| Crystal::TablePrint.new(builder).build do |tp| with tp yield diff --git a/spec/compiler/crystal/types_spec.cr b/spec/compiler/crystal/types_spec.cr index 307d341192e8..68106cf8edb8 100644 --- a/spec/compiler/crystal/types_spec.cr +++ b/spec/compiler/crystal/types_spec.cr @@ -1,6 +1,6 @@ require "../../spec_helper" -private def assert_type_to_s(expected) +private def assert_type_to_s(expected, &) p = Program.new t = with p yield p t.to_s.should eq(expected) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 4d1549b45a1f..467f25a44a2c 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -548,6 +548,178 @@ describe Crystal::Formatter do assert_format "with foo yield bar" + context "adds `&` to yielding methods that don't have a block parameter (#8764)" do + assert_format <<-CRYSTAL, + def foo + yield + end + CRYSTAL + <<-CRYSTAL + def foo(&) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo() + yield + end + CRYSTAL + <<-CRYSTAL + def foo(&) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo( + ) + yield + end + CRYSTAL + <<-CRYSTAL + def foo(&) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo(x) + yield + end + CRYSTAL + <<-CRYSTAL + def foo(x, &) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo(x ,) + yield + end + CRYSTAL + <<-CRYSTAL + def foo(x, &) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo(x, + y) + yield + end + CRYSTAL + <<-CRYSTAL + def foo(x, + y, &) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo(x, + y,) + yield + end + CRYSTAL + <<-CRYSTAL + def foo(x, + y, &) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo(x + ) + yield + end + CRYSTAL + <<-CRYSTAL + def foo(x, + &) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo(x, + ) + yield + end + CRYSTAL + <<-CRYSTAL + def foo(x, + &) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo( + x) + yield + end + CRYSTAL + <<-CRYSTAL + def foo( + x, & + ) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo( + x, y) + yield + end + CRYSTAL + <<-CRYSTAL + def foo( + x, y, & + ) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo( + x, + y) + yield + end + CRYSTAL + <<-CRYSTAL + def foo( + x, + y, & + ) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, + def foo( + x, + ) + yield + end + CRYSTAL + <<-CRYSTAL + def foo( + x, + & + ) + yield + end + CRYSTAL + + assert_format "macro f\n yield\n {{ yield }}\nend" + end + assert_format "1 + 2", "1 + 2" assert_format "1 &+ 2", "1 &+ 2" assert_format "1 > 2", "1 > 2" diff --git a/spec/compiler/semantic/concrete_types_spec.cr b/spec/compiler/semantic/concrete_types_spec.cr index 6473505aa69e..cc8e4159b93c 100644 --- a/spec/compiler/semantic/concrete_types_spec.cr +++ b/spec/compiler/semantic/concrete_types_spec.cr @@ -1,6 +1,6 @@ require "../../spec_helper" -private def assert_concrete_types(str) +private def assert_concrete_types(str, &) result = semantic("struct Witness;end\n\n#{str}") program = result.program diff --git a/spec/compiler/semantic/def_overload_spec.cr b/spec/compiler/semantic/def_overload_spec.cr index f0e2a93864dc..2b8786550c29 100644 --- a/spec/compiler/semantic/def_overload_spec.cr +++ b/spec/compiler/semantic/def_overload_spec.cr @@ -1674,7 +1674,7 @@ describe "Semantic: def overload" do end end -private def each_union_variant(t1, t2) +private def each_union_variant(t1, t2, &) yield "#{t1} | #{t2}" yield "#{t2} | #{t1}" # yield "Union(#{t1}, #{t2})" diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index b3f1a13e7444..655cae7e333e 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -35,7 +35,7 @@ record SemanticResult, program : Program, node : ASTNode -def assert_type(str, *, inject_primitives = false, flags = nil, file = __FILE__, line = __LINE__) +def assert_type(str, *, inject_primitives = false, flags = nil, file = __FILE__, line = __LINE__, &) result = semantic(str, flags: flags, inject_primitives: inject_primitives) program = result.program expected_type = with program yield program @@ -166,7 +166,7 @@ def assert_macro_error(macro_body, message = nil, *, flags = nil, file = __FILE_ end end -def prepare_macro_call(macro_body, flags = nil) +def prepare_macro_call(macro_body, flags = nil, &) program = new_program program.flags.concat(flags.split) if flags args = yield program @@ -280,7 +280,7 @@ def run(code, filename = nil, inject_primitives = true, debug = Crystal::Debug:: end end -def test_c(c_code, crystal_code, *, file = __FILE__) +def test_c(c_code, crystal_code, *, file = __FILE__, &) with_temp_c_object_file(c_code, file: file) do |o_filename| yield run(%( require "prelude" diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index a8fbeb506563..50a234ffb408 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -9,7 +9,7 @@ private def it_converts_to_s(value : BigFloat, str, *, file = __FILE__, line = _ end end -private def with_precision(precision) +private def with_precision(precision, &) old_precision = BigFloat.default_precision BigFloat.default_precision = precision diff --git a/spec/std/deque_spec.cr b/spec/std/deque_spec.cr index 62012587ccbf..e1bc24bd4ed0 100644 --- a/spec/std/deque_spec.cr +++ b/spec/std/deque_spec.cr @@ -9,7 +9,7 @@ private class DequeTester @i : Int32 @c : Array(Int32) | Deque(Int32) | Nil - def step + def step(&) @c = @deque yield @c = @array @@ -30,7 +30,7 @@ private class DequeTester @c.not_nil! end - def test + def test(&) with self yield end end diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index c521c2faa4a0..47d95806e72a 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -1,7 +1,7 @@ require "./spec_helper" require "../support/env" -private def unset_tempdir +private def unset_tempdir(&) {% if flag?(:windows) %} old_tempdirs = {ENV["TMP"]?, ENV["TEMP"]?, ENV["USERPROFILE"]?} begin diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index a7bac45abd48..3383f5ad3f9c 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -3,7 +3,7 @@ require "spec" private class SpecEnumerable include Enumerable(Int32) - def each + def each(&) yield 1 yield 2 yield 3 diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index 1f35c086e9af..d23b3376e043 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -6,7 +6,7 @@ require "http/server" require "http/log" require "log/spec" -private def test_server(host, port, read_time = 0, content_type = "text/plain", write_response = true) +private def test_server(host, port, read_time = 0, content_type = "text/plain", write_response = true, &) server = TCPServer.new(host, port) begin spawn do @@ -475,7 +475,7 @@ module HTTP end class SubClient < HTTP::Client - def around_exec(request) + def around_exec(request, &) raise "from subclass" yield end diff --git a/spec/std/http/spec_helper.cr b/spec/std/http/spec_helper.cr index d7829553aeda..9bf88c82763f 100644 --- a/spec/std/http/spec_helper.cr +++ b/spec/std/http/spec_helper.cr @@ -2,7 +2,7 @@ require "spec" require "../spec_helper" require "../../support/fibers" -private def wait_for +private def wait_for(&) timeout = {% if flag?(:interpreted) %} # TODO: it's not clear why some interpreter specs # take more than 5 seconds to bind to a server. @@ -31,7 +31,7 @@ end # shut down before continuing execution in the current fiber. # 6. If the listening fiber raises an exception, it is rescued and re-raised # in the current fiber. -def run_server(server) +def run_server(server, &) server_done = Channel(Exception?).new f = spawn do @@ -58,7 +58,7 @@ end # Helper method which runs a *handler* # Similar to `run_server` but doesn't go through the network stack. -def run_handler(handler) +def run_handler(handler, &) done = Channel(Exception?).new begin diff --git a/spec/std/http/web_socket_spec.cr b/spec/std/http/web_socket_spec.cr index 3610e524d56e..75a54e91fb2e 100644 --- a/spec/std/http/web_socket_spec.cr +++ b/spec/std/http/web_socket_spec.cr @@ -565,7 +565,7 @@ describe HTTP::WebSocket do typeof(HTTP::WebSocket.new(URI.parse("ws://localhost"), headers: HTTP::Headers{"X-TEST_HEADER" => "some-text"})) end -private def integration_setup +private def integration_setup(&) bin_ch = Channel(Bytes).new txt_ch = Channel(String).new ws_handler = HTTP::WebSocketHandler.new do |ws, ctx| diff --git a/spec/std/io/buffered_spec.cr b/spec/std/io/buffered_spec.cr index ca092219c7c7..d8ef8ff3b979 100644 --- a/spec/std/io/buffered_spec.cr +++ b/spec/std/io/buffered_spec.cr @@ -12,7 +12,7 @@ private class BufferedWrapper < IO @called_unbuffered_read = false end - def self.new(io) + def self.new(io, &) buffered_io = new(io) yield buffered_io buffered_io.flush diff --git a/spec/std/json/builder_spec.cr b/spec/std/json/builder_spec.cr index 5a8ab0836f6d..66f9be33b741 100644 --- a/spec/std/json/builder_spec.cr +++ b/spec/std/json/builder_spec.cr @@ -2,7 +2,7 @@ require "spec" require "json" require "../../support/string" -private def assert_built(expected, *, file = __FILE__, line = __LINE__) +private def assert_built(expected, *, file = __FILE__, line = __LINE__, &) assert_prints JSON.build { |json| with json yield json }, expected, file: file, line: line end diff --git a/spec/std/json/pull_parser_spec.cr b/spec/std/json/pull_parser_spec.cr index 5ab6b34608b4..8cf4127237bf 100644 --- a/spec/std/json/pull_parser_spec.cr +++ b/spec/std/json/pull_parser_spec.cr @@ -36,7 +36,7 @@ class JSON::PullParser read_next end - def assert(value : String) + def assert(value : String, &) kind.should eq(JSON::PullParser::Kind::String) string_value.should eq(value) read_next @@ -61,7 +61,7 @@ class JSON::PullParser end end - def assert_array + def assert_array(&) kind.should eq(JSON::PullParser::Kind::BeginArray) read_next yield @@ -73,7 +73,7 @@ class JSON::PullParser assert_array { } end - def assert_object + def assert_object(&) kind.should eq(JSON::PullParser::Kind::BeginObject) read_next yield diff --git a/spec/std/spec/junit_formatter_spec.cr b/spec/std/spec/junit_formatter_spec.cr index 36591f816f5b..39e0841362a9 100644 --- a/spec/std/spec/junit_formatter_spec.cr +++ b/spec/std/spec/junit_formatter_spec.cr @@ -168,7 +168,7 @@ describe "JUnit Formatter" do end end -private def build_report(timestamp = nil) +private def build_report(timestamp = nil, &) output = String::Builder.new formatter = Spec::JUnitFormatter.new(output) formatter.started_at = timestamp if timestamp @@ -177,7 +177,7 @@ private def build_report(timestamp = nil) output.to_s.chomp end -private def build_report_with_no_timestamp +private def build_report_with_no_timestamp(&) output = build_report do |formatter| yield formatter end diff --git a/spec/std/spec/tap_formatter_spec.cr b/spec/std/spec/tap_formatter_spec.cr index daf8e04a08c8..8e84ce36cb7d 100644 --- a/spec/std/spec/tap_formatter_spec.cr +++ b/spec/std/spec/tap_formatter_spec.cr @@ -1,6 +1,6 @@ require "spec" -private def build_report +private def build_report(&) String.build do |io| formatter = Spec::TAPFormatter.new(io) yield formatter diff --git a/spec/std/spec_helper.cr b/spec/std/spec_helper.cr index 19d035cab142..246762432c04 100644 --- a/spec/std/spec_helper.cr +++ b/spec/std/spec_helper.cr @@ -75,7 +75,7 @@ def spawn_and_check(before : Proc(_), file = __FILE__, line = __LINE__, &block : end end -def compile_file(source_file, *, bin_name = "executable_file", flags = %w(), file = __FILE__) +def compile_file(source_file, *, bin_name = "executable_file", flags = %w(), file = __FILE__, &) with_temp_executable(bin_name, file: file) do |executable_file| compiler = ENV["CRYSTAL_SPEC_COMPILER_BIN"]? || "bin/crystal" Process.run(compiler, ["build"] + flags + ["-o", executable_file, source_file], env: { @@ -89,7 +89,7 @@ def compile_file(source_file, *, bin_name = "executable_file", flags = %w(), fil end end -def compile_source(source, flags = %w(), file = __FILE__) +def compile_source(source, flags = %w(), file = __FILE__, &) with_tempfile("source_file", file: file) do |source_file| File.write(source_file, source) compile_file(source_file, flags: flags, file: file) do |executable_file| @@ -114,7 +114,7 @@ def compile_and_run_source(source, flags = %w(), file = __FILE__) end end -def compile_and_run_source_with_c(c_code, crystal_code, flags = %w(--debug), file = __FILE__) +def compile_and_run_source_with_c(c_code, crystal_code, flags = %w(--debug), file = __FILE__, &) with_temp_c_object_file(c_code, file: file) do |o_filename| yield compile_and_run_source(%( require "prelude" diff --git a/spec/std/time/span_spec.cr b/spec/std/time/span_spec.cr index 3ea26728998e..e59ece5a66d8 100644 --- a/spec/std/time/span_spec.cr +++ b/spec/std/time/span_spec.cr @@ -1,7 +1,7 @@ require "spec" require "spec/helpers/iterate" -private def expect_overflow +private def expect_overflow(&) expect_raises ArgumentError, "Time::Span too big or too small" do yield end diff --git a/spec/std/time/spec_helper.cr b/spec/std/time/spec_helper.cr index 507a210eff5e..00ee69632852 100644 --- a/spec/std/time/spec_helper.cr +++ b/spec/std/time/spec_helper.cr @@ -10,7 +10,7 @@ class Time::Location end end -def with_env(name, value) +def with_env(name, value, &) previous = ENV[name]? begin ENV[name] = value @@ -25,7 +25,7 @@ end ZONEINFO_ZIP = datapath("zoneinfo.zip") -def with_zoneinfo(path = ZONEINFO_ZIP) +def with_zoneinfo(path = ZONEINFO_ZIP, &) with_env("ZONEINFO", path) do Time::Location.__clear_location_cache diff --git a/spec/std/xml/builder_spec.cr b/spec/std/xml/builder_spec.cr index be4e8ecff665..0fd7450b0104 100644 --- a/spec/std/xml/builder_spec.cr +++ b/spec/std/xml/builder_spec.cr @@ -2,7 +2,7 @@ require "spec" require "xml" require "../../support/string" -private def assert_built(expected, quote_char = nil, *, file = __FILE__, line = __LINE__) +private def assert_built(expected, quote_char = nil, *, file = __FILE__, line = __LINE__, &) assert_prints XML.build(quote_char: quote_char) { |xml| with xml yield xml }, expected, file: file, line: line end diff --git a/spec/std/yaml/builder_spec.cr b/spec/std/yaml/builder_spec.cr index c86909e886af..e225a745587d 100644 --- a/spec/std/yaml/builder_spec.cr +++ b/spec/std/yaml/builder_spec.cr @@ -2,7 +2,7 @@ require "spec" require "yaml" require "../../support/string" -private def assert_built(expected, expect_document_end = false, *, file = __FILE__, line = __LINE__) +private def assert_built(expected, expect_document_end = false, *, file = __FILE__, line = __LINE__, &) # libyaml 0.2.1 removed the erroneously written document end marker (`...`) after some scalars in root context (see https://github.com/yaml/libyaml/pull/18). # Earlier libyaml releases still write the document end marker and this is hard to fix on Crystal's side. # So we just ignore it and adopt the specs accordingly to coincide with the used libyaml version. diff --git a/spec/std/yaml/nodes/builder_spec.cr b/spec/std/yaml/nodes/builder_spec.cr index 8f1778dcb629..16a589f4be5e 100644 --- a/spec/std/yaml/nodes/builder_spec.cr +++ b/spec/std/yaml/nodes/builder_spec.cr @@ -1,7 +1,7 @@ require "spec" require "yaml" -private def assert_built(expected, expect_document_end = false) +private def assert_built(expected, expect_document_end = false, &) # libyaml 0.2.1 removed the erroneously written document end marker (`...`) after some scalars in root context (see https://github.com/yaml/libyaml/pull/18). # Earlier libyaml releases still write the document end marker and this is hard to fix on Crystal's side. # So we just ignore it and adopt the specs accordingly to coincide with the used libyaml version. diff --git a/spec/support/env.cr b/spec/support/env.cr index 66f1549c5bea..616f1cc60cd5 100644 --- a/spec/support/env.cr +++ b/spec/support/env.cr @@ -1,4 +1,4 @@ -def with_env(values : Hash) +def with_env(values : Hash, &) old_values = {} of String => String? begin values.each do |key, value| @@ -15,6 +15,6 @@ def with_env(values : Hash) end end -def with_env(**values) +def with_env(**values, &) with_env(values.to_h) { yield } end diff --git a/spec/support/finalize.cr b/spec/support/finalize.cr index 115f036d1993..967b3c78bf9f 100644 --- a/spec/support/finalize.cr +++ b/spec/support/finalize.cr @@ -30,7 +30,7 @@ module FinalizeCounter end end -def assert_finalizes(key : String) +def assert_finalizes(key : String, &) FinalizeState.reset FinalizeState.count(key).should eq(0) diff --git a/spec/support/retry.cr b/spec/support/retry.cr index 1c368dee0134..638804c4be81 100644 --- a/spec/support/retry.cr +++ b/spec/support/retry.cr @@ -1,4 +1,4 @@ -def retry(n = 5) +def retry(n = 5, &) exception = nil n.times do |i| yield diff --git a/spec/support/tempfile.cr b/spec/support/tempfile.cr index 1d62cc33ff87..41aa209abde9 100644 --- a/spec/support/tempfile.cr +++ b/spec/support/tempfile.cr @@ -21,7 +21,7 @@ SPEC_TEMPFILE_CLEANUP = ENV["SPEC_TEMPFILE_CLEANUP"]? != "0" # # If the environment variable `SPEC_TEMPFILE_CLEANUP` is set to `0`, no paths # will be cleaned up, enabling easier debugging. -def with_tempfile(*paths, file = __FILE__) +def with_tempfile(*paths, file = __FILE__, &) calling_spec = File.basename(file).rchop("_spec.cr") paths = paths.map { |path| File.join(SPEC_TEMPFILE_PATH, calling_spec, path) } FileUtils.mkdir_p(File.join(SPEC_TEMPFILE_PATH, calling_spec)) @@ -37,7 +37,7 @@ def with_tempfile(*paths, file = __FILE__) end end -def with_temp_executable(name, file = __FILE__) +def with_temp_executable(name, file = __FILE__, &) {% if flag?(:win32) %} name += ".exe" {% end %} @@ -46,7 +46,7 @@ def with_temp_executable(name, file = __FILE__) end end -def with_temp_c_object_file(c_code, *, filename = "temp_c", file = __FILE__) +def with_temp_c_object_file(c_code, *, filename = "temp_c", file = __FILE__, &) obj_ext = {{ flag?(:msvc) ? ".obj" : ".o" }} with_tempfile("#{filename}.c", "#{filename}#{obj_ext}", file: file) do |c_filename, o_filename| File.write(c_filename, c_code) diff --git a/src/array.cr b/src/array.cr index 31ab55075464..7b283aaf13f2 100644 --- a/src/array.cr +++ b/src/array.cr @@ -1121,7 +1121,7 @@ class Array(T) # `reject!` and `delete` implementation: returns a tuple {x, y} # with x being self/nil (modified, not modified) # and y being the last matching element, or nil - private def internal_delete + private def internal_delete(&) i1 = 0 i2 = 0 match = nil @@ -1214,13 +1214,13 @@ class Array(T) # *arrays* as `Array`s. # Traversal of elements starts from the last given array. @[Deprecated("Use `Indexable.each_cartesian(indexables : Indexable(Indexable), reuse = false, &block)` instead")] - def self.each_product(arrays : Array(Array), reuse = false) + def self.each_product(arrays : Array(Array), reuse = false, &) Indexable.each_cartesian(arrays, reuse: reuse) { |r| yield r } end # :ditto: @[Deprecated("Use `Indexable.each_cartesian(indexables : Indexable(Indexable), reuse = false, &block)` instead")] - def self.each_product(*arrays : Array, reuse = false) + def self.each_product(*arrays : Array, reuse = false, &) Indexable.each_cartesian(arrays, reuse: reuse) { |r| yield r } end @@ -1232,7 +1232,7 @@ class Array(T) ary end - def each_repeated_permutation(size : Int = self.size, reuse = false) : Nil + def each_repeated_permutation(size : Int = self.size, reuse = false, &) : Nil n = self.size return if size != 0 && n == 0 raise ArgumentError.new("Size must be positive") if size < 0 @@ -1269,7 +1269,7 @@ class Array(T) # ``` # # See also: `#truncate`. - def pop + def pop(&) if @size == 0 yield else @@ -1461,7 +1461,7 @@ class Array(T) # ``` # # See also: `#truncate`. - def shift + def shift(&) if @size == 0 yield else diff --git a/src/base64.cr b/src/base64.cr index 10ef444f08f1..241d00c57bda 100644 --- a/src/base64.cr +++ b/src/base64.cr @@ -68,7 +68,7 @@ module Base64 count end - private def encode_with_new_lines(data) + private def encode_with_new_lines(data, &) inc = 0 to_base64(data.to_slice, CHARS_STD, pad: true) do |byte| yield byte @@ -199,7 +199,7 @@ module Base64 (str_size * 3 / 4.0).to_i + 4 end - private def to_base64(data, chars, pad = false) + private def to_base64(data, chars, pad = false, &) bytes = chars.to_unsafe size = data.size cstr = data.to_unsafe diff --git a/src/benchmark.cr b/src/benchmark.cr index da2a10be9def..59cad44ba13e 100644 --- a/src/benchmark.cr +++ b/src/benchmark.cr @@ -85,7 +85,7 @@ module Benchmark # Main interface of the `Benchmark` module. Yields a `Job` to which # one can report the benchmarks. See the module's description. - def bm + def bm(&) {% if !flag?(:release) %} puts "Warning: benchmarking without the `--release` flag won't yield useful results" {% end %} @@ -103,7 +103,7 @@ module Benchmark # those stages in seconds. For more detail on these stages see # `Benchmark::IPS`. When the *interactive* parameter is `true`, results are # displayed and updated as they are calculated, otherwise all at once after they finished. - def ips(calculation = 5, warmup = 2, interactive = STDOUT.tty?) + def ips(calculation = 5, warmup = 2, interactive = STDOUT.tty?, &) {% if !flag?(:release) %} puts "Warning: benchmarking without the `--release` flag won't yield useful results" {% end %} @@ -116,7 +116,7 @@ module Benchmark end # Returns the time used to execute the given block. - def measure(label = "") : BM::Tms + def measure(label = "", &) : BM::Tms t0, r0 = Process.times, Time.monotonic yield t1, r1 = Process.times, Time.monotonic @@ -133,7 +133,7 @@ module Benchmark # ``` # Benchmark.realtime { "a" * 100_000 } # => 00:00:00.0005840 # ``` - def realtime : Time::Span + def realtime(&) : Time::Span Time.measure { yield } end @@ -142,7 +142,7 @@ module Benchmark # ``` # Benchmark.memory { Array(Int32).new } # => 32 # ``` - def memory + def memory(&) bytes_before_measure = GC.stats.total_bytes yield (GC.stats.total_bytes - bytes_before_measure).to_i64 diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index 1cb44c0b1657..b5d9e93f2d29 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -398,7 +398,7 @@ struct BigDecimal < Number round_impl { |rem, rem_range| rem.abs >= rem_range // 2 } end - private def round_impl + private def round_impl(&) return self if @scale <= 0 || zero? # `self == @value / 10 ** @scale == mantissa + (rem / 10 ** @scale)` diff --git a/src/big/big_float.cr b/src/big/big_float.cr index 6a9a7dbe0862..7c90a6985207 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -76,7 +76,7 @@ struct BigFloat < Float def initialize(@mpf : LibGMP::MPF) end - def self.new + def self.new(&) LibGMP.mpf_init(out mpf) yield pointerof(mpf) new(mpf) diff --git a/src/big/big_int.cr b/src/big/big_int.cr index f703a6854126..aaae48999938 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -93,7 +93,7 @@ struct BigInt < Int end # :nodoc: - def self.new + def self.new(&) LibGMP.init(out mpz) yield pointerof(mpz) new(mpz) diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index 9c16d1577c69..c87273788fef 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -74,7 +74,7 @@ struct BigRational < Number end # :nodoc: - def self.new + def self.new(&) LibGMP.mpq_init(out mpq) yield pointerof(mpq) new(mpq) diff --git a/src/bit_array.cr b/src/bit_array.cr index 9003de5d396f..080c8475765b 100644 --- a/src/bit_array.cr +++ b/src/bit_array.cr @@ -643,7 +643,7 @@ struct BitArray bit_index_and_sub_index(index) { raise IndexError.new } end - private def bit_index_and_sub_index(index) + private def bit_index_and_sub_index(index, &) index = check_index_out_of_bounds(index) do return yield end diff --git a/src/channel.cr b/src/channel.cr index 151972418012..999e0af6855d 100644 --- a/src/channel.cr +++ b/src/channel.cr @@ -288,7 +288,7 @@ class Channel(T) receive_impl { return nil } end - private def receive_impl + private def receive_impl(&) receiver = Receiver(T).new @lock.lock @@ -486,7 +486,7 @@ class Channel(T) raise "BUG: Fiber was awaken from select but no action was activated" end - private def self.each_skip_duplicates(ops_locks) + private def self.each_skip_duplicates(ops_locks, &) # Avoid deadlocks from trying to lock the same lock twice. # `ops_lock` is sorted by `lock_object_id`, so identical onces will be in # a row and we skip repeats while iterating. diff --git a/src/char.cr b/src/char.cr index cb8afeb83230..f64b247efd9c 100644 --- a/src/char.cr +++ b/src/char.cr @@ -409,7 +409,7 @@ struct Char # This method takes into account the possibility that an downcase # version of a char might result in multiple chars, like for # 'İ', which results in 'i' and a dot mark. - def downcase(options : Unicode::CaseOptions = :none) + def downcase(options : Unicode::CaseOptions = :none, &) Unicode.downcase(self, options) { |char| yield char } end @@ -441,7 +441,7 @@ struct Char # 'z'.upcase { |v| puts v } # prints 'Z' # 'ffl'.upcase { |v| puts v } # prints 'F', 'F', 'L' # ``` - def upcase(options : Unicode::CaseOptions = :none) + def upcase(options : Unicode::CaseOptions = :none, &) Unicode.upcase(self, options) { |char| yield char } end @@ -615,7 +615,7 @@ struct Char io << dump end - private def dump_or_inspect + private def dump_or_inspect(&) case self when '\'' then "'\\''" when '\\' then "'\\\\'" @@ -804,7 +804,7 @@ struct Char # 129 # 130 # ``` - def each_byte : Nil + def each_byte(&) : Nil # See http://en.wikipedia.org/wiki/UTF-8#Sample_code c = ord diff --git a/src/char/reader.cr b/src/char/reader.cr index 22bb978b5669..6213883cdc3a 100644 --- a/src/char/reader.cr +++ b/src/char/reader.cr @@ -176,7 +176,7 @@ struct Char # B # C # ``` - def each : Nil + def each(&) : Nil while has_next? yield current_char @pos += @current_char_width diff --git a/src/colorize.cr b/src/colorize.cr index 429795087779..8cdd6648e8ff 100644 --- a/src/colorize.cr +++ b/src/colorize.cr @@ -294,7 +294,7 @@ module Colorize end end -private def each_code(mode : Colorize::Mode) +private def each_code(mode : Colorize::Mode, &) yield '1' if mode.bold? yield '2' if mode.dim? yield '4' if mode.underline? @@ -436,7 +436,7 @@ struct Colorize::Object(T) # # io.to_s # returns a colorful string where "colorful" is red, "hello" green, "world" blue and " string" red again # ``` - def surround(io = STDOUT) + def surround(io = STDOUT, &) return yield io unless @enabled Object.surround(io, to_named_tuple) do |io| @@ -458,7 +458,7 @@ struct Colorize::Object(T) mode: Mode::None, } - protected def self.surround(io, color) + protected def self.surround(io, color, &) last_color = @@last_color must_append_end = append_start(io, color) @@last_color = color diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 058108a25710..371df0592767 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -1401,7 +1401,7 @@ module Crystal codegen_type_filter node, &.filter_by_responds_to(node.name) end - def codegen_type_filter(node) + def codegen_type_filter(node, &) accept node.obj obj_type = node.obj.type @@ -1635,7 +1635,7 @@ module Crystal make_fun type, null, null end - def in_main + def in_main(&) old_builder = self.builder old_position = old_builder.insert_block old_llvm_mod = @llvm_mod @@ -1681,7 +1681,7 @@ module Crystal block_value end - def define_main_function(name, arg_types, return_type, needs_alloca = false) + def define_main_function(name, arg_types, return_type, needs_alloca = false, &) if @llvm_mod != @main_mod raise "wrong usage of define_main_function: you must put it inside an `in_main` block" end @@ -1920,7 +1920,7 @@ module Crystal in_alloca_block { builder.alloca type, name } end - def in_alloca_block + def in_alloca_block(&) old_block = insert_block position_at_end alloca_block value = yield @@ -1949,7 +1949,7 @@ module Crystal # debug_codegen_log { {"Lorem %d", [an_int_llvm_value] of LLVM::Value} } # ``` # - def debug_codegen_log(file = __FILE__, line = __LINE__) + def debug_codegen_log(file = __FILE__, line = __LINE__, &) return unless ENV["CRYSTAL_DEBUG_CODEGEN"]? printf_args = yield || "" printf_args = {printf_args, [] of LLVM::Value} if printf_args.is_a?(String) @@ -1986,7 +1986,7 @@ module Crystal @last = type_ptr end - def allocate_tuple(type) + def allocate_tuple(type, &) struct_type = alloca llvm_type(type) type.tuple_types.each_with_index do |tuple_type, i| exp_type, value = yield tuple_type, i @@ -2037,7 +2037,7 @@ module Crystal generic_malloc(type) { crystal_malloc_atomic_fun } end - def generic_malloc(type) + def generic_malloc(type, &) size = type.size if malloc_fun = yield @@ -2057,7 +2057,7 @@ module Crystal generic_array_malloc(type, count) { crystal_malloc_atomic_fun } end - def generic_array_malloc(type, count) + def generic_array_malloc(type, count, &) size = builder.mul type.size, count if malloc_fun = yield @@ -2245,7 +2245,7 @@ module Crystal end end - def request_value(request : Bool = true) + def request_value(request : Bool = true, &) old_needs_value = @needs_value @needs_value = request begin diff --git a/src/compiler/crystal/codegen/context.cr b/src/compiler/crystal/codegen/context.cr index d89f8c0e199d..595306476b34 100644 --- a/src/compiler/crystal/codegen/context.cr +++ b/src/compiler/crystal/codegen/context.cr @@ -61,11 +61,11 @@ class Crystal::CodeGenVisitor end end - def with_cloned_context(new_context = @context) + def with_cloned_context(new_context = @context, &) with_context(new_context.clone) { |ctx| yield ctx } end - def with_context(new_context) + def with_context(new_context, &) old_context = @context @context = new_context value = yield old_context diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index 04d9aef2876d..6124840113c3 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -340,7 +340,7 @@ module Crystal end end - private def declare_local(type, alloca, location, basic_block : LLVM::BasicBlock? = nil) + private def declare_local(type, alloca, location, basic_block : LLVM::BasicBlock? = nil, &) location = location.try &.expanded_location return false unless location diff --git a/src/compiler/crystal/codegen/phi.cr b/src/compiler/crystal/codegen/phi.cr index 2a0b81d2f5b4..168f30d26293 100644 --- a/src/compiler/crystal/codegen/phi.cr +++ b/src/compiler/crystal/codegen/phi.cr @@ -6,7 +6,7 @@ class Crystal::CodeGenVisitor property? force_exit_block = false - def self.open(codegen, node, needs_value = true) + def self.open(codegen, node, needs_value = true, &) block = new codegen, node, needs_value yield block block.close diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index bc71b573e889..da368301015a 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -818,7 +818,7 @@ class Crystal::CodeGenVisitor end end - def set_aggregate_field(node, target_def, call_args) + def set_aggregate_field(node, target_def, call_args, &) call_arg = call_args[1] original_call_arg = call_arg diff --git a/src/compiler/crystal/command/cursor.cr b/src/compiler/crystal/command/cursor.cr index 6ea2d3be0317..cf8d9a0b2803 100644 --- a/src/compiler/crystal/command/cursor.cr +++ b/src/compiler/crystal/command/cursor.cr @@ -23,7 +23,7 @@ class Crystal::Command end end - private def cursor_command(command, no_cleanup = false, wants_doc = false) + private def cursor_command(command, no_cleanup = false, wants_doc = false, &) config, result = compile_no_codegen command, cursor_command: true, no_cleanup: no_cleanup, diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 0baaf2db0c08..a639e2506def 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -285,7 +285,7 @@ module Crystal result end - private def with_file_lock(output_dir) + private def with_file_lock(output_dir, &) File.open(File.join(output_dir, "compiler.lock"), "w") do |file| file.flock_exclusive do yield @@ -604,7 +604,7 @@ module Crystal end end - private def process_wrapper(command, args = nil) + private def process_wrapper(command, args = nil, &) print_command(command, args) if verbose? status = yield command, args diff --git a/src/compiler/crystal/interpreter/class_vars.cr b/src/compiler/crystal/interpreter/class_vars.cr index fd7d4549e11e..a5f9da19294e 100644 --- a/src/compiler/crystal/interpreter/class_vars.cr +++ b/src/compiler/crystal/interpreter/class_vars.cr @@ -64,7 +64,7 @@ class Crystal::Repl::ClassVars # Yields each index of every class variable that is trivially "initialized" # when the program starts: those that don't have initializers. - def each_initialized_index + def each_initialized_index(&) @data.each_value do |value| yield value.index unless value.compiled_def end diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 5f480e7d18ab..7787c63b73e6 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -30,7 +30,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor # This is different than `compiling_block`. Consider this code: # # ``` - # def foo + # def foo(&) # # When this is called from the top-level, `compiled_block` # # will be the block given to `foo`, but `compiling_block` # # will be `nil` because we are not compiling a block. @@ -44,7 +44,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor # end # end # - # def bar + # def bar(&) # yield # end # @@ -1084,7 +1084,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor false end - private def dispatch_class_var(node : ClassVar) + private def dispatch_class_var(node : ClassVar, &) var = node.var owner = var.owner @@ -1102,7 +1102,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor end end - private def dispatch_class_var(owner : Type, metaclass : Bool, node : ASTNode) + private def dispatch_class_var(owner : Type, metaclass : Bool, node : ASTNode, &) types = owner.all_subclasses.select { |t| t.is_a?(ClassVarContainer) } types.push(owner) types.sort_by! { |type| -type.depth } @@ -3046,7 +3046,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor false end - private def with_scope(scope : Type) + private def with_scope(scope : Type, &) old_scope = @scope @scope = scope begin @@ -3409,7 +3409,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor private macro nop end - private def with_node_override(node_override : ASTNode) + private def with_node_override(node_override : ASTNode, &) old_node_override = @node_override @node_override = node_override value = yield diff --git a/src/compiler/crystal/interpreter/pry_reader.cr b/src/compiler/crystal/interpreter/pry_reader.cr index d7b57fcfaded..4e0a9f64d33b 100644 --- a/src/compiler/crystal/interpreter/pry_reader.cr +++ b/src/compiler/crystal/interpreter/pry_reader.cr @@ -20,15 +20,15 @@ class Crystal::PryReader < Crystal::ReplReader end end - def on_ctrl_down + def on_ctrl_down(&) yield "next" end - def on_ctrl_left + def on_ctrl_left(&) yield "finish" end - def on_ctrl_right + def on_ctrl_right(&) yield "step" end end diff --git a/src/compiler/crystal/macros/interpreter.cr b/src/compiler/crystal/macros/interpreter.cr index 5d56f5320c3c..7da3a4dac39d 100644 --- a/src/compiler/crystal/macros/interpreter.cr +++ b/src/compiler/crystal/macros/interpreter.cr @@ -238,7 +238,7 @@ module Crystal visit_macro_for_array_like node, exp, exp.elements, &.itself end - def visit_macro_for_array_like(node, exp, entries) + def visit_macro_for_array_like(node, exp, entries, &) element_var = node.vars[0] index_var = node.vars[1]? @@ -254,7 +254,7 @@ module Crystal @vars.delete index_var.name if index_var end - def visit_macro_for_hash_like(node, exp, entries) + def visit_macro_for_hash_like(node, exp, entries, &) key_var = node.vars[0] value_var = node.vars[1]? index_var = node.vars[2]? diff --git a/src/compiler/crystal/macros/macros.cr b/src/compiler/crystal/macros/macros.cr index 17388305f8be..c74a482f9d03 100644 --- a/src/compiler/crystal/macros/macros.cr +++ b/src/compiler/crystal/macros/macros.cr @@ -34,7 +34,7 @@ class Crystal::Program parse_macro_source generated_source, macro_expansion_pragmas, the_macro, node, vars, current_def, inside_type, inside_exp, visibility, &.parse(mode) end - def parse_macro_source(generated_source, macro_expansion_pragmas, the_macro, node, vars, current_def = nil, inside_type = false, inside_exp = false, visibility : Visibility = :public) + def parse_macro_source(generated_source, macro_expansion_pragmas, the_macro, node, vars, current_def = nil, inside_type = false, inside_exp = false, visibility : Visibility = :public, &) parser = @program.new_parser(generated_source, var_scopes: [vars.dup]) parser.filename = VirtualFile.new(the_macro, generated_source, node.location) parser.macro_expansion_pragmas = macro_expansion_pragmas diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 2ecdd2c141c8..ff5279bfa022 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -4,7 +4,7 @@ require "semantic_version" module Crystal class MacroInterpreter - private def find_source_file(filename) + private def find_source_file(filename, &) # Support absolute paths if filename.starts_with?('/') filename = "#{filename}.cr" unless filename.ends_with?(".cr") @@ -518,21 +518,21 @@ module Crystal to_number <=> other.to_number end - def bool_bin_op(method, args, named_args, block) + def bool_bin_op(method, args, named_args, block, &) interpret_check_args do |other| raise "can't #{method} with #{other}" unless other.is_a?(NumberLiteral) BoolLiteral.new(yield to_number, other.to_number) end end - def num_bin_op(method, args, named_args, block) + def num_bin_op(method, args, named_args, block, &) interpret_check_args do |other| raise "can't #{method} with #{other}" unless other.is_a?(NumberLiteral) NumberLiteral.new(yield to_number, other.to_number) end end - def int_bin_op(method, args, named_args, block) + def int_bin_op(method, args, named_args, block, &) interpret_check_args do |other| raise "can't #{method} with #{other}" unless other.is_a?(NumberLiteral) me = to_number @@ -1073,7 +1073,7 @@ module Crystal end end - def interpret_map(interpreter) + def interpret_map(interpreter, &) ArrayLiteral.map(interpret_to_range(interpreter)) do |num| yield num end @@ -2683,7 +2683,7 @@ private def filter(object, klass, block, interpreter, keep = true) end) end -private def fetch_annotation(node, method, args, named_args, block) +private def fetch_annotation(node, method, args, named_args, block, &) interpret_check_args(node: node) do |arg| unless arg.is_a?(Crystal::TypeNode) args[0].raise "argument to '#{node.class_desc}#annotation' must be a TypeNode, not #{arg.class_desc}" @@ -2699,7 +2699,7 @@ private def fetch_annotation(node, method, args, named_args, block) end end -private def fetch_annotations(node, method, args, named_args, block) +private def fetch_annotations(node, method, args, named_args, block, &) interpret_check_args(node: node, min_count: 0) do |arg| unless arg return yield(nil) || Crystal::NilLiteral.new diff --git a/src/compiler/crystal/progress_tracker.cr b/src/compiler/crystal/progress_tracker.cr index fb49adaa8bd5..cfbe1e1293af 100644 --- a/src/compiler/crystal/progress_tracker.cr +++ b/src/compiler/crystal/progress_tracker.cr @@ -12,7 +12,7 @@ module Crystal getter stage_progress = 0 getter stage_progress_total : Int32? - def stage(name) + def stage(name, &) @current_stage_name = name print_stats diff --git a/src/compiler/crystal/semantic/bindings.cr b/src/compiler/crystal/semantic/bindings.cr index bba14d9be92b..e2a24f00bdff 100644 --- a/src/compiler/crystal/semantic/bindings.cr +++ b/src/compiler/crystal/semantic/bindings.cr @@ -122,7 +122,7 @@ module Crystal end end - def bind(from = nil) + def bind(from = nil, &) # Quick check to provide a better error message when assigning a type # to a variable whose type is frozen if self.is_a?(MetaTypeVar) && (freeze_type = self.freeze_type) && from && diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index 83fe5f7f88e0..dec1f0bc7dc0 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -488,7 +488,7 @@ class Crystal::Call end end - def tuple_indexer_helper(args, arg_types, owner, instance_type, nilable) + def tuple_indexer_helper(args, arg_types, owner, instance_type, nilable, &) index = tuple_indexer_helper_index(args.first, owner, instance_type, nilable) return unless index @@ -559,7 +559,7 @@ class Crystal::Call index end - def named_tuple_indexer_helper(args, arg_types, owner, instance_type, nilable) + def named_tuple_indexer_helper(args, arg_types, owner, instance_type, nilable, &) arg = args.first # Make it work with constants too @@ -754,7 +754,7 @@ class Crystal::Call end end - def in_macro_target + def in_macro_target(&) if with_scope = @with_scope macros = yield with_scope return macros if macros @@ -1099,7 +1099,7 @@ class Crystal::Call context.defining_type.lookup_type?(node, self_type: context.instantiated_type.instance_type, free_vars: context.free_vars, allow_typeof: false) end - def bubbling_exception + def bubbling_exception(&) yield rescue ex : Crystal::CodeError if obj = @obj diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index 875fd5269380..e62ce819dd88 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -241,7 +241,7 @@ class Crystal::Call end end - private def gather_names_in_all_overloads(call_errors, error_type : T.class) forall T + private def gather_names_in_all_overloads(call_errors, error_type : T.class, &) forall T return unless call_errors.all?(T) call_errors = call_errors.map &.as(T) @@ -379,7 +379,7 @@ class Crystal::Call end end - private def raise_no_overload_matches(node, defs, arg_types, inner_exception) + private def raise_no_overload_matches(node, defs, arg_types, inner_exception, &) error_message = String.build do |str| yield str @@ -977,7 +977,7 @@ class Crystal::Call end end - def check_recursive_splat_call(a_def, args) + def check_recursive_splat_call(a_def, args, &) if a_def.splat_index previous_splat_types = program.splat_expansions[a_def] ||= [] of Type previous_splat_types.push(args.values.last.type) diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 0d7aa41ced5b..0126c62d1166 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -120,7 +120,7 @@ module Crystal @last_is_falsey = false end - def compute_last_truthiness + def compute_last_truthiness(&) reset_last_status yield {@last_is_truthy, @last_is_falsey} @@ -877,7 +877,7 @@ module Crystal transform_is_a_or_responds_to node, &.filter_by_responds_to(node.name) end - def transform_is_a_or_responds_to(node) + def transform_is_a_or_responds_to(node, &) obj = node.obj if obj_type = obj.type? diff --git a/src/compiler/crystal/semantic/conversions.cr b/src/compiler/crystal/semantic/conversions.cr index 98e014bdb0ec..8c8449441e50 100644 --- a/src/compiler/crystal/semantic/conversions.cr +++ b/src/compiler/crystal/semantic/conversions.cr @@ -33,7 +33,7 @@ module Crystal::Conversions unsafe_call end - def self.try_to_unsafe(target, visitor) + def self.try_to_unsafe(target, visitor, &) unsafe_call = Call.new(target, "to_unsafe").at(target) begin unsafe_call.accept visitor diff --git a/src/compiler/crystal/semantic/cover.cr b/src/compiler/crystal/semantic/cover.cr index 4674d0a12248..b58515737892 100644 --- a/src/compiler/crystal/semantic/cover.cr +++ b/src/compiler/crystal/semantic/cover.cr @@ -178,7 +178,7 @@ module Crystal end class Type - def each_cover + def each_cover(&) yield self end @@ -214,7 +214,7 @@ module Crystal end class UnionType - def each_cover + def each_cover(&) @union_types.each do |union_type| yield union_type end @@ -236,7 +236,7 @@ module Crystal end class VirtualType - def each_cover + def each_cover(&) subtypes.each do |subtype| yield subtype end diff --git a/src/compiler/crystal/semantic/filters.cr b/src/compiler/crystal/semantic/filters.cr index 86938b7e7b77..66d1a728804b 100644 --- a/src/compiler/crystal/semantic/filters.cr +++ b/src/compiler/crystal/semantic/filters.cr @@ -346,7 +346,7 @@ module Crystal new_filters end - def each + def each(&) pos.each do |key, value| yield key, value end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index e3c710134e1e..f92f61589f28 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -1507,7 +1507,7 @@ module Crystal end end - def check_lib_call_arg(method, arg_index) + def check_lib_call_arg(method, arg_index, &) method_arg = method.args[arg_index]? return unless method_arg @@ -2179,7 +2179,7 @@ module Crystal filter_vars(filters) { |filter| filter } end - def filter_vars(filters) + def filter_vars(filters, &) filters.try &.each do |name, filter| existing_var = @vars[name] filtered_var = MetaVar.new(name) @@ -2249,7 +2249,7 @@ module Crystal @unreachable = true end - def with_block_kind(kind : BlockKind) + def with_block_kind(kind : BlockKind, &) old_block_kind, @last_block_kind = last_block_kind, kind old_inside_ensure, @inside_ensure = @inside_ensure, @inside_ensure || kind.ensure? yield @@ -3011,7 +3011,7 @@ module Crystal expand(node) { @program.literal_expander.expand_named node, generic_type } end - def expand(node) + def expand(node, &) expanded = yield expanded.accept self node.expanded = expanded @@ -3226,7 +3226,7 @@ module Crystal @needs_type_filters > 0 end - def request_type_filters + def request_type_filters(&) @type_filters = nil @needs_type_filters += 1 begin @@ -3236,7 +3236,7 @@ module Crystal end end - def ignoring_type_filters + def ignoring_type_filters(&) needs_type_filters, @needs_type_filters = @needs_type_filters, 0 begin yield diff --git a/src/compiler/crystal/semantic/match.cr b/src/compiler/crystal/semantic/match.cr index f18572f69291..b42c80be7818 100644 --- a/src/compiler/crystal/semantic/match.cr +++ b/src/compiler/crystal/semantic/match.cr @@ -153,7 +153,7 @@ module Crystal end end - def each + def each(&) @success && @matches.try &.each do |match| yield match end diff --git a/src/compiler/crystal/semantic/recursive_struct_checker.cr b/src/compiler/crystal/semantic/recursive_struct_checker.cr index a9c558c6dcad..e7f64913789f 100644 --- a/src/compiler/crystal/semantic/recursive_struct_checker.cr +++ b/src/compiler/crystal/semantic/recursive_struct_checker.cr @@ -183,7 +183,7 @@ class Crystal::RecursiveStructChecker type.struct? && type.is_a?(InstanceVarContainer) && !type.is_a?(PrimitiveType) && !type.is_a?(ProcInstanceType) && !type.abstract? end - def push(path, type) + def push(path, type, &) if path.last? == type yield else diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 9b7ac5c19613..5eea71ecfb03 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -288,7 +288,7 @@ module Crystal end # Yields each pair of corresponding parameters between `self` and *other*. - def each_corresponding_param(other : DefWithMetadata, self_named_args, other_named_args) + def each_corresponding_param(other : DefWithMetadata, self_named_args, other_named_args, &) self_arg_index = 0 other_arg_index = 0 diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index 88164a36f914..62dd21a6092d 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -328,7 +328,7 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor true end - def expand_macro(the_macro, node, mode = nil, *, visibility : Visibility, accept = true) + def expand_macro(the_macro, node, mode = nil, *, visibility : Visibility, accept = true, &) expanded_macro, macro_expansion_pragmas = eval_macro(node) do yield @@ -447,7 +447,7 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor generated_nodes end - def eval_macro(node) + def eval_macro(node, &) yield rescue ex : MacroRaiseException node.raise ex.message, exception_type: MacroRaiseException @@ -455,7 +455,7 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor node.raise "expanding macro", ex end - def process_annotations(annotations) + def process_annotations(annotations, &) annotations.try &.each do |ann| annotation_type = lookup_annotation(ann) validate_annotation(annotation_type, ann) @@ -556,7 +556,7 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor @exp_nest > 0 end - def pushing_type(type : ModuleType) + def pushing_type(type : ModuleType, &) old_type = @current_type @current_type = type read_annotations diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 98ea7faa80f1..e650da690019 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -1152,7 +1152,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor annotations.try(&.first?).try &.doc end - def process_def_annotations(node, annotations) + def process_def_annotations(node, annotations, &) process_annotations(annotations) do |annotation_type, ann| case annotation_type when @program.no_inline_annotation diff --git a/src/compiler/crystal/semantic/type_lookup.cr b/src/compiler/crystal/semantic/type_lookup.cr index 64c3b319cf6e..0db48a0298aa 100644 --- a/src/compiler/crystal/semantic/type_lookup.cr +++ b/src/compiler/crystal/semantic/type_lookup.cr @@ -426,7 +426,7 @@ class Crystal::Type Crystal.check_type_can_be_stored(ident, type, message) end - def in_generic_args + def in_generic_args(&) @in_generic_args += 1 value = yield @in_generic_args -= 1 diff --git a/src/compiler/crystal/semantic/type_merge.cr b/src/compiler/crystal/semantic/type_merge.cr index 0d79faee06b1..d68cdeb38a99 100644 --- a/src/compiler/crystal/semantic/type_merge.cr +++ b/src/compiler/crystal/semantic/type_merge.cr @@ -66,7 +66,7 @@ module Crystal compact_types(types) { |type| type } end - def compact_types(objects) : Array(Type) + def compact_types(objects, &) : Array(Type) all_types = Array(Type).new(objects.size) objects.each { |obj| add_type all_types, yield(obj) } all_types.reject! &.no_return? if all_types.size > 1 diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 81320af3b063..25198b4e863d 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -425,11 +425,11 @@ module Crystal def initialize(@elements = [] of ASTNode, @of = nil, @name = nil) end - def self.map(values, of = nil) + def self.map(values, of = nil, &) new(values.map { |value| (yield value).as(ASTNode) }, of: of) end - def self.map_with_index(values) + def self.map_with_index(values, &) new(values.map_with_index { |value, idx| (yield value, idx).as(ASTNode) }, of: nil) end @@ -540,11 +540,11 @@ module Crystal def initialize(@elements) end - def self.map(values) + def self.map(values, &) new(values.map { |value| (yield value).as(ASTNode) }) end - def self.map_with_index(values) + def self.map_with_index(values, &) new(values.map_with_index { |value, idx| (yield value, idx).as(ASTNode) }) end diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 008c4ff1f9ee..92e4f1377f01 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -2008,7 +2008,7 @@ module Crystal @token end - def lookahead(preserve_token_on_fail = false) + def lookahead(preserve_token_on_fail = false, &) old_pos, old_line, old_column = current_pos, @line_number, @column_number @temp_token.copy_from(@token) if preserve_token_on_fail @@ -2020,7 +2020,7 @@ module Crystal result end - def peek_ahead + def peek_ahead(&) result = uninitialized typeof(yield) lookahead(preserve_token_on_fail: true) do result = yield @@ -2391,7 +2391,7 @@ module Crystal @token end - def char_to_hex(char) + def char_to_hex(char, &) if '0' <= char <= '9' char - '0' elsif 'a' <= char <= 'f' diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 4e85bc738f84..859c1b138f46 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -344,7 +344,7 @@ module Crystal atomic end - def parse_expression_suffix(location) + def parse_expression_suffix(location, &) slash_is_regex! next_token_skip_statement_end exp = parse_op_assign_no_control @@ -1226,7 +1226,7 @@ module Crystal end end - def check_type_declaration + def check_type_declaration(&) if next_comes_colon_space? name = @token.value.to_s var = Var.new(name).at(@token.location).at_end(token_end_location) @@ -1321,7 +1321,7 @@ module Crystal type end - def check_not_inside_def(message) + def check_not_inside_def(message, &) if @def_nest == 0 && @fun_nest == 0 yield else @@ -4400,7 +4400,7 @@ module Crystal comes_plus_or_minus end - def preserve_stop_on_do(new_value = false) + def preserve_stop_on_do(new_value = false, &) old_stop_on_do = @stop_on_do @stop_on_do = new_value value = yield @@ -4436,7 +4436,7 @@ module Crystal end end - def parse_block2 + def parse_block2(&) location = @token.location block_params = [] of Var @@ -5177,7 +5177,7 @@ module Crystal named_args end - def parse_type_splat + def parse_type_splat(&) location = @token.location splat = false @@ -6199,7 +6199,7 @@ module Crystal # If *create_scope* is true, creates an isolated variable scope and returns # the yield result, resetting the scope afterwards. Otherwise simply returns # the yield result without touching the scopes. - def with_isolated_var_scope(create_scope = true) + def with_isolated_var_scope(create_scope = true, &) return yield unless create_scope begin @@ -6212,7 +6212,7 @@ module Crystal # Creates a new variable scope with the same variables as the current scope, # and then returns the yield result, resetting the scope afterwards. - def with_lexical_var_scope + def with_lexical_var_scope(&) current_scope = @var_scopes.last.dup @var_scopes.push current_scope yield @@ -6254,7 +6254,7 @@ module Crystal @var_scopes.last.includes? name end - def open(symbol, location = @token.location) + def open(symbol, location = @token.location, &) @unclosed_stack.push Unclosed.new(symbol, location) begin value = yield @@ -6325,7 +6325,7 @@ module Crystal name == "self" || var_in_scope?(name) end - def push_visibility + def push_visibility(&) old_visibility = @visibility @visibility = nil value = yield diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 405c21b32159..c6cd759d57dd 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -110,7 +110,7 @@ module Crystal false end - def visit_interpolation(node) + def visit_interpolation(node, &) node.expressions.each do |exp| if exp.is_a?(StringLiteral) @str << yield exp.value @@ -476,7 +476,7 @@ module Crystal end end - def in_parenthesis(need_parens) + def in_parenthesis(need_parens, &) if need_parens @str << '(' yield @@ -1509,7 +1509,7 @@ module Crystal end end - def with_indent + def with_indent(&) @indent += 1 yield @indent -= 1 @@ -1534,13 +1534,13 @@ module Crystal newline end - def inside_macro + def inside_macro(&) @inside_macro += 1 yield @inside_macro -= 1 end - def outside_macro + def outside_macro(&) old_inside_macro = @inside_macro @inside_macro = 0 yield diff --git a/src/compiler/crystal/tools/context.cr b/src/compiler/crystal/tools/context.cr index 5bc6dd43e0e9..5be3a349530e 100644 --- a/src/compiler/crystal/tools/context.cr +++ b/src/compiler/crystal/tools/context.cr @@ -112,7 +112,7 @@ module Crystal @found_untyped_def = false end - def inside_typed_def + def inside_typed_def(&) @inside_typed_def = true yield.tap { @inside_typed_def = false } end diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 9650696f8f23..4ac5e9ee37aa 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1504,12 +1504,14 @@ module Crystal end def format_def_args(node : Def | Macro) - format_def_args node.args, node.block_arg, node.splat_index, false, node.double_splat + yields = node.is_a?(Def) && !node.block_arity.nil? + format_def_args node.args, node.block_arg, node.splat_index, false, node.double_splat, yields end - def format_def_args(args : Array, block_arg, splat_index, variadic, double_splat) + def format_def_args(args : Array, block_arg, splat_index, variadic, double_splat, yields) # If there are no args, remove extra "()" if args.empty? && !block_arg && !double_splat && !variadic + write "(&)" if yields if @token.type.op_lparen? next_token_skip_space_or_newline check :OP_RPAREN @@ -1541,7 +1543,7 @@ module Crystal end args.each_with_index do |arg, i| - has_more = !last?(i, args) || double_splat || block_arg || variadic + has_more = !last?(i, args) || double_splat || block_arg || yields || variadic wrote_newline = format_def_arg(wrote_newline, has_more) do if i == splat_index write_token :OP_STAR @@ -1557,7 +1559,7 @@ module Crystal end if double_splat - wrote_newline = format_def_arg(wrote_newline, block_arg) do + wrote_newline = format_def_arg(wrote_newline, block_arg || yields) do write_token :OP_STAR_STAR skip_space_or_newline @@ -1575,6 +1577,11 @@ module Crystal to_skip += 1 if at_skip? block_arg.accept self end + elsif yields + wrote_newline = format_def_arg(wrote_newline, false) do + write "&" + skip_space_or_newline + end end if variadic @@ -1620,7 +1627,7 @@ module Crystal end end - def format_def_arg(wrote_newline, has_more) + def format_def_arg(wrote_newline, has_more, &) write_indent if wrote_newline yield @@ -1653,6 +1660,10 @@ module Crystal else write " " if has_more && !just_wrote_newline end + elsif @token.type.op_rparen? && has_more && !just_wrote_newline + # if we found a `)` and there are still more parameters to write, it + # must have been a missing `&` for a def that yields + write " " end just_wrote_newline @@ -1689,7 +1700,7 @@ module Crystal end end - format_def_args node.args, nil, nil, node.varargs?, nil + format_def_args node.args, nil, nil, node.varargs?, nil, false if return_type = node.return_type skip_space @@ -4445,7 +4456,7 @@ module Crystal false end - def visit_asm_parts(parts, colon_column) : Nil + def visit_asm_parts(parts, colon_column, &) : Nil write " " column = @column @@ -4764,7 +4775,7 @@ module Crystal end end - def indent + def indent(&) @indent += 2 value = yield @indent -= 2 @@ -4775,7 +4786,7 @@ module Crystal indent { accept node } end - def indent(indent : Int) + def indent(indent : Int, &) old_indent = @indent @indent = indent value = yield @@ -4792,7 +4803,7 @@ module Crystal no_indent { accept node } end - def no_indent + def no_indent(&) old_indent = @indent @indent = 0 yield @@ -4812,7 +4823,7 @@ module Crystal indent(indent, node) end - def write_indent(indent = @indent) + def write_indent(indent = @indent, &) write_indent(indent) indent(indent) { yield } end @@ -5149,13 +5160,13 @@ module Crystal index == collection.size - 1 end - def inside_macro + def inside_macro(&) @inside_macro += 1 yield @inside_macro -= 1 end - def outside_macro + def outside_macro(&) old_inside_macro = @inside_macro @inside_macro = 0 yield @@ -5179,13 +5190,13 @@ module Crystal end end - def inside_cond + def inside_cond(&) @inside_cond += 1 yield @inside_cond -= 1 end - def inside_call_or_assign + def inside_call_or_assign(&) @inside_call_or_assign += 1 yield @inside_call_or_assign -= 1 diff --git a/src/compiler/crystal/tools/playground/agent.cr b/src/compiler/crystal/tools/playground/agent.cr index 8e005a8b1263..265bf6be7214 100644 --- a/src/compiler/crystal/tools/playground/agent.cr +++ b/src/compiler/crystal/tools/playground/agent.cr @@ -8,7 +8,7 @@ class Crystal::Playground::Agent @ws = HTTP::WebSocket.new(URI.parse(url)) end - def i(line, names = nil) + def i(line, names = nil, &) value = begin yield rescue ex @@ -58,7 +58,7 @@ class Crystal::Playground::Agent HTML.escape(value.pretty_inspect) end - private def send(message_type) + private def send(message_type, &) message = JSON.build do |json| json.object do json.field "tag", @tag diff --git a/src/compiler/crystal/tools/playground/agent_instrumentor_transformer.cr b/src/compiler/crystal/tools/playground/agent_instrumentor_transformer.cr index 05ca77bffab7..18c02fbe490a 100644 --- a/src/compiler/crystal/tools/playground/agent_instrumentor_transformer.cr +++ b/src/compiler/crystal/tools/playground/agent_instrumentor_transformer.cr @@ -227,7 +227,7 @@ module Crystal node end - def ignoring_line_of_node(node) + def ignoring_line_of_node(node, &) old_ignore_line = @ignore_line @ignore_line = node.location.try(&.line_number) res = yield diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr index 654d0863faba..56d3779b225a 100644 --- a/src/compiler/crystal/tools/playground/server.cr +++ b/src/compiler/crystal/tools/playground/server.cr @@ -124,7 +124,7 @@ module Crystal::Playground Log.warn { "Unable to send message (session=#{@session_key})." } end - def send_with_json_builder + def send_with_json_builder(&) send(JSON.build do |json| json.object do yield json diff --git a/src/compiler/crystal/tools/print_hierarchy.cr b/src/compiler/crystal/tools/print_hierarchy.cr index 75258dbf058f..b6fcb7a76fdc 100644 --- a/src/compiler/crystal/tools/print_hierarchy.cr +++ b/src/compiler/crystal/tools/print_hierarchy.cr @@ -239,7 +239,7 @@ module Crystal end end - def with_indent + def with_indent(&) @indents.push true yield @indents.pop diff --git a/src/compiler/crystal/tools/table_print.cr b/src/compiler/crystal/tools/table_print.cr index c8e53156289c..2c1846010f57 100644 --- a/src/compiler/crystal/tools/table_print.cr +++ b/src/compiler/crystal/tools/table_print.cr @@ -65,7 +65,7 @@ module Crystal @columns = [] of Column end - def build + def build(&) with self yield self render end @@ -74,7 +74,7 @@ module Crystal @data << Separator.new end - def row + def row(&) @last_string_row = [] of Cell @data << last_string_row with self yield @@ -86,7 +86,7 @@ module Crystal column_for_last_cell.will_render(cell) end - def cell(align : Alignment = :left, colspan = 1) + def cell(align : Alignment = :left, colspan = 1, &) cell(String::Builder.build { |io| yield io }, align, colspan) end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index e25f90f1eb6a..8735e77a3730 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -751,7 +751,7 @@ module Crystal # Yields self and returns true if the block returns a truthy value. # UnionType overrides it and yields all types in turn and returns # true if for each of them the block returns true. - def all? + def all?(&) (yield self) ? true : false end @@ -1569,7 +1569,7 @@ module Crystal generic_types.values end - def each_instantiated_type + def each_instantiated_type(&) if types = @generic_types types.each_value { |type| yield type } end @@ -2521,7 +2521,7 @@ module Crystal @instantiations.values end - def each_instantiated_type + def each_instantiated_type(&) @instantiations.each_value { |type| yield type } end @@ -3145,7 +3145,7 @@ module Crystal program.type_merge_union_of filtered_types end - def each_concrete_type + def each_concrete_type(&) union_types.each do |type| if type.is_a?(VirtualType) || type.is_a?(VirtualMetaclassType) type.each_concrete_type do |concrete_type| @@ -3211,7 +3211,7 @@ module Crystal union_types.any? &.unbound? end - def all? + def all?(&) union_types.all? { |union_type| yield union_type } end @@ -3391,7 +3391,7 @@ module Crystal @metaclass ||= VirtualMetaclassType.new(program, self) end - def each_concrete_type + def each_concrete_type(&) subtypes.each do |subtype| yield subtype unless subtype.abstract? end @@ -3490,7 +3490,7 @@ module Crystal base_type.replace_type_parameters(instance).virtual_type.metaclass end - def each_concrete_type + def each_concrete_type(&) instance_type.subtypes.each do |type| yield type.metaclass end diff --git a/src/compress/deflate/reader.cr b/src/compress/deflate/reader.cr index cdb84bbe6a18..d4c6d13d21cf 100644 --- a/src/compress/deflate/reader.cr +++ b/src/compress/deflate/reader.cr @@ -33,7 +33,7 @@ class Compress::Deflate::Reader < IO # Creates a new reader from the given *io*, yields it to the given block, # and closes it at its end. - def self.open(io : IO, sync_close : Bool = false, dict : Bytes? = nil) + def self.open(io : IO, sync_close : Bool = false, dict : Bytes? = nil, &) reader = new(io, sync_close: sync_close, dict: dict) yield reader ensure reader.close end @@ -46,7 +46,7 @@ class Compress::Deflate::Reader < IO # Creates an instance of Flate::Reader for the gzip format, yields it to the given block, and closes # it at its end. - def self.gzip(input, sync_close : Bool = false) + def self.gzip(input, sync_close : Bool = false, &) reader = gzip input, sync_close: sync_close yield reader ensure reader.close end diff --git a/src/compress/deflate/writer.cr b/src/compress/deflate/writer.cr index d72ef4586886..f9a5adeb366f 100644 --- a/src/compress/deflate/writer.cr +++ b/src/compress/deflate/writer.cr @@ -32,7 +32,7 @@ class Compress::Deflate::Writer < IO # and closes it at its end. def self.open(io : IO, level : Int32 = Compress::Deflate::DEFAULT_COMPRESSION, strategy : Compress::Deflate::Strategy = Compress::Deflate::Strategy::DEFAULT, - sync_close : Bool = false, dict : Bytes? = nil) + sync_close : Bool = false, dict : Bytes? = nil, &) writer = new(io, level: level, strategy: strategy, sync_close: sync_close, dict: dict) yield writer ensure writer.close end diff --git a/src/compress/gzip/reader.cr b/src/compress/gzip/reader.cr index 56dac99326d9..0371439acf77 100644 --- a/src/compress/gzip/reader.cr +++ b/src/compress/gzip/reader.cr @@ -65,14 +65,14 @@ class Compress::Gzip::Reader < IO # Creates a new reader from the given *io*, yields it to the given block, # and closes it at the end. - def self.open(io : IO, sync_close = false) + def self.open(io : IO, sync_close = false, &) reader = new(io, sync_close: sync_close) yield reader ensure reader.close end # Creates a new reader from the given *filename*, yields it to the given block, # and closes it at the end. - def self.open(filename : String) + def self.open(filename : String, &) reader = new(filename) yield reader ensure reader.close end diff --git a/src/compress/gzip/writer.cr b/src/compress/gzip/writer.cr index 3d82548e2d2c..f02a320805f6 100644 --- a/src/compress/gzip/writer.cr +++ b/src/compress/gzip/writer.cr @@ -50,14 +50,14 @@ class Compress::Gzip::Writer < IO # Creates a new writer to the given *io*, yields it to the given block, # and closes it at the end. - def self.open(io : IO, level = Compress::Gzip::DEFAULT_COMPRESSION, sync_close = false) + def self.open(io : IO, level = Compress::Gzip::DEFAULT_COMPRESSION, sync_close = false, &) writer = new(io, level: level, sync_close: sync_close) yield writer ensure writer.close end # Creates a new writer to the given *filename*, yields it to the given block, # and closes it at the end. - def self.open(filename : String, level = Compress::Gzip::DEFAULT_COMPRESSION) + def self.open(filename : String, level = Compress::Gzip::DEFAULT_COMPRESSION, &) writer = new(filename, level: level) yield writer ensure writer.close end diff --git a/src/compress/zip/file.cr b/src/compress/zip/file.cr index 166626a0b73f..cea1101c9f30 100644 --- a/src/compress/zip/file.cr +++ b/src/compress/zip/file.cr @@ -50,14 +50,14 @@ class Compress::Zip::File # Opens a `Zip::File` for reading from the given *io*, yields # it to the given block, and closes it at the end. - def self.open(io : IO, sync_close = false) + def self.open(io : IO, sync_close = false, &) zip = new io, sync_close yield zip ensure zip.close end # Opens a `Zip::File` for reading from the given *filename*, yields # it to the given block, and closes it at the end. - def self.open(filename : Path | String) + def self.open(filename : Path | String, &) zip = new filename yield zip ensure zip.close end @@ -166,7 +166,7 @@ class Compress::Zip::File # Yields an `IO` to read this entry's contents. # Multiple entries can be opened and read concurrently. - def open + def open(&) @io.read_at(data_offset.to_i32, compressed_size.to_i32) do |io| io = decompressor_for(io, is_sized: true) checksum_reader = ChecksumReader.new(io, filename, verify: crc32) diff --git a/src/compress/zip/reader.cr b/src/compress/zip/reader.cr index 349216f5d14f..e866bf2c0d0e 100644 --- a/src/compress/zip/reader.cr +++ b/src/compress/zip/reader.cr @@ -42,14 +42,14 @@ class Compress::Zip::Reader # Creates a new reader from the given *io*, yields it to the given block, # and closes it at the end. - def self.open(io : IO, sync_close = false) + def self.open(io : IO, sync_close = false, &) reader = new(io, sync_close: sync_close) yield reader ensure reader.close end # Creates a new reader from the given *filename*, yields it to the given block, # and closes it at the end. - def self.open(filename : Path | String) + def self.open(filename : Path | String, &) reader = new(filename) yield reader ensure reader.close end @@ -94,7 +94,7 @@ class Compress::Zip::Reader end # Yields each entry in the zip to the given block. - def each_entry + def each_entry(&) while entry = next_entry yield entry end diff --git a/src/compress/zip/writer.cr b/src/compress/zip/writer.cr index 87d3495c8fb4..52ddee7400a5 100644 --- a/src/compress/zip/writer.cr +++ b/src/compress/zip/writer.cr @@ -51,14 +51,14 @@ class Compress::Zip::Writer # Creates a new writer to the given *io*, yields it to the given block, # and closes it at the end. - def self.open(io : IO, sync_close = false) + def self.open(io : IO, sync_close = false, &) writer = new(io, sync_close: sync_close) yield writer ensure writer.close end # Creates a new writer to the given *filename*, yields it to the given block, # and closes it at the end. - def self.open(filename : Path | String) + def self.open(filename : Path | String, &) writer = new(filename) yield writer ensure writer.close end @@ -66,7 +66,7 @@ class Compress::Zip::Writer # Adds an entry that will have the given *filename* and current # time (`Time.utc`) and yields an `IO` to write that entry's # contents. - def add(filename : Path | String) + def add(filename : Path | String, &) add(Entry.new(filename.to_s)) do |io| yield io end @@ -85,7 +85,7 @@ class Compress::Zip::Writer # # You can also set the Entry's time (which is `Time.utc` by default) # and extra data before adding it to the zip stream. - def add(entry : Entry) + def add(entry : Entry, &) # bit 3: unknown compression size (not needed for STORED, by if left out it doesn't work...) entry.general_purpose_bit_flag |= (1 << 3) # bit 11: require UTF-8 set diff --git a/src/compress/zlib/reader.cr b/src/compress/zlib/reader.cr index 953e313e255a..36c65ca25716 100644 --- a/src/compress/zlib/reader.cr +++ b/src/compress/zlib/reader.cr @@ -22,7 +22,7 @@ class Compress::Zlib::Reader < IO # Creates a new reader from the given *io*, yields it to the given block, # and closes it at the end. - def self.open(io : IO, sync_close = false, dict : Bytes? = nil) + def self.open(io : IO, sync_close = false, dict : Bytes? = nil, &) reader = new(io, sync_close: sync_close, dict: dict) yield reader ensure reader.close end diff --git a/src/compress/zlib/writer.cr b/src/compress/zlib/writer.cr index bb711be40dc0..909123a13d90 100644 --- a/src/compress/zlib/writer.cr +++ b/src/compress/zlib/writer.cr @@ -26,14 +26,14 @@ class Compress::Zlib::Writer < IO # Creates a new writer to the given *io*, yields it to the given block, # and closes it at the end. - def self.open(io : IO, level = Zlib::DEFAULT_COMPRESSION, sync_close = false, dict : Bytes? = nil) + def self.open(io : IO, level = Zlib::DEFAULT_COMPRESSION, sync_close = false, dict : Bytes? = nil, &) writer = new(io, level: level, sync_close: sync_close, dict: dict) yield writer ensure writer.close end # Creates a new writer to the given *filename*, yields it to the given block, # and closes it at the end. - def self.open(filename : String, level = Zlib::DEFAULT_COMPRESSION, dict : Bytes? = nil) + def self.open(filename : String, level = Zlib::DEFAULT_COMPRESSION, dict : Bytes? = nil, &) writer = new(filename, level: level, dict: dict) yield writer ensure writer.close end diff --git a/src/crystal/dwarf/info.cr b/src/crystal/dwarf/info.cr index 884236a1286b..e4799d9af8bd 100644 --- a/src/crystal/dwarf/info.cr +++ b/src/crystal/dwarf/info.cr @@ -54,7 +54,7 @@ module Crystal @abbreviations = Abbrev.read(io, debug_abbrev_offset) end - def each + def each(&) end_offset = @offset + @unit_length attributes = [] of {AT, FORM, Value} diff --git a/src/crystal/elf.cr b/src/crystal/elf.cr index 160ebf379195..365d88fb59b0 100644 --- a/src/crystal/elf.cr +++ b/src/crystal/elf.cr @@ -146,7 +146,7 @@ module Crystal property! shnum : UInt16 property! shstrndx : UInt16 - def self.open(path) + def self.open(path, &) File.open(path, "r") do |file| yield new(file) end @@ -229,7 +229,7 @@ module Crystal # Searches for a section then yield the `SectionHeader` and the IO object # ready for parsing if the section was found. Returns the valure returned by # the block or nil if the section wasn't found. - def read_section?(name : String) + def read_section?(name : String, &) if sh = section_headers.find { |sh| sh_name(sh.name) == name } @io.seek(sh.offset) do yield sh, @io diff --git a/src/crystal/hasher.cr b/src/crystal/hasher.cr index 4d82fdd0330b..d5b6c863d4ca 100644 --- a/src/crystal/hasher.cr +++ b/src/crystal/hasher.cr @@ -155,7 +155,7 @@ struct Crystal::Hasher {x, exp} end - private def float_normalize_wrap(value) + private def float_normalize_wrap(value, &) return HASH_NAN if value.nan? if value.infinite? return value > 0 ? HASH_INF_PLUS : HASH_INF_MINUS diff --git a/src/crystal/iconv.cr b/src/crystal/iconv.cr index 684cca8745f7..64d4e17f8112 100644 --- a/src/crystal/iconv.cr +++ b/src/crystal/iconv.cr @@ -46,7 +46,7 @@ struct Crystal::Iconv end end - def self.new(from : String, to : String, invalid : Symbol? = nil) + def self.new(from : String, to : String, invalid : Symbol? = nil, &) iconv = new(from, to, invalid) begin yield iconv diff --git a/src/crystal/mach_o.cr b/src/crystal/mach_o.cr index 8a5d3a105549..4a51e1e2d38f 100644 --- a/src/crystal/mach_o.cr +++ b/src/crystal/mach_o.cr @@ -90,7 +90,7 @@ module Crystal @stabs : Array(StabEntry)? @symbols : Array(Nlist64)? - def self.open(path) + def self.open(path, &) File.open(path, "r") do |file| yield new(file) end @@ -366,14 +366,14 @@ module Crystal # Seek to the first matching load command, yields, then returns the value of # the block. - private def seek_to(load_command : LoadCommand) + private def seek_to(load_command : LoadCommand, &) seek_to_each(load_command) do |cmd, cmdsize| return yield cmdsize end end # Seek to each matching load command, yielding each of them. - private def seek_to_each(load_command : LoadCommand) : Nil + private def seek_to_each(load_command : LoadCommand, &) : Nil @io.seek(@ldoff) ncmds.times do @@ -498,7 +498,7 @@ module Crystal String.new(bytes.to_unsafe, len) end - def read_section?(name) + def read_section?(name, &) if sh = sections.find { |s| s.sectname == name } @io.seek(sh.offset) do yield sh, @io diff --git a/src/crystal/main.cr b/src/crystal/main.cr index 7ac9f4da0693..fb95c9930fb4 100644 --- a/src/crystal/main.cr +++ b/src/crystal/main.cr @@ -61,7 +61,7 @@ module Crystal end # :nodoc: - def self.ignore_stdio_errors + def self.ignore_stdio_errors(&) yield rescue IO::Error end diff --git a/src/crystal/pointer_linked_list.cr b/src/crystal/pointer_linked_list.cr index b9476210a542..0ce17b071bd0 100644 --- a/src/crystal/pointer_linked_list.cr +++ b/src/crystal/pointer_linked_list.cr @@ -55,7 +55,7 @@ struct Crystal::PointerLinkedList(T) end # Removes and returns head from the list, yields if empty - def shift + def shift(&) unless empty? @head.tap { |t| delete(t) } else @@ -69,7 +69,7 @@ struct Crystal::PointerLinkedList(T) end # Iterates the list. - def each : Nil + def each(&) : Nil return if empty? node = @head diff --git a/src/crystal/spin_lock.cr b/src/crystal/spin_lock.cr index c5939f884d06..f0b4b2df4ab6 100644 --- a/src/crystal/spin_lock.cr +++ b/src/crystal/spin_lock.cr @@ -20,7 +20,7 @@ class Crystal::SpinLock {% end %} end - def sync + def sync(&) lock begin yield @@ -29,7 +29,7 @@ class Crystal::SpinLock end end - def unsync + def unsync(&) unlock begin yield diff --git a/src/crystal/system/thread_linked_list.cr b/src/crystal/system/thread_linked_list.cr index 035ecdee52c8..b6f3ccf65d4e 100644 --- a/src/crystal/system/thread_linked_list.cr +++ b/src/crystal/system/thread_linked_list.cr @@ -15,7 +15,7 @@ class Thread # stop-the-world situations, where a paused thread could have acquired the # lock to push/delete a node, while still being "safe" to iterate (but only # during a stop-the-world). - def unsafe_each : Nil + def unsafe_each(&) : Nil node = @head while node diff --git a/src/crystal/system/unix.cr b/src/crystal/system/unix.cr index 0d6407487a8c..136472b79535 100644 --- a/src/crystal/system/unix.cr +++ b/src/crystal/system/unix.cr @@ -1,6 +1,6 @@ # :nodoc: module Crystal::System - def self.retry_with_buffer(function_name, max_buffer) + def self.retry_with_buffer(function_name, max_buffer, &) initial_buf = uninitialized UInt8[1024] buf = initial_buf diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index 4af585abe272..bedf2f456aa5 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -16,7 +16,7 @@ class Thread # :nodoc: property previous : Thread? - def self.unsafe_each + def self.unsafe_each(&) threads.unsafe_each { |thread| yield thread } end @@ -44,7 +44,7 @@ class Thread Thread.threads.push(self) end - private def detach + private def detach(&) if @detached.compare_and_set(0, 1).last yield end diff --git a/src/crystal/system/unix/pthread_mutex.cr b/src/crystal/system/unix/pthread_mutex.cr index 41069c02b561..f7b522d8fcdd 100644 --- a/src/crystal/system/unix/pthread_mutex.cr +++ b/src/crystal/system/unix/pthread_mutex.cr @@ -36,7 +36,7 @@ class Thread raise RuntimeError.from_os_error("pthread_mutex_unlock", Errno.new(ret)) unless ret == 0 end - def synchronize + def synchronize(&) lock yield self ensure diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index b48f383d5226..277d1c11aeb0 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -22,7 +22,7 @@ module Crystal::System::Socket {% end %} end - private def system_connect(addr, timeout = nil) + private def system_connect(addr, timeout = nil, &) timeout = timeout.seconds unless timeout.is_a? ::Time::Span | Nil loop do if LibC.connect(fd, addr, addr.size) == 0 @@ -43,13 +43,13 @@ module Crystal::System::Socket # Tries to bind the socket to a local address. # Yields an `Socket::BindError` if the binding failed. - private def system_bind(addr, addrstr) + private def system_bind(addr, addrstr, &) unless LibC.bind(fd, addr, addr.size) == 0 yield ::Socket::BindError.from_errno("Could not bind to '#{addrstr}'") end end - private def system_listen(backlog) + private def system_listen(backlog, &) unless LibC.listen(fd, backlog) == 0 yield ::Socket::Error.from_errno("Listen failed") end @@ -156,7 +156,7 @@ module Crystal::System::Socket val end - private def system_getsockopt(fd, optname, optval, level = LibC::SOL_SOCKET) + private def system_getsockopt(fd, optname, optval, level = LibC::SOL_SOCKET, &) optsize = LibC::SocklenT.new(sizeof(typeof(optval))) ret = LibC.getsockopt(fd, level, optname, pointerof(optval), pointerof(optsize)) yield optval if ret == 0 diff --git a/src/crystal/system/wasi/thread_mutex.cr b/src/crystal/system/wasi/thread_mutex.cr index 0d90bebb67f8..1ff9de139651 100644 --- a/src/crystal/system/wasi/thread_mutex.cr +++ b/src/crystal/system/wasi/thread_mutex.cr @@ -10,7 +10,7 @@ class Thread def unlock end - def synchronize + def synchronize(&) yield end end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 9c065551f0ce..f9c0d525bc72 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -207,7 +207,7 @@ module Crystal::System::FileDescriptor end @[AlwaysInline] - private def system_console_mode(enable, on_mask, off_mask) + private def system_console_mode(enable, on_mask, off_mask, &) windows_handle = self.windows_handle if LibC.GetConsoleMode(windows_handle, out old_mode) == 0 raise IO::Error.from_winerror("GetConsoleMode") diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 8b2adfea6164..339f28648200 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -92,7 +92,7 @@ module Crystal::System::Socket end end - private def system_connect(addr, timeout = nil) + private def system_connect(addr, timeout = nil, &) if type.stream? system_connect_stream(addr, timeout) { |error| yield error } else @@ -100,7 +100,7 @@ module Crystal::System::Socket end end - private def system_connect_stream(addr, timeout) + private def system_connect_stream(addr, timeout, &) address = LibC::SockaddrIn6.new address.sin6_family = family address.sin6_port = 0 @@ -131,20 +131,20 @@ module Crystal::System::Socket end end - private def system_connect_connectionless(addr, timeout) + private def system_connect_connectionless(addr, timeout, &) ret = LibC.connect(fd, addr, addr.size) if ret == LibC::SOCKET_ERROR yield ::Socket::Error.from_wsa_error("connect") end end - private def system_bind(addr, addrstr) + private def system_bind(addr, addrstr, &) unless LibC.bind(fd, addr, addr.size) == 0 yield ::Socket::BindError.from_errno("Could not bind to '#{addrstr}'") end end - private def system_listen(backlog) + private def system_listen(backlog, &) unless LibC.listen(fd, backlog) == 0 yield ::Socket::Error.from_errno("Listen failed") end @@ -281,7 +281,7 @@ module Crystal::System::Socket val end - def system_getsockopt(handle, optname, optval, level = LibC::SOL_SOCKET) + def system_getsockopt(handle, optname, optval, level = LibC::SOL_SOCKET, &) optsize = sizeof(typeof(optval)) ret = LibC.getsockopt(handle, level, optname, pointerof(optval).as(UInt8*), pointerof(optsize)) diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr index 5e7ae49b30fc..aeb7a6749b5a 100644 --- a/src/crystal/system/win32/thread.cr +++ b/src/crystal/system/win32/thread.cr @@ -16,7 +16,7 @@ class Thread # :nodoc: property previous : Thread? - def self.unsafe_each + def self.unsafe_each(&) threads.unsafe_each { |thread| yield thread } end @@ -48,7 +48,7 @@ class Thread Thread.threads.push(self) end - private def detach + private def detach(&) if @detached.compare_and_set(0, 1).last yield end diff --git a/src/crystal/system/win32/thread_mutex.cr b/src/crystal/system/win32/thread_mutex.cr index afd4cb1fbdcb..559af6acb4f0 100644 --- a/src/crystal/system/win32/thread_mutex.cr +++ b/src/crystal/system/win32/thread_mutex.cr @@ -43,7 +43,7 @@ class Thread LibC.LeaveCriticalSection(self) end - def synchronize + def synchronize(&) lock yield self ensure diff --git a/src/crystal/system/win32/windows_registry.cr b/src/crystal/system/win32/windows_registry.cr index bd043e0d47bd..19097652415a 100644 --- a/src/crystal/system/win32/windows_registry.cr +++ b/src/crystal/system/win32/windows_registry.cr @@ -22,7 +22,7 @@ module Crystal::System::WindowsRegistry end end - def self.open?(handle : LibC::HKEY, name : Slice(UInt16), sam = LibC::REGSAM::READ) + def self.open?(handle : LibC::HKEY, name : Slice(UInt16), sam = LibC::REGSAM::READ, &) key_handle = open?(handle, name, sam) return unless key_handle diff --git a/src/crystal/system/windows.cr b/src/crystal/system/windows.cr index 5fcbf7a96503..b303d4d61f6d 100644 --- a/src/crystal/system/windows.cr +++ b/src/crystal/system/windows.cr @@ -1,6 +1,6 @@ # :nodoc: module Crystal::System - def self.retry_wstr_buffer + def self.retry_wstr_buffer(&) buffer_arr = uninitialized LibC::WCHAR[256] buffer_size = yield buffer_arr.to_slice, true diff --git a/src/crystal/thread_local_value.cr b/src/crystal/thread_local_value.cr index 776ae16c90de..5d0b8ca3f438 100644 --- a/src/crystal/thread_local_value.cr +++ b/src/crystal/thread_local_value.cr @@ -24,7 +24,7 @@ struct Crystal::ThreadLocalValue(T) end end - def consume_each + def consume_each(&) @mutex.sync do @values.each_value { |t| yield t } @values.clear diff --git a/src/csv.cr b/src/csv.cr index 494931d8f10e..92fdd4f4dd87 100644 --- a/src/csv.cr +++ b/src/csv.cr @@ -94,7 +94,7 @@ class CSV # ["one", "two"] # ["three"] # ``` - def self.each_row(string_or_io : String | IO, separator : Char = DEFAULT_SEPARATOR, quote_char : Char = DEFAULT_QUOTE_CHAR) + def self.each_row(string_or_io : String | IO, separator : Char = DEFAULT_SEPARATOR, quote_char : Char = DEFAULT_QUOTE_CHAR, &) Parser.new(string_or_io, separator, quote_char).each_row do |row| yield row end @@ -135,7 +135,7 @@ class CSV # ``` # # See: `CSV::Builder::Quoting` - def self.build(separator : Char = DEFAULT_SEPARATOR, quote_char : Char = DEFAULT_QUOTE_CHAR, quoting : Builder::Quoting = Builder::Quoting::RFC) : String + def self.build(separator : Char = DEFAULT_SEPARATOR, quote_char : Char = DEFAULT_QUOTE_CHAR, quoting : Builder::Quoting = Builder::Quoting::RFC, &) : String String.build do |io| build(io, separator, quote_char, quoting) { |builder| yield builder } end @@ -155,7 +155,7 @@ class CSV # end # io.to_s # => "HEADER\none,two\nthree\n" # ``` - def self.build(io : IO, separator : Char = DEFAULT_SEPARATOR, quote_char : Char = DEFAULT_QUOTE_CHAR, quoting : Builder::Quoting = Builder::Quoting::RFC) : Nil + def self.build(io : IO, separator : Char = DEFAULT_SEPARATOR, quote_char : Char = DEFAULT_QUOTE_CHAR, quoting : Builder::Quoting = Builder::Quoting::RFC, &) : Nil builder = Builder.new(io, separator, quote_char, quoting) yield builder io.flush @@ -194,7 +194,7 @@ class CSV # Headers are always stripped. # # See `CSV.parse` about the *separator* and *quote_char* arguments. - def self.new(string_or_io : String | IO, headers = false, strip = false, separator : Char = DEFAULT_SEPARATOR, quote_char : Char = DEFAULT_QUOTE_CHAR) + def self.new(string_or_io : String | IO, headers = false, strip = false, separator : Char = DEFAULT_SEPARATOR, quote_char : Char = DEFAULT_QUOTE_CHAR, &) csv = new(string_or_io, headers, strip, separator, quote_char) csv.each do yield csv @@ -209,7 +209,7 @@ class CSV end # Invokes the block once for each row in this CSV, yielding `self`. - def each : Nil + def each(&) : Nil while self.next yield self end diff --git a/src/csv/builder.cr b/src/csv/builder.cr index 9a9c1457b263..d0fae5cf6dfe 100644 --- a/src/csv/builder.cr +++ b/src/csv/builder.cr @@ -53,7 +53,7 @@ class CSV::Builder # Yields a `CSV::Row` to append a row. A newline is appended # to `IO` after the block exits. - def row + def row(&) yield Row.new(self, @separator, @quote_char, @quoting) @io << '\n' @first_cell_in_row = true @@ -74,7 +74,7 @@ class CSV::Builder end # :nodoc: - def cell + def cell(&) append_cell do yield @io end @@ -96,7 +96,7 @@ class CSV::Builder end end - private def append_cell + private def append_cell(&) @io << @separator unless @first_cell_in_row yield @first_cell_in_row = false diff --git a/src/csv/parser.cr b/src/csv/parser.cr index ae3ec7c890d8..57491b726dce 100644 --- a/src/csv/parser.cr +++ b/src/csv/parser.cr @@ -20,7 +20,7 @@ class CSV::Parser end # Yields each of the remaining rows as an `Array(String)`. - def each_row : Nil + def each_row(&) : Nil while row = next_row yield row end diff --git a/src/deque.cr b/src/deque.cr index 214e550447a1..938f60d993af 100644 --- a/src/deque.cr +++ b/src/deque.cr @@ -257,7 +257,7 @@ class Deque(T) # `reject!` and `delete` implementation: # returns the last matching element, or nil - private def internal_delete + private def internal_delete(&) match = nil i = 0 while i < @size @@ -439,7 +439,7 @@ class Deque(T) # Removes and returns the last item, if not empty, otherwise executes # the given block and returns its value. - def pop + def pop(&) if @size == 0 yield else @@ -517,7 +517,7 @@ class Deque(T) # Removes and returns the first item, if not empty, otherwise executes # the given block and returns its value. - def shift + def shift(&) if @size == 0 yield else @@ -564,7 +564,7 @@ class Deque(T) self end - private def halfs + private def halfs(&) # For [----] yields nothing # For contiguous [-012] yields 1...4 # For separated [234---01] yields 6...8, 0...3 diff --git a/src/dir/glob.cr b/src/dir/glob.cr index 5d39de127866..38c52253e330 100644 --- a/src/dir/glob.cr +++ b/src/dir/glob.cr @@ -322,7 +322,7 @@ class Dir File.join(path, entry) end - private def self.each_child(path) + private def self.each_child(path, &) Dir.open(path || Dir.current) do |dir| while entry = read_entry(dir) next if entry.name.in?(".", "..") diff --git a/src/enumerable.cr b/src/enumerable.cr index a74e39baa616..5476e2b55433 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -10,7 +10,7 @@ # class Three # include Enumerable(Int32) # -# def each +# def each(&) # yield 1 # yield 2 # yield 3 @@ -201,7 +201,7 @@ module Enumerable(T) end end - private def chunks_internal(original_block : T -> U) forall U + private def chunks_internal(original_block : T -> U, &) forall U acc = Chunk::Accumulator(T, U).new each do |val| key = original_block.call(val) @@ -299,7 +299,7 @@ module Enumerable(T) # Chunks of two items can be iterated using `#each_cons_pair`, an optimized # implementation for the special case of `count == 2` which avoids heap # allocations. - def each_cons(count : Int, reuse = false) + def each_cons(count : Int, reuse = false, &) raise ArgumentError.new "Invalid cons size: #{count}" if count <= 0 if reuse.nil? || reuse.is_a?(Bool) # we use an initial capacity of double the count, because a second @@ -310,7 +310,7 @@ module Enumerable(T) end end - private def each_cons_internal(count : Int, reuse, cons) + private def each_cons_internal(count : Int, reuse, cons, &) each do |elem| cons << elem cons.shift if cons.size > count @@ -385,11 +385,11 @@ module Enumerable(T) # # This can be used to prevent many memory allocations when each slice of # interest is to be used in a read-only fashion. - def each_slice(count : Int, reuse = false) + def each_slice(count : Int, reuse = false, &) each_slice_internal(count, Array(T), reuse) { |slice| yield slice } end - private def each_slice_internal(count : Int, type, reuse) + private def each_slice_internal(count : Int, type, reuse, &) if reuse unless reuse.is_a?(Array) reuse = type.new(count) @@ -446,7 +446,7 @@ module Enumerable(T) # User # 1: Alice # User # 2: Bob # ``` - def each_with_index(offset = 0) + def each_with_index(offset = 0, &) i = offset each do |elem| yield elem, i @@ -730,7 +730,7 @@ module Enumerable(T) # [1, 2, 3, 4, 5].reduce { |acc, i| "#{acc}-#{i}" } # => "1-2-3-4-5" # [1].reduce { |acc, i| "#{acc}-#{i}" } # => 1 # ``` - def reduce + def reduce(&) memo = uninitialized typeof(reduce(Enumerable.element_type(self)) { |acc, i| yield acc, i }) found = false @@ -748,7 +748,7 @@ module Enumerable(T) # [1, 2, 3, 4, 5].reduce(10) { |acc, i| acc + i } # => 25 # [1, 2, 3].reduce([] of Int32) { |memo, i| memo.unshift(i) } # => [3, 2, 1] # ``` - def reduce(memo) + def reduce(memo, &) each do |elem| memo = yield memo, elem end @@ -761,7 +761,7 @@ module Enumerable(T) # ``` # ([] of Int32).reduce? { |acc, i| acc + i } # => nil # ``` - def reduce? + def reduce?(&) memo = uninitialized typeof(reduce(Enumerable.element_type(self)) { |acc, i| yield acc, i }) found = false @@ -933,7 +933,7 @@ module Enumerable(T) # (1), (2), (3), (4), (5) # ``` @[Deprecated(%(Use `#join(io : IO, separator = "", & : T, IO ->) instead`))] - def join(separator, io : IO) + def join(separator, io : IO, &) join(io, separator) do |elem, io| yield elem, io end @@ -1763,7 +1763,7 @@ module Enumerable(T) # words.each { |word| word.chars.tally_by(hash, &.downcase) } # hash # => {'c' => 1, 'r' => 2, 'y' => 2, 's' => 1, 't' => 1, 'a' => 1, 'l' => 1, 'u' => 1, 'b' => 1} # ``` - def tally_by(hash) + def tally_by(hash, &) each_with_object(hash) do |item, hash| value = yield item @@ -1939,7 +1939,7 @@ module Enumerable(T) # 2 -- 5 -- 8 # 3 -- nil -- nil # ``` - def zip?(*others : Indexable | Iterable | Iterator) + def zip?(*others : Indexable | Iterable | Iterator, &) Enumerable.zip?(self, others) do |elems| yield elems end diff --git a/src/exception/call_stack/dwarf.cr b/src/exception/call_stack/dwarf.cr index bf5a91c687e8..e8c9e3b98c18 100644 --- a/src/exception/call_stack/dwarf.cr +++ b/src/exception/call_stack/dwarf.cr @@ -45,7 +45,7 @@ struct Exception::CallStack end end - protected def self.parse_function_names_from_dwarf(info, strings, line_strings) + protected def self.parse_function_names_from_dwarf(info, strings, line_strings, &) info.each do |code, abbrev, attributes| next unless abbrev && abbrev.tag.subprogram? name = low_pc = high_pc = nil diff --git a/src/exception/call_stack/mach_o.cr b/src/exception/call_stack/mach_o.cr index 5646511541a7..27902e32b894 100644 --- a/src/exception/call_stack/mach_o.cr +++ b/src/exception/call_stack/mach_o.cr @@ -61,7 +61,7 @@ struct Exception::CallStack # or within a `foo.dSYM` bundle for a program named `foo`. # # See for details. - private def self.locate_dsym_bundle + private def self.locate_dsym_bundle(&) program = Process.executable_path return unless program diff --git a/src/fiber.cr b/src/fiber.cr index 156a318dc58f..c0bf3ae4292d 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -76,7 +76,7 @@ class Fiber end # :nodoc: - def self.unsafe_each + def self.unsafe_each(&) fibers.unsafe_each { |fiber| yield fiber } end diff --git a/src/file.cr b/src/file.cr index cec57c03efae..7551a65f34f7 100644 --- a/src/file.cr +++ b/src/file.cr @@ -659,7 +659,7 @@ class File < IO::FileDescriptor # file as an argument, the file will be automatically closed when the block returns. # # See `self.new` for what *mode* can be. - def self.open(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil) + def self.open(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, &) file = new filename, mode, perm, encoding, invalid begin yield file @@ -703,7 +703,7 @@ class File < IO::FileDescriptor # end # array # => ["foo", "bar"] # ``` - def self.each_line(filename : Path | String, encoding = nil, invalid = nil, chomp = true) + def self.each_line(filename : Path | String, encoding = nil, invalid = nil, chomp = true, &) open(filename, "r", encoding: encoding, invalid: invalid) do |file| file.each_line(chomp: chomp) do |line| yield line diff --git a/src/file/tempfile.cr b/src/file/tempfile.cr index 3d9fb922b2b9..e1b764f35dbf 100644 --- a/src/file/tempfile.cr +++ b/src/file/tempfile.cr @@ -108,7 +108,7 @@ class File # *encoding* and *invalid* are passed to `IO#set_encoding`. # # It is the caller's responsibility to remove the file when no longer needed. - def self.tempfile(prefix : String?, suffix : String?, *, dir : String = Dir.tempdir, encoding = nil, invalid = nil) + def self.tempfile(prefix : String?, suffix : String?, *, dir : String = Dir.tempdir, encoding = nil, invalid = nil, &) tempfile = tempfile(prefix: prefix, suffix: suffix, dir: dir, encoding: encoding, invalid: invalid) begin yield tempfile @@ -139,7 +139,7 @@ class File # *encoding* and *invalid* are passed to `IO#set_encoding`. # # It is the caller's responsibility to remove the file when no longer needed. - def self.tempfile(suffix : String? = nil, *, dir : String = Dir.tempdir, encoding = nil, invalid = nil) + def self.tempfile(suffix : String? = nil, *, dir : String = Dir.tempdir, encoding = nil, invalid = nil, &) tempfile(prefix: nil, suffix: suffix, dir: dir, encoding: encoding, invalid: invalid) do |tempfile| yield tempfile end diff --git a/src/file_utils.cr b/src/file_utils.cr index d416ed05bff7..21c3041cfc74 100644 --- a/src/file_utils.cr +++ b/src/file_utils.cr @@ -24,7 +24,7 @@ module FileUtils # ``` # # NOTE: Alias of `Dir.cd` with block - def cd(path : Path | String) + def cd(path : Path | String, &) Dir.cd(path) { yield } end diff --git a/src/hash.cr b/src/hash.cr index d29064f57b8c..a8213aa478dd 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -842,7 +842,7 @@ class Hash(K, V) end # Yields each non-deleted Entry with its index inside `@entries`. - protected def each_entry_with_index : Nil + protected def each_entry_with_index(&) : Nil return if @size == 0 @first.upto(entries_size - 1) do |i| @@ -1012,7 +1012,7 @@ class Hash(K, V) # h.put(1, "uno") { "didn't exist" } # => "one" # h.put(2, "two") { |key| key.to_s } # => "2" # ``` - def put(key : K, value : V) + def put(key : K, value : V, &) updated_entry = upsert(key, value) updated_entry ? updated_entry.value : yield key end @@ -1181,7 +1181,7 @@ class Hash(K, V) # h.fetch("bar") { "default value" } # => "default value" # h.fetch("bar") { |key| key.upcase } # => "BAR" # ``` - def fetch(key) + def fetch(key, &) entry = find_entry(key) entry ? entry.value : yield key end @@ -1227,7 +1227,7 @@ class Hash(K, V) # hash.key_for("bar") { |value| value.upcase } # => "foo" # hash.key_for("qux") { |value| value.upcase } # => "QUX" # ``` - def key_for(value) + def key_for(value, &) each do |k, v| return k if v == value end @@ -1253,7 +1253,7 @@ class Hash(K, V) # h.fetch("foo", nil) # => nil # h.delete("baz") { |key| "#{key} not found" } # => "baz not found" # ``` - def delete(key) + def delete(key, &) entry = delete_impl(key) entry ? entry.value : yield key end @@ -1779,7 +1779,7 @@ class Hash(K, V) # hash.shift { true } # => true # hash # => {} # ``` - def shift + def shift(&) first_entry = first_entry? if first_entry delete_entry_and_update_counts(@first) @@ -2025,7 +2025,7 @@ class Hash(K, V) @index = @hash.@first end - def base_next + def base_next(&) while true if @index < @hash.entries_size entry = @hash.entries[@index] diff --git a/src/http/client.cr b/src/http/client.cr index eeb6300f4f5d..c9b9e1d699da 100644 --- a/src/http/client.cr +++ b/src/http/client.cr @@ -229,7 +229,7 @@ class HTTP::Client # # This constructor will raise an exception if any scheme but HTTP or HTTPS # is used. - def self.new(uri : URI, tls : TLSContext = nil) + def self.new(uri : URI, tls : TLSContext = nil, &) tls = tls_flag(uri, tls) host = validate_host(uri) client = new(host, uri.port, tls) @@ -250,7 +250,7 @@ class HTTP::Client # client.get "/" # end # ``` - def self.new(host : String, port = nil, tls : TLSContext = nil) + def self.new(host : String, port = nil, tls : TLSContext = nil, &) client = new(host, port, tls) begin yield client @@ -651,7 +651,7 @@ class HTTP::Client raise IO::EOFError.new("Unexpected end of http response") end - private def exec_internal_single(request, ignore_io_error = false, implicit_compression = false) + private def exec_internal_single(request, ignore_io_error = false, implicit_compression = false, &) begin send_request(request) rescue ex : IO::Error @@ -663,7 +663,7 @@ class HTTP::Client end end - private def handle_response(response) + private def handle_response(response, &) yield ensure response.body_io?.try &.close @@ -730,7 +730,7 @@ class HTTP::Client # response.body_io.gets # => "..." # end # ``` - def exec(method : String, path, headers : HTTP::Headers? = nil, body : BodyType = nil) + def exec(method : String, path, headers : HTTP::Headers? = nil, body : BodyType = nil, &) exec(new_request(method, path, headers, body)) do |response| yield response end @@ -762,7 +762,7 @@ class HTTP::Client # response.body_io.gets # => "..." # end # ``` - def self.exec(method, url : String | URI, headers : HTTP::Headers? = nil, body : BodyType = nil, tls : TLSContext = nil) + def self.exec(method, url : String | URI, headers : HTTP::Headers? = nil, body : BodyType = nil, tls : TLSContext = nil, &) headers = default_one_shot_headers(headers) exec(url, tls) do |client, path| client.exec(method, path, headers, body) do |response| @@ -821,7 +821,7 @@ class HTTP::Client end end - private def self.exec(string : String, tls : TLSContext = nil) + private def self.exec(string : String, tls : TLSContext = nil, &) uri = URI.parse(string) unless uri.scheme && uri.host @@ -866,7 +866,7 @@ class HTTP::Client raise ArgumentError.new "Request URI must have host (URI is: #{uri})" end - private def self.exec(uri : URI, tls : TLSContext = nil) + private def self.exec(uri : URI, tls : TLSContext = nil, &) tls = tls_flag(uri, tls) host = validate_host(uri) @@ -886,7 +886,7 @@ class HTTP::Client # This method is called when executing the request. Although it can be # redefined, it is recommended to use the `def_around_exec` macro to be # able to add new behaviors without losing prior existing ones. - protected def around_exec(request) + protected def around_exec(request, &) yield end diff --git a/src/http/client/response.cr b/src/http/client/response.cr index da1a797a98f2..cfb03c736127 100644 --- a/src/http/client/response.cr +++ b/src/http/client/response.cr @@ -110,7 +110,7 @@ class HTTP::Client end end - def self.from_io(io, ignore_body = false, decompress = true) + def self.from_io(io, ignore_body = false, decompress = true, &) from_io?(io, ignore_body, decompress) do |response| if response yield response diff --git a/src/http/common.cr b/src/http/common.cr index 5cda70bbfe51..b2139194b045 100644 --- a/src/http/common.cr +++ b/src/http/common.cr @@ -26,7 +26,7 @@ module HTTP record HeaderLine, name : String, value : String, bytesize : Int32 # :nodoc: - def self.parse_headers_and_body(io, body_type : BodyType = BodyType::OnDemand, decompress = true, *, max_headers_size : Int32 = MAX_HEADERS_SIZE) : HTTP::Status? + def self.parse_headers_and_body(io, body_type : BodyType = BodyType::OnDemand, decompress = true, *, max_headers_size : Int32 = MAX_HEADERS_SIZE, &) : HTTP::Status? headers = Headers.new max_size = max_headers_size diff --git a/src/http/cookie.cr b/src/http/cookie.cr index f6199cf42484..5b672ca2238a 100644 --- a/src/http/cookie.cr +++ b/src/http/cookie.cr @@ -225,7 +225,7 @@ module HTTP CookieString = /(?:^|; )#{Regex::CookiePair}/ SetCookieString = /^#{Regex::CookiePair}(?:;\s*#{Regex::CookieAV})*$/ - def parse_cookies(header) + def parse_cookies(header, &) header.scan(CookieString).each do |pair| value = pair["value"] if value.starts_with?('"') diff --git a/src/http/formdata.cr b/src/http/formdata.cr index ca26eb01c715..433ee494b3de 100644 --- a/src/http/formdata.cr +++ b/src/http/formdata.cr @@ -90,7 +90,7 @@ module HTTP::FormData # ``` # # See: `FormData::Parser` - def self.parse(io, boundary) + def self.parse(io, boundary, &) parser = Parser.new(io, boundary) while parser.has_next? parser.next { |part| yield part } @@ -113,7 +113,7 @@ module HTTP::FormData # ``` # # See: `FormData::Parser` - def self.parse(request : HTTP::Request) + def self.parse(request : HTTP::Request, &) body = request.body raise Error.new "Cannot extract form-data from HTTP request: body is empty" unless body @@ -186,7 +186,7 @@ module HTTP::FormData # ``` # # See: `FormData::Builder` - def self.build(io, boundary = MIME::Multipart.generate_boundary) + def self.build(io, boundary = MIME::Multipart.generate_boundary, &) builder = Builder.new(io, boundary) yield builder builder.finish @@ -212,7 +212,7 @@ module HTTP::FormData # ``` # # See: `FormData::Builder` - def self.build(response : HTTP::Server::Response, boundary = MIME::Multipart.generate_boundary) + def self.build(response : HTTP::Server::Response, boundary = MIME::Multipart.generate_boundary, &) builder = Builder.new(response, boundary) yield builder builder.finish diff --git a/src/http/formdata/parser.cr b/src/http/formdata/parser.cr index e93f2b029e6c..9aec54f1a64e 100644 --- a/src/http/formdata/parser.cr +++ b/src/http/formdata/parser.cr @@ -27,7 +27,7 @@ module HTTP::FormData # part.headers["Content-Type"] # => "text/plain" # end # ``` - def next + def next(&) raise FormData::Error.new("Parser has already finished parsing") unless has_next? while @multipart.has_next? diff --git a/src/http/headers.cr b/src/http/headers.cr index 97cdcf95ab8b..f3ad07d402c5 100644 --- a/src/http/headers.cr +++ b/src/http/headers.cr @@ -146,7 +146,7 @@ struct HTTP::Headers fetch(wrap(key)) { default } end - def fetch(key) + def fetch(key, &) values = @hash[wrap(key)]? values ? concat(values) : yield key end @@ -227,7 +227,7 @@ struct HTTP::Headers result.hash(hasher) end - def each + def each(&) @hash.each do |key, value| yield({key.name, cast(value)}) end diff --git a/src/http/server/handlers/static_file_handler.cr b/src/http/server/handlers/static_file_handler.cr index 8b4041903aed..d38b462e7a3e 100644 --- a/src/http/server/handlers/static_file_handler.cr +++ b/src/http/server/handlers/static_file_handler.cr @@ -146,7 +146,7 @@ class HTTP::StaticFileHandler end record DirectoryListing, request_path : String, path : String do - def each_entry + def each_entry(&) Dir.each_child(path) do |entry| yield entry end diff --git a/src/http/web_socket.cr b/src/http/web_socket.cr index 6516dee2bbb8..9359c6c194bf 100644 --- a/src/http/web_socket.cr +++ b/src/http/web_socket.cr @@ -98,7 +98,7 @@ class HTTP::WebSocket @ws.pong(message) end - def stream(binary = true, frame_size = 1024) + def stream(binary = true, frame_size = 1024, &) check_open @ws.stream(binary: binary, frame_size: frame_size) do |io| yield io diff --git a/src/http/web_socket/protocol.cr b/src/http/web_socket/protocol.cr index 307ff74be24b..2ae1c9e81ce9 100644 --- a/src/http/web_socket/protocol.cr +++ b/src/http/web_socket/protocol.cr @@ -94,7 +94,7 @@ class HTTP::WebSocket::Protocol send(data, Opcode::BINARY) end - def stream(binary = true, frame_size = 1024) + def stream(binary = true, frame_size = 1024, &) stream_io = StreamIO.new(self, binary, frame_size) yield(stream_io) stream_io.flush diff --git a/src/humanize.cr b/src/humanize.cr index 73974b6448df..76f4fc3b2a12 100644 --- a/src/humanize.cr +++ b/src/humanize.cr @@ -260,7 +260,7 @@ struct Number end # :ditto: - def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true) : String + def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, &) : String String.build do |io| humanize(io, precision, separator, delimiter, base: base, significant: significant) do |magnitude, number| yield magnitude, number diff --git a/src/indexable.cr b/src/indexable.cr index f47c5ab441c2..ce493eec2001 100644 --- a/src/indexable.cr +++ b/src/indexable.cr @@ -34,7 +34,7 @@ module Indexable(T) # a.fetch(2) { :default_value } # => :default_value # a.fetch(2) { |index| index * 3 } # => 6 # ``` - def fetch(index : Int) + def fetch(index : Int, &) index = check_index_out_of_bounds(index) do return yield index end @@ -595,7 +595,7 @@ module Indexable(T) # ```text # 2 -- 3 -- # ``` - def each_index(*, start : Int, count : Int) + def each_index(*, start : Int, count : Int, &) # We cannot use `normalize_start_and_count` here because `self` may be # mutated to contain enough elements during iteration even if there weren't # initially `count` elements. @@ -720,7 +720,7 @@ module Indexable(T) # a.equals?(b) { |x, y| x == y.size } # => true # a.equals?(b) { |x, y| x == y } # => false # ``` - def equals?(other) + def equals?(other, &) return false if size != other.size each_with_index do |item, i| return false unless yield(item, other[i]) @@ -729,7 +729,7 @@ module Indexable(T) end # :inherited: - def first + def first(&) size == 0 ? yield : unsafe_fetch(0) end @@ -809,7 +809,7 @@ module Indexable(T) # ([1, 2, 3]).last { 4 } # => 3 # ([] of Int32).last { 4 } # => 4 # ``` - def last + def last(&) size == 0 ? yield : unsafe_fetch(size - 1) end @@ -928,7 +928,7 @@ module Indexable(T) check_index_out_of_bounds(index) { raise IndexError.new } end - private def check_index_out_of_bounds(index) + private def check_index_out_of_bounds(index, &) index += size if index < 0 if 0 <= index < size index @@ -941,12 +941,12 @@ module Indexable(T) Indexable.normalize_start_and_count(start, count, size) end - private def normalize_start_and_count(start, count) + private def normalize_start_and_count(start, count, &) Indexable.normalize_start_and_count(start, count, size) { yield } end # :nodoc: - def self.normalize_start_and_count(start, count, collection_size) + def self.normalize_start_and_count(start, count, collection_size, &) raise ArgumentError.new "Negative count: #{count}" if count < 0 start += collection_size if start < 0 if 0 <= start <= collection_size @@ -1021,7 +1021,7 @@ module Indexable(T) # the method will create a new array and reuse it. This can be # used to prevent many memory allocations when each slice of # interest is to be used in a read-only fashion. - def each_permutation(size : Int = self.size, reuse = false) : Nil + def each_permutation(size : Int = self.size, reuse = false, &) : Nil n = self.size return if size > n @@ -1114,7 +1114,7 @@ module Indexable(T) # the method will create a new array and reuse it. This can be # used to prevent many memory allocations when each slice of # interest is to be used in a read-only fashion. - def each_combination(size : Int = self.size, reuse = false) : Nil + def each_combination(size : Int = self.size, reuse = false, &) : Nil n = self.size return if size > n raise ArgumentError.new("Size must be positive") if size < 0 @@ -1224,7 +1224,7 @@ module Indexable(T) # the method will create a new array and reuse it. This can be # used to prevent many memory allocations when each slice of # interest is to be used in a read-only fashion. - def each_repeated_combination(size : Int = self.size, reuse = false) : Nil + def each_repeated_combination(size : Int = self.size, reuse = false, &) : Nil n = self.size return if size > n && n == 0 raise ArgumentError.new("Size must be positive") if size < 0 diff --git a/src/int.cr b/src/int.cr index 7de873b9c008..8ede6465cd03 100644 --- a/src/int.cr +++ b/src/int.cr @@ -728,7 +728,7 @@ struct Int end end - private def internal_to_s(base, precision, upcase = false) + private def internal_to_s(base, precision, upcase = false, &) # Given sizeof(self) <= 128 bits, we need at most 128 bytes for a base 2 # representation, plus one byte for the negative sign (possibly used by the # string-returning overload). diff --git a/src/io.cr b/src/io.cr index 54162972af1f..7197f0b8316c 100644 --- a/src/io.cr +++ b/src/io.cr @@ -152,7 +152,7 @@ abstract class IO # reader.gets # => "world" # end # ``` - def self.pipe(read_blocking = false, write_blocking = false) + def self.pipe(read_blocking = false, write_blocking = false, &) r, w = IO.pipe(read_blocking, write_blocking) begin yield r, w @@ -976,7 +976,7 @@ abstract class IO # あ # め # ``` - def each_char : Nil + def each_char(&) : Nil while char = read_char yield char end @@ -1011,7 +1011,7 @@ abstract class IO # 129 # 130 # ``` - def each_byte : Nil + def each_byte(&) : Nil while byte = read_byte yield byte end diff --git a/src/io/evented.cr b/src/io/evented.cr index c9ba8fa273e7..c490f3b5939f 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -47,7 +47,7 @@ module IO::Evented write_timeout end - def evented_read(slice : Bytes, errno_msg : String) : Int32 + def evented_read(slice : Bytes, errno_msg : String, &) : Int32 loop do bytes_read = yield slice if bytes_read != -1 @@ -65,7 +65,7 @@ module IO::Evented resume_pending_readers end - def evented_write(slice : Bytes, errno_msg : String) : Nil + def evented_write(slice : Bytes, errno_msg : String, &) : Nil return if slice.empty? begin @@ -88,7 +88,7 @@ module IO::Evented end end - def evented_send(slice : Bytes, errno_msg : String) : Int32 + def evented_send(slice : Bytes, errno_msg : String, &) : Int32 bytes_written = yield slice raise Socket::Error.from_errno(errno_msg) if bytes_written == -1 # `to_i32` is acceptable because `Slice#size` is an Int32 @@ -121,7 +121,7 @@ module IO::Evented end # :nodoc: - def wait_readable(timeout = @read_timeout, *, raise_if_closed = true) : Nil + def wait_readable(timeout = @read_timeout, *, raise_if_closed = true, &) : Nil readers = @readers.get { Deque(Fiber).new } readers << Fiber.current add_read_event(timeout) @@ -146,7 +146,7 @@ module IO::Evented end # :nodoc: - def wait_writable(timeout = @write_timeout) : Nil + def wait_writable(timeout = @write_timeout, &) : Nil writers = @writers.get { Deque(Fiber).new } writers << Fiber.current add_write_event(timeout) diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index ecbcc6cebf88..2f6cf02ec4a0 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -118,7 +118,7 @@ class IO::FileDescriptor < IO # Same as `seek` but yields to the block after seeking and eventually seeks # back to the original position when the block returns. - def seek(offset, whence : Seek = Seek::Set) + def seek(offset, whence : Seek = Seek::Set, &) original_pos = tell begin seek(offset, whence) @@ -176,7 +176,7 @@ class IO::FileDescriptor < IO # TODO: use fcntl/lockf instead of flock (which doesn't lock over NFS) - def flock_shared(blocking = true) + def flock_shared(blocking = true, &) flock_shared blocking begin yield @@ -191,7 +191,7 @@ class IO::FileDescriptor < IO system_flock_shared(blocking) end - def flock_exclusive(blocking = true) + def flock_exclusive(blocking = true, &) flock_exclusive blocking begin yield diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index 75570feae1a8..011b25b06398 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -38,19 +38,19 @@ module IO::Overlapped write_timeout end - def overlapped_write(socket, method) + def overlapped_write(socket, method, &) overlapped_operation(socket, method, write_timeout) do |operation| yield operation end end - def overlapped_read(socket, method) + def overlapped_read(socket, method, &) overlapped_operation(socket, method, read_timeout) do |operation| yield operation end end - def self.wait_queued_completions(timeout) + def self.wait_queued_completions(timeout, &) overlapped_entries = uninitialized LibC::OVERLAPPED_ENTRY[1] if timeout > UInt64::MAX @@ -94,7 +94,7 @@ module IO::Overlapped property previous : OverlappedOperation? @@canceled = Thread::LinkedList(OverlappedOperation).new - def self.run(socket) + def self.run(socket, &) operation = OverlappedOperation.new begin yield operation @@ -103,7 +103,7 @@ module IO::Overlapped end end - def self.schedule(overlapped : LibC::WSAOVERLAPPED*) + def self.schedule(overlapped : LibC::WSAOVERLAPPED*, &) start = overlapped.as(Pointer(UInt8)) - offsetof(OverlappedOperation, @overlapped) operation = Box(OverlappedOperation).unbox(start.as(Pointer(Void))) operation.schedule { |fiber| yield fiber } @@ -116,7 +116,7 @@ module IO::Overlapped pointerof(@overlapped) end - def result(socket) + def result(socket, &) raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started? flags = 0_u32 result = LibC.WSAGetOverlappedResult(socket, pointerof(@overlapped), out bytes, false, pointerof(flags)) @@ -130,7 +130,7 @@ module IO::Overlapped bytes end - protected def schedule + protected def schedule(&) case @state when .started? yield @fiber.not_nil! @@ -170,7 +170,7 @@ module IO::Overlapped Crystal::Scheduler.event_loop.dequeue(timeout_event) end - def overlapped_operation(socket, method, timeout, connreset_is_error = true) + def overlapped_operation(socket, method, timeout, connreset_is_error = true, &) OverlappedOperation.run(socket) do |operation| result = yield operation.start @@ -195,7 +195,7 @@ module IO::Overlapped end end - def overlapped_connect(socket, method) + def overlapped_connect(socket, method, &) OverlappedOperation.run(socket) do |operation| yield operation.start @@ -215,7 +215,7 @@ module IO::Overlapped end end - def overlapped_accept(socket, method) + def overlapped_accept(socket, method, &) OverlappedOperation.run(socket) do |operation| yield operation.start diff --git a/src/io/stapled.cr b/src/io/stapled.cr index 774873476fb6..2dfcd4c6ed08 100644 --- a/src/io/stapled.cr +++ b/src/io/stapled.cr @@ -107,7 +107,7 @@ class IO::Stapled < IO # # Both endpoints and the underlying `IO`s are closed after the block # (even if `sync_close?` is `false`). - def self.pipe(read_blocking : Bool = false, write_blocking : Bool = false) + def self.pipe(read_blocking : Bool = false, write_blocking : Bool = false, &) IO.pipe(read_blocking, write_blocking) do |a_read, a_write| IO.pipe(read_blocking, write_blocking) do |b_read, b_write| a, b = new(a_read, b_write, true), new(b_read, a_write, true) diff --git a/src/iterator.cr b/src/iterator.cr index 3cc614a84194..6bcc926b50d0 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -1314,7 +1314,7 @@ module Iterator(T) end # Yields each element in this iterator together with its index. - def with_index(offset : Int = 0) + def with_index(offset : Int = 0, &) index = offset each do |value| yield value, index diff --git a/src/json/builder.cr b/src/json/builder.cr index 9f7a97c50673..893970e31f1c 100644 --- a/src/json/builder.cr +++ b/src/json/builder.cr @@ -56,7 +56,7 @@ class JSON::Builder flush end - def document + def document(&) start_document yield.tap { end_document } end @@ -187,7 +187,7 @@ class JSON::Builder # Writes the start of an array, invokes the block, # and the writes the end of it. - def array + def array(&) start_array yield.tap { end_array } end @@ -219,7 +219,7 @@ class JSON::Builder # Writes the start of an object, invokes the block, # and the writes the end of it. - def object + def object(&) start_object yield.tap { end_object } end @@ -255,7 +255,7 @@ class JSON::Builder # Writes an object's field and then invokes the block. # This is equivalent of invoking `string(value)` and then # invoking the block. - def field(name) + def field(name, &) string(name) yield end @@ -290,7 +290,7 @@ class JSON::Builder state.is_a?(ObjectState) && state.name end - private def scalar(string = false) + private def scalar(string = false, &) start_scalar(string) yield.tap { end_scalar(string) } end @@ -400,7 +400,7 @@ module JSON # end # string # => %<{"name":"foo","values":[1,2,3]}> # ``` - def self.build(indent = nil) + def self.build(indent = nil, &) String.build do |str| build(str, indent) do |json| yield json @@ -409,7 +409,7 @@ module JSON end # Writes JSON into the given `IO`. A `JSON::Builder` is yielded to the block. - def self.build(io : IO, indent = nil) : Nil + def self.build(io : IO, indent = nil, &) : Nil builder = JSON::Builder.new(io) builder.indent = indent if indent builder.document do diff --git a/src/json/from_json.cr b/src/json/from_json.cr index 13d8e066a444..08e870b5122a 100644 --- a/src/json/from_json.cr +++ b/src/json/from_json.cr @@ -50,7 +50,7 @@ end # ``` # # To parse and get an `Array`, use the block-less overload. -def Array.from_json(string_or_io) : Nil +def Array.from_json(string_or_io, &) : Nil parser = JSON::PullParser.new(string_or_io) new(parser) do |element| yield element @@ -58,7 +58,7 @@ def Array.from_json(string_or_io) : Nil nil end -def Deque.from_json(string_or_io) : Nil +def Deque.from_json(string_or_io, &) : Nil parser = JSON::PullParser.new(string_or_io) new(parser) do |element| yield element @@ -204,7 +204,7 @@ def Array.new(pull : JSON::PullParser) ary end -def Array.new(pull : JSON::PullParser) +def Array.new(pull : JSON::PullParser, &) pull.read_array do yield T.new(pull) end @@ -218,7 +218,7 @@ def Deque.new(pull : JSON::PullParser) ary end -def Deque.new(pull : JSON::PullParser) +def Deque.new(pull : JSON::PullParser, &) pull.read_array do yield T.new(pull) end diff --git a/src/json/lexer.cr b/src/json/lexer.cr index 6769a0f91657..3e61179b9844 100644 --- a/src/json/lexer.cr +++ b/src/json/lexer.cr @@ -146,7 +146,7 @@ abstract class JSON::Lexer consume_string_with_buffer { } end - private def consume_string_with_buffer + private def consume_string_with_buffer(&) @buffer.clear yield while true diff --git a/src/json/parser.cr b/src/json/parser.cr index 37a9ded44cae..865dcd124459 100644 --- a/src/json/parser.cr +++ b/src/json/parser.cr @@ -125,7 +125,7 @@ class JSON::Parser raise ParseException.new(msg, token.line_number, token.column_number) end - private def nest + private def nest(&) @nest += 1 if @nest > @max_nesting parse_exception "Nesting of #{@nest} is too deep" diff --git a/src/json/pull_parser.cr b/src/json/pull_parser.cr index 482e17c01108..4716c7f8f79e 100644 --- a/src/json/pull_parser.cr +++ b/src/json/pull_parser.cr @@ -154,7 +154,7 @@ class JSON::PullParser # You have to consumes the values, if any, so the pull parser does not fail when reading the end of the array. # # If the array is empty, it does not yield. - def read_array + def read_array(&) read_begin_array until kind.end_array? yield @@ -185,7 +185,7 @@ class JSON::PullParser # You have to consumes the values, if any, so the pull parser does not fail when reading the end of the object. # # If the object is empty, it does not yield. - def read_object + def read_object(&) read_begin_object until kind.end_object? key_location = location @@ -332,17 +332,17 @@ class JSON::PullParser end # Reads an array or a null value, and returns it. - def read_array_or_null + def read_array_or_null(&) read_null_or { read_array { yield } } end # Reads an object or a null value, and returns it. - def read_object_or_null + def read_object_or_null(&) read_null_or { read_object { |key| yield key } } end # Reads a null value and returns it, or executes the given block if the value is not null. - def read_null_or + def read_null_or(&) if @kind.null? read_next nil diff --git a/src/kernel.cr b/src/kernel.cr index 154ef5df3d84..fe7595ddcc96 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -98,7 +98,7 @@ ARGF = IO::ARGF.new(ARGV, STDIN) # # ... # end # ``` -def loop +def loop(&) while true yield end diff --git a/src/levenshtein.cr b/src/levenshtein.cr index 2a7eb805b85d..571e0ab830e6 100644 --- a/src/levenshtein.cr +++ b/src/levenshtein.cr @@ -108,7 +108,7 @@ module Levenshtein @best_entry.try &.value end - def self.find(name, tolerance = nil) + def self.find(name, tolerance = nil, &) sn = new name, tolerance yield sn sn.best_match @@ -137,7 +137,7 @@ module Levenshtein # end # best_match # => "ello" # ``` - def self.find(name, tolerance = nil) + def self.find(name, tolerance = nil, &) Finder.find(name, tolerance) do |sn| yield sn end diff --git a/src/llvm/abi/x86_64.cr b/src/llvm/abi/x86_64.cr index 418c8f672eea..e1e996f178e7 100644 --- a/src/llvm/abi/x86_64.cr +++ b/src/llvm/abi/x86_64.cr @@ -44,7 +44,7 @@ class LLVM::ABI::X86_64 < LLVM::ABI # returns the LLVM type (with attributes) and the number of integer and SSE # registers needed to pass this value directly (ie. not using the stack) - def x86_64_type(type, ind_attr, context) : Tuple(ArgType, Int32, Int32) + def x86_64_type(type, ind_attr, context, &) : Tuple(ArgType, Int32, Int32) if int_register?(type) attr = type == context.int1 ? Attribute::ZExt : nil {ArgType.direct(type, attr: attr), 1, 0} diff --git a/src/llvm/basic_block_collection.cr b/src/llvm/basic_block_collection.cr index ba5b961f3a8b..a6218a015d83 100644 --- a/src/llvm/basic_block_collection.cr +++ b/src/llvm/basic_block_collection.cr @@ -11,7 +11,7 @@ struct LLVM::BasicBlockCollection BasicBlock.new LibLLVM.append_basic_block_in_context(context, @function, name) end - def append(name = "") + def append(name = "", &) context = LibLLVM.get_module_context(LibLLVM.get_global_parent(@function)) block = append name # builder = Builder.new(LibLLVM.create_builder_in_context(context), LLVM::Context.new(context, dispose_on_finalize: false)) @@ -21,7 +21,7 @@ struct LLVM::BasicBlockCollection block end - def each : Nil + def each(&) : Nil bb = LibLLVM.get_first_basic_block(@function) while bb yield LLVM::BasicBlock.new bb diff --git a/src/llvm/context.cr b/src/llvm/context.cr index 4e8a986c1155..d349e3045bf0 100644 --- a/src/llvm/context.cr +++ b/src/llvm/context.cr @@ -68,7 +68,7 @@ class LLVM::Context int8.pointer end - def struct(name : String, packed = false) : Type + def struct(name : String, packed = false, &) : Type llvm_struct = LibLLVM.struct_create_named(self, name) the_struct = Type.new llvm_struct element_types = (yield the_struct).as(Array(LLVM::Type)) diff --git a/src/llvm/function_collection.cr b/src/llvm/function_collection.cr index d4e6663fb2d4..a7fcc7cbfcd7 100644 --- a/src/llvm/function_collection.cr +++ b/src/llvm/function_collection.cr @@ -10,7 +10,7 @@ struct LLVM::FunctionCollection Function.new(func) end - def add(name, arg_types : Array(LLVM::Type), ret_type, varargs = false) + def add(name, arg_types : Array(LLVM::Type), ret_type, varargs = false, &) func = add(name, arg_types, ret_type, varargs) yield func func @@ -26,7 +26,7 @@ struct LLVM::FunctionCollection func ? Function.new(func) : nil end - def each : Nil + def each(&) : Nil f = LibLLVM.get_first_function(@mod) while f yield LLVM::Function.new f diff --git a/src/llvm/function_pass_manager.cr b/src/llvm/function_pass_manager.cr index 834d72a20966..50095838626e 100644 --- a/src/llvm/function_pass_manager.cr +++ b/src/llvm/function_pass_manager.cr @@ -12,7 +12,7 @@ class LLVM::FunctionPassManager changed end - def run + def run(&) LibLLVM.initialize_function_pass_manager(self) runner = Runner.new(self) diff --git a/src/llvm/instruction_collection.cr b/src/llvm/instruction_collection.cr index 81f0993bddc7..a442c4b74753 100644 --- a/src/llvm/instruction_collection.cr +++ b/src/llvm/instruction_collection.cr @@ -8,7 +8,7 @@ struct LLVM::InstructionCollection first?.nil? end - def each : Nil + def each(&) : Nil inst = LibLLVM.get_first_instruction @basic_block while inst diff --git a/src/llvm/jit_compiler.cr b/src/llvm/jit_compiler.cr index b881e06ee8d7..ee0c92803102 100644 --- a/src/llvm/jit_compiler.cr +++ b/src/llvm/jit_compiler.cr @@ -12,7 +12,7 @@ class LLVM::JITCompiler @finalized = false end - def self.new(mod) + def self.new(mod, &) jit = new(mod) yield jit ensure jit.dispose end diff --git a/src/llvm/module.cr b/src/llvm/module.cr index f39e672d81fa..162422b752e9 100644 --- a/src/llvm/module.cr +++ b/src/llvm/module.cr @@ -128,7 +128,7 @@ class LLVM::Module @unwrap end - def take_ownership + def take_ownership(&) if @owned yield else diff --git a/src/llvm/pass_builder_options.cr b/src/llvm/pass_builder_options.cr index 955fd4287527..3134acb0b93a 100644 --- a/src/llvm/pass_builder_options.cr +++ b/src/llvm/pass_builder_options.cr @@ -6,7 +6,7 @@ class LLVM::PassBuilderOptions @disposed = false end - def self.new + def self.new(&) options = new begin yield options diff --git a/src/llvm/target.cr b/src/llvm/target.cr index 06ba42c6950a..53bae819296b 100644 --- a/src/llvm/target.cr +++ b/src/llvm/target.cr @@ -1,5 +1,5 @@ struct LLVM::Target - def self.each + def self.each(&) target = LibLLVM.get_first_target while target yield Target.new target diff --git a/src/log/builder.cr b/src/log/builder.cr index 4201a436f656..7be6f10285d3 100644 --- a/src/log/builder.cr +++ b/src/log/builder.cr @@ -84,7 +84,7 @@ class Log::Builder end # :nodoc: - private def each_log + private def each_log(&) @logs.reject! { |_, log_ref| log_ref.value.nil? } @logs.each_value do |log_ref| diff --git a/src/log/main.cr b/src/log/main.cr index b2adecaee1bb..3ff86e169ba4 100644 --- a/src/log/main.cr +++ b/src/log/main.cr @@ -93,7 +93,7 @@ class Log # end # Log.info { %(message with {"a" => 1} context) } # ``` - def self.with_context(**kwargs) + def self.with_context(**kwargs, &) previous = Log.context Log.context.set(**kwargs) unless kwargs.empty? begin @@ -104,7 +104,7 @@ class Log end # :ditto: - def self.with_context(values) + def self.with_context(values, &) previous = Log.context Log.context.set(values) unless values.empty? begin @@ -115,14 +115,14 @@ class Log end # :ditto: - def with_context(**kwargs) + def with_context(**kwargs, &) self.class.with_context(**kwargs) do yield end end # :ditto: - def with_context(values) + def with_context(values, &) self.class.with_context(values) do yield end diff --git a/src/log/metadata.cr b/src/log/metadata.cr index 9ddada2198dd..258b1b817c52 100644 --- a/src/log/metadata.cr +++ b/src/log/metadata.cr @@ -136,7 +136,7 @@ class Log::Metadata fetch(key) { nil } end - def fetch(key) + def fetch(key, &) entry = find_entry(key) entry ? entry[:value] : yield key end diff --git a/src/log/setup.cr b/src/log/setup.cr index 7371f8876b40..ecd2aa154cce 100644 --- a/src/log/setup.cr +++ b/src/log/setup.cr @@ -1,6 +1,6 @@ class Log # Setups logging bindings discarding all previous configurations. - def self.setup(*, builder : Log::Builder = Log.builder) + def self.setup(*, builder : Log::Builder = Log.builder, &) builder.clear yield builder.as(Configuration) end diff --git a/src/log/spec.cr b/src/log/spec.cr index e776705dc359..f2fdfafb2aab 100644 --- a/src/log/spec.cr +++ b/src/log/spec.cr @@ -52,7 +52,7 @@ class Log # # Invocations can be nested in order to capture each source in their own `EntriesChecker`. # - def self.capture(source : String = "*", level : Severity = Log::Severity::Trace, *, builder = Log.builder) + def self.capture(source : String = "*", level : Severity = Log::Severity::Trace, *, builder = Log.builder, &) mem_backend = Log::MemoryBackend.new builder.bind(source, level, mem_backend) begin @@ -66,7 +66,7 @@ class Log # :ditto: def self.capture(level : Log::Severity = Log::Severity::Trace, - *, builder : Log::Builder = Log.builder) + *, builder : Log::Builder = Log.builder, &) capture("*", level, builder: builder) do |dsl| yield dsl end diff --git a/src/mime/media_type.cr b/src/mime/media_type.cr index 4255e50a9b74..e563f2aa27da 100644 --- a/src/mime/media_type.cr +++ b/src/mime/media_type.cr @@ -354,7 +354,7 @@ module MIME MediaType.new mediatype, params end - private def self.parse_parameter_value(reader) + private def self.parse_parameter_value(reader, &) reader = consume_whitespace(reader) # Quoted value. diff --git a/src/mime/multipart.cr b/src/mime/multipart.cr index 179a7b92790e..89175b763ca9 100644 --- a/src/mime/multipart.cr +++ b/src/mime/multipart.cr @@ -24,7 +24,7 @@ module MIME::Multipart # ``` # # See: `Multipart::Parser` - def self.parse(io, boundary) + def self.parse(io, boundary, &) parser = Parser.new(io, boundary) while parser.has_next? parser.next { |headers, io| yield headers, io } @@ -68,7 +68,7 @@ module MIME::Multipart # ``` # # See: `Multipart::Parser` - def self.parse(request : HTTP::Request) + def self.parse(request : HTTP::Request, &) boundary = parse_boundary(request.headers["Content-Type"]) return nil unless boundary @@ -79,7 +79,7 @@ module MIME::Multipart # Yields a `Multipart::Builder` to the given block, writing to *io* and # using *boundary*. `#finish` is automatically called on the builder. - def self.build(io : IO, boundary : String = Multipart.generate_boundary) + def self.build(io : IO, boundary : String = Multipart.generate_boundary, &) builder = Builder.new(io, boundary) yield builder builder.finish @@ -87,7 +87,7 @@ module MIME::Multipart # Yields a `Multipart::Builder` to the given block, returning the generated # message as a `String`. - def self.build(boundary : String = Multipart.generate_boundary) + def self.build(boundary : String = Multipart.generate_boundary, &) String.build do |io| build(io, boundary) { |g| yield g } end diff --git a/src/mime/multipart/builder.cr b/src/mime/multipart/builder.cr index 3f3f4d3aeeae..59d38d766da1 100644 --- a/src/mime/multipart/builder.cr +++ b/src/mime/multipart/builder.cr @@ -65,7 +65,7 @@ module MIME::Multipart # message. Throws if `#body_part` is called before this method. # # Can be called multiple times to append to the preamble multiple times. - def preamble + def preamble(&) fail "Cannot generate preamble: body already started" unless @state.start? || @state.preamble? yield @io @state = State::PREAMBLE @@ -95,7 +95,7 @@ module MIME::Multipart # Yields an IO that can be used to write to a body part which is appended # to the multipart message with the given *headers*. Throws if `#finish` or # `#epilogue` is called before this method. - def body_part(headers : HTTP::Headers) + def body_part(headers : HTTP::Headers, &) body_part_impl(headers) { |io| yield io } end @@ -106,7 +106,7 @@ module MIME::Multipart body_part_impl(headers, empty: true) { } end - private def body_part_impl(headers, empty = false) + private def body_part_impl(headers, empty = false, &) fail "Cannot generate body part: already finished" if @state.finished? fail "Cannot generate body part: after epilogue" if @state.epilogue? @@ -158,7 +158,7 @@ module MIME::Multipart # parts have been appended. # # Can be called multiple times to append to the preamble multiple times. - def epilogue + def epilogue(&) case @state in .start?, .preamble? fail "Cannot generate epilogue: no body parts" diff --git a/src/mime/multipart/parser.cr b/src/mime/multipart/parser.cr index ec3123b4c602..262f500119f0 100644 --- a/src/mime/multipart/parser.cr +++ b/src/mime/multipart/parser.cr @@ -47,7 +47,7 @@ module MIME::Multipart # io.gets_to_end # => "body" # end # ``` - def next + def next(&) raise Multipart::Error.new "Multipart parser already finished parsing" if @state.finished? raise Multipart::Error.new "Multipart parser is in an errored state" if @state.errored? diff --git a/src/mutex.cr b/src/mutex.cr index f659811d2928..6bdd9a98fd25 100644 --- a/src/mutex.cr +++ b/src/mutex.cr @@ -128,7 +128,7 @@ class Mutex nil end - def synchronize + def synchronize(&) lock begin yield diff --git a/src/named_tuple.cr b/src/named_tuple.cr index 6497504b04ed..4708d93d3bef 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -499,7 +499,7 @@ struct NamedTuple # name = Crystal # year = 2011 # ``` - def each : Nil + def each(&) : Nil {% for key in T %} yield {{key.symbolize}}, self[{{key.symbolize}}] {% end %} @@ -520,7 +520,7 @@ struct NamedTuple # name # year # ``` - def each_key : Nil + def each_key(&) : Nil {% for key in T %} yield {{key.symbolize}} {% end %} @@ -541,7 +541,7 @@ struct NamedTuple # Crystal # 2011 # ``` - def each_value : Nil + def each_value(&) : Nil {% for key in T %} yield self[{{key.symbolize}}] {% end %} @@ -562,7 +562,7 @@ struct NamedTuple # 1) name = Crystal # 2) year = 2011 # ``` - def each_with_index(offset = 0) + def each_with_index(offset = 0, &) i = offset each do |key, value| yield key, value, i @@ -577,7 +577,7 @@ struct NamedTuple # tuple = {name: "Crystal", year: 2011} # tuple.map { |k, v| "#{k}: #{v}" } # => ["name: Crystal", "year: 2011"] # ``` - def map + def map(&) {% if T.size == 0 %} [] of NoReturn {% else %} diff --git a/src/oauth/consumer.cr b/src/oauth/consumer.cr index 58340b981c97..14ba9407bd41 100644 --- a/src/oauth/consumer.cr +++ b/src/oauth/consumer.cr @@ -130,7 +130,7 @@ class OAuth::Consumer authenticate client, token.token, token.secret, nil end - private def post(oauth_token, token_shared_secret, extra_params, target_uri) + private def post(oauth_token, token_shared_secret, extra_params, target_uri, &) uri = URI.parse(target_uri) # If the target uri is absolute, we use that instead of the default values @@ -155,7 +155,7 @@ class OAuth::Consumer OAuth.authenticate(client, token, token_secret, @consumer_key, @consumer_secret, extra_params) end - private def handle_response(response) + private def handle_response(response, &) case response.status_code when 200, 201 yield diff --git a/src/oauth2/client.cr b/src/oauth2/client.cr index e2709ec04053..9718c7b9e3ed 100644 --- a/src/oauth2/client.cr +++ b/src/oauth2/client.cr @@ -159,7 +159,7 @@ class OAuth2::Client end end - private def get_access_token : AccessToken + private def get_access_token(&) : AccessToken headers = HTTP::Headers{ "Accept" => "application/json", "Content-Type" => "application/x-www-form-urlencoded", diff --git a/src/object.cr b/src/object.cr index 608dcf618bda..e0933d5a7bb1 100644 --- a/src/object.cr +++ b/src/object.cr @@ -175,7 +175,7 @@ class Object # .select { |x| x % 2 == 0 }.tap { |x| puts "evens: #{x.inspect}" } # .map { |x| x*x }.tap { |x| puts "squares: #{x.inspect}" } # ``` - def tap + def tap(&) yield self self end @@ -189,7 +189,7 @@ class Object # # First program argument in downcase, or nil # ARGV[0]?.try &.downcase # ``` - def try + def try(&) yield self end diff --git a/src/openssl/ssl/server.cr b/src/openssl/ssl/server.cr index f5e99ead39d6..97132f8c4810 100644 --- a/src/openssl/ssl/server.cr +++ b/src/openssl/ssl/server.cr @@ -49,7 +49,7 @@ class OpenSSL::SSL::Server # *context* configures the SSL options, see `OpenSSL::SSL::Context::Server` for details # # The server is closed after the block returns. - def self.open(wrapped : ::Socket::Server, context : OpenSSL::SSL::Context::Server = OpenSSL::SSL::Context::Server.new, sync_close : Bool = true) + def self.open(wrapped : ::Socket::Server, context : OpenSSL::SSL::Context::Server = OpenSSL::SSL::Context::Server.new, sync_close : Bool = true, &) server = new(wrapped, context, sync_close) begin diff --git a/src/openssl/ssl/socket.cr b/src/openssl/ssl/socket.cr index 742ad8f78eea..461185e342d2 100644 --- a/src/openssl/ssl/socket.cr +++ b/src/openssl/ssl/socket.cr @@ -39,7 +39,7 @@ abstract class OpenSSL::SSL::Socket < IO end end - def self.open(io, context : Context::Client = Context::Client.new, sync_close : Bool = false, hostname : String? = nil) + def self.open(io, context : Context::Client = Context::Client.new, sync_close : Bool = false, hostname : String? = nil, &) socket = new(io, context, sync_close, hostname) begin @@ -78,7 +78,7 @@ abstract class OpenSSL::SSL::Socket < IO end end - def self.open(io, context : Context::Server = Context::Server.new, sync_close : Bool = false) + def self.open(io, context : Context::Server = Context::Server.new, sync_close : Bool = false, &) socket = new(io, context, sync_close) begin diff --git a/src/option_parser.cr b/src/option_parser.cr index f82a8a42d607..175926a8c730 100644 --- a/src/option_parser.cr +++ b/src/option_parser.cr @@ -110,7 +110,7 @@ class OptionParser # and uses it to parse the passed *args* (defaults to `ARGV`). # # Refer to `#gnu_optional_args?` for the behaviour of the named parameter. - def self.parse(args = ARGV, *, gnu_optional_args : Bool = false) : self + def self.parse(args = ARGV, *, gnu_optional_args : Bool = false, &) : self parser = OptionParser.new(gnu_optional_args: gnu_optional_args) yield parser parser.parse(args) @@ -131,7 +131,7 @@ class OptionParser # Creates a new parser, with its configuration specified in the block. # # Refer to `#gnu_optional_args?` for the behaviour of the named parameter. - def self.new(*, gnu_optional_args : Bool = false) + def self.new(*, gnu_optional_args : Bool = false, &) new(gnu_optional_args: gnu_optional_args).tap { |parser| yield parser } end @@ -332,7 +332,7 @@ class OptionParser end end - private def with_preserved_state + private def with_preserved_state(&) old_flags = @flags.clone old_handlers = @handlers.clone old_banner = @banner diff --git a/src/path.cr b/src/path.cr index e7397079f3f7..2af60279ded5 100644 --- a/src/path.cr +++ b/src/path.cr @@ -528,7 +528,7 @@ struct Path PartIterator.new(self) end - private def each_part_separator_index + private def each_part_separator_index(&) reader = Char::Reader.new(@name) start_pos = reader.pos diff --git a/src/pretty_print.cr b/src/pretty_print.cr index 038da9d3eb4a..acc52f91dad6 100644 --- a/src/pretty_print.cr +++ b/src/pretty_print.cr @@ -109,7 +109,7 @@ class PrettyPrint # Creates a group of objects. Inside a group all breakable # objects are either turned into newlines or are output # as is, depending on the available width. - def group(indent = 0, open_obj = "", close_obj = "") + def group(indent = 0, open_obj = "", close_obj = "", &) text open_obj group_sub do nest(indent) do @@ -119,7 +119,7 @@ class PrettyPrint text close_obj end - private def group_sub + private def group_sub(&) group = Group.new(@group_stack.last.depth + 1) @group_stack.push group @group_queue.enq group @@ -134,7 +134,7 @@ class PrettyPrint end # Increases the indentation for breakables inside the current group. - def nest(indent = 1) + def nest(indent = 1, &) @indent += indent begin yield @@ -157,7 +157,7 @@ class PrettyPrint # Appends a group that is surrounded by the given *left* and *right* # objects, and optionally is surrounded by the given breakable # objects. - def surround(left, right, left_break = "", right_break = "") : Nil + def surround(left, right, left_break = "", right_break = "", &) : Nil group(1, left, right) do breakable left_break if left_break yield @@ -167,7 +167,7 @@ class PrettyPrint # Appends a list of elements surrounded by *left* and *right* # and separated by commas, yielding each element to the given block. - def list(left, elements, right) : Nil + def list(left, elements, right, &) : Nil group(1, left, right) do elements.each_with_index do |elem, i| comma if i > 0 @@ -298,7 +298,7 @@ class PrettyPrint # Creates a pretty printer and yields it to the block, # appending any output to the given *io*. - def self.format(io : IO, width : Int32, newline = "\n", indent = 0) + def self.format(io : IO, width : Int32, newline = "\n", indent = 0, &) printer = PrettyPrint.new(io, width, newline, indent) yield printer printer.flush diff --git a/src/process.cr b/src/process.cr index 44365f6fe715..3c0ce80cfdac 100644 --- a/src/process.cr +++ b/src/process.cr @@ -65,7 +65,7 @@ class Process # returns a `Process` representing the new child process. # # Available only on Unix-like operating systems. - def self.fork : Process + def self.fork(&) : Process if process = fork process else @@ -136,7 +136,7 @@ class Process # # Raises `IO::Error` if executing the command fails (for example if the executable doesn't exist). def self.run(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, - input : Stdio = Redirect::Pipe, output : Stdio = Redirect::Pipe, error : Stdio = Redirect::Pipe, chdir : Path | String? = nil) + input : Stdio = Redirect::Pipe, output : Stdio = Redirect::Pipe, error : Stdio = Redirect::Pipe, chdir : Path | String? = nil, &) process = new(command, args, env, clear_env, shell, input, output, error, chdir) begin value = yield process diff --git a/src/process/executable_path.cr b/src/process/executable_path.cr index 67f253cdd291..cf2dd091c6ab 100644 --- a/src/process/executable_path.cr +++ b/src/process/executable_path.cr @@ -60,7 +60,7 @@ class Process nil end - private def self.find_executable_possibilities(name, path, pwd) + private def self.find_executable_possibilities(name, path, pwd, &) return if name.to_s.empty? {% if flag?(:win32) %} diff --git a/src/raise.cr b/src/raise.cr index f3d505ebe94e..62f56a1d38a8 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -37,7 +37,7 @@ private struct LEBReader end end -private def traverse_eh_table(leb, start, ip, actions) +private def traverse_eh_table(leb, start, ip, actions, &) # Ref: https://chromium.googlesource.com/native_client/pnacl-libcxxabi/+/master/src/cxa_personality.cpp throw_offset = ip - 1 - start diff --git a/src/range.cr b/src/range.cr index df6c51284c04..d09b91380b3f 100644 --- a/src/range.cr +++ b/src/range.cr @@ -108,7 +108,7 @@ struct Range(B, E) # (10..15).each { |n| print n, ' ' } # # prints: 10 11 12 13 14 15 # ``` - def each : Nil + def each(&) : Nil {% if B == Nil %} {% raise "Can't each beginless range" %} {% end %} @@ -160,7 +160,7 @@ struct Range(B, E) # (10...15).reverse_each { |n| print n, ' ' } # # prints: 14 13 12 11 10 # ``` - def reverse_each : Nil + def reverse_each(&) : Nil {% if E == Nil %} {% raise "Can't reverse_each endless range" %} {% end %} diff --git a/src/reference.cr b/src/reference.cr index f57923e107dd..4f5f9f5571e3 100644 --- a/src/reference.cr +++ b/src/reference.cr @@ -152,7 +152,7 @@ class Reference end end - private def exec_recursive(method) + private def exec_recursive(method, &) hash = ExecRecursive.hash key = {object_id, method} if hash[key]? @@ -202,7 +202,7 @@ class Reference # end # end # ``` - private def exec_recursive_clone + private def exec_recursive_clone(&) hash = ExecRecursiveClone.hash clone_object_id = hash[object_id]? unless clone_object_id diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr index ff68509bed9e..b1eb227c3cae 100644 --- a/src/regex/pcre.cr +++ b/src/regex/pcre.cr @@ -120,7 +120,7 @@ module Regex::PCRE end end - private def fetch_impl(group_name : String) + private def fetch_impl(group_name : String, &) max_start = -1 match = nil exists = false @@ -139,7 +139,7 @@ module Regex::PCRE end end - private def each_named_capture_number(group_name) + private def each_named_capture_number(group_name, &) name_entry_size = LibPCRE.get_stringtable_entries(@code, group_name, out first, out last) return if name_entry_size < 0 diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 176a479bbb57..08b823083086 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -25,7 +25,7 @@ module Regex::PCRE2 end end - protected def self.compile(source, options) + protected def self.compile(source, options, &) if res = LibPCRE2.compile(source, source.bytesize, options, out errorcode, out erroroffset, nil) res else @@ -96,7 +96,7 @@ module Regex::PCRE2 end # :nodoc: - def each_capture_group + def each_capture_group(&) name_table = uninitialized UInt8* pattern_info(LibPCRE2::INFO_NAMETABLE, pointerof(name_table)) @@ -189,7 +189,7 @@ module Regex::PCRE2 end end - private def fetch_impl(group_name : String) + private def fetch_impl(group_name : String, &) selected_range = nil exists = false @regex.each_capture_group do |number, name_entry| diff --git a/src/slice.cr b/src/slice.cr index c944de299560..ebdd7c5f4cc8 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -102,7 +102,7 @@ struct Slice(T) # slice = Slice.new(3) { |i| i + 10 } # slice # => Slice[10, 11, 12] # ``` - def self.new(size : Int, *, read_only = false) + def self.new(size : Int, *, read_only = false, &) pointer = Pointer.malloc(size) { |i| yield i } new(pointer, size, read_only: read_only) end diff --git a/src/socket.cr b/src/socket.cr index 508f76f08e9b..df736a561503 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -140,7 +140,7 @@ class Socket < IO # Tries to listen for connections on the previously bound socket. # Yields an `Socket::Error` on failure. - def listen(backlog : Int = SOMAXCONN) + def listen(backlog : Int = SOMAXCONN, &) system_listen(backlog) { |err| yield err } end @@ -387,7 +387,7 @@ class Socket < IO raise Socket::Error.from_errno("getsockopt") end - protected def getsockopt(optname, optval, level = LibC::SOL_SOCKET) + protected def getsockopt(optname, optval, level = LibC::SOL_SOCKET, &) system_getsockopt(fd, optname, optval, level) { |value| yield value } end diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr index 1667ebb7ae91..01415d9b642c 100644 --- a/src/socket/addrinfo.cr +++ b/src/socket/addrinfo.cr @@ -56,7 +56,7 @@ class Socket # # The iteration will be stopped once the block returns something that isn't # an `Exception` (e.g. a `Socket` or `nil`). - def self.resolve(domain, service, family : Family? = nil, type : Type = nil, protocol : Protocol = Protocol::IP, timeout = nil) + def self.resolve(domain, service, family : Family? = nil, type : Type = nil, protocol : Protocol = Protocol::IP, timeout = nil, &) getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo| loop do value = yield addrinfo.not_nil! @@ -128,7 +128,7 @@ class Socket end end - private def self.getaddrinfo(domain, service, family, type, protocol, timeout) + private def self.getaddrinfo(domain, service, family, type, protocol, timeout, &) {% if flag?(:wasm32) %} raise NotImplementedError.new "Socket::Addrinfo.getaddrinfo" {% else %} @@ -202,7 +202,7 @@ class Socket # Resolves a domain for the TCP protocol with STREAM type, and yields each # possible `Addrinfo`. See `#resolve` for details. - def self.tcp(domain, service, family = Family::UNSPEC, timeout = nil) + def self.tcp(domain, service, family = Family::UNSPEC, timeout = nil, &) resolve(domain, service, family, Type::STREAM, Protocol::TCP) { |addrinfo| yield addrinfo } end @@ -221,7 +221,7 @@ class Socket # Resolves a domain for the UDP protocol with DGRAM type, and yields each # possible `Addrinfo`. See `#resolve` for details. - def self.udp(domain, service, family = Family::UNSPEC, timeout = nil) + def self.udp(domain, service, family = Family::UNSPEC, timeout = nil, &) resolve(domain, service, family, Type::DGRAM, Protocol::UDP) { |addrinfo| yield addrinfo } end diff --git a/src/socket/server.cr b/src/socket/server.cr index 8d282b4c56dd..253593b330f4 100644 --- a/src/socket/server.cr +++ b/src/socket/server.cr @@ -45,7 +45,7 @@ class Socket # socket.puts Time.utc # end # ``` - def accept + def accept(&) sock = accept begin yield sock @@ -68,7 +68,7 @@ class Socket # socket.puts Time.utc # end # ``` - def accept? + def accept?(&) sock = accept? return unless sock diff --git a/src/socket/tcp_server.cr b/src/socket/tcp_server.cr index 4d72259d2ade..51d828d45426 100644 --- a/src/socket/tcp_server.cr +++ b/src/socket/tcp_server.cr @@ -64,7 +64,7 @@ class TCPServer < TCPSocket # server socket when the block returns. # # Returns the value of the block. - def self.open(host, port, backlog = SOMAXCONN, reuse_port = false) + def self.open(host, port, backlog = SOMAXCONN, reuse_port = false, &) server = new(host, port, backlog, reuse_port: reuse_port) begin yield server @@ -77,7 +77,7 @@ class TCPServer < TCPSocket # block. Eventually closes the server socket when the block returns. # # Returns the value of the block. - def self.open(port : Int, backlog = SOMAXCONN, reuse_port = false) + def self.open(port : Int, backlog = SOMAXCONN, reuse_port = false, &) server = new(port, backlog, reuse_port: reuse_port) begin yield server diff --git a/src/socket/tcp_socket.cr b/src/socket/tcp_socket.cr index 09ac37fab531..980f0b338cff 100644 --- a/src/socket/tcp_socket.cr +++ b/src/socket/tcp_socket.cr @@ -51,7 +51,7 @@ class TCPSocket < IPSocket # eventually closes the socket when the block returns. # # Returns the value of the block. - def self.open(host, port) + def self.open(host, port, &) sock = new(host, port) begin yield sock diff --git a/src/socket/unix_server.cr b/src/socket/unix_server.cr index f77fbdfee514..98425e86cc54 100644 --- a/src/socket/unix_server.cr +++ b/src/socket/unix_server.cr @@ -55,7 +55,7 @@ class UNIXServer < UNIXSocket # server socket when the block returns. # # Returns the value of the block. - def self.open(path, type : Type = Type::STREAM, backlog = 128) + def self.open(path, type : Type = Type::STREAM, backlog = 128, &) server = new(path, type, backlog) begin yield server diff --git a/src/socket/unix_socket.cr b/src/socket/unix_socket.cr index 4efbd1c1c4bd..6a4dbb3d340a 100644 --- a/src/socket/unix_socket.cr +++ b/src/socket/unix_socket.cr @@ -37,7 +37,7 @@ class UNIXSocket < Socket # eventually closes the socket when the block returns. # # Returns the value of the block. - def self.open(path, type : Type = Type::STREAM) + def self.open(path, type : Type = Type::STREAM, &) sock = new(path, type) begin yield sock @@ -85,7 +85,7 @@ class UNIXSocket < Socket # block. Eventually closes both sockets when the block returns. # # Returns the value of the block. - def self.pair(type : Type = Type::STREAM) + def self.pair(type : Type = Type::STREAM, &) left, right = pair(type) begin yield left, right diff --git a/src/spec/expectations.cr b/src/spec/expectations.cr index 3313bb43aaa9..ac93de54975e 100644 --- a/src/spec/expectations.cr +++ b/src/spec/expectations.cr @@ -387,7 +387,7 @@ module Spec # TODO: Enable "expect_raises" for wasm32 after exceptions are working. end {% else %} - def expect_raises(klass : T.class, message : String | Regex | Nil = nil, file = __FILE__, line = __LINE__) forall T + def expect_raises(klass : T.class, message : String | Regex | Nil = nil, file = __FILE__, line = __LINE__, &) forall T yield rescue ex : T # We usually bubble Spec::AssertionFailed, unless this is the expected exception diff --git a/src/string.cr b/src/string.cr index 0d25eb667eec..44a811c4ba91 100644 --- a/src/string.cr +++ b/src/string.cr @@ -241,7 +241,7 @@ class String # end # str # => "ab" # ``` - def self.new(capacity : Int) + def self.new(capacity : Int, &) check_capacity_in_bounds(capacity) str = GC.malloc_atomic(capacity.to_u32 + HEADER_SIZE + 1).as(UInt8*) @@ -275,7 +275,7 @@ class String # end # str # => "hello 1" # ``` - def self.build(capacity = 64) : self + def self.build(capacity = 64, &) : self String::Builder.build(capacity) do |builder| yield builder end @@ -735,7 +735,7 @@ class String end end - private def to_f_impl(whitespace : Bool = true, strict : Bool = true) + private def to_f_impl(whitespace : Bool = true, strict : Bool = true, &) return unless whitespace || '0' <= self[0] <= '9' || self[0].in?('-', '+') v, endptr = yield @@ -2340,7 +2340,7 @@ class String # ``` # "hello".sub(/./) { |s| s[0].ord.to_s + ' ' } # => "104 ello" # ``` - def sub(pattern : Regex) : String + def sub(pattern : Regex, &) : String sub_append(pattern) do |str, match, buffer| $~ = match buffer << yield str, match @@ -2468,7 +2468,7 @@ class String end end - private def sub_append(pattern : Regex) + private def sub_append(pattern : Regex, &) match = pattern.match(self) return self unless match @@ -2511,7 +2511,7 @@ class String end end - private def sub_index(index, replacement) + private def sub_index(index, replacement, &) index += size if index < 0 byte_index = char_index_to_byte_index(index) @@ -2561,7 +2561,7 @@ class String end end - private def sub_range(range, replacement) + private def sub_range(range, replacement, &) start, count = Indexable.range_to_index_and_count(range, size) || raise IndexError.new from_index = char_index_to_byte_index(start) @@ -2698,7 +2698,7 @@ class String # ``` # "hello".gsub(/./) { |s| s[0].ord.to_s + ' ' } # => "104 101 108 108 111 " # ``` - def gsub(pattern : Regex) : String + def gsub(pattern : Regex, &) : String gsub_append(pattern) do |string, match, buffer| $~ = match buffer << yield string, match @@ -2840,7 +2840,7 @@ class String end end - private def gsub_append(pattern : Regex) + private def gsub_append(pattern : Regex, &) byte_offset = 0 match = pattern.match_at_byte_index(self, byte_offset) return self unless match @@ -2879,7 +2879,7 @@ class String # ``` # "aabbcc".count &.in?('a', 'b') # => 4 # ``` - def count : Int32 + def count(&) : Int32 count = 0 each_char do |char| count += 1 if yield char @@ -2910,7 +2910,7 @@ class String # ``` # "aabbcc".delete &.in?('a', 'b') # => "cc" # ``` - def delete : String + def delete(&) : String String.build(bytesize) do |buffer| each_char do |char| buffer << char unless yield char @@ -2947,7 +2947,7 @@ class String # "aaabbbccc".squeeze &.in?('a', 'b') # => "abccc" # "aaabbbccc".squeeze &.in?('a', 'c') # => "abbbc" # ``` - def squeeze : String + def squeeze(&) : String previous = nil String.build(bytesize) do |buffer| each_char do |char| @@ -4572,7 +4572,7 @@ class String # Searches the string for instances of *pattern*, # yielding a `Regex::MatchData` for each match. - def scan(pattern : Regex) : self + def scan(pattern : Regex, &) : self byte_offset = 0 while match = pattern.match_at_byte_index(self, byte_offset) @@ -4599,7 +4599,7 @@ class String # Searches the string for instances of *pattern*, # yielding the matched string for each match. - def scan(pattern : String) : self + def scan(pattern : String, &) : self return self if pattern.empty? index = 0 while index = byte_index(pattern, index) @@ -4628,7 +4628,7 @@ class String # end # array # => ['a', 'b', '☃'] # ``` - def each_char : Nil + def each_char(&) : Nil if single_byte_optimizable? each_byte do |byte| yield (byte < 0x80 ? byte.unsafe_chr : Char::REPLACEMENT) @@ -4664,7 +4664,7 @@ class String # # Accepts an optional *offset* parameter, which tells it to start counting # from there. - def each_char_with_index(offset = 0) + def each_char_with_index(offset = 0, &) each_char do |char| yield char, offset offset += 1 @@ -4695,7 +4695,7 @@ class String # ``` # # See also: `Char#ord`. - def each_codepoint + def each_codepoint(&) each_char do |char| yield char.ord end @@ -4739,7 +4739,7 @@ class String # end # array # => [97, 98, 226, 152, 131] # ``` - def each_byte + def each_byte(&) to_slice.each do |byte| yield byte end @@ -4899,7 +4899,7 @@ class String end end - private def dump_or_inspect(io) + private def dump_or_inspect(io, &) io << '"' dump_or_inspect_unquoted(io) do |char, error| yield char, error @@ -4907,7 +4907,7 @@ class String io << '"' end - private def dump_or_inspect_unquoted(io) + private def dump_or_inspect_unquoted(io, &) reader = Char::Reader.new(self) while reader.has_next? current_char = reader.current_char @@ -4959,7 +4959,7 @@ class String end end - private def dump_or_inspect_char(char, error, io) + private def dump_or_inspect_char(char, error, io, &) if error dump_hex(error, io) elsif yield @@ -5218,7 +5218,7 @@ class String @bytesize == 0 || @length > 0 end - protected def each_byte_index_and_char_index + protected def each_byte_index_and_char_index(&) byte_index = 0 char_index = 0 diff --git a/src/string/builder.cr b/src/string/builder.cr index 373b6a7da0c2..d1add8dc9be2 100644 --- a/src/string/builder.cr +++ b/src/string/builder.cr @@ -22,7 +22,7 @@ class String::Builder < IO @finished = false end - def self.build(capacity : Int = 64) : String + def self.build(capacity : Int = 64, &) : String builder = new(capacity) yield builder builder.to_s diff --git a/src/string/utf16.cr b/src/string/utf16.cr index bf79b073a2fd..b3fdc09301af 100644 --- a/src/string/utf16.cr +++ b/src/string/utf16.cr @@ -129,7 +129,7 @@ class String end # Yields each decoded char in the given slice. - private def self.each_utf16_char(slice : Slice(UInt16)) + private def self.each_utf16_char(slice : Slice(UInt16), &) i = 0 while i < slice.size byte = slice[i].to_i @@ -154,7 +154,7 @@ class String end # Yields each decoded char in the given pointer, stopping at the first null byte. - private def self.each_utf16_char(pointer : Pointer(UInt16)) : Pointer(UInt16) + private def self.each_utf16_char(pointer : Pointer(UInt16), &) : Pointer(UInt16) loop do byte = pointer.value.to_i break if byte == 0 diff --git a/src/time/location/loader.cr b/src/time/location/loader.cr index 5386f1570ef0..ffda4929f0c2 100644 --- a/src/time/location/loader.cr +++ b/src/time/location/loader.cr @@ -41,7 +41,7 @@ class Time::Location end end - private def self.open_file_cached(name : String, path : String) + private def self.open_file_cached(name : String, path : String, &) return nil unless File.exists?(path) mtime = File.info(path).modification_time @@ -173,7 +173,7 @@ class Time::Location # This method loads an entry from an uncompressed zip file. # See http://www.onicos.com/staff/iz/formats/zip.html for ZIP format layout - private def self.read_zip_file(name : String, file : File) + private def self.read_zip_file(name : String, file : File, &) file.seek -ZIP_TAIL_SIZE, IO::Seek::End if file.read_bytes(Int32, IO::ByteFormat::LittleEndian) != END_OF_CENTRAL_DIRECTORY_HEADER_SIGNATURE diff --git a/src/tuple.cr b/src/tuple.cr index a3815c856278..d67578271363 100644 --- a/src/tuple.cr +++ b/src/tuple.cr @@ -339,7 +339,7 @@ struct Tuple # tuple.at(0) { 10 } # => 1 # tuple.at(3) { 10 } # => 10 # ``` - def at(index : Int) + def at(index : Int, &) index += size if index < 0 {% for i in 0...T.size %} return self[{{i}}] if {{i}} == index @@ -607,7 +607,7 @@ struct Tuple # # Accepts an optional *offset* parameter, which tells it to start counting # from there. - def map_with_index(offset = 0) + def map_with_index(offset = 0, &) {% begin %} Tuple.new( {% for i in 0...T.size %} @@ -657,7 +657,7 @@ struct Tuple end # :inherit: - def reduce + def reduce(&) {% if T.empty? %} raise Enumerable::EmptyError.new {% else %} @@ -670,7 +670,7 @@ struct Tuple end # :inherit: - def reduce(memo) + def reduce(memo, &) {% for i in 0...T.size %} memo = yield memo, self[{{ i }}] {% end %} @@ -678,7 +678,7 @@ struct Tuple end # :inherit: - def reduce? + def reduce?(&) {% unless T.empty? %} reduce { |memo, elem| yield memo, elem } {% end %} diff --git a/src/unicode/unicode.cr b/src/unicode/unicode.cr index 6a9a7698c3d4..3918b7a1efe6 100644 --- a/src/unicode/unicode.cr +++ b/src/unicode/unicode.cr @@ -161,7 +161,7 @@ module Unicode end # :nodoc: - def self.upcase(char : Char, options : CaseOptions) + def self.upcase(char : Char, options : CaseOptions, &) result = check_upcase_ascii(char, options) if result yield result @@ -231,7 +231,7 @@ module Unicode end # :nodoc: - def self.downcase(char : Char, options : CaseOptions) + def self.downcase(char : Char, options : CaseOptions, &) result = check_downcase_ascii(char, options) if result yield result @@ -555,7 +555,7 @@ module Unicode end end - private def self.search_ranges(haystack, needle) + private def self.search_ranges(haystack, needle, &) value = haystack.bsearch { |v| needle <= v[1] } if value && value[0] <= needle <= value[1] yield value diff --git a/src/uri/encoding.cr b/src/uri/encoding.cr index 3f6041e128ac..a7c5be7f1fb9 100644 --- a/src/uri/encoding.cr +++ b/src/uri/encoding.cr @@ -330,7 +330,7 @@ class URI # :nodoc: # Unencodes one character. Private API - def self.decode_one(string, bytesize, i, byte, char, io, plus_to_space = false) + def self.decode_one(string, bytesize, i, byte, char, io, plus_to_space = false, &) if plus_to_space && char == '+' io.write_byte ' '.ord.to_u8 i += 1 diff --git a/src/uri/params.cr b/src/uri/params.cr index f3cc92dcac11..aa4f5eb30511 100644 --- a/src/uri/params.cr +++ b/src/uri/params.cr @@ -32,7 +32,7 @@ class URI # # ... # end # ``` - def self.parse(query : String) + def self.parse(query : String, &) return if query.empty? key = nil @@ -284,7 +284,7 @@ class URI # params.fetch("email") { raise "Email is missing" } # raises "Email is missing" # params.fetch("non_existent_param") { "default computed value" } # => "default computed value" # ``` - def fetch(name) + def fetch(name, &) return yield name unless has_key?(name) raw_params[name].first end @@ -324,7 +324,7 @@ class URI # # item => keychain # # item => keynote # ``` - def each + def each(&) raw_params.each do |name, values| values.each do |value| yield({name, value}) diff --git a/src/va_list.cr b/src/va_list.cr index b24304c32e4c..9f389089c83d 100644 --- a/src/va_list.cr +++ b/src/va_list.cr @@ -8,7 +8,7 @@ struct VaList def initialize(@to_unsafe : LibC::VaList) end - def self.open + def self.open(&) ap = uninitialized LibC::VaList Intrinsics.va_start pointerof(ap) begin diff --git a/src/xml/attributes.cr b/src/xml/attributes.cr index 7964a89faabf..1df5aea55483 100644 --- a/src/xml/attributes.cr +++ b/src/xml/attributes.cr @@ -49,7 +49,7 @@ class XML::Attributes value if res == 0 end - def each : Nil + def each(&) : Nil return unless @node.element? props = self.props diff --git a/src/xml/builder.cr b/src/xml/builder.cr index 5265a07feb9f..1ebe23efcd47 100644 --- a/src/xml/builder.cr +++ b/src/xml/builder.cr @@ -40,7 +40,7 @@ class XML::Builder # Emits the start of the document, invokes the block, # and then emits the end of the document. - def document(version = nil, encoding = nil) + def document(version = nil, encoding = nil, &) start_document version, encoding yield.tap { end_document } end @@ -70,14 +70,14 @@ class XML::Builder # Emits the start of an element with the given *attributes*, # invokes the block and then emits the end of the element. - def element(__name__ : String, **attributes) + def element(__name__ : String, **attributes, &) element(__name__, attributes) do yield end end # :ditto: - def element(__name__ : String, attributes : Hash | NamedTuple) + def element(__name__ : String, attributes : Hash | NamedTuple, &) start_element __name__ attributes(attributes) yield.tap { end_element } @@ -95,14 +95,14 @@ class XML::Builder # Emits the start of an element with namespace info with the given *attributes*, # invokes the block and then emits the end of the element. - def element(__prefix__ : String?, __name__ : String, __namespace_uri__ : String?, **attributes) + def element(__prefix__ : String?, __name__ : String, __namespace_uri__ : String?, **attributes, &) element(__prefix__, __name__, __namespace_uri__, attributes) do yield end end # :ditto: - def element(__prefix__ : String?, __name__ : String, __namespace_uri__ : String?, attributes : Hash | NamedTuple) + def element(__prefix__ : String?, __name__ : String, __namespace_uri__ : String?, attributes : Hash | NamedTuple, &) start_element __prefix__, __name__, __namespace_uri__ attributes(attributes) yield.tap { end_element } @@ -137,7 +137,7 @@ class XML::Builder # Emits the start of an attribute, invokes the block, # and then emits the end of the attribute. - def attribute(*args, **nargs) + def attribute(*args, **nargs, &) start_attribute *args, **nargs yield.tap { end_attribute } end @@ -208,7 +208,7 @@ class XML::Builder # Emits the start of a comment, invokes the block # and then emits the end of the comment. - def comment + def comment(&) start_comment yield.tap { end_comment } end @@ -230,7 +230,7 @@ class XML::Builder # Emits the start of a `DTD`, invokes the block # and then emits the end of the `DTD`. - def dtd(name : String, pubid : String, sysid : String) : Nil + def dtd(name : String, pubid : String, sysid : String, &) : Nil start_dtd name, pubid, sysid yield.tap { end_dtd } end @@ -322,7 +322,7 @@ module XML # # string # => "\n\n Jane\n Doe\n\n" # ``` - def self.build(version : String? = nil, encoding : String? = nil, indent = nil, quote_char = nil) + def self.build(version : String? = nil, encoding : String? = nil, indent = nil, quote_char = nil, &) String.build do |str| build(str, version, encoding, indent, quote_char) do |xml| yield xml @@ -346,7 +346,7 @@ module XML # # string # => "\n Jane\n Doe\n\n" # ``` - def self.build_fragment(*, indent = nil, quote_char = nil) + def self.build_fragment(*, indent = nil, quote_char = nil, &) String.build do |str| build_fragment(str, indent: indent, quote_char: quote_char) do |xml| yield xml @@ -357,7 +357,7 @@ module XML # Writes XML document into the given `IO`. An `XML::Builder` is yielded to the block. # # Builds an XML document (see `#document`) including XML declaration (``). - def self.build(io : IO, version : String? = nil, encoding : String? = nil, indent = nil, quote_char = nil) : Nil + def self.build(io : IO, version : String? = nil, encoding : String? = nil, indent = nil, quote_char = nil, &) : Nil build_fragment(io, indent: indent, quote_char: quote_char) do |xml| xml.start_document version, encoding yield xml @@ -368,7 +368,7 @@ module XML # Writes XML fragment into the given `IO`. An `XML::Builder` is yielded to the block. # # Builds an XML fragment without XML declaration (``). - def self.build_fragment(io : IO, *, indent = nil, quote_char = nil) : Nil + def self.build_fragment(io : IO, *, indent = nil, quote_char = nil, &) : Nil xml = XML::Builder.new(io) xml.indent = indent if indent xml.quote_char = quote_char if quote_char diff --git a/src/xml/node_set.cr b/src/xml/node_set.cr index 971c7bc63ec4..0263facbfd55 100644 --- a/src/xml/node_set.cr +++ b/src/xml/node_set.cr @@ -18,7 +18,7 @@ class XML::NodeSet internal_at(index) end - def each : Nil + def each(&) : Nil size.times do |i| yield internal_at(i) end diff --git a/src/xml/reader.cr b/src/xml/reader.cr index 684d3ed28871..9d1de3ed4c9e 100644 --- a/src/xml/reader.cr +++ b/src/xml/reader.cr @@ -195,7 +195,7 @@ class XML::Reader @reader end - private def collect_errors + private def collect_errors(&) Error.collect(@errors) { yield }.tap do Error.add_errors(@errors) end diff --git a/src/yaml/builder.cr b/src/yaml/builder.cr index 6fc5dac4272e..2764580dde13 100644 --- a/src/yaml/builder.cr +++ b/src/yaml/builder.cr @@ -67,7 +67,7 @@ class YAML::Builder end # Starts a YAML stream, invokes the block, and ends it. - def stream + def stream(&) start_stream yield.tap { end_stream } end @@ -83,7 +83,7 @@ class YAML::Builder end # Starts a document, invokes the block, and then ends it. - def document + def document(&) start_document yield.tap { end_document } end @@ -109,7 +109,7 @@ class YAML::Builder end # Starts a sequence, invokes the block, and the ends it. - def sequence(anchor : String? = nil, tag : String? = nil, style : YAML::SequenceStyle = YAML::SequenceStyle::ANY) + def sequence(anchor : String? = nil, tag : String? = nil, style : YAML::SequenceStyle = YAML::SequenceStyle::ANY, &) start_sequence(anchor, tag, style) yield.tap { end_sequence } end @@ -128,7 +128,7 @@ class YAML::Builder end # Starts a mapping, invokes the block, and then ends it. - def mapping(anchor : String? = nil, tag : String? = nil, style : YAML::MappingStyle = YAML::MappingStyle::ANY) + def mapping(anchor : String? = nil, tag : String? = nil, style : YAML::MappingStyle = YAML::MappingStyle::ANY, &) start_mapping(anchor, tag, style) yield.tap { end_mapping } end @@ -239,7 +239,7 @@ module YAML # end # string # => "---\nfoo:\n- 1\n- 2\n" # ``` - def self.build + def self.build(&) String.build do |str| build(str) do |yaml| yield yaml @@ -248,7 +248,7 @@ module YAML end # Writes YAML into the given `IO`. A `YAML::Builder` is yielded to the block. - def self.build(io : IO) : Nil + def self.build(io : IO, &) : Nil YAML::Builder.build(io) do |yaml| yaml.stream do yaml.document do diff --git a/src/yaml/from_yaml.cr b/src/yaml/from_yaml.cr index 4895467e8638..741ddcd98228 100644 --- a/src/yaml/from_yaml.cr +++ b/src/yaml/from_yaml.cr @@ -12,7 +12,7 @@ def Object.from_yaml(string_or_io : String | IO) new(YAML::ParseContext.new, parse_yaml(string_or_io)) end -def Array.from_yaml(string_or_io : String | IO) +def Array.from_yaml(string_or_io : String | IO, &) new(YAML::ParseContext.new, parse_yaml(string_or_io)) do |element| yield element end @@ -103,7 +103,7 @@ def Array.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) ary end -def Array.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) +def Array.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node, &) unless node.is_a?(YAML::Nodes::Sequence) node.raise "Expected sequence, not #{node.kind}" end @@ -128,7 +128,7 @@ def Set.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) ary end -def Set.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) +def Set.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node, &) unless node.is_a?(YAML::Nodes::Sequence) node.raise "Expected sequence, not #{node.kind}" end @@ -153,7 +153,7 @@ def Hash.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) hash end -def Hash.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) +def Hash.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node, &) unless node.is_a?(YAML::Nodes::Mapping) node.raise "Expected mapping, not #{node.kind}" end diff --git a/src/yaml/nodes/builder.cr b/src/yaml/nodes/builder.cr index 50e684530dda..9445cb6485d4 100644 --- a/src/yaml/nodes/builder.cr +++ b/src/yaml/nodes/builder.cr @@ -85,7 +85,7 @@ class YAML::Nodes::Builder def sequence(anchor : String? = nil, tag : String? = nil, style : YAML::SequenceStyle = YAML::SequenceStyle::ANY, - reference = nil) : Nil + reference = nil, &) : Nil node = Sequence.new node.anchor = anchor node.tag = tag @@ -102,7 +102,7 @@ class YAML::Nodes::Builder def mapping(anchor : String? = nil, tag : String? = nil, style : YAML::MappingStyle = YAML::MappingStyle::ANY, - reference = nil) : Nil + reference = nil, &) : Nil node = Mapping.new node.anchor = anchor node.tag = tag @@ -130,7 +130,7 @@ class YAML::Nodes::Builder end end - private def push_to_stack(node) + private def push_to_stack(node, &) push_node(node) old_current = @current diff --git a/src/yaml/nodes/nodes.cr b/src/yaml/nodes/nodes.cr index 919554cefa2a..a035a9dae2ab 100644 --- a/src/yaml/nodes/nodes.cr +++ b/src/yaml/nodes/nodes.cr @@ -96,7 +96,7 @@ module YAML::Nodes @nodes << node end - def each + def each(&) @nodes.each do |node| yield node end @@ -132,7 +132,7 @@ module YAML::Nodes end # Yields each key-value pair in this mapping. - def each + def each(&) 0.step(by: 2, to: @nodes.size - 1) do |i| yield({@nodes[i], @nodes[i + 1]}) end diff --git a/src/yaml/nodes/parser.cr b/src/yaml/nodes/parser.cr index d63cf093196c..93fc94e46e51 100644 --- a/src/yaml/nodes/parser.cr +++ b/src/yaml/nodes/parser.cr @@ -5,7 +5,7 @@ class YAML::Nodes::Parser < YAML::Parser @anchors = {} of String => Node end - def self.new(content) + def self.new(content, &) parser = new(content) yield parser ensure parser.close end diff --git a/src/yaml/parse_context.cr b/src/yaml/parse_context.cr index 5ff86c46988a..da1608136d32 100644 --- a/src/yaml/parse_context.cr +++ b/src/yaml/parse_context.cr @@ -30,7 +30,7 @@ class YAML::ParseContext # Tries to read an alias from `node` of type `T`. Invokes # the block if successful, and invokers must return this object # instead of deserializing their members. - def read_alias(node, type : T.class) forall T + def read_alias(node, type : T.class, &) forall T if ptr = read_alias_impl(node, T.crystal_instance_type_id, raise_on_alias: true) yield ptr.unsafe_as(T) end @@ -38,7 +38,7 @@ class YAML::ParseContext # Similar to `read_alias` but doesn't raise if an alias exists # but an instance of type T isn't associated with the current anchor. - def read_alias?(node, type : T.class) forall T + def read_alias?(node, type : T.class, &) forall T if ptr = read_alias_impl(node, T.crystal_instance_type_id, raise_on_alias: false) yield ptr.unsafe_as(T) end diff --git a/src/yaml/parser.cr b/src/yaml/parser.cr index 5b27cee2a3b9..2035f6cd67a6 100644 --- a/src/yaml/parser.cr +++ b/src/yaml/parser.cr @@ -4,7 +4,7 @@ abstract class YAML::Parser @pull_parser = PullParser.new(content) end - def self.new(content) + def self.new(content, &) parser = new(content) yield parser ensure parser.close end @@ -122,7 +122,7 @@ abstract class YAML::Parser sequence end - protected def parse_sequence(sequence) + protected def parse_sequence(sequence, &) @pull_parser.read_sequence_start until @pull_parser.kind.sequence_end? @@ -144,7 +144,7 @@ abstract class YAML::Parser mapping end - protected def parse_mapping(mapping) + protected def parse_mapping(mapping, &) @pull_parser.read_mapping_start until @pull_parser.kind.mapping_end? diff --git a/src/yaml/pull_parser.cr b/src/yaml/pull_parser.cr index 1cdfa8131d92..33ae6bfa07f1 100644 --- a/src/yaml/pull_parser.cr +++ b/src/yaml/pull_parser.cr @@ -36,7 +36,7 @@ class YAML::PullParser # Creates a parser, yields it to the block, and closes # the parser at the end of it. - def self.new(content) + def self.new(content, &) parser = new(content) yield parser ensure parser.close end @@ -126,7 +126,7 @@ class YAML::PullParser # Reads a "stream start" event, yields to the block, # and then reads a "stream end" event. - def read_stream + def read_stream(&) read_stream_start value = yield read_stream_end @@ -135,7 +135,7 @@ class YAML::PullParser # Reads a "document start" event, yields to the block, # and then reads a "document end" event. - def read_document + def read_document(&) read_document_start value = yield read_document_end @@ -144,7 +144,7 @@ class YAML::PullParser # Reads a "sequence start" event, yields to the block, # and then reads a "sequence end" event. - def read_sequence + def read_sequence(&) read_sequence_start value = yield read_sequence_end @@ -153,7 +153,7 @@ class YAML::PullParser # Reads a "mapping start" event, yields to the block, # and then reads a "mapping end" event. - def read_mapping + def read_mapping(&) read_mapping_start value = yield read_mapping_end diff --git a/src/yaml/schema/core.cr b/src/yaml/schema/core.cr index 7ae71fccd83a..b7504f4590e6 100644 --- a/src/yaml/schema/core.cr +++ b/src/yaml/schema/core.cr @@ -128,7 +128,7 @@ module YAML::Schema::Core # If `node` parses to a null value, returns `nil`, otherwise # invokes the given block. - def self.parse_null_or(node : YAML::Nodes::Node) + def self.parse_null_or(node : YAML::Nodes::Node, &) if node.is_a?(YAML::Nodes::Scalar) && parse_null?(node) nil else @@ -140,7 +140,7 @@ module YAML::Schema::Core # values, resolving merge keys (<<) when found (keys and # values of the resolved merge mappings are yielded, # recursively). - def self.each(node : YAML::Nodes::Mapping) + def self.each(node : YAML::Nodes::Mapping, &) # We can't just traverse the nodes and invoke yield because # yield can't recurse. So, we use a stack of {Mapping, index}. # We pop from the stack and traverse the mapping values. @@ -266,13 +266,13 @@ module YAML::Schema::Core raise(YAML::ParseException.new("Invalid timestamp", *location)) end - protected def self.process_scalar_tag(scalar) + protected def self.process_scalar_tag(scalar, &) process_scalar_tag(scalar, scalar.tag) do |value| yield value end end - protected def self.process_scalar_tag(source, tag) + protected def self.process_scalar_tag(source, tag, &) case tag when "tag:yaml.org,2002:binary" yield parse_binary(source.value, source.location) diff --git a/src/yaml/schema/core/parser.cr b/src/yaml/schema/core/parser.cr index 2eaabd7aaa18..d3d205f497f0 100644 --- a/src/yaml/schema/core/parser.cr +++ b/src/yaml/schema/core/parser.cr @@ -86,7 +86,7 @@ class YAML::Schema::Core::Parser < YAML::Parser mapping end - def process_tag(tag) + def process_tag(tag, &) if value = process_collection_tag(@pull_parser, tag) yield value end From c20d8ccb3d63d381e076782bad45a404495811ac Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 17 Jan 2023 00:58:26 +0800 Subject: [PATCH 0247/1551] Support typed LLVM `getelementptr` instructions (#12623) --- samples/llvm/brainfuck.cr | 15 +++-- src/compiler/crystal/codegen/call.cr | 6 +- src/compiler/crystal/codegen/cast.cr | 41 ++++++++----- src/compiler/crystal/codegen/codegen.cr | 45 ++++++++------ src/compiler/crystal/codegen/fun.cr | 21 ++++--- .../crystal/codegen/llvm_builder_helper.cr | 24 ++++++-- src/compiler/crystal/codegen/primitives.cr | 39 ++++++++----- src/compiler/crystal/codegen/unions.cr | 40 +++++++------ src/llvm/builder.cr | 58 +++++++++++++++++-- src/llvm/lib_llvm.cr | 4 ++ 10 files changed, 201 insertions(+), 92 deletions(-) diff --git a/samples/llvm/brainfuck.cr b/samples/llvm/brainfuck.cr index b6cba540f18f..3ca07cdf3584 100644 --- a/samples/llvm/brainfuck.cr +++ b/samples/llvm/brainfuck.cr @@ -18,10 +18,10 @@ class Increment < Instruction builder.position_at_end bb cell_index = builder.load program.cell_index_ptr, "cell_index" - current_cell_ptr = builder.gep program.cells_ptr, cell_index, "current_cell_ptr" + current_cell_ptr = builder.gep program.cell_type, program.cells_ptr, cell_index, "current_cell_ptr" cell_val = builder.load current_cell_ptr, "cell_value" - increment_amount = program.ctx.int(CELL_SIZE_IN_BYTES * 8).const_int(@amount) + increment_amount = program.cell_type.const_int(@amount) new_cell_val = builder.add cell_val, increment_amount, "cell_value" builder.store new_cell_val, current_cell_ptr @@ -53,7 +53,7 @@ class Read < Instruction builder.position_at_end bb cell_index = builder.load program.cell_index_ptr, "cell_index" - current_cell_ptr = builder.gep program.cells_ptr, cell_index, "current_cell_ptr" + current_cell_ptr = builder.gep program.cell_type, program.cells_ptr, cell_index, "current_cell_ptr" getchar = program.mod.functions["getchar"] input_char = builder.call getchar, "input_char" @@ -70,7 +70,7 @@ class Write < Instruction builder.position_at_end bb cell_index = builder.load program.cell_index_ptr, "cell_index" - current_cell_ptr = builder.gep program.cells_ptr, cell_index, "current_cell_ptr" + current_cell_ptr = builder.gep program.cell_type, program.cells_ptr, cell_index, "current_cell_ptr" cell_val = builder.load current_cell_ptr, "cell_value" cell_val_as_char = builder.sext cell_val, program.ctx.int32, "cell_val_as_char" @@ -100,9 +100,9 @@ class Loop < Instruction builder.position_at_end loop_header cell_index = builder.load program.cell_index_ptr, "cell_index" - current_cell_ptr = builder.gep program.cells_ptr, cell_index, "current_cell_ptr" + current_cell_ptr = builder.gep program.cell_type, program.cells_ptr, cell_index, "current_cell_ptr" cell_val = builder.load current_cell_ptr, "cell_value" - zero = program.ctx.int(CELL_SIZE_IN_BYTES * 8).const_int(0) + zero = program.cell_type.const_int(0) cell_val_is_zero = builder.icmp LLVM::IntPredicate::EQ, cell_val, zero builder.cond cell_val_is_zero, loop_after, loop_body_block @@ -123,6 +123,7 @@ class Program getter ctx : LLVM::Context getter builder : LLVM::Builder getter instructions + getter cell_type : LLVM::Type getter! cells_ptr : LLVM::Value getter! cell_index_ptr : LLVM::Value getter! func : LLVM::Function @@ -131,6 +132,8 @@ class Program @ctx = LLVM::Context.new @mod = @ctx.new_module("brainfuck") @builder = @ctx.new_builder + + @cell_type = @ctx.int(CELL_SIZE_IN_BYTES * 8) end def self.new(source : String) diff --git a/src/compiler/crystal/codegen/call.cr b/src/compiler/crystal/codegen/call.cr index 2f92d217ff80..139eb202850f 100644 --- a/src/compiler/crystal/codegen/call.cr +++ b/src/compiler/crystal/codegen/call.cr @@ -205,7 +205,7 @@ class Crystal::CodeGenVisitor abi_arg_type = abi_info.arg_types[i] case abi_arg_type.kind in .direct? - call_arg = codegen_direct_abi_call(call_arg, abi_arg_type) unless arg.type.nil_type? + call_arg = codegen_direct_abi_call(arg.type, call_arg, abi_arg_type) unless arg.type.nil_type? in .indirect? call_arg = codegen_indirect_abi_call(call_arg, abi_arg_type) unless arg.type.nil_type? in .ignore? @@ -233,11 +233,11 @@ class Crystal::CodeGenVisitor call_args end - def codegen_direct_abi_call(call_arg, abi_arg_type) + def codegen_direct_abi_call(call_arg_type, call_arg, abi_arg_type) if cast = abi_arg_type.cast final_value = alloca cast final_value_casted = bit_cast final_value, llvm_context.void_pointer - gep_call_arg = bit_cast gep(call_arg, 0, 0), llvm_context.void_pointer + gep_call_arg = bit_cast gep(llvm_type(call_arg_type), call_arg, 0, 0), llvm_context.void_pointer size = @abi.size(abi_arg_type.type) size = @program.bits64? ? int64(size) : int32(size) align = @abi.align(abi_arg_type.type) diff --git a/src/compiler/crystal/codegen/cast.cr b/src/compiler/crystal/codegen/cast.cr index e069e8f6559d..8e351468d92b 100644 --- a/src/compiler/crystal/codegen/cast.cr +++ b/src/compiler/crystal/codegen/cast.cr @@ -167,7 +167,7 @@ class Crystal::CodeGenVisitor end def assign_distinct(target_pointer, target_type : MixedUnionType, value_type : VoidType, value) - store_void_in_union target_pointer, target_type + store_void_in_union target_type, target_pointer end def assign_distinct(target_pointer, target_type : MixedUnionType, value_type : BoolType, value) @@ -175,7 +175,7 @@ class Crystal::CodeGenVisitor end def assign_distinct(target_pointer, target_type : MixedUnionType, value_type : NilType, value) - store_nil_in_union target_pointer, target_type + store_nil_in_union target_type, target_pointer end def assign_distinct(target_pointer, target_type : MixedUnionType, value_type : Type, value) @@ -229,10 +229,13 @@ class Crystal::CodeGenVisitor end def assign_distinct(target_pointer, target_type : TupleInstanceType, value_type : TupleInstanceType, value) + target_struct_type = llvm_type(target_type) + value_struct_type = llvm_type(value_type) + index = 0 target_type.tuple_types.zip(value_type.tuple_types) do |target_tuple_type, value_tuple_type| - target_ptr = gep target_pointer, 0, index - value_ptr = gep value, 0, index + target_ptr = aggregate_index(target_struct_type, target_pointer, index) + value_ptr = aggregate_index(value_struct_type, value, index) loaded_value = to_lhs(value_ptr, value_tuple_type) assign(target_ptr, target_tuple_type, value_tuple_type, loaded_value) index += 1 @@ -241,12 +244,16 @@ class Crystal::CodeGenVisitor end def assign_distinct(target_pointer, target_type : NamedTupleInstanceType, value_type : NamedTupleInstanceType, value) + target_struct_type = llvm_type(target_type) + value_struct_type = llvm_type(value_type) + value_type.entries.each_with_index do |entry, index| - value_ptr = aggregate_index(value, index) + value_ptr = aggregate_index(value_struct_type, value, index) value_at_index = to_lhs(value_ptr, entry.type) target_index = target_type.name_index(entry.name).not_nil! target_index_type = target_type.name_type(entry.name) - assign aggregate_index(target_pointer, target_index), target_index_type, entry.type, value_at_index + target_ptr = aggregate_index(target_struct_type, target_pointer, target_index) + assign target_ptr, target_index_type, entry.type, value_at_index end end @@ -440,11 +447,14 @@ class Crystal::CodeGenVisitor end def downcast_distinct(value, to_type : TupleInstanceType, from_type : TupleInstanceType) - target_pointer = alloca(llvm_type(to_type)) + target_struct_type = llvm_type(to_type) + value_struct_type = llvm_type(from_type) + target_pointer = alloca(target_struct_type) + index = 0 to_type.tuple_types.zip(from_type.tuple_types) do |target_tuple_type, value_tuple_type| - target_ptr = gep target_pointer, 0, index - value_ptr = gep value, 0, index + target_ptr = aggregate_index(target_struct_type, target_pointer, index) + value_ptr = aggregate_index(value_struct_type, value, index) loaded_value = to_lhs(value_ptr, value_tuple_type) downcasted_value = downcast(loaded_value, target_tuple_type, value_tuple_type, true) downcasted_value = to_rhs(downcasted_value, target_tuple_type) @@ -455,15 +465,18 @@ class Crystal::CodeGenVisitor end def downcast_distinct(value, to_type : NamedTupleInstanceType, from_type : NamedTupleInstanceType) - target_pointer = alloca(llvm_type(to_type)) + target_struct_type = llvm_type(to_type) + value_struct_type = llvm_type(from_type) + target_pointer = alloca(target_struct_type) + from_type.entries.each_with_index do |entry, index| - value_ptr = aggregate_index(value, index) + value_ptr = aggregate_index(value_struct_type, value, index) value_at_index = to_lhs(value_ptr, entry.type) target_index = to_type.name_index(entry.name).not_nil! target_index_type = to_type.name_type(entry.name) downcasted_value = downcast(value_at_index, target_index_type, entry.type, true) downcasted_value = to_rhs(downcasted_value, target_index_type) - store downcasted_value, aggregate_index(target_pointer, target_index) + store downcasted_value, aggregate_index(target_struct_type, target_pointer, target_index) end target_pointer end @@ -603,7 +616,7 @@ class Crystal::CodeGenVisitor def upcast_distinct(value, to_type : MixedUnionType, from_type : VoidType) union_ptr = alloca(llvm_type(to_type)) - store_void_in_union union_ptr, to_type + store_void_in_union to_type, union_ptr union_ptr end @@ -615,7 +628,7 @@ class Crystal::CodeGenVisitor def upcast_distinct(value, to_type : MixedUnionType, from_type : NilType) union_ptr = alloca(llvm_type(to_type)) - store_nil_in_union union_ptr, to_type + store_nil_in_union to_type, union_ptr union_ptr end diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 058108a25710..ac07d54cbfe7 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -533,13 +533,15 @@ module Crystal def visit(node : NamedTupleLiteral) request_value do type = node.type.as(NamedTupleInstanceType) - struct_type = alloca llvm_type(type) + struct_type = llvm_type(type) + tuple = alloca struct_type node.entries.each do |entry| accept entry.value index = type.name_index(entry.key).not_nil! - assign aggregate_index(struct_type, index), type.entries[index].type, entry.value.type, @last + entry_type = type.entries[index].type + assign aggregate_index(struct_type, tuple, index), entry_type, entry.value.type, @last end - @last = struct_type + @last = tuple end false end @@ -1624,9 +1626,10 @@ module Crystal end def make_fun(type, fun_ptr, ctx_ptr) - closure_ptr = alloca llvm_type(type) - store fun_ptr, gep(closure_ptr, 0, 0) - store ctx_ptr, gep(closure_ptr, 0, 1) + struct_type = llvm_type(type) + closure_ptr = alloca struct_type + store fun_ptr, aggregate_index(struct_type, closure_ptr, 0) + store ctx_ptr, aggregate_index(struct_type, closure_ptr, 1) load(closure_ptr) end @@ -1856,12 +1859,12 @@ module Crystal closure_type = @llvm_typer.closure_context_type(closure_vars, parent_closure_type, (self_closured ? current_context.type : nil)) closure_ptr = malloc closure_type closure_vars.each_with_index do |var, i| - current_context.vars[var.name] = LLVMVar.new(gep(closure_ptr, 0, i, var.name), var.type) + current_context.vars[var.name] = LLVMVar.new(gep(closure_type, closure_ptr, 0, i, var.name), var.type) end closure_skip_parent = false if parent_closure_type - store parent_context.not_nil!.closure_ptr.not_nil!, gep(closure_ptr, 0, closure_vars.size, "parent") + store parent_context.not_nil!.closure_ptr.not_nil!, gep(closure_type, closure_ptr, 0, closure_vars.size, "parent") end if self_closured @@ -1869,7 +1872,7 @@ module Crystal self_value = llvm_self self_value = load self_value if current_context.type.passed_by_value? - store self_value, gep(closure_ptr, 0, closure_vars.size + offset, "self") + store self_value, gep(closure_type, closure_ptr, 0, closure_vars.size + offset, "self") current_context.closure_self = current_context.type end @@ -1987,12 +1990,13 @@ module Crystal end def allocate_tuple(type) - struct_type = alloca llvm_type(type) + struct_type = llvm_type(type) + tuple = alloca struct_type type.tuple_types.each_with_index do |tuple_type, i| exp_type, value = yield tuple_type, i - assign aggregate_index(struct_type, i), tuple_type, exp_type, value + assign aggregate_index(struct_type, tuple, i), tuple_type, exp_type, value end - struct_type + tuple end def run_instance_vars_initializers(real_type, type : ClassType | GenericClassInstanceType, type_ptr) @@ -2188,13 +2192,15 @@ module Crystal type.passed_by_value? ? load value : value end - def aggregate_index(ptr, index) - gep ptr, 0, index + # *type* is the pointee type of *ptr* (not the type of the returned + # element) + def aggregate_index(type : LLVM::Type, ptr : LLVM::Value, index : Int32) + gep type, ptr, 0, index end def instance_var_ptr(type, name, pointer) if type.extern_union? - return union_field_ptr(type.instance_vars[name].type, pointer) + return union_field_ptr(type, type.instance_vars[name].type, pointer) end index = type.index_of_instance_var(name).not_nil! @@ -2203,21 +2209,24 @@ module Crystal index += 1 end + target_type = type if type.is_a?(VirtualType) if type.struct? if (_type = type.remove_indirection).is_a?(UnionType) # For a struct we need to cast the second part of the union to the base type _, value_ptr = union_type_and_value_pointer(pointer, _type) - pointer = bit_cast value_ptr, llvm_type(type.base_type).pointer + target_type = type.base_type + pointer = bit_cast value_ptr, llvm_type(target_type).pointer else # Nothing, there's only one subclass so it's the struct already end else - pointer = cast_to pointer, type.base_type + target_type = type.base_type + pointer = cast_to pointer, target_type end end - aggregate_index pointer, index + aggregate_index llvm_struct_type(target_type), pointer, index end def process_finished_hooks diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 03310b305ad3..4c10e569c2a7 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -99,7 +99,10 @@ class Crystal::CodeGenVisitor if is_closure clear_current_debug_location if @debug.line_numbers? - setup_closure_vars target_def.vars, context.closure_vars.not_nil! + void_ptr = context.fun.params.first + closure_type = llvm_typer.copy_type(context.closure_type.not_nil!) + closure_ptr = bit_cast void_ptr, closure_type.pointer + setup_closure_vars target_def.vars, context.closure_vars.not_nil!, self.context, closure_type, closure_ptr else context.reset_closure end @@ -112,7 +115,7 @@ class Crystal::CodeGenVisitor # In the case of a closure proc literal (-> { ... }), the closure_ptr is not # the one of the parent context, it's the last parameter of this proc literal. closure_parent_context = old_context.clone - closure_parent_context.closure_ptr = fun_literal_closure_ptr + closure_parent_context.closure_ptr = closure_ptr.not_nil! context.closure_parent_context = closure_parent_context end @@ -423,10 +426,10 @@ class Crystal::CodeGenVisitor end end - def setup_closure_vars(def_vars, closure_vars, context = self.context, closure_ptr = fun_literal_closure_ptr) + def setup_closure_vars(def_vars, closure_vars, context, closure_type, closure_ptr) if context.closure_skip_parent parent_context = context.closure_parent_context.not_nil! - setup_closure_vars(def_vars, parent_context.closure_vars.not_nil!, parent_context, closure_ptr) + setup_closure_vars(def_vars, parent_context.closure_vars.not_nil!, parent_context, closure_type, closure_ptr) else closure_vars.each_with_index do |var, i| # A closured var in this context might have the same name as @@ -436,16 +439,18 @@ class Crystal::CodeGenVisitor def_var = def_vars.try &.[var.name]? next if def_var && !def_var.closured? - self.context.vars[var.name] = LLVMVar.new(gep(closure_ptr, 0, i, var.name), var.type) + self.context.vars[var.name] = LLVMVar.new(gep(closure_type, closure_ptr, 0, i, var.name), var.type) end if (closure_parent_context = context.closure_parent_context) && (parent_vars = closure_parent_context.closure_vars) - parent_closure_ptr = gep(closure_ptr, 0, closure_vars.size, "parent_ptr") - setup_closure_vars(def_vars, parent_vars, closure_parent_context, load(parent_closure_ptr, "parent")) + parent_closure_type = llvm_typer.copy_type(closure_parent_context.closure_type.not_nil!) + parent_closure_ptr = gep(closure_type, closure_ptr, 0, closure_vars.size, "parent_ptr") + parent_closure = load(parent_closure_ptr, "parent") + setup_closure_vars(def_vars, parent_vars, closure_parent_context, parent_closure_type, parent_closure) elsif closure_self = context.closure_self offset = context.closure_parent_context ? 1 : 0 - self_value = gep(closure_ptr, 0, closure_vars.size + offset, "self") + self_value = gep(closure_type, closure_ptr, 0, closure_vars.size + offset, "self") self_value = load(self_value) unless context.type.passed_by_value? self.context.vars["self"] = LLVMVar.new(self_value, closure_self, true) end diff --git a/src/compiler/crystal/codegen/llvm_builder_helper.cr b/src/compiler/crystal/codegen/llvm_builder_helper.cr index 687b60d33b10..d2848ad95d79 100644 --- a/src/compiler/crystal/codegen/llvm_builder_helper.cr +++ b/src/compiler/crystal/codegen/llvm_builder_helper.cr @@ -79,22 +79,38 @@ module Crystal builder.icmp LLVM::IntPredicate::NE, value, value.type.null end - def gep(ptr, index0 : Int32, name = "") + def gep(ptr : LLVM::Value, index0 : Int32, name = "") gep ptr, int32(index0), name end - def gep(ptr, index0 : LLVM::Value, name = "") + def gep(ptr : LLVM::Value, index0 : LLVM::Value, name = "") builder.inbounds_gep ptr, index0, name end - def gep(ptr, index0 : Int32, index1 : Int32, name = "") + def gep(ptr : LLVM::Value, index0 : Int32, index1 : Int32, name = "") gep ptr, int32(index0), int32(index1), name end - def gep(ptr, index0 : LLVM::Value, index1 : LLVM::Value, name = "") + def gep(ptr : LLVM::Value, index0 : LLVM::Value, index1 : LLVM::Value, name = "") builder.inbounds_gep ptr, index0, index1, name end + def gep(type : LLVM::Type, ptr : LLVM::Value, index0 : Int32, name = "") + gep type, ptr, int32(index0), name + end + + def gep(type : LLVM::Type, ptr : LLVM::Value, index0 : LLVM::Value, name = "") + builder.inbounds_gep type, ptr, index0, name + end + + def gep(type : LLVM::Type, ptr : LLVM::Value, index0 : Int32, index1 : Int32, name = "") + gep type, ptr, int32(index0), int32(index1), name + end + + def gep(type : LLVM::Type, ptr : LLVM::Value, index0 : LLVM::Value, index1 : LLVM::Value, name = "") + builder.inbounds_gep type, ptr, index0, index1, name + end + def call(func, name : String = "") call(func, [] of LLVM::Value, name) end diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index bc71b573e889..ee27902ff7ff 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -731,7 +731,7 @@ class Crystal::CodeGenVisitor allocate_aggregate base_type unless type.struct? - type_id_ptr = aggregate_index(@last, 0) + type_id_ptr = aggregate_index(llvm_struct_type(type), @last, 0) store type_id(base_type), type_id_ptr end @@ -798,19 +798,22 @@ class Crystal::CodeGenVisitor end def codegen_primitive_pointer_add(node, target_def, call_args) - gep call_args[0], call_args[1] + type = context.type.as(PointerInstanceType) + + # `llvm_embedded_type` needed to treat `Void*` like `UInt8*` + gep llvm_embedded_type(type.element_type), call_args[0], call_args[1] end def struct_field_ptr(type, field_name, pointer) index = type.index_of_instance_var('@' + field_name).not_nil! - aggregate_index pointer, index + aggregate_index llvm_type(type), pointer, index end def codegen_primitive_struct_or_union_set(node, target_def, call_args) set_aggregate_field(node, target_def, call_args) do |field_type| type = context.type.as(NonGenericClassType) if type.extern_union? - union_field_ptr(field_type, call_args[0]) + union_field_ptr(type, field_type, call_args[0]) else name = target_def.name.rchop struct_field_ptr(type, name, call_args[0]) @@ -848,8 +851,8 @@ class Crystal::CodeGenVisitor original_call_arg end - def union_field_ptr(field_type, pointer) - ptr = aggregate_index pointer, 0 + def union_field_ptr(union_type, field_type, pointer) + ptr = aggregate_index llvm_type(union_type), pointer, 0 if field_type.is_a?(ProcInstanceType) bit_cast ptr, @llvm_typer.proc_type(field_type).pointer else @@ -912,7 +915,7 @@ class Crystal::CodeGenVisitor end def codegen_primitive_symbol_to_s(node, target_def, call_args) - load(gep @llvm_mod.globals[SYMBOL_TABLE_NAME], int(0), call_args[0]) + load(gep @llvm_typer.llvm_type(@program.string).array(@symbol_table_values.size), @llvm_mod.globals[SYMBOL_TABLE_NAME], int(0), call_args[0]) end def codegen_primitive_class(node, target_def, call_args) @@ -1065,7 +1068,7 @@ class Crystal::CodeGenVisitor abi_arg_type = abi_info.arg_types[index] case abi_arg_type.kind in .direct? - call_arg = codegen_direct_abi_call(call_arg, abi_arg_type) + call_arg = codegen_direct_abi_call(arg.type, call_arg, abi_arg_type) if cast = abi_arg_type.cast null_fun_types << cast else @@ -1089,10 +1092,13 @@ class Crystal::CodeGenVisitor end def codegen_primitive_pointer_diff(node, target_def, call_args) + type = context.type.as(PointerInstanceType) p0 = ptr2int(call_args[0], llvm_context.int64) p1 = ptr2int(call_args[1], llvm_context.int64) sub = builder.sub p0, p1 - builder.exact_sdiv sub, ptr2int(gep(call_args[0].type.null_pointer, 1), llvm_context.int64) + # `llvm_embedded_type` needed to treat `Void*` like `UInt8*` + offsetted = gep(llvm_embedded_type(type.element_type), call_args[0].type.null_pointer, 1) + builder.exact_sdiv sub, ptr2int(offsetted, llvm_context.int64) end def codegen_primitive_tuple_indexer_known_index(node, target_def, call_args) @@ -1103,9 +1109,10 @@ class Crystal::CodeGenVisitor def codegen_tuple_indexer(type, value, index : Range) case type when TupleInstanceType + struct_type = llvm_type(type) tuple_types = type.tuple_types[index].map &.as(Type) allocate_tuple(@program.tuple_of(tuple_types).as(TupleInstanceType)) do |tuple_type, i| - ptr = aggregate_index value, index.begin + i + ptr = aggregate_index struct_type, value, index.begin + i tuple_value = to_lhs ptr, tuple_type {tuple_type, tuple_value} end @@ -1123,10 +1130,10 @@ class Crystal::CodeGenVisitor def codegen_tuple_indexer(type, value, index : Int32) case type when TupleInstanceType - ptr = aggregate_index value, index + ptr = aggregate_index llvm_type(type), value, index to_lhs ptr, type.tuple_types[index] when NamedTupleInstanceType - ptr = aggregate_index value, index + ptr = aggregate_index llvm_type(type), value, index to_lhs ptr, type.entries[index].type else type = type.instance_type @@ -1157,9 +1164,11 @@ class Crystal::CodeGenVisitor failure_ordering = atomic_ordering_get_const(call.args[-1], failure_ordering) value = builder.cmpxchg(ptr, cmp, new, success_ordering, failure_ordering) - value_ptr = alloca llvm_type(node.type) - store extract_value(value, 0), gep(value_ptr, 0, 0) - store extract_value(value, 1), gep(value_ptr, 0, 1) + value_type = node.type.as(TupleInstanceType) + struct_type = llvm_type(value_type) + value_ptr = alloca struct_type + store extract_value(value, 0), gep(struct_type, value_ptr, 0, 0) + store extract_value(value, 1), gep(struct_type, value_ptr, 0, 1) value_ptr end diff --git a/src/compiler/crystal/codegen/unions.cr b/src/compiler/crystal/codegen/unions.cr index 628f5e549d2c..9115cb6a787c 100644 --- a/src/compiler/crystal/codegen/unions.cr +++ b/src/compiler/crystal/codegen/unions.cr @@ -58,48 +58,52 @@ module Crystal end def union_type_and_value_pointer(union_pointer, type : MixedUnionType) - {load(union_type_id(union_pointer)), union_value(union_pointer)} + struct_type = llvm_type(type) + {load(union_type_id(struct_type, union_pointer)), union_value(struct_type, union_pointer)} end - def union_type_id(union_pointer) - aggregate_index union_pointer, 0 + def union_type_id(struct_type, union_pointer) + aggregate_index struct_type, union_pointer, 0 end - def union_value(union_pointer) - aggregate_index union_pointer, 1 + def union_value(struct_type, union_pointer) + aggregate_index struct_type, union_pointer, 1 end def store_in_union(union_type, union_pointer, value_type, value) - store type_id(value, value_type), union_type_id(union_pointer) - casted_value_ptr = cast_to_pointer(union_value(union_pointer), value_type) + struct_type = llvm_type(union_type) + store type_id(value, value_type), union_type_id(struct_type, union_pointer) + casted_value_ptr = cast_to_pointer(union_value(struct_type, union_pointer), value_type) store value, casted_value_ptr end - def store_bool_in_union(union_type, union_pointer, value) - store type_id(value, @program.bool), union_type_id(union_pointer) + def store_bool_in_union(target_type, union_pointer, value) + struct_type = llvm_type(target_type) + store type_id(value, @program.bool), union_type_id(struct_type, union_pointer) # To store a boolean in a union # we sign-extend it to the size in bits of the union - union_value_type = @llvm_typer.union_value_type(union_type) - union_size = @llvm_typer.size_of(union_value_type) + union_size = @llvm_typer.size_of(struct_type.struct_element_types[1]) int_type = llvm_context.int((union_size * 8).to_i32) bool_as_extended_int = builder.zext(value, int_type) - casted_value_ptr = bit_cast(union_value(union_pointer), int_type.pointer) + casted_value_ptr = bit_cast(union_value(struct_type, union_pointer), int_type.pointer) store bool_as_extended_int, casted_value_ptr end - def store_nil_in_union(union_pointer, target_type) - union_value_type = @llvm_typer.union_value_type(target_type) + def store_nil_in_union(target_type, union_pointer) + struct_type = llvm_type(target_type) + union_value_type = struct_type.struct_element_types[1] value = union_value_type.null - store type_id(value, @program.nil), union_type_id(union_pointer) - casted_value_ptr = bit_cast union_value(union_pointer), union_value_type.pointer + store type_id(value, @program.nil), union_type_id(struct_type, union_pointer) + casted_value_ptr = bit_cast union_value(struct_type, union_pointer), union_value_type.pointer store value, casted_value_ptr end - def store_void_in_union(union_pointer, target_type) - store type_id(@program.void), union_type_id(union_pointer) + def store_void_in_union(target_type, union_pointer) + struct_type = llvm_type(target_type) + store type_id(@program.void), union_type_id(struct_type, union_pointer) end def assign_distinct_union_types(target_pointer, target_type, value_type, value) diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index 454d126b34c9..fc04c9b58ba0 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -106,26 +106,72 @@ class LLVM::Builder end {% for method_name in %w(gep inbounds_gep) %} - def {{method_name.id}}(value, indices : Array(LLVM::ValueRef), name = "") + def {{method_name.id}}(value : LLVM::Value, indices : Array(LLVM::ValueRef), name = "") # check_value(value) - Value.new LibLLVM.build_{{method_name.id}}(self, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size, name) + \{% if LibLLVM::IS_LT_80 %} + Value.new LibLLVM.build_{{method_name.id}}(self, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size, name) + \{% else %} + Value.new LibLLVM.build_{{method_name.id}}2(self, value.type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size, name) + \{% end %} end - def {{method_name.id}}(value, index : LLVM::Value, name = "") + def {{method_name.id}}(type : LLVM::Type, value : LLVM::Value, indices : Array(LLVM::ValueRef), name = "") + # check_value(value) + + \{% if LibLLVM::IS_LT_80 %} + Value.new LibLLVM.build_{{method_name.id}}(self, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size, name) + \{% else %} + Value.new LibLLVM.build_{{method_name.id}}2(self, type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size, name) + \{% end %} + end + + def {{method_name.id}}(value : LLVM::Value, index : LLVM::Value, name = "") + # check_value(value) + + indices = pointerof(index).as(LibLLVM::ValueRef*) + \{% if LibLLVM::IS_LT_80 %} + Value.new LibLLVM.build_{{method_name.id}}(self, value, indices, 1, name) + \{% else %} + Value.new LibLLVM.build_{{method_name.id}}2(self, value.type, value, indices, 1, name) + \{% end %} + end + + def {{method_name.id}}(type : LLVM::Type, value : LLVM::Value, index : LLVM::Value, name = "") # check_value(value) indices = pointerof(index).as(LibLLVM::ValueRef*) - Value.new LibLLVM.build_{{method_name.id}}(self, value, indices, 1, name) + \{% if LibLLVM::IS_LT_80 %} + Value.new LibLLVM.build_{{method_name.id}}(self, value, indices, 1, name) + \{% else %} + Value.new LibLLVM.build_{{method_name.id}}2(self, type, value, indices, 1, name) + \{% end %} + end + + def {{method_name.id}}(value : LLVM::Value, index1 : LLVM::Value, index2 : LLVM::Value, name = "") + # check_value(value) + + indices = uninitialized LLVM::Value[2] + indices[0] = index1 + indices[1] = index2 + \{% if LibLLVM::IS_LT_80 %} + Value.new LibLLVM.build_{{method_name.id}}(self, value, indices.to_unsafe.as(LibLLVM::ValueRef*), 2, name) + \{% else %} + Value.new LibLLVM.build_{{method_name.id}}2(self, value.type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), 2, name) + \{% end %} end - def {{method_name.id}}(value, index1 : LLVM::Value, index2 : LLVM::Value, name = "") + def {{method_name.id}}(type : LLVM::Type, value : LLVM::Value, index1 : LLVM::Value, index2 : LLVM::Value, name = "") # check_value(value) indices = uninitialized LLVM::Value[2] indices[0] = index1 indices[1] = index2 - Value.new LibLLVM.build_{{method_name.id}}(self, value, indices.to_unsafe.as(LibLLVM::ValueRef*), 2, name) + \{% if LibLLVM::IS_LT_80 %} + Value.new LibLLVM.build_{{method_name.id}}(self, value, indices.to_unsafe.as(LibLLVM::ValueRef*), 2, name) + \{% else %} + Value.new LibLLVM.build_{{method_name.id}}2(self, type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), 2, name) + \{% end %} end {% end %} diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 7c324cbd8339..5164cc28fd11 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -131,6 +131,10 @@ lib LibLLVM fun build_fsub = LLVMBuildFSub(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef fun build_gep = LLVMBuildGEP(builder : BuilderRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef fun build_inbounds_gep = LLVMBuildInBoundsGEP(builder : BuilderRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef + {% unless LibLLVM::IS_LT_80 %} + fun build_gep2 = LLVMBuildGEP2(builder : BuilderRef, ty : TypeRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef + fun build_inbounds_gep2 = LLVMBuildInBoundsGEP2(builder : BuilderRef, ty : TypeRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef + {% end %} fun build_global_string_ptr = LLVMBuildGlobalStringPtr(builder : BuilderRef, str : UInt8*, name : UInt8*) : ValueRef fun build_icmp = LLVMBuildICmp(builder : BuilderRef, op : LLVM::IntPredicate, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef fun build_int2ptr = LLVMBuildIntToPtr(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef From 72acd8fbc79ff4f555180aad45e98e31ace731c2 Mon Sep 17 00:00:00 2001 From: stellarpower Date: Mon, 16 Jan 2023 18:39:48 +0000 Subject: [PATCH 0248/1551] Missing quotes in Wrapper Script (#12955) Co-authored-by: Julien Reichardt Co-authored-by: Oleh Prypin --- bin/crystal | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/crystal b/bin/crystal index a3f193983a61..a458b9bdb54c 100755 --- a/bin/crystal +++ b/bin/crystal @@ -99,9 +99,8 @@ remove_path_item() { local path item path="$1" - item="$2" - printf "%s" "$path" | awk -v RS=: -v ORS=: '$0 != "'$item'"' | sed 's/:$//' + printf "%s" "$path" | awk -v item="$2" -v RS=: -v ORS=: '$0 != item' | sed 's/:$//' } ############################################################################## From 62b0adecc4cdfb4c84632a8f329d48983da97c6c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 17 Jan 2023 02:40:05 +0800 Subject: [PATCH 0249/1551] Macro interpolation: add `&` to yielding `Def`s without a block parameter (#12952) --- spec/compiler/normalize/def_spec.cr | 8 ++++---- spec/compiler/parser/to_s_spec.cr | 4 ++++ spec/compiler/semantic/restrictions_augmenter_spec.cr | 2 +- src/compiler/crystal/syntax/to_s.cr | 6 ++++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/spec/compiler/normalize/def_spec.cr b/spec/compiler/normalize/def_spec.cr index 23e8fd112656..dfc4a18f7c23 100644 --- a/spec/compiler/normalize/def_spec.cr +++ b/spec/compiler/normalize/def_spec.cr @@ -98,9 +98,9 @@ module Crystal end it "expands with named argument and yield" do - a_def = parse("def foo(x = 1, y = 2); yield x + y; end").as(Def) + a_def = parse("def foo(x = 1, y = 2, &); yield x + y; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 0, ["y"]) - actual.to_s.should eq("def foo:y(y)\n x = 1\n yield x + y\nend") + actual.to_s.should eq("def foo:y(y, &)\n x = 1\n yield x + y\nend") end # Small optimizations: no need to create a separate def in these cases @@ -147,9 +147,9 @@ module Crystal end it "expands with magic constant with named arg with yield" do - a_def = parse("def foo(x, file = __FILE__, line = __LINE__); yield x, file, line; end").as(Def) + a_def = parse("def foo(x, file = __FILE__, line = __LINE__, &); yield x, file, line; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 1, ["line"]) - other_def.to_s.should eq("def foo:line(x, line, file = __FILE__)\n yield x, file, line\nend") + other_def.to_s.should eq("def foo:line(x, line, file = __FILE__, &)\n yield x, file, line\nend") end it "expands a def with double splat and no args" do diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index aaef2b232c1b..849ad1e12b1e 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -219,4 +219,8 @@ describe "ASTNode#to_s" do expect_to_s "->::foo(Int32, String)" expect_to_s "->::Foo::Bar.foo" expect_to_s "yield(1)" + expect_to_s "def foo\n yield\nend", "def foo(&)\n yield\nend" + expect_to_s "def foo(x)\n yield\nend", "def foo(x, &)\n yield\nend" + expect_to_s "def foo(**x)\n yield\nend", "def foo(**x, &)\n yield\nend" + expect_to_s "macro foo(x)\n yield\nend" end diff --git a/spec/compiler/semantic/restrictions_augmenter_spec.cr b/spec/compiler/semantic/restrictions_augmenter_spec.cr index 7d55c7cf1258..b3798dc257c0 100644 --- a/spec/compiler/semantic/restrictions_augmenter_spec.cr +++ b/spec/compiler/semantic/restrictions_augmenter_spec.cr @@ -222,7 +222,7 @@ describe "Semantic: restrictions augmenter" do it "doesn't augment if assigned inside block" do expect_no_augment <<-CRYSTAL - def foo + def foo(&) yield end class Foo diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index c6cd759d57dd..d3589475f439 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -614,7 +614,7 @@ module Crystal @str << '.' end @str << node.name - if node.args.size > 0 || node.block_arg || node.double_splat + if node.args.size > 0 || node.block_arity || node.double_splat @str << '(' printed_arg = false node.args.each_with_index do |arg, i| @@ -633,7 +633,9 @@ module Crystal @current_arg_type = :block_arg @str << ", " if printed_arg block_arg.accept self - printed_arg = true + elsif node.block_arity + @str << ", " if printed_arg + @str << '&' end @str << ')' end From d772c722c5dc3d6ef3443a84cb4084c054f2d8dd Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 17 Jan 2023 14:58:24 +0100 Subject: [PATCH 0250/1551] Added so char can be converted to int128 (#12958) Resolves https://github.com/crystal-lang/crystal/issues/12957 --- spec/std/char_spec.cr | 6 ++++++ src/char.cr | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/std/char_spec.cr b/spec/std/char_spec.cr index 1327f999191f..d60133766bb4 100644 --- a/spec/std/char_spec.cr +++ b/spec/std/char_spec.cr @@ -237,31 +237,37 @@ describe "Char" do '1'.to_i16.should eq(1i16) '1'.to_i32.should eq(1i32) '1'.to_i64.should eq(1i64) + '1'.to_i128.should eq(1i128) expect_raises(ArgumentError) { 'a'.to_i8 } expect_raises(ArgumentError) { 'a'.to_i16 } expect_raises(ArgumentError) { 'a'.to_i32 } expect_raises(ArgumentError) { 'a'.to_i64 } + expect_raises(ArgumentError) { 'a'.to_i128 } 'a'.to_i8?.should be_nil 'a'.to_i16?.should be_nil 'a'.to_i32?.should be_nil 'a'.to_i64?.should be_nil + 'a'.to_i128?.should be_nil '1'.to_u8.should eq(1u8) '1'.to_u16.should eq(1u16) '1'.to_u32.should eq(1u32) '1'.to_u64.should eq(1u64) + '1'.to_u128.should eq(1u128) expect_raises(ArgumentError) { 'a'.to_u8 } expect_raises(ArgumentError) { 'a'.to_u16 } expect_raises(ArgumentError) { 'a'.to_u32 } expect_raises(ArgumentError) { 'a'.to_u64 } + expect_raises(ArgumentError) { 'a'.to_u128 } 'a'.to_u8?.should be_nil 'a'.to_u16?.should be_nil 'a'.to_u32?.should be_nil 'a'.to_u64?.should be_nil + 'a'.to_u128?.should be_nil end it "does to_i with 16 base" do diff --git a/src/char.cr b/src/char.cr index f64b247efd9c..9e7b791bad2a 100644 --- a/src/char.cr +++ b/src/char.cr @@ -722,7 +722,7 @@ struct Char to_i?(base) end - {% for type in %w(i8 i16 i64 u8 u16 u32 u64) %} + {% for type in %w(i8 i16 i64 i128 u8 u16 u32 u64 u128) %} # See also: `to_i`. def to_{{type.id}}(base : Int = 10) to_i(base).to_{{type.id}} From 643c8e5d850e301285c1449e16d024987df98601 Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Tue, 17 Jan 2023 13:12:40 -0300 Subject: [PATCH 0251/1551] Preparing release 1.7.1 (#12968) --- CHANGELOG.md | 12 ++++++++++++ src/VERSION | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03d5f9685437..3266c2c4d2bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 1.7.1 (2023-01-17) + +## Tools + +### Playground + +- Fix baked-in path in playground to resolve at runtime ([#12948](https://github.com/crystal-lang/crystal/pull/12948), thanks @straight-shoota) + +## Other + +- Update `VERSION` to 1.7.1-dev ([#12950](https://github.com/crystal-lang/crystal/pull/12950), thanks @straight-shoota) + # 1.7.0 (2023-01-09) ## Language diff --git a/src/VERSION b/src/VERSION index 896d75a90354..943f9cbc4ec7 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.7.1-dev +1.7.1 From 29dad1aa3148c80c06f55391a0064636aadb52ad Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 18 Jan 2023 19:52:29 +0800 Subject: [PATCH 0252/1551] Escape filenames when running `crystal spec` with multiple files (#12929) --- src/compiler/crystal/command/spec.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/crystal/command/spec.cr b/src/compiler/crystal/command/spec.cr index ab5aaad9caa3..613d13cb9a63 100644 --- a/src/compiler/crystal/command/spec.cr +++ b/src/compiler/crystal/command/spec.cr @@ -87,9 +87,9 @@ class Crystal::Command source_filename = File.expand_path("spec") - source = target_filenames.map { |filename| - %(require "./#{::Path[filename].relative_to(Dir.current).to_posix}") - }.join('\n') + source = target_filenames.join('\n') do |filename| + %(require "./#{::Path[filename].relative_to(Dir.current).to_posix.to_s.inspect_unquoted}") + end sources = [Compiler::Source.new(source_filename, source)] output_filename = Crystal.temp_executable "spec" From 84977e48c784811e885f084ae0270c2dd47f14e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 18 Jan 2023 12:52:53 +0100 Subject: [PATCH 0253/1551] Fill docs for `TupleLiteral` (#12927) Co-authored-by: Sijawusz Pur Rahnama --- src/compiler/crystal/macros.cr | 125 +++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index ae2a2e73c25a..8b5c6845c0b2 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -887,6 +887,131 @@ module Crystal::Macros # # Its macro methods are nearly the same as `ArrayLiteral`. class TupleLiteral < ASTNode + # Similar to `Enumerable#any?` + def any?(&) : BoolLiteral + end + + # Similar to `Enumerable#all?` + def all?(&) : BoolLiteral + end + + # Returns a `MacroId` with all of this tuple's elements joined + # by commas. + # + # If *trailing_string* is given, it will be appended to + # the result unless this tuple is empty. This lets you + # splat a tuple and optionally write a trailing comma + # if needed. + def splat(trailing_string : StringLiteral = nil) : MacroId + end + + # Similar to `Tuple#empty?` + def empty? : BoolLiteral + end + + # Similar to `Enumerable#find` + def find(&) : ASTNode | NilLiteral + end + + # Similar to `Tuple#first`, but returns a `NilLiteral` if the tuple is empty. + def first : ASTNode | NilLiteral + end + + # Similar to `Enumerable#includes?(obj)`. + def includes?(node : ASTNode) : BoolLiteral + end + + # Similar to `Enumerable#join` + def join(separator) : StringLiteral + end + + # Similar to `Tuple#last`, but returns a `NilLiteral` if the tuple is empty. + def last : ASTNode | NilLiteral + end + + # Similar to `Tuple#size` + def size : NumberLiteral + end + + # Similar to `Enumerable#map` + def map(&) : TupleLiteral + end + + # Similar to `Enumerable#map_with_index` + def map_with_index(&) : TupleLiteral + end + + # Similar to `Tuple#each` + def each(&) : Nil + end + + # Similar to `Enumerable#each_with_index` + def each_with_index(&) : Nil + end + + # Similar to `Enumerable#select` + def select(&) : TupleLiteral + end + + # Similar to `Enumerable#reject` + def reject(&) : TupleLiteral + end + + # Similar to `Enumerable#reduce` + def reduce(&) : ASTNode + end + + # Similar to `Enumerable#reduce` + def reduce(memo : ASTNode, &) : ASTNode + end + + # Similar to `Array#shuffle` + def shuffle : TupleLiteral + end + + # Similar to `Array#sort` + def sort : TupleLiteral + end + + # Similar to `Array#sort_by` + def sort_by(&) : TupleLiteral + end + + # Similar to `Array#uniq` + def uniq : TupleLiteral + end + + # Similar to `Tuple#[]`, but returns `NilLiteral` on out of bounds. + def [](index : NumberLiteral) : ASTNode + end + + # Similar to `Tuple#[]`. + def [](index : RangeLiteral) : TupleLiteral(ASTNode) + end + + # Similar to `Array#[]=`. + def []=(index : NumberLiteral, value : ASTNode) : ASTNode + end + + # Similar to `Array#unshift`. + def unshift(value : ASTNode) : TupleLiteral + end + + # Similar to `Array#push`. + def push(value : ASTNode) : TupleLiteral + end + + # Similar to `Array#<<`. + def <<(value : ASTNode) : TupleLiteral + end + + # Similar to `Tuple#+`. + def +(other : TupleLiteral) : TupleLiteral + end + + # Similar to `Array#-`. + def -(other : TupleLiteral) : TupleLiteral + end end # A fictitious node representing a variable or instance From 8cc61e48afa429a263ea4ae259e7daf22b92f12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 19 Jan 2023 13:06:48 +0100 Subject: [PATCH 0254/1551] Drop support for LLVM < 8 (#12906) --- src/compiler/crystal/codegen/codegen.cr | 38 +-- src/compiler/crystal/codegen/fun.cr | 7 +- .../crystal/interpreter/instructions.cr | 222 ++++++------------ src/intrinsics.cr | 166 ++++--------- src/llvm.cr | 10 +- src/llvm/basic_block.cr | 2 +- src/llvm/builder.cr | 12 +- src/llvm/context.cr | 14 +- src/llvm/ext/llvm-versions.txt | 2 +- src/llvm/ext/llvm_ext.cc | 155 +----------- src/llvm/lib_llvm.cr | 84 ++----- src/llvm/lib_llvm_ext.cr | 27 +-- src/llvm/module.cr | 70 ++---- src/llvm/target_machine.cr | 7 +- src/llvm/type.cr | 10 +- 15 files changed, 188 insertions(+), 638 deletions(-) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 371df0592767..284869d54c3f 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -2142,32 +2142,18 @@ module Crystal len_arg = @program.bits64? ? size : trunc(size, llvm_context.int32) pointer = cast_to_void_pointer pointer - res = call @program.memset(@llvm_mod, llvm_context), - if LibLLVM::IS_LT_70 - [pointer, value, len_arg, int32(4), int1(0)] - else - [pointer, value, len_arg, int1(0)] - end + res = call @program.memset(@llvm_mod, llvm_context), [pointer, value, len_arg, int1(0)] - unless LibLLVM::IS_LT_70 - LibLLVM.set_instr_param_alignment(res, 1, 4) - end + LibLLVM.set_instr_param_alignment(res, 1, 4) res end def memcpy(dest, src, len, align, volatile) - res = call @program.memcpy(@llvm_mod, llvm_context), - if LibLLVM::IS_LT_70 - [dest, src, len, int32(align), volatile] - else - [dest, src, len, volatile] - end + res = call @program.memcpy(@llvm_mod, llvm_context), [dest, src, len, volatile] - unless LibLLVM::IS_LT_70 - LibLLVM.set_instr_param_alignment(res, 1, align) - LibLLVM.set_instr_param_alignment(res, 2, align) - end + LibLLVM.set_instr_param_alignment(res, 1, align) + LibLLVM.set_instr_param_alignment(res, 2, align) res end @@ -2312,12 +2298,7 @@ module Crystal len_type = bits64? ? llvm_context.int64 : llvm_context.int32 llvm_mod.functions[name]? || begin - arg_types = - if LibLLVM::IS_LT_70 - [llvm_context.void_pointer, llvm_context.int8, len_type, llvm_context.int32, llvm_context.int1] - else - [llvm_context.void_pointer, llvm_context.int8, len_type, llvm_context.int1] - end + arg_types = [llvm_context.void_pointer, llvm_context.int8, len_type, llvm_context.int1] llvm_mod.functions.add(name, arg_types, llvm_context.void) end @@ -2328,12 +2309,7 @@ module Crystal len_type = bits64? ? llvm_context.int64 : llvm_context.int32 llvm_mod.functions[name]? || begin - arg_types = - if LibLLVM::IS_LT_70 - [llvm_context.void_pointer, llvm_context.void_pointer, len_type, llvm_context.int32, llvm_context.int1] - else - [llvm_context.void_pointer, llvm_context.void_pointer, len_type, llvm_context.int1] - end + arg_types = [llvm_context.void_pointer, llvm_context.void_pointer, len_type, llvm_context.int1] llvm_mod.functions.add(name, arg_types, llvm_context.void) end diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 03310b305ad3..a41a980c01b2 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -87,12 +87,7 @@ class Crystal::CodeGenVisitor context.fun.add_attribute LLVM::Attribute::UWTable if @program.has_flag?("darwin") # Disable frame pointer elimination in Darwin, as it causes issues during stack unwind - {% if compare_versions(Crystal::LLVM_VERSION, "8.0.0") < 0 %} - context.fun.add_target_dependent_attribute "no-frame-pointer-elim", "true" - context.fun.add_target_dependent_attribute "no-frame-pointer-elim-non-leaf", "true" - {% else %} - context.fun.add_target_dependent_attribute "frame-pointer", "all" - {% end %} + context.fun.add_target_dependent_attribute "frame-pointer", "all" end new_entry_block diff --git a/src/compiler/crystal/interpreter/instructions.cr b/src/compiler/crystal/interpreter/instructions.cr index 66d5296bdba3..2c55cfe255df 100644 --- a/src/compiler/crystal/interpreter/instructions.cr +++ b/src/compiler/crystal/interpreter/instructions.cr @@ -1651,163 +1651,73 @@ require "./repl" }, {% if flag?(:bits64) %} - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - interpreter_intrinsics_memcpy: { - pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt64, align : UInt32, is_volatile : Bool], - code: begin - # In the std, align is always set to 0. Let's worry about this if really needed. - raise "BUG: memcpy with align != 0 is not supported" if align != 0 - - # This is a pretty weird `if`, but the `memcpy` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memcpy(dest, src, len, 0, true) - else - LibIntrinsics.memcpy(dest, src, len, 0, false) - end - end, - }, - interpreter_intrinsics_memmove: { - pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt64, align : UInt32, is_volatile : Bool], - code: begin - # In the std, align is always set to 0. Let's worry about this if really needed. - raise "BUG: memcpy with align != 0 is not supported" if align != 0 - - # This is a pretty weird `if`, but the `memmove` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memmove(dest, src, len, 0, true) - else - LibIntrinsics.memmove(dest, src, len, 0, false) - end - end, - }, - interpreter_intrinsics_memset: { - pop_values: [dest : Pointer(Void), val : UInt8, len : UInt64, align : UInt32, is_volatile : Bool], - code: begin - # In the std, align is always set to 0. Let's worry about this if really needed. - raise "BUG: memcpy with align != 0 is not supported" if align != 0 - - # This is a pretty weird `if`, but the `memset` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memset(dest, val, len, 0, true) - else - LibIntrinsics.memset(dest, val, len, 0, false) - end - end, - }, - {% else %} - interpreter_intrinsics_memcpy: { - pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt64, is_volatile : Bool], - code: begin - # This is a pretty weird `if`, but the `memcpy` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memcpy(dest, src, len, true) - else - LibIntrinsics.memcpy(dest, src, len, false) - end - end, - }, - interpreter_intrinsics_memmove: { - pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt64, is_volatile : Bool], - code: begin - # This is a pretty weird `if`, but the `memmove` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memmove(dest, src, len, true) - else - LibIntrinsics.memmove(dest, src, len, false) - end - end, - }, - interpreter_intrinsics_memset: { - pop_values: [dest : Pointer(Void), val : UInt8, len : UInt64, is_volatile : Bool], - code: begin - # This is a pretty weird `if`, but the `memset` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memset(dest, val, len, true) - else - LibIntrinsics.memset(dest, val, len, false) - end - end, - }, - {% end %} + interpreter_intrinsics_memcpy: { + pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt64, is_volatile : Bool], + code: begin + # This is a pretty weird `if`, but the `memcpy` intrinsic requires the last argument to be a constant + if is_volatile + LibIntrinsics.memcpy(dest, src, len, true) + else + LibIntrinsics.memcpy(dest, src, len, false) + end + end, + }, + interpreter_intrinsics_memmove: { + pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt64, is_volatile : Bool], + code: begin + # This is a pretty weird `if`, but the `memmove` intrinsic requires the last argument to be a constant + if is_volatile + LibIntrinsics.memmove(dest, src, len, true) + else + LibIntrinsics.memmove(dest, src, len, false) + end + end, + }, + interpreter_intrinsics_memset: { + pop_values: [dest : Pointer(Void), val : UInt8, len : UInt64, is_volatile : Bool], + code: begin + # This is a pretty weird `if`, but the `memset` intrinsic requires the last argument to be a constant + if is_volatile + LibIntrinsics.memset(dest, val, len, true) + else + LibIntrinsics.memset(dest, val, len, false) + end + end, + }, {% else %} - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - interpreter_intrinsics_memcpy: { - pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt32, align : UInt32, is_volatile : Bool], - code: begin - # In the std, align is always set to 0. Let's worry about this if really needed. - raise "BUG: memcpy with align != 0 is not supported" if align != 0 - - # This is a pretty weird `if`, but the `memcpy` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memcpy(dest, src, len, 0, true) - else - LibIntrinsics.memcpy(dest, src, len, 0, false) - end - end, - }, - interpreter_intrinsics_memmove: { - pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt32, align : UInt32, is_volatile : Bool], - code: begin - # In the std, align is always set to 0. Let's worry about this if really needed. - raise "BUG: memcpy with align != 0 is not supported" if align != 0 - - # This is a pretty weird `if`, but the `memmove` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memmove(dest, src, len, 0, true) - else - LibIntrinsics.memmove(dest, src, len, 0, false) - end - end, - }, - interpreter_intrinsics_memset: { - pop_values: [dest : Pointer(Void), val : UInt8, len : UInt32, align : UInt32, is_volatile : Bool], - code: begin - # In the std, align is always set to 0. Let's worry about this if really needed. - raise "BUG: memcpy with align != 0 is not supported" if align != 0 - - # This is a pretty weird `if`, but the `memset` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memset(dest, val, len, 0, true) - else - LibIntrinsics.memset(dest, val, len, 0, false) - end - end, - }, - {% else %} - interpreter_intrinsics_memcpy: { - pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt32, is_volatile : Bool], - code: begin - # This is a pretty weird `if`, but the `memcpy` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memcpy(dest, src, len, true) - else - LibIntrinsics.memcpy(dest, src, len, false) - end - end, - }, - interpreter_intrinsics_memmove: { - pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt32, is_volatile : Bool], - code: begin - # This is a pretty weird `if`, but the `memmove` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memmove(dest, src, len, true) - else - LibIntrinsics.memmove(dest, src, len, false) - end - end, - }, - interpreter_intrinsics_memset: { - pop_values: [dest : Pointer(Void), val : UInt8, len : UInt32, is_volatile : Bool], - code: begin - # This is a pretty weird `if`, but the `memset` intrinsic requires the last argument to be a constant - if is_volatile - LibIntrinsics.memset(dest, val, len, true) - else - LibIntrinsics.memset(dest, val, len, false) - end - end, - }, - {% end %} + interpreter_intrinsics_memcpy: { + pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt32, is_volatile : Bool], + code: begin + # This is a pretty weird `if`, but the `memcpy` intrinsic requires the last argument to be a constant + if is_volatile + LibIntrinsics.memcpy(dest, src, len, true) + else + LibIntrinsics.memcpy(dest, src, len, false) + end + end, + }, + interpreter_intrinsics_memmove: { + pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt32, is_volatile : Bool], + code: begin + # This is a pretty weird `if`, but the `memmove` intrinsic requires the last argument to be a constant + if is_volatile + LibIntrinsics.memmove(dest, src, len, true) + else + LibIntrinsics.memmove(dest, src, len, false) + end + end, + }, + interpreter_intrinsics_memset: { + pop_values: [dest : Pointer(Void), val : UInt8, len : UInt32, is_volatile : Bool], + code: begin + # This is a pretty weird `if`, but the `memset` intrinsic requires the last argument to be a constant + if is_volatile + LibIntrinsics.memset(dest, val, len, true) + else + LibIntrinsics.memset(dest, val, len, false) + end + end, + }, {% end %} interpreter_intrinsics_debugtrap: { diff --git a/src/intrinsics.cr b/src/intrinsics.cr index 1bbbc8c3330b..f9c4302f7d0d 100644 --- a/src/intrinsics.cr +++ b/src/intrinsics.cr @@ -5,45 +5,23 @@ lib LibIntrinsics fun debugtrap = "llvm.debugtrap" {% if flag?(:bits64) %} - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} - fun memcpy = "llvm.memcpy.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, align : UInt32, is_volatile : Bool) + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} + fun memcpy = "llvm.memcpy.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} - fun memmove = "llvm.memmove.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, align : UInt32, is_volatile : Bool) + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} + fun memmove = "llvm.memmove.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} - fun memset = "llvm.memset.p0i8.i64"(dest : Void*, val : UInt8, len : UInt64, align : UInt32, is_volatile : Bool) - {% else %} - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} - fun memcpy = "llvm.memcpy.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) - - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} - fun memmove = "llvm.memmove.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) - - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} - fun memset = "llvm.memset.p0i8.i64"(dest : Void*, val : UInt8, len : UInt64, is_volatile : Bool) - {% end %} + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} + fun memset = "llvm.memset.p0i8.i64"(dest : Void*, val : UInt8, len : UInt64, is_volatile : Bool) {% else %} - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} - fun memcpy = "llvm.memcpy.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, align : UInt32, is_volatile : Bool) - - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} - fun memmove = "llvm.memmove.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, align : UInt32, is_volatile : Bool) - - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} - fun memset = "llvm.memset.p0i8.i32"(dest : Void*, val : UInt8, len : UInt32, align : UInt32, is_volatile : Bool) - {% else %} - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} - fun memcpy = "llvm.memcpy.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} + fun memcpy = "llvm.memcpy.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} - fun memmove = "llvm.memmove.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} + fun memmove = "llvm.memmove.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} - fun memset = "llvm.memset.p0i8.i32"(dest : Void*, val : UInt8, len : UInt32, is_volatile : Bool) - {% end %} + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} + fun memset = "llvm.memset.p0i8.i32"(dest : Void*, val : UInt8, len : UInt32, is_volatile : Bool) {% end %} {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_read_cycle_counter)] {% end %} @@ -121,37 +99,35 @@ lib LibIntrinsics {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_counttrailing128)] {% end %} fun counttrailing128 = "llvm.cttz.i128"(src : Int128, zero_is_undef : Bool) : Int128 - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") >= 0 %} - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl8)] {% end %} - fun fshl8 = "llvm.fshl.i8"(a : UInt8, b : UInt8, count : UInt8) : UInt8 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl8)] {% end %} + fun fshl8 = "llvm.fshl.i8"(a : UInt8, b : UInt8, count : UInt8) : UInt8 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl16)] {% end %} - fun fshl16 = "llvm.fshl.i16"(a : UInt16, b : UInt16, count : UInt16) : UInt16 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl16)] {% end %} + fun fshl16 = "llvm.fshl.i16"(a : UInt16, b : UInt16, count : UInt16) : UInt16 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl32)] {% end %} - fun fshl32 = "llvm.fshl.i32"(a : UInt32, b : UInt32, count : UInt32) : UInt32 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl32)] {% end %} + fun fshl32 = "llvm.fshl.i32"(a : UInt32, b : UInt32, count : UInt32) : UInt32 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl64)] {% end %} - fun fshl64 = "llvm.fshl.i64"(a : UInt64, b : UInt64, count : UInt64) : UInt64 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl64)] {% end %} + fun fshl64 = "llvm.fshl.i64"(a : UInt64, b : UInt64, count : UInt64) : UInt64 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl128)] {% end %} - fun fshl128 = "llvm.fshl.i128"(a : UInt128, b : UInt128, count : UInt128) : UInt128 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl128)] {% end %} + fun fshl128 = "llvm.fshl.i128"(a : UInt128, b : UInt128, count : UInt128) : UInt128 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr8)] {% end %} - fun fshr8 = "llvm.fshr.i8"(a : UInt8, b : UInt8, count : UInt8) : UInt8 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr8)] {% end %} + fun fshr8 = "llvm.fshr.i8"(a : UInt8, b : UInt8, count : UInt8) : UInt8 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr16)] {% end %} - fun fshr16 = "llvm.fshr.i16"(a : UInt16, b : UInt16, count : UInt16) : UInt16 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr16)] {% end %} + fun fshr16 = "llvm.fshr.i16"(a : UInt16, b : UInt16, count : UInt16) : UInt16 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr32)] {% end %} - fun fshr32 = "llvm.fshr.i32"(a : UInt32, b : UInt32, count : UInt32) : UInt32 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr32)] {% end %} + fun fshr32 = "llvm.fshr.i32"(a : UInt32, b : UInt32, count : UInt32) : UInt32 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr64)] {% end %} - fun fshr64 = "llvm.fshr.i64"(a : UInt64, b : UInt64, count : UInt64) : UInt64 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr64)] {% end %} + fun fshr64 = "llvm.fshr.i64"(a : UInt64, b : UInt64, count : UInt64) : UInt64 - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr128)] {% end %} - fun fshr128 = "llvm.fshr.i128"(a : UInt128, b : UInt128, count : UInt128) : UInt128 - {% end %} + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr128)] {% end %} + fun fshr128 = "llvm.fshr.i128"(a : UInt128, b : UInt128, count : UInt128) : UInt128 fun va_start = "llvm.va_start"(ap : Void*) fun va_end = "llvm.va_end"(ap : Void*) @@ -181,27 +157,15 @@ module Intrinsics end macro memcpy(dest, src, len, is_volatile) - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, 0, {{is_volatile}}) - {% else %} - LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, {{is_volatile}}) - {% end %} + LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, {{is_volatile}}) end macro memmove(dest, src, len, is_volatile) - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, 0, {{is_volatile}}) - {% else %} - LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, {{is_volatile}}) - {% end %} + LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, {{is_volatile}}) end macro memset(dest, val, len, is_volatile) - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, 0, {{is_volatile}}) - {% else %} - LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, {{is_volatile}}) - {% end %} + LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, {{is_volatile}}) end def self.read_cycle_counter @@ -305,83 +269,43 @@ module Intrinsics end def self.fshl8(a, b, count) : UInt8 - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - a.unsafe_shl(count) | b.unsafe_shr((~count &+ 1) & 7) - {% else %} - LibIntrinsics.fshl8(a, b, count) - {% end %} + LibIntrinsics.fshl8(a, b, count) end def self.fshl16(a, b, count) : UInt16 - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - a.unsafe_shl(count) | b.unsafe_shr((~count &+ 1) & 15) - {% else %} - LibIntrinsics.fshl16(a, b, count) - {% end %} + LibIntrinsics.fshl16(a, b, count) end def self.fshl32(a, b, count) : UInt32 - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - a.unsafe_shl(count) | b.unsafe_shr((~count &+ 1) & 31) - {% else %} - LibIntrinsics.fshl32(a, b, count) - {% end %} + LibIntrinsics.fshl32(a, b, count) end def self.fshl64(a, b, count) : UInt64 - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - a.unsafe_shl(count) | b.unsafe_shr((~count &+ 1) & 63) - {% else %} - LibIntrinsics.fshl64(a, b, count) - {% end %} + LibIntrinsics.fshl64(a, b, count) end def self.fshl128(a, b, count) : UInt128 - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - a.unsafe_shl(count) | b.unsafe_shr((~count &+ 1) & 127) - {% else %} - LibIntrinsics.fshl128(a, b, count) - {% end %} + LibIntrinsics.fshl128(a, b, count) end def self.fshr8(a, b, count) : UInt8 - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - b.unsafe_shr(count) | a.unsafe_shl((~count &+ 1) & 7) - {% else %} - LibIntrinsics.fshr8(a, b, count) - {% end %} + LibIntrinsics.fshr8(a, b, count) end def self.fshr16(a, b, count) : UInt16 - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - b.unsafe_shr(count) | a.unsafe_shl((~count &+ 1) & 15) - {% else %} - LibIntrinsics.fshr16(a, b, count) - {% end %} + LibIntrinsics.fshr16(a, b, count) end def self.fshr32(a, b, count) : UInt32 - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - b.unsafe_shr(count) | a.unsafe_shl((~count &+ 1) & 31) - {% else %} - LibIntrinsics.fshr32(a, b, count) - {% end %} + LibIntrinsics.fshr32(a, b, count) end def self.fshr64(a, b, count) : UInt64 - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - b.unsafe_shr(count) | a.unsafe_shl((~count &+ 1) & 63) - {% else %} - LibIntrinsics.fshr64(a, b, count) - {% end %} + LibIntrinsics.fshr64(a, b, count) end def self.fshr128(a, b, count) : UInt128 - {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} - b.unsafe_shr(count) | a.unsafe_shl((~count &+ 1) & 127) - {% else %} - LibIntrinsics.fshr128(a, b, count) - {% end %} + LibIntrinsics.fshr128(a, b, count) end macro va_start(ap) diff --git a/src/llvm.cr b/src/llvm.cr index 5ca6cc566f74..37d5e0038dd9 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -101,21 +101,13 @@ module LLVM end def self.host_cpu_name : String - {% unless LibLLVM::IS_LT_70 %} - String.new LibLLVM.get_host_cpu_name - {% else %} - raise "LibLLVM.host_cpu_name requires LLVM 7.0 or newer" - {% end %} + String.new LibLLVM.get_host_cpu_name end def self.normalize_triple(triple : String) : String normalized = LibLLVMExt.normalize_target_triple(triple) normalized = LLVM.string_and_dispose(normalized) - # Fix LLVM not replacing empty triple parts with "unknown" - # This was fixed in LLVM 8 - normalized = normalized.split('-').map { |c| c.presence || "unknown" }.join('-') - normalized end diff --git a/src/llvm/basic_block.cr b/src/llvm/basic_block.cr index b34406fbb913..973f6e38483b 100644 --- a/src/llvm/basic_block.cr +++ b/src/llvm/basic_block.cr @@ -19,7 +19,7 @@ struct LLVM::BasicBlock end def name - block_name = LibLLVMExt.basic_block_name(self) + block_name = LibLLVM.get_basic_block_name(self) block_name ? LLVM.string_and_dispose(block_name) : nil end end diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index 454d126b34c9..ab743655619f 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -52,11 +52,7 @@ class LLVM::Builder def call(func, name : String = "") # check_func(func) - {% if LibLLVM::IS_LT_80 %} - Value.new LibLLVM.build_call(self, func, nil, 0, name) - {% else %} - Value.new LibLLVM.build_call2(self, func.function_type, func, nil, 0, name) - {% end %} + Value.new LibLLVM.build_call2(self, func.function_type, func, nil, 0, name) end def call(func, arg : LLVM::Value, name : String = "") @@ -64,11 +60,7 @@ class LLVM::Builder # check_value(arg) value = arg.to_unsafe - {% if LibLLVM::IS_LT_80 %} - Value.new LibLLVM.build_call(self, func, pointerof(value), 1, name) - {% else %} - Value.new LibLLVM.build_call2(self, func.function_type, func, pointerof(value), 1, name) - {% end %} + Value.new LibLLVM.build_call2(self, func.function_type, func, pointerof(value), 1, name) end def call(func, args : Array(LLVM::Value), name : String = "", bundle : LLVM::OperandBundleDef = LLVM::OperandBundleDef.null) diff --git a/src/llvm/context.cr b/src/llvm/context.cr index d349e3045bf0..a1c3ec87adfc 100644 --- a/src/llvm/context.cr +++ b/src/llvm/context.cr @@ -9,12 +9,7 @@ class LLVM::Context end def new_module(name : String) : Module - {% if LibLLVM::IS_38 %} - Module.new(LibLLVM.module_create_with_name_in_context(name, self), name, self) - {% else %} - # LLVM >= 3.9 - Module.new(LibLLVM.module_create_with_name_in_context(name, self), self) - {% end %} + Module.new(LibLLVM.module_create_with_name_in_context(name, self), self) end def new_builder : Builder @@ -105,12 +100,7 @@ class LLVM::Context if ret != 0 && msg raise LLVM.string_and_dispose(msg) end - {% if LibLLVM::IS_38 %} - Module.new(mod, "unknown", self) - {% else %} - # LLVM >= 3.9 - Module.new(mod, self) - {% end %} + Module.new(mod, self) end def ==(other : self) diff --git a/src/llvm/ext/llvm-versions.txt b/src/llvm/ext/llvm-versions.txt index 405fc1147bde..d8dbd4849ebe 100644 --- a/src/llvm/ext/llvm-versions.txt +++ b/src/llvm/ext/llvm-versions.txt @@ -1 +1 @@ -14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 7.1 6.0 5.0 4.0 3.9 3.8 +14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc index 18102ceb23b4..0f38b389f802 100644 --- a/src/llvm/ext/llvm_ext.cc +++ b/src/llvm/ext/llvm_ext.cc @@ -26,31 +26,10 @@ using namespace llvm; #define LLVM_VERSION_LE(major, minor) \ (LLVM_VERSION_MAJOR < (major) || LLVM_VERSION_MAJOR == (major) && LLVM_VERSION_MINOR <= (minor)) -#if LLVM_VERSION_GE(7, 0) #include -#else -#include -#endif - -#if LLVM_VERSION_GE(6, 0) #include -#endif - -#if LLVM_VERSION_GE(4, 0) #include #include -#endif - -#if LLVM_VERSION_LE(4, 0) -typedef struct LLVMOpaqueDIBuilder *LLVMDIBuilderRef; -DEFINE_SIMPLE_CONVERSION_FUNCTIONS(DIBuilder, LLVMDIBuilderRef) - -typedef struct LLVMOpaqueMetadata *LLVMMetadataRef; -DEFINE_ISA_CONVERSION_FUNCTIONS(Metadata, LLVMMetadataRef) -inline Metadata **unwrap(LLVMMetadataRef *Vals) { - return reinterpret_cast(Vals); -} -#endif typedef DIBuilder *DIBuilderRef; #define DIArray DINodeArray @@ -58,10 +37,6 @@ template T *unwrapDIptr(LLVMMetadataRef v) { return (T *)(v ? unwrap(v) : NULL); } -#if LLVM_VERSION_LE(3, 6) -#define OperandBundleDef void -#endif - #define DIDescriptor DIScope #define unwrapDI unwrapDIptr @@ -72,9 +47,6 @@ LLVMDIBuilderRef LLVMExtNewDIBuilder(LLVMModuleRef mref) { return wrap(new DIBuilder(*m)); } -// Missing LLVMDIBuilderFinalize in LLVM <= 5.0 -void LLVMExtDIBuilderFinalize(LLVMDIBuilderRef dref) { unwrap(dref)->finalize(); } - LLVMMetadataRef LLVMExtDIBuilderCreateFile( DIBuilderRef Dref, const char *File, const char *Dir) { return wrap(Dref->createFile(File, Dir)); @@ -84,14 +56,9 @@ LLVMMetadataRef LLVMExtDIBuilderCreateCompileUnit( DIBuilderRef Dref, unsigned Lang, const char *File, const char *Dir, const char *Producer, int Optimized, const char *Flags, unsigned RuntimeVersion) { -#if LLVM_VERSION_LE(3, 9) - return wrap(Dref->createCompileUnit(Lang, File, Dir, Producer, Optimized, - Flags, RuntimeVersion)); -#else DIFile *F = Dref->createFile(File, Dir); return wrap(Dref->createCompileUnit(Lang, F, Producer, Optimized, Flags, RuntimeVersion)); -#endif } LLVMMetadataRef LLVMExtDIBuilderCreateFunction( @@ -99,24 +66,13 @@ LLVMMetadataRef LLVMExtDIBuilderCreateFunction( const char *LinkageName, LLVMMetadataRef File, unsigned Line, LLVMMetadataRef CompositeType, bool IsLocalToUnit, bool IsDefinition, unsigned ScopeLine, -#if LLVM_VERSION_LE(3, 9) - unsigned Flags, -#else DINode::DIFlags Flags, -#endif bool IsOptimized, LLVMValueRef Func) { -#if LLVM_VERSION_GE(8, 0) DISubprogram *Sub = Dref->createFunction( unwrapDI(Scope), StringRef(Name), StringRef(LinkageName), unwrapDI(File), Line, unwrapDI(CompositeType), ScopeLine, Flags, DISubprogram::toSPFlags(IsLocalToUnit, IsDefinition, IsOptimized)); -#else - DISubprogram *Sub = Dref->createFunction( - unwrapDI(Scope), Name, LinkageName, unwrapDI(File), Line, - unwrapDI(CompositeType), IsLocalToUnit, IsDefinition, - ScopeLine, Flags, IsOptimized); -#endif unwrap(Func)->setSubprogram(Sub); return wrap(Sub); } @@ -131,11 +87,7 @@ LLVMMetadataRef LLVMExtDIBuilderCreateLexicalBlock( LLVMMetadataRef LLVMExtDIBuilderCreateBasicType( DIBuilderRef Dref, const char *Name, uint64_t SizeInBits, uint64_t AlignInBits, unsigned Encoding) { -#if LLVM_VERSION_LE(3, 9) - return wrap(Dref->createBasicType(Name, SizeInBits, AlignInBits, Encoding)); -#else return wrap(Dref->createBasicType(Name, SizeInBits, Encoding)); -#endif } LLVMMetadataRef LLVMExtDIBuilderGetOrCreateTypeArray( @@ -163,21 +115,11 @@ LLVMMetadataRef LLVMExtDIBuilderCreateAutoVariable( DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, LLVMMetadataRef File, unsigned Line, LLVMMetadataRef Ty, int AlwaysPreserve, -#if LLVM_VERSION_LE(3, 9) - unsigned Flags, -#else DINode::DIFlags Flags, -#endif uint32_t AlignInBits) { -#if LLVM_VERSION_LE(3, 9) - DILocalVariable *V = Dref->createAutoVariable( - unwrapDI(Scope), Name, unwrapDI(File), Line, - unwrapDI(Ty), AlwaysPreserve, Flags); -#else DILocalVariable *V = Dref->createAutoVariable( unwrapDI(Scope), Name, unwrapDI(File), Line, unwrapDI(Ty), AlwaysPreserve, Flags, AlignInBits); -#endif return wrap(V); } @@ -185,11 +127,7 @@ LLVMMetadataRef LLVMExtDIBuilderCreateParameterVariable( DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, unsigned ArgNo, LLVMMetadataRef File, unsigned Line, LLVMMetadataRef Ty, int AlwaysPreserve, -#if LLVM_VERSION_LE(3, 9) - unsigned Flags -#else DINode::DIFlags Flags -#endif ) { DILocalVariable *V = Dref->createParameterVariable (unwrapDI(Scope), Name, ArgNo, unwrapDI(File), Line, @@ -235,11 +173,7 @@ LLVMMetadataRef LLVMExtDIBuilderCreateStructType( DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, LLVMMetadataRef File, unsigned Line, uint64_t SizeInBits, uint64_t AlignInBits, -#if LLVM_VERSION_LE(3, 9) - unsigned Flags, -#else DINode::DIFlags Flags, -#endif LLVMMetadataRef DerivedFrom, LLVMMetadataRef Elements) { DICompositeType *CT = Dref->createStructType( unwrapDI(Scope), Name, unwrapDI(File), Line, @@ -252,11 +186,7 @@ LLVMMetadataRef LLVMExtDIBuilderCreateUnionType( DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, LLVMMetadataRef File, unsigned Line, uint64_t SizeInBits, uint64_t AlignInBits, -#if LLVM_VERSION_LE(3, 9) - unsigned Flags, -#else DINode::DIFlags Flags, -#endif LLVMMetadataRef Elements) { DICompositeType *CT = Dref->createUnionType( unwrapDI(Scope), Name, unwrapDI(File), Line, @@ -283,21 +213,6 @@ LLVMMetadataRef LLVMExtDIBuilderCreateReplaceableCompositeType( return wrap(CT); } -// LLVM 7.0 LLVMDIBuilderCreateUnspecifiedType -LLVMMetadataRef LLVMExtDIBuilderCreateUnspecifiedType( - DIBuilderRef Dref, const char *Name, size_t NameLen) { - return wrap(Dref->createUnspecifiedType({Name, NameLen})); -} - -// LLVM 7.0 LLVMDIBuilderCreateLexicalBlockFile -LLVMMetadataRef LLVMExtDIBuilderCreateLexicalBlockFile( - DIBuilderRef Dref, - LLVMMetadataRef Scope, LLVMMetadataRef File, unsigned Discriminator) { - return wrap(Dref->createLexicalBlockFile(unwrapDI(Scope), - unwrapDI(File), - Discriminator)); -} - void LLVMExtDIBuilderReplaceTemporary( DIBuilderRef Dref, LLVMMetadataRef From, LLVMMetadataRef To) { auto *Node = unwrap(From); @@ -310,11 +225,7 @@ void LLVMExtDIBuilderReplaceTemporary( LLVMMetadataRef LLVMExtDIBuilderCreateMemberType( DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, LLVMMetadataRef File, unsigned Line, uint64_t SizeInBits, uint64_t AlignInBits, uint64_t OffsetInBits, -#if LLVM_VERSION_LE(3, 9) - unsigned Flags, -#else DINode::DIFlags Flags, -#endif LLVMMetadataRef Ty) { DIDerivedType *DT = Dref->createMemberType( unwrapDI(Scope), Name, unwrapDI(File), Line, @@ -327,9 +238,7 @@ LLVMMetadataRef LLVMExtDIBuilderCreatePointerType( uint64_t SizeInBits, uint64_t AlignInBits, const char *Name) { DIDerivedType *T = Dref->createPointerType(unwrapDI(PointeeType), SizeInBits, AlignInBits, -#if LLVM_VERSION_GE(5, 0) None, -#endif Name); return wrap(T); } @@ -366,7 +275,7 @@ void LLVMExtSetCurrentDebugLocation( #endif } -#if LLVM_VERSION_GE(3, 9) +#if LLVM_VERSION_LE(13, 0) // A backported LLVMCreateTypeAttribute for LLVM < 13 // from https://github.com/llvm/llvm-project/blob/bb8ce25e88218be60d2a4ea9c9b0b721809eff27/llvm/lib/IR/Core.cpp#L167 LLVMAttributeRef LLVMExtCreateTypeAttribute( @@ -413,97 +322,59 @@ void LLVMExtSetOrdering(LLVMValueRef MemAccessInst, LLVMAtomicOrdering Ordering) LLVMValueRef LLVMExtBuildCatchPad( LLVMBuilderRef B, LLVMValueRef ParentPad, unsigned ArgCount, LLVMValueRef *LLArgs, const char *Name) { -#if LLVM_VERSION_GE(3, 8) Value **Args = unwrap(LLArgs); return wrap(unwrap(B)->CreateCatchPad( unwrap(ParentPad), ArrayRef(Args, ArgCount), Name)); -#else - return nullptr; -#endif } LLVMValueRef LLVMExtBuildCatchRet( LLVMBuilderRef B, LLVMValueRef Pad, LLVMBasicBlockRef BB) { -#if LLVM_VERSION_GE(3, 8) return wrap(unwrap(B)->CreateCatchRet(cast(unwrap(Pad)), unwrap(BB))); -#else - return nullptr; -#endif } LLVMValueRef LLVMExtBuildCatchSwitch( LLVMBuilderRef B, LLVMValueRef ParentPad, LLVMBasicBlockRef BB, unsigned NumHandlers, const char *Name) { -#if LLVM_VERSION_GE(3, 8) if (ParentPad == nullptr) { Type *Ty = Type::getTokenTy(unwrap(B)->getContext()); ParentPad = wrap(Constant::getNullValue(Ty)); } return wrap(unwrap(B)->CreateCatchSwitch(unwrap(ParentPad), unwrap(BB), NumHandlers, Name)); -#else - return nullptr; -#endif } void LLVMExtAddHandler(LLVMValueRef CatchSwitchRef, LLVMBasicBlockRef Handler) { -#if LLVM_VERSION_GE(3, 8) Value *CatchSwitch = unwrap(CatchSwitchRef); cast(CatchSwitch)->addHandler(unwrap(Handler)); -#endif } OperandBundleDef *LLVMExtBuildOperandBundleDef( const char *Name, LLVMValueRef *Inputs, unsigned NumInputs) { -#if LLVM_VERSION_GE(3, 8) return new OperandBundleDef(Name, makeArrayRef(unwrap(Inputs), NumInputs)); -#else - return nullptr; -#endif } LLVMValueRef LLVMExtBuildCall2( LLVMBuilderRef B, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, OperandBundleDef *Bundle, const char *Name) { -#if LLVM_VERSION_GE(8, 0) unsigned Len = Bundle ? 1 : 0; ArrayRef Bundles = makeArrayRef(Bundle, Len); return wrap(unwrap(B)->CreateCall( (llvm::FunctionType*) unwrap(Ty), unwrap(Fn), makeArrayRef(unwrap(Args), NumArgs), Bundles, Name)); -#elif LLVM_VERSION_GE(3, 8) - unsigned Len = Bundle ? 1 : 0; - ArrayRef Bundles = makeArrayRef(Bundle, Len); - return wrap(unwrap(B)->CreateCall( - unwrap(Fn), makeArrayRef(unwrap(Args), NumArgs), Bundles, Name)); -#else - return LLVMBuildCall(B, Fn, Args, NumArgs, Name); -#endif } LLVMValueRef LLVMExtBuildInvoke2( LLVMBuilderRef B, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, LLVMBasicBlockRef Then, LLVMBasicBlockRef Catch, OperandBundleDef *Bundle, const char *Name) { -#if LLVM_VERSION_GE(8, 0) unsigned Len = Bundle ? 1 : 0; ArrayRef Bundles = makeArrayRef(Bundle, Len); return wrap(unwrap(B)->CreateInvoke((llvm::FunctionType*) unwrap(Ty), unwrap(Fn), unwrap(Then), unwrap(Catch), makeArrayRef(unwrap(Args), NumArgs), Bundles, Name)); -#elif LLVM_VERSION_GE(3, 8) - unsigned Len = Bundle ? 1 : 0; - ArrayRef Bundles = makeArrayRef(Bundle, Len); - return wrap(unwrap(B)->CreateInvoke(unwrap(Fn), unwrap(Then), unwrap(Catch), - makeArrayRef(unwrap(Args), NumArgs), - Bundles, Name)); -#else - return LLVMBuildInvoke(B, Fn, Args, NumArgs, Then, Catch, Name); -#endif } void LLVMExtWriteBitcodeWithSummaryToFile(LLVMModuleRef mref, const char *File) { -#if LLVM_VERSION_GE(4, 0) // https://github.com/ldc-developers/ldc/pull/1840/files Module *m = unwrap(mref); @@ -516,12 +387,7 @@ void LLVMExtWriteBitcodeWithSummaryToFile(LLVMModuleRef mref, const char *File) if (EC) return; llvm::ModuleSummaryIndex moduleSummaryIndex = llvm::buildModuleSummaryIndex(*m, nullptr, nullptr); -#if LLVM_VERSION_GE(7, 0) llvm::WriteBitcodeToFile(*m, OS, true, &moduleSummaryIndex, true); -#else - llvm::WriteBitcodeToFile(m, OS, true, &moduleSummaryIndex, true); -#endif -#endif } // Missing LLVMNormalizeTargetTriple in LLVM <= 7.0 @@ -529,23 +395,12 @@ char *LLVMExtNormalizeTargetTriple(const char* triple) { return strdup(Triple::normalize(StringRef(triple)).c_str()); } -char *LLVMExtBasicBlockName(LLVMBasicBlockRef BB) { -#if LLVM_VERSION_GE(4, 0) - // It seems to work since llvm-4.0 https://stackoverflow.com/a/46045548/30948 - return strdup(unwrap(BB)->getName().data()); -#else - return NULL; -#endif -} - static TargetMachine *unwrap(LLVMTargetMachineRef P) { return reinterpret_cast(P); } void LLVMExtTargetMachineEnableGlobalIsel(LLVMTargetMachineRef T, LLVMBool Enable) { -#if LLVM_VERSION_GE(7, 0) unwrap(T)->setGlobalISel(Enable); -#endif } // Copy paste of https://github.com/llvm/llvm-project/blob/dace8224f38a31636a02fe9c2af742222831f70c/llvm/lib/ExecutionEngine/ExecutionEngineBindings.cpp#L160-L214 @@ -576,9 +431,7 @@ LLVMBool LLVMExtCreateMCJITCompilerForModule( TargetOptions targetOptions; targetOptions.EnableFastISel = options.EnableFastISel; - #if LLVM_VERSION_GE(7, 0) - targetOptions.EnableGlobalISel = EnableGlobalISel; - #endif + targetOptions.EnableGlobalISel = EnableGlobalISel; std::unique_ptr Mod(unwrap(M)); if (Mod) @@ -611,9 +464,7 @@ LLVMBool LLVMExtCreateMCJITCompilerForModule( std::unique_ptr(unwrap(options.MCJMM))); TargetMachine* tm = builder.selectTarget(); - #if LLVM_VERSION_GE(7, 0) - tm->setGlobalISel(EnableGlobalISel); - #endif + tm->setGlobalISel(EnableGlobalISel); if (ExecutionEngine *JIT = builder.create(tm)) { *OutJIT = wrap(JIT); diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 7c324cbd8339..7c6156703162 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -27,16 +27,7 @@ end IS_100 = {{LibLLVM::VERSION.starts_with?("10.0")}} IS_90 = {{LibLLVM::VERSION.starts_with?("9.0")}} IS_80 = {{LibLLVM::VERSION.starts_with?("8.0")}} - IS_71 = {{LibLLVM::VERSION.starts_with?("7.1")}} - IS_70 = {{LibLLVM::VERSION.starts_with?("7.0")}} - IS_60 = {{LibLLVM::VERSION.starts_with?("6.0")}} - IS_50 = {{LibLLVM::VERSION.starts_with?("5.0")}} - IS_40 = {{LibLLVM::VERSION.starts_with?("4.0")}} - IS_39 = {{LibLLVM::VERSION.starts_with?("3.9")}} - IS_38 = {{LibLLVM::VERSION.starts_with?("3.8")}} - IS_LT_70 = {{compare_versions(LibLLVM::VERSION, "7.0.0") < 0}} - IS_LT_80 = {{compare_versions(LibLLVM::VERSION, "8.0.0") < 0}} IS_LT_90 = {{compare_versions(LibLLVM::VERSION, "9.0.0") < 0}} IS_LT_100 = {{compare_versions(LibLLVM::VERSION, "10.0.0") < 0}} IS_LT_110 = {{compare_versions(LibLLVM::VERSION, "11.0.0") < 0}} @@ -76,12 +67,10 @@ lib LibLLVM enable_fast_isel : Int32 end - {% unless LibLLVM::IS_LT_70 %} - enum InlineAsmDialect - ATT - Intel - end - {% end %} + enum InlineAsmDialect + ATT + Intel + end # `LLVMModuleFlagBehavior` (_not_ `LLVM::Module::ModFlagBehavior`, their values disagree) enum ModuleFlagBehavior @@ -93,10 +82,7 @@ lib LibLLVM fun add_function = LLVMAddFunction(module : ModuleRef, name : UInt8*, type : TypeRef) : ValueRef fun add_global = LLVMAddGlobal(module : ModuleRef, type : TypeRef, name : UInt8*) : ValueRef fun add_incoming = LLVMAddIncoming(phi_node : ValueRef, incoming_values : ValueRef*, incoming_blocks : BasicBlockRef*, count : Int32) - {% unless LibLLVM::IS_LT_70 %} - fun add_module_flag = LLVMAddModuleFlag(mod : ModuleRef, behavior : ModuleFlagBehavior, key : UInt8*, len : LibC::SizeT, val : MetadataRef) - {% end %} - fun add_named_metadata_operand = LLVMAddNamedMetadataOperand(mod : ModuleRef, name : UInt8*, val : ValueRef) + fun add_module_flag = LLVMAddModuleFlag(mod : ModuleRef, behavior : ModuleFlagBehavior, key : UInt8*, len : LibC::SizeT, val : MetadataRef) fun add_target_dependent_function_attr = LLVMAddTargetDependentFunctionAttr(fn : ValueRef, a : LibC::Char*, v : LibC::Char*) fun array_type = LLVMArrayType(element_type : TypeRef, count : UInt32) : TypeRef fun vector_type = LLVMVectorType(element_type : TypeRef, count : UInt32) : TypeRef @@ -113,9 +99,7 @@ lib LibLLVM # LLVMBuildCall is deprecated in favor of LLVMBuildCall2, in preparation for opaque pointer types. fun build_call = LLVMBuildCall(builder : BuilderRef, fn : ValueRef, args : ValueRef*, num_args : Int32, name : UInt8*) : ValueRef {% end %} - {% unless LibLLVM::IS_LT_80 %} - fun build_call2 = LLVMBuildCall2(builder : BuilderRef, type : TypeRef, fn : ValueRef, args : ValueRef*, num_args : Int32, name : UInt8*) : ValueRef - {% end %} + fun build_call2 = LLVMBuildCall2(builder : BuilderRef, type : TypeRef, fn : ValueRef, args : ValueRef*, num_args : Int32, name : UInt8*) : ValueRef fun build_cond = LLVMBuildCondBr(builder : BuilderRef, if : ValueRef, then : BasicBlockRef, else : BasicBlockRef) : ValueRef fun build_exact_sdiv = LLVMBuildExactSDiv(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef fun build_extract_value = LLVMBuildExtractValue(builder : BuilderRef, agg_val : ValueRef, index : UInt32, name : UInt8*) : ValueRef @@ -138,9 +122,7 @@ lib LibLLVM # LLVMBuildInvoke is deprecated in favor of LLVMBuildInvoke2, in preparation for opaque pointer types. fun build_invoke = LLVMBuildInvoke(builder : BuilderRef, fn : ValueRef, args : ValueRef*, num_args : UInt32, then : BasicBlockRef, catch : BasicBlockRef, name : UInt8*) : ValueRef {% end %} - {% unless LibLLVM::IS_LT_80 %} - fun build_invoke2 = LLVMBuildInvoke2(builder : BuilderRef, ty : TypeRef, fn : ValueRef, args : ValueRef*, num_args : UInt32, then : BasicBlockRef, catch : BasicBlockRef, name : UInt8*) : ValueRef - {% end %} + fun build_invoke2 = LLVMBuildInvoke2(builder : BuilderRef, ty : TypeRef, fn : ValueRef, args : ValueRef*, num_args : UInt32, then : BasicBlockRef, catch : BasicBlockRef, name : UInt8*) : ValueRef fun build_landing_pad = LLVMBuildLandingPad(builder : BuilderRef, ty : TypeRef, pers_fn : ValueRef, num_clauses : UInt32, name : UInt8*) : ValueRef fun build_load = LLVMBuildLoad(builder : BuilderRef, ptr : ValueRef, name : UInt8*) : ValueRef fun build_lshr = LLVMBuildLShr(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef @@ -193,6 +175,7 @@ lib LibLLVM fun generic_value_to_float = LLVMGenericValueToFloat(type : TypeRef, value : GenericValueRef) : Float64 fun generic_value_to_int = LLVMGenericValueToInt(value : GenericValueRef, signed : Int32) : UInt64 fun generic_value_to_pointer = LLVMGenericValueToPointer(value : GenericValueRef) : Void* + fun get_basic_block_name = LLVMGetBasicBlockName(basic_block : LibLLVM::BasicBlockRef) : Char* fun get_current_debug_location = LLVMGetCurrentDebugLocation(builder : BuilderRef) : ValueRef fun get_element_type = LLVMGetElementType(ty : TypeRef) : TypeRef fun get_first_instruction = LLVMGetFirstInstruction(block : BasicBlockRef) : ValueRef @@ -202,9 +185,7 @@ lib LibLLVM fun get_named_function = LLVMGetNamedFunction(mod : ModuleRef, name : UInt8*) : ValueRef fun get_named_global = LLVMGetNamedGlobal(mod : ModuleRef, name : UInt8*) : ValueRef fun get_count_params = LLVMCountParams(fn : ValueRef) : UInt - {% unless LibLLVM::IS_LT_70 %} - fun get_host_cpu_name = LLVMGetHostCPUName : UInt8* - {% end %} + fun get_host_cpu_name = LLVMGetHostCPUName : UInt8* fun get_param = LLVMGetParam(fn : ValueRef, index : Int32) : ValueRef fun get_param_types = LLVMGetParamTypes(function_type : TypeRef, dest : TypeRef*) fun get_params = LLVMGetParams(fn : ValueRef, params : ValueRef*) @@ -324,10 +305,9 @@ lib LibLLVM fun abi_size_of_type = LLVMABISizeOfType(td : TargetDataRef, ty : TypeRef) : UInt64 fun abi_alignment_of_type = LLVMABIAlignmentOfType(td : TargetDataRef, ty : TypeRef) : UInt32 fun get_target_machine_target = LLVMGetTargetMachineTarget(t : TargetMachineRef) : TargetRef - fun const_inline_asm = LLVMConstInlineAsm(t : TypeRef, asm_string : UInt8*, constraints : UInt8*, has_side_effects : Int32, is_align_stack : Int32) : ValueRef {% if !LibLLVM::IS_LT_130 %} fun get_inline_asm = LLVMGetInlineAsm(t : TypeRef, asm_string : UInt8*, asm_string_len : LibC::SizeT, constraints : UInt8*, constraints_len : LibC::SizeT, has_side_effects : Int32, is_align_stack : Int32, dialect : InlineAsmDialect, can_throw : Int32) : ValueRef - {% elsif !LibLLVM::IS_LT_70 %} + {% else %} fun get_inline_asm = LLVMGetInlineAsm(t : TypeRef, asm_string : UInt8*, asm_string_len : LibC::SizeT, constraints : UInt8*, constraints_len : LibC::SizeT, has_side_effects : Int32, is_align_stack : Int32, dialect : InlineAsmDialect) : ValueRef {% end %} fun create_context = LLVMContextCreate : ContextRef @@ -351,37 +331,21 @@ lib LibLLVM fun write_bitcode_to_fd = LLVMWriteBitcodeToFD(mod : ModuleRef, fd : LibC::Int, should_close : LibC::Int, unbuffered : LibC::Int) : LibC::Int - {% if LibLLVM::IS_38 %} - fun copy_string_rep_of_target_data = LLVMCopyStringRepOfTargetData(data : TargetDataRef) : UInt8* - fun get_target_machine_data = LLVMGetTargetMachineData(t : TargetMachineRef) : TargetDataRef - fun set_data_layout = LLVMSetDataLayout(mod : ModuleRef, data : UInt8*) - {% else %} - # LLVM >= 3.9 - fun create_target_data_layout = LLVMCreateTargetDataLayout(t : TargetMachineRef) : TargetDataRef - fun set_module_data_layout = LLVMSetModuleDataLayout(mod : ModuleRef, data : TargetDataRef) - {% end %} + fun create_target_data_layout = LLVMCreateTargetDataLayout(t : TargetMachineRef) : TargetDataRef + fun set_module_data_layout = LLVMSetModuleDataLayout(mod : ModuleRef, data : TargetDataRef) - {% if LibLLVM::IS_38 %} - fun add_attribute = LLVMAddAttribute(arg : ValueRef, attr : LLVM::Attribute) - fun add_instr_attribute = LLVMAddInstrAttribute(instr : ValueRef, index : UInt32, attr : LLVM::Attribute) - fun add_function_attr = LLVMAddFunctionAttr(fn : ValueRef, pa : LLVM::Attribute) - fun get_function_attr = LLVMGetFunctionAttr(fn : ValueRef) : LLVM::Attribute - fun get_attribute = LLVMGetAttribute(arg : ValueRef) : LLVM::Attribute - {% else %} - # LLVM >= 3.9 - type AttributeRef = Void* - alias AttributeIndex = UInt + type AttributeRef = Void* + alias AttributeIndex = UInt - fun get_last_enum_attribute_kind = LLVMGetLastEnumAttributeKind : UInt - fun get_enum_attribute_kind_for_name = LLVMGetEnumAttributeKindForName(name : Char*, s_len : LibC::SizeT) : UInt - fun create_enum_attribute = LLVMCreateEnumAttribute(c : ContextRef, kind_id : UInt, val : UInt64) : AttributeRef - fun add_attribute_at_index = LLVMAddAttributeAtIndex(f : ValueRef, idx : AttributeIndex, a : AttributeRef) - fun get_enum_attribute_at_index = LLVMGetEnumAttributeAtIndex(f : ValueRef, idx : AttributeIndex, kind_id : UInt) : AttributeRef - fun add_call_site_attribute = LLVMAddCallSiteAttribute(f : ValueRef, idx : AttributeIndex, value : AttributeRef) + fun get_last_enum_attribute_kind = LLVMGetLastEnumAttributeKind : UInt + fun get_enum_attribute_kind_for_name = LLVMGetEnumAttributeKindForName(name : Char*, s_len : LibC::SizeT) : UInt + fun create_enum_attribute = LLVMCreateEnumAttribute(c : ContextRef, kind_id : UInt, val : UInt64) : AttributeRef + fun add_attribute_at_index = LLVMAddAttributeAtIndex(f : ValueRef, idx : AttributeIndex, a : AttributeRef) + fun get_enum_attribute_at_index = LLVMGetEnumAttributeAtIndex(f : ValueRef, idx : AttributeIndex, kind_id : UInt) : AttributeRef + fun add_call_site_attribute = LLVMAddCallSiteAttribute(f : ValueRef, idx : AttributeIndex, value : AttributeRef) - fun get_module_identifier = LLVMGetModuleIdentifier(m : ModuleRef, len : LibC::SizeT*) : UInt8* - fun set_module_identifier = LLVMSetModuleIdentifier(m : ModuleRef, ident : UInt8*, len : LibC::SizeT) - {% end %} + fun get_module_identifier = LLVMGetModuleIdentifier(m : ModuleRef, len : LibC::SizeT*) : UInt8* + fun set_module_identifier = LLVMSetModuleIdentifier(m : ModuleRef, ident : UInt8*, len : LibC::SizeT) fun get_module_context = LLVMGetModuleContext(m : ModuleRef) : ContextRef fun get_global_parent = LLVMGetGlobalParent(global : ValueRef) : ModuleRef @@ -409,9 +373,7 @@ lib LibLLVM fun md_node_in_context = LLVMMDNodeInContext(c : ContextRef, values : ValueRef*, count : Int32) : ValueRef fun md_string_in_context = LLVMMDStringInContext(c : ContextRef, str : UInt8*, length : Int32) : ValueRef - {% unless LibLLVM::IS_LT_70 %} - fun value_as_metadata = LLVMValueAsMetadata(val : ValueRef) : MetadataRef - {% end %} + fun value_as_metadata = LLVMValueAsMetadata(val : ValueRef) : MetadataRef fun append_basic_block_in_context = LLVMAppendBasicBlockInContext(ctx : ContextRef, fn : ValueRef, name : UInt8*) : BasicBlockRef fun create_builder_in_context = LLVMCreateBuilderInContext(c : ContextRef) : BuilderRef diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr index 59dd50c02c13..5d484b06d665 100644 --- a/src/llvm/lib_llvm_ext.cr +++ b/src/llvm/lib_llvm_ext.cr @@ -14,7 +14,7 @@ lib LibLLVMExt type OperandBundleDefRef = Void* fun create_di_builder = LLVMExtNewDIBuilder(LibLLVM::ModuleRef) : DIBuilder - fun di_builder_finalize = LLVMExtDIBuilderFinalize(DIBuilder) + fun di_builder_finalize = LLVMDIBuilderFinalize(DIBuilder) fun di_builder_create_function = LLVMExtDIBuilderCreateFunction( builder : DIBuilder, scope : LibLLVM::MetadataRef, name : Char*, @@ -104,14 +104,14 @@ lib LibLLVMExt file : LibLLVM::MetadataRef, line : UInt) : LibLLVM::MetadataRef - fun di_builder_create_unspecified_type = LLVMExtDIBuilderCreateUnspecifiedType(builder : LibLLVMExt::DIBuilder, - name : Void*, - size : LibC::SizeT) : LibLLVM::MetadataRef + fun di_builder_create_unspecified_type = LLVMDIBuilderCreateUnspecifiedType(builder : LibLLVMExt::DIBuilder, + name : Void*, + size : LibC::SizeT) : LibLLVM::MetadataRef - fun di_builder_create_lexical_block_file = LLVMExtDIBuilderCreateLexicalBlockFile(builder : LibLLVMExt::DIBuilder, - scope : LibLLVM::MetadataRef, - file_scope : LibLLVM::MetadataRef, - discriminator : UInt32) : LibLLVM::MetadataRef + fun di_builder_create_lexical_block_file = LLVMDIBuilderCreateLexicalBlockFile(builder : LibLLVMExt::DIBuilder, + scope : LibLLVM::MetadataRef, + file_scope : LibLLVM::MetadataRef, + discriminator : UInt32) : LibLLVM::MetadataRef fun di_builder_replace_temporary = LLVMExtDIBuilderReplaceTemporary(builder : DIBuilder, from : LibLLVM::MetadataRef, to : LibLLVM::MetadataRef) @@ -154,19 +154,18 @@ lib LibLLVMExt bundle : LibLLVMExt::OperandBundleDefRef, name : LibC::Char*) : LibLLVM::ValueRef - {% unless LibLLVM::IS_38 || LibLLVM::IS_39 %} - fun write_bitcode_with_summary_to_file = LLVMExtWriteBitcodeWithSummaryToFile(module : LibLLVM::ModuleRef, path : UInt8*) : Void - {% end %} + fun write_bitcode_with_summary_to_file = LLVMExtWriteBitcodeWithSummaryToFile(module : LibLLVM::ModuleRef, path : UInt8*) : Void fun normalize_target_triple = LLVMExtNormalizeTargetTriple(triple : Char*) : Char* - fun basic_block_name = LLVMExtBasicBlockName(basic_block : LibLLVM::BasicBlockRef) : Char* fun di_builder_get_or_create_array_subrange = LLVMExtDIBuilderGetOrCreateArraySubrange(builder : DIBuilder, lo : UInt64, count : UInt64) : LibLLVM::MetadataRef fun target_machine_enable_global_isel = LLVMExtTargetMachineEnableGlobalIsel(machine : LibLLVM::TargetMachineRef, enable : Bool) fun create_mc_jit_compiler_for_module = LLVMExtCreateMCJITCompilerForModule(jit : LibLLVM::ExecutionEngineRef*, m : LibLLVM::ModuleRef, options : LibLLVM::JITCompilerOptions*, options_length : UInt32, enable_global_isel : Bool, error : UInt8**) : Int32 - {% unless LibLLVM::IS_38 %} - # LLVMCreateTypeAttribute is implemented in LLVM 13, but needed in 12 + # LLVMCreateTypeAttribute is implemented in LLVM 13, but needed in 12 + {% if LibLLVM::IS_LT_130 %} fun create_type_attribute = LLVMExtCreateTypeAttribute(ctx : LibLLVM::ContextRef, kind_id : LibC::UInt, ty : LibLLVM::TypeRef) : LibLLVM::AttributeRef + {% else %} + fun create_type_attribute = LLVMCreateTypeAttribute(ctx : LibLLVM::ContextRef, kind_id : LibC::UInt, ty : LibLLVM::TypeRef) : LibLLVM::AttributeRef {% end %} end diff --git a/src/llvm/module.cr b/src/llvm/module.cr index 162422b752e9..6a9fe2f5cbdc 100644 --- a/src/llvm/module.cr +++ b/src/llvm/module.cr @@ -6,41 +6,25 @@ class LLVM::Module getter context : Context - {% if LibLLVM::IS_38 %} - def initialize(@unwrap : LibLLVM::ModuleRef, @name : String, @context : Context) - @owned = false - end - - def name : String - @name - end - {% else %} - # LLVM >= 3.9 - def initialize(@unwrap : LibLLVM::ModuleRef, @context : Context) - @owned = false - end + def initialize(@unwrap : LibLLVM::ModuleRef, @context : Context) + @owned = false + end - def name : String - bytes = LibLLVM.get_module_identifier(self, out bytesize) - String.new(Slice.new(bytes, bytesize)) - end + def name : String + bytes = LibLLVM.get_module_identifier(self, out bytesize) + String.new(Slice.new(bytes, bytesize)) + end - def name=(name : String) - LibLLVM.set_module_identifier(self, name, name.bytesize) - end - {% end %} + def name=(name : String) + LibLLVM.set_module_identifier(self, name, name.bytesize) + end def target=(target) LibLLVM.set_target(self, target) end def data_layout=(data : TargetData) - {% if LibLLVM::IS_38 %} - LibLLVM.set_data_layout(self, data.to_data_layout_string) - {% else %} - # LLVM >= 3.9 - LibLLVM.set_module_data_layout(self, data) - {% end %} + LibLLVM.set_module_data_layout(self, data) end def dump @@ -56,34 +40,22 @@ class LLVM::Module end def add_flag(module_flag : LibLLVM::ModuleFlagBehavior, key : String, val : Value) - {% if LibLLVM::IS_LT_70 %} - values = [ - context.int32.const_int(module_flag.value), - context.md_string(key.to_s), - val, - ] - md_node = context.md_node(values) - LibLLVM.add_named_metadata_operand(self, "llvm.module.flags", md_node) - {% else %} - LibLLVM.add_module_flag( - self, - module_flag, - key, - key.bytesize, - LibLLVM.value_as_metadata(val.to_unsafe) - ) - {% end %} + LibLLVM.add_module_flag( + self, + module_flag, + key, + key.bytesize, + LibLLVM.value_as_metadata(val.to_unsafe) + ) end def write_bitcode_to_file(filename : String) LibLLVM.write_bitcode_to_file self, filename end - {% unless LibLLVM::IS_38 || LibLLVM::IS_39 %} - def write_bitcode_with_summary_to_file(filename : String) - LibLLVMExt.write_bitcode_with_summary_to_file self, filename - end - {% end %} + def write_bitcode_with_summary_to_file(filename : String) + LibLLVMExt.write_bitcode_with_summary_to_file self, filename + end def write_bitcode_to_memory_buffer MemoryBuffer.new(LibLLVM.write_bitcode_to_memory_buffer self) diff --git a/src/llvm/target_machine.cr b/src/llvm/target_machine.cr index 1b9dbf685921..80ebc188f9a4 100644 --- a/src/llvm/target_machine.cr +++ b/src/llvm/target_machine.cr @@ -9,12 +9,7 @@ class LLVM::TargetMachine def data_layout : LLVM::TargetData @layout ||= begin - layout = {% if LibLLVM::IS_38 %} - LibLLVM.get_target_machine_data(self) - {% else %} - # LLVM >= 3.9 - LibLLVM.create_target_data_layout(self) - {% end %} + layout = LibLLVM.create_target_data_layout(self) layout ? TargetData.new(layout) : raise "Missing layout for #{self}" end end diff --git a/src/llvm/type.cr b/src/llvm/type.cr index 1343308b1c55..a4731fddc4ba 100644 --- a/src/llvm/type.cr +++ b/src/llvm/type.cr @@ -156,15 +156,7 @@ struct LLVM::Type def inline_asm(asm_string, constraints, has_side_effects = false, is_align_stack = false, can_throw = false) value = - {% if LibLLVM::IS_LT_70 %} - LibLLVM.const_inline_asm( - self, - asm_string, - constraints, - (has_side_effects ? 1 : 0), - (is_align_stack ? 1 : 0) - ) - {% elsif LibLLVM::IS_LT_130 %} + {% if LibLLVM::IS_LT_130 %} LibLLVM.get_inline_asm( self, asm_string, From 2030e558ebe7f954cc9acae3662b6a2df9654b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 19 Jan 2023 13:07:40 +0100 Subject: [PATCH 0255/1551] Spec: Add `--color` option to spec runner (#12932) --- spec/std/spec_spec.cr | 16 ---------------- src/spec/cli.cr | 10 +++++----- src/spec/context.cr | 5 +---- src/spec/dsl.cr | 12 ++---------- 4 files changed, 8 insertions(+), 35 deletions(-) diff --git a/spec/std/spec_spec.cr b/spec/std/spec_spec.cr index f087f3e2bcef..06e5bc5cf377 100644 --- a/spec/std/spec_spec.cr +++ b/spec/std/spec_spec.cr @@ -133,19 +133,3 @@ describe "Spec matchers" do end end end - -describe "Spec" do - describe "use_colors?" do - it "returns if output is colored or not" do - saved = Spec.use_colors? - begin - Spec.use_colors = false - Spec.use_colors?.should be_false - Spec.use_colors = true - Spec.use_colors?.should be_true - ensure - Spec.use_colors = saved - end - end - end -end diff --git a/src/spec/cli.cr b/src/spec/cli.cr index 03847d45bb09..011d90ecc481 100644 --- a/src/spec/cli.cr +++ b/src/spec/cli.cr @@ -4,9 +4,6 @@ require "option_parser" # spec runner on `crystal spec --help`. module Spec - # :nodoc: - class_property? use_colors = true - # :nodoc: class_property pattern : Regex? @@ -109,8 +106,11 @@ module Spec opts.on("--tap", "Generate TAP output (Test Anything Protocol)") do configure_formatter("tap") end - opts.on("--no-color", "Disable colored output") do - Spec.use_colors = false + opts.on("--color", "Enabled ANSI colored output") do + Colorize.enabled = true + end + opts.on("--no-color", "Disable ANSI colored output") do + Colorize.enabled = false end opts.unknown_args do |args| end diff --git a/src/spec/context.cr b/src/spec/context.cr index e2d90e7686de..7aa1922fe21e 100644 --- a/src/spec/context.cr +++ b/src/spec/context.cr @@ -233,10 +233,7 @@ module Spec top_n.each do |res| puts " #{res.description}" res_elapsed = res.elapsed.not_nil!.total_seconds.humanize - if Spec.use_colors? - res_elapsed = res_elapsed.colorize.bold - end - puts " #{res_elapsed} seconds #{Spec.relative_file(res.file)}:#{res.line}" + puts " #{res_elapsed.colorize.bold} seconds #{Spec.relative_file(res.file)}:#{res.line}" end end diff --git a/src/spec/dsl.cr b/src/spec/dsl.cr index b531f13a3f1d..5a5969dbac5d 100644 --- a/src/spec/dsl.cr +++ b/src/spec/dsl.cr @@ -31,20 +31,12 @@ module Spec # :nodoc: def self.color(str, status : Status) - if use_colors? - str.colorize(STATUS_COLORS[status]) - else - str - end + str.colorize(STATUS_COLORS[status]) end # :nodoc: def self.color(str, kind : InfoKind) - if use_colors? - str.colorize(INFO_COLORS[kind]) - else - str - end + str.colorize(INFO_COLORS[kind]) end # :nodoc: From 096b0a28cfa095acdaeb1dbce34feb093378e819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 19 Jan 2023 20:32:21 +0100 Subject: [PATCH 0256/1551] Internalize `Process.fork` (#12934) Co-authored-by: Sijawusz Pur Rahnama --- src/compiler/crystal/compiler.cr | 8 +++++--- src/crystal/system/process.cr | 1 + src/crystal/system/unix/process.cr | 22 ++++++++++++++++++++++ src/crystal/system/wasi/process.cr | 4 ++++ src/crystal/system/win32/process.cr | 4 ++++ src/process.cr | 20 +++++--------------- 6 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index a639e2506def..99484312898e 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -453,7 +453,9 @@ module Crystal return all_reused end - {% if flag?(:preview_mt) %} + {% if !Crystal::System::Process.class.has_method?("fork") %} + raise "Cannot fork compiler. `Crystal::System::Process.fork` is not implemented on this system." + {% elsif flag?(:preview_mt) %} raise "Cannot fork compiler in multithread mode" {% else %} jobs_count = 0 @@ -477,7 +479,7 @@ module Crystal end end - codegen_process = Process.fork do + codegen_process = Crystal::System::Process.fork do pipe_w = pw slice.each do |unit| unit.compile @@ -487,7 +489,7 @@ module Crystal end end end - codegen_process.wait + Process.new(codegen_process).wait if pipe_w = pw pipe_w.close diff --git a/src/crystal/system/process.cr b/src/crystal/system/process.cr index f12796f8a7a9..77577a9e5c6b 100644 --- a/src/crystal/system/process.cr +++ b/src/crystal/system/process.cr @@ -50,6 +50,7 @@ struct Crystal::System::Process # Duplicates the current process. # def self.fork : ProcessInformation + # def self.fork(&) # Launches a child process with the command + args. # def self.spawn(command_args : Args, env : Env?, clear_env : Bool, input : Stdio, output : Stdio, error : Stdio, chdir : Path | String?) : ProcessInformation diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index aa19676bd670..9f95136d6229 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -116,6 +116,28 @@ struct Crystal::System::Process pid end + # Duplicates the current process. + # Returns a `Process` representing the new child process in the current process + # and `nil` inside the new child process. + def self.fork(&) + {% raise("Process fork is unsupported with multithreaded mode") if flag?(:preview_mt) %} + + if pid = fork + return pid + end + + begin + yield + LibC._exit 0 + rescue ex + ex.inspect_with_backtrace STDERR + STDERR.flush + LibC._exit 1 + ensure + LibC._exit 254 # not reached + end + end + def self.spawn(command_args, env, clear_env, input, output, error, chdir) reader_pipe, writer_pipe = IO.pipe diff --git a/src/crystal/system/wasi/process.cr b/src/crystal/system/wasi/process.cr index a9d5d0e9fa69..d0b3769c8b72 100644 --- a/src/crystal/system/wasi/process.cr +++ b/src/crystal/system/wasi/process.cr @@ -60,6 +60,10 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.fork") end + def self.fork(&) + raise NotImplementedError.new("Process.fork") + end + def self.spawn(command_args, env, clear_env, input, output, error, chdir) raise NotImplementedError.new("Process.spawn") end diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index ef9bd87dcb75..e6d46671361d 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -100,6 +100,10 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.fork") end + def self.fork(&) + raise NotImplementedError.new("Process.fork") + end + private def self.handle_from_io(io : IO::FileDescriptor, parent_io) ret = LibC._get_osfhandle(io.fd) raise RuntimeError.from_winerror("_get_osfhandle") if ret == -1 diff --git a/src/process.cr b/src/process.cr index 3c0ce80cfdac..2823ff7b17cc 100644 --- a/src/process.cr +++ b/src/process.cr @@ -65,21 +65,9 @@ class Process # returns a `Process` representing the new child process. # # Available only on Unix-like operating systems. + @[Deprecated("Fork is no longer supported.")] def self.fork(&) : Process - if process = fork - process - else - begin - yield - LibC._exit 0 - rescue ex - ex.inspect_with_backtrace STDERR - STDERR.flush - LibC._exit 1 - ensure - LibC._exit 254 # not reached - end - end + new Crystal::System::Process.fork { yield } end # :nodoc: @@ -89,6 +77,7 @@ class Process # and `nil` inside the new child process. # # Available only on Unix-like operating systems. + @[Deprecated("Fork is no longer supported.")] def self.fork : Process? {% raise("Process fork is unsupported with multithread mode") if flag?(:preview_mt) %} @@ -284,7 +273,8 @@ class Process end end - private def initialize(pid : LibC::PidT) + # :nodoc: + def initialize(pid : LibC::PidT) @process_info = Crystal::System::Process.new(pid) end From 29291fbd25e2640a07d3cae7932a60448b5c2386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 19 Jan 2023 21:47:54 +0100 Subject: [PATCH 0257/1551] Revert "Parser: Fix restrict grammar for name and supertype in type def (#12622)" (#12977) --- spec/compiler/parser/parser_spec.cr | 11 ----------- src/compiler/crystal/syntax/parser.cr | 10 ---------- 2 files changed, 21 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index cc4b11238e41..e5711af97fba 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -273,17 +273,6 @@ module Crystal assert_syntax_error "foo { |(#{kw})| }", "cannot use '#{kw}' as a block parameter name", 1, 9 end - describe "literals in class definitions" do - # #11209 - %w("a" 'a' [1] {1} {|a|a} ->{} ->(x : Bar){} :Bar :bar %x() %w() %()).each do |invalid| - assert_syntax_error "class Foo#{invalid}; end" - assert_syntax_error "class Foo#{invalid} < Baz; end" - assert_syntax_error "class Foo#{invalid} < self; end" - assert_syntax_error "class Foo < Baz#{invalid}; end" - assert_syntax_error "class Foo < self#{invalid}; end" - end - end - it_parses "def self.foo\n1\nend", Def.new("foo", body: 1.int32, receiver: "self".var) it_parses "def self.foo()\n1\nend", Def.new("foo", body: 1.int32, receiver: "self".var) it_parses "def self.foo=\n1\nend", Def.new("foo=", body: 1.int32, receiver: "self".var) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 1a06517d337a..f96684db76c3 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -1685,10 +1685,6 @@ module Crystal name = parse_path skip_space - unexpected_token unless @token.type.op_lt? || # Inheritance - @token.type.op_lparen? || # Generic Arguments - is_statement_end? - type_vars, splat_index = parse_type_vars superclass = nil @@ -1701,8 +1697,6 @@ module Crystal else superclass = parse_generic end - - unexpected_token unless @token.type.space? || is_statement_end? end skip_statement_end @@ -1722,10 +1716,6 @@ module Crystal class_def end - def is_statement_end? - @token.type.newline? || @token.type.op_semicolon? || @token.keyword?(:end) - end - def parse_type_vars type_vars = nil splat_index = nil From 54215cedb0074def7395efc76e19be9ad44f08ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 20 Jan 2023 13:43:38 +0100 Subject: [PATCH 0258/1551] Fix socket specs when network not available (#12961) Co-authored-by: Sijawusz Pur Rahnama --- spec/std/socket/tcp_server_spec.cr | 14 ++++++++++++-- spec/std/socket/tcp_socket_spec.cr | 18 ++++++++++++++---- spec/std/socket/udp_socket_spec.cr | 11 ++++++++++- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr index 454294bef2c0..c33d546827d7 100644 --- a/spec/std/socket/tcp_server_spec.cr +++ b/spec/std/socket/tcp_server_spec.cr @@ -88,14 +88,24 @@ describe TCPServer, tags: "network" do err = expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed") do TCPServer.new("doesnotexist.example.org.", 12345) end - err.os_error.should eq({% if flag?(:win32) %}WinError::WSAHOST_NOT_FOUND{% else %}Errno.new(LibC::EAI_NONAME){% end %}) + # FIXME: Resolve special handling for win32. The error code handling should be identical. + {% if flag?(:win32) %} + [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error + {% else %} + [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error + {% end %} end it "raises (rather than segfault on darwin) when host doesn't exist and port is 0" do err = expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed") do TCPServer.new("doesnotexist.example.org.", 0) end - err.os_error.should eq({% if flag?(:win32) %}WinError::WSAHOST_NOT_FOUND{% else %}Errno.new(LibC::EAI_NONAME){% end %}) + # FIXME: Resolve special handling for win32. The error code handling should be identical. + {% if flag?(:win32) %} + [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error + {% else %} + [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error + {% end %} end end diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index b0746ef914c8..b82db28e6bea 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -64,17 +64,27 @@ describe TCPSocket, tags: "network" do end it "raises when host doesn't exist" do - error = expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed") do + err = expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed") do TCPSocket.new("doesnotexist.example.org.", 12345) end - error.os_error.should eq({% if flag?(:win32) %}WinError::WSAHOST_NOT_FOUND{% else %}Errno.new(LibC::EAI_NONAME){% end %}) + # FIXME: Resolve special handling for win32. The error code handling should be identical. + {% if flag?(:win32) %} + [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error + {% else %} + [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error + {% end %} end it "raises (rather than segfault on darwin) when host doesn't exist and port is 0" do - error = expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed") do + err = expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed") do TCPSocket.new("doesnotexist.example.org.", 0) end - error.os_error.should eq({% if flag?(:win32) %}WinError::WSAHOST_NOT_FOUND{% else %}Errno.new(LibC::EAI_NONAME){% end %}) + # FIXME: Resolve special handling for win32. The error code handling should be identical. + {% if flag?(:win32) %} + [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error + {% else %} + [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error + {% end %} end end diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr index 919d8ce9f94d..24236c2109cf 100644 --- a/spec/std/socket/udp_socket_spec.cr +++ b/spec/std/socket/udp_socket_spec.cr @@ -130,7 +130,16 @@ describe UDPSocket, tags: "network" do raise "Unsupported IP address family: #{family}" end - udp.join_group(addr) + begin + udp.join_group(addr) + rescue e : Socket::Error + if e.os_error == Errno::ENODEV + pending!("Multicast device selection not available on this host") + else + raise e + end + end + udp.multicast_loopback = true udp.multicast_loopback?.should eq(true) From 0ebef4fb80b07c92dfa7bedb003282b5534f1fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 21 Jan 2023 00:22:42 +0100 Subject: [PATCH 0259/1551] Makefile: refactor test recipe (#12979) --- Makefile | 22 ++++++++++++++++------ Makefile.win | 22 ++++++++++++++++------ spec/spec_helper.cr | 2 +- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index b2dac554aa95..7db99f0be775 100644 --- a/Makefile +++ b/Makefile @@ -5,13 +5,17 @@ ## Build the compiler ## $ make ## Build the compiler with progress output -## $ make progress=true +## $ make progress=1 ## Clean up built files then build the compiler ## $ make clean crystal ## Build the compiler in release mode -## $ make crystal release=1 -## Run all specs in verbose mode -## $ make spec verbose=1 +## $ make crystal release=1 interpreter=1 +## Run tests +## $ make test +## Run stdlib tests +## $ make std_spec +## Run compiler tests +## $ make compiler_spec CRYSTAL ?= crystal ## which previous crystal compiler use LLVM_CONFIG ?= ## llvm-config command path to use @@ -80,9 +84,11 @@ check_llvm_config = $(eval \ .PHONY: all all: crystal ## Build all files (currently crystal only) [default] +.PHONY: test +test: spec ## Run tests + .PHONY: spec -spec: $(O)/all_spec ## Run all specs - $(O)/all_spec $(SPEC_FLAGS) +spec: std_spec primitives_spec compiler_spec .PHONY: std_spec std_spec: $(O)/std_spec ## Run standard library specs @@ -100,6 +106,10 @@ primitives_spec: $(O)/primitives_spec ## Run primitives specs smoke_test: ## Build specs as a smoke test smoke_test: $(O)/std_spec $(O)/compiler_spec $(O)/crystal +.PHONY: all_spec +all_spec: $(O)/all_spec ## Run all specs (note: this builds a huge program; `test` recipe builds individual binaries and is recommended for reduced resource usage) + $(O)/all_spec $(SPEC_FLAGS) + .PHONY: samples samples: ## Build example programs $(MAKE) -C samples diff --git a/Makefile.win b/Makefile.win index 17e35ecaa6d7..7c10931ef1d3 100644 --- a/Makefile.win +++ b/Makefile.win @@ -5,13 +5,17 @@ ## Build the compiler ## $ make -f Makefile.win ## Build the compiler with progress output -## $ make -f Makefile.win progress=true +## $ make -f Makefile.win progress=1 ## Clean up built files then build the compiler ## $ make -f Makefile.win clean crystal ## Build the compiler in release mode -## $ make -f Makefile.win crystal release=1 -## Run all specs in verbose mode -## $ make -f Makefile.win spec verbose=1 +## $ make -f Makefile.win crystal release=1 interpreter=1 +## Run tests +## $ make -f Makefile.win test +## Run stdlib tests +## $ make -f Makefile.win std_spec +## Run compiler tests +## $ make -f Makefile.win compiler_spec CRYSTAL ?= crystal ## which previous crystal compiler use LLVM_CONFIG ?= ## llvm-config command path to use @@ -82,9 +86,11 @@ check_llvm_config = $(eval \ .PHONY: all all: crystal ## Build all files (currently crystal only) [default] +.PHONY: test +test: spec ## Run tests + .PHONY: spec -spec: $(O)\all_spec.exe ## Run all specs - $(O)\all_spec $(SPEC_FLAGS) +spec: std_spec primitives_spec compiler_spec .PHONY: std_spec std_spec: $(O)\std_spec.exe ## Run standard library specs @@ -102,6 +108,10 @@ primitives_spec: $(O)\primitives_spec.exe ## Run primitives specs smoke_test: ## Build specs as a smoke test smoke_test: $(O)\std_spec.exe $(O)\compiler_spec.exe $(O)\crystal.exe +.PHONY: all_spec +all_spec: $(O)\all_spec.exe ## Run all specs (note: this builds a huge program; `test` recipe builds individual binaries and is recommended for reduced resource usage) + $(O)\all_spec $(SPEC_FLAGS) + .PHONY: samples samples: ## Build example programs $(MAKE) -C samples -f $(MAKEFILE_LIST) diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 655cae7e333e..cb43107118bb 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -1,4 +1,4 @@ -{% raise("Please use `make spec` or `bin/crystal` when running specs, or set the i_know_what_im_doing flag if you know what you're doing") unless env("CRYSTAL_HAS_WRAPPER") || flag?("i_know_what_im_doing") %} +{% raise("Please use `make test` or `bin/crystal` when running specs, or set the i_know_what_im_doing flag if you know what you're doing") unless env("CRYSTAL_HAS_WRAPPER") || flag?("i_know_what_im_doing") %} ENV["CRYSTAL_PATH"] = "#{__DIR__}/../src" From 41c106da3d32923b718dee3d6892d1b05f2478d2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 21 Jan 2023 20:42:39 +0800 Subject: [PATCH 0260/1551] Clean up `back\slash.txt` in `HTTP::StaticFileHandler` specs (#12984) --- spec/std/http/server/handlers/static_file_handler_spec.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/std/http/server/handlers/static_file_handler_spec.cr b/spec/std/http/server/handlers/static_file_handler_spec.cr index 489f2a1af749..3c2d1a2ae24b 100644 --- a/spec/std/http/server/handlers/static_file_handler_spec.cr +++ b/spec/std/http/server/handlers/static_file_handler_spec.cr @@ -30,6 +30,8 @@ describe HTTP::StaticFileHandler do File.touch(Path[datapath("static_file_handler"), Path.posix("back\\slash.txt")]) response = handle HTTP::Request.new("GET", "/back\\slash.txt"), ignore_body: false response.status_code.should eq 200 + ensure + File.delete(Path[datapath("static_file_handler"), Path.posix("back\\slash.txt")]) end it "adds Etag header" do From 58fb584cb87dcd903f488d050ceadd9067577bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 21 Jan 2023 18:02:44 +0100 Subject: [PATCH 0261/1551] Fix `bin/crystal` print no error message when `crystal` is missing (#12981) Co-authored-by: Jakub Jirutka --- bin/crystal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/crystal b/bin/crystal index a458b9bdb54c..a4bddb1ddf2a 100755 --- a/bin/crystal +++ b/bin/crystal @@ -149,7 +149,7 @@ export CRYSTAL="${CRYSTAL:-"crystal"}" if [ -z "$CRYSTAL_CONFIG_LIBRARY_PATH" ] || [ -z "$CRYSTAL_LIBRARY_PATH" ]; then CRYSTAL_INSTALLED_LIBRARY_PATH="$( export PATH="$(remove_path_item "$(remove_path_item "$PATH" "$SCRIPT_ROOT")" "bin")" - crystal env CRYSTAL_LIBRARY_PATH || echo "" + crystal env CRYSTAL_LIBRARY_PATH 2> /dev/null || echo "" )" export CRYSTAL_LIBRARY_PATH=${CRYSTAL_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} export CRYSTAL_CONFIG_LIBRARY_PATH=${CRYSTAL_CONFIG_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} From 01c0ba2da40701682d459c1c109c0e496aae5448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 23 Jan 2023 15:13:01 +0100 Subject: [PATCH 0262/1551] Update `VERSION` to `1.7.2-dev` (#12993) --- src/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VERSION b/src/VERSION index 943f9cbc4ec7..d69c06dee400 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.7.1 +1.7.2-dev From f1b73235402fcc8bb021d33621d8255507daa5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 23 Jan 2023 15:13:43 +0100 Subject: [PATCH 0263/1551] Fix: Add `Nil` return type restrictions to `load_debug_info` (#12992) --- src/exception/call_stack/dwarf.cr | 2 +- src/exception/call_stack/elf.cr | 2 +- src/exception/call_stack/mach_o.cr | 4 ++-- src/exception/call_stack/stackwalk.cr | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/exception/call_stack/dwarf.cr b/src/exception/call_stack/dwarf.cr index bf5a91c687e8..b52a0df2ac73 100644 --- a/src/exception/call_stack/dwarf.cr +++ b/src/exception/call_stack/dwarf.cr @@ -11,7 +11,7 @@ struct Exception::CallStack @@dwarf_function_names : Array(Tuple(LibC::SizeT, LibC::SizeT, String))? # :nodoc: - def self.load_debug_info + def self.load_debug_info : Nil return if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "0" unless @@dwarf_loaded diff --git a/src/exception/call_stack/elf.cr b/src/exception/call_stack/elf.cr index 6bcaf5eec2b7..9b75f545e670 100644 --- a/src/exception/call_stack/elf.cr +++ b/src/exception/call_stack/elf.cr @@ -4,7 +4,7 @@ require "crystal/elf" {% end %} struct Exception::CallStack - protected def self.load_debug_info_impl + protected def self.load_debug_info_impl : Nil base_address : LibC::Elf_Addr = 0 phdr_callback = LibC::DlPhdrCallback.new do |info, size, data| # The first entry is the header for the current program. diff --git a/src/exception/call_stack/mach_o.cr b/src/exception/call_stack/mach_o.cr index 5646511541a7..6945f201dbeb 100644 --- a/src/exception/call_stack/mach_o.cr +++ b/src/exception/call_stack/mach_o.cr @@ -9,11 +9,11 @@ end struct Exception::CallStack @@image_slide : LibC::Long? - protected def self.load_debug_info_impl + protected def self.load_debug_info_impl : Nil read_dwarf_sections end - protected def self.read_dwarf_sections + protected def self.read_dwarf_sections : Nil locate_dsym_bundle do |mach_o| line_strings = mach_o.read_section?("__debug_line_str") do |sh, io| Crystal::DWARF::Strings.new(io, sh.offset, sh.size) diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index 2a580b498eff..73b1060aa58b 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -7,7 +7,7 @@ struct Exception::CallStack @@sym_loaded = false - def self.load_debug_info + def self.load_debug_info : Nil return if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "0" unless @@sym_loaded @@ -20,7 +20,7 @@ struct Exception::CallStack end end - private def self.load_debug_info_impl + private def self.load_debug_info_impl : Nil # TODO: figure out if and when to call SymCleanup (it cannot be done in # `at_exit` because unhandled exceptions in `main_user_code` are printed # after those handlers) From 846fc1166ec9635d3d53e738a3b7ae7273304454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 23 Jan 2023 15:14:00 +0100 Subject: [PATCH 0264/1551] Add error handling to compiler when linker is unavailable (#12899) Fixes https://github.com/crystal-lang/crystal/issues/12839 --- src/compiler/crystal/compiler.cr | 61 +++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 0baaf2db0c08..3098050a0a0a 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -315,7 +315,8 @@ module Crystal target_machine.emit_obj_to_file llvm_mod, object_name end - print_command(*linker_command(program, [object_name], output_filename, nil)) + _, command, args = linker_command(program, [object_name], output_filename, nil) + print_command(command, args) end private def print_command(command, args) @@ -376,15 +377,15 @@ module Crystal cmd = "#{cl} #{Process.quote_windows("@" + args_filename)}" end - {cmd, nil} + {cl, cmd, nil} elsif program.has_flag? "wasm32" link_flags = @link_flags || "" - { %(wasm-ld "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} -lc #{program.lib_flags}), object_names } + {"wasm-ld", %(wasm-ld "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} -lc #{program.lib_flags}), object_names} else link_flags = @link_flags || "" link_flags += " -rdynamic" - { %(#{CC} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names } + {CC, %(#{CC} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names} end end @@ -416,21 +417,7 @@ module Crystal @progress_tracker.stage("Codegen (linking)") do Dir.cd(output_dir) do - linker_command = linker_command(program, object_names, output_filename, output_dir, expand: true) - - process_wrapper(*linker_command) do |command, args| - Process.run(command, args, shell: true, - input: Process::Redirect::Close, output: Process::Redirect::Inherit, error: Process::Redirect::Pipe) do |process| - process.error.each_line(chomp: false) do |line| - hint_string = colorize("(this usually means you need to install the development package for lib\\1)").yellow.bold - line = line.gsub(/cannot find -l(\S+)\b/, "cannot find -l\\1 #{hint_string}") - line = line.gsub(/unable to find library -l(\S+)\b/, "unable to find library -l\\1 #{hint_string}") - line = line.gsub(/library not found for -l(\S+)\b/, "library not found for -l\\1 #{hint_string}") - STDERR << line - end - end - $? - end + run_linker *linker_command(program, object_names, output_filename, output_dir, expand: true) end end @@ -604,18 +591,50 @@ module Crystal end end - private def process_wrapper(command, args = nil) + private def run_linker(linker_name, command, args) print_command(command, args) if verbose? - status = yield command, args + begin + Process.run(command, args, shell: true, + input: Process::Redirect::Close, output: Process::Redirect::Inherit, error: Process::Redirect::Pipe) do |process| + process.error.each_line(chomp: false) do |line| + hint_string = colorize("(this usually means you need to install the development package for lib\\1)").yellow.bold + line = line.gsub(/cannot find -l(\S+)\b/, "cannot find -l\\1 #{hint_string}") + line = line.gsub(/unable to find library -l(\S+)\b/, "unable to find library -l\\1 #{hint_string}") + line = line.gsub(/library not found for -l(\S+)\b/, "library not found for -l\\1 #{hint_string}") + STDERR << line + end + end + rescue exc : File::AccessDeniedError | File::NotFoundError + linker_not_found exc.class, linker_name + end + status = $? unless status.success? + if status.normal_exit? + case status.exit_code + when 126 + linker_not_found File::AccessDeniedError, linker_name + when 127 + linker_not_found File::NotFoundError, linker_name + end + end msg = status.normal_exit? ? "code: #{status.exit_code}" : "signal: #{status.exit_signal} (#{status.exit_signal.value})" code = status.normal_exit? ? status.exit_code : 1 error "execution of command failed with #{msg}: `#{command}`", exit_code: code end end + private def linker_not_found(exc_class, linker_name) + verbose_info = "\nRun with `--verbose` to print the full linker command." unless verbose? + case exc_class + when File::AccessDeniedError + error "Could not execute linker: `#{linker_name}`: Permission denied#{verbose_info}" + else + error "Could not execute linker: `#{linker_name}`: File not found#{verbose_info}" + end + end + private def error(msg, exit_code = 1) Crystal.error msg, @color, exit_code, stderr: stderr end From 29f9ac503fb028e1b095c8f8e036ed74b5474550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 23 Jan 2023 18:05:17 +0100 Subject: [PATCH 0265/1551] Add changelog for 1.7.2 (#12995) --- CHANGELOG.md | 21 +++++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3266c2c4d2bd..faa5d173025b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +# 1.7.2 (2023-01-23) +## Standard Library + +### Runtime + +- Fix: Add `Nil` return type restrictions to `load_debug_info` ([#12992](https://github.com/crystal-lang/crystal/pull/12992), thanks @straight-shoota) + +## Compiler + +### Codegen + +- Add error handling to compiler when linker is unavailable ([#12899](https://github.com/crystal-lang/crystal/pull/12899), thanks @straight-shoota) + +### Parser + +- Revert "Parser: Fix restrict grammar for name and supertype in type def (#12622)" ([#12977](https://github.com/crystal-lang/crystal/pull/12977), thanks @straight-shoota) + +## Other + +- Update `VERSION` to `1.7.2-dev` ([#12993](https://github.com/crystal-lang/crystal/pull/12993), thanks @straight-shoota) + # 1.7.1 (2023-01-17) ## Tools diff --git a/src/VERSION b/src/VERSION index d69c06dee400..f8a696c8dc56 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.7.2-dev +1.7.2 From 498abb42969b8ab11bba98323bae74447d585493 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Tue, 24 Jan 2023 16:10:53 -0500 Subject: [PATCH 0266/1551] Skip hostname spec if `hostname` command fails (#12987) --- spec/std/system_spec.cr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/std/system_spec.cr b/spec/std/system_spec.cr index f0969bb60944..9a64926d6e2f 100644 --- a/spec/std/system_spec.cr +++ b/spec/std/system_spec.cr @@ -5,7 +5,8 @@ describe System do describe "hostname" do pending_win32 "returns current hostname" do shell_hostname = `hostname`.strip - $?.success?.should be_true # The hostname command has to be available + pending! "`hostname` command was unsuccessful" unless $?.success? + hostname = System.hostname hostname.should eq(shell_hostname) end From c4f3dbfae14f066f0a38e7796f360eb069ebf122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 24 Jan 2023 22:11:08 +0100 Subject: [PATCH 0267/1551] Fix error handling in macro system method when execution fails (#12893) --- spec/compiler/macro/macro_methods_spec.cr | 8 +------- src/compiler/crystal/macros/methods.cr | 11 ++++++++++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 427304789bb2..b72f95a31678 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -3208,13 +3208,7 @@ module Crystal describe ".system" do it "command does not exist" do - # FIXME: This inconsistency between Windows and POSIX is tracked in #12873 - expect_raises( - {{ flag?(:win32) ? File::NotFoundError : Crystal::TypeException }}, - {{ flag?(:win32) ? "Error executing process: 'commanddoesnotexist'" : "error executing command: commanddoesnotexist" }} - ) do - semantic %({{ `commanddoesnotexist` }}) - end + assert_error %({{ `commanddoesnotexist` }}), "error executing command: commanddoesnotexist" end it "successful command" do diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index ff5279bfa022..d67f94e3ac84 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -232,7 +232,16 @@ module Crystal end cmd = cmd.join " " - result = `#{cmd}` + begin + result = `#{cmd}` + rescue exc : File::Error | IO::Error + # Taking the os_error message to avoid duplicating the "error executing process: " + # prefix of the error message and ensure uniqueness between all error messages. + node.raise "error executing command: #{cmd}: #{exc.os_error.try(&.message) || exc.message}" + rescue exc + node.raise "error executing command: #{cmd}: #{exc.message}" + end + if $?.success? @last = MacroId.new(result) elsif result.empty? From 01d5397d02c61928673105f4c45223c1d7a8160a Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 24 Jan 2023 18:12:34 -0300 Subject: [PATCH 0268/1551] Formatter: fix end indent after comment inside begin (#12994) Fixes https://github.com/crystal-lang/crystal/issues/12964 --- spec/compiler/formatter/formatter_spec.cr | 10 ++++++++++ src/compiler/crystal/tools/formatter.cr | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 467f25a44a2c..2b4b8c79434d 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -2416,4 +2416,14 @@ describe Crystal::Formatter do end end CRYSTAL + + # #12964 + assert_format <<-CRYSTAL + begin + begin + a + # b + end + end + CRYSTAL end diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 4ac5e9ee37aa..e297c7bdb1f2 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -319,8 +319,14 @@ module Crystal @indent = old_indent if has_newline && !last_found_comment && (!@wrote_newline || empty_expressions) + # Right after the last expression but we didn't insert a newline yet + # so we are still missing a newline following by an indent and the "end" keyword write_line write_indent + elsif has_newline && last_found_comment && @wrote_newline + # Right after the last expression and we did insert a newline (because of a comment) + # so we are still missing an indent and the "end" keyword + write_indent end if has_paren From 4156601e88fecd15f59111d9f30658c26d7a7241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 25 Jan 2023 13:04:09 +0100 Subject: [PATCH 0269/1551] Avoid `Array` allocation in `Channel.select(Tuple)` (#12960) --- src/channel.cr | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/channel.cr b/src/channel.cr index 999e0af6855d..74010d9c2c15 100644 --- a/src/channel.cr +++ b/src/channel.cr @@ -431,6 +431,11 @@ class Channel(T) # * `StaticArray`: This avoids a heap allocation because we can dup a # static array on the stack. ops_locks = ops.dup + elsif ops.responds_to?(:to_static_array) + # If the collection type implements `to_static_array` we can create a + # copy without allocating an array. This applies to `Tuple` types, which + # the compiler generates for `select` expressions. + ops_locks = ops.to_static_array else ops_locks = ops.to_a end From 8fd0e98675464367721164dfdbdbc415e9276da6 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 25 Jan 2023 15:49:19 -0300 Subject: [PATCH 0270/1551] Fix redundant cast in interpreter (#12996) Fixes https://github.com/crystal-lang/crystal/issues/12954 --- spec/compiler/interpreter/primitives_spec.cr | 18 ++ spec/compiler/interpreter/procs_spec.cr | 10 ++ spec/compiler/interpreter/tuple_spec.cr | 9 + .../crystal/interpreter/primitives.cr | 160 ++++++++++-------- 4 files changed, 125 insertions(+), 72 deletions(-) diff --git a/spec/compiler/interpreter/primitives_spec.cr b/spec/compiler/interpreter/primitives_spec.cr index 05abbaea5660..2ab29a793089 100644 --- a/spec/compiler/interpreter/primitives_spec.cr +++ b/spec/compiler/interpreter/primitives_spec.cr @@ -852,5 +852,23 @@ describe Crystal::Repl::Interpreter do !MyClass(Int32) CRYSTAL end + + it "does math primitive on union" do + interpret(<<-CRYSTAL).should eq(3) + module Test; end + + a = 1 + a.as(Int32 | Test) &+ 2 + CRYSTAL + end + + it "does math convert on union" do + interpret(<<-CRYSTAL).should eq(1) + module Test; end + + a = 1 + a.as(Int32 | Test).to_i64! + CRYSTAL + end end end diff --git a/spec/compiler/interpreter/procs_spec.cr b/spec/compiler/interpreter/procs_spec.cr index 2eca72820607..4bc668cea5c5 100644 --- a/spec/compiler/interpreter/procs_spec.cr +++ b/spec/compiler/interpreter/procs_spec.cr @@ -114,4 +114,14 @@ describe Crystal::Repl::Interpreter do ->{ 42 }.foo.call CRYSTAL end + + it "calls proc primitive on union of module that has no subtypes (#12954)" do + interpret(<<-CRYSTAL).should eq(42) + module Test + end + + proc = ->{ 42 } + proc.as(Proc(Int32) | Test).call + CRYSTAL + end end diff --git a/spec/compiler/interpreter/tuple_spec.cr b/spec/compiler/interpreter/tuple_spec.cr index 15ea8d5dea00..7a27eb9cc2cf 100644 --- a/spec/compiler/interpreter/tuple_spec.cr +++ b/spec/compiler/interpreter/tuple_spec.cr @@ -128,6 +128,15 @@ describe Crystal::Repl::Interpreter do 1 + ({1, 2, 3, 4}; 2) CRYSTAL end + + it "does tuple indexer on union" do + interpret(<<-CRYSTAL).should eq(1) + module Test; end + + a = {1} + a.as(Tuple(Int32) | Test)[0] + CRYSTAL + end end end diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr index e89a3c7fae36..2063830e0eed 100644 --- a/src/compiler/crystal/interpreter/primitives.cr +++ b/src/compiler/crystal/interpreter/primitives.cr @@ -7,15 +7,16 @@ require "./compiler" class Crystal::Repl::Compiler private def visit_primitive(node, body, target_def) + owner = node.super? ? node.scope : node.target_def.owner obj = node.obj case body.name when "unchecked_convert" - primitive_convert(node, body, checked: false) + primitive_convert(node, body, owner, checked: false) when "convert" - primitive_convert(node, body, checked: true) + primitive_convert(node, body, owner, checked: true) when "binary" - primitive_binary(node, body) + primitive_binary(node, body, owner) when "pointer_new" accept_call_members(node) return false unless @wants_value @@ -25,30 +26,28 @@ class Crystal::Repl::Compiler discard_value(obj) if obj request_value(node.args.first) - scope_type = ((obj.try &.type) || scope).instance_type - - pointer_instance_type = scope_type.instance_type.as(PointerInstanceType) + pointer_instance_type = owner.instance_type.as(PointerInstanceType) element_type = pointer_instance_type.element_type element_size = inner_sizeof_type(element_type) pointer_malloc(element_size, node: node) - pop(aligned_sizeof_type(scope_type), node: nil) unless @wants_value + pop(aligned_sizeof_type(pointer_instance_type), node: nil) unless @wants_value when "pointer_realloc" obj ? request_value(obj) : put_self(node: node) request_value(node.args.first) - scope_type = (obj.try &.type) || scope - - pointer_instance_type = scope_type.instance_type.as(PointerInstanceType) + pointer_instance_type = owner.instance_type.as(PointerInstanceType) element_type = pointer_instance_type.element_type element_size = inner_sizeof_type(element_type) pointer_realloc(element_size, node: node) - pop(aligned_sizeof_type(scope_type), node: nil) unless @wants_value + pop(aligned_sizeof_type(pointer_instance_type), node: nil) unless @wants_value when "pointer_set" # Accept in reverse order so that it's easier for the interpreter obj = obj.not_nil! - element_type = obj.type.as(PointerInstanceType).element_type + + pointer_instance_type = owner.instance_type.as(PointerInstanceType) + element_type = pointer_instance_type.element_type arg = node.args.first request_value(arg) @@ -59,7 +58,8 @@ class Crystal::Repl::Compiler pointer_set(inner_sizeof_type(element_type), node: node) when "pointer_get" - element_type = obj.not_nil!.type.as(PointerInstanceType).element_type + pointer_instance_type = owner.instance_type.as(PointerInstanceType) + element_type = pointer_instance_type.element_type accept_call_members(node) return unless @wants_value @@ -74,12 +74,18 @@ class Crystal::Repl::Compiler accept_call_members(node) return unless @wants_value - pointer_diff(inner_sizeof_type(obj.not_nil!.type.as(PointerInstanceType).element_type), node: node) + pointer_instance_type = owner.instance_type.as(PointerInstanceType) + element_type = pointer_instance_type.element_type + + pointer_diff(inner_sizeof_type(element_type), node: node) when "pointer_add" accept_call_members(node) return unless @wants_value - pointer_add(inner_sizeof_type(obj.not_nil!.type.as(PointerInstanceType).element_type), node: node) + pointer_instance_type = owner.instance_type.as(PointerInstanceType) + element_type = pointer_instance_type.element_type + + pointer_add(inner_sizeof_type(element_type), node: node) when "class" obj = obj.not_nil! type = obj.type.remove_indirection @@ -102,23 +108,21 @@ class Crystal::Repl::Compiler put_type type, node: node end when "object_crystal_type_id" - type = obj.try(&.type) || scope - unless @wants_value discard_value obj if obj return end - if type.is_a?(VirtualMetaclassType) + if owner.is_a?(VirtualMetaclassType) # For a virtual metaclass type, the value is already an int # that's exactly the crystal_type_id, so there's nothing else to do. if obj - obj.accept self + request_obj_and_cast_if_needed(obj, owner) else put_self node: node end else - put_i32 type_id(type), node: node + put_i32 type_id(owner), node: node end when "class_crystal_instance_type_id" type = @@ -173,14 +177,15 @@ class Crystal::Repl::Compiler end end when "tuple_indexer_known_index" - obj = obj.not_nil! + unless @wants_value + accept_call_members(node) + return + end - type = obj.type + type = owner case type when TupleInstanceType - obj.accept self - return unless @wants_value - + request_obj_or_self_and_cast_if_needed(node, obj, type) index = body.as(TupleIndexer).index case index in Int32 @@ -201,9 +206,7 @@ class Crystal::Repl::Compiler push_zeros(aligned_sizeof_type(element_type) - value_size, node: node) end when NamedTupleInstanceType - obj.accept self - return unless @wants_value - + request_obj_or_self_and_cast_if_needed(node, obj, type) index = body.as(TupleIndexer).index case index when Int32 @@ -214,9 +217,7 @@ class Crystal::Repl::Compiler node.raise "BUG: missing handling of primitive #{body.name} with range" end else - discard_value obj - return unless @wants_value - + discard_value obj if obj type = type.instance_type case type when TupleInstanceType @@ -254,7 +255,7 @@ class Crystal::Repl::Compiler pointer_address(node: node) when "proc_call" - proc_type = (obj.try(&.type) || scope).as(ProcInstanceType) + proc_type = owner.as(ProcInstanceType) node.args.each_with_index do |arg, arg_index| request_value(arg) @@ -268,7 +269,11 @@ class Crystal::Repl::Compiler end end - obj ? request_value(obj) : put_self(node: node) + if obj + request_obj_and_cast_if_needed(obj, owner) + else + put_self(node: node) + end proc_call(node: node) @@ -634,21 +639,16 @@ class Crystal::Repl::Compiler node.args.each { |arg| request_value(arg) } end - private def primitive_convert(node : ASTNode, body : Primitive, checked : Bool) + private def primitive_convert(node : ASTNode, body : Primitive, owner : Type, checked : Bool) obj = node.obj - return false if !obj && !@wants_value - - obj_type = - if obj - obj.accept self - obj.type - else - put_self(node: node) - scope - end + unless @wants_value + discard_value(obj) if obj + return + end - return false unless @wants_value + obj_type = owner + request_obj_or_self_and_cast_if_needed(node, obj, obj_type) target_type = body.type @@ -835,30 +835,29 @@ class Crystal::Repl::Compiler end end - private def primitive_binary(node, body) + private def primitive_binary(node, body, owner) unless @wants_value - node.obj.try &.accept self - node.args.each &.accept self + accept_call_members(node) return end case node.name when "+", "&+", "-", "&-", "*", "&*", "^", "|", "&", "unsafe_shl", "unsafe_shr", "unsafe_div", "unsafe_mod" - primitive_binary_op_math(node, body, node.name) + primitive_binary_op_math(node, body, owner, node.name) when "<", "<=", ">", ">=", "==", "!=" - primitive_binary_op_cmp(node, body, node.name) + primitive_binary_op_cmp(node, body, owner, node.name) when "/", "fdiv" - primitive_binary_float_div(node, body) + primitive_binary_float_div(node, body, owner) else node.raise "BUG: missing handling of binary op #{node.name}" end end - private def primitive_binary_op_math(node : ASTNode, body : Primitive, op : String) + private def primitive_binary_op_math(node : ASTNode, body : Primitive, owner : Type, op : String) obj = node.obj arg = node.args.first - obj_type = obj.try(&.type) || scope + obj_type = owner arg_type = arg.type primitive_binary_op_math(obj_type, arg_type, obj, arg, node, op) @@ -871,7 +870,7 @@ class Crystal::Repl::Compiler in .mixed64? if left_type.rank > right_type.rank # It's UInt64 op X where X is a signed integer - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) right_node.accept self # TODO: do we need to check for overflow here? @@ -898,7 +897,7 @@ class Crystal::Repl::Compiler kind = NumberKind::U64 else # It's X op UInt64 where X is a signed integer - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) # TODO: do we need to check for overflow here? primitive_convert(node, left_type.kind, :i64, checked: false) @@ -927,7 +926,7 @@ class Crystal::Repl::Compiler in .mixed128? if left_type.rank > right_type.rank # It's UInt128 op X where X is a signed integer - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) right_node.accept self # TODO: do we need to check for overflow here? @@ -954,7 +953,7 @@ class Crystal::Repl::Compiler kind = NumberKind::U128 else # It's X op UInt128 where X is a signed integer - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) # TODO: do we need to check for overflow here? primitive_convert(node, left_type.kind, :i128, checked: false) @@ -995,7 +994,7 @@ class Crystal::Repl::Compiler end private def primitive_binary_op_math(left_type : IntegerType, right_type : FloatType, left_node : ASTNode?, right_node : ASTNode, node : ASTNode, op : String) - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) primitive_convert node, left_type.kind, right_type.kind, checked: false right_node.accept self @@ -1003,7 +1002,7 @@ class Crystal::Repl::Compiler end private def primitive_binary_op_math(left_type : FloatType, right_type : IntegerType, left_node : ASTNode?, right_node : ASTNode, node : ASTNode, op : String) - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) right_node.accept self primitive_convert right_node, right_type.kind, left_type.kind, checked: false @@ -1012,18 +1011,18 @@ class Crystal::Repl::Compiler private def primitive_binary_op_math(left_type : FloatType, right_type : FloatType, left_node : ASTNode?, right_node : ASTNode, node : ASTNode, op : String) if left_type == right_type - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) right_node.accept self kind = left_type.kind elsif left_type.rank < right_type.rank # TODO: not tested - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) primitive_convert node, left_type.kind, right_type.kind, checked: false right_node.accept self kind = right_type.kind else # TODO: not tested - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) right_node.accept self primitive_convert right_node, right_type.kind, left_type.kind, checked: false kind = left_type.kind @@ -1172,11 +1171,11 @@ class Crystal::Repl::Compiler node.raise "BUG: primitive_binary_op_math called with #{left_type} #{op} #{right_type}" end - private def primitive_binary_op_cmp(node : ASTNode, body : Primitive, op : String) + private def primitive_binary_op_cmp(node : ASTNode, body : Primitive, owner : Type, op : String) obj = node.obj.not_nil! arg = node.args.first - obj_type = obj.type + obj_type = owner arg_type = arg.type primitive_binary_op_cmp(obj_type, arg_type, obj, arg, node, op) @@ -1356,7 +1355,7 @@ class Crystal::Repl::Compiler if left_type.rank <= 5 && right_type.rank <= 5 # If both fit in an Int32 # Convert them to Int32 first, then do the comparison - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) primitive_convert(left_node || right_node, left_type.kind, :i32, checked: false) if left_type.rank < 5 right_node.accept self @@ -1365,16 +1364,16 @@ class Crystal::Repl::Compiler NumberKind::I32 elsif left_type.signed? == right_type.signed? if left_type.rank == right_type.rank - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) right_node.accept self left_type.kind elsif left_type.rank < right_type.rank - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) primitive_convert(left_node || right_node, left_type.kind, right_type.kind, checked: false) right_node.accept self right_type.kind else - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) right_node.accept self primitive_convert(right_node, right_type.kind, left_type.kind, checked: false) left_type.kind @@ -1382,7 +1381,7 @@ class Crystal::Repl::Compiler elsif left_type.rank <= 7 && right_type.rank <= 7 # If both fit in an Int64 # Convert them to Int64 first, then do the comparison - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) primitive_convert(left_node || right_node, left_type.kind, :i64, checked: false) if left_type.rank < 7 right_node.accept self @@ -1394,7 +1393,7 @@ class Crystal::Repl::Compiler elsif left_type.rank <= 9 && right_type.rank <= 9 # If both fit in an Int128 # Convert them to Int128 first, then do the comparison - left_node ? left_node.accept(self) : put_self(node: node) + request_obj_or_self_and_cast_if_needed(node, left_node, left_type) primitive_convert(left_node || right_node, left_type.kind, :i128, checked: false) if left_type.rank < 9 right_node.accept self @@ -1406,12 +1405,12 @@ class Crystal::Repl::Compiler end end - private def primitive_binary_float_div(node : ASTNode, body) + private def primitive_binary_float_div(node : ASTNode, body, owner : Type) # TODO: don't assume Float64 op Float64 obj = node.obj.not_nil! arg = node.args.first - obj_type = obj.type + obj_type = owner arg_type = arg.type obj_kind = integer_or_float_kind(obj_type).not_nil! @@ -1455,4 +1454,21 @@ class Crystal::Repl::Compiler nil end end + + private def request_obj_and_cast_if_needed(obj, owner) + request_value(obj) + + obj_type = obj.try &.type?.try &.remove_indirection + if obj_type && obj_type != owner + downcast(obj, obj_type, owner) + end + end + + private def request_obj_or_self_and_cast_if_needed(node, obj, owner) + if obj + request_obj_and_cast_if_needed(obj, owner) + else + put_self(node: node) + end + end end From 9fca36b05b3af55b11f2b676290d3762c6470dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 25 Jan 2023 19:49:35 +0100 Subject: [PATCH 0271/1551] Add `Enum.[]` convenience constructor (#12900) --- spec/std/enum_spec.cr | 26 ++++++++++++++++++++++++++ src/enum.cr | 24 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/spec/std/enum_spec.cr b/spec/std/enum_spec.cr index 0fd244ead5ec..fe5c36c89ddc 100644 --- a/spec/std/enum_spec.cr +++ b/spec/std/enum_spec.cr @@ -275,6 +275,32 @@ describe Enum do SpecEnum::One.clone.should eq(SpecEnum::One) end + describe ".[]" do + it "non-flags enum" do + SpecEnum[].should be_nil + SpecEnum[One].should eq SpecEnum::One + SpecEnum[1].should eq SpecEnum::Two + SpecEnum[One, Two].should eq SpecEnum::One | SpecEnum::Two + SpecEnum[One, :two].should eq SpecEnum::One | SpecEnum::Two + SpecEnum[One, 1].should eq SpecEnum::One | SpecEnum::Two + end + + it "flags enum" do + SpecEnumFlags.flags.should be_nil + SpecEnumFlags[One].should eq SpecEnumFlags::One + SpecEnumFlags[2].should eq SpecEnumFlags::Two + SpecEnumFlags[One, Two].should eq SpecEnumFlags::One | SpecEnumFlags::Two + SpecEnumFlags[One, :two].should eq SpecEnumFlags::One | SpecEnumFlags::Two + SpecEnumFlags[One, 2].should eq SpecEnumFlags::One | SpecEnumFlags::Two + end + + it "private flags enum" do + PrivateFlagsEnum.flags.should be_nil + PrivateFlagsEnum[FOO].should eq PrivateFlagsEnum::FOO + PrivateFlagsEnum[FOO, BAR].should eq PrivateFlagsEnum::FOO | PrivateFlagsEnum::BAR + end + end + describe ".flags" do it "non-flags enum" do SpecEnum.flags.should be_nil diff --git a/src/enum.cr b/src/enum.cr index 6619ad1fe9ee..db6404e0a563 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -497,12 +497,36 @@ struct Enum # ``` # IOMode.flags(Read, Write) # => IOMode::Read | IOMode::Write # ``` + # + # * `Enum.[]` is a more advanced alternative which also allows int and symbol parameters. macro flags(*values) {% for value, i in values %}\ {% if i != 0 %} | {% end %}\ {{ @type }}::{{ value }}{% end %}\ end + # Convenience macro to create a combined enum (combines given members using `|` (or) logical operator). + # + # Arguments can be the name of a member, a symbol representing a member name or a numerical value. + # + # ``` + # IOMode[Read] # => IOMode[Read] + # IOMode[1] # => IOMode[Read] + # IOMode[Read, Write] # => IOMode[Read, Write] + # IOMode[Read, 64] # => IOMode[Read, 64] + # IOMode[Read, :write, 64] # => IOMode[Read, Write, 64] + # ``` + macro [](*values) + {% for value, i in values %}\ + {% if i != 0 %} | {% end %}\ + {% if value.is_a?(Path) %} \ + {{ @type }}::{{ value }} \ + {% else %} \ + {{ @type }}.new({{value}}) \ + {% end %} \ + {% end %}\ + end + # Iterates each member of the enum. # It won't iterate the `None` and `All` members of flags enums. # From 8e1a9a88889ec3c4133402983b52b21aeb003679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 25 Jan 2023 19:50:08 +0100 Subject: [PATCH 0272/1551] Add `MIME::Multipart.parse(HTTP::Client::Response, &)` (#12890) Co-authored-by: Jamie Gaskins --- spec/std/mime/multipart_spec.cr | 15 +++++++++++ src/mime/multipart.cr | 45 ++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/spec/std/mime/multipart_spec.cr b/spec/std/mime/multipart_spec.cr index b3fd52f2fac8..5aef9a98634c 100644 --- a/spec/std/mime/multipart_spec.cr +++ b/spec/std/mime/multipart_spec.cr @@ -35,5 +35,20 @@ describe MIME::Multipart do io.gets_to_end.should eq("body") end end + + it "parses multipart messages from HTTP client responses" do + headers = HTTP::Headers{"Content-Type" => "multipart/byteranges; boundary=aA40"} + body = "--aA40\r\nContent-Type: text/plain\r\n\r\nbody\r\n--aA40--" + response = HTTP::Client::Response.new( + status: :ok, + headers: headers, + body: body, + ) + + MIME::Multipart.parse(response) do |headers, io| + headers["Content-Type"].should eq("text/plain") + io.gets_to_end.should eq("body") + end + end end end diff --git a/src/mime/multipart.cr b/src/mime/multipart.cr index 89175b763ca9..943af6a15167 100644 --- a/src/mime/multipart.cr +++ b/src/mime/multipart.cr @@ -69,7 +69,9 @@ module MIME::Multipart # # See: `Multipart::Parser` def self.parse(request : HTTP::Request, &) - boundary = parse_boundary(request.headers["Content-Type"]) + if content_type = request.headers["Content-Type"]? + boundary = parse_boundary(content_type) + end return nil unless boundary body = request.body @@ -77,6 +79,47 @@ module MIME::Multipart parse(body, boundary) { |headers, io| yield headers, io } end + # Parses a MIME multipart message, yielding `HTTP::Headers` and an `IO` for + # each body part. + # + # Please note that the IO object yielded to the block is only valid while the + # block is executing. The IO is closed as soon as the supplied block returns. + # + # ``` + # require "http" + # require "mime/multipart" + # + # headers = HTTP::Headers{"Content-Type" => "multipart/byteranges; boundary=aA40"} + # body = "--aA40\r\nContent-Type: text/plain\r\n\r\nbody\r\n--aA40--" + # response = HTTP::Client::Response.new( + # status: :ok, + # headers: headers, + # body: body, + # ) + # + # MIME::Multipart.parse(response) do |headers, io| + # headers["Content-Type"] # => "text/plain" + # io.gets_to_end # => "body" + # end + # ``` + # + # See: `Multipart::Parser` + def self.parse(response : HTTP::Client::Response, &) + if content_type = response.headers["Content-Type"]? + boundary = parse_boundary(content_type) + end + return nil unless boundary + + if body = response.body.presence + body = IO::Memory.new(body) + else + body = response.body_io? + end + return nil unless body + + parse(body, boundary) { |headers, io| yield headers, io } + end + # Yields a `Multipart::Builder` to the given block, writing to *io* and # using *boundary*. `#finish` is automatically called on the builder. def self.build(io : IO, boundary : String = Multipart.generate_boundary, &) From fff2d480dcc8693b5337e8e45a083abbda31a701 Mon Sep 17 00:00:00 2001 From: Nicolas Ganz Date: Thu, 26 Jan 2023 13:59:02 +0100 Subject: [PATCH 0273/1551] Prevent infinitely recursive wrapper script (#11712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Beta Ziliani Co-authored-by: Johannes Müller --- bin/crystal | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/bin/crystal b/bin/crystal index a4bddb1ddf2a..4f0301544688 100755 --- a/bin/crystal +++ b/bin/crystal @@ -146,11 +146,22 @@ export CRYSTAL_HAS_WRAPPER=true export CRYSTAL="${CRYSTAL:-"crystal"}" +# check if the crystal command refers to this script +if [ "$(realpath "$(command -v "$CRYSTAL")")" = "$SCRIPT_PATH" ]; then + # remove the path to this script from PATH + NEW_PATH="$(remove_path_item "$(remove_path_item "$PATH" "$SCRIPT_ROOT")" "bin")" + # if the PATH did not change it will lead to an infinite recursion => display error + if [ "$NEW_PATH" = "$PATH" ]; then + __error_msg 'Could not remove the script bin/crystal from the PATH. Remove it by hand or set the CRYSTAL env variable' + exit 1 + fi + # re-execute this script with the cleaned up PATH + export PATH="$NEW_PATH" + exec "$SCRIPT_PATH" "$@" +fi + if [ -z "$CRYSTAL_CONFIG_LIBRARY_PATH" ] || [ -z "$CRYSTAL_LIBRARY_PATH" ]; then - CRYSTAL_INSTALLED_LIBRARY_PATH="$( - export PATH="$(remove_path_item "$(remove_path_item "$PATH" "$SCRIPT_ROOT")" "bin")" - crystal env CRYSTAL_LIBRARY_PATH 2> /dev/null || echo "" - )" + CRYSTAL_INSTALLED_LIBRARY_PATH="$($CRYSTAL env CRYSTAL_LIBRARY_PATH || echo "")" export CRYSTAL_LIBRARY_PATH=${CRYSTAL_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} export CRYSTAL_CONFIG_LIBRARY_PATH=${CRYSTAL_CONFIG_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} fi @@ -161,9 +172,6 @@ if [ -x "$CRYSTAL_DIR/crystal" ]; then elif ! command -v $CRYSTAL > /dev/null; then __error_msg 'You need to have a crystal executable in your path! or set CRYSTAL env variable' exit 1 -elif [ "$(command -v $CRYSTAL)" = "$SCRIPT_PATH" ] || [ "$(command -v $CRYSTAL)" = "bin/crystal" ]; then - export PATH="$(remove_path_item "$(remove_path_item "$PATH" "$SCRIPT_ROOT")" "bin")" - exec "$SCRIPT_PATH" "$@" else exec $CRYSTAL "$@" fi From f2c0b3e2efcac49eb6f82ed946bc3d918c0a4273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 26 Jan 2023 21:42:31 +0100 Subject: [PATCH 0274/1551] Update previous Crystal release - 1.7.2 (#13001) --- .circleci/config.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 496928625134..c84f70121072 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.7.0/crystal-1.7.0-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.7.2/crystal-1.7.2-1" defaults: environment: &env diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 663ad7bcea6e..552e1cc035fd 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.1, 1.6.2, 1.7.0] + crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.1, 1.6.2, 1.7.2] include: # libffi is only available starting from the 1.2.2 build images - crystal_bootstrap_version: 1.0.0 diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 2073783890c8..6a0b5265e293 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -6,7 +6,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.7.0-alpine + container: crystallang/crystal:1.7.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -23,7 +23,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.7.0-alpine + container: crystallang/crystal:1.7.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -36,7 +36,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.7.0-alpine + container: crystallang/crystal:1.7.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index c5262e6d3225..c75093d01378 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -6,7 +6,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.7.0-alpine + container: crystallang/crystal:1.7.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -17,7 +17,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.7.0-alpine + container: crystallang/crystal:1.7.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 9d5b459447a3..07da5682910c 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -8,7 +8,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.7.0-build + container: crystallang/crystal:1.7.2-build steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 2020681daa4c..e06a0ef88f03 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: x86_64-linux-job: runs-on: ubuntu-latest - container: crystallang/crystal:1.7.0-build + container: crystallang/crystal:1.7.2-build steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/bin/ci b/bin/ci index 5b4d93017b1b..60bec5b85088 100755 --- a/bin/ci +++ b/bin/ci @@ -134,8 +134,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.7.0/crystal-1.7.0-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.7.0-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.7.2/crystal-1.7.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.7.2-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -188,7 +188,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.7.0}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.7.2}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 8b3ffaab8e84..deab6682f02d 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.7.0/crystal-1.7.0-1-darwin-universal.tar.gz"; - sha256 = "sha256:1wpghg24xjr27xqh3q3avpk04fxxm6salar85v672k4s3xf5rjrz"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.7.2/crystal-1.7.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:04x30yxib5xnpddvabywr0696biq1v2wak2jfxkiqhc9zikh2cfn"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.7.0/crystal-1.7.0-1-darwin-universal.tar.gz"; - sha256 = "sha256:1wpghg24xjr27xqh3q3avpk04fxxm6salar85v672k4s3xf5rjrz"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.7.2/crystal-1.7.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:04x30yxib5xnpddvabywr0696biq1v2wak2jfxkiqhc9zikh2cfn"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.7.0/crystal-1.7.0-1-linux-x86_64.tar.gz"; - sha256 = "sha256:1d4wcggd32a3h3f7fzkfwlfanwp9lljmh2x5a9gwdf6lblllmkfy"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.7.2/crystal-1.7.2-1-linux-x86_64.tar.gz"; + sha256 = "sha256:0mk3mszvlyh1d3j1apagz3bhidwpyhbzmk0hbnz2mshb2fk9dxly"; }; }.${pkgs.stdenv.system}); From b6c49c400a351095df051871db3a88504a64c791 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 28 Jan 2023 21:24:33 +0800 Subject: [PATCH 0275/1551] Define equality for `Process::Status` and `OAuth::RequestToken` (#13014) --- spec/std/oauth/request_token_spec.cr | 36 ++++++++++++++++++++++++++++ spec/std/process/status_spec.cr | 17 +++++++++++++ src/oauth/request_token.cr | 2 ++ src/process/status.cr | 2 ++ 4 files changed, 57 insertions(+) diff --git a/spec/std/oauth/request_token_spec.cr b/spec/std/oauth/request_token_spec.cr index 33608b220c5f..d4415392715e 100644 --- a/spec/std/oauth/request_token_spec.cr +++ b/spec/std/oauth/request_token_spec.cr @@ -7,4 +7,40 @@ describe OAuth::RequestToken do token.secret.should eq("p58A6bzyGaT8PR54gM0S4ZesOVC2ManiTmwHcho8") token.token.should eq("qyprd6Pe2PbnSxUcyHcWz0VnTF8bg1rxsBbUwOpkQ6bSQEyK") end + + describe "equality" do + it "checks token" do + foo1 = OAuth::RequestToken.new("foo", "secret") + foo2 = OAuth::RequestToken.new("foo", "secret") + bar1 = OAuth::RequestToken.new("bar", "secret") + bar2 = OAuth::RequestToken.new("bar", "secret") + + foo1.should eq(foo2) + foo1.should_not eq(bar2) + bar1.should_not eq(foo2) + bar1.should eq(bar2) + + foo1.hash.should eq(foo2.hash) + foo1.hash.should_not eq(bar2.hash) + bar1.hash.should_not eq(foo2.hash) + bar1.hash.should eq(bar2.hash) + end + + it "checks secret" do + foo1 = OAuth::RequestToken.new("token", "foo") + foo2 = OAuth::RequestToken.new("token", "foo") + bar1 = OAuth::RequestToken.new("token", "bar") + bar2 = OAuth::RequestToken.new("token", "bar") + + foo1.should eq(foo2) + foo1.should_not eq(bar2) + bar1.should_not eq(foo2) + bar1.should eq(bar2) + + foo1.hash.should eq(foo2.hash) + foo1.hash.should_not eq(bar2.hash) + bar1.hash.should_not eq(foo2.hash) + bar1.hash.should eq(bar2.hash) + end + end end diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 48c7672685d6..3bae7bc60a9c 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -41,6 +41,23 @@ describe Process::Status do Process::Status.new(exit_status(255)).signal_exit?.should be_false end + it "equality" do + ok1 = Process::Status.new(exit_status(0)) + ok2 = Process::Status.new(exit_status(0)) + err1 = Process::Status.new(exit_status(1)) + err2 = Process::Status.new(exit_status(1)) + + ok1.should eq(ok2) + ok1.should_not eq(err2) + err1.should_not eq(ok2) + err1.should eq(err2) + + ok1.hash.should eq(ok2.hash) + ok1.hash.should_not eq(err2.hash) + err1.hash.should_not eq(ok2.hash) + err1.hash.should eq(err2.hash) + end + {% if flag?(:unix) && !flag?(:wasi) %} it "#exit_signal" do Process::Status.new(Signal::HUP.value).exit_signal.should eq Signal::HUP diff --git a/src/oauth/request_token.cr b/src/oauth/request_token.cr index 53211df6789e..0fa79c36fc9c 100644 --- a/src/oauth/request_token.cr +++ b/src/oauth/request_token.cr @@ -20,4 +20,6 @@ class OAuth::RequestToken new token.not_nil!, secret.not_nil! end + + def_equals_and_hash @token, @secret end diff --git a/src/process/status.cr b/src/process/status.cr index 56da75515688..b26e5b08d920 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -74,4 +74,6 @@ class Process::Status # define __WTERMSIG(status) ((status) & 0x7f) @exit_status & 0x7f end + + def_equals_and_hash @exit_status end From f83e31ea13836ebbaa4f7978619b98e6f42c2eef Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 30 Jan 2023 17:19:35 -0300 Subject: [PATCH 0276/1551] Compiler: type declaration with initial value gets the value's type (#13025) Fixes https://github.com/crystal-lang/crystal/issues/13023 --- spec/compiler/codegen/var_spec.cr | 4 ++++ spec/compiler/interpreter/primitives_spec.cr | 6 ++++++ spec/compiler/semantic/var_spec.cr | 4 ++++ src/compiler/crystal/codegen/codegen.cr | 1 + src/compiler/crystal/semantic/main_visitor.cr | 10 ++++++++-- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/spec/compiler/codegen/var_spec.cr b/spec/compiler/codegen/var_spec.cr index b4d661cd39a7..dc16348a2856 100644 --- a/spec/compiler/codegen/var_spec.cr +++ b/spec/compiler/codegen/var_spec.cr @@ -5,6 +5,10 @@ describe "Code gen: var" do run("a = 1; 1.5; a").to_i.should eq(1) end + it "codegens var with type declaration" do + run("a = (b : Int32 = 1); a").to_i.should eq(1) + end + it "codegens ivar assignment when not-nil type filter applies" do run(" class Foo diff --git a/spec/compiler/interpreter/primitives_spec.cr b/spec/compiler/interpreter/primitives_spec.cr index 2ab29a793089..0438c805b16e 100644 --- a/spec/compiler/interpreter/primitives_spec.cr +++ b/spec/compiler/interpreter/primitives_spec.cr @@ -114,6 +114,12 @@ describe Crystal::Repl::Interpreter do CRYSTAL end + it "interprets variable set with type restriction (#13023)" do + interpret(<<-CRYSTAL).should eq(1) + a : Int32 = 1 + CRYSTAL + end + it "interprets variable set and get" do interpret(<<-CRYSTAL).should eq(1) a = 1 diff --git a/spec/compiler/semantic/var_spec.cr b/spec/compiler/semantic/var_spec.cr index ca43a4356380..70f0b1475322 100644 --- a/spec/compiler/semantic/var_spec.cr +++ b/spec/compiler/semantic/var_spec.cr @@ -22,6 +22,10 @@ describe "Semantic: var" do node.type.should eq(mod.int32) end + it "types an assign with type declaration" do + assert_type("a : Int32 = 1") { int32 } + end + it "reports undefined local variable or method" do assert_error " def foo diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 284869d54c3f..3ce9fc0bc7c2 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -1155,6 +1155,7 @@ module Crystal if value = node.value codegen_assign(var, value, node) + return false end when Global node.raise "BUG: there should be no use of global variables other than $~ and $?" diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index f92f61589f28..2e3b67e8b313 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -424,11 +424,17 @@ module Crystal if value = node.value type_assign(var, value, node) + + node.bind_to value + else + node.type = @program.nil end when InstanceVar if @untyped_def node.raise "declaring the type of an instance variable must be done at the class level" end + + node.type = @program.nil when ClassVar if @untyped_def node.raise "declaring the type of a class variable must be done at the class level" @@ -439,12 +445,12 @@ module Crystal class_var = lookup_class_var(var) var.var = class_var class_var.thread_local = true if thread_local + + node.type = @program.nil else raise "Bug: unexpected var type: #{var.class}" end - node.type = @program.nil - false end From f33b5fc6f079cfd4fe692b3c2d51b10517718701 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 31 Jan 2023 04:19:56 +0800 Subject: [PATCH 0277/1551] Parser: remove obsolete handling of `else` inside lib struct (#13028) --- spec/compiler/formatter/formatter_spec.cr | 15 +++++++++++++++ src/compiler/crystal/syntax/parser.cr | 2 -- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 2b4b8c79434d..9a66df62a6ae 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -2426,4 +2426,19 @@ describe Crystal::Formatter do end end CRYSTAL + + it do + expect_raises(Crystal::SyntaxException) do + Crystal.format <<-CRYSTAL + lib A + struct B + {% begin %} + x : Int32 + else + {% end %} + end + end + CRYSTAL + end + end end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 9406d75367ab..3a7146cf11c7 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -5946,8 +5946,6 @@ module Crystal else parse_c_struct_or_union_fields exps end - when Keyword::ELSE - break when Keyword::END break else From bc0198932160de0279d2a68512b7ac576d20def2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 31 Jan 2023 04:20:11 +0800 Subject: [PATCH 0278/1551] Replace `LibC.ntohs` and `htons` with native code (#13027) --- spec/std/socket/address_spec.cr | 26 ++++++++++++++++++++++-- src/socket/address.cr | 36 +++++++++++++-------------------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/spec/std/socket/address_spec.cr b/spec/std/socket/address_spec.cr index 77dc12da93a8..6117b2af66fe 100644 --- a/spec/std/socket/address_spec.cr +++ b/spec/std/socket/address_spec.cr @@ -33,10 +33,19 @@ describe Socket::Address do end describe Socket::IPAddress do + c_port = {% if IO::ByteFormat::NetworkEndian != IO::ByteFormat::SystemEndian %} + 36895 # 0x901F + {% else %} + 8080 # 0x1F90 + {% end %} + it "transforms an IPv4 address into a C struct and back" do addr1 = Socket::IPAddress.new("127.0.0.1", 8080) - addr2 = Socket::IPAddress.from(addr1.to_unsafe, addr1.size) + addr1_c = addr1.to_unsafe + addr1_c.as(LibC::SockaddrIn*).value.sin_port.should eq(c_port) + + addr2 = Socket::IPAddress.from(addr1_c, addr1.size) addr2.family.should eq(addr1.family) addr2.port.should eq(addr1.port) typeof(addr2.address).should eq(String) @@ -45,8 +54,11 @@ describe Socket::IPAddress do it "transforms an IPv6 address into a C struct and back" do addr1 = Socket::IPAddress.new("2001:db8:8714:3a90::12", 8080) - addr2 = Socket::IPAddress.from(addr1.to_unsafe, addr1.size) + addr1_c = addr1.to_unsafe + addr1_c.as(LibC::SockaddrIn6*).value.sin6_port.should eq(c_port) + + addr2 = Socket::IPAddress.from(addr1_c, addr1.size) addr2.family.should eq(addr1.family) addr2.port.should eq(addr1.port) typeof(addr2.address).should eq(String) @@ -59,6 +71,16 @@ describe Socket::IPAddress do end end + it "errors on out of range port numbers" do + expect_raises(Socket::Error, /Invalid port number/) do + Socket::IPAddress.new("localhost", -1) + end + + expect_raises(Socket::Error, /Invalid port number/) do + Socket::IPAddress.new("localhost", 65536) + end + end + it "to_s" do Socket::IPAddress.new("127.0.0.1", 80).to_s.should eq("127.0.0.1:80") Socket::IPAddress.new("2001:db8:8714:3a90::12", 443).to_s.should eq("[2001:db8:8714:3a90::12]:443") diff --git a/src/socket/address.cr b/src/socket/address.cr index f29eb8f067b7..1a84006ca32d 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -82,6 +82,8 @@ class Socket @addr : LibC::In6Addr | LibC::InAddr def initialize(@address : String, @port : Int32) + raise Error.new("Invalid port number: #{port}") unless 0 <= port <= UInt16::MAX + if addr = IPAddress.address_v6?(address) @addr = addr @family = Family::INET6 @@ -145,23 +147,13 @@ class Socket protected def initialize(sockaddr : LibC::SockaddrIn6*, @size) @family = Family::INET6 @addr = sockaddr.value.sin6_addr - @port = - {% if flag?(:dragonfly) %} - sockaddr.value.sin6_port.byte_swap.to_i - {% else %} - LibC.ntohs(sockaddr.value.sin6_port).to_i - {% end %} + @port = endian_swap(sockaddr.value.sin6_port).to_i end protected def initialize(sockaddr : LibC::SockaddrIn*, @size) @family = Family::INET @addr = sockaddr.value.sin_addr - @port = - {% if flag?(:dragonfly) %} - sockaddr.value.sin_port.byte_swap.to_i - {% else %} - LibC.ntohs(sockaddr.value.sin_port).to_i - {% end %} + @port = endian_swap(sockaddr.value.sin_port).to_i end # Returns `true` if *address* is a valid IPv4 or IPv6 address. @@ -308,11 +300,7 @@ class Socket private def to_sockaddr_in6(addr) sockaddr = Pointer(LibC::SockaddrIn6).malloc sockaddr.value.sin6_family = family - {% if flag?(:dragonfly) %} - sockaddr.value.sin6_port = port.byte_swap - {% else %} - sockaddr.value.sin6_port = LibC.htons(port) - {% end %} + sockaddr.value.sin6_port = endian_swap(port.to_u16!) sockaddr.value.sin6_addr = addr sockaddr.as(LibC::Sockaddr*) end @@ -320,15 +308,19 @@ class Socket private def to_sockaddr_in(addr) sockaddr = Pointer(LibC::SockaddrIn).malloc sockaddr.value.sin_family = family - {% if flag?(:dragonfly) %} - sockaddr.value.sin_port = port.byte_swap - {% else %} - sockaddr.value.sin_port = LibC.htons(port) - {% end %} + sockaddr.value.sin_port = endian_swap(port.to_u16!) sockaddr.value.sin_addr = addr sockaddr.as(LibC::Sockaddr*) end + private def endian_swap(x : UInt16) : UInt16 + {% if IO::ByteFormat::NetworkEndian != IO::ByteFormat::SystemEndian %} + x.byte_swap + {% else %} + x + {% end %} + end + # Returns `true` if *port* is a valid port number. # # Valid port numbers are in the range `0..65_535`. From e561a5004836f5f2c9c14d04686fde71eddce8a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 31 Jan 2023 21:35:53 +0100 Subject: [PATCH 0279/1551] Changelog helper: Report error from HTTP request (#13011) --- scripts/github-changelog.cr | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index 292ede110d74..63859a37ddef 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -65,6 +65,9 @@ response = HTTP::Client.post("https://api.github.com/graphql", "Authorization" => "bearer #{api_token}", } ) +unless response.success? + abort "GitHub API response: #{response.status}\n#{response.body}" +end module LabelNameConverter def self.from_json(pull : JSON::PullParser) From eca47ab041fac4539325d2c80307b63f53c6b766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 1 Feb 2023 20:24:59 +0100 Subject: [PATCH 0280/1551] Fix wrapper script to handle `CRYSTAL` variable pointing to itself (#13032) This patch ignores `CRYSTAL` if it's a path and points to the wrapper script itself. The fallback is `crystal`. --- bin/crystal | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/bin/crystal b/bin/crystal index 4f0301544688..1f9566399984 100755 --- a/bin/crystal +++ b/bin/crystal @@ -146,8 +146,16 @@ export CRYSTAL_HAS_WRAPPER=true export CRYSTAL="${CRYSTAL:-"crystal"}" -# check if the crystal command refers to this script -if [ "$(realpath "$(command -v "$CRYSTAL")")" = "$SCRIPT_PATH" ]; then +PARENT_CRYSTAL="$CRYSTAL" + +# check if the parent crystal command is a path that refers to this script +if [ -z "${PARENT_CRYSTAL##*/*}" -a "$(realpath "$PARENT_CRYSTAL")" = "$SCRIPT_PATH" ]; then + # ignore it and use `crystal` as parent compiler command + PARENT_CRYSTAL="crystal" +fi + +# check if the parent crystal command refers to this script +if [ "$(realpath "$(command -v "$PARENT_CRYSTAL")")" = "$SCRIPT_PATH" ]; then # remove the path to this script from PATH NEW_PATH="$(remove_path_item "$(remove_path_item "$PATH" "$SCRIPT_ROOT")" "bin")" # if the PATH did not change it will lead to an infinite recursion => display error @@ -161,7 +169,7 @@ if [ "$(realpath "$(command -v "$CRYSTAL")")" = "$SCRIPT_PATH" ]; then fi if [ -z "$CRYSTAL_CONFIG_LIBRARY_PATH" ] || [ -z "$CRYSTAL_LIBRARY_PATH" ]; then - CRYSTAL_INSTALLED_LIBRARY_PATH="$($CRYSTAL env CRYSTAL_LIBRARY_PATH || echo "")" + CRYSTAL_INSTALLED_LIBRARY_PATH="$($PARENT_CRYSTAL env CRYSTAL_LIBRARY_PATH || echo "")" export CRYSTAL_LIBRARY_PATH=${CRYSTAL_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} export CRYSTAL_CONFIG_LIBRARY_PATH=${CRYSTAL_CONFIG_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} fi @@ -169,9 +177,9 @@ fi if [ -x "$CRYSTAL_DIR/crystal" ]; then __warning_msg "Using compiled compiler at ${CRYSTAL_DIR#"$PWD/"}/crystal" exec "$CRYSTAL_DIR/crystal" "$@" -elif ! command -v $CRYSTAL > /dev/null; then +elif ! command -v $PARENT_CRYSTAL > /dev/null; then __error_msg 'You need to have a crystal executable in your path! or set CRYSTAL env variable' exit 1 else - exec $CRYSTAL "$@" + exec $PARENT_CRYSTAL "$@" fi From 632b64ddefa01c11aec03068c4118b37c3948275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 2 Feb 2023 14:57:52 +0100 Subject: [PATCH 0281/1551] Remove obsolete error handling in `XPathContext` (#13038) Resolves https://github.com/crystal-lang/crystal/issues/13036 --- src/xml/xpath_context.cr | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/xml/xpath_context.cr b/src/xml/xpath_context.cr index 33ae9335c742..53ede8aeb929 100644 --- a/src/xml/xpath_context.cr +++ b/src/xml/xpath_context.cr @@ -8,16 +8,8 @@ class XML::XPathContext def evaluate(search_path : String) xpath = XML::Error.collect_generic(@errors) { LibXML.xmlXPathEvalExpression(search_path, self) } - unless xpath - {% if flag?(:arm) || flag?(:aarch64) %} - if errors = XML::Error.errors - raise errors.last - end - {% end %} - raise XML::Error.new("Error in '#{search_path}' expression", 0) - end - raise XML::Error.new("Error in '#{search_path}' expression", 0) unless xpath.value + raise XML::Error.new("Error in '#{search_path}' expression", 0) unless xpath case xpath.value.type when LibXML::XPathObjectType::STRING From aabd773fa6d9a2928adb68142e09470edef47494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 2 Feb 2023 15:11:40 +0100 Subject: [PATCH 0282/1551] Add `Enum#inspect` (#13004) --- spec/std/enum_spec.cr | 31 +++++++++++++- spec/std/file_spec.cr | 4 +- src/enum.cr | 94 +++++++++++++++++++++++++++++++++---------- 3 files changed, 104 insertions(+), 25 deletions(-) diff --git a/spec/std/enum_spec.cr b/spec/std/enum_spec.cr index fe5c36c89ddc..963e9401ad95 100644 --- a/spec/std/enum_spec.cr +++ b/spec/std/enum_spec.cr @@ -60,7 +60,7 @@ describe Enum do it "for flags enum" do assert_prints SpecEnumFlags::None.to_s, "None" - assert_prints SpecEnumFlags::All.to_s, "One | Two | Three" + assert_prints SpecEnumFlags::All.to_s, "All" assert_prints (SpecEnumFlags::One | SpecEnumFlags::Two).to_s, "One | Two" assert_prints SpecEnumFlags.new(128).to_s, "128" assert_prints (SpecEnumFlags::One | SpecEnumFlags.new(128)).to_s, "One | 128" @@ -78,6 +78,35 @@ describe Enum do end end + describe "#inspect" do + it "for simple enum" do + assert_prints SpecEnum::One.inspect, "SpecEnum::One" + assert_prints SpecEnum::Two.inspect, "SpecEnum::Two" + assert_prints SpecEnum::Three.inspect, "SpecEnum::Three" + assert_prints SpecEnum.new(127).inspect, "SpecEnum[127]" + end + + it "for flags enum" do + assert_prints SpecEnumFlags::None.inspect, "SpecEnumFlags::None" + assert_prints SpecEnumFlags::All.inspect, "SpecEnumFlags::All" + assert_prints (SpecEnumFlags::One).inspect, "SpecEnumFlags::One" + assert_prints (SpecEnumFlags::One | SpecEnumFlags::Two).inspect, "SpecEnumFlags[One, Two]" + assert_prints SpecEnumFlags.new(128).inspect, "SpecEnumFlags[128]" + assert_prints (SpecEnumFlags::One | SpecEnumFlags.new(128)).inspect, "SpecEnumFlags[One, 128]" + assert_prints (SpecEnumFlags::One | SpecEnumFlags.new(8) | SpecEnumFlags.new(16)).inspect, "SpecEnumFlags[One, 24]" + assert_prints (SpecEnumFlags::One | SpecEnumFlags::Two | SpecEnumFlags.new(16)).inspect, "SpecEnumFlags[One, Two, 16]" + end + + it "for private enum" do + assert_prints PrivateEnum::FOO.inspect, "PrivateEnum::FOO" + assert_prints PrivateFlagsEnum::FOO.inspect, "PrivateFlagsEnum::FOO" + assert_prints PrivateEnum::QUX.inspect, "PrivateEnum::FOO" + assert_prints (PrivateFlagsEnum::FOO | PrivateFlagsEnum::BAZ).inspect, "PrivateFlagsEnum[FOO, BAZ]" + assert_prints PrivateFlagsEnum.new(128).inspect, "PrivateFlagsEnum[128]" + assert_prints (PrivateFlagsEnum::FOO | PrivateFlagsEnum.new(128)).inspect, "PrivateFlagsEnum[FOO, 128]" + end + end + it "creates an enum instance from an auto-casted symbol (#8573)" do enum_value = SpecEnum.new(:two) enum_value.should eq SpecEnum::Two diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index ea71a9e2497f..daef6a8ca03a 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -1478,8 +1478,8 @@ describe "File" do it "does to_s" do perm = File::Permissions.flags(OwnerAll, GroupRead, GroupWrite, OtherRead) perm.to_s.should eq("rwxrw-r-- (0o764)") - perm.inspect.should eq("rwxrw-r-- (0o764)") - perm.pretty_inspect.should eq("rwxrw-r-- (0o764)") + perm.inspect.should eq("File::Permissions[OtherRead, GroupWrite, GroupRead, OwnerExecute, OwnerWrite, OwnerRead]") + perm.pretty_inspect.should eq("File::Permissions[OtherRead, GroupWrite, GroupRead, OwnerExecute, OwnerWrite, OwnerRead]") end end end diff --git a/src/enum.cr b/src/enum.cr index db6404e0a563..06c7e3afdfbd 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -132,26 +132,14 @@ struct Enum {% if @type.annotation(Flags) %} if value == 0 io << "None" + elsif name = member_name + io << name else - remaining_value = self.value - {% for member in @type.constants %} - {% if member.stringify != "All" %} - if {{@type.constant(member)}} != 0 && value.bits_set? {{@type.constant(member)}} - io << " | " unless remaining_value == self.value - io << {{member.stringify}} - remaining_value &= ~{{@type.constant(member)}} - end - {% end %} - {% end %} - unless remaining_value.zero? - io << " | " unless remaining_value == self.value - io << remaining_value - end + stringify_names(io, " | ") end {% else %} io << to_s {% end %} - nil end # Returns a `String` representation of this enum member. @@ -173,15 +161,77 @@ struct Enum {% if @type.annotation(Flags) %} String.build { |io| to_s(io) } {% else %} - # Can't use `case` here because case with duplicate values do - # not compile, but enums can have duplicates (such as `enum Foo; FOO = 1; BAR = 1; end`). - {% for member, i in @type.constants %} - if value == {{@type.constant(member)}} - return {{member.stringify}} + member_name || value.to_s + {% end %} + end + + # Returns an unambiguous `String` representation of this enum member. + # In the case of a single member value, this is the fully qualified name of + # the member (equvalent to `#to_s` with the enum name as prefix). + # In the case of multiple members (for a flags enum), it's a call to `Enum.[]` + # for recreating the same value. + # + # If the value can't be represented fully by named members, the remainig value + # is appended. + # + # ``` + # Color::Red # => Color:Red + # IOMode::None # => IOMode::None + # (IOMode::Read | IOMode::Write) # => IOMode[Read, Write] + # + # Color.new(10) # => Color[10] + # ``` + def inspect(io : IO) : Nil + {% if @type.annotation(Flags) %} + if value == 0 + io << {{ "#{@type}::None" }} + elsif name = member_name + io << {{ "#{@type}::" }} << name + else + io << {{ "#{@type}[" }} + stringify_names(io, ", ") + io << "]" + end + {% else %} + inspect_single(io) + {% end %} + end + + private def stringify_names(io, separator) : Nil + remaining_value = self.value + {% for member in @type.constants %} + {% if member.stringify != "All" %} + if {{@type.constant(member)}} != 0 && remaining_value.bits_set? {{@type.constant(member)}} + unless remaining_value == self.value + io << separator + end + io << {{member.stringify}} + remaining_value &= ~{{@type.constant(member)}} end {% end %} + {% end %} - value.to_s + unless remaining_value.zero? + io << separator unless remaining_value == self.value + io << remaining_value + end + end + + private def inspect_single(io) : Nil + if name = member_name + io << {{ "#{@type}::" }} << name + else + io << {{ "#{@type}[" }} << value << "]" + end + end + + private def member_name + # Can't use `case` here because case with duplicate values do + # not compile, but enums can have duplicates (such as `enum Foo; FOO = 1; BAR = 1; end`). + {% for member in @type.constants %} + if value == {{@type.constant(member)}} + return {{member.stringify}} + end {% end %} end @@ -495,7 +545,7 @@ struct Enum # Convenience macro to create a combined enum (combines given members using `|` (or) logical operator) # # ``` - # IOMode.flags(Read, Write) # => IOMode::Read | IOMode::Write + # IOMode.flags(Read, Write) # => IOMode[Read, Write] # ``` # # * `Enum.[]` is a more advanced alternative which also allows int and symbol parameters. From 6d37cc441c3cfacac90e8e76cc2a2fdcb5016d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 2 Feb 2023 15:11:53 +0100 Subject: [PATCH 0283/1551] Fix formatter empty array literal with comment on extra line (#12907) --- spec/compiler/formatter/formatter_spec.cr | 2 ++ src/compiler/crystal/tools/formatter.cr | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 9a66df62a6ae..33ce3ed857e7 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -102,6 +102,8 @@ describe Crystal::Formatter do assert_format "Set{ # foo\n 1,\n}" assert_format "begin\n array[\n 0 # Zero\n ]\nend" assert_format "begin\n array[\n 0, # Zero\n ]\nend" + assert_format "[\n # foo\n] of String" + assert_format "[\n# foo\n] of String", "[\n # foo\n] of String" assert_format "{1, 2, 3}" assert_format "{ {1, 2, 3} }" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index e297c7bdb1f2..e485f2f03c0d 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -827,7 +827,7 @@ module Crystal start_column = @indent + 2 if elements.empty? - skip_space_or_newline + skip_space_or_newline(offset, last: true, at_least_one: true) write_token suffix return false end From 851cd5b75356d5b0a8d33c527f8c988936a996a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 2 Feb 2023 19:06:49 +0100 Subject: [PATCH 0284/1551] Fix formatter comment on extra line at end of method args (#12908) --- spec/compiler/formatter/formatter_spec.cr | 2 ++ src/compiler/crystal/tools/formatter.cr | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 33ce3ed857e7..7dfa16e1bb16 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -509,6 +509,8 @@ describe Crystal::Formatter do assert_format "foo.% bar" assert_format "foo.bar(&.%(baz))" assert_format "foo.bar(&.% baz)" + assert_format "if 1\n foo(\n bar\n # comment\n )\nend" + assert_format "if 1\n foo(\n bar,\n # comment\n )\nend" assert_format "foo.bar\n.baz", "foo.bar\n .baz" assert_format "foo.bar.baz\n.qux", "foo.bar.baz\n .qux" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index e485f2f03c0d..cb7206470291 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -2783,7 +2783,7 @@ module Crystal if @token.type.newline? ends_with_newline = true end - skip_space_or_newline + indent(base_indent + 2) { skip_space_or_newline(last: true, at_least_one: ends_with_newline) } elsif has_args || node.block_arg write " " unless passed_backslash_newline skip_space @@ -2975,8 +2975,9 @@ module Crystal if @token.type.newline? && has_newlines write "," write_line - write_indent(column) skip_space_or_newline(column + 2) + write_indent(column) + skip_space_or_newline(column) else found_comment |= skip_space_or_newline(column + 2) if has_newlines From 23f5147e4537b49c736772c609a5da4674d8b25c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 2 Feb 2023 19:07:01 +0100 Subject: [PATCH 0285/1551] Fix formatter not merge consecutive but separated comment lines (#12909) --- spec/compiler/formatter/formatter_spec.cr | 1 + src/compiler/crystal/tools/formatter.cr | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 7dfa16e1bb16..aa012b32a5f3 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1309,6 +1309,7 @@ describe Crystal::Formatter do assert_format "# foo\na = 1 # bar" assert_format "#### ###" assert_format "#######" + assert_format "x\n# foo\n\n# bar" assert_format "A = 1\nFOO = 2\n\nEX = 3", "A = 1\nFOO = 2\n\nEX = 3" assert_format "FOO = 2\nA = 1", "FOO = 2\nA = 1" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index cb7206470291..16688fa4331e 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -4778,7 +4778,7 @@ module Crystal @wrote_double_newlines = true end - skip_space_or_newline + skip_space_or_newline(last: next_comes_end, at_least_one: true) end end From e4e69e1d6ea082f50a10cb406b55f8467be5229d Mon Sep 17 00:00:00 2001 From: Mike Robbins Date: Thu, 2 Feb 2023 13:07:23 -0500 Subject: [PATCH 0286/1551] Add `Spec::Item#all_tags` (#12915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/spec/context_spec.cr | 15 +++++++++++++++ src/spec/item.cr | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/spec/std/spec/context_spec.cr b/spec/std/spec/context_spec.cr index 80cc1b43c772..04416d608b26 100644 --- a/spec/std/spec/context_spec.cr +++ b/spec/std/spec/context_spec.cr @@ -39,4 +39,19 @@ describe Spec::ExampleGroup do root.results_for(:fail).first.description.should eq("child grand_child oops") end end + + describe "#all_tags" do + it "should include ancestor tags" do + root = FakeRootContext.new + child = Spec::ExampleGroup.new(root, "child", "f.cr", 1, 10, false, Set{"A"}) + grand_child = Spec::ExampleGroup.new(child, "grand_child", "f.cr", 2, 9, false, Set{"B"}) + example = Spec::Example.new(grand_child, "example", "f.cr", 3, 8, false, Set{"C"}, nil) + other_group = Spec::ExampleGroup.new(root, "other_group", "f.cr", 11, 20, false, nil) + + child.all_tags.should eq(Set{"A"}) + grand_child.all_tags.should eq(Set{"A", "B"}) + example.all_tags.should eq(Set{"A", "B", "C"}) + other_group.all_tags.should be_empty + end + end end diff --git a/src/spec/item.cr b/src/spec/item.cr index 593e1b2b49ce..044228d47089 100644 --- a/src/spec/item.cr +++ b/src/spec/item.cr @@ -25,5 +25,17 @@ module Spec private def initialize_tags(tags) @tags = tags.is_a?(String) ? Set{tags} : tags.try(&.to_set) end + + # All tags, including tags inherited from ancestor example groups + def all_tags : Set(String) + result = tags.try(&.dup) || Set(String).new + ancestor = self + while ancestor = ancestor.parent.as?(ExampleGroup) + if tags = ancestor.tags + result.concat(tags) + end + end + result + end end end From 7d4f9b24970b98fec75360b007bc86125fa062ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 2 Feb 2023 23:21:25 +0100 Subject: [PATCH 0287/1551] Add more specs for `YAML::Any#[]` and `#[]?` (#11646) --- spec/std/yaml/any_spec.cr | 184 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 179 insertions(+), 5 deletions(-) diff --git a/spec/std/yaml/any_spec.cr b/spec/std/yaml/any_spec.cr index c2a030dcc0f4..33c8dc458db4 100644 --- a/spec/std/yaml/any_spec.cr +++ b/spec/std/yaml/any_spec.cr @@ -2,6 +2,92 @@ require "spec" require "yaml" require "json" +private def it_fetches_from_hash(key, *equivalent_keys) + it "fetches #{key.class}" do + any = YAML::Any.new({YAML::Any.new(key) => YAML::Any.new("bar")}) + + any[key].raw.should eq("bar") + any[YAML::Any.new(key)].raw.should eq("bar") + + equivalent_keys.each do |k| + any[k].raw.should eq("bar") + # FIXME: Can't do `YAML::Any.new` with arbitrary number types (#11645) + if k.is_a?(YAML::Any::Type) + any[YAML::Any.new(k)].raw.should eq("bar") + end + end + + unless key.nil? + expect_raises(KeyError, %(Missing hash key: nil)) do + any[nil] + end + + expect_raises(KeyError, %(Missing hash key: nil)) do + any[YAML::Any.new(nil)] + end + end + + expect_raises(KeyError, %(Missing hash key: "fox")) do + any["fox"] + end + + expect_raises(KeyError, %(Missing hash key: "fox")) do + any[YAML::Any.new("fox")] + end + + expect_raises(KeyError, %(Missing hash key: 2)) do + any[2] + end + + expect_raises(KeyError, %(Missing hash key: 2)) do + any[YAML::Any.new(2i64)] + end + + expect_raises(KeyError, %(Missing hash key: 2)) do + any[2.0] + end + + expect_raises(KeyError, %(Missing hash key: 2)) do + any[YAML::Any.new(2.0f64)] + end + + expect_raises(KeyError, %(Missing hash key: 'c')) do + any['c'] + end + end +end + +private def it_fetches_from_hash?(key, *equivalent_keys) + it "fetches #{key.class}" do + any = YAML::Any.new({YAML::Any.new(key) => YAML::Any.new("bar")}) + + any[key]?.try(&.raw).should eq("bar") + any[YAML::Any.new(key)]?.try(&.raw).should eq("bar") + + equivalent_keys.each do |k| + any[k]?.try(&.raw).should eq("bar") + # FIXME: Can't do `YAML::Any.new` with arbitrary number types (#11645) + if k.is_a?(YAML::Any::Type) + any[YAML::Any.new(k)]?.try(&.raw).should eq("bar") + end + end + + unless key.nil? + any[nil]?.should be_nil + any[YAML::Any.new(nil)]?.should be_nil + end + + any["fox"]?.should be_nil + any[YAML::Any.new("fox")]?.should be_nil + any[2]?.should be_nil + any[YAML::Any.new(2i64)]?.should be_nil + any[2.0]?.should be_nil + any[YAML::Any.new(2.0f64)]?.should be_nil + + any['c']?.should be_nil + end +end + describe YAML::Any do describe "casts" do it "gets nil" do @@ -165,14 +251,69 @@ describe YAML::Any do describe "#[]" do it "of array" do YAML.parse("- foo\n- bar\n")[1].raw.should eq("bar") - end - it "of hash" do - YAML.parse("foo: bar")["foo"].raw.should eq("bar") + any = YAML::Any.new([YAML::Any.new("baz"), YAML::Any.new("bar")]) + + any[1i64].raw.should eq("bar") + any[1i32].raw.should eq("bar") + any[1u8].raw.should eq("bar") + + expect_raises(Exception, %(Expected int key for Array#[], not Array(YAML::Any))) do + any[nil] + end + + expect_raises(Exception, %(Expected int key for Array#[], not Array(YAML::Any))) do + any[YAML::Any.new(nil)] + end + + expect_raises(Exception, %(Expected int key for Array#[], not Array(YAML::Any))) do + any["fox"] + end + + expect_raises(Exception, %(Expected int key for Array#[], not Array(YAML::Any))) do + any[YAML::Any.new("fox")] + end + + expect_raises(IndexError, %(Index out of bounds)) do + any[2] + end + + expect_raises(Exception, %(Expected int key for Array#[], not Array(YAML::Any))) do + any[YAML::Any.new(2i64)] + end + + expect_raises(Exception, %(Expected int key for Array#[], not Array(YAML::Any))) do + any[2.0f64] + end + + expect_raises(Exception, %(Expected int key for Array#[], not Array(YAML::Any))) do + any[YAML::Any.new(2.0f64)] + end + + expect_raises(Exception, %(Expected int key for Array#[], not Array(YAML::Any))) do + any[2.0f32] + end + + expect_raises(Exception, %(Expected int key for Array#[], not Array(YAML::Any))) do + any[YAML::Any.new(2.0f32)] + end + + expect_raises(Exception, %(Expected int key for Array#[], not Array(YAML::Any))) do + any['c'] + end end - it "of hash with integer keys" do - YAML.parse("1: bar")[1].raw.should eq("bar") + context "hash" do + it_fetches_from_hash nil + it_fetches_from_hash true + it_fetches_from_hash 1i64, 1.0f64, 1i32, 1u8, 1.0f32 + it_fetches_from_hash 1.0f64, 1i64, 1i32, 1u8, 1.0f32 + it_fetches_from_hash "foo" + it_fetches_from_hash Time.utc + it_fetches_from_hash "foo".to_slice + it_fetches_from_hash [YAML::Any.new("foo")] + it_fetches_from_hash({YAML::Any.new("foo") => YAML::Any.new("baz")}) + it_fetches_from_hash Set{YAML::Any.new("foo")} end end @@ -180,6 +321,26 @@ describe YAML::Any do it "of array" do YAML.parse("- foo\n- bar\n")[1]?.not_nil!.raw.should eq("bar") YAML.parse("- foo\n- bar\n")[3]?.should be_nil + + any = YAML::Any.new([YAML::Any.new("baz"), YAML::Any.new("bar")]) + + any[1i64]?.try(&.raw).should eq("bar") + any[1i32]?.try(&.raw).should eq("bar") + any[1u8]?.try(&.raw).should eq("bar") + any[1.0f64]?.try(&.raw).should be_nil + any[1.0f32]?.try(&.raw).should be_nil + + any[nil]?.should be_nil + any[YAML::Any.new(nil)]?.should be_nil + any["fox"]?.should be_nil + any[YAML::Any.new("fox")]?.should be_nil + any[2]?.should be_nil + any[YAML::Any.new(2i64)]?.should be_nil + any[2.0f64]?.should be_nil + any[YAML::Any.new(2.0f64)]?.should be_nil + any[2.0f32]?.should be_nil + any[YAML::Any.new(2.0f32)]?.should be_nil + any['c']?.should be_nil end it "of hash" do @@ -191,6 +352,19 @@ describe YAML::Any do YAML.parse("1: bar")[1]?.not_nil!.raw.should eq("bar") YAML.parse("1: bar")[2]?.should be_nil end + + context "hash" do + it_fetches_from_hash? nil + it_fetches_from_hash? true + it_fetches_from_hash? 1i64, 1.0 + it_fetches_from_hash? 1.0, 1i64 + it_fetches_from_hash? "foo" + it_fetches_from_hash? Time.utc + it_fetches_from_hash? "foo".to_slice + it_fetches_from_hash? [YAML::Any.new("foo")] + it_fetches_from_hash?({YAML::Any.new("foo") => YAML::Any.new("baz")}) + it_fetches_from_hash? Set{YAML::Any.new("foo")} + end end describe "#dig?" do From 10d0c73de09613e4db41f047b6c5ee804c39c67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 2 Feb 2023 23:21:44 +0100 Subject: [PATCH 0288/1551] Docs: Add references to `Number` collection convenience constructors (#13020) --- src/number.cr | 23 +++++++++++++++++++++++ src/slice.cr | 3 ++- src/static_array.cr | 3 ++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/number.cr b/src/number.cr index 62177e78d89c..9aae19e1eb8a 100644 --- a/src/number.cr +++ b/src/number.cr @@ -71,6 +71,13 @@ struct Number # ints = Int64[1, 2, 3] # ints.class # => Array(Int64) # ``` + # + # This is similar to an array literal of the same item type: + # + # ``` + # Int64[1, 2, 3, 4] # : Array(Int64) + # [1, 2, 3, 4] of Int64 # : Array(Int64) + # ``` macro [](*nums) Array({{@type}}).build({{nums.size}}) do |%buffer| {% for num, i in nums %} @@ -92,6 +99,14 @@ struct Number # ints = Int64.slice(1, 2, 3) # ints.class # => Slice(Int64) # ``` + # + # This is a convenient alternative to `Slice.[]` for designating a + # specific item type which also considers autocasting. + # + # ``` + # Int64.slice(1, 2, 3, 4) # : Slice(Int64) + # Slice[1_i64, 2_i64, 3_i64, 4_i64] # : Slice(Int64) + # ``` macro slice(*nums, read_only = false) %slice = Slice({{@type}}).new({{nums.size}}, read_only: {{read_only}}) {% for num, i in nums %} @@ -110,6 +125,14 @@ struct Number # ints = Int64.static_array(1, 2, 3) # ints.class # => StaticArray(Int64, 3) # ``` + # + # This is a convenvenient alternative to `StaticArray.[]` for designating a + # specific item type which also considers autocasting. + # + # ``` + # Int64.static_array(1, 2, 3, 4) # : StaticArray(Int64) + # StaticArray[1_i64, 2_i64, 3_i64, 4_i64] # : StaticArray(Int64) + # ``` macro static_array(*nums) %array = uninitialized StaticArray({{@type}}, {{nums.size}}) {% for num, i in nums %} diff --git a/src/slice.cr b/src/slice.cr index ebdd7c5f4cc8..4a49f77b97e6 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -29,7 +29,8 @@ struct Slice(T) # If `T` is a `Number` then this is equivalent to # `Number.slice` (numbers will be coerced to the type `T`) # - # See also: `Number.slice`. + # * `Number.slice` is a convenient alternative for designating a + # specific numerical item type. macro [](*args, read_only = false) # TODO: there should be a better way to check this, probably # asking if @type was instantiated or if T is defined diff --git a/src/static_array.cr b/src/static_array.cr index 714e3368a187..ee502fb63035 100644 --- a/src/static_array.cr +++ b/src/static_array.cr @@ -47,7 +47,8 @@ struct StaticArray(T, N) # ary.class # => StaticArray(Char | Int32, 2) # ``` # - # See also: `Number.static_array`. + # * `Number.static_array` is a convenient alternative for designating a + # specific numerical item type. macro [](*args) %array = uninitialized StaticArray(typeof({{*args}}), {{args.size}}) {% for arg, i in args %} From 00694a2724372bc80c035816b4443a04b687da40 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 3 Feb 2023 06:22:04 +0800 Subject: [PATCH 0289/1551] Don't use deprecated `from_winerror` overload for `flock_*` (#13039) --- src/crystal/system/win32/file.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 3fb3998e3c48..cb5f0b902eb2 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -294,7 +294,7 @@ module Crystal::System::File if winerror == WinError::ERROR_LOCK_VIOLATION false else - raise IO::Error.from_winerror("LockFileEx", winerror) + raise IO::Error.from_os_error("LockFileEx", winerror) end end end From 95463a92ae067ca11dd1f087dfab7e567d3872b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 3 Feb 2023 11:40:35 +0100 Subject: [PATCH 0290/1551] Remove relative path to vendored shard `markd` (#13040) --- src/compiler/crystal/tools/doc/generator.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index 9e2a22a25506..8aa1b30dbecc 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -1,4 +1,4 @@ -require "../../../../../lib/markd/src/markd" +require "markd" require "crystal/syntax_highlighter/html" class Crystal::Doc::Generator From 7aefd61eae68980161752dae21d7d834dc153d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 3 Feb 2023 16:10:16 +0100 Subject: [PATCH 0291/1551] Add `from_json` for 128-bit integers (#13041) --- spec/std/json/serializable_spec.cr | 11 +++++++++++ spec/std/json/serialization_spec.cr | 12 ++++++++++++ src/json/from_json.cr | 20 +++++++++++--------- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index cceb161c6f12..f768c802d0c3 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -106,6 +106,12 @@ class JSONAttrWithBigDecimal property value : BigDecimal end +class JSONAttrWithInt128 + include JSON::Serializable + + property value : Int128 +end + class JSONAttrWithTime include JSON::Serializable @@ -1045,6 +1051,11 @@ describe "JSON mapping" do end end + it "parses 128-bit integer" do + json = JSONAttrWithInt128.from_json(%({"value": #{Int128::MAX}})) + json.value.should eq Int128::MAX + end + describe "work with module and inheritance" do it { JSONAttrModuleTest.from_json(%({"phoo": 20})).to_tuple.should eq({10, 20}) } it { JSONAttrModuleTest.from_json(%({"phoo": 20})).to_tuple.should eq({10, 20}) } diff --git a/spec/std/json/serialization_spec.cr b/spec/std/json/serialization_spec.cr index be0a5efe1713..626926f53d21 100644 --- a/spec/std/json/serialization_spec.cr +++ b/spec/std/json/serialization_spec.cr @@ -40,6 +40,14 @@ describe "JSON serialization" do UInt64.from_json(UInt64::MAX.to_s).should eq(UInt64::MAX) end + it "does UInt128.from_json" do + UInt128.from_json(UInt128::MAX.to_s).should eq(UInt128::MAX) + end + + it "does Int128.from_json" do + Int128.from_json(Int128::MAX.to_s).should eq(Int128::MAX) + end + it "raises ParserException for overflow UInt64.from_json" do expect_raises(JSON::ParseException, "Can't read UInt64 at line 0, column 0") do UInt64.from_json("1#{UInt64::MAX}") @@ -501,6 +509,10 @@ describe "JSON serialization" do 1.to_json.should eq("1") end + it "does for Int128" do + Int128::MAX.to_json.should eq(Int128::MAX.to_s) + end + it "does for Float64" do 1.5.to_json.should eq("1.5") end diff --git a/src/json/from_json.cr b/src/json/from_json.cr index 08e870b5122a..bd77199cdc85 100644 --- a/src/json/from_json.cr +++ b/src/json/from_json.cr @@ -125,19 +125,21 @@ def Bool.new(pull : JSON::PullParser) end {% for type, method in { - "Int8" => "i8", - "Int16" => "i16", - "Int32" => "i32", - "Int64" => "i64", - "UInt8" => "u8", - "UInt16" => "u16", - "UInt32" => "u32", - "UInt64" => "u64", + "Int8" => "i8", + "Int16" => "i16", + "Int32" => "i32", + "Int64" => "i64", + "Int128" => "i128", + "UInt8" => "u8", + "UInt16" => "u16", + "UInt32" => "u32", + "UInt64" => "u64", + "UInt128" => "u128", } %} def {{type.id}}.new(pull : JSON::PullParser) location = pull.location value = - {% if type == "UInt64" %} + {% if type == "UInt64" || type == "UInt128" || type == "Int128" %} pull.read_raw {% else %} pull.read_int From 8853f7333f1e5be8f61a509a427f741bbe9ddb85 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 4 Feb 2023 20:51:10 +0800 Subject: [PATCH 0292/1551] Add `Process.on_interrupt` (#13034) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- samples/sdl/raytracer.cr | 2 +- spec/std/process_spec.cr | 8 +++ src/compiler/crystal/command.cr | 4 +- src/crystal/atomic_semaphore.cr | 16 ++++++ src/crystal/system/process.cr | 14 +++++ src/crystal/system/unix/process.cr | 16 ++++++ src/crystal/system/wasi/process.cr | 15 +++++ src/crystal/system/win32/process.cr | 55 +++++++++++++++++++ src/kernel.cr | 4 +- src/lib_c/x86_64-windows-msvc/c/consoleapi.cr | 7 +++ src/process.cr | 26 +++++++++ src/signal.cr | 6 +- src/spec.cr | 6 +- 13 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 src/crystal/atomic_semaphore.cr diff --git a/samples/sdl/raytracer.cr b/samples/sdl/raytracer.cr index 0528272d8cba..6bcea6383a21 100644 --- a/samples/sdl/raytracer.cr +++ b/samples/sdl/raytracer.cr @@ -202,7 +202,7 @@ def render(scene, surface) surface.update_rect 0, 0, 0, 0 end -Signal::INT.trap { exit } +Process.on_interrupt { exit } scene = Scene.new( [ diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 82265dd94852..cc1253bf9038 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -317,6 +317,14 @@ describe Process do end end + describe ".on_interrupt" do + it "compiles" do + typeof(Process.on_interrupt { }) + typeof(Process.ignore_interrupts!) + typeof(Process.restore_interrupts!) + end + end + describe "#signal" do pending_win32 "kills a process" do process = Process.new(*standing_command) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index ca66b460d83b..940a03bad1c3 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -250,10 +250,10 @@ class Crystal::Command begin elapsed = Time.measure do Process.run(output_filename, args: run_args, input: Process::Redirect::Inherit, output: Process::Redirect::Inherit, error: Process::Redirect::Inherit) do |process| - {% unless flag?(:win32) || flag?(:wasm32) %} + {% unless flag?(:wasm32) %} # Ignore the signal so we don't exit the running process # (the running process can still handle this signal) - ::Signal::INT.ignore # do + Process.ignore_interrupts! {% end %} end end diff --git a/src/crystal/atomic_semaphore.cr b/src/crystal/atomic_semaphore.cr new file mode 100644 index 000000000000..c0d15737763c --- /dev/null +++ b/src/crystal/atomic_semaphore.cr @@ -0,0 +1,16 @@ +# :nodoc: +class Crystal::AtomicSemaphore + @m = Atomic(UInt32).new(0) + + def wait(&) : Nil + m = @m.get + while m == 0 || !@m.compare_and_set(m, m &- 1).last + yield + m = @m.get + end + end + + def signal : Nil + @m.add(1) + end +end diff --git a/src/crystal/system/process.cr b/src/crystal/system/process.cr index 77577a9e5c6b..d613d7ac431c 100644 --- a/src/crystal/system/process.cr +++ b/src/crystal/system/process.cr @@ -42,6 +42,20 @@ struct Crystal::System::Process # Sends a *signal* to the processes identified by the given *pids*. # def self.signal(pid : Int, signal : Int) + # Installs *handler* as the new handler for interrupt requests. Removes any + # previously set interrupt handler. + # def self.on_interrupt(&handler : ->) + + # Ignores all interrupt requests. Removes any custom interrupt handler set + # def self.ignore_interrupts! + + # Restores default handling of interrupt requests. + # def self.restore_interrupts! + + # Spawns a fiber responsible for executing interrupt handlers on the main + # thread. + # def self.start_interrupt_loop + # Whether the process identified by *pid* is still registered in the system. # def self.exists?(pid : Int) : Bool diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 9f95136d6229..f94835887a4e 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -58,6 +58,22 @@ struct Crystal::System::Process raise RuntimeError.from_errno("kill") if ret < 0 end + def self.on_interrupt(&handler : ->) : Nil + ::Signal::INT.trap { |_signal| handler.call } + end + + def self.ignore_interrupts! : Nil + ::Signal::INT.ignore + end + + def self.restore_interrupts! : Nil + ::Signal::INT.reset + end + + def self.start_interrupt_loop : Nil + # do nothing; `Crystal::Signal.start_loop` takes care of this + end + def self.exists?(pid) ret = LibC.kill(pid, 0) if ret == 0 diff --git a/src/crystal/system/wasi/process.cr b/src/crystal/system/wasi/process.cr index d0b3769c8b72..5951fa1c816b 100644 --- a/src/crystal/system/wasi/process.cr +++ b/src/crystal/system/wasi/process.cr @@ -48,6 +48,21 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.signal") end + def self.on_interrupt(&handler : ->) : Nil + raise NotImplementedError.new("Process.on_interrupt") + end + + def self.ignore_interrupts! : Nil + raise NotImplementedError.new("Process.ignore_interrupts!") + end + + def self.restore_interrupts! : Nil + raise NotImplementedError.new("Process.restore_interrupts!") + end + + def self.start_interrupt_loop : Nil + end + def self.exists?(pid) raise NotImplementedError.new("Process.exists?") end diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index e6d46671361d..c934c5b26047 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -2,12 +2,18 @@ require "c/processthreadsapi" require "c/handleapi" require "c/synchapi" require "process/shell" +require "crystal/atomic_semaphore" struct Crystal::System::Process getter pid : LibC::DWORD @thread_id : LibC::DWORD @process_handle : LibC::HANDLE + @@interrupt_handler : Proc(Nil)? + @@interrupt_count = Crystal::AtomicSemaphore.new + @@win32_interrupt_handler : LibC::PHANDLER_ROUTINE? + @@setup_interrupt_handler = Atomic::Flag.new + def initialize(process_info) @pid = process_info.dwProcessId @thread_id = process_info.dwThreadId @@ -72,6 +78,55 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.signal") end + def self.on_interrupt(&@@interrupt_handler : ->) : Nil + restore_interrupts! + @@win32_interrupt_handler = handler = LibC::PHANDLER_ROUTINE.new do |event_type| + next 0 unless event_type.in?(LibC::CTRL_C_EVENT, LibC::CTRL_BREAK_EVENT) + @@interrupt_count.signal + 1 + end + LibC.SetConsoleCtrlHandler(handler, 1) + end + + def self.ignore_interrupts! : Nil + remove_interrupt_handler + LibC.SetConsoleCtrlHandler(nil, 1) + end + + def self.restore_interrupts! : Nil + remove_interrupt_handler + LibC.SetConsoleCtrlHandler(nil, 0) + end + + private def self.remove_interrupt_handler + if old = @@win32_interrupt_handler + LibC.SetConsoleCtrlHandler(old, 0) + @@win32_interrupt_handler = nil + end + end + + def self.start_interrupt_loop : Nil + return unless @@setup_interrupt_handler.test_and_set + + spawn(name: "Interrupt signal loop") do + while true + @@interrupt_count.wait { sleep 50.milliseconds } + + if handler = @@interrupt_handler + non_nil_handler = handler # if handler is closured it will also have the Nil type + spawn do + non_nil_handler.call + rescue ex + ex.inspect_with_backtrace(STDERR) + STDERR.puts("FATAL: uncaught exception while processing interrupt handler, exiting") + STDERR.flush + LibC._exit(1) + end + end + end + end + end + def self.exists?(pid) handle = LibC.OpenProcess(LibC::PROCESS_QUERY_INFORMATION, 0, pid) return false if handle.nil? diff --git a/src/kernel.cr b/src/kernel.cr index fe7595ddcc96..45df75ef1dbe 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -558,7 +558,9 @@ end end end - {% unless flag?(:win32) %} + {% if flag?(:win32) %} + Crystal::System::Process.start_interrupt_loop + {% else %} Signal.setup_default_handlers {% end %} diff --git a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr index b5e7ac1ebf42..680e199be2ab 100644 --- a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr @@ -13,4 +13,11 @@ lib LibC fun GetConsoleCP : DWORD fun GetConsoleOutputCP : DWORD + + CTRL_C_EVENT = 0 + CTRL_BREAK_EVENT = 1 + + alias PHANDLER_ROUTINE = DWORD -> BOOL + + fun SetConsoleCtrlHandler(handlerRoutine : PHANDLER_ROUTINE, add : BOOL) : BOOL end diff --git a/src/process.cr b/src/process.cr index 2823ff7b17cc..24bd8b005200 100644 --- a/src/process.cr +++ b/src/process.cr @@ -46,6 +46,32 @@ class Process Crystal::System::Process.signal(pid, signal.value) end + # Installs *handler* as the new handler for interrupt requests. Removes any + # previously set interrupt handler. + # + # The handler is executed on a fresh fiber every time an interrupt occurs. + # + # * On Unix-like systems, this traps `SIGINT`. + # * On Windows, this captures Ctrl + C and + # Ctrl + Break signals sent to a console application. + def self.on_interrupt(&handler : ->) : Nil + Crystal::System::Process.on_interrupt(&handler) + end + + # Ignores all interrupt requests. Removes any custom interrupt handler set + # with `#on_interrupt`. + # + # * On Windows, interrupts generated by Ctrl + Break + # cannot be ignored in this way. + def self.ignore_interrupts! : Nil + Crystal::System::Process.ignore_interrupts! + end + + # Restores default handling of interrupt requests. + def self.restore_interrupts! : Nil + Crystal::System::Process.restore_interrupts! + end + # Returns `true` if the process identified by *pid* is valid for # a currently registered process, `false` otherwise. Note that this # returns `true` for a process in the zombie or similar state. diff --git a/src/signal.cr b/src/signal.cr index d1352f1f9f0d..9335619d043f 100644 --- a/src/signal.cr +++ b/src/signal.cr @@ -24,8 +24,10 @@ require "c/unistd" # sleep 3 # ``` # -# Note: -# - An uncaught exception in a signal handler is a fatal error. +# NOTE: `Process.on_interrupt` is preferred over `Signal::INT.trap`, as the +# former also works on Windows. +# +# WARNING: An uncaught exception in a signal handler is a fatal error. enum Signal : Int32 HUP = LibC::SIGHUP INT = LibC::SIGINT diff --git a/src/spec.cr b/src/spec.cr index 5d370e71811a..e9cf5d448efd 100644 --- a/src/spec.cr +++ b/src/spec.cr @@ -127,9 +127,9 @@ end Spec.add_split_filter ENV["SPEC_SPLIT"]? -{% unless flag?(:win32) || flag?(:wasm32) %} - # TODO(windows): re-enable this once Signal is ported - Signal::INT.trap { Spec.abort! } +{% unless flag?(:wasm32) %} + # TODO(wasm): Enable this once `Process.on_interrupt` is implemented + Process.on_interrupt { Spec.abort! } {% end %} Spec.run From b05f35419118f06a97024fb8f2f877fa25163fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 6 Feb 2023 13:24:25 +0100 Subject: [PATCH 0293/1551] Error when `find-llvm-config` is unsuccessful (#13045) --- src/llvm/ext/find-llvm-config | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/llvm/ext/find-llvm-config b/src/llvm/ext/find-llvm-config index 82bd0120caf5..9d42b7381ed4 100755 --- a/src/llvm/ext/find-llvm-config +++ b/src/llvm/ext/find-llvm-config @@ -14,4 +14,11 @@ if ! LLVM_CONFIG=$(command -v "$LLVM_CONFIG"); then [ "$LLVM_CONFIG" ] && break done fi -printf "$LLVM_CONFIG" + +if [ "$LLVM_CONFIG" ]; then + printf "$LLVM_CONFIG" +else + printf "Error: Could not find location of llvm-config. Please specify path in environment variable LLVM_CONFIG.\n" >&2 + printf "Supported LLVM versions: $(cat "$(dirname $0)/llvm-versions.txt" | sed 's/\.0//g')\n" >&2 + exit 1 +fi From a6353f8c9a5eabe38225ecdcbb39df60a7af6128 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Mon, 6 Feb 2023 07:31:36 -0500 Subject: [PATCH 0294/1551] Rename internal `Iterator::Slice` type to not conflict with `::Slice` (#12983) --- spec/std/iterator_spec.cr | 18 +++++ src/iterator.cr | 142 ++++++++++++++++++-------------------- 2 files changed, 87 insertions(+), 73 deletions(-) diff --git a/spec/std/iterator_spec.cr b/spec/std/iterator_spec.cr index 84e6c2ca1b21..417b35612035 100644 --- a/spec/std/iterator_spec.cr +++ b/spec/std/iterator_spec.cr @@ -18,6 +18,20 @@ struct StructIter end end +private class MockIterator + include Iterator(Int32) + + def initialize + @x = 0 + @y = Slice(Int32).new(5) + end + + def next + return stop if @x >= 3 + @x += 1 + end +end + describe Iterator do describe "Iterator.of" do it "creates singleton" do @@ -527,6 +541,10 @@ describe Iterator do iter.next.should eq([7, 8]) iter.next.should be_a(Iterator::Stop) end + + it "doesnt conflict with `::Slice` type" do + assert_iterates_iterator [1, 2, 3], MockIterator.new.each + end end describe "step" do diff --git a/src/iterator.cr b/src/iterator.cr index 6bcc926b50d0..78f6fee31e7b 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -105,10 +105,10 @@ module Iterator(T) end def self.of(element : T) - Singleton(T).new(element) + SingletonIterator(T).new(element) end - private struct Singleton(T) + private struct SingletonIterator(T) include Iterator(T) def initialize(@element : T) @@ -120,7 +120,7 @@ module Iterator(T) end def self.of(&block : -> T) - SingletonProc(typeof(without_stop(&block))).new(block) + SingletonProcIterator(typeof(without_stop(&block))).new(block) end private def self.without_stop(&block : -> T) @@ -129,7 +129,7 @@ module Iterator(T) e end - private struct SingletonProc(T) + private struct SingletonProcIterator(T) include Iterator(T) def initialize(@proc : (-> (T | Iterator::Stop)) | (-> T)) @@ -196,7 +196,7 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def accumulate(&block : T, T -> T) - Accumulate(typeof(self), T).new(self, block) + AccumulateIterator(typeof(self), T).new(self, block) end # Returns an iterator that accumulates *initial* with the original iterator's @@ -215,10 +215,10 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def accumulate(initial : U, &block : U, T -> U) forall U - AccumulateInit(typeof(self), T, U).new(self, initial, block) + AccumulateInitIterator(typeof(self), T, U).new(self, initial, block) end - private class AccumulateInit(I, T, U) + private class AccumulateInitIterator(I, T, U) include Iterator(U) @acc : U | Iterator::Stop @@ -235,7 +235,7 @@ module Iterator(T) end end - private class Accumulate(I, T) + private class AccumulateIterator(I, T) include Iterator(T) include IteratorWrapper @@ -266,10 +266,10 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def chain(other : Iterator(U)) forall U - Chain(typeof(self), typeof(other), T, U).new(self, other) + ChainIterator(typeof(self), typeof(other), T, U).new(self, other) end - private class Chain(I1, I2, T1, T2) + private class ChainIterator(I1, I2, T1, T2) include Iterator(T1 | T2) def initialize(@iterator1 : I1, @iterator2 : I2) @@ -302,7 +302,7 @@ module Iterator(T) # iter.next # => 4 # ``` def self.chain(iters : Iterator(Iter)) forall Iter - ChainsAll(Iter, typeof(iters.first.first)).new iters + ChainsAllIterator(Iter, typeof(iters.first.first)).new iters end # the same as `.chain(Iterator(Iter))` @@ -310,7 +310,7 @@ module Iterator(T) chain iters.each end - private class ChainsAll(Iter, T) + private class ChainsAllIterator(Iter, T) include Iterator(T) @iterators : Iterator(Iter) @current : Iter | Stop @@ -342,10 +342,10 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def compact_map(&func : T -> _) - CompactMap(typeof(self), T, typeof(func.call(first).not_nil!)).new(self, func) + CompactMapIterator(typeof(self), T, typeof(func.call(first).not_nil!)).new(self, func) end - private struct CompactMap(I, T, U) + private struct CompactMapIterator(I, T, U) include Iterator(U) include IteratorWrapper @@ -389,13 +389,13 @@ module Iterator(T) if reuse.nil? || reuse.is_a?(Bool) # we use an initial capacity of n * 2, because a second iteration would # have reallocated the array to that capacity anyway - Cons(typeof(self), T, typeof(n), Array(T)).new(self, n, Array(T).new(n * 2), reuse) + ConsIterator(typeof(self), T, typeof(n), Array(T)).new(self, n, Array(T).new(n * 2), reuse) else - Cons(typeof(self), T, typeof(n), typeof(reuse)).new(self, n, reuse, reuse) + ConsIterator(typeof(self), T, typeof(n), typeof(reuse)).new(self, n, reuse, reuse) end end - private struct Cons(I, T, N, V) + private struct ConsIterator(I, T, N, V) include Iterator(Array(T)) include IteratorWrapper @@ -435,10 +435,10 @@ module Iterator(T) # This method is just an optimized implementation for the special case of # `n == 2` to avoid heap allocations. def cons_pair : Iterator({T, T}) - ConsTuple(typeof(self), T).new(self) + ConsTupleIterator(typeof(self), T).new(self) end - private struct ConsTuple(I, T) + private struct ConsTupleIterator(I, T) include Iterator({T, T}) include IteratorWrapper @@ -477,10 +477,10 @@ module Iterator(T) # # and so an and so on # ``` def cycle - Cycle(typeof(self), T).new(self) + CycleIterator(typeof(self), T).new(self) end - private struct Cycle(I, T) + private struct CycleIterator(I, T) include Iterator(T) include IteratorWrapper @@ -533,10 +533,10 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def cycle(n : Int) - CycleN(typeof(self), T, typeof(n)).new(self, n) + CycleNIterator(typeof(self), T, typeof(n)).new(self, n) end - private class CycleN(I, T, N) + private class CycleNIterator(I, T, N) include Iterator(T) include IteratorWrapper @@ -635,10 +635,10 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def flatten - Flatten(typeof(Flatten.iterator_type(self)), typeof(Flatten.element_type(self))).new(self) + FlattenIterator(typeof(FlattenIterator.iterator_type(self)), typeof(FlattenIterator.element_type(self))).new(self) end - private struct Flatten(I, T) + private struct FlattenIterator(I, T) include Iterator(T) @iterator : I @@ -715,10 +715,10 @@ module Iterator(T) # iter.to_a # => [1, 1, 2, 2, 3, 3] # ``` def flat_map(&func : T -> _) - FlatMap(typeof(self), typeof(FlatMap.element_type(self, func)), typeof(FlatMap.iterator_type(self, func)), typeof(func)).new self, func + FlatMapIterator(typeof(self), typeof(FlatMapIterator.element_type(self, func)), typeof(FlatMapIterator.iterator_type(self, func)), typeof(func)).new self, func end - private class FlatMap(I0, T, I, F) + private class FlatMapIterator(I0, T, I, F) include Iterator(T) include IteratorWrapper @@ -809,10 +809,10 @@ module Iterator(T) # interest is to be used in a read-only fashion. def in_groups_of(size : Int, filled_up_with = nil, reuse = false) raise ArgumentError.new("Size must be positive") if size <= 0 - InGroupsOf(typeof(self), T, typeof(size), typeof(filled_up_with)).new(self, size, filled_up_with, reuse) + InGroupsOfIterator(typeof(self), T, typeof(size), typeof(filled_up_with)).new(self, size, filled_up_with, reuse) end - private struct InGroupsOf(I, T, N, U) + private struct InGroupsOfIterator(I, T, N, U) include Iterator(Array(T | U)) include IteratorWrapper @@ -861,10 +861,10 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def map(&func : T -> U) forall U - Map(typeof(self), T, U).new(self, func) + MapIterator(typeof(self), T, U).new(self, func) end - private struct Map(I, T, U) + private struct MapIterator(I, T, U) include Iterator(U) include IteratorWrapper @@ -886,7 +886,7 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def reject(&func : T -> U) forall U - Reject(typeof(self), T, U).new(self, func) + RejectIterator(typeof(self), T, U).new(self, func) end # Returns an iterator that only returns elements @@ -899,7 +899,7 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def reject(type : U.class) forall U - SelectType(typeof(self), typeof(begin + SelectTypeIterator(typeof(self), typeof(begin e = first e.is_a?(U) ? raise("") : e end)).new(self) @@ -919,7 +919,7 @@ module Iterator(T) reject { |elem| pattern === elem } end - private struct Reject(I, T, B) + private struct RejectIterator(I, T, B) include Iterator(T) include IteratorWrapper @@ -946,7 +946,7 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def select(&func : T -> U) forall U - Select(typeof(self), T, U).new(self, func) + SelectIterator(typeof(self), T, U).new(self, func) end # Returns an iterator that only returns elements @@ -959,7 +959,7 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def select(type : U.class) forall U - SelectType(typeof(self), U).new(self) + SelectTypeIterator(typeof(self), U).new(self) end # Returns an iterator that only returns elements @@ -976,7 +976,7 @@ module Iterator(T) self.select { |elem| pattern === elem } end - private struct Select(I, T, B) + private struct SelectIterator(I, T, B) include Iterator(T) include IteratorWrapper @@ -993,7 +993,7 @@ module Iterator(T) end end - private struct SelectType(I, T) + private struct SelectTypeIterator(I, T) include Iterator(T) include IteratorWrapper @@ -1020,10 +1020,10 @@ module Iterator(T) # ``` def skip(n : Int) raise ArgumentError.new "Attempted to skip negative size: #{n}" if n < 0 - Skip(typeof(self), T, typeof(n)).new(self, n) + SkipIterator(typeof(self), T, typeof(n)).new(self, n) end - private class Skip(I, T, N) + private class SkipIterator(I, T, N) include Iterator(T) include IteratorWrapper @@ -1051,10 +1051,10 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def skip_while(&func : T -> U) forall U - SkipWhile(typeof(self), T, U).new(self, func) + SkipWhileIterator(typeof(self), T, U).new(self, func) end - private class SkipWhile(I, T, U) + private class SkipWhileIterator(I, T, U) include Iterator(T) include IteratorWrapper @@ -1077,10 +1077,10 @@ module Iterator(T) # Alias of `each_slice`. def slice(n : Int, reuse = false) raise ArgumentError.new "Invalid slice size: #{n}" if n <= 0 - Slice(typeof(self), T, typeof(n)).new(self, n, reuse) + SliceIterator(typeof(self), T, typeof(n)).new(self, n, reuse) end - private struct Slice(I, T, N) + private struct SliceIterator(I, T, N) include Iterator(Array(T)) include IteratorWrapper @@ -1132,10 +1132,10 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def step(n : Int) - Step(self, T, typeof(n)).new(self, n) + StepByIterator(self, T, typeof(n)).new(self, n) end - private struct Step(I, T, N) + private struct StepByIterator(I, T, N) include Iterator(T) include IteratorWrapper @@ -1166,10 +1166,10 @@ module Iterator(T) # ``` def first(n : Int) raise ArgumentError.new "Attempted to take negative size: #{n}" if n < 0 - First(typeof(self), T, typeof(n)).new(self, n) + FirstIterator(typeof(self), T, typeof(n)).new(self, n) end - private class First(I, T, N) + private class FirstIterator(I, T, N) include Iterator(T) include IteratorWrapper @@ -1197,10 +1197,10 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def take_while(&func : T -> U) forall U - TakeWhile(typeof(self), T, U).new(self, func) + TakeWhileIterator(typeof(self), T, U).new(self, func) end - private class TakeWhile(I, T, U) + private class TakeWhileIterator(I, T, U) include Iterator(T) include IteratorWrapper @@ -1235,10 +1235,10 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def tap(&block : T ->) - Tap(typeof(self), T).new(self, block) + TapIterator(typeof(self), T).new(self, block) end - private struct Tap(I, T) + private struct TapIterator(I, T) include Iterator(T) include IteratorWrapper @@ -1276,10 +1276,10 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def uniq(&func : T -> U) forall U - Uniq(typeof(self), T, U).new(self, func) + UniqIterator(typeof(self), T, U).new(self, func) end - private struct Uniq(I, T, U) + private struct UniqIterator(I, T, U) include Iterator(T) include IteratorWrapper @@ -1310,7 +1310,7 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def with_index(offset : Int = 0) - WithIndex(typeof(self), T, typeof(offset)).new(self, offset) + WithIndexIterator(typeof(self), T, typeof(offset)).new(self, offset) end # Yields each element in this iterator together with its index. @@ -1322,7 +1322,7 @@ module Iterator(T) end end - private class WithIndex(I, T, O) + private class WithIndexIterator(I, T, O) include Iterator({T, Int32}) include IteratorWrapper @@ -1347,7 +1347,7 @@ module Iterator(T) # iter.next # => Iterator::Stop::INSTANCE # ``` def with_object(obj) - WithObject(typeof(self), T, typeof(obj)).new(self, obj) + WithObjectIterator(typeof(self), T, typeof(obj)).new(self, obj) end # Yields each element in this iterator together with *obj*. Returns that object. @@ -1358,7 +1358,7 @@ module Iterator(T) obj end - private struct WithObject(I, T, O) + private struct WithObjectIterator(I, T, O) include Iterator({T, O}) include IteratorWrapper @@ -1392,7 +1392,7 @@ module Iterator(T) protected def self.zip_impl(*iterators : *U) forall U {% begin %} - Zip(U, Tuple( + ZipIterator(U, Tuple( {% for i in 0...U.size %} typeof(iterators[{{ i }}].first), {% end %} @@ -1400,7 +1400,7 @@ module Iterator(T) {% end %} end - private struct Zip(Is, Ts) + private struct ZipIterator(Is, Ts) include Iterator(Ts) def initialize(@iterators : Is) @@ -1456,11 +1456,10 @@ module Iterator(T) # # See also: `Enumerable#chunks`. def chunk(reuse = false, &block : T -> U) forall T, U - Chunk(typeof(self), T, U).new(self, reuse, &block) + ChunkIterator(typeof(self), T, U).new(self, reuse, &block) end - # :nodoc: - class Chunk(I, T, U) + private class ChunkIterator(I, T, U) include Iterator(Tuple(U, Array(T))) @iterator : I @init : {U, T}? @@ -1528,7 +1527,7 @@ module Iterator(T) # This can be used to prevent many memory allocations when each slice of # interest is to be used in a read-only fashion. def slice_after(reuse : Bool | Array(T) = false, &block : T -> B) forall B - SliceAfter(typeof(self), T, B).new(self, block, reuse) + SliceAfterIterator(typeof(self), T, B).new(self, block, reuse) end # Returns an iterator over chunks of elements, where each @@ -1559,8 +1558,7 @@ module Iterator(T) slice_after(reuse) { |elem| pattern === elem } end - # :nodoc: - class SliceAfter(I, T, B) + private class SliceAfterIterator(I, T, B) include Iterator(Array(T)) def initialize(@iterator : I, @block : T -> B, reuse) @@ -1634,7 +1632,7 @@ module Iterator(T) # This can be used to prevent many memory allocations when each slice of # interest is to be used in a read-only fashion. def slice_before(reuse : Bool | Array(T) = false, &block : T -> B) forall B - SliceBefore(typeof(self), T, B).new(self, block, reuse) + SliceBeforeIterator(typeof(self), T, B).new(self, block, reuse) end # Returns an iterator over chunks of elements, where each @@ -1665,8 +1663,7 @@ module Iterator(T) slice_before(reuse) { |elem| pattern === elem } end - # :nodoc: - class SliceBefore(I, T, B) + private class SliceBeforeIterator(I, T, B) include Iterator(Array(T)) @has_value_to_add = false @@ -1748,7 +1745,7 @@ module Iterator(T) # # See also `#chunk_while`, which works similarly but the block's condition is inverted. def slice_when(reuse : Bool | Array(T) = false, &block : T, T -> B) forall B - SliceWhen(typeof(self), T, B).new(self, block, reuse) + SliceWhenIterator(typeof(self), T, B).new(self, block, reuse) end # Returns an iterator for each chunked elements where elements @@ -1778,11 +1775,10 @@ module Iterator(T) # # See also `#slice_when`, which works similarly but the block's condition is inverted. def chunk_while(reuse : Bool | Array(T) = false, &block : T, T -> B) forall B - SliceWhen(typeof(self), T, B).new(self, block, reuse, negate: true) + SliceWhenIterator(typeof(self), T, B).new(self, block, reuse, negate: true) end - # :nodoc: - class SliceWhen(I, T, B) + private class SliceWhenIterator(I, T, B) include Iterator(Array(T)) @has_previous_value = false From 61ea03300de695a361e40de5687cfd87585539f1 Mon Sep 17 00:00:00 2001 From: Chao Yang Date: Mon, 6 Feb 2023 06:31:58 -0600 Subject: [PATCH 0295/1551] Add `OAuth2::Client#make_token_request` returning HTTP response (#12921) --- spec/std/oauth2/client_spec.cr | 55 ++++++++++++++++++++++++++++++++++ src/oauth2/client.cr | 41 +++++++++++++++---------- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/spec/std/oauth2/client_spec.cr b/spec/std/oauth2/client_spec.cr index 7bca9ae76541..e3f24a679ae4 100644 --- a/spec/std/oauth2/client_spec.cr +++ b/spec/std/oauth2/client_spec.cr @@ -128,6 +128,34 @@ describe OAuth2::Client do token.access_token.should eq "access_token" end end + + it "#make_token_request" do + handler = HTTP::Handler::HandlerProc.new do |context| + body = context.request.body.not_nil!.gets_to_end + dpop = context.request.headers.get?("DPoP") + response = {access_token: "access_token", body: body, dpop: dpop} + context.response.print response.to_json + end + + run_handler(handler) do |http_client| + client = OAuth2::Client.new "127.0.0.1", "client_id", "client_secret", scheme: "http" + client.http_client = http_client + + token_response = client.make_token_request do |form, headers| + form.add("redirect_uri", client.redirect_uri) + form.add("grant_type", "authorization_code") + form.add("code", "some_authorization_code") + form.add("code_verifier", "a_code_verifier") + form.add("nonce", "a_nonce") + headers.add("DPoP", "a_DPoP_jwt_token") + end + token_response.status_code.should eq(200) + token = OAuth2::AccessToken.from_json(token_response.body) + token.extra.not_nil!["body"].should eq %("redirect_uri=&grant_type=authorization_code&code=some_authorization_code&code_verifier=a_code_verifier&nonce=a_nonce") + token.extra.not_nil!["dpop"].should eq %(["a_DPoP_jwt_token"]) + token.access_token.should eq "access_token" + end + end end describe "using Request Body to pass credentials" do it "#get_access_token_using_authorization_code" do @@ -197,6 +225,33 @@ describe OAuth2::Client do token.access_token.should eq "access_token" end end + + it "#make_token_request" do + handler = HTTP::Handler::HandlerProc.new do |context| + body = context.request.body.not_nil!.gets_to_end + dpop = context.request.headers.get?("DPoP") + response = {access_token: "access_token", body: body, dpop: dpop} + context.response.print response.to_json + end + + run_handler(handler) do |http_client| + client = OAuth2::Client.new "127.0.0.1", "client_id", "client_secret", scheme: "http", auth_scheme: OAuth2::AuthScheme::RequestBody + client.http_client = http_client + + token_response = client.make_token_request do |form, headers| + form.add("grant_type", "refresh_token") + form.add("refresh_token", "some_refresh_token") + form.add("scope", "read_posts") + form.add("nonce", "a_nonce") + headers.add("DPoP", "a_DPoP_jwt_token") + end + token_response.status_code.should eq(200) + token = OAuth2::AccessToken.from_json(token_response.body) + token.extra.not_nil!["body"].should eq %("client_id=client_id&client_secret=client_secret&grant_type=refresh_token&refresh_token=some_refresh_token&scope=read_posts&nonce=a_nonce") + token.extra.not_nil!["dpop"].should eq %(["a_DPoP_jwt_token"]) + token.access_token.should eq "access_token" + end + end end end diff --git a/src/oauth2/client.cr b/src/oauth2/client.cr index 9718c7b9e3ed..df30eafd8a3b 100644 --- a/src/oauth2/client.cr +++ b/src/oauth2/client.cr @@ -54,9 +54,17 @@ # You can also use an `OAuth2::Session` to automatically refresh expired # tokens before each request. class OAuth2::Client + DEFAULT_HEADERS = HTTP::Headers{ + "Accept" => "application/json", + "Content-Type" => "application/x-www-form-urlencoded", + } + # Sets the `HTTP::Client` to use with this client. setter http_client : HTTP::Client? + # Gets the redirect_uri + getter redirect_uri : String? + # Returns the `HTTP::Client` to use with this client. # # By default, this returns a new instance every time. To reuse the same instance, @@ -105,13 +113,13 @@ class OAuth2::Client end uri.query = URI::Params.build do |form| - form.add "client_id", @client_id - form.add "redirect_uri", @redirect_uri - form.add "response_type", "code" - form.add "scope", scope unless scope.nil? - form.add "state", state unless state.nil? + form.add("client_id", @client_id) + form.add("redirect_uri", @redirect_uri) + form.add("response_type", "code") + form.add("scope", scope) unless scope.nil? + form.add("state", state) unless state.nil? uri.query_params.each do |key, value| - form.add key, value + form.add(key, value) end yield form end @@ -155,16 +163,13 @@ class OAuth2::Client get_access_token do |form| form.add("grant_type", "refresh_token") form.add("refresh_token", refresh_token) - form.add "scope", scope unless scope.nil? + form.add("scope", scope) unless scope.nil? end end - private def get_access_token(&) : AccessToken - headers = HTTP::Headers{ - "Accept" => "application/json", - "Content-Type" => "application/x-www-form-urlencoded", - } - + # Makes a token exchange request with custom headers and form fields + def make_token_request(&block : URI::Params::Builder, HTTP::Headers -> _) : HTTP::Client::Response + headers = DEFAULT_HEADERS.dup body = URI::Params.build do |form| case @auth_scheme when .request_body? @@ -176,10 +181,16 @@ class OAuth2::Client "Basic #{Base64.strict_encode("#{@client_id}:#{@client_secret}")}" ) end - yield form + yield form, headers end - response = http_client.post token_uri.request_target, form: body, headers: headers + http_client.post token_uri.request_target, form: body, headers: headers + end + + private def get_access_token(&) : AccessToken + response = make_token_request do |form, _headers| + yield form + end case response.status when .ok?, .created? OAuth2::AccessToken.from_json(response.body) From d74f07f52fab9de9f18332420334ba11f00f29e6 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Tue, 7 Feb 2023 05:33:29 -0500 Subject: [PATCH 0296/1551] Handle namespaces within `TypeNode#has_constant?` and `TypeNode#constant` (#12966) --- spec/compiler/macro/macro_methods_spec.cr | 120 ++++++++++++++++++++++ src/compiler/crystal/macros.cr | 49 ++++++++- src/compiler/crystal/macros/methods.cr | 46 +++++---- 3 files changed, 192 insertions(+), 23 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index b72f95a31678..2f3c8bde6638 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1866,6 +1866,126 @@ module Crystal end end + describe "#constant" do + it "global path" do + assert_type(%( + ID = 10 + + class A + class B + end + end + + {{ A::B.constant("::ID") == 10 ? 1 : 'f' }} + )) { int32 } + end + + it "global path with extra ::" do + assert_macro_error %({{ @type.constant "::::ID" }}), %(Invalid constant name: "::::ID") + end + + it "const within another type from the top level" do + assert_type(%( + class A + class B + ID = 10 + end + end + + {{ @type.constant("A::B::ID") == 10 ? 1 : 'f' }} + )) { int32 } + end + + it "const within another type from the top level with extra ::" do + assert_macro_error %({{ @type.constant "A::::::B::::ID" }}), %(Invalid constant name: "A::::::B::::ID") + end + + it "type within another type from the top level" do + assert_type(%( + class A + class B + end + end + + {{ @type.constant("A::B").class? ? 1 : 'f' }} + )) { int32 } + end + end + + describe "#has_constant?" do + it "global path with extra ::" do + assert_macro_error %({{ @type.has_constant? "::::ID" }}), %(Invalid constant name: "::::ID") + end + + it "global path" do + assert_type(%( + class A + class B + end + end + + {{ A::B.has_constant?("::A") ? 1 : 'f' }} + )) { int32 } + end + + it "type on the top level" do + assert_type(%( + class A + end + + {{ @type.has_constant?("A") ? 1 : 'f' }} + )) { int32 } + end + + it "constant within a type from that type" do + assert_type(%( + class A + ID = 10 + end + + {{ A.has_constant?("ID") ? 1 : 'f' }} + )) { int32 } + end + + it "type within another type" do + assert_type(%( + class A + class B + end + end + + {{ A.has_constant?("B") ? 1 : 'f' }} + )) { int32 } + end + + it "type within another type from the top level" do + assert_type(%( + class A + class B + end + end + + {{ @type.has_constant?("A::B") ? 1 : 'f' }} + )) { int32 } + end + + it "type within another type from the top level with extra ::" do + assert_macro_error %(class A; class B; end; end; {{ @type.has_constant? "A::::::B" }}), %(Invalid constant name: "A::::::B") + end + + it "constant within a nested type from the top level" do + assert_type(%( + class A + class B + ID = 20 + end + end + + {{ @type.has_constant?("A::B::ID") ? 1 : 'f' }} + )) { int32 } + end + end + describe "#abstract?" do it NonGenericModuleType do assert_macro("{{type.abstract?}}", "false") do |program| diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 8b5c6845c0b2..7a8251e38400 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2097,13 +2097,52 @@ module Crystal::Macros # If the constant is a constant (like `A = 1`), then its value # as an `ASTNode` is returned. If the constant is a type, the # type is returned as a `TypeNode`. Otherwise, `NilLiteral` is returned. - def constant(name : StringLiteral | SymbolLiteral | MacroId) : ASTNode + # + # ``` + # TOP_VALUE = 123 + # + # module Foo + # ID = 1 + # + # class Bar + # struct Baz + # end + # end + # end + # + # {{ Foo.constant "ID" }} # => 10 + # {{ Foo.constant(:Bar).class? }} # => true + # {{ Foo.constant("Bar").class? }} # => true + # {{ Foo.constant(Bar::Baz.id).struct? }} # => true + # {{ Foo.constant("::TOP_VALUE") }} # => 123 + # ``` + def constant(name : StringLiteral | SymbolLiteral | MacroId) : ASTNode | NilLiteral end - # Returns `true` if this type has a constant. For example `DEFAULT_OPTIONS` - # (the name you pass to this method is `"DEFAULT_OPTIONS"` or `:DEFAULT_OPTIONS` - # in this cases). - def has_constant?(name : StringLiteral | SymbolLiteral) : BoolLiteral + # Returns `true` if this type has a constant/type with the provided *name*. + # + # ``` + # module Foo + # ID = 1 + # + # module Bar + # module Baz + # end + # end + # end + # + # {{ Foo.has_constant? "ID" }} # => true + # {{ Foo.has_constant? :Bar }} # => true + # {{ Foo.has_constant? "Bar" }} # => true + # {{ Foo.has_constant? Foo::Bar.id }} # => true + # + # {{ @top_level.has_constant? "Foo" }} # => true + # {{ @type.has_constant? "Foo" }} # => true + # {{ @type.has_constant? "Foo::Bar::Baz" }} # => true + # {{ @type.has_constant? "Bar" }} # => false + # {{ @type.has_constant? "Foo::Bar::Biz" }} # => false + # ``` + def has_constant?(name : StringLiteral | SymbolLiteral | MacroId) : BoolLiteral end # Returns the instance methods defined by this type, without including diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index d67f94e3ac84..d56f03d08dba 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -158,6 +158,31 @@ module Crystal end end + def interpret_constant(arg, type, name) + case type = type.lookup_path parse_path(arg, name) + when Const + type.value + when Type + TypeNode.new(type) + else + NilLiteral.new + end + end + + def interpret_has_constant?(arg, type, name) + BoolLiteral.new !!type.lookup_path parse_path(arg, name) + end + + private def parse_path(arg, name) : Path + parser = @program.new_parser name + parser.next_token + path = parser.parse_path + parser.check :EOF + @last = path + rescue ex : Crystal::SyntaxException + arg.raise "Invalid constant name: #{name.inspect}" + end + def interpret_parse_type(node) interpret_check_args_toplevel do |arg| arg.accept self @@ -1648,12 +1673,13 @@ module Crystal when "constant" interpret_check_args do |arg| value = arg.to_string("argument to 'TypeNode#constant'") - TypeNode.constant(type, value) + interpreter.interpret_constant arg, type, value end when "has_constant?" interpret_check_args do |arg| value = arg.to_string("argument to 'TypeNode#has_constant?'") - TypeNode.has_constant?(type, value) + + interpreter.interpret_has_constant? arg, type, value end when "methods" interpret_check_args { TypeNode.methods(type) } @@ -1913,22 +1939,6 @@ module Crystal end end - def self.has_constant?(type, name) - BoolLiteral.new(type.types.has_key?(name)) - end - - def self.constant(type, name) - type = type.types[name]? - case type - when Const - type.value - when Type - TypeNode.new(type) - else - NilLiteral.new - end - end - def self.methods(type) defs = [] of ASTNode type.defs.try &.each do |name, metadatas| From c2f5c03fbed1aaaba4073540293d4fa0c86bde5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 7 Feb 2023 18:49:57 +0100 Subject: [PATCH 0297/1551] Update distribution-scripts (#13051) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c84f70121072..27d980b883cd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "d8c44c0a2c977bfb90363923047f2c64cef3bf8e" + default: "b668df09ead5cbad9fdbff8ba3c5c0878fc99cbe" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From 33b55a4a6f10deb5adad1a3e075e8e7b7c85f784 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 8 Feb 2023 20:18:14 +0800 Subject: [PATCH 0298/1551] Propagate exit code correctly in Windows wrapper batch script (#13048) --- bin/crystal.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/crystal.ps1 b/bin/crystal.ps1 index e5e314a5bcfb..cf0a0fbe5750 100644 --- a/bin/crystal.ps1 +++ b/bin/crystal.ps1 @@ -206,6 +206,9 @@ function Exec-Process { # workaround to obtain the exit status properly: https://stackoverflow.com/a/23797762 $hnd = $Process.Handle Wait-Process -Id $Process.Id + + # and return it properly too: https://stackoverflow.com/a/50202663 + $host.SetShouldExit($Process.ExitCode) Exit $Process.ExitCode } From 2d49b0795eaed17c63972cc3d75e633d3e33724c Mon Sep 17 00:00:00 2001 From: "Brian J. Cardiff" Date: Wed, 8 Feb 2023 09:19:02 -0300 Subject: [PATCH 0299/1551] Add memory barriers on lock/unlock of SpinLock (#13050) --- src/crystal/spin_lock.cr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/crystal/spin_lock.cr b/src/crystal/spin_lock.cr index f0b4b2df4ab6..e3136cdf3fe9 100644 --- a/src/crystal/spin_lock.cr +++ b/src/crystal/spin_lock.cr @@ -11,11 +11,17 @@ class Crystal::SpinLock Intrinsics.pause end end + {% if flag?(:aarch64) %} + Atomic::Ops.fence :sequentially_consistent, false + {% end %} {% end %} end def unlock {% if flag?(:preview_mt) %} + {% if flag?(:aarch64) %} + Atomic::Ops.fence :sequentially_consistent, false + {% end %} @m.lazy_set(0) {% end %} end From 39cac0e209eca4d181465a0042e8393c2c802807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 9 Feb 2023 18:08:37 +0100 Subject: [PATCH 0300/1551] Increase timeout for slow specs (#13043) --- spec/std/exception_spec.cr | 2 +- spec/support/mt_abort_timeout.cr | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/std/exception_spec.cr b/spec/std/exception_spec.cr index dda6e7dc4cc2..9c827a59c044 100644 --- a/spec/std/exception_spec.cr +++ b/spec/std/exception_spec.cr @@ -35,7 +35,7 @@ describe "Exception" do ex.inspect_with_backtrace.should contain("inner") end - it "collect memory within ensure block" do + it "collect memory within ensure block", tags: %w[slow] do sample = datapath("collect_within_ensure") _, output, error = compile_and_run_file(sample, ["--release"]) diff --git a/spec/support/mt_abort_timeout.cr b/spec/support/mt_abort_timeout.cr index 7587739befbb..7339da6c07a1 100644 --- a/spec/support/mt_abort_timeout.cr +++ b/spec/support/mt_abort_timeout.cr @@ -15,12 +15,17 @@ Spec.around_each do |example| end end + timeout = SPEC_TIMEOUT + if example.example.all_tags.includes?("slow") + timeout *= 4 + end + select when res = done.receive raise res if res - when timeout(SPEC_TIMEOUT) + when timeout(timeout) _it = example.example - ex = Spec::AssertionFailed.new("spec timeout", _it.file, _it.line) - _it.parent.report(:fail, _it.description, _it.file, _it.line, SPEC_TIMEOUT, ex) + ex = Spec::AssertionFailed.new("spec timed out after #{timeout}", _it.file, _it.line) + _it.parent.report(:fail, _it.description, _it.file, _it.line, timeout, ex) end end From 1aa099d45a71c9925b20381860340192c7b31f35 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 11 Feb 2023 06:51:55 +0800 Subject: [PATCH 0301/1551] Do not use `@[ThreadLocal]` for PCRE2's JIT stack (#13056) --- src/regex/pcre2.cr | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 08b823083086..14b64e72546d 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -1,4 +1,5 @@ require "./lib_pcre2" +require "crystal/thread_local_value" # :nodoc: module Regex::PCRE2 @@ -141,13 +142,12 @@ module Regex::PCRE2 # # Only a single `match` function can run per thread at any given time, so there # can't be any concurrent access to the JIT stack. - @[ThreadLocal] - class_getter jit_stack : LibPCRE2::JITStack do - jit_stack = LibPCRE2.jit_stack_create(32_768, 1_048_576, Regex::PCRE2.general_context) - if jit_stack.null? - raise "Error allocating JIT stack" + @@jit_stack = Crystal::ThreadLocalValue(LibPCRE2::JITStack).new + + def self.jit_stack + @@jit_stack.get do + LibPCRE2.jit_stack_create(32_768, 1_048_576, general_context) || raise "Error allocating JIT stack" end - jit_stack end private def match_data(str, byte_index, options) From 21fdcf79d0668c23408f7d692e316ac18b79cd9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 10 Feb 2023 23:54:26 +0100 Subject: [PATCH 0302/1551] [CI] Use Ubuntu 22.04 base image for LLVM tests (#13035) --- .github/workflows/linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 552e1cc035fd..fdf243f7ebeb 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -63,10 +63,10 @@ jobs: include: - llvm_version: 13.0.0 llvm_url: https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz - base_image: ubuntu-20.04 + base_image: ubuntu-22.04 - llvm_version: 14.0.0 llvm_url: https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz - base_image: ubuntu-18.04 + base_image: ubuntu-22.04 runs-on: ${{ matrix.base_image }} name: "Test LLVM ${{ matrix.llvm_version }} (${{ matrix.base_image }})" steps: From 0dd849aae463e6cd9b3d4b859de5a657e00581a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 10 Feb 2023 23:54:38 +0100 Subject: [PATCH 0303/1551] Reduce JSON, YAML serializable test types (#13042) --- spec/std/json/serializable_spec.cr | 108 ++++++----------------------- spec/std/yaml/serializable_spec.cr | 93 +++++++------------------ 2 files changed, 48 insertions(+), 153 deletions(-) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index f768c802d0c3..877818d9398e 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -6,6 +6,12 @@ require "big/json" require "uuid" require "uuid/json" +class JSONAttrValue(T) + include JSON::Serializable + + property value : T +end + record JSONAttrPoint, x : Int32, y : Int32 do include JSON::Serializable end @@ -82,36 +88,6 @@ class JSONAttrPersonEmittingNullsByOptions property value2 : Int32? end -class JSONAttrWithBool - include JSON::Serializable - - property value : Bool -end - -class JSONAttrWithFloat - include JSON::Serializable - - property value : Float64 -end - -class JSONAttrWithUUID - include JSON::Serializable - - property value : UUID -end - -class JSONAttrWithBigDecimal - include JSON::Serializable - - property value : BigDecimal -end - -class JSONAttrWithInt128 - include JSON::Serializable - - property value : Int128 -end - class JSONAttrWithTime include JSON::Serializable @@ -181,12 +157,6 @@ class JSONAttrWithTimeHash3 property value : Hash(String, Time) end -class JSONAttrWithPropertiesKey - include JSON::Serializable - - property properties : Hash(String, String) -end - class JSONAttrWithSimpleMapping include JSON::Serializable @@ -215,12 +185,6 @@ class JSONAttrWithProblematicKeys property pull : Int32 end -class JSONAttrWithSet - include JSON::Serializable - - property set : Set(String) -end - class JSONAttrWithDefaults include JSON::Serializable @@ -283,18 +247,6 @@ class JSONAttrWithNilableRootEmitNull property result : Array(JSONAttrPerson)? end -class JSONAttrWithNilableUnion - include JSON::Serializable - - property value : Int32? -end - -class JSONAttrWithNilableUnion2 - include JSON::Serializable - - property value : Int32 | Nil -end - class JSONAttrWithPresence include JSON::Serializable @@ -639,18 +591,18 @@ describe "JSON mapping" do end it "doesn't raises on false value when not-nil" do - json = JSONAttrWithBool.from_json(%({"value": false})) + json = JSONAttrValue(Bool).from_json(%({"value": false})) json.value.should be_false end it "parses JSON integer into a float property (#8618)" do - json = JSONAttrWithFloat.from_json(%({"value": 123})) + json = JSONAttrValue(Float64).from_json(%({"value": 123})) json.value.should eq(123.0) end it "parses UUID" do - uuid = JSONAttrWithUUID.from_json(%({"value": "ba714f86-cac6-42c7-8956-bcf5105e1b81"})) - uuid.should be_a(JSONAttrWithUUID) + uuid = JSONAttrValue(UUID).from_json(%({"value": "ba714f86-cac6-42c7-8956-bcf5105e1b81"})) + uuid.should be_a(JSONAttrValue(UUID)) uuid.value.should eq(UUID.new("ba714f86-cac6-42c7-8956-bcf5105e1b81")) end @@ -684,11 +636,11 @@ describe "JSON mapping" do json.to_json.should eq(%({"value":null})) end - it "outputs JSON with properties key" do + it "outputs JSON with Hash" do input = { - properties: {"foo" => "bar"}, + value: {"foo" => "bar"}, }.to_json - json = JSONAttrWithPropertiesKey.from_json(input) + json = JSONAttrValue(Hash(String, String)).from_json(input) json.to_json.should eq(input) end @@ -712,8 +664,8 @@ describe "JSON mapping" do end it "parses json array as set" do - json = JSONAttrWithSet.from_json(%({"set": ["a", "a", "b"]})) - json.set.should eq(Set(String){"a", "b"}) + json = JSONAttrValue(Set(String)).from_json(%({"value": ["a", "a", "b"]})) + json.value.should eq(Set(String){"a", "b"}) end it "allows small types of integer" do @@ -908,29 +860,15 @@ describe "JSON mapping" do end it "parses nilable union" do - obj = JSONAttrWithNilableUnion.from_json(%({"value": 1})) - obj.value.should eq(1) - obj.to_json.should eq(%({"value":1})) - - obj = JSONAttrWithNilableUnion.from_json(%({"value": null})) - obj.value.should be_nil - obj.to_json.should eq(%({})) - - obj = JSONAttrWithNilableUnion.from_json(%({})) - obj.value.should be_nil - obj.to_json.should eq(%({})) - end - - it "parses nilable union2" do - obj = JSONAttrWithNilableUnion2.from_json(%({"value": 1})) + obj = JSONAttrValue(Int32?).from_json(%({"value": 1})) obj.value.should eq(1) obj.to_json.should eq(%({"value":1})) - obj = JSONAttrWithNilableUnion2.from_json(%({"value": null})) + obj = JSONAttrValue(Int32?).from_json(%({"value": null})) obj.value.should be_nil obj.to_json.should eq(%({})) - obj = JSONAttrWithNilableUnion2.from_json(%({})) + obj = JSONAttrValue(Int32?).from_json(%({})) obj.value.should be_nil obj.to_json.should eq(%({})) end @@ -1031,28 +969,28 @@ describe "JSON mapping" do describe "BigDecimal" do it "parses json string with BigDecimal" do - json = JSONAttrWithBigDecimal.from_json(%({"value": "10.05"})) + json = JSONAttrValue(BigDecimal).from_json(%({"value": "10.05"})) json.value.should eq(BigDecimal.new("10.05")) end it "parses large json ints with BigDecimal" do - json = JSONAttrWithBigDecimal.from_json(%({"value": 9223372036854775808})) + json = JSONAttrValue(BigDecimal).from_json(%({"value": 9223372036854775808})) json.value.should eq(BigDecimal.new("9223372036854775808")) end it "parses json float with BigDecimal" do - json = JSONAttrWithBigDecimal.from_json(%({"value": 10.05})) + json = JSONAttrValue(BigDecimal).from_json(%({"value": 10.05})) json.value.should eq(BigDecimal.new("10.05")) end it "parses large precision json floats with BigDecimal" do - json = JSONAttrWithBigDecimal.from_json(%({"value": 0.00045808999999999997})) + json = JSONAttrValue(BigDecimal).from_json(%({"value": 0.00045808999999999997})) json.value.should eq(BigDecimal.new("0.00045808999999999997")) end end it "parses 128-bit integer" do - json = JSONAttrWithInt128.from_json(%({"value": #{Int128::MAX}})) + json = JSONAttrValue(Int128).from_json(%({"value": #{Int128::MAX}})) json.value.should eq Int128::MAX end diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index d04c8fccfa26..17c31306456c 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -2,6 +2,12 @@ require "spec" require "yaml" require "../../support/finalize" +class YAMLAttrValue(T) + include YAML::Serializable + + property value : T +end + record YAMLAttrPoint, x : Int32, y : Int32 do include YAML::Serializable end @@ -94,12 +100,6 @@ class YAMLAttrPersonEmittingNullsByOptions property value2 : Int32? end -class YAMLAttrWithBool - include YAML::Serializable - - property value : Bool -end - class YAMLAttrWithTime include YAML::Serializable @@ -148,12 +148,6 @@ class YAMLAttrWithTimeArray3 property value : Array(Time) end -class YAMLAttrWithPropertiesKey - include YAML::Serializable - - property properties : Hash(String, String) -end - class YAMLAttrWithSimpleMapping include YAML::Serializable @@ -168,11 +162,6 @@ class YAMLAttrWithKeywordsMapping property abstract : Int32 end -class YAMLAttrWithAny - include YAML::Serializable - property obj : YAML::Any -end - class YAMLAttrWithProblematicKeys include YAML::Serializable @@ -239,18 +228,6 @@ class YAMLAttrWithTimeEpochMillis property value : Time end -class YAMLAttrWithNilableUnion - include YAML::Serializable - - property value : Int32? -end - -class YAMLAttrWithNilableUnion2 - include YAML::Serializable - - property value : Int32 | Nil -end - class YAMLAttrWithPresence include YAML::Serializable @@ -369,12 +346,6 @@ module YAMLNamespace end end -class YAMLWithShape - include YAML::Serializable - - property shape : YAMLShape -end - enum YAMLVariableDiscriminatorEnumFoo Foo = 4 end @@ -543,7 +514,7 @@ describe "YAML::Serializable" do end it "doesn't raises on false value when not-nil" do - yaml = YAMLAttrWithBool.from_yaml("---\nvalue: false\n") + yaml = YAMLAttrValue(Bool).from_yaml("---\nvalue: false\n") yaml.value.should be_false end @@ -647,11 +618,11 @@ describe "YAML::Serializable" do yaml.to_yaml.should match(/\A---\nvalue: ?\n\z/) end - it "outputs YAML with properties key" do + it "outputs YAML with Hash" do input = { - properties: {"foo" => "bar"}, + value: {"foo" => "bar"}, }.to_yaml - yaml = YAMLAttrWithPropertiesKey.from_yaml(input) + yaml = YAMLAttrValue(Hash(String, String)).from_yaml(input) yaml.to_yaml.should eq(input) end @@ -662,20 +633,20 @@ describe "YAML::Serializable" do end it "parses yaml with any" do - yaml = YAMLAttrWithAny.from_yaml("obj: hello") - yaml.obj.as_s.should eq("hello") + yaml = YAMLAttrValue(YAML::Any).from_yaml("value: hello") + yaml.value.as_s.should eq("hello") - yaml = YAMLAttrWithAny.from_yaml({:obj => ["foo", "bar"]}.to_yaml) - yaml.obj[1].as_s.should eq("bar") + yaml = YAMLAttrValue(YAML::Any).from_yaml({:value => ["foo", "bar"]}.to_yaml) + yaml.value[1].as_s.should eq("bar") - yaml = YAMLAttrWithAny.from_yaml({:obj => {:foo => :bar}}.to_yaml) - yaml.obj["foo"].as_s.should eq("bar") + yaml = YAMLAttrValue(YAML::Any).from_yaml({:value => {:foo => :bar}}.to_yaml) + yaml.value["foo"].as_s.should eq("bar") - yaml = YAMLAttrWithAny.from_yaml("extra: &foo hello\nobj: *foo") - yaml.obj.as_s.should eq("hello") + yaml = YAMLAttrValue(YAML::Any).from_yaml("extra: &foo hello\nvalue: *foo") + yaml.value.as_s.should eq("hello") - expect_raises YAML::ParseException, "Unknown anchor 'foo' at line 1, column 6" do - YAMLAttrWithAny.from_yaml("obj: *foo") + expect_raises YAML::ParseException, "Unknown anchor 'foo' at line 1, column 8" do + YAMLAttrValue(YAML::Any).from_yaml("value: *foo") end end @@ -877,29 +848,15 @@ describe "YAML::Serializable" do end it "parses nilable union" do - obj = YAMLAttrWithNilableUnion.from_yaml(%({"value": 1})) - obj.value.should eq(1) - obj.to_yaml.should eq("---\nvalue: 1\n") - - obj = YAMLAttrWithNilableUnion.from_yaml(%({"value": null})) - obj.value.should be_nil - obj.to_yaml.should eq("--- {}\n") - - obj = YAMLAttrWithNilableUnion.from_yaml(%({})) - obj.value.should be_nil - obj.to_yaml.should eq("--- {}\n") - end - - it "parses nilable union2" do - obj = YAMLAttrWithNilableUnion2.from_yaml(%({"value": 1})) + obj = YAMLAttrValue(Int32?).from_yaml(%({"value": 1})) obj.value.should eq(1) obj.to_yaml.should eq("---\nvalue: 1\n") - obj = YAMLAttrWithNilableUnion2.from_yaml(%({"value": null})) + obj = YAMLAttrValue(Int32?).from_yaml(%({"value": null})) obj.value.should be_nil obj.to_yaml.should eq("--- {}\n") - obj = YAMLAttrWithNilableUnion2.from_yaml(%({})) + obj = YAMLAttrValue(Int32?).from_yaml(%({})) obj.value.should be_nil obj.to_yaml.should eq("--- {}\n") end @@ -993,8 +950,8 @@ describe "YAML::Serializable" do end it "deserializes type which nests type with discriminator (#9849)" do - container = YAMLWithShape.from_yaml(%({"shape": {"type": "point", "x": 1, "y": 2}})) - point = container.shape.as(YAMLPoint) + container = YAMLAttrValue(YAMLShape).from_yaml(%({"value": {"type": "point", "x": 1, "y": 2}})) + point = container.value.as(YAMLPoint) point.x.should eq(1) point.y.should eq(2) end From 6b846f6fb45da02261f32f0be1d250765f4e6ccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 11 Feb 2023 13:05:55 +0100 Subject: [PATCH 0304/1551] Add instructions for other repos to pre-commit hook (#10535) --- scripts/git/pre-commit | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/git/pre-commit b/scripts/git/pre-commit index d892c883e177..2624b25a1fd7 100755 --- a/scripts/git/pre-commit +++ b/scripts/git/pre-commit @@ -6,8 +6,11 @@ # Only staged files (the ones to be committed) are being processed, but each file is checked # entirely as it is stored on disc, even parts that are not staged. # -# To use this script, it needs to be installed in the local git repository. For example by running -# `ln -s scripts/git/pre-commit .git/hooks` in the root folder. +# To use this script, install it in the local git repository. +# +# `curl -sL https://github.com/crystal-lang/crystal/raw/master/scripts/git/pre-commit > .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit`. +# +# Alternatively, in the Crystal repo you can directly link it: `ln -s scripts/git/pre-commit .git/hooks`. # # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if From 674fb08498ec140746a91d0716265c67c441c13e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 11 Feb 2023 13:06:02 +0100 Subject: [PATCH 0305/1551] Improve locations of some AST nodes (#12933) --- src/compiler/crystal/semantic/call.cr | 4 ++-- .../crystal/semantic/literal_expander.cr | 4 ++-- src/compiler/crystal/semantic/main_visitor.cr | 20 +++++++++---------- .../crystal/semantic/semantic_visitor.cr | 2 +- .../crystal/semantic/top_level_visitor.cr | 2 +- src/compiler/crystal/syntax/parser.cr | 8 ++++---- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index dec1f0bc7dc0..06f576a36106 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -1171,7 +1171,7 @@ class Crystal::Call arg_types.each_index do |index| arg = typed_def.args[index] type = arg_types[index] - var = MetaVar.new(arg.name, type).at(arg.location) + var = MetaVar.new(arg.name, type).at(arg) var.bind_to(var) args[arg.name] = var @@ -1208,7 +1208,7 @@ class Crystal::Call else default_value.raise "BUG: unknown magic constant: #{default_value.name}" end - var = MetaVar.new(arg.name, type).at(arg.location) + var = MetaVar.new(arg.name, type).at(arg) var.bind_to(var) args[arg.name] = var arg.type = type diff --git a/src/compiler/crystal/semantic/literal_expander.cr b/src/compiler/crystal/semantic/literal_expander.cr index ead7901501c8..e16b31fe6571 100644 --- a/src/compiler/crystal/semantic/literal_expander.cr +++ b/src/compiler/crystal/semantic/literal_expander.cr @@ -140,7 +140,7 @@ module Crystal end end - TypeOf.new(type_exps).at(node.location) + TypeOf.new(type_exps).at(node) end # Converts an array-like literal to creating a container and storing the values: @@ -176,7 +176,7 @@ module Crystal elem_temp_vars, elem_temp_var_count = complex_elem_temp_vars(node.elements) if generic_type type_of = typeof_exp(node, elem_temp_vars) - node_name = Generic.new(generic_type, type_of).at(node.location) + node_name = Generic.new(generic_type, type_of).at(node) else node_name = node.name end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 2e3b67e8b313..95a988fcf249 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -966,8 +966,8 @@ module Crystal # This is the case of a yield when there's a captured block if block.fun_literal block_arg_name = typed_def.block_arg.not_nil!.name - block_var = Var.new(block_arg_name).at(node.location) - call = Call.new(block_var, "call", node.exps).at(node.location) + block_var = Var.new(block_arg_name).at(node) + call = Call.new(block_var, "call", node.exps).at(node) call.accept self node.bind_to call node.expanded = call @@ -1396,14 +1396,14 @@ module Crystal next end - temp_var = @program.new_temp_var.at(arg.location) - assign = Assign.new(temp_var, exp).at(arg.location) + temp_var = @program.new_temp_var.at(arg) + assign = Assign.new(temp_var, exp).at(arg) exps << assign case arg when Splat - arg.exp = temp_var.clone.at(arg.location) + arg.exp = temp_var.clone.at(arg) when DoubleSplat - arg.exp = temp_var.clone.at(arg.location) + arg.exp = temp_var.clone.at(arg) else next end @@ -1557,7 +1557,7 @@ module Crystal temp_name = @program.new_temp_var_name - new_call = Call.new(node.obj, "new").at(node.location) + new_call = Call.new(node.obj, "new").at(node) new_assign = Assign.new(Var.new(temp_name).at(node), new_call).at(node) exps << new_assign @@ -1738,7 +1738,7 @@ module Crystal if const.is_a?(Path) && const.target_const obj = node.obj.clone.at(node.obj) const = node.const.clone.at(node.const) - comp = Call.new(const, "===", obj).at(node.location) + comp = Call.new(const, "===", obj).at(node) comp.accept self node.syntax_replacement = comp node.bind_to comp @@ -2936,7 +2936,7 @@ module Crystal if name = node.name name.accept self type = name.type.instance_type - generic_type = TypeNode.new(type).at(node.location) if type.is_a?(GenericClassType) + generic_type = TypeNode.new(type).at(node) if type.is_a?(GenericClassType) expand_named(node, generic_type) else expand(node) @@ -2947,7 +2947,7 @@ module Crystal if name = node.name name.accept self type = name.type.instance_type - generic_type = TypeNode.new(type).at(node.location) if type.is_a?(GenericClassType) + generic_type = TypeNode.new(type).at(node) if type.is_a?(GenericClassType) expand_named(node, generic_type) else expand(node) diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index 62dd21a6092d..07f2bf5f1b16 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -426,7 +426,7 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor return expanded end - the_macro = Macro.new("macro_#{node.object_id}", [] of Arg, node).at(node.location) + the_macro = Macro.new("macro_#{node.object_id}", [] of Arg, node).at(node) skip_macro_exception = nil diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index e650da690019..da6d57ed16ab 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -452,7 +452,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor if !@method_added_running && has_hooks?(target_type.metaclass) @method_added_running = true - run_hooks target_type.metaclass, target_type, :method_added, node, Call.new(nil, "method_added", node).at(node.location) + run_hooks target_type.metaclass, target_type, :method_added, node, Call.new(nil, "method_added", node).at(node) @method_added_running = false end end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 3a7146cf11c7..cb9a9124e20b 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -2239,7 +2239,7 @@ module Crystal node.expressions.concat(pieces) else string = combine_pieces(pieces, delimiter_state) - node.expressions.push(StringLiteral.new(string).at(node.location).at_end(token_end_location)) + node.expressions.push(StringLiteral.new(string).at(node).at_end(token_end_location)) end node.heredoc_indent = delimiter_state.heredoc_indent @@ -3497,11 +3497,11 @@ module Crystal when .op_star? next_token_skip_space exp = parse_expression - exp = Splat.new(exp).at(exp.location) + exp = Splat.new(exp).at(exp) when .op_star_star? next_token_skip_space exp = parse_expression - exp = DoubleSplat.new(exp).at(exp.location) + exp = DoubleSplat.new(exp).at(exp) else exp = parse_expression end @@ -5754,7 +5754,7 @@ module Crystal raise "top-level fun parameter must have a name", @token end param_type = parse_union_type - params << Arg.new("", nil, param_type).at(param_type.location) + params << Arg.new("", nil, param_type).at(param_type) end if @token.type.op_comma? From 55dc47580451df161d7cbb7ff26ab635d04acfc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 11 Feb 2023 13:06:35 +0100 Subject: [PATCH 0306/1551] Simplify expectation of loader spec error messages (#12858) --- spec/compiler/loader/unix_spec.cr | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/compiler/loader/unix_spec.cr b/spec/compiler/loader/unix_spec.cr index 8acf48559eea..783175722c13 100644 --- a/spec/compiler/loader/unix_spec.cr +++ b/spec/compiler/loader/unix_spec.cr @@ -28,12 +28,14 @@ describe Crystal::Loader do end it "parses file paths" do - expect_raises(Crystal::Loader::LoadError, /#{Dir.current}\/foobar\.o.+(No such file or directory|image not found|no such file)|Cannot open "#{Dir.current}\/foobar\.o"/) do + exc = expect_raises(Crystal::Loader::LoadError, /no such file|image not found|cannot open/i) do Crystal::Loader.parse(["foobar.o"], search_paths: [] of String) end - expect_raises(Crystal::Loader::LoadError, /(#{Dir.current}\/foo\/bar\.o).+(No such file or directory|image not found|no such file)|Cannot open "#{Dir.current}\/foo\/bar\.o"/) do + exc.message.should contain File.join(Dir.current, "foobar.o") + exc = expect_raises(Crystal::Loader::LoadError, /no such file|image not found|cannot open/i) do Crystal::Loader.parse(["-l", "foo/bar.o"], search_paths: [] of String) end + exc.message.should contain File.join(Dir.current, "foo", "bar.o") end end From 6e833f4dbad996e42bb883cd0dc997f9ce57faff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 13 Feb 2023 12:04:45 +0100 Subject: [PATCH 0307/1551] Makefile: Add `./scripts` to `format` recipe (#13064) * Makefile: Add `./scripts` to format recipe * `make format` * fixup! Makefile: Add `./scripts` to format recipe --- Makefile | 2 +- Makefile.win | 2 +- scripts/generate_unicode_data.cr | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 7db99f0be775..0f74e8a36205 100644 --- a/Makefile +++ b/Makefile @@ -128,7 +128,7 @@ llvm_ext: $(LLVM_EXT_OBJ) .PHONY: format format: ## Format sources - ./bin/crystal tool format$(if $(check), --check) src spec samples + ./bin/crystal tool format$(if $(check), --check) src spec samples scripts .PHONY: install install: $(O)/crystal man/crystal.1.gz ## Install the compiler at DESTDIR diff --git a/Makefile.win b/Makefile.win index 7c10931ef1d3..7d4bcead4176 100644 --- a/Makefile.win +++ b/Makefile.win @@ -130,7 +130,7 @@ llvm_ext: $(LLVM_EXT_OBJ) .PHONY: format format: ## Format sources - .\bin\crystal tool format$(if $(check), --check) src spec samples + .\bin\crystal tool format$(if $(check), --check) src spec samples scripts .PHONY: install install: $(O)\crystal.exe ## Install the compiler at prefix diff --git a/scripts/generate_unicode_data.cr b/scripts/generate_unicode_data.cr index 5adb4bcd036c..3f37428c954e 100644 --- a/scripts/generate_unicode_data.cr +++ b/scripts/generate_unicode_data.cr @@ -100,7 +100,7 @@ def new_alternate_range(first_codepoint, last_codepoint) AlternateRange.new(first_codepoint, last_codepoint.not_nil! + 1) end -def strides(entries, targets) +def strides(entries, targets, &) strides = [] of Stride entries = entries.select { |entry| targets.includes?(yield entry) } From 9c2cd844b9b49fa1e393e0ceeb9d175c44122885 Mon Sep 17 00:00:00 2001 From: Tamnac <49466795+Tamnac@users.noreply.github.com> Date: Mon, 13 Feb 2023 03:04:57 -0800 Subject: [PATCH 0308/1551] Added note about imports where necessary (#13026) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added note about imports where necessary * Removed double and trailing lines * yaml typo and three formatting changes --------- Co-authored-by: Tamnac Co-authored-by: Johannes Müller --- src/benchmark.cr | 2 ++ src/big/big_decimal.cr | 2 ++ src/big/big_float.cr | 2 ++ src/big/big_int.cr | 2 ++ src/big/big_rational.cr | 2 ++ src/bit_array.cr | 2 ++ src/colorize.cr | 2 ++ src/complex.cr | 2 ++ src/compress/deflate/deflate.cr | 2 ++ src/compress/gzip/gzip.cr | 2 ++ src/compress/zip/zip.cr | 2 ++ src/compress/zlib/zlib.cr | 2 ++ src/crypto/bcrypt.cr | 2 ++ src/crypto/bcrypt/password.cr | 2 ++ src/csv.cr | 2 ++ src/digest/adler32.cr | 2 ++ src/digest/crc32.cr | 2 ++ src/digest/md5.cr | 2 ++ src/digest/sha1.cr | 2 ++ src/digest/sha256.cr | 2 ++ src/digest/sha512.cr | 2 ++ src/ecr.cr | 2 ++ src/file_utils.cr | 1 + src/html.cr | 2 ++ src/http.cr | 2 ++ src/http/client.cr | 2 ++ src/http/cookie.cr | 4 ++++ src/http/formdata.cr | 2 ++ src/http/headers.cr | 2 ++ src/http/request.cr | 2 ++ src/http/server.cr | 2 ++ src/http/server/handler.cr | 2 ++ src/http/server/handlers/compress_handler.cr | 2 ++ src/http/server/handlers/error_handler.cr | 2 ++ src/http/server/handlers/log_handler.cr | 2 ++ src/http/server/handlers/static_file_handler.cr | 2 ++ src/http/server/handlers/websocket_handler.cr | 2 ++ src/http/status.cr | 2 ++ src/http/web_socket.cr | 2 ++ src/ini.cr | 1 + src/json.cr | 2 ++ src/levenshtein.cr | 2 ++ src/log.cr | 2 ++ src/mime.cr | 2 ++ src/oauth/oauth.cr | 2 ++ src/oauth2/oauth2.cr | 2 ++ src/openssl.cr | 2 ++ src/option_parser.cr | 2 ++ src/socket/ip_socket.cr | 1 + src/socket/tcp_server.cr | 2 ++ src/socket/tcp_socket.cr | 2 ++ src/socket/udp_socket.cr | 2 ++ src/socket/unix_server.cr | 2 ++ src/socket/unix_socket.cr | 2 ++ src/string_pool.cr | 2 ++ src/string_scanner.cr | 2 ++ src/system/group.cr | 2 ++ src/system/user.cr | 2 ++ src/uri.cr | 2 ++ src/uuid.cr | 2 ++ src/weak_ref.cr | 1 + src/xml.cr | 2 ++ src/yaml.cr | 2 ++ 63 files changed, 124 insertions(+) diff --git a/src/benchmark.cr b/src/benchmark.cr index 59cad44ba13e..a581af2ad23b 100644 --- a/src/benchmark.cr +++ b/src/benchmark.cr @@ -3,6 +3,8 @@ require "./benchmark/**" # The Benchmark module provides methods for benchmarking Crystal code, giving # detailed reports on the time and memory taken for each task. # +# NOTE: To use `Benchmark`, you must explicitly import it with `require "benchmark"` +# # ### Measure the number of iterations per second of each task # # ``` diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index b5d9e93f2d29..470c61e3e85f 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -12,6 +12,8 @@ end # Value contains the actual value, and scale tells the decimal point place. # E.g. when value is `1234` and scale `2`, the result is `12.34`. # +# NOTE: To use `BigDecimal`, you must explicitly import it with `require "big"` +# # The general idea and some of the arithmetic algorithms were adapted from # the MIT/APACHE-licensed [bigdecimal-rs](https://github.com/akubera/bigdecimal-rs). struct BigDecimal < Number diff --git a/src/big/big_float.cr b/src/big/big_float.cr index 7c90a6985207..cc455c7c56cd 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -4,6 +4,8 @@ require "big" # A `BigFloat` can represent arbitrarily large floats. # # It is implemented under the hood with [GMP](https://gmplib.org/). +# +# NOTE: To use `BigFloat`, you must explicitly import it with `require "big"` struct BigFloat < Float include Comparable(Int) include Comparable(BigFloat) diff --git a/src/big/big_int.cr b/src/big/big_int.cr index aaae48999938..e1ca94dbd10b 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -5,6 +5,8 @@ require "random" # A `BigInt` can represent arbitrarily large integers. # # It is implemented under the hood with [GMP](https://gmplib.org/). +# +# NOTE: To use `BigInt`, you must explicitly import it with `require "big"` struct BigInt < Int include Comparable(Int::Signed) include Comparable(Int::Unsigned) diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index c87273788fef..acb34838ca3f 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -5,6 +5,8 @@ require "big" # denominator and the numerator have no common factors, and that the # denominator is positive. Zero has the unique representation 0/1. # +# NOTE: To use `BigRational`, you must explicitly import it with `require "big"` +# # ``` # require "big" # diff --git a/src/bit_array.cr b/src/bit_array.cr index 080c8475765b..4f4eb5726055 100644 --- a/src/bit_array.cr +++ b/src/bit_array.cr @@ -4,6 +4,8 @@ # `UInt32`s. The total number of bits stored is set at creation and is # immutable. # +# NOTE: To use `BitArray`, you must explicitly import it with `require "bit_array"` +# # ### Example # # ``` diff --git a/src/colorize.cr b/src/colorize.cr index 8cdd6648e8ff..4fee40faf616 100644 --- a/src/colorize.cr +++ b/src/colorize.cr @@ -3,6 +3,8 @@ # as its main interface, which calls `to_s` and surrounds it with the necessary escape codes # when it comes to obtaining a string representation of the object. # +# NOTE: To use `Colorize`, you must explicitly import it with `require "colorize"` +# # Its first argument changes the foreground color: # # ``` diff --git a/src/complex.cr b/src/complex.cr index 6fd1d2a2faf2..d4e648e9c8fe 100644 --- a/src/complex.cr +++ b/src/complex.cr @@ -3,6 +3,8 @@ # The a is the real part of the number, and the b is the imaginary part of # the number. # +# NOTE: To use `Complex`, you must explicitly import it with `require "complex"` +# # ``` # require "complex" # diff --git a/src/compress/deflate/deflate.cr b/src/compress/deflate/deflate.cr index 4c6ee0cdc145..58479f503fbf 100644 --- a/src/compress/deflate/deflate.cr +++ b/src/compress/deflate/deflate.cr @@ -6,6 +6,8 @@ require "./*" # # See `Gzip`, `Zip` and `Zlib` for modules that provide access # to DEFLATE-based file formats. +# +# NOTE: To use `Deflate` or its children, you must explicitly import it with `require "compress/deflate"` module Compress::Deflate NO_COMPRESSION = 0 BEST_SPEED = 1 diff --git a/src/compress/gzip/gzip.cr b/src/compress/gzip/gzip.cr index 616c25422a05..2d8ffcd81338 100644 --- a/src/compress/gzip/gzip.cr +++ b/src/compress/gzip/gzip.cr @@ -3,6 +3,8 @@ require "digest/crc32" # The Gzip module contains readers and writers of gzip format compressed # data, as specified in [RFC 1952](https://www.ietf.org/rfc/rfc1952.txt). +# +# NOTE: To use `Gzip` or its children, you must explicitly import it with `require "compress/gzip"` module Compress::Gzip NO_COMPRESSION = Compress::Deflate::NO_COMPRESSION BEST_SPEED = Compress::Deflate::BEST_SPEED diff --git a/src/compress/zip/zip.cr b/src/compress/zip/zip.cr index 61da653dabec..ec03826fcba8 100644 --- a/src/compress/zip/zip.cr +++ b/src/compress/zip/zip.cr @@ -4,6 +4,8 @@ require "digest/crc32" # The Compress::Zip module contains readers and writers of the zip # file format, described at [PKWARE's site](https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.3.TXT). # +# NOTE: To use `Zip` or its children, you must explicitly import it with `require "compress/zip"` +# # ### Reading zip files # # Two types are provided to read from zip files: diff --git a/src/compress/zlib/zlib.cr b/src/compress/zlib/zlib.cr index b996c825e742..0326f24bf815 100644 --- a/src/compress/zlib/zlib.cr +++ b/src/compress/zlib/zlib.cr @@ -3,6 +3,8 @@ require "digest/adler32" # The Compress::Zlib module contains readers and writers of zlib format compressed # data, as specified in [RFC 1950](https://www.ietf.org/rfc/rfc1950.txt). +# +# NOTE: To use `Zlib` or its children, you must explicitly import it with `require "compress/zlib"` module Compress::Zlib NO_COMPRESSION = Compress::Deflate::NO_COMPRESSION BEST_SPEED = Compress::Deflate::BEST_SPEED diff --git a/src/crypto/bcrypt.cr b/src/crypto/bcrypt.cr index 28c36bef647c..92d90495c1df 100644 --- a/src/crypto/bcrypt.cr +++ b/src/crypto/bcrypt.cr @@ -29,6 +29,8 @@ require "./subtle" # Last but not least: beware of denial of services! Always protect your # application using an external strategy (eg: rate limiting), otherwise # endpoints that verifies bcrypt hashes will be an easy target. +# +# NOTE: To use `Bcrypt`, you must explicitly import it with `require "crypto/bcrypt"` class Crypto::Bcrypt class Error < Exception end diff --git a/src/crypto/bcrypt/password.cr b/src/crypto/bcrypt/password.cr index 234ac5c1918a..b98658c9e22f 100644 --- a/src/crypto/bcrypt/password.cr +++ b/src/crypto/bcrypt/password.cr @@ -3,6 +3,8 @@ require "../subtle" # Generate, read and verify `Crypto::Bcrypt` hashes. # +# NOTE: To use `Password`, you must explicitly import it with `require "crypto/bcrypt/password"` +# # ``` # require "crypto/bcrypt/password" # diff --git a/src/csv.cr b/src/csv.cr index 92fdd4f4dd87..d01da95ff138 100644 --- a/src/csv.cr +++ b/src/csv.cr @@ -3,6 +3,8 @@ # # This module conforms to [RFC 4180](https://tools.ietf.org/html/rfc4180). # +# NOTE: To use `CSV` or its children, you must explicitly import it with `require "csv"` +# # ### Parsing # # Several ways of parsing CSV are provided. The most straight-forward, but diff --git a/src/digest/adler32.cr b/src/digest/adler32.cr index 110eb9a8684e..0b31e52a80b1 100644 --- a/src/digest/adler32.cr +++ b/src/digest/adler32.cr @@ -2,6 +2,8 @@ require "lib_z" require "./digest" # Implements the Adler32 checksum algorithm. +# +# NOTE: To use `Adler32`, you must explicitly import it with `require "digest/adler32"` class Digest::Adler32 < ::Digest extend ClassMethods diff --git a/src/digest/crc32.cr b/src/digest/crc32.cr index 07432f455a69..b32ecd68e41f 100644 --- a/src/digest/crc32.cr +++ b/src/digest/crc32.cr @@ -2,6 +2,8 @@ require "lib_z" require "./digest" # Implements the CRC32 checksum algorithm. +# +# NOTE: To use `CRC32`, you must explicitly import it with `require "digest/crc32"` class Digest::CRC32 < ::Digest extend ClassMethods diff --git a/src/digest/md5.cr b/src/digest/md5.cr index 35a4247d0baa..15f74b3e0b95 100644 --- a/src/digest/md5.cr +++ b/src/digest/md5.cr @@ -3,6 +3,8 @@ require "openssl" # Implements the MD5 digest algorithm. # +# NOTE: To use `MD5`, you must explicitly import it with `require "digest/md5"` +# # WARNING: MD5 is no longer a cryptographically secure hash, and should not be # used in security-related components, like password hashing. For passwords, see # `Crypto::Bcrypt::Password`. For a generic cryptographic hash, use SHA-256 via diff --git a/src/digest/sha1.cr b/src/digest/sha1.cr index b777cb55f7fb..01d96ce00bc0 100644 --- a/src/digest/sha1.cr +++ b/src/digest/sha1.cr @@ -3,6 +3,8 @@ require "openssl" # Implements the SHA1 digest algorithm. # +# NOTE: To use `SHA1`, you must explicitly import it with `require "digest/sha1"` +# # WARNING: SHA1 is no longer a cryptographically secure hash, and should not be # used in security-related components, like password hashing. For passwords, see # `Crypto::Bcrypt::Password`. For a generic cryptographic hash, use SHA-256 via diff --git a/src/digest/sha256.cr b/src/digest/sha256.cr index 93606091aca5..4c4cc882b667 100644 --- a/src/digest/sha256.cr +++ b/src/digest/sha256.cr @@ -2,6 +2,8 @@ require "./digest" require "openssl" # Implements the SHA256 digest algorithm. +# +# NOTE: To use `SHA256`, you must explicitly import it with `require "digest/sha256"` class Digest::SHA256 < ::OpenSSL::Digest extend ClassMethods diff --git a/src/digest/sha512.cr b/src/digest/sha512.cr index fe517e7cc2b1..f6364e4db5aa 100644 --- a/src/digest/sha512.cr +++ b/src/digest/sha512.cr @@ -2,6 +2,8 @@ require "./digest" require "openssl" # Implements the SHA512 digest algorithm. +# +# NOTE: To use `SHA256`, you must explicitly import it with `require "digest/sha512"` class Digest::SHA512 < ::OpenSSL::Digest extend ClassMethods diff --git a/src/ecr.cr b/src/ecr.cr index 86ccd035911b..42bee548b22c 100644 --- a/src/ecr.cr +++ b/src/ecr.cr @@ -16,6 +16,8 @@ # tag: `<%# hello %>`. An ECR tag can be inserted directly (i.e. the tag itself may be # escaped) by using a second `%` like so: `<%% a = b %>` or `<%%= foo %>`. # +# NOTE: To use `ECR`, you must explicitly import it with `require "ecr"` +# # Quick Example: # # Create a simple ECR file named `greeter.ecr`: diff --git a/src/file_utils.cr b/src/file_utils.cr index 21c3041cfc74..ad189ecd0b25 100644 --- a/src/file_utils.cr +++ b/src/file_utils.cr @@ -1,3 +1,4 @@ +# NOTE: To use `FileUtils`, you must explicitly import it with `require "file_utils"` module FileUtils extend self diff --git a/src/html.cr b/src/html.cr index 703d3fb47579..49457b1f1e29 100644 --- a/src/html.cr +++ b/src/html.cr @@ -3,6 +3,8 @@ require "./html/entities" # Provides HTML escaping and unescaping methods. # # For HTML *parsing* see module XML, especially `XML.parse_html`. +# +# NOTE: To use `HTML`, you must explicitly import it with `require "html"` module HTML private SUBSTITUTIONS = { '&' => "&", diff --git a/src/http.cr b/src/http.cr index 4be21ed52404..29dd9faf94b9 100644 --- a/src/http.cr +++ b/src/http.cr @@ -5,5 +5,7 @@ require "./http/log" require "./http/common" # The HTTP module contains `HTTP::Client`, `HTTP::Server` and `HTTP::WebSocket` implementations. +# +# NOTE: To use `HTTP`, you must explicitly import it with `require "http"` module HTTP end diff --git a/src/http/client.cr b/src/http/client.cr index c9b9e1d699da..41bfa70e68ed 100644 --- a/src/http/client.cr +++ b/src/http/client.cr @@ -4,6 +4,8 @@ # An HTTP Client. # +# NOTE: To use `Client`, you must explicitly import it with `require "http/client"` +# # ### One-shot usage # # Without a block, an `HTTP::Client::Response` is returned and the response's body diff --git a/src/http/cookie.cr b/src/http/cookie.cr index 5b672ca2238a..83b41297707e 100644 --- a/src/http/cookie.cr +++ b/src/http/cookie.cr @@ -2,6 +2,8 @@ require "./common" module HTTP # Represents a cookie with all its attributes. Provides convenient access and modification of them. + # + # NOTE: To use `Cookie`, you must explicitly import it with `require "http/cookie"` class Cookie # Possible values for the `SameSite` cookie as described in the [Same-site Cookies Draft](https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1.1). enum SameSite @@ -273,6 +275,8 @@ module HTTP # Represents a collection of cookies as it can be present inside # a HTTP request or response. + # + # NOTE: To use `Cookies`, you must explicitly import it with `require "http/cookie"` class Cookies include Enumerable(Cookie) diff --git a/src/http/formdata.cr b/src/http/formdata.cr index 433ee494b3de..267442687864 100644 --- a/src/http/formdata.cr +++ b/src/http/formdata.cr @@ -4,6 +4,8 @@ require "mime/multipart" # Contains utilities for parsing `multipart/form-data` messages, which are # commonly used for encoding HTML form data. # +# NOTE: To use `FormData`, you must explicitly import it with `require "http"` +# # ### Examples # # Commonly, you'll want to parse a from response from a HTTP request, and diff --git a/src/http/headers.cr b/src/http/headers.cr index f3ad07d402c5..61e04881c1a9 100644 --- a/src/http/headers.cr +++ b/src/http/headers.cr @@ -2,6 +2,8 @@ # # Two headers are considered the same if their downcase representation is the same # (in which `_` is the downcase version of `-`). +# +# NOTE: To use `Headers`, you must explicitly import it with `require "http/headers"` struct HTTP::Headers include Enumerable({String, Array(String)}) diff --git a/src/http/request.cr b/src/http/request.cr index 9a01daeab437..c6a72d3469c0 100644 --- a/src/http/request.cr +++ b/src/http/request.cr @@ -12,6 +12,8 @@ require "socket" # When creating a request with a `String` or `Bytes` its body # will be a `IO::Memory` wrapping these, and the `Content-Length` # header will be set appropriately. +# +# NOTE: To use `Request`, you must explicitly import it with `require "http/request"` class HTTP::Request property method : String property headers : Headers diff --git a/src/http/server.cr b/src/http/server.cr index 1a6e7d8d8733..6e44f4150582 100644 --- a/src/http/server.cr +++ b/src/http/server.cr @@ -15,6 +15,8 @@ require "log" # A server is initialized with a handler chain responsible for processing each # incoming request. # +# NOTE: To use `Server`, you must explicitly import it with `require "http/server"` +# # ``` # require "http/server" # diff --git a/src/http/server/handler.cr b/src/http/server/handler.cr index 3b1f97113b80..792879fc949d 100644 --- a/src/http/server/handler.cr +++ b/src/http/server/handler.cr @@ -4,6 +4,8 @@ require "./context" # You can use a handler to intercept any incoming request and can modify the response. # These can be used for request throttling, ip-based filtering, adding custom headers e.g. # +# NOTE: To use `Handler`, you must explicitly import it with `require "http/server/handler"` +# # ### A custom handler # # ``` diff --git a/src/http/server/handlers/compress_handler.cr b/src/http/server/handlers/compress_handler.cr index 8dd5e66c4404..e059b697649c 100644 --- a/src/http/server/handlers/compress_handler.cr +++ b/src/http/server/handlers/compress_handler.cr @@ -5,6 +5,8 @@ # A handler that configures an `HTTP::Server::Response` to compress the response # output, either using gzip or deflate, depending on the `Accept-Encoding` request header. +# +# NOTE: To use `CompressHandler`, you must explicitly import it with `require "http"` class HTTP::CompressHandler include HTTP::Handler diff --git a/src/http/server/handlers/error_handler.cr b/src/http/server/handlers/error_handler.cr index 6036b0d9536e..f92b1a8d0c7a 100644 --- a/src/http/server/handlers/error_handler.cr +++ b/src/http/server/handlers/error_handler.cr @@ -6,6 +6,8 @@ # # This handler also logs the exceptions to the specified logger or # the logger for the source "http.server" by default. +# +# NOTE: To use `ErrorHandler`, you must explicitly import it with `require "http"` class HTTP::ErrorHandler include HTTP::Handler diff --git a/src/http/server/handlers/log_handler.cr b/src/http/server/handlers/log_handler.cr index eb29fd7d1595..6aa82f6d911a 100644 --- a/src/http/server/handlers/log_handler.cr +++ b/src/http/server/handlers/log_handler.cr @@ -2,6 +2,8 @@ require "log" # A handler that logs the request method, resource, status code, and # the time used to execute the next handler +# +# NOTE: To use `LogHandler`, you must explicitly import it with `require "http"` class HTTP::LogHandler include HTTP::Handler diff --git a/src/http/server/handlers/static_file_handler.cr b/src/http/server/handlers/static_file_handler.cr index d38b462e7a3e..3fbb57ab0bc0 100644 --- a/src/http/server/handlers/static_file_handler.cr +++ b/src/http/server/handlers/static_file_handler.cr @@ -8,6 +8,8 @@ require "mime" # This handler can send precompressed content, if the client accepts it, and a file # with the same name and `.gz` extension appended is found in the same directory. # Precompressed files are only served if they are newer than the original file. +# +# NOTE: To use `StaticFileHandler`, you must explicitly import it with `require "http"` class HTTP::StaticFileHandler include HTTP::Handler diff --git a/src/http/server/handlers/websocket_handler.cr b/src/http/server/handlers/websocket_handler.cr index c287e91dbb3b..c201f794e909 100644 --- a/src/http/server/handlers/websocket_handler.cr +++ b/src/http/server/handlers/websocket_handler.cr @@ -3,6 +3,8 @@ require "http/web_socket" # A handler which adds websocket functionality to an `HTTP::Server`. # +# NOTE: To use `WebSocketHandler`, you must explicitly import it with `require "http"` +# # When a request can be upgraded, the associated `HTTP::Websocket` and # `HTTP::Server::Context` will be yielded to the block. For example: # diff --git a/src/http/status.cr b/src/http/status.cr index f3920b808e82..a02299f9fed7 100644 --- a/src/http/status.cr +++ b/src/http/status.cr @@ -4,6 +4,8 @@ # # It provides constants for the defined HTTP status codes as well as helper # methods to easily identify the type of response. +# +# NOTE: To use `Status`, you must explicitly import it with `require "http/status"` enum HTTP::Status CONTINUE = 100 SWITCHING_PROTOCOLS = 101 diff --git a/src/http/web_socket.cr b/src/http/web_socket.cr index 9359c6c194bf..823e6b4e2eab 100644 --- a/src/http/web_socket.cr +++ b/src/http/web_socket.cr @@ -1,6 +1,8 @@ require "./client" require "./headers" + +# NOTE: To use `WebSocket`, you must explicitly import it with `require "http/web_socket"` class HTTP::WebSocket getter? closed = false diff --git a/src/ini.cr b/src/ini.cr index 39a82dc44894..79d7cd1bcdeb 100644 --- a/src/ini.cr +++ b/src/ini.cr @@ -1,3 +1,4 @@ +# NOTE: To use `INI`, you must explicitly import it with `require "ini"` module INI # Exception thrown on an INI parse error. class ParseException < Exception diff --git a/src/json.cr b/src/json.cr index a9eae0d8cf99..f55aca854c00 100644 --- a/src/json.cr +++ b/src/json.cr @@ -1,5 +1,7 @@ # The JSON module allows parsing and generating [JSON](http://json.org/) documents. # +# NOTE: To use `JSON` or its children, you must explicitly import it with `require "json"` +# # ### General type-safe interface # # The general type-safe interface for parsing JSON is to invoke `T.from_json` on a diff --git a/src/levenshtein.cr b/src/levenshtein.cr index 571e0ab830e6..e890d59c90ef 100644 --- a/src/levenshtein.cr +++ b/src/levenshtein.cr @@ -1,4 +1,6 @@ # Levenshtein distance methods. +# +# NOTE: To use `Levenshtein`, you must explicitly import it with `require "levenshtein"` module Levenshtein # Computes the [levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance) of two strings. # diff --git a/src/log.cr b/src/log.cr index 428c8e19fd11..9620a2c56992 100644 --- a/src/log.cr +++ b/src/log.cr @@ -7,6 +7,8 @@ # `#error`, and `#fatal` methods. They expect a block that will evaluate to the # message of the entry: # +# NOTE: To use `Log`, you must explicitly import it with `require "log"` +# # ``` # require "log" # diff --git a/src/mime.cr b/src/mime.cr index 4e794a803271..ac653e5c9f89 100644 --- a/src/mime.cr +++ b/src/mime.cr @@ -2,6 +2,8 @@ require "crystal/system/mime" # This module implements a global MIME registry. # +# NOTE: To use `MIME`, you must explicitly import it with `require "mime"` +# # ``` # require "mime" # diff --git a/src/oauth/oauth.cr b/src/oauth/oauth.cr index 5379ceb1f212..292055cfc777 100644 --- a/src/oauth/oauth.cr +++ b/src/oauth/oauth.cr @@ -1,6 +1,8 @@ # The OAuth module provides an `OAuth::Consumer` as specified by # [RFC 5849](https://tools.ietf.org/html/rfc5849). # +# NOTE: To use `OAuth`, you must explicitly import it with `require "oauth"` +# # ### Performing HTTP client requests with OAuth authentication # # Assuming you have an access token, its secret, the consumer key and the consumer secret, diff --git a/src/oauth2/oauth2.cr b/src/oauth2/oauth2.cr index 014e300a8f4e..3a9cde157f39 100644 --- a/src/oauth2/oauth2.cr +++ b/src/oauth2/oauth2.cr @@ -1,6 +1,8 @@ # The OAuth module provides an `OAuth2::Client` as specified # by [RFC 6749](https://tools.ietf.org/html/rfc6749). # +# NOTE: To use `OAuth2`, you must explicitly import it with `require "oauth2"` +# # ### Performing HTTP client requests with OAuth2 authentication # # Assuming you have an access token, you can setup an `HTTP::Client` diff --git a/src/openssl.cr b/src/openssl.cr index 71086e577df6..802c9a05e7d3 100644 --- a/src/openssl.cr +++ b/src/openssl.cr @@ -1,6 +1,8 @@ require "./openssl/lib_ssl" require "./openssl/error" +# NOTE: To use `OpenSSL`, you must explicitly import it with `require "openssl"` +# # ## OpenSSL Integration # # - TLS sockets need a context, potentially with keys (required for servers) and configuration. diff --git a/src/option_parser.cr b/src/option_parser.cr index 175926a8c730..5f61e73cd58d 100644 --- a/src/option_parser.cr +++ b/src/option_parser.cr @@ -7,6 +7,8 @@ # # Run `crystal` for an example of a CLI built with `OptionParser`. # +# NOTE: To use `OptionParser`, you must explicitly import it with `require "option_parser"` +# # Short example: # # ``` diff --git a/src/socket/ip_socket.cr b/src/socket/ip_socket.cr index 2f98063645b1..918b4d2b8591 100644 --- a/src/socket/ip_socket.cr +++ b/src/socket/ip_socket.cr @@ -1,3 +1,4 @@ +# NOTE: To use `IPSocket`, you must explicitly import it with `require "socket/ip_socket"` class IPSocket < Socket # Returns the `IPAddress` for the local end of the IP socket. getter local_address : Socket::IPAddress { system_local_address } diff --git a/src/socket/tcp_server.cr b/src/socket/tcp_server.cr index 51d828d45426..c5cf3e1fcef0 100644 --- a/src/socket/tcp_server.cr +++ b/src/socket/tcp_server.cr @@ -2,6 +2,8 @@ require "./tcp_socket" # A Transmission Control Protocol (TCP/IP) server. # +# NOTE: To use `TCPServer`, you must explicitly import it with `require "socket"` +# # Usage example: # ``` # require "socket" diff --git a/src/socket/tcp_socket.cr b/src/socket/tcp_socket.cr index 980f0b338cff..06e3d6f9b138 100644 --- a/src/socket/tcp_socket.cr +++ b/src/socket/tcp_socket.cr @@ -2,6 +2,8 @@ require "./ip_socket" # A Transmission Control Protocol (TCP/IP) socket. # +# NOTE: To use `TCPSocket`, you must explicitly import it with `require "socket"` +# # Usage example: # ``` # require "socket" diff --git a/src/socket/udp_socket.cr b/src/socket/udp_socket.cr index 3e9cebe0a01d..86132c54d0c7 100644 --- a/src/socket/udp_socket.cr +++ b/src/socket/udp_socket.cr @@ -15,6 +15,8 @@ require "./ip_socket" # This implementation supports both IPv4 and IPv6 addresses. For IPv4 addresses you must use # `Socket::Family::INET` family (default) or `Socket::Family::INET6` for IPv6 # addresses. # +# NOTE: To use `UDPSocket`, you must explicitly import it with `require "socket"` +# # Usage example: # # ``` diff --git a/src/socket/unix_server.cr b/src/socket/unix_server.cr index 98425e86cc54..e53f71d00842 100644 --- a/src/socket/unix_server.cr +++ b/src/socket/unix_server.cr @@ -4,6 +4,8 @@ require "./unix_socket" # # Only available on UNIX and UNIX-like operating systems. # +# NOTE: To use `UNIXServer`, you must explicitly import it with `require "socket"` +# # Example usage: # ``` # require "socket" diff --git a/src/socket/unix_socket.cr b/src/socket/unix_socket.cr index 6a4dbb3d340a..16ad99700db4 100644 --- a/src/socket/unix_socket.cr +++ b/src/socket/unix_socket.cr @@ -2,6 +2,8 @@ # # Only available on UNIX and UNIX-like operating systems. # +# NOTE: To use `UNIXSocket`, you must explicitly import it with `require "socket"` +# # Example usage: # ``` # require "socket" diff --git a/src/string_pool.cr b/src/string_pool.cr index 03b4246a3e0c..448196bcf622 100644 --- a/src/string_pool.cr +++ b/src/string_pool.cr @@ -2,6 +2,8 @@ # It allows a runtime to save memory by preserving strings in a pool, allowing to # reuse an instance of a common string instead of creating a new one. # +# NOTE: To use `StringPool`, you must explicitly import it with `require "string_pool"` +# # ``` # require "string_pool" # diff --git a/src/string_scanner.cr b/src/string_scanner.cr index b79725a9c354..c679f8de46d7 100644 --- a/src/string_scanner.cr +++ b/src/string_scanner.cr @@ -1,5 +1,7 @@ # `StringScanner` provides for lexical scanning operations on a `String`. # +# NOTE: To use `StringScanner`, you must explicitly import it with `require "string_scanner"` +# # ### Example # # ``` diff --git a/src/system/group.cr b/src/system/group.cr index 47c43ccee1ac..bd992e6af19d 100644 --- a/src/system/group.cr +++ b/src/system/group.cr @@ -2,6 +2,8 @@ require "crystal/system/group" # Represents a group of users on the host system. # +# NOTE: To use Group, you must explicitly import it with `require "system/group"` +# # Groups can be retrieved by either group name or their group ID: # # ``` diff --git a/src/system/user.cr b/src/system/user.cr index 2d0c9607056d..f23e13e6dc4c 100644 --- a/src/system/user.cr +++ b/src/system/user.cr @@ -2,6 +2,8 @@ require "crystal/system/user" # Represents a user on the host system. # +# NOTE: To use User, you must explicitly import it with `require "system/user"` +# # Users can be retrieved by either username or their user ID: # # ``` diff --git a/src/uri.cr b/src/uri.cr index 36a407e1d35b..06842deadf6d 100644 --- a/src/uri.cr +++ b/src/uri.cr @@ -9,6 +9,8 @@ require "./uri/params" # their components or by parsing their string forms and methods for accessing the various # components of an instance. # +# NOTE: To use `URI`, you must explicitly import it with `require "uri"` +# # Basic example: # # ``` diff --git a/src/uuid.cr b/src/uuid.cr index 2146c26b43bb..7c8d44478f84 100644 --- a/src/uuid.cr +++ b/src/uuid.cr @@ -1,4 +1,6 @@ # Represents a UUID (Universally Unique IDentifier). +# +# NOTE: To use `UUID`, you must explicitly import it with `require "uuid"` struct UUID include Comparable(UUID) diff --git a/src/weak_ref.cr b/src/weak_ref.cr index 103aedd30e7c..b5f7468383d0 100644 --- a/src/weak_ref.cr +++ b/src/weak_ref.cr @@ -1,6 +1,7 @@ # Weak Reference class that allows a referenced object to be garbage-collected. # # WARNING: The referenced object cannot be a module. +# NOTE: To use `WeakRef`, you must explicitly import it with `require "weak_ref"` # # ``` # require "weak_ref" diff --git a/src/xml.cr b/src/xml.cr index 9e1a19e4cc7c..1f0085fcd063 100644 --- a/src/xml.cr +++ b/src/xml.cr @@ -1,5 +1,7 @@ # The XML module allows parsing and generating [XML](https://www.w3.org/XML/) documents. # +# NOTE: To use `XML`, you must explicitly import it with `require "xml"` +# # ### Parsing # # `XML#parse` will parse xml from `String` or `IO` and return xml document as an `XML::Node` which represents all kinds of xml nodes. diff --git a/src/yaml.cr b/src/yaml.cr index 23a5843dfecb..c9ecb746ee69 100644 --- a/src/yaml.cr +++ b/src/yaml.cr @@ -10,6 +10,8 @@ require "base64" # version 1.1 to/from native Crystal data structures, with the additional # independent types specified in http://yaml.org/type/ # +# NOTE: To use `YAML`, you must explicitly import it with `require "yaml"` +# # ### Parsing with `#parse` and `#parse_all` # # `YAML.parse` will return an `Any`, which is a convenient wrapper around all possible From 96dc394598585eb9ae2fdde4a2c6c60282b4a335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 13 Feb 2023 13:45:27 +0100 Subject: [PATCH 0309/1551] `crystal tool format` (#13066) --- src/benchmark.cr | 2 +- src/http/web_socket.cr | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/benchmark.cr b/src/benchmark.cr index a581af2ad23b..bd77a93ae026 100644 --- a/src/benchmark.cr +++ b/src/benchmark.cr @@ -3,7 +3,7 @@ require "./benchmark/**" # The Benchmark module provides methods for benchmarking Crystal code, giving # detailed reports on the time and memory taken for each task. # -# NOTE: To use `Benchmark`, you must explicitly import it with `require "benchmark"` +# NOTE: To use `Benchmark`, you must explicitly import it with `require "benchmark"` # # ### Measure the number of iterations per second of each task # diff --git a/src/http/web_socket.cr b/src/http/web_socket.cr index 823e6b4e2eab..79d9bca38336 100644 --- a/src/http/web_socket.cr +++ b/src/http/web_socket.cr @@ -1,7 +1,6 @@ require "./client" require "./headers" - # NOTE: To use `WebSocket`, you must explicitly import it with `require "http/web_socket"` class HTTP::WebSocket getter? closed = false From b627df3aafd7cfc4a793cf411845739e95171fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 14 Feb 2023 12:04:47 +0100 Subject: [PATCH 0310/1551] Add `Process::Status#to_s` and `#inspect` (#13044) --- spec/std/process/status_spec.cr | 37 ++++++++++++++++++ src/compiler/crystal/compiler.cr | 3 +- src/compiler/crystal/macros/methods.cr | 4 +- .../crystal/tools/playground/server.cr | 3 +- src/process/status.cr | 39 +++++++++++++++++++ 5 files changed, 80 insertions(+), 6 deletions(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 3bae7bc60a9c..8cc731fa5c36 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -1,4 +1,5 @@ require "spec" +require "../../support/string" private def exit_status(status) {% if flag?(:unix) %} @@ -79,4 +80,40 @@ describe Process::Status do Process::Status.new(0x7e).signal_exit?.should be_true end {% end %} + + describe "#to_s" do + it "with exit status" do + assert_prints Process::Status.new(exit_status(0)).to_s, "0" + assert_prints Process::Status.new(exit_status(1)).to_s, "1" + assert_prints Process::Status.new(exit_status(127)).to_s, "127" + assert_prints Process::Status.new(exit_status(128)).to_s, "128" + assert_prints Process::Status.new(exit_status(255)).to_s, "255" + end + + {% if flag?(:unix) && !flag?(:wasi) %} + it "with exit signal" do + assert_prints Process::Status.new(Signal::HUP.value).to_s, "HUP" + last_signal = Signal.values[-1] + assert_prints Process::Status.new(last_signal.value).to_s, last_signal.to_s + end + {% end %} + end + + describe "#inspect" do + it "with exit status" do + assert_prints Process::Status.new(exit_status(0)).inspect, "Process::Status[0]" + assert_prints Process::Status.new(exit_status(1)).inspect, "Process::Status[1]" + assert_prints Process::Status.new(exit_status(127)).inspect, "Process::Status[127]" + assert_prints Process::Status.new(exit_status(128)).inspect, "Process::Status[128]" + assert_prints Process::Status.new(exit_status(255)).inspect, "Process::Status[255]" + end + + {% if flag?(:unix) && !flag?(:wasi) %} + it "with exit signal" do + assert_prints Process::Status.new(Signal::HUP.value).inspect, "Process::Status[Signal::HUP]" + last_signal = Signal.values[-1] + assert_prints Process::Status.new(last_signal.value).inspect, "Process::Status[#{last_signal.inspect}]" + end + {% end %} + end end diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 8d63ab52d367..147a11038cb0 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -621,9 +621,8 @@ module Crystal linker_not_found File::NotFoundError, linker_name end end - msg = status.normal_exit? ? "code: #{status.exit_code}" : "signal: #{status.exit_signal} (#{status.exit_signal.value})" code = status.normal_exit? ? status.exit_code : 1 - error "execution of command failed with #{msg}: `#{command}`", exit_code: code + error "execution of command failed with exit status #{status}: #{command}", exit_code: code end end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index d56f03d08dba..1dc45f34d451 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -270,9 +270,9 @@ module Crystal if $?.success? @last = MacroId.new(result) elsif result.empty? - node.raise "error executing command: #{cmd}, got exit status #{$?.exit_code}" + node.raise "error executing command: #{cmd}, got exit status #{$?}" else - node.raise "error executing command: #{cmd}, got exit status #{$?.exit_code}:\n\n#{result}\n" + node.raise "error executing command: #{cmd}, got exit status #{$?}:\n\n#{result}\n" end end diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr index f5b871ab3897..f060778257d4 100644 --- a/src/compiler/crystal/tools/playground/server.cr +++ b/src/compiler/crystal/tools/playground/server.cr @@ -172,12 +172,11 @@ module Crystal::Playground spawn do status = process.wait Log.info { "Code execution ended (session=#{@session_key}, tag=#{tag}, filename=#{output_filename})." } - exit_status = status.normal_exit? ? status.exit_code : status.exit_signal.value send_with_json_builder do |json| json.field "type", "exit" json.field "tag", tag - json.field "status", exit_status + json.field "status", status.to_s end end diff --git a/src/process/status.cr b/src/process/status.cr index b26e5b08d920..d0d9970f45d0 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -76,4 +76,43 @@ class Process::Status end def_equals_and_hash @exit_status + + # Prints a textual representation of the process status to *io*. + # + # The result is equivalent to `#to_s`, but prefixed by the type name and + # delimited by square brackets: `Process::Status[0]`, `Process::Status[1]`, + # `Process::Status[Signal::HUP]`. + def inspect(io : IO) : Nil + io << "Process::Status[" + if normal_exit? + exit_code.inspect(io) + else + exit_signal.inspect(io) + end + io << "]" + end + + # Prints a textual representation of the process status to *io*. + # + # A normal exit status prints the numerical value (`0`, `1` etc). + # A signal exit status prints the name of the `Signal` member (`HUP`, `INT`, etc.). + def to_s(io : IO) : Nil + if normal_exit? + io << exit_code + else + io << exit_signal + end + end + + # Returns a textual representation of the process status. + # + # A normal exit status prints the numerical value (`0`, `1` etc). + # A signal exit status prints the name of the `Signal` member (`HUP`, `INT`, etc.). + def to_s : String + if normal_exit? + exit_code.to_s + else + exit_signal.to_s + end + end end From 67e424d35bb4cbcc53767ad3e9af21c69e09ab67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 14 Feb 2023 18:30:10 +0100 Subject: [PATCH 0311/1551] Add support for 128-bit literals in the interpreter (#12859) Co-authored-by: Ary Borenszweig --- src/compiler/crystal/interpreter/compiler.cr | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 7787c63b73e6..b168342c9c21 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -344,11 +344,9 @@ class Crystal::Repl::Compiler < Crystal::Visitor in .u64? put_u64 value.to_u64, node: node in .i128? - # TODO: implement String#to_i128 and use it - put_i128 value.to_i64.to_i128!, node: node + put_i128 value.to_i128, node: node in .u128? - # TODO: implement String#to_i128 and use it - put_u128 value.to_u64.to_u128!, node: node + put_u128 value.to_u128, node: node in .f32? put_i32 value.to_f32.unsafe_as(Int32), node: node in .f64? From b07834d9c1b81230e41a54963244945bef303be3 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 15 Feb 2023 01:30:31 +0800 Subject: [PATCH 0312/1551] Add `graceful` parameter to `Process#terminate` (#13070) --- spec/std/process_spec.cr | 2 ++ src/crystal/system/process.cr | 4 ++-- src/crystal/system/unix/process.cr | 4 ++-- src/crystal/system/wasi/process.cr | 2 +- src/crystal/system/win32/process.cr | 4 ++-- .../x86_64-windows-msvc/c/processthreadsapi.cr | 1 + src/process.cr | 14 +++++++++++--- 7 files changed, 21 insertions(+), 10 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index cc1253bf9038..349281ec16e8 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -354,6 +354,8 @@ describe Process do process.try(&.wait) end + typeof(Process.new(*standing_command).terminate(graceful: false)) + pending_win32 ".exists?" do # We can't reliably check whether it ever returns false, since we can't predict # how PIDs are used by the system, a new process might be spawned in between diff --git a/src/crystal/system/process.cr b/src/crystal/system/process.cr index d613d7ac431c..387447c083c2 100644 --- a/src/crystal/system/process.cr +++ b/src/crystal/system/process.cr @@ -21,8 +21,8 @@ struct Crystal::System::Process # Whether the process is still registered in the system. # def exists? : Bool - # Asks this process to terminate gracefully. - # def terminate + # Asks this process to terminate. + # def terminate(*, graceful) # Terminates the current process immediately. # def self.exit(status : Int) diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index f94835887a4e..407ebebc6151 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -22,8 +22,8 @@ struct Crystal::System::Process !@channel.closed? && Crystal::System::Process.exists?(@pid) end - def terminate - Crystal::System::Process.signal(@pid, LibC::SIGTERM) + def terminate(*, graceful) + Crystal::System::Process.signal(@pid, graceful ? LibC::SIGTERM : LibC::SIGKILL) end def self.exit(status) diff --git a/src/crystal/system/wasi/process.cr b/src/crystal/system/wasi/process.cr index 5951fa1c816b..a6f9d156c396 100644 --- a/src/crystal/system/wasi/process.cr +++ b/src/crystal/system/wasi/process.cr @@ -19,7 +19,7 @@ struct Crystal::System::Process raise NotImplementedError.new("Process#exists?") end - def terminate + def terminate(*, graceful) raise NotImplementedError.new("Process#terminate") end diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index c934c5b26047..83ae2a110f34 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -50,8 +50,8 @@ struct Crystal::System::Process Crystal::System::Process.exists?(@pid) end - def terminate - raise NotImplementedError.new("Process.kill") + def terminate(*, graceful) + LibC.TerminateProcess(@process_handle, 1) end def self.exit(status) diff --git a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr index 441d9acac697..fb5b3fee46d2 100644 --- a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr @@ -39,6 +39,7 @@ lib LibC fun GetCurrentProcessId : DWORD fun OpenProcess(dwDesiredAccess : DWORD, bInheritHandle : BOOL, dwProcessId : DWORD) : HANDLE fun GetExitCodeProcess(hProcess : HANDLE, lpExitCode : DWORD*) : BOOL + fun TerminateProcess(hProcess : HANDLE, uExitCode : UInt) : BOOL fun CreateProcessW(lpApplicationName : LPWSTR, lpCommandLine : LPWSTR, lpProcessAttributes : SECURITY_ATTRIBUTES*, lpThreadAttributes : SECURITY_ATTRIBUTES*, bInheritHandles : BOOL, dwCreationFlags : DWORD, diff --git a/src/process.cr b/src/process.cr index 24bd8b005200..69d3953e6a7a 100644 --- a/src/process.cr +++ b/src/process.cr @@ -343,9 +343,17 @@ class Process @process_info.release end - # Asks this process to terminate gracefully - def terminate : Nil - @process_info.terminate + # Asks this process to terminate. + # + # If *graceful* is true, prefers graceful termination over abrupt termination + # if supported by the system. + # + # * On Unix-like systems, this causes `Signal::TERM` to be sent to the process + # instead of `Signal::KILL`. + # * On Windows, this parameter has no effect and graceful termination is + # unavailable. The terminated process has an exit status of 1. + def terminate(*, graceful : Bool = true) : Nil + @process_info.terminate(graceful: graceful) end private def channel From c9b90e2499bcf4b689c02cc85e37bce14aee8a78 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 15 Feb 2023 01:31:04 +0800 Subject: [PATCH 0313/1551] Dynamic library loader: search in `-L` directories before default ones (#13069) --- spec/compiler/loader/msvc_spec.cr | 5 +++++ spec/compiler/loader/unix_spec.cr | 5 +++++ src/compiler/crystal/loader/msvc.cr | 9 +++++++-- src/compiler/crystal/loader/unix.cr | 18 +++++++++++++++++- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/spec/compiler/loader/msvc_spec.cr b/spec/compiler/loader/msvc_spec.cr index ec4f5e4904c8..fedb21c352c4 100644 --- a/spec/compiler/loader/msvc_spec.cr +++ b/spec/compiler/loader/msvc_spec.cr @@ -12,6 +12,11 @@ describe Crystal::Loader do loader.search_paths.should eq [%q(C:\foo\bar), "baz"] end + it "prepends directory paths before default search paths" do + loader = Crystal::Loader.parse(%w(/LIBPATH:foo /LIBPATH:bar), search_paths: %w(baz quux)) + loader.search_paths.should eq %w(foo bar baz quux) + end + it "parses file paths" do expect_raises(Crystal::Loader::LoadError, "cannot find foobar.lib") do Crystal::Loader.parse(["foobar.lib"], search_paths: [] of String) diff --git a/spec/compiler/loader/unix_spec.cr b/spec/compiler/loader/unix_spec.cr index 783175722c13..3be1c84f19b7 100644 --- a/spec/compiler/loader/unix_spec.cr +++ b/spec/compiler/loader/unix_spec.cr @@ -12,6 +12,11 @@ describe Crystal::Loader do loader.search_paths.should eq ["/foo/bar/baz", "qux"] end + it "prepends directory paths before default search paths" do + loader = Crystal::Loader.parse(%w(-Lfoo -Lbar), search_paths: %w(baz quux)) + loader.search_paths.should eq %w(foo bar baz quux) + end + it "parses static" do expect_raises(Crystal::Loader::LoadError, "static libraries are not supported by Crystal's runtime loader") do Crystal::Loader.parse(["-static"], search_paths: [] of String) diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index 41cbba0397e3..70560e334ca7 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -23,8 +23,11 @@ class Crystal::Loader libnames = [] of String file_paths = [] of String - # NOTE: `/LIBPATH` overrides the default paths, not the other way round + # NOTE: `/LIBPATH`s are prepended before the default paths: # (https://docs.microsoft.com/en-us/cpp/build/reference/libpath-additional-libpath) + # + # > ... The linker will first search in the path specified by this option, + # > and then search in the path specified in the LIB environment variable. extra_search_paths = [] of String args.each do |arg| @@ -35,8 +38,10 @@ class Crystal::Loader end end + search_paths = extra_search_paths + search_paths + begin - self.new(extra_search_paths + search_paths, libnames, file_paths) + self.new(search_paths, libnames, file_paths) rescue exc : LoadError exc.args = args exc.search_paths = search_paths diff --git a/src/compiler/crystal/loader/unix.cr b/src/compiler/crystal/loader/unix.cr index fb26f58423cf..bb2cc440beaa 100644 --- a/src/compiler/crystal/loader/unix.cr +++ b/src/compiler/crystal/loader/unix.cr @@ -40,10 +40,24 @@ class Crystal::Loader libnames = [] of String file_paths = [] of String + # `man ld(1)` on Linux: + # + # > -L searchdir + # > ... The directories are searched in the order in which they are + # specified on the command line. Directories specified on the command line + # are searched before the default directories. + # + # `man ld(1)` on macOS: + # + # > -Ldir + # > ... Directories specified with -L are searched in the order they appear + # > on the command line and before the default search path... + extra_search_paths = [] of String + # OptionParser removes items from the args array, so we dup it here in order to produce a meaningful error message. OptionParser.parse(args.dup) do |parser| parser.on("-L DIRECTORY", "--library-path DIRECTORY", "Add DIRECTORY to library search path") do |directory| - search_paths << directory + extra_search_paths << directory end parser.on("-l LIBNAME", "--library LIBNAME", "Search for library LIBNAME") do |libname| libnames << libname @@ -56,6 +70,8 @@ class Crystal::Loader end end + search_paths = extra_search_paths + search_paths + begin self.new(search_paths, libnames, file_paths) rescue exc : LoadError From 563458336b34d05c445803c5cadfff1fb106867f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 15 Feb 2023 01:31:19 +0800 Subject: [PATCH 0314/1551] Add `Slice#+(Slice)` and `Slice.join` (#12081) --- spec/std/enumerable_spec.cr | 4 +++ spec/std/slice_spec.cr | 58 ++++++++++++++++++++++++++++++++++++- src/enumerable.cr | 3 ++ src/slice.cr | 42 +++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 3383f5ad3f9c..1c0e98c9aba8 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -1164,6 +1164,10 @@ describe "Enumerable" do [1.0, 2.0, 3.5, 4.5].sum.should eq 11.0 ([1.0, 2.0, 3.5, 4.5] of Float32).sum.should eq 11.0 end + + it "slices" do + [Slice[1, 2], Slice[3, 'a', 'b', 'c']].sum.should eq(Slice[1, 2, 3, 'a', 'b', 'c']) + end end describe "product" do diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr index 8633251f014a..72803e0b661b 100644 --- a/spec/std/slice_spec.cr +++ b/spec/std/slice_spec.cr @@ -87,7 +87,7 @@ describe "Slice" do expect_raises(IndexError) { slice[3] = 1 } end - it "does +" do + it "#+(Int)" do slice = Slice.new(3) { |i| i + 1 } slice1 = slice + 1 @@ -842,6 +842,62 @@ describe "Slice" do (Bytes[1, 2, 3, 4] <=> Bytes[1, 2, 3]).should be > 0 end end + + describe "#+(Slice)" do + it "concatenates two slices" do + a = Slice[1, 2] + b = a + Slice[3, 4, 5] + b.should be_a(Slice(Int32)) + b.should eq(Slice[1, 2, 3, 4, 5]) + a.should eq(Slice[1, 2]) + + c = Slice[1, 2] + Slice['a', 'b', 'c'] + c.should be_a(Slice(Int32 | Char)) + c.should eq(Slice[1, 2, 'a', 'b', 'c']) + end + end + + describe ".join" do + it "concatenates an indexable of slices" do + a = Slice.join([Slice[1, 2], Slice[3, 4, 5]]) + a.should be_a(Slice(Int32)) + a.should eq(Slice[1, 2, 3, 4, 5]) + + b = Slice.join({Slice[1, 2], Slice['a', 'b', 'c']}) + b.should be_a(Slice(Int32 | Char)) + b.should eq(Slice[1, 2, 'a', 'b', 'c']) + + c = Slice.join(Deque{Slice[1, 2], Slice['a', 'b', 'c'], Slice["d", "e"], Slice[3, "f"]}) + c.should be_a(Slice(Int32 | Char | String)) + c.should eq(Slice[1, 2, 'a', 'b', 'c', "d", "e", 3, "f"]) + end + + it "concatenates a slice of slices" do + a = Slice[1] + b = Slice['a'] + c = Slice["xyz"] + + Slice.join(Slice[a, b, c]).should eq(Slice[1, 'a', "xyz"]) + end + + it "concatenates an empty indexable of slices" do + a = Slice.join(Array(Slice(Int32)).new) + a.should be_a(Slice(Int32)) + a.should be_empty + + b = Slice.join(Deque(Slice(Int32)).new) + b.should be_a(Slice(Int32)) + b.should be_empty + end + end + + describe ".additive_identity" do + it "returns an empty slice" do + a = Slice(Int32).additive_identity + a.should be_a(Slice(Int32)) + a.should be_empty + end + end end private def itself(*args) diff --git a/src/enumerable.cr b/src/enumerable.cr index 5476e2b55433..1f1176f75de4 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -1556,6 +1556,9 @@ module Enumerable(T) {% elsif T < Array %} # optimize for array flat_map &.itself + {% elsif T < Slice && @type < Indexable %} + # optimize for slice + Slice.join(self) {% else %} sum additive_identity(Reflect(T)) {% end %} diff --git a/src/slice.cr b/src/slice.cr index 4a49f77b97e6..26a7ae450bb3 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -170,6 +170,48 @@ struct Slice(T) Slice.new(@pointer + offset, @size - offset, read_only: @read_only) end + # Returns a new slice that has `self`'s elements followed by *other*'s + # elements. + # + # ``` + # Slice[1, 2] + Slice[3, 4, 5] # => Slice[1, 2, 3, 4, 5] + # Slice[1, 2, 3] + Slice['a', 'b', 'c'] # => Slice[1, 2, 3, 'a', 'b', 'c'] + # ``` + # + # See also: `Slice.join` to join multiple slices at once without creating + # intermediate results. + def +(other : Slice) : Slice + Slice.join({self, other}) + end + + # Returns a new slice that has the elements from *slices* joined together. + # + # ``` + # Slice.join([Slice[1, 2], Slice[3, 4, 5]]) # => Slice[1, 2, 3, 4, 5] + # Slice.join({Slice[1], Slice['a'], Slice["xyz"]}) # => Slice[1, 'a', "xyz"] + # ``` + # + # See also: `#+(other : Slice)`. + def self.join(slices : Indexable(Slice)) : Slice + total_size = slices.sum(&.size) + buf = Pointer(typeof(Enumerable.element_type Enumerable.element_type slices)).malloc(total_size) + + ptr = buf + slices.each do |slice| + slice.to_unsafe.copy_to(ptr, slice.size) + ptr += slice.size + end + + Slice.new(buf, total_size) + end + + # Returns the additive identity of this type. + # + # This is an empty slice. + def self.additive_identity : self + self.new(0) + end + # :inherit: # # Raises if this slice is read-only. From 677e60f5a6be12473bb71b777db99ccf7a6001e5 Mon Sep 17 00:00:00 2001 From: synthia <124930260+nthiad@users.noreply.github.com> Date: Wed, 15 Feb 2023 02:34:32 -0800 Subject: [PATCH 0315/1551] Add `Enumerable#min(count)` and `#max(count)` (#13057) Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Beta Ziliani --- spec/std/enumerable_spec.cr | 60 +++++++++++++++++++++++++++++ src/enumerable.cr | 77 +++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 1c0e98c9aba8..0339c9d8c0f0 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -803,6 +803,24 @@ describe "Enumerable" do describe "max" do it { [1, 2, 3].max.should eq(3) } + it { [1, 2, 3].max(0).should eq([] of Int32) } + it { [1, 2, 3].max(1).should eq([3]) } + it { [1, 2, 3].max(2).should eq([3, 2]) } + it { [1, 2, 3].max(3).should eq([3, 2, 1]) } + it { [1, 2, 3].max(4).should eq([3, 2, 1]) } + it { ([] of Int32).max(0).should eq([] of Int32) } + it { ([] of Int32).max(5).should eq([] of Int32) } + it { + (0..1000).map { |x| (x*137 + x*x*139) % 5000 }.max(10).should eq([ + 4992, 4990, 4980, 4972, 4962, 4962, 4960, 4960, 4952, 4952, + ]) + } + + it "does not modify the array" do + xs = [7, 5, 2, 4, 9] + xs.max(2).should eq([9, 7]) + xs.should eq([7, 5, 2, 4, 9]) + end it "raises if empty" do expect_raises Enumerable::EmptyError do @@ -810,11 +828,23 @@ describe "Enumerable" do end end + it "raises if n is negative" do + expect_raises ArgumentError do + ([1, 2, 3] of Int32).max(-1) + end + end + it "raises if not comparable" do expect_raises ArgumentError do [Float64::NAN, 1.0, 2.0, Float64::NAN].max end end + + it "raises if not comparable in max(n)" do + expect_raises ArgumentError do + [Float64::NAN, 1.0, 2.0, Float64::NAN].max(2) + end + end end describe "max?" do @@ -851,6 +881,24 @@ describe "Enumerable" do describe "min" do it { [1, 2, 3].min.should eq(1) } + it { [1, 2, 3].min(0).should eq([] of Int32) } + it { [1, 2, 3].min(1).should eq([1]) } + it { [1, 2, 3].min(2).should eq([1, 2]) } + it { [1, 2, 3].min(3).should eq([1, 2, 3]) } + it { [1, 2, 3].min(4).should eq([1, 2, 3]) } + it { ([] of Int32).min(0).should eq([] of Int32) } + it { ([] of Int32).min(1).should eq([] of Int32) } + it { + (0..1000).map { |x| (x*137 + x*x*139) % 5000 }.min(10).should eq([ + 0, 10, 20, 26, 26, 26, 26, 30, 32, 32, + ]) + } + + it "does not modify the array" do + xs = [7, 5, 2, 4, 9] + xs.min(2).should eq([2, 4]) + xs.should eq([7, 5, 2, 4, 9]) + end it "raises if empty" do expect_raises Enumerable::EmptyError do @@ -858,11 +906,23 @@ describe "Enumerable" do end end + it "raises if n is negative" do + expect_raises ArgumentError do + ([1, 2, 3] of Int32).min(-1) + end + end + it "raises if not comparable" do expect_raises ArgumentError do [-1.0, Float64::NAN, -3.0].min end end + + it "raises if not comparable in min(n)" do + expect_raises ArgumentError do + [Float64::NAN, 1.0, 2.0, Float64::NAN].min(2) + end + end end describe "min?" do diff --git a/src/enumerable.cr b/src/enumerable.cr index 1f1176f75de4..7c9482d493ff 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -965,6 +965,35 @@ module Enumerable(T) ary end + private def quickselect_internal(data : Array(T), left : Int, right : Int, k : Int) : T + loop do + return data[left] if left == right + pivot_index = left + (right - left)//2 + pivot_index = quickselect_partition_internal(data, left, right, pivot_index) + if k == pivot_index + return data[k] + elsif k < pivot_index + right = pivot_index - 1 + else + left = pivot_index + 1 + end + end + end + + private def quickselect_partition_internal(data : Array(T), left : Int, right : Int, pivot_index : Int) : Int + pivot_value = data[pivot_index] + data.swap(pivot_index, right) + store_index = left + (left...right).each do |i| + if compare_or_raise(data[i], pivot_value) < 0 + data.swap(store_index, i) + store_index += 1 + end + end + data.swap(right, store_index) + store_index + end + # Returns the element with the maximum value in the collection. # # It compares using `>` so it will work for any type that supports that method. @@ -984,6 +1013,30 @@ module Enumerable(T) max_by? &.itself end + # Returns an array of the maximum *count* elements, sorted descending. + # + # It compares using `<=>` so it will work for any type that supports that method. + # + # ``` + # [7, 5, 2, 4, 9].max(3) # => [9, 7, 5] + # %w[Eve Alice Bob Mallory Carol].max(2) # => ["Mallory", "Eve"] + # ``` + # + # Returns all elements sorted descending if *count* is greater than the number + # of elements in the source. + # + # Raises `Enumerable::ArgumentError` if *count* is negative or if any elements + # are not comparable. + def max(count : Int) : Array(T) + raise ArgumentError.new("Count must be positive") if count < 0 + data = self.is_a?(Array) ? self.dup : self.to_a + n = data.size + count = n if count > n + (0...count).map do |i| + quickselect_internal(data, 0, n - 1, n - 1 - i) + end + end + # Returns the element for which the passed block returns with the maximum value. # # It compares using `>` so the block must return a type that supports that method @@ -1073,6 +1126,30 @@ module Enumerable(T) min_by? &.itself end + # Returns an array of the minimum *count* elements, sorted ascending. + # + # It compares using `<=>` so it will work for any type that supports that method. + # + # ``` + # [7, 5, 2, 4, 9].min(3) # => [2, 4, 5] + # %w[Eve Alice Bob Mallory Carol].min(2) # => ["Alice", "Bob"] + # ``` + # + # Returns all elements sorted ascending if *count* is greater than the number + # of elements in the source. + # + # Raises `Enumerable::ArgumentError` if *count* is negative or if any elements + # are not comparable. + def min(count : Int) : Array(T) + raise ArgumentError.new("Count must be positive") if count < 0 + data = self.is_a?(Array) ? self.dup : self.to_a + n = data.size + count = n if count > n + (0...count).map do |i| + quickselect_internal(data, 0, n - 1, i) + end + end + # Returns the element for which the passed block returns with the minimum value. # # It compares using `<` so the block must return a type that supports that method From 1b6cc3b9388e7f51b1093d93f98848c8ac26d882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 15 Feb 2023 11:35:04 +0100 Subject: [PATCH 0316/1551] Refactor `SemanticVisitor` tighter `rescue` scope in `Require` visitor (#12887) --- .../crystal/semantic/semantic_visitor.cr | 59 ++++++++++--------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index 07f2bf5f1b16..03c28631f1d2 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -46,7 +46,27 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor # Remember that the program depends on this require @program.record_require(filename, relative_to) - filenames = @program.find_in_path(filename, relative_to) + filenames = begin + @program.find_in_path(filename, relative_to) + rescue ex : CrystalPath::NotFoundError + message = "can't find file '#{ex.filename}'" + notes = [] of String + + if ex.filename.starts_with? '.' + if relative_to + message += " relative to '#{relative_to}'" + end + else + notes << <<-NOTE + If you're trying to require a shard: + - Did you remember to run `shards install`? + - Did you make sure you're running the compiler in the same directory as your shard.yml? + NOTE + end + + node.raise "#{message}\n\n#{notes.join("\n")}" + end + if filenames nodes = Array(ASTNode).new(filenames.size) filenames.each do |filename| @@ -54,11 +74,17 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor parser = @program.new_parser(File.read(filename)) parser.filename = filename parser.wants_doc = @program.wants_doc? - parsed_nodes = parser.parse - parsed_nodes = @program.normalize(parsed_nodes, inside_exp: inside_exp?) - # We must type the node immediately, in case a file requires another - # *before* one of the files in `filenames` - parsed_nodes.accept self + begin + parsed_nodes = parser.parse + parsed_nodes = @program.normalize(parsed_nodes, inside_exp: inside_exp?) + # We must type the node immediately, in case a file requires another + # *before* one of the files in `filenames` + parsed_nodes.accept self + rescue ex : CodeError + node.raise "while requiring \"#{node.string}\"", ex + rescue ex + raise Error.new "while requiring \"#{node.string}\"", ex + end nodes << FileNode.new(parsed_nodes, filename) end end @@ -70,27 +96,6 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor node.expanded = expanded node.bind_to(expanded) false - rescue ex : CrystalPath::NotFoundError - message = "can't find file '#{ex.filename}'" - notes = [] of String - - if ex.filename.starts_with? '.' - if relative_to - message += " relative to '#{relative_to}'" - end - else - notes << <<-NOTE - If you're trying to require a shard: - - Did you remember to run `shards install`? - - Did you make sure you're running the compiler in the same directory as your shard.yml? - NOTE - end - - node.raise "#{message}\n\n#{notes.join("\n")}" - rescue ex : Crystal::CodeError - node.raise "while requiring \"#{node.string}\"", ex - rescue ex - raise Error.new("while requiring \"#{node.string}\"", ex) end def visit(node : ClassDef) From 3ecc144e34068994c45c31d1512692cec056aeb0 Mon Sep 17 00:00:00 2001 From: Julien Reichardt Date: Wed, 15 Feb 2023 11:36:08 +0100 Subject: [PATCH 0317/1551] Crystal wrapper script enhancements (#12959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- bin/crystal | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/bin/crystal b/bin/crystal index 1f9566399984..b6bd2e64ba0b 100755 --- a/bin/crystal +++ b/bin/crystal @@ -36,8 +36,8 @@ _resolve_symlinks() { _assert_no_path_cycles "$@" || return local dir_context path - path=$(readlink -- "$1") - if [ $? = 0 ]; then + + if path=$(readlink -- "$1"); then dir_context=$(dirname -- "$1") _resolve_symlinks "$(_prepend_dir_context_if_necessary "$dir_context" "$path")" "$@" else @@ -82,14 +82,14 @@ canonicalize_path() { } _canonicalize_dir_path() { - (cd "$1" 2>/dev/null && pwd -P) + { cd "$1" 2>/dev/null && pwd -P; } } _canonicalize_file_path() { local dir file dir=$(dirname -- "$1") file=$(basename -- "$1") - (cd "$dir" 2>/dev/null >/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file") + { cd "$dir" 2>/dev/null >/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file"; } } ############################################################################## @@ -117,7 +117,7 @@ __has_colors() { __error_msg() { if __has_colors; then # bold red coloring - printf '%b\n' "\033[31;1m$@\033[0m" 1>&2 + printf '%b\n' "\033[31;1m$*\033[0m" 1>&2 else printf '%b\n' "$@" 1>&2 fi @@ -125,7 +125,7 @@ __error_msg() { __warning_msg() { if __has_colors; then # brown coloring - printf '%b\n' "\033[33m$@\033[0m" 1>&2 + printf '%b\n' "\033[33m$*\033[0m" 1>&2 else printf '%b\n' "$@" 1>&2 fi @@ -138,13 +138,12 @@ CRYSTAL_ROOT="$(dirname "$SCRIPT_ROOT")" CRYSTAL_DIR="$CRYSTAL_ROOT/.build" export CRYSTAL_PATH="${CRYSTAL_PATH:-lib:$CRYSTAL_ROOT/src}" -if [ -n "${CRYSTAL_PATH##*$CRYSTAL_ROOT/src*}" ]; then +if [ -n "${CRYSTAL_PATH##*"$CRYSTAL_ROOT"/src*}" ]; then __warning_msg "CRYSTAL_PATH env variable does not contain $CRYSTAL_ROOT/src" fi export CRYSTAL_HAS_WRAPPER=true - -export CRYSTAL="${CRYSTAL:-"crystal"}" +: "${CRYSTAL=crystal}" PARENT_CRYSTAL="$CRYSTAL" @@ -177,9 +176,9 @@ fi if [ -x "$CRYSTAL_DIR/crystal" ]; then __warning_msg "Using compiled compiler at ${CRYSTAL_DIR#"$PWD/"}/crystal" exec "$CRYSTAL_DIR/crystal" "$@" -elif ! command -v $PARENT_CRYSTAL > /dev/null; then +elif ! command -v "$PARENT_CRYSTAL" > /dev/null; then __error_msg 'You need to have a crystal executable in your path! or set CRYSTAL env variable' exit 1 else - exec $PARENT_CRYSTAL "$@" + exec "$PARENT_CRYSTAL" "$@" fi From 9bed917aee1020dce1cf0e580c32b6cc3b2f06ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20A?= Date: Wed, 15 Feb 2023 11:17:51 -0300 Subject: [PATCH 0318/1551] Fix wrong default address when binding sockets (#13006) --- spec/std/socket/socket_spec.cr | 20 ++++++++++++++++++++ src/socket.cr | 12 ++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index c0cd69654eb4..0049a564a96e 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -12,6 +12,10 @@ describe Socket, tags: "network" do sock = Socket.unix(Socket::Type::DGRAM) sock.type.should eq(Socket::Type::DGRAM) + + expect_raises Socket::Error, "Protocol not supported" do + TCPSocket.new(family: :unix) + end end end @@ -116,6 +120,22 @@ describe Socket, tags: "network" do ensure socket.try &.close end + + it "binds to port using default IP" do + socket = TCPSocket.new family + socket.bind unused_local_port + socket.listen + + address = socket.local_address.as(Socket::IPAddress) + address.address.should eq(any_address) + address.port.should be > 0 + + socket.close + + socket = UDPSocket.new family + socket.bind unused_local_port + socket.close + end end end end diff --git a/src/socket.cr b/src/socket.cr index df736a561503..50c5a3e29884 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -116,8 +116,16 @@ class Socket < IO # sock.bind 1234 # ``` def bind(port : Int) - Addrinfo.resolve("::", port, @family, @type, @protocol) do |addrinfo| - system_bind(addrinfo, "::#{port}") { |errno| errno } + if family.inet? + address = "0.0.0.0" + address_and_port = "0.0.0.0:#{port}" + else + address = "::" + address_and_port = "[::]:#{port}" + end + + Addrinfo.resolve(address, port, @family, @type, @protocol) do |addrinfo| + system_bind(addrinfo, address_and_port) { |errno| errno } end end From 6ffac8256f821d29ea13d3149ec18711d517f415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 15 Feb 2023 15:17:59 +0100 Subject: [PATCH 0319/1551] Fix sed command in `scripts/update-distribution-scripts.cr` (#13071) --- scripts/update-distribution-scripts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/update-distribution-scripts.sh b/scripts/update-distribution-scripts.sh index a0aa96deff90..abb74e613095 100755 --- a/scripts/update-distribution-scripts.sh +++ b/scripts/update-distribution-scripts.sh @@ -42,7 +42,7 @@ sed -i -E "/distribution-scripts-version:/{n;n;n;s/default: \".*\"/default: \"$r git add .circleci/config.yml message="Updates \`distribution-scripts\` dependency to https://github.com/crystal-lang/distribution-scripts/commit/$reference" -log=$($GIT_DS log $old_reference..$reference --format="%s" | sed "s/.*(/crystal-lang\/distribution-scripts/;s/^/* /;s/.$//") +log=$($GIT_DS log $old_reference..$reference --format="%s" | sed "s/.*(/crystal-lang\/distribution-scripts/;s/^/* /;") message=$(printf "%s\n\nThis includes the following changes:\n\n%s" "$message" "$log") git commit -m "Update distribution-scripts" -m "$message" From f731a6bf3286ef6e4065582b6d7e0605fcfa0fa7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 15 Feb 2023 22:18:08 +0800 Subject: [PATCH 0320/1551] Handle ARM64 MSVC paths when cross-compiling on Windows (#13073) --- .github/workflows/win.yml | 2 +- src/compiler/crystal/compiler.cr | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index e06a0ef88f03..79ea27e59c74 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -310,7 +310,7 @@ jobs: if: steps.cache-llvm.outputs.cache-hit != 'true' working-directory: ./llvm-src run: | - cmake . -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86" -DLLVM_USE_CRT_RELEASE=MT -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF + cmake . -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DLLVM_USE_CRT_RELEASE=MT -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF cmake --build . --config Release - name: Gather LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 147a11038cb0..bfa36f9586e9 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -341,8 +341,8 @@ module Crystal {% if flag?(:msvc) %} if msvc_path = Crystal::System::VisualStudio.find_latest_msvc_path if win_sdk_libpath = Crystal::System::WindowsSDK.find_win10_sdk_libpath - host_bits = {{ flag?(:bits64) ? "x64" : "x86" }} - target_bits = program.has_flag?("bits64") ? "x64" : "x86" + host_bits = {{ flag?(:aarch64) ? "ARM64" : flag?(:bits64) ? "x64" : "x86" }} + target_bits = program.has_flag?("aarch64") ? "arm64" : program.has_flag?("bits64") ? "x64" : "x86" # MSVC build tools and Windows SDK found; recreate `LIB` environment variable # that is normally expected on the MSVC developer command prompt @@ -352,6 +352,8 @@ module Crystal link_args << Process.quote_windows("/LIBPATH:#{win_sdk_libpath.join("um", target_bits)}") # use exact path for compiler instead of relying on `PATH` + # (letter case shouldn't matter in most cases but being exact doesn't hurt here) + target_bits = target_bits.sub("arm", "ARM") cl = Process.quote_windows(msvc_path.join("bin", "Host#{host_bits}", target_bits, "cl.exe").to_s) end end From 97585678c259a249dcbea1ad63dbe6851f2e3525 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 16 Feb 2023 19:08:34 +0800 Subject: [PATCH 0321/1551] Add `Process::ExitReason` and `Process::Status#exit_reason` (#13052) --- spec/std/process/status_spec.cr | 106 +++++++++++- src/compiler/crystal/command.cr | 51 ++++-- .../x86_64-windows-msvc/c/errhandlingapi.cr | 5 +- src/lib_c/x86_64-windows-msvc/c/ntstatus.cr | 17 ++ src/process/status.cr | 151 ++++++++++++++++-- 5 files changed, 300 insertions(+), 30 deletions(-) create mode 100644 src/lib_c/x86_64-windows-msvc/c/ntstatus.cr diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 8cc731fa5c36..871e2bd837df 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -68,16 +68,116 @@ describe Process::Status do end it "#normal_exit? with signal code" do + Process::Status.new(0x00).normal_exit?.should be_true Process::Status.new(0x01).normal_exit?.should be_false + Process::Status.new(0x7e).normal_exit?.should be_false Process::Status.new(0x7f).normal_exit?.should be_false end it "#signal_exit? with signal code" do + Process::Status.new(0x00).signal_exit?.should be_false Process::Status.new(0x01).signal_exit?.should be_true - - # 0x7f raises arithmetic error due to overflow, but this shouldn't - # matter because actual signal values don't expand to that range Process::Status.new(0x7e).signal_exit?.should be_true + Process::Status.new(0x7f).signal_exit?.should be_false + end + {% end %} + + {% if flag?(:win32) %} + describe "#exit_reason" do + it "returns Normal" do + Process::Status.new(exit_status(0)).exit_reason.normal?.should be_true + Process::Status.new(exit_status(1)).exit_reason.normal?.should be_true + Process::Status.new(exit_status(127)).exit_reason.normal?.should be_true + Process::Status.new(exit_status(128)).exit_reason.normal?.should be_true + Process::Status.new(exit_status(255)).exit_reason.normal?.should be_true + + Process::Status.new(0x3FFFFFFF_u32).exit_reason.normal?.should be_true + Process::Status.new(0x40001234_u32).exit_reason.normal?.should be_false + Process::Status.new(0x80001234_u32).exit_reason.normal?.should be_false + Process::Status.new(0xC0001234_u32).exit_reason.normal?.should be_false + end + + it "returns Aborted" do + Process::Status.new(LibC::STATUS_FATAL_APP_EXIT).exit_reason.aborted?.should be_true + end + + it "returns Interrupted" do + Process::Status.new(LibC::STATUS_CONTROL_C_EXIT).exit_reason.interrupted?.should be_true + end + + it "returns Breakpoint" do + Process::Status.new(LibC::STATUS_BREAKPOINT).exit_reason.breakpoint?.should be_true + end + + it "returns AccessViolation" do + Process::Status.new(LibC::STATUS_ACCESS_VIOLATION).exit_reason.access_violation?.should be_true + Process::Status.new(LibC::STATUS_STACK_OVERFLOW).exit_reason.access_violation?.should be_true + end + + it "returns BadMemoryAccess" do + Process::Status.new(LibC::STATUS_DATATYPE_MISALIGNMENT).exit_reason.bad_memory_access?.should be_true + end + + it "returns BadInstruction" do + Process::Status.new(LibC::STATUS_ILLEGAL_INSTRUCTION).exit_reason.bad_instruction?.should be_true + Process::Status.new(LibC::STATUS_PRIVILEGED_INSTRUCTION).exit_reason.bad_instruction?.should be_true + end + + it "returns FloatException" do + Process::Status.new(LibC::STATUS_FLOAT_DIVIDE_BY_ZERO).exit_reason.float_exception?.should be_true + Process::Status.new(LibC::STATUS_FLOAT_INEXACT_RESULT).exit_reason.float_exception?.should be_true + Process::Status.new(LibC::STATUS_FLOAT_INVALID_OPERATION).exit_reason.float_exception?.should be_true + Process::Status.new(LibC::STATUS_FLOAT_OVERFLOW).exit_reason.float_exception?.should be_true + Process::Status.new(LibC::STATUS_FLOAT_UNDERFLOW).exit_reason.float_exception?.should be_true + end + end + {% elsif flag?(:unix) && !flag?(:wasi) %} + describe "#exit_reason" do + it "returns Normal" do + Process::Status.new(exit_status(0)).exit_reason.normal?.should be_true + Process::Status.new(exit_status(1)).exit_reason.normal?.should be_true + Process::Status.new(exit_status(127)).exit_reason.normal?.should be_true + Process::Status.new(exit_status(128)).exit_reason.normal?.should be_true + Process::Status.new(exit_status(255)).exit_reason.normal?.should be_true + + Process::Status.new(0x01).exit_reason.normal?.should be_false + Process::Status.new(0x7e).exit_reason.normal?.should be_false + + Process::Status.new(0x017f).exit_reason.normal?.should be_false + Process::Status.new(0xffff).exit_reason.normal?.should be_false + end + + it "returns Aborted" do + Process::Status.new(Signal::ABRT.value).exit_reason.aborted?.should be_true + Process::Status.new(Signal::HUP.value).exit_reason.aborted?.should be_true + Process::Status.new(Signal::KILL.value).exit_reason.aborted?.should be_true + Process::Status.new(Signal::QUIT.value).exit_reason.aborted?.should be_true + Process::Status.new(Signal::TERM.value).exit_reason.aborted?.should be_true + end + + it "returns Interrupted" do + Process::Status.new(Signal::INT.value).exit_reason.interrupted?.should be_true + end + + it "returns Breakpoint" do + Process::Status.new(Signal::TRAP.value).exit_reason.breakpoint?.should be_true + end + + it "returns AccessViolation" do + Process::Status.new(Signal::SEGV.value).exit_reason.access_violation?.should be_true + end + + it "returns BadMemoryAccess" do + Process::Status.new(Signal::BUS.value).exit_reason.bad_memory_access?.should be_true + end + + it "returns BadInstruction" do + Process::Status.new(Signal::ILL.value).exit_reason.bad_instruction?.should be_true + end + + it "returns FloatException" do + Process::Status.new(Signal::FPE.value).exit_reason.float_exception?.should be_true + end end {% end %} diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 940a03bad1c3..67b82425fad9 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -282,24 +282,45 @@ class Crystal::Command puts "Execute: #{elapsed_time}" end - case status - when .normal_exit? - exit error_on_exit ? 1 : status.exit_code - when .signal_exit? - case signal = status.exit_signal - when .kill? - STDERR.puts "Program was killed" - when .segv? - STDERR.puts "Program exited because of a segmentation fault (11)" - when .int? - # OK, bubbled from the sub-program + if status.exit_reason.normal? && !error_on_exit + exit status.exit_code + end + + if message = exit_message(status) + STDERR.puts message + STDERR.flush + end + + exit 1 + end + + private def exit_message(status) + case status.exit_reason + when .aborted? + if status.signal_exit? + signal = status.exit_signal + if signal.kill? + "Program was killed" + else + "Program received and didn't handle signal #{signal} (#{signal.value})" + end else - STDERR.puts "Program received and didn't handle signal #{signal} (#{signal.value})" + "Program exited abnormally" end - else - STDERR.puts "Program exited abnormally, the cause is unknown" + when .breakpoint? + "Program hit a breakpoint and no debugger was attached" + when .access_violation?, .bad_memory_access? + # NOTE: this only happens with the empty prelude, because the stdlib + # runtime catches those exceptions and then exits _normally_ with exit + # code 11 or 1 + "Program exited because of an invalid memory access" + when .bad_instruction? + "Program exited because of an invalid instruction" + when .float_exception? + "Program exited because of a floating-point system exception" + when .unknown? + "Program exited abnormally, the cause is unknown" end - exit 1 end record CompilerConfig, diff --git a/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr b/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr index 44421fd2c2d0..0d209f7d6773 100644 --- a/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr @@ -1,10 +1,11 @@ require "c/int_safe" +require "c/ntstatus" lib LibC EXCEPTION_CONTINUE_SEARCH = LONG.new!(0) - EXCEPTION_ACCESS_VIOLATION = 0xC0000005_u32 - EXCEPTION_STACK_OVERFLOW = 0xC00000FD_u32 + EXCEPTION_ACCESS_VIOLATION = LibC::STATUS_ACCESS_VIOLATION + EXCEPTION_STACK_OVERFLOW = LibC::STATUS_STACK_OVERFLOW alias PVECTORED_EXCEPTION_HANDLER = EXCEPTION_POINTERS* -> LONG diff --git a/src/lib_c/x86_64-windows-msvc/c/ntstatus.cr b/src/lib_c/x86_64-windows-msvc/c/ntstatus.cr new file mode 100644 index 000000000000..2a013036adb4 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/ntstatus.cr @@ -0,0 +1,17 @@ +require "lib_c" + +lib LibC + STATUS_FATAL_APP_EXIT = 0x40000015_u32 + STATUS_DATATYPE_MISALIGNMENT = 0x80000002_u32 + STATUS_BREAKPOINT = 0x80000003_u32 + STATUS_ACCESS_VIOLATION = 0xC0000005_u32 + STATUS_ILLEGAL_INSTRUCTION = 0xC000001D_u32 + STATUS_FLOAT_DIVIDE_BY_ZERO = 0xC000008E_u32 + STATUS_FLOAT_INEXACT_RESULT = 0xC000008F_u32 + STATUS_FLOAT_INVALID_OPERATION = 0xC0000090_u32 + STATUS_FLOAT_OVERFLOW = 0xC0000091_u32 + STATUS_FLOAT_UNDERFLOW = 0xC0000093_u32 + STATUS_PRIVILEGED_INSTRUCTION = 0xC0000096_u32 + STATUS_STACK_OVERFLOW = 0xC00000FD_u32 + STATUS_CONTROL_C_EXIT = 0xC000013A_u32 +end diff --git a/src/process/status.cr b/src/process/status.cr index d0d9970f45d0..06b6ff103ae1 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -1,3 +1,86 @@ +{% if flag?(:win32) %} + require "c/ntstatus" +{% end %} + +# The reason why a process terminated. +# +# This enum provides a platform-independent way to query any exceptions that +# occurred upon a process's termination, via `Process::Status#exit_reason`. +enum Process::ExitReason + # The process exited normally. + # + # * On Unix-like systems, this implies `Process::Status#normal_exit?` is true. + # * On Windows, only exit statuses less than `0x40000000` are assumed to be + # reserved for normal exits. + Normal + + # The process terminated abnormally. + # + # * On Unix-like systems, this corresponds to `Signal::ABRT`, `Signal::HUP`, + # `Signal::KILL`, `Signal::QUIT`, and `Signal::TERM`. + # * On Windows, this corresponds to the `NTSTATUS` value + # `STATUS_FATAL_APP_EXIT`. + Aborted + + # The process exited due to an interrupt request. + # + # * On Unix-like systems, this corresponds to `Signal::INT`. + # * On Windows, this corresponds to the Ctrl + C and + # Ctrl + Break signals for console applications. + Interrupted + + # The process reached a debugger breakpoint, but no debugger was attached. + # + # * On Unix-like systems, this corresponds to `Signal::TRAP`. + # * On Windows, this corresponds to the `NTSTATUS` value + # `STATUS_BREAKPOINT`. + Breakpoint + + # The process tried to access a memory address where a read or write was not + # allowed. + # + # * On Unix-like systems, this corresponds to `Signal::SEGV`. + # * On Windows, this corresponds to the `NTSTATUS` values + # `STATUS_ACCESS_VIOLATION` and `STATUS_STACK_OVERFLOW`. + AccessViolation + + # The process tried to access an invalid memory address. + # + # * On Unix-like systems, this corresponds to `Signal::BUS`. + # * On Windows, this corresponds to the `NTSTATUS` value + # `STATUS_DATATYPE_MISALIGNMENT`. + BadMemoryAccess + + # The process tried to execute an invalid instruction. + # + # * On Unix-like systems, this corresponds to `Signal::ILL`. + # * On Windows, this corresponds to the `NTSTATUS` values + # `STATUS_ILLEGAL_INSTRUCTION` and `STATUS_PRIVILEGED_INSTRUCTION`. + BadInstruction + + # A hardware floating-point exception occurred. + # + # * On Unix-like systems, this corresponds to `Signal::FPE`. + # * On Windows, this corresponds to the `NTSTATUS` values + # `STATUS_FLOAT_DIVIDE_BY_ZERO`, `STATUS_FLOAT_INEXACT_RESULT`, + # `STATUS_FLOAT_INVALID_OPERATION`, `STATUS_FLOAT_OVERFLOW`, and + # `STATUS_FLOAT_UNDERFLOW`. + FloatException + + # The process exited due to a POSIX signal. + # + # Only applies to signals without a more specific exit reason. Unused on + # Windows. + Signal + + # The process exited in a way that cannot be represented by any other + # `ExitReason`s. + # + # A `Process::Status` that maps to `Unknown` may map to a different value if + # new enum members are added to `ExitReason`. + Unknown +end + # The status of a terminated process. Returned by `Process#wait`. class Process::Status # Platform-specific exit status code, which usually contains either the exit code or a termination signal. @@ -16,11 +99,66 @@ class Process::Status end {% end %} + # Returns a platform-independent reason why the process terminated. + def exit_reason : ExitReason + {% if flag?(:win32) %} + # TODO: perhaps this should cover everything that SEH can handle? + # https://learn.microsoft.com/en-us/windows/win32/debug/getexceptioncode + case @exit_status + when LibC::STATUS_FATAL_APP_EXIT + ExitReason::Aborted + when LibC::STATUS_CONTROL_C_EXIT + ExitReason::Interrupted + when LibC::STATUS_BREAKPOINT + ExitReason::Breakpoint + when LibC::STATUS_ACCESS_VIOLATION, LibC::STATUS_STACK_OVERFLOW + ExitReason::AccessViolation + when LibC::STATUS_DATATYPE_MISALIGNMENT + ExitReason::BadMemoryAccess + when LibC::STATUS_ILLEGAL_INSTRUCTION, LibC::STATUS_PRIVILEGED_INSTRUCTION + ExitReason::BadInstruction + when LibC::STATUS_FLOAT_DIVIDE_BY_ZERO, LibC::STATUS_FLOAT_INEXACT_RESULT, LibC::STATUS_FLOAT_INVALID_OPERATION, LibC::STATUS_FLOAT_OVERFLOW, LibC::STATUS_FLOAT_UNDERFLOW + ExitReason::FloatException + else + @exit_status & 0xC0000000_u32 == 0 ? ExitReason::Normal : ExitReason::Unknown + end + {% elsif flag?(:unix) && !flag?(:wasm32) %} + if normal_exit? + ExitReason::Normal + elsif signal_exit? + case Signal.from_value?(signal_code) + when Nil + ExitReason::Signal + when .abrt?, .hup?, .kill?, .quit?, .term? + ExitReason::Aborted + when .int? + ExitReason::Interrupted + when .trap? + ExitReason::Breakpoint + when .segv? + ExitReason::AccessViolation + when .bus? + ExitReason::BadMemoryAccess + when .ill? + ExitReason::BadInstruction + when .fpe? + ExitReason::FloatException + else + ExitReason::Signal + end + else + # TODO: stop / continue + ExitReason::Unknown + end + {% else %} + raise NotImplementedError.new("Process::Status#exit_reason") + {% end %} + end + # Returns `true` if the process was terminated by a signal. def signal_exit? : Bool {% if flag?(:unix) %} - # define __WIFSIGNALED(status) (((signed char) (((status) & 0x7f) + 1) >> 1) > 0) - ((LibC::SChar.new(@exit_status & 0x7f) + 1) >> 1) > 0 + 0x01 <= (@exit_status & 0x7F) <= 0x7E {% else %} false {% end %} @@ -41,20 +179,13 @@ class Process::Status # # Available only on Unix-like operating systems. def exit_signal : Signal - {% if flag?(:unix) %} + {% if flag?(:unix) && !flag?(:wasm32) %} Signal.from_value(signal_code) {% else %} raise NotImplementedError.new("Process::Status#exit_signal") {% end %} end - {% if flag?(:wasm32) %} - # wasm32 does not define `Signal` - def exit_signal - raise NotImplementedError.new("Process::Status#exit_signal") - end - {% end %} - # If `normal_exit?` is `true`, returns the exit code of the process. def exit_code : Int32 {% if flag?(:unix) %} From 1fe83c64fa6d84e604b78e04e5d2bb700accd358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 16 Feb 2023 16:51:06 +0100 Subject: [PATCH 0322/1551] Switch default regex engine to PCRE2 (#12978) --- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/win.yml | 2 +- Makefile | 2 +- Makefile.win | 2 +- src/regex/engine.cr | 6 +++++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index c75093d01378..ac3ac7c52fb0 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -24,8 +24,8 @@ jobs: - name: Install PCRE2 run: apk add pcre2-dev - name: Assert using PCRE2 - run: bin/crystal eval -Duse_pcre2 'abort unless Regex::Engine == Regex::PCRE2' + run: bin/crystal eval 'abort unless Regex::Engine == Regex::PCRE2' - name: Assert select PCRE run: bin/crystal eval -Duse_pcre 'abort unless Regex::Engine == Regex::PCRE' - name: Run Regex specs - run: bin/crystal spec -Duse_pcre2 --order=random spec/std/regex* + run: bin/crystal spec --order=random spec/std/regex* diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 79ea27e59c74..226281ef2982 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -15,7 +15,7 @@ jobs: make deps - name: Cross-compile Crystal run: | - LLVM_TARGETS=X86 bin/crystal build --cross-compile --target x86_64-pc-windows-msvc src/compiler/crystal.cr -Dwithout_playground -Dwithout_iconv -Dwithout_interpreter + LLVM_TARGETS=X86 bin/crystal build --cross-compile --target x86_64-pc-windows-msvc src/compiler/crystal.cr -Dwithout_playground -Dwithout_iconv -Dwithout_interpreter -Duse_pcre - name: Upload Crystal object file uses: actions/upload-artifact@v3 diff --git a/Makefile b/Makefile index 0f74e8a36205..b3e7dc51d0e5 100644 --- a/Makefile +++ b/Makefile @@ -197,7 +197,7 @@ $(O)/primitives_spec: $(O)/crystal $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(O)/crystal: $(DEPS) $(SOURCES) $(call check_llvm_config) @mkdir -p $(O) - $(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $@ src/compiler/crystal.cr -D without_openssl -D without_zlib + $(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $@ src/compiler/crystal.cr -D without_openssl -D without_zlib -D use_pcre $(LLVM_EXT_OBJ): $(LLVM_EXT_DIR)/llvm_ext.cc $(call check_llvm_config) diff --git a/Makefile.win b/Makefile.win index 7d4bcead4176..d8b97e512051 100644 --- a/Makefile.win +++ b/Makefile.win @@ -190,7 +190,7 @@ $(O)\crystal.exe: $(DEPS) $(SOURCES) $(O)\crystal.res @$(call MKDIR,"$(O)") $(call export_vars) $(call export_build_vars) - .\bin\crystal build $(FLAGS) -o "$(O)\crystal-next.exe" src\compiler\crystal.cr -D without_openssl -D without_zlib -D without_playground --link-flags=/PDBALTPATH:crystal.pdb "--link-flags=$(realpath $(O)\crystal.res)" + .\bin\crystal build $(FLAGS) -o "$(O)\crystal-next.exe" src\compiler\crystal.cr -D without_openssl -D without_zlib -D without_playground -D use_pcre --link-flags=/PDBALTPATH:crystal.pdb "--link-flags=$(realpath $(O)\crystal.res)" $(call MV,"$(O)\crystal-next.exe","$@") $(call MV,"$(O)\crystal-next.pdb","$(O)\crystal.pdb") diff --git a/src/regex/engine.cr b/src/regex/engine.cr index c2a336accd0d..6c0fdc2e6217 100644 --- a/src/regex/engine.cr +++ b/src/regex/engine.cr @@ -1,4 +1,8 @@ -{% if flag?(:use_pcre2) %} +# The following condition ensures that the engine selection respects `-Duse_pcre2`/`-Duse_pcre`, +# and if none is given it tries to check for availability of `libpcre2` with `pkg-config`. +# If `pkg-config` is unavailable, the default is PCRE2. If `pkg-config` is available but +# has no information about a `libpcre2` package, it falls back to PCRE. +{% if flag?(:use_pcre2) || (!flag?(:use_pcre) && (flag?(:win32) || `hash pkg-config 2> /dev/null && (pkg-config --silence-errors --modversion libpcre2-8 || printf %s false) || true` != "false")) %} require "./pcre2" # :nodoc: From ec257b0b67732d1e8f1a71e42fece2df2394ce60 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 12:44:45 +0100 Subject: [PATCH 0323/1551] Update GH Actions (#13075) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/macos.yml | 2 +- .github/workflows/win.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 357eac5f5821..c0c02b5c9969 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -13,7 +13,7 @@ jobs: - name: Download Crystal source uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v18 + - uses: cachix/install-nix-action@v19 with: install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 226281ef2982..3e2936170e58 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -31,7 +31,7 @@ jobs: run: | git config --global core.autocrlf false - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@7315a94840631165970262a99c72cfb48a65d25d # v1.12.0 + uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 - name: Download Crystal source uses: actions/checkout@v3 From 5295ba87e89fd8fc4fc7ee983af1960f75494880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 22 Feb 2023 16:56:40 +0100 Subject: [PATCH 0324/1551] Fix PCRE2 implementation and tests (#13105) --- .github/workflows/aarch64.yml | 2 +- .github/workflows/wasm32.yml | 2 +- .github/workflows/win.yml | 1 + spec/std/regex_spec.cr | 4 +++- src/regex/lib_pcre2.cr | 10 ++++++---- src/regex/pcre2.cr | 13 ++++++++----- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index 1e8afae7672e..729a016130d5 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -36,7 +36,7 @@ jobs: - name: Run stdlib specs uses: docker://jhass/crystal:1.0.0-alpine-build with: - args: make std_spec + args: make std_spec FLAGS=-Duse_pcre aarch64-musl-test-compiler: needs: aarch64-musl-build runs-on: [linux, ARM64] diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 07da5682910c..e29da38f9825 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -36,7 +36,7 @@ jobs: rm wasm32-wasi-libs.tar.gz - name: Build spec/wasm32_std_spec.cr - run: bin/crystal build spec/wasm32_std_spec.cr -o wasm32_std_spec.wasm --target wasm32-wasi + run: bin/crystal build spec/wasm32_std_spec.cr -o wasm32_std_spec.wasm --target wasm32-wasi -Duse_pcre env: CRYSTAL_LIBRARY_PATH: ${{ github.workspace }}/wasm32-wasi-libs diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 3e2936170e58..b41690942f13 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -42,6 +42,7 @@ jobs: with: path: | # openssl and llvm take much longer to build so they are cached separately libs/pcre.lib + libs/pcre2-8.lib libs/iconv.lib libs/gc.lib libs/ffi.lib diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 72f62a3463cd..11213260a030 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -211,7 +211,9 @@ describe "Regex" do {% else %} # Can't use regex literal because the *LIMIT_DEPTH verb is not supported in libpcre (only libpcre2) # and thus the compiler doesn't recognize it. - str.matches?(Regex.new("(*LIMIT_DEPTH=8192)^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$")) + regex = Regex.new("(*LIMIT_DEPTH=8192)^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") + pending! "PCRE2 JIT mode not available." unless regex.@jit + str.matches?(regex) {% end %} # We don't care whether this actually matches or not, it's just to make # sure the engine does not stack overflow with a large string. diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index 0df60a05499b..b81de3399311 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -179,7 +179,7 @@ lib LibPCRE2 fun get_error_message = pcre2_get_error_message_8(errorcode : Int, buffer : UInt8*, bufflen : LibC::SizeT) : Int - fun compile = pcre2_compile_8(pattern : UInt8*, length : LibC::SizeT, options : UInt32, errorcode : LibC::SizeT*, erroroffset : Int*, ccontext : CompileContext*) : Code* + fun compile = pcre2_compile_8(pattern : UInt8*, length : LibC::SizeT, options : UInt32, errorcode : Int*, erroroffset : LibC::SizeT*, ccontext : CompileContext*) : Code* fun code_free = pcre2_code_free_8(code : Code*) : Void type MatchContext = Void* @@ -207,8 +207,10 @@ lib LibPCRE2 fun get_ovector_pointer = pcre2_get_ovector_pointer_8(match_data : MatchData*) : LibC::SizeT* fun get_ovector_count = pcre2_get_ovector_count_8(match_data : MatchData*) : UInt32 - # void *private_malloc(Int, void *); - # void private_free(void *, void *); - fun general_context_create = pcre2_general_context_create_8(private_malloc : Void*, private_free : Void*, memory_data : Void*) : GeneralContext + fun general_context_create = pcre2_general_context_create_8( + private_malloc : LibC::SizeT, Void* -> Void, + private_free : Void*, Void* -> Void, + memory_data : Void* + ) : GeneralContext fun config = pcre2_config_8(what : UInt32, where : Void*) : Int end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 14b64e72546d..98ba7d9b74f3 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -4,6 +4,7 @@ require "crystal/thread_local_value" # :nodoc: module Regex::PCRE2 @re : LibPCRE2::Code* + @jit : Bool # :nodoc: def initialize(*, _source @source : String, _options @options) @@ -11,19 +12,21 @@ module Regex::PCRE2 raise ArgumentError.new(error_message) end - jit_compile + @jit = jit_compile end - private def jit_compile : Nil + private def jit_compile : Bool ret = LibPCRE2.jit_compile(@re, LibPCRE2::JIT_COMPLETE) if ret < 0 case error = LibPCRE2::Error.new(ret) when .jit_badoption? # okay + return false else raise ArgumentError.new("Regex JIT compile error: #{error}") end end + true end protected def self.compile(source, options, &) @@ -135,7 +138,7 @@ module Regex::PCRE2 end class_getter general_context do - LibPCRE2.general_context_create(->(size : LibC::Int, data : Void*) { GC.malloc(size) }.pointer, ->(pointer : Void*, data : Void*) { GC.free(pointer) }.pointer, nil) + LibPCRE2.general_context_create(->(size, _data) { GC.malloc(size) }, ->(pointer, _data) { GC.free(pointer) }, nil) end # Returns a JIT stack that's shared in the current thread. @@ -153,7 +156,7 @@ module Regex::PCRE2 private def match_data(str, byte_index, options) match_data = LibPCRE2.match_data_create_from_pattern(@re, Regex::PCRE2.general_context) match_context = LibPCRE2.match_context_create(nil) - LibPCRE2.jit_stack_assign(match_context, nil, Regex::PCRE2.jit_stack.as(Void*)) + LibPCRE2.jit_stack_assign(match_context, nil, Regex::PCRE2.jit_stack.as(Void*)) if @jit match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, match_context) if match_count < 0 @@ -176,7 +179,7 @@ module Regex::PCRE2 module MatchData # :nodoc: - def initialize(@regex : Regex, @code : LibPCRE2::Code*, @string : String, @pos : Int32, @ovector : UInt64*, @group_size : Int32) + def initialize(@regex : Regex, @code : LibPCRE2::Code*, @string : String, @pos : Int32, @ovector : LibC::SizeT*, @group_size : Int32) end private def byte_range(n, &) From 2ed4cde6eba7988dc2d549bcabdfcbffcae61fad Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Thu, 23 Feb 2023 04:36:42 -0500 Subject: [PATCH 0325/1551] Revert "Handle namespaces within `TypeNode#has_constant?` and `TypeNode#constant`" (#13106) --- spec/compiler/macro/macro_methods_spec.cr | 120 ---------------------- src/compiler/crystal/macros.cr | 49 +-------- src/compiler/crystal/macros/methods.cr | 46 ++++----- 3 files changed, 23 insertions(+), 192 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 2f3c8bde6638..b72f95a31678 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1866,126 +1866,6 @@ module Crystal end end - describe "#constant" do - it "global path" do - assert_type(%( - ID = 10 - - class A - class B - end - end - - {{ A::B.constant("::ID") == 10 ? 1 : 'f' }} - )) { int32 } - end - - it "global path with extra ::" do - assert_macro_error %({{ @type.constant "::::ID" }}), %(Invalid constant name: "::::ID") - end - - it "const within another type from the top level" do - assert_type(%( - class A - class B - ID = 10 - end - end - - {{ @type.constant("A::B::ID") == 10 ? 1 : 'f' }} - )) { int32 } - end - - it "const within another type from the top level with extra ::" do - assert_macro_error %({{ @type.constant "A::::::B::::ID" }}), %(Invalid constant name: "A::::::B::::ID") - end - - it "type within another type from the top level" do - assert_type(%( - class A - class B - end - end - - {{ @type.constant("A::B").class? ? 1 : 'f' }} - )) { int32 } - end - end - - describe "#has_constant?" do - it "global path with extra ::" do - assert_macro_error %({{ @type.has_constant? "::::ID" }}), %(Invalid constant name: "::::ID") - end - - it "global path" do - assert_type(%( - class A - class B - end - end - - {{ A::B.has_constant?("::A") ? 1 : 'f' }} - )) { int32 } - end - - it "type on the top level" do - assert_type(%( - class A - end - - {{ @type.has_constant?("A") ? 1 : 'f' }} - )) { int32 } - end - - it "constant within a type from that type" do - assert_type(%( - class A - ID = 10 - end - - {{ A.has_constant?("ID") ? 1 : 'f' }} - )) { int32 } - end - - it "type within another type" do - assert_type(%( - class A - class B - end - end - - {{ A.has_constant?("B") ? 1 : 'f' }} - )) { int32 } - end - - it "type within another type from the top level" do - assert_type(%( - class A - class B - end - end - - {{ @type.has_constant?("A::B") ? 1 : 'f' }} - )) { int32 } - end - - it "type within another type from the top level with extra ::" do - assert_macro_error %(class A; class B; end; end; {{ @type.has_constant? "A::::::B" }}), %(Invalid constant name: "A::::::B") - end - - it "constant within a nested type from the top level" do - assert_type(%( - class A - class B - ID = 20 - end - end - - {{ @type.has_constant?("A::B::ID") ? 1 : 'f' }} - )) { int32 } - end - end - describe "#abstract?" do it NonGenericModuleType do assert_macro("{{type.abstract?}}", "false") do |program| diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 7a8251e38400..8b5c6845c0b2 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2097,52 +2097,13 @@ module Crystal::Macros # If the constant is a constant (like `A = 1`), then its value # as an `ASTNode` is returned. If the constant is a type, the # type is returned as a `TypeNode`. Otherwise, `NilLiteral` is returned. - # - # ``` - # TOP_VALUE = 123 - # - # module Foo - # ID = 1 - # - # class Bar - # struct Baz - # end - # end - # end - # - # {{ Foo.constant "ID" }} # => 10 - # {{ Foo.constant(:Bar).class? }} # => true - # {{ Foo.constant("Bar").class? }} # => true - # {{ Foo.constant(Bar::Baz.id).struct? }} # => true - # {{ Foo.constant("::TOP_VALUE") }} # => 123 - # ``` - def constant(name : StringLiteral | SymbolLiteral | MacroId) : ASTNode | NilLiteral + def constant(name : StringLiteral | SymbolLiteral | MacroId) : ASTNode end - # Returns `true` if this type has a constant/type with the provided *name*. - # - # ``` - # module Foo - # ID = 1 - # - # module Bar - # module Baz - # end - # end - # end - # - # {{ Foo.has_constant? "ID" }} # => true - # {{ Foo.has_constant? :Bar }} # => true - # {{ Foo.has_constant? "Bar" }} # => true - # {{ Foo.has_constant? Foo::Bar.id }} # => true - # - # {{ @top_level.has_constant? "Foo" }} # => true - # {{ @type.has_constant? "Foo" }} # => true - # {{ @type.has_constant? "Foo::Bar::Baz" }} # => true - # {{ @type.has_constant? "Bar" }} # => false - # {{ @type.has_constant? "Foo::Bar::Biz" }} # => false - # ``` - def has_constant?(name : StringLiteral | SymbolLiteral | MacroId) : BoolLiteral + # Returns `true` if this type has a constant. For example `DEFAULT_OPTIONS` + # (the name you pass to this method is `"DEFAULT_OPTIONS"` or `:DEFAULT_OPTIONS` + # in this cases). + def has_constant?(name : StringLiteral | SymbolLiteral) : BoolLiteral end # Returns the instance methods defined by this type, without including diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 1dc45f34d451..6894d3d60c15 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -158,31 +158,6 @@ module Crystal end end - def interpret_constant(arg, type, name) - case type = type.lookup_path parse_path(arg, name) - when Const - type.value - when Type - TypeNode.new(type) - else - NilLiteral.new - end - end - - def interpret_has_constant?(arg, type, name) - BoolLiteral.new !!type.lookup_path parse_path(arg, name) - end - - private def parse_path(arg, name) : Path - parser = @program.new_parser name - parser.next_token - path = parser.parse_path - parser.check :EOF - @last = path - rescue ex : Crystal::SyntaxException - arg.raise "Invalid constant name: #{name.inspect}" - end - def interpret_parse_type(node) interpret_check_args_toplevel do |arg| arg.accept self @@ -1673,13 +1648,12 @@ module Crystal when "constant" interpret_check_args do |arg| value = arg.to_string("argument to 'TypeNode#constant'") - interpreter.interpret_constant arg, type, value + TypeNode.constant(type, value) end when "has_constant?" interpret_check_args do |arg| value = arg.to_string("argument to 'TypeNode#has_constant?'") - - interpreter.interpret_has_constant? arg, type, value + TypeNode.has_constant?(type, value) end when "methods" interpret_check_args { TypeNode.methods(type) } @@ -1939,6 +1913,22 @@ module Crystal end end + def self.has_constant?(type, name) + BoolLiteral.new(type.types.has_key?(name)) + end + + def self.constant(type, name) + type = type.types[name]? + case type + when Const + type.value + when Type + TypeNode.new(type) + else + NilLiteral.new + end + end + def self.methods(type) defs = [] of ASTNode type.defs.try &.each do |name, metadatas| From 107970e375854ffbabf9f9557e616b311f5312e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 23 Feb 2023 11:25:30 +0100 Subject: [PATCH 0326/1551] Implement `File.tempfile` in Crystal (#12111) Co-authored-by: Quinton Miller --- spec/std/file/tempfile_spec.cr | 71 ++++++++++++++++++++++++ src/crystal/system/file.cr | 37 ++++++++++++ src/crystal/system/unix/file.cr | 31 ++++------- src/crystal/system/wasi/file.cr | 4 -- src/crystal/system/win32/file.cr | 20 +++---- src/file.cr | 5 ++ src/lib_c/aarch64-darwin/c/fcntl.cr | 1 + src/lib_c/aarch64-linux-gnu/c/fcntl.cr | 1 + src/lib_c/aarch64-linux-musl/c/fcntl.cr | 1 + src/lib_c/arm-linux-gnueabihf/c/fcntl.cr | 1 + src/lib_c/i386-linux-gnu/c/fcntl.cr | 1 + src/lib_c/i386-linux-musl/c/fcntl.cr | 1 + src/lib_c/wasm32-wasi/c/fcntl.cr | 1 + src/lib_c/x86_64-darwin/c/fcntl.cr | 1 + src/lib_c/x86_64-dragonfly/c/fcntl.cr | 1 + src/lib_c/x86_64-freebsd/c/fcntl.cr | 1 + src/lib_c/x86_64-linux-gnu/c/fcntl.cr | 1 + src/lib_c/x86_64-linux-musl/c/fcntl.cr | 1 + src/lib_c/x86_64-netbsd/c/fcntl.cr | 1 + src/lib_c/x86_64-openbsd/c/fcntl.cr | 1 + 20 files changed, 146 insertions(+), 36 deletions(-) diff --git a/spec/std/file/tempfile_spec.cr b/spec/std/file/tempfile_spec.cr index 9a69e7466bca..aac486bacf40 100644 --- a/spec/std/file/tempfile_spec.cr +++ b/spec/std/file/tempfile_spec.cr @@ -1,4 +1,34 @@ require "../spec_helper" +require "../../support/tempfile" + +private class TestRNG(T) + include Random + + def initialize(@data : Array(T)) + @i = 0 + end + + def next_u : T + i = @i + @i = (i + 1) % @data.size + @data[i] + end + + def reset + @i = 0 + end +end + +private def normalize_permissions(permissions, *, directory) + {% if flag?(:win32) %} + normalized_permissions = 0o444 + normalized_permissions |= 0o222 if permissions.bits_set?(0o200) + normalized_permissions |= 0o111 if directory + File::Permissions.new(normalized_permissions) + {% else %} + File::Permissions.new(permissions) + {% end %} +end describe File do describe ".tempname" do @@ -42,6 +72,7 @@ describe File do it "creates and writes" do tempfile = File.tempfile tempfile.print "Hello!" + tempfile.info.permissions.should eq normalize_permissions(0o600, directory: false) tempfile.close File.exists?(tempfile.path).should be_true @@ -53,6 +84,7 @@ describe File do it "accepts single suffix argument" do tempfile = File.tempfile ".bar" tempfile.print "Hello!" + tempfile.info.permissions.should eq normalize_permissions(0o600, directory: false) tempfile.close File.extname(tempfile.path).should eq(".bar") @@ -66,6 +98,7 @@ describe File do it "accepts prefix and suffix arguments" do tempfile = File.tempfile "foo", ".bar" tempfile.print "Hello!" + tempfile.info.permissions.should eq normalize_permissions(0o600, directory: false) tempfile.close File.extname(tempfile.path).should eq(".bar") @@ -156,3 +189,41 @@ describe File do end end end + +describe Crystal::System::File do + describe ".mktemp" do + it "creates random file name" do + with_tempfile "random-path" do |tempdir| + Dir.mkdir tempdir + fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14])) + path.should eq Path[tempdir, "A789abcdeZ"].to_s + ensure + File.from_fd(path, fd).close if fd && path + end + end + + it "retries when file exists" do + with_tempfile "retry" do |tempdir| + Dir.mkdir tempdir + existing_path = Path[tempdir, "A789abcdeZ"] + File.touch existing_path + fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22])) + path.should eq File.join(tempdir, "AfghijklmZ") + ensure + File.from_fd(path, fd).close if fd && path + end + end + + it "raises when no valid path is found" do + with_tempfile "random-path" do |tempdir| + Dir.mkdir tempdir + File.touch Path[tempdir, "A789abcdeZ"] + expect_raises(File::AlreadyExistsError, "Error creating temporary file") do + fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14])) + ensure + File.from_fd(path, fd).close if fd && path + end + end + end + end +end diff --git a/src/crystal/system/file.cr b/src/crystal/system/file.cr index 504b578dd1b3..b5202585d377 100644 --- a/src/crystal/system/file.cr +++ b/src/crystal/system/file.cr @@ -47,6 +47,43 @@ module Crystal::System::File m | o end + LOWER_ALPHANUM = "0123456789abcdefghijklmnopqrstuvwxyz".to_slice + + def self.mktemp(prefix : String?, suffix : String?, dir : String, random : ::Random = ::Random::DEFAULT) : {LibC::Int, String} + mode = LibC::O_RDWR | LibC::O_CREAT | LibC::O_EXCL + perm = ::File::Permissions.new(0o600) + + prefix = ::File.join(dir, prefix || "") + bytesize = prefix.bytesize + 8 + (suffix.try(&.bytesize) || 0) + + 100.times do + path = String.build(bytesize) do |io| + io << prefix + 8.times do + io.write_byte LOWER_ALPHANUM.sample(random) + end + io << suffix + end + + fd, errno = open(path, mode, perm) + + if errno.none? + return {fd, path} + elsif error_is_file_exists?(errno) + # retry + next + else + raise ::File::Error.from_os_error("Error creating temporary file", errno, file: path) + end + end + + raise ::File::AlreadyExistsError.new("Error creating temporary file", file: "#{prefix}********#{suffix}") + end + + private def self.error_is_file_exists?(errno) + Errno.value.in?(Errno::EEXIST, WinError::ERROR_ALREADY_EXISTS) + end + # Closes the internal file descriptor without notifying libevent. # This is directly used after the fork of a process to close the # parent's Crystal::Signal.@@pipe reference before re initializing diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index a976b4b08012..1c3314340c6b 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -3,32 +3,25 @@ require "file/error" # :nodoc: module Crystal::System::File - def self.open(filename, mode, perm) - oflag = open_flag(mode) | LibC::O_CLOEXEC + def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions) + perm = ::File::Permissions.new(perm) if perm.is_a? Int32 - fd = LibC.open(filename.check_no_null_byte, oflag, perm) - if fd < 0 - raise ::File::Error.from_errno("Error opening file with mode '#{mode}'", file: filename) + fd, errno = open(filename, open_flag(mode), perm) + + unless errno.none? + raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", errno, file: filename) end + fd end - def self.mktemp(prefix, suffix, dir) : {LibC::Int, String} - prefix.try &.check_no_null_byte - suffix.try &.check_no_null_byte - dir.check_no_null_byte - - dir = dir + ::File::SEPARATOR - path = "#{dir}#{prefix}.XXXXXX#{suffix}" + def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {LibC::Int, Errno} + filename.check_no_null_byte + flags |= LibC::O_CLOEXEC - if suffix - fd = LibC.mkstemps(path, suffix.bytesize) - else - fd = LibC.mkstemp(path) - end + fd = LibC.open(filename, flags, perm) - raise ::File::Error.from_errno("Error creating temporary file", file: path) if fd == -1 - {fd, path} + {fd, fd < 0 ? Errno.value : Errno::NONE} end def self.info?(path : String, follow_symlinks : Bool) : ::File::Info? diff --git a/src/crystal/system/wasi/file.cr b/src/crystal/system/wasi/file.cr index 3f4a0baa06fe..ecf4daf48159 100644 --- a/src/crystal/system/wasi/file.cr +++ b/src/crystal/system/wasi/file.cr @@ -34,10 +34,6 @@ module Crystal::System::File raise NotImplementedError.new "Crystal::System::File#flock" end - def self.mktemp(prefix, suffix, dir) : {LibC::Int, String} - raise NotImplementedError.new "Crystal::System::File.mktemp" - end - def self.delete(path : String, *, raise_on_missing : Bool) : Bool raise NotImplementedError.new "Crystal::System::File.delete" end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index cb5f0b902eb2..225a345723cc 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -9,8 +9,6 @@ require "c/handleapi" module Crystal::System::File def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions) : LibC::Int perm = ::File::Permissions.new(perm) if perm.is_a? Int32 - oflag = open_flag(mode) | LibC::O_BINARY | LibC::O_NOINHERIT - # Only the owner writable bit is used, since windows only supports # the read only attribute. if perm.owner_write? @@ -19,24 +17,20 @@ module Crystal::System::File perm = LibC::S_IREAD end - fd = LibC._wopen(System.to_wstr(filename), oflag, perm) - if fd == -1 - raise ::File::Error.from_errno("Error opening file with mode '#{mode}'", file: filename) + fd, errno = open(filename, open_flag(mode), ::File::Permissions.new(perm)) + unless errno.none? + raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", errno, file: filename) end fd end - def self.mktemp(prefix : String?, suffix : String?, dir : String) : {LibC::Int, String} - path = "#{dir}#{::File::SEPARATOR}#{prefix}.#{::Random::Secure.hex}#{suffix}" + def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {LibC::Int, Errno} + flags |= LibC::O_BINARY | LibC::O_NOINHERIT - mode = LibC::O_RDWR | LibC::O_CREAT | LibC::O_EXCL | LibC::O_BINARY | LibC::O_NOINHERIT - fd = LibC._wopen(System.to_wstr(path), mode, ::File::DEFAULT_CREATE_PERMISSIONS) - if fd == -1 - raise ::File::Error.from_errno("Error creating temporary file", file: path) - end + fd = LibC._wopen(System.to_wstr(filename), flags, perm) - {fd, path} + {fd, fd == -1 ? Errno.value : Errno::NONE} end NOT_FOUND_ERRORS = { diff --git a/src/file.cr b/src/file.cr index 7551a65f34f7..3fd22017e1e0 100644 --- a/src/file.cr +++ b/src/file.cr @@ -94,6 +94,11 @@ class File < IO::FileDescriptor super(fd, blocking) end + # :nodoc: + def self.from_fd(path : String, fd : Int, *, blocking = false, encoding = nil, invalid = nil) + new(path, fd, blocking: blocking, encoding: encoding, invalid: invalid) + end + # Opens the file named by *filename*. # # *mode* must be one of the following file open modes: diff --git a/src/lib_c/aarch64-darwin/c/fcntl.cr b/src/lib_c/aarch64-darwin/c/fcntl.cr index ea632f5512c4..cf6ce527a729 100644 --- a/src/lib_c/aarch64-darwin/c/fcntl.cr +++ b/src/lib_c/aarch64-darwin/c/fcntl.cr @@ -12,6 +12,7 @@ lib LibC O_CREAT = 0x0200 O_NOFOLLOW = 0x0100 O_TRUNC = 0x0400 + O_EXCL = 0x0800 O_APPEND = 0x0008 O_NONBLOCK = 0x0004 O_SYNC = 0x0080 diff --git a/src/lib_c/aarch64-linux-gnu/c/fcntl.cr b/src/lib_c/aarch64-linux-gnu/c/fcntl.cr index 9e74bbc4f536..e52f375d8dc4 100644 --- a/src/lib_c/aarch64-linux-gnu/c/fcntl.cr +++ b/src/lib_c/aarch64-linux-gnu/c/fcntl.cr @@ -10,6 +10,7 @@ lib LibC FD_CLOEXEC = 1 O_CLOEXEC = 0o2000000 O_CREAT = 0o100 + O_EXCL = 0o200 O_NOFOLLOW = 0o100000 O_TRUNC = 0o1000 O_APPEND = 0o2000 diff --git a/src/lib_c/aarch64-linux-musl/c/fcntl.cr b/src/lib_c/aarch64-linux-musl/c/fcntl.cr index 5633ae3241a6..7664c411a36c 100644 --- a/src/lib_c/aarch64-linux-musl/c/fcntl.cr +++ b/src/lib_c/aarch64-linux-musl/c/fcntl.cr @@ -10,6 +10,7 @@ lib LibC FD_CLOEXEC = 1 O_CLOEXEC = 0o2000000 O_CREAT = 0o100 + O_EXCL = 0o200 O_NOFOLLOW = 0o100000 O_TRUNC = 0o1000 O_APPEND = 0o2000 diff --git a/src/lib_c/arm-linux-gnueabihf/c/fcntl.cr b/src/lib_c/arm-linux-gnueabihf/c/fcntl.cr index 9e74bbc4f536..e52f375d8dc4 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/fcntl.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/fcntl.cr @@ -10,6 +10,7 @@ lib LibC FD_CLOEXEC = 1 O_CLOEXEC = 0o2000000 O_CREAT = 0o100 + O_EXCL = 0o200 O_NOFOLLOW = 0o100000 O_TRUNC = 0o1000 O_APPEND = 0o2000 diff --git a/src/lib_c/i386-linux-gnu/c/fcntl.cr b/src/lib_c/i386-linux-gnu/c/fcntl.cr index 2a12d9b4857f..cea8630785da 100644 --- a/src/lib_c/i386-linux-gnu/c/fcntl.cr +++ b/src/lib_c/i386-linux-gnu/c/fcntl.cr @@ -10,6 +10,7 @@ lib LibC FD_CLOEXEC = 1 O_CLOEXEC = 0o2000000 O_CREAT = 0o100 + O_EXCL = 0o200 O_NOFOLLOW = 0o400000 O_TRUNC = 0o1000 O_APPEND = 0o2000 diff --git a/src/lib_c/i386-linux-musl/c/fcntl.cr b/src/lib_c/i386-linux-musl/c/fcntl.cr index 6beab0d6de74..27a5cf0c22d3 100644 --- a/src/lib_c/i386-linux-musl/c/fcntl.cr +++ b/src/lib_c/i386-linux-musl/c/fcntl.cr @@ -10,6 +10,7 @@ lib LibC FD_CLOEXEC = 1 O_CLOEXEC = 0o2000000 O_CREAT = 0o100 + O_EXCL = 0o200 O_NOFOLLOW = 0o400000 O_TRUNC = 0o1000 O_APPEND = 0o2000 diff --git a/src/lib_c/wasm32-wasi/c/fcntl.cr b/src/lib_c/wasm32-wasi/c/fcntl.cr index 7bbfe10f23ec..029a5721cfdb 100644 --- a/src/lib_c/wasm32-wasi/c/fcntl.cr +++ b/src/lib_c/wasm32-wasi/c/fcntl.cr @@ -10,6 +10,7 @@ lib LibC FD_CLOEXEC = 1 O_CLOEXEC = 0 O_CREAT = 1_u16 << 12 + O_EXCL = 4_u16 << 12 O_NOFOLLOW = 0x01000000 O_TRUNC = 8_u16 << 12 O_APPEND = 1_u16 diff --git a/src/lib_c/x86_64-darwin/c/fcntl.cr b/src/lib_c/x86_64-darwin/c/fcntl.cr index ea632f5512c4..cf6ce527a729 100644 --- a/src/lib_c/x86_64-darwin/c/fcntl.cr +++ b/src/lib_c/x86_64-darwin/c/fcntl.cr @@ -12,6 +12,7 @@ lib LibC O_CREAT = 0x0200 O_NOFOLLOW = 0x0100 O_TRUNC = 0x0400 + O_EXCL = 0x0800 O_APPEND = 0x0008 O_NONBLOCK = 0x0004 O_SYNC = 0x0080 diff --git a/src/lib_c/x86_64-dragonfly/c/fcntl.cr b/src/lib_c/x86_64-dragonfly/c/fcntl.cr index ec8962397ac5..c9b832e2e919 100644 --- a/src/lib_c/x86_64-dragonfly/c/fcntl.cr +++ b/src/lib_c/x86_64-dragonfly/c/fcntl.cr @@ -9,6 +9,7 @@ lib LibC F_SETFL = 4 FD_CLOEXEC = 1 O_CLOEXEC = 0x20000 + O_EXCL = 0x0800 O_TRUNC = 0x0400 O_CREAT = 0x0200 O_NOFOLLOW = 0x0100 diff --git a/src/lib_c/x86_64-freebsd/c/fcntl.cr b/src/lib_c/x86_64-freebsd/c/fcntl.cr index b6f2912bad8c..d5c507efac29 100644 --- a/src/lib_c/x86_64-freebsd/c/fcntl.cr +++ b/src/lib_c/x86_64-freebsd/c/fcntl.cr @@ -12,6 +12,7 @@ lib LibC O_CREAT = 0x0200 O_NOFOLLOW = 0x0100 O_TRUNC = 0x0400 + O_EXCL = 0x0800 O_APPEND = 0x0008 O_NONBLOCK = 0x0004 O_SYNC = 0x0080 diff --git a/src/lib_c/x86_64-linux-gnu/c/fcntl.cr b/src/lib_c/x86_64-linux-gnu/c/fcntl.cr index 1a8fd2f1787b..7f46cb647918 100644 --- a/src/lib_c/x86_64-linux-gnu/c/fcntl.cr +++ b/src/lib_c/x86_64-linux-gnu/c/fcntl.cr @@ -10,6 +10,7 @@ lib LibC FD_CLOEXEC = 1 O_CLOEXEC = 0o2000000 O_CREAT = 0o100 + O_EXCL = 0o200 O_NOFOLLOW = 0o400000 O_TRUNC = 0o1000 O_APPEND = 0o2000 diff --git a/src/lib_c/x86_64-linux-musl/c/fcntl.cr b/src/lib_c/x86_64-linux-musl/c/fcntl.cr index 6beab0d6de74..27a5cf0c22d3 100644 --- a/src/lib_c/x86_64-linux-musl/c/fcntl.cr +++ b/src/lib_c/x86_64-linux-musl/c/fcntl.cr @@ -10,6 +10,7 @@ lib LibC FD_CLOEXEC = 1 O_CLOEXEC = 0o2000000 O_CREAT = 0o100 + O_EXCL = 0o200 O_NOFOLLOW = 0o400000 O_TRUNC = 0o1000 O_APPEND = 0o2000 diff --git a/src/lib_c/x86_64-netbsd/c/fcntl.cr b/src/lib_c/x86_64-netbsd/c/fcntl.cr index 76ff615bef36..3a1ffe9d85c6 100644 --- a/src/lib_c/x86_64-netbsd/c/fcntl.cr +++ b/src/lib_c/x86_64-netbsd/c/fcntl.cr @@ -12,6 +12,7 @@ lib LibC O_CREAT = 0x0200 O_NOFOLLOW = 0x0100 O_TRUNC = 0x0400 + O_EXCL = 0x0800 O_APPEND = 0x0008 O_NONBLOCK = 0x0004 O_SYNC = 0x0080 diff --git a/src/lib_c/x86_64-openbsd/c/fcntl.cr b/src/lib_c/x86_64-openbsd/c/fcntl.cr index ec28cf280dce..6de726e50bf5 100644 --- a/src/lib_c/x86_64-openbsd/c/fcntl.cr +++ b/src/lib_c/x86_64-openbsd/c/fcntl.cr @@ -12,6 +12,7 @@ lib LibC O_CREAT = 0x0200 O_NOFOLLOW = 0x0100 O_TRUNC = 0x0400 + O_EXCL = 0x0800 O_APPEND = 0x0008 O_NONBLOCK = 0x0004 O_SYNC = 0x0080 From 072772893bfea8c0a7469233aeb250be3e13ba8a Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 23 Feb 2023 14:16:37 +0100 Subject: [PATCH 0327/1551] Add missing require for `Crystal::ThreadLocalValue` (#13092) --- src/reference.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/reference.cr b/src/reference.cr index 4f5f9f5571e3..a9dee7109059 100644 --- a/src/reference.cr +++ b/src/reference.cr @@ -1,3 +1,7 @@ +{% if flag?(:preview_mt) %} + require "crystal/thread_local_value" +{% end %} + # `Reference` is the base class of classes you define in your program. # It is set as a class' superclass when you don't specify one: # From 807058a42bb7f0f624a4bb26a2daa95b94747482 Mon Sep 17 00:00:00 2001 From: Julien Reichardt Date: Fri, 24 Feb 2023 08:23:19 +0100 Subject: [PATCH 0328/1551] Use exhaustive case in `HTTP::WebSocket#run` (#13097) --- src/http/web_socket.cr | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/http/web_socket.cr b/src/http/web_socket.cr index 79d9bca38336..0e4008c7b7eb 100644 --- a/src/http/web_socket.cr +++ b/src/http/web_socket.cr @@ -140,7 +140,7 @@ class HTTP::WebSocket end case info.opcode - when .ping? + in .ping? @current_message.write @buffer[0, info.size] if info.final message = @current_message.to_s @@ -148,25 +148,25 @@ class HTTP::WebSocket pong(message) unless closed? @current_message.clear end - when .pong? + in .pong? @current_message.write @buffer[0, info.size] if info.final @on_pong.try &.call(@current_message.to_s) @current_message.clear end - when .text? + in .text? @current_message.write @buffer[0, info.size] if info.final @on_message.try &.call(@current_message.to_s) @current_message.clear end - when .binary? + in .binary? @current_message.write @buffer[0, info.size] if info.final @on_binary.try &.call(@current_message.to_slice) @current_message.clear end - when .close? + in .close? @current_message.write @buffer[0, info.size] if info.final @current_message.rewind @@ -185,7 +185,7 @@ class HTTP::WebSocket @current_message.clear break end - when Protocol::Opcode::CONTINUATION + in .continuation? # TODO: (asterite) I think this is good, but this case wasn't originally handled end end From 94c1f02d64ff8da176e82739321e911fc921dc9d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 25 Feb 2023 22:17:44 +0900 Subject: [PATCH 0329/1551] Do not match expectations outside specs (#13079) --- spec/compiler/loader/spec_helper.cr | 6 ++- spec/std/math_spec.cr | 4 +- spec/std/number_spec.cr | 72 ++++++++++++++--------------- spec/std/signal_spec.cr | 4 +- spec/std/uri/params_spec.cr | 2 +- 5 files changed, 46 insertions(+), 42 deletions(-) diff --git a/spec/compiler/loader/spec_helper.cr b/spec/compiler/loader/spec_helper.cr index fe3f36e6b2e9..74285076bdc5 100644 --- a/spec/compiler/loader/spec_helper.cr +++ b/spec/compiler/loader/spec_helper.cr @@ -7,8 +7,10 @@ def build_c_dynlib(c_filename, target_dir = SPEC_CRYSTAL_LOADER_LIB_PATH) {% if flag?(:msvc) %} o_basename = o_filename.rchop(".lib") - `cl.exe /nologo /LD #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_basename}")} #{Process.quote("/Fe#{o_basename}")}`.should be_truthy + `cl.exe /nologo /LD #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_basename}")} #{Process.quote("/Fe#{o_basename}")}` {% else %} - `#{ENV["CC"]? || "cc"} -shared -fvisibility=hidden #{Process.quote(c_filename)} -o #{Process.quote(o_filename)}`.should be_truthy + `#{ENV["CC"]? || "cc"} -shared -fvisibility=hidden #{Process.quote(c_filename)} -o #{Process.quote(o_filename)}` {% end %} + + raise "BUG: failed to compile dynamic library" unless $?.success? end diff --git a/spec/std/math_spec.cr b/spec/std/math_spec.cr index 51c945ca3453..29a13c994c42 100644 --- a/spec/std/math_spec.cr +++ b/spec/std/math_spec.cr @@ -22,8 +22,8 @@ describe "Math" do end describe "Order-related functions" do - Math.min(2.1, 2.11).should eq(2.1) - Math.max(3.2, 3.11).should eq(3.2) + it { Math.min(2.1, 2.11).should eq(2.1) } + it { Math.max(3.2, 3.11).should eq(3.2) } end pending "Functions for computing quotient and remainder" do diff --git a/spec/std/number_spec.cr b/spec/std/number_spec.cr index 0b703d7199d0..9f67001d9f8a 100644 --- a/spec/std/number_spec.cr +++ b/spec/std/number_spec.cr @@ -306,45 +306,45 @@ describe "Number" do end describe "#round_even" do - -2.5.round_even.should eq -2.0 - -1.5.round_even.should eq -2.0 - -1.0.round_even.should eq -1.0 - -0.9.round_even.should eq -1.0 - -0.5.round_even.should eq -0.0 - -0.1.round_even.should eq 0.0 - 0.0.round_even.should eq 0.0 - 0.1.round_even.should eq 0.0 - 0.5.round_even.should eq 0.0 - 0.9.round_even.should eq 1.0 - 1.0.round_even.should eq 1.0 - 1.5.round_even.should eq 2.0 - 2.5.round_even.should eq 2.0 - - 1.round_even.should eq 1 - 1.round_even.should be_a(Int32) - 1_u8.round_even.should be_a(UInt8) - 1_f32.round_even.should be_a(Float32) + it { -2.5.round_even.should eq -2.0 } + it { -1.5.round_even.should eq -2.0 } + it { -1.0.round_even.should eq -1.0 } + it { -0.9.round_even.should eq -1.0 } + it { -0.5.round_even.should eq -0.0 } + it { -0.1.round_even.should eq 0.0 } + it { 0.0.round_even.should eq 0.0 } + it { 0.1.round_even.should eq 0.0 } + it { 0.5.round_even.should eq 0.0 } + it { 0.9.round_even.should eq 1.0 } + it { 1.0.round_even.should eq 1.0 } + it { 1.5.round_even.should eq 2.0 } + it { 2.5.round_even.should eq 2.0 } + + it { 1.round_even.should eq 1 } + it { 1.round_even.should be_a(Int32) } + it { 1_u8.round_even.should be_a(UInt8) } + it { 1_f32.round_even.should be_a(Float32) } end describe "#round_away" do - -2.5.round_away.should eq -3.0 - -1.5.round_away.should eq -2.0 - -1.0.round_away.should eq -1.0 - -0.9.round_away.should eq -1.0 - -0.5.round_away.should eq -1.0 - -0.1.round_away.should eq 0.0 - 0.0.round_away.should eq 0.0 - 0.1.round_away.should eq 0.0 - 0.5.round_away.should eq 1.0 - 0.9.round_away.should eq 1.0 - 1.0.round_away.should eq 1.0 - 1.5.round_away.should eq 2.0 - 2.5.round_away.should eq 3.0 - - 1.round_away.should eq 1 - 1.round_away.should be_a(Int32) - 1_u8.round_away.should be_a(UInt8) - 1_f32.round_away.should be_a(Float32) + it { -2.5.round_away.should eq -3.0 } + it { -1.5.round_away.should eq -2.0 } + it { -1.0.round_away.should eq -1.0 } + it { -0.9.round_away.should eq -1.0 } + it { -0.5.round_away.should eq -1.0 } + it { -0.1.round_away.should eq 0.0 } + it { 0.0.round_away.should eq 0.0 } + it { 0.1.round_away.should eq 0.0 } + it { 0.5.round_away.should eq 1.0 } + it { 0.9.round_away.should eq 1.0 } + it { 1.0.round_away.should eq 1.0 } + it { 1.5.round_away.should eq 2.0 } + it { 2.5.round_away.should eq 3.0 } + + it { 1.round_away.should eq 1 } + it { 1.round_away.should be_a(Int32) } + it { 1_u8.round_away.should be_a(UInt8) } + it { 1_f32.round_away.should be_a(Float32) } end it "gives the absolute value" do diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr index 60e741e0b44c..7183a544e648 100644 --- a/spec/std/signal_spec.cr +++ b/spec/std/signal_spec.cr @@ -40,15 +40,17 @@ describe "Signal" do it "CHLD.trap is called after default Crystal child handler" do chan = Channel(Process).new + existed = Channel(Bool).new Signal::CHLD.trap do child_process = chan.receive - Process.exists?(child_process.pid).should be_false + existed.send(Process.exists?(child_process.pid)) end child = Process.new("true", shell: true) child.wait # doesn't block forever chan.send(child) + existed.receive.should be_false ensure Signal::CHLD.reset end diff --git a/spec/std/uri/params_spec.cr b/spec/std/uri/params_spec.cr index b49c40b57e8c..0acd647617c5 100644 --- a/spec/std/uri/params_spec.cr +++ b/spec/std/uri/params_spec.cr @@ -4,7 +4,7 @@ require "uri/params" class URI describe Params do describe ".new" do - Params.new.should eq(Params.parse("")) + it { Params.new.should eq(Params.parse("")) } end describe ".parse" do From dcd98c1102241979195ebd311bd5b93512b95493 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Tue, 28 Feb 2023 05:59:03 -0300 Subject: [PATCH 0330/1551] CI: Enable testing with `libpcre2` on wasm32 (#13109) --- .github/workflows/wasm32.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index e29da38f9825..51789ecc44b4 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -30,13 +30,13 @@ jobs: - name: Download wasm32 libs run: | mkdir wasm32-wasi-libs - curl -LO https://github.com/lbguilherme/wasm-libs/releases/download/0.0.2/wasm32-wasi-libs.tar.gz - echo "114dd08b776c92e15b4ec83178fa486dc436e24b7f662c3241a8cdf2506fe426 wasm32-wasi-libs.tar.gz" | sha256sum -c - + curl -LO https://github.com/lbguilherme/wasm-libs/releases/download/0.0.3/wasm32-wasi-libs.tar.gz + echo "cd36f319f8f9f9cd08f723d10e6ec2b92f2e44d3ce3b20344b8041386d85c261 wasm32-wasi-libs.tar.gz" | sha256sum -c - tar -f wasm32-wasi-libs.tar.gz -C wasm32-wasi-libs -xz rm wasm32-wasi-libs.tar.gz - name: Build spec/wasm32_std_spec.cr - run: bin/crystal build spec/wasm32_std_spec.cr -o wasm32_std_spec.wasm --target wasm32-wasi -Duse_pcre + run: bin/crystal build spec/wasm32_std_spec.cr -o wasm32_std_spec.wasm --target wasm32-wasi env: CRYSTAL_LIBRARY_PATH: ${{ github.workspace }}/wasm32-wasi-libs From 43d769d3b00ca6fa77989a4e9097ad1e600e0ca1 Mon Sep 17 00:00:00 2001 From: Julien Reichardt Date: Tue, 28 Feb 2023 16:41:00 +0100 Subject: [PATCH 0331/1551] Clarify WebSocket documentation (#13096) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller Co-authored-by: Quinton Miller --- src/http/web_socket.cr | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/http/web_socket.cr b/src/http/web_socket.cr index 0e4008c7b7eb..54724d9b7bbd 100644 --- a/src/http/web_socket.cr +++ b/src/http/web_socket.cr @@ -51,23 +51,25 @@ class HTTP::WebSocket new(Protocol.new(host, path, port, tls, headers)) end - # Called when the server sends a ping to a client. + # Called when a PING frame is received. def on_ping(&@on_ping : String ->) end - # Called when the server receives a pong from a client. + # Called when a PONG frame is received. + # + # An unsolicited PONG frame should not be responded to. def on_pong(&@on_pong : String ->) end - # Called when the server receives a text message from a client. + # Called when a text message is received. def on_message(&@on_message : String ->) end - # Called when the server receives a binary message from a client. + # Called when a binary message is received. def on_binary(&@on_binary : Bytes ->) end - # Called when the server closes a client's connection. + # Called when the connection is closed by the other party. def on_close(&@on_close : CloseCode, String ->) end @@ -75,25 +77,21 @@ class HTTP::WebSocket raise IO::Error.new "Closed socket" if closed? end - # Sends a message payload (message) to the client. + # Sends a message payload (message). def send(message) : Nil check_open @ws.send(message) end - # It's possible to send a PING frame, which the client must respond to - # with a PONG, or the server can send an unsolicited PONG frame - # which the client should not respond to. + # Sends a PING frame. Received pings will call `#on_ping`. # - # See `#pong`. + # The receiving party must respond with a PONG. def ping(message = nil) check_open @ws.ping(message) end - # Server can send an unsolicited PONG frame which the client should not respond to. - # - # See `#ping`. + # Sends a PONG frame, which must be in response to a previously received PING frame from `#on_ping`. def pong(message = nil) : Nil check_open @ws.pong(message) @@ -106,7 +104,7 @@ class HTTP::WebSocket end end - # Sends a close frame to the client, and closes the connection. + # Sends a close frame, and closes the connection. # The close frame may contain a body (message) that indicates the reason for closing. def close(code : CloseCode | Int? = nil, message = nil) : Nil return if closed? From 820b7afb616d4aca5bbdc989ee067bbc5cb01154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 28 Feb 2023 18:24:33 +0100 Subject: [PATCH 0332/1551] Build the compiler with PCRE2 (#13084) Co-authored-by: Sijawusz Pur Rahnama --- .github/workflows/linux.yml | 12 ++++++++++-- .github/workflows/win.yml | 4 ++-- Makefile | 4 +++- Makefile.win | 2 +- bin/ci | 1 + shell.nix | 2 +- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index fdf243f7ebeb..f11d5b13b954 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -15,13 +15,21 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.1, 1.6.2, 1.7.2] + crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.1, 1.6.2] + env: + USE_PCRE1: true include: # libffi is only available starting from the 1.2.2 build images - crystal_bootstrap_version: 1.0.0 flags: -Dwithout_ffi + env: + USE_PCRE1: true - crystal_bootstrap_version: 1.1.1 flags: -Dwithout_ffi + env: + USE_PCRE1: true + - crystal_bootstrap_version: 1.7.2 + env: "" steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -33,7 +41,7 @@ jobs: run: bin/ci prepare_build - name: Test - run: FLAGS=${{ matrix.flags }} bin/ci build + run: FLAGS=${{ matrix.flags }} ${{ matrix.env }} bin/ci build x86_64-musl-test: env: diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index b41690942f13..0b93e89f0b38 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -15,7 +15,7 @@ jobs: make deps - name: Cross-compile Crystal run: | - LLVM_TARGETS=X86 bin/crystal build --cross-compile --target x86_64-pc-windows-msvc src/compiler/crystal.cr -Dwithout_playground -Dwithout_iconv -Dwithout_interpreter -Duse_pcre + LLVM_TARGETS=X86 bin/crystal build --cross-compile --target x86_64-pc-windows-msvc src/compiler/crystal.cr -Dwithout_playground -Dwithout_iconv -Dwithout_interpreter -Duse_pcre2 - name: Upload Crystal object file uses: actions/upload-artifact@v3 @@ -333,7 +333,7 @@ jobs: run: make -f Makefile.win deps - name: Link Crystal executable run: | - Invoke-Expression "cl crystal.obj /Fecrystal src\llvm\ext\llvm_ext.obj $(llvm\bin\llvm-config.exe --libs) libs\pcre.lib libs\gc.lib WS2_32.lib advapi32.lib libcmt.lib dbghelp.lib ole32.lib shell32.lib legacy_stdio_definitions.lib /link /LIBPATH:$(pwd)\libs /STACK:0x800000 /ENTRY:wmainCRTStartup" + Invoke-Expression "cl crystal.obj /Fecrystal src\llvm\ext\llvm_ext.obj $(llvm\bin\llvm-config.exe --libs) libs\pcre2-8.lib libs\gc.lib WS2_32.lib advapi32.lib libcmt.lib dbghelp.lib ole32.lib shell32.lib legacy_stdio_definitions.lib /link /LIBPATH:$(pwd)\libs /STACK:0x800000 /ENTRY:wmainCRTStartup" mkdir .build mv crystal.exe .build/ diff --git a/Makefile b/Makefile index b3e7dc51d0e5..e01fe5facbdd 100644 --- a/Makefile +++ b/Makefile @@ -197,7 +197,9 @@ $(O)/primitives_spec: $(O)/crystal $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(O)/crystal: $(DEPS) $(SOURCES) $(call check_llvm_config) @mkdir -p $(O) - $(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $@ src/compiler/crystal.cr -D without_openssl -D without_zlib -D use_pcre + # NOTE: USE_PCRE1 is only used for testing compatibility with legacy environments that don't provide libpcre2. + # Newly built compilers should never be distributed with libpcre to ensure syntax consistency. + $(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $@ src/compiler/crystal.cr -D without_openssl -D without_zlib $(if $(USE_PCRE1),-D use_pcre,-D use_pcre2) $(LLVM_EXT_OBJ): $(LLVM_EXT_DIR)/llvm_ext.cc $(call check_llvm_config) diff --git a/Makefile.win b/Makefile.win index d8b97e512051..78cf4bf21d10 100644 --- a/Makefile.win +++ b/Makefile.win @@ -190,7 +190,7 @@ $(O)\crystal.exe: $(DEPS) $(SOURCES) $(O)\crystal.res @$(call MKDIR,"$(O)") $(call export_vars) $(call export_build_vars) - .\bin\crystal build $(FLAGS) -o "$(O)\crystal-next.exe" src\compiler\crystal.cr -D without_openssl -D without_zlib -D without_playground -D use_pcre --link-flags=/PDBALTPATH:crystal.pdb "--link-flags=$(realpath $(O)\crystal.res)" + .\bin\crystal build $(FLAGS) -o "$(O)\crystal-next.exe" src\compiler\crystal.cr -D without_openssl -D without_zlib -D without_playground $(if $(USE_PCRE1),-D use_pcre,-D use_pcre2) --link-flags=/PDBALTPATH:crystal.pdb "--link-flags=$(realpath $(O)\crystal.res)" $(call MV,"$(O)\crystal-next.exe","$@") $(call MV,"$(O)\crystal-next.pdb","$(O)\crystal.pdb") diff --git a/bin/ci b/bin/ci index 60bec5b85088..d4211fd2be90 100755 --- a/bin/ci +++ b/bin/ci @@ -211,6 +211,7 @@ with_build_env() { -w /mnt \ -e CRYSTAL_CACHE_DIR="/tmp/crystal" \ -e SPEC_SPLIT_DOTS \ + -e USE_PCRE1 \ "$DOCKER_TEST_IMAGE" \ "$ARCH_CMD" /bin/sh -c "'$command'" diff --git a/shell.nix b/shell.nix index deab6682f02d..85f781bb0fe3 100644 --- a/shell.nix +++ b/shell.nix @@ -128,7 +128,7 @@ let }; stdLibDeps = with pkgs; [ - boehmgc gmp libevent libiconv libxml2 libyaml openssl pcre zlib + boehmgc gmp libevent libiconv libxml2 libyaml openssl pcre2 zlib ] ++ lib.optionals stdenv.isDarwin [ libiconv ]; tools = [ pkgs.hostname pkgs.git llvm_suite.extra ]; From 448b4d080fa404d8f848821912c99fdc28f6dcf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 28 Feb 2023 20:51:55 +0100 Subject: [PATCH 0333/1551] Prefer matching `llvm-config` in `find-llvm-config` (#13087) --- src/llvm/ext/find-llvm-config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llvm/ext/find-llvm-config b/src/llvm/ext/find-llvm-config index 9d42b7381ed4..40be636e1b23 100755 --- a/src/llvm/ext/find-llvm-config +++ b/src/llvm/ext/find-llvm-config @@ -4,13 +4,13 @@ if ! LLVM_CONFIG=$(command -v "$LLVM_CONFIG"); then llvm_config_version=$(llvm-config --version 2>/dev/null) for version in $(cat "$(dirname $0)/llvm-versions.txt"); do LLVM_CONFIG=$( + ([ "${llvm_config_version#$version}" != "$llvm_config_version" ] && command -v llvm-config) || \ command -v llvm-config-${version%.*} || \ command -v llvm-config-$version || \ command -v llvm-config${version%.*}${version#*.} || \ command -v llvm-config${version%.*} || \ command -v llvm-config$version || \ - command -v llvm${version%.*}-config || \ - [ "${llvm_config_version#$version}" = "$llvm_config_version" ] || command -v llvm-config) + command -v llvm${version%.*}-config) [ "$LLVM_CONFIG" ] && break done fi From d747d2120b9331c537418ed74032570e0fbb4de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 28 Feb 2023 21:34:40 +0100 Subject: [PATCH 0334/1551] Run compiler specs in release mode (#13122) --- Makefile | 2 +- Makefile.win | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e01fe5facbdd..a992cdc66643 100644 --- a/Makefile +++ b/Makefile @@ -188,7 +188,7 @@ $(O)/std_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(O)/compiler_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(call check_llvm_config) @mkdir -p $(O) - $(EXPORT_CC) $(EXPORTS) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler_spec.cr + $(EXPORT_CC) $(EXPORTS) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler_spec.cr --release $(O)/primitives_spec: $(O)/crystal $(DEPS) $(SOURCES) $(SPEC_SOURCES) @mkdir -p $(O) diff --git a/Makefile.win b/Makefile.win index 78cf4bf21d10..be012b422db8 100644 --- a/Makefile.win +++ b/Makefile.win @@ -179,7 +179,7 @@ $(O)\compiler_spec.exe: $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(call check_llvm_config) @$(call MKDIR,"$(O)") $(call export_vars) - .\bin\crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o "$@" spec\compiler_spec.cr + .\bin\crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o "$@" spec\compiler_spec.cr --release $(O)\primitives_spec.exe: $(O)\crystal.exe $(DEPS) $(SOURCES) $(SPEC_SOURCES) @$(call MKDIR,"$(O)") From f4e258e518fd57c7f226fe73453c06e95b54c973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 1 Mar 2023 13:04:37 +0100 Subject: [PATCH 0335/1551] Fix syntax error in Linux CI (#13133) --- .github/workflows/linux.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f11d5b13b954..4b4c5cc04ce2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -16,20 +16,15 @@ jobs: strategy: matrix: crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.1, 1.6.2] - env: - USE_PCRE1: true + flags: ["USE_PCRE1=true"] include: # libffi is only available starting from the 1.2.2 build images - crystal_bootstrap_version: 1.0.0 - flags: -Dwithout_ffi - env: - USE_PCRE1: true + flags: "FLAGS=-Dwithout_ffi USE_PCRE1=true" - crystal_bootstrap_version: 1.1.1 - flags: -Dwithout_ffi - env: - USE_PCRE1: true + flags: "FLAGS=-Dwithout_ffi USE_PCRE1=true" - crystal_bootstrap_version: 1.7.2 - env: "" + flags: "" steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -41,7 +36,7 @@ jobs: run: bin/ci prepare_build - name: Test - run: FLAGS=${{ matrix.flags }} ${{ matrix.env }} bin/ci build + run: ${{ matrix.flags }} bin/ci build x86_64-musl-test: env: From 66372ecc2b7b0326da835cb6580969710fe5da86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 1 Mar 2023 14:27:10 +0100 Subject: [PATCH 0336/1551] Update distribution-scripts (#13068) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 27d980b883cd..ae6750ed1c11 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "b668df09ead5cbad9fdbff8ba3c5c0878fc99cbe" + default: "dd0ef752b995c04b5e3bf80e474881ba107e4476" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From 13a3d8446872b8a6407bd9da08e83934698299ef Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 2 Mar 2023 18:29:57 +0900 Subject: [PATCH 0337/1551] Formatter: add `(&)` to param-less yielding defs before comment line (#13126) --- spec/compiler/formatter/formatter_spec.cr | 12 ++++++++++++ src/compiler/crystal/tools/formatter.cr | 8 +++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index aa012b32a5f3..bba24f11a90e 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -587,6 +587,18 @@ describe Crystal::Formatter do end CRYSTAL + # #13091 + assert_format <<-CRYSTAL, + def foo # bar + yield + end + CRYSTAL + <<-CRYSTAL + def foo(&) # bar + yield + end + CRYSTAL + assert_format <<-CRYSTAL, def foo(x) yield diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 16688fa4331e..e69b98161d20 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1466,6 +1466,13 @@ module Crystal indent do next_token + + # this formats `def foo # ...` to `def foo(&) # ...` for yielding + # methods before consuming the comment line + if node.block_arity && node.args.empty? && !node.block_arg && !node.double_splat + write "(&)" + end + skip_space consume_newline: false next_token_skip_space if @token.type.op_eq? end @@ -1517,7 +1524,6 @@ module Crystal def format_def_args(args : Array, block_arg, splat_index, variadic, double_splat, yields) # If there are no args, remove extra "()" if args.empty? && !block_arg && !double_splat && !variadic - write "(&)" if yields if @token.type.op_lparen? next_token_skip_space_or_newline check :OP_RPAREN From 9eab606d7d10bf637bc39399e7874f438231b36f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 2 Mar 2023 18:49:52 +0900 Subject: [PATCH 0338/1551] Explicitly treat unbound type vars in generic class methods as free variables (#13125) --- spec/compiler/semantic/restrictions_spec.cr | 12 ++++++++++++ src/compiler/crystal/semantic/match.cr | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/restrictions_spec.cr b/spec/compiler/semantic/restrictions_spec.cr index 14c81e618ce2..b8149884ffeb 100644 --- a/spec/compiler/semantic/restrictions_spec.cr +++ b/spec/compiler/semantic/restrictions_spec.cr @@ -1221,6 +1221,18 @@ describe "Restrictions" do CR end + it "sets number as unbound generic type var (#13110)" do + assert_type(<<-CR) { generic_class "Foo", 1.int32 } + class Foo(T) + def self.foo(x : Foo(T)) + x + end + end + + Foo.foo(Foo(1).new) + CR + end + it "restricts aliased typedef type (#9474)" do assert_type(%( lib A diff --git a/src/compiler/crystal/semantic/match.cr b/src/compiler/crystal/semantic/match.cr index f18572f69291..22f643ca393a 100644 --- a/src/compiler/crystal/semantic/match.cr +++ b/src/compiler/crystal/semantic/match.cr @@ -69,7 +69,9 @@ module Crystal def has_def_free_var?(name) return false if get_free_var(name) - !!(@def_free_vars.try &.includes?(name)) + return true if @def_free_vars.try &.includes?(name) + + defining_type.metaclass? && defining_type.type_var?(name) end # Returns the type that corresponds to using `self` when looking From 458f625c3332d1c87e15668957d9db60a56012f1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 11 Feb 2023 06:51:55 +0800 Subject: [PATCH 0339/1551] Do not use `@[ThreadLocal]` for PCRE2's JIT stack (#13056) --- src/regex/pcre2.cr | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 176a479bbb57..cbec9ca3c6d3 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -1,4 +1,5 @@ require "./lib_pcre2" +require "crystal/thread_local_value" # :nodoc: module Regex::PCRE2 @@ -141,13 +142,12 @@ module Regex::PCRE2 # # Only a single `match` function can run per thread at any given time, so there # can't be any concurrent access to the JIT stack. - @[ThreadLocal] - class_getter jit_stack : LibPCRE2::JITStack do - jit_stack = LibPCRE2.jit_stack_create(32_768, 1_048_576, Regex::PCRE2.general_context) - if jit_stack.null? - raise "Error allocating JIT stack" + @@jit_stack = Crystal::ThreadLocalValue(LibPCRE2::JITStack).new + + def self.jit_stack + @@jit_stack.get do + LibPCRE2.jit_stack_create(32_768, 1_048_576, general_context) || raise "Error allocating JIT stack" end - jit_stack end private def match_data(str, byte_index, options) From eea669c8917ce8951330499e9d7248e675e9fdae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 22 Feb 2023 09:35:44 +0100 Subject: [PATCH 0340/1551] Fix `libpcre2` bindings with arch-dependent types (`SizeT`) (#13088) --- .github/workflows/wasm32.yml | 2 +- src/regex/lib_pcre2.cr | 2 +- src/regex/pcre2.cr | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 3dc4cf35e468..d98414608153 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -36,7 +36,7 @@ jobs: rm wasm32-wasi-libs.tar.gz - name: Build spec/wasm32_std_spec.cr - run: bin/crystal build spec/wasm32_std_spec.cr -o wasm32_std_spec.wasm --target wasm32-wasi + run: bin/crystal build spec/wasm32_std_spec.cr -o wasm32_std_spec.wasm --target wasm32-wasi -Duse_pcre env: CRYSTAL_LIBRARY_PATH: ${{ github.workspace }}/wasm32-wasi-libs diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index 0df60a05499b..bb2a8939ca01 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -179,7 +179,7 @@ lib LibPCRE2 fun get_error_message = pcre2_get_error_message_8(errorcode : Int, buffer : UInt8*, bufflen : LibC::SizeT) : Int - fun compile = pcre2_compile_8(pattern : UInt8*, length : LibC::SizeT, options : UInt32, errorcode : LibC::SizeT*, erroroffset : Int*, ccontext : CompileContext*) : Code* + fun compile = pcre2_compile_8(pattern : UInt8*, length : LibC::SizeT, options : UInt32, errorcode : Int*, erroroffset : LibC::SizeT*, ccontext : CompileContext*) : Code* fun code_free = pcre2_code_free_8(code : Code*) : Void type MatchContext = Void* diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index cbec9ca3c6d3..19725833406d 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -176,7 +176,7 @@ module Regex::PCRE2 module MatchData # :nodoc: - def initialize(@regex : Regex, @code : LibPCRE2::Code*, @string : String, @pos : Int32, @ovector : UInt64*, @group_size : Int32) + def initialize(@regex : Regex, @code : LibPCRE2::Code*, @string : String, @pos : Int32, @ovector : LibC::SizeT*, @group_size : Int32) end private def byte_range(n, &) From 59ce77071115c7de4ccc170424a86b01a5117727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 22 Feb 2023 09:36:18 +0100 Subject: [PATCH 0341/1551] Fix `libpcre2` bindings function pointers (#13090) --- src/regex/lib_pcre2.cr | 8 +++++--- src/regex/pcre2.cr | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index bb2a8939ca01..b81de3399311 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -207,8 +207,10 @@ lib LibPCRE2 fun get_ovector_pointer = pcre2_get_ovector_pointer_8(match_data : MatchData*) : LibC::SizeT* fun get_ovector_count = pcre2_get_ovector_count_8(match_data : MatchData*) : UInt32 - # void *private_malloc(Int, void *); - # void private_free(void *, void *); - fun general_context_create = pcre2_general_context_create_8(private_malloc : Void*, private_free : Void*, memory_data : Void*) : GeneralContext + fun general_context_create = pcre2_general_context_create_8( + private_malloc : LibC::SizeT, Void* -> Void, + private_free : Void*, Void* -> Void, + memory_data : Void* + ) : GeneralContext fun config = pcre2_config_8(what : UInt32, where : Void*) : Int end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 19725833406d..d7da661fbb8c 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -135,7 +135,7 @@ module Regex::PCRE2 end class_getter general_context do - LibPCRE2.general_context_create(->(size : LibC::Int, data : Void*) { GC.malloc(size) }.pointer, ->(pointer : Void*, data : Void*) { GC.free(pointer) }.pointer, nil) + LibPCRE2.general_context_create(->(size, _data) { GC.malloc(size) }, ->(pointer, _data) { GC.free(pointer) }, nil) end # Returns a JIT stack that's shared in the current thread. From 9909cffc65efc1018c3611ef0212f25c1483b948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 22 Feb 2023 09:36:50 +0100 Subject: [PATCH 0342/1551] [CI] Fix add PCRE2 to GHA cache for win job (#13089) --- .github/workflows/win.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index aad484818f4a..12a953dbd0c3 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -42,6 +42,7 @@ jobs: with: path: | # openssl and llvm take much longer to build so they are cached separately libs/pcre.lib + libs/pcre2-8.lib libs/iconv.lib libs/gc.lib libs/ffi.lib From 865d05022d7883eaf48376fb44bed3c055e2bb43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 22 Feb 2023 09:37:05 +0100 Subject: [PATCH 0343/1551] Pin `use_pcre` in build environments where PCRE2 is not yet available (#13102) --- .github/workflows/aarch64.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index 1e8afae7672e..729a016130d5 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -36,7 +36,7 @@ jobs: - name: Run stdlib specs uses: docker://jhass/crystal:1.0.0-alpine-build with: - args: make std_spec + args: make std_spec FLAGS=-Duse_pcre aarch64-musl-test-compiler: needs: aarch64-musl-build runs-on: [linux, ARM64] From 0f0eb1d716a37401830f83128b0a09fe99462a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 22 Feb 2023 09:37:23 +0100 Subject: [PATCH 0344/1551] Fix PCRE2 do not allocate JIT stack if unavailable (#13100) --- spec/std/regex_spec.cr | 4 +++- src/regex/pcre2.cr | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 72f62a3463cd..11213260a030 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -211,7 +211,9 @@ describe "Regex" do {% else %} # Can't use regex literal because the *LIMIT_DEPTH verb is not supported in libpcre (only libpcre2) # and thus the compiler doesn't recognize it. - str.matches?(Regex.new("(*LIMIT_DEPTH=8192)^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$")) + regex = Regex.new("(*LIMIT_DEPTH=8192)^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") + pending! "PCRE2 JIT mode not available." unless regex.@jit + str.matches?(regex) {% end %} # We don't care whether this actually matches or not, it's just to make # sure the engine does not stack overflow with a large string. diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index d7da661fbb8c..1af1e79faa46 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -4,6 +4,7 @@ require "crystal/thread_local_value" # :nodoc: module Regex::PCRE2 @re : LibPCRE2::Code* + @jit : Bool # :nodoc: def initialize(*, _source @source : String, _options @options) @@ -11,19 +12,21 @@ module Regex::PCRE2 raise ArgumentError.new(error_message) end - jit_compile + @jit = jit_compile end - private def jit_compile : Nil + private def jit_compile : Bool ret = LibPCRE2.jit_compile(@re, LibPCRE2::JIT_COMPLETE) if ret < 0 case error = LibPCRE2::Error.new(ret) when .jit_badoption? # okay + return false else raise ArgumentError.new("Regex JIT compile error: #{error}") end end + true end protected def self.compile(source, options) @@ -153,7 +156,7 @@ module Regex::PCRE2 private def match_data(str, byte_index, options) match_data = LibPCRE2.match_data_create_from_pattern(@re, Regex::PCRE2.general_context) match_context = LibPCRE2.match_context_create(nil) - LibPCRE2.jit_stack_assign(match_context, nil, Regex::PCRE2.jit_stack.as(Void*)) + LibPCRE2.jit_stack_assign(match_context, nil, Regex::PCRE2.jit_stack.as(Void*)) if @jit match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, match_context) if match_count < 0 From 6e9c35656b5ae9ac89790d99146f7f10d8f21c00 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 2 Mar 2023 19:02:19 +0900 Subject: [PATCH 0345/1551] Define `Math.pw2ceil` for all integer types (#13127) --- spec/std/big/big_int_spec.cr | 18 +++++++++ spec/std/math_spec.cr | 74 +++++++++++++++++++----------------- src/big/big_int.cr | 22 +++++++++++ src/int.cr | 16 ++++++++ src/math/math.cr | 31 +++++---------- 5 files changed, 104 insertions(+), 57 deletions(-) diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr index 69be160b848f..bd79aabb5071 100644 --- a/spec/std/big/big_int_spec.cr +++ b/spec/std/big/big_int_spec.cr @@ -666,4 +666,22 @@ describe "BigInt Math" do it "isqrt" do Math.isqrt(BigInt.new("1" + "0"*48)).should eq(BigInt.new("1" + "0"*24)) end + + it "pw2ceil" do + Math.pw2ceil("-100000000000000000000000000000000".to_big_i).should eq(1.to_big_i) + Math.pw2ceil(-1234567.to_big_i).should eq(1.to_big_i) + Math.pw2ceil(-1.to_big_i).should eq(1.to_big_i) + Math.pw2ceil(0.to_big_i).should eq(1.to_big_i) + Math.pw2ceil(1.to_big_i).should eq(1.to_big_i) + Math.pw2ceil(2.to_big_i).should eq(2.to_big_i) + Math.pw2ceil(3.to_big_i).should eq(4.to_big_i) + Math.pw2ceil(4.to_big_i).should eq(4.to_big_i) + Math.pw2ceil(5.to_big_i).should eq(8.to_big_i) + Math.pw2ceil(32.to_big_i).should eq(32.to_big_i) + Math.pw2ceil(33.to_big_i).should eq(64.to_big_i) + Math.pw2ceil(64.to_big_i).should eq(64.to_big_i) + Math.pw2ceil(2.to_big_i ** 12345 - 1).should eq(2.to_big_i ** 12345) + Math.pw2ceil(2.to_big_i ** 12345).should eq(2.to_big_i ** 12345) + Math.pw2ceil(2.to_big_i ** 12345 + 1).should eq(2.to_big_i ** 12346) + end end diff --git a/spec/std/math_spec.cr b/spec/std/math_spec.cr index 29a13c994c42..9106636a377e 100644 --- a/spec/std/math_spec.cr +++ b/spec/std/math_spec.cr @@ -266,44 +266,48 @@ describe "Math" do # div rem - # pw2ceil - describe ".pw2ceil" do - it "Int32" do - Math.pw2ceil(-1).should eq 1 - Math.pw2ceil(33).should eq(64) - Math.pw2ceil(128).should eq(128) - Math.pw2ceil(0).should eq 1 - Math.pw2ceil(1).should eq 1 - Math.pw2ceil(2).should eq 2 - Math.pw2ceil(3).should eq 4 - Math.pw2ceil(4).should eq 4 - Math.pw2ceil(5).should eq 8 - # 1073741824 is the largest power of 2 that fits into Int32 - Math.pw2ceil(1073741824).should eq 1073741824 - Math.pw2ceil(1073741824 - 1).should eq 1073741824 - expect_raises(OverflowError) do - Math.pw2ceil(1073741824 + 1) + {% for int in %w(Int8 Int16 Int32 Int64 Int128) %} + it {{ int }} do + Math.pw2ceil({{ int.id }}::MIN).should eq 1 + Math.pw2ceil({{ int.id }}::MIN + 1).should eq 1 + Math.pw2ceil({{ int.id }}.new(-11)).should eq 1 + Math.pw2ceil({{ int.id }}.new(-1)).should eq 1 + Math.pw2ceil({{ int.id }}.new(0)).should eq 1 + Math.pw2ceil({{ int.id }}.new(1)).should eq 1 + Math.pw2ceil({{ int.id }}.new(2)).should eq 2 + Math.pw2ceil({{ int.id }}.new(3)).should eq 4 + Math.pw2ceil({{ int.id }}.new(4)).should eq 4 + Math.pw2ceil({{ int.id }}.new(5)).should eq 8 + Math.pw2ceil({{ int.id }}.new(32)).should eq(32) + Math.pw2ceil({{ int.id }}.new(33)).should eq(64) + Math.pw2ceil({{ int.id }}.new(64)).should eq(64) + + Math.pw2ceil({{ int.id }}::MAX // 2).should eq({{ int.id }}::MAX // 2 + 1) + Math.pw2ceil({{ int.id }}::MAX // 2 + 1).should eq({{ int.id }}::MAX // 2 + 1) + expect_raises(OverflowError) { Math.pw2ceil({{ int.id }}::MAX // 2 + 2) } + expect_raises(OverflowError) { Math.pw2ceil({{ int.id }}::MAX) } end - end - - it "Int64" do - Math.pw2ceil(-1_i64).should eq 1 - Math.pw2ceil(33_i64).should eq(64) - Math.pw2ceil(128_i64).should eq(128) - Math.pw2ceil(0_i64).should eq 1 - Math.pw2ceil(1_i64).should eq 1 - Math.pw2ceil(2_i64).should eq 2 - Math.pw2ceil(3_i64).should eq 4 - Math.pw2ceil(4_i64).should eq 4 - Math.pw2ceil(5_i64).should eq 8 - # 4611686018427387904 is the largest power of 2 that fits into Int64 - Math.pw2ceil(4611686018427387904).should eq 4611686018427387904 - Math.pw2ceil(4611686018427387904 - 1).should eq 4611686018427387904 - expect_raises(OverflowError) do - Math.pw2ceil(4611686018427387904 + 1) + {% end %} + + {% for uint in %w(UInt8 UInt16 UInt32 UInt64 UInt128) %} + it {{ uint }} do + Math.pw2ceil({{ uint.id }}.new(0)).should eq 1 + Math.pw2ceil({{ uint.id }}.new(1)).should eq 1 + Math.pw2ceil({{ uint.id }}.new(2)).should eq 2 + Math.pw2ceil({{ uint.id }}.new(3)).should eq 4 + Math.pw2ceil({{ uint.id }}.new(4)).should eq 4 + Math.pw2ceil({{ uint.id }}.new(5)).should eq 8 + Math.pw2ceil({{ uint.id }}.new(32)).should eq(32) + Math.pw2ceil({{ uint.id }}.new(33)).should eq(64) + Math.pw2ceil({{ uint.id }}.new(64)).should eq(64) + + Math.pw2ceil({{ uint.id }}::MAX // 2).should eq({{ uint.id }}::MAX // 2 + 1) + Math.pw2ceil({{ uint.id }}::MAX // 2 + 1).should eq({{ uint.id }}::MAX // 2 + 1) + expect_raises(OverflowError) { Math.pw2ceil({{ uint.id }}::MAX // 2 + 2) } + expect_raises(OverflowError) { Math.pw2ceil({{ uint.id }}::MAX) } end - end + {% end %} end # ** (float and int) diff --git a/src/big/big_int.cr b/src/big/big_int.cr index e1ca94dbd10b..b40ea4670b70 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -585,6 +585,14 @@ struct BigInt < Int LibGMP.scan1(self, 0) end + # :nodoc: + def next_power_of_two : self + one = BigInt.new(1) + return one if self <= 0 + + popcount == 1 ? self : one << bit_length + end + def to_i : Int32 to_i32 end @@ -847,6 +855,20 @@ module Math def isqrt(value : BigInt) BigInt.new { |mpz| LibGMP.sqrt(mpz, value) } end + + # Computes the smallest nonnegative power of 2 that is greater than or equal + # to *v*. + # + # The returned value has the same type as the argument. + # + # ``` + # Math.pw2ceil(33) # => 64 + # Math.pw2ceil(64) # => 64 + # Math.pw2ceil(-5) # => 1 + # ``` + def pw2ceil(v : BigInt) : BigInt + v.next_power_of_two + end end module Random diff --git a/src/int.cr b/src/int.cr index 8ede6465cd03..e3709e7f968b 100644 --- a/src/int.cr +++ b/src/int.cr @@ -448,6 +448,22 @@ struct Int end end + # :nodoc: + def next_power_of_two : self + one = self.class.new!(1) + + bits = sizeof(self) * 8 + shift = bits &- (self &- 1).leading_zeros_count + if self.is_a?(Int::Signed) + shift = 0 if shift >= bits &- 1 + else + shift = 0 if shift == bits + end + + result = one << shift + result >= self ? result : raise OverflowError.new + end + # Returns the greatest common divisor of `self` and *other*. Signed # integers may raise `OverflowError` if either has value equal to `MIN` of # its type. diff --git a/src/math/math.cr b/src/math/math.cr index d031969aa8fd..6693e6a29a19 100644 --- a/src/math/math.cr +++ b/src/math/math.cr @@ -722,31 +722,18 @@ module Math value1 <= value2 ? value1 : value2 end - # Computes the next highest power of 2 of *v*. + # Computes the smallest nonnegative power of 2 that is greater than or equal + # to *v*. + # + # The returned value has the same type as the argument. Raises `OverflowError` + # if the result does not fit into the argument's type. # # ``` # Math.pw2ceil(33) # => 64 + # Math.pw2ceil(64) # => 64 + # Math.pw2ceil(-5) # => 1 # ``` - def pw2ceil(v : Int32) - # Taken from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - v -= 1 - v |= v >> 1 - v |= v >> 2 - v |= v >> 4 - v |= v >> 8 - v |= v >> 16 - v += v == -1 ? 2 : 1 - end - - def pw2ceil(v : Int64) - # Taken from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - v -= 1 - v |= v >> 1 - v |= v >> 2 - v |= v >> 4 - v |= v >> 8 - v |= v >> 16 - v |= v >> 32 - v += v == -1 ? 2 : 1 + def pw2ceil(v : Int::Primitive) + v.next_power_of_two end end From 60be75bbe223e0fb9ac109bc0bdda8bfe99c9686 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 2 Mar 2023 20:24:06 +0800 Subject: [PATCH 0346/1551] `System::User#name`: Fall back to `#username` if unavailable --- src/crystal/system/unix/user.cr | 6 ++++-- src/system/user.cr | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/unix/user.cr b/src/crystal/system/unix/user.cr index cd33d08fddd2..a74e248c995e 100644 --- a/src/crystal/system/unix/user.cr +++ b/src/crystal/system/unix/user.cr @@ -5,8 +5,10 @@ module Crystal::System::User GETPW_R_SIZE_MAX = 1024 * 16 private def from_struct(pwd) - user = String.new(pwd.pw_gecos).partition(',')[0] - new(String.new(pwd.pw_name), pwd.pw_uid.to_s, pwd.pw_gid.to_s, user, String.new(pwd.pw_dir), String.new(pwd.pw_shell)) + username = String.new(pwd.pw_name) + # `pw_gecos` is not part of POSIX and bionic for example always leaves it null + user = pwd.pw_gecos ? String.new(pwd.pw_gecos).partition(',')[0] : username + new(username, pwd.pw_uid.to_s, pwd.pw_gid.to_s, user, String.new(pwd.pw_dir), String.new(pwd.pw_shell)) end private def from_username?(username : String) diff --git a/src/system/user.cr b/src/system/user.cr index f23e13e6dc4c..7d6c250689da 100644 --- a/src/system/user.cr +++ b/src/system/user.cr @@ -29,6 +29,9 @@ class System::User getter group_id : String # The user's real or full name. + # + # May not be present on all platforms. Returns the same value as `#username` + # if neither a real nor full name is available. getter name : String # The user's home directory. From 274239110f9add8eab41b11c978d327ea10a7bbc Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 2 Mar 2023 20:56:47 +0800 Subject: [PATCH 0347/1551] Increase time drift for `HTTP::StaticFileHandler`'s gzip check --- .../http/server/handlers/static_file_handler_spec.cr | 2 +- src/http/server/handlers/static_file_handler.cr | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/std/http/server/handlers/static_file_handler_spec.cr b/spec/std/http/server/handlers/static_file_handler_spec.cr index 3c2d1a2ae24b..267682754a0c 100644 --- a/spec/std/http/server/handlers/static_file_handler_spec.cr +++ b/spec/std/http/server/handlers/static_file_handler_spec.cr @@ -274,7 +274,7 @@ describe HTTP::StaticFileHandler do it "still serve compressed content when modification time is very close" do modification_time = File.info(datapath("static_file_handler", "test.txt")).modification_time - File.touch datapath("static_file_handler", "test.txt.gz"), modification_time - 1.microsecond + File.touch datapath("static_file_handler", "test.txt.gz"), modification_time - 1.millisecond headers = HTTP::Headers{"Accept-Encoding" => "gzip"} response = handle HTTP::Request.new("GET", "/test.txt", headers), decompress: false diff --git a/src/http/server/handlers/static_file_handler.cr b/src/http/server/handlers/static_file_handler.cr index 3fbb57ab0bc0..99e268896103 100644 --- a/src/http/server/handlers/static_file_handler.cr +++ b/src/http/server/handlers/static_file_handler.cr @@ -13,6 +13,13 @@ require "mime" class HTTP::StaticFileHandler include HTTP::Handler + # In some file systems, using `gz --keep` to compress the file will keep the + # modification time of the original file but truncating some decimals. We + # serve the gzipped file nonetheless if the .gz file is modified by a duration + # of `TIME_DRIFT` before the original file. This value should match the + # granularity of the underlying file system's modification times + private TIME_DRIFT = 10.milliseconds + @public_dir : Path # Creates a handler that will serve files in the given *public_dir*, after @@ -91,10 +98,7 @@ class HTTP::StaticFileHandler gz_file_path = "#{file_path}.gz" if (gz_file_info = File.info?(gz_file_path)) && - # Allow small time drift. In some file systems, using `gz --keep` to - # compress the file will keep the modification time of the original file - # but truncating some decimals - last_modified - gz_file_info.modification_time < 1.millisecond + last_modified - gz_file_info.modification_time < TIME_DRIFT file_path = gz_file_path file_info = gz_file_info context.response.headers["Content-Encoding"] = "gzip" From 5ca57a70140d72af2326805ce092b4c470750315 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 4 Mar 2023 02:15:14 +0900 Subject: [PATCH 0348/1551] Fix `getelementptr` usage for the `allocate` primitive (#13148) --- src/compiler/crystal/codegen/codegen.cr | 16 +++++++++++----- src/compiler/crystal/codegen/primitives.cr | 5 ----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index ac07d54cbfe7..dc22f471b0e1 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -1974,18 +1974,24 @@ module Crystal def allocate_aggregate(type) struct_type = llvm_struct_type(type) if type.passed_by_value? - @last = alloca struct_type + type_ptr = alloca struct_type else if type.is_a?(InstanceVarContainer) && !type.struct? && type.all_instance_vars.each_value.any? &.type.has_inner_pointers? - @last = malloc struct_type + type_ptr = malloc struct_type else - @last = malloc_atomic struct_type + type_ptr = malloc_atomic struct_type end end - memset @last, int8(0), struct_type.size - type_ptr = @last + + memset type_ptr, int8(0), struct_type.size run_instance_vars_initializers(type, type, type_ptr) + + unless type.struct? + type_id_ptr = aggregate_index(struct_type, type_ptr, 0) + store type_id(type), type_id_ptr + end + @last = type_ptr end diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index ee27902ff7ff..145e353789f3 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -730,11 +730,6 @@ class Crystal::CodeGenVisitor allocate_aggregate base_type - unless type.struct? - type_id_ptr = aggregate_index(llvm_struct_type(type), @last, 0) - store type_id(base_type), type_id_ptr - end - if type.is_a?(VirtualType) @last = upcast(@last, type, base_type) end From 856fbe076c3f4e64d667770ad82ba57225e94183 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 4 Mar 2023 16:35:42 +0900 Subject: [PATCH 0349/1551] Fix Crystal tool cursor parsing for filenames containing `:` (#13129) --- spec/compiler/lexer/location_spec.cr | 31 +++++++++++++++++++++++++ src/compiler/crystal/command/cursor.cr | 23 ++++-------------- src/compiler/crystal/syntax/location.cr | 23 ++++++++++++++++++ src/compiler/crystal/util.cr | 8 +++++-- 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/spec/compiler/lexer/location_spec.cr b/spec/compiler/lexer/location_spec.cr index fcfd8a6ac86e..72ebdc91c286 100644 --- a/spec/compiler/lexer/location_spec.cr +++ b/spec/compiler/lexer/location_spec.cr @@ -174,4 +174,35 @@ describe "Lexer: location" do (loc1 < loc2).should be_false (loc2 < loc1).should be_false end + + describe "Location.parse" do + it "parses location from string" do + Location.parse("foo:1:2").should eq(Location.new("foo", 1, 2)) + Location.parse("foo:bar/baz:345:6789").should eq(Location.new("foo:bar/baz", 345, 6789)) + Location.parse(%q(C:\foo\bar:1:2)).should eq(Location.new(%q(C:\foo\bar), 1, 2)) + end + + it "raises ArgumentError if missing colon" do + expect_raises(ArgumentError, "cursor location must be file:line:column") { Location.parse("foo") } + expect_raises(ArgumentError, "cursor location must be file:line:column") { Location.parse("foo:1") } + end + + it "raises ArgumentError if missing part" do + expect_raises(ArgumentError, "cursor location must be file:line:column") { Location.parse(":1:2") } + expect_raises(ArgumentError, "cursor location must be file:line:column") { Location.parse("foo::2") } + expect_raises(ArgumentError, "cursor location must be file:line:column") { Location.parse("foo:1:") } + end + + it "raises ArgumentError if line number is invalid" do + expect_raises(ArgumentError, "line must be a positive integer, not a") { Location.parse("foo:a:2") } + expect_raises(ArgumentError, "line must be a positive integer, not 0") { Location.parse("foo:0:2") } + expect_raises(ArgumentError, "line must be a positive integer, not -1") { Location.parse("foo:-1:2") } + end + + it "raises ArgumentError if column number is invalid" do + expect_raises(ArgumentError, "column must be a positive integer, not a") { Location.parse("foo:2:a") } + expect_raises(ArgumentError, "column must be a positive integer, not 0") { Location.parse("foo:2:0") } + expect_raises(ArgumentError, "column must be a positive integer, not -1") { Location.parse("foo:2:-1") } + end + end end diff --git a/src/compiler/crystal/command/cursor.cr b/src/compiler/crystal/command/cursor.cr index cf8d9a0b2803..e8e125972475 100644 --- a/src/compiler/crystal/command/cursor.cr +++ b/src/compiler/crystal/command/cursor.cr @@ -31,27 +31,14 @@ class Crystal::Command format = config.output_format - loc = config.cursor_location.not_nil!.split(':') - if loc.size != 3 - error "cursor location must be file:line:column" + begin + loc = Location.parse(config.cursor_location.not_nil!, expand: true) + rescue ex : ArgumentError + error ex.message end - file, line, col = loc - - line_number = line.to_i? || 0 - if line_number <= 0 - error "line must be a positive integer, not #{line}" - end - - column_number = col.to_i? || 0 - if column_number <= 0 - error "column must be a positive integer, not #{col}" - end - - file = File.expand_path(file) - result = @progress_tracker.stage("Tool (#{command.split(' ')[1]})") do - yield Location.new(file, line_number, column_number), config, result + yield loc, config, result end case format diff --git a/src/compiler/crystal/syntax/location.cr b/src/compiler/crystal/syntax/location.cr index 00e8ca62cb64..d035177d9d76 100644 --- a/src/compiler/crystal/syntax/location.cr +++ b/src/compiler/crystal/syntax/location.cr @@ -9,6 +9,29 @@ class Crystal::Location def initialize(@filename : String | VirtualFile | Nil, @line_number : Int32, @column_number : Int32) end + # Returns a location from a string representation. Used by compiler tools like + # `context` and `implementations`. + def self.parse(cursor : String, *, expand : Bool = false) : self + file_and_line, _, col = cursor.rpartition(':') + file, _, line = file_and_line.rpartition(':') + + raise ArgumentError.new "cursor location must be file:line:column" if file.empty? || line.empty? || col.empty? + + file = File.expand_path(file) if expand + + line_number = line.to_i? || 0 + if line_number <= 0 + raise ArgumentError.new "line must be a positive integer, not #{line}" + end + + column_number = col.to_i? || 0 + if column_number <= 0 + raise ArgumentError.new "column must be a positive integer, not #{col}" + end + + new(file, line_number, column_number) + end + # Returns the directory name of this location's filename. If # the filename is a VirtualFile, this is invoked on its expanded # location. diff --git a/src/compiler/crystal/util.cr b/src/compiler/crystal/util.cr index 80d360cec702..c33bfa5d0d42 100644 --- a/src/compiler/crystal/util.cr +++ b/src/compiler/crystal/util.cr @@ -15,10 +15,14 @@ module Crystal filename end - def self.error(msg, color, exit_code = 1, stderr = STDERR, leading_error = true) + def self.error(msg, color, exit_code : Int = 1, stderr = STDERR, leading_error = true) : NoReturn + error(msg, color, nil, stderr, leading_error) + exit(exit_code) + end + + def self.error(msg, color, exit_code : Nil, stderr = STDERR, leading_error = true) stderr.print "Error: ".colorize.toggle(color).red.bold if leading_error stderr.puts msg.colorize.toggle(color).bright - exit(exit_code) if exit_code end def self.tempfile(basename) From ad57a582f0532b437ad7dbd2424f4cf6393535d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 4 Mar 2023 10:42:23 +0100 Subject: [PATCH 0350/1551] Fix `MatchData#[]` named capture with identical prefix (#13147) --- spec/std/regex/match_data_spec.cr | 6 ++++++ src/regex/pcre2.cr | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/std/regex/match_data_spec.cr b/spec/std/regex/match_data_spec.cr index 70b11b1104a8..458750c6931d 100644 --- a/spec/std/regex/match_data_spec.cr +++ b/spec/std/regex/match_data_spec.cr @@ -213,6 +213,12 @@ describe "Regex::MatchData" do matchdata(re, "barfoo")["g1"].should eq("foo") end + it "named groups with same prefix" do + md = matchdata(/KEY_(?\w+)\s+(?.*)/, "KEY_POWER 116") + md["key"].should eq "POWER" + md["keycode"].should eq "116" + end + it "raises exception when named group doesn't exist" do md = matchdata(/foo/, "foo") expect_raises(KeyError, "Capture group 'group' does not exist") { md["group"] } diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 1af1e79faa46..f2eab8313e4e 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -196,7 +196,7 @@ module Regex::PCRE2 selected_range = nil exists = false @regex.each_capture_group do |number, name_entry| - if name_entry[2, group_name.bytesize]? == group_name.to_slice + if name_entry[2, group_name.bytesize]? == group_name.to_slice && name_entry[2 + group_name.bytesize].zero? exists = true range = byte_range(number) { nil } if (range && selected_range && range.begin > selected_range.begin) || !selected_range From 30f5d649711d8a9cf347a08518fb42496aebd7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 6 Mar 2023 11:04:44 +0100 Subject: [PATCH 0351/1551] Improve PCRE2 match performance for JIT and interpreted (#13146) --- src/regex/lib_pcre2.cr | 25 ++++++++++-------------- src/regex/pcre2.cr | 43 ++++++++++++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index b81de3399311..5fac84326a13 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -172,10 +172,10 @@ lib LibPCRE2 CONFIG_COMPILED_WIDTHS = 14 CONFIG_TABLES_LENGTH = 15 - type Code = Void* - type CompileContext = Void* - type MatchData = Void* - type GeneralContext = Void* + type Code = Void + type CompileContext = Void + type MatchData = Void + type GeneralContext = Void fun get_error_message = pcre2_get_error_message_8(errorcode : Int, buffer : UInt8*, bufflen : LibC::SizeT) : Int @@ -183,7 +183,7 @@ lib LibPCRE2 fun code_free = pcre2_code_free_8(code : Code*) : Void type MatchContext = Void* - fun match_context_create = pcre2_match_context_create_8(gcontext : Void*) : MatchContext + fun match_context_create = pcre2_match_context_create_8(gcontext : Void*) : MatchContext* JIT_COMPLETE = 0x00000001_u32 # For full matching JIT_PARTIAL_SOFT = 0x00000002_u32 @@ -191,15 +191,15 @@ lib LibPCRE2 JIT_INVALID_UTF = 0x00000100_u32 fun jit_compile = pcre2_jit_compile_8(code : Code*, options : UInt32) : Int - type JITStack = Void* + type JITStack = Void - fun jit_stack_create = pcre2_jit_stack_create_8(startsize : LibC::SizeT, maxsize : LibC::SizeT, gcontext : GeneralContext) : JITStack - fun jit_stack_assign = pcre2_jit_stack_assign_8(mcontext : MatchContext, callable_function : Void*, callable_data : Void*) : Void + fun jit_stack_create = pcre2_jit_stack_create_8(startsize : LibC::SizeT, maxsize : LibC::SizeT, gcontext : GeneralContext*) : JITStack* + fun jit_stack_assign = pcre2_jit_stack_assign_8(mcontext : MatchContext*, callable_function : Void* -> JITStack*, callable_data : Void*) : Void fun pattern_info = pcre2_pattern_info_8(code : Code*, what : UInt32, where : Void*) : Int - fun match = pcre2_match_8(code : Code*, subject : UInt8*, length : LibC::SizeT, startoffset : LibC::SizeT, options : UInt32, match_data : MatchData*, mcontext : MatchContext) : Int - fun match_data_create_from_pattern = pcre2_match_data_create_from_pattern_8(code : Code*, gcontext : GeneralContext) : MatchData* + fun match = pcre2_match_8(code : Code*, subject : UInt8*, length : LibC::SizeT, startoffset : LibC::SizeT, options : UInt32, match_data : MatchData*, mcontext : MatchContext*) : Int + fun match_data_create_from_pattern = pcre2_match_data_create_from_pattern_8(code : Code*, gcontext : GeneralContext*) : MatchData* fun match_data_free = pcre2_match_data_free_8(match_data : MatchData*) : Void fun substring_nametable_scan = pcre2_substring_nametable_scan_8(code : Code*, name : UInt8*, first : UInt8*, last : UInt8*) : Int @@ -207,10 +207,5 @@ lib LibPCRE2 fun get_ovector_pointer = pcre2_get_ovector_pointer_8(match_data : MatchData*) : LibC::SizeT* fun get_ovector_count = pcre2_get_ovector_count_8(match_data : MatchData*) : UInt32 - fun general_context_create = pcre2_general_context_create_8( - private_malloc : LibC::SizeT, Void* -> Void, - private_free : Void*, Void* -> Void, - memory_data : Void* - ) : GeneralContext fun config = pcre2_config_8(what : UInt32, where : Void*) : Int end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 98ba7d9b74f3..4df8a61274d8 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -123,10 +123,15 @@ module Regex::PCRE2 private def match_impl(str, byte_index, options) match_data = match_data(str, byte_index, options) || return - ovector = LibPCRE2.get_ovector_pointer(match_data) ovector_count = LibPCRE2.get_ovector_count(match_data) + ovector = Slice.new(LibPCRE2.get_ovector_pointer(match_data), ovector_count &* 2) - ::Regex::MatchData.new(self, @re, str, byte_index, ovector, ovector_count.to_i32 - 1) + # We need to dup the ovector because `match_data` is re-used for subsequent + # matches (see `@match_data`). + # Dup brings the ovector data into the realm of the GC. + ovector = ovector.dup + + ::Regex::MatchData.new(self, @re, str, byte_index, ovector.to_unsafe, ovector_count.to_i32 &- 1) end private def matches_impl(str, byte_index, options) @@ -137,27 +142,45 @@ module Regex::PCRE2 end end - class_getter general_context do - LibPCRE2.general_context_create(->(size, _data) { GC.malloc(size) }, ->(pointer, _data) { GC.free(pointer) }, nil) + class_getter match_context : LibPCRE2::MatchContext* do + match_context = LibPCRE2.match_context_create(nil) + LibPCRE2.jit_stack_assign(match_context, ->(_data) { Regex::PCRE2.jit_stack }, nil) + match_context end # Returns a JIT stack that's shared in the current thread. # # Only a single `match` function can run per thread at any given time, so there # can't be any concurrent access to the JIT stack. - @@jit_stack = Crystal::ThreadLocalValue(LibPCRE2::JITStack).new + @@jit_stack = Crystal::ThreadLocalValue(LibPCRE2::JITStack*).new def self.jit_stack @@jit_stack.get do - LibPCRE2.jit_stack_create(32_768, 1_048_576, general_context) || raise "Error allocating JIT stack" + LibPCRE2.jit_stack_create(32_768, 1_048_576, nil) || raise "Error allocating JIT stack" + end + end + + # Match data is shared per instance and thread. + # + # Match data contains a buffer for backtracking when matching in interpreted mode (non-JIT). + # This buffer is heap-allocated and should be re-used for subsequent matches. + @match_data = Crystal::ThreadLocalValue(LibPCRE2::MatchData*).new + + private def match_data + @match_data.get do + LibPCRE2.match_data_create_from_pattern(@re, nil) + end + end + + def finalize + @match_data.consume_each do |match_data| + LibPCRE2.match_data_free(match_data) end end private def match_data(str, byte_index, options) - match_data = LibPCRE2.match_data_create_from_pattern(@re, Regex::PCRE2.general_context) - match_context = LibPCRE2.match_context_create(nil) - LibPCRE2.jit_stack_assign(match_context, nil, Regex::PCRE2.jit_stack.as(Void*)) if @jit - match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, match_context) + match_data = self.match_data + match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, PCRE2.match_context) if match_count < 0 case error = LibPCRE2::Error.new(match_count) From 49a19804dac111ede001fbe18f9d3d737f55ec92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 6 Mar 2023 11:05:02 +0100 Subject: [PATCH 0352/1551] [CI] Increase `no_output_timeout` on circleci (#13151) --- .circleci/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ae6750ed1c11..52383eb206a9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,9 @@ defaults: - run: bin/ci prepare_system - run: echo 'export CURRENT_TAG="$CIRCLE_TAG"' >> $BASH_ENV - run: bin/ci prepare_build - - run: bin/ci build + - run: + command: bin/ci build + no_output_timeout: 30m - run: when: always command: | From 126b5e7df0fa7aeae3a76f6d5ff18eb73d0e6ce7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 7 Mar 2023 04:30:15 +0900 Subject: [PATCH 0353/1551] Support typed LLVM `load` instructions (#12973) --- samples/llvm/brainfuck.cr | 16 +++++------ spec/compiler/codegen/pointer_spec.cr | 2 -- src/compiler/crystal/codegen/call.cr | 4 +-- src/compiler/crystal/codegen/cast.cr | 25 +++++++++-------- src/compiler/crystal/codegen/codegen.cr | 27 ++++++++++++------ src/compiler/crystal/codegen/cond.cr | 4 +-- src/compiler/crystal/codegen/const.cr | 14 +++++----- src/compiler/crystal/codegen/debug.cr | 2 +- src/compiler/crystal/codegen/exception.cr | 8 ++++-- src/compiler/crystal/codegen/fun.cr | 10 +++---- .../crystal/codegen/llvm_builder_helper.cr | 2 +- src/compiler/crystal/codegen/once.cr | 4 +-- src/compiler/crystal/codegen/primitives.cr | 28 ++++++------------- src/compiler/crystal/codegen/type_id.cr | 6 ++-- src/compiler/crystal/codegen/unions.cr | 7 +++-- src/llvm/builder.cr | 25 +++++++++++++++-- src/llvm/lib_llvm.cr | 3 ++ 17 files changed, 107 insertions(+), 80 deletions(-) diff --git a/samples/llvm/brainfuck.cr b/samples/llvm/brainfuck.cr index 3ca07cdf3584..f487c24f84a5 100644 --- a/samples/llvm/brainfuck.cr +++ b/samples/llvm/brainfuck.cr @@ -17,10 +17,10 @@ class Increment < Instruction builder = program.builder builder.position_at_end bb - cell_index = builder.load program.cell_index_ptr, "cell_index" + cell_index = builder.load program.ctx.int32, program.cell_index_ptr, "cell_index" current_cell_ptr = builder.gep program.cell_type, program.cells_ptr, cell_index, "current_cell_ptr" - cell_val = builder.load current_cell_ptr, "cell_value" + cell_val = builder.load program.cell_type, current_cell_ptr, "cell_value" increment_amount = program.cell_type.const_int(@amount) new_cell_val = builder.add cell_val, increment_amount, "cell_value" builder.store new_cell_val, current_cell_ptr @@ -37,7 +37,7 @@ class DataIncrement < Instruction builder = program.builder builder.position_at_end bb - cell_index = builder.load program.cell_index_ptr, "cell_index" + cell_index = builder.load program.ctx.int32, program.cell_index_ptr, "cell_index" increment_amount = program.ctx.int32.const_int(@amount) new_cell_index = builder.add cell_index, increment_amount, "new_cell_index" @@ -52,7 +52,7 @@ class Read < Instruction builder = program.builder builder.position_at_end bb - cell_index = builder.load program.cell_index_ptr, "cell_index" + cell_index = builder.load program.ctx.int32, program.cell_index_ptr, "cell_index" current_cell_ptr = builder.gep program.cell_type, program.cells_ptr, cell_index, "current_cell_ptr" getchar = program.mod.functions["getchar"] @@ -69,10 +69,10 @@ class Write < Instruction builder = program.builder builder.position_at_end bb - cell_index = builder.load program.cell_index_ptr, "cell_index" + cell_index = builder.load program.ctx.int32, program.cell_index_ptr, "cell_index" current_cell_ptr = builder.gep program.cell_type, program.cells_ptr, cell_index, "current_cell_ptr" - cell_val = builder.load current_cell_ptr, "cell_value" + cell_val = builder.load program.cell_type, current_cell_ptr, "cell_value" cell_val_as_char = builder.sext cell_val, program.ctx.int32, "cell_val_as_char" putchar = program.mod.functions["putchar"] @@ -99,9 +99,9 @@ class Loop < Instruction loop_after = func.basic_blocks.append "loop_after" builder.position_at_end loop_header - cell_index = builder.load program.cell_index_ptr, "cell_index" + cell_index = builder.load program.ctx.int32, program.cell_index_ptr, "cell_index" current_cell_ptr = builder.gep program.cell_type, program.cells_ptr, cell_index, "current_cell_ptr" - cell_val = builder.load current_cell_ptr, "cell_value" + cell_val = builder.load program.cell_type, current_cell_ptr, "cell_value" zero = program.cell_type.const_int(0) cell_val_is_zero = builder.icmp LLVM::IntPredicate::EQ, cell_val, zero diff --git a/spec/compiler/codegen/pointer_spec.cr b/spec/compiler/codegen/pointer_spec.cr index 8e5cd488fd52..60b7718771c8 100644 --- a/spec/compiler/codegen/pointer_spec.cr +++ b/spec/compiler/codegen/pointer_spec.cr @@ -373,8 +373,6 @@ describe "Code gen: pointer" do it "can assign nil to void pointer" do codegen(%( - require "prelude" - ptr = Pointer(Void).malloc(1_u64) ptr.value = ptr.value )) diff --git a/src/compiler/crystal/codegen/call.cr b/src/compiler/crystal/codegen/call.cr index 139eb202850f..56f852650faa 100644 --- a/src/compiler/crystal/codegen/call.cr +++ b/src/compiler/crystal/codegen/call.cr @@ -95,7 +95,7 @@ class Crystal::CodeGenVisitor # - C calling convention passing needs a separate handling of pass-by-value # - Primitives might need a separate handling (for example invoking a Proc) if arg.type.passed_by_value? && !c_calling_convention && !is_primitive - call_arg = load(call_arg) + call_arg = load(llvm_type(arg.type), call_arg) end call_args << call_arg @@ -242,7 +242,7 @@ class Crystal::CodeGenVisitor size = @program.bits64? ? int64(size) : int32(size) align = @abi.align(abi_arg_type.type) memcpy(final_value_casted, gep_call_arg, size, align, int1(0)) - call_arg = load final_value + call_arg = load cast, final_value else # Keep same call arg end diff --git a/src/compiler/crystal/codegen/cast.cr b/src/compiler/crystal/codegen/cast.cr index 8e351468d92b..8b0a5ec063ae 100644 --- a/src/compiler/crystal/codegen/cast.cr +++ b/src/compiler/crystal/codegen/cast.cr @@ -126,6 +126,7 @@ class Crystal::CodeGenVisitor types_needing_cast.each_with_index do |type_needing_cast, i| # Find compatible type compatible_type = target_type.union_types.find! { |ut| type_needing_cast.implements?(ut) } + llvm_compatible_type = llvm_type(compatible_type) matches_label, doesnt_match_label = new_blocks "matches", "doesnt_match_label" cmp_result = equal?(value_type_id, type_id(type_needing_cast)) @@ -135,9 +136,9 @@ class Crystal::CodeGenVisitor # Store value casted_value = cast_to_pointer(union_value_ptr, type_needing_cast) - compatible_ptr = alloca llvm_type(compatible_type) + compatible_ptr = alloca llvm_compatible_type assign(compatible_ptr, compatible_type, type_needing_cast, casted_value) - store_in_union target_type, target_pointer, compatible_type, load(compatible_ptr) + store_in_union target_type, target_pointer, compatible_type, load(llvm_compatible_type, compatible_ptr) br exit_label position_at_end doesnt_match_label @@ -197,7 +198,7 @@ class Crystal::CodeGenVisitor def assign_distinct(target_pointer, target_type : VirtualType, value_type : MixedUnionType, value) _, union_value_ptr = union_type_and_value_pointer(value, value_type) casted_value = cast_to_pointer(union_value_ptr, target_type) - store load(casted_value), target_pointer + store load(llvm_type(target_type), casted_value), target_pointer end def assign_distinct(target_pointer, target_type : VirtualType, value_type : Type, value) @@ -212,7 +213,7 @@ class Crystal::CodeGenVisitor # Can happen when assigning Foo+.class <- Bar.class | Baz.class with Bar < Foo and Baz < Foo _, union_value_ptr = union_type_and_value_pointer(value, value_type) casted_value = cast_to_pointer(union_value_ptr, target_type) - store load(casted_value), target_pointer + store load(llvm_type(target_type), casted_value), target_pointer end def assign_distinct(target_pointer, target_type : NilableProcType, value_type : NilType, value) @@ -269,26 +270,28 @@ class Crystal::CodeGenVisitor # In that case we can simply get the union value and cast it to the target type. # Cast of a non-void proc to a void proc _, union_value_ptr = union_type_and_value_pointer(value, value_type) - value = bit_cast(union_value_ptr, llvm_type(target_type).pointer) - store load(value), target_pointer + value = cast_to_pointer(union_value_ptr, target_type) + store load(llvm_type(target_type), value), target_pointer end def assign_distinct(target_pointer, target_type : Type, value_type : Type, value) raise "BUG: trying to assign #{target_type} (#{target_type.class}) <- #{value_type} (#{value_type.class})" end - def downcast(value, to_type, from_type : VoidType, already_loaded) + def downcast(value, to_type, from_type : VoidType, already_loaded, *, extern = false) value end - def downcast(value, to_type, from_type : Type, already_loaded) + # *extern* is only used in `CodeGenVisitor#read_instance_var`, and it is + # irrelevant whenever `already_loaded == true` + def downcast(value, to_type, from_type : Type, already_loaded, *, extern = false) return llvm_nil if @builder.end from_type = from_type.remove_indirection to_type = to_type.remove_indirection unless already_loaded - value = to_lhs(value, from_type) + value = extern ? extern_to_lhs(value, from_type) : to_lhs(value, from_type) end if from_type != to_type value = downcast_distinct value, to_type, from_type @@ -413,13 +416,13 @@ class Crystal::CodeGenVisitor def downcast_distinct(value, to_type : NilableType, from_type : MixedUnionType) _, value_ptr = union_type_and_value_pointer(value, from_type) - load cast_to_pointer(value_ptr, to_type) + load(llvm_type(to_type), cast_to_pointer(value_ptr, to_type)) end def downcast_distinct(value, to_type : BoolType, from_type : MixedUnionType) _, value_ptr = union_type_and_value_pointer(value, from_type) value = cast_to_pointer(value_ptr, @program.int8) - value = load(value) + value = load(llvm_context.int8, value) trunc value, llvm_context.int1 end diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index dc22f471b0e1..334bd92599cc 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -1142,9 +1142,10 @@ module Crystal thread_local_fun.add_attribute LLVM::Attribute::NoInline end thread_local_fun = check_main_fun(fun_name, thread_local_fun) - indirection_ptr = alloca llvm_type(type).pointer + pointer_type = llvm_type(type).pointer + indirection_ptr = alloca pointer_type call thread_local_fun, indirection_ptr - load indirection_ptr + load pointer_type, indirection_ptr end def visit(node : TypeDeclaration) @@ -1203,7 +1204,7 @@ module Crystal # Special variables always have an extra pointer already_loaded = (node.special_var? ? false : var.already_loaded) - @last = downcast var.pointer, node.type, var.type, already_loaded + @last = downcast var.pointer, node.type, var.type, already_loaded, extern: false elsif node.name == "self" if node.type.metaclass? @last = type_id(node.type) @@ -1260,7 +1261,7 @@ module Crystal type = type.remove_typedef ivar = type.lookup_instance_var(name) ivar_ptr = instance_var_ptr type, name, value - @last = downcast ivar_ptr, node_type, ivar.type, false + @last = downcast ivar_ptr, node_type, ivar.type, false, extern: type.extern? if type.extern? # When reading the instance variable of a C struct or union # we need to convert C functions to Crystal procs. This @@ -1630,7 +1631,7 @@ module Crystal closure_ptr = alloca struct_type store fun_ptr, aggregate_index(struct_type, closure_ptr, 0) store ctx_ptr, aggregate_index(struct_type, closure_ptr, 1) - load(closure_ptr) + load(struct_type, closure_ptr) end def make_nilable_fun(type) @@ -1869,8 +1870,7 @@ module Crystal if self_closured offset = parent_closure_type ? 1 : 0 - self_value = llvm_self - self_value = load self_value if current_context.type.passed_by_value? + self_value = to_rhs(llvm_self, current_context.type) store self_value, gep(closure_type, closure_ptr, 0, closure_vars.size + offset, "self") @@ -2191,11 +2191,20 @@ module Crystal end def to_lhs(value, type) - type.passed_by_value? ? value : load value + # `llvm_embedded_type` needed for void-like types + type.passed_by_value? ? value : load(llvm_embedded_type(type), value) end def to_rhs(value, type) - type.passed_by_value? ? load value : value + type.passed_by_value? ? load(llvm_embedded_type(type), value) : value + end + + def extern_to_lhs(value, type) + type.passed_by_value? ? value : load(llvm_embedded_c_type(type), value) + end + + def extern_to_rhs(value, type) + type.passed_by_value? ? load(llvm_embedded_c_type(type), value) : value end # *type* is the pointee type of *ptr* (not the type of the returned diff --git a/src/compiler/crystal/codegen/cond.cr b/src/compiler/crystal/codegen/cond.cr index 900aa7636b8a..f45b1b006b35 100644 --- a/src/compiler/crystal/codegen/cond.cr +++ b/src/compiler/crystal/codegen/cond.cr @@ -44,7 +44,7 @@ class Crystal::CodeGenVisitor end if has_bool - value = load(bit_cast value_ptr, llvm_context.int1.pointer) + value = load(llvm_context.int1, bit_cast value_ptr, llvm_context.int1.pointer) is_bool = equal? type_id, type_id(@program.bool) cond = and cond, not(and(is_bool, not(value))) end @@ -54,7 +54,7 @@ class Crystal::CodeGenVisitor next unless union_type.is_a?(PointerInstanceType) is_pointer = equal? type_id, type_id(union_type) - pointer_value = load(bit_cast value_ptr, llvm_type(union_type).pointer) + pointer_value = load(llvm_type(union_type), bit_cast value_ptr, llvm_type(union_type).pointer) pointer_null = null_pointer?(pointer_value) cond = and cond, not(and(is_pointer, pointer_null)) end diff --git a/src/compiler/crystal/codegen/const.cr b/src/compiler/crystal/codegen/const.cr index 3bfd61652093..06df37916d25 100644 --- a/src/compiler/crystal/codegen/const.cr +++ b/src/compiler/crystal/codegen/const.cr @@ -79,7 +79,7 @@ class Crystal::CodeGenVisitor const_type = const.value.type if const_type.passed_by_value? - @last = load @last + @last = load llvm_type(const_type), @last end global.initializer = @last @@ -108,7 +108,7 @@ class Crystal::CodeGenVisitor const_type = const.value.type if const_type.passed_by_value? - @last = load @last + @last = load llvm_type(const_type), @last end store @last, global @@ -159,21 +159,21 @@ class Crystal::CodeGenVisitor request_value(const.value) - if const.value.type.passed_by_value? - @last = load @last + const_type = const.value.type + if const_type.passed_by_value? + @last = load llvm_type(const_type), @last end if @last.constant? global.initializer = @last global.global_constant = true - const_type = const.value.type if const_type.is_a?(PrimitiveType) || const_type.is_a?(EnumType) const.initializer = @last end else - global.initializer = llvm_type(const.value.type).null - unless const.value.type.nil_type? || const.value.type.void? + global.initializer = llvm_type(const_type).null + unless const_type.nil_type? || const_type.void? store @last, global end end diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index 04d9aef2876d..a4f793dc3533 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -492,7 +492,7 @@ module Crystal debug_alloca = alloca alloca.type, "dbg.#{arg_name}" store alloca, debug_alloca declare_parameter(arg_name, arg_type, arg_no, debug_alloca, location) - alloca = load debug_alloca + alloca = load alloca.type, debug_alloca set_current_debug_location old_debug_location alloca end diff --git a/src/compiler/crystal/codegen/exception.cr b/src/compiler/crystal/codegen/exception.cr index 96e4598ce7da..85018fc86ad9 100644 --- a/src/compiler/crystal/codegen/exception.cr +++ b/src/compiler/crystal/codegen/exception.cr @@ -122,7 +122,9 @@ class Crystal::CodeGenVisitor position_at_end catch_body # Allocate space for the caught exception - caught_exception_ptr = alloca llvm_type(@program.exception.virtual_type) + exception_type = @program.exception.virtual_type + exception_llvm_type = llvm_type(exception_type) + caught_exception_ptr = alloca exception_llvm_type # The catchpad instruction dictates which types of exceptions this block handles, # we want all of them, so we rescue all void* by passing the void_ptr_type_descriptor. @@ -133,8 +135,8 @@ class Crystal::CodeGenVisitor # builder.printf("catchpad entered #{node.location}\n", catch_pad: @catch_pad) - caught_exception = load caught_exception_ptr - exception_type_id = type_id(caught_exception, @program.exception.virtual_type) + caught_exception = load exception_llvm_type, caught_exception_ptr + exception_type_id = type_id(caught_exception, exception_type) else # Unwind exception handling code - used on non-windows platforms - is a lot simpler. # First we generate the landing pad instruction, this returns a tuple of the libunwind diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 4c10e569c2a7..cf71cfa00f99 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -159,7 +159,7 @@ class Crystal::CodeGenVisitor # (because the closure data is a pointer) and so we must load the # real value first. if args.first.type.is_a?(PrimitiveType) - primitive_params[0] = load(primitive_params[0]) + primitive_params[0] = load(llvm_type(args.first.type), primitive_params[0]) end codegen_primitive(nil, body, target_def, primitive_params) @@ -225,13 +225,13 @@ class Crystal::CodeGenVisitor ret_type = abi_info.return_type if cast = ret_type.cast casted_last = bit_cast @last, cast.pointer - last = load casted_last + last = load cast, casted_last ret last return end if (attr = ret_type.attr) && attr == LLVM::Attribute::StructRet - store load(@last), context.fun.params[0] + store load(llvm_type(target_def.body.type), @last), context.fun.params[0] ret return end @@ -446,12 +446,12 @@ class Crystal::CodeGenVisitor (parent_vars = closure_parent_context.closure_vars) parent_closure_type = llvm_typer.copy_type(closure_parent_context.closure_type.not_nil!) parent_closure_ptr = gep(closure_type, closure_ptr, 0, closure_vars.size, "parent_ptr") - parent_closure = load(parent_closure_ptr, "parent") + parent_closure = load(parent_closure_type.pointer, parent_closure_ptr, "parent") setup_closure_vars(def_vars, parent_vars, closure_parent_context, parent_closure_type, parent_closure) elsif closure_self = context.closure_self offset = context.closure_parent_context ? 1 : 0 self_value = gep(closure_type, closure_ptr, 0, closure_vars.size + offset, "self") - self_value = load(self_value) unless context.type.passed_by_value? + self_value = load(llvm_type(closure_self), self_value) unless context.type.passed_by_value? self.context.vars["self"] = LLVMVar.new(self_value, closure_self, true) end end diff --git a/src/compiler/crystal/codegen/llvm_builder_helper.cr b/src/compiler/crystal/codegen/llvm_builder_helper.cr index d2848ad95d79..109839eaec37 100644 --- a/src/compiler/crystal/codegen/llvm_builder_helper.cr +++ b/src/compiler/crystal/codegen/llvm_builder_helper.cr @@ -196,7 +196,7 @@ module Crystal end delegate llvm_type, llvm_struct_type, llvm_arg_type, llvm_embedded_type, - llvm_c_type, llvm_c_return_type, llvm_return_type, to: llvm_typer + llvm_c_type, llvm_c_return_type, llvm_return_type, llvm_embedded_c_type, to: llvm_typer def llvm_proc_type(type) llvm_typer.proc_type(type.as(ProcInstanceType)) diff --git a/src/compiler/crystal/codegen/once.cr b/src/compiler/crystal/codegen/once.cr index 90f18ed48b22..9c4363ac1b4b 100644 --- a/src/compiler/crystal/codegen/once.cr +++ b/src/compiler/crystal/codegen/once.cr @@ -18,16 +18,16 @@ class Crystal::CodeGenVisitor def run_once(flag, func) once_fun = main_fun(ONCE) + once_init_fun = main_fun(ONCE_INIT) once_state_global = @llvm_mod.globals[ONCE_STATE]? || begin - once_init_fun = main_fun(ONCE_INIT) global = @llvm_mod.globals.add(once_init_fun.return_type, ONCE_STATE) global.linkage = LLVM::Linkage::External global end call main_fun(ONCE), [ - load(once_state_global), + load(once_init_fun.return_type, once_state_global), flag, bit_cast(func.to_value, once_fun.params.last.type), ] diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index 145e353789f3..9942e685606b 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -860,11 +860,7 @@ class Crystal::CodeGenVisitor name = external.real_name var = declare_lib_var name, node.type, external.thread_local? - @last = call_args[0] - - if external.type.passed_by_value? - @last = load @last - end + @last = extern_to_rhs(call_args[0], external.type) store @last, var @@ -877,11 +873,7 @@ class Crystal::CodeGenVisitor external = target_def.as(External) var = get_external_var(external) - if external.type.passed_by_value? - @last = var - else - @last = load var - end + @last = extern_to_lhs(var, external.type) @last = check_c_fun node.type, @last @@ -910,7 +902,10 @@ class Crystal::CodeGenVisitor end def codegen_primitive_symbol_to_s(node, target_def, call_args) - load(gep @llvm_typer.llvm_type(@program.string).array(@symbol_table_values.size), @llvm_mod.globals[SYMBOL_TABLE_NAME], int(0), call_args[0]) + string = llvm_type(@program.string) + table_type = string.array(@symbol_table_values.size) + string_ptr = gep table_type, @llvm_mod.globals[SYMBOL_TABLE_NAME], int(0), call_args[0] + load(string, string_ptr) end def codegen_primitive_class(node, target_def, call_args) @@ -979,15 +974,10 @@ class Crystal::CodeGenVisitor proc_type = context.type.as(ProcInstanceType) target_def.args.size.times do |i| - arg = args[i] proc_arg_type = proc_type.arg_types[i] target_def_arg_type = target_def.args[i].type - args[i] = upcast arg, proc_arg_type, target_def_arg_type - if proc_arg_type.passed_by_value? - closure_args << load(args[i]) - else - closure_args << args[i] - end + args[i] = arg = upcast args[i], proc_arg_type, target_def_arg_type + closure_args << to_rhs(arg, proc_arg_type) end fun_ptr = builder.extract_value closure_ptr, 0 @@ -1196,7 +1186,7 @@ class Crystal::CodeGenVisitor ordering = atomic_ordering_get_const(call.args[-2], ordering) volatile = bool_from_bool_literal(call.args[-1]) - inst = builder.load(ptr) + inst = builder.load(llvm_type(node.type), ptr) inst.ordering = ordering inst.volatile = true if volatile set_alignment inst, node.type diff --git a/src/compiler/crystal/codegen/type_id.cr b/src/compiler/crystal/codegen/type_id.cr index 2bd562784521..5b447d209943 100644 --- a/src/compiler/crystal/codegen/type_id.cr +++ b/src/compiler/crystal/codegen/type_id.cr @@ -14,11 +14,11 @@ class Crystal::CodeGenVisitor end private def type_id_impl(value, type : ReferenceUnionType) - load(value) + load(llvm_context.int32, value) end private def type_id_impl(value, type : VirtualType) - load(value) + load(llvm_context.int32, value) end private def type_id_impl(value, type : NilableReferenceUnionType) @@ -32,7 +32,7 @@ class Crystal::CodeGenVisitor br exit_block position_at_end not_nil_block - phi_table.add insert_block, load(value) + phi_table.add insert_block, load(llvm_context.int32, value) br exit_block position_at_end exit_block diff --git a/src/compiler/crystal/codegen/unions.cr b/src/compiler/crystal/codegen/unions.cr index 9115cb6a787c..33599d84fef0 100644 --- a/src/compiler/crystal/codegen/unions.cr +++ b/src/compiler/crystal/codegen/unions.cr @@ -59,7 +59,10 @@ module Crystal def union_type_and_value_pointer(union_pointer, type : MixedUnionType) struct_type = llvm_type(type) - {load(union_type_id(struct_type, union_pointer)), union_value(struct_type, union_pointer)} + { + load(llvm_context.int32, union_type_id(struct_type, union_pointer)), + union_value(struct_type, union_pointer), + } end def union_type_id(struct_type, union_pointer) @@ -118,7 +121,7 @@ module Crystal # - cast the target pointer to Pointer(A | B) # - store the A | B from the first pointer into the casted target pointer casted_target_pointer = cast_to_pointer target_pointer, value_type - store load(value), casted_target_pointer + store load(llvm_type(value_type), value), casted_target_pointer end def downcast_distinct_union_types(value, to_type : MixedUnionType, from_type : MixedUnionType) diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index fc04c9b58ba0..11e0affe0ddb 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -91,20 +91,39 @@ class LLVM::Builder Value.new LibLLVM.build_store(self, value, ptr) end - def load(ptr, name = "") + def load(ptr : LLVM::Value, name = "") # check_value(ptr) - Value.new LibLLVM.build_load(self, ptr, name) + {% if LibLLVM::IS_LT_80 %} + Value.new LibLLVM.build_load(self, ptr, name) + {% else %} + Value.new LibLLVM.build_load2(self, ptr.type.element_type, ptr, name) + {% end %} + end + + def load(type : LLVM::Type, ptr : LLVM::Value, name = "") + # check_type("load", type) + # check_value(ptr) + + {% if LibLLVM::IS_LT_80 %} + Value.new LibLLVM.build_load(self, ptr, name) + {% else %} + Value.new LibLLVM.build_load2(self, type, ptr, name) + {% end %} end def store_volatile(value, ptr) store(value, ptr).tap { |v| v.volatile = true } end - def load_volatile(ptr, name = "") + def load_volatile(ptr : LLVM::Value, name = "") load(ptr, name).tap { |v| v.volatile = true } end + def load_volatile(type : LLVM::Type, ptr : LLVM::Value, name = "") + load(type, ptr, name).tap { |v| v.volatile = true } + end + {% for method_name in %w(gep inbounds_gep) %} def {{method_name.id}}(value : LLVM::Value, indices : Array(LLVM::ValueRef), name = "") # check_value(value) diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 5164cc28fd11..6c7b3ffb2c81 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -147,6 +147,9 @@ lib LibLLVM {% end %} fun build_landing_pad = LLVMBuildLandingPad(builder : BuilderRef, ty : TypeRef, pers_fn : ValueRef, num_clauses : UInt32, name : UInt8*) : ValueRef fun build_load = LLVMBuildLoad(builder : BuilderRef, ptr : ValueRef, name : UInt8*) : ValueRef + {% unless LibLLVM::IS_LT_80 %} + fun build_load2 = LLVMBuildLoad2(builder : BuilderRef, ty : TypeRef, ptr : ValueRef, name : UInt8*) : ValueRef + {% end %} fun build_lshr = LLVMBuildLShr(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef fun build_malloc = LLVMBuildMalloc(builder : BuilderRef, type : TypeRef, name : UInt8*) : ValueRef fun build_mul = LLVMBuildMul(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef From 7584f8d3a6d7266794e5f497e82d75770ca9ec80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 7 Mar 2023 09:57:30 +0100 Subject: [PATCH 0354/1551] Fix `Regex::Option` behaviour for unnamed members (#13155) --- spec/std/regex_spec.cr | 16 ++++++++++++++++ src/regex/pcre.cr | 34 ++++++++++++++++++++-------------- src/regex/pcre2.cr | 32 +++++++++++++++++++------------- 3 files changed, 55 insertions(+), 27 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 11213260a030..f8cb2c926549 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -9,6 +9,22 @@ describe "Regex" do it "raises exception with invalid regex" do expect_raises(ArgumentError) { Regex.new("+") } end + + describe "options" do + it "regular" do + Regex.new("", Regex::Options::ANCHORED).options.anchored?.should be_true + end + + it "unnamed option" do + {% if Regex::Engine.resolve.name == "Regex::PCRE" %} + Regex.new("^/foo$", Regex::Options.new(0x00000020)).matches?("/foo\n").should be_false + {% else %} + expect_raises ArgumentError, "Unknown Regex::Option value: 32" do + Regex.new("", Regex::Options.new(32)) + end + {% end %} + end + end end it "#options" do diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr index b1eb227c3cae..9d006c59dcd8 100644 --- a/src/regex/pcre.cr +++ b/src/regex/pcre.cr @@ -21,21 +21,27 @@ module Regex::PCRE private def pcre_options(options) flag = 0 - options.each do |option| - flag |= case option - when .ignore_case? then LibPCRE::CASELESS - when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE - when .extended? then LibPCRE::EXTENDED - when .anchored? then LibPCRE::ANCHORED - when .utf_8? then LibPCRE::UTF8 - when .no_utf8_check? then LibPCRE::NO_UTF8_CHECK - when .dupnames? then LibPCRE::DUPNAMES - when .ucp? then LibPCRE::UCP - else - # Unnamed values are explicitly used PCRE options, just pass them through: - option.value - end + Regex::Options.each do |option| + if options.includes?(option) + flag |= case option + when .ignore_case? then LibPCRE::CASELESS + when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE + when .extended? then LibPCRE::EXTENDED + when .anchored? then LibPCRE::ANCHORED + when .utf_8? then LibPCRE::UTF8 + when .no_utf8_check? then LibPCRE::NO_UTF8_CHECK + when .dupnames? then LibPCRE::DUPNAMES + when .ucp? then LibPCRE::UCP + else + raise "unreachable" + end + options &= ~option + end end + + # Unnamed values are explicitly used PCRE options, just pass them through: + flag |= options.value + flag end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 4df8a61274d8..e2e7ac29d175 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -43,19 +43,25 @@ module Regex::PCRE2 private def pcre2_options(options) flag = 0 - options.each do |option| - flag |= case option - when .ignore_case? then LibPCRE2::CASELESS - when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE - when .extended? then LibPCRE2::EXTENDED - when .anchored? then LibPCRE2::ANCHORED - when .utf_8? then LibPCRE2::UTF - when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK - when .dupnames? then LibPCRE2::DUPNAMES - when .ucp? then LibPCRE2::UCP - else - raise "unreachable" - end + Regex::Options.each do |option| + if options.includes?(option) + flag |= case option + when .ignore_case? then LibPCRE2::CASELESS + when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE + when .extended? then LibPCRE2::EXTENDED + when .anchored? then LibPCRE2::ANCHORED + when .utf_8? then LibPCRE2::UTF + when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK + when .dupnames? then LibPCRE2::DUPNAMES + when .ucp? then LibPCRE2::UCP + else + raise "unreachable" + end + options &= ~option + end + end + unless options.none? + raise ArgumentError.new("Unknown Regex::Option value: #{options}") end flag end From 4efd3c4861980971e9f75ad5b0060f55c60a5b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 6 Mar 2023 11:04:44 +0100 Subject: [PATCH 0355/1551] Improve PCRE2 match performance for JIT and interpreted (#13146) --- src/regex/lib_pcre2.cr | 25 ++++++++++-------------- src/regex/pcre2.cr | 43 ++++++++++++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index b81de3399311..5fac84326a13 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -172,10 +172,10 @@ lib LibPCRE2 CONFIG_COMPILED_WIDTHS = 14 CONFIG_TABLES_LENGTH = 15 - type Code = Void* - type CompileContext = Void* - type MatchData = Void* - type GeneralContext = Void* + type Code = Void + type CompileContext = Void + type MatchData = Void + type GeneralContext = Void fun get_error_message = pcre2_get_error_message_8(errorcode : Int, buffer : UInt8*, bufflen : LibC::SizeT) : Int @@ -183,7 +183,7 @@ lib LibPCRE2 fun code_free = pcre2_code_free_8(code : Code*) : Void type MatchContext = Void* - fun match_context_create = pcre2_match_context_create_8(gcontext : Void*) : MatchContext + fun match_context_create = pcre2_match_context_create_8(gcontext : Void*) : MatchContext* JIT_COMPLETE = 0x00000001_u32 # For full matching JIT_PARTIAL_SOFT = 0x00000002_u32 @@ -191,15 +191,15 @@ lib LibPCRE2 JIT_INVALID_UTF = 0x00000100_u32 fun jit_compile = pcre2_jit_compile_8(code : Code*, options : UInt32) : Int - type JITStack = Void* + type JITStack = Void - fun jit_stack_create = pcre2_jit_stack_create_8(startsize : LibC::SizeT, maxsize : LibC::SizeT, gcontext : GeneralContext) : JITStack - fun jit_stack_assign = pcre2_jit_stack_assign_8(mcontext : MatchContext, callable_function : Void*, callable_data : Void*) : Void + fun jit_stack_create = pcre2_jit_stack_create_8(startsize : LibC::SizeT, maxsize : LibC::SizeT, gcontext : GeneralContext*) : JITStack* + fun jit_stack_assign = pcre2_jit_stack_assign_8(mcontext : MatchContext*, callable_function : Void* -> JITStack*, callable_data : Void*) : Void fun pattern_info = pcre2_pattern_info_8(code : Code*, what : UInt32, where : Void*) : Int - fun match = pcre2_match_8(code : Code*, subject : UInt8*, length : LibC::SizeT, startoffset : LibC::SizeT, options : UInt32, match_data : MatchData*, mcontext : MatchContext) : Int - fun match_data_create_from_pattern = pcre2_match_data_create_from_pattern_8(code : Code*, gcontext : GeneralContext) : MatchData* + fun match = pcre2_match_8(code : Code*, subject : UInt8*, length : LibC::SizeT, startoffset : LibC::SizeT, options : UInt32, match_data : MatchData*, mcontext : MatchContext*) : Int + fun match_data_create_from_pattern = pcre2_match_data_create_from_pattern_8(code : Code*, gcontext : GeneralContext*) : MatchData* fun match_data_free = pcre2_match_data_free_8(match_data : MatchData*) : Void fun substring_nametable_scan = pcre2_substring_nametable_scan_8(code : Code*, name : UInt8*, first : UInt8*, last : UInt8*) : Int @@ -207,10 +207,5 @@ lib LibPCRE2 fun get_ovector_pointer = pcre2_get_ovector_pointer_8(match_data : MatchData*) : LibC::SizeT* fun get_ovector_count = pcre2_get_ovector_count_8(match_data : MatchData*) : UInt32 - fun general_context_create = pcre2_general_context_create_8( - private_malloc : LibC::SizeT, Void* -> Void, - private_free : Void*, Void* -> Void, - memory_data : Void* - ) : GeneralContext fun config = pcre2_config_8(what : UInt32, where : Void*) : Int end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index f2eab8313e4e..6a52b318da39 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -123,10 +123,15 @@ module Regex::PCRE2 private def match_impl(str, byte_index, options) match_data = match_data(str, byte_index, options) || return - ovector = LibPCRE2.get_ovector_pointer(match_data) ovector_count = LibPCRE2.get_ovector_count(match_data) + ovector = Slice.new(LibPCRE2.get_ovector_pointer(match_data), ovector_count &* 2) - ::Regex::MatchData.new(self, @re, str, byte_index, ovector, ovector_count.to_i32 - 1) + # We need to dup the ovector because `match_data` is re-used for subsequent + # matches (see `@match_data`). + # Dup brings the ovector data into the realm of the GC. + ovector = ovector.dup + + ::Regex::MatchData.new(self, @re, str, byte_index, ovector.to_unsafe, ovector_count.to_i32 &- 1) end private def matches_impl(str, byte_index, options) @@ -137,27 +142,45 @@ module Regex::PCRE2 end end - class_getter general_context do - LibPCRE2.general_context_create(->(size, _data) { GC.malloc(size) }, ->(pointer, _data) { GC.free(pointer) }, nil) + class_getter match_context : LibPCRE2::MatchContext* do + match_context = LibPCRE2.match_context_create(nil) + LibPCRE2.jit_stack_assign(match_context, ->(_data) { Regex::PCRE2.jit_stack }, nil) + match_context end # Returns a JIT stack that's shared in the current thread. # # Only a single `match` function can run per thread at any given time, so there # can't be any concurrent access to the JIT stack. - @@jit_stack = Crystal::ThreadLocalValue(LibPCRE2::JITStack).new + @@jit_stack = Crystal::ThreadLocalValue(LibPCRE2::JITStack*).new def self.jit_stack @@jit_stack.get do - LibPCRE2.jit_stack_create(32_768, 1_048_576, general_context) || raise "Error allocating JIT stack" + LibPCRE2.jit_stack_create(32_768, 1_048_576, nil) || raise "Error allocating JIT stack" + end + end + + # Match data is shared per instance and thread. + # + # Match data contains a buffer for backtracking when matching in interpreted mode (non-JIT). + # This buffer is heap-allocated and should be re-used for subsequent matches. + @match_data = Crystal::ThreadLocalValue(LibPCRE2::MatchData*).new + + private def match_data + @match_data.get do + LibPCRE2.match_data_create_from_pattern(@re, nil) + end + end + + def finalize + @match_data.consume_each do |match_data| + LibPCRE2.match_data_free(match_data) end end private def match_data(str, byte_index, options) - match_data = LibPCRE2.match_data_create_from_pattern(@re, Regex::PCRE2.general_context) - match_context = LibPCRE2.match_context_create(nil) - LibPCRE2.jit_stack_assign(match_context, nil, Regex::PCRE2.jit_stack.as(Void*)) if @jit - match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, match_context) + match_data = self.match_data + match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, PCRE2.match_context) if match_count < 0 case error = LibPCRE2::Error.new(match_count) From 6a7291ef782cc5dbbaaba066c49a79e208bcd624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 7 Mar 2023 09:57:30 +0100 Subject: [PATCH 0356/1551] Fix `Regex::Option` behaviour for unnamed members (#13155) --- spec/std/regex_spec.cr | 16 ++++++++++++++++ src/regex/pcre.cr | 34 ++++++++++++++++++++-------------- src/regex/pcre2.cr | 32 +++++++++++++++++++------------- 3 files changed, 55 insertions(+), 27 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 11213260a030..f8cb2c926549 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -9,6 +9,22 @@ describe "Regex" do it "raises exception with invalid regex" do expect_raises(ArgumentError) { Regex.new("+") } end + + describe "options" do + it "regular" do + Regex.new("", Regex::Options::ANCHORED).options.anchored?.should be_true + end + + it "unnamed option" do + {% if Regex::Engine.resolve.name == "Regex::PCRE" %} + Regex.new("^/foo$", Regex::Options.new(0x00000020)).matches?("/foo\n").should be_false + {% else %} + expect_raises ArgumentError, "Unknown Regex::Option value: 32" do + Regex.new("", Regex::Options.new(32)) + end + {% end %} + end + end end it "#options" do diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr index ff68509bed9e..9c3aeae2894b 100644 --- a/src/regex/pcre.cr +++ b/src/regex/pcre.cr @@ -21,21 +21,27 @@ module Regex::PCRE private def pcre_options(options) flag = 0 - options.each do |option| - flag |= case option - when .ignore_case? then LibPCRE::CASELESS - when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE - when .extended? then LibPCRE::EXTENDED - when .anchored? then LibPCRE::ANCHORED - when .utf_8? then LibPCRE::UTF8 - when .no_utf8_check? then LibPCRE::NO_UTF8_CHECK - when .dupnames? then LibPCRE::DUPNAMES - when .ucp? then LibPCRE::UCP - else - # Unnamed values are explicitly used PCRE options, just pass them through: - option.value - end + Regex::Options.each do |option| + if options.includes?(option) + flag |= case option + when .ignore_case? then LibPCRE::CASELESS + when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE + when .extended? then LibPCRE::EXTENDED + when .anchored? then LibPCRE::ANCHORED + when .utf_8? then LibPCRE::UTF8 + when .no_utf8_check? then LibPCRE::NO_UTF8_CHECK + when .dupnames? then LibPCRE::DUPNAMES + when .ucp? then LibPCRE::UCP + else + raise "unreachable" + end + options &= ~option + end end + + # Unnamed values are explicitly used PCRE options, just pass them through: + flag |= options.value + flag end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 6a52b318da39..ebc09773bdfb 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -43,19 +43,25 @@ module Regex::PCRE2 private def pcre2_options(options) flag = 0 - options.each do |option| - flag |= case option - when .ignore_case? then LibPCRE2::CASELESS - when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE - when .extended? then LibPCRE2::EXTENDED - when .anchored? then LibPCRE2::ANCHORED - when .utf_8? then LibPCRE2::UTF - when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK - when .dupnames? then LibPCRE2::DUPNAMES - when .ucp? then LibPCRE2::UCP - else - raise "unreachable" - end + Regex::Options.each do |option| + if options.includes?(option) + flag |= case option + when .ignore_case? then LibPCRE2::CASELESS + when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE + when .extended? then LibPCRE2::EXTENDED + when .anchored? then LibPCRE2::ANCHORED + when .utf_8? then LibPCRE2::UTF + when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK + when .dupnames? then LibPCRE2::DUPNAMES + when .ucp? then LibPCRE2::UCP + else + raise "unreachable" + end + options &= ~option + end + end + unless options.none? + raise ArgumentError.new("Unknown Regex::Option value: #{options}") end flag end From d61a01e185baf84046efce50832a97f097ba0a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 7 Mar 2023 15:25:01 +0100 Subject: [PATCH 0357/1551] Add changelog for 1.7.3 (#13142) --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index faa5d173025b..283dd38a0b85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +# 1.7.3 + +## Standard Library + +### Text + +- Do not use `@[ThreadLocal]` for PCRE2's JIT stack ([#13056](https://github.com/crystal-lang/crystal/pull/13056), thanks @HertzDevil) +- Fix `libpcre2` bindings with arch-dependent types (`SizeT`) ([#13088](https://github.com/crystal-lang/crystal/pull/13088), thanks @straight-shoota) +- Fix `libpcre2` bindings function pointers ([#13090](https://github.com/crystal-lang/crystal/pull/13090), thanks @straight-shoota) +- Fix PCRE2 do not allocate JIT stack if unavailable ([#13100](https://github.com/crystal-lang/crystal/pull/13100), thanks @straight-shoota) +- Backport PCRE2 fixes to 1.7 ([#13136](https://github.com/crystal-lang/crystal/pull/13136), thanks @straight-shoota) +- Fix `MatchData#[]` named capture with identical prefix ([#13147](https://github.com/crystal-lang/crystal/pull/13147), thanks @straight-shoota) +- Fix `Regex::Option` behaviour for unnamed members ([#13155](https://github.com/crystal-lang/crystal/pull/13155), thanks @straight-shoota) +- **(performance)** Improve PCRE2 match performance for JIT and interpreted ([#13146](https://github.com/crystal-lang/crystal/pull/13146), thanks @straight-shoota) + +## Compiler + +### Generics + +- Explicitly treat unbound type vars in generic class methods as free variables ([#13125](https://github.com/crystal-lang/crystal/pull/13125), thanks @HertzDevil) + +## Other + +- [CI] Fix add PCRE2 to GHA cache for win job ([#13089](https://github.com/crystal-lang/crystal/pull/13089), thanks @straight-shoota) +- [CI] Pin `use_pcre` in build environments where PCRE2 is not yet available ([#13102](https://github.com/crystal-lang/crystal/pull/13102), thanks @straight-shoota) + # 1.7.2 (2023-01-23) ## Standard Library diff --git a/src/VERSION b/src/VERSION index f8a696c8dc56..661e7aeadf36 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.7.2 +1.7.3 From dd4b89ff8e58f9748a59fa7f7879e798340579f2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 8 Mar 2023 17:39:00 +0800 Subject: [PATCH 0358/1551] Update NOTICE.md (#13159) --- NOTICE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index 2824f40372df..28390dcb8644 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -12,7 +12,7 @@ Please see [LICENSE](/LICENSE) for additional copyright and licensing informatio Crystal compiler links the following libraries, which have their own license: - * [LLVM][] - [BSD-3, effectively][] + * [LLVM][] - [Apache-2.0 with LLVM exceptions][] * [PCRE or PCRE2][] - [BSD-3][] * [libevent2][] - [BSD-3][] * [libiconv][] - [LGPLv3][] @@ -24,7 +24,7 @@ Crystal compiler calls the following tools as external process on compiling, whi Crystal standard library uses the following libraries, which have their own licenses: - * [LLVM][] - [BSD-3, effectively][] + * [LLVM][] - [Apache-2.0 with LLVM exceptions][] * [PCRE or PCRE2][] - [BSD-3][] * [libevent2][] - [BSD-3][] * [libiconv][] - [LGPLv3][] @@ -48,8 +48,8 @@ Crystal playground includes the following libraries, which have their own licens [Apache-2.0]: https://www.openssl.org/source/apache-license-2.0.txt +[Apache-2.0 with LLVM exceptions]: https://raw.githubusercontent.com/llvm/llvm-project/main/llvm/LICENSE.TXT [BSD-3]: https://opensource.org/licenses/BSD-3-Clause -[BSD-3, effectively]: http://releases.llvm.org/2.8/LICENSE.TXT [GPLv3]: https://www.gnu.org/licenses/gpl-3.0.en.html [LGPLv3]: https://www.gnu.org/licenses/lgpl-3.0.en.html [MIT]: https://opensource.org/licenses/MIT From bbe28a0e189eccf16f0100b6e0cf8bbdbd99b4ee Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 8 Mar 2023 18:13:15 +0800 Subject: [PATCH 0359/1551] Add full stub for Windows signals (#13131) --- spec/std/process_spec.cr | 40 +-- src/crystal/system/file.cr | 2 +- src/crystal/system/signal.cr | 20 ++ src/crystal/system/unix/process.cr | 6 +- src/crystal/system/unix/signal.cr | 270 +++++++++++++++ src/crystal/system/wasi/signal.cr | 13 + src/crystal/system/win32/signal.cr | 15 + src/docs_main.cr | 4 +- src/exception/call_stack/libunwind.cr | 2 +- src/kernel.cr | 6 +- src/lib_c/x86_64-windows-msvc/c/signal.cr | 9 + src/prelude.cr | 5 +- src/process.cr | 4 + src/process/status.cr | 3 + src/signal.cr | 394 +++++----------------- src/windows_stubs.cr | 3 - 16 files changed, 445 insertions(+), 351 deletions(-) create mode 100644 src/crystal/system/signal.cr create mode 100644 src/crystal/system/unix/signal.cr create mode 100644 src/crystal/system/wasi/signal.cr create mode 100644 src/crystal/system/win32/signal.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/signal.cr delete mode 100644 src/windows_stubs.cr diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 349281ec16e8..1088c0130b35 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -325,26 +325,28 @@ describe Process do end end - describe "#signal" do - pending_win32 "kills a process" do - process = Process.new(*standing_command) - process.signal(Signal::KILL).should be_nil - ensure - process.try &.wait - end + {% unless flag?(:win32) %} + describe "#signal(Signal::KILL)" do + it "kills a process" do + process = Process.new(*standing_command) + process.signal(Signal::KILL).should be_nil + ensure + process.try &.wait + end - pending_win32 "kills many process" do - process1 = Process.new(*standing_command) - process2 = Process.new(*standing_command) - process1.signal(Signal::KILL).should be_nil - process2.signal(Signal::KILL).should be_nil - ensure - process1.try &.wait - process2.try &.wait + it "kills many process" do + process1 = Process.new(*standing_command) + process2 = Process.new(*standing_command) + process1.signal(Signal::KILL).should be_nil + process2.signal(Signal::KILL).should be_nil + ensure + process1.try &.wait + process2.try &.wait + end end - end + {% end %} - pending_win32 "#terminate" do + it "#terminate" do process = Process.new(*standing_command) process.exists?.should be_true process.terminated?.should be_false @@ -368,7 +370,7 @@ describe Process do process.terminated?.should be_false # Kill, zombie now - process.signal(Signal::KILL) + process.terminate process.exists?.should be_true process.terminated?.should be_false @@ -381,7 +383,7 @@ describe Process do pending_win32 ".pgid" do process = Process.new(*standing_command) Process.pgid(process.pid).should be_a(Int64) - process.signal(Signal::KILL) + process.terminate Process.pgid.should eq(Process.pgid(Process.pid)) ensure process.try(&.wait) diff --git a/src/crystal/system/file.cr b/src/crystal/system/file.cr index b5202585d377..0f645fd8a3ec 100644 --- a/src/crystal/system/file.cr +++ b/src/crystal/system/file.cr @@ -86,7 +86,7 @@ module Crystal::System::File # Closes the internal file descriptor without notifying libevent. # This is directly used after the fork of a process to close the - # parent's Crystal::Signal.@@pipe reference before re initializing + # parent's Crystal::System::Signal.@@pipe reference before re initializing # the event loop. In the case of a fork that will exec there is even # no need to initialize the event loop at all. # def file_descriptor_close diff --git a/src/crystal/system/signal.cr b/src/crystal/system/signal.cr new file mode 100644 index 000000000000..b5ba591b2ec5 --- /dev/null +++ b/src/crystal/system/signal.cr @@ -0,0 +1,20 @@ +module Crystal::System::Signal + # Sets the handler for this signal to the passed function. + # def self.trap(signal, handler) : Nil + + # Resets the handler for this signal to the OS default. + # def self.reset(signal) : Nil + + # Clears the handler for this signal and prevents the OS default action. + # def self.ignore(signal) : Nil +end + +{% if flag?(:wasi) %} + require "./wasi/signal" +{% elsif flag?(:unix) %} + require "./unix/signal" +{% elsif flag?(:win32) %} + require "./win32/signal" +{% else %} + {% raise "No Crystal::System::Signal implementation available" %} +{% end %} diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 407ebebc6151..ce536b354e50 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -8,7 +8,7 @@ struct Crystal::System::Process getter pid : LibC::PidT def initialize(@pid : LibC::PidT) - @channel = Crystal::SignalChildHandler.wait(@pid) + @channel = Crystal::System::SignalChildHandler.wait(@pid) end def release @@ -71,7 +71,7 @@ struct Crystal::System::Process end def self.start_interrupt_loop : Nil - # do nothing; `Crystal::Signal.start_loop` takes care of this + # do nothing; `Crystal::System::Signal.start_loop` takes care of this end def self.exists?(pid) @@ -110,7 +110,7 @@ struct Crystal::System::Process pid = nil if will_exec # reset signal handlers, then sigmask (inherited on exec): - Crystal::Signal.after_fork_before_exec + Crystal::System::Signal.after_fork_before_exec LibC.sigemptyset(pointerof(newmask)) LibC.pthread_sigmask(LibC::SIG_SETMASK, pointerof(newmask), nil) else diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr new file mode 100644 index 000000000000..c30a2b985af2 --- /dev/null +++ b/src/crystal/system/unix/signal.cr @@ -0,0 +1,270 @@ +require "c/signal" +require "c/stdio" +require "c/sys/wait" +require "c/unistd" + +module Crystal::System::Signal + # The number of libc functions that can be called safely from a signal(2) + # handler is very limited. An usual safe solution is to use a pipe(2) and + # just write the signal to the file descriptor and nothing more. A loop in + # the main program is responsible for reading the signals back from the + # pipe(2) and handle the signal there. + + alias Handler = ::Signal -> + + @@pipe = IO.pipe(read_blocking: false, write_blocking: true) + @@handlers = {} of ::Signal => Handler + @@sigset = Sigset.new + class_setter child_handler : Handler? + @@mutex = Mutex.new(:unchecked) + + def self.trap(signal, handler) : Nil + @@mutex.synchronize do + unless @@handlers[signal]? + @@sigset << signal + LibC.signal(signal.value, ->(value : Int32) { + writer.write_bytes(value) unless writer.closed? + }) + end + @@handlers[signal] = handler + end + end + + def self.reset(signal) : Nil + set(signal, LibC::SIG_DFL) + end + + def self.ignore(signal) : Nil + set(signal, LibC::SIG_IGN) + end + + private def self.set(signal, handler) + if signal == ::Signal::CHLD + # Clear any existing signal child handler + @@child_handler = nil + # But keep a default SIGCHLD, Process#wait requires it + trap(signal, ->(signal : ::Signal) { + SignalChildHandler.call + @@child_handler.try(&.call(signal)) + }) + else + @@mutex.synchronize do + @@handlers.delete(signal) + LibC.signal(signal, handler) + @@sigset.delete(signal) + end + end + end + + private def self.start_loop + spawn(name: "Signal Loop") do + loop do + value = reader.read_bytes(Int32) + rescue IO::Error + next + else + process(::Signal.new(value)) + end + end + end + + private def self.process(signal) : Nil + if handler = @@handlers[signal]? + non_nil_handler = handler # if handler is closured it will also have the Nil type + spawn do + non_nil_handler.call(signal) + rescue ex + ex.inspect_with_backtrace(STDERR) + fatal("uncaught exception while processing handler for #{signal}") + end + else + fatal("missing handler for #{signal}") + end + end + + # Replaces the signal pipe so the child process won't share the file + # descriptors of the parent process and send it received signals. + def self.after_fork + @@pipe.each(&.file_descriptor_close) + ensure + @@pipe = IO.pipe(read_blocking: false, write_blocking: true) + end + + # Resets signal handlers to `SIG_DFL`. This avoids the child to receive + # signals that would be sent to the parent process through the signal + # pipe. + # + # We keep a signal set to because accessing @@handlers isn't thread safe —a + # thread could be mutating the hash while another one forked. This allows to + # only reset a few signals (fast) rather than all (very slow). + # + # We eventually close the pipe anyway to avoid a potential race where a sigset + # wouldn't exactly reflect actual signal state. This avoids sending a children + # signal to the parent. Exec will reset the signals properly for the + # sub-process. + def self.after_fork_before_exec + ::Signal.each do |signal| + LibC.signal(signal, LibC::SIG_DFL) if @@sigset.includes?(signal) + end + ensure + {% unless flag?(:preview_mt) %} + @@pipe.each(&.file_descriptor_close) + {% end %} + end + + private def self.reader + @@pipe[0] + end + + private def self.writer + @@pipe[1] + end + + private def self.fatal(message : String) + STDERR.puts("FATAL: #{message}, exiting") + STDERR.flush + LibC._exit(1) + end + + @@setup_default_handlers = Atomic::Flag.new + @@setup_segfault_handler = Atomic::Flag.new + @@segfault_handler = LibC::SigactionHandlerT.new { |sig, info, data| + # Capture fault signals (SEGV, BUS) and finish the process printing a backtrace first + + # Determine if the SEGV was inside or 'near' the top of the stack + # to check for potential stack overflow. 'Near' is a small + # amount larger than a typical stack frame, 4096 bytes here. + addr = info.value.si_addr + + is_stack_overflow = + begin + stack_top = Pointer(Void).new(::Fiber.current.@stack.address - 4096) + stack_bottom = ::Fiber.current.@stack_bottom + stack_top <= addr < stack_bottom + rescue e + Crystal::System.print_error "Error while trying to determine if a stack overflow has occurred. Probable memory corruption\n" + false + end + + if is_stack_overflow + Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n" + else + Crystal::System.print_error "Invalid memory access (signal %d) at address 0x%lx\n", sig, addr + end + + Exception::CallStack.print_backtrace + LibC._exit(sig) + } + + def self.setup_default_handlers : Nil + return unless @@setup_default_handlers.test_and_set + @@sigset.clear + start_loop + ::Signal::PIPE.ignore + ::Signal::CHLD.reset + end + + def self.setup_segfault_handler + return unless @@setup_segfault_handler.test_and_set + + altstack = LibC::StackT.new + altstack.ss_sp = LibC.malloc(LibC::SIGSTKSZ) + altstack.ss_size = LibC::SIGSTKSZ + altstack.ss_flags = 0 + LibC.sigaltstack(pointerof(altstack), nil) + + action = LibC::Sigaction.new + action.sa_flags = LibC::SA_ONSTACK | LibC::SA_SIGINFO + action.sa_sigaction = @@segfault_handler + LibC.sigemptyset(pointerof(action.@sa_mask)) + + LibC.sigaction(::Signal::SEGV, pointerof(action), nil) + LibC.sigaction(::Signal::BUS, pointerof(action), nil) + end +end + +struct Crystal::System::Sigset + {% if flag?(:darwin) || flag?(:openbsd) %} + @set = LibC::SigsetT.new(0) + {% else %} + @set = LibC::SigsetT.new + {% end %} + + def to_unsafe + pointerof(@set) + end + + def <<(signal) : Nil + LibC.sigaddset(pointerof(@set), signal) + end + + def delete(signal) : Nil + LibC.sigdelset(pointerof(@set), signal) + end + + def includes?(signal) : Bool + LibC.sigismember(pointerof(@set), signal) == 1 + end + + def clear : Nil + LibC.sigemptyset(pointerof(@set)) + end +end + +module Crystal::System::SignalChildHandler + # Process#wait will block until the sub-process has terminated. On POSIX + # systems, the SIGCHLD signal is triggered. We thus always trap SIGCHLD then + # reap/memorize terminated child processes and eventually notify + # Process#wait through a channel, that may be created before or after the + # child process exited. + + @@pending = {} of LibC::PidT => Int32 + @@waiting = {} of LibC::PidT => Channel(Int32) + @@mutex = Mutex.new(:unchecked) + + def self.wait(pid : LibC::PidT) : Channel(Int32) + channel = Channel(Int32).new(1) + + @@mutex.lock + if exit_code = @@pending.delete(pid) + @@mutex.unlock + channel.send(exit_code) + channel.close + else + @@waiting[pid] = channel + @@mutex.unlock + end + + channel + end + + def self.call : Nil + loop do + pid = LibC.waitpid(-1, out exit_code, LibC::WNOHANG) + + case pid + when 0 + return + when -1 + return if Errno.value == Errno::ECHILD + raise RuntimeError.from_errno("waitpid") + else + @@mutex.lock + if channel = @@waiting.delete(pid) + @@mutex.unlock + channel.send(exit_code) + channel.close + else + @@pending[pid] = exit_code + @@mutex.unlock + end + end + end + end + + def self.after_fork + @@pending.clear + @@waiting.each_value(&.close) + @@waiting.clear + end +end diff --git a/src/crystal/system/wasi/signal.cr b/src/crystal/system/wasi/signal.cr new file mode 100644 index 000000000000..d66b9c22c5cd --- /dev/null +++ b/src/crystal/system/wasi/signal.cr @@ -0,0 +1,13 @@ +module Crystal::System::Signal + def self.trap(signal, handler) : Nil + raise NotImplementedError.new("Crystal::System::Signal.trap") + end + + def self.reset(signal) : Nil + raise NotImplementedError.new("Crystal::System::Signal.reset") + end + + def self.ignore(signal) : Nil + raise NotImplementedError.new("Crystal::System::Signal.ignore") + end +end diff --git a/src/crystal/system/win32/signal.cr b/src/crystal/system/win32/signal.cr new file mode 100644 index 000000000000..8f5541c7599b --- /dev/null +++ b/src/crystal/system/win32/signal.cr @@ -0,0 +1,15 @@ +require "c/signal" + +module Crystal::System::Signal + def self.trap(signal, handler) : Nil + raise NotImplementedError.new("Crystal::System::Signal.trap") + end + + def self.reset(signal) : Nil + raise NotImplementedError.new("Crystal::System::Signal.reset") + end + + def self.ignore(signal) : Nil + raise NotImplementedError.new("Crystal::System::Signal.ignore") + end +end diff --git a/src/docs_main.cr b/src/docs_main.cr index 0a4f3962b864..661163677b7c 100644 --- a/src/docs_main.cr +++ b/src/docs_main.cr @@ -47,9 +47,7 @@ require "./option_parser" require "./path" require "./random/**" require "./semantic_version" -{% unless flag?(:win32) %} - require "./signal" -{% end %} +require "./signal" require "./string_pool" require "./string_scanner" require "./unicode/unicode" diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index ecb400bee0f7..21683c0351ef 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -33,7 +33,7 @@ struct Exception::CallStack {% end %} def self.setup_crash_handler - Signal.setup_segfault_handler + Crystal::System::Signal.setup_segfault_handler end {% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %} diff --git a/src/kernel.cr b/src/kernel.cr index 45df75ef1dbe..1afd480ae959 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -536,8 +536,8 @@ end def self.after_fork_child_callbacks @@after_fork_child_callbacks ||= [ # clean ups (don't depend on event loop): - ->Crystal::Signal.after_fork, - ->Crystal::SignalChildHandler.after_fork, + ->Crystal::System::Signal.after_fork, + ->Crystal::System::SignalChildHandler.after_fork, # reinit event loop: ->{ Crystal::Scheduler.event_loop.after_fork }, @@ -561,7 +561,7 @@ end {% if flag?(:win32) %} Crystal::System::Process.start_interrupt_loop {% else %} - Signal.setup_default_handlers + Crystal::System::Signal.setup_default_handlers {% end %} # load debug info on start up of the program is executed with CRYSTAL_LOAD_DEBUG_INFO=1 diff --git a/src/lib_c/x86_64-windows-msvc/c/signal.cr b/src/lib_c/x86_64-windows-msvc/c/signal.cr new file mode 100644 index 000000000000..fbe90cd8eca3 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/signal.cr @@ -0,0 +1,9 @@ +lib LibC + SIGINT = 2 + SIGILL = 4 + SIGFPE = 8 + SIGSEGV = 11 + SIGTERM = 15 + SIGBREAK = 21 + SIGABRT = 22 +end diff --git a/src/prelude.cr b/src/prelude.cr index b6f98b2dd25c..f06f5bc87015 100644 --- a/src/prelude.cr +++ b/src/prelude.cr @@ -13,9 +13,6 @@ require "lib_c" require "macros" require "object" require "comparable" -{% if flag?(:win32) %} - require "windows_stubs" -{% end %} require "exception" require "iterable" require "iterator" @@ -70,7 +67,7 @@ require "range" require "reference" require "regex" require "set" -{% unless flag?(:win32) || flag?(:wasm32) %} +{% unless flag?(:wasm32) %} require "signal" {% end %} require "slice" diff --git a/src/process.cr b/src/process.cr index 69d3953e6a7a..358511aebca5 100644 --- a/src/process.cr +++ b/src/process.cr @@ -305,6 +305,10 @@ class Process end # Sends *signal* to this process. + # + # NOTE: `#terminate` is preferred over `signal(Signal::TERM)` and + # `signal(Signal::KILL)` as a portable alternative which also works on + # Windows. def signal(signal : Signal) : Nil Crystal::System::Process.signal(@process_info.pid, signal) end diff --git a/src/process/status.cr b/src/process/status.cr index 06b6ff103ae1..c7b78b1a4583 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -178,6 +178,9 @@ class Process::Status # received and didn't handle. Will raise if `signal_exit?` is `false`. # # Available only on Unix-like operating systems. + # + # NOTE: `#exit_reason` is preferred over this method as a portable alternative + # which also works on Windows. def exit_signal : Signal {% if flag?(:unix) && !flag?(:wasm32) %} Signal.from_value(signal_code) diff --git a/src/signal.cr b/src/signal.cr index 9335619d043f..60eba5b8e7f3 100644 --- a/src/signal.cr +++ b/src/signal.cr @@ -1,7 +1,4 @@ -require "c/signal" -require "c/stdio" -require "c/sys/wait" -require "c/unistd" +require "crystal/system/signal" # Safely handle inter-process signals on POSIX systems. # @@ -24,45 +21,75 @@ require "c/unistd" # sleep 3 # ``` # -# NOTE: `Process.on_interrupt` is preferred over `Signal::INT.trap`, as the -# former also works on Windows. -# # WARNING: An uncaught exception in a signal handler is a fatal error. +# +# ## Portability +# +# The set of available signals is platform-dependent. Only signals that exist on +# the target platform are available as members of this enum. +# +# * `ABRT`, `FPE`, `ILL`, `INT`, `SEGV`, and `TERM` are guaranteed to exist +# on all platforms. +# * `PWR`, `STKFLT`, and `UNUSED` only exist on Linux. +# * `BREAK` only exists on Windows. +# * All other signals exist on all POSIX platforms. +# +# The methods `#trap`, `#reset`, and `#ignore` may not be implemented at all on +# non-POSIX systems. +# +# The standard library provides several platform-agnostic APIs to achieve tasks +# that are typically solved with signals on POSIX systems: +# +# * The portable API for responding to an interrupt signal (`INT.trap`) is +# `Process.on_interrupt`. +# * The portable API for sending a `TERM` or `KILL` signal to a process is +# `Process#terminate`. +# * The portable API for retrieving the exit signal of a process +# (`Process::Status#exit_signal`) is `Process::Status#exit_reason`. enum Signal : Int32 - HUP = LibC::SIGHUP - INT = LibC::SIGINT - QUIT = LibC::SIGQUIT - ILL = LibC::SIGILL - TRAP = LibC::SIGTRAP - IOT = LibC::SIGIOT - ABRT = LibC::SIGABRT - FPE = LibC::SIGFPE - KILL = LibC::SIGKILL - BUS = LibC::SIGBUS - SEGV = LibC::SIGSEGV - SYS = LibC::SIGSYS - PIPE = LibC::SIGPIPE - ALRM = LibC::SIGALRM - TERM = LibC::SIGTERM - URG = LibC::SIGURG - STOP = LibC::SIGSTOP - TSTP = LibC::SIGTSTP - CONT = LibC::SIGCONT - CHLD = LibC::SIGCHLD - TTIN = LibC::SIGTTIN - TTOU = LibC::SIGTTOU - IO = LibC::SIGIO - XCPU = LibC::SIGXCPU - XFSZ = LibC::SIGXFSZ - VTALRM = LibC::SIGVTALRM - USR1 = LibC::SIGUSR1 - USR2 = LibC::SIGUSR2 - WINCH = LibC::SIGWINCH - - {% if flag?(:linux) %} - PWR = LibC::SIGPWR - STKFLT = LibC::SIGSTKFLT - UNUSED = LibC::SIGUNUSED + # Signals required by the ISO C standard. Since every supported platform must + # bind against a C runtime library, these constants must be defined at all + # times, even when the platform does not support POSIX signals + + INT = LibC::SIGINT + ILL = LibC::SIGILL + FPE = LibC::SIGFPE + SEGV = LibC::SIGSEGV + TERM = LibC::SIGTERM + ABRT = LibC::SIGABRT + + {% if flag?(:win32) %} + BREAK = LibC::SIGBREAK + {% else %} + HUP = LibC::SIGHUP + QUIT = LibC::SIGQUIT + TRAP = LibC::SIGTRAP + IOT = LibC::SIGIOT + KILL = LibC::SIGKILL + BUS = LibC::SIGBUS + SYS = LibC::SIGSYS + PIPE = LibC::SIGPIPE + ALRM = LibC::SIGALRM + URG = LibC::SIGURG + STOP = LibC::SIGSTOP + TSTP = LibC::SIGTSTP + CONT = LibC::SIGCONT + CHLD = LibC::SIGCHLD + TTIN = LibC::SIGTTIN + TTOU = LibC::SIGTTOU + IO = LibC::SIGIO + XCPU = LibC::SIGXCPU + XFSZ = LibC::SIGXFSZ + VTALRM = LibC::SIGVTALRM + USR1 = LibC::SIGUSR1 + USR2 = LibC::SIGUSR2 + WINCH = LibC::SIGWINCH + + {% if flag?(:linux) %} + PWR = LibC::SIGPWR + STKFLT = LibC::SIGSTKFLT + UNUSED = LibC::SIGUNUSED + {% end %} {% end %} # Sets the handler for this signal to the passed function. @@ -77,12 +104,17 @@ enum Signal : Int32 # before the custom handler is called, hence a custom `CHLD` handler must # check child processes using `Process.exists?`. Trying to use waitpid with a # zero or negative value won't work. + # + # NOTE: `Process.on_interrupt` is preferred over `Signal::INT.trap` as a + # portable alternative which also works on Windows. def trap(&handler : Signal ->) : Nil - if self == CHLD - Crystal::Signal.child_handler = handler - else - Crystal::Signal.trap(self, handler) - end + {% if @type.has_constant?("CHLD") %} + if self == CHLD + Crystal::System::Signal.child_handler = handler + return + end + {% end %} + Crystal::System::Signal.trap(self, handler) end # Resets the handler for this signal to the OS default. @@ -91,7 +123,7 @@ enum Signal : Int32 # handler that monitors and reaps child processes. This prevents zombie # processes and is required by `Process#wait` for example. def reset : Nil - Crystal::Signal.reset(self) + Crystal::System::Signal.reset(self) end # Clears the handler for this signal and prevents the OS default action. @@ -100,272 +132,6 @@ enum Signal : Int32 # handler that monitors and reaps child processes. This prevents zombie # processes and is required by `Process#wait` for example. def ignore : Nil - Crystal::Signal.ignore(self) - end - - {% if flag?(:darwin) || flag?(:openbsd) %} - @@sigset = LibC::SigsetT.new(0) - {% else %} - @@sigset = LibC::SigsetT.new - {% end %} - - # :nodoc: - def set_add : Nil - LibC.sigaddset(pointerof(@@sigset), self) - end - - # :nodoc: - def set_del : Nil - LibC.sigdelset(pointerof(@@sigset), self) - end - - # :nodoc: - def set? : Bool - LibC.sigismember(pointerof(@@sigset), self) == 1 - end - - @@setup_default_handlers = Atomic::Flag.new - @@setup_segfault_handler = Atomic::Flag.new - @@segfault_handler = LibC::SigactionHandlerT.new { |sig, info, data| - # Capture fault signals (SEGV, BUS) and finish the process printing a backtrace first - - # Determine if the SEGV was inside or 'near' the top of the stack - # to check for potential stack overflow. 'Near' is a small - # amount larger than a typical stack frame, 4096 bytes here. - addr = info.value.si_addr - - is_stack_overflow = - begin - stack_top = Pointer(Void).new(Fiber.current.@stack.address - 4096) - stack_bottom = Fiber.current.@stack_bottom - stack_top <= addr < stack_bottom - rescue e - Crystal::System.print_error "Error while trying to determine if a stack overflow has occurred. Probable memory corruption\n" - false - end - - if is_stack_overflow - Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n" - else - Crystal::System.print_error "Invalid memory access (signal %d) at address 0x%lx\n", sig, addr - end - - Exception::CallStack.print_backtrace - LibC._exit(sig) - } - - # :nodoc: - def self.setup_default_handlers : Nil - return unless @@setup_default_handlers.test_and_set - LibC.sigemptyset(pointerof(@@sigset)) - Crystal::Signal.start_loop - Signal::PIPE.ignore - Signal::CHLD.reset - end - - # :nodoc: - def self.setup_segfault_handler - return unless @@setup_segfault_handler.test_and_set - - altstack = LibC::StackT.new - altstack.ss_sp = LibC.malloc(LibC::SIGSTKSZ) - altstack.ss_size = LibC::SIGSTKSZ - altstack.ss_flags = 0 - LibC.sigaltstack(pointerof(altstack), nil) - - action = LibC::Sigaction.new - action.sa_flags = LibC::SA_ONSTACK | LibC::SA_SIGINFO - action.sa_sigaction = @@segfault_handler - LibC.sigemptyset(pointerof(action.@sa_mask)) - - LibC.sigaction(SEGV, pointerof(action), nil) - LibC.sigaction(BUS, pointerof(action), nil) - end -end - -# :nodoc: -module Crystal::Signal - # The number of libc functions that can be called safely from a signal(2) - # handler is very limited. An usual safe solution is to use a pipe(2) and - # just write the signal to the file descriptor and nothing more. A loop in - # the main program is responsible for reading the signals back from the - # pipe(2) and handle the signal there. - - alias Handler = ::Signal -> - - @@pipe = IO.pipe(read_blocking: false, write_blocking: true) - @@handlers = {} of ::Signal => Handler - @@child_handler : Handler? - @@mutex = Mutex.new(:unchecked) - - def self.trap(signal, handler) : Nil - @@mutex.synchronize do - unless @@handlers[signal]? - signal.set_add - LibC.signal(signal.value, ->(value : Int32) { - writer.write_bytes(value) unless writer.closed? - }) - end - @@handlers[signal] = handler - end - end - - def self.child_handler=(handler : Handler) : Nil - @@child_handler = handler - end - - def self.reset(signal) : Nil - set(signal, LibC::SIG_DFL) - end - - def self.ignore(signal) : Nil - set(signal, LibC::SIG_IGN) - end - - private def self.set(signal, handler) - if signal == ::Signal::CHLD - # Clear any existing signal child handler - @@child_handler = nil - # But keep a default SIGCHLD, Process#wait requires it - trap(signal, ->(signal : ::Signal) { - Crystal::SignalChildHandler.call - @@child_handler.try(&.call(signal)) - }) - else - @@mutex.synchronize do - @@handlers.delete(signal) - LibC.signal(signal, handler) - signal.set_del - end - end - end - - def self.start_loop - spawn(name: "Signal Loop") do - loop do - value = reader.read_bytes(Int32) - rescue IO::Error - next - else - process(::Signal.new(value)) - end - end - end - - private def self.process(signal) : Nil - if handler = @@handlers[signal]? - non_nil_handler = handler # if handler is closured it will also have the Nil type - spawn do - non_nil_handler.call(signal) - rescue ex - ex.inspect_with_backtrace(STDERR) - fatal("uncaught exception while processing handler for #{signal}") - end - else - fatal("missing handler for #{signal}") - end - end - - # Replaces the signal pipe so the child process won't share the file - # descriptors of the parent process and send it received signals. - def self.after_fork - @@pipe.each(&.file_descriptor_close) - ensure - @@pipe = IO.pipe(read_blocking: false, write_blocking: true) - end - - # Resets signal handlers to `SIG_DFL`. This avoids the child to receive - # signals that would be sent to the parent process through the signal - # pipe. - # - # We keep a signal set to because accessing @@handlers isn't thread safe —a - # thread could be mutating the hash while another one forked. This allows to - # only reset a few signals (fast) rather than all (very slow). - # - # We eventually close the pipe anyway to avoid a potential race where a sigset - # wouldn't exactly reflect actual signal state. This avoids sending a children - # signal to the parent. Exec will reset the signals properly for the - # sub-process. - def self.after_fork_before_exec - ::Signal.each do |signal| - LibC.signal(signal, LibC::SIG_DFL) if signal.set? - end - ensure - {% unless flag?(:preview_mt) %} - @@pipe.each(&.file_descriptor_close) - {% end %} - end - - private def self.reader - @@pipe[0] - end - - private def self.writer - @@pipe[1] - end - - private def self.fatal(message : String) - STDERR.puts("FATAL: #{message}, exiting") - STDERR.flush - LibC._exit(1) - end -end - -# :nodoc: -module Crystal::SignalChildHandler - # Process#wait will block until the sub-process has terminated. On POSIX - # systems, the SIGCHLD signal is triggered. We thus always trap SIGCHLD then - # reap/memorize terminated child processes and eventually notify - # Process#wait through a channel, that may be created before or after the - # child process exited. - - @@pending = {} of LibC::PidT => Int32 - @@waiting = {} of LibC::PidT => Channel(Int32) - @@mutex = Mutex.new(:unchecked) - - def self.wait(pid : LibC::PidT) : Channel(Int32) - channel = Channel(Int32).new(1) - - @@mutex.lock - if exit_code = @@pending.delete(pid) - @@mutex.unlock - channel.send(exit_code) - channel.close - else - @@waiting[pid] = channel - @@mutex.unlock - end - - channel - end - - def self.call : Nil - loop do - pid = LibC.waitpid(-1, out exit_code, LibC::WNOHANG) - - case pid - when 0 - return - when -1 - return if Errno.value == Errno::ECHILD - raise RuntimeError.from_errno("waitpid") - else - @@mutex.lock - if channel = @@waiting.delete(pid) - @@mutex.unlock - channel.send(exit_code) - channel.close - else - @@pending[pid] = exit_code - @@mutex.unlock - end - end - end - end - - def self.after_fork - @@pending.clear - @@waiting.each_value(&.close) - @@waiting.clear + Crystal::System::Signal.ignore(self) end end diff --git a/src/windows_stubs.cr b/src/windows_stubs.cr deleted file mode 100644 index 17aba1c25289..000000000000 --- a/src/windows_stubs.cr +++ /dev/null @@ -1,3 +0,0 @@ -enum Signal - KILL = 0 -end From 082b7450f32418db44a7984df29aa4f545ae3a26 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 9 Mar 2023 01:35:29 +0800 Subject: [PATCH 0360/1551] Support typed LLVM `call` and `invoke` instructions (#13158) * Support typed LLVM `call` and `invoke` instructions * fixup --- samples/llvm/brainfuck.cr | 32 ++-- src/compiler/crystal/codegen/asm.cr | 2 +- src/compiler/crystal/codegen/class_var.cr | 16 +- src/compiler/crystal/codegen/codegen.cr | 176 +++++++++--------- src/compiler/crystal/codegen/const.cr | 4 +- src/compiler/crystal/codegen/context.cr | 5 +- .../crystal/codegen/crystal_llvm_builder.cr | 2 +- src/compiler/crystal/codegen/exception.cr | 18 +- src/compiler/crystal/codegen/fun.cr | 69 ++++--- .../crystal/codegen/llvm_builder_helper.cr | 14 +- src/compiler/crystal/codegen/llvm_typer.cr | 10 +- src/compiler/crystal/codegen/match.cr | 2 +- src/compiler/crystal/codegen/once.cr | 16 +- src/compiler/crystal/codegen/primitives.cr | 43 +++-- src/llvm/builder.cr | 40 +++- src/llvm/function_collection.cr | 16 +- src/llvm/lib_llvm.cr | 1 - src/llvm/parameter_collection.cr | 2 +- 18 files changed, 270 insertions(+), 198 deletions(-) diff --git a/samples/llvm/brainfuck.cr b/samples/llvm/brainfuck.cr index f487c24f84a5..5c005e72003c 100644 --- a/samples/llvm/brainfuck.cr +++ b/samples/llvm/brainfuck.cr @@ -55,8 +55,7 @@ class Read < Instruction cell_index = builder.load program.ctx.int32, program.cell_index_ptr, "cell_index" current_cell_ptr = builder.gep program.cell_type, program.cells_ptr, cell_index, "current_cell_ptr" - getchar = program.mod.functions["getchar"] - input_char = builder.call getchar, "input_char" + input_char = program.call_c_function "getchar", name: "input_char" input_byte = builder.trunc input_char, program.ctx.int8, "input_byte" builder.store input_byte, current_cell_ptr @@ -75,8 +74,7 @@ class Write < Instruction cell_val = builder.load program.cell_type, current_cell_ptr, "cell_value" cell_val_as_char = builder.sext cell_val, program.ctx.int32, "cell_val_as_char" - putchar = program.mod.functions["putchar"] - builder.call putchar, cell_val_as_char + program.call_c_function "putchar", cell_val_as_char bb end @@ -128,6 +126,8 @@ class Program getter! cell_index_ptr : LLVM::Value getter! func : LLVM::Function + @func_types = {} of String => LLVM::Type + def initialize(@instructions : Array(Instruction)) @ctx = LLVM::Context.new @mod = @ctx.new_module("brainfuck") @@ -208,10 +208,21 @@ class Program end def declare_c_functions(mod) - mod.functions.add "calloc", [@ctx.int32, @ctx.int32], @ctx.void_pointer - mod.functions.add "free", [@ctx.void_pointer], @ctx.void - mod.functions.add "putchar", [@ctx.int32], @ctx.int32 - mod.functions.add "getchar", ([] of LLVM::Type), @ctx.int32 + declare_c_function mod, "calloc", [@ctx.int32, @ctx.int32], @ctx.void_pointer + declare_c_function mod, "free", [@ctx.void_pointer], @ctx.void + declare_c_function mod, "putchar", [@ctx.int32], @ctx.int32 + declare_c_function mod, "getchar", ([] of LLVM::Type), @ctx.int32 + end + + def declare_c_function(mod, name, param_types, return_type) + func_type = LLVM::Type.function(param_types, return_type) + @func_types[name] = func_type + mod.functions.add name, func_type + end + + def call_c_function(func_name, args = [] of LLVM::Value, name = "") + func = mod.functions[func_name] + @builder.call @func_types[func_name], func, args, name end def create_main(mod) @@ -225,7 +236,7 @@ class Program calloc = mod.functions["calloc"] call_args = [@ctx.int32.const_int(NUM_CELLS), @ctx.int32.const_int(CELL_SIZE_IN_BYTES)] - @cells_ptr = builder.call calloc, call_args, "cells" + @cells_ptr = call_c_function "calloc", call_args, "cells" @cell_index_ptr = builder.alloca @ctx.int32, "cell_index_ptr" zero = @ctx.int32.const_int(0) @@ -235,8 +246,7 @@ class Program def add_cells_cleanup(mod, bb) builder.position_at_end bb - free = mod.functions["free"] - builder.call free, cells_ptr + call_c_function "free", cells_ptr zero = @ctx.int32.const_int(0) builder.ret zero diff --git a/src/compiler/crystal/codegen/asm.cr b/src/compiler/crystal/codegen/asm.cr index 0d4945106fc6..f0b3e2f088ab 100644 --- a/src/compiler/crystal/codegen/asm.cr +++ b/src/compiler/crystal/codegen/asm.cr @@ -55,7 +55,7 @@ class Crystal::CodeGenVisitor value = fun_type.inline_asm(node.text, constraints, node.volatile?, node.alignstack?, node.can_throw?) value = LLVM::Function.from_value(value) - asm_value = call value, input_values + asm_value = call LLVMTypedFunction.new(fun_type, value), input_values if ptrofs = node.output_ptrofs if ptrofs.size > 1 diff --git a/src/compiler/crystal/codegen/class_var.cr b/src/compiler/crystal/codegen/class_var.cr index 1e3df6bc1358..07d8ed0f96b1 100644 --- a/src/compiler/crystal/codegen/class_var.cr +++ b/src/compiler/crystal/codegen/class_var.cr @@ -82,7 +82,7 @@ class Crystal::CodeGenVisitor def initialize_class_var(class_var : MetaTypeVar, initializer : ClassVarInitializer) init_func = create_initialize_class_var_function(class_var, initializer) - init_func = check_main_fun(init_func.name, init_func) if init_func + init_func = check_main_fun(init_func.func.name, init_func) if init_func # For unsafe class var we just initialize them without # using a flag to know if they were initialized @@ -116,7 +116,7 @@ class Crystal::CodeGenVisitor node = initializer.node init_function_name = "~#{class_var_global_initialized_name(class_var)}" - @main_mod.functions[init_function_name]? || begin + typed_fun?(@main_mod, init_function_name) || begin global = declare_class_var(class_var) discard = false @@ -155,7 +155,7 @@ class Crystal::CodeGenVisitor if discard class_var.simple_initializer = true - new_func.delete + new_func.func.delete nil else new_func @@ -199,7 +199,7 @@ class Crystal::CodeGenVisitor func = create_read_class_var_function(class_var, initializer) if func - func = check_main_fun func.name, func + func = check_main_fun func.func.name, func call func else get_class_var_global(class_var) @@ -216,7 +216,7 @@ class Crystal::CodeGenVisitor def read_virtual_class_var_ptr(class_var, owner) self_type_id = type_id(llvm_self, owner) read_function_name = "~#{class_var_global_name(class_var)}:read" - func = @main_mod.functions[read_function_name]? || + func = typed_fun?(@main_mod, read_function_name) || create_read_virtual_class_var_ptr_function(read_function_name, class_var, owner) func = check_main_fun read_function_name, func call func, self_type_id @@ -263,7 +263,7 @@ class Crystal::CodeGenVisitor def read_virtual_metaclass_class_var_ptr(class_var, owner) self_type_id = type_id(llvm_self, owner) read_function_name = "~#{class_var_global_name(class_var)}:read" - func = @main_mod.functions[read_function_name]? || + func = typed_fun?(@main_mod, read_function_name) || create_read_virtual_metaclass_var_ptr_function(read_function_name, class_var, owner) func = check_main_fun read_function_name, func call func, self_type_id @@ -308,7 +308,7 @@ class Crystal::CodeGenVisitor def create_read_class_var_function(class_var, initializer) fun_name = "~#{class_var_global_name(class_var)}:read" - if func = @main_mod.functions[fun_name]? + if func = typed_fun?(@main_mod, fun_name) return func end @@ -320,7 +320,7 @@ class Crystal::CodeGenVisitor in_main do define_main_function(fun_name, ([] of LLVM::Type), llvm_type(class_var.type).pointer) do |func| set_internal_fun_debug_location(func, fun_name, initializer.node.location) - init_func = check_main_fun init_func.name, init_func + init_func = check_main_fun init_func.func.name, init_func ret lazy_initialize_class_var(initializer.node, init_func, global, initialized_flag) end end diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 334bd92599cc..e78af6af3ae0 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -27,22 +27,27 @@ module Crystal end def evaluate(node, debug = Debug::Default) - llvm_mod = codegen(node, single_module: true, debug: debug)[""].mod - llvm_mod.target = target_machine.triple + visitor = CodeGenVisitor.new self, node, single_module: true, debug: debug + visitor.accept node + visitor.process_finished_hooks + visitor.finish - main = llvm_mod.functions[MAIN_NAME] + llvm_mod = visitor.modules[""].mod + llvm_mod.target = target_machine.triple - main_return_type = main.return_type + main = visitor.typed_fun?(llvm_mod, MAIN_NAME).not_nil! # It seems the JIT doesn't like it if we return an empty type (struct {}) llvm_context = llvm_mod.context + main_return_type = main.type.return_type main_return_type = llvm_context.void if node.type.nil_type? - wrapper = llvm_mod.functions.add("__evaluate_wrapper", [] of LLVM::Type, main_return_type) do |func| + wrapper_type = LLVM::Type.function([] of LLVM::Type, main_return_type) + wrapper = llvm_mod.functions.add("__evaluate_wrapper", wrapper_type) do |func| func.basic_blocks.append "entry" do |builder| argc = llvm_context.int32.const_int(0) argv = llvm_context.void_pointer.pointer.null - ret = builder.call(main, [argc, argv]) + ret = builder.call(main.type, main.func, [argc, argv]) (node.type.void? || node.type.nil_type?) ? builder.ret : builder.ret(ret) end end @@ -155,20 +160,22 @@ module Crystal @argv : LLVM::Value @rescue_block : LLVM::BasicBlock? @catch_pad : LLVM::Value? - @malloc_fun : LLVM::Function? - @malloc_atomic_fun : LLVM::Function? - @c_malloc_fun : LLVM::Function? + @fun_types : Hash({LLVM::Module, String}, LLVM::Type) @sret_value : LLVM::Value? @cant_pass_closure_to_c_exception_call : Call? - @realloc_fun : LLVM::Function? - @c_realloc_fun : LLVM::Function? - @raise_overflow_fun : LLVM::Function? @main_llvm_context : LLVM::Context @main_llvm_typer : LLVMTyper @main_module_info : ModuleInfo @main_builder : CrystalLLVMBuilder @call_location : Location? + @malloc_fun : LLVMTypedFunction? + @malloc_atomic_fun : LLVMTypedFunction? + @realloc_fun : LLVMTypedFunction? + @raise_overflow_fun : LLVMTypedFunction? + @c_malloc_fun : LLVMTypedFunction? + @c_realloc_fun : LLVMTypedFunction? + def initialize(@program : Program, @node : ASTNode, single_module = false, @debug = Debug::Default) @single_module = !!single_module @abi = @program.target_machine.abi @@ -181,16 +188,18 @@ module Crystal @main_llvm_typer = @llvm_typer @main_ret_type = node.type? || @program.nil_type ret_type = @llvm_typer.llvm_return_type(@main_ret_type) - @main = @llvm_mod.functions.add(MAIN_NAME, [llvm_context.int32, llvm_context.void_pointer.pointer], ret_type) + main_type = LLVM::Type.function([llvm_context.int32, llvm_context.void_pointer.pointer], ret_type) + @main = @llvm_mod.functions.add(MAIN_NAME, main_type) + @fun_types = { {@llvm_mod, MAIN_NAME} => main_type } if @program.has_flag? "windows" @personality_name = "__CxxFrameHandler3" - @main.personality_function = windows_personality_fun + @main.personality_function = windows_personality_fun.func else @personality_name = "__crystal_personality" end - @context = Context.new @main, @program + @context = Context.new @main, main_type, @program @context.return_type = @main_ret_type @argc = @main.params[0] @@ -278,7 +287,7 @@ module Crystal end def wrap_builder(builder) - CrystalLLVMBuilder.new builder, llvm_typer, @program.printf(@llvm_mod, llvm_context) + CrystalLLVMBuilder.new builder, llvm_typer, c_printf_fun end def define_symbol_table(llvm_mod, llvm_typer) @@ -425,7 +434,7 @@ module Crystal end def visit(node : FileNode) - with_context(Context.new(context.fun, context.type)) do + with_context(Context.new(context.fun, context.fun_type, context.type)) do file_module = @program.file_module(node.filename) if vars = file_module.vars? set_current_debug_location Location.new(node.filename, 1, 1) if @debug.line_numbers? @@ -597,7 +606,7 @@ module Crystal the_fun = check_main_fun fun_literal_name, the_fun set_current_debug_location(node) if @debug.line_numbers? - fun_ptr = bit_cast(the_fun, llvm_context.void_pointer) + fun_ptr = bit_cast(the_fun.func, llvm_context.void_pointer) if is_closure ctx_ptr = bit_cast(context.closure_ptr.not_nil!, llvm_context.void_pointer) else @@ -646,7 +655,7 @@ module Crystal last_fun = target_def_fun(node.call.target_def, owner) set_current_debug_location(node) if @debug.line_numbers? - fun_ptr = bit_cast(last_fun, llvm_context.void_pointer) + fun_ptr = bit_cast(last_fun.func, llvm_context.void_pointer) if call_self && !owner.metaclass? && !owner.is_a?(LibType) ctx_ptr = bit_cast(call_self, llvm_context.void_pointer) else @@ -1130,7 +1139,7 @@ module Crystal # # Making a function that just returns the pointer doesn't work: LLVM inlines it. fun_name = "*#{name}" - thread_local_fun = @main_mod.functions[fun_name]? + thread_local_fun = typed_fun?(@main_mod, fun_name) unless thread_local_fun thread_local_fun = in_main do define_main_function(fun_name, [llvm_type(type).pointer.pointer], llvm_context.void) do |func| @@ -1139,7 +1148,7 @@ module Crystal builder.ret end end - thread_local_fun.add_attribute LLVM::Attribute::NoInline + thread_local_fun.func.add_attribute LLVM::Attribute::NoInline end thread_local_fun = check_main_fun(fun_name, thread_local_fun) pointer_type = llvm_type(type).pointer @@ -1595,10 +1604,10 @@ module Crystal def check_proc_is_not_closure(value, type) check_fun_name = "~check_proc_is_not_closure" - func = @main_mod.functions[check_fun_name]? || create_check_proc_is_not_closure_fun(check_fun_name) + func = typed_fun?(@main_mod, check_fun_name) || create_check_proc_is_not_closure_fun(check_fun_name) func = check_main_fun check_fun_name, func value = call func, [value] of LLVM::Value - bit_cast value, llvm_proc_type(type) + bit_cast value, llvm_proc_type(type).pointer end def create_check_proc_is_not_closure_fun(fun_name) @@ -1646,6 +1655,7 @@ module Crystal old_llvm_context = @llvm_context old_llvm_typer = @llvm_typer old_fun = context.fun + old_fun_type = context.fun_type old_ensure_exception_handlers = @ensure_exception_handlers old_rescue_block = @rescue_block old_catch_pad = @catch_pad @@ -1680,29 +1690,35 @@ module Crystal @alloca_block = old_alloca_block @needs_value = old_needs_value context.fun = old_fun + context.fun_type = old_fun_type set_current_debug_location old_debug_location if @debug.line_numbers? block_value end - def define_main_function(name, arg_types, return_type, needs_alloca = false) + def define_main_function(name, arg_types : Array(LLVM::Type), return_type : LLVM::Type, needs_alloca : Bool = false) + define_main_function(name, LLVM::Type.function(arg_types, return_type), needs_alloca) { |func| yield func } + end + + def define_main_function(name, type : LLVM::Type, needs_alloca : Bool = false) if @llvm_mod != @main_mod raise "wrong usage of define_main_function: you must put it inside an `in_main` block" end - @main_mod.functions.add(name, arg_types, return_type) do |func| - context.fun = func - context.fun.linkage = LLVM::Linkage::Internal if @single_module - if needs_alloca - new_entry_block - yield func - br_from_alloca_to_entry - else - block = func.basic_blocks.append "entry" - position_at_end block - yield func - end + func = add_typed_fun(@main_mod, name, type) + context.fun = func.func + context.fun_type = type + context.fun.linkage = LLVM::Linkage::Internal if @single_module + if needs_alloca + new_entry_block + yield func.func + br_from_alloca_to_entry + else + block = func.func.basic_blocks.append "entry" + position_at_end block + yield func.func end + func end # used for generated internal functions like `~metaclass` and `~match` @@ -1932,7 +1948,7 @@ module Crystal end def printf(format, args = [] of LLVM::Value) - call @program.printf(@llvm_mod, llvm_context), [builder.global_string_pointer(format)] + args + call c_printf_fun, [builder.global_string_pointer(format)] + args end # Emits a debug message that shows the current llvm basic block name, @@ -2081,7 +2097,7 @@ module Crystal end def crystal_malloc_fun - @malloc_fun ||= @main_mod.functions[MALLOC_NAME]? + @malloc_fun ||= typed_fun?(@main_mod, MALLOC_NAME) if malloc_fun = @malloc_fun check_main_fun MALLOC_NAME, malloc_fun else @@ -2090,7 +2106,7 @@ module Crystal end def crystal_malloc_atomic_fun - @malloc_atomic_fun ||= @main_mod.functions[MALLOC_ATOMIC_NAME]? + @malloc_atomic_fun ||= typed_fun?(@main_mod, MALLOC_ATOMIC_NAME) if malloc_fun = @malloc_atomic_fun check_main_fun MALLOC_ATOMIC_NAME, malloc_fun else @@ -2099,7 +2115,7 @@ module Crystal end def crystal_realloc_fun - @realloc_fun ||= @main_mod.functions[REALLOC_NAME]? + @realloc_fun ||= typed_fun?(@main_mod, REALLOC_NAME) if realloc_fun = @realloc_fun check_main_fun REALLOC_NAME, realloc_fun else @@ -2108,7 +2124,7 @@ module Crystal end def crystal_raise_overflow_fun - @raise_overflow_fun ||= @main_mod.functions[RAISE_OVERFLOW_NAME]? + @raise_overflow_fun ||= typed_fun?(@main_mod, RAISE_OVERFLOW_NAME) if raise_overflow_fun = @raise_overflow_fun check_main_fun RAISE_OVERFLOW_NAME, raise_overflow_fun else @@ -2126,9 +2142,9 @@ module Crystal end def c_malloc_fun - malloc_fun = @c_malloc_fun = @main_mod.functions["malloc"]? || begin + malloc_fun = @c_malloc_fun = fetch_typed_fun(@main_mod, "malloc") do size = @program.bits64? ? @main_llvm_context.int64 : @main_llvm_context.int32 - @main_mod.functions.add("malloc", ([size]), @main_llvm_context.void_pointer) + LLVM::Type.function([size], @main_llvm_context.void_pointer) end check_main_fun "malloc", malloc_fun @@ -2140,9 +2156,9 @@ module Crystal end def c_realloc_fun - realloc_fun = @c_realloc_fun = @main_mod.functions["realloc"]? || begin + realloc_fun = @c_realloc_fun = fetch_typed_fun(@main_mod, "realloc") do size = @program.bits64? ? @main_llvm_context.int64 : @main_llvm_context.int32 - @main_mod.functions.add("realloc", ([@main_llvm_context.void_pointer, size]), @main_llvm_context.void_pointer) + LLVM::Type.function([@main_llvm_context.void_pointer, size], @main_llvm_context.void_pointer) end check_main_fun "realloc", realloc_fun @@ -2152,7 +2168,7 @@ module Crystal len_arg = @program.bits64? ? size : trunc(size, llvm_context.int32) pointer = cast_to_void_pointer pointer - res = call @program.memset(@llvm_mod, llvm_context), + res = call c_memset_fun, if LibLLVM::IS_LT_70 [pointer, value, len_arg, int32(4), int1(0)] else @@ -2167,7 +2183,7 @@ module Crystal end def memcpy(dest, src, len, align, volatile) - res = call @program.memcpy(@llvm_mod, llvm_context), + res = call c_memcpy_fun, if LibLLVM::IS_LT_70 [dest, src, len, int32(align), volatile] else @@ -2190,6 +2206,30 @@ module Crystal end end + private def c_printf_fun + fetch_typed_fun(@llvm_mod, "printf") do + LLVM::Type.function([@llvm_context.void_pointer], @llvm_context.int32, true) + end + end + + private def c_memset_fun + name = @program.bits64? ? "llvm.memset.p0i8.i64" : "llvm.memset.p0i8.i32" + fetch_typed_fun(@llvm_mod, name) do + len_type = @program.bits64? ? @llvm_context.int64 : @llvm_context.int32 + arg_types = [@llvm_context.void_pointer, @llvm_context.int8, len_type, @llvm_context.int1] + LLVM::Type.function(arg_types, @llvm_context.void) + end + end + + private def c_memcpy_fun + name = @program.bits64? ? "llvm.memcpy.p0i8.p0i8.i64" : "llvm.memcpy.p0i8.p0i8.i32" + fetch_typed_fun(@llvm_mod, name) do + len_type = @program.bits64? ? @llvm_context.int64 : @llvm_context.int32 + arg_types = [@llvm_context.void_pointer, @llvm_context.void_pointer, len_type, @llvm_context.int1] + LLVM::Type.function(arg_types, @llvm_context.void) + end + end + def to_lhs(value, type) # `llvm_embedded_type` needed for void-like types type.passed_by_value? ? value : load(llvm_embedded_type(type), value) @@ -2321,48 +2361,6 @@ module Crystal name end end - - class Program - def printf(llvm_mod, llvm_context) - llvm_mod.functions["printf"]? || llvm_mod.functions.add("printf", [llvm_context.void_pointer], llvm_context.int32, true) - end - - def realloc(llvm_mod, llvm_context) - llvm_mod.functions["realloc"]? || llvm_mod.functions.add("realloc", ([llvm_context.void_pointer, llvm_context.int64]), llvm_context.void_pointer) - end - - def memset(llvm_mod, llvm_context) - name = bits64? ? "llvm.memset.p0i8.i64" : "llvm.memset.p0i8.i32" - len_type = bits64? ? llvm_context.int64 : llvm_context.int32 - - llvm_mod.functions[name]? || begin - arg_types = - if LibLLVM::IS_LT_70 - [llvm_context.void_pointer, llvm_context.int8, len_type, llvm_context.int32, llvm_context.int1] - else - [llvm_context.void_pointer, llvm_context.int8, len_type, llvm_context.int1] - end - - llvm_mod.functions.add(name, arg_types, llvm_context.void) - end - end - - def memcpy(llvm_mod, llvm_context) - name = bits64? ? "llvm.memcpy.p0i8.p0i8.i64" : "llvm.memcpy.p0i8.p0i8.i32" - len_type = bits64? ? llvm_context.int64 : llvm_context.int32 - - llvm_mod.functions[name]? || begin - arg_types = - if LibLLVM::IS_LT_70 - [llvm_context.void_pointer, llvm_context.void_pointer, len_type, llvm_context.int32, llvm_context.int1] - else - [llvm_context.void_pointer, llvm_context.void_pointer, len_type, llvm_context.int1] - end - - llvm_mod.functions.add(name, arg_types, llvm_context.void) - end - end - end end require "./*" diff --git a/src/compiler/crystal/codegen/const.cr b/src/compiler/crystal/codegen/const.cr index 06df37916d25..88a50022d54d 100644 --- a/src/compiler/crystal/codegen/const.cr +++ b/src/compiler/crystal/codegen/const.cr @@ -128,7 +128,7 @@ class Crystal::CodeGenVisitor return global if const.initializer init_function_name = "~#{const.initialized_llvm_name}" - func = @main_mod.functions[init_function_name]? || create_initialize_const_function(init_function_name, const) + func = typed_fun?(@main_mod, init_function_name) || create_initialize_const_function(init_function_name, const) func = check_main_fun init_function_name, func set_current_debug_location const.locations.try &.first? if @debug.line_numbers? @@ -223,7 +223,7 @@ class Crystal::CodeGenVisitor end read_function_name = "~#{const.llvm_name}:read" - func = @main_mod.functions[read_function_name]? || create_read_const_function(read_function_name, const) + func = typed_fun?(@main_mod, read_function_name) || create_read_const_function(read_function_name, const) func = check_main_fun read_function_name, func call func end diff --git a/src/compiler/crystal/codegen/context.cr b/src/compiler/crystal/codegen/context.cr index d89f8c0e199d..26105deec663 100644 --- a/src/compiler/crystal/codegen/context.cr +++ b/src/compiler/crystal/codegen/context.cr @@ -3,6 +3,7 @@ require "./codegen" class Crystal::CodeGenVisitor class Context property fun : LLVM::Function + property fun_type : LLVM::Type property fun_debug_params = [] of LibLLVM::MetadataRef property type : Type property vars : Hash(String, LLVMVar) @@ -20,7 +21,7 @@ class Crystal::CodeGenVisitor property closure_parent_context : Context? property closure_self : Type? - def initialize(@fun, @type, @vars = LLVMVars.new) + def initialize(@fun, @fun_type, @type, @vars = LLVMVars.new) @closure_skip_parent = false end @@ -37,7 +38,7 @@ class Crystal::CodeGenVisitor end def clone - context = Context.new @fun, @type, @vars + context = Context.new @fun, @fun_type, @type, @vars context.return_type = @return_type context.return_phi = @return_phi context.break_phi = @break_phi diff --git a/src/compiler/crystal/codegen/crystal_llvm_builder.cr b/src/compiler/crystal/codegen/crystal_llvm_builder.cr index f635d34f3cfb..55ad6f49735a 100644 --- a/src/compiler/crystal/codegen/crystal_llvm_builder.cr +++ b/src/compiler/crystal/codegen/crystal_llvm_builder.cr @@ -2,7 +2,7 @@ module Crystal class CrystalLLVMBuilder property end : Bool - def initialize(@builder : LLVM::Builder, @llvm_typer : LLVMTyper, @printf : LLVM::Function) + def initialize(@builder : LLVM::Builder, @llvm_typer : LLVMTyper, @printf : LLVMTypedFunction) @end = false end diff --git a/src/compiler/crystal/codegen/exception.cr b/src/compiler/crystal/codegen/exception.cr index 85018fc86ad9..3b2d5d2cc92e 100644 --- a/src/compiler/crystal/codegen/exception.cr +++ b/src/compiler/crystal/codegen/exception.cr @@ -62,7 +62,7 @@ class Crystal::CodeGenVisitor windows = @program.has_flag? "windows" - context.fun.personality_function = windows_personality_fun if windows + context.fun.personality_function = windows_personality_fun.func if windows # This is the block which is entered when the body raises an exception rescue_block = new_block "rescue" @@ -143,14 +143,14 @@ class Crystal::CodeGenVisitor # exception object and the type ID of the exception. This tuple is set up in the crystal # personality function in raise.cr lp_ret_type = llvm_typer.landing_pad_type - lp = builder.landing_pad lp_ret_type, main_fun(personality_name), [] of LLVM::Value + lp = builder.landing_pad lp_ret_type, main_fun(personality_name).func, [] of LLVM::Value unwind_ex_obj = extract_value lp, 0 exception_type_id = extract_value lp, 1 # We call __crystal_get_exception to get the actual crystal `Exception` object. get_exception_fun = main_fun(GET_EXCEPTION_NAME) set_current_debug_location node if @debug.line_numbers? - caught_exception_ptr = call get_exception_fun, [bit_cast(unwind_ex_obj, get_exception_fun.params.first.type)] + caught_exception_ptr = call get_exception_fun, [bit_cast(unwind_ex_obj, get_exception_fun.type.params_types.first)] caught_exception = int2ptr caught_exception_ptr, llvm_typer.type_id_pointer end @@ -255,7 +255,7 @@ class Crystal::CodeGenVisitor @catch_pad = builder.catch_pad catch_switch, [void_ptr_type_descriptor, int32(0), llvm_context.void_pointer.null] else lp_ret_type = llvm_typer.landing_pad_type - lp = builder.landing_pad lp_ret_type, main_fun(personality_name), [] of LLVM::Value + lp = builder.landing_pad lp_ret_type, main_fun(personality_name).func, [] of LLVM::Value unwind_ex_obj = extract_value lp, 0 end @@ -286,7 +286,7 @@ class Crystal::CodeGenVisitor unreachable else raise_fun = main_fun(RAISE_NAME) - codegen_call_or_invoke(node, nil, nil, raise_fun, [bit_cast(unwind_ex_obj.not_nil!, raise_fun.params.first.type)], true, @program.no_return) + codegen_call_or_invoke(node, nil, nil, raise_fun, [bit_cast(unwind_ex_obj.not_nil!, raise_fun.func.params.first.type)], true, @program.no_return) end end @@ -312,14 +312,14 @@ class Crystal::CodeGenVisitor end private def windows_throw_fun - @llvm_mod.functions["_CxxThrowException"]? || begin - @llvm_mod.functions.add("_CxxThrowException", [llvm_context.void_pointer, llvm_context.void_pointer], llvm_context.void, false) + fetch_typed_fun(@llvm_mod, "_CxxThrowException") do + LLVM::Type.function([@llvm_context.void_pointer, @llvm_context.void_pointer], @llvm_context.void, false) end end private def windows_personality_fun - @llvm_mod.functions["__CxxFrameHandler3"]? || begin - @llvm_mod.functions.add("__CxxFrameHandler3", [] of LLVM::Type, llvm_context.int32, true) + fetch_typed_fun(@llvm_mod, "__CxxFrameHandler3") do + LLVM::Type.function([] of LLVM::Type, @llvm_context.int32, true) end end end diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index cf71cfa00f99..938d04836ad9 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -1,49 +1,60 @@ require "./codegen" class Crystal::CodeGenVisitor - def target_def_fun(target_def, self_type) : LLVM::Function + def typed_fun?(mod : LLVM::Module, name : String) : LLVMTypedFunction? + if func = mod.functions[name]? + LLVMTypedFunction.new(@fun_types[{mod, name}], func) + end + end + + def add_typed_fun(mod : LLVM::Module, name : String, type : LLVM::Type) : LLVMTypedFunction + func = mod.functions.add(name, type) + @fun_types[{mod, name}] = type + LLVMTypedFunction.new(type, func) + end + + def fetch_typed_fun(mod : LLVM::Module, name : String, & : -> LLVM::Type) : LLVMTypedFunction + typed_fun?(mod, name) || add_typed_fun(mod, name, yield) + end + + def target_def_fun(target_def, self_type) : LLVMTypedFunction mangled_name = target_def.mangled_name(@program, self_type) self_type_mod = type_module(self_type).mod - func = self_type_mod.functions[mangled_name]? || codegen_fun(mangled_name, target_def, self_type) + func = typed_fun?(self_type_mod, mangled_name) || codegen_fun(mangled_name, target_def, self_type) check_mod_fun self_type_mod, mangled_name, func end def main_fun(name) - func = @main_mod.functions[name]? - unless func - raise "BUG: #{name} is not defined" - end - + func = typed_fun?(@main_mod, name) || raise "BUG: #{name} is not defined" check_main_fun name, func end - def check_main_fun(name, func) + def check_main_fun(name, func : LLVMTypedFunction) : LLVMTypedFunction check_mod_fun @main_mod, name, func end - def check_mod_fun(mod, name, func) - return func if @llvm_mod == mod - @llvm_mod.functions[name]? || declare_fun(name, func) + def check_mod_fun(mod, name, func : LLVMTypedFunction) : LLVMTypedFunction + if @llvm_mod == mod + func + elsif existing_func = @llvm_mod.functions[name]? + LLVMTypedFunction.new(@fun_types[{@llvm_mod, name}], existing_func) + else + declare_fun(name, func) + end end - def declare_fun(mangled_name, func) - param_types = @llvm_typer.copy_types(func.params.types) - return_type = @llvm_typer.copy_type(func.return_type) - - new_fun = @llvm_mod.functions.add( - mangled_name, - param_types, - return_type, - func.varargs? - ) + def declare_fun(mangled_name, func : LLVMTypedFunction) : LLVMTypedFunction + type = @llvm_typer.copy_type(func.type) + typed_fun = add_typed_fun(@llvm_mod, mangled_name, type) - func.params.to_a.each_with_index do |p1, index| + new_fun = typed_fun.func + func.func.params.to_a.each_with_index do |p1, index| attrs = new_fun.attributes(index + 1) new_fun.add_attribute(attrs, index + 1) unless attrs.value == 0 end - new_fun + typed_fun end def codegen_fun(mangled_name, target_def, self_type, is_exported_fun = false, fun_module_info = type_module(self_type), is_fun_literal = false, is_closure = false) @@ -213,7 +224,7 @@ class Crystal::CodeGenVisitor end end - context.fun + LLVMTypedFunction.new(context.fun_type, context.fun) end end @@ -329,8 +340,9 @@ class Crystal::CodeGenVisitor # This is the case where we declared a fun that was not used and now we # are defining its body. - if existing_fun = @llvm_mod.functions[mangled_name]? - context.fun = existing_fun + if existing_fun = typed_fun?(@llvm_mod, mangled_name) + context.fun = existing_fun.func + context.fun_type = existing_fun.type return args end @@ -408,7 +420,10 @@ class Crystal::CodeGenVisitor end def setup_context_fun(mangled_name, target_def, llvm_args_types, llvm_return_type) : Nil - context.fun = @llvm_mod.functions.add(mangled_name, llvm_args_types, llvm_return_type, target_def.varargs?) + fun_type = LLVM::Type.function(llvm_args_types, llvm_return_type, target_def.varargs?) + typed_fun = add_typed_fun(@llvm_mod, mangled_name, fun_type) + context.fun = typed_fun.func + context.fun_type = typed_fun.type if @debug.variables? context.fun.add_attribute LLVM::Attribute::NoInline diff --git a/src/compiler/crystal/codegen/llvm_builder_helper.cr b/src/compiler/crystal/codegen/llvm_builder_helper.cr index 109839eaec37..9f843856b45f 100644 --- a/src/compiler/crystal/codegen/llvm_builder_helper.cr +++ b/src/compiler/crystal/codegen/llvm_builder_helper.cr @@ -1,4 +1,6 @@ module Crystal + record LLVMTypedFunction, type : LLVM::Type, func : LLVM::Function + module LLVMBuilderHelper def int1(n) llvm_context.int1.const_int(n) @@ -111,32 +113,32 @@ module Crystal builder.inbounds_gep type, ptr, index0, index1, name end - def call(func, name : String = "") + def call(func : LLVMTypedFunction, name : String = "") call(func, [] of LLVM::Value, name) end - def call(func, arg : LLVM::Value, name : String = "") + def call(func : LLVMTypedFunction, arg : LLVM::Value, name : String = "") call(func, [arg], name) end - def call(func : LLVM::Function, args : Array(LLVM::Value), name : String = "") + def call(func : LLVMTypedFunction, args : Array(LLVM::Value), name : String = "") if catch_pad = @catch_pad funclet = builder.build_operand_bundle_def("funclet", [catch_pad]) else funclet = LLVM::OperandBundleDef.null end - builder.call(func, args, bundle: funclet, name: name) + builder.call(func.type, func.func, args, bundle: funclet, name: name) end - def invoke(func : LLVM::Function, args : Array(LLVM::Value), a_then, a_catch, name : String = "") + def invoke(func : LLVMTypedFunction, args : Array(LLVM::Value), a_then, a_catch, name : String = "") if catch_pad = @catch_pad funclet = builder.build_operand_bundle_def("funclet", [catch_pad]) else funclet = LLVM::OperandBundleDef.null end - builder.invoke(func, args, a_then, a_catch, bundle: funclet, name: name) + builder.invoke(func.type, func.func, args, a_then, a_catch, bundle: funclet, name: name) end delegate ptr2int, int2ptr, and, or, not, bit_cast, diff --git a/src/compiler/crystal/codegen/llvm_typer.cr b/src/compiler/crystal/codegen/llvm_typer.cr index 1563c7b502c0..812cf94e79ab 100644 --- a/src/compiler/crystal/codegen/llvm_typer.cr +++ b/src/compiler/crystal/codegen/llvm_typer.cr @@ -405,7 +405,7 @@ module Crystal end def llvm_embedded_c_type(type : ProcInstanceType, wants_size = false) - proc_type(type) + proc_type(type).pointer end def llvm_embedded_c_type(type, wants_size = false) @@ -413,11 +413,11 @@ module Crystal end def llvm_c_type(type : ProcInstanceType) - proc_type(type) + proc_type(type).pointer end def llvm_c_type(type : NilableProcType) - proc_type(type.proc_type) + proc_type(type.proc_type).pointer end def llvm_c_type(type : TupleInstanceType) @@ -461,12 +461,12 @@ module Crystal def closure_type(type : ProcInstanceType) arg_types = type.arg_types.map { |arg_type| llvm_type(arg_type) } arg_types.insert(0, @llvm_context.void_pointer) - LLVM::Type.function(arg_types, llvm_type(type.return_type)).pointer + LLVM::Type.function(arg_types, llvm_type(type.return_type)) end def proc_type(type : ProcInstanceType) arg_types = type.arg_types.map { |arg_type| llvm_type(arg_type).as(LLVM::Type) } - LLVM::Type.function(arg_types, llvm_type(type.return_type)).pointer + LLVM::Type.function(arg_types, llvm_type(type.return_type)) end def closure_context_type(vars, parent_llvm_type, self_type) diff --git a/src/compiler/crystal/codegen/match.cr b/src/compiler/crystal/codegen/match.cr index 1a50bdb8fd0a..109366654f29 100644 --- a/src/compiler/crystal/codegen/match.cr +++ b/src/compiler/crystal/codegen/match.cr @@ -39,7 +39,7 @@ class Crystal::CodeGenVisitor private def match_any_type_id_with_function(type, type_id) match_fun_name = "~match<#{type}>" - func = @main_mod.functions[match_fun_name]? || create_match_fun(match_fun_name, type) + func = typed_fun?(@main_mod, match_fun_name) || create_match_fun(match_fun_name, type) func = check_main_fun match_fun_name, func call func, [type_id] of LLVM::Value end diff --git a/src/compiler/crystal/codegen/once.cr b/src/compiler/crystal/codegen/once.cr index 9c4363ac1b4b..b72820266473 100644 --- a/src/compiler/crystal/codegen/once.cr +++ b/src/compiler/crystal/codegen/once.cr @@ -4,32 +4,32 @@ class Crystal::CodeGenVisitor ONCE_STATE = "~ONCE_STATE" def once_init - if once_init_fun = @main_mod.functions[ONCE_INIT]? + if once_init_fun = typed_fun?(@main_mod, ONCE_INIT) once_init_fun = check_main_fun ONCE_INIT, once_init_fun - once_state_global = @main_mod.globals.add(once_init_fun.return_type, ONCE_STATE) + once_state_global = @main_mod.globals.add(once_init_fun.type.return_type, ONCE_STATE) once_state_global.linkage = LLVM::Linkage::Internal if @single_module - once_state_global.initializer = once_init_fun.return_type.null + once_state_global.initializer = once_init_fun.type.return_type.null state = call once_init_fun store state, once_state_global end end - def run_once(flag, func) + def run_once(flag, func : LLVMTypedFunction) once_fun = main_fun(ONCE) once_init_fun = main_fun(ONCE_INIT) once_state_global = @llvm_mod.globals[ONCE_STATE]? || begin - global = @llvm_mod.globals.add(once_init_fun.return_type, ONCE_STATE) + global = @llvm_mod.globals.add(once_init_fun.type.return_type, ONCE_STATE) global.linkage = LLVM::Linkage::External global end - call main_fun(ONCE), [ - load(once_init_fun.return_type, once_state_global), + call once_fun, [ + load(once_init_fun.type.return_type, once_state_global), flag, - bit_cast(func.to_value, once_fun.params.last.type), + bit_cast(func.func.to_value, once_fun.func.params.last.type), ] end end diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index 9942e685606b..76a0a0bf25c1 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -180,7 +180,7 @@ class Crystal::CodeGenVisitor end llvm_fun = binary_overflow_fun "llvm.#{llvm_op}.with.overflow.i#{calc_width}", calc_type - res_with_overflow = builder.call(llvm_fun, [e1, e2]) + res_with_overflow = builder.call(llvm_fun.type, llvm_fun.func, [e1, e2]) result = extract_value res_with_overflow, 0 overflow = extract_value res_with_overflow, 1 @@ -356,7 +356,7 @@ class Crystal::CodeGenVisitor op_overflow = new_block "overflow" op_normal = new_block "normal" - overflow_condition = builder.call(llvm_expect_i1_fun, [overflow_condition, llvm_false]) + overflow_condition = builder.call(llvm_expect_i1_fun.type, llvm_expect_i1_fun.func, [overflow_condition, llvm_false]) cond overflow_condition, op_overflow, op_normal position_at_end op_overflow @@ -366,15 +366,18 @@ class Crystal::CodeGenVisitor end private def binary_overflow_fun(fun_name, llvm_operand_type) - llvm_mod.functions[fun_name]? || - llvm_mod.functions.add(fun_name, [llvm_operand_type, llvm_operand_type], - llvm_context.struct([llvm_operand_type, llvm_context.int1])) + fetch_typed_fun(@llvm_mod, fun_name) do + LLVM::Type.function( + [llvm_operand_type, llvm_operand_type], + @llvm_context.struct([llvm_operand_type, @llvm_context.int1]), + ) + end end private def llvm_expect_i1_fun - llvm_mod.functions["llvm.expect.i1"]? || - llvm_mod.functions.add("llvm.expect.i1", [llvm_context.int1, llvm_context.int1], - llvm_context.int1) + fetch_typed_fun(@llvm_mod, "llvm.expect.i1") do + LLVM::Type.function([@llvm_context.int1, @llvm_context.int1], @llvm_context.int1) + end end # The below methods (lt, lte, gt, gte, eq, ne) perform @@ -849,7 +852,7 @@ class Crystal::CodeGenVisitor def union_field_ptr(union_type, field_type, pointer) ptr = aggregate_index llvm_type(union_type), pointer, 0 if field_type.is_a?(ProcInstanceType) - bit_cast ptr, @llvm_typer.proc_type(field_type).pointer + bit_cast ptr, @llvm_typer.proc_type(field_type).pointer.pointer else cast_to_pointer ptr, field_type end @@ -920,7 +923,7 @@ class Crystal::CodeGenVisitor def codegen_primitive_class_with_type(type : VirtualType, value) type_id = type_id(value, type) metaclass_fun_name = "~metaclass" - func = @main_mod.functions[metaclass_fun_name]? || create_metaclass_fun(metaclass_fun_name) + func = typed_fun?(@main_mod, metaclass_fun_name) || create_metaclass_fun(metaclass_fun_name) func = check_main_fun metaclass_fun_name, func call func, [type_id] of LLVM::Value end @@ -991,7 +994,8 @@ class Crystal::CodeGenVisitor Phi.open(self, node, @needs_value) do |phi| position_at_end ctx_is_null_block - real_fun_ptr = bit_cast fun_ptr, llvm_proc_type(context.type) + real_fun_llvm_type = llvm_proc_type(context.type) + real_fun_ptr = bit_cast fun_ptr, real_fun_llvm_type.pointer # When invoking a Proc that has extern structs as arguments or return type, it's tricky: # closures are never generated with C ABI because C doesn't support closures. @@ -1003,13 +1007,13 @@ class Crystal::CodeGenVisitor old_c_calling_convention = target_def.c_calling_convention if c_calling_convention - null_fun_ptr, null_args = codegen_extern_primitive_proc_call(target_def, args, fun_ptr) + null_fun_ptr, null_fun_llvm_type, null_args = codegen_extern_primitive_proc_call(target_def, args, fun_ptr) else - null_fun_ptr, null_args = real_fun_ptr, closure_args + null_fun_ptr, null_fun_llvm_type, null_args = real_fun_ptr, real_fun_llvm_type, closure_args end - null_fun_ptr = LLVM::Function.from_value(null_fun_ptr) + null_fun = LLVMTypedFunction.new(null_fun_llvm_type, LLVM::Function.from_value(null_fun_ptr)) - value = codegen_call_or_invoke(node, target_def, nil, null_fun_ptr, null_args, true, target_def.type, false, proc_type) + value = codegen_call_or_invoke(node, target_def, nil, null_fun, null_args, true, target_def.type, false, proc_type) phi.add value, node.type # Reset abi_info + c_calling_convention so the closure part is generated as usual @@ -1017,10 +1021,11 @@ class Crystal::CodeGenVisitor target_def.c_calling_convention = nil position_at_end ctx_is_not_null_block - real_fun_ptr = bit_cast fun_ptr, llvm_closure_type(context.type) - real_fun_ptr = LLVM::Function.from_value(real_fun_ptr) + real_fun_llvm_type = llvm_closure_type(context.type) + real_fun_ptr = bit_cast fun_ptr, real_fun_llvm_type.pointer + real_fun = LLVMTypedFunction.new(real_fun_llvm_type, LLVM::Function.from_value(real_fun_ptr)) closure_args.insert(0, ctx_ptr) - value = codegen_call_or_invoke(node, target_def, nil, real_fun_ptr, closure_args, true, target_def.type, true, proc_type) + value = codegen_call_or_invoke(node, target_def, nil, real_fun, closure_args, true, target_def.type, true, proc_type) phi.add value, node.type, true target_def.abi_info = old_abi_info @@ -1073,7 +1078,7 @@ class Crystal::CodeGenVisitor null_fun_ptr = bit_cast fun_ptr, null_fun_llvm_type.pointer target_def.c_calling_convention = true - {null_fun_ptr, null_args} + {null_fun_ptr, null_fun_llvm_type, null_args} end def codegen_primitive_pointer_diff(node, target_def, call_args) diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index 11e0affe0ddb..4d333dfb830f 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -49,7 +49,7 @@ class LLVM::Builder Value.new phi_node end - def call(func, name : String = "") + def call(func : LLVM::Function, name : String = "") # check_func(func) {% if LibLLVM::IS_LT_80 %} @@ -59,7 +59,14 @@ class LLVM::Builder {% end %} end - def call(func, arg : LLVM::Value, name : String = "") + def call(type : LLVM::Type, func : LLVM::Function, name : String = "") + # check_type("call", type) + # check_func(func) + + Value.new LibLLVM.build_call2(self, type, func, nil, 0, name) + end + + def call(func : LLVM::Function, arg : LLVM::Value, name : String = "") # check_func(func) # check_value(arg) @@ -71,13 +78,30 @@ class LLVM::Builder {% end %} end - def call(func, args : Array(LLVM::Value), name : String = "", bundle : LLVM::OperandBundleDef = LLVM::OperandBundleDef.null) + def call(type : LLVM::Type, func : LLVM::Function, arg : LLVM::Value, name : String = "") + # check_type("call", type) + # check_func(func) + # check_value(arg) + + value = arg.to_unsafe + Value.new LibLLVM.build_call2(self, type, func, pointerof(value), 1, name) + end + + def call(func : LLVM::Function, args : Array(LLVM::Value), name : String = "", bundle : LLVM::OperandBundleDef = LLVM::OperandBundleDef.null) # check_func(func) # check_values(args) Value.new LibLLVMExt.build_call2(self, func.function_type, func, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, bundle, name) end + def call(type : LLVM::Type, func : LLVM::Function, args : Array(LLVM::Value), name : String = "", bundle : LLVM::OperandBundleDef = LLVM::OperandBundleDef.null) + # check_type("call", type) + # check_func(func) + # check_values(args) + + Value.new LibLLVMExt.build_call2(self, type, func, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, bundle, name) + end + def alloca(type, name = "") # check_type("alloca", type) @@ -136,6 +160,7 @@ class LLVM::Builder end def {{method_name.id}}(type : LLVM::Type, value : LLVM::Value, indices : Array(LLVM::ValueRef), name = "") + # check_type({{method_name}}, type) # check_value(value) \{% if LibLLVM::IS_LT_80 %} @@ -157,6 +182,7 @@ class LLVM::Builder end def {{method_name.id}}(type : LLVM::Type, value : LLVM::Value, index : LLVM::Value, name = "") + # check_type({{method_name}}, type) # check_value(value) indices = pointerof(index).as(LibLLVM::ValueRef*) @@ -181,6 +207,7 @@ class LLVM::Builder end def {{method_name.id}}(type : LLVM::Type, value : LLVM::Value, index1 : LLVM::Value, index2 : LLVM::Value, name = "") + # check_type({{method_name}}, type) # check_value(value) indices = uninitialized LLVM::Value[2] @@ -286,6 +313,13 @@ class LLVM::Builder Value.new LibLLVMExt.build_invoke2 self, fn.function_type, fn, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, a_then, a_catch, bundle, name end + def invoke(type : LLVM::Type, fn : LLVM::Function, args : Array(LLVM::Value), a_then, a_catch, bundle : LLVM::OperandBundleDef = LLVM::OperandBundleDef.null, name = "") + # check_type("invoke", type) + # check_func(fn) + + Value.new LibLLVMExt.build_invoke2 self, type, fn, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, a_then, a_catch, bundle, name + end + def switch(value, otherwise, cases) # check_value(value) diff --git a/src/llvm/function_collection.cr b/src/llvm/function_collection.cr index d4e6663fb2d4..62e2bd2e6fc2 100644 --- a/src/llvm/function_collection.cr +++ b/src/llvm/function_collection.cr @@ -4,14 +4,22 @@ struct LLVM::FunctionCollection def add(name, arg_types : Array(LLVM::Type), ret_type, varargs = false) # check_types_context(name, arg_types, ret_type) + add(name, LLVM::Type.function(arg_types, ret_type, varargs)) + end + + def add(name, arg_types : Array(LLVM::Type), ret_type, varargs = false, &) + func = add(name, arg_types, ret_type, varargs) + yield func + func + end - fun_type = LLVM::Type.function(arg_types, ret_type, varargs) + def add(name, fun_type : LLVM::Type) func = LibLLVM.add_function(@mod, name, fun_type) Function.new(func) end - def add(name, arg_types : Array(LLVM::Type), ret_type, varargs = false) - func = add(name, arg_types, ret_type, varargs) + def add(name, fun_type : LLVM::Type, &) + func = add(name, fun_type) yield func func end @@ -26,7 +34,7 @@ struct LLVM::FunctionCollection func ? Function.new(func) : nil end - def each : Nil + def each(&) : Nil f = LibLLVM.get_first_function(@mod) while f yield LLVM::Function.new f diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 6c7b3ffb2c81..279220e8b5f2 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -201,7 +201,6 @@ lib LibLLVM fun generic_value_to_int = LLVMGenericValueToInt(value : GenericValueRef, signed : Int32) : UInt64 fun generic_value_to_pointer = LLVMGenericValueToPointer(value : GenericValueRef) : Void* fun get_current_debug_location = LLVMGetCurrentDebugLocation(builder : BuilderRef) : ValueRef - fun get_element_type = LLVMGetElementType(ty : TypeRef) : TypeRef fun get_first_instruction = LLVMGetFirstInstruction(block : BasicBlockRef) : ValueRef fun get_first_target = LLVMGetFirstTarget : TargetRef fun get_first_basic_block = LLVMGetFirstBasicBlock(fn : ValueRef) : BasicBlockRef diff --git a/src/llvm/parameter_collection.cr b/src/llvm/parameter_collection.cr index d9e850b987d6..cd1a47a3ebc8 100644 --- a/src/llvm/parameter_collection.cr +++ b/src/llvm/parameter_collection.cr @@ -5,7 +5,7 @@ struct LLVM::ParameterCollection end def size - @function.function_type.params_size + LibLLVM.get_count_params(@function).to_i end def to_a From d2c730f5d52c0940ecce762b209b84a131934935 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 9 Mar 2023 17:02:25 +0800 Subject: [PATCH 0361/1551] Use `Crystal::System.print_error` instead of `LibC.printf` (#13161) --- src/gc/boehm.cr | 2 +- src/raise.cr | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 325fa82f31b0..07bfe7325e33 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -158,7 +158,7 @@ module GC # This implements `String#starts_with?` without allocating a `String` (#11728) format_string = Slice.new(msg, Math.min(LibC.strlen(msg), start.bytesize)) unless format_string == start.to_slice - LibC.printf msg, v + Crystal::System.print_error msg, v end end end diff --git a/src/raise.cr b/src/raise.cr index 62f56a1d38a8..1ee3a89caa56 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -105,7 +105,7 @@ end # :nodoc: @[Raises] fun __crystal_raise(unwind_ex : LibUnwind::Exception*) : NoReturn - LibC.printf("EXITING: __crystal_raise called") + Crystal::System.print_error "EXITING: __crystal_raise called" LibC.exit(1) end {% elsif flag?(:arm) %} @@ -160,20 +160,20 @@ end {% elsif flag?(:wasm32) %} # :nodoc: fun __crystal_personality - LibC.printf("EXITING: __crystal_personality called") + Crystal::System.print_error "EXITING: __crystal_personality called" LibC.exit(1) end # :nodoc: @[Raises] fun __crystal_raise(ex : Void*) : NoReturn - LibC.printf("EXITING: __crystal_raise called") + Crystal::System.print_error "EXITING: __crystal_raise called" LibC.exit(1) end # :nodoc: fun __crystal_get_exception(ex : Void*) : UInt64 - LibC.printf("EXITING: __crystal_get_exception called") + Crystal::System.print_error "EXITING: __crystal_get_exception called" LibC.exit(1) 0u64 end @@ -215,7 +215,7 @@ end {% if flag?(:wasm32) %} def raise(exception : Exception) : NoReturn - LibC.printf("EXITING: Attempting to raise:\n#{exception.inspect_with_backtrace}") + Crystal::System.print_error "EXITING: Attempting to raise:\n#{exception.inspect_with_backtrace}" LibIntrinsics.debugtrap LibC.exit(1) end From dd005b96428b33957fd45a5196f1b8d482994786 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 9 Mar 2023 17:02:51 +0800 Subject: [PATCH 0362/1551] Remove `LibLLVM.has_constant?(:AttributeRef)` checks (#13162) --- src/llvm/enums.cr | 348 +++++++++++++++++--------------------- src/llvm/function.cr | 49 ++---- src/llvm/value_methods.cr | 21 +-- 3 files changed, 180 insertions(+), 238 deletions(-) diff --git a/src/llvm/enums.cr b/src/llvm/enums.cr index 649d54383c1d..29b0ddedd8fa 100644 --- a/src/llvm/enums.cr +++ b/src/llvm/enums.cr @@ -1,206 +1,172 @@ module LLVM - {% if LibLLVM.has_constant?(:AttributeRef) %} - @[Flags] - enum Attribute : UInt64 - Alignment - AllocSize - AlwaysInline - ArgMemOnly - Builtin - ByVal - Cold - Convergent - Dereferenceable - DereferenceableOrNull - InAlloca - InReg - InaccessibleMemOnly - InaccessibleMemOrArgMemOnly - InlineHint - JumpTable - MinSize - Naked - Nest - NoAlias - NoBuiltin - NoCapture - NoDuplicate - NoFree - NoImplicitFloat - NoInline - NoRecurse - NoRedZone - NoReturn - NoSync - NoUnwind - NonLazyBind - NonNull - OptimizeForSize - OptimizeNone - ReadNone - ReadOnly - Returned - ImmArg - ReturnsTwice - SExt - SafeStack - SanitizeAddress - SanitizeMemory - SanitizeThread - StackAlignment - StackProtect - StackProtectReq - StackProtectStrong - StructRet - SwiftError - SwiftSelf - UWTable - WillReturn - WriteOnly - ZExt - - @@kind_ids = load_llvm_kinds_from_names.as(Hash(Attribute, UInt32)) - @@typed_attrs = load_llvm_typed_attributes.as(Array(Attribute)) - - def each_kind(&block) - return if value == 0 - \{% for member in @type.constants %} - \{% if member.stringify != "All" %} - if includes?(\{{@type}}::\{{member}}) - yield @@kind_ids[\{{@type}}::\{{member}}] - end - \{% end %} - \{% end %} - end - - private def self.kind_for_name(name : String) - LibLLVM.get_enum_attribute_kind_for_name(name, name.bytesize) - end - - private def self.load_llvm_kinds_from_names - kinds = {} of Attribute => UInt32 - kinds[Alignment] = kind_for_name("align") - kinds[AllocSize] = kind_for_name("allocsize") - kinds[AlwaysInline] = kind_for_name("alwaysinline") - kinds[ArgMemOnly] = kind_for_name("argmemonly") - kinds[Builtin] = kind_for_name("builtin") - kinds[ByVal] = kind_for_name("byval") - kinds[Cold] = kind_for_name("cold") - kinds[Convergent] = kind_for_name("convergent") - kinds[Dereferenceable] = kind_for_name("dereferenceable") - kinds[DereferenceableOrNull] = kind_for_name("dereferenceable_or_null") - kinds[InAlloca] = kind_for_name("inalloca") - kinds[InReg] = kind_for_name("inreg") - kinds[InaccessibleMemOnly] = kind_for_name("inaccessiblememonly") - kinds[InaccessibleMemOrArgMemOnly] = kind_for_name("inaccessiblemem_or_argmemonly") - kinds[InlineHint] = kind_for_name("inlinehint") - kinds[JumpTable] = kind_for_name("jumptable") - kinds[MinSize] = kind_for_name("minsize") - kinds[Naked] = kind_for_name("naked") - kinds[Nest] = kind_for_name("nest") - kinds[NoAlias] = kind_for_name("noalias") - kinds[NoBuiltin] = kind_for_name("nobuiltin") - kinds[NoCapture] = kind_for_name("nocapture") - kinds[NoDuplicate] = kind_for_name("noduplicate") - kinds[NoFree] = kind_for_name("nofree") - kinds[NoImplicitFloat] = kind_for_name("noimplicitfloat") - kinds[NoInline] = kind_for_name("noinline") - kinds[NoRecurse] = kind_for_name("norecurse") - kinds[NoRedZone] = kind_for_name("noredzone") - kinds[NoReturn] = kind_for_name("noreturn") - kinds[NoSync] = kind_for_name("nosync") - kinds[NoUnwind] = kind_for_name("nounwind") - kinds[NonLazyBind] = kind_for_name("nonlazybind") - kinds[NonNull] = kind_for_name("nonnull") - kinds[OptimizeForSize] = kind_for_name("optsize") - kinds[OptimizeNone] = kind_for_name("optnone") - kinds[ReadNone] = kind_for_name("readnone") - kinds[ReadOnly] = kind_for_name("readonly") - kinds[Returned] = kind_for_name("returned") - kinds[ImmArg] = kind_for_name("immarg") - kinds[ReturnsTwice] = kind_for_name("returns_twice") - kinds[SExt] = kind_for_name("signext") - kinds[SafeStack] = kind_for_name("safestack") - kinds[SanitizeAddress] = kind_for_name("sanitize_address") - kinds[SanitizeMemory] = kind_for_name("sanitize_memory") - kinds[SanitizeThread] = kind_for_name("sanitize_thread") - kinds[StackAlignment] = kind_for_name("alignstack") - kinds[StackProtect] = kind_for_name("ssp") - kinds[StackProtectReq] = kind_for_name("sspreq") - kinds[StackProtectStrong] = kind_for_name("sspstrong") - kinds[StructRet] = kind_for_name("sret") - kinds[SwiftError] = kind_for_name("swifterror") - kinds[SwiftSelf] = kind_for_name("swiftself") - kinds[UWTable] = kind_for_name("uwtable") - kinds[WillReturn] = kind_for_name("willreturn") - kinds[WriteOnly] = kind_for_name("writeonly") - kinds[ZExt] = kind_for_name("zeroext") - kinds - end + @[Flags] + enum Attribute : UInt64 + Alignment + AllocSize + AlwaysInline + ArgMemOnly + Builtin + ByVal + Cold + Convergent + Dereferenceable + DereferenceableOrNull + InAlloca + InReg + InaccessibleMemOnly + InaccessibleMemOrArgMemOnly + InlineHint + JumpTable + MinSize + Naked + Nest + NoAlias + NoBuiltin + NoCapture + NoDuplicate + NoFree + NoImplicitFloat + NoInline + NoRecurse + NoRedZone + NoReturn + NoSync + NoUnwind + NonLazyBind + NonNull + OptimizeForSize + OptimizeNone + ReadNone + ReadOnly + Returned + ImmArg + ReturnsTwice + SExt + SafeStack + SanitizeAddress + SanitizeMemory + SanitizeThread + StackAlignment + StackProtect + StackProtectReq + StackProtectStrong + StructRet + SwiftError + SwiftSelf + UWTable + WillReturn + WriteOnly + ZExt + + @@kind_ids = load_llvm_kinds_from_names.as(Hash(Attribute, UInt32)) + @@typed_attrs = load_llvm_typed_attributes.as(Array(Attribute)) + + def each_kind(&block) + return if value == 0 + {% for member in @type.constants %} + {% if member.stringify != "All" %} + if includes?({{@type}}::{{member}}) + yield @@kind_ids[{{@type}}::{{member}}] + end + {% end %} + {% end %} + end - private def self.load_llvm_typed_attributes - typed_attrs = [] of Attribute + private def self.kind_for_name(name : String) + LibLLVM.get_enum_attribute_kind_for_name(name, name.bytesize) + end - unless LibLLVM::IS_LT_120 - # LLVM 12 introduced mandatory type parameters for byval and sret - typed_attrs << ByVal - typed_attrs << StructRet - end + private def self.load_llvm_kinds_from_names + kinds = {} of Attribute => UInt32 + kinds[Alignment] = kind_for_name("align") + kinds[AllocSize] = kind_for_name("allocsize") + kinds[AlwaysInline] = kind_for_name("alwaysinline") + kinds[ArgMemOnly] = kind_for_name("argmemonly") + kinds[Builtin] = kind_for_name("builtin") + kinds[ByVal] = kind_for_name("byval") + kinds[Cold] = kind_for_name("cold") + kinds[Convergent] = kind_for_name("convergent") + kinds[Dereferenceable] = kind_for_name("dereferenceable") + kinds[DereferenceableOrNull] = kind_for_name("dereferenceable_or_null") + kinds[InAlloca] = kind_for_name("inalloca") + kinds[InReg] = kind_for_name("inreg") + kinds[InaccessibleMemOnly] = kind_for_name("inaccessiblememonly") + kinds[InaccessibleMemOrArgMemOnly] = kind_for_name("inaccessiblemem_or_argmemonly") + kinds[InlineHint] = kind_for_name("inlinehint") + kinds[JumpTable] = kind_for_name("jumptable") + kinds[MinSize] = kind_for_name("minsize") + kinds[Naked] = kind_for_name("naked") + kinds[Nest] = kind_for_name("nest") + kinds[NoAlias] = kind_for_name("noalias") + kinds[NoBuiltin] = kind_for_name("nobuiltin") + kinds[NoCapture] = kind_for_name("nocapture") + kinds[NoDuplicate] = kind_for_name("noduplicate") + kinds[NoFree] = kind_for_name("nofree") + kinds[NoImplicitFloat] = kind_for_name("noimplicitfloat") + kinds[NoInline] = kind_for_name("noinline") + kinds[NoRecurse] = kind_for_name("norecurse") + kinds[NoRedZone] = kind_for_name("noredzone") + kinds[NoReturn] = kind_for_name("noreturn") + kinds[NoSync] = kind_for_name("nosync") + kinds[NoUnwind] = kind_for_name("nounwind") + kinds[NonLazyBind] = kind_for_name("nonlazybind") + kinds[NonNull] = kind_for_name("nonnull") + kinds[OptimizeForSize] = kind_for_name("optsize") + kinds[OptimizeNone] = kind_for_name("optnone") + kinds[ReadNone] = kind_for_name("readnone") + kinds[ReadOnly] = kind_for_name("readonly") + kinds[Returned] = kind_for_name("returned") + kinds[ImmArg] = kind_for_name("immarg") + kinds[ReturnsTwice] = kind_for_name("returns_twice") + kinds[SExt] = kind_for_name("signext") + kinds[SafeStack] = kind_for_name("safestack") + kinds[SanitizeAddress] = kind_for_name("sanitize_address") + kinds[SanitizeMemory] = kind_for_name("sanitize_memory") + kinds[SanitizeThread] = kind_for_name("sanitize_thread") + kinds[StackAlignment] = kind_for_name("alignstack") + kinds[StackProtect] = kind_for_name("ssp") + kinds[StackProtectReq] = kind_for_name("sspreq") + kinds[StackProtectStrong] = kind_for_name("sspstrong") + kinds[StructRet] = kind_for_name("sret") + kinds[SwiftError] = kind_for_name("swifterror") + kinds[SwiftSelf] = kind_for_name("swiftself") + kinds[UWTable] = kind_for_name("uwtable") + kinds[WillReturn] = kind_for_name("willreturn") + kinds[WriteOnly] = kind_for_name("writeonly") + kinds[ZExt] = kind_for_name("zeroext") + kinds + end - unless LibLLVM::IS_LT_130 - # LLVM 13 manadates type params for inalloca - typed_attrs << InAlloca - end + private def self.load_llvm_typed_attributes + typed_attrs = [] of Attribute - typed_attrs + unless LibLLVM::IS_LT_120 + # LLVM 12 introduced mandatory type parameters for byval and sret + typed_attrs << ByVal + typed_attrs << StructRet end - def self.kind_for(member) - @@kind_ids[member] + unless LibLLVM::IS_LT_130 + # LLVM 13 manadates type params for inalloca + typed_attrs << InAlloca end - def self.from_kind(kind) - @@kind_ids.key_for(kind) - end + typed_attrs + end - def self.requires_type?(kind) - member = from_kind(kind) - @@typed_attrs.includes?(member) - end + def self.kind_for(member) + @@kind_ids[member] end - {% else %} - @[Flags] - enum Attribute : UInt32 - ZExt = 1 << 0 - SExt = 1 << 1 - NoReturn = 1 << 2 - InReg = 1 << 3 - StructRet = 1 << 4 - NoUnwind = 1 << 5 - NoAlias = 1 << 6 - ByVal = 1 << 7 - Nest = 1 << 8 - ReadNone = 1 << 9 - ReadOnly = 1 << 10 - NoInline = 1 << 11 - AlwaysInline = 1 << 12 - OptimizeForSize = 1 << 13 - StackProtect = 1 << 14 - StackProtectReq = 1 << 15 - Alignment = 31 << 16 - NoCapture = 1 << 21 - NoRedZone = 1 << 22 - NoImplicitFloat = 1 << 23 - Naked = 1 << 24 - InlineHint = 1 << 25 - StackAlignment = 7 << 26 - ReturnsTwice = 1 << 29 - UWTable = 1 << 30 - NonLazyBind = 1 << 31 - # AddressSafety = 1_u64 << 32, - # StackProtectStrong = 1_u64 << 33 + + def self.from_kind(kind) + @@kind_ids.key_for(kind) end - {% end %} + + def self.requires_type?(kind) + member = from_kind(kind) + @@typed_attrs.includes?(member) + end + end # Attribute index are either ReturnIndex (0), FunctionIndex (-1) or a # parameter number ranging from 1 to N. diff --git a/src/llvm/function.cr b/src/llvm/function.cr index 13d6af5ca5cd..20c2a4ebbad2 100644 --- a/src/llvm/function.cr +++ b/src/llvm/function.cr @@ -21,26 +21,16 @@ struct LLVM::Function def add_attribute(attribute : Attribute, index = AttributeIndex::FunctionIndex, type : Type? = nil) return if attribute.value == 0 - {% if LibLLVM.has_constant?(:AttributeRef) %} - context = LibLLVM.get_module_context(LibLLVM.get_global_parent(self)) - attribute.each_kind do |kind| - if type && LLVM::Attribute.requires_type?(kind) - attribute_ref = LibLLVMExt.create_type_attribute(context, kind, type) - else - attribute_ref = LibLLVM.create_enum_attribute(context, kind, 0) - end - LibLLVM.add_attribute_at_index(self, index, attribute_ref) - end - {% else %} - case index - when AttributeIndex::FunctionIndex - LibLLVM.add_function_attr(self, attribute) - when AttributeIndex::ReturnIndex - raise "Unsupported: can't set attributes on function return type in LLVM < 3.9" + + context = LibLLVM.get_module_context(LibLLVM.get_global_parent(self)) + attribute.each_kind do |kind| + if type && LLVM::Attribute.requires_type?(kind) + attribute_ref = LibLLVMExt.create_type_attribute(context, kind, type) else - LibLLVM.add_attribute(params[index.to_i - 1], attribute) + attribute_ref = LibLLVM.create_enum_attribute(context, kind, 0) end - {% end %} + LibLLVM.add_attribute_at_index(self, index, attribute_ref) + end end def add_target_dependent_attribute(name, value) @@ -48,24 +38,13 @@ struct LLVM::Function end def attributes(index = AttributeIndex::FunctionIndex) - {% if LibLLVM.has_constant?(:AttributeRef) %} - attrs = Attribute::None - 0.upto(LibLLVM.get_last_enum_attribute_kind) do |kind| - if LibLLVM.get_enum_attribute_at_index(self, index, kind) - attrs |= Attribute.from_kind(kind) - end - end - attrs - {% else %} - case index - when AttributeIndex::FunctionIndex - LibLLVM.get_function_attr(self) - when AttributeIndex::ReturnIndex - raise "Unsupported: can't get attributes from function return type in LLVM < 3.9" - else - LibLLVM.get_attribute(params[index.to_i - 1]) + attrs = Attribute::None + 0.upto(LibLLVM.get_last_enum_attribute_kind) do |kind| + if LibLLVM.get_enum_attribute_at_index(self, index, kind) + attrs |= Attribute.from_kind(kind) end - {% end %} + end + attrs end def function_type diff --git a/src/llvm/value_methods.cr b/src/llvm/value_methods.cr index 9c6985ae06f7..91fb55700ee0 100644 --- a/src/llvm/value_methods.cr +++ b/src/llvm/value_methods.cr @@ -16,19 +16,16 @@ module LLVM::ValueMethods def add_instruction_attribute(index : Int, attribute : LLVM::Attribute, context : LLVM::Context, type : LLVM::Type? = nil) return if attribute.value == 0 - {% if LibLLVM.has_constant?(:AttributeRef) %} - attribute.each_kind do |kind| - if type && LLVM::Attribute.requires_type?(kind) - attribute_ref = LibLLVMExt.create_type_attribute(context, kind, type) - else - attribute_ref = LibLLVM.create_enum_attribute(context, kind, 0) - end - - LibLLVM.add_call_site_attribute(self, index, attribute_ref) + + attribute.each_kind do |kind| + if type && LLVM::Attribute.requires_type?(kind) + attribute_ref = LibLLVMExt.create_type_attribute(context, kind, type) + else + attribute_ref = LibLLVM.create_enum_attribute(context, kind, 0) end - {% else %} - LibLLVM.add_instr_attribute(self, index, attribute) - {% end %} + + LibLLVM.add_call_site_attribute(self, index, attribute_ref) + end end def constant? From 374224d61e315b06c97ab1a3cee498cb02e1a28b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 9 Mar 2023 18:17:07 +0800 Subject: [PATCH 0363/1551] AArch64 Android support (#13065) --- .github/workflows/smoke.yml | 3 + spec/compiler/loader/unix_spec.cr | 12 +- spec/std/file_spec.cr | 21 ++- spec/std/file_utils_spec.cr | 101 +++++------ spec/std/process_spec.cr | 2 +- spec/std/socket/tcp_server_spec.cr | 12 +- spec/std/socket/tcp_socket_spec.cr | 12 +- spec/std/static_array_spec.cr | 2 +- spec/std/system/group_spec.cr | 14 +- spec/std/system/user_spec.cr | 14 +- spec/std/va_list_spec.cr | 2 +- src/compiler/crystal/codegen/target.cr | 6 + src/compiler/crystal/loader/unix.cr | 7 +- src/compiler/crystal/semantic/flags.cr | 1 + src/crystal/system/unix/lib_event2.cr | 2 +- src/crystal/system/unix/pthread.cr | 4 +- src/errno.cr | 18 +- src/exception/call_stack/elf.cr | 43 +++-- src/gc/boehm.cr | 2 +- src/lib_c.cr | 9 + src/lib_c/aarch64-android/c/arpa/inet.cr | 11 ++ src/lib_c/aarch64-android/c/dirent.cr | 23 +++ src/lib_c/aarch64-android/c/dlfcn.cr | 21 +++ src/lib_c/aarch64-android/c/elf.cr | 24 +++ src/lib_c/aarch64-android/c/errno.cr | 136 +++++++++++++++ src/lib_c/aarch64-android/c/fcntl.cr | 25 +++ src/lib_c/aarch64-android/c/grp.cr | 13 ++ src/lib_c/aarch64-android/c/iconv.cr | 11 ++ src/lib_c/aarch64-android/c/link.cr | 19 +++ src/lib_c/aarch64-android/c/netdb.cr | 40 +++++ src/lib_c/aarch64-android/c/netinet/in.cr | 69 ++++++++ src/lib_c/aarch64-android/c/netinet/tcp.cr | 6 + src/lib_c/aarch64-android/c/pthread.cr | 39 +++++ src/lib_c/aarch64-android/c/pwd.cr | 14 ++ src/lib_c/aarch64-android/c/sched.cr | 3 + src/lib_c/aarch64-android/c/signal.cr | 92 ++++++++++ src/lib_c/aarch64-android/c/stdarg.cr | 3 + src/lib_c/aarch64-android/c/stddef.cr | 3 + src/lib_c/aarch64-android/c/stdint.cr | 10 ++ src/lib_c/aarch64-android/c/stdio.cr | 13 ++ src/lib_c/aarch64-android/c/stdlib.cr | 21 +++ src/lib_c/aarch64-android/c/string.cr | 9 + src/lib_c/aarch64-android/c/sys/file.cr | 11 ++ src/lib_c/aarch64-android/c/sys/mman.cr | 31 ++++ src/lib_c/aarch64-android/c/sys/resource.cr | 25 +++ src/lib_c/aarch64-android/c/sys/select.cr | 14 ++ src/lib_c/aarch64-android/c/sys/socket.cr | 65 +++++++ src/lib_c/aarch64-android/c/sys/stat.cr | 56 ++++++ src/lib_c/aarch64-android/c/sys/syscall.cr | 3 + src/lib_c/aarch64-android/c/sys/time.cr | 19 +++ src/lib_c/aarch64-android/c/sys/types.cr | 44 +++++ src/lib_c/aarch64-android/c/sys/un.cr | 8 + src/lib_c/aarch64-android/c/sys/wait.cr | 8 + src/lib_c/aarch64-android/c/termios.cr | 178 ++++++++++++++++++++ src/lib_c/aarch64-android/c/time.cr | 37 ++++ src/lib_c/aarch64-android/c/unistd.cr | 51 ++++++ src/llvm.cr | 3 + 57 files changed, 1342 insertions(+), 103 deletions(-) create mode 100644 src/lib_c/aarch64-android/c/arpa/inet.cr create mode 100644 src/lib_c/aarch64-android/c/dirent.cr create mode 100644 src/lib_c/aarch64-android/c/dlfcn.cr create mode 100644 src/lib_c/aarch64-android/c/elf.cr create mode 100644 src/lib_c/aarch64-android/c/errno.cr create mode 100644 src/lib_c/aarch64-android/c/fcntl.cr create mode 100644 src/lib_c/aarch64-android/c/grp.cr create mode 100644 src/lib_c/aarch64-android/c/iconv.cr create mode 100644 src/lib_c/aarch64-android/c/link.cr create mode 100644 src/lib_c/aarch64-android/c/netdb.cr create mode 100644 src/lib_c/aarch64-android/c/netinet/in.cr create mode 100644 src/lib_c/aarch64-android/c/netinet/tcp.cr create mode 100644 src/lib_c/aarch64-android/c/pthread.cr create mode 100644 src/lib_c/aarch64-android/c/pwd.cr create mode 100644 src/lib_c/aarch64-android/c/sched.cr create mode 100644 src/lib_c/aarch64-android/c/signal.cr create mode 100644 src/lib_c/aarch64-android/c/stdarg.cr create mode 100644 src/lib_c/aarch64-android/c/stddef.cr create mode 100644 src/lib_c/aarch64-android/c/stdint.cr create mode 100644 src/lib_c/aarch64-android/c/stdio.cr create mode 100644 src/lib_c/aarch64-android/c/stdlib.cr create mode 100644 src/lib_c/aarch64-android/c/string.cr create mode 100644 src/lib_c/aarch64-android/c/sys/file.cr create mode 100644 src/lib_c/aarch64-android/c/sys/mman.cr create mode 100644 src/lib_c/aarch64-android/c/sys/resource.cr create mode 100644 src/lib_c/aarch64-android/c/sys/select.cr create mode 100644 src/lib_c/aarch64-android/c/sys/socket.cr create mode 100644 src/lib_c/aarch64-android/c/sys/stat.cr create mode 100644 src/lib_c/aarch64-android/c/sys/syscall.cr create mode 100644 src/lib_c/aarch64-android/c/sys/time.cr create mode 100644 src/lib_c/aarch64-android/c/sys/types.cr create mode 100644 src/lib_c/aarch64-android/c/sys/un.cr create mode 100644 src/lib_c/aarch64-android/c/sys/wait.cr create mode 100644 src/lib_c/aarch64-android/c/termios.cr create mode 100644 src/lib_c/aarch64-android/c/time.cr create mode 100644 src/lib_c/aarch64-android/c/unistd.cr diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 9fd272badbb3..56fde78bcddf 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -7,12 +7,14 @@ # # ```terminal-session # $ find src/lib_c -maxdepth 1 -mindepth 1 -type d -printf '%P\n' | sort +# aarch64-android # aarch64-darwin # aarch64-linux-gnu # aarch64-linux-musl # arm-linux-gnueabihf # i386-linux-gnu # i386-linux-musl +# wasm32-wasi # x86_64-darwin # x86_64-dragonfly # x86_64-freebsd @@ -43,6 +45,7 @@ jobs: fail-fast: false matrix: target: + - aarch64-linux-android - aarch64-darwin - arm-linux-gnueabihf - i386-linux-gnu diff --git a/spec/compiler/loader/unix_spec.cr b/spec/compiler/loader/unix_spec.cr index 3be1c84f19b7..42a63b88e860 100644 --- a/spec/compiler/loader/unix_spec.cr +++ b/spec/compiler/loader/unix_spec.cr @@ -33,11 +33,11 @@ describe Crystal::Loader do end it "parses file paths" do - exc = expect_raises(Crystal::Loader::LoadError, /no such file|image not found|cannot open/i) do + exc = expect_raises(Crystal::Loader::LoadError, /no such file|not found|cannot open/i) do Crystal::Loader.parse(["foobar.o"], search_paths: [] of String) end exc.message.should contain File.join(Dir.current, "foobar.o") - exc = expect_raises(Crystal::Loader::LoadError, /no such file|image not found|cannot open/i) do + exc = expect_raises(Crystal::Loader::LoadError, /no such file|not found|cannot open/i) do Crystal::Loader.parse(["-l", "foo/bar.o"], search_paths: [] of String) end exc.message.should contain File.join(Dir.current, "foo", "bar.o") @@ -52,7 +52,11 @@ describe Crystal::Loader do search_paths.should eq ["/usr/lib", "/usr/local/lib"] {% else %} search_paths[0, 2].should eq ["ld1", "ld2"] - search_paths[-2..].should eq ["/lib", "/usr/lib"] + {% if flag?(:android) %} + search_paths[-2..].should eq ["/vendor/lib", "/system/lib"] + {% else %} + search_paths[-2..].should eq ["/lib", "/usr/lib"] + {% end %} {% end %} end end @@ -63,6 +67,8 @@ describe Crystal::Loader do {% if flag?(:darwin) %} search_paths[0, 2].should eq ["ld1", "ld2"] search_paths[-2..].should eq ["/usr/lib", "/usr/local/lib"] + {% elsif flag?(:android) %} + search_paths[-2..].should eq ["/vendor/lib", "/system/lib"] {% else %} search_paths[-2..].should eq ["/lib", "/usr/lib"] {% end %} diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index daef6a8ca03a..104cf3e16186 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -191,17 +191,20 @@ describe "File" do end end - describe "link" do - it "creates a hard link" do - with_tempfile("hard_link_source.txt", "hard_link_target.txt") do |in_path, out_path| - File.write(in_path, "") - File.link(in_path, out_path) - File.exists?(out_path).should be_true - File.symlink?(out_path).should be_false - File.same?(in_path, out_path).should be_true + # hard links are practically unavailable on Android + {% unless flag?(:android) %} + describe "link" do + it "creates a hard link" do + with_tempfile("hard_link_source.txt", "hard_link_target.txt") do |in_path, out_path| + File.write(in_path, "") + File.link(in_path, out_path) + File.exists?(out_path).should be_true + File.symlink?(out_path).should be_false + File.same?(in_path, out_path).should be_true + end end end - end + {% end %} describe "same?" do it "compares following symlinks only if requested" do diff --git a/spec/std/file_utils_spec.cr b/spec/std/file_utils_spec.cr index aa32b03fd5f3..54dd0bf4de7e 100644 --- a/spec/std/file_utils_spec.cr +++ b/spec/std/file_utils_spec.cr @@ -549,75 +549,78 @@ describe "FileUtils" do end end - describe ".ln" do - it "creates a hardlink" do - with_tempfile("ln_src", "ln_dst") do |path1, path2| - test_with_string_and_path(path1, path2) do |arg1, arg2| - FileUtils.touch(path1) - FileUtils.ln(arg1, arg2) - File.exists?(path2).should be_true - File.symlink?(path2).should be_false - FileUtils.rm_rf([path1, path2]) + # hard links are practically unavailable on Android + {% unless flag?(:android) %} + describe ".ln" do + it "creates a hardlink" do + with_tempfile("ln_src", "ln_dst") do |path1, path2| + test_with_string_and_path(path1, path2) do |arg1, arg2| + FileUtils.touch(path1) + FileUtils.ln(arg1, arg2) + File.exists?(path2).should be_true + File.symlink?(path2).should be_false + FileUtils.rm_rf([path1, path2]) + end end end - end - it "creates a hardlink inside a destination dir" do - with_tempfile("ln_src", "ln_dst_dir") do |path1, path2| - path2 += File::SEPARATOR - path3 = File.join(path2, File.basename(path1)) - test_with_string_and_path(path1, path2) do |arg1, arg2| - FileUtils.touch(path1) - FileUtils.mkdir(path2) - FileUtils.ln(arg1, arg2) - File.exists?(path3).should be_true - File.symlink?(path3).should be_false - FileUtils.rm_rf([path1, path2]) + it "creates a hardlink inside a destination dir" do + with_tempfile("ln_src", "ln_dst_dir") do |path1, path2| + path2 += File::SEPARATOR + path3 = File.join(path2, File.basename(path1)) + test_with_string_and_path(path1, path2) do |arg1, arg2| + FileUtils.touch(path1) + FileUtils.mkdir(path2) + FileUtils.ln(arg1, arg2) + File.exists?(path3).should be_true + File.symlink?(path3).should be_false + FileUtils.rm_rf([path1, path2]) + end end end - end - it "creates multiple hardlinks inside a destination dir" do - with_tempfile("ln_src_1", "ln_src_2", "ln_src_3", "ln_dst_dir") do |path1, path2, path3, dir_path| - paths = [path1, path2, path3] - dir_path += File::SEPARATOR - test_with_string_and_path(path1, path2, path3, dir_path) do |arg1, arg2, arg3, arg4| - paths.each { |path| FileUtils.touch(path) } - FileUtils.mkdir(dir_path) - FileUtils.ln([arg1, arg2, arg3], arg4) + it "creates multiple hardlinks inside a destination dir" do + with_tempfile("ln_src_1", "ln_src_2", "ln_src_3", "ln_dst_dir") do |path1, path2, path3, dir_path| + paths = [path1, path2, path3] + dir_path += File::SEPARATOR + test_with_string_and_path(path1, path2, path3, dir_path) do |arg1, arg2, arg3, arg4| + paths.each { |path| FileUtils.touch(path) } + FileUtils.mkdir(dir_path) + FileUtils.ln([arg1, arg2, arg3], arg4) - paths.each do |path| - link_path = File.join(dir_path, File.basename(path)) - File.exists?(link_path).should be_true - File.symlink?(link_path).should be_false + paths.each do |path| + link_path = File.join(dir_path, File.basename(path)) + File.exists?(link_path).should be_true + File.symlink?(link_path).should be_false + end + FileUtils.rm_rf(dir_path) end - FileUtils.rm_rf(dir_path) end end - end - it "fails with a nonexistent source" do - with_tempfile("ln_src_missing", "ln_dst_missing") do |path1, path2| - test_with_string_and_path(path1, path2) do |arg1, arg2| - expect_raises(File::NotFoundError, "Error creating link: '#{path1.inspect_unquoted}' -> '#{path2.inspect_unquoted}'") do - FileUtils.ln(arg1, arg2) + it "fails with a nonexistent source" do + with_tempfile("ln_src_missing", "ln_dst_missing") do |path1, path2| + test_with_string_and_path(path1, path2) do |arg1, arg2| + expect_raises(File::NotFoundError, "Error creating link: '#{path1.inspect_unquoted}' -> '#{path2.inspect_unquoted}'") do + FileUtils.ln(arg1, arg2) + end end end end - end - it "fails with an extant destination" do - with_tempfile("ln_src", "ln_dst_exists") do |path1, path2| - FileUtils.touch([path1, path2]) + it "fails with an extant destination" do + with_tempfile("ln_src", "ln_dst_exists") do |path1, path2| + FileUtils.touch([path1, path2]) - test_with_string_and_path(path1, path2) do |arg1, arg2| - expect_raises(File::AlreadyExistsError, "Error creating link: '#{path1.inspect_unquoted}' -> '#{path2.inspect_unquoted}'") do - FileUtils.ln(arg1, arg2) + test_with_string_and_path(path1, path2) do |arg1, arg2| + expect_raises(File::AlreadyExistsError, "Error creating link: '#{path1.inspect_unquoted}' -> '#{path2.inspect_unquoted}'") do + FileUtils.ln(arg1, arg2) + end end end end end - end + {% end %} describe ".ln_s" do it "creates a symlink" do diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 1088c0130b35..6f4676064992 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -415,7 +415,7 @@ describe Process do {% end %} describe ".chroot" do - {% if flag?(:unix) %} + {% if flag?(:unix) && !flag?(:android) %} it "raises when unprivileged" do status, output, _ = compile_and_run_source <<-'CRYSTAL' begin diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr index c33d546827d7..496eac4ac860 100644 --- a/spec/std/socket/tcp_server_spec.cr +++ b/spec/std/socket/tcp_server_spec.cr @@ -42,7 +42,13 @@ describe TCPServer, tags: "network" do error = expect_raises(Socket::Addrinfo::Error) do TCPServer.new(address, -12) end - error.os_error.should eq({% if flag?(:linux) %}Errno.new(LibC::EAI_SERVICE){% elsif flag?(:win32) %}WinError::WSATYPE_NOT_FOUND{% else %}Errno.new(LibC::EAI_NONAME){% end %}) + error.os_error.should eq({% if flag?(:win32) %} + WinError::WSATYPE_NOT_FOUND + {% elsif flag?(:linux) && !flag?(:android) %} + Errno.new(LibC::EAI_SERVICE) + {% else %} + Errno.new(LibC::EAI_NONAME) + {% end %}) end describe "reuse_port" do @@ -91,6 +97,8 @@ describe TCPServer, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error + {% elsif flag?(:android) %} + err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error {% end %} @@ -103,6 +111,8 @@ describe TCPServer, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error + {% elsif flag?(:android) %} + err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error {% end %} diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index b82db28e6bea..95c6f3967a61 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -42,7 +42,13 @@ describe TCPSocket, tags: "network" do error = expect_raises(Socket::Addrinfo::Error) do TCPSocket.new(address, -12) end - error.os_error.should eq({% if flag?(:win32) %}WinError::WSATYPE_NOT_FOUND{% elsif flag?(:linux) %}Errno.new(LibC::EAI_SERVICE){% else %}Errno.new(LibC::EAI_NONAME){% end %}) + error.os_error.should eq({% if flag?(:win32) %} + WinError::WSATYPE_NOT_FOUND + {% elsif flag?(:linux) && !flag?(:android) %} + Errno.new(LibC::EAI_SERVICE) + {% else %} + Errno.new(LibC::EAI_NONAME) + {% end %}) end it "raises when port is zero" do @@ -70,6 +76,8 @@ describe TCPSocket, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error + {% elsif flag?(:android) %} + err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error {% end %} @@ -82,6 +90,8 @@ describe TCPSocket, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error + {% elsif flag?(:android) %} + err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error {% end %} diff --git a/spec/std/static_array_spec.cr b/spec/std/static_array_spec.cr index 78f49aa82ab4..714769da87fa 100644 --- a/spec/std/static_array_spec.cr +++ b/spec/std/static_array_spec.cr @@ -288,7 +288,7 @@ describe "StaticArray" do # StaticArray#sort_by and #sort_by! don't compile on aarch64-darwin and # aarch64-linux-musl due to a codegen error caused by LLVM < 13.0.0. # See https://github.com/crystal-lang/crystal/issues/11358 for details. - {% unless compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 && flag?(:aarch64) && (flag?(:musl) || flag?(:darwin)) %} + {% unless compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 && flag?(:aarch64) && (flag?(:musl) || flag?(:darwin) || flag?(:android)) %} describe "{{ sort }}_by" do it "sorts by" do a = StaticArray["foo", "a", "hello"] diff --git a/spec/std/system/group_spec.cr b/spec/std/system/group_spec.cr index dfe6b476fb9f..5c55611e4d28 100644 --- a/spec/std/system/group_spec.cr +++ b/spec/std/system/group_spec.cr @@ -3,8 +3,10 @@ require "spec" require "system/group" -GROUP_NAME = {{ `id -gn`.stringify.chomp }} -GROUP_ID = {{ `id -g`.stringify.chomp }} +GROUP_NAME = {{ `id -gn`.stringify.chomp }} +GROUP_ID = {{ `id -g`.stringify.chomp }} +INVALID_GROUP_NAME = "this_group_does_not_exist" +INVALID_GROUP_ID = {% if flag?(:android) %}"8888"{% else %}"1234567"{% end %} describe System::Group do describe ".find_by(*, name)" do @@ -18,7 +20,7 @@ describe System::Group do it "raises on nonexistent group" do expect_raises System::Group::NotFoundError, "No such group" do - System::Group.find_by(name: "this_group_does_not_exist") + System::Group.find_by(name: INVALID_GROUP_NAME) end end end @@ -34,7 +36,7 @@ describe System::Group do it "raises on nonexistent group name" do expect_raises System::Group::NotFoundError, "No such group" do - System::Group.find_by(id: "1234567") + System::Group.find_by(id: INVALID_GROUP_ID) end end end @@ -49,7 +51,7 @@ describe System::Group do end it "returns nil on nonexistent group" do - group = System::Group.find_by?(name: "this_group_does_not_exist") + group = System::Group.find_by?(name: INVALID_GROUP_NAME) group.should eq(nil) end end @@ -64,7 +66,7 @@ describe System::Group do end it "returns nil on nonexistent group id" do - group = System::Group.find_by?(id: "1234567") + group = System::Group.find_by?(id: INVALID_GROUP_ID) group.should eq(nil) end end diff --git a/spec/std/system/user_spec.cr b/spec/std/system/user_spec.cr index 2d0089e9c3fc..9fea934bc227 100644 --- a/spec/std/system/user_spec.cr +++ b/spec/std/system/user_spec.cr @@ -3,8 +3,10 @@ require "spec" require "system/user" -USER_NAME = {{ `id -un`.stringify.chomp }} -USER_ID = {{ `id -u`.stringify.chomp }} +USER_NAME = {{ `id -un`.stringify.chomp }} +USER_ID = {{ `id -u`.stringify.chomp }} +INVALID_USER_NAME = "this_user_does_not_exist" +INVALID_USER_ID = {% if flag?(:android) %}"8888"{% else %}"1234567"{% end %} describe System::User do describe ".find_by(*, name)" do @@ -18,7 +20,7 @@ describe System::User do it "raises on a nonexistent user" do expect_raises System::User::NotFoundError, "No such user" do - System::User.find_by(name: "this_user_does_not_exist") + System::User.find_by(name: INVALID_USER_NAME) end end end @@ -34,7 +36,7 @@ describe System::User do it "raises on nonexistent user id" do expect_raises System::User::NotFoundError, "No such user" do - System::User.find_by(id: "1234567") + System::User.find_by(id: INVALID_USER_ID) end end end @@ -49,7 +51,7 @@ describe System::User do end it "returns nil on nonexistent user" do - user = System::User.find_by?(name: "this_user_does_not_exist") + user = System::User.find_by?(name: INVALID_USER_NAME) user.should eq(nil) end end @@ -64,7 +66,7 @@ describe System::User do end it "returns nil on nonexistent user id" do - user = System::User.find_by?(id: "1234567") + user = System::User.find_by?(id: INVALID_USER_ID) user.should eq(nil) end end diff --git a/spec/std/va_list_spec.cr b/spec/std/va_list_spec.cr index 4a8e4e172e23..c861f99686da 100644 --- a/spec/std/va_list_spec.cr +++ b/spec/std/va_list_spec.cr @@ -1,4 +1,4 @@ -{% skip_file if flag?(:win32) %} +{% skip_file if flag?(:win32) || flag?(:aarch64) %} require "./spec_helper" diff --git a/src/compiler/crystal/codegen/target.cr b/src/compiler/crystal/codegen/target.cr index cb0c62d4f080..b4f8c2f9f4a3 100644 --- a/src/compiler/crystal/codegen/target.cr +++ b/src/compiler/crystal/codegen/target.cr @@ -70,6 +70,8 @@ class Crystal::Codegen::Target "openbsd" when .netbsd? "netbsd" + when .android? + "android" else environment end @@ -103,6 +105,10 @@ class Crystal::Codegen::Target @environment.starts_with?("netbsd") end + def android? + environment_parts.any? &.starts_with?("android") + end + def linux? @environment.starts_with?("linux") end diff --git a/src/compiler/crystal/loader/unix.cr b/src/compiler/crystal/loader/unix.cr index bb2cc440beaa..928c646ceb75 100644 --- a/src/compiler/crystal/loader/unix.cr +++ b/src/compiler/crystal/loader/unix.cr @@ -142,13 +142,18 @@ class Crystal::Loader default_search_paths.concat env_library_path.split(Process::PATH_DELIMITER, remove_empty: true) end - {% if flag?(:linux) || flag?(:bsd) %} + {% if (flag?(:linux) && !flag?(:android)) || flag?(:bsd) %} read_ld_conf(default_search_paths) {% end %} {% if flag?(:darwin) %} default_search_paths << "/usr/lib" default_search_paths << "/usr/local/lib" + {% elsif flag?(:android) %} + default_search_paths << "/vendor/lib64" if File.directory?("/vendor/lib64") + default_search_paths << "/system/lib64" if File.directory?("/system/lib64") + default_search_paths << "/vendor/lib" + default_search_paths << "/system/lib" {% else %} {% if flag?(:linux) %} default_search_paths << "/lib64" if File.directory?("/lib64") diff --git a/src/compiler/crystal/semantic/flags.cr b/src/compiler/crystal/semantic/flags.cr index d412dfbc011f..adfc51310233 100644 --- a/src/compiler/crystal/semantic/flags.cr +++ b/src/compiler/crystal/semantic/flags.cr @@ -47,6 +47,7 @@ class Crystal::Program flags.add "netbsd" if target.netbsd? flags.add "openbsd" if target.openbsd? flags.add "dragonfly" if target.dragonfly? + flags.add "android" if target.android? flags.add "bsd" if target.bsd? diff --git a/src/crystal/system/unix/lib_event2.cr b/src/crystal/system/unix/lib_event2.cr index ae407786b071..10d371896e0d 100644 --- a/src/crystal/system/unix/lib_event2.cr +++ b/src/crystal/system/unix/lib_event2.cr @@ -3,7 +3,7 @@ require "c/netdb" # On musl systems, librt is empty. The entire library is already included in libc. # On gnu systems, it's been integrated into `glibc` since 2.34 and it's not available # as a shared library. -{% if flag?(:linux) && flag?(:gnu) && !flag?(:interpreted) %} +{% if flag?(:linux) && flag?(:gnu) && !flag?(:interpreted) && !flag?(:android) %} @[Link("rt")] {% end %} diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index bedf2f456aa5..bde5cc7b4333 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -59,8 +59,8 @@ class Thread end end - {% if flag?(:android) || flag?(:openbsd) %} - # no thread local storage (TLS) for OpenBSD or Android, + {% if flag?(:openbsd) %} + # no thread local storage (TLS) for OpenBSD, # we use pthread's specific storage (TSS) instead: @@current_key : LibC::PthreadKeyT diff --git a/src/errno.cr b/src/errno.cr index cc05673b12f9..9c050aa603c4 100644 --- a/src/errno.cr +++ b/src/errno.cr @@ -2,14 +2,14 @@ require "c/errno" require "c/string" lib LibC - {% if flag?(:linux) || flag?(:dragonfly) %} + {% if flag?(:netbsd) || flag?(:openbsd) || flag?(:android) %} + fun __errno : Int* + {% elsif flag?(:linux) || flag?(:dragonfly) %} fun __errno_location : Int* {% elsif flag?(:wasi) %} $errno : Int {% elsif flag?(:darwin) || flag?(:freebsd) %} fun __error : Int* - {% elsif flag?(:netbsd) || flag?(:openbsd) %} - fun __error = __errno : Int* {% elsif flag?(:win32) %} fun _get_errno(value : Int*) : ErrnoT fun _set_errno(value : Int) : ErrnoT @@ -43,11 +43,13 @@ enum Errno # Returns the value of libc's errno. def self.value : self - {% if flag?(:linux) || flag?(:dragonfly) %} + {% if flag?(:netbsd) || flag?(:openbsd) || flag?(:android) %} + Errno.new LibC.__errno.value + {% elsif flag?(:linux) || flag?(:dragonfly) %} Errno.new LibC.__errno_location.value {% elsif flag?(:wasi) %} Errno.new LibC.errno - {% elsif flag?(:darwin) || flag?(:bsd) %} + {% elsif flag?(:darwin) || flag?(:freebsd) %} Errno.new LibC.__error.value {% elsif flag?(:win32) %} ret = LibC._get_errno(out errno) @@ -58,9 +60,11 @@ enum Errno # Sets the value of libc's errno. def self.value=(errno : Errno) - {% if flag?(:linux) || flag?(:dragonfly) %} + {% if flag?(:netbsd) || flag?(:openbsd) || flag?(:android) %} + LibC.__errno.value = errno.value + {% elsif flag?(:linux) || flag?(:dragonfly) %} LibC.__errno_location.value = errno.value - {% elsif flag?(:darwin) || flag?(:bsd) %} + {% elsif flag?(:darwin) || flag?(:freebsd) %} LibC.__error.value = errno.value {% elsif flag?(:win32) %} ret = LibC._set_errno(errno.value) diff --git a/src/exception/call_stack/elf.cr b/src/exception/call_stack/elf.cr index 9b75f545e670..1bb14f3a4332 100644 --- a/src/exception/call_stack/elf.cr +++ b/src/exception/call_stack/elf.cr @@ -4,25 +4,42 @@ require "crystal/elf" {% end %} struct Exception::CallStack + private struct DlPhdrData + getter program : String + property base_address : LibC::Elf_Addr = 0 + + def initialize(@program : String) + end + end + protected def self.load_debug_info_impl : Nil - base_address : LibC::Elf_Addr = 0 + program = Process.executable_path + return unless program && File.readable? program + data = DlPhdrData.new(program) + phdr_callback = LibC::DlPhdrCallback.new do |info, size, data| - # The first entry is the header for the current program. - # Note that we avoid allocating here and just store the base address - # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns. - # Calling self.read_dwarf_sections from this callback may lead to reallocations - # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084). - data.as(Pointer(LibC::Elf_Addr)).value = info.value.addr - 1 + # `dl_iterate_phdr` does not always visit the current program first; on + # Android the first object is `/system/bin/linker64`, the second is the + # full program path (not the empty string), so we check both here + name_c_str = info.value.name + if name_c_str && (name_c_str.value == 0 || LibC.strcmp(name_c_str, data.as(DlPhdrData*).value.program) == 0) + # The first entry is the header for the current program. + # Note that we avoid allocating here and just store the base address + # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns. + # Calling self.read_dwarf_sections from this callback may lead to reallocations + # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084). + data.as(DlPhdrData*).value.base_address = info.value.addr + 1 + else + 0 + end end - LibC.dl_iterate_phdr(phdr_callback, pointerof(base_address)) - self.read_dwarf_sections(base_address) + LibC.dl_iterate_phdr(phdr_callback, pointerof(data)) + self.read_dwarf_sections(data.program, data.base_address) end - protected def self.read_dwarf_sections(base_address = 0) - program = Process.executable_path - return unless program && File.readable? program + protected def self.read_dwarf_sections(program, base_address = 0) Crystal::ELF.open(program) do |elf| line_strings = elf.read_section?(".debug_line_str") do |sh, io| Crystal::DWARF::Strings.new(io, sh.offset, sh.size) diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 07bfe7325e33..35858ca1bf8b 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -15,7 +15,7 @@ # # OTHERS: On other systems, we add the linker annotation here to make sure libpthread is loaded # before libgc which looks up symbols from libpthread. -{% unless flag?(:win32) || flag?(:musl) || flag?(:darwin) || (flag?(:interpreted) && flag?(:gnu)) %} +{% unless flag?(:win32) || flag?(:musl) || flag?(:darwin) || flag?(:android) || (flag?(:interpreted) && flag?(:gnu)) %} @[Link("pthread")] {% end %} diff --git a/src/lib_c.cr b/src/lib_c.cr index 425a8eb6eec2..a4fe4e6d1ff5 100644 --- a/src/lib_c.cr +++ b/src/lib_c.cr @@ -25,5 +25,14 @@ lib LibC alias Float = Float32 alias Double = Float64 + {% if flag?(:android) %} + {% default_api_version = 31 %} + {% min_supported_version = 28 %} + {% api_version_var = env("ANDROID_PLATFORM") || env("ANDROID_NATIVE_API_LEVEL") %} + {% api_version = api_version_var ? api_version_var.gsub(/^android-/, "").to_i : default_api_version %} + {% raise "TODO: support Android API level below #{min_supported_version}" unless api_version >= min_supported_version %} + ANDROID_API = {{ api_version }} + {% end %} + $environ : Char** end diff --git a/src/lib_c/aarch64-android/c/arpa/inet.cr b/src/lib_c/aarch64-android/c/arpa/inet.cr new file mode 100644 index 000000000000..3719228c1a99 --- /dev/null +++ b/src/lib_c/aarch64-android/c/arpa/inet.cr @@ -0,0 +1,11 @@ +require "../netinet/in" +require "../stdint" + +lib LibC + {% if ANDROID_API >= 21 %} + fun htons(__x : UInt16) : UInt16 + fun ntohs(__x : UInt16) : UInt16 + {% end %} + fun inet_ntop(__af : Int, __src : Void*, __dst : Char*, __size : SocklenT) : Char* + fun inet_pton(__af : Int, __src : Char*, __dst : Void*) : Int +end diff --git a/src/lib_c/aarch64-android/c/dirent.cr b/src/lib_c/aarch64-android/c/dirent.cr new file mode 100644 index 000000000000..c0f33f36d69e --- /dev/null +++ b/src/lib_c/aarch64-android/c/dirent.cr @@ -0,0 +1,23 @@ +require "./sys/types" + +lib LibC + type DIR = Void + + DT_UNKNOWN = 0 + DT_DIR = 4 + DT_LNK = 10 + + struct Dirent + d_ino : InoT + d_off : Long + d_reclen : UShort + d_type : UChar + d_name : StaticArray(Char, 256) + end + + fun closedir(__dir : DIR*) : Int + fun opendir(__path : Char*) : DIR* + fun readdir(__dir : DIR*) : Dirent* + fun rewinddir(__dir : DIR*) + fun dirfd(__dir : DIR*) : Int +end diff --git a/src/lib_c/aarch64-android/c/dlfcn.cr b/src/lib_c/aarch64-android/c/dlfcn.cr new file mode 100644 index 000000000000..1accf5803bf3 --- /dev/null +++ b/src/lib_c/aarch64-android/c/dlfcn.cr @@ -0,0 +1,21 @@ +@[Link(ldflags: "/system/lib64/libdl.so")] +lib LibC + RTLD_LAZY = 0x00001 + RTLD_NOW = 0x00002 + RTLD_GLOBAL = 0x00100 + RTLD_LOCAL = 0 + RTLD_DEFAULT = Pointer(Void).new(0) + + struct DlInfo + dli_fname : Char* + dli_fbase : Void* + dli_sname : Char* + dli_saddr : Void* + end + + fun dlclose(__handle : Void*) : Int + fun dlerror : Char* + fun dlopen(__filename : Char*, __flag : Int) : Void* + fun dlsym(__handle : Void*, __symbol : Char*) : Void* + fun dladdr(__addr : Void*, __info : DlInfo*) : Int +end diff --git a/src/lib_c/aarch64-android/c/elf.cr b/src/lib_c/aarch64-android/c/elf.cr new file mode 100644 index 000000000000..edf953c48e9a --- /dev/null +++ b/src/lib_c/aarch64-android/c/elf.cr @@ -0,0 +1,24 @@ +require "./sys/types" + +lib LibC + alias Elf_Half = UInt16 + alias Elf_Word = UInt32 + alias Elf_Sword = Int32 + alias Elf_Xword = UInt64 + alias Elf_Sxword = Int64 + alias Elf_Addr = UInt64 + alias Elf_Off = UInt64 + alias Elf_Section = UInt16 + alias Elf_Versym = Elf_Half + + struct Elf_Phdr + type : Elf_Word # Segment type + flags : Elf_Word # Segment flags + offset : Elf_Off # Segment file offset + vaddr : Elf_Addr # Segment virtual address + paddr : Elf_Addr # Segment physical address + filesz : Elf_Xword # Segment size in file + memsz : Elf_Xword # Segment size in memory + align : Elf_Xword # Segment alignment + end +end diff --git a/src/lib_c/aarch64-android/c/errno.cr b/src/lib_c/aarch64-android/c/errno.cr new file mode 100644 index 000000000000..245cb73c45cc --- /dev/null +++ b/src/lib_c/aarch64-android/c/errno.cr @@ -0,0 +1,136 @@ +lib LibC + E2BIG = 7 + EACCES = 13 + EADDRINUSE = 98 + EADDRNOTAVAIL = 99 + EADV = 68 + EAFNOSUPPORT = 97 + EAGAIN = 11 + EALREADY = 114 + EBADE = 52 + EBADF = 9 + EBADFD = 77 + EBADMSG = 74 + EBADR = 53 + EBADRQC = 56 + EBADSLT = 57 + EBFONT = 59 + EBUSY = 16 + ECANCELED = 125 + ECHILD = 10 + ECHRNG = 44 + ECOMM = 70 + ECONNABORTED = 103 + ECONNREFUSED = 111 + ECONNRESET = 104 + EDEADLK = 35 + EDEADLOCK = LibC::EDEADLK + EDESTADDRREQ = 89 + EDOM = 33 + EDOTDOT = 73 + EDQUOT = 122 + EEXIST = 17 + EFAULT = 14 + EFBIG = 27 + EHOSTDOWN = 112 + EHOSTUNREACH = 113 + EHWPOISON = 133 + EIDRM = 43 + EILSEQ = 84 + EINPROGRESS = 115 + EINTR = 4 + EINVAL = 22 + EIO = 5 + EISCONN = 106 + EISDIR = 21 + EISNAM = 120 + EKEYEXPIRED = 127 + EKEYREJECTED = 129 + EKEYREVOKED = 128 + EL2HLT = 51 + EL2NSYNC = 45 + EL3HLT = 46 + EL3RST = 47 + ELIBACC = 79 + ELIBBAD = 80 + ELIBEXEC = 83 + ELIBMAX = 82 + ELIBSCN = 81 + ELNRNG = 48 + ELOOP = 40 + EMEDIUMTYPE = 124 + EMFILE = 24 + EMLINK = 31 + EMSGSIZE = 90 + EMULTIHOP = 72 + ENAMETOOLONG = 36 + ENAVAIL = 119 + ENETDOWN = 100 + ENETRESET = 102 + ENETUNREACH = 101 + ENFILE = 23 + ENOANO = 55 + ENOBUFS = 105 + ENOCSI = 50 + ENODATA = 61 + ENODEV = 19 + ENOENT = 2 + ENOEXEC = 8 + ENOKEY = 126 + ENOLCK = 37 + ENOLINK = 67 + ENOMEDIUM = 123 + ENOMEM = 12 + ENOMSG = 42 + ENONET = 64 + ENOPKG = 65 + ENOPROTOOPT = 92 + ENOSPC = 28 + ENOSR = 63 + ENOSTR = 60 + ENOSYS = 38 + ENOTBLK = 15 + ENOTCONN = 107 + ENOTDIR = 20 + ENOTEMPTY = 39 + ENOTNAM = 118 + ENOTRECOVERABLE = 131 + ENOTSOCK = 88 + ENOTSUP = LibC::EOPNOTSUPP + ENOTTY = 25 + ENOTUNIQ = 76 + ENXIO = 6 + EOPNOTSUPP = 95 + EOVERFLOW = 75 + EOWNERDEAD = 130 + EPERM = 1 + EPFNOSUPPORT = 96 + EPIPE = 32 + EPROTO = 71 + EPROTONOSUPPORT = 93 + EPROTOTYPE = 91 + ERANGE = 34 + EREMCHG = 78 + EREMOTE = 66 + EREMOTEIO = 121 + ERESTART = 85 + ERFKILL = 132 + EROFS = 30 + ESHUTDOWN = 108 + ESOCKTNOSUPPORT = 94 + ESPIPE = 29 + ESRCH = 3 + ESRMNT = 69 + ESTALE = 116 + ESTRPIPE = 86 + ETIME = 62 + ETIMEDOUT = 110 + ETOOMANYREFS = 109 + ETXTBSY = 26 + EUCLEAN = 117 + EUNATCH = 49 + EUSERS = 87 + EWOULDBLOCK = LibC::EAGAIN + EXDEV = 18 + EXFULL = 54 +end diff --git a/src/lib_c/aarch64-android/c/fcntl.cr b/src/lib_c/aarch64-android/c/fcntl.cr new file mode 100644 index 000000000000..bf9b5ac46f13 --- /dev/null +++ b/src/lib_c/aarch64-android/c/fcntl.cr @@ -0,0 +1,25 @@ +require "./sys/types" +require "./sys/stat" +require "./unistd" + +lib LibC + F_GETFD = 1 + F_SETFD = 2 + F_GETFL = 3 + F_SETFL = 4 + FD_CLOEXEC = 1 + O_CLOEXEC = 0o2000000 + O_CREAT = 0o100 + O_EXCL = 0o0200 + O_NOFOLLOW = 0o100000 + O_TRUNC = 0o1000 + O_APPEND = 0o2000 + O_NONBLOCK = 0o4000 + O_SYNC = 0o4010000 + O_RDONLY = 0o0 + O_RDWR = 0o2 + O_WRONLY = 0o1 + + fun fcntl(__fd : Int, __cmd : Int, ...) : Int + fun open(__path : Char*, __flags : Int, ...) : Int +end diff --git a/src/lib_c/aarch64-android/c/grp.cr b/src/lib_c/aarch64-android/c/grp.cr new file mode 100644 index 000000000000..5193f58cb7b4 --- /dev/null +++ b/src/lib_c/aarch64-android/c/grp.cr @@ -0,0 +1,13 @@ +lib LibC + struct Group + gr_name : Char* + gr_passwd : Char* + gr_gid : GidT + gr_mem : Char** + end + + {% if ANDROID_API >= 24 %} + fun getgrnam_r(__name : Char*, __group : Group*, __buf : Char*, __n : SizeT, __result : Group**) : Int + fun getgrgid_r(__gid : GidT, __group : Group*, __buf : Char*, __n : SizeT, __result : Group**) : Int + {% end %} +end diff --git a/src/lib_c/aarch64-android/c/iconv.cr b/src/lib_c/aarch64-android/c/iconv.cr new file mode 100644 index 000000000000..6a9a20a6eb7a --- /dev/null +++ b/src/lib_c/aarch64-android/c/iconv.cr @@ -0,0 +1,11 @@ +require "./stddef" + +lib LibC + type IconvT = Void* + + {% if ANDROID_API >= 28 %} + fun iconv(__converter : IconvT, __src_buf : Char**, __src_bytes_left : SizeT*, __dst_buf : Char**, __dst_bytes_left : SizeT*) : SizeT + fun iconv_close(__converter : IconvT) : Int + fun iconv_open(__src_encoding : Char*, __dst_encoding : Char*) : IconvT + {% end %} +end diff --git a/src/lib_c/aarch64-android/c/link.cr b/src/lib_c/aarch64-android/c/link.cr new file mode 100644 index 000000000000..6b8d5eef3055 --- /dev/null +++ b/src/lib_c/aarch64-android/c/link.cr @@ -0,0 +1,19 @@ +require "./elf" + +lib LibC + struct DlPhdrInfo + addr : Elf_Addr + name : Char* + phdr : Elf_Phdr* + phnum : Elf_Half + + # These fields were added in Android R. + adds : ULongLong + subs : ULongLong + tls_modid : SizeT + tls_data : Void* + end + + alias DlPhdrCallback = (DlPhdrInfo*, LibC::SizeT, Void*) -> LibC::Int + fun dl_iterate_phdr(__callback : DlPhdrCallback, __data : Void*) : Int +end diff --git a/src/lib_c/aarch64-android/c/netdb.cr b/src/lib_c/aarch64-android/c/netdb.cr new file mode 100644 index 000000000000..49430ee2c08f --- /dev/null +++ b/src/lib_c/aarch64-android/c/netdb.cr @@ -0,0 +1,40 @@ +require "./netinet/in" +require "./sys/socket" +require "./stdint" + +lib LibC + AI_PASSIVE = 0x0001 + AI_CANONNAME = 0x0002 + AI_NUMERICHOST = 0x0004 + AI_NUMERICSERV = 0x0008 + AI_V4MAPPED = 0x0800 + AI_ALL = 0x0100 + AI_ADDRCONFIG = 0x0400 + EAI_AGAIN = 2 + EAI_BADFLAGS = 3 + EAI_FAIL = 4 + EAI_FAMILY = 5 + EAI_MEMORY = 6 + EAI_NODATA = 7 + EAI_NONAME = 8 + EAI_SERVICE = 9 + EAI_SOCKTYPE = 10 + EAI_SYSTEM = 11 + EAI_OVERFLOW = 14 + + struct Addrinfo + ai_flags : Int + ai_family : Int + ai_socktype : Int + ai_protocol : Int + ai_addrlen : SocklenT + ai_canonname : Char* + ai_addr : Sockaddr* + ai_next : Addrinfo* + end + + fun freeaddrinfo(__ptr : Addrinfo*) + fun gai_strerror(__error : Int) : Char* + fun getaddrinfo(__node : Char*, __service : Char*, __hints : Addrinfo*, __result : Addrinfo**) : Int + fun getnameinfo(__sa : Sockaddr*, __sa_length : SocklenT, __host : Char*, __host_length : SizeT, __service : Char*, __service_length : SizeT, __flags : Int) : Int +end diff --git a/src/lib_c/aarch64-android/c/netinet/in.cr b/src/lib_c/aarch64-android/c/netinet/in.cr new file mode 100644 index 000000000000..716d71fb029f --- /dev/null +++ b/src/lib_c/aarch64-android/c/netinet/in.cr @@ -0,0 +1,69 @@ +require "../sys/socket" +require "../stdint" + +lib LibC + IPPROTO_IP = 0 + IPPROTO_IPV6 = 41 + IPPROTO_ICMP = 1 + IPPROTO_RAW = 255 + IPPROTO_TCP = 6 + IPPROTO_UDP = 17 + + alias BE16 = UInt16 + alias BE32 = UInt32 + alias InAddrT = UInt32 + + struct InAddr + s_addr : InAddrT + end + + union In6AddrIn6U + __u6_addr8 : UInt8[16] + __u6_addr16 : BE16[8] + __u6_addr32 : BE32[4] + end + + struct In6Addr + __in6_u : In6AddrIn6U + end + + struct SockaddrIn + sin_family : UShort + sin_port : BE16 + sin_addr : InAddr + sin_zero : UChar[8] # __SOCK_SIZE__ (16) - ... + end + + struct SockaddrIn6 + sin6_family : UShort + sin6_port : BE16 + sin6_flowinfo : BE32 + sin6_addr : In6Addr + sin6_scope_id : UInt32 + end + + IP_MULTICAST_IF = 32 + IPV6_MULTICAST_IF = 17 + + IP_MULTICAST_TTL = 33 + IPV6_MULTICAST_HOPS = 18 + + IP_MULTICAST_LOOP = 34 + IPV6_MULTICAST_LOOP = 19 + + IP_ADD_MEMBERSHIP = 35 + IPV6_JOIN_GROUP = 20 + + IP_DROP_MEMBERSHIP = 36 + IPV6_LEAVE_GROUP = 21 + + struct IpMreq + imr_multiaddr : InAddr + imr_interface : InAddr + end + + struct Ipv6Mreq + ipv6mr_multiaddr : In6Addr + ipv6mr_ifindex : Int + end +end diff --git a/src/lib_c/aarch64-android/c/netinet/tcp.cr b/src/lib_c/aarch64-android/c/netinet/tcp.cr new file mode 100644 index 000000000000..beed1db6e081 --- /dev/null +++ b/src/lib_c/aarch64-android/c/netinet/tcp.cr @@ -0,0 +1,6 @@ +lib LibC + TCP_NODELAY = 1 + TCP_KEEPIDLE = 4 + TCP_KEEPINTVL = 5 + TCP_KEEPCNT = 6 +end diff --git a/src/lib_c/aarch64-android/c/pthread.cr b/src/lib_c/aarch64-android/c/pthread.cr new file mode 100644 index 000000000000..2203ea4a5170 --- /dev/null +++ b/src/lib_c/aarch64-android/c/pthread.cr @@ -0,0 +1,39 @@ +require "./sys/types" + +lib LibC + PTHREAD_MUTEX_ERRORCHECK = 2 + + fun pthread_attr_destroy(__attr : PthreadAttrT*) : Int + fun pthread_attr_getstack(__attr : PthreadAttrT*, __addr : Void**, __size : SizeT*) : Int + + fun pthread_condattr_destroy(__attr : PthreadCondattrT*) : Int + fun pthread_condattr_init(__attr : PthreadCondattrT*) : Int + {% if ANDROID_API >= 21 %} + fun pthread_condattr_setclock(__attr : PthreadCondattrT*, __clock : ClockidT) : Int + {% end %} + + fun pthread_cond_broadcast(__cond : PthreadCondT*) : Int + fun pthread_cond_destroy(__cond : PthreadCondT*) : Int + fun pthread_cond_init(__cond : PthreadCondT*, __attr : PthreadCondattrT*) : Int + fun pthread_cond_signal(__cond : PthreadCondT*) : Int + fun pthread_cond_timedwait(__cond : PthreadCondT*, __mutex : PthreadMutexT*, __timeout : Timespec*) : Int + fun pthread_cond_wait(__cond : PthreadCondT*, __mutex : PthreadMutexT*) : Int + + fun pthread_create(__pthread_ptr : PthreadT*, __attr : PthreadAttrT*, __start_routine : Void* -> Void*, Void*) : Int + fun pthread_detach(__pthread : PthreadT) : Int + fun pthread_getattr_np(__pthread : PthreadT, __attr : PthreadAttrT*) : Int + fun pthread_equal(__lhs : PthreadT, __rhs : PthreadT) : Int + fun pthread_join(__pthread : PthreadT, __return_value_ptr : Void**) : Int + + fun pthread_mutexattr_destroy(__attr : PthreadMutexattrT*) : Int + fun pthread_mutexattr_init(__attr : PthreadMutexattrT*) : Int + fun pthread_mutexattr_settype(__attr : PthreadMutexattrT*, __type : Int) : Int + + fun pthread_mutex_destroy(__mutex : PthreadMutexT*) : Int + fun pthread_mutex_init(__mutex : PthreadMutexT*, __attr : PthreadMutexattrT*) : Int + fun pthread_mutex_lock(__mutex : PthreadMutexT*) : Int + fun pthread_mutex_trylock(__mutex : PthreadMutexT*) : Int + fun pthread_mutex_unlock(__mutex : PthreadMutexT*) : Int + + fun pthread_self : PthreadT +end diff --git a/src/lib_c/aarch64-android/c/pwd.cr b/src/lib_c/aarch64-android/c/pwd.cr new file mode 100644 index 000000000000..9eef3763fe10 --- /dev/null +++ b/src/lib_c/aarch64-android/c/pwd.cr @@ -0,0 +1,14 @@ +lib LibC + struct Passwd + pw_name : Char* + pw_passwd : Char* + pw_uid : UidT + pw_gid : GidT + pw_gecos : Char* + pw_dir : Char* + pw_shell : Char* + end + + fun getpwnam_r(__name : Char*, __pwd : Passwd*, __buf : Char*, __n : SizeT, __result : Passwd**) : Int + fun getpwuid_r(__uid : UidT, __pwd : Passwd*, __buf : Char*, __n : SizeT, __result : Passwd**) : Int +end diff --git a/src/lib_c/aarch64-android/c/sched.cr b/src/lib_c/aarch64-android/c/sched.cr new file mode 100644 index 000000000000..9d83ed18504e --- /dev/null +++ b/src/lib_c/aarch64-android/c/sched.cr @@ -0,0 +1,3 @@ +lib LibC + fun sched_yield : Int +end diff --git a/src/lib_c/aarch64-android/c/signal.cr b/src/lib_c/aarch64-android/c/signal.cr new file mode 100644 index 000000000000..e8e3ffb212e9 --- /dev/null +++ b/src/lib_c/aarch64-android/c/signal.cr @@ -0,0 +1,92 @@ +require "./sys/types" +require "./time" + +lib LibC + SIGHUP = 1 + SIGINT = 2 + SIGQUIT = 3 + SIGILL = 4 + SIGTRAP = 5 + SIGABRT = 6 + SIGIOT = 6 + SIGBUS = 7 + SIGFPE = 8 + SIGKILL = 9 + SIGUSR1 = 10 + SIGSEGV = 11 + SIGUSR2 = 12 + SIGPIPE = 13 + SIGALRM = 14 + SIGTERM = 15 + SIGSTKFLT = 16 + SIGCHLD = 17 + SIGCONT = 18 + SIGSTOP = 19 + SIGTSTP = 20 + SIGTTIN = 21 + SIGTTOU = 22 + SIGURG = 23 + SIGXCPU = 24 + SIGXFSZ = 25 + SIGVTALRM = 26 + SIGPROF = 27 + SIGWINCH = 28 + SIGIO = 29 + SIGPOLL = LibC::SIGIO + SIGPWR = 30 + SIGSYS = 31 + SIGUNUSED = 31 + + SIGSTKSZ = 16384 + + SIG_SETMASK = 2 + + alias SighandlerT = Int -> + SIG_DFL = SighandlerT.new(Pointer(Void).new(0_u64), Pointer(Void).null) + SIG_IGN = SighandlerT.new(Pointer(Void).new(1_u64), Pointer(Void).null) + + struct SigsetT + val : ULong[1] # (_KERNEL__NSIG / _NSIG_BPW) + end + + SA_ONSTACK = 0x08000000 + SA_SIGINFO = 0x00000004 + + struct SiginfoT + si_signo : Int + si_errno : Int + si_code : Int + __pad0 : Int + si_addr : Void* # Assuming the sigfault form of siginfo_t + __pad1 : Int[26] # SI_MAX_SIZE (128) / sizeof(int) - ... + end + + alias SigactionHandlerT = (Int, SiginfoT*, Void*) -> + + struct Sigaction + sa_flags : Int + sa_sigaction : SigactionHandlerT + sa_mask : SigsetT + sa_restorer : -> + end + + struct StackT + ss_sp : Void* + ss_flags : Int + ss_size : ULong + end + + fun kill(__pid : PidT, __signal : Int) : Int + fun pthread_sigmask(__how : Int, __new_set : SigsetT*, __old_set : SigsetT*) : Int + fun sigaction(__signal : Int, __new_action : Sigaction*, __old_action : Sigaction*) : Int + fun sigaltstack(__new_signal_stack : StackT*, __old_signal_stack : StackT*) : Int + {% if ANDROID_API >= 21 %} + # TODO: defined inline for `ANDROID_API < 21` + fun signal(__signal : Int, __handler : SighandlerT) : SighandlerT + fun sigemptyset(__set : SigsetT*) : Int + fun sigfillset(__set : SigsetT*) : Int + fun sigaddset(__set : SigsetT*, __signal : Int) : Int + fun sigdelset(__set : SigsetT*, __signal : Int) : Int + fun sigismember(__set : SigsetT*, __signal : Int) : Int + {% end %} +end diff --git a/src/lib_c/aarch64-android/c/stdarg.cr b/src/lib_c/aarch64-android/c/stdarg.cr new file mode 100644 index 000000000000..7192cdf0e121 --- /dev/null +++ b/src/lib_c/aarch64-android/c/stdarg.cr @@ -0,0 +1,3 @@ +lib LibC + alias VaList = Char* +end diff --git a/src/lib_c/aarch64-android/c/stddef.cr b/src/lib_c/aarch64-android/c/stddef.cr new file mode 100644 index 000000000000..4afcdf34d723 --- /dev/null +++ b/src/lib_c/aarch64-android/c/stddef.cr @@ -0,0 +1,3 @@ +lib LibC + alias SizeT = ULong +end diff --git a/src/lib_c/aarch64-android/c/stdint.cr b/src/lib_c/aarch64-android/c/stdint.cr new file mode 100644 index 000000000000..f1aae04d919e --- /dev/null +++ b/src/lib_c/aarch64-android/c/stdint.cr @@ -0,0 +1,10 @@ +lib LibC + alias Int8T = SChar + alias Int16T = Short + alias Int32T = Int + alias Int64T = Long + alias UInt8T = Char + alias UInt16T = UShort + alias UInt32T = UInt + alias UInt64T = ULong +end diff --git a/src/lib_c/aarch64-android/c/stdio.cr b/src/lib_c/aarch64-android/c/stdio.cr new file mode 100644 index 000000000000..4092871c309c --- /dev/null +++ b/src/lib_c/aarch64-android/c/stdio.cr @@ -0,0 +1,13 @@ +require "./sys/types" +require "./stddef" + +lib LibC + fun printf(__fmt : Char*, ...) : Int + + {% if ANDROID_API >= 21 %} + fun dprintf(__fd : Int, __fmt : Char*, ...) : Int + {% end %} + + fun rename(__old_path : Char*, __new_path : Char*) : Int + fun snprintf(__buf : Char*, __size : SizeT, __fmt : Char*, ...) : Int +end diff --git a/src/lib_c/aarch64-android/c/stdlib.cr b/src/lib_c/aarch64-android/c/stdlib.cr new file mode 100644 index 000000000000..6c8c85a9119f --- /dev/null +++ b/src/lib_c/aarch64-android/c/stdlib.cr @@ -0,0 +1,21 @@ +require "./stddef" +require "./sys/wait" + +lib LibC + fun exit(__status : Int) : NoReturn + fun free(__ptr : Void*) + fun getenv(__name : Char*) : Char* + fun malloc(__byte_count : SizeT) : Void* + fun mkstemp(__template : Char*) : Int + fun mkstemps(__template : Char*, __flags : Int) : Int + fun putenv(__assignment : Char*) : Int + fun realloc(__ptr : Void*, __byte_count : SizeT) : Void* + fun realpath(__path : Char*, __resolved : Char*) : Char* + fun setenv(__name : Char*, __value : Char*, __overwrite : Int) : Int + fun strtod(__s : Char*, __end_ptr : Char**) : Double + {% if ANDROID_API >= 21 %} + # TODO: defined inline for `ANDROID_API < 21` + fun strtof(__s : Char*, __end_ptr : Char**) : Float + {% end %} + fun unsetenv(__name : Char*) : Int +end diff --git a/src/lib_c/aarch64-android/c/string.cr b/src/lib_c/aarch64-android/c/string.cr new file mode 100644 index 000000000000..5133435e13dc --- /dev/null +++ b/src/lib_c/aarch64-android/c/string.cr @@ -0,0 +1,9 @@ +require "./stddef" + +lib LibC + fun memchr(__s : Void*, __ch : Int, __n : SizeT) : Void* + fun memcmp(__lhs : Void*, __rhs : Void*, __n : SizeT) : Int + fun strcmp(__lhs : Char*, __rhs : Char*) : Int + fun strerror(__errno_value : Int) : Char* + fun strlen(__s : Char*) : SizeT +end diff --git a/src/lib_c/aarch64-android/c/sys/file.cr b/src/lib_c/aarch64-android/c/sys/file.cr new file mode 100644 index 000000000000..8cb28d7da1b5 --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/file.cr @@ -0,0 +1,11 @@ +lib LibC + @[Flags] + enum FlockOp + SH = 0x1 + EX = 0x2 + NB = 0x4 + UN = 0x8 + end + + fun flock(__fd : Int, __op : FlockOp) : Int +end diff --git a/src/lib_c/aarch64-android/c/sys/mman.cr b/src/lib_c/aarch64-android/c/sys/mman.cr new file mode 100644 index 000000000000..b38ec92b9f0e --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/mman.cr @@ -0,0 +1,31 @@ +require "./types" + +lib LibC + PROT_EXEC = 0x4 + PROT_NONE = 0x0 + PROT_READ = 0x1 + PROT_WRITE = 0x2 + MAP_FIXED = 0x10 + MAP_PRIVATE = 0x02 + MAP_SHARED = 0x01 + MAP_ANON = LibC::MAP_ANONYMOUS + MAP_ANONYMOUS = 0x20 + MAP_FAILED = Pointer(Void).new(-1) + POSIX_MADV_DONTNEED = 4 + POSIX_MADV_NORMAL = 0 + POSIX_MADV_RANDOM = 1 + POSIX_MADV_SEQUENTIAL = 2 + POSIX_MADV_WILLNEED = 3 + MADV_DONTNEED = 4 + MADV_NORMAL = 0 + MADV_RANDOM = 1 + MADV_SEQUENTIAL = 2 + MADV_WILLNEED = 3 + MADV_HUGEPAGE = 14 + MADV_NOHUGEPAGE = 15 + + fun mmap(__addr : Void*, __size : SizeT, __prot : Int, __flags : Int, __fd : Int, __offset : OffT) : Void* + fun mprotect(__addr : Void*, __size : SizeT, __prot : Int) : Int + fun munmap(__addr : Void*, __size : SizeT) : Int + fun madvise(__addr : Void*, __size : SizeT, __advice : Int) : Int +end diff --git a/src/lib_c/aarch64-android/c/sys/resource.cr b/src/lib_c/aarch64-android/c/sys/resource.cr new file mode 100644 index 000000000000..c6bfe1cf2e7b --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/resource.cr @@ -0,0 +1,25 @@ +lib LibC + struct RUsage + ru_utime : Timeval + ru_stime : Timeval + ru_maxrss : Long + ru_ixrss : Long + ru_idrss : Long + ru_isrss : Long + ru_minflt : Long + ru_majflt : Long + ru_nswap : Long + ru_inblock : Long + ru_oublock : Long + ru_msgsnd : Long + ru_msgrcv : Long + ru_nsignals : Long + ru_nvcsw : Long + ru_nivcsw : Long + end + + RUSAGE_SELF = 0 + RUSAGE_CHILDREN = -1 + + fun getrusage(__who : Int, __usage : RUsage*) : Int +end diff --git a/src/lib_c/aarch64-android/c/sys/select.cr b/src/lib_c/aarch64-android/c/sys/select.cr new file mode 100644 index 000000000000..b9539050405f --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/select.cr @@ -0,0 +1,14 @@ +require "./types" +require "./time" +require "../time" +require "../signal" + +lib LibC + alias FdMask = Long + + struct FdSet + fds_bits : FdMask[16] # FD_SETSIZE (1024) / NFDBITS (8 * sizeof(FdMask)) + end + + fun select(__max_fd_plus_one : Int, __read_fds : FdSet*, __write_fds : FdSet*, __exception_fds : FdSet*, __timeout : Timeval*) : Int +end diff --git a/src/lib_c/aarch64-android/c/sys/socket.cr b/src/lib_c/aarch64-android/c/sys/socket.cr new file mode 100644 index 000000000000..241be043248b --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/socket.cr @@ -0,0 +1,65 @@ +require "./types" + +lib LibC + SOCK_DGRAM = 2 + SOCK_RAW = 3 + SOCK_SEQPACKET = 5 + SOCK_STREAM = 1 + SOL_SOCKET = 1 + SO_BROADCAST = 6 + SO_KEEPALIVE = 9 + SO_LINGER = 13 + SO_RCVBUF = 8 + SO_REUSEADDR = 2 + SO_REUSEPORT = 15 + SO_SNDBUF = 7 + PF_INET = LibC::AF_INET + PF_INET6 = LibC::AF_INET6 + PF_UNIX = LibC::AF_UNIX + PF_UNSPEC = LibC::AF_UNSPEC + PF_LOCAL = LibC::AF_LOCAL + AF_INET = 2 + AF_INET6 = 10 + AF_UNIX = 1 + AF_UNSPEC = 0 + AF_LOCAL = 1 + SHUT_RD = 0 + SHUT_RDWR = 2 + SHUT_WR = 1 + SOCK_CLOEXEC = 0o2000000 + + alias SocklenT = UInt32 + alias SaFamilyT = UShort + + struct Sockaddr + sa_family : SaFamilyT + sa_data : Char[14] + end + + struct SockaddrStorage + ss_family : SaFamilyT + __align : Void* + __data : Char[112] + end + + struct Linger + l_onoff : Int + l_linger : Int + end + + fun accept(__fd : Int, __addr : Sockaddr*, __addr_length : SocklenT*) : Int + fun bind(__fd : Int, __addr : Sockaddr*, __addr_length : SocklenT) : Int + fun connect(__fd : Int, __addr : Sockaddr*, __addr_length : SocklenT) : Int + fun getpeername(__fd : Int, __addr : Sockaddr*, __addr_length : SocklenT*) : Int + fun getsockname(__fd : Int, __addr : Sockaddr*, __addr_length : SocklenT*) : Int + fun getsockopt(__fd : Int, __level : Int, __option : Int, __value : Void*, __value_length : SocklenT*) : Int + fun listen(__fd : Int, __backlog : Int) : Int + fun recv(__fd : Int, __buf : Void*, __n : SizeT, __flags : Int) : SSizeT + fun recvfrom(__fd : Int, __buf : Void*, __n : SizeT, __flags : Int, __src_addr : Sockaddr*, __src_addr_length : SocklenT*) : SSizeT + fun send(__fd : Int, __buf : Void*, __n : SizeT, __flags : Int) : SSizeT + fun sendto(__fd : Int, __buf : Void*, __n : SizeT, __flags : Int, __dst_addr : Sockaddr*, __dst_addr_length : SocklenT) : SSizeT + fun setsockopt(__fd : Int, __level : Int, __option : Int, __value : Void*, __value_length : SocklenT) : Int + fun shutdown(__fd : Int, __how : Int) : Int + fun socket(__af : Int, __type : Int, __protocol : Int) : Int + fun socketpair(__af : Int, __type : Int, __protocol : Int, __fds : Int[2]) : Int +end diff --git a/src/lib_c/aarch64-android/c/sys/stat.cr b/src/lib_c/aarch64-android/c/sys/stat.cr new file mode 100644 index 000000000000..9216942441f3 --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/stat.cr @@ -0,0 +1,56 @@ +require "./types" +require "../time" + +lib LibC + S_IFMT = 0o170000 + S_IFBLK = 0o060000 + S_IFCHR = 0o020000 + S_IFIFO = 0o010000 + S_IFREG = 0o100000 + S_IFDIR = 0o040000 + S_IFLNK = 0o120000 + S_IFSOCK = 0o140000 + S_IRUSR = 0o400 + S_IWUSR = 0o200 + S_IXUSR = 0o100 + S_IRWXU = 0o400 | 0o200 | 0o100 + S_IRGRP = S_IRUSR >> 3 + S_IWGRP = S_IWUSR >> 3 + S_IXGRP = S_IXUSR >> 3 + S_IRWXG = S_IRWXU >> 3 + S_IROTH = S_IRGRP >> 3 + S_IWOTH = S_IWGRP >> 3 + S_IXOTH = S_IXGRP >> 3 + S_IRWXO = S_IRWXG >> 3 + S_ISUID = 0o4000 + S_ISGID = 0o2000 + S_ISVTX = 0o1000 + + struct Stat + st_dev : DevT + st_ino : InoT + st_mode : ModeT + st_nlink : NlinkT + st_uid : UidT + st_gid : GidT + st_rdev : DevT + __pad1 : ULong + st_size : OffT + st_blksize : Int + __pad2 : Int + st_blocks : Long + st_atim : Timespec + st_mtim : Timespec + st_ctim : Timespec + __unused4 : UInt + __unused5 : UInt + end + + fun chmod(__path : Char*, __mode : ModeT) : Int + fun fchmod(__fd : Int, __mode : ModeT) : Int + fun fstat(__fd : Int, __buf : Stat*) : Int + fun lstat(__path : Char*, __buf : Stat*) : Int + fun mkdir(__path : Char*, __mode : ModeT) : Int + fun stat(__path : Char*, __buf : Stat*) : Int + fun umask(__mask : ModeT) : ModeT +end diff --git a/src/lib_c/aarch64-android/c/sys/syscall.cr b/src/lib_c/aarch64-android/c/sys/syscall.cr new file mode 100644 index 000000000000..9924a675bade --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/syscall.cr @@ -0,0 +1,3 @@ +lib LibC + SYS_getrandom = 278 +end diff --git a/src/lib_c/aarch64-android/c/sys/time.cr b/src/lib_c/aarch64-android/c/sys/time.cr new file mode 100644 index 000000000000..1138c33c880f --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/time.cr @@ -0,0 +1,19 @@ +require "./types" + +lib LibC + struct Timeval + tv_sec : TimeT + tv_usec : SusecondsT + end + + struct Timezone + tz_minuteswest : Int + tz_dsttime : Int + end + + fun gettimeofday(__tv : Timeval*, __tz : Timezone*) : Int + fun utimes(__path : Char*, __times : Timeval[2]) : Int + {% if ANDROID_API >= 19 %} + fun futimens(__dir_fd : Int, __times : Timespec[2]) : Int + {% end %} +end diff --git a/src/lib_c/aarch64-android/c/sys/types.cr b/src/lib_c/aarch64-android/c/sys/types.cr new file mode 100644 index 000000000000..4611e72fa79a --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/types.cr @@ -0,0 +1,44 @@ +require "../stddef" +require "../stdint" + +lib LibC + alias BlkcntT = ULong + alias BlksizeT = ULong + alias ClockT = Long + alias ClockidT = Int + alias DevT = UInt64 + alias GidT = UInt + alias IdT = UInt32 + alias InoT = ULong + alias ModeT = UInt + alias NlinkT = UInt32 + alias OffT = Int64 + alias PidT = Int + + struct PthreadAttrT + flags : UInt32 + stack_base : Void* + stack_size : SizeT + guard_size : SizeT + sched_policy : Int32 + sched_priority : Int32 + __reserved : Char[16] + end + + struct PthreadCondT + __private : Int32[12] + end + + alias PthreadCondattrT = Long + + struct PthreadMutexT + __private : Int32[10] + end + + alias PthreadMutexattrT = Long + alias PthreadT = Long + alias SSizeT = Long + alias SusecondsT = Long + alias TimeT = Long + alias UidT = UInt +end diff --git a/src/lib_c/aarch64-android/c/sys/un.cr b/src/lib_c/aarch64-android/c/sys/un.cr new file mode 100644 index 000000000000..7f5bd705a4dd --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/un.cr @@ -0,0 +1,8 @@ +require "./socket" + +lib LibC + struct SockaddrUn + sun_family : SaFamilyT + sun_path : StaticArray(Char, 108) + end +end diff --git a/src/lib_c/aarch64-android/c/sys/wait.cr b/src/lib_c/aarch64-android/c/sys/wait.cr new file mode 100644 index 000000000000..470533ce60fe --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/wait.cr @@ -0,0 +1,8 @@ +require "./types" +require "../signal" + +lib LibC + WNOHANG = 1 + + fun waitpid(__pid : PidT, __status : Int*, __options : Int) : PidT +end diff --git a/src/lib_c/aarch64-android/c/termios.cr b/src/lib_c/aarch64-android/c/termios.cr new file mode 100644 index 000000000000..01f5a2831a0b --- /dev/null +++ b/src/lib_c/aarch64-android/c/termios.cr @@ -0,0 +1,178 @@ +require "./sys/types" + +lib LibC + BRKINT = 0o000002 + ISTRIP = 0o000040 + ICRNL = 0o000400 + IXON = 0o002000 + + OPOST = 0o000001 + + ISIG = 0o000001 + ICANON = 0o000002 + ECHO = 0o000010 + ECHOE = 0o000020 + ECHOK = 0o000040 + ECHONL = 0o000100 + IEXTEN = 0o100000 + + TCSANOW = 0 + + # the following constants would have been used by the inline version of + # `cfmakeraw` (see the bottom of this file) + VTIME = 5 + VMIN = 6 + + CSIZE = 0o000060 + PARENB = 0o000400 + + CS8 = 0o000060 + + IGNBRK = 0o000001 + PARMRK = 0o000010 + INLCR = 0o000100 + IGNCR = 0o000200 + + # the following constants are unused and solely provided for `::Termios` + VINTR = 0 + VQUIT = 1 + VERASE = 2 + VKILL = 3 + VEOF = 4 + VSWTC = 7 + VSTART = 8 + VSTOP = 9 + VSUSP = 10 + VEOL = 11 + VREPRINT = 12 + VDISCARD = 13 + VWERASE = 14 + VLNEXT = 15 + VEOL2 = 16 + IGNPAR = 0o000004 + INPCK = 0o000020 + IUCLC = 0o001000 + IXANY = 0o004000 + IXOFF = 0o010000 + IMAXBEL = 0o020000 + IUTF8 = 0o040000 + OLCUC = 0o000002 + ONLCR = 0o000004 + OCRNL = 0o000010 + ONOCR = 0o000020 + ONLRET = 0o000040 + OFILL = 0o000100 + OFDEL = 0o000200 + NLDLY = 0o000400 + NL0 = 0o000000 + NL1 = 0o000400 + CRDLY = 0o003000 + CR0 = 0o000000 + CR1 = 0o001000 + CR2 = 0o002000 + CR3 = 0o003000 + TABDLY = 0o014000 + TAB0 = 0o000000 + TAB1 = 0o004000 + TAB2 = 0o010000 + TAB3 = 0o014000 + XTABS = 0o014000 + BSDLY = 0o020000 + BS0 = 0o000000 + BS1 = 0o020000 + VTDLY = 0o040000 + VT0 = 0o000000 + VT1 = 0o040000 + FFDLY = 0o100000 + FF0 = 0o000000 + FF1 = 0o100000 + CBAUD = 0o010017 + B0 = 0o000000 + B50 = 0o000001 + B75 = 0o000002 + B110 = 0o000003 + B134 = 0o000004 + B150 = 0o000005 + B200 = 0o000006 + B300 = 0o000007 + B600 = 0o000010 + B1200 = 0o000011 + B1800 = 0o000012 + B2400 = 0o000013 + B4800 = 0o000014 + B9600 = 0o000015 + B19200 = 0o000016 + B38400 = 0o000017 + + EXTA = LibC::B19200 + EXTB = LibC::B38400 + + CS5 = 0o000000 + CS6 = 0o000020 + CS7 = 0o000040 + CSTOPB = 0o000100 + CREAD = 0o000200 + PARODD = 0o001000 + HUPCL = 0o002000 + CLOCAL = 0o004000 + CBAUDEX = 0o010000 + BOTHER = 0o010000 + B57600 = 0o010001 + B115200 = 0o010002 + B230400 = 0o010003 + B460800 = 0o010004 + B500000 = 0o010005 + B576000 = 0o010006 + B921600 = 0o010007 + B1000000 = 0o010010 + B1152000 = 0o010011 + B1500000 = 0o010012 + B2000000 = 0o010013 + B2500000 = 0o010014 + B3000000 = 0o010015 + B3500000 = 0o010016 + B4000000 = 0o010017 + CIBAUD = 0o02003600000 + CMSPAR = 0o10000000000 + CRTSCTS = 0o20000000000 + IBSHIFT = 16 + XCASE = 0o000004 + NOFLSH = 0o000200 + TOSTOP = 0o000400 + ECHOCTL = 0o001000 + ECHOPRT = 0o002000 + ECHOKE = 0o004000 + FLUSHO = 0o010000 + PENDIN = 0o040000 + EXTPROC = 0o200000 + TCOOFF = 0 + TCOON = 1 + TCIOFF = 2 + TCION = 3 + TCIFLUSH = 0 + TCOFLUSH = 1 + TCIOFLUSH = 2 + TCSADRAIN = 1 + TCSAFLUSH = 2 + + alias CcT = Char + alias SpeedT = UInt + alias TcflagT = UInt + + struct Termios + c_iflag : TcflagT + c_oflag : TcflagT + c_cflag : TcflagT + c_lflag : TcflagT + c_line : CcT + c_cc : StaticArray(CcT, 19) # cc_t[NCCS] + end + + # TODO: defined inline for `21 <= ANDROID_API < 28` in terms of `ioctl`, but + # `lib/reply/src/term_size.cr` contains an incompatible definition of it + {% if ANDROID_API >= 28 %} + fun tcgetattr(__fd : Int, __t : Termios*) : Int + fun tcsetattr(__fd : Int, __optional_actions : Int, __t : Termios*) : Int + fun cfmakeraw(__t : Termios*) + {% end %} +end diff --git a/src/lib_c/aarch64-android/c/time.cr b/src/lib_c/aarch64-android/c/time.cr new file mode 100644 index 000000000000..3108f2e94bff --- /dev/null +++ b/src/lib_c/aarch64-android/c/time.cr @@ -0,0 +1,37 @@ +require "./sys/types" + +lib LibC + CLOCK_MONOTONIC = 1 + CLOCK_REALTIME = 0 + + struct Tm + tm_sec : Int + tm_min : Int + tm_hour : Int + tm_mday : Int + tm_mon : Int + tm_year : Int + tm_wday : Int + tm_yday : Int + tm_isdst : Int + tm_gmtoff : Long + tm_zone : Char* + end + + struct Timespec + tv_sec : TimeT + tv_nsec : Long + end + + fun clock_gettime(__clock : ClockidT, __ts : Timespec*) : Int + fun clock_settime(__clock : ClockidT, __ts : Timespec*) : Int + fun gmtime_r(__t : TimeT*, __tm : Tm*) : Tm* + fun localtime_r(__t : TimeT*, __tm : Tm*) : Tm* + fun mktime(__tm : Tm*) : TimeT + fun tzset : Void + fun timegm(__tm : Tm*) : TimeT + + $daylight : Int + $timezone : Long + $tzname : StaticArray(Char*, 2) +end diff --git a/src/lib_c/aarch64-android/c/unistd.cr b/src/lib_c/aarch64-android/c/unistd.cr new file mode 100644 index 000000000000..81b9a256c305 --- /dev/null +++ b/src/lib_c/aarch64-android/c/unistd.cr @@ -0,0 +1,51 @@ +require "./sys/types" +require "./stdint" + +lib LibC + F_OK = 0 + R_OK = 4 + W_OK = 2 + X_OK = 1 + SC_CLK_TCK = 6 + SC_NPROCESSORS_ONLN = 97 + SC_PAGESIZE = 39 + + fun chroot(__path : Char*) : Int + fun access(__path : Char*, __mode : Int) : Int + fun chdir(__path : Char*) : Int + fun chown(__path : Char*, __owner : UidT, __group : GidT) : Int + fun fchown(__fd : Int, __owner : UidT, __group : GidT) : Int + fun close(__fd : Int) : Int + fun dup2(__old_fd : Int, __new_fd : Int) : Int + fun _exit(__status : Int) : NoReturn + fun execvp(__file : Char*, __argv : Char**) : Int + fun fdatasync(__fd : Int) : Int + @[ReturnsTwice] + fun fork : PidT + fun fsync(__fd : Int) : Int + fun ftruncate(__fd : Int, __length : OffT) : Int + fun getcwd(__buf : Char*, __size : SizeT) : Char* + fun gethostname(__buf : Char*, __buf_size : SizeT) : Int + fun getpgid(__pid : PidT) : PidT + fun getpid : PidT + fun getppid : PidT + fun getuid : UidT + fun isatty(__fd : Int) : Int + fun ttyname_r(__fd : Int, __buf : Char*, __buf_size : SizeT) : Int + fun lchown(__path : Char*, __owner : UidT, __group : GidT) : Int + fun link(__old_path : Char*, __new_path : Char*) : Int + {% if ANDROID_API >= 24 %} + fun lockf(__fd : Int, __cmd : Int, __length : OffT) : Int + {% end %} + fun lseek(__fd : Int, __offset : OffT, __whence : Int) : OffT + fun pipe(__fds : Int[2]) : Int + fun read(__fd : Int, __buf : Void*, __count : SizeT) : SSizeT + fun pread(__fd : Int, __buf : Void*, __count : SizeT, __offest : OffT) : SSizeT + fun rmdir(__path : Char*) : Int + fun symlink(__old_path : Char*, __new_path : Char*) : Int + fun readlink(__path : Char*, __buf : Char*, __buf_size : SizeT) : SSizeT + fun syscall(__number : Long, ...) : Long + fun sysconf(__name : Int) : Long + fun unlink(__path : Char*) : Int + fun write(__fd : Int, __buf : Void*, __count : SizeT) : SSizeT +end diff --git a/src/llvm.cr b/src/llvm.cr index 37d5e0038dd9..5752ade47c1a 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -95,6 +95,9 @@ module LLVM triple = string_and_dispose(chars) if triple =~ /x86_64-apple-macosx|x86_64-apple-darwin/ "x86_64-apple-macosx" + elsif triple =~ /aarch64-unknown-linux-android/ + # remove API version + "aarch64-unknown-linux-android" else triple end From 0a5a25b59b03d88c47468690711c51f2234116e7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 9 Mar 2023 22:22:11 +0800 Subject: [PATCH 0364/1551] Support LLVM 15 (#13164) * allow using llvm 15 * use opaque pointers on llvm 15+ * opaque pointer names in llvm intrinsics * uwtable -> uwtable(async) * use unnamed llvm structs for lib fun bindings of llvm intrinsics * ci support * fix uwtable value * `uwtable(async)` outside aarch64 * don't use string interpolation --- .github/workflows/linux.yml | 3 +++ spec/compiler/codegen/primitives_spec.cr | 3 ++- spec/llvm-ir/test.sh | 3 +++ src/compiler/crystal/codegen/ast.cr | 4 ++++ src/compiler/crystal/codegen/codegen.cr | 14 ++++++++++-- src/compiler/crystal/codegen/fun.cr | 14 ++++++++++-- .../crystal/codegen/llvm_builder_helper.cr | 3 ++- src/compiler/crystal/codegen/llvm_typer.cr | 22 ++++++++++++++++++- src/intrinsics.cr | 22 +++++++++++++++++-- src/llvm/context.cr | 14 +++++++++++- src/llvm/enums.cr | 7 ++++++ src/llvm/ext/llvm-versions.txt | 2 +- src/llvm/function.cr | 10 +++++++++ src/llvm/lib_llvm.cr | 6 +++++ src/llvm/type.cr | 14 ++++++++++-- 15 files changed, 128 insertions(+), 13 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 663ad7bcea6e..54b74851a702 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -67,6 +67,9 @@ jobs: - llvm_version: 14.0.0 llvm_url: https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz base_image: ubuntu-18.04 + - llvm_version: 15.0.6 + llvm_url: https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.6/clang+llvm-15.0.6-x86_64-linux-gnu-ubuntu-18.04.tar.xz + base_image: ubuntu-18.04 runs-on: ${{ matrix.base_image }} name: "Test LLVM ${{ matrix.llvm_version }} (${{ matrix.base_image }})" steps: diff --git a/spec/compiler/codegen/primitives_spec.cr b/spec/compiler/codegen/primitives_spec.cr index cbe5695beb4f..2434beabbf8f 100644 --- a/spec/compiler/codegen/primitives_spec.cr +++ b/spec/compiler/codegen/primitives_spec.cr @@ -277,8 +277,9 @@ describe "Code gen: primitives" do list = VaList.new list.next(Int32) )) + type = {% if LibLLVM::IS_LT_150 %} "%VaList*" {% else %} "ptr" {% end %} str = mod.to_s - str.should contain("va_arg %VaList* %list") + str.should contain("va_arg #{type} %list") end it "works with C code" do diff --git a/spec/llvm-ir/test.sh b/spec/llvm-ir/test.sh index 6c783ae5b4f7..48a8f38d4d8b 100755 --- a/spec/llvm-ir/test.sh +++ b/spec/llvm-ir/test.sh @@ -1,5 +1,8 @@ #!/bin/bash +# TODO: the specs in this folder still expect typed pointers and so will fail +# on LLVM 15+ which use opaque pointers + set -euo pipefail SCRIPT_PATH="$(realpath "$0")" diff --git a/src/compiler/crystal/codegen/ast.cr b/src/compiler/crystal/codegen/ast.cr index bae46133d4bd..816958844dd9 100644 --- a/src/compiler/crystal/codegen/ast.cr +++ b/src/compiler/crystal/codegen/ast.cr @@ -85,6 +85,10 @@ module Crystal @c_calling_convention ? self : nil end + def llvm_intrinsic? + self.is_a?(External) && self.real_name.starts_with?("llvm.") + end + private def compute_c_calling_convention # One case where this is not true if for LLVM intrinsics. # For example overflow intrinsics return a tuple, like {i32, i1}: diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index e78af6af3ae0..b4394d6f9329 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -2213,7 +2213,12 @@ module Crystal end private def c_memset_fun - name = @program.bits64? ? "llvm.memset.p0i8.i64" : "llvm.memset.p0i8.i32" + name = {% if LibLLVM::IS_LT_150 %} + @program.bits64? ? "llvm.memset.p0i8.i64" : "llvm.memset.p0i8.i32" + {% else %} + @program.bits64? ? "llvm.memset.p0.i64" : "llvm.memset.p0.i32" + {% end %} + fetch_typed_fun(@llvm_mod, name) do len_type = @program.bits64? ? @llvm_context.int64 : @llvm_context.int32 arg_types = [@llvm_context.void_pointer, @llvm_context.int8, len_type, @llvm_context.int1] @@ -2222,7 +2227,12 @@ module Crystal end private def c_memcpy_fun - name = @program.bits64? ? "llvm.memcpy.p0i8.p0i8.i64" : "llvm.memcpy.p0i8.p0i8.i32" + name = {% if LibLLVM::IS_LT_150 %} + @program.bits64? ? "llvm.memcpy.p0i8.p0i8.i64" : "llvm.memcpy.p0i8.p0i8.i32" + {% else %} + @program.bits64? ? "llvm.memcpy.p0.p0.i64" : "llvm.memcpy.p0.p0.i32" + {% end %} + fetch_typed_fun(@llvm_mod, name) do len_type = @program.bits64? ? @llvm_context.int64 : @llvm_context.int32 arg_types = [@llvm_context.void_pointer, @llvm_context.void_pointer, len_type, @llvm_context.int1] diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 938d04836ad9..0cb9cb3f069b 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -95,7 +95,12 @@ class Crystal::CodeGenVisitor emit_def_debug_metadata target_def unless @debug.none? set_current_debug_location target_def if @debug.line_numbers? - context.fun.add_attribute LLVM::Attribute::UWTable + {% if LibLLVM::IS_LT_150 %} + context.fun.add_attribute LLVM::Attribute::UWTable + {% else %} + context.fun.add_attribute LLVM::Attribute::UWTable, value: @program.has_flag?("aarch64") ? LLVM::UWTableKind::Sync : LLVM::UWTableKind::Async + {% end %} + if @program.has_flag?("darwin") # Disable frame pointer elimination in Darwin, as it causes issues during stack unwind {% if compare_versions(Crystal::LLVM_VERSION, "8.0.0") < 0 %} @@ -302,7 +307,12 @@ class Crystal::CodeGenVisitor end llvm_arg_type end - llvm_return_type = llvm_return_type(target_def.type) + + llvm_return_type = {% if LibLLVM::IS_LT_150 %} + llvm_return_type(target_def.type) + {% else %} + target_def.llvm_intrinsic? ? llvm_intrinsic_return_type(target_def.type) : llvm_return_type(target_def.type) + {% end %} if is_closure llvm_args_types.insert(0, llvm_context.void_pointer) diff --git a/src/compiler/crystal/codegen/llvm_builder_helper.cr b/src/compiler/crystal/codegen/llvm_builder_helper.cr index 9f843856b45f..be8c0cec069a 100644 --- a/src/compiler/crystal/codegen/llvm_builder_helper.cr +++ b/src/compiler/crystal/codegen/llvm_builder_helper.cr @@ -198,7 +198,8 @@ module Crystal end delegate llvm_type, llvm_struct_type, llvm_arg_type, llvm_embedded_type, - llvm_c_type, llvm_c_return_type, llvm_return_type, llvm_embedded_c_type, to: llvm_typer + llvm_c_type, llvm_c_return_type, llvm_return_type, llvm_embedded_c_type, + llvm_intrinsic_return_type, to: llvm_typer def llvm_proc_type(type) llvm_typer.proc_type(type.as(ProcInstanceType)) diff --git a/src/compiler/crystal/codegen/llvm_typer.cr b/src/compiler/crystal/codegen/llvm_typer.cr index 812cf94e79ab..8b2cfdf65ec4 100644 --- a/src/compiler/crystal/codegen/llvm_typer.cr +++ b/src/compiler/crystal/codegen/llvm_typer.cr @@ -458,6 +458,22 @@ module Crystal llvm_type(type) end + # Since LLVM 15, LLVM intrinsics must return unnamed structs, and instances + # of the named `Tuple` struct are no longer substitutable + # This happens when binding to intrinsics like `llvm.sadd.with.overflow.*` + # as lib funs directly + def llvm_intrinsic_return_type(type : TupleInstanceType) + @llvm_context.struct(type.tuple_types.map { |tuple_type| llvm_embedded_type(tuple_type).as(LLVM::Type) }) + end + + def llvm_intrinsic_return_type(type : NamedTupleInstanceType) + @llvm_context.struct(type.entries.map { |entry| llvm_embedded_type(entry.type).as(LLVM::Type) }) + end + + def llvm_intrinsic_return_type(type : Type) + llvm_return_type(type) + end + def closure_type(type : ProcInstanceType) arg_types = type.arg_types.map { |arg_type| llvm_type(arg_type) } arg_types.insert(0, @llvm_context.void_pointer) @@ -507,7 +523,11 @@ module Crystal when .double? @llvm_context.double when .pointer? - copy_type(type.element_type).pointer + {% if LibLLVM::IS_LT_150 %} + copy_type(type.element_type).pointer + {% else %} + @llvm_context.pointer + {% end %} when .array? copy_type(type.element_type).array(type.array_size) when .vector? diff --git a/src/intrinsics.cr b/src/intrinsics.cr index 1bbbc8c3330b..0f2996a0b4a2 100644 --- a/src/intrinsics.cr +++ b/src/intrinsics.cr @@ -14,7 +14,7 @@ lib LibIntrinsics {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} fun memset = "llvm.memset.p0i8.i64"(dest : Void*, val : UInt8, len : UInt64, align : UInt32, is_volatile : Bool) - {% else %} + {% elsif compare_versions(Crystal::LLVM_VERSION, "15.0.0") < 0 %} {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} fun memcpy = "llvm.memcpy.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) @@ -23,6 +23,15 @@ lib LibIntrinsics {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} fun memset = "llvm.memset.p0i8.i64"(dest : Void*, val : UInt8, len : UInt64, is_volatile : Bool) + {% else %} + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} + fun memcpy = "llvm.memcpy.p0.p0.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} + fun memmove = "llvm.memmove.p0.p0.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} + fun memset = "llvm.memset.p0.i64"(dest : Void*, val : UInt8, len : UInt64, is_volatile : Bool) {% end %} {% else %} {% if compare_versions(Crystal::LLVM_VERSION, "7.0.0") < 0 %} @@ -34,7 +43,7 @@ lib LibIntrinsics {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} fun memset = "llvm.memset.p0i8.i32"(dest : Void*, val : UInt8, len : UInt32, align : UInt32, is_volatile : Bool) - {% else %} + {% elsif compare_versions(Crystal::LLVM_VERSION, "15.0.0") < 0 %} {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} fun memcpy = "llvm.memcpy.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) @@ -43,6 +52,15 @@ lib LibIntrinsics {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} fun memset = "llvm.memset.p0i8.i32"(dest : Void*, val : UInt8, len : UInt32, is_volatile : Bool) + {% else %} + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} + fun memcpy = "llvm.memcpy.p0.p0.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} + fun memmove = "llvm.memmove.p0.p0.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} + fun memset = "llvm.memset.p0.i32"(dest : Void*, val : UInt8, len : UInt32, is_volatile : Bool) {% end %} {% end %} diff --git a/src/llvm/context.cr b/src/llvm/context.cr index 4e8a986c1155..0a83503d1167 100644 --- a/src/llvm/context.cr +++ b/src/llvm/context.cr @@ -64,8 +64,20 @@ class LLVM::Context Type.new LibLLVM.double_type_in_context(self) end + def pointer : Type + {% if LibLLVM::IS_LT_150 %} + {% raise "Opaque pointers are only supported on LLVM 15.0 or above" %} + {% else %} + Type.new LibLLVM.pointer_type_in_context(self, 0) + {% end %} + end + def void_pointer : Type - int8.pointer + {% if LibLLVM::IS_LT_150 %} + int8.pointer + {% else %} + pointer + {% end %} end def struct(name : String, packed = false) : Type diff --git a/src/llvm/enums.cr b/src/llvm/enums.cr index 649d54383c1d..cc32289a070e 100644 --- a/src/llvm/enums.cr +++ b/src/llvm/enums.cr @@ -462,6 +462,13 @@ module LLVM PreserveAccessIndex = 27 # "llvm.preserve.*.access.index" end end + + enum UWTableKind + None = 0 # No unwind table requested + Sync = 1 # "Synchronous" unwind tables + Async = 2 # "Asynchronous" unwind tables (instr precise) + Default = 2 + end end require "./enums/*" diff --git a/src/llvm/ext/llvm-versions.txt b/src/llvm/ext/llvm-versions.txt index 405fc1147bde..e8d62aac3eff 100644 --- a/src/llvm/ext/llvm-versions.txt +++ b/src/llvm/ext/llvm-versions.txt @@ -1 +1 @@ -14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 7.1 6.0 5.0 4.0 3.9 3.8 +15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 7.1 6.0 5.0 4.0 3.9 3.8 diff --git a/src/llvm/function.cr b/src/llvm/function.cr index 13d6af5ca5cd..d2bdfa867eba 100644 --- a/src/llvm/function.cr +++ b/src/llvm/function.cr @@ -43,6 +43,16 @@ struct LLVM::Function {% end %} end + def add_attribute(attribute : Attribute, index = AttributeIndex::FunctionIndex, *, value) + return if attribute.value == 0 + + context = LibLLVM.get_module_context(LibLLVM.get_global_parent(self)) + attribute.each_kind do |kind| + attribute_ref = LibLLVM.create_enum_attribute(context, kind, value.to_u64) + LibLLVM.add_attribute_at_index(self, index, attribute_ref) + end + end + def add_target_dependent_attribute(name, value) LibLLVM.add_target_dependent_function_attr self, name, value end diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 279220e8b5f2..c148083d2c9f 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -19,6 +19,7 @@ end {% begin %} lib LibLLVM + IS_150 = {{LibLLVM::VERSION.starts_with?("15.0")}} IS_140 = {{LibLLVM::VERSION.starts_with?("14.0")}} IS_130 = {{LibLLVM::VERSION.starts_with?("13.0")}} IS_120 = {{LibLLVM::VERSION.starts_with?("12.0")}} @@ -42,6 +43,8 @@ end IS_LT_110 = {{compare_versions(LibLLVM::VERSION, "11.0.0") < 0}} IS_LT_120 = {{compare_versions(LibLLVM::VERSION, "12.0.0") < 0}} IS_LT_130 = {{compare_versions(LibLLVM::VERSION, "13.0.0") < 0}} + IS_LT_140 = {{compare_versions(LibLLVM::VERSION, "14.0.0") < 0}} + IS_LT_150 = {{compare_versions(LibLLVM::VERSION, "15.0.0") < 0}} end {% end %} @@ -407,6 +410,9 @@ lib LibLLVM fun float_type_in_context = LLVMFloatTypeInContext(ContextRef) : TypeRef fun double_type_in_context = LLVMDoubleTypeInContext(ContextRef) : TypeRef fun struct_type_in_context = LLVMStructTypeInContext(c : ContextRef, element_types : TypeRef*, element_count : UInt32, packed : Int32) : TypeRef + {% unless LibLLVM::IS_LT_150 %} + fun pointer_type_in_context = LLVMPointerTypeInContext(ContextRef, address_space : UInt) : TypeRef + {% end %} fun const_string_in_context = LLVMConstStringInContext(c : ContextRef, str : UInt8*, length : UInt32, dont_null_terminate : Int32) : ValueRef fun const_struct_in_context = LLVMConstStructInContext(c : ContextRef, contant_vals : ValueRef*, count : UInt32, packed : Int32) : ValueRef diff --git a/src/llvm/type.cr b/src/llvm/type.cr index 1343308b1c55..3d0c3b2a223e 100644 --- a/src/llvm/type.cr +++ b/src/llvm/type.cr @@ -42,7 +42,11 @@ struct LLVM::Type end def pointer : LLVM::Type - Type.new LibLLVM.pointer_type(self, 0) + {% if LibLLVM::IS_LT_150 %} + Type.new LibLLVM.pointer_type(self, 0) + {% else %} + Type.new LibLLVM.pointer_type_in_context(LibLLVM.get_type_context(self), 0) + {% end %} end def array(count) : LLVM::Type @@ -85,8 +89,14 @@ struct LLVM::Type def element_type : LLVM::Type case kind - when Kind::Array, Kind::Vector, Kind::Pointer + when Kind::Array, Kind::Vector Type.new LibLLVM.get_element_type(self) + when Kind::Pointer + {% if LibLLVM::IS_LT_150 %} + Type.new LibLLVM.get_element_type(self) + {% else %} + raise "Typed pointers are unavailable on LLVM 15.0 or above" + {% end %} else raise "Not a sequential type" end From 2df696b54410916d5e6b5cd8bd6279c19df48a85 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 10 Mar 2023 05:49:02 +0800 Subject: [PATCH 0365/1551] Do not build LLVM `bitcast` instructions for opaque pointer casts (#13171) * `bit_cast`, obvious cases * `__crystal_once` * `__crystal_get_exception` * `__crystal_raise` * use `cast_to_void_pointer` in more places * `cast_to` and `cast_to_pointer` * `pointer_cast` is always a macro --- src/compiler/crystal/codegen/call.cr | 14 +++++----- src/compiler/crystal/codegen/cast.cr | 2 +- src/compiler/crystal/codegen/codegen.cr | 16 +++++------ src/compiler/crystal/codegen/cond.cr | 4 +-- src/compiler/crystal/codegen/exception.cr | 9 +++++-- src/compiler/crystal/codegen/fun.cr | 15 ++++------- .../crystal/codegen/llvm_builder_helper.cr | 27 +++++++++++++------ src/compiler/crystal/codegen/once.cr | 14 +++++----- src/compiler/crystal/codegen/primitives.cr | 12 ++++----- src/compiler/crystal/codegen/unions.cr | 4 +-- 10 files changed, 65 insertions(+), 52 deletions(-) diff --git a/src/compiler/crystal/codegen/call.cr b/src/compiler/crystal/codegen/call.cr index 56f852650faa..d0937cfff40c 100644 --- a/src/compiler/crystal/codegen/call.cr +++ b/src/compiler/crystal/codegen/call.cr @@ -236,8 +236,8 @@ class Crystal::CodeGenVisitor def codegen_direct_abi_call(call_arg_type, call_arg, abi_arg_type) if cast = abi_arg_type.cast final_value = alloca cast - final_value_casted = bit_cast final_value, llvm_context.void_pointer - gep_call_arg = bit_cast gep(llvm_type(call_arg_type), call_arg, 0, 0), llvm_context.void_pointer + final_value_casted = cast_to_void_pointer final_value + gep_call_arg = cast_to_void_pointer gep(llvm_type(call_arg_type), call_arg, 0, 0) size = @abi.size(abi_arg_type.type) size = @program.bits64? ? int64(size) : int32(size) align = @abi.align(abi_arg_type.type) @@ -253,8 +253,8 @@ class Crystal::CodeGenVisitor # and *replace* the call argument by a pointer to the allocated memory def codegen_indirect_abi_call(call_arg, abi_arg_type) final_value = alloca abi_arg_type.type - final_value_casted = bit_cast final_value, llvm_context.void_pointer - call_arg_casted = bit_cast call_arg, llvm_context.void_pointer + final_value_casted = cast_to_void_pointer final_value + call_arg_casted = cast_to_void_pointer call_arg size = @abi.size(abi_arg_type.type) size = @program.bits64? ? int64(size) : int32(size) align = @abi.align(abi_arg_type.type) @@ -513,7 +513,7 @@ class Crystal::CodeGenVisitor external = target_def.try &.c_calling_convention? if external && (external.type.proc? || external.type.is_a?(NilableProcType)) - fun_ptr = bit_cast(@last, llvm_context.void_pointer) + fun_ptr = cast_to_void_pointer(@last) ctx_ptr = llvm_context.void_pointer.null return @last = make_fun(external.type, fun_ptr, ctx_ptr) end @@ -528,9 +528,9 @@ class Crystal::CodeGenVisitor if cast = abi_return.cast cast1 = alloca cast store @last, cast1 - cast2 = bit_cast cast1, llvm_context.void_pointer + cast2 = cast_to_void_pointer(cast1) final_value = alloca abi_return.type - final_value_casted = bit_cast final_value, llvm_context.void_pointer + final_value_casted = cast_to_void_pointer final_value size = @abi.size(abi_return.type) size = @program.@program.bits64? ? int64(size) : int32(size) align = @abi.align(abi_return.type) diff --git a/src/compiler/crystal/codegen/cast.cr b/src/compiler/crystal/codegen/cast.cr index 8b0a5ec063ae..9f8111a5a4dc 100644 --- a/src/compiler/crystal/codegen/cast.cr +++ b/src/compiler/crystal/codegen/cast.cr @@ -339,7 +339,7 @@ class Crystal::CodeGenVisitor def downcast_distinct(value, to_type : PointerInstanceType, from_type : PointerInstanceType) # cast of a pointer being cast to Void* - bit_cast value, llvm_context.void_pointer + cast_to_void_pointer value end def downcast_distinct(value, to_type : ReferenceUnionType, from_type : ReferenceUnionType) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index b4394d6f9329..00bf49e5e5ef 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -606,9 +606,9 @@ module Crystal the_fun = check_main_fun fun_literal_name, the_fun set_current_debug_location(node) if @debug.line_numbers? - fun_ptr = bit_cast(the_fun.func, llvm_context.void_pointer) + fun_ptr = cast_to_void_pointer(the_fun.func) if is_closure - ctx_ptr = bit_cast(context.closure_ptr.not_nil!, llvm_context.void_pointer) + ctx_ptr = cast_to_void_pointer(context.closure_ptr.not_nil!) else ctx_ptr = llvm_context.void_pointer.null end @@ -655,9 +655,9 @@ module Crystal last_fun = target_def_fun(node.call.target_def, owner) set_current_debug_location(node) if @debug.line_numbers? - fun_ptr = bit_cast(last_fun.func, llvm_context.void_pointer) + fun_ptr = cast_to_void_pointer(last_fun.func) if call_self && !owner.metaclass? && !owner.is_a?(LibType) - ctx_ptr = bit_cast(call_self, llvm_context.void_pointer) + ctx_ptr = cast_to_void_pointer(call_self) else ctx_ptr = llvm_context.void_pointer.null end @@ -1607,7 +1607,7 @@ module Crystal func = typed_fun?(@main_mod, check_fun_name) || create_check_proc_is_not_closure_fun(check_fun_name) func = check_main_fun check_fun_name, func value = call func, [value] of LLVM::Value - bit_cast value, llvm_proc_type(type).pointer + pointer_cast value, llvm_proc_type(type).pointer end def create_check_proc_is_not_closure_fun(fun_name) @@ -2072,7 +2072,7 @@ module Crystal pointer = call_c_malloc size end - bit_cast pointer, type.pointer + pointer_cast pointer, type.pointer end def array_malloc(type, count) @@ -2093,7 +2093,7 @@ module Crystal end memset pointer, int8(0), size - bit_cast pointer, type.pointer + pointer_cast pointer, type.pointer end def crystal_malloc_fun @@ -2281,7 +2281,7 @@ module Crystal # For a struct we need to cast the second part of the union to the base type _, value_ptr = union_type_and_value_pointer(pointer, _type) target_type = type.base_type - pointer = bit_cast value_ptr, llvm_type(target_type).pointer + pointer = cast_to_pointer value_ptr, target_type else # Nothing, there's only one subclass so it's the struct already end diff --git a/src/compiler/crystal/codegen/cond.cr b/src/compiler/crystal/codegen/cond.cr index f45b1b006b35..8394af9ad157 100644 --- a/src/compiler/crystal/codegen/cond.cr +++ b/src/compiler/crystal/codegen/cond.cr @@ -44,7 +44,7 @@ class Crystal::CodeGenVisitor end if has_bool - value = load(llvm_context.int1, bit_cast value_ptr, llvm_context.int1.pointer) + value = load(llvm_context.int1, pointer_cast value_ptr, llvm_context.int1.pointer) is_bool = equal? type_id, type_id(@program.bool) cond = and cond, not(and(is_bool, not(value))) end @@ -54,7 +54,7 @@ class Crystal::CodeGenVisitor next unless union_type.is_a?(PointerInstanceType) is_pointer = equal? type_id, type_id(union_type) - pointer_value = load(llvm_type(union_type), bit_cast value_ptr, llvm_type(union_type).pointer) + pointer_value = load(llvm_type(union_type), cast_to_pointer(value_ptr, union_type)) pointer_null = null_pointer?(pointer_value) cond = and cond, not(and(is_pointer, pointer_null)) end diff --git a/src/compiler/crystal/codegen/exception.cr b/src/compiler/crystal/codegen/exception.cr index 3b2d5d2cc92e..9a33e1337550 100644 --- a/src/compiler/crystal/codegen/exception.cr +++ b/src/compiler/crystal/codegen/exception.cr @@ -149,8 +149,11 @@ class Crystal::CodeGenVisitor # We call __crystal_get_exception to get the actual crystal `Exception` object. get_exception_fun = main_fun(GET_EXCEPTION_NAME) + get_exception_arg_type = get_exception_fun.type.params_types.first # Void* or LibUnwind::Exception* + get_exception_arg = pointer_cast(unwind_ex_obj, get_exception_arg_type) + set_current_debug_location node if @debug.line_numbers? - caught_exception_ptr = call get_exception_fun, [bit_cast(unwind_ex_obj, get_exception_fun.type.params_types.first)] + caught_exception_ptr = call get_exception_fun, [get_exception_arg] caught_exception = int2ptr caught_exception_ptr, llvm_typer.type_id_pointer end @@ -286,7 +289,9 @@ class Crystal::CodeGenVisitor unreachable else raise_fun = main_fun(RAISE_NAME) - codegen_call_or_invoke(node, nil, nil, raise_fun, [bit_cast(unwind_ex_obj.not_nil!, raise_fun.func.params.first.type)], true, @program.no_return) + raise_fun_arg_type = raise_fun.func.params.first.type # Void* or LibUnwind::Exception* + raise_fun_arg = pointer_cast(unwind_ex_obj.not_nil!, raise_fun_arg_type) + codegen_call_or_invoke(node, nil, nil, raise_fun, [raise_fun_arg], true, @program.no_return) end end diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 0cb9cb3f069b..7b859f044588 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -117,7 +117,7 @@ class Crystal::CodeGenVisitor clear_current_debug_location if @debug.line_numbers? void_ptr = context.fun.params.first closure_type = llvm_typer.copy_type(context.closure_type.not_nil!) - closure_ptr = bit_cast void_ptr, closure_type.pointer + closure_ptr = pointer_cast void_ptr, closure_type.pointer setup_closure_vars target_def.vars, context.closure_vars.not_nil!, self.context, closure_type, closure_ptr else context.reset_closure @@ -240,7 +240,7 @@ class Crystal::CodeGenVisitor abi_info = abi_info(target_def) ret_type = abi_info.return_type if cast = ret_type.cast - casted_last = bit_cast @last, cast.pointer + casted_last = pointer_cast @last, cast.pointer last = load cast, casted_last ret last return @@ -482,11 +482,6 @@ class Crystal::CodeGenVisitor end end - def fun_literal_closure_ptr - void_ptr = context.fun.params.first - bit_cast void_ptr, llvm_typer.copy_type(context.closure_type.not_nil!).pointer - end - def create_local_copy_of_fun_args(target_def, self_type, args, is_fun_literal, is_closure) offset = is_closure ? 1 : 0 @@ -550,7 +545,7 @@ class Crystal::CodeGenVisitor context.vars[arg.name] = LLVMVar.new(value, var_type) else pointer = alloca(llvm_type(var_type), arg.name) - casted_pointer = bit_cast pointer, value.type.pointer + casted_pointer = pointer_cast pointer, value.type.pointer store value, casted_pointer pointer = declare_debug_for_function_argument(arg.name, var_type, index + 1, pointer, location) unless target_def.naked? context.vars[arg.name] = LLVMVar.new(pointer, var_type) @@ -574,8 +569,8 @@ class Crystal::CodeGenVisitor pointer = declare_debug_for_function_argument(arg.name, var_type, index + 1, pointer, location) unless target_def.naked? if fun_proc - value = bit_cast(value, llvm_context.void_pointer) - value = make_fun(var_type, value, llvm_context.void_pointer.null) + fun_ptr = cast_to_void_pointer(value) + value = make_fun(var_type, fun_ptr, llvm_context.void_pointer.null) end context.vars[arg.name] = LLVMVar.new(pointer, var_type) diff --git a/src/compiler/crystal/codegen/llvm_builder_helper.cr b/src/compiler/crystal/codegen/llvm_builder_helper.cr index be8c0cec069a..088ea772552e 100644 --- a/src/compiler/crystal/codegen/llvm_builder_helper.cr +++ b/src/compiler/crystal/codegen/llvm_builder_helper.cr @@ -157,10 +157,6 @@ module Crystal builder.ret value end - def cast_to_void_pointer(pointer) - bit_cast pointer, llvm_context.void_pointer - end - def extend_int(from_type, to_type, value) from_type.signed? ? builder.sext(value, llvm_type(to_type)) : builder.zext(value, llvm_type(to_type)) end @@ -189,12 +185,27 @@ module Crystal end end - def cast_to(value, type) - bit_cast value, llvm_type(type) + def cast_to(value : LLVM::ValueMethods, type : Type) + pointer_cast value, llvm_type(type) + end + + def cast_to_pointer(value : LLVM::ValueMethods, type : Type) + pointer_cast value, llvm_type(type).pointer + end + + def cast_to_void_pointer(pointer : LLVM::ValueMethods) + pointer_cast pointer, llvm_context.void_pointer end - def cast_to_pointer(value, type) - bit_cast value, llvm_type(type).pointer + # *type* must be a pointer type; on LLVM 15.0 or above *type* is not + # evaluated at all and *value* is returned unchanged, because all opaque + # pointer types (in the same context) are identical + macro pointer_cast(value, type) + {% if LibLLVM::IS_LT_150 %} + bit_cast({{ value }}, {{ type }}) + {% else %} + {{ value }} + {% end %} end delegate llvm_type, llvm_struct_type, llvm_arg_type, llvm_embedded_type, diff --git a/src/compiler/crystal/codegen/once.cr b/src/compiler/crystal/codegen/once.cr index b72820266473..2e91267c1f52 100644 --- a/src/compiler/crystal/codegen/once.cr +++ b/src/compiler/crystal/codegen/once.cr @@ -20,16 +20,18 @@ class Crystal::CodeGenVisitor once_fun = main_fun(ONCE) once_init_fun = main_fun(ONCE_INIT) + # both of these should be Void* + once_state_type = once_init_fun.type.return_type + once_initializer_type = once_fun.func.params.last.type + once_state_global = @llvm_mod.globals[ONCE_STATE]? || begin - global = @llvm_mod.globals.add(once_init_fun.type.return_type, ONCE_STATE) + global = @llvm_mod.globals.add(once_state_type, ONCE_STATE) global.linkage = LLVM::Linkage::External global end - call once_fun, [ - load(once_init_fun.type.return_type, once_state_global), - flag, - bit_cast(func.func.to_value, once_fun.func.params.last.type), - ] + state = load(once_state_type, once_state_global) + initializer = pointer_cast(func.func.to_value, once_initializer_type) + call once_fun, [state, flag, initializer] end end diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index 76a0a0bf25c1..3e29f357bdeb 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -74,7 +74,7 @@ class Crystal::CodeGenVisitor when "store_atomic" codegen_primitive_store_atomic call, node, target_def, call_args when "throw_info" - cast_to void_ptr_throwinfo, @program.pointer_of(@program.void) + cast_to_void_pointer void_ptr_throwinfo when "va_arg" codegen_va_arg call, node, target_def, call_args else @@ -852,7 +852,7 @@ class Crystal::CodeGenVisitor def union_field_ptr(union_type, field_type, pointer) ptr = aggregate_index llvm_type(union_type), pointer, 0 if field_type.is_a?(ProcInstanceType) - bit_cast ptr, @llvm_typer.proc_type(field_type).pointer.pointer + pointer_cast ptr, @llvm_typer.proc_type(field_type).pointer.pointer else cast_to_pointer ptr, field_type end @@ -995,7 +995,7 @@ class Crystal::CodeGenVisitor Phi.open(self, node, @needs_value) do |phi| position_at_end ctx_is_null_block real_fun_llvm_type = llvm_proc_type(context.type) - real_fun_ptr = bit_cast fun_ptr, real_fun_llvm_type.pointer + real_fun_ptr = pointer_cast fun_ptr, real_fun_llvm_type.pointer # When invoking a Proc that has extern structs as arguments or return type, it's tricky: # closures are never generated with C ABI because C doesn't support closures. @@ -1022,7 +1022,7 @@ class Crystal::CodeGenVisitor position_at_end ctx_is_not_null_block real_fun_llvm_type = llvm_closure_type(context.type) - real_fun_ptr = bit_cast fun_ptr, real_fun_llvm_type.pointer + real_fun_ptr = pointer_cast fun_ptr, real_fun_llvm_type.pointer real_fun = LLVMTypedFunction.new(real_fun_llvm_type, LLVM::Function.from_value(real_fun_ptr)) closure_args.insert(0, ctx_ptr) value = codegen_call_or_invoke(node, target_def, nil, real_fun, closure_args, true, target_def.type, true, proc_type) @@ -1075,7 +1075,7 @@ class Crystal::CodeGenVisitor end null_fun_llvm_type = LLVM::Type.function(null_fun_types, null_fun_return_type) - null_fun_ptr = bit_cast fun_ptr, null_fun_llvm_type.pointer + null_fun_ptr = pointer_cast fun_ptr, null_fun_llvm_type.pointer target_def.c_calling_convention = true {null_fun_ptr, null_fun_llvm_type, null_args} @@ -1140,7 +1140,7 @@ class Crystal::CodeGenVisitor def check_c_fun(type, value) if type.proc? - make_fun(type, bit_cast(value, llvm_context.void_pointer), llvm_context.void_pointer.null) + make_fun(type, cast_to_void_pointer(value), llvm_context.void_pointer.null) else value end diff --git a/src/compiler/crystal/codegen/unions.cr b/src/compiler/crystal/codegen/unions.cr index 33599d84fef0..1f29763d504a 100644 --- a/src/compiler/crystal/codegen/unions.cr +++ b/src/compiler/crystal/codegen/unions.cr @@ -90,7 +90,7 @@ module Crystal int_type = llvm_context.int((union_size * 8).to_i32) bool_as_extended_int = builder.zext(value, int_type) - casted_value_ptr = bit_cast(union_value(struct_type, union_pointer), int_type.pointer) + casted_value_ptr = pointer_cast(union_value(struct_type, union_pointer), int_type.pointer) store bool_as_extended_int, casted_value_ptr end @@ -100,7 +100,7 @@ module Crystal value = union_value_type.null store type_id(value, @program.nil), union_type_id(struct_type, union_pointer) - casted_value_ptr = bit_cast union_value(struct_type, union_pointer), union_value_type.pointer + casted_value_ptr = pointer_cast union_value(struct_type, union_pointer), union_value_type.pointer store value, casted_value_ptr end From 1e0df7dd5fb4e14b6e4297bb8524301ec08d9126 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 10 Mar 2023 05:49:22 +0800 Subject: [PATCH 0366/1551] Deprecate LLVM typed pointers (#13172) * Deprecate LLVM typed pointers * don't remove the funs yet * fixup --- src/llvm/builder.cr | 8 ++++++++ src/llvm/function.cr | 7 +++++-- src/llvm/parameter_collection.cr | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index 4d333dfb830f..afca15cbe109 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -49,6 +49,7 @@ class LLVM::Builder Value.new phi_node end + @[Deprecated("Pass the function type of `func` as well (equal to `func.function_type`) in order to support LLVM 15+")] def call(func : LLVM::Function, name : String = "") # check_func(func) @@ -66,6 +67,7 @@ class LLVM::Builder Value.new LibLLVM.build_call2(self, type, func, nil, 0, name) end + @[Deprecated("Pass the function type of `func` as well (equal to `func.function_type`) in order to support LLVM 15+")] def call(func : LLVM::Function, arg : LLVM::Value, name : String = "") # check_func(func) # check_value(arg) @@ -87,6 +89,7 @@ class LLVM::Builder Value.new LibLLVM.build_call2(self, type, func, pointerof(value), 1, name) end + @[Deprecated("Pass the function type of `func` as well (equal to `func.function_type`) in order to support LLVM 15+")] def call(func : LLVM::Function, args : Array(LLVM::Value), name : String = "", bundle : LLVM::OperandBundleDef = LLVM::OperandBundleDef.null) # check_func(func) # check_values(args) @@ -115,6 +118,7 @@ class LLVM::Builder Value.new LibLLVM.build_store(self, value, ptr) end + @[Deprecated("Pass the pointee of `ptr` as well (equal to `ptr.type.element_type`) in order to support LLVM 15+")] def load(ptr : LLVM::Value, name = "") # check_value(ptr) @@ -149,6 +153,7 @@ class LLVM::Builder end {% for method_name in %w(gep inbounds_gep) %} + @[Deprecated("Pass the type of `value` as well (equal to `value.type`) in order to support LLVM 15+")] def {{method_name.id}}(value : LLVM::Value, indices : Array(LLVM::ValueRef), name = "") # check_value(value) @@ -170,6 +175,7 @@ class LLVM::Builder \{% end %} end + @[Deprecated("Pass the type of `value` as well (equal to `value.type`) in order to support LLVM 15+")] def {{method_name.id}}(value : LLVM::Value, index : LLVM::Value, name = "") # check_value(value) @@ -193,6 +199,7 @@ class LLVM::Builder \{% end %} end + @[Deprecated("Pass the type of `value` as well (equal to `value.type`) in order to support LLVM 15+")] def {{method_name.id}}(value : LLVM::Value, index1 : LLVM::Value, index2 : LLVM::Value, name = "") # check_value(value) @@ -307,6 +314,7 @@ class LLVM::Builder LibLLVMExt.build_catch_ret(self, pad, basic_block) end + @[Deprecated("Pass the function type of `fn` as well (equal to `fn.function_type`) in order to support LLVM 15+")] def invoke(fn : LLVM::Function, args : Array(LLVM::Value), a_then, a_catch, bundle : LLVM::OperandBundleDef = LLVM::OperandBundleDef.null, name = "") # check_func(fn) diff --git a/src/llvm/function.cr b/src/llvm/function.cr index d2bdfa867eba..e3580b11d67c 100644 --- a/src/llvm/function.cr +++ b/src/llvm/function.cr @@ -78,16 +78,19 @@ struct LLVM::Function {% end %} end + @[Deprecated] def function_type Type.new LibLLVM.get_element_type(LibLLVM.type_of(self)) end + @[Deprecated] def return_type - function_type.return_type + Type.new(LibLLVM.get_element_type(LibLLVM.type_of(self))).return_type end + @[Deprecated] def varargs? - function_type.varargs? + Type.new(LibLLVM.get_element_type(LibLLVM.type_of(self))).varargs? end def params diff --git a/src/llvm/parameter_collection.cr b/src/llvm/parameter_collection.cr index cd1a47a3ebc8..937d9ec2edcc 100644 --- a/src/llvm/parameter_collection.cr +++ b/src/llvm/parameter_collection.cr @@ -21,6 +21,6 @@ struct LLVM::ParameterCollection end def types - @function.function_type.params_types + to_a.map(&.type) end end From 4b3954471c8aa6e208cd5583605e240bc2980fb9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 10 Mar 2023 06:00:17 +0800 Subject: [PATCH 0367/1551] remove `LibLLVM::IS_LT_80` checks --- src/llvm/builder.cr | 48 ++++++++------------------------------------ src/llvm/lib_llvm.cr | 21 +++---------------- 2 files changed, 11 insertions(+), 58 deletions(-) diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index b322d61a6822..2b254f991d60 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -114,22 +114,14 @@ class LLVM::Builder def load(ptr : LLVM::Value, name = "") # check_value(ptr) - {% if LibLLVM::IS_LT_80 %} - Value.new LibLLVM.build_load(self, ptr, name) - {% else %} - Value.new LibLLVM.build_load2(self, ptr.type.element_type, ptr, name) - {% end %} + Value.new LibLLVM.build_load2(self, ptr.type.element_type, ptr, name) end def load(type : LLVM::Type, ptr : LLVM::Value, name = "") # check_type("load", type) # check_value(ptr) - {% if LibLLVM::IS_LT_80 %} - Value.new LibLLVM.build_load(self, ptr, name) - {% else %} - Value.new LibLLVM.build_load2(self, type, ptr, name) - {% end %} + Value.new LibLLVM.build_load2(self, type, ptr, name) end def store_volatile(value, ptr) @@ -149,22 +141,14 @@ class LLVM::Builder def {{method_name.id}}(value : LLVM::Value, indices : Array(LLVM::ValueRef), name = "") # check_value(value) - \{% if LibLLVM::IS_LT_80 %} - Value.new LibLLVM.build_{{method_name.id}}(self, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size, name) - \{% else %} - Value.new LibLLVM.build_{{method_name.id}}2(self, value.type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size, name) - \{% end %} + Value.new LibLLVM.build_{{method_name.id}}2(self, value.type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size, name) end def {{method_name.id}}(type : LLVM::Type, value : LLVM::Value, indices : Array(LLVM::ValueRef), name = "") # check_type({{method_name}}, type) # check_value(value) - \{% if LibLLVM::IS_LT_80 %} - Value.new LibLLVM.build_{{method_name.id}}(self, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size, name) - \{% else %} - Value.new LibLLVM.build_{{method_name.id}}2(self, type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size, name) - \{% end %} + Value.new LibLLVM.build_{{method_name.id}}2(self, type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size, name) end @[Deprecated("Pass the type of `value` as well (equal to `value.type`) in order to support LLVM 15+")] @@ -172,11 +156,7 @@ class LLVM::Builder # check_value(value) indices = pointerof(index).as(LibLLVM::ValueRef*) - \{% if LibLLVM::IS_LT_80 %} - Value.new LibLLVM.build_{{method_name.id}}(self, value, indices, 1, name) - \{% else %} - Value.new LibLLVM.build_{{method_name.id}}2(self, value.type, value, indices, 1, name) - \{% end %} + Value.new LibLLVM.build_{{method_name.id}}2(self, value.type, value, indices, 1, name) end def {{method_name.id}}(type : LLVM::Type, value : LLVM::Value, index : LLVM::Value, name = "") @@ -184,11 +164,7 @@ class LLVM::Builder # check_value(value) indices = pointerof(index).as(LibLLVM::ValueRef*) - \{% if LibLLVM::IS_LT_80 %} - Value.new LibLLVM.build_{{method_name.id}}(self, value, indices, 1, name) - \{% else %} - Value.new LibLLVM.build_{{method_name.id}}2(self, type, value, indices, 1, name) - \{% end %} + Value.new LibLLVM.build_{{method_name.id}}2(self, type, value, indices, 1, name) end @[Deprecated("Pass the type of `value` as well (equal to `value.type`) in order to support LLVM 15+")] @@ -198,11 +174,7 @@ class LLVM::Builder indices = uninitialized LLVM::Value[2] indices[0] = index1 indices[1] = index2 - \{% if LibLLVM::IS_LT_80 %} - Value.new LibLLVM.build_{{method_name.id}}(self, value, indices.to_unsafe.as(LibLLVM::ValueRef*), 2, name) - \{% else %} - Value.new LibLLVM.build_{{method_name.id}}2(self, value.type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), 2, name) - \{% end %} + Value.new LibLLVM.build_{{method_name.id}}2(self, value.type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), 2, name) end def {{method_name.id}}(type : LLVM::Type, value : LLVM::Value, index1 : LLVM::Value, index2 : LLVM::Value, name = "") @@ -212,11 +184,7 @@ class LLVM::Builder indices = uninitialized LLVM::Value[2] indices[0] = index1 indices[1] = index2 - \{% if LibLLVM::IS_LT_80 %} - Value.new LibLLVM.build_{{method_name.id}}(self, value, indices.to_unsafe.as(LibLLVM::ValueRef*), 2, name) - \{% else %} - Value.new LibLLVM.build_{{method_name.id}}2(self, type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), 2, name) - \{% end %} + Value.new LibLLVM.build_{{method_name.id}}2(self, type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), 2, name) end {% end %} diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 6b1e28fcaa39..0393410724f8 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -98,10 +98,6 @@ lib LibLLVM fun build_atomicrmw = LLVMBuildAtomicRMW(builder : BuilderRef, op : LLVM::AtomicRMWBinOp, ptr : ValueRef, val : ValueRef, ordering : LLVM::AtomicOrdering, singlethread : Int32) : ValueRef fun build_bit_cast = LLVMBuildBitCast(builder : BuilderRef, value : ValueRef, type : TypeRef, name : UInt8*) : ValueRef fun build_br = LLVMBuildBr(builder : BuilderRef, block : BasicBlockRef) : ValueRef - {% if LibLLVM::IS_LT_110 %} - # LLVMBuildCall is deprecated in favor of LLVMBuildCall2, in preparation for opaque pointer types. - fun build_call = LLVMBuildCall(builder : BuilderRef, fn : ValueRef, args : ValueRef*, num_args : Int32, name : UInt8*) : ValueRef - {% end %} fun build_call2 = LLVMBuildCall2(builder : BuilderRef, type : TypeRef, fn : ValueRef, args : ValueRef*, num_args : Int32, name : UInt8*) : ValueRef fun build_cond = LLVMBuildCondBr(builder : BuilderRef, if : ValueRef, then : BasicBlockRef, else : BasicBlockRef) : ValueRef fun build_exact_sdiv = LLVMBuildExactSDiv(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef @@ -116,25 +112,14 @@ lib LibLLVM fun build_fpext = LLVMBuildFPExt(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef fun build_fptrunc = LLVMBuildFPTrunc(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef fun build_fsub = LLVMBuildFSub(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_gep = LLVMBuildGEP(builder : BuilderRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef - fun build_inbounds_gep = LLVMBuildInBoundsGEP(builder : BuilderRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef - {% unless LibLLVM::IS_LT_80 %} - fun build_gep2 = LLVMBuildGEP2(builder : BuilderRef, ty : TypeRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef - fun build_inbounds_gep2 = LLVMBuildInBoundsGEP2(builder : BuilderRef, ty : TypeRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef - {% end %} + fun build_gep2 = LLVMBuildGEP2(builder : BuilderRef, ty : TypeRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef + fun build_inbounds_gep2 = LLVMBuildInBoundsGEP2(builder : BuilderRef, ty : TypeRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef fun build_global_string_ptr = LLVMBuildGlobalStringPtr(builder : BuilderRef, str : UInt8*, name : UInt8*) : ValueRef fun build_icmp = LLVMBuildICmp(builder : BuilderRef, op : LLVM::IntPredicate, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef fun build_int2ptr = LLVMBuildIntToPtr(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - {% if LibLLVM::IS_LT_110 %} - # LLVMBuildInvoke is deprecated in favor of LLVMBuildInvoke2, in preparation for opaque pointer types. - fun build_invoke = LLVMBuildInvoke(builder : BuilderRef, fn : ValueRef, args : ValueRef*, num_args : UInt32, then : BasicBlockRef, catch : BasicBlockRef, name : UInt8*) : ValueRef - {% end %} fun build_invoke2 = LLVMBuildInvoke2(builder : BuilderRef, ty : TypeRef, fn : ValueRef, args : ValueRef*, num_args : UInt32, then : BasicBlockRef, catch : BasicBlockRef, name : UInt8*) : ValueRef fun build_landing_pad = LLVMBuildLandingPad(builder : BuilderRef, ty : TypeRef, pers_fn : ValueRef, num_clauses : UInt32, name : UInt8*) : ValueRef - fun build_load = LLVMBuildLoad(builder : BuilderRef, ptr : ValueRef, name : UInt8*) : ValueRef - {% unless LibLLVM::IS_LT_80 %} - fun build_load2 = LLVMBuildLoad2(builder : BuilderRef, ty : TypeRef, ptr : ValueRef, name : UInt8*) : ValueRef - {% end %} + fun build_load2 = LLVMBuildLoad2(builder : BuilderRef, ty : TypeRef, ptr : ValueRef, name : UInt8*) : ValueRef fun build_lshr = LLVMBuildLShr(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef fun build_malloc = LLVMBuildMalloc(builder : BuilderRef, type : TypeRef, name : UInt8*) : ValueRef fun build_mul = LLVMBuildMul(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef From 14159b9bd79d9f2cf6b0ab2dc81a87e98b846157 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 10 Mar 2023 17:58:26 +0800 Subject: [PATCH 0368/1551] format --- src/compiler/crystal/codegen/codegen.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index c2efff51c7a7..562e52eaf1ff 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -1701,7 +1701,7 @@ module Crystal define_main_function(name, LLVM::Type.function(arg_types, return_type), needs_alloca) { |func| yield func } end - def define_main_function(name, type : LLVM::Type, needs_alloca : Bool = false) + def define_main_function(name, type : LLVM::Type, needs_alloca : Bool = false, &) if @llvm_mod != @main_mod raise "wrong usage of define_main_function: you must put it inside an `in_main` block" end From 9cdf71cdbed808c2fcec5936428f8e3d5a78bb1d Mon Sep 17 00:00:00 2001 From: Pan Gaoyong Date: Sat, 11 Mar 2023 16:54:55 +0800 Subject: [PATCH 0369/1551] Docs: Fix examples for `#byte_swap` with different int types (#13154) --- src/int.cr | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/int.cr b/src/int.cr index e3709e7f968b..dc89dadeaae2 100644 --- a/src/int.cr +++ b/src/int.cr @@ -911,8 +911,7 @@ struct Int8 # Has no effect on 8-bit integers. # # ``` - # 0x1234_u16.byte_swap # => 0x3412 - # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # 0x12_i8.byte_swap # => 0x12 # ``` def byte_swap : self self @@ -1012,8 +1011,7 @@ struct Int16 # Has no effect on 8-bit integers. # # ``` - # 0x1234_u16.byte_swap # => 0x3412 - # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # 0x1234_i16.byte_swap # => 0x3412 # ``` def byte_swap : self Intrinsics.bswap16(self).to_i16! @@ -1113,8 +1111,7 @@ struct Int32 # Has no effect on 8-bit integers. # # ``` - # 0x1234_u16.byte_swap # => 0x3412 - # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # 0x12345678_i32.byte_swap # => 0x78563412 # ``` def byte_swap : self Intrinsics.bswap32(self).to_i32! @@ -1214,8 +1211,8 @@ struct Int64 # Has no effect on 8-bit integers. # # ``` - # 0x1234_u16.byte_swap # => 0x3412 - # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # 0x12345678_i64.byte_swap # => 0x7856341200000000 + # 0x123456789ABCDEF0_i64.byte_swap # => -0xf21436587a9cbee # ``` def byte_swap : self Intrinsics.bswap64(self).to_i64! @@ -1317,8 +1314,7 @@ struct Int128 # Has no effect on 8-bit integers. # # ``` - # 0x1234_u16.byte_swap # => 0x3412 - # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # 0x123456789_i128.byte_swap # => -0x7698badcff0000000000000000000000 # ``` def byte_swap : self Intrinsics.bswap128(self).to_i128! @@ -1422,8 +1418,7 @@ struct UInt8 # Has no effect on 8-bit integers. # # ``` - # 0x1234_u16.byte_swap # => 0x3412 - # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # 0x12_u8.byte_swap # => 0x12 # ``` def byte_swap : self self @@ -1528,7 +1523,6 @@ struct UInt16 # # ``` # 0x1234_u16.byte_swap # => 0x3412 - # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 # ``` def byte_swap : self Intrinsics.bswap16(self) @@ -1632,8 +1626,7 @@ struct UInt32 # Has no effect on 8-bit integers. # # ``` - # 0x1234_u16.byte_swap # => 0x3412 - # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # 0x12345678_u32.byte_swap # => 0x78563412 # ``` def byte_swap : self Intrinsics.bswap32(self) @@ -1737,8 +1730,7 @@ struct UInt64 # Has no effect on 8-bit integers. # # ``` - # 0x1234_u16.byte_swap # => 0x3412 - # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # 0x123456789ABCDEF0_u64.byte_swap # => 0xF0DEBC9A78563412 # ``` def byte_swap : self Intrinsics.bswap64(self) @@ -1844,8 +1836,7 @@ struct UInt128 # Has no effect on 8-bit integers. # # ``` - # 0x1234_u16.byte_swap # => 0x3412 - # 0x5678ABCD_u32.byte_swap # => 0xCDAB7856 + # 0x123456789ABCDEF013579BDF2468ACE0_u128.byte_swap # => 0xE0AC6824DF9B5713F0DEBC9A78563412 # ``` def byte_swap : self Intrinsics.bswap128(self) From 81a6769aa9b612dd264f5a550d6a22f0b1bfbca7 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Sat, 11 Mar 2023 14:47:10 -0500 Subject: [PATCH 0370/1551] Fix formatting from #13154 (#13180) --- src/int.cr | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/int.cr b/src/int.cr index dc89dadeaae2..7dd84d9a0ed4 100644 --- a/src/int.cr +++ b/src/int.cr @@ -911,7 +911,7 @@ struct Int8 # Has no effect on 8-bit integers. # # ``` - # 0x12_i8.byte_swap # => 0x12 + # 0x12_i8.byte_swap # => 0x12 # ``` def byte_swap : self self @@ -1011,7 +1011,7 @@ struct Int16 # Has no effect on 8-bit integers. # # ``` - # 0x1234_i16.byte_swap # => 0x3412 + # 0x1234_i16.byte_swap # => 0x3412 # ``` def byte_swap : self Intrinsics.bswap16(self).to_i16! @@ -1111,7 +1111,7 @@ struct Int32 # Has no effect on 8-bit integers. # # ``` - # 0x12345678_i32.byte_swap # => 0x78563412 + # 0x12345678_i32.byte_swap # => 0x78563412 # ``` def byte_swap : self Intrinsics.bswap32(self).to_i32! @@ -1314,7 +1314,7 @@ struct Int128 # Has no effect on 8-bit integers. # # ``` - # 0x123456789_i128.byte_swap # => -0x7698badcff0000000000000000000000 + # 0x123456789_i128.byte_swap # => -0x7698badcff0000000000000000000000 # ``` def byte_swap : self Intrinsics.bswap128(self).to_i128! @@ -1418,7 +1418,7 @@ struct UInt8 # Has no effect on 8-bit integers. # # ``` - # 0x12_u8.byte_swap # => 0x12 + # 0x12_u8.byte_swap # => 0x12 # ``` def byte_swap : self self @@ -1522,7 +1522,7 @@ struct UInt16 # Has no effect on 8-bit integers. # # ``` - # 0x1234_u16.byte_swap # => 0x3412 + # 0x1234_u16.byte_swap # => 0x3412 # ``` def byte_swap : self Intrinsics.bswap16(self) @@ -1626,7 +1626,7 @@ struct UInt32 # Has no effect on 8-bit integers. # # ``` - # 0x12345678_u32.byte_swap # => 0x78563412 + # 0x12345678_u32.byte_swap # => 0x78563412 # ``` def byte_swap : self Intrinsics.bswap32(self) @@ -1836,7 +1836,7 @@ struct UInt128 # Has no effect on 8-bit integers. # # ``` - # 0x123456789ABCDEF013579BDF2468ACE0_u128.byte_swap # => 0xE0AC6824DF9B5713F0DEBC9A78563412 + # 0x123456789ABCDEF013579BDF2468ACE0_u128.byte_swap # => 0xE0AC6824DF9B5713F0DEBC9A78563412 # ``` def byte_swap : self Intrinsics.bswap128(self) From 03e70e25a7e301f4cddd04bc8ebdfb7b3f2e66f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 11:24:22 +0100 Subject: [PATCH 0371/1551] Update GH Actions (#13132) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/linux.yml | 2 +- .github/workflows/macos.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 78f64473020e..4ff1d051ff10 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -181,7 +181,7 @@ jobs: run: echo $GITHUB_SHA > ./docs/revision.txt - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c0c02b5c9969..c8fa93fe9bc2 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -13,7 +13,7 @@ jobs: - name: Download Crystal source uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v19 + - uses: cachix/install-nix-action@v20 with: install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | From b992e0b32ee72255135372a7e06e2d218fe4187d Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Mon, 13 Mar 2023 06:25:39 -0400 Subject: [PATCH 0372/1551] Fix `Socket#tty?` to `false` on Windows (#13175) --- spec/std/socket/socket_spec.cr | 6 ++++++ src/crystal/system/win32/socket.cr | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index 0049a564a96e..23b7229db88d 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -19,6 +19,12 @@ describe Socket, tags: "network" do end end + describe "#tty?" do + it "with non TTY" do + Socket.new(Socket::Family::INET, Socket::Type::STREAM, Socket::Protocol::TCP).tty?.should be_false + end + end + pending_win32 ".accept" do client_done = Channel(Nil).new server = Socket.new(Socket::Family::INET, Socket::Type::STREAM, Socket::Protocol::TCP) diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 339f28648200..41f657e21ef5 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -335,7 +335,7 @@ module Crystal::System::Socket end private def system_tty? - LibC.isatty(fd) == 1 + false end private def unbuffered_read(slice : Bytes) From 3a360b1bfc966474c2e9f21594c7b4030d29ac73 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Mon, 13 Mar 2023 06:26:31 -0400 Subject: [PATCH 0373/1551] [CI] Cancel in-progress jobs when another commit is pushed (#13179) --- .github/workflows/aarch64.yml | 4 ++++ .github/workflows/linux.yml | 4 ++++ .github/workflows/macos.yml | 4 ++++ .github/workflows/openssl.yml | 4 ++++ .github/workflows/regex-engine.yml | 4 ++++ .github/workflows/smoke.yml | 4 ++++ .github/workflows/wasm32.yml | 4 ++++ .github/workflows/win.yml | 4 ++++ 8 files changed, 32 insertions(+) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index 729a016130d5..9d699a1098f5 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -2,6 +2,10 @@ name: AArch64 CI on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: aarch64-musl-build: runs-on: [linux, ARM64] diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4ff1d051ff10..2ab70c8c7dc2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -2,6 +2,10 @@ name: Linux CI on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: TRAVIS_OS_NAME: linux SPEC_SPLIT_DOTS: 160 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c8fa93fe9bc2..bf3d85df4277 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -2,6 +2,10 @@ name: macOS CI on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: SPEC_SPLIT_DOTS: 160 CI_NIX_SHELL: true diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 6a0b5265e293..516106a9e53d 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -2,6 +2,10 @@ name: OpenSSL CI on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: openssl3: runs-on: ubuntu-latest diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index ac3ac7c52fb0..a0e77180993f 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -2,6 +2,10 @@ name: Regex Engine CI on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: pcre: runs-on: ubuntu-latest diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 56fde78bcddf..4c457f991adf 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -30,6 +30,10 @@ name: Smoke tests on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: TRAVIS_OS_NAME: linux ARCH: x86_64 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 6642de2b24b7..fcdaec6179d7 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -2,6 +2,10 @@ name: WebAssembly CI on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: SPEC_SPLIT_DOTS: 160 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 0b93e89f0b38..649af6315e1e 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -2,6 +2,10 @@ name: Windows CI on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: x86_64-linux-job: runs-on: ubuntu-latest From 766a64dea17a14bf3dca7e1f6691dbe972d1c3aa Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 15 Mar 2023 16:51:55 +0800 Subject: [PATCH 0374/1551] Remove obsolete functions from `llvm_ext.cc` (#13177) --- src/llvm.cr | 2 +- src/llvm/basic_block.cr | 2 +- src/llvm/builder.cr | 12 ++--- src/llvm/ext/llvm_ext.cc | 107 ++------------------------------------ src/llvm/function.cr | 7 +-- src/llvm/lib_llvm.cr | 12 ++++- src/llvm/lib_llvm_ext.cr | 30 ----------- src/llvm/value_methods.cr | 20 ++++--- 8 files changed, 37 insertions(+), 155 deletions(-) diff --git a/src/llvm.cr b/src/llvm.cr index 5752ade47c1a..12106d9e05d3 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -108,7 +108,7 @@ module LLVM end def self.normalize_triple(triple : String) : String - normalized = LibLLVMExt.normalize_target_triple(triple) + normalized = LibLLVM.normalize_target_triple(triple) normalized = LLVM.string_and_dispose(normalized) normalized diff --git a/src/llvm/basic_block.cr b/src/llvm/basic_block.cr index 973f6e38483b..47511320fc82 100644 --- a/src/llvm/basic_block.cr +++ b/src/llvm/basic_block.cr @@ -20,6 +20,6 @@ struct LLVM::BasicBlock def name block_name = LibLLVM.get_basic_block_name(self) - block_name ? LLVM.string_and_dispose(block_name) : nil + block_name ? String.new(block_name) : nil end end diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index 2b254f991d60..3194f880c215 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -255,15 +255,15 @@ class LLVM::Builder end def catch_switch(parent_pad, basic_block, num_handlers, name = "") - Value.new LibLLVMExt.build_catch_switch(self, parent_pad, basic_block, num_handlers, name) + Value.new LibLLVM.build_catch_switch(self, parent_pad, basic_block, num_handlers, name) end def catch_pad(parent_pad, args : Array(LLVM::Value), name = "") - Value.new LibLLVMExt.build_catch_pad(self, parent_pad, args.size, args.to_unsafe.as(LibLLVM::ValueRef*), name) + Value.new LibLLVM.build_catch_pad(self, parent_pad, args.to_unsafe.as(LibLLVM::ValueRef*), args.size, name) end def add_handler(catch_switch_ref, handler) - LibLLVMExt.add_handler catch_switch_ref, handler + LibLLVM.add_handler catch_switch_ref, handler end def build_operand_bundle_def(name, values : Array(LLVM::Value)) @@ -271,7 +271,7 @@ class LLVM::Builder end def build_catch_ret(pad, basic_block) - LibLLVMExt.build_catch_ret(self, pad, basic_block) + LibLLVM.build_catch_ret(self, pad, basic_block) end @[Deprecated("Pass the function type of `fn` as well (equal to `fn.function_type`) in order to support LLVM 15+")] @@ -302,8 +302,8 @@ class LLVM::Builder Value.new LibLLVM.build_atomicrmw(self, op, ptr, val, ordering, singlethread ? 1 : 0) end - def cmpxchg(pointer, cmp, new, success_ordering, failure_ordering) - Value.new LibLLVMExt.build_cmpxchg(self, pointer, cmp, new, success_ordering, failure_ordering) + def cmpxchg(pointer, cmp, new, success_ordering, failure_ordering, singlethread : Bool = false) + Value.new LibLLVM.build_atomic_cmp_xchg(self, pointer, cmp, new, success_ordering, failure_ordering, singlethread ? 1 : 0) end def fence(ordering, singlethread, name = "") diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc index 0f38b389f802..77054025dad9 100644 --- a/src/llvm/ext/llvm_ext.cc +++ b/src/llvm/ext/llvm_ext.cc @@ -1,17 +1,12 @@ -#include "llvm/IR/DIBuilder.h" -#include "llvm/IR/IRBuilder.h" -#include "llvm/IR/Module.h" -#include "llvm/Support/CBindingWrapping.h" -#include +#include +#include +#include +#include #include #include #include #include #include -#include -#include -#include -#include #include #include @@ -27,7 +22,6 @@ using namespace llvm; (LLVM_VERSION_MAJOR < (major) || LLVM_VERSION_MAJOR == (major) && LLVM_VERSION_MINOR <= (minor)) #include -#include #include #include @@ -243,20 +237,6 @@ LLVMMetadataRef LLVMExtDIBuilderCreatePointerType( return wrap(T); } -LLVMMetadataRef LLVMTemporaryMDNode2( - LLVMContextRef C, LLVMMetadataRef *MDs, unsigned Count) { - return wrap(MDTuple::getTemporary(*unwrap(C), - ArrayRef(unwrap(MDs), Count)) - .release()); -} - -void LLVMMetadataReplaceAllUsesWith2( - LLVMMetadataRef MD, LLVMMetadataRef New) { - auto *Node = unwrap(MD); - Node->replaceAllUsesWith(unwrap(New)); - MDNode::deleteTemporary(Node); -} - void LLVMExtSetCurrentDebugLocation( LLVMBuilderRef Bref, unsigned Line, unsigned Col, LLVMMetadataRef Scope, LLVMMetadataRef InlinedAt) { @@ -275,80 +255,6 @@ void LLVMExtSetCurrentDebugLocation( #endif } -#if LLVM_VERSION_LE(13, 0) -// A backported LLVMCreateTypeAttribute for LLVM < 13 -// from https://github.com/llvm/llvm-project/blob/bb8ce25e88218be60d2a4ea9c9b0b721809eff27/llvm/lib/IR/Core.cpp#L167 -LLVMAttributeRef LLVMExtCreateTypeAttribute( - LLVMContextRef C, unsigned KindID, LLVMTypeRef Ty) { - auto &Ctx = *unwrap(C); - auto AttrKind = (Attribute::AttrKind)KindID; -#if LLVM_VERSION_GE(12, 0) - return wrap(Attribute::get(Ctx, AttrKind, unwrap(Ty))); -#else - return wrap(Attribute::get(Ctx, AttrKind)); -#endif -} -#endif - -LLVMValueRef LLVMExtBuildCmpxchg( - LLVMBuilderRef B, LLVMValueRef PTR, LLVMValueRef Cmp, LLVMValueRef New, - LLVMAtomicOrdering SuccessOrdering, LLVMAtomicOrdering FailureOrdering) { -#if LLVM_VERSION_GE(13, 0) - return wrap( - unwrap(B)->CreateAtomicCmpXchg( - unwrap(PTR), - unwrap(Cmp), - unwrap(New), - llvm::MaybeAlign(), - (llvm::AtomicOrdering)SuccessOrdering, - (llvm::AtomicOrdering)FailureOrdering - ) - ); -#else - return wrap(unwrap(B)->CreateAtomicCmpXchg(unwrap(PTR), unwrap(Cmp), unwrap(New), - (llvm::AtomicOrdering)SuccessOrdering, (llvm::AtomicOrdering)FailureOrdering)); -#endif -} - -void LLVMExtSetOrdering(LLVMValueRef MemAccessInst, LLVMAtomicOrdering Ordering) { - Value *P = unwrap(MemAccessInst); - AtomicOrdering O = (AtomicOrdering) Ordering; - - if (LoadInst *LI = dyn_cast(P)) - return LI->setOrdering(O); - return cast(P)->setOrdering(O); -} - -LLVMValueRef LLVMExtBuildCatchPad( - LLVMBuilderRef B, LLVMValueRef ParentPad, unsigned ArgCount, - LLVMValueRef *LLArgs, const char *Name) { - Value **Args = unwrap(LLArgs); - return wrap(unwrap(B)->CreateCatchPad( - unwrap(ParentPad), ArrayRef(Args, ArgCount), Name)); -} - -LLVMValueRef LLVMExtBuildCatchRet( - LLVMBuilderRef B, LLVMValueRef Pad, LLVMBasicBlockRef BB) { - return wrap(unwrap(B)->CreateCatchRet(cast(unwrap(Pad)), - unwrap(BB))); -} - -LLVMValueRef LLVMExtBuildCatchSwitch( - LLVMBuilderRef B, LLVMValueRef ParentPad, LLVMBasicBlockRef BB, - unsigned NumHandlers, const char *Name) { - if (ParentPad == nullptr) { - Type *Ty = Type::getTokenTy(unwrap(B)->getContext()); - ParentPad = wrap(Constant::getNullValue(Ty)); - } - return wrap(unwrap(B)->CreateCatchSwitch(unwrap(ParentPad), unwrap(BB), - NumHandlers, Name)); -} - -void LLVMExtAddHandler(LLVMValueRef CatchSwitchRef, LLVMBasicBlockRef Handler) { - Value *CatchSwitch = unwrap(CatchSwitchRef); - cast(CatchSwitch)->addHandler(unwrap(Handler)); -} - OperandBundleDef *LLVMExtBuildOperandBundleDef( const char *Name, LLVMValueRef *Inputs, unsigned NumInputs) { return new OperandBundleDef(Name, makeArrayRef(unwrap(Inputs), NumInputs)); @@ -390,11 +296,6 @@ void LLVMExtWriteBitcodeWithSummaryToFile(LLVMModuleRef mref, const char *File) llvm::WriteBitcodeToFile(*m, OS, true, &moduleSummaryIndex, true); } -// Missing LLVMNormalizeTargetTriple in LLVM <= 7.0 -char *LLVMExtNormalizeTargetTriple(const char* triple) { - return strdup(Triple::normalize(StringRef(triple)).c_str()); -} - static TargetMachine *unwrap(LLVMTargetMachineRef P) { return reinterpret_cast(P); } diff --git a/src/llvm/function.cr b/src/llvm/function.cr index 1b4d803fc08f..a586cd6fdde5 100644 --- a/src/llvm/function.cr +++ b/src/llvm/function.cr @@ -24,12 +24,7 @@ struct LLVM::Function context = LibLLVM.get_module_context(LibLLVM.get_global_parent(self)) attribute.each_kind do |kind| - if type && LLVM::Attribute.requires_type?(kind) - attribute_ref = LibLLVMExt.create_type_attribute(context, kind, type) - else - attribute_ref = LibLLVM.create_enum_attribute(context, kind, 0) - end - LibLLVM.add_attribute_at_index(self, index, attribute_ref) + LibLLVM.add_attribute_at_index(self, index, attribute_ref(context, kind, type)) end end diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 0393410724f8..dc491c536db2 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -84,6 +84,7 @@ lib LibLLVM fun add_clause = LLVMAddClause(lpad : ValueRef, clause_val : ValueRef) fun add_function = LLVMAddFunction(module : ModuleRef, name : UInt8*, type : TypeRef) : ValueRef fun add_global = LLVMAddGlobal(module : ModuleRef, type : TypeRef, name : UInt8*) : ValueRef + fun add_handler = LLVMAddHandler(catch_switch : ValueRef, dest : BasicBlockRef) fun add_incoming = LLVMAddIncoming(phi_node : ValueRef, incoming_values : ValueRef*, incoming_blocks : BasicBlockRef*, count : Int32) fun add_module_flag = LLVMAddModuleFlag(mod : ModuleRef, behavior : ModuleFlagBehavior, key : UInt8*, len : LibC::SizeT, val : MetadataRef) fun add_target_dependent_function_attr = LLVMAddTargetDependentFunctionAttr(fn : ValueRef, a : LibC::Char*, v : LibC::Char*) @@ -96,9 +97,13 @@ lib LibLLVM fun build_array_malloc = LLVMBuildArrayMalloc(builder : BuilderRef, type : TypeRef, val : ValueRef, name : UInt8*) : ValueRef fun build_ashr = LLVMBuildAShr(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef fun build_atomicrmw = LLVMBuildAtomicRMW(builder : BuilderRef, op : LLVM::AtomicRMWBinOp, ptr : ValueRef, val : ValueRef, ordering : LLVM::AtomicOrdering, singlethread : Int32) : ValueRef + fun build_atomic_cmp_xchg = LLVMBuildAtomicCmpXchg(builder : BuilderRef, ptr : ValueRef, cmp : ValueRef, new : ValueRef, success_ordering : LLVM::AtomicOrdering, failure_ordering : LLVM::AtomicOrdering, single_thread : Int) : ValueRef fun build_bit_cast = LLVMBuildBitCast(builder : BuilderRef, value : ValueRef, type : TypeRef, name : UInt8*) : ValueRef fun build_br = LLVMBuildBr(builder : BuilderRef, block : BasicBlockRef) : ValueRef fun build_call2 = LLVMBuildCall2(builder : BuilderRef, type : TypeRef, fn : ValueRef, args : ValueRef*, num_args : Int32, name : UInt8*) : ValueRef + fun build_catch_pad = LLVMBuildCatchPad(b : BuilderRef, parent_pad : ValueRef, args : ValueRef*, num_args : UInt, name : Char*) : ValueRef + fun build_catch_ret = LLVMBuildCatchRet(b : BuilderRef, catch_pad : ValueRef, bb : BasicBlockRef) : ValueRef + fun build_catch_switch = LLVMBuildCatchSwitch(b : BuilderRef, parent_pad : ValueRef, unwind_bb : BasicBlockRef, num_handlers : UInt, name : Char*) : ValueRef fun build_cond = LLVMBuildCondBr(builder : BuilderRef, if : ValueRef, then : BasicBlockRef, else : BasicBlockRef) : ValueRef fun build_exact_sdiv = LLVMBuildExactSDiv(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef fun build_extract_value = LLVMBuildExtractValue(builder : BuilderRef, agg_val : ValueRef, index : UInt32, name : UInt8*) : ValueRef @@ -160,6 +165,9 @@ lib LibLLVM fun create_jit_compiler_for_module = LLVMCreateJITCompilerForModule(jit : ExecutionEngineRef*, m : ModuleRef, opt_level : Int32, error : UInt8**) : Int32 fun create_mc_jit_compiler_for_module = LLVMCreateMCJITCompilerForModule(jit : ExecutionEngineRef*, m : ModuleRef, options : JITCompilerOptions*, options_length : UInt32, error : UInt8**) : Int32 fun create_target_machine = LLVMCreateTargetMachine(target : TargetRef, triple : UInt8*, cpu : UInt8*, features : UInt8*, level : LLVM::CodeGenOptLevel, reloc : LLVM::RelocMode, code_model : LLVM::CodeModel) : TargetMachineRef + {% unless LibLLVM::IS_LT_120 %} + fun create_type_attribute = LLVMCreateTypeAttribute(ctx : ContextRef, kind_id : UInt, ty : TypeRef) : AttributeRef + {% end %} fun delete_basic_block = LLVMDeleteBasicBlock(block : BasicBlockRef) fun delete_function = LLVMDeleteFunction(fn : ValueRef) fun dispose_message = LLVMDisposeMessage(msg : UInt8*) @@ -170,7 +178,7 @@ lib LibLLVM fun generic_value_to_float = LLVMGenericValueToFloat(type : TypeRef, value : GenericValueRef) : Float64 fun generic_value_to_int = LLVMGenericValueToInt(value : GenericValueRef, signed : Int32) : UInt64 fun generic_value_to_pointer = LLVMGenericValueToPointer(value : GenericValueRef) : Void* - fun get_basic_block_name = LLVMGetBasicBlockName(basic_block : LibLLVM::BasicBlockRef) : Char* + fun get_basic_block_name = LLVMGetBasicBlockName(basic_block : BasicBlockRef) : Char* fun get_current_debug_location = LLVMGetCurrentDebugLocation(builder : BuilderRef) : ValueRef fun get_first_instruction = LLVMGetFirstInstruction(block : BasicBlockRef) : ValueRef fun get_first_target = LLVMGetFirstTarget : TargetRef @@ -189,6 +197,7 @@ lib LibLLVM fun get_target_description = LLVMGetTargetDescription(target : TargetRef) : UInt8* fun get_target_machine_triple = LLVMGetTargetMachineTriple(t : TargetMachineRef) : UInt8* fun get_target_from_triple = LLVMGetTargetFromTriple(triple : UInt8*, target : TargetRef*, error_message : UInt8**) : Int32 + fun normalize_target_triple = LLVMNormalizeTargetTriple(triple : Char*) : Char* fun get_type_kind = LLVMGetTypeKind(ty : TypeRef) : LLVM::Type::Kind fun get_undef = LLVMGetUndef(ty : TypeRef) : ValueRef fun get_value_name = LLVMGetValueName(value : ValueRef) : UInt8* @@ -288,6 +297,7 @@ lib LibLLVM fun set_function_call_convention = LLVMSetFunctionCallConv(fn : ValueRef, cc : LLVM::CallConvention) fun set_instruction_call_convention = LLVMSetInstructionCallConv(instr : ValueRef, cc : LLVM::CallConvention) fun get_instruction_call_convention = LLVMGetInstructionCallConv(instr : ValueRef) : LLVM::CallConvention + fun set_ordering = LLVMSetOrdering(memory_access_inst : ValueRef, ordering : LLVM::AtomicOrdering) fun get_int_type_width = LLVMGetIntTypeWidth(ty : TypeRef) : UInt32 fun is_packed_struct = LLVMIsPackedStruct(ty : TypeRef) : Int32 fun get_struct_name = LLVMGetStructName(ty : TypeRef) : UInt8* diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr index 5d484b06d665..c99656f5e81c 100644 --- a/src/llvm/lib_llvm_ext.cr +++ b/src/llvm/lib_llvm_ext.cr @@ -117,28 +117,6 @@ lib LibLLVMExt fun set_current_debug_location = LLVMExtSetCurrentDebugLocation(LibLLVM::BuilderRef, Int, Int, LibLLVM::MetadataRef, LibLLVM::MetadataRef) - fun build_cmpxchg = LLVMExtBuildCmpxchg(builder : LibLLVM::BuilderRef, pointer : LibLLVM::ValueRef, cmp : LibLLVM::ValueRef, new : LibLLVM::ValueRef, success_ordering : LLVM::AtomicOrdering, failure_ordering : LLVM::AtomicOrdering) : LibLLVM::ValueRef - fun set_ordering = LLVMExtSetOrdering(value : LibLLVM::ValueRef, ordering : LLVM::AtomicOrdering) - - fun build_catch_pad = LLVMExtBuildCatchPad(builder : LibLLVM::BuilderRef, - parent_pad : LibLLVM::ValueRef, - arg_count : LibC::UInt, - args : LibLLVM::ValueRef*, - name : LibC::Char*) : LibLLVM::ValueRef - - fun build_catch_ret = LLVMExtBuildCatchRet(builder : LibLLVM::BuilderRef, - pad : LibLLVM::ValueRef, - basic_block : LibLLVM::BasicBlockRef) : LibLLVM::ValueRef - - fun build_catch_switch = LLVMExtBuildCatchSwitch(builder : LibLLVM::BuilderRef, - parent_pad : LibLLVM::ValueRef, - basic_block : LibLLVM::BasicBlockRef, - num_handlers : LibC::UInt, - name : LibC::Char*) : LibLLVM::ValueRef - - fun add_handler = LLVMExtAddHandler(catch_switch_ref : LibLLVM::ValueRef, - handler : LibLLVM::BasicBlockRef) : Void - fun build_operand_bundle_def = LLVMExtBuildOperandBundleDef(name : LibC::Char*, input : LibLLVM::ValueRef*, num_input : LibC::UInt) : LibLLVMExt::OperandBundleDefRef @@ -156,16 +134,8 @@ lib LibLLVMExt fun write_bitcode_with_summary_to_file = LLVMExtWriteBitcodeWithSummaryToFile(module : LibLLVM::ModuleRef, path : UInt8*) : Void - fun normalize_target_triple = LLVMExtNormalizeTargetTriple(triple : Char*) : Char* fun di_builder_get_or_create_array_subrange = LLVMExtDIBuilderGetOrCreateArraySubrange(builder : DIBuilder, lo : UInt64, count : UInt64) : LibLLVM::MetadataRef fun target_machine_enable_global_isel = LLVMExtTargetMachineEnableGlobalIsel(machine : LibLLVM::TargetMachineRef, enable : Bool) fun create_mc_jit_compiler_for_module = LLVMExtCreateMCJITCompilerForModule(jit : LibLLVM::ExecutionEngineRef*, m : LibLLVM::ModuleRef, options : LibLLVM::JITCompilerOptions*, options_length : UInt32, enable_global_isel : Bool, error : UInt8**) : Int32 - - # LLVMCreateTypeAttribute is implemented in LLVM 13, but needed in 12 - {% if LibLLVM::IS_LT_130 %} - fun create_type_attribute = LLVMExtCreateTypeAttribute(ctx : LibLLVM::ContextRef, kind_id : LibC::UInt, ty : LibLLVM::TypeRef) : LibLLVM::AttributeRef - {% else %} - fun create_type_attribute = LLVMCreateTypeAttribute(ctx : LibLLVM::ContextRef, kind_id : LibC::UInt, ty : LibLLVM::TypeRef) : LibLLVM::AttributeRef - {% end %} end diff --git a/src/llvm/value_methods.cr b/src/llvm/value_methods.cr index 91fb55700ee0..e76daf3d53e0 100644 --- a/src/llvm/value_methods.cr +++ b/src/llvm/value_methods.cr @@ -18,13 +18,19 @@ module LLVM::ValueMethods return if attribute.value == 0 attribute.each_kind do |kind| - if type && LLVM::Attribute.requires_type?(kind) - attribute_ref = LibLLVMExt.create_type_attribute(context, kind, type) - else - attribute_ref = LibLLVM.create_enum_attribute(context, kind, 0) - end + LibLLVM.add_call_site_attribute(self, index, attribute_ref(context, kind, type)) + end + end - LibLLVM.add_call_site_attribute(self, index, attribute_ref) + private def attribute_ref(context, kind, type) + if type.is_a?(Type) && Attribute.requires_type?(kind) + {% if LibLLVM::IS_LT_120 %} + raise "Type arguments are only supported on LLVM 12.0 or above" + {% else %} + LibLLVM.create_type_attribute(context, kind, type) + {% end %} + else + LibLLVM.create_enum_attribute(context, kind, 0) end end @@ -86,7 +92,7 @@ module LLVM::ValueMethods end def ordering=(ordering) - LibLLVMExt.set_ordering(self, ordering) + LibLLVM.set_ordering(self, ordering) end def alignment=(bytes) From f5053377e14939dcaca0d4c7223f54f662826d5d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 15 Mar 2023 16:53:11 +0800 Subject: [PATCH 0375/1551] Implement `Process.ppid` on Windows (#13140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/process_spec.cr | 26 ++++++++++++++------- src/crystal/system/win32/process.cr | 26 +++++++++++++++++++-- src/lib_c/x86_64-windows-msvc/c/tlhelp32.cr | 20 ++++++++++++++++ src/process.cr | 4 ++++ 4 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 src/lib_c/x86_64-windows-msvc/c/tlhelp32.cr diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 6f4676064992..fff7d26169b4 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -358,12 +358,16 @@ describe Process do typeof(Process.new(*standing_command).terminate(graceful: false)) - pending_win32 ".exists?" do - # We can't reliably check whether it ever returns false, since we can't predict - # how PIDs are used by the system, a new process might be spawned in between - # reaping the one we would spawn and checking for it, using the now available - # pid. - Process.exists?(Process.ppid).should be_true + it ".exists?" do + # On Windows killing a parent process does not reparent its children to + # another existing process, so the following isn't guaranteed to work + {% unless flag?(:win32) %} + # We can't reliably check whether it ever returns false, since we can't predict + # how PIDs are used by the system, a new process might be spawned in between + # reaping the one we would spawn and checking for it, using the now available + # pid. + Process.exists?(Process.ppid).should be_true + {% end %} process = Process.new(*standing_command) process.exists?.should be_true @@ -371,8 +375,14 @@ describe Process do # Kill, zombie now process.terminate - process.exists?.should be_true - process.terminated?.should be_false + {% if flag?(:win32) %} + # Windows has no concept of zombie processes + process.exists?.should be_false + process.terminated?.should be_true + {% else %} + process.exists?.should be_true + process.terminated?.should be_false + {% end %} # Reap, gone now process.wait diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 83ae2a110f34..b887d8521c46 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -1,6 +1,7 @@ require "c/processthreadsapi" require "c/handleapi" require "c/synchapi" +require "c/tlhelp32" require "process/shell" require "crystal/atomic_semaphore" @@ -71,7 +72,28 @@ struct Crystal::System::Process end def self.ppid - raise NotImplementedError.new("Process.ppid") + pid = self.pid + each_process_entry do |pe| + return pe.th32ParentProcessID if pe.th32ProcessID == pid + end + raise RuntimeError.new("Cannot locate current process") + end + + private def self.each_process_entry(&) + h = LibC.CreateToolhelp32Snapshot(LibC::TH32CS_SNAPPROCESS, 0) + raise RuntimeError.from_winerror("CreateToolhelp32Snapshot") if h == LibC::INVALID_HANDLE_VALUE + + begin + pe = LibC::PROCESSENTRY32W.new(dwSize: sizeof(LibC::PROCESSENTRY32W)) + if LibC.Process32FirstW(h, pointerof(pe)) != 0 + while true + yield pe + break if LibC.Process32NextW(h, pointerof(pe)) == 0 + end + end + ensure + LibC.CloseHandle(h) + end end def self.signal(pid, signal) @@ -129,7 +151,7 @@ struct Crystal::System::Process def self.exists?(pid) handle = LibC.OpenProcess(LibC::PROCESS_QUERY_INFORMATION, 0, pid) - return false if handle.nil? + return false unless handle begin if LibC.GetExitCodeProcess(handle, out exit_code) == 0 raise RuntimeError.from_winerror("GetExitCodeProcess") diff --git a/src/lib_c/x86_64-windows-msvc/c/tlhelp32.cr b/src/lib_c/x86_64-windows-msvc/c/tlhelp32.cr new file mode 100644 index 000000000000..acfab00321f5 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/tlhelp32.cr @@ -0,0 +1,20 @@ +lib LibC + TH32CS_SNAPPROCESS = 0x00000002 + + struct PROCESSENTRY32W + dwSize : DWORD + cntUsage : DWORD + th32ProcessID : DWORD + th32DefaultHeapID : ULONG_PTR + th32ModuleID : DWORD + cntThreads : DWORD + th32ParentProcessID : DWORD + pcPriClassBase : LONG + dwFlags : DWORD + szExeFile : WCHAR[MAX_PATH] + end + + fun CreateToolhelp32Snapshot(dwFlags : DWORD, th32ProcessID : DWORD) : HANDLE + fun Process32FirstW(hSnapshot : HANDLE, lppe : PROCESSENTRY32W*) : BOOL + fun Process32NextW(hSnapshot : HANDLE, lppe : PROCESSENTRY32W*) : BOOL +end diff --git a/src/process.cr b/src/process.cr index 358511aebca5..ada6dd953b8f 100644 --- a/src/process.cr +++ b/src/process.cr @@ -37,6 +37,10 @@ class Process end # Returns the process identifier of the parent process of the current process. + # + # On Windows, the parent is associated only at process creation time, and the + # system does not re-parent the current process if the parent terminates; thus + # `Process.exists?(Process.ppid)` is not guaranteed to be true. def self.ppid : Int64 Crystal::System::Process.ppid.to_i64 end From 989499789ce1314b5d35f4e1f615bdf528cee49e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 15 Mar 2023 16:53:34 +0800 Subject: [PATCH 0376/1551] Improve `File.symlink` on Windows (#13141) Co-authored-by: Denis Maslennikov Co-authored-by: Beta Ziliani --- src/crystal/system/win32/file.cr | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 225a345723cc..56101a764cea 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -200,8 +200,25 @@ module Crystal::System::File end def self.symlink(old_path : String, new_path : String) : Nil - # TODO: support directory symlinks (copy Go's stdlib logic here) - if LibC.CreateSymbolicLinkW(System.to_wstr(new_path), System.to_wstr(old_path), LibC::SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) == 0 + win_old_path = System.to_wstr(old_path) + win_new_path = System.to_wstr(new_path) + info = info?(old_path, true) + flags = info.try(&.type.directory?) ? LibC::SYMBOLIC_LINK_FLAG_DIRECTORY : 0 + + # Symlink on Windows required the SeCreateSymbolicLink privilege. But in the Windows 10 + # Creators Update (1703), Microsoft added the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE + # flag, that allows creation symlink without SeCreateSymbolicLink privilege if the computer + # is in Developer Mode. + result = LibC.CreateSymbolicLinkW(win_new_path, win_old_path, flags | LibC::SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) + + # If we get an error like ERROR_INVALID_PARAMETER, it means that we have an + # older Windows. Retry without SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE + # flag. + if result == 0 && WinError.value == WinError::ERROR_INVALID_PARAMETER + result = LibC.CreateSymbolicLinkW(win_new_path, win_old_path, flags) + end + + if result == 0 raise ::File::Error.from_winerror("Error creating symlink", file: old_path, other: new_path) end end From 4898782d3d955233a05858880e082d9a9c19c88e Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Thu, 16 Mar 2023 08:11:00 -0400 Subject: [PATCH 0377/1551] Leverage `fileapi` for opening files on windows (#13178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/file_spec.cr | 9 +++ src/crystal/system/file.cr | 2 +- src/crystal/system/win32/file.cr | 80 +++++++++++++++++++++- src/lib_c/x86_64-windows-msvc/c/fileapi.cr | 21 +++++- src/lib_c/x86_64-windows-msvc/c/io.cr | 2 + src/lib_c/x86_64-windows-msvc/c/winnt.cr | 7 +- 6 files changed, 112 insertions(+), 9 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 104cf3e16186..8513b3bebacf 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -517,6 +517,15 @@ describe "File" do end end + it "deletes an open file" do + with_tempfile("delete-file.txt") do |filename| + file = File.open filename, "w" + File.exists?(file.path).should be_true + file.delete + File.exists?(file.path).should be_false + end + end + it "deletes? a file" do with_tempfile("delete-file.txt") do |filename| File.open(filename, "w") { } diff --git a/src/crystal/system/file.cr b/src/crystal/system/file.cr index 0f645fd8a3ec..89d29512c28e 100644 --- a/src/crystal/system/file.cr +++ b/src/crystal/system/file.cr @@ -81,7 +81,7 @@ module Crystal::System::File end private def self.error_is_file_exists?(errno) - Errno.value.in?(Errno::EEXIST, WinError::ERROR_ALREADY_EXISTS) + errno.in?(Errno::EEXIST, WinError::ERROR_ALREADY_EXISTS) end # Closes the internal file descriptor without notifying libevent. diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 56101a764cea..5c5842fcdb34 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -26,11 +26,85 @@ module Crystal::System::File end def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {LibC::Int, Errno} - flags |= LibC::O_BINARY | LibC::O_NOINHERIT + access, disposition, attributes = self.posix_to_open_opts flags, perm - fd = LibC._wopen(System.to_wstr(filename), flags, perm) + handle = LibC.CreateFileW( + System.to_wstr(filename), + access, + LibC::DEFAULT_SHARE_MODE, # UNIX semantics + nil, + disposition, + attributes, + LibC::HANDLE.null + ) + + if handle == LibC::INVALID_HANDLE_VALUE + # Map ERROR_FILE_EXISTS to Errno::EEXIST to avoid changing semantics of other systems + return {-1, WinError.value.error_file_exists? ? Errno::EEXIST : Errno.value} + end + + fd = LibC._open_osfhandle handle, flags + + if fd == -1 + return {-1, Errno.value} + end + + # Only binary mode is supported + LibC._setmode fd, LibC::O_BINARY + + {fd, Errno::NONE} + end + + private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions) + access = if flags.bits_set? LibC::O_WRONLY + LibC::GENERIC_WRITE + elsif flags.bits_set? LibC::O_RDWR + LibC::GENERIC_READ | LibC::GENERIC_WRITE + else + LibC::GENERIC_READ + end + + if flags.bits_set? LibC::O_APPEND + access |= LibC::FILE_APPEND_DATA + end + + if flags.bits_set? LibC::O_TRUNC + if flags.bits_set? LibC::O_CREAT + disposition = LibC::CREATE_ALWAYS + else + disposition = LibC::TRUNCATE_EXISTING + end + elsif flags.bits_set? LibC::O_CREAT + if flags.bits_set? LibC::O_EXCL + disposition = LibC::CREATE_NEW + else + disposition = LibC::OPEN_ALWAYS + end + else + disposition = LibC::OPEN_EXISTING + end + + attributes = LibC::FILE_ATTRIBUTE_NORMAL + unless perm.owner_write? + attributes |= LibC::FILE_ATTRIBUTE_READONLY + end + + if flags.bits_set? LibC::O_TEMPORARY + attributes |= LibC::FILE_FLAG_DELETE_ON_CLOSE | LibC::FILE_ATTRIBUTE_TEMPORARY + access |= LibC::DELETE + end + + if flags.bits_set? LibC::O_SHORT_LIVED + attributes |= LibC::FILE_ATTRIBUTE_TEMPORARY + end + + if flags.bits_set? LibC::O_SEQUENTIAL + attributes |= LibC::FILE_FLAG_SEQUENTIAL_SCAN + elsif flags.bits_set? LibC::O_RANDOM + attributes |= LibC::FILE_FLAG_RANDOM_ACCESS + end - {fd, fd == -1 ? Errno.value : Errno::NONE} + {access, disposition, attributes} end NOT_FOUND_ERRORS = { diff --git a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr index 701a9590ca22..9b88b5341c09 100644 --- a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr @@ -41,15 +41,30 @@ lib LibC fun SetFileAttributesW(lpFileName : LPWSTR, dwFileAttributes : DWORD) : BOOL fun GetFileAttributesExW(lpFileName : LPWSTR, fInfoLevelId : GET_FILEEX_INFO_LEVELS, lpFileInformation : Void*) : BOOL - OPEN_EXISTING = 3 + CREATE_NEW = 1 + CREATE_ALWAYS = 2 + OPEN_EXISTING = 3 + OPEN_ALWAYS = 4 + TRUNCATE_EXISTING = 5 - FILE_ATTRIBUTE_NORMAL = 0x80 - FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 + FILE_ATTRIBUTE_NORMAL = 0x80 + FILE_ATTRIBUTE_TEMPORARY = 0x100 + + FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 + FILE_FLAG_DELETE_ON_CLOSE = 0x04000000 + FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 + FILE_FLAG_RANDOM_ACCESS = 0x10000000 + FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000 FILE_SHARE_READ = 0x1 FILE_SHARE_WRITE = 0x2 FILE_SHARE_DELETE = 0x4 + DEFAULT_SHARE_MODE = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE + + GENERIC_READ = 0x80000000 + GENERIC_WRITE = 0x40000000 + fun CreateFileW(lpFileName : LPWSTR, dwDesiredAccess : DWORD, dwShareMode : DWORD, lpSecurityAttributes : SECURITY_ATTRIBUTES*, dwCreationDisposition : DWORD, dwFlagsAndAttributes : DWORD, hTemplateFile : HANDLE) : HANDLE diff --git a/src/lib_c/x86_64-windows-msvc/c/io.cr b/src/lib_c/x86_64-windows-msvc/c/io.cr index dc3dfd345523..94a5b7b582a3 100644 --- a/src/lib_c/x86_64-windows-msvc/c/io.cr +++ b/src/lib_c/x86_64-windows-msvc/c/io.cr @@ -16,4 +16,6 @@ lib LibC fun _pipe(pfds : Int*, psize : UInt, textmode : Int) : Int fun _dup2(fd1 : Int, fd2 : Int) : Int fun _commit(fd : Int) : Int + fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int + fun _setmode(fd : LibC::Int, mode : LibC::Int) : LibC::Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index edd247c2c723..36c887f78ba7 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -19,8 +19,11 @@ lib LibC FILE_ATTRIBUTE_READONLY = 0x1 FILE_ATTRIBUTE_REPARSE_POINT = 0x400 - FILE_READ_ATTRIBUTES = 0x80 - FILE_WRITE_ATTRIBUTES = 0x0100 + FILE_APPEND_DATA = 0x00000004 + + DELETE = 0x00010000 + FILE_READ_ATTRIBUTES = 0x80 + FILE_WRITE_ATTRIBUTES = 0x0100 # Memory protection constants PAGE_READWRITE = 0x04 From 012e92e198ed941ef0b5bed70dc6ed5db6422c64 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 17 Mar 2023 02:14:51 +0800 Subject: [PATCH 0378/1551] OpenSSL: use Windows' system root certificate store (#13187) --- src/crystal/system/win32/crypto.cr | 57 ++++++++++++++ src/lib_c/x86_64-windows-msvc/c/wincrypt.cr | 83 +++++++++++++++++++++ src/openssl/lib_crypto.cr | 4 + src/openssl/lib_ssl.cr | 1 + src/openssl/ssl/context.cr | 15 ++++ src/openssl/x509/certificate.cr | 13 ++++ 6 files changed, 173 insertions(+) create mode 100644 src/crystal/system/win32/crypto.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/wincrypt.cr diff --git a/src/crystal/system/win32/crypto.cr b/src/crystal/system/win32/crypto.cr new file mode 100644 index 000000000000..716255b1bff1 --- /dev/null +++ b/src/crystal/system/win32/crypto.cr @@ -0,0 +1,57 @@ +require "c/wincrypt" +require "openssl" + +module Crystal::System::Crypto + private ServerAuthOID = "1.3.6.1.5.5.7.3.1" + + # heavily based on cURL's code for importing system certificates on Windows: + # https://github.com/curl/curl/blob/2f17a9b654121dd1ecf4fc043c6d08a9da3522db/lib/vtls/openssl.c#L3015-L3157 + private def self.each_system_certificate(store_name : String, &) + now = ::Time.utc + + return unless cert_store = LibC.CertOpenSystemStoreW(nil, System.to_wstr(store_name)) + + eku = Pointer(LibC::CERT_USAGE).null + cert_context = Pointer(LibC::CERT_CONTEXT).null + while cert_context = LibC.CertEnumCertificatesInStore(cert_store, cert_context) + next unless cert_context.value.dwCertEncodingType == LibC::X509_ASN_ENCODING + + next if cert_context.value.pbCertEncoded.nil? + + not_before = Crystal::System::Time.from_filetime(cert_context.value.pCertInfo.value.notBefore) + not_after = Crystal::System::Time.from_filetime(cert_context.value.pCertInfo.value.notAfter) + next unless not_before <= now <= not_after + + # look for the serverAuth OID if extended key usage exists + if LibC.CertGetEnhancedKeyUsage(cert_context, 0, nil, out eku_size) != 0 + eku = eku.as(UInt8*).realloc(eku_size).as(LibC::CERT_USAGE*) + next unless LibC.CertGetEnhancedKeyUsage(cert_context, 0, eku, pointerof(eku_size)) != 0 + next unless (0...eku.value.cUsageIdentifier).any? do |i| + LibC.strcmp(eku.value.rgpszUsageIdentifier[i], ServerAuthOID) == 0 + end + end + + encoded = Slice.new(cert_context.value.pbCertEncoded, cert_context.value.cbCertEncoded) + until encoded.empty? + cert, encoded = OpenSSL::X509::Certificate.from_der?(encoded) + break unless cert + yield cert + end + end + ensure + LibC.CertCloseStore(cert_store, 0) if cert_store + end + + private class_getter system_root_certificates : Array(OpenSSL::X509::Certificate) do + certs = [] of OpenSSL::X509::Certificate + each_system_certificate("ROOT") { |cert| certs << cert } + certs + end + + def self.populate_system_root_certificates(ssl_context) + cert_store = LibSSL.ssl_ctx_get_cert_store(ssl_context) + system_root_certificates.each do |cert| + LibCrypto.x509_store_add_cert(cert_store, cert) + end + end +end diff --git a/src/lib_c/x86_64-windows-msvc/c/wincrypt.cr b/src/lib_c/x86_64-windows-msvc/c/wincrypt.cr new file mode 100644 index 000000000000..88bb1e1fd0c4 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/wincrypt.cr @@ -0,0 +1,83 @@ +require "c/win_def" +require "c/minwinbase" +require "c/winnt" + +@[Link("crypt32")] +lib LibC + alias HCERTSTORE = Void* + alias HCRYPTPROV_LEGACY = Void* + + struct CERT_NAME_BLOB + cbData : DWORD + pbData : BYTE* + end + + struct CRYPT_INTEGER_BLOB + cbData : DWORD + pbData : BYTE* + end + + struct CRYPT_OBJID_BLOB + cbData : DWORD + pbData : BYTE* + end + + struct CRYPT_BIT_BLOB + cbData : DWORD + pbData : BYTE* + cUnusedBits : DWORD + end + + struct CRYPT_ALGORITHM_IDENTIFIER + pszObjId : LPSTR + parameters : CRYPT_OBJID_BLOB + end + + struct CERT_PUBLIC_KEY_INFO + algorithm : CRYPT_ALGORITHM_IDENTIFIER + publicKey : CRYPT_BIT_BLOB + end + + struct CERT_EXTENSION + pszObjId : LPSTR + fCritical : BOOL + value : CRYPT_OBJID_BLOB + end + + struct CERT_INFO + dwVersion : DWORD + serialNumber : CRYPT_INTEGER_BLOB + signatureAlgorithm : CRYPT_ALGORITHM_IDENTIFIER + issuer : CERT_NAME_BLOB + notBefore : FILETIME + notAfter : FILETIME + subject : CERT_NAME_BLOB + subjectPublicKeyInfo : CERT_PUBLIC_KEY_INFO + issuerUniqueId : CRYPT_BIT_BLOB + subjectUniqueId : CRYPT_BIT_BLOB + cExtension : DWORD + rgExtension : CERT_EXTENSION* + end + + struct CERT_USAGE + cUsageIdentifier : DWORD + rgpszUsageIdentifier : LPSTR* + end + + X509_ASN_ENCODING = 0x00000001 + PKCS_7_ASN_ENCODING = 0x00010000 + + struct CERT_CONTEXT + dwCertEncodingType : DWORD + pbCertEncoded : BYTE* + cbCertEncoded : DWORD + pCertInfo : CERT_INFO* + hCertStore : HCERTSTORE + end + + fun CertOpenSystemStoreW(hProv : HCRYPTPROV_LEGACY, szSubsystemProtocol : LPWSTR) : HCERTSTORE + fun CertCloseStore(hCertStore : HCERTSTORE, dwFlags : DWORD) : BOOL + + fun CertEnumCertificatesInStore(hCertStore : HCERTSTORE, pPrevCertContext : CERT_CONTEXT*) : CERT_CONTEXT* + fun CertGetEnhancedKeyUsage(pCertContext : CERT_CONTEXT*, dwFlags : DWORD, pUsage : CERT_USAGE*, pcbUsage : DWORD*) : BOOL +end diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr index f84e921bef70..c783aa7903a9 100644 --- a/src/openssl/lib_crypto.cr +++ b/src/openssl/lib_crypto.cr @@ -48,6 +48,7 @@ lib LibCrypto type X509_EXTENSION = Void* type X509_NAME = Void* type X509_NAME_ENTRY = Void* + type X509_STORE = Void* type X509_STORE_CTX = Void* struct Bio @@ -316,6 +317,7 @@ lib LibCrypto fun sk_value(x0 : Void*, x1 : Int) : Void* {% end %} + fun d2i_X509(a : X509*, ppin : UInt8**, length : Long) : X509 fun x509_dup = X509_dup(a : X509) : X509 fun x509_free = X509_free(a : X509) fun x509_get_subject_name = X509_get_subject_name(a : X509) : X509_NAME @@ -352,6 +354,8 @@ lib LibCrypto fun x509v3_ext_nconf_nid = X509V3_EXT_nconf_nid(conf : Void*, ctx : Void*, ext_nid : Int, value : Char*) : X509_EXTENSION fun x509v3_ext_print = X509V3_EXT_print(out : Bio*, ext : X509_EXTENSION, flag : Int, indent : Int) : Int + fun x509_store_add_cert = X509_STORE_add_cert(ctx : X509_STORE, x : X509) : Int + {% unless compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %} fun err_load_crypto_strings = ERR_load_crypto_strings fun openssl_add_all_algorithms = OPENSSL_add_all_algorithms_noconf diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr index e6faefa52d8e..37a4cea3a161 100644 --- a/src/openssl/lib_ssl.cr +++ b/src/openssl/lib_ssl.cr @@ -221,6 +221,7 @@ lib LibSSL fun ssl_ctx_get_verify_mode = SSL_CTX_get_verify_mode(ctx : SSLContext) : VerifyMode fun ssl_ctx_set_verify = SSL_CTX_set_verify(ctx : SSLContext, mode : VerifyMode, callback : VerifyCallback) fun ssl_ctx_set_default_verify_paths = SSL_CTX_set_default_verify_paths(ctx : SSLContext) : Int + fun ssl_ctx_get_cert_store = SSL_CTX_get_cert_store(ctx : SSLContext) : LibCrypto::X509_STORE fun ssl_ctx_ctrl = SSL_CTX_ctrl(ctx : SSLContext, cmd : Int, larg : ULong, parg : Void*) : ULong {% if compare_versions(OPENSSL_VERSION, "3.0.0") >= 0 %} diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr index 37c54ca77eb2..45c4bc102fef 100644 --- a/src/openssl/ssl/context.cr +++ b/src/openssl/ssl/context.cr @@ -1,5 +1,8 @@ require "uri/punycode" require "log" +{% if flag?(:win32) %} + require "crystal/system/win32/crypto" +{% end %} # An `SSL::Context` represents a generic secure socket protocol configuration. # @@ -223,6 +226,12 @@ abstract class OpenSSL::SSL::Context {% end %} add_modes(OpenSSL::SSL::Modes.flags(AUTO_RETRY, RELEASE_BUFFERS)) + + # OpenSSL does not support reading from the system root certificate store on + # Windows, so we have to import them ourselves + {% if flag?(:win32) %} + Crystal::System::Crypto.populate_system_root_certificates(self) + {% end %} end # Overriding initialize or new in the child classes as public methods, @@ -233,6 +242,12 @@ abstract class OpenSSL::SSL::Context protected def _initialize_insecure(method : LibSSL::SSLMethod) @handle = LibSSL.ssl_ctx_new(method) raise OpenSSL::Error.new("SSL_CTX_new") if @handle.null? + + # since an insecure context on non-Windows systems still has access to the + # system certificates, we do the same for Windows + {% if flag?(:win32) %} + Crystal::System::Crypto.populate_system_root_certificates(self) + {% end %} end protected def self.insecure(method : LibSSL::SSLMethod) diff --git a/src/openssl/x509/certificate.cr b/src/openssl/x509/certificate.cr index 43018d9b7c98..593f3fb4dc4b 100644 --- a/src/openssl/x509/certificate.cr +++ b/src/openssl/x509/certificate.cr @@ -28,6 +28,19 @@ module OpenSSL::X509 @cert end + # Attempts to decode an ASN.1/DER-encoded certificate from *bytes*. + # + # Returns the decoded certificate and the remaining bytes on success. + # Returns `nil` and *bytes* unchanged on failure. + def self.from_der?(bytes : Bytes) : {self?, Bytes} + ptr = bytes.to_unsafe + if x509 = LibCrypto.d2i_X509(nil, pointerof(ptr), bytes.size) + {new(x509), bytes[ptr - bytes.to_unsafe..]} + else + {nil, bytes} + end + end + def subject : X509::Name subject = LibCrypto.x509_get_subject_name(@cert) raise Error.new("X509_get_subject_name") if subject.null? From 877df2828d98ab3d45d30da12883dbff928f7e9d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 17 Mar 2023 02:15:19 +0800 Subject: [PATCH 0379/1551] Fix type names for generic instances with empty splat type vars (#13189) --- spec/compiler/macro/macro_methods_spec.cr | 49 +++++++++++++++++++++++ src/compiler/crystal/types.cr | 12 ++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index b72f95a31678..f18f9edb7aa5 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1610,6 +1610,55 @@ module Crystal end end + describe "generic instance" do + it "prints generic type arguments" do + assert_macro("{{klass.name}}", "Foo(Int32, 3)") do |program| + generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T", "U"]) + {klass: TypeNode.new(generic_type.instantiate([program.int32, 3.int32] of TypeVar))} + end + end + + it "prints empty splat type var" do + assert_macro("{{klass.name}}", "Foo()") do |program| + generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T"]) + generic_type.splat_index = 0 + {klass: TypeNode.new(generic_type.instantiate([] of TypeVar))} + end + end + + it "prints multiple arguments for splat type var" do + assert_macro("{{klass.name}}", "Foo(Int32, String)") do |program| + generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T"]) + generic_type.splat_index = 0 + {klass: TypeNode.new(generic_type.instantiate([program.int32, program.string] of TypeVar))} + end + end + + it "does not print extra commas for empty splat type var (1)" do + assert_macro("{{klass.name}}", "Foo(Int32)") do |program| + generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T", "U"]) + generic_type.splat_index = 1 + {klass: TypeNode.new(generic_type.instantiate([program.int32] of TypeVar))} + end + end + + it "does not print extra commas for empty splat type var (2)" do + assert_macro("{{klass.name}}", "Foo(Int32)") do |program| + generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T", "U"]) + generic_type.splat_index = 0 + {klass: TypeNode.new(generic_type.instantiate([program.int32] of TypeVar))} + end + end + + it "does not print extra commas for empty splat type var (3)" do + assert_macro("{{klass.name}}", "Foo(Int32, String)") do |program| + generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T", "U", "V"]) + generic_type.splat_index = 1 + {klass: TypeNode.new(generic_type.instantiate([program.int32, program.string] of TypeVar))} + end + end + end + describe :generic_args do describe true do it "includes the generic_args of the type" do diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 8735e77a3730..d4b73df92ec4 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -2072,21 +2072,27 @@ module Crystal generic_type.append_full_name(io) if generic_args io << '(' - type_vars.each_value.with_index do |type_var, i| - io << ", " if i > 0 + first = true + type_vars.each_with_index do |(_, type_var), i| if type_var.is_a?(Var) if i == splat_index tuple = type_var.type.as(TupleInstanceType) - tuple.tuple_types.join(io, ", ") do |tuple_type| + tuple.tuple_types.each do |tuple_type| + io << ", " unless first + first = false tuple_type = tuple_type.devirtualize unless codegen tuple_type.to_s_with_options(io, codegen: codegen) end else + io << ", " unless first + first = false type_var_type = type_var.type type_var_type = type_var_type.devirtualize unless codegen type_var_type.to_s_with_options(io, skip_union_parens: true, codegen: codegen) end else + io << ", " unless first + first = false type_var.to_s(io) end end From c931553785487e6139213bd4beb688ee9a82562d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 17 Mar 2023 02:15:58 +0800 Subject: [PATCH 0380/1551] Enable or fix specs that already work on Windows (#13186) --- spec/compiler/codegen/thread_local_spec.cr | 2 +- spec/manual/string_normalize_spec.cr | 2 +- spec/std/file/tempfile_spec.cr | 8 +- spec/std/file_spec.cr | 8 +- spec/std/http/client/client_spec.cr | 4 +- spec/std/signal_spec.cr | 127 +++++++++++---------- spec/std/socket/socket_spec.cr | 4 +- spec/std/sprintf_spec.cr | 6 +- spec/std/system_spec.cr | 2 +- src/crystal/system/unix/lib_event2.cr | 6 +- 10 files changed, 87 insertions(+), 82 deletions(-) diff --git a/spec/compiler/codegen/thread_local_spec.cr b/spec/compiler/codegen/thread_local_spec.cr index bf03a1f2652f..694cb430b8c1 100644 --- a/spec/compiler/codegen/thread_local_spec.cr +++ b/spec/compiler/codegen/thread_local_spec.cr @@ -3,7 +3,7 @@ require "../../spec_helper" describe "Codegen: thread local" do - pending_win32 "works with class variables" do + it "works with class variables" do run(%( require "prelude" diff --git a/spec/manual/string_normalize_spec.cr b/spec/manual/string_normalize_spec.cr index dcde8339b646..96d58c946894 100644 --- a/spec/manual/string_normalize_spec.cr +++ b/spec/manual/string_normalize_spec.cr @@ -43,7 +43,7 @@ private macro assert_prints_codepoints(call, str, desc, *, file = __FILE__, line ) {{ call.block }} end.should %expectation, file: {{ file }}, line: {{ line }} - {% unless flag?(:win32) %} + {% unless flag?(:without_iconv) %} string_build_via_utf16 do |io| {% if call.receiver %}{{ call.receiver }}.{% end %}{{ call.name }}( io, diff --git a/spec/std/file/tempfile_spec.cr b/spec/std/file/tempfile_spec.cr index aac486bacf40..3ede9e52e44d 100644 --- a/spec/std/file/tempfile_spec.cr +++ b/spec/std/file/tempfile_spec.cr @@ -110,15 +110,17 @@ describe File do tempfile.try &.delete end - pending_win32 "accepts dir argument" do + it "accepts dir argument" do file = File.tempfile(dir: datapath) File.dirname(file.path).should eq(datapath) + file.close ensure file.try &.delete end - pending_win32 "fails in nonwriteable folder" do - expect_raises(File::NotFoundError, "Error creating temporary file: '#{datapath("non-existing-folder")}/") do + it "fails in nonwriteable folder" do + err_directory = (datapath("non-existing-folder") + Path::SEPARATORS[0]).inspect_unquoted + expect_raises(File::NotFoundError, "Error creating temporary file: '#{err_directory}") do File.tempfile dir: datapath("non-existing-folder") end end diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 8513b3bebacf..538fa6a165a2 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -912,7 +912,7 @@ describe "File" do end describe "fsync" do - pending_win32 "syncs OS file buffer to disk" do + it "syncs OS file buffer to disk" do with_tempfile("fsync.txt") do |path| File.open(path, "a") do |f| f.puts("333") @@ -1062,10 +1062,8 @@ describe "File" do File.writable?("foo\0bar") end - pending_win32 "errors on executable?" do - expect_raises(ArgumentError, "String contains null byte") do - File.executable?("foo\0bar") - end + it_raises_on_null_byte "executable?" do + File.executable?("foo\0bar") end it_raises_on_null_byte "file?" do diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index d23b3376e043..b5d522088d31 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -357,7 +357,7 @@ module HTTP end end - pending_win32 "tests write_timeout" do + it "tests write_timeout" do # Here we don't want to write a response on the server side because # it doesn't make sense to try to write because the client will already # timeout on read. Writing a response could lead on an exception in @@ -371,7 +371,7 @@ module HTTP end end - pending_win32 "tests connect_timeout" do + it "tests connect_timeout" do test_server("localhost", 0, 0) do |server| client = Client.new("localhost", server.local_address.port) client.connect_timeout = 0.5 diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr index 7183a544e648..e92b74a6370c 100644 --- a/spec/std/signal_spec.cr +++ b/spec/std/signal_spec.cr @@ -1,79 +1,90 @@ -{% skip_file if flag?(:win32) || flag?(:wasm32) %} +{% skip_file if flag?(:wasm32) %} require "spec" require "signal" describe "Signal" do - typeof(Signal::PIPE.reset) - typeof(Signal::PIPE.ignore) - typeof(Signal::PIPE.trap { 1 }) - - it "runs a signal handler" do - ran = false - Signal::USR1.trap do - ran = true - end - Process.signal Signal::USR1, Process.pid - 10.times do |i| - break if ran - sleep 0.1 - end - ran.should be_true - end - - it "ignores a signal" do - Signal::USR2.ignore - Process.signal Signal::USR2, Process.pid + typeof(Signal::ABRT.reset) + typeof(Signal::ABRT.ignore) + typeof(Signal::ABRT.trap { 1 }) + + it "has constants required by C" do + Signal::INT.should be_a(Signal) + Signal::ILL.should be_a(Signal) + Signal::FPE.should be_a(Signal) + Signal::SEGV.should be_a(Signal) + Signal::TERM.should be_a(Signal) + Signal::ABRT.should be_a(Signal) end - it "CHLD.reset sets default Crystal child handler" do - Signal::CHLD.reset - child = Process.new("true", shell: true) - child.wait # doesn't block forever - end + {% unless flag?(:win32) %} + it "runs a signal handler" do + ran = false + Signal::USR1.trap do + ran = true + end + Process.signal Signal::USR1, Process.pid + 10.times do |i| + break if ran + sleep 0.1 + end + ran.should be_true + end - it "CHLD.ignore sets default Crystal child handler" do - Signal::CHLD.ignore - child = Process.new("true", shell: true) - child.wait # doesn't block forever - end + it "ignores a signal" do + Signal::USR2.ignore + Process.signal Signal::USR2, Process.pid + end - it "CHLD.trap is called after default Crystal child handler" do - chan = Channel(Process).new - existed = Channel(Bool).new + it "CHLD.reset sets default Crystal child handler" do + Signal::CHLD.reset + child = Process.new("true", shell: true) + child.wait # doesn't block forever + end - Signal::CHLD.trap do - child_process = chan.receive - existed.send(Process.exists?(child_process.pid)) + it "CHLD.ignore sets default Crystal child handler" do + Signal::CHLD.ignore + child = Process.new("true", shell: true) + child.wait # doesn't block forever end - child = Process.new("true", shell: true) - child.wait # doesn't block forever - chan.send(child) - existed.receive.should be_false - ensure - Signal::CHLD.reset - end + it "CHLD.trap is called after default Crystal child handler" do + chan = Channel(Process).new + existed = Channel(Bool).new + + Signal::CHLD.trap do + child_process = chan.receive + existed.send(Process.exists?(child_process.pid)) + end + + child = Process.new("true", shell: true) + child.wait # doesn't block forever + chan.send(child) + existed.receive.should be_false + ensure + Signal::CHLD.reset + end - it "CHLD.reset removes previously set trap" do - call_count = 0 + it "CHLD.reset removes previously set trap" do + call_count = 0 - Signal::CHLD.trap do - call_count += 1 - end + Signal::CHLD.trap do + call_count += 1 + end - Process.new("true", shell: true).wait - Fiber.yield + Process.new("true", shell: true).wait + Fiber.yield - call_count.should eq(1) + call_count.should eq(1) - Signal::CHLD.reset + Signal::CHLD.reset - Process.new("true", shell: true).wait - Fiber.yield + Process.new("true", shell: true).wait + Fiber.yield - call_count.should eq(1) - end + call_count.should eq(1) + end - # TODO: test Signal::X.reset + # TODO: test Signal::X.reset + {% end %} end diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index 23b7229db88d..f1500697ca81 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -25,7 +25,7 @@ describe Socket, tags: "network" do end end - pending_win32 ".accept" do + it ".accept" do client_done = Channel(Nil).new server = Socket.new(Socket::Family::INET, Socket::Type::STREAM, Socket::Protocol::TCP) @@ -65,7 +65,7 @@ describe Socket, tags: "network" do expect_raises(IO::TimeoutError) { server.accept? } end - pending_win32 "sends messages" do + it "sends messages" do port = unused_local_port server = Socket.tcp(Socket::Family::INET) server.bind("127.0.0.1", port) diff --git a/spec/std/sprintf_spec.cr b/spec/std/sprintf_spec.cr index 8cb3218b06ab..bd72c962bf35 100644 --- a/spec/std/sprintf_spec.cr +++ b/spec/std/sprintf_spec.cr @@ -1,8 +1,6 @@ require "./spec_helper" require "../support/string" -{% unless flag?(:win32) %} - require "big" -{% end %} +require "big" # use same name for `sprintf` and `IO#printf` so that `assert_prints` can be leveraged private def fprintf(format, *args) @@ -368,7 +366,7 @@ describe "::sprintf" do assert_sprintf "%d", Int64::MIN, "-9223372036854775808" end - pending_win32 "works with BigInt" do + it "works with BigInt" do assert_sprintf "%d", 123.to_big_i, "123" assert_sprintf "%300.250d", 10.to_big_i ** 200, "#{" " * 50}#{"0" * 49}1#{"0" * 200}" assert_sprintf "%- #300.250X", 16.to_big_i ** 200 - 1, " 0X#{"0" * 50}#{"F" * 200}#{" " * 47}" diff --git a/spec/std/system_spec.cr b/spec/std/system_spec.cr index 9a64926d6e2f..7a87b1a238c8 100644 --- a/spec/std/system_spec.cr +++ b/spec/std/system_spec.cr @@ -3,7 +3,7 @@ require "system" describe System do describe "hostname" do - pending_win32 "returns current hostname" do + it "returns current hostname" do shell_hostname = `hostname`.strip pending! "`hostname` command was unsuccessful" unless $?.success? diff --git a/src/crystal/system/unix/lib_event2.cr b/src/crystal/system/unix/lib_event2.cr index 10d371896e0d..5bc8ff514818 100644 --- a/src/crystal/system/unix/lib_event2.cr +++ b/src/crystal/system/unix/lib_event2.cr @@ -19,11 +19,7 @@ require "c/netdb" lib LibEvent2 alias Int = LibC::Int - {% if flag?(:windows) %} - # TODO - {% else %} - alias EvutilSocketT = Int - {% end %} + alias EvutilSocketT = Int type EventBase = Void* type Event = Void* From 420d2cbbef6db1beb1817dc630cd62e448e20fbc Mon Sep 17 00:00:00 2001 From: kipar Date: Fri, 17 Mar 2023 13:12:38 +0300 Subject: [PATCH 0381/1551] Windows 7 support (#11505) Co-authored-by: Beta Ziliani --- src/crystal/system/win32/thread.cr | 13 +++++++++--- src/crystal/system/win32/time.cr | 11 ++++++---- src/lib_c/x86_64-windows-msvc/c/memoryapi.cr | 1 + .../c/processthreadsapi.cr | 6 +++++- src/lib_c/x86_64-windows-msvc/c/sdkddkver.cr | 13 ++++++++++++ src/lib_c/x86_64-windows-msvc/c/sysinfoapi.cr | 5 ++++- src/lib_c/x86_64-windows-msvc/c/winbase.cr | 3 --- src/lib_c/x86_64-windows-msvc/c/winnt.cr | 21 +++++++++++++++++++ 8 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 src/lib_c/x86_64-windows-msvc/c/sdkddkver.cr diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr index aeb7a6749b5a..badfff437ed5 100644 --- a/src/crystal/system/win32/thread.cr +++ b/src/crystal/system/win32/thread.cr @@ -113,9 +113,16 @@ class Thread end private def stack_address : Void* - LibC.GetCurrentThreadStackLimits(out low_limit, out high_limit) - - Pointer(Void).new(low_limit) + {% if LibC.has_method?("GetCurrentThreadStackLimits") %} + LibC.GetCurrentThreadStackLimits(out low_limit, out high_limit) + Pointer(Void).new(low_limit) + {% else %} + tib = LibC.NtCurrentTeb + high_limit = tib.value.stackBase + LibC.VirtualQuery(tib.value.stackLimit, out mbi, sizeof(LibC::MEMORY_BASIC_INFORMATION)) + low_limit = mbi.allocationBase + low_limit + {% end %} end # :nodoc: diff --git a/src/crystal/system/win32/time.cr b/src/crystal/system/win32/time.cr index 2fe6eae533c8..c884ff61a511 100644 --- a/src/crystal/system/win32/time.cr +++ b/src/crystal/system/win32/time.cr @@ -17,10 +17,13 @@ module Crystal::System::Time BIAS_TO_OFFSET_FACTOR = -60 def self.compute_utc_seconds_and_nanoseconds : {Int64, Int32} - # TODO: Needs a check if `GetSystemTimePreciseAsFileTime` is actually available (only >= Windows 8) - # and use `GetSystemTimeAsFileTime` as fallback. - LibC.GetSystemTimePreciseAsFileTime(out filetime) - filetime_to_seconds_and_nanoseconds(filetime) + {% if LibC.has_method?("GetSystemTimePreciseAsFileTime") %} + LibC.GetSystemTimePreciseAsFileTime(out filetime) + filetime_to_seconds_and_nanoseconds(filetime) + {% else %} + LibC.GetSystemTimeAsFileTime(out filetime) + filetime_to_seconds_and_nanoseconds(filetime) + {% end %} end def self.filetime_to_seconds_and_nanoseconds(filetime) : {Int64, Int32} diff --git a/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr b/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr index 278945338d36..dd457ee1b283 100644 --- a/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr @@ -10,4 +10,5 @@ lib LibC MEM_RELEASE = 0x8000 fun VirtualFree(lpAddress : Void*, dwSize : SizeT, dwFreeType : DWORD) : BOOL + fun VirtualQuery(lpAddress : Void*, lpBuffer : MEMORY_BASIC_INFORMATION*, dwLength : SizeT) end diff --git a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr index fb5b3fee46d2..6ea5281b299e 100644 --- a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr @@ -1,5 +1,6 @@ require "./basetsd" require "c/wtypesbase" +require "c/sdkddkver" lib LibC CREATE_UNICODE_ENVIRONMENT = 0x00000400 @@ -32,9 +33,12 @@ lib LibC hStdError : HANDLE end + fun NtCurrentTeb : NT_TIB* fun GetCurrentThread : HANDLE fun GetCurrentThreadId : DWORD - fun GetCurrentThreadStackLimits(lowLimit : ULONG_PTR*, highLimit : ULONG_PTR*) : Void + {% if LibC::WIN32_WINNT >= LibC::WIN32_WINNT_WIN8 %} + fun GetCurrentThreadStackLimits(lowLimit : ULONG_PTR*, highLimit : ULONG_PTR*) : Void + {% end %} fun GetCurrentProcess : HANDLE fun GetCurrentProcessId : DWORD fun OpenProcess(dwDesiredAccess : DWORD, bInheritHandle : BOOL, dwProcessId : DWORD) : HANDLE diff --git a/src/lib_c/x86_64-windows-msvc/c/sdkddkver.cr b/src/lib_c/x86_64-windows-msvc/c/sdkddkver.cr new file mode 100644 index 000000000000..ecb07110c6e6 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/sdkddkver.cr @@ -0,0 +1,13 @@ +lib LibC + WIN32_WINNT_WIN7 = 0x0601 + WIN32_WINNT_WIN8 = 0x0602 + WIN32_WINNT_WIN10 = 0x0A00 # includes Windows 11 too + + # add other version flags here, or use mechanisms other than flags + {% if flag?(:win7) %} + WIN32_WINNT = {{ WIN32_WINNT_WIN7 }} + {% else %} + # TODO - detect host version using `cmd.exe /c ver`, environment variable or some other way. + WIN32_WINNT = {{ WIN32_WINNT_WIN10 }} + {% end %} +end diff --git a/src/lib_c/x86_64-windows-msvc/c/sysinfoapi.cr b/src/lib_c/x86_64-windows-msvc/c/sysinfoapi.cr index d6c3781d2a83..01e0608405a0 100644 --- a/src/lib_c/x86_64-windows-msvc/c/sysinfoapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/sysinfoapi.cr @@ -1,8 +1,11 @@ require "c/winbase" +require "c/sdkddkver" lib LibC fun GetSystemTimeAsFileTime(time : FILETIME*) - fun GetSystemTimePreciseAsFileTime(time : FILETIME*) + {% if LibC::WIN32_WINNT >= LibC::WIN32_WINNT_WIN8 %} + fun GetSystemTimePreciseAsFileTime(time : FILETIME*) + {% end %} fun GetNativeSystemInfo(system_info : SYSTEM_INFO*) diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index f8b27e4850df..0ac284673695 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -15,9 +15,6 @@ lib LibC fun FormatMessageW(dwFlags : DWORD, lpSource : Void*, dwMessageId : DWORD, dwLanguageId : DWORD, lpBuffer : LPWSTR, nSize : DWORD, arguments : Void*) : DWORD - fun GetSystemTimeAsFileTime(time : FILETIME*) - fun GetSystemTimePreciseAsFileTime(time : FILETIME*) - SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1 SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2 diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index 36c887f78ba7..cd2e6006e425 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -171,4 +171,25 @@ lib LibC exceptionRecord : EXCEPTION_RECORD64* contextRecord : CONTEXT* end + + struct NT_TIB + exceptionList : Void* + stackBase : Void* + stackLimit : Void* + subSystemTib : Void* + fiberData : Void* + arbitraryUserPointer : Void* + pvSelf : NT_TIB* + end + + struct MEMORY_BASIC_INFORMATION + baseAddress : Void* + allocationBase : Void* + allocationProtect : DWORD + partitionId : WORD + regionSize : SizeT + state : DWORD + protect : DWORD + type : DWORD + end end From fc2250af9c501117e3b94469ae46f3f92a9272e6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 18 Mar 2023 04:10:04 +0800 Subject: [PATCH 0382/1551] Remove pending spec for `Path#drive` with IPv6 UNC host names (#13190) --- spec/std/path_spec.cr | 3 --- src/path.cr | 6 ++++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/spec/std/path_spec.cr b/spec/std/path_spec.cr index 784884312da4..d1ad7bbf9d81 100644 --- a/spec/std/path_spec.cr +++ b/spec/std/path_spec.cr @@ -409,9 +409,6 @@ describe Path do assert_paths("\\\\%10%20\\share\\", nil, "\\\\%10%20\\share", &.drive) assert_paths("\\\\_.-~!$;=&'()*+,aB1\\ !-.@^_`{}~#$%&'()aB1\\", nil, "\\\\_.-~!$;=&'()*+,aB1\\ !-.@^_`{}~#$%&'()aB1", &.drive) assert_paths("\\\\127.0.0.1\\share\\", nil, "\\\\127.0.0.1\\share", &.drive) - pending do - assert_paths("\\\\2001:4860:4860::8888\\share\\", nil, "\\\\2001:4860:4860::8888\\share", &.drive) - end end describe "#root" do diff --git a/src/path.cr b/src/path.cr index 2af60279ded5..4ec62b39d95d 100644 --- a/src/path.cr +++ b/src/path.cr @@ -1213,9 +1213,11 @@ struct Path reader.next_char # 2. Consume first path component - # The first component is either an IP address or a hostname. + # The first component is either an IPv4 address or a hostname. + # IPv6 addresses are converted into hostnames by replacing all `:`s with + # `-`s, and then appending `.ipv6-literal.net`, so raw IPv6 addresses cannot + # appear here. # Hostname follows the grammar of `reg-name` in [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986). - # TODO: Add support for IPv6 address grammar return if separators.includes?(reader.current_char) while true char = reader.current_char From 1fcdef6ec1e6c04901f3b44395fc6e1a1e3f546e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 20 Mar 2023 12:17:17 +0100 Subject: [PATCH 0383/1551] Mute shell comments in Makefile (#13201) --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a992cdc66643..fe714d2ba393 100644 --- a/Makefile +++ b/Makefile @@ -197,8 +197,8 @@ $(O)/primitives_spec: $(O)/crystal $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(O)/crystal: $(DEPS) $(SOURCES) $(call check_llvm_config) @mkdir -p $(O) - # NOTE: USE_PCRE1 is only used for testing compatibility with legacy environments that don't provide libpcre2. - # Newly built compilers should never be distributed with libpcre to ensure syntax consistency. + @# NOTE: USE_PCRE1 is only used for testing compatibility with legacy environments that don't provide libpcre2. + @# Newly built compilers should never be distributed with libpcre to ensure syntax consistency. $(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $@ src/compiler/crystal.cr -D without_openssl -D without_zlib $(if $(USE_PCRE1),-D use_pcre,-D use_pcre2) $(LLVM_EXT_OBJ): $(LLVM_EXT_DIR)/llvm_ext.cc From 31c17f7e251e3b1a0335685f903b4fcc80c7679a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 22 Mar 2023 11:41:30 +0100 Subject: [PATCH 0384/1551] Update previous Crystal release - 1.7.3 (#13167) --- .circleci/config.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 52383eb206a9..db26200e75fd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.7.2/crystal-1.7.2-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.7.3/crystal-1.7.3-1" defaults: environment: &env diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 2ab70c8c7dc2..0e4427b3232a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -27,7 +27,7 @@ jobs: flags: "FLAGS=-Dwithout_ffi USE_PCRE1=true" - crystal_bootstrap_version: 1.1.1 flags: "FLAGS=-Dwithout_ffi USE_PCRE1=true" - - crystal_bootstrap_version: 1.7.2 + - crystal_bootstrap_version: 1.7.3 flags: "" steps: - name: Download Crystal source diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 516106a9e53d..52df94eb93a0 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.7.2-alpine + container: crystallang/crystal:1.7.3-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -27,7 +27,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.7.2-alpine + container: crystallang/crystal:1.7.3-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -40,7 +40,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.7.2-alpine + container: crystallang/crystal:1.7.3-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index a0e77180993f..34bab7108845 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.7.2-alpine + container: crystallang/crystal:1.7.3-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -21,7 +21,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.7.2-alpine + container: crystallang/crystal:1.7.3-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index fcdaec6179d7..eb603f8f746c 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.7.2-build + container: crystallang/crystal:1.7.3-build steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 649af6315e1e..d24d81733226 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -9,7 +9,7 @@ concurrency: jobs: x86_64-linux-job: runs-on: ubuntu-latest - container: crystallang/crystal:1.7.2-build + container: crystallang/crystal:1.7.3-build steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/bin/ci b/bin/ci index d4211fd2be90..f416e3751bf1 100755 --- a/bin/ci +++ b/bin/ci @@ -134,8 +134,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.7.2/crystal-1.7.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.7.2-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.7.3/crystal-1.7.3-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.7.3-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -188,7 +188,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.7.2}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.7.3}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 85f781bb0fe3..27c64ea7af8e 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.7.2/crystal-1.7.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:04x30yxib5xnpddvabywr0696biq1v2wak2jfxkiqhc9zikh2cfn"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.7.3/crystal-1.7.3-1-darwin-universal.tar.gz"; + sha256 = "sha256:1jc3fdc36mpvh7zahszbij02c0nxhmmbpfjcpd890bapj2q4jkkr"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.7.2/crystal-1.7.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:04x30yxib5xnpddvabywr0696biq1v2wak2jfxkiqhc9zikh2cfn"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.7.3/crystal-1.7.3-1-darwin-universal.tar.gz"; + sha256 = "sha256:1jc3fdc36mpvh7zahszbij02c0nxhmmbpfjcpd890bapj2q4jkkr"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.7.2/crystal-1.7.2-1-linux-x86_64.tar.gz"; - sha256 = "sha256:0mk3mszvlyh1d3j1apagz3bhidwpyhbzmk0hbnz2mshb2fk9dxly"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.7.3/crystal-1.7.3-1-linux-x86_64.tar.gz"; + sha256 = "sha256:0diq6i760yd0rv310f80v60m015f5xkni7h60phspvmyy0yw9jv0"; }; }.${pkgs.stdenv.system}); From 2c8e6062c8befaec94522db911288b6af6754239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 22 Mar 2023 22:37:45 +0100 Subject: [PATCH 0385/1551] Fix `SyntaxHighlighter::HTML` to escape identifier values (#13212) --- spec/std/crystal/syntax_highlighter/html_spec.cr | 2 +- src/crystal/syntax_highlighter/html.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/std/crystal/syntax_highlighter/html_spec.cr b/spec/std/crystal/syntax_highlighter/html_spec.cr index fc1a3d25672c..84e7c69ff410 100644 --- a/spec/std/crystal/syntax_highlighter/html_spec.cr +++ b/spec/std/crystal/syntax_highlighter/html_spec.cr @@ -73,7 +73,7 @@ describe Crystal::SyntaxHighlighter::HTML do == < <= > >= != =~ !~ & | ^ ~ ** >> << % ).each do |op| - it_highlights %(def #{op}), %(def #{op}) + it_highlights %(def #{op}), %(def #{HTML.escape(op)}) end it_highlights %(def //), %(def //) diff --git a/src/crystal/syntax_highlighter/html.cr b/src/crystal/syntax_highlighter/html.cr index f453895f25bb..3f146b662485 100644 --- a/src/crystal/syntax_highlighter/html.cr +++ b/src/crystal/syntax_highlighter/html.cr @@ -52,7 +52,7 @@ class Crystal::SyntaxHighlighter::HTML < Crystal::SyntaxHighlighter when .string? span "s" { ::HTML.escape(value, @io) } when .ident? - span "m", &.print value + span "m" { ::HTML.escape(value, @io) } when .keyword?, .self? span "k", &.print value when .primitive_literal? From 68346f75e65d82fc7a7f53ec38431b3f7d5fa6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 23 Mar 2023 00:21:16 +0100 Subject: [PATCH 0386/1551] Update distribution-scripts (#13213) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index db26200e75fd..2177866aaad7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "dd0ef752b995c04b5e3bf80e474881ba107e4476" + default: "5ced0bc13de682688110387adc313fe02f96bab7" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From cffcc2388a9f470d25da7977eea40b82bbff79c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 23 Mar 2023 15:58:58 +0100 Subject: [PATCH 0387/1551] [CI] Remove cross-compiliation on Windows (#13207) --- .github/workflows/win.yml | 39 +++++++-------------------------------- scripts/release-update.sh | 3 +++ 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index d24d81733226..21d7ff6bcc98 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -7,28 +7,7 @@ concurrency: cancel-in-progress: true jobs: - x86_64-linux-job: - runs-on: ubuntu-latest - container: crystallang/crystal:1.7.3-build - steps: - - name: Download Crystal source - uses: actions/checkout@v3 - - - name: Build C extensions - run: | - make deps - - name: Cross-compile Crystal - run: | - LLVM_TARGETS=X86 bin/crystal build --cross-compile --target x86_64-pc-windows-msvc src/compiler/crystal.cr -Dwithout_playground -Dwithout_iconv -Dwithout_interpreter -Duse_pcre2 - - - name: Upload Crystal object file - uses: actions/upload-artifact@v3 - with: - name: objs - path: crystal.obj - - x86_64-windows-job: - needs: x86_64-linux-job + x86_64-windows: runs-on: windows-2022 steps: - name: Disable CRLF line ending substitution @@ -37,6 +16,11 @@ jobs: - name: Enable Developer Command Prompt uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + - name: Install Crystal + uses: crystal-lang/install-crystal@v1 + with: + crystal: "1.7.3" + - name: Download Crystal source uses: actions/checkout@v3 @@ -329,19 +313,10 @@ jobs: echo "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\.build\crystal.exe" >> ${env:GITHUB_ENV} echo "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" >> ${env:GITHUB_ENV} - - name: Download Crystal object file - uses: actions/download-artifact@v3 - with: - name: objs - name: Build LLVM extensions run: make -f Makefile.win deps - - name: Link Crystal executable - run: | - Invoke-Expression "cl crystal.obj /Fecrystal src\llvm\ext\llvm_ext.obj $(llvm\bin\llvm-config.exe --libs) libs\pcre2-8.lib libs\gc.lib WS2_32.lib advapi32.lib libcmt.lib dbghelp.lib ole32.lib shell32.lib legacy_stdio_definitions.lib /link /LIBPATH:$(pwd)\libs /STACK:0x800000 /ENTRY:wmainCRTStartup" - mkdir .build - mv crystal.exe .build/ - - name: Re-build Crystal + - name: Build Crystal run: | bin/crystal.bat env make -f Makefile.win -B diff --git a/scripts/release-update.sh b/scripts/release-update.sh index afb59c7d5455..ec9c9570636c 100755 --- a/scripts/release-update.sh +++ b/scripts/release-update.sh @@ -26,6 +26,9 @@ sed -i -E "s|crystal-[0-9.]+-[0-9]|crystal-$CRYSTAL_VERSION-1|g" bin/ci sed -i -E "/crystal_bootstrap_version:/ s/(, ${CRYSTAL_VERSION%.*}\.[0-9]*)?\]\$/, $CRYSTAL_VERSION]/" .github/workflows/linux.yml sed -i -E "s|crystallang/crystal:[0-9.]+|crystallang/crystal:$CRYSTAL_VERSION|g" .github/workflows/*.yml +# Edit .github/workflows/*.yml to update version for install-crystal action +sed -i -E "s|crystal: \"[0-9.]+\"|crystal: \"$CRYSTAL_VERSION\"|g" .github/workflows/*.yml + # Edit shell.nix latestCrystalBinary using nix-prefetch-url --unpack darwin_url="https://github.com/crystal-lang/crystal/releases/download/$CRYSTAL_VERSION/crystal-$CRYSTAL_VERSION-1-darwin-universal.tar.gz" darwin_sha=$(nix-prefetch-url --unpack $darwin_url) From 6a44a490527e83a25ec1395f65c967fdeed5fef0 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 24 Mar 2023 17:53:33 +0800 Subject: [PATCH 0388/1551] Workaround for more `Int128`-and-float methods on Windows with LLVM 14+ (#13218) --- src/crystal/compiler_rt.cr | 79 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/crystal/compiler_rt.cr b/src/crystal/compiler_rt.cr index e1681dac4186..2c23b672f69b 100644 --- a/src/crystal/compiler_rt.cr +++ b/src/crystal/compiler_rt.cr @@ -24,6 +24,7 @@ require "./compiler_rt/divmod128.cr" # those functions directly # note that the following defs redefine the ones in `primitives.cr` + # https://github.com/llvm/llvm-project/commit/4a406d32e97b1748c4eed6674a2c1819b9cf98ea struct Int128 {% for int2 in [Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128] %} @[AlwaysInline] @@ -83,4 +84,82 @@ require "./compiler_rt/divmod128.cr" end {% end %} {% end %} + + # https://github.com/llvm/llvm-project/commit/d6216e2cd1a5e07f8509215ee5422ff5ee358da8 + {% if compare_versions(Crystal::LLVM_VERSION, "14.0.0") >= 0 %} + {% for v in [ + {Int128, "to_f32", Float32, "__floattisf"}, + {Int128, "to_f64", Float64, "__floattidf"}, + {UInt128, "to_f32", Float32, "__floatuntisf"}, + {UInt128, "to_f64", Float64, "__floatuntidf"}, + {Float32, "to_i128", Int128, "__fixsfti"}, + {Float32, "to_u128", UInt128, "__fixunssfti"}, + {Float64, "to_i128", Int128, "__fixdfti"}, + {Float64, "to_u128", UInt128, "__fixunsdfti"}, + ] %} + {% type, method, ret, rt_method = v %} + struct {{ type.id }} + @[AlwaysInline] + def {{ method.id }} : {{ ret.id }} + raise OverflowError.new unless {{ ret.id }}::MIN <= self <= {{ ret.id }}::MAX + {{ ret.id }}.new!({{ rt_method.id }}(self)) + end + + @[AlwaysInline] + def {{ method.id }}! : {{ ret.id }} + {{ ret.id }}.new!({{ rt_method.id }}(self)) + end + end + {% end %} + + {% for op in {"+", "-", "*", "fdiv"} %} + struct Int128 + @[AlwaysInline] + def {{ op.id }}(other : Float32) : Float32 + to_f32 {{ op.id }} other + end + + @[AlwaysInline] + def {{ op.id }}(other : Float64) : Float64 + to_f64 {{ op.id }} other + end + end + + struct UInt128 + @[AlwaysInline] + def {{ op.id }}(other : Float32) : Float32 + to_f32 {{ op.id }} other + end + + @[AlwaysInline] + def {{ op.id }}(other : Float64) : Float64 + to_f64 {{ op.id }} other + end + end + + struct Float32 + @[AlwaysInline] + def {{ op.id }}(other : Int128) : Float32 + self.{{ op.id }}(other.to_f32) + end + + @[AlwaysInline] + def {{ op.id }}(other : UInt128) : Float32 + self.{{ op.id }}(other.to_f32) + end + end + + struct Float64 + @[AlwaysInline] + def {{ op.id }}(other : Int128) : Float64 + self.{{ op.id }}(other.to_f64) + end + + @[AlwaysInline] + def {{ op.id }}(other : UInt128) : Float64 + self.{{ op.id }}(other.to_f64) + end + end + {% end %} + {% end %} {% end %} From 018bfbadb2905a80e2bc48ab51b1364db136e6c1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 25 Mar 2023 19:04:02 +0800 Subject: [PATCH 0389/1551] Remove `__declspec(dllimport)` from Windows libiconv build (#13219) --- .github/workflows/win.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 21d7ff6bcc98..fd1720ec11a9 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -96,6 +96,8 @@ jobs: if: steps.cache-libs.outputs.cache-hit != 'true' working-directory: ./libiconv run: | + sed -i 's|__declspec (dllimport) ||' libiconv\include\iconv.h + echo ' $(MsbuildThisFileDirectory)\Override.props From bd2f527b9e21cbcaefce92ffa5fad56f6aaa6a69 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 25 Mar 2023 19:04:55 +0800 Subject: [PATCH 0390/1551] Windows: detect stack overflows on non-main `Fiber`s (#13220) --- spec/std/kernel_spec.cr | 2 +- src/crystal/system/win32/fiber.cr | 23 +++++++++++++++++--- src/exception/call_stack/stackwalk.cr | 2 +- src/lib_c/x86_64-windows-msvc/c/memoryapi.cr | 1 + src/lib_c/x86_64-windows-msvc/c/winnt.cr | 3 ++- 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index fefe880d9350..c89fea79b6f2 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -276,7 +276,7 @@ describe "hardware exception" do end {% end %} - pending_win32 "detects stack overflow on a fiber stack" do + it "detects stack overflow on a fiber stack" do status, _, error = compile_and_run_source <<-'CRYSTAL' def foo y = StaticArray(Int8, 512).new(0) diff --git a/src/crystal/system/win32/fiber.cr b/src/crystal/system/win32/fiber.cr index 9620dbaf1c79..4db7d8df9e93 100644 --- a/src/crystal/system/win32/fiber.cr +++ b/src/crystal/system/win32/fiber.cr @@ -1,14 +1,31 @@ require "c/memoryapi" +require "c/sysinfoapi" require "c/winnt" module Crystal::System::Fiber - def self.allocate_stack(stack_size) : Void* - memory_pointer = LibC.VirtualAlloc(nil, stack_size, LibC::MEM_COMMIT | LibC::MEM_RESERVE, LibC::PAGE_READWRITE) + # stack size in bytes needed for last-minute error handling in case of a stack + # overflow + RESERVED_STACK_SIZE = LibC::DWORD.new(0x10000) + + # the reserved stack size, plus the size of a single page + @@total_reserved_size : LibC::DWORD = begin + LibC.GetNativeSystemInfo(out system_info) + system_info.dwPageSize + RESERVED_STACK_SIZE + end - if memory_pointer.null? + def self.allocate_stack(stack_size) : Void* + unless memory_pointer = LibC.VirtualAlloc(nil, stack_size, LibC::MEM_COMMIT | LibC::MEM_RESERVE, LibC::PAGE_READWRITE) raise RuntimeError.from_winerror("VirtualAlloc") end + # Detects stack overflows by guarding the top of the stack, similar to + # `LibC.mprotect`. Windows will fail to allocate a new guard page for these + # fiber stacks and trigger a stack overflow exception + if LibC.VirtualProtect(memory_pointer, @@total_reserved_size, LibC::PAGE_READWRITE | LibC::PAGE_GUARD, out _) == 0 + LibC.VirtualFree(memory_pointer, 0, LibC::MEM_RELEASE) + raise RuntimeError.from_winerror("VirtualProtect") + end + memory_pointer end diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index 73b1060aa58b..f8377a3b5f69 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -52,7 +52,7 @@ struct Exception::CallStack # ensure that even in the case of stack overflow there is enough reserved # stack space for recovery - stack_size = LibC::DWORD.new!(0x10000) + stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE LibC.SetThreadStackGuarantee(pointerof(stack_size)) end diff --git a/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr b/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr index dd457ee1b283..7b0103713d8a 100644 --- a/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr @@ -10,5 +10,6 @@ lib LibC MEM_RELEASE = 0x8000 fun VirtualFree(lpAddress : Void*, dwSize : SizeT, dwFreeType : DWORD) : BOOL + fun VirtualProtect(lpAddress : Void*, dwSize : SizeT, flNewProtect : DWORD, lpfOldProtect : DWORD*) : BOOL fun VirtualQuery(lpAddress : Void*, lpBuffer : MEMORY_BASIC_INFORMATION*, dwLength : SizeT) end diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index cd2e6006e425..d0d419f4fa01 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -26,7 +26,8 @@ lib LibC FILE_WRITE_ATTRIBUTES = 0x0100 # Memory protection constants - PAGE_READWRITE = 0x04 + PAGE_READWRITE = 0x04 + PAGE_GUARD = 0x100 PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 SYNCHRONIZE = 0x00100000 From 5aac12491ca5ae740dae83e0b4afbca8d5a01d17 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 25 Mar 2023 19:07:06 +0800 Subject: [PATCH 0391/1551] Fix `Int128`-and-float conversion overflow checks on Windows LLVM 14+ (#13222) --- src/crystal/compiler_rt.cr | 80 ++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/src/crystal/compiler_rt.cr b/src/crystal/compiler_rt.cr index 2c23b672f69b..c84aec92f5bc 100644 --- a/src/crystal/compiler_rt.cr +++ b/src/crystal/compiler_rt.cr @@ -87,6 +87,78 @@ require "./compiler_rt/divmod128.cr" # https://github.com/llvm/llvm-project/commit/d6216e2cd1a5e07f8509215ee5422ff5ee358da8 {% if compare_versions(Crystal::LLVM_VERSION, "14.0.0") >= 0 %} + # the following overflow comparisons must be identical to the ones in + # `Crystal::CodeGenVisitor#codegen_out_of_range` + + struct Int128 + @[AlwaysInline] + def to_f32 : Float32 + __floattisf(self) + end + + @[AlwaysInline] + def to_f64 : Float64 + __floattidf(self) + end + end + + struct UInt128 + @[AlwaysInline] + def to_f32 : Float32 + # Float32::MAX.to_u128! (it is okay to use a literal here because + # support for LLVM 14 was added after 128-bit literals) + if self > 340282346638528859811704183484516925440_u128 + raise OverflowError.new + end + __floatuntisf(self) + end + + @[AlwaysInline] + def to_f64 : Float64 + __floatuntidf(self) + end + end + + struct Float32 + @[AlwaysInline] + def to_i128 : Int128 + # Int128::MIN.to_f32!..Int128::MAX.to_f32!.prev_float + if !(self >= -1.7014118e+38_f32) || self > 1.7014117e+38_f32 + raise OverflowError.new + end + __fixsfti(self) + end + + @[AlwaysInline] + def to_u128 : UInt128 + # UInt128::MIN.to_f32!..Float32::MAX + if !(self >= 0_f32) || self > 3.4028235e+38_f32 + raise OverflowError.new + end + __fixunssfti(self) + end + end + + struct Float64 + @[AlwaysInline] + def to_i128 : Int128 + # Int128::MIN.to_f64!..Int128::MAX.to_f64!.prev_float + if !(self >= -1.7014118346046923e+38_f64) || self > 1.7014118346046921e+38_f64 + raise OverflowError.new + end + __fixdfti(self) + end + + @[AlwaysInline] + def to_u128 : UInt128 + # UInt128::MIN.to_f64!..UInt128::MAX.to_f64!.prev_float + if !(self >= 0_f64) || self > 3.4028236692093843e+38_f64 + raise OverflowError.new + end + __fixunsdfti(self) + end + end + {% for v in [ {Int128, "to_f32", Float32, "__floattisf"}, {Int128, "to_f64", Float64, "__floattidf"}, @@ -99,15 +171,9 @@ require "./compiler_rt/divmod128.cr" ] %} {% type, method, ret, rt_method = v %} struct {{ type.id }} - @[AlwaysInline] - def {{ method.id }} : {{ ret.id }} - raise OverflowError.new unless {{ ret.id }}::MIN <= self <= {{ ret.id }}::MAX - {{ ret.id }}.new!({{ rt_method.id }}(self)) - end - @[AlwaysInline] def {{ method.id }}! : {{ ret.id }} - {{ ret.id }}.new!({{ rt_method.id }}(self)) + {{ rt_method.id }}(self) end end {% end %} From a69aa14b50672b8d9bef3ae723cbf7f289dc4fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 25 Mar 2023 17:08:00 +0100 Subject: [PATCH 0392/1551] Formatter: Add feature flag for `method_signature_yield` (#13215) --- spec/compiler/formatter/formatter_spec.cr | 212 ++++++++++++++++++---- src/compiler/crystal/tools/formatter.cr | 20 +- 2 files changed, 193 insertions(+), 39 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index bba24f11a90e..7c9cc990e116 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1,10 +1,10 @@ require "spec" require "../../../src/compiler/crystal/formatter" -private def assert_format(input, output = input, strict = false, file = __FILE__, line = __LINE__) +private def assert_format(input, output = input, strict = false, flags = nil, file = __FILE__, line = __LINE__) it "formats #{input.inspect}", file, line do output = "#{output}\n" unless strict - result = Crystal.format(input) + result = Crystal.format(input, flags: flags) unless result == output message = <<-ERROR Expected @@ -553,133 +553,122 @@ describe Crystal::Formatter do assert_format "with foo yield bar" context "adds `&` to yielding methods that don't have a block parameter (#8764)" do - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo yield end CRYSTAL - <<-CRYSTAL def foo(&) yield end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo() yield end CRYSTAL - <<-CRYSTAL def foo(&) yield end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo( ) yield end CRYSTAL - <<-CRYSTAL def foo(&) yield end CRYSTAL # #13091 - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo # bar yield end CRYSTAL - <<-CRYSTAL def foo(&) # bar yield end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo(x) yield end CRYSTAL - <<-CRYSTAL def foo(x, &) yield end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo(x ,) yield end CRYSTAL - <<-CRYSTAL def foo(x, &) yield end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo(x, y) yield end CRYSTAL - <<-CRYSTAL def foo(x, y, &) yield end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo(x, y,) yield end CRYSTAL - <<-CRYSTAL def foo(x, y, &) yield end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo(x ) yield end CRYSTAL - <<-CRYSTAL def foo(x, &) yield end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo(x, ) yield end CRYSTAL - <<-CRYSTAL def foo(x, &) yield end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo( x) yield end CRYSTAL - <<-CRYSTAL def foo( x, & ) @@ -687,13 +676,12 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo( x, y) yield end CRYSTAL - <<-CRYSTAL def foo( x, y, & ) @@ -701,14 +689,13 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo( x, y) yield end CRYSTAL - <<-CRYSTAL def foo( x, y, & @@ -717,14 +704,13 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] def foo( x, ) yield end CRYSTAL - <<-CRYSTAL def foo( x, & @@ -733,7 +719,171 @@ describe Crystal::Formatter do end CRYSTAL - assert_format "macro f\n yield\n {{ yield }}\nend" + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + def foo(a, **b) + yield + end + CRYSTAL + def foo(a, **b, &) + yield + end + CRYSTAL + + assert_format "macro f\n yield\n {{ yield }}\nend", flags: %w[method_signature_yield] + end + + context "does not add `&` without flag `method_signature_yield`" do + assert_format <<-CRYSTAL + def foo + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL + def foo() + yield + end + CRYSTAL + def foo + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL + def foo( + ) + yield + end + CRYSTAL + def foo + yield + end + CRYSTAL + + # #13091 + assert_format <<-CRYSTAL + def foo # bar + yield + end + CRYSTAL + + assert_format <<-CRYSTAL + def foo(x) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL + def foo(x ,) + yield + end + CRYSTAL + def foo(x) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL + def foo(x, + y) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL + def foo(x, + y,) + yield + end + CRYSTAL + def foo(x, + y) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL + def foo(x + ) + yield + end + CRYSTAL + def foo(x) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL + def foo(x, + ) + yield + end + CRYSTAL + def foo(x) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL + def foo( + x) + yield + end + CRYSTAL + def foo( + x + ) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL + def foo( + x, y) + yield + end + CRYSTAL + def foo( + x, y + ) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL + def foo( + x, + y) + yield + end + CRYSTAL + def foo( + x, + y + ) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL + def foo( + x, + ) + yield + end + CRYSTAL + def foo( + x + ) + yield + end + CRYSTAL + + assert_format <<-CRYSTAL + def foo(a, **b) + yield + end + CRYSTAL end assert_format "1 + 2", "1 + 2" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index e69b98161d20..25915b29ab75 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1,12 +1,12 @@ require "../syntax" module Crystal - def self.format(source, filename = nil, report_warnings : IO? = nil) - Crystal::Formatter.format(source, filename: filename, report_warnings: report_warnings) + def self.format(source, filename = nil, report_warnings : IO? = nil, flags : Array(String)? = nil) + Crystal::Formatter.format(source, filename: filename, report_warnings: report_warnings, flags: flags) end class Formatter < Visitor - def self.format(source, filename = nil, report_warnings : IO? = nil) + def self.format(source, filename = nil, report_warnings : IO? = nil, flags : Array(String)? = nil) parser = Parser.new(source) parser.filename = filename nodes = parser.parse @@ -17,7 +17,7 @@ module Crystal parser.warnings.report(report_warnings) end - formatter = new(source) + formatter = new(source, flags: flags) formatter.skip_space_or_newline nodes.accept formatter formatter.finish @@ -102,7 +102,7 @@ module Crystal property indent property subformat_nesting = 0 - def initialize(source) + def initialize(source, @flags : Array(String)? = nil) @lexer = Lexer.new(source) @lexer.comments_enabled = true @lexer.count_whitespace = true @@ -158,6 +158,10 @@ module Crystal @vars = [Set(String).new] end + def flag?(flag) + !!@flags.try(&.includes?(flag)) + end + def end_visit_any(node) case node when StringInterpolation @@ -1470,7 +1474,7 @@ module Crystal # this formats `def foo # ...` to `def foo(&) # ...` for yielding # methods before consuming the comment line if node.block_arity && node.args.empty? && !node.block_arg && !node.double_splat - write "(&)" + write "(&)" if flag?("method_signature_yield") end skip_space consume_newline: false @@ -1517,7 +1521,7 @@ module Crystal end def format_def_args(node : Def | Macro) - yields = node.is_a?(Def) && !node.block_arity.nil? + yields = node.is_a?(Def) && !node.block_arity.nil? && flag?("method_signature_yield") format_def_args node.args, node.block_arg, node.splat_index, false, node.double_splat, yields end @@ -1675,7 +1679,7 @@ module Crystal elsif @token.type.op_rparen? && has_more && !just_wrote_newline # if we found a `)` and there are still more parameters to write, it # must have been a missing `&` for a def that yields - write " " + write " " if flag?("method_signature_yield") end just_wrote_newline From 8a509b667f65a3a1a4b9d95afcc377c4db5b7f63 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 26 Mar 2023 18:21:30 +0800 Subject: [PATCH 0393/1551] Implement `File.readlink` on Windows (#13195) --- spec/std/dir_spec.cr | 31 +++-- spec/std/file_spec.cr | 46 +++++--- spec/std/file_utils_spec.cr | 2 +- src/crystal/system/win32/dir.cr | 4 +- src/crystal/system/win32/file.cr | 122 ++++++++++++++++---- src/crystal/system/win32/file_info.cr | 2 +- src/file.cr | 5 +- src/lib_c/x86_64-windows-msvc/c/errno.cr | 1 + src/lib_c/x86_64-windows-msvc/c/ioapiset.cr | 11 ++ src/lib_c/x86_64-windows-msvc/c/ntifs.cr | 37 ++++++ src/lib_c/x86_64-windows-msvc/c/winioctl.cr | 4 + src/lib_c/x86_64-windows-msvc/c/winnt.cr | 5 + 12 files changed, 217 insertions(+), 53 deletions(-) create mode 100644 src/lib_c/x86_64-windows-msvc/c/ntifs.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/winioctl.cr diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index 47d95806e72a..d7bfa6cdb19f 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -350,7 +350,7 @@ describe "Dir" do ].sort end - pending_win32 "matches symlinks" do + it "matches symlinks" do link = datapath("f1_link.txt") non_link = datapath("non_link.txt") @@ -369,19 +369,30 @@ describe "Dir" do end end - pending_win32 "matches symlink dir" do + it "matches symlink dir" do with_tempfile "symlink_dir" do |path| - Dir.mkdir_p(Path[path, "glob"]) target = Path[path, "target"] - Dir.mkdir_p(target) + non_link = target / "a.txt" + link_dir = Path[path, "glob", "dir"] - File.write(target / "a.txt", "") - File.symlink(target, Path[path, "glob", "dir"]) + Dir.mkdir_p(Path[path, "glob"]) + Dir.mkdir_p(target) - Dir.glob("#{path}/glob/*/a.txt").sort.should eq [] of String - Dir.glob("#{path}/glob/*/a.txt", follow_symlinks: true).sort.should eq [ - "#{path}/glob/dir/a.txt", - ] + File.write(non_link, "") + File.symlink(target, link_dir) + + begin + Dir.glob("#{path}/glob/*/a.txt").sort.should eq [] of String + Dir.glob("#{path}/glob/*/a.txt", follow_symlinks: true).sort.should eq [ + File.join(path, "glob", "dir", "a.txt"), + ] + ensure + # FIXME: `with_tempfile` will delete this symlink directory using + # `File.delete` otherwise, see #13194 + {% if flag?(:win32) %} + Dir.delete(link_dir) + {% end %} + end end end diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 538fa6a165a2..a0cafb5cad39 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -226,7 +226,7 @@ describe "File" do it "creates a symbolic link" do in_path = datapath("test_file.txt") with_tempfile("test_file_link.txt") do |out_path| - File.symlink(File.real_path(in_path), out_path) + File.symlink(File.realpath(in_path), out_path) File.symlink?(out_path).should be_true File.same?(in_path, out_path, follow_symlinks: true).should be_true end @@ -234,11 +234,6 @@ describe "File" do end describe "symlink?" do - # TODO: this fails depending on how Git checks out the repository - pending_win32 "gives true" do - File.symlink?(datapath("symlink.txt")).should be_true - end - it "gives false" do File.symlink?(datapath("test_file.txt")).should be_false File.symlink?(datapath("unknown_file.txt")).should be_false @@ -254,7 +249,7 @@ describe "File" do end describe ".readlink" do - pending_win32 "reads link" do + it "reads link" do File.readlink(datapath("symlink.txt")).should eq "test_file.txt" end end @@ -393,17 +388,17 @@ describe "File" do end end - # See TODO in win32 Crystal::System::File.chmod - pending_win32 "follows symlinks" do + it "follows symlinks" do with_tempfile("chmod-destination.txt", "chmod-source.txt") do |source_path, target_path| File.write(source_path, "") - File.symlink(File.real_path(source_path), target_path) + File.symlink(File.realpath(source_path), target_path) File.symlink?(target_path).should be_true + File.chmod(source_path, 0o664) File.chmod(target_path, 0o444) - File.info(target_path).permissions.should eq(normalize_permissions(0o444, directory: false)) + File.info(source_path).permissions.should eq(normalize_permissions(0o444, directory: false)) end end @@ -431,10 +426,15 @@ describe "File" do info.type.should eq(File::Type::CharacterDevice) end - # TODO: this fails depending on how Git checks out the repository - pending_win32 "gets for a symlink" do - info = File.info(datapath("symlink.txt"), follow_symlinks: false) - info.type.should eq(File::Type::Symlink) + it "gets for a symlink" do + file_path = File.expand_path(datapath("test_file.txt")) + with_tempfile("symlink.txt") do |symlink_path| + File.symlink(file_path, symlink_path) + info = File.info(symlink_path, follow_symlinks: false) + info.type.should eq(File::Type::Symlink) + info = File.info(symlink_path, follow_symlinks: true) + info.type.should_not eq(File::Type::Symlink) + end end it "gets for open file" do @@ -603,8 +603,7 @@ describe "File" do end end - # TODO: see Crystal::System::File.realpath TODO - pending_win32 "expands paths of symlinks" do + it "expands paths of symlinks" do file_path = File.expand_path(datapath("test_file.txt")) with_tempfile("symlink.txt") do |symlink_path| File.symlink(file_path, symlink_path) @@ -613,6 +612,19 @@ describe "File" do real_symlink_path.should eq(real_file_path) end end + + it "expands multiple layers of symlinks" do + file_path = File.expand_path(datapath("test_file.txt")) + with_tempfile("symlink1.txt") do |symlink_path1| + with_tempfile("symlink2.txt") do |symlink_path2| + File.symlink(file_path, symlink_path1) + File.symlink(symlink_path1, symlink_path2) + real_symlink_path = File.realpath(symlink_path2) + real_file_path = File.realpath(file_path) + real_symlink_path.should eq(real_file_path) + end + end + end end describe "write" do diff --git a/spec/std/file_utils_spec.cr b/spec/std/file_utils_spec.cr index 54dd0bf4de7e..0d536fc7a97a 100644 --- a/spec/std/file_utils_spec.cr +++ b/spec/std/file_utils_spec.cr @@ -670,7 +670,7 @@ describe "FileUtils" do end end - pending_win32 "works with a nonexistent source" do + it "works with a nonexistent source" do with_tempfile("ln_s_src_missing", "ln_s_dst_missing") do |path1, path2| test_with_string_and_path(path1, path2) do |arg1, arg2| FileUtils.ln_s(arg1, arg2) diff --git a/src/crystal/system/win32/dir.cr b/src/crystal/system/win32/dir.cr index 80189b9f030b..fb35e9dafdd0 100644 --- a/src/crystal/system/win32/dir.cr +++ b/src/crystal/system/win32/dir.cr @@ -53,7 +53,9 @@ module Crystal::System::Dir def self.data_to_entry(data) name = String.from_utf16(data.cFileName.to_unsafe)[0] - dir = (data.dwFileAttributes & LibC::FILE_ATTRIBUTE_DIRECTORY) != 0 + unless data.dwFileAttributes.bits_set?(LibC::FILE_ATTRIBUTE_REPARSE_POINT) && data.dwReserved0 == LibC::IO_REPARSE_TAG_SYMLINK + dir = (data.dwFileAttributes & LibC::FILE_ATTRIBUTE_DIRECTORY) != 0 + end Entry.new(name, dir) end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 5c5842fcdb34..095b5fc45924 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -5,6 +5,8 @@ require "c/sys/utime" require "c/sys/stat" require "c/winbase" require "c/handleapi" +require "c/ntifs" +require "c/winioctl" module Crystal::System::File def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions) : LibC::Int @@ -113,8 +115,6 @@ module Crystal::System::File WinError::ERROR_INVALID_NAME, } - REPARSE_TAG_NAME_SURROGATE_MASK = 1 << 29 - private def self.check_not_found_error(message, path) error = WinError.value if NOT_FOUND_ERRORS.includes? error @@ -146,7 +146,7 @@ module Crystal::System::File raise RuntimeError.from_winerror("FindClose") end - if find_data.dwReserved0.bits_set? REPARSE_TAG_NAME_SURROGATE_MASK + if find_data.dwReserved0 == LibC::IO_REPARSE_TAG_SYMLINK return ::File::Info.new(find_data) end end @@ -179,7 +179,10 @@ module Crystal::System::File info?(path, follow_symlinks) || raise ::File::Error.from_winerror("Unable to get file info", file: path) end - def self.exists?(path) + def self.exists?(path, *, follow_symlinks = true) + if follow_symlinks + path = realpath?(path) || return false + end accessible?(path, 0) end @@ -210,7 +213,11 @@ module Crystal::System::File def self.chmod(path : String, mode : Int32 | ::File::Permissions) : Nil mode = ::File::Permissions.new(mode) unless mode.is_a? ::File::Permissions - # TODO: dereference symlinks + unless exists?(path, follow_symlinks: false) + raise ::File::Error.from_os_error("Error changing permissions", Errno::ENOENT, file: path) + end + + path = realpath(path) attributes = LibC.GetFileAttributesW(System.to_wstr(path)) if attributes == LibC::INVALID_FILE_ATTRIBUTES @@ -245,26 +252,37 @@ module Crystal::System::File end end - def self.realpath(path : String) : String - # TODO: read links using https://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx - win_path = System.to_wstr(path) - - realpath = System.retry_wstr_buffer do |buffer, small_buf| - len = LibC.GetFullPathNameW(win_path, buffer.size, buffer, nil) - if 0 < len < buffer.size - break String.from_utf16(buffer[0, len]) - elsif small_buf && len > 0 - next len - else - raise ::File::Error.from_winerror("Error resolving real path", file: path) + private REALPATH_SYMLINK_LIMIT = 100 + + private def self.realpath?(path : String) : String? + REALPATH_SYMLINK_LIMIT.times do + win_path = System.to_wstr(path) + + realpath = System.retry_wstr_buffer do |buffer, small_buf| + len = LibC.GetFullPathNameW(win_path, buffer.size, buffer, nil) + if 0 < len < buffer.size + break String.from_utf16(buffer[0, len]) + elsif small_buf && len > 0 + next len + else + raise ::File::Error.from_winerror("Error resolving real path", file: path) + end + end + + if symlink_info = symlink_info?(realpath) + new_path, is_relative = symlink_info + path = is_relative ? ::File.expand_path(new_path, ::File.dirname(realpath)) : new_path + next end - end - unless exists? realpath - raise ::File::Error.from_os_error("Error resolving real path", Errno::ENOENT, file: path) + return exists?(realpath, follow_symlinks: false) ? realpath : nil end - realpath + raise ::File::Error.from_os_error("Too many symbolic links", Errno::ELOOP, file: path) + end + + def self.realpath(path : String) : String + realpath?(path) || raise ::File::Error.from_os_error("Error resolving real path", Errno::ENOENT, file: path) end def self.link(old_path : String, new_path : String) : Nil @@ -297,8 +315,68 @@ module Crystal::System::File end end + private def self.symlink_info?(path) + handle = LibC.CreateFileW( + System.to_wstr(path), + LibC::FILE_READ_ATTRIBUTES, + LibC::DEFAULT_SHARE_MODE, + nil, + LibC::OPEN_EXISTING, + LibC::FILE_FLAG_BACKUP_SEMANTICS | LibC::FILE_FLAG_OPEN_REPARSE_POINT, + LibC::HANDLE.null + ) + + return nil if handle == LibC::INVALID_HANDLE_VALUE + + begin + size = 0x40 + buf = Pointer(UInt8).malloc(size) + + while true + if LibC.DeviceIoControl(handle, LibC::FSCTL_GET_REPARSE_POINT, nil, 0, buf, size, out _, nil) != 0 + reparse_data = buf.as(LibC::REPARSE_DATA_BUFFER*) + if reparse_data.value.reparseTag == LibC::IO_REPARSE_TAG_SYMLINK + symlink_data = reparse_data.value.dummyUnionName.symbolicLinkReparseBuffer + path_buffer = reparse_data.value.dummyUnionName.symbolicLinkReparseBuffer.pathBuffer.to_unsafe.as(UInt8*) + is_relative = symlink_data.flags.bits_set?(LibC::SYMLINK_FLAG_RELATIVE) + + # the print name is not necessarily set; fall back to substitute + # name if unavailable + if (name_len = symlink_data.printNameLength) > 0 + name_ptr = path_buffer + symlink_data.printNameOffset + name = String.from_utf16(Slice.new(name_ptr, name_len).unsafe_slice_of(UInt16)) + return {name, is_relative} + end + + name_len = symlink_data.substituteNameLength + name_ptr = path_buffer + symlink_data.substituteNameOffset + name = String.from_utf16(Slice.new(name_ptr, name_len).unsafe_slice_of(UInt16)) + # remove the internal prefix for NT paths which shows up when e.g. + # creating a symbolic link with an absolute source + # TODO: support the other possible paths, for example see + # https://github.com/golang/go/blob/ab28b834c4a38bd2295ee43eca4f9e38c28d54a2/src/os/file_windows.go#L362 + if name.starts_with?(%q(\??\)) && name[5]? == ':' + name = name[4..] + end + return {name, is_relative} + else + raise ::File::Error.new("Not a symlink", file: path) + end + end + + return nil if WinError.value != WinError::ERROR_MORE_DATA || size == LibC::MAXIMUM_REPARSE_DATA_BUFFER_SIZE + size *= 2 + buf = buf.realloc(size) + end + ensure + LibC.CloseHandle(handle) + end + end + def self.readlink(path) : String - raise NotImplementedError.new("readlink") + info = symlink_info?(path) || raise ::File::Error.new("Cannot read link", file: path) + path, _is_relative = info + path end def self.rename(old_path : String, new_path : String) : ::File::Error? diff --git a/src/crystal/system/win32/file_info.cr b/src/crystal/system/win32/file_info.cr index 10a163c44644..ba5ed3007a88 100644 --- a/src/crystal/system/win32/file_info.cr +++ b/src/crystal/system/win32/file_info.cr @@ -54,7 +54,7 @@ module Crystal::System::FileInfo when LibC::FILE_TYPE_DISK # See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365511(v=vs.85).aspx if @file_attributes.dwFileAttributes.bits_set?(LibC::FILE_ATTRIBUTE_REPARSE_POINT) && - @reparse_tag.bits_set? File::REPARSE_TAG_NAME_SURROGATE_MASK + @reparse_tag == LibC::IO_REPARSE_TAG_SYMLINK ::File::Type::Symlink elsif @file_attributes.dwFileAttributes.bits_set? LibC::FILE_ATTRIBUTE_DIRECTORY ::File::Type::Directory diff --git a/src/file.cr b/src/file.cr index 3fd22017e1e0..070ccfc0445f 100644 --- a/src/file.cr +++ b/src/file.cr @@ -168,7 +168,10 @@ class File < IO::FileDescriptor Crystal::System::File.info(path.to_s, follow_symlinks) end - # Returns `true` if *path* exists else returns `false` + # Returns whether the file given by *path* exists. + # + # Symbolic links are dereferenced, posibly recursively. Returns `false` if a + # symbolic link refers to a non-existent file. # # ``` # File.delete("foo") if File.exists?("foo") diff --git a/src/lib_c/x86_64-windows-msvc/c/errno.cr b/src/lib_c/x86_64-windows-msvc/c/errno.cr index f5c795889f55..691a04fac013 100644 --- a/src/lib_c/x86_64-windows-msvc/c/errno.cr +++ b/src/lib_c/x86_64-windows-msvc/c/errno.cr @@ -46,6 +46,7 @@ lib LibC ECONNRESET = 108 EINPROGRESS = 112 EISCONN = 113 + ELOOP = 114 ENOPROTOOPT = 123 alias ErrnoT = Int diff --git a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr index df42772fd20a..eaef34deaa3e 100644 --- a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr +++ b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr @@ -21,4 +21,15 @@ lib LibC fun CancelIo( hFile : HANDLE ) : BOOL + + fun DeviceIoControl( + hDevice : HANDLE, + dwIoControlCode : DWORD, + lpInBuffer : Void*, + nInBufferSize : DWORD, + lpOutBuffer : Void*, + nOutBufferSize : DWORD, + lpBytesReturned : DWORD*, + lpOverlapped : OVERLAPPED* + ) : BOOL end diff --git a/src/lib_c/x86_64-windows-msvc/c/ntifs.cr b/src/lib_c/x86_64-windows-msvc/c/ntifs.cr new file mode 100644 index 000000000000..67f51c5b388f --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/ntifs.cr @@ -0,0 +1,37 @@ +lib LibC + SYMLINK_FLAG_RELATIVE = 0x00000001 + + struct REPARSE_DATA_BUFFER_struct1 + substituteNameOffset : UShort + substituteNameLength : UShort + printNameOffset : UShort + printNameLength : UShort + flags : ULong + pathBuffer : WCHAR[1] + end + + struct REPARSE_DATA_BUFFER_struct2 + substituteNameOffset : UShort + substituteNameLength : UShort + printNameOffset : UShort + printNameLength : UShort + pathBuffer : WCHAR[1] + end + + struct REPARSE_DATA_BUFFER_struct3 + dataBuffer : UChar[1] + end + + union REPARSE_DATA_BUFFER_union + symbolicLinkReparseBuffer : REPARSE_DATA_BUFFER_struct1 + mountPointReparseBuffer : REPARSE_DATA_BUFFER_struct2 + genericReparseBuffer : REPARSE_DATA_BUFFER_struct3 + end + + struct REPARSE_DATA_BUFFER + reparseTag : ULong + reparseDataLength : UShort + reserved : UShort + dummyUnionName : REPARSE_DATA_BUFFER_union + end +end diff --git a/src/lib_c/x86_64-windows-msvc/c/winioctl.cr b/src/lib_c/x86_64-windows-msvc/c/winioctl.cr new file mode 100644 index 000000000000..b7917826bbe3 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/winioctl.cr @@ -0,0 +1,4 @@ +lib LibC + FSCTL_SET_REPARSE_POINT = 0x000900A4 # CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) + FSCTL_GET_REPARSE_POINT = 0x000900A8 # CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) +end diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index d0d419f4fa01..52cfab5ebf89 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -25,6 +25,11 @@ lib LibC FILE_READ_ATTRIBUTES = 0x80 FILE_WRITE_ATTRIBUTES = 0x0100 + MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000 + + IO_REPARSE_TAG_SYMLINK = 0xA000000C_u32 + IO_REPARSE_TAG_AF_UNIX = 0x80000023_u32 + # Memory protection constants PAGE_READWRITE = 0x04 PAGE_GUARD = 0x100 From 62f27b208211eab478d98c5d734bfc6f17807786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 27 Mar 2023 12:44:34 +0200 Subject: [PATCH 0394/1551] Drop privileges in chroot spec (#13226) --- spec/std/process_spec.cr | 6 ++++++ src/lib_c/aarch64-android/c/unistd.cr | 1 + src/lib_c/aarch64-darwin/c/unistd.cr | 1 + src/lib_c/aarch64-linux-gnu/c/unistd.cr | 1 + src/lib_c/aarch64-linux-musl/c/unistd.cr | 1 + src/lib_c/arm-linux-gnueabihf/c/unistd.cr | 1 + src/lib_c/i386-linux-gnu/c/unistd.cr | 1 + src/lib_c/i386-linux-musl/c/unistd.cr | 1 + src/lib_c/x86_64-darwin/c/unistd.cr | 1 + src/lib_c/x86_64-dragonfly/c/unistd.cr | 1 + src/lib_c/x86_64-freebsd/c/unistd.cr | 1 + src/lib_c/x86_64-linux-gnu/c/unistd.cr | 1 + src/lib_c/x86_64-linux-musl/c/unistd.cr | 1 + src/lib_c/x86_64-netbsd/c/unistd.cr | 1 + src/lib_c/x86_64-openbsd/c/unistd.cr | 1 + 15 files changed, 20 insertions(+) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index fff7d26169b4..e9e6848828eb 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -428,6 +428,12 @@ describe Process do {% if flag?(:unix) && !flag?(:android) %} it "raises when unprivileged" do status, output, _ = compile_and_run_source <<-'CRYSTAL' + # Try to drop privileges. Ignoring any errors because dropping is only + # necessary for a privileged user and it doesn't matter when it fails + # for an unprivileged one. + # This particular UID is often attributed to the `nobody` user. + LibC.setuid(65534) + begin Process.chroot(".") puts "FAIL" diff --git a/src/lib_c/aarch64-android/c/unistd.cr b/src/lib_c/aarch64-android/c/unistd.cr index 81b9a256c305..c2e22a6ad0fa 100644 --- a/src/lib_c/aarch64-android/c/unistd.cr +++ b/src/lib_c/aarch64-android/c/unistd.cr @@ -30,6 +30,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(__fd : Int) : Int fun ttyname_r(__fd : Int, __buf : Char*, __buf_size : SizeT) : Int fun lchown(__path : Char*, __owner : UidT, __group : GidT) : Int diff --git a/src/lib_c/aarch64-darwin/c/unistd.cr b/src/lib_c/aarch64-darwin/c/unistd.cr index 5847c0f49154..133a390be959 100644 --- a/src/lib_c/aarch64-darwin/c/unistd.cr +++ b/src/lib_c/aarch64-darwin/c/unistd.cr @@ -30,6 +30,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(x0 : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(x0 : Char*, x1 : UidT, x2 : GidT) : Int diff --git a/src/lib_c/aarch64-linux-gnu/c/unistd.cr b/src/lib_c/aarch64-linux-gnu/c/unistd.cr index 9a09fd9ac1eb..1d6cbc3d0b3a 100644 --- a/src/lib_c/aarch64-linux-gnu/c/unistd.cr +++ b/src/lib_c/aarch64-linux-gnu/c/unistd.cr @@ -30,6 +30,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(fd : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(file : Char*, owner : UidT, group : GidT) : Int diff --git a/src/lib_c/aarch64-linux-musl/c/unistd.cr b/src/lib_c/aarch64-linux-musl/c/unistd.cr index fc3add376a53..d9626788ffdc 100644 --- a/src/lib_c/aarch64-linux-musl/c/unistd.cr +++ b/src/lib_c/aarch64-linux-musl/c/unistd.cr @@ -30,6 +30,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(x0 : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(x0 : Char*, x1 : UidT, x2 : GidT) : Int diff --git a/src/lib_c/arm-linux-gnueabihf/c/unistd.cr b/src/lib_c/arm-linux-gnueabihf/c/unistd.cr index 924fb7c1e7c8..67dac4992d0f 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/unistd.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/unistd.cr @@ -30,6 +30,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(fd : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(file : Char*, owner : UidT, group : GidT) : Int diff --git a/src/lib_c/i386-linux-gnu/c/unistd.cr b/src/lib_c/i386-linux-gnu/c/unistd.cr index bbc9b3fc4c81..143bb25062e7 100644 --- a/src/lib_c/i386-linux-gnu/c/unistd.cr +++ b/src/lib_c/i386-linux-gnu/c/unistd.cr @@ -30,6 +30,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(fd : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(file : Char*, owner : UidT, group : GidT) : Int diff --git a/src/lib_c/i386-linux-musl/c/unistd.cr b/src/lib_c/i386-linux-musl/c/unistd.cr index 281340e91d4d..2748bcf83752 100644 --- a/src/lib_c/i386-linux-musl/c/unistd.cr +++ b/src/lib_c/i386-linux-musl/c/unistd.cr @@ -30,6 +30,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(x0 : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(x0 : Char*, x1 : UidT, x2 : GidT) : Int diff --git a/src/lib_c/x86_64-darwin/c/unistd.cr b/src/lib_c/x86_64-darwin/c/unistd.cr index 5847c0f49154..133a390be959 100644 --- a/src/lib_c/x86_64-darwin/c/unistd.cr +++ b/src/lib_c/x86_64-darwin/c/unistd.cr @@ -30,6 +30,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(x0 : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(x0 : Char*, x1 : UidT, x2 : GidT) : Int diff --git a/src/lib_c/x86_64-dragonfly/c/unistd.cr b/src/lib_c/x86_64-dragonfly/c/unistd.cr index 24424d8d0711..8ce61f792735 100644 --- a/src/lib_c/x86_64-dragonfly/c/unistd.cr +++ b/src/lib_c/x86_64-dragonfly/c/unistd.cr @@ -28,6 +28,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(x0 : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(x0 : Char*, x1 : UidT, x2 : GidT) : Int diff --git a/src/lib_c/x86_64-freebsd/c/unistd.cr b/src/lib_c/x86_64-freebsd/c/unistd.cr index 3898d3f2e4ae..af5283c15d16 100644 --- a/src/lib_c/x86_64-freebsd/c/unistd.cr +++ b/src/lib_c/x86_64-freebsd/c/unistd.cr @@ -29,6 +29,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(x0 : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(x0 : Char*, x1 : UidT, x2 : GidT) : Int diff --git a/src/lib_c/x86_64-linux-gnu/c/unistd.cr b/src/lib_c/x86_64-linux-gnu/c/unistd.cr index 4f999029b989..9abd25899316 100644 --- a/src/lib_c/x86_64-linux-gnu/c/unistd.cr +++ b/src/lib_c/x86_64-linux-gnu/c/unistd.cr @@ -30,6 +30,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(fd : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(file : Char*, owner : UidT, group : GidT) : Int diff --git a/src/lib_c/x86_64-linux-musl/c/unistd.cr b/src/lib_c/x86_64-linux-musl/c/unistd.cr index 281340e91d4d..2748bcf83752 100644 --- a/src/lib_c/x86_64-linux-musl/c/unistd.cr +++ b/src/lib_c/x86_64-linux-musl/c/unistd.cr @@ -30,6 +30,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(x0 : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(x0 : Char*, x1 : UidT, x2 : GidT) : Int diff --git a/src/lib_c/x86_64-netbsd/c/unistd.cr b/src/lib_c/x86_64-netbsd/c/unistd.cr index a50be4c4e81e..081a6e9f7b00 100644 --- a/src/lib_c/x86_64-netbsd/c/unistd.cr +++ b/src/lib_c/x86_64-netbsd/c/unistd.cr @@ -29,6 +29,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(x0 : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown = __posix_lchown(x0 : Char*, x1 : UidT, x2 : GidT) : Int diff --git a/src/lib_c/x86_64-openbsd/c/unistd.cr b/src/lib_c/x86_64-openbsd/c/unistd.cr index 972d1990e72f..ef48b3b5af92 100644 --- a/src/lib_c/x86_64-openbsd/c/unistd.cr +++ b/src/lib_c/x86_64-openbsd/c/unistd.cr @@ -29,6 +29,7 @@ lib LibC fun getpid : PidT fun getppid : PidT fun getuid : UidT + fun setuid(uid : UidT) : Int fun isatty(x0 : Int) : Int fun ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int fun lchown(x0 : Char*, x1 : UidT, x2 : GidT) : Int From 7f188fd42ffb92fa4374f5f2caca2248f193973c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 28 Mar 2023 15:41:08 +0200 Subject: [PATCH 0395/1551] [CI] Increase `no_output_timeout` on circleci (cont.) (#13185) --- .circleci/config.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2177866aaad7..a01ac409cd5b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,7 +47,9 @@ jobs: - run: bin/ci prepare_system - run: echo 'export CURRENT_TAG="$CIRCLE_TAG"' >> $BASH_ENV - run: bin/ci prepare_build - - run: bin/ci build + - run: + command: bin/ci build + no_output_timeout: 30m - run: when: always command: | @@ -89,7 +91,9 @@ jobs: - run: echo 'export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/openssl@1.1/lib/pkgconfig"' >> $BASH_ENV - run: echo 'export CURRENT_TAG="$CIRCLE_TAG"' >> $BASH_ENV - run: bin/ci prepare_build - - run: bin/ci build + - run: + command: bin/ci build + no_output_timeout: 30m - run: when: always command: | @@ -119,7 +123,9 @@ jobs: - run: echo 'export CURRENT_TAG="$CIRCLE_TAG"' >> $BASH_ENV - run: bin/ci prepare_build - run: bin/ci with_build_env 'make crystal' - - run: bin/ci with_build_env 'CRYSTAL_WORKERS=4 make std_spec threads=1 FLAGS="-D preview_mt" junit_output=.junit/std_spec.xml' + - run: + command: bin/ci with_build_env 'CRYSTAL_WORKERS=4 make std_spec threads=1 FLAGS="-D preview_mt" junit_output=.junit/std_spec.xml' + no_output_timeout: 30m - run: when: always command: | @@ -472,7 +478,9 @@ jobs: - run: bin/ci prepare_system - run: echo 'export CURRENT_TAG="$CIRCLE_TAG"' >> $BASH_ENV - run: bin/ci prepare_build - - run: bin/ci build + - run: + command: bin/ci build + no_output_timeout: 30m workflows: version: 2 From 7cf6aff724769968e0dd042e0af5009f0b81009a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 28 Mar 2023 15:43:01 +0200 Subject: [PATCH 0396/1551] [CI] Update Windows job to LLVM 15 (#13208) --- .github/workflows/win.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index fd1720ec11a9..d8e8746a6e30 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -283,15 +283,23 @@ jobs: uses: actions/cache@v3 with: path: llvm - key: llvm-libs-13.0.1-msvc-${{ env.VSCMD_VER }} + key: llvm-libs-15.0.7-msvc-${{ env.VSCMD_VER }} - name: Download LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' run: | - iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.1/llvm-13.0.1.src.tar.xz -OutFile llvm.tar.xz - (Get-FileHash -Algorithm SHA256 .\llvm.tar.xz).hash -eq "EC6B80D82C384ACAD2DC192903A6CF2CDBAFFB889B84BFB98DA9D71E630FC834" + iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.7/llvm-15.0.7.src.tar.xz -OutFile llvm.tar.xz + (Get-FileHash -Algorithm SHA256 .\llvm.tar.xz).hash -eq "4AD8B2CC8003C86D0078D15D987D84E3A739F24AAE9033865C027ABAE93EE7A4" 7z x llvm.tar.xz 7z x llvm.tar mv llvm-* llvm-src + - name: Download LLVM's CMake files + if: steps.cache-llvm.outputs.cache-hit != 'true' + run: | + iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.7/cmake-15.0.7.src.tar.xz -OutFile cmake.tar.xz + (Get-FileHash -Algorithm SHA256 .\cmake.tar.xz).hash -eq "8986f29b634fdaa9862eedda78513969fe9788301c9f2d938f4c10a3e7a3e7ea" + 7z x cmake.tar.xz + 7z x cmake.tar + mv cmake-* cmake - name: Patch LLVM for VS 2019 16.7.0 working-directory: ./llvm-src if: steps.cache-llvm.outputs.cache-hit != 'true' @@ -301,7 +309,7 @@ jobs: if: steps.cache-llvm.outputs.cache-hit != 'true' working-directory: ./llvm-src run: | - cmake . -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DLLVM_USE_CRT_RELEASE=MT -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF + cmake . -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DLLVM_USE_CRT_RELEASE=MT -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_BUILD_BENCHMARKS=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_ENABLE_ZSTD=OFF cmake --build . --config Release - name: Gather LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' From a1f516d0d79e436bdfc670c0c141a7fcf2ee0333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 28 Mar 2023 15:44:09 +0200 Subject: [PATCH 0397/1551] Refactor `LLVM::Attribute#each_kind` to use `Enum#each` (#13234) --- src/llvm/enums.cr | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/llvm/enums.cr b/src/llvm/enums.cr index 7b9d4d260e07..9c868fbdeb86 100644 --- a/src/llvm/enums.cr +++ b/src/llvm/enums.cr @@ -61,15 +61,10 @@ module LLVM @@kind_ids = load_llvm_kinds_from_names.as(Hash(Attribute, UInt32)) @@typed_attrs = load_llvm_typed_attributes.as(Array(Attribute)) - def each_kind(&block) - return if value == 0 - {% for member in @type.constants %} - {% if member.stringify != "All" %} - if includes?({{@type}}::{{member}}) - yield @@kind_ids[{{@type}}::{{member}}] - end - {% end %} - {% end %} + def each_kind(& : UInt32 ->) + each do |member| + yield @@kind_ids[member] + end end private def self.kind_for_name(name : String) From cbba344fbff1309d4eaea4558afbfdce046616ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 28 Mar 2023 18:56:22 +0200 Subject: [PATCH 0398/1551] Suppress compiler output in `compile_file` spec helper (#13228) --- spec/std/spec_helper.cr | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/spec/std/spec_helper.cr b/spec/std/spec_helper.cr index 246762432c04..4ea8452af7c4 100644 --- a/spec/std/spec_helper.cr +++ b/spec/std/spec_helper.cr @@ -78,11 +78,18 @@ end def compile_file(source_file, *, bin_name = "executable_file", flags = %w(), file = __FILE__, &) with_temp_executable(bin_name, file: file) do |executable_file| compiler = ENV["CRYSTAL_SPEC_COMPILER_BIN"]? || "bin/crystal" - Process.run(compiler, ["build"] + flags + ["-o", executable_file, source_file], env: { + args = ["build"] + flags + ["-o", executable_file, source_file] + output = IO::Memory.new + status = Process.run(compiler, args, env: { "CRYSTAL_PATH" => Crystal::PATH, "CRYSTAL_LIBRARY_PATH" => Crystal::LIBRARY_PATH, "CRYSTAL_CACHE_DIR" => Crystal::CACHE_DIR, - }, error: Process::Redirect::Inherit) + }, output: output, error: output) + + unless status.success? + fail "Compiler command `#{compiler} #{args.join(" ")}` failed with status #{status}.#{"\n" if output}#{output}" + end + File.exists?(executable_file).should be_true yield executable_file From 61edd548970e060138a563042583f1d9c8cfc7ac Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Tue, 28 Mar 2023 11:56:48 -0500 Subject: [PATCH 0399/1551] Handle `Range` requests in `HTTP::StaticFileHandler` (#12886) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Jamie Gaskins Co-authored-by: Johannes Müller --- spec/std/data/static_file_handler/empty.txt | 0 spec/std/data/static_file_handler/range.txt | 1 + .../handlers/static_file_handler_spec.cr | 237 ++++++++++++++++++ .../server/handlers/static_file_handler.cr | 104 +++++++- 4 files changed, 340 insertions(+), 2 deletions(-) create mode 100644 spec/std/data/static_file_handler/empty.txt create mode 100644 spec/std/data/static_file_handler/range.txt diff --git a/spec/std/data/static_file_handler/empty.txt b/spec/std/data/static_file_handler/empty.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/spec/std/data/static_file_handler/range.txt b/spec/std/data/static_file_handler/range.txt new file mode 100644 index 000000000000..802992c4220d --- /dev/null +++ b/spec/std/data/static_file_handler/range.txt @@ -0,0 +1 @@ +Hello world diff --git a/spec/std/http/server/handlers/static_file_handler_spec.cr b/spec/std/http/server/handlers/static_file_handler_spec.cr index 267682754a0c..036e53eef2cc 100644 --- a/spec/std/http/server/handlers/static_file_handler_spec.cr +++ b/spec/std/http/server/handlers/static_file_handler_spec.cr @@ -177,6 +177,243 @@ describe HTTP::StaticFileHandler do end end + context "when a Range header is provided" do + context "int range" do + it "serves a byte range" do + headers = HTTP::Headers{"Range" => "bytes=0-2"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(206) + response.headers["Content-Range"]?.should eq "bytes 0-2/12" + response.body.should eq "Hel" + end + + it "serves a single byte" do + headers = HTTP::Headers{"Range" => "bytes=0-0"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(206) + response.headers["Content-Range"]?.should eq "bytes 0-0/12" + response.body.should eq "H" + end + + it "serves zero bytes" do + headers = HTTP::Headers{"Range" => "bytes=0-0"} + response = handle HTTP::Request.new("GET", "/empty.txt", headers) + + response.status_code.should eq(416) + response.headers["Content-Range"]?.should eq "bytes */0" + response.body.should eq "" + end + + it "serves an open-ended byte range" do + headers = HTTP::Headers{"Range" => "bytes=6-"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(206) + response.headers["Content-Range"]?.should eq "bytes 6-11/12" + response.body.should eq "world\n" + end + + it "serves multiple byte ranges (separator without whitespace)" do + headers = HTTP::Headers{"Range" => "bytes=0-1,6-7"} + + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(206) + response.headers["Content-Range"]?.should be_nil + count = 0 + MIME::Multipart.parse(response) do |headers, part| + chunk = part.gets_to_end + case range = headers["Content-Range"] + when "bytes 0-1/12" + chunk.should eq "He" + when "bytes 6-7/12" + chunk.should eq "wo" + else + fail "Unknown range: #{range.inspect}" + end + count += 1 + end + count.should eq 2 + end + + it "serves multiple byte ranges (separator with whitespace)" do + headers = HTTP::Headers{"Range" => "bytes=0-1, 6-7"} + + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(206) + response.headers["Content-Range"]?.should be_nil + count = 0 + MIME::Multipart.parse(response) do |headers, part| + chunk = part.gets_to_end + case range = headers["Content-Range"] + when "bytes 0-1/12" + chunk.should eq "He" + when "bytes 6-7/12" + chunk.should eq "wo" + else + fail "Unknown range: #{range.inspect}" + end + count += 1 + end + count.should eq 2 + end + + it "end of the range is larger than the file size" do + headers = HTTP::Headers{"Range" => "bytes=6-14"} + + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq 206 + response.headers["Content-Range"]?.should eq "bytes 6-11/12" + response.body.should eq "world\n" + end + + it "start of the range is larger than the file size" do + headers = HTTP::Headers{"Range" => "bytes=14-15"} + + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq 416 + response.headers["Content-Range"]?.should eq "bytes */12" + end + + it "start >= file_size" do + headers = HTTP::Headers{"Range" => "bytes=12-"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(416) + response.headers["Content-Range"]?.should eq "bytes */12" + end + end + + describe "suffix range" do + it "partial" do + headers = HTTP::Headers{"Range" => "bytes=-6"} + + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(206) + response.headers["Content-Range"]?.should eq "bytes 6-11/12" + response.body.should eq "world\n" + end + + it "more bytes than content" do + headers = HTTP::Headers{"Range" => "bytes=-15"} + + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(206) + response.headers["Content-Range"]?.should eq "bytes 0-11/12" + response.body.should eq "Hello world\n" + end + + it "zero" do + headers = HTTP::Headers{"Range" => "bytes=-0"} + + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(400) + response.headers["Content-Range"]?.should be_nil + end + + it "zero" do + headers = HTTP::Headers{"Range" => "bytes=-0"} + + response = handle HTTP::Request.new("GET", "/empty.txt", headers) + + response.status_code.should eq(400) + response.headers["Content-Range"]?.should be_nil + end + + it "empty file" do + headers = HTTP::Headers{"Range" => "bytes=-1"} + + response = handle HTTP::Request.new("GET", "/empty.txt", headers) + + response.status_code.should eq(200) + response.headers["Content-Range"]?.should be_nil + end + + it "negative size" do + headers = HTTP::Headers{"Range" => "bytes=--2"} + + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(400) + response.headers["Content-Range"]?.should be_nil + end + end + + describe "invalid Range syntax" do + it "byte number without dash" do + headers = HTTP::Headers{"Range" => "bytes=1"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(400) + end + + it "start > end" do + headers = HTTP::Headers{"Range" => "bytes=2-1"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(400) + end + + it "negative end" do + headers = HTTP::Headers{"Range" => "bytes=1--2"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(400) + end + + it "open range with negative end" do + headers = HTTP::Headers{"Range" => "bytes=--2"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(400) + end + + it "open range with negative end" do + headers = HTTP::Headers{"Range" => "bytes=--2"} + response = handle HTTP::Request.new("GET", "/empty.txt", headers) + + response.status_code.should eq(400) + end + + it "unsupported unit" do + headers = HTTP::Headers{"Range" => "chars=1-2"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(416) + response.headers["Content-Range"]?.should eq "bytes */12" + end + + it "multiple dashes" do + headers = HTTP::Headers{"Range" => "bytes=1-2-3"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(400) + end + + it "not a number" do + headers = HTTP::Headers{"Range" => "bytes=a-b"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(400) + end + + it "not a range" do + headers = HTTP::Headers{"Range" => "bytes=-"} + response = handle HTTP::Request.new("GET", "/range.txt", headers) + + response.status_code.should eq(400) + end + end + end + it "lists directory's entries" do response = handle HTTP::Request.new("GET", "/") response.status_code.should eq(200) diff --git a/src/http/server/handlers/static_file_handler.cr b/src/http/server/handlers/static_file_handler.cr index 99e268896103..00f7e34b3523 100644 --- a/src/http/server/handlers/static_file_handler.cr +++ b/src/http/server/handlers/static_file_handler.cr @@ -79,6 +79,8 @@ class HTTP::StaticFileHandler return call_next(context) unless file_info + context.response.headers["Accept-Ranges"] = "bytes" + if @directory_listing && is_dir context.response.content_type = "text/html" directory_listing(context.response, request_path, file_path) @@ -105,15 +107,113 @@ class HTTP::StaticFileHandler end end - context.response.content_length = file_info.size File.open(file_path) do |file| - IO.copy(file, context.response) + if range_header = context.request.headers["Range"]? + range_header = range_header.lchop?("bytes=") + unless range_header + context.response.headers["Content-Range"] = "bytes */#{file_info.size}" + context.response.status = :range_not_satisfiable + context.response.close + return + end + + ranges = parse_ranges(range_header, file_info.size) + unless ranges + context.response.respond_with_status :bad_request + return + end + + if file_info.size.zero? && ranges.size == 1 && ranges[0].begin.zero? + context.response.status = :ok + return + end + + # If any of the ranges start beyond the end of the file, we return an + # HTTP 416 Range Not Satisfiable. + # See https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-11.1 + if ranges.any? { |range| range.begin >= file_info.size } + context.response.headers["Content-Range"] = "bytes */#{file_info.size}" + context.response.status = :range_not_satisfiable + context.response.close + return + end + + ranges.map! { |range| range.begin..(Math.min(range.end, file_info.size - 1)) } + + context.response.status = :partial_content + + if ranges.size == 1 + range = ranges.first + file.seek range.begin + context.response.headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{file_info.size}" + IO.copy file, context.response, range.size + else + MIME::Multipart.build(context.response) do |builder| + content_type = context.response.headers["Content-Type"]? + context.response.headers["Content-Type"] = builder.content_type("byterange") + + ranges.each do |range| + file.seek range.begin + headers = HTTP::Headers{ + "Content-Range" => "bytes #{range.begin}-#{range.end}/#{file_info.size}", + "Content-Length" => range.size.to_s, + } + headers["Content-Type"] = content_type if content_type + chunk_io = IO::Sized.new(file, range.size) + builder.body_part headers, chunk_io + end + end + end + else + context.response.status = :ok + context.response.content_length = file_info.size + IO.copy(file, context.response) + end end else # Not a normal file (FIFO/device/socket) call_next(context) end end + # TODO: Optimize without lots of intermediary strings + private def parse_ranges(header, file_size) + ranges = [] of Range(Int64, Int64) + header.split(",") do |range| + start_string, dash, finish_string = range.lchop(' ').partition("-") + return if dash.empty? + start = start_string.to_i64? + return if start.nil? && !start_string.empty? + if finish_string.empty? + return if start_string.empty? + finish = file_size + else + finish = finish_string.to_i64? || return + end + if file_size.zero? + # > When a selected representation has zero length, the only satisfiable + # > form of range-spec in a GET request is a suffix-range with a non-zero suffix-length. + + if start + # This return value signals an unsatisfiable range. + return [1_i64..0_i64] + elsif finish <= 0 + return + else + start = finish = 0_i64 + end + elsif !start + # suffix-range + start = {file_size - finish, 0_i64}.max + finish = file_size - 1 + end + + range = (start..finish) + return unless 0 <= range.begin <= range.end + ranges << range + end + ranges unless ranges.empty? + end + # given a full path of the request, returns the path # of the file that should be expanded at the public_dir protected def request_path(path : String) : String From e2f831ecbf1bc9ea5e12e70a1d330e1e24332884 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 29 Mar 2023 00:57:16 +0800 Subject: [PATCH 0400/1551] Windows: fix error condition when `File.open` fails (#13235) --- spec/std/file_spec.cr | 8 ++++++++ src/crystal/system/win32/file.cr | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index a0cafb5cad39..ad1f7523f957 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -27,6 +27,14 @@ describe "File" do end end + it "raises if opening a non-existent file" do + with_tempfile("test_nonexistent.txt") do |file| + expect_raises(File::NotFoundError) do + File.open(file) + end + end + end + it "reads entire file" do str = File.read datapath("test_file.txt") str.should eq("Hello World\n" * 20) diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 095b5fc45924..c9a399b7ae0f 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -41,8 +41,7 @@ module Crystal::System::File ) if handle == LibC::INVALID_HANDLE_VALUE - # Map ERROR_FILE_EXISTS to Errno::EEXIST to avoid changing semantics of other systems - return {-1, WinError.value.error_file_exists? ? Errno::EEXIST : Errno.value} + return {-1, WinError.value.to_errno} end fd = LibC._open_osfhandle handle, flags From 09e0fb180bfedf75f141faa121909740d9f8abc2 Mon Sep 17 00:00:00 2001 From: GeopJr Date: Wed, 29 Mar 2023 11:05:30 +0300 Subject: [PATCH 0401/1551] Add `Socket::IPAddress#link_local?` (#13204) Co-authored-by: Mike Robbins <1057385+compumike@users.noreply.github.com> --- spec/std/socket/address_spec.cr | 17 +++++++++++++++++ src/socket/address.cr | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/spec/std/socket/address_spec.cr b/spec/std/socket/address_spec.cr index 6117b2af66fe..6a6f0d926fd7 100644 --- a/spec/std/socket/address_spec.cr +++ b/spec/std/socket/address_spec.cr @@ -184,6 +184,23 @@ describe Socket::IPAddress do Socket::IPAddress.new("2001:4860:4860::8888", 0).private?.should be_false end + it "#link_local?" do + Socket::IPAddress.new("0.0.0.0", 0).link_local?.should be_false + Socket::IPAddress.new("127.0.0.1", 0).link_local?.should be_false + Socket::IPAddress.new("10.0.0.0", 0).link_local?.should be_false + Socket::IPAddress.new("172.16.0.0", 0).link_local?.should be_false + Socket::IPAddress.new("192.168.0.0", 0).link_local?.should be_false + + Socket::IPAddress.new("169.254.1.1", 0).link_local?.should be_true + Socket::IPAddress.new("169.254.254.255", 0).link_local?.should be_true + + Socket::IPAddress.new("::1", 0).link_local?.should be_false + Socket::IPAddress.new("::", 0).link_local?.should be_false + Socket::IPAddress.new("fb84:8bf7:e905::1", 0).link_local?.should be_false + + Socket::IPAddress.new("fe80::4860:4860:4860:1234", 0).link_local?.should be_true + end + it "#==" do Socket::IPAddress.new("127.0.0.1", 8080).should eq Socket::IPAddress.new("127.0.0.1", 8080) Socket::IPAddress.new("127.0.0.1", 8080).hash.should eq Socket::IPAddress.new("127.0.0.1", 8080).hash diff --git a/src/socket/address.cr b/src/socket/address.cr index 1a84006ca32d..4a6ea10dfb62 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -252,6 +252,19 @@ class Socket end end + # Returns `true` if this IP is a link-local address. + # + # IPv4 addresses in `169.254.0.0/16` reserved by [RFC 3927](https://www.rfc-editor.org/rfc/rfc3927) and Link-Local IPv6 + # Unicast Addresses in `fe80::/10` reserved by [RFC 4291](https://tools.ietf.org/html/rfc4291) are considered link-local. + def link_local? + case addr = @addr + in LibC::InAddr + addr.s_addr & 0x000000ffff_u32 == 0x0000fea9_u32 # 169.254.0.0/16 + in LibC::In6Addr + ipv6_addr8(addr).unsafe_as(UInt128) & 0xc0ff_u128 == 0x80fe_u128 + end + end + private def ipv6_addr8(addr : LibC::In6Addr) {% if flag?(:darwin) || flag?(:bsd) %} addr.__u6_addr.__u6_addr8 From b314b5106004996528e2ca5b443d1c1d585f2fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 29 Mar 2023 10:05:53 +0200 Subject: [PATCH 0402/1551] Skip eacces spec for superuser (#13227) --- spec/std/file_spec.cr | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index ad1f7523f957..f8dad66543cc 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -887,7 +887,15 @@ describe "File" do pending_win32 "raises when reading a file with no permission" do with_tempfile("file.txt") do |path| File.touch(path) - File.chmod(path, 0) + File.chmod(path, File::Permissions::None) + {% if flag?(:unix) %} + # TODO: Find a better way to execute this spec when running as privileged + # user. Compiling a program and running a separate process would be a + # lot of overhead. + if LibC.getuid == 0 + pending! "Spec cannot run as superuser" + end + {% end %} expect_raises(File::AccessDeniedError) { File.read(path) } end end From 600a533132ccf9e29db0cfea0b5aa9af6ebe2f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 29 Mar 2023 14:00:28 +0200 Subject: [PATCH 0403/1551] Fix `Enum#includes?` to require all bits set (#13229) --- spec/std/enum_spec.cr | 6 ++++++ src/enum.cr | 19 ++++--------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/spec/std/enum_spec.cr b/spec/std/enum_spec.cr index 963e9401ad95..19248788cbb3 100644 --- a/spec/std/enum_spec.cr +++ b/spec/std/enum_spec.cr @@ -144,6 +144,12 @@ describe Enum do it "does includes?" do (SpecEnumFlags::One | SpecEnumFlags::Two).includes?(SpecEnumFlags::One).should be_true (SpecEnumFlags::One | SpecEnumFlags::Two).includes?(SpecEnumFlags::Three).should be_false + SpecEnumFlags::One.includes?(SpecEnumFlags::None).should be_true + SpecEnumFlags::None.includes?(SpecEnumFlags::None).should be_true + SpecEnumFlags::None.includes?(SpecEnumFlags::One).should be_false + SpecEnumFlags::One.includes?(SpecEnumFlags::One | SpecEnumFlags::Two).should be_false + (SpecEnumFlags::One | SpecEnumFlags::Two).includes?(SpecEnumFlags::One | SpecEnumFlags::Two).should be_true + (SpecEnumFlags::One | SpecEnumFlags::Two | SpecEnumFlags::Three).includes?(SpecEnumFlags::One | SpecEnumFlags::Two).should be_true end describe "each" do diff --git a/src/enum.cr b/src/enum.cr index 06c7e3afdfbd..a42f9901f9cf 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -337,20 +337,9 @@ struct Enum end # Returns `true` if this enum member's value includes *other*. This - # performs a logical "and" between this enum member's value and *other*'s, - # so instead of writing: + # performs a logical "and" between this enum member's value and *other*'s. # - # ``` - # (member & value) != 0 - # ``` - # - # you can write: - # - # ``` - # member.includes?(value) - # ``` - # - # The above is mostly useful with flag enums. + # This is mostly useful for flag enums. # # For example: # @@ -360,7 +349,7 @@ struct Enum # mode.includes?(IOMode::Async) # => false # ``` def includes?(other : self) : Bool - (value & other.value) != 0 + value.bits_set?(other.value) end # Returns `true` if this enum member and *other* have the same underlying value. @@ -390,7 +379,7 @@ struct Enum {% if @type.annotation(Flags) %} return if value == 0 {% for member in @type.constants %} - {% if member.stringify != "All" %} + {% if member.stringify != "All" && member.stringify != "None" %} if includes?(self.class.new({{@type.constant(member)}})) yield self.class.new({{@type.constant(member)}}), {{@type.constant(member)}} end From bcd411cc857ec417ec93452bed3d7608598a9274 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 30 Mar 2023 05:05:39 +0800 Subject: [PATCH 0404/1551] Fix JSON, YAML `use_*_discriminator` for recursive struct types (#13238) --- spec/std/json/serializable_spec.cr | 27 ++++++++++++++++++++++++ spec/std/yaml/serializable_spec.cr | 27 ++++++++++++++++++++++++ src/json/serialization.cr | 34 ++++++++++++------------------ src/yaml/serialization.cr | 31 +++++++++++++-------------- 4 files changed, 82 insertions(+), 37 deletions(-) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index 877818d9398e..e4bd0bbcd3a2 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -426,6 +426,23 @@ end class JSONVariableDiscriminatorEnum8 < JSONVariableDiscriminatorValueType end +class JSONStrictDiscriminator + include JSON::Serializable + include JSON::Serializable::Strict + + property type : String + + use_json_discriminator "type", {foo: JSONStrictDiscriminatorFoo, bar: JSONStrictDiscriminatorBar} +end + +class JSONStrictDiscriminatorFoo < JSONStrictDiscriminator +end + +class JSONStrictDiscriminatorBar < JSONStrictDiscriminator + property x : JSONStrictDiscriminator + property y : JSONStrictDiscriminator +end + module JSONNamespace struct FooRequest include JSON::Serializable @@ -1071,6 +1088,16 @@ describe "JSON mapping" do object_enum = JSONVariableDiscriminatorValueType.from_json(%({"type": 18})) object_enum.should be_a(JSONVariableDiscriminatorEnum8) end + + it "deserializes with discriminator, strict recursive type" do + foo = JSONStrictDiscriminator.from_json(%({"type": "foo"})) + foo = foo.should be_a(JSONStrictDiscriminatorFoo) + + bar = JSONStrictDiscriminator.from_json(%({"type": "bar", "x": {"type": "foo"}, "y": {"type": "foo"}})) + bar = bar.should be_a(JSONStrictDiscriminatorBar) + bar.x.should be_a(JSONStrictDiscriminatorFoo) + bar.y.should be_a(JSONStrictDiscriminatorFoo) + end end describe "namespaced classes" do diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 17c31306456c..297db3459d8e 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -381,6 +381,23 @@ end class YAMLVariableDiscriminatorEnum8 < YAMLVariableDiscriminatorValueType end +class YAMLStrictDiscriminator + include YAML::Serializable + include YAML::Serializable::Strict + + property type : String + + use_yaml_discriminator "type", {foo: YAMLStrictDiscriminatorFoo, bar: YAMLStrictDiscriminatorBar} +end + +class YAMLStrictDiscriminatorFoo < YAMLStrictDiscriminator +end + +class YAMLStrictDiscriminatorBar < YAMLStrictDiscriminator + property x : YAMLStrictDiscriminator + property y : YAMLStrictDiscriminator +end + describe "YAML::Serializable" do it "works with record" do YAMLAttrPoint.new(1, 2).to_yaml.should eq "---\nx: 1\ny: 2\n" @@ -972,6 +989,16 @@ describe "YAML::Serializable" do object_enum = YAMLVariableDiscriminatorValueType.from_yaml(%({"type": 18})) object_enum.should be_a(YAMLVariableDiscriminatorEnum8) end + + it "deserializes with discriminator, strict recursive type" do + foo = YAMLStrictDiscriminator.from_yaml(%({"type": "foo"})) + foo = foo.should be_a(YAMLStrictDiscriminatorFoo) + + bar = YAMLStrictDiscriminator.from_yaml(%({"type": "bar", "x": {"type": "foo"}, "y": {"type": "foo"}})) + bar = bar.should be_a(YAMLStrictDiscriminatorBar) + bar.x.should be_a(YAMLStrictDiscriminatorFoo) + bar.y.should be_a(YAMLStrictDiscriminatorFoo) + end end describe "namespaced classes" do diff --git a/src/json/serialization.cr b/src/json/serialization.cr index 001fe9b0bc54..2eabe22dc313 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -200,7 +200,7 @@ module JSON {% end %} {% for name, value in properties %} - %var{name} = nil + %var{name} = {% if value[:has_default] || value[:nilable] %} nil {% else %} uninitialized ::Union({{value[:type]}}) {% end %} %found{name} = false {% end %} @@ -216,26 +216,18 @@ module JSON case key {% for name, value in properties %} when {{value[:key]}} - %found{name} = true begin - %var{name} = - {% if value[:nilable] || value[:has_default] %} pull.read_null_or { {% end %} - - {% if value[:root] %} - pull.on_key!({{value[:root]}}) do - {% end %} - - {% if value[:converter] %} - {{value[:converter]}}.from_json(pull) - {% else %} - ::Union({{value[:type]}}).new(pull) - {% end %} - - {% if value[:root] %} + {% if value[:has_default] || value[:nilable] %} pull.read_null_or do {% else %} begin {% end %} + %var{name} = + {% if value[:root] %} pull.on_key!({{value[:root]}}) do {% else %} begin {% end %} + {% if value[:converter] %} + {{value[:converter]}}.from_json(pull) + {% else %} + ::Union({{value[:type]}}).new(pull) + {% end %} end - {% end %} - - {% if value[:nilable] || value[:has_default] %} } {% end %} + end + %found{name} = true rescue exc : ::JSON::ParseException raise ::JSON::SerializableError.new(exc.message, self.class.to_s, {{value[:key]}}, *%key_location, exc) end @@ -248,7 +240,7 @@ module JSON {% for name, value in properties %} {% unless value[:nilable] || value[:has_default] %} - if %var{name}.nil? && !%found{name} && !::Union({{value[:type]}}).nilable? + if !%found{name} raise ::JSON::SerializableError.new("Missing JSON attribute: {{value[:key].id}}", self.class.to_s, nil, *%location, nil) end {% end %} @@ -264,7 +256,7 @@ module JSON @{{name}} = %var{name} end {% else %} - @{{name}} = (%var{name}).as({{value[:type]}}) + @{{name}} = %var{name} {% end %} {% if value[:presence] %} diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index bbd85a24eec4..b939c1c4a17b 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -205,7 +205,7 @@ module YAML {% end %} {% for name, value in properties %} - %var{name} = nil + %var{name} = {% if value[:has_default] || value[:nilable] %} nil {% else %} uninitialized ::Union({{value[:type]}}) {% end %} %found{name} = false {% end %} @@ -221,20 +221,19 @@ module YAML case key {% for name, value in properties %} when {{value[:key]}} - %found{name} = true begin - %var{name} = - {% if value[:nilable] || value[:has_default] %} YAML::Schema::Core.parse_null_or(value_node) { {% end %} - - {% if value[:converter] %} - {{value[:converter]}}.from_yaml(ctx, value_node) - {% elsif value[:type].is_a?(Path) || value[:type].is_a?(Generic) %} - {{value[:type]}}.new(ctx, value_node) - {% else %} - ::Union({{value[:type]}}).new(ctx, value_node) - {% end %} - - {% if value[:nilable] || value[:has_default] %} } {% end %} + {% if value[:has_default] && !value[:nilable] %} YAML::Schema::Core.parse_null_or(value_node) do {% else %} begin {% end %} + %var{name} = + {% if value[:converter] %} + {{value[:converter]}}.from_yaml(ctx, value_node) + {% elsif value[:type].is_a?(Path) || value[:type].is_a?(Generic) %} + {{value[:type]}}.new(ctx, value_node) + {% else %} + ::Union({{value[:type]}}).new(ctx, value_node) + {% end %} + end + + %found{name} = true end {% end %} else @@ -253,7 +252,7 @@ module YAML {% for name, value in properties %} {% unless value[:nilable] || value[:has_default] %} - if %var{name}.nil? && !%found{name} && !::Union({{value[:type]}}).nilable? + if !%found{name} node.raise "Missing YAML attribute: {{value[:key].id}}" end {% end %} @@ -269,7 +268,7 @@ module YAML @{{name}} = %var{name} end {% else %} - @{{name}} = (%var{name}).as({{value[:type]}}) + @{{name}} = %var{name} {% end %} {% if value[:presence] %} From 38155d19a9297a2b24f5b39953c5bcfde9fc4b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 30 Mar 2023 21:02:49 +0200 Subject: [PATCH 0405/1551] Clean up `.gitignore` (#13241) --- .gitignore | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 2ae46b52e44f..5d74450c533f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,14 @@ -.DS_Store -/.tags -/.tags_sorted_by_file -/.vagrant -.crystal/ -coverage/ -/deps/ -/.build/ -.*.swp /Makefile.local -all_spec -/tmp + +# Build artifacts +/.build/ /docs/ /src/llvm/ext/llvm_ext.o /src/llvm/ext/llvm_ext.obj /src/llvm/ext/llvm_ext.dwo /man/*.gz + +# CI +/coverage/ +/tmp/ +/.junit/ From b31b07d3bbfc9daad5d37255e5f8da648d183326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 31 Mar 2023 14:31:03 +0200 Subject: [PATCH 0406/1551] Add more members to `Regex::Options` (#13223) --- spec/std/regex_spec.cr | 4 +- src/regex.cr | 35 +++++++++--- src/regex/lib_pcre.cr | 87 +++++++++++++++++++++++++--- src/regex/lib_pcre2.cr | 127 +++++++++++++++++++++++++++++------------ src/regex/pcre.cr | 58 +++++++++++++++---- src/regex/pcre2.cr | 57 ++++++++++++++---- 6 files changed, 291 insertions(+), 77 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index f8cb2c926549..fc8d5a708648 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -19,8 +19,8 @@ describe "Regex" do {% if Regex::Engine.resolve.name == "Regex::PCRE" %} Regex.new("^/foo$", Regex::Options.new(0x00000020)).matches?("/foo\n").should be_false {% else %} - expect_raises ArgumentError, "Unknown Regex::Option value: 32" do - Regex.new("", Regex::Options.new(32)) + expect_raises ArgumentError, "Unknown Regex::Option value: 64" do + Regex.new("", Regex::Options.new(0x00000040)) end {% end %} end diff --git a/src/regex.cr b/src/regex.cr index 136671e2f65c..8f8c1484a6e6 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -208,9 +208,10 @@ class Regex } @[Flags] - enum Options + enum Options : UInt64 # Case insensitive match. - IGNORE_CASE = 1 + IGNORE_CASE = 0x0000_0001 + # PCRE native `PCRE_MULTILINE` flag is `2`, and `PCRE_DOTALL` is `4` # - `PCRE_DOTALL` changes the "`.`" meaning # - `PCRE_MULTILINE` changes "`^`" and "`$`" meanings @@ -218,19 +219,35 @@ class Regex # Crystal modifies this meaning to have essentially one unique "`m`" # flag that activates both behaviours, so here we do the same by # mapping `MULTILINE` to `PCRE_MULTILINE | PCRE_DOTALL`. - MULTILINE = 6 + # The same applies for PCRE2 except that the native values are 0x200 and 0x400. + + # Multiline matching. + # + # Equivalent to `MULTILINE | DOTALL` in PCRE and PCRE2. + MULTILINE = 0x0000_0006 + + DOTALL = 0x0000_0002 + # Ignore white space and `#` comments. - EXTENDED = 8 + EXTENDED = 0x0000_0008 + # Force pattern anchoring. - ANCHORED = 16 + ANCHORED = 0x0000_0010 + + DOLLAR_ENDONLY = 0x0000_0020 + FIRSTLINE = 0x0004_0000 + # :nodoc: - UTF_8 = 0x00000800 + UTF_8 = 0x0000_0800 # :nodoc: - NO_UTF8_CHECK = 0x00002000 + NO_UTF8_CHECK = 0x0000_2000 # :nodoc: - DUPNAMES = 0x00080000 + DUPNAMES = 0x0008_0000 # :nodoc: - UCP = 0x20000000 + UCP = 0x2000_0000 + + ENDANCHORED = 0x8000_0000 + NO_JIT end # Returns a `Regex::Options` representing the optional flags applied to this `Regex`. diff --git a/src/regex/lib_pcre.cr b/src/regex/lib_pcre.cr index cf32142c5358..c5811d348b99 100644 --- a/src/regex/lib_pcre.cr +++ b/src/regex/lib_pcre.cr @@ -2,15 +2,84 @@ lib LibPCRE alias Int = LibC::Int - CASELESS = 0x00000001 - MULTILINE = 0x00000002 - DOTALL = 0x00000004 - EXTENDED = 0x00000008 - ANCHORED = 0x00000010 - UTF8 = 0x00000800 - NO_UTF8_CHECK = 0x00002000 - DUPNAMES = 0x00080000 - UCP = 0x20000000 + # Public options. Some are compile-time only, some are run-time only, and some + # are both. Most of the compile-time options are saved with the compiled regex so + # that they can be inspected during studying (and therefore JIT compiling). Note + # that pcre_study() has its own set of options. Originally, all the options + # defined here used distinct bits. However, almost all the bits in a 32-bit word + # are now used, so in order to conserve them, option bits that were previously + # only recognized at matching time (i.e. by pcre_exec() or pcre_dfa_exec()) may + # also be used for compile-time options that affect only compiling and are not + # relevant for studying or JIT compiling. + + # Some options for pcre_compile() change its behaviour but do not affect the + # behaviour of the execution functions. Other options are passed through to the + # execution functions and affect their behaviour, with or without affecting the + # behaviour of pcre_compile(). + + # Options that can be passed to pcre_compile() are tagged Cx below, with these + # variants: + + # C1 Affects compile only + # C2 Does not affect compile; affects exec, dfa_exec + # C3 Affects compile, exec, dfa_exec + # C4 Affects compile, exec, dfa_exec, study + # C5 Affects compile, exec, study + + # Options that can be set for pcre_exec() and/or pcre_dfa_exec() are flagged with + # E and D, respectively. They take precedence over C3, C4, and C5 settings passed + # from pcre_compile(). Those that are compatible with JIT execution are flagged + # with J. + + CASELESS = 0x00000001 + MULTILINE = 0x00000002 + DOTALL = 0x00000004 + EXTENDED = 0x00000008 + ANCHORED = 0x00000010 + DOLLAR_ENDONLY = 0x00000020 + + EXTRA = 0x00000040 # C1 + NOTBOL = 0x00000080 # E D J + NOTEOL = 0x00000100 # E D J + UNGREEDY = 0x00000200 # C1 + NOTEMPTY = 0x00000400 # E D J + UTF8 = 0x00000800 # C4 ) + UTF16 = 0x00000800 # C4 ) Synonyms + UTF32 = 0x00000800 # C4 ) + NO_AUTO_CAPTURE = 0x00001000 # C1 + NO_UTF8_CHECK = 0x00002000 # C1 E D J ) + NO_UTF16_CHECK = 0x00002000 # C1 E D J ) Synonyms + NO_UTF32_CHECK = 0x00002000 # C1 E D J ) + AUTO_CALLOUT = 0x00004000 # C1 + PARTIAL_SOFT = 0x00008000 # E D J ) Synonyms + PARTIAL = 0x00008000 # E D J ) + + # This pair use the same bit. + NEVER_UTF = 0x00010000 # C1 ) Overlaid + DFA_SHORTEST = 0x00010000 # D ) Overlaid + NOTBOS = 0x00010000 # D ) Overlaid + + # This pair use the same bit. + NO_AUTO_POSSESS = 0x00020000 # C1 ) Overlaid + DFA_RESTART = 0x00020000 # D ) Overlaid + NOTEOS = 0x00020000 # D ) Overlaid + + FIRSTLINE = 0x00040000 # C3 + DUPNAMES = 0x00080000 # C1 + NEWLINE_CR = 0x00100000 # C3 E D + NEWLINE_LF = 0x00200000 # C3 E D + NEWLINE_CRLF = 0x00300000 # C3 E D + NEWLINE_ANY = 0x00400000 # C3 E D + NEWLINE_ANYCRLF = 0x00500000 # C3 E D + BSR_ANYCRLF = 0x00800000 # C3 E D + BSR_UNICODE = 0x01000000 # C3 E D + JAVASCRIPT_COMPAT = 0x02000000 # C5 + NO_START_OPTIMIZE = 0x04000000 # C2 E D ) Synonyms + NO_START_OPTIMISE = 0x04000000 # C2 E D ) + PARTIAL_HARD = 0x08000000 # E D J + NOTEMPTY_ATSTART = 0x10000000 # E D J + UCP = 0x20000000 # C3 + NOTGPOS = 0x40000000 # C3 type Pcre = Void* type PcreExtra = Void* diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index 5fac84326a13..87e584702246 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -4,37 +4,98 @@ lib LibPCRE2 UNSET = ~LibC::SizeT.new(0) - ANCHORED = 0x80000000 - NO_UTF_CHECK = 0x40000000 - ENDANCHORED = 0x20000000 - - ALLOW_EMPTY_CLASS = 0x00000001 - ALT_BSUX = 0x00000002 - AUTO_CALLOUT = 0x00000004 - CASELESS = 0x00000008 - DOLLAR_ENDONLY = 0x00000010 - DOTALL = 0x00000020 - DUPNAMES = 0x00000040 - EXTENDED = 0x00000080 - FIRSTLINE = 0x00000100 - MATCH_UNSET_BACKREF = 0x00000200 - MULTILINE = 0x00000400 - NEVER_UCP = 0x00000800 - NEVER_UTF = 0x00001000 - NO_AUTO_CAPTURE = 0x00002000 - NO_AUTO_POSSESS = 0x00004000 - NO_DOTSTAR_ANCHOR = 0x00008000 - NO_START_OPTIMIZE = 0x00010000 - UCP = 0x00020000 - UNGREEDY = 0x00040000 - UTF = 0x00080000 - NEVER_BACKSLASH_C = 0x00100000 - ALT_CIRCUMFLEX = 0x00200000 - ALT_VERBNAMES = 0x00400000 - USE_OFFSET_LIMIT = 0x00800000 - EXTENDED_MORE = 0x01000000 - LITERAL = 0x02000000 - MATCH_INVALID_UTF = 0x04000000 + # The following option bits can be passed to pcre2_compile(), pcre2_match(), + # or pcre2_dfa_match(). PCRE2_NO_UTF_CHECK affects only the function to which it + # is passed. Put these bits at the most significant end of the options word so + # others can be added next to them + + ANCHORED = 0x80000000_u32 + NO_UTF_CHECK = 0x40000000_u32 + ENDANCHORED = 0x20000000_u32 + + # The following option bits can be passed only to pcre2_compile(). However, + # they may affect compilation, JIT compilation, and/or interpretive execution. + # The following tags indicate which: + + # C alters what is compiled by pcre2_compile() + # J alters what is compiled by pcre2_jit_compile() + # M is inspected during pcre2_match() execution + # D is inspected during pcre2_dfa_match() execution + + ALLOW_EMPTY_CLASS = 0x00000001_u32 # C + ALT_BSUX = 0x00000002_u32 # C + AUTO_CALLOUT = 0x00000004_u32 # C + CASELESS = 0x00000008_u32 # C + DOLLAR_ENDONLY = 0x00000010_u32 # J M D + DOTALL = 0x00000020_u32 # C + DUPNAMES = 0x00000040_u32 # C + EXTENDED = 0x00000080_u32 # C + FIRSTLINE = 0x00000100_u32 # J M D + MATCH_UNSET_BACKREF = 0x00000200_u32 # C J M + MULTILINE = 0x00000400_u32 # C + NEVER_UCP = 0x00000800_u32 # C + NEVER_UTF = 0x00001000_u32 # C + NO_AUTO_CAPTURE = 0x00002000_u32 # C + NO_AUTO_POSSESS = 0x00004000_u32 # C + NO_DOTSTAR_ANCHOR = 0x00008000_u32 # C + NO_START_OPTIMIZE = 0x00010000_u32 # J M D + UCP = 0x00020000_u32 # C J M D + UNGREEDY = 0x00040000_u32 # C + UTF = 0x00080000_u32 # C J M D + NEVER_BACKSLASH_C = 0x00100000_u32 # C + ALT_CIRCUMFLEX = 0x00200000_u32 # J M D + ALT_VERBNAMES = 0x00400000_u32 # C + USE_OFFSET_LIMIT = 0x00800000_u32 # J M D + EXTENDED_MORE = 0x01000000_u32 # C + LITERAL = 0x02000000_u32 # C + MATCH_INVALID_UTF = 0x04000000_u32 # J M D + + # An additional compile options word is available in the compile context. + + EXTRA_ALLOW_SURROGATE_ESCAPES = 0x00000001_u32 # C + EXTRA_BAD_ESCAPE_IS_LITERAL = 0x00000002_u32 # C + EXTRA_MATCH_WORD = 0x00000004_u32 # C + EXTRA_MATCH_LINE = 0x00000008_u32 # C + EXTRA_ESCAPED_CR_IS_LF = 0x00000010_u32 # C + EXTRA_ALT_BSUX = 0x00000020_u32 # C + EXTRA_ALLOW_LOOKAROUND_BSK = 0x00000040_u32 # C + EXTRA_CASELESS_RESTRICT = 0x00000080_u32 # C + EXTRA_ASCII_BSD = 0x00000100_u32 # C + EXTRA_ASCII_BSS = 0x00000200_u32 # C + EXTRA_ASCII_BSW = 0x00000400_u32 # C + EXTRA_ASCII_POSIX = 0x00000800_u32 # C + + # These are for pcre2_jit_compile(). + + JIT_COMPLETE = 0x00000001_u32 # For full matching + JIT_PARTIAL_SOFT = 0x00000002_u32 + JIT_PARTIAL_HARD = 0x00000004_u32 + JIT_INVALID_UTF = 0x00000100_u32 + + # These are for pcre2_match(), pcre2_dfa_match(), pcre2_jit_match(), and + # pcre2_substitute(). Some are allowed only for one of the functions, and in + # these cases it is noted below. Note that PCRE2_ANCHORED, PCRE2_ENDANCHORED and + # PCRE2_NO_UTF_CHECK can also be passed to these functions (though + # pcre2_jit_match() ignores the latter since it bypasses all sanity checks). + + NOTBOL = 0x00000001_u32 + NOTEOL = 0x00000002_u32 + NOTEMPTY = 0x00000004_u32 # ) These two must be kept + NOTEMPTY_ATSTART = 0x00000008_u32 # ) adjacent to each other. + PARTIAL_SOFT = 0x00000010_u32 + PARTIAL_HARD = 0x00000020_u32 + DFA_RESTART = 0x00000040_u32 # pcre2_dfa_match() only + DFA_SHORTEST = 0x00000080_u32 # pcre2_dfa_match() only + SUBSTITUTE_GLOBAL = 0x00000100_u32 # pcre2_substitute() only + SUBSTITUTE_EXTENDED = 0x00000200_u32 # pcre2_substitute() only + SUBSTITUTE_UNSET_EMPTY = 0x00000400_u32 # pcre2_substitute() only + SUBSTITUTE_UNKNOWN_UNSET = 0x00000800_u32 # pcre2_substitute() only + SUBSTITUTE_OVERFLOW_LENGTH = 0x00001000_u32 # pcre2_substitute() only + NO_JIT = 0x00002000_u32 # Not for pcre2_dfa_match() + COPY_MATCHED_SUBJECT = 0x00004000_u32 + SUBSTITUTE_LITERAL = 0x00008000_u32 # pcre2_substitute() only + SUBSTITUTE_MATCHED = 0x00010000_u32 # pcre2_substitute() only + SUBSTITUTE_REPLACEMENT_ONLY = 0x00020000_u32 # pcre2_substitute() only enum Error # "Expected" matching error codes: no match and partial match. @@ -185,10 +246,6 @@ lib LibPCRE2 type MatchContext = Void* fun match_context_create = pcre2_match_context_create_8(gcontext : Void*) : MatchContext* - JIT_COMPLETE = 0x00000001_u32 # For full matching - JIT_PARTIAL_SOFT = 0x00000002_u32 - JIT_PARTIAL_HARD = 0x00000004_u32 - JIT_INVALID_UTF = 0x00000100_u32 fun jit_compile = pcre2_jit_compile_8(code : Code*, options : UInt32) : Int type JITStack = Void diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr index 9d006c59dcd8..f8a337090230 100644 --- a/src/regex/pcre.cr +++ b/src/regex/pcre.cr @@ -7,7 +7,7 @@ module Regex::PCRE source = source.gsub('\u{0}', "\\0") @source = source - @re = LibPCRE.compile(@source, pcre_options(options) | LibPCRE::UTF8 | LibPCRE::NO_UTF8_CHECK | LibPCRE::DUPNAMES | LibPCRE::UCP, out errptr, out erroffset, nil) + @re = LibPCRE.compile(@source, pcre_compile_options(options) | LibPCRE::UTF8 | LibPCRE::NO_UTF8_CHECK | LibPCRE::DUPNAMES | LibPCRE::UCP, out errptr, out erroffset, nil) raise ArgumentError.new("#{String.new(errptr)} at #{erroffset}") if @re.null? @extra = LibPCRE.study(@re, LibPCRE::STUDY_JIT_COMPILE, out studyerrptr) if @extra.null? && studyerrptr @@ -19,19 +19,55 @@ module Regex::PCRE LibPCRE.full_info(@re, nil, LibPCRE::INFO_CAPTURECOUNT, out @captures) end - private def pcre_options(options) + private def pcre_compile_options(options) flag = 0 Regex::Options.each do |option| if options.includes?(option) flag |= case option - when .ignore_case? then LibPCRE::CASELESS - when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE - when .extended? then LibPCRE::EXTENDED - when .anchored? then LibPCRE::ANCHORED - when .utf_8? then LibPCRE::UTF8 - when .no_utf8_check? then LibPCRE::NO_UTF8_CHECK - when .dupnames? then LibPCRE::DUPNAMES - when .ucp? then LibPCRE::UCP + when .ignore_case? then LibPCRE::CASELESS + when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE + when .dotall? then LibPCRE::DOTALL + when .extended? then LibPCRE::EXTENDED + when .anchored? then LibPCRE::ANCHORED + when .dollar_endonly? then LibPCRE::DOLLAR_ENDONLY + when .firstline? then LibPCRE::FIRSTLINE + when .utf_8? then LibPCRE::UTF8 + when .no_utf8_check? then LibPCRE::NO_UTF8_CHECK + when .dupnames? then LibPCRE::DUPNAMES + when .ucp? then LibPCRE::UCP + when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") + when .no_jit? then raise ArgumentError.new("Invalid regex option NO_JIT for `pcre_compile`") + else + raise "unreachable" + end + options &= ~option + end + end + + # Unnamed values are explicitly used PCRE options, just pass them through: + flag |= options.value + + flag + end + + private def pcre_match_options(options) + flag = 0 + Regex::Options.each do |option| + if options.includes?(option) + flag |= case option + when .ignore_case? then raise ArgumentError.new("Invalid regex option IGNORE_CASE for `pcre_exec`") + when .multiline? then raise ArgumentError.new("Invalid regex option MULTILINE for `pcre_exec`") + when .dotall? then raise ArgumentError.new("Invalid regex option DOTALL for `pcre_exec`") + when .extended? then raise ArgumentError.new("Invalid regex option EXTENDED for `pcre_exec`") + when .anchored? then LibPCRE::ANCHORED + when .dollar_endonly? then raise ArgumentError.new("Invalid regex option DOLLAR_ENDONLY for `pcre_exec`") + when .firstline? then raise ArgumentError.new("Invalid regex option FIRSTLINE for `pcre_exec`") + when .utf_8? then raise ArgumentError.new("Invalid regex option UTF_8 for `pcre_exec`") + when .no_utf8_check? then LibPCRE::NO_UTF8_CHECK + when .dupnames? then raise ArgumentError.new("Invalid regex option DUPNAMES for `pcre_exec`") + when .ucp? then raise ArgumentError.new("Invalid regex option UCP for `pcre_exec`") + when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") + when .no_jit? then raise ArgumentError.new("Regex::Option::NO_JIT is not supported with PCRE") else raise "unreachable" end @@ -106,7 +142,7 @@ module Regex::PCRE # Calls `pcre_exec` C function, and handles returning value. private def internal_matches?(str, byte_index, options, ovector, ovector_size) - ret = LibPCRE.exec(@re, @extra, str, str.bytesize, byte_index, pcre_options(options) | LibPCRE::NO_UTF8_CHECK, ovector, ovector_size) + ret = LibPCRE.exec(@re, @extra, str, str.bytesize, byte_index, pcre_match_options(options) | LibPCRE::NO_UTF8_CHECK, ovector, ovector_size) # TODO: when `ret < -1`, it means PCRE error. It should handle correctly. ret >= 0 end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index ff9b1300d40b..96d863aea73d 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -8,7 +8,7 @@ module Regex::PCRE2 # :nodoc: def initialize(*, _source @source : String, _options @options) - @re = PCRE2.compile(source, pcre2_options(options) | LibPCRE2::UTF | LibPCRE2::NO_UTF_CHECK | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| + @re = PCRE2.compile(source, pcre2_compile_options(options) | LibPCRE2::UTF | LibPCRE2::NO_UTF_CHECK | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| raise ArgumentError.new(error_message) end @@ -41,19 +41,54 @@ module Regex::PCRE2 end end - private def pcre2_options(options) + private def pcre2_compile_options(options) flag = 0 Regex::Options.each do |option| if options.includes?(option) flag |= case option - when .ignore_case? then LibPCRE2::CASELESS - when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE - when .extended? then LibPCRE2::EXTENDED - when .anchored? then LibPCRE2::ANCHORED - when .utf_8? then LibPCRE2::UTF - when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK - when .dupnames? then LibPCRE2::DUPNAMES - when .ucp? then LibPCRE2::UCP + when .ignore_case? then LibPCRE2::CASELESS + when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE + when .dotall? then LibPCRE2::DOTALL + when .extended? then LibPCRE2::EXTENDED + when .anchored? then LibPCRE2::ANCHORED + when .dollar_endonly? then LibPCRE2::DOLLAR_ENDONLY + when .firstline? then LibPCRE2::FIRSTLINE + when .utf_8? then LibPCRE2::UTF + when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK + when .dupnames? then LibPCRE2::DUPNAMES + when .ucp? then LibPCRE2::UCP + when .endanchored? then LibPCRE2::ENDANCHORED + when .no_jit? then raise ArgumentError.new("Invalid regex option NO_JIT for `pcre2_compile`") + else + raise "unreachable" + end + options &= ~option + end + end + unless options.none? + raise ArgumentError.new("Unknown Regex::Option value: #{options}") + end + flag + end + + private def pcre2_match_options(options) + flag = 0 + Regex::Options.each do |option| + if options.includes?(option) + flag |= case option + when .ignore_case? then raise ArgumentError.new("Invalid regex option IGNORE_CASE for `pcre2_match`") + when .multiline? then raise ArgumentError.new("Invalid regex option MULTILINE for `pcre2_match`") + when .dotall? then raise ArgumentError.new("Invalid regex option DOTALL for `pcre2_match`") + when .extended? then raise ArgumentError.new("Invalid regex option EXTENDED for `pcre2_match`") + when .anchored? then LibPCRE2::ANCHORED + when .dollar_endonly? then raise ArgumentError.new("Invalid regex option DOLLAR_ENDONLY for `pcre2_match`") + when .firstline? then raise ArgumentError.new("Invalid regex option FIRSTLINE for `pcre2_match`") + when .utf_8? then raise ArgumentError.new("Invalid regex option UTF_8 for `pcre2_match`") + when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK + when .dupnames? then raise ArgumentError.new("Invalid regex option DUPNAMES for `pcre2_match`") + when .ucp? then raise ArgumentError.new("Invalid regex option UCP for `pcre2_match`") + when .endanchored? then LibPCRE2::ENDANCHORED + when .no_jit? then LibPCRE2::NO_JIT else raise "unreachable" end @@ -186,7 +221,7 @@ module Regex::PCRE2 private def match_data(str, byte_index, options) match_data = self.match_data - match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, PCRE2.match_context) + match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_match_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, PCRE2.match_context) if match_count < 0 case error = LibPCRE2::Error.new(match_count) From 8a1b075d54b2ee4000c0742d28688c566e994cc9 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 31 Mar 2023 16:17:49 +0200 Subject: [PATCH 0407/1551] Fix: LibC definition for sys/socket.h (*-linux-gnu targets) (#13242) --- src/lib_c/i386-linux-gnu/c/sys/socket.cr | 8 ++++---- src/lib_c/x86_64-linux-gnu/c/sys/socket.cr | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib_c/i386-linux-gnu/c/sys/socket.cr b/src/lib_c/i386-linux-gnu/c/sys/socket.cr index b02ed75210dc..3a61519d9baa 100644 --- a/src/lib_c/i386-linux-gnu/c/sys/socket.cr +++ b/src/lib_c/i386-linux-gnu/c/sys/socket.cr @@ -54,10 +54,10 @@ lib LibC fun getsockname(fd : Int, addr : Sockaddr*, len : SocklenT*) : Int fun getsockopt(fd : Int, level : Int, optname : Int, optval : Void*, optlen : SocklenT*) : Int fun listen(fd : Int, n : Int) : Int - fun recv(fd : Int, buf : Void*, n : Int, flags : Int) : SSizeT - fun recvfrom(fd : Int, buf : Void*, n : Int, flags : Int, addr : Sockaddr*, addr_len : SocklenT*) : SSizeT - fun send(fd : Int, buf : Void*, n : Int, flags : Int) : SSizeT - fun sendto(fd : Int, buf : Void*, n : Int, flags : Int, addr : Sockaddr*, addr_len : SocklenT) : SSizeT + fun recv(fd : Int, buf : Void*, n : SizeT, flags : Int) : SSizeT + fun recvfrom(fd : Int, buf : Void*, n : SizeT, flags : Int, addr : Sockaddr*, addr_len : SocklenT*) : SSizeT + fun send(fd : Int, buf : Void*, n : SizeT, flags : Int) : SSizeT + fun sendto(fd : Int, buf : Void*, n : SizeT, flags : Int, addr : Sockaddr*, addr_len : SocklenT) : SSizeT fun setsockopt(fd : Int, level : Int, optname : Int, optval : Void*, optlen : SocklenT) : Int fun shutdown(fd : Int, how : Int) : Int fun socket(domain : Int, type : Int, protocol : Int) : Int diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr b/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr index 6cee17373bca..11785c128f52 100644 --- a/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr +++ b/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr @@ -54,10 +54,10 @@ lib LibC fun getsockname(fd : Int, addr : Sockaddr*, len : SocklenT*) : Int fun getsockopt(fd : Int, level : Int, optname : Int, optval : Void*, optlen : SocklenT*) : Int fun listen(fd : Int, n : Int) : Int - fun recv(fd : Int, buf : Void*, n : Int, flags : Int) : SSizeT - fun recvfrom(fd : Int, buf : Void*, n : Int, flags : Int, addr : Sockaddr*, addr_len : SocklenT*) : SSizeT - fun send(fd : Int, buf : Void*, n : Int, flags : Int) : SSizeT - fun sendto(fd : Int, buf : Void*, n : Int, flags : Int, addr : Sockaddr*, addr_len : SocklenT) : SSizeT + fun recv(fd : Int, buf : Void*, n : SizeT, flags : Int) : SSizeT + fun recvfrom(fd : Int, buf : Void*, n : SizeT, flags : Int, addr : Sockaddr*, addr_len : SocklenT*) : SSizeT + fun send(fd : Int, buf : Void*, n : SizeT, flags : Int) : SSizeT + fun sendto(fd : Int, buf : Void*, n : SizeT, flags : Int, addr : Sockaddr*, addr_len : SocklenT) : SSizeT fun setsockopt(fd : Int, level : Int, optname : Int, optval : Void*, optlen : SocklenT) : Int fun shutdown(fd : Int, how : Int) : Int fun socket(domain : Int, type : Int, protocol : Int) : Int From a5b59390d54e6cdbdd72e8135b93c3ce54aae6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 31 Mar 2023 16:19:01 +0200 Subject: [PATCH 0408/1551] [CI] Extract LLVM tests in separate workflow (#13246) Co-authored-by: Sijawusz Pur Rahnama --- .github/workflows/linux.yml | 35 ----------------------- .github/workflows/llvm.yml | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/llvm.yml diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0e4427b3232a..b30745126c0d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -60,41 +60,6 @@ jobs: - name: Test run: bin/ci build - test_llvm: - env: - ARCH: x86_64 - ARCH_CMD: linux64 - strategy: - fail-fast: false - matrix: - include: - - llvm_version: 13.0.0 - llvm_url: https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz - base_image: ubuntu-22.04 - - llvm_version: 14.0.0 - llvm_url: https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz - base_image: ubuntu-22.04 - - llvm_version: 15.0.6 - llvm_url: https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.6/clang+llvm-15.0.6-x86_64-linux-gnu-ubuntu-18.04.tar.xz - base_image: ubuntu-22.04 - runs-on: ${{ matrix.base_image }} - name: "Test LLVM ${{ matrix.llvm_version }} (${{ matrix.base_image }})" - steps: - - name: Download Crystal source - uses: actions/checkout@v3 - - - name: Prepare System - run: bin/ci prepare_system - - - name: Prepare Build - run: bin/ci prepare_build - - - name: Install LLVM ${{ matrix.llvm_version }} - run: mkdir -p llvm && curl -L ${{ matrix.llvm_url }} > llvm.tar.xz && tar x --xz -C llvm --strip-components=1 -f llvm.tar.xz - - - name: Test - run: bin/ci with_build_env "make clean deps compiler_spec crystal std_spec LLVM_CONFIG=\$(pwd)/llvm/bin/llvm-config threads=1 junit_output=.junit/spec.xml" - x86_64-gnu-test-preview_mt: env: ARCH: x86_64 diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml new file mode 100644 index 000000000000..d6f59d3f83e4 --- /dev/null +++ b/.github/workflows/llvm.yml @@ -0,0 +1,55 @@ +name: LLVM CI + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + SPEC_SPLIT_DOTS: 160 + +jobs: + llvm_test: + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + llvm_version: ["13.0.0", "14.0.0", "15.0.6"] + name: "LLVM ${{ matrix.llvm_version }}" + steps: + - name: Checkout Crystal source + uses: actions/checkout@v3 + + - name: Cache LLVM and Clang + id: cache-llvm + uses: actions/cache@v3 + with: + path: | + C:/Program Files/LLVM + ./llvm + key: llvm-${{ matrix.llvm_version }} + if: "${{ !env.ACT }}" + + - uses: KyleMayes/install-llvm-action@13d5d77cbf0bd7e35cb02a8f9ed4bb85bed3393b # v1.8.0 + with: + version: "${{ matrix.llvm_version }}" + cached: ${{ steps.cache-llvm.outputs.cache-hit }} + + - name: Set LLVM_CONFIG + # LLVM_PATH is set by install-llvm-action + run: echo "LLVM_CONFIG=$LLVM_PATH/bin/llvm-config" >> $GITHUB_ENV + + - name: Install Crystal + uses: crystal-lang/install-crystal@v1 + with: + crystal: "1.7.3" + + - name: Build libllvm_ext + run: make -B deps + + - name: Test compiler_spec + run: make compiler_spec junit_output=.junit/compiler_spec.xml + + - name: Integration test + run: make crystal std_spec threads=1 junit_output=.junit/std_spec.xml From d18862204104dd37a28fcde6b16df5266488e058 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 3 Apr 2023 16:59:02 +0800 Subject: [PATCH 0409/1551] Add workaround for `Value#not_nil!` copying the receiver (#13264) --- spec/std/object_spec.cr | 15 +++++++++++++++ src/object.cr | 13 ++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/spec/std/object_spec.cr b/spec/std/object_spec.cr index 57c0ff48f703..564f6af08375 100644 --- a/spec/std/object_spec.cr +++ b/spec/std/object_spec.cr @@ -162,6 +162,14 @@ private class DefEquals def_equals @x end +private struct TestMutableStruct + getter x = 0 + + def foo + @x += 1 + end +end + describe Object do describe "delegate" do it "delegates" do @@ -543,5 +551,12 @@ describe Object do nil.not_nil!("custom message") end end + + it "does not copy its receiver when it is a value (#13263)" do + x = TestMutableStruct.new + x.not_nil!.foo.should eq(1) + x.not_nil!.foo.should eq(2) + x.foo.should eq(3) + end end end diff --git a/src/object.cr b/src/object.cr index e0933d5a7bb1..81a46fbc8396 100644 --- a/src/object.cr +++ b/src/object.cr @@ -220,7 +220,18 @@ class Object # for example using [`if var`](https://crystal-lang.org/reference/syntax_and_semantics/if_var.html). # `not_nil!` is only meant as a last resort when there's no other way to explain this to the compiler. # Either way, consider instead raising a concrete exception with a descriptive message. - def not_nil!(message = nil) + def not_nil! + self + end + + # :ditto: + # + # *message* has no effect. It is only used by `Nil#not_nil!(message = nil)`. + def not_nil!(message) + # FIXME: the above param-less overload cannot be expressed as an optional + # parameter here, because that would copy the receiver if it is a struct; + # see https://github.com/crystal-lang/crystal/issues/13263#issuecomment-1492885817 + # and also #13265 self end From ec097ca9d11c61dde092a61c71a90f576fb2b023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 3 Apr 2023 10:59:55 +0200 Subject: [PATCH 0410/1551] Fix PCRE crashing on invalid UTF-8 (#13240) --- spec/std/regex_spec.cr | 63 ++++++++++++++++++++++++++++++++++++------ src/regex.cr | 3 ++ src/regex/lib_pcre.cr | 43 ++++++++++++++++++++++++++++ src/regex/lib_pcre2.cr | 4 +++ src/regex/pcre.cr | 32 +++++++++++++++++---- src/regex/pcre2.cr | 42 ++++++++++++++++++++++------ 6 files changed, 166 insertions(+), 21 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index fc8d5a708648..3f5331fc8363 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -25,6 +25,13 @@ describe "Regex" do {% end %} end end + + it "raises on invalid UTF-8" do + expect_raises(ArgumentError, /invalid UTF-8 string|UTF-8 error/) do + Regex.new("\x96") + end + Regex.new("\x96", :NO_UTF8_CHECK).should be_a(Regex) + end end it "#options" do @@ -94,6 +101,20 @@ describe "Regex" do /foo/.match(".foo", options: Regex::Options::ANCHORED).should be_nil /foo/.match("foo", options: Regex::Options::ANCHORED).should_not be_nil end + + it "with invalid UTF-8" do + {% if Regex::Engine.resolve.name == "Regex::PCRE" %} + expect_raises(ArgumentError, "UTF-8 error") do + /([\w_\.@#\/\*])+/.match("\xFF\xFE") + end + {% else %} + if Regex::PCRE2.version_number < {10, 35} + pending! "Error in libpcre2 < 10.35" + else + /([\w_\.@#\/\*])+/.match("\xFF\xFE").should be_nil + end + {% end %} + end end describe "#match_at_byte_index" do @@ -126,9 +147,15 @@ describe "Regex" do end it "multibyte index" do - md = /foo/.match_at_byte_index("öfoo", 1).should_not be_nil - md.begin.should eq 1 - md.byte_begin.should eq 2 + if Regex::Engine.version_number < {10, 34} + expect_raises(ArgumentError, "bad offset into UTF string") do + /foo/.match_at_byte_index("öfoo", 1) + end + else + md = /foo/.match_at_byte_index("öfoo", 1).should_not be_nil + md.begin.should eq 1 + md.byte_begin.should eq 2 + end md = /foo/.match_at_byte_index("öfoo", 2).should_not be_nil md.begin.should eq 1 @@ -205,9 +232,17 @@ describe "Regex" do end it "invalid codepoint" do - /foo/.matches?("f\x96o").should be_false - /f\x96o/.matches?("f\x96o").should be_false - /f.o/.matches?("f\x96o").should be_true + if Regex::Engine.version_number < {10, 34} + expect_raises(ArgumentError, "UTF-8 error") do + /foo/.matches?("f\x96o") + end + else + /foo/.matches?("f\x96o").should be_false + /f\x96o/.matches?("f\x96o").should be_false + /f.o/.matches?("f\x96o").should be_false + /\bf\b/.matches?("f\x96o").should be_true + /\bo\b/.matches?("f\x96o").should be_true + end end end @@ -223,7 +258,12 @@ describe "Regex" do LibPCRE.config LibPCRE::CONFIG_JIT, out jit_enabled pending! "PCRE JIT mode not available." unless 1 == jit_enabled - str.matches?(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/) + # This match may raise on JIT stack limit or not. If it raises, the error message should be the expected one. + begin + str.matches?(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/) + rescue exc : Exception + exc.to_s.should eq("Regex match error: JIT_STACKLIMIT") + end {% else %} # Can't use regex literal because the *LIMIT_DEPTH verb is not supported in libpcre (only libpcre2) # and thus the compiler doesn't recognize it. @@ -249,7 +289,14 @@ describe "Regex" do end it "multibyte index" do - /foo/.matches_at_byte_index?("öfoo", 1).should be_true + if Regex::Engine.version_number < {10, 34} + expect_raises(ArgumentError, "bad offset into UTF string") do + /foo/.matches_at_byte_index?("öfoo", 1) + end + else + /foo/.matches_at_byte_index?("öfoo", 1).should be_true + end + /foo/.matches_at_byte_index?("öfoo", 2).should be_true end pending "negative" do diff --git a/src/regex.cr b/src/regex.cr index 8f8c1484a6e6..ebe844c7878e 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -198,6 +198,9 @@ require "./regex/match_data" class Regex include Regex::Engine + class Error < Exception + end + # List of metacharacters that need to be escaped. # # See `Regex.needs_escape?` and `Regex.escape`. diff --git a/src/regex/lib_pcre.cr b/src/regex/lib_pcre.cr index c5811d348b99..909f6f810584 100644 --- a/src/regex/lib_pcre.cr +++ b/src/regex/lib_pcre.cr @@ -91,6 +91,7 @@ lib LibPCRE fun full_info = pcre_fullinfo(code : Pcre, extra : PcreExtra, what : Int, where : Int*) : Int fun get_stringnumber = pcre_get_stringnumber(code : Pcre, string_name : UInt8*) : Int fun get_stringtable_entries = pcre_get_stringtable_entries(code : Pcre, name : UInt8*, first : UInt8**, last : UInt8**) : Int + fun version = pcre_version : LibC::Char* CONFIG_JIT = 9 @@ -102,4 +103,46 @@ lib LibPCRE INFO_NAMETABLE = 9 $free = pcre_free : Void* -> + + # Exec-time and get/set-time error codes + enum Error + NOMATCH = -1 + NULL = -2 + BADOPTION = -3 + BADMAGIC = -4 + UNKNOWN_OPCODE = -5 + UNKNOWN_NODE = -5 # For backward compatibility + NOMEMORY = -6 + NOSUBSTRING = -7 + MATCHLIMIT = -8 + CALLOUT = -9 # Never used by PCRE itself + BADUTF8 = -10 # Same for 8/16/32 + BADUTF16 = -10 # Same for 8/16/32 + BADUTF32 = -10 # Same for 8/16/32 + BADUTF8_OFFSET = -11 # Same for 8/16 + BADUTF16_OFFSET = -11 # Same for 8/16 + PARTIAL = -12 + BADPARTIAL = -13 + INTERNAL = -14 + BADCOUNT = -15 + DFA_UITEM = -16 + DFA_UCOND = -17 + DFA_UMLIMIT = -18 + DFA_WSSIZE = -19 + DFA_RECURSE = -20 + RECURSIONLIMIT = -21 + NULLWSLIMIT = -22 # No longer actually used + BADNEWLINE = -23 + BADOFFSET = -24 + SHORTUTF8 = -25 + SHORTUTF16 = -25 # Same for 8/16 + RECURSELOOP = -26 + JIT_STACKLIMIT = -27 + BADMODE = -28 + BADENDIANNESS = -29 + DFA_BADRESTART = -30 + JIT_BADOPTION = -31 + BADLENGTH = -32 + UNSET = -33 + end end diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index 87e584702246..a04761cbe07e 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -182,6 +182,10 @@ lib LibPCRE2 CONVERT_SYNTAX = -64 INTERNAL_DUPMATCH = -65 DFA_UINVALID_UTF = -66 + + def utf8_validity? + in?(UTF8_ERR21..UTF8_ERR1) + end end INFO_ALLOPTIONS = 0 diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr index f8a337090230..7bcb9a409fad 100644 --- a/src/regex/pcre.cr +++ b/src/regex/pcre.cr @@ -2,12 +2,23 @@ require "./lib_pcre" # :nodoc: module Regex::PCRE + def self.version : String + String.new(LibPCRE.version) + end + + class_getter version_number : {Int32, Int32} = begin + version = self.version + dot = version.index('.') || raise RuntimeError.new("Invalid libpcre2 version") + space = version.index(' ', dot) || raise RuntimeError.new("Invalid libpcre2 version") + {version.byte_slice(0, dot).to_i, version.byte_slice(dot + 1, space - dot - 1).to_i} + end + private def initialize(*, _source source, _options @options) # PCRE's pattern must have their null characters escaped source = source.gsub('\u{0}', "\\0") @source = source - @re = LibPCRE.compile(@source, pcre_compile_options(options) | LibPCRE::UTF8 | LibPCRE::NO_UTF8_CHECK | LibPCRE::DUPNAMES | LibPCRE::UCP, out errptr, out erroffset, nil) + @re = LibPCRE.compile(@source, pcre_compile_options(options) | LibPCRE::UTF8 | LibPCRE::DUPNAMES | LibPCRE::UCP, out errptr, out erroffset, nil) raise ArgumentError.new("#{String.new(errptr)} at #{erroffset}") if @re.null? @extra = LibPCRE.study(@re, LibPCRE::STUDY_JIT_COMPILE, out studyerrptr) if @extra.null? && studyerrptr @@ -89,7 +100,7 @@ module Regex::PCRE end protected def self.error_impl(source) - re = LibPCRE.compile(source, LibPCRE::UTF8 | LibPCRE::NO_UTF8_CHECK | LibPCRE::DUPNAMES, out errptr, out erroffset, nil) + re = LibPCRE.compile(source, LibPCRE::UTF8 | LibPCRE::DUPNAMES, out errptr, out erroffset, nil) if re {% unless flag?(:interpreted) %} LibPCRE.free.call re.as(Void*) @@ -142,9 +153,20 @@ module Regex::PCRE # Calls `pcre_exec` C function, and handles returning value. private def internal_matches?(str, byte_index, options, ovector, ovector_size) - ret = LibPCRE.exec(@re, @extra, str, str.bytesize, byte_index, pcre_match_options(options) | LibPCRE::NO_UTF8_CHECK, ovector, ovector_size) - # TODO: when `ret < -1`, it means PCRE error. It should handle correctly. - ret >= 0 + ret = LibPCRE.exec(@re, @extra, str, str.bytesize, byte_index, pcre_match_options(options), ovector, ovector_size) + + return true if ret >= 0 + + case error = LibPCRE::Error.new(ret) + when .nomatch? + return false + when .badutf8_offset? + raise ArgumentError.new("Regex match error: bad offset into UTF string") + when .badutf8? + raise ArgumentError.new("Regex match error: UTF-8 error") + else + raise Regex::Error.new("Regex match error: #{error}") + end end module MatchData diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 96d863aea73d..08cde2642a47 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -6,9 +6,27 @@ module Regex::PCRE2 @re : LibPCRE2::Code* @jit : Bool + def self.version : String + String.new(24) do |pointer| + size = LibPCRE2.config(LibPCRE2::CONFIG_VERSION, pointer) + {size - 1, size - 1} + end + end + + class_getter version_number : {Int32, Int32} = begin + version = self.version + dot = version.index('.') || raise RuntimeError.new("Invalid libpcre2 version") + space = version.index(' ', dot) || raise RuntimeError.new("Invalid libpcre2 version") + {version.byte_slice(0, dot).to_i, version.byte_slice(dot + 1, space - dot - 1).to_i} + end + # :nodoc: def initialize(*, _source @source : String, _options @options) - @re = PCRE2.compile(source, pcre2_compile_options(options) | LibPCRE2::UTF | LibPCRE2::NO_UTF_CHECK | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| + options = pcre2_compile_options(options) | LibPCRE2::UTF | LibPCRE2::DUPNAMES | LibPCRE2::UCP + if PCRE2.version_number >= {10, 34} + options |= LibPCRE2::MATCH_INVALID_UTF + end + @re = PCRE2.compile(source, options) do |error_message| raise ArgumentError.new(error_message) end @@ -33,14 +51,18 @@ module Regex::PCRE2 if res = LibPCRE2.compile(source, source.bytesize, options, out errorcode, out erroroffset, nil) res else - message = String.new(256) do |buffer| - bytesize = LibPCRE2.get_error_message(errorcode, buffer, 256) - {bytesize, 0} - end + message = get_error_message(errorcode) yield "#{message} at #{erroroffset}" end end + protected def self.get_error_message(errorcode) + String.new(256) do |buffer| + bytesize = LibPCRE2.get_error_message(errorcode, buffer, 256) + {bytesize, 0} + end + end + private def pcre2_compile_options(options) flag = 0 Regex::Options.each do |option| @@ -108,7 +130,7 @@ module Regex::PCRE2 end protected def self.error_impl(source) - code = PCRE2.compile(source, LibPCRE2::UTF | LibPCRE2::NO_UTF_CHECK | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| + code = PCRE2.compile(source, LibPCRE2::UTF | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| return error_message end @@ -221,14 +243,18 @@ module Regex::PCRE2 private def match_data(str, byte_index, options) match_data = self.match_data - match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_match_options(options) | LibPCRE2::NO_UTF_CHECK, match_data, PCRE2.match_context) + match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_match_options(options), match_data, PCRE2.match_context) if match_count < 0 case error = LibPCRE2::Error.new(match_count) when .nomatch? return + when .badutfoffset?, .utf8_validity? + error_message = PCRE2.get_error_message(error) + raise ArgumentError.new("Regex match error: #{error_message}") else - raise Exception.new("Regex match error: #{error}") + error_message = PCRE2.get_error_message(error) + raise Regex::Error.new("Regex match error: #{error_message}") end end From ea16396d7158c724280d8e0c583ae78417d0feec Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 4 Apr 2023 01:42:21 +0800 Subject: [PATCH 0411/1551] Fix `Pointer#copy_to` overflow on unsigned size and different target type (#13269) --- spec/std/pointer_spec.cr | 7 +++++++ src/pointer.cr | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/spec/std/pointer_spec.cr b/spec/std/pointer_spec.cr index 87772f92c6a2..ae51706cf6f6 100644 --- a/spec/std/pointer_spec.cr +++ b/spec/std/pointer_spec.cr @@ -85,6 +85,13 @@ describe "Pointer" do p1.copy_to(p2 || p3, 4) 4.times { |i| p2[i].should eq(p1[i]) } end + + it "doesn't raise OverflowError on unsigned size and different target type" do + p1 = Pointer.malloc(4, 1) + p2 = Pointer.malloc(4, 0 || nil) + p1.copy_to(p2, 4_u32) + 4.times { |i| p2[i].should eq(p1[i]) } + end end describe "move_from" do diff --git a/src/pointer.cr b/src/pointer.cr index 11ee89c0101c..2479ef0bbb77 100644 --- a/src/pointer.cr +++ b/src/pointer.cr @@ -247,7 +247,8 @@ struct Pointer(T) if self.class == source.class Intrinsics.memcpy(self.as(Void*), source.as(Void*), bytesize(count), false) else - while (count -= 1) >= 0 + while count > 0 + count &-= 1 self[count] = source[count] end end From 58ec6c23c1b4276b578636901c256b8ef423e0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 4 Apr 2023 11:21:29 +0200 Subject: [PATCH 0412/1551] [CI] Extract interpreter workflow and split `std_spec` execution (#13267) --- .github/workflows/interpreter.yml | 64 +++++++++++++++++++++++++++++++ .github/workflows/linux.yml | 28 -------------- 2 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/interpreter.yml diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml new file mode 100644 index 000000000000..6fbd213aa8d5 --- /dev/null +++ b/.github/workflows/interpreter.yml @@ -0,0 +1,64 @@ +name: Interpreter Test + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + SPEC_SPLIT_DOTS: 160 + +jobs: + test-interpreter_spec: + runs-on: ubuntu-22.04 + container: + image: crystallang/crystal:1.7.3-build + name: "Test Interpreter" + steps: + - uses: actions/checkout@v3 + + - name: Test interpreter_spec + run: make deps && bin/crystal build -o interpreter_spec spec/compiler/interpreter_spec.cr && ./interpreter_spec --junit_output .junit/interpreter_spec.xml + + build-interpreter: + runs-on: ubuntu-22.04 + container: + image: crystallang/crystal:1.7.3-build + name: Build interpreter + steps: + - uses: actions/checkout@v3 + + - name: Build compiler + run: make interpreter=1 release=1 + + - name: Upload compiler artifact + uses: actions/upload-artifact@v3 + with: + name: crystal-interpreter + path: | + .build/crystal + + test-interpreter-std_spec: + needs: build-interpreter + runs-on: ubuntu-22.04 + container: + image: crystallang/crystal:1.7.3-build + strategy: + matrix: + part: [0, 1, 2, 3] + name: "Test std_spec with interpreter (${{ matrix.part }})" + steps: + - uses: actions/checkout@v3 + + - name: Download compiler artifact + uses: actions/download-artifact@v3 + with: + name: crystal-interpreter + path: .build/ + + - name: Mark downloaded compiler as executable + run: chmod +x .build/crystal + + - name: Run std_spec with interpreter + run: SPEC_SPLIT="${{ matrix.part }}%4" bin/crystal i spec/interpreter_std_spec.cr -- --junit_output .junit/interpreter-std_spec.${{ matrix.part }}.xml diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index b30745126c0d..4f1344880c8c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -81,34 +81,6 @@ jobs: - name: Test run: bin/ci with_build_env 'CRYSTAL_WORKERS=4 make std_spec threads=1 FLAGS="-D preview_mt"' - test_interpreter: - env: - ARCH: ${{ matrix.arch }} - ARCH_CMD: linux64 - runs-on: ubuntu-latest - strategy: - matrix: - arch: - - x86_64 - steps: - - name: Download Crystal source - uses: actions/checkout@v3 - - - name: Prepare System - run: bin/ci prepare_system - - - name: Prepare Build - run: bin/ci prepare_build - - - name: Test interpreter - run: bin/ci with_build_env 'make deps && bin/crystal build -o interpreter_spec spec/compiler/interpreter_spec.cr && ./interpreter_spec' - - - name: Build interpreter - run: bin/ci with_build_env 'make interpreter=1 release=1' - - - name: Test std specs with interpreter - run: bin/ci with_build_env 'bin/crystal i spec/interpreter_std_spec.cr' - check_format: env: ARCH: x86_64 From 7f4658980888da9c3f6935b74b02453944ebd119 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 4 Apr 2023 17:54:57 +0800 Subject: [PATCH 0413/1551] Fix `Array#replace` on shifted arrays (#13256) --- spec/std/array_spec.cr | 50 +++++++++++++++++++++++++++++++++++++----- src/array.cr | 25 +++++++++++++++++++-- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/spec/std/array_spec.cr b/spec/std/array_spec.cr index d922d4ef8afc..a5dd2e60f1fd 100644 --- a/spec/std/array_spec.cr +++ b/spec/std/array_spec.cr @@ -960,11 +960,51 @@ describe "Array" do end end - it "does replace" do - a = [1, 2, 3] - b = [1] - b.replace a - b.should eq(a) + describe "#replace" do + it "replaces all elements" do + a = [1, 2, 3] + b = [4, 5, 6] + a.replace(b).should be(a) + a.should eq(b) + end + + it "reuses the buffer if possible" do + a = [1, 2, 3, 4, 5] + a.shift + b = [6, 7, 8, 9, 10] + a.replace(b).should be(a) + a.should eq(b) + a.@capacity.should eq(5) + a.@offset_to_buffer.should eq(0) + + a = [1, 2, 3, 4, 5] + a.shift(2) + b = [6, 7, 8, 9] + a.replace(b).should be(a) + a.should eq(b) + a.@capacity.should eq(5) + a.@offset_to_buffer.should eq(1) + end + + it "resizes the buffer if capacity is not enough" do + a = [1, 2, 3, 4, 5] + b = [6, 7, 8, 9, 10, 11] + a.replace(b).should be(a) + a.should eq(b) + a.@capacity.should eq(10) + a.@offset_to_buffer.should eq(0) + end + + it "clears unused elements if new size is smaller" do + a = [1, 2, 3, 4, 5] + b = [6, 7, 8] + a.replace(b).should be(a) + a.should eq(b) + a.@capacity.should eq(5) + a.@offset_to_buffer.should eq(0) + a.unsafe_fetch(3).should eq(0) + a.unsafe_fetch(4).should eq(0) + end end it "does reverse with an odd number of elements" do diff --git a/src/array.cr b/src/array.cr index 7b283aaf13f2..cd8341fd63f3 100644 --- a/src/array.cr +++ b/src/array.cr @@ -1392,9 +1392,17 @@ class Array(T) # a2 # => [1, 2, 3] # ``` def replace(other : Array) : self - @size = other.size - resize_to_capacity(Math.pw2ceil(@size)) if @size > @capacity + if other.size > @capacity + reset_buffer_to_root_buffer + resize_to_capacity(calculate_new_capacity(other.size)) + elsif other.size > remaining_capacity + shift_buffer_by(remaining_capacity - other.size) + elsif other.size < @size + (@buffer + other.size).clear(@size - other.size) + end + @buffer.copy_from(other.to_unsafe, other.size) + @size = other.size self end @@ -2051,6 +2059,7 @@ class Array(T) @capacity - @offset_to_buffer end + # behaves like `calculate_new_capacity(@capacity + 1)` private def calculate_new_capacity return INITIAL_CAPACITY if @capacity == 0 @@ -2061,6 +2070,18 @@ class Array(T) end end + private def calculate_new_capacity(new_size) + new_capacity = @capacity == 0 ? INITIAL_CAPACITY : @capacity + while new_capacity < new_size + if new_capacity < CAPACITY_THRESHOLD + new_capacity *= 2 + else + new_capacity += (new_capacity + 3 * CAPACITY_THRESHOLD) // 4 + end + end + new_capacity + end + private def increase_capacity resize_to_capacity(calculate_new_capacity) end From 7b5a849e1dbb0345083d40623a1bcb417ea8a434 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Tue, 4 Apr 2023 11:10:18 -0400 Subject: [PATCH 0414/1551] Avoid `test.cr` in root of repo conflicting with parser warning specs (#13259) Co-authored-by: Quinton Miller --- spec/compiler/parser/warnings_spec.cr | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/compiler/parser/warnings_spec.cr b/spec/compiler/parser/warnings_spec.cr index 8fdf527ce2c7..734207405663 100644 --- a/spec/compiler/parser/warnings_spec.cr +++ b/spec/compiler/parser/warnings_spec.cr @@ -2,7 +2,7 @@ require "../../support/syntax" private def assert_parser_warning(source, message, *, file = __FILE__, line = __LINE__) parser = Parser.new(source) - parser.filename = "test.cr" + parser.filename = "/test.cr" parser.parse warnings = parser.warnings.infos @@ -12,7 +12,7 @@ end private def assert_no_parser_warning(source, *, file = __FILE__, line = __LINE__) parser = Parser.new(source) - parser.filename = "test.cr" + parser.filename = "/test.cr" parser.parse warnings = parser.warnings.infos @@ -39,28 +39,28 @@ describe "Parser warnings" do describe "warns on missing space before colon" do it "in block param type restriction" do - assert_parser_warning("def foo(&block: Foo)\nend", "warning in test.cr:1\nWarning: space required before colon in type restriction (run `crystal tool format` to fix this)") + assert_parser_warning("def foo(&block: Foo)\nend", "warning in /test.cr:1\nWarning: space required before colon in type restriction (run `crystal tool format` to fix this)") assert_no_parser_warning("def foo(&block : Foo)\nend") assert_no_parser_warning("def foo(&@foo)\nend") end it "in anonymous block param type restriction" do - assert_parser_warning("def foo(&: Foo)\nend", "warning in test.cr:1\nWarning: space required before colon in type restriction (run `crystal tool format` to fix this)") + assert_parser_warning("def foo(&: Foo)\nend", "warning in /test.cr:1\nWarning: space required before colon in type restriction (run `crystal tool format` to fix this)") assert_no_parser_warning("def foo(& : Foo)\nend") assert_no_parser_warning("def foo(&)\nend") end it "in type declaration" do - assert_parser_warning("x: Int32", "warning in test.cr:1\nWarning: space required before colon in type declaration (run `crystal tool format` to fix this)") + assert_parser_warning("x: Int32", "warning in /test.cr:1\nWarning: space required before colon in type declaration (run `crystal tool format` to fix this)") assert_no_parser_warning("x : Int32") - assert_parser_warning("class Foo\n@x: Int32\nend", "warning in test.cr:2\nWarning: space required before colon in type declaration (run `crystal tool format` to fix this)") + assert_parser_warning("class Foo\n@x: Int32\nend", "warning in /test.cr:2\nWarning: space required before colon in type declaration (run `crystal tool format` to fix this)") assert_no_parser_warning("class Foo\n@x : Int32\nend") - assert_parser_warning("class Foo\n@@x: Int32\nend", "warning in test.cr:2\nWarning: space required before colon in type declaration (run `crystal tool format` to fix this)") + assert_parser_warning("class Foo\n@@x: Int32\nend", "warning in /test.cr:2\nWarning: space required before colon in type declaration (run `crystal tool format` to fix this)") assert_no_parser_warning("class Foo\n@@x : Int32\nend") end it "in return type restriction" do - assert_parser_warning("def foo: Foo\nend", "warning in test.cr:1\nWarning: space required before colon in return type restriction (run `crystal tool format` to fix this)") + assert_parser_warning("def foo: Foo\nend", "warning in /test.cr:1\nWarning: space required before colon in return type restriction (run `crystal tool format` to fix this)") assert_no_parser_warning("def foo : Foo\nend") end end From 578646b5ae5e3df5f7a2f274eacb6ed50936175b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 5 Apr 2023 09:45:12 +0200 Subject: [PATCH 0415/1551] Makefile: Add `interpreter_spec` (#13251) --- .github/workflows/interpreter.yml | 2 +- .github/workflows/macos.yml | 2 +- Makefile | 9 +++++++++ Makefile.win | 9 +++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 6fbd213aa8d5..1abb3cb16233 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v3 - name: Test interpreter_spec - run: make deps && bin/crystal build -o interpreter_spec spec/compiler/interpreter_spec.cr && ./interpreter_spec --junit_output .junit/interpreter_spec.xml + run: make interpreter_spec junit_output=.junit/interpreter_spec.xml build-interpreter: runs-on: ubuntu-22.04 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index bf3d85df4277..3cdbf8412db1 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -37,4 +37,4 @@ jobs: run: bin/ci build - name: Test interpreter - run: bin/ci with_build_env 'bin/crystal spec --order=random spec/compiler/interpreter_spec.cr' + run: bin/ci with_build_env 'make interpreter_spec' diff --git a/Makefile b/Makefile index fe714d2ba393..789443fe902e 100644 --- a/Makefile +++ b/Makefile @@ -102,6 +102,10 @@ compiler_spec: $(O)/compiler_spec ## Run compiler specs primitives_spec: $(O)/primitives_spec ## Run primitives specs $(O)/primitives_spec $(SPEC_FLAGS) +.PHONY: interpreter_spec +interpreter_spec: $(O)/interpreter_spec ## Run interpreter specs + $(O)/interpreter_spec $(SPEC_FLAGS) + .PHONY: smoke_test smoke_test: ## Build specs as a smoke test smoke_test: $(O)/std_spec $(O)/compiler_spec $(O)/crystal @@ -194,6 +198,11 @@ $(O)/primitives_spec: $(O)/crystal $(DEPS) $(SOURCES) $(SPEC_SOURCES) @mkdir -p $(O) $(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/primitives_spec.cr +$(O)/interpreter_spec: deps $(SOURCES) $(SPEC_SOURCES) + $(eval interpreter=1) + @mkdir -p $(O) + $(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler/interpreter_spec.cr + $(O)/crystal: $(DEPS) $(SOURCES) $(call check_llvm_config) @mkdir -p $(O) diff --git a/Makefile.win b/Makefile.win index be012b422db8..6496eb009fcb 100644 --- a/Makefile.win +++ b/Makefile.win @@ -104,6 +104,10 @@ compiler_spec: $(O)\compiler_spec.exe ## Run compiler specs primitives_spec: $(O)\primitives_spec.exe ## Run primitives specs $(O)\primitives_spec $(SPEC_FLAGS) +.PHONY: interpreter_spec +interpreter_spec: $(O)\interpreter_spec ## Run interpreter specs + $(O)\interpreter_spec $(SPEC_FLAGS) + .PHONY: smoke_test smoke_test: ## Build specs as a smoke test smoke_test: $(O)\std_spec.exe $(O)\compiler_spec.exe $(O)\crystal.exe @@ -185,6 +189,11 @@ $(O)\primitives_spec.exe: $(O)\crystal.exe $(DEPS) $(SOURCES) $(SPEC_SOURCES) @$(call MKDIR,"$(O)") .\bin\crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o "$@" spec\primitives_spec.cr +$(O)\interpreter_spec: deps $(SOURCES) $(SPEC_SOURCES) + $(eval interpreter=1) + @$(call MKDIR, "$(O)") + .\bin\crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec\compiler\interpreter_spec.cr + $(O)\crystal.exe: $(DEPS) $(SOURCES) $(O)\crystal.res $(call check_llvm_config) @$(call MKDIR,"$(O)") From 706075ea66ab7e9d59b2bfdc9df6e22b5438624f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 5 Apr 2023 09:45:33 +0200 Subject: [PATCH 0416/1551] Add specs for regex literal expansion (#13253) --- spec/compiler/normalize/regex_spec.cr | 31 +++++++++++++++++++ .../crystal/semantic/literal_expander.cr | 16 +++++----- 2 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 spec/compiler/normalize/regex_spec.cr diff --git a/spec/compiler/normalize/regex_spec.cr b/spec/compiler/normalize/regex_spec.cr new file mode 100644 index 000000000000..f4c6cc60f3b3 --- /dev/null +++ b/spec/compiler/normalize/regex_spec.cr @@ -0,0 +1,31 @@ +require "../../spec_helper" + +describe "Normalize: regex literal" do + describe "options" do + it "empty" do + assert_expand %q(/#{"".to_s}/), <<-'CRYSTAL' + ::Regex.new("#{"".to_s}", ::Regex::Options.new(0)) + CRYSTAL + end + it "i" do + assert_expand %q(/#{"".to_s}/i), <<-'CRYSTAL' + ::Regex.new("#{"".to_s}", ::Regex::Options.new(1)) + CRYSTAL + end + it "x" do + assert_expand %q(/#{"".to_s}/x), <<-'CRYSTAL' + ::Regex.new("#{"".to_s}", ::Regex::Options.new(8)) + CRYSTAL + end + it "im" do + assert_expand %q(/#{"".to_s}/im), <<-'CRYSTAL' + ::Regex.new("#{"".to_s}", ::Regex::Options.new(7)) + CRYSTAL + end + it "imx" do + assert_expand %q(/#{"".to_s}/imx), <<-'CRYSTAL' + ::Regex.new("#{"".to_s}", ::Regex::Options.new(15)) + CRYSTAL + end + end +end diff --git a/src/compiler/crystal/semantic/literal_expander.cr b/src/compiler/crystal/semantic/literal_expander.cr index e16b31fe6571..8a4d389df267 100644 --- a/src/compiler/crystal/semantic/literal_expander.cr +++ b/src/compiler/crystal/semantic/literal_expander.cr @@ -343,6 +343,14 @@ module Crystal end end + private def regex_new_call(node, value) + Call.new(Path.global("Regex").at(node), "new", value, regex_options(node)).at(node) + end + + private def regex_options(node) + Call.new(Path.global(["Regex", "Options"]).at(node), "new", NumberLiteral.new(node.options.value.to_s).at(node)).at(node) + end + # Convert and to if: # # From: @@ -1019,14 +1027,6 @@ module Crystal end end - private def regex_new_call(node, value) - Call.new(Path.global("Regex").at(node), "new", value, regex_options(node)).at(node) - end - - private def regex_options(node) - Call.new(Path.global(["Regex", "Options"]).at(node), "new", NumberLiteral.new(node.options.value).at(node)).at(node) - end - def expand(node) raise "#{node} (#{node.class}) can't be expanded" end From 852a44d5f8fea470acd051ca726940bad3d91f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 5 Apr 2023 09:45:57 +0200 Subject: [PATCH 0417/1551] Add `Regex::MatchOptions` (#13248) --- spec/compiler/parser/parser_spec.cr | 10 +- spec/std/regex_spec.cr | 74 ++++++++---- .../crystal/semantic/literal_expander.cr | 2 +- src/compiler/crystal/syntax/ast.cr | 4 +- src/compiler/crystal/syntax/parser.cr | 10 +- src/compiler/crystal/syntax/to_s.cr | 6 +- src/regex.cr | 110 ++++++++++++++++-- src/regex/pcre.cr | 25 +++- src/regex/pcre2.cr | 24 +++- src/string.cr | 2 +- src/string_scanner.cr | 10 +- 11 files changed, 216 insertions(+), 61 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index bb4c727dde87..9a804b8f05a4 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1,6 +1,6 @@ require "../../support/syntax" -private def regex(string, options = Regex::Options::None) +private def regex(string, options = Regex::CompileOptions::None) RegexLiteral.new(StringLiteral.new(string), options) end @@ -1197,10 +1197,10 @@ module Crystal it_parses "1.!(\n)", Not.new(1.int32) it_parses "/foo/", regex("foo") - it_parses "/foo/i", regex("foo", Regex::Options::IGNORE_CASE) - it_parses "/foo/m", regex("foo", Regex::Options::MULTILINE) - it_parses "/foo/x", regex("foo", Regex::Options::EXTENDED) - it_parses "/foo/imximx", regex("foo", Regex::Options::IGNORE_CASE | Regex::Options::MULTILINE | Regex::Options::EXTENDED) + it_parses "/foo/i", regex("foo", Regex::CompileOptions::IGNORE_CASE) + it_parses "/foo/m", regex("foo", Regex::CompileOptions::MULTILINE) + it_parses "/foo/x", regex("foo", Regex::CompileOptions::EXTENDED) + it_parses "/foo/imximx", regex("foo", Regex::CompileOptions::IGNORE_CASE | Regex::CompileOptions::MULTILINE | Regex::CompileOptions::EXTENDED) it_parses "/fo\\so/", regex("fo\\so") it_parses "/fo\#{1}o/", RegexLiteral.new(StringInterpolation.new(["fo".string, 1.int32, "o".string] of ASTNode)) it_parses "/(fo\#{\"bar\"}\#{1}o)/", RegexLiteral.new(StringInterpolation.new(["(fo".string, "bar".string, 1.int32, "o)".string] of ASTNode)) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 3f5331fc8363..f71684ef66bb 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -12,15 +12,15 @@ describe "Regex" do describe "options" do it "regular" do - Regex.new("", Regex::Options::ANCHORED).options.anchored?.should be_true + Regex.new("", Regex::CompileOptions::ANCHORED).options.anchored?.should be_true end it "unnamed option" do {% if Regex::Engine.resolve.name == "Regex::PCRE" %} - Regex.new("^/foo$", Regex::Options.new(0x00000020)).matches?("/foo\n").should be_false + Regex.new("^/foo$", Regex::CompileOptions.new(0x00000020)).matches?("/foo\n").should be_false {% else %} expect_raises ArgumentError, "Unknown Regex::Option value: 64" do - Regex.new("", Regex::Options.new(0x00000040)) + Regex.new("", Regex::CompileOptions.new(0x00000040)) end {% end %} end @@ -97,9 +97,16 @@ describe "Regex" do end end - it "with options" do - /foo/.match(".foo", options: Regex::Options::ANCHORED).should be_nil - /foo/.match("foo", options: Regex::Options::ANCHORED).should_not be_nil + context "with options" do + it "deprecated Regex::Options" do + /foo/.match(".foo", options: Regex::Options::ANCHORED).should be_nil + /foo/.match("foo", options: Regex::Options::ANCHORED).should_not be_nil + end + + it "Regex::Match options" do + /foo/.match(".foo", options: Regex::MatchOptions::ANCHORED).should be_nil + /foo/.match("foo", options: Regex::MatchOptions::ANCHORED).should_not be_nil + end end it "with invalid UTF-8" do @@ -168,9 +175,16 @@ describe "Regex" do /foo/.match_at_byte_index("..foo", -2).should be_nil end - it "with options" do - /foo/.match_at_byte_index("..foo", 1, options: Regex::Options::ANCHORED).should be_nil - /foo/.match_at_byte_index(".foo", 1, options: Regex::Options::ANCHORED).should_not be_nil + context "with options" do + it "deprecated Regex::Options" do + /foo/.match_at_byte_index("..foo", 1, options: Regex::Options::ANCHORED).should be_nil + /foo/.match_at_byte_index(".foo", 1, options: Regex::Options::ANCHORED).should_not be_nil + end + + it "Regex::MatchOptions" do + /foo/.match_at_byte_index("..foo", 1, options: Regex::MatchOptions::ANCHORED).should be_nil + /foo/.match_at_byte_index(".foo", 1, options: Regex::MatchOptions::ANCHORED).should_not be_nil + end end end @@ -213,8 +227,8 @@ describe "Regex" do end it "anchored" do - Regex.new("foo", Regex::Options::ANCHORED).matches?("foo").should be_true - Regex.new("foo", Regex::Options::ANCHORED).matches?(".foo").should be_false + Regex.new("foo", Regex::CompileOptions::ANCHORED).matches?("foo").should be_true + Regex.new("foo", Regex::CompileOptions::ANCHORED).matches?(".foo").should be_false end end @@ -246,9 +260,16 @@ describe "Regex" do end end - it "with options" do - /foo/.matches?(".foo", options: Regex::Options::ANCHORED).should be_false - /foo/.matches?("foo", options: Regex::Options::ANCHORED).should be_true + context "with options" do + it "deprecated Regex::Options" do + /foo/.matches?(".foo", options: Regex::Options::ANCHORED).should be_false + /foo/.matches?("foo", options: Regex::Options::ANCHORED).should be_true + end + + it "Regex::MatchOptions" do + /foo/.matches?(".foo", options: Regex::MatchOptions::ANCHORED).should be_false + /foo/.matches?("foo", options: Regex::MatchOptions::ANCHORED).should be_true + end end it "doesn't crash with a large single line string" do @@ -304,9 +325,16 @@ describe "Regex" do /foo/.matches_at_byte_index?("..foo", -2).should be_false end - it "with options" do - /foo/.matches_at_byte_index?("..foo", 1, options: Regex::Options::ANCHORED).should be_false - /foo/.matches_at_byte_index?(".foo", 1, options: Regex::Options::ANCHORED).should be_true + context "with options" do + it "deprecated Regex::Options" do + /foo/.matches_at_byte_index?("..foo", 1, options: Regex::Options::ANCHORED).should be_false + /foo/.matches_at_byte_index?(".foo", 1, options: Regex::Options::ANCHORED).should be_true + end + + it "Regex::MatchOptions" do + /foo/.matches_at_byte_index?("..foo", 1, options: Regex::MatchOptions::ANCHORED).should be_false + /foo/.matches_at_byte_index?(".foo", 1, options: Regex::MatchOptions::ANCHORED).should be_true + end end end @@ -410,18 +438,18 @@ describe "Regex" do end it "#==" do - regex = Regex.new("foo", Regex::Options::IGNORE_CASE) - (regex == Regex.new("foo", Regex::Options::IGNORE_CASE)).should be_true + regex = Regex.new("foo", Regex::CompileOptions::IGNORE_CASE) + (regex == Regex.new("foo", Regex::CompileOptions::IGNORE_CASE)).should be_true (regex == Regex.new("foo")).should be_false - (regex == Regex.new("bar", Regex::Options::IGNORE_CASE)).should be_false + (regex == Regex.new("bar", Regex::CompileOptions::IGNORE_CASE)).should be_false (regex == Regex.new("bar")).should be_false end it "#hash" do - hash = Regex.new("foo", Regex::Options::IGNORE_CASE).hash - hash.should eq(Regex.new("foo", Regex::Options::IGNORE_CASE).hash) + hash = Regex.new("foo", Regex::CompileOptions::IGNORE_CASE).hash + hash.should eq(Regex.new("foo", Regex::CompileOptions::IGNORE_CASE).hash) hash.should_not eq(Regex.new("foo").hash) - hash.should_not eq(Regex.new("bar", Regex::Options::IGNORE_CASE).hash) + hash.should_not eq(Regex.new("bar", Regex::CompileOptions::IGNORE_CASE).hash) hash.should_not eq(Regex.new("bar").hash) end diff --git a/src/compiler/crystal/semantic/literal_expander.cr b/src/compiler/crystal/semantic/literal_expander.cr index 8a4d389df267..995595d8480b 100644 --- a/src/compiler/crystal/semantic/literal_expander.cr +++ b/src/compiler/crystal/semantic/literal_expander.cr @@ -1,7 +1,7 @@ module Crystal class LiteralExpander def initialize(@program : Program) - @regexes = [] of {String, Regex::Options} + @regexes = [] of {String, Regex::CompileOptions} end # Converts an array literal to creating an Array and storing the values: diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 25198b4e863d..b3ab3c761f0c 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -518,9 +518,9 @@ module Crystal class RegexLiteral < ASTNode property value : ASTNode - property options : Regex::Options + property options : Regex::CompileOptions - def initialize(@value, @options = Regex::Options::None) + def initialize(@value, @options = Regex::CompileOptions::None) end def accept_children(visitor) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index cb9a9124e20b..01b58a68fc81 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -2134,7 +2134,7 @@ module Crystal end def consume_delimiter(pieces, delimiter_state, has_interpolation) - options = Regex::Options::None + options = Regex::CompileOptions::None token_end_location = nil while true case @token.type @@ -2194,17 +2194,17 @@ module Crystal end def consume_regex_options - options = Regex::Options::None + options = Regex::CompileOptions::None while true case current_char when 'i' - options |= Regex::Options::IGNORE_CASE + options |= Regex::CompileOptions::IGNORE_CASE next_char when 'm' - options |= Regex::Options::MULTILINE + options |= Regex::CompileOptions::MULTILINE next_char when 'x' - options |= Regex::Options::EXTENDED + options |= Regex::CompileOptions::EXTENDED next_char else if 'a' <= current_char.downcase <= 'z' diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index d3589475f439..cdd0780bdf70 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -994,9 +994,9 @@ module Crystal end @str << '/' end - @str << 'i' if node.options.includes? Regex::Options::IGNORE_CASE - @str << 'm' if node.options.includes? Regex::Options::MULTILINE - @str << 'x' if node.options.includes? Regex::Options::EXTENDED + @str << 'i' if node.options.ignore_case? + @str << 'm' if node.options.multiline? + @str << 'x' if node.options.extended? false end diff --git a/src/regex.cr b/src/regex.cr index ebe844c7878e..6c4109d62f91 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -210,6 +210,10 @@ class Regex '=', '!', '<', '>', '|', ':', '-', } + # Represents compile options passed to `Regex.new`. + # + # This type is intended to be renamed to `CompileOptions`. Please use that + # name. @[Flags] enum Options : UInt64 # Case insensitive match. @@ -234,7 +238,7 @@ class Regex # Ignore white space and `#` comments. EXTENDED = 0x0000_0008 - # Force pattern anchoring. + # Force pattern anchoring at the start of the subject. ANCHORED = 0x0000_0010 DOLLAR_ENDONLY = 0x0000_0020 @@ -249,14 +253,38 @@ class Regex # :nodoc: UCP = 0x2000_0000 + # Force pattern anchoring at the end of the subject. + # + # Unsupported with PCRE. ENDANCHORED = 0x8000_0000 + end + + # Represents compile options passed to `Regex.new`. + # + # This alias is supposed to replace `Options`. + alias CompileOptions = Options + + # Represents options passed to regex match methods such as `Regex#match`. + @[Flags] + enum MatchOptions + # Force pattern anchoring at the start of the subject. + ANCHORED + + # Force pattern anchoring at the end of the subject. + # + # Unsupported with PCRE. + ENDANCHORED + + # Disable JIT engine. + # + # Unsupported with PCRE. NO_JIT end - # Returns a `Regex::Options` representing the optional flags applied to this `Regex`. + # Returns a `Regex::CompileOptions` representing the optional flags applied to this `Regex`. # # ``` - # /ab+c/ix.options # => Regex::Options::IGNORE_CASE | Regex::Options::EXTENDED + # /ab+c/ix.options # => Regex::CompileOptions::IGNORE_CASE | Regex::CompileOptions::EXTENDED # /ab+c/ix.options.to_s # => "IGNORE_CASE | EXTENDED" # ``` getter options : Options @@ -271,9 +299,9 @@ class Regex # Creates a new `Regex` out of the given source `String`. # # ``` - # Regex.new("^a-z+:\\s+\\w+") # => /^a-z+:\s+\w+/ - # Regex.new("cat", Regex::Options::IGNORE_CASE) # => /cat/i - # options = Regex::Options::IGNORE_CASE | Regex::Options::EXTENDED + # Regex.new("^a-z+:\\s+\\w+") # => /^a-z+:\s+\w+/ + # Regex.new("cat", Regex::CompileOptions::IGNORE_CASE) # => /cat/i + # options = Regex::CompileOptions::IGNORE_CASE | Regex::CompileOptions::EXTENDED # Regex.new("dog", options) # => /dog/ix # ``` def self.new(source : String, options : Options = Options::None) @@ -478,7 +506,7 @@ class Regex # /(.)(.)/.match("abc", 1).try &.[2] # => "c" # /(.)(.)/.match("クリスタル", 3).try &.[2] # => "ル" # ``` - def match(str, pos = 0, options = Regex::Options::None) : MatchData? + def match(str : String, pos : Int32 = 0, options : Regex::MatchOptions = :none) : MatchData? if byte_index = str.char_index_to_byte_index(pos) $~ = match_at_byte_index(str, byte_index, options) else @@ -486,6 +514,22 @@ class Regex end end + # :ditto: + @[Deprecated("Use the overload with `Regex::MatchOptions` instead.")] + def match(str, pos = 0, *, options) : MatchData? + if byte_index = str.char_index_to_byte_index(pos) + $~ = match_at_byte_index(str, byte_index, options) + else + $~ = nil + end + end + + # :ditto: + @[Deprecated("Use the overload with `Regex::MatchOptions` instead.")] + def match(str, pos, _options) : MatchData? + match(str, pos, options: _options) + end + # Match at byte index. Matches a regular expression against `String` # *str*. Starts at the byte index given by *pos* if given, otherwise at # the start of *str*. Returns a `Regex::MatchData` if *str* matched, otherwise @@ -496,7 +540,7 @@ class Regex # /(.)(.)/.match_at_byte_index("abc", 1).try &.[2] # => "c" # /(.)(.)/.match_at_byte_index("クリスタル", 3).try &.[2] # => "ス" # ``` - def match_at_byte_index(str, byte_index = 0, options = Regex::Options::None) : MatchData? + def match_at_byte_index(str : String, byte_index : Int32 = 0, options : Regex::MatchOptions = :none) : MatchData? if byte_index > str.bytesize $~ = nil else @@ -504,6 +548,22 @@ class Regex end end + # :ditto: + @[Deprecated("Use the overload with `Regex::MatchOptions` instead.")] + def match_at_byte_index(str, byte_index = 0, *, options) : MatchData? + if byte_index > str.bytesize + $~ = nil + else + $~ = match_impl(str, byte_index, options) + end + end + + # :ditto: + @[Deprecated("Use the overload with `Regex::MatchOptions` instead.")] + def match_at_byte_index(str, byte_index, _options) : MatchData? + match_at_byte_index(str, byte_index, options: _options) + end + # Match at character index. It behaves like `#match`, however it returns `Bool` value. # It neither returns `MatchData` nor assigns it to the `$~` variable. # @@ -514,7 +574,17 @@ class Regex # # `$~` is not set even if last match succeeds. # $~ # raises Exception # ``` - def matches?(str, pos = 0, options = Regex::Options::None) : Bool + def matches?(str : String, pos : Int32 = 0, options : Regex::MatchOptions = :none) : Bool + if byte_index = str.char_index_to_byte_index(pos) + matches_at_byte_index?(str, byte_index, options) + else + false + end + end + + # :ditto: + @[Deprecated("Use the overload with `Regex::MatchOptions` instead.")] + def matches?(str, pos = 0, *, options) : Bool if byte_index = str.char_index_to_byte_index(pos) matches_at_byte_index?(str, byte_index, options) else @@ -522,14 +592,34 @@ class Regex end end + # :ditto: + @[Deprecated("Use the overload with `Regex::MatchOptions` instead.")] + def matches?(str, pos, _options) : Bool + matches?(str, pos, options: _options) + end + # Match at byte index. It behaves like `#match_at_byte_index`, however it returns `Bool` value. # It neither returns `MatchData` nor assigns it to the `$~` variable. - def matches_at_byte_index?(str, byte_index = 0, options = Regex::Options::None) : Bool + def matches_at_byte_index?(str : String, byte_index : Int32 = 0, options : Regex::MatchOptions = :none) : Bool return false if byte_index > str.bytesize matches_impl(str, byte_index, options) end + # :ditto: + @[Deprecated("Use the overload with `Regex::MatchOptions` instead.")] + def matches_at_byte_index?(str, byte_index = 0, *, options) : Bool + return false if byte_index > str.bytesize + + matches_impl(str, byte_index, options) + end + + # :ditto: + @[Deprecated("Use the overload with `Regex::MatchOptions` instead.")] + def matches_at_byte_index?(str, byte_index, _options) : Bool + matches_at_byte_index?(str, byte_index, options: _options) + end + # Returns a `Hash` where the values are the names of capture groups and the # keys are their indexes. Non-named capture groups will not have entries in # the `Hash`. Capture groups are indexed starting from `1`. diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr index 7bcb9a409fad..7f1170b8e42b 100644 --- a/src/regex/pcre.cr +++ b/src/regex/pcre.cr @@ -32,7 +32,7 @@ module Regex::PCRE private def pcre_compile_options(options) flag = 0 - Regex::Options.each do |option| + Regex::CompileOptions.each do |option| if options.includes?(option) flag |= case option when .ignore_case? then LibPCRE::CASELESS @@ -47,7 +47,6 @@ module Regex::PCRE when .dupnames? then LibPCRE::DUPNAMES when .ucp? then LibPCRE::UCP when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") - when .no_jit? then raise ArgumentError.new("Invalid regex option NO_JIT for `pcre_compile`") else raise "unreachable" end @@ -78,7 +77,27 @@ module Regex::PCRE when .dupnames? then raise ArgumentError.new("Invalid regex option DUPNAMES for `pcre_exec`") when .ucp? then raise ArgumentError.new("Invalid regex option UCP for `pcre_exec`") when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") - when .no_jit? then raise ArgumentError.new("Regex::Option::NO_JIT is not supported with PCRE") + else + raise "unreachable" + end + options &= ~option + end + end + + # Unnamed values are explicitly used PCRE options, just pass them through: + flag |= options.value + + flag + end + + private def pcre_match_options(options : Regex::MatchOptions) + flag = 0 + Regex::MatchOptions.each do |option| + if options.includes?(option) + flag |= case option + when .anchored? then LibPCRE::ANCHORED + when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") + when .no_jit? then raise ArgumentError.new("Regex::Option::NO_JIT is not supported with PCRE") else raise "unreachable" end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 08cde2642a47..4bfa074f4289 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -65,7 +65,7 @@ module Regex::PCRE2 private def pcre2_compile_options(options) flag = 0 - Regex::Options.each do |option| + Regex::CompileOptions.each do |option| if options.includes?(option) flag |= case option when .ignore_case? then LibPCRE2::CASELESS @@ -80,7 +80,6 @@ module Regex::PCRE2 when .dupnames? then LibPCRE2::DUPNAMES when .ucp? then LibPCRE2::UCP when .endanchored? then LibPCRE2::ENDANCHORED - when .no_jit? then raise ArgumentError.new("Invalid regex option NO_JIT for `pcre2_compile`") else raise "unreachable" end @@ -110,7 +109,6 @@ module Regex::PCRE2 when .dupnames? then raise ArgumentError.new("Invalid regex option DUPNAMES for `pcre2_match`") when .ucp? then raise ArgumentError.new("Invalid regex option UCP for `pcre2_match`") when .endanchored? then LibPCRE2::ENDANCHORED - when .no_jit? then LibPCRE2::NO_JIT else raise "unreachable" end @@ -123,6 +121,26 @@ module Regex::PCRE2 flag end + private def pcre2_match_options(options : Regex::MatchOptions) + flag = 0 + Regex::MatchOptions.each do |option| + if options.includes?(option) + flag |= case option + when .anchored? then LibPCRE2::ANCHORED + when .endanchored? then LibPCRE2::ENDANCHORED + when .no_jit? then LibPCRE2::NO_JIT + else + raise "unreachable" + end + options &= ~option + end + end + unless options.none? + raise ArgumentError.new("Unknown Regex::MatchOption value: #{options}") + end + flag + end + def finalize {% unless flag?(:interpreted) %} LibPCRE2.code_free @re diff --git a/src/string.cr b/src/string.cr index 44a811c4ba91..7d446d2edd34 100644 --- a/src/string.cr +++ b/src/string.cr @@ -5012,7 +5012,7 @@ class String # "hh22".starts_with?(/[a-z]{2}/) # => true # ``` def starts_with?(re : Regex) : Bool - !!($~ = re.match_at_byte_index(self, 0, Regex::Options::ANCHORED)) + !!($~ = re.match_at_byte_index(self, 0, Regex::MatchOptions::ANCHORED)) end # Returns `true` if this string ends with the given *str*. diff --git a/src/string_scanner.cr b/src/string_scanner.cr index c679f8de46d7..c67631762fde 100644 --- a/src/string_scanner.cr +++ b/src/string_scanner.cr @@ -92,7 +92,7 @@ class StringScanner # s.scan(/.*/) # => "" # ``` def scan(pattern) : String? - match(pattern, advance: true, options: Regex::Options::ANCHORED) + match(pattern, advance: true, options: Regex::MatchOptions::ANCHORED) end # Scans the string _until_ the *pattern* is matched. Returns the substring up @@ -108,10 +108,10 @@ class StringScanner # s.scan_until(/g/) # => "ing" # ``` def scan_until(pattern) : String? - match(pattern, advance: true, options: Regex::Options::None) + match(pattern, advance: true, options: Regex::MatchOptions::None) end - private def match(pattern, advance = true, options = Regex::Options::ANCHORED) + private def match(pattern, advance = true, options = Regex::MatchOptions::ANCHORED) match = pattern.match_at_byte_index(@str, @byte_offset, options) @last_match = match if match @@ -167,7 +167,7 @@ class StringScanner # s.check(/\w+/) # => "is" # ``` def check(pattern) : String? - match(pattern, advance: false, options: Regex::Options::ANCHORED) + match(pattern, advance: false, options: Regex::MatchOptions::ANCHORED) end # Returns the value that `#scan_until` would return, without advancing the @@ -181,7 +181,7 @@ class StringScanner # s.check_until(/g/) # => "test string" # ``` def check_until(pattern) : String? - match(pattern, advance: false, options: Regex::Options::None) + match(pattern, advance: false, options: Regex::MatchOptions::None) end # Returns the *n*-th subgroup in the most recent match. From 251137c53dc2f0abcd65cbadd20a149e471a9bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 5 Apr 2023 15:16:37 +0200 Subject: [PATCH 0418/1551] Remove compile-time error for `Range#size`, `#each`, `#sample` (#13278) --- spec/std/range_spec.cr | 62 ++++++++++++++++++++++++++++++------------ src/range.cr | 28 ------------------- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/spec/std/range_spec.cr b/spec/std/range_spec.cr index c3307ddf3997..9b89f2cd4765 100644 --- a/spec/std/range_spec.cr +++ b/spec/std/range_spec.cr @@ -203,7 +203,7 @@ describe "Range" do end end - describe "each" do + describe "#each" do it "gives correct values with inclusive range" do range = -1..3 arr = [] of Int32 @@ -236,10 +236,14 @@ describe "Range" do end it "raises on beginless" do - range = (true ? nil : 1)..4 expect_raises(ArgumentError, "Can't each beginless range") do - range.each { } + (..4).each { } end + typeof((..4).each { |x| break x }).should eq Nil + expect_raises(ArgumentError, "Can't each beginless range") do + (nil.as(Int32?)..4).each { } + end + typeof((nil.as(Int32?)..4).each { |x| break x }).should eq Int32? end it "doesn't have Nil as a type for endless each" do @@ -251,7 +255,7 @@ describe "Range" do end end - describe "reverse_each" do + describe "#reverse_each" do it "gives correct values with inclusive range" do range = 'a'..'c' arr = [] of Char @@ -274,9 +278,11 @@ describe "Range" do end it "raises on endless range" do - range = (3..(true ? nil : 1)) expect_raises(ArgumentError, "Can't reverse_each endless range") do - range.reverse_each { } + (3..).reverse_each { } + end + expect_raises(ArgumentError, "Can't reverse_each endless range") do + (3..nil.as(Int32?)).reverse_each { } end end @@ -291,7 +297,7 @@ describe "Range" do end end - describe "each iterator" do + describe "#each iterator" do it "does next with inclusive range" do a = 1..3 iter = a.each @@ -317,9 +323,11 @@ describe "Range" do end it "raises on beginless range" do - r = (true ? nil : 1)..3 expect_raises(ArgumentError, "Can't each beginless range") do - r.each + (..3).each + end + expect_raises(ArgumentError, "Can't each beginless range") do + (nil.as(Int32?)..3).each end end @@ -344,7 +352,7 @@ describe "Range" do end end - describe "reverse_each iterator" do + describe "#reverse_each iterator" do it "does next with inclusive range" do a = 1..3 iter = a.reverse_each @@ -393,21 +401,33 @@ describe "Range" do it "raises on endless range" do expect_raises(ArgumentError, "Can't reverse_each endless range") do - (1..(true ? nil : 1)).reverse_each + (1..).reverse_each + end + expect_raises(ArgumentError, "Can't reverse_each endless range") do + (1..nil.as(Int32?)).reverse_each end end end - describe "sample" do + describe "#sample" do it "raises on open range" do expect_raises(ArgumentError, "Can't sample an open range") do - (1..(true ? nil : 1)).sample + (1..).sample + end + expect_raises(ArgumentError, "Can't sample an open range") do + (1..nil.as(Int32?)).sample + end + expect_raises(ArgumentError, "Can't sample an open range") do + (..1).sample + end + expect_raises(ArgumentError, "Can't sample an open range") do + (nil.as(Int32?)..1).sample end expect_raises(ArgumentError, "Can't sample an open range") do - ((true ? nil : 1)..1).sample + (..).sample end expect_raises(ArgumentError, "Can't sample an open range") do - ((true ? nil : 1)..(true ? nil : 1)).sample + (nil.as(Int32?)..nil.as(Int32?)).sample end end @@ -648,7 +668,7 @@ describe "Range" do end end - describe "size" do + describe "#size" do it "optimizes for int range" do (5..12).size.should eq(8) (5...12).size.should eq(7) @@ -661,13 +681,19 @@ describe "Range" do it "raises on beginless range" do expect_raises(ArgumentError, "Can't calculate size of an open range") do - ((true ? nil : 1)..3).size + (..3).size + end + expect_raises(ArgumentError, "Can't calculate size of an open range") do + (nil.as(Int32?)..3).size end end it "raises on endless range" do expect_raises(ArgumentError, "Can't calculate size of an open range") do - (3..(true ? nil : 1)).size + (3..).size + end + expect_raises(ArgumentError, "Can't calculate size of an open range") do + (3..nil.as(Int32?)).size end end end diff --git a/src/range.cr b/src/range.cr index d09b91380b3f..db1b3f32902a 100644 --- a/src/range.cr +++ b/src/range.cr @@ -109,10 +109,6 @@ struct Range(B, E) # # prints: 10 11 12 13 14 15 # ``` def each(&) : Nil - {% if B == Nil %} - {% raise "Can't each beginless range" %} - {% end %} - current = @begin if current.nil? raise ArgumentError.new("Can't each beginless range") @@ -142,10 +138,6 @@ struct Range(B, E) # (1..3).each.skip(1).to_a # => [2, 3] # ``` def each - {% if B == Nil %} - {% raise "Can't each beginless range" %} - {% end %} - if @begin.nil? raise ArgumentError.new("Can't each beginless range") end @@ -161,10 +153,6 @@ struct Range(B, E) # # prints: 14 13 12 11 10 # ``` def reverse_each(&) : Nil - {% if E == Nil %} - {% raise "Can't reverse_each endless range" %} - {% end %} - end_value = @end if end_value.nil? raise ArgumentError.new("Can't reverse_each endless range") @@ -196,10 +184,6 @@ struct Range(B, E) # (1..3).reverse_each.skip(1).to_a # => [2, 1] # ``` def reverse_each - {% if E == Nil %} - {% raise "Can't reverse_each endless range" %} - {% end %} - if @end.nil? raise ArgumentError.new("Can't reverse_each endless range") end @@ -363,10 +347,6 @@ struct Range(B, E) # # Raises `ArgumentError` if `self` is an open range. def sample(random : Random = Random::DEFAULT) - {% if B == Nil || E == Nil %} - {% raise "Can't sample an open range" %} - {% end %} - {% if B < Int && E < Int %} random.rand(self) {% elsif B < Float && E < Float %} @@ -391,10 +371,6 @@ struct Range(B, E) # once. Thus, *random* will be left in a different state compared to the # implementation in `Enumerable`. def sample(n : Int, random = Random::DEFAULT) - {% if B == Nil || E == Nil %} - {% raise "Can't sample an open range" %} - {% end %} - if self.begin.nil? || self.end.nil? raise ArgumentError.new("Can't sample an open range") end @@ -507,10 +483,6 @@ struct Range(B, E) # (3...8).size # => 5 # ``` def size - {% if B == Nil || E == Nil %} - {% raise "Can't calculate size of an open range" %} - {% end %} - b = self.begin e = self.end From 004c6ac10f39ae77e2af93b6174646db0af46708 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 5 Apr 2023 21:17:03 +0800 Subject: [PATCH 0419/1551] Fix `Array(T)#[]=(Int, Int, Array(T))` on shifted arrays (#13275) --- spec/std/array_spec.cr | 18 ++++++++++++++++++ src/array.cr | 18 ++++++++++-------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/spec/std/array_spec.cr b/spec/std/array_spec.cr index a5dd2e60f1fd..f465b49684ff 100644 --- a/spec/std/array_spec.cr +++ b/spec/std/array_spec.cr @@ -445,6 +445,24 @@ describe "Array" do a[3..] = [4, 5, 6] a.should eq([1, 2, 3, 4, 5, 6]) end + + it "reuses the buffer if possible" do + a = [1, 2, 3, 4, 5] + a.pop + a[4, 0] = [6] + a.should eq([1, 2, 3, 4, 6]) + a.@capacity.should eq(5) + a.@offset_to_buffer.should eq(0) + end + + it "resizes the buffer if capacity is not enough" do + a = [1, 2, 3, 4, 5] + a.shift + a[4, 0] = [6, 7, 8, 9] + a.should eq([2, 3, 4, 5, 6, 7, 8, 9]) + a.@capacity.should eq(10) + a.@offset_to_buffer.should eq(1) + end end describe "values_at" do diff --git a/src/array.cr b/src/array.cr index cd8341fd63f3..71dc841de1fe 100644 --- a/src/array.cr +++ b/src/array.cr @@ -531,7 +531,7 @@ class Array(T) @size -= diff else # Need to grow - resize_to_capacity(Math.pw2ceil(@size + diff)) + resize_if_cant_insert(diff) (@buffer + start + values.size).move_from(@buffer + start + count, size - start - count) (@buffer + start).copy_from(values.to_unsafe, values.size) @size += diff @@ -2071,6 +2071,10 @@ class Array(T) end private def calculate_new_capacity(new_size) + # Resizing is done via `Pointer#realloc` on the root buffer, so the space + # between the root and real buffers remains untouched + new_size += @offset_to_buffer + new_capacity = @capacity == 0 ? INITIAL_CAPACITY : @capacity while new_capacity < new_size if new_capacity < CAPACITY_THRESHOLD @@ -2118,13 +2122,11 @@ class Array(T) end private def resize_if_cant_insert(insert_size) - # Resize if we exceed the remaining capacity. - # `remaining_capacity - @size` is the actual number of slots we have - # to push new elements. - if insert_size > remaining_capacity - @size - # The new capacity that we need is what we already have occupied - # because of shift (`@offset_to_buffer`) plus my size plus the insert size. - resize_to_capacity(Math.pw2ceil(@offset_to_buffer + @size + insert_size)) + # Resize if we exceed the remaining capacity. This is less than `@capacity` + # if the array has been shifted and `@offset_to_buffer` is nonzero + new_size = @size + insert_size + if new_size > remaining_capacity + resize_to_capacity(calculate_new_capacity(new_size)) end end From 12a75027f16214723a5fdeb86d8ab2fef26c7a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 5 Apr 2023 18:19:26 +0200 Subject: [PATCH 0420/1551] Fix `bin/crystal` in symlink working directory (#13281) --- bin/crystal | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/crystal b/bin/crystal index b6bd2e64ba0b..f759e8796dc4 100755 --- a/bin/crystal +++ b/bin/crystal @@ -173,6 +173,11 @@ if [ -z "$CRYSTAL_CONFIG_LIBRARY_PATH" ] || [ -z "$CRYSTAL_LIBRARY_PATH" ]; then export CRYSTAL_CONFIG_LIBRARY_PATH=${CRYSTAL_CONFIG_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} fi +# CRYSTAL_PATH has all symlinks resolved. In order to avoid issues with duplicate file +# paths when the working directory is a symlink, we cd into the current directory +# with symlinks resolved as well (see https://github.com/crystal-lang/crystal/issues/12969). +cd "$(realpath "$(pwd)")" + if [ -x "$CRYSTAL_DIR/crystal" ]; then __warning_msg "Using compiled compiler at ${CRYSTAL_DIR#"$PWD/"}/crystal" exec "$CRYSTAL_DIR/crystal" "$@" From ff80d18bf0cffc0ee06ad06bc0247e653e596621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 5 Apr 2023 18:19:44 +0200 Subject: [PATCH 0421/1551] Fix `HTTP::Server::Response#reset` for `status_message` (#13282) --- spec/std/http/server/response_spec.cr | 4 ++++ src/http/server/response.cr | 1 + 2 files changed, 5 insertions(+) diff --git a/spec/std/http/server/response_spec.cr b/spec/std/http/server/response_spec.cr index 0322c1a00f51..517cf1986f29 100644 --- a/spec/std/http/server/response_spec.cr +++ b/spec/std/http/server/response_spec.cr @@ -264,9 +264,13 @@ describe HTTP::Server::Response do response = Response.new(io) response.headers["Foo"] = "Bar" response.cookies["Bar"] = "Foo" + response.status = HTTP::Status::USE_PROXY + response.status_message = "Baz" response.reset response.headers.should be_empty response.cookies.should be_empty + response.status.should eq HTTP::Status::OK + response.status_message.should eq "OK" end it "writes cookie headers" do diff --git a/src/http/server/response.cr b/src/http/server/response.cr index de3312a81e57..573fa0bc28a9 100644 --- a/src/http/server/response.cr +++ b/src/http/server/response.cr @@ -61,6 +61,7 @@ class HTTP::Server @headers.clear @cookies = nil @status = :ok + @status_message = nil @wrote_headers = false @output = @original_output @original_output.reset From 47537876c2eafce018a2d04247439df9927ce272 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 6 Apr 2023 03:59:39 +0800 Subject: [PATCH 0422/1551] Docs: Require all `Indexable`s to be stable (#13061) --- src/indexable.cr | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/indexable.cr b/src/indexable.cr index ce493eec2001..4c69ee3015f4 100644 --- a/src/indexable.cr +++ b/src/indexable.cr @@ -5,6 +5,22 @@ # `-2` is the next to last element, and so on. # # Types including this module are typically `Array`-like types. +# +# ## Stability guarantees +# +# Several methods in `Indexable`, such as `#bsearch` and `#cartesian_product`, +# require the collection to be _stable_; that is, calling `#each(&)` over and +# over again should always yield the same elements, provided the collection is +# not mutated between the calls. In particular, `#each(&)` itself should not +# mutate the collection throughout the loop. Stability of an `Indexable` is +# guaranteed if the following criteria are met: +# +# * `#unsafe_fetch` and `#size` do not mutate the collection +# * `#each(&)` and `#each_index(&)` are not overridden +# +# The standard library assumes that all including types of `Indexable` are +# always stable. It is undefined behavior to implement an `Indexable` that is +# not stable or only conditionally stable. module Indexable(T) include Iterable(T) include Enumerable(T) From 7982ff6533fd5f6cf5eda8a0be49987ef6775359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 5 Apr 2023 21:59:51 +0200 Subject: [PATCH 0423/1551] Makefile: Add `all` target as default before including `Makfile.local` (#13276) --- Makefile | 2 ++ Makefile.win | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 789443fe902e..7a34d8836097 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +all: + -include Makefile.local # for optional local options e.g. threads # Recipes for this Makefile diff --git a/Makefile.win b/Makefile.win index 6496eb009fcb..5c8c39fe215e 100644 --- a/Makefile.win +++ b/Makefile.win @@ -1,3 +1,5 @@ +all: + -include Makefile.win.local # for optional local options e.g. threads # Recipes for this Makefile From 7d0778602a869ec3397e3f74cf4c29e5a1515500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 6 Apr 2023 16:28:59 +0200 Subject: [PATCH 0424/1551] Fix interpreter `value_to_bool` for `NoReturn` (#13290) --- src/compiler/crystal/interpreter/compiler.cr | 2 ++ src/compiler/crystal/interpreter/to_bool.cr | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index b168342c9c21..a6bed4c4b38f 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -1550,6 +1550,8 @@ class Crystal::Repl::Compiler < Crystal::Visitor end def visit(node : Not) + node.type = @context.program.no_return unless node.type? + exp = node.exp exp.accept self return false unless @wants_value diff --git a/src/compiler/crystal/interpreter/to_bool.cr b/src/compiler/crystal/interpreter/to_bool.cr index 56c2f5f459d1..5d93d30c52b6 100644 --- a/src/compiler/crystal/interpreter/to_bool.cr +++ b/src/compiler/crystal/interpreter/to_bool.cr @@ -43,6 +43,10 @@ class Crystal::Repl::Compiler value_to_bool node, type.typedef end + private def value_to_bool(node : ASTNode, type : NoReturnType) + # Nothing to do + end + private def value_to_bool(node : ASTNode, type : Type) node.raise "BUG: missing value_to_bool for #{type} (#{type.class})" end From 4d3385af97005398474b4c627d6e052e848a1c82 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 6 Apr 2023 22:30:03 +0800 Subject: [PATCH 0425/1551] Windows: make `File.delete` remove symlink directories, not `Dir.delete` (#13224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/dir_spec.cr | 67 +++++++++++++++++--------------- spec/std/file_spec.cr | 8 ++++ spec/std/file_utils_spec.cr | 2 +- src/crystal/system/win32/dir.cr | 22 ++++++++--- src/crystal/system/win32/file.cr | 22 +++++++---- src/dir.cr | 14 +++++-- src/file.cr | 12 ++++-- 7 files changed, 96 insertions(+), 51 deletions(-) diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index d7bfa6cdb19f..4fc2cf95d135 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -143,17 +143,29 @@ describe "Dir" do end end - it "tests delete with an nonexistent path" do - with_tempfile("nonexistent") do |path| - expect_raises(File::NotFoundError, "Unable to remove directory: '#{path.inspect_unquoted}'") do - Dir.delete(path) + describe ".delete" do + it "raises with an nonexistent path" do + with_tempfile("nonexistent") do |path| + expect_raises(File::NotFoundError, "Unable to remove directory: '#{path.inspect_unquoted}'") do + Dir.delete(path) + end + end + end + + it "raises with a path that cannot be removed" do + expect_raises(File::Error, "Unable to remove directory: '#{datapath.inspect_unquoted}'") do + Dir.delete(datapath) end end - end - it "tests delete with a path that cannot be removed" do - expect_raises(File::Error, "Unable to remove directory: '#{datapath.inspect_unquoted}'") do - Dir.delete(datapath) + it "raises with symlink directory" do + with_tempfile("delete-target-directory", "delete-symlink-directory") do |target_path, symlink_path| + Dir.mkdir(target_path) + File.symlink(target_path, symlink_path) + expect_raises(File::Error) do + Dir.delete(symlink_path) + end + end end end @@ -351,21 +363,20 @@ describe "Dir" do end it "matches symlinks" do - link = datapath("f1_link.txt") - non_link = datapath("non_link.txt") + with_tempfile "symlinks" do |path| + Dir.mkdir_p(path) - File.symlink(datapath("dir", "f1.txt"), link) - File.symlink(datapath("dir", "nonexisting"), non_link) + link = Path[path, "f1_link.txt"] + non_link = Path[path, "non_link.txt"] - begin - Dir["#{datapath}/*_link.txt"].sort.should eq [ - datapath("f1_link.txt"), - datapath("non_link.txt"), + File.symlink(datapath("dir", "f1.txt"), link) + File.symlink(datapath("dir", "nonexisting"), non_link) + + Dir["#{path}/*_link.txt"].sort.should eq [ + link.to_s, + non_link.to_s, ].sort - Dir["#{datapath}/non_link.txt"].should eq [datapath("non_link.txt")] - ensure - File.delete link - File.delete non_link + Dir["#{path}/non_link.txt"].should eq [non_link.to_s] end end @@ -381,18 +392,10 @@ describe "Dir" do File.write(non_link, "") File.symlink(target, link_dir) - begin - Dir.glob("#{path}/glob/*/a.txt").sort.should eq [] of String - Dir.glob("#{path}/glob/*/a.txt", follow_symlinks: true).sort.should eq [ - File.join(path, "glob", "dir", "a.txt"), - ] - ensure - # FIXME: `with_tempfile` will delete this symlink directory using - # `File.delete` otherwise, see #13194 - {% if flag?(:win32) %} - Dir.delete(link_dir) - {% end %} - end + Dir.glob("#{path}/glob/*/a.txt").sort.should eq [] of String + Dir.glob("#{path}/glob/*/a.txt", follow_symlinks: true).sort.should eq [ + File.join(path, "glob", "dir", "a.txt"), + ] end end diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index f8dad66543cc..7edabb6a390d 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -551,6 +551,14 @@ describe "File" do end end end + + it "deletes a symlink directory" do + with_tempfile("delete-target-directory", "delete-symlink-directory") do |target_path, symlink_path| + Dir.mkdir(target_path) + File.symlink(target_path, symlink_path) + File.delete(symlink_path) + end + end end describe "rename" do diff --git a/spec/std/file_utils_spec.cr b/spec/std/file_utils_spec.cr index 0d536fc7a97a..d7af1f6c3287 100644 --- a/spec/std/file_utils_spec.cr +++ b/spec/std/file_utils_spec.cr @@ -239,7 +239,7 @@ describe "FileUtils" do end end - pending_win32 "doesn't follow symlinks" do + it "doesn't follow symlinks" do with_tempfile("rm_r-removed", "rm_r-linked") do |removed_path, linked_path| link_path = File.join(removed_path, "link") file_path = File.join(linked_path, "file") diff --git a/src/crystal/system/win32/dir.cr b/src/crystal/system/win32/dir.cr index fb35e9dafdd0..661fc39ae08c 100644 --- a/src/crystal/system/win32/dir.cr +++ b/src/crystal/system/win32/dir.cr @@ -132,12 +132,24 @@ module Crystal::System::Dir end def self.delete(path : String, *, raise_on_missing : Bool) : Bool - return true if LibC._wrmdir(System.to_wstr(path)) == 0 + win_path = System.to_wstr(path) - if !raise_on_missing && Errno.value == Errno::ENOENT - false - else - raise ::File::Error.from_errno("Unable to remove directory", file: path) + attributes = LibC.GetFileAttributesW(win_path) + if attributes == LibC::INVALID_FILE_ATTRIBUTES + File.check_not_found_error("Unable to remove directory", path) + raise ::File::Error.from_os_error("Unable to remove directory", Errno::ENOENT, file: path) if raise_on_missing + return false + end + + # all reparse point directories should be deleted like a directory, not just + # symbolic links, so we don't care about the reparse tag here + if attributes.bits_set?(LibC::FILE_ATTRIBUTE_REPARSE_POINT) && attributes.bits_set?(LibC::FILE_ATTRIBUTE_DIRECTORY) + # maintain consistency with POSIX, and treat all reparse points (including + # symbolic links) as non-directories + raise ::File::Error.new("Cannot remove directory that is a reparse point: '#{path.inspect_unquoted}'", file: path) end + + return true if LibC._wrmdir(win_path) == 0 + raise ::File::Error.from_errno("Unable to remove directory", file: path) end end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index c9a399b7ae0f..145ac11f7aa3 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -114,7 +114,7 @@ module Crystal::System::File WinError::ERROR_INVALID_NAME, } - private def self.check_not_found_error(message, path) + def self.check_not_found_error(message, path) error = WinError.value if NOT_FOUND_ERRORS.includes? error nil @@ -242,13 +242,21 @@ module Crystal::System::File end def self.delete(path : String, *, raise_on_missing : Bool) : Bool - if LibC._wunlink(System.to_wstr(path)) == 0 - true - elsif !raise_on_missing && Errno.value == Errno::ENOENT - false - else - raise ::File::Error.from_errno("Error deleting file", file: path) + win_path = System.to_wstr(path) + + attributes = LibC.GetFileAttributesW(win_path) + if attributes == LibC::INVALID_FILE_ATTRIBUTES + check_not_found_error("Error deleting file", path) + raise ::File::Error.from_os_error("Error deleting file", Errno::ENOENT, file: path) if raise_on_missing + return false end + + # all reparse point directories should be deleted like a directory, not just + # symbolic links, so we don't care about the reparse tag here + is_reparse_dir = attributes.bits_set?(LibC::FILE_ATTRIBUTE_REPARSE_POINT) && attributes.bits_set?(LibC::FILE_ATTRIBUTE_DIRECTORY) + result = is_reparse_dir ? LibC._wrmdir(win_path) : LibC._wunlink(win_path) + return true if result == 0 + raise ::File::Error.from_errno("Error deleting file", file: path) end private REALPATH_SYMLINK_LIMIT = 100 diff --git a/src/dir.cr b/src/dir.cr index e69b147baf9d..2b6bfd13c7a9 100644 --- a/src/dir.cr +++ b/src/dir.cr @@ -292,13 +292,21 @@ class Dir mkdir(path, mode) unless Dir.exists?(path) end - # Removes the directory at the given path. + # Removes the directory at *path*. Raises `File::Error` on failure. + # + # On Windows, also raises `File::Error` if *path* points to a directory that + # is a reparse point, such as a symbolic link. Those directories can be + # deleted using `File.delete` instead. def self.delete(path : Path | String) : Nil Crystal::System::Dir.delete(path.to_s, raise_on_missing: true) end - # Removes the directory at the given path. - # Returns `false` if the directory does not exist. + # Removes the directory at *path*, or returns `false` if the directory does + # not exist. Raises `File::Error` on other kinds of failure. + # + # On Windows, also raises `File::Error` if *path* points to a directory that + # is a reparse point, such as a symbolic link. Those directories can be + # deleted using `File.delete?` instead. def self.delete?(path : Path | String) : Bool Crystal::System::Dir.delete(path.to_s, raise_on_missing: false) end diff --git a/src/file.cr b/src/file.cr index 070ccfc0445f..6875f51397b2 100644 --- a/src/file.cr +++ b/src/file.cr @@ -360,7 +360,10 @@ class File < IO::FileDescriptor Crystal::System::File.chmod(path.to_s, permissions) end - # Deletes the file at *path*. Deleting non-existent file will raise an exception. + # Deletes the file at *path*. Raises `File::Error` on failure. + # + # On Windows, this also deletes reparse points, including symbolic links, + # regardless of whether the reparse point is a directory. # # ``` # File.write("foo", "") @@ -371,8 +374,11 @@ class File < IO::FileDescriptor Crystal::System::File.delete(path.to_s, raise_on_missing: true) end - # Deletes the file at *path*. - # Returns `false` if the file does not exist. + # Deletes the file at *path*, or returns `false` if the file does not exist. + # Raises `File::Error` on other kinds of failure. + # + # On Windows, this also deletes reparse points, including symbolic links, + # regardless of whether the reparse point is a directory. # # ``` # File.write("foo", "") From 7eccd73a31825f1ecac7e9e654899412f547c603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 6 Apr 2023 16:30:21 +0200 Subject: [PATCH 0426/1551] Fix `bin/crystal` when no global `crystal` command is installed (#13286) --- bin/crystal | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/bin/crystal b/bin/crystal index f759e8796dc4..8e42d4e7ed86 100755 --- a/bin/crystal +++ b/bin/crystal @@ -153,8 +153,11 @@ if [ -z "${PARENT_CRYSTAL##*/*}" -a "$(realpath "$PARENT_CRYSTAL")" = "$SCRIPT_P PARENT_CRYSTAL="crystal" fi +PARENT_CRYSTAL_COMMAND=$(realpath "$(command -v "$PARENT_CRYSTAL" 2> /dev/null)" 2> /dev/null) +PARENT_CRYSTAL_EXISTS=$(test !$?) + # check if the parent crystal command refers to this script -if [ "$(realpath "$(command -v "$PARENT_CRYSTAL")")" = "$SCRIPT_PATH" ]; then +if [ "$PARENT_CRYSTAL_COMMAND" = "$SCRIPT_PATH" ]; then # remove the path to this script from PATH NEW_PATH="$(remove_path_item "$(remove_path_item "$PATH" "$SCRIPT_ROOT")" "bin")" # if the PATH did not change it will lead to an infinite recursion => display error @@ -162,15 +165,20 @@ if [ "$(realpath "$(command -v "$PARENT_CRYSTAL")")" = "$SCRIPT_PATH" ]; then __error_msg 'Could not remove the script bin/crystal from the PATH. Remove it by hand or set the CRYSTAL env variable' exit 1 fi - # re-execute this script with the cleaned up PATH - export PATH="$NEW_PATH" - exec "$SCRIPT_PATH" "$@" + PARENT_CRYSTAL_COMMAND=$(realpath "$(PATH=$NEW_PATH command -v "$PARENT_CRYSTAL" 2> /dev/null)" 2> /dev/null) + PARENT_CRYSTAL_EXISTS=$(test !$?) + if [ "$PARENT_CRYSTAL_COMMAND" = "$SCRIPT_PATH" ]; then + __error_msg 'Could not remove the script bin/crystal from the PATH. Remove it by hand or set the CRYSTAL env variable' + exit 1 + fi fi -if [ -z "$CRYSTAL_CONFIG_LIBRARY_PATH" ] || [ -z "$CRYSTAL_LIBRARY_PATH" ]; then - CRYSTAL_INSTALLED_LIBRARY_PATH="$($PARENT_CRYSTAL env CRYSTAL_LIBRARY_PATH || echo "")" - export CRYSTAL_LIBRARY_PATH=${CRYSTAL_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} - export CRYSTAL_CONFIG_LIBRARY_PATH=${CRYSTAL_CONFIG_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} +if ($PARENT_CRYSTAL_EXISTS); then + if [ -z "$CRYSTAL_CONFIG_LIBRARY_PATH" ] || [ -z "$CRYSTAL_LIBRARY_PATH" ]; then + CRYSTAL_INSTALLED_LIBRARY_PATH="$($PARENT_CRYSTAL_COMMAND env CRYSTAL_LIBRARY_PATH 2> /dev/null || echo "")" + export CRYSTAL_LIBRARY_PATH=${CRYSTAL_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} + export CRYSTAL_CONFIG_LIBRARY_PATH=${CRYSTAL_CONFIG_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} + fi fi # CRYSTAL_PATH has all symlinks resolved. In order to avoid issues with duplicate file @@ -181,9 +189,9 @@ cd "$(realpath "$(pwd)")" if [ -x "$CRYSTAL_DIR/crystal" ]; then __warning_msg "Using compiled compiler at ${CRYSTAL_DIR#"$PWD/"}/crystal" exec "$CRYSTAL_DIR/crystal" "$@" -elif ! command -v "$PARENT_CRYSTAL" > /dev/null; then +elif !($PARENT_CRYSTAL_EXISTS); then __error_msg 'You need to have a crystal executable in your path! or set CRYSTAL env variable' exit 1 else - exec "$PARENT_CRYSTAL" "$@" + exec "$PARENT_CRYSTAL_COMMAND" "$@" fi From 769c6f59dc873ba8865a5d0921d521dc06ebf274 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 9 Apr 2023 18:37:53 +0800 Subject: [PATCH 0427/1551] Fix some Linux glibc bindings (#13249) --- src/lib_c/aarch64-linux-gnu/c/dirent.cr | 4 ++-- src/lib_c/aarch64-linux-gnu/c/link.cr | 4 ++++ src/lib_c/aarch64-linux-gnu/c/signal.cr | 4 ++-- src/lib_c/aarch64-linux-gnu/c/stddef.cr | 2 +- src/lib_c/aarch64-linux-gnu/c/sys/resource.cr | 2 +- src/lib_c/aarch64-linux-gnu/c/sys/socket.cr | 2 +- src/lib_c/aarch64-linux-gnu/c/sys/types.cr | 17 ++--------------- src/lib_c/i386-linux-gnu/c/dirent.cr | 2 +- src/lib_c/i386-linux-gnu/c/link.cr | 4 ++++ src/lib_c/i386-linux-gnu/c/signal.cr | 6 +++--- src/lib_c/i386-linux-gnu/c/sys/resource.cr | 2 +- src/lib_c/i386-linux-gnu/c/sys/socket.cr | 2 +- src/lib_c/i386-linux-gnu/c/sys/stat.cr | 6 +++--- src/lib_c/i386-linux-gnu/c/sys/types.cr | 17 ++--------------- src/lib_c/x86_64-linux-gnu/c/dirent.cr | 4 ++-- src/lib_c/x86_64-linux-gnu/c/link.cr | 4 ++++ src/lib_c/x86_64-linux-gnu/c/signal.cr | 4 ++-- src/lib_c/x86_64-linux-gnu/c/sys/resource.cr | 2 +- src/lib_c/x86_64-linux-gnu/c/sys/socket.cr | 2 +- src/lib_c/x86_64-linux-gnu/c/sys/types.cr | 13 ------------- 20 files changed, 38 insertions(+), 65 deletions(-) diff --git a/src/lib_c/aarch64-linux-gnu/c/dirent.cr b/src/lib_c/aarch64-linux-gnu/c/dirent.cr index 5a413f754815..778485d8b4bc 100644 --- a/src/lib_c/aarch64-linux-gnu/c/dirent.cr +++ b/src/lib_c/aarch64-linux-gnu/c/dirent.cr @@ -9,9 +9,9 @@ lib LibC struct Dirent d_ino : InoT - d_off : Long + d_off : OffT d_reclen : UShort - d_type : Char + d_type : UChar d_name : StaticArray(Char, 256) end diff --git a/src/lib_c/aarch64-linux-gnu/c/link.cr b/src/lib_c/aarch64-linux-gnu/c/link.cr index a29bd3030a42..5f930116e936 100644 --- a/src/lib_c/aarch64-linux-gnu/c/link.cr +++ b/src/lib_c/aarch64-linux-gnu/c/link.cr @@ -6,6 +6,10 @@ lib LibC name : Char* phdr : Elf_Phdr* phnum : Elf_Half + adds : ULongLong + subs : ULongLong + tls_modid : SizeT + tls_data : Void* end alias DlPhdrCallback = (DlPhdrInfo*, LibC::SizeT, Void*) -> LibC::Int diff --git a/src/lib_c/aarch64-linux-gnu/c/signal.cr b/src/lib_c/aarch64-linux-gnu/c/signal.cr index c9d22e360cb7..d7c2a8790d2a 100644 --- a/src/lib_c/aarch64-linux-gnu/c/signal.cr +++ b/src/lib_c/aarch64-linux-gnu/c/signal.cr @@ -57,7 +57,7 @@ lib LibC si_code : Int __pad0 : Int si_addr : Void* # Assuming the sigfault form of siginfo_t - __pad1 : StaticArray(Int, 20) # __SI_PAD_SIZE (28) - sizeof(void*) (8) = 20 + __pad1 : StaticArray(Int, 26) # __SI_PAD_SIZE (28) - sizeof(void*) / sizeof(int) = 26 end alias SigactionHandlerT = (Int, SiginfoT*, Void*) -> @@ -66,7 +66,7 @@ lib LibC sa_sigaction : SigactionHandlerT sa_mask : SigsetT sa_flags : Int - sa_restorer : Void* + sa_restorer : -> end struct StackT diff --git a/src/lib_c/aarch64-linux-gnu/c/stddef.cr b/src/lib_c/aarch64-linux-gnu/c/stddef.cr index 4169a0d2ad0e..4afcdf34d723 100644 --- a/src/lib_c/aarch64-linux-gnu/c/stddef.cr +++ b/src/lib_c/aarch64-linux-gnu/c/stddef.cr @@ -1,3 +1,3 @@ lib LibC - alias SizeT = ULongLong + alias SizeT = ULong end diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr b/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr index 7f550c37a622..a0900a4730c4 100644 --- a/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr +++ b/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr @@ -21,5 +21,5 @@ lib LibC RUSAGE_SELF = 0 RUSAGE_CHILDREN = -1 - fun getrusage(who : Int, usage : RUsage*) : Int16 + fun getrusage(who : Int, usage : RUsage*) : Int end diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr b/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr index 11785c128f52..82e48d78c9f2 100644 --- a/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr +++ b/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr @@ -38,8 +38,8 @@ lib LibC struct SockaddrStorage ss_family : SaFamilyT + __ss_padding : StaticArray(Char, 118) __ss_align : ULong - __ss_padding : StaticArray(Char, 112) end struct Linger diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/types.cr b/src/lib_c/aarch64-linux-gnu/c/sys/types.cr index 0dcd396eb3a9..bef2498d1494 100644 --- a/src/lib_c/aarch64-linux-gnu/c/sys/types.cr +++ b/src/lib_c/aarch64-linux-gnu/c/sys/types.cr @@ -20,21 +20,9 @@ lib LibC __align : Long end - struct PthreadCondTData - __lock : Int - __futex : UInt - __total_seq : ULongLong - __wakeup_seq : ULongLong - __woken_seq : ULongLong - __mutex : Void* - __nwaiters : UInt - __broadcast_seq : UInt - end - union PthreadCondT - __data : PthreadCondTData __size : StaticArray(Char, 48) - __align : Long + __align : LongLong end union PthreadCondattrT @@ -43,14 +31,13 @@ lib LibC end union PthreadMutexT - __data : Void* __size : StaticArray(Char, 48) __align : Long end union PthreadMutexattrT __size : StaticArray(Char, 8) - __align : Long + __align : Int end alias PthreadT = ULong diff --git a/src/lib_c/i386-linux-gnu/c/dirent.cr b/src/lib_c/i386-linux-gnu/c/dirent.cr index 2d456dfe8bc1..ff905a04d099 100644 --- a/src/lib_c/i386-linux-gnu/c/dirent.cr +++ b/src/lib_c/i386-linux-gnu/c/dirent.cr @@ -11,7 +11,7 @@ lib LibC d_ino : InoT d_off : OffT d_reclen : UShort - d_type : Char + d_type : UChar d_name : StaticArray(Char, 256) end diff --git a/src/lib_c/i386-linux-gnu/c/link.cr b/src/lib_c/i386-linux-gnu/c/link.cr index a29bd3030a42..5f930116e936 100644 --- a/src/lib_c/i386-linux-gnu/c/link.cr +++ b/src/lib_c/i386-linux-gnu/c/link.cr @@ -6,6 +6,10 @@ lib LibC name : Char* phdr : Elf_Phdr* phnum : Elf_Half + adds : ULongLong + subs : ULongLong + tls_modid : SizeT + tls_data : Void* end alias DlPhdrCallback = (DlPhdrInfo*, LibC::SizeT, Void*) -> LibC::Int diff --git a/src/lib_c/i386-linux-gnu/c/signal.cr b/src/lib_c/i386-linux-gnu/c/signal.cr index 22e4fdd561b9..2a2219968e04 100644 --- a/src/lib_c/i386-linux-gnu/c/signal.cr +++ b/src/lib_c/i386-linux-gnu/c/signal.cr @@ -45,7 +45,7 @@ lib LibC SIG_IGN = SighandlerT.new(Pointer(Void).new(1_u64), Pointer(Void).null) struct SigsetT - val : ULong[32] # (1024 / (8 * sizeof(long))) + val : ULong[32] # (1024 / (8 * sizeof(ULong))) end SA_ONSTACK = 0x08000000 @@ -56,7 +56,7 @@ lib LibC si_errno : Int si_code : Int si_addr : Void* # Assuming the sigfault form of siginfo_t - __pad : StaticArray(Int, 25) # __SI_PAD_SIZE (29) - sizeof(void*) (4) = 25 + __pad : StaticArray(Int, 27) # __SI_PAD_SIZE (29) - sizeof(void*) (4) = 25 end alias SigactionHandlerT = (Int, SiginfoT*, Void*) -> @@ -65,7 +65,7 @@ lib LibC sa_sigaction : SigactionHandlerT sa_mask : SigsetT sa_flags : Int - sa_restorer : Void* + sa_restorer : -> end struct StackT diff --git a/src/lib_c/i386-linux-gnu/c/sys/resource.cr b/src/lib_c/i386-linux-gnu/c/sys/resource.cr index 7f550c37a622..a0900a4730c4 100644 --- a/src/lib_c/i386-linux-gnu/c/sys/resource.cr +++ b/src/lib_c/i386-linux-gnu/c/sys/resource.cr @@ -21,5 +21,5 @@ lib LibC RUSAGE_SELF = 0 RUSAGE_CHILDREN = -1 - fun getrusage(who : Int, usage : RUsage*) : Int16 + fun getrusage(who : Int, usage : RUsage*) : Int end diff --git a/src/lib_c/i386-linux-gnu/c/sys/socket.cr b/src/lib_c/i386-linux-gnu/c/sys/socket.cr index 3a61519d9baa..6651736a41c0 100644 --- a/src/lib_c/i386-linux-gnu/c/sys/socket.cr +++ b/src/lib_c/i386-linux-gnu/c/sys/socket.cr @@ -38,8 +38,8 @@ lib LibC struct SockaddrStorage ss_family : SaFamilyT + __ss_padding : StaticArray(Char, 122) __ss_align : ULong - __ss_padding : StaticArray(Char, 120) end struct Linger diff --git a/src/lib_c/i386-linux-gnu/c/sys/stat.cr b/src/lib_c/i386-linux-gnu/c/sys/stat.cr index 7facced563ea..7a6dca15c3ba 100644 --- a/src/lib_c/i386-linux-gnu/c/sys/stat.cr +++ b/src/lib_c/i386-linux-gnu/c/sys/stat.cr @@ -26,16 +26,16 @@ lib LibC S_ISGID = 0o2000 S_ISVTX = 0o1000 - struct Stat + struct Stat # stat64 st_dev : DevT - __pad1 : UShort + __pad1 : UInt __st_ino : ULong st_mode : ModeT st_nlink : NlinkT st_uid : UidT st_gid : GidT st_rdev : DevT - __pad2 : UShort + __pad2 : UInt st_size : OffT st_blksize : BlksizeT st_blocks : BlkcntT diff --git a/src/lib_c/i386-linux-gnu/c/sys/types.cr b/src/lib_c/i386-linux-gnu/c/sys/types.cr index f89d29723e65..a1d00215d55d 100644 --- a/src/lib_c/i386-linux-gnu/c/sys/types.cr +++ b/src/lib_c/i386-linux-gnu/c/sys/types.cr @@ -20,37 +20,24 @@ lib LibC __align : Long end - struct PthreadCondTData - __lock : Int - __futex : UInt - __total_seq : ULongLong - __wakeup_seq : ULongLong - __woken_seq : ULongLong - __mutex : Void* - __nwaiters : UInt - __broadcast_seq : UInt - end - union PthreadCondT - __data : PthreadCondTData __size : StaticArray(Char, 48) __align : LongLong end union PthreadCondattrT __size : StaticArray(Char, 4) - __align : Long + __align : Int end union PthreadMutexT - __data : Void* __size : StaticArray(Char, 24) __align : Long end union PthreadMutexattrT __size : StaticArray(Char, 4) - __align : Long + __align : Int end alias PthreadT = ULong diff --git a/src/lib_c/x86_64-linux-gnu/c/dirent.cr b/src/lib_c/x86_64-linux-gnu/c/dirent.cr index 5a413f754815..778485d8b4bc 100644 --- a/src/lib_c/x86_64-linux-gnu/c/dirent.cr +++ b/src/lib_c/x86_64-linux-gnu/c/dirent.cr @@ -9,9 +9,9 @@ lib LibC struct Dirent d_ino : InoT - d_off : Long + d_off : OffT d_reclen : UShort - d_type : Char + d_type : UChar d_name : StaticArray(Char, 256) end diff --git a/src/lib_c/x86_64-linux-gnu/c/link.cr b/src/lib_c/x86_64-linux-gnu/c/link.cr index a29bd3030a42..5f930116e936 100644 --- a/src/lib_c/x86_64-linux-gnu/c/link.cr +++ b/src/lib_c/x86_64-linux-gnu/c/link.cr @@ -6,6 +6,10 @@ lib LibC name : Char* phdr : Elf_Phdr* phnum : Elf_Half + adds : ULongLong + subs : ULongLong + tls_modid : SizeT + tls_data : Void* end alias DlPhdrCallback = (DlPhdrInfo*, LibC::SizeT, Void*) -> LibC::Int diff --git a/src/lib_c/x86_64-linux-gnu/c/signal.cr b/src/lib_c/x86_64-linux-gnu/c/signal.cr index 0556b55ae3e1..46b6dba5cc62 100644 --- a/src/lib_c/x86_64-linux-gnu/c/signal.cr +++ b/src/lib_c/x86_64-linux-gnu/c/signal.cr @@ -57,7 +57,7 @@ lib LibC si_code : Int __pad0 : Int si_addr : Void* # Assuming the sigfault form of siginfo_t - __pad1 : StaticArray(Int, 20) # __SI_PAD_SIZE (28) - sizeof(void*) (8) = 20 + __pad1 : StaticArray(Int, 26) # __SI_PAD_SIZE (28) - sizeof(void*) / sizeof(int) = 26 end alias SigactionHandlerT = (Int, SiginfoT*, Void*) -> @@ -66,7 +66,7 @@ lib LibC sa_sigaction : SigactionHandlerT sa_mask : SigsetT sa_flags : Int - sa_restorer : Void* + sa_restorer : -> end struct StackT diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr b/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr index 7f550c37a622..a0900a4730c4 100644 --- a/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr +++ b/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr @@ -21,5 +21,5 @@ lib LibC RUSAGE_SELF = 0 RUSAGE_CHILDREN = -1 - fun getrusage(who : Int, usage : RUsage*) : Int16 + fun getrusage(who : Int, usage : RUsage*) : Int end diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr b/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr index 11785c128f52..82e48d78c9f2 100644 --- a/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr +++ b/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr @@ -38,8 +38,8 @@ lib LibC struct SockaddrStorage ss_family : SaFamilyT + __ss_padding : StaticArray(Char, 118) __ss_align : ULong - __ss_padding : StaticArray(Char, 112) end struct Linger diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/types.cr b/src/lib_c/x86_64-linux-gnu/c/sys/types.cr index c217abaf72e1..0fa18856ed06 100644 --- a/src/lib_c/x86_64-linux-gnu/c/sys/types.cr +++ b/src/lib_c/x86_64-linux-gnu/c/sys/types.cr @@ -20,19 +20,7 @@ lib LibC __align : Long end - struct PthreadCondTData - __lock : Int - __futex : UInt - __total_seq : ULongLong - __wakeup_seq : ULongLong - __woken_seq : ULongLong - __mutex : Void* - __nwaiters : UInt - __broadcast_seq : UInt - end - union PthreadCondT - __data : PthreadCondTData __size : StaticArray(Char, 48) __align : LongLong end @@ -43,7 +31,6 @@ lib LibC end union PthreadMutexT - __data : Void* __size : StaticArray(Char, 40) __align : Long end From b17b690075607e27b0337b92d8502896e6e8d937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 9 Apr 2023 12:38:21 +0200 Subject: [PATCH 0428/1551] Update distribution-scripts (#13298) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a01ac409cd5b..28691e8b9a78 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "5ced0bc13de682688110387adc313fe02f96bab7" + default: "0328eb5ee7a4eae440846f890287b15ad2118e30" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From d1fd6c802f950dc084e7da8995bfd4e9d150d410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 9 Apr 2023 12:38:29 +0200 Subject: [PATCH 0429/1551] Update shards 0.17.3 (#13296) --- .github/workflows/win.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index d8e8746a6e30..82436c50e992 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -335,7 +335,7 @@ jobs: uses: actions/checkout@v3 with: repository: crystal-lang/shards - ref: v0.17.2 + ref: v0.17.3 path: shards - name: Download molinillo release From 953ba96cb741ed7025d88ad84d02226677077fda Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 12 Apr 2023 17:03:01 +0800 Subject: [PATCH 0430/1551] Make `BigRational.new(BigFloat)` exact (#13295) --- spec/std/big/big_rational_spec.cr | 8 ++++++++ src/big/big_rational.cr | 26 ++++++++++++++++++++------ src/big/lib_gmp.cr | 1 + 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr index 5526d226b179..7ebbc15f2a70 100644 --- a/spec/std/big/big_rational_spec.cr +++ b/spec/std/big/big_rational_spec.cr @@ -36,6 +36,14 @@ describe BigRational do end end + it "initializes from BigFloat with high precision" do + (0..12).each do |i| + bf = BigFloat.new(2.0, precision: 64) ** 64 + BigFloat.new(2.0, precision: 64) ** i + br = BigRational.new(bf) + br.should eq(bf) + end + end + it "#numerator" do br(10, 3).numerator.should eq(BigInt.new(10)) end diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index acb34838ca3f..76fa64032e57 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -23,9 +23,6 @@ struct BigRational < Number include Comparable(Int) include Comparable(Float) - private MANTISSA_BITS = 53 - private MANTISSA_SHIFT = (1_i64 << MANTISSA_BITS).to_f64 - # Creates a new `BigRational`. # # If *denominator* is 0, this will raise an exception. @@ -47,12 +44,29 @@ struct BigRational < Number end # Creates a exact representation of float as rational. - def initialize(num : Float) + def initialize(num : Float::Primitive) # It ensures that `BigRational.new(f) == f` # It relies on fact, that mantissa is at most 53 bits frac, exp = Math.frexp num - ifrac = (frac.to_f64 * MANTISSA_SHIFT).to_i64 - exp -= MANTISSA_BITS + ifrac = Math.ldexp(frac.to_f64, Float64::MANT_DIGITS).to_i64 + exp -= Float64::MANT_DIGITS + initialize ifrac, 1 + if exp >= 0 + LibGMP.mpq_mul_2exp(out @mpq, self, exp) + else + LibGMP.mpq_div_2exp(out @mpq, self, -exp) + end + end + + # :ditto: + def initialize(num : BigFloat) + frac, exp = Math.frexp num + prec = LibGMP.mpf_get_prec(frac) + # the mantissa has at most `prec + 1` bits, because the first fractional bit + # of `frac` is always 1, and `prec` variable bits follow + # TODO: use `Math.ldexp` after #11007 + ifrac = BigFloat.new { |mpf| LibGMP.mpf_mul_2exp(mpf, frac, prec + 1) }.to_big_i + exp -= prec + 1 initialize ifrac, 1 if exp >= 0 LibGMP.mpq_mul_2exp(out @mpq, self, exp) diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index 446f3ea5adbe..7292dbaff2e7 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -196,6 +196,7 @@ lib LibGMP # # Precision fun mpf_set_default_prec = __gmpf_set_default_prec(prec : BitcntT) fun mpf_get_default_prec = __gmpf_get_default_prec : BitcntT + fun mpf_get_prec = __gmpf_get_prec(op : MPF*) : BitcntT # # Conversion fun mpf_get_str = __gmpf_get_str(str : UInt8*, expptr : MpExp*, base : Int, n_digits : LibC::SizeT, op : MPF*) : UInt8* From c7d704201955322ec8901f9ca2b3062a09e10536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 13 Apr 2023 15:25:08 +0200 Subject: [PATCH 0431/1551] Fix infinite loop with `MATCH_INVALID_UTF` in PCRE2 <10.36 (#13311) --- spec/std/regex_spec.cr | 10 +++++----- src/regex/pcre2.cr | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index f71684ef66bb..698ca17c9a7d 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -115,8 +115,8 @@ describe "Regex" do /([\w_\.@#\/\*])+/.match("\xFF\xFE") end {% else %} - if Regex::PCRE2.version_number < {10, 35} - pending! "Error in libpcre2 < 10.35" + if Regex::PCRE2.version_number < {10, 36} + pending! "Error in libpcre2 < 10.36" else /([\w_\.@#\/\*])+/.match("\xFF\xFE").should be_nil end @@ -154,7 +154,7 @@ describe "Regex" do end it "multibyte index" do - if Regex::Engine.version_number < {10, 34} + if Regex::Engine.version_number < {10, 36} expect_raises(ArgumentError, "bad offset into UTF string") do /foo/.match_at_byte_index("öfoo", 1) end @@ -246,7 +246,7 @@ describe "Regex" do end it "invalid codepoint" do - if Regex::Engine.version_number < {10, 34} + if Regex::Engine.version_number < {10, 36} expect_raises(ArgumentError, "UTF-8 error") do /foo/.matches?("f\x96o") end @@ -310,7 +310,7 @@ describe "Regex" do end it "multibyte index" do - if Regex::Engine.version_number < {10, 34} + if Regex::Engine.version_number < {10, 36} expect_raises(ArgumentError, "bad offset into UTF string") do /foo/.matches_at_byte_index?("öfoo", 1) end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 4bfa074f4289..6c64fa1d8e41 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -23,7 +23,9 @@ module Regex::PCRE2 # :nodoc: def initialize(*, _source @source : String, _options @options) options = pcre2_compile_options(options) | LibPCRE2::UTF | LibPCRE2::DUPNAMES | LibPCRE2::UCP - if PCRE2.version_number >= {10, 34} + # MATCH_INVALID_UTF was introduced in 10.34 but a bug that can lead to an + # infinite loop is only fixed in 10.36 (https://github.com/PCRE2Project/pcre2/commit/e0c6029a62db9c2161941ecdf459205382d4d379). + if PCRE2.version_number >= {10, 36} options |= LibPCRE2::MATCH_INVALID_UTF end @re = PCRE2.compile(source, options) do |error_message| From 45c529691cd2dc037de2a6c957dbbf7dedc86177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 13 Apr 2023 19:33:54 +0200 Subject: [PATCH 0432/1551] Drop `MATCH_INVALID_UTF` (#13313) --- spec/std/regex_spec.cr | 69 +++++++++++++++++++++--------------------- src/regex.cr | 23 ++++++++++++++ src/regex/pcre.cr | 34 +++++++++++---------- src/regex/pcre2.cr | 39 +++++++++++------------- 4 files changed, 94 insertions(+), 71 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 698ca17c9a7d..098c718b2248 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -30,7 +30,7 @@ describe "Regex" do expect_raises(ArgumentError, /invalid UTF-8 string|UTF-8 error/) do Regex.new("\x96") end - Regex.new("\x96", :NO_UTF8_CHECK).should be_a(Regex) + Regex.new("\x96", :NO_UTF_CHECK).should be_a(Regex) end end @@ -110,17 +110,18 @@ describe "Regex" do end it "with invalid UTF-8" do - {% if Regex::Engine.resolve.name == "Regex::PCRE" %} - expect_raises(ArgumentError, "UTF-8 error") do - /([\w_\.@#\/\*])+/.match("\xFF\xFE") - end - {% else %} - if Regex::PCRE2.version_number < {10, 36} - pending! "Error in libpcre2 < 10.36" - else - /([\w_\.@#\/\*])+/.match("\xFF\xFE").should be_nil - end - {% end %} + expect_raises(ArgumentError, "UTF-8 error") do + /([\w_\.@#\/\*])+/.match("\xFF\xFE") + end + + if Regex::Engine.version_number >= {10, 36} + Regex.new("([\\w_\\.@#\\/\\*])+", options: Regex::Options::MATCH_INVALID_UTF).match("\xFF\xFE").should be_nil + end + end + + it "skip invalid UTF check" do + # no exception raised + /f.o/.matches?("f\xFFo", options: Regex::MatchOptions::NO_UTF_CHECK) end end @@ -154,12 +155,12 @@ describe "Regex" do end it "multibyte index" do - if Regex::Engine.version_number < {10, 36} - expect_raises(ArgumentError, "bad offset into UTF string") do - /foo/.match_at_byte_index("öfoo", 1) - end - else - md = /foo/.match_at_byte_index("öfoo", 1).should_not be_nil + expect_raises(ArgumentError, "bad offset into UTF string") do + /foo/.match_at_byte_index("öfoo", 1) + end + + if Regex::Engine.version_number >= {10, 36} + md = Regex.new("foo", options: Regex::CompileOptions::MATCH_INVALID_UTF).match_at_byte_index("öfoo", 1).should_not be_nil md.begin.should eq 1 md.byte_begin.should eq 2 end @@ -246,16 +247,16 @@ describe "Regex" do end it "invalid codepoint" do - if Regex::Engine.version_number < {10, 36} - expect_raises(ArgumentError, "UTF-8 error") do - /foo/.matches?("f\x96o") - end - else - /foo/.matches?("f\x96o").should be_false - /f\x96o/.matches?("f\x96o").should be_false - /f.o/.matches?("f\x96o").should be_false - /\bf\b/.matches?("f\x96o").should be_true - /\bo\b/.matches?("f\x96o").should be_true + expect_raises(ArgumentError, "UTF-8 error") do + /foo/.matches?("f\x96o") + end + + if Regex::Engine.version_number >= {10, 36} + Regex.new("foo", options: Regex::CompileOptions::MATCH_INVALID_UTF).matches?("f\x96o").should be_false + Regex.new("f\x96o", options: Regex::CompileOptions::MATCH_INVALID_UTF | Regex::CompileOptions::NO_UTF_CHECK).matches?("f\x96o").should be_false + Regex.new("f.o", options: Regex::CompileOptions::MATCH_INVALID_UTF).matches?("f\x96o").should be_false + Regex.new("\\bf\\b", options: Regex::CompileOptions::MATCH_INVALID_UTF).matches?("f\x96o").should be_true + Regex.new("\\bo\\b", options: Regex::CompileOptions::MATCH_INVALID_UTF).matches?("f\x96o").should be_true end end end @@ -310,12 +311,12 @@ describe "Regex" do end it "multibyte index" do - if Regex::Engine.version_number < {10, 36} - expect_raises(ArgumentError, "bad offset into UTF string") do - /foo/.matches_at_byte_index?("öfoo", 1) - end - else - /foo/.matches_at_byte_index?("öfoo", 1).should be_true + expect_raises(ArgumentError, "bad offset into UTF string") do + /foo/.matches_at_byte_index?("öfoo", 1) + end + + if Regex::Engine.version_number >= {10, 36} + Regex.new("foo", options: Regex::CompileOptions::MATCH_INVALID_UTF).matches_at_byte_index?("öfoo", 1).should be_true end /foo/.matches_at_byte_index?("öfoo", 2).should be_true end diff --git a/src/regex.cr b/src/regex.cr index 6c4109d62f91..561e9d2fcc4e 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -257,6 +257,23 @@ class Regex # # Unsupported with PCRE. ENDANCHORED = 0x8000_0000 + + # Do not check the pattern for valid UTF encoding. + NO_UTF_CHECK = NO_UTF8_CHECK + + # Enable matching against subjects containing invalid UTF bytes. + # Invalid bytes never match anything. The entire subject string is + # effectively split into segments of valid UTF. + # + # Read more in the [PCRE2 documentation](https://www.pcre.org/current/doc/html/pcre2unicode.html#matchinvalid). + # + # When this option is set, `MatchOptions::NO_UTF_CHECK` is ignored at match time. + # + # Unsupported with PCRE. + # + # NOTE: This option was introduced in PCRE2 10.34 but a bug that can lead to an + # infinite loop is only fixed in 10.36 (https://github.com/PCRE2Project/pcre2/commit/e0c6029a62db9c2161941ecdf459205382d4d379). + MATCH_INVALID_UTF = 0x1_0000_0000 end # Represents compile options passed to `Regex.new`. @@ -279,6 +296,12 @@ class Regex # # Unsupported with PCRE. NO_JIT + + # Do not check subject for valid UTF encoding. + # + # This option has no effect if the pattern was compiled with + # `CompileOptions::MATCH_INVALID_UTF` when using PCRE2 10.34+. + NO_UTF_CHECK end # Returns a `Regex::CompileOptions` representing the optional flags applied to this `Regex`. diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr index 7f1170b8e42b..d3cbad25c3ec 100644 --- a/src/regex/pcre.cr +++ b/src/regex/pcre.cr @@ -35,18 +35,19 @@ module Regex::PCRE Regex::CompileOptions.each do |option| if options.includes?(option) flag |= case option - when .ignore_case? then LibPCRE::CASELESS - when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE - when .dotall? then LibPCRE::DOTALL - when .extended? then LibPCRE::EXTENDED - when .anchored? then LibPCRE::ANCHORED - when .dollar_endonly? then LibPCRE::DOLLAR_ENDONLY - when .firstline? then LibPCRE::FIRSTLINE - when .utf_8? then LibPCRE::UTF8 - when .no_utf8_check? then LibPCRE::NO_UTF8_CHECK - when .dupnames? then LibPCRE::DUPNAMES - when .ucp? then LibPCRE::UCP - when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") + when .ignore_case? then LibPCRE::CASELESS + when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE + when .dotall? then LibPCRE::DOTALL + when .extended? then LibPCRE::EXTENDED + when .anchored? then LibPCRE::ANCHORED + when .dollar_endonly? then LibPCRE::DOLLAR_ENDONLY + when .firstline? then LibPCRE::FIRSTLINE + when .utf_8? then LibPCRE::UTF8 + when .no_utf_check? then LibPCRE::NO_UTF8_CHECK + when .dupnames? then LibPCRE::DUPNAMES + when .ucp? then LibPCRE::UCP + when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") + when .match_invalid_utf? then raise ArgumentError.new("Regex::Option::MATCH_INVALID_UTF is not supported with PCRE") else raise "unreachable" end @@ -73,7 +74,7 @@ module Regex::PCRE when .dollar_endonly? then raise ArgumentError.new("Invalid regex option DOLLAR_ENDONLY for `pcre_exec`") when .firstline? then raise ArgumentError.new("Invalid regex option FIRSTLINE for `pcre_exec`") when .utf_8? then raise ArgumentError.new("Invalid regex option UTF_8 for `pcre_exec`") - when .no_utf8_check? then LibPCRE::NO_UTF8_CHECK + when .no_utf_check? then LibPCRE::NO_UTF8_CHECK when .dupnames? then raise ArgumentError.new("Invalid regex option DUPNAMES for `pcre_exec`") when .ucp? then raise ArgumentError.new("Invalid regex option UCP for `pcre_exec`") when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") @@ -95,9 +96,10 @@ module Regex::PCRE Regex::MatchOptions.each do |option| if options.includes?(option) flag |= case option - when .anchored? then LibPCRE::ANCHORED - when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") - when .no_jit? then raise ArgumentError.new("Regex::Option::NO_JIT is not supported with PCRE") + when .anchored? then LibPCRE::ANCHORED + when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") + when .no_jit? then raise ArgumentError.new("Regex::Option::NO_JIT is not supported with PCRE") + when .no_utf_check? then LibPCRE::NO_UTF8_CHECK else raise "unreachable" end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 6c64fa1d8e41..973e1967499d 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -23,11 +23,6 @@ module Regex::PCRE2 # :nodoc: def initialize(*, _source @source : String, _options @options) options = pcre2_compile_options(options) | LibPCRE2::UTF | LibPCRE2::DUPNAMES | LibPCRE2::UCP - # MATCH_INVALID_UTF was introduced in 10.34 but a bug that can lead to an - # infinite loop is only fixed in 10.36 (https://github.com/PCRE2Project/pcre2/commit/e0c6029a62db9c2161941ecdf459205382d4d379). - if PCRE2.version_number >= {10, 36} - options |= LibPCRE2::MATCH_INVALID_UTF - end @re = PCRE2.compile(source, options) do |error_message| raise ArgumentError.new(error_message) end @@ -70,18 +65,19 @@ module Regex::PCRE2 Regex::CompileOptions.each do |option| if options.includes?(option) flag |= case option - when .ignore_case? then LibPCRE2::CASELESS - when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE - when .dotall? then LibPCRE2::DOTALL - when .extended? then LibPCRE2::EXTENDED - when .anchored? then LibPCRE2::ANCHORED - when .dollar_endonly? then LibPCRE2::DOLLAR_ENDONLY - when .firstline? then LibPCRE2::FIRSTLINE - when .utf_8? then LibPCRE2::UTF - when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK - when .dupnames? then LibPCRE2::DUPNAMES - when .ucp? then LibPCRE2::UCP - when .endanchored? then LibPCRE2::ENDANCHORED + when .ignore_case? then LibPCRE2::CASELESS + when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE + when .dotall? then LibPCRE2::DOTALL + when .extended? then LibPCRE2::EXTENDED + when .anchored? then LibPCRE2::ANCHORED + when .dollar_endonly? then LibPCRE2::DOLLAR_ENDONLY + when .firstline? then LibPCRE2::FIRSTLINE + when .utf_8? then LibPCRE2::UTF + when .no_utf_check? then LibPCRE2::NO_UTF_CHECK + when .dupnames? then LibPCRE2::DUPNAMES + when .ucp? then LibPCRE2::UCP + when .endanchored? then LibPCRE2::ENDANCHORED + when .match_invalid_utf? then LibPCRE2::MATCH_INVALID_UTF else raise "unreachable" end @@ -107,7 +103,7 @@ module Regex::PCRE2 when .dollar_endonly? then raise ArgumentError.new("Invalid regex option DOLLAR_ENDONLY for `pcre2_match`") when .firstline? then raise ArgumentError.new("Invalid regex option FIRSTLINE for `pcre2_match`") when .utf_8? then raise ArgumentError.new("Invalid regex option UTF_8 for `pcre2_match`") - when .no_utf8_check? then LibPCRE2::NO_UTF_CHECK + when .no_utf_check? then LibPCRE2::NO_UTF_CHECK when .dupnames? then raise ArgumentError.new("Invalid regex option DUPNAMES for `pcre2_match`") when .ucp? then raise ArgumentError.new("Invalid regex option UCP for `pcre2_match`") when .endanchored? then LibPCRE2::ENDANCHORED @@ -128,9 +124,10 @@ module Regex::PCRE2 Regex::MatchOptions.each do |option| if options.includes?(option) flag |= case option - when .anchored? then LibPCRE2::ANCHORED - when .endanchored? then LibPCRE2::ENDANCHORED - when .no_jit? then LibPCRE2::NO_JIT + when .anchored? then LibPCRE2::ANCHORED + when .endanchored? then LibPCRE2::ENDANCHORED + when .no_jit? then LibPCRE2::NO_JIT + when .no_utf_check? then LibPCRE2::NO_UTF_CHECK else raise "unreachable" end From 927f0410b3523cf7f3c38d9d532c04c21190d29d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 14 Apr 2023 01:54:22 +0800 Subject: [PATCH 0433/1551] Remove `Regex::PCRE2#finalize` redefinition (#13309) --- src/regex/pcre2.cr | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 973e1967499d..de625d3676d3 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -140,12 +140,6 @@ module Regex::PCRE2 flag end - def finalize - {% unless flag?(:interpreted) %} - LibPCRE2.code_free @re - {% end %} - end - protected def self.error_impl(source) code = PCRE2.compile(source, LibPCRE2::UTF | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| return error_message @@ -256,6 +250,7 @@ module Regex::PCRE2 @match_data.consume_each do |match_data| LibPCRE2.match_data_free(match_data) end + LibPCRE2.code_free @re end private def match_data(str, byte_index, options) @@ -278,12 +273,6 @@ module Regex::PCRE2 match_data end - def self.config(what, type : T.class) : T forall T - value = uninitialized T - LibPCRE2.config(what, pointerof(value)) - value - end - module MatchData # :nodoc: def initialize(@regex : Regex, @code : LibPCRE2::Code*, @string : String, @pos : Int32, @ovector : LibC::SizeT*, @group_size : Int32) From 2c9dba72046a7bb90b6f3738b7992d43e2877cfd Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 14 Apr 2023 02:49:49 +0800 Subject: [PATCH 0434/1551] Clarify behavior of strings with invalid UTF-8 byte sequences (#13314) --- src/string.cr | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/string.cr b/src/string.cr index 7d446d2edd34..9454f6d1ea03 100644 --- a/src/string.cr +++ b/src/string.cr @@ -98,12 +98,12 @@ require "c/string" # # ### Non UTF-8 valid strings # -# String might end up being conformed of bytes which are an invalid +# A string might end up being composed of bytes which form an invalid # byte sequence according to UTF-8. This can happen if the string is created # via one of the constructors that accept bytes, or when getting a string -# from `String.build` or `IO::Memory`. No exception will be raised, but -# invalid byte sequences, when asked as chars, will use the unicode replacement -# char (value 0xFFFD). For example: +# from `String.build` or `IO::Memory`. No exception will be raised, but every +# byte that doesn't start a valid UTF-8 byte sequence is interpreted as though +# it encodes the Unicode replacement character (U+FFFD) by itself. For example: # # ``` # # here 255 is not a valid byte value in the UTF-8 encoding @@ -134,6 +134,13 @@ require "c/string" # and having a program raise an exception or stop because of this # is not good. It's better if programs are more resilient, but # show a replacement character when there's an error in incoming data. +# +# Note that this interpretation only applies to methods inside Crystal; calling +# `#to_slice` or `#to_unsafe`, e.g. when passing a string to a C library, will +# expose the invalid UTF-8 byte sequences. In particular, `Regex`'s underlying +# engine may reject strings that are not valid UTF-8, or it may invoke undefined +# behavior on invalid strings. If this is undesired, `#scrub` could be used to +# remove the offending byte sequences first. class String # :nodoc: TYPE_ID = "".crystal_type_id @@ -5254,11 +5261,17 @@ class String # Returns the underlying bytes of this String. # # The returned slice is read-only. + # + # May contain invalid UTF-8 byte sequences; `#scrub` may be used to first + # obtain a `String` that is guaranteed to be valid UTF-8. def to_slice : Bytes Slice.new(to_unsafe, bytesize, read_only: true) end # Returns a pointer to the underlying bytes of this String. + # + # May contain invalid UTF-8 byte sequences; `#scrub` may be used to first + # obtain a `String` that is guaranteed to be valid UTF-8. def to_unsafe : UInt8* pointerof(@c) end From 469b5b27788797aeec4cade9e9292d8351c73834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 13 Apr 2023 21:37:01 +0200 Subject: [PATCH 0435/1551] Revert "Remove relative path to vendored shard `markd` (#13040)" (#13315) --- src/compiler/crystal/interpreter/repl_reader.cr | 2 +- src/compiler/crystal/tools/doc/generator.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/interpreter/repl_reader.cr b/src/compiler/crystal/interpreter/repl_reader.cr index 535ec53e64a9..f805150dbcd7 100644 --- a/src/compiler/crystal/interpreter/repl_reader.cr +++ b/src/compiler/crystal/interpreter/repl_reader.cr @@ -1,4 +1,4 @@ -require "reply" +require "../../../../lib/reply/src/reply" class Crystal::ReplReader < Reply::Reader KEYWORDS = %w( diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index 8aa1b30dbecc..9e2a22a25506 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -1,4 +1,4 @@ -require "markd" +require "../../../../../lib/markd/src/markd" require "crystal/syntax_highlighter/html" class Crystal::Doc::Generator From 5dc9424ad564945439fff22fa108ccc51bc99d3e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 14 Apr 2023 03:38:20 +0800 Subject: [PATCH 0436/1551] Always use 0 for offsets of lib / extern union members (#13305) --- spec/compiler/codegen/debug_spec.cr | 27 ++++++++++++++++ spec/compiler/codegen/offsetof_spec.cr | 17 ++++++++++ spec/debug/extern_unions.cr | 17 ++++++++++ src/compiler/crystal/codegen/codegen.cr | 3 ++ src/compiler/crystal/codegen/debug.cr | 41 +++++++++++++++---------- src/llvm/target_data.cr | 2 ++ 6 files changed, 90 insertions(+), 17 deletions(-) create mode 100644 spec/debug/extern_unions.cr diff --git a/spec/compiler/codegen/debug_spec.cr b/spec/compiler/codegen/debug_spec.cr index 1903d1e58601..4a57056fc7a3 100644 --- a/spec/compiler/codegen/debug_spec.cr +++ b/spec/compiler/codegen/debug_spec.cr @@ -16,6 +16,33 @@ describe "Code gen: debug" do ), debug: Crystal::Debug::All) end + it "codegens lib union (#7335)" do + codegen <<-CRYSTAL, debug: Crystal::Debug::All + lib Foo + union Bar + a : Int32 + b : Int16 + c : Int8 + end + end + + x = Foo::Bar.new + CRYSTAL + end + + it "codegens extern union (#7335)" do + codegen <<-CRYSTAL, debug: Crystal::Debug::All + @[Extern(union: true)] + struct Foo + @a = uninitialized Int32 + @b = uninitialized Int16 + @c = uninitialized Int8 + end + + x = Foo.new + CRYSTAL + end + it "inlines instance var access through getter in debug mode" do run(%( struct Bar diff --git a/spec/compiler/codegen/offsetof_spec.cr b/spec/compiler/codegen/offsetof_spec.cr index fa2a0dbe8b37..f7e7971be6b2 100644 --- a/spec/compiler/codegen/offsetof_spec.cr +++ b/spec/compiler/codegen/offsetof_spec.cr @@ -39,4 +39,21 @@ describe "Code gen: offsetof" do run(code).to_b.should be_true end + + it "returns offset of extern union" do + run(<<-CRYSTAL).to_b.should be_true + @[Extern(union: true)] + struct Foo + @x = 1.0_f32 + @y = uninitialized UInt32 + + def y + @y + end + end + + f = Foo.new + (pointerof(f).as(Void*) + offsetof(Foo, @y).to_i64).as(UInt32*).value == f.y + CRYSTAL + end end diff --git a/spec/debug/extern_unions.cr b/spec/debug/extern_unions.cr new file mode 100644 index 000000000000..66943926dcff --- /dev/null +++ b/spec/debug/extern_unions.cr @@ -0,0 +1,17 @@ +@[Extern(union: true)] +struct Foo + @x : Float32 + @y = uninitialized UInt32 + @z = uninitialized UInt8[4] + + def initialize(@x) + end +end + +raise "wrong endianness" unless IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian + +x = Foo.new(1.0_f32) +# print: x +# lldb-check: $0 = (x = 1065353216, y = 1, z = "\0\0\x80?") +# gdb-check: $1 = {x = 1065353216, y = 1, z = "\000\000\200?"} +debugger diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 562e52eaf1ff..3b3a81757bb8 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -105,10 +105,13 @@ module Crystal end def offset_of(type, element_index) + return 0_u64 if type.extern_union? llvm_typer.offset_of(llvm_typer.llvm_type(type), element_index) end def instance_offset_of(type, element_index) + # extern unions must be value types, which always use the above + # `offset_of` instead llvm_typer.offset_of(llvm_typer.llvm_struct_type(type), element_index + 1) end end diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index a363e994d1dd..df1b33b23bd8 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -82,12 +82,20 @@ module Crystal @debug_files_per_module[@llvm_mod] ||= {} of DebugFilename => LibLLVM::MetadataRef end - def current_debug_file - filename = @current_debug_location.try(&.filename) || "??" - debug_files_cache[filename] ||= begin - file, dir = file_and_dir(filename) - di_builder.create_file(file, dir) - end + private def current_debug_file + # These debug files are only used for `DIBuilder#create_union_type`, even + # though they are unneeded here, just as struct types don't need a file; + # LLVM 12 or below produces an assertion failure that is now removed + # (https://github.com/llvm/llvm-project/commit/ad60802a7187aa39b0374536be3fa176fe3d6256) + {% if LibLLVM::IS_LT_130 %} + filename = @current_debug_location.try(&.filename) || "??" + debug_files_cache[filename] ||= begin + file, dir = file_and_dir(filename) + di_builder.create_file(file, dir) + end + {% else %} + Pointer(Void).null.as(LibLLVM::MetadataRef) + {% end %} end def get_debug_type(type, original_type : Type) @@ -148,21 +156,23 @@ module Crystal ivars.each_with_index do |(name, ivar), idx| next if ivar.type.is_a?(NilType) if (ivar_type = ivar.type?) && (ivar_debug_type = get_debug_type(ivar_type)) - offset = @program.target_machine.data_layout.offset_of_element(struct_type, idx &+ (type.struct? ? 0 : 1)) + offset = type.extern_union? ? 0_u64 : @program.target_machine.data_layout.offset_of_element(struct_type, idx &+ (type.struct? ? 0 : 1)) size = @program.target_machine.data_layout.size_in_bits(llvm_embedded_type(ivar_type)) - # FIXME structs like LibC::PthreadMutexT generate huge offset values - next if offset > UInt64::MAX // 8u64 - member = di_builder.create_member_type(nil, name[1..-1], nil, 1, size, size, 8u64 * offset, LLVM::DIFlags::Zero, ivar_debug_type) element_types << member end end size = @program.target_machine.data_layout.size_in_bits(struct_type) - debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, size, size, LLVM::DIFlags::Zero, nil, di_builder.get_or_create_type_array(element_types)) - unless type.struct? - debug_type = di_builder.create_pointer_type(debug_type, 8u64 * llvm_typer.pointer_size, 8u64 * llvm_typer.pointer_size, original_type.to_s) + elements = di_builder.get_or_create_type_array(element_types) + if type.extern_union? + debug_type = di_builder.create_union_type(nil, original_type.to_s, current_debug_file, 1, size, size, LLVM::DIFlags::Zero, elements) + else + debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, size, size, LLVM::DIFlags::Zero, nil, elements) + unless type.struct? + debug_type = di_builder.create_pointer_type(debug_type, 8u64 * llvm_typer.pointer_size, 8u64 * llvm_typer.pointer_size, original_type.to_s) + end end di_builder.replace_temporary(tmp_debug_type, debug_type) debug_type @@ -257,7 +267,7 @@ module Crystal if ivar_debug_type = get_debug_type(ivar_type) offset = @program.target_machine.data_layout.offset_of_element(struct_type, idx &+ (type.struct? ? 0 : 1)) size = @program.target_machine.data_layout.size_in_bits(llvm_embedded_type(ivar_type)) - next if offset > UInt64::MAX // 8u64 # TODO: Figure out why it is happening sometimes with offset + member = di_builder.create_member_type(nil, "[#{idx}]", nil, 1, size, size, 8u64 * offset, LLVM::DIFlags::Zero, ivar_debug_type) element_types << member end @@ -286,9 +296,6 @@ module Crystal offset = @program.target_machine.data_layout.offset_of_element(struct_type, idx &+ (type.struct? ? 0 : 1)) size = @program.target_machine.data_layout.size_in_bits(llvm_embedded_type(ivar_type)) - # FIXME structs like LibC::PthreadMutexT generate huge offset values - next if offset > UInt64::MAX // 8u64 - member = di_builder.create_member_type(nil, ivar.name, nil, 1, size, size, 8u64 * offset, LLVM::DIFlags::Zero, ivar_debug_type) element_types << member end diff --git a/src/llvm/target_data.cr b/src/llvm/target_data.cr index 150e193bc7cb..ed8d0c07ed91 100644 --- a/src/llvm/target_data.cr +++ b/src/llvm/target_data.cr @@ -23,6 +23,8 @@ struct LLVM::TargetData end def offset_of_element(struct_type, element) + # element_count = LibLLVM.count_struct_element_types(struct_type) + # raise "Invalid element idx!" unless element < element_count LibLLVM.offset_of_element(self, struct_type, element) end From 23a8370a99ab159641a61b9a26502b5d4b3be49a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 14 Apr 2023 22:12:12 +0800 Subject: [PATCH 0437/1551] Refer to PCRE2 in `Regex`'s summary (#13318) --- src/regex.cr | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/regex.cr b/src/regex.cr index 561e9d2fcc4e..834212ffe70c 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -80,27 +80,31 @@ require "./regex/match_data" # have their own language for describing strings. # # Many programming languages and tools implement their own regular expression -# language, but Crystal uses [PCRE](http://www.pcre.org/), a popular C library, with -# [JIT complication](http://www.pcre.org/original/doc/html/pcrejit.html) enabled +# language, but Crystal uses [PCRE2](http://www.pcre.org/), a popular C library, with +# [JIT complication](http://www.pcre.org/current/doc/html/pcre2jit.html) enabled # for providing regular expressions. Here give a brief summary of the most # basic features of regular expressions - grouping, repetition, and -# alternation - but the feature set of PCRE extends far beyond these, and we +# alternation - but the feature set of PCRE2 extends far beyond these, and we # don't attempt to describe it in full here. For more information, refer to -# the PCRE documentation, especially the -# [full pattern syntax](http://www.pcre.org/original/doc/html/pcrepattern.html) +# the PCRE2 documentation, especially the +# [full pattern syntax](http://www.pcre.org/current/doc/html/pcre2pattern.html) # or -# [syntax quick reference](http://www.pcre.org/original/doc/html/pcresyntax.html). +# [syntax quick reference](http://www.pcre.org/current/doc/html/pcre2syntax.html). +# +# NOTE: Prior to Crystal 1.8 the compiler expected regex literals to follow the +# original [PCRE pattern syntax](https://www.pcre.org/original/doc/html/pcrepattern.html). +# The following summary applies to both PCRE and PCRE2. # # The regular expression language can be used to match much more than just the # static substrings in the above examples. Certain characters, called -# [metacharacters](http://www.pcre.org/original/doc/html/pcrepattern.html#SEC4), +# [metacharacters](http://www.pcre.org/current/doc/html/pcre2pattern.html#SEC4), # are given special treatment in regular expressions, and can be used to # describe more complex patterns. To match metacharacters literally in a # regular expression, they must be escaped by being preceded with a backslash # (`\`). `escape` will do this automatically for a given String. # # A group of characters (often called a capture group or -# [subpattern](http://www.pcre.org/original/doc/html/pcrepattern.html#SEC14)) +# [subpattern](http://www.pcre.org/current/doc/html/pcre2pattern.html#SEC14)) # can be identified by enclosing it in parentheses (`()`). The contents of # each capture group can be extracted on a successful match: # @@ -131,7 +135,7 @@ require "./regex/match_data" # would return `nil`. `$2?.nil?` would return `true`. # # A character or group can be -# [repeated](http://www.pcre.org/original/doc/html/pcrepattern.html#SEC17) +# [repeated](http://www.pcre.org/current/doc/html/pcre2pattern.html#SEC17) # or made optional using an asterisk (`*` - zero or more), a plus sign # (`+` - one or more), integer bounds in curly braces # (`{n,m}`) (at least `n`, no more than `m`), or a question mark @@ -152,12 +156,12 @@ require "./regex/match_data" # ``` # # Alternatives can be separated using a -# [vertical bar](http://www.pcre.org/original/doc/html/pcrepattern.html#SEC12) +# [vertical bar](http://www.pcre.org/current/doc/html/pcre2pattern.html#SEC12) # (`|`). Any single character can be represented by -# [dot](http://www.pcre.org/original/doc/html/pcrepattern.html#SEC7) +# [dot](http://www.pcre.org/current/doc/html/pcre2pattern.html#SEC7) # (`.`). When matching only one character, specific # alternatives can be expressed as a -# [character class](http://www.pcre.org/original/doc/html/pcrepattern.html#SEC9), +# [character class](http://www.pcre.org/current/doc/html/pcre2pattern.html#SEC9), # enclosed in square brackets (`[]`): # # ``` @@ -175,11 +179,11 @@ require "./regex/match_data" # ``` # # Regular expressions can be defined with these 3 -# [optional flags](http://www.pcre.org/original/doc/html/pcreapi.html#SEC11): +# [optional flags](http://www.pcre.org/current/doc/html/pcre2pattern.html#SEC13): # -# * `i`: ignore case (PCRE_CASELESS) -# * `m`: multiline (PCRE_MULTILINE and PCRE_DOTALL) -# * `x`: extended (PCRE_EXTENDED) +# * `i`: ignore case (`Regex::Options::IGNORE_CASE`) +# * `m`: multiline (`Regex::Options::MULTILINE`) +# * `x`: extended (`Regex::Options::EXTENDED`) # # ``` # /asdf/ =~ "ASDF" # => nil @@ -188,10 +192,10 @@ require "./regex/match_data" # /^z/im =~ "ASDF\nZ" # => 5 # ``` # -# PCRE supports other encodings, but Crystal strings are UTF-8 only, so Crystal +# PCRE2 supports other encodings, but Crystal strings are UTF-8 only, so Crystal # regular expressions are also UTF-8 only (by default). # -# PCRE optionally permits named capture groups (named subpatterns) to not be +# PCRE2 optionally permits named capture groups (named subpatterns) to not be # unique. Crystal exposes the name table of a `Regex` as a # `Hash` of `String` => `Int32`, and therefore requires named capture groups to have # unique names within a single `Regex`. From 14bfa992ebd9cfa8946a151065dff7eebf829333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 14 Apr 2023 16:13:40 +0200 Subject: [PATCH 0438/1551] Add changelog for 1.8.0 (#13274) --- CHANGELOG.md | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 244 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 283dd38a0b85..85f013219f80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,246 @@ +# 1.8.0 (2023-04-14) + +## Language + +- The compiler uses PCRE2 to validate regex literals ([#13084](https://github.com/crystal-lang/crystal/pull/13084), thanks @straight-shoota) +- Fill docs for `TupleLiteral` ([#12927](https://github.com/crystal-lang/crystal/pull/12927), thanks @straight-shoota) +- Allow namespaced `Path`s as type names for `lib` ([#12903](https://github.com/crystal-lang/crystal/pull/12903), thanks @HertzDevil) + +## Standard Library + +- Fix `SyntaxHighlighter::HTML` to escape identifier values ([#13212](https://github.com/crystal-lang/crystal/pull/13212), thanks @straight-shoota) +- Add workaround for `Value#not_nil!` copying the receiver ([#13264](https://github.com/crystal-lang/crystal/pull/13264), thanks @HertzDevil) +- Fix `Pointer#copy_to` overflow on unsigned size and different target type ([#13269](https://github.com/crystal-lang/crystal/pull/13269), thanks @HertzDevil) +- Docs: Added note about imports where necessary ([#13026](https://github.com/crystal-lang/crystal/pull/13026), [#13066](https://github.com/crystal-lang/crystal/pull/13066), thanks @Tamnac, @straight-shoota) +- Suppress compiler output in `compile_file` spec helper ([#13228](https://github.com/crystal-lang/crystal/pull/13228), thanks @straight-shoota) +- Define equality for `Process::Status` and `OAuth::RequestToken` ([#13014](https://github.com/crystal-lang/crystal/pull/13014), thanks @HertzDevil) +- Fix some Linux glibc bindings ([#13242](https://github.com/crystal-lang/crystal/pull/13242), [#13249](https://github.com/crystal-lang/crystal/pull/13249), thanks @ysbaddaden, @HertzDevil) + +### Collection + +- **(breaking-change)** Fix `Enum#includes?` to require all bits set ([#13229](https://github.com/crystal-lang/crystal/pull/13229), thanks @straight-shoota) +- **(breaking-change)** Deprecate `Enum.flags` ([#12900](https://github.com/crystal-lang/crystal/pull/12900), thanks @straight-shoota) +- **(breaking-change)** Remove compile-time error for `Range#size`, `#each`, `#sample` ([#13278](https://github.com/crystal-lang/crystal/pull/13278), thanks @straight-shoota) +- **(breaking-change)** Docs: Require all `Indexable`s to be stable ([#13061](https://github.com/crystal-lang/crystal/pull/13061), thanks @HertzDevil) +- Add `Enum.[]` convenience constructor ([#12900](https://github.com/crystal-lang/crystal/pull/12900), thanks @straight-shoota) +- Rename internal `Iterator::Slice` type to not conflict with `::Slice` ([#12983](https://github.com/crystal-lang/crystal/pull/12983), thanks @Blacksmoke16) +- Fix `Array#replace` on shifted arrays ([#13256](https://github.com/crystal-lang/crystal/pull/13256), thanks @HertzDevil) +- Add `Tuple#to_static_array` ([#12930](https://github.com/crystal-lang/crystal/pull/12930), thanks @straight-shoota) +- Add `Enum#inspect` ([#13004](https://github.com/crystal-lang/crystal/pull/13004), thanks @straight-shoota) +- Add `Slice#+(Slice)` and `Slice.join` ([#12081](https://github.com/crystal-lang/crystal/pull/12081), thanks @HertzDevil) +- Add `Enumerable#min(count)` and `#max(count)` ([#13057](https://github.com/crystal-lang/crystal/pull/13057), thanks @nthiad) +- Fix `Array(T)#[]=(Int, Int, Array(T))` on shifted arrays ([#13275](https://github.com/crystal-lang/crystal/pull/13275), thanks @HertzDevil) + +### Concurrency + +- Fix: Make sure to dup `Array` in `Channel.select_impl` ([#12827](https://github.com/crystal-lang/crystal/pull/12827), [#12962](https://github.com/crystal-lang/crystal/pull/12962), thanks @straight-shoota) +- Add memory barriers on lock/unlock of SpinLock ([#13050](https://github.com/crystal-lang/crystal/pull/13050), thanks @bcardiff) +- **(performance)** Avoid `Array` allocation in `Channel.select(Tuple)` ([#12960](https://github.com/crystal-lang/crystal/pull/12960), thanks @straight-shoota) + +### Files + +- **(breaking-change)** Deprecate `Termios` ([#12940](https://github.com/crystal-lang/crystal/pull/12940), thanks @HertzDevil) +- **(breaking-change)** Windows: make `File.delete` remove symlink directories, not `Dir.delete` ([#13224](https://github.com/crystal-lang/crystal/pull/13224), thanks @HertzDevil) +- Leverage `fileapi` for opening files on windows ([#13178](https://github.com/crystal-lang/crystal/pull/13178), thanks @Blacksmoke16) +- Windows: fix error condition when `File.open` fails ([#13235](https://github.com/crystal-lang/crystal/pull/13235), thanks @HertzDevil) +- Skip eacces spec for superuser ([#13227](https://github.com/crystal-lang/crystal/pull/13227), thanks @straight-shoota) +- Improve `File.symlink` on Windows ([#13141](https://github.com/crystal-lang/crystal/pull/13141), thanks @HertzDevil) +- Implement `File.readlink` on Windows ([#13195](https://github.com/crystal-lang/crystal/pull/13195), thanks @HertzDevil) + +### LLVM + +- **(breaking-change)** Drop support for LLVM < 8 ([#12906](https://github.com/crystal-lang/crystal/pull/12906), thanks @straight-shoota) +- **(breaking-change)** Support LLVM 15 ([#13173](https://github.com/crystal-lang/crystal/pull/13173), thanks @HertzDevil) +- Error when `find-llvm-config` is unsuccessful ([#13045](https://github.com/crystal-lang/crystal/pull/13045), thanks @straight-shoota) +- Remove `LibLLVM.has_constant?(:AttributeRef)` checks ([#13162](https://github.com/crystal-lang/crystal/pull/13162), thanks @HertzDevil) +- Refactor `LLVM::Attribute#each_kind` to use `Enum#each` ([#13234](https://github.com/crystal-lang/crystal/pull/13234), thanks @straight-shoota) + +### Networking + +- Fix socket specs when network not available ([#12961](https://github.com/crystal-lang/crystal/pull/12961), thanks @straight-shoota) +- Fix wrong default address when binding sockets ([#13006](https://github.com/crystal-lang/crystal/pull/13006), thanks @etra0) +- Clarify WebSocket documentation ([#13096](https://github.com/crystal-lang/crystal/pull/13096), thanks @j8r) +- Add `Socket::IPAddress#link_local?` ([#13204](https://github.com/crystal-lang/crystal/pull/13204), thanks @GeopJr) +- Clean up `back\slash.txt` in `HTTP::StaticFileHandler` specs ([#12984](https://github.com/crystal-lang/crystal/pull/12984), thanks @HertzDevil) +- Add `MIME::Multipart.parse(HTTP::Client::Response, &)` ([#12890](https://github.com/crystal-lang/crystal/pull/12890), thanks @straight-shoota) +- Replace `LibC.ntohs` and `htons` with native code ([#13027](https://github.com/crystal-lang/crystal/pull/13027), thanks @HertzDevil) +- Add `OAuth2::Client#make_token_request` returning HTTP response ([#12921](https://github.com/crystal-lang/crystal/pull/12921), thanks @cyangle) +- Use exhaustive case in `HTTP::WebSocket#run` ([#13097](https://github.com/crystal-lang/crystal/pull/13097), thanks @j8r) +- Increase time drift for `HTTP::StaticFileHandler`'s gzip check ([#13138](https://github.com/crystal-lang/crystal/pull/13138), thanks @HertzDevil) +- OpenSSL: use Windows' system root certificate store ([#13187](https://github.com/crystal-lang/crystal/pull/13187), thanks @HertzDevil) +- Handle `Range` requests in `HTTP::StaticFileHandler` ([#12886](https://github.com/crystal-lang/crystal/pull/12886), thanks @jgaskins, @straight-shoota) +- Skip hostname spec if `hostname` command fails ([#12987](https://github.com/crystal-lang/crystal/pull/12987), thanks @Blacksmoke16) +- Fix `Socket#tty?` to `false` on Windows ([#13175](https://github.com/crystal-lang/crystal/pull/13175), thanks @Blacksmoke16) +- Fix `HTTP::Server::Response#reset` for `status_message` ([#13282](https://github.com/crystal-lang/crystal/pull/13282), thanks @straight-shoota) + +### Numeric + +- Define `Math.pw2ceil` for all integer types ([#13127](https://github.com/crystal-lang/crystal/pull/13127), thanks @HertzDevil) +- Workaround for more `Int128`-and-float methods on Windows with LLVM 14+ ([#13218](https://github.com/crystal-lang/crystal/pull/13218), thanks @HertzDevil) +- Fix `Int128`-and-float conversion overflow checks on Windows LLVM 14+ ([#13222](https://github.com/crystal-lang/crystal/pull/13222), thanks @HertzDevil) +- Add `Char.to_i128` and `.to_u128` ([#12958](https://github.com/crystal-lang/crystal/pull/12958), thanks @meatball133) +- Docs: Add references to `Number` collection convenience constructors ([#13020](https://github.com/crystal-lang/crystal/pull/13020), thanks @straight-shoota) +- Docs: Fix examples for `#byte_swap` with different int types ([#13154](https://github.com/crystal-lang/crystal/pull/13154), [#13180](https://github.com/crystal-lang/crystal/pull/13180), thanks @pan, @Blacksmoke16) +- Make `BigRational.new(BigFloat)` exact ([#13295](https://github.com/crystal-lang/crystal/pull/13295), thanks @HertzDevil) + +### Runtime + +- Increase timeout for slow specs ([#13043](https://github.com/crystal-lang/crystal/pull/13043), thanks @straight-shoota) +- Use `Crystal::System.print_error` instead of `LibC.printf` ([#13161](https://github.com/crystal-lang/crystal/pull/13161), thanks @HertzDevil) +- Windows: detect stack overflows on non-main `Fiber`s ([#13220](https://github.com/crystal-lang/crystal/pull/13220), thanks @HertzDevil) +- Add missing require for `Crystal::ThreadLocalValue` ([#13092](https://github.com/crystal-lang/crystal/pull/13092), thanks @Sija) + +### Serialization + +- Remove obsolete error handling in `XPathContext` ([#13038](https://github.com/crystal-lang/crystal/pull/13038), thanks @straight-shoota) +- Fix JSON, YAML `use_*_discriminator` for recursive `Serializable::Strict` types ([#13238](https://github.com/crystal-lang/crystal/pull/13238), thanks @HertzDevil) +- Add more specs for `YAML::Any#[]` and `#[]?` ([#11646](https://github.com/crystal-lang/crystal/pull/11646), thanks @straight-shoota) +- Add `from_json` for 128-bit integers ([#13041](https://github.com/crystal-lang/crystal/pull/13041), thanks @straight-shoota) +- Reduce JSON, YAML serializable test types ([#13042](https://github.com/crystal-lang/crystal/pull/13042), thanks @straight-shoota) + +### Specs + +- Format spec results with pretty inspect ([#11635](https://github.com/crystal-lang/crystal/pull/11635), thanks @JamesGood626) +- Spec: Add `--color` option to spec runner ([#12932](https://github.com/crystal-lang/crystal/pull/12932), thanks @straight-shoota) +- Add `Spec::Item#all_tags` ([#12915](https://github.com/crystal-lang/crystal/pull/12915), thanks @compumike) + +### System + +- **(breaking-change)** Add full stub for Windows signals ([#13131](https://github.com/crystal-lang/crystal/pull/13131), thanks @HertzDevil) +- **(breaking-change)** Deprecate and internalize `Process.fork` ([#12934](https://github.com/crystal-lang/crystal/pull/12934), thanks @straight-shoota) +- Fix `Process` spec to wait on started processes ([#12941](https://github.com/crystal-lang/crystal/pull/12941), thanks @straight-shoota) +- Drop privileges in chroot spec ([#13226](https://github.com/crystal-lang/crystal/pull/13226), thanks @straight-shoota) +- Drop deprecated `from_winerror` overload for `flock_*` ([#13039](https://github.com/crystal-lang/crystal/pull/13039), thanks @HertzDevil) +- Add `Process.on_interrupt` ([#13034](https://github.com/crystal-lang/crystal/pull/13034), thanks @HertzDevil) +- Add `Process::Status#to_s` and `#inspect` ([#13044](https://github.com/crystal-lang/crystal/pull/13044), thanks @straight-shoota) +- Add `graceful` parameter to `Process#terminate` ([#13070](https://github.com/crystal-lang/crystal/pull/13070), thanks @HertzDevil) +- Add `Process::ExitReason` and `Process::Status#exit_reason` ([#13052](https://github.com/crystal-lang/crystal/pull/13052), thanks @HertzDevil) +- Implement `File.tempfile` in Crystal ([#12111](https://github.com/crystal-lang/crystal/pull/12111), thanks @straight-shoota) +- `System::User#name`: Fall back to `#username` if unavailable ([#13137](https://github.com/crystal-lang/crystal/pull/13137), thanks @HertzDevil) +- Implement `Process.ppid` on Windows ([#13140](https://github.com/crystal-lang/crystal/pull/13140), thanks @HertzDevil) +- AArch64 Android support ([#13065](https://github.com/crystal-lang/crystal/pull/13065), thanks @HertzDevil) +- Windows 7 support ([#11505](https://github.com/crystal-lang/crystal/pull/11505), thanks @konovod) + +### Text + +- **(breaking-change)** Fix PCRE crashing on invalid UTF-8 ([#13240](https://github.com/crystal-lang/crystal/pull/13240), [#13311](https://github.com/crystal-lang/crystal/pull/13311), [#13313](https://github.com/crystal-lang/crystal/pull/13313), thanks @straight-shoota) +- **(breaking-change)** Switch default regex engine to PCRE2 ([#12978](https://github.com/crystal-lang/crystal/pull/12978), thanks @straight-shoota) +- **(breaking-change)** Add more members to `Regex::Options` ([#13223](https://github.com/crystal-lang/crystal/pull/13223), thanks @straight-shoota) +- **(breaking-change)** Add `Regex::MatchOptions` ([#13248](https://github.com/crystal-lang/crystal/pull/13248), thanks @straight-shoota) +- Fix PCRE2 implementation and tests ([#13105](https://github.com/crystal-lang/crystal/pull/13105), thanks @straight-shoota) +- Remove pending spec for `Path#drive` with IPv6 UNC host names ([#13190](https://github.com/crystal-lang/crystal/pull/13190), thanks @HertzDevil) +- Remove `Regex::PCRE2#finalize` redefinition ([#13309](https://github.com/crystal-lang/crystal/pull/13309), thanks @HertzDevil) +- Clarify behavior of strings with invalid UTF-8 byte sequences ([#13314](https://github.com/crystal-lang/crystal/pull/13314), thanks @HertzDevil) +- Refer to PCRE2 in `Regex`'s summary ([#13318](https://github.com/crystal-lang/crystal/pull/13318), thanks @HertzDevil) + +## Compiler + +- Escape filenames when running `crystal spec` with multiple files ([#12929](https://github.com/crystal-lang/crystal/pull/12929), thanks @HertzDevil) +- Handle ARM64 MSVC paths when cross-compiling on Windows ([#13073](https://github.com/crystal-lang/crystal/pull/13073), thanks @HertzDevil) +- Use relative paths to vendored shards" ([#13315](https://github.com/crystal-lang/crystal/pull/13315), thanks @straight-shoota) + +### Debugger +- Always use 0 for offsets of lib / extern union members ([#13305](https://github.com/crystal-lang/crystal/pull/13305), thanks @HertzDevil) + +### Codegen + +- **(breaking-change)** Support LLVM 15 ([#13173](https://github.com/crystal-lang/crystal/pull/13173), thanks @HertzDevil) +- Remove obsolete functions from `llvm_ext.cc` ([#13177](https://github.com/crystal-lang/crystal/pull/13177), thanks @HertzDevil) + +### Generics + +- Fix type names for generic instances with empty splat type vars ([#13189](https://github.com/crystal-lang/crystal/pull/13189), thanks @HertzDevil) + +### Interpreter + +- Fix: Interpreter `value_to_bool` for module, generic module and generic module metaclass ([#12920](https://github.com/crystal-lang/crystal/pull/12920), thanks @asterite) +- Fix redundant cast in interpreter ([#12996](https://github.com/crystal-lang/crystal/pull/12996), thanks @asterite) +- Dynamic library loader: search in `-L` directories before default ones ([#13069](https://github.com/crystal-lang/crystal/pull/13069), thanks @HertzDevil) +- Simplify expectation of loader spec error messages ([#12858](https://github.com/crystal-lang/crystal/pull/12858), thanks @straight-shoota) +- Add support for 128-bit literals in the interpreter ([#12859](https://github.com/crystal-lang/crystal/pull/12859), thanks @straight-shoota) +- Fix interpreter `value_to_bool` for `NoReturn` ([#13290](https://github.com/crystal-lang/crystal/pull/13290), thanks @straight-shoota) + +### Parser + +- Fix `x @y` and `x @@y` in def parameters when `y` is reserved ([#12922](https://github.com/crystal-lang/crystal/pull/12922), thanks @HertzDevil) +- Disallow empty exponents in number literals ([#12910](https://github.com/crystal-lang/crystal/pull/12910), thanks @HertzDevil) +- Stricter checks for multiple assignment syntax ([#12919](https://github.com/crystal-lang/crystal/pull/12919), thanks @HertzDevil) + +### Semantic + +- Compiler: type declaration with initial value gets the value's type ([#13025](https://github.com/crystal-lang/crystal/pull/13025), thanks @asterite) +- Stricter checks for enum definitions ([#12945](https://github.com/crystal-lang/crystal/pull/12945), thanks @HertzDevil) +- Fix error handling in macro system method when execution fails ([#12893](https://github.com/crystal-lang/crystal/pull/12893), thanks @straight-shoota) +- Add comment for `LiteralExpander` `select` ([#12926](https://github.com/crystal-lang/crystal/pull/12926), thanks @straight-shoota) +- Improve locations of some AST nodes ([#12933](https://github.com/crystal-lang/crystal/pull/12933), thanks @straight-shoota) +- Refactor `SemanticVisitor` tighter `rescue` scope in `Require` visitor ([#12887](https://github.com/crystal-lang/crystal/pull/12887), thanks @straight-shoota) +- Add specs for regex literal expansion ([#13253](https://github.com/crystal-lang/crystal/pull/13253), thanks @straight-shoota) + +## Tools + +- Fix Crystal tool cursor parsing for filenames containing `:` ([#13129](https://github.com/crystal-lang/crystal/pull/13129), thanks @HertzDevil) + +### Formatter + +- Formatter: fix end indent after comment inside begin ([#12994](https://github.com/crystal-lang/crystal/pull/12994), thanks @asterite) +- Parser: remove obsolete handling of `else` inside lib struct ([#13028](https://github.com/crystal-lang/crystal/pull/13028), thanks @HertzDevil) +- Fix formatter empty array literal with comment on extra line ([#12907](https://github.com/crystal-lang/crystal/pull/12907), thanks @straight-shoota) +- Fix formatter comment on extra line at end of method args ([#12908](https://github.com/crystal-lang/crystal/pull/12908), thanks @straight-shoota) +- Fix formatter not merge consecutive but separated comment lines ([#12909](https://github.com/crystal-lang/crystal/pull/12909), thanks @straight-shoota) +- Formatter: add `(&)` to param-less yielding defs before comment line ([#13126](https://github.com/crystal-lang/crystal/pull/13126), thanks @HertzDevil) +- Formatter: add `&` to yielding methods without a block parameter ([#12951](https://github.com/crystal-lang/crystal/pull/12951), thanks @HertzDevil) +- Formatter: Add feature flag for `method_signature_yield` ([#13215](https://github.com/crystal-lang/crystal/pull/13215), thanks @straight-shoota) +- Macro interpolation: add `&` to yielding `Def`s without a block parameter ([#12952](https://github.com/crystal-lang/crystal/pull/12952), thanks @HertzDevil) + +## Infrastructure + +- Fix `bin/crystal` print no error message when `crystal` is missing ([#12981](https://github.com/crystal-lang/crystal/pull/12981), thanks @straight-shoota) +- Prevent infinitely recursive wrapper script ([#11712](https://github.com/crystal-lang/crystal/pull/11712), thanks @ThunderKey) +- Changelog helper: Report error from HTTP request ([#13011](https://github.com/crystal-lang/crystal/pull/13011), thanks @straight-shoota) +- Fix wrapper script to handle `CRYSTAL` variable pointing to itself ([#13032](https://github.com/crystal-lang/crystal/pull/13032), thanks @straight-shoota) +- Propagate exit code correctly in Windows wrapper batch script ([#13048](https://github.com/crystal-lang/crystal/pull/13048), thanks @HertzDevil) +- Remove `__declspec(dllimport)` from Windows libiconv build ([#13219](https://github.com/crystal-lang/crystal/pull/13219), thanks @HertzDevil) +- Update previous Crystal release - 1.7.0 ([#12925](https://github.com/crystal-lang/crystal/pull/12925), thanks @straight-shoota) +- [CI] Remove `verbose=1` in `test_llvm` ([#12931](https://github.com/crystal-lang/crystal/pull/12931), thanks @straight-shoota) +- Missing quotes in Wrapper Script ([#12955](https://github.com/crystal-lang/crystal/pull/12955), thanks @stellarpower) +- Makefile: refactor test recipe ([#12979](https://github.com/crystal-lang/crystal/pull/12979), thanks @straight-shoota) +- Merge release branch for 1.7 into master ([#12998](https://github.com/crystal-lang/crystal/pull/12998), thanks @straight-shoota) +- Update previous Crystal release - 1.7.2 ([#13001](https://github.com/crystal-lang/crystal/pull/13001), thanks @straight-shoota) +- Update distribution-scripts ([#13051](https://github.com/crystal-lang/crystal/pull/13051), [#13068](https://github.com/crystal-lang/crystal/pull/13068), [#13188](https://github.com/crystal-lang/crystal/pull/13188), [#13213](https://github.com/crystal-lang/crystal/pull/13213), [#13298](https://github.com/crystal-lang/crystal/pull/13298), thanks @straight-shoota) +- [CI] Use Ubuntu 22.04 base image for LLVM tests ([#13035](https://github.com/crystal-lang/crystal/pull/13035), thanks @straight-shoota) +- Add instructions for other repos to pre-commit hook ([#10535](https://github.com/crystal-lang/crystal/pull/10535), thanks @straight-shoota) +- Makefile: Add `./scripts` to `format` recipe ([#13064](https://github.com/crystal-lang/crystal/pull/13064), thanks @straight-shoota) +- Crystal wrapper script enhancements ([#12959](https://github.com/crystal-lang/crystal/pull/12959), thanks @j8r) +- Fix sed command in `scripts/update-distribution-scripts.cr` ([#13071](https://github.com/crystal-lang/crystal/pull/13071), thanks @straight-shoota) +- Update GH Actions ([#13075](https://github.com/crystal-lang/crystal/pull/13075), [#13132](https://github.com/crystal-lang/crystal/pull/13132), thanks @renovate) +- CI: Enable testing with `libpcre2` on wasm32 ([#13109](https://github.com/crystal-lang/crystal/pull/13109), thanks @lbguilherme) +- Build the compiler with PCRE2 ([#13084](https://github.com/crystal-lang/crystal/pull/13084), [#13133](https://github.com/crystal-lang/crystal/pull/13133), thanks @straight-shoota) +- Prefer matching `llvm-config` in `find-llvm-config` ([#13087](https://github.com/crystal-lang/crystal/pull/13087), thanks @straight-shoota) +- **(performance)** Run compiler specs in release mode ([#13122](https://github.com/crystal-lang/crystal/pull/13122), thanks @straight-shoota) +- [CI] Increase `no_output_timeout` on circleci ([#13151](https://github.com/crystal-lang/crystal/pull/13151), thanks @straight-shoota) +- Update NOTICE.md ([#13159](https://github.com/crystal-lang/crystal/pull/13159), thanks @HertzDevil) +- Merge `release/1.7`@1.7.3 ([#13168](https://github.com/crystal-lang/crystal/pull/13168), thanks @straight-shoota) +- [CI] Cancel in-progress jobs when another commit is pushed ([#13179](https://github.com/crystal-lang/crystal/pull/13179), thanks @Blacksmoke16) +- Mute shell comments in Makefile ([#13201](https://github.com/crystal-lang/crystal/pull/13201), thanks @straight-shoota) +- Update previous Crystal release - 1.7.3 ([#13167](https://github.com/crystal-lang/crystal/pull/13167), thanks @straight-shoota) +- [CI] Remove cross-compiliation on Windows ([#13207](https://github.com/crystal-lang/crystal/pull/13207), thanks @straight-shoota) +- [CI] Increase `no_output_timeout` on circleci (cont.) ([#13185](https://github.com/crystal-lang/crystal/pull/13185), thanks @straight-shoota) +- [CI] Update Windows job to LLVM 15 ([#13208](https://github.com/crystal-lang/crystal/pull/13208), thanks @straight-shoota) +- Clean up `.gitignore` ([#13241](https://github.com/crystal-lang/crystal/pull/13241), thanks @straight-shoota) +- [CI] Extract LLVM tests in separate workflow ([#13246](https://github.com/crystal-lang/crystal/pull/13246), thanks @straight-shoota) +- [CI] Extract interpreter workflow and split `std_spec` execution ([#13267](https://github.com/crystal-lang/crystal/pull/13267), thanks @straight-shoota) +- Avoid `test.cr` in root of repo conflicting with parser warning specs ([#13259](https://github.com/crystal-lang/crystal/pull/13259), thanks @Blacksmoke16) +- Fix `bin/crystal` in symlink working directory ([#13281](https://github.com/crystal-lang/crystal/pull/13281), thanks @straight-shoota) +- Fix `bin/crystal` when no global `crystal` command is installed ([#13286](https://github.com/crystal-lang/crystal/pull/13286), thanks @straight-shoota) +- Makefile: Add `interpreter_spec` ([#13251](https://github.com/crystal-lang/crystal/pull/13251), thanks @straight-shoota) +- Makefile: Add `all` target as default before including `Makfile.local` ([#13276](https://github.com/crystal-lang/crystal/pull/13276), thanks @straight-shoota) +- Update shards 0.17.3 ([#13296](https://github.com/crystal-lang/crystal/pull/13296), thanks @straight-shoota) + +## Other + +- Do not match expectations outside specs ([#13079](https://github.com/crystal-lang/crystal/pull/13079), thanks @HertzDevil) +- Enable or fix specs that already work on Windows ([#13186](https://github.com/crystal-lang/crystal/pull/13186), thanks @HertzDevil) + # 1.7.3 ## Standard Library diff --git a/src/VERSION b/src/VERSION index 0ef074f2eca2..27f9cd322bb9 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.8.0-dev +1.8.0 From cdba0ab330f01d308a7d4b69e216aa35d11c3c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 17 Apr 2023 22:31:01 +0200 Subject: [PATCH 0439/1551] Update previous Crystal release - 1.8.0 (#13322) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 6 +++--- .github/workflows/linux.yml | 2 ++ .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 15 +++++++-------- .github/workflows/regex-engine.yml | 8 ++++---- .github/workflows/wasm32.yml | 2 +- .github/workflows/win.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ src/VERSION | 2 +- 11 files changed, 30 insertions(+), 29 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 28691e8b9a78..c672cad87847 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.7.3/crystal-1.7.3-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.8.0/crystal-1.8.0-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 1abb3cb16233..45eace56968f 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.7.3-build + image: crystallang/crystal:1.8.0-build name: "Test Interpreter" steps: - uses: actions/checkout@v3 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.7.3-build + image: crystallang/crystal:1.8.0-build name: Build interpreter steps: - uses: actions/checkout@v3 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.7.3-build + image: crystallang/crystal:1.8.0-build strategy: matrix: part: [0, 1, 2, 3] diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4f1344880c8c..3070efd70171 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -29,6 +29,8 @@ jobs: flags: "FLAGS=-Dwithout_ffi USE_PCRE1=true" - crystal_bootstrap_version: 1.7.3 flags: "" + - crystal_bootstrap_version: 1.8.0 + flags: "" steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index d6f59d3f83e4..2b86b41542b0 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -43,7 +43,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.7.3" + crystal: "1.8.0" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 52df94eb93a0..c572dafae072 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.7.3-alpine + container: crystallang/crystal:1.8.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -19,7 +19,7 @@ jobs: - name: Upgrade alpine-keys run: apk upgrade alpine-keys - name: Install openssl 3.0 - run: apk add "openssl3-dev" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.15/main + run: apk add "openssl-dev=~3.0" - name: Check LibSSL version run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs @@ -27,12 +27,14 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.7.3-alpine + container: crystallang/crystal:1.8.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 + - name: Uninstall openssl + run: apk del openssl-dev - name: Install openssl 1.1.1 - run: apk add "openssl-dev=~1.1.1" + run: apk add "openssl1.1-compat-dev=~1.1.1" - name: Check LibSSL version run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs @@ -40,7 +42,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.7.3-alpine + container: crystallang/crystal:1.8.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -50,9 +52,6 @@ jobs: run: apk upgrade alpine-keys - name: Install libressl 3.4 run: apk add "libressl-dev=~3.4" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.15/community - # We need a recent libc which include reallocarray - - name: Upgrade musl-dev - run: apk add "musl-dev=~1.2" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.15/main - name: Check LibSSL version run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index 34bab7108845..e32c8e67f6d6 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,10 +10,12 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.7.3-alpine + container: crystallang/crystal:1.8.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 + - name: Remove PCRE2 + run: apk del pcre2-dev - name: Assert using PCRE run: bin/crystal eval 'abort unless Regex::Engine == Regex::PCRE' - name: Run Regex specs @@ -21,12 +23,10 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.7.3-alpine + container: crystallang/crystal:1.8.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 - - name: Install PCRE2 - run: apk add pcre2-dev - name: Assert using PCRE2 run: bin/crystal eval 'abort unless Regex::Engine == Regex::PCRE2' - name: Assert select PCRE diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index eb603f8f746c..db451ef6933e 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.7.3-build + container: crystallang/crystal:1.8.0-build steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 82436c50e992..344b0af94d0d 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -19,7 +19,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.7.3" + crystal: "1.8.0" - name: Download Crystal source uses: actions/checkout@v3 diff --git a/bin/ci b/bin/ci index f416e3751bf1..b734c2e4c04f 100755 --- a/bin/ci +++ b/bin/ci @@ -134,8 +134,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.7.3/crystal-1.7.3-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.7.3-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.8.0/crystal-1.8.0-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.8.0-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -188,7 +188,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.7.3}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.8.0}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 27c64ea7af8e..3b844ba588bc 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.7.3/crystal-1.7.3-1-darwin-universal.tar.gz"; - sha256 = "sha256:1jc3fdc36mpvh7zahszbij02c0nxhmmbpfjcpd890bapj2q4jkkr"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.8.0/crystal-1.8.0-1-darwin-universal.tar.gz"; + sha256 = "sha256:1rwcx9bxxhfyw6zqd14drrqzhk72932zv2jgbrzrpyfmk2i63gng"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.7.3/crystal-1.7.3-1-darwin-universal.tar.gz"; - sha256 = "sha256:1jc3fdc36mpvh7zahszbij02c0nxhmmbpfjcpd890bapj2q4jkkr"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.8.0/crystal-1.8.0-1-darwin-universal.tar.gz"; + sha256 = "sha256:1rwcx9bxxhfyw6zqd14drrqzhk72932zv2jgbrzrpyfmk2i63gng"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.7.3/crystal-1.7.3-1-linux-x86_64.tar.gz"; - sha256 = "sha256:0diq6i760yd0rv310f80v60m015f5xkni7h60phspvmyy0yw9jv0"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.8.0/crystal-1.8.0-1-linux-x86_64.tar.gz"; + sha256 = "sha256:0bnzbqil7avndz7qyijzixy4jsp3lk8dvyh6j7qarvjmrz5lj7m9"; }; }.${pkgs.stdenv.system}); diff --git a/src/VERSION b/src/VERSION index 27f9cd322bb9..b57588e592f8 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.8.0 +1.9.0-dev From 37f392d5d36f3adedfe7bbc0f4453700a569c053 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Tue, 18 Apr 2023 04:30:01 -0400 Subject: [PATCH 0440/1551] Add macro `#warning` method (#13262) --- spec/compiler/macro/macro_methods_spec.cr | 24 +++++++++++++++++++++++ src/compiler/crystal/macros.cr | 13 ++++++++++-- src/compiler/crystal/macros/methods.cr | 20 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index f18f9edb7aa5..295a2548bab4 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1694,6 +1694,18 @@ module Crystal end end + describe "#warning" do + it "emits a warning at a specific node" do + assert_warning <<-CRYSTAL, "Oh noes" + macro test(node) + {% node.warning "Oh noes" %} + end + + test 10 + CRYSTAL + end + end + it "executes instance_vars" do assert_macro("{{x.instance_vars.map &.stringify}}", %(["bytesize", "length", "c"])) do |program| {x: TypeNode.new(program.string)} @@ -3066,6 +3078,18 @@ module Crystal assert_macro %({{compare_versions("1.10.3", "1.2.3")}}), %(1) end + describe "#warning" do + it "emits a top level warning" do + assert_warning <<-CRYSTAL, "Oh noes" + macro test + {% warning "Oh noes" %} + end + + test + CRYSTAL + end + end + describe "#parse_type" do it "path" do assert_type(%[class Bar; end; {{ parse_type("Bar").is_a?(Path) ? 1 : 'a'}}]) { int32 } diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 8b5c6845c0b2..5f4268d36d09 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -228,6 +228,10 @@ module Crystal::Macros def raise(message) : NoReturn end + # Emits a compile-time warning with the given *message*. + def warning(message : StringLiteral) : NilLiteral + end + # Returns `true` if the given *filename* exists, `false` otherwise. def file_exists?(filename) : BoolLiteral end @@ -413,11 +417,16 @@ module Crystal::Macros def !=(other : ASTNode) : BoolLiteral end - # Gives a compile-time error with the given *message*. This will - # highlight this node in the error message. + # Gives a compile-time error with the given *message*. + # This will highlight this node in the error message. def raise(message) : NoReturn end + # Emits a compile-time warning with the given *message*. + # This will highlight this node in the warning message. + def warning(message : StringLiteral) : NilLiteral + end + # Returns `true` if this node's type is the given *type* or any of its # subclasses. # diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 6894d3d60c15..00557567861f 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -69,6 +69,8 @@ module Crystal interpret_system(node) when "raise" interpret_raise(node) + when "warning" + interpret_warning(node) when "file_exists?" interpret_file_exists?(node) when "read_file" @@ -255,6 +257,10 @@ module Crystal macro_raise(node, node.args, self) end + def interpret_warning(node) + macro_warning(node, node.args, self) + end + def interpret_file_exists?(node) interpret_check_args_toplevel do |arg| arg.accept self @@ -382,6 +388,8 @@ module Crystal interpret_check_args { class_name } when "raise" macro_raise self, args, interpreter + when "warning" + macro_warning self, args, interpreter when "filename" interpret_check_args do filename = location.try &.original_filename @@ -2678,6 +2686,18 @@ private def macro_raise(node, args, interpreter) node.raise msg, exception_type: Crystal::MacroRaiseException end +private def macro_warning(node, args, interpreter) + msg = args.map do |arg| + arg.accept interpreter + interpreter.last.to_macro_id + end + msg = msg.join " " + + interpreter.warnings.add_warning_at(node.location, msg) + + Crystal::NilLiteral.new +end + private def empty_no_return_array Crystal::ArrayLiteral.new(of: Crystal::Path.global("NoReturn")) end From 7f885dd42d98741d3c0a12194646f582a89cd1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Tue, 18 Apr 2023 01:30:23 -0700 Subject: [PATCH 0441/1551] Crystal lexer cleanup (#13270) --- src/compiler/crystal/syntax/lexer.cr | 166 +++++++++----------------- src/compiler/crystal/syntax/parser.cr | 2 +- src/compiler/crystal/syntax/token.cr | 4 - 3 files changed, 60 insertions(+), 112 deletions(-) diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 92e4f1377f01..98d2e4a7d2dc 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -117,7 +117,6 @@ module Crystal if char_sequence?('<', 'l', 'o', 'c', ':', column_increment: false) next_char_no_column_increment consume_loc_pragma - start = current_pos else if @doc_enabled && @comment_is_doc consume_doc @@ -165,15 +164,13 @@ module Crystal unknown_token end when '\n' - @token.type = :NEWLINE - next_char + next_char :NEWLINE incr_line_number reset_regex_flags = false consume_newlines when '\r' if next_char == '\n' - next_char - @token.type = :NEWLINE + next_char :NEWLINE incr_line_number reset_regex_flags = false consume_newlines @@ -219,7 +216,7 @@ module Crystal when '=' next_char :OP_LT_LT_EQ when '-' - consume_heredoc_start + consume_heredoc_start(start) else @token.type = :OP_LT_LT end @@ -370,8 +367,7 @@ module Crystal when '(' then next_char :OP_LPAREN when ')' then next_char :OP_RPAREN when '{' - char = next_char - case char + case next_char when '%' next_char :OP_LCURLY_PERCENT when '{' @@ -492,7 +488,6 @@ module Crystal @token.type = :OP_CARET end when '\'' - start = current_pos line = @line_number column = @column_number @token.type = :CHAR @@ -552,28 +547,21 @@ module Crystal when '0'..'9' scan_number start when '@' - start = current_pos case next_char when '[' next_char :OP_AT_LSQUARE + when '@' + next_char + consume_variable :CLASS_VAR, start else - if current_char == '@' - next_char - consume_variable :CLASS_VAR, start - else - consume_variable :INSTANCE_VAR, start - end + consume_variable :INSTANCE_VAR, start end when '$' - start = current_pos - next_char - case current_char + case next_char when '~' - next_char - @token.type = :OP_DOLLAR_TILDE + next_char :OP_DOLLAR_TILDE when '?' - next_char - @token.type = :OP_DOLLAR_QUESTION + next_char :OP_DOLLAR_QUESTION when .ascii_number? start = current_pos if current_char == '0' @@ -600,15 +588,13 @@ module Crystal return check_ident_or_keyword(:alias, start) end when 's' - peek = peek_next_char - case peek + case peek_next_char when 'm' next_char return check_ident_or_keyword(:asm, start) when '?' next_char - next_char - @token.type = :IDENT + next_char :IDENT @token.value = :as_question return @token else @@ -738,8 +724,7 @@ module Crystal # scan_ident end else - next_char - @token.type = :IDENT + next_char :IDENT @token.value = :in return @token end @@ -1001,32 +986,28 @@ module Crystal when 'D' if char_sequence?('I', 'R', '_', '_') unless ident_part_or_end?(peek_next_char) - next_char - @token.type = :MAGIC_DIR + next_char :MAGIC_DIR return @token end end when 'E' if char_sequence?('N', 'D', '_', 'L', 'I', 'N', 'E', '_', '_') unless ident_part_or_end?(peek_next_char) - next_char - @token.type = :MAGIC_END_LINE + next_char :MAGIC_END_LINE return @token end end when 'F' if char_sequence?('I', 'L', 'E', '_', '_') unless ident_part_or_end?(peek_next_char) - next_char - @token.type = :MAGIC_FILE + next_char :MAGIC_FILE return @token end end when 'L' if char_sequence?('I', 'N', 'E', '_', '_') unless ident_part_or_end?(peek_next_char) - next_char - @token.type = :MAGIC_LINE + next_char :MAGIC_LINE return @token end end @@ -1043,7 +1024,6 @@ module Crystal scan_ident(start) else if current_char.ascii_uppercase? - start = current_pos while ident_part?(next_char) # Nothing to do end @@ -1085,14 +1065,10 @@ module Crystal end def consume_doc - char = current_char - start_pos = current_pos - # Ignore first whitespace after comment, like in `# some doc` - if char == ' ' - char = next_char - start_pos = current_pos - end + next_char if current_char == ' ' + + start_pos = current_pos skip_comment @@ -1114,8 +1090,7 @@ module Crystal def consume_whitespace start_pos = current_pos - @token.type = :SPACE - next_char + next_char :SPACE while true case current_char when ' ', '\t' @@ -1170,8 +1145,7 @@ module Crystal if ident_part_or_end?(peek_next_char) scan_ident(start) else - next_char - @token.type = :IDENT + next_char :IDENT @token.value = keyword end @token @@ -1458,8 +1432,7 @@ module Crystal @token.delimiter_state = delimiter_state.with_open_count_delta(-1) end when string_nest - next_char - @token.type = :STRING + next_char :STRING @token.value = string_nest.to_s @token.delimiter_state = delimiter_state.with_open_count_delta(+1) when '\\' @@ -1467,8 +1440,7 @@ module Crystal if delimiter_state.kind.regex? char = next_char raise_unterminated_quoted delimiter_state if char == '\0' - next_char - @token.type = :STRING + next_char :STRING if char == '/' || char.ascii_whitespace? @token.value = char.to_s else @@ -1502,21 +1474,18 @@ module Crystal string_token_escape_value "\e" when 'x' value = consume_string_hex_escape - next_char - @token.type = :STRING + next_char :STRING @token.value = String.new(1) do |buffer| buffer[0] = value {1, 0} end when 'u' value = consume_string_unicode_escape - next_char - @token.type = :STRING + next_char :STRING @token.value = value when '0', '1', '2', '3', '4', '5', '6', '7' value = consume_octal_escape(char) - next_char - @token.type = :STRING + next_char :STRING @token.value = String.new(1) do |buffer| buffer[0] = value {1, 0} @@ -1560,16 +1529,13 @@ module Crystal if delimiter_state.allow_escapes if peek_next_char == '{' next_char - next_char - @token.type = :INTERPOLATION_START + next_char :INTERPOLATION_START else - next_char - @token.type = :STRING + next_char :STRING @token.value = "#" end else - next_char - @token.type = :STRING + next_char :STRING @token.value = "#" end when '\r', '\n' @@ -1732,8 +1698,7 @@ module Crystal if current_char == '\\' && peek_next_char == '%' beginning_of_line = false next_char - next_char - @token.type = :MACRO_LITERAL + next_char :MACRO_LITERAL @token.value = "%" @token.macro_state = Token::MacroState.new(whitespace, nest, control_nest, delimiter_state, beginning_of_line, yields, comment, heredocs) @token.raw = "%" @@ -1744,14 +1709,12 @@ module Crystal case next_char when '{' beginning_of_line = false - next_char - @token.type = :MACRO_EXPRESSION_START + next_char :MACRO_EXPRESSION_START @token.macro_state = Token::MacroState.new(whitespace, nest, control_nest, delimiter_state, beginning_of_line, yields, comment, heredocs) return @token when '%' beginning_of_line = false - next_char - @token.type = :MACRO_CONTROL_START + next_char :MACRO_CONTROL_START @token.macro_state = Token::MacroState.new(whitespace, nest, control_nest, delimiter_state, beginning_of_line, yields, comment, heredocs) return @token else @@ -1842,8 +1805,7 @@ module Crystal when 'd' if whitespace && !ident_part_or_end?(peek_next_char) && peek_next_char != ':' if nest == 0 && control_nest == 0 - next_char - @token.type = :MACRO_END + next_char :MACRO_END @token.macro_state = Token::MacroState.default return @token else @@ -2089,7 +2051,7 @@ module Crystal when 'f' char_sequence?('u', 'n') && peek_not_ident_part_or_end_next_char && :fun when 'i' - beginning_of_line && next_char == 'f' && (char = next_char) && (!ident_part_or_end?(char) && :if) + beginning_of_line && next_char == 'f' && peek_not_ident_part_or_end_next_char && :if when 'l' char_sequence?('i', 'b') && peek_not_ident_part_or_end_next_char && :lib when 'm' @@ -2134,17 +2096,14 @@ module Crystal has_single_quote = false found_closing_single_quote = false - char = next_char - start_here = current_pos - - if char == '\'' + if next_char == '\'' has_single_quote = true - char = next_char - start_here = current_pos + next_char end - return nil unless ident_part?(char) + return nil unless ident_part?(current_char) + start_here = current_pos end_here = 0 while true @@ -2165,18 +2124,18 @@ module Crystal # ok when char == '\0' return nil - else - if char == '\'' && has_single_quote + when has_single_quote + if char == '\'' found_closing_single_quote = true end_here = current_pos next_char break - elsif has_single_quote - # wait until another quote else - end_here = current_pos - break + # wait until another quote end + else + end_here = current_pos + break end end @@ -2306,8 +2265,7 @@ module Crystal end def string_token_escape_value(value) - next_char - @token.type = :STRING + next_char :STRING @token.value = value end @@ -2334,8 +2292,7 @@ module Crystal if current_char == @token.delimiter_state.end @token.raw = current_char.to_s if @wants_raw - next_char - @token.type = :STRING_ARRAY_END + next_char :STRING_ARRAY_END return @token end @@ -2499,25 +2456,20 @@ module Crystal end end - private def consume_heredoc_start - start = current_pos - 2 - + private def consume_heredoc_start(start) has_single_quote = false found_closing_single_quote = false - char = next_char - start_here = current_pos - - if char == '\'' + if next_char == '\'' has_single_quote = true - char = next_char - start_here = current_pos + next_char end - unless ident_part?(char) + unless ident_part?(current_char) raise "heredoc identifier starts with invalid character" end + start_here = current_pos end_here = 0 while true @@ -2538,18 +2490,18 @@ module Crystal # ok when char == '\0' raise "Unexpected EOF on heredoc identifier" - else - if char == '\'' && has_single_quote + when has_single_quote + if char == '\'' found_closing_single_quote = true end_here = current_pos next_char break - elsif has_single_quote - # wait until another quote else - end_here = current_pos - break + # wait until another quote end + else + end_here = current_pos + break end end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 01b58a68fc81..68d51731115f 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -2914,7 +2914,7 @@ module Crystal skip_statement_end return true else - unexpected_token "expecting ',', ';' or '\n'" + unexpected_token "expecting ',', ';' or '\\n'" end end false diff --git a/src/compiler/crystal/syntax/token.cr b/src/compiler/crystal/syntax/token.cr index db85b0f697a7..125ec14ee589 100644 --- a/src/compiler/crystal/syntax/token.cr +++ b/src/compiler/crystal/syntax/token.cr @@ -353,10 +353,6 @@ module Crystal def location=(@location) end - def token?(token) - @type.token? && @value == token - end - def keyword? @type.ident? && @value.is_a?(Keyword) end From 70bf29ea2ee12f6d43a362a017dc1741755bbfde Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 18 Apr 2023 16:30:35 +0800 Subject: [PATCH 0442/1551] Optimize `Array#concat(Indexable)` (#13280) --- spec/std/array_spec.cr | 14 ++++++++++++++ src/array.cr | 23 +++++++++++++++++++++-- src/deque.cr | 33 ++++++++++++++++++--------------- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/spec/std/array_spec.cr b/spec/std/array_spec.cr index f465b49684ff..57e2e27c4334 100644 --- a/spec/std/array_spec.cr +++ b/spec/std/array_spec.cr @@ -559,6 +559,20 @@ describe "Array" do a.@capacity.should eq(6) end + it "concats indexable" do + a = [1, 2, 3] + a.concat(Slice.new(97) { |i| i + 4 }) + a.should eq((1..100).to_a) + + a = [1, 2, 3] + a.concat(StaticArray(Int32, 97).new { |i| i + 4 }) + a.should eq((1..100).to_a) + + a = [1, 2, 3] + a.concat(Deque.new(97) { |i| i + 4 }) + a.should eq((1..100).to_a) + end + it "concats a union of arrays" do a = [1, '2'] a.concat([3] || ['4']) diff --git a/src/array.cr b/src/array.cr index 71dc841de1fe..f243d4a674e4 100644 --- a/src/array.cr +++ b/src/array.cr @@ -746,18 +746,37 @@ class Array(T) # ary.concat(["c", "d"]) # ary # => ["a", "b", "c", "d"] # ``` - def concat(other : Array) : self + def concat(other : Indexable) : self other_size = other.size resize_if_cant_insert(other_size) - (@buffer + @size).copy_from(other.to_unsafe, other_size) + concat_indexable(other) @size += other_size self end + private def concat_indexable(other : Array | Slice | StaticArray) + (@buffer + @size).copy_from(other.to_unsafe, other.size) + end + + private def concat_indexable(other : Deque) + ptr = @buffer + @size + Deque.half_slices(other) do |slice| + ptr.copy_from(slice.to_unsafe, slice.size) + ptr += slice.size + end + end + + private def concat_indexable(other) + appender = (@buffer + @size).appender + other.each do |elem| + appender << elem + end + end + # :ditto: def concat(other : Enumerable) : self left_before_resize = remaining_capacity - @size diff --git a/src/deque.cr b/src/deque.cr index 938f60d993af..2a2802dcfa95 100644 --- a/src/deque.cr +++ b/src/deque.cr @@ -20,6 +20,7 @@ class Deque(T) @start = 0 protected setter size protected getter buffer + protected getter capacity # Creates a new empty Deque def initialize @@ -150,8 +151,8 @@ class Deque(T) # Removes all elements from `self`. def clear - halfs do |r| - (@buffer + r.begin).clear(r.end - r.begin) + Deque.half_slices(self) do |slice| + slice.to_unsafe.clear(slice.size) end @size = 0 @start = 0 @@ -339,9 +340,9 @@ class Deque(T) # # Do not modify the deque while using this variant of `each`! def each(& : T ->) : Nil - halfs do |r| - r.each do |i| - yield @buffer[i] + Deque.half_slices(self) do |slice| + slice.each do |elem| + yield elem end end end @@ -564,20 +565,22 @@ class Deque(T) self end - private def halfs(&) + # :nodoc: + def self.half_slices(deque : Deque, &) # For [----] yields nothing - # For contiguous [-012] yields 1...4 - # For separated [234---01] yields 6...8, 0...3 + # For contiguous [-012] yields @buffer[1...4] + # For separated [234---01] yields @buffer[6...8], @buffer[0...3] - return if empty? - a = @start - b = @start + size - b -= @capacity if b > @capacity + return if deque.empty? + a = deque.@start + b = deque.@start + deque.size + b -= deque.capacity if b > deque.capacity if a < b - yield a...b + # TODO: this `typeof` is a workaround for 1.0.0; remove it eventually + yield Slice(typeof(deque.buffer.value)).new(deque.buffer + a, deque.size) else - yield a...@capacity - yield 0...b + yield Slice(typeof(deque.buffer.value)).new(deque.buffer + a, deque.capacity - a) + yield Slice(typeof(deque.buffer.value)).new(deque.buffer, b) end end From e96f24902d6f498411d879c7c70da66f1d58372a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 18 Apr 2023 18:45:46 +0800 Subject: [PATCH 0443/1551] Handle NaNs when comparing `BigInt` against `Float` (#13293) --- spec/std/big/big_int_spec.cr | 13 +++++++++++++ src/big/big_int.cr | 7 ++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr index bd79aabb5071..f852b795221b 100644 --- a/spec/std/big/big_int_spec.cr +++ b/spec/std/big/big_int_spec.cr @@ -61,6 +61,19 @@ describe "BigInt" do 1.1.should_not eq(1.to_big_i) [1.1, 1.to_big_i, 3.to_big_i, 2.2].sort.should eq([1, 1.1, 2.2, 3]) + + (1.to_big_i <=> Float64::NAN).should be_nil + (1.to_big_i <=> Float32::NAN).should be_nil + (Float64::NAN <=> 1.to_big_i).should be_nil + (Float32::NAN <=> 1.to_big_i).should be_nil + + typeof(1.to_big_i <=> Float64::NAN).should eq(Int32?) + typeof(1.to_big_i <=> Float32::NAN).should eq(Int32?) + typeof(Float64::NAN <=> 1.to_big_i).should eq(Int32?) + typeof(Float32::NAN <=> 1.to_big_i).should eq(Int32?) + + typeof(1.to_big_i <=> 1.to_big_f).should eq(Int32) + typeof(1.to_big_f <=> 1.to_big_i).should eq(Int32) end it "divides and calculates the modulo" do diff --git a/src/big/big_int.cr b/src/big/big_int.cr index b40ea4670b70..5c3bf7e2ef65 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -121,8 +121,8 @@ struct BigInt < Int end end - def <=>(other : Float) - LibGMP.cmp_d(mpz, other) + def <=>(other : Float::Primitive) + LibGMP.cmp_d(mpz, other) unless other.nan? end def +(other : BigInt) : BigInt @@ -811,7 +811,8 @@ struct Float include Comparable(BigInt) def <=>(other : BigInt) - -(other <=> self) + cmp = other <=> self + -cmp if cmp end # Returns a `BigInt` representing this float (rounded using `floor`). From c23c906add6f2515a13b5bf70f672e5868ecdf72 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 18 Apr 2023 18:46:11 +0800 Subject: [PATCH 0444/1551] Handle NaNs when comparing `BigFloat` against `Float` (#13294) --- spec/std/big/big_float_spec.cr | 16 ++++++++++++++++ src/big/big_float.cr | 21 +++++++++++++++------ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index 50a234ffb408..1c84f8c4aa86 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -87,6 +87,22 @@ describe "BigFloat" do end end + describe "#<=>" do + it "compares against NaNs" do + (1.to_big_f <=> Float64::NAN).should be_nil + (1.to_big_f <=> Float32::NAN).should be_nil + (Float64::NAN <=> 1.to_big_f).should be_nil + (Float32::NAN <=> 1.to_big_f).should be_nil + + typeof(1.to_big_f <=> Float64::NAN).should eq(Int32?) + typeof(1.to_big_f <=> Float32::NAN).should eq(Int32?) + typeof(Float64::NAN <=> 1.to_big_f).should eq(Int32?) + typeof(Float32::NAN <=> 1.to_big_f).should eq(Int32?) + + typeof(1.to_big_f <=> 1.to_big_f).should eq(Int32) + end + end + describe "unary #-" do it do bf = "0.12345".to_big_f diff --git a/src/big/big_float.cr b/src/big/big_float.cr index cc455c7c56cd..0a888086fdd7 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -103,8 +103,8 @@ struct BigFloat < Float LibGMP.mpf_cmp_z(self, other) end - def <=>(other : Float32 | Float64) - LibGMP.mpf_cmp_d(self, other.to_f64) + def <=>(other : Float::Primitive) + LibGMP.mpf_cmp_d(self, other) unless other.nan? end def <=>(other : Number) @@ -387,10 +387,6 @@ end struct Number include Comparable(BigFloat) - def <=>(other : BigFloat) - -(other <=> self) - end - def +(other : BigFloat) other + self end @@ -412,6 +408,19 @@ struct Number end end +struct Int + def <=>(other : BigFloat) + -(other <=> self) + end +end + +struct Float + def <=>(other : BigFloat) + cmp = other <=> self + -cmp if cmp + end +end + class String # Converts `self` to a `BigFloat`. # From fbf23411392231906dc92b31002f64b56f2e8206 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 18 Apr 2023 18:46:21 +0800 Subject: [PATCH 0445/1551] Refactor code for `Deque` buffer resizing (#13257) --- src/deque.cr | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/deque.cr b/src/deque.cr index 2a2802dcfa95..2f8efe6ebd01 100644 --- a/src/deque.cr +++ b/src/deque.cr @@ -364,7 +364,7 @@ class Deque(T) return unshift(value) if index == 0 return push(value) if index == @size - increase_capacity if @size >= @capacity + resize_if_cant_insert rindex = @start + index rindex -= @capacity if rindex >= @capacity @@ -475,7 +475,7 @@ class Deque(T) # a.push 3 # => Deque{1, 2, 3} # ``` def push(value : T) - increase_capacity if @size >= @capacity + resize_if_cant_insert index = @start + @size index -= @capacity if index >= @capacity @buffer[index] = value @@ -557,7 +557,7 @@ class Deque(T) # a.unshift 0 # => Deque{0, 1, 2} # ``` def unshift(value : T) : self - increase_capacity if @size >= @capacity + resize_if_cant_insert @start -= 1 @start += @capacity if @start < 0 @buffer[@start] = value @@ -584,15 +584,30 @@ class Deque(T) end end - private def increase_capacity + private INITIAL_CAPACITY = 4 + + # behaves like `calculate_new_capacity(@capacity + 1)` + private def calculate_new_capacity + return INITIAL_CAPACITY if @capacity == 0 + + @capacity * 2 + end + + # behaves like `resize_if_cant_insert(1)` + private def resize_if_cant_insert + if @size >= @capacity + resize_to_capacity(calculate_new_capacity) + end + end + + private def resize_to_capacity(capacity) + old_capacity, @capacity = @capacity, capacity + unless @buffer - @capacity = 4 @buffer = Pointer(T).malloc(@capacity) return end - old_capacity = @capacity - @capacity *= 2 @buffer = @buffer.realloc(@capacity) finish = @start + @size From 5f5d8ac5e46416f11050ea201e4f0182bb357fdb Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 18 Apr 2023 22:41:01 +0800 Subject: [PATCH 0446/1551] Show PCRE/PCRE2 configuration on CI (#13307) --- .github/workflows/regex-engine.yml | 4 + bin/ci | 1 + scripts/print_regex_config.cr | 139 +++++++++++++++++++++++++++++ spec/std/regex_spec.cr | 3 +- src/regex/lib_pcre.cr | 2 +- 5 files changed, 147 insertions(+), 2 deletions(-) create mode 100755 scripts/print_regex_config.cr diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index e32c8e67f6d6..3ce84de5cf66 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -18,6 +18,8 @@ jobs: run: apk del pcre2-dev - name: Assert using PCRE run: bin/crystal eval 'abort unless Regex::Engine == Regex::PCRE' + - name: Show PCRE config + run: bin/crystal scripts/print_regex_config.cr - name: Run Regex specs run: bin/crystal spec --order=random spec/std/regex* pcre2: @@ -31,5 +33,7 @@ jobs: run: bin/crystal eval 'abort unless Regex::Engine == Regex::PCRE2' - name: Assert select PCRE run: bin/crystal eval -Duse_pcre 'abort unless Regex::Engine == Regex::PCRE' + - name: Show PCRE2 config + run: bin/crystal scripts/print_regex_config.cr - name: Run Regex specs run: bin/crystal spec --order=random spec/std/regex* diff --git a/bin/ci b/bin/ci index b734c2e4c04f..a200626b7226 100755 --- a/bin/ci +++ b/bin/ci @@ -98,6 +98,7 @@ prepare_system() { } build() { + with_build_env 'bin/crystal scripts/print_regex_config.cr' with_build_env 'make std_spec clean threads=1 junit_output=.junit/std_spec.xml' case $ARCH in diff --git a/scripts/print_regex_config.cr b/scripts/print_regex_config.cr new file mode 100755 index 000000000000..3b07121b6eb3 --- /dev/null +++ b/scripts/print_regex_config.cr @@ -0,0 +1,139 @@ +#! /usr/bin/env crystal + +{% if Regex::Engine.resolve.name == "Regex::PCRE2" %} + enum LibPCRE2::BSR : UInt32 + UNICODE = 1 + ANYCRLF = 2 + end + + @[Flags] + enum LibPCRE2::COMPILED_WIDTHS : UInt32 + U8 + U16 + U32 + Unused + end + + enum LibPCRE2::NEWLINE : UInt32 + CR = 1 + LF = 2 + CRLF = 3 + ANY = 4 + ANYCRLF = 5 + NUL = 6 + end + + def config(kind : UInt32.class, what) + where = uninitialized UInt32 + LibPCRE2.config(what, pointerof(where)) + where + end + + def config(kind : Bool.class, what) + config(UInt32, what) != 0 + end + + def config(kind : String.class, what) + len = LibPCRE2.config(what, nil) + if len > 0 + where = Bytes.new(len - 1) + LibPCRE2.config(what, where) + ret = String.new(where) + end + ret.inspect + end + + def config(kind : Enum.class, what) + kind.new(config(UInt32, what)) + end + + puts <<-EOS + Using PCRE2 #{config(String, LibPCRE2::CONFIG_VERSION)} + * PCRE2_CONFIG_BSR: #{config(LibPCRE2::BSR, LibPCRE2::CONFIG_BSR)} + * PCRE2_CONFIG_COMPILED_WIDTHS: #{config(LibPCRE2::COMPILED_WIDTHS, LibPCRE2::CONFIG_COMPILED_WIDTHS)} + * PCRE2_CONFIG_DEPTHLIMIT: #{config(UInt32, LibPCRE2::CONFIG_DEPTHLIMIT)} + * PCRE2_CONFIG_HEAPLIMIT: #{config(UInt32, LibPCRE2::CONFIG_HEAPLIMIT)} + * PCRE2_CONFIG_JIT: #{config(Bool, LibPCRE2::CONFIG_JIT)} + * PCRE2_CONFIG_JITTARGET: #{config(String, LibPCRE2::CONFIG_JITTARGET)} + * PCRE2_CONFIG_LINKSIZE: #{config(UInt32, LibPCRE2::CONFIG_LINKSIZE)} + * PCRE2_CONFIG_MATCHLIMIT: #{config(UInt32, LibPCRE2::CONFIG_MATCHLIMIT)} + * PCRE2_CONFIG_NEVER_BACKSLASH_C: #{config(Bool, LibPCRE2::CONFIG_NEVER_BACKSLASH_C)} + * PCRE2_CONFIG_NEWLINE: #{config(LibPCRE2::NEWLINE, LibPCRE2::CONFIG_NEWLINE)} + * PCRE2_CONFIG_PARENSLIMIT: #{config(UInt32, LibPCRE2::CONFIG_PARENSLIMIT)} + * PCRE2_CONFIG_UNICODE: #{config(Bool, LibPCRE2::CONFIG_UNICODE)} + * PCRE2_CONFIG_UNICODE_VERSION: #{config(String, LibPCRE2::CONFIG_UNICODE_VERSION)} + EOS +{% else %} + enum LibPCRE::BSR : LibC::Int + UNICODE = 0 + ANYCRLF = 1 + end + + enum LibPCRE::NEWLINE : LibC::Int + CR = 0x000d + LF = 0x000a + CRLF = 0x0d0a + ANYCRLF = -2 + ANY = -1 + end + + lib LibPCRE + CONFIG_UTF8 = 0 + CONFIG_NEWLINE = 1 + CONFIG_LINK_SIZE = 2 + CONFIG_POSIX_MALLOC_THRESHOLD = 3 + CONFIG_MATCH_LIMIT = 4 + CONFIG_STACKRECURSE = 5 + CONFIG_UNICODE_PROPERTIES = 6 + CONFIG_MATCH_LIMIT_RECURSION = 7 + CONFIG_BSR = 8 + CONFIG_UTF16 = 10 + CONFIG_JITTARGET = 11 + CONFIG_UTF32 = 12 + CONFIG_PARENS_LIMIT = 13 + end + + def config(kind : LibC::Int.class, what) + where = uninitialized LibC::Int + LibPCRE.config(what, pointerof(where)) + where + end + + def config(kind : LibC::ULong.class, what) + where = uninitialized LibC::ULong + LibPCRE.config(what, pointerof(where)) + where + end + + def config(kind : Bool.class, what) + config(LibC::Int, what) != 0 + end + + def config(kind : String.class, what) + where = uninitialized LibC::Char* + LibPCRE.config(what, pointerof(where)) + (where ? String.new(where) : nil).inspect + end + + def config(kind : Enum.class, what) + kind.new(config(LibC::Int, what)) + end + + puts <<-EOS + Using PCRE #{String.new(LibPCRE.version).inspect} + * PCRE_CONFIG_BSR: #{config(LibPCRE::BSR, LibPCRE::CONFIG_BSR)} + * PCRE_CONFIG_JIT: #{config(Bool, LibPCRE::CONFIG_JIT)} + * PCRE_CONFIG_JITTARGET: #{config(String, LibPCRE::CONFIG_JITTARGET)} + * PCRE_CONFIG_LINK_SIZE: #{config(LibC::Int, LibPCRE::CONFIG_LINK_SIZE)} + * PCRE_CONFIG_PARENS_LIMIT: #{config(LibC::ULong, LibPCRE::CONFIG_PARENS_LIMIT)} + * PCRE_CONFIG_MATCH_LIMIT: #{config(LibC::ULong, LibPCRE::CONFIG_MATCH_LIMIT)} + * PCRE_CONFIG_MATCH_LIMIT_RECURSION: #{config(LibC::ULong, LibPCRE::CONFIG_MATCH_LIMIT_RECURSION)} + * PCRE_CONFIG_NEWLINE: #{config(LibPCRE::NEWLINE, LibPCRE::CONFIG_NEWLINE)} + * PCRE_CONFIG_POSIX_MALLOC_THRESHOLD: #{config(LibC::Int, LibPCRE::CONFIG_POSIX_MALLOC_THRESHOLD)} + * PCRE_CONFIG_STACKRECURSE: #{config(Bool, LibPCRE::CONFIG_STACKRECURSE)} + * PCRE_CONFIG_UTF16: #{config(Bool, LibPCRE::CONFIG_UTF16)} + * PCRE_CONFIG_UTF32: #{config(Bool, LibPCRE::CONFIG_UTF32)} + * PCRE_CONFIG_UTF8: #{config(Bool, LibPCRE::CONFIG_UTF8)} + * PCRE_CONFIG_UNICODE_PROPERTIES: #{config(Bool, LibPCRE::CONFIG_UNICODE_PROPERTIES)} + EOS +{% end %} diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 098c718b2248..ff3338b89842 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -277,7 +277,8 @@ describe "Regex" do str = File.read(datapath("large_single_line_string.txt")) {% if Regex::Engine.resolve.name == "Regex::PCRE" %} - LibPCRE.config LibPCRE::CONFIG_JIT, out jit_enabled + jit_enabled = uninitialized LibC::Int + LibPCRE.config LibPCRE::CONFIG_JIT, pointerof(jit_enabled) pending! "PCRE JIT mode not available." unless 1 == jit_enabled # This match may raise on JIT stack limit or not. If it raises, the error message should be the expected one. diff --git a/src/regex/lib_pcre.cr b/src/regex/lib_pcre.cr index 909f6f810584..5f110eba0ce7 100644 --- a/src/regex/lib_pcre.cr +++ b/src/regex/lib_pcre.cr @@ -84,7 +84,7 @@ lib LibPCRE type Pcre = Void* type PcreExtra = Void* fun compile = pcre_compile(pattern : UInt8*, options : Int, errptr : UInt8**, erroffset : Int*, tableptr : Void*) : Pcre - fun config = pcre_config(what : Int, where : Int*) : Int + fun config = pcre_config(what : Int, where : Void*) : Int fun exec = pcre_exec(code : Pcre, extra : PcreExtra, subject : UInt8*, length : Int, offset : Int, options : Int, ovector : Int*, ovecsize : Int) : Int fun study = pcre_study(code : Pcre, options : Int, errptr : UInt8**) : PcreExtra fun free_study = pcre_free_study(extra : PcreExtra) : Void From b913a0165b3333ad25554c75671e74841d19e8cc Mon Sep 17 00:00:00 2001 From: Chao Yang Date: Tue, 18 Apr 2023 09:41:23 -0500 Subject: [PATCH 0447/1551] Regenerate spec/interpreter_std_spec.cr (#13310) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/interpreter_std_spec.cr | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/spec/interpreter_std_spec.cr b/spec/interpreter_std_spec.cr index cb47c8dc1027..6ca5aeac2ea3 100644 --- a/spec/interpreter_std_spec.cr +++ b/spec/interpreter_std_spec.cr @@ -1,4 +1,4 @@ -# 2022-08-09 01:11:30+00:00 This file is autogenerated by `./spec/generate_interpreter_spec.sh std spec/interpreter_std_spec.cr` +# 2023-04-12 22:54:02-05:00 This file is autogenerated by `spec/generate_interpreter_spec.sh std spec/interpreter_std_spec.cr` require "./std/array_spec.cr" require "./std/atomic_spec.cr" require "./std/base64_spec.cr" @@ -26,22 +26,24 @@ require "./std/compress/zlib/reader_spec.cr" require "./std/compress/zlib/stress_spec.cr" require "./std/compress/zlib/writer_spec.cr" require "./std/concurrent/select_spec.cr" -# require "./std/concurrent_spec.cr" (failed to run) +require "./std/concurrent_spec.cr" require "./std/crypto/bcrypt/base64_spec.cr" require "./std/crypto/bcrypt/password_spec.cr" -# require "./std/crypto/bcrypt_spec.cr" (failed to run) +require "./std/crypto/bcrypt_spec.cr" require "./std/crypto/blowfish_spec.cr" require "./std/crypto/subtle_spec.cr" -# require "./std/crystal/compiler_rt/ashlti3_spec.cr" (failed to run) -# require "./std/crystal/compiler_rt/ashrti3_spec.cr" (failed to run) +require "./std/crystal/compiler_rt/ashlti3_spec.cr" +require "./std/crystal/compiler_rt/ashrti3_spec.cr" require "./std/crystal/compiler_rt/divmod128_spec.cr" -# require "./std/crystal/compiler_rt/fixint_spec.cr" (failed to run) -# require "./std/crystal/compiler_rt/float_spec.cr" (failed to run) -# require "./std/crystal/compiler_rt/lshrti3_spec.cr" (failed to run) +require "./std/crystal/compiler_rt/fixint_spec.cr" +require "./std/crystal/compiler_rt/float_spec.cr" +require "./std/crystal/compiler_rt/lshrti3_spec.cr" require "./std/crystal/compiler_rt/mulodi4_spec.cr" require "./std/crystal/compiler_rt/mulosi4_spec.cr" -# require "./std/crystal/compiler_rt/muloti4_spec.cr" (failed to run) -# require "./std/crystal/compiler_rt/multi3_spec.cr" (failed to run) +require "./std/crystal/compiler_rt/muloti4_spec.cr" +require "./std/crystal/compiler_rt/multi3_spec.cr" +require "./std/crystal/compiler_rt/powidf2_spec.cr" +require "./std/crystal/compiler_rt/powisf2_spec.cr" require "./std/crystal/digest/md5_spec.cr" require "./std/crystal/digest/sha1_spec.cr" require "./std/crystal/hasher_spec.cr" @@ -146,7 +148,6 @@ require "./std/log/log_spec.cr" require "./std/log/main_spec.cr" require "./std/log/metadata_spec.cr" require "./std/log/spec_spec.cr" -require "./std/regex/match_data_spec.cr" require "./std/math_spec.cr" require "./std/mime/media_type_spec.cr" require "./std/mime/multipart/builder_spec.cr" @@ -183,6 +184,8 @@ require "./std/pp_spec.cr" require "./std/pretty_print_spec.cr" require "./std/process/find_executable_spec.cr" # require "./std/process_spec.cr" (failed to run) +require "./std/process/status_spec.cr" +require "./std/process/utils_spec.cr" require "./std/proc_spec.cr" require "./std/raise_spec.cr" require "./std/random/isaac_spec.cr" @@ -192,6 +195,7 @@ require "./std/random_spec.cr" require "./std/range_spec.cr" require "./std/record_spec.cr" require "./std/reference_spec.cr" +require "./std/regex/match_data_spec.cr" require "./std/regex_spec.cr" require "./std/semantic_version_spec.cr" require "./std/set_spec.cr" @@ -213,6 +217,7 @@ require "./std/spec/helpers/iterate_spec.cr" require "./std/spec/junit_formatter_spec.cr" require "./std/spec_spec.cr" require "./std/spec/tap_formatter_spec.cr" +require "./std/sprintf_spec.cr" require "./std/static_array_spec.cr" require "./std/string_builder_spec.cr" require "./std/string/grapheme_break_spec.cr" From bc1391d1832663b1af4c1a64a7327e4480aa6f5f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 18 Apr 2023 22:41:42 +0800 Subject: [PATCH 0448/1551] Always use 0 for offset of `StaticArray`'s `@buffer` (#13319) --- spec/compiler/codegen/offsetof_spec.cr | 8 +++++++ spec/compiler/interpreter/pointers_spec.cr | 26 ++++++++++++++++++++++ src/compiler/crystal/codegen/codegen.cr | 6 ++--- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/spec/compiler/codegen/offsetof_spec.cr b/spec/compiler/codegen/offsetof_spec.cr index f7e7971be6b2..c42fc6f4ad59 100644 --- a/spec/compiler/codegen/offsetof_spec.cr +++ b/spec/compiler/codegen/offsetof_spec.cr @@ -56,4 +56,12 @@ describe "Code gen: offsetof" do (pointerof(f).as(Void*) + offsetof(Foo, @y).to_i64).as(UInt32*).value == f.y CRYSTAL end + + it "returns offset of `StaticArray#@buffer`" do + run(<<-CRYSTAL).to_b.should be_true + x = uninitialized Int32[4] + pointerof(x.@buffer).value = 12345 + (pointerof(x).as(Void*) + offsetof(Int32[4], @buffer).to_i64).as(Int32*).value == x.@buffer + CRYSTAL + end end diff --git a/spec/compiler/interpreter/pointers_spec.cr b/spec/compiler/interpreter/pointers_spec.cr index 571e63843925..d3049d2ac256 100644 --- a/spec/compiler/interpreter/pointers_spec.cr +++ b/spec/compiler/interpreter/pointers_spec.cr @@ -121,6 +121,32 @@ describe Crystal::Repl::Interpreter do CRYSTAL end + it "pointerof read `StaticArray#@buffer` (1)" do + interpret(<<-CRYSTAL).should eq(2) + struct StaticArray(T, N) + def to_unsafe + pointerof(@buffer) + end + + def x + @buffer + end + end + + foo = uninitialized Int32[4] + foo.to_unsafe.value = 2 + foo.x + CRYSTAL + end + + it "pointerof read `StaticArray#@buffer` (2)" do + interpret(<<-CRYSTAL).should eq(2) + foo = uninitialized Int32[4] + pointerof(foo.@buffer).value = 2 + foo.@buffer + CRYSTAL + end + it "interprets pointer set and get (union type)" do interpret(<<-CRYSTAL).should eq(10) ptr = Pointer(Int32 | Bool).malloc(1_u64) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 3b3a81757bb8..34dbb49d4b76 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -105,13 +105,13 @@ module Crystal end def offset_of(type, element_index) - return 0_u64 if type.extern_union? + return 0_u64 if type.extern_union? || type.is_a?(StaticArrayInstanceType) llvm_typer.offset_of(llvm_typer.llvm_type(type), element_index) end def instance_offset_of(type, element_index) - # extern unions must be value types, which always use the above - # `offset_of` instead + # extern unions and static arrays must be value types, which always use + # the above `offset_of` instead llvm_typer.offset_of(llvm_typer.llvm_struct_type(type), element_index + 1) end end From d907d5073a12faec24a1f1a725ef9112dd71eec1 Mon Sep 17 00:00:00 2001 From: threez Date: Tue, 18 Apr 2023 19:12:17 +0200 Subject: [PATCH 0449/1551] Fix: Remove double URL escape in `HTTP::Server::Response.redirect` (#13321) --- spec/std/http/server/response_spec.cr | 9 +++++++++ src/http/server/response.cr | 12 +++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/spec/std/http/server/response_spec.cr b/spec/std/http/server/response_spec.cr index 517cf1986f29..99e462151f6b 100644 --- a/spec/std/http/server/response_spec.cr +++ b/spec/std/http/server/response_spec.cr @@ -370,6 +370,15 @@ describe HTTP::Server::Response do io.to_s.should eq("HTTP/1.1 302 Found\r\nLocation: https://example.com/path%0Afoo%20bar\r\nContent-Length: 0\r\n\r\n") end + it "doesn't encode URIs twice" do + io = IO::Memory.new + response = Response.new(io) + u = URI.new "https", host: "example.com", path: "auth", + query: URI::Params.new({"redirect_uri" => ["http://example.com/callback"]}) + response.redirect(u) + io.to_s.should eq("HTTP/1.1 302 Found\r\nLocation: https://example.com/auth?redirect_uri=http%3A%2F%2Fexample.com%2Fcallback\r\nContent-Length: 0\r\n\r\n") + end + it "permanent redirect" do io = IO::Memory.new response = Response.new(io) diff --git a/src/http/server/response.cr b/src/http/server/response.cr index 573fa0bc28a9..d07295914482 100644 --- a/src/http/server/response.cr +++ b/src/http/server/response.cr @@ -187,9 +187,15 @@ class HTTP::Server check_headers self.status = status - headers["Location"] = String.build do |io| - URI.encode(location.to_s, io) { |byte| URI.reserved?(byte) || URI.unreserved?(byte) } - end + headers["Location"] = if location.is_a? URI + location.to_s + else + String.build do |io| + URI.encode(location.to_s, io) do |byte| + URI.reserved?(byte) || URI.unreserved?(byte) + end + end + end close end From 949efb7011302b6affda840a8be28773f7e6434b Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 19 Apr 2023 03:13:12 +1000 Subject: [PATCH 0450/1551] Implement Socket `reuse_port` (=`reuse_address`) on Windows (#13326) --- spec/std/http/server/server_spec.cr | 2 +- spec/std/socket/tcp_server_spec.cr | 9 ++++----- src/crystal/system/win32/socket.cr | 13 ++++++++++--- src/socket.cr | 29 ++++++++++++++++++++++------- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index 1cc5b20f666b..26e67229d77f 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -81,7 +81,7 @@ describe HTTP::Server do ch.receive.end?.should be_true end - pending_win32 "reuses the TCP port (SO_REUSEPORT)" do + it "reuses the TCP port (SO_REUSEPORT)" do s1 = HTTP::Server.new { |ctx| } address = s1.bind_unused_port(reuse_port: true) diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr index 496eac4ac860..5743697a0c5f 100644 --- a/spec/std/socket/tcp_server_spec.cr +++ b/spec/std/socket/tcp_server_spec.cr @@ -1,7 +1,6 @@ {% skip_file if flag?(:wasm32) %} require "./spec_helper" -require "../../support/win32" describe TCPServer, tags: "network" do describe ".new" do @@ -52,7 +51,7 @@ describe TCPServer, tags: "network" do end describe "reuse_port" do - pending_win32 "raises when port is in use" do + it "raises when port is in use" do TCPServer.open(address, 0) do |server| expect_raises(Socket::BindError, "Could not bind to '#{address}:#{server.local_address.port}': ") do TCPServer.open(address, server.local_address.port) { } @@ -60,7 +59,7 @@ describe TCPServer, tags: "network" do end end - pending_win32 "raises when not binding with reuse_port" do + it "raises when not binding with reuse_port" do TCPServer.open(address, 0, reuse_port: true) do |server| expect_raises(Socket::BindError) do TCPServer.open(address, server.local_address.port) { } @@ -68,7 +67,7 @@ describe TCPServer, tags: "network" do end end - pending_win32 "raises when port is not ready to be reused" do + it "raises when port is not ready to be reused" do TCPServer.open(address, 0) do |server| expect_raises(Socket::BindError) do TCPServer.open(address, server.local_address.port, reuse_port: true) { } @@ -76,7 +75,7 @@ describe TCPServer, tags: "network" do end end - pending_win32 "binds to used port with reuse_port = true" do + it "binds to used port with reuse_port = true" do TCPServer.open(address, 0, reuse_port: true) do |server| TCPServer.open(address, server.local_address.port, reuse_port: true) { } end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 41f657e21ef5..dc830a0e5661 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -86,7 +86,7 @@ module Crystal::System::Socket private def initialize_handle(handle) value = 1_u8 - ret = LibC.setsockopt(handle, LibC::SOL_SOCKET, LibC::SO_REUSEADDR, pointerof(value), 1) + ret = LibC.setsockopt(handle, LibC::SOL_SOCKET, LibC::SO_EXCLUSIVEADDRUSE, pointerof(value), 1) if ret == LibC::SOCKET_ERROR raise ::Socket::Error.from_wsa_error("setsockopt") end @@ -254,11 +254,18 @@ module Crystal::System::Socket end private def system_reuse_port? - false + getsockopt_bool LibC::SO_REUSEADDR end private def system_reuse_port=(val : Bool) - raise NotImplementedError.new("Socket#reuse_port=") + if val + setsockopt_bool LibC::SO_EXCLUSIVEADDRUSE, false + setsockopt_bool LibC::SO_REUSEADDR, true + else + setsockopt_bool LibC::SO_REUSEADDR, false + setsockopt_bool LibC::SO_EXCLUSIVEADDRUSE, true + end + val end private def system_linger diff --git a/src/socket.cr b/src/socket.cr index 50c5a3e29884..9c4b5d9c3aa0 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -339,13 +339,28 @@ class Socket < IO val end - def reuse_address? : Bool - getsockopt_bool LibC::SO_REUSEADDR - end - - def reuse_address=(val : Bool) - setsockopt_bool LibC::SO_REUSEADDR, val - end + # SO_REUSEADDR, as used in posix, is always assumed on windows + # the SO_REUSEADDR flag on windows is the equivalent of SO_REUSEPORT on linux + # https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse#application-strategies + {% if flag?(:win32) %} + # the address component of a binding can always be reused on windows + def reuse_address? : Bool + true + end + + # there is no effect on windows + def reuse_address=(val : Bool) + val + end + {% else %} + def reuse_address? : Bool + getsockopt_bool LibC::SO_REUSEADDR + end + + def reuse_address=(val : Bool) + setsockopt_bool LibC::SO_REUSEADDR, val + end + {% end %} def reuse_port? : Bool system_reuse_port? From 6c351758fd814343e1a37fd08bb04bc0e9850a9a Mon Sep 17 00:00:00 2001 From: Josh Rickard Date: Tue, 18 Apr 2023 12:13:33 -0500 Subject: [PATCH 0451/1551] Fix WebSocket capitalization in docs (#13331) --- src/http/server/handlers/websocket_handler.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/server/handlers/websocket_handler.cr b/src/http/server/handlers/websocket_handler.cr index c201f794e909..8652a828c1e7 100644 --- a/src/http/server/handlers/websocket_handler.cr +++ b/src/http/server/handlers/websocket_handler.cr @@ -5,7 +5,7 @@ require "http/web_socket" # # NOTE: To use `WebSocketHandler`, you must explicitly import it with `require "http"` # -# When a request can be upgraded, the associated `HTTP::Websocket` and +# When a request can be upgraded, the associated `HTTP::WebSocket` and # `HTTP::Server::Context` will be yielded to the block. For example: # # ``` From 2300befb0e946fdb93f09b98cfc9db24e25ed582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 18 Apr 2023 20:53:16 +0200 Subject: [PATCH 0452/1551] Reorder and enhance specs for `String.new(&)` (#13333) --- spec/std/string_spec.cr | 84 +++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index b53c7479a31e..579c1d9f9dd9 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2582,27 +2582,64 @@ describe "String" do end end - it "raises if string capacity is negative" do - expect_raises(ArgumentError, "Negative capacity") do - String.new(-1) { |buf| {0, 0} } + describe "String.new(&)" do + it "creates with matching capacity" do + String.new(3) { |buf| + buf[0] = 'f'.ord.to_u8 + buf[1] = 'o'.ord.to_u8 + buf[2] = 'o'.ord.to_u8 + {3, 3} + }.should eq "foo" end - end - it "raises if capacity too big on new with UInt32::MAX" do - expect_raises(ArgumentError, "Capacity too big") do - String.new(UInt32::MAX) { {0, 0} } + it "creates with excess capacity" do + String.new(5) { |buf| + buf[0] = 'f'.ord.to_u8 + buf[1] = 'o'.ord.to_u8 + buf[2] = 'o'.ord.to_u8 + {3, 3} + }.should eq "foo" end - end - it "raises if capacity too big on new with UInt32::MAX - String::HEADER_SIZE - 1" do - expect_raises(ArgumentError, "Capacity too big") do - String.new(UInt32::MAX - String::HEADER_SIZE) { {0, 0} } + it "raises if string capacity is negative" do + expect_raises(ArgumentError, "Negative capacity") do + String.new(-1) { |buf| {0, 0} } + end end - end - it "raises if capacity too big on new with UInt64::MAX" do - expect_raises(ArgumentError, "Capacity too big") do - String.new(UInt64::MAX) { {0, 0} } + it "raises if capacity too big with UInt32::MAX" do + expect_raises(ArgumentError, "Capacity too big") do + String.new(UInt32::MAX) { {0, 0} } + end + end + + it "raises if capacity too big with UInt32::MAX - String::HEADER_SIZE - 1" do + expect_raises(ArgumentError, "Capacity too big") do + String.new(UInt32::MAX - String::HEADER_SIZE) { {0, 0} } + end + end + + it "raises if capacity too big with UInt64::MAX" do + expect_raises(ArgumentError, "Capacity too big") do + String.new(UInt64::MAX) { {0, 0} } + end + end + + {% unless flag?(:wasm32) %} + it "allocates buffer of correct size (#3332)" do + String.new(255_u8) do |buffer| + LibGC.size(buffer).should be > 255 + {255, 0} + end + end + {% end %} + + it "raises if returned bytesize is greater than capacity" do + expect_raises ArgumentError, "Bytesize out of capacity bounds" do + String.new(123) do |buffer| + {124, 0} + end + end end end @@ -2813,23 +2850,6 @@ describe "String" do string.should be(clone) end - {% unless flag?(:wasm32) %} - it "allocates buffer of correct size when UInt8 is given to new (#3332)" do - String.new(255_u8) do |buffer| - LibGC.size(buffer).should be >= 255 - {255, 0} - end - end - {% end %} - - it "raises on String.new if returned bytesize is greater than capacity" do - expect_raises ArgumentError, "Bytesize out of capacity bounds" do - String.new(123) do |buffer| - {124, 0} - end - end - end - describe "invalid UTF-8 byte sequence" do it "gets size" do string = String.new(Bytes[255, 0, 0, 0, 65]) From e12da898359eed1c72ada8c68c0fb5faa5afd569 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 18 Apr 2023 14:54:03 -0400 Subject: [PATCH 0453/1551] Add `BigDecimal#%` (#13255) --- spec/std/big/big_decimal_spec.cr | 15 +++++++++++++++ src/big/big_decimal.cr | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index e783d2f5bb10..7888fdd84200 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -262,6 +262,21 @@ describe BigDecimal do BigDecimal.new(3333.to_big_i, 7_u64).should eq(BigDecimal.new(1).div(BigDecimal.new(3000), 7)) (-BigDecimal.new(3)).should eq(BigDecimal.new(-3)) + + (BigDecimal.new(5) % BigDecimal.new(2)).should eq(BigDecimal.new(1)) + (BigDecimal.new(500) % BigDecimal.new(2)).should eq(BigDecimal.new(0)) + (BigDecimal.new(500) % BigDecimal.new(2000)).should eq(BigDecimal.new(500)) + end + + it "handles modulus correctly" do + (BigDecimal.new(13.0) % BigDecimal.new(4.0)).should eq(BigDecimal.new(1.0)) + (BigDecimal.new(13.0) % BigDecimal.new(-4.0)).should eq(BigDecimal.new(-3.0)) + (BigDecimal.new(-13.0) % BigDecimal.new(4.0)).should eq(BigDecimal.new(3.0)) + (BigDecimal.new(-13.0) % BigDecimal.new(-4.0)).should eq(BigDecimal.new(-1.0)) + (BigDecimal.new(11.5) % BigDecimal.new(4.0)).should eq(BigDecimal.new(3.5)) + (BigDecimal.new(11.5) % BigDecimal.new(-4.0)).should eq(BigDecimal.new(-0.5)) + (BigDecimal.new(-11.5) % BigDecimal.new(4.0)).should eq(BigDecimal.new(0.5)) + (BigDecimal.new(-11.5) % BigDecimal.new(-4.0)).should eq(BigDecimal.new(-3.5)) end it "performs arithmetic with other number types" do diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index 470c61e3e85f..eccd82109142 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -200,6 +200,22 @@ struct BigDecimal < Number self * BigDecimal.new(other) end + def %(other : BigDecimal) : BigDecimal + if @scale > other.scale + scaled = other.scale_to(self) + BigDecimal.new(@value % scaled.value, @scale) + elsif @scale < other.scale + scaled = scale_to(other) + BigDecimal.new(scaled.value % other.value, other.scale) + else + BigDecimal.new(@value % other.value, @scale) + end + end + + def %(other : Int) + self % BigDecimal.new(other) + end + def /(other : BigDecimal) : BigDecimal div other end From 6d02f3b69a0216b2f7751fbfb460c3e92c66ea49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 19 Apr 2023 14:31:45 +0200 Subject: [PATCH 0454/1551] Fix `String#gsub` with empty match at multibyte char (#13342) --- spec/std/string_spec.cr | 12 +++++++++++- src/string.cr | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 579c1d9f9dd9..b988c55fe719 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -1734,7 +1734,7 @@ describe "String" do end end - describe "gsub" do + describe "#gsub" do it "gsubs char with char" do "foobar".gsub('o', 'e').should eq("feebar") end @@ -1910,6 +1910,16 @@ describe "String" do it "ignores if backreferences: false" do "foo".gsub(/o/, "x\\0x", backreferences: false).should eq("fx\\0xx\\0x") end + + it "empty match" do + "a b".gsub(/\B/, "-").should eq "a - b" + "┬ 7".gsub(/\B/, "-").should eq "-┬- - 7" + end + + it "empty string" do + "ab".gsub("", "-").should eq "-a-b-" + "┬7".gsub("", "-").should eq "-┬-7-" + end end it "scans using $~" do diff --git a/src/string.cr b/src/string.cr index 9454f6d1ea03..eb437fcaf4af 100644 --- a/src/string.cr +++ b/src/string.cr @@ -2807,7 +2807,8 @@ class String buffer << yield string if string.bytesize == 0 - byte_offset = index + 1 + # The pattern matched an empty result. We must advance one character to avoid stagnation. + byte_offset = index + Char::Reader.new(self, pos: byte_offset).current_char_width last_byte_offset = index else byte_offset = index + string.bytesize @@ -2864,7 +2865,8 @@ class String yield str, match, buffer if str.bytesize == 0 - byte_offset = index + 1 + # The pattern matched an empty result. We must advance one character to avoid stagnation. + byte_offset = index + Char::Reader.new(self, pos: byte_offset).current_char_width last_byte_offset = index else byte_offset = index + str.bytesize From 102002db8ee2e61f2f3aac512256d203f9c142f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 19 Apr 2023 14:31:45 +0200 Subject: [PATCH 0455/1551] Fix `String#gsub` with empty match at multibyte char (#13342) --- spec/std/string_spec.cr | 12 +++++++++++- src/string.cr | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index b53c7479a31e..342866f46a42 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -1734,7 +1734,7 @@ describe "String" do end end - describe "gsub" do + describe "#gsub" do it "gsubs char with char" do "foobar".gsub('o', 'e').should eq("feebar") end @@ -1910,6 +1910,16 @@ describe "String" do it "ignores if backreferences: false" do "foo".gsub(/o/, "x\\0x", backreferences: false).should eq("fx\\0xx\\0x") end + + it "empty match" do + "a b".gsub(/\B/, "-").should eq "a - b" + "┬ 7".gsub(/\B/, "-").should eq "-┬- - 7" + end + + it "empty string" do + "ab".gsub("", "-").should eq "-a-b-" + "┬7".gsub("", "-").should eq "-┬-7-" + end end it "scans using $~" do diff --git a/src/string.cr b/src/string.cr index 9454f6d1ea03..eb437fcaf4af 100644 --- a/src/string.cr +++ b/src/string.cr @@ -2807,7 +2807,8 @@ class String buffer << yield string if string.bytesize == 0 - byte_offset = index + 1 + # The pattern matched an empty result. We must advance one character to avoid stagnation. + byte_offset = index + Char::Reader.new(self, pos: byte_offset).current_char_width last_byte_offset = index else byte_offset = index + string.bytesize @@ -2864,7 +2865,8 @@ class String yield str, match, buffer if str.bytesize == 0 - byte_offset = index + 1 + # The pattern matched an empty result. We must advance one character to avoid stagnation. + byte_offset = index + Char::Reader.new(self, pos: byte_offset).current_char_width last_byte_offset = index else byte_offset = index + str.bytesize From b8c514b655bc755703f51908b4288264f4de5303 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 20 Apr 2023 03:36:56 +0800 Subject: [PATCH 0456/1551] Fix PCRE2 `Regex` with more than 127 named capture groups (#13349) --- spec/std/regex/match_data_spec.cr | 12 ++++++++++++ spec/std/regex_spec.cr | 6 ++++++ src/regex/pcre2.cr | 8 ++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/spec/std/regex/match_data_spec.cr b/spec/std/regex/match_data_spec.cr index 458750c6931d..db035f481677 100644 --- a/spec/std/regex/match_data_spec.cr +++ b/spec/std/regex/match_data_spec.cr @@ -400,6 +400,11 @@ describe "Regex::MatchData" do matchdata(/(Cr)(s)?/, "Crystal").captures.should eq(["Cr", nil]) matchdata(/(Cr)(?s)?(tal)?/, "Crystal").captures.should eq(["Cr", nil]) end + + it "doesn't get named captures when there are more than 255" do + regex = Regex.new(Array.new(256) { |i| "(?.)" }.join) + matchdata(regex, "x" * 256).captures.should eq([] of String) + end end describe "#named_captures" do @@ -416,6 +421,13 @@ describe "Regex::MatchData" do it "gets a hash of named captures with duplicated name" do matchdata(/(?Cr)y(?s)/, "Crystal").named_captures.should eq({"name" => "s"}) end + + it "gets more than 127 named captures" do + regex = Regex.new(Array.new(128) { |i| "(?.)" }.join) + captures = matchdata(regex, "x" * 128).named_captures + captures.size.should eq(128) + 128.times { |i| captures["c#{i}"].should eq("x") } + end end describe "#to_a" do diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index ff3338b89842..bfb520b7170c 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -398,6 +398,12 @@ describe "Regex" do it "duplicate name" do /(?)(?)/.name_table.should eq({1 => "foo", 2 => "foo"}) end + + it "more than 255 groups" do + regex = Regex.new(Array.new(1000) { |i| "(?.)" }.join) + name_table = Array.new(1000) { |i| {i + 1, "c#{i}"} }.to_h + regex.name_table.should eq(name_table) + end end it "#capture_count" do diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index de625d3676d3..80ef62137ac5 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -166,7 +166,7 @@ module Regex::PCRE2 private def name_table_impl lookup = Hash(Int32, String).new - each_capture_group do |capture_number, name_entry| + each_named_capture_group do |capture_number, name_entry| lookup[capture_number] = String.new(name_entry.to_unsafe + 2) end @@ -174,7 +174,7 @@ module Regex::PCRE2 end # :nodoc: - def each_capture_group(&) + def each_named_capture_group(&) name_table = uninitialized UInt8* pattern_info(LibPCRE2::INFO_NAMETABLE, pointerof(name_table)) @@ -182,7 +182,7 @@ module Regex::PCRE2 name_count = pattern_info(LibPCRE2::INFO_NAMECOUNT) name_count.times do - capture_number = (name_table[0] << 8) | name_table[1] + capture_number = (name_table[0].to_i << 8) | name_table[1] yield capture_number, Slice.new(name_table, name_entry_size) @@ -291,7 +291,7 @@ module Regex::PCRE2 private def fetch_impl(group_name : String, &) selected_range = nil exists = false - @regex.each_capture_group do |number, name_entry| + @regex.each_named_capture_group do |number, name_entry| if name_entry[2, group_name.bytesize]? == group_name.to_slice && name_entry[2 + group_name.bytesize].zero? exists = true range = byte_range(number) { nil } From d3f5d75e7d285a06f578d24659d7862d4a230b5c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 20 Apr 2023 03:39:02 +0800 Subject: [PATCH 0457/1551] Fix `JSON::Serializable` on certain recursively defined types (#13344) --- spec/std/json/serializable_spec.cr | 17 +++++++++++++++++ spec/std/yaml/serializable_spec.cr | 17 +++++++++++++++++ src/json/serialization.cr | 12 ++++++++---- src/yaml/serialization.cr | 14 ++++++++------ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index e4bd0bbcd3a2..8c934563b7ed 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -465,6 +465,18 @@ module JSONNamespace end end +class JSONSomething + include JSON::Serializable + + property value : JSONAttrValue(Set(JSONSomethingElse)?)? +end + +class JSONSomethingElse + include JSON::Serializable + + property value : JSONAttrValue(Set(JSONSomethingElse)?)? +end + describe "JSON mapping" do it "works with record" do JSONAttrPoint.new(1, 2).to_json.should eq "{\"x\":1,\"y\":2}" @@ -1107,4 +1119,9 @@ describe "JSON mapping" do request.bar.id.should eq "id:bar" end end + + it "fixes #13337" do + JSONSomething.from_json(%({"value":{}})).value.should_not be_nil + JSONSomethingElse.from_json(%({"value":{}})).value.should_not be_nil + end end diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 297db3459d8e..7e4364c2113a 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -398,6 +398,18 @@ class YAMLStrictDiscriminatorBar < YAMLStrictDiscriminator property y : YAMLStrictDiscriminator end +class YAMLSomething + include YAML::Serializable + + property value : YAMLAttrValue(Set(YAMLSomethingElse)?)? +end + +class YAMLSomethingElse + include YAML::Serializable + + property value : YAMLAttrValue(Set(YAMLSomethingElse)?)? +end + describe "YAML::Serializable" do it "works with record" do YAMLAttrPoint.new(1, 2).to_yaml.should eq "---\nx: 1\ny: 2\n" @@ -1008,4 +1020,9 @@ describe "YAML::Serializable" do request.bar.id.should eq "id:bar" end end + + it "fixes #13337" do + YAMLSomething.from_yaml(%({"value":{}})).value.should_not be_nil + YAMLSomethingElse.from_yaml(%({"value":{}})).value.should_not be_nil + end end diff --git a/src/json/serialization.cr b/src/json/serialization.cr index 2eabe22dc313..ca4b8e36f4ac 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -186,7 +186,6 @@ module JSON {% unless ann && (ann[:ignore] || ann[:ignore_deserialize]) %} {% properties[ivar.id] = { - type: ivar.type, key: ((ann && ann[:key]) || ivar).id.stringify, has_default: ivar.has_default_value?, default: ivar.default_value, @@ -199,8 +198,14 @@ module JSON {% end %} {% end %} + # `%var`'s type must be exact to avoid type inference issues with + # recursively defined serializable types {% for name, value in properties %} - %var{name} = {% if value[:has_default] || value[:nilable] %} nil {% else %} uninitialized ::Union({{value[:type]}}) {% end %} + %var{name} = {% if value[:has_default] || value[:nilable] %} + nil.as(::Nil | typeof(@{{name}})) + {% else %} + uninitialized typeof(@{{name}}) + {% end %} %found{name} = false {% end %} @@ -223,7 +228,7 @@ module JSON {% if value[:converter] %} {{value[:converter]}}.from_json(pull) {% else %} - ::Union({{value[:type]}}).new(pull) + typeof(@{{name}}).new(pull) {% end %} end end @@ -288,7 +293,6 @@ module JSON {% unless ann && (ann[:ignore] || ann[:ignore_serialize] == true) %} {% properties[ivar.id] = { - type: ivar.type, key: ((ann && ann[:key]) || ivar).id.stringify, root: ann && ann[:root], converter: ann && ann[:converter], diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index b939c1c4a17b..ebc429ceb77a 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -192,7 +192,6 @@ module YAML {% unless ann && (ann[:ignore] || ann[:ignore_deserialize]) %} {% properties[ivar.id] = { - type: ivar.type, key: ((ann && ann[:key]) || ivar).id.stringify, has_default: ivar.has_default_value?, default: ivar.default_value, @@ -204,8 +203,14 @@ module YAML {% end %} {% end %} + # `%var`'s type must be exact to avoid type inference issues with + # recursively defined serializable types {% for name, value in properties %} - %var{name} = {% if value[:has_default] || value[:nilable] %} nil {% else %} uninitialized ::Union({{value[:type]}}) {% end %} + %var{name} = {% if value[:has_default] || value[:nilable] %} + nil.as(::Nil | typeof(@{{name}})) + {% else %} + uninitialized typeof(@{{name}}) + {% end %} %found{name} = false {% end %} @@ -226,10 +231,8 @@ module YAML %var{name} = {% if value[:converter] %} {{value[:converter]}}.from_yaml(ctx, value_node) - {% elsif value[:type].is_a?(Path) || value[:type].is_a?(Generic) %} - {{value[:type]}}.new(ctx, value_node) {% else %} - ::Union({{value[:type]}}).new(ctx, value_node) + typeof(@{{name}}).new(ctx, value_node) {% end %} end @@ -299,7 +302,6 @@ module YAML {% unless ann && (ann[:ignore] || ann[:ignore_serialize]) %} {% properties[ivar.id] = { - type: ivar.type, key: ((ann && ann[:key]) || ivar).id.stringify, converter: ann && ann[:converter], emit_null: (ann && (ann[:emit_null] != nil) ? ann[:emit_null] : emit_nulls), From 99a2b4da8a470a17e944d1d11fce9ec7a2b95c93 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 20 Apr 2023 03:36:56 +0800 Subject: [PATCH 0458/1551] Fix PCRE2 `Regex` with more than 127 named capture groups (#13349) --- spec/std/regex/match_data_spec.cr | 12 ++++++++++++ spec/std/regex_spec.cr | 6 ++++++ src/regex/pcre2.cr | 8 ++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/spec/std/regex/match_data_spec.cr b/spec/std/regex/match_data_spec.cr index 458750c6931d..db035f481677 100644 --- a/spec/std/regex/match_data_spec.cr +++ b/spec/std/regex/match_data_spec.cr @@ -400,6 +400,11 @@ describe "Regex::MatchData" do matchdata(/(Cr)(s)?/, "Crystal").captures.should eq(["Cr", nil]) matchdata(/(Cr)(?s)?(tal)?/, "Crystal").captures.should eq(["Cr", nil]) end + + it "doesn't get named captures when there are more than 255" do + regex = Regex.new(Array.new(256) { |i| "(?.)" }.join) + matchdata(regex, "x" * 256).captures.should eq([] of String) + end end describe "#named_captures" do @@ -416,6 +421,13 @@ describe "Regex::MatchData" do it "gets a hash of named captures with duplicated name" do matchdata(/(?Cr)y(?s)/, "Crystal").named_captures.should eq({"name" => "s"}) end + + it "gets more than 127 named captures" do + regex = Regex.new(Array.new(128) { |i| "(?.)" }.join) + captures = matchdata(regex, "x" * 128).named_captures + captures.size.should eq(128) + 128.times { |i| captures["c#{i}"].should eq("x") } + end end describe "#to_a" do diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 098c718b2248..e542f7c73f61 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -397,6 +397,12 @@ describe "Regex" do it "duplicate name" do /(?)(?)/.name_table.should eq({1 => "foo", 2 => "foo"}) end + + it "more than 255 groups" do + regex = Regex.new(Array.new(1000) { |i| "(?.)" }.join) + name_table = Array.new(1000) { |i| {i + 1, "c#{i}"} }.to_h + regex.name_table.should eq(name_table) + end end it "#capture_count" do diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index de625d3676d3..80ef62137ac5 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -166,7 +166,7 @@ module Regex::PCRE2 private def name_table_impl lookup = Hash(Int32, String).new - each_capture_group do |capture_number, name_entry| + each_named_capture_group do |capture_number, name_entry| lookup[capture_number] = String.new(name_entry.to_unsafe + 2) end @@ -174,7 +174,7 @@ module Regex::PCRE2 end # :nodoc: - def each_capture_group(&) + def each_named_capture_group(&) name_table = uninitialized UInt8* pattern_info(LibPCRE2::INFO_NAMETABLE, pointerof(name_table)) @@ -182,7 +182,7 @@ module Regex::PCRE2 name_count = pattern_info(LibPCRE2::INFO_NAMECOUNT) name_count.times do - capture_number = (name_table[0] << 8) | name_table[1] + capture_number = (name_table[0].to_i << 8) | name_table[1] yield capture_number, Slice.new(name_table, name_entry_size) @@ -291,7 +291,7 @@ module Regex::PCRE2 private def fetch_impl(group_name : String, &) selected_range = nil exists = false - @regex.each_capture_group do |number, name_entry| + @regex.each_named_capture_group do |number, name_entry| if name_entry[2, group_name.bytesize]? == group_name.to_slice && name_entry[2 + group_name.bytesize].zero? exists = true range = byte_range(number) { nil } From e95461cb050a12740ae7594bc2f8d8e98035eccd Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 20 Apr 2023 03:39:02 +0800 Subject: [PATCH 0459/1551] Fix `JSON::Serializable` on certain recursively defined types (#13344) --- spec/std/json/serializable_spec.cr | 17 +++++++++++++++++ spec/std/yaml/serializable_spec.cr | 17 +++++++++++++++++ src/json/serialization.cr | 12 ++++++++---- src/yaml/serialization.cr | 14 ++++++++------ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index e4bd0bbcd3a2..8c934563b7ed 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -465,6 +465,18 @@ module JSONNamespace end end +class JSONSomething + include JSON::Serializable + + property value : JSONAttrValue(Set(JSONSomethingElse)?)? +end + +class JSONSomethingElse + include JSON::Serializable + + property value : JSONAttrValue(Set(JSONSomethingElse)?)? +end + describe "JSON mapping" do it "works with record" do JSONAttrPoint.new(1, 2).to_json.should eq "{\"x\":1,\"y\":2}" @@ -1107,4 +1119,9 @@ describe "JSON mapping" do request.bar.id.should eq "id:bar" end end + + it "fixes #13337" do + JSONSomething.from_json(%({"value":{}})).value.should_not be_nil + JSONSomethingElse.from_json(%({"value":{}})).value.should_not be_nil + end end diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 297db3459d8e..7e4364c2113a 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -398,6 +398,18 @@ class YAMLStrictDiscriminatorBar < YAMLStrictDiscriminator property y : YAMLStrictDiscriminator end +class YAMLSomething + include YAML::Serializable + + property value : YAMLAttrValue(Set(YAMLSomethingElse)?)? +end + +class YAMLSomethingElse + include YAML::Serializable + + property value : YAMLAttrValue(Set(YAMLSomethingElse)?)? +end + describe "YAML::Serializable" do it "works with record" do YAMLAttrPoint.new(1, 2).to_yaml.should eq "---\nx: 1\ny: 2\n" @@ -1008,4 +1020,9 @@ describe "YAML::Serializable" do request.bar.id.should eq "id:bar" end end + + it "fixes #13337" do + YAMLSomething.from_yaml(%({"value":{}})).value.should_not be_nil + YAMLSomethingElse.from_yaml(%({"value":{}})).value.should_not be_nil + end end diff --git a/src/json/serialization.cr b/src/json/serialization.cr index 2eabe22dc313..ca4b8e36f4ac 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -186,7 +186,6 @@ module JSON {% unless ann && (ann[:ignore] || ann[:ignore_deserialize]) %} {% properties[ivar.id] = { - type: ivar.type, key: ((ann && ann[:key]) || ivar).id.stringify, has_default: ivar.has_default_value?, default: ivar.default_value, @@ -199,8 +198,14 @@ module JSON {% end %} {% end %} + # `%var`'s type must be exact to avoid type inference issues with + # recursively defined serializable types {% for name, value in properties %} - %var{name} = {% if value[:has_default] || value[:nilable] %} nil {% else %} uninitialized ::Union({{value[:type]}}) {% end %} + %var{name} = {% if value[:has_default] || value[:nilable] %} + nil.as(::Nil | typeof(@{{name}})) + {% else %} + uninitialized typeof(@{{name}}) + {% end %} %found{name} = false {% end %} @@ -223,7 +228,7 @@ module JSON {% if value[:converter] %} {{value[:converter]}}.from_json(pull) {% else %} - ::Union({{value[:type]}}).new(pull) + typeof(@{{name}}).new(pull) {% end %} end end @@ -288,7 +293,6 @@ module JSON {% unless ann && (ann[:ignore] || ann[:ignore_serialize] == true) %} {% properties[ivar.id] = { - type: ivar.type, key: ((ann && ann[:key]) || ivar).id.stringify, root: ann && ann[:root], converter: ann && ann[:converter], diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index b939c1c4a17b..ebc429ceb77a 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -192,7 +192,6 @@ module YAML {% unless ann && (ann[:ignore] || ann[:ignore_deserialize]) %} {% properties[ivar.id] = { - type: ivar.type, key: ((ann && ann[:key]) || ivar).id.stringify, has_default: ivar.has_default_value?, default: ivar.default_value, @@ -204,8 +203,14 @@ module YAML {% end %} {% end %} + # `%var`'s type must be exact to avoid type inference issues with + # recursively defined serializable types {% for name, value in properties %} - %var{name} = {% if value[:has_default] || value[:nilable] %} nil {% else %} uninitialized ::Union({{value[:type]}}) {% end %} + %var{name} = {% if value[:has_default] || value[:nilable] %} + nil.as(::Nil | typeof(@{{name}})) + {% else %} + uninitialized typeof(@{{name}}) + {% end %} %found{name} = false {% end %} @@ -226,10 +231,8 @@ module YAML %var{name} = {% if value[:converter] %} {{value[:converter]}}.from_yaml(ctx, value_node) - {% elsif value[:type].is_a?(Path) || value[:type].is_a?(Generic) %} - {{value[:type]}}.new(ctx, value_node) {% else %} - ::Union({{value[:type]}}).new(ctx, value_node) + typeof(@{{name}}).new(ctx, value_node) {% end %} end @@ -299,7 +302,6 @@ module YAML {% unless ann && (ann[:ignore] || ann[:ignore_serialize]) %} {% properties[ivar.id] = { - type: ivar.type, key: ((ann && ann[:key]) || ivar).id.stringify, converter: ann && ann[:converter], emit_null: (ann && (ann[:emit_null] != nil) ? ann[:emit_null] : emit_nulls), From 061dc10eb1e13c253bd8398a2b68f7b38bbd736c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 20 Apr 2023 10:42:14 +0200 Subject: [PATCH 0460/1551] Refactor String header layout reflection (#13335) --- src/string.cr | 22 +++++++++++++++------- src/string/builder.cr | 8 ++++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/string.cr b/src/string.cr index eb437fcaf4af..103474584626 100644 --- a/src/string.cr +++ b/src/string.cr @@ -143,10 +143,9 @@ require "c/string" # remove the offending byte sequences first. class String # :nodoc: - TYPE_ID = "".crystal_type_id - - # :nodoc: - HEADER_SIZE = sizeof({Int32, Int32, Int32}) + # + # Holds the offset to the first character byte. + HEADER_SIZE = offsetof(String, @c) include Comparable(self) @@ -266,9 +265,18 @@ class String str = GC.realloc(str, bytesize.to_u32 + HEADER_SIZE + 1) end - str_header = str.as({Int32, Int32, Int32}*) - str_header.value = {TYPE_ID, bytesize.to_i, size.to_i} - str.as(String) + set_crystal_type_id(str) + str = str.as(String) + str.initialize_header(bytesize.to_i, size.to_i) + str + end + + # :nodoc: + # + # Initializes the header information of a `String` instance. + # The actual character content at `@c` is expected to be already filled and is + # unaffected by this method. + def initialize_header(@bytesize : Int32, @length : Int32 = 0) end # Builds a `String` by creating a `String::Builder` with the given initial capacity, yielding diff --git a/src/string/builder.cr b/src/string/builder.cr index d1add8dc9be2..0fb77f4ca41b 100644 --- a/src/string/builder.cr +++ b/src/string/builder.cr @@ -14,7 +14,6 @@ class String::Builder < IO # Make sure to also be able to hold # the header size plus the trailing zero byte capacity += String::HEADER_SIZE + 1 - String.check_capacity_in_bounds(capacity) @buffer = GC.malloc_atomic(capacity.to_u32).as(UInt8*) @bytesize = 0 @@ -117,9 +116,10 @@ class String::Builder < IO resize_to_capacity(real_bytesize) end - header = @buffer.as({Int32, Int32, Int32}*) - header.value = {String::TYPE_ID, @bytesize - 1, 0} - @buffer.as(String) + String.set_crystal_type_id(@buffer) + str = @buffer.as(String) + str.initialize_header((bytesize - 1).to_i) + str end private def real_bytesize From 24ea737fef4efd9a131cf05fb7cdfc1f0a800a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 20 Apr 2023 10:43:59 +0200 Subject: [PATCH 0461/1551] Fix size of type_id in `Object.set_crystal_type_id` (#13338) --- spec/std/object_spec.cr | 11 +++++++++++ src/object.cr | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/std/object_spec.cr b/spec/std/object_spec.cr index 564f6af08375..3df134513ae2 100644 --- a/spec/std/object_spec.cr +++ b/spec/std/object_spec.cr @@ -122,6 +122,10 @@ private class TestObject def self.test_annotation_count {{ @type.instance_vars.select(&.annotation(TestObject::TestAnnotation)).size }} end + + def self.do_set_crystal_type_id(ptr) + set_crystal_type_id(ptr) + end end private class DelegatedTestObject @@ -559,4 +563,11 @@ describe Object do x.foo.should eq(3) end end + + it ".set_crystal_type_id" do + ary = StaticArray[Int32::MAX, Int32::MAX] + TestObject.do_set_crystal_type_id(pointerof(ary)) + ary[0].should eq TestObject.crystal_instance_type_id + ary[1].should eq Int32::MAX + end end diff --git a/src/object.cr b/src/object.cr index 81a46fbc8396..b3b06872a396 100644 --- a/src/object.cr +++ b/src/object.cr @@ -1430,7 +1430,7 @@ class Object end protected def self.set_crystal_type_id(ptr) - ptr.as(LibC::SizeT*).value = LibC::SizeT.new(crystal_instance_type_id) + ptr.as(Pointer(typeof(crystal_instance_type_id))).value = crystal_instance_type_id ptr end end From a59a3dbd738269d5aad6051c3834fc70f482f469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 20 Apr 2023 14:56:27 +0200 Subject: [PATCH 0462/1551] Add Changelog for 1.8.1 (#13356) --- CHANGELOG.md | 13 +++++++++++++ src/VERSION | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85f013219f80..02ea8be6c01c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 1.8.1 (2023-04-20) + +## Standard Library + +### Serialization + +- Fix `JSON::Serializable` on certain recursively defined types ([#13344](https://github.com/crystal-lang/crystal/pull/13344), thanks @HertzDevil) + +### Text + +- Fix `String#gsub` with empty match at multibyte char ([#13342](https://github.com/crystal-lang/crystal/pull/13342), thanks @straight-shoota) +- Fix PCRE2 `Regex` with more than 127 named capture groups ([#13349](https://github.com/crystal-lang/crystal/pull/13349), thanks @HertzDevil) + # 1.8.0 (2023-04-14) ## Language diff --git a/src/VERSION b/src/VERSION index 27f9cd322bb9..a8fdfda1c782 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.8.0 +1.8.1 From 9bbd6c298099c020eb69330ea15db7d531e2a784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 20 Apr 2023 21:08:00 +0200 Subject: [PATCH 0463/1551] Optimize `Doc::Method#compute_doc_info` to avoid duplicate regex (#13324) --- src/compiler/crystal/tools/doc/method.cr | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/compiler/crystal/tools/doc/method.cr b/src/compiler/crystal/tools/doc/method.cr index dee37cb6d1b8..10b223a81d76 100644 --- a/src/compiler/crystal/tools/doc/method.cr +++ b/src/compiler/crystal/tools/doc/method.cr @@ -57,15 +57,17 @@ class Crystal::Doc::Method private def compute_doc_info : DocInfo? def_doc = @def.doc if def_doc - has_inherit = def_doc =~ /^\s*:inherit:\s*$/m - if has_inherit - ancestor_info = self.ancestor_doc_info - if ancestor_info - def_doc = def_doc.gsub(/^[ \t]*:inherit:[ \t]*$/m, ancestor_info.doc.not_nil!) - return DocInfo.new(def_doc, nil) - end + ancestor_doc_info = nil + # TODO: warn about `:inherit:` not finding an ancestor + inherit_def_doc = def_doc.gsub(/^[ \t]*:inherit:[ \t]*$/m) do + ancestor_doc_info ||= self.ancestor_doc_info + ancestor_doc_info.try(&.doc) || break + end - # TODO: warn about `:inherit:` not finding an ancestor + # inherit_def_doc is nil when breaking from the gsub block which means + # no ancestor doc info was found + if inherit_def_doc && !inherit_def_doc.same?(def_doc) + return DocInfo.new(inherit_def_doc, nil) end if @def.name.starts_with?(PSEUDO_METHOD_PREFIX) From 84be62abbcd77f4127bbc352b8469bf03ac68ff1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 21 Apr 2023 03:09:12 +0800 Subject: [PATCH 0464/1551] Move more `Socket` methods to `Crystal::System::Socket` (#13346) --- src/crystal/system/socket.cr | 22 ++++- src/crystal/system/unix/socket.cr | 42 ++++++++- src/crystal/system/wasi/socket.cr | 40 ++++++++ src/crystal/system/win32/socket.cr | 44 ++++++++- src/socket.cr | 145 +++++++++-------------------- 5 files changed, 187 insertions(+), 106 deletions(-) diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr index 54c49f62adaa..de199376615b 100644 --- a/src/crystal/system/socket.cr +++ b/src/crystal/system/socket.cr @@ -25,10 +25,30 @@ module Crystal::System::Socket # private def system_close_write - # private def system_reuse_port? + # private def system_send_buffer_size : Int + + # private def system_send_buffer_size=(val : Int) + + # private def system_recv_buffer_size : Int + + # private def system_recv_buffer_size=(val : Int) + + # private def system_reuse_address? : Bool + + # private def system_reuse_address=(val : Bool) + + # private def system_reuse_port? : Bool # private def system_reuse_port=(val : Bool) + # private def system_broadcast? : Bool + + # private def system_broadcast=(val : Bool) + + # private def system_keepalive? : Bool + + # private def system_keepalive=(val : Bool) + # private def system_linger # private def system_linger=(val) diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 277d1c11aeb0..2b96cd6948f0 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -120,7 +120,31 @@ module Crystal::System::Socket end end - private def system_reuse_port? + private def system_send_buffer_size : Int + getsockopt LibC::SO_SNDBUF, 0 + end + + private def system_send_buffer_size=(val : Int) + setsockopt LibC::SO_SNDBUF, val + end + + private def system_recv_buffer_size : Int + getsockopt LibC::SO_RCVBUF, 0 + end + + private def system_recv_buffer_size=(val : Int) + setsockopt LibC::SO_RCVBUF, val + end + + private def system_reuse_address? : Bool + getsockopt_bool LibC::SO_REUSEADDR + end + + private def system_reuse_address=(val : Bool) + setsockopt_bool LibC::SO_REUSEADDR, val + end + + private def system_reuse_port? : Bool system_getsockopt(fd, LibC::SO_REUSEPORT, 0) do |value| return value != 0 end @@ -136,6 +160,22 @@ module Crystal::System::Socket setsockopt_bool LibC::SO_REUSEPORT, val end + private def system_broadcast? : Bool + getsockopt_bool LibC::SO_BROADCAST + end + + private def system_broadcast=(val : Bool) + setsockopt_bool LibC::SO_BROADCAST, val + end + + private def system_keepalive? : Bool + getsockopt_bool LibC::SO_KEEPALIVE + end + + private def system_keepalive=(val : Bool) + setsockopt_bool LibC::SO_KEEPALIVE, val + end + private def system_linger v = LibC::Linger.new ret = getsockopt LibC::SO_LINGER, v diff --git a/src/crystal/system/wasi/socket.cr b/src/crystal/system/wasi/socket.cr index 974613b847c4..71122ca46a33 100644 --- a/src/crystal/system/wasi/socket.cr +++ b/src/crystal/system/wasi/socket.cr @@ -59,6 +59,30 @@ module Crystal::System::Socket end end + private def system_send_buffer_size : Int + raise NotImplementedError.new "Crystal::System::Socket#system_send_buffer_size" + end + + private def system_send_buffer_size=(val : Int) + raise NotImplementedError.new "Crystal::System::Socket#system_send_buffer_size=" + end + + private def system_recv_buffer_size : Int + raise NotImplementedError.new "Crystal::System::Socket#system_recv_buffer_size" + end + + private def system_recv_buffer_size=(val : Int) + raise NotImplementedError.new "Crystal::System::Socket#system_recv_buffer_size=" + end + + private def system_reuse_address? : Bool + raise NotImplementedError.new "Crystal::System::Socket#system_reuse_address?" + end + + private def system_reuse_address=(val : Bool) + raise NotImplementedError.new "Crystal::System::Socket#system_reuse_address=" + end + private def system_reuse_port? raise NotImplementedError.new "Crystal::System::Socket#system_reuse_port?" end @@ -67,6 +91,22 @@ module Crystal::System::Socket raise NotImplementedError.new "Crystal::System::Socket#system_reuse_port=" end + private def system_broadcast? : Bool + raise NotImplementedError.new "Crystal::System::Socket#system_broadcast?" + end + + private def system_broadcast=(val : Bool) + raise NotImplementedError.new "Crystal::System::Socket#system_broadcast=" + end + + private def system_keepalive? : Bool + raise NotImplementedError.new "Crystal::System::Socket#system_keepalive?" + end + + private def system_keepalive=(val : Bool) + raise NotImplementedError.new "Crystal::System::Socket#system_keepalive=" + end + private def system_linger raise NotImplementedError.new "Crystal::System::Socket#system_linger" end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index dc830a0e5661..748f8b5851bf 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -253,6 +253,33 @@ module Crystal::System::Socket end end + private def system_send_buffer_size : Int + getsockopt LibC::SO_SNDBUF, 0 + end + + private def system_send_buffer_size=(val : Int) + setsockopt LibC::SO_SNDBUF, val + end + + private def system_recv_buffer_size : Int + getsockopt LibC::SO_RCVBUF, 0 + end + + private def system_recv_buffer_size=(val : Int) + setsockopt LibC::SO_RCVBUF, val + end + + # SO_REUSEADDR, as used in posix, is always assumed on windows + # the SO_REUSEADDR flag on windows is the equivalent of SO_REUSEPORT on linux + # https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse#application-strategies + private def system_reuse_address? : Bool + true + end + + private def system_reuse_address=(val : Bool) + raise NotImplementedError.new("Crystal::System::Socket#system_reuse_address=") unless val + end + private def system_reuse_port? getsockopt_bool LibC::SO_REUSEADDR end @@ -265,7 +292,22 @@ module Crystal::System::Socket setsockopt_bool LibC::SO_REUSEADDR, false setsockopt_bool LibC::SO_EXCLUSIVEADDRUSE, true end - val + end + + private def system_broadcast? : Bool + getsockopt_bool LibC::SO_BROADCAST + end + + private def system_broadcast=(val : Bool) + setsockopt_bool LibC::SO_BROADCAST, val + end + + private def system_keepalive? : Bool + getsockopt_bool LibC::SO_KEEPALIVE + end + + private def system_keepalive=(val : Bool) + setsockopt_bool LibC::SO_KEEPALIVE, val end private def system_linger diff --git a/src/socket.cr b/src/socket.cr index 9c4b5d9c3aa0..86c75db86fb2 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -272,120 +272,59 @@ class Socket < IO io << "#<#{self.class}:fd #{fd}>" end - {% if flag?(:wasm32) %} - def send_buffer_size : Int32 - raise NotImplementedError.new "Socket#send_buffer_size" - end - - def send_buffer_size=(val : Int32) - raise NotImplementedError.new "Socket#send_buffer_size=" - end - - def recv_buffer_size : Int32 - raise NotImplementedError.new "Socket#recv_buffer_size" - end - - def recv_buffer_size=(val : Int32) - raise NotImplementedError.new "Socket#recv_buffer_size=" - end - - def reuse_address? : Bool - raise NotImplementedError.new "Socket#reuse_address?" - end - - def reuse_address=(val : Bool) - raise NotImplementedError.new "Socket#reuse_address=" - end - - def reuse_port? : Bool - raise NotImplementedError.new "Socket#reuse_port?" - end - - def reuse_port=(val : Bool) - raise NotImplementedError.new "Socket#reuse_port=" - end - - def broadcast? : Bool - raise NotImplementedError.new "Socket#broadcast?" - end - - def broadcast=(val : Bool) - raise NotImplementedError.new "Socket#broadcast=" - end + def send_buffer_size : Int32 + system_send_buffer_size + end - def keepalive? - raise NotImplementedError.new "Socket#keepalive?" - end + def send_buffer_size=(val : Int32) + self.system_send_buffer_size = val + val + end - def keepalive=(val : Bool) - raise NotImplementedError.new "Socket#keepalive=" - end - {% else %} - def send_buffer_size : Int32 - getsockopt LibC::SO_SNDBUF, 0 - end + def recv_buffer_size : Int32 + system_recv_buffer_size + end - def send_buffer_size=(val : Int32) - setsockopt LibC::SO_SNDBUF, val - val - end + def recv_buffer_size=(val : Int32) + self.system_recv_buffer_size = val + val + end - def recv_buffer_size : Int32 - getsockopt LibC::SO_RCVBUF, 0 - end + def reuse_address? : Bool + system_reuse_address? + end - def recv_buffer_size=(val : Int32) - setsockopt LibC::SO_RCVBUF, val - val - end + def reuse_address=(val : Bool) + self.system_reuse_address = val + val + end - # SO_REUSEADDR, as used in posix, is always assumed on windows - # the SO_REUSEADDR flag on windows is the equivalent of SO_REUSEPORT on linux - # https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse#application-strategies - {% if flag?(:win32) %} - # the address component of a binding can always be reused on windows - def reuse_address? : Bool - true - end - - # there is no effect on windows - def reuse_address=(val : Bool) - val - end - {% else %} - def reuse_address? : Bool - getsockopt_bool LibC::SO_REUSEADDR - end - - def reuse_address=(val : Bool) - setsockopt_bool LibC::SO_REUSEADDR, val - end - {% end %} - - def reuse_port? : Bool - system_reuse_port? - end + def reuse_port? : Bool + system_reuse_port? + end - def reuse_port=(val : Bool) - self.system_reuse_port = val - end + def reuse_port=(val : Bool) + self.system_reuse_port = val + val + end - def broadcast? : Bool - getsockopt_bool LibC::SO_BROADCAST - end + def broadcast? : Bool + system_broadcast? + end - def broadcast=(val : Bool) - setsockopt_bool LibC::SO_BROADCAST, val - end + def broadcast=(val : Bool) + self.system_broadcast = val + val + end - def keepalive? - getsockopt_bool LibC::SO_KEEPALIVE - end + def keepalive? + system_keepalive? + end - def keepalive=(val : Bool) - setsockopt_bool LibC::SO_KEEPALIVE, val - end - {% end %} + def keepalive=(val : Bool) + self.system_keepalive = val + val + end def linger system_linger From f4b1e0992189c55af3b258001a253b30e063a174 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 21 Apr 2023 16:28:03 +0800 Subject: [PATCH 0465/1551] Handle NaNs when comparing `BigRational` against `Float` (#13350) --- spec/std/big/big_rational_spec.cr | 54 ++++++++++++++++++++----------- src/big/big_rational.cr | 9 +++--- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr index 7ebbc15f2a70..c4fbd4f43346 100644 --- a/spec/std/big/big_rational_spec.cr +++ b/spec/std/big/big_rational_spec.cr @@ -125,30 +125,46 @@ describe BigRational do BigDecimal.new("1.123").to_big_r.should eq(br(1123, 1000)) end - it "#<=>(:BigRational) and Comparable" do - a = br(11, 3) - l = br(10, 3) - e = a - g = br(12, 3) + describe "#<=>" do + it "BigRational and Comparable" do + a = br(11, 3) + l = br(10, 3) + e = a + g = br(12, 3) - # verify things aren't swapped - [l, e, g].each { |o| (a <=> o).should eq(a.to_f <=> o.to_f) } + # verify things aren't swapped + [l, e, g].each { |o| (a <=> o).should eq(a.to_f <=> o.to_f) } - test_comp(a, l, e, g) - end + test_comp(a, l, e, g) + end - it "#<=>(:Int) and Comparable" do - test_comp(br(10, 2), 4_i32, 5_i32, 6_i32) - test_comp(br(10, 2), 4_i64, 5_i64, 6_i64) - end + it "Int and Comparable" do + test_comp(br(10, 2), 4_i32, 5_i32, 6_i32) + test_comp(br(10, 2), 4_i64, 5_i64, 6_i64) + end - it "#<=>(:BigInt) and Comparable" do - test_comp(br(10, 2), BigInt.new(4), BigInt.new(5), BigInt.new(6)) - end + it "BigInt and Comparable" do + test_comp(br(10, 2), BigInt.new(4), BigInt.new(5), BigInt.new(6)) + end - it "#<=>(:Float) and Comparable" do - test_comp(br(10, 2), 4.0_f32, 5.0_f32, 6.0_f32) - test_comp(br(10, 2), 4.0_f64, 5.0_f64, 6.0_f64) + it "Float and Comparable" do + test_comp(br(10, 2), 4.0_f32, 5.0_f32, 6.0_f32) + test_comp(br(10, 2), 4.0_f64, 5.0_f64, 6.0_f64) + end + + it "compares against NaNs" do + (1.to_big_r <=> Float64::NAN).should be_nil + (1.to_big_r <=> Float32::NAN).should be_nil + (Float64::NAN <=> 1.to_big_r).should be_nil + (Float32::NAN <=> 1.to_big_r).should be_nil + + typeof(1.to_big_r <=> Float64::NAN).should eq(Int32?) + typeof(1.to_big_r <=> Float32::NAN).should eq(Int32?) + typeof(Float64::NAN <=> 1.to_big_r).should eq(Int32?) + typeof(Float32::NAN <=> 1.to_big_r).should eq(Int32?) + + typeof(1.to_big_r <=> 1.to_big_f).should eq(Int32) + end end it "#+" do diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index 76fa64032e57..d91f93a2e657 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -108,11 +108,11 @@ struct BigRational < Number LibGMP.mpq_cmp(mpq, other) end - def <=>(other : Float32 | Float64) - self <=> BigRational.new(other) + def <=>(other : Float::Primitive) + self <=> BigRational.new(other) unless other.nan? end - def <=>(other : Float) + def <=>(other : BigFloat) to_big_f <=> other.to_big_f end @@ -367,7 +367,8 @@ struct Float end def <=>(other : BigRational) - -(other <=> self) + cmp = other <=> self + -cmp if cmp end end From 5908aee7f6ad7c1ab94309ae86e3bd9624f72951 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 21 Apr 2023 16:28:28 +0800 Subject: [PATCH 0466/1551] Optimize `Deque#concat(Indexable)` (#13283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/deque_spec.cr | 54 ++++++++++++++++++++++++++++++ src/deque.cr | 75 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/spec/std/deque_spec.cr b/spec/std/deque_spec.cr index e1bc24bd4ed0..9aad0b1e53cb 100644 --- a/spec/std/deque_spec.cr +++ b/spec/std/deque_spec.cr @@ -37,6 +37,22 @@ end private alias RecursiveDeque = Deque(RecursiveDeque) +# Yields multiple forms of `Deque{}`, `Deque{0}`, `Deque{0, 1}`, `Deque{0, 1, 2}`, ..., +# `Deque{0, 1, 2, ..., max_size - 1}`. All deques have *max_size* as their +# capacity; each deque is yielded *max_size* times with a different start +# position in the internal buffer. Every deque is a fresh instance. +private def each_queue_repr(max_size, &) + (0..max_size).each do |size| + max_size.times do |i| + x = Deque(Int32).new(max_size, 0) + x.rotate!(i) + x.pop(max_size - size) + x.fill &.itself + yield x + end + end +end + describe "Deque" do describe "implementation" do it "works the same as array" do @@ -242,6 +258,44 @@ describe "Deque" do a.concat((4..1000)) a.should eq(Deque.new((1..1000).to_a)) end + + it "concats indexable" do + each_queue_repr(16) do |a| + size = a.size + b = Array.new(10, &.+(size)) + a.concat(b).should be(a) + a.should eq(Deque.new(size + 10, &.itself)) + end + + each_queue_repr(16) do |a| + size = a.size + b = Slice.new(10, &.+(size)) + a.concat(b).should be(a) + a.should eq(Deque.new(size + 10, &.itself)) + end + + each_queue_repr(16) do |a| + size = a.size + b = StaticArray(Int32, 10).new(&.+(size)) + a.concat(b).should be(a) + a.should eq(Deque.new(size + 10, &.itself)) + end + + each_queue_repr(16) do |a| + size = a.size + b = Deque.new(10, &.+(size)) + a.concat(b).should be(a) + a.should eq(Deque.new(size + 10, &.itself)) + end + end + + it "concats itself" do + each_queue_repr(16) do |a| + size = a.size + a.concat(a).should be(a) + a.should eq(Deque.new((0...size).to_a * 2)) + end + end end describe "delete" do diff --git a/src/deque.cr b/src/deque.cr index 2f8efe6ebd01..d2154bb462e6 100644 --- a/src/deque.cr +++ b/src/deque.cr @@ -179,7 +179,65 @@ class Deque(T) end # Appends the elements of *other* to `self`, and returns `self`. - def concat(other : Enumerable(T)) + # + # ``` + # deq = Deque{"a", "b"} + # deq.concat(Deque{"c", "d"}) + # deq # => Deque{"a", "b", "c", "d"} + # ``` + def concat(other : Indexable) : self + other_size = other.size + + resize_if_cant_insert(other_size) + + index = @start + @size + index -= @capacity if index >= @capacity + concat_indexable(other, index) + + @size += other_size + + self + end + + private def concat_indexable(other : Deque, index) + Deque.half_slices(other) do |slice| + index = concat_indexable(slice, index) + end + end + + private def concat_indexable(other : Array | StaticArray, index) + concat_indexable(Slice.new(other.to_unsafe, other.size), index) + end + + private def concat_indexable(other : Slice, index) + if index + other.size <= @capacity + # there is enough space after the last element; one copy will suffice + (@buffer + index).copy_from(other.to_unsafe, other.size) + index += other.size + index == @capacity ? 0 : index + else + # copy the first half of *other* to the end of the buffer, and then the + # remaining half to the start, which must be available after a call to + # `#resize_if_cant_insert` + first_half_size = @capacity - index + second_half_size = other.size - first_half_size + (@buffer + index).copy_from(other.to_unsafe, first_half_size) + @buffer.copy_from(other.to_unsafe + first_half_size, second_half_size) + second_half_size + end + end + + private def concat_indexable(other, index) + appender = (@buffer + index).appender + buffer_end = @buffer + @capacity + other.each do |elem| + appender << elem + appender = @buffer.appender if appender.pointer == buffer_end + end + end + + # :ditto: + def concat(other : Enumerable(T)) : self other.each do |x| push x end @@ -593,6 +651,14 @@ class Deque(T) @capacity * 2 end + private def calculate_new_capacity(new_size) + new_capacity = @capacity == 0 ? INITIAL_CAPACITY : @capacity + while new_capacity < new_size + new_capacity *= 2 + end + new_capacity + end + # behaves like `resize_if_cant_insert(1)` private def resize_if_cant_insert if @size >= @capacity @@ -600,6 +666,13 @@ class Deque(T) end end + private def resize_if_cant_insert(insert_size) + new_capacity = calculate_new_capacity(@size + insert_size) + if new_capacity > @capacity + resize_to_capacity(new_capacity) + end + end + private def resize_to_capacity(capacity) old_capacity, @capacity = @capacity, capacity From 8e6e7edfdb7ed15c71a0e290a6e12a69dd58bed0 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 21 Apr 2023 16:28:52 +0800 Subject: [PATCH 0467/1551] Skip `Time::Location.load_local` spec if unable to change time zone (#13355) --- spec/std/time/location_spec.cr | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/std/time/location_spec.cr b/spec/std/time/location_spec.cr index e3c073e8bdc6..ea52847383db 100644 --- a/spec/std/time/location_spec.cr +++ b/spec/std/time/location_spec.cr @@ -229,7 +229,9 @@ class Time::Location ) info.standardName.to_slice.copy_from "Central Europe Standard Time".to_utf16 info.daylightName.to_slice.copy_from "Central Europe Summer Time".to_utf16 - LibC.SetTimeZoneInformation(pointerof(info)) + if LibC.SetTimeZoneInformation(pointerof(info)) == 0 + pending! "Unable to set time zone" if WinError.value.error_privilege_not_held? + end location = Location.load_local location.zones.should eq [Time::Location::Zone.new("CET", 3600, false), Time::Location::Zone.new("CEST", 7200, true)] From 63150726ccda1b104774e3ffc764aa2118eae25f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 21 Apr 2023 18:18:15 +0200 Subject: [PATCH 0468/1551] Refactor JSON, YAML specs for #13337 for simplicity (#13358) --- spec/std/json/serializable_spec.cr | 9 +-------- spec/std/yaml/serializable_spec.cr | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index 8c934563b7ed..4eba9cc9fde0 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -468,13 +468,7 @@ end class JSONSomething include JSON::Serializable - property value : JSONAttrValue(Set(JSONSomethingElse)?)? -end - -class JSONSomethingElse - include JSON::Serializable - - property value : JSONAttrValue(Set(JSONSomethingElse)?)? + property value : JSONSomething? end describe "JSON mapping" do @@ -1122,6 +1116,5 @@ describe "JSON mapping" do it "fixes #13337" do JSONSomething.from_json(%({"value":{}})).value.should_not be_nil - JSONSomethingElse.from_json(%({"value":{}})).value.should_not be_nil end end diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 7e4364c2113a..9050381d45f4 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -401,13 +401,7 @@ end class YAMLSomething include YAML::Serializable - property value : YAMLAttrValue(Set(YAMLSomethingElse)?)? -end - -class YAMLSomethingElse - include YAML::Serializable - - property value : YAMLAttrValue(Set(YAMLSomethingElse)?)? + property value : YAMLSomething? end describe "YAML::Serializable" do @@ -1023,6 +1017,5 @@ describe "YAML::Serializable" do it "fixes #13337" do YAMLSomething.from_yaml(%({"value":{}})).value.should_not be_nil - YAMLSomethingElse.from_yaml(%({"value":{}})).value.should_not be_nil end end From 20594b7881b49c0ffeddf4edee00ab73a436110e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 22 Apr 2023 00:18:32 +0800 Subject: [PATCH 0469/1551] Fix `TCPSocket#tcp_keepalive_idle` on Windows (#13364) --- spec/std/socket/tcp_socket_spec.cr | 2 +- src/crystal/system/win32/socket.cr | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index 95c6f3967a61..9f06b4382323 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -128,7 +128,7 @@ describe TCPSocket, tags: "network" do end end - pending_win32 "settings" do + it "settings" do port = unused_local_port TCPServer.open("::", port) do |server| diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 748f8b5851bf..2cf8e646f5f0 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -449,11 +449,11 @@ module Crystal::System::Socket end private def system_tcp_keepalive_idle - getsockopt LibC::SO_KEEPALIVE, 0, level: ::Socket::Protocol::TCP + getsockopt LibC::TCP_KEEPIDLE, 0, level: ::Socket::Protocol::TCP end private def system_tcp_keepalive_idle=(val : Int) - setsockopt LibC::SO_KEEPALIVE, val, level: ::Socket::Protocol::TCP + setsockopt LibC::TCP_KEEPIDLE, val, level: ::Socket::Protocol::TCP val end From 7732579004eca663682685cb31b6fec6c8865556 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 22 Apr 2023 00:18:52 +0800 Subject: [PATCH 0470/1551] Fix client-side `TCPSocket#remote_address` on Windows (#13363) --- spec/std/http/server/server_spec.cr | 2 +- spec/std/socket/tcp_socket_spec.cr | 5 +---- src/crystal/system/win32/socket.cr | 14 +++++++++++++- src/lib_c/x86_64-windows-msvc/c/mswsock.cr | 3 ++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index 26e67229d77f..e5aa0ec261a7 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -472,7 +472,7 @@ describe HTTP::Server do end end - pending_win32 describe: "#remote_address / #local_address" do + describe "#remote_address / #local_address" do it "for http server" do remote_address = nil local_address = nil diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index 9f06b4382323..68c00ccd2e79 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -21,10 +21,7 @@ describe TCPSocket, tags: "network" do sock.local_address.port.should eq(port) sock.local_address.address.should eq(address) - # FIXME: This should work on win32 - {% unless flag?(:win32) %} - client.remote_address.port.should eq(port) - {% end %} + client.remote_address.port.should eq(port) sock.remote_address.address.should eq address end end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 2cf8e646f5f0..3781a8463ebd 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -127,7 +127,19 @@ module Crystal::System::Socket end if error - yield error + return yield error + end + + # from https://learn.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options: + # + # > This option is used with the ConnectEx, WSAConnectByList, and + # > WSAConnectByName functions. This option updates the properties of the + # > socket after the connection is established. This option should be set + # > if the getpeername, getsockname, getsockopt, setsockopt, or shutdown + # > functions are to be used on the connected socket. + optname = LibC::SO_UPDATE_CONNECT_CONTEXT + if LibC.setsockopt(fd, LibC::SOL_SOCKET, optname, nil, 0) == LibC::SOCKET_ERROR + return yield ::Socket::Error.from_wsa_error("setsockopt #{optname}") end end diff --git a/src/lib_c/x86_64-windows-msvc/c/mswsock.cr b/src/lib_c/x86_64-windows-msvc/c/mswsock.cr index 74ab85b97d28..dc87494cd25a 100644 --- a/src/lib_c/x86_64-windows-msvc/c/mswsock.cr +++ b/src/lib_c/x86_64-windows-msvc/c/mswsock.cr @@ -2,7 +2,8 @@ require "./guiddef" @[Link("mswsock")] lib LibC - SO_UPDATE_ACCEPT_CONTEXT = 0x700B + SO_UPDATE_ACCEPT_CONTEXT = 0x700B + SO_UPDATE_CONNECT_CONTEXT = 0x7010 alias AcceptEx = Proc(SOCKET, SOCKET, Void*, DWORD, DWORD, DWORD, DWORD*, OVERLAPPED*, BOOL) WSAID_ACCEPTEX = GUID.new(0xb5367df1, 0xcbac, 0x11cf, UInt8.static_array(0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92)) From 01c8f9f29145910ac8c4afceac33855a544f7c65 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 24 Apr 2023 17:59:52 +0800 Subject: [PATCH 0471/1551] Allow `/SUBSYSTEM:WINDOWS` on Windows (#13332) --- src/crystal/system/win32/file_descriptor.cr | 22 ++++++++++++++------- src/crystal/system/win32/process.cr | 4 +--- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index f9c0d525bc72..576fb64e53c8 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -54,8 +54,19 @@ module Crystal::System::FileDescriptor end private def windows_handle + FileDescriptor.windows_handle!(fd) + end + + def self.windows_handle(fd) + ret = LibC._get_osfhandle(fd) + return LibC::INVALID_HANDLE_VALUE if ret == -1 || ret == -2 + LibC::HANDLE.new(ret) + end + + def self.windows_handle!(fd) ret = LibC._get_osfhandle(fd) raise RuntimeError.from_errno("_get_osfhandle") if ret == -1 + raise RuntimeError.new("_get_osfhandle returned -2") if ret == -2 LibC::HANDLE.new(ret) end @@ -154,9 +165,7 @@ module Crystal::System::FileDescriptor end def self.pread(fd, buffer, offset) - handle = LibC._get_osfhandle(fd) - raise IO::Error.from_errno("_get_osfhandle") if handle == -1 - handle = LibC::HANDLE.new(handle) + handle = windows_handle!(fd) overlapped = LibC::OVERLAPPED.new overlapped.union.offset.offset = LibC::DWORD.new(offset) @@ -172,9 +181,8 @@ module Crystal::System::FileDescriptor def self.from_stdio(fd) console_handle = false - handle = LibC._get_osfhandle(fd) - if handle != -1 - handle = LibC::HANDLE.new(handle) + handle = windows_handle(fd) + if handle != LibC::INVALID_HANDLE_VALUE # TODO: use `out old_mode` after implementing interpreter out closured var old_mode = uninitialized LibC::DWORD if LibC.GetConsoleMode(handle, pointerof(old_mode)) != 0 @@ -187,7 +195,7 @@ module Crystal::System::FileDescriptor end end - io = IO::FileDescriptor.new(fd) + io = IO::FileDescriptor.new(fd, blocking: true) # Set sync or flush_on_newline as described in STDOUT and STDERR docs. # See https://crystal-lang.org/api/toplevel.html#STDERR if console_handle diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index b887d8521c46..a9bb85e9aa5a 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -182,9 +182,7 @@ struct Crystal::System::Process end private def self.handle_from_io(io : IO::FileDescriptor, parent_io) - ret = LibC._get_osfhandle(io.fd) - raise RuntimeError.from_winerror("_get_osfhandle") if ret == -1 - source_handle = LibC::HANDLE.new(ret) + source_handle = FileDescriptor.windows_handle!(io.fd) cur_proc = LibC.GetCurrentProcess if LibC.DuplicateHandle(cur_proc, source_handle, cur_proc, out new_handle, 0, true, LibC::DUPLICATE_SAME_ACCESS) == 0 From 264daee46a31542ec81c2845c9f73fb121238ae4 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 24 Apr 2023 20:00:09 +1000 Subject: [PATCH 0472/1551] add multicast support to win32 target (#13325) --- spec/std/socket/udp_socket_spec.cr | 7 +++---- src/lib_c/x86_64-windows-msvc/c/ws2def.cr | 23 ++++++++++++++++++++- src/lib_c/x86_64-windows-msvc/c/ws2ipdef.cr | 12 +++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr index 24236c2109cf..f767e2ee26db 100644 --- a/spec/std/socket/udp_socket_spec.cr +++ b/spec/std/socket/udp_socket_spec.cr @@ -72,7 +72,6 @@ describe UDPSocket, tags: "network" do server.close end - {% unless flag?(:win32) %} if {{ flag?(:darwin) }} && family == Socket::Family::INET6 # Darwin is failing to join IPv6 multicast groups on older versions. # However this is known to work on macOS Mojave with Darwin 18.2.0. @@ -160,13 +159,13 @@ describe UDPSocket, tags: "network" do sleep 100.milliseconds udp.close end - expect_raises(IO::Error, "Closed stream") { udp.receive } + expect_raises(IO::Error) { udp.receive } + udp.closed?.should be_true end end - {% end %} end - {% if flag?(:linux) %} + {% if flag?(:linux) || flag?(:win32) %} it "sends broadcast message" do port = unused_local_port diff --git a/src/lib_c/x86_64-windows-msvc/c/ws2def.cr b/src/lib_c/x86_64-windows-msvc/c/ws2def.cr index 1e5b45071742..9fc19857f4a3 100644 --- a/src/lib_c/x86_64-windows-msvc/c/ws2def.cr +++ b/src/lib_c/x86_64-windows-msvc/c/ws2def.cr @@ -111,7 +111,28 @@ lib LibC SIO_GET_EXTENSION_FUNCTION_POINTER = IOC_INOUT | IOC_WS2 | 6 - IPPROTO_IP = 0 + IPPROTO_IP = 0 + IPPROTO_IPV6 = 41 + + IP_MULTICAST_IF = 9 + IPV6_MULTICAST_IF = 9 + + IP_MULTICAST_TTL = 10 + IPV6_MULTICAST_HOPS = 10 + + IP_MULTICAST_LOOP = 11 + IPV6_MULTICAST_LOOP = 11 + + IP_ADD_MEMBERSHIP = 12 + IP_DROP_MEMBERSHIP = 13 + + # JOIN and LEAVE are the same as ADD and DROP + # https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-ipv6-socket-options + IPV6_ADD_MEMBERSHIP = 12 + IPV6_JOIN_GROUP = 12 + + IPV6_DROP_MEMBERSHIP = 13 + IPV6_LEAVE_GROUP = 13 enum IPPROTO IPPROTO_HOPOPTS = 0 # IPv6 Hop-by-Hop options diff --git a/src/lib_c/x86_64-windows-msvc/c/ws2ipdef.cr b/src/lib_c/x86_64-windows-msvc/c/ws2ipdef.cr index dcf609ba2c04..d9da08387e33 100644 --- a/src/lib_c/x86_64-windows-msvc/c/ws2ipdef.cr +++ b/src/lib_c/x86_64-windows-msvc/c/ws2ipdef.cr @@ -17,6 +17,18 @@ lib LibC sin_zero : StaticArray(CHAR, 8) end + # https://learn.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-ip_mreq + struct IpMreq + imr_multiaddr : InAddr + imr_interface : InAddr + end + + # https://learn.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-ipv6_mreq + struct Ipv6Mreq + ipv6mr_multiaddr : In6Addr + ipv6mr_interface : ULong + end + TCP_EXPEDITED_1122 = 0x0002 TCP_KEEPALIVE = 3 TCP_MAXSEG = 4 From 7195379a0376cb91c6190190d8e61cffbf32f4c7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 24 Apr 2023 18:00:38 +0800 Subject: [PATCH 0473/1551] Optimize `BigInt.new(Int::Primitive)` (#13303) --- src/big/big_int.cr | 43 +++++++++++++++++++++++++++++---------- src/big/lib_gmp.cr | 12 +++++++++-- src/int.cr | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 13 deletions(-) diff --git a/src/big/big_int.cr b/src/big/big_int.cr index 5c3bf7e2ef65..0bf709034e11 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -47,21 +47,42 @@ struct BigInt < Int end # Creates a `BigInt` from the given *num*. - def initialize(num : Int::Signed) - if LibC::Long::MIN <= num <= LibC::Long::MAX - LibGMP.init_set_si(out @mpz, num) + def self.new(num : Int::Primitive) + if LibGMP::SI::MIN <= num <= LibGMP::UI::MAX + if num <= LibGMP::SI::MAX + LibGMP.init_set_si(out mpz1, LibGMP::SI.new!(num)) + new(mpz1) + else + LibGMP.init_set_ui(out mpz2, LibGMP::UI.new!(num)) + new(mpz2) + end else - LibGMP.init_set_str(out @mpz, num.to_s, 10) + negative = num < 0 + num = num.abs_unsigned + capacity = (num.bit_length - 1) // (sizeof(LibGMP::MpLimb) * 8) + 1 + + unsafe_build(capacity) do |limbs| + appender = limbs.to_unsafe.appender + limbs.size.times do + appender << LibGMP::MpLimb.new!(num) + num = num.unsafe_shr(sizeof(LibGMP::MpLimb) * 8) + end + {capacity, negative} + end end end - # :ditto: - def initialize(num : Int::Unsigned) - if num <= LibC::ULong::MAX - LibGMP.init_set_ui(out @mpz, num) - else - LibGMP.init_set_str(out @mpz, num.to_s, 10) - end + private def self.unsafe_build(capacity : Int, & : Slice(LibGMP::MpLimb) -> {Int, Bool}) + # https://gmplib.org/manual/Initializing-Integers: + # + # > In preparation for an operation, GMP often allocates one limb more than + # > ultimately needed. To make sure GMP will not perform reallocation for x, + # > you need to add the number of bits in mp_limb_t to n. + LibGMP.init2(out mpz, (capacity + 1) * sizeof(LibGMP::MpLimb) * 8) + limbs = LibGMP.limbs_write(pointerof(mpz), capacity) + size, negative = yield Slice.new(limbs, capacity) + LibGMP.limbs_finish(pointerof(mpz), size * (negative ? -1 : 1)) + new(mpz) end # :ditto: diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index 7292dbaff2e7..ec0f853b3068 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -29,18 +29,21 @@ lib LibGMP {% if flag?(:win32) && flag?(:bits64) %} alias MpExp = LibC::Long + alias MpSize = LibC::LongLong alias MpLimb = LibC::ULongLong {% elsif flag?(:bits64) %} alias MpExp = Int64 + alias MpSize = LibC::Long alias MpLimb = LibC::ULong {% else %} alias MpExp = Int32 + alias MpSize = LibC::Long alias MpLimb = LibC::ULong {% end %} struct MPZ - _mp_alloc : Int32 - _mp_size : Int32 + _mp_alloc : Int + _mp_size : Int _mp_d : MpLimb* end @@ -144,6 +147,11 @@ lib LibGMP fun lcm_ui = __gmpz_lcm_ui(rop : MPZ*, op1 : MPZ*, op2 : UI) fun remove = __gmpz_remove(rop : MPZ*, op : MPZ*, f : MPZ*) : BitcntT + # Special Functions + + fun limbs_write = __gmpz_limbs_write(x : MPZ*, n : MpSize) : MpLimb* + fun limbs_finish = __gmpz_limbs_finish(x : MPZ*, s : MpSize) + # MPQ struct MPQ _mp_num : MPZ diff --git a/src/int.cr b/src/int.cr index 7dd84d9a0ed4..a9ef18e09bd7 100644 --- a/src/int.cr +++ b/src/int.cr @@ -890,6 +890,11 @@ struct Int8 0_i8 - self end + # :nodoc: + def abs_unsigned : UInt8 + self < 0 ? 0_u8 &- self : to_u8! + end + def popcount : Int8 Intrinsics.popcount8(self) end @@ -990,6 +995,11 @@ struct Int16 0_i16 - self end + # :nodoc: + def abs_unsigned : UInt16 + self < 0 ? 0_u16 &- self : to_u16! + end + def popcount : Int16 Intrinsics.popcount16(self) end @@ -1090,6 +1100,11 @@ struct Int32 0 - self end + # :nodoc: + def abs_unsigned : UInt32 + self < 0 ? 0_u32 &- self : to_u32! + end + def popcount : Int32 Intrinsics.popcount32(self) end @@ -1190,6 +1205,11 @@ struct Int64 0_i64 - self end + # :nodoc: + def abs_unsigned : UInt64 + self < 0 ? 0_u64 &- self : to_u64! + end + def popcount : Int64 Intrinsics.popcount64(self) end @@ -1293,6 +1313,11 @@ struct Int128 Int128.new(0) - self end + # :nodoc: + def abs_unsigned : UInt128 + self < 0 ? UInt128.new(0) &- self : to_u128! + end + def popcount Intrinsics.popcount128(self) end @@ -1397,6 +1422,11 @@ struct UInt8 self end + # :nodoc: + def abs_unsigned : self + self + end + def popcount : Int8 Intrinsics.popcount8(self) end @@ -1501,6 +1531,11 @@ struct UInt16 self end + # :nodoc: + def abs_unsigned : self + self + end + def popcount : Int16 Intrinsics.popcount16(self) end @@ -1605,6 +1640,11 @@ struct UInt32 self end + # :nodoc: + def abs_unsigned : self + self + end + def popcount : Int32 Intrinsics.popcount32(self) end @@ -1709,6 +1749,11 @@ struct UInt64 self end + # :nodoc: + def abs_unsigned : self + self + end + def popcount : Int64 Intrinsics.popcount64(self) end @@ -1815,6 +1860,11 @@ struct UInt128 self end + # :nodoc: + def abs_unsigned : self + self + end + def popcount Intrinsics.popcount128(self) end From 692e95f230fa72af0b26d9d5b61f2f654112f5d3 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 24 Apr 2023 19:59:00 +0800 Subject: [PATCH 0474/1551] Fix `IO::FileDescriptor`'s `STDIN` mode spec (#13365) --- spec/std/io/file_descriptor_spec.cr | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/spec/std/io/file_descriptor_spec.cr b/spec/std/io/file_descriptor_spec.cr index d6fd920dfe30..cec34b13310f 100644 --- a/spec/std/io/file_descriptor_spec.cr +++ b/spec/std/io/file_descriptor_spec.cr @@ -5,12 +5,20 @@ class IO::FileDescriptor include FinalizeCounter end +private def shell_command(command) + {% if flag?(:win32) %} + "cmd.exe /c #{Process.quote(command)}" + {% else %} + "/bin/sh -c #{Process.quote(command)}" + {% end %} +end + describe IO::FileDescriptor do - pending_win32 "reopen STDIN with the right mode" do + it "reopen STDIN with the right mode" do code = %q(puts "#{STDIN.blocking} #{STDIN.info.type}") compile_source(code) do |binpath| - `#{Process.quote(binpath)} < #{Process.quote(binpath)}`.chomp.should eq("true File") - `echo "" | #{Process.quote(binpath)}`.chomp.should eq("false Pipe") + `#{shell_command %(#{Process.quote(binpath)} < #{Process.quote(binpath)})}`.chomp.should eq("true File") + `#{shell_command %(echo "" | #{Process.quote(binpath)})}`.chomp.should eq("#{{{ flag?(:win32) }}} Pipe") end end From d7d9f87be55913ad1583d38fc31c6b22d05cd70b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 24 Apr 2023 19:59:05 +0800 Subject: [PATCH 0475/1551] Build samples on Windows CI (#13334) --- .github/workflows/win.yml | 3 +++ samples/Makefile.win | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 344b0af94d0d..3ccedaa7fea6 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -373,3 +373,6 @@ jobs: - name: Run primitives specs run: make -f Makefile.win primitives_spec + + - name: Build samples + run: make -f Makefile.win samples diff --git a/samples/Makefile.win b/samples/Makefile.win index b9625941ce4a..205487e48cec 100644 --- a/samples/Makefile.win +++ b/samples/Makefile.win @@ -1,7 +1,14 @@ +all: + +MAKEFLAGS += --no-builtin-rules +.SUFFIXES: + +SHELL := cmd.exe + MKDIR = if not exist $1 mkdir $1 RMDIR = if exist $1 rd /S /Q $1 -CRYSTAL := ../bin/crystal# Crystal compiler to use +CRYSTAL := ..\bin\crystal.bat# Crystal compiler to use O := .build# Output directory BUILDABLE_SOURCES := $(wildcard *.cr llvm/*.cr compiler/*.cr) From 27f395066e256a162c1c6694c476b23aa2253484 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 24 Apr 2023 19:59:14 +0800 Subject: [PATCH 0476/1551] Fix `bin\crystal.ps1` writing to standard error stream (#13372) --- bin/crystal.ps1 | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/bin/crystal.ps1 b/bin/crystal.ps1 index cf0a0fbe5750..16940094439f 100644 --- a/bin/crystal.ps1 +++ b/bin/crystal.ps1 @@ -35,6 +35,37 @@ function Resolve-RealPath { $realPath } +# adopted from https://stackoverflow.com/a/15669365 +function Write-StdErr { +<# +.SYNOPSIS +Writes text to stderr when running in a regular console window, +to the host''s error stream otherwise. + +.DESCRIPTION +Writing to true stderr allows you to write a well-behaved CLI +as a PS script that can be invoked from a batch file, for instance. + +Note that PS by default sends ALL its streams to *stdout* when invoked from +cmd.exe. +#> + param( + [Parameter(Mandatory)] [string] $Line, + $ForegroundColor + ) + if ($Host.Name -eq 'ConsoleHost') { + if ($ForegroundColor) { + [Console]::ForegroundColor = $ForegroundColor + } + [Console]::Error.WriteLine($Line) + if ($ForegroundColor) { + [Console]::ResetColor() + } + } else { + [void] $host.ui.WriteErrorLine($Line) + } +} + # https://stackoverflow.com/a/43030126 function Invoke-WithEnvironment { <# @@ -225,7 +256,7 @@ Invoke-WithEnvironment @{ CRYSTAL_LIBRARY_PATH = $env:CRYSTAL_LIBRARY_PATH } { if (!$env:CRYSTAL_PATH.Contains("$CrystalRoot\src")) { - Write-Host "CRYSTAL_PATH env variable does not contain $CrystalRoot\src" -ForegroundColor DarkYellow + Write-StdErr "CRYSTAL_PATH env variable does not contain $CrystalRoot\src" -ForegroundColor DarkYellow } if (!$env:CRYSTAL_CONFIG_LIBRARY_PATH -or !$env:CRYSTAL_LIBRARY_PATH) { @@ -238,12 +269,12 @@ Invoke-WithEnvironment @{ } if (Test-Path -Path "$CrystalDir/crystal.exe" -PathType Leaf) { - Write-Host "Using compiled compiler at $($CrystalDir.Replace($pwd, "."))\crystal.exe" -ForegroundColor DarkYellow + Write-StdErr "Using compiled compiler at $($CrystalDir.Replace($pwd, "."))\crystal.exe" -ForegroundColor DarkYellow Exec-Process "$CrystalDir/crystal.exe" $CrystalArgs } else { $CrystalCmd = Get-Command $env:CRYSTAL -CommandType ExternalScript, Application -ErrorAction SilentlyContinue if (!$CrystalCmd) { - Write-Host 'You need to have a crystal executable in your path! or set CRYSTAL env variable' -ForegroundColor Red + Write-StdErr 'You need to have a crystal executable in your path! or set CRYSTAL env variable' -ForegroundColor Red Exit 1 } else { $CrystalInstalledDir = Split-Path -Path $CrystalCmd.Path -Parent From 93f8fd0976f0fe10955e2934230ca52bbef0ff51 Mon Sep 17 00:00:00 2001 From: Will Leinweber Date: Mon, 24 Apr 2023 17:18:49 +0200 Subject: [PATCH 0477/1551] Add message about non-release mode to `crystal --version` (#13254) --- Makefile | 4 ++-- src/compiler/crystal/config.cr | 20 ++++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 7a34d8836097..490d6b83917d 100644 --- a/Makefile +++ b/Makefile @@ -41,9 +41,9 @@ override FLAGS += -D strict_multi_assign -D preview_overload_order $(if $(releas SPEC_WARNINGS_OFF := --exclude-warnings spec/std --exclude-warnings spec/compiler --exclude-warnings spec/primitives SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) )$(if $(order),--order=$(order) ) CRYSTAL_CONFIG_LIBRARY_PATH := '$$ORIGIN/../lib/crystal' -CRYSTAL_CONFIG_BUILD_COMMIT := $(shell git rev-parse --short HEAD 2> /dev/null) +CRYSTAL_CONFIG_BUILD_COMMIT ?= $(shell git rev-parse --short HEAD 2> /dev/null) CRYSTAL_CONFIG_PATH := '$$ORIGIN/../share/crystal/src' -SOURCE_DATE_EPOCH := $(shell (git show -s --format=%ct HEAD || stat -c "%Y" Makefile || stat -f "%m" Makefile) 2> /dev/null) +SOURCE_DATE_EPOCH ?= $(shell (git show -s --format=%ct HEAD || stat -c "%Y" Makefile || stat -f "%m" Makefile) 2> /dev/null) ifeq ($(shell command -v ld.lld >/dev/null && uname -s),Linux) EXPORT_CC ?= CC="$(CC) -fuse-ld=lld" endif diff --git a/src/compiler/crystal/config.cr b/src/compiler/crystal/config.cr index eb77b9e8886f..2f71aa49815c 100644 --- a/src/compiler/crystal/config.cr +++ b/src/compiler/crystal/config.cr @@ -15,13 +15,17 @@ module Crystal end def self.description - formatted_sha = "[#{build_commit}] " if build_commit - <<-DOC - Crystal #{version} #{formatted_sha}(#{date}) + String.build do |io| + io << "Crystal " << version + io << " [" << build_commit << "]" if build_commit + io << " (" << date << ")" unless date.empty? - LLVM: #{llvm_version} - Default target: #{self.host_target} - DOC + io << "\n\nThe compiler was not built in release mode." unless release_mode? + + io << "\n\nLLVM: " << llvm_version + io << "\nDefault target: " << host_target + io << "\n" + end end def self.build_commit @@ -40,6 +44,10 @@ module Crystal end end + def self.release_mode? + {{ flag?(:release) }} + end + @@host_target : Crystal::Codegen::Target? def self.host_target : Crystal::Codegen::Target From adc939aa1004b4ad662e3ec7045dbd41a604a4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 25 Apr 2023 09:58:56 +0200 Subject: [PATCH 0478/1551] Add Regex options support inspection (#13354) --- spec/std/regex_spec.cr | 10 ++++++++++ src/regex.cr | 16 ++++++++++++++++ src/regex/pcre.cr | 8 ++++++++ src/regex/pcre2.cr | 8 ++++++++ 4 files changed, 42 insertions(+) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index bfb520b7170c..aca655179f03 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -540,4 +540,14 @@ describe "Regex" do end ) end + + it ".supports_compile_options?" do + Regex.supports_compile_options?(:anchored).should be_true + Regex.supports_compile_options?(:endanchored).should eq Regex::Engine.version_number >= {10, 0} + end + + it ".supports_match_options?" do + Regex.supports_match_options?(:anchored).should be_true + Regex.supports_match_options?(:endanchored).should eq Regex::Engine.version_number >= {10, 0} + end end diff --git a/src/regex.cr b/src/regex.cr index 834212ffe70c..aae2a8e1a643 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -285,6 +285,14 @@ class Regex # This alias is supposed to replace `Options`. alias CompileOptions = Options + # Returns `true` if the regex engine supports all *options* flags when compiling a pattern. + def self.supports_compile_options?(options : CompileOptions) : Bool + options.each do |flag| + return false unless Engine.supports_compile_flag?(flag) + end + true + end + # Represents options passed to regex match methods such as `Regex#match`. @[Flags] enum MatchOptions @@ -308,6 +316,14 @@ class Regex NO_UTF_CHECK end + # Returns `true` if the regex engine supports all *options* flags when matching a pattern. + def self.supports_match_options?(options : MatchOptions) : Bool + options.each do |flag| + return false unless Engine.supports_match_flag?(flag) + end + true + end + # Returns a `Regex::CompileOptions` representing the optional flags applied to this `Regex`. # # ``` diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr index d3cbad25c3ec..2f2faa514c17 100644 --- a/src/regex/pcre.cr +++ b/src/regex/pcre.cr @@ -61,6 +61,10 @@ module Regex::PCRE flag end + def self.supports_compile_flag?(options) + !options.endanchored? && !options.match_invalid_utf? + end + private def pcre_match_options(options) flag = 0 Regex::Options.each do |option| @@ -113,6 +117,10 @@ module Regex::PCRE flag end + def self.supports_match_flag?(options) + !options.endanchored? && !options.no_jit? + end + def finalize LibPCRE.free_study @extra {% unless flag?(:interpreted) %} diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 80ef62137ac5..4e7884fef65f 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -90,6 +90,10 @@ module Regex::PCRE2 flag end + def self.supports_compile_flag?(options) + true + end + private def pcre2_match_options(options) flag = 0 Regex::Options.each do |option| @@ -140,6 +144,10 @@ module Regex::PCRE2 flag end + def self.supports_match_flag?(options) + true + end + protected def self.error_impl(source) code = PCRE2.compile(source, LibPCRE2::UTF | LibPCRE2::DUPNAMES | LibPCRE2::UCP) do |error_message| return error_message From 67e0364f4fcf46ba27f78197b4c2c7543cfc80c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 25 Apr 2023 09:59:42 +0200 Subject: [PATCH 0479/1551] Update previous Crystal release - 1.8.1 (#13373) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 6 +++--- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c672cad87847..b8caa7d14367 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.8.0/crystal-1.8.0-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 45eace56968f..447e5dc8be21 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.8.0-build + image: crystallang/crystal:1.8.1-build name: "Test Interpreter" steps: - uses: actions/checkout@v3 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.8.0-build + image: crystallang/crystal:1.8.1-build name: Build interpreter steps: - uses: actions/checkout@v3 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.8.0-build + image: crystallang/crystal:1.8.1-build strategy: matrix: part: [0, 1, 2, 3] diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 3070efd70171..5717e2f28605 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -29,7 +29,7 @@ jobs: flags: "FLAGS=-Dwithout_ffi USE_PCRE1=true" - crystal_bootstrap_version: 1.7.3 flags: "" - - crystal_bootstrap_version: 1.8.0 + - crystal_bootstrap_version: 1.8.1 flags: "" steps: - name: Download Crystal source diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 2b86b41542b0..b03e2d68d148 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -43,7 +43,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.8.0" + crystal: "1.8.1" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index c572dafae072..b30bbb07a491 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.8.0-alpine + container: crystallang/crystal:1.8.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -27,7 +27,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.8.0-alpine + container: crystallang/crystal:1.8.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -42,7 +42,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.8.0-alpine + container: crystallang/crystal:1.8.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index 3ce84de5cf66..e4bfe14d600c 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.8.0-alpine + container: crystallang/crystal:1.8.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.8.0-alpine + container: crystallang/crystal:1.8.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index db451ef6933e..0cf511f3175f 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.8.0-build + container: crystallang/crystal:1.8.1-build steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 3ccedaa7fea6..0ef6a4031f81 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -19,7 +19,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.8.0" + crystal: "1.8.1" - name: Download Crystal source uses: actions/checkout@v3 diff --git a/bin/ci b/bin/ci index a200626b7226..0c53ee6acafe 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.8.0/crystal-1.8.0-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.8.0-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.8.1-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.8.0}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.8.1}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 3b844ba588bc..9a7f2898af17 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.8.0/crystal-1.8.0-1-darwin-universal.tar.gz"; - sha256 = "sha256:1rwcx9bxxhfyw6zqd14drrqzhk72932zv2jgbrzrpyfmk2i63gng"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:0p25mghlrb4q0q6fwm9gksm8giq3dlr49d5kc4h7141pfdj1m9in"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.8.0/crystal-1.8.0-1-darwin-universal.tar.gz"; - sha256 = "sha256:1rwcx9bxxhfyw6zqd14drrqzhk72932zv2jgbrzrpyfmk2i63gng"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:0p25mghlrb4q0q6fwm9gksm8giq3dlr49d5kc4h7141pfdj1m9in"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.8.0/crystal-1.8.0-1-linux-x86_64.tar.gz"; - sha256 = "sha256:0bnzbqil7avndz7qyijzixy4jsp3lk8dvyh6j7qarvjmrz5lj7m9"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-linux-x86_64.tar.gz"; + sha256 = "sha256:0ymd76yj157bydpk05l5vgxg6fsm432rhc98ixrzhcr552x3g6gw"; }; }.${pkgs.stdenv.system}); From 2668bd82ba2a55a4b8fdd017fb9c5dc9dd4f1a12 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 25 Apr 2023 16:05:42 +0800 Subject: [PATCH 0480/1551] Implement `Process.exec` on Windows (#13374) --- spec/std/process_spec.cr | 12 ++-- src/crystal/system/win32/file_descriptor.cr | 20 ++----- src/crystal/system/win32/process.cr | 63 ++++++++++++++++++++- src/lib_c/x86_64-windows-msvc/c/io.cr | 1 + src/process.cr | 2 - 5 files changed, 71 insertions(+), 27 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index e9e6848828eb..f2e7175c6f4b 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -414,15 +414,15 @@ describe Process do end end end + {% end %} - describe ".exec" do - it "gets error from exec" do - expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do - Process.exec("foobarbaz") - end + describe ".exec" do + it "gets error from exec" do + expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do + Process.exec("foobarbaz") end end - {% end %} + end describe ".chroot" do {% if flag?(:unix) && !flag?(:android) %} diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 576fb64e53c8..f4f20472d9bd 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -114,22 +114,10 @@ module Crystal::System::FileDescriptor end private def system_reopen(other : IO::FileDescriptor) - {% if LibC.has_method?("dup3") %} - # dup doesn't copy the CLOEXEC flag, so copy it manually using dup3 - flags = other.close_on_exec? ? LibC::O_CLOEXEC : 0 - if LibC.dup3(other.fd, self.fd, flags) == -1 - raise IO::Error.from_errno("Could not reopen file descriptor") - end - {% else %} - # dup doesn't copy the CLOEXEC flag, copy it manually to the new - if LibC._dup2(other.fd, self.fd) == -1 - raise IO::Error.from_errno("Could not reopen file descriptor") - end - - if other.close_on_exec? - self.close_on_exec = true - end - {% end %} + # Windows doesn't implement the CLOEXEC flag + if LibC._dup2(other.fd, self.fd) == -1 + raise IO::Error.from_errno("Could not reopen file descriptor") + end # Mark the handle open, since we had to have dup'd a live handle. @closed = false diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index a9bb85e9aa5a..24bb4c17ffa3 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -203,6 +203,8 @@ struct Crystal::System::Process process_info = LibC::PROCESS_INFORMATION.new + command_args = ::Process.quote_windows(command_args) unless command_args.is_a?(String) + if LibC.CreateProcessW( nil, System.to_wstr(command_args), nil, nil, true, LibC::CREATE_UNICODE_ENVIRONMENT, make_env_block(env, clear_env), chdir.try { |str| System.to_wstr(str) }, @@ -226,7 +228,7 @@ struct Crystal::System::Process process_info end - def self.prepare_args(command : String, args : Enumerable(String)?, shell : Bool) : String + def self.prepare_args(command : String, args : Enumerable(String)?, shell : Bool) if shell if args raise NotImplementedError.new("Process with args and shell: true is not supported on Windows") @@ -235,12 +237,67 @@ struct Crystal::System::Process else command_args = [command] command_args.concat(args) if args - ::Process.quote_windows(command_args) + command_args end end + private def self.try_replace(command_args, env, clear_env, input, output, error, chdir) + reopen_io(input, ORIGINAL_STDIN) + reopen_io(output, ORIGINAL_STDOUT) + reopen_io(error, ORIGINAL_STDERR) + + ENV.clear if clear_env + env.try &.each do |key, val| + if val + ENV[key] = val + else + ENV.delete key + end + end + + ::Dir.cd(chdir) if chdir + + if command_args.is_a?(String) + command = System.to_wstr(command_args) + argv = [command] + else + command = System.to_wstr(command_args[0]) + argv = command_args.map { |arg| System.to_wstr(arg) } + end + argv << Pointer(LibC::WCHAR).null + + LibC._wexecvp(command, argv) + end + def self.replace(command_args, env, clear_env, input, output, error, chdir) : NoReturn - raise NotImplementedError.new("Process.exec") + try_replace(command_args, env, clear_env, input, output, error, chdir) + raise_exception_from_errno(command_args.is_a?(String) ? command_args : command_args[0]) + end + + private def self.raise_exception_from_errno(command, errno = Errno.value) + case errno + when Errno::EACCES, Errno::ENOENT + raise ::File::Error.from_os_error("Error executing process", errno, file: command) + else + raise IO::Error.from_os_error("Error executing process: '#{command}'", errno) + end + end + + private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor) + src_io = to_real_fd(src_io) + + dst_io.reopen(src_io) + dst_io.blocking = true + dst_io.close_on_exec = false + end + + private def self.to_real_fd(fd : IO::FileDescriptor) + case fd + when STDIN then ORIGINAL_STDIN + when STDOUT then ORIGINAL_STDOUT + when STDERR then ORIGINAL_STDERR + else fd + end end def self.chroot(path) diff --git a/src/lib_c/x86_64-windows-msvc/c/io.cr b/src/lib_c/x86_64-windows-msvc/c/io.cr index 94a5b7b582a3..8aaae731cfd6 100644 --- a/src/lib_c/x86_64-windows-msvc/c/io.cr +++ b/src/lib_c/x86_64-windows-msvc/c/io.cr @@ -11,6 +11,7 @@ lib LibC fun _wchmod(filename : WCHAR*, pmode : Int) : Int fun _wunlink(filename : WCHAR*) : Int fun _wmktemp_s(template : WCHAR*, sizeInChars : SizeT) : ErrnoT + fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT fun _chsize_s(fd : Int, size : Int64) : ErrnoT fun _get_osfhandle(fd : Int) : IntPtrT fun _pipe(pfds : Int*, psize : UInt, textmode : Int) : Int diff --git a/src/process.cr b/src/process.cr index ada6dd953b8f..888c0db655f5 100644 --- a/src/process.cr +++ b/src/process.cr @@ -169,8 +169,6 @@ class Process # Replaces the current process with a new one. This function never returns. # - # Available only on Unix-like operating systems. - # # Raises `IO::Error` if executing the command fails (for example if the executable doesn't exist). def self.exec(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, input : ExecStdio = Redirect::Inherit, output : ExecStdio = Redirect::Inherit, error : ExecStdio = Redirect::Inherit, chdir : Path | String? = nil) : NoReturn From e4f6f27bfcca0f9351640376111b7c8e1ba3cacf Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Tue, 25 Apr 2023 11:32:24 -0300 Subject: [PATCH 0481/1551] PR template: adding a line about force-pushes (#12794) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- .github/PULL_REQUEST_TEMPLATE/pull_request_template.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index 5d76092f9bbf..3178ea563554 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -5,3 +5,5 @@ We thank you for helping improve Crystal. In order to ease the reviewing process 2. Focus the PR to the referred issue, and restraint from adding unrelated changes/additions. We do welcome another PR if you fixed another issue. 3. If your change is big, consider breaking it into several smaller PRs. In general, the smaller the change, the quicker we can review it. + +4. ⚠️ Please do not amend previous commits and force push to the PR branch. This makes reviews much harder because reference to previous state is hidden. From 6d8bca9b8842f36e0b2365a201062c21ec6b18f0 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 25 Apr 2023 22:33:10 +0800 Subject: [PATCH 0482/1551] Fix doc for return type of `Crystal::Macros::Case#else` (#13385) --- src/compiler/crystal/macros.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 5f4268d36d09..97e5856c30f4 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1525,7 +1525,7 @@ module Crystal::Macros end # Returns the `else` of this `case`. - def else : ArrayLiteral(When) + def else : ASTNode end # Returns whether this `case` is exhaustive (`case ... in`). From 08a4600d633a128bcdbdbe25e6eee288512bf2e9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 25 Apr 2023 22:33:25 +0800 Subject: [PATCH 0483/1551] Document `target` variable in Makefiles (#13384) --- Makefile | 1 + Makefile.win | 1 + 2 files changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 490d6b83917d..e52b33798518 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ debug ?= ## Add symbolic debug info verbose ?= ## Run specs in verbose mode junit_output ?= ## Path to output junit results static ?= ## Enable static linking +target ?= ## Cross-compilation target interpreter ?= ## Enable interpreter feature check ?= ## Enable only check when running format order ?=random ## Enable order for spec execution (values: "default" | "random" | seed number) diff --git a/Makefile.win b/Makefile.win index 5c8c39fe215e..6927ef514bda 100644 --- a/Makefile.win +++ b/Makefile.win @@ -30,6 +30,7 @@ debug ?= ## Add symbolic debug info verbose ?= ## Run specs in verbose mode junit_output ?= ## Path to output junit results static ?= ## Enable static linking +target ?= ## Cross-compilation target interpreter ?= ## Enable interpreter feature check ?= ## Enable only check when running format order ?= ## Enable order for spec execution (values: "default" | "random" | seed number) From 0413243558b4dc6c8884a345d309936336c64213 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 26 Apr 2023 03:43:55 +0800 Subject: [PATCH 0484/1551] Less verbose output in `Makefile.win` (#13383) --- Makefile.win | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile.win b/Makefile.win index 6927ef514bda..4b5f4fff4ebf 100644 --- a/Makefile.win +++ b/Makefile.win @@ -45,9 +45,9 @@ RC := rc.exe GLOB = $(shell dir $1 /B /S) MKDIR = if not exist $1 mkdir $1 CP = copy /B /Y $1 $2 -CPDIR = robocopy /E /NJH /NJS $1 $2 & if %%ERRORLEVEL%% GEQ 8 exit /B 1 +CPDIR = robocopy /E /NJH /NJS /NS /NC /NP $1 $2 & if %%ERRORLEVEL%% GEQ 8 exit /B 1 INSTALL = copy /B /Y $1 $2 -INSTALLDIR = robocopy /E /NJH /NJS $1 $2 & if %%ERRORLEVEL%% GEQ 8 exit /B 1 +INSTALLDIR = robocopy /E /NJH /NJS /NS /NC /NP $1 $2 & if %%ERRORLEVEL%% GEQ 8 exit /B 1 MV = move /Y $1 $2 RM = if exist $1 del /F /Q $1 RMDIR = if exist $1 rd /S /Q $1 @@ -208,7 +208,7 @@ $(O)\crystal.exe: $(DEPS) $(SOURCES) $(O)\crystal.res $(O)\crystal.res: $(O)\crystal.rc @$(call MKDIR,"$(O)") - $(RC) /Fo "$@" "$<" + $(RC) /nologo /Fo "$@" "$<" $(O)\crystal.rc: $(MAKEFILE_LIST) @$(call MKDIR,"$(O)") @@ -216,7 +216,7 @@ $(O)\crystal.rc: $(MAKEFILE_LIST) $(LLVM_EXT_OBJ): $(LLVM_EXT_DIR)\llvm_ext.cc $(call check_llvm_config) - $(CXX) /c $(CXXFLAGS) "/Fo$@" "$<" $(shell $(LLVM_CONFIG) --cxxflags) + $(CXX) /nologo /c $(CXXFLAGS) "/Fo$@" "$<" $(shell $(LLVM_CONFIG) --cxxflags) .PHONY: clean clean: clean_crystal ## Clean up built directories and files From 665c21b7ba21a40d70da930f1e851c9b8461040f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 26 Apr 2023 03:44:12 +0800 Subject: [PATCH 0485/1551] Fix `String#scan` with empty `Regex` match at multibyte char (#13387) --- spec/std/string_spec.cr | 6 +++++- src/string.cr | 13 ++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index b988c55fe719..2a0861908117 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2266,7 +2266,7 @@ describe "String" do end end - describe "scan" do + describe "#scan" do it "does without block" do a = "cruel world" a.scan(/\w+/).map(&.[0]).should eq(["cruel", "world"]) @@ -2304,6 +2304,10 @@ describe "String" do "hello world".scan(/\w+|(?= )/).map(&.[0]).should eq(["hello", "", "world"]) end + it "works when match is empty, multibyte char" do + "\u{80}\u{800}\u{10000}".scan(/()/).map(&.begin).should eq([0, 1, 2, 3]) + end + it "works with strings with block" do res = [] of String "bla bla ablf".scan("bl") { |s| res << s } diff --git a/src/string.cr b/src/string.cr index 103474584626..34837cb41e76 100644 --- a/src/string.cr +++ b/src/string.cr @@ -1723,8 +1723,8 @@ class String if single_byte_optimizable? unsafe_byte_slice_string(1, bytesize - 1) else - reader = Char::Reader.new(self) - unsafe_byte_slice_string(reader.current_char_width, bytesize - reader.current_char_width) + first_char_bytesize = char_bytesize_at(0) + unsafe_byte_slice_string(first_char_bytesize, bytesize - first_char_bytesize) end end @@ -2532,8 +2532,7 @@ class String byte_index = char_index_to_byte_index(index) raise IndexError.new unless byte_index - reader = Char::Reader.new(self, pos: byte_index) - width = reader.current_char_width + width = char_bytesize_at(byte_index) replacement_width = replacement.bytesize new_bytesize = bytesize - width + replacement_width @@ -2816,7 +2815,7 @@ class String if string.bytesize == 0 # The pattern matched an empty result. We must advance one character to avoid stagnation. - byte_offset = index + Char::Reader.new(self, pos: byte_offset).current_char_width + byte_offset = index + char_bytesize_at(byte_offset) last_byte_offset = index else byte_offset = index + string.bytesize @@ -2874,7 +2873,7 @@ class String if str.bytesize == 0 # The pattern matched an empty result. We must advance one character to avoid stagnation. - byte_offset = index + Char::Reader.new(self, pos: byte_offset).current_char_width + byte_offset = index + char_bytesize_at(byte_offset) last_byte_offset = index else byte_offset = index + str.bytesize @@ -4597,7 +4596,7 @@ class String $~ = match yield match match_bytesize = match.byte_end(0) - index - match_bytesize += 1 if match_bytesize == 0 + match_bytesize += char_bytesize_at(byte_offset) if match_bytesize == 0 byte_offset = index + match_bytesize end From ba5e2b96d71f5d38478bf0cbe8c9aced2db734bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 26 Apr 2023 18:18:01 +0200 Subject: [PATCH 0486/1551] Fix `Array#flatten` to discard `Iterator::Stop` (#13388) --- spec/std/array_spec.cr | 4 +++- src/array.cr | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/spec/std/array_spec.cr b/spec/std/array_spec.cr index 57e2e27c4334..d118125ccd9a 100644 --- a/spec/std/array_spec.cr +++ b/spec/std/array_spec.cr @@ -2116,7 +2116,9 @@ describe "Array" do t = [4, 5, 6, [7, 8]] u = [9, [10, 11].each] a = [s, t, u, 12, 13] - a.flatten.to_a.should eq([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) + result = a.flatten.to_a + result.should eq([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) + result.should be_a(Array(Int32)) end it "#skip" do diff --git a/src/array.cr b/src/array.cr index f243d4a674e4..d9ce3992db93 100644 --- a/src/array.cr +++ b/src/array.cr @@ -2224,10 +2224,9 @@ class Array(T) def self.element_type(ary) case ary - when Array - element_type(ary.first) - when Iterator - element_type(ary.next) + when Array, Iterator + ary.each { |elem| return element_type(elem) } + ::raise "" else ary end From 535d08a47b7b4860d6d0888b1eacc90a7f56ed24 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 27 Apr 2023 00:20:10 +0800 Subject: [PATCH 0487/1551] Respect `%CC%` on Windows (#13376) --- spec/compiler/loader/spec_helper.cr | 2 +- spec/support/tempfile.cr | 15 ++++++++------- src/compiler/crystal/compiler.cr | 16 ++++++++-------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/spec/compiler/loader/spec_helper.cr b/spec/compiler/loader/spec_helper.cr index 74285076bdc5..282af7871f81 100644 --- a/spec/compiler/loader/spec_helper.cr +++ b/spec/compiler/loader/spec_helper.cr @@ -7,7 +7,7 @@ def build_c_dynlib(c_filename, target_dir = SPEC_CRYSTAL_LOADER_LIB_PATH) {% if flag?(:msvc) %} o_basename = o_filename.rchop(".lib") - `cl.exe /nologo /LD #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_basename}")} #{Process.quote("/Fe#{o_basename}")}` + `#{ENV["CC"]? || "cl.exe"} /nologo /LD #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_basename}")} #{Process.quote("/Fe#{o_basename}")}` {% else %} `#{ENV["CC"]? || "cc"} -shared -fvisibility=hidden #{Process.quote(c_filename)} -o #{Process.quote(o_filename)}` {% end %} diff --git a/spec/support/tempfile.cr b/spec/support/tempfile.cr index 41aa209abde9..43c7df1a2ae6 100644 --- a/spec/support/tempfile.cr +++ b/spec/support/tempfile.cr @@ -53,13 +53,14 @@ def with_temp_c_object_file(c_code, *, filename = "temp_c", file = __FILE__, &) {% if flag?(:msvc) %} # following is based on `Crystal::Compiler#linker_command` - cl = "cl.exe" - - if msvc_path = Crystal::System::VisualStudio.find_latest_msvc_path - # we won't be cross-compiling the specs binaries, so host and target - # bits are identical - bits = {{ flag?(:bits64) ? "x64" : "x86" }} - cl = Process.quote(msvc_path.join("bin", "Host#{bits}", bits, "cl.exe").to_s) + unless cl = ENV["CC"]? + cl = "cl.exe" + if msvc_path = Crystal::System::VisualStudio.find_latest_msvc_path + # we won't be cross-compiling the specs binaries, so host and target + # bits are identical + bits = {{ flag?(:bits64) ? "x64" : "x86" }} + cl = Process.quote(msvc_path.join("bin", "Host#{bits}", bits, cl).to_s) + end end `#{cl} /nologo /c #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_filename}")}`.should be_truthy diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index bfa36f9586e9..423defd6cb28 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -20,8 +20,8 @@ module Crystal # A Compiler parses source code, type checks it and # optionally generates an executable. class Compiler - CC = ENV["CC"]? || "cc" - CL = "cl.exe" + private DEFAULT_LINKER = ENV["CC"]? || "cc" + private MSVC_LINKER = ENV["CC"]? || "cl.exe" # A source to the compiler: its filename and source code. record Source, @@ -332,7 +332,7 @@ module Crystal object_arg = Process.quote_windows(object_names) output_arg = Process.quote_windows("/Fe#{output_filename}") - cl = CL + linker = MSVC_LINKER link_args = [] of String # if the compiler and the target both have the `msvc` flag, we are not @@ -354,7 +354,7 @@ module Crystal # use exact path for compiler instead of relying on `PATH` # (letter case shouldn't matter in most cases but being exact doesn't hurt here) target_bits = target_bits.sub("arm", "ARM") - cl = Process.quote_windows(msvc_path.join("bin", "Host#{host_bits}", target_bits, "cl.exe").to_s) + linker = Process.quote_windows(msvc_path.join("bin", "Host#{host_bits}", target_bits, "cl.exe").to_s) unless ENV.has_key?("CC") end end {% end %} @@ -365,7 +365,7 @@ module Crystal @link_flags.try { |flags| link_args << flags } args = %(/nologo #{object_arg} #{output_arg} /link #{link_args.join(' ')}).gsub("\n", " ") - cmd = "#{cl} #{args}" + cmd = "#{linker} #{args}" if cmd.to_utf16.size > 32000 # The command line would be too big, pass the args through a UTF-16-encoded file instead. @@ -376,10 +376,10 @@ module Crystal args_filename = "#{output_dir}/linker_args.txt" File.write(args_filename, args_bytes) - cmd = "#{cl} #{Process.quote_windows("@" + args_filename)}" + cmd = "#{linker} #{Process.quote_windows("@" + args_filename)}" end - {cl, cmd, nil} + {linker, cmd, nil} elsif program.has_flag? "wasm32" link_flags = @link_flags || "" {"wasm-ld", %(wasm-ld "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} -lc #{program.lib_flags}), object_names} @@ -387,7 +387,7 @@ module Crystal link_flags = @link_flags || "" link_flags += " -rdynamic" - {CC, %(#{CC} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names} + {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names} end end From e4853743dbffa5731860408dfac9cada75121276 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Thu, 27 Apr 2023 03:42:06 -0400 Subject: [PATCH 0488/1551] Do not cancel in progress CI jobs for master branch (#13393) --- .github/workflows/aarch64.yml | 2 +- .github/workflows/interpreter.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/openssl.yml | 2 +- .github/workflows/regex-engine.yml | 2 +- .github/workflows/smoke.yml | 2 +- .github/workflows/wasm32.yml | 2 +- .github/workflows/win.yml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index 9d699a1098f5..ca8796333544 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -4,7 +4,7 @@ on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: aarch64-musl-build: diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 447e5dc8be21..067687c969ad 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -4,7 +4,7 @@ on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: SPEC_SPLIT_DOTS: 160 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 5717e2f28605..ca052d39a154 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -4,7 +4,7 @@ on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: TRAVIS_OS_NAME: linux diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index b03e2d68d148..e446fc5cbbd7 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -4,7 +4,7 @@ on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: SPEC_SPLIT_DOTS: 160 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 3cdbf8412db1..0e2523b05cc4 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -4,7 +4,7 @@ on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: SPEC_SPLIT_DOTS: 160 diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index b30bbb07a491..74f20e4ace9d 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -4,7 +4,7 @@ on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: openssl3: diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index e4bfe14d600c..461706b9f323 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -4,7 +4,7 @@ on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: pcre: diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 4c457f991adf..58302463a126 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -32,7 +32,7 @@ on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: TRAVIS_OS_NAME: linux diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 0cf511f3175f..884b568a481f 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -4,7 +4,7 @@ on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: SPEC_SPLIT_DOTS: 160 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 0ef6a4031f81..23d6a455200f 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -4,7 +4,7 @@ on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: x86_64-windows: From b5450a2a4e09cb5043dbfd3b79a062548d5f2fbd Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Thu, 27 Apr 2023 03:42:18 -0400 Subject: [PATCH 0489/1551] More accurate macro errors (#13260) --- spec/compiler/semantic/macro_spec.cr | 151 +++++++++++++++--- src/compiler/crystal/interpreter/compiler.cr | 4 +- src/compiler/crystal/macros/interpreter.cr | 2 + src/compiler/crystal/macros/methods.cr | 8 +- src/compiler/crystal/semantic/call.cr | 10 ++ src/compiler/crystal/semantic/exception.cr | 11 +- .../crystal/semantic/semantic_visitor.cr | 18 ++- 7 files changed, 165 insertions(+), 39 deletions(-) diff --git a/spec/compiler/semantic/macro_spec.cr b/spec/compiler/semantic/macro_spec.cr index 4e75f621387b..190903a8cbba 100644 --- a/spec/compiler/semantic/macro_spec.cr +++ b/spec/compiler/semantic/macro_spec.cr @@ -271,39 +271,142 @@ describe "Semantic: macro" do CRYSTAL end - it "executes raise inside macro" do - ex = assert_error(<<-CRYSTAL, "OH NO") - macro foo - {{ raise "OH NO" }} - end + describe "raise" do + describe "inside macro" do + describe "without node" do + it "does not contain `expanding macro`" do + ex = assert_error(<<-CRYSTAL, "OH NO") + macro foo + {{ raise "OH NO" }} + end - foo - CRYSTAL + foo + CRYSTAL - ex.to_s.should_not contain("expanding macro") - end + ex.to_s.should_not contain("expanding macro") + end - it "executes raise inside macro, with node (#5669)" do - ex = assert_error(<<-CRYSTAL, "OH") - macro foo(x) - {{ x.raise "OH\nNO" }} + it "supports an empty message (#8631)" do + assert_error(<<-CRYSTAL, "") + macro foo + {{ raise "" }} + end + + foo + CRYSTAL + end + + it "renders both frames (#7147)" do + ex = assert_error(<<-CRYSTAL, "OH NO") + macro macro_raise(node) + {% raise "OH NO" %} + end + + macro_raise 10 + CRYSTAL + + ex.to_s.should contain "OH NO" + ex.to_s.should contain "error in line 2" + ex.to_s.should contain "error in line 5" + ex.to_s.scan("error in line").size.should eq 2 + end end - foo(1) - CRYSTAL + describe "with node" do + it "contains the message and not `expanding macro` (#5669)" do + ex = assert_error(<<-CRYSTAL, "OH") + macro foo(x) + {{ x.raise "OH\nNO" }} + end - ex.to_s.should contain "NO" - ex.to_s.should_not contain("expanding macro") - end + foo(1) + CRYSTAL - it "executes raise inside macro, with empty message (#8631)" do - assert_error(<<-CRYSTAL, "") - macro foo - {{ raise "" }} + ex.to_s.should contain "NO" + ex.to_s.should_not contain("expanding macro") + end + + it "renders both frames (#7147)" do + ex = assert_error(<<-'CRYSTAL', "OH") + macro macro_raise_on(arg) + {% arg.raise "OH NO" %} + end + + macro_raise_on 123 + CRYSTAL + + ex.to_s.should contain "OH NO" + ex.to_s.should contain "error in line 5" + ex.to_s.scan("error in line").size.should eq 2 + end + + it "pointing at the correct node in complex/nested macro (#7147)" do + ex = assert_error(<<-'CRYSTAL', "Value method must be an instance method") + class Child + def self.value : Nil + end + end + + module ExampleModule + macro calculate_value + {% begin %} + {% + if method = Child.class.methods.find &.name.stringify.==("value") + method.raise "Value method must be an instance method." + else + raise "BUG: Didn't find value method." + end + %} + {% end %} + end + + class_getter value : Nil do + calculate_value + end + end + + ExampleModule.value + CRYSTAL + + ex.to_s.should contain "error in line 20" + ex.to_s.should contain "error in line 2" + ex.to_s.scan("error in line").size.should eq 2 + end + + # TODO: Remove this spec once symbols literals have their location fixed + it "points to caller when missing node location information (#7147)" do + ex = assert_error(<<-'CRYSTAL', "foo") + macro macro_raise_on(arg) + {% arg.raise "foo" %} + end + + macro_raise_on :this + CRYSTAL + + ex.to_s.should contain "error in line 5" + ex.to_s.scan("error in line").size.should eq 1 + end end + end - foo - CRYSTAL + describe "inside method" do + describe "without node" do + it "renders both frames (#7147)" do + ex = assert_error(<<-CRYSTAL, "OH") + def foo(x) + {% raise "OH NO" %} + end + + foo 1 + CRYSTAL + + ex.to_s.should contain "OH NO" + ex.to_s.should contain "error in line 2" + ex.to_s.should contain "error in line 5" + ex.to_s.scan("error in line").size.should eq 2 + end + end + end end it "can specify tuple as return type" do diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index a6bed4c4b38f..8f59b63ec667 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -1622,7 +1622,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor begin create_compiled_def(call, target_def) rescue ex : Crystal::TypeException - node.raise ex, inner: ex + node.raise ex.message, inner: ex end call compiled_def, node: node @@ -1873,7 +1873,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor begin create_compiled_def(node, target_def) rescue ex : Crystal::TypeException - node.raise ex, inner: ex + node.raise ex.message, inner: ex end if (block = node.block) && !block.fun_literal diff --git a/src/compiler/crystal/macros/interpreter.cr b/src/compiler/crystal/macros/interpreter.cr index 7da3a4dac39d..af76e087ced3 100644 --- a/src/compiler/crystal/macros/interpreter.cr +++ b/src/compiler/crystal/macros/interpreter.cr @@ -361,6 +361,7 @@ module Crystal begin @last = receiver.interpret(node.name, args, named_args, node.block, self, node.name_location) rescue ex : MacroRaiseException + # Re-raise to avoid the logic in the other rescue blocks and to retain the original location raise ex rescue ex : Crystal::CodeError node.raise ex.message, inner: ex @@ -369,6 +370,7 @@ module Crystal end else # no receiver: special calls + # may raise `Crystal::TopLevelMacroRaiseException` interpret_top_level_call node end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 00557567861f..484ca665862a 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -254,7 +254,7 @@ module Crystal end def interpret_raise(node) - macro_raise(node, node.args, self) + macro_raise(node, node.args, self, Crystal::TopLevelMacroRaiseException) end def interpret_warning(node) @@ -387,7 +387,7 @@ module Crystal when "class_name" interpret_check_args { class_name } when "raise" - macro_raise self, args, interpreter + macro_raise self, args, interpreter, Crystal::MacroRaiseException when "warning" macro_warning self, args, interpreter when "filename" @@ -2676,14 +2676,14 @@ private def visibility_to_symbol(visibility) Crystal::SymbolLiteral.new(visibility_name) end -private def macro_raise(node, args, interpreter) +private def macro_raise(node, args, interpreter, exception_type) msg = args.map do |arg| arg.accept interpreter interpreter.last.to_macro_id end msg = msg.join " " - node.raise msg, exception_type: Crystal::MacroRaiseException + node.raise msg, exception_type: exception_type end private def macro_warning(node, args, interpreter) diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index 06f576a36106..9e8b038b4f8c 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -1101,6 +1101,16 @@ class Crystal::Call def bubbling_exception(&) yield + rescue ex : Crystal::TopLevelMacroRaiseException + # Sets the last frame to the method call that includes the top level macro raise re-raised within `SemanticVisitor#eval_macro`. + # The first frame will be the actual actual `#raise` method call. + ex.inner = Crystal::MacroRaiseException.for_node self, ex.message + + ::raise ex + rescue ex : Crystal::MacroRaiseException + # Raise another exception on this node, keeping the original as the inner exception. + # This will insert this node into the trace as the new first frame. + self.raise ex.message, ex, exception_type: Crystal::MacroRaiseException rescue ex : Crystal::CodeError if obj = @obj if name == "initialize" diff --git a/src/compiler/crystal/semantic/exception.cr b/src/compiler/crystal/semantic/exception.cr index 09a29438fbe0..10142789d281 100644 --- a/src/compiler/crystal/semantic/exception.cr +++ b/src/compiler/crystal/semantic/exception.cr @@ -39,14 +39,6 @@ module Crystal def initialize(message, @line_number, @column_number : Int32, @filename, @size, @inner = nil) @error_trace = true - # If the inner exception is a macro raise, we replace this exception's - # message with that message. In this way the error message will - # look like a regular message produced by the compiler, and not - # because of an incorrect macro expansion. - if inner.is_a?(MacroRaiseException) - message = inner.message - @inner = nil - end super(message) end @@ -302,6 +294,9 @@ module Crystal class MacroRaiseException < TypeException end + class TopLevelMacroRaiseException < MacroRaiseException + end + class SkipMacroException < ::Exception getter expanded_before_skip : String getter macro_expansion_pragmas : Hash(Int32, Array(Lexer::LocPragma))? diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index 03c28631f1d2..0b686db9dc40 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -454,8 +454,24 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor def eval_macro(node, &) yield + rescue ex : TopLevelMacroRaiseException + # If the node that caused a top level macro raise is a `Call`, it denotes it happened within the context of a macro. + # In this case, we want the inner most exception to be the call of the macro itself so that it's the last frame in the trace. + # This will make the actual `#raise` method call be the first frame. + if node.is_a? Call + ex.inner = Crystal::MacroRaiseException.for_node node, ex.message + end + + # Otherwise, if the current node is _NOT_ a `Call`, it denotes a top level raise within a method. + # In this case, we want the same behavior as if it were a `Call`, but do not want to set the inner exception here since that will be handled via `Call#bubbling_exception`. + # So just re-raise the exception to keep the original location intact. + raise ex rescue ex : MacroRaiseException - node.raise ex.message, exception_type: MacroRaiseException + # Raise another exception on this node, keeping the original as the inner exception. + # This will retain the location of the node specific raise as the last frame, while also adding in this node into the trace. + # + # If the original exception does not have a location, it'll essentially be dropped and this node will take its place as the last frame. + node.raise ex.message, ex, exception_type: Crystal::MacroRaiseException rescue ex : Crystal::CodeError node.raise "expanding macro", ex end From 8c76ff16ed109aff2d06b9a12cf09fda6ef7f542 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 27 Apr 2023 15:42:26 +0800 Subject: [PATCH 0490/1551] Fix `Dir#info` on Windows (#13395) --- spec/std/dir_spec.cr | 5 ++-- src/crystal/system/win32/dir.cr | 51 ++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index 4fc2cf95d135..60e20cea581f 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -68,15 +68,14 @@ describe "Dir" do end end - # TODO: do we even want this? - pending_win32 "tests empty? on a directory path to a file" do + it "tests empty? on a directory path to a file" do expect_raises(File::Error, "Error opening directory: '#{datapath("dir", "f1.txt", "/").inspect_unquoted}'") do Dir.empty?(datapath("dir", "f1.txt", "/")) end end end - pending_win32 "tests info on existing directory" do + it "tests info on existing directory" do Dir.open(datapath) do |dir| info = dir.info info.directory?.should be_true diff --git a/src/crystal/system/win32/dir.cr b/src/crystal/system/win32/dir.cr index 661fc39ae08c..7e426be33207 100644 --- a/src/crystal/system/win32/dir.cr +++ b/src/crystal/system/win32/dir.cr @@ -6,10 +6,11 @@ require "c/processenv" module Crystal::System::Dir private class DirHandle - property handle : LibC::HANDLE + property iter_handle : LibC::HANDLE + property file_handle : LibC::HANDLE = LibC::INVALID_HANDLE_VALUE getter query : LibC::LPWSTR - def initialize(@handle, @query) + def initialize(@iter_handle, @query) end end @@ -22,11 +23,11 @@ module Crystal::System::Dir end def self.next_entry(dir : DirHandle, path : String) : Entry? - if dir.handle == LibC::INVALID_HANDLE_VALUE + if dir.iter_handle == LibC::INVALID_HANDLE_VALUE # Directory is at start, use FindFirstFile handle = LibC.FindFirstFileW(dir.query, out data) if handle != LibC::INVALID_HANDLE_VALUE - dir.handle = handle + dir.iter_handle = handle data_to_entry(data) else error = WinError.value @@ -38,7 +39,7 @@ module Crystal::System::Dir end else # Use FindNextFile - if LibC.FindNextFileW(dir.handle, out data_) != 0 + if LibC.FindNextFileW(dir.iter_handle, out data_) != 0 data_to_entry(data_) else error = WinError.value @@ -64,24 +65,34 @@ module Crystal::System::Dir end def self.info(dir : DirHandle, path) : ::File::Info - if dir.handle == LibC::INVALID_HANDLE_VALUE - handle = LibC.FindFirstFileW(dir.query, out data) - begin - Crystal::System::FileDescriptor.system_info handle, LibC::FILE_TYPE_DISK - ensure - close(handle, path) rescue nil + if dir.file_handle == LibC::INVALID_HANDLE_VALUE + handle = LibC.CreateFileW( + System.to_wstr(path), + LibC::FILE_READ_ATTRIBUTES, + LibC::FILE_SHARE_READ | LibC::FILE_SHARE_WRITE | LibC::FILE_SHARE_DELETE, + nil, + LibC::OPEN_EXISTING, + LibC::FILE_FLAG_BACKUP_SEMANTICS, + LibC::HANDLE.null, + ) + + if handle == LibC::INVALID_HANDLE_VALUE + raise ::File::Error.from_winerror("Unable to get directory info", file: path) end - else - Crystal::System::FileDescriptor.system_info dir.handle, LibC::FILE_TYPE_DISK + + dir.file_handle = handle end + + Crystal::System::FileDescriptor.system_info dir.file_handle, LibC::FILE_TYPE_DISK end def self.close(dir : DirHandle, path : String) : Nil - close(dir.handle, path) - dir.handle = LibC::INVALID_HANDLE_VALUE + close_iter(dir.iter_handle, path) + close_file(dir.file_handle, path) + dir.iter_handle = dir.file_handle = LibC::INVALID_HANDLE_VALUE end - def self.close(handle : LibC::HANDLE, path : String) : Nil + private def self.close_iter(handle : LibC::HANDLE, path : String) : Nil return if handle == LibC::INVALID_HANDLE_VALUE if LibC.FindClose(handle) == 0 @@ -89,6 +100,14 @@ module Crystal::System::Dir end end + private def self.close_file(handle : LibC::HANDLE, path : String) : Nil + return if handle == LibC::INVALID_HANDLE_VALUE + + if LibC.CloseHandle(handle) == 0 + raise ::File::Error.from_winerror("CloseHandle", file: path) + end + end + def self.current : String System.retry_wstr_buffer do |buffer, small_buf| len = LibC.GetCurrentDirectoryW(buffer.size, buffer) From 35c0c73fe4848db181955a0db9d14546f964e9d6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 28 Apr 2023 01:02:42 +0800 Subject: [PATCH 0491/1551] Fix `Log::Metadata#dup` crash with 2+ entries (#13369) --- spec/std/log/metadata_spec.cr | 8 ++++++++ src/log/metadata.cr | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/spec/std/log/metadata_spec.cr b/spec/std/log/metadata_spec.cr index 884a4f9e2898..ba0b9c1a616d 100644 --- a/spec/std/log/metadata_spec.cr +++ b/spec/std/log/metadata_spec.cr @@ -28,6 +28,14 @@ describe Log::Metadata do m({a: 1}).extend({} of Symbol => String).should_not be_empty end + describe "#dup" do + it "creates a shallow copy" do + Log::Metadata.empty.dup.should eq(Log::Metadata.empty) + m({a: 1}).dup.should eq(m({a: 1})) + m({a: 1, b: 3}).dup.should eq(m({a: 1, b: 3})) + end + end + it "extend" do m({a: 1}).extend({b: 2}).should eq(m({a: 1, b: 2})) m({a: 1, b: 3}).extend({b: 2}).should eq(m({a: 1, b: 2})) diff --git a/src/log/metadata.cr b/src/log/metadata.cr index 258b1b817c52..b1ea8ae38fa9 100644 --- a/src/log/metadata.cr +++ b/src/log/metadata.cr @@ -37,6 +37,10 @@ class Log::Metadata data end + def dup : self + self + end + protected def setup(@parent : Metadata?, entries : NamedTuple | Hash) @size = @overridden_size = entries.size @max_total_size = @size + (@parent.try(&.max_total_size) || 0) From 3b14f80d1a972c736b2b848bbe3440f8ba962813 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 28 Apr 2023 16:23:30 +0800 Subject: [PATCH 0492/1551] Windows: open standard streams in binary mode (#13397) --- spec/std/io/file_descriptor_spec.cr | 28 +++++++++++++++++++++ src/crystal/system/win32/file_descriptor.cr | 1 + 2 files changed, 29 insertions(+) diff --git a/spec/std/io/file_descriptor_spec.cr b/spec/std/io/file_descriptor_spec.cr index cec34b13310f..2391d97c03c9 100644 --- a/spec/std/io/file_descriptor_spec.cr +++ b/spec/std/io/file_descriptor_spec.cr @@ -37,6 +37,34 @@ describe IO::FileDescriptor do end end + it "opens STDIN in binary mode" do + code = %q(print STDIN.gets_to_end.includes?('\r')) + compile_source(code) do |binpath| + io_in = IO::Memory.new("foo\r\n") + io_out = IO::Memory.new + Process.run(binpath, input: io_in, output: io_out) + io_out.to_s.should eq("true") + end + end + + it "opens STDOUT in binary mode" do + code = %q(puts "foo") + compile_source(code) do |binpath| + io = IO::Memory.new + Process.run(binpath, output: io) + io.to_s.should eq("foo\n") + end + end + + it "opens STDERR in binary mode" do + code = %q(STDERR.puts "foo") + compile_source(code) do |binpath| + io = IO::Memory.new + Process.run(binpath, error: io) + io.to_s.should eq("foo\n") + end + end + it "does not close if close_on_finalize is false" do pipes = [] of IO::FileDescriptor assert_finalizes("fd") do diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index f4f20472d9bd..d99dd471f240 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -171,6 +171,7 @@ module Crystal::System::FileDescriptor console_handle = false handle = windows_handle(fd) if handle != LibC::INVALID_HANDLE_VALUE + LibC._setmode fd, LibC::O_BINARY # TODO: use `out old_mode` after implementing interpreter out closured var old_mode = uninitialized LibC::DWORD if LibC.GetConsoleMode(handle, pointerof(old_mode)) != 0 From 8bc95c1c7e77f1d4d28d35d1eff5d35780f03efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Fri, 28 Apr 2023 10:03:28 -0700 Subject: [PATCH 0493/1551] Parser: Don't skip the token immediately after `lib` name (#13407) --- spec/compiler/parser/parser_spec.cr | 2 ++ src/compiler/crystal/syntax/parser.cr | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 9a804b8f05a4..93c2a98d3d55 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2129,6 +2129,8 @@ module Crystal it_parses "macro foo; bar class: 1; end", Macro.new("foo", body: MacroLiteral.new(" bar class: 1; ")) + assert_syntax_error "lib Foo%end", %(unexpected token: "%") + describe "end locations" do assert_end_location "nil" assert_end_location "false" diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 68d51731115f..4d18ce7ef1aa 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -5589,7 +5589,7 @@ module Crystal name_location = @token.location name = parse_path - next_token_skip_statement_end + skip_statement_end body = push_visibility { parse_lib_body_expressions } From dfa8fe46a87d674505b44907ec61f66119fa0c22 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 29 Apr 2023 01:03:43 +0800 Subject: [PATCH 0494/1551] Support asynchronous `IO.pipe` on Windows (#13362) --- .../crystal/tools/doc/project_info_spec.cr | 2 +- spec/std/http/client/client_spec.cr | 2 +- spec/std/http/spec_helper.cr | 38 +++--- spec/std/io/io_spec.cr | 8 +- spec/std/log/io_backend_spec.cr | 2 +- spec/std/oauth2/client_spec.cr | 2 +- spec/std/process_spec.cr | 8 +- src/crystal/system/unix/file_descriptor.cr | 4 + src/crystal/system/wasi/file_descriptor.cr | 3 + src/crystal/system/win32/file_descriptor.cr | 85 ++++++++---- src/crystal/system/win32/socket.cr | 53 +++++++- src/io/file_descriptor.cr | 4 +- src/io/overlapped.cr | 124 +++++++++--------- src/lib_c/x86_64-windows-msvc/c/fileapi.cr | 14 +- src/lib_c/x86_64-windows-msvc/c/ioapiset.cr | 7 + src/lib_c/x86_64-windows-msvc/c/minwinbase.cr | 2 +- src/lib_c/x86_64-windows-msvc/c/winbase.cr | 6 +- src/log/io_backend.cr | 2 +- 18 files changed, 241 insertions(+), 125 deletions(-) diff --git a/spec/compiler/crystal/tools/doc/project_info_spec.cr b/spec/compiler/crystal/tools/doc/project_info_spec.cr index 9e220e4e3fc3..61bf20c2da67 100644 --- a/spec/compiler/crystal/tools/doc/project_info_spec.cr +++ b/spec/compiler/crystal/tools/doc/project_info_spec.cr @@ -33,7 +33,7 @@ describe Crystal::Doc::ProjectInfo do File.write("shard.yml", "name: foo\nversion: 1.0") end - pending_win32 "git missing" do + it "git missing" do Crystal::Git.executable = "git-missing-executable" assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "1.0", refname: nil)) diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index b5d522088d31..4c9da8db7ad7 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -200,7 +200,7 @@ module HTTP end end - pending_win32 "will retry a broken socket" do + it "will retry a broken socket" do server = HTTP::Server.new do |context| context.response.output.print "foo" context.response.output.close diff --git a/spec/std/http/spec_helper.cr b/spec/std/http/spec_helper.cr index 9bf88c82763f..410e211e4dbe 100644 --- a/spec/std/http/spec_helper.cr +++ b/spec/std/http/spec_helper.cr @@ -61,29 +61,27 @@ end def run_handler(handler, &) done = Channel(Exception?).new - begin - IO::Stapled.pipe do |server_io, client_io| - processor = HTTP::Server::RequestProcessor.new(handler) - f = spawn do - processor.process(server_io, server_io) - rescue exc - done.send exc - else - done.send nil - end + IO::Stapled.pipe do |server_io, client_io| + processor = HTTP::Server::RequestProcessor.new(handler) + f = spawn do + processor.process(server_io, server_io) + rescue exc + done.send exc + else + done.send nil + end - client = HTTP::Client.new(client_io) + client = HTTP::Client.new(client_io) - begin - wait_until_blocked f + begin + wait_until_blocked f - yield client - ensure - processor.close - server_io.close - if exc = done.receive - raise exc - end + yield client + ensure + processor.close + server_io.close + if exc = done.receive + raise exc end end end diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 010625fd6fe5..f8497f5360bc 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -99,7 +99,7 @@ end describe IO do describe "partial read" do - pending_win32 "doesn't block on first read. blocks on 2nd read" do + it "doesn't block on first read. blocks on 2nd read" do IO.pipe do |read, write| write.puts "hello" slice = Bytes.new 1024 @@ -920,8 +920,8 @@ describe IO do end {% end %} - pending_win32 describe: "#close" do - it "aborts 'read' in a different thread" do + describe "#close" do + it "aborts 'read' in a different fiber" do ch = Channel(SpecChannelStatus).new(1) IO.pipe do |read, write| @@ -942,7 +942,7 @@ describe IO do end end - it "aborts 'write' in a different thread" do + it "aborts 'write' in a different fiber" do ch = Channel(SpecChannelStatus).new(1) IO.pipe do |read, write| diff --git a/spec/std/log/io_backend_spec.cr b/spec/std/log/io_backend_spec.cr index 80f66c08c202..3ebcb846f70e 100644 --- a/spec/std/log/io_backend_spec.cr +++ b/spec/std/log/io_backend_spec.cr @@ -14,7 +14,7 @@ private def io_logger(*, stdout : IO, config = nil, source : String = "") end describe Log::IOBackend do - pending_win32 "creates with defaults" do + it "creates with defaults" do backend = Log::IOBackend.new backend.io.should eq(STDOUT) backend.formatter.should eq(Log::ShortFormat) diff --git a/spec/std/oauth2/client_spec.cr b/spec/std/oauth2/client_spec.cr index e3f24a679ae4..3ee66e29ab49 100644 --- a/spec/std/oauth2/client_spec.cr +++ b/spec/std/oauth2/client_spec.cr @@ -40,7 +40,7 @@ describe OAuth2::Client do end end - pending_win32 describe: "get_access_token_using_*" do + describe "get_access_token_using_*" do describe "using HTTP Basic authentication to pass credentials" do it "#get_access_token_using_authorization_code" do handler = HTTP::Handler::HandlerProc.new do |context| diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index f2e7175c6f4b..c881223eb92c 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -137,12 +137,12 @@ describe Process do value.should eq("hello#{newline}") end - pending_win32 "sends input in IO" do + it "sends input in IO" do value = Process.run(*stdin_to_stdout_command, input: IO::Memory.new("hello")) do |proc| proc.input?.should be_nil proc.output.gets_to_end end - value.should eq("hello") + value.chomp.should eq("hello") end it "sends output to IO" do @@ -305,6 +305,8 @@ describe Process do {% end %} end + # TODO: this spec gives "WaitForSingleObject: The handle is invalid." + # is this because standard streams on windows aren't async? pending_win32 "can link processes together" do buffer = IO::Memory.new Process.run(*stdin_to_stdout_command) do |cat| @@ -313,7 +315,7 @@ describe Process do cat.close end end - buffer.to_s.lines.size.should eq(1000) + buffer.to_s.chomp.lines.size.should eq(1000) end end diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index d8084de32834..8a5c01cff44e 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -44,6 +44,10 @@ module Crystal::System::FileDescriptor fcntl(LibC::F_SETFL, new_flags) unless new_flags == current_flags end + private def system_blocking_init(value) + self.system_blocking = false unless value + end + private def system_close_on_exec? flags = fcntl(LibC::F_GETFD) flags.bits_set? LibC::FD_CLOEXEC diff --git a/src/crystal/system/wasi/file_descriptor.cr b/src/crystal/system/wasi/file_descriptor.cr index df25c9557ffe..9a2bc1a0bd96 100644 --- a/src/crystal/system/wasi/file_descriptor.cr +++ b/src/crystal/system/wasi/file_descriptor.cr @@ -11,6 +11,9 @@ module Crystal::System::FileDescriptor raise NotImplementedError.new "Crystal::System::FileDescriptor.pipe" end + private def system_blocking_init(value) + end + private def system_reopen(other : IO::FileDescriptor) raise NotImplementedError.new "Crystal::System::FileDescriptor#system_reopen" end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index d99dd471f240..2bc4a7d55b37 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -2,30 +2,50 @@ require "c/io" require "c/consoleapi" require "c/consoleapi2" require "c/winnls" +require "io/overlapped" module Crystal::System::FileDescriptor + include IO::Overlapped + @volatile_fd : Atomic(LibC::Int) + @system_blocking = true private def unbuffered_read(slice : Bytes) - bytes_read = LibC._read(fd, slice, slice.size) - if bytes_read == -1 - if Errno.value == Errno::EBADF - raise IO::Error.new "File not open for reading" - else - raise IO::Error.from_errno("Error reading file") + if system_blocking? + bytes_read = LibC._read(fd, slice, slice.size) + if bytes_read == -1 + if Errno.value == Errno::EBADF + raise IO::Error.new "File not open for reading" + else + raise IO::Error.from_errno("Error reading file") + end + end + bytes_read + else + handle = windows_handle + overlapped_operation(handle, "ReadFile", read_timeout) do |overlapped| + ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped) + {ret, byte_count} end end - bytes_read end private def unbuffered_write(slice : Bytes) until slice.empty? - bytes_written = LibC._write(fd, slice, slice.size) - if bytes_written == -1 - if Errno.value == Errno::EBADF - raise IO::Error.new "File not open for writing" - else - raise IO::Error.from_errno("Error writing file") + if system_blocking? + bytes_written = LibC._write(fd, slice, slice.size) + if bytes_written == -1 + if Errno.value == Errno::EBADF + raise IO::Error.new "File not open for writing" + else + raise IO::Error.from_errno("Error writing file") + end + end + else + handle = windows_handle + bytes_written = overlapped_operation(handle, "WriteFile", write_timeout, writing: true) do |overlapped| + ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped) + {ret, byte_count} end end @@ -34,11 +54,17 @@ module Crystal::System::FileDescriptor end private def system_blocking? - true + @system_blocking end private def system_blocking=(blocking) - raise NotImplementedError.new("Crystal::System::FileDescriptor#system_blocking=") unless blocking + unless blocking == @system_blocking + raise IO::Error.new("Cannot reconfigure `IO::FileDescriptor#blocking` after creation") + end + end + + private def system_blocking_init(value) + @system_blocking = value end private def system_close_on_exec? @@ -124,11 +150,12 @@ module Crystal::System::FileDescriptor end private def system_close + LibC.CancelIoEx(windows_handle, nil) unless system_blocking? + file_descriptor_close end def file_descriptor_close - err = nil if LibC._close(fd) != 0 case Errno.value when Errno::EINTR @@ -139,14 +166,26 @@ module Crystal::System::FileDescriptor end end - def self.pipe(read_blocking, write_blocking) - pipe_fds = uninitialized StaticArray(LibC::Int, 2) - if LibC._pipe(pipe_fds, 8192, LibC::O_BINARY | LibC::O_NOINHERIT) != 0 - raise IO::Error.from_errno("Could not create pipe") - end + private PIPE_BUFFER_SIZE = 8192 - r = IO::FileDescriptor.new(pipe_fds[0], read_blocking) - w = IO::FileDescriptor.new(pipe_fds[1], write_blocking) + def self.pipe(read_blocking, write_blocking) + pipe_name = ::Path.windows(::File.tempname("crystal", nil, dir: %q(\\.\pipe))).normalize.to_s + pipe_mode = 0 # PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT + + w_pipe_flags = LibC::PIPE_ACCESS_OUTBOUND | LibC::FILE_FLAG_FIRST_PIPE_INSTANCE + w_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless write_blocking + w_pipe = LibC.CreateNamedPipeA(pipe_name, w_pipe_flags, pipe_mode, 1, PIPE_BUFFER_SIZE, PIPE_BUFFER_SIZE, 0, nil) + raise IO::Error.from_winerror("CreateNamedPipeA") if w_pipe == LibC::INVALID_HANDLE_VALUE + Crystal::Scheduler.event_loop.create_completion_port(w_pipe) unless write_blocking + + r_pipe_flags = LibC::FILE_FLAG_NO_BUFFERING + r_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless read_blocking + r_pipe = LibC.CreateFileW(System.to_wstr(pipe_name), LibC::GENERIC_READ | LibC::FILE_WRITE_ATTRIBUTES, 0, nil, LibC::OPEN_EXISTING, r_pipe_flags, nil) + raise IO::Error.from_winerror("CreateFileW") if r_pipe == LibC::INVALID_HANDLE_VALUE + Crystal::Scheduler.event_loop.create_completion_port(r_pipe) unless read_blocking + + r = IO::FileDescriptor.new(LibC._open_osfhandle(r_pipe, 0), read_blocking) + w = IO::FileDescriptor.new(LibC._open_osfhandle(w_pipe, 0), write_blocking) w.sync = true {r, w} diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 3781a8463ebd..6a492f044478 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -143,6 +143,26 @@ module Crystal::System::Socket end end + private def overlapped_connect(socket, method, &) + OverlappedOperation.run(socket) do |operation| + yield operation.start + + schedule_overlapped(read_timeout || 1.seconds) + + operation.wsa_result(socket) do |error| + case error + when .wsa_io_incomplete?, .wsaeconnrefused? + return ::Socket::ConnectError.from_os_error(method, error) + when .error_operation_aborted? + # FIXME: Not sure why this is necessary + return ::Socket::ConnectError.from_os_error(method, error) + end + end + + nil + end + end + private def system_connect_connectionless(addr, timeout, &) ret = LibC.connect(fd, addr, addr.size) if ret == LibC::SOCKET_ERROR @@ -206,6 +226,25 @@ module Crystal::System::Socket true end + private def overlapped_accept(socket, method, &) + OverlappedOperation.run(socket) do |operation| + yield operation.start + + unless schedule_overlapped(read_timeout) + raise IO::TimeoutError.new("accept timed out") + end + + operation.wsa_result(socket) do |error| + case error + when .wsa_io_incomplete?, .wsaenotsock? + return false + end + end + + true + end + end + private def wsa_buffer(bytes) wsabuf = LibC::WSABUF.new wsabuf.len = bytes.size @@ -402,7 +441,7 @@ module Crystal::System::Socket private def unbuffered_read(slice : Bytes) wsabuf = wsa_buffer(slice) - bytes_read = overlapped_operation(fd, "WSARecv", read_timeout, connreset_is_error: false) do |overlapped| + bytes_read = overlapped_read(fd, "WSARecv", connreset_is_error: false) do |overlapped| flags = 0_u32 LibC.WSARecv(fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), overlapped, nil) end @@ -419,6 +458,18 @@ module Crystal::System::Socket bytes.to_i32 end + private def overlapped_write(socket, method, &) + wsa_overlapped_operation(socket, method, write_timeout) do |operation| + yield operation + end + end + + private def overlapped_read(socket, method, *, connreset_is_error = true, &) + wsa_overlapped_operation(socket, method, read_timeout, connreset_is_error) do |operation| + yield operation + end + end + def system_close handle = @volatile_fd.swap(LibC::INVALID_SOCKET) diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 2f6cf02ec4a0..2929a62a410a 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -32,9 +32,7 @@ class IO::FileDescriptor < IO end end - unless blocking || {{ flag?(:win32) || flag?(:wasi) }} - self.blocking = false - end + system_blocking_init(blocking) end # :nodoc: diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index 011b25b06398..fe7a5445b38c 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -38,18 +38,6 @@ module IO::Overlapped write_timeout end - def overlapped_write(socket, method, &) - overlapped_operation(socket, method, write_timeout) do |operation| - yield operation - end - end - - def overlapped_read(socket, method, &) - overlapped_operation(socket, method, read_timeout) do |operation| - yield operation - end - end - def self.wait_queued_completions(timeout, &) overlapped_entries = uninitialized LibC::OVERLAPPED_ENTRY[1] @@ -87,23 +75,24 @@ module IO::Overlapped CANCELLED end - @overlapped = LibC::WSAOVERLAPPED.new + @overlapped = LibC::OVERLAPPED.new @fiber : Fiber? = nil @state : State = :initialized property next : OverlappedOperation? property previous : OverlappedOperation? @@canceled = Thread::LinkedList(OverlappedOperation).new + property? synchronous = false - def self.run(socket, &) + def self.run(handle, &) operation = OverlappedOperation.new begin yield operation ensure - operation.done(socket) + operation.done(handle) end end - def self.schedule(overlapped : LibC::WSAOVERLAPPED*, &) + def self.schedule(overlapped : LibC::OVERLAPPED*, &) start = overlapped.as(Pointer(UInt8)) - offsetof(OverlappedOperation, @overlapped) operation = Box(OverlappedOperation).unbox(start.as(Pointer(Void))) operation.schedule { |fiber| yield fiber } @@ -116,7 +105,20 @@ module IO::Overlapped pointerof(@overlapped) end - def result(socket, &) + def result(handle, &) + raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started? + result = LibC.GetOverlappedResult(handle, pointerof(@overlapped), out bytes, 0) + if result.zero? + error = WinError.value + yield error + + raise IO::Error.from_os_error("GetOverlappedResult", error) + end + + bytes + end + + def wsa_result(socket, &) raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started? flags = 0_u32 result = LibC.WSAGetOverlappedResult(socket, pointerof(@overlapped), out bytes, false, pointerof(flags)) @@ -142,12 +144,17 @@ module IO::Overlapped end end - protected def done(socket) + protected def done(handle) case @state when .started? + handle = LibC::HANDLE.new(handle) if handle.is_a?(LibC::SOCKET) + # Microsoft documentation: - # The application must not free or reuse the OVERLAPPED structure associated with the canceled I/O operations until they have completed - if LibC.CancelIoEx(LibC::HANDLE.new(socket), pointerof(@overlapped)) != 0 + # The application must not free or reuse the OVERLAPPED structure + # associated with the canceled I/O operations until they have completed + # (this applies even to asynchronous operations that completed + # synchronously) + if synchronous? || LibC.CancelIoEx(handle, pointerof(@overlapped)) != 0 @state = :cancelled @@canceled.push(self) # to increase lifetime end @@ -170,67 +177,66 @@ module IO::Overlapped Crystal::Scheduler.event_loop.dequeue(timeout_event) end - def overlapped_operation(socket, method, timeout, connreset_is_error = true, &) - OverlappedOperation.run(socket) do |operation| - result = yield operation.start - - if result == LibC::SOCKET_ERROR - error = WinError.wsa_value - - unless error.wsa_io_pending? + def overlapped_operation(handle, method, timeout, *, writing = false, &) + OverlappedOperation.run(handle) do |operation| + result, value = yield operation.start + + if result == 0 + case error = WinError.value + when .error_handle_eof? + return 0_u32 + when .error_broken_pipe? + return 0_u32 + when .error_io_pending? + # the operation is running asynchronously; do nothing + when .error_access_denied? + raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}" + else raise IO::Error.from_os_error(method, error) end + else + operation.synchronous = true + return value end schedule_overlapped(timeout) - operation.result(socket) do |error| + operation.result(handle) do |error| case error - when .wsa_io_incomplete? - raise TimeoutError.new("#{method} timed out") - when .wsaeconnreset? - return 0_u32 unless connreset_is_error + when .error_io_incomplete? + raise IO::TimeoutError.new("#{method} timed out") + when .error_handle_eof? + return 0_u32 + when .error_broken_pipe? + # TODO: this is needed for `Process.run`, can we do without it? + return 0_u32 end end end end - def overlapped_connect(socket, method, &) + def wsa_overlapped_operation(socket, method, timeout, connreset_is_error = true, &) OverlappedOperation.run(socket) do |operation| - yield operation.start + result = yield operation.start - schedule_overlapped(read_timeout || 1.seconds) + if result == LibC::SOCKET_ERROR + error = WinError.wsa_value - operation.result(socket) do |error| - case error - when .wsa_io_incomplete?, .wsaeconnrefused? - return ::Socket::ConnectError.from_os_error(method, error) - when .error_operation_aborted? - # FIXME: Not sure why this is necessary - return ::Socket::ConnectError.from_os_error(method, error) + unless error.wsa_io_pending? + raise IO::Error.from_os_error(method, error) end end - nil - end - end - - def overlapped_accept(socket, method, &) - OverlappedOperation.run(socket) do |operation| - yield operation.start - - unless schedule_overlapped(read_timeout) - raise IO::TimeoutError.new("accept timed out") - end + schedule_overlapped(timeout) - operation.result(socket) do |error| + operation.wsa_result(socket) do |error| case error - when .wsa_io_incomplete?, .wsaenotsock? - return false + when .wsa_io_incomplete? + raise TimeoutError.new("#{method} timed out") + when .wsaeconnreset? + return 0_u32 unless connreset_is_error end end - - true end end end diff --git a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr index 9b88b5341c09..3ad2e25f663b 100644 --- a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr @@ -50,11 +50,14 @@ lib LibC FILE_ATTRIBUTE_NORMAL = 0x80 FILE_ATTRIBUTE_TEMPORARY = 0x100 - FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 - FILE_FLAG_DELETE_ON_CLOSE = 0x04000000 - FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 - FILE_FLAG_RANDOM_ACCESS = 0x10000000 - FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000 + FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 + FILE_FLAG_DELETE_ON_CLOSE = 0x04000000 + FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000 + FILE_FLAG_NO_BUFFERING = 0x20000000 + FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 + FILE_FLAG_OVERLAPPED = 0x40000000 + FILE_FLAG_RANDOM_ACCESS = 0x10000000 + FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000 FILE_SHARE_READ = 0x1 FILE_SHARE_WRITE = 0x2 @@ -70,6 +73,7 @@ lib LibC dwFlagsAndAttributes : DWORD, hTemplateFile : HANDLE) : HANDLE fun ReadFile(hFile : HANDLE, lpBuffer : Void*, nNumberOfBytesToRead : DWORD, lpNumberOfBytesRead : DWORD*, lpOverlapped : OVERLAPPED*) : BOOL + fun WriteFile(hFile : HANDLE, lpBuffer : Void*, nNumberOfBytesToWrite : DWORD, lpNumberOfBytesWritten : DWORD*, lpOverlapped : OVERLAPPED*) : BOOL MAX_PATH = 260 diff --git a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr index eaef34deaa3e..1c94b66db4c8 100644 --- a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr +++ b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr @@ -1,4 +1,11 @@ lib LibC + fun GetOverlappedResult( + hFile : HANDLE, + lpOverlapped : OVERLAPPED*, + lpNumberOfBytesTransferred : DWORD*, + bWait : BOOL + ) : BOOL + fun CreateIoCompletionPort( fileHandle : HANDLE, existingCompletionPort : HANDLE, diff --git a/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr b/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr index cb56e3269de2..4f22a8242d18 100644 --- a/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr @@ -19,7 +19,7 @@ lib LibC struct OVERLAPPED_ENTRY lpCompletionKey : ULONG_PTR - lpOverlapped : WSAOVERLAPPED* + lpOverlapped : OVERLAPPED* internal : ULONG_PTR dwNumberOfBytesTransferred : DWORD end diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index 0ac284673695..f9c4e261ab05 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -18,8 +18,12 @@ lib LibC SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1 SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2 - fun CreateHardLinkW(lpFileName : LPWSTR, lpExistingFileName : LPWSTR, lpSecurityAttributes : Void*) : BOOL + PIPE_ACCESS_OUTBOUND = 0x00000002 + + fun CreateHardLinkW(lpFileName : LPWSTR, lpExistingFileName : LPWSTR, lpSecurityAttributes : SECURITY_ATTRIBUTES*) : BOOL fun CreateSymbolicLinkW(lpSymlinkFileName : LPWSTR, lpTargetFileName : LPWSTR, dwFlags : DWORD) : BOOLEAN + fun CreateNamedPipeA(lpName : LPSTR, dwOpenMode : DWORD, dwPipeMode : DWORD, nMaxInstances : DWORD, + nOutBufferSize : DWORD, nInBufferSize : DWORD, nDefaultTimeOut : DWORD, lpSecurityAttributes : SECURITY_ATTRIBUTES*) : HANDLE fun GetEnvironmentVariableW(lpName : LPWSTR, lpBuffer : LPWSTR, nSize : DWORD) : DWORD fun GetEnvironmentStringsW : LPWCH diff --git a/src/log/io_backend.cr b/src/log/io_backend.cr index 9092495c8bf4..2d520fc9346b 100644 --- a/src/log/io_backend.cr +++ b/src/log/io_backend.cr @@ -3,7 +3,7 @@ class Log::IOBackend < Log::Backend property io : IO property formatter : Formatter - {% if flag?(:win32) || flag?(:wasm32) %} + {% if flag?(:wasm32) %} # TODO: this constructor must go away once channels are fixed in Windows / WebAssembly def initialize(@io = STDOUT, *, @formatter : Formatter = ShortFormat, dispatcher : Dispatcher::Spec = DispatchMode::Sync) super(dispatcher) From dc97b527082fbcac24ddc31e72c361f3205c0c69 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 29 Apr 2023 01:04:18 +0800 Subject: [PATCH 0495/1551] Use correct format strings for crash stack traces (#13408) --- src/exception/call_stack/libunwind.cr | 28 +++++++++++---------------- src/exception/call_stack/stackwalk.cr | 25 ++++++++++-------------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 21683c0351ef..1f32df229894 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -102,35 +102,29 @@ struct Exception::CallStack end private def self.print_frame(repeated_frame) + Crystal::System.print_error "[0x%llx] ", repeated_frame.ip.address.to_u64 + print_frame_location(repeated_frame) + Crystal::System.print_error " (%d times)", repeated_frame.count + 1 unless repeated_frame.count == 0 + Crystal::System.print_error "\n" + end + + private def self.print_frame_location(repeated_frame) {% if flag?(:debug) %} if @@dwarf_loaded && (name = decode_function_name(repeated_frame.ip.address)) file, line, column = Exception::CallStack.decode_line_number(repeated_frame.ip.address) if file && file != "??" - if repeated_frame.count == 0 - Crystal::System.print_error "[0x%lx] %s at %s:%ld:%i\n", repeated_frame.ip, name, file, line, column - else - Crystal::System.print_error "[0x%lx] %s at %s:%ld:%i (%ld times)\n", repeated_frame.ip, name, file, line, column, repeated_frame.count + 1 - end + Crystal::System.print_error "%s at %s:%d:%d", name, file, line, column return end end {% end %} - frame = decode_frame(repeated_frame.ip) - if frame + if frame = decode_frame(repeated_frame.ip) offset, sname, fname = frame - if repeated_frame.count == 0 - Crystal::System.print_error "[0x%lx] %s +%ld in %s\n", repeated_frame.ip, sname, offset, fname - else - Crystal::System.print_error "[0x%lx] %s +%ld in %s (%ld times)\n", repeated_frame.ip, sname, offset, fname, repeated_frame.count + 1 - end + Crystal::System.print_error "%s +%lld in %s", sname, offset.to_i64, fname else - if repeated_frame.count == 0 - Crystal::System.print_error "[0x%lx] ???\n", repeated_frame.ip - else - Crystal::System.print_error "[0x%lx] ??? (%ld times)\n", repeated_frame.ip, repeated_frame.count + 1 - end + Crystal::System.print_error "???" end end diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index f8377a3b5f69..9652514e7f25 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -150,31 +150,26 @@ struct Exception::CallStack end private def self.print_frame(repeated_frame) + Crystal::System.print_error "[0x%llx] ", repeated_frame.ip.address.to_u64 + print_frame_location(repeated_frame) + Crystal::System.print_error " (%d times)", repeated_frame.count + 1 unless repeated_frame.count == 0 + Crystal::System.print_error "\n" + end + + private def self.print_frame_location(repeated_frame) if name = decode_function_name(repeated_frame.ip.address) file, line, _ = decode_line_number(repeated_frame.ip.address) if file != "??" && line != 0 - if repeated_frame.count == 0 - Crystal::System.print_error "[0x%llx] %s at %s:%ld\n", repeated_frame.ip, name, file, line - else - Crystal::System.print_error "[0x%llx] %s at %s:%ld (%ld times)\n", repeated_frame.ip, name, file, line, repeated_frame.count + 1 - end + Crystal::System.print_error "%s at %s:%d", name, file, line return end end if frame = decode_frame(repeated_frame.ip) offset, sname, fname = frame - if repeated_frame.count == 0 - Crystal::System.print_error "[0x%llx] %s +%lld in %s\n", repeated_frame.ip, sname, offset, fname - else - Crystal::System.print_error "[0x%llx] %s +%lld in %s (%ld times)\n", repeated_frame.ip, sname, offset, fname, repeated_frame.count + 1 - end + Crystal::System.print_error "%s +%lld in %s", sname, offset.to_i64, fname else - if repeated_frame.count == 0 - Crystal::System.print_error "[0x%llx] ???\n", repeated_frame.ip - else - Crystal::System.print_error "[0x%llx] ??? (%ld times)\n", repeated_frame.ip, repeated_frame.count + 1 - end + Crystal::System.print_error "???" end end From 95e1270b642854ddac36be3fe7fc5b6a861dc87d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 29 Apr 2023 20:05:52 +0800 Subject: [PATCH 0496/1551] Check subject UTF8 validity just once for `String#gsub`, `#scan`, `#split` (#13406) --- src/string.cr | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/string.cr b/src/string.cr index 34837cb41e76..c5f8c1dcff01 100644 --- a/src/string.cr +++ b/src/string.cr @@ -2880,7 +2880,7 @@ class String last_byte_offset = byte_offset end - match = pattern.match_at_byte_index(self, byte_offset) + match = pattern.match_at_byte_index(self, byte_offset, Regex::MatchOptions::NO_UTF_CHECK) end if last_byte_offset < bytesize @@ -4108,7 +4108,8 @@ class String count = 0 match_offset = slice_offset = 0 - while match = separator.match_at_byte_index(self, match_offset) + options = Regex::MatchOptions::None + while match = separator.match_at_byte_index(self, match_offset, options) index = match.byte_begin(0) match_bytesize = match.byte_end(0) - index next_offset = index + match_bytesize @@ -4132,6 +4133,7 @@ class String break if limit && count + 1 == limit break if match_offset >= bytesize + options |= :no_utf_check end yield byte_slice(slice_offset) unless remove_empty && slice_offset == bytesize @@ -4591,13 +4593,15 @@ class String def scan(pattern : Regex, &) : self byte_offset = 0 - while match = pattern.match_at_byte_index(self, byte_offset) + options = Regex::MatchOptions::None + while match = pattern.match_at_byte_index(self, byte_offset, options) index = match.byte_begin(0) $~ = match yield match match_bytesize = match.byte_end(0) - index match_bytesize += char_bytesize_at(byte_offset) if match_bytesize == 0 byte_offset = index + match_bytesize + options |= :no_utf_check end self From 9f79ded3caf43e30ddfe3afc9aa84bdc3f37c35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 2 May 2023 11:39:25 +0200 Subject: [PATCH 0497/1551] Refactor implementation of `Iterator::ChainIterator` (#13412) --- spec/std/iterator_spec.cr | 5 +++++ src/iterator.cr | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/std/iterator_spec.cr b/spec/std/iterator_spec.cr index 417b35612035..e308e435cde6 100644 --- a/spec/std/iterator_spec.cr +++ b/spec/std/iterator_spec.cr @@ -165,6 +165,11 @@ describe Iterator do iter.next.should be_a(Iterator::Stop) end + # NOTE: This spec would only fail in release mode. + it "does not experience tuple upcase bug of #13411" do + [{true}].each.chain([{1}].each).first(3).to_a.should eq [{true}, {1}] + end + describe "chain indeterminate number of iterators" do it "chains all together" do iters = [[0], [1], [2, 3], [4, 5, 6]].each.map &.each diff --git a/src/iterator.cr b/src/iterator.cr index 78f6fee31e7b..22b215440065 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -277,16 +277,15 @@ module Iterator(T) end def next - if @iterator1_consumed - @iterator2.next - else + unless @iterator1_consumed value = @iterator1.next if value.is_a?(Stop) @iterator1_consumed = true - value = @iterator2.next + else + return value end - value end + @iterator2.next end end From c7d52dc7fa4da31565b858e323e462f2b33d911d Mon Sep 17 00:00:00 2001 From: David Keller Date: Tue, 2 May 2023 11:39:51 +0200 Subject: [PATCH 0498/1551] Improve `HTML.escape(string, io)` performance (#13139) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller Co-authored-by: Quinton Miller --- src/html.cr | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/html.cr b/src/html.cr index 49457b1f1e29..b3943fb82cb9 100644 --- a/src/html.cr +++ b/src/html.cr @@ -37,9 +37,30 @@ module HTML # io.to_s # => "Crystal & You" # ``` def self.escape(string : String, io : IO) : Nil - string.each_char do |char| - io << SUBSTITUTIONS.fetch(char, char) + escape(string.to_slice, io) + end + + # Same as `escape(String, IO)` but accepts `Bytes` instead of `String`. + # + # The slice is assumed to be valid UTF-8. + def self.escape(string : Bytes, io : IO) : Nil + last_copy_at = 0 + string.each_with_index do |byte, index| + str = case byte + when '&' then "&" + when '<' then "<" + when '>' then ">" + when '"' then """ + when '\'' then "'" + else + next + end + + io.write_string(string[last_copy_at, index &- last_copy_at]) + last_copy_at = index &+ 1 + io << str end + io.write_string(string[last_copy_at, string.size &- last_copy_at]) end # These replacements permit compatibility with old numeric entities that From 6a9b3ec74e0e992a1861205a5faa719c1ad0514f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 2 May 2023 17:40:06 +0800 Subject: [PATCH 0499/1551] Support synchronous socket operations on Windows (#13414) --- src/crystal/system/win32/event_loop_iocp.cr | 9 +++ src/crystal/system/win32/socket.cr | 81 ++++++++++++--------- src/io/overlapped.cr | 18 +++-- src/lib_c/x86_64-windows-msvc/c/winbase.cr | 4 + 4 files changed, 70 insertions(+), 42 deletions(-) diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 353c50a35974..4f239a5a24cb 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -22,6 +22,15 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop if iocp.null? raise IO::Error.from_winerror("CreateIoCompletionPort") end + if parent + # all overlapped operations may finish synchronously, in which case we do + # not reschedule the running fiber; the following call tells Win32 not to + # queue an I/O completion packet to the associated IOCP as well, as this + # would be done by default + if LibC.SetFileCompletionNotificationModes(handle, LibC::FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) == 0 + raise IO::Error.from_winerror("SetFileCompletionNotificationModes") + end + end iocp end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 6a492f044478..f6faaf928f75 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -110,20 +110,7 @@ module Crystal::System::Socket error = overlapped_connect(fd, "ConnectEx") do |overlapped| # This is: LibC.ConnectEx(fd, addr, addr.size, nil, 0, nil, overlapped) - result = Crystal::System::Socket.connect_ex.call(fd, addr.to_unsafe, addr.size, Pointer(Void).null, 0_u32, Pointer(UInt32).null, overlapped) - - if result.zero? - wsa_error = WinError.wsa_value - - case wsa_error - when .wsa_io_pending? - next - when .wsaeaddrnotavail? - return yield ::Socket::ConnectError.from_os_error("ConnectEx", wsa_error) - else - return yield ::Socket::Error.from_os_error("ConnectEx", wsa_error) - end - end + Crystal::System::Socket.connect_ex.call(fd, addr.to_unsafe, addr.size, Pointer(Void).null, 0_u32, Pointer(UInt32).null, overlapped) end if error @@ -145,7 +132,21 @@ module Crystal::System::Socket private def overlapped_connect(socket, method, &) OverlappedOperation.run(socket) do |operation| - yield operation.start + result = yield operation.start + + if result == 0 + case error = WinError.wsa_value + when .wsa_io_pending? + # the operation is running asynchronously; do nothing + when .wsaeaddrnotavail? + return ::Socket::ConnectError.from_os_error("ConnectEx", error) + else + return ::Socket::Error.from_os_error("ConnectEx", error) + end + else + operation.synchronous = true + return nil + end schedule_overlapped(read_timeout || 1.seconds) @@ -201,19 +202,11 @@ module Crystal::System::Socket output_buffer = Bytes.new(address_size * 2 + buffer_size) success = overlapped_accept(fd, "AcceptEx") do |overlapped| + # This is: LibC.AcceptEx(fd, client_socket, output_buffer, buffer_size, address_size, address_size, out received_bytes, overlapped) received_bytes = uninitialized UInt32 - - result = Crystal::System::Socket.accept_ex.call(fd, client_socket, + Crystal::System::Socket.accept_ex.call(fd, client_socket, output_buffer.to_unsafe.as(Void*), buffer_size.to_u32!, address_size.to_u32!, address_size.to_u32!, pointerof(received_bytes), overlapped) - - if result.zero? - error = WinError.wsa_value - - unless error.wsa_io_pending? - return false - end - end end return false unless success @@ -228,7 +221,19 @@ module Crystal::System::Socket private def overlapped_accept(socket, method, &) OverlappedOperation.run(socket) do |operation| - yield operation.start + result = yield operation.start + + if result == 0 + case error = WinError.wsa_value + when .wsa_io_pending? + # the operation is running asynchronously; do nothing + else + return false + end + else + operation.synchronous = true + return true + end unless schedule_overlapped(read_timeout) raise IO::TimeoutError.new("accept timed out") @@ -256,7 +261,8 @@ module Crystal::System::Socket wsabuf = wsa_buffer(message) bytes = overlapped_write(fd, "WSASend") do |overlapped| - LibC.WSASend(fd, pointerof(wsabuf), 1, out bytes_sent, 0, overlapped, nil) + ret = LibC.WSASend(fd, pointerof(wsabuf), 1, out bytes_sent, 0, overlapped, nil) + {ret, bytes_sent} end bytes.to_i32 @@ -264,13 +270,14 @@ module Crystal::System::Socket private def system_send_to(bytes : Bytes, addr : ::Socket::Address) wsabuf = wsa_buffer(bytes) - bytes_sent = overlapped_write(fd, "WSASendTo") do |overlapped| - LibC.WSASendTo(fd, pointerof(wsabuf), 1, out bytes_sent, 0, addr, addr.size, overlapped, nil) + bytes_written = overlapped_write(fd, "WSASendTo") do |overlapped| + ret = LibC.WSASendTo(fd, pointerof(wsabuf), 1, out bytes_sent, 0, addr, addr.size, overlapped, nil) + {ret, bytes_sent} end - raise ::Socket::Error.from_errno("Error sending datagram to #{addr}") if bytes_sent == -1 + raise ::Socket::Error.from_errno("Error sending datagram to #{addr}") if bytes_written == -1 # to_i32 is fine because string/slice sizes are an Int32 - bytes_sent.to_i32 + bytes_written.to_i32 end private def system_receive(bytes) @@ -286,7 +293,8 @@ module Crystal::System::Socket flags = 0_u32 bytes_read = overlapped_read(fd, "WSARecvFrom") do |overlapped| - LibC.WSARecvFrom(fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), sockaddr, pointerof(addrlen), overlapped, nil) + ret = LibC.WSARecvFrom(fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), sockaddr, pointerof(addrlen), overlapped, nil) + {ret, bytes_received} end {bytes_read.to_i32, sockaddr, addrlen} @@ -443,8 +451,10 @@ module Crystal::System::Socket bytes_read = overlapped_read(fd, "WSARecv", connreset_is_error: false) do |overlapped| flags = 0_u32 - LibC.WSARecv(fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), overlapped, nil) + ret = LibC.WSARecv(fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), overlapped, nil) + {ret, bytes_received} end + bytes_read.to_i32 end @@ -452,9 +462,10 @@ module Crystal::System::Socket wsabuf = wsa_buffer(slice) bytes = overlapped_write(fd, "WSASend") do |overlapped| - LibC.WSASend(fd, pointerof(wsabuf), 1, out bytes_sent, 0, overlapped, nil) + ret = LibC.WSASend(fd, pointerof(wsabuf), 1, out bytes_sent, 0, overlapped, nil) + {ret, bytes_sent} end - # we could return bytes (from WSAGetOverlappedResult) or bytes_sent + bytes.to_i32 end diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index fe7a5445b38c..8830b5d65a7c 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -152,9 +152,9 @@ module IO::Overlapped # Microsoft documentation: # The application must not free or reuse the OVERLAPPED structure # associated with the canceled I/O operations until they have completed - # (this applies even to asynchronous operations that completed - # synchronously) - if synchronous? || LibC.CancelIoEx(handle, pointerof(@overlapped)) != 0 + # (this does not apply to asynchronous operations that finished + # synchronously, as nothing would be queued to the IOCP) + if !synchronous? && LibC.CancelIoEx(handle, pointerof(@overlapped)) != 0 @state = :cancelled @@canceled.push(self) # to increase lifetime end @@ -217,14 +217,18 @@ module IO::Overlapped def wsa_overlapped_operation(socket, method, timeout, connreset_is_error = true, &) OverlappedOperation.run(socket) do |operation| - result = yield operation.start + result, value = yield operation.start if result == LibC::SOCKET_ERROR - error = WinError.wsa_value - - unless error.wsa_io_pending? + case error = WinError.wsa_value + when .wsa_io_pending? + # the operation is running asynchronously; do nothing + else raise IO::Error.from_os_error(method, error) end + else + operation.synchronous = true + return value end schedule_overlapped(timeout) diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index f9c4e261ab05..406fe65003cf 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -25,6 +25,10 @@ lib LibC fun CreateNamedPipeA(lpName : LPSTR, dwOpenMode : DWORD, dwPipeMode : DWORD, nMaxInstances : DWORD, nOutBufferSize : DWORD, nInBufferSize : DWORD, nDefaultTimeOut : DWORD, lpSecurityAttributes : SECURITY_ATTRIBUTES*) : HANDLE + FILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1_u8 + + fun SetFileCompletionNotificationModes(fileHandle : HANDLE, flags : UChar) : BOOL + fun GetEnvironmentVariableW(lpName : LPWSTR, lpBuffer : LPWSTR, nSize : DWORD) : DWORD fun GetEnvironmentStringsW : LPWCH fun FreeEnvironmentStringsW(lpszEnvironmentBlock : LPWCH) : BOOL From b65280a651f76a4c13df937a4d70d8fe313e2650 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 3 May 2023 17:27:26 +0800 Subject: [PATCH 0500/1551] Use sentence case for all standard library exceptions (#13400) --- samples/meteor.cr | 2 +- samples/pretty_json.cr | 2 +- samples/sdl/fire.cr | 2 +- scripts/generate_grapheme_break_specs.cr | 2 +- scripts/generate_unicode_data.cr | 2 +- spec/compiler/codegen/cast_spec.cr | 8 ++++---- spec/compiler/interpreter/casts_spec.cr | 2 +- spec/std/ini_spec.cr | 8 ++++---- spec/std/named_tuple_spec.cr | 4 ++-- spec/std/spec_spec.cr | 4 ++-- spec/std/tuple_spec.cr | 4 ++-- spec/support/fibers.cr | 4 ++-- src/array.cr | 8 ++++---- src/channel.cr | 4 ++-- src/compiler/crystal/codegen/codegen.cr | 2 +- src/crystal/digest/sha1.cr | 2 +- src/crystal/dwarf/line_numbers.cr | 6 +++--- src/crystal/syntax_highlighter.cr | 2 +- src/crystal/system/event_loop.cr | 2 +- src/crystal/system/fiber.cr | 2 +- src/crystal/system/thread.cr | 2 +- src/crystal/system/thread_condition_variable.cr | 2 +- src/crystal/system/thread_mutex.cr | 2 +- src/crystal/system/unix/process.cr | 2 +- src/crystal/system/win32/process.cr | 2 +- src/crystal/system/win32/socket.cr | 2 +- src/dir/glob.cr | 4 ++-- src/docs_pseudo_methods.cr | 2 +- src/exception/call_stack/stackwalk.cr | 2 +- src/float/printer/dragonbox.cr | 2 +- src/indexable/mutable.cr | 4 ++-- src/ini.cr | 6 +++--- src/int.cr | 6 +++--- src/json/pull_parser.cr | 2 +- src/json/serialization.cr | 4 ++-- src/lib_c.cr | 2 +- src/llvm/type.cr | 10 +++++----- src/named_tuple.cr | 2 +- src/openssl/cipher.cr | 2 +- src/process.cr | 6 +++--- src/raise.cr | 2 +- src/regex/pcre.cr | 6 +++--- src/regex/pcre2.cr | 8 ++++---- src/slice.cr | 8 ++++---- src/spec/cli.cr | 2 +- src/spec/context.cr | 2 +- src/spec/helpers/iterate.cr | 2 +- src/static_array.cr | 8 ++++---- src/yaml/any.cr | 2 +- src/yaml/serialization.cr | 4 ++-- 50 files changed, 91 insertions(+), 91 deletions(-) diff --git a/samples/meteor.cr b/samples/meteor.cr index 65a1b8e5cb84..c9b535bec9c4 100644 --- a/samples/meteor.cr +++ b/samples/meteor.cr @@ -111,7 +111,7 @@ def get_id(m : UInt64) 10.times do |id| return id.to_u8 if bm(m, id + 50) != 0 end - raise "does not have a valid identifier" + raise "Does not have a valid identifier" end def to_utf8(raw_sol) diff --git a/samples/pretty_json.cr b/samples/pretty_json.cr index 0fb30599ef65..e9240ca295c2 100644 --- a/samples/pretty_json.cr +++ b/samples/pretty_json.cr @@ -47,7 +47,7 @@ class PrettyPrinter when .eof? # We are done when .end_array?, .end_object? - raise "Bug: shouldn't happen" + raise "Bug: Shouldn't happen" end end diff --git a/samples/sdl/fire.cr b/samples/sdl/fire.cr index 7476b190984b..3e25ec78bf03 100644 --- a/samples/sdl/fire.cr +++ b/samples/sdl/fire.cr @@ -204,7 +204,7 @@ class RainbowColorPattern < ColorPattern end def interpolate(life) - raise "shouldn't reach here" + raise "Shouldn't reach here" end end diff --git a/scripts/generate_grapheme_break_specs.cr b/scripts/generate_grapheme_break_specs.cr index e31c602cb38f..a03f154b89ef 100644 --- a/scripts/generate_grapheme_break_specs.cr +++ b/scripts/generate_grapheme_break_specs.cr @@ -54,7 +54,7 @@ File.open(path, "w") do |file| end grapheme = String::Builder.new when "×" - else raise "unexpected operator #{operator.inspect}" + else raise "Unexpected operator #{operator.inspect}" end grapheme << char end diff --git a/scripts/generate_unicode_data.cr b/scripts/generate_unicode_data.cr index 3f37428c954e..4337841cd292 100644 --- a/scripts/generate_unicode_data.cr +++ b/scripts/generate_unicode_data.cr @@ -296,7 +296,7 @@ canonical_combining_classes.sort_by! &.low canonical_decompositions = entries.compact_map do |entry| next unless entry.decomposition_type.canonical? mapping = entry.decomposition_mapping.not_nil! - raise "BUG: mapping longer than 2 codepoints" unless mapping.size <= 2 + raise "BUG: Mapping longer than 2 codepoints" unless mapping.size <= 2 {entry.codepoint, mapping[0], mapping[1]? || 0} end diff --git a/spec/compiler/codegen/cast_spec.cr b/spec/compiler/codegen/cast_spec.cr index b4ad3d4bdc3e..32b265ea0415 100644 --- a/spec/compiler/codegen/cast_spec.cr +++ b/spec/compiler/codegen/cast_spec.cr @@ -48,7 +48,7 @@ describe "Code gen: cast" do a.as(Char) false rescue ex - ex.message.not_nil!.includes?("cast from Int32 to Char failed") && (ex.class == TypeCastError) + ex.message.not_nil!.includes?("Cast from Int32 to Char failed") && (ex.class == TypeCastError) end )).to_b.should be_true end @@ -72,7 +72,7 @@ describe "Code gen: cast" do a.as(Float64 | Char) false rescue ex - ex.message.not_nil!.includes?("cast from Int32 to (Char | Float64) failed") && (ex.class == TypeCastError) + ex.message.not_nil!.includes?("Cast from Int32 to (Char | Float64) failed") && (ex.class == TypeCastError) end )).to_b.should be_true end @@ -120,7 +120,7 @@ describe "Code gen: cast" do a.as(CastSpecBaz) false rescue ex - ex.message.not_nil!.includes?("cast from CastSpecBar to CastSpecBaz failed") && (ex.class == TypeCastError) + ex.message.not_nil!.includes?("Cast from CastSpecBar to CastSpecBaz failed") && (ex.class == TypeCastError) end )).to_b.should be_true end @@ -187,7 +187,7 @@ describe "Code gen: cast" do a.as(Nil) false rescue ex - ex.message.not_nil!.includes?("cast from Reference to Nil failed") && (ex.class == TypeCastError) + ex.message.not_nil!.includes?("Cast from Reference to Nil failed") && (ex.class == TypeCastError) end )).to_b.should be_true end diff --git a/spec/compiler/interpreter/casts_spec.cr b/spec/compiler/interpreter/casts_spec.cr index ac28197ce534..6398b6526876 100644 --- a/spec/compiler/interpreter/casts_spec.cr +++ b/spec/compiler/interpreter/casts_spec.cr @@ -222,7 +222,7 @@ describe Crystal::Repl::Interpreter do end it "raises when as fails" do - interpret(<<-CRYSTAL, prelude: "prelude").to_s.should contain("cast from Int32 to Char failed") + interpret(<<-CRYSTAL, prelude: "prelude").to_s.should contain("Cast from Int32 to Char failed") x = 1 || 'a' begin x.as(Char) diff --git a/spec/std/ini_spec.cr b/spec/std/ini_spec.cr index d93e7bd4f2b3..4dce43f1bd1e 100644 --- a/spec/std/ini_spec.cr +++ b/spec/std/ini_spec.cr @@ -5,23 +5,23 @@ describe "INI" do describe "parse" do context "from String" do it "fails on malformed section" do - expect_raises(INI::ParseException, "unterminated section") do + expect_raises(INI::ParseException, "Unterminated section") do INI.parse("[section") end end it "fails on data after section" do - expect_raises(INI::ParseException, "data after section") do + expect_raises(INI::ParseException, "Data after section") do INI.parse("[section] foo ") end end it "fails on malformed declaration" do - expect_raises(INI::ParseException, "expected declaration") do + expect_raises(INI::ParseException, "Expected declaration") do INI.parse("foobar") end - expect_raises(INI::ParseException, "expected declaration") do + expect_raises(INI::ParseException, "Expected declaration") do INI.parse("foo: bar") end end diff --git a/spec/std/named_tuple_spec.cr b/spec/std/named_tuple_spec.cr index e0484973598b..4097215dfca3 100644 --- a/spec/std/named_tuple_spec.cr +++ b/spec/std/named_tuple_spec.cr @@ -41,7 +41,7 @@ describe "NamedTuple" do NamedTuple(foo: Int32, bar: Int32).from({:foo => 1, :baz => 2}) end - expect_raises(TypeCastError, /cast from String to Int32 failed/) do + expect_raises(TypeCastError, /[Cc]ast from String to Int32 failed/) do NamedTuple(foo: Int32, bar: Int32).from({:foo => 1, :bar => "foo"}) end end @@ -63,7 +63,7 @@ describe "NamedTuple" do {foo: Int32, bar: Int32}.from({:foo => 1, :baz => 2}) end - expect_raises(TypeCastError, /cast from String to Int32 failed/) do + expect_raises(TypeCastError, /[Cc]ast from String to Int32 failed/) do {foo: Int32, bar: Int32}.from({:foo => 1, :bar => "foo"}) end diff --git a/spec/std/spec_spec.cr b/spec/std/spec_spec.cr index 06e5bc5cf377..e26dc71a4dc7 100644 --- a/spec/std/spec_spec.cr +++ b/spec/std/spec_spec.cr @@ -117,13 +117,13 @@ describe "Spec matchers" do it "detects a nesting `it`" do ex = expect_raises(Spec::NestingSpecError) { it { } } - ex.message.should eq "can't nest `it` or `pending`" + ex.message.should eq "Can't nest `it` or `pending`" ex.file.should eq __FILE__ end it "detects a nesting `pending`" do ex = expect_raises(Spec::NestingSpecError) { pending } - ex.message.should eq "can't nest `it` or `pending`" + ex.message.should eq "Can't nest `it` or `pending`" ex.file.should eq __FILE__ end diff --git a/spec/std/tuple_spec.cr b/spec/std/tuple_spec.cr index c6ead7b1ccf6..015dc436c659 100644 --- a/spec/std/tuple_spec.cr +++ b/spec/std/tuple_spec.cr @@ -225,7 +225,7 @@ describe "Tuple" do Tuple(Int32).from([1, 2]) end - expect_raises(TypeCastError, /cast from String to Int32 failed/) do + expect_raises(TypeCastError, /[Cc]ast from String to Int32 failed/) do Tuple(Int32, String).from(["foo", 1]) end end @@ -239,7 +239,7 @@ describe "Tuple" do {Int32}.from([1, 2]) end - expect_raises(TypeCastError, /cast from String to Int32 failed/) do + expect_raises(TypeCastError, /[Cc]ast from String to Int32 failed/) do {Int32, String}.from(["foo", 1]) end end diff --git a/spec/support/fibers.cr b/spec/support/fibers.cr index 50e5b38e300f..1b095a4422d6 100644 --- a/spec/support/fibers.cr +++ b/spec/support/fibers.cr @@ -3,7 +3,7 @@ def wait_until_blocked(f : Fiber, timeout = 5.seconds) until f.resumable? Fiber.yield - raise "fiber failed to block within #{timeout}" if (Time.monotonic - now) > timeout + raise "Fiber failed to block within #{timeout}" if (Time.monotonic - now) > timeout end end @@ -11,6 +11,6 @@ def wait_until_finished(f : Fiber, timeout = 5.seconds) now = Time.monotonic until f.dead? Fiber.yield - raise "fiber failed to finish within #{timeout}" if (Time.monotonic - now) > timeout + raise "Fiber failed to finish within #{timeout}" if (Time.monotonic - now) > timeout end end diff --git a/src/array.cr b/src/array.cr index d9ce3992db93..f98b11f450c0 100644 --- a/src/array.cr +++ b/src/array.cr @@ -1621,7 +1621,7 @@ class Array(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def sort(&block : T, T -> U) : Array(T) forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} dup.sort! &block @@ -1643,7 +1643,7 @@ class Array(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def unstable_sort(&block : T, T -> U) : Array(T) forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} dup.unstable_sort!(&block) @@ -1664,7 +1664,7 @@ class Array(T) # :inherit: def sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} to_unsafe_slice.sort!(&block) @@ -1674,7 +1674,7 @@ class Array(T) # :inherit: def unstable_sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} to_unsafe_slice.unstable_sort!(&block) diff --git a/src/channel.cr b/src/channel.cr index 74010d9c2c15..f5c46343dc56 100644 --- a/src/channel.cr +++ b/src/channel.cr @@ -71,7 +71,7 @@ class Channel(T) # Implementor that returns `Channel::UseDefault` in `#execute` # must redefine `#default_result` def default_result - raise "unreachable" + raise "Unreachable" end end @@ -406,7 +406,7 @@ class Channel(T) # :nodoc: def self.select(ops : Indexable(SelectAction)) i, m = select_impl(ops, false) - raise "BUG: blocking select returned not ready status" if m.is_a?(NotReady) + raise "BUG: Blocking select returned not ready status" if m.is_a?(NotReady) return i, m end diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 34dbb49d4b76..ae41939fca83 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -1380,7 +1380,7 @@ module Crystal def type_cast_exception_call(from_type, to_type, node, var_name) pieces = [ - StringLiteral.new("cast from ").at(node), + StringLiteral.new("Cast from ").at(node), Call.new(Var.new(var_name).at(node), "class").at(node), StringLiteral.new(" to #{to_type} failed").at(node), ] of ASTNode diff --git a/src/crystal/digest/sha1.cr b/src/crystal/digest/sha1.cr index 7889bd21cbfb..d18a052aaf1f 100644 --- a/src/crystal/digest/sha1.cr +++ b/src/crystal/digest/sha1.cr @@ -42,7 +42,7 @@ class Crystal::Digest::SHA1 < ::Digest if @length_low == 0 @length_high &+= 1 if @length_high == 0 - raise ArgumentError.new "Crypto.sha1: message too long" + raise ArgumentError.new "Message too long" end end diff --git a/src/crystal/dwarf/line_numbers.cr b/src/crystal/dwarf/line_numbers.cr index ab6724629053..26149b9c7096 100644 --- a/src/crystal/dwarf/line_numbers.cr +++ b/src/crystal/dwarf/line_numbers.cr @@ -218,7 +218,7 @@ module Crystal sequence.version = @io.read_bytes(UInt16) if sequence.version < 2 || sequence.version > 5 - raise "unknown line table version: #{sequence.version}" + raise "Unknown line table version: #{sequence.version}" end if sequence.version >= 5 @@ -239,14 +239,14 @@ module Crystal end if sequence.maximum_operations_per_instruction == 0 - raise "invalid maximum operations per instruction: 0" + raise "Invalid maximum operations per instruction: 0" end sequence.default_is_stmt = @io.read_byte == 1 sequence.line_base = @io.read_bytes(Int8).to_i sequence.line_range = @io.read_bytes(UInt8).to_i if sequence.line_range == 0 - raise "invalid line range: 0" + raise "Invalid line range: 0" end sequence.opcode_base = @io.read_bytes(UInt8).to_i diff --git a/src/crystal/syntax_highlighter.cr b/src/crystal/syntax_highlighter.cr index f23109a6f43e..1d4abcb60c70 100644 --- a/src/crystal/syntax_highlighter.cr +++ b/src/crystal/syntax_highlighter.cr @@ -248,7 +248,7 @@ abstract class Crystal::SyntaxHighlighter raise "Unterminated symbol array literal" end else - raise "Bug: shouldn't happen" + raise "BUG: Shouldn't happen" end end end diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index 056aaf7ad77f..78b8344a8212 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -38,5 +38,5 @@ end {% elsif flag?(:win32) %} require "./win32/event_loop_iocp" {% else %} - {% raise "event_loop not supported" %} + {% raise "Event loop not supported" %} {% end %} diff --git a/src/crystal/system/fiber.cr b/src/crystal/system/fiber.cr index 230cf4cc010b..1cc47e2917e1 100644 --- a/src/crystal/system/fiber.cr +++ b/src/crystal/system/fiber.cr @@ -16,5 +16,5 @@ end {% elsif flag?(:win32) %} require "./win32/fiber" {% else %} - {% raise "fiber not supported" %} + {% raise "Fiber not supported" %} {% end %} diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index 3666b7ad512a..9e58962fd49b 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -36,5 +36,5 @@ require "./thread_condition_variable" {% elsif flag?(:win32) %} require "./win32/thread" {% else %} - {% raise "thread not supported" %} + {% raise "Thread not supported" %} {% end %} diff --git a/src/crystal/system/thread_condition_variable.cr b/src/crystal/system/thread_condition_variable.cr index ea5923601aec..a5b933f0af1d 100644 --- a/src/crystal/system/thread_condition_variable.cr +++ b/src/crystal/system/thread_condition_variable.cr @@ -27,5 +27,5 @@ end {% elsif flag?(:win32) %} require "./win32/thread_condition_variable" {% else %} - {% raise "thread condition variable not supported" %} + {% raise "Thread condition variable not supported" %} {% end %} diff --git a/src/crystal/system/thread_mutex.cr b/src/crystal/system/thread_mutex.cr index e3cf9ffeb6cb..2d46523b06d9 100644 --- a/src/crystal/system/thread_mutex.cr +++ b/src/crystal/system/thread_mutex.cr @@ -24,5 +24,5 @@ end {% elsif flag?(:win32) %} require "./win32/thread_mutex" {% else %} - {% raise "thread mutex not supported" %} + {% raise "Thread mutex not supported" %} {% end %} diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index ce536b354e50..9558235ebcf1 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -209,7 +209,7 @@ struct Crystal::System::Process if args unless command.includes?(%("${@}")) - raise ArgumentError.new(%(can't specify arguments in both command and args without including "${@}" into your command)) + raise ArgumentError.new(%(Can't specify arguments in both command and args without including "${@}" into your command)) end {% if flag?(:freebsd) || flag?(:dragonfly) %} diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 24bb4c17ffa3..e920a1d84d5c 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -42,7 +42,7 @@ struct Crystal::System::Process raise RuntimeError.from_winerror("GetExitCodeProcess") end if exit_code == LibC::STILL_ACTIVE - raise "BUG: process still active" + raise "BUG: Process still active" end exit_code end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index f6faaf928f75..058920441732 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -236,7 +236,7 @@ module Crystal::System::Socket end unless schedule_overlapped(read_timeout) - raise IO::TimeoutError.new("accept timed out") + raise IO::TimeoutError.new("#{method} timed out") end operation.wsa_result(socket) do |error| diff --git a/src/dir/glob.cr b/src/dir/glob.cr index 38c52253e330..088480873879 100644 --- a/src/dir/glob.cr +++ b/src/dir/glob.cr @@ -176,10 +176,10 @@ class Dir next_pos = pos - 1 case cmd in RootDirectory - raise "unreachable" if path + raise "Unreachable" if path path_stack << {next_pos, root, nil} in DirectoriesOnly - raise "unreachable" unless path + raise "Unreachable" unless path # FIXME: [win32] File::SEPARATOR_STRING comparison is not sufficient for Windows paths. if path == File::SEPARATOR_STRING fullpath = path diff --git a/src/docs_pseudo_methods.cr b/src/docs_pseudo_methods.cr index 2bfcc6a9863d..d4f1fb832263 100644 --- a/src/docs_pseudo_methods.cr +++ b/src/docs_pseudo_methods.cr @@ -154,7 +154,7 @@ class Object # typeof(a.as(Bool)) # Compile Error: can't cast (Int32 | String) to Bool # # typeof(a.as(String)) # => String - # a.as(String) # Runtime Error: cast from Int32 to String failed + # a.as(String) # Runtime Error: Cast from Int32 to String failed # # typeof(a.as(Int32 | Bool)) # => Int32 # a.as(Int32 | Bool) # => 1 diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index 9652514e7f25..f49c87fae623 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -83,7 +83,7 @@ struct Exception::CallStack # TODO: use WOW64_CONTEXT in place of CONTEXT {% raise "x86 not supported" %} {% else %} - {% raise "architecture not supported" %} + {% raise "Architecture not supported" %} {% end %} stack_frame = LibC::STACKFRAME64.new diff --git a/src/float/printer/dragonbox.cr b/src/float/printer/dragonbox.cr index cb857379266f..65e090e5d0cc 100644 --- a/src/float/printer/dragonbox.cr +++ b/src/float/printer/dragonbox.cr @@ -245,7 +245,7 @@ module Float::Printer::Dragonbox {% elsif D::KAPPA == 2 %} Div.check_divisibility_and_divide_by_pow10_k2(n) {% else %} - {% raise "expected kappa == 1 or kappa == 2" %} + {% raise "Expected kappa == 1 or kappa == 2" %} {% end %} end end diff --git a/src/indexable/mutable.cr b/src/indexable/mutable.cr index 86331554f924..6df8517ee877 100644 --- a/src/indexable/mutable.cr +++ b/src/indexable/mutable.cr @@ -374,7 +374,7 @@ module Indexable::Mutable(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} slice = Slice.new(size) { |i| unsafe_fetch(i) }.sort!(&block) @@ -406,7 +406,7 @@ module Indexable::Mutable(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def unstable_sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} slice = Slice.new(size) { |i| unsafe_fetch(i) }.unstable_sort!(&block) diff --git a/src/ini.cr b/src/ini.cr index 79d7cd1bcdeb..c8d1d50e0384 100644 --- a/src/ini.cr +++ b/src/ini.cr @@ -42,14 +42,14 @@ module INI next when '[' end_idx = line.index(']', offset) - raise ParseException.new("unterminated section", lineno, line.size) unless end_idx - raise ParseException.new("data after section", lineno, end_idx + 1) unless end_idx == line.size - 1 + raise ParseException.new("Unterminated section", lineno, line.size) unless end_idx + raise ParseException.new("Data after section", lineno, end_idx + 1) unless end_idx == line.size - 1 current_section_name = line[offset + 1...end_idx] current_section = ini[current_section_name] ||= Hash(String, String).new else key, eq, value = line.partition('=') - raise ParseException.new("expected declaration", lineno, key.size) if eq != "=" + raise ParseException.new("Expected declaration", lineno, key.size) if eq != "=" current_section[key.strip] = value.strip end diff --git a/src/int.cr b/src/int.cr index a9ef18e09bd7..9033348e7ef1 100644 --- a/src/int.cr +++ b/src/int.cr @@ -369,16 +369,16 @@ struct Int def bits(range : Range) start_index = range.begin if start_index - raise IndexError.new("start index (#{start_index}) must be positive") if start_index < 0 + raise IndexError.new("Start index (#{start_index}) must be positive") if start_index < 0 else start_index = 0 end end_index = range.end if end_index - raise IndexError.new("end index (#{end_index}) must be positive") if end_index < 0 + raise IndexError.new("End index (#{end_index}) must be positive") if end_index < 0 end_index += 1 unless range.exclusive? - raise IndexError.new("end index (#{end_index}) must be greater than start index (#{start_index})") if end_index <= start_index + raise IndexError.new("End index (#{end_index}) must be greater than start index (#{start_index})") if end_index <= start_index else # if there is no end index then we only need to shift return self >> start_index diff --git a/src/json/pull_parser.cr b/src/json/pull_parser.cr index 4716c7f8f79e..90ca53bfea1a 100644 --- a/src/json/pull_parser.cr +++ b/src/json/pull_parser.cr @@ -224,7 +224,7 @@ class JSON::PullParser when .float? float_value.tap { read_next } else - raise "expecting int or float but was #{@kind}" + raise "Expecting int or float but was #{@kind}" end end diff --git a/src/json/serialization.cr b/src/json/serialization.cr index ca4b8e36f4ac..c45f4119a3e4 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -418,7 +418,7 @@ module JSON # ``` macro use_json_discriminator(field, mapping) {% unless mapping.is_a?(HashLiteral) || mapping.is_a?(NamedTupleLiteral) %} - {% mapping.raise "mapping argument must be a HashLiteral or a NamedTupleLiteral, not #{mapping.class_name.id}" %} + {% mapping.raise "Mapping argument must be a HashLiteral or a NamedTupleLiteral, not #{mapping.class_name.id}" %} {% end %} def self.new(pull : ::JSON::PullParser) @@ -471,7 +471,7 @@ module JSON {% elsif key.is_a?(Path) %} when {{key.resolve}} {% else %} - {% key.raise "mapping keys must be one of StringLiteral, NumberLiteral, BoolLiteral, or Path, not #{key.class_name.id}" %} + {% key.raise "Mapping keys must be one of StringLiteral, NumberLiteral, BoolLiteral, or Path, not #{key.class_name.id}" %} {% end %} {% end %} {{value.id}}.from_json(json) diff --git a/src/lib_c.cr b/src/lib_c.cr index a4fe4e6d1ff5..b707d18a3a9d 100644 --- a/src/lib_c.cr +++ b/src/lib_c.cr @@ -30,7 +30,7 @@ lib LibC {% min_supported_version = 28 %} {% api_version_var = env("ANDROID_PLATFORM") || env("ANDROID_NATIVE_API_LEVEL") %} {% api_version = api_version_var ? api_version_var.gsub(/^android-/, "").to_i : default_api_version %} - {% raise "TODO: support Android API level below #{min_supported_version}" unless api_version >= min_supported_version %} + {% raise "TODO: Support Android API level below #{min_supported_version}" unless api_version >= min_supported_version %} ANDROID_API = {{ api_version }} {% end %} diff --git a/src/llvm/type.cr b/src/llvm/type.cr index cd3e5f9d5264..5f9fc70099b6 100644 --- a/src/llvm/type.cr +++ b/src/llvm/type.cr @@ -71,7 +71,7 @@ struct LLVM::Type # The name can be `nil` if the struct is anonymous. # Raises if this type is not a struct. def struct_name : String? - raise "not a Struct" unless kind == Kind::Struct + raise "Not a Struct" unless kind == Kind::Struct name = LibLLVM.get_struct_name(self) name ? String.new(name) : nil @@ -108,12 +108,12 @@ struct LLVM::Type end def vector_size - raise "not a Vector" unless kind == Kind::Vector + raise "Not a Vector" unless kind == Kind::Vector LibLLVM.get_vector_size(self).to_i32 end def return_type - raise "not a Function" unless kind == Kind::Function + raise "Not a Function" unless kind == Kind::Function Type.new LibLLVM.get_return_type(self) end @@ -126,12 +126,12 @@ struct LLVM::Type end def params_size - raise "not a Function" unless kind == Kind::Function + raise "Not a Function" unless kind == Kind::Function LibLLVM.count_param_types(self).to_i end def varargs? - raise "not a Function" unless kind == Kind::Function + raise "Not a Function" unless kind == Kind::Function LibLLVM.is_function_var_arg(self) != 0 end diff --git a/src/named_tuple.cr b/src/named_tuple.cr index 4708d93d3bef..3bf8abd38938 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -85,7 +85,7 @@ struct NamedTuple # NamedTuple(name: String, val: Int32).from({"name" => "number", "val" => num_or_str}) # => {name: "number", val: 42} # # num_or_str = "a string".as(Int32 | String) - # NamedTuple(name: String, val: Int32).from({"name" => "number", "val" => num_or_str}) # raises TypeCastError (cast from String to Int32 failed) + # NamedTuple(name: String, val: Int32).from({"name" => "number", "val" => num_or_str}) # raises TypeCastError (Cast from String to Int32 failed) # ``` # See also: `#from`. def self.from(hash : Hash) : self diff --git a/src/openssl/cipher.cr b/src/openssl/cipher.cr index 7a766974c4bd..bea9a298fc97 100644 --- a/src/openssl/cipher.cr +++ b/src/openssl/cipher.cr @@ -70,7 +70,7 @@ class OpenSSL::Cipher end def iv=(iv) - raise ArgumentError.new "iv length too short: wanted #{iv_len}, got #{iv.bytesize}" if iv.bytesize < iv_len + raise ArgumentError.new "IV length too short: wanted #{iv_len}, got #{iv.bytesize}" if iv.bytesize < iv_len cipherinit iv: iv iv end diff --git a/src/process.cr b/src/process.cr index 888c0db655f5..12173c68b4a7 100644 --- a/src/process.cr +++ b/src/process.cr @@ -196,7 +196,7 @@ class Process File.open(File::NULL, "w") end else - raise "BUG: impossible type in ExecStdio #{stdio.class}" + raise "BUG: Impossible type in ExecStdio #{stdio.class}" end end @@ -284,7 +284,7 @@ class Process when STDERR @error, fork_io = IO.pipe(write_blocking: true) else - raise "BUG: unknown destination io #{dst_io}" + raise "BUG: Unknown destination io #{dst_io}" end fork_io @@ -297,7 +297,7 @@ class Process File.open(File::NULL, "w") end else - raise "BUG: impossible type in stdio #{stdio.class}" + raise "BUG: Impossible type in stdio #{stdio.class}" end end diff --git a/src/raise.cr b/src/raise.cr index 1ee3a89caa56..dd289e7762f0 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -272,6 +272,6 @@ end {% if flag?(:interpreted) %} def __crystal_raise_cast_failed(obj, type_name : String, location : String) - raise TypeCastError.new("cast from #{obj.class} to #{type_name} failed, at #{location}") + raise TypeCastError.new("Cast from #{obj.class} to #{type_name} failed, at #{location}") end {% end %} diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr index 2f2faa514c17..e6cf6eaca7b0 100644 --- a/src/regex/pcre.cr +++ b/src/regex/pcre.cr @@ -49,7 +49,7 @@ module Regex::PCRE when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") when .match_invalid_utf? then raise ArgumentError.new("Regex::Option::MATCH_INVALID_UTF is not supported with PCRE") else - raise "unreachable" + raise "Unreachable" end options &= ~option end @@ -83,7 +83,7 @@ module Regex::PCRE when .ucp? then raise ArgumentError.new("Invalid regex option UCP for `pcre_exec`") when .endanchored? then raise ArgumentError.new("Regex::Option::ENDANCHORED is not supported with PCRE") else - raise "unreachable" + raise "Unreachable" end options &= ~option end @@ -105,7 +105,7 @@ module Regex::PCRE when .no_jit? then raise ArgumentError.new("Regex::Option::NO_JIT is not supported with PCRE") when .no_utf_check? then LibPCRE::NO_UTF8_CHECK else - raise "unreachable" + raise "Unreachable" end options &= ~option end diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 4e7884fef65f..19416723af24 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -79,7 +79,7 @@ module Regex::PCRE2 when .endanchored? then LibPCRE2::ENDANCHORED when .match_invalid_utf? then LibPCRE2::MATCH_INVALID_UTF else - raise "unreachable" + raise "Unreachable" end options &= ~option end @@ -112,7 +112,7 @@ module Regex::PCRE2 when .ucp? then raise ArgumentError.new("Invalid regex option UCP for `pcre2_match`") when .endanchored? then LibPCRE2::ENDANCHORED else - raise "unreachable" + raise "Unreachable" end options &= ~option end @@ -133,7 +133,7 @@ module Regex::PCRE2 when .no_jit? then LibPCRE2::NO_JIT when .no_utf_check? then LibPCRE2::NO_UTF_CHECK else - raise "unreachable" + raise "Unreachable" end options &= ~option end @@ -167,7 +167,7 @@ module Regex::PCRE2 private def pattern_info(what, where) ret = LibPCRE2.pattern_info(@re, what, where) if ret != 0 - raise "error pattern_info #{what}: #{ret}" + raise "Error pattern_info #{what}: #{ret}" end end diff --git a/src/slice.cr b/src/slice.cr index 26a7ae450bb3..0ba78e683ed4 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -932,7 +932,7 @@ struct Slice(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def sort(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} dup.sort! &block @@ -954,7 +954,7 @@ struct Slice(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def unstable_sort(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} dup.unstable_sort!(&block) @@ -1055,7 +1055,7 @@ struct Slice(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} Slice.merge_sort!(self, block) @@ -1098,7 +1098,7 @@ struct Slice(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def unstable_sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} Slice.intro_sort!(to_unsafe, size, block) diff --git a/src/spec/cli.cr b/src/spec/cli.cr index 011d90ecc481..d495c8e38928 100644 --- a/src/spec/cli.cr +++ b/src/spec/cli.cr @@ -50,7 +50,7 @@ module Spec when UInt64 mode else - raise ArgumentError.new("order must be either 'default', 'random', or a numeric seed value") + raise ArgumentError.new("Order must be either 'default', 'random', or a numeric seed value") end @@randomizer_seed = seed diff --git a/src/spec/context.cr b/src/spec/context.cr index 7aa1922fe21e..b612c095cbbc 100644 --- a/src/spec/context.cr +++ b/src/spec/context.cr @@ -307,7 +307,7 @@ module Spec @@spec_nesting = false def check_nesting_spec(file, line, &block) - raise NestingSpecError.new("can't nest `it` or `pending`", file, line) if @@spec_nesting + raise NestingSpecError.new("Can't nest `it` or `pending`", file, line) if @@spec_nesting @@spec_nesting = true begin diff --git a/src/spec/helpers/iterate.cr b/src/spec/helpers/iterate.cr index 122a90af6b3a..be302ebb49c2 100644 --- a/src/spec/helpers/iterate.cr +++ b/src/spec/helpers/iterate.cr @@ -81,7 +81,7 @@ module Spec::Methods # Compare the actual value directly. Since there are less # then expected values, the expectation will fail and raise. %ary.should eq({{ expected }}) - raise "unreachable" + raise "Unreachable" end %ary << %v end diff --git a/src/static_array.cr b/src/static_array.cr index ee502fb63035..0bfaa006e8b8 100644 --- a/src/static_array.cr +++ b/src/static_array.cr @@ -228,7 +228,7 @@ struct StaticArray(T, N) # Raises `ArgumentError` if for any two elements the block returns `nil`.= def sort(&block : T, T -> U) : StaticArray(T, N) forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} ary = dup @@ -251,7 +251,7 @@ struct StaticArray(T, N) # Raises `ArgumentError` if for any two elements the block returns `nil`. def unstable_sort(&block : T, T -> U) : StaticArray(T, N) forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} ary = dup @@ -273,7 +273,7 @@ struct StaticArray(T, N) # :inherit: def sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} to_slice.sort!(&block) @@ -283,7 +283,7 @@ struct StaticArray(T, N) # :inherit: def unstable_sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}" %} {% end %} to_slice.unstable_sort!(&block) diff --git a/src/yaml/any.cr b/src/yaml/any.cr index 2ed8f242999c..8acd9fa057df 100644 --- a/src/yaml/any.cr +++ b/src/yaml/any.cr @@ -348,7 +348,7 @@ struct YAML::Any if raw.responds_to?(:to_json_object_key) raw.to_json_object_key else - raise JSON::Error.new("can't convert #{raw.class} to a JSON object key") + raise JSON::Error.new("Can't convert #{raw.class} to a JSON object key") end end end diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index ebc429ceb77a..ebceb13529ec 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -406,7 +406,7 @@ module YAML # ``` macro use_yaml_discriminator(field, mapping) {% unless mapping.is_a?(HashLiteral) || mapping.is_a?(NamedTupleLiteral) %} - {% mapping.raise "mapping argument must be a HashLiteral or a NamedTupleLiteral, not #{mapping.class_name.id}" %} + {% mapping.raise "Mapping argument must be a HashLiteral or a NamedTupleLiteral, not #{mapping.class_name.id}" %} {% end %} def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) @@ -415,7 +415,7 @@ module YAML end unless node.is_a?(YAML::Nodes::Mapping) - node.raise "expected YAML mapping, not #{node.class}" + node.raise "Expected YAML mapping, not #{node.class}" end node.each do |key, value| From 3226a35caa55ef0a313909f9948acd12dfd66678 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 3 May 2023 17:27:38 +0800 Subject: [PATCH 0501/1551] Fix `File.info(File::NULL)` on Windows (#13421) --- spec/std/file_spec.cr | 3 +-- src/crystal/system/win32/file.cr | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 7edabb6a390d..190b718e217c 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -428,8 +428,7 @@ describe "File" do info.type.should eq(File::Type::Directory) end - # TODO: support stating nul on windows - pending_win32 "gets for a character device" do + it "gets for a character device" do info = File.info(File::NULL) info.type.should eq(File::Type::CharacterDevice) end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 145ac11f7aa3..21930501eaf7 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -164,11 +164,7 @@ module Crystal::System::File return check_not_found_error("Unable to get file info", path) if handle == LibC::INVALID_HANDLE_VALUE begin - if LibC.GetFileInformationByHandle(handle, out file_info) == 0 - raise ::File::Error.from_winerror("Unable to get file info", file: path) - end - - ::File::Info.new(file_info, LibC::FILE_TYPE_DISK) + FileDescriptor.system_info(handle) ensure LibC.CloseHandle(handle) end From 2e43fc41638a214c79382f7a483e1935cfa83871 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 3 May 2023 17:27:59 +0800 Subject: [PATCH 0502/1551] Fix `preview_mt` infinite loop on Windows (#13419) --- src/crystal/scheduler.cr | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index 61df03294875..0667626f787e 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -73,7 +73,7 @@ class Crystal::Scheduler {% end %} {% if flag?(:preview_mt) %} - @fiber_channel = Crystal::FiberChannel.new + private getter(fiber_channel : Crystal::FiberChannel) { Crystal::FiberChannel.new } @free_stacks = Deque(Void*).new {% end %} @lock = Crystal::SpinLock.new @@ -199,6 +199,7 @@ class Crystal::Scheduler end def run_loop + fiber_channel = self.fiber_channel loop do @lock.lock if runnable = @runnables.shift? @@ -208,7 +209,7 @@ class Crystal::Scheduler else @sleeping = true @lock.unlock - fiber = @fiber_channel.receive + fiber = fiber_channel.receive @lock.lock @sleeping = false @@ -222,7 +223,7 @@ class Crystal::Scheduler def send_fiber(fiber : Fiber) @lock.lock if @sleeping - @fiber_channel.send(fiber) + fiber_channel.send(fiber) else @runnables << fiber end From 3114b23fc37ebb419eed30ed018760ee550adecb Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 3 May 2023 23:36:12 +0800 Subject: [PATCH 0503/1551] Clean up unused code for Windows event loop (#13424) --- src/crystal/system/event_loop.cr | 11 -------- src/crystal/system/wasi/event_loop.cr | 5 ---- src/crystal/system/win32/event_loop_iocp.cr | 29 --------------------- src/kernel.cr | 2 +- 4 files changed, 1 insertion(+), 46 deletions(-) diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index 78b8344a8212..cb567d34ff48 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -5,23 +5,12 @@ abstract class Crystal::EventLoop # Runs the event loop. abstract def run_once : Nil - {% unless flag?(:preview_mt) %} - # Reinitializes the event loop after a fork. - abstract def after_fork : Nil - {% end %} - # Create a new resume event for a fiber. abstract def create_resume_event(fiber : Fiber) : Event # Creates a timeout_event. abstract def create_timeout_event(fiber : Fiber) : Event - # Creates a write event for a file descriptor. - abstract def create_fd_write_event(io : IO::Evented, edge_triggered : Bool = false) : Event - - # Creates a read event for a file descriptor. - abstract def create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Event - abstract struct Event # Frees the event. abstract def free : Nil diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr index 8152c0c545b2..b49206b792d2 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/system/wasi/event_loop.cr @@ -7,11 +7,6 @@ end # :nodoc: class Crystal::Wasi::EventLoop < Crystal::EventLoop - {% unless flag?(:preview_mt) %} - def after_fork : Nil - end - {% end %} - # Runs the event loop. def run_once : Nil raise NotImplementedError.new("Crystal::Wasi::EventLoop.run_once") diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 4f239a5a24cb..f617571f381c 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -34,21 +34,6 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop iocp end - # This is a temporary stub as a stand in for fiber swapping required for concurrency - def wait_completion(timeout = nil) - result = LibC.GetQueuedCompletionStatusEx(iocp, out io_entry, 1, out removed, timeout, false) - if result == 0 - error = WinError.value - if timeout && error.wait_timeout? - return false - else - raise IO::Error.from_os_error("GetQueuedCompletionStatusEx", error) - end - end - - true - end - # Runs the event loop. def run_once : Nil next_event = @queue.min_by(&.wake_at) @@ -83,10 +68,6 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop end end - # Reinitializes the event loop after a fork. - def after_fork : Nil - end - def enqueue(event : Crystal::Iocp::Event) unless @queue.includes?(event) @queue << event @@ -102,16 +83,6 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop Crystal::Iocp::Event.new(fiber) end - # Creates a write event for a file descriptor. - def create_fd_write_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::EventLoop::Event - Crystal::Iocp::Event.new(Fiber.current) - end - - # Creates a read event for a file descriptor. - def create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::EventLoop::Event - Crystal::Iocp::Event.new(Fiber.current) - end - def create_timeout_event(fiber) : Crystal::EventLoop::Event Crystal::Iocp::Event.new(fiber, timeout: true) end diff --git a/src/kernel.cr b/src/kernel.cr index 1afd480ae959..bf7dc037862d 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -528,7 +528,7 @@ def abort(message = nil, status = 1) : NoReturn exit status end -{% unless flag?(:preview_mt) || flag?(:wasm32) %} +{% if !flag?(:preview_mt) && flag?(:unix) %} class Process # :nodoc: # From 404726e49f8fb20ed3197be162826953c74d1874 Mon Sep 17 00:00:00 2001 From: Kostya M Date: Fri, 5 May 2023 11:53:14 +0300 Subject: [PATCH 0504/1551] Fixup for `JSON::Serializable` on certain recursively defined types (#13430) --- spec/std/json/serializable_spec.cr | 27 +++++++++++++++++++++++++++ spec/std/yaml/serializable_spec.cr | 27 +++++++++++++++++++++++++++ src/json/serialization.cr | 7 ++++--- src/yaml/serialization.cr | 7 ++++--- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index 4eba9cc9fde0..9d9b00725fa6 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -471,6 +471,25 @@ class JSONSomething property value : JSONSomething? end +module JsonDiscriminatorBug + abstract class Base + include JSON::Serializable + + use_json_discriminator("type", {"a" => A, "b" => B, "c" => C}) + end + + class A < Base + end + + class B < Base + property source : Base + property value : Int32 = 1 + end + + class C < B + end +end + describe "JSON mapping" do it "works with record" do JSONAttrPoint.new(1, 2).to_json.should eq "{\"x\":1,\"y\":2}" @@ -1104,6 +1123,14 @@ describe "JSON mapping" do bar.x.should be_a(JSONStrictDiscriminatorFoo) bar.y.should be_a(JSONStrictDiscriminatorFoo) end + + it "deserializes with discriminator, another recursive type, fixes: #13429" do + c = JsonDiscriminatorBug::Base.from_json %q({"type": "c", "source": {"type": "a"}, "value": 2}) + c.as(JsonDiscriminatorBug::C).value.should eq 2 + + c = JsonDiscriminatorBug::Base.from_json %q({"type": "c", "source": {"type": "a"}}) + c.as(JsonDiscriminatorBug::C).value.should eq 1 + end end describe "namespaced classes" do diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 9050381d45f4..0ea0117eb993 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -404,6 +404,25 @@ class YAMLSomething property value : YAMLSomething? end +module YAMLDiscriminatorBug + abstract class Base + include YAML::Serializable + + use_yaml_discriminator("type", {"a" => A, "b" => B, "c" => C}) + end + + class A < Base + end + + class B < Base + property source : Base + property value : Int32 = 1 + end + + class C < B + end +end + describe "YAML::Serializable" do it "works with record" do YAMLAttrPoint.new(1, 2).to_yaml.should eq "---\nx: 1\ny: 2\n" @@ -1005,6 +1024,14 @@ describe "YAML::Serializable" do bar.x.should be_a(YAMLStrictDiscriminatorFoo) bar.y.should be_a(YAMLStrictDiscriminatorFoo) end + + it "deserializes with discriminator, another recursive type, fixes: #13429" do + c = YAMLDiscriminatorBug::Base.from_yaml %q({"type": "c", "source": {"type": "a"}, "value": 2}) + c.as(YAMLDiscriminatorBug::C).value.should eq 2 + + c = YAMLDiscriminatorBug::Base.from_yaml %q({"type": "c", "source": {"type": "a"}}) + c.as(YAMLDiscriminatorBug::C).value.should eq 1 + end end describe "namespaced classes" do diff --git a/src/json/serialization.cr b/src/json/serialization.cr index c45f4119a3e4..5bfe9af36f34 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -190,6 +190,7 @@ module JSON has_default: ivar.has_default_value?, default: ivar.default_value, nilable: ivar.type.nilable?, + type: ivar.type, root: ann && ann[:root], converter: ann && ann[:converter], presence: ann && ann[:presence], @@ -202,9 +203,9 @@ module JSON # recursively defined serializable types {% for name, value in properties %} %var{name} = {% if value[:has_default] || value[:nilable] %} - nil.as(::Nil | typeof(@{{name}})) + nil.as(::Union(::Nil, {{value[:type]}})) {% else %} - uninitialized typeof(@{{name}}) + uninitialized ::Union({{value[:type]}}) {% end %} %found{name} = false {% end %} @@ -228,7 +229,7 @@ module JSON {% if value[:converter] %} {{value[:converter]}}.from_json(pull) {% else %} - typeof(@{{name}}).new(pull) + ::Union({{value[:type]}}).new(pull) {% end %} end end diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index ebceb13529ec..446fdfde6da5 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -196,6 +196,7 @@ module YAML has_default: ivar.has_default_value?, default: ivar.default_value, nilable: ivar.type.nilable?, + type: ivar.type, converter: ann && ann[:converter], presence: ann && ann[:presence], } @@ -207,9 +208,9 @@ module YAML # recursively defined serializable types {% for name, value in properties %} %var{name} = {% if value[:has_default] || value[:nilable] %} - nil.as(::Nil | typeof(@{{name}})) + nil.as(::Union(::Nil, {{value[:type]}})) {% else %} - uninitialized typeof(@{{name}}) + uninitialized ::Union({{value[:type]}}) {% end %} %found{name} = false {% end %} @@ -232,7 +233,7 @@ module YAML {% if value[:converter] %} {{value[:converter]}}.from_yaml(ctx, value_node) {% else %} - typeof(@{{name}}).new(ctx, value_node) + ::Union({{value[:type]}}).new(ctx, value_node) {% end %} end From 24479462102dfd2b2544393ac2396e2612df790d Mon Sep 17 00:00:00 2001 From: Kostya M Date: Fri, 5 May 2023 11:53:14 +0300 Subject: [PATCH 0505/1551] Fixup for `JSON::Serializable` on certain recursively defined types (#13430) --- spec/std/json/serializable_spec.cr | 27 +++++++++++++++++++++++++++ spec/std/yaml/serializable_spec.cr | 27 +++++++++++++++++++++++++++ src/json/serialization.cr | 7 ++++--- src/yaml/serialization.cr | 7 ++++--- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index 8c934563b7ed..b218611db495 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -477,6 +477,25 @@ class JSONSomethingElse property value : JSONAttrValue(Set(JSONSomethingElse)?)? end +module JsonDiscriminatorBug + abstract class Base + include JSON::Serializable + + use_json_discriminator("type", {"a" => A, "b" => B, "c" => C}) + end + + class A < Base + end + + class B < Base + property source : Base + property value : Int32 = 1 + end + + class C < B + end +end + describe "JSON mapping" do it "works with record" do JSONAttrPoint.new(1, 2).to_json.should eq "{\"x\":1,\"y\":2}" @@ -1110,6 +1129,14 @@ describe "JSON mapping" do bar.x.should be_a(JSONStrictDiscriminatorFoo) bar.y.should be_a(JSONStrictDiscriminatorFoo) end + + it "deserializes with discriminator, another recursive type, fixes: #13429" do + c = JsonDiscriminatorBug::Base.from_json %q({"type": "c", "source": {"type": "a"}, "value": 2}) + c.as(JsonDiscriminatorBug::C).value.should eq 2 + + c = JsonDiscriminatorBug::Base.from_json %q({"type": "c", "source": {"type": "a"}}) + c.as(JsonDiscriminatorBug::C).value.should eq 1 + end end describe "namespaced classes" do diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 7e4364c2113a..976ecbadbb34 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -410,6 +410,25 @@ class YAMLSomethingElse property value : YAMLAttrValue(Set(YAMLSomethingElse)?)? end +module YAMLDiscriminatorBug + abstract class Base + include YAML::Serializable + + use_yaml_discriminator("type", {"a" => A, "b" => B, "c" => C}) + end + + class A < Base + end + + class B < Base + property source : Base + property value : Int32 = 1 + end + + class C < B + end +end + describe "YAML::Serializable" do it "works with record" do YAMLAttrPoint.new(1, 2).to_yaml.should eq "---\nx: 1\ny: 2\n" @@ -1011,6 +1030,14 @@ describe "YAML::Serializable" do bar.x.should be_a(YAMLStrictDiscriminatorFoo) bar.y.should be_a(YAMLStrictDiscriminatorFoo) end + + it "deserializes with discriminator, another recursive type, fixes: #13429" do + c = YAMLDiscriminatorBug::Base.from_yaml %q({"type": "c", "source": {"type": "a"}, "value": 2}) + c.as(YAMLDiscriminatorBug::C).value.should eq 2 + + c = YAMLDiscriminatorBug::Base.from_yaml %q({"type": "c", "source": {"type": "a"}}) + c.as(YAMLDiscriminatorBug::C).value.should eq 1 + end end describe "namespaced classes" do diff --git a/src/json/serialization.cr b/src/json/serialization.cr index ca4b8e36f4ac..c4a1bb7f5902 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -190,6 +190,7 @@ module JSON has_default: ivar.has_default_value?, default: ivar.default_value, nilable: ivar.type.nilable?, + type: ivar.type, root: ann && ann[:root], converter: ann && ann[:converter], presence: ann && ann[:presence], @@ -202,9 +203,9 @@ module JSON # recursively defined serializable types {% for name, value in properties %} %var{name} = {% if value[:has_default] || value[:nilable] %} - nil.as(::Nil | typeof(@{{name}})) + nil.as(::Union(::Nil, {{value[:type]}})) {% else %} - uninitialized typeof(@{{name}}) + uninitialized ::Union({{value[:type]}}) {% end %} %found{name} = false {% end %} @@ -228,7 +229,7 @@ module JSON {% if value[:converter] %} {{value[:converter]}}.from_json(pull) {% else %} - typeof(@{{name}}).new(pull) + ::Union({{value[:type]}}).new(pull) {% end %} end end diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index ebc429ceb77a..c3ff4ad13ae7 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -196,6 +196,7 @@ module YAML has_default: ivar.has_default_value?, default: ivar.default_value, nilable: ivar.type.nilable?, + type: ivar.type, converter: ann && ann[:converter], presence: ann && ann[:presence], } @@ -207,9 +208,9 @@ module YAML # recursively defined serializable types {% for name, value in properties %} %var{name} = {% if value[:has_default] || value[:nilable] %} - nil.as(::Nil | typeof(@{{name}})) + nil.as(::Union(::Nil, {{value[:type]}})) {% else %} - uninitialized typeof(@{{name}}) + uninitialized ::Union({{value[:type]}}) {% end %} %found{name} = false {% end %} @@ -232,7 +233,7 @@ module YAML {% if value[:converter] %} {{value[:converter]}}.from_yaml(ctx, value_node) {% else %} - typeof(@{{name}}).new(ctx, value_node) + ::Union({{value[:type]}}).new(ctx, value_node) {% end %} end From 2886546e099e53334c8d654a39132e52f042e6fe Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sun, 7 May 2023 05:44:39 +1000 Subject: [PATCH 0506/1551] HTTP Server should allow custom concurrency models (#13428) --- src/http/server.cr | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/http/server.cr b/src/http/server.cr index 6e44f4150582..53ee873ed330 100644 --- a/src/http/server.cr +++ b/src/http/server.cr @@ -445,6 +445,12 @@ class HTTP::Server listen end + # Overwrite this method to implement an alternative concurrency handler + # one example could be the use of a fiber pool + protected def dispatch(io) + spawn handle_client(io) + end + # Starts the server. Blocks until the server is closed. def listen : Nil raise "Can't re-start closed server" if closed? @@ -465,9 +471,7 @@ class HTTP::Server end if io - # a non nillable version of the closured io - _io = io - spawn handle_client(_io) + dispatch(io) else break end From 3610f8c6e4ca7ba3e98c39d7265a166f5ecdf588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 6 May 2023 21:47:32 +0200 Subject: [PATCH 0507/1551] Simplify implementation of `Serializable#initialize` (#13433) --- spec/std/json/serializable_spec.cr | 3 +- spec/std/yaml/serializable_spec.cr | 3 +- src/json/pull_parser.cr | 13 ++++++-- src/json/serialization.cr | 50 +++++++++++------------------- src/yaml/schema/core.cr | 13 +++++--- src/yaml/serialization.cr | 47 ++++++++++------------------ 6 files changed, 57 insertions(+), 72 deletions(-) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index 9d9b00725fa6..0cc9d662cc93 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -738,9 +738,10 @@ describe "JSON mapping" do json.a.should eq 11 json.b.should eq "Haha" - json = JSONAttrWithDefaults.from_json(%({"a":null,"b":null})) + json = JSONAttrWithDefaults.from_json(%({"a":null,"b":null,"f":null})) json.a.should eq 11 json.b.should eq "Haha" + json.f.should be_nil end it "bool" do diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 0ea0117eb993..724d82bc094d 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -788,9 +788,10 @@ describe "YAML::Serializable" do yaml.a.should eq 11 yaml.b.should eq "Haha" - yaml = YAMLAttrWithDefaults.from_yaml(%({"a":null,"b":null})) + yaml = YAMLAttrWithDefaults.from_yaml(%({"a":null,"b":null,"f":null})) yaml.a.should eq 11 yaml.b.should eq "Haha" + yaml.f.should be_nil yaml = YAMLAttrWithDefaults.from_yaml(%({"b":""})) yaml.b.should eq "" diff --git a/src/json/pull_parser.cr b/src/json/pull_parser.cr index 90ca53bfea1a..40bc190a72d9 100644 --- a/src/json/pull_parser.cr +++ b/src/json/pull_parser.cr @@ -343,11 +343,20 @@ class JSON::PullParser # Reads a null value and returns it, or executes the given block if the value is not null. def read_null_or(&) + unless read_null? + yield + end + end + + # Reads the current token if its value is null. + # + # Returns `true` if the token was read. + def read_null? : Bool if @kind.null? read_next - nil + true else - yield + false end end diff --git a/src/json/serialization.cr b/src/json/serialization.cr index 5bfe9af36f34..386be1191887 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -202,11 +202,7 @@ module JSON # `%var`'s type must be exact to avoid type inference issues with # recursively defined serializable types {% for name, value in properties %} - %var{name} = {% if value[:has_default] || value[:nilable] %} - nil.as(::Union(::Nil, {{value[:type]}})) - {% else %} - uninitialized ::Union({{value[:type]}}) - {% end %} + %var{name} = uninitialized ::Union({{value[:type]}}) %found{name} = false {% end %} @@ -223,16 +219,18 @@ module JSON {% for name, value in properties %} when {{value[:key]}} begin - {% if value[:has_default] || value[:nilable] %} pull.read_null_or do {% else %} begin {% end %} - %var{name} = - {% if value[:root] %} pull.on_key!({{value[:root]}}) do {% else %} begin {% end %} - {% if value[:converter] %} - {{value[:converter]}}.from_json(pull) - {% else %} - ::Union({{value[:type]}}).new(pull) - {% end %} - end - end + {% if (value[:has_default] && !value[:nilable]) || value[:root] %} + next if pull.read_null? + {% end %} + + %var{name} = + {% if value[:root] %} pull.on_key!({{value[:root]}}) do {% else %} begin {% end %} + {% if value[:converter] %} + {{value[:converter]}}.from_json(pull) + {% else %} + ::Union({{value[:type]}}).new(pull) + {% end %} + end %found{name} = true rescue exc : ::JSON::ParseException raise ::JSON::SerializableError.new(exc.message, self.class.to_s, {{value[:key]}}, *%key_location, exc) @@ -245,25 +243,13 @@ module JSON pull.read_next {% for name, value in properties %} - {% unless value[:nilable] || value[:has_default] %} - if !%found{name} + if %found{name} + @{{name}} = %var{name} + else + {% unless value[:has_default] || value[:nilable] %} raise ::JSON::SerializableError.new("Missing JSON attribute: {{value[:key].id}}", self.class.to_s, nil, *%location, nil) - end - {% end %} - - {% if value[:nilable] %} - {% if value[:has_default] != nil %} - @{{name}} = %found{name} ? %var{name} : {{value[:default]}} - {% else %} - @{{name}} = %var{name} {% end %} - {% elsif value[:has_default] %} - if %found{name} && !%var{name}.nil? - @{{name}} = %var{name} - end - {% else %} - @{{name}} = %var{name} - {% end %} + end {% if value[:presence] %} @{{name}}_present = %found{name} diff --git a/src/yaml/schema/core.cr b/src/yaml/schema/core.cr index b7504f4590e6..0add1e3e5bda 100644 --- a/src/yaml/schema/core.cr +++ b/src/yaml/schema/core.cr @@ -129,9 +129,7 @@ module YAML::Schema::Core # If `node` parses to a null value, returns `nil`, otherwise # invokes the given block. def self.parse_null_or(node : YAML::Nodes::Node, &) - if node.is_a?(YAML::Nodes::Scalar) && parse_null?(node) - nil - else + unless parse_null?(node) yield end end @@ -293,8 +291,13 @@ module YAML::Schema::Core end end - private def self.parse_null?(node : Nodes::Scalar) - parse_null?(node.value) && node.style.plain? + # Returns `true` if *node* parses to a null value. + def self.parse_null?(node : Nodes::Node) + if node.is_a?(Nodes::Scalar) + parse_null?(node.value) && node.style.plain? + else + false + end end private def self.parse_null?(string) diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index 446fdfde6da5..9ad76db851e0 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -207,11 +207,7 @@ module YAML # `%var`'s type must be exact to avoid type inference issues with # recursively defined serializable types {% for name, value in properties %} - %var{name} = {% if value[:has_default] || value[:nilable] %} - nil.as(::Union(::Nil, {{value[:type]}})) - {% else %} - uninitialized ::Union({{value[:type]}}) - {% end %} + %var{name} = uninitialized ::Union({{value[:type]}}) %found{name} = false {% end %} @@ -228,15 +224,16 @@ module YAML {% for name, value in properties %} when {{value[:key]}} begin - {% if value[:has_default] && !value[:nilable] %} YAML::Schema::Core.parse_null_or(value_node) do {% else %} begin {% end %} - %var{name} = - {% if value[:converter] %} - {{value[:converter]}}.from_yaml(ctx, value_node) - {% else %} - ::Union({{value[:type]}}).new(ctx, value_node) - {% end %} - end - + {% if value[:has_default] && !value[:nilable] %} + next if YAML::Schema::Core.parse_null?(value_node) + {% end %} + + %var{name} = + {% if value[:converter] %} + {{value[:converter]}}.from_yaml(ctx, value_node) + {% else %} + ::Union({{value[:type]}}).new(ctx, value_node) + {% end %} %found{name} = true end {% end %} @@ -255,25 +252,13 @@ module YAML end {% for name, value in properties %} - {% unless value[:nilable] || value[:has_default] %} - if !%found{name} + if %found{name} + @{{name}} = %var{name} + else + {% unless value[:has_default] || value[:nilable] %} node.raise "Missing YAML attribute: {{value[:key].id}}" - end - {% end %} - - {% if value[:nilable] %} - {% if value[:has_default] != nil %} - @{{name}} = %found{name} ? %var{name} : {{value[:default]}} - {% else %} - @{{name}} = %var{name} {% end %} - {% elsif value[:has_default] %} - if %found{name} && !%var{name}.nil? - @{{name}} = %var{name} - end - {% else %} - @{{name}} = %var{name} - {% end %} + end {% if value[:presence] %} @{{name}}_present = %found{name} From 54d6469943f78e7f2ab661676a324c2014b43890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 6 May 2023 21:47:47 +0200 Subject: [PATCH 0508/1551] Docs: Reference `Process.executable_path` at `PROGRAM_NAME` (#13434) Co-authored-by: Quinton Miller --- src/kernel.cr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/kernel.cr b/src/kernel.cr index bf7dc037862d..7bca29cef605 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -42,6 +42,12 @@ STDOUT = IO::FileDescriptor.from_stdio(1) STDERR = IO::FileDescriptor.from_stdio(2) # The name, the program was called with. +# +# The result may be a relative or absolute path (including symbolic links), +# just the command name or the empty string. +# +# See `Process.executable_path` for a more convenient alternative that always +# returns the absolute real path to the executable file (if it exists). PROGRAM_NAME = String.new(ARGV_UNSAFE.value) # An array of arguments passed to the program. From c9de7305270fc49ea67390be0916bcd407679e77 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 8 May 2023 19:00:40 +0800 Subject: [PATCH 0509/1551] Disallow creating `Big*` numbers from infinity or NaN (#13351) --- spec/std/big/big_decimal_spec.cr | 10 ++++++++ spec/std/big/big_float_spec.cr | 14 ++++++++++++ spec/std/big/big_int_spec.cr | 10 ++++++++ spec/std/big/big_rational_spec.cr | 38 ++++++++++++++++++++----------- src/big/big_decimal.cr | 1 + src/big/big_float.cr | 6 +++++ src/big/big_int.cr | 3 +++ src/big/big_rational.cr | 1 + 8 files changed, 70 insertions(+), 13 deletions(-) diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index 7888fdd84200..1470aa9d8357 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -180,6 +180,16 @@ describe BigDecimal do end end + it "raises if creating from infinity" do + expect_raises(ArgumentError, "Can only construct from a finite number") { BigDecimal.new(Float32::INFINITY) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigDecimal.new(Float64::INFINITY) } + end + + it "raises if creating from NaN" do + expect_raises(ArgumentError, "Can only construct from a finite number") { BigDecimal.new(Float32::NAN) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigDecimal.new(Float64::NAN) } + end + it "performs arithmetic with bigdecimals" do BigDecimal.new(0).should eq(BigDecimal.new(0) + BigDecimal.new(0)) BigDecimal.new(1).should eq(BigDecimal.new(0) + BigDecimal.new(1)) diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index 1c84f8c4aa86..69322ef86c1a 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -85,6 +85,20 @@ describe "BigFloat" do BigFloat.new(-2147483648_i32).to_s.should eq("-2147483648.0") BigFloat.new(-9223372036854775808_i64).to_s.should eq("-9.223372036854775808e+18") end + + it "raises if creating from infinity" do + expect_raises(ArgumentError, "Can only construct from a finite number") { BigFloat.new(Float32::INFINITY) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigFloat.new(Float64::INFINITY) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigFloat.new(Float32::INFINITY, precision: 128) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigFloat.new(Float64::INFINITY, precision: 128) } + end + + it "raises if creating from NaN" do + expect_raises(ArgumentError, "Can only construct from a finite number") { BigFloat.new(Float32::NAN) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigFloat.new(Float64::NAN) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigFloat.new(Float32::NAN, precision: 128) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigFloat.new(Float64::NAN, precision: 128) } + end end describe "#<=>" do diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr index f852b795221b..5b45b4e3a34a 100644 --- a/spec/std/big/big_int_spec.cr +++ b/spec/std/big/big_int_spec.cr @@ -40,6 +40,16 @@ describe "BigInt" do end end + it "raises if creating from infinity" do + expect_raises(ArgumentError, "Can only construct from a finite number") { BigInt.new(Float32::INFINITY) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigInt.new(Float64::INFINITY) } + end + + it "raises if creating from NaN" do + expect_raises(ArgumentError, "Can only construct from a finite number") { BigInt.new(Float32::NAN) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigInt.new(Float64::NAN) } + end + it "creates from float" do BigInt.new(12.3).to_s.should eq("12") end diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr index c4fbd4f43346..652e15b34726 100644 --- a/spec/std/big/big_rational_spec.cr +++ b/spec/std/big/big_rational_spec.cr @@ -23,24 +23,36 @@ private def test_comp(val, less, equal, greater, file = __FILE__, line = __LINE_ end describe BigRational do - it "initialize" do - BigRational.new(BigInt.new(10), BigInt.new(3)) - .should eq(BigRational.new(10, 3)) + describe ".new" do + it "initialize" do + BigRational.new(BigInt.new(10), BigInt.new(3)) + .should eq(BigRational.new(10, 3)) + + expect_raises(DivisionByZeroError) do + BigRational.new(BigInt.new(2), BigInt.new(0)) + end + + expect_raises(DivisionByZeroError) do + BigRational.new(2, 0) + end + end - expect_raises(DivisionByZeroError) do - BigRational.new(BigInt.new(2), BigInt.new(0)) + it "initializes from BigFloat with high precision" do + (0..12).each do |i| + bf = BigFloat.new(2.0, precision: 64) ** 64 + BigFloat.new(2.0, precision: 64) ** i + br = BigRational.new(bf) + br.should eq(bf) + end end - expect_raises(DivisionByZeroError) do - BigRational.new(2, 0) + it "raises if creating from infinity" do + expect_raises(ArgumentError, "Can only construct from a finite number") { BigRational.new(Float32::INFINITY) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigRational.new(Float64::INFINITY) } end - end - it "initializes from BigFloat with high precision" do - (0..12).each do |i| - bf = BigFloat.new(2.0, precision: 64) ** 64 + BigFloat.new(2.0, precision: 64) ** i - br = BigRational.new(bf) - br.should eq(bf) + it "raises if creating from NaN" do + expect_raises(ArgumentError, "Can only construct from a finite number") { BigRational.new(Float32::NAN) } + expect_raises(ArgumentError, "Can only construct from a finite number") { BigRational.new(Float64::NAN) } end end diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index eccd82109142..b30c1a936681 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -39,6 +39,7 @@ struct BigDecimal < Number # NOTE: Floats are fundamentally less precise than BigDecimals, # which makes initialization from them risky. def self.new(num : Float) + raise ArgumentError.new "Can only construct from a finite number" unless num.finite? new(num.to_s) end diff --git a/src/big/big_float.cr b/src/big/big_float.cr index 0a888086fdd7..72042d46c010 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -66,11 +66,17 @@ struct BigFloat < Float end end + def initialize(num : Float::Primitive) + raise ArgumentError.new "Can only construct from a finite number" unless num.finite? + LibGMP.mpf_init_set_d(out @mpf, num) + end + def initialize(num : Number) LibGMP.mpf_init_set_d(out @mpf, num.to_f64) end def initialize(num : Float, precision : Int) + raise ArgumentError.new "Can only construct from a finite number" unless num.finite? LibGMP.mpf_init2(out @mpf, precision.to_u64) LibGMP.mpf_set_d(self, num.to_f64) end diff --git a/src/big/big_int.cr b/src/big/big_int.cr index 0bf709034e11..ebffe49da9ba 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -86,7 +86,10 @@ struct BigInt < Int end # :ditto: + # + # *num* must be finite. def initialize(num : Float::Primitive) + raise ArgumentError.new "Can only construct from a finite number" unless num.finite? LibGMP.init_set_d(out @mpz, num) end diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index d91f93a2e657..54f1447b17e5 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -45,6 +45,7 @@ struct BigRational < Number # Creates a exact representation of float as rational. def initialize(num : Float::Primitive) + raise ArgumentError.new "Can only construct from a finite number" unless num.finite? # It ensures that `BigRational.new(f) == f` # It relies on fact, that mantissa is at most 53 bits frac, exp = Math.frexp num From 2aeaea0df5c548b7c485375bb5063d92b36ebbe9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 8 May 2023 19:01:18 +0800 Subject: [PATCH 0510/1551] Remove two outdated LLVM fun bindings (#13438) --- src/llvm.cr | 4 ---- src/llvm/lib_llvm.cr | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/llvm.cr b/src/llvm.cr index 12106d9e05d3..16286b77a9cc 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -14,7 +14,6 @@ module LLVM LibLLVM.initialize_x86_target_mc LibLLVM.initialize_x86_asm_printer LibLLVM.initialize_x86_asm_parser - # LibLLVM.link_in_jit LibLLVM.link_in_mc_jit {% else %} raise "ERROR: LLVM was built without X86 target" @@ -31,7 +30,6 @@ module LLVM LibLLVM.initialize_aarch64_target_mc LibLLVM.initialize_aarch64_asm_printer LibLLVM.initialize_aarch64_asm_parser - # LibLLVM.link_in_jit LibLLVM.link_in_mc_jit {% else %} raise "ERROR: LLVM was built without AArch64 target" @@ -48,7 +46,6 @@ module LLVM LibLLVM.initialize_arm_target_mc LibLLVM.initialize_arm_asm_printer LibLLVM.initialize_arm_asm_parser - # LibLLVM.link_in_jit LibLLVM.link_in_mc_jit {% else %} raise "ERROR: LLVM was built without ARM target" @@ -65,7 +62,6 @@ module LLVM LibLLVM.initialize_webassembly_target_mc LibLLVM.initialize_webassembly_asm_printer LibLLVM.initialize_webassembly_asm_parser - # LibLLVM.link_in_jit LibLLVM.link_in_mc_jit {% else %} raise "ERROR: LLVM was built without WebAssembly target" diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index dc491c536db2..613a8cdd4a71 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -222,7 +222,6 @@ lib LibLLVM fun initialize_webassembly_target = LLVMInitializeWebAssemblyTarget fun initialize_webassembly_target_info = LLVMInitializeWebAssemblyTargetInfo fun initialize_webassembly_target_mc = LLVMInitializeWebAssemblyTargetMC - fun initialize_native_target = LLVMInitializeNativeTarget fun is_constant = LLVMIsConstant(val : ValueRef) : Int32 fun is_function_var_arg = LLVMIsFunctionVarArg(ty : TypeRef) : Int32 fun module_create_with_name_in_context = LLVMModuleCreateWithNameInContext(module_id : UInt8*, context : ContextRef) : ModuleRef @@ -266,7 +265,6 @@ lib LibLLVM fun type_of = LLVMTypeOf(val : ValueRef) : TypeRef fun write_bitcode_to_file = LLVMWriteBitcodeToFile(module : ModuleRef, path : UInt8*) : Int32 fun verify_module = LLVMVerifyModule(module : ModuleRef, action : LLVM::VerifierFailureAction, outmessage : UInt8**) : Int32 - fun link_in_jit = LLVMLinkInJIT fun link_in_mc_jit = LLVMLinkInMCJIT fun start_multithreaded = LLVMStartMultithreaded : Int32 fun stop_multithreaded = LLVMStopMultithreaded From 98c091ee9dd5a0e75b3dd47b642bd170b4c84a39 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 8 May 2023 19:01:28 +0800 Subject: [PATCH 0511/1551] Use `Int#bit_length` instead of `Math.log2` followed by `#to_i` (#13440) --- src/hash.cr | 2 +- src/slice/sort.cr | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hash.cr b/src/hash.cr index a8213aa478dd..82ded213602f 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -255,7 +255,7 @@ class Hash(K, V) @indices_bytesize = 1 end - @indices_size_pow2 = Math.log2(initial_indices_size).to_u8 + @indices_size_pow2 = initial_indices_size.bit_length.to_u8 - 1 end @size = 0 diff --git a/src/slice/sort.cr b/src/slice/sort.cr index a25057af94c7..7e903c833fe4 100644 --- a/src/slice/sort.cr +++ b/src/slice/sort.cr @@ -1,7 +1,7 @@ struct Slice(T) protected def self.intro_sort!(a, n) return if n < 2 - quick_sort_for_intro_sort!(a, n, Math.log2(n).to_i * 2) + quick_sort_for_intro_sort!(a, n, (n.bit_length - 1) * 2) insertion_sort!(a, n) end @@ -99,7 +99,7 @@ struct Slice(T) protected def self.intro_sort!(a, n, comp) return if n < 2 - quick_sort_for_intro_sort!(a, n, Math.log2(n).to_i * 2, comp) + quick_sort_for_intro_sort!(a, n, (n.bit_length - 1) * 2, comp) insertion_sort!(a, n, comp) end From c14880635c779951421a2c4c8af4a676b5c24c44 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 18 Apr 2023 22:41:42 +0800 Subject: [PATCH 0512/1551] Always use 0 for offset of `StaticArray`'s `@buffer` (#13319) --- spec/compiler/codegen/offsetof_spec.cr | 8 +++++++ spec/compiler/interpreter/pointers_spec.cr | 26 ++++++++++++++++++++++ src/compiler/crystal/codegen/codegen.cr | 6 ++--- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/spec/compiler/codegen/offsetof_spec.cr b/spec/compiler/codegen/offsetof_spec.cr index f7e7971be6b2..c42fc6f4ad59 100644 --- a/spec/compiler/codegen/offsetof_spec.cr +++ b/spec/compiler/codegen/offsetof_spec.cr @@ -56,4 +56,12 @@ describe "Code gen: offsetof" do (pointerof(f).as(Void*) + offsetof(Foo, @y).to_i64).as(UInt32*).value == f.y CRYSTAL end + + it "returns offset of `StaticArray#@buffer`" do + run(<<-CRYSTAL).to_b.should be_true + x = uninitialized Int32[4] + pointerof(x.@buffer).value = 12345 + (pointerof(x).as(Void*) + offsetof(Int32[4], @buffer).to_i64).as(Int32*).value == x.@buffer + CRYSTAL + end end diff --git a/spec/compiler/interpreter/pointers_spec.cr b/spec/compiler/interpreter/pointers_spec.cr index 571e63843925..d3049d2ac256 100644 --- a/spec/compiler/interpreter/pointers_spec.cr +++ b/spec/compiler/interpreter/pointers_spec.cr @@ -121,6 +121,32 @@ describe Crystal::Repl::Interpreter do CRYSTAL end + it "pointerof read `StaticArray#@buffer` (1)" do + interpret(<<-CRYSTAL).should eq(2) + struct StaticArray(T, N) + def to_unsafe + pointerof(@buffer) + end + + def x + @buffer + end + end + + foo = uninitialized Int32[4] + foo.to_unsafe.value = 2 + foo.x + CRYSTAL + end + + it "pointerof read `StaticArray#@buffer` (2)" do + interpret(<<-CRYSTAL).should eq(2) + foo = uninitialized Int32[4] + pointerof(foo.@buffer).value = 2 + foo.@buffer + CRYSTAL + end + it "interprets pointer set and get (union type)" do interpret(<<-CRYSTAL).should eq(10) ptr = Pointer(Int32 | Bool).malloc(1_u64) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 3b3a81757bb8..34dbb49d4b76 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -105,13 +105,13 @@ module Crystal end def offset_of(type, element_index) - return 0_u64 if type.extern_union? + return 0_u64 if type.extern_union? || type.is_a?(StaticArrayInstanceType) llvm_typer.offset_of(llvm_typer.llvm_type(type), element_index) end def instance_offset_of(type, element_index) - # extern unions must be value types, which always use the above - # `offset_of` instead + # extern unions and static arrays must be value types, which always use + # the above `offset_of` instead llvm_typer.offset_of(llvm_typer.llvm_struct_type(type), element_index + 1) end end From e3b339b7324af889dccb33638f05b66921ae581c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 26 Apr 2023 03:44:12 +0800 Subject: [PATCH 0513/1551] Fix `String#scan` with empty `Regex` match at multibyte char (#13387) --- spec/std/string_spec.cr | 6 +++++- src/string.cr | 13 ++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 342866f46a42..c70a432ecccc 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2266,7 +2266,7 @@ describe "String" do end end - describe "scan" do + describe "#scan" do it "does without block" do a = "cruel world" a.scan(/\w+/).map(&.[0]).should eq(["cruel", "world"]) @@ -2304,6 +2304,10 @@ describe "String" do "hello world".scan(/\w+|(?= )/).map(&.[0]).should eq(["hello", "", "world"]) end + it "works when match is empty, multibyte char" do + "\u{80}\u{800}\u{10000}".scan(/()/).map(&.begin).should eq([0, 1, 2, 3]) + end + it "works with strings with block" do res = [] of String "bla bla ablf".scan("bl") { |s| res << s } diff --git a/src/string.cr b/src/string.cr index eb437fcaf4af..2f116834f0c9 100644 --- a/src/string.cr +++ b/src/string.cr @@ -1715,8 +1715,8 @@ class String if single_byte_optimizable? unsafe_byte_slice_string(1, bytesize - 1) else - reader = Char::Reader.new(self) - unsafe_byte_slice_string(reader.current_char_width, bytesize - reader.current_char_width) + first_char_bytesize = char_bytesize_at(0) + unsafe_byte_slice_string(first_char_bytesize, bytesize - first_char_bytesize) end end @@ -2524,8 +2524,7 @@ class String byte_index = char_index_to_byte_index(index) raise IndexError.new unless byte_index - reader = Char::Reader.new(self, pos: byte_index) - width = reader.current_char_width + width = char_bytesize_at(byte_index) replacement_width = replacement.bytesize new_bytesize = bytesize - width + replacement_width @@ -2808,7 +2807,7 @@ class String if string.bytesize == 0 # The pattern matched an empty result. We must advance one character to avoid stagnation. - byte_offset = index + Char::Reader.new(self, pos: byte_offset).current_char_width + byte_offset = index + char_bytesize_at(byte_offset) last_byte_offset = index else byte_offset = index + string.bytesize @@ -2866,7 +2865,7 @@ class String if str.bytesize == 0 # The pattern matched an empty result. We must advance one character to avoid stagnation. - byte_offset = index + Char::Reader.new(self, pos: byte_offset).current_char_width + byte_offset = index + char_bytesize_at(byte_offset) last_byte_offset = index else byte_offset = index + str.bytesize @@ -4589,7 +4588,7 @@ class String $~ = match yield match match_bytesize = match.byte_end(0) - index - match_bytesize += 1 if match_bytesize == 0 + match_bytesize += char_bytesize_at(byte_offset) if match_bytesize == 0 byte_offset = index + match_bytesize end From 51b842bf56808ccee21b05172e93f7c74e926fe4 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 28 Apr 2023 01:02:42 +0800 Subject: [PATCH 0514/1551] Fix `Log::Metadata#dup` crash with 2+ entries (#13369) --- spec/std/log/metadata_spec.cr | 8 ++++++++ src/log/metadata.cr | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/spec/std/log/metadata_spec.cr b/spec/std/log/metadata_spec.cr index 884a4f9e2898..ba0b9c1a616d 100644 --- a/spec/std/log/metadata_spec.cr +++ b/spec/std/log/metadata_spec.cr @@ -28,6 +28,14 @@ describe Log::Metadata do m({a: 1}).extend({} of Symbol => String).should_not be_empty end + describe "#dup" do + it "creates a shallow copy" do + Log::Metadata.empty.dup.should eq(Log::Metadata.empty) + m({a: 1}).dup.should eq(m({a: 1})) + m({a: 1, b: 3}).dup.should eq(m({a: 1, b: 3})) + end + end + it "extend" do m({a: 1}).extend({b: 2}).should eq(m({a: 1, b: 2})) m({a: 1, b: 3}).extend({b: 2}).should eq(m({a: 1, b: 2})) diff --git a/src/log/metadata.cr b/src/log/metadata.cr index 258b1b817c52..b1ea8ae38fa9 100644 --- a/src/log/metadata.cr +++ b/src/log/metadata.cr @@ -37,6 +37,10 @@ class Log::Metadata data end + def dup : self + self + end + protected def setup(@parent : Metadata?, entries : NamedTuple | Hash) @size = @overridden_size = entries.size @max_total_size = @size + (@parent.try(&.max_total_size) || 0) From c8b2def8348d1eac62bae24d6efbcb72fad8f600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 2 May 2023 11:39:25 +0200 Subject: [PATCH 0515/1551] Refactor implementation of `Iterator::ChainIterator` (#13412) --- spec/std/iterator_spec.cr | 5 +++++ src/iterator.cr | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/std/iterator_spec.cr b/spec/std/iterator_spec.cr index 417b35612035..e308e435cde6 100644 --- a/spec/std/iterator_spec.cr +++ b/spec/std/iterator_spec.cr @@ -165,6 +165,11 @@ describe Iterator do iter.next.should be_a(Iterator::Stop) end + # NOTE: This spec would only fail in release mode. + it "does not experience tuple upcase bug of #13411" do + [{true}].each.chain([{1}].each).first(3).to_a.should eq [{true}, {1}] + end + describe "chain indeterminate number of iterators" do it "chains all together" do iters = [[0], [1], [2, 3], [4, 5, 6]].each.map &.each diff --git a/src/iterator.cr b/src/iterator.cr index 78f6fee31e7b..22b215440065 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -277,16 +277,15 @@ module Iterator(T) end def next - if @iterator1_consumed - @iterator2.next - else + unless @iterator1_consumed value = @iterator1.next if value.is_a?(Stop) @iterator1_consumed = true - value = @iterator2.next + else + return value end - value end + @iterator2.next end end From 88c0206818f68452cefe87e27a0391fd71301068 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 29 Apr 2023 20:05:52 +0800 Subject: [PATCH 0516/1551] Check subject UTF8 validity just once for `String#gsub`, `#scan`, `#split` (#13406) --- src/string.cr | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/string.cr b/src/string.cr index 2f116834f0c9..63700558b26c 100644 --- a/src/string.cr +++ b/src/string.cr @@ -2872,7 +2872,7 @@ class String last_byte_offset = byte_offset end - match = pattern.match_at_byte_index(self, byte_offset) + match = pattern.match_at_byte_index(self, byte_offset, Regex::MatchOptions::NO_UTF_CHECK) end if last_byte_offset < bytesize @@ -4100,7 +4100,8 @@ class String count = 0 match_offset = slice_offset = 0 - while match = separator.match_at_byte_index(self, match_offset) + options = Regex::MatchOptions::None + while match = separator.match_at_byte_index(self, match_offset, options) index = match.byte_begin(0) match_bytesize = match.byte_end(0) - index next_offset = index + match_bytesize @@ -4124,6 +4125,7 @@ class String break if limit && count + 1 == limit break if match_offset >= bytesize + options |= :no_utf_check end yield byte_slice(slice_offset) unless remove_empty && slice_offset == bytesize @@ -4583,13 +4585,15 @@ class String def scan(pattern : Regex, &) : self byte_offset = 0 - while match = pattern.match_at_byte_index(self, byte_offset) + options = Regex::MatchOptions::None + while match = pattern.match_at_byte_index(self, byte_offset, options) index = match.byte_begin(0) $~ = match yield match match_bytesize = match.byte_end(0) - index match_bytesize += char_bytesize_at(byte_offset) if match_bytesize == 0 byte_offset = index + match_bytesize + options |= :no_utf_check end self From 7aa5cdd86544005a37090f848d1304df1c181fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 9 May 2023 10:52:26 +0200 Subject: [PATCH 0517/1551] Add Changelog for 1.8.2 (#13445) Co-authored-by: Stephen von Takach --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02ea8be6c01c..3021502fc9f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,34 @@ +# 1.8.2 (2023-05-08) + +## Standard Library + +### Collection + +- Fix codegen bug with `Iterator::ChainIterator` ([#13412](https://github.com/crystal-lang/crystal/pull/13412), thanks @straight-shoota) + +### Log + +- Fix `Log::Metadata#dup` crash with 2+ entries ([#13369](https://github.com/crystal-lang/crystal/pull/13369), thanks @HertzDevil) + +### Serialization + +- Fixup for `JSON::Serializable` on certain recursively defined types ([#13430](https://github.com/crystal-lang/crystal/pull/13430), thanks @kostya) + +### Text + +- Fix `String#scan` with empty `Regex` match at multibyte char ([#13387](https://github.com/crystal-lang/crystal/pull/13387), thanks @HertzDevil) +- **(performance)** Check subject UTF-8 validity just once for `String#gsub`, `#scan`, `#split` ([#13406](https://github.com/crystal-lang/crystal/pull/13406), thanks @HertzDevil) + +## Compiler + +### Codegen + +- Always use 0 for offset of `StaticArray`'s `@buffer` ([#13319](https://github.com/crystal-lang/crystal/pull/13319), thanks @HertzDevil) + +## Other + +- Backport bugfixes to release/1.8 for release 1.8.2 ([#3435](https://github.com/crystal-lang/crystal/pull/13435), thanks @straight-shoota) + # 1.8.1 (2023-04-20) ## Standard Library diff --git a/src/VERSION b/src/VERSION index a8fdfda1c782..53adb84c8220 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.8.1 +1.8.2 From c5b81b8c341102e793a5abfe0892cacb23a19ad9 Mon Sep 17 00:00:00 2001 From: pricelessrabbit Date: Tue, 9 May 2023 15:16:47 +0200 Subject: [PATCH 0518/1551] Added `Enumerable#in_slices_of` (#13108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Quinton Miller Co-authored-by: Johannes Müller --- spec/std/enumerable_spec.cr | 13 +++++++++++++ src/enumerable.cr | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 0339c9d8c0f0..41ba27e5e1f8 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -569,6 +569,19 @@ describe "Enumerable" do end end + describe "in slices of" do + it { [1, 2, 3].in_slices_of(1).should eq([[1], [2], [3]]) } + it { [1, 2, 3].in_slices_of(2).should eq([[1, 2], [3]]) } + it { [1, 2, 3, 4].in_slices_of(3).should eq([[1, 2, 3], [4]]) } + it { ([] of Int32).in_slices_of(2).should eq([] of Array(Int32)) } + + it "raises argument error if size is less than 0" do + expect_raises ArgumentError, "Size must be positive" do + [1, 2, 3].in_slices_of(0) + end + end + end + describe "includes?" do it "is true if the object exists in the collection" do [1, 2, 3].includes?(2).should be_true diff --git a/src/enumerable.cr b/src/enumerable.cr index 7c9482d493ff..d71d93ea48c7 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -629,6 +629,22 @@ module Enumerable(T) end end + # Returns an `Array` with chunks in the given size. + # Last chunk can be smaller depending on the number of remaining items. + # + # ``` + # [1, 2, 3].in_slices_of(2) # => [[1, 2], [3]] + # ``` + def in_slices_of(size : Int) : Array(Array(T)) + raise ArgumentError.new("Size must be positive") if size <= 0 + + ary = Array(Array(T)).new + each_slice_internal(size, Array(T), false) do |slice| + ary << slice + end + ary + end + # Returns `true` if the collection contains *obj*, `false` otherwise. # # ``` From 9a94d7ff659896704fef7d7e2f73012ff56083df Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 10 May 2023 15:45:42 +0800 Subject: [PATCH 0519/1551] Use GMP's functions for `Float`-to-`BigRational` conversion (#13352) Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Beta Ziliani --- src/big/big_rational.cr | 36 ++++++++---------------------------- src/big/lib_gmp.cr | 4 +++- 2 files changed, 11 insertions(+), 29 deletions(-) diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index 54f1447b17e5..0547b7934e55 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -43,37 +43,17 @@ struct BigRational < Number initialize(num, 1) end - # Creates a exact representation of float as rational. - def initialize(num : Float::Primitive) + # Creates an exact representation of float as rational. + # + # Raises `ArgumentError` if *num* is not finite. + def self.new(num : Float::Primitive) raise ArgumentError.new "Can only construct from a finite number" unless num.finite? - # It ensures that `BigRational.new(f) == f` - # It relies on fact, that mantissa is at most 53 bits - frac, exp = Math.frexp num - ifrac = Math.ldexp(frac.to_f64, Float64::MANT_DIGITS).to_i64 - exp -= Float64::MANT_DIGITS - initialize ifrac, 1 - if exp >= 0 - LibGMP.mpq_mul_2exp(out @mpq, self, exp) - else - LibGMP.mpq_div_2exp(out @mpq, self, -exp) - end + new { |mpq| LibGMP.mpq_set_d(mpq, num) } end - # :ditto: - def initialize(num : BigFloat) - frac, exp = Math.frexp num - prec = LibGMP.mpf_get_prec(frac) - # the mantissa has at most `prec + 1` bits, because the first fractional bit - # of `frac` is always 1, and `prec` variable bits follow - # TODO: use `Math.ldexp` after #11007 - ifrac = BigFloat.new { |mpf| LibGMP.mpf_mul_2exp(mpf, frac, prec + 1) }.to_big_i - exp -= prec + 1 - initialize ifrac, 1 - if exp >= 0 - LibGMP.mpq_mul_2exp(out @mpq, self, exp) - else - LibGMP.mpq_div_2exp(out @mpq, self, -exp) - end + # Creates an exact representation of float as rational. + def self.new(num : BigFloat) + new { |mpq| LibGMP.mpq_set_f(mpq, num) } end # Creates a `BigRational` from the given *num*. diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index ec0f853b3068..c610ca04d081 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -168,7 +168,9 @@ lib LibGMP # # Conversion fun mpq_get_str = __gmpq_get_str(str : UInt8*, base : Int, op : MPQ*) : UInt8* - fun mpq_get_d = __gmpq_get_d(x : MPQ*) : Float64 + fun mpq_get_d = __gmpq_get_d(op : MPQ*) : Double + fun mpq_set_d = __gmpq_set_d(rop : MPQ*, op : Double) + fun mpq_set_f = __gmpq_set_f(rop : MPQ*, op : MPF*) # # Compare fun mpq_cmp = __gmpq_cmp(x : MPQ*, o : MPQ*) : Int32 From 4c6f27f97460e82eb6d4e684c2afa93a9dbf0ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 10 May 2023 09:45:56 +0200 Subject: [PATCH 0520/1551] Fix `Log::Builder` append `BroadcastBackend` to itself (#13405) --- spec/std/log/builder_spec.cr | 57 ++++++++++++++++++++++++++++++++++++ src/log/builder.cr | 28 ++++++++---------- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/spec/std/log/builder_spec.cr b/spec/std/log/builder_spec.cr index d91453044b60..83627a288fd1 100644 --- a/spec/std/log/builder_spec.cr +++ b/spec/std/log/builder_spec.cr @@ -47,6 +47,63 @@ describe Log::Builder do log.level.should eq(s(:info)) end + it "does not alter user-provided broadcast backend" do + builder = Log::Builder.new + a = Log::MemoryBackend.new + b = Log::MemoryBackend.new + + broadcast = Log::BroadcastBackend.new + broadcast.append(a, :fatal) + previous_backends = broadcast.@backends.dup + + builder.bind("db", :trace, broadcast) + builder.bind("db", :info, b) + + log = builder.for("db") + + backend = log.backend.should be_a(Log::BroadcastBackend) + backend.should_not be broadcast + broadcast.@backends.should eq(previous_backends) + end + + it "creates a log for broadcast backend" do + builder = Log::Builder.new + a = Log::MemoryBackend.new + b = Log::MemoryBackend.new + + broadcast = Log::BroadcastBackend.new + broadcast.append(a, :fatal) + + builder.bind("db", :trace, broadcast) + builder.bind("db", :info, b) + + log = builder.for("db") + + backend = log.backend.should be_a(Log::BroadcastBackend) + backend.@backends.should eq({broadcast => s(:trace), b => s(:info)}) + log.source.should eq("db") + log.level.should eq(s(:trace)) + end + + it "creates a log for same broadcast backend added multiple times" do + builder = Log::Builder.new + a = Log::MemoryBackend.new + + broadcast = Log::BroadcastBackend.new + broadcast.append(a, :fatal) + + builder.bind("db", :trace, broadcast) + builder.bind("db", :info, broadcast) + + log = builder.for("db") + + backend = log.backend.should be_a(Log::BroadcastBackend) + backend.should be(broadcast) + backend.@backends.should eq({a => s(:fatal)}) + log.source.should eq("db") + log.level.should eq(s(:info)) + end + it "uses last level for a source x backend" do builder = Log::Builder.new a = Log::MemoryBackend.new diff --git a/src/log/builder.cr b/src/log/builder.cr index 7be6f10285d3..a6ed72b08960 100644 --- a/src/log/builder.cr +++ b/src/log/builder.cr @@ -100,26 +100,24 @@ class Log::Builder when Nil log.backend = backend log.initial_level = level - when BroadcastBackend - current_backend.append(backend, level) - # initial_level needs to be recomputed since the append_backend - # might be called with the same backend as before but with a - # different (higher) level - log.initial_level = current_backend.min_level - current_backend.level = log.changed_level + when backend + # if the bind applies for the same backend, the last applied + # level should be used + log.initial_level = level else - if current_backend == backend - # if the bind applies for the same backend, the last applied - # level should be used - log.initial_level = level - else + broadcast = current_backend.as?(BroadcastBackend) + # If the current backend is not a broadcast backend , we need to + # auto-create a broadcast backend for distributing the different log backends. + # A broadcast backend explicitly added as a binding, must not be mutated, + # so that requires to create a new one as well. + if !broadcast || @bindings.any? { |binding| binding.backend.same?(current_backend) } broadcast = BroadcastBackend.new broadcast.append(current_backend, log.initial_level) - broadcast.append(backend, level) - broadcast.level = log.changed_level log.backend = broadcast - log.initial_level = broadcast.min_level end + broadcast.append(backend, level) + broadcast.level = log.changed_level + log.initial_level = broadcast.min_level end end From 0d2471ce1f6eb1622681a3e08ccb7b6780e63cf2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 10 May 2023 20:04:43 +0800 Subject: [PATCH 0521/1551] Remove most `LLVM::DIBuilder` functions from `llvm_ext.cc` (#13448) --- .../crystal/codegen/crystal_llvm_builder.cr | 8 + src/compiler/crystal/codegen/debug.cr | 46 ++-- src/llvm/di_builder.cr | 154 +++++++++--- src/llvm/enums.cr | 105 ++++++++- src/llvm/ext/llvm_ext.cc | 222 +----------------- src/llvm/lib_llvm.cr | 135 ++++++++++- src/llvm/lib_llvm_ext.cr | 108 +-------- 7 files changed, 388 insertions(+), 390 deletions(-) diff --git a/src/compiler/crystal/codegen/crystal_llvm_builder.cr b/src/compiler/crystal/codegen/crystal_llvm_builder.cr index 55ad6f49735a..9bf84dc25d7f 100644 --- a/src/compiler/crystal/codegen/crystal_llvm_builder.cr +++ b/src/compiler/crystal/codegen/crystal_llvm_builder.cr @@ -61,6 +61,14 @@ module Crystal @builder.build_operand_bundle_def(name, values) end + def current_debug_location_metadata + {% if LibLLVM::IS_LT_90 %} + LibLLVM.value_as_metadata LibLLVM.get_current_debug_location(@builder) + {% else %} + LibLLVM.get_current_debug_location2(@builder) + {% end %} + end + def to_unsafe @builder.to_unsafe end diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index df1b33b23bd8..f866e14e12f8 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -2,12 +2,18 @@ require "./codegen" module Crystal class CodeGenVisitor - CRYSTAL_LANG_DEBUG_IDENTIFIER = 0x28_u32 - # - # We have to use it because LLDB has builtin type system support for C++/clang that we can use for now for free. - # Later on we can implement LLDB Crystal type system so we can get official Language ID - # - CPP_LANG_DEBUG_IDENTIFIER = 0x0004_u32 + # workaround for `LLVM::Builder` not being GC'ed (#13250) + private class DIBuilder + def initialize(mod : LLVM::Module) + @builder = LLVM::DIBuilder.new(mod) + end + + def finalize + @builder.dispose + end + + forward_missing_to @builder + end record FunMetadata, filename : String, metadata : LibLLVM::MetadataRef @@ -20,13 +26,14 @@ module Crystal @debug_types_per_module = {} of LLVM::Module => Hash(Type, LibLLVM::MetadataRef?) def di_builder(llvm_module = @llvm_mod || @main_mod) - di_builders = @di_builders ||= {} of LLVM::Module => LLVM::DIBuilder - di_builders[llvm_module] ||= LLVM::DIBuilder.new(llvm_module).tap do |di_builder| + di_builders = @di_builders ||= {} of LLVM::Module => DIBuilder + di_builders[llvm_module] ||= DIBuilder.new(llvm_module).tap do |di_builder| file, dir = file_and_dir(llvm_module.name == "" ? "main" : llvm_module.name) # @debug.variables? is set to true if parameter --debug is set in command line. # This flag affects only debug variables generation. It sets Optimized parameter to false. is_optimised = !@debug.variables? - di_builder.create_compile_unit(CPP_LANG_DEBUG_IDENTIFIER, file, dir, "Crystal", is_optimised, "", 0_u32) + # TODO: switch to Crystal's language code for LLVM 16+ (#13174) + di_builder.create_compile_unit(LLVM::DwarfSourceLanguage::C_plus_plus, file, dir, "Crystal", is_optimised, "", 0_u32) end end @@ -66,8 +73,7 @@ module Crystal int = di_builder.create_basic_type("int", 32, 32, LLVM::DwarfTypeEncoding::Signed) debug_types << int end - debug_types_array = di_builder.get_or_create_type_array(debug_types) - di_builder.create_subroutine_type(nil, debug_types_array) + di_builder.create_subroutine_type(nil, debug_types) end def debug_type_cache @@ -141,7 +147,6 @@ module Crystal end di_builder.create_enumerator(name, value) end - elements = di_builder.get_or_create_array(elements) di_builder.create_enumeration_type(nil, original_type.to_s, nil, 1, 32, 32, elements, get_debug_type(type.base_type)) end @@ -165,11 +170,10 @@ module Crystal end size = @program.target_machine.data_layout.size_in_bits(struct_type) - elements = di_builder.get_or_create_type_array(element_types) if type.extern_union? - debug_type = di_builder.create_union_type(nil, original_type.to_s, current_debug_file, 1, size, size, LLVM::DIFlags::Zero, elements) + debug_type = di_builder.create_union_type(nil, original_type.to_s, current_debug_file, 1, size, size, LLVM::DIFlags::Zero, element_types) else - debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, size, size, LLVM::DIFlags::Zero, nil, elements) + debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, size, size, LLVM::DIFlags::Zero, nil, element_types) unless type.struct? debug_type = di_builder.create_pointer_type(debug_type, 8u64 * llvm_typer.pointer_size, 8u64 * llvm_typer.pointer_size, original_type.to_s) end @@ -206,12 +210,12 @@ module Crystal size = @program.target_machine.data_layout.size_in_bits(struct_type.struct_element_types[is_struct ? 0 : 1]) offset = @program.target_machine.data_layout.offset_of_element(struct_type, 1) * 8u64 - debug_type = di_builder.create_union_type(nil, nil, current_debug_file, 1, size, size, LLVM::DIFlags::Zero, di_builder.get_or_create_type_array(element_types)) + debug_type = di_builder.create_union_type(nil, "", current_debug_file, 1, size, size, LLVM::DIFlags::Zero, element_types) unless is_struct element_types.clear element_types << di_builder.create_member_type(nil, "type_id", nil, 1, 32, 32, 0, LLVM::DIFlags::Zero, get_debug_type(@program.uint32)) element_types << di_builder.create_member_type(nil, "union", nil, 1, size, size, offset, LLVM::DIFlags::Zero, debug_type) - debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, struct_type_size, struct_type_size, LLVM::DIFlags::Zero, nil, di_builder.get_or_create_type_array(element_types)) + debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, struct_type_size, struct_type_size, LLVM::DIFlags::Zero, nil, element_types) end di_builder.replace_temporary(tmp_debug_type, debug_type) debug_type @@ -234,7 +238,7 @@ module Crystal end size = @program.target_machine.data_layout.size_in_bits(struct_type) - debug_type = di_builder.create_union_type(nil, original_type.to_s, current_debug_file, 1, size, size, LLVM::DIFlags::Zero, di_builder.get_or_create_type_array(element_types)) + debug_type = di_builder.create_union_type(nil, original_type.to_s, current_debug_file, 1, size, size, LLVM::DIFlags::Zero, element_types) di_builder.replace_temporary(tmp_debug_type, debug_type) debug_type end @@ -274,7 +278,7 @@ module Crystal end size = @program.target_machine.data_layout.size_in_bits(struct_type) - debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, size, size, LLVM::DIFlags::Zero, nil, di_builder.get_or_create_type_array(element_types)) + debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, size, size, LLVM::DIFlags::Zero, nil, element_types) unless type.struct? debug_type = di_builder.create_pointer_type(debug_type, 8u64 * llvm_typer.pointer_size, 8u64 * llvm_typer.pointer_size, original_type.to_s) end @@ -302,7 +306,7 @@ module Crystal end size = @program.target_machine.data_layout.size_in_bits(struct_type) - debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, size, size, LLVM::DIFlags::Zero, nil, di_builder.get_or_create_type_array(element_types)) + debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, size, size, LLVM::DIFlags::Zero, nil, element_types) unless type.struct? debug_type = di_builder.create_pointer_type(debug_type, 8u64 * llvm_typer.pointer_size, 8u64 * llvm_typer.pointer_size, original_type.to_s) end @@ -370,7 +374,7 @@ module Crystal old_debug_location = @current_debug_location set_current_debug_location location if builder.current_debug_location != llvm_nil && (ptr = alloca) - di_builder.insert_declare_at_end(ptr, var, expr, builder.current_debug_location, block) + di_builder.insert_declare_at_end(ptr, var, expr, builder.current_debug_location_metadata, block) set_current_debug_location old_debug_location true else diff --git a/src/llvm/di_builder.cr b/src/llvm/di_builder.cr index 451594865689..1fca2301f1dd 100644 --- a/src/llvm/di_builder.cr +++ b/src/llvm/di_builder.cr @@ -1,122 +1,200 @@ require "./lib_llvm" struct LLVM::DIBuilder - def initialize(@llvm_module : Module) - @unwrap = LibLLVMExt.create_di_builder(llvm_module) + private DW_TAG_structure_type = 19 + + private def initialize(@unwrap : LibLLVM::DIBuilderRef, @llvm_module : Module) + end + + def self.new(mod : LLVM::Module) + new(LibLLVM.create_di_builder(mod), mod) + end + + def dispose + LibLLVM.dispose_di_builder(self) + end + + def create_compile_unit(lang : DwarfSourceLanguage, file, dir, producer, optimized, flags, runtime_version) + file = create_file(file, dir) + {% if LibLLVM::IS_LT_110 %} + LibLLVM.di_builder_create_compile_unit(self, + lang, file, producer, producer.bytesize, optimized ? 1 : 0, flags, flags.bytesize, runtime_version, + split_name: nil, split_name_len: 0, kind: LibLLVM::DWARFEmissionKind::Full, dwo_id: 0, + split_debug_inlining: 1, debug_info_for_profiling: 0, + ) + {% else %} + LibLLVM.di_builder_create_compile_unit(self, + lang, file, producer, producer.bytesize, optimized ? 1 : 0, flags, flags.bytesize, runtime_version, + split_name: nil, split_name_len: 0, kind: LibLLVM::DWARFEmissionKind::Full, dwo_id: 0, + split_debug_inlining: 1, debug_info_for_profiling: 0, sys_root: nil, sys_root_len: 0, sdk: nil, sdk_len: 0, + ) + {% end %} end - def create_compile_unit(lang, file, dir, producer, optimized, flags, runtime_version) - LibLLVMExt.di_builder_create_compile_unit(self, lang, file, dir, producer, optimized ? 1 : 0, flags, runtime_version) + @[Deprecated("Pass an `LLVM::DwarfSourceLanguage` for `lang` instead")] + def create_compile_unit(lang cpp_lang_code, file, dir, producer, optimized, flags, runtime_version) + # map the c++ values from `llvm::dwarf::SourceLanguage` to the c values from `LLVMDWARFSourceLanguage` + c_lang_code = + case cpp_lang_code + when 0x8001; DwarfSourceLanguage::Mips_Assembler + when 0x8e57; DwarfSourceLanguage::GOOGLE_RenderScript + when 0xb000; DwarfSourceLanguage::BORLAND_Delphi + else DwarfSourceLanguage.new(lang - 1) + end + + create_compile_unit(c_lang_code, file, dir, producer, optimized, flags, runtime_version) end def create_basic_type(name, size_in_bits, align_in_bits, encoding) - LibLLVMExt.di_builder_create_basic_type(self, name, size_in_bits, align_in_bits, encoding.value) + LibLLVM.di_builder_create_basic_type(self, name, name.bytesize, size_in_bits, encoding.value, DIFlags::Zero) end def get_or_create_type_array(types : Array(LibLLVM::MetadataRef)) - LibLLVMExt.di_builder_get_or_create_type_array(self, types, types.size) + LibLLVM.di_builder_get_or_create_type_array(self, types, types.size) end def create_subroutine_type(file, parameter_types) - LibLLVMExt.di_builder_create_subroutine_type(self, file, parameter_types) + LibLLVM.di_builder_create_subroutine_type(self, file, parameter_types, parameter_types.size, DIFlags::Zero) end def create_file(file, dir) - LibLLVMExt.di_builder_create_file(self, file, dir) + LibLLVM.di_builder_create_file(self, file, file.bytesize, dir, dir.bytesize) end def create_lexical_block(scope, file_scope, line, column) - LibLLVMExt.di_builder_create_lexical_block(self, scope, file_scope, line, column) + LibLLVM.di_builder_create_lexical_block(self, scope, file_scope, line, column) end def create_lexical_block_file(scope, file_scope, discriminator = 0) - LibLLVMExt.di_builder_create_lexical_block_file(self, scope, file_scope, discriminator) + LibLLVM.di_builder_create_lexical_block_file(self, scope, file_scope, discriminator) end def create_function(scope, name, linkage_name, file, line, composite_type, is_local_to_unit, is_definition, scope_line, flags, is_optimized, func) - LibLLVMExt.di_builder_create_function(self, scope, name, linkage_name, file, line, composite_type, - is_local_to_unit, is_definition, scope_line, flags, is_optimized, func) + sub = LibLLVM.di_builder_create_function(self, scope, name, name.bytesize, + linkage_name, linkage_name.bytesize, file, line, composite_type, is_local_to_unit ? 1 : 0, + is_definition ? 1 : 0, scope_line, flags, is_optimized ? 1 : 0) + LibLLVM.set_subprogram(func, sub) + sub end def create_auto_variable(scope, name, file, line, type, align_in_bits, flags = DIFlags::Zero) - LibLLVMExt.di_builder_create_auto_variable(self, scope, name, file, line, type, 1, flags, align_in_bits) + LibLLVM.di_builder_create_auto_variable(self, scope, name, name.bytesize, file, line, type, 1, flags, align_in_bits) end def create_parameter_variable(scope, name, argno, file, line, type, flags = DIFlags::Zero) - LibLLVMExt.di_builder_create_parameter_variable(self, scope, name, argno, file, line, type, 1, flags) + LibLLVM.di_builder_create_parameter_variable(self, scope, name, name.bytesize, argno, file, line, type, 1, flags) end def create_expression(addr, length) - LibLLVMExt.di_builder_create_expression(self, addr, length) + LibLLVM.di_builder_create_expression(self, addr, length) end - def insert_declare_at_end(storage, var_info, expr, dl, block) - LibLLVMExt.di_builder_insert_declare_at_end(self, storage, var_info, expr, dl, block) + def insert_declare_at_end(storage, var_info, expr, dl : LibLLVM::MetadataRef, block) + LibLLVM.di_builder_insert_declare_at_end(self, storage, var_info, expr, dl, block) end def get_or_create_array(elements : Array(LibLLVM::MetadataRef)) - LibLLVMExt.di_builder_get_or_create_array(self, elements, elements.size) + LibLLVM.di_builder_get_or_create_array(self, elements, elements.size) end def create_enumerator(name, value) - LibLLVMExt.di_builder_create_enumerator(self, name, value) + {% if LibLLVM::IS_LT_90 %} + LibLLVMExt.di_builder_create_enumerator(self, name, value) + {% else %} + LibLLVM.di_builder_create_enumerator(self, name, name.bytesize, value, 0) + {% end %} end def create_enumeration_type(scope, name, file, line_number, size_in_bits, align_in_bits, elements, underlying_type) - LibLLVMExt.di_builder_create_enumeration_type(self, scope, name, file, line_number, size_in_bits, - align_in_bits, elements, underlying_type) + LibLLVM.di_builder_create_enumeration_type(self, scope, name, name.bytesize, file, line_number, + size_in_bits, align_in_bits, elements, elements.size, underlying_type) end def create_struct_type(scope, name, file, line, size_in_bits, align_in_bits, flags, derived_from, element_types) - LibLLVMExt.di_builder_create_struct_type(self, scope, name, file, line, size_in_bits, align_in_bits, - flags, derived_from, element_types) + LibLLVM.di_builder_create_struct_type(self, scope, name, name.bytesize, file, line, + size_in_bits, align_in_bits, flags, derived_from, element_types, element_types.size, 0, nil, nil, 0) end def create_union_type(scope, name, file, line, size_in_bits, align_in_bits, flags, element_types) - LibLLVMExt.di_builder_create_union_type(self, scope, name, file, line, size_in_bits, align_in_bits, - flags, element_types) + LibLLVM.di_builder_create_union_type(self, scope, name, name.bytesize, file, line, + size_in_bits, align_in_bits, flags, element_types, element_types.size, 0, nil, 0) end def create_array_type(size_in_bits, align_in_bits, type, subs) - elements = self.get_or_create_array(subs) - LibLLVMExt.di_builder_create_array_type(self, size_in_bits, align_in_bits, type, elements) + LibLLVM.di_builder_create_array_type(self, size_in_bits, align_in_bits, type, subs, subs.size) end def create_member_type(scope, name, file, line, size_in_bits, align_in_bits, offset_in_bits, flags, ty) - LibLLVMExt.di_builder_create_member_type(self, scope, name, file, line, size_in_bits, align_in_bits, + LibLLVM.di_builder_create_member_type(self, scope, name, name.bytesize, file, line, size_in_bits, align_in_bits, offset_in_bits, flags, ty) end def create_pointer_type(pointee, size_in_bits, align_in_bits, name) - LibLLVMExt.di_builder_create_pointer_type(self, pointee, size_in_bits, align_in_bits, name) + LibLLVM.di_builder_create_pointer_type(self, pointee, size_in_bits, align_in_bits, 0, name, name.bytesize) end def create_replaceable_composite_type(scope, name, file, line) - LibLLVMExt.di_builder_create_replaceable_composite_type(self, scope, name, file, line) + LibLLVM.di_builder_create_replaceable_composite_type(self, DW_TAG_structure_type, name, name.bytesize, + scope, file, line, 0, 0, 0, DIFlags::FwdDecl, nil, 0) end def replace_temporary(from, to) - LibLLVMExt.di_builder_replace_temporary(self, from, to) + LibLLVM.metadata_replace_all_uses_with(from, to) end def create_unspecified_type(name : String) - LibLLVMExt.di_builder_create_unspecified_type(self, name, name.size) + LibLLVM.di_builder_create_unspecified_type(self, name, name.bytesize) end def get_or_create_array_subrange(lo, count) - LibLLVMExt.di_builder_get_or_create_array_subrange(self, lo, count) - end - - def create_reference_type(debug_type) - LibLLVM.di_builder_create_reference_type(self, 16, debug_type) # 16 is the code for DW_TAG_reference_type + LibLLVM.di_builder_get_or_create_subrange(self, lo, count) end def end - LibLLVMExt.di_builder_finalize(self) + LibLLVM.di_builder_finalize(self) end def to_unsafe @unwrap end + + @[Deprecated("Use a `LibLLVM::MetadataRef` for `dl` instead")] + def insert_declare_at_end(storage, var_info, expr, dl : LibLLVM::ValueRef | LLVM::Value, block) + dl = dl.to_unsafe unless dl.is_a?(LibLLVM::ValueRef) + insert_declare_at_end(storage, var_info, expr, LibLLVM.value_as_metadata(dl), block) + end + + @[Deprecated("Pass an array for `parameter_types` directly")] + def create_subroutine_type(file, parameter_types : LibLLVM::MetadataRef) + create_subroutine_type(file, extract_metadata_array(parameter_types)) + end + + @[Deprecated("Pass an array for `elements` directly")] + def create_enumeration_type(scope, name, file, line_number, size_in_bits, align_in_bits, elements : LibLLVM::MetadataRef, underlying_type) + create_enumeration_type(scope, name, file, line_number, size_in_bits, align_in_bits, extract_metadata_array(elements), underlying_type) + end + + @[Deprecated("Pass an array for `element_types` directly")] + def create_struct_type(scope, name, file, line, size_in_bits, align_in_bits, flags, derived_from, element_types : LibLLVM::MetadataRef) + create_struct_type(scope, name, file, line, size_in_bits, align_in_bits, flags, derived_from, extract_metadata_array(element_types)) + end + + @[Deprecated("Pass an array for `element_types` directly")] + def create_union_type(scope, name, file, line, size_in_bits, align_in_bits, flags, element_types : LibLLVM::MetadataRef) + create_union_type(scope, name, file, line, size_in_bits, align_in_bits, flags, extract_metadata_array(element_types)) + end + + @[Deprecated("Pass an array for `subs` directly")] + def create_array_type(size_in_bits, align_in_bits, type, subs : LibLLVM::MetadataRef) + create_array_type(size_in_bits, align_in_bits, type, extract_metadata_array(subs)) + end + + private def extract_metadata_array(metadata : LibLLVM::MetadataRef) + metadata_as_value = LibLLVM.metadata_as_value(@llvm_module.context, metadata) + operand_count = LibLLVM.get_md_node_num_operands(metadata_as_value).to_i + operands = Pointer(LibLLVM::ValueRef).malloc(operand_count) + LibLLVM.get_md_node_operands(metadata_as_value, operands) + Slice.new(operand_count) { |i| LibLLVM.value_as_metadata(operands[i]) } + end end diff --git a/src/llvm/enums.cr b/src/llvm/enums.cr index 9c868fbdeb86..b8e06fb46f89 100644 --- a/src/llvm/enums.cr +++ b/src/llvm/enums.cr @@ -322,14 +322,87 @@ module LLVM HiUser = 0xff end + enum DwarfSourceLanguage + C89 + C + Ada83 + C_plus_plus + Cobol74 + Cobol85 + Fortran77 + Fortran90 + Pascal83 + Modula2 + + # New in DWARF v3: + + Java + C99 + Ada95 + Fortran95 + PLI + ObjC + ObjC_plus_plus + UPC + D + + # New in DWARF v4: + + Python + + # New in DWARF v5: + + OpenCL + Go + Modula3 + Haskell + C_plus_plus_03 + C_plus_plus_11 + OCaml + Rust + C11 + Swift + Julia + Dylan + C_plus_plus_14 + Fortran03 + Fortran08 + RenderScript + BLISS + + {% unless LibLLVM::IS_LT_160 %} + Kotlin + Zig + Crystal + C_plus_plus_17 + C_plus_plus_20 + C17 + Fortran18 + Ada2005 + Ada2012 + {% end %} + + # Vendor extensions: + + Mips_Assembler + GOOGLE_RenderScript + BORLAND_Delphi + end + enum DIFlags : UInt32 - Zero = 0 - Private = 1 - Protected = 2 - Public = 3 - FwdDecl = 1 << 2 - AppleBlock = 1 << 3 - BlockByrefStruct = 1 << 4 + Zero = 0 + Private = 1 + Protected = 2 + Public = 3 + FwdDecl = 1 << 2 + AppleBlock = 1 << 3 + + {% if LibLLVM::IS_LT_100 %} + BlockByrefStruct = 1 << 4 + {% else %} + ReservedBit4 = 1 << 4 + {% end %} + Virtual = 1 << 5 Artificial = 1 << 6 Explicit = 1 << 7 @@ -347,14 +420,24 @@ module LLVM IntroducedVirtual = 1 << 18 BitField = 1 << 19 NoReturn = 1 << 20 - MainSubprogram = 1 << 21 + + {% if LibLLVM::IS_LT_90 %} + MainSubprogram = 1 << 21 + {% end %} + PassByValue = 1 << 22 TypePassByReference = 1 << 23 EnumClass = 1 << 24 Thunk = 1 << 25 - NonTrivial = 1 << 26 - BigEndian = 1 << 27 - LittleEndian = 1 << 28 + + {% if LibLLVM::IS_LT_90 %} + Trivial = 1 << 26 + {% else %} + NonTrivial = 1 << 26 + {% end %} + + BigEndian = 1 << 27 + LittleEndian = 1 << 28 end struct Value diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc index 77054025dad9..184f0b3d6e90 100644 --- a/src/llvm/ext/llvm_ext.cc +++ b/src/llvm/ext/llvm_ext.cc @@ -1,10 +1,6 @@ #include #include -#include -#include #include -#include -#include #include #include #include @@ -25,217 +21,20 @@ using namespace llvm; #include #include -typedef DIBuilder *DIBuilderRef; -#define DIArray DINodeArray template T *unwrapDIptr(LLVMMetadataRef v) { return (T *)(v ? unwrap(v) : NULL); } -#define DIDescriptor DIScope -#define unwrapDI unwrapDIptr - extern "C" { -LLVMDIBuilderRef LLVMExtNewDIBuilder(LLVMModuleRef mref) { - Module *m = unwrap(mref); - return wrap(new DIBuilder(*m)); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateFile( - DIBuilderRef Dref, const char *File, const char *Dir) { - return wrap(Dref->createFile(File, Dir)); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateCompileUnit( - DIBuilderRef Dref, unsigned Lang, const char *File, const char *Dir, - const char *Producer, int Optimized, const char *Flags, - unsigned RuntimeVersion) { - DIFile *F = Dref->createFile(File, Dir); - return wrap(Dref->createCompileUnit(Lang, F, Producer, Optimized, - Flags, RuntimeVersion)); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateFunction( - DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, - const char *LinkageName, LLVMMetadataRef File, unsigned Line, - LLVMMetadataRef CompositeType, bool IsLocalToUnit, bool IsDefinition, - unsigned ScopeLine, - DINode::DIFlags Flags, - bool IsOptimized, - LLVMValueRef Func) { - DISubprogram *Sub = Dref->createFunction( - unwrapDI(Scope), StringRef(Name), StringRef(LinkageName), unwrapDI(File), Line, - unwrapDI(CompositeType), - ScopeLine, Flags, DISubprogram::toSPFlags(IsLocalToUnit, IsDefinition, IsOptimized)); - unwrap(Func)->setSubprogram(Sub); - return wrap(Sub); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateLexicalBlock( - DIBuilderRef Dref, LLVMMetadataRef Scope, LLVMMetadataRef File, - unsigned Line, unsigned Column) { - return wrap(Dref->createLexicalBlock(unwrapDI(Scope), - unwrapDI(File), Line, Column)); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateBasicType( - DIBuilderRef Dref, const char *Name, uint64_t SizeInBits, - uint64_t AlignInBits, unsigned Encoding) { - return wrap(Dref->createBasicType(Name, SizeInBits, Encoding)); -} - -LLVMMetadataRef LLVMExtDIBuilderGetOrCreateTypeArray( - DIBuilderRef Dref, LLVMMetadataRef *Data, unsigned Length) { - Metadata **DataValue = unwrap(Data); - return wrap( - Dref->getOrCreateTypeArray(ArrayRef(DataValue, Length)) - .get()); -} - -LLVMMetadataRef LLVMExtDIBuilderGetOrCreateArray( - DIBuilderRef Dref, LLVMMetadataRef *Data, unsigned Length) { - Metadata **DataValue = unwrap(Data); - return wrap( - Dref->getOrCreateArray(ArrayRef(DataValue, Length)).get()); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateSubroutineType( - DIBuilderRef Dref, LLVMMetadataRef File, LLVMMetadataRef ParameterTypes) { - DISubroutineType *CT = Dref->createSubroutineType(DITypeRefArray(unwrap(ParameterTypes))); - return wrap(CT); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateAutoVariable( - DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, - LLVMMetadataRef File, unsigned Line, LLVMMetadataRef Ty, - int AlwaysPreserve, - DINode::DIFlags Flags, - uint32_t AlignInBits) { - DILocalVariable *V = Dref->createAutoVariable( - unwrapDI(Scope), Name, unwrapDI(File), Line, - unwrapDI(Ty), AlwaysPreserve, Flags, AlignInBits); - return wrap(V); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateParameterVariable( - DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, - unsigned ArgNo, LLVMMetadataRef File, unsigned Line, - LLVMMetadataRef Ty, int AlwaysPreserve, - DINode::DIFlags Flags - ) { - DILocalVariable *V = Dref->createParameterVariable - (unwrapDI(Scope), Name, ArgNo, unwrapDI(File), Line, - unwrapDI(Ty), AlwaysPreserve, Flags); - return wrap(V); -} - -LLVMValueRef LLVMExtDIBuilderInsertDeclareAtEnd( - DIBuilderRef Dref, LLVMValueRef Storage, LLVMMetadataRef VarInfo, - LLVMMetadataRef Expr, LLVMValueRef DL, LLVMBasicBlockRef Block) { - Instruction *Instr = - Dref->insertDeclare(unwrap(Storage), unwrap(VarInfo), - unwrapDI(Expr), - DebugLoc(cast(unwrap(DL)->getMetadata())), - unwrap(Block)); - return wrap(Instr); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateExpression( - DIBuilderRef Dref, uint64_t *Addr, size_t Length) { - return wrap(Dref->createExpression(ArrayRef(Addr, Length))); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateEnumerationType( - DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, - LLVMMetadataRef File, unsigned LineNumber, uint64_t SizeInBits, - uint64_t AlignInBits, LLVMMetadataRef Elements, - LLVMMetadataRef UnderlyingType) { - DICompositeType *enumType = Dref->createEnumerationType( - unwrapDI(Scope), Name, unwrapDI(File), LineNumber, - SizeInBits, AlignInBits, DINodeArray(unwrapDI(Elements)), - unwrapDI(UnderlyingType)); - return wrap(enumType); -} - +#if LLVM_VERSION_GE(9, 0) +#else LLVMMetadataRef LLVMExtDIBuilderCreateEnumerator( - DIBuilderRef Dref, const char *Name, int64_t Value) { - DIEnumerator *e = Dref->createEnumerator(Name, Value); + LLVMDIBuilderRef Dref, const char *Name, int64_t Value) { + DIEnumerator *e = unwrap(Dref)->createEnumerator(Name, Value); return wrap(e); } - -LLVMMetadataRef LLVMExtDIBuilderCreateStructType( - DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, - LLVMMetadataRef File, unsigned Line, uint64_t SizeInBits, - uint64_t AlignInBits, - DINode::DIFlags Flags, - LLVMMetadataRef DerivedFrom, LLVMMetadataRef Elements) { - DICompositeType *CT = Dref->createStructType( - unwrapDI(Scope), Name, unwrapDI(File), Line, - SizeInBits, AlignInBits, Flags, unwrapDI(DerivedFrom), - DINodeArray(unwrapDI(Elements))); - return wrap(CT); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateUnionType( - DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, - LLVMMetadataRef File, unsigned Line, uint64_t SizeInBits, - uint64_t AlignInBits, - DINode::DIFlags Flags, - LLVMMetadataRef Elements) { - DICompositeType *CT = Dref->createUnionType( - unwrapDI(Scope), Name, unwrapDI(File), Line, - SizeInBits, AlignInBits, Flags, - DINodeArray(unwrapDI(Elements))); - return wrap(CT); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateArrayType( - DIBuilderRef Dref, uint64_t Size, uint64_t AlignInBits, - LLVMMetadataRef Type, LLVMMetadataRef Subs) { - return wrap(Dref->createArrayType(Size, AlignInBits, unwrapDI(Type), DINodeArray(unwrapDI(Subs)))); -} - - -LLVMMetadataRef LLVMExtDIBuilderCreateReplaceableCompositeType( - DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, - LLVMMetadataRef File, unsigned Line) { - DICompositeType *CT = Dref->createReplaceableCompositeType(llvm::dwarf::DW_TAG_structure_type, - Name, - unwrapDI(Scope), - unwrapDI(File), - Line); - return wrap(CT); -} - -void LLVMExtDIBuilderReplaceTemporary( - DIBuilderRef Dref, LLVMMetadataRef From, LLVMMetadataRef To) { - auto *Node = unwrap(From); - auto *Type = unwrap(To); - - llvm::TempMDNode fwd_decl(Node); - Dref->replaceTemporary(std::move(fwd_decl), Type); -} - -LLVMMetadataRef LLVMExtDIBuilderCreateMemberType( - DIBuilderRef Dref, LLVMMetadataRef Scope, const char *Name, LLVMMetadataRef File, - unsigned Line, uint64_t SizeInBits, uint64_t AlignInBits, uint64_t OffsetInBits, - DINode::DIFlags Flags, - LLVMMetadataRef Ty) { - DIDerivedType *DT = Dref->createMemberType( - unwrapDI(Scope), Name, unwrapDI(File), Line, - SizeInBits, AlignInBits, OffsetInBits, Flags, unwrapDI(Ty)); - return wrap(DT); -} - -LLVMMetadataRef LLVMExtDIBuilderCreatePointerType( - DIBuilderRef Dref, LLVMMetadataRef PointeeType, - uint64_t SizeInBits, uint64_t AlignInBits, const char *Name) { - DIDerivedType *T = Dref->createPointerType(unwrapDI(PointeeType), - SizeInBits, AlignInBits, - None, - Name); - return wrap(T); -} +#endif void LLVMExtSetCurrentDebugLocation( LLVMBuilderRef Bref, unsigned Line, unsigned Col, LLVMMetadataRef Scope, @@ -246,8 +45,8 @@ void LLVMExtSetCurrentDebugLocation( else unwrap(Bref)->SetCurrentDebugLocation( DILocation::get(unwrap(Scope)->getContext(), Line, Col, - unwrapDI(Scope), - unwrapDI(InlinedAt))); + unwrapDIptr(Scope), + unwrapDIptr(InlinedAt))); #else unwrap(Bref)->SetCurrentDebugLocation( DebugLoc::get(Line, Col, Scope ? unwrap(Scope) : nullptr, @@ -375,9 +174,4 @@ LLVMBool LLVMExtCreateMCJITCompilerForModule( return 1; } -LLVMMetadataRef LLVMExtDIBuilderGetOrCreateArraySubrange( - DIBuilderRef Dref, uint64_t Lo, - uint64_t Count) { - return wrap(Dref->getOrCreateSubrange(Lo, Count)); - } -} +} // extern "C" diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 613a8cdd4a71..e4b1f5a9efbd 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -36,6 +36,7 @@ end IS_LT_130 = {{compare_versions(LibLLVM::VERSION, "13.0.0") < 0}} IS_LT_140 = {{compare_versions(LibLLVM::VERSION, "14.0.0") < 0}} IS_LT_150 = {{compare_versions(LibLLVM::VERSION, "15.0.0") < 0}} + IS_LT_160 = {{compare_versions(LibLLVM::VERSION, "16.0.0") < 0}} end {% end %} @@ -43,6 +44,7 @@ lib LibLLVM alias Char = LibC::Char alias Int = LibC::Int alias UInt = LibC::UInt + alias SizeT = LibC::SizeT type ContextRef = Void* type ModuleRef = Void* @@ -62,6 +64,7 @@ lib LibLLVM type MemoryBufferRef = Void* type PassBuilderOptionsRef = Void* type ErrorRef = Void* + type DIBuilderRef = Void* struct JITCompilerOptions opt_level : UInt32 @@ -75,11 +78,17 @@ lib LibLLVM Intel end - # `LLVMModuleFlagBehavior` (_not_ `LLVM::Module::ModFlagBehavior`, their values disagree) + # NOTE: the following C enums usually have different values from their C++ + # counterparts (e.g. `LLVMModuleFlagBehavior` v.s. `LLVM::Module::ModFlagBehavior`) + enum ModuleFlagBehavior Warning = 1 end + enum DWARFEmissionKind + Full = 1 + end + fun add_case = LLVMAddCase(switch : ValueRef, onval : ValueRef, dest : BasicBlockRef) fun add_clause = LLVMAddClause(lpad : ValueRef, clause_val : ValueRef) fun add_function = LLVMAddFunction(module : ModuleRef, name : UInt8*, type : TypeRef) : ValueRef @@ -180,6 +189,9 @@ lib LibLLVM fun generic_value_to_pointer = LLVMGenericValueToPointer(value : GenericValueRef) : Void* fun get_basic_block_name = LLVMGetBasicBlockName(basic_block : BasicBlockRef) : Char* fun get_current_debug_location = LLVMGetCurrentDebugLocation(builder : BuilderRef) : ValueRef + {% unless LibLLVM::IS_LT_90 %} + fun get_current_debug_location2 = LLVMGetCurrentDebugLocation2(builder : BuilderRef) : MetadataRef + {% end %} fun get_first_instruction = LLVMGetFirstInstruction(block : BasicBlockRef) : ValueRef fun get_first_target = LLVMGetFirstTarget : TargetRef fun get_first_basic_block = LLVMGetFirstBasicBlock(fn : ValueRef) : BasicBlockRef @@ -379,6 +391,7 @@ lib LibLLVM fun md_string_in_context = LLVMMDStringInContext(c : ContextRef, str : UInt8*, length : Int32) : ValueRef fun value_as_metadata = LLVMValueAsMetadata(val : ValueRef) : MetadataRef + fun metadata_as_value = LLVMMetadataAsValue(c : ContextRef, md : MetadataRef) : ValueRef fun append_basic_block_in_context = LLVMAppendBasicBlockInContext(ctx : ContextRef, fn : ValueRef, name : UInt8*) : BasicBlockRef fun create_builder_in_context = LLVMCreateBuilderInContext(c : ContextRef) : BuilderRef @@ -394,6 +407,9 @@ lib LibLLVM fun get_num_arg_operands = LLVMGetNumArgOperands(instr : ValueRef) : UInt fun get_arg_operand = LLVMGetArgOperand(val : ValueRef, index : UInt) : ValueRef + fun get_md_node_num_operands = LLVMGetMDNodeNumOperands(v : ValueRef) : UInt + fun get_md_node_operands = LLVMGetMDNodeOperands(v : ValueRef, dest : ValueRef*) + fun set_instr_param_alignment = LLVMSetInstrParamAlignment(instr : ValueRef, index : UInt, align : UInt) fun set_param_alignment = LLVMSetParamAlignment(arg : ValueRef, align : UInt) @@ -403,4 +419,121 @@ lib LibLLVM fun create_pass_builder_options = LLVMCreatePassBuilderOptions : PassBuilderOptionsRef fun dispose_pass_builder_options = LLVMDisposePassBuilderOptions(options : PassBuilderOptionsRef) {% end %} + + fun create_di_builder = LLVMCreateDIBuilder(m : ModuleRef) : DIBuilderRef + fun dispose_di_builder = LLVMDisposeDIBuilder(builder : DIBuilderRef) + fun di_builder_finalize = LLVMDIBuilderFinalize(builder : DIBuilderRef) + + {% if LibLLVM::IS_LT_110 %} + fun di_builder_create_compile_unit = LLVMDIBuilderCreateCompileUnit( + builder : DIBuilderRef, lang : LLVM::DwarfSourceLanguage, file_ref : MetadataRef, producer : Char*, + producer_len : SizeT, is_optimized : Int, flags : Char*, flags_len : SizeT, runtime_ver : UInt, + split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt, + split_debug_inlining : Int, debug_info_for_profiling : Int + ) : MetadataRef + {% else %} + fun di_builder_create_compile_unit = LLVMDIBuilderCreateCompileUnit( + builder : DIBuilderRef, lang : LLVM::DwarfSourceLanguage, file_ref : MetadataRef, producer : Char*, + producer_len : SizeT, is_optimized : Int, flags : Char*, flags_len : SizeT, runtime_ver : UInt, + split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt, + split_debug_inlining : Int, debug_info_for_profiling : Int, sys_root : Char*, + sys_root_len : SizeT, sdk : Char*, sdk_len : SizeT + ) : MetadataRef + {% end %} + + fun di_builder_create_file = LLVMDIBuilderCreateFile( + builder : DIBuilderRef, filename : Char*, filename_len : SizeT, + directory : Char*, directory_len : SizeT + ) : MetadataRef + + fun di_builder_create_function = LLVMDIBuilderCreateFunction( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, + linkage_name : Char*, linkage_name_len : SizeT, file : MetadataRef, line_no : UInt, + ty : MetadataRef, is_local_to_unit : Int, is_definition : Int, scope_line : UInt, + flags : LLVM::DIFlags, is_optimized : Int + ) : MetadataRef + + fun di_builder_create_lexical_block = LLVMDIBuilderCreateLexicalBlock( + builder : DIBuilderRef, scope : MetadataRef, file : MetadataRef, line : UInt, column : UInt + ) : MetadataRef + fun di_builder_create_lexical_block_file = LLVMDIBuilderCreateLexicalBlockFile( + builder : DIBuilderRef, scope : MetadataRef, file_scope : MetadataRef, discriminator : UInt + ) : MetadataRef + + {% unless LibLLVM::IS_LT_90 %} + fun di_builder_create_enumerator = LLVMDIBuilderCreateEnumerator( + builder : DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : Int + ) : MetadataRef + {% end %} + + fun di_builder_create_subroutine_type = LLVMDIBuilderCreateSubroutineType( + builder : DIBuilderRef, file : MetadataRef, parameter_types : MetadataRef*, + num_parameter_types : UInt, flags : LLVM::DIFlags + ) : MetadataRef + fun di_builder_create_enumeration_type = LLVMDIBuilderCreateEnumerationType( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, + line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, + elements : MetadataRef*, num_elements : UInt, class_ty : MetadataRef + ) : MetadataRef + fun di_builder_create_union_type = LLVMDIBuilderCreateUnionType( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, + line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags, + elements : MetadataRef*, num_elements : UInt, run_time_lang : UInt, unique_id : Char*, unique_id_len : SizeT + ) : MetadataRef + fun di_builder_create_array_type = LLVMDIBuilderCreateArrayType( + builder : DIBuilderRef, size : UInt64, align_in_bits : UInt32, + ty : MetadataRef, subscripts : MetadataRef*, num_subscripts : UInt + ) : MetadataRef + fun di_builder_create_unspecified_type = LLVMDIBuilderCreateUnspecifiedType(builder : DIBuilderRef, name : Char*, name_len : SizeT) : MetadataRef + fun di_builder_create_basic_type = LLVMDIBuilderCreateBasicType( + builder : DIBuilderRef, name : Char*, name_len : SizeT, size_in_bits : UInt64, + encoding : UInt, flags : LLVM::DIFlags + ) : MetadataRef + fun di_builder_create_pointer_type = LLVMDIBuilderCreatePointerType( + builder : DIBuilderRef, pointee_ty : MetadataRef, size_in_bits : UInt64, align_in_bits : UInt32, + address_space : UInt, name : Char*, name_len : SizeT + ) : MetadataRef + fun di_builder_create_struct_type = LLVMDIBuilderCreateStructType( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, + line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags, + derived_from : MetadataRef, elements : MetadataRef*, num_elements : UInt, + run_time_lang : UInt, v_table_holder : MetadataRef, unique_id : Char*, unique_id_len : SizeT + ) : MetadataRef + fun di_builder_create_member_type = LLVMDIBuilderCreateMemberType( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, + line_no : UInt, size_in_bits : UInt64, align_in_bits : UInt32, offset_in_bits : UInt64, + flags : LLVM::DIFlags, ty : MetadataRef + ) : MetadataRef + fun di_builder_create_replaceable_composite_type = LLVMDIBuilderCreateReplaceableCompositeType( + builder : DIBuilderRef, tag : UInt, name : Char*, name_len : SizeT, scope : MetadataRef, + file : MetadataRef, line : UInt, runtime_lang : UInt, size_in_bits : UInt64, align_in_bits : UInt32, + flags : LLVM::DIFlags, unique_identifier : Char*, unique_identifier_len : SizeT + ) : MetadataRef + + fun di_builder_get_or_create_subrange = LLVMDIBuilderGetOrCreateSubrange(builder : DIBuilderRef, lo : Int64, count : Int64) : MetadataRef + fun di_builder_get_or_create_array = LLVMDIBuilderGetOrCreateArray(builder : DIBuilderRef, data : MetadataRef*, length : SizeT) : MetadataRef + fun di_builder_get_or_create_type_array = LLVMDIBuilderGetOrCreateTypeArray(builder : DIBuilderRef, types : MetadataRef*, length : SizeT) : MetadataRef + + {% if LibLLVM::IS_LT_140 %} + fun di_builder_create_expression = LLVMDIBuilderCreateExpression(builder : DIBuilderRef, addr : Int64*, length : SizeT) : MetadataRef + {% else %} + fun di_builder_create_expression = LLVMDIBuilderCreateExpression(builder : DIBuilderRef, addr : UInt64*, length : SizeT) : MetadataRef + {% end %} + + fun di_builder_insert_declare_at_end = LLVMDIBuilderInsertDeclareAtEnd( + builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef, + expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef + ) : ValueRef + + fun di_builder_create_auto_variable = LLVMDIBuilderCreateAutoVariable( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, + line_no : UInt, ty : MetadataRef, always_preserve : Int, flags : LLVM::DIFlags, align_in_bits : UInt32 + ) : MetadataRef + fun di_builder_create_parameter_variable = LLVMDIBuilderCreateParameterVariable( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, arg_no : UInt, + file : MetadataRef, line_no : UInt, ty : MetadataRef, always_preserve : Int, flags : LLVM::DIFlags + ) : MetadataRef + + fun set_subprogram = LLVMSetSubprogram(func : ValueRef, sp : MetadataRef) + fun metadata_replace_all_uses_with = LLVMMetadataReplaceAllUsesWith(target_metadata : MetadataRef, replacement : MetadataRef) end diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr index c99656f5e81c..b1f30763ab9e 100644 --- a/src/llvm/lib_llvm_ext.cr +++ b/src/llvm/lib_llvm_ext.cr @@ -8,112 +8,12 @@ lib LibLLVMExt alias Char = LibC::Char alias Int = LibC::Int alias UInt = LibC::UInt - alias SizeT = LibC::SizeT - type DIBuilder = Void* type OperandBundleDefRef = Void* - fun create_di_builder = LLVMExtNewDIBuilder(LibLLVM::ModuleRef) : DIBuilder - fun di_builder_finalize = LLVMDIBuilderFinalize(DIBuilder) - - fun di_builder_create_function = LLVMExtDIBuilderCreateFunction( - builder : DIBuilder, scope : LibLLVM::MetadataRef, name : Char*, - linkage_name : Char*, file : LibLLVM::MetadataRef, line : UInt, - composite_type : LibLLVM::MetadataRef, is_local_to_unit : Bool, is_definition : Bool, - scope_line : UInt, flags : LLVM::DIFlags, is_optimized : Bool, func : LibLLVM::ValueRef - ) : LibLLVM::MetadataRef - - fun di_builder_create_file = LLVMExtDIBuilderCreateFile(builder : DIBuilder, file : Char*, dir : Char*) : LibLLVM::MetadataRef - fun di_builder_create_compile_unit = LLVMExtDIBuilderCreateCompileUnit(builder : DIBuilder, - lang : UInt, file : Char*, - dir : Char*, - producer : Char*, - optimized : Int, flags : Char*, - runtime_version : UInt) : LibLLVM::MetadataRef - fun di_builder_create_lexical_block = LLVMExtDIBuilderCreateLexicalBlock(builder : DIBuilder, - scope : LibLLVM::MetadataRef, - file : LibLLVM::MetadataRef, - line : Int, - column : Int) : LibLLVM::MetadataRef - - fun di_builder_create_basic_type = LLVMExtDIBuilderCreateBasicType(builder : DIBuilder, - name : Char*, - size_in_bits : UInt64, - align_in_bits : UInt64, - encoding : UInt) : LibLLVM::MetadataRef - - fun di_builder_create_auto_variable = LLVMExtDIBuilderCreateAutoVariable(builder : DIBuilder, - scope : LibLLVM::MetadataRef, - name : Char*, - file : LibLLVM::MetadataRef, line : UInt, - type : LibLLVM::MetadataRef, - always_preserve : Int, - flags : LLVM::DIFlags, - align_in_bits : UInt32) : LibLLVM::MetadataRef - - fun di_builder_create_parameter_variable = LLVMExtDIBuilderCreateParameterVariable(builder : DIBuilder, - scope : LibLLVM::MetadataRef, - name : Char*, arg_no : UInt, - file : LibLLVM::MetadataRef, line : UInt, type : LibLLVM::MetadataRef, - always_preserve : Int, flags : LLVM::DIFlags) : LibLLVM::MetadataRef - - fun di_builder_insert_declare_at_end = LLVMExtDIBuilderInsertDeclareAtEnd(builder : DIBuilder, - storage : LibLLVM::ValueRef, - var_info : LibLLVM::MetadataRef, - expr : LibLLVM::MetadataRef, - dl : LibLLVM::ValueRef, - block : LibLLVM::BasicBlockRef) : LibLLVM::ValueRef - - fun di_builder_create_expression = LLVMExtDIBuilderCreateExpression(builder : DIBuilder, - addr : UInt64*, length : SizeT) : LibLLVM::MetadataRef - - fun di_builder_get_or_create_array = LLVMExtDIBuilderGetOrCreateArray(builder : DIBuilder, data : LibLLVM::MetadataRef*, length : SizeT) : LibLLVM::MetadataRef - fun di_builder_create_enumerator = LLVMExtDIBuilderCreateEnumerator(builder : DIBuilder, name : Char*, value : Int64) : LibLLVM::MetadataRef - fun di_builder_create_enumeration_type = LLVMExtDIBuilderCreateEnumerationType(builder : DIBuilder, - scope : LibLLVM::MetadataRef, name : Char*, file : LibLLVM::MetadataRef, line_number : UInt, - size_in_bits : UInt64, align_in_bits : UInt64, elements : LibLLVM::MetadataRef, underlying_type : LibLLVM::MetadataRef) : LibLLVM::MetadataRef - - fun di_builder_get_or_create_type_array = LLVMExtDIBuilderGetOrCreateTypeArray(builder : DIBuilder, data : LibLLVM::MetadataRef*, length : SizeT) : LibLLVM::MetadataRef - fun di_builder_create_subroutine_type = LLVMExtDIBuilderCreateSubroutineType(builder : DIBuilder, file : LibLLVM::MetadataRef, parameter_types : LibLLVM::MetadataRef) : LibLLVM::MetadataRef - - fun di_builder_create_struct_type = LLVMExtDIBuilderCreateStructType(builder : DIBuilder, - scope : LibLLVM::MetadataRef, name : Char*, file : LibLLVM::MetadataRef, line : UInt, size_in_bits : UInt64, - align_in_bits : UInt64, flags : LLVM::DIFlags, derived_from : LibLLVM::MetadataRef, element_types : LibLLVM::MetadataRef) : LibLLVM::MetadataRef - - fun di_builder_create_union_type = LLVMExtDIBuilderCreateUnionType(builder : DIBuilder, - scope : LibLLVM::MetadataRef, name : Char*, file : LibLLVM::MetadataRef, line : UInt, size_in_bits : UInt64, - align_in_bits : UInt64, flags : LLVM::DIFlags, element_types : LibLLVM::MetadataRef) : LibLLVM::MetadataRef - - fun di_builder_create_array_type = LLVMExtDIBuilderCreateArrayType(builder : DIBuilder, size : UInt64, - alignInBits : UInt64, ty : LibLLVM::MetadataRef, - subscripts : LibLLVM::MetadataRef) : LibLLVM::MetadataRef - - fun di_builder_create_member_type = LLVMExtDIBuilderCreateMemberType(builder : DIBuilder, - scope : LibLLVM::MetadataRef, name : Char*, file : LibLLVM::MetadataRef, line : UInt, size_in_bits : UInt64, - align_in_bits : UInt64, offset_in_bits : UInt64, flags : LLVM::DIFlags, ty : LibLLVM::MetadataRef) : LibLLVM::MetadataRef - - fun di_builder_create_pointer_type = LLVMExtDIBuilderCreatePointerType(builder : DIBuilder, - pointee_type : LibLLVM::MetadataRef, - size_in_bits : UInt64, - align_in_bits : UInt64, - name : Char*) : LibLLVM::MetadataRef - - fun di_builder_create_replaceable_composite_type = LLVMExtDIBuilderCreateReplaceableCompositeType(builder : DIBuilder, - scope : LibLLVM::MetadataRef, - name : Char*, - file : LibLLVM::MetadataRef, - line : UInt) : LibLLVM::MetadataRef - - fun di_builder_create_unspecified_type = LLVMDIBuilderCreateUnspecifiedType(builder : LibLLVMExt::DIBuilder, - name : Void*, - size : LibC::SizeT) : LibLLVM::MetadataRef - - fun di_builder_create_lexical_block_file = LLVMDIBuilderCreateLexicalBlockFile(builder : LibLLVMExt::DIBuilder, - scope : LibLLVM::MetadataRef, - file_scope : LibLLVM::MetadataRef, - discriminator : UInt32) : LibLLVM::MetadataRef - - fun di_builder_replace_temporary = LLVMExtDIBuilderReplaceTemporary(builder : DIBuilder, from : LibLLVM::MetadataRef, to : LibLLVM::MetadataRef) + {% if LibLLVM::IS_LT_90 %} + fun di_builder_create_enumerator = LLVMExtDIBuilderCreateEnumerator(builder : LibLLVM::DIBuilderRef, name : Char*, value : Int64) : LibLLVM::MetadataRef + {% end %} fun set_current_debug_location = LLVMExtSetCurrentDebugLocation(LibLLVM::BuilderRef, Int, Int, LibLLVM::MetadataRef, LibLLVM::MetadataRef) @@ -134,8 +34,6 @@ lib LibLLVMExt fun write_bitcode_with_summary_to_file = LLVMExtWriteBitcodeWithSummaryToFile(module : LibLLVM::ModuleRef, path : UInt8*) : Void - fun di_builder_get_or_create_array_subrange = LLVMExtDIBuilderGetOrCreateArraySubrange(builder : DIBuilder, lo : UInt64, count : UInt64) : LibLLVM::MetadataRef - fun target_machine_enable_global_isel = LLVMExtTargetMachineEnableGlobalIsel(machine : LibLLVM::TargetMachineRef, enable : Bool) fun create_mc_jit_compiler_for_module = LLVMExtCreateMCJITCompilerForModule(jit : LibLLVM::ExecutionEngineRef*, m : LibLLVM::ModuleRef, options : LibLLVM::JITCompilerOptions*, options_length : UInt32, enable_global_isel : Bool, error : UInt8**) : Int32 end From 9714499eeb52aa63ff816d8aa6e826f7e6273429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 11 May 2023 10:21:00 +0200 Subject: [PATCH 0522/1551] Update previous Crystal release - 1.8.2 (#13450) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 6 +++--- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b8caa7d14367..303c21992438 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.8.2/crystal-1.8.2-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 067687c969ad..203cdb8933ed 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.8.1-build + image: crystallang/crystal:1.8.2-build name: "Test Interpreter" steps: - uses: actions/checkout@v3 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.8.1-build + image: crystallang/crystal:1.8.2-build name: Build interpreter steps: - uses: actions/checkout@v3 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.8.1-build + image: crystallang/crystal:1.8.2-build strategy: matrix: part: [0, 1, 2, 3] diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ca052d39a154..5b28a02be318 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -29,7 +29,7 @@ jobs: flags: "FLAGS=-Dwithout_ffi USE_PCRE1=true" - crystal_bootstrap_version: 1.7.3 flags: "" - - crystal_bootstrap_version: 1.8.1 + - crystal_bootstrap_version: 1.8.2 flags: "" steps: - name: Download Crystal source diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index e446fc5cbbd7..fdd67b5754e0 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -43,7 +43,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.8.1" + crystal: "1.8.2" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 74f20e4ace9d..b8f637008ff7 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.8.1-alpine + container: crystallang/crystal:1.8.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -27,7 +27,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.8.1-alpine + container: crystallang/crystal:1.8.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -42,7 +42,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.8.1-alpine + container: crystallang/crystal:1.8.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index 461706b9f323..c8ba20e26db7 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.8.1-alpine + container: crystallang/crystal:1.8.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.8.1-alpine + container: crystallang/crystal:1.8.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 884b568a481f..40a792a9b0a1 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.8.1-build + container: crystallang/crystal:1.8.2-build steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 23d6a455200f..fd651dff0d98 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -19,7 +19,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.8.1" + crystal: "1.8.2" - name: Download Crystal source uses: actions/checkout@v3 diff --git a/bin/ci b/bin/ci index 0c53ee6acafe..033331527759 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.8.1-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.8.2/crystal-1.8.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.8.2-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.8.1}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.8.2}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 9a7f2898af17..ed0468d2317e 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:0p25mghlrb4q0q6fwm9gksm8giq3dlr49d5kc4h7141pfdj1m9in"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.8.2/crystal-1.8.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:06hj2lcin4sdlmdb42asg677860c9ryca6wm5s6mps2fshbmq933"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:0p25mghlrb4q0q6fwm9gksm8giq3dlr49d5kc4h7141pfdj1m9in"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.8.2/crystal-1.8.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:06hj2lcin4sdlmdb42asg677860c9ryca6wm5s6mps2fshbmq933"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-linux-x86_64.tar.gz"; - sha256 = "sha256:0ymd76yj157bydpk05l5vgxg6fsm432rhc98ixrzhcr552x3g6gw"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.8.2/crystal-1.8.2-1-linux-x86_64.tar.gz"; + sha256 = "sha256:16pkvr28n5ba4440apmnflx7i09hap37vv51x763aym34yq1a0xd"; }; }.${pkgs.stdenv.system}); From a515985d0661de1f07c6fc4d44c346a80b2ae563 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 11 May 2023 23:18:08 +0800 Subject: [PATCH 0523/1551] Add `Socket::IPaddress.v4`, `.v6`, `.v4_mapped_v6` (#13422) * Add `Socket.v4`, `.v6`, `.v4_mapped_v6` * format * stricter int types * Revert "stricter int types" This reverts commit f0474efbb3bea1d658aad77e7b6267bef92e0510. * add `StaticArray` constructors * construct from `UInt16`s directly * `.v4_mapped_v6` --- spec/std/socket/address_spec.cr | 110 ++++++++++++++++++++++++++++++++ src/socket/address.cr | 107 +++++++++++++++++++++++++++++-- 2 files changed, 211 insertions(+), 6 deletions(-) diff --git a/spec/std/socket/address_spec.cr b/spec/std/socket/address_spec.cr index 6a6f0d926fd7..e2793e4f1ce0 100644 --- a/spec/std/socket/address_spec.cr +++ b/spec/std/socket/address_spec.cr @@ -121,6 +121,116 @@ describe Socket::IPAddress do end end + describe ".v4" do + it "constructs an IPv4 address" do + Socket::IPAddress.v4(0, 0, 0, 0, port: 0).should eq Socket::IPAddress.new("0.0.0.0", 0) + Socket::IPAddress.v4(127, 0, 0, 1, port: 1234).should eq Socket::IPAddress.new("127.0.0.1", 1234) + Socket::IPAddress.v4(192, 168, 0, 1, port: 8081).should eq Socket::IPAddress.new("192.168.0.1", 8081) + Socket::IPAddress.v4(255, 255, 255, 254, port: 65535).should eq Socket::IPAddress.new("255.255.255.254", 65535) + end + + it "raises on out of bound field" do + expect_raises(Socket::Error, "Invalid IPv4 field: 256") { Socket::IPAddress.v4(256, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: 256") { Socket::IPAddress.v4(0, 256, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: 256") { Socket::IPAddress.v4(0, 0, 256, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: 256") { Socket::IPAddress.v4(0, 0, 0, 256, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: -1") { Socket::IPAddress.v4(-1, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: -1") { Socket::IPAddress.v4(0, -1, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: -1") { Socket::IPAddress.v4(0, 0, -1, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: -1") { Socket::IPAddress.v4(0, 0, 0, -1, port: 0) } + end + + it "raises on out of bound port number" do + expect_raises(Socket::Error, "Invalid port number: 65536") { Socket::IPAddress.v4(0, 0, 0, 0, port: 65536) } + expect_raises(Socket::Error, "Invalid port number: -1") { Socket::IPAddress.v4(0, 0, 0, 0, port: -1) } + end + + it "constructs from StaticArray" do + Socket::IPAddress.v4(UInt8.static_array(0, 0, 0, 0), 0).should eq Socket::IPAddress.new("0.0.0.0", 0) + Socket::IPAddress.v4(UInt8.static_array(127, 0, 0, 1), 1234).should eq Socket::IPAddress.new("127.0.0.1", 1234) + Socket::IPAddress.v4(UInt8.static_array(192, 168, 0, 1), 8081).should eq Socket::IPAddress.new("192.168.0.1", 8081) + Socket::IPAddress.v4(UInt8.static_array(255, 255, 255, 254), 65535).should eq Socket::IPAddress.new("255.255.255.254", 65535) + end + end + + describe ".v6" do + it "constructs an IPv6 address" do + Socket::IPAddress.v6(0, 0, 0, 0, 0, 0, 0, 0, port: 0).should eq Socket::IPAddress.new("::", 0) + Socket::IPAddress.v6(1, 2, 3, 4, 5, 6, 7, 8, port: 8080).should eq Socket::IPAddress.new("1:2:3:4:5:6:7:8", 8080) + Socket::IPAddress.v6(0xfe80, 0, 0, 0, 0x4860, 0x4860, 0x4860, 0x1234, port: 55001).should eq Socket::IPAddress.new("fe80::4860:4860:4860:1234", 55001) + Socket::IPAddress.v6(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xfffe, port: 65535).should eq Socket::IPAddress.new("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", 65535) + Socket::IPAddress.v6(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x0001, port: 0).should eq Socket::IPAddress.new("::ffff:192.168.0.1", 0) + end + + it "raises on out of bound field" do + expect_raises(Socket::Error, "Invalid IPv6 field: 65536") { Socket::IPAddress.v6(65536, 0, 0, 0, 0, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: 65536") { Socket::IPAddress.v6(0, 65536, 0, 0, 0, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: 65536") { Socket::IPAddress.v6(0, 0, 65536, 0, 0, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: 65536") { Socket::IPAddress.v6(0, 0, 0, 65536, 0, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: 65536") { Socket::IPAddress.v6(0, 0, 0, 0, 65536, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: 65536") { Socket::IPAddress.v6(0, 0, 0, 0, 0, 65536, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: 65536") { Socket::IPAddress.v6(0, 0, 0, 0, 0, 0, 65536, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: 65536") { Socket::IPAddress.v6(0, 0, 0, 0, 0, 0, 0, 65536, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: -1") { Socket::IPAddress.v6(-1, 0, 0, 0, 0, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: -1") { Socket::IPAddress.v6(0, -1, 0, 0, 0, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: -1") { Socket::IPAddress.v6(0, 0, -1, 0, 0, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: -1") { Socket::IPAddress.v6(0, 0, 0, -1, 0, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: -1") { Socket::IPAddress.v6(0, 0, 0, 0, -1, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: -1") { Socket::IPAddress.v6(0, 0, 0, 0, 0, -1, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: -1") { Socket::IPAddress.v6(0, 0, 0, 0, 0, 0, -1, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv6 field: -1") { Socket::IPAddress.v6(0, 0, 0, 0, 0, 0, 0, -1, port: 0) } + end + + it "raises on out of bound port number" do + expect_raises(Socket::Error, "Invalid port number: 65536") { Socket::IPAddress.v6(0, 0, 0, 0, 0, 0, 0, 0, port: 65536) } + expect_raises(Socket::Error, "Invalid port number: -1") { Socket::IPAddress.v6(0, 0, 0, 0, 0, 0, 0, 0, port: -1) } + end + + it "constructs from StaticArray" do + Socket::IPAddress.v6(UInt16.static_array(0, 0, 0, 0, 0, 0, 0, 0), 0).should eq Socket::IPAddress.new("::", 0) + Socket::IPAddress.v6(UInt16.static_array(1, 2, 3, 4, 5, 6, 7, 8), 8080).should eq Socket::IPAddress.new("1:2:3:4:5:6:7:8", 8080) + Socket::IPAddress.v6(UInt16.static_array(0xfe80, 0, 0, 0, 0x4860, 0x4860, 0x4860, 0x1234), 55001).should eq Socket::IPAddress.new("fe80::4860:4860:4860:1234", 55001) + Socket::IPAddress.v6(UInt16.static_array(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xfffe), 65535).should eq Socket::IPAddress.new("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", 65535) + Socket::IPAddress.v6(UInt16.static_array(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x0001), 0).should eq Socket::IPAddress.new("::ffff:192.168.0.1", 0) + end + end + + describe ".v4_mapped_v6" do + it "constructs an IPv4-mapped IPv6 address" do + # windows returns the former which, while correct, is not canonical + # TODO: implement also `#to_s` in Crystal + {Socket::IPAddress.new("::ffff:0:0", 0), Socket::IPAddress.new("::ffff:0.0.0.0", 0)}.should contain Socket::IPAddress.v4_mapped_v6(0, 0, 0, 0, port: 0) + Socket::IPAddress.v4_mapped_v6(127, 0, 0, 1, port: 1234).should eq Socket::IPAddress.new("::ffff:127.0.0.1", 1234) + Socket::IPAddress.v4_mapped_v6(192, 168, 0, 1, port: 8081).should eq Socket::IPAddress.new("::ffff:192.168.0.1", 8081) + Socket::IPAddress.v4_mapped_v6(255, 255, 255, 254, port: 65535).should eq Socket::IPAddress.new("::ffff:255.255.255.254", 65535) + end + + it "raises on out of bound field" do + expect_raises(Socket::Error, "Invalid IPv4 field: 256") { Socket::IPAddress.v4_mapped_v6(256, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: 256") { Socket::IPAddress.v4_mapped_v6(0, 256, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: 256") { Socket::IPAddress.v4_mapped_v6(0, 0, 256, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: 256") { Socket::IPAddress.v4_mapped_v6(0, 0, 0, 256, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: -1") { Socket::IPAddress.v4_mapped_v6(-1, 0, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: -1") { Socket::IPAddress.v4_mapped_v6(0, -1, 0, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: -1") { Socket::IPAddress.v4_mapped_v6(0, 0, -1, 0, port: 0) } + expect_raises(Socket::Error, "Invalid IPv4 field: -1") { Socket::IPAddress.v4_mapped_v6(0, 0, 0, -1, port: 0) } + end + + it "raises on out of bound port number" do + expect_raises(Socket::Error, "Invalid port number: 65536") { Socket::IPAddress.v4_mapped_v6(0, 0, 0, 0, port: 65536) } + expect_raises(Socket::Error, "Invalid port number: -1") { Socket::IPAddress.v4_mapped_v6(0, 0, 0, 0, port: -1) } + end + + it "constructs from StaticArray" do + # windows returns the former which, while correct, is not canonical + # TODO: implement also `#to_s` in Crystal + {Socket::IPAddress.new("::ffff:0:0", 0), Socket::IPAddress.new("::ffff:0.0.0.0", 0)}.should contain Socket::IPAddress.v4_mapped_v6(UInt8.static_array(0, 0, 0, 0), 0) + Socket::IPAddress.v4_mapped_v6(UInt8.static_array(127, 0, 0, 1), 1234).should eq Socket::IPAddress.new("::ffff:127.0.0.1", 1234) + Socket::IPAddress.v4_mapped_v6(UInt8.static_array(192, 168, 0, 1), 8081).should eq Socket::IPAddress.new("::ffff:192.168.0.1", 8081) + Socket::IPAddress.v4_mapped_v6(UInt8.static_array(255, 255, 255, 254), 65535).should eq Socket::IPAddress.new("::ffff:255.255.255.254", 65535) + end + end + it ".valid_v6?" do Socket::IPAddress.valid_v6?("::1").should be_true Socket::IPAddress.valid_v6?("x").should be_false diff --git a/src/socket/address.cr b/src/socket/address.cr index 4a6ea10dfb62..c077e4d74e48 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -82,7 +82,7 @@ class Socket @addr : LibC::In6Addr | LibC::InAddr def initialize(@address : String, @port : Int32) - raise Error.new("Invalid port number: #{port}") unless 0 <= port <= UInt16::MAX + raise Error.new("Invalid port number: #{port}") unless IPAddress.valid_port?(port) if addr = IPAddress.address_v6?(address) @addr = addr @@ -144,16 +144,111 @@ class Socket parse URI.parse(uri) end + # Returns the IPv4 address with the given address *fields* and *port* + # number. + def self.v4(fields : UInt8[4], port : UInt16) : self + addr_value = UInt32.zero + fields.each_with_index do |field, i| + addr_value = (addr_value << 8) | field + end + + addr = LibC::SockaddrIn.new( + sin_family: LibC::AF_INET, + sin_port: endian_swap(port), + sin_addr: LibC::InAddr.new(s_addr: endian_swap(addr_value)), + ) + new(pointerof(addr), sizeof(typeof(addr))) + end + + # Returns the IPv4 address `x0.x1.x2.x3:port`. + # + # Raises `Socket::Error` if any field or the port number is out of range. + def self.v4(x0 : Int, x1 : Int, x2 : Int, x3 : Int, *, port : Int) : self + fields = StaticArray[x0, x1, x2, x3].map { |field| to_v4_field(field) } + port = valid_port?(port) ? port.to_u16! : raise Error.new("Invalid port number: #{port}") + v4(fields, port) + end + + private def self.to_v4_field(field) + 0 <= field <= 0xff ? field.to_u8! : raise Error.new("Invalid IPv4 field: #{field}") + end + + # Returns the IPv6 address with the given address *fields* and *port* + # number. + def self.v6(fields : UInt16[8], port : UInt16) : self + fields.map! { |field| endian_swap(field) } + addr = LibC::SockaddrIn6.new( + sin6_family: LibC::AF_INET6, + sin6_port: endian_swap(port), + sin6_addr: ipv6_from_addr16(fields), + ) + new(pointerof(addr), sizeof(typeof(addr))) + end + + # Returns the IPv6 address `[x0:x1:x2:x3:x4:x5:x6:x7]:port`. + # + # Raises `Socket::Error` if any field or the port number is out of range. + def self.v6(x0 : Int, x1 : Int, x2 : Int, x3 : Int, x4 : Int, x5 : Int, x6 : Int, x7 : Int, *, port : Int) : self + fields = StaticArray[x0, x1, x2, x3, x4, x5, x6, x7].map { |field| to_v6_field(field) } + port = valid_port?(port) ? port.to_u16! : raise Error.new("Invalid port number: #{port}") + v6(fields, port) + end + + private def self.to_v6_field(field) + 0 <= field <= 0xffff ? field.to_u16! : raise Error.new("Invalid IPv6 field: #{field}") + end + + # Returns the IPv4-mapped IPv6 address with the given IPv4 address *fields* + # and *port* number. + def self.v4_mapped_v6(fields : UInt8[4], port : UInt16) : self + v6_fields = StaticArray[ + 0_u16, 0_u16, 0_u16, 0_u16, 0_u16, 0xffff_u16, + fields[0].to_u16! << 8 | fields[1], + fields[2].to_u16! << 8 | fields[3], + ] + v6(v6_fields, port) + end + + # Returns the IPv4-mapped IPv6 address `[::ffff:x0.x1.x2.x3]:port`. + # + # Raises `Socket::Error` if any field or the port number is out of range. + def self.v4_mapped_v6(x0 : Int, x1 : Int, x2 : Int, x3 : Int, *, port : Int) : self + v4_fields = StaticArray[x0, x1, x2, x3].map { |field| to_v4_field(field) } + port = valid_port?(port) ? port.to_u16! : raise Error.new("Invalid port number: #{port}") + v4_mapped_v6(v4_fields, port) + end + + private def self.ipv6_from_addr16(bytes : UInt16[8]) + addr = LibC::In6Addr.new + {% if flag?(:darwin) || flag?(:bsd) %} + addr.__u6_addr.__u6_addr16 = bytes + {% elsif flag?(:linux) && flag?(:musl) %} + addr.__in6_union.__s6_addr16 = bytes + {% elsif flag?(:wasm32) %} + bytes.each_with_index do |byte, i| + addr.s6_addr[2 * i] = byte.to_u8! + addr.s6_addr[2 * i + 1] = (byte >> 8).to_u8! + end + {% elsif flag?(:linux) %} + addr.__in6_u.__u6_addr16 = bytes + {% elsif flag?(:win32) %} + addr.u.word = bytes + {% else %} + {% raise "Unsupported platform" %} + {% end %} + addr + end + protected def initialize(sockaddr : LibC::SockaddrIn6*, @size) @family = Family::INET6 @addr = sockaddr.value.sin6_addr - @port = endian_swap(sockaddr.value.sin6_port).to_i + @port = IPAddress.endian_swap(sockaddr.value.sin6_port).to_i end protected def initialize(sockaddr : LibC::SockaddrIn*, @size) @family = Family::INET @addr = sockaddr.value.sin_addr - @port = endian_swap(sockaddr.value.sin_port).to_i + @port = IPAddress.endian_swap(sockaddr.value.sin_port).to_i end # Returns `true` if *address* is a valid IPv4 or IPv6 address. @@ -313,7 +408,7 @@ class Socket private def to_sockaddr_in6(addr) sockaddr = Pointer(LibC::SockaddrIn6).malloc sockaddr.value.sin6_family = family - sockaddr.value.sin6_port = endian_swap(port.to_u16!) + sockaddr.value.sin6_port = IPAddress.endian_swap(port.to_u16!) sockaddr.value.sin6_addr = addr sockaddr.as(LibC::Sockaddr*) end @@ -321,12 +416,12 @@ class Socket private def to_sockaddr_in(addr) sockaddr = Pointer(LibC::SockaddrIn).malloc sockaddr.value.sin_family = family - sockaddr.value.sin_port = endian_swap(port.to_u16!) + sockaddr.value.sin_port = IPAddress.endian_swap(port.to_u16!) sockaddr.value.sin_addr = addr sockaddr.as(LibC::Sockaddr*) end - private def endian_swap(x : UInt16) : UInt16 + protected def self.endian_swap(x : Int::Primitive) : Int::Primitive {% if IO::ByteFormat::NetworkEndian != IO::ByteFormat::SystemEndian %} x.byte_swap {% else %} From 39aae80643979ef6ba19e6a16766112518abaeb7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 11 May 2023 23:19:51 +0800 Subject: [PATCH 0524/1551] Support DLL delay-loading on Windows (#13436) --- src/compiler/crystal/compiler.cr | 14 ++ src/compiler/crystal/loader.cr | 2 - src/compiler/crystal/loader/msvc.cr | 44 +++- src/crystal/main.cr | 1 + src/crystal/system/win32/delay_load.cr | 205 ++++++++++++++++++ src/lib_c/x86_64-windows-msvc/c/delayimp.cr | 40 ++++ .../x86_64-windows-msvc/c/errhandlingapi.cr | 1 + .../x86_64-windows-msvc/c/libloaderapi.cr | 1 + src/lib_c/x86_64-windows-msvc/c/winnt.cr | 100 +++++++++ src/raise.cr | 3 + 10 files changed, 400 insertions(+), 11 deletions(-) create mode 100644 src/crystal/system/win32/delay_load.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/delayimp.cr diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 423defd6cb28..af573e817a3d 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -3,6 +3,7 @@ require "file_utils" require "colorize" require "crystal/digest/md5" {% if flag?(:msvc) %} + require "./loader" require "crystal/system/win32/visual_studio" require "crystal/system/win32/windows_sdk" {% end %} @@ -364,6 +365,19 @@ module Crystal link_args << lib_flags @link_flags.try { |flags| link_args << flags } + {% if flag?(:msvc) %} + if program.has_flag?("preview_dll") && !program.has_flag?("no_win32_delay_load") + # "LINK : warning LNK4199: /DELAYLOAD:foo.dll ignored; no imports found from foo.dll" + # it is harmless to skip this error because not all import libraries are always used, much + # less the individual DLLs they refer to + link_args << "/IGNORE:4199" + + Loader.search_dlls(Process.parse_arguments_windows(link_args.join(' '))).each do |dll| + link_args << "/DELAYLOAD:#{dll}" + end + end + {% end %} + args = %(/nologo #{object_arg} #{output_arg} /link #{link_args.join(' ')}).gsub("\n", " ") cmd = "#{linker} #{args}" diff --git a/src/compiler/crystal/loader.cr b/src/compiler/crystal/loader.cr index fde96c0b5eef..d7d0133cc993 100644 --- a/src/compiler/crystal/loader.cr +++ b/src/compiler/crystal/loader.cr @@ -10,8 +10,6 @@ require "option_parser" # finding symbols inside them. # # See system-specific implementations in ./loader for details. -# -# A Windows implementation is not yet available. class Crystal::Loader class LoadError < Exception property args : Array(String)? diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index 70560e334ca7..54c3bd5d41af 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -20,9 +20,42 @@ class Crystal::Loader # Parses linker arguments in the style of `link.exe`. def self.parse(args : Array(String), *, search_paths : Array(String) = default_search_paths) : self - libnames = [] of String + search_paths, libnames = parse_args(args, search_paths) file_paths = [] of String + begin + self.new(search_paths, libnames, file_paths) + rescue exc : LoadError + exc.args = args + exc.search_paths = search_paths + raise exc + end + end + + # Returns the list of DLLs imported from the libraries specified in the given + # linker arguments. Used by the compiler for delay-loaded DLL support. + def self.search_dlls(args : Array(String), *, search_paths : Array(String) = default_search_paths) : Set(String) + search_paths, libnames = parse_args(args, search_paths) + dlls = Set(String).new + + libnames.each do |libname| + search_paths.each do |directory| + library_path = File.join(directory, library_filename(libname)) + next unless File.file?(library_path) + + Crystal::System::LibraryArchive.imported_dlls(library_path).each do |dll| + dlls << dll unless dll.compare("kernel32.dll", case_insensitive: true).zero? + end + break + end + end + + dlls + end + + private def self.parse_args(args, search_paths) + libnames = [] of String + # NOTE: `/LIBPATH`s are prepended before the default paths: # (https://docs.microsoft.com/en-us/cpp/build/reference/libpath-additional-libpath) # @@ -39,14 +72,7 @@ class Crystal::Loader end search_paths = extra_search_paths + search_paths - - begin - self.new(search_paths, libnames, file_paths) - rescue exc : LoadError - exc.args = args - exc.search_paths = search_paths - raise exc - end + {search_paths, libnames} end def self.library_filename(libname : String) : String diff --git a/src/crystal/main.cr b/src/crystal/main.cr index fb95c9930fb4..6a63d838d234 100644 --- a/src/crystal/main.cr +++ b/src/crystal/main.cr @@ -129,6 +129,7 @@ end {% if flag?(:win32) %} require "./system/win32/wmain" + require "./system/win32/delay_load" {% end %} {% if flag?(:wasi) %} diff --git a/src/crystal/system/win32/delay_load.cr b/src/crystal/system/win32/delay_load.cr new file mode 100644 index 000000000000..37831d48e11c --- /dev/null +++ b/src/crystal/system/win32/delay_load.cr @@ -0,0 +1,205 @@ +require "c/delayimp" + +private ERROR_SEVERITY_ERROR = 0xC0000000_u32 +private FACILITY_VISUALCPP = 0x6d + +private macro vcpp_exception(err) + {{ ERROR_SEVERITY_ERROR | (FACILITY_VISUALCPP << 16) | WinError.constant(err.id) }} +end + +lib LibC + $image_base = __ImageBase : IMAGE_DOS_HEADER +end + +private macro p_from_rva(rva) + pointerof(LibC.image_base).as(UInt8*) + {{ rva }} +end + +module Crystal::System::DelayLoad + @[Extern] + record InternalImgDelayDescr, + grAttrs : LibC::DWORD, + szName : LibC::LPSTR, + phmod : LibC::HMODULE*, + pIAT : LibC::IMAGE_THUNK_DATA*, + pINT : LibC::IMAGE_THUNK_DATA*, + pBoundIAT : LibC::IMAGE_THUNK_DATA*, + pUnloadIAT : LibC::IMAGE_THUNK_DATA*, + dwTimeStamp : LibC::DWORD + + @[AlwaysInline] + def self.pinh_from_image_base(hmod : LibC::HMODULE) + (hmod.as(UInt8*) + hmod.as(LibC::IMAGE_DOS_HEADER*).value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*) + end + + @[AlwaysInline] + def self.interlocked_exchange(atomic : LibC::HMODULE*, value : LibC::HMODULE) + Atomic::Ops.atomicrmw(:xchg, atomic, value, :sequentially_consistent, false) + end +end + +# This is a port of the default delay-load helper function in the `DelayHlp.cpp` +# file that comes with Microsoft Visual C++, except that all user-defined hooks +# are omitted. It is called every time the program attempts to load a symbol +# from a DLL. For more details see: +# https://learn.microsoft.com/en-us/cpp/build/reference/understanding-the-helper-function +# +# It is available even when the `preview_dll` flag is absent, so that system +# DLLs such as `advapi32.dll` and shards can be delay-loaded in the usual mixed +# static/dynamic builds by passing the appropriate linker flags explicitly. +# +# The delay load helper cannot call functions from the library being loaded, as +# that leads to an infinite recursion. In particular, if `preview_dll` is in +# effect, `Crystal::System.print_error` will not work, because the C runtime +# library DLLs are also delay-loaded and `LibC.snprintf` is unavailable. If you +# want print debugging inside this function, try the following: +# +# ``` +# lib LibC +# STD_OUTPUT_HANDLE = -11 +# +# fun GetStdHandle(nStdHandle : DWORD) : HANDLE +# fun FormatMessageA(dwFlags : DWORD, lpSource : Void*, dwMessageId : DWORD, dwLanguageId : DWORD, lpBuffer : LPSTR, nSize : DWORD, arguments : Void*) : DWORD +# end +# +# buf = uninitialized LibC::CHAR[512] +# args = StaticArray[dli.szDll, dli.dlp.union.szProcName] +# len = LibC.FormatMessageA(LibC::FORMAT_MESSAGE_FROM_STRING | LibC::FORMAT_MESSAGE_ARGUMENT_ARRAY, "Loading `%2` from `%1`\n", 0, 0, buf, buf.size, args) +# LibC.WriteFile(LibC.GetStdHandle(LibC::STD_OUTPUT_HANDLE), buf, len, out _, nil) +# ``` +# +# `kernel32.dll` is the only DLL guaranteed to be available. It cannot be +# delay-loaded and the Crystal compiler excludes it from the linker arguments. +# +# This function does _not_ work with the empty prelude yet! +fun __delayLoadHelper2(pidd : LibC::ImgDelayDescr*, ppfnIATEntry : LibC::FARPROC*) : LibC::FARPROC + # TODO: support protected delay load? (/GUARD:CF) + # DloadAcquireSectionWriteAccess + + # Set up some data we use for the hook procs but also useful for our own use + idd = Crystal::System::DelayLoad::InternalImgDelayDescr.new( + grAttrs: pidd.value.grAttrs, + szName: p_from_rva(pidd.value.rvaDLLName).as(LibC::LPSTR), + phmod: p_from_rva(pidd.value.rvaHmod).as(LibC::HMODULE*), + pIAT: p_from_rva(pidd.value.rvaIAT).as(LibC::IMAGE_THUNK_DATA*), + pINT: p_from_rva(pidd.value.rvaINT).as(LibC::IMAGE_THUNK_DATA*), + pBoundIAT: p_from_rva(pidd.value.rvaBoundIAT).as(LibC::IMAGE_THUNK_DATA*), + pUnloadIAT: p_from_rva(pidd.value.rvaUnloadIAT).as(LibC::IMAGE_THUNK_DATA*), + dwTimeStamp: pidd.value.dwTimeStamp, + ) + + dli = LibC::DelayLoadInfo.new( + cb: sizeof(LibC::DelayLoadInfo), + pidd: pidd, + ppfn: ppfnIATEntry, + szDll: idd.szName, + dlp: LibC::DelayLoadProc.new, + hmodCur: LibC::HMODULE.null, + pfnCur: LibC::FARPROC.null, + dwLastError: LibC::DWORD.zero, + ) + + if 0 == idd.grAttrs & LibC::DLAttrRva + rgpdli = pointerof(dli) + + # DloadReleaseSectionWriteAccess + + LibC.RaiseException( + vcpp_exception(ERROR_INVALID_PARAMETER), + 0, + 1, + pointerof(rgpdli).as(LibC::ULONG_PTR*), + ) + end + + hmod = idd.phmod.value + + # Calculate the index for the IAT entry in the import address table + # N.B. The INT entries are ordered the same as the IAT entries so + # the calculation can be done on the IAT side. + iIAT = ppfnIATEntry.as(LibC::IMAGE_THUNK_DATA*) - idd.pIAT + iINT = iIAT + + pitd = idd.pINT + iINT + + dli.dlp.fImportByName = pitd.value.u1.ordinal & LibC::IMAGE_ORDINAL_FLAG == 0 + + if dli.dlp.fImportByName + import_by_name = p_from_rva(LibC::RVA.new!(pitd.value.u1.addressOfData)) + dli.dlp.union.szProcName = import_by_name + offsetof(LibC::IMAGE_IMPORT_BY_NAME, @name) + else + dli.dlp.union.dwOrdinal = LibC::DWORD.new!(pitd.value.u1.ordinal & 0xFFFF) + end + + # Check to see if we need to try to load the library. + if !hmod + # note: ANSI variant used here + unless hmod = LibC.LoadLibraryExA(dli.szDll, nil, 0) + dli.dwLastError = LibC.GetLastError + + rgpdli = pointerof(dli) + + # DloadReleaseSectionWriteAccess + LibC.RaiseException( + vcpp_exception(ERROR_MOD_NOT_FOUND), + 0, + 1, + pointerof(rgpdli).as(LibC::ULONG_PTR*), + ) + + # If we get to here, we blindly assume that the handler of the exception + # has magically fixed everything up and left the function pointer in + # dli.pfnCur. + return dli.pfnCur + end + + # Store the library handle. If it is already there, we infer + # that another thread got there first, and we need to do a + # FreeLibrary() to reduce the refcount + hmodT = Crystal::System::DelayLoad.interlocked_exchange(idd.phmod, hmod) + LibC.FreeLibrary(hmod) if hmodT == hmod + end + + # Go for the procedure now. + dli.hmodCur = hmod + if pidd.value.rvaBoundIAT != 0 && pidd.value.dwTimeStamp != 0 + # bound imports exist...check the timestamp from the target image + pinh = Crystal::System::DelayLoad.pinh_from_image_base(hmod) + + if pinh.value.signature == LibC::IMAGE_NT_SIGNATURE && + pinh.value.fileHeader.timeDateStamp == idd.dwTimeStamp && + hmod.address == pinh.value.optionalHeader.imageBase + # Everything is good to go, if we have a decent address + # in the bound IAT! + if pfnRet = LibC::FARPROC.new(idd.pBoundIAT[iIAT].u1.function) + ppfnIATEntry.value = pfnRet + # DloadReleaseSectionWriteAccess + return pfnRet + end + end + end + + unless pfnRet = LibC.GetProcAddress(hmod, dli.dlp.union.szProcName) + dli.dwLastError = LibC.GetLastError + + rgpdli = pointerof(dli) + + # DloadReleaseSectionWriteAccess + LibC.RaiseException( + vcpp_exception(ERROR_PROC_NOT_FOUND), + 0, + 1, + pointerof(rgpdli).as(LibC::ULONG_PTR*), + ) + # DloadAcquireSectionWriteAccess + + # If we get to here, we blindly assume that the handler of the exception + # has magically fixed everything up and left the function pointer in + # dli.pfnCur. + pfnRet = dli.pfnCur + end + + ppfnIATEntry.value = pfnRet + # DloadReleaseSectionWriteAccess + pfnRet +end diff --git a/src/lib_c/x86_64-windows-msvc/c/delayimp.cr b/src/lib_c/x86_64-windows-msvc/c/delayimp.cr new file mode 100644 index 000000000000..5de0f9f08864 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/delayimp.cr @@ -0,0 +1,40 @@ +require "c/libloaderapi" +require "c/winnt" + +lib LibC + alias RVA = DWORD + + struct ImgDelayDescr + grAttrs : DWORD # attributes + rvaDLLName : RVA # RVA to dll name + rvaHmod : RVA # RVA of module handle + rvaIAT : RVA # RVA of the IAT + rvaINT : RVA # RVA of the INT + rvaBoundIAT : RVA # RVA of the optional bound IAT + rvaUnloadIAT : RVA # RVA of optional copy of original IAT + dwTimeStamp : DWORD # 0 if not bound, O.W. date/time stamp of DLL bound to (Old BIND) + end + + DLAttrRva = 0x1 + + union DelayLoadProc_union + szProcName : LPSTR + dwOrdinal : DWORD + end + + struct DelayLoadProc + fImportByName : BOOL + union : DelayLoadProc_union + end + + struct DelayLoadInfo + cb : DWORD # size of structure + pidd : ImgDelayDescr* # raw form of data (everything is there) + ppfn : FARPROC* # points to address of function to load + szDll : LPSTR # name of dll + dlp : DelayLoadProc # name or ordinal of procedure + hmodCur : HMODULE # the hInstance of the library we have loaded + pfnCur : FARPROC # the actual function that will be called + dwLastError : DWORD # error received (if an error notification) + end +end diff --git a/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr b/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr index 0d209f7d6773..5ac7f9feb7c4 100644 --- a/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr @@ -11,5 +11,6 @@ lib LibC fun GetLastError : DWORD fun SetLastError(dwErrCode : DWORD) + fun RaiseException(dwExceptionCode : DWORD, dwExceptionFlags : DWORD, nNumberOfArguments : DWORD, lpArguments : ULONG_PTR*) fun AddVectoredExceptionHandler(first : DWORD, handler : PVECTORED_EXCEPTION_HANDLER) : Void* end diff --git a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr index c96282b74a8e..2b6259bf7015 100644 --- a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr @@ -4,6 +4,7 @@ require "c/winnt" lib LibC alias FARPROC = Void* + fun LoadLibraryExA(lpLibFileName : LPSTR, hFile : HANDLE, dwFlags : DWORD) : HMODULE fun LoadLibraryExW(lpLibFileName : LPWSTR, hFile : HANDLE, dwFlags : DWORD) : HMODULE fun FreeLibrary(hLibModule : HMODULE) : BOOL diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index 52cfab5ebf89..0c7ceae716f3 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -198,4 +198,104 @@ lib LibC protect : DWORD type : DWORD end + + IMAGE_NT_SIGNATURE = 0x00004550 # PE00 + + struct IMAGE_DOS_HEADER + e_magic : WORD + e_cblp : WORD + e_cp : WORD + e_crlc : WORD + e_cparhdr : WORD + e_minalloc : WORD + e_maxalloc : WORD + e_ss : WORD + e_sp : WORD + e_csum : WORD + e_ip : WORD + e_cs : WORD + e_lfarlc : WORD + e_ovno : WORD + e_res : WORD[4] + e_oemid : WORD + e_oeminfo : WORD + e_res2 : WORD[10] + e_lfanew : LONG + end + + struct IMAGE_FILE_HEADER + machine : WORD + numberOfSections : WORD + timeDateStamp : DWORD + pointerToSymbolTable : DWORD + numberOfSymbols : DWORD + sizeOfOptionalHeader : WORD + characteristics : WORD + end + + struct IMAGE_DATA_DIRECTORY + virtualAddress : DWORD + size : DWORD + end + + struct IMAGE_OPTIONAL_HEADER64 + magic : WORD + majorLinkerVersion : BYTE + minorLinkerVersion : BYTE + sizeOfCode : DWORD + sizeOfInitializedData : DWORD + sizeOfUninitializedData : DWORD + addressOfEntryPoint : DWORD + baseOfCode : DWORD + imageBase : ULongLong + sectionAlignment : DWORD + fileAlignment : DWORD + majorOperatingSystemVersion : WORD + minorOperatingSystemVersion : WORD + majorImageVersion : WORD + minorImageVersion : WORD + majorSubsystemVersion : WORD + minorSubsystemVersion : WORD + win32VersionValue : DWORD + sizeOfImage : DWORD + sizeOfHeaders : DWORD + checkSum : DWORD + subsystem : WORD + dllCharacteristics : WORD + sizeOfStackReserve : ULongLong + sizeOfStackCommit : ULongLong + sizeOfHeapReserve : ULongLong + sizeOfHeapCommit : ULongLong + loaderFlags : DWORD + numberOfRvaAndSizes : DWORD + dataDirectory : IMAGE_DATA_DIRECTORY[16] # IMAGE_NUMBEROF_DIRECTORY_ENTRIES + end + + struct IMAGE_NT_HEADERS64 + signature : DWORD + fileHeader : IMAGE_FILE_HEADER + optionalHeader : IMAGE_OPTIONAL_HEADER64 + end + + struct IMAGE_IMPORT_BY_NAME + hint : WORD + name : CHAR[1] + end + + union IMAGE_THUNK_DATA64_u1 + forwarderString : ULongLong + function : ULongLong + ordinal : ULongLong + addressOfData : ULongLong + end + + struct IMAGE_THUNK_DATA64 + u1 : IMAGE_THUNK_DATA64_u1 + end + + IMAGE_ORDINAL_FLAG64 = 0x8000000000000000_u64 + + alias IMAGE_NT_HEADERS = IMAGE_NT_HEADERS64 + alias IMAGE_THUNK_DATA = IMAGE_THUNK_DATA64 + IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG64 end diff --git a/src/raise.cr b/src/raise.cr index dd289e7762f0..bf15257ddaf1 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -94,6 +94,9 @@ end {% elsif flag?(:win32) %} require "exception/lib_unwind" + {% begin %} + @[Link({{ flag?(:preview_dll) ? "vcruntime" : "libvcruntime" }})] + {% end %} lib LibC fun _CxxThrowException(ex : Void*, throw_info : Void*) : NoReturn end From 5b151d8737465e7b2ef76e16c53aafade2d6b917 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 12 May 2023 22:08:11 +0800 Subject: [PATCH 0525/1551] Support LLVM 16 (#13181) --- .github/workflows/llvm.yml | 35 ++++++++++++++++++++++------------ src/llvm/ext/llvm-versions.txt | 2 +- src/llvm/ext/llvm_ext.cc | 8 +++++++- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index fdd67b5754e0..061dd8f4f582 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -15,30 +15,41 @@ jobs: strategy: fail-fast: false matrix: - llvm_version: ["13.0.0", "14.0.0", "15.0.6"] + include: + - llvm_version: "13.0.0" + llvm_ubuntu_version: "20.04" + - llvm_version: "14.0.0" + llvm_ubuntu_version: "18.04" + - llvm_version: "15.0.6" + llvm_ubuntu_version: "18.04" + - llvm_version: "16.0.3" + llvm_ubuntu_version: "22.04" name: "LLVM ${{ matrix.llvm_version }}" steps: - name: Checkout Crystal source uses: actions/checkout@v3 - - name: Cache LLVM and Clang + - name: Cache LLVM id: cache-llvm uses: actions/cache@v3 with: - path: | - C:/Program Files/LLVM - ./llvm + path: ./llvm key: llvm-${{ matrix.llvm_version }} if: "${{ !env.ACT }}" - - uses: KyleMayes/install-llvm-action@13d5d77cbf0bd7e35cb02a8f9ed4bb85bed3393b # v1.8.0 - with: - version: "${{ matrix.llvm_version }}" - cached: ${{ steps.cache-llvm.outputs.cache-hit }} + - name: Install LLVM ${{ matrix.llvm_version }} + run: | + mkdir -p llvm + curl -L "https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ matrix.llvm_version }}/clang+llvm-${{ matrix.llvm_version }}-x86_64-linux-gnu-ubuntu-${{ matrix.llvm_ubuntu_version }}.tar.xz" > llvm.tar.xz + tar x --xz -C llvm --strip-components=1 -f llvm.tar.xz + if: steps.cache-llvm.outputs.cache-hit != 'true' - - name: Set LLVM_CONFIG - # LLVM_PATH is set by install-llvm-action - run: echo "LLVM_CONFIG=$LLVM_PATH/bin/llvm-config" >> $GITHUB_ENV + - name: Set up LLVM + run: | + sudo apt-get install -y libtinfo5 + echo "PATH=$(pwd)/llvm/bin:$PATH" >> $GITHUB_ENV + echo "LLVM_CONFIG=$(pwd)/llvm/bin/llvm-config" >> $GITHUB_ENV + echo "LD_LIBRARY_PATH=$(pwd)/llvm/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV - name: Install Crystal uses: crystal-lang/install-crystal@v1 diff --git a/src/llvm/ext/llvm-versions.txt b/src/llvm/ext/llvm-versions.txt index 3895e35b2bcc..eee7a8d7f17d 100644 --- a/src/llvm/ext/llvm-versions.txt +++ b/src/llvm/ext/llvm-versions.txt @@ -1 +1 @@ -15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 +16.0 15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc index 184f0b3d6e90..9c17b4681c4a 100644 --- a/src/llvm/ext/llvm_ext.cc +++ b/src/llvm/ext/llvm_ext.cc @@ -21,6 +21,12 @@ using namespace llvm; #include #include +#if LLVM_VERSION_GE(16, 0) +#define makeArrayRef ArrayRef +#endif + +typedef DIBuilder *DIBuilderRef; +#define DIArray DINodeArray template T *unwrapDIptr(LLVMMetadataRef v) { return (T *)(v ? unwrap(v) : NULL); } @@ -157,7 +163,7 @@ LLVMBool LLVMExtCreateMCJITCompilerForModule( .setOptLevel((CodeGenOpt::Level)options.OptLevel) .setTargetOptions(targetOptions); bool JIT; - if (Optional CM = unwrap(options.CodeModel, JIT)) + if (auto CM = unwrap(options.CodeModel, JIT)) builder.setCodeModel(*CM); if (options.MCJMM) builder.setMCJITMemoryManager( From 1a43461bb87d8f27576b1695091cc83b94ecdcb1 Mon Sep 17 00:00:00 2001 From: Abdullah Alhusaini <44743015+a-alhusaini@users.noreply.github.com> Date: Fri, 12 May 2023 17:08:54 +0300 Subject: [PATCH 0526/1551] Fix error message for calling `record` macro with kwargs (#13367) Co-authored-by: Sijawusz Pur Rahnama --- src/macros.cr | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/macros.cr b/src/macros.cr index 687a8f9149b5..e8163cbec046 100644 --- a/src/macros.cr +++ b/src/macros.cr @@ -61,7 +61,15 @@ # p.copy_with x: 3 # => # # p # => # # ``` -macro record(name, *properties) +macro record(__name name, *properties, **kwargs) + {% raise <<-TXT unless kwargs.empty? + macro `record` does not accept named arguments + Did you mean: + + record #{name}, #{(properties + kwargs.map { |name, value| "#{name} : #{value}" }).join(", ").id} + TXT + %} + struct {{name.id}} {% for property in properties %} {% if property.is_a?(Assign) %} From 0956b333676039791b0a941932fa357672b7213b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Mon, 15 May 2023 01:18:12 -0700 Subject: [PATCH 0527/1551] Allow newline after hash rocket (#13460) --- spec/compiler/parser/parser_spec.cr | 1 + src/compiler/crystal/syntax/parser.cr | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 93c2a98d3d55..d25aeef9ba27 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1282,6 +1282,7 @@ module Crystal it_parses "foo z: out x; x", [Call.new(nil, "foo", named_args: [NamedArgument.new("z", Out.new("x".var))]), "x".var] it_parses "{1 => 2, 3 => 4}", HashLiteral.new([HashLiteral::Entry.new(1.int32, 2.int32), HashLiteral::Entry.new(3.int32, 4.int32)]) + it_parses "{1 =>\n2, 3 =>\n4}", HashLiteral.new([HashLiteral::Entry.new(1.int32, 2.int32), HashLiteral::Entry.new(3.int32, 4.int32)]) it_parses %({A::B => 1, C::D => 2}), HashLiteral.new([HashLiteral::Entry.new(Path.new("A", "B"), 1.int32), HashLiteral::Entry.new(Path.new("C", "D"), 2.int32)]) assert_syntax_error %({"foo" => 1, "bar": 2}), "can't use 'key: value' syntax in a hash literal" diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 4d18ce7ef1aa..e531ad5fda6c 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -2523,7 +2523,7 @@ module Crystal end end slash_is_regex! - next_token_skip_space + next_token_skip_space_or_newline parse_hash_literal first_key, location, allow_of end end From 228ddad97093c9558920bafaf1e19e72b1fe0f2a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 16 May 2023 19:22:01 +0800 Subject: [PATCH 0528/1551] Upgrade Windows CI to LLVM 16 (#13469) --- .github/workflows/win.yml | 17 ++++++----------- Makefile.win | 6 +++++- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index fd651dff0d98..0d80873b0e1d 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -283,33 +283,28 @@ jobs: uses: actions/cache@v3 with: path: llvm - key: llvm-libs-15.0.7-msvc-${{ env.VSCMD_VER }} + key: llvm-libs-16.0.3-msvc-${{ env.VSCMD_VER }} - name: Download LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' run: | - iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.7/llvm-15.0.7.src.tar.xz -OutFile llvm.tar.xz - (Get-FileHash -Algorithm SHA256 .\llvm.tar.xz).hash -eq "4AD8B2CC8003C86D0078D15D987D84E3A739F24AAE9033865C027ABAE93EE7A4" + iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.3/llvm-16.0.3.src.tar.xz -OutFile llvm.tar.xz + (Get-FileHash -Algorithm SHA256 .\llvm.tar.xz).hash -eq "D820E63BC3A6F4F833EC69A1EF49A2E81992E90BC23989F98946914B061AB6C7" 7z x llvm.tar.xz 7z x llvm.tar mv llvm-* llvm-src - name: Download LLVM's CMake files if: steps.cache-llvm.outputs.cache-hit != 'true' run: | - iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.7/cmake-15.0.7.src.tar.xz -OutFile cmake.tar.xz - (Get-FileHash -Algorithm SHA256 .\cmake.tar.xz).hash -eq "8986f29b634fdaa9862eedda78513969fe9788301c9f2d938f4c10a3e7a3e7ea" + iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.3/cmake-16.0.3.src.tar.xz -OutFile cmake.tar.xz + (Get-FileHash -Algorithm SHA256 .\cmake.tar.xz).hash -eq "B6D83C91F12757030D8361DEDC5DD84357B3EDB8DA406B5D0850DF8B6F7798B1" 7z x cmake.tar.xz 7z x cmake.tar mv cmake-* cmake - - name: Patch LLVM for VS 2019 16.7.0 - working-directory: ./llvm-src - if: steps.cache-llvm.outputs.cache-hit != 'true' - run: | - sed -i 's/#ifdef HAVE_STD_IS_TRIVIALLY_COPYABLE/#if 0/' include/llvm/Support/type_traits.h - name: Build LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' working-directory: ./llvm-src run: | - cmake . -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DLLVM_USE_CRT_RELEASE=MT -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_BUILD_BENCHMARKS=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_ENABLE_ZSTD=OFF + cmake . -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DLLVM_USE_CRT_RELEASE=MT -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_ZSTD=OFF cmake --build . --config Release - name: Gather LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' diff --git a/Makefile.win b/Makefile.win index 4b5f4fff4ebf..4e11f34fe071 100644 --- a/Makefile.win +++ b/Makefile.win @@ -216,7 +216,11 @@ $(O)\crystal.rc: $(MAKEFILE_LIST) $(LLVM_EXT_OBJ): $(LLVM_EXT_DIR)\llvm_ext.cc $(call check_llvm_config) - $(CXX) /nologo /c $(CXXFLAGS) "/Fo$@" "$<" $(shell $(LLVM_CONFIG) --cxxflags) + @rem Disable some harmless warnings: + @rem - warning C4244: 'initializing': conversion from '_Ty' to '_Ty2', possible loss of data + @rem - warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc + @rem - warning C4624: '...': destructor was implicitly defined as deleted + $(CXX) /nologo /c $(CXXFLAGS) /WX /wd4244 /wd4624 /wd4530 "/Fo$@" "$<" $(shell $(LLVM_CONFIG) --cxxflags) .PHONY: clean clean: clean_crystal ## Clean up built directories and files From 206e04f9e9b7bed9123c0d694fb028e6ad8a1832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Tue, 16 May 2023 04:22:18 -0700 Subject: [PATCH 0529/1551] Parser: Add missing locations of various AST nodes (#13452) --- spec/compiler/parser/parser_spec.cr | 77 +++++++++++++++++++++++++++ src/compiler/crystal/syntax/ast.cr | 12 +++++ src/compiler/crystal/syntax/parser.cr | 58 +++++++++++--------- 3 files changed, 121 insertions(+), 26 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index d25aeef9ba27..e6c1b17ea1ab 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2585,5 +2585,82 @@ module Crystal exps.expressions[1].location.not_nil!.line_number.should eq(7) end end + + it "sets correct location of parameter in proc literal" do + source = "->(foo : Bar, baz) { }" + args = Parser.parse(source).as(ProcLiteral).def.args + node_source(source, args[0]).should eq("foo : Bar") + node_source(source, args[1]).should eq("baz") + end + + it "sets correct location of splat in multiple assignment" do + source = "*foo, bar = 1, 2" + node = Parser.parse(source).as(MultiAssign).targets[0] + node_source(source, node).should eq("*foo") + + source = "foo, *bar = 1, 2" + node = Parser.parse(source).as(MultiAssign).targets[1] + node_source(source, node).should eq("*bar") + end + + it "sets correct location of tuple type" do + source = "x : {Foo, Bar}" + node = Parser.parse(source).as(TypeDeclaration).declared_type + node_source(source, node).should eq("{Foo, Bar}") + end + + it "sets correct location of named tuple type" do + source = "x : {foo: Bar}" + node = Parser.parse(source).as(TypeDeclaration).declared_type + node_source(source, node).should eq("{foo: Bar}") + end + + it "sets correct location of argument in named tuple type" do + source = "x : {foo: Bar}" + node = Parser.parse(source).as(TypeDeclaration).declared_type.as(Generic).named_args.not_nil!.first + node_source(source, node).should eq("foo: Bar") + end + + it "sets correct location of instance variable in proc pointer" do + source = "->@foo.x" + node = Parser.parse(source).as(ProcPointer).obj.not_nil! + node_source(source, node).should eq("@foo") + end + + it "sets correct location of instance variable in proc pointer" do + source = "->@@foo.x" + node = Parser.parse(source).as(ProcPointer).obj.not_nil! + node_source(source, node).should eq("@@foo") + end + + it "sets correct location of annotation on method parameter" do + source = "def x(@[Foo] y) end" + node = Parser.parse(source).as(Def).args.first.parsed_annotations.not_nil!.first + node_source(source, node).should eq("@[Foo]") + end + + it "sets correct location of annotation in lib" do + source = "lib X; @[Foo]; end" + node = Parser.parse(source).as(LibDef).body + node_source(source, node).should eq("@[Foo]") + end + + it "sets correct location of annotation in enum" do + source = "enum X; @[Foo]; end" + node = Parser.parse(source).as(EnumDef).members.first + node_source(source, node).should eq("@[Foo]") + end + + it "sets correct location of private method in enum" do + source = "enum X; private def foo; end; end" + node = Parser.parse(source).as(EnumDef).members.first + node_source(source, node).should eq("private def foo; end") + end + + it "sets correct location of protected macro in enum" do + source = "enum X; protected macro foo; end; end" + node = Parser.parse(source).as(EnumDef).members.first + node_source(source, node).should eq("protected macro foo; end") + end end end diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index b3ab3c761f0c..925749aee50a 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -721,6 +721,10 @@ module Crystal NamedArgument.new(name, value.clone) end + def end_location + @end_location || value.end_location + end + def_equals_and_hash name, value end @@ -1155,6 +1159,10 @@ module Crystal @exp.accept visitor end + def end_location + @end_location || @exp.end_location + end + def_equals_and_hash exp end @@ -1224,6 +1232,10 @@ module Crystal VisibilityModifier.new(@modifier, @exp.clone) end + def end_location + @end_location || @exp.end_location + end + def_equals_and_hash modifier, exp end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index e531ad5fda6c..7694ef2383bc 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -135,7 +135,7 @@ module Crystal location = @token.location if @token.type.op_star? - lhs_splat_index = 0 + lhs_splat = {index: 0, location: @token.location} next_token_skip_space end @@ -147,17 +147,17 @@ module Crystal case @token.type when .op_comma? unless last_is_target - unexpected_token if lhs_splat_index + unexpected_token if lhs_splat raise "Multiple assignment is not allowed for constants" if last.is_a?(Path) unexpected_token end when .newline?, .op_semicolon? - unexpected_token if lhs_splat_index && !multi_assign_middle?(last) - return last unless lhs_splat_index + unexpected_token if lhs_splat && !multi_assign_middle?(last) + return last unless lhs_splat else if end_token? - unexpected_token if lhs_splat_index && !multi_assign_middle?(last) - return last unless lhs_splat_index + unexpected_token if lhs_splat && !multi_assign_middle?(last) + return last unless lhs_splat else unexpected_token end @@ -178,8 +178,8 @@ module Crystal next_token_skip_space_or_newline if @token.type.op_star? - raise "splat assignment already specified" if lhs_splat_index - lhs_splat_index = i + raise "splat assignment already specified" if lhs_splat + lhs_splat = {index: i, location: @token.location} next_token_skip_space end @@ -217,13 +217,15 @@ module Crystal raise "BUG: multi_assign index expression can only be Assign or Call" end - if lhs_splat_index - targets[lhs_splat_index] = Splat.new(targets[lhs_splat_index]) + if lhs_splat + lhs_splat_location = lhs_splat[:location] + lhs_splat_index = lhs_splat[:index] + targets[lhs_splat_index] = Splat.new(targets[lhs_splat_index]).at(lhs_splat_location) end values.concat exps[assign_index + 1..-1] if values.size != 1 - if lhs_splat_index + if lhs_splat raise "Multiple assignment count mismatch", location if targets.size - 1 > values.size else raise "Multiple assignment count mismatch", location if targets.size != values.size @@ -1340,6 +1342,7 @@ module Crystal def parse_annotation doc = @token.doc + location = @token.location next_token_skip_space name = parse_path @@ -1369,10 +1372,11 @@ module Crystal end end check :OP_RSQUARE + end_location = token_end_location @wants_regex = false next_token_skip_space - ann = Annotation.new(name, args, named_args) + ann = Annotation.new(name, args, named_args).at(location).at_end(end_location) ann.doc = doc ann end @@ -1880,10 +1884,9 @@ module Crystal if @token.type.op_lparen? next_token_skip_space_or_newline while !@token.type.op_rparen? - param_location = @token.location - param = parse_fun_literal_param.at(param_location) + param = parse_fun_literal_param if params.any? &.name.==(param.name) - raise "duplicated proc literal parameter name: #{param.name}", param_location + raise "duplicated proc literal parameter name: #{param.name}", param.location.not_nil! end params << param @@ -1950,12 +1953,15 @@ module Crystal def parse_fun_literal_param name = check_ident + location = @token.location + end_location = token_end_location next_token_skip_space_or_newline if @token.type.op_colon? next_token_skip_space_or_newline type = parse_bare_proc_type + end_location = type.end_location end if @token.type.op_comma? @@ -1965,7 +1971,7 @@ module Crystal check :OP_RPAREN end - Arg.new name, restriction: type + Arg.new(name, restriction: type).at(location).at_end(end_location) end def parse_fun_pointer @@ -2005,7 +2011,7 @@ module Crystal name = "#{name}=" if equals_sign when .instance_var? raise "ProcPointer of instance variable cannot be global", location if global - obj = InstanceVar.new(@token.value.to_s) + obj = InstanceVar.new(@token.value.to_s).at(location).at_end(token_end_location) next_token_skip_space check :OP_PERIOD name = consume_def_or_macro_name @@ -2013,7 +2019,7 @@ module Crystal name = "#{name}=" if equals_sign when .class_var? raise "ProcPointer of class variable cannot be global", location if global - obj = ClassVar.new(@token.value.to_s) + obj = ClassVar.new(@token.value.to_s).at(location).at_end(token_end_location) next_token_skip_space check :OP_PERIOD name = consume_def_or_macro_name @@ -4977,8 +4983,9 @@ module Crystal type = make_tuple_type parse_union_types(:OP_RCURLY, allow_splats: true) end check :OP_RCURLY + end_location = token_end_location next_token_skip_space - type + type.at(location).at_end(end_location) when .op_minus_gt? parse_proc_type_output(nil, location) when .op_lparen? @@ -5153,7 +5160,7 @@ module Crystal type = parse_bare_proc_type skip_space - named_args << NamedArgument.new(name, type) + named_args << NamedArgument.new(name, type).at(name_location) if @token.type.op_comma? next_token_skip_space_or_newline @@ -5396,7 +5403,7 @@ module Crystal next_token_skip_space exp = parse_op_assign - modifier = VisibilityModifier.new(modifier, exp).at(location).at_end(exp) + modifier = VisibilityModifier.new(modifier, exp).at(location) modifier.doc = doc exp.doc = doc modifier @@ -6027,9 +6034,9 @@ module Crystal private def parse_enum_body_expressions members = [] of ASTNode until end_token? + location = @token.location case @token.type when .const? - location = @token.location constant_name = @token.value.to_s member_doc = @token.doc @@ -6075,17 +6082,17 @@ module Crystal case @token.value when Keyword::DEF member = parse_def.at(def_location) - member = VisibilityModifier.new(visibility, member) if visibility + member = VisibilityModifier.new(visibility, member).at(location) if visibility members << member when Keyword::MACRO member = parse_macro.at(def_location) - member = VisibilityModifier.new(visibility, member) if visibility + member = VisibilityModifier.new(visibility, member).at(location) if visibility members << member else unexpected_token end when .class_var? - class_var = ClassVar.new(@token.value.to_s).at(@token.location) + class_var = ClassVar.new(@token.value.to_s).at(location) next_token_skip_space check :OP_EQ @@ -6096,7 +6103,6 @@ module Crystal when .op_lcurly_lcurly? members << parse_percent_macro_expression when .op_lcurly_percent? - location = @token.location members << parse_percent_macro_control.at(location) when .op_at_lsquare? members << parse_annotation From bf98a78334cf1ac2ffc2086b8fb49408baa421a9 Mon Sep 17 00:00:00 2001 From: Margret Riegert Date: Tue, 16 May 2023 07:22:28 -0400 Subject: [PATCH 0530/1551] Add a testcase line number to the output of JUnitFormatter (#13468) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/spec/junit_formatter_spec.cr | 18 +++++++++--------- src/spec/junit_formatter.cr | 2 ++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/spec/std/spec/junit_formatter_spec.cr b/spec/std/spec/junit_formatter_spec.cr index 39e0841362a9..699a2f4ad45b 100644 --- a/spec/std/spec/junit_formatter_spec.cr +++ b/spec/std/spec/junit_formatter_spec.cr @@ -18,8 +18,8 @@ describe "JUnit Formatter" do expected = <<-XML - - + + XML @@ -34,7 +34,7 @@ describe "JUnit Formatter" do expected = <<-XML - + @@ -51,7 +51,7 @@ describe "JUnit Formatter" do expected = <<-XML - + @@ -68,7 +68,7 @@ describe "JUnit Formatter" do expected = <<-XML - + @@ -88,14 +88,14 @@ describe "JUnit Formatter" do expected = <<-XML - - + + - + - + diff --git a/src/spec/junit_formatter.cr b/src/spec/junit_formatter.cr index 05c4c466d5fe..0463ec8e82bd 100644 --- a/src/spec/junit_formatter.cr +++ b/src/spec/junit_formatter.cr @@ -67,6 +67,8 @@ module Spec HTML.escape(classname(result), io) io << %(" name=") HTML.escape(escape_xml_attr(result.description), io) + io << %(" line=") + io << result.line if elapsed = result.elapsed io << %(" time=") From 7c05e8cfb9809f4c09f54a2ceb6879813e4780c6 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Wed, 17 May 2023 04:39:54 -0400 Subject: [PATCH 0531/1551] Update distribution-scripts (#13457) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 303c21992438..a684ee6fa308 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "0328eb5ee7a4eae440846f890287b15ad2118e30" + default: "4b3af3fb9ebeb2c74850bc7025a17a38ab82c5f4" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From 0135d69da56cc97a08fb3f94b1f56bffa61ddcf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 17 May 2023 14:28:36 +0200 Subject: [PATCH 0532/1551] Add `Regex.literal` (#13339) --- spec/std/regex_spec.cr | 8 ++++++++ src/regex.cr | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index aca655179f03..116da32b24e5 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -34,6 +34,14 @@ describe "Regex" do end end + it ".literal" do + Regex.literal("foo").should eq /foo/ + Regex.literal("foo", i: true).should eq /foo/i + Regex.literal("foo", i: true, m: true).should eq /foo/im + Regex.literal("foo", i: true, m: true, x: true).should eq /foo/imx + Regex.literal("foo", x: true).should eq /foo/x + end + it "#options" do /cat/.options.ignore_case?.should be_false /cat/i.options.ignore_case?.should be_true diff --git a/src/regex.cr b/src/regex.cr index aae2a8e1a643..0d94fc03cc1f 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -351,6 +351,15 @@ class Regex new(_source: source, _options: options) end + # Creates a new `Regex` instance from a literal consisting of a *pattern* and the named parameter modifiers. + def self.literal(pattern : String, *, i : Bool = false, m : Bool = false, x : Bool = false) : self + options = CompileOptions::None + options |= :ignore_case if i + options |= :multiline if m + options |= :extended if x + new(pattern, options: options) + end + # Determines Regex's source validity. If it is, `nil` is returned. # If it's not, a `String` containing the error message is returned. # From 04103318a5d6bfa0efc31c2d3bfa392c3463003d Mon Sep 17 00:00:00 2001 From: skinnyjames Date: Wed, 17 May 2023 08:29:26 -0400 Subject: [PATCH 0533/1551] Add `URI::Params#merge`, `#merge!` and `URI#update_query_params` (#13415) Co-authored-by: Quinton Miller --- spec/std/uri/params_spec.cr | 58 +++++++++++++++++++++++++++++++++++++ spec/std/uri_spec.cr | 17 +++++++++++ src/uri.cr | 20 +++++++++++++ src/uri/params.cr | 38 ++++++++++++++++++++++++ 4 files changed, 133 insertions(+) diff --git a/spec/std/uri/params_spec.cr b/spec/std/uri/params_spec.cr index 0acd647617c5..44fc0b3abb4c 100644 --- a/spec/std/uri/params_spec.cr +++ b/spec/std/uri/params_spec.cr @@ -296,6 +296,64 @@ class URI end end + describe "#merge!" do + it "modifies the reciever" do + params = Params.parse("foo=bar&foo=baz&qux=zoo") + other_params = Params.parse("foo=buzz&foo=extra") + + params.merge!(other_params, replace: false) + + params.to_s.should eq("foo=bar&foo=baz&foo=buzz&foo=extra&qux=zoo") + end + + describe "does not modify the other params" do + it "with replace: true" do + params = Params.parse("foo=bar") + other_params = Params.parse("foo=buzz&foo=extra") + + params.merge!(other_params, replace: true) + params.add("foo", "another") + + other_params.to_s.should eq("foo=buzz&foo=extra") + end + + it "with replace: false" do + params = Params.parse("foo=bar") + other_params = Params.parse("foo=buzz&foo=extra") + + params.merge!(other_params, replace: false) + params.add("foo", "another") + + other_params.to_s.should eq("foo=buzz&foo=extra") + end + end + end + + describe "#merge" do + it "replaces all values with the same key by default" do + params = Params.parse("foo=bar&foo=baz&qux=zoo") + other_params = Params.parse("foo=buzz&foo=extra") + + params.merge(other_params).to_s.should eq("foo=buzz&foo=extra&qux=zoo") + end + + it "appends values with the same key with replace: false" do + params = Params.parse("foo=bar&foo=baz&qux=zoo") + other_params = Params.parse("foo=buzz&foo=extra") + + params.merge(other_params, replace: false).to_s.should eq("foo=bar&foo=baz&foo=buzz&foo=extra&qux=zoo") + end + + it "does not modify the reciever" do + params = Params.parse("foo=bar&foo=baz&qux=zoo") + other_params = Params.parse("foo=buzz&foo=extra") + + params.merge(other_params) + + params.to_s.should eq("foo=bar&foo=baz&qux=zoo") + end + end + describe "#empty?" do it "test empty?" do Params.parse("foo=bar&foo=baz&baz=qux").empty?.should be_false diff --git a/spec/std/uri_spec.cr b/spec/std/uri_spec.cr index 96aea2e16989..402263742a96 100644 --- a/spec/std/uri_spec.cr +++ b/spec/std/uri_spec.cr @@ -369,6 +369,23 @@ describe "URI" do end end + describe "#update_query_params" do + it "returns self" do + expected_params = URI::Params{"id" => "30"} + + uri = URI.parse("http://foo.com?id=30&limit=5#time=1305298413") + uri.update_query_params { |params| params.delete("limit") }.should be(uri) + uri.query_params.should eq(expected_params) + end + + it "commits changes to the URI::Object" do + uri = URI.parse("http://foo.com?id=30&limit=5#time=1305298413") + uri.update_query_params { |params| params.delete("limit") } + + uri.to_s.should eq("http://foo.com?id=30#time=1305298413") + end + end + describe "#==" do it { URI.parse("http://example.com").should eq(URI.parse("http://example.com")) } end diff --git a/src/uri.cr b/src/uri.cr index 06842deadf6d..dfdf26f9f419 100644 --- a/src/uri.cr +++ b/src/uri.cr @@ -286,6 +286,26 @@ class URI self.query = params.to_s end + # Yields the value of `#query_params` commits any modifications of the `URI::Params` instance to self. + # Returns the modified `URI::Params` + # + # ``` + # require "uri" + # uri = URI.parse("http://foo.com?id=30&limit=5#time=1305298413") + # uri.update_query_params { |params| params.delete_all("limit") } # => URI::Params{"id" => ["30"]} + # + # puts uri.to_s # => "http://foo.com?id=30#time=1305298413" + # ``` + def update_query_params(& : URI::Params -> _) : URI + params = query_params + + yield params + + self.query_params = params + + self + end + # Returns the authority component of this URI. # It is formatted as `user:pass@host:port` with missing parts being omitted. # diff --git a/src/uri/params.cr b/src/uri/params.cr index aa4f5eb30511..70dd0f1b373a 100644 --- a/src/uri/params.cr +++ b/src/uri/params.cr @@ -362,6 +362,44 @@ class URI raw_params.delete(name) end + # Merges *params* into self. + # + # ``` + # params = URI::Params.parse("foo=bar&foo=baz&qux=zoo") + # other_params = URI::Params.parse("foo=buzz&foo=extra") + # params.merge!(other_params).to_s # => "foo=buzz&foo=extra&qux=zoo" + # params.fetch_all("foo") # => ["buzz", "extra"] + # ``` + # + # See `#merge` for a non-mutating alternative + def merge!(params : URI::Params, *, replace : Bool = true) : URI::Params + if replace + @raw_params.merge!(params.raw_params) { |_, _first, second| second.dup } + else + @raw_params.merge!(params.raw_params) do |_, first, second| + first + second + end + end + + self + end + + # Merges *params* and self into a new instance. + # If *replace* is `false` values with the same key are concatenated. + # Otherwise the value in *params* overrides the one in self. + # + # ``` + # params = URI::Params.parse("foo=bar&foo=baz&qux=zoo") + # other_params = URI::Params.parse("foo=buzz&foo=extra") + # params.merge(other_params).to_s # => "foo=buzz&foo=extra&qux=zoo" + # params.merge(other_params, replace: false).to_s # => "foo=bar&foo=baz&foo=buzz&foo=extra&qux=zoo" + # ``` + # + # See `#merge!` for a mutating alternative + def merge(params : URI::Params, *, replace : Bool = true) : URI::Params + dup.merge!(params, replace: replace) + end + # Serializes to string representation as http url-encoded form. # # ``` From 92184d4a030dfeeac20eae019bf7799a026d5a5c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 22 May 2023 18:11:38 +0800 Subject: [PATCH 0534/1551] Parse IP addresses in Crystal instead of using `LibC.inet_pton` (#13463) --- spec/std/socket/address_spec.cr | 132 ++++++++------- src/openssl/ssl/hostname_validation.cr | 18 +-- src/socket/address.cr | 213 +++++++++++++++++++++---- 3 files changed, 259 insertions(+), 104 deletions(-) diff --git a/spec/std/socket/address_spec.cr b/spec/std/socket/address_spec.cr index e2793e4f1ce0..a354d4216ce6 100644 --- a/spec/std/socket/address_spec.cr +++ b/spec/std/socket/address_spec.cr @@ -121,6 +121,69 @@ describe Socket::IPAddress do end end + # Tests from libc-test: + # https://repo.or.cz/libc-test.git/blob/2113a3ed8217775797dd9a82aa420c10ef1712d5:/src/functional/inet_pton.c + describe ".parse_v4_fields?" do + # dotted-decimal notation + it { Socket::IPAddress.parse_v4_fields?("0.0.0.0").should eq UInt8.static_array(0, 0, 0, 0) } + it { Socket::IPAddress.parse_v4_fields?("127.0.0.1").should eq UInt8.static_array(127, 0, 0, 1) } + it { Socket::IPAddress.parse_v4_fields?("10.0.128.31").should eq UInt8.static_array(10, 0, 128, 31) } + it { Socket::IPAddress.parse_v4_fields?("255.255.255.255").should eq UInt8.static_array(255, 255, 255, 255) } + + # numbers-and-dots notation, but not dotted-decimal + it { Socket::IPAddress.parse_v4_fields?("1.2.03.4").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1.2.0x33.4").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1.2.0XAB.4").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1.2.0xabcd").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1.0xabcdef").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("00377.0x0ff.65534").should be_nil } + + # invalid + it { Socket::IPAddress.parse_v4_fields?(".1.2.3").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1..2.3").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1.2.3.").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1.2.3.4.5").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1.2.3.a").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1.256.2.3").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1.2.4294967296.3").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1.2.-4294967295.3").should be_nil } + it { Socket::IPAddress.parse_v4_fields?("1.2. 3.4").should be_nil } + end + + describe ".parse_v6_fields?" do + it { Socket::IPAddress.parse_v6_fields?(":").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("::").should eq UInt16.static_array(0, 0, 0, 0, 0, 0, 0, 0) } + it { Socket::IPAddress.parse_v6_fields?("::1").should eq UInt16.static_array(0, 0, 0, 0, 0, 0, 0, 1) } + it { Socket::IPAddress.parse_v6_fields?(":::").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("192.168.1.1").should be_nil } + it { Socket::IPAddress.parse_v6_fields?(":192.168.1.1").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("::192.168.1.1").should eq UInt16.static_array(0, 0, 0, 0, 0, 0, 0xc0a8, 0x0101) } + it { Socket::IPAddress.parse_v6_fields?("0:0:0:0:0:0:192.168.1.1").should eq UInt16.static_array(0, 0, 0, 0, 0, 0, 0xc0a8, 0x0101) } + it { Socket::IPAddress.parse_v6_fields?("0:0::0:0:0:192.168.1.1").should eq UInt16.static_array(0, 0, 0, 0, 0, 0, 0xc0a8, 0x0101) } + it { Socket::IPAddress.parse_v6_fields?("::012.34.56.78").should be_nil } + it { Socket::IPAddress.parse_v6_fields?(":ffff:192.168.1.1").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("::ffff:192.168.1.1").should eq UInt16.static_array(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x0101) } + it { Socket::IPAddress.parse_v6_fields?(".192.168.1.1").should be_nil } + it { Socket::IPAddress.parse_v6_fields?(":.192.168.1.1").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("a:0b:00c:000d:E:F::").should eq UInt16.static_array(0xa, 0x0b, 0x00c, 0x000d, 0xE, 0xF, 0, 0) } + it { Socket::IPAddress.parse_v6_fields?("a:0b:00c:000d:0000e:f::").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("1:2:3:4:5:6::").should eq UInt16.static_array(1, 2, 3, 4, 5, 6, 0, 0) } + it { Socket::IPAddress.parse_v6_fields?("1:2:3:4:5:6:7::").should eq UInt16.static_array(1, 2, 3, 4, 5, 6, 7, 0) } + it { Socket::IPAddress.parse_v6_fields?("1:2:3:4:5:6:7:8::").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("1:2:3:4:5:6:7::9").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("::1:2:3:4:5:6").should eq UInt16.static_array(0, 0, 1, 2, 3, 4, 5, 6) } + it { Socket::IPAddress.parse_v6_fields?("::1:2:3:4:5:6:7").should eq UInt16.static_array(0, 1, 2, 3, 4, 5, 6, 7) } + it { Socket::IPAddress.parse_v6_fields?("::1:2:3:4:5:6:7:8").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("a:b::c:d:e:f").should eq UInt16.static_array(0xa, 0xb, 0, 0, 0xc, 0xd, 0xe, 0xf) } + it { Socket::IPAddress.parse_v6_fields?("ffff:c0a8:5e4").should be_nil } + it { Socket::IPAddress.parse_v6_fields?(":ffff:c0a8:5e4").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("0:0:0:0:0:ffff:c0a8:5e4").should eq UInt16.static_array(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x5e4) } + it { Socket::IPAddress.parse_v6_fields?("0:0:0:0:ffff:c0a8:5e4").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("0::ffff:c0a8:5e4").should eq UInt16.static_array(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x5e4) } + it { Socket::IPAddress.parse_v6_fields?("::0::ffff:c0a8:5e4").should be_nil } + it { Socket::IPAddress.parse_v6_fields?("c0a8").should be_nil } + end + describe ".v4" do it "constructs an IPv4 address" do Socket::IPAddress.v4(0, 0, 0, 0, port: 0).should eq Socket::IPAddress.new("0.0.0.0", 0) @@ -397,68 +460,15 @@ end {% end %} describe Socket do - # Tests from libc-test: - # http://repo.or.cz/libc-test.git/blob/master:/src/functional/inet_pton.c + # Most of the specs are moved to `.parse_v4_fields?` and `.parse_v6_fields?`, + # which are implemented in pure Crystal; the remaining ones here are test + # cases that were once known to break on certain platforms when `Socket.ip?` + # was still using the system `inet_pton` it ".ip?" do - # dotted-decimal notation - Socket.ip?("0.0.0.0").should be_true - Socket.ip?("127.0.0.1").should be_true - Socket.ip?("10.0.128.31").should be_true - Socket.ip?("255.255.255.255").should be_true - - # numbers-and-dots notation, but not dotted-decimal - # Socket.ip?("1.2.03.4").should be_false # fails on darwin - Socket.ip?("1.2.0x33.4").should be_false - Socket.ip?("1.2.0XAB.4").should be_false - Socket.ip?("1.2.0xabcd").should be_false - Socket.ip?("1.0xabcdef").should be_false - Socket.ip?("00377.0x0ff.65534").should be_false - - # invalid - Socket.ip?(".1.2.3").should be_false - Socket.ip?("1..2.3").should be_false - Socket.ip?("1.2.3.").should be_false - Socket.ip?("1.2.3.4.5").should be_false - Socket.ip?("1.2.3.a").should be_false - Socket.ip?("1.256.2.3").should be_false - Socket.ip?("1.2.4294967296.3").should be_false - Socket.ip?("1.2.-4294967295.3").should be_false - Socket.ip?("1.2. 3.4").should be_false - - # ipv6 - Socket.ip?(":").should be_false - Socket.ip?("::").should be_true - Socket.ip?("::1").should be_true - Socket.ip?(":::").should be_false - Socket.ip?(":192.168.1.1").should be_false - Socket.ip?("::192.168.1.1").should be_true - Socket.ip?("0:0:0:0:0:0:192.168.1.1").should be_true - Socket.ip?("0:0::0:0:0:192.168.1.1").should be_true - # Socket.ip?("::012.34.56.78").should be_false # fails on darwin - Socket.ip?(":ffff:192.168.1.1").should be_false - Socket.ip?("::ffff:192.168.1.1").should be_true - Socket.ip?(".192.168.1.1").should be_false - Socket.ip?(":.192.168.1.1").should be_false - Socket.ip?("a:0b:00c:000d:E:F::").should be_true - # Socket.ip?("a:0b:00c:000d:0000e:f::").should be_false # fails on GNU libc - Socket.ip?("1:2:3:4:5:6::").should be_true - Socket.ip?("1:2:3:4:5:6:7::").should be_true - Socket.ip?("1:2:3:4:5:6:7:8::").should be_false - Socket.ip?("1:2:3:4:5:6:7::9").should be_false - Socket.ip?("::1:2:3:4:5:6").should be_true - # FIXME: On older Windows versions, this returned `false`. It was apparently fixed in Windows Server 2022. - {% unless flag?(:win32) %} - Socket.ip?("::1:2:3:4:5:6:7").should be_true - {% end %} - Socket.ip?("::1:2:3:4:5:6:7:8").should be_false - Socket.ip?("a:b::c:d:e:f").should be_true - Socket.ip?("ffff:c0a8:5e4").should be_false - Socket.ip?(":ffff:c0a8:5e4").should be_false - Socket.ip?("0:0:0:0:0:ffff:c0a8:5e4").should be_true - Socket.ip?("0:0:0:0:ffff:c0a8:5e4").should be_false - Socket.ip?("0::ffff:c0a8:5e4").should be_true - Socket.ip?("::0::ffff:c0a8:5e4").should be_false - Socket.ip?("c0a8").should be_false + Socket.ip?("1.2.03.4").should be_false + Socket.ip?("::012.34.56.78").should be_false + Socket.ip?("a:0b:00c:000d:0000e:f::").should be_false + Socket.ip?("::1:2:3:4:5:6:7").should be_true end it "==" do diff --git a/src/openssl/ssl/hostname_validation.cr b/src/openssl/ssl/hostname_validation.cr index 9f09dfc9d4eb..8c0a787e6541 100644 --- a/src/openssl/ssl/hostname_validation.cr +++ b/src/openssl/ssl/hostname_validation.cr @@ -44,19 +44,19 @@ module OpenSSL::SSL::HostnameValidation pattern = String.new(dns_name, dns_name_len) return Result::MatchFound if matches_hostname?(pattern, hostname) when LibCrypto::GEN_IPADD - data = LibCrypto.asn1_string_data(current_name.value) - len = LibCrypto.asn1_string_length(current_name.value) + data = Slice.new(LibCrypto.asn1_string_data(current_name.value), LibCrypto.asn1_string_length(current_name.value)) - case len + case data.size when 4 - addr = uninitialized LibC::InAddr - if LibC.inet_pton(LibC::AF_INET, hostname, pointerof(addr).as(Void*)) > 0 - return Result::MatchFound if addr == data.as(LibC::InAddr*).value + if v4_fields = ::Socket::IPAddress.parse_v4_fields?(hostname) + return Result::MatchFound if v4_fields.to_slice == data end when 16 - addr6 = uninitialized LibC::In6Addr - if LibC.inet_pton(LibC::AF_INET6, hostname, pointerof(addr6).as(Void*)) > 0 - return Result::MatchFound if addr6.unsafe_as(StaticArray(UInt32, 4)) == data.as(StaticArray(UInt32, 4)*).value + if v6_fields = ::Socket::IPAddress.parse_v6_fields?(hostname) + {% if IO::ByteFormat::NetworkEndian != IO::ByteFormat::SystemEndian %} + v6_fields.map! &.byte_swap + {% end %} + return Result::MatchFound if v6_fields.to_slice.to_unsafe_bytes == data end else # not a length we expect diff --git a/src/socket/address.cr b/src/socket/address.cr index c077e4d74e48..0466c9bfef7c 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -58,14 +58,6 @@ class Socket # `String`, or directly received from an opened connection (e.g. # `Socket#local_address`, `Socket#receive`). # - # Example: - # ``` - # require "socket" - # - # Socket::IPAddress.new("127.0.0.1", 8080) - # Socket::IPAddress.new("fe80::2ab2:bdff:fe59:8e2c", 1234) - # ``` - # # `IPAddress` won't resolve domains, including `localhost`. If you must # resolve an IP, or don't know whether a `String` contains an IP or a domain # name, you should use `Addrinfo.resolve` instead. @@ -81,20 +73,33 @@ class Socket @addr : LibC::In6Addr | LibC::InAddr - def initialize(@address : String, @port : Int32) + # Creates an `IPAddress` from the given IPv4 or IPv6 *address* and *port* + # number. + # + # *address* is parsed using `.parse_v4_fields?` and `.parse_v6_fields?`. + # Raises `Socket::Error` if *address* does not contain a valid IP address or + # the port number is out of range. + # + # ``` + # require "socket" + # + # Socket::IPAddress.new("127.0.0.1", 8080) # => Socket::IPAddress(127.0.0.1:8080) + # Socket::IPAddress.new("fe80::2ab2:bdff:fe59:8e2c", 1234) # => Socket::IPAddress([fe80::2ab2:bdff:fe59:8e2c]:1234) + # ``` + def self.new(address : String, port : Int32) raise Error.new("Invalid port number: #{port}") unless IPAddress.valid_port?(port) - if addr = IPAddress.address_v6?(address) - @addr = addr - @family = Family::INET6 - @size = sizeof(LibC::SockaddrIn6) - elsif addr = IPAddress.address_v4?(address) - @addr = addr - @family = Family::INET - @size = sizeof(LibC::SockaddrIn) + if v4_fields = parse_v4_fields?(address) + addr = v4(v4_fields, port.to_u16!) + elsif v6_fields = parse_v6_fields?(address) + addr = v6(v6_fields, port.to_u16!) else raise Error.new("Invalid IP address: #{address}") end + + # TODO: canonicalize (#13423) + addr.address = address + addr end # Creates an `IPAddress` from the internal OS representation. Supports both @@ -144,6 +149,158 @@ class Socket parse URI.parse(uri) end + # Parses *str* as an IPv4 address and returns its fields, or returns `nil` + # if *str* does not contain a valid IPv4 address. + # + # The format of IPv4 addresses follows + # [RFC 3493, section 6.3](https://datatracker.ietf.org/doc/html/rfc3493#section-6.3). + # No extensions (e.g. octal fields, fewer than 4 fields) are supported. + # + # ``` + # require "socket" + # + # Socket::IPAddress.parse_v4_fields?("192.168.0.1") # => UInt8.static_array(192, 168, 0, 1) + # Socket::IPAddress.parse_v4_fields?("255.255.255.254") # => UInt8.static_array(255, 255, 255, 254) + # Socket::IPAddress.parse_v4_fields?("01.2.3.4") # => nil + # ``` + def self.parse_v4_fields?(str : String) : UInt8[4]? + parse_v4_fields?(str.to_slice) + end + + private def self.parse_v4_fields?(bytes : Bytes) + # port of https://git.musl-libc.org/cgit/musl/tree/src/network/inet_pton.c?id=7e13e5ae69a243b90b90d2f4b79b2a150f806335 + fields = StaticArray(UInt8, 4).new(0) + ptr = bytes.to_unsafe + finish = ptr + bytes.size + + 4.times do |i| + decimal = 0_u32 + old_ptr = ptr + + 3.times do + break unless ptr < finish + ch = ptr.value &- 0x30 + break unless ch <= 0x09 + decimal = decimal &* 10 &+ ch + ptr += 1 + end + + return nil if ptr == old_ptr # no decimal + return nil if ptr - old_ptr > 1 && old_ptr.value === '0' # octal etc. + return nil if decimal > 0xFF # overflow + + fields[i] = decimal.to_u8! + + break if i == 3 + return nil unless ptr < finish && ptr.value === '.' + ptr += 1 + end + + fields if ptr == finish + end + + # Parses *str* as an IPv6 address and returns its fields, or returns `nil` + # if *str* does not contain a valid IPv6 address. + # + # The format of IPv6 addresses follows + # [RFC 4291, section 2.2](https://datatracker.ietf.org/doc/html/rfc4291#section-2.2). + # Both canonical and non-canonical forms are supported. + # + # ``` + # require "socket" + # + # Socket::IPAddress.parse_v6_fields?("::1") # => UInt16.static_array(0, 0, 0, 0, 0, 0, 0, 1) + # Socket::IPAddress.parse_v6_fields?("a:0b:00c:000d:E:F::") # => UInt16.static_array(10, 11, 12, 13, 14, 15, 0, 0) + # Socket::IPAddress.parse_v6_fields?("::ffff:192.168.1.1") # => UInt16.static_array(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x0101) + # Socket::IPAddress.parse_v6_fields?("1::2::") # => nil + # ``` + def self.parse_v6_fields?(str : String) : UInt16[8]? + parse_v6_fields?(str.to_slice) + end + + private def self.parse_v6_fields?(bytes : Bytes) + # port of https://git.musl-libc.org/cgit/musl/tree/src/network/inet_pton.c?id=7e13e5ae69a243b90b90d2f4b79b2a150f806335 + ptr = bytes.to_unsafe + finish = ptr + bytes.size + + if ptr < finish && ptr.value === ':' + ptr += 1 + return nil unless ptr < finish && ptr.value === ':' + end + + fields = StaticArray(UInt16, 8).new(0) + brk = -1 + need_v4 = false + + i = 0 + while true + if ptr < finish && ptr.value === ':' && brk < 0 + brk = i + fields[i] = 0 + ptr += 1 + break if ptr == finish + return nil if i == 7 + i &+= 1 + next + end + + field = 0_u16 + old_ptr = ptr + + 4.times do + break unless ptr < finish + ch = from_hex(ptr.value) + break unless ch <= 0x0F + field = field.unsafe_shl(4) | ch + ptr += 1 + end + + return nil if ptr == old_ptr # no field + + fields[i] = field + break if ptr == finish && (brk >= 0 || i == 7) + return nil if i == 7 + + unless ptr < finish && ptr.value === ':' + return nil if !(ptr < finish && ptr.value === '.') || (i < 6 && brk < 0) + need_v4 = true + i &+= 1 + fields[i] = 0 + ptr = old_ptr + break + end + + ptr += 1 + i &+= 1 + end + + if brk >= 0 + fields_brk = fields.to_unsafe + brk + (fields_brk + 7 - i).move_from(fields_brk, i &+ 1 &- brk) + fields_brk.clear(7 &- i) + end + + if need_v4 + x0, x1, x2, x3 = parse_v4_fields?(Slice.new(ptr, finish - ptr)) || return nil + fields[6] = x0.to_u16! << 8 | x1 + fields[7] = x2.to_u16! << 8 | x3 + end + + fields + end + + private def self.from_hex(ch : UInt8) + if 0x30 <= ch <= 0x39 + ch &- 0x30 + elsif 0x41 <= ch <= 0x46 + ch &- 0x37 + elsif 0x61 <= ch <= 0x66 + ch &- 0x57 + else + 0xFF_u8 + end + end + # Returns the IPv4 address with the given address *fields* and *port* # number. def self.v4(fields : UInt8[4], port : UInt16) : self @@ -258,24 +415,12 @@ class Socket # Returns `true` if *address* is a valid IPv6 address. def self.valid_v6?(address : String) : Bool - !address_v6?(address).nil? - end - - # :nodoc: - protected def self.address_v6?(address : String) - addr = uninitialized LibC::In6Addr - addr if LibC.inet_pton(LibC::AF_INET6, address, pointerof(addr)) == 1 + !parse_v6_fields?(address).nil? end # Returns `true` if *address* is a valid IPv4 address. def self.valid_v4?(address : String) : Bool - !address_v4?(address).nil? - end - - # :nodoc: - protected def self.address_v4?(address : String) - addr = uninitialized LibC::InAddr - addr if LibC.inet_pton(LibC::AF_INET, address, pointerof(addr)) == 1 + !parse_v4_fields?(address).nil? end # Returns a `String` representation of the IP address. @@ -287,6 +432,8 @@ class Socket # ``` getter(address : String) { address(@addr) } + protected setter address + private def address(addr : LibC::In6Addr) String.new(46) do |buffer| unless LibC.inet_ntop(family, pointerof(addr).as(Void*), buffer, 46) @@ -547,8 +694,6 @@ class Socket # Returns `true` if the string represents a valid IPv4 or IPv6 address. @[Deprecated("Use `IPAddress.valid?` instead")] def self.ip?(string : String) - addr = LibC::In6Addr.new - ptr = pointerof(addr).as(Void*) - LibC.inet_pton(LibC::AF_INET, string, ptr) > 0 || LibC.inet_pton(LibC::AF_INET6, string, ptr) > 0 + IPAddress.valid?(string) end end From e326e8f069be2a4a1a22bc3da38ebbb9e6d1f610 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 22 May 2023 18:51:35 +0800 Subject: [PATCH 0535/1551] Allow `File.delete` to remove read-only files on Windows (#13462) --- spec/std/file_spec.cr | 18 ++++++++++++++++-- spec/support/tempfile.cr | 22 ++-------------------- src/crystal/system/win32/file.cr | 10 ++++++++++ 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 190b718e217c..09731808e459 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -220,7 +220,7 @@ describe "File" do other = datapath("test_file.ini") with_tempfile("test_file_symlink.txt") do |symlink| - File.symlink(File.real_path(file), symlink) + File.symlink(File.realpath(file), symlink) File.same?(file, symlink).should be_false File.same?(file, symlink, follow_symlinks: true).should be_true @@ -514,7 +514,7 @@ describe "File" do end end - describe "delete" do + describe ".delete" do it "deletes a file" do with_tempfile("delete-file.txt") do |filename| File.open(filename, "w") { } @@ -533,6 +533,20 @@ describe "File" do end end + it "deletes a read-only file" do + with_tempfile("delete-file-dir") do |path| + Dir.mkdir(path) + File.chmod(path, 0o755) + + filename = File.join(path, "foo") + File.open(filename, "w") { } + File.exists?(filename).should be_true + File.chmod(filename, 0o000) + File.delete(filename) + File.exists?(filename).should be_false + end + end + it "deletes? a file" do with_tempfile("delete-file.txt") do |filename| File.open(filename, "w") { } diff --git a/spec/support/tempfile.cr b/spec/support/tempfile.cr index 43c7df1a2ae6..a5e0c57d010a 100644 --- a/spec/support/tempfile.cr +++ b/spec/support/tempfile.cr @@ -31,7 +31,7 @@ def with_tempfile(*paths, file = __FILE__, &) ensure if SPEC_TEMPFILE_CLEANUP paths.each do |path| - rm_rf(path) if File.exists?(path) + FileUtils.rm_rf(path) if File.exists?(path) end end end @@ -74,24 +74,6 @@ end if SPEC_TEMPFILE_CLEANUP at_exit do - rm_rf(SPEC_TEMPFILE_PATH) if Dir.exists?(SPEC_TEMPFILE_PATH) - end -end - -private def rm_rf(path : String) : Nil - if Dir.exists?(path) && !File.symlink?(path) - Dir.each_child(path) do |entry| - src = File.join(path, entry) - rm_rf(src) - end - Dir.delete(path) - else - begin - File.delete(path) - rescue File::AccessDeniedError - # To be able to delete read-only files (e.g. ones under .git/) on Windows. - File.chmod(path, 0o666) - File.delete(path) - end + FileUtils.rm_rf(SPEC_TEMPFILE_PATH) if Dir.exists?(SPEC_TEMPFILE_PATH) end end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 21930501eaf7..0dddf9d2a417 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -247,11 +247,21 @@ module Crystal::System::File return false end + # Windows cannot delete read-only files, so we unset the attribute here, but + # restore it afterwards if deletion still failed + read_only_removed = false + if attributes.bits_set?(LibC::FILE_ATTRIBUTE_READONLY) + if LibC.SetFileAttributesW(win_path, attributes & ~LibC::FILE_ATTRIBUTE_READONLY) != 0 + read_only_removed = true + end + end + # all reparse point directories should be deleted like a directory, not just # symbolic links, so we don't care about the reparse tag here is_reparse_dir = attributes.bits_set?(LibC::FILE_ATTRIBUTE_REPARSE_POINT) && attributes.bits_set?(LibC::FILE_ATTRIBUTE_DIRECTORY) result = is_reparse_dir ? LibC._wrmdir(win_path) : LibC._wunlink(win_path) return true if result == 0 + LibC.SetFileAttributesW(win_path, attributes) if read_only_removed raise ::File::Error.from_errno("Error deleting file", file: path) end From ef4076b762bd41ce2cb0d8792ba4fcf4724126da Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 22 May 2023 18:53:08 +0800 Subject: [PATCH 0536/1551] Windows: do not set `SO_EXCLUSIVEADDRUSE` if `SO_REUSEADDR` already present (#13477) --- src/crystal/system/win32/socket.cr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 058920441732..ec814e0d20ce 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -85,10 +85,10 @@ module Crystal::System::Socket end private def initialize_handle(handle) - value = 1_u8 - ret = LibC.setsockopt(handle, LibC::SOL_SOCKET, LibC::SO_EXCLUSIVEADDRUSE, pointerof(value), 1) - if ret == LibC::SOCKET_ERROR - raise ::Socket::Error.from_wsa_error("setsockopt") + system_getsockopt(handle, LibC::SO_REUSEADDR, 0) do |value| + if value == 0 + system_setsockopt(handle, LibC::SO_EXCLUSIVEADDRUSE, 1) + end end end From 8e4e5208f566182683936fed46f0dc811427246a Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 22 May 2023 10:23:43 -0500 Subject: [PATCH 0537/1551] Add print macro (#13336) Co-authored-by: Jack Thorne --- spec/compiler/macro/macro_methods_spec.cr | 9 +++++++++ src/compiler/crystal/macros.cr | 4 ++++ src/compiler/crystal/macros/methods.cr | 14 ++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 295a2548bab4..7ffc1ca62063 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -3161,6 +3161,15 @@ module Crystal end.should eq %(bar\n) end + it "print" do + String.build do |io| + assert_macro(%({% print foo %}), "") do |program| + program.stdout = io + {foo: "bar".string} + end + end.should eq %(bar) + end + it "p" do String.build do |io| assert_macro(%({% p foo %}), "") do |program| diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 97e5856c30f4..fcbe87249c2f 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -186,6 +186,10 @@ module Crystal::Macros def puts(*expressions) : Nop end + # Prints AST nodes at compile-time. Useful for debugging macros. + def print(*expressions) : Nop + end + # Same as `puts`. def p(*expressions) : Nop end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 484ca665862a..74021a7ce825 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -59,6 +59,8 @@ module Crystal interpret_parse_type(node) when "puts" interpret_puts(node) + when "print" + interpret_print(node) when "p", "pp" interpret_p(node) when "p!", "pp!" @@ -198,6 +200,18 @@ module Crystal @last = Nop.new end + def interpret_print(node) + node.args.each do |arg| + arg.accept self + last = @last + last = last.value if last.is_a?(StringLiteral) + + @program.stdout.print last + end + + @last = Nop.new + end + def interpret_p(node) node.args.each do |arg| arg.accept self From 3c43a9365a927a5ff6f3924dfb1b0492c0c41988 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 22 May 2023 23:24:02 +0800 Subject: [PATCH 0538/1551] Disable `Process.pgid` spec on Windows (#13476) --- spec/std/process_spec.cr | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index c881223eb92c..c2cb43235c02 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -392,14 +392,16 @@ describe Process do process.terminated?.should be_true end - pending_win32 ".pgid" do - process = Process.new(*standing_command) - Process.pgid(process.pid).should be_a(Int64) - process.terminate - Process.pgid.should eq(Process.pgid(Process.pid)) - ensure - process.try(&.wait) - end + {% unless flag?(:win32) %} + it ".pgid" do + process = Process.new(*standing_command) + Process.pgid(process.pid).should be_a(Int64) + process.terminate + Process.pgid.should eq(Process.pgid(Process.pid)) + ensure + process.try(&.wait) + end + {% end %} {% unless flag?(:preview_mt) || flag?(:win32) %} describe ".fork" do From 986e03b42cf6f566d00862b7455339387afc6351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Mon, 22 May 2023 08:24:19 -0700 Subject: [PATCH 0539/1551] Fix AST location of call name in operator assignment (#13456) --- spec/compiler/parser/parser_spec.cr | 6 ++++++ src/compiler/crystal/syntax/parser.cr | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index e6c1b17ea1ab..003129ecba54 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2514,6 +2514,12 @@ module Crystal source_between(source, node.name_location, node.name_end_location).should eq("foo") end + it "sets correct location of call name in operator assignment" do + source = "@foo.bar += 1" + node = Parser.parse(source).as(OpAssign).target.as(Call) + source_between(source, node.name_location, node.name_end_location).should eq("bar") + end + it "sets correct location of element in array literal" do source = "%i(foo bar)" elements = Parser.new(source).parse.as(ArrayLiteral).elements diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 7694ef2383bc..016d3d7e3027 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -698,7 +698,6 @@ module Crystal end check AtomicWithMethodCheck - name_location = @token.location if @token.value == Keyword::IS_A_QUESTION atomic = parse_is_a(atomic).at(location) @@ -722,6 +721,7 @@ module Crystal else @token.type.to_s end + name_location = @token.location end_location = token_end_location @wants_regex = false @@ -765,14 +765,14 @@ module Crystal atomic.name_location = name_location next when .assignment_operator? - name_location = @token.location + op_name_location = @token.location method = @token.type.to_s.byte_slice(0, @token.type.to_s.size - 1) next_token_skip_space_or_newline value = parse_op_assign call = Call.new(atomic, name).at(location) call.name_location = name_location atomic = OpAssign.new(call, method, value).at(location) - atomic.name_location = name_location + atomic.name_location = op_name_location next else call_args = preserve_stop_on_do { space_consumed ? parse_call_args_space_consumed : parse_call_args } From 0d6517c81f0ec5a6ccf7b031b0464a72311be4eb Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 23 May 2023 16:12:20 +0800 Subject: [PATCH 0540/1551] Add `.gitattributes` to repository (#13479) --- .gitattributes | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..78c53472cd74 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,28 @@ +## Sources + +*.cr text eol=lf + +## Sourced-in dependencies + +lib/** linguist-vendored + +## Generated files + +# produced by scripts/generate_windows_zone_names.cr +src/crystal/system/win32/zone_names.cr linguist-generated +# produced by scripts/generate_ssl_server_defaults.cr +src/openssl/ssl/defaults.cr linguist-generated +# produced by scripts/generate_grapheme_properties.cr +src/string/grapheme/properties.cr linguist-generated +# produced by scripts/generate_unicode_data.cr +src/unicode/data.cr linguist-generated +# produced by spec/generate_interpreter_spec.sh +spec/interpreter_std_spec.cr linguist-generated +# produced by scripts/generate_grapheme_break_specs.cr +spec/std/string/grapheme_break_spec.cr linguist-generated +# produced by spec/generate_wasm32_spec.sh +spec/wasm32_std_spec.cr linguist-generated + +## Syntax highlighting + +Makefile.win linguist-language=makefile From 5c4dc9c4ab7d8542ba2f4e8548fa32c3b65a3b90 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 23 May 2023 16:12:43 +0800 Subject: [PATCH 0541/1551] Print error if unable to delay-load DLL on Windows (#13475) --- src/crystal/system/win32/delay_load.cr | 99 +++++++------------ .../x86_64-windows-msvc/c/errhandlingapi.cr | 1 - src/lib_c/x86_64-windows-msvc/c/processenv.cr | 2 + .../c/processthreadsapi.cr | 1 + src/lib_c/x86_64-windows-msvc/c/winbase.cr | 4 + 5 files changed, 40 insertions(+), 67 deletions(-) diff --git a/src/crystal/system/win32/delay_load.cr b/src/crystal/system/win32/delay_load.cr index 37831d48e11c..3f596822e928 100644 --- a/src/crystal/system/win32/delay_load.cr +++ b/src/crystal/system/win32/delay_load.cr @@ -1,12 +1,5 @@ require "c/delayimp" -private ERROR_SEVERITY_ERROR = 0xC0000000_u32 -private FACILITY_VISUALCPP = 0x6d - -private macro vcpp_exception(err) - {{ ERROR_SEVERITY_ERROR | (FACILITY_VISUALCPP << 16) | WinError.constant(err.id) }} -end - lib LibC $image_base = __ImageBase : IMAGE_DOS_HEADER end @@ -15,6 +8,21 @@ private macro p_from_rva(rva) pointerof(LibC.image_base).as(UInt8*) + {{ rva }} end +private macro print_error(format, *args) + {% if args.empty? %} + %str = {{ format }} + LibC.WriteFile(LibC.GetStdHandle(LibC::STD_ERROR_HANDLE), %str, %str.bytesize, out _, nil) + {% else %} + %buf = uninitialized LibC::CHAR[1024] + %args = uninitialized Void*[{{ args.size }}] + {% for arg, i in args %} + %args[{{ i }}] = ({{ arg }}).as(Void*) + {% end %} + %len = LibC.FormatMessageA(LibC::FORMAT_MESSAGE_FROM_STRING | LibC::FORMAT_MESSAGE_ARGUMENT_ARRAY, {{ format }}, 0, 0, %buf, %buf.size, %args) + LibC.WriteFile(LibC.GetStdHandle(LibC::STD_ERROR_HANDLE), %buf, %len, out _, nil) + {% end %} +end + module Crystal::System::DelayLoad @[Extern] record InternalImgDelayDescr, @@ -52,21 +60,9 @@ end # that leads to an infinite recursion. In particular, if `preview_dll` is in # effect, `Crystal::System.print_error` will not work, because the C runtime # library DLLs are also delay-loaded and `LibC.snprintf` is unavailable. If you -# want print debugging inside this function, try the following: -# -# ``` -# lib LibC -# STD_OUTPUT_HANDLE = -11 -# -# fun GetStdHandle(nStdHandle : DWORD) : HANDLE -# fun FormatMessageA(dwFlags : DWORD, lpSource : Void*, dwMessageId : DWORD, dwLanguageId : DWORD, lpBuffer : LPSTR, nSize : DWORD, arguments : Void*) : DWORD -# end -# -# buf = uninitialized LibC::CHAR[512] -# args = StaticArray[dli.szDll, dli.dlp.union.szProcName] -# len = LibC.FormatMessageA(LibC::FORMAT_MESSAGE_FROM_STRING | LibC::FORMAT_MESSAGE_ARGUMENT_ARRAY, "Loading `%2` from `%1`\n", 0, 0, buf, buf.size, args) -# LibC.WriteFile(LibC.GetStdHandle(LibC::STD_OUTPUT_HANDLE), buf, len, out _, nil) -# ``` +# want print debugging inside this function, use the `print_error` macro +# instead. Note that its format string is passed to `LibC.FormatMessageA`, which +# uses different conventions from `LibC.printf`. # # `kernel32.dll` is the only DLL guaranteed to be available. It cannot be # delay-loaded and the Crystal compiler excludes it from the linker arguments. @@ -100,16 +96,9 @@ fun __delayLoadHelper2(pidd : LibC::ImgDelayDescr*, ppfnIATEntry : LibC::FARPROC ) if 0 == idd.grAttrs & LibC::DLAttrRva - rgpdli = pointerof(dli) - # DloadReleaseSectionWriteAccess - - LibC.RaiseException( - vcpp_exception(ERROR_INVALID_PARAMETER), - 0, - 1, - pointerof(rgpdli).as(LibC::ULONG_PTR*), - ) + print_error("FATAL: Delay load descriptor does not support RVAs\n") + LibC.ExitProcess(1) end hmod = idd.phmod.value @@ -122,11 +111,12 @@ fun __delayLoadHelper2(pidd : LibC::ImgDelayDescr*, ppfnIATEntry : LibC::FARPROC pitd = idd.pINT + iINT - dli.dlp.fImportByName = pitd.value.u1.ordinal & LibC::IMAGE_ORDINAL_FLAG == 0 + import_by_name = (pitd.value.u1.ordinal & LibC::IMAGE_ORDINAL_FLAG) == 0 + dli.dlp.fImportByName = import_by_name ? 1 : 0 - if dli.dlp.fImportByName - import_by_name = p_from_rva(LibC::RVA.new!(pitd.value.u1.addressOfData)) - dli.dlp.union.szProcName = import_by_name + offsetof(LibC::IMAGE_IMPORT_BY_NAME, @name) + if import_by_name + image_import_by_name = p_from_rva(LibC::RVA.new!(pitd.value.u1.addressOfData)) + dli.dlp.union.szProcName = image_import_by_name + offsetof(LibC::IMAGE_IMPORT_BY_NAME, @name) else dli.dlp.union.dwOrdinal = LibC::DWORD.new!(pitd.value.u1.ordinal & 0xFFFF) end @@ -135,22 +125,9 @@ fun __delayLoadHelper2(pidd : LibC::ImgDelayDescr*, ppfnIATEntry : LibC::FARPROC if !hmod # note: ANSI variant used here unless hmod = LibC.LoadLibraryExA(dli.szDll, nil, 0) - dli.dwLastError = LibC.GetLastError - - rgpdli = pointerof(dli) - # DloadReleaseSectionWriteAccess - LibC.RaiseException( - vcpp_exception(ERROR_MOD_NOT_FOUND), - 0, - 1, - pointerof(rgpdli).as(LibC::ULONG_PTR*), - ) - - # If we get to here, we blindly assume that the handler of the exception - # has magically fixed everything up and left the function pointer in - # dli.pfnCur. - return dli.pfnCur + print_error("FATAL: Cannot find the DLL named `%1`, exiting\n", dli.szDll) + LibC.ExitProcess(1) end # Store the library handle. If it is already there, we infer @@ -180,23 +157,13 @@ fun __delayLoadHelper2(pidd : LibC::ImgDelayDescr*, ppfnIATEntry : LibC::FARPROC end unless pfnRet = LibC.GetProcAddress(hmod, dli.dlp.union.szProcName) - dli.dwLastError = LibC.GetLastError - - rgpdli = pointerof(dli) - # DloadReleaseSectionWriteAccess - LibC.RaiseException( - vcpp_exception(ERROR_PROC_NOT_FOUND), - 0, - 1, - pointerof(rgpdli).as(LibC::ULONG_PTR*), - ) - # DloadAcquireSectionWriteAccess - - # If we get to here, we blindly assume that the handler of the exception - # has magically fixed everything up and left the function pointer in - # dli.pfnCur. - pfnRet = dli.pfnCur + if import_by_name + print_error("FATAL: Cannot find the symbol named `%1` within `%2`, exiting\n", dli.dlp.union.szProcName, dli.szDll) + else + print_error("FATAL: Cannot find the symbol with the ordinal #%1!u! within `%2`, exiting\n", Pointer(Void).new(dli.dlp.union.dwOrdinal), dli.szDll) + end + LibC.ExitProcess(1) end ppfnIATEntry.value = pfnRet diff --git a/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr b/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr index 5ac7f9feb7c4..0d209f7d6773 100644 --- a/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/errhandlingapi.cr @@ -11,6 +11,5 @@ lib LibC fun GetLastError : DWORD fun SetLastError(dwErrCode : DWORD) - fun RaiseException(dwExceptionCode : DWORD, dwExceptionFlags : DWORD, nNumberOfArguments : DWORD, lpArguments : ULONG_PTR*) fun AddVectoredExceptionHandler(first : DWORD, handler : PVECTORED_EXCEPTION_HANDLER) : Void* end diff --git a/src/lib_c/x86_64-windows-msvc/c/processenv.cr b/src/lib_c/x86_64-windows-msvc/c/processenv.cr index 06155af8872e..1928020df864 100644 --- a/src/lib_c/x86_64-windows-msvc/c/processenv.cr +++ b/src/lib_c/x86_64-windows-msvc/c/processenv.cr @@ -2,6 +2,8 @@ require "c/winnt" @[Link("kernel32")] lib LibC + fun GetStdHandle(nStdHandle : DWORD) : HANDLE + fun GetCurrentDirectoryW(nBufferLength : DWORD, lpBuffer : LPWSTR) : DWORD fun SetCurrentDirectoryW(lpPathname : LPWSTR) : BOOL diff --git a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr index 6ea5281b299e..9c22d4530c81 100644 --- a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr @@ -43,6 +43,7 @@ lib LibC fun GetCurrentProcessId : DWORD fun OpenProcess(dwDesiredAccess : DWORD, bInheritHandle : BOOL, dwProcessId : DWORD) : HANDLE fun GetExitCodeProcess(hProcess : HANDLE, lpExitCode : DWORD*) : BOOL + fun ExitProcess(uExitCode : UInt) : NoReturn fun TerminateProcess(hProcess : HANDLE, uExitCode : UInt) : BOOL fun CreateProcessW(lpApplicationName : LPWSTR, lpCommandLine : LPWSTR, lpProcessAttributes : SECURITY_ATTRIBUTES*, lpThreadAttributes : SECURITY_ATTRIBUTES*, diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index 406fe65003cf..96ca0710bc2d 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -12,6 +12,10 @@ lib LibC FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000_u32 FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF_u32 + STD_ERROR_HANDLE = DWORD.new!(-12) + + fun FormatMessageA(dwFlags : DWORD, lpSource : Void*, dwMessageId : DWORD, dwLanguageId : DWORD, + lpBuffer : LPSTR, nSize : DWORD, arguments : Void*) : DWORD fun FormatMessageW(dwFlags : DWORD, lpSource : Void*, dwMessageId : DWORD, dwLanguageId : DWORD, lpBuffer : LPWSTR, nSize : DWORD, arguments : Void*) : DWORD From f690598a79e9fc4e40c856823653cdad6953d729 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 23 May 2023 16:12:58 +0800 Subject: [PATCH 0542/1551] Support `-static` and `-dynamic` `.lib` suffixes on Windows (#13473) --- src/compiler/crystal/compiler.cr | 31 ++++++++--- src/compiler/crystal/loader/msvc.cr | 57 ++++++++++++++++----- src/crystal/system/win32/library_archive.cr | 7 ++- 3 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index af573e817a3d..9d8e3b156975 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -366,14 +366,29 @@ module Crystal @link_flags.try { |flags| link_args << flags } {% if flag?(:msvc) %} - if program.has_flag?("preview_dll") && !program.has_flag?("no_win32_delay_load") - # "LINK : warning LNK4199: /DELAYLOAD:foo.dll ignored; no imports found from foo.dll" - # it is harmless to skip this error because not all import libraries are always used, much - # less the individual DLLs they refer to - link_args << "/IGNORE:4199" - - Loader.search_dlls(Process.parse_arguments_windows(link_args.join(' '))).each do |dll| - link_args << "/DELAYLOAD:#{dll}" + unless @cross_compile + extra_suffix = program.has_flag?("preview_dll") ? "-dynamic" : "-static" + search_result = Loader.search_libraries(Process.parse_arguments_windows(link_args.join(' ').gsub('\n', ' ')), extra_suffix: extra_suffix) + if not_found = search_result.not_found? + error "Cannot locate the .lib files for the following libraries: #{not_found.join(", ")}" + end + + link_args = search_result.remaining_args.concat(search_result.library_paths).map { |arg| Process.quote_windows(arg) } + + if !program.has_flag?("no_win32_delay_load") + # "LINK : warning LNK4199: /DELAYLOAD:foo.dll ignored; no imports found from foo.dll" + # it is harmless to skip this error because not all import libraries are always used, much + # less the individual DLLs they refer to + link_args << "/IGNORE:4199" + + dlls = Set(String).new + search_result.library_paths.each do |library_path| + Crystal::System::LibraryArchive.imported_dlls(library_path).each do |dll| + dlls << dll.downcase + end + end + dlls.delete "kernel32.dll" + dlls.each { |dll| link_args << "/DELAYLOAD:#{dll}" } end end {% end %} diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index 54c3bd5d41af..9f9a592477f2 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -32,28 +32,53 @@ class Crystal::Loader end end - # Returns the list of DLLs imported from the libraries specified in the given - # linker arguments. Used by the compiler for delay-loaded DLL support. - def self.search_dlls(args : Array(String), *, search_paths : Array(String) = default_search_paths) : Set(String) - search_paths, libnames = parse_args(args, search_paths) - dlls = Set(String).new + struct SearchLibResult + getter library_paths = [] of String + getter remaining_args = [] of String + getter(not_found) { [] of String } + + def not_found? + @not_found + end + end + + # Extracts the command-line arguments from *args* that add libraries and + # expands them to their absolute paths. Returns a `SearchLibResult` with those + # expanded paths, plus unused arguments and libraries that were not found. + def self.search_libraries(args : Array(String), *, search_paths : Array(String) = default_search_paths, extra_suffix : String? = nil) : SearchLibResult + result = SearchLibResult.new + search_paths, libnames = parse_args(args, search_paths, remaining: result.remaining_args) libnames.each do |libname| - search_paths.each do |directory| - library_path = File.join(directory, library_filename(libname)) - next unless File.file?(library_path) + if found_path = search_library(libname, search_paths, extra_suffix) + result.library_paths << found_path + else + result.not_found << libname + end + end + + result + end - Crystal::System::LibraryArchive.imported_dlls(library_path).each do |dll| - dlls << dll unless dll.compare("kernel32.dll", case_insensitive: true).zero? + private def self.search_library(libname, search_paths, extra_suffix) + if ::Path::SEPARATORS.any? { |separator| libname.includes?(separator) } + libname = File.expand_path(libname) + library_path = library_filename(libname) + return library_path if File.file?(library_path) + else + search_paths.each do |directory| + if extra_suffix + library_path = File.join(directory, library_filename(libname + extra_suffix)) + return library_path if File.file?(library_path) end - break + + library_path = File.join(directory, library_filename(libname)) + return library_path if File.file?(library_path) end end - - dlls end - private def self.parse_args(args, search_paths) + def self.parse_args(args, search_paths, *, remaining = nil) libnames = [] of String # NOTE: `/LIBPATH`s are prepended before the default paths: @@ -68,10 +93,14 @@ class Crystal::Loader extra_search_paths << lib_path elsif !arg.starts_with?('/') && (name = arg.rchop?(".lib")) libnames << name + elsif remaining + remaining << arg end end search_paths = extra_search_paths + search_paths + search_paths.uniq! &.downcase + libnames.uniq! &.downcase {search_paths, libnames} end diff --git a/src/crystal/system/win32/library_archive.cr b/src/crystal/system/win32/library_archive.cr index a1ea8c275443..e1487fb907d9 100644 --- a/src/crystal/system/win32/library_archive.cr +++ b/src/crystal/system/win32/library_archive.cr @@ -70,8 +70,11 @@ module Crystal::System::LibraryArchive sig2 = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian) return unless sig2 == 0xFFFF - # version(2) + machine(2) + time(4) + size(4) + ordinal/hint(2) + flags(2) - io.skip(16) + version = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian) + return unless version == 0 # 1 and 2 are used by object files (ANON_OBJECT_HEADER) + + # machine(2) + time(4) + size(4) + ordinal/hint(2) + flags(2) + io.skip(14) # TODO: is there a way to do this without constructing a temporary string, # but with the optimizations present in `IO#gets`? From fafcde5d38df80f5bda55dbac2ffdc46b1525b59 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 May 2023 00:25:41 +0800 Subject: [PATCH 0543/1551] Fix `LibM.hypotf` and `ldexpf` link errors on Windows (#13485) --- src/math/libm.cr | 10 ++++++++-- src/math/math.cr | 9 +++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/math/libm.cr b/src/math/libm.cr index a7a7e3f16b91..8130a8ac31a2 100644 --- a/src/math/libm.cr +++ b/src/math/libm.cr @@ -180,11 +180,17 @@ lib LibM fun frexp_f64 = frexp(value : Float64, exp : Int32*) : Float64 fun gamma_f32 = lgammaf(value : Float32) : Float32 fun gamma_f64 = lgamma(value : Float64) : Float64 - fun hypot_f32 = hypotf(value1 : Float32, value2 : Float32) : Float32 + {% if flag?(:win32) %} + fun hypot_f32 = _hypotf(value1 : Float32, value2 : Float32) : Float32 + {% else %} + fun hypot_f32 = hypotf(value1 : Float32, value2 : Float32) : Float32 + {% end %} fun hypot_f64 = hypot(value1 : Float64, value2 : Float64) : Float64 fun ilogb_f32 = ilogbf(value : Float32) : Int32 fun ilogb_f64 = ilogb(value : Float64) : Int32 - fun ldexp_f32 = ldexpf(value1 : Float32, value2 : Int32) : Float32 + {% unless flag?(:win32) %} + fun ldexp_f32 = ldexpf(value1 : Float32, value2 : Int32) : Float32 + {% end %} fun ldexp_f64 = ldexp(value1 : Float64, value2 : Int32) : Float64 fun log1p_f32 = log1pf(value : Float32) : Float32 fun log1p_f64 = log1p(value : Float64) : Float64 diff --git a/src/math/math.cr b/src/math/math.cr index 6693e6a29a19..f29eeab386e4 100644 --- a/src/math/math.cr +++ b/src/math/math.cr @@ -609,7 +609,12 @@ module Math # Multiplies the given floating-point *value* by 2 raised to the power *exp*. def ldexp(value : Float32, exp : Int32) : Float32 - LibM.ldexp_f32(value, exp) + {% if flag?(:win32) %} + # ucrt does not export `ldexpf` and instead defines it like this + LibM.ldexp_f64(value, exp).to_f32! + {% else %} + LibM.ldexp_f32(value, exp) + {% end %} end # :ditto: @@ -657,7 +662,7 @@ module Math # Decomposes the given floating-point *value* into a normalized fraction and an integral power of two. def frexp(value : Float32) : {Float32, Int32} {% if flag?(:win32) %} - # libucrt does not export `frexpf` and instead defines it like this + # ucrt does not export `frexpf` and instead defines it like this frac = LibM.frexp_f64(value, out exp) {frac.to_f32, exp} {% else %} From da4b1299494fd6ebc927ebe99f385c45abb89c77 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 May 2023 00:25:54 +0800 Subject: [PATCH 0544/1551] Use per-thread libxml2 global state on Windows (#13486) --- src/xml.cr | 30 ++++++++++++++++++++++++++++++ src/xml/libxml2.cr | 15 +++++++++++++-- src/xml/node.cr | 41 ++++++++++++++++++----------------------- 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/src/xml.cr b/src/xml.cr index 1f0085fcd063..05a1e3d41891 100644 --- a/src/xml.cr +++ b/src/xml.cr @@ -105,6 +105,36 @@ module XML Node.new(doc, errors) end + + protected def self.with_indent_tree_output(indent : Bool, &) + ptr = {% if flag?(:win32) %} + LibXML.__xmlIndentTreeOutput + {% else %} + pointerof(LibXML.xmlIndentTreeOutput) + {% end %} + + old, ptr.value = ptr.value, indent ? 1 : 0 + begin + yield + ensure + ptr.value = old + end + end + + protected def self.with_tree_indent_string(string : String, &) + ptr = {% if flag?(:win32) %} + LibXML.__xmlTreeIndentString + {% else %} + pointerof(LibXML.xmlTreeIndentString) + {% end %} + + old, ptr.value = ptr.value, string.to_unsafe + begin + yield + ensure + ptr.value = old + end + end end require "./xml/*" diff --git a/src/xml/libxml2.cr b/src/xml/libxml2.cr index f2e4574e94f1..68c9943425d2 100644 --- a/src/xml/libxml2.cr +++ b/src/xml/libxml2.cr @@ -8,8 +8,15 @@ require "./save_options" lib LibXML alias Int = LibC::Int - $xmlIndentTreeOutput : Int - $xmlTreeIndentString : UInt8* + # TODO: check if other platforms also support per-thread globals + {% if flag?(:win32) %} + fun xmlInitParser + fun __xmlIndentTreeOutput : Int* + fun __xmlTreeIndentString : UInt8** + {% else %} + $xmlIndentTreeOutput : Int + $xmlTreeIndentString : UInt8* + {% end %} type Dtd = Void* type Dict = Void* @@ -314,6 +321,10 @@ lib LibXML fun xmlValidateNameValue(value : UInt8*) : Int end +{% if flag?(:win32) %} + LibXML.xmlInitParser +{% end %} + LibXML.xmlGcMemSetup( ->GC.free, ->GC.malloc(LibC::SizeT), diff --git a/src/xml/node.cr b/src/xml/node.cr index 89451839dc85..45878e762b0f 100644 --- a/src/xml/node.cr +++ b/src/xml/node.cr @@ -440,29 +440,24 @@ class XML::Node def to_xml(io : IO, indent = 2, indent_text = " ", options : SaveOptions = SaveOptions.xml_default) # We need to use a mutex because we modify global libxml variables SAVE_MUTEX.synchronize do - oldXmlIndentTreeOutput = LibXML.xmlIndentTreeOutput - LibXML.xmlIndentTreeOutput = 1 - - oldXmlTreeIndentString = LibXML.xmlTreeIndentString - LibXML.xmlTreeIndentString = (indent_text * indent).to_unsafe - - save_ctx = LibXML.xmlSaveToIO( - ->(ctx, buffer, len) { - Box(IO).unbox(ctx).write_string Slice.new(buffer, len) - len - }, - ->(ctx) { - Box(IO).unbox(ctx).flush - 0 - }, - Box(IO).box(io), - @node.value.doc.value.encoding, - options) - LibXML.xmlSaveTree(save_ctx, self) - LibXML.xmlSaveClose(save_ctx) - - LibXML.xmlIndentTreeOutput = oldXmlIndentTreeOutput - LibXML.xmlTreeIndentString = oldXmlTreeIndentString + XML.with_indent_tree_output(true) do + XML.with_tree_indent_string(indent_text * indent) do + save_ctx = LibXML.xmlSaveToIO( + ->(ctx, buffer, len) { + Box(IO).unbox(ctx).write_string Slice.new(buffer, len) + len + }, + ->(ctx) { + Box(IO).unbox(ctx).flush + 0 + }, + Box(IO).box(io), + @node.value.doc.value.encoding, + options) + LibXML.xmlSaveTree(save_ctx, self) + LibXML.xmlSaveClose(save_ctx) + end + end end io From 87f32d13f21fa647588fe21732198bdc158c117a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 May 2023 00:26:06 +0800 Subject: [PATCH 0545/1551] Implement `Socket::IPAddress#to_s` with Crystal instead of `LibC.inet_ntop` (#13483) --- spec/std/socket/address_spec.cr | 52 ++++++++-- src/socket/address.cr | 174 +++++++++++++++++++++++++++----- 2 files changed, 190 insertions(+), 36 deletions(-) diff --git a/spec/std/socket/address_spec.cr b/spec/std/socket/address_spec.cr index a354d4216ce6..89da5943a859 100644 --- a/spec/std/socket/address_spec.cr +++ b/spec/std/socket/address_spec.cr @@ -1,6 +1,7 @@ require "spec" require "socket" require "../../support/win32" +require "../../support/string" describe Socket::Address do describe ".parse" do @@ -81,9 +82,46 @@ describe Socket::IPAddress do end end - it "to_s" do - Socket::IPAddress.new("127.0.0.1", 80).to_s.should eq("127.0.0.1:80") - Socket::IPAddress.new("2001:db8:8714:3a90::12", 443).to_s.should eq("[2001:db8:8714:3a90::12]:443") + it "#to_s" do + assert_prints Socket::IPAddress.v4(UInt8.static_array(127, 0, 0, 1), 80).to_s, "127.0.0.1:80" + + assert_prints Socket::IPAddress.v6(UInt16.static_array(0x2001, 0xdb8, 0x8714, 0x3a90, 0, 0, 0, 0x12), 443).to_s, "[2001:db8:8714:3a90::12]:443" + assert_prints Socket::IPAddress.v6(UInt16.static_array(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff), 0xffff).to_s, "[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535" + assert_prints Socket::IPAddress.v6(UInt16.static_array(0x2001, 0xdb8, 0, 1, 1, 1, 1, 1), 443).to_s, "[2001:db8:0:1:1:1:1:1]:443" + assert_prints Socket::IPAddress.v6(UInt16.static_array(0x2001, 0, 0, 1, 0, 0, 0, 1), 443).to_s, "[2001:0:0:1::1]:443" + assert_prints Socket::IPAddress.v6(UInt16.static_array(0x2001, 0, 0, 0, 1, 0, 0, 1), 443).to_s, "[2001::1:0:0:1]:443" + assert_prints Socket::IPAddress.v6(UInt16.static_array(0x2001, 0xdb8, 0, 0, 1, 0, 0, 1), 443).to_s, "[2001:db8::1:0:0:1]:443" + assert_prints Socket::IPAddress.v6(UInt16.static_array(0, 0, 0, 0, 0, 0, 0, 1), 443).to_s, "[::1]:443" + assert_prints Socket::IPAddress.v6(UInt16.static_array(1, 0, 0, 0, 0, 0, 0, 0), 443).to_s, "[1::]:443" + assert_prints Socket::IPAddress.v6(UInt16.static_array(0, 0, 0, 0, 0, 0, 0, 0), 443).to_s, "[::]:443" + + assert_prints Socket::IPAddress.v4_mapped_v6(UInt8.static_array(0, 0, 0, 0), 443).to_s, "[::ffff:0.0.0.0]:443" + assert_prints Socket::IPAddress.v4_mapped_v6(UInt8.static_array(192, 168, 1, 15), 443).to_s, "[::ffff:192.168.1.15]:443" + + assert_prints Socket::IPAddress.new("0:0:0:0:0:0:0:1", 443).to_s, "[::1]:443" + assert_prints Socket::IPAddress.new("0:0:0:0:0:ffff:c0a8:010f", 443).to_s, "[::ffff:192.168.1.15]:443" + assert_prints Socket::IPAddress.new("::ffff:0:0", 443).to_s, "[::ffff:0.0.0.0]:443" + end + + it "#address" do + Socket::IPAddress.v4(UInt8.static_array(127, 0, 0, 1), 80).address.should eq "127.0.0.1" + + Socket::IPAddress.v6(UInt16.static_array(0x2001, 0xdb8, 0x8714, 0x3a90, 0, 0, 0, 0x12), 443).address.should eq "2001:db8:8714:3a90::12" + Socket::IPAddress.v6(UInt16.static_array(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff), 0xffff).address.should eq "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" + Socket::IPAddress.v6(UInt16.static_array(0x2001, 0xdb8, 0, 1, 1, 1, 1, 1), 443).address.should eq "2001:db8:0:1:1:1:1:1" + Socket::IPAddress.v6(UInt16.static_array(0x2001, 0, 0, 1, 0, 0, 0, 1), 443).address.should eq "2001:0:0:1::1" + Socket::IPAddress.v6(UInt16.static_array(0x2001, 0, 0, 0, 1, 0, 0, 1), 443).address.should eq "2001::1:0:0:1" + Socket::IPAddress.v6(UInt16.static_array(0x2001, 0xdb8, 0, 0, 1, 0, 0, 1), 443).address.should eq "2001:db8::1:0:0:1" + Socket::IPAddress.v6(UInt16.static_array(0, 0, 0, 0, 0, 0, 0, 1), 443).address.should eq "::1" + Socket::IPAddress.v6(UInt16.static_array(1, 0, 0, 0, 0, 0, 0, 0), 443).address.should eq "1::" + Socket::IPAddress.v6(UInt16.static_array(0, 0, 0, 0, 0, 0, 0, 0), 443).address.should eq "::" + + Socket::IPAddress.v4_mapped_v6(UInt8.static_array(0, 0, 0, 0), 443).address.should eq "::ffff:0.0.0.0" + Socket::IPAddress.v4_mapped_v6(UInt8.static_array(192, 168, 1, 15), 443).address.should eq "::ffff:192.168.1.15" + + Socket::IPAddress.new("0:0:0:0:0:0:0:1", 443).address.should eq "::1" + Socket::IPAddress.new("0:0:0:0:0:ffff:c0a8:010f", 443).address.should eq "::ffff:192.168.1.15" + Socket::IPAddress.new("::ffff:0:0", 443).address.should eq "::ffff:0.0.0.0" end describe ".parse" do @@ -260,9 +298,7 @@ describe Socket::IPAddress do describe ".v4_mapped_v6" do it "constructs an IPv4-mapped IPv6 address" do - # windows returns the former which, while correct, is not canonical - # TODO: implement also `#to_s` in Crystal - {Socket::IPAddress.new("::ffff:0:0", 0), Socket::IPAddress.new("::ffff:0.0.0.0", 0)}.should contain Socket::IPAddress.v4_mapped_v6(0, 0, 0, 0, port: 0) + Socket::IPAddress.v4_mapped_v6(0, 0, 0, 0, port: 0).should eq Socket::IPAddress.new("::ffff:0.0.0.0", 0) Socket::IPAddress.v4_mapped_v6(127, 0, 0, 1, port: 1234).should eq Socket::IPAddress.new("::ffff:127.0.0.1", 1234) Socket::IPAddress.v4_mapped_v6(192, 168, 0, 1, port: 8081).should eq Socket::IPAddress.new("::ffff:192.168.0.1", 8081) Socket::IPAddress.v4_mapped_v6(255, 255, 255, 254, port: 65535).should eq Socket::IPAddress.new("::ffff:255.255.255.254", 65535) @@ -285,9 +321,7 @@ describe Socket::IPAddress do end it "constructs from StaticArray" do - # windows returns the former which, while correct, is not canonical - # TODO: implement also `#to_s` in Crystal - {Socket::IPAddress.new("::ffff:0:0", 0), Socket::IPAddress.new("::ffff:0.0.0.0", 0)}.should contain Socket::IPAddress.v4_mapped_v6(UInt8.static_array(0, 0, 0, 0), 0) + Socket::IPAddress.v4_mapped_v6(UInt8.static_array(0, 0, 0, 0), 0).should eq Socket::IPAddress.new("::ffff:0.0.0.0", 0) Socket::IPAddress.v4_mapped_v6(UInt8.static_array(127, 0, 0, 1), 1234).should eq Socket::IPAddress.new("::ffff:127.0.0.1", 1234) Socket::IPAddress.v4_mapped_v6(UInt8.static_array(192, 168, 0, 1), 8081).should eq Socket::IPAddress.new("::ffff:192.168.0.1", 8081) Socket::IPAddress.v4_mapped_v6(UInt8.static_array(255, 255, 255, 254), 65535).should eq Socket::IPAddress.new("::ffff:255.255.255.254", 65535) diff --git a/src/socket/address.cr b/src/socket/address.cr index 0466c9bfef7c..2904657e03f4 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -97,8 +97,6 @@ class Socket raise Error.new("Invalid IP address: #{address}") end - # TODO: canonicalize (#13423) - addr.address = address addr end @@ -423,35 +421,49 @@ class Socket !parse_v4_fields?(address).nil? end - # Returns a `String` representation of the IP address. + # Returns a `String` representation of the IP address, without the port + # number. + # + # IPv6 addresses are canonicalized according to + # [RFC 5952, section 4](https://datatracker.ietf.org/doc/html/rfc5952#section-4). + # IPv4-mapped IPv6 addresses use the mixed notation according to RFC 5952, + # section 5. # - # Example: # ``` - # ip_address = socket.remote_address - # ip_address.address # => "127.0.0.1" + # require "socket" + # + # v4 = Socket::IPAddress.v4(UInt8.static_array(127, 0, 0, 1), 8080) + # v4.address # => "127.0.0.1" + # + # v6 = Socket::IPAddress.v6(UInt16.static_array(0x2001, 0xdb8, 0, 0, 1, 0, 0, 1), 443) + # v6.address # => "2001:db8::1:0:0:1" + # + # mapped = Socket::IPAddress.v4_mapped_v6(UInt8.static_array(192, 168, 1, 15), 55001) + # mapped.address # => "::ffff:192.168.1.15" # ``` - getter(address : String) { address(@addr) } - - protected setter address - - private def address(addr : LibC::In6Addr) - String.new(46) do |buffer| - unless LibC.inet_ntop(family, pointerof(addr).as(Void*), buffer, 46) - raise Socket::Error.from_errno("Failed to convert IP address") + # + # To obtain both the address and the port number in one string, see `#to_s`. + def address : String + case addr = @addr + in LibC::InAddr + String.build(IPV4_MAX_SIZE) do |io| + address_to_s(io, addr) end - {LibC.strlen(buffer), 0} - end - end - - private def address(addr : LibC::InAddr) - String.new(16) do |buffer| - unless LibC.inet_ntop(family, pointerof(addr).as(Void*), buffer, 16) - raise Socket::Error.from_errno("Failed to convert IP address") + in LibC::In6Addr + String.build(IPV6_MAX_SIZE) do |io| + address_to_s(io, addr) end - {LibC.strlen(buffer), 0} end end + private IPV4_MAX_SIZE = 15 # "255.255.255.255".size + + # NOTE: INET6_ADDRSTRLEN is 46 bytes (including the terminating null + # character), but it is only attainable for mixed-notation addresses that + # use all 24 hexadecimal digits in the IPv6 field part, which we do not + # support + private IPV6_MAX_SIZE = 39 # "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff".size + # Returns `true` if this IP is a loopback address. # # In the IPv4 family, loopback addresses are all addresses in the subnet @@ -523,13 +535,114 @@ class Socket {% end %} end - def_equals_and_hash family, port, address + def_equals_and_hash family, port, address_value + protected def address_value + case addr = @addr + in LibC::InAddr + addr.s_addr + in LibC::In6Addr + ipv6_addr8(addr).unsafe_as(UInt128) + end + end + + # Writes the `String` representation of the IP address plus the port number + # to the given *io*. + # + # IPv6 addresses are canonicalized according to + # [RFC 5952, section 4](https://datatracker.ietf.org/doc/html/rfc5952#section-4), + # and surrounded within a pair of square brackets according to + # [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986). + # IPv4-mapped IPv6 addresses use the mixed notation according to RFC 5952, + # section 5. + # + # To obtain the address alone without the port number, see `#address`. def to_s(io : IO) : Nil - if family == Family::INET6 - io << '[' << address << ']' << ':' << port + case addr = @addr + in LibC::InAddr + address_to_s(io, addr) + io << ':' << port + in LibC::In6Addr + io << '[' + address_to_s(io, addr) + io << ']' << ':' << port + end + end + + private def address_to_s(io : IO, addr : LibC::InAddr) + io << (addr.s_addr & 0xFF) + io << '.' << (addr.s_addr >> 8 & 0xFF) + io << '.' << (addr.s_addr >> 16 & 0xFF) + io << '.' << (addr.s_addr >> 24) + end + + private def address_to_s(io : IO, addr : LibC::In6Addr) + bytes = ipv6_addr8(addr) + if Slice.new(bytes.to_unsafe, 10).all?(&.zero?) && bytes[10] == 0xFF && bytes[11] == 0xFF + io << "::ffff:" << bytes[12] << '.' << bytes[13] << '.' << bytes[14] << '.' << bytes[15] else - io << address << ':' << port + fields = bytes.unsafe_as(StaticArray(UInt16, 8)).map! { |field| IPAddress.endian_swap(field) } + + zeros_start = nil + zeros_count_max = 1 + + count = 0 + fields.each_with_index do |field, i| + if field == 0 + count += 1 + if count > zeros_count_max + zeros_start = i - count + 1 + zeros_count_max = count + end + else + count = 0 + end + end + + i = 0 + while i < 8 + if i == zeros_start + io << ':' + i += zeros_count_max + io << ':' if i == 8 + else + io << ':' if i > 0 + fields[i].to_s(io, base: 16) + i += 1 + end + end + end + end + + private IPV4_FULL_MAX_SIZE = IPV4_MAX_SIZE + 6 # ":65535".size + private IPV6_FULL_MAX_SIZE = IPV6_MAX_SIZE + 8 # "[".size + "]:65535".size + + # Returns a `String` representation of the IP address plus the port number. + # + # IPv6 addresses are canonicalized according to + # [RFC 5952, section 4](https://datatracker.ietf.org/doc/html/rfc5952#section-4), + # and surrounded within a pair of square brackets according to + # [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986). + # IPv4-mapped IPv6 addresses use the mixed notation according to RFC 5952, + # section 5. + # + # ``` + # require "socket" + # + # v4 = Socket::IPAddress.v4(UInt8.static_array(127, 0, 0, 1), 8080) + # v4.to_s # => "127.0.0.1:8080" + # + # v6 = Socket::IPAddress.v6(UInt16.static_array(0x2001, 0xdb8, 0, 0, 1, 0, 0, 1), 443) + # v6.to_s # => "[2001:db8::1:0:0:1]:443" + # + # mapped = Socket::IPAddress.v4_mapped_v6(UInt8.static_array(192, 168, 1, 15), 55001) + # mapped.to_s # => "[::ffff:192.168.1.15]:55001" + # ``` + # + # To obtain the address alone without the port number, see `#address`. + def to_s : String + String.build(@addr.is_a?(LibC::InAddr) ? IPV4_FULL_MAX_SIZE : IPV6_FULL_MAX_SIZE) do |io| + to_s(io) end end @@ -539,6 +652,13 @@ class Socket io << ")" end + def inspect : String + # 19 == "Socket::IPAddress(".size + ")".size + String.build((@addr.is_a?(LibC::InAddr) ? IPV4_FULL_MAX_SIZE : IPV6_FULL_MAX_SIZE) + 19) do |io| + inspect(io) + end + end + def pretty_print(pp) pp.text inspect end From fdc1e051d45e474eb3409423f6bc66f0b3ae6170 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 May 2023 03:05:55 +0800 Subject: [PATCH 0546/1551] Deprecate `LLVM::Module#write_bitcode_with_summary_to_file` (#13488) --- man/crystal.1 | 2 -- src/llvm/ext/llvm_ext.cc | 20 -------------------- src/llvm/lib_llvm_ext.cr | 2 -- src/llvm/module.cr | 3 ++- 4 files changed, 2 insertions(+), 25 deletions(-) diff --git a/man/crystal.1 b/man/crystal.1 index 35c8b282f38f..3e60eb27aff0 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -113,8 +113,6 @@ Generate the output with symbolic debug symbols. These are read when debugging the built program with tools like lldb, gdb, valgrind etc. and provide mappings to the original source code for those tools. .It Fl -no-debug Generate the output without any symbolic debug symbols. -.It Fl -lto Ar FLAG -Use ThinLTO --lto=thin. .It Fl D Ar FLAG, Fl -define Ar FLAG Define a compile-time flag. This is useful to conditionally define types, methods, or commands based on flags available at compile time. The default flags are from the target triple given with --target-triple or the hosts default, if none is given. .It Fl -emit Op asm|llvm-bc|llvm-ir|obj diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc index 9c17b4681c4a..513a156a838a 100644 --- a/src/llvm/ext/llvm_ext.cc +++ b/src/llvm/ext/llvm_ext.cc @@ -1,8 +1,6 @@ #include #include #include -#include -#include #include #include @@ -18,8 +16,6 @@ using namespace llvm; (LLVM_VERSION_MAJOR < (major) || LLVM_VERSION_MAJOR == (major) && LLVM_VERSION_MINOR <= (minor)) #include -#include -#include #if LLVM_VERSION_GE(16, 0) #define makeArrayRef ArrayRef @@ -85,22 +81,6 @@ LLVMValueRef LLVMExtBuildInvoke2( Bundles, Name)); } -void LLVMExtWriteBitcodeWithSummaryToFile(LLVMModuleRef mref, const char *File) { - // https://github.com/ldc-developers/ldc/pull/1840/files - Module *m = unwrap(mref); - - std::error_code EC; -#if LLVM_VERSION_GE(13, 0) - raw_fd_ostream OS(File, EC, sys::fs::OF_None); -#else - raw_fd_ostream OS(File, EC, sys::fs::F_None); -#endif - if (EC) return; - - llvm::ModuleSummaryIndex moduleSummaryIndex = llvm::buildModuleSummaryIndex(*m, nullptr, nullptr); - llvm::WriteBitcodeToFile(*m, OS, true, &moduleSummaryIndex, true); -} - static TargetMachine *unwrap(LLVMTargetMachineRef P) { return reinterpret_cast(P); } diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr index b1f30763ab9e..0a050bdd8c3e 100644 --- a/src/llvm/lib_llvm_ext.cr +++ b/src/llvm/lib_llvm_ext.cr @@ -32,8 +32,6 @@ lib LibLLVMExt bundle : LibLLVMExt::OperandBundleDefRef, name : LibC::Char*) : LibLLVM::ValueRef - fun write_bitcode_with_summary_to_file = LLVMExtWriteBitcodeWithSummaryToFile(module : LibLLVM::ModuleRef, path : UInt8*) : Void - fun target_machine_enable_global_isel = LLVMExtTargetMachineEnableGlobalIsel(machine : LibLLVM::TargetMachineRef, enable : Bool) fun create_mc_jit_compiler_for_module = LLVMExtCreateMCJITCompilerForModule(jit : LibLLVM::ExecutionEngineRef*, m : LibLLVM::ModuleRef, options : LibLLVM::JITCompilerOptions*, options_length : UInt32, enable_global_isel : Bool, error : UInt8**) : Int32 end diff --git a/src/llvm/module.cr b/src/llvm/module.cr index 6a9fe2f5cbdc..95940f6ed193 100644 --- a/src/llvm/module.cr +++ b/src/llvm/module.cr @@ -53,8 +53,9 @@ class LLVM::Module LibLLVM.write_bitcode_to_file self, filename end + @[Deprecated("ThinLTO is no longer supported; use `#write_bitcode_to_file` instead")] def write_bitcode_with_summary_to_file(filename : String) - LibLLVMExt.write_bitcode_with_summary_to_file self, filename + LibLLVM.write_bitcode_to_file self, filename end def write_bitcode_to_memory_buffer From 9b97e8483528a2c26c3e57afa3704f8ebb429142 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 May 2023 03:06:04 +0800 Subject: [PATCH 0547/1551] Add `File::BadExecutableError` (#13491) --- spec/std/process_spec.cr | 11 ++++++++++- src/crystal/system/unix/process.cr | 2 +- src/crystal/system/win32/process.cr | 2 +- src/file/error.cr | 18 +++++++++++++++++- src/system_error.cr | 2 +- src/winerror.cr | 19 +++++++++++++++++++ 6 files changed, 49 insertions(+), 5 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index c2cb43235c02..9292fd25aec9 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -68,9 +68,18 @@ describe Process do end end - pending_win32 "raises if command is not executable" do + it "raises if command is not executable" do with_tempfile("crystal-spec-run") do |path| File.touch path + expect_raises({% if flag?(:win32) %} File::BadExecutableError {% else %} File::AccessDeniedError {% end %}, "Error executing process: '#{path.inspect_unquoted}'") do + Process.new(path) + end + end + end + + it "raises if command is not executable" do + with_tempfile("crystal-spec-run") do |path| + Dir.mkdir path expect_raises(File::AccessDeniedError, "Error executing process: '#{path.inspect_unquoted}'") do Process.new(path) end diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 9558235ebcf1..f07a91806857 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -257,7 +257,7 @@ struct Crystal::System::Process private def self.raise_exception_from_errno(command, errno = Errno.value) case errno - when Errno::EACCES, Errno::ENOENT + when Errno::EACCES, Errno::ENOENT, Errno::ENOEXEC raise ::File::Error.from_os_error("Error executing process", errno, file: command) else raise IO::Error.from_os_error("Error executing process: '#{command}'", errno) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index e920a1d84d5c..36878bc935bf 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -212,7 +212,7 @@ struct Crystal::System::Process ) == 0 error = WinError.value case error.to_errno - when Errno::EACCES, Errno::ENOENT + when Errno::EACCES, Errno::ENOENT, Errno::ENOEXEC raise ::File::Error.from_os_error("Error executing process", error, file: command_args) else raise IO::Error.from_os_error("Error executing process: '#{command_args}'", error) diff --git a/src/file/error.cr b/src/file/error.cr index 3a6a9b8f4ed5..79a0ec5978dc 100644 --- a/src/file/error.cr +++ b/src/file/error.cr @@ -11,8 +11,10 @@ class File::Error < IO::Error File::NotFoundError.new(message, **opts) when Errno::EEXIST, WinError::ERROR_ALREADY_EXISTS File::AlreadyExistsError.new(message, **opts) - when Errno::EACCES, WinError::ERROR_PRIVILEGE_NOT_HELD + when Errno::EACCES, WinError::ERROR_ACCESS_DENIED, WinError::ERROR_PRIVILEGE_NOT_HELD File::AccessDeniedError.new(message, **opts) + when Errno::ENOEXEC, WinError::ERROR_BAD_EXE_FORMAT + File::BadExecutableError.new(message, **opts) else super message, os_error, **opts end @@ -26,6 +28,17 @@ class File::Error < IO::Error "#{message}: '#{file.inspect_unquoted}' -> '#{other.inspect_unquoted}'" end + {% if flag?(:win32) %} + protected def self.os_error_message(os_error : WinError, *, file : String) : String? + case os_error + when WinError::ERROR_BAD_EXE_FORMAT + os_error.formatted_message(file) + else + super + end + end + {% end %} + def initialize(message, *, file : String | Path, @other : String? = nil) @file = file.to_s super message @@ -40,3 +53,6 @@ end class File::AccessDeniedError < File::Error end + +class File::BadExecutableError < File::Error +end diff --git a/src/system_error.cr b/src/system_error.cr index 7d434695b003..1f4cc4c7b3a4 100644 --- a/src/system_error.cr +++ b/src/system_error.cr @@ -36,7 +36,7 @@ # Returns the respective error message for *os_error*. # By default it returns the result of `Errno#message` or `WinError#message`. # This method can be overridden for customization of the error message based -# on *or_error* and *opts*. +# on *os_error* and *opts*. module SystemError macro included extend ::SystemError::ClassMethods diff --git a/src/winerror.cr b/src/winerror.cr index 16ad9da65ab6..466855813b74 100644 --- a/src/winerror.cr +++ b/src/winerror.cr @@ -61,6 +61,11 @@ enum WinError : UInt32 # # On non-win32 platforms the result is always an empty string. def message : String + formatted_message + end + + # :nodoc: + def formatted_message : String {% if flag?(:win32) %} buffer = uninitialized UInt16[256] size = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, value, 0, buffer, buffer.size, nil) @@ -70,6 +75,20 @@ enum WinError : UInt32 {% end %} end + # :nodoc: + def formatted_message(*args : String) : String + {% if flag?(:win32) %} + buffer = uninitialized UInt16[512] + args = args.to_static_array.map do |arg| + Crystal::System.to_wstr(arg) + end + size = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM | LibC::FORMAT_MESSAGE_ARGUMENT_ARRAY, nil, value, 0, buffer, buffer.size, args) + String.from_utf16(buffer.to_slice[0, size]).strip + {% else %} + "" + {% end %} + end + # Transforms this `WinError` value to the equivalent `Errno` value. # # This is only defined for some values. If no transformation is defined for From 033de0c225c7aeb9b304406bf856fa5ba45196cd Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 May 2023 17:33:00 +0800 Subject: [PATCH 0548/1551] Fix file permission specs on Windows (#13465) --- spec/std/file_spec.cr | 37 ++++++++++++++++++++++++++++--------- spec/std/file_utils_spec.cr | 12 ++++++------ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 09731808e459..24bb937662f7 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -905,7 +905,26 @@ describe "File" do end end - pending_win32 "raises when reading a file with no permission" do + # Crystal does not expose ways to make a file unreadable on Windows + {% unless flag?(:win32) %} + it "raises when reading a file with no permission" do + with_tempfile("file.txt") do |path| + File.touch(path) + File.chmod(path, File::Permissions::None) + {% if flag?(:unix) %} + # TODO: Find a better way to execute this spec when running as privileged + # user. Compiling a program and running a separate process would be a + # lot of overhead. + if LibC.getuid == 0 + pending! "Spec cannot run as superuser" + end + {% end %} + expect_raises(File::AccessDeniedError) { File.read(path) } + end + end + {% end %} + + it "raises when writing to a file with no permission" do with_tempfile("file.txt") do |path| File.touch(path) File.chmod(path, File::Permissions::None) @@ -917,7 +936,7 @@ describe "File" do pending! "Spec cannot run as superuser" end {% end %} - expect_raises(File::AccessDeniedError) { File.read(path) } + expect_raises(File::AccessDeniedError) { File.write(path, "foo") } end end @@ -1390,29 +1409,29 @@ describe "File" do end end - pending_win32 "copies permissions" do + it "copies permissions" do with_tempfile("cp-permissions-src.txt", "cp-permissions-out.txt") do |src_path, out_path| File.write(src_path, "foo") - File.chmod(src_path, 0o700) + File.chmod(src_path, 0o444) File.copy(src_path, out_path) - File.info(out_path).permissions.should eq(File::Permissions.new(0o700)) + File.info(out_path).permissions.should eq(File::Permissions.new(0o444)) File.same_content?(src_path, out_path).should be_true end end - pending_win32 "overwrites existing destination and permissions" do + it "overwrites existing destination and permissions" do with_tempfile("cp-permissions-src.txt", "cp-permissions-out.txt") do |src_path, out_path| File.write(src_path, "foo") - File.chmod(src_path, 0o700) + File.chmod(src_path, 0o444) File.write(out_path, "bar") - File.chmod(out_path, 0o777) + File.chmod(out_path, 0o666) File.copy(src_path, out_path) - File.info(out_path).permissions.should eq(File::Permissions.new(0o700)) + File.info(out_path).permissions.should eq(File::Permissions.new(0o444)) File.same_content?(src_path, out_path).should be_true end end diff --git a/spec/std/file_utils_spec.cr b/spec/std/file_utils_spec.cr index d7af1f6c3287..448ff620208f 100644 --- a/spec/std/file_utils_spec.cr +++ b/spec/std/file_utils_spec.cr @@ -100,15 +100,15 @@ describe "FileUtils" do end end - pending_win32 "copies permissions" do + it "copies permissions" do with_tempfile("cp-permissions-src.txt", "cp-permissions-out.txt") do |src_path, out_path| - test_with_string_and_path(src_path, out_path) do |*args| - File.write(src_path, "foo") - File.chmod(src_path, 0o700) + File.write(src_path, "foo") + File.chmod(src_path, 0o444) + test_with_string_and_path(src_path, out_path) do |*args| FileUtils.cp(*args) - File.info(out_path).permissions.should eq(File::Permissions.new(0o700)) + File.info(out_path).permissions.should eq(File::Permissions.new(0o444)) FileUtils.cmp(src_path, out_path).should be_true FileUtils.rm_rf(out_path) @@ -678,7 +678,7 @@ describe "FileUtils" do File.symlink?(path2).should be_true expect_raises(File::NotFoundError, "Error resolving real path: '#{path2.inspect_unquoted}'") do - File.real_path(path2) + File.realpath(path2) end FileUtils.rm_rf(path2) end From bd325d51d7e1a51a0215b5957d464c12f1a2f802 Mon Sep 17 00:00:00 2001 From: Daniel Gilchrist Date: Wed, 24 May 2023 10:33:32 +0100 Subject: [PATCH 0549/1551] Add `start_day` parameter to `Time#at_beginning_of_week` (#13446) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Johannes Müller --- spec/std/time/time_spec.cr | 31 +++++++++++++++++++++++++++++++ src/time.cr | 12 ++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/spec/std/time/time_spec.cr b/spec/std/time/time_spec.cr index 8ecff6050016..a7e8044ae1da 100644 --- a/spec/std/time/time_spec.cr +++ b/spec/std/time/time_spec.cr @@ -659,6 +659,37 @@ describe Time do Time.utc(2014, 11, i).at_beginning_of_week.should eq Time.utc(2014, 11, 3) end + sunday_day_of_week = Time::DayOfWeek::Sunday + Time.utc(2014, 11, 1).at_beginning_of_week(sunday_day_of_week).should eq Time.utc(2014, 10, 26) + 2.upto(8) do |i| + Time.utc(2014, 11, i).at_beginning_of_week(sunday_day_of_week).should eq Time.utc(2014, 11, 2) + end + Time.utc(2014, 11, 9).at_beginning_of_week(sunday_day_of_week).should eq Time.utc(2014, 11, 9) + + Time.utc(2014, 11, 1).at_beginning_of_week(:sunday).should eq Time.utc(2014, 10, 26) + 2.upto(8) do |i| + Time.utc(2014, 11, i).at_beginning_of_week(:sunday).should eq Time.utc(2014, 11, 2) + end + Time.utc(2014, 11, 9).at_beginning_of_week(:sunday).should eq Time.utc(2014, 11, 9) + + Time.utc(2014, 11, 10).at_beginning_of_week(Time::DayOfWeek::Sunday).should eq Time.utc(2014, 11, 9) + Time.utc(2014, 11, 10).at_beginning_of_week(Time::DayOfWeek::Monday).should eq Time.utc(2014, 11, 10) + Time.utc(2014, 11, 10).at_beginning_of_week(Time::DayOfWeek::Tuesday).should eq Time.utc(2014, 11, 4) + Time.utc(2014, 11, 10).at_beginning_of_week(Time::DayOfWeek::Wednesday).should eq Time.utc(2014, 11, 5) + Time.utc(2014, 11, 10).at_beginning_of_week(Time::DayOfWeek::Thursday).should eq Time.utc(2014, 11, 6) + Time.utc(2014, 11, 10).at_beginning_of_week(Time::DayOfWeek::Friday).should eq Time.utc(2014, 11, 7) + Time.utc(2014, 11, 10).at_beginning_of_week(Time::DayOfWeek::Saturday).should eq Time.utc(2014, 11, 8) + + at_beginning_of_week_default = Time.local.at_beginning_of_week + at_beginning_of_week_default.hour.should eq 0 + at_beginning_of_week_default.minute.should eq 0 + at_beginning_of_week_default.second.should eq 0 + + at_beginning_of_week_sunday = Time.local.at_beginning_of_week(:sunday) + at_beginning_of_week_sunday.hour.should eq 0 + at_beginning_of_week_sunday.minute.should eq 0 + at_beginning_of_week_sunday.second.should eq 0 + t1.at_beginning_of_day.should eq Time.utc(2014, 11, 25) t1.at_beginning_of_hour.should eq Time.utc(2014, 11, 25, 10) t1.at_beginning_of_minute.should eq Time.utc(2014, 11, 25, 10, 11) diff --git a/src/time.cr b/src/time.cr index 1cf7a9be7536..0d3cff1102e2 100644 --- a/src/time.cr +++ b/src/time.cr @@ -1359,9 +1359,17 @@ struct Time # Returns a copy of this `Time` representing the beginning of the week. # + # The week starts on Monday by default, but can be configured by passing a different `start_day` as a `Time::DayOfWeek`. + # + # ``` + # now = Time.utc(2023, 5, 16, 17, 53, 22) + # now.at_beginning_of_week # => 2023-05-15 00:00:00 UTC + # now.at_beginning_of_week(:sunday) # => 2023-05-14 00:00:00 UTC + # now.at_beginning_of_week(:wednesday) # => 2023-05-10 00:00:00 UTC + # ``` # TODO: Ensure correctness in local time-line. - def at_beginning_of_week : Time - (self - (day_of_week.value - 1).days).at_beginning_of_day + def at_beginning_of_week(start_day : Time::DayOfWeek = :monday) : Time + (self - ((day_of_week.value - start_day.value) % 7).days).at_beginning_of_day end def_at_end(year) { Time.local(year, 12, 31, 23, 59, 59, nanosecond: 999_999_999, location: location) } From 6dfee042d276da77efd3d10cd7b85f1d317d5334 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 May 2023 21:09:46 +0800 Subject: [PATCH 0550/1551] Support Unix sockets on Windows (#13493) --- spec/std/socket/address_spec.cr | 2 +- spec/std/socket/socket_spec.cr | 43 +++++++++++------ spec/std/socket/unix_server_spec.cr | 25 ++++++---- spec/std/socket/unix_socket_spec.cr | 67 ++++++++++++++------------- src/crystal/system/win32/file.cr | 26 ++++++----- src/crystal/system/win32/file_info.cr | 12 +++-- src/crystal/system/win32/socket.cr | 10 ++-- src/socket/address.cr | 14 +++--- src/socket/unix_server.cr | 6 ++- src/socket/unix_socket.cr | 9 +++- 10 files changed, 128 insertions(+), 86 deletions(-) diff --git a/spec/std/socket/address_spec.cr b/spec/std/socket/address_spec.cr index 89da5943a859..6835b8e518d1 100644 --- a/spec/std/socket/address_spec.cr +++ b/spec/std/socket/address_spec.cr @@ -20,7 +20,7 @@ describe Socket::Address do address.should eq Socket::IPAddress.new("192.168.0.1", 8081) end - pending_win32 "parses UNIX" do + it "parses UNIX" do address = Socket::Address.parse "unix://socket.sock" address.should eq Socket::UNIXAddress.new("socket.sock") end diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index f1500697ca81..79c5d8b2c9c5 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -4,18 +4,30 @@ require "../../support/win32" describe Socket, tags: "network" do describe ".unix" do - pending_win32 "creates a unix socket" do + it "creates a unix socket" do sock = Socket.unix sock.should be_a(Socket) sock.family.should eq(Socket::Family::UNIX) sock.type.should eq(Socket::Type::STREAM) - sock = Socket.unix(Socket::Type::DGRAM) - sock.type.should eq(Socket::Type::DGRAM) + # Datagram socket type is not supported on Windows yet: + # https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/#unsupportedunavailable + # https://github.com/microsoft/WSL/issues/5272 + {% unless flag?(:win32) %} + sock = Socket.unix(Socket::Type::DGRAM) + sock.type.should eq(Socket::Type::DGRAM) + {% end %} - expect_raises Socket::Error, "Protocol not supported" do + error = expect_raises(Socket::Error) do TCPSocket.new(family: :unix) end + error.os_error.should eq({% if flag?(:win32) %} + WinError::WSAEPROTONOSUPPORT + {% elsif flag?(:wasi) %} + WasiError::PROTONOSUPPORT + {% else %} + Errno.new(LibC::EPROTONOSUPPORT) + {% end %}) end end @@ -87,19 +99,22 @@ describe Socket, tags: "network" do server.try &.close end - pending_win32 "sends datagram over unix socket" do - with_tempfile("datagram_unix") do |path| - server = Socket.unix(Socket::Type::DGRAM) - server.bind Socket::UNIXAddress.new(path) + # Datagram socket type is not supported on Windows yet + {% unless flag?(:win32) %} + it "sends datagram over unix socket" do + with_tempfile("datagram_unix") do |path| + server = Socket.unix(Socket::Type::DGRAM) + server.bind Socket::UNIXAddress.new(path) - client = Socket.unix(Socket::Type::DGRAM) - client.connect Socket::UNIXAddress.new(path) - client.send "foo" + client = Socket.unix(Socket::Type::DGRAM) + client.connect Socket::UNIXAddress.new(path) + client.send "foo" - message, _ = server.receive - message.should eq "foo" + message, _ = server.receive + message.should eq "foo" + end end - end + {% end %} describe "#bind" do each_ip_family do |family, _, any_address| diff --git a/spec/std/socket/unix_server_spec.cr b/spec/std/socket/unix_server_spec.cr index d5806211d436..93dd124ed60b 100644 --- a/spec/std/socket/unix_server_spec.cr +++ b/spec/std/socket/unix_server_spec.cr @@ -1,4 +1,3 @@ -{% skip_file if flag?(:win32) %} require "../spec_helper" require "socket" require "../../support/fibers" @@ -21,6 +20,7 @@ describe UNIXServer do with_tempfile("unix_server.sock") do |path| UNIXServer.open(path) do File.exists?(path).should be_true + File.info(path).type.socket?.should be_true end File.exists?(path).should be_false @@ -149,17 +149,22 @@ describe UNIXServer do end end - describe "datagrams" do - it "can send and receive datagrams" do - with_tempfile("unix_dgram_server.sock") do |path| - UNIXServer.open(path, Socket::Type::DGRAM) do |s| - UNIXSocket.open(path, Socket::Type::DGRAM) do |c| - c.send("foobar") - msg, _addr = s.receive(512) - msg.should eq "foobar" + # Datagram socket type is not supported on Windows yet: + # https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/#unsupportedunavailable + # https://github.com/microsoft/WSL/issues/5272 + {% unless flag?(:win32) %} + describe "datagrams" do + it "can send and receive datagrams" do + with_tempfile("unix_dgram_server.sock") do |path| + UNIXServer.open(path, Socket::Type::DGRAM) do |s| + UNIXSocket.open(path, Socket::Type::DGRAM) do |c| + c.send("foobar") + msg, _addr = s.receive(512) + msg.should eq "foobar" + end end end end end - end + {% end %} end diff --git a/spec/std/socket/unix_socket_spec.cr b/spec/std/socket/unix_socket_spec.cr index 700308c1c248..5968ffe381aa 100644 --- a/spec/std/socket/unix_socket_spec.cr +++ b/spec/std/socket/unix_socket_spec.cr @@ -1,4 +1,3 @@ -{% skip_file if flag?(:win32) %} require "spec" require "socket" require "../../support/tempfile" @@ -58,47 +57,51 @@ describe UNIXSocket do end end - it "creates a pair of sockets" do - UNIXSocket.pair do |left, right| - left.local_address.family.should eq(Socket::Family::UNIX) - left.local_address.path.should eq("") + # `LibC.socketpair` is not supported in Winsock 2.0 yet: + # https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/#unsupportedunavailable + {% unless flag?(:win32) %} + it "creates a pair of sockets" do + UNIXSocket.pair do |left, right| + left.local_address.family.should eq(Socket::Family::UNIX) + left.local_address.path.should eq("") - left << "ping" - right.gets(4).should eq("ping") + left << "ping" + right.gets(4).should eq("ping") - right << "pong" - left.gets(4).should eq("pong") + right << "pong" + left.gets(4).should eq("pong") + end end - end - it "tests read and write timeouts" do - UNIXSocket.pair do |left, right| - # BUG: shrink the socket buffers first - left.write_timeout = 0.0001 - right.read_timeout = 0.0001 - buf = ("a" * IO::DEFAULT_BUFFER_SIZE).to_slice + it "tests read and write timeouts" do + UNIXSocket.pair do |left, right| + # BUG: shrink the socket buffers first + left.write_timeout = 0.0001 + right.read_timeout = 0.0001 + buf = ("a" * IO::DEFAULT_BUFFER_SIZE).to_slice - expect_raises(IO::TimeoutError, "Write timed out") do - loop { left.write buf } - end + expect_raises(IO::TimeoutError, "Write timed out") do + loop { left.write buf } + end - expect_raises(IO::TimeoutError, "Read timed out") do - loop { right.read buf } + expect_raises(IO::TimeoutError, "Read timed out") do + loop { right.read buf } + end end end - end - it "tests socket options" do - UNIXSocket.pair do |left, right| - size = 12000 - # linux returns size * 2 - sizes = [size, size * 2] + it "tests socket options" do + UNIXSocket.pair do |left, right| + size = 12000 + # linux returns size * 2 + sizes = [size, size * 2] - (left.send_buffer_size = size).should eq(size) - sizes.should contain(left.send_buffer_size) + (left.send_buffer_size = size).should eq(size) + sizes.should contain(left.send_buffer_size) - (left.recv_buffer_size = size).should eq(size) - sizes.should contain(left.recv_buffer_size) + (left.recv_buffer_size = size).should eq(size) + sizes.should contain(left.recv_buffer_size) + end end - end + {% end %} end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 0dddf9d2a417..c6ec882421d8 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -126,16 +126,14 @@ module Crystal::System::File def self.info?(path : String, follow_symlinks : Bool) : ::File::Info? winpath = System.to_wstr(path) - unless follow_symlinks - # First try using GetFileAttributes to check if it's a reparse point - file_attributes = uninitialized LibC::WIN32_FILE_ATTRIBUTE_DATA - ret = LibC.GetFileAttributesExW( - winpath, - LibC::GET_FILEEX_INFO_LEVELS::GetFileExInfoStandard, - pointerof(file_attributes) - ) - return check_not_found_error("Unable to get file info", path) if ret == 0 - + # First try using GetFileAttributes to check if it's a reparse point + file_attributes = uninitialized LibC::WIN32_FILE_ATTRIBUTE_DATA + ret = LibC.GetFileAttributesExW( + winpath, + LibC::GET_FILEEX_INFO_LEVELS::GetFileExInfoStandard, + pointerof(file_attributes) + ) + if ret != 0 if file_attributes.dwFileAttributes.bits_set? LibC::FILE_ATTRIBUTE_REPARSE_POINT # Could be a symlink, retrieve its reparse tag with FindFirstFile handle = LibC.FindFirstFileW(winpath, out find_data) @@ -145,7 +143,10 @@ module Crystal::System::File raise RuntimeError.from_winerror("FindClose") end - if find_data.dwReserved0 == LibC::IO_REPARSE_TAG_SYMLINK + case find_data.dwReserved0 + when LibC::IO_REPARSE_TAG_SYMLINK + return ::File::Info.new(find_data) unless follow_symlinks + when LibC::IO_REPARSE_TAG_AF_UNIX return ::File::Info.new(find_data) end end @@ -373,7 +374,8 @@ module Crystal::System::File end return {name, is_relative} else - raise ::File::Error.new("Not a symlink", file: path) + # not a symlink (e.g. IO_REPARSE_TAG_AF_UNIX) + return nil end end diff --git a/src/crystal/system/win32/file_info.cr b/src/crystal/system/win32/file_info.cr index ba5ed3007a88..e30f6835a320 100644 --- a/src/crystal/system/win32/file_info.cr +++ b/src/crystal/system/win32/file_info.cr @@ -53,9 +53,15 @@ module Crystal::System::FileInfo ::File::Type::CharacterDevice when LibC::FILE_TYPE_DISK # See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365511(v=vs.85).aspx - if @file_attributes.dwFileAttributes.bits_set?(LibC::FILE_ATTRIBUTE_REPARSE_POINT) && - @reparse_tag == LibC::IO_REPARSE_TAG_SYMLINK - ::File::Type::Symlink + if @file_attributes.dwFileAttributes.bits_set?(LibC::FILE_ATTRIBUTE_REPARSE_POINT) + case @reparse_tag + when LibC::IO_REPARSE_TAG_SYMLINK + ::File::Type::Symlink + when LibC::IO_REPARSE_TAG_AF_UNIX + ::File::Type::Socket + else + ::File::Type::Unknown + end elsif @file_attributes.dwFileAttributes.bits_set? LibC::FILE_ATTRIBUTE_DIRECTORY ::File::Type::Directory else diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index ec814e0d20ce..d1fe5baf2b46 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -85,9 +85,11 @@ module Crystal::System::Socket end private def initialize_handle(handle) - system_getsockopt(handle, LibC::SO_REUSEADDR, 0) do |value| - if value == 0 - system_setsockopt(handle, LibC::SO_EXCLUSIVEADDRUSE, 1) + unless @family.unix? + system_getsockopt(handle, LibC::SO_REUSEADDR, 0) do |value| + if value == 0 + system_setsockopt(handle, LibC::SO_EXCLUSIVEADDRUSE, 1) + end end end end @@ -173,7 +175,7 @@ module Crystal::System::Socket private def system_bind(addr, addrstr, &) unless LibC.bind(fd, addr, addr.size) == 0 - yield ::Socket::BindError.from_errno("Could not bind to '#{addrstr}'") + yield ::Socket::BindError.from_wsa_error("Could not bind to '#{addrstr}'") end end diff --git a/src/socket/address.cr b/src/socket/address.cr index 2904657e03f4..851180d3e9b9 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -722,11 +722,11 @@ class Socket MAX_PATH_SIZE = {% if flag?(:wasm32) %} 0 {% else %} - LibC::SockaddrUn.new.sun_path.size - 1 + sizeof(typeof(LibC::SockaddrUn.new.sun_path)) - 1 {% end %} def initialize(@path : String) - if @path.bytesize + 1 > MAX_PATH_SIZE + if @path.bytesize > MAX_PATH_SIZE raise ArgumentError.new("Path size exceeds the maximum size of #{MAX_PATH_SIZE} bytes") end @family = Family::UNIX @@ -740,7 +740,7 @@ class Socket # Creates an `UNIXSocket` from the internal OS representation. def self.from(sockaddr : LibC::Sockaddr*, addrlen) : UNIXAddress {% if flag?(:wasm32) %} - raise NotImplementedError.new "Socket::UnixAddress.from" + raise NotImplementedError.new "Socket::UNIXAddress.from" {% else %} new(sockaddr.as(LibC::SockaddrUn*), addrlen.to_i) {% end %} @@ -773,10 +773,10 @@ class Socket raise Socket::Error.new("Invalid UNIX address: missing path") if unix_path.empty? - {% if flag?(:unix) %} - UNIXAddress.new(unix_path) + {% if flag?(:wasm32) %} + raise NotImplementedError.new "Socket::UNIXAddress.parse" {% else %} - raise NotImplementedError.new("UNIX address not available") + UNIXAddress.new(unix_path) {% end %} end @@ -801,7 +801,7 @@ class Socket def to_unsafe : LibC::Sockaddr* {% if flag?(:wasm32) %} - raise NotImplementedError.new "Socket::UnixAddress#to_unsafe" + raise NotImplementedError.new "Socket::UNIXAddress#to_unsafe" {% else %} sockaddr = Pointer(LibC::SockaddrUn).malloc sockaddr.value.sun_family = family diff --git a/src/socket/unix_server.cr b/src/socket/unix_server.cr index e53f71d00842..75195a09ff70 100644 --- a/src/socket/unix_server.cr +++ b/src/socket/unix_server.cr @@ -2,7 +2,8 @@ require "./unix_socket" # A local interprocess communication server socket. # -# Only available on UNIX and UNIX-like operating systems. +# Available on UNIX-like operating systems, and Windows 10 Build 17063 or above. +# Not all features are supported on Windows. # # NOTE: To use `UNIXServer`, you must explicitly import it with `require "socket"` # @@ -30,9 +31,12 @@ class UNIXServer < UNIXSocket # # The server is of stream type by default, but this can be changed for # another type. For example datagram messages: + # # ``` # UNIXServer.new("/tmp/dgram.sock", Socket::Type::DGRAM) # ``` + # + # [Only the stream type is supported on Windows](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/#unsupportedunavailable). def initialize(@path : String, type : Type = Type::STREAM, backlog : Int = 128) super(Family::UNIX, type) diff --git a/src/socket/unix_socket.cr b/src/socket/unix_socket.cr index 16ad99700db4..0639dce97ca9 100644 --- a/src/socket/unix_socket.cr +++ b/src/socket/unix_socket.cr @@ -1,6 +1,7 @@ # A local interprocess communication clientsocket. # -# Only available on UNIX and UNIX-like operating systems. +# Available on UNIX-like operating systems, and Windows 10 Build 17063 or above. +# Not all features are supported on Windows. # # NOTE: To use `UNIXSocket`, you must explicitly import it with `require "socket"` # @@ -50,6 +51,8 @@ class UNIXSocket < Socket # Returns a pair of unnamed UNIX sockets. # + # [Not supported on Windows](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/#unsupportedunavailable). + # # ``` # require "socket" # @@ -65,7 +68,7 @@ class UNIXSocket < Socket # left.gets # => "message" # ``` def self.pair(type : Type = Type::STREAM) : {UNIXSocket, UNIXSocket} - {% if flag?(:wasm32) %} + {% if flag?(:wasm32) || flag?(:win32) %} raise NotImplementedError.new "UNIXSocket.pair" {% else %} fds = uninitialized Int32[2] @@ -87,6 +90,8 @@ class UNIXSocket < Socket # block. Eventually closes both sockets when the block returns. # # Returns the value of the block. + # + # [Not supported on Windows](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/#unsupportedunavailable). def self.pair(type : Type = Type::STREAM, &) left, right = pair(type) begin From b45504f6f2df8a173f8834e91fbfc765e3a1782a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 May 2023 21:09:55 +0800 Subject: [PATCH 0551/1551] Split Windows library build scripts from CI (#13478) --- .github/workflows/win.yml | 219 ++--------------------------------- etc/win-ci/build-ffi.ps1 | 55 +++++++++ etc/win-ci/build-gc.ps1 | 35 ++++++ etc/win-ci/build-iconv.ps1 | 56 +++++++++ etc/win-ci/build-mpir.ps1 | 48 ++++++++ etc/win-ci/build-openssl.ps1 | 38 ++++++ etc/win-ci/build-pcre.ps1 | 49 ++++++++ etc/win-ci/build-pcre2.ps1 | 32 +++++ etc/win-ci/build-xml2.ps1 | 32 +++++ etc/win-ci/build-yaml.ps1 | 32 +++++ etc/win-ci/build-z.ps1 | 32 +++++ etc/win-ci/setup.ps1 | 78 +++++++++++++ 12 files changed, 499 insertions(+), 207 deletions(-) create mode 100644 etc/win-ci/build-ffi.ps1 create mode 100644 etc/win-ci/build-gc.ps1 create mode 100644 etc/win-ci/build-iconv.ps1 create mode 100644 etc/win-ci/build-mpir.ps1 create mode 100644 etc/win-ci/build-openssl.ps1 create mode 100644 etc/win-ci/build-pcre.ps1 create mode 100644 etc/win-ci/build-pcre2.ps1 create mode 100644 etc/win-ci/build-xml2.ps1 create mode 100644 etc/win-ci/build-yaml.ps1 create mode 100644 etc/win-ci/build-z.ps1 create mode 100644 etc/win-ci/setup.ps1 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 0d80873b0e1d..b2c7765334f4 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -38,210 +38,34 @@ jobs: libs/mpir.lib libs/yaml.lib libs/xml2.lib - key: win-libs-${{ hashFiles('.github/workflows/win.yml') }}-msvc-${{ env.VSCMD_VER }} - - name: Download libgc - if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v3 - with: - repository: ivmai/bdwgc - ref: v8.2.2 - path: bdwgc - - name: Download libatomic_ops - if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v3 - with: - repository: ivmai/libatomic_ops - ref: v7.6.14 - path: bdwgc/libatomic_ops + key: win-libs-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc-${{ env.VSCMD_VER }} - name: Build libgc if: steps.cache-libs.outputs.cache-hit != 'true' - working-directory: ./bdwgc - run: | - cmake . -DBUILD_SHARED_LIBS=OFF -Denable_large_config=ON -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF - cmake --build . --config Release - - name: Download libpcre - if: steps.cache-libs.outputs.cache-hit != 'true' - run: | - iwr https://cs.stanford.edu/pub/exim/pcre/pcre-8.45.zip -OutFile pcre.zip - (Get-FileHash -Algorithm SHA256 .\pcre.zip).hash -eq "5b709aa45ea3b8bb73052947200ad187f651a2049158fb5bbfed329e4322a977" - 7z x pcre.zip - mv pcre-* pcre + run: .\etc\win-ci\build-gc.ps1 -BuildTree deps\gc -Version 8.2.2 -AtomicOpsVersion 7.8.0 - name: Build libpcre if: steps.cache-libs.outputs.cache-hit != 'true' - working-directory: ./pcre - run: | - cmake . -DBUILD_SHARED_LIBS=OFF -DPCRE_SUPPORT_UNICODE_PROPERTIES=ON -DPCRE_SUPPORT_JIT=ON -DPCRE_STATIC_RUNTIME=ON -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF - cmake --build . --config Release - - name: Download libpcre2 - if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v3 - with: - repository: PCRE2Project/pcre2 - ref: pcre2-10.42 - path: pcre2 + run: .\etc\win-ci\build-pcre.ps1 -BuildTree deps\pcre -Version 8.45 - name: Build libpcre2 if: steps.cache-libs.outputs.cache-hit != 'true' - working-directory: ./pcre2 - run: | - cmake . -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DPCRE2_STATIC_RUNTIME=ON -DPCRE2_BUILD_PCRE2GREP=OFF -DPCRE2_BUILD_TESTS=OFF -DPCRE2_SUPPORT_UNICODE=ON -DPCRE2_SUPPORT_JIT=ON -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF - cmake --build . --config Release - - name: Download libiconv - if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v3 - with: - repository: pffang/libiconv-for-Windows - ref: 1353455a6c4e15c9db6865fd9c2bf7203b59c0ec # master@{2022-10-11} - path: libiconv + run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.42 - name: Build libiconv if: steps.cache-libs.outputs.cache-hit != 'true' - working-directory: ./libiconv - run: | - sed -i 's|__declspec (dllimport) ||' libiconv\include\iconv.h - - echo ' - - $(MsbuildThisFileDirectory)\Override.props - - ' > 'Directory.Build.props' - - echo ' - - - MultiThreaded - None - false - - - ' > 'Override.props' - - MSBuild.exe /p:Platform=x64 /p:Configuration=ReleaseStatic libiconv.vcxproj - - name: Download libffi - if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v3 - with: - repository: winlibs/libffi - ref: libffi-3.3 - path: libffi + run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv - name: Build libffi if: steps.cache-libs.outputs.cache-hit != 'true' - working-directory: ./libffi - run: | - echo ' - - $(MsbuildThisFileDirectory)\Override.props - - ' > 'Directory.Build.props' - - echo ' - - - MultiThreaded - None - false - - - false - - - ' > 'Override.props' - - MSBuild.exe /p:PlatformToolset=v143 /p:Platform=x64 /p:Configuration=Release win32\vs16_x64\libffi-msvc.sln -target:libffi:Rebuild - - name: Download zlib - if: steps.cache-libs.outputs.cache-hit != 'true' - run: | - iwr https://github.com/madler/zlib/archive/v1.2.13.zip -OutFile zlib.zip - (Get-FileHash -Algorithm SHA256 .\zlib.zip).hash -eq "C2856951BBF30E30861ACE3765595D86BA13F2CF01279D901F6C62258C57F4FF" - 7z x zlib.zip - mv zlib-* zlib + run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.3 - name: Build zlib if: steps.cache-libs.outputs.cache-hit != 'true' - working-directory: ./zlib - run: | - cmake . -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF - cmake --build . --config Release - - name: Download mpir - if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v3 - with: - repository: BrianGladman/mpir - ref: 28d01062f62de1218d511c4574da4006f92be3bd # master@{2022-10-12} - path: mpir + run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.2.13 - name: Build mpir if: steps.cache-libs.outputs.cache-hit != 'true' - working-directory: ./mpir - run: | - $VsVersion = "vs$((& "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -property displayName) -replace '.*\b\d\d(\d\d)\b.*', '$1')" - - echo ' - - $(MsbuildThisFileDirectory)\Override.props - - ' > 'msvc\Directory.Build.props' - - echo ' - - - None - false - - - ' > 'msvc\Override.props' - - MSBuild.exe /p:Platform=x64 /p:Configuration=Release /p:DefineConstants=MSC_BUILD_DLL ".\msvc\$VsVersion\lib_mpir_gc\lib_mpir_gc.vcxproj" - - name: Download libyaml - if: steps.cache-libs.outputs.cache-hit != 'true' - run: | - iwr https://github.com/yaml/libyaml/archive/0.2.5.zip -OutFile libyaml.zip - (Get-FileHash -Algorithm SHA256 .\libyaml.zip).hash -eq "14605BAA6DFC0C4D3AB943A46A627413C0388736E453B67FE4E90C9683C8CBC8" - 7z x libyaml.zip - mv libyaml-* libyaml + run: .\etc\win-ci\build-mpir.ps1 -BuildTree deps\mpir - name: Build libyaml if: steps.cache-libs.outputs.cache-hit != 'true' - working-directory: ./libyaml - run: | - cmake . -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF - cmake --build . --config Release - - name: Download libxml2 - if: steps.cache-libs.outputs.cache-hit != 'true' - uses: actions/checkout@v3 - with: - repository: GNOME/libxml2 - ref: f507d167f1755b7eaea09fb1a44d29aab828b6d1 # v2.10.3 - path: libxml2 + run: .\etc\win-ci\build-yaml.ps1 -BuildTree deps\yaml -Version 0.2.5 - name: Build libxml2 if: steps.cache-libs.outputs.cache-hit != 'true' - working-directory: ./libxml2 - run: | - cmake . -DBUILD_SHARED_LIBS=OFF -DLIBXML2_WITH_PROGRAMS=OFF -DLIBXML2_WITH_HTTP=OFF -DLIBXML2_WITH_FTP=OFF -DLIBXML2_WITH_TESTS=OFF -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLIBXML2_WITH_ICONV=OFF -DLIBXML2_WITH_LZMA=OFF -DLIBXML2_WITH_PYTHON=OFF -DLIBXML2_WITH_ZLIB=OFF - - echo ' - - $(MsbuildThisFileDirectory)\Override.props - - ' > 'Directory.Build.props' - - echo ' - - - LIBXML_STATIC;%(PreprocessorDefinitions) - - - ' > 'Override.props' - - cmake --build . --config Release - - name: Gather libraries - if: steps.cache-libs.outputs.cache-hit != 'true' - run: | - mkdir libs - mv pcre/Release/pcre.lib libs/ - mv pcre2/Release/pcre2-8-static.lib libs/pcre2-8.lib - mv libiconv/output/x64/ReleaseStatic/libiconvStatic.lib libs/iconv.lib - mv bdwgc/Release/gc.lib libs/ - mv libffi/win32/vs16_x64/x64/Release/libffi.lib libs/ffi.lib - mv zlib/Release/zlibstatic.lib libs/z.lib - mv mpir/lib/x64/Release/mpir.lib libs/ - mv libyaml/Release/yaml.lib libs/ - mv libxml2/Release/libxml2s.lib libs/xml2.lib + run: .\etc\win-ci\build-xml2.ps1 -BuildTree deps\xml2 -Version 2.11.3 - name: Cache OpenSSL id: cache-openssl @@ -251,32 +75,13 @@ jobs: libs/crypto.lib libs/ssl.lib libs/openssl_VERSION - key: win-openssl-libs-3.0.7-msvc-${{ env.VSCMD_VER }} + key: win-openssl-libs-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc-${{ env.VSCMD_VER }} - name: Set up NASM if: steps.cache-openssl.outputs.cache-hit != 'true' uses: ilammy/setup-nasm@321e6ed62a1fc77024a3bd853deb33645e8b22c4 # v1.4.0 - - name: Download OpenSSL - if: steps.cache-openssl.outputs.cache-hit != 'true' - run: | - iwr https://www.openssl.org/source/openssl-3.0.7.tar.gz -OutFile openssl.tar.gz - (Get-FileHash -Algorithm SHA256 .\openssl.tar.gz).hash -eq "83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e" - 7z x openssl.tar.gz - 7z x openssl.tar - mv openssl-* openssl - name: Build OpenSSL if: steps.cache-openssl.outputs.cache-hit != 'true' - working-directory: ./openssl - run: | - sed -i 's|/Zi /Fd.*\.pdb||' Configurations/10-main.conf - sed -i 's|/debug|/debug:none|' Configurations/10-main.conf - perl Configure VC-WIN64A /MT -static no-tests --with-zlib-lib=..\zlib\Release --with-zlib-include=..\zlib - nmake - - name: Gather OpenSSL - if: steps.cache-openssl.outputs.cache-hit != 'true' - run: | - cp openssl/libcrypto.lib libs/crypto.lib - cp openssl/libssl.lib libs/ssl.lib - [IO.File]::WriteAllLines("libs/openssl_VERSION", "3.0.7") + run: .\etc\win-ci\build-openssl.ps1 -BuildTree deps\openssl -Version 3.1.0 - name: Cache LLVM id: cache-llvm diff --git a/etc/win-ci/build-ffi.ps1 b/etc/win-ci/build-ffi.ps1 new file mode 100644 index 000000000000..4340630bea64 --- /dev/null +++ b/etc/win-ci/build-ffi.ps1 @@ -0,0 +1,55 @@ +param( + [Parameter(Mandatory)] [string] $BuildTree, + [Parameter(Mandatory)] [string] $Version, + [switch] $Dynamic +) + +. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" + +[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) +Setup-Git -Path $BuildTree -Url https://github.com/winlibs/libffi.git -Ref libffi-$Version + +Run-InDirectory $BuildTree { + if ($Dynamic) { + Replace-Text win32\vs16_x64\libffi\libffi.vcxproj 'StaticLibrary' 'DynamicLibrary' + } + + echo ' + + $(MsbuildThisFileDirectory)\Override.props + + ' > 'Directory.Build.props' + + echo " + + false + + + + $(if ($Dynamic) { + 'FFI_BUILDING_DLL;%(PreprocessorDefinitions)' + } else { + 'MultiThreaded' + }) + None + false + + + false + + + " > 'Override.props' + + MSBuild.exe /p:PlatformToolset=v143 /p:Platform=x64 /p:Configuration=Release win32\vs16_x64\libffi-msvc.sln -target:libffi:Rebuild + if (-not $?) { + Write-Host "Error: Failed to build libffi" -ForegroundColor Red + Exit 1 + } +} + +if ($Dynamic) { + mv -Force $BuildTree\win32\vs16_x64\x64\Release\libffi.lib libs\ffi-dynamic.lib + mv -Force $BuildTree\win32\vs16_x64\x64\Release\libffi.dll dlls\ +} else { + mv -Force $BuildTree\win32\vs16_x64\x64\Release\libffi.lib libs\ffi.lib +} diff --git a/etc/win-ci/build-gc.ps1 b/etc/win-ci/build-gc.ps1 new file mode 100644 index 000000000000..4509b581141c --- /dev/null +++ b/etc/win-ci/build-gc.ps1 @@ -0,0 +1,35 @@ +param( + [Parameter(Mandatory)] [string] $BuildTree, + [Parameter(Mandatory)] [string] $Version, + [Parameter(Mandatory)] [string] $AtomicOpsVersion, + [switch] $Dynamic +) + +. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" + +[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) +Setup-Git -Path $BuildTree -Url https://github.com/ivmai/bdwgc.git -Ref v$Version +Setup-Git -Path $BuildTree\libatomic_ops -Url https://github.com/ivmai/libatomic_ops.git -Ref v$AtomicOpsVersion + +Run-InDirectory $BuildTree { + $args = "-Dbuild_cord=OFF -Denable_large_config=ON -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF" + if ($Dynamic) { + $args = "-DBUILD_SHARED_LIBS=ON $args" + } else { + $args = "-DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded $args" + } + & $cmake . $args.split(' ') + & $cmake --build . --config Release + if (-not $?) { + Write-Host "Error: Failed to build libgc" -ForegroundColor Red + Exit 1 + } +} + +if ($Dynamic) { + mv -Force $BuildTree\Release\gc.lib libs\gc-dynamic.lib + mv -Force $BuildTree\Release\gc.dll dlls\ +} else { + mv -Force $BuildTree\Release\gc.lib libs\ +} + diff --git a/etc/win-ci/build-iconv.ps1 b/etc/win-ci/build-iconv.ps1 new file mode 100644 index 000000000000..56d0417bd729 --- /dev/null +++ b/etc/win-ci/build-iconv.ps1 @@ -0,0 +1,56 @@ +param( + [Parameter(Mandatory)] [string] $BuildTree, + [switch] $Dynamic +) + +. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" + +[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) +Setup-Git -Path $BuildTree -Url https://github.com/pffang/libiconv-for-Windows.git -Ref 1353455a6c4e15c9db6865fd9c2bf7203b59c0ec # master@{2022-10-11} + +Run-InDirectory $BuildTree { + Replace-Text libiconv\include\iconv.h '__declspec (dllimport) ' '' + + echo ' + + $(MsbuildThisFileDirectory)\Override.props + + ' > 'Directory.Build.props' + + echo " + + false + + + + None + false + + + false + + + + + MultiThreadedDLL + + + " > 'Override.props' + + if ($Dynamic) { + MSBuild.exe /p:Platform=x64 /p:Configuration=Release libiconv.vcxproj + } else { + MSBuild.exe /p:Platform=x64 /p:Configuration=ReleaseStatic libiconv.vcxproj + } + if (-not $?) { + Write-Host "Error: Failed to build libiconv" -ForegroundColor Red + Exit 1 + } +} + +if ($Dynamic) { + mv -Force $BuildTree\output\x64\Release\libiconv.lib libs\iconv-dynamic.lib + mv -Force $BuildTree\output\x64\Release\libiconv.dll dlls\ +} else { + mv -Force $BuildTree\output\x64\ReleaseStatic\libiconvStatic.lib libs\iconv.lib +} diff --git a/etc/win-ci/build-mpir.ps1 b/etc/win-ci/build-mpir.ps1 new file mode 100644 index 000000000000..24111e762b7b --- /dev/null +++ b/etc/win-ci/build-mpir.ps1 @@ -0,0 +1,48 @@ +param( + [Parameter(Mandatory)] [string] $BuildTree, + [switch] $Dynamic +) + +. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" + +[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) +Setup-Git -Path $BuildTree -Url https://github.com/BrianGladman/mpir.git -Ref dc82b0475dea84d5338356e49176c40be03a5bdf # master@{2023-02-10} + +Run-InDirectory $BuildTree { + $vsVersion = "vs$((& "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -property displayName) -replace '.*\b\d\d(\d\d)\b.*', '$1')" + + echo ' + + $(MsbuildThisFileDirectory)\Override.props + + ' > 'msvc\Directory.Build.props' + + echo ' + + + None + false + + + false + + + ' > 'msvc\Override.props' + + if ($Dynamic) { + MSBuild.exe /p:Platform=x64 /p:Configuration=Release "msvc\$vsVersion\dll_mpir_gc\dll_mpir_gc.vcxproj" + } else { + MSBuild.exe /p:Platform=x64 /p:Configuration=Release "msvc\$vsVersion\lib_mpir_gc\lib_mpir_gc.vcxproj" + } + if (-not $?) { + Write-Host "Error: Failed to build MPIR" -ForegroundColor Red + Exit 1 + } +} + +if ($Dynamic) { + mv -Force $BuildTree\dll\x64\Release\mpir.lib libs\mpir-dynamic.lib + mv -Force $BuildTree\dll\x64\Release\mpir.dll dlls\ +} else { + mv -Force $BuildTree\lib\x64\Release\mpir.lib libs\ +} diff --git a/etc/win-ci/build-openssl.ps1 b/etc/win-ci/build-openssl.ps1 new file mode 100644 index 000000000000..174f8bf7747d --- /dev/null +++ b/etc/win-ci/build-openssl.ps1 @@ -0,0 +1,38 @@ +param( + [Parameter(Mandatory)] [string] $BuildTree, + [Parameter(Mandatory)] [string] $Version, + [switch] $Dynamic +) + +. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" + +[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) +Setup-Git -Path $BuildTree -Url https://github.com/openssl/openssl -Ref openssl-$Version + +Run-InDirectory $BuildTree { + Replace-Text Configurations\10-main.conf '/Zi /Fdossl_static.pdb' '' + Replace-Text Configurations\10-main.conf '"/nologo /debug"' '"/nologo /debug:none"' + + if ($Dynamic) { + perl Configure VC-WIN64A no-tests + } else { + perl Configure VC-WIN64A /MT -static no-tests + } + nmake + if (-not $?) { + Write-Host "Error: Failed to build OpenSSL" -ForegroundColor Red + Exit 1 + } +} + +if ($Dynamic) { + $major = $Version -replace '\..*', '' + mv -Force $BuildTree\libcrypto.lib libs\crypto-dynamic.lib + mv -Force $BuildTree\libssl.lib libs\ssl-dynamic.lib + mv -Force $BuildTree\libcrypto-$major-x64.dll dlls\ + mv -Force $BuildTree\libssl-$major-x64.dll dlls\ +} else { + mv -Force $BuildTree\libcrypto.lib libs\crypto.lib + mv -Force $BuildTree\libssl.lib libs\ssl.lib +} +[IO.File]::WriteAllLines("libs\openssl_VERSION", $Version) diff --git a/etc/win-ci/build-pcre.ps1 b/etc/win-ci/build-pcre.ps1 new file mode 100644 index 000000000000..ea021e309de2 --- /dev/null +++ b/etc/win-ci/build-pcre.ps1 @@ -0,0 +1,49 @@ +param( + [Parameter(Mandatory)] [string] $BuildTree, + [Parameter(Mandatory)] [string] $Version, + [switch] $Dynamic +) + +function Find-7Zip { + $Path = Get-Command "7z" -CommandType Application -TotalCount 1 -ErrorAction SilentlyContinue + if ($Path) { return $Path.Path } + + $Path = "$env:ProgramFiles\7-Zip\7z.exe" + if (Test-Path -Path $Path -PathType Leaf) { return $Path } + + $Path = "${env:ProgramFiles(x86)}\7-Zip\7z.exe" + if (Test-Path -Path $Path -PathType Leaf) { return $Path } + + Write-Host "Error: Cannot locate 7-Zip executable" -ForegroundColor Red + Exit 1 +} + +. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" + +[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) +Invoke-WebRequest https://cs.stanford.edu/pub/exim/pcre/pcre-$Version.zip -OutFile pcre.zip +& (Find-7Zip) x pcre.zip +mv pcre-* $BuildTree +rm pcre.zip + +Run-InDirectory $BuildTree { + $args = "-DPCRE_BUILD_PCREGREP=OFF -DPCRE_BUILD_TESTS=OFF -DPCRE_BUILD_PCRECPP=OFF -DPCRE_SUPPORT_JIT=ON -DPCRE_SUPPORT_UNICODE_PROPERTIES=ON -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF" + if ($Dynamic) { + $args = "-DBUILD_SHARED_LIBS=ON $args" + } else { + $args = "-DBUILD_SHARED_LIBS=OFF -DPCRE_STATIC_RUNTIME=ON $args" + } + & $cmake . $args.split(' ') + & $cmake --build . --config Release + if (-not $?) { + Write-Host "Error: Failed to build PCRE" -ForegroundColor Red + Exit 1 + } +} + +if ($Dynamic) { + mv -Force $BuildTree\Release\pcre.lib libs\pcre-dynamic.lib + mv -Force $BuildTree\Release\pcre.dll dlls\ +} else { + mv -Force $BuildTree\Release\pcre.lib libs\ +} diff --git a/etc/win-ci/build-pcre2.ps1 b/etc/win-ci/build-pcre2.ps1 new file mode 100644 index 000000000000..ceab8668b999 --- /dev/null +++ b/etc/win-ci/build-pcre2.ps1 @@ -0,0 +1,32 @@ +param( + [Parameter(Mandatory)] [string] $BuildTree, + [Parameter(Mandatory)] [string] $Version, + [switch] $Dynamic +) + +. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" + +[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) +Setup-Git -Path $BuildTree -Url https://github.com/PCRE2Project/pcre2.git -Ref pcre2-$Version + +Run-InDirectory $BuildTree { + $args = "-DPCRE2_BUILD_PCRE2GREP=OFF -DPCRE2_BUILD_TESTS=OFF -DPCRE2_SUPPORT_UNICODE=ON -DPCRE2_SUPPORT_JIT=ON -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF" + if ($Dynamic) { + $args = "-DBUILD_STATIC_LIBS=OFF -DBUILD_SHARED_LIBS=ON $args" + } else { + $args = "-DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DPCRE2_STATIC_RUNTIME=ON $args" + } + & $cmake . $args.split(' ') + & $cmake --build . --config Release + if (-not $?) { + Write-Host "Error: Failed to build PCRE2" -ForegroundColor Red + Exit 1 + } +} + +if ($Dynamic) { + mv -Force $BuildTree\Release\pcre2-8.lib libs\pcre2-8-dynamic.lib + mv -Force $BuildTree\Release\pcre2-8.dll dlls\ +} else { + mv -Force $BuildTree\Release\pcre2-8-static.lib libs\pcre2-8.lib +} diff --git a/etc/win-ci/build-xml2.ps1 b/etc/win-ci/build-xml2.ps1 new file mode 100644 index 000000000000..3485a357b270 --- /dev/null +++ b/etc/win-ci/build-xml2.ps1 @@ -0,0 +1,32 @@ +param( + [Parameter(Mandatory)] [string] $BuildTree, + [Parameter(Mandatory)] [string] $Version, + [switch] $Dynamic +) + +. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" + +[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) +Setup-Git -Path $BuildTree -Url https://gitlab.gnome.org/GNOME/libxml2.git -Ref v$Version + +Run-InDirectory $BuildTree { + $args = "-DLIBXML2_WITH_TESTS=OFF -DLIBXML2_WITH_PROGRAMS=OFF -DLIBXML2_WITH_HTTP=OFF -DLIBXML2_WITH_FTP=OFF -DLIBXML2_WITH_ICONV=OFF -DLIBXML2_WITH_LZMA=OFF -DLIBXML2_WITH_PYTHON=OFF -DLIBXML2_WITH_ZLIB=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF" + if ($Dynamic) { + $args = "-DBUILD_SHARED_LIBS=ON $args" + } else { + $args = "-DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded $args" + } + & $cmake . $args.split(' ') + & $cmake --build . --config Release + if (-not $?) { + Write-Host "Error: Failed to build libxml2" -ForegroundColor Red + Exit 1 + } +} + +if ($Dynamic) { + mv -Force $BuildTree\Release\libxml2.lib libs\xml2-dynamic.lib + mv -Force $BuildTree\Release\libxml2.dll dlls\ +} else { + mv -Force $BuildTree\Release\libxml2s.lib libs\xml2.lib +} diff --git a/etc/win-ci/build-yaml.ps1 b/etc/win-ci/build-yaml.ps1 new file mode 100644 index 000000000000..5f0972003f71 --- /dev/null +++ b/etc/win-ci/build-yaml.ps1 @@ -0,0 +1,32 @@ +param( + [Parameter(Mandatory)] [string] $BuildTree, + [Parameter(Mandatory)] [string] $Version, + [switch] $Dynamic +) + +. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" + +[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) +Setup-Git -Path $BuildTree -Url https://github.com/yaml/libyaml.git -Ref $Version + +Run-InDirectory $BuildTree { + $args = "-DBUILD_TESTING=OFF -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF" + if ($Dynamic) { + $args = "-DBUILD_SHARED_LIBS=ON $args" + } else { + $args = "-DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded $args" + } + & $cmake . $args.split(' ') + & $cmake --build . --config Release + if (-not $?) { + Write-Host "Error: Failed to build libyaml" -ForegroundColor Red + Exit 1 + } +} + +if ($Dynamic) { + mv -Force $BuildTree\Release\yaml.lib libs\yaml-dynamic.lib + mv -Force $BuildTree\Release\yaml.dll dlls\ +} else { + mv -Force $BuildTree\Release\yaml.lib libs\ +} diff --git a/etc/win-ci/build-z.ps1 b/etc/win-ci/build-z.ps1 new file mode 100644 index 000000000000..e12794543dc8 --- /dev/null +++ b/etc/win-ci/build-z.ps1 @@ -0,0 +1,32 @@ +param( + [Parameter(Mandatory)] [string] $BuildTree, + [Parameter(Mandatory)] [string] $Version, + [switch] $Dynamic +) + +. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" + +[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) +Setup-Git -Path $BuildTree -Url https://github.com/madler/zlib.git -Ref v$Version + +Run-InDirectory $BuildTree { + $args = "-DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF" + if ($Dynamic) { + $args = "-DBUILD_SHARED_LIBS=ON $args" + } else { + $args = "-DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded $args" + } + & $cmake . $args.split(' ') + & $cmake --build . --target $(if ($Dynamic) { 'zlib' } else { 'zlibstatic' }) --config Release + if (-not $?) { + Write-Host "Error: Failed to build zlib" -ForegroundColor Red + Exit 1 + } +} + +if ($Dynamic) { + mv -Force $BuildTree\Release\zlib.lib libs\z-dynamic.lib + mv -Force $BuildTree\Release\zlib1.dll dlls\ +} else { + mv -Force $BuildTree\Release\zlibstatic.lib libs\z.lib +} diff --git a/etc/win-ci/setup.ps1 b/etc/win-ci/setup.ps1 new file mode 100644 index 000000000000..c5c93f0465fa --- /dev/null +++ b/etc/win-ci/setup.ps1 @@ -0,0 +1,78 @@ +function Run-InDirectory { + param( + [Parameter(Mandatory)] [string] $Path, + [Parameter(Mandatory)] [scriptblock] $ScriptBlock + ) + + [void](New-Item -Name $Path -ItemType Directory -Force) + Push-Location $Path + [Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath + try { & $ScriptBlock } finally { + Pop-Location + [Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath + } +} + +function Find-Git { + $Path = Get-Command "git" -CommandType Application -TotalCount 1 -ErrorAction SilentlyContinue + if ($Path) { return $Path.Path } + + $Path = "$env:ProgramFiles\Git\cmd\git.exe" + if (Test-Path -Path $Path -PathType Leaf) { return $Path } + + Write-Host "Error: Cannot locate Git executable" -ForegroundColor Red + Exit 1 +} + +function Find-CMake { + $Path = Get-Command "cmake" -CommandType Application -TotalCount 1 -ErrorAction SilentlyContinue + if ($Path) { return $Path.Path } + + $Path = "$env:ProgramFiles\CMake\bin\cmake.exe" + if (Test-Path -Path $Path -PathType Leaf) { return $Path } + + Write-Host "Error: Cannot locate CMake executable" -ForegroundColor Red + Exit 1 +} + +function Setup-Git { + param( + [Parameter(Mandatory)] [string] $Path, + [Parameter(Mandatory)] [string] $Url, + [string] $Ref = $null + ) + + if (-not (Test-Path $Path)) { + $args = "clone", "--config", "core.autocrlf=false", $Url, $Path + Write-Host "$git $args" -ForegroundColor Cyan + & $git $args + if (-not $?) { + Write-Host "Error: Failed to clone Git repository" -ForegroundColor Red + Exit 1 + } + } + + if ($Ref) { + Run-InDirectory $Path { + Write-Host "$git checkout $Ref" -ForegroundColor Cyan + & $git checkout $Ref + } + } +} + +function Replace-Text { + param( + [Parameter(Mandatory)] [string] $Path, + [Parameter(Mandatory)] [string] $Pattern, + [Parameter(Mandatory)] [AllowEmptyString()] [string] $Replacement + ) + + $content = [System.IO.File]::ReadAllText($Path).Replace($Pattern, $Replacement) + [System.IO.File]::WriteAllText($Path, $content) +} + +$git = Find-Git +$cmake = Find-CMake + +[void](New-Item -Name libs -ItemType Directory -Force) +[void](New-Item -Name dlls -ItemType Directory -Force) From 93cb352dd29acbc061a48da9fc90e1ade1b6e93e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 25 May 2023 02:20:27 +0800 Subject: [PATCH 0552/1551] Ensure `Socket` checks `WinError.wsa_value` on Windows, not `Errno.value` (#13494) --- src/crystal/system/socket.cr | 2 ++ src/crystal/system/unix/socket.cr | 7 ++++++- src/crystal/system/wasi/socket.cr | 4 ++++ src/crystal/system/win32/socket.cr | 27 +++++++++++++-------------- src/socket.cr | 3 +-- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr index de199376615b..552957bcff9a 100644 --- a/src/crystal/system/socket.cr +++ b/src/crystal/system/socket.cr @@ -53,6 +53,8 @@ module Crystal::System::Socket # private def system_linger=(val) + # private def system_getsockopt(fd, optname, optval, level = LibC::SOL_SOCKET, &) + # private def system_getsockopt(fd, optname, optval, level = LibC::SOL_SOCKET) # private def system_setsockopt(fd, optname, optval, level = LibC::SOL_SOCKET) diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 2b96cd6948f0..222ec7216fb9 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -203,11 +203,16 @@ module Crystal::System::Socket ret end + private def system_getsockopt(fd, optname, optval, level = LibC::SOL_SOCKET) + system_getsockopt(fd, optname, optval, level) { |value| return value } + raise ::Socket::Error.from_errno("getsockopt #{optname}") + end + private def system_setsockopt(fd, optname, optval, level = LibC::SOL_SOCKET) optsize = LibC::SocklenT.new(sizeof(typeof(optval))) ret = LibC.setsockopt(fd, level, optname, pointerof(optval), optsize) - raise ::Socket::Error.from_errno("setsockopt") if ret == -1 + raise ::Socket::Error.from_errno("setsockopt #{optname}") if ret == -1 ret end diff --git a/src/crystal/system/wasi/socket.cr b/src/crystal/system/wasi/socket.cr index 71122ca46a33..add7b751eada 100644 --- a/src/crystal/system/wasi/socket.cr +++ b/src/crystal/system/wasi/socket.cr @@ -119,6 +119,10 @@ module Crystal::System::Socket raise NotImplementedError.new "Crystal::System::Socket#system_getsockopt" end + private def system_getsockopt(fd, optname, optval, level = LibC::SOL_SOCKET) + raise NotImplementedError.new "Crystal::System::Socket#system_getsockopt" + end + private def system_setsockopt(fd, optname, optval, level = LibC::SOL_SOCKET) raise NotImplementedError.new "Crystal::System::Socket#system_setsockopt" end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index d1fe5baf2b46..86a787cef1d3 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -181,7 +181,7 @@ module Crystal::System::Socket private def system_listen(backlog, &) unless LibC.listen(fd, backlog) == 0 - yield ::Socket::Error.from_errno("Listen failed") + yield ::Socket::Error.from_wsa_error("Listen failed") end end @@ -276,7 +276,7 @@ module Crystal::System::Socket ret = LibC.WSASendTo(fd, pointerof(wsabuf), 1, out bytes_sent, 0, addr, addr.size, overlapped, nil) {ret, bytes_sent} end - raise ::Socket::Error.from_errno("Error sending datagram to #{addr}") if bytes_written == -1 + raise ::Socket::Error.from_wsa_error("Error sending datagram to #{addr}") if bytes_written == -1 # to_i32 is fine because string/slice sizes are an Int32 bytes_written.to_i32 @@ -304,13 +304,13 @@ module Crystal::System::Socket private def system_close_read if LibC.shutdown(fd, LibC::SH_RECEIVE) != 0 - raise ::Socket::Error.from_errno("shutdown read") + raise ::Socket::Error.from_wsa_error("shutdown read") end end private def system_close_write if LibC.shutdown(fd, LibC::SH_SEND) != 0 - raise ::Socket::Error.from_errno("shutdown write") + raise ::Socket::Error.from_wsa_error("shutdown write") end end @@ -391,20 +391,19 @@ module Crystal::System::Socket val end - def system_getsockopt(handle, optname, optval, level = LibC::SOL_SOCKET, &) + private def system_getsockopt(handle, optname, optval, level = LibC::SOL_SOCKET, &) optsize = sizeof(typeof(optval)) ret = LibC.getsockopt(handle, level, optname, pointerof(optval).as(UInt8*), pointerof(optsize)) - - if ret.zero? - yield optval - else - raise ::Socket::Error.from_wsa_error("getsockopt #{optname}") - end - + yield optval if ret == 0 ret end - def system_setsockopt(handle, optname, optval, level = LibC::SOL_SOCKET) + private def system_getsockopt(fd, optname, optval, level = LibC::SOL_SOCKET) + system_getsockopt(fd, optname, optval, level) { |value| return value } + raise ::Socket::Error.from_wsa_error("getsockopt #{optname}") + end + + private def system_setsockopt(handle, optname, optval, level = LibC::SOL_SOCKET) optsize = sizeof(typeof(optval)) ret = LibC.setsockopt(handle, level, optname, pointerof(optval).as(UInt8*), optsize) @@ -493,7 +492,7 @@ module Crystal::System::Socket when Errno::EINTR, Errno::EINPROGRESS # ignore else - return ::Socket::Error.from_errno("Error closing socket") + return ::Socket::Error.from_wsa_error("Error closing socket") end end end diff --git a/src/socket.cr b/src/socket.cr index 86c75db86fb2..32a552fa34d4 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -345,8 +345,7 @@ class Socket < IO # Returns the modified *optval*. protected def getsockopt(optname, optval, level = LibC::SOL_SOCKET) - getsockopt(optname, optval, level) { |value| return value } - raise Socket::Error.from_errno("getsockopt") + system_getsockopt(fd, optname, optval, level) end protected def getsockopt(optname, optval, level = LibC::SOL_SOCKET, &) From 61a2c52d1e15c09018efe60c8c5a52540f112cfa Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 25 May 2023 02:20:40 +0800 Subject: [PATCH 0553/1551] Make `fcntl` defined on all platforms (#13495) --- src/crystal/system/wasi/file_descriptor.cr | 6 ++++++ src/crystal/system/win32/file_descriptor.cr | 4 ++++ src/crystal/system/win32/socket.cr | 4 +--- src/io/file_descriptor.cr | 14 ++++++-------- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/crystal/system/wasi/file_descriptor.cr b/src/crystal/system/wasi/file_descriptor.cr index 9a2bc1a0bd96..6eae9b8832d2 100644 --- a/src/crystal/system/wasi/file_descriptor.cr +++ b/src/crystal/system/wasi/file_descriptor.cr @@ -11,6 +11,12 @@ module Crystal::System::FileDescriptor raise NotImplementedError.new "Crystal::System::FileDescriptor.pipe" end + def self.fcntl(fd, cmd, arg = 0) + r = LibC.fcntl(fd, cmd, arg) + raise IO::Error.from_errno("fcntl() failed") if r == -1 + r + end + private def system_blocking_init(value) end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 2bc4a7d55b37..84cfe12c6650 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -79,6 +79,10 @@ module Crystal::System::FileDescriptor false end + def self.fcntl(fd, cmd, arg = 0) + raise NotImplementedError.new "Crystal::System::FileDescriptor.fcntl" + end + private def windows_handle FileDescriptor.windows_handle!(fd) end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 86a787cef1d3..88278544dbf0 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -438,9 +438,7 @@ module Crystal::System::Socket end def self.fcntl(fd, cmd, arg = 0) - ret = LibC.fcntl fd, cmd, arg - raise Socket::Error.from_errno("fcntl() failed") if ret == -1 - ret + raise NotImplementedError.new "Crystal::System::Socket.fcntl" end private def system_tty? diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 2929a62a410a..c367d2ecaf74 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -56,15 +56,13 @@ class IO::FileDescriptor < IO self.system_close_on_exec = value end - {% unless flag?(:win32) %} - def self.fcntl(fd, cmd, arg = 0) - Crystal::System::FileDescriptor.fcntl(fd, cmd, arg) - end + def self.fcntl(fd, cmd, arg = 0) + Crystal::System::FileDescriptor.fcntl(fd, cmd, arg) + end - def fcntl(cmd, arg = 0) - Crystal::System::FileDescriptor.fcntl(fd, cmd, arg) - end - {% end %} + def fcntl(cmd, arg = 0) + Crystal::System::FileDescriptor.fcntl(fd, cmd, arg) + end # Returns a `File::Info` object for this file descriptor, or raises # `IO::Error` in case of an error. From 0627b47967fc0dfabef0e8642de2478f8072cfe2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 25 May 2023 17:47:32 +0800 Subject: [PATCH 0554/1551] Make compiler aware of output extension when building programs (#13370) --- src/compiler/crystal/codegen/link.cr | 8 -------- src/compiler/crystal/codegen/target.cr | 15 ++++++++++++++ src/compiler/crystal/command.cr | 27 +++++++++++++++----------- src/compiler/crystal/compiler.cr | 9 ++++----- src/compiler/crystal/macros/macros.cr | 2 +- 5 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index dbda33033695..483edc500b35 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -104,14 +104,6 @@ module Crystal end class Program - def object_extension - case - when has_flag?("windows") then ".obj" - when has_flag?("wasm32") then ".wasm" - else ".o" - end - end - def lib_flags has_flag?("windows") ? lib_flags_windows : lib_flags_posix end diff --git a/src/compiler/crystal/codegen/target.cr b/src/compiler/crystal/codegen/target.cr index b4f8c2f9f4a3..51881c5a3571 100644 --- a/src/compiler/crystal/codegen/target.cr +++ b/src/compiler/crystal/codegen/target.cr @@ -77,6 +77,21 @@ class Crystal::Codegen::Target end end + def executable_extension + case + when windows? then ".exe" + else "" + end + end + + def object_extension + case + when windows? then ".obj" + when @architecture == "wasm32" then ".wasm" + else ".o" + end + end + def macos? @environment.starts_with?("darwin") || @environment.starts_with?("macos") end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 67b82425fad9..a8b4e43fac77 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -327,14 +327,13 @@ class Crystal::Command compiler : Compiler, sources : Array(Compiler::Source), output_filename : String, - original_output_filename : String, arguments : Array(String), specified_output : Bool, hierarchy_exp : String?, cursor_location : String?, output_format : String? do def compile(output_filename = self.output_filename) - compiler.emit_base_filename = original_output_filename + compiler.emit_base_filename = output_filename.rchop(File.extname(output_filename)) compiler.compile sources, output_filename end @@ -521,17 +520,23 @@ class Crystal::Command if has_stdin_filename sources << Compiler::Source.new(filenames.shift, STDIN.gets_to_end) end - sources += gather_sources(filenames) - first_filename = sources.first.filename - first_file_ext = File.extname(first_filename) - original_output_filename = File.basename(first_filename, first_file_ext) + sources.concat gather_sources(filenames) - # Check if we'll overwrite the main source file - if first_file_ext.empty? && !output_filename && !no_codegen && !run && first_filename == File.expand_path(original_output_filename) - error "compilation will overwrite source file '#{Crystal.relative_filename(first_filename)}', either change its extension to '.cr' or specify an output file with '-o'" + output_extension = compiler.cross_compile? ? compiler.codegen_target.object_extension : compiler.codegen_target.executable_extension + if output_filename + if File.extname(output_filename).empty? + output_filename += output_extension + end + else + first_filename = sources.first.filename + output_filename = "#{::Path[first_filename].stem}#{output_extension}" + + # Check if we'll overwrite the main source file + if !no_codegen && !run && first_filename == File.expand_path(output_filename) + error "compilation will overwrite source file '#{Crystal.relative_filename(first_filename)}', either change its extension to '.cr' or specify an output file with '-o'" + end end - output_filename ||= original_output_filename output_format ||= "text" unless output_format.in?("text", "json") error "You have input an invalid format, only text and JSON are supported" @@ -543,7 +548,7 @@ class Crystal::Command error "can't use `#{output_filename}` as output filename because it's a directory" end - @config = CompilerConfig.new compiler, sources, output_filename, original_output_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format + @config = CompilerConfig.new compiler, sources, output_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format end private def gather_sources(filenames) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 9d8e3b156975..300ea71ae75b 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -39,7 +39,7 @@ module Crystal # If `true`, doesn't generate an executable but instead # creates a `.o` file and outputs a command line to link # it in the target machine. - property cross_compile = false + property? cross_compile = false # Compiler flags. These will be true when checked in macro # code by the `flag?(...)` macro method. @@ -306,17 +306,16 @@ module Crystal private def cross_compile(program, units, output_filename) unit = units.first llvm_mod = unit.llvm_mod - object_name = output_filename + program.object_extension @progress_tracker.stage("Codegen (bc+obj)") do optimize llvm_mod if @release unit.emit(@emit_targets, emit_base_filename || output_filename) - target_machine.emit_obj_to_file llvm_mod, object_name + target_machine.emit_obj_to_file llvm_mod, output_filename end - _, command, args = linker_command(program, [object_name], output_filename, nil) + _, command, args = linker_command(program, [output_filename], output_filename, nil) print_command(command, args) end @@ -708,7 +707,7 @@ module Crystal @name = "#{@name[0..16]}-#{::Crystal::Digest::MD5.hexdigest(@name)}" end - @object_extension = program.object_extension + @object_extension = compiler.codegen_target.object_extension end def compile diff --git a/src/compiler/crystal/macros/macros.cr b/src/compiler/crystal/macros/macros.cr index c74a482f9d03..b5d1bb281c1a 100644 --- a/src/compiler/crystal/macros/macros.cr +++ b/src/compiler/crystal/macros/macros.cr @@ -127,7 +127,7 @@ class Crystal::Program # When cross-compiling, the host compiler shouldn't copy the config for # the target compiler and use the system defaults instead. # TODO: Add configuration overrides for host compiler to CLI. - unless compiler.cross_compile + unless compiler.cross_compile? host_compiler.flags = compiler.flags host_compiler.dump_ll = compiler.dump_ll? host_compiler.link_flags = compiler.link_flags From 55a84d8d32980fd4024e6e64040d941d3e4c2229 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 26 May 2023 01:58:20 +0800 Subject: [PATCH 0555/1551] Remove incorrect `CRYSTAL` in comments (#13500) --- spec/std/string/grapheme_break_spec.cr | 134 ++++++++++++------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/spec/std/string/grapheme_break_spec.cr b/spec/std/string/grapheme_break_spec.cr index 1e757064d43f..9ff17889fe05 100644 --- a/spec/std/string/grapheme_break_spec.cr +++ b/spec/std/string/grapheme_break_spec.cr @@ -10,8 +10,8 @@ require "./spec_helper" describe "String#each_grapheme" do it_iterates_graphemes " ", [' ', ' '] # ÷ [0.2] SPACE (Other) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes " \u0308 ", [" \u0308", ' '] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes " \r", [' ', '\r'] # ÷ [0.2] SPACE (Other) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes " \u0308\r", [" \u0308", '\r'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes " \r", [' ', '\r'] # ÷ [0.2] SPACE (Other) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes " \u0308\r", [" \u0308", '\r'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes " \n", [' ', '\n'] # ÷ [0.2] SPACE (Other) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes " \u0308\n", [" \u0308", '\n'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes " \u0001", [' ', '\u0001'] # ÷ [0.2] SPACE (Other) ÷ [5.0] (Control) ÷ [0.3] @@ -42,44 +42,44 @@ describe "String#each_grapheme" do it_iterates_graphemes " \u0308\u200D", [" \u0308\u200D"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u0378", [' ', '\u0378'] # ÷ [0.2] SPACE (Other) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes " \u0308\u0378", [" \u0308", '\u0378'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\r ", ['\r', ' '] # ÷ [0.2] (CRYSTAL) ÷ [4.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\r\u0308 ", ['\r', '\u0308', ' '] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\r\r", ['\r', '\r'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\r\u0308\r", ['\r', '\u0308', '\r'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\r\n", ["\r\n"] # ÷ [0.2] (CRYSTAL) × [3.0] (LF) ÷ [0.3] - it_iterates_graphemes "\r\u0308\n", ['\r', '\u0308', '\n'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] - it_iterates_graphemes "\r\u0001", ['\r', '\u0001'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] (Control) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0001", ['\r', '\u0308', '\u0001'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\r\u034F", ['\r', '\u034F'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u034F", ['\r', "\u0308\u034F"] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\r\u{1F1E6}", ['\r', '\u{1F1E6}'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u{1F1E6}", ['\r', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\r\u0600", ['\r', '\u0600'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0600", ['\r', '\u0308', '\u0600'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\r\u0903", ['\r', '\u0903'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0903", ['\r', "\u0308\u0903"] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\r\u1100", ['\r', '\u1100'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u1100", ['\r', '\u0308', '\u1100'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\r\u1160", ['\r', '\u1160'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u1160", ['\r', '\u0308', '\u1160'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\r\u11A8", ['\r', '\u11A8'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u11A8", ['\r', '\u0308', '\u11A8'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\r\uAC00", ['\r', '\uAC00'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\r\u0308\uAC00", ['\r', '\u0308', '\uAC00'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\r\uAC01", ['\r', '\uAC01'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\r\u0308\uAC01", ['\r', '\u0308', '\uAC01'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\r\u231A", ['\r', '\u231A'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u231A", ['\r', '\u0308', '\u231A'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\r\u0300", ['\r', '\u0300'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0300", ['\r', "\u0308\u0300"] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\r\u200D", ['\r', '\u200D'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u200D", ['\r', "\u0308\u200D"] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\r\u0378", ['\r', '\u0378'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] (Other) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0378", ['\r', '\u0308', '\u0378'] # ÷ [0.2] (CRYSTAL) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\r ", ['\r', ' '] # ÷ [0.2] (CR) ÷ [4.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\r\u0308 ", ['\r', '\u0308', ' '] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\r\r", ['\r', '\r'] # ÷ [0.2] (CR) ÷ [4.0] (CR) ÷ [0.3] + it_iterates_graphemes "\r\u0308\r", ['\r', '\u0308', '\r'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\r\n", ["\r\n"] # ÷ [0.2] (CR) × [3.0] (LF) ÷ [0.3] + it_iterates_graphemes "\r\u0308\n", ['\r', '\u0308', '\n'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\r\u0001", ['\r', '\u0001'] # ÷ [0.2] (CR) ÷ [4.0] (Control) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0001", ['\r', '\u0308', '\u0001'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\r\u034F", ['\r', '\u034F'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u034F", ['\r', "\u0308\u034F"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\r\u{1F1E6}", ['\r', '\u{1F1E6}'] # ÷ [0.2] (CR) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u{1F1E6}", ['\r', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\r\u0600", ['\r', '\u0600'] # ÷ [0.2] (CR) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0600", ['\r', '\u0308', '\u0600'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\r\u0903", ['\r', '\u0903'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0903", ['\r', "\u0308\u0903"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\r\u1100", ['\r', '\u1100'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u1100", ['\r', '\u0308', '\u1100'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\r\u1160", ['\r', '\u1160'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u1160", ['\r', '\u0308', '\u1160'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\r\u11A8", ['\r', '\u11A8'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u11A8", ['\r', '\u0308', '\u11A8'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\r\uAC00", ['\r', '\uAC00'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\r\u0308\uAC00", ['\r', '\u0308', '\uAC00'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\r\uAC01", ['\r', '\uAC01'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\r\u0308\uAC01", ['\r', '\u0308', '\uAC01'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\r\u231A", ['\r', '\u231A'] # ÷ [0.2] (CR) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u231A", ['\r', '\u0308', '\u231A'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\r\u0300", ['\r', '\u0300'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0300", ['\r', "\u0308\u0300"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u200D", ['\r', '\u200D'] # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u200D", ['\r', "\u0308\u200D"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u0378", ['\r', '\u0378'] # ÷ [0.2] (CR) ÷ [4.0] (Other) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0378", ['\r', '\u0308', '\u0378'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\n ", ['\n', ' '] # ÷ [0.2] (LF) ÷ [4.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\n\u0308 ", ['\n', '\u0308', ' '] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\n\r", ['\n', '\r'] # ÷ [0.2] (LF) ÷ [4.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\n\u0308\r", ['\n', '\u0308', '\r'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\n\r", ['\n', '\r'] # ÷ [0.2] (LF) ÷ [4.0] (CR) ÷ [0.3] + it_iterates_graphemes "\n\u0308\r", ['\n', '\u0308', '\r'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\n\n", ['\n', '\n'] # ÷ [0.2] (LF) ÷ [4.0] (LF) ÷ [0.3] it_iterates_graphemes "\n\u0308\n", ['\n', '\u0308', '\n'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\n\u0001", ['\n', '\u0001'] # ÷ [0.2] (LF) ÷ [4.0] (Control) ÷ [0.3] @@ -112,8 +112,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\n\u0308\u0378", ['\n', '\u0308', '\u0378'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0001 ", ['\u0001', ' '] # ÷ [0.2] (Control) ÷ [4.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0001\u0308 ", ['\u0001', '\u0308', ' '] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0001\r", ['\u0001', '\r'] # ÷ [0.2] (Control) ÷ [4.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u0001\u0308\r", ['\u0001', '\u0308', '\r'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u0001\r", ['\u0001', '\r'] # ÷ [0.2] (Control) ÷ [4.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\r", ['\u0001', '\u0308', '\r'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u0001\n", ['\u0001', '\n'] # ÷ [0.2] (Control) ÷ [4.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\n", ['\u0001', '\u0308', '\n'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0001\u0001", ['\u0001', '\u0001'] # ÷ [0.2] (Control) ÷ [4.0] (Control) ÷ [0.3] @@ -146,8 +146,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0001\u0308\u0378", ['\u0001', '\u0308', '\u0378'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u034F ", ['\u034F', ' '] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u034F\u0308 ", ["\u034F\u0308", ' '] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u034F\r", ['\u034F', '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\r", ["\u034F\u0308", '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u034F\r", ['\u034F', '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u034F\u0308\r", ["\u034F\u0308", '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u034F\n", ['\u034F', '\n'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u034F\u0308\n", ["\u034F\u0308", '\n'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u034F\u0001", ['\u034F', '\u0001'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (Control) ÷ [0.3] @@ -180,8 +180,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u034F\u0308\u0378", ["\u034F\u0308", '\u0378'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u{1F1E6} ", ['\u{1F1E6}', ' '] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308 ", ["\u{1F1E6}\u0308", ' '] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\r", ['\u{1F1E6}', '\r'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\u0308\r", ["\u{1F1E6}\u0308", '\r'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\r", ['\u{1F1E6}', '\r'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\r", ["\u{1F1E6}\u0308", '\r'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\n", ['\u{1F1E6}', '\n'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\n", ["\u{1F1E6}\u0308", '\n'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0001", ['\u{1F1E6}', '\u0001'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (Control) ÷ [0.3] @@ -214,8 +214,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u{1F1E6}\u0308\u0378", ["\u{1F1E6}\u0308", '\u0378'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0600 ", ["\u0600 "] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0600\u0308 ", ["\u0600\u0308", ' '] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0600\r", ['\u0600', '\r'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u0600\u0308\r", ["\u0600\u0308", '\r'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u0600\r", ['\u0600', '\r'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\r", ["\u0600\u0308", '\r'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u0600\n", ['\u0600', '\n'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\n", ["\u0600\u0308", '\n'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0600\u0001", ['\u0600', '\u0001'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (Control) ÷ [0.3] @@ -248,8 +248,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0600\u0308\u0378", ["\u0600\u0308", '\u0378'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0903 ", ['\u0903', ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0903\u0308 ", ["\u0903\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0903\r", ['\u0903', '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\r", ["\u0903\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u0903\r", ['\u0903', '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\r", ["\u0903\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u0903\n", ['\u0903', '\n'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0903\u0308\n", ["\u0903\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0903\u0001", ['\u0903', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (Control) ÷ [0.3] @@ -282,8 +282,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0903\u0308\u0378", ["\u0903\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u1100 ", ['\u1100', ' '] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u1100\u0308 ", ["\u1100\u0308", ' '] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u1100\r", ['\u1100', '\r'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u1100\u0308\r", ["\u1100\u0308", '\r'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u1100\r", ['\u1100', '\r'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\r", ["\u1100\u0308", '\r'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u1100\n", ['\u1100', '\n'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\n", ["\u1100\u0308", '\n'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u1100\u0001", ['\u1100', '\u0001'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (Control) ÷ [0.3] @@ -316,8 +316,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1100\u0308\u0378", ["\u1100\u0308", '\u0378'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u1160 ", ['\u1160', ' '] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u1160\u0308 ", ["\u1160\u0308", ' '] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u1160\r", ['\u1160', '\r'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u1160\u0308\r", ["\u1160\u0308", '\r'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u1160\r", ['\u1160', '\r'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\r", ["\u1160\u0308", '\r'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u1160\n", ['\u1160', '\n'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\n", ["\u1160\u0308", '\n'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u1160\u0001", ['\u1160', '\u0001'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (Control) ÷ [0.3] @@ -350,8 +350,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1160\u0308\u0378", ["\u1160\u0308", '\u0378'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u11A8 ", ['\u11A8', ' '] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308 ", ["\u11A8\u0308", ' '] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u11A8\r", ['\u11A8', '\r'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u11A8\u0308\r", ["\u11A8\u0308", '\r'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u11A8\r", ['\u11A8', '\r'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\r", ["\u11A8\u0308", '\r'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u11A8\n", ['\u11A8', '\n'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\n", ["\u11A8\u0308", '\n'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u11A8\u0001", ['\u11A8', '\u0001'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (Control) ÷ [0.3] @@ -384,8 +384,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u11A8\u0308\u0378", ["\u11A8\u0308", '\u0378'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\uAC00 ", ['\uAC00', ' '] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308 ", ["\uAC00\u0308", ' '] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\uAC00\r", ['\uAC00', '\r'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\uAC00\u0308\r", ["\uAC00\u0308", '\r'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\uAC00\r", ['\uAC00', '\r'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\r", ["\uAC00\u0308", '\r'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\uAC00\n", ['\uAC00', '\n'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\n", ["\uAC00\u0308", '\n'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\uAC00\u0001", ['\uAC00', '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (Control) ÷ [0.3] @@ -418,8 +418,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC00\u0308\u0378", ["\uAC00\u0308", '\u0378'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\uAC01 ", ['\uAC01', ' '] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308 ", ["\uAC01\u0308", ' '] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\uAC01\r", ['\uAC01', '\r'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\uAC01\u0308\r", ["\uAC01\u0308", '\r'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\uAC01\r", ['\uAC01', '\r'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\r", ["\uAC01\u0308", '\r'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\uAC01\n", ['\uAC01', '\n'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\n", ["\uAC01\u0308", '\n'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\uAC01\u0001", ['\uAC01', '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (Control) ÷ [0.3] @@ -452,8 +452,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC01\u0308\u0378", ["\uAC01\u0308", '\u0378'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u231A ", ['\u231A', ' '] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u231A\u0308 ", ["\u231A\u0308", ' '] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u231A\r", ['\u231A', '\r'] # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u231A\u0308\r", ["\u231A\u0308", '\r'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u231A\r", ['\u231A', '\r'] # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\r", ["\u231A\u0308", '\r'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u231A\n", ['\u231A', '\n'] # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\n", ["\u231A\u0308", '\n'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u231A\u0001", ['\u231A', '\u0001'] # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (Control) ÷ [0.3] @@ -486,8 +486,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u231A\u0308\u0378", ["\u231A\u0308", '\u0378'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0300 ", ['\u0300', ' '] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0300\u0308 ", ["\u0300\u0308", ' '] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0300\r", ['\u0300', '\r'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u0300\u0308\r", ["\u0300\u0308", '\r'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u0300\r", ['\u0300', '\r'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\r", ["\u0300\u0308", '\r'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u0300\n", ['\u0300', '\n'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\n", ["\u0300\u0308", '\n'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0300\u0001", ['\u0300', '\u0001'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] @@ -520,8 +520,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0300\u0308\u0378", ["\u0300\u0308", '\u0378'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u200D ", ['\u200D', ' '] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u200D\u0308 ", ["\u200D\u0308", ' '] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u200D\r", ['\u200D', '\r'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u200D\u0308\r", ["\u200D\u0308", '\r'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u200D\r", ['\u200D', '\r'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\r", ["\u200D\u0308", '\r'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u200D\n", ['\u200D', '\n'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\n", ["\u200D\u0308", '\n'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u200D\u0001", ['\u200D', '\u0001'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] @@ -554,8 +554,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u200D\u0308\u0378", ["\u200D\u0308", '\u0378'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0378 ", ['\u0378', ' '] # ÷ [0.2] (Other) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0378\u0308 ", ["\u0378\u0308", ' '] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0378\r", ['\u0378', '\r'] # ÷ [0.2] (Other) ÷ [5.0] (CRYSTAL) ÷ [0.3] - it_iterates_graphemes "\u0378\u0308\r", ["\u0378\u0308", '\r'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CRYSTAL) ÷ [0.3] + it_iterates_graphemes "\u0378\r", ['\u0378', '\r'] # ÷ [0.2] (Other) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\r", ["\u0378\u0308", '\r'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] it_iterates_graphemes "\u0378\n", ['\u0378', '\n'] # ÷ [0.2] (Other) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\n", ["\u0378\u0308", '\n'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0378\u0001", ['\u0378', '\u0001'] # ÷ [0.2] (Other) ÷ [5.0] (Control) ÷ [0.3] @@ -586,7 +586,7 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0378\u0308\u200D", ["\u0378\u0308\u200D"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0378\u0378", ['\u0378', '\u0378'] # ÷ [0.2] (Other) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u0378", ["\u0378\u0308", '\u0378'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\r\na\n\u0308", ["\r\n", 'a', '\n', '\u0308'] # ÷ [0.2] (CRYSTAL) × [3.0] (LF) ÷ [4.0] LATIN SMALL LETTER A (Other) ÷ [5.0] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\na\n\u0308", ["\r\n", 'a', '\n', '\u0308'] # ÷ [0.2] (CR) × [3.0] (LF) ÷ [4.0] LATIN SMALL LETTER A (Other) ÷ [5.0] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "a\u0308", ["a\u0308"] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u200D\u0646", [" \u200D", '\u0646'] # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC LETTER NOON (Other) ÷ [0.3] it_iterates_graphemes "\u0646\u200D ", ["\u0646\u200D", ' '] # ÷ [0.2] ARABIC LETTER NOON (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] From b2498a37e4c9588baca0a927e8a69f1136742c18 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 26 May 2023 01:58:24 +0800 Subject: [PATCH 0556/1551] Update list of Windows time zones (#13501) --- scripts/generate_windows_zone_names.cr | 27 +++--- src/crystal/system/win32/zone_names.cr | 114 +++++++++++++------------ 2 files changed, 70 insertions(+), 71 deletions(-) diff --git a/scripts/generate_windows_zone_names.cr b/scripts/generate_windows_zone_names.cr index 9d70ff371373..6ab301033cf3 100644 --- a/scripts/generate_windows_zone_names.cr +++ b/scripts/generate_windows_zone_names.cr @@ -1,32 +1,24 @@ +#! /usr/bin/env crystal +# # This script generates the file src/crystal/system/win32/zone_names.cr # that contains mappings for windows time zone names based on the values -# found in http://unicode.org/cldr/data/common/supplemental/windowsZones.xml +# found in https://github.com/unicode-org/cldr/blob/main/common/supplemental/windowsZones.xml require "http/client" require "xml" require "../src/compiler/crystal/formatter" -WINDOWS_ZONE_NAMES_SOURCE = "http://unicode.org/cldr/data/common/supplemental/windowsZones.xml" +WINDOWS_ZONE_NAMES_SOURCE = "https://raw.githubusercontent.com/unicode-org/cldr/main/common/supplemental/windowsZones.xml" TARGET_FILE = File.join(__DIR__, "..", "src", "crystal", "system", "win32", "zone_names.cr") response = HTTP::Client.get(WINDOWS_ZONE_NAMES_SOURCE) - -# Simple redirection resolver -# TODO: Needs to be replaced by proper redirect handling that should be provided by `HTTP::Client` -if (300..399).includes?(response.status_code) && (location = response.headers["Location"]?) - response = HTTP::Client.get(location) -end - xml = XML.parse(response.body) - nodes = xml.xpath_nodes("/supplementalData/windowsZones/mapTimezones/mapZone[@territory=001]") -entries = [] of {key: String, zones: {String, String}, tzdata_name: String} - -nodes.each do |node| +entries = nodes.compact_map do |node| location = Time::Location.load(node["type"]) next unless location - time = Time.now(location).at_beginning_of_year + time = Time.local(location).at_beginning_of_year zone1 = time.zone zone2 = (time + 6.months).zone @@ -38,20 +30,21 @@ nodes.each do |node| zones = {zone1.name, zone2.name} end - entries << {key: node["other"], zones: zones, tzdata_name: location.name} + {key: node["other"], zones: zones, tzdata_name: location.name} rescue err : Time::Location::InvalidLocationNameError pp err + nil end # sort by IANA database identifier entries.sort_by! &.[:tzdata_name] hash_items = String.build do |io| - entries.each do |entry| + entries.join(io, '\n') do |entry| entry[:key].inspect(io) io << " => " entry[:zones].inspect(io) - io << ", # " << entry[:tzdata_name] << '\n' + io << ", # " << entry[:tzdata_name] end end diff --git a/src/crystal/system/win32/zone_names.cr b/src/crystal/system/win32/zone_names.cr index b29df3597772..74898c4a8389 100644 --- a/src/crystal/system/win32/zone_names.cr +++ b/src/crystal/system/win32/zone_names.cr @@ -6,104 +6,109 @@ module Crystal::System::Time # These mappings for windows time zone names are based on - # http://unicode.org/cldr/data/common/supplemental/windowsZones.xml + # https://raw.githubusercontent.com/unicode-org/cldr/main/common/supplemental/windowsZones.xml WINDOWS_ZONE_NAMES = { - "Egypt Standard Time" => {"EET", "EET"}, # Africa/Cairo - "Morocco Standard Time" => {"WET", "WEST"}, # Africa/Casablanca + "Egypt Standard Time" => {"EET", "EEST"}, # Africa/Cairo + "Morocco Standard Time" => {"+01", "+01"}, # Africa/Casablanca "South Africa Standard Time" => {"SAST", "SAST"}, # Africa/Johannesburg + "South Sudan Standard Time" => {"CAT", "CAT"}, # Africa/Juba + "Sudan Standard Time" => {"CAT", "CAT"}, # Africa/Khartoum "W. Central Africa Standard Time" => {"WAT", "WAT"}, # Africa/Lagos "E. Africa Standard Time" => {"EAT", "EAT"}, # Africa/Nairobi + "Sao Tome Standard Time" => {"GMT", "GMT"}, # Africa/Sao_Tome "Libya Standard Time" => {"EET", "EET"}, # Africa/Tripoli - "Namibia Standard Time" => {"WAT", "WAST"}, # Africa/Windhoek + "Namibia Standard Time" => {"CAT", "CAT"}, # Africa/Windhoek "Aleutian Standard Time" => {"HST", "HDT"}, # America/Adak "Alaskan Standard Time" => {"AKST", "AKDT"}, # America/Anchorage - "Tocantins Standard Time" => {"BRT", "BRT"}, # America/Araguaina - "Paraguay Standard Time" => {"PYT", "PYST"}, # America/Asuncion - "Bahia Standard Time" => {"BRT", "BRT"}, # America/Bahia - "SA Pacific Standard Time" => {"COT", "COT"}, # America/Bogota - "Argentina Standard Time" => {"ART", "ART"}, # America/Buenos_Aires + "Tocantins Standard Time" => {"-03", "-03"}, # America/Araguaina + "Paraguay Standard Time" => {"-04", "-03"}, # America/Asuncion + "Bahia Standard Time" => {"-03", "-03"}, # America/Bahia + "SA Pacific Standard Time" => {"-05", "-05"}, # America/Bogota + "Argentina Standard Time" => {"-03", "-03"}, # America/Buenos_Aires "Eastern Standard Time (Mexico)" => {"EST", "EST"}, # America/Cancun - "Venezuela Standard Time" => {"VET", "VET"}, # America/Caracas - "SA Eastern Standard Time" => {"GFT", "GFT"}, # America/Cayenne + "Venezuela Standard Time" => {"-04", "-04"}, # America/Caracas + "SA Eastern Standard Time" => {"-03", "-03"}, # America/Cayenne "Central Standard Time" => {"CST", "CDT"}, # America/Chicago - "Mountain Standard Time (Mexico)" => {"MST", "MDT"}, # America/Chihuahua - "Central Brazilian Standard Time" => {"AMT", "AMST"}, # America/Cuiaba + "Central Brazilian Standard Time" => {"-04", "-04"}, # America/Cuiaba "Mountain Standard Time" => {"MST", "MDT"}, # America/Denver - "Greenland Standard Time" => {"WGT", "WGST"}, # America/Godthab - "Turks And Caicos Standard Time" => {"AST", "AST"}, # America/Grand_Turk + "Greenland Standard Time" => {"-03", "-02"}, # America/Godthab + "Turks And Caicos Standard Time" => {"EST", "EDT"}, # America/Grand_Turk "Central America Standard Time" => {"CST", "CST"}, # America/Guatemala "Atlantic Standard Time" => {"AST", "ADT"}, # America/Halifax "Cuba Standard Time" => {"CST", "CDT"}, # America/Havana "US Eastern Standard Time" => {"EST", "EDT"}, # America/Indianapolis - "SA Western Standard Time" => {"BOT", "BOT"}, # America/La_Paz + "SA Western Standard Time" => {"-04", "-04"}, # America/La_Paz "Pacific Standard Time" => {"PST", "PDT"}, # America/Los_Angeles - "Central Standard Time (Mexico)" => {"CST", "CDT"}, # America/Mexico_City - "Saint Pierre Standard Time" => {"PMST", "PMDT"}, # America/Miquelon - "Montevideo Standard Time" => {"UYT", "UYT"}, # America/Montevideo + "Mountain Standard Time (Mexico)" => {"MST", "MST"}, # America/Mazatlan + "Central Standard Time (Mexico)" => {"CST", "CST"}, # America/Mexico_City + "Saint Pierre Standard Time" => {"-03", "-02"}, # America/Miquelon + "Montevideo Standard Time" => {"-03", "-03"}, # America/Montevideo "Eastern Standard Time" => {"EST", "EDT"}, # America/New_York "US Mountain Standard Time" => {"MST", "MST"}, # America/Phoenix - "Haiti Standard Time" => {"EST", "EST"}, # America/Port-au-Prince + "Haiti Standard Time" => {"EST", "EDT"}, # America/Port-au-Prince + "Magallanes Standard Time" => {"-03", "-03"}, # America/Punta_Arenas "Canada Central Standard Time" => {"CST", "CST"}, # America/Regina - "Pacific SA Standard Time" => {"CLT", "CLST"}, # America/Santiago - "E. South America Standard Time" => {"BRT", "BRST"}, # America/Sao_Paulo + "Pacific SA Standard Time" => {"-04", "-03"}, # America/Santiago + "E. South America Standard Time" => {"-03", "-03"}, # America/Sao_Paulo "Newfoundland Standard Time" => {"NST", "NDT"}, # America/St_Johns "Pacific Standard Time (Mexico)" => {"PST", "PDT"}, # America/Tijuana + "Yukon Standard Time" => {"MST", "MST"}, # America/Whitehorse "Central Asia Standard Time" => {"+06", "+06"}, # Asia/Almaty - "Jordan Standard Time" => {"EET", "EEST"}, # Asia/Amman - "Arabic Standard Time" => {"AST", "AST"}, # Asia/Baghdad + "Jordan Standard Time" => {"+03", "+03"}, # Asia/Amman + "Arabic Standard Time" => {"+03", "+03"}, # Asia/Baghdad "Azerbaijan Standard Time" => {"+04", "+04"}, # Asia/Baku - "SE Asia Standard Time" => {"ICT", "ICT"}, # Asia/Bangkok + "SE Asia Standard Time" => {"+07", "+07"}, # Asia/Bangkok "Altai Standard Time" => {"+07", "+07"}, # Asia/Barnaul "Middle East Standard Time" => {"EET", "EEST"}, # Asia/Beirut "India Standard Time" => {"IST", "IST"}, # Asia/Calcutta "Transbaikal Standard Time" => {"+09", "+09"}, # Asia/Chita "Sri Lanka Standard Time" => {"+0530", "+0530"}, # Asia/Colombo - "Syria Standard Time" => {"EET", "EEST"}, # Asia/Damascus - "Bangladesh Standard Time" => {"BDT", "BDT"}, # Asia/Dhaka - "Arabian Standard Time" => {"GST", "GST"}, # Asia/Dubai + "Syria Standard Time" => {"+03", "+03"}, # Asia/Damascus + "Bangladesh Standard Time" => {"+06", "+06"}, # Asia/Dhaka + "Arabian Standard Time" => {"+04", "+04"}, # Asia/Dubai "West Bank Standard Time" => {"EET", "EEST"}, # Asia/Hebron - "W. Mongolia Standard Time" => {"HOVT", "HOVST"}, # Asia/Hovd + "W. Mongolia Standard Time" => {"+07", "+07"}, # Asia/Hovd "North Asia East Standard Time" => {"+08", "+08"}, # Asia/Irkutsk "Israel Standard Time" => {"IST", "IDT"}, # Asia/Jerusalem - "Afghanistan Standard Time" => {"AFT", "AFT"}, # Asia/Kabul + "Afghanistan Standard Time" => {"+0430", "+0430"}, # Asia/Kabul "Russia Time Zone 11" => {"+12", "+12"}, # Asia/Kamchatka "Pakistan Standard Time" => {"PKT", "PKT"}, # Asia/Karachi - "Nepal Standard Time" => {"NPT", "NPT"}, # Asia/Katmandu + "Nepal Standard Time" => {"+0545", "+0545"}, # Asia/Katmandu "North Asia Standard Time" => {"+07", "+07"}, # Asia/Krasnoyarsk "Magadan Standard Time" => {"+11", "+11"}, # Asia/Magadan "N. Central Asia Standard Time" => {"+07", "+07"}, # Asia/Novosibirsk "Omsk Standard Time" => {"+06", "+06"}, # Asia/Omsk "North Korea Standard Time" => {"KST", "KST"}, # Asia/Pyongyang - "Myanmar Standard Time" => {"MMT", "MMT"}, # Asia/Rangoon - "Arab Standard Time" => {"AST", "AST"}, # Asia/Riyadh + "Qyzylorda Standard Time" => {"+05", "+05"}, # Asia/Qyzylorda + "Myanmar Standard Time" => {"+0630", "+0630"}, # Asia/Rangoon + "Arab Standard Time" => {"+03", "+03"}, # Asia/Riyadh "Sakhalin Standard Time" => {"+11", "+11"}, # Asia/Sakhalin "Korea Standard Time" => {"KST", "KST"}, # Asia/Seoul "China Standard Time" => {"CST", "CST"}, # Asia/Shanghai - "Singapore Standard Time" => {"SGT", "SGT"}, # Asia/Singapore + "Singapore Standard Time" => {"+08", "+08"}, # Asia/Singapore "Russia Time Zone 10" => {"+11", "+11"}, # Asia/Srednekolymsk "Taipei Standard Time" => {"CST", "CST"}, # Asia/Taipei "West Asia Standard Time" => {"+05", "+05"}, # Asia/Tashkent "Georgian Standard Time" => {"+04", "+04"}, # Asia/Tbilisi - "Iran Standard Time" => {"IRST", "IRDT"}, # Asia/Tehran + "Iran Standard Time" => {"+0330", "+0330"}, # Asia/Tehran "Tokyo Standard Time" => {"JST", "JST"}, # Asia/Tokyo "Tomsk Standard Time" => {"+07", "+07"}, # Asia/Tomsk - "Ulaanbaatar Standard Time" => {"ULAT", "ULAST"}, # Asia/Ulaanbaatar + "Ulaanbaatar Standard Time" => {"+08", "+08"}, # Asia/Ulaanbaatar "Vladivostok Standard Time" => {"+10", "+10"}, # Asia/Vladivostok "Yakutsk Standard Time" => {"+09", "+09"}, # Asia/Yakutsk "Ekaterinburg Standard Time" => {"+05", "+05"}, # Asia/Yekaterinburg "Caucasus Standard Time" => {"+04", "+04"}, # Asia/Yerevan - "Azores Standard Time" => {"AZOT", "AZOST"}, # Atlantic/Azores - "Cape Verde Standard Time" => {"CVT", "CVT"}, # Atlantic/Cape_Verde + "Azores Standard Time" => {"-01", "+00"}, # Atlantic/Azores + "Cape Verde Standard Time" => {"-01", "-01"}, # Atlantic/Cape_Verde "Greenwich Standard Time" => {"GMT", "GMT"}, # Atlantic/Reykjavik "Cen. Australia Standard Time" => {"ACST", "ACDT"}, # Australia/Adelaide "E. Australia Standard Time" => {"AEST", "AEST"}, # Australia/Brisbane "AUS Central Standard Time" => {"ACST", "ACST"}, # Australia/Darwin - "Aus Central W. Standard Time" => {"ACWST", "ACWST"}, # Australia/Eucla + "Aus Central W. Standard Time" => {"+0845", "+0845"}, # Australia/Eucla "Tasmania Standard Time" => {"AEST", "AEDT"}, # Australia/Hobart - "Lord Howe Standard Time" => {"LHST", "LHDT"}, # Australia/Lord_Howe + "Lord Howe Standard Time" => {"+1030", "+11"}, # Australia/Lord_Howe "W. Australia Standard Time" => {"AWST", "AWST"}, # Australia/Perth "AUS Eastern Standard Time" => {"AEST", "AEDT"}, # Australia/Sydney - "UTC" => {"GMT", "GMT"}, # Etc/GMT "UTC-11" => {"-11", "-11"}, # Etc/GMT+11 "Dateline Standard Time" => {"-12", "-12"}, # Etc/GMT+12 "UTC-02" => {"-02", "-02"}, # Etc/GMT+2 @@ -111,6 +116,7 @@ module Crystal::System::Time "UTC-09" => {"-09", "-09"}, # Etc/GMT+9 "UTC+12" => {"+12", "+12"}, # Etc/GMT-12 "UTC+13" => {"+13", "+13"}, # Etc/GMT-13 + "UTC" => {"UTC", "UTC"}, # Etc/UTC "Astrakhan Standard Time" => {"+04", "+04"}, # Europe/Astrakhan "W. Europe Standard Time" => {"CET", "CEST"}, # Europe/Berlin "GTB Standard Time" => {"EET", "EEST"}, # Europe/Bucharest @@ -125,21 +131,21 @@ module Crystal::System::Time "Romance Standard Time" => {"CET", "CEST"}, # Europe/Paris "Russia Time Zone 3" => {"+04", "+04"}, # Europe/Samara "Saratov Standard Time" => {"+04", "+04"}, # Europe/Saratov + "Volgograd Standard Time" => {"MSK", "MSK"}, # Europe/Volgograd "Central European Standard Time" => {"CET", "CEST"}, # Europe/Warsaw - "Mauritius Standard Time" => {"MUT", "MUT"}, # Indian/Mauritius - "Samoa Standard Time" => {"WSST", "WSDT"}, # Pacific/Apia + "Mauritius Standard Time" => {"+04", "+04"}, # Indian/Mauritius + "Samoa Standard Time" => {"+13", "+13"}, # Pacific/Apia "New Zealand Standard Time" => {"NZST", "NZDT"}, # Pacific/Auckland - "Bougainville Standard Time" => {"BST", "BST"}, # Pacific/Bougainville - "Chatham Islands Standard Time" => {"CHAST", "CHADT"}, # Pacific/Chatham - "Easter Island Standard Time" => {"EAST", "EASST"}, # Pacific/Easter - "Fiji Standard Time" => {"FJT", "FJST"}, # Pacific/Fiji - "Central Pacific Standard Time" => {"SBT", "SBT"}, # Pacific/Guadalcanal + "Bougainville Standard Time" => {"+11", "+11"}, # Pacific/Bougainville + "Chatham Islands Standard Time" => {"+1245", "+1345"}, # Pacific/Chatham + "Easter Island Standard Time" => {"-06", "-05"}, # Pacific/Easter + "Fiji Standard Time" => {"+12", "+12"}, # Pacific/Fiji + "Central Pacific Standard Time" => {"+11", "+11"}, # Pacific/Guadalcanal "Hawaiian Standard Time" => {"HST", "HST"}, # Pacific/Honolulu - "Line Islands Standard Time" => {"LINT", "LINT"}, # Pacific/Kiritimati - "Marquesas Standard Time" => {"MART", "MART"}, # Pacific/Marquesas - "Norfolk Standard Time" => {"NFT", "NFT"}, # Pacific/Norfolk - "West Pacific Standard Time" => {"PGT", "PGT"}, # Pacific/Port_Moresby - "Tonga Standard Time" => {"+13", "+14"}, # Pacific/Tongatapu - + "Line Islands Standard Time" => {"+14", "+14"}, # Pacific/Kiritimati + "Marquesas Standard Time" => {"-0930", "-0930"}, # Pacific/Marquesas + "Norfolk Standard Time" => {"+11", "+12"}, # Pacific/Norfolk + "West Pacific Standard Time" => {"+10", "+10"}, # Pacific/Port_Moresby + "Tonga Standard Time" => {"+13", "+13"}, # Pacific/Tongatapu } end From 341b1efceedd0a13c21552c965208ef9a1ed46e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 26 May 2023 09:46:17 +0200 Subject: [PATCH 0557/1551] Add `slow` tag to stdlib specs that compile a program (#13498) --- spec/std/exception/call_stack_spec.cr | 10 ++++---- spec/std/io/file_descriptor_spec.cr | 8 +++--- spec/std/kernel_spec.cr | 36 +++++++++++++-------------- spec/std/process_spec.cr | 2 +- spec/std/spec/hooks_spec.cr | 2 +- spec/std/string_spec.cr | 3 +-- spec/std/va_list_spec.cr | 2 +- 7 files changed, 31 insertions(+), 32 deletions(-) diff --git a/spec/std/exception/call_stack_spec.cr b/spec/std/exception/call_stack_spec.cr index b5bf92597dc2..8ecba204eba4 100644 --- a/spec/std/exception/call_stack_spec.cr +++ b/spec/std/exception/call_stack_spec.cr @@ -1,7 +1,7 @@ require "../spec_helper" describe "Backtrace" do - it "prints file line:column" do + it "prints file line:column", tags: %w[slow] do source_file = datapath("backtrace_sample") # CallStack tries to make files relative to the current dir, @@ -28,7 +28,7 @@ describe "Backtrace" do output.should_not contain("src/raise.cr") end - it "doesn't relativize paths outside of current dir (#10169)" do + it "doesn't relativize paths outside of current dir (#10169)", tags: %w[slow] do with_tempfile("source_file") do |source_file| source_path = Path.new(source_file) source_path.absolute?.should be_true @@ -46,7 +46,7 @@ describe "Backtrace" do end end - it "prints exception backtrace to stderr" do + it "prints exception backtrace to stderr", tags: %w[slow] do sample = datapath("exception_backtrace_sample") _, output, error = compile_and_run_file(sample) @@ -55,7 +55,7 @@ describe "Backtrace" do error.to_s.should contain("IndexError") end - it "prints crash backtrace to stderr" do + it "prints crash backtrace to stderr", tags: %w[slow] do sample = datapath("crash_backtrace_sample") _, output, error = compile_and_run_file(sample) @@ -68,7 +68,7 @@ describe "Backtrace" do # In windows, the current working directory of the process cannot be # removed. So we probably don't need to test that. # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/rmdir-wrmdir?view=msvc-170#remarks - it "print exception with non-existing PWD" do + it "print exception with non-existing PWD", tags: %w[slow] do source_file = datapath("blank_test_file.txt") compile_file(source_file) do |executable_file| output, error = IO::Memory.new, IO::Memory.new diff --git a/spec/std/io/file_descriptor_spec.cr b/spec/std/io/file_descriptor_spec.cr index 2391d97c03c9..509cbb72eedb 100644 --- a/spec/std/io/file_descriptor_spec.cr +++ b/spec/std/io/file_descriptor_spec.cr @@ -14,7 +14,7 @@ private def shell_command(command) end describe IO::FileDescriptor do - it "reopen STDIN with the right mode" do + it "reopen STDIN with the right mode", tags: %w[slow] do code = %q(puts "#{STDIN.blocking} #{STDIN.info.type}") compile_source(code) do |binpath| `#{shell_command %(#{Process.quote(binpath)} < #{Process.quote(binpath)})}`.chomp.should eq("true File") @@ -37,7 +37,7 @@ describe IO::FileDescriptor do end end - it "opens STDIN in binary mode" do + it "opens STDIN in binary mode", tags: %w[slow] do code = %q(print STDIN.gets_to_end.includes?('\r')) compile_source(code) do |binpath| io_in = IO::Memory.new("foo\r\n") @@ -47,7 +47,7 @@ describe IO::FileDescriptor do end end - it "opens STDOUT in binary mode" do + it "opens STDOUT in binary mode", tags: %w[slow] do code = %q(puts "foo") compile_source(code) do |binpath| io = IO::Memory.new @@ -56,7 +56,7 @@ describe IO::FileDescriptor do end end - it "opens STDERR in binary mode" do + it "opens STDERR in binary mode", tags: %w[slow] do code = %q(STDERR.puts "foo") compile_source(code) do |binpath| io = IO::Memory.new diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index c89fea79b6f2..149e6385ac97 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -2,7 +2,7 @@ require "spec" require "./spec_helper" describe "PROGRAM_NAME" do - it "works for UTF-8 name" do + it "works for UTF-8 name", tags: %w[slow] do with_tempfile("source_file") do |source_file| if ENV["IN_NIX_SHELL"]? pending! "Example is broken in Nix shell (#12332)" @@ -20,7 +20,7 @@ describe "PROGRAM_NAME" do end describe "ARGV" do - it "accepts UTF-8 command-line arguments" do + it "accepts UTF-8 command-line arguments", tags: %w[slow] do with_tempfile("source_file") do |source_file| File.write(source_file, "ARGV.inspect(STDOUT)") @@ -35,12 +35,12 @@ describe "ARGV" do end describe "exit" do - it "exits normally with status 0" do + it "exits normally with status 0", tags: %w[slow] do status, _, _ = compile_and_run_source "exit" status.success?.should be_true end - it "exits with given error code" do + it "exits with given error code", tags: %w[slow] do status, _, _ = compile_and_run_source "exit 42" status.success?.should be_false status.exit_code.should eq(42) @@ -48,7 +48,7 @@ describe "exit" do end describe "at_exit" do - it "runs handlers on normal program ending" do + it "runs handlers on normal program ending", tags: %w[slow] do status, output, _ = compile_and_run_source <<-CRYSTAL at_exit do print "handler code." @@ -59,7 +59,7 @@ describe "at_exit" do output.should eq("handler code.") end - it "runs handlers on explicit program ending" do + it "runs handlers on explicit program ending", tags: %w[slow] do status, output, _ = compile_and_run_source <<-'CRYSTAL' at_exit do |exit_code| print "handler code, exit code: #{exit_code}." @@ -72,7 +72,7 @@ describe "at_exit" do output.should eq("handler code, exit code: 42.") end - it "runs handlers in reverse order" do + it "runs handlers in reverse order", tags: %w[slow] do status, output, _ = compile_and_run_source <<-CRYSTAL at_exit do print "first handler code." @@ -87,7 +87,7 @@ describe "at_exit" do output.should eq("second handler code.first handler code.") end - it "runs all handlers maximum once" do + it "runs all handlers maximum once", tags: %w[slow] do status, output, _ = compile_and_run_source <<-CRYSTAL at_exit do print "first handler code." @@ -109,7 +109,7 @@ describe "at_exit" do output.should eq("third handler code.second handler code, explicit exit!first handler code.") end - it "allows handlers to change the exit code with explicit `exit` call" do + it "allows handlers to change the exit code with explicit `exit` call", tags: %w[slow] do status, output, _ = compile_and_run_source <<-'CRYSTAL' at_exit do |exit_code| print "first handler code, exit code: #{exit_code}." @@ -132,7 +132,7 @@ describe "at_exit" do output.should eq("third handler code, exit code: 0.second handler code, re-exiting.first handler code, exit code: 42.") end - it "allows handlers to change the exit code with explicit `exit` call (2)" do + it "allows handlers to change the exit code with explicit `exit` call (2)", tags: %w[slow] do status, output, _ = compile_and_run_source <<-'CRYSTAL' at_exit do |exit_code| print "first handler code, exit code: #{exit_code}." @@ -157,7 +157,7 @@ describe "at_exit" do output.should eq("third handler code, exit code: 21.second handler code, re-exiting.first handler code, exit code: 42.") end - it "changes final exit code when an handler raises an error" do + it "changes final exit code when an handler raises an error", tags: %w[slow] do status, output, error = compile_and_run_source <<-'CRYSTAL' at_exit do |exit_code| print "first handler code, exit code: #{exit_code}." @@ -181,7 +181,7 @@ describe "at_exit" do error.should contain("Error running at_exit handler: Raised from at_exit handler!") end - it "shows unhandled exceptions after at_exit handlers" do + it "shows unhandled exceptions after at_exit handlers", tags: %w[slow] do status, _, error = compile_and_run_source <<-CRYSTAL at_exit do STDERR.print "first handler code." @@ -198,7 +198,7 @@ describe "at_exit" do error.should contain("second handler code.first handler code.Unhandled exception: Kaboom!") end - it "can get unhandled exception in at_exit handler" do + it "can get unhandled exception in at_exit handler", tags: %w[slow] do status, _, error = compile_and_run_source <<-CRYSTAL at_exit do |_, ex| STDERR.print ex.try &.message @@ -211,7 +211,7 @@ describe "at_exit" do error.should contain("Kaboom!Unhandled exception: Kaboom!") end - it "allows at_exit inside at_exit" do + it "allows at_exit inside at_exit", tags: %w[slow] do status, output, _ = compile_and_run_source <<-CRYSTAL at_exit do print "1" @@ -232,7 +232,7 @@ describe "at_exit" do output.should eq("3412") end - it "prints unhandled exception with cause" do + it "prints unhandled exception with cause", tags: %w[slow] do status, _, error = compile_and_run_source <<-CRYSTAL raise Exception.new("secondary", cause: Exception.new("primary")) CRYSTAL @@ -244,7 +244,7 @@ describe "at_exit" do end describe "hardware exception" do - it "reports invalid memory access" do + it "reports invalid memory access", tags: %w[slow] do status, _, error = compile_and_run_source <<-'CRYSTAL' puts Pointer(Int64).null.value CRYSTAL @@ -258,7 +258,7 @@ describe "hardware exception" do # FIXME: Pending as mitigation for https://github.com/crystal-lang/crystal/issues/7482 pending "detects stack overflow on the main stack" {% else %} - it "detects stack overflow on the main stack" do + it "detects stack overflow on the main stack", tags: %w[slow] do # This spec can take some time under FreeBSD where # the default stack size is 0.5G. Setting a # smaller stack size with `ulimit -s 8192` @@ -276,7 +276,7 @@ describe "hardware exception" do end {% end %} - it "detects stack overflow on a fiber stack" do + it "detects stack overflow on a fiber stack", tags: %w[slow] do status, _, error = compile_and_run_source <<-'CRYSTAL' def foo y = StaticArray(Int8, 512).new(0) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 9292fd25aec9..3f0a6fad00d8 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -439,7 +439,7 @@ describe Process do describe ".chroot" do {% if flag?(:unix) && !flag?(:android) %} - it "raises when unprivileged" do + it "raises when unprivileged", tags: %w[slow] do status, output, _ = compile_and_run_source <<-'CRYSTAL' # Try to drop privileges. Ignoring any errors because dropping is only # necessary for a privileged user and it doesn't matter when it fails diff --git a/spec/std/spec/hooks_spec.cr b/spec/std/spec/hooks_spec.cr index a0667acbf51f..d3c3c600d21c 100644 --- a/spec/std/spec/hooks_spec.cr +++ b/spec/std/spec/hooks_spec.cr @@ -2,7 +2,7 @@ require "./spec_helper" describe Spec do describe "hooks" do - it "runs in correct order" do + it "runs in correct order", tags: %w[slow] do compile_and_run_source(<<-CRYSTAL, flags: %w(--no-debug))[1].lines[..-5].should eq <<-OUT.lines require "prelude" require "spec" diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 2a0861908117..4659e0bcbb05 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -850,8 +850,7 @@ describe "String" do it { "\n\t ".strip.should eq("") } it { "\u00A0".strip.should eq("") } - # TODO: add spec tags so this can be run with tag:slow - # it { (" " * 167772160).strip.should eq("") } + it(tags: %w[slow]) { (" " * 167772160).strip.should eq("") } it { "".strip("xyz").should eq("") } it { "foobar".strip("").should eq("foobar") } diff --git a/spec/std/va_list_spec.cr b/spec/std/va_list_spec.cr index c861f99686da..ca0da624380c 100644 --- a/spec/std/va_list_spec.cr +++ b/spec/std/va_list_spec.cr @@ -3,7 +3,7 @@ require "./spec_helper" describe VaList do - it "works with C code" do + it "works with C code", tags: %w[slow] do compile_and_run_source_with_c( %( #include From 1946163f1c8b637804273e75add11a199d57123f Mon Sep 17 00:00:00 2001 From: Devonte W Date: Fri, 26 May 2023 22:35:28 +0100 Subject: [PATCH 0558/1551] Fix for Process: ensure chdir is a string (#13503) --- spec/std/process_spec.cr | 15 ++++++++++++++- src/process.cr | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 3f0a6fad00d8..5ab14694cc98 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -180,7 +180,7 @@ describe Process do $?.exit_code.should eq(0) end - it "sets working directory" do + it "sets working directory with string" do parent = File.dirname(Dir.current) command = {% if flag?(:win32) %} "cmd.exe /c echo %cd%" @@ -193,6 +193,19 @@ describe Process do value.should eq "#{parent}#{newline}" end + it "sets working directory with path" do + parent = Path.new File.dirname(Dir.current) + command = {% if flag?(:win32) %} + "cmd.exe /c echo %cd%" + {% else %} + "pwd" + {% end %} + value = Process.run(command, shell: true, chdir: parent, output: Process::Redirect::Pipe) do |proc| + proc.output.gets_to_end + end + value.should eq "#{parent}#{newline}" + end + pending_win32 "disallows passing arguments to nowhere" do expect_raises ArgumentError, /args.+@/ do Process.run("foo bar", {"baz"}, shell: true) diff --git a/src/process.cr b/src/process.cr index 12173c68b4a7..159b08b39eaa 100644 --- a/src/process.cr +++ b/src/process.cr @@ -243,7 +243,7 @@ class Process fork_output = stdio_to_fd(output, for: STDOUT) fork_error = stdio_to_fd(error, for: STDERR) - pid = Crystal::System::Process.spawn(command_args, env, clear_env, fork_input, fork_output, fork_error, chdir) + pid = Crystal::System::Process.spawn(command_args, env, clear_env, fork_input, fork_output, fork_error, chdir.try &.to_s) @process_info = Crystal::System::Process.new(pid) fork_input.close unless fork_input.in?(input, STDIN) From 5761bbb7aa5e79f4de24837d6007a03a673990f2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 27 May 2023 05:35:43 +0800 Subject: [PATCH 0559/1551] Formatter: escape bi-directional control characters within strings (#13067) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/compiler/formatter/formatter_spec.cr | 30 +++++++++++++++++++++++ src/compiler/crystal/syntax/lexer.cr | 15 ++++++++++++ src/compiler/crystal/tools/formatter.cr | 14 ++++++----- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 7c9cc990e116..754093190048 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -2608,4 +2608,34 @@ describe Crystal::Formatter do CRYSTAL end end + + # CVE-2021-42574 + describe "Unicode bi-directional control characters" do + ['\u202A', '\u202B', '\u202C', '\u202D', '\u202E', '\u2066', '\u2067', '\u2068', '\u2069'].each do |char| + assert_format %("#{char}"), %("#{char.unicode_escape}") + assert_format %("\\c#{char}"), %("c#{char.unicode_escape}") + assert_format %("#{char}\#{1}"), %("#{char.unicode_escape}\#{1}") + assert_format %("\\c#{char}\#{1}"), %("c#{char.unicode_escape}\#{1}") + assert_format %(%(#{char})), %(%(#{char.unicode_escape})) + assert_format %(%Q(#{char})), %(%Q(#{char.unicode_escape})) + assert_format %(%Q(#{char}\#{1})), %(%Q(#{char.unicode_escape}\#{1})) + assert_format %(<<-EOS\n#{char}\nEOS), %(<<-EOS\n#{char.unicode_escape}\nEOS) + assert_format %(<<-EOS\n#{char}\#{1}\nEOS), %(<<-EOS\n#{char.unicode_escape}\#{1}\nEOS) + assert_format %(def foo("#{char}" x)\nend), %(def foo("#{char.unicode_escape}" x)\nend) + assert_format %(foo("#{char}": 1)), %(foo("#{char.unicode_escape}": 1)) + assert_format %(NamedTuple("#{char}": Int32)), %(NamedTuple("#{char.unicode_escape}": Int32)) + assert_format %({"#{char}": 1}), %({"#{char.unicode_escape}": 1}) + + # the following contexts do not accept escape sequences, escaping these + # control characters would alter the meaning of the source code + assert_format %(/#{char}/) + assert_format %(%r(#{char})) + assert_format %(%q(#{char})) + assert_format %(%w(#{char})) + assert_format %(%i(#{char})) + assert_format %(/#{char}\#{1}/) + assert_format %(%r(#{char}\#{1})) + assert_format %(<<-'EOS'\n#{char}\nEOS) + end + end end diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 98d2e4a7d2dc..7bafb7ddae04 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -2735,6 +2735,21 @@ module Crystal end end + private UNICODE_BIDI_CONTROL_CHARACTERS = {'\u202A', '\u202B', '\u202C', '\u202D', '\u202E', '\u2066', '\u2067', '\u2068', '\u2069'} + + private FORBIDDEN_ESCAPES = UNICODE_BIDI_CONTROL_CHARACTERS.to_h { |ch| {ch, ch.unicode_escape} } + + # used by the formatter + def self.escape_forbidden_characters(string) + string.each_char do |ch| + if ch.in?(UNICODE_BIDI_CONTROL_CHARACTERS) + return string.gsub(FORBIDDEN_ESCAPES) + end + end + + string + end + def next_char_no_column_increment char = @reader.next_char if error = @reader.error diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 25915b29ab75..540487758842 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -491,11 +491,7 @@ module Crystal while true case @token.type when .string? - if @token.invalid_escape - write @token.value - else - write @token.raw - end + write_sanitized_string_body(@token.delimiter_state.allow_escapes && !is_regex) next_string_token when .interpolation_start? # This is the case of #{__DIR__} @@ -534,6 +530,12 @@ module Crystal false end + private def write_sanitized_string_body(escape) + body = @token.invalid_escape ? @token.value.as(String) : @token.raw + body = Lexer.escape_forbidden_characters(body) if escape + write body + end + def visit(node : StringInterpolation) if @token.delimiter_state.kind.heredoc? # For heredoc, only write the start: on a newline will print it @@ -609,7 +611,7 @@ module Crystal else loop do check :STRING - write @token.invalid_escape ? @token.value : @token.raw + write_sanitized_string_body(delimiter_state.allow_escapes && !is_regex) next_string_token # On heredoc, pieces of contents are combined due to removing indentation. From 94f9200c5013c408ca9332a2f66569128ae08a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 5 Jun 2023 10:58:27 +0200 Subject: [PATCH 0560/1551] Correctly ignore nested deprecation warnings (#13513) --- spec/compiler/semantic/warnings_spec.cr | 13 +++++++++++++ .../crystal/semantic/cleanup_transformer.cr | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/warnings_spec.cr b/spec/compiler/semantic/warnings_spec.cr index c9b606b36a35..502351ce305b 100644 --- a/spec/compiler/semantic/warnings_spec.cr +++ b/spec/compiler/semantic/warnings_spec.cr @@ -267,6 +267,19 @@ describe "Semantic: warnings" do end end + it "ignores nested calls to deprecated methods" do + x = assert_warning <<-CRYSTAL, + @[Deprecated] + def foo; bar; end + + @[Deprecated] + def bar; end + + foo + CRYSTAL + "warning in line 7\nWarning: Deprecated top-level foo." + end + it "errors if invalid argument type" do assert_error <<-CRYSTAL, @[Deprecated(42)] diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 0126c62d1166..70a8ffe63590 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -464,7 +464,9 @@ module Crystal return expanded.transform self end - @program.check_call_to_deprecated_method(node) + unless @current_def.try(&.annotation(@program.deprecated_annotation)) + @program.check_call_to_deprecated_method(node) + end # Need to transform these manually because node.block doesn't # need to be transformed if it has a fun_literal From 76c24b0297c6b4fffa11032517b417b85e078f3d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 5 Jun 2023 16:58:50 +0800 Subject: [PATCH 0561/1551] Support `CRYSTAL_LIBRARY_RPATH` for adding dynamic library lookup paths (#13499) --- spec/std/spec_helper.cr | 7 +- src/compiler/crystal/codegen/link.cr | 55 +++++- src/compiler/crystal/command.cr | 8 +- src/compiler/crystal/command/env.cr | 11 +- src/compiler/crystal/command/eval.cr | 2 +- src/compiler/crystal/command/spec.cr | 2 +- src/compiler/crystal/compiler.cr | 11 +- src/compiler/crystal/loader/msvc.cr | 1 + src/compiler/crystal/loader/unix.cr | 5 + src/compiler/crystal/macros/macros.cr | 2 +- src/compiler/crystal/program.cr | 1 + src/compiler/crystal/tools/doc/generator.cr | 2 +- src/crystal/main.cr | 14 ++ src/crystal/system/win32/delay_load.cr | 185 +++++++++++++++++- src/lib_c/x86_64-windows-msvc/c/heapapi.cr | 8 + .../x86_64-windows-msvc/c/libloaderapi.cr | 3 +- .../x86_64-windows-msvc/c/stringapiset.cr | 9 +- src/lib_c/x86_64-windows-msvc/c/winbase.cr | 2 +- src/process/executable_path.cr | 6 +- 19 files changed, 311 insertions(+), 23 deletions(-) create mode 100644 src/lib_c/x86_64-windows-msvc/c/heapapi.cr diff --git a/spec/std/spec_helper.cr b/spec/std/spec_helper.cr index 4ea8452af7c4..75f8a1fe36d3 100644 --- a/spec/std/spec_helper.cr +++ b/spec/std/spec_helper.cr @@ -81,9 +81,10 @@ def compile_file(source_file, *, bin_name = "executable_file", flags = %w(), fil args = ["build"] + flags + ["-o", executable_file, source_file] output = IO::Memory.new status = Process.run(compiler, args, env: { - "CRYSTAL_PATH" => Crystal::PATH, - "CRYSTAL_LIBRARY_PATH" => Crystal::LIBRARY_PATH, - "CRYSTAL_CACHE_DIR" => Crystal::CACHE_DIR, + "CRYSTAL_PATH" => Crystal::PATH, + "CRYSTAL_LIBRARY_PATH" => Crystal::LIBRARY_PATH, + "CRYSTAL_LIBRARY_RPATH" => Crystal::LIBRARY_RPATH, + "CRYSTAL_CACHE_DIR" => Crystal::CACHE_DIR, }, output: output, error: output) unless status.success? diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index 483edc500b35..7ef2ab85f92c 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -85,7 +85,7 @@ module Crystal end end - class CrystalLibraryPath + module CrystalLibraryPath def self.default_paths : Array(String) paths = ENV.fetch("CRYSTAL_LIBRARY_PATH", Crystal::Config.library_path).split(Process::PATH_DELIMITER, remove_empty: true) @@ -98,6 +98,59 @@ module Crystal default_paths.join(Process::PATH_DELIMITER) end + def self.default_rpath : String + # do not call `CrystalPath.expand_paths`, as `$ORIGIN` inside this env + # variable is always expanded at run time + ENV.fetch("CRYSTAL_LIBRARY_RPATH", "") + end + + # Adds the compiler itself's RPATH to the environment for the duration of + # the block. `$ORIGIN` in the compiler's RPATH is expanded immediately, but + # `$ORIGIN`s in the existing environment variable are not expanded. For + # example, on Linux: + # + # - CRYSTAL_LIBRARY_RPATH of the compiler: `$ORIGIN/so` + # - Current $CRYSTAL_LIBRARY_RPATH: `/home/foo:$ORIGIN/mylibs` + # - Compiler's full path: `/opt/crystal` + # - Generated executable's Crystal::LIBRARY_RPATH: `/home/foo:$ORIGIN/mylibs:/opt/so` + # + # On Windows we additionally append the compiler's parent directory to the + # list, as if by appending `$ORIGIN` to the compiler's RPATH. This directory + # is effectively the first search entry on any Windows executable. Example: + # + # - CRYSTAL_LIBRARY_RPATH of the compiler: `$ORIGIN\dll` + # - Current %CRYSTAL_LIBRARY_RPATH%: `C:\bar;$ORIGIN\mylibs` + # - Compiler's full path: `C:\foo\crystal.exe` + # - Generated executable's Crystal::LIBRARY_RPATH: `C:\bar;$ORIGIN\mylibs;C:\foo\dll;C:\foo` + # + # Combining RPATHs multiple times has no effect; the `CRYSTAL_LIBRARY_RPATH` + # environment variable at compiler startup is used, not really the "current" + # one. This can happen when running a program that also uses macro `run`s. + def self.add_compiler_rpath(&) + executable_path = Process.executable_path + compiler_origin = File.dirname(executable_path) if executable_path + + current_rpaths = ORIGINAL_CRYSTAL_LIBRARY_RPATH.try &.split(Process::PATH_DELIMITER, remove_empty: true) + compiler_rpaths = Crystal::LIBRARY_RPATH.split(Process::PATH_DELIMITER, remove_empty: true) + CrystalPath.expand_paths(compiler_rpaths, compiler_origin) + + rpaths = compiler_rpaths + rpaths.concat(current_rpaths) if current_rpaths + {% if flag?(:win32) %} + rpaths << compiler_origin if compiler_origin + {% end %} + + old_env = ENV["CRYSTAL_LIBRARY_RPATH"]? + ENV["CRYSTAL_LIBRARY_RPATH"] = rpaths.join(Process::PATH_DELIMITER) + begin + yield + ensure + ENV["CRYSTAL_LIBRARY_RPATH"] = old_env + end + end + + private ORIGINAL_CRYSTAL_LIBRARY_RPATH = ENV["CRYSTAL_LIBRARY_RPATH"]? + class_getter paths : Array(String) do default_paths end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index a8b4e43fac77..273554728f66 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -331,10 +331,11 @@ class Crystal::Command specified_output : Bool, hierarchy_exp : String?, cursor_location : String?, - output_format : String? do + output_format : String?, + combine_rpath : Bool do def compile(output_filename = self.output_filename) compiler.emit_base_filename = output_filename.rchop(File.extname(output_filename)) - compiler.compile sources, output_filename + compiler.compile sources, output_filename, combine_rpath: combine_rpath end def top_level_semantic @@ -548,7 +549,8 @@ class Crystal::Command error "can't use `#{output_filename}` as output filename because it's a directory" end - @config = CompilerConfig.new compiler, sources, output_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format + combine_rpath = run && !no_codegen + @config = CompilerConfig.new compiler, sources, output_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format, combine_rpath end private def gather_sources(filenames) diff --git a/src/compiler/crystal/command/env.cr b/src/compiler/crystal/command/env.cr index c4605860bf36..cf450ec55f3d 100644 --- a/src/compiler/crystal/command/env.cr +++ b/src/compiler/crystal/command/env.cr @@ -18,11 +18,12 @@ class Crystal::Command end vars = { - "CRYSTAL_CACHE_DIR" => CacheDir.instance.dir, - "CRYSTAL_PATH" => CrystalPath.default_path, - "CRYSTAL_VERSION" => Config.version || "", - "CRYSTAL_LIBRARY_PATH" => CrystalLibraryPath.default_path, - "CRYSTAL_OPTS" => ENV.fetch("CRYSTAL_OPTS", ""), + "CRYSTAL_CACHE_DIR" => CacheDir.instance.dir, + "CRYSTAL_PATH" => CrystalPath.default_path, + "CRYSTAL_VERSION" => Config.version || "", + "CRYSTAL_LIBRARY_PATH" => CrystalLibraryPath.default_path, + "CRYSTAL_LIBRARY_RPATH" => CrystalLibraryPath.default_rpath, + "CRYSTAL_OPTS" => ENV.fetch("CRYSTAL_OPTS", ""), } if var_names.empty? diff --git a/src/compiler/crystal/command/eval.cr b/src/compiler/crystal/command/eval.cr index 507c4cbe9750..5db37cb0cd27 100644 --- a/src/compiler/crystal/command/eval.cr +++ b/src/compiler/crystal/command/eval.cr @@ -26,7 +26,7 @@ class Crystal::Command output_filename = Crystal.temp_executable "eval" - compiler.compile sources, output_filename + compiler.compile sources, output_filename, combine_rpath: true execute output_filename, program_args, compiler end end diff --git a/src/compiler/crystal/command/spec.cr b/src/compiler/crystal/command/spec.cr index 613d13cb9a63..3d4663e1cfe0 100644 --- a/src/compiler/crystal/command/spec.cr +++ b/src/compiler/crystal/command/spec.cr @@ -95,7 +95,7 @@ class Crystal::Command output_filename = Crystal.temp_executable "spec" ENV["CRYSTAL_SPEC_COMPILER_BIN"] ||= Process.executable_path - compiler.compile sources, output_filename + compiler.compile sources, output_filename, combine_rpath: true report_warnings execute output_filename, options, compiler, error_on_exit: warnings_fail_on_exit? end diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 300ea71ae75b..5e91c2f60127 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -150,12 +150,21 @@ module Crystal # Compiles the given *source*, with *output_filename* as the name # of the generated executable. # + # If *combine_rpath* is true, add the compiler itself's RPATH to the + # generated executable via `CrystalLibraryPath.add_compiler_rpath`. This is + # used by the `run` / `eval` / `spec` commands as well as the macro `run` + # (via `Crystal::Program#macro_compile`), and never during cross-compiling. + # # Raises `Crystal::CodeError` if there's an error in the # source code. # # Raises `InvalidByteSequenceError` if the source code is not # valid UTF-8. - def compile(source : Source | Array(Source), output_filename : String) : Result + def compile(source : Source | Array(Source), output_filename : String, *, combine_rpath : Bool = false) : Result + if combine_rpath + return CrystalLibraryPath.add_compiler_rpath { compile(source, output_filename, combine_rpath: false) } + end + source = [source] unless source.is_a?(Array) program = new_program(source) node = parse program, source diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index 9f9a592477f2..01b8e6ce7b06 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -149,6 +149,7 @@ class Crystal::Loader end private def open_library(path : String) + # TODO: respect Crystal::LIBRARY_RPATH (#13490) LibC.LoadLibraryExW(System.to_wstr(path), nil, 0) end diff --git a/src/compiler/crystal/loader/unix.cr b/src/compiler/crystal/loader/unix.cr index 928c646ceb75..4c202231d7e6 100644 --- a/src/compiler/crystal/loader/unix.cr +++ b/src/compiler/crystal/loader/unix.cr @@ -137,11 +137,16 @@ class Crystal::Loader def self.default_search_paths : Array(String) default_search_paths = [] of String + # TODO: respect the compiler's DT_RPATH (#13490) + if env_library_path = ENV[{{ flag?(:darwin) ? "DYLD_LIBRARY_PATH" : "LD_LIBRARY_PATH" }}]? # TODO: Expand tokens $ORIGIN, $LIB, $PLATFORM default_search_paths.concat env_library_path.split(Process::PATH_DELIMITER, remove_empty: true) end + # TODO: respect the compiler's DT_RUNPATH + # TODO: respect $DYLD_FALLBACK_LIBRARY_PATH and the compiler's LC_RPATH on darwin + {% if (flag?(:linux) && !flag?(:android)) || flag?(:bsd) %} read_ld_conf(default_search_paths) {% end %} diff --git a/src/compiler/crystal/macros/macros.cr b/src/compiler/crystal/macros/macros.cr index b5d1bb281c1a..57c3711c4a63 100644 --- a/src/compiler/crystal/macros/macros.cr +++ b/src/compiler/crystal/macros/macros.cr @@ -93,7 +93,7 @@ class Crystal::Program return CompiledMacroRun.new(executable_path, elapsed_time, true) end - result = host_compiler.compile Compiler::Source.new(filename, source), executable_path + result = host_compiler.compile Compiler::Source.new(filename, source), executable_path, combine_rpath: true # Write the new files from which 'filename' depends into the cache dir # (here we store how to obtain these files, because a require might use diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 1c0a70430965..3fa7aa37f45a 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -278,6 +278,7 @@ module Crystal define_crystal_string_constant "DESCRIPTION", Crystal::Config.description define_crystal_string_constant "PATH", Crystal::CrystalPath.default_path define_crystal_string_constant "LIBRARY_PATH", Crystal::CrystalLibraryPath.default_path + define_crystal_string_constant "LIBRARY_RPATH", Crystal::CrystalLibraryPath.default_rpath define_crystal_string_constant "VERSION", Crystal::Config.version define_crystal_string_constant "LLVM_VERSION", Crystal::Config.llvm_version end diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index 9e2a22a25506..1299737e8f1f 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -227,7 +227,7 @@ class Crystal::Doc::Generator {"BUILD_COMMIT", "BUILD_DATE", "CACHE_DIR", "DEFAULT_PATH", "DESCRIPTION", "PATH", "VERSION", "LLVM_VERSION", - "LIBRARY_PATH"}.each do |name| + "LIBRARY_PATH", "LIBRARY_RPATH"}.each do |name| return true if type == crystal_type.types[name]? end diff --git a/src/crystal/main.cr b/src/crystal/main.cr index 6a63d838d234..480571d06fb4 100644 --- a/src/crystal/main.cr +++ b/src/crystal/main.cr @@ -1,3 +1,17 @@ +require "process/executable_path" # Process::PATH_DELIMITER + +module Crystal + {% unless Crystal.has_constant?("LIBRARY_RPATH") %} + LIBRARY_RPATH = {{ env("CRYSTAL_LIBRARY_RPATH") || "" }} + {% end %} +end + +{% if flag?(:unix) && !flag?(:darwin) %} + {% unless Crystal::LIBRARY_RPATH.empty? %} + # TODO: is there a better way to quote this? + @[Link(ldflags: {{ "'-Wl,-rpath,#{Crystal::LIBRARY_RPATH.id}'" }})] + {% end %} +{% end %} lib LibCrystalMain @[Raises] fun __crystal_main(argc : Int32, argv : UInt8**) diff --git a/src/crystal/system/win32/delay_load.cr b/src/crystal/system/win32/delay_load.cr index 3f596822e928..49ed467156cb 100644 --- a/src/crystal/system/win32/delay_load.cr +++ b/src/crystal/system/win32/delay_load.cr @@ -1,4 +1,5 @@ require "c/delayimp" +require "c/heapapi" lib LibC $image_base = __ImageBase : IMAGE_DOS_HEADER @@ -44,6 +45,187 @@ module Crystal::System::DelayLoad def self.interlocked_exchange(atomic : LibC::HMODULE*, value : LibC::HMODULE) Atomic::Ops.atomicrmw(:xchg, atomic, value, :sequentially_consistent, false) end + + # the functions below work on null-terminated strings; they must not use any C + # runtime features nor the GC! bang methods may allocate memory + + # returns the length in character units of the null-terminated string *str* + private def self.strlen(str : LibC::WCHAR*) : Int32 + len = 0 + while str.value != 0 + len &+= 1 + str += 1 + end + len + end + + # assigns *src* to *dst*, and returns the end of the new string in *dst* + private def self.strcpy(dst : LibC::WCHAR*, src : LibC::WCHAR*) : LibC::WCHAR* + while src.value != 0 + dst.value = src.value + dst += 1 + src += 1 + end + dst + end + + # assigns the concatenation of *args* to the buffer at *buf* with the given + # *size*, possibly reallocating it, and returns the new buffer + private def self.strcat(buf : LibC::WCHAR*, size : Int32, *args : *T) : {LibC::WCHAR*, Int32} forall T + new_size = 1 + {% for i in 0...T.size %} + %len{i} = strlen(args[{{ i }}]) + new_size &+= %len{i} + {% end %} + if new_size > size + size = new_size + buf = LibC.HeapReAlloc(LibC.GetProcessHeap, 0, buf, size &* 2).as(LibC::WCHAR*) + end + + ptr = buf + {% for i in 0...T.size %} + ptr = strcpy(ptr, args[{{ i }}]) + {% end %} + ptr.value = 0 + + {buf, size} + end + + # if *str* starts with *prefix*, returns the substring with *prefix* removed, + # otherwise returns *str* unmodified + private def self.str_lchop(str : LibC::WCHAR*, prefix : LibC::WCHAR*) : LibC::WCHAR* + src = str + + while prefix.value != 0 + return src unless prefix.value == str.value + prefix += 1 + str += 1 + end + + str + end + + # given *str*, a normalized absolute path of *size* UTF-16 code units, returns + # its parent directory by replacing the last directory separator with a null + # character + private def self.dirname(str : LibC::WCHAR*, size : Int32) + ptr = str + size - 1 + + # C:\foo.exe -> C: + # C:\foo\bar.exe -> C:\foo + # C:\foo\bar\baz.exe -> C:\foo\bar + while ptr != str + if ptr.value === '\\' + ptr.value = 0 + return {str, (ptr - str).to_i32!} + end + ptr -= 1 + end + + {str, size} + end + + # effective returns `::File.dirname(::Process.executable_path).to_utf16` + private def self.get_origin! : {LibC::WCHAR*, Int32} + buf = LibC.HeapAlloc(LibC.GetProcessHeap, 0, LibC::MAX_PATH &* 2).as(LibC::WCHAR*) + len = LibC.GetModuleFileNameW(nil, buf, LibC::MAX_PATH) + return dirname(buf, len.to_i32!) unless WinError.value.error_insufficient_buffer? + + buf = LibC.HeapReAlloc(LibC.GetProcessHeap, 0, buf, 65534).as(LibC::WCHAR*) + len = LibC.GetModuleFileNameW(nil, buf, 32767) + return dirname(buf, len.to_i32!) unless WinError.value.error_insufficient_buffer? + + print_error("FATAL: Failed to get current executable path\n") + LibC.ExitProcess(1) + end + + # converts *utf8_str* to a UTF-16 string + private def self.to_utf16!(utf8_str : LibC::Char*) : LibC::WCHAR* + utf16_size = LibC.MultiByteToWideChar(LibC::CP_UTF8, 0, utf8_str, -1, nil, 0) + utf16_str = LibC.HeapAlloc(LibC.GetProcessHeap, 0, utf16_size &* 2).as(LibC::WCHAR*) + LibC.MultiByteToWideChar(LibC::CP_UTF8, 0, utf8_str, -1, utf16_str, utf16_size) + utf16_str + end + + # replaces all instances of "$ORIGIN" in *str* with the directory containing + # the running executable + # if "$ORIGIN" is not found, returns *str* unmodified without allocating + # memory + private def self.expand_origin!(str : LibC::WCHAR*) : LibC::WCHAR* + origin_prefix = UInt16.static_array(0x24, 0x4F, 0x52, 0x49, 0x47, 0x49, 0x4E, 0x00) # "$ORIGIN".to_utf16 + ptr = str + origin = Pointer(LibC::WCHAR).null + origin_size = 0 + output_size = 1 + + while ptr.value != 0 + new_ptr = str_lchop(ptr, origin_prefix.to_unsafe) + if new_ptr != ptr + origin, origin_size = get_origin! unless origin + output_size &+= origin_size + ptr = new_ptr + next + end + output_size &+= 1 + ptr += 1 + end + + return str unless origin + output = LibC.HeapAlloc(LibC.GetProcessHeap, 0, output_size &* 2).as(LibC::WCHAR*) + dst = output + ptr = str + + while ptr.value != 0 + new_ptr = str_lchop(ptr, origin_prefix.to_unsafe) + if new_ptr != ptr + dst = strcpy(dst, origin) + ptr = new_ptr + next + end + dst.value = ptr.value + dst += 1 + ptr += 1 + end + dst.value = 0 + + LibC.HeapFree(LibC.GetProcessHeap, 0, origin) + output + end + + # `dll` is an ASCII base name without directory separators, e.g. `WS2_32.dll` + def self.load_library(dll : LibC::Char*) : LibC::HMODULE + utf16_dll = to_utf16!(dll) + + {% begin %} + {% paths = Crystal::LIBRARY_RPATH.gsub(/\$\{ORIGIN\}/, "$ORIGIN").split(::Process::PATH_DELIMITER).reject(&.empty?) %} + {% unless paths.empty? %} + size = 0x40 + buf = LibC.HeapAlloc(LibC.GetProcessHeap, 0, size &* 2).as(LibC::WCHAR*) + + {% for path, i in paths %} + # TODO: can this `to_utf16` be done at compilation time? + root = to_utf16!({{ path.ends_with?("\\") ? path : path + "\\" }}.to_unsafe) + root_expanded = expand_origin!(root) + buf, size = strcat(buf, size, root_expanded, utf16_dll) + handle = LibC.LoadLibraryExW(buf, nil, LibC::LOAD_WITH_ALTERED_SEARCH_PATH) + LibC.HeapFree(LibC.GetProcessHeap, 0, root_expanded) if root_expanded != root + LibC.HeapFree(LibC.GetProcessHeap, 0, root) + + if handle + LibC.HeapFree(LibC.GetProcessHeap, 0, buf) + LibC.HeapFree(LibC.GetProcessHeap, 0, utf16_dll) + return handle + end + {% end %} + + LibC.HeapFree(LibC.GetProcessHeap, 0, buf) + {% end %} + {% end %} + + handle = LibC.LoadLibraryExW(utf16_dll, nil, 0) + LibC.HeapFree(LibC.GetProcessHeap, 0, utf16_dll) + handle + end end # This is a port of the default delay-load helper function in the `DelayHlp.cpp` @@ -123,8 +305,7 @@ fun __delayLoadHelper2(pidd : LibC::ImgDelayDescr*, ppfnIATEntry : LibC::FARPROC # Check to see if we need to try to load the library. if !hmod - # note: ANSI variant used here - unless hmod = LibC.LoadLibraryExA(dli.szDll, nil, 0) + unless hmod = Crystal::System::DelayLoad.load_library(dli.szDll) # DloadReleaseSectionWriteAccess print_error("FATAL: Cannot find the DLL named `%1`, exiting\n", dli.szDll) LibC.ExitProcess(1) diff --git a/src/lib_c/x86_64-windows-msvc/c/heapapi.cr b/src/lib_c/x86_64-windows-msvc/c/heapapi.cr new file mode 100644 index 000000000000..1738cf774cac --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/heapapi.cr @@ -0,0 +1,8 @@ +require "c/winnt" + +lib LibC + fun GetProcessHeap : HANDLE + fun HeapAlloc(hHeap : HANDLE, dwFlags : DWORD, dwBytes : SizeT) : Void* + fun HeapReAlloc(hHeap : HANDLE, dwFlags : DWORD, lpMem : Void*, dwBytes : SizeT) : Void* + fun HeapFree(hHeap : HANDLE, dwFlags : DWORD, lpMem : Void*) : BOOL +end diff --git a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr index 2b6259bf7015..37a95f3fa089 100644 --- a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr @@ -4,7 +4,8 @@ require "c/winnt" lib LibC alias FARPROC = Void* - fun LoadLibraryExA(lpLibFileName : LPSTR, hFile : HANDLE, dwFlags : DWORD) : HMODULE + LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 + fun LoadLibraryExW(lpLibFileName : LPWSTR, hFile : HANDLE, dwFlags : DWORD) : HMODULE fun FreeLibrary(hLibModule : HMODULE) : BOOL diff --git a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr index 060049841819..971e96fa9eb5 100644 --- a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr +++ b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr @@ -6,8 +6,15 @@ lib LibC # this is only for the `wmain` entry point where Crystal's standard library is # unusable, all other code should use `String.from_utf16` instead fun WideCharToMultiByte( - codePage : DWORD, dwFlags : DWORD, lpWideCharStr : WCHAR*, + codePage : UInt, dwFlags : DWORD, lpWideCharStr : LPWSTR, cchWideChar : Int, lpMultiByteStr : LPSTR, cbMultiByte : Int, lpDefaultChar : CHAR*, lpUsedDefaultChar : BOOL* ) : Int + + # this is only for the delay-load helper, all other code should use + # `String#to_utf16` instead + fun MultiByteToWideChar( + codePage : UInt, dwFlags : DWORD, lpMultiByteStr : LPSTR, + cbMultiByte : Int, lpWideCharStr : LPWSTR, cchWideChar : Int + ) : Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index 96ca0710bc2d..40ef8a695d20 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -12,7 +12,7 @@ lib LibC FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000_u32 FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF_u32 - STD_ERROR_HANDLE = DWORD.new!(-12) + STD_ERROR_HANDLE = 0xFFFFFFF4_u32 fun FormatMessageA(dwFlags : DWORD, lpSource : Void*, dwMessageId : DWORD, dwLanguageId : DWORD, lpBuffer : LPSTR, nSize : DWORD, arguments : Void*) : DWORD diff --git a/src/process/executable_path.cr b/src/process/executable_path.cr index cf2dd091c6ab..20c78b77e232 100644 --- a/src/process/executable_path.cr +++ b/src/process/executable_path.cr @@ -3,7 +3,11 @@ # - http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe class Process - PATH_DELIMITER = {% if flag?(:windows) %} ';' {% else %} ':' {% end %} + {% if flag?(:windows) %} + PATH_DELIMITER = ';' + {% else %} + PATH_DELIMITER = ':' + {% end %} # :nodoc: INITIAL_PATH = ENV["PATH"]? From 02ca9d582a3a805a8ce15e9155b2f149911843f8 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 5 Jun 2023 22:38:32 +0800 Subject: [PATCH 0562/1551] Fix: Do not drop `/LIBPATH` from Windows linker command (#13530) --- src/compiler/crystal/loader/msvc.cr | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index 01b8e6ce7b06..bf0b5c74a36f 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -89,12 +89,13 @@ class Crystal::Loader extra_search_paths = [] of String args.each do |arg| - if lib_path = arg.lchop?("/LIBPATH:") - extra_search_paths << lib_path - elsif !arg.starts_with?('/') && (name = arg.rchop?(".lib")) + if !arg.starts_with?('/') && (name = arg.rchop?(".lib")) libnames << name - elsif remaining - remaining << arg + else + remaining << arg if remaining + if lib_path = arg.lchop?("/LIBPATH:") + extra_search_paths << lib_path + end end end From f2e4c8c53a9af5bcfe75c255b418debbcd3e82b4 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 5 Jun 2023 22:39:49 +0800 Subject: [PATCH 0563/1551] Docs: Fix operators in `Atomic#add`, `#sub`, `#max`, `#min` (#13523) --- src/atomic.cr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/atomic.cr b/src/atomic.cr index 99d2febaf941..3e14c1fba4ad 100644 --- a/src/atomic.cr +++ b/src/atomic.cr @@ -47,7 +47,7 @@ struct Atomic(T) {% end %} end - # Performs `atomic_value += value`. Returns the old value. + # Performs `atomic_value &+= value`. Returns the old value. # # ``` # atomic = Atomic.new(1) @@ -58,7 +58,7 @@ struct Atomic(T) Ops.atomicrmw(:add, pointerof(@value), value, :sequentially_consistent, false) end - # Performs `atomic_value -= value`. Returns the old value. + # Performs `atomic_value &-= value`. Returns the old value. # # ``` # atomic = Atomic.new(9) @@ -113,7 +113,7 @@ struct Atomic(T) Ops.atomicrmw(:xor, pointerof(@value), value, :sequentially_consistent, false) end - # Performs `atomic_value = max(atomic_value, value)`. Returns the old value. + # Performs `atomic_value = {atomic_value, value}.max`. Returns the old value. # # ``` # atomic = Atomic.new(5) @@ -132,7 +132,7 @@ struct Atomic(T) {% end %} end - # Performs `atomic_value = min(atomic_value, value)`. Returns the old value. + # Performs `atomic_value = {atomic_value, value}.min`. Returns the old value. # # ``` # atomic = Atomic.new(5) From 1b5dcee2e274de6854466f101f353ac9255ccb75 Mon Sep 17 00:00:00 2001 From: Devonte W Date: Tue, 6 Jun 2023 09:34:16 +0100 Subject: [PATCH 0564/1551] Implement `#match!` for Regex (#13285) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Johannes Müller --- spec/std/regex_spec.cr | 27 ++++++++++++ spec/std/string_spec.cr | 19 ++++++++ src/regex.cr | 15 +++++++ src/regex/match_data.cr | 98 ++++++++++++++++++++--------------------- src/string.cr | 19 +++++--- 5 files changed, 124 insertions(+), 54 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 116da32b24e5..0d600603c3cc 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -133,6 +133,33 @@ describe "Regex" do end end + describe "#match!" do + it "returns match data" do + md = /(?.)(?.)/.match!("Crystal") + md[0].should eq "Cr" + md.captures.should eq [] of String + md.named_captures.should eq({"bar" => "C", "foo" => "r"}) + end + + it "assigns captures" do + md = /foo/.match!("foo") + $~.should eq md + end + + it "raises on non-match" do + expect_raises(Regex::Error, "Match not found") { /Crystal/.match!("foo") } + expect_raises(NilAssertionError) { $~ } + end + + context "with options" do + it "Regex::Match options" do + expect_raises(Regex::Error, "Match not found") do + /foo/.match!(".foo", options: Regex::MatchOptions::ANCHORED) + end + end + end + end + describe "#match_at_byte_index" do it "assigns captures" do matchdata = /foo/.match_at_byte_index("..foo", 1) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 4659e0bcbb05..e3aee09eb108 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2467,6 +2467,25 @@ describe "String" do end end + describe "match!" do + it "returns matchdata" do + md = "Crystal".match! /(?.)(?.)/ + md[0].should eq "Cr" + md.captures.should eq [] of String + md.named_captures.should eq({"bar" => "C", "foo" => "r"}) + end + + it "assigns captures" do + md = "foo".match! /foo/ + $~.should eq md + end + + it "raises on non-match" do + expect_raises(Regex::Error, "Match not found") { "foo".match! /Crystal/ } + expect_raises(NilAssertionError) { $~ } + end + end + it "does %" do ("Hello %d world" % 123).should eq("Hello 123 world") ("Hello %d world" % [123]).should eq("Hello 123 world") diff --git a/src/regex.cr b/src/regex.cr index 0d94fc03cc1f..c0f0ad56b4da 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -582,6 +582,21 @@ class Regex match(str, pos, options: _options) end + # Matches a regular expression against *str*. This starts at the character + # index *pos* if given, otherwise at the start of *str*. Returns a `Regex::MatchData` + # if *str* matched, otherwise raises `Regex::Error`. `$~` will contain the same value + # if matched. + # + # ``` + # /(.)(.)(.)/.match!("abc")[2] # => "b" + # /(.)(.)/.match!("abc", 1)[2] # => "c" + # /(.)(タ)/.match!("クリスタル", 3)[2] # raises Exception + # ``` + def match!(str : String, pos : Int32 = 0, *, options : Regex::MatchOptions = :none) : MatchData + byte_index = str.char_index_to_byte_index(pos) || raise Error.new "Match not found" + $~ = match_at_byte_index(str, byte_index, options) || raise Error.new "Match not found" + end + # Match at byte index. Matches a regular expression against `String` # *str*. Starts at the byte index given by *pos* if given, otherwise at # the start of *str*. Returns a `Regex::MatchData` if *str* matched, otherwise diff --git a/src/regex/match_data.cr b/src/regex/match_data.cr index 949bce29e603..b9271d423f82 100644 --- a/src/regex/match_data.cr +++ b/src/regex/match_data.cr @@ -21,32 +21,32 @@ class Regex # Returns the original regular expression. # # ``` - # "Crystal".match(/[p-s]/).not_nil!.regex # => /[p-s]/ + # "Crystal".match!(/[p-s]/).regex # => /[p-s]/ # ``` getter regex : Regex # Returns the number of capture groups, including named capture groups. # # ``` - # "Crystal".match(/[p-s]/).not_nil!.group_size # => 0 - # "Crystal".match(/r(ys)/).not_nil!.group_size # => 1 - # "Crystal".match(/r(ys)(?ta)/).not_nil!.group_size # => 2 + # "Crystal".match!(/[p-s]/).group_size # => 0 + # "Crystal".match!(/r(ys)/).group_size # => 1 + # "Crystal".match!(/r(ys)(?ta)/).group_size # => 2 # ``` getter group_size : Int32 # Returns the original string. # # ``` - # "Crystal".match(/[p-s]/).not_nil!.string # => "Crystal" + # "Crystal".match!(/[p-s]/).string # => "Crystal" # ``` getter string : String # Returns the number of elements in this match object. # # ``` - # "Crystal".match(/[p-s]/).not_nil!.size # => 1 - # "Crystal".match(/r(ys)/).not_nil!.size # => 2 - # "Crystal".match(/r(ys)(?ta)/).not_nil!.size # => 3 + # "Crystal".match!(/[p-s]/).size # => 1 + # "Crystal".match!(/r(ys)/).size # => 2 + # "Crystal".match!(/r(ys)(?ta)/).size # => 3 # ``` def size : Int32 group_size + 1 @@ -61,11 +61,11 @@ class Regex # subpattern is unused. # # ``` - # "Crystal".match(/r/).not_nil!.begin(0) # => 1 - # "Crystal".match(/r(ys)/).not_nil!.begin(1) # => 2 - # "クリスタル".match(/リ(ス)/).not_nil!.begin(0) # => 1 - # "Crystal".match(/r/).not_nil!.begin(1) # IndexError: Invalid capture group index: 1 - # "Crystal".match(/r(x)?/).not_nil!.begin(1) # IndexError: Capture group 1 was not matched + # "Crystal".match!(/r/).begin(0) # => 1 + # "Crystal".match!(/r(ys)/).begin(1) # => 2 + # "クリスタル".match!(/リ(ス)/).begin(0) # => 1 + # "Crystal".match!(/r/).begin(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match!(/r(x)?/).begin(1) # IndexError: Capture group 1 was not matched # ``` def begin(n = 0) : Int32 @string.byte_index_to_char_index(byte_begin(n)).not_nil! @@ -80,11 +80,11 @@ class Regex # subpattern is unused. # # ``` - # "Crystal".match(/r/).not_nil!.end(0) # => 2 - # "Crystal".match(/r(ys)/).not_nil!.end(1) # => 4 - # "クリスタル".match(/リ(ス)/).not_nil!.end(0) # => 3 - # "Crystal".match(/r/).not_nil!.end(1) # IndexError: Invalid capture group index: 1 - # "Crystal".match(/r(x)?/).not_nil!.end(1) # IndexError: Capture group 1 was not matched + # "Crystal".match!(/r/).end(0) # => 2 + # "Crystal".match!(/r(ys)/).end(1) # => 4 + # "クリスタル".match!(/リ(ス)/).end(0) # => 3 + # "Crystal".match!(/r/).end(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match!(/r(x)?/).end(1) # IndexError: Capture group 1 was not matched # ``` def end(n = 0) : Int32 @string.byte_index_to_char_index(byte_end(n)).not_nil! @@ -99,11 +99,11 @@ class Regex # subpattern is unused. # # ``` - # "Crystal".match(/r/).not_nil!.byte_begin(0) # => 1 - # "Crystal".match(/r(ys)/).not_nil!.byte_begin(1) # => 2 - # "クリスタル".match(/リ(ス)/).not_nil!.byte_begin(0) # => 3 - # "Crystal".match(/r/).not_nil!.byte_begin(1) # IndexError: Invalid capture group index: 1 - # "Crystal".match(/r(x)?/).not_nil!.byte_begin(1) # IndexError: Capture group 1 was not matched + # "Crystal".match!(/r/).byte_begin(0) # => 1 + # "Crystal".match!(/r(ys)/).byte_begin(1) # => 2 + # "クリスタル".match!(/リ(ス)/).byte_begin(0) # => 3 + # "Crystal".match!(/r/).byte_begin(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match!(/r(x)?/).byte_begin(1) # IndexError: Capture group 1 was not matched # ``` def byte_begin(n = 0) : Int32 check_index_out_of_bounds n @@ -119,11 +119,11 @@ class Regex # subpattern is unused. # # ``` - # "Crystal".match(/r/).not_nil!.byte_end(0) # => 2 - # "Crystal".match(/r(ys)/).not_nil!.byte_end(1) # => 4 - # "クリスタル".match(/リ(ス)/).not_nil!.byte_end(0) # => 9 - # "Crystal".match(/r/).not_nil!.byte_end(1) # IndexError: Invalid capture group index: 1 - # "Crystal".match(/r(x)?/).not_nil!.byte_end(1) # IndexError: Capture group 1 was not matched + # "Crystal".match!(/r/).byte_end(0) # => 2 + # "Crystal".match!(/r(ys)/).byte_end(1) # => 4 + # "クリスタル".match!(/リ(ス)/).byte_end(0) # => 9 + # "Crystal".match!(/r/).byte_end(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match!(/r(x)?/).byte_end(1) # IndexError: Capture group 1 was not matched # ``` def byte_end(n = 0) : Int32 check_index_out_of_bounds n @@ -136,9 +136,9 @@ class Regex # When *n* is `0`, returns the match for the entire `Regex`. # # ``` - # "Crystal".match(/r(ys)/).not_nil![0]? # => "rys" - # "Crystal".match(/r(ys)/).not_nil![1]? # => "ys" - # "Crystal".match(/r(ys)/).not_nil![2]? # => nil + # "Crystal".match!(/r(ys)/)[0]? # => "rys" + # "Crystal".match!(/r(ys)/)[1]? # => "ys" + # "Crystal".match!(/r(ys)/)[2]? # => nil # ``` def []?(n : Int) : String? return unless valid_group?(n) @@ -151,8 +151,8 @@ class Regex # if there is no *n*th capture group. # # ``` - # "Crystal".match(/r(ys)/).not_nil![1] # => "ys" - # "Crystal".match(/r(ys)/).not_nil![2] # raises IndexError + # "Crystal".match!(/r(ys)/)[1] # => "ys" + # "Crystal".match!(/r(ys)/)[2] # raises IndexError # ``` def [](n : Int) : String check_index_out_of_bounds n @@ -165,15 +165,15 @@ class Regex # `nil` if there is no such named capture group. # # ``` - # "Crystal".match(/r(?ys)/).not_nil!["ok"]? # => "ys" - # "Crystal".match(/r(?ys)/).not_nil!["ng"]? # => nil + # "Crystal".match!(/r(?ys)/)["ok"]? # => "ys" + # "Crystal".match!(/r(?ys)/)["ng"]? # => nil # ``` # # When there are capture groups having same name, it returns the last # matched capture group. # # ``` - # "Crystal".match(/(?Cr).*(?al)/).not_nil!["ok"]? # => "al" + # "Crystal".match!(/(?Cr).*(?al)/)["ok"]? # => "al" # ``` def []?(group_name : String) : String? fetch_impl(group_name) { nil } @@ -183,15 +183,15 @@ class Regex # raises an `KeyError` if there is no such named capture group. # # ``` - # "Crystal".match(/r(?ys)/).not_nil!["ok"] # => "ys" - # "Crystal".match(/r(?ys)/).not_nil!["ng"] # raises KeyError + # "Crystal".match!(/r(?ys)/)["ok"] # => "ys" + # "Crystal".match!(/r(?ys)/)["ng"] # raises KeyError # ``` # # When there are capture groups having same name, it returns the last # matched capture group. # # ``` - # "Crystal".match(/(?Cr).*(?al)/).not_nil!["ok"] # => "al" + # "Crystal".match!(/(?Cr).*(?al)/)["ok"] # => "al" # ``` def [](group_name : String) : String fetch_impl(group_name) { |exists| @@ -230,7 +230,7 @@ class Regex # starts at the start of the string, returns the empty string. # # ``` - # "Crystal".match(/yst/).not_nil!.pre_match # => "Cr" + # "Crystal".match!(/yst/).pre_match # => "Cr" # ``` def pre_match : String @string.byte_slice(0, byte_begin(0)) @@ -240,7 +240,7 @@ class Regex # at the end of the string, returns the empty string. # # ``` - # "Crystal".match(/yst/).not_nil!.post_match # => "al" + # "Crystal".match!(/yst/).post_match # => "al" # ``` def post_match : String @string.byte_slice(byte_end(0)) @@ -251,12 +251,12 @@ class Regex # It is a difference from `to_a` that the result array does not contain the match for the entire `Regex` (`self[0]`). # # ``` - # match = "Crystal".match(/(Cr)(?y)(st)(?al)/).not_nil! + # match = "Crystal".match!(/(Cr)(?y)(st)(?al)/) # match.captures # => ["Cr", "st"] # # # When this regex has an optional group, result array may contain # # a `nil` if this group is not matched. - # match = "Crystal".match(/(Cr)(stal)?/).not_nil! + # match = "Crystal".match!(/(Cr)(stal)?/) # match.captures # => ["Cr", nil] # ``` def captures : Array(String?) @@ -273,12 +273,12 @@ class Regex # Returns a hash of named capture groups. # # ``` - # match = "Crystal".match(/(Cr)(?y)(st)(?al)/).not_nil! + # match = "Crystal".match!(/(Cr)(?y)(st)(?al)/) # match.named_captures # => {"name1" => "y", "name2" => "al"} # # # When this regex has an optional group, result hash may contain # # a `nil` if this group is not matched. - # match = "Crystal".match(/(?Cr)(?stal)?/).not_nil! + # match = "Crystal".match!(/(?Cr)(?stal)?/) # match.named_captures # => {"name1" => "Cr", "name2" => nil} # ``` def named_captures : Hash(String, String?) @@ -297,12 +297,12 @@ class Regex # Convert this match data into an array. # # ``` - # match = "Crystal".match(/(Cr)(?y)(st)(?al)/).not_nil! + # match = "Crystal".match!(/(Cr)(?y)(st)(?al)/) # match.to_a # => ["Crystal", "Cr", "y", "st", "al"] # # # When this regex has an optional group, result array may contain # # a `nil` if this group is not matched. - # match = "Crystal".match(/(Cr)(?stal)?/).not_nil! + # match = "Crystal".match!(/(Cr)(?stal)?/) # match.to_a # => ["Cr", "Cr", nil] # ``` def to_a : Array(String?) @@ -312,12 +312,12 @@ class Regex # Convert this match data into a hash. # # ``` - # match = "Crystal".match(/(Cr)(?y)(st)(?al)/).not_nil! + # match = "Crystal".match!(/(Cr)(?y)(st)(?al)/) # match.to_h # => {0 => "Crystal", 1 => "Cr", "name1" => "y", 3 => "st", "name2" => "al"} # # # When this regex has an optional group, result array may contain # # a `nil` if this group is not matched. - # match = "Crystal".match(/(Cr)(?stal)?/).not_nil! + # match = "Crystal".match!(/(Cr)(?stal)?/) # match.to_h # => {0 => "Cr", 1 => "Cr", "name1" => nil} # ``` def to_h : Hash(Int32 | String, String?) diff --git a/src/string.cr b/src/string.cr index c5f8c1dcff01..56a1f7d986d7 100644 --- a/src/string.cr +++ b/src/string.cr @@ -4558,8 +4558,7 @@ class String end end - # Finds match of *regex*, starting at *pos*. - # It also updates `$~` with the result. + # Finds matches of *regex* starting at *pos* and updates `$~` to the result. # # ``` # "foo".match(/foo/) # => Regex::MatchData("foo") @@ -4569,9 +4568,19 @@ class String # $~ # raises Exception # ``` def match(regex : Regex, pos = 0) : Regex::MatchData? - match = regex.match self, pos - $~ = match - match + $~ = regex.match self, pos + end + + # Finds matches of *regex* starting at *pos* and updates `$~` to the result. + # Raises `Regex::Error` if there are no matches. + # + # ``` + # "foo".match!(/foo/) # => Regex::MatchData("foo") + # $~ # => Regex::MatchData("foo") + # + # "foo".match!(/bar/) # => raises Exception + def match!(regex : Regex, pos = 0) : Regex::MatchData + $~ = regex.match! self, pos end # Finds match of *regex* like `#match`, but it returns `Bool` value. From 8de8537d603a59a73b1eac64ff0e7f8fa36cd298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 6 Jun 2023 10:52:51 +0200 Subject: [PATCH 0565/1551] Add support for dash separator to `Enum.parse` (#13508) --- spec/std/enum_spec.cr | 5 ++++- spec/std/json/serialization_spec.cr | 5 ++--- spec/std/yaml/serialization_spec.cr | 5 ++--- src/enum.cr | 12 +++++++----- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/spec/std/enum_spec.cr b/spec/std/enum_spec.cr index 19248788cbb3..e4c67fad3a35 100644 --- a/spec/std/enum_spec.cr +++ b/spec/std/enum_spec.cr @@ -288,6 +288,8 @@ describe Enum do SpecEnum2.parse("FORTY_FOUR").should eq(SpecEnum2::FORTY_FOUR) SpecEnum2.parse("forty_four").should eq(SpecEnum2::FORTY_FOUR) + SpecEnum2.parse("FORTY-FOUR").should eq(SpecEnum2::FORTY_FOUR) + SpecEnum2.parse("forty-four").should eq(SpecEnum2::FORTY_FOUR) SpecEnum2.parse("FortyFour").should eq(SpecEnum2::FORTY_FOUR) SpecEnum2.parse("FORTYFOUR").should eq(SpecEnum2::FORTY_FOUR) SpecEnum2.parse("fortyfour").should eq(SpecEnum2::FORTY_FOUR) @@ -301,9 +303,10 @@ describe Enum do SpecEnumWithCaseSensitiveMembers.parse("Foo").should eq SpecEnumWithCaseSensitiveMembers::FOO end - it "parses?" do + it ".parse?" do SpecEnum.parse?("Two").should eq(SpecEnum::Two) SpecEnum.parse?("Four").should be_nil + SpecEnum.parse?("Fo-ur").should be_nil end it "clones" do diff --git a/spec/std/json/serialization_spec.cr b/spec/std/json/serialization_spec.cr index 626926f53d21..980bc1d65533 100644 --- a/spec/std/json/serialization_spec.cr +++ b/spec/std/json/serialization_spec.cr @@ -293,9 +293,8 @@ describe "JSON serialization" do JSONSpecEnum.from_json(%("One")).should eq(JSONSpecEnum::One) JSONSpecEnum.from_json(%("two")).should eq(JSONSpecEnum::Two) JSONSpecEnum.from_json(%("ONE_HUNDRED")).should eq(JSONSpecEnum::OneHundred) - expect_raises(JSON::ParseException, %(Unknown enum JSONSpecEnum value: "ONE-HUNDRED")) do - JSONSpecEnum.from_json(%("ONE-HUNDRED")) - end + JSONSpecEnum.from_json(%("ONE-HUNDRED")).should eq(JSONSpecEnum::OneHundred) + expect_raises(JSON::ParseException, %(Unknown enum JSONSpecEnum value: " one ")) do JSONSpecEnum.from_json(%(" one ")) end diff --git a/spec/std/yaml/serialization_spec.cr b/spec/std/yaml/serialization_spec.cr index 54d2993ee481..b65667d8a504 100644 --- a/spec/std/yaml/serialization_spec.cr +++ b/spec/std/yaml/serialization_spec.cr @@ -232,9 +232,8 @@ describe "YAML serialization" do YAMLSpecEnum.from_yaml(%("One")).should eq(YAMLSpecEnum::One) YAMLSpecEnum.from_yaml(%("two")).should eq(YAMLSpecEnum::Two) YAMLSpecEnum.from_yaml(%("ONE_HUNDRED")).should eq(YAMLSpecEnum::OneHundred) - expect_raises(YAML::ParseException, %(Unknown enum YAMLSpecEnum value: "ONE-HUNDRED")) do - YAMLSpecEnum.from_yaml(%("ONE-HUNDRED")) - end + YAMLSpecEnum.from_yaml(%("ONE-HUNDRED")).should eq(YAMLSpecEnum::OneHundred) + expect_raises(YAML::ParseException, %(Unknown enum YAMLSpecEnum value: " one ")) do YAMLSpecEnum.from_yaml(%(" one ")) end diff --git a/src/enum.cr b/src/enum.cr index a42f9901f9cf..d2ae191b7318 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -477,9 +477,10 @@ struct Enum # Returns the enum member that has the given name, or # raises `ArgumentError` if no such member exists. The comparison is made by using # `String#camelcase` and `String#downcase` between *string* and - # the enum members names, so a member named "FortyTwo" or "FORTY_TWO" + # the enum members names. Dashes (`-`) in *string* have the same meaning as an underscore (`_`). + # A member named "FortyTwo" or "FORTY_TWO" # is found with any of these strings: "forty_two", "FortyTwo", "FORTY_TWO", - # "FORTYTWO", "fortytwo". + # "Forty-Two", "FORTYTWO", "fortytwo". # # ``` # Color.parse("Red") # => Color::Red @@ -493,9 +494,10 @@ struct Enum # Returns the enum member that has the given name, or # `nil` if no such member exists. The comparison is made by using # `String#camelcase` and `String#downcase` between *string* and - # the enum members names, so a member named "FortyTwo" or "FORTY_TWO" + # the enum members names. Dashes (`-`) in *string* have the same meaning as an underscore (`_`). + # A member named "FortyTwo", or "FORTY_TWO" # is found with any of these strings: "forty_two", "FortyTwo", "FORTY_TWO", - # "FORTYTWO", "fortytwo". + # "Forty-Two", "FORTYTWO", "fortytwo". # # ``` # Color.parse?("Red") # => Color::Red @@ -506,7 +508,7 @@ struct Enum # If multiple members match the same normalized string, the first one is returned. def self.parse?(string : String) : self? {% begin %} - case string.camelcase.downcase + case string.gsub('-', '_').camelcase.downcase # Temporarily map all constants to their normalized value in order to # avoid duplicates in the `case` conditions. # `FOO` and `Foo` members would both generate `when "foo"` which creates a compile time error. From b91648f92a1bb6319aa31be4518f12bb4c208728 Mon Sep 17 00:00:00 2001 From: GeopJr Date: Tue, 6 Jun 2023 11:53:13 +0300 Subject: [PATCH 0566/1551] Add dark mode to docs (#13512) --- .../crystal/tools/doc/html/css/style.css | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/src/compiler/crystal/tools/doc/html/css/style.css b/src/compiler/crystal/tools/doc/html/css/style.css index ebbb89251043..281a0b943966 100644 --- a/src/compiler/crystal/tools/doc/html/css/style.css +++ b/src/compiler/crystal/tools/doc/html/css/style.css @@ -724,3 +724,152 @@ span.flag.lime { .main-content h6:hover .anchor .octicon-link { visibility: visible } + +@media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + } + + html, body { + background: #1b1b1b; + } + + body { + color: white; + } + + a { + color: #8cb4ff; + } + + .main-content a:visited { + color: #5f8de3; + } + + h1, h2, h3, h4, h5, h6 { + color: white; + } + + h1.type-name { + color: white; + background-color: #202020; + border: 1px solid #353535; + } + + .superclass-hierarchy .superclass a, + .superclass-hierarchy .superclass a:visited, + .other-type a, + .other-type a:visited, + .entry-summary .signature, + .entry-summary a:visited { + background-color: #202020; + color: white; + border: 1px solid #353535; + } + + .superclass-hierarchy .superclass a:hover, + .other-type a:hover, + .entry-summary .signature:hover { + background: #443d4d; + border-color: #b092d4; + } + + .kind { + color: #b092d4; + } + + .n { + color: #00ade6; + } + + .t { + color: #00ade6; + } + + .k { + color: #ff66ae; + } + + .o { + color: #ff66ae; + } + + .s { + color: #7799ff; + } + + .i { + color: #b38668; + } + + .m { + color: #b9a5d6; + } + + .c { + color: #a1a1a1; + } + + .methods-inherited a, .methods-inherited a:visited { + color: #B290D9; + } + + .methods-inherited a:hover { + color: #D4B7F4; + } + + .methods-inherited .tooltip>span { + background: #443d4d; + } + + .methods-inherited .tooltip * { + color: white; + } + + .entry-detail:target .signature { + background-color: #443d4d; + border: 1px solid #b092d4; + } + + .entry-detail .signature { + background-color: #202020; + color: white; + border: 1px solid #353535; + } + + .entry-detail .signature .method-permalink { + color: #b092d4; + } + + :not(pre)>code { + background-color: #202020; + } + + span.flag.purple { + background-color: #443d4d; + color: #ECE1F9; + border-color: #b092d4; + } + + .sidebar input::-webkit-input-placeholder { /* Chrome/Opera/Safari */ + color: white; + } + + .sidebar input::-moz-placeholder { /* Firefox 19+ */ + color: white; + } + + .sidebar input:-ms-input-placeholder { /* IE 10+ */ + color: white; + } + + .sidebar input:-moz-placeholder { /* Firefox 18- */ + color: white; + } + + pre { + color: white; + background: #202020; + border: 1px solid #353535; + } +} \ No newline at end of file From 38a23f9f4b7c10a4a61d27d6231e95d627e0eebd Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 6 Jun 2023 21:02:45 +0800 Subject: [PATCH 0567/1551] Make comparisons between `BigRational` and `BigFloat` exact (#13538) --- spec/std/big/big_rational_spec.cr | 45 ++++++++++++++++++++----------- src/big/big_rational.cr | 8 +++++- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr index 652e15b34726..ce9387d8489a 100644 --- a/spec/std/big/big_rational_spec.cr +++ b/spec/std/big/big_rational_spec.cr @@ -5,21 +5,31 @@ private def br(n, d) BigRational.new(n, d) end -private def test_comp(val, less, equal, greater, file = __FILE__, line = __LINE__) - (val < greater).should eq(true), file: file, line: line - (greater < val).should eq(false), file: file, line: line - (val <=> greater).should eq(-1), file: file, line: line - (greater <=> val).should eq(1), file: file, line: line - - (val == equal).should eq(true), file: file, line: line - (equal == val).should eq(true), file: file, line: line - (val <=> equal).should eq(0), file: file, line: line - (equal <=> val).should eq(0), file: file, line: line - - (val > less).should eq(true), file: file, line: line - (less > val).should eq(false), file: file, line: line - (val <=> less).should eq(1), file: file, line: line - (less <=> val).should eq(-1), file: file, line: line +private def test_greater(val, other, *, file = __FILE__, line = __LINE__) + val.should be > other, file: file, line: line + other.should_not be > val, file: file, line: line + (val <=> other).should_not(be_nil).should be > 0, file: file, line: line + (other <=> val).should_not(be_nil).should be < 0, file: file, line: line +end + +private def test_equal(val, other, *, file = __FILE__, line = __LINE__) + (val == other).should be_true, file: file, line: line + (other == val).should be_true, file: file, line: line + (val <=> other).should eq(0), file: file, line: line + (other <=> val).should eq(0), file: file, line: line +end + +private def test_less(val, other, *, file = __FILE__, line = __LINE__) + val.should be < other, file: file, line: line + other.should_not be < val, file: file, line: line + (val <=> other).should_not(be_nil).should be < 0, file: file, line: line + (other <=> val).should_not(be_nil).should be > 0, file: file, line: line +end + +private def test_comp(val, less, equal, greater, *, file = __FILE__, line = __LINE__) + test_greater(val, less, file: file, line: line) + test_equal(val, equal, file: file, line: line) + test_less(val, greater, file: file, line: line) end describe BigRational do @@ -164,6 +174,11 @@ describe BigRational do test_comp(br(10, 2), 4.0_f64, 5.0_f64, 6.0_f64) end + it "BigFloat and Comparable" do + test_greater(1.to_big_r + 0.5.to_big_r ** (BigFloat.default_precision + 66), 1.to_big_f) + test_less(1.to_big_r - 0.5.to_big_r ** (BigFloat.default_precision + 66), 1.to_big_f) + end + it "compares against NaNs" do (1.to_big_r <=> Float64::NAN).should be_nil (1.to_big_r <=> Float32::NAN).should be_nil diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index 0547b7934e55..a91263b1c05a 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -94,7 +94,7 @@ struct BigRational < Number end def <=>(other : BigFloat) - to_big_f <=> other.to_big_f + self <=> other.to_big_r end def <=>(other : Int) @@ -353,6 +353,12 @@ struct Float end end +struct BigFloat + def <=>(other : BigRational) + -(other <=> self) + end +end + module Math # Calculates the square root of *value*. # From 6ea944155dcc79fd5ea7360957e0b139f9af6154 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 6 Jun 2023 21:03:13 +0800 Subject: [PATCH 0568/1551] Fix `String#underscore` with multi-character downcasing (#13540) --- spec/std/string_spec.cr | 44 +++++++++++++++-------------------------- src/string.cr | 18 ++++++++--------- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index e3aee09eb108..ed86176771e8 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2185,34 +2185,22 @@ describe "String" do end describe "#underscore" do - it { "Foo".underscore.should eq "foo" } - it { "FooBar".underscore.should eq "foo_bar" } - it { "ABCde".underscore.should eq "ab_cde" } - it { "FOO_bar".underscore.should eq "foo_bar" } - it { "Char_S".underscore.should eq "char_s" } - it { "Char_".underscore.should eq "char_" } - it { "C_".underscore.should eq "c_" } - it { "HTTP".underscore.should eq "http" } - it { "HTTP_CLIENT".underscore.should eq "http_client" } - it { "CSS3".underscore.should eq "css3" } - it { "HTTP1.1".underscore.should eq "http1.1" } - it { "3.14IsPi".underscore.should eq "3.14_is_pi" } - it { "I2C".underscore.should eq "i2_c" } - - describe "with IO" do - it { String.build { |io| "Foo".underscore io }.should eq "foo" } - it { String.build { |io| "FooBar".underscore io }.should eq "foo_bar" } - it { String.build { |io| "ABCde".underscore io }.should eq "ab_cde" } - it { String.build { |io| "FOO_bar".underscore io }.should eq "foo_bar" } - it { String.build { |io| "Char_S".underscore io }.should eq "char_s" } - it { String.build { |io| "Char_".underscore io }.should eq "char_" } - it { String.build { |io| "C_".underscore io }.should eq "c_" } - it { String.build { |io| "HTTP".underscore io }.should eq "http" } - it { String.build { |io| "HTTP_CLIENT".underscore io }.should eq "http_client" } - it { String.build { |io| "CSS3".underscore io }.should eq "css3" } - it { String.build { |io| "HTTP1.1".underscore io }.should eq "http1.1" } - it { String.build { |io| "3.14IsPi".underscore io }.should eq "3.14_is_pi" } - it { String.build { |io| "I2C".underscore io }.should eq "i2_c" } + it { assert_prints "Foo".underscore, "foo" } + it { assert_prints "FooBar".underscore, "foo_bar" } + it { assert_prints "ABCde".underscore, "ab_cde" } + it { assert_prints "FOO_bar".underscore, "foo_bar" } + it { assert_prints "Char_S".underscore, "char_s" } + it { assert_prints "Char_".underscore, "char_" } + it { assert_prints "C_".underscore, "c_" } + it { assert_prints "HTTP".underscore, "http" } + it { assert_prints "HTTP_CLIENT".underscore, "http_client" } + it { assert_prints "CSS3".underscore, "css3" } + it { assert_prints "HTTP1.1".underscore, "http1.1" } + it { assert_prints "3.14IsPi".underscore, "3.14_is_pi" } + it { assert_prints "I2C".underscore, "i2_c" } + + it "handles multi-character mappings correctly" do + assert_prints "İxİİ0İİxİ0".underscore, "i̇x_i̇i̇0_i̇_i̇x_i̇0" end end diff --git a/src/string.cr b/src/string.cr index 56a1f7d986d7..8d5a65182f26 100644 --- a/src/string.cr +++ b/src/string.cr @@ -4232,18 +4232,18 @@ class String mem : Char? = nil each_char do |char| - digit = char.ascii_number? - - if options.none? + if options.ascii? + digit = char.ascii_number? downcase = digit || char.ascii_lowercase? upcase = char.ascii_uppercase? else + digit = char.number? downcase = digit || char.lowercase? upcase = char.uppercase? end if first - io << char.downcase(options) + char.downcase(options) { |c| io << c } elsif last_is_downcase && upcase if mem # This is the case of A1Bcd, we need to put 'mem' (not to need to convert as downcase @@ -4255,7 +4255,7 @@ class String # This is the case of AbcDe, we need to put an underscore before the 'D' # ^ io << '_' - io << char.downcase(options) + char.downcase(options) { |c| io << c } elsif (last_is_upcase || last_is_digit) && (upcase || digit) # This is the case of 1) A1Bcd, 2) A1BCd or 3) A1B_cd:if the next char is upcase (case 1) we need # ^ ^ ^ @@ -4265,7 +4265,7 @@ class String # 3) we need to append this char as downcase and then a single underscore if mem # case 2 - io << mem.downcase(options) + mem.downcase(options) { |c| io << c } end mem = char else @@ -4276,11 +4276,11 @@ class String # case 1 io << '_' end - io << mem.downcase(options) + mem.downcase(options) { |c| io << c } mem = nil end - io << char.downcase(options) + char.downcase(options) { |c| io << c } end last_is_downcase = downcase @@ -4289,7 +4289,7 @@ class String first = false end - io << mem.downcase(options) if mem + mem.downcase(options) { |c| io << c } if mem end # Converts underscores to camelcase boundaries. From 4c31746a4158dbfae1cd0626930987e2939c9771 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 6 Jun 2023 23:45:58 +0800 Subject: [PATCH 0569/1551] Distribute DLLs and import libraries on Windows (#13543) --- .github/workflows/win.yml | 230 +++++++++++++++++++++++++++++++++++--- 1 file changed, 215 insertions(+), 15 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index b2c7765334f4..b5b2652ccbff 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -7,20 +7,16 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: - x86_64-windows: + x86_64-windows-libs: runs-on: windows-2022 steps: - name: Disable CRLF line ending substitution run: | git config --global core.autocrlf false + - name: Enable Developer Command Prompt uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 - - name: Install Crystal - uses: crystal-lang/install-crystal@v1 - with: - crystal: "1.8.2" - - name: Download Crystal source uses: actions/checkout@v3 @@ -38,7 +34,7 @@ jobs: libs/mpir.lib libs/yaml.lib libs/xml2.lib - key: win-libs-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc-${{ env.VSCMD_VER }} + key: win-libs-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc - name: Build libgc if: steps.cache-libs.outputs.cache-hit != 'true' run: .\etc\win-ci\build-gc.ps1 -BuildTree deps\gc -Version 8.2.2 -AtomicOpsVersion 7.8.0 @@ -75,7 +71,7 @@ jobs: libs/crypto.lib libs/ssl.lib libs/openssl_VERSION - key: win-openssl-libs-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc-${{ env.VSCMD_VER }} + key: win-openssl-libs-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc - name: Set up NASM if: steps.cache-openssl.outputs.cache-hit != 'true' uses: ilammy/setup-nasm@321e6ed62a1fc77024a3bd853deb33645e8b22c4 # v1.4.0 @@ -83,12 +79,101 @@ jobs: if: steps.cache-openssl.outputs.cache-hit != 'true' run: .\etc\win-ci\build-openssl.ps1 -BuildTree deps\openssl -Version 3.1.0 + x86_64-windows-dlls: + runs-on: windows-2022 + steps: + - name: Disable CRLF line ending substitution + run: | + git config --global core.autocrlf false + + - name: Enable Developer Command Prompt + uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + + - name: Download Crystal source + uses: actions/checkout@v3 + + - name: Cache libraries + id: cache-dlls + uses: actions/cache@v3 + with: + path: | # openssl and llvm take much longer to build so they are cached separately + libs/pcre-dynamic.lib + libs/pcre2-8-dynamic.lib + libs/iconv-dynamic.lib + libs/gc-dynamic.lib + libs/ffi-dynamic.lib + libs/z-dynamic.lib + libs/mpir-dynamic.lib + libs/yaml-dynamic.lib + libs/xml2-dynamic.lib + dlls/pcre.dll + dlls/pcre2-8.dll + dlls/libiconv.dll + dlls/gc.dll + dlls/libffi.dll + dlls/zlib1.dll + dlls/mpir.dll + dlls/yaml.dll + dlls/libxml2.dll + key: win-dlls-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc + - name: Build libgc + if: steps.cache-dlls.outputs.cache-hit != 'true' + run: .\etc\win-ci\build-gc.ps1 -BuildTree deps\gc -Version 8.2.2 -AtomicOpsVersion 7.8.0 -Dynamic + - name: Build libpcre + if: steps.cache-dlls.outputs.cache-hit != 'true' + run: .\etc\win-ci\build-pcre.ps1 -BuildTree deps\pcre -Version 8.45 -Dynamic + - name: Build libpcre2 + if: steps.cache-dlls.outputs.cache-hit != 'true' + run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.42 -Dynamic + - name: Build libiconv + if: steps.cache-dlls.outputs.cache-hit != 'true' + run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Dynamic + - name: Build libffi + if: steps.cache-dlls.outputs.cache-hit != 'true' + run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.3 -Dynamic + - name: Build zlib + if: steps.cache-dlls.outputs.cache-hit != 'true' + run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.2.13 -Dynamic + - name: Build mpir + if: steps.cache-dlls.outputs.cache-hit != 'true' + run: .\etc\win-ci\build-mpir.ps1 -BuildTree deps\mpir -Dynamic + - name: Build libyaml + if: steps.cache-dlls.outputs.cache-hit != 'true' + run: .\etc\win-ci\build-yaml.ps1 -BuildTree deps\yaml -Version 0.2.5 -Dynamic + - name: Build libxml2 + if: steps.cache-dlls.outputs.cache-hit != 'true' + run: .\etc\win-ci\build-xml2.ps1 -BuildTree deps\xml2 -Version 2.11.3 -Dynamic + + - name: Cache OpenSSL + id: cache-openssl-dlls + uses: actions/cache@v3 + with: + path: | + libs/crypto-dynamic.lib + libs/ssl-dynamic.lib + dlls/libcrypto-3-x64.dll + dlls/libssl-3-x64.dll + key: win-openssl-dlls-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc + - name: Set up NASM + if: steps.cache-openssl-dlls.outputs.cache-hit != 'true' + uses: ilammy/setup-nasm@321e6ed62a1fc77024a3bd853deb33645e8b22c4 # v1.4.0 + - name: Build OpenSSL + if: steps.cache-openssl-dlls.outputs.cache-hit != 'true' + run: .\etc\win-ci\build-openssl.ps1 -BuildTree deps\openssl -Version 3.1.0 -Dynamic + + x86_64-windows-llvm: + runs-on: windows-2022 + steps: + - name: Enable Developer Command Prompt + uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + - name: Cache LLVM id: cache-llvm uses: actions/cache@v3 with: path: llvm - key: llvm-libs-16.0.3-msvc-${{ env.VSCMD_VER }} + key: llvm-libs-16.0.3-msvc + - name: Download LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' run: | @@ -97,6 +182,7 @@ jobs: 7z x llvm.tar.xz 7z x llvm.tar mv llvm-* llvm-src + - name: Download LLVM's CMake files if: steps.cache-llvm.outputs.cache-hit != 'true' run: | @@ -105,22 +191,102 @@ jobs: 7z x cmake.tar.xz 7z x cmake.tar mv cmake-* cmake + - name: Build LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' working-directory: ./llvm-src run: | cmake . -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DLLVM_USE_CRT_RELEASE=MT -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_ZSTD=OFF cmake --build . --config Release - - name: Gather LLVM - if: steps.cache-llvm.outputs.cache-hit != 'true' + cmake -DCMAKE_INSTALL_PREFIX=$(pwd)\llvm -P cmake_install.cmake + + x86_64-windows: + runs-on: windows-2022 + needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm] + steps: + - name: Disable CRLF line ending substitution run: | - mv llvm-src/Release llvm - mv llvm-src/include llvm/ + git config --global core.autocrlf false + + - name: Enable Developer Command Prompt + uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + + - name: Install Crystal + uses: crystal-lang/install-crystal@v1 + with: + crystal: "1.8.2" + + - name: Download Crystal source + uses: actions/checkout@v3 + + - name: Restore libraries + uses: actions/cache/restore@v3 + with: + path: | + libs/pcre.lib + libs/pcre2-8.lib + libs/iconv.lib + libs/gc.lib + libs/ffi.lib + libs/z.lib + libs/mpir.lib + libs/yaml.lib + libs/xml2.lib + key: win-libs-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore OpenSSL + uses: actions/cache/restore@v3 + with: + path: | + libs/crypto.lib + libs/ssl.lib + libs/openssl_VERSION + key: win-openssl-libs-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore DLLs + uses: actions/cache/restore@v3 + with: + path: | + libs/pcre-dynamic.lib + libs/pcre2-8-dynamic.lib + libs/iconv-dynamic.lib + libs/gc-dynamic.lib + libs/ffi-dynamic.lib + libs/z-dynamic.lib + libs/mpir-dynamic.lib + libs/yaml-dynamic.lib + libs/xml2-dynamic.lib + dlls/pcre.dll + dlls/pcre2-8.dll + dlls/libiconv.dll + dlls/gc.dll + dlls/libffi.dll + dlls/zlib1.dll + dlls/mpir.dll + dlls/yaml.dll + dlls/libxml2.dll + key: win-dlls-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore OpenSSL DLLs + uses: actions/cache/restore@v3 + with: + path: | + libs/crypto-dynamic.lib + libs/ssl-dynamic.lib + dlls/libcrypto-3-x64.dll + dlls/libssl-3-x64.dll + key: win-openssl-dlls-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore LLVM + uses: actions/cache/restore@v3 + with: + path: llvm + key: llvm-libs-16.0.3-msvc + fail-on-cache-miss: true - name: Set up environment run: | echo "CRYSTAL_LIBRARY_PATH=$(pwd)\libs" >> ${env:GITHUB_ENV} - echo "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\.build\crystal.exe" >> ${env:GITHUB_ENV} echo "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" >> ${env:GITHUB_ENV} - name: Build LLVM extensions @@ -156,6 +322,7 @@ jobs: cp .build/crystal.exe crystal/ cp shards/shards.exe crystal/ cp libs/* crystal/lib/ + cp dlls/* crystal/ cp src/* crystal/src -Recurse rm crystal/src/llvm/ext/llvm_ext.obj @@ -165,6 +332,39 @@ jobs: name: crystal path: crystal + x86_64-windows-test: + runs-on: windows-2022 + needs: [x86_64-windows] + steps: + - name: Disable CRLF line ending substitution + run: | + git config --global core.autocrlf false + + - name: Enable Developer Command Prompt + uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + + - name: Download Crystal source + uses: actions/checkout@v3 + + - name: Download Crystal executable + uses: actions/download-artifact@v3 + with: + name: crystal + path: build + + - name: Restore LLVM + uses: actions/cache/restore@v3 + with: + path: llvm + key: llvm-libs-16.0.3-msvc + fail-on-cache-miss: true + + - name: Set up environment + run: | + Add-Content $env:GITHUB_PATH "$(pwd)\build" + Add-Content $env:GITHUB_ENV "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\build\crystal.exe" + Add-Content $env:GITHUB_ENV "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" + - name: Run stdlib specs run: make -f Makefile.win std_spec @@ -172,7 +372,7 @@ jobs: run: make -f Makefile.win compiler_spec - name: Run primitives specs - run: make -f Makefile.win primitives_spec + run: make -f Makefile.win -o .build\crystal.exe primitives_spec # we know the compiler is fresh; do not rebuild it here - name: Build samples run: make -f Makefile.win samples From fa8203df9ac049798f6a84ebd41da5cf93373e78 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 6 Jun 2023 23:46:25 +0800 Subject: [PATCH 0570/1551] Do not attempt downcasing first when case-folding a `Char` (#13542) --- spec/std/char_spec.cr | 2 ++ spec/std/string_spec.cr | 35 ++++++++--------------- src/char.cr | 33 ++++++++++++++++++++-- src/unicode/unicode.cr | 61 ++++++++++++++++++++++++++++------------- 4 files changed, 87 insertions(+), 44 deletions(-) diff --git a/spec/std/char_spec.cr b/spec/std/char_spec.cr index d60133766bb4..d9e9ef8b4671 100644 --- a/spec/std/char_spec.cr +++ b/spec/std/char_spec.cr @@ -18,6 +18,8 @@ describe "Char" do actual.should eq(['s', 's']) end it { 'Ń'.downcase(Unicode::CaseOptions::Fold).should eq('ń') } + it { 'ꭰ'.downcase(Unicode::CaseOptions::Fold).should eq('Ꭰ') } + it { 'Ꭰ'.downcase(Unicode::CaseOptions::Fold).should eq('Ꭰ') } end it "#succ" do diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index ed86176771e8..e6ceba320ee9 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -635,33 +635,22 @@ describe "String" do end describe "#downcase" do - it { "HELLO!".downcase.should eq("hello!") } - it { "HELLO MAN!".downcase.should eq("hello man!") } - it { "ÁÉÍÓÚĀ".downcase.should eq("áéíóúā") } - it { "AEIİOU".downcase(Unicode::CaseOptions::Turkic).should eq("aeıiou") } - it { "ÁEÍOÚ".downcase(Unicode::CaseOptions::ASCII).should eq("ÁeÍoÚ") } - it { "İ".downcase.should eq("i̇") } - it { "Baffle".downcase(Unicode::CaseOptions::Fold).should eq("baffle") } - it { "ff".downcase(Unicode::CaseOptions::Fold).should eq("ff") } - it { "tschüß".downcase(Unicode::CaseOptions::Fold).should eq("tschüss") } - it { "ΣίσυφοςfiÆ".downcase(Unicode::CaseOptions::Fold).should eq("σίσυφοσfiæ") } + it { assert_prints "HELLO!".downcase, "hello!" } + it { assert_prints "HELLO MAN!".downcase, "hello man!" } + it { assert_prints "ÁÉÍÓÚĀ".downcase, "áéíóúā" } + it { assert_prints "AEIİOU".downcase(Unicode::CaseOptions::Turkic), "aeıiou" } + it { assert_prints "ÁEÍOÚ".downcase(Unicode::CaseOptions::ASCII), "ÁeÍoÚ" } + it { assert_prints "İ".downcase, "i̇" } + it { assert_prints "Baffle".downcase(Unicode::CaseOptions::Fold), "baffle" } + it { assert_prints "ff".downcase(Unicode::CaseOptions::Fold), "ff" } + it { assert_prints "tschüß".downcase(Unicode::CaseOptions::Fold), "tschüss" } + it { assert_prints "ΣίσυφοςfiÆ".downcase(Unicode::CaseOptions::Fold), "σίσυφοσfiæ" } + it { assert_prints "ꭰ".downcase(Unicode::CaseOptions::Fold), "Ꭰ" } + it { assert_prints "Ꭰ".downcase(Unicode::CaseOptions::Fold), "Ꭰ" } it "does not touch invalid code units in an otherwise ascii string" do "\xB5!\xE0\xC1\xB5?".downcase.should eq("\xB5!\xE0\xC1\xB5?") end - - describe "with IO" do - it { String.build { |io| "HELLO!".downcase io }.should eq "hello!" } - it { String.build { |io| "HELLO MAN!".downcase io }.should eq "hello man!" } - it { String.build { |io| "ÁÉÍÓÚĀ".downcase io }.should eq "áéíóúā" } - it { String.build { |io| "AEIİOU".downcase io, Unicode::CaseOptions::Turkic }.should eq "aeıiou" } - it { String.build { |io| "ÁEÍOÚ".downcase io, Unicode::CaseOptions::ASCII }.should eq "ÁeÍoÚ" } - it { String.build { |io| "İ".downcase io }.should eq "i̇" } - it { String.build { |io| "Baffle".downcase io, Unicode::CaseOptions::Fold }.should eq "baffle" } - it { String.build { |io| "ff".downcase io, Unicode::CaseOptions::Fold }.should eq "ff" } - it { String.build { |io| "tschüß".downcase io, Unicode::CaseOptions::Fold }.should eq "tschüss" } - it { String.build { |io| "ΣίσυφοςfiÆ".downcase io, Unicode::CaseOptions::Fold }.should eq "σίσυφοσfiæ" } - end end describe "#upcase" do diff --git a/src/char.cr b/src/char.cr index 9e7b791bad2a..2a6b912db118 100644 --- a/src/char.cr +++ b/src/char.cr @@ -400,8 +400,26 @@ struct Char # 'x'.downcase # => 'x' # '.'.downcase # => '.' # ``` + # + # If `options.fold?` is true, then returns the case-folded equivalent instead. + # Note that this will return `self` if a multiple-character case folding + # exists, even if a separate single-character transformation is also defined + # in Unicode. + # + # ``` + # 'Z'.downcase(Unicode::CaseOptions::Fold) # => 'z' + # 'x'.downcase(Unicode::CaseOptions::Fold) # => 'x' + # 'ς'.downcase(Unicode::CaseOptions::Fold) # => 'σ' + # 'ꭰ'.downcase(Unicode::CaseOptions::Fold) # => 'Ꭰ' + # 'ẞ'.downcase(Unicode::CaseOptions::Fold) # => 'ẞ' # not U+00DF 'ß' + # 'ᾈ'.downcase(Unicode::CaseOptions::Fold) # => "ᾈ" # not U+1F80 'ᾀ' + # ``` def downcase(options : Unicode::CaseOptions = :none) : Char - Unicode.downcase(self, options) + if options.fold? + Unicode.foldcase(self, options) + else + Unicode.downcase(self, options) + end end # Yields each char for the downcase equivalent of this char. @@ -409,8 +427,19 @@ struct Char # This method takes into account the possibility that an downcase # version of a char might result in multiple chars, like for # 'İ', which results in 'i' and a dot mark. + # + # ``` + # 'Z'.downcase { |v| puts v } # prints 'z' + # 'ς'.downcase(Unicode::CaseOptions::Fold) { |v| puts v } # prints 'σ' + # 'ẞ'.downcase(Unicode::CaseOptions::Fold) { |v| puts v } # prints 's', 's' + # 'ᾈ'.downcase(Unicode::CaseOptions::Fold) { |v| puts v } # prints 'ἀ', 'ι' + # ``` def downcase(options : Unicode::CaseOptions = :none, &) - Unicode.downcase(self, options) { |char| yield char } + if options.fold? + Unicode.foldcase(self, options) { |char| yield char } + else + Unicode.downcase(self, options) { |char| yield char } + end end # Returns the upcase equivalent of this char. diff --git a/src/unicode/unicode.cr b/src/unicode/unicode.cr index 3918b7a1efe6..491a702c68ed 100644 --- a/src/unicode/unicode.cr +++ b/src/unicode/unicode.cr @@ -20,6 +20,19 @@ module Unicode Turkic # Unicode case folding, which is more far-reaching than Unicode case mapping. + # + # Note that only full mappings are defined, and calling `Char#downcase` with + # this option will return its receiver unchanged if a multiple-character + # case folding exists, even if a separate single-character transformation is + # also defined in Unicode. + # + # ``` + # "ẞ".downcase(Unicode::CaseOptions::Fold) # => "ss" + # 'ẞ'.downcase(Unicode::CaseOptions::Fold) # => 'ẞ' # not U+00DF 'ß' + # + # "ᾈ".downcase(Unicode::CaseOptions::Fold) # => "ἀι" + # 'ᾈ'.downcase(Unicode::CaseOptions::Fold) # => "ᾈ" # not U+1F80 'ᾀ' + # ``` Fold end @@ -224,9 +237,6 @@ module Unicode result = check_downcase_turkic(char, options) return result if result - results = check_downcase_fold(char, options) - return results[0].unsafe_chr if results && results.size == 1 - check_downcase_ranges(char) end @@ -244,12 +254,6 @@ module Unicode return end - result = check_downcase_fold(char, options) - if result - result.each { |c| yield c.unsafe_chr if c != 0 } - return - end - result = special_cases_downcase[char.ord]? if result result.each { |c| yield c.unsafe_chr if c != 0 } @@ -283,16 +287,6 @@ module Unicode end end - private def self.check_downcase_fold(char, options) - if options.fold? - result = search_ranges(casefold_ranges, char.ord) - return {char.ord + result} if result - - return fold_cases[char.ord]? - end - nil - end - private def self.check_downcase_ranges(char) result = search_ranges(downcase_ranges, char.ord) return char + result if result @@ -303,6 +297,35 @@ module Unicode char end + # :nodoc: + def self.foldcase(char : Char, options : CaseOptions) : Char + results = check_foldcase(char, options) + return results[0].unsafe_chr if results && results.size == 1 + + char + end + + # :nodoc: + def self.foldcase(char : Char, options : CaseOptions, &) + result = check_foldcase(char, options) + if result + result.each { |c| yield c.unsafe_chr if c != 0 } + return + end + + yield char + end + + private def self.check_foldcase(char, options) + if options.fold? + result = search_ranges(casefold_ranges, char.ord) + return {char.ord + result} if result + + return fold_cases[char.ord]? + end + nil + end + # :nodoc: def self.lowercase?(char : Char) : Bool in_category?(char.ord, category_Ll) From 66844f02b638cb135e4628ad10258f097ffdfb58 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 6 Jun 2023 23:46:35 +0800 Subject: [PATCH 0571/1551] Handle case folding in `String#compare` correctly (#13532) --- scripts/generate_unicode_data.cr | 2 +- scripts/unicode_data.ecr | 8 +- spec/std/string_spec.cr | 8 +- src/crystal/small_deque.cr | 53 ++++++++ src/string.cr | 58 +++++++-- src/unicode/data.cr | 216 +++++++++++++++---------------- 6 files changed, 217 insertions(+), 128 deletions(-) create mode 100644 src/crystal/small_deque.cr diff --git a/scripts/generate_unicode_data.cr b/scripts/generate_unicode_data.cr index 4337841cd292..f360f27b76e6 100644 --- a/scripts/generate_unicode_data.cr +++ b/scripts/generate_unicode_data.cr @@ -171,7 +171,7 @@ body.each_line do |line| casefold = nil end if casefold - while casefold.size < 4 + while casefold.size < 3 casefold << 0 end special_cases_casefold << SpecialCase.new(codepoint, casefold) diff --git a/scripts/unicode_data.ecr b/scripts/unicode_data.ecr index e48559408926..499a2fb42836 100644 --- a/scripts/unicode_data.ecr +++ b/scripts/unicode_data.ecr @@ -88,10 +88,10 @@ module Unicode end # Fold case transformation that involve mapping a codepoint - # to multiple codepoints. The maximum transformation is always 4 - # codepoints, so we store them all as 4 codepoints and 0 means end. - private class_getter fold_cases : Hash(Int32, {Int32, Int32, Int32, Int32}) do - data = Hash(Int32, {Int32, Int32, Int32, Int32}).new(initial_capacity: <%= special_cases_casefold.size %>) + # to multiple codepoints. The maximum transformation is always 3 + # codepoints, so we store them all as 3 codepoints and 0 means end. + private class_getter fold_cases : Hash(Int32, {Int32, Int32, Int32}) do + data = Hash(Int32, {Int32, Int32, Int32}).new(initial_capacity: <%= special_cases_casefold.size %>) <%- special_cases_casefold.each do |a_case| -%> put(data, <%= a_case.codepoint %>, <%= a_case.value.join(", ") %>) <%- end -%> diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index e6ceba320ee9..0fea08e02f43 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2652,7 +2652,7 @@ describe "String" do end end - describe "compare" do + describe "#compare" do it "compares case-sensitive" do "fo".compare("foo").should eq(-1) "foo".compare("fo").should eq(1) @@ -2685,6 +2685,12 @@ describe "String" do "abcA".compare("abca", case_insensitive: true).should eq(0) end + it "compares case-insensitive, multiple chars after case conversion (#4513)" do + "ffl".compare("ffl", case_insensitive: true, options: Unicode::CaseOptions::Fold).should eq(0) + "FFL".compare("ffl", case_insensitive: true, options: Unicode::CaseOptions::Fold).should eq(0) + "sß".compare("ßs", case_insensitive: true, options: Unicode::CaseOptions::Fold).should eq(0) + end + it "treats invalid code units as replacement char in an otherwise ascii string" do "\xC0".compare("\xE0", case_insensitive: true).should eq(0) "\xE0".compare("\xC0", case_insensitive: true).should eq(0) diff --git a/src/crystal/small_deque.cr b/src/crystal/small_deque.cr new file mode 100644 index 000000000000..c303f80120d6 --- /dev/null +++ b/src/crystal/small_deque.cr @@ -0,0 +1,53 @@ +module Crystal + # :nodoc: + # An internal deque type whose storage is backed by a `StaticArray`. The deque + # capacity is fixed to N and storing more than N elements is an error. Only a + # subset of `::Deque`'s functionality is defined as needed. + struct SmallDeque(T, N) + include Indexable::Mutable(T) + + @start = 0 + @buffer = uninitialized T[N] + getter size : Int32 = 0 + + def unsafe_fetch(index : Int) : T + index_to_ptr(index).value + end + + def unsafe_put(index : Int, value : T) + index_to_ptr(index).value = value + end + + def <<(value : T) + check_capacity_for_insert + unsafe_put(@size, value) + @size += 1 + self + end + + def shift(&) + if @size == 0 + yield + else + ptr = index_to_ptr(0) + value = ptr.value + ptr.clear + @size &-= 1 + @start &+= 1 + @start &-= N if @start >= N + value + end + end + + # precondition: 0 <= index <= N + private def index_to_ptr(index) + index &+= @start + index &-= N if index >= N + @buffer.to_unsafe + index + end + + private def check_capacity_for_insert + raise "Out of capacity" if @size >= N + end + end +end diff --git a/src/string.cr b/src/string.cr index 8d5a65182f26..c22307216088 100644 --- a/src/string.cr +++ b/src/string.cr @@ -1,5 +1,6 @@ require "c/stdlib" require "c/string" +require "crystal/small_deque" {% unless flag?(:without_iconv) %} require "crystal/iconv" {% end %} @@ -3116,6 +3117,7 @@ class String # "abcdef".compare("ABCDEG", case_insensitive: true) # => -1 # # "heIIo".compare("heııo", case_insensitive: true, options: Unicode::CaseOptions::Turkic) # => 0 + # "Baffle".compare("baffle", case_insensitive: true, options: Unicode::CaseOptions::Fold) # => 0 # ``` # # Case-sensitive only comparison is provided by the comparison operator `#<=>`. @@ -3153,23 +3155,51 @@ class String else reader1 = Char::Reader.new(self) reader2 = Char::Reader.new(other) - char1 = reader1.current_char - char2 = reader2.current_char - while reader1.has_next? && reader2.has_next? - comparison = char1.downcase(options) <=> char2.downcase(options) - return comparison.sign unless comparison == 0 + # 3 chars maximum for case folding; 2 held in temporary buffers + chars1 = Crystal::SmallDeque(Char, 2).new + chars2 = Crystal::SmallDeque(Char, 2).new + + while true + lhs = chars1.shift do + next unless reader1.has_next? + lhs_ = nil + reader1.current_char.downcase(options) do |char| + if lhs_ + chars1 << char + else + lhs_ = char + end + end + reader1.next_char + lhs_ + end - char1 = reader1.next_char - char2 = reader2.next_char - end + rhs = chars2.shift do + next unless reader2.has_next? + rhs_ = nil + reader2.current_char.downcase(options) do |char| + if rhs_ + chars2 << char + else + rhs_ = char + end + end + reader2.next_char + rhs_ + end - if reader1.has_next? - 1 - elsif reader2.has_next? - -1 - else - 0 + case {lhs, rhs} + in {Nil, Nil} + return 0 + in {Nil, Char} + return -1 + in {Char, Nil} + return 1 + in {Char, Char} + comparison = lhs <=> rhs + return comparison.sign unless comparison == 0 + end end end end diff --git a/src/unicode/data.cr b/src/unicode/data.cr index 537fefbe8fbf..5fe4b621c8a5 100644 --- a/src/unicode/data.cr +++ b/src/unicode/data.cr @@ -2757,114 +2757,114 @@ module Unicode end # Fold case transformation that involve mapping a codepoint - # to multiple codepoints. The maximum transformation is always 4 - # codepoints, so we store them all as 4 codepoints and 0 means end. - private class_getter fold_cases : Hash(Int32, {Int32, Int32, Int32, Int32}) do - data = Hash(Int32, {Int32, Int32, Int32, Int32}).new(initial_capacity: 104) - put(data, 223, 115, 115, 0, 0) - put(data, 304, 105, 775, 0, 0) - put(data, 329, 700, 110, 0, 0) - put(data, 496, 106, 780, 0, 0) - put(data, 912, 953, 776, 769, 0) - put(data, 944, 965, 776, 769, 0) - put(data, 1415, 1381, 1410, 0, 0) - put(data, 7830, 104, 817, 0, 0) - put(data, 7831, 116, 776, 0, 0) - put(data, 7832, 119, 778, 0, 0) - put(data, 7833, 121, 778, 0, 0) - put(data, 7834, 97, 702, 0, 0) - put(data, 7838, 115, 115, 0, 0) - put(data, 8016, 965, 787, 0, 0) - put(data, 8018, 965, 787, 768, 0) - put(data, 8020, 965, 787, 769, 0) - put(data, 8022, 965, 787, 834, 0) - put(data, 8064, 7936, 953, 0, 0) - put(data, 8065, 7937, 953, 0, 0) - put(data, 8066, 7938, 953, 0, 0) - put(data, 8067, 7939, 953, 0, 0) - put(data, 8068, 7940, 953, 0, 0) - put(data, 8069, 7941, 953, 0, 0) - put(data, 8070, 7942, 953, 0, 0) - put(data, 8071, 7943, 953, 0, 0) - put(data, 8072, 7936, 953, 0, 0) - put(data, 8073, 7937, 953, 0, 0) - put(data, 8074, 7938, 953, 0, 0) - put(data, 8075, 7939, 953, 0, 0) - put(data, 8076, 7940, 953, 0, 0) - put(data, 8077, 7941, 953, 0, 0) - put(data, 8078, 7942, 953, 0, 0) - put(data, 8079, 7943, 953, 0, 0) - put(data, 8080, 7968, 953, 0, 0) - put(data, 8081, 7969, 953, 0, 0) - put(data, 8082, 7970, 953, 0, 0) - put(data, 8083, 7971, 953, 0, 0) - put(data, 8084, 7972, 953, 0, 0) - put(data, 8085, 7973, 953, 0, 0) - put(data, 8086, 7974, 953, 0, 0) - put(data, 8087, 7975, 953, 0, 0) - put(data, 8088, 7968, 953, 0, 0) - put(data, 8089, 7969, 953, 0, 0) - put(data, 8090, 7970, 953, 0, 0) - put(data, 8091, 7971, 953, 0, 0) - put(data, 8092, 7972, 953, 0, 0) - put(data, 8093, 7973, 953, 0, 0) - put(data, 8094, 7974, 953, 0, 0) - put(data, 8095, 7975, 953, 0, 0) - put(data, 8096, 8032, 953, 0, 0) - put(data, 8097, 8033, 953, 0, 0) - put(data, 8098, 8034, 953, 0, 0) - put(data, 8099, 8035, 953, 0, 0) - put(data, 8100, 8036, 953, 0, 0) - put(data, 8101, 8037, 953, 0, 0) - put(data, 8102, 8038, 953, 0, 0) - put(data, 8103, 8039, 953, 0, 0) - put(data, 8104, 8032, 953, 0, 0) - put(data, 8105, 8033, 953, 0, 0) - put(data, 8106, 8034, 953, 0, 0) - put(data, 8107, 8035, 953, 0, 0) - put(data, 8108, 8036, 953, 0, 0) - put(data, 8109, 8037, 953, 0, 0) - put(data, 8110, 8038, 953, 0, 0) - put(data, 8111, 8039, 953, 0, 0) - put(data, 8114, 8048, 953, 0, 0) - put(data, 8115, 945, 953, 0, 0) - put(data, 8116, 940, 953, 0, 0) - put(data, 8118, 945, 834, 0, 0) - put(data, 8119, 945, 834, 953, 0) - put(data, 8124, 945, 953, 0, 0) - put(data, 8130, 8052, 953, 0, 0) - put(data, 8131, 951, 953, 0, 0) - put(data, 8132, 942, 953, 0, 0) - put(data, 8134, 951, 834, 0, 0) - put(data, 8135, 951, 834, 953, 0) - put(data, 8140, 951, 953, 0, 0) - put(data, 8146, 953, 776, 768, 0) - put(data, 8147, 953, 776, 769, 0) - put(data, 8150, 953, 834, 0, 0) - put(data, 8151, 953, 776, 834, 0) - put(data, 8162, 965, 776, 768, 0) - put(data, 8163, 965, 776, 769, 0) - put(data, 8164, 961, 787, 0, 0) - put(data, 8166, 965, 834, 0, 0) - put(data, 8167, 965, 776, 834, 0) - put(data, 8178, 8060, 953, 0, 0) - put(data, 8179, 969, 953, 0, 0) - put(data, 8180, 974, 953, 0, 0) - put(data, 8182, 969, 834, 0, 0) - put(data, 8183, 969, 834, 953, 0) - put(data, 8188, 969, 953, 0, 0) - put(data, 64256, 102, 102, 0, 0) - put(data, 64257, 102, 105, 0, 0) - put(data, 64258, 102, 108, 0, 0) - put(data, 64259, 102, 102, 105, 0) - put(data, 64260, 102, 102, 108, 0) - put(data, 64261, 115, 116, 0, 0) - put(data, 64262, 115, 116, 0, 0) - put(data, 64275, 1396, 1398, 0, 0) - put(data, 64276, 1396, 1381, 0, 0) - put(data, 64277, 1396, 1387, 0, 0) - put(data, 64278, 1406, 1398, 0, 0) - put(data, 64279, 1396, 1389, 0, 0) + # to multiple codepoints. The maximum transformation is always 3 + # codepoints, so we store them all as 3 codepoints and 0 means end. + private class_getter fold_cases : Hash(Int32, {Int32, Int32, Int32}) do + data = Hash(Int32, {Int32, Int32, Int32}).new(initial_capacity: 104) + put(data, 223, 115, 115, 0) + put(data, 304, 105, 775, 0) + put(data, 329, 700, 110, 0) + put(data, 496, 106, 780, 0) + put(data, 912, 953, 776, 769) + put(data, 944, 965, 776, 769) + put(data, 1415, 1381, 1410, 0) + put(data, 7830, 104, 817, 0) + put(data, 7831, 116, 776, 0) + put(data, 7832, 119, 778, 0) + put(data, 7833, 121, 778, 0) + put(data, 7834, 97, 702, 0) + put(data, 7838, 115, 115, 0) + put(data, 8016, 965, 787, 0) + put(data, 8018, 965, 787, 768) + put(data, 8020, 965, 787, 769) + put(data, 8022, 965, 787, 834) + put(data, 8064, 7936, 953, 0) + put(data, 8065, 7937, 953, 0) + put(data, 8066, 7938, 953, 0) + put(data, 8067, 7939, 953, 0) + put(data, 8068, 7940, 953, 0) + put(data, 8069, 7941, 953, 0) + put(data, 8070, 7942, 953, 0) + put(data, 8071, 7943, 953, 0) + put(data, 8072, 7936, 953, 0) + put(data, 8073, 7937, 953, 0) + put(data, 8074, 7938, 953, 0) + put(data, 8075, 7939, 953, 0) + put(data, 8076, 7940, 953, 0) + put(data, 8077, 7941, 953, 0) + put(data, 8078, 7942, 953, 0) + put(data, 8079, 7943, 953, 0) + put(data, 8080, 7968, 953, 0) + put(data, 8081, 7969, 953, 0) + put(data, 8082, 7970, 953, 0) + put(data, 8083, 7971, 953, 0) + put(data, 8084, 7972, 953, 0) + put(data, 8085, 7973, 953, 0) + put(data, 8086, 7974, 953, 0) + put(data, 8087, 7975, 953, 0) + put(data, 8088, 7968, 953, 0) + put(data, 8089, 7969, 953, 0) + put(data, 8090, 7970, 953, 0) + put(data, 8091, 7971, 953, 0) + put(data, 8092, 7972, 953, 0) + put(data, 8093, 7973, 953, 0) + put(data, 8094, 7974, 953, 0) + put(data, 8095, 7975, 953, 0) + put(data, 8096, 8032, 953, 0) + put(data, 8097, 8033, 953, 0) + put(data, 8098, 8034, 953, 0) + put(data, 8099, 8035, 953, 0) + put(data, 8100, 8036, 953, 0) + put(data, 8101, 8037, 953, 0) + put(data, 8102, 8038, 953, 0) + put(data, 8103, 8039, 953, 0) + put(data, 8104, 8032, 953, 0) + put(data, 8105, 8033, 953, 0) + put(data, 8106, 8034, 953, 0) + put(data, 8107, 8035, 953, 0) + put(data, 8108, 8036, 953, 0) + put(data, 8109, 8037, 953, 0) + put(data, 8110, 8038, 953, 0) + put(data, 8111, 8039, 953, 0) + put(data, 8114, 8048, 953, 0) + put(data, 8115, 945, 953, 0) + put(data, 8116, 940, 953, 0) + put(data, 8118, 945, 834, 0) + put(data, 8119, 945, 834, 953) + put(data, 8124, 945, 953, 0) + put(data, 8130, 8052, 953, 0) + put(data, 8131, 951, 953, 0) + put(data, 8132, 942, 953, 0) + put(data, 8134, 951, 834, 0) + put(data, 8135, 951, 834, 953) + put(data, 8140, 951, 953, 0) + put(data, 8146, 953, 776, 768) + put(data, 8147, 953, 776, 769) + put(data, 8150, 953, 834, 0) + put(data, 8151, 953, 776, 834) + put(data, 8162, 965, 776, 768) + put(data, 8163, 965, 776, 769) + put(data, 8164, 961, 787, 0) + put(data, 8166, 965, 834, 0) + put(data, 8167, 965, 776, 834) + put(data, 8178, 8060, 953, 0) + put(data, 8179, 969, 953, 0) + put(data, 8180, 974, 953, 0) + put(data, 8182, 969, 834, 0) + put(data, 8183, 969, 834, 953) + put(data, 8188, 969, 953, 0) + put(data, 64256, 102, 102, 0) + put(data, 64257, 102, 105, 0) + put(data, 64258, 102, 108, 0) + put(data, 64259, 102, 102, 105) + put(data, 64260, 102, 102, 108) + put(data, 64261, 115, 116, 0) + put(data, 64262, 115, 116, 0) + put(data, 64275, 1396, 1398, 0) + put(data, 64276, 1396, 1381, 0) + put(data, 64277, 1396, 1387, 0) + put(data, 64278, 1406, 1398, 0) + put(data, 64279, 1396, 1389, 0) data end From 9d86367baee3d773f664c6b4bb6b8748ee89e7be Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 7 Jun 2023 15:27:32 +0800 Subject: [PATCH 0572/1551] Don't use symbols in `Crystal::Lexer#check_macro_opening_keyword` (#13277) --- src/compiler/crystal/syntax/lexer.cr | 77 ++++++++++++---------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 7bafb7ddae04..33f991c5fc0f 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -1925,10 +1925,10 @@ module Crystal char = current_char whitespace = true beginning_of_line = false - elsif !delimiter_state && whitespace && (keyword = lookahead { check_macro_opening_keyword(beginning_of_line) }) + elsif !delimiter_state && whitespace && (keyword = lookahead { macro_starts_with_keyword?(beginning_of_line) }) char = current_char - nest += 1 unless keyword == :abstract_def + nest += 1 unless keyword.abstract_def? whitespace = true beginning_of_line = false next @@ -2006,7 +2006,12 @@ module Crystal end end - def check_macro_opening_keyword(beginning_of_line) + enum MacroKeywordState + AbstractDef + Other + end + + def macro_starts_with_keyword?(beginning_of_line) : MacroKeywordState? case char = current_char when 'a' case next_char @@ -2014,79 +2019,65 @@ module Crystal if char_sequence?('s', 't', 'r', 'a', 'c', 't') && next_char.whitespace? case next_char when 'd' - char_sequence?('e', 'f') && peek_not_ident_part_or_end_next_char && :abstract_def + MacroKeywordState::AbstractDef if char_sequence?('e', 'f') && peek_not_ident_part_or_end_next_char when 'c' - char_sequence?('l', 'a', 's', 's') && peek_not_ident_part_or_end_next_char && :abstract_class + MacroKeywordState::Other if char_sequence?('l', 'a', 's', 's') && peek_not_ident_part_or_end_next_char when 's' - char_sequence?('t', 'r', 'u', 'c', 't') && peek_not_ident_part_or_end_next_char && :abstract_struct - else - false + MacroKeywordState::Other if char_sequence?('t', 'r', 'u', 'c', 't') && peek_not_ident_part_or_end_next_char end end when 'n' - char_sequence?('n', 'o', 't', 'a', 't', 'i', 'o', 'n') && peek_not_ident_part_or_end_next_char && :annotation - else - false + MacroKeywordState::Other if char_sequence?('n', 'o', 't', 'a', 't', 'i', 'o', 'n') && peek_not_ident_part_or_end_next_char end when 'b' - char_sequence?('e', 'g', 'i', 'n') && peek_not_ident_part_or_end_next_char && :begin + MacroKeywordState::Other if char_sequence?('e', 'g', 'i', 'n') && peek_not_ident_part_or_end_next_char when 'c' case next_char when 'a' - char_sequence?('s', 'e') && peek_not_ident_part_or_end_next_char && :case + MacroKeywordState::Other if char_sequence?('s', 'e') && peek_not_ident_part_or_end_next_char when 'l' - char_sequence?('a', 's', 's') && peek_not_ident_part_or_end_next_char && :class - else - false + MacroKeywordState::Other if char_sequence?('a', 's', 's') && peek_not_ident_part_or_end_next_char end when 'd' case next_char when 'o' - peek_not_ident_part_or_end_next_char && :do + MacroKeywordState::Other if peek_not_ident_part_or_end_next_char when 'e' - next_char == 'f' && peek_not_ident_part_or_end_next_char && :def - else - false + MacroKeywordState::Other if next_char == 'f' && peek_not_ident_part_or_end_next_char end when 'f' - char_sequence?('u', 'n') && peek_not_ident_part_or_end_next_char && :fun + MacroKeywordState::Other if char_sequence?('u', 'n') && peek_not_ident_part_or_end_next_char when 'i' - beginning_of_line && next_char == 'f' && peek_not_ident_part_or_end_next_char && :if + MacroKeywordState::Other if beginning_of_line && next_char == 'f' && peek_not_ident_part_or_end_next_char when 'l' - char_sequence?('i', 'b') && peek_not_ident_part_or_end_next_char && :lib + MacroKeywordState::Other if char_sequence?('i', 'b') && peek_not_ident_part_or_end_next_char when 'm' case next_char when 'a' - char_sequence?('c', 'r', 'o') && peek_not_ident_part_or_end_next_char && :macro + MacroKeywordState::Other if char_sequence?('c', 'r', 'o') && peek_not_ident_part_or_end_next_char when 'o' - char_sequence?('d', 'u', 'l', 'e') && peek_not_ident_part_or_end_next_char && :module - else - false + MacroKeywordState::Other if char_sequence?('d', 'u', 'l', 'e') && peek_not_ident_part_or_end_next_char end when 's' case next_char when 'e' - char_sequence?('l', 'e', 'c', 't') && !ident_part_or_end?(peek_next_char) && next_char && :select + MacroKeywordState::Other if char_sequence?('l', 'e', 'c', 't') && !ident_part_or_end?(peek_next_char) && next_char when 't' - char_sequence?('r', 'u', 'c', 't') && !ident_part_or_end?(peek_next_char) && next_char && :struct - else - false + MacroKeywordState::Other if char_sequence?('r', 'u', 'c', 't') && !ident_part_or_end?(peek_next_char) && next_char end when 'u' - next_char == 'n' && case next_char - when 'i' - char_sequence?('o', 'n') && peek_not_ident_part_or_end_next_char && :union - when 'l' - beginning_of_line && char_sequence?('e', 's', 's') && peek_not_ident_part_or_end_next_char && :unless - when 't' - beginning_of_line && char_sequence?('i', 'l') && peek_not_ident_part_or_end_next_char && :until - else - false + if next_char == 'n' + case next_char + when 'i' + MacroKeywordState::Other if char_sequence?('o', 'n') && peek_not_ident_part_or_end_next_char + when 'l' + MacroKeywordState::Other if beginning_of_line && char_sequence?('e', 's', 's') && peek_not_ident_part_or_end_next_char + when 't' + MacroKeywordState::Other if beginning_of_line && char_sequence?('i', 'l') && peek_not_ident_part_or_end_next_char + end end when 'w' - beginning_of_line && char_sequence?('h', 'i', 'l', 'e') && peek_not_ident_part_or_end_next_char && :while - else - false + MacroKeywordState::Other if beginning_of_line && char_sequence?('h', 'i', 'l', 'e') && peek_not_ident_part_or_end_next_char end end From 9055de010fcce6cf845ab4f0f1f84114f1236765 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 7 Jun 2023 15:27:47 +0800 Subject: [PATCH 0573/1551] Fix `Atomic#max` and `#min` for signed enums (#13524) --- spec/std/atomic_spec.cr | 21 +++++++++++++++++++++ src/atomic.cr | 16 ++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/spec/std/atomic_spec.cr b/spec/std/atomic_spec.cr index 35031cb91d9f..3b473f8ff3d7 100644 --- a/spec/std/atomic_spec.cr +++ b/spec/std/atomic_spec.cr @@ -4,6 +4,7 @@ enum AtomicEnum One Two Three + Minus = -1 end @[Flags] @@ -126,6 +127,16 @@ describe Atomic do atomic.get.should eq(UInt32::MAX) end + it "#max with signed enum" do + atomic = Atomic.new(AtomicEnum::Two) + atomic.max(AtomicEnum::One).should eq(AtomicEnum::Two) + atomic.get.should eq(AtomicEnum::Two) + atomic.max(AtomicEnum::Three).should eq(AtomicEnum::Two) + atomic.get.should eq(AtomicEnum::Three) + atomic.max(AtomicEnum::Minus).should eq(AtomicEnum::Three) + atomic.get.should eq(AtomicEnum::Three) + end + it "#min with signed" do atomic = Atomic.new(5) atomic.min(10).should eq(5) @@ -142,6 +153,16 @@ describe Atomic do atomic.get.should eq(10_u32) end + it "#min with signed enum" do + atomic = Atomic.new(AtomicEnum::Two) + atomic.min(AtomicEnum::Three).should eq(AtomicEnum::Two) + atomic.get.should eq(AtomicEnum::Two) + atomic.min(AtomicEnum::One).should eq(AtomicEnum::Two) + atomic.get.should eq(AtomicEnum::One) + atomic.min(AtomicEnum::Minus).should eq(AtomicEnum::One) + atomic.get.should eq(AtomicEnum::Minus) + end + it "#set" do atomic = Atomic.new(1) atomic.set(2).should eq(2) diff --git a/src/atomic.cr b/src/atomic.cr index 3e14c1fba4ad..d182c04db368 100644 --- a/src/atomic.cr +++ b/src/atomic.cr @@ -125,7 +125,13 @@ struct Atomic(T) # atomic.get # => 10 # ``` def max(value : T) - {% if T < Int::Signed %} + {% if T < Enum %} + if @value.value.is_a?(Int::Signed) + Ops.atomicrmw(:max, pointerof(@value), value, :sequentially_consistent, false) + else + Ops.atomicrmw(:umax, pointerof(@value), value, :sequentially_consistent, false) + end + {% elsif T < Int::Signed %} Ops.atomicrmw(:max, pointerof(@value), value, :sequentially_consistent, false) {% else %} Ops.atomicrmw(:umax, pointerof(@value), value, :sequentially_consistent, false) @@ -144,7 +150,13 @@ struct Atomic(T) # atomic.get # => 3 # ``` def min(value : T) - {% if T < Int::Signed %} + {% if T < Enum %} + if @value.value.is_a?(Int::Signed) + Ops.atomicrmw(:min, pointerof(@value), value, :sequentially_consistent, false) + else + Ops.atomicrmw(:umin, pointerof(@value), value, :sequentially_consistent, false) + end + {% elsif T < Int::Signed %} Ops.atomicrmw(:min, pointerof(@value), value, :sequentially_consistent, false) {% else %} Ops.atomicrmw(:umin, pointerof(@value), value, :sequentially_consistent, false) From 15cd7480b34cbaab094026089a725bc7a2898592 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 Jun 2023 09:28:08 +0200 Subject: [PATCH 0574/1551] Update cachix/install-nix-action action to v21 (#13531) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 0e2523b05cc4..6d838395d4b8 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -17,7 +17,7 @@ jobs: - name: Download Crystal source uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v20 + - uses: cachix/install-nix-action@v21 with: install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | From 2f38e16b22cf98250cf280e2a3d3cd29f3e89cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 14 Jun 2023 15:11:06 +0200 Subject: [PATCH 0575/1551] Fix return type of `Iterator#chunk` and `Enumerable#chunks` without `Drop` (#13506) --- spec/std/enumerable_spec.cr | 8 ++++--- src/enumerable.cr | 42 +++++++++++++++++++++++-------------- src/iterator.cr | 24 +++++++++------------ 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 41ba27e5e1f8..435a056191e3 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -177,6 +177,7 @@ describe "Enumerable" do it "drop all" do result = [1, 2].chunk { Enumerable::Chunk::Drop }.to_a + result.should be_a(Array(Tuple(NoReturn, Array(Int32)))) result.size.should eq 0 end @@ -192,14 +193,14 @@ describe "Enumerable" do it "reuses true" do iter = [1, 1, 2, 3, 3].chunk(reuse: true, &.itself) - a = iter.next.as(Tuple) + a = iter.next.should be_a(Tuple(Int32, Array(Int32))) a.should eq({1, [1, 1]}) - b = iter.next.as(Tuple) + b = iter.next.should be_a(Tuple(Int32, Array(Int32))) b.should eq({2, [2]}) b[1].should be(a[1]) - c = iter.next.as(Tuple) + c = iter.next.should be_a(Tuple(Int32, Array(Int32))) c.should eq({3, [3, 3]}) c[1].should be(a[1]) end @@ -239,6 +240,7 @@ describe "Enumerable" do it "drop all" do result = [1, 2].chunks { Enumerable::Chunk::Drop } + result.should be_a(Array(Tuple(NoReturn, Array(Int32)))) result.size.should eq 0 end diff --git a/src/enumerable.cr b/src/enumerable.cr index d71d93ea48c7..d7f978bd088d 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -131,8 +131,8 @@ module Enumerable(T) # # See also: `Iterator#chunk`. def chunks(&block : T -> U) forall U - res = [] of Tuple(U, Array(T)) - chunks_internal(block) { |k, v| res << {k, v} } + res = [] of Tuple(typeof(Chunk.key_type(self, block)), Array(T)) + chunks_internal(block) { |*kv| res << kv } res end @@ -166,7 +166,6 @@ module Enumerable(T) end def init(key, val) - return if key == Drop @key = key if @reuse @@ -190,33 +189,44 @@ module Enumerable(T) def same_as?(key) : Bool return false unless @initialized - return false if key.in?(Alone, Drop) + return false if key.is_a?(Alone.class) || key.is_a?(Drop.class) @key == key end - def reset - @initialized = false - @data.clear + def acc(key, val, &) + if same_as?(key) + add(val) + else + if tuple = fetch + yield *tuple + end + + init(key, val) unless key.is_a?(Drop.class) + end end end + + def self.key_type(ary, block) + ary.each do |item| + key = block.call(item) + ::raise "" if key.is_a?(Drop.class) + return key + end + ::raise "" + end end private def chunks_internal(original_block : T -> U, &) forall U - acc = Chunk::Accumulator(T, U).new + acc = Chunk::Accumulator(T, typeof(Chunk.key_type(self, original_block))).new each do |val| key = original_block.call(val) - if acc.same_as?(key) - acc.add(val) - else - if tuple = acc.fetch - yield(*tuple) - end - acc.init(key, val) + acc.acc(key, val) do |*tuple| + yield *tuple end end if tuple = acc.fetch - yield(*tuple) + yield *tuple end end diff --git a/src/iterator.cr b/src/iterator.cr index 22b215440065..1d7d54cdcb58 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -1455,21 +1455,22 @@ module Iterator(T) # # See also: `Enumerable#chunks`. def chunk(reuse = false, &block : T -> U) forall T, U - ChunkIterator(typeof(self), T, U).new(self, reuse, &block) + ChunkIterator(typeof(self), T, U, typeof(::Enumerable::Chunk.key_type(self, block))).new(self, reuse, &block) end - private class ChunkIterator(I, T, U) - include Iterator(Tuple(U, Array(T))) + private class ChunkIterator(I, T, U, V) + include Iterator(Tuple(V, Array(T))) @iterator : I - @init : {U, T}? + @init : {V, T}? def initialize(@iterator : Iterator(T), reuse, &@original_block : T -> U) - @acc = Enumerable::Chunk::Accumulator(T, U).new(reuse) + @acc = ::Enumerable::Chunk::Accumulator(T, V).new(reuse) end def next if init = @init - @acc.init(*init) + k, v = init + @acc.init(k, v) @init = nil end @@ -1481,10 +1482,10 @@ module Iterator(T) else tuple = @acc.fetch if tuple - @init = {key, val} + @init = {key, val} unless key.is_a?(::Enumerable::Chunk::Drop.class) return tuple else - @acc.init(key, val) + @acc.init(key, val) unless key.is_a?(::Enumerable::Chunk::Drop.class) end end end @@ -1492,13 +1493,8 @@ module Iterator(T) if tuple = @acc.fetch return tuple end - stop - end - private def init_state - @init = nil - @acc.reset - self + stop end end From 601aa1ffd3bdded7f6e3d1007a9e5b0454674d31 Mon Sep 17 00:00:00 2001 From: Will Richardson Date: Wed, 14 Jun 2023 23:11:54 +1000 Subject: [PATCH 0576/1551] Fix handling of quoted boolean values in `YAML::Any` (#13546) --- spec/std/yaml/any_spec.cr | 20 ++++++++++++++++++++ src/yaml/any.cr | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/spec/std/yaml/any_spec.cr b/spec/std/yaml/any_spec.cr index 33c8dc458db4..ffab8f14babc 100644 --- a/spec/std/yaml/any_spec.cr +++ b/spec/std/yaml/any_spec.cr @@ -236,6 +236,26 @@ describe YAML::Any do YAML.parse("*foo") end end + + it "gets yes/no unquoted booleans" do + YAML.parse("yes").as_bool.should be_true + YAML.parse("no").as_bool.should be_false + YAML.parse("'yes'").as_bool?.should be_nil + YAML.parse("'no'").as_bool?.should be_nil + YAML::Any.from_yaml("yes").as_bool.should be_true + YAML::Any.from_yaml("no").as_bool.should be_false + YAML::Any.from_yaml("'yes'").as_bool?.should be_nil + YAML::Any.from_yaml("'no'").as_bool?.should be_nil + end + + it "doesn't get quoted numbers" do + YAML.parse("1").as_i64.should eq(1) + YAML.parse("'1'").as_i64?.should be_nil + YAML.parse("'1'").as_s?.should eq("1") + YAML::Any.from_yaml("1").as_i64.should eq(1) + YAML::Any.from_yaml("'1'").as_i64?.should be_nil + YAML::Any.from_yaml("'1'").as_s?.should eq("1") + end end describe "#size" do diff --git a/src/yaml/any.cr b/src/yaml/any.cr index 8acd9fa057df..1518623efd5f 100644 --- a/src/yaml/any.cr +++ b/src/yaml/any.cr @@ -31,7 +31,7 @@ struct YAML::Any def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) case node when YAML::Nodes::Scalar - new YAML::Schema::Core.parse_scalar(node.value) + new YAML::Schema::Core.parse_scalar(node) when YAML::Nodes::Sequence ary = [] of YAML::Any From 71a35361978ca5834d62913210d97c3e0bc91e73 Mon Sep 17 00:00:00 2001 From: Alexandre ZANNI <16578570+noraj@users.noreply.github.com> Date: Wed, 14 Jun 2023 15:12:13 +0200 Subject: [PATCH 0577/1551] Docs: Add note about graphemes in `String#reverse` (#13536) Co-authored-by: Quinton Miller --- src/string.cr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/string.cr b/src/string.cr index c22307216088..47d589e3fca7 100644 --- a/src/string.cr +++ b/src/string.cr @@ -4371,6 +4371,12 @@ class String # "Argentina".reverse # => "anitnegrA" # "racecar".reverse # => "racecar" # ``` + # + # Works on Unicode graphemes (and not codepoints) so combining characters are preserved. + # + # ``` + # "Noe\u0308l".reverse # => "lëoN" + # ``` def reverse : String return self if bytesize <= 1 From 9930425f7745e1dca52ed23341f3f77d942dc8b1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 15 Jun 2023 02:04:20 +0800 Subject: [PATCH 0578/1551] Fix timeout events getting lost on Windows (#13525) --- src/crystal/system/event_loop.cr | 2 +- src/crystal/system/unix/event_libevent.cr | 4 +++- src/crystal/system/wasi/event_loop.cr | 4 +++- src/crystal/system/win32/event_loop_iocp.cr | 4 +++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index cb567d34ff48..b9efa4dc09c3 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -11,7 +11,7 @@ abstract class Crystal::EventLoop # Creates a timeout_event. abstract def create_timeout_event(fiber : Fiber) : Event - abstract struct Event + module Event # Frees the event. abstract def free : Nil diff --git a/src/crystal/system/unix/event_libevent.cr b/src/crystal/system/unix/event_libevent.cr index 62fbff94f555..ee414a142053 100644 --- a/src/crystal/system/unix/event_libevent.cr +++ b/src/crystal/system/unix/event_libevent.cr @@ -5,7 +5,9 @@ require "./lib_event2" {% end %} # :nodoc: -struct Crystal::LibEvent::Event < Crystal::EventLoop::Event +struct Crystal::LibEvent::Event + include Crystal::EventLoop::Event + VERSION = String.new(LibEvent2.event_get_version) def self.callback(&block : Int32, LibEvent2::EventFlags, Void* ->) diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr index b49206b792d2..0f411d80709f 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/system/wasi/event_loop.cr @@ -33,7 +33,9 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop end end -struct Crystal::Wasi::Event < Crystal::EventLoop::Event +struct Crystal::Wasi::Event + include Crystal::EventLoop::Event + def add(timeout : Time::Span?) : Nil end diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index f617571f381c..f7636a869af9 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -88,7 +88,9 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop end end -struct Crystal::Iocp::Event < Crystal::EventLoop::Event +class Crystal::Iocp::Event + include Crystal::EventLoop::Event + getter fiber getter wake_at getter? timeout From 97e9b3dfa2c6fe0325c837633a6189be99e79eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 14 Jun 2023 20:04:34 +0200 Subject: [PATCH 0579/1551] Add parameters for `Regex::MatchOptions` to matching methods (#13353) --- spec/std/string_spec.cr | 9 +++ src/csv.cr | 16 +++--- src/log/spec.cr | 8 +-- src/string.cr | 120 +++++++++++++++++++++------------------- src/string_scanner.cr | 24 ++++---- 5 files changed, 95 insertions(+), 82 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 0fea08e02f43..f5ac06188dc6 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2461,6 +2461,15 @@ describe "String" do expect_raises(Regex::Error, "Match not found") { "foo".match! /Crystal/ } expect_raises(NilAssertionError) { $~ } end + + context "with options" do + it "Regex::Match options" do + expect_raises(Regex::Error, "Match not found") do + ".foo".match!(/foo/, options: :anchored) + end + "foo".match!(/foo/, options: :anchored) + end + end end it "does %" do diff --git a/src/csv.cr b/src/csv.cr index d01da95ff138..0ad9a053dcfa 100644 --- a/src/csv.cr +++ b/src/csv.cr @@ -265,15 +265,15 @@ class CSV # Returns the current row's value corresponding to the given *header_pattern*. # Raises `KeyError` if no such header exists. # Raises `CSV::Error` if headers were not requested. - def [](header_pattern : Regex) : String - row_internal[header_pattern] + def [](header_pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String + row_internal[header_pattern, options: options] end # Returns the current row's value corresponding to the given *header_pattern*. # Returns `nil` if no such header exists. # Raises `CSV::Error` if headers were not requested. - def []?(header_pattern : Regex) : String? - row_internal[header_pattern]? + def []?(header_pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String? + row_internal[header_pattern, options: options]? end # Returns a tuple of the current row's values at given indices @@ -387,8 +387,8 @@ class CSV # Returns this row's value corresponding to the given *header_pattern*. # Raises `KeyError` if no such header exists. # Raises `CSV::Error` if headers were not requested. - def [](header_pattern : Regex) : String - value = self.[]?(header_pattern) + def [](header_pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String + value = self.[]?(header_pattern, options: options) raise KeyError.new("Missing header pattern: #{header_pattern}") unless value value end @@ -396,9 +396,9 @@ class CSV # Returns this row's value corresponding to the given *header_pattern*. # Returns `nil` if no such header exists. # Raises `CSV::Error` if headers were not requested. - def []?(header_pattern : Regex) : String? + def []?(header_pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String? csv.headers.each_with_index do |header, i| - if header =~ header_pattern + if header.matches?(header_pattern, options: options) return maybe_strip(@row[i]? || "") end end diff --git a/src/log/spec.cr b/src/log/spec.cr index f2fdfafb2aab..09c0717de522 100644 --- a/src/log/spec.cr +++ b/src/log/spec.cr @@ -102,8 +102,8 @@ class Log end # :ditto: - def check(level : Severity, pattern : Regex, file = __FILE__, line = __LINE__) : self - self.check("#{level} matching #{pattern.inspect}", file, line) { |e| e.severity == level && e.message.matches?(pattern) } + def check(level : Severity, pattern : Regex, file = __FILE__, line = __LINE__, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : self + self.check("#{level} matching #{pattern.inspect}", file, line) { |e| e.severity == level && e.message.matches?(pattern, options: options) } end # :nodoc: @@ -127,8 +127,8 @@ class Log end # :ditto: - def next(level : Severity, pattern : Regex, file = __FILE__, line = __LINE__) : self - self.next("#{level} matching #{pattern.inspect}", file, line) { |e| e.severity == level && e.message.matches?(pattern) } + def next(level : Severity, pattern : Regex, file = __FILE__, line = __LINE__, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : self + self.next("#{level} matching #{pattern.inspect}", file, line) { |e| e.severity == level && e.message.matches?(pattern, options: options) } end # Clears the emitted entries so far diff --git a/src/string.cr b/src/string.cr index 47d589e3fca7..f899ecf2512f 100644 --- a/src/string.cr +++ b/src/string.cr @@ -934,12 +934,12 @@ class String includes?(str) ? str : nil end - def []?(regex : Regex) : String? - self[regex, 0]? + def []?(regex : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String? + self[regex, 0, options: options]? end - def []?(regex : Regex, group) : String? - match = match(regex) + def []?(regex : Regex, group, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String? + match = match(regex, options: options) match[group]? if match end @@ -953,12 +953,12 @@ class String self[str]?.not_nil! end - def [](regex : Regex) : String - self[regex]?.not_nil! + def [](regex : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String + self[regex, options: options]?.not_nil! end - def [](regex : Regex, group) : String - self[regex, group]?.not_nil! + def [](regex : Regex, group, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String + self[regex, group, options: options]?.not_nil! end # Returns the `Char` at the given *index*. @@ -2356,8 +2356,8 @@ class String # ``` # "hello".sub(/./) { |s| s[0].ord.to_s + ' ' } # => "104 ello" # ``` - def sub(pattern : Regex, &) : String - sub_append(pattern) do |str, match, buffer| + def sub(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None, &) : String + sub_append(pattern, options) do |str, match, buffer| $~ = match buffer << yield str, match end @@ -2401,11 +2401,11 @@ class String # # Raises `IndexError` if a named group referenced in *replacement* is not present # in *pattern*. - def sub(pattern : Regex, replacement, backreferences = true) : String + def sub(pattern : Regex, replacement, backreferences = true, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String if backreferences && replacement.is_a?(String) && replacement.has_back_references? - sub_append(pattern) { |_, match, buffer| scan_backreferences(replacement, match, buffer) } + sub_append(pattern, options) { |_, match, buffer| scan_backreferences(replacement, match, buffer) } else - sub(pattern) { replacement } + sub(pattern, options: options) { replacement } end end @@ -2417,8 +2417,10 @@ class String # "hello".sub(/(he|l|o)/, {"he": "ha", "l": "la"}) # => "hallo" # "hello".sub(/(he|l|o)/, {"l": "la"}) # => "hello" # ``` - def sub(pattern : Regex, hash : Hash(String, _) | NamedTuple) : String - sub(pattern) do |match| + def sub(pattern : Regex, hash : Hash(String, _) | NamedTuple, options : Regex::MatchOptions = Regex::MatchOptions::None) : String + # FIXME: The options parameter should be a named-only parameter, but that breaks overload ordering (fixed with -Dpreview_overload_ordering). + + sub(pattern, options: options) do |match| if hash.has_key?(match) hash[match] else @@ -2484,8 +2486,8 @@ class String end end - private def sub_append(pattern : Regex, &) - match = pattern.match(self) + private def sub_append(pattern : Regex, options : Regex::MatchOptions, &) + match = pattern.match(self, options: options) return self unless match String.build(bytesize) do |buffer| @@ -2713,8 +2715,8 @@ class String # ``` # "hello".gsub(/./) { |s| s[0].ord.to_s + ' ' } # => "104 101 108 108 111 " # ``` - def gsub(pattern : Regex, &) : String - gsub_append(pattern) do |string, match, buffer| + def gsub(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None, &) : String + gsub_append(pattern, options) do |string, match, buffer| $~ = match buffer << yield string, match end @@ -2758,11 +2760,11 @@ class String # # Raises `IndexError` if a named group referenced in *replacement* is not present # in *pattern*. - def gsub(pattern : Regex, replacement, backreferences = true) : String + def gsub(pattern : Regex, replacement, backreferences = true, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String if backreferences && replacement.is_a?(String) && replacement.has_back_references? - gsub_append(pattern) { |_, match, buffer| scan_backreferences(replacement, match, buffer) } + gsub_append(pattern, options) { |_, match, buffer| scan_backreferences(replacement, match, buffer) } else - gsub(pattern) { replacement } + gsub(pattern, options: options) { replacement } end end @@ -2776,8 +2778,10 @@ class String # # but "o" is not and so is not included # "hello".gsub(/(he|l|o)/, {"he": "ha", "l": "la"}) # => "halala" # ``` - def gsub(pattern : Regex, hash : Hash(String, _) | NamedTuple) : String - gsub(pattern) do |match| + def gsub(pattern : Regex, hash : Hash(String, _) | NamedTuple, options : Regex::MatchOptions = Regex::MatchOptions::None) : String + # FIXME: The options parameter should be a named-only parameter, but that breaks overload ordering (fixed with -Dpreview_overload_ordering). + + gsub(pattern, options: options) do |match| hash[match]? end end @@ -2856,9 +2860,9 @@ class String end end - private def gsub_append(pattern : Regex, &) + private def gsub_append(pattern : Regex, options : Regex::MatchOptions, &) byte_offset = 0 - match = pattern.match_at_byte_index(self, byte_offset) + match = pattern.match_at_byte_index(self, byte_offset, options: options) return self unless match last_byte_offset = 0 @@ -2881,7 +2885,7 @@ class String last_byte_offset = byte_offset end - match = pattern.match_at_byte_index(self, byte_offset, Regex::MatchOptions::NO_UTF_CHECK) + match = pattern.match_at_byte_index(self, byte_offset, options: options | Regex::MatchOptions::NO_UTF_CHECK) end if last_byte_offset < bytesize @@ -3216,14 +3220,14 @@ class String # # "Haystack" =~ 45 # => nil # ``` - def =~(regex : Regex) : Int32? - match = regex.match(self) + def =~(regex : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32? + match = regex.match(self, options: options) $~ = match match.try &.begin(0) end # :ditto: - def =~(other) : Nil + def =~(other, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Nil nil end @@ -3402,11 +3406,11 @@ class String end # :ditto: - def index(search : Regex, offset = 0) : Int32? + def index(search : Regex, offset = 0, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32? offset += size if offset < 0 return nil unless 0 <= offset <= size - self.match(search, offset).try &.begin + self.match(search, offset, options: options).try &.begin end # :ditto: @@ -3509,12 +3513,12 @@ class String end # :ditto: - def rindex(search : Regex, offset = size) : Int32? + def rindex(search : Regex, offset = size, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32? offset += size if offset < 0 return nil unless 0 <= offset <= size match_result = nil - scan(search) do |match_data| + scan(search, options: options) do |match_data| break if (index = match_data.begin) && index > offset match_result = match_data end @@ -3525,8 +3529,8 @@ class String # :ditto: # # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`. - def rindex!(search : Regex, offset = size) : Int32 - rindex(search, offset) || raise Enumerable::NotFoundError.new + def rindex!(search : Regex, offset = size, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32 + rindex(search, offset, options: options) || raise Enumerable::NotFoundError.new end # :ditto: @@ -3565,9 +3569,9 @@ class String end # :ditto: - def partition(search : Regex) : Tuple(String, String, String) + def partition(search : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Tuple(String, String, String) pre = mid = post = "" - case m = self.match(search) + case m = self.match(search, options: options) when .nil? pre = self else @@ -3608,12 +3612,12 @@ class String end # :ditto: - def rpartition(search : Regex) : Tuple(String, String, String) + def rpartition(search : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Tuple(String, String, String) match_result = nil pos = self.size - 1 while pos >= 0 - self[pos..-1].scan(search) do |m| + self[pos..-1].scan(search, options: options) do |m| match_result = m end break unless match_result.nil? @@ -4089,9 +4093,9 @@ class String # long_river_name.split(/s+/) # => ["Mi", "i", "ippi"] # long_river_name.split(//) # => ["M", "i", "s", "s", "i", "s", "s", "i", "p", "p", "i"] # ``` - def split(separator : Regex, limit = nil, *, remove_empty = false) : Array(String) + def split(separator : Regex, limit = nil, *, remove_empty = false, options : Regex::MatchOptions = Regex::MatchOptions::None) : Array(String) ary = Array(String).new - split(separator, limit, remove_empty: remove_empty) do |string| + split(separator, limit, remove_empty: remove_empty, options: options) do |string| ary << string end ary @@ -4117,7 +4121,7 @@ class String # long_river_name.split(//) { |s| ary << s } # ary # => ["M", "i", "s", "s", "i", "s", "s", "i", "p", "p", "i"] # ``` - def split(separator : Regex, limit = nil, *, remove_empty = false, &block : String -> _) + def split(separator : Regex, limit = nil, *, remove_empty = false, options : Regex::MatchOptions = Regex::MatchOptions::None, &block : String -> _) if empty? yield "" unless remove_empty return @@ -4139,7 +4143,7 @@ class String match_offset = slice_offset = 0 options = Regex::MatchOptions::None - while match = separator.match_at_byte_index(self, match_offset, options) + while match = separator.match_at_byte_index(self, match_offset, options: options) index = match.byte_begin(0) match_bytesize = match.byte_end(0) - index next_offset = index + match_bytesize @@ -4603,8 +4607,8 @@ class String # "foo".match(/bar/) # => nil # $~ # raises Exception # ``` - def match(regex : Regex, pos = 0) : Regex::MatchData? - $~ = regex.match self, pos + def match(regex : Regex, pos = 0, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Regex::MatchData? + $~ = regex.match self, pos, options: options end # Finds matches of *regex* starting at *pos* and updates `$~` to the result. @@ -4615,8 +4619,8 @@ class String # $~ # => Regex::MatchData("foo") # # "foo".match!(/bar/) # => raises Exception - def match!(regex : Regex, pos = 0) : Regex::MatchData - $~ = regex.match! self, pos + def match!(regex : Regex, pos = 0, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Regex::MatchData + $~ = regex.match! self, pos, options: options end # Finds match of *regex* like `#match`, but it returns `Bool` value. @@ -4629,17 +4633,17 @@ class String # # `$~` is not set even if last match succeeds. # $~ # raises Exception # ``` - def matches?(regex : Regex, pos = 0) : Bool - regex.matches? self, pos + def matches?(regex : Regex, pos = 0, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Bool + regex.matches? self, pos, options: options end # Searches the string for instances of *pattern*, # yielding a `Regex::MatchData` for each match. - def scan(pattern : Regex, &) : self + def scan(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None, &) : self byte_offset = 0 options = Regex::MatchOptions::None - while match = pattern.match_at_byte_index(self, byte_offset, options) + while match = pattern.match_at_byte_index(self, byte_offset, options: options) index = match.byte_begin(0) $~ = match yield match @@ -4654,9 +4658,9 @@ class String # Searches the string for instances of *pattern*, # returning an `Array` of `Regex::MatchData` for each match. - def scan(pattern : Regex) : Array(Regex::MatchData) + def scan(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Array(Regex::MatchData) matches = [] of Regex::MatchData - scan(pattern) do |match| + scan(pattern, options: options) do |match| matches << match end matches @@ -5076,8 +5080,8 @@ class String # "h22".starts_with?(/[a-z]{2}/) # => false # "hh22".starts_with?(/[a-z]{2}/) # => true # ``` - def starts_with?(re : Regex) : Bool - !!($~ = re.match_at_byte_index(self, 0, Regex::MatchOptions::ANCHORED)) + def starts_with?(re : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Bool + !!($~ = re.match_at_byte_index(self, 0, options: options | Regex::MatchOptions::ANCHORED)) end # Returns `true` if this string ends with the given *str*. @@ -5126,8 +5130,8 @@ class String # "22h".ends_with?(/[a-z]{2}/) # => false # "22hh".ends_with?(/[a-z]{2}/) # => true # ``` - def ends_with?(re : Regex) : Bool - !!($~ = /#{re}\z/.match(self)) + def ends_with?(re : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Bool + !!($~ = /#{re}\z/.match(self, options: options)) end # Interpolates *other* into the string using top-level `::sprintf`. diff --git a/src/string_scanner.cr b/src/string_scanner.cr index c67631762fde..a18a45f35cfc 100644 --- a/src/string_scanner.cr +++ b/src/string_scanner.cr @@ -91,8 +91,8 @@ class StringScanner # s.scan(/\s\w+/) # => " string" # s.scan(/.*/) # => "" # ``` - def scan(pattern) : String? - match(pattern, advance: true, options: Regex::MatchOptions::ANCHORED) + def scan(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String? + match(pattern, advance: true, options: options | Regex::MatchOptions::ANCHORED) end # Scans the string _until_ the *pattern* is matched. Returns the substring up @@ -107,8 +107,8 @@ class StringScanner # s.scan_until(/tr/) # => nil # s.scan_until(/g/) # => "ing" # ``` - def scan_until(pattern) : String? - match(pattern, advance: true, options: Regex::MatchOptions::None) + def scan_until(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String? + match(pattern, advance: true, options: options) end private def match(pattern, advance = true, options = Regex::MatchOptions::ANCHORED) @@ -134,8 +134,8 @@ class StringScanner # # This method is the same as `#scan`, but without returning the matched # string. - def skip(pattern) : Int32? - match = scan(pattern) + def skip(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32? + match = scan(pattern, options: options) match.size if match end @@ -150,8 +150,8 @@ class StringScanner # # This method is the same as `#scan_until`, but without returning the matched # string. - def skip_until(pattern) : Int32? - match = scan_until(pattern) + def skip_until(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32? + match = scan_until(pattern, options: options) match.size if match end @@ -166,8 +166,8 @@ class StringScanner # s.check(/\w+/) # => "is" # s.check(/\w+/) # => "is" # ``` - def check(pattern) : String? - match(pattern, advance: false, options: Regex::MatchOptions::ANCHORED) + def check(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String? + match(pattern, advance: false, options: options | Regex::MatchOptions::ANCHORED) end # Returns the value that `#scan_until` would return, without advancing the @@ -180,8 +180,8 @@ class StringScanner # s.check_until(/tr/) # => "test str" # s.check_until(/g/) # => "test string" # ``` - def check_until(pattern) : String? - match(pattern, advance: false, options: Regex::MatchOptions::None) + def check_until(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String? + match(pattern, advance: false, options: options) end # Returns the *n*-th subgroup in the most recent match. From f836b3a40e7ad175038d34ce2243b54257d13eb8 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 15 Jun 2023 19:00:46 +0800 Subject: [PATCH 0580/1551] Optimize `BigRational#<=>(Int)` (#13555) --- src/big/big_rational.cr | 20 ++++++++++++++++++-- src/big/lib_gmp.cr | 10 +++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index a91263b1c05a..6ff0284502ea 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -97,8 +97,24 @@ struct BigRational < Number self <=> other.to_big_r end - def <=>(other : Int) - LibGMP.mpq_cmp(mpq, other.to_big_r) + def <=>(other : Int::Primitive) + if LibGMP::SI::MIN <= other <= LibGMP::UI::MAX + if other <= LibGMP::SI::MAX + LibGMP.mpq_cmp_si(self, LibGMP::SI.new!(other), 1) + else + LibGMP.mpq_cmp_ui(self, LibGMP::UI.new!(other), 1) + end + else + self <=> other.to_big_i + end + end + + def <=>(other : BigInt) + LibGMP.mpq_cmp_z(self, other) + end + + def ==(other : BigRational) : Bool + LibGMP.mpq_equal(self, other) != 0 end def +(other : BigRational) : BigRational diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index c610ca04d081..c502cc5a47d7 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -172,9 +172,6 @@ lib LibGMP fun mpq_set_d = __gmpq_set_d(rop : MPQ*, op : Double) fun mpq_set_f = __gmpq_set_f(rop : MPQ*, op : MPF*) - # # Compare - fun mpq_cmp = __gmpq_cmp(x : MPQ*, o : MPQ*) : Int32 - # # Arithmetic fun mpq_add = __gmpq_add(rop : MPQ*, op1 : MPQ*, op2 : MPQ*) fun mpq_sub = __gmpq_sub(rop : MPQ*, op1 : MPQ*, op2 : MPQ*) @@ -187,6 +184,13 @@ lib LibGMP fun mpq_div_2exp = __gmpq_div_2exp(q : MPQ*, n : MPQ*, b : BitcntT) fun mpq_mul_2exp = __gmpq_mul_2exp(rop : MPQ*, op1 : MPQ*, op2 : BitcntT) + # # Compare + fun mpq_cmp = __gmpq_cmp(op1 : MPQ*, op2 : MPQ*) : Int + fun mpq_cmp_z = __gmpq_cmp_z(op1 : MPQ*, op2 : MPZ*) : Int + fun mpq_cmp_ui = __gmpq_cmp_ui(op1 : MPQ*, num2 : UI, den2 : UI) : Int + fun mpq_cmp_si = __gmpq_cmp_si(op1 : MPQ*, num2 : SI, den2 : SI) : Int + fun mpq_equal = __gmpq_equal(op1 : MPQ*, op2 : MPQ*) : Int + # MPF struct MPF _mp_prec : Int From 1a852f116fd3142c6bd9844850337d39b80c46ca Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 15 Jun 2023 19:01:05 +0800 Subject: [PATCH 0581/1551] Map IANA time zone identifiers to Windows time zones (#13517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- scripts/generate_windows_zone_names.cr | 60 +- scripts/windows_zone_names.ecr | 39 ++ spec/std/time/location_spec.cr | 9 + src/crystal/system/time.cr | 4 + src/crystal/system/unix/time.cr | 4 + src/crystal/system/win32/time.cr | 44 +- src/crystal/system/win32/zone_names.cr | 767 ++++++++++++++++++++----- src/time/location.cr | 9 + 8 files changed, 748 insertions(+), 188 deletions(-) create mode 100644 scripts/windows_zone_names.ecr diff --git a/scripts/generate_windows_zone_names.cr b/scripts/generate_windows_zone_names.cr index 6ab301033cf3..570e009778ee 100644 --- a/scripts/generate_windows_zone_names.cr +++ b/scripts/generate_windows_zone_names.cr @@ -7,63 +7,45 @@ require "http/client" require "xml" require "../src/compiler/crystal/formatter" +require "ecr" -WINDOWS_ZONE_NAMES_SOURCE = "https://raw.githubusercontent.com/unicode-org/cldr/main/common/supplemental/windowsZones.xml" +WINDOWS_ZONE_NAMES_SOURCE = "https://raw.githubusercontent.com/unicode-org/cldr/817409270794bb1538fe6b4aa3e9c79aff2c34d2/common/supplemental/windowsZones.xml" TARGET_FILE = File.join(__DIR__, "..", "src", "crystal", "system", "win32", "zone_names.cr") response = HTTP::Client.get(WINDOWS_ZONE_NAMES_SOURCE) xml = XML.parse(response.body) -nodes = xml.xpath_nodes("/supplementalData/windowsZones/mapTimezones/mapZone[@territory=001]") +nodes = xml.xpath_nodes("/supplementalData/windowsZones/mapTimezones/mapZone") +entries = nodes.flat_map do |node| + windows_name = node["other"] + territory = node["territory"] + node["type"].split(' ', remove_empty: true).map do |tzdata_name| + {tzdata_name, territory, windows_name} + end +end.sort! + +iana_to_windows_items = entries.map do |tzdata_name, territory, windows_name| + {tzdata_name, windows_name} +end.uniq! -entries = nodes.compact_map do |node| - location = Time::Location.load(node["type"]) +windows_zone_names_items = entries.compact_map do |tzdata_name, territory, windows_name| + next unless territory == "001" + location = Time::Location.load(tzdata_name) next unless location time = Time.local(location).at_beginning_of_year zone1 = time.zone zone2 = (time + 6.months).zone + # southern hemisphere if zone1.offset > zone2.offset - # southern hemisphere - zones = {zone2.name, zone1.name} - else - # northern hemisphere - zones = {zone1.name, zone2.name} + zone1, zone2 = zone2, zone1 end - {key: node["other"], zones: zones, tzdata_name: location.name} + {windows_name, zone1.name, zone2.name, location.name} rescue err : Time::Location::InvalidLocationNameError pp err nil end -# sort by IANA database identifier -entries.sort_by! &.[:tzdata_name] - -hash_items = String.build do |io| - entries.join(io, '\n') do |entry| - entry[:key].inspect(io) - io << " => " - entry[:zones].inspect(io) - io << ", # " << entry[:tzdata_name] - end -end - -source = <<-CRYSTAL - # This file was automatically generated by running: - # - # scripts/generate_windows_zone_names.cr - # - # DO NOT EDIT - - module Crystal::System::Time - # These mappings for windows time zone names are based on - # #{WINDOWS_ZONE_NAMES_SOURCE} - WINDOWS_ZONE_NAMES = { - #{hash_items} - } - end - CRYSTAL - +source = ECR.render "#{__DIR__}/windows_zone_names.ecr" source = Crystal.format(source) - File.write(TARGET_FILE, source) diff --git a/scripts/windows_zone_names.ecr b/scripts/windows_zone_names.ecr new file mode 100644 index 000000000000..e427583a1a9b --- /dev/null +++ b/scripts/windows_zone_names.ecr @@ -0,0 +1,39 @@ +# This file was automatically generated by running: +# +# scripts/generate_windows_zone_names.cr +# +# DO NOT EDIT + +module Crystal::System::Time + # These mappings from IANA to Windows time zone names are based on + # <%= WINDOWS_ZONE_NAMES_SOURCE %> + private class_getter iana_to_windows : Hash(String, String) do + data = Hash(String, String).new(initial_capacity: <%= iana_to_windows_items.size %>) + <%- iana_to_windows_items.each do |tzdata_name, windows_name| -%> + put(data, <%= tzdata_name.inspect %>, <%= windows_name.inspect %>) + <%- end -%> + data + end + + # These mappings from Windows time zone names to tzdata abbreviations are based on + # <%= WINDOWS_ZONE_NAMES_SOURCE %> + private class_getter windows_zone_names : Hash(String, {String, String}) do + data = Hash(String, {String, String}).new(initial_capacity: <%= windows_zone_names_items.size %>) + <%- windows_zone_names_items.each do |windows_name, zone1, zone2, tzdata_name| -%> + put(data, <%= windows_name.inspect %>, <%= zone1.inspect %>, <%= zone2.inspect %>) # <%= tzdata_name %> + <%- end -%> + data + end + + # TODO: this is needed to avoid generating lots of allocas + # in LLVM, which makes LLVM really slow. The compiler should + # try to avoid/reuse temporary allocas. + # Explanation: https://github.com/crystal-lang/crystal/issues/4516#issuecomment-306226171 + private def self.put(hash : Hash, key, value) : Nil + hash[key] = value + end + + private def self.put(hash : Hash, key, *values) : Nil + hash[key] = values + end +end diff --git a/spec/std/time/location_spec.cr b/spec/std/time/location_spec.cr index ea52847383db..d92b67950d7a 100644 --- a/spec/std/time/location_spec.cr +++ b/spec/std/time/location_spec.cr @@ -33,6 +33,15 @@ class Time::Location end end + {% if flag?(:win32) %} + it "maps IANA timezone identifier to Windows name (#13166)" do + location = Location.load("Europe/Berlin") + location.name.should eq "Europe/Berlin" + location.utc?.should be_false + location.fixed?.should be_false + end + {% end %} + it "invalid timezone identifier" do with_zoneinfo(datapath("zoneinfo")) do expect_raises(InvalidLocationNameError, "Foobar/Baz") do diff --git a/src/crystal/system/time.cr b/src/crystal/system/time.cr index c61074437ab4..0e127c5f7879 100644 --- a/src/crystal/system/time.cr +++ b/src/crystal/system/time.cr @@ -8,6 +8,10 @@ module Crystal::System::Time # Returns a list of paths where time zone data should be looked up. # def self.zone_sources : Enumerable(String) + # Loads a time zone by its IANA zone identifier directly. May return `nil` on + # systems where tzdata is assumed to be available. + # def self.load_iana_zone(iana_name : String) : ::Time::Location? + # Returns the system's current local time zone # def self.load_localtime : ::Time::Location? end diff --git a/src/crystal/system/unix/time.cr b/src/crystal/system/unix/time.cr index 333b66075b43..c2dc9976fd79 100644 --- a/src/crystal/system/unix/time.cr +++ b/src/crystal/system/unix/time.cr @@ -53,6 +53,10 @@ module Crystal::System::Time ZONE_SOURCES end + def self.load_iana_zone(iana_name : String) : ::Time::Location? + nil + end + def self.load_localtime : ::Time::Location? if ::File.file?(LOCALTIME) && ::File.readable?(LOCALTIME) ::File.open(LOCALTIME) do |file| diff --git a/src/crystal/system/win32/time.cr b/src/crystal/system/win32/time.cr index c884ff61a511..78c7d60cae1d 100644 --- a/src/crystal/system/win32/time.cr +++ b/src/crystal/system/win32/time.cr @@ -72,7 +72,7 @@ module Crystal::System::Time def self.load_localtime : ::Time::Location? if LibC.GetTimeZoneInformation(out info) != LibC::TIME_ZONE_ID_UNKNOWN - initialize_location_from_TZI(info) + initialize_location_from_TZI(info, "Local") end end @@ -80,13 +80,44 @@ module Crystal::System::Time [] of String end - private def self.initialize_location_from_TZI(info) + # https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/ns-timezoneapi-time_zone_information#remarks + @[Extern] + private record REG_TZI_FORMAT, + bias : LibC::LONG, + standardBias : LibC::LONG, + daylightBias : LibC::LONG, + standardDate : LibC::SYSTEMTIME, + daylightDate : LibC::SYSTEMTIME + + def self.load_iana_zone(iana_name : String) : ::Time::Location? + return unless windows_name = iana_to_windows[iana_name]? + + WindowsRegistry.open?(LibC::HKEY_LOCAL_MACHINE, REGISTRY_TIME_ZONES) do |key_handle| + WindowsRegistry.open?(key_handle, windows_name.to_utf16) do |sub_handle| + reg_tzi = uninitialized REG_TZI_FORMAT + WindowsRegistry.get_raw(sub_handle, TZI, Slice.new(pointerof(reg_tzi), 1).to_unsafe_bytes) + + tzi = LibC::TIME_ZONE_INFORMATION.new( + bias: reg_tzi.bias, + standardDate: reg_tzi.standardDate, + standardBias: reg_tzi.standardBias, + daylightDate: reg_tzi.daylightDate, + daylightBias: reg_tzi.daylightBias, + ) + WindowsRegistry.get_raw(sub_handle, Std, tzi.standardName.to_slice.to_unsafe_bytes) + WindowsRegistry.get_raw(sub_handle, Dlt, tzi.daylightName.to_slice.to_unsafe_bytes) + initialize_location_from_TZI(tzi, iana_name) + end + end + end + + private def self.initialize_location_from_TZI(info, name) stdname, dstname = normalize_zone_names(info) if info.standardDate.wMonth == 0_u16 # No DST zone = ::Time::Location::Zone.new(stdname, info.bias * BIAS_TO_OFFSET_FACTOR, false) - return ::Time::Location.new("Local", [zone]) + return ::Time::Location.new(name, [zone]) end zones = [ @@ -116,7 +147,7 @@ module Crystal::System::Time transitions << ::Time::Location::ZoneTransition.new(tstamp, second_index, second_index == 0, false) end - ::Time::Location.new("Local", zones, transitions) + ::Time::Location.new(name, zones, transitions) end # Calculates the day of a DST switch in year *year* by extrapolating the date given in @@ -161,14 +192,14 @@ module Crystal::System::Time private def self.normalize_zone_names(info : LibC::TIME_ZONE_INFORMATION) : Tuple(String, String) stdname, _ = String.from_utf16(info.standardName.to_slice.to_unsafe) - if normalized_names = WINDOWS_ZONE_NAMES[stdname]? + if normalized_names = windows_zone_names[stdname]? return normalized_names end dstname, _ = String.from_utf16(info.daylightName.to_slice.to_unsafe) if english_name = translate_zone_name(stdname, dstname) - if normalized_names = WINDOWS_ZONE_NAMES[english_name]? + if normalized_names = windows_zone_names[english_name]? return normalized_names end end @@ -181,6 +212,7 @@ module Crystal::System::Time REGISTRY_TIME_ZONES = %q(SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones).to_utf16 Std = "Std".to_utf16 Dlt = "Dlt".to_utf16 + TZI = "TZI".to_utf16 # Searches the registry for an English name of a time zone named *stdname* or *dstname* # and returns the English name. diff --git a/src/crystal/system/win32/zone_names.cr b/src/crystal/system/win32/zone_names.cr index 74898c4a8389..b07c678e695d 100644 --- a/src/crystal/system/win32/zone_names.cr +++ b/src/crystal/system/win32/zone_names.cr @@ -5,147 +5,628 @@ # DO NOT EDIT module Crystal::System::Time - # These mappings for windows time zone names are based on - # https://raw.githubusercontent.com/unicode-org/cldr/main/common/supplemental/windowsZones.xml - WINDOWS_ZONE_NAMES = { - "Egypt Standard Time" => {"EET", "EEST"}, # Africa/Cairo - "Morocco Standard Time" => {"+01", "+01"}, # Africa/Casablanca - "South Africa Standard Time" => {"SAST", "SAST"}, # Africa/Johannesburg - "South Sudan Standard Time" => {"CAT", "CAT"}, # Africa/Juba - "Sudan Standard Time" => {"CAT", "CAT"}, # Africa/Khartoum - "W. Central Africa Standard Time" => {"WAT", "WAT"}, # Africa/Lagos - "E. Africa Standard Time" => {"EAT", "EAT"}, # Africa/Nairobi - "Sao Tome Standard Time" => {"GMT", "GMT"}, # Africa/Sao_Tome - "Libya Standard Time" => {"EET", "EET"}, # Africa/Tripoli - "Namibia Standard Time" => {"CAT", "CAT"}, # Africa/Windhoek - "Aleutian Standard Time" => {"HST", "HDT"}, # America/Adak - "Alaskan Standard Time" => {"AKST", "AKDT"}, # America/Anchorage - "Tocantins Standard Time" => {"-03", "-03"}, # America/Araguaina - "Paraguay Standard Time" => {"-04", "-03"}, # America/Asuncion - "Bahia Standard Time" => {"-03", "-03"}, # America/Bahia - "SA Pacific Standard Time" => {"-05", "-05"}, # America/Bogota - "Argentina Standard Time" => {"-03", "-03"}, # America/Buenos_Aires - "Eastern Standard Time (Mexico)" => {"EST", "EST"}, # America/Cancun - "Venezuela Standard Time" => {"-04", "-04"}, # America/Caracas - "SA Eastern Standard Time" => {"-03", "-03"}, # America/Cayenne - "Central Standard Time" => {"CST", "CDT"}, # America/Chicago - "Central Brazilian Standard Time" => {"-04", "-04"}, # America/Cuiaba - "Mountain Standard Time" => {"MST", "MDT"}, # America/Denver - "Greenland Standard Time" => {"-03", "-02"}, # America/Godthab - "Turks And Caicos Standard Time" => {"EST", "EDT"}, # America/Grand_Turk - "Central America Standard Time" => {"CST", "CST"}, # America/Guatemala - "Atlantic Standard Time" => {"AST", "ADT"}, # America/Halifax - "Cuba Standard Time" => {"CST", "CDT"}, # America/Havana - "US Eastern Standard Time" => {"EST", "EDT"}, # America/Indianapolis - "SA Western Standard Time" => {"-04", "-04"}, # America/La_Paz - "Pacific Standard Time" => {"PST", "PDT"}, # America/Los_Angeles - "Mountain Standard Time (Mexico)" => {"MST", "MST"}, # America/Mazatlan - "Central Standard Time (Mexico)" => {"CST", "CST"}, # America/Mexico_City - "Saint Pierre Standard Time" => {"-03", "-02"}, # America/Miquelon - "Montevideo Standard Time" => {"-03", "-03"}, # America/Montevideo - "Eastern Standard Time" => {"EST", "EDT"}, # America/New_York - "US Mountain Standard Time" => {"MST", "MST"}, # America/Phoenix - "Haiti Standard Time" => {"EST", "EDT"}, # America/Port-au-Prince - "Magallanes Standard Time" => {"-03", "-03"}, # America/Punta_Arenas - "Canada Central Standard Time" => {"CST", "CST"}, # America/Regina - "Pacific SA Standard Time" => {"-04", "-03"}, # America/Santiago - "E. South America Standard Time" => {"-03", "-03"}, # America/Sao_Paulo - "Newfoundland Standard Time" => {"NST", "NDT"}, # America/St_Johns - "Pacific Standard Time (Mexico)" => {"PST", "PDT"}, # America/Tijuana - "Yukon Standard Time" => {"MST", "MST"}, # America/Whitehorse - "Central Asia Standard Time" => {"+06", "+06"}, # Asia/Almaty - "Jordan Standard Time" => {"+03", "+03"}, # Asia/Amman - "Arabic Standard Time" => {"+03", "+03"}, # Asia/Baghdad - "Azerbaijan Standard Time" => {"+04", "+04"}, # Asia/Baku - "SE Asia Standard Time" => {"+07", "+07"}, # Asia/Bangkok - "Altai Standard Time" => {"+07", "+07"}, # Asia/Barnaul - "Middle East Standard Time" => {"EET", "EEST"}, # Asia/Beirut - "India Standard Time" => {"IST", "IST"}, # Asia/Calcutta - "Transbaikal Standard Time" => {"+09", "+09"}, # Asia/Chita - "Sri Lanka Standard Time" => {"+0530", "+0530"}, # Asia/Colombo - "Syria Standard Time" => {"+03", "+03"}, # Asia/Damascus - "Bangladesh Standard Time" => {"+06", "+06"}, # Asia/Dhaka - "Arabian Standard Time" => {"+04", "+04"}, # Asia/Dubai - "West Bank Standard Time" => {"EET", "EEST"}, # Asia/Hebron - "W. Mongolia Standard Time" => {"+07", "+07"}, # Asia/Hovd - "North Asia East Standard Time" => {"+08", "+08"}, # Asia/Irkutsk - "Israel Standard Time" => {"IST", "IDT"}, # Asia/Jerusalem - "Afghanistan Standard Time" => {"+0430", "+0430"}, # Asia/Kabul - "Russia Time Zone 11" => {"+12", "+12"}, # Asia/Kamchatka - "Pakistan Standard Time" => {"PKT", "PKT"}, # Asia/Karachi - "Nepal Standard Time" => {"+0545", "+0545"}, # Asia/Katmandu - "North Asia Standard Time" => {"+07", "+07"}, # Asia/Krasnoyarsk - "Magadan Standard Time" => {"+11", "+11"}, # Asia/Magadan - "N. Central Asia Standard Time" => {"+07", "+07"}, # Asia/Novosibirsk - "Omsk Standard Time" => {"+06", "+06"}, # Asia/Omsk - "North Korea Standard Time" => {"KST", "KST"}, # Asia/Pyongyang - "Qyzylorda Standard Time" => {"+05", "+05"}, # Asia/Qyzylorda - "Myanmar Standard Time" => {"+0630", "+0630"}, # Asia/Rangoon - "Arab Standard Time" => {"+03", "+03"}, # Asia/Riyadh - "Sakhalin Standard Time" => {"+11", "+11"}, # Asia/Sakhalin - "Korea Standard Time" => {"KST", "KST"}, # Asia/Seoul - "China Standard Time" => {"CST", "CST"}, # Asia/Shanghai - "Singapore Standard Time" => {"+08", "+08"}, # Asia/Singapore - "Russia Time Zone 10" => {"+11", "+11"}, # Asia/Srednekolymsk - "Taipei Standard Time" => {"CST", "CST"}, # Asia/Taipei - "West Asia Standard Time" => {"+05", "+05"}, # Asia/Tashkent - "Georgian Standard Time" => {"+04", "+04"}, # Asia/Tbilisi - "Iran Standard Time" => {"+0330", "+0330"}, # Asia/Tehran - "Tokyo Standard Time" => {"JST", "JST"}, # Asia/Tokyo - "Tomsk Standard Time" => {"+07", "+07"}, # Asia/Tomsk - "Ulaanbaatar Standard Time" => {"+08", "+08"}, # Asia/Ulaanbaatar - "Vladivostok Standard Time" => {"+10", "+10"}, # Asia/Vladivostok - "Yakutsk Standard Time" => {"+09", "+09"}, # Asia/Yakutsk - "Ekaterinburg Standard Time" => {"+05", "+05"}, # Asia/Yekaterinburg - "Caucasus Standard Time" => {"+04", "+04"}, # Asia/Yerevan - "Azores Standard Time" => {"-01", "+00"}, # Atlantic/Azores - "Cape Verde Standard Time" => {"-01", "-01"}, # Atlantic/Cape_Verde - "Greenwich Standard Time" => {"GMT", "GMT"}, # Atlantic/Reykjavik - "Cen. Australia Standard Time" => {"ACST", "ACDT"}, # Australia/Adelaide - "E. Australia Standard Time" => {"AEST", "AEST"}, # Australia/Brisbane - "AUS Central Standard Time" => {"ACST", "ACST"}, # Australia/Darwin - "Aus Central W. Standard Time" => {"+0845", "+0845"}, # Australia/Eucla - "Tasmania Standard Time" => {"AEST", "AEDT"}, # Australia/Hobart - "Lord Howe Standard Time" => {"+1030", "+11"}, # Australia/Lord_Howe - "W. Australia Standard Time" => {"AWST", "AWST"}, # Australia/Perth - "AUS Eastern Standard Time" => {"AEST", "AEDT"}, # Australia/Sydney - "UTC-11" => {"-11", "-11"}, # Etc/GMT+11 - "Dateline Standard Time" => {"-12", "-12"}, # Etc/GMT+12 - "UTC-02" => {"-02", "-02"}, # Etc/GMT+2 - "UTC-08" => {"-08", "-08"}, # Etc/GMT+8 - "UTC-09" => {"-09", "-09"}, # Etc/GMT+9 - "UTC+12" => {"+12", "+12"}, # Etc/GMT-12 - "UTC+13" => {"+13", "+13"}, # Etc/GMT-13 - "UTC" => {"UTC", "UTC"}, # Etc/UTC - "Astrakhan Standard Time" => {"+04", "+04"}, # Europe/Astrakhan - "W. Europe Standard Time" => {"CET", "CEST"}, # Europe/Berlin - "GTB Standard Time" => {"EET", "EEST"}, # Europe/Bucharest - "Central Europe Standard Time" => {"CET", "CEST"}, # Europe/Budapest - "E. Europe Standard Time" => {"EET", "EEST"}, # Europe/Chisinau - "Turkey Standard Time" => {"+03", "+03"}, # Europe/Istanbul - "Kaliningrad Standard Time" => {"EET", "EET"}, # Europe/Kaliningrad - "FLE Standard Time" => {"EET", "EEST"}, # Europe/Kiev - "GMT Standard Time" => {"GMT", "BST"}, # Europe/London - "Belarus Standard Time" => {"+03", "+03"}, # Europe/Minsk - "Russian Standard Time" => {"MSK", "MSK"}, # Europe/Moscow - "Romance Standard Time" => {"CET", "CEST"}, # Europe/Paris - "Russia Time Zone 3" => {"+04", "+04"}, # Europe/Samara - "Saratov Standard Time" => {"+04", "+04"}, # Europe/Saratov - "Volgograd Standard Time" => {"MSK", "MSK"}, # Europe/Volgograd - "Central European Standard Time" => {"CET", "CEST"}, # Europe/Warsaw - "Mauritius Standard Time" => {"+04", "+04"}, # Indian/Mauritius - "Samoa Standard Time" => {"+13", "+13"}, # Pacific/Apia - "New Zealand Standard Time" => {"NZST", "NZDT"}, # Pacific/Auckland - "Bougainville Standard Time" => {"+11", "+11"}, # Pacific/Bougainville - "Chatham Islands Standard Time" => {"+1245", "+1345"}, # Pacific/Chatham - "Easter Island Standard Time" => {"-06", "-05"}, # Pacific/Easter - "Fiji Standard Time" => {"+12", "+12"}, # Pacific/Fiji - "Central Pacific Standard Time" => {"+11", "+11"}, # Pacific/Guadalcanal - "Hawaiian Standard Time" => {"HST", "HST"}, # Pacific/Honolulu - "Line Islands Standard Time" => {"+14", "+14"}, # Pacific/Kiritimati - "Marquesas Standard Time" => {"-0930", "-0930"}, # Pacific/Marquesas - "Norfolk Standard Time" => {"+11", "+12"}, # Pacific/Norfolk - "West Pacific Standard Time" => {"+10", "+10"}, # Pacific/Port_Moresby - "Tonga Standard Time" => {"+13", "+13"}, # Pacific/Tongatapu - } + # These mappings from IANA to Windows time zone names are based on + # https://raw.githubusercontent.com/unicode-org/cldr/817409270794bb1538fe6b4aa3e9c79aff2c34d2/common/supplemental/windowsZones.xml + private class_getter iana_to_windows : Hash(String, String) do + data = Hash(String, String).new(initial_capacity: 460) + put(data, "Africa/Abidjan", "Greenwich Standard Time") + put(data, "Africa/Accra", "Greenwich Standard Time") + put(data, "Africa/Addis_Ababa", "E. Africa Standard Time") + put(data, "Africa/Algiers", "W. Central Africa Standard Time") + put(data, "Africa/Asmera", "E. Africa Standard Time") + put(data, "Africa/Bamako", "Greenwich Standard Time") + put(data, "Africa/Bangui", "W. Central Africa Standard Time") + put(data, "Africa/Banjul", "Greenwich Standard Time") + put(data, "Africa/Bissau", "Greenwich Standard Time") + put(data, "Africa/Blantyre", "South Africa Standard Time") + put(data, "Africa/Brazzaville", "W. Central Africa Standard Time") + put(data, "Africa/Bujumbura", "South Africa Standard Time") + put(data, "Africa/Cairo", "Egypt Standard Time") + put(data, "Africa/Casablanca", "Morocco Standard Time") + put(data, "Africa/Ceuta", "Romance Standard Time") + put(data, "Africa/Conakry", "Greenwich Standard Time") + put(data, "Africa/Dakar", "Greenwich Standard Time") + put(data, "Africa/Dar_es_Salaam", "E. Africa Standard Time") + put(data, "Africa/Djibouti", "E. Africa Standard Time") + put(data, "Africa/Douala", "W. Central Africa Standard Time") + put(data, "Africa/El_Aaiun", "Morocco Standard Time") + put(data, "Africa/Freetown", "Greenwich Standard Time") + put(data, "Africa/Gaborone", "South Africa Standard Time") + put(data, "Africa/Harare", "South Africa Standard Time") + put(data, "Africa/Johannesburg", "South Africa Standard Time") + put(data, "Africa/Juba", "South Sudan Standard Time") + put(data, "Africa/Kampala", "E. Africa Standard Time") + put(data, "Africa/Khartoum", "Sudan Standard Time") + put(data, "Africa/Kigali", "South Africa Standard Time") + put(data, "Africa/Kinshasa", "W. Central Africa Standard Time") + put(data, "Africa/Lagos", "W. Central Africa Standard Time") + put(data, "Africa/Libreville", "W. Central Africa Standard Time") + put(data, "Africa/Lome", "Greenwich Standard Time") + put(data, "Africa/Luanda", "W. Central Africa Standard Time") + put(data, "Africa/Lubumbashi", "South Africa Standard Time") + put(data, "Africa/Lusaka", "South Africa Standard Time") + put(data, "Africa/Malabo", "W. Central Africa Standard Time") + put(data, "Africa/Maputo", "South Africa Standard Time") + put(data, "Africa/Maseru", "South Africa Standard Time") + put(data, "Africa/Mbabane", "South Africa Standard Time") + put(data, "Africa/Mogadishu", "E. Africa Standard Time") + put(data, "Africa/Monrovia", "Greenwich Standard Time") + put(data, "Africa/Nairobi", "E. Africa Standard Time") + put(data, "Africa/Ndjamena", "W. Central Africa Standard Time") + put(data, "Africa/Niamey", "W. Central Africa Standard Time") + put(data, "Africa/Nouakchott", "Greenwich Standard Time") + put(data, "Africa/Ouagadougou", "Greenwich Standard Time") + put(data, "Africa/Porto-Novo", "W. Central Africa Standard Time") + put(data, "Africa/Sao_Tome", "Sao Tome Standard Time") + put(data, "Africa/Tripoli", "Libya Standard Time") + put(data, "Africa/Tunis", "W. Central Africa Standard Time") + put(data, "Africa/Windhoek", "Namibia Standard Time") + put(data, "America/Adak", "Aleutian Standard Time") + put(data, "America/Anchorage", "Alaskan Standard Time") + put(data, "America/Anguilla", "SA Western Standard Time") + put(data, "America/Antigua", "SA Western Standard Time") + put(data, "America/Araguaina", "Tocantins Standard Time") + put(data, "America/Argentina/La_Rioja", "Argentina Standard Time") + put(data, "America/Argentina/Rio_Gallegos", "Argentina Standard Time") + put(data, "America/Argentina/Salta", "Argentina Standard Time") + put(data, "America/Argentina/San_Juan", "Argentina Standard Time") + put(data, "America/Argentina/San_Luis", "Argentina Standard Time") + put(data, "America/Argentina/Tucuman", "Argentina Standard Time") + put(data, "America/Argentina/Ushuaia", "Argentina Standard Time") + put(data, "America/Aruba", "SA Western Standard Time") + put(data, "America/Asuncion", "Paraguay Standard Time") + put(data, "America/Bahia", "Bahia Standard Time") + put(data, "America/Bahia_Banderas", "Central Standard Time (Mexico)") + put(data, "America/Barbados", "SA Western Standard Time") + put(data, "America/Belem", "SA Eastern Standard Time") + put(data, "America/Belize", "Central America Standard Time") + put(data, "America/Blanc-Sablon", "SA Western Standard Time") + put(data, "America/Boa_Vista", "SA Western Standard Time") + put(data, "America/Bogota", "SA Pacific Standard Time") + put(data, "America/Boise", "Mountain Standard Time") + put(data, "America/Buenos_Aires", "Argentina Standard Time") + put(data, "America/Cambridge_Bay", "Mountain Standard Time") + put(data, "America/Campo_Grande", "Central Brazilian Standard Time") + put(data, "America/Cancun", "Eastern Standard Time (Mexico)") + put(data, "America/Caracas", "Venezuela Standard Time") + put(data, "America/Catamarca", "Argentina Standard Time") + put(data, "America/Cayenne", "SA Eastern Standard Time") + put(data, "America/Cayman", "SA Pacific Standard Time") + put(data, "America/Chicago", "Central Standard Time") + put(data, "America/Chihuahua", "Central Standard Time (Mexico)") + put(data, "America/Ciudad_Juarez", "Mountain Standard Time") + put(data, "America/Coral_Harbour", "SA Pacific Standard Time") + put(data, "America/Cordoba", "Argentina Standard Time") + put(data, "America/Costa_Rica", "Central America Standard Time") + put(data, "America/Creston", "US Mountain Standard Time") + put(data, "America/Cuiaba", "Central Brazilian Standard Time") + put(data, "America/Curacao", "SA Western Standard Time") + put(data, "America/Danmarkshavn", "Greenwich Standard Time") + put(data, "America/Dawson", "Yukon Standard Time") + put(data, "America/Dawson_Creek", "US Mountain Standard Time") + put(data, "America/Denver", "Mountain Standard Time") + put(data, "America/Detroit", "Eastern Standard Time") + put(data, "America/Dominica", "SA Western Standard Time") + put(data, "America/Edmonton", "Mountain Standard Time") + put(data, "America/Eirunepe", "SA Pacific Standard Time") + put(data, "America/El_Salvador", "Central America Standard Time") + put(data, "America/Fort_Nelson", "US Mountain Standard Time") + put(data, "America/Fortaleza", "SA Eastern Standard Time") + put(data, "America/Glace_Bay", "Atlantic Standard Time") + put(data, "America/Godthab", "Greenland Standard Time") + put(data, "America/Goose_Bay", "Atlantic Standard Time") + put(data, "America/Grand_Turk", "Turks And Caicos Standard Time") + put(data, "America/Grenada", "SA Western Standard Time") + put(data, "America/Guadeloupe", "SA Western Standard Time") + put(data, "America/Guatemala", "Central America Standard Time") + put(data, "America/Guayaquil", "SA Pacific Standard Time") + put(data, "America/Guyana", "SA Western Standard Time") + put(data, "America/Halifax", "Atlantic Standard Time") + put(data, "America/Havana", "Cuba Standard Time") + put(data, "America/Hermosillo", "US Mountain Standard Time") + put(data, "America/Indiana/Knox", "Central Standard Time") + put(data, "America/Indiana/Marengo", "US Eastern Standard Time") + put(data, "America/Indiana/Petersburg", "Eastern Standard Time") + put(data, "America/Indiana/Tell_City", "Central Standard Time") + put(data, "America/Indiana/Vevay", "US Eastern Standard Time") + put(data, "America/Indiana/Vincennes", "Eastern Standard Time") + put(data, "America/Indiana/Winamac", "Eastern Standard Time") + put(data, "America/Indianapolis", "US Eastern Standard Time") + put(data, "America/Inuvik", "Mountain Standard Time") + put(data, "America/Iqaluit", "Eastern Standard Time") + put(data, "America/Jamaica", "SA Pacific Standard Time") + put(data, "America/Jujuy", "Argentina Standard Time") + put(data, "America/Juneau", "Alaskan Standard Time") + put(data, "America/Kentucky/Monticello", "Eastern Standard Time") + put(data, "America/Kralendijk", "SA Western Standard Time") + put(data, "America/La_Paz", "SA Western Standard Time") + put(data, "America/Lima", "SA Pacific Standard Time") + put(data, "America/Los_Angeles", "Pacific Standard Time") + put(data, "America/Louisville", "Eastern Standard Time") + put(data, "America/Lower_Princes", "SA Western Standard Time") + put(data, "America/Maceio", "SA Eastern Standard Time") + put(data, "America/Managua", "Central America Standard Time") + put(data, "America/Manaus", "SA Western Standard Time") + put(data, "America/Marigot", "SA Western Standard Time") + put(data, "America/Martinique", "SA Western Standard Time") + put(data, "America/Matamoros", "Central Standard Time") + put(data, "America/Mazatlan", "Mountain Standard Time (Mexico)") + put(data, "America/Mendoza", "Argentina Standard Time") + put(data, "America/Menominee", "Central Standard Time") + put(data, "America/Merida", "Central Standard Time (Mexico)") + put(data, "America/Metlakatla", "Alaskan Standard Time") + put(data, "America/Mexico_City", "Central Standard Time (Mexico)") + put(data, "America/Miquelon", "Saint Pierre Standard Time") + put(data, "America/Moncton", "Atlantic Standard Time") + put(data, "America/Monterrey", "Central Standard Time (Mexico)") + put(data, "America/Montevideo", "Montevideo Standard Time") + put(data, "America/Montreal", "Eastern Standard Time") + put(data, "America/Montserrat", "SA Western Standard Time") + put(data, "America/Nassau", "Eastern Standard Time") + put(data, "America/New_York", "Eastern Standard Time") + put(data, "America/Nipigon", "Eastern Standard Time") + put(data, "America/Nome", "Alaskan Standard Time") + put(data, "America/Noronha", "UTC-02") + put(data, "America/North_Dakota/Beulah", "Central Standard Time") + put(data, "America/North_Dakota/Center", "Central Standard Time") + put(data, "America/North_Dakota/New_Salem", "Central Standard Time") + put(data, "America/Ojinaga", "Central Standard Time") + put(data, "America/Panama", "SA Pacific Standard Time") + put(data, "America/Pangnirtung", "Eastern Standard Time") + put(data, "America/Paramaribo", "SA Eastern Standard Time") + put(data, "America/Phoenix", "US Mountain Standard Time") + put(data, "America/Port-au-Prince", "Haiti Standard Time") + put(data, "America/Port_of_Spain", "SA Western Standard Time") + put(data, "America/Porto_Velho", "SA Western Standard Time") + put(data, "America/Puerto_Rico", "SA Western Standard Time") + put(data, "America/Punta_Arenas", "Magallanes Standard Time") + put(data, "America/Rainy_River", "Central Standard Time") + put(data, "America/Rankin_Inlet", "Central Standard Time") + put(data, "America/Recife", "SA Eastern Standard Time") + put(data, "America/Regina", "Canada Central Standard Time") + put(data, "America/Resolute", "Central Standard Time") + put(data, "America/Rio_Branco", "SA Pacific Standard Time") + put(data, "America/Santa_Isabel", "Pacific Standard Time (Mexico)") + put(data, "America/Santarem", "SA Eastern Standard Time") + put(data, "America/Santiago", "Pacific SA Standard Time") + put(data, "America/Santo_Domingo", "SA Western Standard Time") + put(data, "America/Sao_Paulo", "E. South America Standard Time") + put(data, "America/Scoresbysund", "Azores Standard Time") + put(data, "America/Sitka", "Alaskan Standard Time") + put(data, "America/St_Barthelemy", "SA Western Standard Time") + put(data, "America/St_Johns", "Newfoundland Standard Time") + put(data, "America/St_Kitts", "SA Western Standard Time") + put(data, "America/St_Lucia", "SA Western Standard Time") + put(data, "America/St_Thomas", "SA Western Standard Time") + put(data, "America/St_Vincent", "SA Western Standard Time") + put(data, "America/Swift_Current", "Canada Central Standard Time") + put(data, "America/Tegucigalpa", "Central America Standard Time") + put(data, "America/Thule", "Atlantic Standard Time") + put(data, "America/Thunder_Bay", "Eastern Standard Time") + put(data, "America/Tijuana", "Pacific Standard Time (Mexico)") + put(data, "America/Toronto", "Eastern Standard Time") + put(data, "America/Tortola", "SA Western Standard Time") + put(data, "America/Vancouver", "Pacific Standard Time") + put(data, "America/Whitehorse", "Yukon Standard Time") + put(data, "America/Winnipeg", "Central Standard Time") + put(data, "America/Yakutat", "Alaskan Standard Time") + put(data, "America/Yellowknife", "Mountain Standard Time") + put(data, "Antarctica/Casey", "Central Pacific Standard Time") + put(data, "Antarctica/Davis", "SE Asia Standard Time") + put(data, "Antarctica/DumontDUrville", "West Pacific Standard Time") + put(data, "Antarctica/Macquarie", "Tasmania Standard Time") + put(data, "Antarctica/Mawson", "West Asia Standard Time") + put(data, "Antarctica/McMurdo", "New Zealand Standard Time") + put(data, "Antarctica/Palmer", "SA Eastern Standard Time") + put(data, "Antarctica/Rothera", "SA Eastern Standard Time") + put(data, "Antarctica/Syowa", "E. Africa Standard Time") + put(data, "Antarctica/Vostok", "Central Asia Standard Time") + put(data, "Arctic/Longyearbyen", "W. Europe Standard Time") + put(data, "Asia/Aden", "Arab Standard Time") + put(data, "Asia/Almaty", "Central Asia Standard Time") + put(data, "Asia/Amman", "Jordan Standard Time") + put(data, "Asia/Anadyr", "Russia Time Zone 11") + put(data, "Asia/Aqtau", "West Asia Standard Time") + put(data, "Asia/Aqtobe", "West Asia Standard Time") + put(data, "Asia/Ashgabat", "West Asia Standard Time") + put(data, "Asia/Atyrau", "West Asia Standard Time") + put(data, "Asia/Baghdad", "Arabic Standard Time") + put(data, "Asia/Bahrain", "Arab Standard Time") + put(data, "Asia/Baku", "Azerbaijan Standard Time") + put(data, "Asia/Bangkok", "SE Asia Standard Time") + put(data, "Asia/Barnaul", "Altai Standard Time") + put(data, "Asia/Beirut", "Middle East Standard Time") + put(data, "Asia/Bishkek", "Central Asia Standard Time") + put(data, "Asia/Brunei", "Singapore Standard Time") + put(data, "Asia/Calcutta", "India Standard Time") + put(data, "Asia/Chita", "Transbaikal Standard Time") + put(data, "Asia/Choibalsan", "Ulaanbaatar Standard Time") + put(data, "Asia/Colombo", "Sri Lanka Standard Time") + put(data, "Asia/Damascus", "Syria Standard Time") + put(data, "Asia/Dhaka", "Bangladesh Standard Time") + put(data, "Asia/Dili", "Tokyo Standard Time") + put(data, "Asia/Dubai", "Arabian Standard Time") + put(data, "Asia/Dushanbe", "West Asia Standard Time") + put(data, "Asia/Famagusta", "GTB Standard Time") + put(data, "Asia/Gaza", "West Bank Standard Time") + put(data, "Asia/Hebron", "West Bank Standard Time") + put(data, "Asia/Hong_Kong", "China Standard Time") + put(data, "Asia/Hovd", "W. Mongolia Standard Time") + put(data, "Asia/Irkutsk", "North Asia East Standard Time") + put(data, "Asia/Jakarta", "SE Asia Standard Time") + put(data, "Asia/Jayapura", "Tokyo Standard Time") + put(data, "Asia/Jerusalem", "Israel Standard Time") + put(data, "Asia/Kabul", "Afghanistan Standard Time") + put(data, "Asia/Kamchatka", "Russia Time Zone 11") + put(data, "Asia/Karachi", "Pakistan Standard Time") + put(data, "Asia/Katmandu", "Nepal Standard Time") + put(data, "Asia/Khandyga", "Yakutsk Standard Time") + put(data, "Asia/Krasnoyarsk", "North Asia Standard Time") + put(data, "Asia/Kuala_Lumpur", "Singapore Standard Time") + put(data, "Asia/Kuching", "Singapore Standard Time") + put(data, "Asia/Kuwait", "Arab Standard Time") + put(data, "Asia/Macau", "China Standard Time") + put(data, "Asia/Magadan", "Magadan Standard Time") + put(data, "Asia/Makassar", "Singapore Standard Time") + put(data, "Asia/Manila", "Singapore Standard Time") + put(data, "Asia/Muscat", "Arabian Standard Time") + put(data, "Asia/Nicosia", "GTB Standard Time") + put(data, "Asia/Novokuznetsk", "North Asia Standard Time") + put(data, "Asia/Novosibirsk", "N. Central Asia Standard Time") + put(data, "Asia/Omsk", "Omsk Standard Time") + put(data, "Asia/Oral", "West Asia Standard Time") + put(data, "Asia/Phnom_Penh", "SE Asia Standard Time") + put(data, "Asia/Pontianak", "SE Asia Standard Time") + put(data, "Asia/Pyongyang", "North Korea Standard Time") + put(data, "Asia/Qatar", "Arab Standard Time") + put(data, "Asia/Qostanay", "Central Asia Standard Time") + put(data, "Asia/Qyzylorda", "Qyzylorda Standard Time") + put(data, "Asia/Rangoon", "Myanmar Standard Time") + put(data, "Asia/Riyadh", "Arab Standard Time") + put(data, "Asia/Saigon", "SE Asia Standard Time") + put(data, "Asia/Sakhalin", "Sakhalin Standard Time") + put(data, "Asia/Samarkand", "West Asia Standard Time") + put(data, "Asia/Seoul", "Korea Standard Time") + put(data, "Asia/Shanghai", "China Standard Time") + put(data, "Asia/Singapore", "Singapore Standard Time") + put(data, "Asia/Srednekolymsk", "Russia Time Zone 10") + put(data, "Asia/Taipei", "Taipei Standard Time") + put(data, "Asia/Tashkent", "West Asia Standard Time") + put(data, "Asia/Tbilisi", "Georgian Standard Time") + put(data, "Asia/Tehran", "Iran Standard Time") + put(data, "Asia/Thimphu", "Bangladesh Standard Time") + put(data, "Asia/Tokyo", "Tokyo Standard Time") + put(data, "Asia/Tomsk", "Tomsk Standard Time") + put(data, "Asia/Ulaanbaatar", "Ulaanbaatar Standard Time") + put(data, "Asia/Urumqi", "Central Asia Standard Time") + put(data, "Asia/Ust-Nera", "Vladivostok Standard Time") + put(data, "Asia/Vientiane", "SE Asia Standard Time") + put(data, "Asia/Vladivostok", "Vladivostok Standard Time") + put(data, "Asia/Yakutsk", "Yakutsk Standard Time") + put(data, "Asia/Yekaterinburg", "Ekaterinburg Standard Time") + put(data, "Asia/Yerevan", "Caucasus Standard Time") + put(data, "Atlantic/Azores", "Azores Standard Time") + put(data, "Atlantic/Bermuda", "Atlantic Standard Time") + put(data, "Atlantic/Canary", "GMT Standard Time") + put(data, "Atlantic/Cape_Verde", "Cape Verde Standard Time") + put(data, "Atlantic/Faeroe", "GMT Standard Time") + put(data, "Atlantic/Madeira", "GMT Standard Time") + put(data, "Atlantic/Reykjavik", "Greenwich Standard Time") + put(data, "Atlantic/South_Georgia", "UTC-02") + put(data, "Atlantic/St_Helena", "Greenwich Standard Time") + put(data, "Atlantic/Stanley", "SA Eastern Standard Time") + put(data, "Australia/Adelaide", "Cen. Australia Standard Time") + put(data, "Australia/Brisbane", "E. Australia Standard Time") + put(data, "Australia/Broken_Hill", "Cen. Australia Standard Time") + put(data, "Australia/Currie", "Tasmania Standard Time") + put(data, "Australia/Darwin", "AUS Central Standard Time") + put(data, "Australia/Eucla", "Aus Central W. Standard Time") + put(data, "Australia/Hobart", "Tasmania Standard Time") + put(data, "Australia/Lindeman", "E. Australia Standard Time") + put(data, "Australia/Lord_Howe", "Lord Howe Standard Time") + put(data, "Australia/Melbourne", "AUS Eastern Standard Time") + put(data, "Australia/Perth", "W. Australia Standard Time") + put(data, "Australia/Sydney", "AUS Eastern Standard Time") + put(data, "CST6CDT", "Central Standard Time") + put(data, "EST5EDT", "Eastern Standard Time") + put(data, "Etc/GMT", "UTC") + put(data, "Etc/GMT+1", "Cape Verde Standard Time") + put(data, "Etc/GMT+10", "Hawaiian Standard Time") + put(data, "Etc/GMT+11", "UTC-11") + put(data, "Etc/GMT+12", "Dateline Standard Time") + put(data, "Etc/GMT+2", "UTC-02") + put(data, "Etc/GMT+3", "SA Eastern Standard Time") + put(data, "Etc/GMT+4", "SA Western Standard Time") + put(data, "Etc/GMT+5", "SA Pacific Standard Time") + put(data, "Etc/GMT+6", "Central America Standard Time") + put(data, "Etc/GMT+7", "US Mountain Standard Time") + put(data, "Etc/GMT+8", "UTC-08") + put(data, "Etc/GMT+9", "UTC-09") + put(data, "Etc/GMT-1", "W. Central Africa Standard Time") + put(data, "Etc/GMT-10", "West Pacific Standard Time") + put(data, "Etc/GMT-11", "Central Pacific Standard Time") + put(data, "Etc/GMT-12", "UTC+12") + put(data, "Etc/GMT-13", "UTC+13") + put(data, "Etc/GMT-14", "Line Islands Standard Time") + put(data, "Etc/GMT-2", "South Africa Standard Time") + put(data, "Etc/GMT-3", "E. Africa Standard Time") + put(data, "Etc/GMT-4", "Arabian Standard Time") + put(data, "Etc/GMT-5", "West Asia Standard Time") + put(data, "Etc/GMT-6", "Central Asia Standard Time") + put(data, "Etc/GMT-7", "SE Asia Standard Time") + put(data, "Etc/GMT-8", "Singapore Standard Time") + put(data, "Etc/GMT-9", "Tokyo Standard Time") + put(data, "Etc/UTC", "UTC") + put(data, "Europe/Amsterdam", "W. Europe Standard Time") + put(data, "Europe/Andorra", "W. Europe Standard Time") + put(data, "Europe/Astrakhan", "Astrakhan Standard Time") + put(data, "Europe/Athens", "GTB Standard Time") + put(data, "Europe/Belgrade", "Central Europe Standard Time") + put(data, "Europe/Berlin", "W. Europe Standard Time") + put(data, "Europe/Bratislava", "Central Europe Standard Time") + put(data, "Europe/Brussels", "Romance Standard Time") + put(data, "Europe/Bucharest", "GTB Standard Time") + put(data, "Europe/Budapest", "Central Europe Standard Time") + put(data, "Europe/Busingen", "W. Europe Standard Time") + put(data, "Europe/Chisinau", "E. Europe Standard Time") + put(data, "Europe/Copenhagen", "Romance Standard Time") + put(data, "Europe/Dublin", "GMT Standard Time") + put(data, "Europe/Gibraltar", "W. Europe Standard Time") + put(data, "Europe/Guernsey", "GMT Standard Time") + put(data, "Europe/Helsinki", "FLE Standard Time") + put(data, "Europe/Isle_of_Man", "GMT Standard Time") + put(data, "Europe/Istanbul", "Turkey Standard Time") + put(data, "Europe/Jersey", "GMT Standard Time") + put(data, "Europe/Kaliningrad", "Kaliningrad Standard Time") + put(data, "Europe/Kiev", "FLE Standard Time") + put(data, "Europe/Kirov", "Russian Standard Time") + put(data, "Europe/Lisbon", "GMT Standard Time") + put(data, "Europe/Ljubljana", "Central Europe Standard Time") + put(data, "Europe/London", "GMT Standard Time") + put(data, "Europe/Luxembourg", "W. Europe Standard Time") + put(data, "Europe/Madrid", "Romance Standard Time") + put(data, "Europe/Malta", "W. Europe Standard Time") + put(data, "Europe/Mariehamn", "FLE Standard Time") + put(data, "Europe/Minsk", "Belarus Standard Time") + put(data, "Europe/Monaco", "W. Europe Standard Time") + put(data, "Europe/Moscow", "Russian Standard Time") + put(data, "Europe/Oslo", "W. Europe Standard Time") + put(data, "Europe/Paris", "Romance Standard Time") + put(data, "Europe/Podgorica", "Central Europe Standard Time") + put(data, "Europe/Prague", "Central Europe Standard Time") + put(data, "Europe/Riga", "FLE Standard Time") + put(data, "Europe/Rome", "W. Europe Standard Time") + put(data, "Europe/Samara", "Russia Time Zone 3") + put(data, "Europe/San_Marino", "W. Europe Standard Time") + put(data, "Europe/Sarajevo", "Central European Standard Time") + put(data, "Europe/Saratov", "Saratov Standard Time") + put(data, "Europe/Simferopol", "Russian Standard Time") + put(data, "Europe/Skopje", "Central European Standard Time") + put(data, "Europe/Sofia", "FLE Standard Time") + put(data, "Europe/Stockholm", "W. Europe Standard Time") + put(data, "Europe/Tallinn", "FLE Standard Time") + put(data, "Europe/Tirane", "Central Europe Standard Time") + put(data, "Europe/Ulyanovsk", "Astrakhan Standard Time") + put(data, "Europe/Uzhgorod", "FLE Standard Time") + put(data, "Europe/Vaduz", "W. Europe Standard Time") + put(data, "Europe/Vatican", "W. Europe Standard Time") + put(data, "Europe/Vienna", "W. Europe Standard Time") + put(data, "Europe/Vilnius", "FLE Standard Time") + put(data, "Europe/Volgograd", "Volgograd Standard Time") + put(data, "Europe/Warsaw", "Central European Standard Time") + put(data, "Europe/Zagreb", "Central European Standard Time") + put(data, "Europe/Zaporozhye", "FLE Standard Time") + put(data, "Europe/Zurich", "W. Europe Standard Time") + put(data, "Indian/Antananarivo", "E. Africa Standard Time") + put(data, "Indian/Chagos", "Central Asia Standard Time") + put(data, "Indian/Christmas", "SE Asia Standard Time") + put(data, "Indian/Cocos", "Myanmar Standard Time") + put(data, "Indian/Comoro", "E. Africa Standard Time") + put(data, "Indian/Kerguelen", "West Asia Standard Time") + put(data, "Indian/Mahe", "Mauritius Standard Time") + put(data, "Indian/Maldives", "West Asia Standard Time") + put(data, "Indian/Mauritius", "Mauritius Standard Time") + put(data, "Indian/Mayotte", "E. Africa Standard Time") + put(data, "Indian/Reunion", "Mauritius Standard Time") + put(data, "MST7MDT", "Mountain Standard Time") + put(data, "PST8PDT", "Pacific Standard Time") + put(data, "Pacific/Apia", "Samoa Standard Time") + put(data, "Pacific/Auckland", "New Zealand Standard Time") + put(data, "Pacific/Bougainville", "Bougainville Standard Time") + put(data, "Pacific/Chatham", "Chatham Islands Standard Time") + put(data, "Pacific/Easter", "Easter Island Standard Time") + put(data, "Pacific/Efate", "Central Pacific Standard Time") + put(data, "Pacific/Enderbury", "UTC+13") + put(data, "Pacific/Fakaofo", "UTC+13") + put(data, "Pacific/Fiji", "Fiji Standard Time") + put(data, "Pacific/Funafuti", "UTC+12") + put(data, "Pacific/Galapagos", "Central America Standard Time") + put(data, "Pacific/Gambier", "UTC-09") + put(data, "Pacific/Guadalcanal", "Central Pacific Standard Time") + put(data, "Pacific/Guam", "West Pacific Standard Time") + put(data, "Pacific/Honolulu", "Hawaiian Standard Time") + put(data, "Pacific/Johnston", "Hawaiian Standard Time") + put(data, "Pacific/Kiritimati", "Line Islands Standard Time") + put(data, "Pacific/Kosrae", "Central Pacific Standard Time") + put(data, "Pacific/Kwajalein", "UTC+12") + put(data, "Pacific/Majuro", "UTC+12") + put(data, "Pacific/Marquesas", "Marquesas Standard Time") + put(data, "Pacific/Midway", "UTC-11") + put(data, "Pacific/Nauru", "UTC+12") + put(data, "Pacific/Niue", "UTC-11") + put(data, "Pacific/Norfolk", "Norfolk Standard Time") + put(data, "Pacific/Noumea", "Central Pacific Standard Time") + put(data, "Pacific/Pago_Pago", "UTC-11") + put(data, "Pacific/Palau", "Tokyo Standard Time") + put(data, "Pacific/Pitcairn", "UTC-08") + put(data, "Pacific/Ponape", "Central Pacific Standard Time") + put(data, "Pacific/Port_Moresby", "West Pacific Standard Time") + put(data, "Pacific/Rarotonga", "Hawaiian Standard Time") + put(data, "Pacific/Saipan", "West Pacific Standard Time") + put(data, "Pacific/Tahiti", "Hawaiian Standard Time") + put(data, "Pacific/Tarawa", "UTC+12") + put(data, "Pacific/Tongatapu", "Tonga Standard Time") + put(data, "Pacific/Truk", "West Pacific Standard Time") + put(data, "Pacific/Wake", "UTC+12") + put(data, "Pacific/Wallis", "UTC+12") + data + end + + # These mappings from Windows time zone names to tzdata abbreviations are based on + # https://raw.githubusercontent.com/unicode-org/cldr/817409270794bb1538fe6b4aa3e9c79aff2c34d2/common/supplemental/windowsZones.xml + private class_getter windows_zone_names : Hash(String, {String, String}) do + data = Hash(String, {String, String}).new(initial_capacity: 139) + put(data, "Egypt Standard Time", "EET", "EEST") # Africa/Cairo + put(data, "Morocco Standard Time", "+01", "+01") # Africa/Casablanca + put(data, "South Africa Standard Time", "SAST", "SAST") # Africa/Johannesburg + put(data, "South Sudan Standard Time", "CAT", "CAT") # Africa/Juba + put(data, "Sudan Standard Time", "CAT", "CAT") # Africa/Khartoum + put(data, "W. Central Africa Standard Time", "WAT", "WAT") # Africa/Lagos + put(data, "E. Africa Standard Time", "EAT", "EAT") # Africa/Nairobi + put(data, "Sao Tome Standard Time", "GMT", "GMT") # Africa/Sao_Tome + put(data, "Libya Standard Time", "EET", "EET") # Africa/Tripoli + put(data, "Namibia Standard Time", "CAT", "CAT") # Africa/Windhoek + put(data, "Aleutian Standard Time", "HST", "HDT") # America/Adak + put(data, "Alaskan Standard Time", "AKST", "AKDT") # America/Anchorage + put(data, "Tocantins Standard Time", "-03", "-03") # America/Araguaina + put(data, "Paraguay Standard Time", "-04", "-03") # America/Asuncion + put(data, "Bahia Standard Time", "-03", "-03") # America/Bahia + put(data, "SA Pacific Standard Time", "-05", "-05") # America/Bogota + put(data, "Argentina Standard Time", "-03", "-03") # America/Buenos_Aires + put(data, "Eastern Standard Time (Mexico)", "EST", "EST") # America/Cancun + put(data, "Venezuela Standard Time", "-04", "-04") # America/Caracas + put(data, "SA Eastern Standard Time", "-03", "-03") # America/Cayenne + put(data, "Central Standard Time", "CST", "CDT") # America/Chicago + put(data, "Central Brazilian Standard Time", "-04", "-04") # America/Cuiaba + put(data, "Mountain Standard Time", "MST", "MDT") # America/Denver + put(data, "Greenland Standard Time", "-03", "-02") # America/Godthab + put(data, "Turks And Caicos Standard Time", "EST", "EDT") # America/Grand_Turk + put(data, "Central America Standard Time", "CST", "CST") # America/Guatemala + put(data, "Atlantic Standard Time", "AST", "ADT") # America/Halifax + put(data, "Cuba Standard Time", "CST", "CDT") # America/Havana + put(data, "US Eastern Standard Time", "EST", "EDT") # America/Indianapolis + put(data, "SA Western Standard Time", "-04", "-04") # America/La_Paz + put(data, "Pacific Standard Time", "PST", "PDT") # America/Los_Angeles + put(data, "Mountain Standard Time (Mexico)", "MST", "MST") # America/Mazatlan + put(data, "Central Standard Time (Mexico)", "CST", "CST") # America/Mexico_City + put(data, "Saint Pierre Standard Time", "-03", "-02") # America/Miquelon + put(data, "Montevideo Standard Time", "-03", "-03") # America/Montevideo + put(data, "Eastern Standard Time", "EST", "EDT") # America/New_York + put(data, "US Mountain Standard Time", "MST", "MST") # America/Phoenix + put(data, "Haiti Standard Time", "EST", "EDT") # America/Port-au-Prince + put(data, "Magallanes Standard Time", "-03", "-03") # America/Punta_Arenas + put(data, "Canada Central Standard Time", "CST", "CST") # America/Regina + put(data, "Pacific SA Standard Time", "-04", "-03") # America/Santiago + put(data, "E. South America Standard Time", "-03", "-03") # America/Sao_Paulo + put(data, "Newfoundland Standard Time", "NST", "NDT") # America/St_Johns + put(data, "Pacific Standard Time (Mexico)", "PST", "PDT") # America/Tijuana + put(data, "Yukon Standard Time", "MST", "MST") # America/Whitehorse + put(data, "Central Asia Standard Time", "+06", "+06") # Asia/Almaty + put(data, "Jordan Standard Time", "+03", "+03") # Asia/Amman + put(data, "Arabic Standard Time", "+03", "+03") # Asia/Baghdad + put(data, "Azerbaijan Standard Time", "+04", "+04") # Asia/Baku + put(data, "SE Asia Standard Time", "+07", "+07") # Asia/Bangkok + put(data, "Altai Standard Time", "+07", "+07") # Asia/Barnaul + put(data, "Middle East Standard Time", "EET", "EEST") # Asia/Beirut + put(data, "India Standard Time", "IST", "IST") # Asia/Calcutta + put(data, "Transbaikal Standard Time", "+09", "+09") # Asia/Chita + put(data, "Sri Lanka Standard Time", "+0530", "+0530") # Asia/Colombo + put(data, "Syria Standard Time", "+03", "+03") # Asia/Damascus + put(data, "Bangladesh Standard Time", "+06", "+06") # Asia/Dhaka + put(data, "Arabian Standard Time", "+04", "+04") # Asia/Dubai + put(data, "West Bank Standard Time", "EET", "EEST") # Asia/Hebron + put(data, "W. Mongolia Standard Time", "+07", "+07") # Asia/Hovd + put(data, "North Asia East Standard Time", "+08", "+08") # Asia/Irkutsk + put(data, "Israel Standard Time", "IST", "IDT") # Asia/Jerusalem + put(data, "Afghanistan Standard Time", "+0430", "+0430") # Asia/Kabul + put(data, "Russia Time Zone 11", "+12", "+12") # Asia/Kamchatka + put(data, "Pakistan Standard Time", "PKT", "PKT") # Asia/Karachi + put(data, "Nepal Standard Time", "+0545", "+0545") # Asia/Katmandu + put(data, "North Asia Standard Time", "+07", "+07") # Asia/Krasnoyarsk + put(data, "Magadan Standard Time", "+11", "+11") # Asia/Magadan + put(data, "N. Central Asia Standard Time", "+07", "+07") # Asia/Novosibirsk + put(data, "Omsk Standard Time", "+06", "+06") # Asia/Omsk + put(data, "North Korea Standard Time", "KST", "KST") # Asia/Pyongyang + put(data, "Qyzylorda Standard Time", "+05", "+05") # Asia/Qyzylorda + put(data, "Myanmar Standard Time", "+0630", "+0630") # Asia/Rangoon + put(data, "Arab Standard Time", "+03", "+03") # Asia/Riyadh + put(data, "Sakhalin Standard Time", "+11", "+11") # Asia/Sakhalin + put(data, "Korea Standard Time", "KST", "KST") # Asia/Seoul + put(data, "China Standard Time", "CST", "CST") # Asia/Shanghai + put(data, "Singapore Standard Time", "+08", "+08") # Asia/Singapore + put(data, "Russia Time Zone 10", "+11", "+11") # Asia/Srednekolymsk + put(data, "Taipei Standard Time", "CST", "CST") # Asia/Taipei + put(data, "West Asia Standard Time", "+05", "+05") # Asia/Tashkent + put(data, "Georgian Standard Time", "+04", "+04") # Asia/Tbilisi + put(data, "Iran Standard Time", "+0330", "+0330") # Asia/Tehran + put(data, "Tokyo Standard Time", "JST", "JST") # Asia/Tokyo + put(data, "Tomsk Standard Time", "+07", "+07") # Asia/Tomsk + put(data, "Ulaanbaatar Standard Time", "+08", "+08") # Asia/Ulaanbaatar + put(data, "Vladivostok Standard Time", "+10", "+10") # Asia/Vladivostok + put(data, "Yakutsk Standard Time", "+09", "+09") # Asia/Yakutsk + put(data, "Ekaterinburg Standard Time", "+05", "+05") # Asia/Yekaterinburg + put(data, "Caucasus Standard Time", "+04", "+04") # Asia/Yerevan + put(data, "Azores Standard Time", "-01", "+00") # Atlantic/Azores + put(data, "Cape Verde Standard Time", "-01", "-01") # Atlantic/Cape_Verde + put(data, "Greenwich Standard Time", "GMT", "GMT") # Atlantic/Reykjavik + put(data, "Cen. Australia Standard Time", "ACST", "ACDT") # Australia/Adelaide + put(data, "E. Australia Standard Time", "AEST", "AEST") # Australia/Brisbane + put(data, "AUS Central Standard Time", "ACST", "ACST") # Australia/Darwin + put(data, "Aus Central W. Standard Time", "+0845", "+0845") # Australia/Eucla + put(data, "Tasmania Standard Time", "AEST", "AEDT") # Australia/Hobart + put(data, "Lord Howe Standard Time", "+1030", "+11") # Australia/Lord_Howe + put(data, "W. Australia Standard Time", "AWST", "AWST") # Australia/Perth + put(data, "AUS Eastern Standard Time", "AEST", "AEDT") # Australia/Sydney + put(data, "UTC-11", "-11", "-11") # Etc/GMT+11 + put(data, "Dateline Standard Time", "-12", "-12") # Etc/GMT+12 + put(data, "UTC-02", "-02", "-02") # Etc/GMT+2 + put(data, "UTC-08", "-08", "-08") # Etc/GMT+8 + put(data, "UTC-09", "-09", "-09") # Etc/GMT+9 + put(data, "UTC+12", "+12", "+12") # Etc/GMT-12 + put(data, "UTC+13", "+13", "+13") # Etc/GMT-13 + put(data, "UTC", "UTC", "UTC") # Etc/UTC + put(data, "Astrakhan Standard Time", "+04", "+04") # Europe/Astrakhan + put(data, "W. Europe Standard Time", "CET", "CEST") # Europe/Berlin + put(data, "GTB Standard Time", "EET", "EEST") # Europe/Bucharest + put(data, "Central Europe Standard Time", "CET", "CEST") # Europe/Budapest + put(data, "E. Europe Standard Time", "EET", "EEST") # Europe/Chisinau + put(data, "Turkey Standard Time", "+03", "+03") # Europe/Istanbul + put(data, "Kaliningrad Standard Time", "EET", "EET") # Europe/Kaliningrad + put(data, "FLE Standard Time", "EET", "EEST") # Europe/Kiev + put(data, "GMT Standard Time", "GMT", "BST") # Europe/London + put(data, "Belarus Standard Time", "+03", "+03") # Europe/Minsk + put(data, "Russian Standard Time", "MSK", "MSK") # Europe/Moscow + put(data, "Romance Standard Time", "CET", "CEST") # Europe/Paris + put(data, "Russia Time Zone 3", "+04", "+04") # Europe/Samara + put(data, "Saratov Standard Time", "+04", "+04") # Europe/Saratov + put(data, "Volgograd Standard Time", "MSK", "MSK") # Europe/Volgograd + put(data, "Central European Standard Time", "CET", "CEST") # Europe/Warsaw + put(data, "Mauritius Standard Time", "+04", "+04") # Indian/Mauritius + put(data, "Samoa Standard Time", "+13", "+13") # Pacific/Apia + put(data, "New Zealand Standard Time", "NZST", "NZDT") # Pacific/Auckland + put(data, "Bougainville Standard Time", "+11", "+11") # Pacific/Bougainville + put(data, "Chatham Islands Standard Time", "+1245", "+1345") # Pacific/Chatham + put(data, "Easter Island Standard Time", "-06", "-05") # Pacific/Easter + put(data, "Fiji Standard Time", "+12", "+12") # Pacific/Fiji + put(data, "Central Pacific Standard Time", "+11", "+11") # Pacific/Guadalcanal + put(data, "Hawaiian Standard Time", "HST", "HST") # Pacific/Honolulu + put(data, "Line Islands Standard Time", "+14", "+14") # Pacific/Kiritimati + put(data, "Marquesas Standard Time", "-0930", "-0930") # Pacific/Marquesas + put(data, "Norfolk Standard Time", "+11", "+12") # Pacific/Norfolk + put(data, "West Pacific Standard Time", "+10", "+10") # Pacific/Port_Moresby + put(data, "Tonga Standard Time", "+13", "+13") # Pacific/Tongatapu + data + end + + # TODO: this is needed to avoid generating lots of allocas + # in LLVM, which makes LLVM really slow. The compiler should + # try to avoid/reuse temporary allocas. + # Explanation: https://github.com/crystal-lang/crystal/issues/4516#issuecomment-306226171 + private def self.put(hash : Hash, key, value) : Nil + hash[key] = value + end + + private def self.put(hash : Hash, key, *values) : Nil + hash[key] = values + end end diff --git a/src/time/location.cr b/src/time/location.cr index 22eec8066633..fea9dde00528 100644 --- a/src/time/location.cr +++ b/src/time/location.cr @@ -298,6 +298,15 @@ class Time::Location return location end + # If none of the database sources contains a suitable location, + # try getting it from the operating system. + # This is only implemented on Windows. Unix systems usually have a + # copy of the time zone database available, and no system API + # for loading time zone information. + if location = Crystal::System::Time.load_iana_zone(name) + return location + end + raise InvalidLocationNameError.new(name) end end From e2b89670271fffb213f420f6d85bb387abccb23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 15 Jun 2023 19:42:32 +0200 Subject: [PATCH 0582/1551] Refactor `String.ends_with?` to use `MatchOptions::ENDANCHORED` (#13551) --- src/string.cr | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/string.cr b/src/string.cr index f899ecf2512f..9e67c9debb20 100644 --- a/src/string.cr +++ b/src/string.cr @@ -5131,7 +5131,14 @@ class String # "22hh".ends_with?(/[a-z]{2}/) # => true # ``` def ends_with?(re : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Bool - !!($~ = /#{re}\z/.match(self, options: options)) + if Regex.supports_match_options?(Regex::MatchOptions::ENDANCHORED) + result = re.match(self, options: options | Regex::MatchOptions::ENDANCHORED) + else + # Workaround when ENDANCHORED is unavailable (PCRE). + result = /#{re}\z/.match(self, options: options) + end + $~ = result + !!result end # Interpolates *other* into the string using top-level `::sprintf`. From 77c8ceef3a5dcdb6c02ebbf59e5cdf03deea64c2 Mon Sep 17 00:00:00 2001 From: threez Date: Thu, 15 Jun 2023 19:42:56 +0200 Subject: [PATCH 0583/1551] Add `HTTP::Request#form_params` (#13418) Co-authored-by: Quinton Miller --- spec/std/http/request_spec.cr | 25 +++++++++++++++++++++++++ src/http/request.cr | 19 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/spec/std/http/request_spec.cr b/spec/std/http/request_spec.cr index b030692fc842..f997ca8998bc 100644 --- a/spec/std/http/request_spec.cr +++ b/spec/std/http/request_spec.cr @@ -438,7 +438,30 @@ module HTTP request.query = new_query request.query_params.to_s.should eq(new_query) end + end + + describe "#form_params" do + it "returns can safely be called on get requests" do + request = Request.from_io(IO::Memory.new("GET /api/v3/some/resource HTTP/1.1\r\n\r\n")).as(Request) + request.form_params?.should eq(nil) + request.form_params.size.should eq(0) + end + + it "returns parsed HTTP::Params" do + request = Request.new("POST", "/form", HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded"}, HTTP::Params.encode({"test" => "foobar"})) + request.form_params?.should_not eq(nil) + request.form_params.size.should eq(1) + request.form_params["test"].should eq("foobar") + end + it "returns ignors invalid content-type" do + request = Request.new("POST", "/form", nil, HTTP::Params.encode({"test" => "foobar"})) + request.form_params?.should eq(nil) + request.form_params.size.should eq(0) + end + end + + describe "#hostname" do it "gets request hostname from the headers" do request = Request.from_io(IO::Memory.new("GET / HTTP/1.1\r\nHost: host.example.org:3000\r\nReferer:\r\n\r\n")).as(Request) request.hostname.should eq("host.example.org") @@ -472,7 +495,9 @@ module HTTP request = Request.new("GET", "/") request.hostname.should be_nil end + end + describe "#host_with_port" do it "gets request host with port from the headers" do request = Request.from_io(IO::Memory.new("GET / HTTP/1.1\r\nHost: host.example.org:3000\r\nReferer:\r\n\r\n")).as(Request) request.host_with_port.should eq("host.example.org:3000") diff --git a/src/http/request.cr b/src/http/request.cr index c6a72d3469c0..185ee99c56bf 100644 --- a/src/http/request.cr +++ b/src/http/request.cr @@ -21,6 +21,7 @@ class HTTP::Request property version : String @cookies : Cookies? @query_params : URI::Params? + @form_params : HTTP::Params? @uri : URI? # The network address that sent the request to an HTTP server. @@ -80,6 +81,24 @@ class HTTP::Request @query_params ||= uri.query_params end + # Returns a convenience wrapper to parse form params, see `URI::Params`. + # Returns `nil` in case the content type `"application/x-www-form-urlencoded"` + # is not present or the body is `nil`. + def form_params? : HTTP::Params? + @form_params ||= begin + if headers["Content-Type"]? == "application/x-www-form-urlencoded" + if body = self.body + HTTP::Params.parse(body.gets_to_end) + end + end + end + end + + # Returns a convenience wrapper to parse form params, see `URI::Params`. + def form_params : HTTP::Params + form_params? || HTTP::Params.new + end + def resource : String update_uri @uri.try(&.request_target) || @resource From c30f00901c7ebe042777dcb2eb298f47d131142f Mon Sep 17 00:00:00 2001 From: GeopJr Date: Sun, 18 Jun 2023 16:13:59 +0300 Subject: [PATCH 0584/1551] Add mobile support to docs (#13515) --- .../crystal/tools/doc/html/_head.html | 1 + .../crystal/tools/doc/html/css/style.css | 74 ++++++++++++++++++- src/compiler/crystal/tools/doc/html/js/doc.js | 2 +- src/compiler/crystal/tools/doc/html/main.html | 1 + src/compiler/crystal/tools/doc/html/type.html | 1 + src/compiler/crystal/tools/doc/templates.cr | 8 ++ 6 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/tools/doc/html/_head.html b/src/compiler/crystal/tools/doc/html/_head.html index f4b7e2715498..fbd427b27c33 100644 --- a/src/compiler/crystal/tools/doc/html/_head.html +++ b/src/compiler/crystal/tools/doc/html/_head.html @@ -1,5 +1,6 @@ + diff --git a/src/compiler/crystal/tools/doc/html/css/style.css b/src/compiler/crystal/tools/doc/html/css/style.css index 281a0b943966..2b91cf4ca7d9 100644 --- a/src/compiler/crystal/tools/doc/html/css/style.css +++ b/src/compiler/crystal/tools/doc/html/css/style.css @@ -725,6 +725,74 @@ span.flag.lime { visibility: visible } +img { + max-width: 100%; +} + +#sidebar-btn { + height: 32px; + width: 32px; +} + +#sidebar-btn-label { + height: 2em; + width: 2em; +} + +#sidebar-btn, #sidebar-btn-label { + display: none; + margin: .7rem; + appearance: none; + color: black; + cursor: pointer; +} + +@media only screen and (max-width: 635px) { + .sidebar, .main-content { + /* svg size + vertical margin - .search-box padding-top */ + padding-top: calc(2em + 2 * 0.7rem - 13px); + } + + #sidebar-btn, #sidebar-btn-label { + display: block; + position: absolute; + z-index: 50; + transition-duration: 200ms; + left: 0; + } + + #sidebar-btn:not(:checked) ~ #sidebar-btn-label > .close, + #sidebar-btn:checked ~ #sidebar-btn-label > .open, + #sidebar-btn:checked ~ .main-content { + display: none; + } + + #sidebar-btn:checked { + left: calc(100% - 32px - (2 * 0.7rem)); + } + + #sidebar-btn:checked ~ #sidebar-btn-label { + color: white; + /* 100% - svg size - horizontal margin */ + left: calc(100% - 2em - (2 * 0.7rem)); + } + + #sidebar-btn~.sidebar { + width: 0%; + } + + #sidebar-btn:checked~.sidebar { + visibility: visible; + width: 100%; + } + + .sidebar { + transition-duration: 200ms; + max-width: 100vw; + visibility: hidden; + } +} + @media (prefers-color-scheme: dark) { :root { color-scheme: dark; @@ -872,4 +940,8 @@ span.flag.lime { background: #202020; border: 1px solid #353535; } -} \ No newline at end of file + + #sidebar-btn, #sidebar-btn-label { + color: white; + } +} diff --git a/src/compiler/crystal/tools/doc/html/js/doc.js b/src/compiler/crystal/tools/doc/html/js/doc.js index cdfa0f45ae21..e5ed78e5ea83 100644 --- a/src/compiler/crystal/tools/doc/html/js/doc.js +++ b/src/compiler/crystal/tools/doc/html/js/doc.js @@ -29,7 +29,7 @@ document.addEventListener('DOMContentLoaded', function() { var openTypes = typesList.querySelectorAll('.current'); if (openTypes.length > 0) { var lastOpenType = openTypes[openTypes.length - 1]; - lastOpenType.scrollIntoView(); + lastOpenType.scrollIntoView(!(window.matchMedia('only screen and (max-width: 635px)')).matches); } } diff --git a/src/compiler/crystal/tools/doc/html/main.html b/src/compiler/crystal/tools/doc/html/main.html index 5a1118e957e1..65b5dfc4e30c 100644 --- a/src/compiler/crystal/tools/doc/html/main.html +++ b/src/compiler/crystal/tools/doc/html/main.html @@ -11,6 +11,7 @@ <%= Crystal::Doc::SVG_DEFS %> +<%= Crystal::Doc::SIDEBAR_BUTTON %> <%= SidebarTemplate.new(project_info, types, nil) %>
diff --git a/src/compiler/crystal/tools/doc/html/type.html b/src/compiler/crystal/tools/doc/html/type.html index c29c70867229..0e8e5bb54948 100644 --- a/src/compiler/crystal/tools/doc/html/type.html +++ b/src/compiler/crystal/tools/doc/html/type.html @@ -11,6 +11,7 @@ <%= Crystal::Doc::SVG_DEFS %> +<%= Crystal::Doc::SIDEBAR_BUTTON %> <%= SidebarTemplate.new(project_info, types, type) %>
diff --git a/src/compiler/crystal/tools/doc/templates.cr b/src/compiler/crystal/tools/doc/templates.cr index 77496edb1fd4..156318a64269 100644 --- a/src/compiler/crystal/tools/doc/templates.cr +++ b/src/compiler/crystal/tools/doc/templates.cr @@ -9,6 +9,14 @@ module Crystal::Doc SVG + SIDEBAR_BUTTON = <<-HTML + + + HTML + def self.anchor_link(anchor : String) anchor = anchor.downcase.gsub(' ', '-') From cf796c93d4d182bb93e10fc67b045b66aee05f48 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 18 Jun 2023 21:15:49 +0800 Subject: [PATCH 0585/1551] Fix local timezones without DST on Windows (#13516) --- spec/std/time/location_spec.cr | 44 +++++---- spec/support/time.cr | 94 +++++++++++++++++++ src/crystal/system/win32/time.cr | 2 +- .../x86_64-windows-msvc/c/timezoneapi.cr | 16 +++- 4 files changed, 135 insertions(+), 21 deletions(-) create mode 100644 spec/support/time.cr diff --git a/spec/std/time/location_spec.cr b/spec/std/time/location_spec.cr index d92b67950d7a..f3e03cb3fb70 100644 --- a/spec/std/time/location_spec.cr +++ b/spec/std/time/location_spec.cr @@ -1,4 +1,5 @@ require "./spec_helper" +require "../../support/time" class Time::Location describe Time::Location do @@ -225,27 +226,32 @@ class Time::Location {% if flag?(:win32) %} it "loads time zone information from registry" do - LibC.GetTimeZoneInformation(out old_info) - begin - info = LibC::TIME_ZONE_INFORMATION.new( - bias: -60, - standardBias: 0, - daylightBias: -60, - standardDate: LibC::SYSTEMTIME.new(wYear: 0, wMonth: 10, wDayOfWeek: 0, wDay: 5, wHour: 3, wMinute: 0, wSecond: 0, wMilliseconds: 0), - daylightDate: LibC::SYSTEMTIME.new(wYear: 0, wMonth: 3, wDayOfWeek: 0, wDay: 5, wHour: 2, wMinute: 0, wSecond: 0, wMilliseconds: 0), - standardName: StaticArray(UInt16, 32).new(0), - daylightName: StaticArray(UInt16, 32).new(0), - ) - info.standardName.to_slice.copy_from "Central Europe Standard Time".to_utf16 - info.daylightName.to_slice.copy_from "Central Europe Summer Time".to_utf16 - if LibC.SetTimeZoneInformation(pointerof(info)) == 0 - pending! "Unable to set time zone" if WinError.value.error_privilege_not_held? - end - + info = LibC::DYNAMIC_TIME_ZONE_INFORMATION.new( + bias: -60, + standardBias: 0, + daylightBias: -60, + standardDate: LibC::SYSTEMTIME.new(wYear: 0, wMonth: 10, wDayOfWeek: 0, wDay: 5, wHour: 3, wMinute: 0, wSecond: 0, wMilliseconds: 0), + daylightDate: LibC::SYSTEMTIME.new(wYear: 0, wMonth: 3, wDayOfWeek: 0, wDay: 5, wHour: 2, wMinute: 0, wSecond: 0, wMilliseconds: 0), + ) + info.standardName.to_slice.copy_from "Central Europe Standard Time".to_utf16 + info.daylightName.to_slice.copy_from "Central Europe Summer Time".to_utf16 + info.timeZoneKeyName.to_slice.copy_from "Central Europe Standard Time".to_utf16 + + with_system_time_zone(info) do location = Location.load_local location.zones.should eq [Time::Location::Zone.new("CET", 3600, false), Time::Location::Zone.new("CEST", 7200, true)] - ensure - LibC.SetTimeZoneInformation(pointerof(old_info)) + end + end + + it "loads time zone without DST (#13502)" do + info = LibC::DYNAMIC_TIME_ZONE_INFORMATION.new(bias: -480) + info.standardName.to_slice.copy_from "China Standard Time".to_utf16 + info.daylightName.to_slice.copy_from "China Daylight Time".to_utf16 + info.timeZoneKeyName.to_slice.copy_from "China Standard Time".to_utf16 + + with_system_time_zone(info) do + location = Location.load_local + location.zones.should eq [Time::Location::Zone.new("CST", 28800, false)] end end {% end %} diff --git a/spec/support/time.cr b/spec/support/time.cr new file mode 100644 index 000000000000..ea6d6bc6a4c9 --- /dev/null +++ b/spec/support/time.cr @@ -0,0 +1,94 @@ +{% if flag?(:win32) %} + lib LibC + struct LUID + lowPart : DWORD + highPart : Long + end + + struct LUID_AND_ATTRIBUTES + luid : LUID + attributes : DWORD + end + + struct TOKEN_PRIVILEGES + privilegeCount : DWORD + privileges : LUID_AND_ATTRIBUTES[1] + end + + TOKEN_QUERY = 0x0008 + TOKEN_ADJUST_PRIVILEGES = 0x0020 + + TokenPrivileges = 3 + + SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001_u32 + SE_PRIVILEGE_ENABLED = 0x00000002_u32 + + fun OpenProcessToken(processHandle : HANDLE, desiredAccess : DWORD, tokenHandle : HANDLE*) : BOOL + fun GetTokenInformation(tokenHandle : HANDLE, tokenInformationClass : Int, tokenInformation : Void*, tokenInformationLength : DWORD, returnLength : DWORD*) : BOOL + fun LookupPrivilegeValueW(lpSystemName : LPWSTR, lpName : LPWSTR, lpLuid : LUID*) : BOOL + fun AdjustTokenPrivileges(tokenHandle : HANDLE, disableAllPrivileges : BOOL, newState : TOKEN_PRIVILEGES*, bufferLength : DWORD, previousState : TOKEN_PRIVILEGES*, returnLength : DWORD*) : BOOL + + fun SetDynamicTimeZoneInformation(lpTimeZoneInformation : DYNAMIC_TIME_ZONE_INFORMATION*) : BOOL + end + + private SeTimeZonePrivilege = Crystal::System.to_wstr("SeTimeZonePrivilege") + + module Crystal::System::Time + # Enable the `SeTimeZonePrivilege` privilege before changing the system time + # zone. This is necessary because the privilege is by default granted but + # disabled for any new process. This only needs to be done once per run. + class_getter? time_zone_privilege_enabled : Bool do + if LibC.LookupPrivilegeValueW(nil, SeTimeZonePrivilege, out time_zone_luid) == 0 + raise RuntimeError.from_winerror("LookupPrivilegeValueW") + end + + if LibC.OpenProcessToken(LibC.GetCurrentProcess, LibC::TOKEN_QUERY, out token) != 0 + begin + LibC.GetTokenInformation(token, LibC::TokenPrivileges, nil, 0, out len) + buf = Pointer(UInt8).malloc(len).as(LibC::TOKEN_PRIVILEGES*) + LibC.GetTokenInformation(token, LibC::TokenPrivileges, buf, len, out _) + privileges = Slice.new(pointerof(buf.value.@privileges).as(LibC::LUID_AND_ATTRIBUTES*), buf.value.privilegeCount) + return true if privileges.any? { |pr| pr.luid == time_zone_luid && pr.attributes & (LibC::SE_PRIVILEGE_ENABLED_BY_DEFAULT | LibC::SE_PRIVILEGE_ENABLED) != 0 } + ensure + LibC.CloseHandle(token) + end + end + + if LibC.OpenProcessToken(LibC.GetCurrentProcess, LibC::TOKEN_ADJUST_PRIVILEGES | LibC::TOKEN_QUERY, out adjust_token) != 0 + new_privileges = LibC::TOKEN_PRIVILEGES.new( + privilegeCount: 1, + privileges: StaticArray[ + LibC::LUID_AND_ATTRIBUTES.new( + luid: time_zone_luid, + attributes: LibC::SE_PRIVILEGE_ENABLED, + ), + ], + ) + if LibC.AdjustTokenPrivileges(adjust_token, 0, pointerof(new_privileges), 0, nil, nil) != 0 + return true + end + end + + false + end + end + + def with_system_time_zone(dtzi : LibC::DYNAMIC_TIME_ZONE_INFORMATION, *, file = __FILE__, line = __LINE__, &) + unless Crystal::System::Time.time_zone_privilege_enabled? + pending! "Unable to set system time zone", file: file, line: line + end + + LibC.GetDynamicTimeZoneInformation(out old_dtzi) + unless LibC.SetDynamicTimeZoneInformation(pointerof(dtzi)) != 0 + error = WinError.value + raise RuntimeError.from_os_error("Failed to set system time zone", error) unless error.error_privilege_not_held? + pending! "Unable to set system time zone", file: file, line: line + end + + begin + yield + ensure + LibC.SetDynamicTimeZoneInformation(pointerof(old_dtzi)) + end + end +{% end %} diff --git a/src/crystal/system/win32/time.cr b/src/crystal/system/win32/time.cr index 78c7d60cae1d..358cc79a4926 100644 --- a/src/crystal/system/win32/time.cr +++ b/src/crystal/system/win32/time.cr @@ -71,7 +71,7 @@ module Crystal::System::Time end def self.load_localtime : ::Time::Location? - if LibC.GetTimeZoneInformation(out info) != LibC::TIME_ZONE_ID_UNKNOWN + if LibC.GetTimeZoneInformation(out info) != LibC::TIME_ZONE_ID_INVALID initialize_location_from_TZI(info, "Local") end end diff --git a/src/lib_c/x86_64-windows-msvc/c/timezoneapi.cr b/src/lib_c/x86_64-windows-msvc/c/timezoneapi.cr index 3e2751f1b4d3..56c502462137 100644 --- a/src/lib_c/x86_64-windows-msvc/c/timezoneapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/timezoneapi.cr @@ -12,11 +12,25 @@ lib LibC daylightBias : LONG end - TIME_TONE_ID_INVALID = 0xffffffff_u32 + struct DYNAMIC_TIME_ZONE_INFORMATION + bias : LONG + standardName : StaticArray(WCHAR, 32) + standardDate : SYSTEMTIME + standardBias : LONG + daylightName : StaticArray(WCHAR, 32) + daylightDate : SYSTEMTIME + daylightBias : LONG + timeZoneKeyName : StaticArray(WCHAR, 128) + dynamicDaylightTimeDisabled : BOOLEAN + end + + TIME_ZONE_ID_INVALID = 0xffffffff_u32 TIME_ZONE_ID_UNKNOWN = 0_u32 TIME_ZONE_ID_STANDARD = 1_u32 TIME_ZONE_ID_DAYLIGHT = 2_u32 fun GetTimeZoneInformation(tz_info : TIME_ZONE_INFORMATION*) : DWORD fun SetTimeZoneInformation(tz_info : TIME_ZONE_INFORMATION*) : BOOL + + fun GetDynamicTimeZoneInformation(pTimeZoneInformation : DYNAMIC_TIME_ZONE_INFORMATION*) : DWORD end From 6de28c8128a480c9fc7c3e8f9feb538831a5db76 Mon Sep 17 00:00:00 2001 From: Gary Mardell Date: Sun, 18 Jun 2023 06:16:01 -0700 Subject: [PATCH 0586/1551] Add `Time.unix_ns` and `#to_unix_ns` (#13359) --- spec/std/time/time_spec.cr | 26 ++++++++++++++++++++++++++ src/time.cr | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/spec/std/time/time_spec.cr b/spec/std/time/time_spec.cr index a7e8044ae1da..6e0feb167542 100644 --- a/spec/std/time/time_spec.cr +++ b/spec/std/time/time_spec.cr @@ -212,6 +212,32 @@ describe Time do time.utc?.should be_true end + describe ".unix_ns" do + it "supports Int64 values" do + nanoseconds = 1439404155001457425i64 + time = Time.unix_ns(nanoseconds) + time.should eq(Time.utc(2015, 8, 12, 18, 29, 15, nanosecond: 1457425)) + time.to_unix_ns.should eq(nanoseconds) + time.utc?.should be_true + end + + it "supports maximum valid time" do + nanoseconds = Int128.new("253402300799999999999") + time = Time.unix_ns(nanoseconds) + time.should eq(Time.utc(9999, 12, 31, 23, 59, 59, nanosecond: 999999999)) + time.to_unix_ns.should eq(nanoseconds) + time.utc?.should be_true + end + + it "supports minimum valid time" do + nanoseconds = Int128.new("-62135596800000000000") + time = Time.unix_ns(nanoseconds) + time.should eq(Time.utc(1, 1, 1, 0, 0, 0, nanosecond: 0)) + time.to_unix_ns.should eq(nanoseconds) + time.utc?.should be_true + end + end + describe ".local without arguments" do it "current time is similar in different locations" do (Time.local - Time.utc).should be_close(0.seconds, 1.second) diff --git a/src/time.cr b/src/time.cr index 0d3cff1102e2..da662a2c9ab4 100644 --- a/src/time.cr +++ b/src/time.cr @@ -535,6 +535,21 @@ struct Time utc(seconds: seconds, nanoseconds: nanoseconds.to_i) end + # Creates a new `Time` instance that corresponds to the number of + # *nanoseconds* elapsed since the Unix epoch (`1970-01-01 00:00:00.000000000 UTC`). + # + # The time zone is always UTC. + # + # ``` + # time = Time.unix_ns(981173106789479273) # => 2001-02-03 04:05:06.789479273 UTC + # time.nanosecond # => 789479273 + # ``` + def self.unix_ns(nanoseconds : Int) : Time + seconds = UNIX_EPOCH.total_seconds + (nanoseconds // 1_000_000_000) + nanoseconds = nanoseconds % 1_000_000_000 + utc(seconds: seconds, nanoseconds: nanoseconds.to_i) + end + # Creates a new `Time` instance with the same local date-time representation # (wall clock) in a different *location*. # @@ -1254,6 +1269,17 @@ struct Time to_unix * 1_000 + (nanosecond // NANOSECONDS_PER_MILLISECOND) end + # Returns the number of nanoseconds since the Unix epoch + # (`1970-01-01 00:00:00.000000000 UTC`). + # + # ``` + # time = Time.utc(2016, 1, 12, 3, 4, 5, nanosecond: 678_910_123) + # time.to_unix_ns # => 1452567845678910123 + # ``` + def to_unix_ns : Int128 + (to_unix.to_i128 * NANOSECONDS_PER_SECOND) + nanosecond + end + # Returns the number of seconds since the Unix epoch # (`1970-01-01 00:00:00 UTC`) as `Float64` with nanosecond precision. # From 82673dd7f225e7d6a2f1f33afd5b866951d14335 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sun, 18 Jun 2023 22:01:38 +0200 Subject: [PATCH 0587/1551] Add `ignore_serialize` for `YAML::Serializable` (#13556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/yaml/serializable_spec.cr | 70 ++++++++++++++++++++++++++++++ src/yaml/serialization.cr | 42 ++++++++++-------- 2 files changed, 95 insertions(+), 17 deletions(-) diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 724d82bc094d..7a098283e808 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -244,6 +244,29 @@ class YAMLAttrWithPresence getter? last_name_present : Bool end +class YAMLAttrWithPresenceAndIgnoreSerialize + include YAML::Serializable + + @[YAML::Field(presence: true, ignore_serialize: ignore_first_name?)] + property first_name : String? + + @[YAML::Field(presence: true, ignore_serialize: last_name.nil? && !last_name_present?, emit_null: true)] + property last_name : String? + + @[YAML::Field(ignore: true)] + getter? first_name_present : Bool = false + + @[YAML::Field(ignore: true)] + getter? last_name_present : Bool = false + + def initialize(@first_name : String? = nil, @last_name : String? = nil) + end + + def ignore_first_name? + first_name.nil? || first_name == "" + end +end + class YAMLAttrWithQueryAttributes include YAML::Serializable @@ -914,6 +937,53 @@ describe "YAML::Serializable" do end end + describe "serializes YAML with presence markers and ignore_serialize" do + context "ignore_serialize is set to a method which returns true when value is nil or empty string" do + it "ignores field when value is empty string" do + yaml = YAMLAttrWithPresenceAndIgnoreSerialize.from_yaml(%({"first_name": ""})) + yaml.first_name_present?.should be_true + yaml.to_yaml.should eq("--- {}\n") + end + + it "ignores field when value is nil" do + yaml = YAMLAttrWithPresenceAndIgnoreSerialize.from_yaml(%({"first_name": null})) + yaml.first_name_present?.should be_true + yaml.to_yaml.should eq("--- {}\n") + end + end + + context "ignore_serialize is set to conditional expressions 'last_name.nil? && !last_name_present?'" do + it "emits null when value is null and @last_name_present is true" do + yaml = YAMLAttrWithPresenceAndIgnoreSerialize.from_yaml(%({"last_name": null})) + yaml.last_name_present?.should be_true + + # libyaml 0.2.5 removes traling space for empty scalar nodes + if YAML.libyaml_version >= SemanticVersion.new(0, 2, 5) + yaml.to_yaml.should eq("---\nlast_name:\n") + else + yaml.to_yaml.should eq("---\nlast_name: \n") + end + end + it "does not emit null when value is null and @last_name_present is false" do + yaml = YAMLAttrWithPresenceAndIgnoreSerialize.from_yaml(%({})) + yaml.last_name_present?.should be_false + yaml.to_yaml.should eq("--- {}\n") + end + + it "emits field when value is not nil and @last_name_present is false" do + yaml = YAMLAttrWithPresenceAndIgnoreSerialize.new(last_name: "something") + yaml.last_name_present?.should be_false + yaml.to_yaml.should eq("---\nlast_name: something\n") + end + + it "emits field when value is not nil and @last_name_present is true" do + yaml = YAMLAttrWithPresenceAndIgnoreSerialize.from_yaml(%({"last_name":"something"})) + yaml.last_name_present?.should be_true + yaml.to_yaml.should eq("---\nlast_name: something\n") + end + end + end + describe "with query attributes" do it "defines query getter" do yaml = YAMLAttrWithQueryAttributes.from_yaml(%({"foo": true})) diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index 9ad76db851e0..30e9fa6640d2 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -60,7 +60,7 @@ module YAML # # `YAML::Field` properties: # * **ignore**: if `true` skip this field in serialization and deserialization (by default false) - # * **ignore_serialize**: if `true` skip this field in serialization (by default false) + # * **ignore_serialize**: If truthy, skip this field in serialization (default: `false`). The value can be any Crystal expression and is evaluated at runtime. # * **ignore_deserialize**: if `true` skip this field in deserialization (by default false) # * **key**: the value of the key in the yaml object (by default the name of the instance variable) # * **converter**: specify an alternate type for parsing and generation. The converter must define `from_yaml(YAML::ParseContext, YAML::Nodes::Node)` and `to_yaml(value, YAML::Nodes::Builder)`. Examples of converters are a `Time::Format` instance and `Time::EpochConverter` for `Time`. @@ -285,12 +285,13 @@ module YAML {% properties = {} of Nil => Nil %} {% for ivar in @type.instance_vars %} {% ann = ivar.annotation(::YAML::Field) %} - {% unless ann && (ann[:ignore] || ann[:ignore_serialize]) %} + {% unless ann && (ann[:ignore] || ann[:ignore_serialize] == true) %} {% properties[ivar.id] = { - key: ((ann && ann[:key]) || ivar).id.stringify, - converter: ann && ann[:converter], - emit_null: (ann && (ann[:emit_null] != nil) ? ann[:emit_null] : emit_nulls), + key: ((ann && ann[:key]) || ivar).id.stringify, + converter: ann && ann[:converter], + emit_null: (ann && (ann[:emit_null] != nil) ? ann[:emit_null] : emit_nulls), + ignore_serialize: ann && ann[:ignore_serialize], } %} {% end %} @@ -300,23 +301,30 @@ module YAML {% for name, value in properties %} _{{name}} = @{{name}} - {% unless value[:emit_null] %} - unless _{{name}}.nil? + {% if value[:ignore_serialize] %} + unless {{value[:ignore_serialize]}} {% end %} - {{value[:key]}}.to_yaml(yaml) + {% unless value[:emit_null] %} + unless _{{name}}.nil? + {% end %} + + {{value[:key]}}.to_yaml(yaml) - {% if value[:converter] %} - if _{{name}} - {{ value[:converter] }}.to_yaml(_{{name}}, yaml) - else - nil.to_yaml(yaml) + {% if value[:converter] %} + if _{{name}} + {{ value[:converter] }}.to_yaml(_{{name}}, yaml) + else + nil.to_yaml(yaml) + end + {% else %} + _{{name}}.to_yaml(yaml) + {% end %} + + {% unless value[:emit_null] %} end - {% else %} - _{{name}}.to_yaml(yaml) {% end %} - - {% unless value[:emit_null] %} + {% if value[:ignore_serialize] %} end {% end %} {% end %} From 3d6426649762ae924502a352b5a79b2eb1197a2e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 19 Jun 2023 04:01:57 +0800 Subject: [PATCH 0588/1551] Make `BigDecimal`'s comparisons exact (#13554) --- spec/std/big/big_decimal_spec.cr | 53 ++++++++++++++++++++++++++++++-- src/big/big_decimal.cr | 41 +++++++++++++++++++++--- src/big/big_int.cr | 5 ++- src/big/big_rational.cr | 9 ++++-- 4 files changed, 98 insertions(+), 10 deletions(-) diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index 1470aa9d8357..085736de5294 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -377,8 +377,23 @@ describe BigDecimal do (BigDecimal.new("7.5") > 6.6).should be_true (6.6 < BigDecimal.new("7.5")).should be_true - BigDecimal.new("6.6").should eq(6.6) - 6.6.should eq(BigDecimal.new("6.6")) + "1.0000000000000002".to_big_d.should be < 1.0.next_float + (1.0.to_big_d + 0.5.to_big_d ** 52).should eq(1.0.next_float) + "1.0000000000000003".to_big_d.should be > 1.0.next_float + + 1.0.next_float.should be > "1.0000000000000002".to_big_d + 1.0.next_float.should eq(1.0.to_big_d + 0.5.to_big_d ** 52) + 1.0.next_float.should be < "1.0000000000000003".to_big_d + + 0.to_big_d.should be < Float64::INFINITY + (Float64::MAX.to_big_d ** 7).should be < Float64::INFINITY + 0.to_big_d.should be > -Float64::INFINITY + (Float64::MIN.to_big_d ** 7).should be > -Float64::INFINITY + + Float64::INFINITY.should be > 0.to_big_d + Float64::INFINITY.should be > (Float64::MAX.to_big_d ** 7) + (-Float64::INFINITY).should be < 0.to_big_d + (-Float64::INFINITY).should be < (Float64::MIN.to_big_d ** 7) (BigDecimal.new("6.5") > 7).should be_false (BigDecimal.new("7.5") > 6).should be_true @@ -388,6 +403,40 @@ describe BigDecimal do BigRational.new(1, 2).should eq(BigDecimal.new("0.5")) BigRational.new(1, 4).should eq(BigDecimal.new("0.25")) + + (1.to_big_d / 3).should be < BigRational.new(1, 3) + (-(1.to_big_d / 3)).should be > BigRational.new(-1, 3) + (-1.to_big_d / 3).should be < BigRational.new(-1, 3) + + BigRational.new(1, 3).should be > 1.to_big_d / 3 + BigRational.new(-1, 3).should be < -(1.to_big_d / 3) + BigRational.new(-1, 3).should be > -1.to_big_d / 3 + + (1.to_big_d / 3 + BigDecimal.new(1, BigDecimal::DEFAULT_PRECISION)).should be > BigRational.new(1, 3) + (-(1.to_big_d / 3) - BigDecimal.new(1, BigDecimal::DEFAULT_PRECISION)).should be < BigRational.new(-1, 3) + + BigRational.new(1, 3).should be < (1.to_big_d / 3 + BigDecimal.new(1, BigDecimal::DEFAULT_PRECISION)) + BigRational.new(-1, 3).should be > (-(1.to_big_d / 3) - BigDecimal.new(1, BigDecimal::DEFAULT_PRECISION)) + + (0.5.to_big_d ** 10000).should eq(0.5.to_big_f ** 10000) + "5.0123727492064520093e-3011".to_big_d.should be > 0.5.to_big_f ** 10000 + + (0.5.to_big_f ** 10000).should eq(0.5.to_big_d ** 10000) + (0.5.to_big_f ** 10000).should be < "5.0123727492064520093e-3011".to_big_d + end + + describe "#<=>" do + it "compares against NaNs" do + (1.to_big_d <=> Float64::NAN).should be_nil + (1.to_big_d <=> Float32::NAN).should be_nil + (Float64::NAN <=> 1.to_big_d).should be_nil + (Float32::NAN <=> 1.to_big_d).should be_nil + + typeof(1.to_big_d <=> Float64::NAN).should eq(Int32?) + typeof(1.to_big_d <=> Float32::NAN).should eq(Int32?) + typeof(Float64::NAN <=> 1.to_big_d).should eq(Int32?) + typeof(Float32::NAN <=> 1.to_big_d).should eq(Int32?) + end end it "keeps precision" do diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index b30c1a936681..0a97a21b9723 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -227,8 +227,9 @@ struct BigDecimal < Number # Divides `self` with another `BigDecimal`, with an optionally configurable # *precision*. # - # When the division is inexact, the returned value's scale is never greater - # than `scale - other.scale + precision`. + # When the division is inexact, the returned value rounds towards negative + # infinity, and its scale is never greater than + # `scale - other.scale + precision`. # # ``` # BigDecimal.new(1).div(BigDecimal.new(2)) # => BigDecimal(@value=5, @scale=2) @@ -325,7 +326,30 @@ struct BigDecimal < Number end end - def <=>(other : Int | Float | BigRational) + def <=>(other : BigRational) : Int32 + if @scale == 0 + @value <=> other + else + # `@value / power_ten_to(@scale) <=> other.numerator / other.denominator` + @value * other.denominator <=> power_ten_to(@scale) * other.numerator + end + end + + def <=>(other : Float::Primitive) : Int32? + return nil if other.nan? + + if sign = other.infinite? + return -sign + end + + self <=> other.to_big_r + end + + def <=>(other : BigFloat) : Int32 + self <=> other.to_big_r + end + + def <=>(other : Int) self <=> BigDecimal.new(other) end @@ -784,7 +808,8 @@ struct Float include Comparable(BigDecimal) def <=>(other : BigDecimal) - to_big_d <=> other + cmp = other <=> self + -cmp if cmp end # Converts `self` to `BigDecimal`. @@ -800,11 +825,17 @@ struct Float end end +struct BigFloat + def <=>(other : BigDecimal) + -(other <=> self) + end +end + struct BigRational include Comparable(BigDecimal) def <=>(other : BigDecimal) - to_big_d <=> other + -(other <=> self) end # Converts `self` to `BigDecimal`. diff --git a/src/big/big_int.cr b/src/big/big_int.cr index ebffe49da9ba..7e75d3a9ec77 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -433,8 +433,11 @@ struct BigInt < Int def **(other : Int) : BigInt if other < 0 raise ArgumentError.new("Negative exponent isn't supported") + elsif other == 1 + self + else + BigInt.new { |mpz| LibGMP.pow_ui(mpz, self, other) } end - BigInt.new { |mpz| LibGMP.pow_ui(mpz, self, other) } end # Returns the greatest common divisor of `self` and *other*. diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index 6ff0284502ea..d8975e637341 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -78,11 +78,16 @@ struct BigRational < Number end def numerator : BigInt - BigInt.new { |mpz| LibGMP.mpq_get_num(mpz, self) } + # Returns `LibGMP.mpq_numref(self)`, whose C macro expansion effectively + # produces a raw member access. This is only as safe as copying `BigInt`s by + # value, as both involve copying `LibGMP::MPZ` around which has reference + # semantics, and `BigInt`s cannot be safely mutated in-place this way; see + # #9825 for details. Ditto for `#denominator`. + BigInt.new(@mpq._mp_num) end def denominator : BigInt - BigInt.new { |mpz| LibGMP.mpq_get_den(mpz, self) } + BigInt.new(@mpq._mp_den) end def <=>(other : BigRational) From 33a74d1e74539b7290a42260d692be26a8d6717b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 19 Jun 2023 16:31:57 +0800 Subject: [PATCH 0589/1551] Split `LLVM::Builder` overloads that don't take an operand bundle (#13564) --- src/llvm/builder.cr | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index 3194f880c215..818316186532 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -89,7 +89,15 @@ class LLVM::Builder Value.new LibLLVMExt.build_call2(self, func.function_type, func, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, bundle, name) end - def call(type : LLVM::Type, func : LLVM::Function, args : Array(LLVM::Value), name : String = "", bundle : LLVM::OperandBundleDef = LLVM::OperandBundleDef.null) + def call(type : LLVM::Type, func : LLVM::Function, args : Array(LLVM::Value), name : String = "") + # check_type("call", type) + # check_func(func) + # check_values(args) + + Value.new LibLLVM.build_call2(self, type, func, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, name) + end + + def call(type : LLVM::Type, func : LLVM::Function, args : Array(LLVM::Value), name : String, bundle : LLVM::OperandBundleDef) # check_type("call", type) # check_func(func) # check_values(args) @@ -97,6 +105,10 @@ class LLVM::Builder Value.new LibLLVMExt.build_call2(self, type, func, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, bundle, name) end + def call(type : LLVM::Type, func : LLVM::Function, args : Array(LLVM::Value), bundle : LLVM::OperandBundleDef) + call(type, func, args, "", bundle) + end + def alloca(type, name = "") # check_type("alloca", type) @@ -281,7 +293,14 @@ class LLVM::Builder Value.new LibLLVMExt.build_invoke2 self, fn.function_type, fn, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, a_then, a_catch, bundle, name end - def invoke(type : LLVM::Type, fn : LLVM::Function, args : Array(LLVM::Value), a_then, a_catch, bundle : LLVM::OperandBundleDef = LLVM::OperandBundleDef.null, name = "") + def invoke(type : LLVM::Type, fn : LLVM::Function, args : Array(LLVM::Value), a_then, a_catch, *, name = "") + # check_type("invoke", type) + # check_func(fn) + + Value.new LibLLVM.build_invoke2 self, type, fn, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, a_then, a_catch, name + end + + def invoke(type : LLVM::Type, fn : LLVM::Function, args : Array(LLVM::Value), a_then, a_catch, bundle : LLVM::OperandBundleDef, name = "") # check_type("invoke", type) # check_func(fn) From a12dcf8399499af9a0c1fda5d9cff64fa27ddfa8 Mon Sep 17 00:00:00 2001 From: Philip Ross Date: Mon, 19 Jun 2023 11:07:50 -0700 Subject: [PATCH 0590/1551] Add compiler command `crystal clear_cache` (#13553) Co-authored-by: Sijawusz Pur Rahnama --- .../crystal/commands/clear_cache_spec.cr | 37 +++++++++++++++++++ src/compiler/crystal/command.cr | 4 ++ src/compiler/crystal/command/clear_cache.cr | 27 ++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 spec/compiler/crystal/commands/clear_cache_spec.cr create mode 100644 src/compiler/crystal/command/clear_cache.cr diff --git a/spec/compiler/crystal/commands/clear_cache_spec.cr b/spec/compiler/crystal/commands/clear_cache_spec.cr new file mode 100644 index 000000000000..f6f9180719c4 --- /dev/null +++ b/spec/compiler/crystal/commands/clear_cache_spec.cr @@ -0,0 +1,37 @@ +require "../../../spec_helper" + +class Crystal::CacheDir + class_setter instance + + def initialize(@dir) + Dir.mkdir_p(dir) + end +end + +describe Crystal::Command do + describe "clear_cache" do + around_each do |example| + old_cache_dir = CacheDir.instance + temp_dir_name = File.tempname + begin + CacheDir.instance = CacheDir.new(temp_dir_name) + example.run + ensure + FileUtils.rm_rf(temp_dir_name) + CacheDir.instance = old_cache_dir + end + end + + it "clears any cached compiler files" do + file_path = File.tempname(dir: CacheDir.instance.dir) + Dir.mkdir_p(File.dirname(file_path)) + File.touch(file_path) + File.exists?(file_path).should be_true + + Crystal::Command.run(["clear_cache"]) + + File.exists?(file_path).should be_false + File.exists?(CacheDir.instance.dir).should be_false + end + end +end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 273554728f66..2836cbdda88f 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -19,6 +19,7 @@ class Crystal::Command Command: init generate a new project build build an executable + clear_cache clear the compiler cache docs generate documentation env print Crystal environment information eval eval code from args or standard input @@ -112,6 +113,9 @@ class Crystal::Command when "tool".starts_with?(command) options.shift tool + when command == "clear_cache" + options.shift + clear_cache when "help".starts_with?(command), "--help" == command, "-h" == command puts USAGE exit diff --git a/src/compiler/crystal/command/clear_cache.cr b/src/compiler/crystal/command/clear_cache.cr new file mode 100644 index 000000000000..c2eadb7e77e8 --- /dev/null +++ b/src/compiler/crystal/command/clear_cache.cr @@ -0,0 +1,27 @@ +# Implementation of the `crystal clear_cache` command + +class Crystal::Command + private def clear_cache + verbose = false + OptionParser.parse(@options) do |opts| + opts.banner = <<-'BANNER' + Usage: crystal clear_cache + + Clears the compiler cache + + Options: + BANNER + + opts.on("-h", "--help", "Show this message") do + puts opts + exit + end + + opts.on("-v", "--verbose", "Display detailed information") do + verbose = true + end + end + puts "Clearing compiler cache at #{CacheDir.instance.dir.inspect}" if verbose + FileUtils.rm_rf(CacheDir.instance.dir) + end +end From ea473c962ff9a54894670a00764faea7a7af682f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 20 Jun 2023 17:18:30 +0800 Subject: [PATCH 0591/1551] Update `shell.nix` to nixpkgs-23.05 (#13571) --- shell.nix | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/shell.nix b/shell.nix index ed0468d2317e..6ead65a185a0 100644 --- a/shell.nix +++ b/shell.nix @@ -23,9 +23,9 @@ let nixpkgs = import (builtins.fetchTarball { - name = "nixpkgs-22.05"; - url = "https://github.com/NixOS/nixpkgs/archive/22.05.tar.gz"; - sha256 = "0d643wp3l77hv2pmg2fi7vyxn4rwy0iyr8djcw1h5x72315ck9ik"; + name = "nixpkgs-23.05"; + url = "https://github.com/NixOS/nixpkgs/archive/23.05.tar.gz"; + sha256 = "10wn0l08j9lgqcw8177nh2ljrnxdrpri7bp0g7nvrsn9rkawvlbf"; }) { inherit system; }; @@ -70,13 +70,21 @@ let pkgconfig = pkgs.pkgconfig; llvm_suite = ({ + llvm_16 = { + llvm = pkgs.llvm_16; + extra = [ pkgs.lld_16 pkgs.lldb_16 ]; + }; + llvm_15 = { + llvm = pkgs.llvm_15; + extra = [ pkgs.lld_15 pkgs.lldb_15 ]; + }; llvm_14 = { llvm = pkgs.llvm_14; - extra = [ pkgs.lld_14 ]; # lldb marked as broken + extra = [ pkgs.lld_14 pkgs.lldb_14 ]; }; llvm_13 = { llvm = pkgs.llvm_13; - extra = [ pkgs.lld_13 ]; # lldb marked as broken + extra = [ pkgs.lld_13 pkgs.lldb_13 ]; }; llvm_12 = { llvm = pkgs.llvm_12; @@ -88,7 +96,7 @@ let }; llvm_10 = { llvm = pkgs.llvm_10; - extra = [ pkgs.lld_10 pkgs.lldb_10 ]; + extra = [ pkgs.lld_10 ]; # lldb marked as broken }; llvm_9 = { llvm = pkgs.llvm_9; @@ -98,23 +106,15 @@ let llvm = pkgs.llvm_8; extra = [ pkgs.lld_8 ]; # lldb marked as broken }; - llvm_7 = { - llvm = pkgs.llvm_7; - extra = [ pkgs.lld_7 ]; # lldb it fails to compile on Darwin - }; - llvm_6 = { - llvm = pkgs.llvm_6; - extra = [ pkgs.lld_6 ]; # lldb it fails to compile on Darwin - }; }."llvm_${toString llvm}"); boehmgc = pkgs.stdenv.mkDerivation rec { pname = "boehm-gc"; - version = "8.2.0"; + version = "8.2.4"; src = builtins.fetchTarball { url = "https://github.com/ivmai/bdwgc/releases/download/v${version}/gc-${version}.tar.gz"; - sha256 = "0f3m27sfc4wssdvk32vivdg64b04ydw0slxm45zdv23qddrihxq4"; + sha256 = "0primpxl7hykfbmszf7ppbv7k1nj41f1r5m56n96q92mmzqlwybm"; }; configureFlags = [ From 0e821186d85c8a0142b496bbc884ab8d93ce36cd Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 20 Jun 2023 17:00:21 +0200 Subject: [PATCH 0592/1551] Add `Enum#to_i128` and `#to_u128` (#13576) --- spec/std/enum_spec.cr | 31 +++++++++++++++++++++++++++++++ src/enum.cr | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/spec/std/enum_spec.cr b/spec/std/enum_spec.cr index e4c67fad3a35..0b7c846138c2 100644 --- a/spec/std/enum_spec.cr +++ b/spec/std/enum_spec.cr @@ -124,9 +124,40 @@ describe Enum do it "gets value with to_i" do SpecEnum::Two.to_i.should eq(1) SpecEnum::Two.to_i.should be_a(Int32) + end + + it "gets value with to_i" do + SpecEnum::Two.to_i8.should eq(1) + SpecEnum::Two.to_i8.should be_a(Int8) + + SpecEnum::Two.to_i16.should eq(1) + SpecEnum::Two.to_i16.should be_a(Int16) + + SpecEnum::Two.to_i32.should eq(1) + SpecEnum::Two.to_i32.should be_a(Int32) SpecEnum::Two.to_i64.should eq(1) SpecEnum::Two.to_i64.should be_a(Int64) + + SpecEnum::Two.to_i128.should eq(1) + SpecEnum::Two.to_i128.should be_a(Int128) + end + + it "gets value with to_u" do + SpecEnum::Two.to_u8.should eq(1) + SpecEnum::Two.to_u8.should be_a(UInt8) + + SpecEnum::Two.to_u16.should eq(1) + SpecEnum::Two.to_u16.should be_a(UInt16) + + SpecEnum::Two.to_u32.should eq(1) + SpecEnum::Two.to_u32.should be_a(UInt32) + + SpecEnum::Two.to_u64.should eq(1) + SpecEnum::Two.to_u64.should be_a(UInt64) + + SpecEnum::Two.to_u128.should eq(1) + SpecEnum::Two.to_u128.should be_a(UInt128) end it "does +" do diff --git a/src/enum.cr b/src/enum.cr index d2ae191b7318..6ee61f3f38de 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -247,7 +247,7 @@ struct Enum value.to_i32 end - {% for name in %w(i8 i16 i32 i64 u8 u16 u32 u64 f32 f64) %} + {% for name in %w(i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64) %} {% prefix = name.starts_with?('i') ? "Int".id : (name.starts_with?('u') ? "UInt".id : "Float".id) %} {% type = "#{prefix}#{name[1..-1].id}".id %} # Returns the value of this enum member as a `{{type}}` From 9bbfb5117c78deb5ee00458a4db3fc99efa4b437 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Jun 2023 12:27:57 +0200 Subject: [PATCH 0593/1551] Update cachix/install-nix-action action to v22 (#13586) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 6d838395d4b8..be1674e163d2 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -17,7 +17,7 @@ jobs: - name: Download Crystal source uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v21 + - uses: cachix/install-nix-action@v22 with: install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | From 739461fe78c6ce65033f009af3e360750d5b020d Mon Sep 17 00:00:00 2001 From: Philip Ross Date: Wed, 21 Jun 2023 03:28:17 -0700 Subject: [PATCH 0594/1551] Add `Enumerable#partition` overload with type filter (#13572) --- spec/std/enumerable_spec.cr | 32 ++++++++++++++++++++++++++++++++ src/enumerable.cr | 23 +++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 435a056191e3..3b3cef2bd0c1 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -1049,6 +1049,38 @@ describe "Enumerable" do describe "partition" do it { [1, 2, 2, 3].partition { |x| x == 2 }.should eq({[2, 2], [1, 3]}) } it { [1, 2, 3, 4, 5, 6].partition(&.even?).should eq({[2, 4, 6], [1, 3, 5]}) } + + it "with mono type on union type" do + ints, others = [1, true, nil, 3, false, "string", 'c'].partition(Int32) + ints.should eq([1, 3]) + others.should eq([true, nil, false, "string", 'c']) + ints.should be_a(Array(Int32)) + others.should be_a(Array(Bool | String | Char | Nil)) + end + + it "with union type on union type" do + ints_bools, others = [1, true, nil, 3, false, "string", 'c'].partition(Int32 | Bool) + ints_bools.should eq([1, true, 3, false]) + others.should eq([nil, "string", 'c']) + ints_bools.should be_a(Array(Int32 | Bool)) + others.should be_a(Array(String | Char | Nil)) + end + + it "with missing type on union type" do + symbols, others = [1, true, nil, 3, false, "string", 'c'].partition(Symbol) + symbols.empty?.should be_true + others.should eq([1, true, nil, 3, false, "string", 'c']) + symbols.should be_a(Array(Symbol)) + others.should be_a(Array(Int32 | Bool | String | Char | Nil)) + end + + it "with mono type on mono type" do + ints, others = [1, 3].partition(Int32) + ints.should eq([1, 3]) + others.empty?.should be_true + ints.should be_a(Array(Int32)) + others.should be_a(Array(NoReturn)) + end end describe "reject" do diff --git a/src/enumerable.cr b/src/enumerable.cr index d7f978bd088d..7d95cb6f81b2 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -1441,6 +1441,29 @@ module Enumerable(T) {a, b} end + # Returns a `Tuple` with two arrays. The first one contains the elements + # in the collection that are of the given *type*, + # and the second one that are **not** of the given *type*. + # + # ``` + # ints, others = [1, true, nil, 3, false, "string", 'c'].partition(Int32) + # ints # => [1, 3] + # others # => [true, nil, false, "string", 'c'] + # typeof(ints) # => Array(Int32) + # typeof(others) # => Array(String | Char | Nil) + # ``` + def partition(type : U.class) forall U + a = [] of U + b = [] of typeof(begin + e = Enumerable.element_type(self) + e.is_a?(U) ? raise("") : e + end) + each do |e| + e.is_a?(U) ? a.push(e) : b.push(e) + end + {a, b} + end + # Returns an `Array` with all the elements in the collection for which # the passed block is falsey. # From df960cf765c53e6fbbba974a5e0087c64a81bd9c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 22 Jun 2023 01:40:26 +0800 Subject: [PATCH 0595/1551] Add `Dir::GlobOptions` to control `Dir.glob`'s behavior (#13550) --- spec/std/dir_spec.cr | 86 +++++++++++++-- src/crystal/system/dir.cr | 14 +-- src/crystal/system/unix/dir.cr | 6 +- src/crystal/system/wasi/dir.cr | 2 +- src/crystal/system/win32/dir.cr | 6 +- src/dir/glob.cr | 131 ++++++++++++++++++----- src/file.cr | 37 +++++++ src/lib_c/x86_64-windows-msvc/c/winnt.cr | 2 + 8 files changed, 244 insertions(+), 40 deletions(-) diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index 60e20cea581f..6fb95a9efcd3 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -25,6 +25,20 @@ private def unset_tempdir(&) {% end %} end +{% if flag?(:win32) %} + private def make_hidden(path) + wstr = Crystal::System.to_wstr(path) + attributes = LibC.GetFileAttributesW(wstr) + LibC.SetFileAttributesW(wstr, attributes | LibC::FILE_ATTRIBUTE_HIDDEN) + end + + private def make_system(path) + wstr = Crystal::System.to_wstr(path) + attributes = LibC.GetFileAttributesW(wstr) + LibC.SetFileAttributesW(wstr, attributes | LibC::FILE_ATTRIBUTE_SYSTEM) + end +{% end %} + private def it_raises_on_null_byte(operation, &block) it "errors on #{operation}" do expect_raises(ArgumentError, "String contains null byte") do @@ -438,26 +452,86 @@ describe "Dir" do ].sort end - context "match_hidden: true" do - it "matches hidden files" do + context "match: :dot_files / match_hidden" do + it "matches dot files" do + Dir.glob("#{datapath}/dir/dots/**/*", match: :dot_files).sort.should eq [ + datapath("dir", "dots", ".dot.hidden"), + datapath("dir", "dots", ".hidden"), + datapath("dir", "dots", ".hidden", "f1.txt"), + ].sort Dir.glob("#{datapath}/dir/dots/**/*", match_hidden: true).sort.should eq [ datapath("dir", "dots", ".dot.hidden"), datapath("dir", "dots", ".hidden"), datapath("dir", "dots", ".hidden", "f1.txt"), ].sort end - end - context "match_hidden: false" do it "ignores hidden files" do - Dir.glob("#{datapath}/dir/dots/*", match_hidden: false).size.should eq 0 + Dir.glob("#{datapath}/dir/dots/*", match: :none).should be_empty + Dir.glob("#{datapath}/dir/dots/*", match_hidden: false).should be_empty end it "ignores hidden files recursively" do - Dir.glob("#{datapath}/dir/dots/**/*", match_hidden: false).size.should eq 0 + Dir.glob("#{datapath}/dir/dots/**/*", match: :none).should be_empty + Dir.glob("#{datapath}/dir/dots/**/*", match_hidden: false).should be_empty end end + {% if flag?(:win32) %} + it "respects `NativeHidden` and `OSHidden`" do + with_tempfile("glob-system-hidden") do |path| + FileUtils.mkdir_p(path) + + visible_txt = File.join(path, "visible.txt") + hidden_txt = File.join(path, "hidden.txt") + system_txt = File.join(path, "system.txt") + system_hidden_txt = File.join(path, "system_hidden.txt") + + File.write(visible_txt, "") + File.write(hidden_txt, "") + File.write(system_txt, "") + File.write(system_hidden_txt, "") + make_hidden(hidden_txt) + make_hidden(system_hidden_txt) + make_system(system_txt) + make_system(system_hidden_txt) + + visible_dir = File.join(path, "visible_dir") + hidden_dir = File.join(path, "hidden_dir") + system_dir = File.join(path, "system_dir") + system_hidden_dir = File.join(path, "system_hidden_dir") + + Dir.mkdir(visible_dir) + Dir.mkdir(hidden_dir) + Dir.mkdir(system_dir) + Dir.mkdir(system_hidden_dir) + make_hidden(hidden_dir) + make_hidden(system_hidden_dir) + make_system(system_dir) + make_system(system_hidden_dir) + + inside_visible = File.join(visible_dir, "inside.txt") + inside_hidden = File.join(hidden_dir, "inside.txt") + inside_system = File.join(system_dir, "inside.txt") + inside_system_hidden = File.join(system_hidden_dir, "inside.txt") + + File.write(inside_visible, "") + File.write(inside_hidden, "") + File.write(inside_system, "") + File.write(inside_system_hidden, "") + + expected = [visible_txt, visible_dir, inside_visible, system_txt, system_dir, inside_system].sort! + expected_hidden = (expected + [hidden_txt, hidden_dir, inside_hidden]).sort! + expected_system_hidden = (expected_hidden + [system_hidden_txt, system_hidden_dir, inside_system_hidden]).sort! + + Dir.glob("#{path}/**/*", match: :none).sort.should eq(expected) + Dir.glob("#{path}/**/*", match: :native_hidden).sort.should eq(expected_hidden) + Dir.glob("#{path}/**/*", match: :os_hidden).sort.should eq(expected) + Dir.glob("#{path}/**/*", match: File::MatchOptions[NativeHidden, OSHidden]).sort.should eq(expected_system_hidden) + end + end + {% end %} + context "with path" do expected = [ datapath("dir", "f1.txt"), diff --git a/src/crystal/system/dir.cr b/src/crystal/system/dir.cr index eef9f330fbab..9d66a2653bc0 100644 --- a/src/crystal/system/dir.cr +++ b/src/crystal/system/dir.cr @@ -4,17 +4,19 @@ module Crystal::System::Dir # # Information about a directory entry. # - # In particular we only care about the name and whether its - # a directory or not to improve the performance of Dir.glob - # by avoid having to call File.info on every directory entry. + # In particular we only care about the name, whether it's a directory, and + # whether any hidden file attributes are set to improve the performance of + # `Dir.glob` by not having to call `File.info` on every directory entry. # If dir is nil, the type is unknown. # In the future we might change Dir's API to expose these entries # with more info but right now it's not necessary. struct Entry - getter name - getter? dir + getter name : String + getter? dir : Bool? + getter? native_hidden : Bool + getter? os_hidden : Bool - def initialize(@name : String, @dir : Bool?) + def initialize(@name, @dir, @native_hidden, @os_hidden = false) end end diff --git a/src/crystal/system/unix/dir.cr b/src/crystal/system/unix/dir.cr index e2faa53b4482..ddaeb34f9eea 100644 --- a/src/crystal/system/unix/dir.cr +++ b/src/crystal/system/unix/dir.cr @@ -19,7 +19,11 @@ module Crystal::System::Dir when LibC::DT_UNKNOWN, LibC::DT_LNK then nil else false end - Entry.new(name, dir) + + # TODO: support `st_flags & UF_HIDDEN` on BSD-like systems: https://man.freebsd.org/cgi/man.cgi?query=stat&sektion=2 + # TODO: support hidden file attributes on macOS / HFS+: https://stackoverflow.com/a/15236292 + # (are these the same?) + Entry.new(name, dir, false) elsif Errno.value != Errno::NONE raise ::File::Error.from_errno("Error reading directory entries", file: path) else diff --git a/src/crystal/system/wasi/dir.cr b/src/crystal/system/wasi/dir.cr index c44390ac5bb2..5d64a2f3aecd 100644 --- a/src/crystal/system/wasi/dir.cr +++ b/src/crystal/system/wasi/dir.cr @@ -56,7 +56,7 @@ module Crystal::System::Dir else false end - Entry.new(name, is_dir) + Entry.new(name, is_dir, false) end def self.rewind(dir) : Nil diff --git a/src/crystal/system/win32/dir.cr b/src/crystal/system/win32/dir.cr index 7e426be33207..1c3502efc269 100644 --- a/src/crystal/system/win32/dir.cr +++ b/src/crystal/system/win32/dir.cr @@ -55,9 +55,11 @@ module Crystal::System::Dir def self.data_to_entry(data) name = String.from_utf16(data.cFileName.to_unsafe)[0] unless data.dwFileAttributes.bits_set?(LibC::FILE_ATTRIBUTE_REPARSE_POINT) && data.dwReserved0 == LibC::IO_REPARSE_TAG_SYMLINK - dir = (data.dwFileAttributes & LibC::FILE_ATTRIBUTE_DIRECTORY) != 0 + dir = data.dwFileAttributes.bits_set?(LibC::FILE_ATTRIBUTE_DIRECTORY) end - Entry.new(name, dir) + native_hidden = data.dwFileAttributes.bits_set?(LibC::FILE_ATTRIBUTE_HIDDEN) + os_hidden = native_hidden && data.dwFileAttributes.bits_set?(LibC::FILE_ATTRIBUTE_SYSTEM) + Entry.new(name, dir, native_hidden, os_hidden) end def self.rewind(dir : DirHandle) : Nil diff --git a/src/dir/glob.cr b/src/dir/glob.cr index 088480873879..69efb55cc9ee 100644 --- a/src/dir/glob.cr +++ b/src/dir/glob.cr @@ -4,30 +4,76 @@ class Dir # The pattern syntax is similar to shell filename globbing, see `File.match?` for details. # # NOTE: Path separator in patterns needs to be always `/`. The returned file names use system-specific path separators. - def self.[](*patterns : Path | String, match_hidden = false, follow_symlinks = false) : Array(String) - glob(patterns, match_hidden: match_hidden, follow_symlinks: follow_symlinks) + def self.[](*patterns : Path | String, match : File::MatchOptions = File::MatchOptions.glob_default, follow_symlinks : Bool = false) : Array(String) + glob(patterns, match: match, follow_symlinks: follow_symlinks) end # :ditto: - def self.[](patterns : Enumerable, match_hidden = false, follow_symlinks = false) : Array(String) - glob(patterns, match_hidden: match_hidden, follow_symlinks: follow_symlinks) + def self.[](patterns : Enumerable, match : File::MatchOptions = File::MatchOptions.glob_default, follow_symlinks : Bool = false) : Array(String) + glob(patterns, match: match, follow_symlinks: follow_symlinks) + end + + # :ditto: + # + # For compatibility, a falsey *match_hidden* argument is equivalent to passing + # `match: File::MatchOptions.glob_default`, and a truthy *match_hidden* is + # equivalent to + # `match: File::MatchOptions.glob_default | File::MatchOptions::DotFiles`. + @[Deprecated("Use the overload with a `match` parameter instead")] + def self.[](*patterns : Path | String, match_hidden, follow_symlinks = false) : Array(String) + glob(patterns, match: match_hidden_to_options(match_hidden), follow_symlinks: follow_symlinks) + end + + # :ditto: + # + # For compatibility, a falsey *match_hidden* argument is equivalent to passing + # `match: File::MatchOptions.glob_default`, and a truthy *match_hidden* is + # equivalent to + # `match: File::MatchOptions.glob_default | File::MatchOptions::DotFiles`. + @[Deprecated("Use the overload with a `match` parameter instead")] + def self.[](patterns : Enumerable, match_hidden, follow_symlinks = false) : Array(String) + glob(patterns, match: match_hidden_to_options(match_hidden), follow_symlinks: follow_symlinks) end # Returns an array of all files that match against any of *patterns*. # # The pattern syntax is similar to shell filename globbing, see `File.match?` for details. # - # If *match_hidden* is `true` the pattern will match hidden files and folders. - # # NOTE: Path separator in patterns needs to be always `/`. The returned file names use system-specific path separators. - def self.glob(*patterns : Path | String, match_hidden = false, follow_symlinks = false) : Array(String) - glob(patterns, match_hidden: match_hidden, follow_symlinks: follow_symlinks) + def self.glob(*patterns : Path | String, match : File::MatchOptions = File::MatchOptions.glob_default, follow_symlinks : Bool = false) : Array(String) + glob(patterns, match: match, follow_symlinks: follow_symlinks) end # :ditto: - def self.glob(patterns : Enumerable, match_hidden = false, follow_symlinks = false) : Array(String) + def self.glob(patterns : Enumerable, match : File::MatchOptions = File::MatchOptions.glob_default, follow_symlinks : Bool = false) : Array(String) paths = [] of String - glob(patterns, match_hidden: match_hidden, follow_symlinks: follow_symlinks) do |path| + glob(patterns, match: match, follow_symlinks: follow_symlinks) do |path| + paths << path + end + paths + end + + # :ditto: + # + # For compatibility, a falsey *match_hidden* argument is equivalent to passing + # `match: File::MatchOptions.glob_default`, and a truthy *match_hidden* is + # equivalent to + # `match: File::MatchOptions.glob_default | File::MatchOptions::DotFiles`. + @[Deprecated("Use the overload with a `match` parameter instead")] + def self.glob(*patterns : Path | String, match_hidden, follow_symlinks = false) : Array(String) + glob(patterns, match: match_hidden_to_options(match_hidden), follow_symlinks: follow_symlinks) + end + + # :ditto: + # + # For compatibility, a falsey *match_hidden* argument is equivalent to passing + # `match: File::MatchOptions.glob_default`, and a truthy *match_hidden* is + # equivalent to + # `match: File::MatchOptions.glob_default | File::MatchOptions::DotFiles`. + @[Deprecated("Use the overload with a `match` parameter instead")] + def self.glob(patterns : Enumerable, match_hidden, follow_symlinks = false) : Array(String) + paths = [] of String + glob(patterns, match: match_hidden_to_options(match_hidden), follow_symlinks: follow_symlinks) do |path| paths << path end paths @@ -37,22 +83,52 @@ class Dir # # The pattern syntax is similar to shell filename globbing, see `File.match?` for details. # - # If *match_hidden* is `true` the pattern will match hidden files and folders. - # # NOTE: Path separator in patterns needs to be always `/`. The returned file names use system-specific path separators. - def self.glob(*patterns : Path | String, match_hidden = false, follow_symlinks = false, &block : String -> _) - glob(patterns, match_hidden: match_hidden, follow_symlinks: follow_symlinks) do |path| + def self.glob(*patterns : Path | String, match : File::MatchOptions = File::MatchOptions.glob_default, follow_symlinks : Bool = false, &block : String -> _) + glob(patterns, match: match, follow_symlinks: follow_symlinks) do |path| + yield path + end + end + + # :ditto: + def self.glob(patterns : Enumerable, match : File::MatchOptions = File::MatchOptions.glob_default, follow_symlinks : Bool = false, &block : String -> _) + Globber.glob(patterns, match: match, follow_symlinks: follow_symlinks) do |path| yield path end end # :ditto: - def self.glob(patterns : Enumerable, match_hidden = false, follow_symlinks = false, &block : String -> _) - Globber.glob(patterns, match_hidden: match_hidden, follow_symlinks: follow_symlinks) do |path| + # + # For compatibility, a falsey *match_hidden* argument is equivalent to passing + # `match: File::MatchOptions.glob_default`, and a truthy *match_hidden* is + # equivalent to + # `match: File::MatchOptions.glob_default | File::MatchOptions::DotFiles`. + @[Deprecated("Use the overload with a `match` parameter instead")] + def self.glob(*patterns : Path | String, match_hidden, follow_symlinks = false, &block : String -> _) + glob(patterns, match: match_hidden_to_options(match_hidden), follow_symlinks: follow_symlinks) do |path| yield path end end + # :ditto: + # + # For compatibility, a falsey *match_hidden* argument is equivalent to passing + # `match: File::MatchOptions.glob_default`, and a truthy *match_hidden* is + # equivalent to + # `match: File::MatchOptions.glob_default | File::MatchOptions::DotFiles`. + @[Deprecated("Use the overload with a `match` parameter instead")] + def self.glob(patterns : Enumerable, match_hidden, follow_symlinks = false, &block : String -> _) + Globber.glob(patterns, match: match_hidden_to_options(match_hidden), follow_symlinks: follow_symlinks) do |path| + yield path + end + end + + private def self.match_hidden_to_options(match_hidden) + options = File::MatchOptions.glob_default + options |= File::MatchOptions::DotFiles if match_hidden + options + end + # :nodoc: module Globber record DirectoriesOnly @@ -72,7 +148,7 @@ class Dir end alias PatternType = DirectoriesOnly | ConstantEntry | EntryMatch | RecursiveDirectories | ConstantDirectory | RootDirectory | DirectoryMatch - def self.glob(patterns : Enumerable, *, match_hidden, follow_symlinks, &block : String -> _) + def self.glob(patterns : Enumerable, *, match, follow_symlinks, &block : String -> _) patterns.each do |pattern| if pattern.is_a?(Path) pattern = pattern.to_posix.to_s @@ -81,11 +157,11 @@ class Dir sequences.each do |sequence| if sequence.count(&.is_a?(RecursiveDirectories)) > 1 - run_tracking(sequence, match_hidden: match_hidden, follow_symlinks: follow_symlinks) do |match| + run_tracking(sequence, match: match, follow_symlinks: follow_symlinks) do |match| yield match end else - run(sequence, match_hidden: match_hidden, follow_symlinks: follow_symlinks) do |match| + run(sequence, match: match, follow_symlinks: follow_symlinks) do |match| yield match end end @@ -153,17 +229,17 @@ class Dir true end - private def self.run_tracking(sequence, match_hidden, follow_symlinks, &block : String -> _) + private def self.run_tracking(sequence, match, follow_symlinks, &block : String -> _) result_tracker = Set(String).new - run(sequence, match_hidden, follow_symlinks) do |result| + run(sequence, match, follow_symlinks) do |result| if result_tracker.add?(result) yield result end end end - private def self.run(sequence, match_hidden, follow_symlinks, &block : String -> _) + private def self.run(sequence, match, follow_symlinks, &block : String -> _) return if sequence.empty? path_stack = [] of Tuple(Int32, String?, Crystal::System::Dir::Entry?) @@ -195,7 +271,7 @@ class Dir in EntryMatch next if sequence[pos + 1]?.is_a?(RecursiveDirectories) each_child(path) do |entry| - next if !match_hidden && entry.name.starts_with?('.') + next unless matches_file?(entry, match) yield join(path, entry.name) if cmd.matches?(entry.name) end in DirectoryMatch @@ -255,7 +331,7 @@ class Dir if entry = read_entry(dir) next if entry.name.in?(".", "..") - next if !match_hidden && entry.name.starts_with?('.') + next unless matches_file?(entry, match) if dir_path.bytesize == 0 fullpath = entry.name @@ -340,5 +416,12 @@ class Dir # call File.info? which is really expensive. Crystal::System::Dir.next_entry(dir.@dir, dir.path) end + + private def self.matches_file?(entry, match) + return false if entry.name.starts_with?('.') && !match.dot_files? + return false if entry.native_hidden? && !match.native_hidden? + return false if entry.os_hidden? && !match.os_hidden? + true + end end end diff --git a/src/file.cr b/src/file.cr index 6875f51397b2..7092abb13648 100644 --- a/src/file.cr +++ b/src/file.cr @@ -85,6 +85,43 @@ class File < IO::FileDescriptor "/dev/null" {% end %} + # Options used to control the behavior of `Dir.glob`. + @[Flags] + enum MatchOptions + # Includes files whose name begins with a period (`.`). + DotFiles + + # Includes files which have a hidden attribute backed by the native + # filesystem. + # + # On Windows, this matches files that have the NTFS hidden attribute set. + # This option alone doesn't match files with _both_ the hidden and the + # system attributes, `OSHidden` must also be used. + # + # On other systems, this has no effect. + NativeHidden + + # Includes files which are considered hidden by operating system + # conventions (apart from `DotFiles`), but not by the filesystem. + # + # On Windows, this option alone has no effect. However, combining it with + # `NativeHidden` matches files that have both the NTFS hidden and system + # attributes set. Note that files with just the system attribute, but not + # the hidden attribute, are always matched regardless of this option or + # `NativeHidden`. + # + # On other systems, this has no effect. + OSHidden + + # Returns a suitable platform-specific default set of options for + # `Dir.glob` and `Dir.[]`. + # + # Currently this is always `NativeHidden | OSHidden`. + def self.glob_default + NativeHidden | OSHidden + end + end + include Crystal::System::File # This constructor is provided for subclasses to be able to initialize an diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index 0c7ceae716f3..9496f051a6a4 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -16,8 +16,10 @@ lib LibC INVALID_FILE_ATTRIBUTES = DWORD.new!(-1) FILE_ATTRIBUTE_DIRECTORY = 0x10 + FILE_ATTRIBUTE_HIDDEN = 0x2 FILE_ATTRIBUTE_READONLY = 0x1 FILE_ATTRIBUTE_REPARSE_POINT = 0x400 + FILE_ATTRIBUTE_SYSTEM = 0x4 FILE_APPEND_DATA = 0x00000004 From dfb1cdc3411512b9059b3f3652ce27f44958121a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 23 Jun 2023 00:44:15 +0800 Subject: [PATCH 0596/1551] Fix instantiated method signatures in error traces (#13580) --- spec/compiler/semantic/call_error_spec.cr | 255 ++++++++++++++++++++ src/compiler/crystal/semantic/call.cr | 21 +- src/compiler/crystal/semantic/call_error.cr | 45 +++- 3 files changed, 310 insertions(+), 11 deletions(-) diff --git a/spec/compiler/semantic/call_error_spec.cr b/spec/compiler/semantic/call_error_spec.cr index ea4fcf85234e..01c93c795f7e 100644 --- a/spec/compiler/semantic/call_error_spec.cr +++ b/spec/compiler/semantic/call_error_spec.cr @@ -293,4 +293,259 @@ describe "Call errors" do ), "expected argument #2 to 'foo' to be Int32, not (Int32 | Nil)" end + + describe "method signatures in error traces" do + it "includes named argument" do + assert_error <<-CRYSTAL, "instantiating 'bar(y: Int32)'" + def foo(x) + end + + def bar(**opts) + foo + end + + bar(y: 1) + CRYSTAL + end + + it "includes named arguments" do + assert_error <<-CRYSTAL, "instantiating 'bar(y: Int32, z: String)'" + def foo(x) + end + + def bar(**opts) + foo + end + + bar(y: 1, z: "") + CRYSTAL + end + + it "includes positional and named argument" do + assert_error <<-CRYSTAL, "instantiating 'bar(Int32, y: String)'" + def foo(x) + end + + def bar(*args, **opts) + foo + end + + bar(1, y: "") + CRYSTAL + end + + it "expands single splat argument" do + assert_error <<-CRYSTAL, "instantiating 'bar(Int32)'" + def foo(x) + end + + def bar(*args) + foo + end + + bar(*{1}) + CRYSTAL + end + + it "expands single splat argument, more elements" do + assert_error <<-CRYSTAL, "instantiating 'bar(Int32, String)'" + def foo(x) + end + + def bar(*args) + foo + end + + bar(*{1, ""}) + CRYSTAL + end + + it "expands single splat argument, empty tuple" do + assert_error <<-CRYSTAL, "instantiating 'bar()'" + #{tuple_new} + + def foo(x) + end + + def bar(*args) + foo + end + + bar(*Tuple.new) + CRYSTAL + end + + it "expands positional and single splat argument" do + assert_error <<-CRYSTAL, "instantiating 'bar(Int32, String)'" + def foo(x) + end + + def bar(*args) + foo + end + + bar(1, *{""}) + CRYSTAL + end + + it "expands positional and single splat argument, more elements" do + assert_error <<-CRYSTAL, "instantiating 'bar(Int32, String, Bool)'" + def foo(x) + end + + def bar(*args) + foo + end + + bar(1, *{"", true}) + CRYSTAL + end + + it "expands positional and single splat argument, empty tuple" do + assert_error <<-CRYSTAL, "instantiating 'bar(Int32)'" + #{tuple_new} + + def foo(x) + end + + def bar(*args) + foo + end + + bar(1, *Tuple.new) + CRYSTAL + end + + it "expands double splat argument" do + assert_error <<-CRYSTAL, "instantiating 'bar(y: Int32)'" + def foo(x) + end + + def bar(**opts) + foo + end + + bar(**{y: 1}) + CRYSTAL + end + + it "expands double splat argument, more elements" do + assert_error <<-CRYSTAL, "instantiating 'bar(y: Int32, z: String)'" + def foo(x) + end + + def bar(**opts) + foo + end + + bar(**{y: 1, z: ""}) + CRYSTAL + end + + it "expands double splat argument, empty named tuple" do + assert_error <<-CRYSTAL, "instantiating 'bar()'" + #{named_tuple_new} + + def foo(x) + end + + def bar(**opts) + foo + end + + bar(**NamedTuple.new) + CRYSTAL + end + + it "expands positional and double splat argument" do + assert_error <<-CRYSTAL, "instantiating 'bar(Int32, y: String)'" + def foo(x) + end + + def bar(*args, **opts) + foo + end + + bar(1, **{y: ""}) + CRYSTAL + end + + it "expands positional and double splat argument, more elements" do + assert_error <<-CRYSTAL, "instantiating 'bar(Int32, y: String, z: Bool)'" + def foo(x) + end + + def bar(*args, **opts) + foo + end + + bar(1, **{y: "", z: true}) + CRYSTAL + end + + it "expands positional and double splat argument, empty named tuple" do + assert_error <<-CRYSTAL, "instantiating 'bar(Int32)'" + #{named_tuple_new} + + def foo(x) + end + + def bar(*args, **opts) + foo + end + + bar(1, **NamedTuple.new) + CRYSTAL + end + + it "uses `T.method` instead of `T.class#method`" do + assert_error <<-CRYSTAL, "instantiating 'Bar.bar()'" + def foo(x) + end + + class Bar + def self.bar + foo + end + end + + Bar.bar + CRYSTAL + end + + it "uses `T.method` instead of `T:module#method`" do + assert_error <<-CRYSTAL, "instantiating 'Bar.bar()'" + def foo(x) + end + + module Bar + def self.bar + foo + end + end + + Bar.bar + CRYSTAL + end + end +end + +private def tuple_new + <<-CRYSTAL + struct Tuple + def self.new(*args) + args + end + end + CRYSTAL +end + +private def named_tuple_new + <<-CRYSTAL + struct NamedTuple + def self.new(**opts) + opts + end + end + CRYSTAL end diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index 9e8b038b4f8c..8ba058c8c4ad 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -1112,17 +1112,18 @@ class Crystal::Call # This will insert this node into the trace as the new first frame. self.raise ex.message, ex, exception_type: Crystal::MacroRaiseException rescue ex : Crystal::CodeError - if obj = @obj - if name == "initialize" - # Avoid putting 'initialize' in the error trace - # because it's most likely that this is happening - # inside a generated 'new' method - ::raise ex - else - raise "instantiating '#{obj.type}##{name}(#{args.map(&.type).join ", "})'", ex - end + if (obj = @obj) && name == "initialize" + # Avoid putting 'initialize' in the error trace + # because it's most likely that this is happening + # inside a generated 'new' method + ::raise ex else - raise "instantiating '#{name}(#{args.map(&.type).join ", "})'", ex + msg = String.build do |io| + io << "instantiating '" + signature(io) + io << "'" + end + raise msg, ex end end diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index e62ce819dd88..89136be3dc16 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -1005,7 +1005,7 @@ class Crystal::Call def self.full_name(owner, method_name = name) case owner - when Program + when Program, Nil method_name when owner.program.class_type # Class's instance_type is Object, not Class, so we cannot treat it like @@ -1018,6 +1018,49 @@ class Crystal::Call end end + def signature(io : IO) : Nil + io << full_name(obj.try(&.type)) << '(' + + first = true + args.each do |arg| + case {arg_type = arg.type, arg} + when {TupleInstanceType, Splat} + next if arg_type.tuple_types.empty? + io << ", " unless first + arg_type.tuple_types.join(io, ", ") + when {NamedTupleInstanceType, DoubleSplat} + next if arg_type.entries.empty? + io << ", " unless first + arg_type.entries.join(io, ", ") do |entry| + if Symbol.needs_quotes_for_named_argument?(entry.name) + entry.name.inspect(io) + else + io << entry.name + end + io << ": " << entry.type + end + else + io << ", " unless first + io << arg.type + end + first = false + end + + if named_args = @named_args + io << ", " unless first + named_args.join(io, ", ") do |named_arg| + if Symbol.needs_quotes_for_named_argument?(named_arg.name) + named_arg.name.inspect(io) + else + io << named_arg.name + end + io << ": " << named_arg.value.type + end + end + + io << ')' + end + private def colorize(obj) program.colorize(obj) end From 78b2d35dfefed2d133b7576ae1cd81cba1d9cd4a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 23 Jun 2023 00:44:30 +0800 Subject: [PATCH 0597/1551] Deprecate LLVM's legacy pass manager (#13579) --- src/compiler/crystal/compiler.cr | 68 +++++++++++++---------------- src/llvm/function_pass_manager.cr | 6 +++ src/llvm/lib_llvm.cr | 72 +++++++++++++++++-------------- src/llvm/module.cr | 3 ++ src/llvm/module_pass_manager.cr | 3 ++ src/llvm/pass_manager_builder.cr | 3 ++ src/llvm/pass_registry.cr | 3 ++ 7 files changed, 89 insertions(+), 69 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 5e91c2f60127..f79df429dea8 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -586,51 +586,45 @@ module Crystal exit 1 end - protected def optimize(llvm_mod) - {% if LibLLVM::IS_LT_130 %} - optimize_with_old_pass_manager(llvm_mod) - {% else %} - optimize_with_new_pass_manager(llvm_mod) - {% end %} - end - - private def optimize_with_old_pass_manager(llvm_mod) - fun_pass_manager = llvm_mod.new_function_pass_manager - pass_manager_builder.populate fun_pass_manager - fun_pass_manager.run llvm_mod - module_pass_manager.run llvm_mod - end - - private def optimize_with_new_pass_manager(llvm_mod) - LLVM::PassBuilderOptions.new do |options| - LLVM.run_passes(llvm_mod, "default", target_machine, options) + {% if LibLLVM::IS_LT_130 %} + protected def optimize(llvm_mod) + fun_pass_manager = llvm_mod.new_function_pass_manager + pass_manager_builder.populate fun_pass_manager + fun_pass_manager.run llvm_mod + module_pass_manager.run llvm_mod end - end - @module_pass_manager : LLVM::ModulePassManager? + @module_pass_manager : LLVM::ModulePassManager? - private def module_pass_manager - @module_pass_manager ||= begin - mod_pass_manager = LLVM::ModulePassManager.new - pass_manager_builder.populate mod_pass_manager - mod_pass_manager + private def module_pass_manager + @module_pass_manager ||= begin + mod_pass_manager = LLVM::ModulePassManager.new + pass_manager_builder.populate mod_pass_manager + mod_pass_manager + end end - end - @pass_manager_builder : LLVM::PassManagerBuilder? + @pass_manager_builder : LLVM::PassManagerBuilder? - private def pass_manager_builder - @pass_manager_builder ||= begin - registry = LLVM::PassRegistry.instance - registry.initialize_all + private def pass_manager_builder + @pass_manager_builder ||= begin + registry = LLVM::PassRegistry.instance + registry.initialize_all - builder = LLVM::PassManagerBuilder.new - builder.opt_level = 3 - builder.size_level = 0 - builder.use_inliner_with_threshold = 275 - builder + builder = LLVM::PassManagerBuilder.new + builder.opt_level = 3 + builder.size_level = 0 + builder.use_inliner_with_threshold = 275 + builder + end end - end + {% else %} + protected def optimize(llvm_mod) + LLVM::PassBuilderOptions.new do |options| + LLVM.run_passes(llvm_mod, "default", target_machine, options) + end + end + {% end %} private def run_linker(linker_name, command, args) print_command(command, args) if verbose? diff --git a/src/llvm/function_pass_manager.cr b/src/llvm/function_pass_manager.cr index 50095838626e..620f24420a2e 100644 --- a/src/llvm/function_pass_manager.cr +++ b/src/llvm/function_pass_manager.cr @@ -1,3 +1,6 @@ +{% unless LibLLVM::IS_LT_130 %} + @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] +{% end %} class LLVM::FunctionPassManager def initialize(@unwrap : LibLLVM::PassManagerRef) end @@ -31,6 +34,9 @@ class LLVM::FunctionPassManager LibLLVM.dispose_pass_manager(@unwrap) end + {% unless LibLLVM::IS_LT_130 %} + @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] + {% end %} struct Runner @fpm : FunctionPassManager diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index e4b1f5a9efbd..b35ca99af2e0 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -19,6 +19,8 @@ end {% begin %} lib LibLLVM + IS_170 = {{LibLLVM::VERSION.starts_with?("17.0")}} + IS_160 = {{LibLLVM::VERSION.starts_with?("16.0")}} IS_150 = {{LibLLVM::VERSION.starts_with?("15.0")}} IS_140 = {{LibLLVM::VERSION.starts_with?("14.0")}} IS_130 = {{LibLLVM::VERSION.starts_with?("13.0")}} @@ -37,6 +39,7 @@ end IS_LT_140 = {{compare_versions(LibLLVM::VERSION, "14.0.0") < 0}} IS_LT_150 = {{compare_versions(LibLLVM::VERSION, "15.0.0") < 0}} IS_LT_160 = {{compare_versions(LibLLVM::VERSION, "16.0.0") < 0}} + IS_LT_170 = {{compare_versions(LibLLVM::VERSION, "17.0.0") < 0}} end {% end %} @@ -58,9 +61,6 @@ lib LibLLVM type TargetRef = Void* type TargetDataRef = Void* type TargetMachineRef = Void* - type PassManagerBuilderRef = Void* - type PassManagerRef = Void* - type PassRegistryRef = Void* type MemoryBufferRef = Void* type PassBuilderOptionsRef = Void* type ErrorRef = Void* @@ -238,24 +238,10 @@ lib LibLLVM fun is_function_var_arg = LLVMIsFunctionVarArg(ty : TypeRef) : Int32 fun module_create_with_name_in_context = LLVMModuleCreateWithNameInContext(module_id : UInt8*, context : ContextRef) : ModuleRef fun offset_of_element = LLVMOffsetOfElement(td : TargetDataRef, struct_type : TypeRef, element : LibC::UInt) : UInt64 - fun pass_manager_builder_create = LLVMPassManagerBuilderCreate : PassManagerBuilderRef - fun pass_manager_builder_set_opt_level = LLVMPassManagerBuilderSetOptLevel(builder : PassManagerBuilderRef, opt_level : UInt32) - fun pass_manager_builder_set_size_level = LLVMPassManagerBuilderSetSizeLevel(builder : PassManagerBuilderRef, size_level : UInt32) - fun pass_manager_builder_set_disable_unroll_loops = LLVMPassManagerBuilderSetDisableUnrollLoops(builder : PassManagerBuilderRef, value : Int32) - fun pass_manager_builder_set_disable_simplify_lib_calls = LLVMPassManagerBuilderSetDisableSimplifyLibCalls(builder : PassManagerBuilderRef, value : Int32) - fun pass_manager_builder_use_inliner_with_threshold = LLVMPassManagerBuilderUseInlinerWithThreshold(builder : PassManagerBuilderRef, threshold : UInt32) - fun pass_manager_builder_populate_function_pass_manager = LLVMPassManagerBuilderPopulateFunctionPassManager(builder : PassManagerBuilderRef, pm : PassManagerRef) - fun pass_manager_builder_populate_module_pass_manager = LLVMPassManagerBuilderPopulateModulePassManager(builder : PassManagerBuilderRef, pm : PassManagerRef) - fun pass_manager_create = LLVMCreatePassManager : PassManagerRef - fun create_function_pass_manager_for_module = LLVMCreateFunctionPassManagerForModule(mod : ModuleRef) : PassManagerRef fun pointer_type = LLVMPointerType(element_type : TypeRef, address_space : UInt32) : TypeRef fun position_builder_at_end = LLVMPositionBuilderAtEnd(builder : BuilderRef, block : BasicBlockRef) fun print_module_to_file = LLVMPrintModuleToFile(m : ModuleRef, filename : UInt8*, error_msg : UInt8**) : Int32 fun run_function = LLVMRunFunction(ee : ExecutionEngineRef, f : ValueRef, num_args : Int32, args : GenericValueRef*) : GenericValueRef - fun run_pass_manager = LLVMRunPassManager(pm : PassManagerRef, m : ModuleRef) : Int32 - fun initialize_function_pass_manager = LLVMInitializeFunctionPassManager(fpm : PassManagerRef) : Int32 - fun run_function_pass_manager = LLVMRunFunctionPassManager(fpm : PassManagerRef, f : ValueRef) : Int32 - fun finalize_function_pass_manager = LLVMFinalizeFunctionPassManager(fpm : PassManagerRef) : Int32 fun set_cleanup = LLVMSetCleanup(lpad : ValueRef, val : Int32) fun set_global_constant = LLVMSetGlobalConstant(global : ValueRef, is_constant : Int32) fun is_global_constant = LLVMIsGlobalConstant(global : ValueRef) : Int32 @@ -285,19 +271,6 @@ lib LibLLVM fun get_next_function = LLVMGetNextFunction(f : ValueRef) : ValueRef fun get_next_basic_block = LLVMGetNextBasicBlock(bb : BasicBlockRef) : BasicBlockRef fun get_next_instruction = LLVMGetNextInstruction(inst : ValueRef) : ValueRef - fun get_global_pass_registry = LLVMGetGlobalPassRegistry : PassRegistryRef - fun initialize_core = LLVMInitializeCore(r : PassRegistryRef) - fun initialize_transform_utils = LLVMInitializeTransformUtils(r : PassRegistryRef) - fun initialize_scalar_opts = LLVMInitializeScalarOpts(r : PassRegistryRef) - fun initialize_obj_c_arc_opts = LLVMInitializeObjCARCOpts(r : PassRegistryRef) - fun initialize_vectorization = LLVMInitializeVectorization(r : PassRegistryRef) - fun initialize_inst_combine = LLVMInitializeInstCombine(r : PassRegistryRef) - fun initialize_ipo = LLVMInitializeIPO(r : PassRegistryRef) - fun initialize_instrumentation = LLVMInitializeInstrumentation(r : PassRegistryRef) - fun initialize_analysis = LLVMInitializeAnalysis(r : PassRegistryRef) - fun initialize_ipa = LLVMInitializeIPA(r : PassRegistryRef) - fun initialize_code_gen = LLVMInitializeCodeGen(r : PassRegistryRef) - fun initialize_target = LLVMInitializeTarget(r : PassRegistryRef) fun get_next_target = LLVMGetNextTarget(t : TargetRef) : TargetRef fun get_default_target_triple = LLVMGetDefaultTargetTriple : UInt8* fun print_module_to_string = LLVMPrintModuleToString(mod : ModuleRef) : UInt8* @@ -330,9 +303,7 @@ lib LibLLVM fun dispose_generic_value = LLVMDisposeGenericValue(GenericValueRef) fun dispose_execution_engine = LLVMDisposeExecutionEngine(ExecutionEngineRef) fun dispose_context = LLVMContextDispose(ContextRef) - fun dispose_pass_manager = LLVMDisposePassManager(PassManagerRef) fun dispose_target_data = LLVMDisposeTargetData(TargetDataRef) - fun dispose_pass_manager_builder = LLVMPassManagerBuilderDispose(PassManagerBuilderRef) fun set_volatile = LLVMSetVolatile(value : ValueRef, volatile : UInt32) fun set_alignment = LLVMSetAlignment(value : ValueRef, bytes : UInt32) fun get_return_type = LLVMGetReturnType(TypeRef) : TypeRef @@ -536,4 +507,41 @@ lib LibLLVM fun set_subprogram = LLVMSetSubprogram(func : ValueRef, sp : MetadataRef) fun metadata_replace_all_uses_with = LLVMMetadataReplaceAllUsesWith(target_metadata : MetadataRef, replacement : MetadataRef) + + {% if LibLLVM::IS_LT_170 %} + type PassManagerRef = Void* + fun pass_manager_create = LLVMCreatePassManager : PassManagerRef + fun create_function_pass_manager_for_module = LLVMCreateFunctionPassManagerForModule(mod : ModuleRef) : PassManagerRef + fun run_pass_manager = LLVMRunPassManager(pm : PassManagerRef, m : ModuleRef) : Int32 + fun initialize_function_pass_manager = LLVMInitializeFunctionPassManager(fpm : PassManagerRef) : Int32 + fun run_function_pass_manager = LLVMRunFunctionPassManager(fpm : PassManagerRef, f : ValueRef) : Int32 + fun finalize_function_pass_manager = LLVMFinalizeFunctionPassManager(fpm : PassManagerRef) : Int32 + fun dispose_pass_manager = LLVMDisposePassManager(PassManagerRef) + + type PassRegistryRef = Void* + fun get_global_pass_registry = LLVMGetGlobalPassRegistry : PassRegistryRef + fun initialize_core = LLVMInitializeCore(r : PassRegistryRef) + fun initialize_transform_utils = LLVMInitializeTransformUtils(r : PassRegistryRef) + fun initialize_scalar_opts = LLVMInitializeScalarOpts(r : PassRegistryRef) + fun initialize_obj_c_arc_opts = LLVMInitializeObjCARCOpts(r : PassRegistryRef) + fun initialize_vectorization = LLVMInitializeVectorization(r : PassRegistryRef) + fun initialize_inst_combine = LLVMInitializeInstCombine(r : PassRegistryRef) + fun initialize_ipo = LLVMInitializeIPO(r : PassRegistryRef) + fun initialize_instrumentation = LLVMInitializeInstrumentation(r : PassRegistryRef) + fun initialize_analysis = LLVMInitializeAnalysis(r : PassRegistryRef) + fun initialize_ipa = LLVMInitializeIPA(r : PassRegistryRef) + fun initialize_code_gen = LLVMInitializeCodeGen(r : PassRegistryRef) + fun initialize_target = LLVMInitializeTarget(r : PassRegistryRef) + + type PassManagerBuilderRef = Void* + fun pass_manager_builder_create = LLVMPassManagerBuilderCreate : PassManagerBuilderRef + fun pass_manager_builder_set_opt_level = LLVMPassManagerBuilderSetOptLevel(builder : PassManagerBuilderRef, opt_level : UInt32) + fun pass_manager_builder_set_size_level = LLVMPassManagerBuilderSetSizeLevel(builder : PassManagerBuilderRef, size_level : UInt32) + fun pass_manager_builder_set_disable_unroll_loops = LLVMPassManagerBuilderSetDisableUnrollLoops(builder : PassManagerBuilderRef, value : Int32) + fun pass_manager_builder_set_disable_simplify_lib_calls = LLVMPassManagerBuilderSetDisableSimplifyLibCalls(builder : PassManagerBuilderRef, value : Int32) + fun pass_manager_builder_use_inliner_with_threshold = LLVMPassManagerBuilderUseInlinerWithThreshold(builder : PassManagerBuilderRef, threshold : UInt32) + fun pass_manager_builder_populate_function_pass_manager = LLVMPassManagerBuilderPopulateFunctionPassManager(builder : PassManagerBuilderRef, pm : PassManagerRef) + fun pass_manager_builder_populate_module_pass_manager = LLVMPassManagerBuilderPopulateModulePassManager(builder : PassManagerBuilderRef, pm : PassManagerRef) + fun dispose_pass_manager_builder = LLVMPassManagerBuilderDispose(PassManagerBuilderRef) + {% end %} end diff --git a/src/llvm/module.cr b/src/llvm/module.cr index 95940f6ed193..e4d9dc110231 100644 --- a/src/llvm/module.cr +++ b/src/llvm/module.cr @@ -84,6 +84,9 @@ class LLVM::Module self end + {% unless LibLLVM::IS_LT_130 %} + @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] + {% end %} def new_function_pass_manager FunctionPassManager.new LibLLVM.create_function_pass_manager_for_module(self) end diff --git a/src/llvm/module_pass_manager.cr b/src/llvm/module_pass_manager.cr index 51922780972b..c0bcad4e6198 100644 --- a/src/llvm/module_pass_manager.cr +++ b/src/llvm/module_pass_manager.cr @@ -1,3 +1,6 @@ +{% unless LibLLVM::IS_LT_130 %} + @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] +{% end %} class LLVM::ModulePassManager def initialize @unwrap = LibLLVM.pass_manager_create diff --git a/src/llvm/pass_manager_builder.cr b/src/llvm/pass_manager_builder.cr index 141c4aff83ed..148cd19fca35 100644 --- a/src/llvm/pass_manager_builder.cr +++ b/src/llvm/pass_manager_builder.cr @@ -1,3 +1,6 @@ +{% unless LibLLVM::IS_LT_130 %} + @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] +{% end %} class LLVM::PassManagerBuilder def initialize @unwrap = LibLLVM.pass_manager_builder_create diff --git a/src/llvm/pass_registry.cr b/src/llvm/pass_registry.cr index e3e21aab9610..7a1377c35716 100644 --- a/src/llvm/pass_registry.cr +++ b/src/llvm/pass_registry.cr @@ -1,3 +1,6 @@ +{% unless LibLLVM::IS_LT_130 %} + @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] +{% end %} struct LLVM::PassRegistry def self.instance : self new LibLLVM.get_global_pass_registry From eec05940552168a599ad9e060ac8f617c1333c90 Mon Sep 17 00:00:00 2001 From: ZeroPlayerRodent <95110394+ZeroPlayerRodent@users.noreply.github.com> Date: Sat, 24 Jun 2023 09:02:53 -0300 Subject: [PATCH 0598/1551] Implemented ',' command in brainfuck sample program (#13559) Co-authored-by: Sijawusz Pur Rahnama --- samples/brainfuck.cr | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/samples/brainfuck.cr b/samples/brainfuck.cr index 30d15d4c673e..e2fda4ea0594 100644 --- a/samples/brainfuck.cr +++ b/samples/brainfuck.cr @@ -27,6 +27,10 @@ struct Tape @pos -= 1 raise "pos should be > 0" if @pos < 0 end + + def set(value) + @tape[@pos] = value + end end class Program @@ -43,6 +47,7 @@ class Program when '+'; tape.inc when '-'; tape.dec when '.'; print tape.get.chr + when ','; STDIN.read_byte.try { |byte| tape.set byte.to_i } when '['; pc = @bracket_map[pc] if tape.get == 0 when ']'; pc = @bracket_map[pc] if tape.get != 0 else # skip From 5b8cee04e5f737ee9b382f7d62a0e93121d52862 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 24 Jun 2023 20:03:34 +0800 Subject: [PATCH 0599/1551] Add `Char#titlecase` for correct mixed-case transformations (#13539) --- scripts/generate_unicode_data.cr | 28 +++++- scripts/unicode_data.ecr | 11 +++ spec/std/char_spec.cr | 26 +++-- spec/std/string_spec.cr | 81 +++++++--------- spec/support/string.cr | 16 ++-- src/char.cr | 61 +++++++++++- src/string.cr | 17 +++- src/unicode/data.cr | 158 ++++++++++++++++++++++++------- src/unicode/unicode.cr | 51 ++++++++++ 9 files changed, 340 insertions(+), 109 deletions(-) diff --git a/scripts/generate_unicode_data.cr b/scripts/generate_unicode_data.cr index f360f27b76e6..610eae8cc2dd 100644 --- a/scripts/generate_unicode_data.cr +++ b/scripts/generate_unicode_data.cr @@ -147,6 +147,7 @@ end entries = [] of Entry special_cases_downcase = [] of SpecialCase +special_cases_titlecase = [] of SpecialCase special_cases_upcase = [] of SpecialCase special_cases_casefold = [] of SpecialCase casefold_mapping = Hash(Int32, Int32).new @@ -200,6 +201,7 @@ body.each_line do |line| end upcase = pieces[12].to_i?(16) downcase = pieces[13].to_i?(16) + titlecase = pieces[14].to_i?(16) casefold = casefold_mapping[codepoint]? entries << Entry.new( codepoint: codepoint, @@ -211,6 +213,9 @@ body.each_line do |line| downcase: downcase, casefold: casefold, ) + if titlecase && titlecase != upcase + special_cases_titlecase << SpecialCase.new(codepoint, [titlecase, 0, 0]) + end end url = "#{UCD_ROOT}SpecialCasing.txt" @@ -223,22 +228,30 @@ body.each_line do |line| pieces = line.split(';') codepoint = pieces[0].to_i(16) + downcase = pieces[1].split.map(&.to_i(16)) - upcase = pieces[3].split.map(&.to_i(16)) - downcase = nil if downcase.size == 1 - upcase = nil if upcase.size == 1 - if downcase + if downcase.size > 1 while downcase.size < 3 downcase << 0 end special_cases_downcase << SpecialCase.new(codepoint, downcase) end - if upcase + + upcase = pieces[3].split.map(&.to_i(16)) + if upcase.size > 1 while upcase.size < 3 upcase << 0 end special_cases_upcase << SpecialCase.new(codepoint, upcase) end + + titlecase = pieces[2].split.map(&.to_i(16)) + if titlecase.size > 1 + while titlecase.size < 3 + titlecase << 0 + end + special_cases_titlecase << SpecialCase.new(codepoint, titlecase) + end end url = "#{UCD_ROOT}extracted/DerivedCombiningClass.txt" @@ -282,6 +295,11 @@ upcase_ranges.select! { |r| r.delta != -1 } alternate_ranges = alternate_ranges(downcase_one_ranges) +special_cases_downcase.sort_by! &.codepoint +special_cases_upcase.sort_by! &.codepoint +special_cases_titlecase.reject! &.in?(special_cases_upcase) +special_cases_titlecase.sort_by! &.codepoint + casefold_ranges = case_ranges entries, &.casefold all_strides = {} of String => Array(Stride) diff --git a/scripts/unicode_data.ecr b/scripts/unicode_data.ecr index 499a2fb42836..c3144c8281c8 100644 --- a/scripts/unicode_data.ecr +++ b/scripts/unicode_data.ecr @@ -87,6 +87,17 @@ module Unicode data end + # Titlecase transformation that differs from the uppercase transformation. + # The maximum transformation is always 3 codepoints, so we store them all as 3 + # codepoints and 0 means end. + private class_getter special_cases_titlecase : Hash(Int32, {Int32, Int32, Int32}) do + data = Hash(Int32, {Int32, Int32, Int32}).new(initial_capacity: <%= special_cases_titlecase.size %>) + <%- special_cases_titlecase.each do |a_case| -%> + put(data, <%= a_case.codepoint %>, <%= a_case.value.join(", ") %>) + <%- end %> + data + end + # Fold case transformation that involve mapping a codepoint # to multiple codepoints. The maximum transformation is always 3 # codepoints, so we store them all as 3 codepoints and 0 means end. diff --git a/spec/std/char_spec.cr b/spec/std/char_spec.cr index d9e9ef8b4671..2c5ea0345252 100644 --- a/spec/std/char_spec.cr +++ b/spec/std/char_spec.cr @@ -4,24 +4,29 @@ require "spec/helpers/iterate" require "../support/string" describe "Char" do - describe "upcase" do + describe "#upcase" do it { 'a'.upcase.should eq('A') } it { '1'.upcase.should eq('1') } + it { assert_iterates_yielding ['F', 'F', 'L'], 'ffl'.upcase } end - describe "downcase" do + describe "#downcase" do it { 'A'.downcase.should eq('a') } it { '1'.downcase.should eq('1') } - it do - actual = [] of Char - 'ß'.downcase(Unicode::CaseOptions::Fold) { |c| actual << c } - actual.should eq(['s', 's']) - end + it { assert_iterates_yielding ['i', '\u{0307}'], 'İ'.downcase } + it { assert_iterates_yielding ['s', 's'], 'ß'.downcase(Unicode::CaseOptions::Fold) } it { 'Ń'.downcase(Unicode::CaseOptions::Fold).should eq('ń') } it { 'ꭰ'.downcase(Unicode::CaseOptions::Fold).should eq('Ꭰ') } it { 'Ꭰ'.downcase(Unicode::CaseOptions::Fold).should eq('Ꭰ') } end + describe "#titlecase" do + it { 'a'.titlecase.should eq('A') } + it { '1'.titlecase.should eq('1') } + it { '\u{10D0}'.titlecase.should eq('\u{10D0}') } # GEORGIAN LETTER AN + it { assert_iterates_yielding ['F', 'f', 'l'], 'ffl'.titlecase } + end + it "#succ" do 'a'.succ.should eq('b') 'あ'.succ.should eq('ぃ') @@ -89,6 +94,13 @@ describe "Char" do it { ' '.lowercase?.should be_false } end + describe "#titlecase?" do + it { 'Dz'.titlecase?.should be_true } + it { 'ᾈ'.titlecase?.should be_true } + it { 'A'.titlecase?.should be_false } + it { 'a'.titlecase?.should be_false } + end + describe "ascii_letter?" do it { 'a'.ascii_letter?.should be_true } it { 'A'.ascii_letter?.should be_true } diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index f5ac06188dc6..7a9f587126f3 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -682,47 +682,36 @@ describe "String" do end describe "#capitalize" do - it { "HELLO!".capitalize.should eq("Hello!") } - it { "HELLO MAN!".capitalize.should eq("Hello man!") } - it { "".capitalize.should eq("") } - it { "fflİ".capitalize.should eq("FFLi̇") } - it { "iO".capitalize(Unicode::CaseOptions::Turkic).should eq("İo") } + it { assert_prints "HELLO!".capitalize, "Hello!" } + it { assert_prints "HELLO MAN!".capitalize, "Hello man!" } + it { assert_prints "".capitalize, "" } + it { assert_prints "iO".capitalize(Unicode::CaseOptions::Turkic), "İo" } - it "does not touch invalid code units in an otherwise ascii string" do - "\xB5!\xE0\xC1\xB5?".capitalize.should eq("\xB5!\xE0\xC1\xB5?") + it "handles multi-character mappings correctly (#13533)" do + assert_prints "fflİ".capitalize, "Ffli̇" end - describe "with IO" do - it { String.build { |io| "HELLO!".capitalize io }.should eq "Hello!" } - it { String.build { |io| "HELLO MAN!".capitalize io }.should eq "Hello man!" } - it { String.build { |io| "".capitalize io }.should be_empty } - it { String.build { |io| "fflİ".capitalize io }.should eq "FFLi̇" } - it { String.build { |io| "iO".capitalize io, Unicode::CaseOptions::Turkic }.should eq "İo" } + it "does not touch invalid code units in an otherwise ascii string" do + assert_prints "\xB5!\xE0\xC1\xB5?".capitalize, "\xB5!\xE0\xC1\xB5?" end end describe "#titleize" do - it { "hEllO tAb\tworld".titleize.should eq("Hello Tab\tWorld") } - it { " spaces before".titleize.should eq(" Spaces Before") } - it { "testa-se muito".titleize.should eq("Testa-se Muito") } - it { "hÉllÕ tAb\tworld".titleize.should eq("Héllõ Tab\tWorld") } - it { " spáçes before".titleize.should eq(" Spáçes Before") } - it { "testá-se múitô".titleize.should eq("Testá-se Múitô") } - it { "iO iO".titleize(Unicode::CaseOptions::Turkic).should eq("İo İo") } + it { assert_prints "hEllO tAb\tworld".titleize, "Hello Tab\tWorld" } + it { assert_prints " spaces before".titleize, " Spaces Before" } + it { assert_prints "testa-se muito".titleize, "Testa-se Muito" } + it { assert_prints "hÉllÕ tAb\tworld".titleize, "Héllõ Tab\tWorld" } + it { assert_prints " spáçes before".titleize, " Spáçes Before" } + it { assert_prints "testá-se múitô".titleize, "Testá-se Múitô" } + it { assert_prints "iO iO".titleize(Unicode::CaseOptions::Turkic), "İo İo" } - it "does not touch invalid code units in an otherwise ascii string" do - "\xB5!\xE0\xC1\xB5?".titleize.should eq("\xB5!\xE0\xC1\xB5?") - "a\xA0b".titleize.should eq("A\xA0b") + it "handles multi-character mappings correctly (#13533)" do + assert_prints "fflİ İffl dz DZ".titleize, "Ffli̇ İffl Dz Dz" end - describe "with IO" do - it { String.build { |io| "hEllO tAb\tworld".titleize io }.should eq "Hello Tab\tWorld" } - it { String.build { |io| " spaces before".titleize io }.should eq " Spaces Before" } - it { String.build { |io| "testa-se muito".titleize io }.should eq "Testa-se Muito" } - it { String.build { |io| "hÉllÕ tAb\tworld".titleize io }.should eq "Héllõ Tab\tWorld" } - it { String.build { |io| " spáçes before".titleize io }.should eq " Spáçes Before" } - it { String.build { |io| "testá-se múitô".titleize io }.should eq "Testá-se Múitô" } - it { String.build { |io| "iO iO".titleize io, Unicode::CaseOptions::Turkic }.should eq "İo İo" } + it "does not touch invalid code units in an otherwise ascii string" do + assert_prints "\xB5!\xE0\xC1\xB5?".titleize, "\xB5!\xE0\xC1\xB5?" + assert_prints "a\xA0b".titleize, "A\xA0b" end end @@ -2194,24 +2183,18 @@ describe "String" do end describe "#camelcase" do - it { "foo".camelcase.should eq "Foo" } - it { "foo_bar".camelcase.should eq "FooBar" } - it { "foo".camelcase(lower: true).should eq "foo" } - it { "foo_bar".camelcase(lower: true).should eq "fooBar" } - it { "Foo".camelcase.should eq "Foo" } - it { "Foo_bar".camelcase.should eq "FooBar" } - it { "Foo".camelcase(lower: true).should eq "foo" } - it { "Foo_bar".camelcase(lower: true).should eq "fooBar" } - - describe "with IO" do - it { String.build { |io| "foo".camelcase io }.should eq "Foo" } - it { String.build { |io| "foo_bar".camelcase io }.should eq "FooBar" } - it { String.build { |io| "foo".camelcase io, lower: true }.should eq "foo" } - it { String.build { |io| "foo_bar".camelcase io, lower: true }.should eq "fooBar" } - it { String.build { |io| "Foo".camelcase io }.should eq "Foo" } - it { String.build { |io| "Foo_bar".camelcase io }.should eq "FooBar" } - it { String.build { |io| "Foo".camelcase io, lower: true }.should eq "foo" } - it { String.build { |io| "Foo_bar".camelcase io, lower: true }.should eq "fooBar" } + it { assert_prints "foo".camelcase, "Foo" } + it { assert_prints "foo_bar".camelcase, "FooBar" } + it { assert_prints "foo".camelcase(lower: true), "foo" } + it { assert_prints "foo_bar".camelcase(lower: true), "fooBar" } + it { assert_prints "Foo".camelcase, "Foo" } + it { assert_prints "Foo_bar".camelcase, "FooBar" } + it { assert_prints "Foo".camelcase(lower: true), "foo" } + it { assert_prints "Foo_bar".camelcase(lower: true), "fooBar" } + + it "handles multi-character mappings correctly (#13533)" do + assert_prints "ffl_xffl".camelcase, "FflXffl" + assert_prints "İ_xffl".camelcase(lower: true), "i̇Xffl" end end diff --git a/spec/support/string.cr b/spec/support/string.cr index 45a8dc476e9c..be2306bdd509 100644 --- a/spec/support/string.cr +++ b/spec/support/string.cr @@ -25,11 +25,13 @@ end # Given a call of the form `foo.bar(*args, **opts)`, tests the following cases: # # * This call itself should return a `String` equal to *str*. -# * `String.build { |io| foo.bar(io, *args, **opts) }` should be equal to *str*. -# * `string_build_via_utf16 { |io| foo.bar(io, *args, **opts) }` should be equal -# to *str*; that is, the `IO` overload should not fail when the `IO` argument -# uses a non-default encoding. This case is skipped if the `without_iconv` -# flag is set. +# * `String.build { |io| foo.bar(io, *args, **opts) }` should be equal to +# `str.scrub`; writing to a `String::Builder` must not produce any invalid +# UTF-8 byte sequences. +# * `string_build_via_utf16 { |io| foo.bar(io, *args, **opts) }` should also be +# equal to `str.scrub`; that is, the `IO` overload should not fail when the +# `IO` argument uses a non-default encoding. This case is skipped if the +# `without_iconv` flag is set. macro assert_prints(call, str, *, file = __FILE__, line = __LINE__) %str = ({{ str }}).as(String) %file = {{ file }} @@ -45,7 +47,7 @@ macro assert_prints(call, str, *, file = __FILE__, line = __LINE__) {% for arg in call.args %} {{ arg }}, {% end %} {% if call.named_args %} {% for narg in call.named_args %} {{ narg.name }}: {{ narg.value }}, {% end %} {% end %} ) {{ call.block }} - end.should eq(%str), file: %file, line: %line + end.should eq(%str.scrub), file: %file, line: %line {% unless flag?(:without_iconv) %} string_build_via_utf16 do |io| @@ -54,6 +56,6 @@ macro assert_prints(call, str, *, file = __FILE__, line = __LINE__) {% for arg in call.args %} {{ arg }}, {% end %} {% if call.named_args %} {% for narg in call.named_args %} {{ narg.name }}: {{ narg.value }}, {% end %} {% end %} ) {{ call.block }} - end.should eq(%str), file: %file, line: %line + end.should eq(%str.scrub), file: %file, line: %line {% end %} end diff --git a/src/char.cr b/src/char.cr index 2a6b912db118..761d4187c243 100644 --- a/src/char.cr +++ b/src/char.cr @@ -197,6 +197,7 @@ struct Char # 'ç'.lowercase? # => true # 'G'.lowercase? # => false # '.'.lowercase? # => false + # 'Dz'.lowercase? # => false # ``` def lowercase? : Bool ascii? ? ascii_lowercase? : Unicode.lowercase?(self) @@ -221,11 +222,24 @@ struct Char # 'Á'.uppercase? # => true # 'c'.uppercase? # => false # '.'.uppercase? # => false + # 'Dz'.uppercase? # => false # ``` def uppercase? : Bool ascii? ? ascii_uppercase? : Unicode.uppercase?(self) end + # Returns `true` if this char is a titlecase character, i.e. a ligature + # consisting of an uppercase letter followed by lowercase characters. + # + # ``` + # 'Dz'.titlecase? # => true + # 'H'.titlecase? # => false + # 'c'.titlecase? # => false + # ``` + def titlecase? : Bool + !ascii? && Unicode.titlecase?(self) + end + # Returns `true` if this char is an ASCII letter ('a' to 'z', 'A' to 'Z'). # # ``` @@ -393,7 +407,7 @@ struct Char # characters, like 'İ', than when downcased result in multiple # characters (in this case: 'I' and the dot mark). # - # For a more correct method see the method that receives a block. + # For more correct behavior see the overload that receives a block. # # ``` # 'Z'.downcase # => 'z' @@ -449,7 +463,7 @@ struct Char # characters, like 'ffl', than when upcased result in multiple # characters (in this case: 'F', 'F', 'L'). # - # For a more correct method see the method that receives a block. + # For more correct behavior see the overload that receives a block. # # ``` # 'z'.upcase # => 'Z' @@ -474,6 +488,49 @@ struct Char Unicode.upcase(self, options) { |char| yield char } end + # Returns the titlecase equivalent of this char. + # + # Usually this is equivalent to `#upcase`, but a few precomposed characters + # consisting of multiple letters may return a different character where only + # the first letter is uppercase and the rest lowercase. + # + # Note that this only works for characters whose titlecase + # equivalent yields a single codepoint. There are a few + # characters, like 'ffl', than when titlecased result in multiple + # characters (in this case: 'F', 'f', 'l'). + # + # For more correct behavior see the overload that receives a block. + # + # ``` + # 'z'.titlecase # => 'Z' + # 'X'.titlecase # => 'X' + # '.'.titlecase # => '.' + # 'DZ'.titlecase # => 'Dz' + # 'dz'.titlecase # => 'Dz' + # ``` + def titlecase(options : Unicode::CaseOptions = :none) : Char + Unicode.titlecase(self, options) + end + + # Yields each char for the titlecase equivalent of this char. + # + # Usually this is equivalent to `#upcase`, but a few precomposed characters + # consisting of multiple letters may yield a different character sequence + # where only the first letter is uppercase and the rest lowercase. + # + # This method takes into account the possibility that a titlecase + # version of a char might result in multiple chars, like for + # 'ffl', which results in 'F', 'f' and 'l'. + # + # ``` + # 'z'.titlecase { |v| puts v } # prints 'Z' + # 'DZ'.titlecase { |v| puts v } # prints 'Dz' + # 'ffl'.titlecase { |v| puts v } # prints 'F', 'f', 'l' + # ``` + def titlecase(options : Unicode::CaseOptions = :none, &) + Unicode.titlecase(self, options) { |char| yield char } + end + # See `Object#hash(hasher)` def hash(hasher) hasher.char(self) diff --git a/src/string.cr b/src/string.cr index 9e67c9debb20..7c9eed3dd186 100644 --- a/src/string.cr +++ b/src/string.cr @@ -1499,7 +1499,7 @@ class String def capitalize(io : IO, options : Unicode::CaseOptions = :none) : Nil each_char_with_index do |char, i| if i.zero? - char.upcase(options) { |c| io << c } + char.titlecase(options) { |c| io << c } else char.downcase(options) { |c| io << c } end @@ -1551,8 +1551,11 @@ class String upcase_next = true each_char_with_index do |char, i| - replaced_char = upcase_next ? char.upcase(options) : char.downcase(options) - io << replaced_char + if upcase_next + char.titlecase(options) { |c| io << c } + else + char.downcase(options) { |c| io << c } + end upcase_next = char.whitespace? end end @@ -4356,11 +4359,15 @@ class String each_char do |char| if first - io << (lower ? char.downcase(options) : char.upcase(options)) + if lower + char.downcase(options) { |c| io << c } + else + char.titlecase(options) { |c| io << c } + end elsif char == '_' last_is_underscore = true elsif last_is_underscore - io << char.upcase(options) + char.titlecase(options) { |c| io << c } last_is_underscore = false else io << char diff --git a/src/unicode/data.cr b/src/unicode/data.cr index 5fe4b621c8a5..050c6c8836eb 100644 --- a/src/unicode/data.cr +++ b/src/unicode/data.cr @@ -2651,23 +2651,11 @@ module Unicode private class_getter special_cases_upcase : Hash(Int32, {Int32, Int32, Int32}) do data = Hash(Int32, {Int32, Int32, Int32}).new(initial_capacity: 102) put(data, 223, 83, 83, 0) - put(data, 64256, 70, 70, 0) - put(data, 64257, 70, 73, 0) - put(data, 64258, 70, 76, 0) - put(data, 64259, 70, 70, 73) - put(data, 64260, 70, 70, 76) - put(data, 64261, 83, 84, 0) - put(data, 64262, 83, 84, 0) - put(data, 1415, 1333, 1362, 0) - put(data, 64275, 1348, 1350, 0) - put(data, 64276, 1348, 1333, 0) - put(data, 64277, 1348, 1339, 0) - put(data, 64278, 1358, 1350, 0) - put(data, 64279, 1348, 1341, 0) put(data, 329, 700, 78, 0) + put(data, 496, 74, 780, 0) put(data, 912, 921, 776, 769) put(data, 944, 933, 776, 769) - put(data, 496, 74, 780, 0) + put(data, 1415, 1333, 1362, 0) put(data, 7830, 72, 817, 0) put(data, 7831, 84, 776, 0) put(data, 7832, 87, 778, 0) @@ -2677,18 +2665,6 @@ module Unicode put(data, 8018, 933, 787, 768) put(data, 8020, 933, 787, 769) put(data, 8022, 933, 787, 834) - put(data, 8118, 913, 834, 0) - put(data, 8134, 919, 834, 0) - put(data, 8146, 921, 776, 768) - put(data, 8147, 921, 776, 769) - put(data, 8150, 921, 834, 0) - put(data, 8151, 921, 776, 834) - put(data, 8162, 933, 776, 768) - put(data, 8163, 933, 776, 769) - put(data, 8164, 929, 787, 0) - put(data, 8166, 933, 834, 0) - put(data, 8167, 933, 776, 834) - put(data, 8182, 937, 834, 0) put(data, 8064, 7944, 921, 0) put(data, 8065, 7945, 921, 0) put(data, 8066, 7946, 921, 0) @@ -2737,21 +2713,135 @@ module Unicode put(data, 8109, 8045, 921, 0) put(data, 8110, 8046, 921, 0) put(data, 8111, 8047, 921, 0) - put(data, 8115, 913, 921, 0) - put(data, 8124, 913, 921, 0) - put(data, 8131, 919, 921, 0) - put(data, 8140, 919, 921, 0) - put(data, 8179, 937, 921, 0) - put(data, 8188, 937, 921, 0) put(data, 8114, 8122, 921, 0) + put(data, 8115, 913, 921, 0) put(data, 8116, 902, 921, 0) + put(data, 8118, 913, 834, 0) + put(data, 8119, 913, 834, 921) + put(data, 8124, 913, 921, 0) put(data, 8130, 8138, 921, 0) + put(data, 8131, 919, 921, 0) put(data, 8132, 905, 921, 0) + put(data, 8134, 919, 834, 0) + put(data, 8135, 919, 834, 921) + put(data, 8140, 919, 921, 0) + put(data, 8146, 921, 776, 768) + put(data, 8147, 921, 776, 769) + put(data, 8150, 921, 834, 0) + put(data, 8151, 921, 776, 834) + put(data, 8162, 933, 776, 768) + put(data, 8163, 933, 776, 769) + put(data, 8164, 929, 787, 0) + put(data, 8166, 933, 834, 0) + put(data, 8167, 933, 776, 834) put(data, 8178, 8186, 921, 0) + put(data, 8179, 937, 921, 0) put(data, 8180, 911, 921, 0) - put(data, 8119, 913, 834, 921) - put(data, 8135, 919, 834, 921) + put(data, 8182, 937, 834, 0) put(data, 8183, 937, 834, 921) + put(data, 8188, 937, 921, 0) + put(data, 64256, 70, 70, 0) + put(data, 64257, 70, 73, 0) + put(data, 64258, 70, 76, 0) + put(data, 64259, 70, 70, 73) + put(data, 64260, 70, 70, 76) + put(data, 64261, 83, 84, 0) + put(data, 64262, 83, 84, 0) + put(data, 64275, 1348, 1350, 0) + put(data, 64276, 1348, 1333, 0) + put(data, 64277, 1348, 1339, 0) + put(data, 64278, 1358, 1350, 0) + put(data, 64279, 1348, 1341, 0) + + data + end + + # Titlecase transformation that differs from the uppercase transformation. + # The maximum transformation is always 3 codepoints, so we store them all as 3 + # codepoints and 0 means end. + private class_getter special_cases_titlecase : Hash(Int32, {Int32, Int32, Int32}) do + data = Hash(Int32, {Int32, Int32, Int32}).new(initial_capacity: 81) + put(data, 223, 83, 115, 0) + put(data, 452, 453, 0, 0) + put(data, 453, 453, 0, 0) + put(data, 454, 453, 0, 0) + put(data, 455, 456, 0, 0) + put(data, 456, 456, 0, 0) + put(data, 457, 456, 0, 0) + put(data, 458, 459, 0, 0) + put(data, 459, 459, 0, 0) + put(data, 460, 459, 0, 0) + put(data, 497, 498, 0, 0) + put(data, 498, 498, 0, 0) + put(data, 499, 498, 0, 0) + put(data, 1415, 1333, 1410, 0) + put(data, 4304, 4304, 0, 0) + put(data, 4305, 4305, 0, 0) + put(data, 4306, 4306, 0, 0) + put(data, 4307, 4307, 0, 0) + put(data, 4308, 4308, 0, 0) + put(data, 4309, 4309, 0, 0) + put(data, 4310, 4310, 0, 0) + put(data, 4311, 4311, 0, 0) + put(data, 4312, 4312, 0, 0) + put(data, 4313, 4313, 0, 0) + put(data, 4314, 4314, 0, 0) + put(data, 4315, 4315, 0, 0) + put(data, 4316, 4316, 0, 0) + put(data, 4317, 4317, 0, 0) + put(data, 4318, 4318, 0, 0) + put(data, 4319, 4319, 0, 0) + put(data, 4320, 4320, 0, 0) + put(data, 4321, 4321, 0, 0) + put(data, 4322, 4322, 0, 0) + put(data, 4323, 4323, 0, 0) + put(data, 4324, 4324, 0, 0) + put(data, 4325, 4325, 0, 0) + put(data, 4326, 4326, 0, 0) + put(data, 4327, 4327, 0, 0) + put(data, 4328, 4328, 0, 0) + put(data, 4329, 4329, 0, 0) + put(data, 4330, 4330, 0, 0) + put(data, 4331, 4331, 0, 0) + put(data, 4332, 4332, 0, 0) + put(data, 4333, 4333, 0, 0) + put(data, 4334, 4334, 0, 0) + put(data, 4335, 4335, 0, 0) + put(data, 4336, 4336, 0, 0) + put(data, 4337, 4337, 0, 0) + put(data, 4338, 4338, 0, 0) + put(data, 4339, 4339, 0, 0) + put(data, 4340, 4340, 0, 0) + put(data, 4341, 4341, 0, 0) + put(data, 4342, 4342, 0, 0) + put(data, 4343, 4343, 0, 0) + put(data, 4344, 4344, 0, 0) + put(data, 4345, 4345, 0, 0) + put(data, 4346, 4346, 0, 0) + put(data, 4349, 4349, 0, 0) + put(data, 4350, 4350, 0, 0) + put(data, 4351, 4351, 0, 0) + put(data, 8114, 8122, 837, 0) + put(data, 8116, 902, 837, 0) + put(data, 8119, 913, 834, 837) + put(data, 8130, 8138, 837, 0) + put(data, 8132, 905, 837, 0) + put(data, 8135, 919, 834, 837) + put(data, 8178, 8186, 837, 0) + put(data, 8180, 911, 837, 0) + put(data, 8183, 937, 834, 837) + put(data, 64256, 70, 102, 0) + put(data, 64257, 70, 105, 0) + put(data, 64258, 70, 108, 0) + put(data, 64259, 70, 102, 105) + put(data, 64260, 70, 102, 108) + put(data, 64261, 83, 116, 0) + put(data, 64262, 83, 116, 0) + put(data, 64275, 1348, 1398, 0) + put(data, 64276, 1348, 1381, 0) + put(data, 64277, 1348, 1387, 0) + put(data, 64278, 1358, 1398, 0) + put(data, 64279, 1348, 1389, 0) data end diff --git a/src/unicode/unicode.cr b/src/unicode/unicode.cr index 491a702c68ed..224d5c59a042 100644 --- a/src/unicode/unicode.cr +++ b/src/unicode/unicode.cr @@ -298,6 +298,52 @@ module Unicode end # :nodoc: + def self.titlecase(char : Char, options : CaseOptions) : Char + result = check_upcase_ascii(char, options) + return result if result + + result = check_upcase_turkic(char, options) + return result if result + + # there are no ASCII or Turkic special cases for titlecasing; this is the + # only part that differs from `.upcase` + result = special_cases_titlecase[char.ord]? + return result.first.unsafe_chr if result && result[1] == 0 && result[2] == 0 + + check_upcase_ranges(char) + end + + # :nodoc: + def self.titlecase(char : Char, options : CaseOptions, &) + result = check_upcase_ascii(char, options) + if result + yield result + return + end + + result = check_upcase_turkic(char, options) + if result + yield result + return + end + + # there are no ASCII or Turkic special cases for titlecasing; this is the + # only part that differs from `.upcase` + result = special_cases_titlecase[char.ord]? + if result + result.each { |c| yield c.unsafe_chr if c != 0 } + return + end + + result = special_cases_upcase[char.ord]? + if result + result.each { |c| yield c.unsafe_chr if c != 0 } + return + end + + yield check_upcase_ranges(char) + end + def self.foldcase(char : Char, options : CaseOptions) : Char results = check_foldcase(char, options) return results[0].unsafe_chr if results && results.size == 1 @@ -336,6 +382,11 @@ module Unicode in_category?(char.ord, category_Lu) end + # :nodoc: + def self.titlecase?(char : Char) : Bool + in_category?(char.ord, category_Lt) + end + # :nodoc: def self.letter?(char : Char) : Bool in_any_category?(char.ord, category_Lu, category_Ll, category_Lt, category_Lm, category_Lo) From 8689c06b2fb0d4723ae4624b51161c398d22ceca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 24 Jun 2023 14:04:12 +0200 Subject: [PATCH 0600/1551] Fix `LLVM.default_target_triple` to normalize aarch64 darwin target (#13597) --- src/llvm.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/llvm.cr b/src/llvm.cr index 16286b77a9cc..5f44889aaf0a 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -90,7 +90,11 @@ module LLVM chars = LibLLVM.get_default_target_triple triple = string_and_dispose(chars) if triple =~ /x86_64-apple-macosx|x86_64-apple-darwin/ + # normalize on `macosx` and remove minimum deployment target version "x86_64-apple-macosx" + elsif triple =~ /aarch64-apple-macosx|aarch64-apple-darwin/ + # normalize on `macosx` and remove minimum deployment target version + "aarch64-apple-macosx" elsif triple =~ /aarch64-unknown-linux-android/ # remove API version "aarch64-unknown-linux-android" From d2add86f9e446d59cfdcb0ab108e43ec3d40d2ab Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 25 Jun 2023 07:15:54 +0800 Subject: [PATCH 0601/1551] Support `Atomic(T)#compare_and_set` when `T` is a reference union (#13565) --- spec/std/atomic_spec.cr | 152 +++++++++++++++++++++++++--------------- src/atomic.cr | 61 ++++++++++------ 2 files changed, 136 insertions(+), 77 deletions(-) diff --git a/spec/std/atomic_spec.cr b/spec/std/atomic_spec.cr index 3b473f8ff3d7..8f5fe8e8b22a 100644 --- a/spec/std/atomic_spec.cr +++ b/spec/std/atomic_spec.cr @@ -15,64 +15,88 @@ enum AtomicEnumFlags end describe Atomic do - it "compares and sets with integer" do - atomic = Atomic.new(1) + describe "#compare_and_set" do + it "with integer" do + atomic = Atomic.new(1) - atomic.compare_and_set(2, 3).should eq({1, false}) - atomic.get.should eq(1) + atomic.compare_and_set(2, 3).should eq({1, false}) + atomic.get.should eq(1) - atomic.compare_and_set(1, 3).should eq({1, true}) - atomic.get.should eq(3) - end + atomic.compare_and_set(1, 3).should eq({1, true}) + atomic.get.should eq(3) + end - it "compares and set with enum" do - atomic = Atomic(AtomicEnum).new(AtomicEnum::One) + it "with enum" do + atomic = Atomic(AtomicEnum).new(AtomicEnum::One) - atomic.compare_and_set(AtomicEnum::Two, AtomicEnum::Three).should eq({AtomicEnum::One, false}) - atomic.get.should eq(AtomicEnum::One) + atomic.compare_and_set(AtomicEnum::Two, AtomicEnum::Three).should eq({AtomicEnum::One, false}) + atomic.get.should eq(AtomicEnum::One) - atomic.compare_and_set(AtomicEnum::One, AtomicEnum::Three).should eq({AtomicEnum::One, true}) - atomic.get.should eq(AtomicEnum::Three) - end + atomic.compare_and_set(AtomicEnum::One, AtomicEnum::Three).should eq({AtomicEnum::One, true}) + atomic.get.should eq(AtomicEnum::Three) + end - it "compares and set with flags enum" do - atomic = Atomic(AtomicEnumFlags).new(AtomicEnumFlags::One) + it "with flags enum" do + atomic = Atomic(AtomicEnumFlags).new(AtomicEnumFlags::One) - atomic.compare_and_set(AtomicEnumFlags::Two, AtomicEnumFlags::Three).should eq({AtomicEnumFlags::One, false}) - atomic.get.should eq(AtomicEnumFlags::One) + atomic.compare_and_set(AtomicEnumFlags::Two, AtomicEnumFlags::Three).should eq({AtomicEnumFlags::One, false}) + atomic.get.should eq(AtomicEnumFlags::One) - atomic.compare_and_set(AtomicEnumFlags::One, AtomicEnumFlags::Three).should eq({AtomicEnumFlags::One, true}) - atomic.get.should eq(AtomicEnumFlags::Three) - end + atomic.compare_and_set(AtomicEnumFlags::One, AtomicEnumFlags::Three).should eq({AtomicEnumFlags::One, true}) + atomic.get.should eq(AtomicEnumFlags::Three) + end - it "compares and sets with nilable type" do - atomic = Atomic(String?).new(nil) - string = "hello" + it "with nilable reference" do + atomic = Atomic(String?).new(nil) + string = "hello" - atomic.compare_and_set(string, "foo").should eq({nil, false}) - atomic.get.should be_nil + atomic.compare_and_set(string, "foo").should eq({nil, false}) + atomic.get.should be_nil - atomic.compare_and_set(nil, string).should eq({nil, true}) - atomic.get.should be(string) + atomic.compare_and_set(nil, string).should eq({nil, true}) + atomic.get.should be(string) - atomic.compare_and_set(string, nil).should eq({string, true}) - atomic.get.should be_nil - end + atomic.compare_and_set(string, nil).should eq({string, true}) + atomic.get.should be_nil + end + + it "with reference type" do + str1 = "hello" + str2 = "bye" + + atomic = Atomic(String).new(str1) + + atomic.compare_and_set(str2, "foo").should eq({str1, false}) + atomic.get.should be(str1) - it "compares and sets with reference type" do - str1 = "hello" - str2 = "bye" + atomic.compare_and_set(str1, str2).should eq({str1, true}) + atomic.get.should be(str2) - atomic = Atomic(String).new(str1) + atomic.compare_and_set(str2, str1).should eq({str2, true}) + atomic.get.should be(str1) - atomic.compare_and_set(str2, "foo").should eq({str1, false}) - atomic.get.should eq(str1) + atomic.compare_and_set(String.build(&.<< "bye"), str2).should eq({str1, false}) + atomic.get.should be(str1) + end - atomic.compare_and_set(str1, str2).should eq({str1, true}) - atomic.get.should be(str2) + it "with reference union" do + arr1 = [1] + arr2 = [""] - atomic.compare_and_set(str2, str1).should eq({str2, true}) - atomic.get.should be(str1) + atomic = Atomic(Array(Int32) | Array(String)).new(arr1) + + atomic.compare_and_set(arr2, ["foo"]).should eq({arr1, false}) + atomic.get.should be(arr1) + + atomic.compare_and_set(arr1, arr2).should eq({arr1, true}) + atomic.get.should be(arr2) + + atomic.compare_and_set(arr2, arr1).should eq({arr2, true}) + atomic.get.should be(arr1) + + atomic.compare_and_set([1], arr2).should eq({arr1, false}) + atomic.get.should be(arr1) + end end it "#adds" do @@ -185,26 +209,40 @@ describe Atomic do atomic.get.should eq(2) end - it "#swap" do - atomic = Atomic.new(1) - atomic.swap(2).should eq(1) - atomic.get.should eq(2) - end + describe "#swap" do + it "with integer" do + atomic = Atomic.new(1) + atomic.swap(2).should eq(1) + atomic.get.should eq(2) + end - it "#swap with Reference type" do - atomic = Atomic.new("hello") - atomic.swap("world").should eq("hello") - atomic.get.should eq("world") - end + it "with reference type" do + atomic = Atomic.new("hello") + atomic.swap("world").should eq("hello") + atomic.get.should eq("world") + end - it "#swap with nil" do - atomic = Atomic(String?).new(nil) + it "with nilable reference" do + atomic = Atomic(String?).new(nil) - atomic.swap("not nil").should eq(nil) - atomic.get.should eq("not nil") + atomic.swap("not nil").should eq(nil) + atomic.get.should eq("not nil") - atomic.swap(nil).should eq("not nil") - atomic.get.should eq(nil) + atomic.swap(nil).should eq("not nil") + atomic.get.should eq(nil) + end + + it "with reference union" do + arr1 = [1] + arr2 = [""] + atomic = Atomic(Array(Int32) | Array(String)).new(arr1) + + atomic.swap(arr2).should be(arr1) + atomic.get.should be(arr2) + + atomic.swap(arr1).should be(arr2) + atomic.get.should be(arr1) + end end end diff --git a/src/atomic.cr b/src/atomic.cr index d182c04db368..eedf081141d2 100644 --- a/src/atomic.cr +++ b/src/atomic.cr @@ -2,17 +2,19 @@ require "llvm/enums/atomic" # A value that may be updated atomically. # -# Only primitive integer types, reference types or nilable reference types -# can be used with an Atomic type. +# If `T` is a non-union primitive integer type or enum type, all operations are +# supported. If `T` is a reference type, or a union type containing only +# reference types or `Nil`, then only `#compare_and_set`, `#swap`, `#set`, +# `#lazy_set`, `#get`, and `#lazy_get` are available. struct Atomic(T) # Creates an Atomic with the given initial value. def initialize(@value : T) {% if !T.union? && (T == Char || T < Int::Primitive || T < Enum) %} # Support integer types, enum types, or char (because it's represented as an integer) - {% elsif T < Reference || (T.union? && T.union_types.all? { |t| t == Nil || t < Reference }) %} + {% elsif T.union_types.all? { |t| t == Nil || t < Reference } && T != Nil %} # Support reference types, or union types with only nil or reference types {% else %} - {{ raise "Can only create Atomic with primitive integer types, reference types or nilable reference types, not #{T}" }} + {% raise "Can only create Atomic with primitive integer types, reference types or nilable reference types, not #{T}" %} {% end %} end @@ -21,6 +23,8 @@ struct Atomic(T) # * if they are equal, sets the value to *new*, and returns `{old_value, true}` # * if they are not equal the value remains the same, and returns `{old_value, false}` # + # Reference types are compared by `#same?`, not `#==`. + # # ``` # atomic = Atomic.new(1) # @@ -31,90 +35,97 @@ struct Atomic(T) # atomic.get # => 3 # ``` def compare_and_set(cmp : T, new : T) : {T, Bool} - # Check if it's a nilable reference type - {% if T.union? && T.union_types.all? { |t| t == Nil || t < Reference } %} - # If so, use addresses because LLVM < 3.9 doesn't support cmpxchg with pointers - address, success = Ops.cmpxchg(pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(cmp.as(T).object_id), LibC::SizeT.new(new.as(T).object_id), :sequentially_consistent, :sequentially_consistent) - {address == 0 ? nil : Pointer(T).new(address).as(T), success} - # Check if it's a reference type - {% elsif T < Reference %} - # Use addresses again (but this can't return nil) - address, success = Ops.cmpxchg(pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(cmp.as(T).object_id), LibC::SizeT.new(new.as(T).object_id), :sequentially_consistent, :sequentially_consistent) - {Pointer(T).new(address).as(T), success} - {% else %} - # Otherwise, this is an integer type - Ops.cmpxchg(pointerof(@value), cmp, new, :sequentially_consistent, :sequentially_consistent) - {% end %} + Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :sequentially_consistent) end # Performs `atomic_value &+= value`. Returns the old value. # + # `T` cannot contain any reference types. + # # ``` # atomic = Atomic.new(1) # atomic.add(2) # => 1 # atomic.get # => 3 # ``` def add(value : T) : T + check_reference_type Ops.atomicrmw(:add, pointerof(@value), value, :sequentially_consistent, false) end # Performs `atomic_value &-= value`. Returns the old value. # + # `T` cannot contain any reference types. + # # ``` # atomic = Atomic.new(9) # atomic.sub(2) # => 9 # atomic.get # => 7 # ``` def sub(value : T) : T + check_reference_type Ops.atomicrmw(:sub, pointerof(@value), value, :sequentially_consistent, false) end # Performs `atomic_value &= value`. Returns the old value. # + # `T` cannot contain any reference types. + # # ``` # atomic = Atomic.new(5) # atomic.and(3) # => 5 # atomic.get # => 1 # ``` def and(value : T) : T + check_reference_type Ops.atomicrmw(:and, pointerof(@value), value, :sequentially_consistent, false) end # Performs `atomic_value = ~(atomic_value & value)`. Returns the old value. # + # `T` cannot contain any reference types. + # # ``` # atomic = Atomic.new(5) # atomic.nand(3) # => 5 # atomic.get # => -2 # ``` def nand(value : T) : T + check_reference_type Ops.atomicrmw(:nand, pointerof(@value), value, :sequentially_consistent, false) end # Performs `atomic_value |= value`. Returns the old value. # + # `T` cannot contain any reference types. + # # ``` # atomic = Atomic.new(5) # atomic.or(2) # => 5 # atomic.get # => 7 # ``` def or(value : T) : T + check_reference_type Ops.atomicrmw(:or, pointerof(@value), value, :sequentially_consistent, false) end # Performs `atomic_value ^= value`. Returns the old value. # + # `T` cannot contain any reference types. + # # ``` # atomic = Atomic.new(5) # atomic.xor(3) # => 5 # atomic.get # => 6 # ``` def xor(value : T) : T + check_reference_type Ops.atomicrmw(:xor, pointerof(@value), value, :sequentially_consistent, false) end # Performs `atomic_value = {atomic_value, value}.max`. Returns the old value. # + # `T` cannot contain any reference types. + # # ``` # atomic = Atomic.new(5) # @@ -125,6 +136,7 @@ struct Atomic(T) # atomic.get # => 10 # ``` def max(value : T) + check_reference_type {% if T < Enum %} if @value.value.is_a?(Int::Signed) Ops.atomicrmw(:max, pointerof(@value), value, :sequentially_consistent, false) @@ -140,6 +152,8 @@ struct Atomic(T) # Performs `atomic_value = {atomic_value, value}.min`. Returns the old value. # + # `T` cannot contain any reference types. + # # ``` # atomic = Atomic.new(5) # @@ -150,6 +164,7 @@ struct Atomic(T) # atomic.get # => 3 # ``` def min(value : T) + check_reference_type {% if T < Enum %} if @value.value.is_a?(Int::Signed) Ops.atomicrmw(:min, pointerof(@value), value, :sequentially_consistent, false) @@ -171,8 +186,8 @@ struct Atomic(T) # atomic.get # => 10 # ``` def swap(value : T) - {% if T.union? && T.union_types.all? { |t| t == Nil || t < Reference } || T < Reference %} - address = Ops.atomicrmw(:xchg, pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(value.as(T).object_id), :sequentially_consistent, false) + {% if T.union_types.all? { |t| t == Nil || t < Reference } && T != Nil %} + address = Ops.atomicrmw(:xchg, pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(value.as(Void*).address), :sequentially_consistent, false) Pointer(T).new(address).as(T) {% else %} Ops.atomicrmw(:xchg, pointerof(@value), value, :sequentially_consistent, false) @@ -211,6 +226,12 @@ struct Atomic(T) @value end + private macro check_reference_type + {% if T.union_types.all? { |t| t == Nil || t < Reference } && T != Nil %} + {% raise "Cannot call `#{@type}##{@def.name}` as `#{T}` is a reference type" %} + {% end %} + end + # :nodoc: module Ops # Defines methods that directly map to LLVM instructions related to atomic operations. From 26294039673e6855f5ee78258a1c2c2138c6422c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 25 Jun 2023 01:17:03 +0200 Subject: [PATCH 0602/1551] Refactor symbol quoting into `Symbol.quote_for_named_argument` (#13595) --- src/compiler/crystal/macros/methods.cr | 6 +----- src/compiler/crystal/semantic/call_error.cr | 12 ++---------- src/compiler/crystal/syntax/to_s.cr | 12 ++---------- src/compiler/crystal/tools/doc/macro.cr | 11 ++++------- src/compiler/crystal/tools/doc/method.cr | 11 ++++------- src/compiler/crystal/tools/doc/type.cr | 12 ++---------- src/compiler/crystal/types.cr | 6 +----- src/named_tuple.cr | 14 ++------------ src/symbol.cr | 20 ++++++++++++++++++++ 9 files changed, 38 insertions(+), 66 deletions(-) diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 74021a7ce825..36bd79fa64e9 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1048,11 +1048,7 @@ module Crystal private def to_double_splat(trailing_string = "") MacroId.new(entries.join(", ") do |entry| - if Symbol.needs_quotes_for_named_argument?(entry.key) - "#{entry.key.inspect}: #{entry.value}" - else - "#{entry.key}: #{entry.value}" - end + "#{Symbol.quote_for_named_argument(entry.key)}: #{entry.value}" end + trailing_string) end end diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index 89136be3dc16..a0528e83c44f 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -1032,11 +1032,7 @@ class Crystal::Call next if arg_type.entries.empty? io << ", " unless first arg_type.entries.join(io, ", ") do |entry| - if Symbol.needs_quotes_for_named_argument?(entry.name) - entry.name.inspect(io) - else - io << entry.name - end + Symbol.quote_for_named_argument(io, entry.name) io << ": " << entry.type end else @@ -1049,11 +1045,7 @@ class Crystal::Call if named_args = @named_args io << ", " unless first named_args.join(io, ", ") do |named_arg| - if Symbol.needs_quotes_for_named_argument?(named_arg.name) - named_arg.name.inspect(io) - else - io << named_arg.name - end + Symbol.quote_for_named_argument(io, named_arg.name) io << ": " << named_arg.value.type end end diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index cdd0780bdf70..006e73778a45 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -885,11 +885,7 @@ module Crystal end def visit_named_arg_name(name) - if Symbol.needs_quotes_for_named_argument?(name) - name.inspect(@str) - else - @str << name - end + Symbol.quote_for_named_argument(@str, name) end def visit(node : Underscore) @@ -1123,11 +1119,7 @@ module Crystal else @str << node.name @str << " = " - if Symbol.needs_quotes_for_named_argument?(node.real_name) - node.real_name.inspect(@str) - else - @str << node.real_name - end + Symbol.quote_for_named_argument(@str, node.real_name) end if node.args.size > 0 @str << '(' diff --git a/src/compiler/crystal/tools/doc/macro.cr b/src/compiler/crystal/tools/doc/macro.cr index 2dffca65b94a..49b9c30795bc 100644 --- a/src/compiler/crystal/tools/doc/macro.cr +++ b/src/compiler/crystal/tools/doc/macro.cr @@ -107,14 +107,11 @@ class Crystal::Doc::Macro def arg_to_html(arg : Arg, io, html : HTMLOption = :all) if arg.external_name != arg.name if name = arg.external_name.presence - if Symbol.needs_quotes_for_named_argument? name - if html.none? - name.inspect io - else - HTML.escape name.inspect, io - end - else + name = Symbol.quote_for_named_argument(name) + if html.none? io << name + else + HTML.escape name, io end else io << "_" diff --git a/src/compiler/crystal/tools/doc/method.cr b/src/compiler/crystal/tools/doc/method.cr index 10b223a81d76..069deb48ee61 100644 --- a/src/compiler/crystal/tools/doc/method.cr +++ b/src/compiler/crystal/tools/doc/method.cr @@ -272,14 +272,11 @@ class Crystal::Doc::Method def arg_to_html(arg : Arg, io, html : HTMLOption = :all) if arg.external_name != arg.name if name = arg.external_name.presence - if Symbol.needs_quotes_for_named_argument? name - if html.none? - name.inspect io - else - HTML.escape name.inspect, io - end - else + name = Symbol.quote_for_named_argument(name) + if html.none? io << name + else + HTML.escape name, io end else io << "_" diff --git a/src/compiler/crystal/tools/doc/type.cr b/src/compiler/crystal/tools/doc/type.cr index d8748ef898e3..619c32cd1fcd 100644 --- a/src/compiler/crystal/tools/doc/type.cr +++ b/src/compiler/crystal/tools/doc/type.cr @@ -517,11 +517,7 @@ class Crystal::Doc::Type if (named_args = node.named_args) && !named_args.empty? io << ", " unless node.type_vars.empty? named_args.join(io, ", ") do |entry| - if Symbol.needs_quotes_for_named_argument?(entry.name) - entry.name.inspect(io) - else - io << entry.name - end + Symbol.quote_for_named_argument(io, entry.name) io << ": " node_to_html entry.value, io, html: html end @@ -636,11 +632,7 @@ class Crystal::Doc::Type def type_to_html(type : Crystal::NamedTupleInstanceType, io, text = nil, html : HTMLOption = :all) io << '{' type.entries.join(io, ", ") do |entry| - if Symbol.needs_quotes_for_named_argument?(entry.name) - entry.name.inspect(io) - else - io << entry.name - end + Symbol.quote_for_named_argument(io, entry.name) io << ": " type_to_html entry.type, io, html: html end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index d4b73df92ec4..7d4bc9795a7f 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -2628,11 +2628,7 @@ module Crystal def to_s_with_options(io : IO, skip_union_parens : Bool = false, generic_args : Bool = true, codegen : Bool = false) : Nil io << "NamedTuple(" @entries.join(io, ", ") do |entry| - if Symbol.needs_quotes_for_named_argument?(entry.name) - entry.name.inspect(io) - else - io << entry.name - end + Symbol.quote_for_named_argument(io, entry.name) io << ": " entry_type = entry.type entry_type = entry_type.devirtualize unless codegen diff --git a/src/named_tuple.cr b/src/named_tuple.cr index 3bf8abd38938..7cceb568a260 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -449,12 +449,7 @@ struct NamedTuple {% if i > 0 %} io << ", " {% end %} - key = {{key.stringify}} - if Symbol.needs_quotes_for_named_argument?(key) - key.inspect(io) - else - io << key - end + Symbol.quote_for_named_argument io, {{key.stringify}} io << ": " self[{{key.symbolize}}].inspect(io) {% end %} @@ -468,12 +463,7 @@ struct NamedTuple pp.comma {% end %} pp.group do - key = {{key.stringify}} - if Symbol.needs_quotes_for_named_argument?(key) - pp.text key.inspect - else - pp.text key - end + pp.text Symbol.quote_for_named_argument({{key.stringify}}) pp.text ": " pp.nest do pp.breakable "" diff --git a/src/symbol.cr b/src/symbol.cr index a3d6ffd8d4fe..88b9d0db904e 100644 --- a/src/symbol.cr +++ b/src/symbol.cr @@ -99,6 +99,26 @@ struct Symbol end end + # :nodoc: + # Appends *string* to *io* and quotes it if necessary. + def self.quote_for_named_argument(io : IO, string : String) : Nil + if needs_quotes_for_named_argument?(string) + string.inspect(io) + else + io << string + end + end + + # :nodoc: + # Returns *string* and quotes it if necessary. + def self.quote_for_named_argument(string : String) : String + if needs_quotes_for_named_argument?(string) + string.inspect + else + string + end + end + def clone self end From d57747d236822e8265833ac2f00424bfc8d4ac67 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 25 Jun 2023 07:17:13 +0800 Subject: [PATCH 0603/1551] Improve conversions from `BigInt` to `Int::Primitive` (#13562) --- spec/std/big/big_int_spec.cr | 176 +++++++++++++++++++---------------- src/big/big_int.cr | 152 ++++++++++++++++++------------ src/big/lib_gmp.cr | 17 +++- src/int.cr | 50 ++++++++++ 4 files changed, 254 insertions(+), 141 deletions(-) diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr index 5b45b4e3a34a..f7eebc53ffb4 100644 --- a/spec/std/big/big_int_spec.cr +++ b/spec/std/big/big_int_spec.cr @@ -498,98 +498,118 @@ describe "BigInt" do a.should eq(b) end - it "can be casted into other Number types" do - big = BigInt.new(1234567890) - big.to_i.should eq(1234567890) - big.to_i8!.should eq(-46) - big.to_i16!.should eq(722) - big.to_i32.should eq(1234567890) - big.to_i64.should eq(1234567890) - big.to_u.should eq(1234567890) - big.to_u8!.should eq(210) - big.to_u16!.should eq(722) - big.to_u32.should eq(1234567890) - - expect_raises(OverflowError) { BigInt.new(-1234567890).to_u } - - u64 = big.to_u64 - u64.should eq(1234567890) - u64.should be_a(UInt64) - end - - context "conversion to 64-bit" do - it "above 64 bits" do - big = BigInt.new("9" * 20) - expect_raises(OverflowError) { big.to_i64 } - expect_raises(OverflowError) { big.to_u64 } - big.to_i64!.should eq(7766279631452241919) # 99999999999999999999 - 5*(2**64) - big.to_u64!.should eq(7766279631452241919) - - big = BigInt.new("9" * 32) - expect_raises(OverflowError) { big.to_i64 } - expect_raises(OverflowError) { big.to_u64 } - big.to_i64!.should eq(-8814407033341083649) # 99999999999999999999999999999999 - 5421010862428*(2**64) - big.to_u64!.should eq(9632337040368467967u64) # 99999999999999999999999999999999 - 5421010862427*(2**64) + describe "#to_i" do + it "converts to Int32" do + BigInt.new(1234567890).to_i.should(be_a(Int32)).should eq(1234567890) + expect_raises(OverflowError) { BigInt.new(2147483648).to_i } + expect_raises(OverflowError) { BigInt.new(-2147483649).to_i } end + end - it "between 63 and 64 bits" do - big = BigInt.new(i = 9999999999999999999u64) - expect_raises(OverflowError) { big.to_i64 } - big.to_u64.should eq(i) - big.to_i64!.should eq(-8446744073709551617) # 9999999999999999999 - 2**64 - big.to_u64!.should eq(i) + describe "#to_i!" do + it "converts to Int32" do + BigInt.new(1234567890).to_i!.should(be_a(Int32)).should eq(1234567890) + BigInt.new(2147483648).to_i!.should eq(Int32::MIN) + BigInt.new(-2147483649).to_i!.should eq(Int32::MAX) end + end - it "between 32 and 63 bits" do - big = BigInt.new(i = 9999999999999) - big.to_i64.should eq(i) - big.to_u64.should eq(i) - big.to_i64!.should eq(i) - big.to_u64!.should eq(i) + describe "#to_u" do + it "converts to UInt32" do + BigInt.new(1234567890).to_u.should(be_a(UInt32)).should eq(1234567890_u32) + expect_raises(OverflowError) { BigInt.new(4294967296).to_u } + expect_raises(OverflowError) { BigInt.new(-1).to_u } end + end - it "negative under 32 bits" do - big = BigInt.new(i = -9999) - big.to_i64.should eq(i) - expect_raises(OverflowError) { big.to_u64 } - big.to_i64!.should eq(i) - big.to_u64!.should eq(18446744073709541617u64) # -9999 + 2**64 + describe "#to_u!" do + it "converts to UInt32" do + BigInt.new(1234567890).to_u!.should(be_a(UInt32)).should eq(1234567890_u32) + BigInt.new(4294967296).to_u!.should eq(0_u32) + BigInt.new(-1).to_u!.should eq(UInt32::MAX) end + end + + {% for n in [8, 16, 32, 64, 128] %} + describe "#to_u{{n}}" do + it "converts to UInt{{n}}" do + (0..{{n - 1}}).each do |i| + (1.to_big_i << i).to_u{{n}}.should eq(UInt{{n}}.new(1) << i) + end + + UInt{{n}}::MIN.to_big_i.to_u{{n}}.should eq(UInt{{n}}::MIN) + UInt{{n}}::MAX.to_big_i.to_u{{n}}.should eq(UInt{{n}}::MAX) + end - it "negative between 32 and 63 bits" do - big = BigInt.new(i = -9999999999999) - big.to_i64.should eq(i) - expect_raises(OverflowError) { big.to_u64 } - big.to_i64!.should eq(i) - big.to_u64!.should eq(18446734073709551617u64) # -9999999999999 + 2**64 + it "raises OverflowError" do + expect_raises(OverflowError) { (1.to_big_i << {{n}}).to_u{{n}} } + expect_raises(OverflowError) { (-1.to_big_i).to_u{{n}} } + expect_raises(OverflowError) { (-1.to_big_i << {{n}}).to_u{{n}} } + end end - it "negative between 63 and 64 bits" do - big = BigInt.new("-9999999999999999999") - expect_raises(OverflowError) { big.to_i64 } - expect_raises(OverflowError) { big.to_u64 } - big.to_i64!.should eq(8446744073709551617) # -9999999999999999999 + 2**64 - big.to_u64!.should eq(8446744073709551617) + describe "#to_i{{n}}" do + it "converts to Int{{n}}" do + (0..{{n - 2}}).each do |i| + (1.to_big_i << i).to_i{{n}}.should eq(Int{{n}}.new(1) << i) + (-1.to_big_i << i).to_i{{n}}.should eq(Int{{n}}.new(-1) << i) + end + + Int{{n}}.zero.to_big_i.to_i{{n}}.should eq(Int{{n}}.zero) + Int{{n}}::MAX.to_big_i.to_i{{n}}.should eq(Int{{n}}::MAX) + Int{{n}}::MIN.to_big_i.to_i{{n}}.should eq(Int{{n}}::MIN) + end + + it "raises OverflowError" do + expect_raises(OverflowError) { (Int{{n}}::MAX.to_big_i + 1).to_i{{n}} } + expect_raises(OverflowError) { (Int{{n}}::MIN.to_big_i - 1).to_i{{n}} } + expect_raises(OverflowError) { (1.to_big_i << {{n}}).to_i{{n}} } + expect_raises(OverflowError) { (-1.to_big_i << {{n}}).to_i{{n}} } + end end - it "negative above 64 bits" do - big = BigInt.new("-" + "9" * 20) - expect_raises(OverflowError) { big.to_i64 } - expect_raises(OverflowError) { big.to_u64 } - big.to_i64!.should eq(-7766279631452241919) # -9999999999999999999 + 5*(2**64) - big.to_u64!.should eq(10680464442257309697u64) # -9999999999999999999 + 6*(2**64) - - big = BigInt.new("-" + "9" * 32) - expect_raises(OverflowError) { big.to_i64 } - expect_raises(OverflowError) { big.to_u64 } - big.to_i64!.should eq(8814407033341083649) # -99999999999999999999999999999999 + 5421010862428*(2**64) - big.to_u64!.should eq(8814407033341083649) + describe "#to_u{{n}}!" do + it "converts to UInt{{n}}" do + (0..{{n - 1}}).each do |i| + (1.to_big_i << i).to_u{{n}}!.should eq(UInt{{n}}.new(1) << i) + end + + UInt{{n}}::MAX.to_big_i.to_u{{n}}!.should eq(UInt{{n}}::MAX) + end + + it "converts modulo (2 ** {{n}})" do + (1.to_big_i << {{n}}).to_u{{n}}!.should eq(UInt{{n}}.new(0)) + (-1.to_big_i).to_u{{n}}!.should eq(UInt{{n}}::MAX) + (-1.to_big_i << {{n}}).to_u{{n}}!.should eq(UInt{{n}}.new(0)) + (123.to_big_i - (1.to_big_i << {{n}})).to_u{{n}}!.should eq(UInt{{n}}.new(123)) + (123.to_big_i + (1.to_big_i << {{n}})).to_u{{n}}!.should eq(UInt{{n}}.new(123)) + (123.to_big_i - (1.to_big_i << {{n + 2}})).to_u{{n}}!.should eq(UInt{{n}}.new(123)) + (123.to_big_i + (1.to_big_i << {{n + 2}})).to_u{{n}}!.should eq(UInt{{n}}.new(123)) + end end - end - it "can cast UInt64::MAX to UInt64 (#2264)" do - BigInt.new(UInt64::MAX).to_u64.should eq(UInt64::MAX) - end + describe "#to_i{{n}}!" do + it "converts to Int{{n}}" do + (0..126).each do |i| + (1.to_big_i << i).to_i{{n}}!.should eq(Int{{n}}.new(1) << i) + (-1.to_big_i << i).to_i{{n}}!.should eq(Int{{n}}.new(-1) << i) + end + + Int{{n}}::MAX.to_big_i.to_i{{n}}!.should eq(Int{{n}}::MAX) + Int{{n}}::MIN.to_big_i.to_i{{n}}!.should eq(Int{{n}}::MIN) + end + + it "converts modulo (2 ** {{n}})" do + (1.to_big_i << {{n - 1}}).to_i{{n}}!.should eq(Int{{n}}::MIN) + (1.to_big_i << {{n}}).to_i{{n}}!.should eq(Int{{n}}.new(0)) + (-1.to_big_i << {{n}}).to_i{{n}}!.should eq(Int{{n}}.new(0)) + (123.to_big_i - (1.to_big_i << {{n}})).to_i{{n}}!.should eq(Int{{n}}.new(123)) + (123.to_big_i + (1.to_big_i << {{n}})).to_i{{n}}!.should eq(Int{{n}}.new(123)) + (123.to_big_i - (1.to_big_i << {{n + 2}})).to_i{{n}}!.should eq(Int{{n}}.new(123)) + (123.to_big_i + (1.to_big_i << {{n + 2}})).to_i{{n}}!.should eq(Int{{n}}.new(123)) + end + end + {% end %} it "does String#to_big_i" do "123456789123456789".to_big_i.should eq(BigInt.new("123456789123456789")) diff --git a/src/big/big_int.cr b/src/big/big_int.cr index 7e75d3a9ec77..9cc3f15e5890 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -61,6 +61,8 @@ struct BigInt < Int num = num.abs_unsigned capacity = (num.bit_length - 1) // (sizeof(LibGMP::MpLimb) * 8) + 1 + # This assumes GMP wasn't built with its experimental nails support: + # https://gmplib.org/manual/Low_002dlevel-Functions unsafe_build(capacity) do |limbs| appender = limbs.to_unsafe.appender limbs.size.times do @@ -85,6 +87,16 @@ struct BigInt < Int new(mpz) end + # Returns a read-only `Slice` of the limbs that make up this integer, which + # is effectively `abs.digits(2 ** N)` where `N` is the number of bits in + # `LibGMP::MpLimb`, except that an empty `Slice` is returned for zero. + # + # This assumes GMP wasn't built with its experimental nails support: + # https://gmplib.org/manual/Low_002dlevel-Functions + private def limbs + Slice.new(LibGMP.limbs_read(self), LibGMP.size(self), read_only: true) + end + # :ditto: # # *num* must be finite. @@ -624,92 +636,114 @@ struct BigInt < Int to_i32 end - def to_i8 : Int8 - to_i32.to_i8 + def to_i! : Int32 + to_i32! end - def to_i16 : Int16 - to_i32.to_i16 + def to_u : UInt32 + to_u32 end - def to_i32 : Int32 - LibGMP.get_si(self).to_i32 + def to_u! : UInt32 + to_u32! end - def to_i64 : Int64 - if LibGMP::Long::MIN <= self <= LibGMP::Long::MAX - LibGMP.get_si(self).to_i64 - else - to_s.to_i64 { raise OverflowError.new } + {% for n in [8, 16, 32, 64, 128] %} + def to_i{{n}} : Int{{n}} + \{% if Int{{n}} == LibGMP::SI %} + LibGMP.{{ flag?(:win32) ? "fits_si_p".id : "fits_slong_p".id }}(self) != 0 ? LibGMP.get_si(self) : raise OverflowError.new + \{% elsif Int{{n}}::MAX.is_a?(NumberLiteral) && Int{{n}}::MAX < LibGMP::SI::MAX %} + LibGMP::SI.new(self).to_i{{n}} + \{% else %} + to_primitive_i(Int{{n}}) + \{% end %} end - end - def to_i! - to_i32! - end - - def to_i8! : Int8 - LibGMP.get_si(self).to_i8! - end + def to_u{{n}} : UInt{{n}} + \{% if UInt{{n}} == LibGMP::UI %} + LibGMP.{{ flag?(:win32) ? "fits_ui_p".id : "fits_ulong_p".id }}(self) != 0 ? LibGMP.get_ui(self) : raise OverflowError.new + \{% elsif UInt{{n}}::MAX.is_a?(NumberLiteral) && UInt{{n}}::MAX < LibGMP::UI::MAX %} + LibGMP::UI.new(self).to_u{{n}} + \{% else %} + to_primitive_u(UInt{{n}}) + \{% end %} + end - def to_i16! : Int16 - LibGMP.get_si(self).to_i16! - end + def to_i{{n}}! : Int{{n}} + to_u{{n}}!.to_i{{n}}! + end - def to_i32! : Int32 - LibGMP.get_si(self).to_i32! - end + def to_u{{n}}! : UInt{{n}} + \{% if UInt{{n}} == LibGMP::UI %} + LibGMP.get_ui(self) &* sign + \{% elsif UInt{{n}}::MAX.is_a?(NumberLiteral) && UInt{{n}}::MAX < LibGMP::UI::MAX %} + LibGMP::UI.new!(self).to_u{{n}}! + \{% else %} + to_primitive_u!(UInt{{n}}) + \{% end %} + end + {% end %} - def to_i64! : Int64 - (self % BITS64).to_u64.to_i64! + private def to_primitive_i(type : T.class) : T forall T + self >= 0 ? to_primitive_i_positive(T) : to_primitive_i_negative(T) end - def to_u : UInt32 - to_u32 + private def to_primitive_u(type : T.class) : T forall T + self >= 0 ? to_primitive_i_positive(T) : raise OverflowError.new end - def to_u8 : UInt8 - to_u32.to_u8 - end + private def to_primitive_u!(type : T.class) : T forall T + limbs = self.limbs + max_bits = sizeof(T) * 8 + bits_per_limb = sizeof(LibGMP::MpLimb) * 8 - def to_u16 : UInt16 - to_u32.to_u16 + x = T.zero + limbs.each_with_index do |limb, i| + break if i * bits_per_limb >= max_bits + x |= T.new!(limb) << (i * bits_per_limb) + end + x &* sign end - def to_u32 : UInt32 - to_u64.to_u32 - end + private def to_primitive_i_positive(type : T.class) : T forall T + limbs = self.limbs + bits_per_limb = sizeof(LibGMP::MpLimb) * 8 - def to_u64 : UInt64 - if LibGMP::ULong::MIN <= self <= LibGMP::ULong::MAX - LibGMP.get_ui(self).to_u64 - else - to_s.to_u64 { raise OverflowError.new } + highest_limb_index = (sizeof(T) * 8 - 1) // bits_per_limb + raise OverflowError.new if limbs.size > highest_limb_index + 1 + if highest_limb = limbs[highest_limb_index]? + mask = LibGMP::MpLimb.new!(T::MAX >> (bits_per_limb * highest_limb_index)) + raise OverflowError.new if highest_limb > mask end - end - def to_u! - to_u32! - end - - def to_u8! : UInt8 - LibGMP.get_ui(self).to_u8! + x = T.zero + preshift_limit = T::MAX >> bits_per_limb + limbs.reverse_each do |limb| + x <<= bits_per_limb + x |= limb + end + x end - def to_u16! : UInt16 - LibGMP.get_ui(self).to_u16! - end + private def to_primitive_i_negative(type : T.class) : T forall T + limbs = self.limbs + bits_per_limb = sizeof(LibGMP::MpLimb) * 8 - def to_u32! : UInt32 - LibGMP.get_ui(self).to_u32! - end + x = T.zero.abs_unsigned + limit = T::MIN.abs_unsigned + preshift_limit = limit >> bits_per_limb + limbs.reverse_each do |limb| + raise OverflowError.new if x > preshift_limit + x <<= bits_per_limb - def to_u64! : UInt64 - (self % BITS64).to_u64 + # precondition: T must be larger than LibGMP::MpLimb, otherwise overflows + # like `0_i8 | 256` would happen and `x += limb` should be called instead + x |= limb + raise OverflowError.new if x > limit + end + x.neg_signed end - private BITS64 = BigInt.new(1) << 64 - def to_f : Float64 to_f64 end diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index c502cc5a47d7..e3fcf09dfd57 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -68,6 +68,7 @@ lib LibGMP fun get_si = __gmpz_get_si(op : MPZ*) : SI fun get_ui = __gmpz_get_ui(op : MPZ*) : UI fun get_d = __gmpz_get_d(op : MPZ*) : Double + fun get_d_2exp = __gmpz_get_d_2exp(exp : Long*, op : MPZ*) : Double # # Arithmetic @@ -136,9 +137,6 @@ lib LibGMP fun cmp_ui = __gmpz_cmp_ui(op1 : MPZ*, op2 : UI) : Int fun cmp_d = __gmpz_cmp_d(op1 : MPZ*, op2 : Double) : Int - # # Conversion - fun get_d_2exp = __gmpz_get_d_2exp(exp : Long*, op : MPZ*) : Double - # # Number Theoretic Functions fun gcd = __gmpz_gcd(rop : MPZ*, op1 : MPZ*, op2 : MPZ*) @@ -147,8 +145,19 @@ lib LibGMP fun lcm_ui = __gmpz_lcm_ui(rop : MPZ*, op1 : MPZ*, op2 : UI) fun remove = __gmpz_remove(rop : MPZ*, op : MPZ*, f : MPZ*) : BitcntT - # Special Functions + # # Miscellaneous Functions + + fun fits_ulong_p = __gmpz_fits_ulong_p(op : MPZ*) : Int + fun fits_slong_p = __gmpz_fits_slong_p(op : MPZ*) : Int + {% if flag?(:win32) %} + fun fits_ui_p = __gmpz_fits_ui_p(op : MPZ*) : Int + fun fits_si_p = __gmpz_fits_si_p(op : MPZ*) : Int + {% end %} + + # # Special Functions + fun size = __gmpz_size(op : MPZ*) : SizeT + fun limbs_read = __gmpz_limbs_read(x : MPZ*) : MpLimb* fun limbs_write = __gmpz_limbs_write(x : MPZ*, n : MpSize) : MpLimb* fun limbs_finish = __gmpz_limbs_finish(x : MPZ*, s : MpSize) diff --git a/src/int.cr b/src/int.cr index 9033348e7ef1..4f10b543fc67 100644 --- a/src/int.cr +++ b/src/int.cr @@ -895,6 +895,11 @@ struct Int8 self < 0 ? 0_u8 &- self : to_u8! end + # :nodoc: + def neg_signed : self + -self + end + def popcount : Int8 Intrinsics.popcount8(self) end @@ -1000,6 +1005,11 @@ struct Int16 self < 0 ? 0_u16 &- self : to_u16! end + # :nodoc: + def neg_signed : self + -self + end + def popcount : Int16 Intrinsics.popcount16(self) end @@ -1105,6 +1115,11 @@ struct Int32 self < 0 ? 0_u32 &- self : to_u32! end + # :nodoc: + def neg_signed : self + -self + end + def popcount : Int32 Intrinsics.popcount32(self) end @@ -1210,6 +1225,11 @@ struct Int64 self < 0 ? 0_u64 &- self : to_u64! end + # :nodoc: + def neg_signed : self + -self + end + def popcount : Int64 Intrinsics.popcount64(self) end @@ -1318,6 +1338,11 @@ struct Int128 self < 0 ? UInt128.new(0) &- self : to_u128! end + # :nodoc: + def neg_signed : self + -self + end + def popcount Intrinsics.popcount128(self) end @@ -1427,6 +1452,11 @@ struct UInt8 self end + # :nodoc: + def neg_signed : Int8 + 0_i8 - self + end + def popcount : Int8 Intrinsics.popcount8(self) end @@ -1536,6 +1566,11 @@ struct UInt16 self end + # :nodoc: + def neg_signed : Int16 + 0_i16 - self + end + def popcount : Int16 Intrinsics.popcount16(self) end @@ -1645,6 +1680,11 @@ struct UInt32 self end + # :nodoc: + def neg_signed : Int32 + 0_i32 - self + end + def popcount : Int32 Intrinsics.popcount32(self) end @@ -1754,6 +1794,11 @@ struct UInt64 self end + # :nodoc: + def neg_signed : Int64 + 0_i64 - self + end + def popcount : Int64 Intrinsics.popcount64(self) end @@ -1865,6 +1910,11 @@ struct UInt128 self end + # :nodoc: + def neg_signed : Int128 + Int128.new(0) - self + end + def popcount Intrinsics.popcount128(self) end From 52b8e3bd3065f517d4bcdafd4d130a9cc3839a08 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 25 Jun 2023 23:46:49 +0800 Subject: [PATCH 0604/1551] Fix leap second handling for timezone information files (#13600) --- src/time/location/loader.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/time/location/loader.cr b/src/time/location/loader.cr index ffda4929f0c2..7eb45cb20b19 100644 --- a/src/time/location/loader.cr +++ b/src/time/location/loader.cr @@ -110,7 +110,7 @@ class Time::Location abbreviations = read_buffer(io, abbrev_length) - leap_second_time_pairs = Bytes.new(num_leap_seconds) + leap_second_time_pairs = Bytes.new(num_leap_seconds * 8) io.read_fully(leap_second_time_pairs) isstddata = Bytes.new(num_std_wall) From c8d9b39cd5b15a483221f5d1003c4a7facc65d40 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 27 Jun 2023 20:12:52 +0800 Subject: [PATCH 0605/1551] Windows: drop internal environment variables from `ENV` (#13570) --- spec/std/env_spec.cr | 10 ++++++++++ src/crystal/system/win32/env.cr | 22 ++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/spec/std/env_spec.cr b/spec/std/env_spec.cr index 5d316751aa4a..8342494b10f6 100644 --- a/spec/std/env_spec.cr +++ b/spec/std/env_spec.cr @@ -173,4 +173,14 @@ describe "ENV" do ensure ENV.delete("FOO") end + + {% if flag?(:win32) %} + it "skips internal environment variables" do + key = "=#{Path[Dir.current].drive}" + ENV.has_key?(key).should be_false + ENV[key]?.should be_nil + expect_raises(ArgumentError) { ENV[key] = "foo" } + expect_raises(ArgumentError) { ENV[key] = nil } + end + {% end %} end diff --git a/src/crystal/system/win32/env.cr b/src/crystal/system/win32/env.cr index 490ad70a1ea1..44cfd32a6dff 100644 --- a/src/crystal/system/win32/env.cr +++ b/src/crystal/system/win32/env.cr @@ -5,6 +5,7 @@ require "c/processenv" module Crystal::System::Env # Sets an environment variable or unsets it if *value* is `nil`. def self.set(key : String, value : String) : Nil + check_valid_key(key) key = System.to_wstr(key, "key") value = System.to_wstr(value, "value") @@ -15,6 +16,7 @@ module Crystal::System::Env # Unsets an environment variable. def self.set(key : String, value : Nil) : Nil + check_valid_key(key) key = System.to_wstr(key, "key") if LibC.SetEnvironmentVariableW(key, nil) == 0 @@ -24,6 +26,7 @@ module Crystal::System::Env # Gets an environment variable. def self.get(key : String) : String? + return nil unless valid_key?(key) key = System.to_wstr(key, "key") System.retry_wstr_buffer do |buffer, small_buf| @@ -52,6 +55,7 @@ module Crystal::System::Env # Returns `true` if environment variable is set. def self.has_key?(key : String) : Bool + return false unless valid_key?(key) key = System.to_wstr(key, "key") buffer = uninitialized UInt16[1] @@ -66,10 +70,10 @@ module Crystal::System::Env begin while !pointer.value.zero? string, pointer = String.from_utf16(pointer) + # Skip internal environment variables that are reserved by `cmd.exe` + # (`%=ExitCode%`, `%=ExitCodeAscii%`, `%=::%`, `%=C:%` ...) + next if string.starts_with?('=') key, _, value = string.partition('=') - # The actual env variables are preceded by these weird lines in the output: - # "=::=::\", "=C:=c:\foo\bar", "=ExitCode=00000000" -- skip them. - next if key.empty? yield key, value end ensure @@ -83,12 +87,18 @@ module Crystal::System::Env # `System.to_wstr` here String.build do |io| env.each do |(key, value)| - if key.includes?('=') || key.empty? - raise ArgumentError.new("Invalid env key #{key.inspect}") - end + check_valid_key(key) io << key.check_no_null_byte("key") << '=' << value.check_no_null_byte("value") << '\0' end io << '\0' end.to_utf16.to_unsafe end + + private def self.valid_key?(key : String) + !(key.empty? || key.includes?('=')) + end + + private def self.check_valid_key(key : String) + raise ArgumentError.new("Invalid env key #{key.inspect}") unless valid_key?(key) + end end From 9b3b1a555e5821836aac9c24adaa89be432bc90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 27 Jun 2023 20:01:51 +0200 Subject: [PATCH 0606/1551] Fix: Avoid calling realpath of parent crystal in wrapper script (#13596) --- bin/crystal | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/bin/crystal b/bin/crystal index 8e42d4e7ed86..3f7ceb1b88f4 100755 --- a/bin/crystal +++ b/bin/crystal @@ -153,11 +153,8 @@ if [ -z "${PARENT_CRYSTAL##*/*}" -a "$(realpath "$PARENT_CRYSTAL")" = "$SCRIPT_P PARENT_CRYSTAL="crystal" fi -PARENT_CRYSTAL_COMMAND=$(realpath "$(command -v "$PARENT_CRYSTAL" 2> /dev/null)" 2> /dev/null) -PARENT_CRYSTAL_EXISTS=$(test !$?) - # check if the parent crystal command refers to this script -if [ "$PARENT_CRYSTAL_COMMAND" = "$SCRIPT_PATH" ]; then +if [ "$(realpath "$(command -v "$PARENT_CRYSTAL" 2> /dev/null)" 2> /dev/null)" = "$SCRIPT_PATH" ]; then # remove the path to this script from PATH NEW_PATH="$(remove_path_item "$(remove_path_item "$PATH" "$SCRIPT_ROOT")" "bin")" # if the PATH did not change it will lead to an infinite recursion => display error @@ -165,17 +162,18 @@ if [ "$PARENT_CRYSTAL_COMMAND" = "$SCRIPT_PATH" ]; then __error_msg 'Could not remove the script bin/crystal from the PATH. Remove it by hand or set the CRYSTAL env variable' exit 1 fi - PARENT_CRYSTAL_COMMAND=$(realpath "$(PATH=$NEW_PATH command -v "$PARENT_CRYSTAL" 2> /dev/null)" 2> /dev/null) - PARENT_CRYSTAL_EXISTS=$(test !$?) - if [ "$PARENT_CRYSTAL_COMMAND" = "$SCRIPT_PATH" ]; then + PARENT_CRYSTAL=$(PATH=$NEW_PATH command -v "$PARENT_CRYSTAL" 2> /dev/null) + if [ "$(realpath "$PARENT_CRYSTAL" 2> /dev/null)" = "$SCRIPT_PATH" ]; then __error_msg 'Could not remove the script bin/crystal from the PATH. Remove it by hand or set the CRYSTAL env variable' exit 1 fi fi +command -v "$PARENT_CRYSTAL" > /dev/null 2> /dev/null +PARENT_CRYSTAL_EXISTS=$(test !$?) if ($PARENT_CRYSTAL_EXISTS); then if [ -z "$CRYSTAL_CONFIG_LIBRARY_PATH" ] || [ -z "$CRYSTAL_LIBRARY_PATH" ]; then - CRYSTAL_INSTALLED_LIBRARY_PATH="$($PARENT_CRYSTAL_COMMAND env CRYSTAL_LIBRARY_PATH 2> /dev/null || echo "")" + CRYSTAL_INSTALLED_LIBRARY_PATH="$($PARENT_CRYSTAL env CRYSTAL_LIBRARY_PATH 2> /dev/null || echo "")" export CRYSTAL_LIBRARY_PATH=${CRYSTAL_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} export CRYSTAL_CONFIG_LIBRARY_PATH=${CRYSTAL_CONFIG_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} fi @@ -193,5 +191,5 @@ elif !($PARENT_CRYSTAL_EXISTS); then __error_msg 'You need to have a crystal executable in your path! or set CRYSTAL env variable' exit 1 else - exec "$PARENT_CRYSTAL_COMMAND" "$@" + exec "$PARENT_CRYSTAL" "$@" fi From f692397d9661271f9e4f5978e6719f42fa07e493 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 28 Jun 2023 02:02:15 +0800 Subject: [PATCH 0607/1551] Publish the `assert_prints` spec helper (#13599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/manual/string_normalize_spec.cr | 26 +------- spec/std/base64_spec.cr | 2 +- spec/std/big/big_decimal_spec.cr | 2 +- spec/std/big/big_float_spec.cr | 2 +- spec/std/char_spec.cr | 2 +- spec/std/csv/csv_build_spec.cr | 2 +- spec/std/enum_spec.cr | 2 +- spec/std/float_printer_spec.cr | 2 +- spec/std/float_spec.cr | 2 +- spec/std/http/http_spec.cr | 2 +- spec/std/humanize_spec.cr | 2 +- spec/std/json/builder_spec.cr | 2 +- spec/std/mime/media_type_spec.cr | 2 +- spec/std/process/status_spec.cr | 2 +- spec/std/slice_spec.cr | 2 +- spec/std/socket/address_spec.cr | 2 +- spec/std/sprintf_spec.cr | 2 +- spec/std/string_spec.cr | 11 ++-- spec/std/time/format_spec.cr | 2 +- spec/std/uri_spec.cr | 2 +- spec/std/uuid_spec.cr | 2 +- spec/std/xml/builder_spec.cr | 2 +- spec/std/xml/xml_spec.cr | 2 +- spec/std/yaml/builder_spec.cr | 2 +- spec/support/string.cr | 61 ------------------ src/spec/helpers/string.cr | 92 ++++++++++++++++++++++++++++ 26 files changed, 123 insertions(+), 111 deletions(-) delete mode 100644 spec/support/string.cr create mode 100644 src/spec/helpers/string.cr diff --git a/spec/manual/string_normalize_spec.cr b/spec/manual/string_normalize_spec.cr index 96d58c946894..9a67a891b702 100644 --- a/spec/manual/string_normalize_spec.cr +++ b/spec/manual/string_normalize_spec.cr @@ -1,6 +1,6 @@ require "spec" require "http/client" -require "../support/string" +require "spec/helpers/string" UCD_ROOT = "http://www.unicode.org/Public/#{Unicode::VERSION}/ucd/" @@ -27,31 +27,9 @@ private struct CodepointsEqualExpectation end end -# same as `assert_prints`, but uses `CodepointsEqualExpectation` instead of `eq` private macro assert_prints_codepoints(call, str, desc, *, file = __FILE__, line = __LINE__) %expectation = CodepointsEqualExpectation.new(({{ str }}).as(String), {{ desc }}) - - %result = {{ call }} - %result.should be_a(String), file: {{ file }}, line: {{ line }} - %result.should %expectation, file: {{ file }}, line: {{ line }} - - String.build do |io| - {% if call.receiver %}{{ call.receiver }}.{% end %}{{ call.name }}( - io, - {% for arg in call.args %} {{ arg }}, {% end %} - {% if call.named_args %} {% for narg in call.named_args %} {{ narg.name }}: {{ narg.value }}, {% end %} {% end %} - ) {{ call.block }} - end.should %expectation, file: {{ file }}, line: {{ line }} - - {% unless flag?(:without_iconv) %} - string_build_via_utf16 do |io| - {% if call.receiver %}{{ call.receiver }}.{% end %}{{ call.name }}( - io, - {% for arg in call.args %} {{ arg }}, {% end %} - {% if call.named_args %} {% for narg in call.named_args %} {{ narg.name }}: {{ narg.value }}, {% end %} {% end %} - ) {{ call.block }} - end.should %expectation, file: {{ file }}, line: {{ line }} - {% end %} + assert_prints({{ call }}, should: %expectation, file: {{ file }}, line: {{ line }}) end private def assert_normalized(source, target, form : Unicode::NormalizationForm, *, file = __FILE__, line = __LINE__) diff --git a/spec/std/base64_spec.cr b/spec/std/base64_spec.cr index 89e5d863825a..567340d6fa38 100644 --- a/spec/std/base64_spec.cr +++ b/spec/std/base64_spec.cr @@ -1,7 +1,7 @@ require "spec" require "base64" require "crystal/digest/md5" -require "../support/string" +require "spec/helpers/string" # rearrange parameters for `assert_prints` {% for method in %w(encode strict_encode urlsafe_encode) %} diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index 085736de5294..81fd2fb82b36 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -1,6 +1,6 @@ require "spec" require "big" -require "../../support/string" +require "spec/helpers/string" describe BigDecimal do it "initializes from valid input" do diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index 69322ef86c1a..beea465df8c3 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -1,6 +1,6 @@ require "spec" require "big" -require "../../support/string" +require "spec/helpers/string" private def it_converts_to_s(value : BigFloat, str, *, file = __FILE__, line = __LINE__) it "converts to #{str}", file: file, line: line do diff --git a/spec/std/char_spec.cr b/spec/std/char_spec.cr index 2c5ea0345252..e947fc17d296 100644 --- a/spec/std/char_spec.cr +++ b/spec/std/char_spec.cr @@ -1,7 +1,7 @@ require "spec" require "unicode" require "spec/helpers/iterate" -require "../support/string" +require "spec/helpers/string" describe "Char" do describe "#upcase" do diff --git a/spec/std/csv/csv_build_spec.cr b/spec/std/csv/csv_build_spec.cr index 7cbb91f7f746..b222df22ab93 100644 --- a/spec/std/csv/csv_build_spec.cr +++ b/spec/std/csv/csv_build_spec.cr @@ -1,6 +1,6 @@ require "spec" require "csv" -require "../../support/string" +require "spec/helpers/string" describe CSV do describe "build" do diff --git a/spec/std/enum_spec.cr b/spec/std/enum_spec.cr index 0b7c846138c2..437ba42e1c1f 100644 --- a/spec/std/enum_spec.cr +++ b/spec/std/enum_spec.cr @@ -1,5 +1,5 @@ require "spec" -require "../support/string" +require "spec/helpers/string" enum SpecEnum : Int8 One diff --git a/spec/std/float_printer_spec.cr b/spec/std/float_printer_spec.cr index 94c2409c71a9..3db7b344b170 100644 --- a/spec/std/float_printer_spec.cr +++ b/spec/std/float_printer_spec.cr @@ -35,7 +35,7 @@ require "spec" require "./spec_helper" -require "../support/string" +require "spec/helpers/string" require "../support/number" # Tests that `v.to_s` is the same as the *v* literal is written in the source diff --git a/spec/std/float_spec.cr b/spec/std/float_spec.cr index b93597c389c6..4ddfd194313d 100644 --- a/spec/std/float_spec.cr +++ b/spec/std/float_spec.cr @@ -1,5 +1,5 @@ require "spec" -require "../support/string" +require "spec/helpers/string" describe "Float" do describe "**" do diff --git a/spec/std/http/http_spec.cr b/spec/std/http/http_spec.cr index 6d23b9356cd6..6159cdc9dc5e 100644 --- a/spec/std/http/http_spec.cr +++ b/spec/std/http/http_spec.cr @@ -1,6 +1,6 @@ require "spec" require "http" -require "../../support/string" +require "spec/helpers/string" private def http_quote_string(io : IO, string) HTTP.quote_string(string, io) diff --git a/spec/std/humanize_spec.cr b/spec/std/humanize_spec.cr index a7342b495947..482be02b77eb 100644 --- a/spec/std/humanize_spec.cr +++ b/spec/std/humanize_spec.cr @@ -1,5 +1,5 @@ require "spec" -require "../support/string" +require "spec/helpers/string" private LENGTH_UNITS = ->(magnitude : Int32, number : Float64) do case magnitude diff --git a/spec/std/json/builder_spec.cr b/spec/std/json/builder_spec.cr index 66f9be33b741..2bbd9c49a5ef 100644 --- a/spec/std/json/builder_spec.cr +++ b/spec/std/json/builder_spec.cr @@ -1,6 +1,6 @@ require "spec" require "json" -require "../../support/string" +require "spec/helpers/string" private def assert_built(expected, *, file = __FILE__, line = __LINE__, &) assert_prints JSON.build { |json| with json yield json }, expected, file: file, line: line diff --git a/spec/std/mime/media_type_spec.cr b/spec/std/mime/media_type_spec.cr index 5ed73256ecb4..383e62c5ea42 100644 --- a/spec/std/mime/media_type_spec.cr +++ b/spec/std/mime/media_type_spec.cr @@ -1,6 +1,6 @@ require "../spec_helper" require "mime/media_type" -require "../../support/string" +require "spec/helpers/string" private def parse(string) type = MIME::MediaType.parse(string) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 871e2bd837df..45f60aba4c06 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -1,5 +1,5 @@ require "spec" -require "../../support/string" +require "spec/helpers/string" private def exit_status(status) {% if flag?(:unix) %} diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr index 72803e0b661b..4a2469f69aa7 100644 --- a/spec/std/slice_spec.cr +++ b/spec/std/slice_spec.cr @@ -1,6 +1,6 @@ require "spec" require "spec/helpers/iterate" -require "../support/string" +require "spec/helpers/string" private class BadSortingClass include Comparable(self) diff --git a/spec/std/socket/address_spec.cr b/spec/std/socket/address_spec.cr index 6835b8e518d1..d2e4768db987 100644 --- a/spec/std/socket/address_spec.cr +++ b/spec/std/socket/address_spec.cr @@ -1,7 +1,7 @@ require "spec" require "socket" require "../../support/win32" -require "../../support/string" +require "spec/helpers/string" describe Socket::Address do describe ".parse" do diff --git a/spec/std/sprintf_spec.cr b/spec/std/sprintf_spec.cr index bd72c962bf35..ad6ceda4809d 100644 --- a/spec/std/sprintf_spec.cr +++ b/spec/std/sprintf_spec.cr @@ -1,5 +1,5 @@ require "./spec_helper" -require "../support/string" +require "spec/helpers/string" require "big" # use same name for `sprintf` and `IO#printf` so that `assert_prints` can be leveraged diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 7a9f587126f3..905930463cfc 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -1,6 +1,6 @@ require "./spec_helper" require "spec/helpers/iterate" -require "../support/string" +require "spec/helpers/string" describe "String" do describe "[]" do @@ -692,7 +692,8 @@ describe "String" do end it "does not touch invalid code units in an otherwise ascii string" do - assert_prints "\xB5!\xE0\xC1\xB5?".capitalize, "\xB5!\xE0\xC1\xB5?" + "\xB5!\xE0\xC1\xB5?".capitalize.should eq("\xB5!\xE0\xC1\xB5?") + String.build { |io| "\xB5!\xE0\xC1\xB5?".capitalize(io) }.should eq("\xB5!\xE0\xC1\xB5?".scrub) end end @@ -710,8 +711,10 @@ describe "String" do end it "does not touch invalid code units in an otherwise ascii string" do - assert_prints "\xB5!\xE0\xC1\xB5?".titleize, "\xB5!\xE0\xC1\xB5?" - assert_prints "a\xA0b".titleize, "A\xA0b" + "\xB5!\xE0\xC1\xB5?".titleize.should eq("\xB5!\xE0\xC1\xB5?") + "a\xA0b".titleize.should eq("A\xA0b") + String.build { |io| "\xB5!\xE0\xC1\xB5?".titleize(io) }.should eq("\xB5!\xE0\xC1\xB5?".scrub) + String.build { |io| "a\xA0b".titleize(io) }.should eq("A\xA0b".scrub) end end diff --git a/spec/std/time/format_spec.cr b/spec/std/time/format_spec.cr index 08baa2e910fd..8e7cb27b6bd7 100644 --- a/spec/std/time/format_spec.cr +++ b/spec/std/time/format_spec.cr @@ -1,5 +1,5 @@ require "./spec_helper" -require "../../support/string" +require "spec/helpers/string" def parse_time(format, string) Time.parse_utc(format, string) diff --git a/spec/std/uri_spec.cr b/spec/std/uri_spec.cr index 402263742a96..9f0e69458629 100644 --- a/spec/std/uri_spec.cr +++ b/spec/std/uri_spec.cr @@ -2,7 +2,7 @@ require "spec" require "uri" require "uri/json" require "uri/yaml" -require "../support/string" +require "spec/helpers/string" private def assert_uri(string, file = __FILE__, line = __LINE__, **args) it "`#{string}`", file, line do diff --git a/spec/std/uuid_spec.cr b/spec/std/uuid_spec.cr index 5086b7964f95..0992c92095d1 100644 --- a/spec/std/uuid_spec.cr +++ b/spec/std/uuid_spec.cr @@ -1,6 +1,6 @@ require "spec" require "uuid" -require "../support/string" +require "spec/helpers/string" describe "UUID" do describe "#==" do diff --git a/spec/std/xml/builder_spec.cr b/spec/std/xml/builder_spec.cr index 0fd7450b0104..3aaaa9f503d0 100644 --- a/spec/std/xml/builder_spec.cr +++ b/spec/std/xml/builder_spec.cr @@ -1,6 +1,6 @@ require "spec" require "xml" -require "../../support/string" +require "spec/helpers/string" private def assert_built(expected, quote_char = nil, *, file = __FILE__, line = __LINE__, &) assert_prints XML.build(quote_char: quote_char) { |xml| with xml yield xml }, expected, file: file, line: line diff --git a/spec/std/xml/xml_spec.cr b/spec/std/xml/xml_spec.cr index 3c695f517497..efbb79e6e226 100644 --- a/spec/std/xml/xml_spec.cr +++ b/spec/std/xml/xml_spec.cr @@ -1,6 +1,6 @@ require "spec" require "xml" -require "../../support/string" +require "spec/helpers/string" describe XML do it "parses" do diff --git a/spec/std/yaml/builder_spec.cr b/spec/std/yaml/builder_spec.cr index e225a745587d..342777445ca6 100644 --- a/spec/std/yaml/builder_spec.cr +++ b/spec/std/yaml/builder_spec.cr @@ -1,6 +1,6 @@ require "spec" require "yaml" -require "../../support/string" +require "spec/helpers/string" private def assert_built(expected, expect_document_end = false, *, file = __FILE__, line = __LINE__, &) # libyaml 0.2.1 removed the erroneously written document end marker (`...`) after some scalars in root context (see https://github.com/yaml/libyaml/pull/18). diff --git a/spec/support/string.cr b/spec/support/string.cr deleted file mode 100644 index be2306bdd509..000000000000 --- a/spec/support/string.cr +++ /dev/null @@ -1,61 +0,0 @@ -# Builds a `String` through a UTF-16 `IO`. -# -# Similar to `String.build`, but the yielded `IO` is configured to use the -# UTF-16 encoding, and the written contents are decoded back into a UTF-8 -# `String`. This method is mainly used by `assert_prints` to test the behaviour -# of string-generating methods under different encodings. -# -# Raises if the `without_iconv` flag is set. -def string_build_via_utf16(& : IO -> _) - {% if flag?(:without_iconv) %} - raise NotImplementedError.new("string_build_via_utf16") - {% else %} - io = IO::Memory.new - io.set_encoding(IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian ? "UTF-16LE" : "UTF-16BE") - yield io - byte_slice = io.to_slice - utf16_slice = byte_slice.unsafe_slice_of(UInt16) - String.from_utf16(utf16_slice) - {% end %} -end - -# Asserts that the given *call* and its `IO`-accepting variants produce the -# given string *str*. -# -# Given a call of the form `foo.bar(*args, **opts)`, tests the following cases: -# -# * This call itself should return a `String` equal to *str*. -# * `String.build { |io| foo.bar(io, *args, **opts) }` should be equal to -# `str.scrub`; writing to a `String::Builder` must not produce any invalid -# UTF-8 byte sequences. -# * `string_build_via_utf16 { |io| foo.bar(io, *args, **opts) }` should also be -# equal to `str.scrub`; that is, the `IO` overload should not fail when the -# `IO` argument uses a non-default encoding. This case is skipped if the -# `without_iconv` flag is set. -macro assert_prints(call, str, *, file = __FILE__, line = __LINE__) - %str = ({{ str }}).as(String) - %file = {{ file }} - %line = {{ line }} - - %result = {{ call }} - %result.should be_a(String), file: %file, line: %line - %result.should eq(%str), file: %file, line: %line - - String.build do |io| - {% if call.receiver %}{{ call.receiver }}.{% end %}{{ call.name }}( - io, - {% for arg in call.args %} {{ arg }}, {% end %} - {% if call.named_args %} {% for narg in call.named_args %} {{ narg.name }}: {{ narg.value }}, {% end %} {% end %} - ) {{ call.block }} - end.should eq(%str.scrub), file: %file, line: %line - - {% unless flag?(:without_iconv) %} - string_build_via_utf16 do |io| - {% if call.receiver %}{{ call.receiver }}.{% end %}{{ call.name }}( - io, - {% for arg in call.args %} {{ arg }}, {% end %} - {% if call.named_args %} {% for narg in call.named_args %} {{ narg.name }}: {{ narg.value }}, {% end %} {% end %} - ) {{ call.block }} - end.should eq(%str.scrub), file: %file, line: %line - {% end %} -end diff --git a/src/spec/helpers/string.cr b/src/spec/helpers/string.cr new file mode 100644 index 000000000000..892d2012524e --- /dev/null +++ b/src/spec/helpers/string.cr @@ -0,0 +1,92 @@ +module Spec::Methods + # Asserts that the given *call* and its `IO`-accepting variant both match the + # given *expectation*, used to test string printing. + # + # Given a call of the form `foo.bar(*args, **opts)`, this tests the following + # cases: + # + # * The call itself. Additionally this call must return a `String`. + # * `String.build { |io| foo.bar(io, *args, **opts) }`, which constructs a + # `String` via an `IO` overload. + # * `io = ...; foo.bar(io, *args, **opts); io.to_s`, where `io` is an `IO` + # configured to use the UTF-16 encoding, and contents written to it are + # decoded back into a UTF-8 `String`. This case ensures that the `IO` + # overload does not produce malformed UTF-8 byte sequences via a non-default + # encoding. This case is skipped if the `without_iconv` flag is set. + # + # The overload that accepts a *str* argument is usually easier to work with. + macro assert_prints(call, *, should expectation, file = __FILE__, line = __LINE__) + %expectation = {{ expectation }} + %file = {{ file }} + %line = {{ line }} + + %result = {{ call }} + %result.should be_a(::String), file: %file, line: %line + %result.should(%expectation, file: %file, line: %line) + + ::String.build do |io| + {% if call.receiver %}{{ call.receiver }}.{% end %}{{ call.name }}( + io, + {% for arg in call.args %} {{ arg }}, {% end %} + {% if call.named_args %} {% for narg in call.named_args %} {{ narg.name }}: {{ narg.value }}, {% end %} {% end %} + ) {{ call.block }} + end.should(%expectation, file: %file, line: %line) + + {% unless flag?(:without_iconv) %} + %utf16_io = ::IO::Memory.new + %utf16_io.set_encoding(::IO::ByteFormat::SystemEndian == ::IO::ByteFormat::LittleEndian ? "UTF-16LE" : "UTF-16BE") + {% if call.receiver %}{{ call.receiver }}.{% end %}{{ call.name }}( + %utf16_io, + {% for arg in call.args %} {{ arg }}, {% end %} + {% if call.named_args %} {% for narg in call.named_args %} {{ narg.name }}: {{ narg.value }}, {% end %} {% end %} + ) {{ call.block }} + %result = ::String.from_utf16(%utf16_io.to_slice.unsafe_slice_of(::UInt16)) + %result.should(%expectation, file: %file, line: %line) + {% end %} + end + + # Asserts that the given *call* and its `IO`-accepting variant both produce + # the given string *str*. + # + # Equivalent to `assert_prints call, should: eq(str)`. *str* must be validly + # encoded in UTF-8. + # + # ``` + # require "spec" + # require "spec/helpers/string" + # + # it "prints integers with `Int#to_s`" do + # assert_prints 123.to_s, "123" + # assert_prints 123.to_s(16), "7b" + # end + # ``` + # + # Methods that do not follow the convention of `IO`-accepting and + # `String`-returning overloads can also be tested as long as suitable wrapper + # methods are defined: + # + # ``` + # require "spec" + # require "spec/helpers/string" + # + # private def fprintf(format, *args) + # sprintf(format, *args) + # end + # + # private def fprintf(io : IO, format, *args) + # io.printf(format, *args) + # end + # + # it "prints with `sprintf` and `IO#printf`" do + # assert_prints fprintf("%d", 123), "123" + # assert_prints fprintf("%x %b", 123, 6), "7b 110" + # end + # ``` + macro assert_prints(call, str, *, file = __FILE__, line = __LINE__) + %str = ({{ str }}).as(::String) + unless %str.valid_encoding? + ::fail "`str` contains invalid UTF-8 byte sequences: #{%str.inspect}", file: {{ file }}, line: {{ line }} + end + assert_prints({{ call }}, should: eq(%str), file: {{ file }}, line: {{ line }}) + end +end From 742d920954b8f11c78f0b262c5bdc667a203cbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 27 Jun 2023 20:02:59 +0200 Subject: [PATCH 0608/1551] Add default interrupt handlers (#13568) --- spec/std/kernel_spec.cr | 61 +++++++++++++++++++++++++++++ src/crystal/system/process.cr | 3 ++ src/crystal/system/unix/process.cr | 6 +++ src/crystal/system/win32/process.cr | 7 ++++ src/kernel.cr | 1 + src/process.cr | 20 ++++++++-- 6 files changed, 94 insertions(+), 4 deletions(-) diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index 149e6385ac97..efe5e81563e4 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -294,3 +294,64 @@ describe "hardware exception" do error.should contain("Stack overflow") end end + +private def compile_and_run_exit_handler(&block : Process -> _) + with_tempfile("source_file") do |source_file| + File.write(source_file, <<-CRYSTAL) + at_exit { print "Exiting" } + print "." + STDOUT.flush + sleep + CRYSTAL + output = nil + compile_file(source_file) do |executable_file| + error = IO::Memory.new + process = Process.new executable_file, output: :pipe, error: error + + spawn do + process.output.read_byte + block.call(process) + output = process.output.gets_to_end + end + + status = process.wait + {status, output, error.to_s} + end + end +end + +describe "default interrupt handlers", tags: %w[slow] do + # TODO: Implementation for sending console control commands on Windows. + # So long this behaviour can only be tested manually. + # + # ``` + # lib LibC + # fun GenerateConsoleCtrlEvent(dwCtrlEvent : DWORD, dwProcessGroupId : DWORD) : BOOL + # end + + # at_exit { print "Exiting"; } + # print "." + # STDOUT.flush + # LibC.GenerateConsoleCtrlEvent(LibC::CTRL_C_EVENT, 0) + # sleep + # ``` + {% unless flag?(:windows) %} + it "handler for SIGINT" do + status, output, _ = compile_and_run_exit_handler(&.signal(Signal::INT)) + output.should eq "Exiting" + status.inspect.should eq "Process::Status[130]" + end + + it "handler for SIGTERM" do + status, output, _ = compile_and_run_exit_handler(&.terminate) + output.should eq "Exiting" + status.inspect.should eq "Process::Status[143]" + end + {% end %} + + it "no handler for SIGKILL" do + status, output, _ = compile_and_run_exit_handler(&.terminate(graceful: false)) + output.should eq "" + status.inspect.should eq {{ flag?(:unix) ? "Process::Status[Signal::KILL]" : "Process::Status[1]" }} + end +end diff --git a/src/crystal/system/process.cr b/src/crystal/system/process.cr index 387447c083c2..8a5598fc7b16 100644 --- a/src/crystal/system/process.cr +++ b/src/crystal/system/process.cr @@ -56,6 +56,9 @@ struct Crystal::System::Process # thread. # def self.start_interrupt_loop + # Trap interrupt to exit normally with `at_exit` handlers being executed. + # def self.setup_default_interrupt_handler + # Whether the process identified by *pid* is still registered in the system. # def self.exists?(pid : Int) : Bool diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index f07a91806857..9212226d9aa4 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -74,6 +74,12 @@ struct Crystal::System::Process # do nothing; `Crystal::System::Signal.start_loop` takes care of this end + def self.setup_default_interrupt_handlers + # Status 128 + signal number indicates process exit was caused by the signal. + ::Signal::INT.trap { ::exit 128 + ::Signal::INT.value } + ::Signal::TERM.trap { ::exit 128 + ::Signal::TERM.value } + end + def self.exists?(pid) ret = LibC.kill(pid, 0) if ret == 0 diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 36878bc935bf..e39db2f46fe4 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -149,6 +149,13 @@ struct Crystal::System::Process end end + def self.setup_default_interrupt_handlers + on_interrupt do + # Exit code 3 indicates `std::abort` was called which corresponds to the interrupt handler + ::exit 3 + end + end + def self.exists?(pid) handle = LibC.OpenProcess(LibC::PROCESS_QUERY_INFORMATION, 0, pid) return false unless handle diff --git a/src/kernel.cr b/src/kernel.cr index 7bca29cef605..5291a4dc57f1 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -569,6 +569,7 @@ end {% else %} Crystal::System::Signal.setup_default_handlers {% end %} + Crystal::System::Process.setup_default_interrupt_handlers # load debug info on start up of the program is executed with CRYSTAL_LOAD_DEBUG_INFO=1 # this will make debug info available on print_frame that is used by Crystal's segfault handler diff --git a/src/process.cr b/src/process.cr index 159b08b39eaa..0eea5263b223 100644 --- a/src/process.cr +++ b/src/process.cr @@ -58,6 +58,10 @@ class Process # * On Unix-like systems, this traps `SIGINT`. # * On Windows, this captures Ctrl + C and # Ctrl + Break signals sent to a console application. + # + # The default interrupt handler calls `::exit` to ensure `at_exit` handlers + # execute. It returns a platform-specific status code indicating an interrupt + # (`130` on Unix, `3` on Windows). def self.on_interrupt(&handler : ->) : Nil Crystal::System::Process.on_interrupt(&handler) end @@ -72,8 +76,14 @@ class Process end # Restores default handling of interrupt requests. + # + # The default interrupt handler calls `::exit` to ensure `at_exit` handlers + # execute. It returns a platform-specific status code indicating an interrupt + # (`130` on Unix, `3` on Windows). def self.restore_interrupts! : Nil Crystal::System::Process.restore_interrupts! + + Crystal::System::Process.setup_default_interrupt_handlers end # Returns `true` if the process identified by *pid* is valid for @@ -301,10 +311,12 @@ class Process end end - # :nodoc: - def initialize(pid : LibC::PidT) - @process_info = Crystal::System::Process.new(pid) - end + {% if flag?(:unix) %} + # :nodoc: + def initialize(pid : LibC::PidT) + @process_info = Crystal::System::Process.new(pid) + end + {% end %} # Sends *signal* to this process. # From 95650278cd496e0c9c037a8b1f07118a5d51625b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 28 Jun 2023 05:21:28 +0800 Subject: [PATCH 0609/1551] Fix: Display `Bool`'s size as 1 byte in `crystal tool hierarchy`, not 0 (#13588) --- spec/compiler/crystal/tools/hierarchy_spec.cr | 20 +++++++++++++++++++ src/compiler/crystal/codegen/codegen.cr | 3 --- src/compiler/crystal/interpreter/compiler.cr | 3 ++- .../crystal/interpreter/local_vars.cr | 3 ++- src/llvm/target_data.cr | 3 ++- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/spec/compiler/crystal/tools/hierarchy_spec.cr b/spec/compiler/crystal/tools/hierarchy_spec.cr index 9e01acded7ec..ae1bcd2dbadb 100644 --- a/spec/compiler/crystal/tools/hierarchy_spec.cr +++ b/spec/compiler/crystal/tools/hierarchy_spec.cr @@ -21,6 +21,26 @@ describe Crystal::TextHierarchyPrinter do +- class Bar (4 bytes)\n EOS end + + it "shows correct size for Bool member" do + program = semantic(<<-CRYSTAL).program + struct Foo + @x = true + end + CRYSTAL + + output = String.build { |io| Crystal.print_hierarchy(program, io, "Foo", "text") } + output.should eq(<<-EOS) + - class Object (4 bytes) + | + +- struct Value (0 bytes) + | + +- struct Struct (0 bytes) + | + +- struct Foo (1 bytes) + @x : Bool (1 bytes)\n + EOS + end end describe Crystal::JSONHierarchyPrinter do diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index ae41939fca83..e036efbf48e8 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -92,9 +92,6 @@ module Crystal # `Pointer(Void).malloc` must work like `Pointer(UInt8).malloc`, # that is, consider Void like the size of a byte. 1 - elsif type.is_a?(BoolType) - # LLVM reports 0 for bool (i1) but it must be 1 because it does occupy memory - 1 else llvm_typer.size_of(llvm_typer.llvm_type(type)) end diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 8f59b63ec667..eddc87d65132 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -2010,7 +2010,8 @@ class Crystal::Repl::Compiler < Crystal::Visitor args = node.args obj_type = obj.try(&.type) || target_def.owner - if obj_type == @context.program + # TODO: should this use `Type#passed_as_self?` instead? + if obj_type == @context.program || obj_type.is_a?(FileModule) # Nothing elsif obj_type.passed_by_value? args_bytesize += sizeof(Pointer(UInt8)) diff --git a/src/compiler/crystal/interpreter/local_vars.cr b/src/compiler/crystal/interpreter/local_vars.cr index d8637a7f2264..5111496dd3e0 100644 --- a/src/compiler/crystal/interpreter/local_vars.cr +++ b/src/compiler/crystal/interpreter/local_vars.cr @@ -80,7 +80,8 @@ class Crystal::Repl::LocalVars def declare(name : String, type : Type) : Int32? is_self = name == "self" - return if is_self && type.is_a?(Program) + # TODO: should this use `Type#passed_as_self?` instead? + return if is_self && (type.is_a?(Program) || type.is_a?(FileModule)) key = Key.new(name, @block_level) diff --git a/src/llvm/target_data.cr b/src/llvm/target_data.cr index ed8d0c07ed91..653c69d6b0b3 100644 --- a/src/llvm/target_data.cr +++ b/src/llvm/target_data.cr @@ -7,7 +7,8 @@ struct LLVM::TargetData end def size_in_bytes(type) - size_in_bits(type) // 8 + size_in_bits = size_in_bits(type) + size_in_bits // 8 &+ (size_in_bits & 0x7 != 0 ? 1 : 0) end def abi_size(type) From 1c186dc0eee3724d127a1528a0c3cffe4d9c769b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 29 Jun 2023 17:00:16 +0800 Subject: [PATCH 0610/1551] Fix: Place `--emit` files back in current directory when running source (#13604) --- src/compiler/crystal/command.cr | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 2836cbdda88f..64622c37a315 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -331,6 +331,7 @@ class Crystal::Command compiler : Compiler, sources : Array(Compiler::Source), output_filename : String, + emit_base_filename : String?, arguments : Array(String), specified_output : Bool, hierarchy_exp : String?, @@ -338,7 +339,7 @@ class Crystal::Command output_format : String?, combine_rpath : Bool do def compile(output_filename = self.output_filename) - compiler.emit_base_filename = output_filename.rchop(File.extname(output_filename)) + compiler.emit_base_filename = emit_base_filename || output_filename.rchop(File.extname(output_filename)) compiler.compile sources, output_filename, combine_rpath: combine_rpath end @@ -553,8 +554,12 @@ class Crystal::Command error "can't use `#{output_filename}` as output filename because it's a directory" end + if run + emit_base_filename = ::Path[sources.first.filename].stem + end + combine_rpath = run && !no_codegen - @config = CompilerConfig.new compiler, sources, output_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format, combine_rpath + @config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format, combine_rpath end private def gather_sources(filenames) From ab365326da308a531733d4de18af9b342f5c9e2d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 30 Jun 2023 17:13:02 +0800 Subject: [PATCH 0611/1551] Fix free variable matching of bound numeric values (#13606) --- spec/compiler/semantic/restrictions_spec.cr | 13 +++++++ src/compiler/crystal/semantic/call.cr | 6 ++-- src/compiler/crystal/semantic/call_error.cr | 2 +- src/compiler/crystal/semantic/main_visitor.cr | 2 +- src/compiler/crystal/semantic/match.cr | 22 ++++++------ .../crystal/semantic/method_lookup.cr | 8 ++--- src/compiler/crystal/semantic/restrictions.cr | 36 +++++++++---------- 7 files changed, 51 insertions(+), 38 deletions(-) diff --git a/spec/compiler/semantic/restrictions_spec.cr b/spec/compiler/semantic/restrictions_spec.cr index b8149884ffeb..33e4ed1be918 100644 --- a/spec/compiler/semantic/restrictions_spec.cr +++ b/spec/compiler/semantic/restrictions_spec.cr @@ -1221,6 +1221,19 @@ describe "Restrictions" do CR end + it "matches number in bound free variable (#13605)" do + assert_type(<<-CR) { generic_class "Foo", 1.int32 } + class Foo(T) + end + + def foo(x : Foo(T), y : Foo(T)) forall T + y + end + + foo(Foo(1).new, Foo(1).new) + CR + end + it "sets number as unbound generic type var (#13110)" do assert_type(<<-CR) { generic_class "Foo", 1.int32 } class Foo(T) diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index 8ba058c8c4ad..f581ea79d577 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -1040,7 +1040,7 @@ class Crystal::Call when Self match.context.instantiated_type when Crystal::Path - match.context.defining_type.lookup_type_var(output, match.context.free_vars) + match.context.defining_type.lookup_type_var(output, match.context.bound_free_vars) else output end @@ -1091,12 +1091,12 @@ class Crystal::Call private def lookup_node_type(context, node) bubbling_exception do - context.defining_type.lookup_type(node, self_type: context.instantiated_type.instance_type, free_vars: context.free_vars, allow_typeof: false) + context.defining_type.lookup_type(node, self_type: context.instantiated_type.instance_type, free_vars: context.bound_free_vars, allow_typeof: false) end end private def lookup_node_type?(context, node) - context.defining_type.lookup_type?(node, self_type: context.instantiated_type.instance_type, free_vars: context.free_vars, allow_typeof: false) + context.defining_type.lookup_type?(node, self_type: context.instantiated_type.instance_type, free_vars: context.bound_free_vars, allow_typeof: false) end def bubbling_exception(&) diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index a0528e83c44f..5d3061850e2e 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -532,7 +532,7 @@ class Crystal::Call unless expected_type restriction = def_arg.restriction if restriction - expected_type = match_context.instantiated_type.lookup_type?(restriction, free_vars: match_context.free_vars) + expected_type = match_context.instantiated_type.lookup_type?(restriction, free_vars: match_context.bound_free_vars) end end expected_type ||= def_arg.restriction.not_nil! diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 95a988fcf249..16b3931ebefb 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -3076,7 +3076,7 @@ module Crystal # # Helpers def free_vars - match_context.try &.free_vars + match_context.try &.bound_free_vars end def check_closured(var, mark_as_mutably_closured : Bool = false) diff --git a/src/compiler/crystal/semantic/match.cr b/src/compiler/crystal/semantic/match.cr index 789b13120680..c1b69a762032 100644 --- a/src/compiler/crystal/semantic/match.cr +++ b/src/compiler/crystal/semantic/match.cr @@ -44,9 +44,9 @@ module Crystal property defining_type : Type # Any instance variables associated with the method instantiation - getter free_vars : Hash(String, TypeVar)? + getter bound_free_vars : Hash(String, TypeVar)? - # Def free variables, unbound (`def (X, Y) ...`) + # Def free variables (`def ... forall X, Y`) property def_free_vars : Array(String)? # The type that represents `self` (overriding `instantiated_type`), used to @@ -54,21 +54,21 @@ module Crystal # subtype property self_restriction_type : Type? - def initialize(@instantiated_type, @defining_type, @free_vars = nil, @def_free_vars = nil, @self_restriction_type = nil) + def initialize(@instantiated_type, @defining_type, @bound_free_vars = nil, @def_free_vars = nil, @self_restriction_type = nil) end - def get_free_var(name) - @free_vars.try &.[name]? + def bound_free_var?(name) + @bound_free_vars.try &.[name]? end - def set_free_var(name, type) - free_vars = @free_vars ||= {} of String => TypeVar + def bind_free_var(name, type) + bound_free_vars = @bound_free_vars ||= {} of String => TypeVar type = type.remove_literal if type.is_a?(Type) - free_vars[name] = type + bound_free_vars[name] = type end - def has_def_free_var?(name) - return false if get_free_var(name) + def has_unbound_free_var?(name) + return false if bound_free_var?(name) return true if @def_free_vars.try &.includes?(name) defining_type.metaclass? && defining_type.type_var?(name) @@ -98,7 +98,7 @@ module Crystal end def clone - MatchContext.new(@instantiated_type, @defining_type, @free_vars.dup, @def_free_vars.dup, @self_restriction_type) + MatchContext.new(@instantiated_type, @defining_type, @bound_free_vars.dup, @def_free_vars.dup, @self_restriction_type) end end diff --git a/src/compiler/crystal/semantic/method_lookup.cr b/src/compiler/crystal/semantic/method_lookup.cr index 9c5e42c12ac0..34b7038262e1 100644 --- a/src/compiler/crystal/semantic/method_lookup.cr +++ b/src/compiler/crystal/semantic/method_lookup.cr @@ -122,7 +122,7 @@ module Crystal context.defining_type = macro_owner if macro_owner context.self_restriction_type = item.def.self_restriction_type context.def_free_vars = item.def.free_vars - context.free_vars.try &.clear + context.bound_free_vars.try &.clear match = signature.match(item, context) @@ -146,7 +146,7 @@ module Crystal context.defining_type = path_lookup if macro_owner context.self_restriction_type = nil context.def_free_vars = nil - context.free_vars.try &.clear + context.bound_free_vars.try &.clear end end @@ -369,7 +369,7 @@ module Crystal # We reuse a match context without free vars, but we create # new ones when there are free vars. - context = context.clone if context.free_vars + context = context.clone if context.bound_free_vars Match.new(a_def, (matched_arg_types || arg_types), context, matched_named_arg_types) end @@ -481,7 +481,7 @@ module Crystal end new_subtype_matches ||= [] of Match - new_subtype_matches.push Match.new(cloned_def, full_subtype_match.arg_types, MatchContext.new(subtype_lookup, full_subtype_match.context.defining_type, full_subtype_match.context.free_vars), full_subtype_match.named_arg_types) + new_subtype_matches.push Match.new(cloned_def, full_subtype_match.arg_types, MatchContext.new(subtype_lookup, full_subtype_match.context.defining_type, full_subtype_match.context.bound_free_vars), full_subtype_match.named_arg_types) end # Reset the `self` restriction override diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 5eea71ecfb03..9877f1dabbf7 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -876,7 +876,7 @@ module Crystal free_var_count = other.types.count do |other_type| other_type.is_a?(Path) && (first_name = other_type.single_name?) && - context.has_def_free_var?(first_name) + context.has_unbound_free_var?(first_name) end if free_var_count > 1 other.raise "can't specify more than one free var in union restriction" @@ -890,8 +890,8 @@ module Crystal def restrict(other : Path, context) if first_name = other.single_name? - if context.has_def_free_var?(first_name) - return context.set_free_var(first_name, self) + if context.has_unbound_free_var?(first_name) + return context.bind_free_var(first_name, self) end end @@ -902,12 +902,12 @@ module Crystal # and a restriction X, it matches, and we add X to the free vars. if owner.is_a?(GenericType) if owner.type_vars.includes?(first_name) - context.set_free_var(first_name, self) + context.bind_free_var(first_name, self) return self end end - ident_type = context.get_free_var(other.names.first) + ident_type = context.bound_free_var?(other.names.first) end had_ident_type = !!ident_type @@ -923,7 +923,7 @@ module Crystal if first_name if context.defining_type.type_var?(first_name) - return context.set_free_var(first_name, self) + return context.bind_free_var(first_name, self) end end @@ -1044,7 +1044,7 @@ module Crystal free_vars, other_types = other.types.partition do |other_type| other_type.is_a?(Path) && (first_name = other_type.single_name?) && - context.has_def_free_var?(first_name) + context.has_unbound_free_var?(first_name) end if free_vars.size > 1 other.raise "can't specify more than one free var in union restriction" @@ -1263,15 +1263,15 @@ module Crystal end when Path if first_name = other_type_var.single_name? - if context.has_def_free_var?(first_name) - # If the free variable is already set to another - # number, there's no match - existing = context.get_free_var(first_name) - if existing && existing != type_var - return nil - end + # If the free variable is already set to another + # number, there's no match + if existing = context.bound_free_var?(first_name) + return existing == type_var ? existing : nil + end - context.set_free_var(first_name, type_var) + # If the free variable is not yet bound, there is a match + if context.has_unbound_free_var?(first_name) + context.bind_free_var(first_name, type_var) return type_var end end @@ -1493,8 +1493,8 @@ module Crystal def restrict(other : Path, context) if first_name = other.single_name? - if context.has_def_free_var?(first_name) - return context.set_free_var(first_name, self) + if context.has_unbound_free_var?(first_name) + return context.bind_free_var(first_name, self) end end @@ -1506,7 +1506,7 @@ module Crystal else if first_name = other.single_name? if context.defining_type.type_var?(first_name) - return context.set_free_var(first_name, self) + return context.bind_free_var(first_name, self) else other.raise_undefined_constant(context.defining_type) end From 0f2c6373c6271c9e86cd2f338f4c7756c6872868 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 2 Jul 2023 17:22:29 +0800 Subject: [PATCH 0612/1551] Fix `Iterator#with_index(offset)` with non-`Int32` `offset` (#13612) --- spec/std/iterator_spec.cr | 36 ++++-------------------------------- src/iterator.cr | 2 +- 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/spec/std/iterator_spec.cr b/spec/std/iterator_spec.cr index e308e435cde6..a07b8bedb191 100644 --- a/spec/std/iterator_spec.cr +++ b/spec/std/iterator_spec.cr @@ -672,38 +672,10 @@ describe Iterator do end end - describe "with_index" do - it "does with_index from range" do - iter = (1..3).each.with_index - iter.next.should eq({1, 0}) - iter.next.should eq({2, 1}) - iter.next.should eq({3, 2}) - iter.next.should be_a(Iterator::Stop) - end - - it "does with_index with offset from range" do - iter = (1..3).each.with_index(10) - iter.next.should eq({1, 10}) - iter.next.should eq({2, 11}) - iter.next.should eq({3, 12}) - iter.next.should be_a(Iterator::Stop) - end - - it "does with_index from range, with block" do - tuples = [] of {Int32, Int32} - (1..3).each.with_index do |value, index| - tuples << {value, index} - end - tuples.should eq([{1, 0}, {2, 1}, {3, 2}]) - end - - it "does with_index from range, with block with offset" do - tuples = [] of {Int32, Int32} - (1..3).each.with_index(10) do |value, index| - tuples << {value, index} - end - tuples.should eq([{1, 10}, {2, 11}, {3, 12}]) - end + describe "#with_index" do + it_iterates "with default offset", [{1, 0}, {2, 1}, {3, 2}], (1..3).each.with_index, tuple: true + it_iterates "with explicit offset", [{1, 10}, {2, 11}, {3, 12}], (1..3).each.with_index(10), tuple: true + it_iterates "with non-Int32 offset", [{1, Int64::MIN}, {2, Int64::MIN + 1}, {3, Int64::MIN + 2}], (1..3).each.with_index(Int64::MIN), tuple: true end describe "with object" do diff --git a/src/iterator.cr b/src/iterator.cr index 1d7d54cdcb58..d4d9eccc803a 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -1322,7 +1322,7 @@ module Iterator(T) end private class WithIndexIterator(I, T, O) - include Iterator({T, Int32}) + include Iterator({T, O}) include IteratorWrapper def initialize(@iterator : I, @offset : O, @index : O = offset) From 9b76eb36047851693b9ad96660a729246e1f0f1f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 2 Jul 2023 17:22:40 +0800 Subject: [PATCH 0613/1551] Build Windows portable and installer packages on CI (#13578) --- .github/workflows/win.yml | 167 +++-------- .github/workflows/win_build_portable.yml | 138 +++++++++ Makefile | 1 + Makefile.win | 1 + etc/win-ci/crystal.bmp | Bin 0 -> 981958 bytes etc/win-ci/crystal.iss | 341 +++++++++++++++++++++++ etc/win-ci/crystal_small.bmp | Bin 0 -> 58294 bytes 7 files changed, 526 insertions(+), 122 deletions(-) create mode 100644 .github/workflows/win_build_portable.yml create mode 100644 etc/win-ci/crystal.bmp create mode 100644 etc/win-ci/crystal.iss create mode 100644 etc/win-ci/crystal_small.bmp diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index b5b2652ccbff..1d6be1e017c5 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -118,7 +118,7 @@ jobs: key: win-dlls-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc - name: Build libgc if: steps.cache-dlls.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-gc.ps1 -BuildTree deps\gc -Version 8.2.2 -AtomicOpsVersion 7.8.0 -Dynamic + run: .\etc\win-ci\build-gc.ps1 -BuildTree deps\gc -Version 8.2.4 -AtomicOpsVersion 7.8.0 -Dynamic - name: Build libpcre if: steps.cache-dlls.outputs.cache-hit != 'true' run: .\etc\win-ci\build-pcre.ps1 -BuildTree deps\pcre -Version 8.45 -Dynamic @@ -201,8 +201,14 @@ jobs: cmake -DCMAKE_INSTALL_PREFIX=$(pwd)\llvm -P cmake_install.cmake x86_64-windows: - runs-on: windows-2022 needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm] + uses: ./.github/workflows/win_build_portable.yml + with: + release: false + + x86_64-windows-test: + runs-on: windows-2022 + needs: [x86_64-windows] steps: - name: Disable CRLF line ending substitution run: | @@ -211,72 +217,15 @@ jobs: - name: Enable Developer Command Prompt uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 - - name: Install Crystal - uses: crystal-lang/install-crystal@v1 - with: - crystal: "1.8.2" - - name: Download Crystal source uses: actions/checkout@v3 - - name: Restore libraries - uses: actions/cache/restore@v3 - with: - path: | - libs/pcre.lib - libs/pcre2-8.lib - libs/iconv.lib - libs/gc.lib - libs/ffi.lib - libs/z.lib - libs/mpir.lib - libs/yaml.lib - libs/xml2.lib - key: win-libs-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc - fail-on-cache-miss: true - - name: Restore OpenSSL - uses: actions/cache/restore@v3 - with: - path: | - libs/crypto.lib - libs/ssl.lib - libs/openssl_VERSION - key: win-openssl-libs-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc - fail-on-cache-miss: true - - name: Restore DLLs - uses: actions/cache/restore@v3 - with: - path: | - libs/pcre-dynamic.lib - libs/pcre2-8-dynamic.lib - libs/iconv-dynamic.lib - libs/gc-dynamic.lib - libs/ffi-dynamic.lib - libs/z-dynamic.lib - libs/mpir-dynamic.lib - libs/yaml-dynamic.lib - libs/xml2-dynamic.lib - dlls/pcre.dll - dlls/pcre2-8.dll - dlls/libiconv.dll - dlls/gc.dll - dlls/libffi.dll - dlls/zlib1.dll - dlls/mpir.dll - dlls/yaml.dll - dlls/libxml2.dll - key: win-dlls-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc - fail-on-cache-miss: true - - name: Restore OpenSSL DLLs - uses: actions/cache/restore@v3 + - name: Download Crystal executable + uses: actions/download-artifact@v3 with: - path: | - libs/crypto-dynamic.lib - libs/ssl-dynamic.lib - dlls/libcrypto-3-x64.dll - dlls/libssl-3-x64.dll - key: win-openssl-dlls-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc - fail-on-cache-miss: true + name: crystal + path: build + - name: Restore LLVM uses: actions/cache/restore@v3 with: @@ -286,71 +235,44 @@ jobs: - name: Set up environment run: | - echo "CRYSTAL_LIBRARY_PATH=$(pwd)\libs" >> ${env:GITHUB_ENV} - echo "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" >> ${env:GITHUB_ENV} - - - name: Build LLVM extensions - run: make -f Makefile.win deps + Add-Content $env:GITHUB_PATH "$(pwd)\build" + Add-Content $env:GITHUB_ENV "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\build\crystal.exe" + Add-Content $env:GITHUB_ENV "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" - - name: Build Crystal - run: | - bin/crystal.bat env - make -f Makefile.win -B + - name: Run stdlib specs + run: make -f Makefile.win std_spec - - name: Download shards release - uses: actions/checkout@v3 - with: - repository: crystal-lang/shards - ref: v0.17.3 - path: shards + - name: Run compiler specs + run: make -f Makefile.win compiler_spec - - name: Download molinillo release - uses: actions/checkout@v3 - with: - repository: crystal-lang/crystal-molinillo - ref: v0.2.0 - path: shards/lib/molinillo + - name: Run primitives specs + run: make -f Makefile.win -o .build\crystal.exe primitives_spec # we know the compiler is fresh; do not rebuild it here - - name: Build shards release - working-directory: ./shards - run: ../bin/crystal.bat build src/shards.cr + - name: Build samples + run: make -f Makefile.win samples - - name: Gather Crystal binaries - run: | - mkdir crystal/src - mkdir crystal/lib - cp .build/crystal.exe crystal/ - cp shards/shards.exe crystal/ - cp libs/* crystal/lib/ - cp dlls/* crystal/ - cp src/* crystal/src -Recurse - rm crystal/src/llvm/ext/llvm_ext.obj - - - name: Upload Crystal binaries - uses: actions/upload-artifact@v3 - with: - name: crystal - path: crystal + x86_64-windows-release: + needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm] + uses: ./.github/workflows/win_build_portable.yml + with: + release: true - x86_64-windows-test: + x86_64-windows-installer: runs-on: windows-2022 - needs: [x86_64-windows] + needs: [x86_64-windows-release] steps: - name: Disable CRLF line ending substitution run: | git config --global core.autocrlf false - - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 - - name: Download Crystal source uses: actions/checkout@v3 - name: Download Crystal executable uses: actions/download-artifact@v3 with: - name: crystal - path: build + name: crystal-release + path: etc/win-ci/portable - name: Restore LLVM uses: actions/cache/restore@v3 @@ -361,18 +283,19 @@ jobs: - name: Set up environment run: | - Add-Content $env:GITHUB_PATH "$(pwd)\build" - Add-Content $env:GITHUB_ENV "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\build\crystal.exe" + Add-Content $env:GITHUB_PATH "$(pwd)\etc\win-ci\portable" Add-Content $env:GITHUB_ENV "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" - - name: Run stdlib specs - run: make -f Makefile.win std_spec + - name: Build docs + run: make -f Makefile.win install_docs prefix=etc\win-ci\portable - - name: Run compiler specs - run: make -f Makefile.win compiler_spec - - - name: Run primitives specs - run: make -f Makefile.win -o .build\crystal.exe primitives_spec # we know the compiler is fresh; do not rebuild it here + - name: Build installer + working-directory: ./etc/win-ci + run: | + iscc.exe crystal.iss - - name: Build samples - run: make -f Makefile.win samples + - name: Upload Crystal installer + uses: actions/upload-artifact@v3 + with: + name: crystal-installer + path: etc/win-ci/Output/crystal-setup.exe diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml new file mode 100644 index 000000000000..408133e95a4c --- /dev/null +++ b/.github/workflows/win_build_portable.yml @@ -0,0 +1,138 @@ +name: Windows CI / Build Portable Package + +on: + workflow_call: + inputs: + release: + required: true + type: boolean + +jobs: + build: + runs-on: windows-2022 + steps: + - name: Disable CRLF line ending substitution + run: | + git config --global core.autocrlf false + + - name: Enable Developer Command Prompt + uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + + - name: Install Crystal + uses: crystal-lang/install-crystal@v1 + with: + crystal: "1.8.2" + + - name: Download Crystal source + uses: actions/checkout@v3 + + - name: Restore libraries + uses: actions/cache/restore@v3 + with: + path: | + libs/pcre.lib + libs/pcre2-8.lib + libs/iconv.lib + libs/gc.lib + libs/ffi.lib + libs/z.lib + libs/mpir.lib + libs/yaml.lib + libs/xml2.lib + key: win-libs-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore OpenSSL + uses: actions/cache/restore@v3 + with: + path: | + libs/crypto.lib + libs/ssl.lib + libs/openssl_VERSION + key: win-openssl-libs-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore DLLs + uses: actions/cache/restore@v3 + with: + path: | + libs/pcre-dynamic.lib + libs/pcre2-8-dynamic.lib + libs/iconv-dynamic.lib + libs/gc-dynamic.lib + libs/ffi-dynamic.lib + libs/z-dynamic.lib + libs/mpir-dynamic.lib + libs/yaml-dynamic.lib + libs/xml2-dynamic.lib + dlls/pcre.dll + dlls/pcre2-8.dll + dlls/libiconv.dll + dlls/gc.dll + dlls/libffi.dll + dlls/zlib1.dll + dlls/mpir.dll + dlls/yaml.dll + dlls/libxml2.dll + key: win-dlls-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore OpenSSL DLLs + uses: actions/cache/restore@v3 + with: + path: | + libs/crypto-dynamic.lib + libs/ssl-dynamic.lib + dlls/libcrypto-3-x64.dll + dlls/libssl-3-x64.dll + key: win-openssl-dlls-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore LLVM + uses: actions/cache/restore@v3 + with: + path: llvm + key: llvm-libs-16.0.3-msvc + fail-on-cache-miss: true + + - name: Set up environment + run: | + echo "CRYSTAL_LIBRARY_PATH=$(pwd)\libs" >> ${env:GITHUB_ENV} + echo "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" >> ${env:GITHUB_ENV} + + - name: Build LLVM extensions + run: make -f Makefile.win deps + + - name: Build Crystal + run: | + bin/crystal.bat env + make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }} + + - name: Download shards release + uses: actions/checkout@v3 + with: + repository: crystal-lang/shards + ref: v0.17.3 + path: shards + + - name: Download molinillo release + uses: actions/checkout@v3 + with: + repository: crystal-lang/crystal-molinillo + ref: v0.2.0 + path: shards/lib/molinillo + + - name: Build shards release + working-directory: ./shards + run: ../bin/crystal.bat build ${{ inputs.release && '--release' || '' }} src/shards.cr + + - name: Gather Crystal binaries + run: | + make -f Makefile.win install prefix=crystal + mkdir crystal/lib + cp shards/shards.exe crystal/ + cp libs/* crystal/lib/ + cp dlls/* crystal/ + cp README.md crystal/ + + - name: Upload Crystal binaries + uses: actions/upload-artifact@v3 + with: + name: ${{ inputs.release && 'crystal-release' || 'crystal' }} + path: crystal diff --git a/Makefile b/Makefile index e52b33798518..524848ed26f7 100644 --- a/Makefile +++ b/Makefile @@ -176,6 +176,7 @@ install_docs: docs ## Install docs at DESTDIR cp -av docs "$(DATADIR)/docs" cp -av samples "$(DATADIR)/examples" + rm -rf "$(DATADIR)/examples/.gitignore" .PHONY: uninstall_docs uninstall_docs: ## Uninstall docs from DESTDIR diff --git a/Makefile.win b/Makefile.win index 4e11f34fe071..be923531da97 100644 --- a/Makefile.win +++ b/Makefile.win @@ -165,6 +165,7 @@ install_docs: docs ## Install docs at prefix $(call MKDIR,"$(DATADIR)") $(call INSTALLDIR,docs,"$(DATADIR)\docs") $(call INSTALLDIR,samples,"$(DATADIR)\examples") + $(call RM,"$(DATADIR)\examples\.gitignore") .PHONY: uninstall_docs uninstall_docs: ## Uninstall docs from prefix diff --git a/etc/win-ci/crystal.bmp b/etc/win-ci/crystal.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8218ee2126e3a0da0dd4404cedf313542af9d575 GIT binary patch literal 981958 zcmeI53D{QC+Q&l@4MHJ>jxls9L!8JlbTTA!CsL76G#iR!h)hX|lXDE2$IFlm#W6*a z=~Rk{5*Zo{Nu`X(_y0Vf_j|p)&(kyPVXb@pTo;~cuf6Wy|6c!ht-aS?>%haO{8YQj z|NgV5{%x#(Z(gfPl`Yq)Qe~|woB!{}RYvLi?KiFR+rPhkp#S{JKM;Tb1WXXX&rHZj z5Dg-00i(easUDlutESov!Wz%KmY>x894v}2v{M2pIK3o zI3NH4{EQrc00gWMz|X8GNgNP>0DeXeKmY<(2;gT{lq3!aKmb1@2Ot0eD+KT}D@qav z1R#K)kpmEbfE5DxnH4380|F4h&&UA?K)?zC{LG4y!~p>a;Ai9j1R!9A0DfjgN#cM2 z1n@I*00I!OLI6Lrq9k!Z00Q_KIRF6&SRsI)Sy7TWAOHdUj2wUf1gsFi&#Wj(91ws2 zent*J00LGB;Ad8pBn}8b06!xKAOHa?1n@H}N)iVIAb_8d0}z0K6$1E~6(xxS0uaE@ z$N>mIzzPBU%!-o40Raf$XXF3`AYg?6er82U;(!1I@H27%0uZo506(*$Bym6h0{9s@ z009VCA%LG*QIa?y00I1r9Do1>tPsG@tSCtw5P$%FMh-v#0#*p%XI7LX4hTR1KO+Yq z00Aom@G~n)5(fkzfS-{A5P*Oc0{EE~C5Zz95WvsK0SG|A3IY7giju?u0SMq{0SH(jfS*}Wk~km$0sM>{fB*!n z5Wvr@C`lX;fB=3*4nP0`RtVr{R+J6G2LvF1pOFI)fPfVO z_?Zg-00i(easUDlutESo zv!Wz%KmY>x894v}2v{M2pIK3oI3NH4{EQrc00gWMz|X8GNgNP>0DeXeKmY<(2;gT{ zlq3!aKmb1@2Ot0eD+KT}D@qav1R#K)kpmEbfE5DxnH4380|F4h&&UA?K)?zC{LG4y z!~p>a;Ai9j1R!9A0DfjgN#cM21n@I*00I!OLI6Lrq9k!Z00Q_KIRF6&SRsI)Sy7TW zAOHdUj2wUf1gsFi&#Wj(91ws2ent*J00LGB;Ad8pBn}8b06!xKAOHa?1n@H}N)iVI zAb_8d0}z0K6$1E~6(xxS0uaE@$N>mIzzPBU%!-o40Raf$XXF3`AYg?6er82U;(!1I z@H27%0uZo506(*$Bym6h0{9s@009VCA%LG*QIa?y00I1r9Do1>tPsG@tSCtw5P$%F zMh-v#0#*p%XI7LX4hTR1KO+Yq00Aom@G~n)5(fkzfS-{A5P*Oc0{EE~C5Zz95WvsK z0SG|A3IY7giju?u0SMq{ z0SH(jfS*}Wk~km$0sM>{fB*!n5Wvr@C`lX;fB=3*4nP0`RtVr{R+J6G2LvF1pOFI)fPfVO_?Zg-00i(easUDlutESov!Wz%KmY>x894v}2v{M2pIK3oI3NH4{EQrc00gWM zz|X8GNgNP>0DeXeKmY<(2;gT{lq3!aKmb1@2Ot0eD+KT}D@qav1R#K)kpmEbfE5Dx znH4380|F4h&&UA?K)?zC{LG4y!~p>a;Ai9j1R!9A0DfjgN#cM21n@I*00I!OLI6Lr zq9k!Z00Q_KIRF6&SRsI)Sy7TWAOHdUj2wUf1gsFi&#Wj(91ws2ent*J00LGB;Ad8p zBn}8b06!xKAOHa?1n@H}N)iVIAb_8d0}z0K6$1E~6(xxS0uaE@$N>mIzzPBU%!-o4 z0Raf$XXF3`AYg?6er82U;(!1I@H27%0uZo506(*$Bym6h0{9s@009VCA%LG*QIa?y z00I1r9Do1>tPsG@tSCtw5P$%FMh-v#0#*p%XI7LX4hTR1KO+Yq00Aom@G~n)5(fkz zfS-{A5P*Oc0{EE~C5Zz95WvsK0SG|A3IY7giju?u0SMq{0SH(jfS*}Wk~km$0sM>{fB*!n5Wvr@C`lX;fB=3* z4nP0`RtVr{R+J6G2LvF1pOFI)fPfVO_?Zg-00i(easUDlutESov!Wz%KmY>x894v} z2v{M2pIK3oI3NH4{EQrc00gWMz|X8GNgNP>0DeXeKmY<(2;gT{lq3!aKmb1@2Ot0e zD+KT}D@qav1R#K)kpmEbfE5DxnH4380|F4h&&UA?K)?zC{LG4y!~p>a;Ai9j1R!9A z0DfjgN#cM21n@I*00I!OLI6Lrq9k!Z00Q_KIRF6&SRsI)Sy7TWAOHdUj2wUf1gsFi z&#Wj(91ws2ent*J00LGB;Ad8pBn}8b06!xKAOHa?1n@H}N)iVIAb_8d0}z0K6$1E~ z6(xxS0uaE@$N>mIzzPBU%!-o40Raf$XXF3`AYg?6er82U;(!1I@H27%0uZo506(*$ zBym6h0{9s@009VCA%LG*QIa?y00I1r9Do1>tPsG@tSCtw5P$%FMh-v#0#*p%XI7LX z4hTR1KO+Yq00Aom@G~n)5(fkzfS-{A5P*Oc0{EE~C5fW~fnR?4<%=)Ac=E|7`}gm^ z|Ni^mb=O_`q{44_Ym5MXW=zoF!&$Ln#pKD8FTM0qd06e*wX6L8Pt~ebM~xcw-~awM zctVx|;AfWA4I(yK)XzWv{Qmpzj~+exsH2YBY_rYQUVH7|UF;A4m!(~I-E~-+;k3Zd z42x?zG~GdtXPuEFM>6oVni}|-Rb`FFwPeYXr=Na$ zz<>d}@4ox`>#v{Pxw3wfu{3&VCKCidGbyW~pkzGXfB*fPZ@ziul~=ZI-Fm|fH_Yl( zd4GYW8A^~pgo2;_0S9kfA@IWwKg^yz`=*<2YTv%SE{WxBRr&rP9eKKzhF z4vFNFSdoTSrHU*~)u5N=Rr+MDEPj?14PQh|K%Oyo?%Z2%y|rV?zp37v=;3;u^uQ(yZY*@8F>1hllYnM z3c6Q+^~1jT=9`HVCth&D1$*zkcfyy%incvj+ErIw#lX|Ogess$_*nt4_-QTz>WaPf z)?1o=f9Roy)~i>qXkSV3Kn;P_OT*G~$?W!b_?g@KI9|ISfBf;Ik3PEn_S-vk>Quje zeRnR26>W-{UK#^W#}hAG_3*Q7WcWM_0d-#%E?hWn+_>JodpB*`R0dGAdANC?BGR)6 z^wP5E@ASv`nbYz(R5?9#G-=YL3opD-m&D4i!{eqWl~^kVo(^SRAyvW83ZWI+Z&$8d zIdkSrEgN>&VTaYKRjWjk@au3{ntEvrJpIaf{LC-C;;*e*@P{9Mc;}sWcInb(n{BpP zYpu2X@}UT8tAXbgS6sosGybHD>WGFo@NKN+JZ@+!wi6{E@?c1bD6JKUnL}*>v zic&9a*sx((nlH(XpZTIx)b&&wUAlCsmZrYsl1o~(YE`XTwQOxV@XuvwmtTIlW}`%{ z0Odtb06&YKsnaF?^wUo>X3V($`srV>li^ivenUMQR-GS5Jae^{*uKNhNMs9{4nN(1iUdQ>JJf zvG2b7n%}K0Ee|ku-puU*EG>@~7THhmv+O8Wty(o})-0{VbL5dnZnV)x##$TUBtSrx zrrSacJhL~lh`+(l{s>fEvCKL=-MV$#ZoBR5ZT_7=Bjyz4325LcOXK#yAKO`6@9?wV z$SU1ld+jx?!_&NZb7okSZ;re!oq&32+E<=|XIv&$u3Y@=HyCs0&RuW4^-9lNN`OG6 z1Y~K01`X2YTIJdvFQX@bpZ&)4=9_P>G!6L>0#Ol=rJZ-)c?>+GH?eXh;%C3XXy3kl zRB1~&5XeSAmUjO6=W~0YoB>7r57_C7jM&G1FAhKZ z^;g{q;7Rcan=D1;CLl}Ga(4_ofA^p$|Bs*j`m2`a&z*pL3xQ|}WGszdT9j7yTUo*T zneL(=c;JE2W-FyZV2uQ1X#)lfz|x|%wBY@0$&w{(nzY8o#}DElkg+u8rA26I!TZ_x z@#Eu2Rmy}wJ_KZGXPR z1o9-1u`~vrr5jt=ex^syS->pMHpkE6CZM5$RtPPGt{mo9nc8b5ZLL8EZWin_Oq2MS2k+YD1jv96c8wgfR=J+ z;8~od1?*?jrcGnH#)8@zKTd{#dTD+7^kLvxgrx=SXCp?8NG3fw3j~TF(6?`2R^lks z(n9q!Jq>*5p@$ZcWgG&5#0aRD#=x^6OAFP{mMvRWr%s*35|a}_pa=pQc=qnyn_gP} zmKLg?z3{>dMdTNUKp=Sny?XWHT3Vi#7OJ0V<>TZNlQTk~I08L;_S6ordA8}0e-^5r z?Xkxm#bp*pK_C?ZXPj{c1J5;Db&!6xV8Mdw)vKqHm7Ej;MH5gjjoSmcEiFhtd*Fcw ziq0txgg}Y}PCxy02A(-AEl58*?X=TUOi4}+fl>%`@7|q(XEsX<&(FU9{`(zv*rAk! z;#dfzOQ1)O9`w>GwY2d3?45Vssamyay7|c2Ay6U#S(^SbSK6jS{xCd0yWxf#N=zjV zhky$NPCfNh2A&nnJ1{@fV@^jMb(9OqNF5+hIssW41JCl77MP!Dji9=9>z1BJlmG!o z2{IY><)5FvqX-MTUG49U;F{PN2hHEKkdK@kL{7g&ZMV>ws2m#j!$kKwnmKKbkX<-7kLU*m@QJRQH89|*)sK-*=9Ht-C? z&-9qn@y8z@Ywl1u1RNyLxpU{BEG-N_)4ZI`H{aaBbfZcTh?{^cO^^8na-%a0Kbtyr zYTQ{v`4DiHfGjPLskUMG*;Q9vTPB_8*z%u|pTe4)yh8u1eUz+$;vYz<*_rL$Gt0`T( zcJ-^~)V6d2>ZREpcn08SPd)Wi>4{*Y1o`?i&pe~Mz4Ehr@4eSXP()D>0o~{~+)E3< z&(1mLoPq?m`r|rv>d4H>?sF|#v}oYMd99SXEc^eJ^S(?3Gnsq#in#x50N^TcO+a=6O2^@Ruv6cp&ru|G8&$irh%SvSo;lqtL-gwfa zN#zZ!!fW|`n>KAiKtYT-2*}b_tXNSYH#~i7+RtXqnw3LF+h3{Um7`}?maW5v4YSRG z=<*@ZzI}Te*V0V;*{D&Y@)6VCZ#2&=A2XAyz468y>#VbmJpx2jD1l>+ImWgl za-81;Md0Y8kM_>ejQZI_4?Pq}>S>r+ikW44Z0sB#NVXIjrUstNmoKlhTkqzF7X3^e zj?+&+y-?|V{Jn;mue|a~>6x`~;X*CI;UfX+95;cZjylStskTP_Y};+Oja%o`%hzm_ zNX$%js7q-3?6Xhm5m5m*2^?|65x!WOML(N6cdnax5n-(&8D^I6*j;?_#SvDIqKpwx zFU^aAr$s-z>86{EWmAQ4w9@R9DO1Y3QR%Psd=#@bDsbj)c>+fsd8GTd2Q2!TE@GC~ z!Psl{%px~4*`XeE3Gi_WW4RG`IENp8xZ@8*ne#L8HoO&jy?XWHFtbeePT3lFgP?rh z38#n=b_tr-J6Cj}51I|7WWzNrr4jr0+ zq{*K`Ju{h^rlMqXqtZWr@4feukBc+)QKQ^7;adn~2)H@$wB=`Ce)*+V%r};Cnl$_R>#vu&PJA!a`3roz1!I{LcR1>$ zxi;{$Rxep=2d%&U`W33dTL|PJpxff#e*5j}xs=K$hWt#!rJZ-)IR|*ZzS620N%qV# z*{5}ucH3d%+!pulH#eSo-*@iAsb;nkCwpAH{YCAx>h)^AwTQXsZ+En zrBa$6XVM;W6;w*{?Kj_iQ>#|3RAT3(J`&J=haY_KL6Rw&<6Kt!O!tiqY=y2TwbC`S zOs2GM-P*^7MV(V2u+vUExwCPn6+fFgb*k_C;%b=bf|<$EuDtTfR9Xfn^^ri29zAk& zm=gZVik}T1KHSG#D66w>0KEC;n+c1b?R5Y7&wt7)#tR7KCh+jX4`)lH?78Qj%9iAPE87`O_}R11KI>^J=?1{GY16Walj7%W zn(k>vC0uRo+MQySu6}A0el~F6z=UOtodnU<*b9qOgZQHg^T|86U@!!wxzyE%}5}`qZ25w$6t6-&L z$By+2EVWITz{rs!E96Y-Z~gbPZr!>iEMqCB+ibJVX3Ut8y6pK>VDaL`tQ}N}*0zt; zL%t4OkT-C&!~kzd^9tqo;GLJGP#Au%kRw- zs9wGLym|BDHm4%W_u0?11EAKVa6ES!Hf-q24S?0tM}IN2^r+*6sb2B~nlx$R+Dwg1 z_WJB+lP6D3Ude*as3!p4dFP$geZr$pMvWR(P{{l^1Oom0_jfagefG19F1pC&+}UcY zt$a5#<*aVVdN*C!<*cw#w$DHRIiP-*rUc(yMXONhjqKHNOdjz<&Gf_scK8 zl%7{{l;EwOIr}tDqehKp&6*X5R%w-a&pr19(ozXC9|A*%4oxc+*LYrU{mjj$rFG5B zn3*Yqw6Uo#la}*oj{K%X0#l|;S);F{^#ecs%+1woWM;Ex&rVCp_|B_4^G%yJEwK@B zxD5ijWxs6MviOp%#FG8=Gueh~s}26?Pk*v>&8$LNoqhJ%Hku<*lt|#1V~(ki8&19T z)6Z_X<(3jD^$^?X>OM7o>|6Ob$^~s(~C|L zGY2Z$fByMr*Z$a+uaAd*=FV=n>Y16m0Z=|c%3fc7`Q>KXC4m%2;M7x3Ez1WtUwG(e zS6p#LacV?+l>S!DnKQ>tRpY4D>#x7Q_S$Pl%aGFiBJkjY55|#N5tiwppB-?(0jXz! z7R3#xXO>AftsSK29#SVy1=1p*$9S~_dxYK-M^V1{nI0U|7V~LInmMnAncAK*4mBJv zvwi#a>5}K{DH3SWqJ`rZ&(wT;^Rw~e$EPT%PQBfB+kN=qhmIRhOx3&j=9|-n&)HKX zFlf-AnDQ#ZVtweNZ`5Wo{P|UVk*ijKT~(N zdGqFJNvbCR=FXiPlMY@KtC@f5o~Fg0^QK6kZr!?C(Z>BO^~%q*WVzN}N=4NjcGw}< zVP^R(KI4otQsK`@(ZB(JKbW_HP7t@4x?E3-TnKVmt1*qnrD36{mLTN9hu#%gdZ6oF0LkPLCIAg|5<*CxH_D z@iT4Ak#PFR%mTjwP-y}V9y~ZKXI-8Z@W;<|hb1v#ciL$uhMCziNNWeVzm0QZsbc;_dT96K zk3Y_q46c3diJxhS-wik1Fqz!pn%NqYv~AnA$!Nh@QYWCT(0#dhro8pU&z^nu*|pYM zD*;_ zy_sbGyd)qxr^rp<@y8!eG1Ce?wGV!##ZmQae{Slff7`fmW9FIVnLbj)oh5=xm!1-` z5vX0e_QHh=^UMkte&&OpX#lvv1{-8U9?9qGnSJ`{r!ENMOC8uWJ(9$8ypUF{T6r|s zRNL{v&+fnf{#a$#6;Fnlh5G2-ci$bWC=~7jfgwYN6q*?6{@w>aJLQy9;?q`BGni*q zL=FlXo2JK?mM)a6OQe$~O)4TcQar?YKhwQpEz}c}yy}^K_St7C;`jH|KmGL6uDk9U zlP(nN0)dS;-gwojReq(Y^M0mH$79mAY15`m%_t>X&ph+Yn53at7YH1AnT^Zo0Ui|sbfByXQ z&y$C3?2KQ0@kO0Fb)pi5a-1Tdi)TJ=ls8S`da@_W_mtJ})+Ii^Q0EjlTF1ze9N7^6Nh?YRNZr%Kvd6r3RxBcwG z3ondNCH2f0W=890Z@lqFgaT2NI|MZ8Gul*1rZk8BOb^vWxD+bG%s3KCvo>^B-Em*LiX3d(B=%ZmKJ+nCcOl2N>?6Hw(L?P}F*loAn)SHMYVPY-T zVLyB1kw+p?xLLDim|1*Dbkj{YMIsZ0xI}Qc|^{8Pc^UPw+p!eT@ zpH0)Xj#%w)2%M~@yQipAmX5cu2Q{+3MI#Bml^{j9{RGP-8Q zFf)#PskF?89(u@~=15ISByi=GS5~UB7azLnXQkXVlbJ1Eyx0r;HY%xpa8!4qOKgiA zUOE9^wnDG8?KtUYD_5@EY_rXZQ%1L%xtm$3)bV|&iR63kxo2^Dagb_Vk?>i6#XnO4;*bSrfA%$R4EZdz%Oa_FIlI@LOZVB%BS-*b$@|Sw|-FN2( zK)M-JcJ>)FW<9X-%eb4 zrUXALnt*m6{OYT(+)1b6YT}rmX;Oclg6IYScQcFAJiep4bm@|(eEiHc0xes%j4v?~ zE7>tWyW@^K^3hqtOnPRC3GkV-2x76yHG0b!}?=)*eYc%CnkZ&C% zP_JISZ@>N4*}TfXVlMfa9)!)kGGL1qEf{9zuWc&7?BBn?gYA_npx=En8z4x|v8t)XEt@8#r)arP66@)6ShcYhu&9dGoYMN)aJ)NWuhOd+jwIe5y2=-Td(G zyYEgoL*hG~Gk&(l1y0vre|@c^an@O9O_(sj#=YhER86K) zcJM%F{7h_QY1;d*Xi0V3d7Evv>DsmHJ@?$hwA*xZZPu(=TCbsKDLF7P0-7(FZmL9k zc9;B2k*!#17q7c_?;fFu6cq=70}eRA z*q?g${7j8iw=Z)%D^Rqs>a=aT>83{>dF1uiU;pmA?@CEaS3+Ggxd$J7uxOti)~Ll@q{k6(P@h$snz|}Q?~^P!H<(9 zu=(bjd%YEUrCI8*pQ*l@TNZaOtwILXs#R;>efJ$QWQZ=d>7h2ftgdtW-1akp)Sr-c?b@Z11siR&(cy<5uBB8ovu)+dl^&_;NL62W;e}L$ zwAsg9cip8WrRlWgnSfd|MGK$C zBnrirNMO#KIeF^n(a(JFGvUD#yOU^wUo^+^F;@rcIlcdrycm zfV@bOfMy4lF0O+mc;#o}r|a^1`aDVH3q6;bZ-WL6v}T+35@lwaCibmy&#poV^ZODB zXiY&&YXp_IRp0zfb=Sc2=%bG=QPN}%&pbxjyLaz#9*T$`{08QOx{RPPR$-wP_{BwNA9e2!JY+JZ+q5IQ`*fc$VW#jv$?mYQ^?V`-ODt%$d&lH~qo`3)Q z-{VUMH%qQjqlRX->0+DK=W%wY0+B^w&`M`&1_Q^yRg%;;d?gau{-;^Y15|W_sOv6XPI7FhYlT_&8&ngra#PDv#nRJ zUSr3O)y1~7I&Ey4p0Mr}PS>YTpGx~QmOeDZ<=wjQ% zi4!$8Ptx1;0E9-wt5=Cnk|FTeV~<&~u1aIE>t_mA*KFve)^nx4N9(f(3f zFlO@rOP>eeXOc?;PkL!Zv=2QZubFLw2M^Z8Hf`2adcRF;2Wj9_L`^uPL;|n8@`@$j z$`(r)ex~Rdc$U~8wDa5{ha94rZQ2r`=x1iMr%;O)ElR8jhZjmfYXq5JW-sG#!T6cN z)e?}$AAfwIxxnu$5YS_!n%SnuNHw!<-n@B*PHAG(^a?x^c&n99OYd7}>k>Kjc}RXH z@EUlkAtsPPft*4&5}Gt=l9}0-=LQMSKmR!09J4?4jIDkd0uV=HRDb%NcixH5LYyi&JUK|#g*Z+`nUxag)~#E7{^455q57G0(7=;kTBR)@rVlmyW2c>VI^&Ep&N=6tm`*{l zs}s2IzWZEro%re#u%8L4?#ibz_WAb&K1&uw-E4`fM(4&yPw4G%`W2q<7ef7%F?=Y>EdtxQgaBDC(x!%8$B>v zF2U`;#Lxbyv7WTX(#p39Ui(X6*sx)L%x?Sd@Uzu4RxjWyTL_m+$zWL^x zt7W(G5Ad_f1lD8gUAuOrSwO&F0$Xmmr5=^7EW0g#hM#3oR|8Kh&EM8c&9#7OmejWV zF@Ba^T@5@>KKW!?1_XR0aLX;XWKV6g-{5CCrPWFtdYsqCbf(TB6IgfMb+xZe&eXQ~ zC4ROhT{ZB;(n9tIA7kHPhaG}?%&Dvq<7Z_kvcJ&nfm2R7#mBs+&S4VJA|~0>+448| zS$^v3fv8hYJvGe6LDb$67&~@setB)}SNK`piptX14%fSu9bkpEMvyj6%{#A6{SZGZ zL{1GnyLay%U=tx^j|gbX)M$5sE~JU1{9F92Kml}nphu4$9%VOG4UNEs7hYIkQrr1C zepa}a8hB!9p_zn7@xJuZONA%3kt6W4qJ-4Ew9`*N-J=|*s(}#Lbkj}0`R1FVlG?&S z_*tJmlIo;?F;C4}h9ie0@?~)8#tGi(S9F|pOvhimH_SDySMwvP7TcyXxOk}XbaL- zXukMa1t3Me)xZ-=Gw&Ae2YBL%Cl-~zb`HYNBGOWp)~8P&_fwu48YXbpU3W#4&b}6d zpGBvfy83Uy=A(%$li}cJ|q4V`;7#fTjBAF{f$M zrp27deiV3Zv}xAV?BYn_9~9(xQA%d7^xO z55AnGMvWSW9(w4A5hHX-?7Q#2OFSj*oDe@tLrY!%7(93|y)<9^CXRa6s#R;Rz4lTX zy!hga+SfIWG&Fcl{46C=HIx`MXb_ebhgCRRrY4FtY0^a7=!_jZ_Vdp_=dym}jSoMI z9BJMKYT!vP%~|h>s$%++r5P3-J9Zp1X3X5VbG6|_-U-4F3nqY{6^ty(1N0YKyIIpq zi^@FQDQA;SHaX~^gN6?uK6UC;O{k7CX(%f@0sJgGn`FMx!1IC&E?9f*wcTlp)TB59 znqi?!V!GL*3Hx7v{WZhMWLi-9v*2gtX{7X8f4|jB!_ta#k#vvJC9!R`*`|B58S)hW_ug_*uIAe?PnamJb;+1WQZTobo)ot`BUn#TLgNd+d!j-Z*>qZ2Dop zXL0)f$IsH&;Qwl%KkXM^d@+`mr>P|Wvzj$)9(dq^mtTJQYp=bweED)`5{HWXKLPyg z|5>E}N|r`1EqSBLamH%Zsx@ueREyY6m@q*P9Wh}){YH|r2KZS{Jjs8l!ONwWUP>=5 zhsVVIl`e^GyY04JyLP?jo_prcpPzgVaK@qu;Acgni|s)5(k{8=5-crl->Ojl#v5;Z z_~D0NfBp3{XU<%;YE^9VQgDm}@Us}<7GJc+HA9CE#nLKRPULU(;N(90?4xyfCQqKM z2Pe6*Uwq2CaU_1`h7POOf`Mn`?v*J}m&EkYQJ+42#*G{I#TQ?!PM%NvC4iqftepm) zTuY0})ilH6gcDA<{r21S&=JeJI-K<-RSiEY31937Yv8E~0t`Hha5c>m(!G8YZn{aA#56ExI2lXkc~vNW=8s%zuez3|*`8%>I=~kjZn$CV)~z!S9kDdEKgpX= zP4TmYKu2>L^+K+=;)>9EX^OX5vu4_6{;8*)`s%B%1R)xllwytme&(B`>ZJ`EHq5-G zX~#*e!=s0e?!EWk1q&81mh>%8)2T0hmJaul&Z@?(UYf~)r)>;+UTWeS=UyrT4`aCmtTH)*|KGH#r#Z3Cu)tKIUz$amGFAtS-pDo zU3S@}Z{NP!{CmlgCB?+gF%Vdd0DiU_5|2KRrD+m}M+48Tx87Qtf8TM(9oqa`+go{r znyPw106+6WrtFo}!1L;>uXfhb^w5!JShQ{1cI3#BnRR#=PG%RAZ%QSApOs25{o~~j zdPLDFORHVG_TGE%txaAgPMpXrvGh|Um>S?`CZtv`O_Mm%wKUzF&?T|ny?bjN9%fjW zNGtz>z|Z{CtnwPm(vt6`$w3=5XrN1Cx7~J|mZoM6i^{d()1(OCXGxJx;5-_5UUSVg z3G~wH)vKpVVk1V3(B|J<)=xlLPLU@8{47sUp8PDn+XGtGwPnke+RFXK7hlv23oh$> zk`ZB56F)Pjx12^jr$~Bfn(5lOapRsndp`Q;qdYiiE>jE!gr6A{^;cP%=0_A~X_{f7 zSz=n6`qo=-{osQS)Zw#8h?r~=z|U;z`m21Xpk7+7TD1;298m}&7t3=TiDuDS-EqeqX{T4EhLcGM*?EltfjJk~W2H$MC06z;?dm@K`H3ImVHARUB0^t(C&%)K7$RS{j0DfjoQR0C>xCHRCaJ46L z2v{S4pIK9scpwli0sJgn?TH)$)(GHd))XZk2!u-jKMPlTB8PxA0{EFVMTrLj;S#{l z!quM0Az+OFer8Qk;(Y- zQCdfHea6nKea;2Lj;|z|X?fp2#6! zjR1aTO;O^3K)3|(vv9R1atK%>fS*}Ylz1QzE&=>3T06z;? zdm@K`H3ImVHARUB0^t(C&%)K7$RS{j0DfjoQR0C>xCHRCaJ46L2v{S4pIK9scpwli z0sJgn?TH)$)(GHd))XZk2!u-jKMPlTB8PxA0{EFVMTrLj;S#{l!quM0Az+OFer8Qk z;(Y-QCdfHea6nKea;2Lj;|z|X?fp2#6!jR1aTO;O^3K)3|( zvv9R1atK%>fS*}Ylz1QzE&=>3T06z;?dm@K`H3ImVHARUB z0^t(C&%)K7$RS{j0DfjoQR0C>xCHRCaJ46L2v{S4pIK9scpwli0sJgn?TH)$)(GHd z))XZk2!u-jKMPlTB8PxA0{EFVMTrLj;S#{l!quM0Az+OFer8Qk;(Y-QCdfHea6nKea;2Lj;|z|X?fp2#6!jR1aTO;O^3K)3|(vv9R1atK%>fS*}Y zlz1QzE&=>3T06z;?dm@K`H3ImVHARUB0^t(C&%)K7$RS{j z0DfjoQR0C>xCHRCaJ46L2v{S4pIK9scpwli0sJgn?TH)$)(GHd))XZk2!u-jKMPlT zB8PxA0{EFVMTrLj;S#{l!quM0Az+OFer8Qk;(Y-QCdfHea6 znKea;2Lj;|z|X?fp2#6!jR1aTO;O^3K)3|(vv9R1atK%>fS*}Ylz1QzE&=>3T06z;?dm@K`H3ImVHARUB0^t(C&%)K7$RS{j0DfjoQR0C>xCHRC zaJ46L2v{S4pIK9scpwli0sJgn?TH)$)(GHd))XZk2!u-jKMPlTB8PxA0{EFVMTrLj z;S#{l!quM0Az+OFer8Qk;(Y-QCdfHea6nKea;2Lj;|z|X?f zp2#6!jR1aTO;O^3K)3|(vv9R1atK%>fS*}Ylz1QzE&=>3T z06z;?dm@K`H3ImVHARUB0^t(C&%)K7$RS{j0DfjoQR0C>xCHRCaJ46L2v{S4pIK9s zcpwli0sJgn?TH)$)(GHd))XZk2!u-jKMPlTB8PxA0{EFVMTrLj;S#{l!quM0Az+OF zer8Qk;(Y-QCdfHea6nKea;2Lj;|z|X?fp2#6!jR1aTO;O^3 zK)3|(vv9R1atK%>fS*}Ylz1QzE&=>3T06z;?dm@K`H3ImV zHARUB0^t(C&%)K7$RS{j0DfjoQR0C>xCHRCaJ46L2v{S4pIK9scpwli0sJgn?TH)$ z)(GHd))XZk2!u-jKMPlTB8PxA0{EFVMTrLj;S#{l!quM0Az+OFer8Qk;(Y-QCdfHea6nKea;2Lj;|z|X?fp2#6!jR1aTO;O^3K)3|(vv9R1atK%> zfS*}Ylz1QzE&=>3T06z;?dm@K`H3ImVHARUB0^t(C&%)K7 z$RS{j0DfjoQR0C>xCHRCaJ46L2v{S4pIK9scpwli0sJgn?TH)$)(GHd))XZk2!u-j zKMPlTB8PxA0{EFVMTrLj;S#{l!quM0Az+OFer8Qk;( 0 then + Continue; + if not LoadStringFromFile(setupPath + '\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt', msvcVersion) then + Continue; + if not FileExists(setupPath + '\VC\Tools\MSVC\' + TrimRight(msvcVersion) + '\bin\Hostx64\x64\cl.exe') then + Continue; + Log('MSVC location: ' + setupPath + '\VC\Tools\MSVC\' + TrimRight(msvcVersion)); + result := True; + exit; + end; +end; + +function HasWinSDKAt(const rootKey: Integer; const subKey: String): Boolean; +var + installationFolder: String; + productVersion: String; +begin + result := False; + if RegQueryStringValue(rootKey, subKey, 'InstallationFolder', installationFolder) then + if RegQueryStringValue(rootKey, subKey, 'ProductVersion', productVersion) then + if FileExists(installationFolder + '\Include\' + productVersion + '.0\um\winsdkver.h') then + begin + Log('Windows SDK location: ' + installationFolder + '\Lib\' + productVersion); + result := True; + end; +end; + +function HasWinSDK: Boolean; +begin + result := HasWinSDKAt(HKEY_LOCAL_MACHINE, Win10SDK64) or + HasWinSDKAt(HKEY_LOCAL_MACHINE, Win10SDK32) or + HasWinSDKAt(HKEY_CURRENT_USER, Win10SDK64) or + HasWinSDKAt(HKEY_CURRENT_USER, Win10SDK32); +end; + +{ Adopted from https://stackoverflow.com/a/46609047 } +procedure EnvAddPath(Path: string; IsSystem: Boolean); +var + Paths: string; + Status: Boolean; +begin + if IsSystem then + Status := RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', Paths) + else + Status := RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', Paths); + + if not Status then + Paths := ''; + + if Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';') > 0 then + exit; + + Paths := Paths + ';' + Path + ';'; + + if IsSystem then + RegWriteStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', Paths) + else + RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', Paths); +end; + +procedure EnvRemovePath(Path: string; IsSystem: Boolean); +var + Paths: string; + Status: Boolean; + P: Integer; +begin + if IsSystem then + Status := RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', Paths) + else + Status := RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', Paths); + + if not Status then + exit; + + P := Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';'); + if P = 0 then + exit; + + Delete(Paths, P - 1, Length(Path) + 1); + + if IsSystem then + RegWriteStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', Paths) + else + RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', Paths); +end; + +function GetUninstallString(): String; +var + sUnInstPath: String; + sUnInstallString: String; +begin + sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1'); + sUnInstallString := ''; + if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then + RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString); + result := sUnInstallString; +end; + +procedure InitializeWizard; +var + updatingPage: TOutputMsgWizardPage; + + warningsPage: TOutputMsgMemoWizardPage; + isMsvcFound: Boolean; + isWinSdkFound: Boolean; + message: String; +begin + if GetUninstallString() <> '' then + begin + updatingPage := CreateOutputMsgPage( + wpSelectTasks, + 'Pre-Install Checks', + 'A previous Crystal installation already exists.', + 'Setup has detected a previous installation of Crystal; it will be uninstalled before the new version is installed. ' + + 'This ensures that requiring Crystal files will not pick up any leftover files from the previous version.'#13#13 + + 'To use multiple Crystal installations side-by-side, the portable packages for the extra versions must be downloaded manually.'); + end; + + isMsvcFound := HasMSVC; + isWinSdkFound := HasWinSDK; + message := ''; + + if not isMsvcFound then + message := message + + '{\b WARNING:} Setup was unable to detect a copy of the Build Tools for Visual Studio 2017 or newer on this machine. ' + + 'The MSVC build tools are required to link Crystal programs into Windows executables. \line\line '; + + if not isWinSdkFound then + message := message + + '{\b WARNING:} Setup was unable to detect a copy of the Windows 10 / 11 SDK on this machine. ' + + 'The Crystal runtime relies on the Win32 libraries to interface with the Windows system. \line\line '; + + if not isMsvcFound or not isWinSdkFound then + message := message + + 'Please install the missing components using one of the following options: \line\line ' + + '\emspace\bullet\emspace https://aka.ms/vs/17/release/vs_BuildTools.exe for the build tools alone \line ' + + '\emspace\bullet\emspace https://visualstudio.microsoft.com/downloads/ for the build tools + Visual Studio 2022 \line\line ' + + 'The {\b Desktop development with C++} workload should be selected.'; + + if message <> '' then + warningsPage := CreateOutputMsgMemoPage(wpInfoAfter, + 'Post-Install Checks', + 'Some components are missing and must be manually installed.', + 'Information', + '{\rtf1 ' + message + '}'); +end; + +procedure CurStepChanged(CurStep: TSetupStep); +var + uninstallString: String; + exitCode: Integer; +begin + case CurStep of + ssInstall: + begin + uninstallString := GetUninstallString(); + if uninstallString <> '' then + if not Exec(RemoveQuotes(uninstallString), '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, exitCode) then + begin + SuppressibleMsgBox('Failed to remove the previous Crystal installation. Setup will now exit.', mbCriticalError, MB_OK, IDOK); + Abort; + end; + end; + ssPostInstall: + if WizardIsTaskSelected('addtopath') then + EnvAddPath(ExpandConstant('{app}'), IsAdminInstallMode()); + end; +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +begin + if CurUninstallStep = usPostUninstall then + EnvRemovePath(ExpandConstant('{app}'), IsAdminInstallMode()); +end; diff --git a/etc/win-ci/crystal_small.bmp b/etc/win-ci/crystal_small.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5309805aa19badf6ba4545450b03bd404f4c70a0 GIT binary patch literal 58294 zcmeI5cc>Ld7r>t_u^Y=Xb`w#tK20JbmS{vHF^UbbBZ`VOMtImFRxl>0L_|X{Xo&iU z33gGj7c3}t#oqPVqDHYM#_#)mS-xf2d++Y-?#%Ao`>yb0cJJ)&Ip?=CXUdt`)*UzA z%=q_TWB#wj-}n5@<}Xi1IsVVcn9A=p${K(Fp5VX#r2lDbfixeD;UHR?PnwV37SLO| zbooj1(c1!gOP4M`X+C;eKyT^N&3&x36EnK6dQb zym|9FcI=pym38982~KJYDHrg;{D1xR*QHCBmMvR0V8DQC)vCSw?z{gV9nPp+x$^Pj z$F)V2qI|ITn>TOn-@m_k^XBaA>_&|m6)RTEWMWHv_~D0?C_NFS9G}1c{`>IZ!{2`U z?dP9=Ub19K8;V4SAAIn^sZ*yktmRUI4@^##i)i6A(iuUN67hQ26i z`M~7r)vLe%{`=6OLu=NoS*TDUXY-5viZ9X)QBv{2rTp>7A3JvJm^g7_lO|2xd+$9F zt9#0WFVd>HkPLjlg3V^np8draUu0%xda}2V%pf{>@}wrdNLM~EIe-5A;>C-5_3Bl= ze0d*c4suDQN|iLJ=yc)(lbbhhu358Y((t?xNr~~2fP+vL@A2A(H%9PfByOB z@#DvP_UxH5WlD<{Equ4W0@8?kITc;xr6?jkU^#Q<41+A4I&})a`ie^MD%|n@kI*w zpvRLZPYxYAG;7wZHf`FJEn7BEo;Tz(;h-RwlF*5jCy~{fv7SFJ64@+O@Rb^XJd6?kS4yjAd9E5u{%fn9sg_ z`_#3pSroEv-8zaW3wS>M_+v#@RC-$~x*!3=f%(jsF+-7l1d~v5fW!O8|6|6CQS>0y zw+%9k8jKI&Ijvf?QlTGkR;^lPtr^y@U$3s9s!+jtPv5>M7@u3WZmEd)#OF}0S>cc- z%#?7Poa(h_~3l%)vMMigQ!UY9 z06rHlUMyI!pzYzl4~=Rqk(kDL;!H!!hf>5CovB))gkL_Gs&C)EQdFc`6MHTOI3yd&kki2Nqh1+Mz~5}`+$K3=;Y)V1QnH-b`=?fdWl_wlJ@{8y$-nFkLZ2x{t)54EPJP*I6K$EMJM0|%0H zpodk4_;8y#i4HyTVfagYP63~zqU0E=JbU&`$^c;aCGflNzDqsaH~%;%^`oIQJ1%C@j!(qr=*PXk+C_#8ZV&{dTn1D{?QzI^$ze*OBcB59Rj(V|7l z_)MEN%~@A`4pD?&%^W2Eh!G>4Mbhe5N;_+DJn1W3erQ>L_St6+>W1TUY|1ev;UJQ3 zKh>&LOT1-mXe!L-?%lf%!#V`A%Nkp=5pX`dbLS3SEZsOHl4tnv;m+#B{wmC8!-frs z9!s%}1PpA-95S9oMg4 zw@NOO7UM&-4#7aN8sl>$j!KKj8=Gg>u3bqn*U+?SQ?JekWySc=>WHo8@j2>-Z(=zl zI`iVi3tp4hcSpA~lO|0{q=LhFF+RWi@{3K~2z^ehHUWcGY^o_b+_`gStmLj|#Q5+L z1gq5e92x|!_YN(?4?p~16;H!7d$2!z_|S(z1o+TRw~iE3nFNWx@gbw@CG4xIc|=c# z4jl+eb5$fZg8(0PvNmU0@Hx7{K_}iIHk{P3*q9>CojW(y_9ABl_?V%h%=|>v&v8|y zU%!5)F*HnwELDiBqW~Xcrk^(K%$YLFX3qJ!(sQtC94Wwe_hrHCFAi~uNs>noY zojP@xKVuAtalsH+u>rTk42YoE(zAW_)mOd?4;KpaF%VIM#rh&wol2D|)vQ@F2B3ED z-mT7A@d~tV-P%={Ff-8T4LH!^g^%$bfXKlYxoXeCAv)7ID@vpER>kz`(_IA!GXvo? z?2Hyx`7xg(=!;w}QK3SGK7IPIPM_AX6jmY;EUr>X%<$=_p9XP07~FgL;Uatyn>UL4 zB3F%ZR`^k-0C85laK2g54HSt0bCpL{1|n+Am)c*#7as!=6`e#xrb%#CymovJmEBy} zv&!rH*QHAr^H$L&a6YKu4~uz~Up~eSocl$=%|mOQ52W&$RN}q`L5W8o2N%!z4izg_ z^f#wq!XXii4>zEq^USyWG80Yv$C?-?vjuqHVCviD_oGL6)=gO68!x9`SVRP`%VPr!;?Xz$QQX9 ziM38GTef6hxc&S0|M};iM!90?y6c6iGvIvqfVFQd=7lo&7&pTgMZ5VIt#zst{EurK zvT8WopO2+7WNVt)cS0T?Lf#wf;gfrO5#Qls;~8=DyXFjq4^kMu zDB1xaDaFJ2CbWn6pqQ)G#WRG($B>9&)YL}Ar}%gi#m(=UGh9B1ld8Um6+TQQGK}tO za!(mT=VSPy)MzdkQ}I)$PGugOYE5FZ&14ulAD#>%N>P$lbk)tXmGg*1e@-$whFqvq>$M8j3AtHjC-J{2k zAI}!9>OzsN1f0{6mLEhjd{MMxbj14*QbN=MTNAT;bO@pR&P=SoM$N}S#4u|3%>`Ot z1T*r%C#qRbdm38pr#h_xA4A1(d=Z-iFn&qA50Tow*MzEOV!gjMeAM|O)}=OV*pN?w z5--OtX;fc4%W4Ci)7tRCNSIC5d=VQq5HiUZ@>u(M{`~o*C@OJZ6P@^&hzR9%9)?Wx zE1{*dh*@03o*Hag5m)2nI;j~S!+7{2@c|&1utgs$$=QG?HJDi6AT9Y&(J|}l&@Uo@ ziJg0g4I9Qz417+D)>jg_6s!4N(^~RDYV1v~-P$iIP@n*9aQ2U8i(*2XDJrST#0pJU zJ_aJ13pH!jY}>Z&tXZ>|mt-rx+`uGB_*livwP}sZPp)NRRXokdY7PC;X+HWfu}YNY zW3`5U=` Date: Tue, 4 Jul 2023 09:53:27 +0200 Subject: [PATCH 0614/1551] Add manual entry for `clear_cache` command (#13621) --- man/crystal.1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/man/crystal.1 b/man/crystal.1 index 3e60eb27aff0..cad5824c502a 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -370,6 +370,12 @@ to specify the cursor position. The format for the cursor position is file:line: Show type of main variables of file. .El .Pp +.It +.Cm clear_cache +.Pp +Clear the compiler cache (located at 'CRYSTAL_CACHE_DIR'). +.El +.Pp .It Cm help, Fl -help, h .Pp Show help. Option --help or -h can also be added to each command for command-specific help. From 34b43dda6bf02b4e32754938cf004253fc7a5618 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 4 Jul 2023 19:26:42 +0800 Subject: [PATCH 0615/1551] Hide `Crystal::LibEvent` from public docs (#13624) --- src/crystal/system/unix/event_libevent.cr | 128 +++++++++++----------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/src/crystal/system/unix/event_libevent.cr b/src/crystal/system/unix/event_libevent.cr index ee414a142053..852c9483809d 100644 --- a/src/crystal/system/unix/event_libevent.cr +++ b/src/crystal/system/unix/event_libevent.cr @@ -5,91 +5,93 @@ require "./lib_event2" {% end %} # :nodoc: -struct Crystal::LibEvent::Event - include Crystal::EventLoop::Event +module Crystal::LibEvent + struct Event + include Crystal::EventLoop::Event - VERSION = String.new(LibEvent2.event_get_version) + VERSION = String.new(LibEvent2.event_get_version) - def self.callback(&block : Int32, LibEvent2::EventFlags, Void* ->) - block - end - - def initialize(@event : LibEvent2::Event) - @freed = false - end - - def add(timeout : Time::Span?) : Nil - if timeout - timeval = LibC::Timeval.new( - tv_sec: LibC::TimeT.new(timeout.total_seconds), - tv_usec: timeout.nanoseconds // 1_000 - ) - LibEvent2.event_add(@event, pointerof(timeval)) - else - LibEvent2.event_add(@event, nil) + def self.callback(&block : Int32, LibEvent2::EventFlags, Void* ->) + block end - end - def free : Nil - LibEvent2.event_free(@event) unless @freed - @freed = true - end + def initialize(@event : LibEvent2::Event) + @freed = false + end - def delete - unless LibEvent2.event_del(@event) == 0 - raise "Error deleting event" + def add(timeout : Time::Span?) : Nil + if timeout + timeval = LibC::Timeval.new( + tv_sec: LibC::TimeT.new(timeout.total_seconds), + tv_usec: timeout.nanoseconds // 1_000 + ) + LibEvent2.event_add(@event, pointerof(timeval)) + else + LibEvent2.event_add(@event, nil) + end end - end - # :nodoc: - struct Base - def initialize - @base = LibEvent2.event_base_new + def free : Nil + LibEvent2.event_free(@event) unless @freed + @freed = true end - def reinit : Nil - unless LibEvent2.event_reinit(@base) == 0 - raise "Error reinitializing libevent" + def delete + unless LibEvent2.event_del(@event) == 0 + raise "Error deleting event" end end - def new_event(s : Int32, flags : LibEvent2::EventFlags, data, &callback : LibEvent2::Callback) - event = LibEvent2.event_new(@base, s, flags, callback, data.as(Void*)) - Crystal::LibEvent::Event.new(event) - end + # :nodoc: + struct Base + def initialize + @base = LibEvent2.event_base_new + end - def run_loop : Nil - LibEvent2.event_base_loop(@base, LibEvent2::EventLoopFlags::None) - end + def reinit : Nil + unless LibEvent2.event_reinit(@base) == 0 + raise "Error reinitializing libevent" + end + end - def run_once : Nil - LibEvent2.event_base_loop(@base, LibEvent2::EventLoopFlags::Once) - end + def new_event(s : Int32, flags : LibEvent2::EventFlags, data, &callback : LibEvent2::Callback) + event = LibEvent2.event_new(@base, s, flags, callback, data.as(Void*)) + Crystal::LibEvent::Event.new(event) + end - def loop_break : Nil - LibEvent2.event_base_loopbreak(@base) - end + def run_loop : Nil + LibEvent2.event_base_loop(@base, LibEvent2::EventLoopFlags::None) + end - def new_dns_base(init = true) - DnsBase.new LibEvent2.evdns_base_new(@base, init ? 1 : 0) - end - end + def run_once : Nil + LibEvent2.event_base_loop(@base, LibEvent2::EventLoopFlags::Once) + end - struct DnsBase - def initialize(@dns_base : LibEvent2::DnsBase) - end + def loop_break : Nil + LibEvent2.event_base_loopbreak(@base) + end - def getaddrinfo(nodename, servname, hints, data, &callback : LibEvent2::DnsGetAddrinfoCallback) - request = LibEvent2.evdns_getaddrinfo(@dns_base, nodename, servname, hints, callback, data.as(Void*)) - GetAddrInfoRequest.new request if request + def new_dns_base(init = true) + DnsBase.new LibEvent2.evdns_base_new(@base, init ? 1 : 0) + end end - struct GetAddrInfoRequest - def initialize(@request : LibEvent2::DnsGetAddrinfoRequest) + struct DnsBase + def initialize(@dns_base : LibEvent2::DnsBase) end - def cancel - LibEvent2.evdns_getaddrinfo_cancel(@request) + def getaddrinfo(nodename, servname, hints, data, &callback : LibEvent2::DnsGetAddrinfoCallback) + request = LibEvent2.evdns_getaddrinfo(@dns_base, nodename, servname, hints, callback, data.as(Void*)) + GetAddrInfoRequest.new request if request + end + + struct GetAddrInfoRequest + def initialize(@request : LibEvent2::DnsGetAddrinfoRequest) + end + + def cancel + LibEvent2.evdns_getaddrinfo_cancel(@request) + end end end end From 03301a5a9330ee74c2b25f0651ecb887f59c9bf9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 4 Jul 2023 22:05:35 +0800 Subject: [PATCH 0616/1551] Restrict Windows CI jobs for installer packages to release branches (#13623) --- .github/workflows/win.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 1d6be1e017c5..be017271c97f 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -252,12 +252,14 @@ jobs: run: make -f Makefile.win samples x86_64-windows-release: + if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/ci/')) needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm] uses: ./.github/workflows/win_build_portable.yml with: release: true x86_64-windows-installer: + if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/ci/')) runs-on: windows-2022 needs: [x86_64-windows-release] steps: From b35413049c7bb9e74e5d88027382f3fd62e97388 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 5 Jul 2023 00:43:47 +0800 Subject: [PATCH 0617/1551] Do not reopen current file in `File#utime` on Windows (#13625) --- spec/std/file_spec.cr | 4 ++-- src/crystal/system/unix/file.cr | 28 +++++++--------------------- src/crystal/system/unix/time.cr | 14 ++++++++++++++ src/crystal/system/win32/file.cr | 22 +++++++++++++--------- src/file.cr | 2 +- 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 24bb937662f7..7a331853094e 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -1270,7 +1270,7 @@ describe "File" do end describe "utime" do - it "sets times with utime" do + it "sets times with class method" do with_tempfile("utime-set.txt") do |path| File.write(path, "") @@ -1284,7 +1284,7 @@ describe "File" do end end - it "sets times with futime" do + it "sets times with instance method" do with_tempfile("utime-set.txt") do |path| File.open(path, "w") do |file| atime = Time.utc(2000, 1, 2) diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index 1c3314340c6b..2d2c243b9b48 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -183,24 +183,24 @@ module Crystal::System::File def self.utime(atime : ::Time, mtime : ::Time, filename : String) : Nil timevals = uninitialized LibC::Timeval[2] - timevals[0] = to_timeval(atime) - timevals[1] = to_timeval(mtime) + timevals[0] = Crystal::System::Time.to_timeval(atime) + timevals[1] = Crystal::System::Time.to_timeval(mtime) ret = LibC.utimes(filename, timevals) if ret != 0 raise ::File::Error.from_errno("Error setting time on file", file: filename) end end - def self.futimens(filename : String, fd : Int, atime : ::Time, mtime : ::Time) : Nil + private def system_utime(atime : ::Time, mtime : ::Time, filename : String) : Nil ret = {% if LibC.has_method?("futimens") %} timespecs = uninitialized LibC::Timespec[2] - timespecs[0] = to_timespec(atime) - timespecs[1] = to_timespec(mtime) + timespecs[0] = Crystal::System::Time.to_timespec(atime) + timespecs[1] = Crystal::System::Time.to_timespec(mtime) LibC.futimens(fd, timespecs) {% elsif LibC.has_method?("futimes") %} timevals = uninitialized LibC::Timeval[2] - timevals[0] = to_timeval(atime) - timevals[1] = to_timeval(mtime) + timevals[0] = Crystal::System::Time.to_timeval(atime) + timevals[1] = Crystal::System::Time.to_timeval(mtime) LibC.futimes(fd, timevals) {% else %} {% raise "Missing futimens & futimes" %} @@ -211,20 +211,6 @@ module Crystal::System::File end end - private def self.to_timespec(time : ::Time) - t = uninitialized LibC::Timespec - t.tv_sec = typeof(t.tv_sec).new(time.to_unix) - t.tv_nsec = typeof(t.tv_nsec).new(time.nanosecond) - t - end - - private def self.to_timeval(time : ::Time) - t = uninitialized LibC::Timeval - t.tv_sec = typeof(t.tv_sec).new(time.to_unix) - t.tv_usec = typeof(t.tv_usec).new(time.nanosecond // ::Time::NANOSECONDS_PER_MICROSECOND) - t - end - private def system_truncate(size) : Nil flush code = LibC.ftruncate(fd, size) diff --git a/src/crystal/system/unix/time.cr b/src/crystal/system/unix/time.cr index c2dc9976fd79..92d8a7516eb2 100644 --- a/src/crystal/system/unix/time.cr +++ b/src/crystal/system/unix/time.cr @@ -40,6 +40,20 @@ module Crystal::System::Time {% end %} end + def self.to_timespec(time : ::Time) + t = uninitialized LibC::Timespec + t.tv_sec = typeof(t.tv_sec).new(time.to_unix) + t.tv_nsec = typeof(t.tv_nsec).new(time.nanosecond) + t + end + + def self.to_timeval(time : ::Time) + t = uninitialized LibC::Timeval + t.tv_sec = typeof(t.tv_sec).new(time.to_unix) + t.tv_usec = typeof(t.tv_usec).new(time.nanosecond // ::Time::NANOSECONDS_PER_MICROSECOND) + t + end + # Many systems use /usr/share/zoneinfo, Solaris 2 has # /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ. ZONE_SOURCES = { diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index c6ec882421d8..3cb11e53564f 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -401,12 +401,10 @@ module Crystal::System::File end def self.utime(access_time : ::Time, modification_time : ::Time, path : String) : Nil - atime = Crystal::System::Time.to_filetime(access_time) - mtime = Crystal::System::Time.to_filetime(modification_time) handle = LibC.CreateFileW( System.to_wstr(path), LibC::FILE_WRITE_ATTRIBUTES, - LibC::FILE_SHARE_READ | LibC::FILE_SHARE_WRITE | LibC::FILE_SHARE_DELETE, + LibC::DEFAULT_SHARE_MODE, nil, LibC::OPEN_EXISTING, LibC::FILE_FLAG_BACKUP_SEMANTICS, @@ -415,18 +413,24 @@ module Crystal::System::File if handle == LibC::INVALID_HANDLE_VALUE raise ::File::Error.from_winerror("Error setting time on file", file: path) end + begin - if LibC.SetFileTime(handle, nil, pointerof(atime), pointerof(mtime)) == 0 - raise ::File::Error.from_winerror("Error setting time on file", file: path) - end + utime(handle, access_time, modification_time, path) ensure LibC.CloseHandle(handle) end end - def self.futimens(path : String, fd : Int, access_time : ::Time, modification_time : ::Time) : Nil - # TODO: use fd instead of path - utime access_time, modification_time, path + def self.utime(handle : LibC::HANDLE, access_time : ::Time, modification_time : ::Time, path : String) : Nil + atime = Crystal::System::Time.to_filetime(access_time) + mtime = Crystal::System::Time.to_filetime(modification_time) + if LibC.SetFileTime(handle, nil, pointerof(atime), pointerof(mtime)) == 0 + raise ::File::Error.from_winerror("Error setting time on file", file: path) + end + end + + private def system_utime(access_time : ::Time, modification_time : ::Time, path : String) : Nil + Crystal::System::File.utime(windows_handle, access_time, modification_time, path) end private def system_truncate(size : Int) : Nil diff --git a/src/file.cr b/src/file.cr index 7092abb13648..fdc4b2c090b0 100644 --- a/src/file.cr +++ b/src/file.cr @@ -948,7 +948,7 @@ class File < IO::FileDescriptor # Sets the access and modification times def utime(atime : Time, mtime : Time) : Nil - Crystal::System::File.futimens(@path, fd, atime, mtime) + system_utime(atime, mtime, @path) end # Attempts to set the access and modification times From 641031490871434b4909f66756fc109097ece612 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 5 Jul 2023 16:50:17 +0800 Subject: [PATCH 0618/1551] Allow `Dir.delete` to remove read-only directories on Windows (#13626) --- spec/std/dir_spec.cr | 9 +++++++++ src/crystal/system/win32/dir.cr | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index 6fb95a9efcd3..bba717798423 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -180,6 +180,15 @@ describe "Dir" do end end end + + it "deletes a read-only directory" do + with_tempfile("delete-readonly-dir") do |path| + Dir.mkdir(path) + File.chmod(path, 0o000) + Dir.delete(path) + Dir.exists?(path).should be_false + end + end end describe "glob" do diff --git a/src/crystal/system/win32/dir.cr b/src/crystal/system/win32/dir.cr index 1c3502efc269..76165e75d8e2 100644 --- a/src/crystal/system/win32/dir.cr +++ b/src/crystal/system/win32/dir.cr @@ -170,7 +170,17 @@ module Crystal::System::Dir raise ::File::Error.new("Cannot remove directory that is a reparse point: '#{path.inspect_unquoted}'", file: path) end + # Windows cannot delete read-only files, so we unset the attribute here, but + # restore it afterwards if deletion still failed + read_only_removed = false + if attributes.bits_set?(LibC::FILE_ATTRIBUTE_READONLY) + if LibC.SetFileAttributesW(win_path, attributes & ~LibC::FILE_ATTRIBUTE_READONLY) != 0 + read_only_removed = true + end + end + return true if LibC._wrmdir(win_path) == 0 + LibC.SetFileAttributesW(win_path, attributes) if read_only_removed raise ::File::Error.from_errno("Unable to remove directory", file: path) end end From 7881486891da367c2947d67030b345ce4600a2db Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 5 Jul 2023 16:50:35 +0800 Subject: [PATCH 0619/1551] Use current directory's root for `Dir.glob("/...")` on Windows (#13628) --- spec/std/dir_spec.cr | 1 + src/dir/glob.cr | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index bba717798423..6f709f53c26c 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -428,6 +428,7 @@ describe "Dir" do it "root pattern" do {% if flag?(:windows) %} Dir["C:/"].should eq ["C:\\"] + Dir["/"].should eq [Path[Dir.current].anchor.not_nil!.to_s] {% else %} Dir["/"].should eq ["/"] {% end %} diff --git a/src/dir/glob.cr b/src/dir/glob.cr index 69efb55cc9ee..25ec6bcd7683 100644 --- a/src/dir/glob.cr +++ b/src/dir/glob.cr @@ -375,12 +375,7 @@ class Dir end private def self.root - # TODO: better implementation for windows? - {% if flag?(:windows) %} - "C:\\" - {% else %} - File::SEPARATOR_STRING - {% end %} + Path[Dir.current].anchor.not_nil!.to_s end private def self.dir?(path, follow_symlinks) From ea92174624fb28bd1870674b28f326bdb4b60d98 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 5 Jul 2023 16:50:46 +0800 Subject: [PATCH 0620/1551] Do not reopen current file in `File#chmod` on Windows (#13627) --- spec/std/file_spec.cr | 4 ++-- src/crystal/system/unix/file.cr | 2 +- src/crystal/system/win32/file.cr | 23 ++++++++++++++++--- src/file.cr | 2 +- src/lib_c/x86_64-windows-msvc/c/fileapi.cr | 1 + src/lib_c/x86_64-windows-msvc/c/minwinbase.cr | 4 ++++ src/lib_c/x86_64-windows-msvc/c/winbase.cr | 10 ++++++++ 7 files changed, 39 insertions(+), 7 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 7a331853094e..ae3e7d7519cf 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -351,7 +351,7 @@ describe "File" do end describe "chmod" do - it "changes file permissions" do + it "changes file permissions with class method" do path = datapath("chmod.txt") begin File.write(path, "") @@ -362,7 +362,7 @@ describe "File" do end end - it "changes file permissions with fchmod" do + it "changes file permissions with instance method" do path = datapath("chmod.txt") begin File.open(path, "w") do |file| diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index 2d2c243b9b48..665a61b22493 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -119,7 +119,7 @@ module Crystal::System::File end end - def self.fchmod(path, fd, mode) + private def system_chmod(path, mode) if LibC.fchmod(fd, mode) == -1 raise ::File::Error.from_errno("Error changing permissions", file: path) end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 3cb11e53564f..4fceee02c6d4 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -233,9 +233,26 @@ module Crystal::System::File end end - def self.fchmod(path : String, fd : Int, mode : Int32 | ::File::Permissions) : Nil - # TODO: use fd instead of path - chmod path, mode + private def system_chmod(path : String, mode : Int32 | ::File::Permissions) : Nil + mode = ::File::Permissions.new(mode) unless mode.is_a? ::File::Permissions + handle = windows_handle + + basic_info = uninitialized LibC::FILE_BASIC_INFO + if LibC.GetFileInformationByHandleEx(handle, LibC::FILE_INFO_BY_HANDLE_CLASS::FileBasicInfo, pointerof(basic_info), sizeof(typeof(basic_info))) == 0 + raise ::File::Error.from_winerror("Error changing permissions", file: path) + end + + # Only the owner writable bit is used, since windows only supports + # the read only attribute. + if mode.owner_write? + basic_info.fileAttributes &= ~LibC::FILE_ATTRIBUTE_READONLY + else + basic_info.fileAttributes |= LibC::FILE_ATTRIBUTE_READONLY + end + + if LibC.SetFileInformationByHandle(handle, LibC::FILE_INFO_BY_HANDLE_CLASS::FileBasicInfo, pointerof(basic_info), sizeof(typeof(basic_info))) == 0 + raise ::File::Error.from_winerror("Error changing permissions", file: path) + end end def self.delete(path : String, *, raise_on_missing : Bool) : Bool diff --git a/src/file.cr b/src/file.cr index fdc4b2c090b0..8c363868f50e 100644 --- a/src/file.cr +++ b/src/file.cr @@ -943,7 +943,7 @@ class File < IO::FileDescriptor # file.info.permissions.value # => 0o700 # ``` def chmod(permissions : Int | Permissions) : Nil - Crystal::System::File.fchmod(@path, fd, permissions) + system_chmod(@path, permissions) end # Sets the access and modification times diff --git a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr index 3ad2e25f663b..88b485c0981b 100644 --- a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr @@ -111,4 +111,5 @@ lib LibC ) : BOOL fun SetFileTime(hFile : HANDLE, lpCreationTime : FILETIME*, lpLastAccessTime : FILETIME*, lpLastWriteTime : FILETIME*) : BOOL + fun SetFileInformationByHandle(hFile : HANDLE, fileInformationClass : FILE_INFO_BY_HANDLE_CLASS, lpFileInformation : Void*, dwBufferSize : DWORD) : BOOL end diff --git a/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr b/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr index 4f22a8242d18..bb36bbf279af 100644 --- a/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr @@ -45,6 +45,10 @@ lib LibC GetFileExMaxInfoLevel end + enum FILE_INFO_BY_HANDLE_CLASS + FileBasicInfo = 0 + end + LOCKFILE_FAIL_IMMEDIATELY = DWORD.new(0x00000001) LOCKFILE_EXCLUSIVE_LOCK = DWORD.new(0x00000002) diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index 40ef8a695d20..138eb04c1333 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -57,4 +57,14 @@ lib LibC fun MoveFileExW(lpExistingFileName : LPWSTR, lpNewFileName : LPWSTR, dwFlags : DWORD) : BOOL fun GetBinaryTypeW(lpApplicationName : LPWSTR, lpBinaryType : DWORD*) : BOOL + + struct FILE_BASIC_INFO + creationTime : LARGE_INTEGER + lastAccessTime : LARGE_INTEGER + lastWriteTime : LARGE_INTEGER + changeTime : LARGE_INTEGER + fileAttributes : DWORD + end + + fun GetFileInformationByHandleEx(hFile : HANDLE, fileInformationClass : FILE_INFO_BY_HANDLE_CLASS, lpFileInformation : Void*, dwBufferSize : DWORD) : BOOL end From 3268c819d5a3b9b38c60372c27fe6b31586ee241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 7 Jul 2023 19:20:20 +0200 Subject: [PATCH 0621/1551] Fix ambiguous call with untyped int literal in `{JSON,YAML}::Any.new` (#13618) --- spec/std/json/any_spec.cr | 13 +++++++++++++ spec/std/yaml/any_spec.cr | 16 ++++++++++++++++ src/json/any.cr | 12 ++++++++++++ src/yaml/any.cr | 12 ++++++++++++ 4 files changed, 53 insertions(+) diff --git a/spec/std/json/any_spec.cr b/spec/std/json/any_spec.cr index 6601a53378bd..95f7425f2ed4 100644 --- a/spec/std/json/any_spec.cr +++ b/spec/std/json/any_spec.cr @@ -3,6 +3,19 @@ require "json" require "yaml" describe JSON::Any do + it ".new" do + JSON::Any.new(nil).raw.should be_nil + JSON::Any.new(true).raw.should eq true + JSON::Any.new(1_i64).raw.should eq 1_i64 + JSON::Any.new(1).raw.should eq 1 + JSON::Any.new(1_u8).raw.should eq 1 + JSON::Any.new(0.0).raw.should eq 0.0 + JSON::Any.new(0.0_f32).raw.should eq 0.0 + JSON::Any.new("foo").raw.should eq "foo" + JSON::Any.new([] of JSON::Any).raw.should eq [] of JSON::Any + JSON::Any.new({} of String => JSON::Any).raw.should eq({} of String => JSON::Any) + end + describe "casts" do it "gets nil" do JSON.parse("null").as_nil.should be_nil diff --git a/spec/std/yaml/any_spec.cr b/spec/std/yaml/any_spec.cr index ffab8f14babc..1eedb5e4366b 100644 --- a/spec/std/yaml/any_spec.cr +++ b/spec/std/yaml/any_spec.cr @@ -89,6 +89,22 @@ private def it_fetches_from_hash?(key, *equivalent_keys) end describe YAML::Any do + it ".new" do + YAML::Any.new(nil).raw.should be_nil + YAML::Any.new(true).raw.should eq true + YAML::Any.new(1_i64).raw.should eq 1_i64 + YAML::Any.new(1).raw.should eq 1 + YAML::Any.new(1_u8).raw.should eq 1 + YAML::Any.new(0.0).raw.should eq 0.0 + YAML::Any.new(0.0_f32).raw.should eq 0.0 + YAML::Any.new("foo").raw.should eq "foo" + YAML::Any.new(Time.utc(2023, 7, 2)).raw.should eq Time.utc(2023, 7, 2) + YAML::Any.new(Bytes[1, 2, 3]).raw.should eq Bytes[1, 2, 3] + YAML::Any.new([] of YAML::Any).raw.should eq [] of YAML::Any + YAML::Any.new({} of YAML::Any => YAML::Any).raw.should eq({} of YAML::Any => YAML::Any) + YAML::Any.new(Set(YAML::Any).new).raw.should eq Set(YAML::Any).new + end + describe "casts" do it "gets nil" do YAML.parse("").as_nil.should be_nil diff --git a/src/json/any.cr b/src/json/any.cr index d64b7859bc53..057fd54e737d 100644 --- a/src/json/any.cr +++ b/src/json/any.cr @@ -57,6 +57,18 @@ struct JSON::Any def initialize(@raw : Type) end + # :ditto: + def self.new(raw : Int) + # FIXME: Workaround for https://github.com/crystal-lang/crystal/issues/11645 + new(raw.to_i64) + end + + # :ditto: + def self.new(raw : Float) + # FIXME: Workaround for https://github.com/crystal-lang/crystal/issues/11645 + new(raw.to_f64) + end + # Assumes the underlying value is an `Array` or `Hash` and returns its size. # Raises if the underlying value is not an `Array` or `Hash`. def size : Int diff --git a/src/yaml/any.cr b/src/yaml/any.cr index 1518623efd5f..745662282dd7 100644 --- a/src/yaml/any.cr +++ b/src/yaml/any.cr @@ -66,6 +66,18 @@ struct YAML::Any def initialize(@raw : Type) end + # :ditto: + def self.new(raw : Int) + # FIXME: Workaround for https://github.com/crystal-lang/crystal/issues/11645 + new(raw.to_i64) + end + + # :ditto: + def self.new(raw : Float) + # FIXME: Workaround for https://github.com/crystal-lang/crystal/issues/11645 + new(raw.to_f64) + end + # Assumes the underlying value is an `Array` or `Hash` and returns its size. # # Raises if the underlying value is not an `Array` or `Hash`. From e2222d4b3f726d3c05085a62aaecf0596aa6f508 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 11 Jul 2023 05:27:41 +0800 Subject: [PATCH 0622/1551] Make DLL delay-load opt-in (#13645) --- src/compiler/crystal/compiler.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index f79df429dea8..f8e02b799859 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -383,7 +383,7 @@ module Crystal link_args = search_result.remaining_args.concat(search_result.library_paths).map { |arg| Process.quote_windows(arg) } - if !program.has_flag?("no_win32_delay_load") + if program.has_flag?("preview_win32_delay_load") # "LINK : warning LNK4199: /DELAYLOAD:foo.dll ignored; no imports found from foo.dll" # it is harmless to skip this error because not all import libraries are always used, much # less the individual DLLs they refer to From 6f97ec71a60742dc2ff61093d2439d587b715d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 11 Jul 2023 17:31:21 +0200 Subject: [PATCH 0623/1551] Add changelog for 1.9.0 (#13633) Co-authored-by: Quinton Miller --- CHANGELOG.md | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++ UPGRADING.md | 19 +++++ src/VERSION | 2 +- 3 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 UPGRADING.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 3021502fc9f6..cf7beaebc581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,230 @@ +# 1.9.0 (2023-07-11) +### Breaking changes + +#### stdlib + +- *(numeric)* Handle NaNs when comparing `Big*` numbers against `Float` ([#13293](https://github.com/crystal-lang/crystal/pull/13293), [#13294](https://github.com/crystal-lang/crystal/pull/13294), [#13350](https://github.com/crystal-lang/crystal/pull/13350), [#13554](https://github.com/crystal-lang/crystal/pull/13554), thanks @HertzDevil) +- *(llvm)* Remove most `LLVM::DIBuilder` functions from `llvm_ext.cc` ([#13448](https://github.com/crystal-lang/crystal/pull/13448), thanks @HertzDevil) + +### Features + +#### lang + +- *(macros)* Add `warning` macro ([#13262](https://github.com/crystal-lang/crystal/pull/13262), thanks @Blacksmoke16) +- *(macros)* Add `print` macro ([#13336](https://github.com/crystal-lang/crystal/pull/13336), thanks @jkthorne) + +#### stdlib + +- *(collection)* Add `Enumerable#in_slices_of` ([#13108](https://github.com/crystal-lang/crystal/pull/13108), thanks @pricelessrabbit) +- *(collection)* Add support for dash separator to `Enum.parse` ([#13508](https://github.com/crystal-lang/crystal/pull/13508), thanks @straight-shoota) +- *(collection)* Add `Enum#to_i128` and `#to_u128` ([#13576](https://github.com/crystal-lang/crystal/pull/13576), thanks @meatball133) +- *(collection)* Add `Enumerable#partition` overload with type filter ([#13572](https://github.com/crystal-lang/crystal/pull/13572), thanks @baseballlover723) +- *(concurrency)* Support asynchronous `IO.pipe` on Windows ([#13362](https://github.com/crystal-lang/crystal/pull/13362), thanks @HertzDevil) +- *(files)* **[deprecation]** Add `File::MatchOptions` to control `Dir.glob`'s behavior ([#13550](https://github.com/crystal-lang/crystal/pull/13550), thanks @HertzDevil) +- *(networking)* Implement `Socket#reuse_port` on Windows ([#13326](https://github.com/crystal-lang/crystal/pull/13326), thanks @stakach) +- *(networking)* Add multicast support to `UDPSocket` on Windows ([#13325](https://github.com/crystal-lang/crystal/pull/13325), thanks @stakach) +- *(networking)* HTTP Server should allow custom concurrency models ([#13428](https://github.com/crystal-lang/crystal/pull/13428), thanks @stakach) +- *(networking)* Add `Socket::IPaddress.v4`, `.v6`, `.v4_mapped_v6` ([#13422](https://github.com/crystal-lang/crystal/pull/13422), thanks @HertzDevil) +- *(networking)* Add `URI::Params#merge`, `#merge!` and `URI#update_query_params` ([#13415](https://github.com/crystal-lang/crystal/pull/13415), thanks @skinnyjames) +- *(networking)* Support Unix sockets on Windows ([#13493](https://github.com/crystal-lang/crystal/pull/13493), thanks @HertzDevil) +- *(networking)* Add `HTTP::Request#form_params` ([#13418](https://github.com/crystal-lang/crystal/pull/13418), thanks @threez) +- *(numeric)* Add `BigDecimal#%` ([#13255](https://github.com/crystal-lang/crystal/pull/13255), thanks @MattAlp) +- *(numeric)* Improve conversions from `BigInt` to `Int::Primitive` ([#13562](https://github.com/crystal-lang/crystal/pull/13562), thanks @HertzDevil) +- *(runtime)* Print error if unable to delay-load DLL on Windows ([#13475](https://github.com/crystal-lang/crystal/pull/13475), thanks @HertzDevil) +- *(runtime)* Add default interrupt handlers ([#13568](https://github.com/crystal-lang/crystal/pull/13568), thanks @straight-shoota) +- *(serialization)* Add `ignore_serialize` for `YAML::Serializable` ([#13556](https://github.com/crystal-lang/crystal/pull/13556), thanks @meatball133) +- *(specs)* Add a testcase line number to the output of JUnitFormatter ([#13468](https://github.com/crystal-lang/crystal/pull/13468), thanks @nobodywasishere) +- *(specs)* Publish the `assert_prints` spec helper ([#13599](https://github.com/crystal-lang/crystal/pull/13599), thanks @HertzDevil) +- *(system)* Implement `Process.exec` on Windows ([#13374](https://github.com/crystal-lang/crystal/pull/13374), thanks @HertzDevil) +- *(system)* Add `File::BadExecutableError` ([#13491](https://github.com/crystal-lang/crystal/pull/13491), thanks @HertzDevil) +- *(text)* Add inspection of Regex options support ([#13354](https://github.com/crystal-lang/crystal/pull/13354), thanks @straight-shoota) +- *(text)* Add `Regex.literal` ([#13339](https://github.com/crystal-lang/crystal/pull/13339), thanks @straight-shoota) +- *(text)* Implement `#match!` for Regex ([#13285](https://github.com/crystal-lang/crystal/pull/13285), thanks @devnote-dev) +- *(text)* Add parameters for `Regex::MatchOptions` to matching methods ([#13353](https://github.com/crystal-lang/crystal/pull/13353), thanks @straight-shoota) +- *(text)* Add `Char#titlecase` for correct mixed-case transformations ([#13539](https://github.com/crystal-lang/crystal/pull/13539), thanks @HertzDevil) +- *(time)* Add `start_day` parameter to `Time#at_beginning_of_week` ([#13446](https://github.com/crystal-lang/crystal/pull/13446), thanks @DanielGilchrist) +- *(time)* Map IANA time zone identifiers to Windows time zones ([#13517](https://github.com/crystal-lang/crystal/pull/13517), thanks @HertzDevil) +- *(time)* Add `Time.unix_ns` and `#to_unix_ns` ([#13359](https://github.com/crystal-lang/crystal/pull/13359), thanks @garymardell) + +#### compiler + +- Add message about non-release mode to `crystal --version` ([#13254](https://github.com/crystal-lang/crystal/pull/13254), thanks @will) +- Respect `%CC%` on Windows ([#13376](https://github.com/crystal-lang/crystal/pull/13376), thanks @HertzDevil) +- Support DLL delay-loading on Windows ([#13436](https://github.com/crystal-lang/crystal/pull/13436), thanks @HertzDevil) +- Support `-static` and `-dynamic` `.lib` suffixes on Windows ([#13473](https://github.com/crystal-lang/crystal/pull/13473), [#13645](https://github.com/crystal-lang/crystal/pull/13645), thanks @HertzDevil) +- Make compiler aware of output extension when building programs ([#13370](https://github.com/crystal-lang/crystal/pull/13370), thanks @HertzDevil) +- Support `CRYSTAL_LIBRARY_RPATH` for adding dynamic library lookup paths ([#13499](https://github.com/crystal-lang/crystal/pull/13499), thanks @HertzDevil) +- Add compiler command `crystal clear_cache` ([#13553](https://github.com/crystal-lang/crystal/pull/13553), thanks @baseballlover723) +- *(codegen)* Support LLVM 16 ([#13181](https://github.com/crystal-lang/crystal/pull/13181), thanks @HertzDevil) +- *(semantic)* Correctly ignore nested deprecation warnings ([#13513](https://github.com/crystal-lang/crystal/pull/13513), thanks @straight-shoota) + +#### tools + +- *(docs-generator)* Add dark mode to docs ([#13512](https://github.com/crystal-lang/crystal/pull/13512), thanks @GeopJr) +- *(docs-generator)* Add mobile support to docs ([#13515](https://github.com/crystal-lang/crystal/pull/13515), thanks @GeopJr) +- *(formatter)* **[security]** Formatter: escape bi-directional control characters within strings ([#13067](https://github.com/crystal-lang/crystal/pull/13067), thanks @HertzDevil) + +### Bugfixes + +#### stdlib + +- *(collection)* Fix `Array#flatten` to discard `Iterator::Stop` ([#13388](https://github.com/crystal-lang/crystal/pull/13388), thanks @straight-shoota) +- *(collection)* Fix return type of `Iterator#chunk` and `Enumerable#chunks` without `Drop` ([#13506](https://github.com/crystal-lang/crystal/pull/13506), thanks @straight-shoota) +- *(collection)* Fix `Iterator#with_index(offset)` with non-`Int32` `offset` ([#13612](https://github.com/crystal-lang/crystal/pull/13612), thanks @HertzDevil) +- *(concurrency)* Fix `preview_mt` infinite loop on Windows ([#13419](https://github.com/crystal-lang/crystal/pull/13419), thanks @HertzDevil) +- *(concurrency)* Fix `Atomic#max` and `#min` for signed enums ([#13524](https://github.com/crystal-lang/crystal/pull/13524), thanks @HertzDevil) +- *(concurrency)* Fix timeout events getting lost on Windows ([#13525](https://github.com/crystal-lang/crystal/pull/13525), thanks @HertzDevil) +- *(concurrency)* Support `Atomic(T)#compare_and_set` when `T` is a reference union ([#13565](https://github.com/crystal-lang/crystal/pull/13565), thanks @HertzDevil) +- *(files)* Fix `Dir#info` on Windows ([#13395](https://github.com/crystal-lang/crystal/pull/13395), thanks @HertzDevil) +- *(files)* Windows: open standard streams in binary mode ([#13397](https://github.com/crystal-lang/crystal/pull/13397), thanks @HertzDevil) +- *(files)* Fix `File.info(File::NULL)` on Windows ([#13421](https://github.com/crystal-lang/crystal/pull/13421), thanks @HertzDevil) +- *(files)* Allow `File.delete` to remove read-only files on Windows ([#13462](https://github.com/crystal-lang/crystal/pull/13462), thanks @HertzDevil) +- *(files)* Make `fcntl` defined on all platforms ([#13495](https://github.com/crystal-lang/crystal/pull/13495), thanks @HertzDevil) +- *(files)* Allow `Dir.delete` to remove read-only directories on Windows ([#13626](https://github.com/crystal-lang/crystal/pull/13626), thanks @HertzDevil) +- *(files)* Use current directory's root for `Dir.glob("/...")` on Windows ([#13628](https://github.com/crystal-lang/crystal/pull/13628), thanks @HertzDevil) +- *(llvm)* Fix `LLVM.default_target_triple` to normalize aarch64 darwin target ([#13597](https://github.com/crystal-lang/crystal/pull/13597), thanks @straight-shoota) +- *(log)* Fix `Log::Builder` append `BroadcastBackend` to itself ([#13405](https://github.com/crystal-lang/crystal/pull/13405), thanks @straight-shoota) +- *(macros)* Fix error message for calling `record` macro with kwargs ([#13367](https://github.com/crystal-lang/crystal/pull/13367), thanks @a-alhusaini) +- *(networking)* Remove double URL escape in `HTTP::Server::Response.redirect` ([#13321](https://github.com/crystal-lang/crystal/pull/13321), thanks @threez) +- *(networking)* Fix WebSocket capitalization in docs ([#13331](https://github.com/crystal-lang/crystal/pull/13331), thanks @joshrickard) +- *(networking)* Fix `TCPSocket#tcp_keepalive_idle` on Windows ([#13364](https://github.com/crystal-lang/crystal/pull/13364), thanks @HertzDevil) +- *(networking)* Fix client-side `TCPSocket#remote_address` on Windows ([#13363](https://github.com/crystal-lang/crystal/pull/13363), thanks @HertzDevil) +- *(networking)* Parse IP addresses in Crystal instead of using `LibC.inet_pton` ([#13463](https://github.com/crystal-lang/crystal/pull/13463), thanks @HertzDevil) +- *(networking)* Windows: do not set `SO_EXCLUSIVEADDRUSE` if `SO_REUSEADDR` already present ([#13477](https://github.com/crystal-lang/crystal/pull/13477), thanks @HertzDevil) +- *(networking)* Implement `Socket::IPAddress#to_s` with Crystal instead of `LibC.inet_ntop` ([#13483](https://github.com/crystal-lang/crystal/pull/13483), thanks @HertzDevil) +- *(networking)* Ensure `Socket` checks `WinError.wsa_value` on Windows, not `Errno.value` ([#13494](https://github.com/crystal-lang/crystal/pull/13494), thanks @HertzDevil) +- *(numeric)* Disallow creating `Big*` numbers from infinity or NaN ([#13351](https://github.com/crystal-lang/crystal/pull/13351), thanks @HertzDevil) +- *(numeric)* Fix `LibM.hypotf` and `ldexpf` link errors on Windows ([#13485](https://github.com/crystal-lang/crystal/pull/13485), thanks @HertzDevil) +- *(numeric)* Make comparisons between `BigRational` and `BigFloat` exact ([#13538](https://github.com/crystal-lang/crystal/pull/13538), thanks @HertzDevil) +- *(runtime)* Fix size of type_id in `Object.set_crystal_type_id` ([#13338](https://github.com/crystal-lang/crystal/pull/13338), thanks @straight-shoota) +- *(runtime)* Allow `/SUBSYSTEM:WINDOWS` on Windows ([#13332](https://github.com/crystal-lang/crystal/pull/13332), thanks @HertzDevil) +- *(runtime)* Use correct format strings for crash stack traces ([#13408](https://github.com/crystal-lang/crystal/pull/13408), thanks @HertzDevil) +- *(serialization)* Fix handling of quoted boolean values in `YAML::Any` ([#13546](https://github.com/crystal-lang/crystal/pull/13546), thanks @willhbr) +- *(serialization)* Fix ambiguous call with untyped int literal in `{JSON,YAML}::Any.new` ([#13618](https://github.com/crystal-lang/crystal/pull/13618), thanks @straight-shoota) +- *(system)* Fix for Process: ensure chdir is a string ([#13503](https://github.com/crystal-lang/crystal/pull/13503), thanks @devnote-dev) +- *(system)* Windows: drop internal environment variables from `ENV` ([#13570](https://github.com/crystal-lang/crystal/pull/13570), thanks @HertzDevil) +- *(text)* Fix `String#underscore` with multi-character downcasing ([#13540](https://github.com/crystal-lang/crystal/pull/13540), thanks @HertzDevil) +- *(text)* Do not attempt downcasing first when case-folding a `Char` ([#13542](https://github.com/crystal-lang/crystal/pull/13542), thanks @HertzDevil) +- *(text)* Handle case folding in `String#compare` correctly ([#13532](https://github.com/crystal-lang/crystal/pull/13532), thanks @HertzDevil) +- *(time)* Update list of Windows time zones ([#13501](https://github.com/crystal-lang/crystal/pull/13501), thanks @HertzDevil) +- *(time)* Fix local timezones without DST on Windows ([#13516](https://github.com/crystal-lang/crystal/pull/13516), thanks @HertzDevil) +- *(time)* Fix leap second handling for timezone information files ([#13600](https://github.com/crystal-lang/crystal/pull/13600), thanks @HertzDevil) + +#### compiler + +- More accurate macro errors ([#13260](https://github.com/crystal-lang/crystal/pull/13260), thanks @Blacksmoke16) +- Do not drop `/LIBPATH` from Windows linker command ([#13530](https://github.com/crystal-lang/crystal/pull/13530), thanks @HertzDevil) +- Fix instantiated method signatures in error traces ([#13580](https://github.com/crystal-lang/crystal/pull/13580), thanks @HertzDevil) +- Place `--emit` files back in current directory when running source ([#13604](https://github.com/crystal-lang/crystal/pull/13604), thanks @HertzDevil) +- *(generics)* Fix free variable matching of bound numeric values ([#13606](https://github.com/crystal-lang/crystal/pull/13606), thanks @HertzDevil) +- *(parser)* Don't skip the token immediately after `lib` name ([#13407](https://github.com/crystal-lang/crystal/pull/13407), thanks @FnControlOption) +- *(parser)* Allow newline after hash rocket ([#13460](https://github.com/crystal-lang/crystal/pull/13460), thanks @FnControlOption) +- *(parser)* Add missing locations of various AST nodes ([#13452](https://github.com/crystal-lang/crystal/pull/13452), thanks @FnControlOption) +- *(parser)* Fix AST location of call name in operator assignment ([#13456](https://github.com/crystal-lang/crystal/pull/13456), thanks @FnControlOption) + +#### tools + +- Display `Bool`'s size as 1 byte in `crystal tool hierarchy`, not 0 ([#13588](https://github.com/crystal-lang/crystal/pull/13588), thanks @HertzDevil) + +### Performance + +#### stdlib + +- *(collection)* Optimize `Array#concat(Indexable)` ([#13280](https://github.com/crystal-lang/crystal/pull/13280), thanks @HertzDevil) +- *(collection)* Optimize `Deque#concat(Indexable)` ([#13283](https://github.com/crystal-lang/crystal/pull/13283), thanks @HertzDevil) +- *(concurrency)* Support synchronous socket operations on Windows ([#13414](https://github.com/crystal-lang/crystal/pull/13414), thanks @HertzDevil) +- *(numeric)* Optimize `BigInt.new(Int::Primitive)` ([#13303](https://github.com/crystal-lang/crystal/pull/13303), thanks @HertzDevil) +- *(numeric)* Optimize `BigRational#<=>(Int)` ([#13555](https://github.com/crystal-lang/crystal/pull/13555), thanks @HertzDevil) +- *(text)* Improve `HTML.escape(string, io)` performance ([#13139](https://github.com/crystal-lang/crystal/pull/13139), thanks @BlobCodes) +- *(text)* Refactor `String.ends_with?` to use `MatchOptions::ENDANCHORED` ([#13551](https://github.com/crystal-lang/crystal/pull/13551), thanks @straight-shoota) + +#### tools + +- *(docs-generator)* Optimize `Doc::Method#compute_doc_info` to avoid duplicate regex ([#13324](https://github.com/crystal-lang/crystal/pull/13324), thanks @straight-shoota) + +### Refactor + +#### stdlib + +- Use sentence case for all standard library exceptions ([#13400](https://github.com/crystal-lang/crystal/pull/13400), thanks @HertzDevil) +- *(collection)* Refactor code for `Deque` buffer resizing ([#13257](https://github.com/crystal-lang/crystal/pull/13257), thanks @HertzDevil) +- *(concurrency)* Clean up unused code for Windows event loop ([#13424](https://github.com/crystal-lang/crystal/pull/13424), thanks @HertzDevil) +- *(files)* Do not reopen current file in `File#utime` on Windows ([#13625](https://github.com/crystal-lang/crystal/pull/13625), thanks @HertzDevil) +- *(files)* Do not reopen current file in `File#chmod` on Windows ([#13627](https://github.com/crystal-lang/crystal/pull/13627), thanks @HertzDevil) +- *(llvm)* **[deprecation]** Deprecate `LLVM::Module#write_bitcode_with_summary_to_file` ([#13488](https://github.com/crystal-lang/crystal/pull/13488), thanks @HertzDevil) +- *(llvm)* **[deprecation]** Deprecate LLVM's legacy pass manager ([#13579](https://github.com/crystal-lang/crystal/pull/13579), thanks @HertzDevil) +- *(llvm)* Remove two outdated LLVM fun bindings ([#13438](https://github.com/crystal-lang/crystal/pull/13438), thanks @HertzDevil) +- *(llvm)* Split `LLVM::Builder` overloads that don't take an operand bundle ([#13564](https://github.com/crystal-lang/crystal/pull/13564), thanks @HertzDevil) +- *(networking)* Move more `Socket` methods to `Crystal::System::Socket` ([#13346](https://github.com/crystal-lang/crystal/pull/13346), thanks @HertzDevil) +- *(numeric)* Use `Int#bit_length` instead of `Math.log2` followed by `#to_i` ([#13440](https://github.com/crystal-lang/crystal/pull/13440), thanks @HertzDevil) +- *(numeric)* Use GMP's functions for `Float`-to-`BigRational` conversion ([#13352](https://github.com/crystal-lang/crystal/pull/13352), thanks @HertzDevil) +- *(serialization)* Simplify implementation of `Serializable#initialize` ([#13433](https://github.com/crystal-lang/crystal/pull/13433), thanks @straight-shoota) +- *(serialization)* Use per-thread libxml2 global state on Windows ([#13486](https://github.com/crystal-lang/crystal/pull/13486), thanks @HertzDevil) +- *(text)* Refactor String header layout reflection ([#13335](https://github.com/crystal-lang/crystal/pull/13335), thanks @straight-shoota) +- *(text)* Refactor symbol quoting into `Symbol.quote_for_named_argument` ([#13595](https://github.com/crystal-lang/crystal/pull/13595), thanks @straight-shoota) + +#### compiler + +- *(parser)* Crystal lexer cleanup ([#13270](https://github.com/crystal-lang/crystal/pull/13270), thanks @FnControlOption) +- *(parser)* Don't use symbols in `Crystal::Lexer#check_macro_opening_keyword` ([#13277](https://github.com/crystal-lang/crystal/pull/13277), thanks @HertzDevil) + +### Documentation + +#### stdlib + +- *(concurrency)* Fix operators in `Atomic#add`, `#sub`, `#max`, `#min` ([#13523](https://github.com/crystal-lang/crystal/pull/13523), thanks @HertzDevil) +- *(concurrency)* Hide `Crystal::LibEvent` from public docs ([#13624](https://github.com/crystal-lang/crystal/pull/13624), thanks @HertzDevil) +- *(macros)* Fix doc for return type of `Crystal::Macros::Case#else` ([#13385](https://github.com/crystal-lang/crystal/pull/13385), thanks @HertzDevil) +- *(system)* Reference `Process.executable_path` at `PROGRAM_NAME` ([#13434](https://github.com/crystal-lang/crystal/pull/13434), thanks @straight-shoota) +- *(text)* Add note about graphemes in `String#reverse` ([#13536](https://github.com/crystal-lang/crystal/pull/13536), thanks @noraj) + +#### compiler + +- Add manual entry for `clear_cache` command ([#13621](https://github.com/crystal-lang/crystal/pull/13621), thanks @straight-shoota) + +#### other + +- Implemented ',' command in brainfuck sample program ([#13559](https://github.com/crystal-lang/crystal/pull/13559), thanks @ZeroPlayerRodent) + +### Specs + +#### stdlib + +- *(files)* Fix `IO::FileDescriptor`'s `STDIN` mode spec ([#13365](https://github.com/crystal-lang/crystal/pull/13365), thanks @HertzDevil) +- *(files)* Fix file permission specs on Windows ([#13465](https://github.com/crystal-lang/crystal/pull/13465), thanks @HertzDevil) +- *(files)* Add `slow` tag to stdlib specs that compile a program ([#13498](https://github.com/crystal-lang/crystal/pull/13498), thanks @straight-shoota) +- *(serialization)* Refactor JSON, YAML specs for #13337 for simplicity ([#13358](https://github.com/crystal-lang/crystal/pull/13358), thanks @straight-shoota) +- *(system)* Disable `Process.pgid` spec on Windows ([#13476](https://github.com/crystal-lang/crystal/pull/13476), thanks @HertzDevil) +- *(text)* Reorder and enhance specs for `String.new(&)` ([#13333](https://github.com/crystal-lang/crystal/pull/13333), thanks @straight-shoota) +- *(text)* Remove incorrect `CRYSTAL` in comments ([#13500](https://github.com/crystal-lang/crystal/pull/13500), thanks @HertzDevil) +- *(time)* Skip `Time::Location.load_local` spec if unable to change time zone ([#13355](https://github.com/crystal-lang/crystal/pull/13355), thanks @HertzDevil) + +#### compiler + +- *(interpreter)* Regenerate `spec/interpreter_std_spec.cr` ([#13310](https://github.com/crystal-lang/crystal/pull/13310), thanks @cyangle) + +### Infrastructure + +- Update changelog with previous Crystal releases ([#13322](https://github.com/crystal-lang/crystal/pull/13322), [#13373](https://github.com/crystal-lang/crystal/pull/13373), [#13450](https://github.com/crystal-lang/crystal/pull/13450), thanks @straight-shoota) +- Merge `release/1.8` ([#13361](https://github.com/crystal-lang/crystal/pull/13361), [#13449](https://github.com/crystal-lang/crystal/pull/13449), thanks @straight-shoota) +- PR template: adding a line about force-pushes ([#12794](https://github.com/crystal-lang/crystal/pull/12794), thanks @beta-ziliani) +- Less verbose output in `Makefile.win` ([#13383](https://github.com/crystal-lang/crystal/pull/13383), thanks @HertzDevil) +- Update distribution-scripts ([#13457](https://github.com/crystal-lang/crystal/pull/13457), thanks @Blacksmoke16) +- Add `.gitattributes` to repository ([#13479](https://github.com/crystal-lang/crystal/pull/13479), thanks @HertzDevil) +- Update `shell.nix` to nixpkgs-23.05 ([#13571](https://github.com/crystal-lang/crystal/pull/13571), thanks @HertzDevil) +- Document `target` variable in Makefiles ([#13384](https://github.com/crystal-lang/crystal/pull/13384), thanks @HertzDevil) +- Fix `bin\crystal.ps1` writing to standard error stream ([#13372](https://github.com/crystal-lang/crystal/pull/13372), thanks @HertzDevil) +- Avoid calling realpath of parent crystal in wrapper script ([#13596](https://github.com/crystal-lang/crystal/pull/13596), thanks @straight-shoota) +- *(ci)* Show PCRE/PCRE2 configuration on CI ([#13307](https://github.com/crystal-lang/crystal/pull/13307), thanks @HertzDevil) +- *(ci)* Update cachix/install-nix-action action ([#13531](https://github.com/crystal-lang/crystal/pull/13531), [#13586](https://github.com/crystal-lang/crystal/pull/13586), thanks @renovate) +- *(ci)* Restrict Windows CI jobs for installer packages to release branches ([#13623](https://github.com/crystal-lang/crystal/pull/13623), thanks @HertzDevil) +- *(ci)* Build samples on Windows CI ([#13334](https://github.com/crystal-lang/crystal/pull/13334), thanks @HertzDevil) +- *(ci)* Do not cancel in progress CI jobs for master branch ([#13393](https://github.com/crystal-lang/crystal/pull/13393), thanks @Blacksmoke16) +- *(ci)* Upgrade Windows CI to LLVM 16 ([#13469](https://github.com/crystal-lang/crystal/pull/13469), thanks @HertzDevil) +- *(ci)* Distribute DLLs and import libraries on Windows ([#13543](https://github.com/crystal-lang/crystal/pull/13543), thanks @HertzDevil) +- *(ci)* Build Windows portable and installer packages on CI ([#13578](https://github.com/crystal-lang/crystal/pull/13578), thanks @HertzDevil) +- *(ci)* Split Windows library build scripts from CI ([#13478](https://github.com/crystal-lang/crystal/pull/13478), thanks @HertzDevil) + # 1.8.2 (2023-05-08) ## Standard Library diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 000000000000..ec30083c7e18 --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,19 @@ +# Upgrading + +This guide provides instructions for upgrading Crystal from one release to the next. +Upgrades must be run sequentially, meaning you should not skip minor/major releases while upgrading. + +Crystal commits to a backwards-compatibility guarantee that code should continue +to work with all future minor releases of the same major release series. + +Still, even bug fixes introduce changes that may break existing code in some edge cases. +We're only listing the most relevant changes here that could have a relevant impact. + +The [changelog](./CHANGELOG.md) contains more information about all changes in +a specific release. + +## Crystal 1.9 + +* The implementation of the comparison operator `#<=>` between `Big*` (`BigDecimal`, + `BigFloat`, `BigInt`, `BigRational`) and `Float` (`Float32`, `Float64`) number types + is now nilable. When invoking these comparisons, `Nil` values must be handled. diff --git a/src/VERSION b/src/VERSION index b57588e592f8..f8e233b27332 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.9.0-dev +1.9.0 From 9865d2197d1f44d545f9b6178f8c30a2230a8039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 14 Jul 2023 09:33:01 +0200 Subject: [PATCH 0624/1551] Fix `Serializable` with converter parsing `null` value (#13656) --- spec/std/json/serializable_spec.cr | 24 ++++++++++++++++++++++++ spec/std/yaml/serializable_spec.cr | 24 ++++++++++++++++++++++++ src/json/serialization.cr | 10 ++++++++-- src/yaml/serialization.cr | 10 ++++++++-- 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index 0cc9d662cc93..042e42ff5fd5 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -212,6 +212,20 @@ class JSONAttrWithTimeEpoch property value : Time end +class JSONAttrNilableWithTimeEpoch + include JSON::Serializable + + @[JSON::Field(converter: Time::EpochConverter)] + property value : Time? +end + +class JSONAttrDefaultWithTimeEpoch + include JSON::Serializable + + @[JSON::Field(converter: Time::EpochConverter)] + property value : Time = Time.unix(0) +end + class JSONAttrWithTimeEpochMillis include JSON::Serializable @@ -791,6 +805,16 @@ describe "JSON mapping" do end end + it "converter with null value (#13655)" do + JSONAttrNilableWithTimeEpoch.from_json(%({"value": null})).value.should be_nil + JSONAttrNilableWithTimeEpoch.from_json(%({"value":1459859781})).value.should eq Time.unix(1459859781) + end + + it "converter with default value" do + JSONAttrDefaultWithTimeEpoch.from_json(%({"value": null})).value.should eq Time.unix(0) + JSONAttrDefaultWithTimeEpoch.from_json(%({"value":1459859781})).value.should eq Time.unix(1459859781) + end + it "uses Time::EpochConverter" do string = %({"value":1459859781}) json = JSONAttrWithTimeEpoch.from_json(string) diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 7a098283e808..a8a7e3558b90 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -221,6 +221,20 @@ class YAMLAttrWithTimeEpoch property value : Time end +class YAMLAttrNilableWithTimeEpoch + include YAML::Serializable + + @[YAML::Field(converter: Time::EpochConverter)] + property value : Time? +end + +class YAMLAttrDefaultWithTimeEpoch + include YAML::Serializable + + @[YAML::Field(converter: Time::EpochConverter)] + property value : Time = Time.unix(0) +end + class YAMLAttrWithTimeEpochMillis include YAML::Serializable @@ -871,6 +885,16 @@ describe "YAML::Serializable" do end end + it "converter with null value (#13655)" do + YAMLAttrNilableWithTimeEpoch.from_yaml(%({"value": null})).value.should be_nil + YAMLAttrNilableWithTimeEpoch.from_yaml(%({"value":1459859781})).value.should eq Time.unix(1459859781) + end + + it "converter with default value" do + YAMLAttrDefaultWithTimeEpoch.from_yaml(%({"value": null})).value.should eq Time.unix(0) + YAMLAttrDefaultWithTimeEpoch.from_yaml(%({"value":1459859781})).value.should eq Time.unix(1459859781) + end + it "uses Time::EpochConverter" do string = %({"value":1459859781}) yaml = YAMLAttrWithTimeEpoch.from_yaml(string) diff --git a/src/json/serialization.cr b/src/json/serialization.cr index 386be1191887..610979517a18 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -219,8 +219,14 @@ module JSON {% for name, value in properties %} when {{value[:key]}} begin - {% if (value[:has_default] && !value[:nilable]) || value[:root] %} - next if pull.read_null? + {% if value[:has_default] || value[:nilable] || value[:root] %} + if pull.read_null? + {% if value[:nilable] %} + %var{name} = nil + %found{name} = true + {% end %} + next + end {% end %} %var{name} = diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index 30e9fa6640d2..d5fae8dfe9c0 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -224,8 +224,14 @@ module YAML {% for name, value in properties %} when {{value[:key]}} begin - {% if value[:has_default] && !value[:nilable] %} - next if YAML::Schema::Core.parse_null?(value_node) + {% if value[:has_default] || value[:nilable] %} + if YAML::Schema::Core.parse_null?(value_node) + {% if value[:nilable] %} + %var{name} = nil + %found{name} = true + {% end %} + next + end {% end %} %var{name} = From 3a02b56c95bf6921dba8e138ef37b79c8e12eeaa Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Fri, 14 Jul 2023 19:34:11 +0200 Subject: [PATCH 0625/1551] Fix generated cc command for cross compile (#13661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/compiler/crystal/compiler.cr | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index f8e02b799859..f32ecd344be1 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -323,8 +323,9 @@ module Crystal target_machine.emit_obj_to_file llvm_mod, output_filename end - - _, command, args = linker_command(program, [output_filename], output_filename, nil) + object_names = [output_filename] + output_filename = output_filename.rchop(unit.object_extension) + _, command, args = linker_command(program, object_names, output_filename, nil) print_command(command, args) end @@ -684,7 +685,7 @@ module Crystal getter original_name getter llvm_mod getter? reused_previous_compilation = false - @object_extension : String + getter object_extension : String def initialize(@compiler : Compiler, program : Program, @name : String, @llvm_mod : LLVM::Module, @output_dir : String, @bc_flags_changed : Bool) From c355a34e5512807b5dd72d4ad6b1bbc7082526d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 17 Jul 2023 16:28:39 +0200 Subject: [PATCH 0626/1551] Add changelog for 1.9.1 (#13669) --- CHANGELOG.md | 12 ++++++++++++ src/VERSION | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf7beaebc581..6fbb743865e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 1.9.1 (2023-07-17) + +### Bugfixes + +#### stdlib + +- *(serialization)* Fix `Serializable` with converter parsing `null` value ([#13656](https://github.com/crystal-lang/crystal/pull/13656), thanks @straight-shoota) + +#### compiler + +- *(codegen)* Fix generated cc command for cross compile ([#13661](https://github.com/crystal-lang/crystal/pull/13661), thanks @fnordfish) + # 1.9.0 (2023-07-11) ### Breaking changes diff --git a/src/VERSION b/src/VERSION index f8e233b27332..9ab8337f3962 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.9.0 +1.9.1 From c6d14d1c6f41a73e5d9e9c438ba8d03b5a77a3fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 18 Jul 2023 23:12:56 +0200 Subject: [PATCH 0627/1551] Revert "Add default interrupt handlers" (#13673) --- spec/std/kernel_spec.cr | 61 ----------------------------- src/crystal/system/process.cr | 3 -- src/crystal/system/unix/process.cr | 6 --- src/crystal/system/win32/process.cr | 7 ---- src/kernel.cr | 1 - src/process.cr | 20 ++-------- 6 files changed, 4 insertions(+), 94 deletions(-) diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index efe5e81563e4..149e6385ac97 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -294,64 +294,3 @@ describe "hardware exception" do error.should contain("Stack overflow") end end - -private def compile_and_run_exit_handler(&block : Process -> _) - with_tempfile("source_file") do |source_file| - File.write(source_file, <<-CRYSTAL) - at_exit { print "Exiting" } - print "." - STDOUT.flush - sleep - CRYSTAL - output = nil - compile_file(source_file) do |executable_file| - error = IO::Memory.new - process = Process.new executable_file, output: :pipe, error: error - - spawn do - process.output.read_byte - block.call(process) - output = process.output.gets_to_end - end - - status = process.wait - {status, output, error.to_s} - end - end -end - -describe "default interrupt handlers", tags: %w[slow] do - # TODO: Implementation for sending console control commands on Windows. - # So long this behaviour can only be tested manually. - # - # ``` - # lib LibC - # fun GenerateConsoleCtrlEvent(dwCtrlEvent : DWORD, dwProcessGroupId : DWORD) : BOOL - # end - - # at_exit { print "Exiting"; } - # print "." - # STDOUT.flush - # LibC.GenerateConsoleCtrlEvent(LibC::CTRL_C_EVENT, 0) - # sleep - # ``` - {% unless flag?(:windows) %} - it "handler for SIGINT" do - status, output, _ = compile_and_run_exit_handler(&.signal(Signal::INT)) - output.should eq "Exiting" - status.inspect.should eq "Process::Status[130]" - end - - it "handler for SIGTERM" do - status, output, _ = compile_and_run_exit_handler(&.terminate) - output.should eq "Exiting" - status.inspect.should eq "Process::Status[143]" - end - {% end %} - - it "no handler for SIGKILL" do - status, output, _ = compile_and_run_exit_handler(&.terminate(graceful: false)) - output.should eq "" - status.inspect.should eq {{ flag?(:unix) ? "Process::Status[Signal::KILL]" : "Process::Status[1]" }} - end -end diff --git a/src/crystal/system/process.cr b/src/crystal/system/process.cr index 8a5598fc7b16..387447c083c2 100644 --- a/src/crystal/system/process.cr +++ b/src/crystal/system/process.cr @@ -56,9 +56,6 @@ struct Crystal::System::Process # thread. # def self.start_interrupt_loop - # Trap interrupt to exit normally with `at_exit` handlers being executed. - # def self.setup_default_interrupt_handler - # Whether the process identified by *pid* is still registered in the system. # def self.exists?(pid : Int) : Bool diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 9212226d9aa4..f07a91806857 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -74,12 +74,6 @@ struct Crystal::System::Process # do nothing; `Crystal::System::Signal.start_loop` takes care of this end - def self.setup_default_interrupt_handlers - # Status 128 + signal number indicates process exit was caused by the signal. - ::Signal::INT.trap { ::exit 128 + ::Signal::INT.value } - ::Signal::TERM.trap { ::exit 128 + ::Signal::TERM.value } - end - def self.exists?(pid) ret = LibC.kill(pid, 0) if ret == 0 diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index e39db2f46fe4..36878bc935bf 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -149,13 +149,6 @@ struct Crystal::System::Process end end - def self.setup_default_interrupt_handlers - on_interrupt do - # Exit code 3 indicates `std::abort` was called which corresponds to the interrupt handler - ::exit 3 - end - end - def self.exists?(pid) handle = LibC.OpenProcess(LibC::PROCESS_QUERY_INFORMATION, 0, pid) return false unless handle diff --git a/src/kernel.cr b/src/kernel.cr index 5291a4dc57f1..7bca29cef605 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -569,7 +569,6 @@ end {% else %} Crystal::System::Signal.setup_default_handlers {% end %} - Crystal::System::Process.setup_default_interrupt_handlers # load debug info on start up of the program is executed with CRYSTAL_LOAD_DEBUG_INFO=1 # this will make debug info available on print_frame that is used by Crystal's segfault handler diff --git a/src/process.cr b/src/process.cr index 0eea5263b223..159b08b39eaa 100644 --- a/src/process.cr +++ b/src/process.cr @@ -58,10 +58,6 @@ class Process # * On Unix-like systems, this traps `SIGINT`. # * On Windows, this captures Ctrl + C and # Ctrl + Break signals sent to a console application. - # - # The default interrupt handler calls `::exit` to ensure `at_exit` handlers - # execute. It returns a platform-specific status code indicating an interrupt - # (`130` on Unix, `3` on Windows). def self.on_interrupt(&handler : ->) : Nil Crystal::System::Process.on_interrupt(&handler) end @@ -76,14 +72,8 @@ class Process end # Restores default handling of interrupt requests. - # - # The default interrupt handler calls `::exit` to ensure `at_exit` handlers - # execute. It returns a platform-specific status code indicating an interrupt - # (`130` on Unix, `3` on Windows). def self.restore_interrupts! : Nil Crystal::System::Process.restore_interrupts! - - Crystal::System::Process.setup_default_interrupt_handlers end # Returns `true` if the process identified by *pid* is valid for @@ -311,12 +301,10 @@ class Process end end - {% if flag?(:unix) %} - # :nodoc: - def initialize(pid : LibC::PidT) - @process_info = Crystal::System::Process.new(pid) - end - {% end %} + # :nodoc: + def initialize(pid : LibC::PidT) + @process_info = Crystal::System::Process.new(pid) + end # Sends *signal* to this process. # From 1908c816faf4580fddef226a8272cf8e4ffd4c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 19 Jul 2023 18:33:31 +0200 Subject: [PATCH 0628/1551] Add changelog for 1.9.2 (#13682) --- CHANGELOG.md | 10 +++++++++- src/VERSION | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fbb743865e6..de572ab822a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 1.9.2 (2023-07-19) + +### Bugfixes + +#### stdlib + +- *(runtime)* Revert "Add default interrupt handlers" ([#13673](https://github.com/crystal-lang/crystal/pull/13673), thanks @straight-shoota) + # 1.9.1 (2023-07-17) ### Bugfixes @@ -43,7 +51,7 @@ - *(numeric)* Add `BigDecimal#%` ([#13255](https://github.com/crystal-lang/crystal/pull/13255), thanks @MattAlp) - *(numeric)* Improve conversions from `BigInt` to `Int::Primitive` ([#13562](https://github.com/crystal-lang/crystal/pull/13562), thanks @HertzDevil) - *(runtime)* Print error if unable to delay-load DLL on Windows ([#13475](https://github.com/crystal-lang/crystal/pull/13475), thanks @HertzDevil) -- *(runtime)* Add default interrupt handlers ([#13568](https://github.com/crystal-lang/crystal/pull/13568), thanks @straight-shoota) +- *(runtime)* Add default interrupt handlers ([#13568](https://github.com/crystal-lang/crystal/pull/13568), thanks @straight-shoota) ⚠️ This was reverted in 1.9.2 - *(serialization)* Add `ignore_serialize` for `YAML::Serializable` ([#13556](https://github.com/crystal-lang/crystal/pull/13556), thanks @meatball133) - *(specs)* Add a testcase line number to the output of JUnitFormatter ([#13468](https://github.com/crystal-lang/crystal/pull/13468), thanks @nobodywasishere) - *(specs)* Publish the `assert_prints` spec helper ([#13599](https://github.com/crystal-lang/crystal/pull/13599), thanks @HertzDevil) diff --git a/src/VERSION b/src/VERSION index 9ab8337f3962..8fdcf3869464 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.9.1 +1.9.2 From 84f389ac5424e581da68032fb53c5eb05393d0ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 21 Jul 2023 14:36:29 +0200 Subject: [PATCH 0629/1551] Update previous Crystal release - 1.9.2 (#13650) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 6 +++--- .github/workflows/linux.yml | 2 ++ .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win_build_portable.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ src/VERSION | 2 +- 11 files changed, 24 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a684ee6fa308..769bd58805d6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.8.2/crystal-1.8.2-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.9.2/crystal-1.9.2-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 203cdb8933ed..8bc156bf5d7f 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.8.2-build + image: crystallang/crystal:1.9.2-build name: "Test Interpreter" steps: - uses: actions/checkout@v3 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.8.2-build + image: crystallang/crystal:1.9.2-build name: Build interpreter steps: - uses: actions/checkout@v3 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.8.2-build + image: crystallang/crystal:1.9.2-build strategy: matrix: part: [0, 1, 2, 3] diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 5b28a02be318..815b2a7984a4 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -31,6 +31,8 @@ jobs: flags: "" - crystal_bootstrap_version: 1.8.2 flags: "" + - crystal_bootstrap_version: 1.9.2 + flags: "" steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 061dd8f4f582..fe4b0f90875f 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -54,7 +54,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.8.2" + crystal: "1.9.2" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index b8f637008ff7..f087d357ff98 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.8.2-alpine + container: crystallang/crystal:1.9.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -27,7 +27,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.8.2-alpine + container: crystallang/crystal:1.9.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -42,7 +42,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.8.2-alpine + container: crystallang/crystal:1.9.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index c8ba20e26db7..6ca07c89cc48 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.8.2-alpine + container: crystallang/crystal:1.9.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.8.2-alpine + container: crystallang/crystal:1.9.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 40a792a9b0a1..d05980b87945 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.8.2-build + container: crystallang/crystal:1.9.2-build steps: - name: Download Crystal source uses: actions/checkout@v3 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 408133e95a4c..fcfafeb5b6a3 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -21,7 +21,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.8.2" + crystal: "1.9.2" - name: Download Crystal source uses: actions/checkout@v3 diff --git a/bin/ci b/bin/ci index 033331527759..008965ccded0 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.8.2/crystal-1.8.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.8.2-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.9.2/crystal-1.9.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.9.2-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.8.2}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.9.2}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 6ead65a185a0..453894568061 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.8.2/crystal-1.8.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:06hj2lcin4sdlmdb42asg677860c9ryca6wm5s6mps2fshbmq933"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.9.2/crystal-1.9.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:0ngiflk7yxb6ry5ax1zrbm3rh4psq7flv7xj6ph4g8qqx74qv79m"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.8.2/crystal-1.8.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:06hj2lcin4sdlmdb42asg677860c9ryca6wm5s6mps2fshbmq933"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.9.2/crystal-1.9.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:0ngiflk7yxb6ry5ax1zrbm3rh4psq7flv7xj6ph4g8qqx74qv79m"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.8.2/crystal-1.8.2-1-linux-x86_64.tar.gz"; - sha256 = "sha256:16pkvr28n5ba4440apmnflx7i09hap37vv51x763aym34yq1a0xd"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.9.2/crystal-1.9.2-1-linux-x86_64.tar.gz"; + sha256 = "sha256:1d4wmr49m3ykylh4zwp184mm98vj0cqmflhgnmgry8nkwhkvs900"; }; }.${pkgs.stdenv.system}); diff --git a/src/VERSION b/src/VERSION index 8fdcf3869464..a01185b4d67a 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.9.2 +1.10.0-dev From d7a7df31e080a1fb24d5280d9aad4dca9ff8f39e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 21 Jul 2023 20:38:27 +0800 Subject: [PATCH 0630/1551] Use `Set(T)` instead of `Hash(T, Bool)` (#13611) --- src/array.cr | 53 ++++++++++++++++-------------------------------- src/iterator.cr | 8 ++------ src/reference.cr | 11 +++++----- 3 files changed, 24 insertions(+), 48 deletions(-) diff --git a/src/array.cr b/src/array.cr index f98b11f450c0..df31a2699251 100644 --- a/src/array.cr +++ b/src/array.cr @@ -256,7 +256,7 @@ class Array(T) return Array(T).new if self.empty? || other.empty? # Heuristic: for small arrays we do a linear scan, which is usually - # faster than creating an intermediate Hash. + # faster than creating an intermediate Set. if self.size + other.size <= SMALL_ARRAY_SIZE * 2 ary = Array(T).new each do |elem| @@ -265,20 +265,13 @@ class Array(T) return ary end - hash = other.to_lookup_hash - hash_size = hash.size + set = other.to_set Array(T).build(Math.min(size, other.size)) do |buffer| - i = 0 + appender = buffer.appender each do |obj| - hash.delete(obj) - new_hash_size = hash.size - if hash_size != new_hash_size - hash_size = new_hash_size - buffer[i] = obj - i += 1 - end + appender << obj if set.delete(obj) end - i + appender.size.to_i end end @@ -292,7 +285,7 @@ class Array(T) # See also: `#uniq`. def |(other : Array(U)) : Array(T | U) forall U # Heuristic: if the combined size is small we just do a linear scan - # instead of using a Hash for lookup. + # instead of using a Set for lookup. if size + other.size <= SMALL_ARRAY_SIZE ary = Array(T | U).new each do |elem| @@ -305,23 +298,15 @@ class Array(T) end Array(T | U).build(size + other.size) do |buffer| - hash = Hash(T, Bool).new - i = 0 + set = Set(T).new + appender = buffer.appender each do |obj| - unless hash.has_key?(obj) - buffer[i] = obj - hash[obj] = true - i += 1 - end + appender << obj if set.add?(obj) end other.each do |obj| - unless hash.has_key?(obj) - buffer[i] = obj - hash[obj] = true - i += 1 - end + appender << obj if set.add?(obj) end - i + appender.size.to_i end end @@ -356,7 +341,7 @@ class Array(T) # ``` def -(other : Array(U)) : Array(T) forall U # Heuristic: if any of the arrays is small we just do a linear scan - # instead of using a Hash for lookup. + # instead of using a Set for lookup. if size <= SMALL_ARRAY_SIZE || other.size <= SMALL_ARRAY_SIZE ary = Array(T).new each do |elem| @@ -366,9 +351,9 @@ class Array(T) end ary = Array(T).new(Math.max(size - other.size, 0)) - hash = other.to_lookup_hash + set = other.to_set each do |obj| - ary << obj unless hash.has_key?(obj) + ary << obj unless set.includes?(obj) end ary end @@ -1858,7 +1843,7 @@ class Array(T) end # Heuristic: for a small array it's faster to do a linear scan - # than creating a Hash to find out duplicates. + # than creating a Set to find out duplicates. if size <= SMALL_ARRAY_SIZE ary = Array(T).new each do |elem| @@ -1867,8 +1852,8 @@ class Array(T) return ary end - # Convert the Array into a Hash and then ask for its values - to_lookup_hash.values + # Convert the Array into a Set and then ask for its values + to_set.to_a end # Returns a new `Array` by removing duplicate values in `self`, using the block's @@ -2176,10 +2161,6 @@ class Array(T) Slice.new(@buffer + start, count) end - protected def to_lookup_hash - to_lookup_hash { |elem| elem } - end - protected def to_lookup_hash(& : T -> U) forall U each_with_object(Hash(U, T).new) do |o, h| key = yield o diff --git a/src/iterator.cr b/src/iterator.cr index d4d9eccc803a..2fa5eecee1c5 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -1283,18 +1283,14 @@ module Iterator(T) include IteratorWrapper def initialize(@iterator : I, @func : T -> U) - @hash = {} of U => Bool + @set = Set(U).new end def next while true value = wrapped_next transformed = @func.call value - - unless @hash[transformed]? - @hash[transformed] = true - return value - end + return value if @set.add?(transformed) end end end diff --git a/src/reference.cr b/src/reference.cr index a9dee7109059..9a032500a6e5 100644 --- a/src/reference.cr +++ b/src/reference.cr @@ -139,7 +139,8 @@ class Reference # :nodoc: module ExecRecursive - alias Registry = Hash({UInt64, Symbol}, Bool) + # NOTE: can't use `Set` here because of prelude require order + alias Registry = Hash({UInt64, Symbol}, Nil) {% if flag?(:preview_mt) %} @@exec_recursive = Crystal::ThreadLocalValue(Registry).new @@ -159,14 +160,12 @@ class Reference private def exec_recursive(method, &) hash = ExecRecursive.hash key = {object_id, method} - if hash[key]? - false - else - hash[key] = true + hash.put(key, nil) do yield hash.delete(key) - true + return true end + false end # :nodoc: From 2c9b55235f0e03a095e6b7fe8442d5f0f9626ad3 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 21 Jul 2023 20:38:51 +0800 Subject: [PATCH 0631/1551] Restrict some boolean properties to `Bool` in the compiler (#13614) --- src/compiler/crystal/codegen/codegen.cr | 3 +-- src/compiler/crystal/exception.cr | 5 +---- src/compiler/crystal/semantic/exception.cr | 12 ++++++------ src/compiler/crystal/syntax/exception.cr | 4 ---- src/compiler/crystal/syntax/parser.cr | 7 +++---- 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index e036efbf48e8..2c357183ffa5 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -176,8 +176,7 @@ module Crystal @c_malloc_fun : LLVMTypedFunction? @c_realloc_fun : LLVMTypedFunction? - def initialize(@program : Program, @node : ASTNode, single_module = false, @debug = Debug::Default) - @single_module = !!single_module + def initialize(@program : Program, @node : ASTNode, @single_module : Bool = false, @debug = Debug::Default) @abi = @program.target_machine.abi @llvm_context = LLVM::Context.new # LLVM::Context.register(@llvm_context, "main") diff --git a/src/compiler/crystal/exception.cr b/src/compiler/crystal/exception.cr index c40f9a39051a..089cdc1ad2f4 100644 --- a/src/compiler/crystal/exception.cr +++ b/src/compiler/crystal/exception.cr @@ -7,6 +7,7 @@ module Crystal abstract class CodeError < Error property? color = false property? error_trace = false + property? warning = false @filename : String | VirtualFile | Nil @@ -14,10 +15,6 @@ module Crystal to_s_with_source(io, nil) end - def warning=(warning) - @warning = !!warning - end - abstract def to_s_with_source(io : IO, source) def to_json(json : JSON::Builder) diff --git a/src/compiler/crystal/semantic/exception.cr b/src/compiler/crystal/semantic/exception.cr index 10142789d281..359f12ffb882 100644 --- a/src/compiler/crystal/semantic/exception.cr +++ b/src/compiler/crystal/semantic/exception.cr @@ -11,19 +11,19 @@ module Crystal getter column_number : Int32 getter size : Int32 - def color=(color) - @color = !!color + def color=(@color : Bool) inner.try &.color=(color) + color end - def error_trace=(error_trace) - @error_trace = !!error_trace + def error_trace=(@error_trace : Bool) inner.try &.error_trace=(error_trace) + error_trace end - def warning=(warning) - super + def warning=(@warning : Bool) inner.try &.warning=(warning) + warning end def self.for_node(node, message, inner = nil) diff --git a/src/compiler/crystal/syntax/exception.cr b/src/compiler/crystal/syntax/exception.cr index 3bc3591de017..338e79d72de4 100644 --- a/src/compiler/crystal/syntax/exception.cr +++ b/src/compiler/crystal/syntax/exception.cr @@ -13,10 +13,6 @@ module Crystal super(message) end - def color=(color) - @color = !!color - end - def has_location? @filename || @line_number end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 016d3d7e3027..16ff0c48876f 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -77,9 +77,8 @@ module Crystal @assigned_vars = [] of String end - def wants_doc=(wants_doc) - @wants_doc = !!wants_doc - @doc_enabled = !!wants_doc + def wants_doc=(@wants_doc : Bool) + @doc_enabled = wants_doc end def parse @@ -3738,7 +3737,7 @@ module Crystal end @def_nest -= 1 - @doc_enabled = !!@wants_doc + @doc_enabled = @wants_doc node = Def.new name, params, body, receiver, block_param, return_type, @is_macro_def, @block_arity, is_abstract, splat_index, double_splat: double_splat, free_vars: free_vars node.name_location = name_location From c3bf074f19a48d654cb0f26a8e57e19a0f229476 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 21 Jul 2023 20:39:31 +0800 Subject: [PATCH 0632/1551] Add `Hash#put_if_absent` (#13590) --- samples/havlak.cr | 2 +- spec/std/hash_spec.cr | 29 ++++++++++++- src/csv.cr | 2 +- src/hash.cr | 99 ++++++++++++++++++++++++++++++++++++++++++- src/ini.cr | 2 +- src/mime.cr | 3 +- src/spec/cli.cr | 3 +- src/spec/source.cr | 2 +- src/uri/params.cr | 9 ++-- 9 files changed, 135 insertions(+), 16 deletions(-) diff --git a/samples/havlak.cr b/samples/havlak.cr index 5d01b3cdaf1d..0e58ea1791cc 100644 --- a/samples/havlak.cr +++ b/samples/havlak.cr @@ -51,7 +51,7 @@ class CFG property :basic_block_map def create_node(name) - node = (@basic_block_map[name] ||= BasicBlock.new(name)) + node = @basic_block_map.put_if_absent(name) { BasicBlock.new(name) } @start_node ||= node node end diff --git a/spec/std/hash_spec.cr b/spec/std/hash_spec.cr index 8bb8eca096bc..85a3ad2833f8 100644 --- a/spec/std/hash_spec.cr +++ b/spec/std/hash_spec.cr @@ -169,7 +169,7 @@ describe "Hash" do end end - describe "put" do + describe "#put" do it "puts in a small hash" do a = {} of Int32 => Int32 a.put(1, 2) { nil }.should eq(nil) @@ -191,6 +191,33 @@ describe "Hash" do end end + describe "#put_if_absent" do + it "puts if key doesn't exist" do + v = [] of String + h = {} of Int32 => Array(String) + h.put_if_absent(1, v).should be(v) + h.should eq({1 => v}) + h[1].should be(v) + end + + it "returns existing value if key exists" do + v = [] of String + h = {1 => v} + h.put_if_absent(1, [] of String).should be(v) + h.should eq({1 => v}) + h[1].should be(v) + end + + it "accepts a block" do + v = [] of String + h = {1 => v} + h.put_if_absent(1) { [] of String }.should be(v) + h.put_if_absent(2) { |key| [key.to_s] }.should eq(["2"]) + h.should eq({1 => v, 2 => ["2"]}) + h[1].should be(v) + end + end + describe "update" do it "updates the value of an existing key with the given block" do h = {"a" => 0, "b" => 1} diff --git a/src/csv.cr b/src/csv.cr index 0ad9a053dcfa..6751085d28cc 100644 --- a/src/csv.cr +++ b/src/csv.cr @@ -181,7 +181,7 @@ class CSV headers = @headers = headers.map &.strip indices = @indices = {} of String => Int32 headers.each_with_index do |header, index| - indices[header] ||= index + indices.put_if_absent(header, index) end end @traversed = false diff --git a/src/hash.cr b/src/hash.cr index 82ded213602f..38ea694cb421 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -400,6 +400,56 @@ class Hash(K, V) end end + # Inserts a key-value pair. Assumes that the given key doesn't exist. + private def insert_new(key, value) + # Unless otherwise noted, this body should be identical to `#upsert` + + if @entries.null? + @indices_size_pow2 = 3 + @entries = malloc_entries(4) + end + + hash = key_hash(key) + + if @indices.null? + # don't call `#update_linear_scan` here + + if !entries_full? + add_entry_and_increment_size(hash, key, value) + return + end + + resize + + if @indices.null? + add_entry_and_increment_size(hash, key, value) + return + end + end + + index = fit_in_indices(hash) + + while true + entry_index = get_index(index) + + if entry_index == -1 + if entries_full? + resize + index = fit_in_indices(hash) + next + end + + set_index(index, entries_size) + add_entry_and_increment_size(hash, key, value) + return + end + + # don't call `#get_entry` and `#entry_matches?` here + + index = next_index(index) + end + end + # Tries to update a key-value-hash triplet by doing a linear scan. # Returns an old `Entry` if it was updated, otherwise `nil`. private def update_linear_scan(key, value, hash) : Entry(K, V)? @@ -1003,7 +1053,7 @@ class Hash(K, V) # Sets the value of *key* to the given *value*. # - # If a value already exists for `key`, that (old) value is returned. + # If a value already exists for *key*, that (old) value is returned. # Otherwise the given block is invoked with *key* and its value is returned. # # ``` @@ -1011,12 +1061,57 @@ class Hash(K, V) # h.put(1, "one") { "didn't exist" } # => "didn't exist" # h.put(1, "uno") { "didn't exist" } # => "one" # h.put(2, "two") { |key| key.to_s } # => "2" + # h # => {1 => "one", 2 => "two"} # ``` def put(key : K, value : V, &) updated_entry = upsert(key, value) updated_entry ? updated_entry.value : yield key end + # Sets the value of *key* to the given *value*, unless a value for *key* + # already exists. + # + # If a value already exists for *key*, that (old) value is returned. + # Otherwise *value* is returned. + # + # ``` + # h = {} of Int32 => Array(String) + # h.put_if_absent(1, "one") # => "one" + # h.put_if_absent(1, "uno") # => "one" + # h.put_if_absent(2, "two") # => "two" + # h # => {1 => "one", 2 => "two"} + # ``` + def put_if_absent(key : K, value : V) : V + put_if_absent(key) { value } + end + + # Sets the value of *key* to the value returned by the given block, unless a + # value for *key* already exists. + # + # If a value already exists for *key*, that (old) value is returned. + # Otherwise the given block is invoked with *key* and its value is returned. + # + # ``` + # h = {} of Int32 => Array(String) + # h.put_if_absent(1) { |key| [key.to_s] } # => ["1"] + # h.put_if_absent(1) { [] of String } # => ["1"] + # h.put_if_absent(2) { |key| [key.to_s] } # => ["2"] + # h # => {1 => ["1"], 2 => ["2"]} + # ``` + # + # `hash.put_if_absent(key) { value }` is a more performant alternative to + # `hash[key] ||= value` that also works correctly when the hash may contain + # falsey values. + def put_if_absent(key : K, & : K -> V) : V + if entry = find_entry(key) + entry.value + else + value = yield key + insert_new(key, value) + value + end + end + # Updates the current value of *key* with the value returned by the given block # (the current value is used as input for the block). # @@ -1049,7 +1144,7 @@ class Hash(K, V) entry.value elsif block = @block default_value = block.call(self, key) - upsert(key, yield default_value) + insert_new(key, yield default_value) default_value else raise KeyError.new "Missing hash key: #{key.inspect}" diff --git a/src/ini.cr b/src/ini.cr index c8d1d50e0384..d9cea1c82e82 100644 --- a/src/ini.cr +++ b/src/ini.cr @@ -46,7 +46,7 @@ module INI raise ParseException.new("Data after section", lineno, end_idx + 1) unless end_idx == line.size - 1 current_section_name = line[offset + 1...end_idx] - current_section = ini[current_section_name] ||= Hash(String, String).new + current_section = ini.put_if_absent(current_section_name) { Hash(String, String).new } else key, eq, value = line.partition('=') raise ParseException.new("Expected declaration", lineno, key.size) if eq != "=" diff --git a/src/mime.cr b/src/mime.cr index ac653e5c9f89..7eb65f23818b 100644 --- a/src/mime.cr +++ b/src/mime.cr @@ -232,8 +232,7 @@ module MIME @@types[extension] = type @@types_lower[extension.downcase] = type - type_extensions = @@extensions[mediatype] ||= Set(String).new - type_extensions << extension + @@extensions.put_if_absent(mediatype) { Set(String).new } << extension end # Returns all extensions registered for *type*. diff --git a/src/spec/cli.cr b/src/spec/cli.cr index d495c8e38928..5e514d01a3c6 100644 --- a/src/spec/cli.cr +++ b/src/spec/cli.cr @@ -22,8 +22,7 @@ module Spec # :nodoc: def self.add_location(file, line) locations = @@locations ||= {} of String => Array(Int32) - lines = locations[File.expand_path(file)] ||= [] of Int32 - lines << line + locations.put_if_absent(File.expand_path(file)) { [] of Int32 } << line end # :nodoc: diff --git a/src/spec/source.cr b/src/spec/source.cr index fb328d502367..6db12c936ae4 100644 --- a/src/spec/source.cr +++ b/src/spec/source.cr @@ -8,7 +8,7 @@ module Spec def self.read_line(file, line) return nil unless File.file?(file) - lines = lines_cache[file] ||= File.read_lines(file) + lines = lines_cache.put_if_absent(file) { File.read_lines(file) } lines[line - 1]? end diff --git a/src/uri/params.cr b/src/uri/params.cr index 70dd0f1b373a..0e9ab181af57 100644 --- a/src/uri/params.cr +++ b/src/uri/params.cr @@ -16,8 +16,7 @@ class URI def self.parse(query : String) : self parsed = {} of String => Array(String) parse(query) do |key, value| - ary = parsed[key] ||= [] of String - ary.push value + parsed.put_if_absent(key) { [] of String } << value end Params.new(parsed) end @@ -297,9 +296,9 @@ class URI # params.fetch_all("item") # => ["pencil", "book", "workbook", "keychain"] # ``` def add(name, value) - raw_params[name] ||= [] of String - raw_params[name] = [] of String if raw_params[name] == [""] - raw_params[name] << value + params = raw_params.put_if_absent(name) { [] of String } + params.clear if params.size == 1 && params[0] == "" + params << value end # Sets all *values* for specified param *name* at once. From a8876014710bd70bf1ca7639bbce12beb00a0002 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 21 Jul 2023 22:43:27 +0800 Subject: [PATCH 0633/1551] Add `Set#rehash` (#13630) --- spec/std/set_spec.cr | 14 ++++++++++++++ src/hash.cr | 8 ++++---- src/set.cr | 9 +++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/spec/std/set_spec.cr b/spec/std/set_spec.cr index 7afdcdbd8867..9c8a5330c573 100644 --- a/spec/std/set_spec.cr +++ b/spec/std/set_spec.cr @@ -421,4 +421,18 @@ describe "Set" do set.clone.compare_by_identity?.should be_true end end + + describe "#rehash" do + it "rehashes" do + a = [1] + s = Set{a} + (10..100).each do |i| + s << [i] + end + a << 2 + s.should_not contain(a) + s.rehash + s.should contain(a) + end + end end diff --git a/src/hash.cr b/src/hash.cr index 38ea694cb421..10a1eb4db23b 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -2071,11 +2071,11 @@ class Hash(K, V) self end - # Rebuilds the hash table based on the current value of each key. + # Rebuilds the hash table based on the current keys. # - # When using mutable data types as keys, changing the value of a key after - # it was inserted into the `Hash` may lead to undefined behaviour. - # This method re-indexes the hash using the current key values. + # When using mutable data types as keys, modifying a key after it was inserted + # into the `Hash` may lead to undefined behaviour. This method re-indexes the + # hash using the current keys. def rehash : Nil do_compaction(rehash: true) end diff --git a/src/set.cr b/src/set.cr index 2d70a8bde059..ca3addf72c9a 100644 --- a/src/set.cr +++ b/src/set.cr @@ -479,6 +479,15 @@ struct Set(T) def same?(other : Set) : Bool @hash.same?(other.@hash) end + + # Rebuilds the set based on the current elements. + # + # When using mutable data types as elements, modifying an elements after it + # was inserted into the `Set` may lead to undefined behaviour. This method + # re-indexes the set using the current elements. + def rehash : Nil + @hash.rehash + end end module Enumerable From 9801815749bac61e9dd44a246cc379775d7f356b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20H=C3=B6rberg?= Date: Sat, 22 Jul 2023 15:19:17 +0200 Subject: [PATCH 0634/1551] Use `IO::DEFAULT_BUFFER_SIZE` in `Digest#update` (#13635) --- src/digest/digest.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/digest/digest.cr b/src/digest/digest.cr index 765f940ca47b..e6a401e90545 100644 --- a/src/digest/digest.cr +++ b/src/digest/digest.cr @@ -219,7 +219,7 @@ abstract class Digest # Reads the io's data and updates the digest with it. def update(io : IO) : self - buffer = uninitialized UInt8[4096] + buffer = uninitialized UInt8[IO::DEFAULT_BUFFER_SIZE] while (read_bytes = io.read(buffer.to_slice)) > 0 self << buffer.to_slice[0, read_bytes] end From 55eefa45fc8436a338d125ddf9ba0d1aa9e04b45 Mon Sep 17 00:00:00 2001 From: Philip Ross Date: Sat, 22 Jul 2023 06:19:38 -0700 Subject: [PATCH 0635/1551] Add yield `key` in `Hash#transform_values` and `value` in `#transform_keys` (#13608) --- spec/std/hash_spec.cr | 21 +++++++++++++++++++++ src/hash.cr | 23 +++++++++++++++-------- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/spec/std/hash_spec.cr b/spec/std/hash_spec.cr index 85a3ad2833f8..66f9c7a9adee 100644 --- a/spec/std/hash_spec.cr +++ b/spec/std/hash_spec.cr @@ -882,6 +882,13 @@ describe "Hash" do h2.should be_empty end + it "transforms keys with values included" do + h1 = {1 => "a", 2 => "b", 3 => "c"} + + h2 = h1.transform_keys { |k, v| "#{k}#{v}" } + h2.should eq({"1a" => "a", "2b" => "b", "3c" => "c"}) + end + it "transforms values" do h1 = {"a" => 1, "b" => 2, "c" => 3} @@ -905,6 +912,13 @@ describe "Hash" do h2.should be_empty end + it "transforms values with keys included" do + h1 = {"a" => 1, "b" => 2, "c" => 3} + + h2 = h1.transform_values { |v, k| "#{k}#{v}" } + h2.should eq({"a" => "a1", "b" => "b2", "c" => "c3"}) + end + it "transform values in place" do h = {"a" => 1, "b" => 2, "c" => 3} @@ -912,6 +926,13 @@ describe "Hash" do h.should eq({"a" => 2, "b" => 3, "c" => 4}) end + it "transform values in place with keys included" do + h = {"a" => "1", "b" => "2", "c" => "3"} + + h.transform_values! { |v, k| "#{k}#{v}" } + h.should eq({"a" => "a1", "b" => "b2", "c" => "c3"}) + end + it "zips" do ary1 = [1, 2, 3] ary2 = ['a', 'b', 'c'] diff --git a/src/hash.cr b/src/hash.cr index 10a1eb4db23b..05ea0ca3b1de 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -1706,42 +1706,49 @@ class Hash(K, V) # Returns a new hash with all keys converted using the block operation. # The block can change a type of keys. + # The block yields the key and value. # # ``` # hash = {:a => 1, :b => 2, :c => 3} - # hash.transform_keys { |key| key.to_s } # => {"a" => 1, "b" => 2, "c" => 3} + # hash.transform_keys { |key| key.to_s } # => {"a" => 1, "b" => 2, "c" => 3} + # hash.transform_keys { |key, value| key.to_s * value } # => {"a" => 1, "bb" => 2, "ccc" => 3} # ``` - def transform_keys(& : K -> K2) : Hash(K2, V) forall K2 + def transform_keys(& : K, V -> K2) : Hash(K2, V) forall K2 each_with_object({} of K2 => V) do |(key, value), memo| - memo[yield(key)] = value + memo[yield(key, value)] = value end end # Returns a new hash with the results of running block once for every value. # The block can change a type of values. + # The block yields the value and key. # # ``` # hash = {:a => 1, :b => 2, :c => 3} - # hash.transform_values { |value| value + 1 } # => {:a => 2, :b => 3, :c => 4} + # hash.transform_values { |value| value + 1 } # => {:a => 2, :b => 3, :c => 4} + # hash.transform_values { |value, key| "#{key}#{value}" } # => {:a => "a1", :b => "b2", :c => "c3"} # ``` - def transform_values(& : V -> V2) : Hash(K, V2) forall V2 + def transform_values(& : V, K -> V2) : Hash(K, V2) forall V2 each_with_object({} of K => V2) do |(key, value), memo| - memo[key] = yield(value) + memo[key] = yield(value, key) end end # Destructively transforms all values using a block. Same as transform_values but modifies in place. # The block cannot change a type of values. + # The block yields the value and key. # # ``` # hash = {:a => 1, :b => 2, :c => 3} # hash.transform_values! { |value| value + 1 } # hash # => {:a => 2, :b => 3, :c => 4} + # hash.transform_values! { |value, key| value + key.to_s[0].ord } + # hash # => {:a => 99, :b => 101, :c => 103} # ``` # See `#update` for updating a *single* value. - def transform_values!(& : V -> V) : self + def transform_values!(& : V, K -> V) : self each_entry_with_index do |entry, i| - new_value = yield entry.value + new_value = yield entry.value, entry.key set_entry(i, Entry(K, V).new(entry.hash, entry.key, new_value)) end self From 56e32f6596ed35c041e9493340bff39fef0d835d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20H=C3=B6rberg?= Date: Sat, 22 Jul 2023 18:33:00 +0200 Subject: [PATCH 0636/1551] Add `File#rename` (#13640) --- spec/std/file_spec.cr | 10 ++++++++++ src/file.cr | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index ae3e7d7519cf..4dbe8e51b53a 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -604,6 +604,16 @@ describe "File" do end end end + + it "renames a File instance" do + with_tempfile("rename-source.txt", "rename-target.txt") do |source_path, target_path| + f = File.new(source_path, "w") + f.rename target_path + f.path.should eq target_path + File.exists?(source_path).should be_false + File.exists?(target_path).should be_true + end + end end # There are more detailed specs for `Path#expand` in path_spec.cr diff --git a/src/file.cr b/src/file.cr index 8c363868f50e..19a62e6b630d 100644 --- a/src/file.cr +++ b/src/file.cr @@ -866,6 +866,12 @@ class File < IO::FileDescriptor end end + # Rename the current `File` + def rename(new_filename : Path | String) : Nil + File.rename(@path, new_filename) + @path = new_filename.to_s + end + # Sets the access and modification times of *filename*. # # Use `#utime` if the `File` is already open. From 78ef924032e5f03407bdd32f3dc8c579b3f35144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 22 Jul 2023 18:33:13 +0200 Subject: [PATCH 0637/1551] Add shell completions for `clear_cache` (#13636) --- etc/completion.bash | 4 ++-- etc/completion.fish | 4 +++- etc/completion.zsh | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/etc/completion.bash b/etc/completion.bash index 34b5109bf145..d36a6d282a07 100644 --- a/etc/completion.bash +++ b/etc/completion.bash @@ -33,7 +33,7 @@ _crystal() local cur="${COMP_WORDS[COMP_CWORD]}" local prev="${COMP_WORDS[COMP_CWORD-1]}" - commands="init build docs eval play run spec tool help version --help --version" + commands="init build clear_cache docs eval play run spec tool help version --help --version" case "${cmd}" in init) @@ -81,7 +81,7 @@ _crystal() _crystal_compgen_sources "${cur}" fi ;; - docs|eval|spec|version|help) + clear_cache|docs|eval|spec|version|help) # These commands do not accept any options nor subcommands _crystal_compgen_files "${cur}" ;; diff --git a/etc/completion.fish b/etc/completion.fish index c0d53961a854..5bdb8532fec7 100644 --- a/etc/completion.fish +++ b/etc/completion.fish @@ -1,4 +1,4 @@ -set -l crystal_commands init build docs env eval i interactive play run spec tool help version +set -l crystal_commands init build clear_cache docs env eval i interactive play run spec tool help version set -l tool_subcommands context expand format hierarchy implementations types complete -c crystal -s h -l help -d "Show help" -x @@ -38,6 +38,8 @@ complete -c crystal -n "__fish_seen_subcommand_from build" -l verbose -d "Displa complete -c crystal -n "__fish_seen_subcommand_from build" -l static -d "Link statically" complete -c crystal -n "__fish_seen_subcommand_from build" -l stdin-filename -d "Source file name to be read from STDIN" +complete -c crystal -n "not __fish_seen_subcommand_from $crystal_commands" -a "clear_cache" -d "clear the compiler cache" + complete -c crystal -n "not __fish_seen_subcommand_from $crystal_commands" -a "docs" -d "generate documentation" complete -c crystal -n "__fish_seen_subcommand_from docs" -l project-name -d "Set project name" complete -c crystal -n "__fish_seen_subcommand_from docs" -l project-version -d "Set project version" diff --git a/etc/completion.zsh b/etc/completion.zsh index 6d64ccd0ff76..783b9f3cd295 100644 --- a/etc/completion.zsh +++ b/etc/completion.zsh @@ -7,6 +7,7 @@ _crystal_commands() { commands=( "init:generate new crystal project" "build:build an executable" + "clear_cache:clear the compiler cache" "docs:generate documentation" "env:print Crystal environment information" "eval:eval code from args or standard input" From 302bb427c256c4f611d455a6f2a72794718b4664 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 23 Jul 2023 00:33:21 +0800 Subject: [PATCH 0638/1551] Pre-allocate Dragonbox cache array (#13649) --- src/float/printer/dragonbox_cache.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/float/printer/dragonbox_cache.cr b/src/float/printer/dragonbox_cache.cr index eec330246d19..523af21da0b6 100644 --- a/src/float/printer/dragonbox_cache.cr +++ b/src/float/printer/dragonbox_cache.cr @@ -99,7 +99,7 @@ module Float::Printer::Dragonbox end CACHE = begin - cache = [] of WUInt::UInt128 + cache = Array(WUInt::UInt128).new(619) put(cache, 0xff77b1fcbebcdc4f_u64, 0x25e8e89c13bb0f7b_u64) put(cache, 0x9faacf3df73609b1_u64, 0x77b191618c54e9ad_u64) put(cache, 0xc795830d75038c1d_u64, 0xd59df5b9ef6a2418_u64) From 81c3367069066e344ff05c3cbbbd2658cd598144 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 23 Jul 2023 07:08:07 +0800 Subject: [PATCH 0639/1551] Fix lookup scope for `@[Primitive]` def's return type (#13658) --- spec/compiler/semantic/primitives_spec.cr | 18 ++++++++++++++++++ src/compiler/crystal/semantic/main_visitor.cr | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/primitives_spec.cr b/spec/compiler/semantic/primitives_spec.cr index a5d732957b21..485fc80d3a58 100644 --- a/spec/compiler/semantic/primitives_spec.cr +++ b/spec/compiler/semantic/primitives_spec.cr @@ -235,4 +235,22 @@ describe "Semantic: primitives" do list.next(Int32) )) { int32 } end + + it "looks up return type in correct scope (#13652)" do + assert_type(<<-CRYSTAL) { types["A"] } + class A + end + + class Foo + @[Primitive(:foo)] + def foo : A + end + end + + class Bar::A < Foo + end + + Bar::A.new.foo + CRYSTAL + end end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 16b3931ebefb..5f81822de0b6 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -2266,7 +2266,7 @@ module Crystal def visit(node : Primitive) # If the method where this primitive is defined has a return type, use it if return_type = typed_def.return_type - node.type = scope.lookup_type(return_type, free_vars: free_vars) + node.type = (path_lookup || scope).lookup_type(return_type, free_vars: free_vars) return false end From ccff71cb4244d0bbfd527bb22d08ca915a2edd34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 23 Jul 2023 01:08:18 +0200 Subject: [PATCH 0640/1551] New changelog format (#13662) --- scripts/github-changelog.cr | 257 +++++++++++++++++++++++------------- 1 file changed, 164 insertions(+), 93 deletions(-) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index 63859a37ddef..b46adb9c656b 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -25,23 +25,25 @@ api_token = ENV["GITHUB_TOKEN"] repository = "crystal-lang/crystal" milestone = ARGV.first -query = <<-GRAPHQL - query($milestone: String, $owner: String!, $repository: String!) { - repository(owner: $owner, name: $repository) { - milestones(query: $milestone, first: 1) { - nodes { - pullRequests(first: 300) { - nodes { - number - title - mergedAt - permalink - author { - login - } - labels(first: 10) { - nodes { - name +def query_prs(api_token, repository, milestone) + query = <<-GRAPHQL + query($milestone: String, $owner: String!, $repository: String!) { + repository(owner: $owner, name: $repository) { + milestones(query: $milestone, first: 1) { + nodes { + pullRequests(first: 300) { + nodes { + number + title + mergedAt + permalink + author { + login + } + labels(first: 10) { + nodes { + name + } } } } @@ -49,24 +51,26 @@ query = <<-GRAPHQL } } } + GRAPHQL + + owner, _, name = repository.partition("/") + variables = { + owner: owner, + repository: name, + milestone: milestone, } - GRAPHQL -owner, _, name = repository.partition("/") -variables = { - owner: owner, - repository: name, - milestone: milestone, -} + response = HTTP::Client.post("https://api.github.com/graphql", + body: {query: query, variables: variables}.to_json, + headers: HTTP::Headers{ + "Authorization" => "bearer #{api_token}", + } + ) + unless response.success? + abort "GitHub API response: #{response.status}\n#{response.body}" + end -response = HTTP::Client.post("https://api.github.com/graphql", - body: {query: query, variables: variables}.to_json, - headers: HTTP::Headers{ - "Authorization" => "bearer #{api_token}", - } -) -unless response.success? - abort "GitHub API response: #{response.status}\n#{response.body}" + response end module LabelNameConverter @@ -97,16 +101,19 @@ record PullRequest, @labels : Array(String) def to_s(io : IO) - if labels.includes?("breaking-change") - io << "**(breaking-change)** " + if topic = self.sub_topic + io << "*(" << sub_topic << ")* " end if labels.includes?("security") - io << "**(security)** " + io << "**[security]** " end - if labels.includes?("performance") - io << "**(performance)** " + if labels.includes?("breaking-change") + io << "**[breaking]** " end - io << title << " (" + if deprecated? + io << "**[deprecation]** " + end + io << title.sub(/^#{type}: /i, "") << " (" io << "[#" << number << "](" << permalink << ")" if author = self.author io << ", thanks @" << author @@ -120,14 +127,94 @@ record PullRequest, def sort_tuple { - labels.includes?("security") ? 0 : 1, - labels.includes?("breaking-change") ? 0 : 1, - labels.includes?("kind:bug") ? 0 : 1, + type || "", + topic || [] of String, + deprecated? ? 0 : 1, + merged_at || Time.unix(0), + } + end + + def infra_sort_tuple + { + topic || [] of String, + type || "", + deprecated? ? 0 : 1, merged_at || Time.unix(0), } end + + def primary_topic + topic.try(&.[0]?) || "other" + end + + def sub_topic + topic.try(&.[1..].join(":").presence) + end + + def topic + labels.find { |label| + label.starts_with?("topic:") && label != "topic:multithreading" + }.try(&.lchop("topic:").split(/:|\//)) + end + + def deprecated? + labels.includes?("deprecation") + end + + def breaking? + labels.includes?("kind:breaking") + end + + def feature? + labels.includes?("kind:feature") + end + + def fix? + labels.includes?("kind:bug") + end + + def refactor? + labels.includes?("kind:refactor") + end + + def docs? + labels.includes?("kind:docs") + end + + def specs? + labels.includes?("kind:specs") + end + + def performance? + labels.includes?("performance") + end + + def infra? + labels.any?(&.starts_with?("topic:infrastructure")) + end + + def type + case + when feature? then "feature" + when fix? then "fix" + when docs? then "docs" + when specs? then "specs" + when performance? then "performance" + when refactor? then "refactor" + else nil + end + end + + def section + case + when breaking? then "breaking" + when infra? then "infra" + else type || "" + end + end end +response = query_prs(api_token, repository, milestone) parser = JSON::PullParser.new(response.body) array = parser.on_key! "data" do parser.on_key! "repository" do @@ -148,62 +235,46 @@ end changelog = File.read("CHANGELOG.md") array.select! { |pr| pr.merged_at && !changelog.index(pr.permalink) } -sections = array.group_by { |pr| - pr.labels.each do |label| - case label - when .starts_with?("topic:lang") - break "Language" - when .starts_with?("topic:compiler") - if label == "topic:compiler" - break "Compiler" - else - break "Compiler: #{label.lchop("topic:compiler:").titleize}" - end - when .starts_with?("topic:tools") - if label == "topic:tools" - break "Tools" - else - break "Tools: #{label.lchop("topic:tools:").titleize}" - end - when .starts_with?("topic:stdlib") - if label == "topic:stdlib" - break "Standard Library" - else - break "Standard Library: #{label.lchop("topic:stdlib:").titleize}" - end - else - next - end - end || "Other" + +sections = array.group_by(&.section) + +SECTION_TITLES = { + "breaking" => "Breaking changes", + "feature" => "Features", + "fix" => "Bugfixes", + "performance" => "Performance", + "refactor" => "Refactor", + "docs" => "Documentation", + "specs" => "Specs", + "infra" => "Infrastructure", + "" => "Chores", } -titles = [] of String -["Language", "Standard Library", "Compiler", "Tools", "Other"].each do |main_section| - titles.concat sections.each_key.select(&.starts_with?(main_section)).to_a.sort! -end -sections.keys.sort!.each do |section| - titles << section unless titles.includes?(section) -end -last_title1 = nil - -titles.each do |title| - prs = sections[title]? || next - title1, _, title2 = title.partition(": ") - if title2.presence - if title1 != last_title1 - puts "## #{title1}" +TOPIC_ORDER = %w[lang stdlib compiler tools other] + +SECTION_TITLES.each do |id, title| + prs = sections[id]? || next + puts "### #{title}" + puts + + topics = prs.group_by(&.primary_topic) + + topic_titles = topics.keys.sort_by! { |k| TOPIC_ORDER.index(k) || Int32::MAX } + + topic_titles.each do |topic_title| + topic_prs = topics[topic_title]? || next + + if id == "infra" + topic_prs.sort_by!(&.infra_sort_tuple) + else + topic_prs.sort! + puts "#### #{topic_title}" puts end - puts "### #{title2}" - else - puts "## #{title1}" - end - last_title1 = title1 - puts - prs.sort! - prs.each do |pr| - puts "- #{pr}" + topic_prs.each do |pr| + puts "- #{pr}" + end + puts end - puts end From 609e1291f13c341ee4d79631782e6b632a65833b Mon Sep 17 00:00:00 2001 From: Evan Paterakis Date: Sun, 23 Jul 2023 02:08:29 +0300 Subject: [PATCH 0641/1551] Fix octicon-link icon color on dark mode (#13670) --- src/compiler/crystal/tools/doc/templates.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/tools/doc/templates.cr b/src/compiler/crystal/tools/doc/templates.cr index 156318a64269..6463ea637479 100644 --- a/src/compiler/crystal/tools/doc/templates.cr +++ b/src/compiler/crystal/tools/doc/templates.cr @@ -4,7 +4,7 @@ module Crystal::Doc SVG_DEFS = <<-SVG SVG From 30bdc9208ce4d5d79196baac0768e65d3e22bd72 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 24 Jul 2023 00:23:39 +0800 Subject: [PATCH 0642/1551] Detect developer mode in Windows installer (#13681) --- etc/win-ci/crystal.iss | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/etc/win-ci/crystal.iss b/etc/win-ci/crystal.iss index 7454473062c3..18fdf2e79fec 100644 --- a/etc/win-ci/crystal.iss +++ b/etc/win-ci/crystal.iss @@ -200,6 +200,16 @@ begin HasWinSDKAt(HKEY_CURRENT_USER, Win10SDK32); end; +function IsDeveloperModeEnabled: Boolean; +var + regValue: Cardinal; +begin + result := False; + if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock', 'AllowDevelopmentWithoutDevLicense', regValue) then + if regValue <> 0 then + result := True; +end; + { Adopted from https://stackoverflow.com/a/46609047 } procedure EnvAddPath(Path: string; IsSystem: Boolean); var @@ -302,7 +312,13 @@ begin 'Please install the missing components using one of the following options: \line\line ' + '\emspace\bullet\emspace https://aka.ms/vs/17/release/vs_BuildTools.exe for the build tools alone \line ' + '\emspace\bullet\emspace https://visualstudio.microsoft.com/downloads/ for the build tools + Visual Studio 2022 \line\line ' + - 'The {\b Desktop development with C++} workload should be selected.'; + 'The {\b Desktop development with C++} workload should be selected. \line\line '; + + if not IsDeveloperModeEnabled() then + message := message + + '{\b WARNING:} Developer Mode is not enabled on this machine. Please refer to ' + + 'https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development ' + + 'for instructions on how to enable Developer Mode. \line\line '; if message <> '' then warningsPage := CreateOutputMsgMemoPage(wpInfoAfter, From b470af236c399316cee6a2a080cb47615e394171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 23 Jul 2023 18:23:57 +0200 Subject: [PATCH 0643/1551] [CI] Trigger windows release jobs on tag (#13683) --- .github/workflows/win.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index be017271c97f..0714c400e2af 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -252,14 +252,14 @@ jobs: run: make -f Makefile.win samples x86_64-windows-release: - if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/ci/')) + if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/')) needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm] uses: ./.github/workflows/win_build_portable.yml with: release: true x86_64-windows-installer: - if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/ci/')) + if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/')) runs-on: windows-2022 needs: [x86_64-windows-release] steps: From f6266db87dd6fff89e3e9af7e31b0eb22a7c7451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 23 Jul 2023 18:24:07 +0200 Subject: [PATCH 0644/1551] Upgrade SSL defaults to Mozilla guidelines version 5.7 (#13685) --- src/openssl/ssl/defaults.cr | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/openssl/ssl/defaults.cr b/src/openssl/ssl/defaults.cr index 79d9f5bec952..6cccb707faa1 100644 --- a/src/openssl/ssl/defaults.cr +++ b/src/openssl/ssl/defaults.cr @@ -1,5 +1,5 @@ # THIS FILE WAS AUTOMATICALLY GENERATED BY script/ssl_server_defaults.cr -# on 2020-10-09 20:33:59 UTC. +# on 2023-07-21 10:32:46 UTC. abstract class OpenSSL::SSL::Context # The list of secure ciphers on **modern** compatibility level as per Mozilla @@ -15,8 +15,8 @@ abstract class OpenSSL::SSL::Context # * Opera 57 # * Safari 12.1 # - # This list represents version 5.6 of the modern configuration - # available at https://ssl-config.mozilla.org/guidelines/5.6.json. + # This list represents version 5.7 of the modern configuration + # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. CIPHERS_MODERN = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS" @@ -34,8 +34,8 @@ abstract class OpenSSL::SSL::Context # * Opera 57 # * Safari 12.1 # - # This list represents version 5.6 of the modern configuration - # available at https://ssl-config.mozilla.org/guidelines/5.6.json. + # This list represents version 5.7 of the modern configuration + # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. CIPHER_SUITES_MODERN = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" @@ -54,11 +54,11 @@ abstract class OpenSSL::SSL::Context # * Opera 20 # * Safari 9 # - # This list represents version 5.6 of the intermediate configuration - # available at https://ssl-config.mozilla.org/guidelines/5.6.json. + # This list represents version 5.7 of the intermediate configuration + # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. - CIPHERS_INTERMEDIATE = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS" + CIPHERS_INTERMEDIATE = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS" # The list of secure ciphersuites on **intermediate** compatibility level as per Mozilla # recommendations. @@ -74,8 +74,8 @@ abstract class OpenSSL::SSL::Context # * Opera 20 # * Safari 9 # - # This list represents version 5.6 of the intermediate configuration - # available at https://ssl-config.mozilla.org/guidelines/5.6.json. + # This list represents version 5.7 of the intermediate configuration + # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. CIPHER_SUITES_INTERMEDIATE = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" @@ -94,8 +94,8 @@ abstract class OpenSSL::SSL::Context # * Opera 5 # * Safari 1 # - # This list represents version 5.6 of the old configuration - # available at https://ssl-config.mozilla.org/guidelines/5.6.json. + # This list represents version 5.7 of the old configuration + # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. CIPHERS_OLD = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS" @@ -114,8 +114,8 @@ abstract class OpenSSL::SSL::Context # * Opera 5 # * Safari 1 # - # This list represents version 5.6 of the old configuration - # available at https://ssl-config.mozilla.org/guidelines/5.6.json. + # This list represents version 5.7 of the old configuration + # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. CIPHER_SUITES_OLD = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" From 8c2c24841da853cb5906cfba58179bd5b2a14201 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 25 Jul 2023 06:02:24 -0300 Subject: [PATCH 0645/1551] Optimize `IO::Delimited` (#11242) --- spec/std/io/delimited_spec.cr | 355 +++++++++++++++++++++++++++++----- spec/std/io/io_spec.cr | 5 + src/io.cr | 37 ++-- src/io/delimited.cr | 231 +++++++++++++++++++++- 4 files changed, 566 insertions(+), 62 deletions(-) diff --git a/spec/std/io/delimited_spec.cr b/spec/std/io/delimited_spec.cr index c88b4705fdc0..63096322237d 100644 --- a/spec/std/io/delimited_spec.cr +++ b/spec/std/io/delimited_spec.cr @@ -21,76 +21,333 @@ private class PartialReaderIO < IO end end +private class MemoryIOWithoutPeek < IO::Memory + def peek + nil + end +end + +private class MemoryIOWithFixedPeek < IO::Memory + property peek_size = 0 + + def peek + Slice.new(@buffer + @pos, {@bytesize - @pos, peek_size}.min) + end +end + describe "IO::Delimited" do describe "#read" do - it "doesn't read past the limit" do - io = IO::Memory.new("abcderzzrfgzr") - delimited = IO::Delimited.new(io, read_delimiter: "zr") + context "without peeking" do + it "doesn't read past the limit" do + io = MemoryIOWithoutPeek.new("abcderzzrfgzr") + delimited = IO::Delimited.new(io, read_delimiter: "zr") - delimited.gets_to_end.should eq("abcderz") - io.gets_to_end.should eq("fgzr") - end + delimited.gets_to_end.should eq("abcderz") + io.gets_to_end.should eq("fgzr") + end - it "doesn't read past the limit (char-by-char)" do - io = IO::Memory.new("abcderzzrfg") - delimited = IO::Delimited.new(io, read_delimiter: "zr") + it "doesn't read past the limit (char-by-char)" do + io = MemoryIOWithoutPeek.new("abcderzzrfg") + delimited = IO::Delimited.new(io, read_delimiter: "zr") - delimited.read_char.should eq('a') - delimited.read_char.should eq('b') - delimited.read_char.should eq('c') - delimited.read_char.should eq('d') - delimited.read_char.should eq('e') - delimited.read_char.should eq('r') - delimited.read_char.should eq('z') - delimited.read_char.should eq(nil) - delimited.read_char.should eq(nil) - delimited.read_char.should eq(nil) - delimited.read_char.should eq(nil) - - io.read_char.should eq('f') - io.read_char.should eq('g') - end + delimited.read_char.should eq('a') + delimited.read_char.should eq('b') + delimited.read_char.should eq('c') + delimited.read_char.should eq('d') + delimited.read_char.should eq('e') + delimited.read_char.should eq('r') + delimited.read_char.should eq('z') + delimited.read_char.should eq(nil) + delimited.read_char.should eq(nil) + delimited.read_char.should eq(nil) + delimited.read_char.should eq(nil) - it "doesn't clobber active_delimiter_buffer" do - io = IO::Memory.new("ab12312") - delimited = IO::Delimited.new(io, read_delimiter: "12345") + io.read_char.should eq('f') + io.read_char.should eq('g') + end - delimited.gets_to_end.should eq("ab12312") - end + it "doesn't clobber active_delimiter_buffer" do + io = MemoryIOWithoutPeek.new("ab12312") + delimited = IO::Delimited.new(io, read_delimiter: "12345") - it "handles the delimiter at the start" do - io = IO::Memory.new("ab12312") - delimited = IO::Delimited.new(io, read_delimiter: "ab1") + delimited.gets_to_end.should eq("ab12312") + end - delimited.read_char.should eq(nil) + it "handles the delimiter at the start" do + io = MemoryIOWithoutPeek.new("ab12312") + delimited = IO::Delimited.new(io, read_delimiter: "ab1") + + delimited.read_char.should eq(nil) + end + + it "handles the delimiter at the end" do + io = MemoryIOWithoutPeek.new("ab12312z") + delimited = IO::Delimited.new(io, read_delimiter: "z") + + delimited.gets_to_end.should eq("ab12312") + end + + it "handles nearly a delimiter at the end" do + io = MemoryIOWithoutPeek.new("ab12312") + delimited = IO::Delimited.new(io, read_delimiter: "122") + + delimited.gets_to_end.should eq("ab12312") + end + + it "doesn't clobber the buffer on closely-offset partial matches" do + io = MemoryIOWithoutPeek.new("abab1234abcdefgh") + delimited = IO::Delimited.new(io, read_delimiter: "abcdefgh") + + delimited.gets_to_end.should eq("abab1234") + end end - it "handles the delimiter at the end" do - io = IO::Memory.new("ab12312z") - delimited = IO::Delimited.new(io, read_delimiter: "z") + context "with partial read" do + it "handles partial reads" do + io = PartialReaderIO.new("abab1234abcdefgh") + delimited = IO::Delimited.new(io, read_delimiter: "abcdefgh") - delimited.gets_to_end.should eq("ab12312") + delimited.gets_to_end.should eq("abab1234") + end end - it "handles nearly a delimiter at the end" do - io = IO::Memory.new("ab12312") - delimited = IO::Delimited.new(io, read_delimiter: "122") + context "with peeking" do + it "returns empty when there's no data" do + io = IO::Memory.new("") + delimited = IO::Delimited.new(io, read_delimiter: "zr") + + delimited.peek.should eq("".to_slice) + delimited.gets_to_end.should eq("") + io.gets_to_end.should eq("") + end - delimited.gets_to_end.should eq("ab12312") + it "doesn't read past the limit" do + io = IO::Memory.new("abcderzzrfgzr") + delimited = IO::Delimited.new(io, read_delimiter: "zr") + + delimited.peek.should eq("abcderz".to_slice) + delimited.gets_to_end.should eq("abcderz") + io.gets_to_end.should eq("fgzr") + end + + it "doesn't read past the limit, single byte" do + io = IO::Memory.new("abcderzzrfgzr") + delimited = IO::Delimited.new(io, read_delimiter: "f") + + delimited.peek.should eq("abcderzzr".to_slice) + delimited.gets_to_end.should eq("abcderzzr") + io.gets_to_end.should eq("gzr") + end + + it "doesn't read past the limit (char-by-char)" do + io = IO::Memory.new("abcderzzrfg") + delimited = IO::Delimited.new(io, read_delimiter: "zr") + + delimited.read_char.should eq('a') + delimited.read_char.should eq('b') + delimited.read_char.should eq('c') + delimited.read_char.should eq('d') + delimited.read_char.should eq('e') + delimited.read_char.should eq('r') + delimited.read_char.should eq('z') + delimited.read_char.should eq(nil) + delimited.read_char.should eq(nil) + delimited.read_char.should eq(nil) + delimited.read_char.should eq(nil) + + io.read_char.should eq('f') + io.read_char.should eq('g') + end + + it "doesn't clobber active_delimiter_buffer" do + io = IO::Memory.new("ab12312") + delimited = IO::Delimited.new(io, read_delimiter: "12345") + + delimited.peek.should eq("ab123".to_slice) + delimited.gets_to_end.should eq("ab12312") + end + + it "handles the delimiter at the start" do + io = IO::Memory.new("ab12312") + delimited = IO::Delimited.new(io, read_delimiter: "ab1") + + delimited.peek.should eq(Bytes.empty) + delimited.read_char.should eq(nil) + end + + it "handles the delimiter at the end" do + io = IO::Memory.new("ab12312z") + delimited = IO::Delimited.new(io, read_delimiter: "z") + + delimited.peek.should eq("ab12312".to_slice) + delimited.gets_to_end.should eq("ab12312") + end + + it "handles nearly a delimiter at the end" do + io = IO::Memory.new("ab12312") + delimited = IO::Delimited.new(io, read_delimiter: "122") + + delimited.peek.should eq("ab123".to_slice) + delimited.gets_to_end.should eq("ab12312") + end + + it "doesn't clobber the buffer on closely-offset partial matches" do + io = IO::Memory.new("abab1234abcdefgh") + delimited = IO::Delimited.new(io, read_delimiter: "abcdefgh") + + delimited.peek.should eq("abab1234".to_slice) + delimited.gets_to_end.should eq("abab1234") + end + + it "handles the case of peek matching first byte, not having enough room, but rest not matching" do + # not a delimiter + # --- + io = MemoryIOWithFixedPeek.new("abcdefwhi") + # ------- + # peek + io.peek_size = 7 + delimited = IO::Delimited.new(io, read_delimiter: "fgh") + + delimited.peek.should eq("abcde".to_slice) + delimited.gets_to_end.should eq("abcdefwhi") + delimited.gets_to_end.should eq("") + io.gets_to_end.should eq("") + end + + it "handles the case of peek matching first byte, not having enough room, but rest not immediately matching (with a potential match afterwards)" do + # not a delimiter, but the second 'f' will actually match the delimiter + # --- + io = MemoryIOWithFixedPeek.new("abcdeffghijk") + # ------- + # peek + io.peek_size = 7 + delimited = IO::Delimited.new(io, read_delimiter: "fgh") + + delimited.peek.should eq("abcde".to_slice) + delimited.gets_to_end.should eq("abcdef") + delimited.gets_to_end.should eq("") + io.gets_to_end.should eq("ijk") + end + + it "handles the case of peek matching first byte, not having enough room, but later matching" do + # delimiter + # --- + io = MemoryIOWithFixedPeek.new("abcdefghijk") + # ------- + # peek + io.peek_size = 7 + delimited = IO::Delimited.new(io, read_delimiter: "fgh") + + delimited.peek.should eq("abcde".to_slice) + delimited.gets_to_end.should eq("abcde") + delimited.gets_to_end.should eq("") + io.gets_to_end.should eq("ijk") + end + + it "handles the case of peek matching first byte, not having enough room, but later not matching" do + # not a delimiter + # --- + io = MemoryIOWithFixedPeek.new("abcdefgwijkfghhello") + # ------- --- + # peek delimiter + io.peek_size = 7 + delimited = IO::Delimited.new(io, read_delimiter: "fgh") + + delimited.peek.should eq("abcde".to_slice) + delimited.gets_to_end.should eq("abcdefgwijk") + delimited.gets_to_end.should eq("") + io.gets_to_end.should eq("hello") + end + + it "handles the case of peek matching first byte, not having enough room, later only partially matching" do + # delimiter + # ------------ + io = MemoryIOWithFixedPeek.new("abcdefghijklmnopqrst") + # -------~~~~~~~ + # peek peek + io.peek_size = 7 + delimited = IO::Delimited.new(io, read_delimiter: "fghijklmnopq") + + delimited.peek.should eq("abcde".to_slice) + delimited.gets_to_end.should eq("abcde") + delimited.gets_to_end.should eq("") + io.gets_to_end.should eq("rst") + end + + it "peeks, everything matches but we can't know what will happen after that" do + io = MemoryIOWithFixedPeek.new("fgh") + io.peek_size = 2 + delimited = IO::Delimited.new(io, read_delimiter: "fgh") + + delimited.peek.should be_nil + end + + it "handles the case of the active delimited buffer including the delimiter" do + # delimiter + # --- + io = MemoryIOWithFixedPeek.new("aaabcde") + # -- + # peek + io.peek_size = 2 + delimited = IO::Delimited.new(io, read_delimiter: "aab") + + delimited.peek.should be_nil + delimited.gets_to_end.should eq("a") + delimited.gets_to_end.should eq("") + io.gets_to_end.should eq("cde") + end + + it "can peek if first byte found but doesn't fully match, and there's that first byte again in the peek buffer" do + # delimiter + # ----- + io = MemoryIOWithFixedPeek.new("holhhello") + # ---- + # peek + io.peek_size = 4 + delimited = IO::Delimited.new(io, read_delimiter: "hello") + + delimited.peek.should eq("hol".to_slice) + end + + it "can peek if first byte found but doesn't fully match, and the byte isn't there in the peek buffer" do + # delimiter + # ----- + io = MemoryIOWithFixedPeek.new("holahello") + # ---- + # peek + io.peek_size = 4 + delimited = IO::Delimited.new(io, read_delimiter: "hello") + + delimited.peek.should eq("hola".to_slice) + end end + end - it "doesn't clobber the buffer on closely-offset partial matches" do - io = IO::Memory.new("abab1234abcdefgh") - delimited = IO::Delimited.new(io, read_delimiter: "abcdefgh") + describe "#gets with peeking when can't peek" do + it "gets" do + io = MemoryIOWithFixedPeek.new("abcdefghel") + io.peek_size = 7 - delimited.gets_to_end.should eq("abab1234") + delimited = IO::Delimited.new(io, read_delimiter: "hello") + + # We first peek "abcdefg". + # The delimiter's first byte isn't there so we read everything. + # Next we peek "hel". It matches the delimiter's beginning + # but `peek` can't tell whether there's more content or not + # after that, and it can't return an empty buffer because that + # means EOF. So it must return `nil`. So `IO#gets` should + # handle this case where `peek` becomes `nil`. + delimited.gets.should eq("abcdefghel") end - it "handles partial reads" do - io = PartialReaderIO.new("abab1234abcdefgh") - delimited = IO::Delimited.new(io, read_delimiter: "abcdefgh") + it "peeks" do + io = MemoryIOWithFixedPeek.new("hel") + io.peek_size = 1 + + delimited = IO::Delimited.new(io, read_delimiter: "hello") + delimited.read(Bytes.new(1)) - delimited.gets_to_end.should eq("abab1234") + delimited.peek.should eq("el".to_slice) end end diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index f8497f5360bc..ac51d4bddaea 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -246,6 +246,11 @@ describe IO do io.gets('a', 3).should be_nil end + it "doesn't underflow when limit is unsigned" do + io = IO::Memory.new("aїa") + io.gets('є', 2u32).should eq("aї") + end + it "raises if invoking gets with negative limit" do io = SimpleIOMemory.new("hello\nworld\n") expect_raises ArgumentError, "Negative limit" do diff --git a/src/io.cr b/src/io.cr index 7197f0b8316c..bce6497db606 100644 --- a/src/io.cr +++ b/src/io.cr @@ -449,7 +449,7 @@ abstract class IO # Peeks into this IO, if possible. # # It returns: - # - `nil` if this IO isn't peekable + # - `nil` if this IO isn't peekable at this moment or at all # - an empty slice if it is, but EOF was reached # - a non-empty slice if some data can be peeked # @@ -716,7 +716,15 @@ abstract class IO peek = self.peek end - if !peek || peek.empty? + unless peek + # If for some reason this IO became unpeekable, + # default to the slow method. One example where this can + # happen is `IO::Delimited`. + gets_slow(delimiter, limit, chomp, buffer) + break + end + + if peek.empty? if buffer.bytesize == 0 return nil else @@ -741,14 +749,18 @@ abstract class IO end private def gets_slow(delimiter : Char, limit, chomp) + buffer = String::Builder.new + gets_slow(delimiter, limit, chomp, buffer) + buffer.empty? ? nil : buffer.to_s + end + + private def gets_slow(delimiter : Char, limit, chomp, buffer : String::Builder) : Nil chomp_rn = delimiter == '\n' && chomp - buffer = String::Builder.new - total = 0 while true info = read_char_with_bytesize unless info - return buffer.empty? ? nil : buffer.to_s + break end char, char_bytesize = info @@ -767,12 +779,14 @@ abstract class IO end buffer << '\r' - total += char_bytesize - break if total >= limit + + break if char_bytesize >= limit + limit -= char_bytesize buffer << char2 - total += char_bytesize2 - break if total >= limit + + break if char_bytesize2 >= limit + limit -= char_bytesize2 next elsif char == delimiter @@ -782,10 +796,9 @@ abstract class IO buffer << char end - total += char_bytesize - break if total >= limit + break if char_bytesize >= limit + limit -= char_bytesize end - buffer.to_s end # Reads until *delimiter* is found or the end of the `IO` is reached. diff --git a/src/io/delimited.cr b/src/io/delimited.cr index 3451b47f4492..ea1039724330 100644 --- a/src/io/delimited.cr +++ b/src/io/delimited.cr @@ -44,8 +44,175 @@ class IO::Delimited < IO def read(slice : Bytes) : Int32 check_open - return 0 if @finished + + if @finished + # We might still have stuff to read from @active_delimiter_buffer + return read_from_active_delimited_buffer(slice) + end + + read_internal(slice) + end + + private def read_internal(slice : Bytes) : Int32 + if peek = @io.peek + read_with_peek(slice, peek) + else + read_without_peek(slice) + end + end + + private def read_with_peek(slice : Bytes, peek : Bytes) : Int32 + # If there's nothing else to peek, we reached EOF + if peek.empty? + @finished = true + + if @active_delimiter_buffer.empty? + return 0 + else + # If we have something in the active delimiter buffer, + # but we don't have any more data to read, that wasn't + # the delimiter so we must include it in the slice. + return read_from_active_delimited_buffer(slice) + end + end + first_byte = @read_delimiter[0] + + # If we have something in the active delimiter buffer + unless @active_delimiter_buffer.empty? + # This is the rest of the delimiter we have to match + delimiter_remaining = @read_delimiter[@active_delimiter_buffer.size..] + + # This is how much we can actually match of that (peek might not have enough data!) + min_size = Math.min(delimiter_remaining.size, peek.size) + + # See if what remains to match in the delimiter matches whatever + # we have in peek, limited to what's available + if delimiter_remaining[0, min_size] == peek[0, min_size] + # If peek has enough data to match the entire rest of the delimiter... + if peek.size >= delimiter_remaining.size + # We found the delimiter! + @io.skip(min_size) + @active_delimiter_buffer = Bytes.empty + @finished = true + return 0 + else + # Copy the remaining of peek to the active delimiter buffer for now + (@delimiter_buffer + @active_delimiter_buffer.size).copy_from(peek) + @active_delimiter_buffer = @delimiter_buffer[0, @active_delimiter_buffer.size + peek.size] + + # Skip whatever we had in peek, and try reading more + @io.skip(peek.size) + return read_internal(slice) + end + else + # No match. + # We first need to check if the delimiter could actually start in this active buffer. + next_index = @active_delimiter_buffer.index(first_byte, 1) + + # We read up to that new match, if any, or the entire buffer + read_bytes = next_index || @active_delimiter_buffer.size + + slice.copy_from(@active_delimiter_buffer[0, read_bytes]) + slice += read_bytes + @active_delimiter_buffer += read_bytes + return read_bytes + read_internal(slice) + end + end + + index = + if slice.size == 1 + # For a size of 1, this is much faster + first_byte == peek[0] ? 0 : nil + elsif slice.size < peek.size + peek[0, slice.size].index(first_byte) + else + peek.index(first_byte) + end + + # If we can't find the delimiter's first byte we can just read from peek + unless index + # If we have more in peek than what we need to read, read all of that + if peek.size >= slice.size + if slice.size == 1 + # For a size of 1, this is much faster + slice[0] = peek[0] + else + slice.copy_from(peek[0, slice.size]) + end + @io.skip(slice.size) + return slice.size + else + # Otherwise, read from peek for now + slice.copy_from(peek) + @io.skip(peek.size) + return peek.size + end + end + + # If the delimiter is just a single byte, we can stop right here + if @delimiter_buffer.size == 1 + slice.copy_from(peek[0, index]) + @io.skip(index + 1) + @finished = true + return index + end + + # If the delimiter fits the rest of the peek buffer, + # we can check it right now. + if index + @delimiter_buffer.size <= peek.size + # If we found the delimiter, we are done + if peek[index, @delimiter_buffer.size] == @read_delimiter + slice.copy_from(peek[0, index]) + @io.skip(index + @delimiter_buffer.size) + @finished = true + return index + else + # Otherwise, we can read up to past that byte for now + slice.copy_from(peek[0, index + 1]) + @io.skip(index + 1) + slice += index + 1 + return index + 1 + end + end + + # If the part past in the peek buffer past the matching index + # doesn't match the read delimiter's portion, we can move on + rest = peek[index..] + unless rest == @read_delimiter[0, rest.size] + # We can read up to past that byte for now + safe_to_read = peek[0, index + 1] + slice.copy_from(safe_to_read) + @io.skip(safe_to_read.size) + slice += safe_to_read.size + return safe_to_read.size + end + + # Copy up to index into slice + slice.copy_from(peek[0, index]) + slice += index + + # Copy the rest of the peek buffer into delimted buffer + @delimiter_buffer.copy_from(rest) + @active_delimiter_buffer = @delimiter_buffer[0, rest.size] + + @io.skip(peek.size) + + index + read_internal(slice) + end + + private def read_from_active_delimited_buffer(slice : Bytes) : Int32 + if @active_delimiter_buffer.empty? + return 0 + end + + available = Math.min(@active_delimiter_buffer.size, slice.size) + slice.copy_from(@active_delimiter_buffer[0, available]) + @active_delimiter_buffer += available + available + end + + private def read_without_peek(slice : Bytes) : Int32 first_byte = @read_delimiter[0] read_bytes = 0 @@ -93,6 +260,7 @@ class IO::Delimited < IO if buffer == @read_delimiter @finished = true + @active_delimiter_buffer = Bytes.empty return read_bytes end @@ -111,6 +279,67 @@ class IO::Delimited < IO raise IO::Error.new "Can't write to IO::Delimited" end + def peek : Bytes? + if @finished + # It's fine to return this internal buffer because peek + # clients aren't supposed to write to that buffer. + return @active_delimiter_buffer + end + + peek = @io.peek + return nil unless peek + return peek if peek.empty? + + # See if we can find the first byte + first_byte = @read_delimiter[0] + + offset = 0 + loop do + index = peek.index(first_byte, offset: offset) + + # If we can't find it, the entire underlying peek buffer is visible + unless index + return peek + end + + # If the delimiter is just one byte, we found it! + if @read_delimiter.size == 1 + return peek[0, index] + end + + # If the delimiter fits the rest of the peek buffer, + # we can check it right now. + if index + @delimiter_buffer.size <= peek.size + # If we found the delimiter, we are done + if peek[index, @delimiter_buffer.size] == @read_delimiter + return peek[0, index] + else + offset = index + 1 + next + end + else + # Otherwise we can't know without reading further, + # return what we have so far + if index == 0 + # Check if whatever remains in peek actually matches the delimiter. + min_size = Math.min(@read_delimiter.size, peek.size) + if @read_delimiter[0, min_size] == peek[0, min_size] + # The entire peek buffer partially matches the delimiter, + # but we don't know what will happen next. We can't peek. + return nil + else + # It didn't fully match, so we can at least return + # the part up to the next match + next_index = peek.index(first_byte, 1) + return peek[0, next_index || peek.size] + end + else + return peek[0, index] + end + end + end + end + def close : Nil return if @closed @closed = true From b69838c0b6c08788e557f1959a788f779feefa04 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 25 Jul 2023 06:03:05 -0300 Subject: [PATCH 0646/1551] Add unlimited block unpacking (#11597) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/compiler/formatter/formatter_spec.cr | 2 + spec/compiler/normalize/block_spec.cr | 96 +++++++++ spec/compiler/parser/parser_spec.cr | 74 +++++-- spec/compiler/parser/to_s_spec.cr | 10 + spec/compiler/semantic/block_spec.cr | 12 ++ src/compiler/crystal/semantic/main_visitor.cr | 57 +++-- src/compiler/crystal/semantic/normalizer.cr | 76 +++++++ src/compiler/crystal/syntax/ast.cr | 18 +- src/compiler/crystal/syntax/parser.cr | 200 ++++++++++-------- src/compiler/crystal/syntax/to_s.cr | 23 +- src/compiler/crystal/tools/formatter.cr | 81 ++++--- 11 files changed, 472 insertions(+), 177 deletions(-) create mode 100644 spec/compiler/normalize/block_spec.cr diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 754093190048..f7c4243e7795 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1840,6 +1840,8 @@ describe Crystal::Formatter do assert_format "foo { | a, ( b , c ) | a + b + c }", "foo { |a, (b, c)| a + b + c }" assert_format "foo { | a, ( b , c, ), | a + b + c }", "foo { |a, (b, c)| a + b + c }" assert_format "foo { | a, ( _ , c ) | a + c }", "foo { |a, (_, c)| a + c }" + assert_format "foo { | a, ( b , (c, d) ) | a + b + c }", "foo { |a, (b, (c, d))| a + b + c }" + assert_format "foo { | ( a, *b , c ) | a }", "foo { |(a, *b, c)| a }" assert_format "def foo\n {{@type}}\nend" diff --git a/spec/compiler/normalize/block_spec.cr b/spec/compiler/normalize/block_spec.cr new file mode 100644 index 000000000000..74716f2d9ef4 --- /dev/null +++ b/spec/compiler/normalize/block_spec.cr @@ -0,0 +1,96 @@ +require "../../spec_helper" + +describe "Normalize: block" do + it "normalizes unpacking with empty body" do + assert_normalize <<-FROM, <<-TO + foo do |(x, y), z| + end + FROM + foo do |__temp_1, z| + x, y = __temp_1 + end + TO + end + + it "normalizes unpacking with single expression body" do + assert_normalize <<-FROM, <<-TO + foo do |(x, y), z| + z + end + FROM + foo do |__temp_1, z| + x, y = __temp_1 + z + end + TO + end + + it "normalizes unpacking with multiple body expressions" do + assert_normalize <<-FROM, <<-TO + foo do |(x, y), z| + x + y + z + end + FROM + foo do |__temp_1, z| + x, y = __temp_1 + x + y + z + end + TO + end + + it "normalizes unpacking with underscore" do + assert_normalize <<-FROM, <<-TO + foo do |(x, _), z| + end + FROM + foo do |__temp_1, z| + x, _ = __temp_1 + end + TO + end + + it "normalizes nested unpacking" do + assert_normalize <<-FROM, <<-TO + foo do |(a, (b, c))| + 1 + end + FROM + foo do |__temp_1| + a, __temp_2 = __temp_1 + b, c = __temp_2 + 1 + end + TO + end + + it "normalizes multiple nested unpackings" do + assert_normalize <<-FROM, <<-TO + foo do |(a, (b, (c, (d, e)), f))| + 1 + end + FROM + foo do |__temp_1| + a, __temp_2 = __temp_1 + b, __temp_3, f = __temp_2 + c, __temp_4 = __temp_3 + d, e = __temp_4 + 1 + end + TO + end + + it "normalizes unpacking with splat" do + assert_normalize <<-FROM, <<-TO + foo do |(x, *y, z)| + end + FROM + foo do |__temp_1| + x, *y, z = __temp_1 + end + TO + end +end diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 003129ecba54..b5a4d51a2c3f 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -770,28 +770,68 @@ module Crystal assert_syntax_error "foo(&block) {}" it_parses "foo { |a, (b, c), (d, e)| a; b; c; d; e }", Call.new(nil, "foo", - block: Block.new(["a".var, "__arg0".var, "__arg1".var], + block: Block.new( + ["a".var, "".var, "".var], Expressions.new([ - Assign.new("b".var, Call.new("__arg0".var, "[]", 0.int32)), - Assign.new("c".var, Call.new("__arg0".var, "[]", 1.int32)), - Assign.new("d".var, Call.new("__arg1".var, "[]", 0.int32)), - Assign.new("e".var, Call.new("__arg1".var, "[]", 1.int32)), - "a".var, "b".var, "c".var, "d".var, "e".var, - ] of ASTNode))) + "a".var, + "b".var, + "c".var, + "d".var, + "e".var, + ] of ASTNode), + unpacks: { + 1 => Expressions.new(["b".var, "c".var] of ASTNode), + 2 => Expressions.new(["d".var, "e".var] of ASTNode), + }, + ), + ) it_parses "foo { |(_, c)| c }", Call.new(nil, "foo", - block: Block.new(["__arg0".var], - Expressions.new([ - Assign.new("c".var, Call.new("__arg0".var, "[]", 1.int32)), - "c".var, - ] of ASTNode))) + block: Block.new(["".var], + "c".var, + unpacks: {0 => Expressions.new([Underscore.new, "c".var] of ASTNode)}, + ) + ) it_parses "foo { |(_, c, )| c }", Call.new(nil, "foo", - block: Block.new(["__arg0".var], - Expressions.new([ - Assign.new("c".var, Call.new("__arg0".var, "[]", 1.int32)), - "c".var, - ] of ASTNode))) + block: Block.new(["".var], + "c".var, + unpacks: {0 => Expressions.new([Underscore.new, "c".var] of ASTNode)}, + ) + ) + + it_parses "foo { |(a, (b, (c, d)))| }", Call.new(nil, "foo", + block: Block.new( + ["".var], + Nop.new, + unpacks: { + 0 => Expressions.new([ + "a".var, + Expressions.new([ + "b".var, + Expressions.new([ + "c".var, + "d".var, + ] of ASTNode), + ]), + ]), + }, + ), + ) + + it_parses "foo { |(a, *b, c)| }", Call.new(nil, "foo", + block: Block.new( + ["".var], + Nop.new, + unpacks: { + 0 => Expressions.new([ + "a".var, + Splat.new("b".var), + "c".var, + ]), + }, + ), + ) assert_syntax_error "foo { |a b| }", "expecting ',' or '|', not b" assert_syntax_error "foo { |(a b)| }", "expecting ',' or ')', not b" diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index 849ad1e12b1e..b2a62cf3e5c6 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -219,6 +219,16 @@ describe "ASTNode#to_s" do expect_to_s "->::foo(Int32, String)" expect_to_s "->::Foo::Bar.foo" expect_to_s "yield(1)" + expect_to_s "foo { |(x, y)| x }", <<-CODE + foo do |(x, y)| + x + end + CODE + expect_to_s "foo { |(x, (y, z))| x }", <<-CODE + foo do |(x, (y, z))| + x + end + CODE expect_to_s "def foo\n yield\nend", "def foo(&)\n yield\nend" expect_to_s "def foo(x)\n yield\nend", "def foo(x, &)\n yield\nend" expect_to_s "def foo(**x)\n yield\nend", "def foo(**x, &)\n yield\nend" diff --git a/spec/compiler/semantic/block_spec.cr b/spec/compiler/semantic/block_spec.cr index 98ab54449665..998b5ccc8918 100644 --- a/spec/compiler/semantic/block_spec.cr +++ b/spec/compiler/semantic/block_spec.cr @@ -1489,6 +1489,18 @@ describe "Block inference" do CRYSTAL end + it "unpacks block argument" do + assert_type(%( + def foo + yield({1, 'a'}) + end + + foo do |(x, y)| + {x, y} + end + )) { tuple_of([int32, char]) } + end + it "correctly types unpacked tuple block arg after block (#3339)" do assert_type(%( def foo diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 5f81822de0b6..2f84fea5d81c 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -1014,7 +1014,6 @@ module Crystal before_block_vars = node.vars.try(&.dup) || MetaVars.new - arg_counter = 0 body_exps = node.body.as?(Expressions).try(&.expressions) # Variables that we don't want to get their type merged @@ -1025,28 +1024,32 @@ module Crystal ignored_vars_after_block = nil meta_vars = @meta_vars.dup - node.args.each do |arg| - # The parser generates __argN block arguments for tuple unpacking, - # and they need a special treatment because they shouldn't override - # local variables. So we search the unpacked vars in the body. - if arg.name.starts_with?("__arg") && body_exps - ignored_vars_after_block = node.args.dup - - while arg_counter < body_exps.size && - (assign = body_exps[arg_counter]).is_a?(Assign) && - (target = assign.target).is_a?(Var) && - (call = assign.value).is_a?(Call) && - (call_var = call.obj).is_a?(Var) && - call_var.name == arg.name - bind_block_var(node, target, meta_vars, before_block_vars) - ignored_vars_after_block << Var.new(target.name) - arg_counter += 1 - end - end + node.args.each do |arg| bind_block_var(node, arg, meta_vars, before_block_vars) end + # If the block has unpacking, like: + # + # do |(x, y)| + # ... + # end + # + # it was transformed to unpack the block vars inside the body: + # + # do |__temp_1| + # x, y = __temp_1 + # ... + # end + # + # We need to treat these variables as block arguments (so they don't override existing local variables). + if unpacks = node.unpacks + ignored_vars_after_block = node.args.dup + unpacks.each_value do |unpack| + handle_unpacked_block_argument(node, unpack, meta_vars, before_block_vars, ignored_vars_after_block) + end + end + @block_nest += 1 block_visitor = MainVisitor.new(program, before_block_vars, @typed_def, meta_vars) @@ -1096,6 +1099,22 @@ module Crystal false end + def handle_unpacked_block_argument(node, arg, meta_vars, before_block_vars, ignored_vars_after_block) + case arg + when Var + bind_block_var(node, arg, meta_vars, before_block_vars) + ignored_vars_after_block << Var.new(arg.name) + when Underscore + # Nothing + when Splat + handle_unpacked_block_argument(node, arg.exp, meta_vars, before_block_vars, ignored_vars_after_block) + when Expressions + arg.expressions.each do |exp| + handle_unpacked_block_argument(node, exp, meta_vars, before_block_vars, ignored_vars_after_block) + end + end + end + def bind_block_var(node, target, meta_vars, before_block_vars) meta_var = new_meta_var(target.name, context: node) meta_var.bind_to(target) diff --git a/src/compiler/crystal/semantic/normalizer.cr b/src/compiler/crystal/semantic/normalizer.cr index f2652b326c3f..6d39000c15dd 100644 --- a/src/compiler/crystal/semantic/normalizer.cr +++ b/src/compiler/crystal/semantic/normalizer.cr @@ -428,5 +428,81 @@ module Crystal super end + + # Turn block argument unpacking to multi assigns at the beginning + # of a block. + # + # So this: + # + # foo do |(x, y), z| + # x + y + z + # end + # + # is transformed to: + # + # foo do |__temp_1, z| + # x, y = __temp_1 + # x + y + z + # end + def transform(node : Block) + node = super + + unpacks = node.unpacks + return node unless unpacks + + extra_expressions = [] of ASTNode + next_unpacks = [] of {String, Expressions} + + unpacks.each do |index, expressions| + temp_name = program.new_temp_var_name + node.args[index] = Var.new(temp_name).at(node.args[index]) + + extra_expressions << block_unpack_multiassign(temp_name, expressions, next_unpacks) + end + + if next_unpacks + while next_unpack = next_unpacks.shift? + var_name, expressions = next_unpack + + extra_expressions << block_unpack_multiassign(var_name, expressions, next_unpacks) + end + end + + body = node.body + case body + when Nop + node.body = Expressions.new(extra_expressions).at(node.body) + when Expressions + body.expressions = extra_expressions + body.expressions + else + extra_expressions << node.body + node.body = Expressions.new(extra_expressions).at(node.body) + end + + node + end + + private def block_unpack_multiassign(var_name, expressions, next_unpacks) + targets = expressions.expressions.map do |exp| + case exp + when Var + exp + when Underscore + exp + when Splat + exp + when Expressions + next_temp_name = program.new_temp_var_name + + next_unpacks << {next_temp_name, exp} + + Var.new(next_temp_name).at(exp) + else + raise "BUG: unexpected block var #{exp} (#{exp.class})" + end + end + values = [Var.new(var_name).at(expressions)] of ASTNode + MultiAssign.new(targets, values).at(expressions) + end end end diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 925749aee50a..f94685f1f597 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -600,20 +600,26 @@ module Crystal property call : Call? property splat_index : Int32? - def initialize(@args = [] of Var, body = nil, @splat_index = nil) + # When a block argument unpacks, the corresponding Var will + # have an empty name, and `unpacks` will have the unpacked + # Expressions in that index. + property unpacks : Hash(Int32, Expressions)? + + def initialize(@args = [] of Var, body = nil, @splat_index = nil, @unpacks = nil) @body = Expressions.from body end def accept_children(visitor) @args.each &.accept visitor @body.accept visitor + @unpacks.try &.each_value &.accept visitor end def clone_without_location - Block.new(@args.clone, @body.clone, @splat_index) + Block.new(@args.clone, @body.clone, @splat_index, @unpacks.clone) end - def_equals_and_hash args, body, splat_index + def_equals_and_hash args, body, splat_index, unpacks end # A method call. @@ -877,15 +883,11 @@ module Crystal @end_location || @values.last.end_location end - def ==(other : self) - other.targets == targets && other.values == values - end - def clone_without_location MultiAssign.new(@targets.clone, @values.clone) end - def_hash @targets, @values + def_equals_and_hash @targets, @values end # An instance variable. diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 16ff0c48876f..f2c6bd8ed867 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3811,7 +3811,7 @@ module Crystal warnings.add_warning_at @token.location, "space required before colon in type restriction (run `crystal tool format` to fix this)" end - block_param = parse_block_param(extra_assigns, annotations) + block_param = parse_def_block_param(extra_assigns, annotations) skip_space_or_newline # When block_param.name is empty, this is an anonymous parameter. # An anonymous parameter should not conflict other parameters names. @@ -3948,7 +3948,7 @@ module Crystal ArgExtras.new(nil, !!default_value, splat, !!double_splat) end - def parse_block_param(extra_assigns, annotations) + def parse_def_block_param(extra_assigns, annotations : Array(Annotation)?) name_location = @token.location if @token.type.op_rparen? || @token.type.newline? || @token.type.op_colon? @@ -4433,136 +4433,158 @@ module Crystal def parse_block2(&) location = @token.location + block_params, splat_index, unpacks = parse_block_params(location) + with_lexical_var_scope do + push_vars block_params + + unpacks.try &.each do |index, expressions| + push_block_vars(expressions) + end + + block_body = parse_expressions + block_body, end_location = yield block_body + + Block.new(block_params, block_body, splat_index, unpacks).at(location).at_end(end_location) + end + end + + def parse_block_params(location) block_params = [] of Var all_names = [] of String - extra_assigns = nil block_body = nil param_index = 0 splat_index = nil + unpacks = nil slash_is_regex! next_token_skip_space if @token.type.op_bar? next_token_skip_space_or_newline while true - if @token.type.op_star? - if splat_index - raise "splat block parameter already specified", @token - end - splat_index = param_index - next_token + var, found_splat, unpack_expressions = parse_block_param( + found_splat: !!splat_index, + all_names: all_names, + ) + splat_index ||= param_index if found_splat + block_params << var + + if unpack_expressions + unpacks ||= {} of Int32 => Expressions + unpacks[param_index] = Expressions.new(unpack_expressions) end + next_token_skip_space_or_newline + case @token.type - when .ident? - if @token.keyword? && invalid_internal_name?(@token.value) - raise "cannot use '#{@token}' as a block parameter name", @token - end + when .op_comma? + next_token_skip_space_or_newline + break if @token.type.op_bar? + when .op_bar? + break + else + raise "expecting ',' or '|', not #{@token}", @token + end - param_name = @token.value.to_s + param_index += 1 + end + next_token_skip_statement_end + else + skip_statement_end + end - if all_names.includes?(param_name) - raise "duplicated block parameter name: #{param_name}", @token - end - all_names << param_name - when .underscore? - param_name = "_" - when .op_lparen? - block_param_name = temp_arg_name + {block_params, splat_index, unpacks} + end - next_token_skip_space_or_newline + def parse_block_param(found_splat, all_names : Array(String)) + if @token.type.op_star? + if found_splat + raise "splat block parameter already specified", @token + end + found_splat = true + next_token + end - i = 0 - while true - case @token.type - when .ident? - if @token.keyword? && invalid_internal_name?(@token.value) - raise "cannot use '#{@token}' as a block parameter name", @token - end + location = @token.location - sub_param_name = @token.value.to_s + case @token.type + when .ident? + if @token.keyword? && invalid_internal_name?(@token.value) + raise "cannot use '#{@token}' as a block parameter name", @token + end - if all_names.includes?(sub_param_name) - raise "duplicated block parameter name: #{sub_param_name}", @token - end - all_names << sub_param_name - when .underscore? - sub_param_name = "_" - else - raise "expecting block parameter name, not #{@token.type}", @token - end + param_name = @token.value.to_s - push_var_name sub_param_name - location = @token.location + if all_names.includes?(param_name) + raise "duplicated block parameter name: #{param_name}", @token + end + all_names << param_name + when .underscore? + param_name = "_" + when .op_lparen? + next_token_skip_space_or_newline - unless sub_param_name == "_" - extra_assigns ||= [] of ASTNode - extra_assigns << Assign.new( - Var.new(sub_param_name).at(location), - Call.new(Var.new(block_param_name).at(location), "[]", NumberLiteral.new(i)).at(location) - ).at(location) - end + unpack_expressions = [] of ASTNode + found_splat_in_nested_expression = false - next_token_skip_space_or_newline - case @token.type - when .op_comma? - next_token_skip_space_or_newline - break if @token.type.op_rparen? - when .op_rparen? - break - else - raise "expecting ',' or ')', not #{@token}", @token - end + while true + sub_location = @token.location + sub_var, new_found_splat_in_nested_expression, sub_unpack_expressions = parse_block_param( + found_splat: found_splat_in_nested_expression, + all_names: all_names, + ) - i += 1 + unpack_expression = + if sub_unpack_expressions + Expressions.new(sub_unpack_expressions).at(sub_location) + elsif sub_var.name == "_" + Underscore.new.at(sub_location) + else + sub_var end - param_name = block_param_name - else - raise "expecting block parameter name, not #{@token.type}", @token + if new_found_splat_in_nested_expression && !found_splat_in_nested_expression + unpack_expression = Splat.new(unpack_expression).at(sub_location) end + found_splat_in_nested_expression = new_found_splat_in_nested_expression - var = Var.new(param_name).at(@token.location) - block_params << var + unpack_expressions << unpack_expression next_token_skip_space_or_newline - case @token.type when .op_comma? next_token_skip_space_or_newline - break if @token.type.op_bar? - when .op_bar? + break if @token.type.op_rparen? + when .op_rparen? break else - raise "expecting ',' or '|', not #{@token}", @token + raise "expecting ',' or ')', not #{@token}", @token end - - param_index += 1 end - next_token_skip_statement_end + + param_name = "" else - skip_statement_end + raise "expecting block parameter name, not #{@token.type}", @token end - with_lexical_var_scope do - push_vars block_params - - block_body = parse_expressions + var = Var.new(param_name).at(location) + {var, found_splat, unpack_expressions} + end - if extra_assigns - exps = [] of ASTNode - exps.concat extra_assigns - if block_body.is_a?(Expressions) - exps.concat block_body.expressions - else - exps.push block_body - end - block_body = Expressions.from(exps).at(block_body) + def push_block_vars(node) + case node + when Expressions + node.expressions.each do |expression| + push_block_vars(expression) end - - block_body, end_location = yield block_body - Block.new(block_params, block_body, splat_index).at(location).at_end(end_location) + when Var + push_var node + when Underscore + # Nothing to do + when Splat + push_block_vars(node.exp) + else + raise "BUG: unxpected block var: #{node} (#{node.class})" end end diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 006e73778a45..4a729599b6d3 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -1034,7 +1034,15 @@ module Crystal node.args.each_with_index do |arg, i| @str << ", " if i > 0 @str << '*' if i == node.splat_index - arg.accept self + + if arg.name == "" + # This is an unpack + unpack = node.unpacks.not_nil![i] + + visit_unpack(unpack) + else + arg.accept self + end end @str << '|' end @@ -1048,6 +1056,19 @@ module Crystal false end + def visit_unpack(node) + case node + when Expressions + @str << "(" + node.expressions.join(@str, ", ") do |exp| + visit_unpack exp + end + @str << ")" + else + node.accept self + end + end + def visit(node : Include) @str << "include " node.name.accept self diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 540487758842..32168af580e9 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -3217,63 +3217,58 @@ module Crystal def format_block_args(args, node) return node.body if args.empty? - to_skip = 0 - write_token " ", :OP_BAR skip_space_or_newline - args.each_with_index do |arg, i| - if @token.type.op_star? - write_token :OP_STAR - end + args.each_index do |i| + format_block_arg - if @token.type.op_lparen? - write "(" + if @token.type.op_comma? next_token_skip_space_or_newline + write ", " unless last?(i, args) + end + end + skip_space_or_newline + write_token :OP_BAR + skip_space - while true - case @token.type - when .ident? - underscore = false - when .underscore? - underscore = true - else - raise "expecting block parameter name, not #{@token.type}" - end + node.body + end - write(underscore ? "_" : @token.value) + def format_block_arg + if @token.type.op_star? + write_token :OP_STAR + end - unless underscore - to_skip += 1 - end + case @token.type + when .op_lparen? + write "(" + next_token_skip_space_or_newline + while true + format_block_arg + if @token.type.op_comma? next_token_skip_space_or_newline - if @token.type.op_comma? - next_token_skip_space_or_newline - end - - if @token.type.op_rparen? - next_token - write ")" - break - else - write ", " - end end - else - accept arg - end - skip_space_or_newline - if @token.type.op_comma? - next_token_skip_space_or_newline - write ", " unless last?(i, args) + if @token.type.op_rparen? + next_token + write ")" + break + else + write ", " + end end + when .ident? + write @token.value + next_token + when .underscore? + write("_") + next_token + else + raise "BUG: unexpected token #{@token.type}" end - skip_space_or_newline - write_token :OP_BAR - skip_space - remove_to_skip node, to_skip + skip_space_or_newline end def remove_to_skip(node, to_skip) From 01c8c45a113398d0b274a4d4cc921011fdb1ed5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20H=C3=B6rberg?= Date: Tue, 25 Jul 2023 11:03:18 +0200 Subject: [PATCH 0647/1551] Allow OpenSSL clients to choose cipher (#13695) --- spec/std/openssl/ssl/context_spec.cr | 1 - src/openssl/ssl/context.cr | 1 - 2 files changed, 2 deletions(-) diff --git a/spec/std/openssl/ssl/context_spec.cr b/spec/std/openssl/ssl/context_spec.cr index 2dbf91d6535f..74c79411c82a 100644 --- a/spec/std/openssl/ssl/context_spec.cr +++ b/spec/std/openssl/ssl/context_spec.cr @@ -32,7 +32,6 @@ describe OpenSSL::SSL::Context do (context.options & OpenSSL::SSL::Options::NO_SESSION_RESUMPTION_ON_RENEGOTIATION).should eq(OpenSSL::SSL::Options::NO_SESSION_RESUMPTION_ON_RENEGOTIATION) (context.options & OpenSSL::SSL::Options::SINGLE_ECDH_USE).should eq(OpenSSL::SSL::Options::SINGLE_ECDH_USE) (context.options & OpenSSL::SSL::Options::SINGLE_DH_USE).should eq(OpenSSL::SSL::Options::SINGLE_DH_USE) - (context.options & OpenSSL::SSL::Options::CIPHER_SERVER_PREFERENCE).should eq(OpenSSL::SSL::Options::CIPHER_SERVER_PREFERENCE) {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 %} (context.options & OpenSSL::SSL::Options::NO_RENEGOTIATION).should eq(OpenSSL::SSL::Options::NO_RENEGOTIATION) {% end %} diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr index 45c4bc102fef..a1f443057d9b 100644 --- a/src/openssl/ssl/context.cr +++ b/src/openssl/ssl/context.cr @@ -133,7 +133,6 @@ abstract class OpenSSL::SSL::Context def initialize(method : LibSSL::SSLMethod = Context.default_method) super(method) - add_options(OpenSSL::SSL::Options::CIPHER_SERVER_PREFERENCE) {% if LibSSL.has_method?(:x509_verify_param_lookup) %} self.default_verify_param = "ssl_client" {% end %} From ce82727494446ef2a014e6b98d2757ec0cac1f18 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 25 Jul 2023 22:55:32 +0800 Subject: [PATCH 0648/1551] Use `Fiber.inactive` inside `Fiber#run`'s `ensure` block (#13701) --- src/fiber.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fiber.cr b/src/fiber.cr index c0bf3ae4292d..f89489c2bd13 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -162,7 +162,7 @@ class Fiber {% end %} # Remove the current fiber from the linked list - Fiber.fibers.delete(self) + Fiber.inactive(self) # Delete the resume event if it was used by `yield` or `sleep` @resume_event.try &.free From ffa176f5074dbc5ccc3d377fefdd0f22ede82a50 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 25 Jul 2023 22:55:49 +0800 Subject: [PATCH 0649/1551] Update `OpenSSL::SSL::Context` default ciphers (#13667) --- scripts/generate_ssl_server_defaults.cr | 45 +++++++++++++++++-------- src/openssl/ssl/defaults.cr | 2 +- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/scripts/generate_ssl_server_defaults.cr b/scripts/generate_ssl_server_defaults.cr index bab12ad2bb0e..7383faefd2b3 100755 --- a/scripts/generate_ssl_server_defaults.cr +++ b/scripts/generate_ssl_server_defaults.cr @@ -7,36 +7,53 @@ require "http" require "json" +struct Configuration + include JSON::Serializable + + getter oldest_clients : Array(String) + getter ciphersuites : Array(String) + @[JSON::Field(root: "openssl")] + getter ciphers : Array(String) +end + +struct Guidelines + include JSON::Serializable + + @[JSON::Field(converter: String::RawConverter)] + getter version : String + getter href : String + getter configurations : Hash(String, Configuration) +end + url = ARGV.shift? || "https://ssl-config.mozilla.org/guidelines/latest.json" -DEFAULTS_FILE = "src/openssl/ssl/defaults.cr" +DEFAULTS_FILE = File.expand_path("../src/openssl/ssl/defaults.cr", __DIR__) -json = JSON.parse(HTTP::Client.get(url).body) +guidelines = Guidelines.from_json(HTTP::Client.get(url).body) +disabled_ciphers = %w(!RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS) File.open(DEFAULTS_FILE, "w") do |file| - file.print <<-CRYSTAL - # THIS FILE WAS AUTOMATICALLY GENERATED BY script/ssl_server_defaults.cr + file.puts <<-CRYSTAL + # THIS FILE WAS AUTOMATICALLY GENERATED BY scripts/#{File.basename(__FILE__)} # on #{Time.utc}. abstract class OpenSSL::SSL::Context CRYSTAL - configuration = json["configurations"].as_h.each do |level, configuration| - clients = configuration["oldest_clients"].as_a - ciphersuites = configuration["ciphersuites"].as_a - ciphers = configuration["ciphers"]["openssl"].as_a - disabled_ciphers = %w(!RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS) + guidelines.configurations.join(file, '\n') do |(level, configuration)| + clients = configuration.oldest_clients + ciphersuites = configuration.ciphersuites + ciphers = configuration.ciphers all_ciphers = ciphersuites + ciphers + disabled_ciphers file.puts <<-CRYSTAL - # The list of secure ciphers on **#{level}** compatibility level as per Mozilla # recommendations. # # The oldest clients supported by this configuration are: # * #{clients.join("\n # * ")} # - # This list represents version #{json["version"]} of the #{level} configuration - # available at #{json["href"]}. + # This list represents version #{guidelines.version} of the #{level} configuration + # available at #{guidelines.href}. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. CIPHERS_#{level.upcase} = "#{all_ciphers.join(":")}" @@ -47,8 +64,8 @@ File.open(DEFAULTS_FILE, "w") do |file| # The oldest clients supported by this configuration are: # * #{clients.join("\n # * ")} # - # This list represents version #{json["version"]} of the #{level} configuration - # available at #{json["href"]}. + # This list represents version #{guidelines.version} of the #{level} configuration + # available at #{guidelines.href}. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. CIPHER_SUITES_#{level.upcase} = "#{ciphersuites.join(":")}" diff --git a/src/openssl/ssl/defaults.cr b/src/openssl/ssl/defaults.cr index 6cccb707faa1..0803a1107f46 100644 --- a/src/openssl/ssl/defaults.cr +++ b/src/openssl/ssl/defaults.cr @@ -1,4 +1,4 @@ -# THIS FILE WAS AUTOMATICALLY GENERATED BY script/ssl_server_defaults.cr +# THIS FILE WAS AUTOMATICALLY GENERATED BY scripts/generate_ssl_server_defaults.cr # on 2023-07-21 10:32:46 UTC. abstract class OpenSSL::SSL::Context From 0efe3c1751b1b17c597dae68dd43582351a19d0b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 25 Jul 2023 22:56:10 +0800 Subject: [PATCH 0650/1551] Add support for Android's system timezone database (#13666) --- spec/std/data/android_tzdata | Bin 0 -> 481827 bytes spec/std/time/format_spec.cr | 2 +- spec/std/time/location_spec.cr | 44 ++++++++++-- spec/std/time/spec_helper.cr | 34 --------- spec/std/time/time_spec.cr | 2 +- spec/support/time.cr | 36 ++++++++++ src/crystal/system/unix/time.cr | 66 +++++++++++++++--- .../c/sys/system_properties.cr | 12 ++++ src/time/location.cr | 6 ++ src/time/location/loader.cr | 66 +++++++++++++++++- 10 files changed, 215 insertions(+), 53 deletions(-) create mode 100644 spec/std/data/android_tzdata delete mode 100644 spec/std/time/spec_helper.cr create mode 100644 src/lib_c/aarch64-android/c/sys/system_properties.cr diff --git a/spec/std/data/android_tzdata b/spec/std/data/android_tzdata new file mode 100644 index 0000000000000000000000000000000000000000..2034b6c3eff9b71ebc47d3eb4aaa7db3662d4ca3 GIT binary patch literal 481827 zcmeF431Aat`v0eGT5gPrT!Karxgp$#h=FoLq0j;akv6oU4U{INhZGUwu^y|d@mNn* zgSYEJR8;W5c;X2aJir6vi6_RJMU?;ZdDEszo0*nX*Zu9^7n;vxGEd%j-uHRF^Ugc( zOv+Xk*vsq#tpf+zHA(F?ZADOPGQaDX#U5wAz5kd+&VnWO5ChDB2!#@4}Ddr4ur)6k}mM?U_XO{(i!Y?hQ48``K3tLdNk;~&M(RDU4sOLMAL=6nRI)Tj;SCPG> z#8E14TBr(9|K)^!vhD}gSMSf1a(!W|Ht&ACXw_{oHdF1(+znKAz4n<}WcQT0%5_{o z<<#@Vy7@Q@?Zrl3kf08xI!j86B!;TXo(T2)V4A(qWvtd(eH-T@kHb(x64J(~=1fD`YF~&XO!Ee z;#}p0E`yqruZp$Fbcr1Fo$jj8_l0_8%d)$2vyEfOdYR<&3&pOk>ujeSwU?F|7pzX% zvOP|>%UE~@_(ZFg$L3RB;%q=e1}BNly)ia(oh1cDF2_<<>7=3eXdaC1F zU^g<5Ek|B1{e!`AHYL)c9($p_`lO5BG)s@Skv?Cs!67wyaW~mMaxAGSTS=L-u-w2$ z6WK_)%I!`A&(3Hrb)eiaS_hs&hh$Y^??1NOQR0&Rxzpo_tNqN?nr4cxgQ!iqoL5L^ zyIv0~Tk2ZZ#*q5?x|xY0_SBAIBj-Rdeh}TC zY4+S%PS=u{Qn8(;*$0ZvSeEM7TZW^gz_qfTiRO@c)~f@_BzK}+I?Z*Cmd@MtZKU6q zuxq;PZT1+_Go5d#UkBOc`Q^*%XQQn=M{F_^ z+su}ugFV&(stdUKRk5k#Zw>UU62(Gkc}c$0Rd3I$GZCEKRQmA;;>uR$Tt`sL!6DH*kRPO)U+$*oH5x}$Huu*Ul3)}Ym%jGalSK_6SKZq za)PGQMhV(}@y}1fq1_rX_y65C2PJ73?oTY}lqusb! zG=6?uK04~jHjz|cN7L=g7I~Zng$`-gL`tP;cZp6lJs$g-{Sh0tXom+!sQ%(Q*f2`d zQg4p8DakiHHPbeQL9D9)8rPC4!N)Y)3SO;k2l;-g!wdHv&Dx=Z3f4p#LJy-}m>UTGikg_wGiBYT-n zxGA&bQ&YrdQk+e>(_p-gEq6%0rJf&egYOvBb%NL)XD^YC#8NrotSI5T>Ze`fB(@)B zeT^TCv!7OKv^$#imh@labthtzn=WGyI(@(QWt_q!eT0~N93>TwgpJ8oq>mSi9$dsy zv}}$tkIPxcDxLJzHxk=%$C7cbWzG_V7S$5e+xUWIt`fsW-FLf`trLZ)Dcg9br@X}B z)(^U*q^G5>rC%OnQ_R4VblO}x6_I4hmd!(PHYKHUnq_dxoxFXgltgDl++Jy4E~ibbOq7!P z;nP3xQMBI5EA0j+KaxfNBjhD5)@G6ni!HXVU{&IjWR-HY%HzYDd?wi|8p!7)`NC%I zI2(s&nX6RJ0`+7uX~HK`Hh-+mUum*olII1<=lVo8GLRr=wE27#pOPwz z#6~o`UOx5PrN%O0`;=EW^=_TqA*V@c9^L9q$P+DwtXFglcI zNtZ}nC%qqMqjSd0Hjz~S+TkRjq`IL4Lq z_!!x#d$6zBA8g7K7OFb8q~KSQ&w_Xxk5kS9t2ao1g!|)>Q8DN8a=l(s(o<9Ao9gV3 zod3w>Dc#~FJv~UYswBQ_EA3@+E}WoLkyT00uM?ZNr&@_5u2UsP2`mET*_4!0P@(sJnN#RM-ubLHCHjx{L$D=>?pLF2bdTokL zg|on+7Z1tFO~vNpy7_n`1N#q0}}%h~TUBh?raRpdbNX(rB_Fj}{B25{jqwEu$>$iUw`M2D+sNhMB0Xo5Et9)X zl$W@E{B$|TD9V*f@G>&gf2^at*j~g})~4iMM@l}^SW%-paW?640IPdKm^>)g(1uEs zGpM-1wd7HE*0U)u*E{k=%9r4LVa5{av=kcEK}Lr7TU;GvijEo{ff9^Uh)rRM(^FPn zQdnD8u}jHW+oVnVv8txtGUejCoY>1OgxC#ZmXlBUS%Nh!&PJ}}lsFT2UzKHoaSASi z^|hDxjU2UNzQ1NMd7Etc**Mp9EzZqy%SDr#?P8fAe$>Ub+%fsO6AF?qI#K*h zMP#%NBp+Aq97l1HP9-Gge=`2T~(Zwv;{7f0IN_jOtpHf$Gd6|Ar$>+RLuWWKPW~`h)>s7UupiO7FO2w;n z+J*vY^~dbw%8InlWSN!ZNUZY-#zT~iCr&lgw?|)+FJ3PdA?FOWb&ZYFXs|Q+(q^JN z@71+&<*E^=L}!4K#kHSFoXUAqY(6gO_?Fl0iL*&0C9mBeF?Ta>YWyH8-zB4viz?;v zjPj4_Eo+f{V~(^>-1%u%k<;yQ@81F?CRuJJnfMW?)l4wk4lw<@pAejS`nFyU4Xn zM+|iLCvw`HII~rx~(g^3!?ZZ@cT*aLG*f3X7Kf!f4UAxZ^^$bBSCt)hU}+L4!Re@lD`W} z|9U##SQFbac2eSWJ9Ih+vSIR1Qr9iS7iw+h*_XUZO{ly=97zIp4|jFUME`f-}lex2C#tZP%w zko>sWo&+NsQf1q>%k~e~v60i~<>hifNqpQ&=^(n>^woMcMwdTQx?C^YZ>ejOpHVIs zn^>D5rClTK)9f#NGN!iPGVR53LYQE4wJN2nlsWaUb!=w2L`HRI_@?yuO16(27i#m# zl8LHCxtXrSF`l?=S(UES^aC~Jr)Vr9-X)%GFK2wSG$7C4SI!10Rejb;>OK0=+G3ZDaRG*%U9c*B?c# z-JYcs=D($U#9}0$WyaS|mCeB0Bz{h+wUMhhmAW3lW|GB-52fBBZK}$)yv%T1P}z(= zUks%`R#Ud+Wv)fyPDUwZtej`1e9I=0{V6Rflq-}53L=|Nm?h$)ny$V3m1&gn?Bv?K4j^wkn z!C84JnWAH+7x@lV2i%Duvt6R!nP?$pw&+1zJ{}o;(~lj#BV|simc1;oL^pMJUt}ki3-tp z;ybcGGLgX8fU*_79#;qHMK0$edvU43FrSuEe4Q9R5R;Ec$a1INsW#hbrqtQu*O9Sx z{2;wZPR-@iO#cX+z{XYVTBdt;uCmcm+?i4bsu5M2j82DYWfgw#0gI8c$^5bgTt{s7%I3Gk z*vMRk5@WSWIYW*m$upuh6J#i)u-s?@lay_ZT$5??Mbu`ZUH=+28pPPii=+q3SLmmdOUFq*m$8AWw@ET+Tjb2maF)~=K#Tc`lxrmxl0>tk zHYJ6*Q^mJqwyC?AvUZ3R@ctN^3L~jj<|+P9q#{m_+D!IH$Fj5meY;sM^qJnOwUMSN zS!675smt0(`$()-@6QzOf|^XM9>gCA@2aTUvD5BFe5?7_&W8kMLPl|?^9;(fU zo3msrV3{}&D-)%ZEmNefrF=DYP%f=w7;#dz=bjgvT1}iiAdxs=_~ zKQR3*&ZgA4$Z6FL-n;9}jDxVUbD($1{R{23@C8uW6`PDC)mh!dus8(CHnG)EB zjw4&w?UT=Bf@VqLIV&|;r+k|yhTrm5m2GC_%F1Gy3~J;FDL-aOT_;6tWH7$a)xd$< zG*aq1sb#H=v(jFmf5>LLda<2lH@=>yZ0a2Av+Q!3Rczc7N%`qtvOo7>uIeC*%SAE? z-{^pE(nRBv$JE+5T*e1&+0vB!h1jTGUL+sU_`)LNZeQI#QkICQ-7N#y(HiKGnp(-ZX0p^>)P{4W#N)4QL^7El zjO&kPFOtr3sc}Z8BPm-8%p+xU$oXjl{X|ncv&`RV8)GB42Uj>%2bGnHQr*5wb`iIm zCEBufo$0s}4nxlIggwrA>#h7|F+_-imrLXpR9)>!sdN09Eq8jCEV3J_kE#3T60EiTDzPb%(e5(6dAh1>b!|N5 z%a$9vfmXLKHoLT}?{xhkzDOPP5x+5g$g--pIZkm<5p1LQsjKmGiseSq3d7+y&19AF z#F+R|gUSsYwoF6fk6H83(T4oGj*iZANS9A0$;df_?rq|x6J=Z@<{V?5LvD6%Ab?D^ z@nRFv5GDu7@OA$&E4gr{sx{$@Y24E?2P2{(H8x{q(rLND(Zn=MuCp~;pxUR#CS49% zMh`PEow8HXv~;o5_4>H7&2m&OT_RTtmg?kVsys^C^sjL? zkrD5S4wqb?Np#FNopFO~e{;M|S?(mcKxuS&O18hEj*VTe)hE29$#$A)b-KK4RyHy` zA-y*_5ge1QP9)U^30_POJRoB=7uT_ox#4!1_>^FKR+-M0eoIq7i({hsjP*E64el>D zUARts=cc+gE;-iaPLlc5d{8%;{x(kXk@CgnBaXd@$x?MUu700zf4HWpb4>;_(=}}* zeulDGyFcUXp3@AvJf^?PF*`}(JJx1=dA`wkhUu!&V)J^u%_O-Y)>Fp1gehU!rcIih zaO3KgrklG-%t_tFmTj6-E}-gPGceuAx#nASY@`#LJ4vRIR)29FB+MtnQOrG4dIty7 zouWB){cU>I+&W(!_cmdhs+nAIo5j6J*glyu8ONZ%Y`XtPse?N4Gqc>`C@B^7E{Oa7 znl}mkS_wkU7WJ7hezGd1e`yfgiQ+WdF3(6RNvJvGn$UOVIA!vrN8ct<`^=Ra&g$<* zbWOr~lLK+SOCm@|;bLN2=IpF=7WX%pj8&ae%>12dn{f_t1H*}fG4lqiZN}RTcSiDx zneS3<(?A7?P0W1d^znv@8?82xYSHV+JiGL)?4jx78ewO8cDZO(8ehU8zCZ-N`fykK zy>*vj?tQ7YVdkUp$$iW<{%RZQ&Ee5I{*q_R+=yzM3>S0R3}jT(YVT{Swvic@i^Z%! zH{@dH;#AvY+GSdrGr#VR$Gnf3Pf=}?WiOR(Uw)C}G`((I%)GQ}n|b9+8|Yld%%_ks z^|uHXx79~$A*uF$rtyvTQO4hz zsOTS`)d>G2Gt=vGE~BZD7O@#JzEOVt#;G-aJ-)Egtul-jTyLTFSz&T7q|QOAj&16+ zj?(9oac@l{KXANcCp3~A8jLsMNF)d4iJGkv(CuFWu`?fJ~~p^R6>@6YfCZ9dAYYcrx@n^)`D z^liv>wE8~Uw;|WjOdm5w9_t5v8}`wUWsFNx!$#FU+P7gJ-Gi&d+ce~GragDmvuViT zOrIQE&!!=VGkx;+x;70JO*4HeV<_?U)=<&J##`5>p`wY+_jPR=j)l);Y$LvG4adS~ z@7A?xI2Jz7u4~h9EPVcaU7Lnu;R`v>irb$CL!n+Q$b$t+PIKlPoGnOf#~&AF8=q-Q zktCVExL;m9ED{T|v!x5zP^xQF!Zd%mh-_q6$7X(cv5HwElen^#dt7cu|1rzx^A+el zr&7Mn^rf6@wphsbj<2?n=P2o)pR=K9-`UcE*&1uZXoXy4(C=NE4$c(Azs1_f)I@^` z+p=Y9%5Lc+y&hvT)=|9Jxci@a#Sb#i+ZJzA=#h~mRwYiUEjLIX$;*e;bs!^l4O}lt zZ6l%FbbqXk+yLg3E?9A{HrS}IRjU{&Ij+PR;!k81mxvdL9sxkKFG zBq+7VO_I;~d|}k4!0t9O(NYKWmj0V9&ZbnZv*-@k$(Cs)f025NEt_ESoKn-Dko}n#V>7ONQA6z$(@%`}^+b8TCC~0svCDVWOR4i7mAdA5 zUsG=r<@O(WdPZfvO`|L7`X3d_6BYi|?IWiO4sLVi=U}nIuuWk7}D9H)G`@^O?{0|tMi%abX4eUT)jG#XOcCg zFH&nGkDW9$NUkM*zCgdCCZB0b#ChaF+qz+@)z<@=M_kZA zKK1HtTKNix%;?jm*K z85L(EEA9*IT?(JM`r=>oZC3WCsvD$YsT%|Jb z#0tHXx?_hJ9^~U{n}%m@q}0yasJ59=s(;5+BPX02w0`Qf3ED$Wh~a% zxC#noz|LT%W_^Fl`NR5Pg6}0r&)5FYnh>nq_uuT%60=q zsIG%7=d#6e4NM*%lBis)OFMGDR5<5AQJbYM_Y&jwsn@5-axK;!wdJcNpAPM$eLAyP zQ?@d>->%42yo_}TQ`<4~aVlXUhXEW!N_1){&KIJwy+iDw(OyCtEJF>5ceT+YD$W zHplZO#-`Mu)}+en9qVTo%ak8ig@KNwjgV7V)3-7C%wAa}@#8X3#kA2cnM|%;b|{b)%VlTnzy*~0hss7v&SHkQFnT`^-Y3-ZZXN_0eq|5n*{_|ikOsha+x3Czs zkz>6~T2}kW`g$qtG|}G3yyzMmm&_oow_WUtw9|IU+k03Qwc%kkvD?&JuD3r^9Ukc) z$n=AHo7SvITXCK&-oo4Jd@}4sx)B<&N~`Qs&!$*r{TR)6NvnKCY+~yz!!E&HW~6Xw zE78NR$uwHFN_jrJVe#tK!7PV7pxm%io_6{Ll0~Er8k{UgX(YwWnVVedvFkh`AhK=E zYs{Rv2A}s;V-xe-uPJu7;bEu7BzFGW)YI%slg4B> zWFcC0(>AH=Hf%UuhMSfci%0eM)jpdcb5Y8u4`=?csNknogYX(t@y?oo5&Smojh0_b3Hg}vsh*|8klHl4@f^gxg(oIZFtIp zOoP-Nu}XV#lWhK3tjz?uCBwDYz=_fxl6upgi?eYqaq?a_rER=W9H;}I*OYBSd5L|o z(T^adJ$kcjerv3aJe}X}Had2vJtp&>w7u~*%NEJ6m9Z*uO4~eKJ|M4X*?69++=VLL z3Y`E?+x)g{9$&U{Bgc`g(;klGM0Zt&K+>PFJemflGUiQ=PjpkJ>m3PBXk&ByC%TY`#9$X1a8E z)F3!36Q#8050lk0CQ_Tvbf@v~j+XX93)%d|I2)H|vE1Fps>CVnh1aE%E&daekNiND z!|0%&wsnHkn>x3sse??3H<=-y`1wP8d4f8Shf^4h`io6fH?iD+c{CqqS-$*kPzmc2 zrnD;h$}jL))J7iiV|Y(S+AHFtN&Vw(O61}PYjjdV8+j&KM&ubu39VwDA5v4cS$2<0 zf=+)FE^Ws}qSvSJeKj_6JCAYaFM)>00G!LHNfWhCw*1zGZd_BKwAc2?>n$wSlr38Z z(+r;KAvUke{={oyHv3;Dch&MmiBj4d1Eqb+_$Zo>r-5hfB{-hWwJ$YlQ7PL_v^q|& z=Q1#6bas}uYg%2Kd3F^}6gP2-Et@<1_2)C3zOSQT@BR%33nQr{4BuN6-|H@aLF%`xWMRHLrDV;FNye0Em#XQTp+QxWJC;7xY7gTH}Nl-iW4)M{wOsnmOh)s2|`ew=HQ~kh;d5*K#WXK4g zT#{(0u4{jP0h@;Bn^MA$7t1*Y+M|h0wT$r2AjLeBS!^1VQ1wkQ$GJw$^S~(9M;kNG zqmIqozFD(X-VMFPTmzfi=yUX4-^jIs<$QIc=992t%>HE0cPk`6%o*2BToJzR zjXv(Rk6!BjM{rX3`U`$>-{9{R_UGLaUgvEYzOmmC;hR=Y3*S8C%<#<{)7;Mmkl;o{Z-Rm@w7E>pMJdi)u~O~A8p;CeY+qKPG0duI3@kLaI0-^Xsz!a z7d~voe9c@m&wNBhy8Fn*W4SDBBRk*g({l2c;-nbK0_pD9h&HesQODNQPovkjgp zW0Kh{=E(2+XzGlT*G`tC!&$GjjeJVwX3F1OrugCx=`2L%u_w_cM%7-go~ZVv&tG>u zq`n|^rYQ9WsXIvhLFy1vkC3{A)F-4)A@vHWTSci~NF77!SyAd5Qs0m|htxZy?jiLL zX#+?*sG5_ufV2msO-Qs0q-{vF52TGC?F4Bn_(!!@b7{Q31N7WGMM}mWiEv}NlyP!Xt!LnP@rM8a>TV)=Hys_{8A7@(1|Y1$h3 zT9#XR<>k4{H-w(se|pvP2fhluu;r7g7hk+5@1=KpR=s@Jd!en*epj{alEtB_J1VPQ zIVUf){jYatelj#a@00KPW`5RUQ{HE<l*yenlYO^4wfBu$TKeJPCM{p{ zHa&j4yXoF$TJzKu;pW>$d0XuHIo$HzC%mnmAK-3%+4tIE|11cb%X)YZzwm1J5wjO) zN0xMUw;A@HI9O)5ZHG4AcK*x5?ec{{!4c6YvEuy)LY zH-(S&7J4mLz2QF2ex2534gdqUA`wn$}qzcY9fr z#1%VPv}T&dGJ!K5F|EGgBFW%@Q_r7?n=>BExD(>nisIW4|AzQD#LpqV4)J%0&qMqk z;`;UIDy0pBwipf1Bn|*>_FlN5<`$UQj}PN z#FL`L6eO-7u?2}QNQ^-hXDkqFB#Adb%t7J~5_=@#4-$hU;t&#xka$#-m?ROGiV~ZU z_=LnLBu*i*N+Mn%F$;-XNbHh`Uq}p-h+{}BL*iLcVwyx;D@trb;u{jUP-sMpD0Z)y^AL{jCi z<&`u4wf5PAZM<7@?s9M0^`&?Fx`E-hhTN>ZwYPitr*0nGPfvd3Zo2dwZ_{U&gf1r@ zv{OHFXI!<>JALp);TdcF{>-M2_%jDwYsoCU%|CP1S^k+jU-ZvPz1}~oxYVCDY^6Wz zp5y#kZ%?pfPn+$Z-EM(@_NtI&_MImGoIb<+bABA{pPO}_f9{!gS#plbw&c8F@z1k- z=bx98<)8QcP|N&>lKu04Tx&U{ThPB?Yr22Ifg;Po>jM6TI}Y>bd2%dy{#E`(CqH9Z zblUNjg0{u}f{YjajxXC-79Ty_U-drHnX^Q&Gauf2mGG(|FU@Y@A(g-d3w#w-Mu-^t3D}UqQ|B7l_Y5{6MTkE_d=lc95Z{FO zr=s|%qWCGqR~5xyAwH`pehcwkMe$!n@nMJ`Lws3L{8>?a8sgWA;@c4aRumtH__?C^ zI>g@<#pe~p?;*agDE<$L0Z1GuN-RL)K~Z7?5*LaR8<6-=lo)};iK4^`Bwipf1Bn|& zi5*D%C`t@L;z&_q2@+3=5>t@4Qk2+|ySAaSNBu?C4ZMTt2`+$l=zLE=wQVh|FC zkXVGoBP1pvaj7V=35icdiBWjwixy(k#b)0!4Vcd z_jQbv^U_V5)aRG(iIns5^G;CjFW>wZ#lN4r)uKuI`-R`A_gD2kQN6!vQKX#LB)zZJ zUo#?7&TDu6RegS4TBMw7Z~0NZU;9p^oY$|4`1$qsN6P8Xiukqv%t$$JwA`<@zcD9L z&YOOmufG50Zfn%~Tb|yc-rsT{QqJ41bE);WZI6`m4o_S4`5pd9Iqy9AZ1w)miby%{ zZrfXJe|JWtocDaOU46d3W2Bschfh}T17AnVd7p2O+Wx-BTd4IL7DnP{!`~z2d~onj zYW;)7k#cTqag*A9nYM6ZY*Jdty?TL0vaA!Ywm|42EX-g>@T|4eG6oX_6UUw!`Ujz~G5^Q=|xpSvSc z&KD++Q`^7bjg<4HV?S4)zcf8k&X>Q;QSY}NA1UXyr!}?xwr`GC>tDHanR@@qbCGiH zC~BkD@3mU<%iTAuE6Mw3>R0Z6?kUmke*U)bJ+}?i*869;1Lw8T?!928cAwko zJ*58FMLzSftIjlAiaqAz7UY||Ov^TR9W~y3e4nA_Zk@WDPiWcR+&wkj*Zup!z8)WS z^YwhKt*_TJO?FeyZ?|d@S93;#)bsQMcy$PI_ddZ}hoid?#Pm z$!A;K)i>sx5$3TIQhn(KN1MlW+v6Lb{P>hr#dUEeiN-21V4a>^6t$y=W@Px;^` z^VEC(X`c4dzB>;S2M^HQ$c9-i59ygEiw=-&>2EW=N`aSVxNNIXMg z8WPu#*oMS6B*r0e4vBS0yhCCh68DM{`;hoo6b*pr07MHQdH~S`h%P|10iq8OjezI` zL@OYA0nrSIZYYX&K=eaVGz6j}5G{e|2}DyMx&qM_h`vBH2BI?%t%2wbL~|gz1JNFc z{wRtDL39YBMG!rLXc9!1Ald}cCx}KtbPA$X5WRwE7DTro+6B=sMbR*bjwy`2lQF2@kbc%a83U}azwR-gYN$zBvViKEADt8qwRk1q z(;H9ua~!4d<0wfr2hrb-GB)n7z1kK%IJT_0=5`1}C!1=ToWm2%F7Gv(UhxozJpLTw zNcK~24DrD~hdBNm;=m979OC$Mhyy?PPYiMV4u_lH?!aaAO^rRM9rCCKHD8m`WPu(| zGq1dyHDO!POZQyleffHqd+Ws|-fgRAx+B#mqxUx1Ks%~ETSu?Zt84RFW8~Vr&Iu(p zH7S}EN7iMhkFi0rgk%fJ8j?Lcgd#MjAdS10)>X=sDu0JRc+;D2YuC36C9NEsnS92- zLMf*dR+;ivhf+`aqAJan7iw~>C9|nzY^a%*Rn=TOKGgiZ^DCU{iy>m0$_4r#}`<9ngb!h6! z>+t2XnH|5rFm!a))XYw=EDLqMcXicp?|4gIyN(S{zeq`zYtuJVozVtp#&YEJUw)-s zPW;npjC8q!ZM^(7qEVtXL1nAj;QOUU0JQ?C8A$CwY6wzGkeY(j7No`?wFapAZa(HqK3t@lt=kpPTaSf4XV4oBlqn(s(zOBZ4}H9C7ZQ?pH3# z%+s#?XZkDaW`4CINlQ)<)#$GpOKsDnDf56^o3+-66_b(WiuNF@Hn(wV70n5?A~#5m zkX+%R=iFSf*PrQGU3!Hic@-7bnw~Y`neXJyF&DAAlO-~_8r^M#=0?AS(&~OuTg7GqhC*P=dZj~O(q~4 z$xVL4tkGFDI2o1iBXMNrug!%AUpa8^-ZyhLYzb98@#Wn)FBPwRRYu-94t6<*ExYz$ zpX3PGT!w7#(ppEB*}vY_f#Oyi1Z8RPkj6AiG+XwQtRdM$iU27F{1ruME~V<*sNpAS zp1)R?ee5kbV)1hKktgiVlM0Xs>cs9#9Les>Omx+x#L(PE_Qj&#%)apLd>uK!#^quh zG&PmU_emL5dn*6D?Bk2Q^CzEgUeN0ixuTLHpQXt$SSBy#v#BImBl{&P!E%jCuy0(z zh`i`HZNfa+!G)uKwnldJVlQ7o{_xk7q0!!Kk&LK;!`FTD#PRPua<6%J*SquHTe02z z-X2Hjqy8tmKf2?k(C5>i4Y$2)Y+k#jKf2rP?;UFYSudG_oSWD2rS;*XANw$`(}tDd z&Ns9R9dmgL_pxW~&9k_t$Q0z&p)Rww$rR+2(D8$l!`-?K%R9mLPFb7ws`mwD^ z7Fkd`O|`S*ke!EA0HhKWr6M4ep(qsssT97Dih)#)qEry1k{}fYsVqo^K`ISWagfS` zR3M}hAr%R!Oh|=7Diu<(kjjNrFr<6DmbbcuUU)w{^y1yl@JoB9nqR)7o$uu*+iEYr6fkeSafqgFthwNWP~^s% zwt20#yc=$P!Cmgiy!npqE;o$~cOBIue0-k{;clIpht+)d)b8%FWv#;LqrB>(QKAdP z>Sh~LYV=?9Dr)rXS56hwfi!Zn4T7dbpjFLljl^+-P1F@d4@3|QoUvYo;8gP_9Qhrr zxvPfC;XM_kD3t`MC`e^NDhyI-ic)ckQhAUHgweZYq#}7=d$&y6JZTS&N`+J`texcC zQN5>rCU+?w}OS{uM?3JKz z_{V?qj=1+m-^gcO-V-l9$2V&IMcPTr7yCwEQlgza|7^3(m8p#x=`fF-F;GkIIMF<= ze;aMw{+{Lut$)xayxGD$ao-W%Nl$h6O@8A$@06RG`KCVfx_8=HpZnB|*hMew$D9*7S(|rfwt3#(#d63_ zNj~I>0wSMgbrg(@xrh==Cr8exA^f z230i~&^FlQZ_BEhnfC>o-Ti51^YEL&7NK2LEqDEBYZZK?s`aK_w!`k&nQ6Y}5!>Mx zJe+w%$>Cz>+wTd*t$*GX+7bnFKyj> zJ#6iMuvxHY^R?EVyFU;1`fRne_vV*_eRfq8B8y@EikVCqrvgxE(nbO_AA@OzPDN@?s&yExz+jB z$@kxHoATun>(sT|g413fiavp?PIH6Vb6N+o z4~z)T84?c6`Dl(U=h#-(oM(pH=Kb=Ob^a|KZKv#h#k$~}1GZB)-)~)5u-le<{ok#5 z8T*6w^X?5S>ic#uzwq)vL95L{$Hel0<4b?Au>0IV;mcIHuoh5tjmXXw|QKNpMB` z&4Cr0KMhvy^9NR5|6=g;oo5ABt-d>W#>Rrc>V=mC*IYdzAVQiH?MBpgC}Nhii=1UV$oA;As_cu3Gg0$)*rUr_`AA_5R0fQSJ^5Fnxe5eA4jKm-CJ z5)h$)hy_G2Afllt!T}KvMG+8)h(Lq{A|?<)frtu3SRmp85g3TbK!gS&HW0yqhz>+} zAmXDa0t68uh!8=<2qH)jQGy5)M4TW31raHTP(j2BB3Ka7QWW8Wh?k-W7(~PrMaUpx z1`#xfs6m7cB5n|YgNPhN=pbST5j=?KDT?qx#7|KK5F&z#B7_hzga{%;6d}S05l4tX zLPQcGln}9m2qr`{A;Jj}Pel<>h=?kRkV3>%Q3Mqtst{p?h$}>3AtDPAT8P*}1Q#N@ z5aET0uc8PrM1&PZh#_LED1rShcIi+mg=jVNG7q-IlU^T)<>c52nsMA&@qvTd>K1rhz7Z%MCU& ze->zV_gl8+;g*26Ej%XUQNCDtRI7u(v* zTW&pSvfI{nM3%K(&snziU0i_<%?8^#CeI9X?A^xP@td)Mqu=l5@3j5+K<6i$_>Z|S zEpY6*PyLpP5!U0*e9_-!&e7JcOFpq2Kjdd?w<#}JPB`jaYxiDvS-Su7Q=n(_i!D8O zzZ2;7ncLEP^W%X&J1+9~y?$Mw-~A>2{;SUpSTE1?4=D6o2bK=>51M$Eb#PW2|B&tl z)}aG`@DFP;!8*Ltk(S|mdRRyN^u1-|mgd$I-+IF`DsV#Jq(?ScM*EruPQLCsi_P^} zV9YrW`Nz(9DUe=pjelJKdjjLfo$eq1?R(aVeXq7m-0_rka;ue=$@kx4o$}=Z%ha_` z1g5>d!k@9~roi+E=lf?Y_)8%3%2EEAqY4AFDvt4Ib($Q=o}>A*5A+Vq8S=h=&PS81 zImaBdfd7pNG%)nBpuUx5|v1O648-wIUjyUf4x z`o{vN?=17LT75&{jE!^rs~4UbSabDo|9@Yf>FIsMUuhnFCg~MPpGjV&&m?Q4&m_x7 z@v3z^c<>T$(lKheD}5%$w#P`HX^ixlR9|TfouzrKol5s8#I2&lE+l>- zF${@gNGwC*84}ZwxQ4_wB)%ar4vBL}tV7}*67!I_hr~W4{uM<76h#LhS^&`lh$cXE z0iq2MeSl~LL?<9x0nrPHWu6di$R2}DmIngY=kh_*oV1)?z!oq=c# zL~kIP1JNCb_CWLpqCtwHLl7;3=n+JdAi4z6CWt;kGzy|q5Uqmf6-2Wjx&_fLMbR&a zhAE1UL9|R!^bDeD5M6_48${nA8VAuih}J>$4x)Jw-GgYKqUaw)0~JLFAzG*?dI-@( zh%Q335u%R}jfChVL@ObB3DHc5ZbGyZqMr~ARTLeCXsM#;DMV8hMOPu(3ei`H#zJ%! zqO}mcg=j8BcOlvf(O-xLD~b+7v{+H}7^2CFqRS9%hUhayqaivC(Q1fZ!)nd`Z|gc$ zi}s4&Rk~}nyQT+)sE%)0Tg$)Sxu+x(lp9&CMhcbVnQ z;rCeI{CvOl?GEn+-rh9N^3G33S$AKz+w$%^*IGl_gDl@&bujRK>O#v8!2;`#t&j2V zo7u;@Z_6{5{mr)r_TRqG|I-&~)&m!w;qUzX#nxk1yx~9AKO|row9S9qnNDk$7SH*+ z=9mLr-*;QO4f`h0?bR(7tGCiRpmTEl1~xr2dGuGapOp1#yNdV1KOdA7qk(=x)pMvgT4_d@D^A`hh6 zUPaq|5K5B6?rT*w52P9>52YGpbQPsuZE~!mMj^GTC^ZYIT}TZ>YFSZg8dBSeQsa>q?I5o#g5QgkQRfq8l>eQtp{mANGmEz zOF~*xQCbwzs*sk2v@WEDA+4+^Ee&aHMQL$Jt3z5I()th&fOrAK6CmCI@d$`lKs*Dk zk$u(mf#^+(^|olhJDQIY9~s@KDGQ^e5NYz)#w#y3UUrG~!P95i9_sjfVB;~D*fzfE zwm#hMjNro$+!T0Z-@Ud+uYAM$*sc$Qn^!asJpM>~TX6m*fhYd)58IO`K4pDsbye`` zPCo>m$urrW`K6b2%a~!oEur<+=gz#s_Cm(GzzciN4Q|aiXx$q4GPtAXkie_y1;Ou5 zYHI!Nh9838rxsg(7-+Wr@bL|SeMhDT_ifo>{nvM=+4k=k8u;m{A;ANWW?6r}YnJVo zwL`22FB)uXa?_MR)8k$Ku2(z|INp4PU&U;=!r%S4!>rw_a{N8DudO`;BmKQXJFLC0 znQKw8`gpIv#M7DuCceGKGHL&ffys~BEz~~OdK&jHX4KM@nxfvN%0sC{wGJM9s*29> zmY^n`v1dA*~2$Nl0r#S`^Z%kd}qCE~JGat*j_54QXvfX>mxaLt0)@S|8#8 zisA(jPk?v>#3K~ND!LfuYq_D#CxFfAQr@n#NjnO3F1u< zkAip=jOvGY7w_>fMe#C-rzwiJK|BuPbug+I;(ffw0~N&!A)W~FMz}_b_Q!bSpPF&d zmPj*fk9=*lz1AXFqaFWV3(4hVG$UDFMKhk1^WOi2W~}cc?)0Z-{MTv5K^w7-x50)q z0}o_7yb#vakU!2N|J01cno*+_zpkHAtr;kXXomDK{v(=kw>p0Q+0XcI>t`IAX4KV) z-=?4OTX^IlXvQcJyZ@bYlU{R!-QRyJ&|~jk6ZSJ|G~?eBW-%9XCS|Y{~dxZoGiQdzoQF`S-kv! zzsCtD*!0FNexq{`HDnPz33=l8Itht35Axty8~F1iMCyT|E$ZVBAGi=#LmqH9c&Gyy zzthRcZ{eDUpdit)HLp~`|M^kR4ufsoUw$gkV}D_=XJAsGSLi)k?5L+LaAaW2oYB8( z1Vasx#*AP*zUp`CV?|^}l;vpU;fuiAM5Kn`68^q(F-l!)Y$me(= z{Ehk?+@ujboc((q`TwnEG;pQlx9M@jtNP=e{zIBk@TX?{7j-!P%_}9pPe z|CEXU5}Ek5BN*a}ax~U(NBCQujQmEfd58+qL`vVrD=+`2dlo<6;_qHFf-&*vV9i`D zTdZPSe!xF*$E?7luPbbm?;jkP@=A_vjfCW(s=SP1L{1o)@Wn;dCthkuK_b(+xSQeM zn8JmQs769RJZsl)fPd=9AEYDVXwn`U&Zby1m?2Kb`?^!La6s1A(+P1#*5i;6r-V2r z#5v&_Y3o1KK@UMqDDeN*6%{oLH1T6&ov@^(X#@^6Ysw2crE zId{1k{wXGZkeKMs2R*cNm*^;m_@l&Nz0SyQI&b4w`sg9(Npuw7EBo|+x_8ouS>?7( z(|>~*qIFJf_We#O5^b4?irjntpDOY{FjguahXFB{u^ z?@R6drme|=aG?3NvS5p=UI?_j_g}$QD}J%IzPzvPu=xW5=CZut;RWe|BW7P=J95Hv zfi}ZC*pBLcz}mLMv|zht{R8dxdu{DM%eQuT>;7QJmp-u`y?J=B(>+HAI^R%iJLZxZ z)?>Xl1TF5f0>{~R*t%pj33MHoV>^EEiPmmoPPCnHR7s$F+T*tFKip`I%-E9as@on5 z_TJRY+UL$YZGGJ>1N|=A9qd12Z@^k|gl)jkBJ04p{epuIyDup~D!Ij?xPQ7MvaAC!(f!xYT!MxnBt@e4nf{RYxVa*@WGFWiz16D`-y+Oyp ztE`2|Eo_DFR$7bR`_kro{1oewC$`y^-hNu3c-_6WW#`Wel$?3F%~f<_z`bOBaQURp z0ne06gQeXM2FiMs2FnlcWUXjEJ6I9^#d_Lj!-6ZSLe|O`Is{h+p0u8R|4`d1-_6!D zE^lvJ?LODK#=LLw3pm5|7UL?SZi6CkOQ6z{eL6ixiP7sBHs1!u0AZi6sEQo4BlnbI>5Cwy%n4%~dM9m&$Y=DbL6L5LY4S=LC)*So`4|K8vFpUB<7Z+*yd>fgRsJX-%!6uDP?X(#yq`@Q1P zdp*S$aMb8jN%0BZ;};O$fcOW*M<9Lz@fG+--+PX~Q4V|#u8}tWJ^k(w`u?X%aTq9X zf6IfPxYvJE5_)j)oV6Wv0`fb_XL!f;zsxs&T&{P*VP(FF$B*$&+;^#YvZE%)zT+<8uqW?wxzyl8T^ zmcP&vF6cQ_bBs<7FFsaK-*L+KR)bhF834*DAm2 z8(#Tvx_0`u!@{d>%JrUc&)4DAXOHo&xp+soT2SOkod*Bp*42OO-@$|b$VQs};iC(E zr=J_-FY(fazSGYQ@|yxYag+6Qq%-7+Q0elQDSsLAh^Tb=Yea`S>ORO8E(CESh$}(d z3F1-n>ueJ6~sPz>WRULMf7BZKWR2^RNe&~qo%&H@E z59YP8j?PR$(qy(sO5~qBj^b!ENKV>CgCeK#k=L!y@u&{gtJ8QMN+-%A4^J6nl?TGf z@2^+C>S$2Hf}?CAIAv+7%Vfef3 zbaSbT!Fp{*t~KcxS*Y&4mzDjed+%$c`V4z9JS*p-U)4K~?jKDhPf{v_|LeWuHL{Pt zrd}K9{l$jPBJm&Icf5SzuD}g9oEY+tpSg40n$Dpc`<3L}wD4f)=GK`xw~T%-bj#j> zIk$EHCGWPaZF25t63V;d?jLggx#!8eJFhxo=UrQF&bzzfyPfykc5dGKIj`>woL7{0 z@6e4q?{l9My1(tUJ2zx54sG~h)y@a4lR^)^dri(pbFa|G$4}4M6mA)M_=Zz+9@*71 z@6k0U=RCHlMc(Gz<8mIq`pdlF$tgKctk{$A%N>qDV2U*){#@rK?%?vx(IeU(_=k0mow46_VO3(Xr-MpO7-s+b3`CrR-e(^|?yf2r`-MRPL zPxHQ-GGb@=oYc_Qy^h}bP5z$Hx6OXu`Ca<+q3=F@cjpgX?+pF0{XovXl)r`c-M2gE zUmv(a`!5UT{PfaAc?Uc<<}_LKR$h}$RhjMW%k$dbIjrHI46J(&nqo3^mH5r+NFF?R zcP24;-|a}UbpM*Gu3&5RdW3pyizN584iPD)OPDSX5>3yHbO{k2k!JPU2x*S`7jcV^ zLHrEjYY=~f_#DLVAif9jKSl9Dh#xA7FGBoLQG62Omk{5C_$S0iA$|(+RfxYrd=}!j z5Z{IPucG)c#E%ulmm&VFC_WAGYlv?{{2Suq5I=|bI>g^0J`eGGi0?!E9})wQI8c;W zfW(8M!~`TR6eTtw@d1euNSwgvV_GF%cu&kIO58wVM^WMj5<`$Ug2WOeo**#=i7QBK zLE;M%V~{w5#2O^tATg&XaR-S#MTtL13_{`%5{r;{gv2ByE+MfAiBCw3LgEw>tB`nw z#H^ykEhKgoC4M0>tSE5|iDgJULt+{d*F*@httjygiE+jMV{xwfl#S_)bdkNQ?v1Po zZ9Hslc;nC01Dn1cYkPS2S=Kj)KWcmPi>9`>JA4&KZqK)mqg%<`oKfl&?%!-c!$NJNQ zmO<|Xjyv--TbCBEN3Qj4yxcnC-LGvUKJ6S>BQ4ROG>RH-v7m;v9n^^S4HEqu+?W&K z=swf(SN?O2@(A{w_v}BU4j}aasS8D^4@jLTO1(hp22wweI)c;_q^@A}8EsN$yry;4dudv89~MmhLZZux!BhkZ1&ysGmMo}|4gps5E0j*VVI zZ1itms?6lO4=!9gI1I^D^47>0k~v%>pB+*G*y|yqhv@A!BX#_du982NJ9*{h`dcUY zZ+PS=pa0xm{&m;>=(~NwG|P(@wDd(Dh2LJ=zpvc<(BKR-88`(@(@^TFFY zNp&4uY8XdYj#Sueo{nMsJJI zI$z6OkISR9&-S(6v`!wO?KhjRIa?l`eU|x%N{2i$yTIIL-b8s+_5^d=5k2J***(nd zyNvgCXx7}^F}b_9<2NVxj()$Hx6}5fzRpj4?mgz-&wR(Od)aHL=xjdj%=O+bvk#iP zE_q2Fas8gT+mw6c+T>H_?!A0k_g~)i^=$6ZdhUM0*XuL+snX3i`TFc|d;4Dh7hk{o zXLgjFf$*mU3HNXqZQ{?j3)U~(x zroFzvo3ZLV-}DDZduJ?g`ZBMycxR59>YG)O?9J-b*Oxu}LvQwh!+djwJnNnFQ6F>8 zF&}6-&$Kqr`{fyJ{>@?YDZ6izM?P;ipSt-1dDQa;bMEy^zRGWEtyZjFS}U%mSV6Y{HNu<`pqB+N;KIwaglgghkdNrXNm{7FavL$$-cPL^>ey0g(`hjF6BLh@6m+6o{-SinKuFMNuRMA~PhU1|l~Q$$`iY zM0z0d1Ap_;Ymq9x7r7$+)}z-*8{dn(DT>5FWKL0}4kC9XBo88c5b1-+A4CEnG6<1E zh#W#B5h9Cg6v>6iE<}1E@=HR3Au>!tiXn1LLXsh}OhTF=@~kKl4UuU@k!pxs zD~e=8WE&#g5c!5kI7G%FQV#z=lk?`H^20>s8~HT+7E(j}kxrLCmbYn7B!bIblF6HJW=<<#(qh3 zJA97bjPzinHzTjon~@%j^k(E5t@H(Cjr`7!EO%g8lix;^a~JvX!pM5I>}$1Z8F9TE z>E6^_#$^m|y!=HwJLFc~@6qk{B#Z}oA;bjK>&_4(d`_H@(VyLcUwJWGVwprdLtZE^j`ue zU)IuQ>lX@)IWNsNw%yNGW>Ba9_^x$)hCMhw^iyEs;aAuuK6I0H68B47^%v_D?w9bc z3ru^sESQmZc3}FobAmG_`>mO0m)d4pCI)8Z&$eaBv&^#7huN|}onW2QwS#TW^F6FN z$v@e0?r3hE_x?M!`R9KjKN?sPTu`*tx<-yG4UQ|uU-CMXcQP9MlAJUCiqYVxS8xQx zGvNRFXfWPLSm2QmuT&Jzgm|Z-cqqh66~$8_-U{(ph}S|q7vjAT4~BR##FJt4z5?-R z-s9DZHTM;Wck><(hkB#ecs!ru^$^d8ct0cpAW;B`1V}VMA_CUjS3qR&p6F1N2!TY2 zqC^TLS|AYvi5f`cK%xf{L69hdL=q&LAQ1(LDoA8OqDxUC3=(CE5^3=Nv3Dl$O;qdu z4*L#+P-G{pr9dGpg|Y_$OW8WCmertOEuxGp6{ME&FN)ls%Ag1+-Wn7UyedT$i)alB zhgo7xc@ z9GJ(D16zW#-r|ns(XWpl-A@vQZb4a~I>K;=6kYfNj4v=F3IUbN> z0y!>_W1}d?hr%&Zl;cFWu-WnUT%YY5zTL4trIYc_ ztcM)$#>}!ejJ?CLvBfR+rhfl0->cBe-W)!`+lNk2Nw{#wEx={E|lNs95p(!$w4C z2j%TQ%a@x1a*5)_eCNxaCtjCJ4!7G;=}*X|h8R0~^$fYh(9-Vn)IziG_Wnk{#OY@L zH7+A&l3O=)A9q|e5rYJ+z4ZAxd+T~QJsx(M{hOb zLhBfDANMxnOB6EVUutHKKX|~nZ9%kS!l&o$+Y_2fSN=YG;?Q!^h5xR7XT*2Xb^n|_ zIb@4;+0VA8WF0jVB9|Ho8K0Wdsy<*$OIu}5KYgolck*NAj2*p=#H6X_J*mx%q|rB+ zGZ&RN^3`%Ga?x)UEq2k1D>R`1GHq}Q3aymv zzRK(|hhlCyeU*jsbBQ*`7ct+_MY6mQe}s4>#3vzM3GqvaXF_}v;++uxgm@^#M-|0O zA%3bTo(l0*h_^!g72>fFpM`iW#BU*<3-Miu_d@&^;=vFfhIlc=j}^s}A-=3A-VE_) zh(|+w8sgOuzlL}=#J3^d4e@V?heLcE;^h!OR}@c&_`0HaJH+2%?xpWi>i^;OypP{Q zJRjow5buZhKO_ezN*;jZ0!Ti9u zCWX8S$(v=Jz@6CiB`3hf0*n}I^R0n&D$(0+ilAt3Dt zNLvEZo`AF|Angi8X(cYJf8JjIZ_LrICzDv)Y5iX+Dw9{M zY5GG4WzuSdHE_aanXG!PHMs8!GD-DEYiP5FWOC|mD>h`g85?-Z8g}v@=5XKB)`)Er z%#kbJ)JDD5-yA(BS-bIZm+6T&wJ}NkWU}c^+RdX|$RyJq+PE$iWpb&(+DIWHM<3Yy95V9k;zw+?uc{-MoF?A?uE1ubC6?{J=6ESZLlk{A+E}Ez`}(9p2Zb zTtC8`TK!*ILW^mRgfkCo(<%&iOxrb4oBmxVnFKmOoAGIFnf!UXb5yoLL_8$w zA(0P>eux4fDu5^fq6P|zfT)6kG9c=ppb&^k6h$czwNOwDL^TwY15pn|K@b%|lmt-| zL{Sh`L6iki7erwYl_`qSAZk+-#X(d@L3t4MQBWX6g%p$s|Cnmby@s5x8Q1Jcb}20z zgj_{ao}N8arC-Ii(VmL0-=SBk($rIFp{|E640syfvBn>E*E^oDcXs+OTptiKl5@t^ zc!xZTcS!yzrOvO*(Mvn1H{JgiPNwr=7CB!7vxuMFq#xw#$hUIvl@#S`LB5)zd_Bll zgnUiNS5^FVUpL=#jR++=T;=-vjQM|;>+gTb>+aGg{^0~-`B%@u%83O6mw12$SKVv5 z5hCM4;Gg}fd%;!qtJn>E8O>ehY8vMsJSa4w*wz-C(T+;}67|Zh&sZV#zp<)R+GAZ? z0Yb)yYs9XZ@#6~eDP(i)>BVwwI8yyI*Ipc&IyyWx+8Da>h&G0 z)$i0(YtXE%*05%%)~IY%Ewos7EA(U=tMOM2t*~uXtR@>vSWRC&Z8cl^Z!3KMF01*h z53Lqc-n3j}U$R_Z_jeU4s;;32ucTkvQ}^+YKB7XKxzn5OOTp^)Rv;u7^K!9H3z9ZNDV@25mJ+o z+Jw|7q*fs{3#na54MS=fQqz#yhSa#C)H?D>2>wJUVqt?CC_Y?QU8rof>$KlI8U`muueCA zMl0buVpWQtyO{1HHi4m*|7JUTBDFH&d?^wT4>f$Z&+zl z3(NS_+vM;~TGO;u-e&1ZR(SGb-sY=4R*R&mUe{ySSuNupb+($^z-k>e#o1<1aVsKp zsIzUWLsq*Ikl&ap&>;9#6nR~|Db@9WRd*U8% zw^=K!>tfQp-N*i))uV;i+q1{*R?idZ&ZzKzXi@2}Iit%>(4to_boM^lU+eSKYH#1| z1)M|yvwUEXl@PYG|W3NL9+(c?C2dlVzm1 zqEd)bA!=0=#X?l8D9VMXS5XuUQ87fx5H&*-4N)~j+3>HWZgqB_SHJAV8NY+A^ zwRX5fP3PgQA=)EDCcL zda$fkcv=}}kvkh(MaP7Cixsjhaqufzdnji#g0#Qp_QIYop1dvFV$brdDKoB&8D~%1 z*2RqV3)8lVu%Q3uIU3-SM+L_m-FZqR#T`p~2CSuXm;Ge6Decbrjvp(;T4zco{Q5OK9*!tj!$X#8{adf$f&KnEzrKpy z4mmmCqQ5DAKc8$6oct;cacREG_JyT9$OBySQcHGRnBXch3g&=9d9=MRsXmNmbkSfN z8x`^zB|_8)Q6xl_5M@HtsVEACdCz^#Zkx|n{}1Kz`>0p(^nZD&4^cj~Rj|~Dss^=+ z5cN^8)JG!KpjI=21xtN|o74lVD#5?ZQXhXw*U!D;ny=9_7oQJ2EC*MerQQEly%A4z zn(z4VLEBJgXr8ly&m)%GI~+>_XIonjojueskh!CSeQtBDKymjMMhVwDo)QmNGfHVo zJ*6Gb8l~+AJY@p+8)bJs6Da44Gs>@>6{xV{K3jX@mO#ZhskFqNdYZ3^F z4zsJYFB`Zvtc+c?UYMs^$)k4l!eu^thIx#fsaYyGF5+N(Y>>MU91aV9wI zy7xZjsW)W5U4Q(_K!b=4cEjHD0*ykJ*`ZA)2ST&nHNr{{34~=lXEZt7F3>b>w$Uto zuqQk@-e|tMt*1p&AH(%nRnMhnyR~hV=4n@=oE=$XS0M7>cXs=;Zw5MS+G2OyogC=2 z@>`?xIy2B^uHWdo_@;n+;wwhCSu;J?#mqOlj~(Ob(IUa<*`vFs=ZWQZRQQd7sPuo> z(dD`YqE}C_dmpVI==0PZqwjXNr(fc2M*lVSJTW8t8Tx{vo&lX&7z3vs^bD$5(HJ~r zvuE(x6UNYp7d%71s$j>~-V})4aLgW-wLCCVDftU%zP#~rPF&2oq zKnwhm>9&!AZ7+JG>EA|j16LL5QBr59K`4#W(P4mi0MI$Pf^SdVt|Tb zf)FEA6f=YvBE%FS#t1P-h(SV35@M7PvxFEX#55tssVL?NF;GP@QHYT$ikU(T6=JFo zV}+P2#9$#N3;(i4tLEv7`{r{!vSLyfJSYp~!FJ^bGd>9+e4AT63_dAHtgKJ$-5 z!5*aXA6U;tylXBS^tJYU$aCiM*6(Y}v)*xBynflXAqTYW&OOqT_MfY~mUQk_Jw&A) zZ(9Ycy#7sBdCl&e`NQ-Qoj?D$_jl|W%25@~5R?T`7tHAy5|!~9r9spNb9#nEb-YG- z5cNS62vMP;C=uq>bA&SaIqIZbZV^`$%kO3P?4Vp;7cAmBiE2>mM8kqbTt`z4YQ1V$ zu!w5~lX_s?Z1|^L#Pu)b)>qi0!-2~#j6bApB;&vzG*c_w;z&I=(zo`QuD_9Cxpzf= zpzk_TMc-5HoPW>SsqVdJTk08$&$z$XyG9QTj&Xm#sD}S!vAf+r_)Ps&`3}C*1A6JF z-`nRtQ~F!~nH80NXOGm=Gym@iU#<6F&}&aS;j7~v;CHtE##i^=`}KNdzVtQoZPCMT z3AlTCX6gfH7WK^(2fygjM)onfm_=9vB+VqR$tk)FPgIhO#kh+4@7o^T0^#-XsNc}t> zM?pRg@{ur~ANz~*F6c4kL76KLw*MaETn3c3igzpJWsF@(=QrL`+9y_N$H(5%c8XOd zu+m$0=f_$(-#l;mwJWs>D_(YLPt4OQ&as@=OrNY3j1;z&d z5Le;ovP)yxQ0=n?FWgr3zkSEt`mtI{{r!fs zVY}lg4L^I&+3tr;DUqr7JKKM8z}5bEZ)eBN=TkaOEbpBuN#Qc>*X(l&Ih)vhh~y=6 zm*i|>Th6kDtIIvj!xMe;$;F$*K2yo_=Gm{^5iBJ;4zTNBx21uAY7I zk$O?jcb;PUR=v1;i>HL^RlTHhwWpM}Kriih+*8_~rk4raO1b>AU zQv=!)vHps4ZU|g6y@S6}eEUGhPh>V?kf)sJO* zYV7+$uel||Q)~Ttz4j`fr_PdRb?4OM0d=lCN_P)>GC}dF}w8=Ps zXx3|DePf?|I_MKvehue^h#6Ai7*@fAs3nf!;?$ z{C%F9;pw}*mEJFLl&Ali%6iPmt{(mIAM^p8>UsuF-JuVvS;RAV$Q%0LvtN0JMm(ht z{i;wPwsxvNcEf?du&gKj!(kO6_e?+C zSHC;?F3*e|&Gp2jVV-+ZH9cu`N6*ZK$2kxE!?eGpALJwaD|hy{+LnO~@{Y!-aW@&V zz|G;i$qVq8XSwh~cQ}uRuS%kB0a_bOg~7L{AV+ zL39Pt7DQi)qA`fhAX?ir+vy2jV*r?}7La#DgF{1o0w>A3;0`;!6;3QWSrJc$A{}6vV3( z#jhZq1@SG2cR~CM;$aXUgLoOl&mf)#@imCILHrHkaf;$|5U*1dzk_(5qWB)f`yl=Y z@j!?VLc9>-hY(MM_#(s`A^r&QNJa5Uh*v6#UqU=nQG65Poe=+ocqqh2AzljcQ}|c% z)Y4+LZNzf(IZmOB>^={Q__*4xYsyA)YSKebzp*_rExF$WUuw(9_0zvw{?4LGk?#)M zvV6l`CnGnuU%hNV|&tHR@AcXldU(gqf3N7ZyJCsh-CH+q-jgvUcJGSYdu5~z zjs0RyOs_z4WbD59mc8~TRgXOoU8&cXqcUT^YJ9TS!KkV`zP_g7=0lCn>^OAn#OA}r z_v|>lW!vU&4yNrmvg-BCM>lzQeEW7C53?R9MK)3L{A{=Z%)Cf*zS{f%RL zos79T_J^+5_d3<0XYA?5H*G$>>$cb*D>V0Dlp?1UCcbrW5WemkLMT<6SI zll&7}N7cz}Gw_uO5ut@L+qRlNpUFI%J?7BGeh(j%aW|j;N|v~Sf2FSA zcdz8{Ui`b#o*PMf?vwdk(b}_=bu~WOthRTsy=Mo5ROMMyEj*cs|DMkw=bUo>S~K_# zZNC1}-s>;zzW&nQ>rcBcyA8;Md{tBn@Z!xp8iSWKC>nL6J9laTF)D~zK@1CGS`g!c zm>0yrASMPeGKiT$3=Lvx5MzUwo1z#T#N=RZqx0ZqUSoI=(}Nfv#QY!z2r)s35h{uq zLJUz+Oc7#?5OahWB*Y{kMhP)Xh+#rZ6JneY^Mn{E#6%%Rswid(F;qn{Rfw@d%oSp= z5R-)%EyQdgh6^!Wi19+q7h=E=6NVTu#EccikRhfFF=jjL6Q+9AwiN7Br!pf6C^BeZ=Lcaq2SXFK>{Mfq@e#b4kGN3Zf_ zu6eXph1rXC&TyBWS?tZQZEm0X(85Iu6_Wmp>Fl+1+Q?pi*n`2#GU%Xu@EvktaKBr( ziOBB&1U^uGD0|NbLq1&Ickl%uUjojQpUKtBEB_*wS<^QAOYj+A%7fQ(UHxC)C%??_ zE#6+qm3@8vnSWmEe%7_hwe;?x?&oUMaV;C<^)1UBn6kXnTkhq1+N8Wt^J(`BA9ry5 zEAw9Wi*L_!z4Yx4_sb7uxmLc})15MNh-=l$Pq|;|W2CHpB+311gLSUg?(n$Rg!ock z?|+>;wdA!aYa9IAxAyBFQ`Qyz(6??&71vviQSP_?*)k>V_^a->tz9l(`q#eoqoZ8! zJp73JT|L>g;daBl(Pg?eP3q)+PrJ#rx$Pak%|9lkd{9OAeX!G$a`AY8^r_nepSeHv zZyS;t*zR2A|2*Q!zz)Y_{+%I-ft`V={yiBpJbPD6(KFIUdA^u4RL>qius^A;=RkCI z{a{p`z}MGQ^&bi?6gYJ3jQ?=)1A)U^_V~X!xG`{KRhs|U+-08QGw0|hCO+u-{>Iz% zlQH8xKXmPl)z6bF>i2p%6P*Hpk;)ROhhY(Lx6kmjRBg7ve9trVDh*v`V z65^Q<--LK4#6KY(3h_~hmqPp$;;D+_s}OHh6n}+ytfKfV#A_jb3-Mft??Sv6;=d3N zhWIeViy?jt@nl8uWr#N`ia*1=#-HHV{2b4Q_%_75A^r{VaEOm9ikCzD9OCH^Ux#=* zym)*Iukm?^*F*ds;`tEYR}}Au_`jm$07xEy zGRnPq#4Y~K=>yy!bnNB-;LY28A2k@KfAmz0Z_9=<`qqvoed*60)jzB8x$l&xnE%xI zd)z;E?&1IOz#!k5-5vdBpMSuexp9U6-0WN3S*Z^H`R5P#91HjR3(el>D}2`me-UH9 zyQpWMUM!-rueh8!m8jCoUDElSUh>ooUunl|y|g{vT_zB(m)-e*ubi)sUViPZz6!5A zqigppc2^v5*niC}ulp*s{K#MV`X_uLBmSSiN~0ORYdenhS1mruSFOhN{%Qxi`l_F8 z?62|ZNO#TMrTw*5c5&CfdyrmduG8%t9--HrSlC^!`v8Ca{$IKq)Nk!?*nE?_QOH01 zq1W#Bg=RgkH$Jsh#zD`~n|%JNjDx;SZ}$2Ee|T~~z4^jv{uW6sbk|+O{Vn7A_*>o3 z$=^CE+~3AMOpmCwO=qlQ+jFn$!Ew;({@^(1*Zje8&|CH3IOtb(H4gfg5&qz~=ZE}J z4X)Lr_N>sOi~OiZuh#v&5A4?aJaxO?_oGw(e)rGu_kX*;KjyB0KRE8WlCH)**Ep#M z$31_pt8vd?p7009J#X^|$34I94~~0&!XG?;nBfnOdtRgm$35Sz2gf~+)Pv)myXe7j z&rUr!?zylY9QRz?9~}4W@CU~|@An7CJ#X*_$2~9e2gg0Xs|UwDKc@%BJYlx#g)jP7tmxJIC#~DfwEJIC9V@xrNx@E~Ud7#nGyYpWQeWqCANDAPR)2P*Ic!QKOQ@v8 zfVcp}2_S9&aRi7fK%4>M4iJZcxCF#0AZ`J142Ww$oCD$>isB#;7lAkl#7!WM0&x|H zvq0Ph;xG`GfjAAsZ6J;VaUF>BD2n?)90=k<5GR7T5yX)ot^{!=h&w?X3gS`_r-HZ@ z#IYc*1+zzK<6gYZ9i<%{aFaVq`!)6db4O`!RllD*N;^3EK4+A+xEy~kcNF$R>NSqX z`?wy&`4q+dAPxv|L5LGV+z{f35Lbk`qp&OKyv`YgEiTFH+)>z{tJgWBu*Eg`InJrL zK{`;S4|(=kMo^!gi?FxudXyddwY#9n@p)DC}kG_j5;K2lbdc3OlIB+)>y; zJ?4(W4(c&?6n0RLxudYd)%X4DIy_e`BE`&@!9*Ayhd^x&~dFXf*`>=d^R%%WA{PX7n zj)hrvq1pQafA`gXFU;n=R}#C+a{WJqzoO!NB#`)@o zPWHj5PL8kJ)wU0X)^=oPpu<~E+ut1A?>Mr3gMD<<2FJH=?=!wz88Gdq-!+cSeaHO% z#@WW1({YZohobGw9np?+o15BMsZAZ{mzT30i^_>~zq1S9^_`>0on?)pW6GGt^ly#g z?xSW2m)|JyaHv^Id&MaA(zCKg=6s_};4!o8&J??xZ>m{-?W1;u6_3iS+bMR%Ia6e| z?NGZ?{7{)?8)=6`N6M1m)$MD;s>`g}c1E?5nX&|UHKY3Rs%DLSXN{U$&X~2@8U7Bj5$&2})m4lsJ2 zcwGjB-)={xKOqCbW9;bFGh{YsOS{ig3(daU`y2fdr!t9yx1+%-+1)9zAxM zx-*}r-#g}*+0%`i!k3yi$Bi(?mV3Y)7uDGqcl1^>F0_sj_i=ACzCdqPv0b-B-;IJBJ1wtUyVGvYg$W%-;vIb@5>uAFU8$vSE#L@qTFGCnn@ zReivimbS{Ae)?A9?&QbJ89RC#iAht!2^W>KKPVldc8KB? zf0B?f)FQ!xFN(5A+88 z;>Z+S8RE-m@k>x4sIcF5p1D^l9*AeZd`8M}DD(DT6hQlS=D;MkPzl|z8eS<+WdY_13^NZ*Vzq( z%bnp9`l2|wY?S4JNB|-OnD-rtvJ?G$_5YCvzn`1vx~tdRCX4qA?pV`8)d#ohqCWnL zcdW_lbmk&U_@mmxZ|F6kp_;GE^<`Y=z?EjI?6dykGoLd~OkAqY{!hj{VE)iG-Z<6b zR`X2BIQ#eN4fy%l`QPhKcX@>~=hk_glXKpEQM6xq0+jRa@c%*T%ZXu9$8Sx2#5sTG z_fBm;J@-W;d@eeH+2_LOh1X~Xq8o^I6#r!B%DE{vuXD+NsFYi9Q@DyM<#K0CQtn^j zrf`21PrAZ ziWi?W=JRs#g>LFRE*W1k^2i#0)hv109^=biD$Xk_74&doUw^M2&Oeg_{D$7k0dipT zbq+{3(B*m`3wjwPZAdTUZ#t2>_|!|LP`dM%KJu9|WYxty(gi`lgOU>zS0i0y_aFY& z{=D3ihwdJ}WI_MjG<9M6?n}Sm&(I5LCIDJmwT@9!I>DbZBj_v%ty4!v_ z)I%M0Op@j1tpZ%ZfFr~iZ8l@pe~NACK|48#15qb%~ybvFj^cw_|& zF4_FOF4_DEEaVC^9#NcKrpZPzd4vRt_cyufdb;s^b^F#z@nf~h!=hvq@AX=h_F*!5 zx3N~WUQe@HsnS~Y!l7pMnd z`r|*6QMmiGhP_{w(YNnvp-n7DXx0~2Sm{YJcYD3n$KGT1Xz{4kv&W5Q&l4N9sPIIQ^YdDCxzUd3)pNAoN8K`d_c^QY z_K{}4#MxH=HC@b@k@1$kz-bQX)W;f_P}m$)Gu#?H-KvdBnR^pOc+9+Q!4293e|Z@#+g`h4r7fdmYiP#Ytui|H zoHl9Vt1>F~3vEiw0>_lBvsOanD`rB*UTa#_`R25=x2@@?6U@7lmsm4)#F~jo_gVL( zb}*AhkF#bjs%fe-yL{fNCBL_H$_5Sc5A)FE{ou_A~aK`aUW$!tl@l9!bM@_E0x(z1syKX|l%>3?8%*tEs&xH~z}Y2~*@=XGYF%Ur+F zb@5FB_rzC>ZnI{3u8Wy(bRRqBZ$AQGhEf!{co=o7Eq|Dqwhg?v*P;$2N zxWIPF$R|M){V-=LzNmoLC;_4dJ_AKSQ~^;2L>&-?KvV)z3Pdf6q8Ny3Aj*NL2cjT| ziXcjYs0pGdh^ipUf~X6kFo?<&MQISVDT?ACs)HyGqCSWMAu5C@5njEUNM!NnlxiW$ zg{T*zV2Fw#N`|NzqG*V!AQk`E>Qk`-0e4y~PHUwu*NvW5{)XO~TN|X*dZ&c9_AA9x>MZ%U*O}1XRrlTxz4eCHaMd4w$k`y` zoU38)51frczHo&$dC?h~bv7le^dru&jJ+vM4jayX@AOMl24p1dTb`RY5oEt2j_ zaXqGcTgG`^ttPkfwvPILt~P@zdm}=}y4tq-!P~CH^{&X8m7I}ZH+Ho@d(zqAz0$6Z zyFYh!N@U1W_Wv^*yoB0 zU*wERf7caV?rvxF>gQa&kB)Tqd1`%1-|f@9{Su!|>Az;UH)iDhDf)s=-T|HBQU)f} z_718Uoicc+!#g;$Y0A)u{obKp^>W45KJSd(*u*s~YoBxYb7fs4GTwELob|11RN8aS z(PRCt8du1OR9&dDKXT~lISaZWjZ zZAwDqC*FjNA5*4PP4P}k+nqA~^rPOpliy02v15uiG3n`)ds2sblSbd0GILR+S7PZ0JU$_l!?k#a0j&zyPt~zO?{rFFd`^3nzv>)YJnNkwb(I5j#Zi5Ya1&@D)Y;5Cedi0K^C& zW&kk+h$%pf0b&jigMgR>#3&$U0Wl0kF%5`uD2jPN3$@3B*t!rUEe*h`B%v z24XT0qk)(W#Bd;{12G;&F&~HlK}-l@L=ZEA7!t&kAjSkSCx}5oObTLD5VL|9mZF#z z#JCj2ydVaqC?*CmGKiT$3=Lvx5MzUw8^qusCI>M(h}l65Pf<({Vtk5Xeh>px6cdCP zA;b(Jh6pi5h%rLU5n_-KlY|&0#4I6(2{BEGaVm;=LJU+)(l~j0JUyRXU?rsbcu-!E2irAGdHU(%5lbhZ zay(b1qVYkw{+C!n^7ESMOG_D7vQ%Ww6y6I9cm4ZXhgv%F@DKYhy>M4;%gEf(!9KUS zR^X?D0t?IR`}{0C@mrhp|NCyf@9g$x-wYIV^Q||gRh{OUmiDYM{q%6p-O2YGGj??H zBqqff_oUYLB#n+XW-fAg)JR<}4qsxijht)7|1nbXw~d>;^3f_k{U5Sshb-WA@$@UT z{d=$CnWKCfW%-?wg(L9$xB|o(6n9*1RnO0*T`H@4ietdvYn9HQO`QJ8qG31=pP#*o zC(gs`d@mX%j>PZdN)Ts)mm9H(TZzG5wY5V2m#6aV2d)VJzFoQBspE+=Qx64QxyMv} z49i-s>Z9Pk!Otpvkg13dvK1s_L9!Mkb3w8fEXZKg0u9?M?B>0=O*>!yLY{5fix;E) zvo8W0ma*Dwa=546H0^P-S^7;zc=BCl^VL0!7D>ZQ*JF*0mT?Jk-d@sZ9Th9<)E_n? zLOaNL`bS2)5;bKM$sxPo67m;DLgWHZLdF%eEq|$9nbY{pX=z^iou4)Vf5U5;p*iK3 zT90y#S$YTMTJojWfR~Oy)`7nux(mpo5Ac#2qxF;D)?;2v&K4}OTGgcL|5r<_;v;-M zUIOtGh^Ih&1>!9be}Q-m#AhgY4a9FKcn-vOD0mOVe<*m6q6}rBFq{Pzw5AGLQ^W!1 z`-*bT4}W!Q>TlrLSJ+;nIu)F;cw5H zGIEFclC>-AU2C_`()MILXYF0_qLq<0+xlY8BUT_e-r5&$So@RuSO=m9T3?P1x4sH% zV;zj@t$lsX0PRp{Gwsmv*4p6`<+a0GL$q%W+S-v-r?jJ+wrbzb+o^rG@>|QkbDef< zuHQO7bG>z9;w#qoH$H2fjG1r!(Di=nREq@bbmM2VA1lOKXG+e}&YZr_dG^pPTIP;% z&U2f4{mw%ShG=;XF*x4FTVwyVR?V$dytUT9e}B6O&;ZL338yAqMk$eP8p$gitA+n@bf>#!-) z*>U&#TBnq%-p=d(rFEHm#@luA!90AX%T3UtS1)w-KH6XF^VDi@-|geAeu*M1#f-emt1oD74d^t?J1{}B z2G#879X#Z?H8``DcWA_C*3hpyIAd!c(_%N)bPmf(*M=|6a*oJ&O&d8Y;2f2D-DQm}_l$R3)G%w@(OKTO(2iEz$G3RnOVqOBU+U!@ zfAG9@+k$b<3I3Ye?FmuNJ62|C6NiR5jky8s&bDQolP12SO%6HgoD#ECn{vLCHzCqz zC1iZ#omTZ3Yg*dJ-sz`jS$8L|^v>9EiS@p9LV{^#iI^H(!r}#dEI36l1_kyjV@YUSsISOYg_^1 z3=nsKd5t%~E%-T(0dWn8b3oh!;vf(gfj9}oO(2c}aTSQOK->l5Fc6o4I1R*Y6vc5M zuA?Z<192aS13_E};zSTPf;bYyl_1UpaVLmFL0k&rR1mjJm- z4E}k?YYfqdx+@;9Auh`Q!$~1-3UO43t3sR=;;s;fg}5xlX(4V4aa@S&LY!Ao+!x}& zisHf$Cx*B&#E~Jc3~^?NJ3|~A;?fYOhPXAvu_3Mvac+ovLmXUDTpZ%$5I2W7I>gl> z&JJ;Rh{Ho%9^&*6w}&`B#PuQ0uPE*h$pDIy1t6I~QL+IfBS5kOBr`y=10+L0vIHbk zK(YlSV?eS7By%WA_JCv%Mad$NOrj{+1d>r8Sp||=AlU_yVIWxsl4&5>29j|gSqGAN zAlV0!ffOYRK{An|WFtsMQk1L&$xM*!1j$g4ECtC_kZc9XSdgp*$y|`^1<7EFlEomI zOi{8KB%>)xR)b_VNOpr{I7pU*WI9N;gMZ(QS6UKbqzt>u=co)SE4wt2jUw_WyWoXe z6H_Zh2U5?uy=#xPalMf-#kni;W!J86&bane+n2Iu?J(!wvmvgG#Tm{o_CDzf3~ufG ze&P9)lf_0mfAEcUoho13dwM`K*Xj2*I?t5ensR3OY46!1#a)^IcaOK$`;WM4Py5_k z#~YF2Z2hUX?!ED@dSy0y>&I#-_4gajhV720H2my6r^}q~YFVqFx7`n$QX*6DceelH zfUEuS-p-Dj&!=>nSl&BRvPHfd9fc*+&Y}%*AV3>pn?HCPtZ-K=IYDZjpA)2Uq~G*N-iuGTT++^=<))_I-#@p0Kjla1o?sBFs9Tj9M974JU9Q)+1!z2bn=fr_6^)GM{z z6{z&;06lEXJZF>jee|Z?_Xb+M|DAqatS`{LYrLrI4zp*SVIJu*P_wc)DK9GjWK4zX zwOCozAyM`(x!IcP1J4;#!8^29`^kf;RP=!}ohpx>Z~1mPCFeVHJmY6K8w>gF{0842 zvJa5`P?UXv?2n@C6J)<2`v%!R$UZ{$6SA+6{e@R^pVi`>`8irn`QQt#G_yx)g&XYD zbNy4+9=p!{M#leh?TUQUz3WIx_nvCs`Sz@hbL~A_-<`4ei0g~J%iV#&-CW-{ zY=Y|t-z4{`^376C59r`N{a(83OzD8{%!=YEXOC2NXa3*+rPO-=DR=E@U!~OXcK113 z?@g(D?_KVCWwxg@^S$d1zh$dyrquRjB2|w5dvD?~+x&Qp^j4{3jlblxrz&SpIkpR4 znAroe$BMGokUfX&J){N{r4}GHp(wQhsS!x6Kxzh3JCGWJ)DonoAhiXlF-560NX;oq z?Lle~Qj2n=#AAY;IMgPusZmI+LTVOLyO0`&)H0kYf9to^^cD2Y6_pRu)4=yd=AYXluH^^UC8hUQcE}0@6yvb+2uhX6^YIa=nj@@ZxrrCMjBX*a$8D`hT zhV7o{GrP@t*tjm{8MFJ?iAIkWv&^171{l{@xW$ZW(AtPPeV-$`XowNLW1OS+fm6m` z{pO&Tx%zsUpw9DupFjVM8(Wd&Az>)D*jBcZJrg${sUzp$6LSQB1cS_KJqF~&@hux( z^^0i|H2hrx2NFDx0D=S&B#)Tz+?qqi%Hwwg*4#4G^LqEKp475! zJ*np&us=L}r)S5!S8X{@Hd&9Q)#NH>>!@3_HUm$a5uv@bwyk!VKU>=9bk4sMq?Nqvs@RH$Lt%J@KYCCaIs}rs$irn@6{Bj1B9djq6g;amzIxE3VcFN8Iu2 ztXt1*bHs0LV2$7Vy5qK2idz#lrJJ|UJ7nFl>@{=ZogY}n0}IVNhkvb2x@EdKxx@R~ zl*_=OK368k#Oc=ZCZulj%m9lYSX{#rAr1V0d6^W)`>KIY!(JY0A*1}Y5ORVh z=+cJ_Nbo=c2ogk)KvI-of&>%{1{HB=m^@$g#eO6R`MU%nijL5af_Rrx5IrfTs}jkie%9{15>siU=S=fQSJi2#6>k z!hnbaA`pm3AVQ%a7KmUdhz24Y3gUqXNKr%t5fTM4K?FrXR1jf7#03!;L}U=5LBs|T z97J>w;X%X)5ul=o5F$iH5hFy96hsLTCIxXq1WG}q@DD50++}^*G;i75B{#%%HIHcS z%IiG!;<0ISWxse`)s&~7&S|=M{Ac!J=08^Gt!@X%t-XUcu=N`y& z>uheD%`LN?a=UD9m7RSq!0oZQH8!`!=9bu@ayx8pg?;tLdIqFV|HXWtLvXuoZnYgH zx7p?v+tG4+ZEmgoS3iePEt3mZX@B!wn0|Y^NtS2dOTLq{4mtP8?$BZqxV$6zZf%M~ zhIwXpYtfaZ&P!M+=)&?rqJ`k4ScN>!RxT+c^4XZXqHl)C2|oSCk18*EA1JS0FL`pe zdY#u0&z~;Mj^y||*>@5|dX(Io=K2j@BSVN36-AB|BngovM4AwJLL>^2DFvxQr!=Q;GbVRf zC(Y=7#?10oKHKp-N42n8Y*3W9-%hJtV) z;-MfQMG+ANAt{QOC{x86Xd~Yh%f|D`dH<^E$5)tADoaN@sIf$L@f6=>^;@jDC8gh&?YzdLbION!%DaJg=Ngqn;fp;YnpbO-Yh-R9iH4z zZ@#*^yG2q9-SuduyJcJ-f2&Cu?$%M^{x$=B?ugI|{td$q-N*KH_h>O(@7W{N z-Sfml{;2TozNqv&{L$qa`l46s{@zDR`1(9`yWV$u19!i~7`^|R;_jG{Ep`3zL+$~c zD(M5Ke&8Ndq`d?RN) z;UATz`$mtQ;lDAtmCw^}kv?X2jQghWyY-vnTDr%U8>x?rs^lJbw2L0s=%hRDW2YWp z{Bw8wONI6E2iLl9TTt6S;nQut+Y=oAJ668#n>b{@-u%@Iel8yk?v_}v-RnxySVR8j@M`GaJmzd`snwh7Ir6%4%cTc{L(#B z{4tnEetX8a++UC}habk0Qr`1%?yyb9sjD&TYTh%$4TGaeTjeK_pw@1TrL;Apxx@K@ACPNN_^}91`S^K!*f7B;X-I4+(ro@IwRu5dlO9iXsMxAQVLu z5Md~aI3NOnhy)@Oh*%(kfrtho9Ef-z0)mJLA|yo-6GTvoA}WZm6h&MRfk8wD5gJ5n z5Wzu22N51bd=LRbL*`i;H%x=xY)b+6WUzg4rk|E>AO+-YYs{clhH+U?t& z;a@-KefK--eExS^|I58$@iYF7l^%9)GEV#7EBCQ`bFX9m%|}Ai<+mO`2cIPVp=Mhf^b~qmM?+i%{>tBv8;`yp^rhYK0PT=cns`?Lw77F~k z%9sAb#Sa7y|FFsb&B2XTiZj%I;#n=AOUQp*RzVj5*x9TOGt39Q(1$t@6GE=^Zn&~!#w5J zCip9?m>STYi1k;Tb3@>o=^f<$PVEDght>3lMAryZiOlj}+xT3dYP}kIwUS>1suwz^ zS3j2Nsj=@1z2=q-Pp$Rq_1dd^o;pjO)tyt12kPE?zg}<1XMy_Tm-rh*tO+#ieV;54 zu_zGQWSl=V>oreU=_r3##zIe%!(slWX`>RGr8k)np4>ID`RcM0S|rs?bUpg*gqCrg zGh0paPiP%gC$r7KS0+S+7RqefYW{?F#Sdgg)<`)Ld2nN9`?HT8>9Fbf%#ORK9O<-j zLt^K3LyvTsyDYKm;>aWJi4P`rn>A#@bur@;yN_)*p+}3pi9LH%o6z&blYyx40sg4; z#6Wbp*8b?#qXWH(P;wVr5HI?<4kzGCd<3H#FI@R?IoVr6FRI`X@ z@Q^q3!Dqkn42^h7ANp0HKy2+)f9!?>fnixs`iC#w7#NX}=pQ-j`M{{O(f-k6=LBv{ zcKbd3mU+g^9`*m(I~O>c%KiUu*J0e2P%eXo7`I`&4C9vV*34kcc5T#PV>AuL#+Zg& zmQr%+9IFy_IAbXlon*_UkxW}fQ&EFxh_MMtF59KZW&FSIXPOzOKc#Xy)o-48?fqVB z&u#B#eLtV?v!3;==Wz}TdE7NTzN0g)%t+UW@LJ9h$9uZsgNr!hw={7j6hGulSf;r~ z9{#{NYHnlS{hxlB@jy~p-{|EVGsg7y``oi%$ru~D%{Ok$tc>xMR{0X6M`a{tZ*?Vw zJ?>1(TIouzJkpt*_N;5-`JT>6DG$0H%537CJawRJN~-3ZI=G!{+Pu@wAJ0>fb81<% z*w1D9Kj`;mvX8E`KV)EsYJcR;yQ1!V(A0B9PHeeziCZ7kEf8{I+ewCds8T5B`8p@M z*R&tnCxdGoS5uB)L+oQk5IcfclA_oW#F`Yvo*)(lu_=gELF@`*SrFTTSQo^;AQlF( zF^H8Zik(3$O;KzOVr?*Qy#cW}_OUsL)j{kIVtEkTgIFKL{uIRmAvOrHLWmtgED>Uh z5Nm|kBg7&hHVLsxh+RS~6JnbX>x9@R#6lr9swh?pu~UepLTnXctq^;KSS-Y5Ayx~q zTZrXCY!_m^5c`E#FvNx-R;(y?46$U0EkmpsDtl%Fi>8Q81FRZi*AUBw*fzwvA@&Wi zaEOg7ij_m`9AfDZTZdRX#NHtm53zZO)kEwaV)+o;hgd(v{vjyRnH;|MANjs3#14%!S6a+~_kW{27=?IdNAZZDbnjq;3lA<7K3X-ZI=?apvAZZJd zx*+KblEM@vjX_eGqNFoON>h}y21#v@^ae?BkTeHLb&zxiNqLa82T6U9^an|SijoE) zsZdeUAtWU#N?L@ZMo4;uq)14bgrrJHx`d=mNZN#?PDuKMq)_evb`F3RyP$&Zok1Z3TiXA+$9@D2e}Ir0qW0TI_x{IU zkhzV{((+?CkQ&}waGRk-@-M#4uwX!tA=UP09T4>U3+p#rLBMSTYX;x zvtiCXsqYTC$MfElyO+J>7vuFZRd2DEnKsiZ`_(jixgiObwri)oe8(PE z`B@>J3e8@yYQ5LbUOPBhue0c&=XPz5RkwS*r|wtn_263N?7eTZ)d*?fwdb>otn9;Su0&lmPJ)GUnO!bDBh?o8dmp9@_r1VF0_I6KiB>fRX z96gtm$%vZV$F^-|3PwQcBGJ= zv#l;H%Q<+?^qwtR*mJK6s4Zr+X(n6OIRi(Wxg{@vIDsOFAb|u4CP+X*f(jB?kl=y@ z7$nFbfd&aSNWejY4ib2f;DZDpBnTma2nj|=Kth5N5}1(SRFnXP1Sup?6(v|90jnrM z3kh6B30_D5D@qVU0vW1crltsJiUc(vuoWe^Apx!^K@JIYNU$qPz(aywQ34+l{15>^ zL;w+jqKE+^2#6>k!hnbaA`pm36h$Zyu_%gQAfize;XuR#5fDT~5FtUt1Q8TOREi=j zh`1C*U=Wc(ga#2CL~sz%L4*epA4Gr<5kiCr5hFy95K$_MFyS;gMuiGrQ<_DJ{?Wzx zGm{k+lh4P4a!ejeagg>QZ1vL3U!7yWaK>xB^q+%G=zwQupr*WE9LzT$eh z???VuD$Q~&Y4MtWN%l*=S1bI(|7zAu-@nhe{ja4Z_?B(&<9|J+hj01HmVVFF5Z{Ug zz1?rbcXzFv+`_#oys>L_Y$f-a;Igha+nsZ#7Wcc>R@>!Xdw82`UD0p->o%=&y?yX= z|N7+_zO?t(`rn!ThR?frzJJ4*IlgyiO!2=Lo#cBzZis(l(-_~TsLAdR%C+^ShYWV7 zpTEoXQ5lE(qs%t0k01BDbFK(n^lhJT(EpiZuWv{HP5zzwJHF3Dm-sVni+sB({msA2 z_lR$I_ABl$yd!*jvSzvWE`8XQl{U(~Z&sYkmlEaPpKzz^z|^MhFC*)_4h}BwK6Fg{=pZUHyyxo6n=NjMfO{@JU-r4RtxqO@3zi_qd z)a+I6)6?d;&WxGsK09Qh>)YsL_jeudah+>=xBGnk2Yug{Ywx~LVxaHB`56DjBkg>@ zZb`VCx5{(Dl5pkxF*P>%V%|UHXTcv9`?)9UZ@$r(#z+HTIC)( zbChdX$XxgE_$XIgnPm5f@TRU2$M1H>2bXunZ)xvND1OG3u&joA6q+;n^N*`-Y0k(inz~>h7Yn#(&`mBDbo=3Afx1?3BUAL|c_4{-a1p-=@j{3n zLOc=Tix6*w_#?z4AwCK5N{C-VJX2A86XKnU;-3%?h4?7MOCf#=@l=SfLcA5?uMm%g z_$RxhWIhWlOetg@n(oWLp&Pd(-5zQ_%+0{A-)aq zZbk8Lh=(hRk3+m1=3fH2sD2M$=XJat;_ncThxk0i>mh!xD4q}TeTer%{2!77Ab9|i z3n2Lbk`o|#0g@Xa`2mt6AbA3kDp%3?$D$at$QkKynTw??7^oqU0Y)4uZemB7los(&Vs}TsFrK1O1P=V8J4QFS)6Q zhDTfbV578|YQORJelGSyQWDgDla;O1e$%Nv)V}?h%4)w^e7v8ZYd-FR+HVmasrFm; z*`xMDgBz*+R?XAYe(QiTYCo*nZl6s`*x{3EzhDu-O{zX<4^S@!ivT9Os0Ws+g}-{) znu0dV&$d~9;j%YZpU=&dLz90#m->u4pG!X<>Ad>fsD!@3T~b@@3beiG#&-*rlm44O zvP>sm z-PP~n8TWwDS6u@R{rS!v{s5-~(#UoE7n}8YQlMEg?!7RS zK;#0EjAB7^#Ya6f$SP>AP!HILqF4yTMj%!Ku@i`;Kx_qKEf9NwSPaBwAXWph8%41k zi0vqf^+38V6bqUws(zAh(kgp@a+~~xEs2s6LOdv@JlL*h$kWHOx@M@J)uB$ug>C&k z*{NflSC;g1*q-a_EcAFQN8#j9XOXdeyhVrh&M4NqrMGye78wD}ZuJKI{eg^<+Ieru zWnVi3{ky%Te7iDA?>grw<6W0gcI_@lxuuy-?fG?%^0VG@-ZJrDjtU72oD~O7b5x3) z>b$khP{(ZzT+Yh1r+BNB?Ch*sc!;-Zql#M9)15ul4%D@Rw%zWj{%(L(Gdk@vj;bPO%Kk#=xGr654}OwUQffLZoN_3 zJD$edpR__!7I~Vi9Bnn7`iRH=jMHisZ`qrVZ*H{+f7sr#Z$&FKIL_Xx`FB?9fIIDB zK^63{uj<>|T>MsV`$3?+-4~zh?LGB89oDVY?=VYvI?kW3JH{OKbei#|)j4{zr%T-P zR@bJld%AUf$m(`xzdbzUIX!&)d-ljOlk~`yFWI{vkJWoD*x>28Gueuo{GzAVn!By& z*hf9ix$Ujq?c+Utl4@9egCafs2H32Aml}C~auS3YC+vgLUeX80W!Q(L%+y^`$2>!4 zzGw{#`NT6k{!uHgjK?z~Jl+~{{Ao{oaHJK#CDD@*(8x+yHo!CTt1{N8x#R8kXEf9w zNa}AN?J2E~8PMA9o_#_e+p3Cv+?Wh~e5H%_#OOEl#4ERXlEOZ*lCr+{BvlEV;X>P|;#P^;Nt?!0-5Fv#l9Tvtxvl zp(x{B3__M?=}nYZG_@@gC`@5M@Ht2~j9Sr4Xe;)T$_og{W3hlnYTWM8OahLzE0rGepr4RYQ~wQ8z^45S2re z4pBQq@etKRln+tAqBww}xB$cnAZ`G01c)m@oB`qv5Ql)c1jH#IZUJ!&h-*NcLs8rV z;vkCRA`mBmxCz8jAg%&&7Kpn*90uYt5T}8-4a9LEt^;u%i2FbsNKsq};zSTPf;bYy zl_1UpaVLmFL0k&rR1mjy;ejkT~xFp0WA#MqAOo(ejoD<@n5C>Hh7lk;fqPQu< zQ5D5iAq&I@s0hyyE%3qzb(QQR2f$cp015NC$CGsK}G zE)8*Nh+D%S9J^@YoLOnE_Z(2N#P9f(YuRxp{)@()@7zCZ{iX>!7^P7}94`CB^$!ibM)HSdO)J>aTWm0E zO}|I4UFt2fPE28|&VgZC-PQ-Kx}UApT7Q>ng{3~Iwb{2rZ}W8_vt9ZrtNj?8IZg7$ z&s;Xl?XHu1hxad__D}hltX0N||FHf{GZ_(dLw5}4)d<;&T7lGzqSOwgh9I>BsVPON zEl791X}3QIw+sIWmx=135y9a+DxP3UajIG4qZ)EQ2x$=(# zmhSw_I$L(P(!*C@Gcg%;?4!6@Zh zWR~9bjx;VFG0U!9B#jD7)1H4s8WRs|w@e%%jfgm{;=pieJlv_>+NOat8tQA6Yj-oM zlnm6W77jM5o~~zBJ5bUH+E&7>zTq3A#)_k6%|%-bJ*m1@Yx;7dcK<6{ospX*>F?L- zc7I)x{d-z)L(>+Vz0YhAI8J&sHkb{M_LHRkqS<)6+XzW{)NHb{kI{5$ylH=?rE$Zn zDr`ETwfkb-4X>)u?aW3kJY=#heETa}WSPM-+G&>7{kTJt=}TtMov}vL^+8a?k(=#?K>EKCY*Km4XSDM>%Y_8?@}RSKxnFaz@Zw_Bk-9&W+N9a*7)yUd_eBu zc+MX?<9$hhll+6@UY5ic>vu)HYYd$^(LF3=u`xXU9(P=s$BYr-9o!?1-)F=J*L25k z>24$h6mlmlYizKz$EdlHa__|z{{u;l%KFquuKbugIcZE~ z_fEALuk|aw&%Q47gJaVG&Z%gK*l#_!fIR}W6Q-CNr zL5PwQgxu+>MKZaWMNSQ_b2@;!cu?}xhVKk6TG|6q9YlE$^+6N}Q6bDfO{gsIT>MNF z2~j0PnGkhCrBF6dDMgeDP%8z+LR1S;E=0W$1w&M3(gE$|={U8nqaY2X^LflYM91-G*isFnE+)+^+ zl7dS@oRWfDLL8HVYeJlpf_p+76yl-~Cxy5v#8DxxswmD1aaTogScuCiiqpbB<3dXR zcU(OGwu%-_nyYm@nz5coL0jr4+fvUOjkgbThon4cG+Eiz-E`_e!~RS?_YF@>xXC9j zRj|g#f3Yp~E80jm+OCq8N{NEDR6$#c-1gsVOFe7w_i-7B(?Hw?;y4i3fj50RN^vL( zE(Hsg2`Okx5eJ<4Le736XTXrNV91#;+rT$3$QrBK{xnRAt+-~sS z$eW@~>HRrg(3UD_OOY%7du^#dTfbCJf8&iV@LjOvT0y_mulDjktc(9tTdDub z?DC)_$%F0E^Nss#6{03PE4H{`R;u%jd259|=50l`n75rtGb?|&+>{HjX4UteGONA4 zUJH8db*=h>g<6d#O|9nS>6(83IIULfaIJRFep;RO-L%^qx6kHg9p?v` zj(h8voo2MrI^S8$>=IW^>st3Kvs;(T+MUHdG{fuc)51?2)*_2+&>}xc*SdfCqSoW} zSGAs--!Y^9@ub%4wMAz1=+Uxj`6FiUyZ$DtmX9#|w!U9hEe|*Q-_}c3EpK2BIA@nt z%kR`;KEFj)Ew8WLz2>Z}S{|syTIIDttBz@dr=8J;%=tuf4cVa$opRJ1*6~ek_@K>Z zT+QdT5glGP?=7)fRxLNp_#^XV)$(y>Li$8mwY;A>YRNsaYWZ00fyX<@s^xvPG56J! zRm(%QvAx^Ns^zz7o1G#1p&BNuHOrhdxlEUT7JQ`h+m6}?I8Qv8y+MP<~$rv`169T^O^%X$9N$B;Xoxo(y_ z=eS&RDkSkIPh2Cr(P-t;6%k7zY9WyeiC#zqL!wwwA{i3RkcfsvH6*ej(G7`kNR&e& z9TM%3h=)WyB=RBA4^aR_1rQ}DiW(q_fT#kZ42U`)3W2Buq7;Z)Ac}#g2BI8@dLRmd zs0gAYMNtz(QHr7}h_WE+f+!54GKkV3YJ(^aqB@B3AnJoC5TZhe5+Q1YC{j^W2~nn^ zs1u@4h)N+!g{T#xScqyN%7v&GqF{)MAxeg*8KP)KQ8h%_ilT0a!XYY$C>^49h~got zhbSMSeux7=Tma$(5I2B00>l*{&Y&pn0C5P2OF*0g;ua9cfVc+4IUw!TNT%Cuv<_$}neLn|?5om}>2}%LetV1AZq3#k z9!YX%xkNMk_D)9l`KPqVqIHbO%<)?HgT;*ekt8$CUTf|!qGJsH*Z-i=?@~E)K z3Pdf6q8Ny3Aj*NLhk}A2Dx#nyh?*!U3Zg0s%7Um%Q4|JInW88SqBccQ97J^xU4-LCcIjEU_c zty(6dxdG+p{Y{jgFteT-n{^|9Tiw`oy$n>EcPS6bBzdsi;Kdz%jH{Q-IM>wfIA8xB z@9(!{#6(x)?RPpuQtojzSy|uNbZQ3|lU6Wc1(Q`UQ3aD!FhK>AQ!p_FlTw5o&K7U_ z$lrF;R)4!MQhe=~pKy0rXZY@zo#E~{f0)lP<_&kJ8Pi;yqvyE0#0_aquQBp0CnYaI&_6WC}b3MjO%1o1TmiK{| zStsROPa<1hsidZCs-m<~U5%Zr#de{(SHB)&x7#}Xu8=VF&Tx6lL34QsrrT3WNzX<~mQ7BTkC5t#hu_B44m_vBfo z78a}Dkzp%Khn_R1Q?EtN#KG6`;@p1-^ZqFw%Rh$vbI2RuhjF6ZOTZ2IVY|E`8w*B{y;P;zof%aN zope+^b;4O~|5iuP)(mI$4J#crR=nY?x#(Gk{?r_2&LX8OL$=(Pvkci&zMN&q#`|)X zA?xqUS%$2&uTdIp%6_h#Hf1YUew%WjFQ-k}&PSW_$BT{r+=M=VmPtUbULNdPH`xEW z9)Fqq(N6fj-4$`m6q_Ygd#%jX+e+vtJ-1CJM_BsqeziKwVc4|L&Hol)*2=up34^uFE-OX!R{Ncp* z8}fV&iAmL$G56JnkEE8n%b$9=muKy%&W^XT9=7ied)u-5SP93TDkr^r*2dfSUaaHD znt#l`Z|@R^uU{wo+2_i5zb$sZ{X6eC$GNhNJ?DG3b)5fTyZu6-&wF8ManHqL6&;tJ zc*0Zt!v&5S$%i~Otu9`@#a@qc_EI}N(?swp?Og8yyd==~E9aj{$~oj!R|;F+NWV(n z=fhn1jF8W%D4!Mbc_E(}^0^h|vqL_=qSOJT9w2oAsSik))Dfhf6s4{p z^`$6v2B|kl-C-KkAEXW;^$4j;m}hcQr|eIY-~BE1N(-sw9WAey*=Z5+pyY0=7ShMx zHTSAJfMu0J5lJDL3^;(JC&x|lM@~P0Jm;-4&1#OE-{+jZ=Une?uKw(QJmvlmUd-Lr zsGmuPpIr<2zcBBf$6xvX)sj@#`_v`mzw@9>kO$ip4SAYv5AQZV_Kru!t7^A&u5Po~v8G6T#+y|?_r7^>zBBc5tat4Px^r{TXvgOJ zzslIrqQ7@X($kL1TiZLbpT6L^vU!WwHg1on&;+Nqup`n_qu$Nx{xueYE8TPXOTI(ox-y3%Au^ry>5p(Rf z-20}tLj5Fr#XFz(Rx1Cr=hk`;d2c#) z<##x0xG#BX&ek3Jv?BIeV+uQJx4CGq)9aw)_9}brb;E4ly5GNJuXp;qH+a_~d;N3o zdmFsF-qY~&m%WW%Sm)g%L+?C*H(cyD@OGgh! zXsNFDR%KFU!T24XFk3lC*vBR8ZN6^gZTsp`d%F+Ic-#MNv%SNTlioWDKHGSuo!12`@j#5q`3;J>pc7Bl7Jr_U>C_96eHP zo}Me(I-)iO*n2r2_C_yw-Q(=M%iDXh>FLwxJ4fHxah`sqG97odK5V~h`7v)ym&cSN zi5bZ}MTK*})F_7=y}RXC{#-zS%&$EIsjOdzROmofvXL$`X?Dr8$VNgwrxQ)iuhd-U zebxC@s0^HFsRmC-EUIA>xx7vr?3#g$d=h3>zXSQ3MGQB}AAIaY6(N z5h+Bd5V1l83lS|uxDfF|1Pl?eq6irxW{996qJ{_?B5sJlAtHwe9U^v!;31-i2p=MT zhyg%M0Ad6XGk_QZ#1s_87$D{VF$joBK#T%n77)XLm3Knw?BIuPT5m=DB&ASMJcB8VA53`tQ;31UoenuPpc#+-hp z7bdqWh|fJ!9_&{%3iQ3q&%CA{&TwAo77o2qGhhlqkpvA}I>8f=G*kydV;zATx;6 zD98;WISR5<6zM_a2azB|h7c)2Z9k*UT*58U}}y?szFVN1ep|x!o*0B$&n~bkOY||iNZulkjauL zOqir7lO{nXPEwS~lOPi)Das^D@Ymi>`X{%^pSri<8odY9RBm}tHt2q?^wpE2ZReWy zbg>vtK}+B-H;32_-AW16sN;h$VMi z=nakz%ZppmIk5|gUq}q|&kNFSk~#*>$Cpqx*&D{jB^#1yOghySqGNdDZaX@z?9 z7N1(RrYG2ImrA$l#Au#62i$ty)~7vnKl?y${oN){Sn8vCn|)tO-*P#tUHTPI`!Qv$ zY2uqVcuptRp!P*@N+)9%#n`3u^@nRvtbpC(My^5S)d+4q_tkhctc99kpW1=c5TuqM zHKi!E1*tJbsWnK=L23_DgOFN;)Fh-fAvFrARY=V$O6@{wSW#*jQqz#yChDniNUcL^ z9#Z>|BLF!HkRzceM+0(16y>Nujtu1JK#q{293{w+f*dV4P2S7RIbuK4^F;fQ2PIS< zY*&8l@-smxsms-Tx2s1)``6ebd~bGZ=1m_sV(i9^rZKk^Q9IoOZ`4ms$M0}na)kN{AWAbIU)8toC6ZO$gbcVuwW)Xg05G=tjK=q;Sb*R`ls@w-*677%c0=1m8^O4n3-Dc zUYi>4bjs;`E6a534*STt``E3{JykAe>{&a(vG-y#XV&}+j(vOAIDP%19cQ1bmho+| zNsjNlhVxw6w%+r-Bb?_y*zdRycp~G%(u&@T$7(w-J@LG^`iHMNYb2lX*0g$O=qL)CnBFF*_a?thl62gzCTE%qxK@^mvsdv4z| z+xuCy4?P8=KL7WlJ_p{GZXY=N6Yt&UUbWvd#^a4$8t)mDIN3XRR-|W0zrkKtLL<-6 zmJaW*$TFVc6>EFr>Ywz)oh|CU_mq2|Ab@g-A{YQ)qTY~zHOo>vDhr{gsKBP6Aq2?CS3^gJh(B+ zo4mWKXW~muy_4Q+ZGY&|^4`fWRRSi!4qYPq2>%1@Y4hsgg77S{Y zz_u~k9sXY()Qa!&`SD(e|3W+%;=>RxhWIhWlOetg@n(oWLp&Pd(~9EN5WiLw&xZIm z#JeH>4e@Y@k3+m1;^z=ghxj_g+adlA@py>OL%bg1_ln~A5Z{M*Kg9nbIRKIeAh`gN z4ZsgRVyi_1p9;zAg z^wz%4t_vl4WL$XVJ;#+-PG#7-Jd|E&=Jt$27p6rNar>P`_7;sO=G^8i_Rg$`0Q)Lu zfc18INo}sP&BeEUZ5!-&ad%+Ryf2x4MQ&kFrqQ3~vZ+RY{#~@*)Iu^aL{0;4Q<<>q zRd>vx5JQO|?MLrkcvnk5wgM3Cs z`J9l?swkfq@|hLob3;Bmvcp8mrZZ-C?rBkc zq~0>DpS|Vz4SFlRroGjt_gPn8#BMvgao$Dj4(~r@zoVDm>iAT)-O*&5)#9`aM}LPwahOeVPQ-PoDB$JwW=GZ%Zf5XFU85TTGLRg_4DL@Oj>AyEs7TuAgnA{Y|IkVu9^GbExRQLQME4T)|=iEwzm z0Rcoj-~aJ|0HUAQQ2;~*5G6p=08s=)6^fz^h&mKSArO@)ic%nIfhY!|8i;Zr>VYT- zq9TZrAZmgr3Zg2AvLNbG6oo-lrYK5-s7+B62T>hFc@XtM6bPqDto`p)cy)KmH3s3z zZNPcUnleg{eQmzcvvP%~(T<8OZ}(QJ^SxCt!T4Wff^q4SxYi^j|JB!|Y4y#0Iaz{c zmMl&#t)MS`IomlmZWm(SH(So_=PXqoVsB<^WJgqmr0_lZ9j?>fx@H99??f@bN+d&~ z84}U3VAAoq8ud^x>G=I7^-wVB_-!g4{wI@;|EZmc9NX-UX6?nZ=^;;&6*D558(<=__uT_fdr{CJ9wRT&BR(j>yea$K*E9+GYhniIzRn)4U?(C^{ zpsp3P?RHP~cLS^%Zxr{`T=cc2C$+KHn*Om>dq6dNosmcM+e0tg>vsQ0uUBcGJ-Fd( zdT{ndPlLdJ=nb;=dKw;e>y6Uh@igB4q!p5~$kSxyXshYeM?CgtoK~}V%ier^bE`%8 z!}gYaD_Wt!arRctzq48g+-VOBs-TB`Ro~v`;hgrhY zasGVWG3KbJ(~LK*&e5AaUE-d%x;A~?)2-`6R<|?z?cpKM>EYYovqzShq(`oN$=>~V ztlne622anO$yU_l7d^e!+-*h2KI(DKZEy8%AMfdtRKw~U6zS4Gbc)DlWb77Xux>x8YCsQx>n`V#wrB@R+NFhRnh!rAOh-e|gg@_j-V2FqzLWYPLB4~)HA;N};8zOLs z$RR>k6tOFc;31-i2p=MThyg%M0Ad6XGk_QZ#1tUL05J!MK|oAGQH%m&7K&mR5YvDd z2gE!e1_Ch=h><|d1Y#%*5EFwK8N|#Wh6XV;h_OM;4PtN* zlT#FANkWVgVwQ?xm=M!c z6yt=Lr=l1r#6%%R3NcfNp+Za*VyqBzg%~WvWFbZiFyVCQ8XRE}%IO9^!#fcYVUy94> z`SRe~_Po;7+jB{W@AoXd{e_;dR{vtpzl+Z3`R~i?_q=xS-k!_$F5L6_mWMYlUq5}1 zXIb3l6)&g6zVY`vH?Mrmh+XwS{mrZI9TvMLDsc0gcXo|UZG3F=+Isb3-@3JA&voTG z?^*ZlH$C4zdHbIAJGS&pONqKa?bG5*-dVl8vNyF^*oKKGm%RI2g|PPqZe8;JgWra2 zY_oF7rlI9iKdAETl61$J)DH{4QU4=-N9soh=G5P8doxuv@?3}0w?E!B^RwzlzFr#fMfrf3FScz=-;~ z#q3XL7I9!|)tE0MD?}U|bSdUg{cj@X!29N9tY3JaXzx`qAQhGLLTEk^ar$ zw9I2G-b_Ef$;v$OPFlpt_T!_vPm7aVh;MA!5S=euNHJW?HsmejHKU@li~GeOW{a{k52q+U$rx+dpCg{bM6a`P?z3cl|xRjJHor z*|lTR%Pk$9sXgC2z5Fa^<}DLjq*q92o>_5VrSwXX6*F%QJC}Z2{qHg>*RB{*MXIH0 zq3~PxIHW!~b*mje3 zw%r#G9BjXQePV}o(FgCCy)d!k{ALFoW2Pr|n$at+bM)}UE^+p_u1&iocI$deT(>h1 zo(&Iaaxi@Rz_XEMw1bf=+nw!x?DWAN3t|#`?kpDNqGQ8ioX?(&=-s|* zOrHr`Bl=dm6w|N&%7}g!vtkB>J{vLM;DyYX8Y|Lc-rtisF#DPGyBDWr-jg*UJ$8nb zIVkO}^ucitXAVhelkSRI7&CO{fQVrs(_@CmhegDd86GntylTXVs^Y7-x3^? zP&_LlVOhzTk%zqzqvqDly#LcZ=?^3YWR6~*mOiHc*O~6wR{Ge`k2A-Oc{qK1rDd6k z(Q)aC*&oFug-wr0%6cs(x$^Lcm*#ITY3cTohHfuu{`Qh)a4+sjS0%UIt1R_8U18VsO`5TTJG49o z@lS|{LVOhBr4T=bcq+tKA>In{SBS?#d=}!h5Wj_ZF2r{s-m5773-Mrx4@0~d;>QqA zhWIkXn<4%T@o0!oL%bT|*AUN!_%_75A^xo>9uDzwh?hhBTv0q7;_DD^hxj|h;~_o| z@p_2gLp&ei`w;Jk_&+2EC`ulH?l0?8+koC3)!klX^vFOVDq$up2#1Iagvl5-$=M^SPQB>zBi5F`&l zauFmSL2?o#FF|q>BtJoN6eLeUaup7m|Aw zCI3Qlu%hH)NG^utV@OVh1hU8~Rj)vrENUnzDYe>$9s4$0+^ zd=AO!kh~7b?U4Kq$?=dp56ShAd=JU_kh~Ac{fd(RA#DIfX$L^s0*cZefV2r9?E*;K z0Mb5yv=Jcf1V~!}(q4eH86fQjNZUbC+7FO6grc+~AZ-anX-`1f6p(fWq-_CdUqIRz zkah;7tpRCoK-wITb_b;G0cn3g+8~P34uP~q6s0`^X_F{Qy9ClUfwWH`Z4^j51=3c5 zv{xW)7D&4V(sqHgUm$H5MQO)C+A@mLo`JM!6s27QY1=^BH;^_Cq@4q4>pXu(k^PzwuN1qNH=&ISzL|;56W%wU|S{(m|Hn_!hrv{!9S_cPu}3qeb!&!R_;G; z)Iagm$F`HpPniC(%cKjiiYd+9YuuzS2*v+A?YHy*qc{Ou%P{X>H?8tjPmHQd-SBWFREA1@LZ=xi3x zf-c9LEy7vQacEH z#vQZYb#ub_dY`LX*HCA-Gyn93hxE<}-#*zFS*Ars z&e^+tEmxljna;jJMO^**zvb+A z@sMjk=mO_}Lxp@XHBvKT-v81!F#DewcQ4-PyC-XMM(m7Ne1p;kXAF*;>4_AkaJkb_5_ zkJxW)tV+sRLb8QZ?t)!R=hr=ACxl&?)mTAG4SH8**+lgP6^~ z;f|6cVJ6Kvk4rg#@gk1T7?R6(x8f z0SpOZNFYOk84}QtpoRoCB)A~~4heEdpestSLjqn=f*un1iW2+~0YF3m5duUE5J5mh z0TBj791wv(L;?{CL@W@&D2iwx!ci3QKm?>HB7z7BA|{BSAfkc@3nDIvz#t-n2n`}O zh~N}ObP(YwiufP`R1^_Hga{EMM34|sLWBtsC;XoaRE@h9wf>5h&=d0hRZGZ_joG(9 zzdNTR`l8P^FWX;e=6+w{2Yvn`?rc|4*JXDxr_WVF-{3B(Ep`RkUUUcgA9I!R zJ?bvK>jhsKZ@jzg+8MrbOCRxT&)@4SKWl{lmWdHEb!WK0;=qQ!N|6oxx3($myRAVf zf92W@TvbXO_g5`k%2oArpu5`syU9wH7wh4^u_bFy`qg~_ z1xwbtlPf2?lG9#vPdtCOYf{Ri?uRnlyCzSKcTY*J;hH))(mm}tn@crpfAZqhPhHr$ zso#^{!+trfX+{;~&I~|@s>~H2?N@nL6J0TrC$o5VlWx>*(w)jOXq23e)awo>L>fFO zc~T+AjXUvhV~AuRvVr-1b|N2&NC+S!h?F35f=CJ?D~Plp@`6ZAQDg>@8bodo$w6cX zksd^T5D7wL2$3R0ju1&gWC@WbM4k|dLS(8aQiaGBB3X!RA<~7&7b0PZj3H8n`3t_Fn@u!m(+g#0&g?bKChAOYOU&R=MTZqzy631&4*q zRr|x^qtt#}nPjy;BD|^EA94I{wI3f`UhV&tmcsoD+UhS{^6qN8eO97@{mL(``Oa86 zG1|TS^kn~$fv5c+AN_mIvLesF<^H_dKwIWhtNb|=EA}ySCRS`|=1i>E zTcb1L$mtf^(SWkr(QTD9HNoPFa~kvEop@@O_M68?o^EMXJ8+vBwCz@_`iAe#8Y|9Q zH5Ywh>Pe0CTGQ8?wFi{Z>x|s3-5z>UuiO1?tzM*6 zdRR~~E$pkRdYg-1X>B)M(%XIUq1N70+3K+F-`X9sFIXMt|6Oy8*<*E@vDEAwoo023 zd&2D6)UvvDeZai4+{0G*?a^lV`GtC9(Pn03=5)RL!3t)N1uLzdJ4c#Plb^MEt?6k- z$3AE|=Qc5Ww;yQrNz%-|LG7%5{ZE_yE>*V%gnniYIMh~;sc}k+*%+h`%-*iuy*OLH zCu_A9JHw|BN}Hz*j(b-hk}^?qMP*q-XFh8V3-MaR;~zBR%DiBW2p?#UI6lLQ4{m41 zZ@Je>2&ir*EQ_#49=>9ZnmafrI#wC5U{ zocI-a+{L7>*Guz-o@zorT{Sph&ezE z0%8&nqkxzN#4sSH0Wl6mF%O7=D2j4 zF*AsvK}-!|Y!GvU7#zgpAVvo#C#zJtSBZ7F=9nAV~8OuiYY^k8Dh>5gNB$i#Hb-=4gd0n9Vl)dCga<0u>Yo* z)CmvD*YaSyq9IQysn56UYJwd|6>28j>V4+^1oC++4b_h9sRGA z?&Vu@XD$DdV|L%GK_9wb-4W&b_oaWkUt8VOwQSGd-LF4e-nIPgF>cS)5Z{Ugz1?rb zcXzFv+`_#oys>L_Y$f-a;Igha+nsZ#7Wcc?kx(z z9Nz9fwsVc|_@>qV6Yp$yom{@n?O(Xsb!zr1_vvZ#TxZ72b)OwF(e-U~virM^_qfhA zz1z+59p9I0@4irCpzp%@82`m1?R=Lq+xjo3SNCP72KleNBAw0G=`Snp*yj$gzgy66 z)-=i8t!o!ow=+xp;UPnO;oJY_k1W&47rF9&fA`~cd_5M-a`)Wna79fXKbsUoIj?$;l-rmYh^_LdnS_Cz6~*CKNy8N?2CIJ@W7l*QmK|{r7)*%J)E0kbm^@ z?Y=Snv;FSbt9@fbeg1J{=K01~de@&AJ<*q#z0aK#HrJJuwc&>P&3@TcoNHV>&b?mq zN2iR%n?pm@>qeA?6G*XoyKej2dFr@LD~c zypDN83>;$O5F>|}ImFN*rVcT7MKO1X!7GZ%LyR6`_7KB|m_Ee#A?6QB0FWdANd%B& z07(dtqyR|_kmR5!2?CNNAc+E!EFcL3k~AQR1Cl%-2?UZvAc+K$Odtsbl2jmx1(IAK z2}V(p3?$J&k_{x`K$UdZAn{OsVNWwjiC-fz!GfOWf}UsMfCQ~5Ng9%<6(w0i61Jiw zZAjvVByUIpha_=GB8Mb%NJ57sbx2}|BzH)HSCk|VN%V@6>>&wXQIb9^=y^_dQ4d_S zgIu_ST)cx^z=K@GgIvgiT+D-9&?{(|sLO>v-7xuuec4x^rKL$dUv-wY-deb>l6m!g z{o9ku>7R8fq3!5@O5dp;)jkj1u4mddYr86~)_3_{*LG)bwZ8D0+McYH*50MBnOSMi zTKi`G!}O&*XzfpMn+K*2w7!g_-@Bc4r~&=nG5S}x(C^(=&*}FL(sTN~v-M-D-`l6t zZFX|`CCfi{opx$=mUVjC2J_4quXT3Fi{`h{FIeAoeAGPGbcS`l{tMdow(M#ns^!Q=#2Pga1j7E({w>5RXo zJJ6aN+#%_Q)0y=a`#1%>-sA*02j9m%APxd?5r~sO+yvq%isC8|XMwm2#9<&V192LN z+dv!#;yMuLfw&LEfgmmfaUzHtK^#djuaiGW?c+|o{>|ZRwU0}&e|(cq?c-ML<5&>a zf;bn%y&w(-aWROKLEH@DXb@L}I2**>6vg2nE(dWsh}*%NJsAYfNTB3S27yDe|A(0j zqTr&2&hhxYE^3e=_gSp!nYjGL>O+2heyf7^r2g~cH)8#& zQ6??9VugR{^iMzk^D^3DdK|sRG6$42v!=Tl{~+5kmQ$W3`7-`Njf<$FwyUd=618Z4 z4V2~=b3+Z3(VYWjbmu^Bl+PU^5hdTCsPh+NU&=kk{)B$+ z15-N~Uq-fa9~@lMIMkrB`*3)B+gG==^dAYXVLNjAR{v3XH%GUg_kVNvfbH1M_x;B= zy>C15)Z6|uV-_1{n+@}SU#`1xp+u7H!ufdHMVW8lQf8#>a(Y`^c4{Nrl_fzkIb9hU zY@RIx%}>gNbXAR_LrWROoR^H^j^jpvJ#wfk(9T`YI z!6>_SkqjVzS_Y3lA_K=0WzhHt@uvYYSUg+?iigP{@dh$Lys8Wi?`BjfaY+V+2OCvS zS2n60C}{+3yjkPm@7K zUZc~D`9|mH7mO}(Q;e=nXBgeO_I7tW^QN@1AMl57e_qc=$Sc~_uhS&Rz2UZtGzq#$ zlYsKW^wH7~xK8%JrZbSW;|_Y}Kpa9*Tms@05VwFh2E;WW&H?jV10~h#xCpP~BoH@& zI10p7AkG4D7e#Rxh|4I7(?Hw?;y4i3fjAGueIO15aUqBkLEH%9NDxEG~oDC{>V*`hyh|5uMItp$_!SNuj z2XQ`#`#~HK;(`z-gt(!iI3mOqArxHbjnhPXEc2Zy*g1t*8NIR!^o6j!I<>=fLcg2PjAd5F_f zaC?a3Q*eEV^F!Ppk^vxD0Fnt5B^yx42#S&wC}ajj$qp1U1SCs9G6f`Cz=8#yuc-R@ zgD>!0kO6JKaM|dqGvjOtboOf)@^p2NOLl61`;{eOy6w5v_Ck+W)e9$Au@@QJ$|^cE z)Kjc?Wvh6n+dKjG3s%72dwWW1d#sYnw%7yxX;vxUdQa(HU+87L3q57muGh;gecP@* zzfdnfE5&}x#OZp41jAl&;BdWCJlHblV9EUB-_&WB;T!~`kVfvuj$%zk{1$jT&I~?LcYa= zV#ZOj1JQ2M`-Y1P2fuM0gPKK?Dd9Aw-A}F)E56A)-_iVM4?S z5hz5Y5TQcE3K1+sv=HG!#0wEHM8pswL&OXbG(^-8VMD~NSTG~GPCc*$0xX!3{EALJ zuoMFE!I?d{ts?%&+2{WhD?5LU3 zX;xj!=`_2d<#d|u*K#_|-qUhA&Gwl&on{-%oKCYB&74lNN6nm0vv@P7(`<&8(`k0E zmeXk#q2+X%HPlq6*;l2toKCY7n(8$31e!UWX2;B&PP0$UoK7>3nbT?ZwAroe5To0f zjaqofWLx<5SG347gKd#3XKCG!JLGieC9~(wSR-okOtaUTJB;Yq1k*WJH+r}4VfINX zZ1fEZG5hsDX!N^O&KwZB$rx~`u@+O~OVMvxZD96BxiikM-IMi-+!41;8yf1M}+qT4z_BVd-gV&8R)V$ZpQ|IJa<6&)L{sFNVNJ_|(1zEW9x#9S z`lEi{A|Vg+dpzz``}sW{_0@h}kB3NspU>~{sAsaD*W)45U_ZadM%pp>T$Q>eii0mQKhsYmd0T3Hd6f1z( zfudLf#1<6A8X)#S!6G0wLBT2@c0s{1AhtolIw1A|u@H!jK&%8}CyHVz5L;0cYk}B{ zqF4;XW*}Aru^S4O1F;oxErJ*k1X*-c6i}92 zz=ciz?>T8w6;&Kbx`LslHJ@{kRHYy@OOAR9wrgW#{S zQDxi8MkdF^7I-l`sUE6G74~jkJ9yY@}m$CmHYo|VnJ#+v!?IeWLKpRsmN`PQRW zOJh=w;>G=cCMhfX;xfwLK{fN(U%@SuT-Tyo=aMMLiXv3rWKWMgwJ`_8rcp7Nu#^RL~j zU3g)pcJZz2wC^XojZ06p5%XMVgxpz6E7tCxGND*2qeO+8a>gUvDEW=eDBb*`QTl@# zMwv<<8)epAFv^~M)hPEwb*=o~kF*Li&ubNPJX)oJ2Q|whPij{sZ`LYLnXO%2d#QF! zzY*Fs-_Mds6qx3L|Dwf34ZQcN)znCTrFkM;oyNt~cWP%+=zP+8QkqN6EEFYa5AGJ8Ov-HKXM> zO*PwrPmNYj9W`2S++wuZ+{9?R;u9_DiSkDBoY%E>Gfx=y;VZQE1HDFv)H}75*N4-!Zg;lQy5HJFb2xh&*Xo^& z9*&kqPkWTn%Ua#&9Z}ZkQ|Y|X=h88w@5zHkzmInq*S)*hxPG(SxFP3&)_>JI+JHx1 z(FV@@C;6r6qwd!R_q#(Il75Re)HX~T7S&rD9@a@4an;Sn$dDn%$ba`VMt#!J82w&? zG3M1q#@Oep8RM3Q8aKMm8sm*GjhmeN4ds@>9RpEj}aHf>V%joRcg z&uNp-KCaz*^geCMhlVzF>&@D!2v(vw1pQb?No`1aLsA@) z=8#mUknWI_hon7))TfaC6fA&(4N$NGMX>`4mH@E@3f2Iz2MQJeu?Y%R0kI1TmI1L1 zh;>k~4~T_Oun~xrD2kmxEQNxtK&%B~FA$4?*bD`$f!GZN%YoPq1?z#>4+RT?*bu~u zAa+E-k`%?3C|DB(d!k@b6l{uuRZ*}j3YG=2Er@kN>8V1E=Wkb(_TutExU2(d&(u|R(4)1WIvvz^(kJ5pmM^u4m5@Ukxc%Ebw|SJE>#h*T5By>9gE4A8oEP{4SfnqZX_k;2lTgrRgvtkTqx6&;k1gLk@|^a~7f-mq zyE?`5-COTl&z`x}e(wE5_xaZoJQp_hv|e1@!~Xq}1nZ^wE$pS2b#{j~xGB4ub)!41 z)BsP|dwty1zi8*F@x09){%(JJ&HHP(YyC6XUfX%WUFW_Cdqh@oYu%fQ+3U4BZ>`_$ zki9|mgVu&N&C~GgW^1EU-+3ZGTxyN_c8jO+Yn$9n_N??oKly~a=}YrGF?ZbVZuamP zPxB!|-B#mHdu)ePcbq=j9-p+w+QOb`Pbk;Xnph#n-m=tgw@s^Lw`~ixwmKT^Y5mNX z);3!!c-r2*-I}!Oq$hdI8f&|G?|JM!-*&ej^`fUk{Azbf`h%X1S1)j<#=c-rJ?(U- zUA4%bcCwf?{oj-A85?i1c7Cs)Ju^phcX__Gz3ZE$t=$}Vc)BlH<92lT&~xo{m%B&w zH};7Myw1{g2)LXDTu5TMOqMfK_mu|8ANIjxj`fcksU;O5cxqQ z2$3N~iV!&}iXI1hDaMC zZ$*(fMCK5wL*x#TJVf>o=|kiXNdZV2KvDsc4v>_9q=llS1|&TcB}E`<0!bA}xlPw;6JDlCxyKmNoktRvQwXFpoUyvOA`R7lf0bSf$5 zP{J)88yG0VWL)0RPtc3AX5yDfufeCZm}?j_660oz@}>O5*0p0vg_ zB6OW=WG$C#dB&B-i+rJuNqls^fCbO|aa2 zZClHP5!EcW#MZV>%-j#1IlkMTsROo{ADvNL(SYg~S&UV@RAKv4+GO5_3r0A+d+VAEE(> z4irTT5Irc0CLp?iXak}Th(;hffoKJy7l>vcx`Aj1q92HcAUc9*38E)O(G)~i5N$#9 z1<@EpXArGH^ajx!M0XJFLG%aFAVh}{Ekg9DD4K-mQc<)C(I-Ts5S>D_3ehV>vk=`X z{sG#hd1_6z%6(jNOdQ9(6Qt1{t6Jea`ekl;wBKC@85r<3(UP>h18wdC@`Ozfl%QM6 zhg{;*c%k4=WbG4^V-gE$T=Umuxs%dDZalU;Q~kCB>z19gR*9UET{Y|lOSRDFBdVSK z+!c28PxCH*|L=Vy58OmFRp7#HoS+rOr#UY81<`fa}V zG>AUwX&7#L8kKv`6ItS-J@U*6d(@%hj>c0SbTr=OwKv)Fsy%w`3-+eVm)T<$EwVS8 zJ;&aB;$*vZ*hqWqfQ6p82{(G;lO}pv^yuzMsMW`l7#HhFylC^-!n@gR2Wxnwab$13 z@q(w#{>t{YE34a+Hk`31&pB^zxA+6QefU9p`&p+vs%iqWN0%u1gMXz;|6t*VFYs8b zDZK%eB8R=@@epT-*qel^eBeItOo%-5)A!Idjf3NI$XRZQe2h5Z6%L_Y76k%KBp}h? zQ-}y8Dv-!1N^~F*f7*6EfZFIYd*JIuk}P3Z|#K_^*VQ$j)(~# z>ve~`=BU@>RlR=ZN8Sd*HtG$dJ>EtwZ_*>nJ?V`qcZ(VIX=g{{L$ByfwvF&czdYP* z`fR#4<{xQhvnlJm&1VnOt^HP*vBNs)aqVBz<2y#_E$S}U6B?A&6GP_eEvG%8xBT)+ z)As3fv(@{X_13QqG~2wmRByX%p`P@>EIoPFM7`Z3-F5rWK6?A{-lo7Zm|Y~HZ5t=a#v6m!4} zwatMu8k&Rt(MHz?lr{(7R!bjJ?UFh4`b+xIZ$B}IxBf&QZho(itns=&(qrnQi+^m6 z&VEfF^VzHBSYx?9?)7Enjrv@D{IWUbP4-c`bJj@n=Ek$l2}3i?Tgr?uCw3X3Pdu7# zPHvj6Pu|+ZoKn7tKIQ51=G0^5^=b1?n73^^p--PsPM@)^tU2@A<9gPjW9F4xb;U!} zKmX;{{f~cgU+gE#d4Eyxv*H$R5tP=%N<*+_1hWG8?|B9vgO0YgL#5i&%~5J5vk4G}g(+z^37L=F);MC=g3Lqrb|K1BSG z1b`#~BoQFVpeP9eNeW0}K#~KJAdn=1Bnl*1APECW8c5gpfppBqJmt6(uPliK!^b2}w{$l0p&{ zlB|$~g(NK`aUsbINnl74LlRk0k{OcFijvfj#D*j{B*7s`4oP%KvO^LclJt$R4Y&}-lGwO;48FZ779d-S?DGE!7%%XN?v)NX&`NX=Wby#IHc0jToH=(*7 zpA@0Dxc0oBP^*}p7<*7pyl_aj)i`U~4!o(iI=A0!y>XS^X5S{W?TRgC(uOC@X}Y4tnmX(uxEjN*;-j2$t0=g&g* z%r%wtF5BaLUGKf*477hrgvh@i9TP4c`lH2y29L%BCw z*L!JJ`=1Xy=Er5RoBd0kS553#Im!^*LT>Sa&OUkV>_YaVE(;|;F@zHmQr3vDLP84( zucCw)5@txKA>oFE91?a&=po^UNB|-Oh!h}lfJg!&3y3rz@_8Z&OfU1ofe%WTnOteH@2 zf|(fC)l9tD%d~}$(QOA4%~t2T=&d(}nQitr)7!30(34)a=*e@d>FpMu((S{~>g{J& zG*xhzwo?B6$Xfzd6I%N6f4EEY2&o3mW)Ga%4D7o1((`uO#cu+S@t*v5`gh>PS=3H^ zA|F$9X9)&CL?zDRpXHrE@F4^P=I^+l&SsgwK!O7a5F|*DKtX~92^b`3kibEL2MHh~ zh>$=+f(Z#IB&doKSV(Xo0albCLjny6HYDJXphE%=2|h#s0&$czCPWAjF+cjk4w zQ)Ez$o9rfxXl}%bceS^j{)9LfpKm-C22}CH0A{K~X6h$-;;XuR#5fDT~5FtUt1Q8TOR1jgoKc%>e zlJaF(dnq;K#$!qOdprI(_}ml17qVl4R0I_umJe z;fFPtl;!i2%-@}ePv9tcVP}Oe+jvro3V;Qt*k&sqPNl#<>#u2+Y(qhZjeqq@E{fD> zC{hA#7TK|Vz_D(5ImhqcXe`Pv%dgmI4F0hEpz%nf(dwo?npl59qtT+WAnzwdc180V zjnUGdNtL3W`3VLAsS^lDF!+@J{)XeP^=mRlr@q*`fWKSA@%I)~QPR5+<0s; z>&iyc(ch;t0m;_VKtMeIT&q3$drvGSW*%VIBSEl-~{Y@fH) zT759nX#M6*Mw^#j(Aqw=OiOy?b0c})2S&ST)iwLr8;th-AD3gy_h~6f?-?CqUNchb zl+-#^{6;CjKuR77&c)8Sy);OD#m&8|lg8Y!>Kjw$5gF2~8 zdi&G5&=VP#RXnLYdhYY;WG(^V8wrA<1OgHaNI)P#fdmE;97up5L4pJd5-do-AVGr! z4iY>_03kty1X59gNg<$+pi&4dB)E_OQwTDJKtqBJ2{?tILjq4B_!I;H5dlO96vUt? zf`EvEf-oTBfCz+wNGJ#eA{K~XD2N6k917y0ARvf{AVQ)bCJKUre~73^>-glD0(+J4 ziyipYPRV;L4a5gK9^O~pQ6=(GkN=nZ9ecHNA?~m*HBa^Z3Dz3h=Xk<5E;MVdo@M@` z^!~c69`$({XX9PxoK3cT<%}Khp(Sp@T1$M=bJDDzCr$eMq&YuE28P`%&G@;7?O;za z+)+mBO$pL$?`*VPd99K3a#JIDZoEtaTG6nNsA9CA9WBT8zLiNpE6DM@4~^86lX4vI zWh1Trds^Cwy>jgCMJ;2;203Q;pd7DTBFE~UH@YsEDSfmaFuIS|rEk_G zgxE&l87;5P8)yXttU|SV(Ug<|jpJuSi*XQMNQn6a!VL4<1n<~cCghN?LqZP;KSTl$ z8NlDnNJKV#KGK262O=Sej382i$cciaD9DO}v=l{N6eOl7GNT|hh}I1 zhDaMCZ-~TUmS~}%-H1Pt%(1c-$uTVoJ8I45P$cL4<5LWI@Z^}0Thul(K4qZ#5s-UH zvms9ZksJSSam&K?BjsaM34XBD*`+?~=WKy|0k3AsV>Jk-urFaVt~7Kw;K&{2LwRgj zx_icjb;~}9s1kXTt7_PCIS91OQtj*>Bkbr0uIl^CNlM<}3g0-&U32wy?pjMayKB#H zFjXOYNyC z>ph)H&a$V~f5ek^;t5Yi@oAon9k+Wrf7aiVxn`)R%l4`Et_xBtOMk%dH;Pa(pPC{u{EqC^`KaY)oDL>>}-hyoxgfG7c?28bdcs(>g1 zq7H~cAS!_<1)>&3Q4B;i5alR}dLRmds0gAYh?*dZf~ZPSlm-8+UGq>X>xWt)iiM~a zqFji26-B`i6)TF8A!>#w8lq~5vLWh*C>&;qCJHj;KVIcUF)_A+a<)ZLa$21;>#lbIc=WVjN$V`>|XrBaF)+~x53haErda#-U`Gc)|XCEl95 zhv>Cl@H%R*f6%P6sJJ)c;jMbzNi7}qmNhl&_uc4dFmjOI(00<>sO>sEvPOMJRIMXs z)cHQ%Cfbc=lO1Ke(Q9|;{QbWe2@i zmr9P_)f(u1qW|sa^KB`;UzG~pejgmsuRC+nd;R*i^&57*=k5Q*7v_MqFM0=N<(h*Q zJ?Pa3J!1}@@q%MWyZPqO0gD{N>WnvsCrx&Y2)#!iS*xF8hVsp>^3JiJLs6~ zd&!(qakFEJ_fd1|iKUKd?%T}UcFb~2fAkT3#+u=dnNz3fSqst}SMGxNJHA6)GhH~ z{y=gFl1GqSg5(n!LfpMiJ{#BU&;1MwY*_dxsy;z1A}f_M?c zj}*m|Aikt1-URU{h(|$u3gT4|zk+xc#J3>c1@SM4he3P{;$;v&gLoRm*A&IuApQpN zIEc?dybj`b5YL179>n_~{s-|uhz~-%5aNdrPlWiQqVFZOj6brBM?!oO;*}7;gm@;z zHzD2$|7!mE!w!}HVNo(EL9A5`kg^uG*B8pxN@2I1$8rE`rsX#qarDYEPvgTaWmjoM zMD$zLWmoCWh?te0TG9y2m~S$h)^J6fd~d78i;Tp;(-VVA|i;86h%xhOJehf3W^G(Np|8@LE~_Zh6ai|BQV_XpaRyTI%Bx9}oEoJ~vAq ztHU{ke+`>rOOCM>=1~59b@@q_kL9st>Hf3X>z2JCyZb$zRm1KytA)<>RXh8#9(Htt zv--XhW{vIboZ%b0xN5FWa@AVW%vF1S9ao*{7FWc$5LewBPPyu3e&(v*W|yl$^c${* z;p<(E%01?INO8p{b#}Gr(a@DptEnq7uCy!hVnvrNyn)4bz~^dpzLcf)#+|M< z`;J)Jt~hB)+VHj|dCq&5c8gbA?89HQw4ash>X7=NC1vO{u8s|FwWOxZcXcXxoh7aQ zcvsqqNv@3I4p+vGzOK%n#kn%qv~qRX-rdr5LAa||=2qF&-BFmc6*ivohbP`bOXG86 zbg9<7X&dKf<7zWyKHb25;2C_*X&{&R+5A)lo(~*oXPQUE(~1vMM|2ABUUFGn(GRhO z#Fs*hA#sMpT2bN+i8&CZkK=c982t+3ktw8jmD4K!j z2BIBB(GNsJ5FJ6Z1kn>jQxIK2v<1-@L}L)0L9_Dm{_aim{x_E7~4YOEL3T^T{HLn!%>yW9guv@U)<>*rt-Pd zKTPEz4g%*t~P5QH`*?L%$2mrFp_6yx!O$}V%UcbcD3)-(df`I z#g)>ck*?8jKTq)wK`G`R$XK*{xms>X^&_Ny zS%=6>rpjA_&LiF;vY6@0^PId6Jq+RIm~kuRG)4G{3f4B3LX8swY8_JZ@N(;@r}I3K zfWeU?5)qd3M~-~1mh(rByrq`&M~*zLmh(rB+@qHBM~*z@5+oIhfu zt6I(_mkmy4c08s%%2@o|v6ai5ML>UluKokN| z2?eD<)B;hAqNoO<9H>(KcUMnQS{|$&rHtG}np&74cXvLvsphupYesEz9MGbD-fs0ca_t{^(^v;NTrkQIfjEM$ctE6sYb;*gbxYyixX_b^@RueuRb zw``WoE!R4}(EU-?fby{1Xkk`Zdg|FJPu||#ST^u6=kmc<87q>roGb08ji+i4cCL!p zZ9H9ihVS3^_0Ue;u*7-#mMG&)o0-n9dp~DT~j+r&?_Z!abK&Q{!~o&Drx-?_8t zGHdWj=Y{?6Xct#Zbbh}{GcL`QG<>{;7BX_bvsl(hqj*L|Ux~rTjFQn0_)4Wj$dS9Z zoS_YR8llI}`O22OQ!D#!GiSLY<+bw9zv!&6wVhFM>~+3M&)lL}y4>pwJM)H|T)NR& zV^3%4Ds;+OyLWx9&fT9oBhsd6b?>U~tJij-R)6f{z6NodjfM?VosFt=G$KnzIwQZi z!HhcWay0%lTyOGr6-V?7(R$NmgZzW@=TCPupMINez44#k*g={e=V;-LPySSI5j)C} zPSot5`mcTO=S)7$o`xn|NMFFTUwZP43IJ>j*F zZKt>Ie~VWQ&QE&E(J^MFo?7{$Lk-SvlI%$Px~HB|?q;ulaDFjI=DKQnm#v2!{=xZg zIvD5cxNDW;+FqOV9;5Hh8>GL|>mQ^)&)etBP`zK}F<$>5{VrbrApK@u{~&#f*FQ)< z#^E2NU&%3~?NJ$||8K{zMy2%Ot>1TyD0xH%>92E){QPYhr2n3G^c$;Xkp7F_u`3qH zApHluH!gU_9KY;V?@i<9o6cF+d2jAEUIyv6@!rzRA%pb$IVM$%lR^5e9g|O7BZKs7 zI;QN2)u(>-y<^%-SL?TJGab_(`dXi{_JVijMd9O!6enQ0(SEI949a z9ul4mf@BdSlOWjy$tXxxK{5-HU62feWEmvWAlU}VI7rq(G7pk{ijsklEL4agOY6_VR$!JI0?i}AdUiY6^OGy+y&w=5SLLDr-8T)#Bm_5192XR`#>BB;zAH7 zg18aHkzjDv0&ymmaVLmF!QiX~;#4f-RuIQh49;3mOudeK@jMO&^Jgu1Q!NK(Ex^%u z9an=m8_b`@V5OJk{8K8XN9;c#9<*W3vpVA z+d>>yQCt_|yo%z!5C?|1FvN)=ZVYi`h%3Xtt~3AiD11Wmz)^U4RFvIAY`>J;Y8rC) z_WrrK+BV;{UAJ!GJzA#H( z`?c1Ywq#L{f0mT*rWsj&;TK_Bwmll>EBB?<+3NL2^HxY5qMWpmHFT;&TXNtlZ)elL zqgjN;cknCNe!d3sReUO62l+}$mb^w&K=uv_``TY}NI^bI;}*K~)T67^=ZA=w@8#QU z_PpsCq@#Qhwj}sTvXatjcytw?QSc|Sl2__fh_WBY=fywHX8orPU%R6QeRoJ?gbr%`_(E{?IIR3 z`M7+MI&{Vg&CGjz)rwndH!XAgpJv&E3%%ve4KvHX@}{H0@>Y7qyFc+(njNlNCUy5- zHT0rhxu@y4x@&dwn)ufoRqDU0SG{_k-C2-2D;`m z69Xe}n28}xM&2+J10!!_CI&U~hM5=`c_TA1NFQr8m*TEtx_=giU(mlPZi*%GVwS}g zzQ|=eI9M)0IN8;DQ;J-Iu%D~#%0aHAms`7%=eCzi5Y}|rN7R=~5Vo@Xmec3<^=5hh z3RCC)(mgl1R^b)@mAvv>^4=kB`d!HI=jU1Dd98x{h|BzII7qOQlME-}ujI6dIJ80m zru?%~$3T!fAA#f{vWG|?B7aB z&Yv4&hg$yCyCF%s@;#(1ByANXbs^~sNnuDDLsA)%&XAObq%|b9A?XcCarjr8Z5jJu zz5kwN`CAn~W&o!?{y(!U|Cd#96Ws1~Nekm&txltaH8;{jMq1@}< z)pF0R)VA7|7~4Pp%6Mn>_u7uP>S*uYH%s$Ay+#Z@;A1(I0h0-0{3#H)EZ*-oVGr`Vj*h4X(H94YhU-jIGfJ zUw9+Gde@Bd6qmV}r48|P9sl&KcZK+m01tZF(fqc?rZxQyuk~>0f~RP$I#{=r>gcdt znqjs&se4<0JjiVGZU=AM&F#&ke>U(YKT+RocV8*5J*%YIzW?oJhk=L9l=d(Af=7WX zerR?&a+8xOTGL;;%6}9%yRDx2T(c!z25<7p@slOp5?=Ek1-{{aQym5FnC(9boH5SP zyUI?pPvic2pRZr@^}G6Qz2ENTzUxk})~{bT*LTDF3v_i9_{AZPfirK_2R+b3*9Tsw ztEpP)CoMBIs(#WkL!;^^T|=g7rJwY<6}svtz3)z4^^<0*)_KxTnyFgpCp}B1YLz~M zYN}THNvA}cs-HAdwfdx=ba|Pom44Dp)k;5UrfQ|1^s`=F^^?Bm1^q`;@c(QpLCto< z>;%D)<$?Kc6pNQAqy81S{qVD2@{_-o_Aa00b&afYk=4T#3CRz0kH~*T|g`YVjB?afY=AbLLfE*u@Z=#Kr96Y+Dljq%h(IV zVjwmHu^Jeh@K!8`<=~-Xu^yJOABY7hiVeYEJn^kq6n}?JDT-A=>N9kLue zge=a_DFgOuzp!;^- zM7Y83+j$e=2D@*I-ST%>F2r_Wu=}>yFU!I1+kuI2#fo`8I1z53`}VAnraYbBeS2tz zS`K#K7MtevVE667M7UzvJRh70H_&}MFcEH``*vU=+(7qjv2*?|*nRtd+tz>F=4v6+ z>$a53Wk`2$9>)1E?{)>-e?)5Y?^f6E-RgR|ltZ0+r`5&8_&;cMb?aZw>^A$r->t8o zw!war)>jERX%KN3ODXsm&+pUz3ML}$4QYQ>hkv)f{%(IE&i|VBmlz-0b6NWl=ak}==8GwWfM-w!`h7UUm4pjGUu&?pbwoZ*TF5fcJ}L(sB@opu$}*C(5eeN2IgFRrTwb!*VuC|Jzjs6wxC{4$jp+f zij6OkQ+&YTRVBK9kyA3QL{zDk#cibVc7-DZN%Q<+xRPX5U^~XAWRd3Y@Zw7cYw?+9IBim?}zKA+d$T7ZPJgoFTD>#2XTG zNZb`A_K^51iUuG$fM@}t2Z$yhx`1c{q7R5hAUc6)1)>*-W+1vz6zxFtqbM4J=m?@E zh@K#tg6ImOEr`A#8iVKzqBV%#Aew{d4x&AX{vaAu6dgje2+<=%lMr1(rA;27Pl{+1 zpi_ueA$o;q7NT2-b|Lyz6b(aktSDNB=ozAEh^`^phJQAFmlfp~J|dE}rmzby8KYU| zzJFm+rE*_p|9DEPWWzrKGQ#kWfGlSCM?fAj{39UWH2fnVR~h~hkZ)-I5s**G;Xy_} z-layZHC;YdjaF+myQ>yH)OY6{x5gZ zq9xhMv;X04H*sdReb@|l`(Ao>hmM2XDJ?o=cWlt!omy*9MDHrA+tSELf!olWOI2saja@{87mU zujdaoj8Mz@gAI$R<@~{hhtzWZV8b`na{ge$RcbkZu;CjTh48?rLr(z}CP=6#`GX9{ zs^$DahF#Ti{vgBVYB@N_P(sNQ!9j)+QkH{*3?;NI6JAJ&Az_Au8WL_u$RS~egdP%p zhy)-qfJgx%2Z$sTMHUcgz~5LNMQJURXFa5($c@L>7;^Vy~46t&LFACairZLQR{nXi9{d zHeurR5o+3m0h6=iCS02xpVTkAMUVLGgj%h$6XU96Ctj?XZ3~Z!upRh5yVdz?B3f@W zv)keDMNFzJ2spXk(#nNyHm-L z5oz_OXQ!Q*m7P(1V0Oli;n|%(vu9_nNz3lCeLzIl1&y&tiPijB-D^_^Ld0E5_U-F6(#&Ie~{q?^*U1EdE@|*1Vk1P zX+Y!wkqAU45UD`q0+9?vHW2ARM%<*5c1cNJJxokc^23#ie#K>NUPtlr>D#@ zH&p#d-?n+yyq$ecIQO)DGj!jjM%tNmugqhopm@pRvQIiJfyT)TIpfHYY3c<1AJJbYHvJ(p zuI+e7eC&&QiyGg16RvttPx$vrN6QkSM$30YoVITU8LbYSHCua+JKL*ui&guMcIK?hI$o7B9;*-O zb4XGtnG4MA99Hv)%mKF0IigevV`Qy~&XJ#gVT`)i$2t0qTw}~Zn{(`nXN+-g_VV30 zXTC9hWuot<;o}YG+%VtG-R_aOyw3S=B#{QYy}C`aA{ z`NjG9E;>J(ab&j4jCc-`dGNPBRYbP(J!C8-Yay8n$zDatU`Q52G8vN1kc@_8H6*hk z*$v5XNR~q~9g^*kjE7`BB=Z#|`zilxU373Bwj1|>I1t2zAWozxZUk{8MR6sFGeO)5 z;!qHmf;bh#tsssCaV>~*LEH-lFZm!Y#xhPu!Ob9!rYNokaW)F>25~qFE(dWs3T_8+ zJc#Q-oDbrD5C?>~AjAnFZU}Kih$||JGeX=^Q5+KDk`SkaxFy6fDYz!YIVrdc;z zD8xxAxGBU@DYz=cSt0JKC=Ls8S%}j@+!o@v5Z8q`FT{Nz4h(T&h!azAV~8VDaAk-y zQ*dWRacBxIttd`S!L1>VO~JJx&JA&Ih=arbO&2dKJuqY9#Oj-NjraG$NeuLYE^333 z_FP4((0g`|Jxi=jjUu$0@{AhjC#jbn7`({!Qd>w%#ms> zmNVLZ=fuj799(6tD%Sm3YukUOx|5!W%1&>eZOtf`mYs3vptbX{+alzYX7RkK6s7TV zfX|VE_mY1yyOW&e?Bw8ZrOfvv2SDU;b^k7W-po$o%v_q#!RbwhoaZbg=ka2?4s z))TU>koASEGjC?SA?prVf5X6eue1#ln-C%S`y;BabE-^Yb94!Y{XBwSKX37Co z-AFqz(#R;@!N}N=VRZhifswf;+UT;qy&N>HV00T_%)z9Ns=5Pv>P7vbD;rwohyTiz z;kPcU3YF|M$hG`ANYz0z`F#sKlRsPOVmJHX^Rqda=D(NUKzT+EvgOU$oIi7EwEP}Y z{AURSg=FSs;TA#3{H2G!f`89nI?6>w{!#OnB3r&6=~9p{M8Xtg43RPgIYT5(LDq^Q zZHT-TMdA>dL!=IoI|a!@WDf)LnP!P5f@ik>I`YT+iaNC^zo3$mT4h7-?tE&V zgWPy*S$ej@sXy{+WY%HVSz`yxcE?Q^%w&Z+DmNW362mv~>5%{5&E{g7J%! z;fK>!mn;#=FG1gmRBbOEA?5e_P^X$Ca)D>~B?!ESxCWjNxPN~C5I*h7=Z7x~DcT~e z@PGAEWXOu@Ls}4dK_mu|8ANIjxhaa|AhJ^w=|SWNksw5d5Gg|B2$3X2mJn$|oXnlrnOYl+hv# zxtlv{&aw?7ySY4N59x2e__@CA{z0KT({J=uz5nY_wcT<)SWcvr7E>P7N8Yau9zbRe zQJpwOMv#Xi)mafaIV{~hrTrDA+HvZ%a8L19e`k%BaX&pZTu38~ub~82ROG&Y|O zcf2}awejliY{%=#ruO>!_q}gKTy4DZNNLAgA!*uMQ(Jp;KijTtyGEWsrJMmpJW|J5KSS=oV=2DU{7Np@g)?B9XSyFP>pQITgz3vPzg zt6V9flnMr$!!nzsD4PY@G)37w$R;YvW zbMET!oNssLv(67DcJb|L*VDJRc}L&g@6I{*RoLy^xA%Z^|M8cc2XbC<9^CPz@1v#r zd>^l0=QD5L==JiU=H+~!4|>S?MaAR3U$6Ul&oF=Y^MtV9toyl& zOURGf&4Knn@De`x7xO9Tk(?}mJpp&=TJ7q!w-)Tvoo2ZbGX zxGbjVjJQH#t0?h>#26B1NUS08hQu5acS!6Z@rP&tq60RtfyM&7;m++PuRnbKJKW#Z>Oim_WF+SjaH}sS{Id_wbivQ z&Nw5+a;=LGob@uf)Hrum}Ra=nZG zzL-T^?;_dPd?MGoi15V@kn3Gch;*v!UGym9RM)$R{mPkm;gr)BUfO3nu*=!%+)-ca zjq9Cl_HFaEU9r=bwBb2l@|?B4c8l-x*@r*mYd>qXvqS0>U&_!0&W;VQ_ob#dot;Xy z^`+JC;Y>Smoin3&3unfTHqOqURdr^rspaglJD2nXApd;Y)s%#-KXAbFYqV z&CjMewaUZk9XW(0%_;wV;29cNf%njA3p^h<)gx9G8z;9P9^xwO0M})4C9Q}pB)+T^ zF^0q$5^F_?HzekexI zAR2_|5TZqh9wC~9=n|q$h&~}2h3FKbRft|8nuX{VqFqJNFGRzNqGO1bA$o>r8lr26 zwjuhaWQi7n&8HvzKo{`@rtHiA)*6@gSZr)^Ol)jXPPI#lsY6OtmYy2m+E8`3Yumh9 z1|3P+Bam(ssy5_a9!q;c(v0KC`Aua16+HY+W9c&4zOeR!szA95ekF;;*FwG;^7ZgP zSOxlSwlCbn1FOP*6bIBShTOfn!Tz)d;J_ z^Q;X*t$&JSqWX8^% zzbB~O>PoBsu_f)q7nY3TuUay8cm>2S6vZ>(-!s#GtLov;HZ$#S z?Q1`tfFq&V<&{KsL5hd7V@tLp)&;RBbys7rC3sOB<;`;wli{Ia6@mdeH8aOe> zKJ5@E4Qc;<;3QrC$vd9+AA|mx6Ly6(DKCp3zgol){(ZV`wwo~g_n)qtYG*sqFU*qI z|JgKLl=E`z7g0LOjmL5=?CK4EPcw@CzMC8(S!Rz}w8-9U_8fcjiIeTtVI%Fa0~UJX zCfw+WPnzgy(WAR3p;jMHVqB~z@uJOR3-4yP9jxJLb>3=kz43yl&Hl>vwkxaKlQx{O zC(k)=Z@2gZyM6dUd;3|ZJRMRu+f#<_@^oyt)SjBM-qWe%EPGo0M?7gKp73N8pXSNf zal5DUXZ<~yYleEdZ1)>dugufdz7K4_qA`V*E_KC!CDgHquBtorFYzXPsxXJr3Omqt zSyZtmA`AapEea)3P!mK^5LH2x1yL77VGxx;lm<~7L~#(+DT?wS>QfX2LR1J*B1DZ4 zMM6{wQ6@y45QRci3Q;OVtq{dRR0~lqM7@flV2FwpMad8~Llg~BH4Io4>Sh^*!z|H6 zVb=A>s=O#B7C5e)-zVxt`}*JPnox(1I~q@UP$t&*+M8^7)gHa}1^b`Tz^)Y5A01i^ zocsJSRiCJiZzGaUa`7q*iDs49n3$9QKJW~76?hNFFaysAnpl@#@=E$e71GGMETkgR zOG_oJ@Gp1OE0GM9h-?%^IuQ9lBn1CJJ=Px4fLAO5{E6s$rgpYN5|ZR6Bb*JM8EwclG^|5jD2o>h@nxY?9Z1J+Z!C|MkRL zdHvTD3-_w)iA7)ZHVnUD`mZNeUH4y4?7ZH1$}+w2u8$l|wj9)>*K$p<&3eotx87{_ zQoZ@ag}QavEIoF>8Z&OfU1ofe%WTnOteH@2f|(fC)l9tD%d~}$(QOA4%~t2T=&d(} znQitr)7!30(34)a=*e@d>FpMu((S{~>g{J&G*yfOhsJ}W;Tc9Rv-kM%GxyBbV{IUH zA;%ao9I+H-IC*<*ev=74)5(Dy+3fTy;+_7zxp*}c#(zCZs#eOCMxk962@#l_n0(*> zBqDqWQGrAT5*FNI{|n^DkjF$;)#7C9L|Y<@`%nwNlIZm#_*~%fU-nNmTiJ zA`1hTWQZ(TU1vG}5?1HcGSQZ&<;3Ji4oK7?k%vSdq5y~rAWDF!K_Km2lmk%@L_rW0L6ihh6GTxERlzKY38&7~NtZu`vPgD@^AzQ5 zpa@oV&#H6Jr3KoCsvm3HrY;Zt=|nhn_f0f76;?uK$drNG2AeJSk`U6NPCh}-5K2#F zr;#_wWmUDS5RUvE{$7vq{$57@X>jD6;V+yEr;b$dB~}o!l8_aJh0TG(ivA`mU-YSP zII$oVrIo*D&%c)}{G#f2JDU%7_TEv+(xaJ!UO&_d-vWe772e zW>q;a-YA_$CuJih@ zTXxa@>-++8Ma_@B>WZ3YE;ynN{qPs?T}Sj9SjDcCm@iZ@F{oM&-ayJH$VTL){9NhV_gkiXU|;>WPx=1gd$S9POv%s!=Y|SpgmRKj zXp5fr`5FJi_V3T-ePNb=SwuuylJ|47;a@lJf4Zyv-;wty43T6>YzoN#KTu`Aq#-PN z*SLl54o_UGHYY^(O}4Xf?E3}wkGa=PY>j3MnbLHMq=z)Bk`hU*utx7wgaCUtN4+x(J1^otBQhtK3YNyVv|RH0 z6&KJ0+2ViRuL+s8GOyP$gDEIi+P%Llnh1r6LZZrt{N3+3(n&q!_dBj;u#CzeN>dcI zK@_Jbs)Hzxg8Co|q@Y3yN~EAh3W}usQ3L8xG~bV^DOsY0A{b>-g1fuTax zWm9-8yHayn{&wxE;|^6{mmP-jH*jRvIC(3TYE%24h3&)mbNaG2OCh#@*_KM+q`fky zCH(8PSNx|2vSi(V{`b5T$3*|b+WaNI%e4Xmx@V7XiaLIh66xDB{66QN6OZYP5B%_* z7<1pAyM6ngt7IPd;#1$j`TsT<+jVH{OW%Edb=asch7_N2_^K{Pjx={pIdb^Ss4pw^ znDXUYAB_6yWQ!@sp2`_@{N1WkPTaY8lrQ1tDG|$bSKaX=UG-*laMkaY;mV%@yF^II zAN*52QRRpYN6&tA+(u?+k%PkipOHC_@R(;1Rp3ZYrgRi9L`{4kCAitBh)svVqd8Iq zEK?1NQWcQufK&;jS|C*esUAob!N3qWs)}W*3sPl}YJ*grqEsKG3KgXqAyo;fPDqtP zsufbTY!uZCsbWYq!{4x~i+=2i8s{)mc|-29AAZtUI<`iy z-HaKodLx%c)Nee<)gWzFM8nF{EsY|FM>IM$&=OTTEh1{C-BReqhGZ_wJ+e+BtH9_3 z1jUd|hHw^9Ols2z#>~W&#*j^fY$jw=A)Bixn+(}(McH&1xWpAzz;bZ>rc?#XR0pI= zAk_k?8bzrdwuUN#R1>7CAk_t_GDx*Sst!_pkSbJ^YJ^m!;(u11R~`Ja3{kiWr517{(2cOY=A5FML z`*__jr#XF&rY67`kYjvy>o(`1V;G>q?#dB4XJJzoI^sYon@*XQvHw+K*9hC1tc7b5)w#QAfbVT2NEJk zm>{8ogbNZfYMZc82puGRkPt$`2!F#+Ub($Q8;Qc903OSsfLUdJ%kGd_QbPqfhU~4| zzh!OYAb|XS<@XB=oUjZ$gLHY1`bASGF1gT_|KueH_zIEmWgAS$*l74y?JSrj>;9Wp zYEcdh1loUrGj>%BiRLra7l$0)S6;fRJ?g0%w!>O2^c{P(b0O}qFEvl~{R!3@+vj+~ zH!d`5uAXJqS~AhBJ%6}aXL=tqVqBV8x4+G-*QK#pzikb(LDO>PWnI-iFXQxgQ=@k+ zC+Qg;z)AYR@Bn5UT%r?l-0A?FxXaKVfGBkkM1{oT_Y=aC9V79@7cKgA% z-0f%A^mRx*>Qpsd(51J^6qA1(e)!3FvNEdq*7y?AmV1%5oVAcW6zP*zK^^zHDsUfo zCPbd`zeg_OlJ_cN@8!RMRiN7%r{AR@c}WquR!ae0KrRc5=tZv8vLOT#9!QAzG{OW4 z6(n4W5;92GAfbbV4-!I17$KpAgcA}{NLV4Eg@hLpVn~?b75lD9=y_j$pS4%*Ea#uD zU#6Ck1J7UHw?8mz;6e|tUp`x4AN73xY=JhloIhJ&4YiyZd1` zdfJxwb)VdphY#e~KDax*)mDqG^}i~mw|VwKTieAQa+Bug<|N-XIlbMD7i{+aPv^Gp z-aMy6yJop5v1@bEKHZw0zPr0Eq`ouR4*o-W7J`_R+4->2oqPz3(RrZ2jK5H~qRTSKF?C!IOK# zicUHGA2^dfVD2>Az)6w0gGN4=qxW5%JGgUP&JdfKJ~VoSZCH(~bBC9IG-vqvwDb|j z-?5E6xIKN8w})-?E0=P|JU=IAY<7J5xP||+-FW+m-0`;@%eiUD;&f;42;0r6b0Ei+)-PoGIIqbEj_Fk~3}fg!J1U|IRl3-W9ns zjOd)1<3CK#8a&9Rv=zLdcK)Tcf6YhIDaM8f9 zh%zDSgeVlEQixI^YK15kqFRV@A?k%F7@}f`k|AnV6h%W+4N*2k-4KOCR1Q%(MC}m8 zLsSn@K1BVH41i<-BoiRn0LchQRzNZXk{yr?fn*6JQy|#_$rwo1Kr#oCJ&+87WRap| z5+s`-83oBINM=E@3zA`wEQ4ekB-U$!5y!PWIQD6 zp>gd%wS0TZA+>x*-2LkPvl>=Y@1K438|w8rr83p)bIyFHUY~n-rh5I(Ju}quytj+1 z<-2m8SMR_3*_P`4_dI$?y*_{b81?$Sw}q+K7u@)`di}lukEvysqrO_cKiR9^zc99s zdjA6vCDiMSD&MVMe=ww_di}xEo7C$MeYR08Km0)lwJbYe>iv(tG+DiW@yhz@^?y9_ zw0eEXoqg5ok4-(OUVnV_0k!-@{~>BQJF}8{|I(xf)%%}pnxbA`R(q>@eR&0c{x83D zLA}1>i%p+H*(M>+6o+q+VZtaJ*W6 z>D@DGIp>w7>iz$EK2p7Z!xOvJ>n|_7LA}25_E7curdt-M*IyZWuUg*R=W4a=>G-O8 z|En$2)cap+ct*Ycde}7e`j*mB>h&#W*QnRuIQpDge)B-QT7K(oQ@uZT(+Ktct*ft5 zufP5HV)gpAd(+hG+q2$LufKECcD1}?NDsCA?zNhFzc*#BdjETI@#^)R4gRHGfB)){ z>h)bEkE_>reSJ(V?>;g?Eq}15j(Y!|tt-^~_vZ9euYdT=hwAlxkIq%E@1I{@y?$VN zIkkLn{QYYAqXBX;D=8m$f8%%gmHah-aSQ&eU+1@&rl0&mOJ{Al+7`_~dEB&f%T~?f zS~Tq(ml!YnKlaW8u!-{hC{W7`dt+EKl>-5bB0ZH66!k|Md1H@-ZUu)1**9FmfYtqB)R17dA`qk&%NVz zYK7eK;*sm%=LJTT>eVuJ>Xr6#Z7(53&1NHw7PF^Z6wxt65CY!)M>ttq%X0V+d55_maKx)Cu+~ z;~ueB9WlWETc;$byZcPS9UVg;HaRg~&OusfoNL<81#S;r-{0ETVy^Wk8?8fuSKS@6^wK;2Fvi{KbhIzK>2I-n+mtxgS7jnaKU6FWvZ^~vDSeAb%}YC6 zNdep>Ly2Kf0*4gk;IQ%}hZb^pA%_@pm|^O0zE#yWhn(j*>@amW-wSFxbvWOHYCCl} z-?!Cv>TtfVsO{9@d{M$y+`6eH3k|)nWDDJ^58e`v5Q#pwd=lyo|gPHg|W#0`d%~HZOQMuwD1j=%*f=vhB0ZS$ zcbc&vPBpxK4!*&Od~C)L<-WT%|JEVOF?G(fzso?p*h1hg&NViEv!1=zyztL9d6BpX zxxkOG-|Rx(e@ijYENo$ym>zGvzFK-cHE|XU9i3Fj8s{nW(^5z1nI+D`M?6VIc1Adh zuB&epTSA@4W~e&Rn7a&Lo#->8baU!NTa3FaQYTtul*>V#=mSgG_tc5r(!%3-IDN*wY8aIih+@ub6-6<8* zy~1gQoLasVrx$XHA*WeUPBr9oLryv5v_nokQ>lYy9xVxX=ziOs9(YJ=4_rbNM(PMzgWVO}nH8X3pEo;^+UdgOgEt^^EQZ6&H=-pc6F|S$sVpgrrx_#!oN50hR z&OfKsd*?%~{`kFGgIAYmQTX45QPw3spvnK5Ta znz5N2X6)`xX0ub(%;w8#n=N*>)>=MS)cno!BVVgx>g0}|i^=<{W&XL4(JfIarC#Wh zbd->F|7*~#)K>;7WGORW@o7HUO1ExtT7A0Z(}ecEd>|$SF(Zg6 zLCgtaQV_F(m=^r2=0e6a*+1q9F;R$_LQEB6t`L)jn609iF2sBl#e^Yd3^8ShIYUeu z{;s)?|6J4NlJa)vIi_(kkGzH~=6v^Xn;qZ%61w-*{wbGMnHU`(npxCRcT{TMPEsbYQyi+}>PdScJBNG$$d{1s%;@DDO~^zBw7|M_o^HjtmEZjVai-2496w$nfB z6*hmZS6p>WuQYF$UU}+zy~?=ddesqb-QM>Fxx?`LhW2dKckJr<FCtvMJW2GOGODMBUE;#>NkLD+u%8ec{9+Gf^1)w;>`^K~ar?332j_rZT| z>*}9%oDCy^7z)H#AO-_58i?UQj0gVJBY6ypA;zgF1`07! zh@nD^6=JXuqlNKuuG6|s{!0xP`MFt}ZRv?%;X(1pgY{5D7E?Z7?4Hqlk~!n@Dfi6R zg3Z^q&o*bpKWENfykB$QubXowXEo$V-x-dpp5 zwtn_w=7#ADwZs`YwT*F;wD$)*s%`2#Slis7l=(qqOKnS?mD<+M+sv(pFPhtfnw#6t z*EK)J zN%P+`_jV|t?VbF9WnZQZPZ8MC!#(syeH~VaAMSWjg9EbYZ7E^wf@o!uyxG zi)@rn5}e+-nKGz+u81})Yw+GSWR2G zkaD)LEN$FjKQwZeKV97&e(-L0g%1k2E3Qg5E6qD&R-SsqtTJwgS#`uZ)82Qf8PVgg zR&Dqyt-1`(TchpETFv6GX|<}wX|*oJYmr5#nvutQX|*qoGV8n-t=)UHvsrh+{bs#) z>zMT?G&CE`C}u`IP{wRHuC~^wX%;iOZ&9sr*)Pl{(U-KQS^k?7_$ejHDx>CH8`sj_ z$|kS8`jPf4ulb+g8;q^3hTcwjK6{RkKk6?;;-c3gy(mwYw`9I@<-9k@D*ZJ*7J2^a zb)FF+vIJ>7Z;{uqNrLbMm6zYqBq$5RHcDG(@Wv zMXw>6tth$;(Qb%-D~g6gbX-xi9HQrnqUmtA=(?h4J4D|VMdKkluP9m%(R)SFe2DHV ziuObF9})u~aR3qvAn^ba6BH#bKw<+VK0smwBu+qL1teZTVg@8`Kw<|Zen4UfB#uC0 z2_&9CVhSX#C`xRB#1}}6fy5a|tbxQENX&u69Z2kf#2-iug2W+6EP})%NKAsnB}i;i zl=y^$5u+e+3KFXz@d^^NAaM&4yCCrk62l;I3=+!}C7wZI8YHekVjCpBL1G*v&Ou@w zB;G+{9whETVjm>_L1G{z4nkrfBpxbCOoYTmNNj|}M@Wo>#7Ri3gv3io%!I^ENbH2f zPe=@f#8F5rg~U@uiK&pdswlA)5?>)P77}M6u@(|bKC=A@@k)dHrX_fxeUm+MEh{d*?d zq@<)euP}6Nso$_)vGW~sJ&9l2)kOlt?A=u%Bdn*pWl$h z%$>`czV>$WTC>(XxMlXZHMLf4D*pM7-U~cC_peLZHEXYD_m+G~dq$3SCM~x+_IBvz z-1mH5$H8F(oFCe|AaigQeibQA0Ij=ixBfs;MdSbkzbcX|;GK1zM-Cc9Fr);yQlFAjB?g^{V zFDdNuTh8$8F-hSkra3Eo6`oXa{b*;U{Y@Q}XLWN{Sy|pub!WQh`CMjnB7*A}@>PfLH zdwQCETOg_V%*UK9KC0$uIi`oR)$06?*4-O89WVXQ(WY)GXWNm79PNr_a<=dAj-&m> z)6R}HXF58b4)Sy^u_meW=2M<7mtRflI`0Edw-aNMx{q7v>9M|NQqR7VJonFvN^-Vb z;OsTAhog68m$Og*29CZt2RZx2lyda@wuQ5QSSCmRT~(X|vz~SgT&g(-eZJK(_~lBT zA$v|GJvb_t=b_~vBn|E0_2|zpOd3{mpJ({cNl6bESmhbfdT`Q+%X^)pB3+JACssP+ zLI*kG*1zN&o!r9l=&VPaj~%Mw7&Equ^YJyBV{FfRo$=GYbEqyYzn%j8Z?Y}5(>h#Z z>}?h#*BG)c*AEAig2b1%N_#-A85&7n8wOTrDE)34%6b}C1>#eI67V!?Jp(6(HidW` z#OolQ2k}0L2SU6M;)xJ%gm@&xDsSs~f6pw{?t)h4?#CsLRgCSn5 zD4q=QW{5{ayc*)!{9?Qt;^7c4R}@c&css=7Azlyhe2Di$6abLfG7q;H6Y3XQ4ffMKvV>xBoH-$C<;VXAj$$!7l^_ripoHgMp4uT zqBs!MfhZ3|eIN=1Q6Y#DLDUGMNDx(mC=*1TAPNOhDTq=jidsPw3!+*O<$|aeM8O~` z22nDInn4r|qG}LjgQy!s;S@#XAWEkwY6np~MNvJ7@{2 z9%%TZEnmx8mi*O!HVc&b+ANswm{};(F0;^u^=9a)@tH10{Ju2V-Lr%5ieQd^M#l&`@oDTm@>qiAtntm zYeg|_h5g+`9oX);sy{`fVcz1B_M7AaScUr4~UCE+yvq( zFeCM{P2)H;JN3F^^JR+_TkPDRj!(>+0{-cDIAs)pmDx9>pH3 zzESKMe?js7sk;@O_a9X3_4rpNpVzy=+iJT{kDY3}Z^>8GcE5Vd)b<0`w-o=4sh5AK zBxM(&vLjUfG)9C!jgeNfK9UwZDE{>jZnErrX{K%8h%s7@LO({ui)wL1f)dGz1m;R~?Mm*7Qw5g0Ge{pK%|lIlt8 zXb=c|!9ePIDeF0(d2n9mjH<@dCc1n5O&>b`fy+Lcx88ilcL0ixj zGzC3DOVANC1pVkQY1==ksv#|8|J1)LSAJTA-AWPDk{X8Z{;BFIdsV%HT1A?5iW}M` z&Vg<6$r*Krca=zF+_uT6JKzt0m+j()ET()u)11-# zF=NK%gXYYc7me4pzhKOYH;mbfKd`v(?`_O^CfS(V@C)PhhaNZPl__eO-+F~*ah6NQ z;_qLxEZx4^vTR{B%W`)QBVl5G%Zf>lTHe&{#>(L@8><|7jkl|HHP+<*$XIh_tFiXn z8e`qIEtdDzylq)O`$1#F^j9p2uU@fijC;)T{(vEtO`UsKHaEy;d=S~dvZc;^%ht~C z7+Vj2YitjyXKX)T#rW{kVU`_l^|I{vW`%dB(Z##-*b2+8m!d7ZHg(Z=4^Qy!{$Q7} zXJlE+o+Tr_No}Wiljbis_IAi-**m$DcVD#;-hJa=F!p!ZZ#fXV*>bS=blcmdmo)+KR;-S{kuNzC0uB?EaCg;*$F>XHxvF>=81$K^9@f(&eSI% z`9k}IoHL5bo&ONI>wk}-jVN#A>04L^tPeHwMPD>-oH2RNoslv52UcVLgge67t-)9Q z{ZS3kZuq&0Eq*TSOAF!keHw-aRCw=An^ebBOq}C z5-T9_0unPIaRU-NAn^kdLm+Vk5=$WQ1QJsqaYa#L3nackVhkkCKw=Ff-aujwBlcK~Y9E=!6Ax=?(jPoh?%oj>n zUfb(3XAN&-%s!Y&bGI9BnX|fmb%ZA;4{maX-|Y+H{MO4wd5+P!^KIopn` zW!*cLG;;4ew#&9_^4;#;>z3R049V(Fn)!llZ}TtR`^GPI@2~iw`#`_v-3M#$wSClN znET_xD{Y6$wsRl)=_T8dT)OSZ;YVyoPZx3@TieBUe0L4oiMb=)pDZhA^E}zfee&64 z+o!#2yH7oM#P(UEqVCfz*V#TVb;J-KDAl3)5K!_DWED>Uj z5Q~IZCB!lz)(Npth?PPt6=JOri-lONqF64(dLb4Jv10hwTXJ?W>NJ~iV9)*{!&0ch zBdvz4#LIUNlF5&bC;D$E`EIpWOY3%#yeCispTrRuIN?$v|C9D7a?ht){c365^9g*c zti;G8n>_LxvUsua{dG6B?wYu{RwnxgRqG~hDRA1pHMC3A)+<2?+mi2%+J53x!iR^7 zN9|buLBh_ruSD&t^@P1}p36~%_peJRa(=U|=;FTiVtW_Zia*`dUSd(Vgpz}5McG{S z5=u1-jVe8$WI~znpQG-+FDRjG@Uf_J5hd*9&a96rpT}Y^zwbz5xc5_gg*VqFR!rJ# zuQX*=V&#Md_A0}T#HyytZg1N=F=F(*glhN3M^*1KA)!XmUQsn`4@jtaF*@p=!p#!y zIa)R<@~27(wcpJaRp)Spqh-N7 zy2!K6#trv48vi`h*|gLON7Ey1ocCqlm2}^x2R*T0El+CpdTUSf4_`=X@l=FI9p!7% zo2qF=`tzMxL_D`|=H$y##8cXZ%7y*bM0%i|^3dPxU{XqZQ8jc*JJk}Sx?I55kjHJA zN#JBEGntrY9pt1!PAuf)LQXK`BtuTLqMU5V35T3?$ccxXe24)+j6iJ1f*~l1F+dCg zViXX=fEWkFKp;i}F%*cgKnw+SJF*u0PK@1OKd=LXv6eENfqM{fh#2_I?2{BBFaY76fVx$m5g%~TuU?D~eF)9$=Q7ZnTp9ddWay|@_4?Isre409hQ;!n zd~eos8M2t!YP)OZx?0v9?ccHP*gC|u^X(q4T?<>ecDo<7?wM$JWzwTunRAx0W^t5u zW%*wNS2lZQSGJEzxq@?l?h0O=$(8-wHdl_>HfzYC)7G5vcUg0-*=o(*^E0b9{S9lL z`dh4dMAy| zg{%0+JgyR>Uw4(9w$^IvGTv3nIOru1fn}c2$0+ysOHaajvQ(npo{qy1F8I^m0`jUe{H}hQ|t+ln)#3*a)MmlR3YpDLIOCOZWI;_hT>+o2w_2Jqlts}zsSw|M$WE~Z}$~x+&`PN6yyljm-JkdIO zdz|&rwF9k>y*br2X6|^`<5Nbt#y;8K6+gVQD=LU~>utmi_cA1?Sze)w44Ogh9@6vba4KBFjp1MwY* z|3G{Q;zxWqz69|nh)*esUqO6JQTz+yV~XNu5MP7%8^q@zeh2YAi2p%+5aNdrUxfH0 z#3vQSFCo6EDEN-Fo=#pv<#wW5KV*V8bsS5`UcTB zh|WQ@4x)Du%~KTJgJ>T_{~#I&(Lsn7Li7-#i4a|cXd^@)AsPwMNr+ZL^b(?(ilUnk z?S$wjL_;Au3ei%Co=lN2F>`Z-d6o2j~t4t>L>3TGuKtM>}bEzvSVvMW9Qo)ja>_4 zjNR^sEqf-08=3UlM&_KwL_2m_%AWg5G-J6XY|l>7iWe;5%a>WMi&diWaU5(Jq*-<{ z*-f}$slr7Q!i8$Uee-a^*P{f!Dl$L(+u2I)XSRCZV4JTE`Ra=D^&vk2@-rYm1@d$F zP<|5RXHocRit_U){6t0hnG}Ai;_v&p+4|Ao z)@!PeaVvdQ$j&dhGOCdO{wn0SnRw9eszUk`vZj0x>-q5MhYPy;!cm#$d*FYqDv6(R zWdEwFB!15R)2vF0-}C=zRwYFT`2RpvQuKiBjH+ZtRT4Y+TURCT%JLiUSl#ZOt@ORl zV)3c0)V8D^AvH>=d5PlYy6&Q#3fR@vO0M1SK)q6ScXhq;7I`m!PD85ii#XH2D>nGk z3)$s^u1YUVQ7yslL`+D%Z18{I-%w+}o#Y|S=com0hAd{bI*@Jimc$eh1{YD9Z1F{3ginlAkO|ne#r%?_-><<_d|>>(r=uJ7@J(*)c~+rG#f3j8m6fuN`S@%rbfz zv)?IaxSi2PT9;rK-+$Kf%bQ~@m$r7XTppERTv>Rp8rVty4oOV zt8tRDnj`6|2Q2w7KP73Z`=rxrYpGN8H9}8C$QfvA6j|8ED0;HIQEXxpqxi<`Mv4Am zM#*V!O6qB>QOekDlx{Rb(oJ7Ws_AicJyq_m`+b*+G8;|#Z%-O(;-X!f3^k$S()os( zxair5hMKtO!GVUFxTxa|D>hf>=2{=_)V4uW6v5<{pMR5_DnJwHJNCMuK%XdxXSBBlM+*nrdgU>VzRBU z#QZo{(m5|mDrcOeaYjlC=K)FIbduD~eUi4REh(D{lCJ4sv~e~y+B)tr+C`N&+S>~o z9cL-nJEJ}8@{NRlNzaz;`kMLsBB^BptHUGU}&Uk_LHRQXqz;KZZ%_qqn3z zo;AkI9cw&3WvDUsNv9DnjS+5O;eNsWi)pbutuDLoNPZyTS18DcDwfY6{ssN{0sM?@ zd=27n5TAqT$sh1~{1M`l5Wj@VH(9|yDdM95Kc(QSlwao`@LQh8cOm|(C_W7FV@2_0 zh(AMonu1?b@NEkIO~J=0_&Eh%r{M1te4c{eD~j(^@P7&#KtTr-MGGkCfud*vL>D01 z0MQ4CMnH6ef>uEE0-_ldbc2F+Q2hBAG=vh!$B33t&=VM#eN=RX?ZE7#qAzR*X0#HW zVLLFRmFNxIff=ntcPN1wtwet)ff=nthbU+fM2{$F5=56E+62)jh(=M+DTr24&?|^$ zQP3@jc2UqTh=xIQ45DQe^h{ASje@RG&^8MCMnU5!=o|&Dqo8*bG>?MrL9`E|e-I6X z=paN3A$q7Nnh4QFh&Dp>5u%Y0our_Z6!emUW>U~i3ff6QKPhM^1s$cJr4;m(f~G=r zRZ+ARqOTB*h3G6qYax0I(Oih`LbMm6zZ5i>f(}#AVhVapL6a%yvZ81+1%0NV(G+x= zf>u+|Ylvn;bQ_}G@ZX}}{)A^k$@>1LM0Ei<7@6gf%a8?G-`|s{u5jjIxsmmuQEA`v zM&-CAH;#}N%QH)4Uw0;X9r*ghgyG-LJplj8=IE z^58;EDNFv#4|objMkN>g;^_gQrFz7LZXGh9NKk{gA`4p$D0;F~T(ODv0mV0FiYqbt zIlbhx#mTlVx?alI8dti}WN(>)r;_h3{gAh8><7tZ&mHxKg)dAF+w-0`Ja|%a`10A_ z3TFlTMnNxOP!R^!E0=aUDwjqIb;weO$*YC-hF|K8ox7 zNuu6m&ziWd8|LcW)*MLgzG%I-$Mn_7JzZ|^{c+Qiodcfq_Uim-a_{^4diz9nPwrd3 zskdKf{p1Jo^wIkVb&KnNzKK5IWWBh7d&BfW8%xFwUJAS|sA7yD_TCZscOl8g8P!xX6-Q z`Edc?jVDQY$Yh|^?XtA1WyH;zEAVNQfSKaD4;Oqy1v1Q)|HNb!#cUy_t0?9RF=0h9 zV~8n3%o$?R5VMAuHpILkCJr%kh^a%&9b)o|V)hWzR}}MyxPYR#0mKy`?f`KKh+9Bh z1L7VK7lF75#8n{f0&y9bI+O8tY8&_Ad0a?Q+z8@I5O;#O6vV9{t_5)~h>Jno4B~1K zcZ0Yb#O)xi2XQ}$3o42mLR=Bzju4lGxFy6jA?^urQHYyDTovN35SN9xEyQ&p?hA2Y zMR8+@D?{8F;?fYehPXDwy&*0RadU{PL);zW@({O&xIV=FArb(Q0f-bRiX1>B0U`?! zX@JNBL?R$E0g(!bTtFlPA{!9tfXD|#LLf2%krG9b6NschWCbEE5P5+}3`AxiQUj42 zh~y~#vt&n-M@SI!OUqq+*@%!;-vB|Wck*u*L#Yx`o|6ab>8c*x$s|$1FJ>DmNwoC| zVm7`Q}6haM15xZl0?yCeMzD+P9q~pbaii>KYQI`nfN7UeMb^Q0pIWi?(u4d z&O2TA{<|h_@Jo(lAaNu9$@Xn0Zp3eS{dI{O@nK536E`<#NW{^8X$M<0%aU&YQ zb|7){d#57*b>c=egAz#Gh;~o{i5t-nN+59~TEcc9aU+_-b|7&h+QN1qaU&YTHabJe zNZe#3ZvI%6y?ssfU%db3WKBLX=&NcYC)_jFHMi|(zsI&?>l5ysZx46xTG+=Oocp3X zcy*||NKkKgk%jT@@Uq?A;mbd?H5sIb=jS6aF?5=XL;*Cf$Br z@&-2?Z`3yRhIPr#&l&cNy5zt7hNH#u>}Fi(kElz=tB#BRmAWK;$&tL{b;;Yj35Z`) z0yhDFZ(UOSpU*=BU`AcC0*4{vCSb-*z*{s7xqVIj-E~Qq7$jA+;29x>oHGs{@y?8xO#kF=fR+avW*PAJ{kRn(-BGz8^`#YTVB(W(yLt^(}cOnx?kVGHV4* zC~wj|)_*+`Fnq2rFn&dG0EiPn90B4C5Ql&`1;jBR&H-@{h?77Z1>!6ahmkL6!D-;F z=PXSb5l)>Xl2TL z8UEQa((cbn+Yug=m*l~^Hfup8X-xf-?w*j#nDgpUeTVC^v9HYc`o3Pl-k+2ETP}VX z>;2_WtmV?Sir&j>Dq5~A%IURC&nXQw&w1~9Sw6ej~hjCXJBk{5pE`4eTh> z_(ggP#70^Q)-K{L6kbH8?Yl=WZ2iSjGx)P%N2M_rrmqO8+LiG5uUw|H~hp(qrz8BycO5K zWK`PUTdzFp5u?h=#(LGUT@3q+<@AXDqb$`Pw(8YmI?E0Euk@N>b)=vDPW_&2#YA|% z^hSPuS%#qe&|7DVN2bSH;;p;<7o*<0&w1-Vf5K=mL-$4vO*9&gdsc7Mdae=Occ|XD z>NulG<2HJe3vbJGcMp1F_P-+2+_m<`u6#_Uw~O#LpE=EF@lh+i<(Sb%tJQYBb@y(D z6<=*Lk1Hv~<(G z-N$W~>F6Hy_UyYrrlITZb+&xp=rwV)-n;TVqfh^Cdf%KAjD9io^nTwCF#3m;)cfyh zW(>?0qz_zL$r$wcDShzEu`-?974L(iD#|o+C%r>E=9KBOn7|1^7PXXq(XuXMHt{@Taxf%EbSHT@sC&qlSBo@iAUAX- zY6dtI#Hk>T1#vEjgF&23Q5+58Y!HWoI32|CpmIJ|a6pPUAq7W-I3opzgg7O{F(J+g zaZp8ZQi!7}inCI1Scua?92ere5C?`hF$G74I5P!@hB!3^$A&mJ1qX*XIR!_DI6DQ0 zR}`m*I6lPrAp!sq0f-Pl#DIbzKtzFpFhInCf}W=5h#dAL4-;LWmer5JZS5QV>RnI6?#xB9aiHgoq_XFcn2KA;PIB z;t3H@MG;YmkV3?if}lb~m4dKB#Fc`;LPVB=&_cwPg5WBO=t6{7QN$M_z=|Tm5Fv($ zF+`9dq6`sch&aQ4oj_+7-L9p^^|-C+Ik{}~UphXTvL2j~``D&2_aF2AG3Oui{V~@c z^ZYT#AM^YDE9TJp%c>+~IH++~uv4$_EqSI1*^VZX?U0dmRY{}1aYM6}Ps+$ZtV%XH z4wdGeQLH@mh+>s-I~1#qSf^<3yHqiv$6<}vR~x=cv3k9C6*H#a>ZguR#?+m4l|3*k zFm}Ssz>wL2Au|L+W(iijlVj5V{pBgUXjSEEsFr`8&gy~8LyeoYn&4RZ3lgX<#iaTv z@|T9>`Cskluilki-kN4d@*7^B&00`GpZ(6)PWJ?hKIhe4&K)kVu5umDi=Lm8J9{sF zd9UY}Lv_5DwiWkWUQ^6_W#JW%W!hzL(8QCTyB_g)GwGL|nVpyPEDnz|Yt$!tHv9X| zY|j?fgSC0i;H6W&*}W5-IXvU_ki&C5ITQQqxz>*Jfzx5WmD~GelciHu#C%<(T+xv}PeB&Nxi50u` zl5@C~u1y8vG3%p?!9`=M?-lT_T@8AhPF<-Cn z)jghy>nG}!_P2Ldo;6Ufva*)5>e%MG{l!pcMF0NYYQukaR*#AG)@XaoSu?Dnw^p_F z&U>=u^hOpv;)(qHoVWJHb)Gs~_ImFAE>-mYdc4DjPV+om;C=6i)}uWmE`OqrihS8Q>cmDpE;P;=xBhi~baGecqqD~Aj~%M( z95c4R{`i^_&apjX_4w%)=ifBfu{Hq*+nPwOLza^3kfr20c%A{}RINjD9Zh~`uA_v? zVc=AxCU7i>b3q&o267$ZXl&zb5Ql>}9mMgVaz0jYK#Djaz!4$N2ysY=Q$idQ;+zl% zRTL+MII5yJE5uKcAqxekuO)g?$aQgR>6=aQa!m+xbE*KN<5!CTZG)yy(#{=)R^G4)-y znEBlI)l!!!`+V3b!WCgp*RPZgt10i2eMZ>pN8Hx*AnZD2r~Mr4^tSrR-r!yAG@^b) zL^|uyoub(3ZA~n3)lU8IVyD&WN8HvVBUioslt*@Yd$U>G;9Z>3+nm_ss&^kuVxQI1 zc}o57VxPB|j(~mnOUfDDn#ewDq;sGCcd^eJ^?%ESQDuA0bna8?cJfVYTG(gJ`e|Mo z`G23EDZf+hg2}sUrE{PDcd^e}^=qZma9=ao=RN7%r~h5-^Pc+mq;sFo8|*VOo%{5^ zi+x7cziIaX|8a#W$My-Geb!FrKK<`vpSA1PPS@{LjjPmjvFVZA)ORsj8+~lkxzovR zsXV4rbJZ9G>RqarFMWK|xz|_4@TE&|x(+Yx+z-^EVl_@?*pHg&Mm z^hFuzftB(ub}FMerZ3FMC6JSyrZ3E-zKfmKs$V&MaYn9xbatA)IFtG=c6v|!%IOO< za`9uZ)AR+J)OWGdNEs|MouV|>WT)wiH1rGfy^EdJu3tI5p~fPmL;L7->aYJ@+2yTi zDn9qjefP~RmQ^Y1r3_h_5mM!w83#Y=KJ)OJkk{sI2${98Y{=|s1w-6s!^v|-C5Oz_ z%S?W~9-GrEL#{mdHL>FCMQg+IC;g25|iK5?+aNurESP6M}?5L9v(k=wLK){?e=R!*5p1L zvNkGi^4fDrA@7C`oBZyfZziw%X@1CiYj#gwf8@~Q4bu}QC%)HX^2WF+liy#sWAgjQ zdUW5^d4#?>ezX2TWJi6A^GEO2Qp3I58o%ktXpvu3Yh3mVvq|(Nt!b9+W=xq+w3xG> zYq6O&YO%Zj8+WCpKl5!5ik&S>R{8T!ESw|culkcj!c=iXOWt{ODVBBXwd$K?F=uL<}NiiXvtZL4$}I zMA#tW1`#-j$U%foQN#`+co5Np2p>fJAOZ*xL7344BTU1t%Y}q0hGmJCs4O}AA8II; zKV4Qa{9r-F3Lj)steDaMLE|{ldpn;I`3UG{LCvENO>U9@H zOIy1Vdi@DyrLCPsk9r`Rw6!bdZPet99^L1%w{h7WGFG<7+ce8kJ*Le2-k7u7y|J0* zd1H6~pSRhm3Et+*p7pla`MTcnxuM?nEs8rk{+M5n7ZDANFP&LU1TV|1*|KI+3s!Fa zmpNNzk^0Ia@ibdU_M19_MD`{5EYJCt{$~gTzSr=&rBF-h`d_vBtqMTRAxrLlx$T^0bXUMsRoNvfEhn#oFxrdy8hz&sO0AdRedw|#k z#4Z%YHX!x^u@Q)!Kx_qKFA$r7*bT&XAoc^XA&4D8Yzbmd5SvmIyMox3qSzP2#vpbE zu{DUjL2M3UcM#iy*dN3OA$AC{MTk8@Y!YIZ5ZhD~`-Ir2qSz_KRw4EZu~~@SLTnde zzc60T{ol=oNtNI1o=bN5-aIHz%Y*eIhAd{t+)r!AobjyPvOH<~cYQn`YKweH;KePx zyXS^__PjVy|0Lfm=O=GxcLaaa)0rdmduNVa%Pb-9&6d6%9gJL)dl21!FR5y4_@AQqAO%09 z;END{g!m-HFCo4O@lS}4Li`lss}O&M_^hJ%EyQ)zEbTlRHcZ|s}XTHD{JsCl4y8S`N6y|#n>zc3Fjdf4(&_$uw= zN=39o!7pov@7ib{J`<;93s#Ei-4c{`r4$dF>KwCavI$`(o@@ zuS;@4QV+gm>hr!jfHWWTRRp9)AVt*#{7uEutPI@Hp$O(f=k`j5f7XDqI3xSRAr-|b zA&v=gPKbjlijyjeqe7e&;;;~>g*dLFI4_3<2Zk8~(5_O)A!GQLr%mi3qxsWce`EGP zBgVg>=95Eglm}&vJXkMi$YOfqSYt*=S^Y*s-p}5hY9&Ov)psM?W$ZobRbvD|z zcK*n{^+>RLTeHHp>#`^h^)&rM^zVNmN+S?t3|IHx=ok=mR~FSXzfQ8kFNLDUVRa1fP)C>=!YAc_Z3J&5u_)DNP75EWDuC4{IUyk#Cv6q46n zJvRf}s3k-(v4V`b8j{s<$QUW{byfAx=qd1zZo`;06V2Iz(~+bIG=IyIJ;z`5=MRk% zASEK0`ZuxF}KUG{Q>#Bdi z+X?%uYwKTdu01!?`OYWLI^W$h*12xm8=m*pyy{uM=o!z3>0>;Jt_M9E<9d4DAJE#f zsdJQP^L-JX4qmEtdulZfUXig0$9qQd;UgEUonVN(;RX(mJoHw9LClTIH3O z7I}rGX?J#MiC5DI`ysot{AU-PzE+yYofTK@c2=6V!C84~g0srF7adhcOmW)#j&wxy zSmvoV{IR5r{s<*ankUmVna)29e9EAw40uZFo~c&ia{Xw=C2iJ5pFM0ony^57Z1=n7 zm{;C6A7A#GId=3sGyb`FGf3*J*Z$#m*FJOgmd`7%sl(o)N$TyrR+`YnVY#vtPfKl< zl-9c6qXNy9RAm!aQYFoL<_(>MddHAG9vOF0>Q~|{5Qk9|r-3*Q#CaeN1aTsWBSD-A z;!qH$f;bk$xgZV(aWaUbL7a`BfWtwY4&rza=Yu#P#0epes3^_|aY%?$LL3v~oD>|C zf|F8kR0_^Y!C@&lEd|G=;Jg$Zn1T~iaAXS3tSAl*acY>_y!N=-#=&_WCx^k<3pSuW~2f$Qi0gP-=dGkb%OaX5z*h>>}Z;tp+K{vv~GL4U)`+D={wq-s~6E; zZ~3ye{lZ($>k@0sefdh9XW!G?_5F?oa{IG@aj^C#P2Kh^JpY>8o}bQL{@qs9<9f!Z?vBA7qt7Q?$(_5AJlq1{*~Fg z!P{D&9y?7MA<_Vm#)mXK>@STDX>dsQE4p6M?TRi}bho0b72T}vL>)$D;#1~!Rh=fK z&s5479lxIO_0MS#<=czI@Xt`F)LWlvty7t~Y9dYQ|Gs)mn$?)An?(hDjOxSSLNCmO z>Ae$*5Ar_z5aNpve}woXyk&aFKlP^gDzC?1AwCQ7TZr$%z@1R>VYY8{CzR*$X^3A# zd>i855Fdy5ImFi$#osvz_&mh#A-)gse~1PsiVi@u0HOylqd|8sjpIOHZ%BV{n9-oS zygCkOB*z1tglHv1FCm%<(M^bULi7`&p%5K~|D^`q|7QLE4S5_+zp@44vnlK43|W~q zcy`F0$mMKX_oDN|M;^a^G_zKwvq0Fwq=KQxq6%4$Itu-qJ2CXDTuFtGMn@Id`C?Mh zb#8C5B`SXWcYGBdmO&p&I(7`xt*obYtkh>CZCtyl7lR@E)A3BsS}LIjuO#RC$HK#wa zwYX#+RC2P&_at~E%_Ccq=259sU*9yAzGoz@DydYdH%V&2w~UMmD#8C+O8b}oXZ)X_ z>QE_Z)<9Ay(AKhnH8jH&+5S6Zi})V6 zG{mjpEvsqb>ikID9pds3w^tO`hqymP0w6K~kphSuKqLVo3lM35$OA+oATj}w3W!|5 zUw6(%I(R-co1_%bf{gHdYBp()gYDF8(h9Ym{%q1xK1Rt7M0zO5kD^Er1sPHlDWV`p z5J{pSOAu*-$P+}OATkA!Du`TBkSvI7QIIZ(d{K}vh>R(Ulu?i~MUgZLvPMDLD99T` z;wZ=*MCvHW9YpdVvImhqi2Ok$5F&$$B83n+R1`^s$RY)4gvcWWiKHNt6r_@ZTvCus z3bF~2P73k~kx&XU3XxKXoGOZ>LSz*ptq^&INGwEVDM&3uZYfAEM0P1iF9rFfAi)%5 zn1U2jkYh!WWD2sZDAG(po*@!VL8c*64UubzWWx;EW_HC}%Jy}6slcpeX^!Q5^%e7Y zInr$BG-NTO#AN4;gX@#FMR(F~m~|RnP}_I6X6_$7UOP~9sd=#HQtjX;9_{0p?dHcD z-p^=c|1X<$RIWyXuBkTtnK>i>zNE?IPO;5vaz|1t5;5{+lTvd^>CYWe4O3My;e?d+ zJX9J6PS~>w4huO~1L7Hq;vEnVfw!z&_;WzwIlLb4fp`$aiy)q)DBc9|C`Iuq`Cb{V z-!mpZRd$du_~+knSTb4-q&Cr!wcESTe`jCimq`&zxu_IT6kQXUlsdF;>3b&idH+S? z)yekMY`d)ehI9=TL>`pS+D$++%mYHgA)A*38%3v!@ktyT`v{ zn=|Sc_uPInZLdGJ%{{Nx8u$DL7i|m5I&BNXw%8U`sAOC8b+UW0Wxspz!2-7bJL9!2 z**e6wbo)NrvW2Z|%iW{h2@~zM6;qPjH*4d*Hg9KN8}~C7UmN$MoqTQFS6sffjr*gQ zd~Mv@%)73Q`+-=G?O=&g?vGk6v3*>ryzNkx=WK_5ddYSqmu@@q^*6Sorwh4{?bvNQ zzWFEjiB(^@KUr4L=9#+FeRBAH?oS7gu$^jK!Tni#(|x*Hi2L(;OKo2i4Yz%nzrFj* zi#cp(E)BDNb>v&y*T>u0zIkVl?c0?1=tX;a-IJh{+!i*Vy4->|E5 z2K$CxeWImr*wtQDeZ#IcYi_DxS1VLj!>)#B&+Qv_^~`zSu&dkm`-WY8<1OE?tCOZ> z47+-VZxIIFb=7F9jMa2U|FT-I|G0Ftbi6}dH)W>QyDb)3=0*&yC4FK@eoTr4(p*}K zJSvf7o#&Cn#`1`j7W<9l!K8sC$s{|LaubrRH1cFO)IO?ko%Uuw2GKx8(Lsn7Li7-# zi4a|cXd^@)AsPwMNr+ZL^b(?(5Z#1mr=sX5L_-xtMVaXgoycAzBa7dx+*kbRVMq5dDY507x8w!~#e>fW!nyT!6#|MTrlP7y*eB zkXQkU7m%0%i5rmE0f`@w7y^kSkXQnVCyTy1_R@l zNi1XAKYkf8jqSiFC=%P)4&3gR7{_*C{4$AkYzIbkmYBzOU_@t$eQXCtbe0&%c3?zj ziG^$jMs${#$hLpp1Y#rGfq4@oMzS54H$h@0+YD0 z&-f^d?Vo&w_F=(t=7-a|m^=2CFn2z3ueoc5#oX=etnD#BHIq_$Bk#RfP1`r{vTc8# zaJT=8?qH)!CTVPH0v}z@Uc2G5yvH?0c=xs96EoTR~;8R z2yk378o+K=$AQk491l8NLONYSI$lCLUqU)yLONllFW&m1?0+HV{)W3TS>=%Opp23S z>#AwbyoZMt-Lbokb7$gd=k=YJGiF}+M~-)#(slXHX7g|LX%Z{@VP3M7y5)_#DpRYx zPac7msJy|KPfxQdc|+5ss?hG8ax)+%3^8MfDf79v?h2eSIRVEhV~FF7A&#*F#z=sF z$PmZ>Oi8+~L1oqI{;XQn8foR#a>y~{K^Y+r)(ac5FrWEhy05eE`lPx2`gmS{>`BMG zR)%wagH=fj%7%FshQ%i>E??fc_~fOe|NCi@XUV1r$F?ULc(w=Sb8K(+gY(1PhGWN$ zWzKzrgB|B!eBJxBL-cN{yA z1Q#Bo2Pf^YWdEtBo@2#j zBjj+Du4ZXkuvBM8nB2*E9EB$(=!K6Zc#3|Wo_CyZEP>D}OpzrkeX% zzhuz96|*UGN1$Eg zdM%K%sz1wQ4D?QWsyN~$$ONYHN-^xK2Lrjmx&%-EBuKY4F31h0NMZ)NqUFVmEJX)g zA7RPfNZ$|1-rP9ys{SE)h`+H{$6w#nb~X4^DeCsNZ&iimG;VL-(7=hj(hcMg#L!`C zZ(CnWNsON7F?@*eLmU9&1Q17nID?`%1jH#IjsbBFh=VAKlRz8=;w%t{fjAAsaUjkE zaUh5jK^zI@p{Awl2nHsMWhr(s32kmqApOd|mDvg^zUp&vT8o0@$mHSitr^8C!h z%S`y%ytfo*tu+;AkDI25neqSW?LvSF@;=NEVu}!RsQqlP(l_JJbNn|UeD4Pz-(V|P|xB6E) zWOV!}$Nt~X&v$2b*Y2=Hq51A(=K1~3&V0Y0@8|P=zdrZp>+{B&v-Xv!udMj=@O8^8 z>hJDsn2}S&ckNu^IyYi%JxaUw{dQAL;jMa^<@kvNo-@D{TqJ7qXefuF*vVWC(pB$=-s-X2! z6Z3OT`BXU;GDa39uUyqN>+=8^R=zGmM$u>e*5MFWfw&9AWniET2^Zpu7fuEf;Uzr2 zrM02OW$b70<_zTi2El1 zJG1t+CAQ}7b<2yVl#6{Vy?pd8M}@GR&I*5pq(%Q|ORxB8Q%B5>JJTy|nrl{Gb%$AH z;bgPweIv|j)BBkAaUIR-1Cz|llIxl^TE>_)8x%Kv3jq1rM6HE?esa}c`4$3`G_0{C%}6k?|kTZPyw z#AYFO3sWV{FEradUF(#%fd64HX4ZZsx9rUQ?g96LF4wyjoY?AKxbT4MiFfXBElQo} zTD)?-*E6J-YstMox|UwK&$Vp)1lRH!k=`e}FZHerJ?L8b`vUdte~&l)aj*CFspGs`Mi277(XX5L z%_~A(ZzVarZ?|~FyS3jc*VcVsy50$C0d6~Jx-NoGT*;4P$ zhnskJzR^YBHF2rA>#fbM_iiljeQ(t$Go!~$Gvmo6uHC(kcz55|+1!&b(%f_B9j?9o z-}JuUd7YPM1b$d|i~XaDFWNs2U#_Ot?)%{``zQNuvVZ!{X!~a`_qTuk!c5N>OK@7GkD6ZFrjj+iw)%08nqFHx#;&H<{&>uqUi2@`}_MWA7 z@3e>PhsMsd|2k}q=eM37&+l#Lc>ZX7yXSv3CV38*8SXh!xVPuXZyh|5&(^b*SWw9p zb#J6+wJQS{vZ$>ct@_LF}|xU{zB z+?Tqnw6@BoKBvt!O5 z?~qxD)I#J|Q6v{4yAbJx$S*{KAuDOVIZhe*1j$T~#Y6-C}560ayS50QFBk$Z^bD~jwxq#q*x(D#^F zvH_0C3Lx2mV%Dr&vIUOG8X(yNB#VG#6OgO|l3hTu4CtSgD_IA}{#m(_g>dYjl`B~Z z$NpKllBIA=wgP{UtOfdKKaea4k_|z!B1m=w z$&w)1lA>fycq-Ww8(9<^*%X_vyO`{XW3ntrwgt($V8I`&`>KcUldVDj>{!X(IQGwu zUAR~s`)9{azfB$cXU7g4rjGryV_UXY$Nt%|m1EVhe|BtWTZ8laX2;g~ww5~f&yIbo zm^${)j$QtPI`+?wow;2d`)9`vU#*UVvt!3=#A|S2p$l(epIK(C`X?WolHXk2Q+(!|b5vM;$9gZ=+@&H? zxjgyrpPCDteH|$~t|Q;~SLUQORjPMVXq2j+8iM1SEledZ*Q{VFcJkuaf&dk|oRfk5 za!G+TW>ENc7@Q1zVJwFfoD3XXehE(wF2599ehEA15c)UAH9v5ls!$eqTywK=sv@3e zTyulE>T@Pw5UzgZf|IR@2cBjNd7`ai#sxOn`sA`r*6}4)>`^N(NwKKF^~WelEM9Cc z$&3B+GncO3WG%Y?iFe_TPn%CHf6T==Va5nAHs5r4^e4SbW_;^e+OV{D*|ag*@}{?W zpB#LJ@l>s*-lt<%+E&yH@jg@RaqXF3nz&YeKi>B2$4gzSwx-x<02_MNbsk_}@6bFiB~0;bDEX^rL*_Tu`nVt7YpsvF?Q3g&+{LqV zua7%=mbE_a6^-(&kGrjn!=7`*<$H6RXc-gIG*%1Q)6Hq@o#WKr?^44kv3!^jb#IJT za%Ou&o1AEr8Xjw?PPDG=wER2Kf-}-D#u@3Vk`w4bO8GAdTb$Vgs$VSoVNCz@o-9=P zCO#^aAyo}Jc_xBXn5@1r=?{}l-mOpkJ!ER0f;AJtcLu6Z{y7N#4m0^RwWzFaGIh4B zw}7>(d_{!X6hfkERsQD`WKBX4QQ>oh1)e+!0g>Tzga#ru5W#_n4n%k$;sX&NhzLQ1 z2qH#`B1jNXf(R2toM3jhT7o)8sQ4VQf(RBwv|x6(T0;-V2pB}fAVLNaGer?J=wEDB zgpFhWVzVM}9QzlW6`|ue;9|2PeEi?P*sKU3$ERCt7E$DTfyUG#lKdZ`gubPf5KN8{ zO^9$}9Ec}GKp`Ru5mJblLVx#;h$_bg@7`IXejn`KnWy3*xYSXw^o{?VkM$9P8Kggp zJ@X9mEY?HHIfZ?xq$$U0sNJGh-tsQ?G`D$-O5P>6jdQDEbdqu9Ek#eWWtXB$ySKda zvkZ?LWP7vI72Y?muVlW}e6qf^`ZeyYe>~vb{>K;kj)!lshQWD0aJ{!IPS0?^(%aK3mCKVtI2t z>Yn4?U}-sP-3Do1$amcaX-4p`+aOH`G#k)laIp+IE0U+$EPV?3K7_Pbn17C~l$QUw zKUv}ZKBUm!V_;3u^$!%20<$U%+bXESb3Xfcxxe(AHC#*<7iG+t6oWZ;_GmG65TI`% zUq_~tG$V1H4dT!cr-nE-#JQn=kXKge^z^0CY3e+AOQWUL`49rmix@xzK~Y2jA`B35 zP!xfHhy+9^AY*3XE)fifXh4JmA|4O{frto1NFZXOD1t(CA}Wd^ED&*l2n68}E1CqkT~2u4zW{5gqXhg8HGn7rL=_;)peX79Q3!}iD2h@*)Iw1d1ELxb<$y{(*g-+q zL`48f0#Or)qCivyqAU<~fhY__WfVneAZi0q9Ej>bln0_d5Cwv$5JZU}Y6MXvh$=yp ziOv7?CQ&GkgKM=vq2eboVXDRBsfxh`UW3C2@Q?Gk!0Uf2hdGO~BMD%&kJxPOv-Z{E zS1T*7Oj4^94R+;PrAYOeM-L4C7vlMEt?-sAHgIWp#_3i;$l0R%vRDqs)!gzn zXKL~83p!5qF8HW{YvHCjt|!tvd98|eJ+G>0yCc2U;HYG;H8`q<%NiWDz`LSfZPzo! zZuLH+21gC?KKoIMch%M_PrEw8y3UQf>l=NSXO)CkKGeM%KYrVL#;YZK67G4o!ZqG^ zzwTvjUp*#wV*rDsW*_qU21ym)*j%;71_w!-{UU2S^U#hC_Zbp2&?g^d`|j@thpqzI=S_URN&RJ0XCVMoV`y5n{vt?A=M?M zvR8E`a@hj(2>JDcEOdOS#q%IK0?`sh(G!TKKy(FWSNH3wV}EsDbcX+DkBdrH$Jyhe zYPk4+_PD48>i=jEpQA&H{&7)5)G?aG=jam5?un|Qjs? zAw2eh_~6kGFxZoIjf#hhp(iVtN1kHTL_T@sSu|9TRa@D$Jdhx3Uo})L7*ciX6Rm%} zc~QMz{#P3&LsDARRiePKyCSw^v(Z(o*ItVlu2CMyo-{9M-1ukQO?7Gm^& zpYQluJG#nvoUT0jH+zBnrm6|VzYcNsKpp9QK}xL;)SfKGH(<=jgslA>$k)Ee$NQ{L z-wLrah^;~FO;KzPVs{YRgT5smu|bZpLq)Mgh&?KbO+xGvVw;5PLTfVdtzmrfl92u; z6tR24ecGq21eb{P5>NRp5&2*C%ZRQ$y99njO3q%=`z7|W$2i1(V_We4E&8^Xy6U;c zFwksqdo62j*U)0xPiySP&nuF(1M^#Hzszc=9h_QC^N!ZEL;XUuUpxM){nqRo?f2OC zwLdDnrTtIy8iyMd)ed|27-fQUzH&c(>>?k<%8>SamPnKZ67wC(dVW8ZPH0(?Uo|=S zXL6m7aRq*lA%mItOU-d`fq0Ihcn`#b;Atzk;#GXlU%?gc;usHucp1dgAl?S?IEdFl zJP+c15D$cSA;c3Q-U#tXMe#}^4bOzZi9>5uJn;Mu;-g8AM--b@3HG}aAHiw+6_h

g!eAkhO1h8R;pPM3>RFaj|BeGFSJn%wTuXag9Yv zPWg1#OSQWw^o+ZU!f1R5FA^RRmgD(I*-uLPXZBP-kvM6f-XhC?rEn$<1=2$#O%wk2 z7&REw|2f~u({tosby`g-qMyim>~IqiVJT#x#~O(%^rLz!y5`)NBJ-1cfLRMiLpqsh_Q&Slsu$uz!G5pJ|dIz zzrL1(lVv%lF3bK|6n;v|rFgN;kQe)E1kIwC?>@13<^v}@vpUT1Y`^`)>hBMIv2D$8 z{oSD>Qt~wKN#{?7G5Z`nsJx=Bc4UMtHngcb_K(``0#5_#y2%#kVdX7eZL3}WX+NXo zQ$vha8(SEy@4C{sVnJoYIkJXv<;><M7^q_iSg{(TOJTJ9Gh} z4G?{RXaqzjAX)*@3y5YwbOWLt5dDC`^~fJn@i3y*b1Gg&hOJW^RmHHl|53%!$0sU| z*>(}P*#vdd>bl5BH_z(v<~av;JtNMPJ+SL+7q4O9*Z9GPny+>~7OxrqkgHhn8@*xe zTX@5M>g$bY*w+=Yy}36ss<}%ohxmV{S1pIQ^SDc!9OEjr`b$^op2u9lfn?(^xq)Pe zfh7pi>JxbJn(Sxip5NhQ`JY`nk=Bw;WjFh%0+zJdSf6Bno~t=Xe&P4=fDJ)3rt)H2 zDKGX*QKo5KXMZwuZMw|`|M5j^d=t<`3Anz$U+rR9#70zxNJVKNYNIHM15q7CQ67l; zDEih(6cyswzfPj45yvPJM3o@Q1cQU=_G!dJ!o=WUI^yB#zV{fg$G8!SJ%i7{QT8!$ z-GS0f?#Z$_~K9*iSdY7X@*iL7Kze3WYf3&4n{IsbfX2+fB zl{U>aE3dl4tg>*jS@pgVX0_>kO#8TwX7zzd=4Hus%^EFZ%$f~~o3$!Oc=Js9vTc=# zFF)(`-uzszzjl}2VEJY}ZeE(+aOM)d(d4fzJz-w;%~Z3?bEzhiOu9ZW z!J8^spmbGPe|}>j(ZY%q%GGA-|7Y$`ixw*)DcbONiA>sPWMAfINh-=rE;Yj|=RT=x zeMjbVsc-TL-zU9Ulcv6-iM*OhT3g9Y8;mtmi}MHQbjmkj$=iK1tw>;gPa;DS8j{$G zlHia;ha@~C@gW8PF#?DoK#T!m5D=q)7zV^RAO-?45{RL|)6Sd{<6*;qAVvf+B#1FV z3<_dY5W|8Pm%xUBL5vJyXb@wA7#zgt6vgl$#-}I-2r)v4AwrB1VvrD{gcv5oI3WfK zF;a-3LW~t+un?n#7%s$k6~%xdMyx1?3^8ViLBo@0PF+;S%{ntpTE{hs2w>$>EcbV< zB3~SzBl)W1t=XWW_hk99Wa}qa{k5OD8Z6)Gio3w(s`hK!&81S*`w$fK1VTr&?YbcPnh_qV&hhe6r1$8S+Qvqw_;+#H5QK# zRZOZh)y40UK5wUvn;#vZj$6E1TOD8iX-jq7@~O6ptu|IwY<*WP#VZ!r6rCfBDPB3V zvX}FtR+*t*Dz{mb0)wzA@KJ5!gp7r8&rdV%jPPBPEg zbo=`odh1GuLcKTtbk|?|qr1WKPu+3zcDUoVyq(c-=AF()lV_$lhL3W_5AtLrOk};l zjT$qem<`1Wq=~ZnC@YWh*reu1rx^cShv8s1mT-#I(h8{=_r+v|!SJxtKT|GBrn5*NBkDKI`8MgBTO^EjNf!aeTU-G7OCvxo~>OvPytrw1f-8 zg%~fyfFVW63>)kwUjB0qcj`&>Im?!}O7w7C z-YswWjq=z%v8KJ$u!AY3dtYIe=~_IaY}4vyxdsO^%9lxZRfv2qqrz`#?&xopxGV0R z>yCNj4tJ$ZYozAnky_InsWIIywWUc?QyMO{q~20P>L9hFW>Pb%BgKrIqdp?^I{$Yd z#VUiomts|&pHH!>&JU$nRp%R1tg7=Z85fS2U)+y!vYG-yidcVnx(%{ESN}ctqohQh zKNFiH>ygF*$*LsVlB`N)IxG}peJ)K0vTwcf3FUAyBL76HtmR&4IiUH#+5&b|Fhao^ zrC<$yVn2o~rO*0ZHQ~fJzKMpX%VJnlg74uTToV_8xCz8nAnpQj8Hn3JTt`vd2jW5y zH-fkl#GTl1DK^}S4cB7Bz1VOuHr$L2S7XE7*l;;E+>R~pV2uc!&v8STUCMt?9cMQ} zJg<(k8zCN2$Jvb#H>%_8Mu=N7IA1Vpy2l_Mf(_|SHR9oht6M7mD-G$^;QT_N)?c2c zgRIZhf5XDh^`B4oDvi!Au8aDK?uq(IoQT$yQ|hOR@$6@Rg8Fq1pn#OLb4Z}YWP8~53EIXr}v&Tl-wE{R{Y=-FJBIJcL%$>v4h||jtg)e zhxAeg=!MMPCwY#K{K8cA7?=VaU>rz$D0%iQv|sDIp! zMK2r1;+c;cOKkOB%O_1p-j=q`eE01~^Uleby!{+CujZ_opTT?#)y%qD`Wa_QALE14 zznI<38k~pmrgrl$`;2L6PieR8+GtFFY>RR0b59sEZeDJrK9FjJ6e(0VMAl)YbE{~W z^%rm&hD^&S5fvJBj%H&>U9^c<8t?v$SZrM8Ct2@O>sB>zmSMFrXkWu|*21i+K{>18 z*s8Z%HTEiG;=NQxF0(Mm&3!G%EZLe}Z|C9^T>Z6oKH#GO&BizfFE+okkgNAuzhei) zX%xkAAkG7EAczw|90}q~@U(S)aWF1=;WR4;s>`3Ji5LE6=G8=la&(BZvjuvxTX3Sq z?*{R6%?A;x&O6XkN^#KIpA`qE{-!u&-Y!Lb$Onqo-uk1#?}lFSk~$tXXoor;UhOe; z{GXU;5fsL}!uYo<_IHX^sfzt0E1QhocKzKhU(5f_4L=xRb((v_4z|`KLZ^BoGHx)7 z|IpnPxweyDV$XHnsCo7Dl4-|eMnj}t%J@QNH2k8MaizP;UjDLPuG7n|@?~DoE5v`` zt#E9YSz2FzvGBURsTo z?X{W>617^DW3}3m(OPV1TO;<5CPtlaYZ-NA?H|_tslWD!!BZmQ=6zx`l;@l`n!L$i zLVEn5eH!C+gS`UP4A)&vHJa9FR%4+@7I|cW$0?1<8B@GpYg3~R8&fk*Qx@?}XUN0c zYh0YyBWX#WkM>C!Bxk84YyZre2e5QrY<|lp!^ip|9so}+0N@S$2#-({uYh=lqId_y zLm*xP@f3)+Ks*NGH4x8%cn`#bAYKIVB#1XbJW5f#3gTIc;$09AgLoOl(;(gk@i>Ur zK|BxQeGm_Xcp=0SA>Ih_NQhS|if2N+6XKyTxH{MC8u1WZo$C^t>6u3fneKI7^)Gju z>shJcS*#5qoe*?HsI6_*e%bRrX#OI2Qc9o0W%C_<8mb1KhJ~vKr{w8K%JZo;{xnV} zf~~QozR{*cyEWv>_eu8nQ|bRv`5uG0r0$*dP4Yl%5WO`bwZ21!wMqxLs!3C3^mT^I zwF1oReqt8_phlbV=U(Z1f;SR}uQt8R7-<*SEDdO)J)5@n-Bf2h@CwYR^)++DT zC(`GEQ`S6=K}BcLKH=nG`z$ijWk8BZ)m(7{LbA4qCsDN*n$EsG|*7|GPn73jHYbq0oOq-wFLD^qJ6KLSG5}B=nK6 z5EYA1jgkeZSbU0wr&uVQMZ$Tk?Dex+8RMVoshz9RMS!+}kV1JzSfBT2jY-?%)!4Cg z+)5Qv8dJjq?OTP3td3gwg}>Agu)L73pOEiQSURK;JkwQw@gshRFTnzrH~eF<%d6kx zYy2L6gZLc8?_hqb{t`*}A;cFU{s{3&h+is-Z$kVN;-e5hg~4XnDJmX<3+|Ox@qlJw z7w8rY_5<%z@xWjh;)4M(kU=q!fiaN5F^~Z=mzt)YMdNH~i3(n9v{bOK26`;)UM*$G zomb6YddT5jHgc-IZPk0Ocl+1NGygWj(dq&kAeMU`Zg7F#00YD5W(ZY}#tIF${`?Q* z&O13cs$`T_>YV&ZZnp_TE0ae5Yx&X4&XVGTv=~eKgQi+4WID0$lYAEFwI&-^;hQ1V zeZP`oNObnEtn-9ar|8jFUR~5!vFu@DYB``BW5oy$gTyGBuyLHd@kxMo?tDFml7NgD zYX|Wlh!;UT3F1uKy|>l5&Wz*!hr^xj7QD82YR8S`n?dgqRoGlk#Fe#w~A;t*Dm~=)rBC z$Jg7IjCj@d)UDI?ZBxF`@^t9Ve8sl=;km9o_iWVn4$X9_=Gnokb2ZQIEA+a-gP`{J z+GE>)Xs!E)uby=Oxcd?J&t)I6{rt*{wgW4d+kSccVcWs?2iUyRZn7O(+tT*yfd01M z=2f-*-r{oGAGfw}|1YMBt;F))?x=esZ6#+mb8D09xJwPM=q}wW!d<3oqBJ@E>@HX1 zb9ebNyWABDZ+4&Asv5!ZvlmkHZ`K2dq|R1Yt*M!vMfm!gPecF2pNR9fr)qZMJXZbA zn%5(>Iq3t=xi57$>4}yaU-|5JawER!tF`$TZB@O_bU(9^FR99!I^sA$-7H_XCU@$y zK10>XaBq&!v^5#m=Xbb2L;@f(08d+o6KUXk$OA+oATj~{t;!-79Ou`n%;(65qDTlt zMifO#AaVkc6o{-SinKuF1tKvJnSn?RL~bCG1Cbqw^g!eXB0+4(5JZaDkRymBL1YOc zO%QovL!uxu1(7O!-b@v`Ek z`pdQAPedXAxW98W;X3EUZJCR6M2yU4&#YF1-GLW09T>)OAbO0Fh#tJ%nMB0}QZ0v6cv^%M#ieb&E` zwQA=w2gkluJMkQj0~NUNB(8elG?r%#6D)pIRhMH!V^#QK|A`c;JdG~N-Ye=6sX3(Eno$k{UvFLZqOd0MJyVo0Gv(%1KYe6K?aQPE5HJqn8% zyom(~mH)(uEGOq|TmTm6B6PDQwvJ0I$Zbe?ZM?$VzOze=467^$wxKR-kt@fRAz-I*V8d+l3ss|@FcAQBtBM9d7-nEx= zmQ$Tm#b#rN#Al1f1+ol(M0|4XP`B^QT*WeqIEO<$5v>3Q>OT`<C2W~UaL={sfPV_ZN+DP%=ylQW4>)iA)>at1Q*842X6o{Gh==IMe-TZbtXGe3t2Q=Yof6KTy8|NU%6mIjLu^@qzxK-OMd1pU7(7=LI z&Gp*T&1sZaKFo-^H%2Qtv%R5BPBcmlk2Om7iZ+6)XJ2BgXWL|Y`y%cx4s!sWUwLL68kKv0OopG1@*-qHPVF(0H192RP^FSO3;zSTfg2Cmx zgUfeg2f^jLFTfV$i%A$GL?Pqi(aHv08#ip)(9xi4yaP50I2;kX%ef)DMKUv)xz_cJ zYnZ-Y$#c7U1+4V2gds1shvmh-w&?ld_#=8$h8%X(3E5DqRyj6 zF~=9~V)r*S!nJgF`0D4i2=7vNadU|gxo@+rMEV^@)XPh3C74Sa-~?wT((&hr2892{Wp^{*$}v z^FJEZmh5!fr&Q6Z-?qkmS?^4(#@HRUnoYmbYF+i5t#-LLwAgxvE%wBhM%{>swz^-u zZq)lx%I~i&GaBr5x#JeyX*7JXhr7{?QHJB8ChquAGqr?C<=u@tj+AFS9&tB~?Ih25 z{KVa?SUq{h<6&FU_mT39$9=XIZ~vlQ{^>?r%d`li)y5}mtsnf^xMD%7%{gJ0aplYf z?l#@ijJCsXb+>CY*J$5vh`ar7Z)qLlrrSF1eOl{WVz90Ai?g+>q`=%|;gd%4hXdVR zr{8UKTi@EXu!M9#aardsdD!diDN4cduh5jXq7^cK7+Vf!43;H@1Fn zMQQy{yk#4(+^b#l#nZNdGxunNUYl(jJbbM-WYKjty=#VX?cH~~hsM2N3>!7ZJ-oy` zV?@U;?hyy37^7n2+@p5(GsYAv=^nGXg)#QKL+8p*)h_YQjrMWgH5r+o3bF*SOgd+KYOjhl~5aZg*c#JJ_- ze(vcr?l5lM(84`q@CYMyeq}dHhZQO;Zx_;^axHPNXFVa(!YuDnrIhzb>+`Pdq?AKR zr?0$H)DT1*Qtlu?)hJ(QqRfLsL7WQWSPYm8_o!ENQhHH924T45C>HhCxtkwqBtwWVIfWnaa=Z>7vjKdI5EVL*>GlvL$l%3 z5XWZ2xgieDhLb}a9pdZ|hgTG*hd4gO`5^)T5dnx0K*Rtd2oO<#2m>4901*f_L;@ld ziXs*e!B7;@fCvX0;sFs5h=@Rh1R^F7L4k-0L|7o=0udO9$UuY!A~q1gQ54aE2oD?L z0}&uLL$|W6e6f>h$=)_*$`KVz_KB-5TRv5Y$1ZH zD547yUPTdKhyW{!2t$MzBE}FwhKMpmm?7c}FFAo$WggLQN?r-7|| z(Nmo~i#L`tJa^UeEP4E}v3+)o$0`NY*8VzjhyCdOk=n73N7#>V>!h97(9xc`LY#Ge zU3-&8|w+ZI>IP=Ww@u9<7cDT{cSzr+Abq}^=5m7H_a$+uJJ_f z+o_dEpW}&od5u=`ndj`5MF~2tPRQkGtQF%{!PnFmIG^##-#8Yj_r-pq>eS7uWUNtW3Q^H?c~y zrU&h{uG*y4F1OnrTW^sTd*XnnZiHK_`^9^ndOr@;>c959r@`JCM%v4eawFO zr|)Pj)1p1CHa@Ghe(*2P6$|dyoD)9tTsiX@qfPhMJZ*>HYqV?R@w9Juz0v-+z4nfA z_h=pWuCsS8F;46J;-mJf4s_GHEPT-2{`-PR`?-3Q*}NqMB9adpf7o*q*) zqi5yIJ-vGWYV^6$=#Q0lQY9T zQ8q*f7@!az9NMrhpIi6cLbTVcPp5#fLU(dKeOF<91 zt!f6PpeGDULGmu8AU;o#Qc#MNf>NXul=3%AL7hAlR&XkaV?mq?;$Sel6jWQ4c5pU6 z$KfDO2XQ>8oR1wGkWHKr;D``sgg7L`DItytaZZSXDvFar992=A72>cEr-e8!#CahO z3~^$JBSV}S;?NMMhB!9Fxgib?adL>GL!2Gr@QUK}5XXl&KSTf^A^;Hrh!{Ww0U`1tKmGfq{q&L}(yl z0}&iW5gmx|K*R?kKoAjv2oXe#Ac6!DC5SLV#0er$5Rrlim7<6hM6eV^v>?KzDB=YX zFo=jjgbX5P5J7{88bsJ2;sy~oh{!>NPEo`TB6x}-dJy4L6!C)yAVdTqLI@EM+ejF>5SBS z)!t(EuJ()Gr|5FCPiBVR_q)5yeqX+z_kVk|IbicV{hBp*>H}A#nS&l2r4ODp*BmmT zv#yVxY^I8ShZJH!TYl@;NdYa*ZL?)&4v{crW)AK|elB5YQk8kXdcV6+f`tgnBRiLhA|80|lr5at72862v0uh5Z)s~^*#G?1clUc?>bIj`Z2PUgw%^v#>uPM(*C#zQ`lZ6p z=o_N%9lhb_x6GH1TtE7i9Z#DZKkhzy(>MFgSD*Xe{@31k*WCQT$NSTltukL9|H}R? zcRXOe(QV=WH;1{*w;Ima|90~I`qn6Y|F#Ab^lgW49lht1$DQHP$Ib9nPnr=2pVW&l zI%Y!oJtsiVv?RcD%I`*k+Uo)~GCPpWU0|Nag= zx_T!)dg};1CZwJo^K?hO(pQms<-6h2fx%N*fmjWN5- znk9cSYc&kfYZq!`#+LX-kNvu)S?9o8dfhEW&3ZeZ*6Tl8LvOHpwjOtHsNV4Y>-0ur zztg|vIu6H;w zK<{|?PP5Y&E%nYHjWVx#t*YMTrOsyZBFPuBeEnSd`L*5a>q9&BF^4rOp%1T*w$=gOvaK8EUvjCo&EJ0AtH1u(&<*^ z9ii(sKuYG_2BQqQhWInYry+g~@ok8ILwp?K=ZfO%5P#?9@p(n@ zdx-Bt{2!tL5FLPM0YncVngG!Sh&Dj<0iqF#q7x9UP!zp@XojNb21Gj)ML!@K0?`qO zmO%6bqA3tvfoKavUmzL-(HV%=K=cNpIf|k?5bc5J4@83?It0-oh#o;S38G67ZGz|% zM57=&1<@*qUO_ZVQFIHUT@d|(Xc$DtAX)~|Gl-@^bPb|y5PgGa97N|JS_jcPh~_DZ z?m@Ht~9c>f!J@v zu9| z1c~i-d9h#KEpK~1d1OHh%7A&yT^vlxfbC{N%$^Gd%Szwq!~=CS7%VFV%Szb6rCe6x z{!&E)LM~p}Bfl37lpH^yMU$qqM#(`|xu_8zHRzgEIFkKSl?jLoHi|5wytYWUyv;AQ z%=9$>)BDbk5&E7|QC<~U%%up)%xt+szCfWwzHJqf*w0x=Iz_ddpR!RsN1mncEi<$1 zSDTx_8Hu&mLaq)80VEWVknrPF*|%CY3i*QbKr;V0RiKD4nQD|Z1+6h(&%F^OUTpWt zi~X{0d3$-QbJp?QEuAOk{*{)wV`sW;;%8|gH>RW)a&}HD+&dw?h`oMVXzT9IqP7xg z#fooo7W?vGTG)v)&hX8<(;`0q!&!XAFOJApKXR6smEnkb;$>&a(Jwf(yFN%S)oGri z^vIXe%hZ|TDBIz&^m3)Aiu@%5!4U zs;p`4tm-V5Q7di|w~(;B=qD?RS^+tM07IySx0&|zr~_ubC;Hto|A^fAuHtv+%z zadvSwE#A(NSYm^-S2v29gbEj4yLyr`<&y7 zS-aDnJ>N~ca`cPoZ5ppiYuoAJ^mb()NNXSeg0uZ^uC$J&<~chaEad2PXo|D*rU{O# zcK363*)XW~-BNB(@4oQ4w3Igc(yyLwr1hx(KWEQ@6VrMX|Jd2L*>{e< zYkp4eUtyc0|AV{I2OM7MxMo6H`oL#Kr45=qJ$>-(&S^t>4Nlj`)K9y%i8Fm@=Mrhd z%3hW}yw1V2;lCG2A6YumG4k{3&QXVcag2Vouyf4r49D1o-#V{*{sqUl>06!G&za{K zKkymnglTDMu9o*YZx}E)ZDOsb(be<*5|qpM8Q&(ag-prqYQZ??rrr z{+=*!IqUsdk1b5vuF^s@oVUHa;$$9WoXkm#leu21aK&68d0CSm3hFcBotb0qZX@TG zvy1OQ{0Ax@Vh2BB6JG-O6U3(=eg*L@h<`zR4B}^s;%g9pgZLc8?;ySh@jr+ULi`Zo zix7W=_$0(HA-)OmPl%5~{8UkV72>ao;NFNlUgbPS?p5Iuuv8bsG1+6K`# zh{i#54x)96qIVF@Qxx5UXdguXAQ}kKL5LPY^bn$n5M6|5BSaq|8VS)!h*m=M5~7)k zqMH!ygy<(kLm@f}(Nc(>LNpbks}OC4=&Pbg$0fUy>v)&J#GU{L3SY>oY@?WJz%7jnsARty(jx?W1BU<);Ry|%dqmi+w+$B(hTE6#1 z-od?b4en1-?h$gIkb8yPFXWyf_YJvs$o)g&01^+7xPZh5Bu*gl0*M<%i62NDLE;G# zSCIIE#2HMLu%F9#D^Qys7iN>@P4!ac`)YKe3!Ib4Ih?v$z@;fCah;_mSP1o=-7@Gx@gGvcn}KA20KKdgA33%*2oW(vxa7 z(UV^KOz%>vf!XD`l6vx%AN8&iuQa_G$8t-skaTvtN~O&3@Awn*EP&H3tmP%xgY>#vIuES98$YZ|j3AzHbisqL{81 zI;0Pq@RT`h%Q}5T_gUtMrH|^P8jUqa-F~w^rev}?=DLT?u?HKP*LAtc9QSb-{rdX- z&G8%J^a;f;H(m2f>aL%vm=kY0q)&V|)12IQuReKgWqpeCpgHBC6Z%y9Zgc7+Q=evg z(VW(Ei++pwusOZ)a{boyo6H%N)6G;*e>3&iQj_RZQ`)5}c2bh1ivuSn0D z3a>_vBq&cTX#7i1GD!mxhICMrw1A|CqNE8VT@)p4An5~1BS<8U7b3Q1Q;+CtJ7lE#p9hNLwly&-81Nq0p_dr0~#iVZ;QKv8S~ zVh@U96A-(A*apNtAT|QA6Ns%q>_t&*24XjgVmlD~Q4|}3*pZ^x62zVq#ik&3r6{%q zu`h^?LF^1-YY=-=6q{2#&vvI;Gib|DN+9zGW>nG&Aj|#>aT8ojPHN+Vkc%qH zh2a@pJgb~ikh}HA?jYv9C5uUY|a$A2bnhqL*Eo$nb5WAoN$ zW;gS?H@?pbqf8O~o%2iaCChFK)a#c!jB>W@Fy?p0Gs?cp%DG6=a&CFs-t1vBqv34t z&&^)59ZWppJ-WbVJ8?zqeVHLuZJC4md2Nq2u!VHE(N*ZCC|lv$eO<*|kJ^gu*zJuB zn`4Wdes_Zso9DTsMl5r+XggS^>+_^PF}TDga+zH1X^*GzY@Pc&Hq~0tQHJBtAKFm{ z^WXn|JLNN4Oxf1$p(xqB^NllTxzoRQU6e99P#Af9~f zkNGGNQNxRkwnz5My5((SXxmO3tKZOX`|MWRta&+|lRmhAQYTWbNQeYzoEVJZyh}5WylZONE@i~B{+6wiBv&Vy-_XgL-(VAklr7*us(gb)q)#?ouR(&QD8YjS z5oXV9=$ypyISy{Y*Ahb}cxi7m_|#pxPm%OSq0O^OzI6DrWoGtu5_3`j4pRl-NY;^( zyvRSMwg9&6zYcfHwKy}^gIp7)%KxO=RN(98E*IxxGMv=6`9e_IEuWO`{i|HHzQ=b( zu@3n-GjpuOhO!9hU6qnnl*&`$9Zjq;#04BX?7x!SjuiMiaY1!O$W`H)Uzxj=<*VYG z6eME_mrM8^WPYiKGw3UL{Xo+UUynuRU6oQ2?;Yi|!Xc~og%qLlqGK|FL)U#?D;^xF zN(fqRy|+et$fyr7gaR0VM5xtcK%|uYYOU(Xn2}hy3C9Ew`Z_cSD91TFHaI5GgpYv3 zRQX7a2$5&uP*bx)KYs`RJJWkOquj=)KhbWvk}q1{>u{Tw5H^?+l@+apLyCk-rnJzM zZ)F8YqEoy`1X1)y{7NEqMY^r5Rqtnnx;2aj?RW1&x1XMIb>W zOsVpbMBv{ICUKWQ@w6bODYvh3>auRxIoUMdYt_+3AgzEYvlL78@YMh*>W?}#`l!J6 z=nsX&fJ9qaH$0sks?hPptY}P?U2++5p0fz$8O55f>=ZFCCApKju6XOMNr=|8tc3`r zL?@FS3gC(a}#N}bCd`6l{E>qAT zoKZrNd-00;a>>}_Kc3p4TNe~{(xpTs?@}UCnQ4TTxx^<}lT!+iZvIotEo5?f$R*@* zS@(6?%ScknIJi>}gF{l->dTA$s&0Av?8+nV@RIM24qx~E=sqRC8$D?L8t>p)9`BH; zbG-WK+r8KJo8%qZX}EV-Qg83@IvuQH;v=&ur8V|K^7#=aiy zy6*XK*Er8#uIr7TT;tuJxhA-Fx?E$9cyH+cgLh)*PrQ?wZ}(2FyUBZF#ns*^;fuUe z{(R7T(+_U%)O|O2Z+>T}ciPK6y|=uO>YBdP<+^q5wXPYr_i&|7Z0kyu_@LdjV2#|8 zKRzK%d-CokyOSaIVuUi>UNqU-mn@__W*HKbB{E8tm2t)=V4`YHkt%nb=1G}(Vw9Ad zkyv3CBxVT)v8yOC42fk(OhaND6626qhr~Q2_8}<%Nd-tsKvDyeB8rkKkd%R>4kU#j zsRT(WNNPb+43cV)l!K%mBn2U<2uVpuYC=*}QBoC>vXIn;q%b6vAt?<>ZAgkkQXP`= ziV~OqUh4bH4=OKIOFYTgTmLANQ{{~47h9OT*q6?UHz!AZ_ex61J{!}uw-=_AYIe|> z;OXLR9NkXYxN6!gOrEn)%4W($Wnauy&Jdt8fu4j@NKBp}V3-7h(-Ra(a3Dc~1Pc;0 zNbq2){Prw^=#Rxy#TVCLBOgN(!`$*VC-i7ohli7ELa8He?D%0 ze5-9?@O0;mkdvQUP9<<^^JTj#xT$h#3fv9m@jodKS`8@$YwYo51S)`Kzj|*R0iBVU z_%r8HXQniqb?UMwUqIqm+J52+)cE7OQj}ZH(zIQhF#p-_63ffXoTbK2Jm|JrxtSwL zWHHW>RFG1L|I{k*%^LnrIJpEY;AJS7B(+W|h_N8SSh?nwx08#VImU2FVNQ~z&HM7L zd`g_+WBwK|Uk&IKZpBI1xF@P((%eAp=j8_Ko)hh>;OR=8ClBi?Qkc}WLO8D%rzcdo z>udRi^Nf89gUw;AHrs6RDyvP$$`A`w+VrJ!3GTJe`T@#l8SBrvQspzXbh&lv*PX>Sz(0tekY|rgJ55Euiky~&F6)-JFPG@sv{jYrl)7SFbm^OKvzMv%Xmr{B!|Y`< zZ;meCyuH2r{)gvBUv{l0dfQF&V+ytP#H{E)ztY#WJe6l%KEKMAVxFp_tIV&qWajkb&$dBsTkhV*twU}f5(SWGfJB9&L(mhAW;a3Mo3gbq7xFOiW04ms8y8cg{g9H z*39MnqxmeNm;$k*b-`BN_bd3kl-?UuSVSr&Nla(AB9e9m5Cs33&1%O@ayuE+oi`5^PA& zA;E{-f}-38OqFw~g6iMD72-U5XuxTd(Vw1fUm(OX7M=skh|XmhdCyoN%OGah@(tyx ze*Ek2*>Bhi&Y>b-?mO=pPa8@xG>lDc)3Wx%gKM#G)2{kvnbzgGm0I$S%?1zR;~{)J zfRCkc-`iVbIOS8OzSlcHnzYQ^|L`!v3vFmbJ7<5n9LgU{cxR*&S_dc;rg z`CAo~T}+CvsyMm*pNcn@vMEk!RL&rtrg*(data, _name, value, _serial) do + data.as(String*).value = String.new(value) + end, pointerof(value)) + value.presence + {% else %} + buf = uninitialized LibC::Char[LibC::PROP_VALUE_MAX] + len = LibC.__system_property_get(key, buf) + String.new(buf.to_slice[0, len]) if len > 0 + {% end %} + end + {% else %} + private LOCALTIME = "/etc/localtime" + + def self.load_localtime : ::Time::Location? + if ::File.file?(LOCALTIME) && ::File.readable?(LOCALTIME) + ::File.open(LOCALTIME) do |file| + ::Time::Location.read_zoneinfo("Local", file) + rescue ::Time::Location::InvalidTZDataError + nil + end + end + end + {% end %} {% if flag?(:darwin) %} @@mach_timebase_info : LibC::MachTimebaseInfo? diff --git a/src/lib_c/aarch64-android/c/sys/system_properties.cr b/src/lib_c/aarch64-android/c/sys/system_properties.cr new file mode 100644 index 000000000000..5597df03aaba --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/system_properties.cr @@ -0,0 +1,12 @@ +lib LibC + {% if ANDROID_API >= 26 %} + alias PropInfo = Void + + fun __system_property_find(__name : Char*) : PropInfo* + fun __system_property_read_callback(__pi : PropInfo*, __callback : (Void*, Char*, Char*, UInt32 ->), __cookie : Void*) + {% else %} + PROP_VALUE_MAX = 92 + + fun __system_property_get(__name : Char*, __value : Char*) : Int + {% end %} +end diff --git a/src/time/location.cr b/src/time/location.cr index fea9dde00528..d84c1ab14218 100644 --- a/src/time/location.cr +++ b/src/time/location.cr @@ -298,6 +298,12 @@ class Time::Location return location end + {% if flag?(:android) %} + if location = load_android(name, Crystal::System::Time.android_tzdata_sources) + return location + end + {% end %} + # If none of the database sources contains a suitable location, # try getting it from the operating system. # This is only implemented on Windows. Unix systems usually have a diff --git a/src/time/location/loader.cr b/src/time/location/loader.cr index 7eb45cb20b19..8c85aadd4909 100644 --- a/src/time/location/loader.cr +++ b/src/time/location/loader.cr @@ -25,6 +25,13 @@ class Time::Location end end + # :nodoc: + def self.load_android(name : String, sources : Enumerable(String)) : Time::Location? + if path = find_android_tzdata_file(sources) + load_from_android_tzdata(name, path) || raise InvalidLocationNameError.new(name, path) + end + end + # :nodoc: def self.load_from_dir_or_zip(name : String, source : String) : Time::Location? if source.ends_with?(".zip") @@ -41,6 +48,23 @@ class Time::Location end end + # :nodoc: + def self.load_from_android_tzdata(name : String, path : String) : Time::Location? + return nil unless File.exists?(path) + + mtime = File.info(path).modification_time + if (cache = @@location_cache[name]?) && cache[:time] == mtime + cache[:location] + else + File.open(path) do |file| + read_android_tzdata(file, false) do |location_name, location| + @@location_cache[location_name] = {time: mtime, location: location} + end + @@location_cache[name].try &.[:location] + end + end + end + private def self.open_file_cached(name : String, path : String, &) return nil unless File.exists?(path) @@ -72,11 +96,17 @@ class Time::Location end end + # :nodoc: + def self.find_android_tzdata_file(sources : Enumerable(String)) : String? + sources.find do |path| + File.exists?(path) && File.file?(path) && File.readable?(path) + end + end + + # :nodoc: # Parse "zoneinfo" time zone file. # This is the standard file format used by most operating systems. # See https://data.iana.org/time-zones/tz-link.html, https://github.com/eggert/tz, tzfile(5) - - # :nodoc: def self.read_zoneinfo(location_name : String, io : IO) : Time::Location raise InvalidTZDataError.new unless io.read_string(4) == "TZif" @@ -150,6 +180,38 @@ class Time::Location raise InvalidTZDataError.new(cause: exc) end + private ANDROID_TZDATA_NAME_LENGTH = 40 + private ANDROID_TZDATA_ENTRY_SIZE = ANDROID_TZDATA_NAME_LENGTH + 12 + + # :nodoc: + # Reads a packed tzdata file for Android's Bionic C runtime. Defined in + # https://android.googlesource.com/platform/bionic/+/master/libc/tzcode/bionic.cpp + def self.read_android_tzdata(io : IO, local : Bool, & : String, Time::Location ->) + header = io.read_string(12) + raise InvalidTZDataError.new unless header.starts_with?("tzdata") && header.ends_with?('\0') + + index_offset = read_int32(io) + data_offset = read_int32(io) + io.skip(4) # final_offset + unless index_offset <= data_offset && (data_offset - index_offset).divisible_by?(ANDROID_TZDATA_ENTRY_SIZE) + raise InvalidTZDataError.new + end + + io.seek(index_offset) + entries = Array.new((data_offset - index_offset) // ANDROID_TZDATA_ENTRY_SIZE) do + name = io.read_string(40).rstrip('\0') + start = read_int32(io) + length = read_int32(io) + io.skip(4) # unused + {name, start, length} + end + + entries.each do |(name, start, length)| + io.seek(start + data_offset) + yield name, read_zoneinfo(local ? "Local" : name, read_buffer(io, length)) + end + end + private def self.read_int32(io : IO) io.read_bytes(Int32, IO::ByteFormat::BigEndian) end From ba5c63438c0b586d61b0bada15116962a95420a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 1 Aug 2023 16:38:26 +0200 Subject: [PATCH 0651/1551] Refactor `LLVM.default_target_triple` to avoid regex (#13659) There's no need to use regex for matching plain strings in LLVM.default_target_triple. I replaced it with String#starts_with?. The functional equivalent would've been #includes? but as far as I am aware, the target triple always starts with the arch so this there should never be anything before these search strings. Co-authored-by: Quinton Miller --- spec/std/llvm/llvm_spec.cr | 25 +++++++++++++++++++++++++ src/llvm.cr | 8 ++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/spec/std/llvm/llvm_spec.cr b/spec/std/llvm/llvm_spec.cr index 435be6c85d35..f562a36c70bc 100644 --- a/spec/std/llvm/llvm_spec.cr +++ b/spec/std/llvm/llvm_spec.cr @@ -11,4 +11,29 @@ describe LLVM do LLVM.normalize_triple("x86_64-linux-gnu").should eq("x86_64-unknown-linux-gnu") end end + + it ".default_target_triple" do + triple = LLVM.default_target_triple + {% if flag?(:darwin) %} + triple.should match(/-apple-macosx$/) + {% elsif flag?(:android) %} + triple.should match(/-android$/) + {% elsif flag?(:linux) %} + triple.should match(/-linux/) + {% elsif flag?(:windows) %} + triple.should match(/-windows-/) + {% elsif flag?(:freebsd) %} + triple.should match(/-freebsd/) + {% elsif flag?(:openbsd) %} + triple.should match(/-openbsd/) + {% elsif flag?(:dragonfly) %} + triple.should match(/-dragonfly/) + {% elsif flag?(:netbsd) %} + triple.should match(/-netbsd/) + {% elsif flag?(:wasi) %} + triple.should match(/-wasi/) + {% else %} + pending! "Unknown operating system" + {% end %} + end end diff --git a/src/llvm.cr b/src/llvm.cr index 5f44889aaf0a..a8d7a6b77818 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -88,14 +88,14 @@ module LLVM def self.default_target_triple : String chars = LibLLVM.get_default_target_triple - triple = string_and_dispose(chars) - if triple =~ /x86_64-apple-macosx|x86_64-apple-darwin/ + case triple = string_and_dispose(chars) + when .starts_with?("x86_64-apple-macosx"), .starts_with?("x86_64-apple-darwin") # normalize on `macosx` and remove minimum deployment target version "x86_64-apple-macosx" - elsif triple =~ /aarch64-apple-macosx|aarch64-apple-darwin/ + when .starts_with?("aarch64-apple-macosx"), .starts_with?("aarch64-apple-darwin") # normalize on `macosx` and remove minimum deployment target version "aarch64-apple-macosx" - elsif triple =~ /aarch64-unknown-linux-android/ + when .starts_with?("aarch64-unknown-linux-android") # remove API version "aarch64-unknown-linux-android" else From b68298d6c70be0f76eb4686538d3777b9fb2e3a9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 1 Aug 2023 22:39:38 +0800 Subject: [PATCH 0652/1551] Do not add trailing `+` in `TypeNode#id` for virtual types (#13708) Fixes forum.crystal-lang.org/t/how-to-make-union-types-macro-work-also-on-base-classes/5875. --- spec/compiler/macro/macro_methods_spec.cr | 10 ++++++++++ src/compiler/crystal/semantic/ast.cr | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 7ffc1ca62063..8bc05df4f39a 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1694,6 +1694,16 @@ module Crystal end end + describe "#id" do + it "does not include trailing + for virtual type" do + assert_macro("{{klass.id}}", "Foo") do |program| + foo = NonGenericClassType.new(program, program, "Foo", program.reference) + bar = NonGenericClassType.new(program, program, "Bar", foo) + {klass: TypeNode.new(foo.virtual_type)} + end + end + end + describe "#warning" do it "emits a warning at a specific node" do assert_warning <<-CRYSTAL, "Oh noes" diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index 79e02d1ccf00..d976752b5777 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -87,7 +87,7 @@ module Crystal end def to_macro_id - @type.to_s + @type.not_nil!.devirtualize.to_s end def clone_without_location From 69468c815c9399381b4cf0b36ebb68d745e13784 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 1 Aug 2023 11:41:16 -0300 Subject: [PATCH 0653/1551] Compiler: missing normalization of macro expressions (#13709) (and others expressions too) Fixes #13707 --- spec/compiler/codegen/macro_spec.cr | 16 ++++++++++++++++ src/compiler/crystal/syntax/transformer.cr | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/spec/compiler/codegen/macro_spec.cr b/spec/compiler/codegen/macro_spec.cr index 5fb2567b6bfa..9429f2075139 100644 --- a/spec/compiler/codegen/macro_spec.cr +++ b/spec/compiler/codegen/macro_spec.cr @@ -1879,4 +1879,20 @@ describe "Code gen: macro" do Foo.new.bar )).to_b.should eq(true) end + + it "does block unpacking inside macro expression (##13707)" do + run(%( + {% begin %} + {% + data = [{1, 2}, {3, 4}] + value = 0 + data.each do |(k, v)| + value += k + value += v + end + %} + {{ value }} + {% end %} + )).to_i.should eq(10) + end end diff --git a/src/compiler/crystal/syntax/transformer.cr b/src/compiler/crystal/syntax/transformer.cr index 299e1c53c6ad..bdbd500dc98b 100644 --- a/src/compiler/crystal/syntax/transformer.cr +++ b/src/compiler/crystal/syntax/transformer.cr @@ -559,6 +559,7 @@ module Crystal end def transform(node : MacroExpression) + node.exp = node.exp.transform(self) node end @@ -571,10 +572,15 @@ module Crystal end def transform(node : MacroIf) + node.cond = node.cond.transform(self) + node.then = node.then.transform(self) + node.else = node.else.transform(self) node end def transform(node : MacroFor) + node.exp = node.exp.transform(self) + node.body = node.body.transform(self) node end From a5a617d5499772b1a5bedef70e5dc67ec00bf709 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 1 Aug 2023 22:43:48 +0800 Subject: [PATCH 0654/1551] Hierarchy tool: Fix byte sizes for `Proc`s inside extern structs(#13711) Extern structs store `Proc`s without the closure pointer, so they should use `llvm_embedded_c_type` rather than `llvm_embedded_type` for the correct byte sizes. The total sizes of the extern structs themselves are already correct and unaffected by this PR. --- spec/compiler/crystal/tools/hierarchy_spec.cr | 30 +++++++++++++++++++ src/compiler/crystal/tools/print_hierarchy.cr | 11 +++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/spec/compiler/crystal/tools/hierarchy_spec.cr b/spec/compiler/crystal/tools/hierarchy_spec.cr index ae1bcd2dbadb..8b971947b299 100644 --- a/spec/compiler/crystal/tools/hierarchy_spec.cr +++ b/spec/compiler/crystal/tools/hierarchy_spec.cr @@ -41,6 +41,36 @@ describe Crystal::TextHierarchyPrinter do @x : Bool (1 bytes)\n EOS end + + it "shows correct size for Proc inside extern struct" do + program = semantic(<<-CRYSTAL).program + @[Extern] + struct Foo + @x = uninitialized -> + end + + lib Bar + struct Foo + x : Int32 -> Int32 + end + end + CRYSTAL + + output = String.build { |io| Crystal.print_hierarchy(program, io, "Foo", "text") } + output.should eq(<<-EOS) + - class Object (4 bytes) + | + +- struct Value (0 bytes) + | + +- struct Struct (0 bytes) + | + +- struct Bar::Foo (8 bytes) + | @x : Proc(Int32, Int32) (8 bytes) + | + +- struct Foo (8 bytes) + @x : Proc(Nil) (8 bytes)\n + EOS + end end describe Crystal::JSONHierarchyPrinter do diff --git a/src/compiler/crystal/tools/print_hierarchy.cr b/src/compiler/crystal/tools/print_hierarchy.cr index b6fcb7a76fdc..0e6b3427020c 100644 --- a/src/compiler/crystal/tools/print_hierarchy.cr +++ b/src/compiler/crystal/tools/print_hierarchy.cr @@ -106,8 +106,9 @@ module Crystal @llvm_typer.size_of(@llvm_typer.llvm_struct_type(type)) end - def ivar_size(ivar) - @llvm_typer.size_of(@llvm_typer.llvm_embedded_type(ivar.type)) + def ivar_size(ivar, extern) + llvm_type = extern ? @llvm_typer.llvm_embedded_c_type(ivar.type) : @llvm_typer.llvm_embedded_type(ivar.type) + @llvm_typer.size_of(llvm_type) end end @@ -201,7 +202,7 @@ module Crystal max_name_size = instance_vars.max_of &.name.size max_type_size = typed_instance_vars.max_of?(&.type.to_s.size) || 0 - max_bytes_size = typed_instance_vars.max_of? { |var| ivar_size(var).to_s.size } || 0 + max_bytes_size = typed_instance_vars.max_of? { |var| ivar_size(var, type.extern?).to_s.size } || 0 instance_vars.each do |ivar| print_indent @@ -214,7 +215,7 @@ module Crystal ivar_type.to_s.ljust(@io, max_type_size) with_color.light_gray.surround(@io) do @io << " (" - ivar_size(ivar).to_s.rjust(@io, max_bytes_size) + ivar_size(ivar, type.extern?).to_s.rjust(@io, max_bytes_size) @io << " bytes)" end else @@ -330,7 +331,7 @@ module Crystal @json.object do @json.field "name", instance_var.name.to_s @json.field "type", ivar_type.to_s - @json.field "size_in_bytes", ivar_size(instance_var) + @json.field "size_in_bytes", ivar_size(instance_var, type.extern?) end end end From c592ded7cc8486a891f367af743cfcdaca5e6044 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 4 Aug 2023 21:12:26 +0800 Subject: [PATCH 0655/1551] Support generic types in `crystal tool hierarchy` (#13715) --- spec/compiler/crystal/tools/hierarchy_spec.cr | 234 ++++++++++++------ src/compiler/crystal/tools/print_hierarchy.cr | 176 +++++++++---- 2 files changed, 287 insertions(+), 123 deletions(-) diff --git a/spec/compiler/crystal/tools/hierarchy_spec.cr b/spec/compiler/crystal/tools/hierarchy_spec.cr index 8b971947b299..9a94fcf0a311 100644 --- a/spec/compiler/crystal/tools/hierarchy_spec.cr +++ b/spec/compiler/crystal/tools/hierarchy_spec.cr @@ -1,49 +1,145 @@ require "../../../spec_helper" +private def assert_text_hierarchy(source, filter, expected, *, file = __FILE__, line = __LINE__) + program = semantic(source).program + output = String.build { |io| Crystal.print_hierarchy(program, io, filter, "text") } + output.should eq(expected), file: file, line: line +end + +private def assert_json_hierarchy(source, filter, expected, *, file = __FILE__, line = __LINE__) + program = semantic(source).program + output = String.build { |io| Crystal.print_hierarchy(program, io, filter, "json") } + JSON.parse(output).should eq(JSON.parse(expected)), file: file, line: line +end + describe Crystal::TextHierarchyPrinter do it "works" do - program = semantic(<<-CRYSTAL).program + assert_text_hierarchy <<-CRYSTAL, "ar$", <<-EOS class Foo end class Bar < Foo end CRYSTAL - - output = String.build { |io| Crystal.print_hierarchy(program, io, "ar$", "text") } - output.should eq(<<-EOS) - - class Object (4 bytes) - | - +- class Reference (4 bytes) - | - +- class Foo (4 bytes) - | - +- class Bar (4 bytes)\n - EOS + - class Object (4 bytes) + | + +- class Reference (4 bytes) + | + +- class Foo (4 bytes) + | + +- class Bar (4 bytes)\n + EOS end it "shows correct size for Bool member" do - program = semantic(<<-CRYSTAL).program + assert_text_hierarchy <<-CRYSTAL, "Foo", <<-EOS struct Foo @x = true end CRYSTAL + - class Object (4 bytes) + | + +- struct Value (0 bytes) + | + +- struct Struct (0 bytes) + | + +- struct Foo (1 bytes) + @x : Bool (1 bytes)\n + EOS + end + + it "shows correct size for members with bound types" do + assert_text_hierarchy <<-CRYSTAL, "Foo", <<-EOS + struct Bar1(T) + @x = uninitialized T + end + + class Bar2(T) + @x = uninitialized T + end + + module Bar3(T) + struct I(T) + include Bar3(T) + + @x = uninitialized T + end + end + + module Bar4(T) + class I(T) + include Bar3(T) + + @x = uninitialized T + end + end + + class Foo(T) + @a = uninitialized T* + @b = uninitialized T + @c = uninitialized T[4] + @d = uninitialized Int32[T] + @e = uninitialized T -> + @f = uninitialized T? + @g = uninitialized {T} + @h = uninitialized {x: T} + @i = uninitialized Bar1(T) + @j = uninitialized Bar2(T) + @k = uninitialized Bar3(T) + @l = uninitialized Bar4(T) + end + CRYSTAL + - class Object (4 bytes) + | + +- class Reference (4 bytes) + | + +- class Foo(T) + @a : Pointer(T) ( 8 bytes) + @b : T + @c : StaticArray(T, 4) + @d : StaticArray(Int32, T) + @e : Proc(T, Nil) (16 bytes) + @f : (T | Nil) + @g : Tuple(T) + @h : NamedTuple(x: T) + @i : Bar1(T) + @j : Bar2(T) ( 8 bytes) + @k : Bar3(T) + @l : Bar4(T) ( 8 bytes)\n + EOS + end - output = String.build { |io| Crystal.print_hierarchy(program, io, "Foo", "text") } - output.should eq(<<-EOS) - - class Object (4 bytes) - | - +- struct Value (0 bytes) - | - +- struct Struct (0 bytes) - | - +- struct Foo (1 bytes) - @x : Bool (1 bytes)\n - EOS + it "shows correct total size of generic class if known" do + assert_text_hierarchy <<-CRYSTAL, "Foo", <<-EOS + class Bar1(T) + @x = uninitialized T + end + + class Bar2(T) + @x = uninitialized T + end + + class Foo(T) + @a = uninitialized T* + @b : Bar1(T) | Bar2(T)? + @c = uninitialized T*[6] + @d = uninitialized Int64 + end + CRYSTAL + - class Object (4 bytes) + | + +- class Reference (4 bytes) + | + +- class Foo(T) (80 bytes) + @a : Pointer(T) ( 8 bytes) + @b : (Bar1(T) | Bar2(T) | Nil) ( 8 bytes) + @c : StaticArray(Pointer(T), 6) (48 bytes) + @d : Int64 ( 8 bytes)\n + EOS end it "shows correct size for Proc inside extern struct" do - program = semantic(<<-CRYSTAL).program + assert_text_hierarchy <<-CRYSTAL, "Foo", <<-EOS @[Extern] struct Foo @x = uninitialized -> @@ -55,63 +151,57 @@ describe Crystal::TextHierarchyPrinter do end end CRYSTAL - - output = String.build { |io| Crystal.print_hierarchy(program, io, "Foo", "text") } - output.should eq(<<-EOS) - - class Object (4 bytes) - | - +- struct Value (0 bytes) - | - +- struct Struct (0 bytes) - | - +- struct Bar::Foo (8 bytes) - | @x : Proc(Int32, Int32) (8 bytes) - | - +- struct Foo (8 bytes) - @x : Proc(Nil) (8 bytes)\n - EOS + - class Object (4 bytes) + | + +- struct Value (0 bytes) + | + +- struct Struct (0 bytes) + | + +- struct Bar::Foo (8 bytes) + | @x : Proc(Int32, Int32) (8 bytes) + | + +- struct Foo (8 bytes) + @x : Proc(Nil) (8 bytes)\n + EOS end end describe Crystal::JSONHierarchyPrinter do it "works" do - program = semantic(<<-CRYSTAL).program + assert_json_hierarchy <<-CRYSTAL, "ar$", <<-JSON class Foo end class Bar < Foo end CRYSTAL - - output = String.build { |io| Crystal.print_hierarchy(program, io, "ar$", "json") } - JSON.parse(output).should eq(JSON.parse(<<-EOS)) - { - "name": "Object", - "kind": "class", - "size_in_bytes": 4, - "sub_types": [ - { - "name": "Reference", - "kind": "class", - "size_in_bytes": 4, - "sub_types": [ - { - "name": "Foo", - "kind": "class", - "size_in_bytes": 4, - "sub_types": [ - { - "name": "Bar", - "kind": "class", - "size_in_bytes": 4, - "sub_types": [] - } - ] - } - ] - } - ] - } - EOS + { + "name": "Object", + "kind": "class", + "size_in_bytes": 4, + "sub_types": [ + { + "name": "Reference", + "kind": "class", + "size_in_bytes": 4, + "sub_types": [ + { + "name": "Foo", + "kind": "class", + "size_in_bytes": 4, + "sub_types": [ + { + "name": "Bar", + "kind": "class", + "size_in_bytes": 4, + "sub_types": [] + } + ] + } + ] + } + ] + } + JSON end end diff --git a/src/compiler/crystal/tools/print_hierarchy.cr b/src/compiler/crystal/tools/print_hierarchy.cr index 0e6b3427020c..5ec189acbe29 100644 --- a/src/compiler/crystal/tools/print_hierarchy.cr +++ b/src/compiler/crystal/tools/print_hierarchy.cr @@ -103,13 +103,111 @@ module Crystal end def type_size(type) - @llvm_typer.size_of(@llvm_typer.llvm_struct_type(type)) + return nil unless constant_type_size?(type) + + if type.is_a?(GenericClassType) + # obtain a "generic instance" where all arguments are simply the unbound + # parameters themselves; if we are here, `LLVMTyper` should never be + # requesting the size of a `TypeParameter` + type_vars = type.type_vars.map { |type_var| type.type_parameter(type_var).as(TypeVar) } + type = type.instantiate(type_vars) + end + + llvm_type = + case type + when PointerInstanceType, ProcInstanceType + @llvm_typer.llvm_type(type, wants_size: true) + when InstanceVarContainer + @llvm_typer.llvm_struct_type(type, wants_size: true) + else + @llvm_typer.llvm_type(type, wants_size: true) + end + + @llvm_typer.size_of(llvm_type) end def ivar_size(ivar, extern) - llvm_type = extern ? @llvm_typer.llvm_embedded_c_type(ivar.type) : @llvm_typer.llvm_embedded_type(ivar.type) + return nil unless constant_ivar_size?(ivar.type) + + llvm_type = if extern + @llvm_typer.llvm_embedded_c_type(ivar.type, wants_size: true) + else + @llvm_typer.llvm_embedded_type(ivar.type, wants_size: true) + end + @llvm_typer.size_of(llvm_type) end + + # Returns `true` if `type`'s size (`sizeof` for values, `instance_sizeof` + # for references) is a constant, in particular if it doesn't depend on + # `type`'s generic type parameters. `type` is never a generic instance. + def constant_type_size?(type) + case type + when GenericUnionType, StaticArrayType, TupleType, NamedTupleType + false + when PointerType, ProcType + true + when GenericClassType + type.all_instance_vars.each do |_, ivar| + return false unless ivar_type = ivar.type? + return false unless constant_ivar_size?(ivar_type) + end + true + else + true + end + end + + # Returns `true` if `sizeof(type)` is a constant, in particular if it + # doesn't depend on `type`'s generic type parameters. Unlike + # `#constant_type_size?`, here `type` can be a generic instance, but not an + # uninstantiated generic. + def constant_ivar_size?(type) + return true unless type.unbound? || type.is_a?(GenericType) + + case type + when GenericType + type_vars = type.type_vars.map { |type_var| type.type_parameter(type_var).as(TypeVar) } + constant_ivar_size?(type.instantiate(type_vars)) + when TypeParameter + false + when MixedUnionType + type.union_types.all? { |t| constant_ivar_size?(t) } + when StaticArrayInstanceType + return false unless constant_ivar_size?(type.element_type) + case size_var = type.size + when NumberLiteral + true + when Var + return false unless size_var_type = size_var.type? + return false unless constant_ivar_size?(size_var_type) + else + false + end + when TupleInstanceType + type.tuple_types.all? { |t| constant_ivar_size?(t) } + when NamedTupleInstanceType + type.entries.all? { |entry| constant_ivar_size?(entry.type) } + when GenericModuleInstanceType + # TODO: verify + type.generic_type.each_instantiated_type do |instance| + instance.as(GenericModuleInstanceType).raw_including_types.try &.each do |including_type| + return false unless constant_ivar_size?(including_type) + end + end + true + when InstanceVarContainer + if type.struct? + type.all_instance_vars.each do |_, ivar| + return false unless ivar_type = ivar.type? + return false unless constant_ivar_size?(ivar_type) + end + end + true + else + true + end + end end class TextHierarchyPrinter < HierarchyPrinter @@ -150,10 +248,9 @@ module Crystal @io << "+" unless @indents.empty? @io << "- " << (type.struct? ? "struct" : "class") << " " << type - if (type.is_a?(NonGenericClassType) || type.is_a?(GenericClassInstanceType)) && - !type.is_a?(PointerInstanceType) && !type.is_a?(ProcInstanceType) + if type_size = type_size(type) with_color.light_gray.surround(@io) do - @io << " (" << type_size(type) << " bytes)" + @io << " (" << type_size << " bytes)" end end @io << '\n' @@ -174,35 +271,22 @@ module Crystal # Nothing to do end - def print_instance_vars(type : GenericClassType, has_subtypes) - instance_vars = type.instance_vars - return if instance_vars.empty? - - max_name_size = instance_vars.keys.max_of &.size - - instance_vars.each do |name, var| - print_indent - @io << (@indents.last ? "|" : " ") << (has_subtypes ? " . " : " ") - - with_color.light_gray.surround(@io) do - name.ljust(@io, max_name_size) - @io << " : " << var - end - @io << '\n' - end - end - def print_instance_vars(type, has_subtypes) instance_vars = type.instance_vars return if instance_vars.empty? instance_vars = instance_vars.values - typed_instance_vars = instance_vars.select &.type? + instance_var_types = {} of MetaTypeVar => {Type, UInt64?} + instance_vars.each do |ivar| + if ivar_type = ivar.type? + instance_var_types[ivar] = {ivar_type, ivar_size(ivar, type.extern?)} + end + end max_name_size = instance_vars.max_of &.name.size - max_type_size = typed_instance_vars.max_of?(&.type.to_s.size) || 0 - max_bytes_size = typed_instance_vars.max_of? { |var| ivar_size(var, type.extern?).to_s.size } || 0 + max_type_size = instance_var_types.max_of? { |_, (type, _)| type.to_s.size } || 0 + max_bytes_size = instance_var_types.max_of? { |_, (_, size)| size.try(&.to_s.size) || 0 } || 0 instance_vars.each do |ivar| print_indent @@ -211,12 +295,17 @@ module Crystal with_color.light_gray.surround(@io) do ivar.name.ljust(@io, max_name_size) @io << " : " - if ivar_type = ivar.type? - ivar_type.to_s.ljust(@io, max_type_size) - with_color.light_gray.surround(@io) do - @io << " (" - ivar_size(ivar, type.extern?).to_s.rjust(@io, max_bytes_size) - @io << " bytes)" + if entry = instance_var_types[ivar]? + ivar_type, size = entry + if size + ivar_type.to_s.ljust(@io, max_type_size) + with_color.light_gray.surround(@io) do + @io << " (" + size.to_s.rjust(@io, max_bytes_size) + @io << " bytes)" + end + else + @io << ivar_type end else @io << "MISSING".colorize.red.bright @@ -285,8 +374,7 @@ module Crystal @json.field "name", type.to_s @json.field "kind", type.struct? ? "struct" : "class" - if (type.is_a?(NonGenericClassType) || type.is_a?(GenericClassInstanceType)) && - !type.is_a?(PointerInstanceType) && !type.is_a?(ProcInstanceType) + if type_size = type_size(type) @json.field "size_in_bytes", type_size(type) end end @@ -303,22 +391,6 @@ module Crystal # Nothing to do end - def print_instance_vars(type : GenericClassType, has_subtypes) - instance_vars = type.instance_vars - return if instance_vars.empty? - - @json.field "instance_vars" do - @json.array do - instance_vars.each do |name, var| - @json.object do - @json.field "name", name.to_s - @json.field "type", var.to_s - end - end - end - end - end - def print_instance_vars(type, has_subtypes) instance_vars = type.instance_vars return if instance_vars.empty? @@ -331,7 +403,9 @@ module Crystal @json.object do @json.field "name", instance_var.name.to_s @json.field "type", ivar_type.to_s - @json.field "size_in_bytes", ivar_size(instance_var, type.extern?) + if ivar_size = ivar_size(instance_var, type.extern?) + @json.field "size_in_bytes", ivar_size + end end end end From 14d8fb202045a4f0ce2cf486cbe582beba0c9388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20H=C3=B6rberg?= Date: Mon, 14 Aug 2023 19:57:29 +0200 Subject: [PATCH 0656/1551] Add additional fields to `GC:ProfStats` (#13734) --- src/gc.cr | 4 +++- src/gc/boehm.cr | 6 +++++- src/gc/none.cr | 4 +++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/gc.cr b/src/gc.cr index 15d3423e0482..e7254a4a140a 100644 --- a/src/gc.cr +++ b/src/gc.cr @@ -66,7 +66,9 @@ module GC gc_no : UInt64, markers_m1 : UInt64, bytes_reclaimed_since_gc : UInt64, - reclaimed_bytes_before_gc : UInt64 + reclaimed_bytes_before_gc : UInt64, + expl_freed_bytes_since_gc : UInt64, + obtained_from_os_bytes : UInt64 # Allocates and clears *size* bytes of memory. # diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 35858ca1bf8b..28056f75aead 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -54,6 +54,8 @@ lib LibGC markers_m1 : Word bytes_reclaimed_since_gc : Word reclaimed_bytes_before_gc : Word + expl_freed_bytes_since_gc : Word + obtained_from_os_bytes : Word end fun init = GC_init @@ -241,7 +243,9 @@ module GC gc_no: stats.gc_no.to_u64!, markers_m1: stats.markers_m1.to_u64!, bytes_reclaimed_since_gc: stats.bytes_reclaimed_since_gc.to_u64!, - reclaimed_bytes_before_gc: stats.reclaimed_bytes_before_gc.to_u64!) + reclaimed_bytes_before_gc: stats.reclaimed_bytes_before_gc.to_u64!, + expl_freed_bytes_since_gc: stats.expl_freed_bytes_since_gc.to_u64!, + obtained_from_os_bytes: stats.obtained_from_os_bytes.to_u64!) end {% if flag?(:win32) %} diff --git a/src/gc/none.cr b/src/gc/none.cr index 2c0530e7599d..4e4441f6e54e 100644 --- a/src/gc/none.cr +++ b/src/gc/none.cr @@ -66,7 +66,9 @@ module GC gc_no: 0, markers_m1: 0, bytes_reclaimed_since_gc: 0, - reclaimed_bytes_before_gc: 0) + reclaimed_bytes_before_gc: 0, + expl_freed_bytes_since_gc: 0, + obtained_from_os_bytes: 0) end {% if flag?(:win32) %} From dfaf4335f0e6e3ba514a4c3bcfea7e9ec975d252 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 15 Aug 2023 01:57:44 +0800 Subject: [PATCH 0657/1551] Clear `Time::Location` cache before `.load_android` specs (#13718) --- spec/std/time/location_spec.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/std/time/location_spec.cr b/spec/std/time/location_spec.cr index 6059c4783021..bcf7595cffd8 100644 --- a/spec/std/time/location_spec.cr +++ b/spec/std/time/location_spec.cr @@ -168,6 +168,7 @@ class Time::Location describe ".load_android" do it "loads Europe/Berlin" do + Location.__clear_location_cache location = Location.load_android("Europe/Berlin", {datapath("android_tzdata")}).should_not be_nil location.name.should eq "Europe/Berlin" @@ -187,6 +188,7 @@ class Time::Location it "loads new data if tzdata file was changed" do tzdata_path = datapath("android_tzdata") + Location.__clear_location_cache location1 = Location.load_android("Europe/Berlin", {tzdata_path}) File.touch(tzdata_path) location2 = Location.load_android("Europe/Berlin", {tzdata_path}) From cb756f985bd26fed0875a308942b5afd2df49a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 15 Aug 2023 14:39:33 +0200 Subject: [PATCH 0658/1551] Fix: Set encoding in `XML.parse_html` explicitly to UTF-8 (#13705) --- spec/std/xml/xml_spec.cr | 32 ++++++++++++++++++++++++++++++++ src/xml.cr | 4 ++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/spec/std/xml/xml_spec.cr b/spec/std/xml/xml_spec.cr index efbb79e6e226..6ad874fb290a 100644 --- a/spec/std/xml/xml_spec.cr +++ b/spec/std/xml/xml_spec.cr @@ -419,6 +419,38 @@ describe XML do assert_prints node.to_xml, %(

<foo>

) end + it "parses HTML UTF-8 from memory (#13703)" do + doc = XML.parse_html("

České psaní

") + + node = doc.root.try(&.children.first).should_not be_nil + + node.text.should eq "České psaní" + end + + it "parses HTML UTF-8 from IO (#13703)" do + doc = XML.parse_html(IO::Memory.new("

České psaní

")) + + node = doc.root.try(&.children.first).should_not be_nil + + node.text.should eq "České psaní" + end + + it "parses XML UTF-8 from memory (#13703)" do + doc = XML.parse("

České psaní

") + + node = doc.root.try(&.children.first).should_not be_nil + + node.text.should eq "České psaní" + end + + it "parses XML UTF-8 from IO (#13703)" do + doc = XML.parse(IO::Memory.new("

České psaní

")) + + node = doc.root.try(&.children.first).should_not be_nil + + node.text.should eq "České psaní" + end + it "gets empty content" do doc = XML.parse("") doc.children.first.content.should eq("") diff --git a/src/xml.cr b/src/xml.cr index 05a1e3d41891..e0529be130f3 100644 --- a/src/xml.cr +++ b/src/xml.cr @@ -78,7 +78,7 @@ module XML # See `HTMLParserOptions.default` for default options. def self.parse_html(string : String, options : HTMLParserOptions = HTMLParserOptions.default) : Node raise XML::Error.new("Document is empty", 0) if string.empty? - from_ptr { LibXML.htmlReadMemory(string, string.bytesize, nil, nil, options) } + from_ptr { LibXML.htmlReadMemory(string, string.bytesize, nil, "utf-8", options) } end # Parses an HTML document from *io* with *options* into an `XML::Node`. @@ -92,7 +92,7 @@ module XML ->(ctx) { 0 }, Box(IO).box(io), nil, - nil, + "utf-8", options, ) } end From 9390ee503b290e79c0dd886642cd1ea033d52b2e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 15 Aug 2023 20:39:47 +0800 Subject: [PATCH 0659/1551] Add more `Colorize::Mode` flags (#13745) --- spec/std/colorize_spec.cr | 5 +++++ src/colorize.cr | 29 ++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/spec/std/colorize_spec.cr b/spec/std/colorize_spec.cr index 5b21c589af11..c318cfaa8dbc 100644 --- a/spec/std/colorize_spec.cr +++ b/spec/std/colorize_spec.cr @@ -86,10 +86,15 @@ describe "colorize" do colorize("hello").bold.to_s.should eq("\e[1mhello\e[0m") colorize("hello").bright.to_s.should eq("\e[1mhello\e[0m") colorize("hello").dim.to_s.should eq("\e[2mhello\e[0m") + colorize("hello").italic.to_s.should eq("\e[3mhello\e[0m") colorize("hello").underline.to_s.should eq("\e[4mhello\e[0m") colorize("hello").blink.to_s.should eq("\e[5mhello\e[0m") + colorize("hello").blink_fast.to_s.should eq("\e[6mhello\e[0m") colorize("hello").reverse.to_s.should eq("\e[7mhello\e[0m") colorize("hello").hidden.to_s.should eq("\e[8mhello\e[0m") + colorize("hello").strikethrough.to_s.should eq("\e[9mhello\e[0m") + colorize("hello").double_underline.to_s.should eq("\e[21mhello\e[0m") + colorize("hello").overline.to_s.should eq("\e[53mhello\e[0m") end it "colorizes mode combination" do diff --git a/src/colorize.cr b/src/colorize.cr index 4fee40faf616..48975c8a451f 100644 --- a/src/colorize.cr +++ b/src/colorize.cr @@ -285,7 +285,7 @@ module Colorize Bright = 1 # Dims the text color. Dim - # Underlines the text. + # Draws a line below the text. Underline # Makes the text blink slowly. Blink @@ -293,16 +293,31 @@ module Colorize Reverse # Makes the text invisible. Hidden + # Italicizes the text. + Italic + # Makes the text blink quickly. + BlinkFast + # Crosses out the text. + Strikethrough + # Draws two lines below the text. + DoubleUnderline + # Draws a line above the text. + Overline end end private def each_code(mode : Colorize::Mode, &) - yield '1' if mode.bold? - yield '2' if mode.dim? - yield '4' if mode.underline? - yield '5' if mode.blink? - yield '7' if mode.reverse? - yield '8' if mode.hidden? + yield "1" if mode.bold? + yield "2" if mode.dim? + yield "3" if mode.italic? + yield "4" if mode.underline? + yield "5" if mode.blink? + yield "6" if mode.blink_fast? + yield "7" if mode.reverse? + yield "8" if mode.hidden? + yield "9" if mode.strikethrough? + yield "21" if mode.double_underline? + yield "53" if mode.overline? end # A colorized object. Colors and text decorations can be modified. From d0ad0275eb9db4f9a40dffc8d9825eac46eeb36b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 17 Aug 2023 03:59:35 +0800 Subject: [PATCH 0660/1551] Do not use nilable `Pointer`s (#13710) --- src/compiler/crystal/interpreter/interpreter.cr | 2 +- src/io/buffered.cr | 2 ++ src/openssl/ssl/context.cr | 2 +- src/proc.cr | 2 +- src/string/formatter.cr | 4 ++-- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index ad02f5e52cd2..608fb6d85d1a 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -1082,7 +1082,7 @@ class Crystal::Repl::Interpreter argv.size + 1 end - @argv_unsafe : Pointer(Pointer(UInt8))? + @argv_unsafe = Pointer(Pointer(UInt8)).null private def argv_unsafe @argv_unsafe ||= begin diff --git a/src/io/buffered.cr b/src/io/buffered.cr index bb5a0bb35fe3..df66fca6dc19 100644 --- a/src/io/buffered.cr +++ b/src/io/buffered.cr @@ -6,6 +6,8 @@ # Additionally, several methods, like `#gets`, are implemented in a more # efficient way. module IO::Buffered + @in_buffer = Pointer(UInt8).null + @out_buffer = Pointer(UInt8).null @in_buffer_rem = Bytes.empty @out_count = 0 @sync = false diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr index a1f443057d9b..a46e924346a7 100644 --- a/src/openssl/ssl/context.cr +++ b/src/openssl/ssl/context.cr @@ -452,7 +452,7 @@ abstract class OpenSSL::SSL::Context LibSSL.ssl_ctx_set_verify(@handle, mode, nil) end - @alpn_protocol : Pointer(Void)? + @alpn_protocol = Pointer(Void).null # Specifies an ALPN protocol to negotiate with the remote endpoint. This is # required to negotiate HTTP/2 with browsers, since browser vendors decided diff --git a/src/proc.cr b/src/proc.cr index 429d278c6dfa..fca714517dbf 100644 --- a/src/proc.cr +++ b/src/proc.cr @@ -69,7 +69,7 @@ # ``` # module Ticker # # The callback for the user doesn't have a Void* -# @@box : Pointer(Void)? +# @@box = Pointer(Void).null # # def self.on_tick(&callback : Int32 ->) # # Since Proc is a {Void*, Void*}, we can't turn that into a Void*, so we diff --git a/src/string/formatter.cr b/src/string/formatter.cr index 54369c9627b0..8ff32cf2ada0 100644 --- a/src/string/formatter.cr +++ b/src/string/formatter.cr @@ -15,8 +15,8 @@ struct String::Formatter(A) Named end - @format_buf : Pointer(UInt8)? - @temp_buf : Pointer(UInt8)? + @format_buf = Pointer(UInt8).null + @temp_buf = Pointer(UInt8).null @arg_mode : Mode = :none def initialize(string, @args : A, @io : IO) From 996f0eb6797d99fc19beadc07e386b1a87cb6219 Mon Sep 17 00:00:00 2001 From: HOMODELUNA <72009635+HOMODELUNA@users.noreply.github.com> Date: Thu, 17 Aug 2023 16:33:00 +0800 Subject: [PATCH 0661/1551] Fix: Chop git suffix from `LibLLVM::VERSION` (#13699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/llvm/lib_llvm.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index b35ca99af2e0..2f3d52d1d3bd 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -10,7 +10,7 @@ end {% end %} @[Link(ldflags: {{"`#{LibLLVM::LLVM_CONFIG} --libs --system-libs --ldflags#{" --link-static".id if flag?(:static)}#{" 2> /dev/null".id unless flag?(:win32)}`"}})] lib LibLLVM - VERSION = {{`#{LibLLVM::LLVM_CONFIG} --version`.chomp.stringify}} + VERSION = {{`#{LibLLVM::LLVM_CONFIG} --version`.chomp.stringify.gsub(/git/, "")}} BUILT_TARGETS = {{ ( env("LLVM_TARGETS") || `#{LibLLVM::LLVM_CONFIG} --targets-built` ).strip.downcase.split(' ').map(&.id.symbolize) }} From e1367912a5e9653a95c7b84eece94ce36470d044 Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Fri, 18 Aug 2023 03:23:52 -0300 Subject: [PATCH 0662/1551] Fix typo in regex.cr (#13751) --- src/regex.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/regex.cr b/src/regex.cr index c0f0ad56b4da..68d25f5d6be8 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -81,7 +81,7 @@ require "./regex/match_data" # # Many programming languages and tools implement their own regular expression # language, but Crystal uses [PCRE2](http://www.pcre.org/), a popular C library, with -# [JIT complication](http://www.pcre.org/current/doc/html/pcre2jit.html) enabled +# [JIT compilation](http://www.pcre.org/current/doc/html/pcre2jit.html) enabled # for providing regular expressions. Here give a brief summary of the most # basic features of regular expressions - grouping, repetition, and # alternation - but the feature set of PCRE2 extends far beyond these, and we From bfc7e2078d032bdd33846b170b4ee633fbddcf3b Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Fri, 18 Aug 2023 01:24:13 -0500 Subject: [PATCH 0663/1551] Optimize `IO#read_string(0)` (#13732) --- src/io.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/io.cr b/src/io.cr index bce6497db606..0294ec0272b7 100644 --- a/src/io.cr +++ b/src/io.cr @@ -433,6 +433,8 @@ abstract class IO # io.read_string(6) # raises IO::EOFError # ``` def read_string(bytesize : Int) : String + return "" if bytesize == 0 + String.new(bytesize) do |ptr| if decoder = decoder() read = decoder.read_utf8(self, Slice.new(ptr, bytesize)) From cc0c1098177c30a3b2e793ee31c944813b8e7376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 19 Aug 2023 21:32:08 +0200 Subject: [PATCH 0664/1551] Remove double `.cr.cr` extension in `require` path lookup (#13749) --- .../crystal_path/crystal_path_spec.cr | 28 ++++++++++++++++-- src/compiler/crystal/crystal_path.cr | 29 +++++++++---------- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/spec/compiler/crystal_path/crystal_path_spec.cr b/spec/compiler/crystal_path/crystal_path_spec.cr index a4e87b9690a4..d9ecb3f0b9f2 100644 --- a/spec/compiler/crystal_path/crystal_path_spec.cr +++ b/spec/compiler/crystal_path/crystal_path_spec.cr @@ -111,11 +111,33 @@ describe Crystal::CrystalPath do it "foo.cr" do assert_iterates_yielding [ "x/foo.cr", - "x/foo.cr/foo.cr.cr", - "x/foo.cr/src/foo.cr.cr", + "x/foo.cr/foo.cr", + "x/foo.cr/src/foo.cr", ], path.each_file_expansion("foo.cr", "x") end + it "foo.cr/bar" do + assert_iterates_yielding [ + "x/foo.cr/bar.cr", + "x/foo.cr/src/bar.cr", + "x/foo.cr/src/foo.cr/bar.cr", + "x/foo.cr/bar/bar.cr", + "x/foo.cr/src/bar/bar.cr", + "x/foo.cr/src/foo.cr/bar/bar.cr", + ], path.each_file_expansion("foo.cr/bar", "x") + end + + it "foo.cr/bar.cr" do + assert_iterates_yielding [ + "x/foo.cr/bar.cr", + "x/foo.cr/src/bar.cr", + "x/foo.cr/src/foo.cr/bar.cr", + "x/foo.cr/bar.cr/bar.cr", + "x/foo.cr/src/bar.cr/bar.cr", + "x/foo.cr/src/foo.cr/bar.cr/bar.cr", + ], path.each_file_expansion("foo.cr/bar.cr", "x") + end + it "foo" do assert_iterates_yielding [ "x/foo.cr", @@ -134,7 +156,7 @@ describe Crystal::CrystalPath do it "./foo.cr" do assert_iterates_yielding [ "x/./foo.cr", - "x/./foo.cr/foo.cr.cr", + "x/./foo.cr/foo.cr", ], path.each_file_expansion("./foo.cr", "x") end diff --git a/src/compiler/crystal/crystal_path.cr b/src/compiler/crystal/crystal_path.cr index 0e2da64781cd..6ba5b96033ef 100644 --- a/src/compiler/crystal/crystal_path.cr +++ b/src/compiler/crystal/crystal_path.cr @@ -138,34 +138,33 @@ module Crystal if !filename_is_relative && shard_path shard_src = "#{relative_to}/#{shard_name}/src" + shard_path_stem = shard_path.rchop(".cr") # If it's "foo/bar/baz", check if "foo/src/bar/baz.cr" exists (for a shard, non-namespaced structure) - yield "#{shard_src}/#{shard_path}.cr" + yield "#{shard_src}/#{shard_path_stem}.cr" # Then check if "foo/src/foo/bar/baz.cr" exists (for a shard, namespaced structure) - yield "#{shard_src}/#{shard_name}/#{shard_path}.cr" + yield "#{shard_src}/#{shard_name}/#{shard_path_stem}.cr" # If it's "foo/bar/baz", check if "foo/bar/baz/baz.cr" exists (std, nested) - basename = File.basename(relative_filename) + basename = File.basename(relative_filename, ".cr") yield "#{relative_filename}/#{basename}.cr" # If it's "foo/bar/baz", check if "foo/src/foo/bar/baz/baz.cr" exists (shard, non-namespaced, nested) - yield "#{shard_src}/#{shard_path}/#{shard_path}.cr" + yield "#{shard_src}/#{shard_path}/#{shard_path_stem}.cr" # If it's "foo/bar/baz", check if "foo/src/foo/bar/baz/baz.cr" exists (shard, namespaced, nested) - yield "#{shard_src}/#{shard_name}/#{shard_path}/#{shard_path}.cr" - - return nil - end - - basename = File.basename(relative_filename) + yield "#{shard_src}/#{shard_name}/#{shard_path}/#{shard_path_stem}.cr" + else + basename = File.basename(relative_filename, ".cr") - # If it's "foo", check if "foo/foo.cr" exists (for the std, nested) - yield "#{relative_filename}/#{basename}.cr" + # If it's "foo", check if "foo/foo.cr" exists (for the std, nested) + yield "#{relative_filename}/#{basename}.cr" - unless filename_is_relative - # If it's "foo", check if "foo/src/foo.cr" exists (for a shard) - yield "#{relative_filename}/src/#{basename}.cr" + unless filename_is_relative + # If it's "foo", check if "foo/src/foo.cr" exists (for a shard) + yield "#{relative_filename}/src/#{basename}.cr" + end end end From e351ddea6266973b4e5022d51a43bf1352f10d9a Mon Sep 17 00:00:00 2001 From: syeopite <70992037+syeopite@users.noreply.github.com> Date: Sun, 20 Aug 2023 09:52:01 +0000 Subject: [PATCH 0665/1551] Update PGP key link (#13754) --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index d4c06533aa11..b2435e444ea4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,6 +6,6 @@ the issue tracker. We have a dedicated mailbox where we keep track of them: You can encrypt your message via [Keybase](https://keybase.io/encrypt) (set -recipient `crystal`) or with our [PGP key](https://crystal-lang.org/crystal-pgp-key.txt) +recipient `crystal`) or with our [PGP key](https://crystal-lang.org/community/crystal-pgp-key.txt) (fingerprint `5995 C83C D754 BE44 8164 1929 0961 7FD3 7CC0 6B54`) also available on [Keybase server](https://keybase.io/crystal/pgp_keys.asc). From d8be15afad896731b5bea9156e2a0176407995ca Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 28 Aug 2023 06:53:42 -0500 Subject: [PATCH 0666/1551] Add documentation for `HTTP::Headers#add` (#13762) Co-authored-by: Jack Thorne --- src/http/headers.cr | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/http/headers.cr b/src/http/headers.cr index 61e04881c1a9..3d4fd49e34c5 100644 --- a/src/http/headers.cr +++ b/src/http/headers.cr @@ -120,6 +120,18 @@ struct HTTP::Headers false end + # Adds a header with *key* and *value* to the header set. If a header with + # *key* already exists in the set, *value* is appended to the existing header. + # + # ``` + # require "http/headers" + # + # headers = HTTP::Headers.new + # headers.add("Connection", "keep-alive") + # headers["Connection"] # => "keep-alive" + # headers.add("Connection", "Upgrade") + # headers["Connection"] # => "keep-alive,Upgrade" + # ``` def add(key, value : String) : self check_invalid_header_content value unsafe_add(key, value) From 2155979bd401b06887c036df353b0e0f4d362e55 Mon Sep 17 00:00:00 2001 From: kojix2 <2xijok@gmail.com> Date: Mon, 28 Aug 2023 20:53:54 +0900 Subject: [PATCH 0667/1551] Fix typo in call_error.cr (#13764) --- src/compiler/crystal/semantic/call_error.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index 5d3061850e2e..40c6e22424bf 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -633,7 +633,7 @@ class Crystal::Call msg << '\n' if similar_name == def_name # This check is for the case `a if a = 1` - msg << "If you declared '#{def_name}' in a suffix if, declare it in a regular if for this to work. If the variable was declared in a macro it's not visible outside it)" + msg << "If you declared '#{def_name}' in a suffix if, declare it in a regular if for this to work. If the variable was declared in a macro it's not visible outside it." else msg << "Did you mean '#{similar_name}'?" end From 63e2342ccb1f022430bc077d95befe97c993bab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 29 Aug 2023 10:28:46 +0200 Subject: [PATCH 0668/1551] Change spec runner to exit with failure for `focus: true` (#13653) --- src/spec/dsl.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spec/dsl.cr b/src/spec/dsl.cr index 5a5969dbac5d..99078cc66b3c 100644 --- a/src/spec/dsl.cr +++ b/src/spec/dsl.cr @@ -251,7 +251,7 @@ module Spec def self.finish_run elapsed_time = Time.monotonic - @@start_time.not_nil! root_context.finish(elapsed_time, @@aborted) - exit 1 if !root_context.succeeded || @@aborted + exit 1 if !root_context.succeeded || @@aborted || (focus? && ENV["SPEC_FOCUS_NO_FAIL"]? != "1") end # :nodoc: From 19e1c810f656fa7dfa0bb0e5a05dd8f580b3da9e Mon Sep 17 00:00:00 2001 From: Evan Paterakis Date: Thu, 31 Aug 2023 11:12:01 +0300 Subject: [PATCH 0669/1551] Update octicons to v19.5.0 (#13738) --- NOTICE.md | 2 +- .../tools/playground/public/application.css | 5 - .../tools/playground/public/application.js | 7 +- .../tools/playground/public/session.js | 13 +- .../vendor/octicons-19.5.0/octicons.css | 22 ++ .../vendor/octicons-19.5.0/octicons.svg | 31 +++ .../public/vendor/octicons-3.5.0/octicons.css | 226 ------------------ .../public/vendor/octicons-3.5.0/octicons.eot | Bin 30848 -> 0 bytes .../public/vendor/octicons-3.5.0/octicons.svg | 188 --------------- .../public/vendor/octicons-3.5.0/octicons.ttf | Bin 30680 -> 0 bytes .../vendor/octicons-3.5.0/octicons.woff | Bin 17052 -> 0 bytes .../tools/playground/views/_about.html | 10 +- .../tools/playground/views/_index.html | 8 +- .../tools/playground/views/_settings.html | 2 +- .../tools/playground/views/layout.html.ecr | 10 +- 15 files changed, 79 insertions(+), 445 deletions(-) create mode 100644 src/compiler/crystal/tools/playground/public/vendor/octicons-19.5.0/octicons.css create mode 100644 src/compiler/crystal/tools/playground/public/vendor/octicons-19.5.0/octicons.svg delete mode 100644 src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.css delete mode 100644 src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.eot delete mode 100644 src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.svg delete mode 100644 src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.ttf delete mode 100644 src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.woff diff --git a/NOTICE.md b/NOTICE.md index 28390dcb8644..6d13be6a19ce 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -41,7 +41,7 @@ Crystal playground includes the following libraries, which have their own licens * [jQuery][] - [MIT][] `Copyright JS Foundation and other contributors, https://js.foundation/` - * [Octicons][] - [MIT][] (for codes) or [OFL-1.1][] (for fonts) `(c) 2012-2016 GitHub, Inc.` + * [Octicons][] - [MIT][] `(c) 2023 GitHub Inc.` * [Materialize][] - [MIT][] `Copyright (c) 2014-2015 Materialize` * [CodeMirror][] - [MIT][] `Copyright (C) 2016 by Marijn Haverbeke and others` * [ansi\_up][] - [MIT][] `Copyright (c) 2011 Dru Nelson` diff --git a/src/compiler/crystal/tools/playground/public/application.css b/src/compiler/crystal/tools/playground/public/application.css index a8bc47b74023..962a30835b94 100644 --- a/src/compiler/crystal/tools/playground/public/application.css +++ b/src/compiler/crystal/tools/playground/public/application.css @@ -183,11 +183,6 @@ h4 { .code-btn .icon { margin-right: 3px; } .code-btn .icon :before { font-size:14px;} -.run-button { - padding-top: 5px; - padding-left: 2px; -} - .run-button-preloader { position: absolute; top: 11px; diff --git a/src/compiler/crystal/tools/playground/public/application.js b/src/compiler/crystal/tools/playground/public/application.js index 13e9facb5971..8799fdf41300 100644 --- a/src/compiler/crystal/tools/playground/public/application.js +++ b/src/compiler/crystal/tools/playground/public/application.js @@ -84,8 +84,7 @@ $(function(){ .attr("href", msg.html_url) .attr("target", "_blank") .append($("").text(msg.html_url)) - .append(" ") - .append($("").addClass("octicon octicon-link-external")) + .append(` `) )).openModal(); } }); @@ -132,7 +131,9 @@ function initDemoPlayground(dom) { output = $("").addClass("output").css("min-height", "1.5em") ))) ).append( - outputIndicator = $("
").addClass("col s1") + `
` + ).append( + outputIndicator = $("
") ).append( $("
").addClass("col s4").append(buttonsContainer = $("
").addClass("demoButtonsContainer")) )); diff --git a/src/compiler/crystal/tools/playground/public/session.js b/src/compiler/crystal/tools/playground/public/session.js index d605b2f66d34..3da8fa06433d 100644 --- a/src/compiler/crystal/tools/playground/public/session.js +++ b/src/compiler/crystal/tools/playground/public/session.js @@ -60,7 +60,7 @@ Playground.RunButtons = function(options) { return $("").addClass("run-button btn-floating btn-large waves-effect waves-light tooltipped") .attr("href", "#") .attr("data-position", "left").attr("data-delay", "50").attr("data-tooltip", tooltip) - .append($("").addClass("mega-octicon " + octicon)); + .append(``); } var buildProgress = function() { @@ -81,8 +81,8 @@ Playground.RunButtons = function(options) { var mac = /Mac/.test(navigator.platform); options.container - .prepend(this.stopButton = buildAnchor("Stops code", "octicon-primitive-square")) - .prepend(this.playButton = buildAnchor(mac ? "⌘ + Enter" : "Ctrl + Enter", "octicon-triangle-right")) + .prepend(this.stopButton = buildAnchor("Stops code", "square-fill")) + .prepend(this.playButton = buildAnchor(mac ? "⌘ + Enter" : "Ctrl + Enter", "triangle-right")) .prepend(this.progress = buildProgress()); this.stopButton.hide().tooltip(); @@ -130,8 +130,6 @@ Playground.OutputIndicator = function(dom) { this.dom = dom; this.blinkTimeout = null; - this.dom.addClass("octicon octicon-terminal"); - this.turnOnWithBlink = function () { this.dom.removeClass('teal-text red-text'); this.blinkTimeout = window.setTimeout(function(){ @@ -632,11 +630,6 @@ Playground.Session = function(options) { run(); }); - var iconCont = btn.getElementsByClassName('icon')[0]; - if (iconCont) { - iconCont.classList.add('octicon', 'octicon-checklist'); - } - function run() { if (isRunning) { return console.info('code formatter is already running. Attempt aborted...'); diff --git a/src/compiler/crystal/tools/playground/public/vendor/octicons-19.5.0/octicons.css b/src/compiler/crystal/tools/playground/public/vendor/octicons-19.5.0/octicons.css new file mode 100644 index 000000000000..3bc71a4b43e8 --- /dev/null +++ b/src/compiler/crystal/tools/playground/public/vendor/octicons-19.5.0/octicons.css @@ -0,0 +1,22 @@ +/* + +.octicon is optimized for 16px. +.mega-octicon is optimized for larger sizes. + +*/ +.octicon, .mega-octicon { + display: inline-block; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + width: 16px; + height: 16px; + vertical-align: text-bottom; +} + +.mega-octicon { + width: 50px; + height: 50px; + vertical-align: middle; +} diff --git a/src/compiler/crystal/tools/playground/public/vendor/octicons-19.5.0/octicons.svg b/src/compiler/crystal/tools/playground/public/vendor/octicons-19.5.0/octicons.svg new file mode 100644 index 000000000000..c072c9af1106 --- /dev/null +++ b/src/compiler/crystal/tools/playground/public/vendor/octicons-19.5.0/octicons.svg @@ -0,0 +1,31 @@ + + + Copyright (c) 2023 GitHub Inc. + + MIT License (https://raw.githubusercontent.com/primer/octicons/v19.5.0/LICENSE) + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.css b/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.css deleted file mode 100644 index c49658ebecdf..000000000000 --- a/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.css +++ /dev/null @@ -1,226 +0,0 @@ -@font-face { - font-family: 'octicons'; - src: url('octicons.eot?#iefix') format('embedded-opentype'), - url('octicons.woff') format('woff'), - url('octicons.ttf') format('truetype'), - url('octicons.svg#octicons') format('svg'); - font-weight: normal; - font-style: normal; -} - -/* - -.octicon is optimized for 16px. -.mega-octicon is optimized for 32px but can be used larger. - -*/ -.octicon, .mega-octicon { - font: normal normal normal 16px/1 octicons; - display: inline-block; - text-decoration: none; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.mega-octicon { font-size: 32px; } - -.octicon-alert:before { content: '\f02d'} /*  */ -.octicon-arrow-down:before { content: '\f03f'} /*  */ -.octicon-arrow-left:before { content: '\f040'} /*  */ -.octicon-arrow-right:before { content: '\f03e'} /*  */ -.octicon-arrow-small-down:before { content: '\f0a0'} /*  */ -.octicon-arrow-small-left:before { content: '\f0a1'} /*  */ -.octicon-arrow-small-right:before { content: '\f071'} /*  */ -.octicon-arrow-small-up:before { content: '\f09f'} /*  */ -.octicon-arrow-up:before { content: '\f03d'} /*  */ -.octicon-microscope:before, -.octicon-beaker:before { content: '\f0dd'} /*  */ -.octicon-bell:before { content: '\f0de'} /*  */ -.octicon-bold:before { content: '\f0e2'} /*  */ -.octicon-book:before { content: '\f007'} /*  */ -.octicon-bookmark:before { content: '\f07b'} /*  */ -.octicon-briefcase:before { content: '\f0d3'} /*  */ -.octicon-broadcast:before { content: '\f048'} /*  */ -.octicon-browser:before { content: '\f0c5'} /*  */ -.octicon-bug:before { content: '\f091'} /*  */ -.octicon-calendar:before { content: '\f068'} /*  */ -.octicon-check:before { content: '\f03a'} /*  */ -.octicon-checklist:before { content: '\f076'} /*  */ -.octicon-chevron-down:before { content: '\f0a3'} /*  */ -.octicon-chevron-left:before { content: '\f0a4'} /*  */ -.octicon-chevron-right:before { content: '\f078'} /*  */ -.octicon-chevron-up:before { content: '\f0a2'} /*  */ -.octicon-circle-slash:before { content: '\f084'} /*  */ -.octicon-circuit-board:before { content: '\f0d6'} /*  */ -.octicon-clippy:before { content: '\f035'} /*  */ -.octicon-clock:before { content: '\f046'} /*  */ -.octicon-cloud-download:before { content: '\f00b'} /*  */ -.octicon-cloud-upload:before { content: '\f00c'} /*  */ -.octicon-code:before { content: '\f05f'} /*  */ -.octicon-comment-add:before, -.octicon-comment:before { content: '\f02b'} /*  */ -.octicon-comment-discussion:before { content: '\f04f'} /*  */ -.octicon-credit-card:before { content: '\f045'} /*  */ -.octicon-dash:before { content: '\f0ca'} /*  */ -.octicon-dashboard:before { content: '\f07d'} /*  */ -.octicon-database:before { content: '\f096'} /*  */ -.octicon-clone:before, -.octicon-desktop-download:before { content: '\f0dc'} /*  */ -.octicon-device-camera:before { content: '\f056'} /*  */ -.octicon-device-camera-video:before { content: '\f057'} /*  */ -.octicon-device-desktop:before { content: '\f27c'} /*  */ -.octicon-device-mobile:before { content: '\f038'} /*  */ -.octicon-diff:before { content: '\f04d'} /*  */ -.octicon-diff-added:before { content: '\f06b'} /*  */ -.octicon-diff-ignored:before { content: '\f099'} /*  */ -.octicon-diff-modified:before { content: '\f06d'} /*  */ -.octicon-diff-removed:before { content: '\f06c'} /*  */ -.octicon-diff-renamed:before { content: '\f06e'} /*  */ -.octicon-ellipsis:before { content: '\f09a'} /*  */ -.octicon-eye-unwatch:before, -.octicon-eye-watch:before, -.octicon-eye:before { content: '\f04e'} /*  */ -.octicon-file-binary:before { content: '\f094'} /*  */ -.octicon-file-code:before { content: '\f010'} /*  */ -.octicon-file-directory:before { content: '\f016'} /*  */ -.octicon-file-media:before { content: '\f012'} /*  */ -.octicon-file-pdf:before { content: '\f014'} /*  */ -.octicon-file-submodule:before { content: '\f017'} /*  */ -.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */ -.octicon-file-symlink-file:before { content: '\f0b0'} /*  */ -.octicon-file-text:before { content: '\f011'} /*  */ -.octicon-file-zip:before { content: '\f013'} /*  */ -.octicon-flame:before { content: '\f0d2'} /*  */ -.octicon-fold:before { content: '\f0cc'} /*  */ -.octicon-gear:before { content: '\f02f'} /*  */ -.octicon-gift:before { content: '\f042'} /*  */ -.octicon-gist:before { content: '\f00e'} /*  */ -.octicon-gist-secret:before { content: '\f08c'} /*  */ -.octicon-git-branch-create:before, -.octicon-git-branch-delete:before, -.octicon-git-branch:before { content: '\f020'} /*  */ -.octicon-git-commit:before { content: '\f01f'} /*  */ -.octicon-git-compare:before { content: '\f0ac'} /*  */ -.octicon-git-merge:before { content: '\f023'} /*  */ -.octicon-git-pull-request-abandoned:before, -.octicon-git-pull-request:before { content: '\f009'} /*  */ -.octicon-globe:before { content: '\f0b6'} /*  */ -.octicon-graph:before { content: '\f043'} /*  */ -.octicon-heart:before { content: '\2665'} /* ♥ */ -.octicon-history:before { content: '\f07e'} /*  */ -.octicon-home:before { content: '\f08d'} /*  */ -.octicon-horizontal-rule:before { content: '\f070'} /*  */ -.octicon-hubot:before { content: '\f09d'} /*  */ -.octicon-inbox:before { content: '\f0cf'} /*  */ -.octicon-info:before { content: '\f059'} /*  */ -.octicon-issue-closed:before { content: '\f028'} /*  */ -.octicon-issue-opened:before { content: '\f026'} /*  */ -.octicon-issue-reopened:before { content: '\f027'} /*  */ -.octicon-italic:before { content: '\f0e4'} /*  */ -.octicon-jersey:before { content: '\f019'} /*  */ -.octicon-key:before { content: '\f049'} /*  */ -.octicon-keyboard:before { content: '\f00d'} /*  */ -.octicon-law:before { content: '\f0d8'} /*  */ -.octicon-light-bulb:before { content: '\f000'} /*  */ -.octicon-link:before { content: '\f05c'} /*  */ -.octicon-link-external:before { content: '\f07f'} /*  */ -.octicon-list-ordered:before { content: '\f062'} /*  */ -.octicon-list-unordered:before { content: '\f061'} /*  */ -.octicon-location:before { content: '\f060'} /*  */ -.octicon-gist-private:before, -.octicon-mirror-private:before, -.octicon-git-fork-private:before, -.octicon-lock:before { content: '\f06a'} /*  */ -.octicon-logo-gist:before { content: '\f0ad'} /*  */ -.octicon-logo-github:before { content: '\f092'} /*  */ -.octicon-mail:before { content: '\f03b'} /*  */ -.octicon-mail-read:before { content: '\f03c'} /*  */ -.octicon-mail-reply:before { content: '\f051'} /*  */ -.octicon-mark-github:before { content: '\f00a'} /*  */ -.octicon-markdown:before { content: '\f0c9'} /*  */ -.octicon-megaphone:before { content: '\f077'} /*  */ -.octicon-mention:before { content: '\f0be'} /*  */ -.octicon-milestone:before { content: '\f075'} /*  */ -.octicon-mirror-public:before, -.octicon-mirror:before { content: '\f024'} /*  */ -.octicon-mortar-board:before { content: '\f0d7'} /*  */ -.octicon-mute:before { content: '\f080'} /*  */ -.octicon-no-newline:before { content: '\f09c'} /*  */ -.octicon-octoface:before { content: '\f008'} /*  */ -.octicon-organization:before { content: '\f037'} /*  */ -.octicon-package:before { content: '\f0c4'} /*  */ -.octicon-paintcan:before { content: '\f0d1'} /*  */ -.octicon-pencil:before { content: '\f058'} /*  */ -.octicon-person-add:before, -.octicon-person-follow:before, -.octicon-person:before { content: '\f018'} /*  */ -.octicon-pin:before { content: '\f041'} /*  */ -.octicon-plug:before { content: '\f0d4'} /*  */ -.octicon-repo-create:before, -.octicon-gist-new:before, -.octicon-file-directory-create:before, -.octicon-file-add:before, -.octicon-plus:before { content: '\f05d'} /*  */ -.octicon-primitive-dot:before { content: '\f052'} /*  */ -.octicon-primitive-square:before { content: '\f053'} /*  */ -.octicon-pulse:before { content: '\f085'} /*  */ -.octicon-question:before { content: '\f02c'} /*  */ -.octicon-quote:before { content: '\f063'} /*  */ -.octicon-radio-tower:before { content: '\f030'} /*  */ -.octicon-repo-delete:before, -.octicon-repo:before { content: '\f001'} /*  */ -.octicon-repo-clone:before { content: '\f04c'} /*  */ -.octicon-repo-force-push:before { content: '\f04a'} /*  */ -.octicon-gist-fork:before, -.octicon-repo-forked:before { content: '\f002'} /*  */ -.octicon-repo-pull:before { content: '\f006'} /*  */ -.octicon-repo-push:before { content: '\f005'} /*  */ -.octicon-rocket:before { content: '\f033'} /*  */ -.octicon-rss:before { content: '\f034'} /*  */ -.octicon-ruby:before { content: '\f047'} /*  */ -.octicon-search-save:before, -.octicon-search:before { content: '\f02e'} /*  */ -.octicon-server:before { content: '\f097'} /*  */ -.octicon-settings:before { content: '\f07c'} /*  */ -.octicon-shield:before { content: '\f0e1'} /*  */ -.octicon-log-in:before, -.octicon-sign-in:before { content: '\f036'} /*  */ -.octicon-log-out:before, -.octicon-sign-out:before { content: '\f032'} /*  */ -.octicon-smiley:before { content: '\f0e7'} /*  */ -.octicon-squirrel:before { content: '\f0b2'} /*  */ -.octicon-star-add:before, -.octicon-star-delete:before, -.octicon-star:before { content: '\f02a'} /*  */ -.octicon-stop:before { content: '\f08f'} /*  */ -.octicon-repo-sync:before, -.octicon-sync:before { content: '\f087'} /*  */ -.octicon-tag-remove:before, -.octicon-tag-add:before, -.octicon-tag:before { content: '\f015'} /*  */ -.octicon-tasklist:before { content: '\f0e5'} /*  */ -.octicon-telescope:before { content: '\f088'} /*  */ -.octicon-terminal:before { content: '\f0c8'} /*  */ -.octicon-text-size:before { content: '\f0e3'} /*  */ -.octicon-three-bars:before { content: '\f05e'} /*  */ -.octicon-thumbsdown:before { content: '\f0db'} /*  */ -.octicon-thumbsup:before { content: '\f0da'} /*  */ -.octicon-tools:before { content: '\f031'} /*  */ -.octicon-trashcan:before { content: '\f0d0'} /*  */ -.octicon-triangle-down:before { content: '\f05b'} /*  */ -.octicon-triangle-left:before { content: '\f044'} /*  */ -.octicon-triangle-right:before { content: '\f05a'} /*  */ -.octicon-triangle-up:before { content: '\f0aa'} /*  */ -.octicon-unfold:before { content: '\f039'} /*  */ -.octicon-unmute:before { content: '\f0ba'} /*  */ -.octicon-unverified:before { content: '\f0e8'} /*  */ -.octicon-verified:before { content: '\f0e6'} /*  */ -.octicon-versions:before { content: '\f064'} /*  */ -.octicon-watch:before { content: '\f0e0'} /*  */ -.octicon-remove-close:before, -.octicon-x:before { content: '\f081'} /*  */ -.octicon-zap:before { content: '\26A1'} /* ⚡ */ diff --git a/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.eot b/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.eot deleted file mode 100644 index c1d2f2927173e6085a6dc6ef6b290f4a78288bc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30848 zcmdUYdwd&Lo$vf+q|w8YWm%SFTed7&mX!Ey%eL}rJb5|Kl904Xn!fDVvg5|D`jI9n zv{PuIwB-_L%d1cfZFzlw3oWo07RnS@Xm@!O78Y7qFtA%-3tdAg3%l|7et%~qTS?Ph z?%w;)m93dGXU?3*@BH59cSibYN&4YUl0=d$@kf^Iibx>m9C2#j0qc78D6(<+)VHqp zt>zX|WzwWHD9uWvI8I6v(hP2tN(ZH!G$PGOW72>$jhv8l7&+6pJ1R=WqzR zJ&$Z|)Gg_fBE|L}YU_N>j}IL~<{IQ49vmN-`pxNxE=jf+MsU~2*qPxEzV`JClH|ng z@drKOXow#)7rw3iY)rX}Y z0f7ABNps^5&G02Yq~z$FWS5-so8)ic`YqO(o|ihg_p9?ON*5gt4DT3{v{df*ud(`$ zcAwz-tRmbyMhscE&c$iISNL*%Aze6g-X*_WSN_WbSxIvquKD>wUU8c9R_ZQF7xF|O zkzaUr<+`Xo&MW>F>gO|KLGg7VUEC+m6ZtrE-}$?er(11vJuA=PzJNhKPoz1IuM6Lc zuaVDv6!qX-s9&^KbS<8jR{L|Q`b1yz&oEZ5w2$X5cphcAy~1||MqDmmR@GM1#r1MM zh3i7PcwR4VvrxA9%<00n&@Vo7KWzANzY6z@&(i9UYAl?k+tFdh-Wth9CvZM2X_B4k zCY4LQe^Fj6U94J+EjBE!S=_U@fAP@b(Bg^3iNzZiPcPoKc*o+Mi*H_h%i`M>?^}Gw z;=32$yZG?pM;AZ7_~_zi7eBxF_~PFz{_Wy77QeUn!^NL0{_6LySr*hM>XjDli)D*- zi;2b5V&mez#aAyLM!h#IPA$$Y-n@AG;u{z5LA`H9y>DN9*W!B?--miXw)n|>yfHaU-CU(+Ek!v zz~uk&ht&Va75@KP-GuZ3>9bTp$LYIrjeJ)Av9eG3TiXWPUA7)emJ|Ggws(pU0e$u~S6&tb3ZJ?uT_{b8wGI$Qb) z-z~mRmq}$6We3VWR%Voauk2T4=l#w8ulj%G|6_Sq`9%4Bfi;2tz{3@l6+;yttvDa7 z2(|_{1>YY0TJVL?*3f~_b)m;YKMY61o5HWFlq)}0`7c%Ls(w|ysd~El1Jyr?lt=m^ zMT0kIqMbU)NPPT=(|6KgAp4di=S1z5dbq ze@Zkb{zu}$#3vJ9PJA!%T;fj+YQt=@CV55jT=IN+dwPD2y5{hj`__D8t#fU~+Wxg4 zSo?>@vyG28$xU5NUudpqzO#9;6+Fm2D+QswW(!V3e|(372Vc%yxysJ)Pmr6bz}3D1}Rj zkB*8^wx>Ls0>^&V{Ha|r9tf1tiJeLyNzqz+YEANJrOod-c8`rBRjM@ivc37L(h8!ozypS2H-D;VI6V6?^L&ruFS)*~&(Rd4u$$CoB}?9_dbc`qu5o+B zy{kuYwfX&RE~RIeJF>m;+=%LFYq+Pxuk?`ZW6x4tqUGgfLm}|E2*#F5qo=APmBv6g z6$*#!4H#HDm6pHRaZPJqRfkNSmD@Y69+b%+$x@W+vXL^PPFdSzqeRU*r*mCRf^0YK zs5-W8^10_G*Bz_cQGlT)>wuvh?Zg8VS7d!zGL{UI4AKb+Qv8?6cVu5*#*TGtz1L$+vWlaTLe|<9_^+q~$-dL=6$s=B56vW9}XX5dd>l2AZLITTVjO$|#qT|)3&32{I-C66dy_LFpIzts|;AII}%I=~>t*+N5XjfLGjf;}z?pSlP z*4#XQ!3jcgSGHlj|DdiP^sjHglh*aLF@34aZxC0ZH80(oPsoLYH9m|r5BpLm^aAP* zz}&86{P+tnUidMtcsKtg{BZ?S62un+^#frnm;)VkUDuZK@Fr-t$db1Za;-d>A+9Y; zrbavi4G{r)%!7<6!6`ohd3+FjLxrB4mfEC@l$AE3XQBt;B-*CTrEnn}OmqVcf_apn zJnkqMjiQ-4>BbUzO9r=f5pq~~VPS}=mge<)Bs#3HI?q_$@YYR`pnb8OG*h8v6;G3 z$(5AyahE4bC#TzU!@2h9lN9y1(#$$|jfd=|d%RAkcTcJP(2Zq{C%63m!>an>-)}j&w5;yFFS)Iz$z`*- znrgNs@7ql~Wo=*!(Ut*CUP8WpWy0 zYo#oP9{umi#`rWFh(uDsHKcgP zkp>UT_6P|ra1p!!!%X#LGb|P{++YTnTZ*XN{f4nO zsVEKm4k@P(-gG4$YB{Sq-x$^Pk^617`wfFMBl<3v?fwyE=Iu8shwN7dO0QB6?HHET ztIID}x~>^$w;h_AnjL5ObN`Ih{!tZAm?F>~TxXmmTJz0!W^kaR@4TDo2u1ZqKA+W|yo&lZdBP9y(=a zlH$O&ya0DFn?pWJdX_r@a7TVl)Rhnu!rWqk?56gS(gBJHwa4Q)xY&cJq|r-*jFQn^$qMbYkx;ATOsprx?g!F|c_ zAuY$uSpIP|1Mvr8VVDqcvH5vpem>UU4>cn%q`6Q1OY@3k+&a*&&EwNM&<_f;_^5>8 zT6$ZpRL||QbhD(KkaYR6TJs$cQccgxFetAOQQ|A>+!}WUXTSv6G6v4mz%ZuyrFrX~ z(9&21W_n`1R~O?}B)?RJ)}fkQh+*7fgtB4QL?96_Zo~ko%aZ=`>NP^!x?#z5s|_!F z5!HZgy_Av~p%20Z`7$VHSRD-~p^_%>4`H%$hVESpzyR~R6RfGcd8 zhOF!Xt1mGd8sM_}VO3DmAQ1R@hD|}vNs?Dcx~$M%FTjch5^hHHn}zv#p}}$ z=-HlL2!7^~omuX7Jp>^PwRBG^kqKYa`z4JecR+Q!{NaALcXON~&Am03jc=Cs(wU`s z)m{;4?u&*Z&HZgn*TzcSU*->RvV$@SeMgP zSuMZEJT0rChFOo(FLiI?nZ-}HfN(K}3)#Q1Pl=>tUs1iz9XvYVieID!z` z6za@UPggpbx{Sw_=?uGMOb`9f@P^of+mFeWVYLG&Pt>(;!X8j20oOEHQBk`Zh$pPRS#%4|V(FOxbaVMR2PPWeQv-gIVL| zWH3CT>4BJ-@<;*$Txp(Zf|&4(LLa~&xev?}S!Z17PQJffR+B`ZG+BZk#jtK-4cDTE z3~Nqo9&~45ASmGMe*6VtI^dEODBRx6#wJ_+0_VU4xKbT070y6@w5-cx2M> zUwaJ{H`#y!cy+LSNjuVF(`fs3*KJocIpE5)nX8W&G1G{}NW+Pwr*9vtN~NmC%s+XS z_+hR@^W47FE=%pkxxG_%d1mjO2i9I6bpLI9~%%Xn2+PI2=+nVu|8iOmEWV?cNHtk}k=$I`SR4e}x99BWF$ryn3}1TmeT?)l(#~vF>!*RS#^SxGYkWUNsIaSt}6dhzc6g){Z&hEfBL|Ir)7;K zbAe*ZQok-#al^tnMMpQdeM_$roFmUg3QUDPi=bh4PmuAzw`{g=JzyOlU$Wbm9v8=3 zpa|&l$5(DW@U0_-TPX0?Q0erd_E@*6Wm|$!FYB-I1Pe*bbXPW^=555VpE7yUs&Hg_ z@)(fz%M!Px)BX4NXYRWSJyEaVZrt$b=Hc_tBk%sB7vzC;X{Mz2Z`n)eUA(R?jt0^a~D_Oh;k3F01h1b?8$Tr8zjOcn)Q0C+ZuSfLRxY%-g3QbW zvqH#0jKtyggY!ufZX*^H3p5Xz!E!*;4MmcLNRW`Vuz*Y!oJ&uziNGI=W_1ClsL?ajrP8>8l57YTmy4X^FHpwI=EgGHK!2s>ET0h4A} zPiav5PFS{wrDF(mkXU0(bpal!dQc72$WW)Un#4_(S-8zAWr4&fRj*b6juomyt$^0a zt}yDTPb-;hJe*NfM>yet9TpIUf)%TU(y4mNcszA=o~oEPf{!=$R&U(X($doGjaPZ% zu~^(g{mmPRHa3eReP<=VD(-D=X))W@B%iuIOH$AEPc^JzB}&)!c|2vlqAKZg-nch@ zxZYD=<*Du4v!m8S(;iRT%$b=sPgT9Q{)(%vz|s6#p%HFshqqeXIh63?skWK3Gv@sj zOTUEGTp6f)4$a74N2O)yU{f3gyfuA$`f5CqvixU(Yg@pz>xFkYREOElrc>dbR+(oz z4TTEMPDcUj(t$LE$w%Q{!nv-juD)U2Sg73Vt2r{Y?lrNqH~VUuT5qC%xc`s7R5}G; z)1mj8o$Jl#d#?(=IYz;CG+7pJNN$)3mb*919;@}~uZ`cjezv78GJ2m`hEMFuxcs3H zn2C+%FY4b~dCgX;c~!Z*KY_xpz48U(wz4+>&E3RiS9#n z^_vxq-Z0|ax2-b}_+!;&iMm7GiL2s<-h7W!qvim*Wn`l9?x`LJ`~}Q&&?#tk;Fcb& zBT1PAv=t>7lEN7Tp$MhE73?p&>(-ZOhpIWucbJQJen!!3&p=y~cWbKo*Dp~Sjs96v zK6B?o&v13Tw!^fpp>~H<3hD|;-JmWh3%@t}z{Bitjd!!dNGv*JSb>ZK0BpmsrUafO z{ISBbPkblQs??XtjzlT!1)c?s%_fWmJ`8qE%+JeE!dK0F!_uI52H@m?JH?PC+}0uu zazhWkoWcW;`lv$}^pan(hzLiEr90sX=qz|v7c<$@fM+3lc#N1ztY6^|7%mZvoc!7Z z_-A*)661vldoU~f{oHNtAXXI~DhnWCIpIxIWQYNsgBJ(`Lqopk}58QURvI11cDNax(#g5>+}{ zmh0=wl||EeeRLyT=T3xJ>ue(R^V!`*yR+i>6!K!Bgj-Rm37#hg1>l5#%oPaBvJ&~s z*W%_<4vdcdb(lb`^WZEibqx)5(PZ9UsMvCd=+G9ZyTY(Y-dY}J&ABmF{;5K%Xxn^| zGbvnNQ66jTLIVbB*F_5vjnkaPjdCBX+vXO-fTJjHdjbu4f>?fQV%^pUZ(I##rJ&K| z)ftF$%hF*NpbV|gs#BQzFvI^j(bm@2*7lDkxCDs!LU-o*R^pp|ZQlj0=GzkTG%l^d zY8gC^Ohet7blic3QBX{lRlqpBQSmJB5YuRfk1FlR1TanEY(id`M+?i+0?dAWK_K2d z=MZwBB`yed2EA0!hd5Gf9{mNyaM$LG(we~@EF91;!WV#$`z3gyTrxaxiev(>`{!vM z8YS*?O8BWzj`@xc{2>Sr8?4tVc1Go4#mv|4qM#T00ROcx^z)0gVii&8H5vY>TU+Sw zU(lE}zYM0JU()&wBWCpX<6uY^@}_#o{YI?R<=36V;7zN3L^#5DwIGufyeJqBc_|59 z6dg(0$xEyg!lVIaGCVAFtWbOtLU_l5OQw+lkjFI?#;!oFkt8Qdnm;Q7xeRlt0S zf?+#2bsf0~!5`V~0JD%ZrBqo5Bwh^pd`~e8f6AW(@n&#ozTi*FFJ?3Y5Ppi?Ay4^y zp%*C@!!?!r5WdRuW&qy_bERSJ?h5IsbdA)HwperTfj%ZYepWmNtMm>P%0(Exa0D}A zB^$;3!Z#-5X$AohLfC>zGR*$!2unoiR1Z8paGtR@+aJ&3tY}AF9$8KMo2wnwnc$jG zXiYFvEl1kQ)2i%o9c7Zhv{~#NMEsb)&eu?;1}nW@lFL1xd%<2+<7@E6$|9fll*{CO zf@jJ?8%|*g+5J~mSF$Bl?xl*dy4KdZvY@v-wWXx43>5>w7Mfu0A`R;kl_jO*^43J$ ziNI@Bx347Jur82rM{1UK^Xdc$3`fj|jpsw?Hg;$lURV?m<}9U>%%k&Pn{*yR2{c~# zA_T%X5Bf&0cnN|G^bl}|N6r9G0zF$(4=e`CF@`UPspzC()O?Ba13(@PD-3UtUw8cY z>tr=rkD#Bk4=MFg_21>YW{;gD`wNdf_JW;G9-F;Owv+E0-+gd?l z(IJ0km!g~-uc^gt@!9FX<@g=MH}`d5mUvzy%!^y-=K|8%M1-(J30Phb69f0Qa5|xR z__$@r1L$goaqj6Q3|r_fj0ND&8s8FGAbkyNQ)qmQZ9;6IpBIP2aIARVHYo#svo;Zf z-M&HED)mWw0EamDL@Ya5ty>GRfCmVT)2y7OL5lf4!=(a^8%0?oML-W!(=7T9%^;(I zn0W&8nvVI;nub1$Ia?AC(n0i^H6XpepS|t`5@u}Ibx{DA&zX1*^te?*+-+nqPZ0_o58wc9vLjRncy2RvIM*g z0TjUjdA1G9T+OSsX3ku|rsFKe=aE1od1N&}o_vgRXC@zQO;1VQB*C(tFU zqw+jQL%HN`gjWD6mbkd)_nqo5@0I7x1#`hr&c6Rgzb&)M%aY9VF0sJ`bQX{-f&*Cb zD)>;pXhS>$h$MjotWe6BOx$C6XU!L{)}|~dp$7~k{I6&Lt?aFiLlM8^1cDYMj8H(y zLLrVM04Xm$!jw$0>u;6iTdx<#Em#@E`k{5SO0luBihGfUTQ&4AUmOqpWTm)#3|;2& zEdMp;^$y|vv@>%r%&C}AJ9H;rOZ%qz7%INdTDTl!PI39{uRdY*v(W4(epTYHko5}4 zdx1B)GAus_=(yRo?}mds&D;TJgah<+)={X!kR^yH(4>6tA>$BMIEMoo=OHW(OIFAV z1cobsbQX+n6FQi41;!%&31%hFHcKrw7&o#jF`?iJlZvUut-|aA>U3{8%;<)mtFgqz zaZ+00{EBkroqEimQ5UFjG5yF*79@pDMU)a-5hd+RONyzvN97^ru^*EzKDm& z6Olh{oiDmx>bU3zNeP6!@pm<4>V-U>6!Zn!p~P`C5?o-8uuFmCP=V_xv(G!9x3E@38*QFr!gHCzXk=RWS$|8c~<2BZdfyhwK5$(Yp`Et!IkiF zAOF6~_L(t%>>6>Mfq83$El`zEI?%hYHu$L?vV~Upz-fVWLPYq|BAP z)s(f~t}8O8#2SAOV#cNsmlsm=n8U~gT;k#6$8W!2RAiZLyr75BuzDW}ngUSRDcTug zZ($IwjP9OvS1KVg5!Qj)gTUDeHIYg?LcFGdRZ0oNE*__uylAxa?c&>j($d|nrFFsf zp3iD!u98p$)_z;MW^Ewp3Pk+0xz$kH8mXhHC7JM4c&dr2>e5wI;g3|+)n2xC9Yy{V zsOc`3t?^CStoZ~fk>0vgOGPm0@|F3kwysUC`@LMAtWQ#Gt=k>;`r6A%Jst`-B`DQU z$!=Pv4dB!s`FU7!34z_go-piI3z@shvvw@aUYJ97&Cd9|z1PchWY^V$^zeIS*Torg zyZ`5{Hrl`URY!^LzWuXQa_rR0as=Pc)1?n~UR-#aiUk$RC`e;ogXC3M5EleGBtg*? zRK$Xaw9Z$vJcS~Lj}TywtfXZ9B4KBOOoiQteAYdBp`~e@mLd(jPYiaT23%u1s%a%| z{8G`1BhpE%?aaVuDj_zpLQvyn#VG)@NKp`Dg0QsX%R)0;^yR$bYfk?qd5WZ!m+GbE zhB#V7w>-`m(2@qjT7u@x!!oAhElpnS(R@anQor;i?8mt=pgK?mzlGRf#aQ+Ft$+wJ)+7c08FUK6V zTQ1#(&PwYmHSm{@1w65!Rb9FrfO0jC>eB7$MN6l}FjPg%qa@(vk}u)Sf$ndx@U>>N z2u{6AJTys%{fHGZy%>Z%=^}p9&Vv*_O1Aj|(6-`;#=^XCycpIKGHjq`@dc9cC60)x zi|0u|X6(?}faHZ4IH`2}!xIUtHt+?b!B^AK1oS{0bg)ifF(p}?1WD%*V))WflyBRB zHyZFpL$|G!DluxY=EVA5Tlr_h~Vv!%Kz&@<>Gv2^L=M~6=LkK`lSznt3H+z3S6rpcs%moWM z1#M(1G8K0Oj`57}*Dx4H;se*S^@#ac2AvdcN}SF9Od#-vicb17=pLXHM?-^?km(41 zLkYdMB3^lSP`yL7IZ`Ur-Ym93WV(TP!t27EKh;M5UJ~0}?7<`j?cK8OFT30{5A>a~ z*-rJ>+2l`RqS79;|dhBbJSt z#1e(qhUCz?8jBt3a2M+up2UY1gq|p1i5TkzsDX=%{XBYB4J8?S^)&f5WeY4Hj*EkS z5%eeR9J%BOhY*>q_QKnXXoE~0Z^$#HL@jBg)n0j1s~R|cy4>D+Q(5TVma>+6!-`g$ zIIn%}YYLPdbMeSsLHFUq?%-WVeDYm)t<1Hd7|{0$G12S|O+XgGir}amjv%rFg*f7! zudV2KYiEMkFav!D%gnIeyHe~pFQg4?3UP#Ug+B}i_~;`Iu+wp%IEjLx&knFhO~_V24)#atnTF zVVb7>@f8;U+XF>`UF<6c3=NG#wn)lHY*}7mE0#;d`M|471_x?kb;}CgvK-6#5H4Yq ziuM6j;D(5Cf{p~>73iK-BsjMa_!9QZ1~ZWYdoc`596X8rPs5>TjaJri*1%q@Y|=&D zUr>H7e>ETb2|kU08jOV*7xuq|>}dyrYq)xZ{!4PU>IJTYdDRP5S^B5#x7EiW^g@4itDP#eY3a~H1 z3Lnd4j2)~4y=x^0kOgiSbTxoAX)OE!Fk}=3P(9u|)n-Z!X}}#RS)>#xZ|ZYY->cFM zrqnjo`@pxB8q*E3mE<|+zCHsx@>%xLBfO}Du{RrmzqDIk`1qUqo_yzX8#X*Q{48l7 z{2G1U9M)&d4^dyg&dIT$<9OX!%tehFbzUmqdYt0*hW@SF}Zoym?9CPDBa33_*9v zY`T?$XwXktVou3_8h;HJum?Tb>mNQ zkxepm18cBMU1FhKgPLIMBmR>N)>N=!lP57DKPB+W`)Hr}Y00*NzYd|N2u+Xt1h=!| zh^PIm9I(y;GM2$+S)9S0gym=iPA(8mnvM?#4Ha&h7!__T^n;OG^lK=0gG|_3L2P0- zyz($>nSv2JjNol(06fcVb22n->!!^bbC=4veJeVxZMDZM8DQGxOjF`HCeBMZC*y1d_ZV-7aTRU z&=G2h`_Hw?mtpBiUi-TeqLS+<*}QJlrkvWdskhATjM&ucPu%hqyWh4axq^O1<%-&V#Z?;}3e|?6q>^#F`bMYo?sD06 zq=deEW3P?es-wRIyj;XRfs8;WF6dP?kmHSrD(u8Q4iV1_^Wb7@6zm74XJOAeZ%Ong zn4ALnGl&#eugel}ydZK5+s_Q0`6b7*0P_WP;H-gv#9qE)Br~t%^-T@b&&NE^;J%i@ zAJHvoP^p=odtj`^dZ0anj=`lt;MtCqypB*UM5D+CK>&ME2%H@R0xuSv$?T;hZ~c>0 z_GG=6tcYAyv0E8-MO}%}wc>Dvq5Lc_o#=2p>waHjcXy-DulMX;=4-cGRXImh-R?pj z+|PUqr(S@B^~*>DM!@o$$HG>cwZ322m(LulcpoTR+su;0!UYytFeAnO5wUpspWNrN z{{Ny5*(gkvP=$C=hUX>!|DWH~pg38o&!wJ)8fWP}*6B;kw($Wozf?Brx^#JVN|ZDC$s)=Dw_Uj{cbsY3?x zzaW6ZnjrhrmU-Mdsuyj*e&zMKFm#YKM#3TpQZ@@?6bzY_W`V7CYf(Ow1Q+LhQa0{8 z2nQmuXfe+mBzN0J_#23Br8@JEA}z7t1*M@gMN54@Aa@Ef#y0bhoV4sx13I07 zG4o!`4&H~!uKXgTN(Uh>@ekBPoOL$Cp{87<2>N0_7gHEFjBW~z0Ote!2k_s{hXd`b z4Z%wQT@-hOcgVi<xCCd;U}Af>k%gKK1<4c8b08 zZ*8`}eJ5bbybKxw<_-RcfFf4!dF;qxr^JQ#iTxiDf`S70z=8vV;pZ=?gSx#0?q^xj zKvx0Dd>QyxGzpp-7Wy1%T3IlWEGyZn2W4VhCW)Q0(Bw7CcGgMbLWQvT3 z`4&e9_4A61X4NBb5yMKlNP(^c<&JGn#-Y4#g=07PoeNSj?;U0x2Qvd{%#(xYgtSuF z7e;^4OBu8aUa?SXy>`LK?`2we<+m_sSZ$=YU9cZ)!g}F??Bys>er3uB{_B@8NcLs8 zvQ)pK!Mm^q*mzW$0>C&37$f924Zysy28pE`UW|bC5B&yjHR{GL!vOXb@E(C4SU{_V zVOMiu#7n{IfKde0hHi&Nk~B73bvuGqBwH4hb*l-~0!Kp2duxt0pBaXxaAwoB>!ZW3 zdgKmSx#NTTr;|UUcWujN!xd#U&5@d>8VdEb%5vwfj_oz^y$vU>l$BjK>}rzb^_O?= z2~}U2nu6A`=Y-twz>c9I`Pwb+Dp#PQt>XcDtGVoTDWAUQp3f-Z%I#%UkUoz*@{BE9 zPGjbG>0Rc;#x7bPsFvG8HE5;A{D|5Rheyck-c(wmT(z!ir(11FB)h59waM+1ui3PD zr~AR|e8n4U>P%y|Ml!9J*S63)u(N`!;l1oHi=7oa*^|I_IH3@*C&$cK5jzKS7qN&; z2-m-{TE1uc0{$uYi*4*N(3*U0?!6Czj6{1DKViuh$J||V*DgX#@=bQz6%CAGWdp6? z3)FD_pP6h(x0>Y$RG}fSNE<2Aum#>Ft6|-;TW%G0%Ldl@A@(8V!Ac#B5jY3)ZZ>2> zXBJyU7$+Dt;zDsnh@z#zlJ)M93RRJqca{fTk8Q72N^PYfmt(|LI?%C3_N;kzTYG!s zrem+03}~$th~9hK$7HzH+O{{f+=IBMr3_XIYRbz&zc=hZ^8Ppf!xL0lUQ*(-QBpo~ z#vgFnTfOyds%@PO|Jy$8!jVQ#HD1xM=TL3B_O9`B=2!UfnsSRj>%#y30qIlH7p1?G zo-K?H8_9v?K;%|A+YQR-=>qZBmr|>uw#m5Ds$e%d<9n)y?}l(ri)qgGL}5PmfFOF* zV5k(rC4@VCurWzfCc)ow?L z(^=mh@*v*R3vJp*vfE*^B`fVpnZs$byP6fby)snapn6Juo{F_z#Zwcit|U8E%B4=X zHxzF0x$QyOsk#-avdLfqN~yyUYw;&a%bVOzH`!_(Efw|7*ye@+IX&yWYHzSC6mJX# ze9m`&S+Or&X$!U0+GKaA(V@C4obQxn6~=?3R&hl4xT|Duu-aK#R}!(=4!fG2k(j-t z+?}ecR(u6_ic4(?g!Tl=!YM~ddCF$txx8*tyydP+mpdHv%W9*|=W_V=XM*Jw z?y@G2GbESP`F$mL@10GyRmd*YuKGRYW%Yiq0~fO0AE+p`sUC7T9eCYZ%H;@@l+?-y zm$b!WHE~iSvZu$TRM{OdXK7fqkuNmvb0=!*yq-E|^0yB^zGMz4_G%kB}tvH{o}nh&{){A z#>)Sy7nAV?j%+&!5hb9&bftXosT9((u_n!`19u{9c2Kc0K_VQNma z;0+~@{zy^GpS}3qXGrVH?$K@G=o1N{SuDQ~bMRBKo3U1`9wNpcLf;@1 z;D9oTIB$D0Yj=>{pXEJIXbg~|M{8$TvA4``F zNCWF#CIR@`AjN|TjpP!umzI?_l5jrd7)tzydt82 zw-51)gIMnk^EOoMgM*hXBf`qUz-vVOCg5^Q2!~f10w48oUAiH3@_7vwho&3aJTwDf z2ll8S2TuBY4!nKvssl!bh60){=4+k{7t-9{0{x-0z*oGcIY04A0aPqU5VQgvhqoLA z;sN-h6n&R1QMGigDq-7IFd4pE@S=6$t7!s%b@&D}(DoK-pL7*q zRn$53E8n*O0xjzC?iJQ=YQa=j)BG-kv~YF`KL=P_8W|km18EMX=jafu4MN#+fWQ{} z%$?YJhz{DGskIaj>nPT-kllR8gLgDzqrkyiYHM$Km=6c<{Itz_N)dY&AL8MehUM-c zF}#PKS-L&O5fkDt&naD8vKNF)%!F%d!ZVSEFBPJm47f_r57gVUn{kHl2JkBo@6;@; zWL7qyjFhu7p@yK(AS4Nj2egSS>n18EpwiQ}R8?!oZ9T8L?8UM0RP}+r?XRME`k`~= zo$c7Q&LO`FZz-+aR`DUFdE~a{_Wqp{=HG1JY@Qq%si^2%pW0Mj|NU|OwKu3&@9*vo z279C0FHQ=YvUm#f6OQF$=n9;=`HfZn#@D5bc*7t!}H3Kkb{;3On(+Ntg#=dPSz^KCr2IgT| zS9rxpJ`ra}^k&NgEo>`v2onKS6LALU7ZVhV(!90^JWM#)l?A=PVC>?>Q?XoHSVhJ8 zHY^&Kzdd-MBiZHhzG`dwGXL9y`#T%DTqUVAgAx!T?e60HdyV}1JG&jYR`#^vWIkY> zu!mZq#M*d#&g;dM4Zx0Ed|SWo22c&+%JJr6ik= zY$`g`bO16|t*B!MPDPlV@_4mV7bp$n$C4P^+3f{mf?j$_IS1e2OC=t>IMxi8l}(nG z!`q8@AK+MCO6wge{Tjl!-0Jcl_RY8BGrvz!=v}h>PDiaHaig=gp|rI8k?QJ4%1cWd zYMs{}C*nI@1#98L`P(M-NgyOJRBHzZBMNIg0X`&nYB3->e@HA0@imkmCdmbPMyiS`6c zkhIvJCOC!IwkF1ikP0^O0{kWsg@ufW4FTr>l45jlmT=r?SOIas-oL7*J5cZNmNd3* zY1F%S%ku8-iEDi2bpM94o?eps)~Scm=E;-VRc-4_ee&9>rh^K_;(9brWc2R+7f*F~ zP3Mm5Tf5ru;If1YvMyS;g&t`Ph!F@!B|6aK(5ujAu)J zJdlr$I09D+M8tdP*mlK%CoA3A!KWUMZw<1MW-&iN0IO%6u;K!$vb@=Qc7r(<@Le-u zjYDtT(%M+!b<_vCYp#NCgKk^51GcMPACr}XO;u~<($e*9S7|5d{`4W0G==ukYqzW; zSJoQJ9%}1qy>3TGW4PM$FME4w`+9XpZG7vF$DHu}?7anpf(fWUFYCsGjU8CEh_{SA<1i)1VKhu;Cq94Azwj5{u!>%CO2C+bX1?Daq2SaCA{cwgH5k%*?Y_>?B}20L_Z-nYzS$gn?|Z5xI$3oSGx zqOXAWU}L}<)(3C_2teOs+&pmvz!TEy$O*Ep1nC|*(OVGsP!^!4W3I+No|(1gXEyWk zu?w#(K`xE}gN5HhFLjCcDcb#35V>Cn?c!~Ayz5<%%F|yr+AQ z9F4eK@@_@b&0GJ;{I$(S;eVprmW;5vMDR3H+w4n&Y1G ztm(1B;^`0KY~Gl^oVCv9Q^$r$J%;xsr}8Bt`6pJlCalJ}tA~cki^AsT3p(Hmt_a?d zl`aD&r-f0_MmULBKGt{;(#KJKpdOCX5T{Tlq9WMoBu;pT4B}`gtUSNNuXt>2_2ad{ z!;!6#!@-*I`Zk-#=HC$vUUVxIS-G_|ztFG4VNb6)T$Fq91Fu+@`BU-upk!zMBgRq! z`frvtiT6gXL~^Bgt);$I?%FKFuU4U2JL8!;F{Kd0NfC0idX1SD4n1ZU)*Jlo6o2+v ziep#jPmgVS-=hzn*|zP>gO9%N^tNqF=cp;3iUAv%VySo&z*2mM^kDfD5ruk z>s9{zg8=#J&VOgqN{RG||v{~j2=)ur`bJoFiN;PjF;MGdY zhg7f2<}3}keJ+J)j7afRlzHGGtAY=|R8RrKFQlV!Gu&{%otpUX;1 z>`q^)tE}8@SDb#;SzcS^ag|n-yQ?ZykJIK<{gs~TdY?bm9t#%aD|ijBA4{uV2f(68 zpt66Tx1PU(6pirCa4@z@3D2b`WF|(e8vH~ek&+0KJf9{BQa_)TCA=9epH`6Holo14 zzAT@{&Li59Ppim3kWag%COVo=mq-D6JurxDi1L;9(cANBlFF2y=hL!Os{B5mR-`hR zD^__M(q;KHe&xm%%coW3_vF)V>9DOYpDvM7w)eQ#46coJws&;4;LsD>IXb&*?zr21 zFHa9amF%p|Sk;`Yt#wJH5XPRTjbFrDZ>0E4fGB!LpHa2F_YU6JG6UWY=7UrSVwDz z^%NeO8Jr%Snw@E#869h#oE~Yz{k85bQ&VH3s2c4LjE$iWsAYI`EH~qJZ<`#-t=4$? z?kiSm8ay#MIg=Y0vs$ydVWnr<#z$w{URLcS20opycXDucbZ~NF=3s7QZfsz>a9KDX z&P~sZPEMfb?S-tJxryBLz-(?PcKl3i=F~{%?CfxCczSX?#)HlQYO$&5$s2Nmv#ksu z43&q$`P?-`7O#;8VQ0q>i_(rYz)tbbe@^uvZzp~-Z5CW~4lz;u+h0ed6H*R8v=&1S z|D4){G=lW3$i1+13_rg%iMza4r5R<8WQJY zIkY*1GGn6c9O{dq|0ht}EY>b(u%6zAzY)>v6Qb9x7~P~aj@;G$tQBMFL;eJwi^225 zKOo2Bn!#}cat^_k+lTXhSap2nAB^k3d9^L8r5QXoji;wj&kV{iOvZ4YMD7UMw4Ueo zwxG7=@ekziQHMhlVLgyD2yXjm}$lv#)24Tg74jc=lSOYTURj1n$^N=6v`K% zX7OnL6}(G-J`9E}lR}6`BSfi@6Yq|3Qwe$SrXALz$`E&1P64XGyCg&K)l^ayHVsAa za@Sg+SH%#OSx*T*#vwrvdzm)F2H}j)rN3PS7abKqn!O$7zBlX^L*7X_}!~nxj*66P>0rbe3+W*U)R} z7DUA8bQ`^nUQf5v9rOlzBi%`FqPysBx`*CO|AXE_=jg5UHo6y)srS>{;n#R4y^G#W z572w)z4RcxkLKz9^Z|N^9;Qd=gY+T#upPgiI6bSJ9hh>DjgFj{Z8<(ScHB0do0=@) zL(A~w^vT?iOQfgfW=<4RV`H}ClanW%;E|KV1B1CTh=i7@xv{a9>D-NTxtZCL@qy`+ zEta4t9UPmS8)_MvylG-=a$v|~UCvGMh4W5-f|J~}-;IqeypnVHMAOiqFI50zRM(>d!he-DG0 z$qm_NW(TGnTw!iv)+t5~>0uuj%T3R!kWtfvCu}1)l}rx|jZU`APTrK8w$DyZj?Fk{ zMn@)ECg)~V$e@$CS!H@=Mjad*otir15VuArJP?xu6QgGbW>KeiD0d1%u4R1kIAEmC zO+a1`*#}SL22a|?2S&$Sd;s(T(g6(Nrk1&>5-Tyy6YI9pJO}yYSZ;V$nHrq{;D=}J zBhv#@C%m)MqXQEom;%08GB^#O16-ILJBEvfrss~IaUGw=ybTV_%qo~WUja?fHAWD( zxJK!ToNZ`ycv#7u$(85f(=s$VGdMTH^3|R1?bO&A@6_}trepL}4j3|9wt6*l zt}yOF{7&ceKp+nV>#F4xbdyQU@WkNgm~9jwDP0+X81?Fac;GguF(++PV{b<%YcC0;o7TJi2;|e{^)ncVcpS^ej|`0n8gP)o)GF%s4cm z7SVmzIIBXlfTc@JH220eo*ThbAE_T$nf~+#K80!=g`3H!0~~Z9B5z~0JA^>gr5XZ zoluQNr)EZH+!K>66ScZ`0z#Of}9oJ;sj2DLFC5lBV&`tnF<+(4kmsek7H_J z@Z`Wq&H)O!2?KWm&f|c`n3Ly4U?5M4O;GOW#PP|~&e>^X3=T{o1DmSDTH(6t z@!Y`4+_ddDpl`ovV0Q3?I&)$)H#P)DJ~o8OK8-0JJ)2WUfsdnu&e?&PlZM diff --git a/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.svg b/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.svg deleted file mode 100644 index 090870663f31..000000000000 --- a/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.svg +++ /dev/null @@ -1,188 +0,0 @@ - - - - -(c) 2012-2016 GitHub - -When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos) - -Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL) -Applies to all font files - -Code License: MIT (http://choosealicense.com/licenses/mit/) -Applies to all other files - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.ttf b/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.ttf deleted file mode 100644 index 3dab5b6698d2675745b2101db73a07f08c6bcdb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30680 zcmdUYdwd&Lo$vf+q|w8YWm%SFTed7&mX!Ey%eL}rJb5|Kl904Xn!fDVvg5|D`jI9n zv{PuIwB-_L%d1cfZFzlw3oWo07RnS@Xm@!O78Y7qFtA%-3tdAg3%l|7et%~qTS?Ph z?%w;)RXj6i&YU@q-}$}I?@T0;B*`u5k|M?SA8PA-&5sWqlO$S$+{1(815>{_9nmGp z7Lz3Tu92}b!ykO@>lY-+iO0uJpU4di`TnD_7dKDi=sAIm(x*zwk^UHth7;qnr#B#h z`gFWMKQ=iy(9qyskR%n)>EQUl=_z_r`X$m_VQgYxJom1VuXan)4W}i^K0h@%Gn;6C z7G*tR*(Im^Cixr4dy93Y=cP{W zy)4<4e94P02ZnbHNm?rR``1`~N4rm;NWSOXJAP5tt!r_bpA`$Bp z$V!^?aL?}-@`}@(w^Daex{xROi2TC4EB8h9abEFJsGqNl1;zJ;ba9_JPvqmuedl8( zPq*6UdRE@Sa{+^Vo=9^Z-xrRH?~%`a6!qX*s9&^KbT8hQR{L|Q`b1yz?=V)bw2${L zcpqiBy~42qBQBROt7@z1;(EEB!hIoKJg*nGStwh4<#gdF^oy_D4;v2dSK)c_RazZV zjfJyxJ37qRTO--%1g?iAO|moHq;jeEFUpIhi&cxU#fHT-i+dLLFCJPPT0F5hv3TR+ z>BZX??^wKZ@y&~GS$x~#eT(l{eD~se7av~y=;Fs0A6@+H;^!A1U;LZJzg_&s;`bJR zxcHOBU;X|y%Yynuz0#t6v23w!F|n9hY+T&8`0B;OsP~4&sl~a)n-_0ieBbg`hM*Df$txD-}8OP_buPw z`M&1+s_!elCwzwQOTNcTn+h}ynEXHfk^29*!v9~Zn~**reU>WdIDJ>Hk zYujME%l2dYS^H=0i)yDjq#BM5jPuzn6qc`br)w z`G&{iIqa3ahrQ>#KP;6?XG=ffyT$kEGO4Vh>_FMa%8at_mHn#hyuaE1RsXO2e=P4R zpD4dCuqMzSc(|gnVyNPy73YH$!PelW;M;>=3%(HA8afcVF7$Zlhv8^=Q}}h2a^=S= z|D|eO)vu~IRZmxcp!z3~@(fR1_ z>$>WO>)u}Xr+8yrk3Uzh*FReSPl@Kl|42NT_+;YCiSH$zOZ=%pZJ15gB(F%GOP)_} zPtUJW*BoAR-Mxv8t^3(XbHcQ!A!9Bp||%a2>jTKiky z-TK`&xvjqKRNIf+ujp`gYyuj9l9!LzBt?_9;YO7lq4IF3r#r2tvaRGu^@O7oj8b+p z;WjPK*{)Eyr!%~nf+5urrEn?n*-;V7_LOH+;MmWaKea2y1A#I+u~P{oDOzh!tw|oO zwD~>9?y*s%N_7Ud@7{d-=H1%^PPHmRwl`l@T0vA6c)(EX=1&z3r)NKAp6^loCD)hr zIhtY=c9YtyWXW4q?^Z|7HExf%cl9W)How2krS$A_N47Vf8&N%N4fmAzl^(Kv>{*IS zw7k4*CX50ka(l8?6cVu5*#*TGtz1L$jr5{hOD8Wt{M2JFG(n%qZ|q;oTi4QoUWliO9uJ| zfwimy{*u%p^+>(aE(@|1p>WWV23)(X8Rz>jfCzjB_GY_M>F%uc)ZR*6J)NNnHSn^8 zEM<35qE^>y6SONU(#Azeb9bz{S!-^dzu*EPxhvbS-hWWn5Bk?P;7#j(+L*r7?Kg<4 z(3+QS%_rnS!WtjOnumR<)M`Q90hrsBj3563j2C{4E8flj68^Y?DGB1hK>a`%3+6ya zUDvgxJiH0oEwbb-gj_36W{7LclBp5TKtn`;9`hh$N^r?fKpr0i-%z0^r=>P2BW0zH z=$Yt2IEl6?b17U12NT^ugJ2#dD33b|Mx$t^PP(y#-jcy>U4$GKURW4nYWW{&=)7TY zr+Ew)qDe&8MZdW|E}{WlBp2|-Pz>3S3b-7Q!oZzQj6e!-&j3U|NTm5b7!QcCIMVR~ zR)-m}?Pc{x{BMm0S%z5Sy=6sBF*+Tci zR2fSKN~0j?Ld&N%#+WW*OAC^~`OgFX3iPm6@RF^7Ja}1;3ieRXr!W{Ca6M$ph8lqD zq^8ncJ=vZRP@j1^cu6UNA~sW3D!GzUKJM~F>Ev{KZaCLIeUhRcSGw6omB07}Roj}) z=7)Z0tB-X(GBor^SFGOlL-PyqrlvR!;!`}<+$1-a-FV1ey2tBudiRvt58YVScyi0{ zKdh=B{{5DdOUvr+`;yygnp`%UtEpyN^1j`)Q`QEy5N#RIl5)0jJq4 z?UW8kQnKqZ0{9c%j&OGd*p4Y#A!ZEOqG2upYQWfvA2TOXN~u1@g_J^BFyraH8jzh93Rz%H->IsrIP9%omNWb^N!5a%pFkx4pz! zNtE*b9|~oRPsaE(8;C?w!8N3K#*qdO%k~HfEpQRM0K-i6WHT%lG2CDVm|Kde4`w=h zpo+*EgIqUkH-F^xBg*vy{r!fqH>oHM`wl6m58iYo9cnqNI^P)8^^yB+w)+i(G$Z;h zm+k%$W#;WSDu?V>21>6|5A7J1)vL=dSGuklXty1jnwlMF_;de^)&5Z;8&3EW^ncPn zGDQDK`wKe=Hf)%Nto_A(6n+ZP$6_89k?N!bxG{6$UTAbXrM=RA>5z0px>~wk8U$)V zTH66cX3rLj?oK2DZRWy!WN|Pjwxl?)Eib?w%;u2KlAh&G0Njz^6LsZMml5xuKAh_&g6Sp zLdrnG)B!GGM>^X^j&L@e zrTmzge>lMIvpn{N`T6##sdM6Nev(_scb$c4udFX&nBrl(Ris@utf4Jw))m+;^c2yq zS1Ok(r6}565!@`O6|@w#JGd_yKBeWD8Oy(pW+46`EDRGOE;c`J%+JUA`=Ms!g*5l6 ze`#Kkj9Ul#wR!w{2l_!_79W)`TuX1OmFl@&mTs1m6Ot}JR%^ZkLaOO`83yGQB1#;x z&aH7*a0N_|Eo0z34Gd$NUz)d`2`!CPV5TS5dv!5xMe<8kXdSA_g&4*yMkpI*O#~A0 z;zkURx-980uU;dxts9n1x7zT+7f}t^)=Md=5&9rpkS~LBhSkw<5-Mo|e-I`sSLoig z01Pm{JHeXD%Vz)s#S~chq%%gbYRJkSu=*0Sp#d(dKUM`b4FZARXV?_voFsXLq{|BJ z^#ZI&@|}?8Y#6{SVUue!QM^74fu8N@h2Uo%*_q{T*FzA(P)qlu5}EKtyRYeroJc3ZM6|fj&(U*mDTcl%+sn3^smpj=na;3F#`Mtt3~z`%xc!)18K&DS zIx8p@2rMO|w4v$2#wfkX{Ej&fPV?4qrF?ArgRu=^^EH70rBJXX+W27828x=e&F{!> z(b2pl_`9Nu_QQ-F-93p5Al^zz(1y@Px0pr|MpJ{CG!4>Z%xDpk$`TW&pl?Ie>y$hK z`%t$(&XgT@SOmA)P^Q3TGMF`PP6ophnjVOWDUT#Dz?J5iCWr~oDD(jglKa3sk#)wE z?&SN+Wi?6kNs}e$Q4H%Q)^IIq$gt+b=0SG`27&_4?#I6%Ob6Vu0)@w$+1O;OU*H^= z09UG`rNR}+kCt_L?BKyMS)3oYlpS&AzCwl1cL-pE%KHRH2_om=6<)!Mt(K}K&XV9+ z8f;ZfaI6Gqc(b$>nz_Rt&Sd>*CE?E`Q`tm?TA%7pgk;O`VJN54fjGdL&VX9@5`vl( zP8Pkk5R2)tSPXrBYtZ(kj{deM>}#)q;wBqV0Iv?VFKI_wY#MF9?z-)YCI?)ZHgokA zBW4=07-_hW^z`jxRjE|fnE5Bq62HuqXr9}b+GVNTIJbAoF3;?}^T68cV_baYErEL= z08BJEVJ+suf;FV705|j<*rF>Cm;Wk!$JJNg5q|2a{MFPp?U&{Kn>HOFIzajrH^}l0 zR}?PaEIin>X}_3vUD(9k1?_oNF^2XJPY58jY8kKU$|-KXC)1O~C9#=6V+`o7o)y~| z^;nuVq(R;!miKk;?q7l`-Iq;lyZ~c4(78{h z8fq_AlZ%w*u7oXQE3xfMk7UZ%|1PgFpZ{e(LCZxt^s21wgf6sQx*V{EUBm+x0KU}7 z*&`rOraMYZ8L*pQW-$~EzF~cN=44}XOCVHTfWs;Xwj696ZY|H-{bnfNv%J+n3wa3W zi6~JlR(p7Dc_z97g`yDyCcoUawgTR`!Nv1yeJ%(sEj$h4NNNH6~6FG^KBIXyuWJc?N1*#@U*OvWG+x_S?bq?DsEUfr|9Sgw{PiHf^+1#NP(%aXAv~a z?g=s;_?FG~tp}|0<4bn?(&OTM3lsre{`ktH2flTr@CXI|8Y-P$)E?_LwQNfe>Sg^k zo?sz~neNIa)Vz%t_ERQLS{05=PaXr(ep%wSbh`ik{>*(>p(pAU+>IL^-8_8$dF0)H z^nyIFF3ptm{w;e6y^Gh?#nE9pdigN(w3p{%2}9r_=7lAn19CnMOBSOKps!g+n9>fW zOyC<}#a`AGFhShIj^H2kIq-O6S+d}C)qR=CGK~e4i)jnyAq#G_ zcgx1hkI)L>22R~4;YYv|L(CIAA(Sj3dss=@6-76{{>C?&Uw>{R1u3|ZjI|Utf4%lQ z=6CMDpW5&q%gufv-^zs+SCE;RU{(k@h>eaX_zNeb^MaPkFnk1JXqb(wpd{@QY7ti0B5?%}D8$;&>!-?v1* z`l#O@t#1SUp>BqCufUl~_&+2&@FS0~7&1&?NH&~%x{@oXQ%cHj|4^T_J@e2*&)9I@ zd}G^7uFA?vR{&%CsG@vS7w1DSwcWVcDuHvd{ts@K%U#j=`NDIQ;<0i2y1)_E8YJp& zqiovIMhPGAHSD8c9UDt^l<5W1rnALTOVM9byKdKPU-R&qou_tfsJ*%Pc4O51>mtEV zzTvff7ZkeScCbjZ5Mc+)I$+Wa>nROt-wDh1uyjnCME{^Mrn&%+R6VE$YGkNWSxw?0 z%Pc%*m9ju$l&V)N0LKc|p;kcaWLFq<)Tfn9HXhC>sw13mzzz$DLcxmFLg`dJWjvm` zI!{&18^O;Td#g9@X=!O`_QtC`@mMVGq5kHLL>rsMnZC1KV0vrukzIP?b%W5p=pn&ZRX5Oo2RPYTYtq>SKw@Z zt50 zz_l&l+V#S_9IC@?XVa;0PpizcorXdMXQ!ipb?HEw!sMfHFX38OR#)G!ZY)&p_0=4i zTKAgR*_(YeO|3W4KivOEUn-q~uj$Zx&Cd1a^SxJv-yEahI+`qtHzYUA1k2qUW{=hS z^w-93T|e8>78$+IEWB}bp*5BFv$dx*`!p*_Px%Lt=s=l6?J4M zyX!ZEAW-!{GilCynswvymPGfVy86wEMsFB#?%UQG2>h|?vP9jX?!;AbLvOyvsZnzP z-7+%Kc=uF~1O5W$Ip`ELJ8(-6){&%40@{ia3`yY(f>4A~-wO7Z-F53rv_sV#<~z*A zJ3pgnwr8NN$-6bx{Ogyfj7I;gDWAFXp=Y={UfW?>*HF7dDg||gq;61`l!f1$ec)ks zxW>ELVI&qEGOR$x0RXmPSW^N|68>1>*(ZLIXjSS#%2@70v`suCg$g5 zDB-JSzF}!lJOglYz@1{q5*}-j2DzaJ2dD4?q(17<1-;}~EF!|uV(CtJ13C-d)x}Kq zG~ivx9v&m666;s^2Mm`8MoxZh0{pYPV2SaehPW9 zP{OUK)CA8Hg932EALa^#Wm$=Q<$LjPDF;T!{yIz`)_HK1mAZz8x@a^P|JV7kKHL-5%gEy`Qvr^D#^6Cu4xn=3F3s8pEXVodpeVF0@oM>z7Yis*Q6I=pB ze4#t@d@J$8zP9gzR`YEMc^a42V6_Y$N2a0fOgiqs!YC*v%PL?T-l%vMc!+7V!$*~N zWCEC`a5f6X#r-xz90~9o^uE}&=MB}JA+;-=tG<-Hjn;-Vz_JbMQP1o4;D`7 z7vTUP;8F~henC#oDzO2lw-c*1Ahp@!v^cMik(q;STXapyC~>| zKEVH482b6eTCs|#^qLHR)U7S__b+J7nqLM}&@XBIh7mLR`*AX)3wcvLhkN( zVeqC^KO!7qyjqaS3SJZphrE=8E{cvM?c^oa31QLzGZ`Khx>o0qy!^4pE(f4;)k|Ag zg$E3=F1H|~EIjfC+c>fWjJ21RdTrvM`Bk_IKCJ?- zSP2EkVJ2vRf5?CXkDhk({|s&u2YCK7N)<3)qF~q#PF+VXLhwhnJHRX?O(|8@0f`qwKHpQ!!k_Xd zLA)8Vd}x&NKFA z`{P+$744|YBdckDbG4&76I>GttqEqTS>*Gca+$nO@Jv}~!zoN5yZ_4SO17lRy;MuwbDx>@M*BN5PkXV0qcENGy+mJQuPzv07uT*f3aFSZYGGD!`T+e#Bap2B!iy z%e+<>{LcxkB?dgOxSl^#EZta-0v2HEvBW0g26ek$Zl!dE5HW zPor8?KK=Ri+cy8?9#m_$o4>R4Wkbw!mBgBs&}b^c;4m3zC;TrPV*Mlq{n?VOO82hu zv2%)Qo)Kfw`)Zgd?l(IJ0km!g~-uc^gj@!RRZ?f4zU5BGIomUvzy%!^y- z=K|8%M1-(J30Phb69f0Qa5|xR__$@r1L$goaqj6Q3|r_fj0ND&8b1GRfCmVT)2y7OL5lf4 z!=(a^8%0?oML-W!(=7T9%^;(In0W&8nvVI;nub1$Ia?AC(n0i^H6XpepS|t`5@u}I zbx{DA&zX1+sqqbm~g9Zm2Z_MLR{?4!|O z(cFre&0yUzj|`UEOz;gDSpr^$0E*y%Jllq4uIANRGiNSfawMJ}z_Z_+mo?eI9Z#58 zr2)<|S@W5vc_dWZ0S+L^f*=2T3m9l8^*rG3+U3>9B!EnE&V zr?`CfSD&!@S!nhXzbf%p$a)3jy}%n?8J3>|blhy)cf&!RX6}G1!U1|Z>nK!V$Pz>p zXi~oSkZ}kroWlW)^AHw?B`ahF0>c$RIt#|P2_4M20%H;X1hbN7o23>Tj2qdNm{4$s zNyXISQDJrgb-K44W^_Z()mY-elB-&UEDdI?w%NxB`tWoh1mp7Rri7I-{wDL$FVTWWiDfX9JDIL&hV3O4h-LIPwr& z$ifr0o-mrcNaZ<-5<4%fY&N!-vs&ZjnS*sKX&U^!%It2EcF!uby9bfJ^5q@NQpe@8 zyicCiG%QUOG7t>hvzrQqm1(@Ky#Cm+*DE;hn2PKP?OLN;=(s;c7^st zrgm7xj=erVZuW^YO+iAya}IF;Q$dk1!K`TN%m(ub)8OGM%O*}T#?0{oYx%wU6e6YI zZVov@s&c^{S(yndQ+{@7U&KS?iO8R}t{2@ebzJm-qy$3V_*hMudLfS|1$}{bC~+K( z1Q(bi>{8%3lwsd3^us2aXX^=RSr-`^vwXyKE-XlbM+&)J0%}U)ZA^#7uR%d6nP-S& zo>e)32iA;XtxSi{8tj)@a3y@)$G`8geddi@=8mm!jywd<$^oR2p7SOh-Vkv>41Kh1$;1b?GXq@JFiZYA;*6jw1gF)O452*7&Au)_j7LNN-)Lr6QPg`O5rNTh}Jn{a!9l z)+Z^p*6j{^eeGqX9uI|^5|nDFWH&9-25@SR{5&kVguw1#PZ)Nqh0I;$Sv!_yFU+C4 zW@mif-s@#Lvg_(WdicGv>*9>L-T(7e8|~lws-r}A-~L%DIdCy)~FD|@I z#e#}u6r?e)LGmgrhzkN8lA!1gDq=xITGy*toc4pu+l@Oa)A*k`P;uL^cq$r3nL0H;x zu+R(_9h_Hu&*{G;Pm#3pQoXd?5NB)Xmd6Ah_9 z^8#hRbr#iHDgryUmq>;Rtg}G0en$SPX)jMP-l+nnZC&Z>88~uF!wxw2t6m= zQU-w?NnOONY%*X==_t$nWd5%UJCSjeBQP(4p~5hN^264}>YDPZQxS4fgH?k+Fb?!o zvUV zhN_5plmxt7@+G`E(ESY-zSfKu!KrtNhbHN;AF)EF7lV)|UBqwNd62?K$u?gA+EyIV zSeO@%7sGl&h7Hs#4j>ud;*6NOc%B4g#txkgNM4wMlS;=QJdwa^0|yul4oycB&;xPO z!8(D(lw@%dB%MQu;Y&kNeryBYXuul{-L_V$#HhuZ6YG0%vGWAGViGdHCl#<)W*QPd z8LE|rq3MjH=oGgH6A1Or3!BX@B3FvJ6Z}T$Y#6>#)*K6qMSiRT`>@i_cmoTaS0ED( zApkjL9X1JW_Wph-Lf_1o3l?$;+Q?L7D((mz;~C+@Fc?PS2iLRpiuqUuofK|LT+RMW zAn=BYPWm(G9-tIwLxYo$=?H#93B9%=UU_#=y+gG*QYzHmEVe;px`BAY>%yEr)kgka z65Cwt!6XIk-Lmd4yWBJn^qsQVPW9C$^sT4TW&Y|U9Ou=^jj=1607qv3)}ctQ&X3g4 zo+C&0kh9!;_8%J$R=S`O%SKINiNb3`a%f$R#SV42i**fe;ztWYPZY33jP(N4z{SOW z9zCmul8n82ntYqG1(pxT#X-La`jd8!Tylg%h|E@d;q67VL8gv30zGeK;afxd%fW?1iCDR!I}(uOsKIK#QZKL!JQ z^pOVG={QmHl1Uw|Qwj9R7%Jlg3IV9CwacD{CstC+D=CZ5Yw$-(Qeg~nMEqN{0PN!) zGd8jppBG`pDj|Ctx|2d3vlMFRBd~oEE1r_Bv@D_TIPxrAHezPKqq@#QU5zi*0o%ey zKC)oLdBbSyruA>y`wjDV_PlG+oQAGkF-JxS-_+M>`So1$1)jX2kSuZTFC)qfg1*04PZ?g3x5C%8ASn9kM~Zs znNmXucK6t{1#WLZO`L}_L{#XYJ@yyEu0%fr2=t#vL<=~1-&stv;}0i8>tKth2< zudplHqD9`kq;Myq1YU-qyJR-q%0V>frz|n2x3e%<6wqiU@?l{GC;A;uxDe5!``S<{nDGhJpFzTmKMIbR;`v;Mi|OX zz-eAvDuun>0Dm0TnbU}La>NxNM?8=YI}l*ki(O7h0V#G1F*^V?w=WzJoXOBZ)cTjd ztm)qh)vFC^_0q#%uC7L+KJ+bp9$XwGf&Lrj)9r9I2H#fQysq8ZT>Z9S#IWdE$f+{n zJxfD(NK+;N^kNT8072dOr?|)_8M=WrSf(zq(5^vEF!mAulML2Wuws)ZF(E%C@XGsW zpZRIYwt~M7p{EE1@IaPDG76EW<} zhC$(oxzEUu{eKtw48|G_Z=HFwp3>huvo&1H;S!$-{axW1fGW%`WI=N>G;Qmq%^Gu; z%D8L8wD0my!R2s%D zBI*#07e)b=%kYanPd0Zq%ln+Ow&*% zgj#mh5V_yvCVBYK_N!5a-EkN#(eTC{$VUyZrLI@)vV3s=bwgyod8VO}(zU7eMCpIu zV6X5bqr-01Htf2~wZ}>Bo%U0kBJCb>(;vuv%1!q=>_^}I?qhZ;RZ-8h-KN&c>IZ(e z_%?^F!$x+QrqDn;xy^qk6)E@9db^63+s&u>PX4-^%JR!&KX;SUZZ^6dE+_q*O3a7l zpj_WT?Oe|M+=)H1(xiAj@T$7$CvLwSm48>L&+YXoZHn@m_rLEt^9H$sen#br+J41V z8yyPOhM%O8al86Pr}OS|*>$9ZzI$V@johlEzXZHo#65wGKqoHfRW*>~jfg7j#6AuY z&kOV5Vrvxa2c~CX&pK~O^e32{0{JV56j-mz5^%gAatqtf44wHU$Fl(Q1$E%6fq%qa zzG5UZujKVj4b;!)JkQ|1mcbv|W-3w_8;?M^)YKLLS`D{0Ns`fQ0qSNCQT|@|(xPR+_cGU)Yz=9IJRAC|ldi zlElIV7FjSO#r_eoc>13_=d%9)q7K<8OqEcDcu|JuCI9!IKh&T&S*p*a-h~=x={?rv zOU$%~kTgcZA_!783u6=v znU!XNt#)fsK9mF(=Y3K(?mGwvBC%*O&m1Io+eY{sh;F4i^N%7evET)zp)*BGeLo;~ z9l>|8apT4|^N*ag>{0_doq;j)Ud#^Ohsmz|BBV+OAujO;)I*$gHp8K&T%-v4Vm}vC z7&nY=3XK5Q1N{f^*UqN{?W_&KO8{LIPlR{KzVziK$@YUs9{GXoB3z-V1Nh;^0|!9= zvTl3+Q|5wIH^n~n{L*%cz4LEvw!eKRV9LA<8Up4G{)m7gR_}T2$YH0%h0lro9}$9r z0{Fm!1B2oBFQ|jMy#(%OS<*mP0m*zB_*XOuni>}R9BEovFp(@P*{TO+Vq7MPowCs6 zHOqF^N#jC=wqS{fdSFL5i2C^vM+f!uii~E}BXAMJO1emat^?(cZBNFbyl;hLH~5tD37L|3Y3Dg2d zLd$z=jy0bdhNf_4)3xiP!>@Ye4q3V5gZrnGKcjbT%VxtBWi`!_nx+~G^|i`!=dO~$%h zzUQ9LDB;TOWmS+qk38~>EnH4x=6C5`=ETM>S|6yE+d?&HrN;b-+7O3F$m`xzTB2OF zu4|`TZAm1%snoT}?US$Bw0Wod!R&m+8*A!JW4A^!t(Vuf&^oZQf~?`a>@SO*6+79J zz;-yH5U?l5%vcdS2Xhy(h)f9Azp+}rXZiyEDff$Q>@m=qd~NQ%4}pwCdlo-o$ri`l zU2@khLQL{acH0#VjA3O1t>6pPaQ>f}Y)H47z_77UH~+&ER9Rk9;fuzmuLVj1C*gf#pEtRyo@Z%IN6=@z6WsKcs1nwABDZiacUq{M!AELh=9^aeXrdZLP&1@zI)6#4*`Z@DE}8=bPt z6RD24VV z_0HJlh5$J|>%D4kuq+gB3 zWN)zASz1>TvDps0nw^oDy`_Nfey;;JvfUr3 zD7C2`ayT9M+*-=z2$Yo6$_SUV#bY&bQX{gb$E8%+9WiHVShbNaH12aJYU;e6I%o2? z4?n(S4k-3&8#&}kwuo$WN|x>Tr`o+92R_PX^OlfHQB`QTG2q-A4GqA3!MMw$|9 z%omD1XtJE8!KTeSc5H6y+rNLlxuvtSrMYY4M(XXprlBd)cd!rdTtn-NylQdzo$+v_ zY4by0cnB|j{`;S+Xlj{z!(4My*WL4XGhc!f75bxw{b(A<3d|mYlBK55wO9qK2gW9V z>fj0JGF$&R3qv6d&udip(~t=P1eB&nHqB)sYma9id{R;W^q~)aSUJA2Gv;gC=BVh5 z-f)Xd2ez)=7ghT*SH8n8fA_DxZ7!&`-_AF55*^%GnGD3E8}w*&Q&kOp@sr@w?BE)|K6(+rrT&5<;_Bejn!Gr(!o_ ztyn!oj6a0FJ(PtbtsCBWe3!uiWfF1T_GH%XAiF=yd!Ev`sSeS6SsBfTs;fh0e_5H? zkB!j=crbK}d78%`6b<~$ex2s!HnSd1q)D4?67D1V38xhW>7|E<=~E*gYMN{^ZyBaf zCf-y3p8DuyJ@)vq76w0-E*p>r*0)Ro@U=mT2NN7+%Lhk+b2NAc6Hu(kvtPvi56e_iewBO0*pwJQ3vIjL!NM8^cAdo&@c%%~y3~rB!P3nzjP`Tq zWd2}GaZ_T|&CtX{S_wbRn>uJz$U7zq=yE=>vIBVs^eNJeVUsx0ykrl>IgrMh8=Q-u zJ%3+aBT-|WGA6F(Nsi&<<4f=B*ZXB1kIV|I2>qt6(In&Mn;{H&P8JLUAkOQV66q{) z{aDz7$${rW{<6H#FbiH0QNY`W_`^Z0_l9{JD)zy_%a##gWnthoB7PHaxg~_dD-D5< zdblp#5IXt128%<}4Q(Ep0k8vmRFDHFeLe@?K6upuBSS+0O&9Yu&xH$V?r(wq&{^Os zKGU3^c%=X;mLmvSfsVsl4g&E2{85U&%a*8GI#-pj?JAfI->q@h1w}~rVjrZNQ%3w^9wQ-dTL_Nn^lH^QwS!M8KsfIQ3Jea zT{tvN;I9tffCk#$BJGo|0<4NUhkoVz7C@jyJ-)rd`b{mE>S~(5g^(7`PT}VOYfB@8 z1AHLO!Soy*g0(>?TMiJ|VxPGaTMyAe+cUM6;$fY|Iu^2&8Wg_X?829%Lb)Zc!J2JpyX~zZ)z5w2VO4|B95UXYY=FC5J zp>H}tsnyuGEd>~L7{S0iEb9uN7|AE%?1OAD*0xZZ|Ez}1J}x)HeAdHtgHOM-Q|d0bGy5e+xz8IES)NGbv1PE58jQsEPk&QJx0%j zdjjAPbHA)Ci}gAFYN?cD^N~$OhnfyR#;O%{?7*oAvr`_QcIpD9f&5q!V>`ROU`)_U zFDd8XJAA3cgAd1=;j*&H(sFov@$Cbg%S&m!L#1Cs7?)dJ{=>fccKqh|DGI$ymfz{9 zbtGpTsVK*q&^9R1cqwu;9x{yjVHjT1Wye| z+VXU;vXfDG!V>J*u?mT(cMj17PTL)sc-nzaP4x5}?CGI*Fg^isbS8T(v{oDl@m?F! zaL5AdHU8?1Rfqv9;^GZ$X}%wm)!&aIT+_iG^HwXy6C9i|pG)%eF+1hi1mP5(b6a?( zv8WO91iozW$+WZ;V@$LsV1lH@{xrcU#I`jtMub$bi5K86i6|^&L~IB+2apt_gR_L= zM#Bn-1NQz^HQj-Fhqt7$bxWh(y<3)dcTZg7E2sN6ob~jQ+_z3Wlr~SE)UIkGbFaU!F4@4tAe!)rQsT;JN&h8LG5RFHMix-Ilthq`{dN%1-j-cK#7-LeNE z+Rn#zY>n5(v4ShcqhUN-^5cPgbi@(3QXnGUOUJe=PCQxZ&JMoxaC~c!jWmn-0RmV( z>x2~-P?hD)-m@Fbv4HQI32PjBeGzx*f1x_4=5s9BisuE0>n8 zZ@Wr6N%yA@siY~imtMPN9l5gBQ1(z;SL<~>rRM>z3@Ns`VBW@Sw} z9WL3?Crf=hO6ai20sDZuDn1s#(o+Ls2pK-m?HqLD)^bEd=UNc<+bHF>mPOn z(Kd($@+&ZR(Kt9#+#eR&DLm#-(y+*hJu>h7A+${Ghu%bke7+&EwD$mM2cCv(w|>L) z97&pnWytDymW`I3hR`K-@KP{XNh{2&;HN?Mz9bSFNV5+cQkEsNqDf)T6 zE#awcf65~yujiW`wVs4|0|)@{*o8)u>3tu5^sSK8U{G&;^uzno=8r@)wZ*42p*Pr} zYxBNk9z%xx$!yy&j9F-*ArXBAyayWt*04T+8$ba19^>YTBLJR|R!2^dbtg#o$cf&9 zz=yH`Jsop3_VLWDH9xbNkB?n=X9;p~1{f^-7J8{md{5Etw}Qz1LTDFnv*VR_R@lmE z@8BTqEnhNr%Pv<0VdFjBd*o=u<&t+Rnr`0uPv);}HVXd}-L_;rRuWXpyJMFA5$i5j zgC&m%-u)%HUH&rWzLI@We*QWsv<6o7m=OfP1iBD+6AWO@lfPIKeCg~vI3y~oQAlBIuRAYPA74} zH)Ie;Lt*9l9e%}QYpWlx4IYkcjT{cvjMul>JU0K1VDO?xp~%XkrTK+^9S(bX&EcZl zi(h!fy3C)7*9Rp#^B*ym63~CMv`Kt7awU>0#cM6~t#a398Gf}2)!G@))QKsD7*2|i zqt$!Nv~cJ#yRg3CZ>RXP&r%$_I)8d>)B7HM@XWStXC8d?eW$l=TRKNg@l*`h&=gC> zn*f&L+vU`wPd}PcBSAS8j9H)Z=l>8OU)}ldY+9-3G-{>Wiz>RH9@PGutA~cJu4t5H ze0YUvz#4BB>k>ErU(8-1j=Zaf#d9#r^X-5QAmaoD8@tWr>}CaIo2Un@5T&2MmxDIT zya7EJ`ftuUxK63&4FtSeY59=qb=jPy0k_Yk5RDNjo{BOLJY-ey;g_n4qPrcwb}iMo zwWW&wyJ)h^77-dNFa2{_X^GwGD|MBXyX}h8uR6PK(7Y|kquG4@;-Wd zK21`Y^7DLJmP(c1=hKQ*26M$KZ$r8)pT_@kV~gd}D)M{sX}5IP)|XG0NGaQU+-nBc z#yZV(Cn4Fx+4UAc>S>3SGGi~Fevu!V{b`k@h&euCR zI6FExIWcoEH!?RiFkQGUTo31_XGSL{(DU{}*3R5SZhBxgHxxU5CN^_wq;qz5I5s>z zIUeId=K!_X)b!*Hxxv|11`vkI!{B`G8X}9=NQ1DmV~9m*#~NU#_~t*SdXTqM8kJ_j zMduI`#sBx$5$S}KlP2(F4tr2Wks3jIR^(n-Iwp-_pUQ~XtI~`z$MIea@62Hr6{lwL zBqj~xJSIxsB=WfSSE?t5+UC&a5Xy{+wsWX2hW?*GZL?UroWXi}8~%-mUY`)XZpG*( zrE%o0_GhgaOCRzl@Lmj_pHYf`0; z;}w_a(;(n6iGMs6uD^I}#n33upFv;8QF0d7|GKx6D91B3Eqcx)w)#5>Xv~WCcr-K8 zLFDtCjiDdY=+T8wFTOu4YGerV+ZN2*U-s5cl$sDF2hdgy&|%y&bA@Et%EwD+ICSl^-z}9(R$i|kkm~g46v6p+Cp1t8*Qf@)JHpM7wx7! zFf8}c<@74rPY3ALbdV0w6?7#XrXzHeuA*afHC;p3(sgt__0s_U7pXxSq8ts=2%Vr& zx`9qY9*@%mP0|$INYgY!vouGi=q5T%XXq^5Os}EW(k+OH(djmN9lf4zr#t8k^hUaq z-b8oN-E1}i`B2(|Dx5KaTPI?!;n;xL|(0l1YdLPZx`{@Jp5Iszf z&D<(037=YqC#O&5hFl^&H8*pjkQy7a z9iN;$=>(6Q93B|Vl|dx5OwEmrwM^%3oXgG3mW&TfpKP%NMd{$!4AyC6E4mj z&rOfy)bY{j>B(u&=*-Mqu4QrxtbeG~x|z;dxA|un#7u6;HZwag?cfS?6SGb+a!3#R zz*ug2R)vh39z03vr*goM*|OEUnH%Q@rgMdH590snoE`||pFi=#cNkt_Tg=PUumzZeojcYtN zf~i8D7kQ_qCnv13PNqjDI_FGob{5*+jB99M=7gXv#|gX$((p0>T0m2|>4||c+xXmU zj!t_9N2dod89U&6b%Q@L|?=VsaeMXD6qW<8vb= ztp4SxyhM=U@zIHa=`+ruf!TrM12Z|$z%&46fdmLY37|Tm8jViPjLx_xCR-+QH?eYQ zhdwzuTUrc(xv8?^Tb{rRG8jeO1rW?lc?wA`xN;-#cm*dw5_$Tc0%p1Lou8TYi>}X{ z85i*3o6rS0E4;-CoCJf&joC-WCXX`}G7KF|{6HSZ)WG1$fsvd86mk;=?gX620go{! z&x^o7o)Vj&+|h~Slc$}t)5sVcm~c)Fj84oVVILj?Zh*!{bHkvq9HZ-qm#1hBq$p<7 zGd?*Du4COOV*@uip^c9ppP8EiT602xYA82z60@|zb=Bj!fs?su+i^hOe$&A0;0bl+ z#At482#kDe2$OvpQ#^V$r;Y+2M+cp=12ZQX-x%Ko?y574jc43*6NQ}8JTc~*lBE9) DY{4$w diff --git a/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.woff b/src/compiler/crystal/tools/playground/public/vendor/octicons-3.5.0/octicons.woff deleted file mode 100644 index 0cd624c86d776330a49fe3df86289ad9865a6e26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17052 zcmV)_K!3k?Pew)n0RR91079Gq3IG5A0C(5`0RR91000000000000000000000000_ zQ!g?A008s=002Y)0035e+*4X(ZDDW#00B4v009&L00N5acpHFcYjO7@{-~odCOwlW_*QNICakH+Wbx(V~o$_y3{y!8YgU%lg))r$nK!2 zHGWfUjMwVTQ9yta%BiG^IvQxAg%Is@(oG-z3^L3JV~jJ&G&9Vzz!EF0vPP6`_BbHU zDJkw>dNY~di&@v;$EA!4JgTXuk)Un^hV=f>Ez?hy&j zNax&SB-!lxT*o!~)|Wo@p?AIMRp?nydenpNbgLU(>q==|Dy0jZ>qH5~b*w|}Yez9{ zX;T|oS2R$Q`%gZ~Dax1pf9eMz8!hdRN`Yx##@Pt0a)5`Q08#Hl($Z1gfMZ zBFRLhtgFT95NO3pG7a%GRHp!UbzWA-6UoY0ath)JRZBy%6{yr=iE1TMElNy!-u|f~ zTMtCr;HnLBv;gUDrMRrHr`;0{?7Q0osSec}UAJlKol~3EMZIcA3Ow)K+ZqFCi#}k< ziv3gBz~cG8uuoUyaLa+V8LdA9Nk6CqautFdIlp@4iN19y|Hg{!8xDtueR5@^Keev! z#Fc7bxcBasuv`JpUpx<43FhbLEg6_8rLb*99c!v;MI9SLTZv>s>BWZWMV&n}dg0Ja z$0&ob_I0ECFJll+RUr*M)l?h67&BIRAm26N^-gr_Y{2vQfy&%$%33x&@L2Cg6r`;B_*;&cf?r(x^0n*NVQwUp!l$a(X$cYE->= zew=E`tBc3S(k$Kzki}C&0JW3f^uV&@^n?!#-^6rs8F;>VetUFzzYi?%^)HXM$Fm@x zx@Gc>JNh7NUkXNW&*bDD*^nQ*bLdd<%F8N-QMv5O;-R5CAG3Rxr&sT(D5|Pd_N-1X z@7n#j|1j2gz{$m;}8k+p5*fR4?Er!}WCCG%Yad7Blcd)3EU0tV?)- ziPzypvcxa0Uttj5zHlu@jM=k$S7+ zMZH`VXO$rsuZ$&PYV?c^CRH~=zS}gr^RThXx2+|c_NL6hfH5#IyJ#g#cv0_)@D9`5 z5nj=Ymy7+Q*3q-=ehcU(W1xQA*j~nxnZ2*lxZ;E$4jswFX!Id)r z4n1(bp-O&9mxiU1RFx*NX1oT;0=^qcXQPE=JYPmKh&vRo; zhoB2VA+}3JSUWsmQKii@$RuT&yx#OYx`d{oT`l7p1}T>r7on0326=XJo2)NitHgrMG_et(t5&E zP4ptIgId(bE7eK@NuPK+@{(4F(cXBm(Abhc;|rwWnxi9E9K2%W=rxcI`1An}wEz6) z(CHbl2TuLSlgo@heEH=Mk7sh8AK72Z_V;IT0V=YYfqvH4cHK^;b#u_`4Q_5#c3#)k zcg^Zwe^OOH`Rmo!)aTVr_Z8N5_4_;?Uw_xy!hM@y12ZmJ4Y2wWgEh8jj1-#FoaR%= zX{MwN(soHIjGqJ8{rR$%ESHdy!cCJ|b7w3xW_$B#8Z)^Saz(mGZp>~=KDxy~;^`@6MN;S-DkQ^rlPi`N)=|GSZ(3#SZ&TO;h{R?nM4pW3t;9Hcx$c zD7>Qnka!M@HfFn!Cuf(*Bn(gjbD8lc%H)eTs!BTC7D}g=Yk#s(ZXN3gj>RvB^}ai8npd9mcurc#Fs$?keV&t7%E#V+ zoxD@IAlkZD-MRiCQ};*CmB%lb?av>)DQC2WImjS|4seC;`IaLkE|fF zVavw3{YTd^A#uUCgomZ19x0F9m^kq?QqKlyi?mJJDeac_O9!OOgxpX_N;?Z+OA5FU z7pA4^aAG0F7nrt2Yz~hTrzW`*Mea!V@n<>0zaQeIEN-F<;oKQn7IIt@8Jme{$lW%G z8zaXC68@(h<}Q=uKW)mytPOsJ0Vc`9R`_T{!4? zt!dbnxLy1S-f77M#}w>2>N5!^i(VZDEm_s8;0h|I531Dmx!Ku~BS%i~(tgC1NAwn| zIy39oG=6eKTr$F(+SHxe;7EBKo+e2_DI&E?X+n#x;3h#W8(Kz$6ebw|W;@kFq_DB{Gf8_C(Y+B>| z?#7P6dX@8pRL&d$Y$y(apeIqFd?2ugRDG7wFgUTge*^^$14RJc+AQa&-X!U&9K9~8 zZy_tx70!G#5bS*lb%C6Y0=-(92El{Is#NWq#)X1jDdtPbCAG)ylKfHC?+Yhu{@_#= zQUlXn=Uh9*w!qExSyhRp24>QU)Ie>x|DsH*KQh=^=u5m|ARseOHu*7YN$Y0iXuNoM z)yz{7Pj?CmhsM3W_D=Ta_DxJp^j;tE`T}7k-XXK=T7sacGe@^ygtfCAdbmZN!5IxJSu)UMK4(#LRY;4hOaGY_uY$J&$d&e&KCiqUAjkcO50 z5B8!JFB-`1(bq{7%ChfOIqfo>P2h)8I_OFDreUtrN_Ge~;zsn|nf0ECI zN`6s~4?zLv1V^K*ZL5jopw}+g$X1xaftgFBjDuu{uh4V7Qh-w*K0C~xnAOCDTX<2) zzk_M-oUFQbO%OQg8I6j5U!xE z5|9l`=YapWY3smf^%82|^^&ckWVX8=OiLpydHV!Zr{fH%CP zx*~*kDuh)=$onL*bBQi4?LLep(?nT(mqWU$Wn#64dftqnh^EWZ$e8?z7q z?vIbujopJD1J+%9@jBUHQD14;Ubw}|*j6S32ChJQ_ML}1ip7pY_J0TJbTd7~2)wgC z!lV)F#FirpJGSMH?aL2jjOM#I?coH#fksZ)%_*Py8pWpos-?2h)Z^^0leh2Re|z%j zr=7KJtlGxdwpFXP18fKL{Hqze`h0iwjC*3$s%=d=aoI)B3&1$eEg|7%&3FMT7wPqt zQibqkehP4oQAdwgqD=tXLC>a(i`mw(ZKKUF*%{BQ+NrkpUcR-sj6phYO9Jd>#0qK- z_LUltI6AhKK^KfP!{jE=Jf8O?JT0EB`jw@~idP)OeEJv82Fy2M=ml9jhFEBwbS}0Z z^`bHd(E8b1P8I=9GUYUsIIe~>G=`?e_pXSPuIVeRjwU+K)D%|l=sP$RaVl;n7HbVK z=d8_q8X%MD-nBeZO23Yp@Qby}V+h34;?eePOB4os!A>AIbn+JDYY4S{4BHm=K^Qf= z0%9~J4B=836x7c(l>2&Bp0g2PwMJkL<|0EV)o60!geK@jfYh?^J%jf5eI4O?jVrdZ z;g0(IpWVLwS+dXAbC8*rY66P8_=ae%#lfPIBdr8YyG(^Fi#Qp{JOS1N-}ZRE{eURH zQdgAvS9p0VnIza}8mAuk_HOqSmS03jXEwc$uuZqX>S2ywvw7#WP06k3jlwilnleN-)d+~MZ`Sg~nADudQ`b9i;a?he;$hr($GHa{10M;wp)04$o z!Jc#JgmmW7d50D~E(QxCK9>sE`Y6`5swF|!pfTsGO}w|N5zpmyLp+B(-kO(0XY9SN zG&%Ly)s61DB==-~Dd;n=ed49O9E;Er+E2Z_B@HfA{1`7! z!JGpJve~Gy$*8Z;CY_v{a|CDoNm2_I=ByacG{lDgNTd~s+(cb}%h4~t4#A-UU%Px-#+vbqBdrtWa5ybcArxYm;Y zOP)qv9;Y5D05`sX8bbz>9h-vj0=JGLO}=Dr_N8-apS{@|?vBWZBHdwoGYxHq&4$I`_}Vk`(XNKhVzs;&(~ zK7{m|3_-kyl%*ahO#|sn%~DG?-`YK~@%ov8gUdGDuyJMgTbg$#)Akpe1V4lhE}vO+ z(W2eUnvjKrs7lPE0i-`2XZr@!Y zFmq2Z8yFlM90+DR0@+L^8-UutB*5eVFX6k5;~m-Hz~G=gysYr_fhtIq15fuZ;~KHC zH4tbEH9Zo(5X=U%yK;eCN1%IV^ZM=p91R49kKKH1IM9&`=FZ=HK9=@3+?Pc2p(xjowR0=|ZQ6Cc{9$NRH*L9lCP^GsJjlIMTNX>B!f?Hso* zCwg$St9mh68Dccrx=L~pHB*P`^r#L=2tjfhaNX0^lk1&0l!ydFUAvD=yft&nTS8s^ zLpQ=dp8RbHdn=Sq@BFYmw!(gKdT;W*8Hi87;kImVVdb%S#J}?Tecd7RZQ0{1t{)su zz41P~4gWG1WZ5S^X6GmEpXcsrziNG3QMZG($|gLWY1ZxQk~U7%WD7ESv;YjFd)^G?9N@o_vKBikzJU78>x z0&iN0cn{~;@oKqR5tccRR2*Hm#e3zNb-(FIYt_T$+{y$BR5LmNhWG9P(>ixBU*6f1 zo01K9*OlI_YsaF|-*%jn@7Y<-@6B4~z};R02BKIktCY@`k5sg@;5k?+M0Ut615!+q zN_j*pay%g=OH5KYs2@W1SKWC0HCV42n*9O$AvxE| zw0tWF!?-~zi%E94`Yiuri!>-p;3i9`*bD&(cMhS(zI?8_JG{iDboIsMIcY66dsb4eFfHWbkm#&j;l-?n|hvOogNG{CORfn32 z4F+GBf|5YqdZk&)aUfb9L#~F06e4&U@&#S)EL*goNJI^kU`iG#2z`JY=u75IwoNCz7Gr@W;~lgiy+MEw#H~pSfV4On}U=UDJnS z3i#Lj@bJv=@IToSDcDQQnWu+^Ch)enW;3Z^cx$lm9 z?Pi;{5!xfi!$QmpsvN74CCbODNsuKQ>AE%xvxq1O(&84kQ*pi3#J-9k+UX?iwF4IV zikPqBgLn|}0R0a(8BMs+NS?N-|MA(zT&*_8Z5(IxX^PlH&9X99t%ilQgg50-?)OQ9 zuHQL{Gj+j^SeXW*^-`7dqIfb9gaTqwtVltDAjgCjH=H#5n$UR#Zl3$tW9Q<|6`l3f zTv$lhbM_p=Yi$cl)N#1Y`9S?=PE~ud(TxQnIU=sY#wc<#$kegcHZCx`(65|NtJtql zB922$kU|2;(lalvSb80~$m8yLswz^gb;fnXzw$m*4)7#z5ARkY?j_fieJCWu+>}lx z7mK%v+aZAW>vM7kQoaQ7q=KBf2YirBRI24DF%BJysN`Z#yqXAwo+cLlbhv<%w}iX) zOW^{0wPaY>!%vf8$mH5}1Z7VzzHcoK7%l>Jz^C)C@f#@mBIV3EKVUQ#-`LcO6(Tk45G zghB908VNx*v}FkMUt3Sh>S81av9_L}p`NyQFj8FI($hxF#yr(B$*RDpQ3vv{P_5=SiVVi>Y@ z8s{|Did+z23Ed7zM`JXf#n2-a<66vN$7d96Xy|8H|An_F?8tsuB6)mvZ;kNS2#jTr zbAFKn=EsDp$W62010eqAMbwhvJ^=p4((CDPOtLtaGmY2L*pQv^Zoc;QR5ZEa$CL4` zL7HFTlHy}oA95>A=CctQ5HUGMBAt60fOv#u5>E%IUUQ~1x6@;1e*MRcUV~@S0z%~1>zmq8t`qsA)wM&bi zM^e(2nmDqz8GJ;IS$rTqHq~*wu)l=Q#3z^b7mjzN?*7Tt+7(Y6#mC~qk3O+t?bJ{1 z#>Xm({Yt~Oj$PlukycSO7&%Od`UC%~3F?~#f4yAZc>z zj&2nINw?V_*Yi|cSxph?Fa~>z)=lGoWbTyxX{FyN^;YBeqjKt)L z6P9)2*`)z7Mk5{ucqEKKeCF>7hrzeIx=9<-H=&6P>g_9~HPVc%9?uZOZGEQc0dbb=D!sB}QnALd?ndBL8# zt2^KnCTcf@6BH@mjVR$h1$(s!4w`pDPn>2tku=}o2iL@Ei4Q$OniMP;k+EJk*H0iTEBbX^88$AtmrxZqUX8?wchWppTU+ zi6r)l5EmEzdQAPry=>N=v*#@NmXH4US8WciKz@Y%D}~+8#GY*G2V}(y-Pgb@bPx7O zfgm)E77nH(@5FeKx3(yH$x-1KgK&ii>U9PX=Yqshv8!Ql*+iPg!(1w54jgCf_yJz7 zX8YEy+s8!dPWWcpOZ6T{Y2aRtOmJG^TGOkQu^vdo zP%}mmMYf!KcUn7RW3hu#gO1g4s<0>?ibfB{#$0>qZ5)Gn8+}|9OgEQwq!tOQNzz60 za+B9TJyO<@8xE869Fs<37pK!?%rgIc2$Dyw9rQvv($cH8c7ZQY#71eh{YL+*wAbe~N zU!vg>?ac~0v?obFm^~?L7I(^Aiu<>5RFoWkGTyd}cf8fW9IJP=(<2W!J2hpch zX}z>ll1d2`6%*#d3SuQ#^4!^!L!cXlAIVJ=ER^ZmnzaW>8K=S z8Z3LSz6LAyRf9S@SP>iuHQA(+7lnC{FTw>Ye}R~+aK1umPZS|Xeta-LzX(+2c!Li zg?u0u=mhBK(K{d(PIdHjpR;@dQopCyOrOuw_nvCieiG!=bWd?G7BBchZQ+hJ%L@~~ zW|2a!0GZ`}e=-;vX=@DxAlaXXVsCre^`BVsX7(a#a(PbO@k(+m;d&(_RppE$aOd^M zLc!?+40dnae;GXVVdh(U;LV%<^0)`KZ8>ibz+LbBD`?qw!x=|7-*4DzqEePyo0eF% zDAJ64Yhl4NQJ`y!_9*tn5s_lOpR2(fAoBf(8(TaktXbIEI8C|U=iEKbqx1+}BT`Oq zTEm&o^{D$76gpp3}T_mNq%Cnp=mO7G=r)AA3xej7Y25 z8nJlU2<3A_`%tl_&>L#5uxg5*Ej8`@p?zl6$lX{I<)Vl|#BCsy5!(%4TnHvRbBl)E zjrSwW)rEX@xilz^;#{UaX$IvBfvahXJ`$Rz!j%y*kx3$Q8@#I=83Ng1uEH#Osq^@F_1C6;Vza z7LT-D6q-gCoPLr{(kW}nyOL*rm&|QPXL|>?1-#x1{`j^uW9GBI2fK2?+j;i)nN6CT&LZqVDpodtkGXJImPLI0xM z7FqI*eX0vK@7}!`yb=5Pf9l)O?n6Y(tgd_=lFJh;G0`R1A^Cbt!x`5(juUxb>Q$IS z82IwbHEKOULy67L&l~JEc{OT2-5k}b@o+(*$R#bA7zRz92EACChEfkD)A^gh|xns02mF_KnOTz3{_sF({*EI&~rS@AX3I>~L41rEoYq{0LR z9OWC9l5w3$BAV1;>CDR5HLbI!$ynGK^~`R>_EvK3c zGi#Cmnw&b;iDzrio^Znm+<0*&4x_?K&;FA9&+KoU*iYos!`zD@+*Xx@qHDUwA790h zVAYP7oX49lbyVSB*4=um`(t<4ugWG9FFAbdr9|>So6b5q^(*W`%ZLX^_pra&)DVdE89D|$3?aG@6b zXTIX_=CADBV*jdm{tXbnW>rkOWI!v#Svj|?ZX0Jjk^lZyk} z@FoM3Et=A2LazRvuO0npK-J36ELS_ZjIiVzxoq>H)a;;$vO+Rj)l1c^mX(n>vQa&$ zmBuR5P$?EPQOQZr#cUp#+s-)?*^j~e+Sd&8+lidot9I5O`dVivZsZc*HfNEGTb%D% z_Om0Vm5RTwb6{e`JJ9*Qc*+vzlM603TbSnWgRS=Go8kwx1*8XIT4XJ&hY1{%gcG3QVx=$-ltpuMfGZBQ zW$sE9C+%5&Vy?#HO$Beb3!hKgibVNO<*LqOip%3gy~+^)jWV$)uJqz;<|qcIG-tro za49Uufs;cvQ7KnNB05JCJW0JAMh1Wvo_&uW z*ukCa_TwWIZ5O@?4o0ZgZ5Lk-%3F^0_JQ7ATmhi}&q^g0D5MYiRnI}+ zoxaUp@NZCVSd|(HfFFJX{u}&oucqwz(1-RZ(5m8djw&9to2egr<>mKjo>31d436Ln zM!;|X52#ej`U*wGn@8+tX^(x=4{eTn?4SMMRqQ^$=JUcoLyP?oi?duWjL>!V7p~gO z!v;6SG8ITNKTw4kG=Qd9PDyX>6jbOE1X?t$vAU!)+r@1oTup&b=DO8 zOwmHk`ly?JW^sN=@hrr9TquqFBXg$5i1SMFZyG!zun|Kp_>wM_DM6fKS<4ihm8n#D zu7u7fcy+XKY&6jg&S6K3ZUtdJ7s;PwrG#MaDQJ5t7X%S$tICQvpvY&3T}1 ze;Mjwk(xrq4TbJFuF!-ZB%3v(| z#6q|D?1JUwGR?jIR~X3K2=7~z5B4VU!37p{Kbi8eKl~8}Ntv(X5QMR9-O@6o#y!#z z@)%-eolJgIZ$u;M!x_lNQTs=H6H8RfxnemwPPqbzXrTGxytp`xb4k@uDT?!WUcFM( z3n<~VxQJw{;={^n9-k5}!Qh9x_6^*85Rt;ot1eoRK6u{4w=?Fjyz9J?QQcPG31=-JZPGdP~?qANap%H`7wIlI}{ z%i4`+-B*^Cv*z!f{#ULvnBuh*fqF(J?`lf#;x9IZ-g2DlEi3ugIJlHYvet+qa2LYzJtfIyf ztw7LSFH%Y2n)JafRV&irR&K<>hYV4$2X#+EO^&76l$@5i(t%36tnn;&g8ukw#^>A= zs`qk{=etkwJFzwuJeQW$s`xlc5$oV_%@RX>Ugis=I#d1(uyz?(8)7eir(bLFdUGR* z0I*09(R2uyU-Nhh?TXx{c|D46KxQNDiCnK5XblBo%Y$;DE794Ga@f1W$5rQ9B{c+(=vnMd~2lN@AZSHTN{k!yqT%qD0l-ag6ed{T(bA@AS6zw4^+qUA_TtDxqECyqv~)9;^?9LWyfb{(M(YFwo;I{OX~v)a^@TrPBi%YbTA!12-_H;J>N_16l>Jo-N>$ zWi=G=%W}6CXR^0d^(oQLgs|&>Nw(PENC7wZk8*8vo^M5-{{<^%QiRzk8c*Hg-z^Hk z!Zx#(<@;0VbgDnU%zmlag2q@CF6*CKzkaHJX4|&efx)q{!GZC~NtiBQ*xR3)*)fCn zTnHR!wpE~uWQ+U%8KlnndfAG!kdhFGREHm1a7r!D1Np&`+1W>bEX(%KUj5#4V2oEco1SF)$vj6E^B=)6 z_$kk3?B?D>se@E-B~dMq-j8EZC~62Mp%7LIRYe0OT&0{R9d|ktFx%D!vx&~mgk5WE zvulY?`ep!Oi&;umm=zuv8xhMa8@6+aoO;;fIjrUq@DtjWW!$bmbPzswB*)jN>yZ!fwS5i6aMJSCkJQW>Gx3xi6PLh$ z^z_+az2z+&tlG6X5$Ej0?14!&2*26wfCq4K=Tow5(p@ zVP#2;o4fMWY%>LKp?bXs& zX|Is>Zsiv07L60EC%YP@SvPOfw-9u0?BsS1GK_G}#ba1VI~WzJnri^Tj(z-&J07Q$ zYR_}s0(giY_haGVf!iOveE@oYyyMpH?pq(CV#ghy_lUb>p0jw0nrBT&U-C_h#$kK;_k=|_^dGOjDGG6Ao>#%+hMwFIK7W+&U)4!1^p zUhp3AVi{?L6`BhFhQc@^s{EU+vm^L7yH%FqgN)s;b!++SyxqO6t&xX2J0Fg;w)S>= zFS-SN#X=`DDLrbLMMd?c- zG6@+j{--#RimxwH+-Oq9$$|R*-mY>qrv+R3hF15P*3BJ7@IJPFO}0DRBQ@u*i}s*=)UvryBX*8*=}31?3S_E}jnb+@@vU*; zX(#4K(59LlLoF_<%JWmfTUOeKqM-}lB-&y2tsd%Y32M1$xohu1Z0v0l>rr<#a~US@ z=mh-+g4A2uPU0d8HUG)E?z&{m+TDu`<7`~w?bXton5p3 zF)tK5w%m%1qJYAlEZF9B!;eTfq{uBe`0pdjVVFZdmv1_N%mqv2R3rvPhPTtElekSZ zv8!eM43lQox4P!zGFZ3d^YO z;>+pNU*RN2ry`b(=Yh%h|A_L3eiY32veUi!`j+irY=5>P;Sa(K6oWA@*_~Ogv}_Pq zQaFf#f5etG@`>YQeJP+&5MP4h5x2<-4pkxvVxm!^-f%w9J@RybOWwdUquqhLeKk%1 z?2nE3#)7`}laJmblLVFTdGwQ8b^FK8x40JKv=ts9!_juQq&OZ<9(Lu#)4N&p+&r0YlK%p? z)vd=`;%cOv5%?ohj;QgL$9SK7m5s2k@x1XYLQneoC%z)iqf_!(lx+HVQOn1pW0f*F zDC4i)@&PtcTKxu)Pu*t!*S~My1Y5RTe(Zw%`*s~;7VNre;=-t>b^m!s55w1C#n{9M zeBb`Ww6SX9)rRfMb1UM6R-RiXDS16ho@V5#r4*F7`Bg5+dD(O8iF54J=bRY$-r!x| zpS|k~AGPD2RV~U!cJC#gn(^$jCr`42_SI~p{@=kL6zt!vh5h!!POLIq#bb#=6xk=| zqe3C&+Uw_!A2aWQ;X+qGz({X*AEQXcU*(4(mnjTF&dudvI0h6y?!+4}%0p=0$rq|g zNctA%Cdkj*0Vk~cH+*eh=j>iD)~Ad64uZPRo`s@wMasFODM_Ib=dSKN2tmBqeqtfY zz4`kgRp}gxLEuWkFyM;E@)6?UE0Vnl3W4CO#Mm_Osl`{7aS8^6r2OLgupICV=dSIJ z?@Fyn?TU9@n;Z57JmK~6_>xnJRO3{ARv7Hzt<6W5-tfB5vVY2Nzf9!eIp6x~k_Mzz z{JW7&Id3R$b3<%=ijAciifA5UBs`TQh6_vflN{Wm;KCU!z80)N_W9=_t0?wQ_pSQK zqYvJ^cJ0j%KKhZH)~>Cefc|VTgVfNUDQ5ezTbg(0i;q70Xi-hYSuvgwUkz@0-0APG zYCPvAd@8)N=|PJh3;qAG|MJWC$NCuSYyP%bRoWoE<^N;!-01Ofp4Jhs(j6MI2ZjX# z>`HZtRi|W}w)q^-MT_Uh=@=!T#}j|zs3X^DHG)0OUZd zEf5T79UZ=Ct18Q;Uki;G#lAIz9q^};$-MK;t4Qnb+FDx_Z>ZJR7V#^xH>`Ri-5mj6 zYb@gLXjcPXk5>)12Rd`1aAqVEcjYVcnk9b9#R8Q_Oj~Y3_!&|(MVa9|A@NM%S$`7W z@&5oM8V|Lgzu+*-`;KrROZ6G#*Q zQ!NZ(0001ZoMT{QU|@Fm@4yhl?ZCkB|0TB;15gA7tN;LPRRr;PoOMz`4uCKSV@SOC zsXdLSYn(_ecxVO;N?|h**)FKB6Kw;qAF&pmQ1FpAVn+D}_uNmNxWlF%2;>@Gxud?s zWocKTTwgq!&<G@FiF&hoQH3099Aa|c>+s_#`zAN|aYCr!5 zX1M3|d>h6I7Eve_00000003?Pq5!r56aiuZ(gGX;dIH=7OasaUIs}RY+y#0CwgvD8 zG6rM@zy~@9dIzQl@CZr>xCrbCYzgKHCJJ5(vI_bO77IoTo(s$j3=CEc0u5FTkPX-l z0uFQzt`B|>v=9Ig6cA7ltPsEu)DY+p{1Ge>#1ZHb_7XG_ZW5FeC=*~4q!btwWE83t z{1p@xE)_f#mKDYo?iNB8P!?Vmz82UQ9v3_pf*1lAtQhJUI2q^~JQ{Kuq8i>C5*tPv zSQ~^J;2bg>L>zh?+#N_Ah#j^a>>dUlLLOiqpdRucG#_RkmLK>bDj|R&<|2S1vLfUo zEF+F1o+Gd$!Xwlp<|Ftd1|)PP93@mGkR|*lL?@sq1}Ii2lqk$82q_dPU@4+0^eTEP zzAFMNG%LI;6fB4=@GV#^oGsig5H4UYnl92W0xu#jb}!s8R51uKJOBWAoMT{QU|?9m zaGODl0R)(Um^Syg&EJ{&NjhOT7j`dA8Fec@+RtvUG3rvlxT3w68MBZTPd20># zv`q>1h-pB3)W^E((LT5_6=dj$8k!+9q$|ovP>D!G8nKcxVB^^5*-VlYq6&FC*v(vq zMyz(oAi|Vll`ix4pE``SL1c~`>CP6=WBAXJZK%R-NHPAItIu71gzgkAz&`rvxn4%V zCGwai$WL5f8tV|uIUR##Iz^WK_My{%c2;|mMqQ2S3Y+N|5$f=y~ z)^*$e)OFv^q4knF1)F0%9)h2{nXGy|fyax#cuKHWu213gpVmE{##3H{S-7di|KV2o zo2r-mDkvJKtDNo`czb6OmGw$=d4K5??F5y3VRq(wD_LXb*=A0`3rxqptvmO&ZgASb z%_4QqYS$}2+_dm-jnSKpU4yae(b5FdrF=iNKY|)02mku%I|7(A% zg&k-kKnGo{AVd#+3^2q9W9-B#c40U6;4mDHy*L6#;wT)AV{j~v!|^x)C*mZWj8kwb zPQ&Rq183qaoQ-pEE+U+V^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4kxEZ(L zR@{c$aR=_iUAP z_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;6?C<0xjB9U- zs$a+~cTrpyabT#@1AmJq+Ds|!HE~tjd@Cx7Al7;s>cr`ZNN6>au33%@t<_}RpcG~r z@oHYj%xXsW-8LP>l&R*$+eBO-K3CJHD77Aq#A0I2_-j$|K=#Ab(t^_I_CbmwVLQ6 z?IbxR)1VZx==mEL$&d1^_7}0|U>O!PaqUVf27cl?nGsb!az+Z3F?@b= zkQfG?;bL|=+%Esf)bYF*8|Ez$*0q_tom(>D(!4HB6Qk*54o6B;V@I;Eo~VJXUqJg}v>{B&oojoj4A2}XjIx@*CUErqv{ zFTYnZ)z&eVy@qICO|PVksp7Q}udN%c7H326M{l~&l5%j%NE_vVSsvXzapwO9K zx_r?1E|yA|d6)_(Vqu9rU>LAP->hoQIEGZP%&oM2r6WZ%E{Zz*0qw>IBT!eX2dAFE zLl(S5`&$sy)o?5H2e*?($J-)cODz3gv9fy0;;q}Y7#Z`j!N(_i4_VosEg@@?2Lu(J ztkAJ{p~rB=i<>2}Qi)_LGFoAW%((H8aV{B;BJ{j83iOl&jdP`n^+xCnFC;>Rm5|DD z(3uph4fdF%344sZT(gm zluQsa`kk3@N#3=&q$1@(UZg!gX(KB)sViBJG6|iqjo!T88|`+jSL3{2tF4vs(u~Vr LwOaoHQYeW8y;gUU diff --git a/src/compiler/crystal/tools/playground/views/_about.html b/src/compiler/crystal/tools/playground/views/_about.html index 9d79019eb7ce..9308a739793e 100644 --- a/src/compiler/crystal/tools/playground/views/_about.html +++ b/src/compiler/crystal/tools/playground/views/_about.html @@ -35,7 +35,7 @@

Usage

puts s

-In the main playground this is hidden by default and will appear when is pressed. Since the sidebar will likely show all the needed information you won't need this output all the time. Also, puts returns nil, but the sidebar will show the argument instead of the returned value. The same applies for print and raises. +In the main playground this is hidden by default and will appear when is pressed. Since the sidebar will likely show all the needed information you won't need this output all the time. Also, puts returns nil, but the sidebar will show the argument instead of the returned value. The same applies for print and raises.

@@ -54,7 +54,7 @@

Usage

sample 5
@@ -129,17 +129,17 @@ 

Load, Store and Share

- +

-And you can use the whole standard library. Seriously. Run the next sample and click here . Once you finish you can stop the long running process with the stop button. That will kill the process, hence display an exit status and a . +And you can use the whole standard library. Seriously. Run the next sample and click here . Once you finish you can stop the long running process with the stop button. That will kill the process, hence display an exit status and a .

diff --git a/src/compiler/crystal/tools/playground/views/_index.html b/src/compiler/crystal/tools/playground/views/_index.html index 7c921596a51b..07d164ad3fe7 100644 --- a/src/compiler/crystal/tools/playground/views/_index.html +++ b/src/compiler/crystal/tools/playground/views/_index.html @@ -7,13 +7,13 @@
@@ -23,12 +23,12 @@ diff --git a/src/compiler/crystal/tools/playground/views/_settings.html b/src/compiler/crystal/tools/playground/views/_settings.html index f89bda2818e8..6964b609fc01 100644 --- a/src/compiler/crystal/tools/playground/views/_settings.html +++ b/src/compiler/crystal/tools/playground/views/_settings.html @@ -6,7 +6,7 @@

Settings

- Create a GitHub Access Token in order to use the share as gist button. + Create a GitHub Access Token in order to use the share as gist button.
diff --git a/src/compiler/crystal/tools/playground/views/layout.html.ecr b/src/compiler/crystal/tools/playground/views/layout.html.ecr index e0d3ae9eb851..0f5a75a150de 100644 --- a/src/compiler/crystal/tools/playground/views/layout.html.ecr +++ b/src/compiler/crystal/tools/playground/views/layout.html.ecr @@ -5,7 +5,7 @@ - + <% styles.each do |path| %> @@ -28,7 +28,13 @@
  • Playground
  • Workbook
  • About
  • -
  • +
  • + + + + + +
  • From fce2482762fe99b3af0d9424c8fb1dae3cd7eab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 31 Aug 2023 10:12:16 +0200 Subject: [PATCH 0670/1551] Fix log format in update-distribution-scripts.sh (#13777) --- scripts/update-distribution-scripts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/update-distribution-scripts.sh b/scripts/update-distribution-scripts.sh index abb74e613095..467e5b036de5 100755 --- a/scripts/update-distribution-scripts.sh +++ b/scripts/update-distribution-scripts.sh @@ -42,7 +42,7 @@ sed -i -E "/distribution-scripts-version:/{n;n;n;s/default: \".*\"/default: \"$r git add .circleci/config.yml message="Updates \`distribution-scripts\` dependency to https://github.com/crystal-lang/distribution-scripts/commit/$reference" -log=$($GIT_DS log $old_reference..$reference --format="%s" | sed "s/.*(/crystal-lang\/distribution-scripts/;s/^/* /;") +log=$($GIT_DS log $old_reference..$reference --format="%s" | sed "s/.*(/crystal-lang\/distribution-scripts/;s/^/* /;s/)$//") message=$(printf "%s\n\nThis includes the following changes:\n\n%s" "$message" "$log") git commit -m "Update distribution-scripts" -m "$message" From 9fb416e5bf2120d8109ba29c5f1b67fa3edb67a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20H=C3=B6rberg?= Date: Mon, 4 Sep 2023 13:59:52 +0200 Subject: [PATCH 0671/1551] Avoid realloc callstack array when unwinding (#13781) --- src/exception/call_stack/libunwind.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 1f32df229894..220db21b71f0 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -38,7 +38,7 @@ struct Exception::CallStack {% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %} protected def self.unwind : Array(Void*) - callstack = [] of Void* + callstack = Array(Void*).new(32) backtrace_fn = ->(context : LibUnwind::Context, data : Void*) do bt = data.as(typeof(callstack)) From 574d8f43ed3e01166d37f9c24030d2ac5f32fffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 5 Sep 2023 13:37:03 +0200 Subject: [PATCH 0672/1551] Fix `Process.new` with nilable chdir parameter on Windows (#13768) --- spec/std/process_spec.cr | 6 ++++++ src/crystal/system/win32/process.cr | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 5ab14694cc98..7ce5f3849a39 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -68,6 +68,12 @@ describe Process do end end + it "accepts nilable string for `chdir` (#13767)" do + expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do + Process.new("foobarbaz", chdir: nil.as(String?)) + end + end + it "raises if command is not executable" do with_tempfile("crystal-spec-run") do |path| File.touch path diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 36878bc935bf..ed7caf769bca 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -207,7 +207,7 @@ struct Crystal::System::Process if LibC.CreateProcessW( nil, System.to_wstr(command_args), nil, nil, true, LibC::CREATE_UNICODE_ENVIRONMENT, - make_env_block(env, clear_env), chdir.try { |str| System.to_wstr(str) }, + make_env_block(env, clear_env), chdir.try { |str| System.to_wstr(str) } || Pointer(UInt16).null, pointerof(startup_info), pointerof(process_info) ) == 0 error = WinError.value From fed02a6e4acff5911722794edc99d48149d04226 Mon Sep 17 00:00:00 2001 From: Mike Robbins Date: Wed, 6 Sep 2023 02:59:09 -0700 Subject: [PATCH 0673/1551] Fix memory leak in `OpenSSL::SSL::Socket#peer_certificate` (#13785) Co-authored-by: Sijawusz Pur Rahnama --- src/openssl/ssl/socket.cr | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/openssl/ssl/socket.cr b/src/openssl/ssl/socket.cr index 461185e342d2..d5f0d88561a7 100644 --- a/src/openssl/ssl/socket.cr +++ b/src/openssl/ssl/socket.cr @@ -274,7 +274,13 @@ abstract class OpenSSL::SSL::Socket < IO # an anonymous cipher is used, no certificates are sent. That a certificate # is returned does not indicate information about the verification state. def peer_certificate : OpenSSL::X509::Certificate? - cert = LibSSL.ssl_get_peer_certificate(@ssl) - OpenSSL::X509::Certificate.new cert if cert + raw_cert = LibSSL.ssl_get_peer_certificate(@ssl) + if raw_cert + begin + OpenSSL::X509::Certificate.new(raw_cert) + ensure + LibCrypto.x509_free(raw_cert) + end + end end end From d5b83186d89ebb7409b39bd1120aaf83919838dd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 11:59:31 +0200 Subject: [PATCH 0674/1551] Update GH Actions (#13748) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/aarch64.yml | 12 ++++++------ .github/workflows/interpreter.yml | 6 +++--- .github/workflows/linux.yml | 12 ++++++------ .github/workflows/llvm.yml | 2 +- .github/workflows/macos.yml | 6 +++--- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/smoke.yml | 2 +- .github/workflows/wasm32.yml | 2 +- .github/workflows/win.yml | 8 ++++---- .github/workflows/win_build_portable.yml | 6 +++--- 11 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index ca8796333544..2671fe5d61d6 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build Crystal uses: docker://jhass/crystal:1.0.0-alpine-build with: @@ -30,7 +30,7 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download Crystal executable uses: actions/download-artifact@v3 with: @@ -47,7 +47,7 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download Crystal executable uses: actions/download-artifact@v3 with: @@ -63,7 +63,7 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build Crystal uses: docker://jhass/crystal:1.0.0-build with: @@ -81,7 +81,7 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download Crystal executable uses: actions/download-artifact@v3 with: @@ -98,7 +98,7 @@ jobs: if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download Crystal executable uses: actions/download-artifact@v3 with: diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 8bc156bf5d7f..949c43a307a8 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -16,7 +16,7 @@ jobs: image: crystallang/crystal:1.9.2-build name: "Test Interpreter" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Test interpreter_spec run: make interpreter_spec junit_output=.junit/interpreter_spec.xml @@ -27,7 +27,7 @@ jobs: image: crystallang/crystal:1.9.2-build name: Build interpreter steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build compiler run: make interpreter=1 release=1 @@ -49,7 +49,7 @@ jobs: part: [0, 1, 2, 3] name: "Test std_spec with interpreter (${{ matrix.part }})" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download compiler artifact uses: actions/download-artifact@v3 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 815b2a7984a4..c9d00e9084d0 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -35,7 +35,7 @@ jobs: flags: "" steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Prepare System run: bin/ci prepare_system @@ -53,7 +53,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Prepare System run: bin/ci prepare_system @@ -71,7 +71,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Prepare System run: bin/ci prepare_system @@ -92,7 +92,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Prepare System run: bin/ci prepare_system @@ -111,7 +111,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Prepare System run: bin/ci prepare_system @@ -126,7 +126,7 @@ jobs: run: echo $GITHUB_SHA > ./docs/revision.txt - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v3 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index fe4b0f90875f..bf8660be39bd 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -27,7 +27,7 @@ jobs: name: "LLVM ${{ matrix.llvm_version }}" steps: - name: Checkout Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cache LLVM id: cache-llvm diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index be1674e163d2..bfee3af4fdde 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -12,12 +12,12 @@ env: jobs: x86_64-darwin-test: - runs-on: macos-11 + runs-on: macos-13 steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v22 + - uses: cachix/install-nix-action@v23 with: install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index f087d357ff98..7c4c60ced711 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -13,7 +13,7 @@ jobs: container: crystallang/crystal:1.9.2-alpine steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Uninstall openssl 1.1 run: apk del openssl-dev - name: Upgrade alpine-keys @@ -30,7 +30,7 @@ jobs: container: crystallang/crystal:1.9.2-alpine steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Uninstall openssl run: apk del openssl-dev - name: Install openssl 1.1.1 @@ -45,7 +45,7 @@ jobs: container: crystallang/crystal:1.9.2-alpine steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Uninstall openssl run: apk del openssl-dev openssl-libs-static - name: Upgrade alpine-keys diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index 6ca07c89cc48..d5120a378640 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -13,7 +13,7 @@ jobs: container: crystallang/crystal:1.9.2-alpine steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Remove PCRE2 run: apk del pcre2-dev - name: Assert using PCRE @@ -28,7 +28,7 @@ jobs: container: crystallang/crystal:1.9.2-alpine steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Assert using PCRE2 run: bin/crystal eval 'abort unless Regex::Engine == Regex::PCRE2' - name: Assert select PCRE diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 58302463a126..6f52859e3932 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -61,7 +61,7 @@ jobs: steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build fresh compiler run: bin/ci with_build_env make diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index d05980b87945..a11e353f6509 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -15,7 +15,7 @@ jobs: container: crystallang/crystal:1.9.2-build steps: - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install wasmtime uses: mwilliamson/setup-wasmtime-action@v2 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 0714c400e2af..d4d3df576ef9 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -18,7 +18,7 @@ jobs: uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cache libraries id: cache-libs @@ -90,7 +90,7 @@ jobs: uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cache libraries id: cache-dlls @@ -218,7 +218,7 @@ jobs: uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download Crystal executable uses: actions/download-artifact@v3 @@ -268,7 +268,7 @@ jobs: git config --global core.autocrlf false - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download Crystal executable uses: actions/download-artifact@v3 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index fcfafeb5b6a3..7b784df7c871 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -24,7 +24,7 @@ jobs: crystal: "1.9.2" - name: Download Crystal source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Restore libraries uses: actions/cache/restore@v3 @@ -105,14 +105,14 @@ jobs: make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }} - name: Download shards release - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: crystal-lang/shards ref: v0.17.3 path: shards - name: Download molinillo release - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: crystal-lang/crystal-molinillo ref: v0.2.0 From 045ad1584916874e1f06f82a6c73bdeb6be09e4f Mon Sep 17 00:00:00 2001 From: Erdian718 <37168484+erdian718@users.noreply.github.com> Date: Tue, 12 Sep 2023 19:34:51 +0800 Subject: [PATCH 0675/1551] Mark the return type of methods such as `Slice#copy_to` as `Nil` (#13774) --- src/slice.cr | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/slice.cr b/src/slice.cr index 0ba78e683ed4..ac83eacae99a 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -488,14 +488,14 @@ struct Slice(T) super(range) { |i| yield i } end - def copy_from(source : Pointer(T), count) + def copy_from(source : Pointer(T), count) : Nil check_writable check_size(count) @pointer.copy_from(source, count) end - def copy_to(target : Pointer(T), count) + def copy_to(target : Pointer(T), count) : Nil check_size(count) @pointer.copy_to(target, count) @@ -513,7 +513,7 @@ struct Slice(T) # dst # => Slice['a', 'a', 'a', 'b', 'b'] # dst.copy_to src # raises IndexError # ``` - def copy_to(target : self) + def copy_to(target : self) : Nil target.check_writable raise IndexError.new if target.size < size @@ -524,18 +524,18 @@ struct Slice(T) # # Raises `IndexError` if the destination slice cannot fit the data being transferred. @[AlwaysInline] - def copy_from(source : self) + def copy_from(source : self) : Nil source.copy_to(self) end - def move_from(source : Pointer(T), count) + def move_from(source : Pointer(T), count) : Nil check_writable check_size(count) @pointer.move_from(source, count) end - def move_to(target : Pointer(T), count) + def move_to(target : Pointer(T), count) : Nil @pointer.move_to(target, count) end @@ -554,7 +554,7 @@ struct Slice(T) # ``` # # See also: `Pointer#move_to`. - def move_to(target : self) + def move_to(target : self) : Nil target.check_writable raise IndexError.new if target.size < size @@ -566,7 +566,7 @@ struct Slice(T) # # Raises `IndexError` if the destination slice cannot fit the data being transferred. @[AlwaysInline] - def move_from(source : self) + def move_from(source : self) : Nil source.move_to(self) end From 29e1e005a495effec4ecdcbd26fe74535fcaef3c Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 12 Sep 2023 06:35:12 -0500 Subject: [PATCH 0676/1551] Add types to HTTP::StaticFileHandler (#13778) Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Jack Thorne --- src/http/server/handlers/static_file_handler.cr | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/http/server/handlers/static_file_handler.cr b/src/http/server/handlers/static_file_handler.cr index 00f7e34b3523..12ead6963e58 100644 --- a/src/http/server/handlers/static_file_handler.cr +++ b/src/http/server/handlers/static_file_handler.cr @@ -31,10 +31,14 @@ class HTTP::StaticFileHandler # # If *directory_listing* is `false`, directory listing is disabled. This means that # paths matching directories are ignored and next handler is called. - def initialize(public_dir : String, fallthrough = true, directory_listing = true) + def initialize(public_dir : String, @fallthrough : Bool = true, @directory_listing : Bool = true) @public_dir = Path.new(public_dir).expand - @fallthrough = !!fallthrough - @directory_listing = !!directory_listing + end + + # :ditto: + @[Deprecated] + def self.new(public_dir : String, fallthrough = true, directory_listing = true) + new(public_dir, fallthrough: !!fallthrough, listing: !!listing) end def call(context) : Nil From 7b70718f6920f2f137a4b67e6dad381247a0c847 Mon Sep 17 00:00:00 2001 From: Will Richardson Date: Tue, 12 Sep 2023 21:35:30 +1000 Subject: [PATCH 0677/1551] Fix error message when parsing unknown JSON enum value (#13728) --- spec/std/json/serialization_spec.cr | 3 +++ src/json/from_json.cr | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/std/json/serialization_spec.cr b/spec/std/json/serialization_spec.cr index 980bc1d65533..4657759481ea 100644 --- a/spec/std/json/serialization_spec.cr +++ b/spec/std/json/serialization_spec.cr @@ -302,6 +302,9 @@ describe "JSON serialization" do expect_raises(JSON::ParseException, %(Unknown enum JSONSpecEnum value: "three")) do JSONSpecEnum.from_json(%("three")) end + expect_raises(JSON::ParseException, %(Unknown enum JSONSpecEnum value: "three")) do + NamedTuple(foo: JSONSpecEnum).from_json(%({"foo": "three", "other": 1})) + end expect_raises(JSON::ParseException, %(Expected String but was Int)) do JSONSpecEnum.from_json(%(1)) end diff --git a/src/json/from_json.cr b/src/json/from_json.cr index bd77199cdc85..fdc18b3a9171 100644 --- a/src/json/from_json.cr +++ b/src/json/from_json.cr @@ -318,11 +318,13 @@ def Enum.new(pull : JSON::PullParser) {% if @type.annotation(Flags) %} value = {{ @type }}::None pull.read_array do - value |= parse?(pull.read_string) || pull.raise "Unknown enum #{self} value: #{pull.string_value.inspect}" + string = pull.read_string + value |= parse?(string) || pull.raise "Unknown enum #{self} value: #{string.inspect}" end value {% else %} - parse?(pull.read_string) || pull.raise "Unknown enum #{self} value: #{pull.string_value.inspect}" + string = pull.read_string + parse?(string) || pull.raise "Unknown enum #{self} value: #{string.inspect}" {% end %} end From 663818b33b6dc71b0b690325247ed1af48a8a02e Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Wed, 13 Sep 2023 06:55:11 -0500 Subject: [PATCH 0678/1551] Fix docs for `Digest::SHA512` (#13796) --- src/digest/sha512.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/digest/sha512.cr b/src/digest/sha512.cr index f6364e4db5aa..aba62d1ac3ec 100644 --- a/src/digest/sha512.cr +++ b/src/digest/sha512.cr @@ -3,7 +3,7 @@ require "openssl" # Implements the SHA512 digest algorithm. # -# NOTE: To use `SHA256`, you must explicitly import it with `require "digest/sha512"` +# NOTE: To use `SHA512`, you must explicitly import it with `require "digest/sha512"` class Digest::SHA512 < ::OpenSSL::Digest extend ClassMethods From 5ad6cbc161a47d8947a5cc1a50f37102577847da Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 16 Sep 2023 05:43:26 -0500 Subject: [PATCH 0679/1551] Document `Dir#mkdir`, `Dir#exists?` (#13795) Co-authored-by: Jack Thorne --- src/dir.cr | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/dir.cr b/src/dir.cr index 2b6bfd13c7a9..121a77172789 100644 --- a/src/dir.cr +++ b/src/dir.cr @@ -246,6 +246,11 @@ class Dir end # Returns `true` if the given path exists and is a directory + # + # ``` + # Dir.mkdir("testdir") + # Dir.exists?("testdir") # => true + # ``` def self.exists?(path : Path | String) : Bool if info = File.info?(path) info.type.directory? @@ -274,6 +279,11 @@ class Dir # can be specified, with a default of 777 (0o777). # # NOTE: *mode* is ignored on windows. + # + # ``` + # Dir.mkdir("testdir") + # Dir.exists?("testdir") # => true + # ``` def self.mkdir(path : Path | String, mode = 0o777) : Nil Crystal::System::Dir.create(path.to_s, mode) end From 1898dfc57d3d477f91a5522139a1cc3bb50380a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 19 Sep 2023 21:39:13 +0200 Subject: [PATCH 0680/1551] Fix end location for `FunDef` (#13789) --- spec/compiler/parser/parser_spec.cr | 28 +++++++++++++++++++++++++++ src/compiler/crystal/syntax/parser.cr | 3 ++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index b5a4d51a2c3f..28f273be147c 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2630,6 +2630,34 @@ module Crystal exps = Parser.parse(code).as(Expressions) exps.expressions[1].location.not_nil!.line_number.should eq(7) end + + it "sets correct location for fun def" do + source = "lib LibFoo; fun foo(x : Int32); end" + node = Parser.new(source).parse.as(LibDef).body + + node_source(source, node).should eq("fun foo(x : Int32)") + end + + it "sets correct location for fun def with return type" do + source = "lib LibFoo; fun foo(x : Int32) : Void; end" + node = Parser.new(source).parse.as(LibDef).body + + node_source(source, node).should eq("fun foo(x : Int32) : Void") + end + + it "sets correct location for fun def on multiple lines" do + source = "lib LibFoo\nfun foo(\n x : Int32\n )\nend" + node = Parser.new(source).parse.as(LibDef).body + + node_source(source, node).should eq("fun foo(\n x : Int32\n )") + end + + it "sets correct location for fun def with body" do + source = "fun foo(x : Int32) : Void\nend" + node = Parser.new(source).parse.as(FunDef) + + node_source(source, node).should eq("fun foo(x : Int32) : Void\nend") + end end it "sets correct location of parameter in proc literal" do diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index f2c6bd8ed867..f891fe640824 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -5793,11 +5793,13 @@ module Crystal break end end + end_location = token_end_location next_token_skip_statement_end end if @token.type.op_colon? next_token_skip_space_or_newline + end_location = token_end_location return_type = parse_bare_proc_type end @@ -5818,7 +5820,6 @@ module Crystal @fun_nest -= 1 else body = nil - end_location = token_end_location end fun_def = FunDef.new name, params, return_type, varargs, body, real_name From b5bd994b1fc7d22f1ee9a6372a07d7bc102b76f3 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 20 Sep 2023 21:53:49 +0800 Subject: [PATCH 0681/1551] Fix `BigDecimal#round` for large digit counts in base 10 (#13811) --- spec/std/big/big_decimal_spec.cr | 105 +++++++++++++++++++++++++++++++ src/big/big_decimal.cr | 51 ++++++++++----- 2 files changed, 140 insertions(+), 16 deletions(-) diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index 81fd2fb82b36..dd307ee9812f 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -818,6 +818,23 @@ describe BigDecimal do "-12.345".to_big_d.round(0, mode: :to_zero).should eq "-12".to_big_d "-12.345".to_big_d.round(1, mode: :to_zero).should eq "-12.3".to_big_d "-12.345".to_big_d.round(2, mode: :to_zero).should eq "-12.34".to_big_d + + # 1 + 3.0000e-200 -> 1 + 3.0e-200 (ditto for others) + (1.to_big_d + BigDecimal.new(30000, 204)).round(200, mode: :to_zero).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(30001, 204)).round(200, mode: :to_zero).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(39999, 204)).round(200, mode: :to_zero).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(40000, 204)).round(200, mode: :to_zero).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(40001, 204)).round(200, mode: :to_zero).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(49999, 204)).round(200, mode: :to_zero).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(50000, 204)).round(200, mode: :to_zero).should eq(1.to_big_d + BigDecimal.new(5, 200)) + + (-1.to_big_d - BigDecimal.new(30000, 204)).round(200, mode: :to_zero).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(30001, 204)).round(200, mode: :to_zero).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(39999, 204)).round(200, mode: :to_zero).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(40000, 204)).round(200, mode: :to_zero).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(40001, 204)).round(200, mode: :to_zero).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(49999, 204)).round(200, mode: :to_zero).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(50000, 204)).round(200, mode: :to_zero).should eq(-1.to_big_d - BigDecimal.new(5, 200)) end it "to_positive" do @@ -829,6 +846,23 @@ describe BigDecimal do "-12.345".to_big_d.round(0, mode: :to_positive).should eq "-12".to_big_d "-12.345".to_big_d.round(1, mode: :to_positive).should eq "-12.3".to_big_d "-12.345".to_big_d.round(2, mode: :to_positive).should eq "-12.34".to_big_d + + # 1 + 3.0000e-200 -> 1 + 3.0e-200 (ditto for others) + (1.to_big_d + BigDecimal.new(30000, 204)).round(200, mode: :to_positive).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(30001, 204)).round(200, mode: :to_positive).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(39999, 204)).round(200, mode: :to_positive).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(40000, 204)).round(200, mode: :to_positive).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(40001, 204)).round(200, mode: :to_positive).should eq(1.to_big_d + BigDecimal.new(5, 200)) + (1.to_big_d + BigDecimal.new(49999, 204)).round(200, mode: :to_positive).should eq(1.to_big_d + BigDecimal.new(5, 200)) + (1.to_big_d + BigDecimal.new(50000, 204)).round(200, mode: :to_positive).should eq(1.to_big_d + BigDecimal.new(5, 200)) + + (-1.to_big_d - BigDecimal.new(30000, 204)).round(200, mode: :to_positive).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(30001, 204)).round(200, mode: :to_positive).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(39999, 204)).round(200, mode: :to_positive).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(40000, 204)).round(200, mode: :to_positive).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(40001, 204)).round(200, mode: :to_positive).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(49999, 204)).round(200, mode: :to_positive).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(50000, 204)).round(200, mode: :to_positive).should eq(-1.to_big_d - BigDecimal.new(5, 200)) end it "to_negative" do @@ -840,6 +874,23 @@ describe BigDecimal do "-12.345".to_big_d.round(0, mode: :to_negative).should eq "-13".to_big_d "-12.345".to_big_d.round(1, mode: :to_negative).should eq "-12.4".to_big_d "-12.345".to_big_d.round(2, mode: :to_negative).should eq "-12.35".to_big_d + + # 1 + 3.0000e-200 -> 1 + 3.0e-200 (ditto for others) + (1.to_big_d + BigDecimal.new(30000, 204)).round(200, mode: :to_negative).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(30001, 204)).round(200, mode: :to_negative).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(39999, 204)).round(200, mode: :to_negative).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(40000, 204)).round(200, mode: :to_negative).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(40001, 204)).round(200, mode: :to_negative).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(49999, 204)).round(200, mode: :to_negative).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(50000, 204)).round(200, mode: :to_negative).should eq(1.to_big_d + BigDecimal.new(5, 200)) + + (-1.to_big_d - BigDecimal.new(30000, 204)).round(200, mode: :to_negative).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(30001, 204)).round(200, mode: :to_negative).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(39999, 204)).round(200, mode: :to_negative).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(40000, 204)).round(200, mode: :to_negative).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(40001, 204)).round(200, mode: :to_negative).should eq(-1.to_big_d - BigDecimal.new(5, 200)) + (-1.to_big_d - BigDecimal.new(49999, 204)).round(200, mode: :to_negative).should eq(-1.to_big_d - BigDecimal.new(5, 200)) + (-1.to_big_d - BigDecimal.new(50000, 204)).round(200, mode: :to_negative).should eq(-1.to_big_d - BigDecimal.new(5, 200)) end it "ties_away" do @@ -851,6 +902,33 @@ describe BigDecimal do "-13.825".to_big_d.round(0, mode: :ties_away).should eq "-14".to_big_d "-13.825".to_big_d.round(1, mode: :ties_away).should eq "-13.8".to_big_d "-13.825".to_big_d.round(2, mode: :ties_away).should eq "-13.83".to_big_d + + # 1 + 3.0000e-200 -> 1 + 3.0e-200 (ditto for others) + (1.to_big_d + BigDecimal.new(30000, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(30001, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(34999, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(35000, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(35001, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(39999, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(40000, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(40001, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(44999, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(45000, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(5, 200)) + (1.to_big_d + BigDecimal.new(45001, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(5, 200)) + (1.to_big_d + BigDecimal.new(50000, 204)).round(200, mode: :ties_away).should eq(1.to_big_d + BigDecimal.new(5, 200)) + + (-1.to_big_d - BigDecimal.new(30000, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(30001, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(34999, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(35000, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(35001, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(39999, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(40000, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(40001, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(44999, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(45000, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(5, 200)) + (-1.to_big_d - BigDecimal.new(45001, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(5, 200)) + (-1.to_big_d - BigDecimal.new(50000, 204)).round(200, mode: :ties_away).should eq(-1.to_big_d - BigDecimal.new(5, 200)) end it "ties_even" do @@ -862,6 +940,33 @@ describe BigDecimal do "-15.255".to_big_d.round(0, mode: :ties_even).should eq "-15".to_big_d "-15.255".to_big_d.round(1, mode: :ties_even).should eq "-15.3".to_big_d "-15.255".to_big_d.round(2, mode: :ties_even).should eq "-15.26".to_big_d + + # 1 + 3.0000e-200 -> 1 + 3.0e-200 (ditto for others) + (1.to_big_d + BigDecimal.new(30000, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(30001, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(34999, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(3, 200)) + (1.to_big_d + BigDecimal.new(35000, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(35001, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(39999, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(40000, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(40001, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(44999, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(45000, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(4, 200)) + (1.to_big_d + BigDecimal.new(45001, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(5, 200)) + (1.to_big_d + BigDecimal.new(50000, 204)).round(200, mode: :ties_even).should eq(1.to_big_d + BigDecimal.new(5, 200)) + + (-1.to_big_d - BigDecimal.new(30000, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(30001, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(34999, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(3, 200)) + (-1.to_big_d - BigDecimal.new(35000, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(35001, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(39999, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(40000, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(40001, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(44999, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(45000, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(4, 200)) + (-1.to_big_d - BigDecimal.new(45001, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(5, 200)) + (-1.to_big_d - BigDecimal.new(50000, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(5, 200)) end end end diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index 0a97a21b9723..376192c5124b 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -460,27 +460,37 @@ struct BigDecimal < Number end def round(digits : Number, base = 10, *, mode : RoundingMode = :ties_even) : BigDecimal - return self if (base == 10 && @scale <= digits) || zero? + return self if zero? - # the following is same as the overload in `Number` except `base.to_f` - # becomes `.to_big_d` - if digits < 0 - multiplier = base.to_big_d ** digits.abs - shifted = self / multiplier + if base == 10 + return self if @scale <= digits + + # optimized version that skips `#div` completely, always exact + shifted = mul_power_of_ten(digits) + rounded = shifted.round(mode) + rounded.mul_power_of_ten(-digits) else - multiplier = base.to_big_d ** digits - shifted = self * multiplier - end + # the following is same as the overload in `Number` except `base.to_f` + # becomes `base.to_big_d`; note that the `#/` calls always use + # `DEFAULT_PRECISION` + if digits < 0 + multiplier = base.to_big_d ** digits.abs + shifted = self / multiplier + else + multiplier = base.to_big_d ** digits + shifted = self * multiplier + end - rounded = shifted.round(mode) + rounded = shifted.round(mode) - if digits < 0 - result = rounded * multiplier - else - result = rounded / multiplier - end + if digits < 0 + result = rounded * multiplier + else + result = rounded / multiplier + end - BigDecimal.new result + BigDecimal.new result + end end def to_s(io : IO) : Nil @@ -759,6 +769,15 @@ struct BigDecimal < Number TEN_I ** x end + # returns `self * 10 ** exponent` + protected def mul_power_of_ten(exponent : Int) + if exponent <= scale + BigDecimal.new(@value, @scale - exponent) + else + BigDecimal.new(@value * power_ten_to(exponent - scale), 0_u64) + end + end + # Factors out any extra powers of ten in the internal representation. # For instance, value=100 scale=2 => value=1 scale=0 protected def factor_powers_of_ten From 3f283c7608288f0d0df698a725e22f1f0d4c5e44 Mon Sep 17 00:00:00 2001 From: Erdian718 Date: Wed, 20 Sep 2023 21:55:07 +0800 Subject: [PATCH 0682/1551] Optimize the constructors of `Time::Span` (#13807) Co-authored-by: Quinton Miller --- spec/std/time/span_spec.cr | 17 +++++++++++++++++ src/time/span.cr | 26 ++++++++++---------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/spec/std/time/span_spec.cr b/spec/std/time/span_spec.cr index e59ece5a66d8..de9bf5c63a2b 100644 --- a/spec/std/time/span_spec.cr +++ b/spec/std/time/span_spec.cr @@ -34,6 +34,23 @@ describe Time::Span do t1.to_s.should eq("1.01:00:00") end + it "initializes with type restrictions" do + t = Time::Span.new seconds: 1_u8, nanoseconds: 1_u8 + t.should eq(Time::Span.new seconds: 1, nanoseconds: 1) + + t = Time::Span.new seconds: 127_i8, nanoseconds: 1_000_000_000 + t.should eq(Time::Span.new seconds: 128) + + t = Time::Span.new seconds: -128_i8, nanoseconds: -1_000_000_000 + t.should eq(Time::Span.new seconds: -129) + + t = Time::Span.new seconds: 255_u8, nanoseconds: 1_000_000_000 + t.should eq(Time::Span.new seconds: 256) + + t = Time::Span.new seconds: 0_u8, nanoseconds: -1_000_000_000 + t.should eq(Time::Span.new seconds: -1) + end + it "initializes with big seconds value" do t = Time::Span.new hours: 0, minutes: 0, seconds: 1231231231231 t.total_seconds.should eq(1231231231231) diff --git a/src/time/span.cr b/src/time/span.cr index d4ae3e39f1b1..13ea2981016d 100644 --- a/src/time/span.cr +++ b/src/time/span.cr @@ -65,22 +65,19 @@ struct Time::Span # ``` def initialize(*, seconds : Int, nanoseconds : Int) # Normalize nanoseconds in the range 0...1_000_000_000 - seconds += nanoseconds.tdiv(NANOSECONDS_PER_SECOND) - nanoseconds = nanoseconds.remainder(NANOSECONDS_PER_SECOND) + @seconds = seconds.to_i64 + nanoseconds.tdiv(NANOSECONDS_PER_SECOND).to_i64 + @nanoseconds = nanoseconds.remainder(NANOSECONDS_PER_SECOND).to_i32 # Make sure that if seconds is positive, nanoseconds is # positive too. Likewise, if seconds is negative, make # sure that nanoseconds is negative too. - if seconds > 0 && nanoseconds < 0 - seconds -= 1 - nanoseconds += NANOSECONDS_PER_SECOND - elsif seconds < 0 && nanoseconds > 0 - seconds += 1 - nanoseconds -= NANOSECONDS_PER_SECOND + if @seconds > 0 && @nanoseconds < 0 + @seconds -= 1 + @nanoseconds += NANOSECONDS_PER_SECOND + elsif @seconds < 0 && @nanoseconds > 0 + @seconds += 1 + @nanoseconds -= NANOSECONDS_PER_SECOND end - - @seconds = seconds.to_i64 - @nanoseconds = nanoseconds.to_i32 end # Creates a new `Time::Span` from the *nanoseconds* given @@ -93,10 +90,7 @@ struct Time::Span # Time::Span.new(nanoseconds: 5_500_000_000) # => 00:00:05.500000000 # ``` def self.new(*, nanoseconds : Int) - new( - seconds: nanoseconds.to_i64.tdiv(NANOSECONDS_PER_SECOND), - nanoseconds: nanoseconds.to_i64.remainder(NANOSECONDS_PER_SECOND), - ) + new(seconds: 0, nanoseconds: nanoseconds) end # Creates a new `Time::Span` from the *days*, *hours*, *minutes*, *seconds* and *nanoseconds* given @@ -111,7 +105,7 @@ struct Time::Span def self.new(*, days : Int = 0, hours : Int = 0, minutes : Int = 0, seconds : Int = 0, nanoseconds : Int = 0) new( seconds: compute_seconds(days, hours, minutes, seconds), - nanoseconds: nanoseconds.to_i64, + nanoseconds: nanoseconds, ) end From fb92a1e94669fd736c40d7b93611f43d5db5e028 Mon Sep 17 00:00:00 2001 From: Jeremy Woertink Date: Wed, 20 Sep 2023 06:55:36 -0700 Subject: [PATCH 0683/1551] Add overloads for `URI::Params.encode` with `IO` parameter (#13798) --- spec/std/uri/params_spec.cr | 20 ++++++++++++++++++ src/uri/params.cr | 41 ++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/spec/std/uri/params_spec.cr b/spec/std/uri/params_spec.cr index 44fc0b3abb4c..52903309ae66 100644 --- a/spec/std/uri/params_spec.cr +++ b/spec/std/uri/params_spec.cr @@ -61,6 +61,14 @@ class URI encoded.should eq("foo%20bar=hello%20world") end + + it "builds with IO" do + io = IO::Memory.new + Params.build(io) do |form| + form.add("custom", "key") + end + io.to_s.should eq("custom=key") + end end describe ".encode" do @@ -69,10 +77,22 @@ class URI encoded.should eq("foo=bar&baz=quux&baz=quuz") end + it "builds from hash with IO" do + io = IO::Memory.new + Params.encode(io, {"foo" => "bar", "baz" => ["quux", "quuz"]}) + io.to_s.should eq("foo=bar&baz=quux&baz=quuz") + end + it "builds from named tuple" do encoded = Params.encode({foo: "bar", baz: ["quux", "quuz"]}) encoded.should eq("foo=bar&baz=quux&baz=quuz") end + + it "builds from named tuple with IO" do + io = IO::Memory.new + encoded = Params.encode(io, {foo: "bar", baz: ["quux", "quuz"]}) + io.to_s.should eq("foo=bar&baz=quux&baz=quuz") + end end describe "#to_s" do diff --git a/src/uri/params.cr b/src/uri/params.cr index 0e9ab181af57..4670c2725681 100644 --- a/src/uri/params.cr +++ b/src/uri/params.cr @@ -94,6 +94,23 @@ class URI end end + # Appends the given key value pairs as a url-encoded URI form/query to the given `io`. + # + # ``` + # require "uri/params" + # + # io = IO::Memory.new + # URI::Params.encode(io, {"foo" => "bar", "baz" => ["quux", "quuz"]}) + # io.to_s # => "foo=bar&baz=quux&baz=quuz" + # ``` + def self.encode(io : IO, hash : Hash(String, String | Array(String))) : Nil + build(io) do |builder| + hash.each do |key, value| + builder.add key, value + end + end + end + # Returns the given key value pairs as a url-encoded URI form/query. # # ``` @@ -101,7 +118,7 @@ class URI # # URI::Params.encode({foo: "bar", baz: ["quux", "quuz"]}) # => "foo=bar&baz=quux&baz=quuz" # ``` - def self.encode(named_tuple : NamedTuple) + def self.encode(named_tuple : NamedTuple) : String build do |builder| named_tuple.each do |key, value| builder.add key.to_s, value @@ -109,6 +126,23 @@ class URI end end + # Appends the given key value pairs as a url-encoded URI form/query to the given `io`. + # + # ``` + # require "uri/params" + # + # io = IO::Memory.new + # URI::Params.encode(io, {foo: "bar", baz: ["quux", "quuz"]}) + # io.to_s # => "foo=bar&baz=quux&baz=quuz" + # ``` + def self.encode(io : IO, named_tuple : NamedTuple) : Nil + build(io) do |builder| + named_tuple.each do |key, value| + builder.add key.to_s, value + end + end + end + # Builds an url-encoded URI form/query. # # The yielded object has an `add` method that accepts two arguments, @@ -143,6 +177,11 @@ class URI end end + # :ditto: + def self.build(io : IO, *, space_to_plus : Bool = true, & : Builder ->) : Nil + yield Builder.new(io, space_to_plus: space_to_plus) + end + protected getter raw_params # Returns an empty `URI::Params`. From 1b9325744d0df52722d41ec443ca3d532636293b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 22 Sep 2023 17:43:23 +0200 Subject: [PATCH 0684/1551] Update distribution-scripts (#13776) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 769bd58805d6..33fe27c0540c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "4b3af3fb9ebeb2c74850bc7025a17a38ab82c5f4" + default: "e15cbd3b6b3e1bac1b16905f1b1a15ba6ae4e554" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From feb17ec8db6723d68f1a23a300802500c0c30fa7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 22 Sep 2023 23:45:19 +0800 Subject: [PATCH 0685/1551] Remove overflowing `Float#to_u!` interpreter primitive specs (#13737) Co-authored-by: Beta Ziliani --- spec/compiler/interpreter/primitives_spec.cr | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/spec/compiler/interpreter/primitives_spec.cr b/spec/compiler/interpreter/primitives_spec.cr index 0438c805b16e..ffdedb9b4e5c 100644 --- a/spec/compiler/interpreter/primitives_spec.cr +++ b/spec/compiler/interpreter/primitives_spec.cr @@ -270,20 +270,22 @@ describe Crystal::Repl::Interpreter do interpret("23.8_f32.to_{{target_type}}!").should eq(f.to_{{target_type}}!) end - it "interprets Float32#to_{{target_type}}! (negative)" do - f = -23.8_f32 - interpret("-23.8_f32.to_{{target_type}}!").should eq(f.to_{{target_type}}!) - end - it "interprets Float64#to_{{target_type}}! (positive)" do f = 23.8_f64 interpret("23.8_f64.to_{{target_type}}!").should eq(f.to_{{target_type}}!) end - it "interprets Float64#to_{{target_type}}! (negative)" do - f = -23.8_f64 - interpret("-23.8_f64.to_{{target_type}}!").should eq(f.to_{{target_type}}!) - end + {% unless target_type.starts_with?("u") %} # Do not test undefined behavior that might differ (#13736) + it "interprets Float32#to_{{target_type}}! (negative)" do + f = -23.8_f32 + interpret("-23.8_f32.to_{{target_type}}!").should eq(f.to_{{target_type}}!) + end + + it "interprets Float64#to_{{target_type}}! (negative)" do + f = -23.8_f64 + interpret("-23.8_f64.to_{{target_type}}!").should eq(f.to_{{target_type}}!) + end + {% end %} {% end %} it "interprets Char#ord" do From e3d79fa3c21637a180c1e2662e9b414e19945a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 22 Sep 2023 17:45:37 +0200 Subject: [PATCH 0686/1551] Refactor narrow OpenSSL requires for digest implementations (#13818) --- src/digest/md5.cr | 2 +- src/digest/sha1.cr | 2 +- src/digest/sha256.cr | 2 +- src/digest/sha512.cr | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/digest/md5.cr b/src/digest/md5.cr index 15f74b3e0b95..2a46dc03e1ce 100644 --- a/src/digest/md5.cr +++ b/src/digest/md5.cr @@ -1,5 +1,5 @@ require "./digest" -require "openssl" +require "openssl/digest" # Implements the MD5 digest algorithm. # diff --git a/src/digest/sha1.cr b/src/digest/sha1.cr index 01d96ce00bc0..0a0589d0adff 100644 --- a/src/digest/sha1.cr +++ b/src/digest/sha1.cr @@ -1,5 +1,5 @@ require "./digest" -require "openssl" +require "openssl/digest" # Implements the SHA1 digest algorithm. # diff --git a/src/digest/sha256.cr b/src/digest/sha256.cr index 4c4cc882b667..54e702b4065b 100644 --- a/src/digest/sha256.cr +++ b/src/digest/sha256.cr @@ -1,5 +1,5 @@ require "./digest" -require "openssl" +require "openssl/digest" # Implements the SHA256 digest algorithm. # diff --git a/src/digest/sha512.cr b/src/digest/sha512.cr index aba62d1ac3ec..88bb2b0e2046 100644 --- a/src/digest/sha512.cr +++ b/src/digest/sha512.cr @@ -1,5 +1,5 @@ require "./digest" -require "openssl" +require "openssl/digest" # Implements the SHA512 digest algorithm. # From 4099c7f90d369fbdc673a70ac7e239f9e0a263c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20H=C3=B6rberg?= Date: Fri, 22 Sep 2023 17:45:47 +0200 Subject: [PATCH 0687/1551] Avoid double file buffering (#13780) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/crystal/system/unix/getrandom.cr | 2 +- src/digest/digest.cr | 2 ++ src/file.cr | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/crystal/system/unix/getrandom.cr b/src/crystal/system/unix/getrandom.cr index 6b4b6de5cc09..7be835ff5ab9 100644 --- a/src/crystal/system/unix/getrandom.cr +++ b/src/crystal/system/unix/getrandom.cr @@ -33,7 +33,7 @@ module Crystal::System::Random return unless urandom.info.type.character_device? urandom.close_on_exec = true - urandom.sync = true # don't buffer bytes + urandom.read_buffering = false # don't buffer bytes @@urandom = urandom end end diff --git a/src/digest/digest.cr b/src/digest/digest.cr index e6a401e90545..c55d2a84ead1 100644 --- a/src/digest/digest.cr +++ b/src/digest/digest.cr @@ -213,6 +213,8 @@ abstract class Digest # Reads the file's content and updates the digest with it. def file(file_name : Path | String) : self File.open(file_name) do |io| + # `#update` works with big buffers so there's no need for additional read buffering in the file + io.read_buffering = false self << io end end diff --git a/src/file.cr b/src/file.cr index 19a62e6b630d..31b8e5420937 100644 --- a/src/file.cr +++ b/src/file.cr @@ -797,8 +797,10 @@ class File < IO::FileDescriptor open(filename, mode, perm, encoding: encoding, invalid: invalid) do |file| case content when Bytes + file.sync = true file.write(content) when IO + file.sync = true IO.copy(content, file) else file.print(content) From c09562a03ca640a23296511d97af9920d5d06366 Mon Sep 17 00:00:00 2001 From: Remilia Scarlet <26493542+MistressRemilia@users.noreply.github.com> Date: Fri, 22 Sep 2023 09:58:31 -0600 Subject: [PATCH 0688/1551] Fix YAML scalar type validation error message (#13771) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller Co-authored-by: yukiraven Co-authored-by: MistressRemilia <4798372-RemiliaScarlet@users.noreply.gitlab.com> --- spec/std/yaml/serializable_spec.cr | 20 ++++++++++++++++++++ src/yaml/from_yaml.cr | 11 ++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index a8a7e3558b90..91596523644f 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -745,6 +745,26 @@ describe "YAML::Serializable" do typeof(yaml.bar).should eq(Int8) end + it "checks that values fit into integer types" do + expect_raises(YAML::ParseException, /Expected Int16/) do + YAMLAttrWithSmallIntegers.from_yaml(%({"foo": 21000000, "bar": 7})) + end + + expect_raises(YAML::ParseException, /Expected Int8/) do + YAMLAttrWithSmallIntegers.from_yaml(%({"foo": 21, "bar": 7000})) + end + end + + it "checks that non-integer values for integer fields report the expected type" do + expect_raises(YAML::ParseException, /Expected Int16, not "a"/) do + YAMLAttrWithSmallIntegers.from_yaml(%({"foo": "a", "bar": 7})) + end + + expect_raises(YAML::ParseException, /Expected Int8, not "a"/) do + YAMLAttrWithSmallIntegers.from_yaml(%({"foo": 21, "bar": "a"})) + end + end + it "parses recursive" do yaml = <<-YAML --- &1 diff --git a/src/yaml/from_yaml.cr b/src/yaml/from_yaml.cr index 741ddcd98228..f40fcfb2a402 100644 --- a/src/yaml/from_yaml.cr +++ b/src/yaml/from_yaml.cr @@ -30,7 +30,8 @@ private def parse_yaml(string_or_io) end end -private def parse_scalar(ctx, node, type : T.class) forall T +private def parse_scalar(ctx, node, type : T.class, + expected_type : Class = T) forall T ctx.read_alias(node, T) do |obj| return obj end @@ -41,7 +42,7 @@ private def parse_scalar(ctx, node, type : T.class) forall T ctx.record_anchor(node, value) value else - node.raise "Expected #{T}, not #{node.value.inspect}" + node.raise "Expected #{expected_type}, not #{node.value.inspect}" end else node.raise "Expected scalar, not #{node.kind}" @@ -58,7 +59,11 @@ end {% for type in %w(Int8 Int16 Int32 Int64 UInt8 UInt16 UInt32 UInt64) %} def {{type.id}}.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) - {{type.id}}.new! parse_scalar(ctx, node, Int64) + begin + {{type.id}}.new parse_scalar(ctx, node, Int64, {{type.id}}) + rescue err : OverflowError | ArgumentError + node.raise "Expected {{type.id}}" + end end {% end %} From 2d5f314a5e24f306aadc4c686ce5d05ba0de36ba Mon Sep 17 00:00:00 2001 From: Sokolov Yura Date: Sat, 23 Sep 2023 10:21:18 +0300 Subject: [PATCH 0689/1551] Add `String#byte_index(Char)` (#13819) --- spec/std/string_spec.cr | 13 +++++++++++++ src/string.cr | 43 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 905930463cfc..d079f7618948 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -1277,6 +1277,19 @@ describe "String" do "Dizzy Miss Lizzy".byte_index('z'.ord, -17).should be_nil } + it { "foo".byte_index('o').should eq(1) } + it { "foo bar booz".byte_index('o', 3).should eq(9) } + it { "foo".byte_index('a').should be_nil } + it { "foo".byte_index('a').should be_nil } + it { "foo".byte_index('o', 3).should be_nil } + it { "Hi, 💣".byte_index('💣').should eq(4) } + it { + "Dizzy Miss Lizzy".byte_index('z').should eq(2) + "Dizzy Miss Lizzy".byte_index('z', 3).should eq(3) + "Dizzy Miss Lizzy".byte_index('z', -4).should eq(13) + "Dizzy Miss Lizzy".byte_index('z', -17).should be_nil + } + it "gets byte index of string" do "hello world".byte_index("he").should eq(0) "hello world".byte_index("lo").should eq(3) diff --git a/src/string.cr b/src/string.cr index 7c9eed3dd186..551c2d2dd9a2 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3670,6 +3670,49 @@ class String nil end + # Returns the index of the _first_ occurrence of *char* in the string, or `nil` if not present. + # If *offset* is present, it defines the position to start the search. + # + # Negative *offset* can be used to start the search from the end of the string. + # + # ``` + # "Hello, World".byte_index('o') # => 4 + # "Hello, World".byte_index('Z') # => nil + # "Hello, World".byte_index('o', 5) # => 8 + # "Hi, 💣".byte_index('💣') # => 4 + # "Dizzy Miss Lizzy".byte_index('z') # => 2 + # "Dizzy Miss Lizzy".byte_index('z', 3) # => 3 + # "Dizzy Miss Lizzy".byte_index('z', -4) # => 13 + # "Dizzy Miss Lizzy".byte_index('z', -17) # => nil + # ``` + def byte_index(char : Char, offset = 0) : Int32? + return byte_index(char.ord, offset) if char.ascii? + + offset += bytesize if offset < 0 + return if offset < 0 + return if offset + char.bytesize > bytesize + + # Simplified "Rabin-Karp" algorithm + search_hash = 0u32 + search_mask = 0u32 + hash = 0u32 + char.each_byte do |byte| + search_hash = (search_hash << 8) | byte + search_mask = (search_mask << 8) | 0xff + hash = (hash << 8) | to_unsafe[offset] + offset += 1 + end + + offset.upto(bytesize) do |i| + if (hash & search_mask) == search_hash + return i - char.bytesize + end + # rely on zero terminating byte + hash = (hash << 8) | to_unsafe[i] + end + nil + end + # Returns the byte index of *search* in the string, or `nil` if the string is not present. # If *offset* is present, it defines the position to start the search. # From b597473ceed1b07287d064bddcc4773d941ae2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 23 Sep 2023 09:21:57 +0200 Subject: [PATCH 0690/1551] infra(make): Add `generate_data` target for running generator scripts (#13700) --- Makefile | 24 ++++++++++++++++++++++++ Makefile.win | 24 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/Makefile b/Makefile index 524848ed26f7..2ddcc270bdab 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,8 @@ all: ## $ make std_spec ## Run compiler tests ## $ make compiler_spec +## Run generators (Unicode, SSL config, ...) +## $ make -B generate_data CRYSTAL ?= crystal ## which previous crystal compiler use LLVM_CONFIG ?= ## llvm-config command path to use @@ -137,6 +139,28 @@ llvm_ext: $(LLVM_EXT_OBJ) format: ## Format sources ./bin/crystal tool format$(if $(check), --check) src spec samples scripts +generate_data: ## Run generator scripts for Unicode, SSL config, ... (usually with `-B`/`--always-make` flag) + +generate_data: spec/std/string/graphemes_break_spec.cr +spec/std/string/graphemes_break_spec.cr: scripts/generate_grapheme_break_specs.cr + $(CRYSTAL) run $< + +generate_data: src/string/grapheme/properties.cr +src/string/grapheme/properties.cr: scripts/generate_grapheme_properties.cr + $(CRYSTAL) run $< + +generate_data: src/openssl/ssl/defaults.cr +src/openssl/ssl/defaults.cr: scripts/generate_ssl_server_defaults.cr + $(CRYSTAL) run $< + +generate_data: src/unicode/data.cr +src/unicode/data.cr: scripts/generate_unicode_data.cr + $(CRYSTAL) run $< + +generate_data: src/crystal/system/win32/zone_names.cr +src/crystal/system/win32/zone_names.cr: scripts/generate_windows_zone_names.cr + $(CRYSTAL) run $< + .PHONY: install install: $(O)/crystal man/crystal.1.gz ## Install the compiler at DESTDIR $(INSTALL) -d -m 0755 "$(BINDIR)/" diff --git a/Makefile.win b/Makefile.win index be923531da97..70efc2e8c426 100644 --- a/Makefile.win +++ b/Makefile.win @@ -18,6 +18,8 @@ all: ## $ make -f Makefile.win std_spec ## Run compiler tests ## $ make -f Makefile.win compiler_spec +## Run generators (Unicode, SSL config, ...) +## $ make -B generate_data CRYSTAL ?= crystal ## which previous crystal compiler use LLVM_CONFIG ?= ## llvm-config command path to use @@ -139,6 +141,28 @@ llvm_ext: $(LLVM_EXT_OBJ) format: ## Format sources .\bin\crystal tool format$(if $(check), --check) src spec samples scripts +generate_data: ## Run generator scripts for Unicode, SSL config, ... (usually with `-B`/`--always-make` flag) + +generate_data: spec\std\string\graphemes_break_spec.cr +spec\std\string\graphemes_break_spec.cr: scripts\generate_grapheme_break_specs.cr + $(CRYSTAL) run $< + +generate_data: src\string\grapheme\properties.cr +src\string\grapheme\properties.cr: scripts\generate_grapheme_properties.cr + $(CRYSTAL) run $< + +generate_data: src\openssl\ssl\defaults.cr +src\openssl\ssl\defaults.cr: scripts\generate_ssl_server_defaults.cr + $(CRYSTAL) run $< + +generate_data: src\unicode\data.cr +src\unicode\data.cr: scripts\generate_unicode_data.cr + $(CRYSTAL) run $< + +generate_data: src\crystal\system\win32\zone_names.cr +src\crystal\system\win32\zone_names.cr: scripts\generate_windows_zone_names.cr + $(CRYSTAL) run $< + .PHONY: install install: $(O)\crystal.exe ## Install the compiler at prefix $(call MKDIR,"$(BINDIR)") From 6473d3d4cfccb6e7b4ccd78815f69cbe99162538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 23 Sep 2023 09:22:08 +0200 Subject: [PATCH 0691/1551] Fix `Char::Reader#each` bounds check after block (#13817) --- spec/std/char/reader_spec.cr | 37 +++++++++++++++++++++++------------- src/char/reader.cr | 3 ++- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/spec/std/char/reader_spec.cr b/spec/std/char/reader_spec.cr index 0d34beac6d45..4d9ec1924f4f 100644 --- a/spec/std/char/reader_spec.cr +++ b/spec/std/char/reader_spec.cr @@ -70,20 +70,31 @@ describe "Char::Reader" do reader.current_char.ord.should eq(225) end - it "is an Enumerable(Char)" do - reader = Char::Reader.new("abc") - sum = 0 - reader.each do |char| - sum += char.ord - end.should be_nil - sum.should eq(294) - end + describe "#each" do + it "yields chars" do + reader = Char::Reader.new("abc") + chars = [] of Char + reader.each do |char| + chars << char + end.should be_nil + chars.should eq ['a', 'b', 'c'] + end - it "is an Enumerable(Char) but doesn't yield if empty" do - reader = Char::Reader.new("") - reader.each do |char| - fail "reader each shouldn't yield on empty string" - end.should be_nil + it "does not yield if empty" do + reader = Char::Reader.new("") + reader.each do |char| + fail "reader each shouldn't yield on empty string" + end.should be_nil + end + + it "checks bounds after block" do + string = "f" + reader = Char::Reader.new(string) + reader.each do |c| + c.should eq 'f' + reader.next_char + end + end end it "starts at end" do diff --git a/src/char/reader.cr b/src/char/reader.cr index 6213883cdc3a..ae1a20258db4 100644 --- a/src/char/reader.cr +++ b/src/char/reader.cr @@ -177,8 +177,9 @@ struct Char # C # ``` def each(&) : Nil - while has_next? + while @pos < @string.bytesize yield current_char + @pos += @current_char_width decode_current_char end From 4beaf278a59f8c2b4f0baee1c82834e7eb1ef652 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 25 Sep 2023 00:29:08 +0800 Subject: [PATCH 0692/1551] Experimental: Add `Slice.literal` for numeric slice constants (#13716) --- spec/compiler/semantic/primitives_spec.cr | 78 +++++++++++++++++++ spec/primitives/slice_spec.cr | 15 ++++ src/compiler/crystal/codegen/codegen.cr | 32 ++++++++ src/compiler/crystal/program.cr | 8 ++ src/compiler/crystal/semantic/main_visitor.cr | 47 +++++++++++ src/primitives.cr | 19 +++++ 6 files changed, 199 insertions(+) create mode 100644 spec/primitives/slice_spec.cr diff --git a/spec/compiler/semantic/primitives_spec.cr b/spec/compiler/semantic/primitives_spec.cr index 485fc80d3a58..b806361dc01d 100644 --- a/spec/compiler/semantic/primitives_spec.cr +++ b/spec/compiler/semantic/primitives_spec.cr @@ -253,4 +253,82 @@ describe "Semantic: primitives" do Bar::A.new.foo CRYSTAL end + + describe "Slice.literal" do + def_slice_literal = <<-CRYSTAL + struct Slice(T) + def initialize(pointer : T*, size : Int32, *, read_only : Bool) + end + + @[Primitive(:slice_literal)] + def self.literal(*args) + end + end + CRYSTAL + + context "with element type" do + it "types primitive int literal" do + assert_type(<<-CRYSTAL) { generic_class "Slice", uint8 } + #{def_slice_literal} + Slice(UInt8).literal(0, 1, 4, 9) + CRYSTAL + end + + it "types primitive float literal" do + assert_type(<<-CRYSTAL) { generic_class "Slice", float64 } + #{def_slice_literal} + Slice(Float64).literal(0, 1, 4, 9) + CRYSTAL + end + + it "types empty literal" do + assert_type(<<-CRYSTAL) { generic_class "Slice", int32 } + #{def_slice_literal} + Slice(Int32).literal + CRYSTAL + end + + it "errors if element type is not primitive int or float" do + assert_error <<-CRYSTAL, "Only slice literals of primitive integer or float types can be created" + #{def_slice_literal} + Slice(String).literal + CRYSTAL + + assert_error <<-CRYSTAL, "Only slice literals of primitive integer or float types can be created" + #{def_slice_literal} + Slice(Bool).literal + CRYSTAL + + assert_error <<-CRYSTAL, "Only slice literals of primitive integer or float types can be created" + #{def_slice_literal} + Slice(Int32 | Int64).literal + CRYSTAL + end + + it "errors if element is not number literal" do + assert_error <<-CRYSTAL, "Expected NumberLiteral, got StringLiteral" + #{def_slice_literal} + Slice(Int32).literal("") + CRYSTAL + + assert_error <<-CRYSTAL, "Expected NumberLiteral, got Var" + #{def_slice_literal} + x = 1 + Slice(Int32).literal(x) + CRYSTAL + end + + it "errors if element is out of range" do + assert_error <<-CRYSTAL, "Argument out of range for a Slice(UInt8)" + #{def_slice_literal} + Slice(UInt8).literal(-1) + CRYSTAL + + assert_error <<-CRYSTAL, "Argument out of range for a Slice(UInt8)" + #{def_slice_literal} + Slice(UInt8).literal(256) + CRYSTAL + end + end + end end diff --git a/spec/primitives/slice_spec.cr b/spec/primitives/slice_spec.cr new file mode 100644 index 000000000000..8e440d2ff905 --- /dev/null +++ b/spec/primitives/slice_spec.cr @@ -0,0 +1,15 @@ +require "spec" +require "../support/number" + +describe "Primitives: Slice" do + describe ".literal" do + {% for num in BUILTIN_NUMBER_TYPES %} + it {{ "creates a read-only Slice(#{num})" }} do + slice = Slice({{ num }}).literal(0, 1, 4, 9, 16, 25) + slice.should be_a(Slice({{ num }})) + slice.to_a.should eq([0, 1, 4, 9, 16, 25] of {{ num }}) + slice.read_only?.should be_true + end + {% end %} + end +end diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 2c357183ffa5..9890454ecfef 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -234,6 +234,10 @@ module Crystal symbol_table.initializer = llvm_type(@program.string).const_array(@symbol_table_values) end + program.const_slices.each do |info| + define_slice_constant(info) + end + @last = llvm_nil @fun_literal_count = 0 @@ -293,6 +297,34 @@ module Crystal llvm_mod.globals.add llvm_typer.llvm_type(@program.string).array(@symbol_table_values.size), SYMBOL_TABLE_NAME end + def define_slice_constant(info : Program::ConstSliceInfo) + args = info.args.to_unsafe + kind = info.element_type + llvm_element_type = llvm_type(@program.type_from_literal_kind(kind)) + llvm_elements = Array.new(info.args.size) do |i| + num = args[i].as(NumberLiteral) + case kind + in .i8? then llvm_element_type.const_int(num.value.to_i8) + in .i16? then llvm_element_type.const_int(num.value.to_i16) + in .i32? then llvm_element_type.const_int(num.value.to_i32) + in .i64? then llvm_element_type.const_int(num.value.to_i64) + in .i128? then llvm_element_type.const_int(num.value.to_i128) + in .u8? then llvm_element_type.const_int(num.value.to_u8) + in .u16? then llvm_element_type.const_int(num.value.to_u16) + in .u32? then llvm_element_type.const_int(num.value.to_u32) + in .u64? then llvm_element_type.const_int(num.value.to_u64) + in .u128? then llvm_element_type.const_int(num.value.to_u128) + in .f32? then llvm_element_type.const_float(num.value) + in .f64? then llvm_element_type.const_double(num.value) + end + end + + global = @llvm_mod.globals.add(llvm_element_type.array(info.args.size), info.name) + global.linkage = LLVM::Linkage::Private + global.global_constant = true + global.initializer = llvm_element_type.const_array(llvm_elements) + end + def data_layout @program.target_machine.data_layout end diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 3fa7aa37f45a..d1bf702d70b1 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -84,6 +84,14 @@ module Crystal # This pool is passed to the parser, macro expander, etc. getter string_pool = StringPool.new + record ConstSliceInfo, + name : String, + element_type : NumberKind, + args : Array(ASTNode) + + # All constant slices constructed via the `Slice.literal` primitive. + getter const_slices = [] of ConstSliceInfo + # Here we store constants, in the # order that they are used. They will be initialized as soon # as the program starts, before the main code. diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 2f84fea5d81c..712d9e8d72cf 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -2298,6 +2298,8 @@ module Crystal visit_pointer_set node when "pointer_new" visit_pointer_new node + when "slice_literal" + visit_slice_literal node when "argc" # Already typed when "argv" @@ -2417,6 +2419,51 @@ module Crystal node.type = scope.instance_type end + def visit_slice_literal(node) + call = self.call.not_nil! + + case slice_type = scope.instance_type + when GenericClassType # Slice + call.raise "TODO: implement slice_literal primitive for Slice without generic arguments" + when GenericClassInstanceType # Slice(T) + element_type = slice_type.type_vars["T"].type + kind = case element_type + when IntegerType + element_type.kind + when FloatType + element_type.kind + else + call.raise "Only slice literals of primitive integer or float types can be created" + end + + call.args.each do |arg| + arg.raise "Expected NumberLiteral, got #{arg.class_desc}" unless arg.is_a?(NumberLiteral) + arg.raise "Argument out of range for a Slice(#{element_type})" unless arg.representable_in?(element_type) + end + + # create the internal constant `$Slice:n` to hold the slice contents + const_name = "$Slice:#{@program.const_slices.size}" + const_value = Nop.new + const_value.type = @program.static_array_of(element_type, call.args.size) + const = Const.new(@program, @program, const_name, const_value) + @program.types[const_name] = const + @program.const_slices << Program::ConstSliceInfo.new(const_name, kind, call.args) + + # ::Slice.new(pointerof($Slice:n.@buffer), {{ args.size }}, read_only: true) + pointer_node = PointerOf.new(ReadInstanceVar.new(Path.new(const_name).at(node), "@buffer").at(node)).at(node) + size_node = NumberLiteral.new(call.args.size.to_s, :i32).at(node) + read_only_node = NamedArgument.new("read_only", BoolLiteral.new(true).at(node)).at(node) + extra = Call.new(Path.global("Slice").at(node), "new", [pointer_node, size_node], named_args: [read_only_node]).at(node) + + extra.accept self + node.extra = extra + node.type = slice_type + call.expanded = extra + else + node.raise "BUG: Unknown scope for slice_literal primitive" + end + end + def visit_struct_or_union_set(node) scope = @scope.as(NonGenericClassType) diff --git a/src/primitives.cr b/src/primitives.cr index bb64121da705..e01be8884dbb 100644 --- a/src/primitives.cr +++ b/src/primitives.cr @@ -254,6 +254,25 @@ struct Pointer(T) end end +struct Slice(T) + # Constructs a read-only `Slice` constant from the given *args*. The slice + # contents are stored in the program's read-only data section. + # + # `T` must be one of the `Number::Primitive` types and cannot be a union. It + # also cannot be inferred. The *args* must all be number literals that fit + # into `T`'s range, as if they are autocasted into `T`. + # + # ``` + # x = Slice(UInt8).literal(0, 1, 4, 9, 16, 25) + # x # => Slice[0, 1, 4, 9, 16, 25] + # x.read_only? # => true + # ``` + @[Experimental("Slice literals are still under development. Join the discussion at [#2886](https://github.com/crystal-lang/crystal/issues/2886).")] + @[Primitive(:slice_literal)] + def self.literal(*args) + end +end + struct Proc # Invokes this `Proc` and returns the result. # From d2929d005c37732756ed8645fad20cd40bf8c903 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 25 Sep 2023 00:29:26 +0800 Subject: [PATCH 0693/1551] Fix block parameter unpacking inside macros (#13813) --- spec/compiler/codegen/macro_spec.cr | 2 +- spec/compiler/semantic/macro_spec.cr | 19 +++++++++++++++++++ spec/spec_helper.cr | 20 +++++++++++++++++++- src/compiler/crystal/macros/interpreter.cr | 5 ++++- src/compiler/crystal/semantic/normalizer.cr | 6 ++++++ src/compiler/crystal/syntax/transformer.cr | 6 ------ 6 files changed, 49 insertions(+), 9 deletions(-) diff --git a/spec/compiler/codegen/macro_spec.cr b/spec/compiler/codegen/macro_spec.cr index 9429f2075139..bbf64946d03e 100644 --- a/spec/compiler/codegen/macro_spec.cr +++ b/spec/compiler/codegen/macro_spec.cr @@ -1880,7 +1880,7 @@ describe "Code gen: macro" do )).to_b.should eq(true) end - it "does block unpacking inside macro expression (##13707)" do + it "does block unpacking inside macro expression (#13707)" do run(%( {% begin %} {% diff --git a/spec/compiler/semantic/macro_spec.cr b/spec/compiler/semantic/macro_spec.cr index 190903a8cbba..caeba6269f34 100644 --- a/spec/compiler/semantic/macro_spec.cr +++ b/spec/compiler/semantic/macro_spec.cr @@ -1657,6 +1657,25 @@ describe "Semantic: macro" do method.location.not_nil!.expanded_location.not_nil!.line_number.should eq(9) end + it "unpacks block parameters inside macros (#13742)" do + assert_no_errors <<-CRYSTAL + macro foo + {% [{1, 2}, {3, 4}].each { |(k, v)| k } %} + end + + foo + CRYSTAL + + assert_no_errors <<-CRYSTAL + macro foo + {% [{1, 2}, {3, 4}].each { |(k, v)| k } %} + end + + foo + foo + CRYSTAL + end + it "executes OpAssign (#9356)" do assert_type(<<-CRYSTAL) { int32 } {% begin %} diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index cb43107118bb..7ca8860383de 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -10,6 +10,8 @@ require "./support/tempfile" require "./support/win32" class Crystal::Program + setter temp_var_counter + def union_of(type1, type2, type3) union_of([type1, type2, type3] of Type).not_nil! end @@ -82,7 +84,23 @@ def assert_normalize(from, to, flags = nil, *, file = __FILE__, line = __LINE__) program.flags.concat(flags.split) if flags from_nodes = Parser.parse(from) to_nodes = program.normalize(from_nodes) - to_nodes.to_s.strip.should eq(to.strip), file: file, line: line + to_nodes_str = to_nodes.to_s.strip + to_nodes_str.should eq(to.strip), file: file, line: line + + # first idempotency check: the result should be fully normalized + to_nodes_str2 = program.normalize(to_nodes).to_s.strip + unless to_nodes_str2 == to_nodes_str + fail "Idempotency failed:\nBefore: #{to_nodes_str.inspect}\nAfter: #{to_nodes_str2.inspect}", file: file, line: line + end + + # second idempotency check: if the normalizer mutates the original node, + # further normalizations should not produce a different result + program.temp_var_counter = 0 + to_nodes_str2 = program.normalize(from_nodes).to_s.strip + unless to_nodes_str2 == to_nodes_str + fail "Idempotency failed:\nBefore: #{to_nodes_str.inspect}\nAfter: #{to_nodes_str2.inspect}", file: file, line: line + end + to_nodes end diff --git a/src/compiler/crystal/macros/interpreter.cr b/src/compiler/crystal/macros/interpreter.cr index af76e087ced3..ac07bc17bb9e 100644 --- a/src/compiler/crystal/macros/interpreter.cr +++ b/src/compiler/crystal/macros/interpreter.cr @@ -358,8 +358,11 @@ module Crystal args = node.args.map { |arg| accept arg } named_args = node.named_args.try &.to_h { |arg| {arg.name, accept arg.value} } + # normalize needed for param unpacking + block = node.block.try { |b| @program.normalize(b) } + begin - @last = receiver.interpret(node.name, args, named_args, node.block, self, node.name_location) + @last = receiver.interpret(node.name, args, named_args, block, self, node.name_location) rescue ex : MacroRaiseException # Re-raise to avoid the logic in the other rescue blocks and to retain the original location raise ex diff --git a/src/compiler/crystal/semantic/normalizer.cr b/src/compiler/crystal/semantic/normalizer.cr index 6d39000c15dd..386febbf06f5 100644 --- a/src/compiler/crystal/semantic/normalizer.cr +++ b/src/compiler/crystal/semantic/normalizer.cr @@ -450,6 +450,12 @@ module Crystal unpacks = node.unpacks return node unless unpacks + # as `node` is mutated in-place, ensure it can only be mutated once + # we consider a block to be mutated if any unpack already has a + # corresponding block parameter with a name (as the fictitious packed + # parameters have empty names) + return node if unpacks.any? { |index, _| !node.args[index].name.empty? } + extra_expressions = [] of ASTNode next_unpacks = [] of {String, Expressions} diff --git a/src/compiler/crystal/syntax/transformer.cr b/src/compiler/crystal/syntax/transformer.cr index bdbd500dc98b..299e1c53c6ad 100644 --- a/src/compiler/crystal/syntax/transformer.cr +++ b/src/compiler/crystal/syntax/transformer.cr @@ -559,7 +559,6 @@ module Crystal end def transform(node : MacroExpression) - node.exp = node.exp.transform(self) node end @@ -572,15 +571,10 @@ module Crystal end def transform(node : MacroIf) - node.cond = node.cond.transform(self) - node.then = node.then.transform(self) - node.else = node.else.transform(self) node end def transform(node : MacroFor) - node.exp = node.exp.transform(self) - node.body = node.body.transform(self) node end From 71d613f521182a178a6c726fc1f32e77e27c2b3d Mon Sep 17 00:00:00 2001 From: Thomas Fini Hansen Date: Sun, 24 Sep 2023 18:29:49 +0200 Subject: [PATCH 0694/1551] Fix shard crystal version in `crystal init` (#13730) --- spec/compiler/crystal/tools/init_spec.cr | 2 +- src/compiler/crystal/tools/init/template/shard.yml.ecr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/compiler/crystal/tools/init_spec.cr b/spec/compiler/crystal/tools/init_spec.cr index 80b800b62dd8..71bbd8de9d35 100644 --- a/spec/compiler/crystal/tools/init_spec.cr +++ b/spec/compiler/crystal/tools/init_spec.cr @@ -165,7 +165,7 @@ module Crystal parsed["version"].should eq("0.1.0") parsed["authors"].should eq(["John Smith "]) parsed["license"].should eq("MIT") - parsed["crystal"].should eq(Crystal::Config.version) + parsed["crystal"].should eq(">= #{Crystal::Config.version}") parsed["targets"]?.should be_nil end diff --git a/src/compiler/crystal/tools/init/template/shard.yml.ecr b/src/compiler/crystal/tools/init/template/shard.yml.ecr index 77e8aff0ae3a..4277a2053ee3 100644 --- a/src/compiler/crystal/tools/init/template/shard.yml.ecr +++ b/src/compiler/crystal/tools/init/template/shard.yml.ecr @@ -10,6 +10,6 @@ targets: main: src/<%= config.name %>.cr <%- end -%> -crystal: <%= Crystal::Config.version %> +crystal: '>= <%= Crystal::Config.version %>' license: MIT From d74c91c89e916f8bf7aa72fb87d1a041443603c2 Mon Sep 17 00:00:00 2001 From: Margret Riegert Date: Mon, 25 Sep 2023 08:27:14 -0400 Subject: [PATCH 0695/1551] Allow word breaks between module names in docs (#13827) --- src/compiler/crystal/tools/doc/html/type.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/tools/doc/html/type.html b/src/compiler/crystal/tools/doc/html/type.html index 0e8e5bb54948..aaa3d338df9e 100644 --- a/src/compiler/crystal/tools/doc/html/type.html +++ b/src/compiler/crystal/tools/doc/html/type.html @@ -17,9 +17,9 @@

    <% if type.program? %> - <%= type.full_name %> + <%= type.full_name.gsub("::", "::") %> <% else %> - <%= type.abstract? ? "abstract " : ""%><%= type.kind %> <%= type.full_name %> + <%= type.abstract? ? "abstract " : ""%><%= type.kind %> <%= type.full_name.gsub("::", "::") %> <% end %>

    From 05209443a1ddd9e2b9b677bdc2b9e71537fa69af Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 25 Sep 2023 20:28:46 +0800 Subject: [PATCH 0696/1551] Fix incorrect overflow in `UInt64.from_yaml` (#13829) --- spec/std/yaml/serializable_spec.cr | 8 ++++---- spec/std/yaml/serialization_spec.cr | 26 ++++++++++++++++++++++---- src/yaml/from_yaml.cr | 14 ++++++++++---- src/yaml/schema/core.cr | 21 ++++++++++++++++++++- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 91596523644f..7d13f4318350 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -746,21 +746,21 @@ describe "YAML::Serializable" do end it "checks that values fit into integer types" do - expect_raises(YAML::ParseException, /Expected Int16/) do + expect_raises(YAML::ParseException, /Can't read Int16/) do YAMLAttrWithSmallIntegers.from_yaml(%({"foo": 21000000, "bar": 7})) end - expect_raises(YAML::ParseException, /Expected Int8/) do + expect_raises(YAML::ParseException, /Can't read Int8/) do YAMLAttrWithSmallIntegers.from_yaml(%({"foo": 21, "bar": 7000})) end end it "checks that non-integer values for integer fields report the expected type" do - expect_raises(YAML::ParseException, /Expected Int16, not "a"/) do + expect_raises(YAML::ParseException, /Can't read Int16/) do YAMLAttrWithSmallIntegers.from_yaml(%({"foo": "a", "bar": 7})) end - expect_raises(YAML::ParseException, /Expected Int8, not "a"/) do + expect_raises(YAML::ParseException, /Can't read Int8/) do YAMLAttrWithSmallIntegers.from_yaml(%({"foo": 21, "bar": "a"})) end end diff --git a/spec/std/yaml/serialization_spec.cr b/spec/std/yaml/serialization_spec.cr index b65667d8a504..6907bdbec594 100644 --- a/spec/std/yaml/serialization_spec.cr +++ b/spec/std/yaml/serialization_spec.cr @@ -53,14 +53,32 @@ describe "YAML serialization" do end end - it "does Int32#from_yaml" do - Int32.from_yaml("123").should eq(123) + {% for int in [Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64] %} + it "does {{ int }}.from_yaml" do + {{ int }}.from_yaml("0").should(be_a({{ int }})).should eq(0) + {{ int }}.from_yaml("123").should(be_a({{ int }})).should eq(123) + {{ int }}.from_yaml({{ int }}::MIN.to_s).should(be_a({{ int }})).should eq({{ int }}::MIN) + {{ int }}.from_yaml({{ int }}::MAX.to_s).should(be_a({{ int }})).should eq({{ int }}::MAX) + end + + it "raises if {{ int }}.from_yaml overflows" do + expect_raises(YAML::ParseException, "Can't read {{ int }}") do + {{ int }}.from_yaml(({{ int }}::MIN.to_big_i - 1).to_s) + end + expect_raises(YAML::ParseException, "Can't read {{ int }}") do + {{ int }}.from_yaml(({{ int }}::MAX.to_big_i + 1).to_s) + end + end + {% end %} + + it "does Int.from_yaml with prefixes" do Int32.from_yaml("0xabc").should eq(0xabc) Int32.from_yaml("0b10110").should eq(0b10110) + Int32.from_yaml("0777").should eq(0o777) end - it "does Int64#from_yaml" do - Int64.from_yaml("123456789123456789").should eq(123456789123456789) + it "does Int.from_yaml with underscores" do + Int32.from_yaml("1_2_34").should eq(1_2_34) end it "does String#from_yaml" do diff --git a/src/yaml/from_yaml.cr b/src/yaml/from_yaml.cr index f40fcfb2a402..e6f11e1924ba 100644 --- a/src/yaml/from_yaml.cr +++ b/src/yaml/from_yaml.cr @@ -59,10 +59,16 @@ end {% for type in %w(Int8 Int16 Int32 Int64 UInt8 UInt16 UInt32 UInt64) %} def {{type.id}}.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) - begin - {{type.id}}.new parse_scalar(ctx, node, Int64, {{type.id}}) - rescue err : OverflowError | ArgumentError - node.raise "Expected {{type.id}}" + ctx.read_alias(node, {{type.id}}) do |obj| + return obj + end + + if node.is_a?(YAML::Nodes::Scalar) + value = YAML::Schema::Core.parse_int(node, {{type.id}}) + ctx.record_anchor(node, value) + value + else + node.raise "Expected scalar, not #{node.kind}" end end {% end %} diff --git a/src/yaml/schema/core.cr b/src/yaml/schema/core.cr index 0add1e3e5bda..d8ca344ab92a 100644 --- a/src/yaml/schema/core.cr +++ b/src/yaml/schema/core.cr @@ -238,7 +238,7 @@ module YAML::Schema::Core raise YAML::ParseException.new("Invalid bool", *location) end - protected def self.parse_int(string, location) : Int64 + protected def self.parse_int(string : String, location) : Int64 return 0_i64 if string == "0" string.to_i64?(underscore: true, prefix: true, leading_zero_is_octal: true) || @@ -324,6 +324,25 @@ module YAML::Schema::Core parse_int?(string) || parse_float?(string) end + # Parses an integer of the given *type* according to the core schema. + # + # *type* must be a primitive integer type. Raises `YAML::ParseException` if + # *node* is not a valid integer or its value is outside *type*'s range. + def self.parse_int(node : Nodes::Node, type : T.class) : T forall T + {% unless Int::Primitive.union_types.includes?(T) %} + {% raise "Expected `type` to be a primitive integer type, not #{T}" %} + {% end %} + + string = node.value + return T.zero if string == "0" + + begin + T.new(string, underscore: true, prefix: true, leading_zero_is_octal: true) + rescue ex : ArgumentError + raise YAML::ParseException.new("Can't read #{T}", *node.location) + end + end + private def self.parse_int?(string) string.to_i64?(underscore: true, leading_zero_is_octal: true) end From b5065f05308e22929b795de2d38cf69554ce71e9 Mon Sep 17 00:00:00 2001 From: Margret Riegert Date: Tue, 26 Sep 2023 12:23:54 -0400 Subject: [PATCH 0697/1551] Add CSS for tables (#13822) --- .../crystal/tools/doc/html/css/style.css | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/tools/doc/html/css/style.css b/src/compiler/crystal/tools/doc/html/css/style.css index 2b91cf4ca7d9..9198b29b0d5e 100644 --- a/src/compiler/crystal/tools/doc/html/css/style.css +++ b/src/compiler/crystal/tools/doc/html/css/style.css @@ -729,6 +729,30 @@ img { max-width: 100%; } +table { + font-size: 14px; + display: block; + max-width: -moz-fit-content; + max-width: fit-content; + overflow-x: auto; + white-space: nowrap; + background: #fdfdfd; + text-align: center; + border: 1px solid #eee; + border-collapse: collapse; + padding: 0px 5px 0px 5px; +} + +table th { + padding: 10px; + letter-spacing: 1px; + border-bottom: 1px solid #eee; +} + +table td { + padding: 10px; +} + #sidebar-btn { height: 32px; width: 32px; @@ -935,13 +959,18 @@ img { color: white; } - pre { + pre, + table { color: white; background: #202020; border: 1px solid #353535; } + table th { + border-bottom: 1px solid #353535; + } + #sidebar-btn, #sidebar-btn-label { - color: white; + color: white; } } From a5e9ac803af2ba1fd47d3eb45609b08e920f89af Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 27 Sep 2023 00:24:04 +0800 Subject: [PATCH 0698/1551] Update specs for `Int::Primitive.from_json` (#13835) --- spec/std/json/serialization_spec.cr | 37 +++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/spec/std/json/serialization_spec.cr b/spec/std/json/serialization_spec.cr index 4657759481ea..34734b97d076 100644 --- a/spec/std/json/serialization_spec.cr +++ b/spec/std/json/serialization_spec.cr @@ -1,4 +1,5 @@ require "../spec_helper" +require "../../support/number" require "spec/helpers/iterate" require "json" require "big" @@ -36,22 +37,34 @@ describe "JSON serialization" do Path.from_json(%("foo/bar")).should eq(Path.new("foo/bar")) end - it "does UInt64.from_json" do - UInt64.from_json(UInt64::MAX.to_s).should eq(UInt64::MAX) - end + {% for int in BUILTIN_INTEGER_TYPES %} + it "does {{ int }}.from_json" do + {{ int }}.from_json("0").should(be_a({{ int }})).should eq(0) + {{ int }}.from_json("123").should(be_a({{ int }})).should eq(123) + {{ int }}.from_json({{ int }}::MIN.to_s).should(be_a({{ int }})).should eq({{ int }}::MIN) + {{ int }}.from_json({{ int }}::MAX.to_s).should(be_a({{ int }})).should eq({{ int }}::MAX) + end - it "does UInt128.from_json" do - UInt128.from_json(UInt128::MAX.to_s).should eq(UInt128::MAX) - end + # NOTE: "Invalid" shows up only for `Int64` + it "raises if {{ int }}.from_json overflows" do + expect_raises(JSON::ParseException, /(Can't read|Invalid) {{ int }}/) do + {{ int }}.from_json(({{ int }}::MIN.to_big_i - 1).to_s) + end + expect_raises(JSON::ParseException, /(Can't read|Invalid) {{ int }}/) do + {{ int }}.from_json(({{ int }}::MAX.to_big_i + 1).to_s) + end + end + {% end %} - it "does Int128.from_json" do - Int128.from_json(Int128::MAX.to_s).should eq(Int128::MAX) + it "errors on non-base-10 ints" do + expect_raises(JSON::ParseException) { Int32.from_json "0b1" } + expect_raises(JSON::ParseException) { Int32.from_json "0o1" } + expect_raises(JSON::ParseException) { Int32.from_json "0x1" } + expect_raises(JSON::ParseException) { Int32.from_json "01" } end - it "raises ParserException for overflow UInt64.from_json" do - expect_raises(JSON::ParseException, "Can't read UInt64 at line 0, column 0") do - UInt64.from_json("1#{UInt64::MAX}") - end + it "errors on underscores inside ints" do + expect_raises(JSON::ParseException) { Int32.from_json "1_2" } end it "does Array(Nil)#from_json" do From dcc5158705b31d58a8d04c9e42b442d6ecadc83b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 27 Sep 2023 00:24:12 +0800 Subject: [PATCH 0699/1551] Support YAML deserialization of 128-bit integers (#13834) --- spec/std/yaml/serialization_spec.cr | 3 ++- src/yaml/from_yaml.cr | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/std/yaml/serialization_spec.cr b/spec/std/yaml/serialization_spec.cr index 6907bdbec594..414d44541fab 100644 --- a/spec/std/yaml/serialization_spec.cr +++ b/spec/std/yaml/serialization_spec.cr @@ -1,4 +1,5 @@ require "../spec_helper" +require "../../support/number" require "yaml" require "big" require "big/yaml" @@ -53,7 +54,7 @@ describe "YAML serialization" do end end - {% for int in [Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64] %} + {% for int in BUILTIN_INTEGER_TYPES %} it "does {{ int }}.from_yaml" do {{ int }}.from_yaml("0").should(be_a({{ int }})).should eq(0) {{ int }}.from_yaml("123").should(be_a({{ int }})).should eq(123) diff --git a/src/yaml/from_yaml.cr b/src/yaml/from_yaml.cr index e6f11e1924ba..b9b6e7fae45c 100644 --- a/src/yaml/from_yaml.cr +++ b/src/yaml/from_yaml.cr @@ -57,7 +57,7 @@ def Bool.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) parse_scalar(ctx, node, self) end -{% for type in %w(Int8 Int16 Int32 Int64 UInt8 UInt16 UInt32 UInt64) %} +{% for type in %w(Int8 Int16 Int32 Int64 Int128 UInt8 UInt16 UInt32 UInt64 UInt128) %} def {{type.id}}.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) ctx.read_alias(node, {{type.id}}) do |obj| return obj From 0914f732c8f86ce6d4af66b37ab3e1524a9503f1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 27 Sep 2023 04:50:42 +0800 Subject: [PATCH 0700/1551] Support 128-bit integers in `JSON::PullParser#read?` (#13837) --- spec/std/json/pull_parser_spec.cr | 77 +++++++++-------------------- spec/std/json/serialization_spec.cr | 10 ++-- src/json/from_json.cr | 3 +- src/json/pull_parser.cr | 38 +++++++------- 4 files changed, 49 insertions(+), 79 deletions(-) diff --git a/spec/std/json/pull_parser_spec.cr b/spec/std/json/pull_parser_spec.cr index 8cf4127237bf..8de524e86c87 100644 --- a/spec/std/json/pull_parser_spec.cr +++ b/spec/std/json/pull_parser_spec.cr @@ -343,15 +343,17 @@ describe JSON::PullParser do assert_raw %({"foo":"bar"}) end - describe "read?" do + describe "#read?" do {% for pair in [[Int8, 1_i8], [Int16, 1_i16], [Int32, 1_i32], [Int64, 1_i64], + [Int128, "Int128.new(1)".id], [UInt8, 1_u8], [UInt16, 1_u16], [UInt32, 1_u32], [UInt64, 1_u64], + [UInt128, "UInt128.new(1)".id], [Float32, 1.0_f32], [Float64, 1.0], [String, "foo"], @@ -370,33 +372,27 @@ describe JSON::PullParser do end {% end %} - it_reads Float32::MIN - it_reads -10_f32 - it_reads 0_f32 - it_reads 10_f32 - it_reads Float32::MAX - - it_reads Float64::MIN - it_reads -10_f64 - it_reads 0_f64 - it_reads 10_f64 - it_reads Float64::MAX - - it_reads Int64::MIN - it_reads -10_i64 - it_reads 0_i64 - it_reads 10_i64 - it_reads Int64::MAX - - it "reads > Int64::MAX" do - pull = JSON::PullParser.new(Int64::MAX.to_s + "0") - pull.read?(Int64).should be_nil - end + {% for num in Int::Primitive.union_types %} + it_reads {{ num }}::MIN + {% unless num < Int::Unsigned %} + it_reads {{ num }}.new(-10) + it_reads {{ num }}.zero + {% end %} + it_reads {{ num }}.new(10) + it_reads {{ num }}::MAX + {% end %} - it "reads < Int64::MIN" do - pull = JSON::PullParser.new(Int64::MIN.to_s + "0") - pull.read?(Int64).should be_nil - end + {% for i in [8, 16, 32, 64, 128] %} + it "returns nil in place of Int{{i}} when an overflow occurs" do + JSON::PullParser.new(Int{{i}}::MAX.to_s + "0").read?(Int{{i}}).should be_nil + JSON::PullParser.new(Int{{i}}::MIN.to_s + "0").read?(Int{{i}}).should be_nil + end + + it "returns nil in place of UInt{{i}} when an overflow occurs" do + JSON::PullParser.new(UInt{{i}}::MAX.to_s + "0").read?(UInt{{i}}).should be_nil + JSON::PullParser.new("-1").read?(UInt{{i}}).should be_nil + end + {% end %} it "reads > Float32::MAX" do pull = JSON::PullParser.new(Float64::MAX.to_s) @@ -408,16 +404,6 @@ describe JSON::PullParser do pull.read?(Float32).should be_nil end - it "reads > UInt64::MAX" do - pull = JSON::PullParser.new(UInt64::MAX.to_s + "0") - pull.read?(UInt64).should be_nil - end - - it "reads == UInt64::MAX" do - pull = JSON::PullParser.new(UInt64::MAX.to_s) - pull.read?(UInt64).should eq(UInt64::MAX) - end - it "reads > Float64::MAX" do pull = JSON::PullParser.new("1" + Float64::MAX.to_s) pull.read?(Float64).should be_nil @@ -428,23 +414,6 @@ describe JSON::PullParser do pull.read?(Float64).should be_nil end - {% for pair in [[Int8, Int64::MAX], - [Int16, Int64::MAX], - [Int32, Int64::MAX], - [UInt8, -1], - [UInt16, -1], - [UInt32, -1], - [UInt64, -1], - [Float32, Float64::MAX]] %} - {% type = pair[0] %} - {% value = pair[1] %} - - it "returns nil in place of {{type}} when an overflow occurs" do - pull = JSON::PullParser.new({{value}}.to_json) - pull.read?({{type}}).should be_nil - end - {% end %} - it "doesn't accept nan or infinity" do pull = JSON::PullParser.new(%("nan")) pull.read?(Float64).should be_nil diff --git a/spec/std/json/serialization_spec.cr b/spec/std/json/serialization_spec.cr index 34734b97d076..7e320b9f81cd 100644 --- a/spec/std/json/serialization_spec.cr +++ b/spec/std/json/serialization_spec.cr @@ -437,11 +437,11 @@ describe "JSON serialization" do Union(Bool, Array(Int32)).from_json(%(true)).should be_true end - {% for type in %w(Int8 Int16 Int32 Int64 UInt8 UInt16 UInt32 UInt64).map(&.id) %} - it "deserializes union with {{type}} (fast path)" do - Union({{type}}, Array(Int32)).from_json(%(#{ {{type}}::MAX })).should eq({{type}}::MAX) - end - {% end %} + {% for type in Int::Primitive.union_types %} + it "deserializes union with {{type}} (fast path)" do + Union({{type}}, Array(Int32)).from_json({{type}}::MAX.to_s).should eq({{type}}::MAX) + end + {% end %} it "deserializes union with Float32 (fast path)" do Union(Float32, Array(Int32)).from_json(%(1)).should eq(1) diff --git a/src/json/from_json.cr b/src/json/from_json.cr index fdc18b3a9171..1c6a9e3c9c29 100644 --- a/src/json/from_json.cr +++ b/src/json/from_json.cr @@ -137,6 +137,7 @@ end "UInt128" => "u128", } %} def {{type.id}}.new(pull : JSON::PullParser) + # TODO: use `PullParser#read?` instead location = pull.location value = {% if type == "UInt64" || type == "UInt128" || type == "Int128" %} @@ -401,7 +402,7 @@ def Union.new(pull : JSON::PullParser) return pull.read_string {% end %} when .int? - {% type_order = [Int64, UInt64, Int32, UInt32, Int16, UInt16, Int8, UInt8, Float64, Float32] %} + {% type_order = [Int128, UInt128, Int64, UInt64, Int32, UInt32, Int16, UInt16, Int8, UInt8, Float64, Float32] %} {% for type in type_order.select { |t| T.includes? t } %} value = pull.read?({{type}}) return value unless value.nil? diff --git a/src/json/pull_parser.cr b/src/json/pull_parser.cr index 40bc190a72d9..a4c5b4797bd4 100644 --- a/src/json/pull_parser.cr +++ b/src/json/pull_parser.cr @@ -418,27 +418,27 @@ class JSON::PullParser read_bool if kind.bool? end - {% for type in [Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32] %} - # Reads an {{type}} value and returns it. - # - # If the value is not an integer or does not fit in a {{type}} variable, it returns `nil`. - def read?(klass : {{type}}.class) - {{type}}.new(int_value).tap { read_next } if kind.int? - rescue JSON::ParseException | OverflowError - nil - end + {% begin %} + # types that don't fit into `Int64` (integer type for `JSON::Any`)'s range + {% large_ints = [UInt64, Int128, UInt128] %} + + {% for int in Int::Primitive.union_types %} + {% is_large_int = large_ints.includes?(int) %} + + # Reads an `{{int}}` value and returns it. + # + # If the value is not an integer or does not fit in an `{{int}}`, it + # returns `nil`. + def read?(klass : {{int}}.class) : {{int}}? + if kind.int? + {{int}}.new({{ is_large_int ? "raw_value".id : "int_value".id }}).tap { read_next } + end + rescue JSON::ParseException | {{ is_large_int ? ArgumentError : OverflowError }} + nil + end + {% end %} {% end %} - # Reads an `Int64` value and returns it. - # - # If the value is not an integer or does not fin in an `Int64` variable, it returns `nil`. - def read?(klass : UInt64.class) : UInt64? - # UInt64 is a special case due to exceeding bounds of @int_value - UInt64.new(raw_value).tap { read_next } if kind.int? - rescue JSON::ParseException | ArgumentError - nil - end - # Reads an `Float32` value and returns it. # # If the value is not an integer or does not fit in an `Float32`, it returns `nil`. From 8e15d169542e94a1488bf6a8e6a802a83a1c8e07 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 27 Sep 2023 04:50:53 +0800 Subject: [PATCH 0701/1551] Add `Complex#to_i128`, `Complex#to_u128` (#13838) --- spec/std/complex_spec.cr | 5 ++--- src/complex.cr | 24 +++--------------------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/spec/std/complex_spec.cr b/spec/std/complex_spec.cr index 0524f912aa6d..fe0f1ac628ac 100644 --- a/spec/std/complex_spec.cr +++ b/spec/std/complex_spec.cr @@ -17,9 +17,8 @@ end describe "Complex" do describe "as numbers" do it_can_convert_between([Complex], [Complex]) - it_can_convert_between({{BUILTIN_NUMBER_TYPES_LTE_64}}, [Complex]) - it_can_convert_between([Complex], {{BUILTIN_NUMBER_TYPES_LTE_64}}) - # TODO pending conversion between Int128 + it_can_convert_between({{BUILTIN_NUMBER_TYPES}}, [Complex]) + it_can_convert_between([Complex], {{BUILTIN_NUMBER_TYPES}}) division_between_returns {{BUILTIN_NUMBER_TYPES}}, [Complex], Complex division_between_returns [Complex], {{BUILTIN_NUMBER_TYPES}}, Complex diff --git a/src/complex.cr b/src/complex.cr index d4e648e9c8fe..bbe7eb54e921 100644 --- a/src/complex.cr +++ b/src/complex.cr @@ -59,32 +59,14 @@ struct Complex @real end - # Returns the value as a `Float32` if possible (the imaginary part should be exactly zero), - # raises otherwise. - def to_f32 : Float32 - to_f64.to_f32 - end - # See `#to_f64`. def to_f to_f64 end - # Returns the value as an `Int64` if possible (the imaginary part should be exactly zero), - # raises otherwise. - def to_i64 : Int64 - to_f64.to_i64 - end - - delegate to_i32, to_i16, to_i8, to: to_i64 - - # Returns the value as an `UInt64` if possible (the imaginary part should be exactly zero), - # raises otherwise. - def to_u64 : UInt64 - to_f64.to_u64 - end - - delegate to_u32, to_u16, to_u8, to: to_u64 + delegate to_i128, to_i64, to_i32, to_i16, to_i8, to: to_f64 + delegate to_u128, to_u64, to_u32, to_u16, to_u8, to: to_f64 + delegate to_f32, to: to_f64 # See `#to_i32`. def to_i From 2d8ff9cb94a211c5beef1217f8b7b804cc3b8644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 26 Sep 2023 22:51:16 +0200 Subject: [PATCH 0702/1551] Add `tool unreachable` (#13783) Co-authored-by: Brian J. Cardiff Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Beta Ziliani --- man/crystal.1 | 4 + .../crystal/tools/unreachable_spec.cr | 429 ++++++++++++++++++ src/compiler/crystal/command.cr | 28 +- src/compiler/crystal/syntax/ast.cr | 9 + src/compiler/crystal/syntax/location.cr | 8 + src/compiler/crystal/tools/unreachable.cr | 180 ++++++++ 6 files changed, 653 insertions(+), 5 deletions(-) create mode 100644 spec/compiler/crystal/tools/unreachable_spec.cr create mode 100644 src/compiler/crystal/tools/unreachable.cr diff --git a/man/crystal.1 b/man/crystal.1 index cad5824c502a..5b2f2b247a3d 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -368,6 +368,10 @@ Show implementations for a given call. Use to specify the cursor position. The format for the cursor position is file:line:column. .It Cm types Show type of main variables of file. +.It Cm unreachable +Show methods that are never called. The output is a list of lines with columns +separated by tab. The first column is the location of the def, the second column +its reference name and the third column is the length in lines. .El .Pp .It diff --git a/spec/compiler/crystal/tools/unreachable_spec.cr b/spec/compiler/crystal/tools/unreachable_spec.cr new file mode 100644 index 000000000000..46a297e8359b --- /dev/null +++ b/spec/compiler/crystal/tools/unreachable_spec.cr @@ -0,0 +1,429 @@ +require "../../../spec_helper" +include Crystal + +def processed_unreachable_visitor(code) + compiler = Compiler.new + compiler.prelude = "empty" + compiler.no_codegen = true + result = compiler.compile(Compiler::Source.new(".", code), "fake-no-build") + + visitor = UnreachableVisitor.new + visitor.excludes << Path[Dir.current].to_posix.to_s + visitor.includes << "." + + process_result = visitor.process(result) + + {visitor, process_result} +end + +private def assert_unreachable(code, file = __FILE__, line = __LINE__) + expected_locations = [] of Location + + code.lines.each_with_index do |line, line_number_0| + if column_number = line.index('༓') + expected_locations << Location.new(".", line_number_0 + 1, column_number + 1) + end + end + + code = code.gsub('༓', "") + + visitor, result = processed_unreachable_visitor(code) + + result_location = result.defs.try &.compact_map(&.location).sort_by! do |loc| + {loc.filename.as(String), loc.line_number, loc.column_number} + end.map(&.to_s) + + result_location.should eq(expected_locations.map(&.to_s)), file: file, line: line +end + +# References +# +# ༓ marks the expected unreachable code to be found +# +describe "unreachable" do + it "finds top level methods" do + assert_unreachable <<-CRYSTAL + ༓def foo + 1 + end + + def bar + 2 + end + + bar + CRYSTAL + end + + it "finds instance methods" do + assert_unreachable <<-CRYSTAL + class Foo + ༓def foo + 1 + end + + def bar + 2 + end + end + + Foo.new.bar + CRYSTAL + end + + it "finds class methods" do + assert_unreachable <<-CRYSTAL + class Foo + ༓def self.foo + 1 + end + + def self.bar + 2 + end + end + + Foo.bar + CRYSTAL + end + + it "finds instance methods in nested types" do + assert_unreachable <<-CRYSTAL + module Mod + class Foo + ༓def foo + 1 + end + + def bar + 2 + end + end + end + + Mod::Foo.new.bar + CRYSTAL + end + + it "finds initializer" do + assert_unreachable <<-CRYSTAL + class Foo + ༓def initialize + end + end + + class Bar + def initialize + end + end + + Bar.new + CRYSTAL + end + + it "finds method with free variable" do + assert_unreachable <<-CRYSTAL + ༓def foo(u : U) forall U + end + + def bar(u : U) forall U + end + + bar(1) + CRYSTAL + end + + it "finds yielding methods" do + assert_unreachable <<-CRYSTAL + ༓def foo + yield + end + + def bar + yield + end + + bar {} + CRYSTAL + end + + it "finds method called from block" do + assert_unreachable <<-CRYSTAL + ༓def foo + end + + def bar + end + + def baz + yield + end + + baz do + bar + end + CRYSTAL + end + + it "finds method called from proc" do + assert_unreachable <<-CRYSTAL + ༓def foo + end + + def bar + end + + def baz(&proc : ->) + proc.call + end + + baz do + bar + end + CRYSTAL + end + + it "finds methods with proc parameter" do + assert_unreachable <<-CRYSTAL + ༓def foo(&proc : ->) + proc.call + end + + def bar(&proc : ->) + proc.call + end + + bar {} + CRYSTAL + end + + it "finds shadowed method" do + assert_unreachable <<-CRYSTAL + ༓def foo + end + + ༓def foo + end + + ༓def bar + end + + def bar + end + + bar + CRYSTAL + end + + it "finds method with `previous_def`" do + assert_unreachable <<-CRYSTAL + ༓def foo + end + + ༓def foo + previous_def + end + + def bar + end + + def bar + previous_def + end + + bar + CRYSTAL + end + + it "finds methods called from reachable code" do + assert_unreachable <<-CRYSTAL + ༓def qux_foo + end + + ༓def foo + qux_foo + end + + def qux_bar + end + + def bar + qux_bar + end + + bar + CRYSTAL + end + + it "finds method with `super`" do + assert_unreachable <<-CRYSTAL + class Foo + ༓def foo + end + + def bar + end + end + + class Qux < Foo + ༓def foo + super + end + + def bar + super + end + end + + Qux.new.bar + CRYSTAL + end + + it "finds methods in generic type" do + assert_unreachable <<-CRYSTAL + class Foo(T) + ༓def foo + 1 + end + + def bar + 2 + end + end + + Foo(Int32).new.bar + CRYSTAL + end + + it "finds method in abstract type" do + assert_unreachable <<-CRYSTAL + abstract class Foo + ༓def foo + end + + def bar + end + end + + class Baz < Foo + end + + Baz.new.bar + CRYSTAL + end + + # TODO: Should abstract Foo#bar be reported as well? + it "finds abstract method" do + assert_unreachable <<-CRYSTAL + abstract class Foo + abstract def foo + + abstract def bar + end + + class Baz < Foo + ༓def foo + end + + def bar + end + end + + Baz.new.bar + CRYSTAL + end + + it "finds virtual method" do + assert_unreachable <<-CRYSTAL + abstract class Foo + ༓def foo + end + + def bar + end + end + + class Baz < Foo + end + + class Qux < Foo + ༓def foo + end + + def bar + end + end + + Baz.new.as(Baz | Qux).bar + CRYSTAL + end + + it "ignores autogenerated enum predicates" do + assert_unreachable <<-CRYSTAL + enum Foo + BAR + BAZ + + ༓def foo + end + end + CRYSTAL + end + + it "finds method called from instance variable initializer" do + assert_unreachable <<-CRYSTAL + ༓def foo + end + + def bar + 1 + end + + class Foo + @status = Bar.new + @other : Int32 = bar + end + + class Bar + def initialize + end + end + + Foo.new + CRYSTAL + end + + it "finds method called from expanded macro" do + assert_unreachable <<-CRYSTAL + ༓def foo + end + + def bar + end + + macro bar_macro + bar + end + + def go(&block) + block.call + end + + go { bar_macro } + CRYSTAL + end + + it "finds method called from expanded macro expression" do + assert_unreachable <<-CRYSTAL + ༓def foo + end + + def bar + end + + {% begin %} + bar + {% end %} + CRYSTAL + end +end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 64622c37a315..c8a743a1800f 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -44,6 +44,7 @@ class Crystal::Command format format project, directories and/or files hierarchy show type hierarchy implementations show implementations for given call in location + unreachable show methods that are never called types show type of main variables --help, -h show this help USAGE @@ -187,6 +188,9 @@ class Crystal::Command when "types".starts_with?(tool) options.shift types + when "unreachable".starts_with?(tool) + options.shift + unreachable when "--help" == tool, "-h" == tool puts COMMANDS_USAGE exit @@ -239,8 +243,8 @@ class Crystal::Command end end - private def compile_no_codegen(command, wants_doc = false, hierarchy = false, no_cleanup = false, cursor_command = false, top_level = false) - config = create_compiler command, no_codegen: true, hierarchy: hierarchy, cursor_command: cursor_command + private def compile_no_codegen(command, wants_doc = false, hierarchy = false, no_cleanup = false, cursor_command = false, top_level = false, path_filter = false) + config = create_compiler command, no_codegen: true, hierarchy: hierarchy, cursor_command: cursor_command, path_filter: path_filter config.compiler.no_codegen = true config.compiler.no_cleanup = no_cleanup config.compiler.wants_doc = wants_doc @@ -337,7 +341,9 @@ class Crystal::Command hierarchy_exp : String?, cursor_location : String?, output_format : String?, - combine_rpath : Bool do + combine_rpath : Bool, + includes : Array(String), + excludes : Array(String) do def compile(output_filename = self.output_filename) compiler.emit_base_filename = emit_base_filename || output_filename.rchop(File.extname(output_filename)) compiler.compile sources, output_filename, combine_rpath: combine_rpath @@ -350,7 +356,7 @@ class Crystal::Command private def create_compiler(command, no_codegen = false, run = false, hierarchy = false, cursor_command = false, - single_file = false) + single_file = false, path_filter = false) compiler = new_compiler compiler.progress_tracker = @progress_tracker link_flags = [] of String @@ -363,6 +369,8 @@ class Crystal::Command hierarchy_exp = nil cursor_location = nil output_format = nil + excludes = [] of String + includes = [] of String option_parser = parse_with_crystal_opts do |opts| opts.banner = "Usage: crystal #{command} [options] [programfile] [--] [arguments]\n\nOptions:" @@ -420,6 +428,16 @@ class Crystal::Command exit end + if path_filter + opts.on("-i ", "--include ", "Include path") do |f| + includes << f + end + + opts.on("-e ", "--exclude ", "Exclude path (default: lib)") do |f| + excludes << f + end + end + unless no_codegen opts.on("--ll", "Dump ll to Crystal's cache directory") do compiler.dump_ll = true @@ -559,7 +577,7 @@ class Crystal::Command end combine_rpath = run && !no_codegen - @config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format, combine_rpath + @config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format, combine_rpath, includes, excludes end private def gather_sources(filenames) diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index f94685f1f597..7b37c369927f 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -34,6 +34,11 @@ module Crystal self end + # Returns the number of lines between start and end locations + def length : Int32? + Location.lines(location, end_location) + end + # Returns a deep copy of this node. Copied nodes retain # the location and end location of the original nodes. def clone @@ -1115,6 +1120,10 @@ module Crystal end def_equals_and_hash @name, @args, @body, @receiver, @block_arg, @return_type, @macro_def, @block_arity, @abstract, @splat_index, @double_splat + + def autogenerated? + location == body.location + end end class Macro < ASTNode diff --git a/src/compiler/crystal/syntax/location.cr b/src/compiler/crystal/syntax/location.cr index d035177d9d76..de40526faae7 100644 --- a/src/compiler/crystal/syntax/location.cr +++ b/src/compiler/crystal/syntax/location.cr @@ -97,4 +97,12 @@ class Crystal::Location nil end end + + # Returns the number of lines between start and finish locations. + def self.lines(start, finish) + return unless start && finish && start.filename == finish.filename + start, finish = finish, start if finish < start + + finish.line_number - start.line_number + 1 + end end diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr new file mode 100644 index 000000000000..f9e80db7bc66 --- /dev/null +++ b/src/compiler/crystal/tools/unreachable.cr @@ -0,0 +1,180 @@ +require "../syntax/ast" +require "../compiler" +require "json" + +module Crystal + class Command + private def unreachable + config, result = compile_no_codegen "tool unreachable", path_filter: true + format = config.output_format + + unreachable = UnreachableVisitor.new + + unreachable.includes.concat config.includes.map { |path| ::Path[path].expand.to_posix.to_s } + + unreachable.excludes.concat CrystalPath.default_paths.map { |path| ::Path[path].expand.to_posix.to_s } + unreachable.excludes.concat config.excludes.map { |path| ::Path[path].expand.to_posix.to_s } + + defs = unreachable.process(result) + defs.defs.sort_by! do |a_def| + location = a_def.location.not_nil! + { + location.filename.as(String), + location.line_number, + location.column_number, + } + end + + case format + when "json" + defs.to_json(STDOUT) + else + defs.to_text(STDOUT) + end + end + end + + record UnreachableResult, defs : Array(Def) do + include JSON::Serializable + + def to_text(io) + defs.each do |a_def| + io << a_def.location << "\t" + io << a_def.short_reference << "\t" + io << a_def.length << " lines" + io.puts + end + end + + def to_json(builder : JSON::Builder) + builder.array do + defs.each do |a_def| + builder.object do + builder.field "name", a_def.short_reference + builder.field "location", a_def.location.to_s + if lines = a_def.length + builder.field "lines", lines + end + end + end + end + end + end + + # This visitor walks the entire reachable code tree and collect locations + # of all defs that are a target of a call into `@used_def_locations`. + # The locations are filtered to only those that we're interested in per + # `@includes` and `@excludes`. + # Then it traverses all types and their defs and reports those that are not + # in `@used_def_locations` (and match the filter). + class UnreachableVisitor < Visitor + @used_def_locations = Set(Location).new + @defs : Set(Def) = Set(Def).new.compare_by_identity + @visited_defs : Set(Def) = Set(Def).new.compare_by_identity + + property includes = [] of String + property excludes = [] of String + + def process_type(type) + if type.is_a?(ModuleType) + track_unused_defs type + end + + type.types?.try &.each_value do |inner_type| + process_type(inner_type) + end + + process_type(type.metaclass) if type.metaclass != type + end + + def process(result : Compiler::Result) + @defs.clear + + result.node.accept(self) + + process_type(result.program) + + UnreachableResult.new @defs.to_a + end + + def visit(node) + true + end + + def visit(node : ExpandableNode) + if expanded = node.expanded + expanded.accept self + end + + true + end + + def visit(node : Call) + if expanded = node.expanded + expanded.accept(self) + + return true + end + + node.target_defs.try &.each do |a_def| + if (location = a_def.location) + @used_def_locations << location if interested_in(location) + end + + if @visited_defs.add?(a_def) + a_def.body.accept(self) + end + end + + true + end + + def visit(node : ClassDef) + node.resolved_type.instance_vars_initializers.try &.each do |initializer| + initializer.value.accept(self) + end + + true + end + + private def track_unused_defs(module_type : ModuleType) + module_type.defs.try &.each_value.each do |defs_with_meta| + defs_with_meta.each do |def_with_meta| + check_def(def_with_meta.def) + end + end + end + + private def check_def(a_def : Def) + return if a_def.abstract? + return if a_def.autogenerated? + return unless interested_in(a_def.location) + + previous = a_def.previous.try(&.def) + + check_def(previous) if previous && !a_def.calls_previous_def? + + return if @used_def_locations.includes?(a_def.location) + + check_def(previous) if previous && a_def.calls_previous_def? + + @defs << a_def + end + + private def interested_in(location) + if filename = location.try(&.filename).as?(String) + match_path?(filename) + end + end + + def match_path?(path) + paths = ::Path[path].parents << ::Path[path] + + match_any_pattern?(includes, paths) || !match_any_pattern?(excludes, paths) + end + + private def match_any_pattern?(patterns, paths) + patterns.any? { |pattern| paths.any? { |path| path == pattern || File.match?(pattern, path.to_posix) } } + end + end +end From 09d71d482bf4bad2e931d6dce332666c7e18b72d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 27 Sep 2023 10:26:59 +0200 Subject: [PATCH 0703/1551] Feature: Add `crystal tool dependencies` (#13631) Co-authored-by: Quinton Miller Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Caspian Baska --- etc/completion.bash | 2 +- etc/completion.fish | 12 ++ etc/completion.zsh | 17 ++ man/crystal.1 | 23 ++- src/compiler/crystal/command.cr | 42 ++++- src/compiler/crystal/compiler.cr | 2 + src/compiler/crystal/program.cr | 16 ++ .../crystal/semantic/semantic_visitor.cr | 41 ++-- src/compiler/crystal/tools/dependencies.cr | 178 ++++++++++++++++++ 9 files changed, 308 insertions(+), 25 deletions(-) create mode 100644 src/compiler/crystal/tools/dependencies.cr diff --git a/etc/completion.bash b/etc/completion.bash index d36a6d282a07..d8731c65bff7 100644 --- a/etc/completion.bash +++ b/etc/completion.bash @@ -66,7 +66,7 @@ _crystal() _crystal_compgen_options "${opts}" "${cur}" else if [[ "${prev}" == "tool" ]] ; then - local subcommands="context format hierarchy implementations types" + local subcommands="context dependencies format hierarchy implementations types" _crystal_compgen_options "${subcommands}" "${cur}" else _crystal_compgen_sources "${cur}" diff --git a/etc/completion.fish b/etc/completion.fish index 5bdb8532fec7..150dd37108d8 100644 --- a/etc/completion.fish +++ b/etc/completion.fish @@ -149,6 +149,18 @@ complete -c crystal -n "__fish_seen_subcommand_from context" -s p -l progress -d complete -c crystal -n "__fish_seen_subcommand_from context" -s t -l time -d "Enable execution time output" complete -c crystal -n "__fish_seen_subcommand_from context" -l stdin-filename -d "Source file name to be read from STDIN" +complete -c crystal -n "__fish_seen_subcommand_from tool; and not __fish_seen_subcommand_from $tool_subcommands" -a "dependencies" -d "show tree of required source files" -x +complete -c crystal -n "__fish_seen_subcommand_from context" -s i -l include -d "Include path in output" +complete -c crystal -n "__fish_seen_subcommand_from context" -s e -l exclude -d "Exclude path in output" +complete -c crystal -n "__fish_seen_subcommand_from context" -s D -l define -d "Define a compile-time flag" +complete -c crystal -n "__fish_seen_subcommand_from context" -s f -l format -d "Output format 'tree' (default), 'flat', 'dot', or 'mermaid'." -a "tree flat dot mermaid" -f +complete -c crystal -n "__fish_seen_subcommand_from context" -l error-trace -d "Show full error trace" +complete -c crystal -n "__fish_seen_subcommand_from context" -l no-color -d "Disable colored output" +complete -c crystal -n "__fish_seen_subcommand_from context" -l prelude -d "Use given file as prelude" +complete -c crystal -n "__fish_seen_subcommand_from context" -s s -l stats -d "Enable statistics output" +complete -c crystal -n "__fish_seen_subcommand_from context" -s p -l progress -d "Enable progress output" +complete -c crystal -n "__fish_seen_subcommand_from context" -s t -l time -d "Enable execution time output" + complete -c crystal -n "__fish_seen_subcommand_from tool; and not __fish_seen_subcommand_from $tool_subcommands" -a "expand" -d "show macro expansion for given location" -x complete -c crystal -n "__fish_seen_subcommand_from expand" -s D -l define -d "Define a compile-time flag" complete -c crystal -n "__fish_seen_subcommand_from expand" -s c -l cursor -d "Cursor location with LOC as path/to/file.cr:line:column" diff --git a/etc/completion.zsh b/etc/completion.zsh index 783b9f3cd295..bf57d80208df 100644 --- a/etc/completion.zsh +++ b/etc/completion.zsh @@ -61,6 +61,11 @@ local -a cursor_args; cursor_args=( '(-c --cursor)'{-c,--cursor}'[cursor location with LOC as path/to/file.cr:line:column]:LOC' ) +local -a include_exclude_args; cursor_args=( + '(-i --include)'{-i,--include}'[Include path in output]' \ + '(-i --exclude)'{-i,--exclude}'[Exclude path in output]' +) + local -a programfile; programfile='*:Crystal File:_files -g "*.cr(.)"' # TODO make 'emit' allow completion with more than one @@ -158,6 +163,7 @@ _crystal-tool() { commands=( "context:show context for given location" + "dependencies:show tree of required source files" "expand:show macro expansion for given location" "format:format project, directories and/or files" "hierarchy:show type hierarchy" @@ -183,6 +189,17 @@ _crystal-tool() { $cursor_args ;; + (dependencies) + _arguments \ + $programfile \ + $help_args \ + $no_color_args \ + $exec_args \ + '(-f --format)'{-f,--format}'[output format 'tree' (default), 'flat', 'dot', or 'mermaid']:' \ + $prelude_args \ + $include_exclude_args + ;; + (expand) _arguments \ $programfile \ diff --git a/man/crystal.1 b/man/crystal.1 index 5b2f2b247a3d..5f08ce29d07e 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -346,12 +346,33 @@ Disable colored output. .Op -- .Op arguments .Pp -Run a tool. The available tools are: context, format, hierarchy, implementations, and types. +Run a tool. The available tools are: context, dependencies, format, hierarchy, implementations, and types. .Pp Tools: .Bl -tag -offset indent .It Cm context Show context for given location. +.It Cm dependencies +Show tree of required source files. +.Pp +Options: +.Bl -tag -width "12345678" -compact +.Pp +.It Fl D Ar FLAG, Fl -define= Ar FLAG +Define a compile-time flag. This is useful to conditionally define types, methods, or commands based on flags available at compile time. The default flags are from the target triple given with --target-triple or the hosts default, if none is given. +.It Fl f Ar FORMAT, Fl -format= Ar FORMAT +Output format 'tree' (default), 'flat', 'dot', or 'mermaid'. +.It Fl i Ar PATH, Fl -include= Ar PATH +Include path in output. +.It Fl e Ar PATH, Fl -exclude= Ar PATH +Exclude path in output. +.It Fl -error-trace +Show full error trace. +.It Fl -prelude +Specify prelude to use. The default one initializes the garbage collector. You can also use --prelude=empty to use no preludes. This can be useful for checking code generation for a specific source code file. +.It Fl -verbose +Show skipped and heads of filtered paths +.El .It Cm expand Show macro expansion for given location. .It Cm format diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index c8a743a1800f..1540aafa9a46 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -43,6 +43,7 @@ class Crystal::Command expand show macro expansion for given location format format project, directories and/or files hierarchy show type hierarchy + dependencies show file dependency tree implementations show implementations for given call in location unreachable show methods that are never called types show type of main variables @@ -182,6 +183,9 @@ class Crystal::Command when "hierarchy".starts_with?(tool) options.shift hierarchy + when "dependencies".starts_with?(tool) + options.shift + dependencies when "implementations".starts_with?(tool) options.shift implementations @@ -341,9 +345,11 @@ class Crystal::Command hierarchy_exp : String?, cursor_location : String?, output_format : String?, + dependency_output_format : DependencyPrinter::Format, combine_rpath : Bool, includes : Array(String), - excludes : Array(String) do + excludes : Array(String), + verbose : Bool do def compile(output_filename = self.output_filename) compiler.emit_base_filename = emit_base_filename || output_filename.rchop(File.extname(output_filename)) compiler.compile sources, output_filename, combine_rpath: combine_rpath @@ -356,7 +362,8 @@ class Crystal::Command private def create_compiler(command, no_codegen = false, run = false, hierarchy = false, cursor_command = false, - single_file = false, path_filter = false) + single_file = false, dependencies = false, + path_filter = false) compiler = new_compiler compiler.progress_tracker = @progress_tracker link_flags = [] of String @@ -369,8 +376,10 @@ class Crystal::Command hierarchy_exp = nil cursor_location = nil output_format = nil + dependency_output_format = nil excludes = [] of String includes = [] of String + verbose = false option_parser = parse_with_crystal_opts do |opts| opts.banner = "Usage: crystal #{command} [options] [programfile] [--] [arguments]\n\nOptions:" @@ -414,8 +423,27 @@ class Crystal::Command end end - opts.on("-f text|json", "--format text|json", "Output format text (default) or json") do |f| - output_format = f + if dependencies + opts.on("-f tree|flat|dot|mermaid", "--format tree|flat|dot|mermaid", "Output format tree (default), flat, dot, or mermaid") do |f| + dependency_output_format = DependencyPrinter::Format.parse?(f) + error "Invalid format: #{f}. Options are: tree, flat, dot, or mermaid" unless dependency_output_format + end + + opts.on("-i ", "--include ", "Include path") do |f| + includes << f + end + + opts.on("-e ", "--exclude ", "Exclude path (default: lib)") do |f| + excludes << f + end + + opts.on("--verbose", "Show skipped and filtered paths") do + verbose = true + end + else + opts.on("-f text|json", "--format text|json", "Output format text (default) or json") do |f| + output_format = f + end end opts.on("--error-trace", "Show full error trace") do @@ -561,6 +589,8 @@ class Crystal::Command end end + dependency_output_format ||= DependencyPrinter::Format::Tree + output_format ||= "text" unless output_format.in?("text", "json") error "You have input an invalid format, only text and JSON are supported" @@ -577,7 +607,9 @@ class Crystal::Command end combine_rpath = run && !no_codegen - @config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format, combine_rpath, includes, excludes + @config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, + arguments, specified_output, hierarchy_exp, cursor_location, output_format, + dependency_output_format.not_nil!, combine_rpath, includes, excludes, verbose end private def gather_sources(filenames) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index f32ecd344be1..1099569c97aa 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -144,6 +144,8 @@ module Crystal # Whether to link statically property? static = false + property dependency_printer : DependencyPrinter? = nil + # Program that was created for the last compilation. property! program : Program diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index d1bf702d70b1..97808279061c 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -462,6 +462,22 @@ module Crystal recorded_requires << RecordedRequire.new(filename, relative_to) end + def run_requires(node : Require, filenames) : Nil + dependency_printer = compiler.try(&.dependency_printer) + + filenames.each do |filename| + unseen_file = requires.add?(filename) + + dependency_printer.try(&.enter_file(filename, unseen_file)) + + if unseen_file + yield filename + end + + dependency_printer.try(&.leave_file) + end + end + # Finds *filename* in the configured CRYSTAL_PATH for this program, # relative to *relative_to*. def find_in_path(filename, relative_to = nil) : Array(String)? diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index 0b686db9dc40..b85fdba37109 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -69,25 +69,11 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor if filenames nodes = Array(ASTNode).new(filenames.size) - filenames.each do |filename| - if @program.requires.add?(filename) - parser = @program.new_parser(File.read(filename)) - parser.filename = filename - parser.wants_doc = @program.wants_doc? - begin - parsed_nodes = parser.parse - parsed_nodes = @program.normalize(parsed_nodes, inside_exp: inside_exp?) - # We must type the node immediately, in case a file requires another - # *before* one of the files in `filenames` - parsed_nodes.accept self - rescue ex : CodeError - node.raise "while requiring \"#{node.string}\"", ex - rescue ex - raise Error.new "while requiring \"#{node.string}\"", ex - end - nodes << FileNode.new(parsed_nodes, filename) - end + + @program.run_requires(node, filenames) do |filename| + nodes << require_file(node, filename) end + expanded = Expressions.from(nodes) else expanded = Nop.new @@ -98,6 +84,25 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor false end + private def require_file(node : Require, filename : String) + parser = @program.new_parser(File.read(filename)) + parser.filename = filename + parser.wants_doc = @program.wants_doc? + begin + parsed_nodes = parser.parse + parsed_nodes = @program.normalize(parsed_nodes, inside_exp: inside_exp?) + # We must type the node immediately, in case a file requires another + # *before* one of the files in `filenames` + parsed_nodes.accept self + rescue ex : CodeError + node.raise "while requiring \"#{node.string}\"", ex + rescue ex + raise Error.new "while requiring \"#{node.string}\"", ex + end + + FileNode.new(parsed_nodes, filename) + end + def visit(node : ClassDef) check_outside_exp node, "declare class" pushing_type(node.resolved_type) do diff --git a/src/compiler/crystal/tools/dependencies.cr b/src/compiler/crystal/tools/dependencies.cr new file mode 100644 index 000000000000..745e7339f0ec --- /dev/null +++ b/src/compiler/crystal/tools/dependencies.cr @@ -0,0 +1,178 @@ +require "set" +require "colorize" +require "../syntax/ast" + +class Crystal::Command + private def dependencies + config = create_compiler "tool dependencies", no_codegen: true, dependencies: true + + dependency_printer = DependencyPrinter.create(STDOUT, format: config.dependency_output_format, verbose: config.verbose) + + dependency_printer.includes.concat config.includes.map { |path| ::Path[path].expand.to_s } + dependency_printer.excludes.concat config.excludes.map { |path| ::Path[path].expand.to_s } + config.compiler.dependency_printer = dependency_printer + + dependency_printer.start_format + config.compiler.top_level_semantic config.sources + dependency_printer.end_format + end +end + +module Crystal + abstract class DependencyPrinter + enum Format + Flat + Tree + Dot + Mermaid + end + + @stack = [] of String + @filter_depth = Int32::MAX + + @format : Format + + property includes = [] of String + property excludes = [] of String + + getter default_paths : Array(::Path) = CrystalPath.default_paths.map { |path| ::Path[path].expand } + + def self.create(io : IO, format : Format = Format::Tree, verbose : Bool = false) + case format + in .flat?, .tree? + List.new(io, format, verbose) + in .dot? + Dot.new(io, format, verbose) + in .mermaid? + Mermaid.new(io, format, verbose) + end + end + + def initialize(@io : IO, @format : Format = Format::Tree, @verbose : Bool = false) + end + + def enter_file(filename : String, unseen : Bool) + if @stack.size <= @filter_depth + filter = filter?(filename) + + if filter + @filter_depth = @stack.size + else + @filter_depth = Int32::MAX + end + + if (unseen && !filter) || @verbose + print_indent + + print_file(filename, @stack.last?, filter, unseen) + end + end + + @stack << filename + end + + def leave_file + @stack.pop + end + + def start_format + end + + private def print_indent + end + + private abstract def print_file(filename, parent, filter, unseen) + + def end_format + end + + private def edge_comment(filter = false, unseen = false) + if unseen + "filtered" if filter + else + "duplicate skipped" + end + end + + private def path(filename) + ::Path[filename].relative_to?(Dir.current) || filename + end + + private def filter?(filename) + paths = ::Path[filename].parents + paths << ::Path[filename] + + return false if match_patterns?(includes, paths) + + return true if default_paths.any? { |path| paths.includes?(path) } + + match_patterns?(excludes, paths) + end + + private def match_patterns?(patterns, paths) + patterns.any? { |pattern| paths.any? { |path| File.match?(pattern, path) } } + end + + class List < DependencyPrinter + private def print_file(filename, parent, filter, unseen) + @io.print path(filename) + if comment = edge_comment(filter, unseen) + @io.print " " + @io.print comment + end + @io.puts + end + + private def print_indent + @io.print " " * @stack.size unless @stack.empty? + end + end + + class Dot < DependencyPrinter + def start_format + @io.puts "digraph G {" + end + + def end_format + @io.puts "}" + end + + private def print_file(filename, parent, filter, unseen) + return unless parent + + @io.print " " + @io.print path(parent) + @io.print " -> " + @io.print path(filename) + if comment = edge_comment(filter, unseen) + @io.print %( [label="#{comment}"]) + end + @io.puts + end + + private def path(filename) + super.to_s.inspect + end + end + + class Mermaid < DependencyPrinter + def start_format + @io.puts "graph LR" + end + + private def print_file(filename, parent, filter, unseen) + return unless parent + + @io.print " " + @io.print path(parent) + @io.print " -->" + if comment = edge_comment(filter, unseen) + @io.print "|#{comment}|" + end + @io.print " " + @io.print path(filename) + @io.puts + end + end + end +end From e291c62c1418ee840811208282feb327433dea82 Mon Sep 17 00:00:00 2001 From: Evan Paterakis Date: Wed, 27 Sep 2023 11:27:14 +0300 Subject: [PATCH 0704/1551] Fix docs dark mode dropdown background on blink (#13840) --- src/compiler/crystal/tools/doc/html/css/style.css | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/compiler/crystal/tools/doc/html/css/style.css b/src/compiler/crystal/tools/doc/html/css/style.css index 9198b29b0d5e..3d0a8a5f3b2c 100644 --- a/src/compiler/crystal/tools/doc/html/css/style.css +++ b/src/compiler/crystal/tools/doc/html/css/style.css @@ -1,3 +1,7 @@ +:root { + color-scheme: light dark; +} + html, body { background: #FFFFFF; position: relative; @@ -818,10 +822,6 @@ table td { } @media (prefers-color-scheme: dark) { - :root { - color-scheme: dark; - } - html, body { background: #1b1b1b; } @@ -848,6 +848,10 @@ table td { border: 1px solid #353535; } + .project-versions-nav > option { + background-color: #222; + } + .superclass-hierarchy .superclass a, .superclass-hierarchy .superclass a:visited, .other-type a, From 6b9ad16c98f0289a90a6cc74493a83898050af6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 27 Sep 2023 10:27:23 +0200 Subject: [PATCH 0705/1551] Minor fixup for `HTML.decode_codepoint` (#13843) --- src/html.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/html.cr b/src/html.cr index b3943fb82cb9..75ff1c246f02 100644 --- a/src/html.cr +++ b/src/html.cr @@ -162,7 +162,7 @@ module HTML case codepoint when 0x80..0x9F # Replace characters from Windows-1252 with UTF-8 equivalents. - CHARACTER_REPLACEMENTS[codepoint - 0x80].to_s + CHARACTER_REPLACEMENTS[codepoint - 0x80] when 0, .>(Char::MAX_CODEPOINT), 0xD800..0xDFFF # unicode surrogate characters @@ -175,7 +175,7 @@ module HTML (0xFDD0..0xFDEF).includes?(codepoint) || # last two of each plane (nonchars) disallowed codepoint & 0xFFFF >= 0xFFFE || - # unicode control characters expect space + # unicode control characters except space (codepoint < 0x0020 && !codepoint.in?(0x0009, 0x000A, 0x000C)) codepoint.unsafe_chr end From 95747506d08de43e70b26c20785409e6f778d450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 28 Sep 2023 14:56:55 +0200 Subject: [PATCH 0706/1551] [CI] Refactor `crystal_bootstrap_version` (#13845) --- .github/workflows/linux.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c9d00e9084d0..8be77f17ff05 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,20 +19,24 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.2.2, 1.3.2, 1.4.1, 1.5.1, 1.6.2] - flags: ["USE_PCRE1=true"] + crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2] + flags: [""] include: # libffi is only available starting from the 1.2.2 build images - crystal_bootstrap_version: 1.0.0 flags: "FLAGS=-Dwithout_ffi USE_PCRE1=true" - crystal_bootstrap_version: 1.1.1 flags: "FLAGS=-Dwithout_ffi USE_PCRE1=true" - - crystal_bootstrap_version: 1.7.3 - flags: "" - - crystal_bootstrap_version: 1.8.2 - flags: "" - - crystal_bootstrap_version: 1.9.2 - flags: "" + - crystal_bootstrap_version: 1.2.2 + flags: "USE_PCRE1=true" + - crystal_bootstrap_version: 1.3.2 + flags: "USE_PCRE1=true" + - crystal_bootstrap_version: 1.4.1 + flags: "USE_PCRE1=true" + - crystal_bootstrap_version: 1.5.1 + flags: "USE_PCRE1=true" + - crystal_bootstrap_version: 1.6.2 + flags: "USE_PCRE1=true" steps: - name: Download Crystal source uses: actions/checkout@v4 From ca0dc199179676dc0d55da1b5a288817632c0ee7 Mon Sep 17 00:00:00 2001 From: kojix2 <2xijok@gmail.com> Date: Wed, 4 Oct 2023 20:44:12 +0900 Subject: [PATCH 0707/1551] Fix typo in unistd.cr (#13850) --- src/lib_c/aarch64-android/c/unistd.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib_c/aarch64-android/c/unistd.cr b/src/lib_c/aarch64-android/c/unistd.cr index c2e22a6ad0fa..bb8f78e8d1d7 100644 --- a/src/lib_c/aarch64-android/c/unistd.cr +++ b/src/lib_c/aarch64-android/c/unistd.cr @@ -41,7 +41,7 @@ lib LibC fun lseek(__fd : Int, __offset : OffT, __whence : Int) : OffT fun pipe(__fds : Int[2]) : Int fun read(__fd : Int, __buf : Void*, __count : SizeT) : SSizeT - fun pread(__fd : Int, __buf : Void*, __count : SizeT, __offest : OffT) : SSizeT + fun pread(__fd : Int, __buf : Void*, __count : SizeT, __offset : OffT) : SSizeT fun rmdir(__path : Char*) : Int fun symlink(__old_path : Char*, __new_path : Char*) : Int fun readlink(__path : Char*, __buf : Char*, __buf_size : SizeT) : SSizeT From 672389d23ba53b96be6af34aaea7680507240c5f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 5 Oct 2023 17:40:12 +0800 Subject: [PATCH 0708/1551] Support LLVM 17 (#13782) --- .github/workflows/llvm.yml | 2 ++ .github/workflows/win.yml | 22 ++++++++++++++-------- .github/workflows/win_build_portable.yml | 5 ++++- src/llvm/ext/llvm-versions.txt | 2 +- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index bf8660be39bd..ee07fdb39b04 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -24,6 +24,8 @@ jobs: llvm_ubuntu_version: "18.04" - llvm_version: "16.0.3" llvm_ubuntu_version: "22.04" + - llvm_version: "17.0.2" + llvm_ubuntu_version: "22.04" name: "LLVM ${{ matrix.llvm_version }}" steps: - name: Checkout Crystal source diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index d4d3df576ef9..45a87b90f04e 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -6,6 +6,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} +env: + CI_LLVM_VERSION: "17.0.2" + jobs: x86_64-windows-libs: runs-on: windows-2022 @@ -172,12 +175,12 @@ jobs: uses: actions/cache@v3 with: path: llvm - key: llvm-libs-16.0.3-msvc + key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc - name: Download LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' run: | - iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.3/llvm-16.0.3.src.tar.xz -OutFile llvm.tar.xz + iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.CI_LLVM_VERSION }}/llvm-${{ env.CI_LLVM_VERSION }}.src.tar.xz -OutFile llvm.tar.xz (Get-FileHash -Algorithm SHA256 .\llvm.tar.xz).hash -eq "D820E63BC3A6F4F833EC69A1EF49A2E81992E90BC23989F98946914B061AB6C7" 7z x llvm.tar.xz 7z x llvm.tar @@ -186,7 +189,7 @@ jobs: - name: Download LLVM's CMake files if: steps.cache-llvm.outputs.cache-hit != 'true' run: | - iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.3/cmake-16.0.3.src.tar.xz -OutFile cmake.tar.xz + iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.CI_LLVM_VERSION }}/cmake-${{ env.CI_LLVM_VERSION }}.src.tar.xz -OutFile cmake.tar.xz (Get-FileHash -Algorithm SHA256 .\cmake.tar.xz).hash -eq "B6D83C91F12757030D8361DEDC5DD84357B3EDB8DA406B5D0850DF8B6F7798B1" 7z x cmake.tar.xz 7z x cmake.tar @@ -194,17 +197,19 @@ jobs: - name: Build LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' - working-directory: ./llvm-src run: | - cmake . -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DLLVM_USE_CRT_RELEASE=MT -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_ZSTD=OFF + mkdir llvm-build + cd llvm-build + cmake ..\llvm-src -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DLLVM_USE_CRT_RELEASE=MT -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_ZSTD=OFF cmake --build . --config Release - cmake -DCMAKE_INSTALL_PREFIX=$(pwd)\llvm -P cmake_install.cmake + cmake "-DCMAKE_INSTALL_PREFIX=$(pwd)\..\llvm" -P cmake_install.cmake x86_64-windows: needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm] uses: ./.github/workflows/win_build_portable.yml with: release: false + llvm_version: ${{ env.CI_LLVM_VERSION }} x86_64-windows-test: runs-on: windows-2022 @@ -230,7 +235,7 @@ jobs: uses: actions/cache/restore@v3 with: path: llvm - key: llvm-libs-16.0.3-msvc + key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc fail-on-cache-miss: true - name: Set up environment @@ -257,6 +262,7 @@ jobs: uses: ./.github/workflows/win_build_portable.yml with: release: true + llvm_version: ${{ env.CI_LLVM_VERSION }} x86_64-windows-installer: if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/')) @@ -280,7 +286,7 @@ jobs: uses: actions/cache/restore@v3 with: path: llvm - key: llvm-libs-16.0.3-msvc + key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc fail-on-cache-miss: true - name: Set up environment diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 7b784df7c871..fd3bb1da3120 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -6,6 +6,9 @@ on: release: required: true type: boolean + llvm_version: + required: true + type: string jobs: build: @@ -88,7 +91,7 @@ jobs: uses: actions/cache/restore@v3 with: path: llvm - key: llvm-libs-16.0.3-msvc + key: llvm-libs-${{ inputs.llvm_version }}-msvc fail-on-cache-miss: true - name: Set up environment diff --git a/src/llvm/ext/llvm-versions.txt b/src/llvm/ext/llvm-versions.txt index eee7a8d7f17d..7c8773c02212 100644 --- a/src/llvm/ext/llvm-versions.txt +++ b/src/llvm/ext/llvm-versions.txt @@ -1 +1 @@ -16.0 15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 +17.0 16.0 15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 From 5ed476a41adf0719fc540e6c072a521b4c8ed3ec Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 6 Oct 2023 05:09:33 +0800 Subject: [PATCH 0709/1551] Change `IO::Buffered#peek`'s return type to `Bytes` (#13863) --- src/io/buffered.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/buffered.cr b/src/io/buffered.cr index df66fca6dc19..11d9d15827e8 100644 --- a/src/io/buffered.cr +++ b/src/io/buffered.cr @@ -98,7 +98,7 @@ module IO::Buffered # peek data if the current buffer is empty: # otherwise no read is performed and whatever # is in the buffer is returned. - def peek : Bytes? + def peek : Bytes check_open if @in_buffer_rem.empty? From 9c011d77d660a5a7441370dce9e101b3ca094d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 9 Oct 2023 18:02:40 +0200 Subject: [PATCH 0710/1551] Add changelog for 1.10.0 (#13864) --- CHANGELOG.md | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de572ab822a5..04f4eef15188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,140 @@ +# 1.10.0 (2023-10-09) + +### Features + +#### lang + +- Add unlimited block unpacking ([#11597](https://github.com/crystal-lang/crystal/pull/11597), thanks @asterite) + +#### stdlib + +- Add more `Colorize::Mode` flags ([#13745](https://github.com/crystal-lang/crystal/pull/13745), thanks @HertzDevil) +- *(collection)* Add `Hash#put_if_absent` ([#13590](https://github.com/crystal-lang/crystal/pull/13590), thanks @HertzDevil) +- *(collection)* Add `Set#rehash` ([#13630](https://github.com/crystal-lang/crystal/pull/13630), thanks @HertzDevil) +- *(collection)* Add yield `key` in `Hash#transform_values` and `value` in `#transform_keys` ([#13608](https://github.com/crystal-lang/crystal/pull/13608), thanks @baseballlover723) +- *(crypto)* Upgrade SSL defaults to Mozilla guidelines version 5.7 ([#13685](https://github.com/crystal-lang/crystal/pull/13685), thanks @straight-shoota) +- *(crypto)* **[security]** Allow OpenSSL clients to choose cipher ([#13695](https://github.com/crystal-lang/crystal/pull/13695), thanks @carlhoerberg) +- *(files)* Add `File#rename` ([#13640](https://github.com/crystal-lang/crystal/pull/13640), thanks @carlhoerberg) +- *(llvm)* Support LLVM 17 ([#13782](https://github.com/crystal-lang/crystal/pull/13782), thanks @HertzDevil) +- *(networking)* Add overloads for `URI::Params.encode` with `IO` parameter ([#13798](https://github.com/crystal-lang/crystal/pull/13798), thanks @jwoertink) +- *(numeric)* Add `Complex#to_i128`, `Complex#to_u128` ([#13838](https://github.com/crystal-lang/crystal/pull/13838), thanks @HertzDevil) +- *(runtime)* Add additional fields to `GC:ProfStats` ([#13734](https://github.com/crystal-lang/crystal/pull/13734), thanks @carlhoerberg) +- *(serialization)* Support YAML deserialization of 128-bit integers ([#13834](https://github.com/crystal-lang/crystal/pull/13834), thanks @HertzDevil) +- *(serialization)* Support 128-bit integers in `JSON::PullParser#read?` ([#13837](https://github.com/crystal-lang/crystal/pull/13837), thanks @HertzDevil) +- *(specs)* **[breaking]** Change spec runner to exit with failure for `focus: true` ([#13653](https://github.com/crystal-lang/crystal/pull/13653), thanks @straight-shoota) +- *(text)* Add `String#byte_index(Char)` ([#13819](https://github.com/crystal-lang/crystal/pull/13819), thanks @funny-falcon) +- *(time)* Support Android's system timezone database ([#13666](https://github.com/crystal-lang/crystal/pull/13666), thanks @HertzDevil) + +#### compiler + +- Experimental: Add `Slice.literal` for numeric slice constants ([#13716](https://github.com/crystal-lang/crystal/pull/13716), thanks @HertzDevil) + +#### tools + +- Add `tool unreachable` ([#13783](https://github.com/crystal-lang/crystal/pull/13783), thanks @straight-shoota) +- *(dependencies)* Add `crystal tool dependencies` ([#13631](https://github.com/crystal-lang/crystal/pull/13631), thanks @straight-shoota) +- *(docs-generator)* Add CSS for tables ([#13822](https://github.com/crystal-lang/crystal/pull/13822), thanks @nobodywasishere) +- *(hierarchy)* Support generic types in `crystal tool hierarchy` ([#13715](https://github.com/crystal-lang/crystal/pull/13715), thanks @HertzDevil) +- *(playground)* Update octicons to v19.5.0 ([#13738](https://github.com/crystal-lang/crystal/pull/13738), thanks @GeopJr) + +### Bugfixes + +#### lang + +- *(macros)* Fix missing normalization of macro expressions (and others) ([#13709](https://github.com/crystal-lang/crystal/pull/13709), thanks @asterite) +- *(macros)* Fix block parameter unpacking inside macros ([#13813](https://github.com/crystal-lang/crystal/pull/13813), thanks @HertzDevil) + +#### stdlib + +- *(collection)* **[breaking]** Mark the return type of methods such as `Slice#copy_to` as `Nil` ([#13774](https://github.com/crystal-lang/crystal/pull/13774), thanks @erdian718) +- *(files)* Change `IO::Buffered#peek`'s return type to `Bytes` ([#13863](https://github.com/crystal-lang/crystal/pull/13863), thanks @HertzDevil) +- *(llvm)* Chop git suffix from `LibLLVM::VERSION` ([#13699](https://github.com/crystal-lang/crystal/pull/13699), thanks @HOMODELUNA) +- *(macros)* Do not add trailing `+` in `TypeNode#id` for virtual types ([#13708](https://github.com/crystal-lang/crystal/pull/13708), thanks @HertzDevil) +- *(numeric)* Fix `BigDecimal#round` for large digit counts in base 10 ([#13811](https://github.com/crystal-lang/crystal/pull/13811), thanks @HertzDevil) +- *(serialization)* Set encoding in `XML.parse_html` explicitly to UTF-8 ([#13705](https://github.com/crystal-lang/crystal/pull/13705), thanks @straight-shoota) +- *(serialization)* Fix error message when parsing unknown JSON enum value ([#13728](https://github.com/crystal-lang/crystal/pull/13728), thanks @willhbr) +- *(serialization)* Fix YAML scalar type validation error message ([#13771](https://github.com/crystal-lang/crystal/pull/13771), thanks @MistressRemilia) +- *(serialization)* Fix incorrect overflow in `UInt64.from_yaml` ([#13829](https://github.com/crystal-lang/crystal/pull/13829), thanks @HertzDevil) +- *(system)* Fix `Process.new` with nilable chdir parameter on Windows ([#13768](https://github.com/crystal-lang/crystal/pull/13768), thanks @straight-shoota) +- *(system)* Fix typo in unistd.cr ([#13850](https://github.com/crystal-lang/crystal/pull/13850), thanks @kojix2) +- *(text)* Fix `Char::Reader#each` bounds check after block ([#13817](https://github.com/crystal-lang/crystal/pull/13817), thanks @straight-shoota) +- *(text)* Minor fixup for `HTML.decode_codepoint` ([#13843](https://github.com/crystal-lang/crystal/pull/13843), thanks @straight-shoota) + +#### compiler + +- **[breaking]** Remove double `.cr.cr` extension in `require` path lookup ([#13749](https://github.com/crystal-lang/crystal/pull/13749), thanks @straight-shoota) +- *(parser)* Fix end location for `FunDef` ([#13789](https://github.com/crystal-lang/crystal/pull/13789), thanks @straight-shoota) +- *(semantic)* Fix lookup scope for `@[Primitive]` def's return type ([#13658](https://github.com/crystal-lang/crystal/pull/13658), thanks @HertzDevil) +- *(semantic)* Fix typo in call_error.cr ([#13764](https://github.com/crystal-lang/crystal/pull/13764), thanks @kojix2) + +#### tools + +- *(docs-generator)* Fix octicon-link icon color on dark mode ([#13670](https://github.com/crystal-lang/crystal/pull/13670), thanks @GeopJr) +- *(docs-generator)* Allow word breaks between module names in docs ([#13827](https://github.com/crystal-lang/crystal/pull/13827), thanks @nobodywasishere) +- *(docs-generator)* Fix docs dark mode dropdown background on blink ([#13840](https://github.com/crystal-lang/crystal/pull/13840), thanks @GeopJr) +- *(init)* Fix shard crystal version in `crystal init` ([#13730](https://github.com/crystal-lang/crystal/pull/13730), thanks @xendk) +- *(hierarchy)*: Fix byte sizes for `Proc`s inside extern structs ([#13711](https://github.com/crystal-lang/crystal/pull/13711), thanks @HertzDevil) + +### Performance + +#### stdlib + +- Optimize `IO::Delimited` ([#11242](https://github.com/crystal-lang/crystal/pull/11242), thanks @asterite) +- *(crypto)* Use `IO::DEFAULT_BUFFER_SIZE` in `Digest#update` ([#13635](https://github.com/crystal-lang/crystal/pull/13635), thanks @carlhoerberg) +- *(crypto)* Fix memory leak in `OpenSSL::SSL::Socket#peer_certificate` ([#13785](https://github.com/crystal-lang/crystal/pull/13785), thanks @compumike) +- *(files)* Optimize `IO#read_string(0)` ([#13732](https://github.com/crystal-lang/crystal/pull/13732), thanks @jgaskins) +- *(files)* Avoid double file buffering ([#13780](https://github.com/crystal-lang/crystal/pull/13780), thanks @carlhoerberg) +- *(llvm)* Refactor `LLVM.default_target_triple` to avoid regex ([#13659](https://github.com/crystal-lang/crystal/pull/13659), thanks @straight-shoota) +- *(numeric)* Pre-allocate Dragonbox cache array ([#13649](https://github.com/crystal-lang/crystal/pull/13649), thanks @HertzDevil) +- *(runtime)* Avoid realloc callstack array when unwinding ([#13781](https://github.com/crystal-lang/crystal/pull/13781), thanks @carlhoerberg) +- *(time)* Optimize the constructors of `Time::Span` ([#13807](https://github.com/crystal-lang/crystal/pull/13807), thanks @erdian718) + +### Refactor + +#### stdlib + +- Do not use nilable `Pointer`s ([#13710](https://github.com/crystal-lang/crystal/pull/13710), thanks @HertzDevil) +- *(collection)* Use `Set(T)` instead of `Hash(T, Bool)` ([#13611](https://github.com/crystal-lang/crystal/pull/13611), thanks @HertzDevil) +- *(concurrency)* Use `Fiber.inactive` inside `Fiber#run`'s `ensure` block ([#13701](https://github.com/crystal-lang/crystal/pull/13701), thanks @HertzDevil) +- *(crypto)* Use `JSON::Serializable` in `scripts/generate_ssl_server_defaults.cr` ([#13667](https://github.com/crystal-lang/crystal/pull/13667), thanks @HertzDevil) +- *(crypto)* Refactor narrow OpenSSL requires for digest implementations ([#13818](https://github.com/crystal-lang/crystal/pull/13818), thanks @straight-shoota) +- *(networking)* **[deprecation]** Add types to `HTTP::StaticFileHandler` ([#13778](https://github.com/crystal-lang/crystal/pull/13778), thanks @jkthorne) + +#### compiler + +- Restrict some boolean properties to `Bool` in the compiler ([#13614](https://github.com/crystal-lang/crystal/pull/13614), thanks @HertzDevil) + +### Documentation + +#### stdlib + +- *(crypto)* Fix docs for `Digest::SHA512` ([#13796](https://github.com/crystal-lang/crystal/pull/13796), thanks @jgaskins) +- *(files)* Document `Dir#mkdir`, `Dir#exists?` ([#13795](https://github.com/crystal-lang/crystal/pull/13795), thanks @jkthorne) +- *(networking)* Add documentation for `HTTP::Headers#add` ([#13762](https://github.com/crystal-lang/crystal/pull/13762), thanks @jkthorne) +- *(text)* Fix typo in regex.cr ([#13751](https://github.com/crystal-lang/crystal/pull/13751), thanks @beta-ziliani) + +### Specs + +#### stdlib + +- *(numeric)* Update specs for `Int::Primitive.from_json` ([#13835](https://github.com/crystal-lang/crystal/pull/13835), thanks @HertzDevil) +- *(numeric)* Remove overflowing `Float#to_u!` interpreter primitive specs ([#13737](https://github.com/crystal-lang/crystal/pull/13737), thanks @HertzDevil) +- *(time)* Clear `Time::Location` cache before `.load_android` specs ([#13718](https://github.com/crystal-lang/crystal/pull/13718), thanks @HertzDevil) + +### Infrastructure + +- Update previous Crystal release - 1.9.2 ([#13650](https://github.com/crystal-lang/crystal/pull/13650), thanks @straight-shoota) +- Update distribution-scripts ([#13776](https://github.com/crystal-lang/crystal/pull/13776), thanks @straight-shoota) +- make: Add `generate_data` target for running generator scripts ([#13700](https://github.com/crystal-lang/crystal/pull/13700), thanks @straight-shoota) +- Add shell completions for `clear_cache` ([#13636](https://github.com/crystal-lang/crystal/pull/13636), thanks @straight-shoota) +- New changelog format ([#13662](https://github.com/crystal-lang/crystal/pull/13662), thanks @straight-shoota) +- Detect developer mode in Windows installer ([#13681](https://github.com/crystal-lang/crystal/pull/13681), thanks @HertzDevil) +- Update PGP key link ([#13754](https://github.com/crystal-lang/crystal/pull/13754), thanks @syeopite) +- Fix log format in update-distribution-scripts.sh ([#13777](https://github.com/crystal-lang/crystal/pull/13777), thanks @straight-shoota) +- *(ci)* Trigger windows release jobs on tag ([#13683](https://github.com/crystal-lang/crystal/pull/13683), thanks @straight-shoota) +- *(ci)* Update GH Actions ([#13748](https://github.com/crystal-lang/crystal/pull/13748), thanks @renovate) +- *(ci)* Refactor `crystal_bootstrap_version` ([#13845](https://github.com/crystal-lang/crystal/pull/13845), thanks @straight-shoota) + # 1.9.2 (2023-07-19) ### Bugfixes diff --git a/src/VERSION b/src/VERSION index a01185b4d67a..81c871de46b3 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.10.0-dev +1.10.0 From 8cd1481925aba5bf5d9d7c2b08b81b1f79b8c534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 10 Oct 2023 20:49:36 +0200 Subject: [PATCH 0711/1551] Fix `win.yml` (#13876) --- .github/workflows/win.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 45a87b90f04e..a98e0dcb7c7e 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -209,7 +209,7 @@ jobs: uses: ./.github/workflows/win_build_portable.yml with: release: false - llvm_version: ${{ env.CI_LLVM_VERSION }} + llvm_version: "17.0.2" x86_64-windows-test: runs-on: windows-2022 @@ -262,7 +262,7 @@ jobs: uses: ./.github/workflows/win_build_portable.yml with: release: true - llvm_version: ${{ env.CI_LLVM_VERSION }} + llvm_version: "17.0.2" x86_64-windows-installer: if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/')) From 4867d81e6a993c2395d32e6038a23c489e1736c2 Mon Sep 17 00:00:00 2001 From: Mike Robbins Date: Thu, 12 Oct 2023 08:57:24 -0400 Subject: [PATCH 0712/1551] `IO#gets` should have same result regardless of #peek availability (#13882) Co-authored-by: Sijawusz Pur Rahnama --- spec/std/io/io_spec.cr | 34 ++++++++++++++++++++++++++++++++++ src/io.cr | 10 +++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index ac51d4bddaea..e2c065ecebf0 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -183,6 +183,40 @@ describe IO do io.gets(chomp: false).should be_nil end + it "does gets with empty string (no peek)" do + io = SimpleIOMemory.new("") + io.gets(chomp: true).should be_nil + end + + it "does gets with empty string (with peek)" do + io = IO::Memory.new("") + io.gets(chomp: true).should be_nil + end + + it "does gets with \\n (no peek)" do + io = SimpleIOMemory.new("\n") + io.gets(chomp: true).should eq("") + io.gets(chomp: true).should be_nil + end + + it "does gets with \\n (with peek)" do + io = IO::Memory.new("\n") + io.gets(chomp: true).should eq("") + io.gets(chomp: true).should be_nil + end + + it "does gets with \\r\\n (no peek)" do + io = SimpleIOMemory.new("\r\n") + io.gets(chomp: true).should eq("") + io.gets(chomp: true).should be_nil + end + + it "does gets with \\r\\n (with peek)" do + io = IO::Memory.new("\r\n") + io.gets(chomp: true).should eq("") + io.gets(chomp: true).should be_nil + end + it "does gets with big line" do big_line = "a" * 20_000 io = SimpleIOMemory.new("#{big_line}\nworld\n") diff --git a/src/io.cr b/src/io.cr index 0294ec0272b7..28a3652c9e4c 100644 --- a/src/io.cr +++ b/src/io.cr @@ -752,11 +752,12 @@ abstract class IO private def gets_slow(delimiter : Char, limit, chomp) buffer = String::Builder.new - gets_slow(delimiter, limit, chomp, buffer) - buffer.empty? ? nil : buffer.to_s + bytes_read = gets_slow(delimiter, limit, chomp, buffer) + buffer.to_s if bytes_read end - private def gets_slow(delimiter : Char, limit, chomp, buffer : String::Builder) : Nil + private def gets_slow(delimiter : Char, limit, chomp, buffer : String::Builder) : Bool + bytes_read = false chomp_rn = delimiter == '\n' && chomp while true @@ -766,6 +767,7 @@ abstract class IO end char, char_bytesize = info + bytes_read = true # Consider the case of \r\n when the delimiter is \n and chomp = true if chomp_rn && char == '\r' @@ -801,6 +803,8 @@ abstract class IO break if char_bytesize >= limit limit -= char_bytesize end + + bytes_read end # Reads until *delimiter* is found or the end of the `IO` is reached. From 1d0a7ab02320349ea9db5a178c45c9fdacf742a6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 12 Oct 2023 20:57:44 +0800 Subject: [PATCH 0713/1551] Support Android API levels 24 - 27 (#13884) --- lib/reply/src/term_size.cr | 6 +- src/crystal/iconv.cr | 2 +- src/crystal/system/unix/file_descriptor.cr | 68 +++++++++++++++++++--- src/io/console.cr | 8 +-- src/lib_c.cr | 2 +- src/lib_c/aarch64-android/c/iconv.cr | 4 +- src/lib_c/aarch64-android/c/sys/ioctl.cr | 8 +++ src/lib_c/aarch64-android/c/termios.cr | 2 - 8 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 src/lib_c/aarch64-android/c/sys/ioctl.cr diff --git a/lib/reply/src/term_size.cr b/lib/reply/src/term_size.cr index d0a4c2e79699..fd0c60421c4f 100644 --- a/lib/reply/src/term_size.cr +++ b/lib/reply/src/term_size.cr @@ -145,6 +145,10 @@ end {% end %} {% end %} - fun ioctl(fd : Int, request : ULong, ...) : Int + {% if flag?(:android) %} + fun ioctl(__fd : Int, __request : Int, ...) : Int + {% else %} + fun ioctl(fd : Int, request : ULong, ...) : Int + {% end %} end {% end %} diff --git a/src/crystal/iconv.cr b/src/crystal/iconv.cr index 64d4e17f8112..593c492d4ce3 100644 --- a/src/crystal/iconv.cr +++ b/src/crystal/iconv.cr @@ -1,4 +1,4 @@ -{% if flag?(:use_libiconv) || flag?(:win32) %} +{% if flag?(:use_libiconv) || flag?(:win32) || (flag?(:android) && LibC::ANDROID_API < 28) %} require "./lib_iconv" private USE_LIBICONV = true {% else %} diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 8a5c01cff44e..d77708f314bb 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -1,6 +1,9 @@ require "c/fcntl" require "io/evented" require "termios" +{% if flag?(:android) && LibC::ANDROID_API < 28 %} + require "c/sys/ioctl" +{% end %} # :nodoc: module Crystal::System::FileDescriptor @@ -198,7 +201,7 @@ module Crystal::System::FileDescriptor system_console_mode do |mode| flags = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL mode.c_lflag = enable ? (mode.c_lflag | flags) : (mode.c_lflag & ~flags) - if LibC.tcsetattr(fd, LibC::TCSANOW, pointerof(mode)) != 0 + if FileDescriptor.tcsetattr(fd, LibC::TCSANOW, pointerof(mode)) != 0 raise IO::Error.from_errno("tcsetattr") end yield @@ -208,13 +211,13 @@ module Crystal::System::FileDescriptor private def system_raw(enable : Bool, & : ->) system_console_mode do |mode| if enable - LibC.cfmakeraw(pointerof(mode)) + FileDescriptor.cfmakeraw(pointerof(mode)) else mode.c_iflag |= LibC::BRKINT | LibC::ISTRIP | LibC::ICRNL | LibC::IXON mode.c_oflag |= LibC::OPOST mode.c_lflag |= LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL | LibC::ICANON | LibC::ISIG | LibC::IEXTEN end - if LibC.tcsetattr(fd, LibC::TCSANOW, pointerof(mode)) != 0 + if FileDescriptor.tcsetattr(fd, LibC::TCSANOW, pointerof(mode)) != 0 raise IO::Error.from_errno("tcsetattr") end yield @@ -223,13 +226,60 @@ module Crystal::System::FileDescriptor @[AlwaysInline] private def system_console_mode(&) - if LibC.tcgetattr(fd, out mode) != 0 - raise IO::Error.from_errno("tcgetattr") + before = FileDescriptor.tcgetattr(fd) + begin + yield before + ensure + FileDescriptor.tcsetattr(fd, LibC::TCSANOW, pointerof(before)) end + end + + @[AlwaysInline] + def self.tcgetattr(fd) + termios = uninitialized LibC::Termios + ret = {% if flag?(:android) && !LibC.has_method?(:tcgetattr) %} + LibC.ioctl(fd, LibC::TCGETS, pointerof(termios)) + {% else %} + LibC.tcgetattr(fd, pointerof(termios)) + {% end %} + raise IO::Error.from_errno("tcgetattr") if ret != 0 + termios + end + + @[AlwaysInline] + def self.tcsetattr(fd, optional_actions, termios_p) + {% if flag?(:android) && !LibC.has_method?(:tcsetattr) %} + optional_actions = optional_actions.value if optional_actions.is_a?(Termios::LineControl) + cmd = case optional_actions + when LibC::TCSANOW + LibC::TCSETS + when LibC::TCSADRAIN + LibC::TCSETSW + when LibC::TCSAFLUSH + LibC::TCSETSF + else + Errno.value = Errno::EINVAL + return LibC::Int.new(-1) + end + + LibC.ioctl(fd, cmd, termios_p) + {% else %} + LibC.tcsetattr(fd, optional_actions, termios_p) + {% end %} + end - before = mode - ret = yield mode - LibC.tcsetattr(fd, LibC::TCSANOW, pointerof(before)) - ret + @[AlwaysInline] + def self.cfmakeraw(termios_p) + {% if flag?(:android) && !LibC.has_method?(:cfmakeraw) %} + s.value.c_iflag &= ~(LibC::IGNBRK | LibC::BRKINT | LibC::PARMRK | LibC::ISTRIP | LibC::INLCR | LibC::IGNCR | LibC::ICRNL | LibC::IXON) + s.value.c_oflag &= ~LibC::OPOST + s.value.c_lflag &= ~(LibC::ECHO | LibC::ECHONL | LibC::ICANON | LibC::ISIG | LibC::IEXTEN) + s.value.c_cflag &= ~(LibC::CSIZE | LibC::PARENB) + s.value.c_cflag |= LibC::CS8 + s.value.c_cc[LibC::VMIN] = 1 + s.value.c_cc[LibC::VTIME] = 0 + {% else %} + LibC.cfmakeraw(termios_p) + {% end %} end end diff --git a/src/io/console.cr b/src/io/console.cr index d5c756edc7e8..5ac51b497c29 100644 --- a/src/io/console.cr +++ b/src/io/console.cr @@ -92,7 +92,7 @@ class IO::FileDescriptor < IO @[Deprecated] macro noecho_from_tc_mode! mode.c_lflag &= ~(Termios::LocalMode.flags(ECHO, ECHOE, ECHOK, ECHONL).value) - LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode)) + Crystal::System::FileDescriptor.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode)) end @[Deprecated] @@ -109,12 +109,12 @@ class IO::FileDescriptor < IO Termios::LocalMode::ICANON | Termios::LocalMode::ISIG | Termios::LocalMode::IEXTEN).value - LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode)) + Crystal::System::FileDescriptor.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode)) end @[Deprecated] macro raw_from_tc_mode! - LibC.cfmakeraw(pointerof(mode)) - LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode)) + Crystal::System::FileDescriptor.cfmakeraw(pointerof(mode)) + Crystal::System::FileDescriptor.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode)) end end diff --git a/src/lib_c.cr b/src/lib_c.cr index b707d18a3a9d..b859a4c85061 100644 --- a/src/lib_c.cr +++ b/src/lib_c.cr @@ -27,7 +27,7 @@ lib LibC {% if flag?(:android) %} {% default_api_version = 31 %} - {% min_supported_version = 28 %} + {% min_supported_version = 24 %} {% api_version_var = env("ANDROID_PLATFORM") || env("ANDROID_NATIVE_API_LEVEL") %} {% api_version = api_version_var ? api_version_var.gsub(/^android-/, "").to_i : default_api_version %} {% raise "TODO: Support Android API level below #{min_supported_version}" unless api_version >= min_supported_version %} diff --git a/src/lib_c/aarch64-android/c/iconv.cr b/src/lib_c/aarch64-android/c/iconv.cr index 6a9a20a6eb7a..ea48b1122c32 100644 --- a/src/lib_c/aarch64-android/c/iconv.cr +++ b/src/lib_c/aarch64-android/c/iconv.cr @@ -1,9 +1,9 @@ require "./stddef" lib LibC - type IconvT = Void* - {% if ANDROID_API >= 28 %} + type IconvT = Void* + fun iconv(__converter : IconvT, __src_buf : Char**, __src_bytes_left : SizeT*, __dst_buf : Char**, __dst_bytes_left : SizeT*) : SizeT fun iconv_close(__converter : IconvT) : Int fun iconv_open(__src_encoding : Char*, __dst_encoding : Char*) : IconvT diff --git a/src/lib_c/aarch64-android/c/sys/ioctl.cr b/src/lib_c/aarch64-android/c/sys/ioctl.cr new file mode 100644 index 000000000000..4667c87864a4 --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/ioctl.cr @@ -0,0 +1,8 @@ +lib LibC + TCGETS = 0x5401 + TCSETS = 0x5402 + TCSETSW = 0x5403 + TCSETSF = 0x5404 + + fun ioctl(__fd : Int, __request : Int, ...) : Int +end diff --git a/src/lib_c/aarch64-android/c/termios.cr b/src/lib_c/aarch64-android/c/termios.cr index 01f5a2831a0b..cf96bf3eb3c7 100644 --- a/src/lib_c/aarch64-android/c/termios.cr +++ b/src/lib_c/aarch64-android/c/termios.cr @@ -168,8 +168,6 @@ lib LibC c_cc : StaticArray(CcT, 19) # cc_t[NCCS] end - # TODO: defined inline for `21 <= ANDROID_API < 28` in terms of `ioctl`, but - # `lib/reply/src/term_size.cr` contains an incompatible definition of it {% if ANDROID_API >= 28 %} fun tcgetattr(__fd : Int, __t : Termios*) : Int fun tcsetattr(__fd : Int, __optional_actions : Int, __t : Termios*) : Int From c6f3552f5be159eb06c8f348c6b9e23ff7f17dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 13 Oct 2023 09:24:41 +0200 Subject: [PATCH 0714/1551] Add changelog for 1.10.1 (#13886) Co-authored-by: Sijawusz Pur Rahnama --- CHANGELOG.md | 13 +++++++++++++ src/VERSION | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04f4eef15188..02e481aa1d26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 1.10.1 (2023-10-13) + +### Bugfixes + +#### stdlib + +- `IO#gets` should have same result regardless of `#peek` availability ([#13882](https://github.com/crystal-lang/crystal/pull/13882), thanks @compumike) +- Support Android API levels 24 - 27 ([#13884](https://github.com/crystal-lang/crystal/pull/13884), thanks @HertzDevil) + +### Infrastructure + +- *(ci)* Fix `win.yml` ([#13876](https://github.com/crystal-lang/crystal/pull/13876), thanks @straight-shoota) + # 1.10.0 (2023-10-09) ### Features diff --git a/src/VERSION b/src/VERSION index 81c871de46b3..4dae2985b58c 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.10.0 +1.10.1 From edb0b5be7714a769b77b2ec22e5a366f642eb321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 16 Oct 2023 19:15:41 +0200 Subject: [PATCH 0715/1551] Update previous Crystal release - 1.10.0 (#13878) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 6 +++--- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win_build_portable.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ src/VERSION | 2 +- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 33fe27c0540c..840e1a974f21 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.9.2/crystal-1.9.2-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.10.1/crystal-1.10.1-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 949c43a307a8..a034a9f5b410 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.9.2-build + image: crystallang/crystal:1.10.1-build name: "Test Interpreter" steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.9.2-build + image: crystallang/crystal:1.10.1-build name: Build interpreter steps: - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.9.2-build + image: crystallang/crystal:1.10.1-build strategy: matrix: part: [0, 1, 2, 3] diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8be77f17ff05..f34bf180bd98 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2] + crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1] flags: [""] include: # libffi is only available starting from the 1.2.2 build images diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index ee07fdb39b04..883302cf64a6 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -56,7 +56,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.9.2" + crystal: "1.10.1" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 7c4c60ced711..07e2d7e94558 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.9.2-alpine + container: crystallang/crystal:1.10.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -27,7 +27,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.9.2-alpine + container: crystallang/crystal:1.10.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.9.2-alpine + container: crystallang/crystal:1.10.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index d5120a378640..a7ad3afbbb65 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.9.2-alpine + container: crystallang/crystal:1.10.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.9.2-alpine + container: crystallang/crystal:1.10.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index a11e353f6509..3da1ef4ca89a 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.9.2-build + container: crystallang/crystal:1.10.1-build steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index fd3bb1da3120..f5b0ad542335 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -24,7 +24,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.9.2" + crystal: "1.10.1" - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index 008965ccded0..8e9f7a5ddadf 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.9.2/crystal-1.9.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.9.2-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.10.1/crystal-1.10.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.10.1-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.9.2}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.10.1}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 453894568061..ab90080aca13 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.9.2/crystal-1.9.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:0ngiflk7yxb6ry5ax1zrbm3rh4psq7flv7xj6ph4g8qqx74qv79m"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.10.1/crystal-1.10.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:08k8sixhnk9ld99nyrya11rkpp34zamsg3lk9h50ppbmzfixjyyc"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.9.2/crystal-1.9.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:0ngiflk7yxb6ry5ax1zrbm3rh4psq7flv7xj6ph4g8qqx74qv79m"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.10.1/crystal-1.10.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:08k8sixhnk9ld99nyrya11rkpp34zamsg3lk9h50ppbmzfixjyyc"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.9.2/crystal-1.9.2-1-linux-x86_64.tar.gz"; - sha256 = "sha256:1d4wmr49m3ykylh4zwp184mm98vj0cqmflhgnmgry8nkwhkvs900"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.10.1/crystal-1.10.1-1-linux-x86_64.tar.gz"; + sha256 = "sha256:02hzslzgv0xxsal3fkbcdrnrrnzf9lraamy36p36sjf8n14v45a2"; }; }.${pkgs.stdenv.system}); diff --git a/src/VERSION b/src/VERSION index 4dae2985b58c..1f724bf455d7 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.10.1 +1.11.0-dev From 8ceea56467ceba46f4b7be0d94f354274f532b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 16 Oct 2023 19:17:51 +0200 Subject: [PATCH 0716/1551] Use `Char#to_i?` in lexer (#13841) --- src/compiler/crystal/syntax/lexer.cr | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 33f991c5fc0f..bb4fbe50f4a9 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -2194,8 +2194,8 @@ module Crystal def consume_non_braced_unicode_escape codepoint = 0 4.times do - hex_value = char_to_hex(next_char) { expected_hexadecimal_character_in_unicode_escape } - codepoint = 16 * codepoint + hex_value + hex_value = next_char.to_i?(16) || expected_hexadecimal_character_in_unicode_escape + codepoint = 16 &* codepoint &+ hex_value end if 0xD800 <= codepoint <= 0xDFFF raise "invalid unicode codepoint (surrogate half)" @@ -2224,8 +2224,8 @@ module Crystal expected_hexadecimal_character_in_unicode_escape end else - hex_value = char_to_hex(char) { expected_hexadecimal_character_in_unicode_escape } - codepoint = 16 * codepoint + hex_value + hex_value = char.to_i?(16) || expected_hexadecimal_character_in_unicode_escape + codepoint = 16 &* codepoint &+ hex_value found_digit = true end end @@ -2339,18 +2339,6 @@ module Crystal @token end - def char_to_hex(char, &) - if '0' <= char <= '9' - char - '0' - elsif 'a' <= char <= 'f' - 10 + (char - 'a') - elsif 'A' <= char <= 'F' - 10 + (char - 'A') - else - yield - end - end - def consume_loc_pragma case current_char when '"' From 51fb08fdcaedd27159c5210e8a317ea47a354707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 16 Oct 2023 19:18:08 +0200 Subject: [PATCH 0717/1551] Remove unnecessary file check for CLI arguments (#13853) --- src/compiler/crystal/command.cr | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 1540aafa9a46..a6b5c18455ea 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -614,9 +614,6 @@ class Crystal::Command private def gather_sources(filenames) filenames.map do |filename| - unless File.file?(filename) - error "file '#{filename}' does not exist" - end filename = File.expand_path(filename) Compiler::Source.new(filename, File.read(filename)) end From fc5a56a1d0a0bf76d0fedd8f3f69c4c2305dd079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 17 Oct 2023 11:11:59 +0200 Subject: [PATCH 0718/1551] Fix tool init error message when target exists but not a dir (#13869) --- src/compiler/crystal/tools/init.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/tools/init.cr b/src/compiler/crystal/tools/init.cr index 456386f5b14e..96b004eec2fd 100644 --- a/src/compiler/crystal/tools/init.cr +++ b/src/compiler/crystal/tools/init.cr @@ -238,8 +238,8 @@ module Crystal end def run - if File.file?(config.expanded_dir) - raise Error.new "#{config.dir.inspect} is a file" + if (info = File.info?(config.expanded_dir)) && !info.directory? + raise Error.new "#{config.dir.inspect} is a #{info.type.to_s.downcase}" end views = self.views From 44583d7d164baba3a31b65bc8f7c5a906dcce226 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 17 Oct 2023 17:12:13 +0800 Subject: [PATCH 0719/1551] Implement `BigRational`'s rounding modes (#13871) --- spec/std/big/big_rational_spec.cr | 94 +++++++++++++++++++++++++++++++ src/big/big_rational.cr | 21 +++++-- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr index ce9387d8489a..2c9b138ba357 100644 --- a/spec/std/big/big_rational_spec.cr +++ b/spec/std/big/big_rational_spec.cr @@ -299,6 +299,100 @@ describe BigRational do br(-291, 100).trunc.should eq(-2) end + describe "#round" do + describe "rounding modes" do + it "to_zero" do + br(-9, 6).round(:to_zero).should eq BigRational.new(-1) + br(-6, 6).round(:to_zero).should eq BigRational.new(-1) + br(-5, 6).round(:to_zero).should eq BigRational.new(0) + br(-3, 6).round(:to_zero).should eq BigRational.new(0) + br(-1, 6).round(:to_zero).should eq BigRational.new(0) + br(0, 6).round(:to_zero).should eq BigRational.new(0) + br(1, 6).round(:to_zero).should eq BigRational.new(0) + br(3, 6).round(:to_zero).should eq BigRational.new(0) + br(5, 6).round(:to_zero).should eq BigRational.new(0) + br(6, 6).round(:to_zero).should eq BigRational.new(1) + br(9, 6).round(:to_zero).should eq BigRational.new(1) + end + + it "to_positive" do + br(-9, 6).round(:to_positive).should eq BigRational.new(-1) + br(-6, 6).round(:to_positive).should eq BigRational.new(-1) + br(-5, 6).round(:to_positive).should eq BigRational.new(0) + br(-3, 6).round(:to_positive).should eq BigRational.new(0) + br(-1, 6).round(:to_positive).should eq BigRational.new(0) + br(0, 6).round(:to_positive).should eq BigRational.new(0) + br(1, 6).round(:to_positive).should eq BigRational.new(1) + br(3, 6).round(:to_positive).should eq BigRational.new(1) + br(5, 6).round(:to_positive).should eq BigRational.new(1) + br(6, 6).round(:to_positive).should eq BigRational.new(1) + br(9, 6).round(:to_positive).should eq BigRational.new(2) + end + + it "to_negative" do + br(-9, 6).round(:to_negative).should eq BigRational.new(-2) + br(-6, 6).round(:to_negative).should eq BigRational.new(-1) + br(-5, 6).round(:to_negative).should eq BigRational.new(-1) + br(-3, 6).round(:to_negative).should eq BigRational.new(-1) + br(-1, 6).round(:to_negative).should eq BigRational.new(-1) + br(0, 6).round(:to_negative).should eq BigRational.new(0) + br(1, 6).round(:to_negative).should eq BigRational.new(0) + br(3, 6).round(:to_negative).should eq BigRational.new(0) + br(5, 6).round(:to_negative).should eq BigRational.new(0) + br(6, 6).round(:to_negative).should eq BigRational.new(1) + br(9, 6).round(:to_negative).should eq BigRational.new(1) + end + + it "ties_even" do + br(-15, 6).round(:ties_even).should eq BigRational.new(-2) + br(-9, 6).round(:ties_even).should eq BigRational.new(-2) + br(-6, 6).round(:ties_even).should eq BigRational.new(-1) + br(-5, 6).round(:ties_even).should eq BigRational.new(-1) + br(-3, 6).round(:ties_even).should eq BigRational.new(0) + br(-1, 6).round(:ties_even).should eq BigRational.new(0) + br(0, 6).round(:ties_even).should eq BigRational.new(0) + br(1, 6).round(:ties_even).should eq BigRational.new(0) + br(3, 6).round(:ties_even).should eq BigRational.new(0) + br(5, 6).round(:ties_even).should eq BigRational.new(1) + br(6, 6).round(:ties_even).should eq BigRational.new(1) + br(9, 6).round(:ties_even).should eq BigRational.new(2) + br(15, 6).round(:ties_even).should eq BigRational.new(2) + end + + it "ties_away" do + br(-15, 6).round(:ties_away).should eq BigRational.new(-3) + br(-9, 6).round(:ties_away).should eq BigRational.new(-2) + br(-6, 6).round(:ties_away).should eq BigRational.new(-1) + br(-5, 6).round(:ties_away).should eq BigRational.new(-1) + br(-3, 6).round(:ties_away).should eq BigRational.new(-1) + br(-1, 6).round(:ties_away).should eq BigRational.new(0) + br(0, 6).round(:ties_away).should eq BigRational.new(0) + br(1, 6).round(:ties_away).should eq BigRational.new(0) + br(3, 6).round(:ties_away).should eq BigRational.new(1) + br(5, 6).round(:ties_away).should eq BigRational.new(1) + br(6, 6).round(:ties_away).should eq BigRational.new(1) + br(9, 6).round(:ties_away).should eq BigRational.new(2) + br(15, 6).round(:ties_away).should eq BigRational.new(3) + end + + it "default (=ties_even)" do + br(-15, 6).round.should eq BigRational.new(-2) + br(-9, 6).round.should eq BigRational.new(-2) + br(-6, 6).round.should eq BigRational.new(-1) + br(-5, 6).round.should eq BigRational.new(-1) + br(-3, 6).round.should eq BigRational.new(0) + br(-1, 6).round.should eq BigRational.new(0) + br(0, 6).round.should eq BigRational.new(0) + br(1, 6).round.should eq BigRational.new(0) + br(3, 6).round.should eq BigRational.new(0) + br(5, 6).round.should eq BigRational.new(1) + br(6, 6).round.should eq BigRational.new(1) + br(9, 6).round.should eq BigRational.new(2) + br(15, 6).round.should eq BigRational.new(2) + end + end + end + it "#hash" do b = br(10, 3) hash = b.hash diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index d8975e637341..6b6a6c0bb0b2 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -154,16 +154,29 @@ struct BigRational < Number Number.expand_div [BigInt, BigFloat, BigDecimal], BigRational def ceil : BigRational - diff = (denominator - numerator % denominator) % denominator - BigRational.new(numerator + diff, denominator) + BigRational.new(-(-numerator // denominator)) end def floor : BigRational - BigRational.new(numerator - numerator % denominator, denominator) + BigRational.new(numerator // denominator) end def trunc : BigRational - self < 0 ? ceil : floor + BigRational.new(numerator.tdiv(denominator)) + end + + def round_away : BigRational + rem2 = numerator.remainder(denominator).abs * 2 + x = BigRational.new(numerator.tdiv(denominator)) + x += sign if rem2 >= denominator + x + end + + def round_even : BigRational + rem2 = numerator.remainder(denominator).abs * 2 + x = BigRational.new(numerator.tdiv(denominator)) + x += sign if rem2 > denominator || (rem2 == denominator && x.numerator.odd?) + x end # Divides the rational by (2 ** *other*) From 67885d6ac3c0dde2acdadaed927221dafc08e817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 17 Oct 2023 11:12:31 +0200 Subject: [PATCH 0720/1551] Add `Enumerable#present?` (#13866) Co-authored-by: Quinton Miller --- spec/std/enumerable_spec.cr | 7 ++++++- src/enumerable.cr | 29 ++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 3b3cef2bd0c1..624666d35992 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -436,11 +436,16 @@ describe "Enumerable" do end end - describe "empty?" do + describe "#empty?" do it { SpecEnumerable.new.empty?.should be_false } it { SpecEmptyEnumerable.new.empty?.should be_true } end + describe "#present?" do + it { SpecEnumerable.new.present?.should be_true } + it { SpecEmptyEnumerable.new.present?.should be_false } + end + describe "find" do it "finds" do [1, 2, 3].find { |x| x > 2 }.should eq(3) diff --git a/src/enumerable.cr b/src/enumerable.cr index 7d95cb6f81b2..d3f9a1dbb02a 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -107,7 +107,16 @@ module Enumerable(T) # ``` # [nil, true, 99].any? # => true # [nil, false].any? # => false + # ([] of Int32).any? # => false # ``` + # + # * `#present?` does not consider truthiness of elements. + # * `#any?(&)` and `#any(pattern)` allow custom conditions. + # + # NOTE: `#any?` usually has the same semantics as `#present?`. They only + # differ if the element type can be falsey (i.e. `T <= Nil || T <= Pointer || T <= Bool`). + # It's typically advised to prefer `#present?` unless these specific truthiness + # semantics are required. def any? : Bool any? &.itself end @@ -1613,17 +1622,35 @@ module Enumerable(T) count { true } end - # Returns `true` if `self` is empty, `false` otherwise. + # Returns `true` if `self` does not contain any element. # # ``` # ([] of Int32).empty? # => true # ([1]).empty? # => false + # [nil, false].empty? # => false # ``` + # + # * `#present?` returns the inverse. def empty? : Bool each { return false } true end + # Returns `true` if `self` contains at least one element. + # + # ``` + # ([] of Int32).present? # => false + # ([1]).present? # => true + # [nil, false].present? # => true + # ``` + # + # * `#empty?` returns the inverse. + # * `#any?` considers only truthy elements. + # * `#any?(&)` and `#any(pattern)` allow custom conditions. + def present? : Bool + !empty? + end + # Returns an `Array` with the first *count* elements removed # from the original collection. # From 80b0ee8beafce6a9484f21431d504d3d1f6ff7ea Mon Sep 17 00:00:00 2001 From: Margret Riegert Date: Tue, 17 Oct 2023 12:06:37 -0400 Subject: [PATCH 0721/1551] Add "--dry-run" flag for specs (#13804) --- src/spec/cli.cr | 6 ++++++ src/spec/example.cr | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/spec/cli.cr b/src/spec/cli.cr index 5e514d01a3c6..77856019181b 100644 --- a/src/spec/cli.cr +++ b/src/spec/cli.cr @@ -19,6 +19,9 @@ module Spec # :nodoc: class_property? focus = false + # :nodoc: + class_property? dry_run = false + # :nodoc: def self.add_location(file, line) locations = @@locations ||= {} of String => Array(Int32) @@ -111,6 +114,9 @@ module Spec opts.on("--no-color", "Disable ANSI colored output") do Colorize.enabled = false end + opts.on("--dry-run", "Pass all tests without execution") do + Spec.dry_run = true + end opts.unknown_args do |args| end end diff --git a/src/spec/example.cr b/src/spec/example.cr index 01f46a6fae62..e6ae51942a38 100644 --- a/src/spec/example.cr +++ b/src/spec/example.cr @@ -21,6 +21,11 @@ module Spec Spec.root_context.check_nesting_spec(file, line) do Spec.formatters.each(&.before_example(description)) + if Spec.dry_run? + @parent.report(:success, description, file, line) + return + end + unless block = @block @parent.report(:pending, description, file, line) return From 955f7a7cb0dd6a794345edc47a929f5eec05df6b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 18 Oct 2023 00:07:01 +0800 Subject: [PATCH 0722/1551] Make `String#to_f(whitespace: false)` work with infinity and NaN (#13875) --- spec/std/string_spec.cr | 18 ++++++++++++++++++ src/string.cr | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index d079f7618948..798b547eb06e 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -511,10 +511,16 @@ describe "String" do " NaN".to_f?.try(&.nan?).should be_true "NaN".to_f?.try(&.nan?).should be_true "-NaN".to_f?.try(&.nan?).should be_true + "nan".to_f?(whitespace: false).try(&.nan?).should be_true + " nan".to_f?(whitespace: false).should be_nil + "nan ".to_f?(whitespace: false).should be_nil + "nani".to_f?(strict: true).should be_nil " INF".to_f?.should eq Float64::INFINITY "INF".to_f?.should eq Float64::INFINITY "-INF".to_f?.should eq -Float64::INFINITY " +INF".to_f?.should eq Float64::INFINITY + "inf".to_f?(whitespace: false).should eq Float64::INFINITY + "info".to_f?(strict: true).should be_nil end it "does to_f32" do @@ -548,10 +554,16 @@ describe "String" do " NaN".to_f32?.try(&.nan?).should be_true "NaN".to_f32?.try(&.nan?).should be_true "-NaN".to_f32?.try(&.nan?).should be_true + "nan".to_f32?(whitespace: false).try(&.nan?).should be_true + " nan".to_f32?(whitespace: false).should be_nil + "nan ".to_f32?(whitespace: false).should be_nil + "nani".to_f32?(strict: true).should be_nil " INF".to_f32?.should eq Float32::INFINITY "INF".to_f32?.should eq Float32::INFINITY "-INF".to_f32?.should eq -Float32::INFINITY " +INF".to_f32?.should eq Float32::INFINITY + "inf".to_f32?(whitespace: false).should eq Float32::INFINITY + "info".to_f32?(strict: true).should be_nil end it "does to_f64" do @@ -585,10 +597,16 @@ describe "String" do " NaN".to_f64?.try(&.nan?).should be_true "NaN".to_f64?.try(&.nan?).should be_true "-NaN".to_f64?.try(&.nan?).should be_true + "nan".to_f64?(whitespace: false).try(&.nan?).should be_true + " nan".to_f64?(whitespace: false).should be_nil + "nan ".to_f64?(whitespace: false).should be_nil + "nani".to_f64?(strict: true).should be_nil " INF".to_f64?.should eq Float64::INFINITY "INF".to_f64?.should eq Float64::INFINITY "-INF".to_f64?.should eq -Float64::INFINITY " +INF".to_f64?.should eq Float64::INFINITY + "inf".to_f64?(whitespace: false).should eq Float64::INFINITY + "info".to_f64?(strict: true).should be_nil end it "compares strings: different size" do diff --git a/src/string.cr b/src/string.cr index 551c2d2dd9a2..3c378bd1d455 100644 --- a/src/string.cr +++ b/src/string.cr @@ -752,7 +752,7 @@ class String end private def to_f_impl(whitespace : Bool = true, strict : Bool = true, &) - return unless whitespace || '0' <= self[0] <= '9' || self[0].in?('-', '+') + return unless whitespace || '0' <= self[0] <= '9' || self[0].in?('-', '+', 'i', 'I', 'n', 'N') v, endptr = yield From 580b2fc502463769a9150031ccdf063d762cf86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 17 Oct 2023 18:07:26 +0200 Subject: [PATCH 0723/1551] Refactor specs for `HTML.unescape` (#13842) --- spec/std/html_spec.cr | 156 +++++++++++++++++++----------------------- 1 file changed, 69 insertions(+), 87 deletions(-) diff --git a/spec/std/html_spec.cr b/spec/std/html_spec.cr index 016dd0d04aa5..39e80a5961ca 100644 --- a/spec/std/html_spec.cr +++ b/spec/std/html_spec.cr @@ -4,124 +4,106 @@ require "html" describe "HTML" do describe ".escape" do it "does not change a safe string" do - str = HTML.escape("safe_string") - - str.should eq("safe_string") + HTML.escape("safe_string").should eq("safe_string") end it "escapes dangerous characters from a string" do - str = HTML.escape("< & > ' \"") - - str.should eq("< & > ' "") + HTML.escape("< & > ' \"").should eq("< & > ' "") end end describe ".unescape" do - it "does not change a safe string" do - str = HTML.unescape("safe_string") - - str.should eq("safe_string") - end - - it "unescapes html special characters" do - str = HTML.unescape("< & >") - - str.should eq("< & >") - end - - it "unescapes javascript example from a string" do - str = HTML.unescape("<script>alert('You are being hacked')</script>") - - str.should eq("") - end - - it "unescapes decimal encoded chars" do - str = HTML.unescape("<hello world>") - - str.should eq("") + it "identity" do + HTML.unescape("safe_string").should be("safe_string") end - it "unescapes with invalid entities" do - str = HTML.unescape("&<&>"&abcdefghijklmn &ThisIsNotAnEntity;") - - str.should eq("&<&>\"&abcdefghijklmn &ThisIsNotAnEntity;") + it "empty entity" do + HTML.unescape("foo&;bar").should eq "foo&;bar" end - it "unescapes hex encoded chars" do - str = HTML.unescape("3 + 2 = 5") + context "numeric entities" do + it "decimal" do + HTML.unescape("3 + 2 = 5").should eq("3 + 2 = 5") + end - str.should eq("3 + 2 = 5") - end + it "hex" do + HTML.unescape("3 + 2 = 5").should eq("3 + 2 = 5") + end - it "unescapes decimal encoded chars" do - str = HTML.unescape("3 + 2 = 5") + it "early termination" do + HTML.unescape("&# &#x €43 ©f ©").should eq "&# &#x €43 ©f ©" + end - str.should eq("3 + 2 = 5") - end + it "ISO-8859-1 replacement" do + HTML.unescape("‡").should eq "‡" + end - it "unescapes  " do - str = HTML.unescape("nbsp space ") + it "does not unescape Char::MAX_CODEPOINT" do + # U+10FFFF and U+10FFFE are noncharacter and are not replaced + HTML.unescape("limit 􏿿").should eq("limit 􏿿") + HTML.unescape("limit 􏿾").should eq("limit 􏿾") + HTML.unescape("limit 􏿽").should eq("limit \u{10FFFD}") + end - str.should eq("nbsp\u{0000A0}space ") - end + it "does not unescape characters above Char::MAX_CODEPOINT" do + HTML.unescape("limit �").should eq("limit \uFFFD") + HTML.unescape("limit �").should eq("limit \uFFFD") + end - it "does not unescape Char::MAX_CODEPOINT" do - # Char::MAX_CODEPOINT is actually a noncharacter and is not replaced - str = HTML.unescape("limit 􏿿") - str.should eq("limit 􏿿") + it "space characters" do + HTML.unescape(" €Ÿ").should eq(" \t\n\f\u20AC\u0178") + end - str = HTML.unescape("limit 􏿿") - str.should eq("limit 􏿿") - end + it "does not escape non-space unicode control characters" do + HTML.unescape("- �").should eq("- \uFFFD") + end - it "does not unescape characters above Char::MAX_CODEPOINT" do - str = HTML.unescape("limit �") - str.should eq("limit \uFFFD") + it "does not escape noncharacter codepoints" do + # noncharacters http://www.unicode.org/faq/private_use.html + string = "﷐-﷯ ￾ &#FFFF; 🿾 🿿 𯿾 􏿿" + HTML.unescape(string).should eq(string) + end - str = HTML.unescape("limit �") - str.should eq("limit \uFFFD") + it "does not escape unicode surrogate characters" do + HTML.unescape("�-�").should eq("\uFFFD-\uFFFD") + end end - it "unescapes ⊐̸" do - str = HTML.unescape(" ⊐̸ ") + context "named entities" do + it "simple named entities" do + HTML.unescape("< & >").should eq("< & >") + HTML.unescape("nbsp space ").should eq("nbsp\u{0000A0}space ") + end - str.should eq(" ⊐̸ ") - end + it "without trailing semicolon" do + HTML.unescape("&hello").should eq("&hello") + end - it "unescapes entities without trailing semicolon" do - str = HTML.unescape("&hello") - str.should eq("&hello") - end - - it "unescapes named character reference with numerical characters" do - str = HTML.unescape("¾") - str.should eq("\u00BE") - end + it "end of string" do + HTML.unescape("& &").should eq("& &") + end - it "does not escape unicode control characters except space characters" do - string = "- " - HTML.unescape(string).should eq(string) + it "multi codepoint" do + HTML.unescape(" ⊐̸ ").should eq(" ⊐̸ ") + end - string = HTML.unescape("€-Ÿ") - string.should eq("\u20AC-\u0178") + it "invalid entities" do + HTML.unescape("&<&>"&abcdefghijklmn &ThisIsNotAnEntity;").should eq("&<&>\"&abcdefghijklmn &ThisIsNotAnEntity;") + end - HTML.unescape("�").should eq("\uFFFD") + it "entity with numerical characters" do + HTML.unescape("¾").should eq("\u00BE") + end end - it "escapes space characters" do - string = HTML.unescape(" ") - string.should eq(" \t\n\f") - end - - it "does not escape noncharacter codepoints" do - # noncharacters http://www.unicode.org/faq/private_use.html - string = "﷐-﷯ ￾ &#FFFF; 🿾 🿿 𯿾 􏿿" - HTML.unescape(string).should eq(string) + it "unescapes javascript example from a string" do + HTML.unescape("<script>alert('You are being hacked')</script>").should eq("") end - it "does not escape unicode surrogate characters" do - string = "�-�" - HTML.unescape(string).should eq("\uFFFD-\uFFFD") + it "invalid utf-8" do + expect_raises(ArgumentError, "UTF-8 error") do + HTML.unescape("test \xff\xfe").should eq "test \xff\xfe" + end end end end From 21c55ffce5fa3055813661b8843f9a555ceef8b8 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 18 Oct 2023 18:00:49 +0800 Subject: [PATCH 0724/1551] Fix `Box(T?)` crashing on `nil` (#13893) --- spec/std/box_spec.cr | 39 +++++++++++++++++++++++++++++++++++++++ src/box.cr | 29 ++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/spec/std/box_spec.cr b/spec/std/box_spec.cr index 8b889a2cdf53..b592b20ca9a6 100644 --- a/spec/std/box_spec.cr +++ b/spec/std/box_spec.cr @@ -15,10 +15,49 @@ describe "Box" do Box(String).unbox(box).should be(a) end + it "boxing a nilable reference returns the same pointer" do + a = "foo".as(String?) + box = Box.box(a) + box.address.should eq(a.object_id) + + b = Box(String?).unbox(box) + b.should be_a(String) + b.should be(a) + end + + it "boxing a nilable value returns the same value" do + a = 1.as(Int32?) + box = Box.box(a) + + b = Box(Int32?).unbox(box) + b.should be_a(Int32) + b.should eq(a) + end + + it "boxes with explicit type" do + box = Box(Int32?).box(1) + b = Box(Int32?).unbox(box) + b.should be_a(Int32) + b.should eq(1) + end + it "boxing nil returns a null pointer" do box = Box.box(nil) box.address.should eq(0) Box(Nil).unbox(box).should be_nil end + + it "boxing nil in a reference-like union returns a null pointer (#11839)" do + box = Box.box(nil.as(String?)) + box.address.should eq(0) + + Box(String?).unbox(box).should be_nil + end + + it "boxing nil in a value-like union doesn't crash (#11839)" do + box = Box.box(nil.as(Int32?)) + + Box(Int32?).unbox(box).should be_nil + end end diff --git a/src/box.cr b/src/box.cr index 652292efd080..78799838e688 100644 --- a/src/box.cr +++ b/src/box.cr @@ -14,20 +14,31 @@ class Box(T) def initialize(@object : T) end - # Creates a Box for a reference type (or `nil`) and returns the same pointer (or `NULL`) - def self.box(r : Reference?) : Void* - r.as(Void*) - end - - # Creates a Box for an object and returns it as a `Void*`. - def self.box(object) : Void* - new(object).as(Void*) + # Turns *object* into a `Void*`. + # + # If `T` is not a reference type, nor a union between reference types and + # `Nil`, this method effectively copies *object* to the dynamic heap. + # + # NOTE: The returned pointer might not be a null pointer even when *object* is + # `nil`. + def self.box(object : T) : Void* + {% if T.union_types.all? { |t| t == Nil || t < Reference } %} + object.as(Void*) + {% else %} + # NOTE: if `T` is explicitly specified and `typeof(object) < T` (e.g. + # `Box(Int32?).box(1)`, then `.new` will perform the appropriate upcast + new(object).as(Void*) + {% end %} end # Unboxes a `Void*` into an object of type `T`. Note that for this you must # specify T: `Box(T).unbox(data)`. + # + # WARNING: It is undefined behavior to box an object in one type and unbox it + # via a different type; in particular, when boxing a `T` and unboxing it as a + # `T?`, or vice-versa. def self.unbox(pointer : Void*) : T - {% if T <= Reference || T == Nil %} + {% if T.union_types.all? { |t| t == Nil || t < Reference } %} pointer.as(T) {% else %} pointer.as(self).object From 9e625d5b9c24f87f452e186521cf68ac61868163 Mon Sep 17 00:00:00 2001 From: Philip Ross Date: Wed, 18 Oct 2023 03:00:56 -0700 Subject: [PATCH 0725/1551] Add `Enumerable#each_step` and `Iterable#each_step` (#13610) Co-authored-by: Sijawusz Pur Rahnama --- spec/std/enumerable_spec.cr | 38 +++++++++++++++++++++++++++++++++++++ src/enumerable.cr | 38 +++++++++++++++++++++++++++++++++++++ src/iterable.cr | 14 ++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 624666d35992..f15ce053216e 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -1,4 +1,5 @@ require "spec" +require "spec/helpers/iterate" private class SpecEnumerable include Enumerable(Int32) @@ -393,6 +394,43 @@ describe "Enumerable" do end end + describe "each_step" do + it_iterates "yields every 2nd element", %w[a c e], %w[a b c d e f].each_step(2) + it_iterates "accepts an optional offset parameter", %w[b d f], %w[a b c d e f].each_step(2, offset: 1) + it_iterates "accepts an offset of 0", %w[a c e], %w[a b c d e f].each_step(2, offset: 0) + it_iterates "accepts an offset larger then the step size", %w[d f], %w[a b c d e f].each_step(2, offset: 3) + + it_iterates "accepts a step larger then the enumerable size", %w[a], %w[a b c d e f].each_step(7) + it_iterates "accepts an offset larger then the enumerable size", %w[], %w[a b c d e f].each_step(1, offset: 7) + + it "doesn't accept a negative step" do + expect_raises(ArgumentError) do + %w[a b c d e f].each_step(-2) + end + expect_raises(ArgumentError) do + %w[a b c d e f].each_step(-2) { } + end + end + + it "doesn't accept a step of 0" do + expect_raises(ArgumentError) do + %w[a b c d e f].each_step(0) + end + expect_raises(ArgumentError) do + %w[a b c d e f].each_step(0) { } + end + end + + it "doesn't accept a negative offset" do + expect_raises(ArgumentError) do + %w[a b c d e f].each_step(2, offset: -2) + end + expect_raises(ArgumentError) do + %w[a b c d e f].each_step(2, offset: -2) { } + end + end + end + describe "each_with_index" do it "yields the element and the index" do collection = [] of {String, Int32} diff --git a/src/enumerable.cr b/src/enumerable.cr index d3f9a1dbb02a..c7b566e647e4 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -435,6 +435,44 @@ module Enumerable(T) nil end + # Iterates over the collection, yielding every *n*th element, starting with the first. + # + # ``` + # %w[Alice Bob Charlie David].each_step(2) do |user| + # puts "User: #{user}" + # end + # ``` + # + # Prints: + # + # ```text + # User: Alice + # User: Charlie + # ``` + # + # Accepts an optional *offset* parameter + # + # ``` + # %w[Alice Bob Charlie David].each_step(2, offset: 1) do |user| + # puts "User: #{user}" + # end + # ``` + # + # Which would print: + # + # ```text + # User: Bob + # User: David + # ``` + def each_step(n : Int, *, offset : Int = 0, & : T ->) : Nil + raise ArgumentError.new("Invalid n size: #{n}") if n <= 0 + raise ArgumentError.new("Invalid offset size: #{offset}") if offset < 0 + offset_mod = offset % n + each_with_index do |elem, i| + yield elem if i >= offset && i % n == offset_mod + end + end + # Iterates over the collection, yielding both the elements and their index. # # ``` diff --git a/src/iterable.cr b/src/iterable.cr index 86351bba0e70..f2d70e85062e 100644 --- a/src/iterable.cr +++ b/src/iterable.cr @@ -51,6 +51,20 @@ module Iterable(T) each.cons_pair end + # Same as `each.step(n)`. + # + # See also: `Iterator#step`. + def each_step(n : Int) + each.step(n) + end + + # Same as `each.skip(offset).step(n)`. + # + # See also: `Iterator#step`. + def each_step(n : Int, *, offset : Int) + each.skip(offset).step(n) + end + # Same as `each.with_index(offset)`. # # See also: `Iterator#with_index(offset)`. From 7d969a4d610fba3d93d464afdfe0448ab121df29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 18 Oct 2023 12:01:05 +0200 Subject: [PATCH 0726/1551] Fix check for file type (#13760) --- spec/compiler/crystal_path/crystal_path_spec.cr | 3 +++ spec/compiler/crystal_path/foo.cr/foo.cr | 0 src/compiler/crystal/crystal_path.cr | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 spec/compiler/crystal_path/foo.cr/foo.cr diff --git a/spec/compiler/crystal_path/crystal_path_spec.cr b/spec/compiler/crystal_path/crystal_path_spec.cr index d9ecb3f0b9f2..fa2091223d54 100644 --- a/spec/compiler/crystal_path/crystal_path_spec.cr +++ b/spec/compiler/crystal_path/crystal_path_spec.cr @@ -68,6 +68,9 @@ describe Crystal::CrystalPath do assert_finds "../test_folder", relative_to: "test_files/test_folder/file_three.cr", results: [ "test_files/test_folder/test_folder.cr", ] + assert_finds "foo.cr", results: [ + "foo.cr/foo.cr", + ] # For `require "foo"`: # 1. foo.cr (to find something in the standard library) diff --git a/spec/compiler/crystal_path/foo.cr/foo.cr b/spec/compiler/crystal_path/foo.cr/foo.cr new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/compiler/crystal/crystal_path.cr b/src/compiler/crystal/crystal_path.cr index 6ba5b96033ef..e957a513a714 100644 --- a/src/compiler/crystal/crystal_path.cr +++ b/src/compiler/crystal/crystal_path.cr @@ -120,7 +120,7 @@ module Crystal each_file_expansion(filename, relative_to) do |path| absolute_path = File.expand_path(path, dir: @current_dir) - return absolute_path if File.exists?(absolute_path) + return absolute_path if File.file?(absolute_path) end nil From a7f75206f3e53d4c455ff4fadd33a974422159e0 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 19 Oct 2023 03:51:18 +0800 Subject: [PATCH 0727/1551] Support Unicode 15.1.0 (#13812) --- scripts/generate_grapheme_break_specs.cr | 5 +- spec/std/string/grapheme_break_spec.cr | 719 ++++++++++++++++++++--- src/string/grapheme/properties.cr | 4 +- src/unicode/data.cr | 3 +- src/unicode/unicode.cr | 2 +- 5 files changed, 661 insertions(+), 72 deletions(-) diff --git a/scripts/generate_grapheme_break_specs.cr b/scripts/generate_grapheme_break_specs.cr index a03f154b89ef..72b868c3aa46 100644 --- a/scripts/generate_grapheme_break_specs.cr +++ b/scripts/generate_grapheme_break_specs.cr @@ -39,6 +39,9 @@ File.open(path, "w") do |file| format, _, comment = line.partition('#') + # TODO: implement grapheme boundary rule GB9c in UAX29 + pending = comment.includes?("[9.3]") + graphemes = [] of String | Char string = String.build do |io| grapheme = String::Builder.new @@ -61,7 +64,7 @@ File.open(path, "w") do |file| graphemes << string_or_char(grapheme.to_s) end - file.puts " it_iterates_graphemes #{string.dump}, [#{graphemes.join(", ", &.dump)}] # #{comment}" + file.puts " #{%(pending "GB9c" { ) if pending} it_iterates_graphemes #{string.dump}, [#{graphemes.join(", ", &.dump)}] #{" }" if pending} # #{comment}" end file.puts "end" end diff --git a/spec/std/string/grapheme_break_spec.cr b/spec/std/string/grapheme_break_spec.cr index 9ff17889fe05..f1a86656ef12 100644 --- a/spec/std/string/grapheme_break_spec.cr +++ b/spec/std/string/grapheme_break_spec.cr @@ -22,8 +22,8 @@ describe "String#each_grapheme" do it_iterates_graphemes " \u0308\u{1F1E6}", [" \u0308", '\u{1F1E6}'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes " \u0600", [' ', '\u0600'] # ÷ [0.2] SPACE (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes " \u0308\u0600", [" \u0308", '\u0600'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes " \u0903", [" \u0903"] # ÷ [0.2] SPACE (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes " \u0308\u0903", [" \u0308\u0903"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes " \u0A03", [" \u0A03"] # ÷ [0.2] SPACE (Other) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes " \u0308\u0A03", [" \u0308\u0A03"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes " \u1100", [' ', '\u1100'] # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes " \u0308\u1100", [" \u0308", '\u1100'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes " \u1160", [' ', '\u1160'] # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -34,10 +34,24 @@ describe "String#each_grapheme" do it_iterates_graphemes " \u0308\uAC00", [" \u0308", '\uAC00'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes " \uAC01", [' ', '\uAC01'] # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes " \u0308\uAC01", [" \u0308", '\uAC01'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes " \u0900", [" \u0900"] # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes " \u0308\u0900", [" \u0308\u0900"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes " \u0903", [" \u0903"] # ÷ [0.2] SPACE (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes " \u0308\u0903", [" \u0308\u0903"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes " \u0904", [' ', '\u0904'] # ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes " \u0308\u0904", [" \u0308", '\u0904'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes " \u0D4E", [' ', '\u0D4E'] # ÷ [0.2] SPACE (Other) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes " \u0308\u0D4E", [" \u0308", '\u0D4E'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes " \u0915", [' ', '\u0915'] # ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes " \u0308\u0915", [" \u0308", '\u0915'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes " \u231A", [' ', '\u231A'] # ÷ [0.2] SPACE (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes " \u0308\u231A", [" \u0308", '\u231A'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes " \u0300", [" \u0300"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u0308\u0300", [" \u0308\u0300"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes " \u093C", [" \u093C"] # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes " \u0308\u093C", [" \u0308\u093C"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes " \u094D", [" \u094D"] # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes " \u0308\u094D", [" \u0308\u094D"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u200D", [" \u200D"] # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u0308\u200D", [" \u0308\u200D"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u0378", [' ', '\u0378'] # ÷ [0.2] SPACE (Other) ÷ [999.0] (Other) ÷ [0.3] @@ -56,8 +70,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\r\u0308\u{1F1E6}", ['\r', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\r\u0600", ['\r', '\u0600'] # ÷ [0.2] (CR) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\r\u0308\u0600", ['\r', '\u0308', '\u0600'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\r\u0903", ['\r', '\u0903'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0903", ['\r', "\u0308\u0903"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\r\u0A03", ['\r', '\u0A03'] # ÷ [0.2] (CR) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0A03", ['\r', "\u0308\u0A03"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\r\u1100", ['\r', '\u1100'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\r\u0308\u1100", ['\r', '\u0308', '\u1100'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\r\u1160", ['\r', '\u1160'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -68,10 +82,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\r\u0308\uAC00", ['\r', '\u0308', '\uAC00'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\r\uAC01", ['\r', '\uAC01'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\r\u0308\uAC01", ['\r', '\u0308', '\uAC01'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\r\u0900", ['\r', '\u0900'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0900", ['\r', "\u0308\u0900"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\r\u0903", ['\r', '\u0903'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0903", ['\r', "\u0308\u0903"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\r\u0904", ['\r', '\u0904'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0904", ['\r', '\u0308', '\u0904'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\r\u0D4E", ['\r', '\u0D4E'] # ÷ [0.2] (CR) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0D4E", ['\r', '\u0308', '\u0D4E'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\r\u0915", ['\r', '\u0915'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0915", ['\r', '\u0308', '\u0915'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\r\u231A", ['\r', '\u231A'] # ÷ [0.2] (CR) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\r\u0308\u231A", ['\r', '\u0308', '\u231A'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\r\u0300", ['\r', '\u0300'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\r\u0308\u0300", ['\r', "\u0308\u0300"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u093C", ['\r', '\u093C'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u093C", ['\r', "\u0308\u093C"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u094D", ['\r', '\u094D'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u094D", ['\r', "\u0308\u094D"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\r\u200D", ['\r', '\u200D'] # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\r\u0308\u200D", ['\r', "\u0308\u200D"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\r\u0378", ['\r', '\u0378'] # ÷ [0.2] (CR) ÷ [4.0] (Other) ÷ [0.3] @@ -90,8 +118,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\n\u0308\u{1F1E6}", ['\n', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\n\u0600", ['\n', '\u0600'] # ÷ [0.2] (LF) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\n\u0308\u0600", ['\n', '\u0308', '\u0600'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\n\u0903", ['\n', '\u0903'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\n\u0308\u0903", ['\n', "\u0308\u0903"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\n\u0A03", ['\n', '\u0A03'] # ÷ [0.2] (LF) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\n\u0308\u0A03", ['\n', "\u0308\u0A03"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\n\u1100", ['\n', '\u1100'] # ÷ [0.2] (LF) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\n\u0308\u1100", ['\n', '\u0308', '\u1100'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\n\u1160", ['\n', '\u1160'] # ÷ [0.2] (LF) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -102,10 +130,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\n\u0308\uAC00", ['\n', '\u0308', '\uAC00'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\n\uAC01", ['\n', '\uAC01'] # ÷ [0.2] (LF) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\n\u0308\uAC01", ['\n', '\u0308', '\uAC01'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\n\u0900", ['\n', '\u0900'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\n\u0308\u0900", ['\n', "\u0308\u0900"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\n\u0903", ['\n', '\u0903'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\n\u0308\u0903", ['\n', "\u0308\u0903"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\n\u0904", ['\n', '\u0904'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\n\u0308\u0904", ['\n', '\u0308', '\u0904'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\n\u0D4E", ['\n', '\u0D4E'] # ÷ [0.2] (LF) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\n\u0308\u0D4E", ['\n', '\u0308', '\u0D4E'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\n\u0915", ['\n', '\u0915'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\n\u0308\u0915", ['\n', '\u0308', '\u0915'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\n\u231A", ['\n', '\u231A'] # ÷ [0.2] (LF) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\n\u0308\u231A", ['\n', '\u0308', '\u231A'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\n\u0300", ['\n', '\u0300'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\n\u0308\u0300", ['\n', "\u0308\u0300"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\n\u093C", ['\n', '\u093C'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\n\u0308\u093C", ['\n', "\u0308\u093C"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\n\u094D", ['\n', '\u094D'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\n\u0308\u094D", ['\n', "\u0308\u094D"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\n\u200D", ['\n', '\u200D'] # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\n\u0308\u200D", ['\n', "\u0308\u200D"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\n\u0378", ['\n', '\u0378'] # ÷ [0.2] (LF) ÷ [4.0] (Other) ÷ [0.3] @@ -124,8 +166,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0001\u0308\u{1F1E6}", ['\u0001', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0001\u0600", ['\u0001', '\u0600'] # ÷ [0.2] (Control) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u0600", ['\u0001', '\u0308', '\u0600'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u0001\u0903", ['\u0001', '\u0903'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u0001\u0308\u0903", ['\u0001', "\u0308\u0903"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0001\u0A03", ['\u0001', '\u0A03'] # ÷ [0.2] (Control) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\u0A03", ['\u0001', "\u0308\u0A03"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\u0001\u1100", ['\u0001', '\u1100'] # ÷ [0.2] (Control) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u1100", ['\u0001', '\u0308', '\u1100'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u0001\u1160", ['\u0001', '\u1160'] # ÷ [0.2] (Control) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -136,10 +178,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0001\u0308\uAC00", ['\u0001', '\u0308', '\uAC00'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0001\uAC01", ['\u0001', '\uAC01'] # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\uAC01", ['\u0001', '\u0308', '\uAC01'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0001\u0900", ['\u0001', '\u0900'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\u0900", ['\u0001', "\u0308\u0900"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0001\u0903", ['\u0001', '\u0903'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\u0903", ['\u0001', "\u0308\u0903"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0001\u0904", ['\u0001', '\u0904'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\u0904", ['\u0001', '\u0308', '\u0904'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0001\u0D4E", ['\u0001', '\u0D4E'] # ÷ [0.2] (Control) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\u0D4E", ['\u0001', '\u0308', '\u0D4E'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0001\u0915", ['\u0001', '\u0915'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\u0915", ['\u0001', '\u0308', '\u0915'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\u0001\u231A", ['\u0001', '\u231A'] # ÷ [0.2] (Control) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u231A", ['\u0001', '\u0308', '\u231A'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0001\u0300", ['\u0001', '\u0300'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u0300", ['\u0001', "\u0308\u0300"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0001\u093C", ['\u0001', '\u093C'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\u093C", ['\u0001', "\u0308\u093C"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0001\u094D", ['\u0001', '\u094D'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\u094D", ['\u0001', "\u0308\u094D"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0001\u200D", ['\u0001', '\u200D'] # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u200D", ['\u0001', "\u0308\u200D"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0001\u0378", ['\u0001', '\u0378'] # ÷ [0.2] (Control) ÷ [4.0] (Other) ÷ [0.3] @@ -158,8 +214,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u034F\u0308\u{1F1E6}", ["\u034F\u0308", '\u{1F1E6}'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u034F\u0600", ['\u034F', '\u0600'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\u034F\u0308\u0600", ["\u034F\u0308", '\u0600'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u034F\u0903", ["\u034F\u0903"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u0903", ["\u034F\u0308\u0903"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u034F\u0A03", ["\u034F\u0A03"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u034F\u0308\u0A03", ["\u034F\u0308\u0A03"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\u034F\u1100", ['\u034F', '\u1100'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u034F\u0308\u1100", ["\u034F\u0308", '\u1100'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u034F\u1160", ['\u034F', '\u1160'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -170,10 +226,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u034F\u0308\uAC00", ["\u034F\u0308", '\uAC00'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u034F\uAC01", ['\u034F', '\uAC01'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u034F\u0308\uAC01", ["\u034F\u0308", '\uAC01'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u034F\u0900", ["\u034F\u0900"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u034F\u0308\u0900", ["\u034F\u0308\u0900"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u034F\u0903", ["\u034F\u0903"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u034F\u0308\u0903", ["\u034F\u0308\u0903"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u034F\u0904", ['\u034F', '\u0904'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u034F\u0308\u0904", ["\u034F\u0308", '\u0904'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u034F\u0D4E", ['\u034F', '\u0D4E'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u034F\u0308\u0D4E", ["\u034F\u0308", '\u0D4E'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u034F\u0915", ['\u034F', '\u0915'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u034F\u0308\u0915", ["\u034F\u0308", '\u0915'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\u034F\u231A", ['\u034F', '\u231A'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u034F\u0308\u231A", ["\u034F\u0308", '\u231A'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u034F\u0300", ["\u034F\u0300"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u034F\u0308\u0300", ["\u034F\u0308\u0300"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u034F\u093C", ["\u034F\u093C"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u034F\u0308\u093C", ["\u034F\u0308\u093C"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u034F\u094D", ["\u034F\u094D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u034F\u0308\u094D", ["\u034F\u0308\u094D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u034F\u200D", ["\u034F\u200D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u034F\u0308\u200D", ["\u034F\u0308\u200D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u034F\u0378", ['\u034F', '\u0378'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] (Other) ÷ [0.3] @@ -192,8 +262,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u{1F1E6}\u0308\u{1F1E6}", ["\u{1F1E6}\u0308", '\u{1F1E6}'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0600", ['\u{1F1E6}', '\u0600'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\u0600", ["\u{1F1E6}\u0308", '\u0600'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\u0903", ["\u{1F1E6}\u0903"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\u0308\u0903", ["\u{1F1E6}\u0308\u0903"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0A03", ["\u{1F1E6}\u0A03"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\u0A03", ["\u{1F1E6}\u0308\u0A03"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u1100", ['\u{1F1E6}', '\u1100'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\u1100", ["\u{1F1E6}\u0308", '\u1100'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u1160", ['\u{1F1E6}', '\u1160'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -204,10 +274,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u{1F1E6}\u0308\uAC00", ["\u{1F1E6}\u0308", '\uAC00'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\uAC01", ['\u{1F1E6}', '\uAC01'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\uAC01", ["\u{1F1E6}\u0308", '\uAC01'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0900", ["\u{1F1E6}\u0900"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\u0900", ["\u{1F1E6}\u0308\u0900"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0903", ["\u{1F1E6}\u0903"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\u0903", ["\u{1F1E6}\u0308\u0903"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0904", ['\u{1F1E6}', '\u0904'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\u0904", ["\u{1F1E6}\u0308", '\u0904'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0D4E", ['\u{1F1E6}', '\u0D4E'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\u0D4E", ["\u{1F1E6}\u0308", '\u0D4E'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0915", ['\u{1F1E6}', '\u0915'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\u0915", ["\u{1F1E6}\u0308", '\u0915'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u231A", ['\u{1F1E6}', '\u231A'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\u231A", ["\u{1F1E6}\u0308", '\u231A'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0300", ["\u{1F1E6}\u0300"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\u0300", ["\u{1F1E6}\u0308\u0300"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u093C", ["\u{1F1E6}\u093C"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\u093C", ["\u{1F1E6}\u0308\u093C"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u094D", ["\u{1F1E6}\u094D"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\u094D", ["\u{1F1E6}\u0308\u094D"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u200D", ["\u{1F1E6}\u200D"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\u200D", ["\u{1F1E6}\u0308\u200D"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0378", ['\u{1F1E6}', '\u0378'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] (Other) ÷ [0.3] @@ -226,8 +310,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0600\u0308\u{1F1E6}", ["\u0600\u0308", '\u{1F1E6}'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0600\u0600", ["\u0600\u0600"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\u0600", ["\u0600\u0308", '\u0600'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u0600\u0903", ["\u0600\u0903"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u0600\u0308\u0903", ["\u0600\u0308\u0903"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0600\u0A03", ["\u0600\u0A03"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\u0A03", ["\u0600\u0308\u0A03"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\u0600\u1100", ["\u0600\u1100"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\u1100", ["\u0600\u0308", '\u1100'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u0600\u1160", ["\u0600\u1160"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -238,48 +322,76 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0600\u0308\uAC00", ["\u0600\u0308", '\uAC00'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0600\uAC01", ["\u0600\uAC01"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\uAC01", ["\u0600\u0308", '\uAC01'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0600\u0900", ["\u0600\u0900"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\u0900", ["\u0600\u0308\u0900"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0600\u0903", ["\u0600\u0903"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\u0903", ["\u0600\u0308\u0903"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0600\u0904", ["\u0600\u0904"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\u0904", ["\u0600\u0308", '\u0904'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0600\u0D4E", ["\u0600\u0D4E"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\u0D4E", ["\u0600\u0308", '\u0D4E'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0600\u0915", ["\u0600\u0915"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\u0915", ["\u0600\u0308", '\u0915'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\u0600\u231A", ["\u0600\u231A"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\u231A", ["\u0600\u0308", '\u231A'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0600\u0300", ["\u0600\u0300"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\u0300", ["\u0600\u0308\u0300"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0600\u093C", ["\u0600\u093C"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\u093C", ["\u0600\u0308\u093C"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0600\u094D", ["\u0600\u094D"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\u094D", ["\u0600\u0308\u094D"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0600\u200D", ["\u0600\u200D"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\u200D", ["\u0600\u0308\u200D"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0600\u0378", ["\u0600\u0378"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] (Other) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\u0378", ["\u0600\u0308", '\u0378'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\u0903 ", ['\u0903', ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308 ", ["\u0903\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0903\r", ['\u0903', '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\r", ["\u0903\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u0903\n", ['\u0903', '\n'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (LF) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\n", ["\u0903\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] - it_iterates_graphemes "\u0903\u0001", ['\u0903', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u0001", ["\u0903\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0903\u034F", ["\u0903\u034F"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u034F", ["\u0903\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0903\u{1F1E6}", ['\u0903', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u{1F1E6}", ["\u0903\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\u0903\u0600", ['\u0903', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u0600", ["\u0903\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u0903\u0903", ["\u0903\u0903"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u0903", ["\u0903\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u0903\u1100", ['\u0903', '\u1100'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u1100", ["\u0903\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\u0903\u1160", ['\u0903', '\u1160'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u1160", ["\u0903\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\u0903\u11A8", ['\u0903', '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u11A8", ["\u0903\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\u0903\uAC00", ['\u0903', '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\uAC00", ["\u0903\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\u0903\uAC01", ['\u0903', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\uAC01", ["\u0903\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0903\u231A", ['\u0903', '\u231A'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u231A", ["\u0903\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\u0903\u0300", ["\u0903\u0300"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u0300", ["\u0903\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0903\u200D", ["\u0903\u200D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u200D", ["\u0903\u0308\u200D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0903\u0378", ['\u0903', '\u0378'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u0378", ["\u0903\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0A03 ", ['\u0A03', ' '] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308 ", ["\u0A03\u0308", ' '] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0A03\r", ['\u0A03', '\r'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\r", ["\u0A03\u0308", '\r'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0A03\n", ['\u0A03', '\n'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\n", ["\u0A03\u0308", '\n'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0001", ['\u0A03', '\u0001'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u0001", ["\u0A03\u0308", '\u0001'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0A03\u034F", ["\u0A03\u034F"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u034F", ["\u0A03\u0308\u034F"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0A03\u{1F1E6}", ['\u0A03', '\u{1F1E6}'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u{1F1E6}", ["\u0A03\u0308", '\u{1F1E6}'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0600", ['\u0A03', '\u0600'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u0600", ["\u0A03\u0308", '\u0600'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0A03", ["\u0A03\u0A03"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u0A03", ["\u0A03\u0308\u0A03"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0A03\u1100", ['\u0A03', '\u1100'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u1100", ["\u0A03\u0308", '\u1100'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0A03\u1160", ['\u0A03', '\u1160'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u1160", ["\u0A03\u0308", '\u1160'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0A03\u11A8", ['\u0A03', '\u11A8'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u11A8", ["\u0A03\u0308", '\u11A8'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0A03\uAC00", ['\u0A03', '\uAC00'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\uAC00", ["\u0A03\u0308", '\uAC00'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0A03\uAC01", ['\u0A03', '\uAC01'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\uAC01", ["\u0A03\u0308", '\uAC01'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0900", ["\u0A03\u0900"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u0900", ["\u0A03\u0308\u0900"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0903", ["\u0A03\u0903"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u0903", ["\u0A03\u0308\u0903"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0904", ['\u0A03', '\u0904'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u0904", ["\u0A03\u0308", '\u0904'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0D4E", ['\u0A03', '\u0D4E'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u0D4E", ["\u0A03\u0308", '\u0D4E'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0915", ['\u0A03', '\u0915'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u0915", ["\u0A03\u0308", '\u0915'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0A03\u231A", ['\u0A03', '\u231A'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u231A", ["\u0A03\u0308", '\u231A'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0300", ["\u0A03\u0300"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u0300", ["\u0A03\u0308\u0300"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0A03\u093C", ["\u0A03\u093C"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u093C", ["\u0A03\u0308\u093C"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0A03\u094D", ["\u0A03\u094D"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u094D", ["\u0A03\u0308\u094D"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0A03\u200D", ["\u0A03\u200D"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u200D", ["\u0A03\u0308\u200D"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0378", ['\u0A03', '\u0378'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u0378", ["\u0A03\u0308", '\u0378'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u1100 ", ['\u1100', ' '] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u1100\u0308 ", ["\u1100\u0308", ' '] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u1100\r", ['\u1100', '\r'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (CR) ÷ [0.3] @@ -294,8 +406,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1100\u0308\u{1F1E6}", ["\u1100\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u1100\u0600", ['\u1100', '\u0600'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\u0600", ["\u1100\u0308", '\u0600'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u1100\u0903", ["\u1100\u0903"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u1100\u0308\u0903", ["\u1100\u0308\u0903"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u1100\u0A03", ["\u1100\u0A03"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\u0A03", ["\u1100\u0308\u0A03"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\u1100\u1100", ["\u1100\u1100"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\u1100", ["\u1100\u0308", '\u1100'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u1100\u1160", ["\u1100\u1160"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -306,10 +418,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1100\u0308\uAC00", ["\u1100\u0308", '\uAC00'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u1100\uAC01", ["\u1100\uAC01"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\uAC01", ["\u1100\u0308", '\uAC01'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u1100\u0900", ["\u1100\u0900"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\u0900", ["\u1100\u0308\u0900"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1100\u0903", ["\u1100\u0903"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\u0903", ["\u1100\u0308\u0903"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1100\u0904", ['\u1100', '\u0904'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\u0904", ["\u1100\u0308", '\u0904'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1100\u0D4E", ['\u1100', '\u0D4E'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\u0D4E", ["\u1100\u0308", '\u0D4E'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1100\u0915", ['\u1100', '\u0915'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\u0915", ["\u1100\u0308", '\u0915'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\u1100\u231A", ['\u1100', '\u231A'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\u231A", ["\u1100\u0308", '\u231A'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u1100\u0300", ["\u1100\u0300"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\u0300", ["\u1100\u0308\u0300"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1100\u093C", ["\u1100\u093C"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\u093C", ["\u1100\u0308\u093C"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1100\u094D", ["\u1100\u094D"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\u094D", ["\u1100\u0308\u094D"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1100\u200D", ["\u1100\u200D"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\u200D", ["\u1100\u0308\u200D"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1100\u0378", ['\u1100', '\u0378'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] (Other) ÷ [0.3] @@ -328,8 +454,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1160\u0308\u{1F1E6}", ["\u1160\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u1160\u0600", ['\u1160', '\u0600'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\u0600", ["\u1160\u0308", '\u0600'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u1160\u0903", ["\u1160\u0903"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u1160\u0308\u0903", ["\u1160\u0308\u0903"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u1160\u0A03", ["\u1160\u0A03"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\u0A03", ["\u1160\u0308\u0A03"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\u1160\u1100", ['\u1160', '\u1100'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\u1100", ["\u1160\u0308", '\u1100'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u1160\u1160", ["\u1160\u1160"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -340,10 +466,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1160\u0308\uAC00", ["\u1160\u0308", '\uAC00'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u1160\uAC01", ['\u1160', '\uAC01'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\uAC01", ["\u1160\u0308", '\uAC01'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u1160\u0900", ["\u1160\u0900"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\u0900", ["\u1160\u0308\u0900"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1160\u0903", ["\u1160\u0903"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\u0903", ["\u1160\u0308\u0903"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1160\u0904", ['\u1160', '\u0904'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\u0904", ["\u1160\u0308", '\u0904'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1160\u0D4E", ['\u1160', '\u0D4E'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\u0D4E", ["\u1160\u0308", '\u0D4E'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u1160\u0915", ['\u1160', '\u0915'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\u0915", ["\u1160\u0308", '\u0915'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\u1160\u231A", ['\u1160', '\u231A'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\u231A", ["\u1160\u0308", '\u231A'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u1160\u0300", ["\u1160\u0300"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\u0300", ["\u1160\u0308\u0300"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1160\u093C", ["\u1160\u093C"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\u093C", ["\u1160\u0308\u093C"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1160\u094D", ["\u1160\u094D"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\u094D", ["\u1160\u0308\u094D"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1160\u200D", ["\u1160\u200D"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\u200D", ["\u1160\u0308\u200D"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1160\u0378", ['\u1160', '\u0378'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] (Other) ÷ [0.3] @@ -362,8 +502,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u11A8\u0308\u{1F1E6}", ["\u11A8\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u11A8\u0600", ['\u11A8', '\u0600'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\u0600", ["\u11A8\u0308", '\u0600'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u11A8\u0903", ["\u11A8\u0903"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u11A8\u0308\u0903", ["\u11A8\u0308\u0903"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0A03", ["\u11A8\u0A03"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\u0A03", ["\u11A8\u0308\u0A03"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\u11A8\u1100", ['\u11A8', '\u1100'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\u1100", ["\u11A8\u0308", '\u1100'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u11A8\u1160", ['\u11A8', '\u1160'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -374,10 +514,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u11A8\u0308\uAC00", ["\u11A8\u0308", '\uAC00'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u11A8\uAC01", ['\u11A8', '\uAC01'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\uAC01", ["\u11A8\u0308", '\uAC01'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0900", ["\u11A8\u0900"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\u0900", ["\u11A8\u0308\u0900"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0903", ["\u11A8\u0903"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\u0903", ["\u11A8\u0308\u0903"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0904", ['\u11A8', '\u0904'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\u0904", ["\u11A8\u0308", '\u0904'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0D4E", ['\u11A8', '\u0D4E'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\u0D4E", ["\u11A8\u0308", '\u0D4E'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0915", ['\u11A8', '\u0915'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\u0915", ["\u11A8\u0308", '\u0915'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\u11A8\u231A", ['\u11A8', '\u231A'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\u231A", ["\u11A8\u0308", '\u231A'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u11A8\u0300", ["\u11A8\u0300"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\u0300", ["\u11A8\u0308\u0300"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u11A8\u093C", ["\u11A8\u093C"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\u093C", ["\u11A8\u0308\u093C"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u11A8\u094D", ["\u11A8\u094D"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\u094D", ["\u11A8\u0308\u094D"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u11A8\u200D", ["\u11A8\u200D"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\u200D", ["\u11A8\u0308\u200D"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u11A8\u0378", ['\u11A8', '\u0378'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] (Other) ÷ [0.3] @@ -396,8 +550,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC00\u0308\u{1F1E6}", ["\uAC00\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\uAC00\u0600", ['\uAC00', '\u0600'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\u0600", ["\uAC00\u0308", '\u0600'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\uAC00\u0903", ["\uAC00\u0903"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\uAC00\u0308\u0903", ["\uAC00\u0308\u0903"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0A03", ["\uAC00\u0A03"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\u0A03", ["\uAC00\u0308\u0A03"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\uAC00\u1100", ['\uAC00', '\u1100'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\u1100", ["\uAC00\u0308", '\u1100'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\uAC00\u1160", ["\uAC00\u1160"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -408,10 +562,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC00\u0308\uAC00", ["\uAC00\u0308", '\uAC00'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\uAC00\uAC01", ['\uAC00', '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\uAC01", ["\uAC00\u0308", '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0900", ["\uAC00\u0900"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\u0900", ["\uAC00\u0308\u0900"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0903", ["\uAC00\u0903"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\u0903", ["\uAC00\u0308\u0903"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0904", ['\uAC00', '\u0904'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\u0904", ["\uAC00\u0308", '\u0904'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0D4E", ['\uAC00', '\u0D4E'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\u0D4E", ["\uAC00\u0308", '\u0D4E'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0915", ['\uAC00', '\u0915'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\u0915", ["\uAC00\u0308", '\u0915'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\uAC00\u231A", ['\uAC00', '\u231A'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\u231A", ["\uAC00\u0308", '\u231A'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\uAC00\u0300", ["\uAC00\u0300"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\u0300", ["\uAC00\u0308\u0300"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC00\u093C", ["\uAC00\u093C"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\u093C", ["\uAC00\u0308\u093C"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC00\u094D", ["\uAC00\u094D"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\u094D", ["\uAC00\u0308\u094D"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC00\u200D", ["\uAC00\u200D"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\u200D", ["\uAC00\u0308\u200D"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC00\u0378", ['\uAC00', '\u0378'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] (Other) ÷ [0.3] @@ -430,8 +598,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC01\u0308\u{1F1E6}", ["\uAC01\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\uAC01\u0600", ['\uAC01', '\u0600'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u0600", ["\uAC01\u0308", '\u0600'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\uAC01\u0903", ["\uAC01\u0903"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\uAC01\u0308\u0903", ["\uAC01\u0308\u0903"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0A03", ["\uAC01\u0A03"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\u0A03", ["\uAC01\u0308\u0A03"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\uAC01\u1100", ['\uAC01', '\u1100'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u1100", ["\uAC01\u0308", '\u1100'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\uAC01\u1160", ['\uAC01', '\u1160'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -442,14 +610,268 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC01\u0308\uAC00", ["\uAC01\u0308", '\uAC00'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\uAC01\uAC01", ['\uAC01', '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\uAC01", ["\uAC01\u0308", '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0900", ["\uAC01\u0900"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\u0900", ["\uAC01\u0308\u0900"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0903", ["\uAC01\u0903"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\u0903", ["\uAC01\u0308\u0903"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0904", ['\uAC01', '\u0904'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\u0904", ["\uAC01\u0308", '\u0904'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0D4E", ['\uAC01', '\u0D4E'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\u0D4E", ["\uAC01\u0308", '\u0D4E'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0915", ['\uAC01', '\u0915'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\u0915", ["\uAC01\u0308", '\u0915'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\uAC01\u231A", ['\uAC01', '\u231A'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u231A", ["\uAC01\u0308", '\u231A'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\uAC01\u0300", ["\uAC01\u0300"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u0300", ["\uAC01\u0308\u0300"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC01\u093C", ["\uAC01\u093C"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\u093C", ["\uAC01\u0308\u093C"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC01\u094D", ["\uAC01\u094D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\u094D", ["\uAC01\u0308\u094D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC01\u200D", ["\uAC01\u200D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u200D", ["\uAC01\u0308\u200D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC01\u0378", ['\uAC01', '\u0378'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u0378", ["\uAC01\u0308", '\u0378'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0900 ", ['\u0900', ' '] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308 ", ["\u0900\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0900\r", ['\u0900', '\r'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\r", ["\u0900\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0900\n", ['\u0900', '\n'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\n", ["\u0900\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0900\u0001", ['\u0900', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0001", ["\u0900\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0900\u034F", ["\u0900\u034F"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u034F", ["\u0900\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0900\u{1F1E6}", ['\u0900', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u{1F1E6}", ["\u0900\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0900\u0600", ['\u0900', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0600", ["\u0900\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0900\u0A03", ["\u0900\u0A03"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0A03", ["\u0900\u0308\u0A03"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0900\u1100", ['\u0900', '\u1100'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u1100", ["\u0900\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0900\u1160", ['\u0900', '\u1160'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u1160", ["\u0900\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0900\u11A8", ['\u0900', '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u11A8", ["\u0900\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0900\uAC00", ['\u0900', '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\uAC00", ["\u0900\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0900\uAC01", ['\u0900', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\uAC01", ["\u0900\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0900\u0900", ["\u0900\u0900"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0900", ["\u0900\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0903", ["\u0900\u0903"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0903", ["\u0900\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0904", ['\u0900', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0904", ["\u0900\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0D4E", ['\u0900', '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0D4E", ["\u0900\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0915", ['\u0900', '\u0915'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0915", ["\u0900\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0900\u231A", ['\u0900', '\u231A'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u231A", ["\u0900\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0900\u0300", ["\u0900\u0300"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0300", ["\u0900\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u093C", ["\u0900\u093C"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u093C", ["\u0900\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u094D", ["\u0900\u094D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u094D", ["\u0900\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u200D", ["\u0900\u200D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u200D", ["\u0900\u0308\u200D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u0378", ['\u0900', '\u0378'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0378", ["\u0900\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0903 ", ['\u0903', ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308 ", ["\u0903\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0903\r", ['\u0903', '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\r", ["\u0903\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0903\n", ['\u0903', '\n'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\n", ["\u0903\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0903\u0001", ['\u0903', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u0001", ["\u0903\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0903\u034F", ["\u0903\u034F"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u034F", ["\u0903\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0903\u{1F1E6}", ['\u0903', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u{1F1E6}", ["\u0903\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0903\u0600", ['\u0903', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u0600", ["\u0903\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0903\u0A03", ["\u0903\u0A03"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u0A03", ["\u0903\u0308\u0A03"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0903\u1100", ['\u0903', '\u1100'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u1100", ["\u0903\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0903\u1160", ['\u0903', '\u1160'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u1160", ["\u0903\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0903\u11A8", ['\u0903', '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u11A8", ["\u0903\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0903\uAC00", ['\u0903', '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\uAC00", ["\u0903\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0903\uAC01", ['\u0903', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\uAC01", ["\u0903\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0903\u0900", ["\u0903\u0900"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u0900", ["\u0903\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0903\u0903", ["\u0903\u0903"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u0903", ["\u0903\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0903\u0904", ['\u0903', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u0904", ["\u0903\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0903\u0D4E", ['\u0903', '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u0D4E", ["\u0903\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0903\u0915", ['\u0903', '\u0915'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u0915", ["\u0903\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0903\u231A", ['\u0903', '\u231A'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u231A", ["\u0903\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0903\u0300", ["\u0903\u0300"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u0300", ["\u0903\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0903\u093C", ["\u0903\u093C"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u093C", ["\u0903\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0903\u094D", ["\u0903\u094D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u094D", ["\u0903\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0903\u200D", ["\u0903\u200D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u200D", ["\u0903\u0308\u200D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0903\u0378", ['\u0903', '\u0378'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u0378", ["\u0903\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0904 ", ['\u0904', ' '] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308 ", ["\u0904\u0308", ' '] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0904\r", ['\u0904', '\r'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\r", ["\u0904\u0308", '\r'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0904\n", ['\u0904', '\n'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\n", ["\u0904\u0308", '\n'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0904\u0001", ['\u0904', '\u0001'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u0001", ["\u0904\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0904\u034F", ["\u0904\u034F"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u034F", ["\u0904\u0308\u034F"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0904\u{1F1E6}", ['\u0904', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u{1F1E6}", ["\u0904\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0904\u0600", ['\u0904', '\u0600'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u0600", ["\u0904\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0904\u0A03", ["\u0904\u0A03"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u0A03", ["\u0904\u0308\u0A03"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0904\u1100", ['\u0904', '\u1100'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u1100", ["\u0904\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0904\u1160", ['\u0904', '\u1160'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u1160", ["\u0904\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0904\u11A8", ['\u0904', '\u11A8'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u11A8", ["\u0904\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0904\uAC00", ['\u0904', '\uAC00'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\uAC00", ["\u0904\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0904\uAC01", ['\u0904', '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\uAC01", ["\u0904\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0904\u0900", ["\u0904\u0900"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u0900", ["\u0904\u0308\u0900"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0904\u0903", ["\u0904\u0903"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u0903", ["\u0904\u0308\u0903"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0904\u0904", ['\u0904', '\u0904'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u0904", ["\u0904\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0904\u0D4E", ['\u0904', '\u0D4E'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u0D4E", ["\u0904\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0904\u0915", ['\u0904', '\u0915'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u0915", ["\u0904\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0904\u231A", ['\u0904', '\u231A'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u231A", ["\u0904\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0904\u0300", ["\u0904\u0300"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u0300", ["\u0904\u0308\u0300"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0904\u093C", ["\u0904\u093C"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u093C", ["\u0904\u0308\u093C"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0904\u094D", ["\u0904\u094D"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u094D", ["\u0904\u0308\u094D"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0904\u200D", ["\u0904\u200D"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u200D", ["\u0904\u0308\u200D"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0904\u0378", ['\u0904', '\u0378'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u0378", ["\u0904\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0D4E ", ["\u0D4E "] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308 ", ["\u0D4E\u0308", ' '] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0D4E\r", ['\u0D4E', '\r'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\r", ["\u0D4E\u0308", '\r'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0D4E\n", ['\u0D4E', '\n'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\n", ["\u0D4E\u0308", '\n'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0001", ['\u0D4E', '\u0001'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u0001", ["\u0D4E\u0308", '\u0001'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u034F", ["\u0D4E\u034F"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u034F", ["\u0D4E\u0308\u034F"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u{1F1E6}", ["\u0D4E\u{1F1E6}"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u{1F1E6}", ["\u0D4E\u0308", '\u{1F1E6}'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0600", ["\u0D4E\u0600"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u0600", ["\u0D4E\u0308", '\u0600'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0A03", ["\u0D4E\u0A03"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u0A03", ["\u0D4E\u0308\u0A03"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u1100", ["\u0D4E\u1100"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u1100", ["\u0D4E\u0308", '\u1100'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u1160", ["\u0D4E\u1160"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u1160", ["\u0D4E\u0308", '\u1160'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u11A8", ["\u0D4E\u11A8"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u11A8", ["\u0D4E\u0308", '\u11A8'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0D4E\uAC00", ["\u0D4E\uAC00"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\uAC00", ["\u0D4E\u0308", '\uAC00'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0D4E\uAC01", ["\u0D4E\uAC01"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\uAC01", ["\u0D4E\u0308", '\uAC01'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0900", ["\u0D4E\u0900"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u0900", ["\u0D4E\u0308\u0900"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0903", ["\u0D4E\u0903"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u0903", ["\u0D4E\u0308\u0903"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0904", ["\u0D4E\u0904"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u0904", ["\u0D4E\u0308", '\u0904'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0D4E", ["\u0D4E\u0D4E"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u0D4E", ["\u0D4E\u0308", '\u0D4E'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0915", ["\u0D4E\u0915"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u0915", ["\u0D4E\u0308", '\u0915'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u231A", ["\u0D4E\u231A"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u231A", ["\u0D4E\u0308", '\u231A'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0300", ["\u0D4E\u0300"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u0300", ["\u0D4E\u0308\u0300"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u093C", ["\u0D4E\u093C"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u093C", ["\u0D4E\u0308\u093C"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u094D", ["\u0D4E\u094D"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u094D", ["\u0D4E\u0308\u094D"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u200D", ["\u0D4E\u200D"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u200D", ["\u0D4E\u0308\u200D"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0378", ["\u0D4E\u0378"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] (Other) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u0378", ["\u0D4E\u0308", '\u0378'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0915 ", ['\u0915', ' '] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308 ", ["\u0915\u0308", ' '] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0915\r", ['\u0915', '\r'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\r", ["\u0915\u0308", '\r'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0915\n", ['\u0915', '\n'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\n", ["\u0915\u0308", '\n'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0915\u0001", ['\u0915', '\u0001'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u0001", ["\u0915\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0915\u034F", ["\u0915\u034F"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u034F", ["\u0915\u0308\u034F"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0915\u{1F1E6}", ['\u0915', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u{1F1E6}", ["\u0915\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0915\u0600", ['\u0915', '\u0600'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u0600", ["\u0915\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0915\u0A03", ["\u0915\u0A03"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u0A03", ["\u0915\u0308\u0A03"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0915\u1100", ['\u0915', '\u1100'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u1100", ["\u0915\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0915\u1160", ['\u0915', '\u1160'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u1160", ["\u0915\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0915\u11A8", ['\u0915', '\u11A8'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u11A8", ["\u0915\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0915\uAC00", ['\u0915', '\uAC00'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\uAC00", ["\u0915\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0915\uAC01", ['\u0915', '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\uAC01", ["\u0915\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0915\u0900", ["\u0915\u0900"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u0900", ["\u0915\u0308\u0900"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0915\u0903", ["\u0915\u0903"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u0903", ["\u0915\u0308\u0903"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0915\u0904", ['\u0915', '\u0904'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u0904", ["\u0915\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0915\u0D4E", ['\u0915', '\u0D4E'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u0D4E", ["\u0915\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0915\u0915", ['\u0915', '\u0915'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u0915", ["\u0915\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0915\u231A", ['\u0915', '\u231A'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u231A", ["\u0915\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0915\u0300", ["\u0915\u0300"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u0300", ["\u0915\u0308\u0300"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0915\u093C", ["\u0915\u093C"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u093C", ["\u0915\u0308\u093C"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0915\u094D", ["\u0915\u094D"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u094D", ["\u0915\u0308\u094D"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0915\u200D", ["\u0915\u200D"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u200D", ["\u0915\u0308\u200D"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0915\u0378", ['\u0915', '\u0378'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u0378", ["\u0915\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u231A ", ['\u231A', ' '] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u231A\u0308 ", ["\u231A\u0308", ' '] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u231A\r", ['\u231A', '\r'] # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (CR) ÷ [0.3] @@ -464,8 +886,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u231A\u0308\u{1F1E6}", ["\u231A\u0308", '\u{1F1E6}'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u231A\u0600", ['\u231A', '\u0600'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\u0600", ["\u231A\u0308", '\u0600'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u231A\u0903", ["\u231A\u0903"] # ÷ [0.2] WATCH (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u231A\u0308\u0903", ["\u231A\u0308\u0903"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u231A\u0A03", ["\u231A\u0A03"] # ÷ [0.2] WATCH (ExtPict) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\u0A03", ["\u231A\u0308\u0A03"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\u231A\u1100", ['\u231A', '\u1100'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\u1100", ["\u231A\u0308", '\u1100'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u231A\u1160", ['\u231A', '\u1160'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -476,10 +898,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u231A\u0308\uAC00", ["\u231A\u0308", '\uAC00'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u231A\uAC01", ['\u231A', '\uAC01'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\uAC01", ["\u231A\u0308", '\uAC01'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u231A\u0900", ["\u231A\u0900"] # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\u0900", ["\u231A\u0308\u0900"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u231A\u0903", ["\u231A\u0903"] # ÷ [0.2] WATCH (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\u0903", ["\u231A\u0308\u0903"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u231A\u0904", ['\u231A', '\u0904'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\u0904", ["\u231A\u0308", '\u0904'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u231A\u0D4E", ['\u231A', '\u0D4E'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\u0D4E", ["\u231A\u0308", '\u0D4E'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u231A\u0915", ['\u231A', '\u0915'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\u0915", ["\u231A\u0308", '\u0915'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\u231A\u231A", ['\u231A', '\u231A'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\u231A", ["\u231A\u0308", '\u231A'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u231A\u0300", ["\u231A\u0300"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\u0300", ["\u231A\u0308\u0300"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u231A\u093C", ["\u231A\u093C"] # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\u093C", ["\u231A\u0308\u093C"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u231A\u094D", ["\u231A\u094D"] # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\u094D", ["\u231A\u0308\u094D"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u231A\u200D", ["\u231A\u200D"] # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\u200D", ["\u231A\u0308\u200D"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u231A\u0378", ['\u231A', '\u0378'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] (Other) ÷ [0.3] @@ -498,8 +934,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0300\u0308\u{1F1E6}", ["\u0300\u0308", '\u{1F1E6}'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0300\u0600", ['\u0300', '\u0600'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u0600", ["\u0300\u0308", '\u0600'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u0300\u0903", ["\u0300\u0903"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u0300\u0308\u0903", ["\u0300\u0308\u0903"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0300\u0A03", ["\u0300\u0A03"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\u0A03", ["\u0300\u0308\u0A03"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\u0300\u1100", ['\u0300', '\u1100'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u1100", ["\u0300\u0308", '\u1100'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u0300\u1160", ['\u0300', '\u1160'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -510,14 +946,124 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0300\u0308\uAC00", ["\u0300\u0308", '\uAC00'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0300\uAC01", ['\u0300', '\uAC01'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\uAC01", ["\u0300\u0308", '\uAC01'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0300\u0900", ["\u0300\u0900"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\u0900", ["\u0300\u0308\u0900"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0300\u0903", ["\u0300\u0903"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\u0903", ["\u0300\u0308\u0903"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0300\u0904", ['\u0300', '\u0904'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\u0904", ["\u0300\u0308", '\u0904'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0300\u0D4E", ['\u0300', '\u0D4E'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\u0D4E", ["\u0300\u0308", '\u0D4E'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0300\u0915", ['\u0300', '\u0915'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\u0915", ["\u0300\u0308", '\u0915'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\u0300\u231A", ['\u0300', '\u231A'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u231A", ["\u0300\u0308", '\u231A'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0300\u0300", ["\u0300\u0300"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u0300", ["\u0300\u0308\u0300"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0300\u093C", ["\u0300\u093C"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\u093C", ["\u0300\u0308\u093C"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0300\u094D", ["\u0300\u094D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\u094D", ["\u0300\u0308\u094D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0300\u200D", ["\u0300\u200D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u200D", ["\u0300\u0308\u200D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0300\u0378", ['\u0300', '\u0378'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u0378", ["\u0300\u0308", '\u0378'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u093C ", ['\u093C', ' '] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308 ", ["\u093C\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u093C\r", ['\u093C', '\r'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\r", ["\u093C\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u093C\n", ['\u093C', '\n'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\n", ["\u093C\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u093C\u0001", ['\u093C', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u0001", ["\u093C\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u093C\u034F", ["\u093C\u034F"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u034F", ["\u093C\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u093C\u{1F1E6}", ['\u093C', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u{1F1E6}", ["\u093C\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u093C\u0600", ['\u093C', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u0600", ["\u093C\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u093C\u0A03", ["\u093C\u0A03"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u0A03", ["\u093C\u0308\u0A03"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u093C\u1100", ['\u093C', '\u1100'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u1100", ["\u093C\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u093C\u1160", ['\u093C', '\u1160'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u1160", ["\u093C\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u093C\u11A8", ['\u093C', '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u11A8", ["\u093C\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u093C\uAC00", ['\u093C', '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\uAC00", ["\u093C\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u093C\uAC01", ['\u093C', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\uAC01", ["\u093C\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u093C\u0900", ["\u093C\u0900"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u0900", ["\u093C\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u093C\u0903", ["\u093C\u0903"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u0903", ["\u093C\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u093C\u0904", ['\u093C', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u0904", ["\u093C\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u093C\u0D4E", ['\u093C', '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u0D4E", ["\u093C\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u093C\u0915", ['\u093C', '\u0915'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u0915", ["\u093C\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u093C\u231A", ['\u093C', '\u231A'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u231A", ["\u093C\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u093C\u0300", ["\u093C\u0300"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u0300", ["\u093C\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u093C\u093C", ["\u093C\u093C"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u093C", ["\u093C\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u093C\u094D", ["\u093C\u094D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u094D", ["\u093C\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u093C\u200D", ["\u093C\u200D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u200D", ["\u093C\u0308\u200D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u093C\u0378", ['\u093C', '\u0378'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u093C\u0308\u0378", ["\u093C\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u094D ", ['\u094D', ' '] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308 ", ["\u094D\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u094D\r", ['\u094D', '\r'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\r", ["\u094D\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u094D\n", ['\u094D', '\n'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\n", ["\u094D\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u094D\u0001", ['\u094D', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u0001", ["\u094D\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u094D\u034F", ["\u094D\u034F"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u034F", ["\u094D\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u094D\u{1F1E6}", ['\u094D', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u{1F1E6}", ["\u094D\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u094D\u0600", ['\u094D', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u0600", ["\u094D\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u094D\u0A03", ["\u094D\u0A03"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u0A03", ["\u094D\u0308\u0A03"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u094D\u1100", ['\u094D', '\u1100'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u1100", ["\u094D\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u094D\u1160", ['\u094D', '\u1160'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u1160", ["\u094D\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u094D\u11A8", ['\u094D', '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u11A8", ["\u094D\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u094D\uAC00", ['\u094D', '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\uAC00", ["\u094D\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u094D\uAC01", ['\u094D', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\uAC01", ["\u094D\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u094D\u0900", ["\u094D\u0900"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u0900", ["\u094D\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u094D\u0903", ["\u094D\u0903"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u0903", ["\u094D\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u094D\u0904", ['\u094D', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u0904", ["\u094D\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u094D\u0D4E", ['\u094D', '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u0D4E", ["\u094D\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u094D\u0915", ['\u094D', '\u0915'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u0915", ["\u094D\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u094D\u231A", ['\u094D', '\u231A'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u231A", ["\u094D\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u094D\u0300", ["\u094D\u0300"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u0300", ["\u094D\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u094D\u093C", ["\u094D\u093C"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u093C", ["\u094D\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u094D\u094D", ["\u094D\u094D"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u094D", ["\u094D\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u094D\u200D", ["\u094D\u200D"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u200D", ["\u094D\u0308\u200D"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u094D\u0378", ['\u094D', '\u0378'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u0378", ["\u094D\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u200D ", ['\u200D', ' '] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u200D\u0308 ", ["\u200D\u0308", ' '] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u200D\r", ['\u200D', '\r'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] @@ -532,8 +1078,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u200D\u0308\u{1F1E6}", ["\u200D\u0308", '\u{1F1E6}'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u200D\u0600", ['\u200D', '\u0600'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\u0600", ["\u200D\u0308", '\u0600'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u200D\u0903", ["\u200D\u0903"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u200D\u0308\u0903", ["\u200D\u0308\u0903"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u200D\u0A03", ["\u200D\u0A03"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\u0A03", ["\u200D\u0308\u0A03"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\u200D\u1100", ['\u200D', '\u1100'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\u1100", ["\u200D\u0308", '\u1100'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u200D\u1160", ['\u200D', '\u1160'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -544,10 +1090,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u200D\u0308\uAC00", ["\u200D\u0308", '\uAC00'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u200D\uAC01", ['\u200D', '\uAC01'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\uAC01", ["\u200D\u0308", '\uAC01'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u200D\u0900", ["\u200D\u0900"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\u0900", ["\u200D\u0308\u0900"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200D\u0903", ["\u200D\u0903"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\u0903", ["\u200D\u0308\u0903"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200D\u0904", ['\u200D', '\u0904'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\u0904", ["\u200D\u0308", '\u0904'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200D\u0D4E", ['\u200D', '\u0D4E'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\u0D4E", ["\u200D\u0308", '\u0D4E'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200D\u0915", ['\u200D', '\u0915'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\u0915", ["\u200D\u0308", '\u0915'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\u200D\u231A", ['\u200D', '\u231A'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\u231A", ["\u200D\u0308", '\u231A'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u200D\u0300", ["\u200D\u0300"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\u0300", ["\u200D\u0308\u0300"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200D\u093C", ["\u200D\u093C"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\u093C", ["\u200D\u0308\u093C"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200D\u094D", ["\u200D\u094D"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\u094D", ["\u200D\u0308\u094D"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u200D\u200D", ["\u200D\u200D"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\u200D", ["\u200D\u0308\u200D"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u200D\u0378", ['\u200D', '\u0378'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] @@ -566,8 +1126,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0378\u0308\u{1F1E6}", ["\u0378\u0308", '\u{1F1E6}'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0378\u0600", ['\u0378', '\u0600'] # ÷ [0.2] (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u0600", ["\u0378\u0308", '\u0600'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u0378\u0903", ["\u0378\u0903"] # ÷ [0.2] (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u0378\u0308\u0903", ["\u0378\u0308\u0903"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0378\u0A03", ["\u0378\u0A03"] # ÷ [0.2] (Other) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\u0A03", ["\u0378\u0308\u0A03"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] it_iterates_graphemes "\u0378\u1100", ['\u0378', '\u1100'] # ÷ [0.2] (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u1100", ["\u0378\u0308", '\u1100'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] it_iterates_graphemes "\u0378\u1160", ['\u0378', '\u1160'] # ÷ [0.2] (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] @@ -578,10 +1138,24 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0378\u0308\uAC00", ["\u0378\u0308", '\uAC00'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0378\uAC01", ['\u0378', '\uAC01'] # ÷ [0.2] (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\uAC01", ["\u0378\u0308", '\uAC01'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0378\u0900", ["\u0378\u0900"] # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\u0900", ["\u0378\u0308\u0900"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0378\u0903", ["\u0378\u0903"] # ÷ [0.2] (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\u0903", ["\u0378\u0308\u0903"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0378\u0904", ['\u0378', '\u0904'] # ÷ [0.2] (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\u0904", ["\u0378\u0308", '\u0904'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0378\u0D4E", ['\u0378', '\u0D4E'] # ÷ [0.2] (Other) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\u0D4E", ["\u0378\u0308", '\u0D4E'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0378\u0915", ['\u0378', '\u0915'] # ÷ [0.2] (Other) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\u0915", ["\u0378\u0308", '\u0915'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] it_iterates_graphemes "\u0378\u231A", ['\u0378', '\u231A'] # ÷ [0.2] (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u231A", ["\u0378\u0308", '\u231A'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0378\u0300", ["\u0378\u0300"] # ÷ [0.2] (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u0300", ["\u0378\u0308\u0300"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0378\u093C", ["\u0378\u093C"] # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\u093C", ["\u0378\u0308\u093C"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0378\u094D", ["\u0378\u094D"] # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\u094D", ["\u0378\u0308\u094D"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0378\u200D", ["\u0378\u200D"] # ÷ [0.2] (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u200D", ["\u0378\u0308\u200D"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0378\u0378", ['\u0378', '\u0378'] # ÷ [0.2] (Other) ÷ [999.0] (Other) ÷ [0.3] @@ -600,7 +1174,7 @@ describe "String#each_grapheme" do it_iterates_graphemes "a\u{1F1E6}\u{1F1E7}\u{1F1E8}\u{1F1E9}b", ['a', "\u{1F1E6}\u{1F1E7}", "\u{1F1E8}\u{1F1E9}", 'b'] # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER D (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] it_iterates_graphemes "a\u200D", ["a\u200D"] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "a\u0308b", ["a\u0308", 'b'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] - it_iterates_graphemes "a\u0903b", ["a\u0903", 'b'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] + it_iterates_graphemes "a\u0903b", ["a\u0903", 'b'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] it_iterates_graphemes "a\u0600b", ['a', "\u0600b"] # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) × [9.2] LATIN SMALL LETTER B (Other) ÷ [0.3] it_iterates_graphemes "\u{1F476}\u{1F3FF}\u{1F476}", ["\u{1F476}\u{1F3FF}", '\u{1F476}'] # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [999.0] BABY (ExtPict) ÷ [0.3] it_iterates_graphemes "a\u{1F3FF}\u{1F476}", ["a\u{1F3FF}", '\u{1F476}'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [999.0] BABY (ExtPict) ÷ [0.3] @@ -610,4 +1184,15 @@ describe "String#each_grapheme" do it_iterates_graphemes "a\u200D\u{1F6D1}", ["a\u200D", '\u{1F6D1}'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] it_iterates_graphemes "\u2701\u200D\u2701", ["\u2701\u200D\u2701"] # ÷ [0.2] UPPER BLADE SCISSORS (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] UPPER BLADE SCISSORS (Other) ÷ [0.3] it_iterates_graphemes "a\u200D\u2701", ["a\u200D", '\u2701'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] UPPER BLADE SCISSORS (Other) ÷ [0.3] + it_iterates_graphemes "\u0915\u0924", ['\u0915', '\u0924'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + pending "GB9c" { it_iterates_graphemes "\u0915\u094D\u0924", ["\u0915\u094D\u0924"] } # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + pending "GB9c" { it_iterates_graphemes "\u0915\u094D\u094D\u0924", ["\u0915\u094D\u094D\u0924"] } # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + pending "GB9c" { it_iterates_graphemes "\u0915\u094D\u200D\u0924", ["\u0915\u094D\u200D\u0924"] } # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + pending "GB9c" { it_iterates_graphemes "\u0915\u093C\u200D\u094D\u0924", ["\u0915\u093C\u200D\u094D\u0924"] } # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + pending "GB9c" { it_iterates_graphemes "\u0915\u093C\u094D\u200D\u0924", ["\u0915\u093C\u094D\u200D\u0924"] } # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + pending "GB9c" { it_iterates_graphemes "\u0915\u094D\u0924\u094D\u092F", ["\u0915\u094D\u0924\u094D\u092F"] } # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER YA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0915\u094Da", ["\u0915\u094D", 'a'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER A (Other) ÷ [0.3] + it_iterates_graphemes "a\u094D\u0924", ["a\u094D", '\u0924'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "?\u094D\u0924", ["?\u094D", '\u0924'] # ÷ [0.2] QUESTION MARK (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + pending "GB9c" { it_iterates_graphemes "\u0915\u094D\u094D\u0924", ["\u0915\u094D\u094D\u0924"] } # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] end diff --git a/src/string/grapheme/properties.cr b/src/string/grapheme/properties.cr index 0bf837eabb20..65b51fba0935 100644 --- a/src/string/grapheme/properties.cr +++ b/src/string/grapheme/properties.cr @@ -58,9 +58,9 @@ struct String::Grapheme # ranges in this slice are numerically sorted. # # These ranges were taken from - # http://www.unicode.org/Public/15.0.0/ucd/auxiliary/GraphemeBreakProperty.txt + # http://www.unicode.org/Public/15.1.0/ucd/auxiliary/GraphemeBreakProperty.txt # as well as - # http://www.unicode.org/Public/15.0.0/ucd/emoji/emoji-data.txt + # http://www.unicode.org/Public/15.1.0/ucd/emoji/emoji-data.txt # ("Extended_Pictographic" only). See # https://www.unicode.org/license.html for the Unicode license agreement. @@codepoints : Array(Tuple(Int32, Int32, Property))? diff --git a/src/unicode/data.cr b/src/unicode/data.cr index 050c6c8836eb..a02db251d0c8 100644 --- a/src/unicode/data.cr +++ b/src/unicode/data.cr @@ -752,7 +752,7 @@ module Unicode data end private class_getter category_Lo : Array({Int32, Int32, Int32}) do - data = Array({Int32, Int32, Int32}).new(485) + data = Array({Int32, Int32, Int32}).new(486) put(data, 170, 186, 16) put(data, 443, 448, 5) put(data, 449, 451, 1) @@ -1235,6 +1235,7 @@ module Unicode put(data, 177984, 178205, 1) put(data, 178208, 183969, 1) put(data, 183984, 191456, 1) + put(data, 191472, 192093, 1) put(data, 194560, 195101, 1) put(data, 196608, 201546, 1) put(data, 201552, 205743, 1) diff --git a/src/unicode/unicode.cr b/src/unicode/unicode.cr index 224d5c59a042..1fb4b530686b 100644 --- a/src/unicode/unicode.cr +++ b/src/unicode/unicode.cr @@ -1,7 +1,7 @@ # Provides the `Unicode::CaseOptions` enum for special case conversions like Turkic. module Unicode # The currently supported [Unicode](https://home.unicode.org) version. - VERSION = "15.0.0" + VERSION = "15.1.0" # Case options to pass to various `Char` and `String` methods such as `upcase` or `downcase`. @[Flags] From 9e84f6e0f32161f7259ae468621f2b360261c04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 19 Oct 2023 11:37:23 +0200 Subject: [PATCH 0728/1551] Fix `FileUtils.ln_sf` to override special file types (#13896) --- spec/std/file_utils_spec.cr | 35 +++++++++++++++++++++++++++++++++++ src/file_utils.cr | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/spec/std/file_utils_spec.cr b/spec/std/file_utils_spec.cr index 448ff620208f..b90203b8b1a4 100644 --- a/spec/std/file_utils_spec.cr +++ b/spec/std/file_utils_spec.cr @@ -715,6 +715,41 @@ describe "FileUtils" do end end + {% if flag?(:unix) %} + it "overwrites a destination named pipe" do + with_tempfile("ln_sf_src", "ln_sf_dst_pipe_exists") do |path1, path2| + test_with_string_and_path(path1, path2) do |arg1, arg2| + FileUtils.touch([path1]) + `mkfifo #{path2}` + File.symlink?(path1).should be_false + File.symlink?(path2).should be_false + + FileUtils.ln_sf(arg1, arg2) + File.symlink?(path1).should be_false + File.symlink?(path2).should be_true + FileUtils.rm_rf([path1, path2]) + end + end + end + {% end %} + + it "overwrites a destination dir in dir" do + with_tempfile("ln_sf_src", "ln_sf_dst_dir_exists") do |path1, path2| + test_with_string_and_path(path1, path2) do |arg1, arg2| + FileUtils.touch([path1]) + dir_dest = File.join(path2, File.basename(path1)) + Dir.mkdir_p(dir_dest) + File.symlink?(path1).should be_false + File.symlink?(path2).should be_false + + FileUtils.ln_sf(arg1, arg2) + File.symlink?(path1).should be_false + File.symlink?(dir_dest).should be_true + FileUtils.rm_rf([path1, path2]) + end + end + end + it "overwrites a destination file inside a dir" do with_tempfile("ln_sf_dst_dir", "ln_sf_dst") do |dir, path2| dir += File::SEPARATOR diff --git a/src/file_utils.cr b/src/file_utils.cr index ad189ecd0b25..c5b1ced3c7c5 100644 --- a/src/file_utils.cr +++ b/src/file_utils.cr @@ -223,7 +223,7 @@ module FileUtils dest_path = File.join(dest_path, File.basename(src_path)) end - File.delete(dest_path) if File.file?(dest_path) + rm_rf(dest_path) if File.exists?(dest_path) File.symlink(src_path, dest_path) end From 008d76a15778772fb4b8bca2a38d834985dd44a8 Mon Sep 17 00:00:00 2001 From: threez Date: Fri, 20 Oct 2023 11:32:31 +0200 Subject: [PATCH 0729/1551] Add `UUID.v1`, `.v2`, `.v3`, `.v4`, `.v5` (#13693) Co-authored-by: Quinton Miller --- .github/workflows/wasm32.yml | 2 +- spec/std/uuid_spec.cr | 90 +++++++++++++++++++++ src/uuid.cr | 149 ++++++++++++++++++++++++++++++++++- 3 files changed, 238 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 3da1ef4ca89a..721ded21b861 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -40,7 +40,7 @@ jobs: rm wasm32-wasi-libs.tar.gz - name: Build spec/wasm32_std_spec.cr - run: bin/crystal build spec/wasm32_std_spec.cr -o wasm32_std_spec.wasm --target wasm32-wasi -Duse_pcre + run: bin/crystal build spec/wasm32_std_spec.cr -o wasm32_std_spec.wasm --target wasm32-wasi -Duse_pcre -Dwithout_openssl env: CRYSTAL_LIBRARY_PATH: ${{ github.workspace }}/wasm32-wasi-libs diff --git a/spec/std/uuid_spec.cr b/spec/std/uuid_spec.cr index 0992c92095d1..12e497829b16 100644 --- a/spec/std/uuid_spec.cr +++ b/spec/std/uuid_spec.cr @@ -162,10 +162,30 @@ describe "UUID" do expect_raises(ArgumentError) { UUID.new "xyz:uuid:1ed1ee2f-ef9a-4f9c-9615-ab14d8ef2892" } end + describe "v1" do + it "returns true if UUID is v1, false otherwise" do + uuid = UUID.v1 + uuid.v1?.should eq(true) + uuid = UUID.v1(node_id: StaticArray(UInt8, 6).new { |i| (i*10).to_u8 }) + uuid.to_s[24..36].should eq("000a141e2832") + end + end + + describe "v2" do + it "returns true if UUID is v2, false otherwise" do + uuid = UUID.v2(UUID::Domain::Person, 42) + uuid.v2?.should eq(true) + uuid = UUID.v2(UUID::Domain::Person, 42, node_id: StaticArray(UInt8, 6).new { |i| (i*10).to_u8 }) + uuid.to_s[24..36].should eq("000a141e2832") + end + end + describe "v4?" do it "returns true if UUID is v4, false otherwise" do uuid = UUID.random uuid.v4?.should eq(true) + uuid = UUID.v4 + uuid.v4?.should eq(true) uuid = UUID.new("00000000-0000-0000-0000-000000000000", version: UUID::Version::V5) uuid.v4?.should eq(false) end @@ -175,8 +195,78 @@ describe "UUID" do it "returns true if UUID is v4, raises otherwise" do uuid = UUID.random uuid.v4!.should eq(true) + uuid = UUID.v4 + uuid.v4!.should eq(true) uuid = UUID.new("00000000-0000-0000-0000-000000000000", version: UUID::Version::V5) expect_raises(UUID::Error) { uuid.v4! } end end + + describe "v3" do + it "generates DNS based names correctly" do + data = "crystal-lang.org" + expected = "60a4b7b5-3333-3f1e-a2cd-30d8a2d0b83b" + UUID.v3(data, UUID::Namespace::DNS).to_s.should eq(expected) + UUID.v3_dns(data).to_s.should eq(expected) + UUID.v3_dns(data).v3?.should eq(true) + end + + it "generates URL based names correctly" do + data = "https://crystal-lang.org" + expected = "c25c7b79-5f5f-3844-98a4-2548f5d0e7f9" + UUID.v3(data, UUID::Namespace::URL).to_s.should eq(expected) + UUID.v3_url(data).to_s.should eq(expected) + UUID.v3_url(data).v3?.should eq(true) + end + + it "generates OID based names correctly" do + data = "1.3.6.1.4.1.343" + expected = "77bc1dc3-0a9f-3e7e-bfa5-3f611a660c80" + UUID.v3(data, UUID::Namespace::OID).to_s.should eq(expected) + UUID.v3_oid(data).to_s.should eq(expected) + UUID.v3_oid(data).v3?.should eq(true) + end + + it "generates X500 based names correctly" do + data = "cn=John Doe, ou=People, o=example, c=com" + expected = "fcab1a4c-fc81-3ebc-9874-9a8b931911d3" + UUID.v3(data, UUID::Namespace::X500).to_s.should eq(expected) + UUID.v3_x500(data).to_s.should eq(expected) + UUID.v3_x500(data).v3?.should eq(true) + end + end + + describe "v5" do + it "generates DNS based names correctly" do + data = "crystal-lang.org" + expected = "11caf27c-b803-5e62-9c4b-15332b04047e" + UUID.v5(data, UUID::Namespace::DNS).to_s.should eq(expected) + UUID.v5_dns(data).to_s.should eq(expected) + UUID.v5_dns(data).v5?.should eq(true) + end + + it "generates URL based names correctly" do + data = "https://crystal-lang.org" + expected = "29fec3f0-9ad0-5e8a-a42e-214ff695f50e" + UUID.v5(data, UUID::Namespace::URL).to_s.should eq(expected) + UUID.v5_url(data).to_s.should eq(expected) + UUID.v5_url(data).v5?.should eq(true) + end + + it "generates OID based names correctly" do + data = "1.3.6.1.4.1.343" + expected = "6aab0456-7392-582a-b92a-ba5a7096945d" + UUID.v5(data, UUID::Namespace::OID).to_s.should eq(expected) + UUID.v5_oid(data).to_s.should eq(expected) + UUID.v5_oid(data).v5?.should eq(true) + end + + it "generates X500 based names correctly" do + data = "cn=John Doe, ou=People, o=example, c=com" + expected = "bc10b2d9-f370-5c65-9561-5e3f6d9b236d" + UUID.v5(data, UUID::Namespace::X500).to_s.should eq(expected) + UUID.v5_x500(data).to_s.should eq(expected) + UUID.v5_x500(data).v5?.should eq(true) + end + end end diff --git a/src/uuid.cr b/src/uuid.cr index 7c8d44478f84..c7aaee0a605c 100644 --- a/src/uuid.cr +++ b/src/uuid.cr @@ -1,3 +1,14 @@ +require "time" +require "io" + +{% if flag?(:without_openssl) %} + require "crystal/digest/sha1" + require "crystal/digest/md5" +{% else %} + require "digest/sha1" + require "digest/md5" +{% end %} + # Represents a UUID (Universally Unique IDentifier). # # NOTE: To use `UUID`, you must explicitly import it with `require "uuid"` @@ -10,7 +21,7 @@ struct UUID Unknown # Reserved by the NCS for backward compatibility. NCS - # Reserved for RFC4122 Specification (default). + # Reserved for RFC 4122 Specification (default). RFC4122 # Reserved by Microsoft for backward compatibility. Microsoft @@ -22,7 +33,7 @@ struct UUID enum Version # Unknown version. Unknown = 0 - # Date-time and MAC address. + # Date-time and NodeID address. V1 = 1 # DCE security. V2 = 2 @@ -34,6 +45,31 @@ struct UUID V5 = 5 end + # A Domain represents a Version 2 domain (DCE security). + enum Domain + Person = 0 + Group = 1 + Org = 2 + end + + # MAC address to be used as NodeID. + alias MAC = UInt8[6] + + # Namespaces as defined per in the RFC 4122 Appendix C. + # + # They are used with the functions `v3` amd `v5` to generate + # a `UUID` based on a `name`. + module Namespace + # A UUID is generated using the provided `name`, which is assumed to be a fully qualified domain name. + DNS = UUID.new("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + # A UUID is generated using the provided `name`, which is assumed to be a URL. + URL = UUID.new("6ba7b811-9dad-11d1-80b4-00c04fd430c8") + # A UUID is generated using the provided `name`, which is assumed to be an ISO OID. + OID = UUID.new("6ba7b812-9dad-11d1-80b4-00c04fd430c8") + # A UUID is generated using the provided `name`, which is assumed to be a X.500 DN in DER or a text output format. + X500 = UUID.new("6ba7b814-9dad-11d1-80b4-00c04fd430c8") + end + @bytes : StaticArray(UInt8, 16) # Generates UUID from *bytes*, applying *version* and *variant* to the UUID if @@ -176,6 +212,115 @@ struct UUID new(new_bytes, variant, version) end + # Generates RFC 4122 v1 UUID. + # + # The traditional method for generating a `node_id` involves using the machine’s MAC address. + # However, this approach is only effective if there is only one process running on the machine + # and if privacy is not a concern. In modern languages, the default is to prioritize security + # and privacy. Therefore, a pseudo-random `node_id` is generated as described in section 4.5 of + # the RFC. + # + # The sequence number `clock_seq` is used to generate the UUID. This number should be + # monotonically increasing, with only 14 bits of the clock sequence being used effectively. + # The clock sequence should be stored in a stable location, such as a file. If it is not + # stored, a random value is used by default. If not provided the current time milliseconds + # are used. In case the traditional MAC address based approach should be taken the + # `node_id` can be provided. Otherwise secure random is used. + def self.v1(*, clock_seq : UInt16? = nil, node_id : MAC? = nil) : self + tl = Time.local + now = (tl.to_unix_ns / 100).to_u64 + 122192928000000000 + seq = ((clock_seq || (tl.nanosecond/1000000).to_u16) & 0x3fff) | 0x8000 + + time_low = UInt32.new(now & 0xffffffff) + time_mid = UInt16.new((now >> 32) & 0xffff) + time_hi = UInt16.new((now >> 48) & 0x0fff) + time_hi |= 0x1000 # Version 1 + + uuid = uninitialized UInt8[16] + IO::ByteFormat::BigEndian.encode(time_low, uuid.to_slice[0..3]) + IO::ByteFormat::BigEndian.encode(time_mid, uuid.to_slice[4..5]) + IO::ByteFormat::BigEndian.encode(time_hi, uuid.to_slice[6..7]) + IO::ByteFormat::BigEndian.encode(seq, uuid.to_slice[8..9]) + + if node_id + 6.times do |i| + uuid.to_slice[10 + i] = node_id[i] + end + else + Random::Secure.random_bytes(uuid.to_slice[10..15]) + # set multicast bit as recommended per section 4.5 of the RFC 4122 spec + # to not conflict with real MAC addresses + uuid[10] |= 0x01_u8 + end + + new(uuid, version: UUID::Version::V1, variant: UUID::Variant::RFC4122) + end + + # Generates RFC 4122 v2 UUID. + # + # Version 2 UUIDs are generated using the current time, the local machine’s MAC address, + # and the local user or group ID. However, they are not widely used due to their limitations. + # For a given domain/id pair, the same token may be returned for a duration of up to 7 minutes + # and 10 seconds. + # + # The `id` depends on the `domain`, for the `Domain::Person` usually the local user id (uid) is + # used, for `Domain::Group` usually the local group id (gid) is used. In case the traditional + # MAC address based approach should be taken the `node_id` can be provided. Otherwise secure + # random is used. + def self.v2(domain : Domain, id : UInt32, node_id : MAC? = nil) : self + uuid = v1(node_id: node_id).bytes + uuid[6] = (uuid[6] & 0x0f) | 0x20 # Version 2 + uuid[9] = domain.to_u8 + IO::ByteFormat::BigEndian.encode(id, uuid.to_slice[0..3]) + new(uuid, version: UUID::Version::V2, variant: UUID::Variant::RFC4122) + end + + # Generates RFC 4122 v3 UUID using the `name` to generate the UUID, it can be a string of any size. + # The `namespace` specifies the type of the name, usually one of `Namespace`. + def self.v3(name : String, namespace : UUID) : self + klass = {% if flag?(:without_openssl) %}::Crystal::Digest::MD5{% else %}::Digest::MD5{% end %} + hash = klass.digest do |ctx| + ctx.update namespace.bytes + ctx.update name + end + new(hash[0...16], version: UUID::Version::V3, variant: UUID::Variant::RFC4122) + end + + # Generates RFC 4122 v4 UUID. + # + # It is strongly recommended to use a cryptographically random source for + # *random*, such as `Random::Secure`. + def self.v4(random r : Random = Random::Secure) : self + random(r) + end + + # Generates RFC 4122 v5 UUID using the `name` to generate the UUID, it can be a string of any size. + # The `namespace` specifies the type of the name, usually one of `Namespace`. + def self.v5(name : String, namespace : UUID) : self + klass = {% if flag?(:without_openssl) %}::Crystal::Digest::SHA1{% else %}::Digest::SHA1{% end %} + hash = klass.digest do |ctx| + ctx.update namespace.bytes + ctx.update name + end + new(hash[0...16], version: UUID::Version::V5, variant: UUID::Variant::RFC4122) + end + + {% for name in %w(DNS URL OID X500).map(&.id) %} + # Generates RFC 4122 v3 UUID with the `Namespace::{{ name }}`. + # + # * `name`: The name used to generate the UUID, it can be a string of any size. + def self.v3_{{ name.downcase }}(name : String) + v3(name, Namespace::{{ name }}) + end + + # Generates RFC 4122 v5 UUID with the `Namespace::{{ name }}`. + # + # * `name`: The name used to generate the UUID, it can be a string of any size. + def self.v5_{{ name.downcase }}(name : String) + v5(name, Namespace::{{ name }}) + end + {% end %} + # Generates an empty UUID. # # ``` From ad7d42cb8ea982ff7e99911f851d43122fde0f6a Mon Sep 17 00:00:00 2001 From: Andrea Manzini Date: Thu, 26 Oct 2023 11:53:06 +0200 Subject: [PATCH 0730/1551] Fix documentation of `Hash#put_if_absent` (#13898) --- src/hash.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hash.cr b/src/hash.cr index 05ea0ca3b1de..156f2d7d6313 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -1075,7 +1075,7 @@ class Hash(K, V) # Otherwise *value* is returned. # # ``` - # h = {} of Int32 => Array(String) + # h = {} of Int32 => String # h.put_if_absent(1, "one") # => "one" # h.put_if_absent(1, "uno") # => "one" # h.put_if_absent(2, "two") # => "two" From a55b1157668dc2f6429c624ee16a1216b126612d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 27 Oct 2023 18:31:14 +0800 Subject: [PATCH 0731/1551] Use `CMAKE_MSVC_RUNTIME_LIBRARY` flag in win.yml (#13900) --- .github/workflows/win.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index a98e0dcb7c7e..bf8687d32c1e 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -200,7 +200,7 @@ jobs: run: | mkdir llvm-build cd llvm-build - cmake ..\llvm-src -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DLLVM_USE_CRT_RELEASE=MT -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_ZSTD=OFF + cmake ..\llvm-src -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_ZSTD=OFF cmake --build . --config Release cmake "-DCMAKE_INSTALL_PREFIX=$(pwd)\..\llvm" -P cmake_install.cmake From 845431202be56000d9b661b28914430910f5c990 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Sat, 28 Oct 2023 04:09:35 -0500 Subject: [PATCH 0732/1551] Add `CharLiteral#ord` (#13910) --- spec/compiler/macro/macro_methods_spec.cr | 7 +++++++ src/compiler/crystal/macros.cr | 4 ++++ src/compiler/crystal/macros/methods.cr | 13 +++++++++++++ 3 files changed, 24 insertions(+) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 8bc05df4f39a..f8e196ddadfe 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -367,6 +367,13 @@ module Crystal end end + describe "char methods" do + it "executes ord" do + assert_macro %({{'a'.ord}}), %(97) + assert_macro %({{'龍'.ord}}), %(40845) + end + end + describe "string methods" do it "executes string == string" do assert_macro %({{"foo" == "foo"}}), %(true) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index fcbe87249c2f..9f6072c212da 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -562,6 +562,10 @@ module Crystal::Macros # Returns a `MacroId` for this character's contents. def id : MacroId end + + # Similar to `Char#ord`. + def ord : NumberLiteral + end end # A string literal. diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 36bd79fa64e9..9adf5986327f 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -602,6 +602,19 @@ module Crystal def to_macro_id @value.to_s end + + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "ord" + interpret_check_args { NumberLiteral.new(ord) } + else + super + end + end + + def ord + @value.ord + end end class StringLiteral From 856cb21193ca0b8ddb72a34205641d3bbb871026 Mon Sep 17 00:00:00 2001 From: sbsoftware Date: Sat, 28 Oct 2023 11:29:30 +0200 Subject: [PATCH 0733/1551] Add macro methods for `MacroIf` and `MacroFor` nodes (#13902) --- spec/compiler/macro/macro_methods_spec.cr | 28 +++++++++++++ src/compiler/crystal/macros.cr | 48 ++++++++++++++++++++--- src/compiler/crystal/macros/methods.cr | 30 ++++++++++++++ src/compiler/crystal/macros/types.cr | 4 +- 4 files changed, 102 insertions(+), 8 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index f8e196ddadfe..0f77f4ae3d54 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2510,6 +2510,34 @@ module Crystal end end + describe "macro if methods" do + it "executes cond" do + assert_macro %({{x.cond}}), "true", {x: MacroIf.new(BoolLiteral.new(true), NilLiteral.new)} + end + + it "executes then" do + assert_macro %({{x.then}}), "\"test\"", {x: MacroIf.new(BoolLiteral.new(true), StringLiteral.new("test"), StringLiteral.new("foo"))} + end + + it "executes else" do + assert_macro %({{x.else}}), "\"foo\"", {x: MacroIf.new(BoolLiteral.new(true), StringLiteral.new("test"), StringLiteral.new("foo"))} + end + end + + describe "macro for methods" do + it "executes vars" do + assert_macro %({{x.vars}}), "[bar]", {x: MacroFor.new([Var.new("bar")], Var.new("foo"), Call.new(nil, "puts", [Var.new("bar")] of ASTNode))} + end + + it "executes exp" do + assert_macro %({{x.exp}}), "foo", {x: MacroFor.new([Var.new("bar")], Var.new("foo"), Call.new(nil, "puts", [Var.new("bar")] of ASTNode))} + end + + it "executes body" do + assert_macro %({{x.body}}), "puts(bar)", {x: MacroFor.new([Var.new("bar")], Var.new("foo"), Call.new(nil, "puts", [Var.new("bar")] of ASTNode))} + end + end + describe "unary expression methods" do it "executes exp" do assert_macro %({{x.exp}}), "some_call", {x: Not.new("some_call".call)} diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 9f6072c212da..8472a6626eda 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1882,13 +1882,49 @@ module Crystal::Macros # class MacroLiteral < ASTNode # end - # if inside a macro - # class MacroIf < ASTNode - # end + # An `if` inside a macro, e.g. + # + # ``` + # {% if cond %} + # puts "Then" + # {% else %} + # puts "Else" + # {% end %} + # ``` + class MacroIf < ASTNode + # The condition of the `if` clause. + def cond : ASTNode + end - # for inside a macro: - # class MacroFor < ASTNode - # end + # The `then` branch of the `if`. + def then : ASTNode + end + + # The `else` branch of the `if`. + def else : ASTNode + end + end + + # A `for` loop inside a macro, e.g. + # + # ``` + # {% for x in exp %} + # puts {{x}} + # {% end %} + # ``` + class MacroFor < ASTNode + # The variables declared after `for`. + def vars : ArrayLiteral(Var) + end + + # The expression after `in`. + def exp : ASTNode + end + + # The body of the `for` loop. + def body : ASTNode + end + end # The `_` expression. May appear in code, such as an assignment target, and in # type names. diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 9adf5986327f..90c52fa60347 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1478,6 +1478,36 @@ module Crystal end end + class MacroIf + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "cond" + interpret_check_args { @cond } + when "then" + interpret_check_args { @then } + when "else" + interpret_check_args { @else } + else + super + end + end + end + + class MacroFor + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "vars" + interpret_check_args { ArrayLiteral.map(@vars, &.itself) } + when "exp" + interpret_check_args { @exp } + when "body" + interpret_check_args { @body } + else + super + end + end + end + class UnaryExpression def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) case method diff --git a/src/compiler/crystal/macros/types.cr b/src/compiler/crystal/macros/types.cr index e86bd45c4ad9..3a3bfa402c57 100644 --- a/src/compiler/crystal/macros/types.cr +++ b/src/compiler/crystal/macros/types.cr @@ -88,6 +88,8 @@ module Crystal @macro_types["Cast"] = NonGenericMacroType.new self, "Cast", ast_node @macro_types["NilableCast"] = NonGenericMacroType.new self, "NilableCast", ast_node @macro_types["MacroId"] = NonGenericMacroType.new self, "MacroId", ast_node + @macro_types["MacroFor"] = NonGenericMacroType.new self, "MacroFor", ast_node + @macro_types["MacroIf"] = NonGenericMacroType.new self, "MacroIf", ast_node @macro_types["TypeNode"] = NonGenericMacroType.new self, "TypeNode", ast_node # bottom type @@ -114,8 +116,6 @@ module Crystal @macro_types["Include"] = NonGenericMacroType.new self, "Include", ast_node @macro_types["TypeDef"] = NonGenericMacroType.new self, "TypeDef", ast_node @macro_types["MacroExpression"] = NonGenericMacroType.new self, "MacroExpression", ast_node - @macro_types["MacroFor"] = NonGenericMacroType.new self, "MacroFor", ast_node - @macro_types["MacroIf"] = NonGenericMacroType.new self, "MacroIf", ast_node @macro_types["MacroLiteral"] = NonGenericMacroType.new self, "MacroLiteral", ast_node @macro_types["MacroVar"] = NonGenericMacroType.new self, "MacroVar", ast_node @macro_types["MacroVerbatim"] = NonGenericMacroType.new self, "MacroVerbatim", unary_expression From 26819e39a0ed865251b84680547d60aac1f5e777 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 28 Oct 2023 17:29:52 +0800 Subject: [PATCH 0734/1551] Make `Process#wait` asynchronous on Windows (#13908) --- Makefile.win | 2 +- src/crystal/system/win32/process.cr | 52 +++++++++++++++++++++- src/io/overlapped.cr | 23 +++++++++- src/lib_c/x86_64-windows-msvc/c/jobapi2.cr | 7 +++ src/lib_c/x86_64-windows-msvc/c/winnt.cr | 45 +++++++++++++++++++ 5 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 src/lib_c/x86_64-windows-msvc/c/jobapi2.cr diff --git a/Makefile.win b/Makefile.win index 70efc2e8c426..1e1d63fdabb2 100644 --- a/Makefile.win +++ b/Makefile.win @@ -227,7 +227,7 @@ $(O)\crystal.exe: $(DEPS) $(SOURCES) $(O)\crystal.res @$(call MKDIR,"$(O)") $(call export_vars) $(call export_build_vars) - .\bin\crystal build $(FLAGS) -o "$(O)\crystal-next.exe" src\compiler\crystal.cr -D without_openssl -D without_zlib -D without_playground $(if $(USE_PCRE1),-D use_pcre,-D use_pcre2) --link-flags=/PDBALTPATH:crystal.pdb "--link-flags=$(realpath $(O)\crystal.res)" + .\bin\crystal build $(FLAGS) -o "$(O)\crystal-next.exe" src\compiler\crystal.cr -D without_openssl -D without_zlib $(if $(USE_PCRE1),-D use_pcre,-D use_pcre2) --link-flags=/PDBALTPATH:crystal.pdb "--link-flags=$(realpath $(O)\crystal.res)" $(call MV,"$(O)\crystal-next.exe","$@") $(call MV,"$(O)\crystal-next.pdb","$(O)\crystal.pdb") diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index ed7caf769bca..b92203b38510 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -1,5 +1,6 @@ require "c/processthreadsapi" require "c/handleapi" +require "c/jobapi2" require "c/synchapi" require "c/tlhelp32" require "process/shell" @@ -9,6 +10,8 @@ struct Crystal::System::Process getter pid : LibC::DWORD @thread_id : LibC::DWORD @process_handle : LibC::HANDLE + @job_object : LibC::HANDLE + @completion_key = IO::Overlapped::CompletionKey.new @@interrupt_handler : Proc(Nil)? @@interrupt_count = Crystal::AtomicSemaphore.new @@ -19,15 +22,62 @@ struct Crystal::System::Process @pid = process_info.dwProcessId @thread_id = process_info.dwThreadId @process_handle = process_info.hProcess + + @job_object = LibC.CreateJobObjectW(nil, nil) + + # enable IOCP notifications + config_job_object( + LibC::JOBOBJECTINFOCLASS::AssociateCompletionPortInformation, + LibC::JOBOBJECT_ASSOCIATE_COMPLETION_PORT.new( + completionKey: @completion_key.as(Void*), + completionPort: Crystal::Scheduler.event_loop.iocp, + ), + ) + + # but not for any child processes + config_job_object( + LibC::JOBOBJECTINFOCLASS::ExtendedLimitInformation, + LibC::JOBOBJECT_EXTENDED_LIMIT_INFORMATION.new( + basicLimitInformation: LibC::JOBOBJECT_BASIC_LIMIT_INFORMATION.new( + limitFlags: LibC::JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK, + ), + ), + ) + + if LibC.AssignProcessToJobObject(@job_object, @process_handle) == 0 + raise RuntimeError.from_winerror("AssignProcessToJobObject") + end + end + + private def config_job_object(kind, info) + if LibC.SetInformationJobObject(@job_object, kind, pointerof(info), sizeof(typeof(info))) == 0 + raise RuntimeError.from_winerror("SetInformationJobObject") + end end def release return if @process_handle == LibC::HANDLE.null close_handle(@process_handle) @process_handle = LibC::HANDLE.null + close_handle(@job_object) + @job_object = LibC::HANDLE.null end def wait + if LibC.GetExitCodeProcess(@process_handle, out exit_code) == 0 + raise RuntimeError.from_winerror("GetExitCodeProcess") + end + return exit_code unless exit_code == LibC::STILL_ACTIVE + + # let `@job_object` do its job + # TODO: message delivery is "not guaranteed"; does it ever happen? Are we + # stuck forever in that case? + # (https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-jobobject_associate_completion_port) + @completion_key.fiber = ::Fiber.current + Crystal::Scheduler.reschedule + + # If the IOCP notification is delivered before the process fully exits, + # wait for it if LibC.WaitForSingleObject(@process_handle, LibC::INFINITE) != LibC::WAIT_OBJECT_0 raise RuntimeError.from_winerror("WaitForSingleObject") end @@ -38,7 +88,7 @@ struct Crystal::System::Process # waitpid returns, we wait 5 milliseconds to attempt to replicate this behaviour. sleep 5.milliseconds - if LibC.GetExitCodeProcess(@process_handle, out exit_code) == 0 + if LibC.GetExitCodeProcess(@process_handle, pointerof(exit_code)) == 0 raise RuntimeError.from_winerror("GetExitCodeProcess") end if exit_code == LibC::STILL_ACTIVE diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index 8830b5d65a7c..d4b9f5f0cb6f 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -3,6 +3,11 @@ require "c/handleapi" require "crystal/system/thread_linked_list" module IO::Overlapped + # :nodoc: + class CompletionKey + property fiber : Fiber? + end + @read_timeout : Time::Span? @write_timeout : Time::Span? @@ -61,7 +66,23 @@ module IO::Overlapped end removed.times do |i| - OverlappedOperation.schedule(overlapped_entries[i].lpOverlapped) { |fiber| yield fiber } + entry = overlapped_entries[i] + + # at the moment only `::Process#wait` uses a non-nil completion key; all + # I/O operations, including socket ones, do not set this field + case completion_key = Pointer(Void).new(entry.lpCompletionKey).as(CompletionKey?) + when Nil + OverlappedOperation.schedule(entry.lpOverlapped) { |fiber| yield fiber } + else + case entry.dwNumberOfBytesTransferred + when LibC::JOB_OBJECT_MSG_EXIT_PROCESS, LibC::JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS + if fiber = completion_key.fiber + yield fiber + else + # the `Process` exits before a call to `#wait`; do nothing + end + end + end end false diff --git a/src/lib_c/x86_64-windows-msvc/c/jobapi2.cr b/src/lib_c/x86_64-windows-msvc/c/jobapi2.cr new file mode 100644 index 000000000000..6c8e445495a7 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/jobapi2.cr @@ -0,0 +1,7 @@ +require "./winnt" + +lib LibC + fun CreateJobObjectW(lpJobAttributes : SECURITY_ATTRIBUTES*, lpName : LPWSTR) : HANDLE + fun SetInformationJobObject(hJob : HANDLE, jobObjectInformationClass : JOBOBJECTINFOCLASS, lpJobObjectInformation : Void*, cbJobObjectInformationLength : DWORD) : BOOL + fun AssignProcessToJobObject(hJob : HANDLE, hProcess : HANDLE) : BOOL +end diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index 9496f051a6a4..addd17e2fa1b 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -90,6 +90,51 @@ lib LibC WRITE = 0x20006 end + enum JOBOBJECTINFOCLASS + AssociateCompletionPortInformation = 7 + ExtendedLimitInformation = 9 + end + + struct JOBOBJECT_BASIC_LIMIT_INFORMATION + perProcessUserTimeLimit : LARGE_INTEGER + perJobUserTimeLimit : LARGE_INTEGER + limitFlags : DWORD + minimumWorkingSetSize : SizeT + maximumWorkingSetSize : SizeT + activeProcessLimit : DWORD + affinity : ULONG_PTR + priorityClass : DWORD + schedulingClass : DWORD + end + + struct IO_COUNTERS + readOperationCount : ULongLong + writeOperationCount : ULongLong + otherOperationCount : ULongLong + readTransferCount : ULongLong + writeTransferCount : ULongLong + otherTransferCount : ULongLong + end + + struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION + basicLimitInformation : JOBOBJECT_BASIC_LIMIT_INFORMATION + ioInfo : IO_COUNTERS + processMemoryLimit : SizeT + jobMemoryLimit : SizeT + peakProcessMemoryUsed : SizeT + peakJobMemoryUsed : SizeT + end + + struct JOBOBJECT_ASSOCIATE_COMPLETION_PORT + completionKey : Void* + completionPort : HANDLE + end + + JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000 + + JOB_OBJECT_MSG_EXIT_PROCESS = 7 + JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS = 8 + struct CONTEXT p1Home : DWORD64 p2Home : DWORD64 From 64974a0593d4391a483f4f4680cfdfb8451e7d91 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 29 Oct 2023 18:06:56 +0800 Subject: [PATCH 0735/1551] Respect Windows `Path` directory separators in `File.match?` (#13912) --- spec/std/file_spec.cr | 155 +++++++++++++++++++++++++----------------- src/file.cr | 27 +++++--- 2 files changed, 111 insertions(+), 71 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 4dbe8e51b53a..53d28bdcee4e 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -8,6 +8,18 @@ private def it_raises_on_null_byte(operation, file = __FILE__, line = __LINE__, end end +private def assert_file_matches(pattern, path : String, *, file = __FILE__, line = __LINE__) + File.match?(pattern, path).should be_true, file: file, line: line + File.match?(pattern, Path.posix(path)).should be_true, file: file, line: line + File.match?(pattern, Path.posix(path).to_windows).should be_true, file: file, line: line +end + +private def refute_file_matches(pattern, path : String, *, file = __FILE__, line = __LINE__) + File.match?(pattern, path).should be_false, file: file, line: line + File.match?(pattern, Path.posix(path)).should be_false, file: file, line: line + File.match?(pattern, Path.posix(path).to_windows).should be_false, file: file, line: line +end + private def normalize_permissions(permissions, *, directory) {% if flag?(:win32) %} normalized_permissions = 0o444 @@ -1449,73 +1461,89 @@ describe "File" do describe ".match?" do it "matches basics" do - File.match?("abc", Path["abc"]).should be_true - File.match?("abc", "abc").should be_true - File.match?("*", "abc").should be_true - File.match?("*c", "abc").should be_true - File.match?("a*", "a").should be_true - File.match?("a*", "abc").should be_true - File.match?("a*/b", "abc/b").should be_true - File.match?("*x", "xxx").should be_true + assert_file_matches "abc", "abc" + assert_file_matches "*", "abc" + assert_file_matches "*c", "abc" + assert_file_matches "a*", "a" + assert_file_matches "a*", "abc" + assert_file_matches "a*/b", "abc/b" + assert_file_matches "*x", "xxx" end + it "matches multiple expansions" do - File.match?("a*b*c*d*e*/f", "axbxcxdxe/f").should be_true - File.match?("a*b*c*d*e*/f", "axbxcxdxexxx/f").should be_true - File.match?("a*b?c*x", "abxbbxdbxebxczzx").should be_true - File.match?("a*b?c*x", "abxbbxdbxebxczzy").should be_false + assert_file_matches "a*b*c*d*e*/f", "axbxcxdxe/f" + assert_file_matches "a*b*c*d*e*/f", "axbxcxdxexxx/f" + assert_file_matches "a*b?c*x", "abxbbxdbxebxczzx" + refute_file_matches "a*b?c*x", "abxbbxdbxebxczzy" end + it "matches unicode characters" do - File.match?("a?b", "a☺b").should be_true - File.match?("a???b", "a☺b").should be_false - end - it "* don't match /" do - File.match?("a*", "ab/c").should be_false - File.match?("a*/b", "a/c/b").should be_false - File.match?("a*b*c*d*e*/f", "axbxcxdxe/xxx/f").should be_false - File.match?("a*b*c*d*e*/f", "axbxcxdxexxx/fff").should be_false - end - it "** matches /" do - File.match?("a**", "ab/c").should be_true - File.match?("a**/b", "a/c/b").should be_true - File.match?("a*b*c*d*e**/f", "axbxcxdxe/xxx/f").should be_true - File.match?("a*b*c*d*e**/f", "axbxcxdxexxx/f").should be_true - File.match?("a*b*c*d*e**/f", "axbxcxdxexxx/fff").should be_false + assert_file_matches "a?b", "a☺b" + refute_file_matches "a???b", "a☺b" + end + + it "* don't match path separator" do + refute_file_matches "a*", "ab/c" + refute_file_matches "a*/b", "a/c/b" + refute_file_matches "a*b*c*d*e*/f", "axbxcxdxe/xxx/f" + refute_file_matches "a*b*c*d*e*/f", "axbxcxdxexxx/fff" end + + it "** matches path separator" do + assert_file_matches "a**", "ab/c" + assert_file_matches "a**/b", "a/c/b" + assert_file_matches "a*b*c*d*e**/f", "axbxcxdxe/xxx/f" + assert_file_matches "a*b*c*d*e**/f", "axbxcxdxexxx/f" + refute_file_matches "a*b*c*d*e**/f", "axbxcxdxexxx/fff" + end + it "classes" do - File.match?("ab[c]", "abc").should be_true - File.match?("ab[b-d]", "abc").should be_true - File.match?("ab[d-b]", "abc").should be_false - File.match?("ab[e-g]", "abc").should be_false - File.match?("ab[e-gc]", "abc").should be_true - File.match?("ab[^c]", "abc").should be_false - File.match?("ab[^b-d]", "abc").should be_false - File.match?("ab[^e-g]", "abc").should be_true - File.match?("a[^a]b", "a☺b").should be_true - File.match?("a[^a][^a][^a]b", "a☺b").should be_false - File.match?("[a-ζ]*", "α").should be_true - File.match?("*[a-ζ]", "A").should be_false + assert_file_matches "ab[c]", "abc" + assert_file_matches "ab[b-d]", "abc" + refute_file_matches "ab[d-b]", "abc" + refute_file_matches "ab[e-g]", "abc" + assert_file_matches "ab[e-gc]", "abc" + refute_file_matches "ab[^c]", "abc" + refute_file_matches "ab[^b-d]", "abc" + assert_file_matches "ab[^e-g]", "abc" + assert_file_matches "a[^a]b", "a☺b" + refute_file_matches "a[^a][^a][^a]b", "a☺b" + assert_file_matches "[a-ζ]*", "α" + refute_file_matches "*[a-ζ]", "A" end + it "escape" do + # NOTE: `*` is forbidden in Windows paths File.match?("a\\*b", "a*b").should be_true - File.match?("a\\*b", "ab").should be_false + refute_file_matches "a\\*b", "ab" File.match?("a\\**b", "a*bb").should be_true - File.match?("a\\**b", "abb").should be_false + refute_file_matches "a\\**b", "abb" File.match?("a*\\*b", "ab*b").should be_true - File.match?("a*\\*b", "abb").should be_false + refute_file_matches "a*\\*b", "abb" + + assert_file_matches "a\\[b\\]", "a[b]" + refute_file_matches "a\\[b\\]", "ab" + assert_file_matches "a\\[bb\\]", "a[bb]" + refute_file_matches "a\\[bb\\]", "abb" + assert_file_matches "a[b]\\[b\\]", "ab[b]" + refute_file_matches "a[b]\\[b\\]", "abb" end + it "special chars" do - File.match?("a?b", "a/b").should be_false - File.match?("a*b", "a/b").should be_false + refute_file_matches "a?b", "a/b" + refute_file_matches "a*b", "a/b" end + it "classes escapes" do - File.match?("[\\]a]", "]").should be_true - File.match?("[\\-]", "-").should be_true - File.match?("[x\\-]", "x").should be_true - File.match?("[x\\-]", "-").should be_true - File.match?("[x\\-]", "z").should be_false - File.match?("[\\-x]", "x").should be_true - File.match?("[\\-x]", "-").should be_true - File.match?("[\\-x]", "a").should be_false + assert_file_matches "[\\]a]", "]" + assert_file_matches "[\\-]", "-" + assert_file_matches "[x\\-]", "x" + assert_file_matches "[x\\-]", "-" + refute_file_matches "[x\\-]", "z" + assert_file_matches "[\\-x]", "x" + assert_file_matches "[\\-x]", "-" + refute_file_matches "[\\-x]", "a" + expect_raises(File::BadPatternError, "empty character set") do File.match?("[]a]", "]") end @@ -1547,18 +1575,19 @@ describe "File" do File.match?("a[", "a") end end + it "alternates" do - File.match?("{abc,def}", "abc").should be_true - File.match?("ab{c,}", "abc").should be_true - File.match?("ab{c,}", "ab").should be_true - File.match?("ab{d,e}", "abc").should be_false - File.match?("ab{*,/cde}", "abcde").should be_true - File.match?("ab{*,/cde}", "ab/cde").should be_true - File.match?("ab{?,/}de", "abcde").should be_true - File.match?("ab{?,/}de", "ab/de").should be_true - File.match?("ab{{c,d}ef,}", "ab").should be_true - File.match?("ab{{c,d}ef,}", "abcef").should be_true - File.match?("ab{{c,d}ef,}", "abdef").should be_true + assert_file_matches "{abc,def}", "abc" + assert_file_matches "ab{c,}", "abc" + assert_file_matches "ab{c,}", "ab" + refute_file_matches "ab{d,e}", "abc" + assert_file_matches "ab{*,/cde}", "abcde" + assert_file_matches "ab{*,/cde}", "ab/cde" + assert_file_matches "ab{?,/}de", "abcde" + assert_file_matches "ab{?,/}de", "ab/de" + assert_file_matches "ab{{c,d}ef,}", "ab" + assert_file_matches "ab{{c,d}ef,}", "abcef" + assert_file_matches "ab{{c,d}ef,}", "abdef" end end diff --git a/src/file.cr b/src/file.cr index 31b8e5420937..c1910abc6826 100644 --- a/src/file.cr +++ b/src/file.cr @@ -458,33 +458,44 @@ class File < IO::FileDescriptor # # The pattern syntax is similar to shell filename globbing. It may contain the following metacharacters: # - # * `*` matches an unlimited number of arbitrary characters excluding `/`. + # * `*` matches an unlimited number of arbitrary characters, excluding any directory separators. # * `"*"` matches all regular files. # * `"c*"` matches all files beginning with `c`. # * `"*c"` matches all files ending with `c`. # * `"*c*"` matches all files that have `c` in them (including at the beginning or end). # * `**` matches directories recursively if followed by `/`. # If this path segment contains any other characters, it is the same as the usual `*`. - # * `?` matches any one character excluding `/`. + # * `?` matches one arbitrary character, excluding any directory separators. # * character sets: - # * `[abc]` matches any one of these character. + # * `[abc]` matches any one of these characters. # * `[^abc]` matches any one character other than these. # * `[a-z]` matches any one character in the range. # * `{a,b}` matches subpattern `a` or `b`. # * `\\` escapes the next character. # - # NOTE: Only `/` is recognized as path separator in both *pattern* and *path*. + # If *path* is a `Path`, all directory separators supported by *path* are + # recognized, according to the path's kind. If *path* is a `String`, only `/` + # is considered a directory separator. + # + # NOTE: Only `/` in *pattern* matches directory separators in *path*. def self.match?(pattern : String, path : Path | String) : Bool expanded_patterns = [] of String File.expand_brace_pattern(pattern, expanded_patterns) + if path.is_a?(Path) + separators = Path.separators(path.@kind) + path = path.to_s + else + separators = Path.separators(Path::Kind::POSIX) + end + expanded_patterns.each do |expanded_pattern| - return true if match_single_pattern(expanded_pattern, path.to_s) + return true if match_single_pattern(expanded_pattern, path, separators) end false end - private def self.match_single_pattern(pattern : String, path : String) + private def self.match_single_pattern(pattern : String, path : String, separators) # linear-time algorithm adapted from https://research.swtch.com/glob preader = Char::Reader.new(pattern) sreader = Char::Reader.new(path) @@ -509,14 +520,14 @@ class File < IO::FileDescriptor preader.next_char next when {'?', false} - if snext && char != '/' + if snext && !char.in?(separators) preader.next_char sreader.next_char next end when {'*', false} double_star = preader.peek_next_char == '*' - if char == '/' && !double_star + if char.in?(separators) && !double_star preader.next_char next_spos = 0 next From 50763f059d4f1186852910aa932e1eeea462c9f5 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 29 Oct 2023 18:07:07 +0800 Subject: [PATCH 0736/1551] Support full exponent range in `BigFloat#**(BigInt)` (#13881) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/big/big_float_spec.cr | 12 +++++++++++- src/big/big_float.cr | 30 ++++++++++++++++++++++++++++++ src/big/lib_gmp.cr | 1 + 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index beea465df8c3..5c98beb320ca 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -180,7 +180,17 @@ describe "BigFloat" do describe "#**" do it { ("1.34".to_big_f ** 2).should be_close("1.7956".to_big_f, 1e-12) } it { ("-0.05".to_big_f ** 10).should be_close("0.00000000000009765625".to_big_f, 1e-12) } - it { (0.1234567890.to_big_f ** 3).should be_close("0.001881676371789154860897069".to_big_f, 1e-12) } + it { ("0.1234567890".to_big_f ** 3).should be_close("0.001881676371789154860897069".to_big_f, 1e-12) } + + it { ("1.34".to_big_f ** 2.to_big_i).should be_close("1.7956".to_big_f, 1e-12) } + it { ("-0.05".to_big_f ** 10.to_big_i).should be_close("0.00000000000009765625".to_big_f, 1e-12) } + it { ("0.1234567890".to_big_f ** 3.to_big_i).should be_close("0.001881676371789154860897069".to_big_f, 1e-12) } + + it { ("10".to_big_f ** (-5).to_big_i).should be_close("0.00001".to_big_f, 1e-12) } + it { ("0.1".to_big_f ** (-5).to_big_i).should be_close("100000".to_big_f, 1e-12) } + it { ("0".to_big_f ** 1.to_big_i).should eq(0.to_big_f) } + it { ("0".to_big_f ** 0.to_big_i).should eq(1.to_big_f) } + it { expect_raises(ArgumentError, "Cannot raise 0 to a negative power") { "0".to_big_f ** (-1).to_big_i } } end describe "#abs" do diff --git a/src/big/big_float.cr b/src/big/big_float.cr index 72042d46c010..33231b422db3 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -149,6 +149,36 @@ struct BigFloat < Float Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational + def **(other : BigInt) : BigFloat + is_zero = self.zero? + if is_zero + case other + when .>(0) + return self + when .<(0) + # there is no BigFloat::Infinity + raise ArgumentError.new "Cannot raise 0 to a negative power" + end + end + + BigFloat.new do |result| + LibGMP.mpf_init_set_si(result, 1) + next if is_zero # define `0 ** 0 == 1` + + # these are mutated and must be copies of `other` and `self`! + exponent = BigInt.new { |mpz| LibGMP.abs(mpz, other) } # `other.abs` + k = BigFloat.new { |mpf| LibGMP.mpf_set(mpf, self) } # `self` + + while exponent > 0 + LibGMP.mpf_mul(result, result, k) if exponent.to_i!.odd? # `result *= k` + LibGMP.fdiv_q_2exp(exponent, exponent, 1) # `exponent /= 2` + LibGMP.mpf_mul(k, k, k) if exponent > 0 # `k *= k` + end + + LibGMP.mpf_ui_div(result, 1, result) if other < 0 # `result = 1 / result` + end + end + def **(other : Int) : BigFloat BigFloat.new { |mpf| LibGMP.mpf_pow_ui(mpf, self, other.to_u64) } end diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index e3fcf09dfd57..52eecdf945fb 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -238,6 +238,7 @@ lib LibGMP fun mpf_mul = __gmpf_mul(rop : MPF*, op1 : MPF*, op2 : MPF*) fun mpf_div = __gmpf_div(rop : MPF*, op1 : MPF*, op2 : MPF*) fun mpf_div_ui = __gmpf_div_ui(rop : MPF*, op1 : MPF*, op2 : UI) + fun mpf_ui_div = __gmpf_ui_div(rop : MPF*, op1 : UI, op2 : MPF*) fun mpf_neg = __gmpf_neg(rop : MPF*, op : MPF*) fun mpf_abs = __gmpf_abs(rop : MPF*, op : MPF*) fun mpf_sqrt = __gmpf_sqrt(rop : MPF*, op : MPF*) From 7aa02829e5e4097952697acdd530af4237c78ad3 Mon Sep 17 00:00:00 2001 From: Sokolov Yura Date: Sun, 29 Oct 2023 13:09:10 +0300 Subject: [PATCH 0737/1551] Add `String` and `Char` patterns to `StringScanner` (#13806) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/string_scanner_spec.cr | 98 +++++++++++++++++++-- src/string_scanner.cr | 145 ++++++++++++++++++++++++++++++-- 2 files changed, 225 insertions(+), 18 deletions(-) diff --git a/spec/std/string_scanner_spec.cr b/spec/std/string_scanner_spec.cr index ae241360a5ec..5513e44b7902 100644 --- a/spec/std/string_scanner_spec.cr +++ b/spec/std/string_scanner_spec.cr @@ -4,8 +4,9 @@ require "string_scanner" describe StringScanner, "#scan" do it "returns the string matched and advances the offset" do s = StringScanner.new("this is a string") - s.scan(/\w+\s/).should eq("this ") - s.scan(/\w+\s/).should eq("is ") + s.scan(/\w+/).should eq("this") + s.scan(' ').should eq(" ") + s.scan("is ").should eq("is ") s.scan(/\w+\s/).should eq("a ") s.scan(/\w+/).should eq("string") end @@ -14,6 +15,8 @@ describe StringScanner, "#scan" do s = StringScanner.new("test string") s.scan(/\w+/).should_not be_nil # => "test" s.scan(/\w+/).should be_nil + s.scan('s').should be_nil + s.scan("string").should be_nil s.scan(/\s\w+/).should_not be_nil # => " string" s.scan(/.*/).should_not be_nil # => "" end @@ -22,15 +25,20 @@ end describe StringScanner, "#scan_until" do it "returns the string matched and advances the offset" do s = StringScanner.new("test string") - s.scan_until(/tr/).should eq("test str") + s.scan_until(/t /).should eq("test ") + s.offset.should eq(5) + s.scan_until("tr").should eq("str") s.offset.should eq(8) - s.scan_until(/g/).should eq("ing") + s.scan_until('n').should eq("in") + s.offset.should eq(10) end it "returns nil if it can't match from the offset" do s = StringScanner.new("test string") s.offset = 8 s.scan_until(/tr/).should be_nil + s.scan_until('r').should be_nil + s.scan_until("tr").should be_nil end end @@ -45,7 +53,10 @@ describe StringScanner, "#skip" do s.skip(/\d+/).should eq(nil) s.offset.should eq(5) - s.skip(/\w+\s/).should eq(3) + s.skip('i').should eq(1) + s.offset.should eq(6) + + s.skip("s ").should eq(2) s.offset.should eq(8) s.skip(/\w+\s/).should eq(2) @@ -64,12 +75,17 @@ describe StringScanner, "#skip_until" do s.offset.should eq(0) s[0]?.should be_nil - s.skip_until(/a\s/).should eq(10) - s.offset.should eq(10) + s.skip_until(/\sis\s/).should eq(8) + s.offset.should eq(8) s[0]?.should_not be_nil - s.skip_until(/ng/).should eq(6) + s.skip_until("st").should eq(4) + s.offset.should eq(12) + s[0]?.should_not be_nil + + s.skip_until("ng").should eq(4) s.offset.should eq(16) + s[0]?.should_not be_nil end end @@ -91,11 +107,17 @@ describe StringScanner, "#check" do s.offset.should eq(5) s.check(/\w+\s/).should eq("is ") s.offset.should eq(5) + s.check('i').should eq("i") + s.offset.should eq(5) + s.check("is ").should eq("is ") + s.offset.should eq(5) end it "returns nil if it can't match from the offset" do s = StringScanner.new("test string") s.check(/\d+/).should be_nil + s.check('0').should be_nil + s.check("01").should be_nil end end @@ -104,14 +126,24 @@ describe StringScanner, "#check_until" do s = StringScanner.new("test string") s.check_until(/tr/).should eq("test str") s.offset.should eq(0) + s.check_until('r').should eq("test str") + s.offset.should eq(0) + s.check_until("tr").should eq("test str") + s.offset.should eq(0) s.check_until(/g/).should eq("test string") s.offset.should eq(0) + s.check_until('g').should eq("test string") + s.offset.should eq(0) + s.check_until("ng").should eq("test string") + s.offset.should eq(0) end it "returns nil if it can't match from the offset" do s = StringScanner.new("test string") s.offset = 8 s.check_until(/tr/).should be_nil + s.check_until('r').should be_nil + s.check_until("tr").should be_nil end end @@ -140,23 +172,47 @@ describe StringScanner, "#[]" do s["wday"].should eq("Fri") s["month"].should eq("Dec") s["day"].should eq("12") + + s.scan(' ').should eq(" ") + s[0].should eq(" ") + s.scan("1975").should eq("1975") + s[0].should eq("1975") end it "raises when there is no last match" do s = StringScanner.new("Fri Dec 12 1975 14:39") + s.scan(/this is not there/) + expect_raises(Exception, "Nil assertion failed") { s[0] } + s.scan('t') + expect_raises(Exception, "Nil assertion failed") { s[0] } + + s.scan("this is not there") expect_raises(Exception, "Nil assertion failed") { s[0] } end it "raises when there is no subgroup" do s = StringScanner.new("Fri Dec 12 1975 14:39") regex = /(?\w+) (?\w+) (?\d+)/ + s.scan(regex) s[0].should_not be_nil expect_raises(IndexError) { s[5] } expect_raises(KeyError, "Capture group 'something' does not exist") { s["something"] } + + s.scan(' ') + + s[0].should_not be_nil + expect_raises(IndexError) { s[1] } + expect_raises(KeyError, "Capture group 'something' does not exist") { s["something"] } + + s.scan("1975") + + s[0].should_not be_nil + expect_raises(IndexError) { s[1] } + expect_raises(KeyError, "Capture group 'something' does not exist") { s["something"] } end end @@ -173,6 +229,11 @@ describe StringScanner, "#[]?" do s["wday"]?.should eq("Fri") s["month"]?.should eq("Dec") s["day"]?.should eq("12") + + s.scan(' ').should eq(" ") + s[0]?.should eq(" ") + s.scan("1975").should eq("1975") + s[0]?.should eq("1975") end it "returns nil when there is no last match" do @@ -180,15 +241,34 @@ describe StringScanner, "#[]?" do s.scan(/this is not there/) s[0]?.should be_nil + + s.scan('t') + s[0]?.should be_nil + + s.scan("this is not there") + s[0]?.should be_nil end it "raises when there is no subgroup" do s = StringScanner.new("Fri Dec 12 1975 14:39") + s.scan(/(?\w+) (?\w+) (?\d+)/) - s[0].should_not be_nil + s[0]?.should_not be_nil s[5]?.should be_nil s["something"]?.should be_nil + + s.scan(' ') + + s[0]?.should_not be_nil + s[1]?.should be_nil + s["something"]?.should be_nil + + s.scan("1975") + + s[0]?.should_not be_nil + s[1]?.should be_nil + s["something"]?.should be_nil end end diff --git a/src/string_scanner.cr b/src/string_scanner.cr index a18a45f35cfc..5e0654bb60b2 100644 --- a/src/string_scanner.cr +++ b/src/string_scanner.cr @@ -61,7 +61,7 @@ # * `#inspect` # * `#string` class StringScanner - @last_match : Regex::MatchData? + @last_match : Regex::MatchData | StringMatchData | Nil def initialize(@str : String) @byte_offset = 0 @@ -86,15 +86,27 @@ class StringScanner # require "string_scanner" # # s = StringScanner.new("test string") - # s.scan(/\w+/) # => "test" - # s.scan(/\w+/) # => nil - # s.scan(/\s\w+/) # => " string" - # s.scan(/.*/) # => "" + # s.scan(/\w+/) # => "test" + # s.scan(/\w+/) # => nil + # s.scan(/\s\w/) # => " s" + # s.scan('t') # => "t" + # s.scan("ring") # => "ring" + # s.scan(/.*/) # => "" # ``` def scan(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String? match(pattern, advance: true, options: options | Regex::MatchOptions::ANCHORED) end + # :ditto: + def scan(pattern : String) : String? + match(pattern, advance: true, anchored: true) + end + + # :ditto: + def scan(pattern : Char) : String? + match(pattern, advance: true, anchored: true) + end + # Scans the string _until_ the *pattern* is matched. Returns the substring up # to and including the end of the match, the last match is saved, and # advances the scan offset. Returns `nil` if no match. @@ -103,15 +115,26 @@ class StringScanner # require "string_scanner" # # s = StringScanner.new("test string") - # s.scan_until(/tr/) # => "test str" - # s.scan_until(/tr/) # => nil - # s.scan_until(/g/) # => "ing" + # s.scan_until(/ s/) # => "test s" + # s.scan_until(/ s/) # => nil + # s.scan_until('r') # => "tr" + # s.scan_until("ng") # => "ing" # ``` def scan_until(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : String? match(pattern, advance: true, options: options) end - private def match(pattern, advance = true, options = Regex::MatchOptions::ANCHORED) + # :ditto: + def scan_until(pattern : String) : String? + match(pattern, advance: true, anchored: false) + end + + # :ditto: + def scan_until(pattern : Char) : String? + match(pattern, advance: true, anchored: false) + end + + private def match(pattern : Regex, advance = true, options = Regex::MatchOptions::ANCHORED) match = pattern.match_at_byte_index(@str, @byte_offset, options) @last_match = match if match @@ -125,6 +148,32 @@ class StringScanner end end + private def match(pattern : String | Char, advance = true, anchored = true) + @last_match = nil + if pattern.bytesize > @str.bytesize - @byte_offset + nil + elsif anchored + i = 0 + # check string starts with string or char + unsafe_ptr = @str.to_unsafe + @byte_offset + pattern.each_byte do |byte| + return nil unless unsafe_ptr[i] == byte + i += 1 + end + # ok, it starts + result = pattern.to_s + @last_match = StringMatchData.new(result) + @byte_offset += pattern.bytesize if advance + result + elsif (found = @str.byte_index(pattern, @byte_offset)) + finish = found + pattern.bytesize + result = @str.byte_slice(@byte_offset, finish - @byte_offset) + @byte_offset = finish if advance + @last_match = StringMatchData.new(result) + result + end + end + # Attempts to skip over the given *pattern* beginning with the scan offset. # In other words, the pattern is not anchored to the current scan offset. # @@ -139,6 +188,18 @@ class StringScanner match.size if match end + # :ditto: + def skip(pattern : String) : Int32? + match = scan(pattern) + match.size if match + end + + # :ditto: + def skip(pattern : Char) : Int32? + match = scan(pattern) + match.size if match + end + # Attempts to skip _until_ the given *pattern* is found after the scan # offset. In other words, the pattern is not anchored to the current scan # offset. @@ -155,6 +216,18 @@ class StringScanner match.size if match end + # :ditto: + def skip_until(pattern : String) : Int32? + match = scan_until(pattern) + match.size if match + end + + # :ditto: + def skip_until(pattern : Char) : Int32? + match = scan_until(pattern) + match.size if match + end + # Returns the value that `#scan` would return, without advancing the scan # offset. The last match is still saved, however. # @@ -170,6 +243,16 @@ class StringScanner match(pattern, advance: false, options: options | Regex::MatchOptions::ANCHORED) end + # :ditto: + def check(pattern : String) : String? + match(pattern, advance: false, anchored: true) + end + + # :ditto: + def check(pattern : Char) : String? + match(pattern, advance: false, anchored: true) + end + # Returns the value that `#scan_until` would return, without advancing the # scan offset. The last match is still saved, however. # @@ -184,6 +267,16 @@ class StringScanner match(pattern, advance: false, options: options) end + # :ditto: + def check_until(pattern : String) : String? + match(pattern, advance: false, anchored: false) + end + + # :ditto: + def check_until(pattern : Char) : String? + match(pattern, advance: false, anchored: false) + end + # Returns the *n*-th subgroup in the most recent match. # # Raises an exception if there was no last match or if there is no subgroup. @@ -293,4 +386,38 @@ class StringScanner start = Math.min(Math.max(offset - 2, 0), Math.max(0, @str.size - 5)) io << " \"" << @str[start, 5] << "\" >" end + + # :nodoc: + struct StringMatchData + def initialize(@str : String) + end + + def []?(n : Int) : String? + return unless n == 0 || n == -1 + @str + end + + def [](n : Int) : String + self[n]? || raise IndexError.new("Invalid capture group index: #{n}") + end + + def []?(group_name : String) : String? + nil + end + + def [](group_name : String) : String + raise KeyError.new("Capture group '#{group_name}' does not exist") + end + + def []?(range : Range) : Array(String)? + start, count = Indexable.range_to_index_and_count(range, 1) || return nil + start, count = Indexable.normalize_start_and_count(start, count, 1) { return nil } + return [] of String if count == 0 + [@str] + end + + def [](range : Range) : Array(String) + self[range]? || raise IndexError.new + end + end end From 4c836be2d2e96ea50c262f649f402ae5c7115ea6 Mon Sep 17 00:00:00 2001 From: kojix2 <2xijok@gmail.com> Date: Tue, 31 Oct 2023 19:02:05 +0900 Subject: [PATCH 0738/1551] Fix a typo (#13914) --- src/llvm/lib_llvm.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 2f3d52d1d3bd..acc60746a018 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -355,7 +355,7 @@ lib LibLLVM {% end %} fun const_string_in_context = LLVMConstStringInContext(c : ContextRef, str : UInt8*, length : UInt32, dont_null_terminate : Int32) : ValueRef - fun const_struct_in_context = LLVMConstStructInContext(c : ContextRef, contant_vals : ValueRef*, count : UInt32, packed : Int32) : ValueRef + fun const_struct_in_context = LLVMConstStructInContext(c : ContextRef, constant_vals : ValueRef*, count : UInt32, packed : Int32) : ValueRef fun get_md_kind_id_in_context = LLVMGetMDKindIDInContext(c : ContextRef, name : UInt8*, slen : UInt32) : UInt32 fun md_node_in_context = LLVMMDNodeInContext(c : ContextRef, values : ValueRef*, count : Int32) : ValueRef From c97c20e36167b0c9689995b9e020a0105a2d0ec8 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Tue, 31 Oct 2023 05:02:17 -0500 Subject: [PATCH 0739/1551] Fix `Process.exists?` throwing errors on EPERM (#13911) --- src/crystal/system/unix/process.cr | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index f07a91806857..0d4b3c1a9235 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -79,8 +79,14 @@ struct Crystal::System::Process if ret == 0 true else - return false if Errno.value == Errno::ESRCH - raise RuntimeError.from_errno("kill") + case Errno.value + when Errno::EPERM + true + when Errno::ESRCH + false + else + raise RuntimeError.from_errno("kill") + end end end From f304b578839eb82bf70a6c97c6ddb2692573a2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 1 Nov 2023 20:21:51 +0100 Subject: [PATCH 0740/1551] Drop `Char::Reader#@end` (#13920) --- spec/std/char/reader_spec.cr | 5 +++++ src/char/reader.cr | 30 ++++++++++++------------------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/spec/std/char/reader_spec.cr b/spec/std/char/reader_spec.cr index 4d9ec1924f4f..6197a3d4b6c8 100644 --- a/spec/std/char/reader_spec.cr +++ b/spec/std/char/reader_spec.cr @@ -102,6 +102,7 @@ describe "Char::Reader" do reader.pos.should eq(0) reader.current_char.ord.should eq(0) reader.has_previous?.should be_false + reader.has_next?.should be_false end it "gets previous char (ascii)" do @@ -109,8 +110,10 @@ describe "Char::Reader" do reader.pos.should eq(4) reader.current_char.should eq('o') reader.has_previous?.should be_true + reader.has_next?.should be_true reader.previous_char.should eq('l') + reader.has_next?.should be_true reader.previous_char.should eq('l') reader.previous_char.should eq('e') reader.previous_char.should eq('h') @@ -126,8 +129,10 @@ describe "Char::Reader" do reader.pos.should eq(9) reader.current_char.should eq('語') reader.has_previous?.should be_true + reader.has_next?.should be_true reader.previous_char.should eq('本') + reader.has_next?.should be_true reader.previous_char.should eq('日') reader.previous_char.should eq('á') reader.previous_char.should eq('h') diff --git a/src/char/reader.cr b/src/char/reader.cr index ae1a20258db4..cb307117cdbb 100644 --- a/src/char/reader.cr +++ b/src/char/reader.cr @@ -59,7 +59,6 @@ struct Char @pos = pos.to_i @current_char = '\0' @current_char_width = 0 - @end = false decode_current_char end @@ -69,7 +68,6 @@ struct Char @pos = @string.bytesize @current_char = '\0' @current_char_width = 0 - @end = false decode_previous_char end @@ -83,7 +81,7 @@ struct Char # reader.peek_next_char # => '\0' # ``` def has_next? : Bool - !@end + @pos < @string.bytesize end # Reads the next character in the string, @@ -177,7 +175,7 @@ struct Char # C # ``` def each(&) : Nil - while @pos < @string.bytesize + while has_next? yield current_char @pos += @current_char_width @@ -251,26 +249,22 @@ struct Char private def decode_current_char decode_char_at(@pos) do |code_point, width, error| @current_char_width = width - @end = @pos == @string.bytesize @error = error @current_char = code_point.unsafe_chr end end private def decode_previous_char - if @pos == 0 - @end = @pos == @string.bytesize - else - while @pos > 0 - @pos -= 1 - break if (byte_at(@pos) & 0xC0) != 0x80 - end - decode_char_at(@pos) do |code_point, width, error| - @current_char_width = width - @end = @pos == @string.bytesize - @error = error - @current_char = code_point.unsafe_chr - end + return if @pos == 0 + + while @pos > 0 + @pos -= 1 + break if (byte_at(@pos) & 0xC0) != 0x80 + end + decode_char_at(@pos) do |code_point, width, error| + @current_char_width = width + @error = error + @current_char = code_point.unsafe_chr end end From 1e4be9ba5b737d24cd8caf08dd34b9c3a0e6cbb7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 2 Nov 2023 03:22:27 +0800 Subject: [PATCH 0741/1551] Add `Crystal::HOST_TRIPLE` and `TARGET_TRIPLE` (#13823) --- spec/compiler/config_spec.cr | 5 ++--- src/compiler/crystal/program.cr | 9 ++++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/spec/compiler/config_spec.cr b/spec/compiler/config_spec.cr index 1803638dde43..4dfceff7968d 100644 --- a/spec/compiler/config_spec.cr +++ b/spec/compiler/config_spec.cr @@ -4,9 +4,8 @@ require "./spec_helper" describe Crystal::Config do it ".host_target" do {% begin %} - # TODO: CRYSTAL_SPEC_COMPILER_BIN must be quoted (#11456) - {% compiler = (env("CRYSTAL_SPEC_COMPILER_BIN") || "bin/crystal").id %} - Crystal::Config.host_target.should eq Crystal::Codegen::Target.new({{ `#{compiler} --version`.lines[-1] }}.lchop("Default target: ")) + {% host_triple = Crystal.constant("HOST_TRIPLE") || Crystal::DESCRIPTION.lines[-1].gsub(/^Default target: /, "") %} + Crystal::Config.host_target.should eq Crystal::Codegen::Target.new({{ host_triple }}) {% end %} end diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 97808279061c..936fc6a0a270 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -118,7 +118,7 @@ module Crystal # A `ProgressTracker` object which tracks compilation progress. property progress_tracker = ProgressTracker.new - property codegen_target = Config.host_target + getter codegen_target = Config.host_target getter predefined_constants = Array(Const).new @@ -289,6 +289,8 @@ module Crystal define_crystal_string_constant "LIBRARY_RPATH", Crystal::CrystalLibraryPath.default_rpath define_crystal_string_constant "VERSION", Crystal::Config.version define_crystal_string_constant "LLVM_VERSION", Crystal::Config.llvm_version + define_crystal_string_constant "HOST_TRIPLE", Crystal::Config.host_target.to_s + define_crystal_string_constant "TARGET_TRIPLE", Crystal::Config.host_target.to_s end private def define_crystal_string_constant(name, value) @@ -307,6 +309,11 @@ module Crystal property(target_machine : LLVM::TargetMachine) { codegen_target.to_target_machine } + def codegen_target=(@codegen_target : Codegen::Target) : Codegen::Target + crystal.types["TARGET_TRIPLE"].as(Const).value.as(StringLiteral).value = codegen_target.to_s + @codegen_target + end + # Returns the `Type` for `Array(type)` def array_of(type) array.instantiate [type] of TypeVar From 7196d0ffa7d681fb642e059de6285aa68bd4cb92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 2 Nov 2023 13:56:32 +0100 Subject: [PATCH 0742/1551] Add `--check` flag to `crystal tool unreachable` (#13930) --- src/compiler/crystal/command.cr | 18 +++++++++++++----- src/compiler/crystal/tools/unreachable.cr | 6 +++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index a6b5c18455ea..cdc98b7a96f5 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -247,8 +247,8 @@ class Crystal::Command end end - private def compile_no_codegen(command, wants_doc = false, hierarchy = false, no_cleanup = false, cursor_command = false, top_level = false, path_filter = false) - config = create_compiler command, no_codegen: true, hierarchy: hierarchy, cursor_command: cursor_command, path_filter: path_filter + private def compile_no_codegen(command, wants_doc = false, hierarchy = false, no_cleanup = false, cursor_command = false, top_level = false, path_filter = false, unreachable_command = false) + config = create_compiler command, no_codegen: true, hierarchy: hierarchy, cursor_command: cursor_command, path_filter: path_filter, unreachable_command: unreachable_command config.compiler.no_codegen = true config.compiler.no_cleanup = no_cleanup config.compiler.wants_doc = wants_doc @@ -349,7 +349,8 @@ class Crystal::Command combine_rpath : Bool, includes : Array(String), excludes : Array(String), - verbose : Bool do + verbose : Bool, + check : Bool do def compile(output_filename = self.output_filename) compiler.emit_base_filename = emit_base_filename || output_filename.rchop(File.extname(output_filename)) compiler.compile sources, output_filename, combine_rpath: combine_rpath @@ -363,7 +364,7 @@ class Crystal::Command private def create_compiler(command, no_codegen = false, run = false, hierarchy = false, cursor_command = false, single_file = false, dependencies = false, - path_filter = false) + path_filter = false, unreachable_command = false) compiler = new_compiler compiler.progress_tracker = @progress_tracker link_flags = [] of String @@ -380,6 +381,7 @@ class Crystal::Command excludes = [] of String includes = [] of String verbose = false + check = false option_parser = parse_with_crystal_opts do |opts| opts.banner = "Usage: crystal #{command} [options] [programfile] [--] [arguments]\n\nOptions:" @@ -446,6 +448,12 @@ class Crystal::Command end end + if unreachable_command + opts.on("--check", "Exits with error if there is any unreachable code") do |f| + check = true + end + end + opts.on("--error-trace", "Show full error trace") do compiler.show_error_trace = true @error_trace = true @@ -609,7 +617,7 @@ class Crystal::Command combine_rpath = run && !no_codegen @config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format, - dependency_output_format.not_nil!, combine_rpath, includes, excludes, verbose + dependency_output_format.not_nil!, combine_rpath, includes, excludes, verbose, check end private def gather_sources(filenames) diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr index f9e80db7bc66..db05a591d046 100644 --- a/src/compiler/crystal/tools/unreachable.cr +++ b/src/compiler/crystal/tools/unreachable.cr @@ -5,7 +5,7 @@ require "json" module Crystal class Command private def unreachable - config, result = compile_no_codegen "tool unreachable", path_filter: true + config, result = compile_no_codegen "tool unreachable", path_filter: true, unreachable_command: true format = config.output_format unreachable = UnreachableVisitor.new @@ -31,6 +31,10 @@ module Crystal else defs.to_text(STDOUT) end + + if config.check + exit 1 unless defs.defs.empty? + end end end From 6e3a66e9100870369671250e494705ac52955895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 2 Nov 2023 13:57:02 +0100 Subject: [PATCH 0743/1551] Add annotations to output of `crystal tool unreachable` (#13927) --- src/compiler/crystal/tools/unreachable.cr | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr index db05a591d046..f70de18a0282 100644 --- a/src/compiler/crystal/tools/unreachable.cr +++ b/src/compiler/crystal/tools/unreachable.cr @@ -46,6 +46,10 @@ module Crystal io << a_def.location << "\t" io << a_def.short_reference << "\t" io << a_def.length << " lines" + if annotations = a_def.all_annotations + io << "\t" + annotations.join(io, " ") + end io.puts end end @@ -59,6 +63,9 @@ module Crystal if lines = a_def.length builder.field "lines", lines end + if annotations = a_def.all_annotations + builder.field "annotations", annotations.map(&.to_s) + end end end end From 0dbafcb1b727c6e6844f373399cdd4cd98940f9b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 3 Nov 2023 01:45:52 +0800 Subject: [PATCH 0744/1551] Add `Math.fma` (#13934) --- spec/std/math_spec.cr | 10 +++++++++ .../crystal/interpreter/instructions.cr | 10 +++++++++ .../crystal/interpreter/primitives.cr | 6 ++++++ src/math/libm.cr | 6 ++++++ src/math/math.cr | 21 +++++++++++++++++++ 5 files changed, 53 insertions(+) diff --git a/spec/std/math_spec.cr b/spec/std/math_spec.cr index 9106636a377e..ede2149110eb 100644 --- a/spec/std/math_spec.cr +++ b/spec/std/math_spec.cr @@ -266,6 +266,16 @@ describe "Math" do # div rem + it "fma" do + x = Math.fma(0.1, 10.0, -1.0) + x.should be_close(5.551115123125783e-17, 1e-25) + x.should_not eq(0.0) + + x = Math.fma(0.1_f32, 10.0_f32, -1.0_f32) + x.should be_close(1.4901161e-8_f32, 1e-16_f32) + x.should_not eq(0.0_f32) + end + describe ".pw2ceil" do {% for int in %w(Int8 Int16 Int32 Int64 Int128) %} it {{ int }} do diff --git a/src/compiler/crystal/interpreter/instructions.cr b/src/compiler/crystal/interpreter/instructions.cr index 2c55cfe255df..67966277bf15 100644 --- a/src/compiler/crystal/interpreter/instructions.cr +++ b/src/compiler/crystal/interpreter/instructions.cr @@ -1837,6 +1837,16 @@ require "./repl" push: true, code: LibM.floor_f64(value), }, + libm_fma_f32: { + pop_values: [value1 : Float32, value2 : Float32, value3 : Float32], + push: true, + code: LibM.fma_f32(value1, value2, value3), + }, + libm_fma_f64: { + pop_values: [value1 : Float64, value2 : Float64, value3 : Float64], + push: true, + code: LibM.fma_f64(value1, value2, value3), + }, libm_log_f32: { pop_values: [value : Float32], push: true, diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr index 2063830e0eed..1d50568c5d62 100644 --- a/src/compiler/crystal/interpreter/primitives.cr +++ b/src/compiler/crystal/interpreter/primitives.cr @@ -552,6 +552,12 @@ class Crystal::Repl::Compiler when "interpreter_libm_floor_f64" accept_call_args(node) libm_floor_f64 node: node + when "interpreter_libm_fma_f32" + accept_call_args(node) + libm_fma_f32 node: node + when "interpreter_libm_fma_f64" + accept_call_args(node) + libm_fma_f64 node: node when "interpreter_libm_log_f32" accept_call_args(node) libm_log_f32 node: node diff --git a/src/math/libm.cr b/src/math/libm.cr index 8130a8ac31a2..59e66f12737e 100644 --- a/src/math/libm.cr +++ b/src/math/libm.cr @@ -47,6 +47,12 @@ lib LibM {% if flag?(:interpreted) %} @[Primitive(:interpreter_libm_floor_f64)] {% end %} fun floor_f64 = "llvm.floor.f64"(value : Float64) : Float64 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_libm_fma_f32)] {% end %} + fun fma_f32 = "llvm.fma.f32"(value1 : Float32, value2 : Float32, value3 : Float32) : Float32 + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_libm_fma_f64)] {% end %} + fun fma_f64 = "llvm.fma.f64"(value1 : Float64, value2 : Float64, value3 : Float64) : Float64 + {% if flag?(:interpreted) %} @[Primitive(:interpreter_libm_log_f32)] {% end %} fun log_f32 = "llvm.log.f32"(value : Float32) : Float32 diff --git a/src/math/math.cr b/src/math/math.cr index f29eeab386e4..efeef9ad9e12 100644 --- a/src/math/math.cr +++ b/src/math/math.cr @@ -575,6 +575,27 @@ module Math hypot(value1.to_f, value2.to_f) end + # Fused multiply-add; returns `value1 * value2 + value3`, performing a single + # rounding instead of two. + # + # ``` + # Math.fma(0.1, 10.0, -1.0) # => 5.551115123125783e-17 + # 1.0 * 10.0 - 1.0 # => 0.0 + # ``` + def fma(value1 : Float32, value2 : Float32, value3 : Float32) : Float32 + LibM.fma_f32(value1, value2, value3) + end + + # :ditto: + def fma(value1 : Float64, value2 : Float64, value3 : Float64) : Float64 + LibM.fma_f64(value1, value2, value3) + end + + # :ditto: + def fma(value1, value2, value3) + fma(value1.to_f, value2.to_f, value3.to_f) + end + # Returns the unbiased base 2 exponent of the given floating-point *value*. def ilogb(value : Float32) : Int32 LibM.ilogb_f32(value) From ee412b0d82ae129557cf006f7d00c4b01f03dc92 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 3 Nov 2023 01:46:16 +0800 Subject: [PATCH 0745/1551] Support `%r` and `%x` when not followed by delimiter start (#13933) --- spec/compiler/parser/parser_spec.cr | 28 ++++++++++++++++++++++++++++ src/compiler/crystal/syntax/lexer.cr | 10 ++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 28f273be147c..87f77174d49f 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -160,6 +160,34 @@ module Crystal it_parses "[1] /2", Call.new(([1.int32] of ASTNode).array, "/", 2.int32) it_parses "2**3**4", Call.new(2.int32, "**", Call.new(3.int32, "**", 4.int32)) + it_parses %(foo%i), Call.new("foo".call, "%", "i".call) + it_parses %(foo%q), Call.new("foo".call, "%", "q".call) + it_parses %(foo%Q), Call.new("foo".call, "%", "Q".path) + it_parses %(foo%r), Call.new("foo".call, "%", "r".call) + it_parses %(foo%x), Call.new("foo".call, "%", "x".call) + it_parses %(foo%w), Call.new("foo".call, "%", "w".call) + + it_parses %(foo %i), Call.new("foo".call, "%", "i".call) + it_parses %(foo %q), Call.new("foo".call, "%", "q".call) + it_parses %(foo %Q), Call.new("foo".call, "%", "Q".path) + it_parses %(foo %r), Call.new("foo".call, "%", "r".call) + it_parses %(foo %x), Call.new("foo".call, "%", "x".call) + it_parses %(foo %w), Call.new("foo".call, "%", "w".call) + + it_parses %(foo %i()), "foo".call(([] of ASTNode).array_of(Path.global("Symbol"))) + it_parses %(foo %q()), "foo".call("".string) + it_parses %(foo %Q()), "foo".call("".string) + it_parses %(foo %r()), "foo".call(regex("")) + it_parses %(foo %x()), "foo".call(Call.new(nil, "`", "".string)) + it_parses %(foo %w()), "foo".call(([] of ASTNode).array_of(Path.global("String"))) + + it_parses %(foo % i()), Call.new("foo".call, "%", "i".call) + it_parses %(foo % q()), Call.new("foo".call, "%", "q".call) + it_parses %(foo % Q()), Call.new("foo".call, "%", Generic.new("Q".path, [] of ASTNode)) + it_parses %(foo % r()), Call.new("foo".call, "%", "r".call) + it_parses %(foo % x()), Call.new("foo".call, "%", "x".call) + it_parses %(foo % w()), Call.new("foo".call, "%", "w".call) + it_parses "!1", Not.new(1.int32) it_parses "- 1", Call.new(1.int32, "-") it_parses "+ 1", Call.new(1.int32, "+") diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index bb4fbe50f4a9..cdb0f1a33e1c 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -335,18 +335,20 @@ module Crystal @token.type = :OP_PERCENT end when 'r' - case next_char + case peek_next_char when '(', '[', '{', '<', '|' + next_char delimited_pair :regex, current_char, closing_char, start else - raise "unknown %r char" + @token.type = :OP_PERCENT end when 'x' - case next_char + case peek_next_char when '(', '[', '{', '<', '|' + next_char delimited_pair :command, current_char, closing_char, start else - raise "unknown %x char" + @token.type = :OP_PERCENT end when 'w' case peek_next_char From b0b006d1cc3b58f34f0a5541ec0fd5d84c67cbf3 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 3 Nov 2023 01:46:28 +0800 Subject: [PATCH 0746/1551] Fix location of global `Path` nodes in certain constructs (#13932) --- spec/compiler/parser/parser_spec.cr | 12 ++++++++++++ src/compiler/crystal/syntax/parser.cr | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 87f77174d49f..c70bac5fa998 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2764,5 +2764,17 @@ module Crystal node = Parser.parse(source).as(EnumDef).members.first node_source(source, node).should eq("protected macro foo; end") end + + it "sets correct location of global path in class def" do + source = "class ::Foo; end" + node = Parser.parse(source).as(ClassDef).name + node_source(source, node).should eq("::Foo") + end + + it "sets correct location of global path in annotation" do + source = "@[::Foo]" + node = Parser.parse(source).as(Annotation).path + node_source(source, node).should eq("::Foo") + end end end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index f891fe640824..e65c7a981b3b 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -5101,7 +5101,7 @@ module Crystal global = true end - path = parse_path(global, @token.location) + path = parse_path(global, location) skip_space path end From 5d689798aec7bb47da9340bbdc9acc6a30213f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 3 Nov 2023 00:03:42 +0100 Subject: [PATCH 0747/1551] Refactor `HTML.unescape` in native Crystal (#13844) --- spec/std/html_spec.cr | 14 +- spec/wasm32_std_spec.cr | 2 +- src/html.cr | 156 +- src/html/entities.cr | 4469 ++++++++++++++++++++------------------- 4 files changed, 2364 insertions(+), 2277 deletions(-) diff --git a/spec/std/html_spec.cr b/spec/std/html_spec.cr index 39e80a5961ca..b4f374dca936 100644 --- a/spec/std/html_spec.cr +++ b/spec/std/html_spec.cr @@ -23,11 +23,11 @@ describe "HTML" do context "numeric entities" do it "decimal" do - HTML.unescape("3 + 2 = 5").should eq("3 + 2 = 5") + HTML.unescape("3 + 2 = 5 ").should eq("3 + 2 = 5 \t") end it "hex" do - HTML.unescape("3 + 2 = 5").should eq("3 + 2 = 5") + HTML.unescape("3 + 2 = 5 ").should eq("3 + 2 = 5 \t") end it "early termination" do @@ -48,6 +48,12 @@ describe "HTML" do it "does not unescape characters above Char::MAX_CODEPOINT" do HTML.unescape("limit �").should eq("limit \uFFFD") HTML.unescape("limit �").should eq("limit \uFFFD") + HTML.unescape("limit �").should eq("limit \uFFFD") + end + + it "ignores leading zeros" do + HTML.unescape("A").should eq("A") + HTML.unescape("e").should eq("e") end it "space characters" do @@ -101,9 +107,7 @@ describe "HTML" do end it "invalid utf-8" do - expect_raises(ArgumentError, "UTF-8 error") do - HTML.unescape("test \xff\xfe").should eq "test \xff\xfe" - end + HTML.unescape("test \xff\xfe").should eq "test \xff\xfe" end end end diff --git a/spec/wasm32_std_spec.cr b/spec/wasm32_std_spec.cr index cd0e0c1507f1..bd1fd39de7a0 100644 --- a/spec/wasm32_std_spec.cr +++ b/spec/wasm32_std_spec.cr @@ -84,7 +84,7 @@ require "./std/float_printer/ieee_spec.cr" require "./std/float_spec.cr" require "./std/gc_spec.cr" require "./std/hash_spec.cr" -require "./std/html_spec.cr" +# require "./std/html_spec.cr" (failed to run) # require "./std/http/chunked_content_spec.cr" (failed linking) # require "./std/http/client/client_spec.cr" (failed linking) # require "./std/http/client/response_spec.cr" (failed linking) diff --git a/src/html.cr b/src/html.cr index 75ff1c246f02..fa90c25498c9 100644 --- a/src/html.cr +++ b/src/html.cr @@ -114,47 +114,83 @@ module HTML # HTML.unescape("Crystal & You") # => "Crystal & You" # ``` def self.unescape(string : String) : String - string.gsub(/&(?:([a-zA-Z0-9]{2,32};?)|\#([0-9]+);?|\#[xX]([0-9A-Fa-f]+);?)/) do |string, match| - if code = match[1]? - # Try to find the code - value = named_entity(code) - - unless value || code.ends_with?(';') - # If we can't find it and it doesn't end with ';', - # we need to find each prefix of it. - # We start from the largest prefix. - removed = 0 - until code.empty? - code = code.rchop - removed += 1 - - value = named_entity(code) - if value - # If we find it, we need to append the part that - # isn't part of the matched code - value += string[-removed..-1] - break - end - end - end - - # We either found the code or not, - # in which case we need to return the original string - value || string - elsif code = match[2]? - # Find by decimal code - decode_codepoint(code.to_i) || string - elsif code = match[3]? - # Find by hexadecimal code - decode_codepoint(code.to_i(16)) || string - else - string - end + return string unless string.includes?('&') + + String.build(string.bytesize) do |io| + unescape(string.to_slice, io) + end + end + + private def self.unescape(slice, io) + while bytesize = slice.index('&'.ord) + io.write(slice[0, bytesize]) + slice += bytesize &+ 1 + + ptr = unescape_entity(slice.to_unsafe, io) + slice += ptr - slice.to_unsafe end + + io.write slice end - private def self.named_entity(code) - HTML::SINGLE_CHAR_ENTITIES[code]? || HTML::DOUBLE_CHAR_ENTITIES[code]? + private def self.unescape_entity(ptr, io) + if '#' === ptr.value + unescape_numbered_entity(ptr, io) + else + unescape_named_entity(ptr, io) + end + end + + private def self.unescape_numbered_entity(ptr, io) + start_ptr = ptr + + ptr += 1 + + hex = ptr.value.unsafe_chr.in?('x', 'X') + if hex + ptr += 1 + base = 16 + else + base = 10 + end + + x = 0_u32 + + # skip leading zeros + while ptr.value === '0' + ptr += 1 + end + number_start_ptr = ptr + + while digit = ptr.value.unsafe_chr.to_i?(base) + # The number of consumed digits is limited to the representation of + # Char::MAX_CODEPOINT which is below that of UInt32::MAX + x &*= base + x &+= digit + + ptr += 1 + end + + if ptr - number_start_ptr > 8 + # size exceeds maxlength, so it can't be a valid codepoint and might have + # overflow. + x = 0_u32 + end + + size = ptr - start_ptr - (hex ? 2 : 1) + unless size > 0 && (char = decode_codepoint(x)) + # No characters matched or invalid codepoint + io << '&' + return start_ptr + end + + char.to_s(io) + + if ptr.value === ';' + ptr += 1 + end + + return ptr end # see https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state @@ -181,4 +217,48 @@ module HTML end end end + + private def self.unescape_named_entity(ptr, io) + # Consume the maximum number of characters possible, with the + # consumed characters matching one of the named references. + start_ptr = ptr + + while ptr.value.unsafe_chr.ascii_alphanumeric? + ptr += 1 + end + + if ptr == start_ptr + io << '&' + return start_ptr + end + + # The entity name cannot be longer than the longest name in the lookup tables. + entity_name = Slice.new(start_ptr, Math.min(ptr - start_ptr, MAX_ENTITY_NAME_SIZE)) + + # If we cann't find an entity on the first try, we need to search each prefix + # of it, starting from the largest. + while entity_name.size >= 2 + case + when x = SINGLE_CHAR_ENTITIES[entity_name]? + io << x + when x = DOUBLE_CHAR_ENTITIES[entity_name]? + io << x + else + entity_name = entity_name[0..-2] + next + end + + ptr = start_ptr + entity_name.size + if ptr.value === ';' + ptr += 1 + end + + return ptr + end + + # range -1 includes the leading '&' + start_ptr -= 1 + io.write Slice.new(start_ptr, ptr - start_ptr) + ptr + end end diff --git a/src/html/entities.cr b/src/html/entities.cr index 33b0a956bad3..3fe05fb63983 100644 --- a/src/html/entities.cr +++ b/src/html/entities.cr @@ -1,2240 +1,2243 @@ module HTML # :nodoc: SINGLE_CHAR_ENTITIES = { - "AElig;" => '\u{0000C6}', - "AMP;" => '\u{000026}', - "Aacute;" => '\u{0000C1}', - "Abreve;" => '\u{000102}', - "Acirc;" => '\u{0000C2}', - "Acy;" => '\u{000410}', - "Afr;" => '\u{01D504}', - "Agrave;" => '\u{0000C0}', - "Alpha;" => '\u{000391}', - "Amacr;" => '\u{000100}', - "And;" => '\u{002A53}', - "Aogon;" => '\u{000104}', - "Aopf;" => '\u{01D538}', - "ApplyFunction;" => '\u{002061}', - "Aring;" => '\u{0000C5}', - "Ascr;" => '\u{01D49C}', - "Assign;" => '\u{002254}', - "Atilde;" => '\u{0000C3}', - "Auml;" => '\u{0000C4}', - "Backslash;" => '\u{002216}', - "Barv;" => '\u{002AE7}', - "Barwed;" => '\u{002306}', - "Bcy;" => '\u{000411}', - "Because;" => '\u{002235}', - "Bernoullis;" => '\u{00212C}', - "Beta;" => '\u{000392}', - "Bfr;" => '\u{01D505}', - "Bopf;" => '\u{01D539}', - "Breve;" => '\u{0002D8}', - "Bscr;" => '\u{00212C}', - "Bumpeq;" => '\u{00224E}', - "CHcy;" => '\u{000427}', - "COPY;" => '\u{0000A9}', - "Cacute;" => '\u{000106}', - "Cap;" => '\u{0022D2}', - "CapitalDifferentialD;" => '\u{002145}', - "Cayleys;" => '\u{00212D}', - "Ccaron;" => '\u{00010C}', - "Ccedil;" => '\u{0000C7}', - "Ccirc;" => '\u{000108}', - "Cconint;" => '\u{002230}', - "Cdot;" => '\u{00010A}', - "Cedilla;" => '\u{0000B8}', - "CenterDot;" => '\u{0000B7}', - "Cfr;" => '\u{00212D}', - "Chi;" => '\u{0003A7}', - "CircleDot;" => '\u{002299}', - "CircleMinus;" => '\u{002296}', - "CirclePlus;" => '\u{002295}', - "CircleTimes;" => '\u{002297}', - "ClockwiseContourIntegral;" => '\u{002232}', - "CloseCurlyDoubleQuote;" => '\u{00201D}', - "CloseCurlyQuote;" => '\u{002019}', - "Colon;" => '\u{002237}', - "Colone;" => '\u{002A74}', - "Congruent;" => '\u{002261}', - "Conint;" => '\u{00222F}', - "ContourIntegral;" => '\u{00222E}', - "Copf;" => '\u{002102}', - "Coproduct;" => '\u{002210}', - "CounterClockwiseContourIntegral;" => '\u{002233}', - "Cross;" => '\u{002A2F}', - "Cscr;" => '\u{01D49E}', - "Cup;" => '\u{0022D3}', - "CupCap;" => '\u{00224D}', - "DD;" => '\u{002145}', - "DDotrahd;" => '\u{002911}', - "DJcy;" => '\u{000402}', - "DScy;" => '\u{000405}', - "DZcy;" => '\u{00040F}', - "Dagger;" => '\u{002021}', - "Darr;" => '\u{0021A1}', - "Dashv;" => '\u{002AE4}', - "Dcaron;" => '\u{00010E}', - "Dcy;" => '\u{000414}', - "Del;" => '\u{002207}', - "Delta;" => '\u{000394}', - "Dfr;" => '\u{01D507}', - "DiacriticalAcute;" => '\u{0000B4}', - "DiacriticalDot;" => '\u{0002D9}', - "DiacriticalDoubleAcute;" => '\u{0002DD}', - "DiacriticalGrave;" => '\u{000060}', - "DiacriticalTilde;" => '\u{0002DC}', - "Diamond;" => '\u{0022C4}', - "DifferentialD;" => '\u{002146}', - "Dopf;" => '\u{01D53B}', - "Dot;" => '\u{0000A8}', - "DotDot;" => '\u{0020DC}', - "DotEqual;" => '\u{002250}', - "DoubleContourIntegral;" => '\u{00222F}', - "DoubleDot;" => '\u{0000A8}', - "DoubleDownArrow;" => '\u{0021D3}', - "DoubleLeftArrow;" => '\u{0021D0}', - "DoubleLeftRightArrow;" => '\u{0021D4}', - "DoubleLeftTee;" => '\u{002AE4}', - "DoubleLongLeftArrow;" => '\u{0027F8}', - "DoubleLongLeftRightArrow;" => '\u{0027FA}', - "DoubleLongRightArrow;" => '\u{0027F9}', - "DoubleRightArrow;" => '\u{0021D2}', - "DoubleRightTee;" => '\u{0022A8}', - "DoubleUpArrow;" => '\u{0021D1}', - "DoubleUpDownArrow;" => '\u{0021D5}', - "DoubleVerticalBar;" => '\u{002225}', - "DownArrow;" => '\u{002193}', - "DownArrowBar;" => '\u{002913}', - "DownArrowUpArrow;" => '\u{0021F5}', - "DownBreve;" => '\u{000311}', - "DownLeftRightVector;" => '\u{002950}', - "DownLeftTeeVector;" => '\u{00295E}', - "DownLeftVector;" => '\u{0021BD}', - "DownLeftVectorBar;" => '\u{002956}', - "DownRightTeeVector;" => '\u{00295F}', - "DownRightVector;" => '\u{0021C1}', - "DownRightVectorBar;" => '\u{002957}', - "DownTee;" => '\u{0022A4}', - "DownTeeArrow;" => '\u{0021A7}', - "Downarrow;" => '\u{0021D3}', - "Dscr;" => '\u{01D49F}', - "Dstrok;" => '\u{000110}', - "ENG;" => '\u{00014A}', - "ETH;" => '\u{0000D0}', - "Eacute;" => '\u{0000C9}', - "Ecaron;" => '\u{00011A}', - "Ecirc;" => '\u{0000CA}', - "Ecy;" => '\u{00042D}', - "Edot;" => '\u{000116}', - "Efr;" => '\u{01D508}', - "Egrave;" => '\u{0000C8}', - "Element;" => '\u{002208}', - "Emacr;" => '\u{000112}', - "EmptySmallSquare;" => '\u{0025FB}', - "EmptyVerySmallSquare;" => '\u{0025AB}', - "Eogon;" => '\u{000118}', - "Eopf;" => '\u{01D53C}', - "Epsilon;" => '\u{000395}', - "Equal;" => '\u{002A75}', - "EqualTilde;" => '\u{002242}', - "Equilibrium;" => '\u{0021CC}', - "Escr;" => '\u{002130}', - "Esim;" => '\u{002A73}', - "Eta;" => '\u{000397}', - "Euml;" => '\u{0000CB}', - "Exists;" => '\u{002203}', - "ExponentialE;" => '\u{002147}', - "Fcy;" => '\u{000424}', - "Ffr;" => '\u{01D509}', - "FilledSmallSquare;" => '\u{0025FC}', - "FilledVerySmallSquare;" => '\u{0025AA}', - "Fopf;" => '\u{01D53D}', - "ForAll;" => '\u{002200}', - "Fouriertrf;" => '\u{002131}', - "Fscr;" => '\u{002131}', - "GJcy;" => '\u{000403}', - "GT;" => '\u{00003E}', - "Gamma;" => '\u{000393}', - "Gammad;" => '\u{0003DC}', - "Gbreve;" => '\u{00011E}', - "Gcedil;" => '\u{000122}', - "Gcirc;" => '\u{00011C}', - "Gcy;" => '\u{000413}', - "Gdot;" => '\u{000120}', - "Gfr;" => '\u{01D50A}', - "Gg;" => '\u{0022D9}', - "Gopf;" => '\u{01D53E}', - "GreaterEqual;" => '\u{002265}', - "GreaterEqualLess;" => '\u{0022DB}', - "GreaterFullEqual;" => '\u{002267}', - "GreaterGreater;" => '\u{002AA2}', - "GreaterLess;" => '\u{002277}', - "GreaterSlantEqual;" => '\u{002A7E}', - "GreaterTilde;" => '\u{002273}', - "Gscr;" => '\u{01D4A2}', - "Gt;" => '\u{00226B}', - "HARDcy;" => '\u{00042A}', - "Hacek;" => '\u{0002C7}', - "Hat;" => '\u{00005E}', - "Hcirc;" => '\u{000124}', - "Hfr;" => '\u{00210C}', - "HilbertSpace;" => '\u{00210B}', - "Hopf;" => '\u{00210D}', - "HorizontalLine;" => '\u{002500}', - "Hscr;" => '\u{00210B}', - "Hstrok;" => '\u{000126}', - "HumpDownHump;" => '\u{00224E}', - "HumpEqual;" => '\u{00224F}', - "IEcy;" => '\u{000415}', - "IJlig;" => '\u{000132}', - "IOcy;" => '\u{000401}', - "Iacute;" => '\u{0000CD}', - "Icirc;" => '\u{0000CE}', - "Icy;" => '\u{000418}', - "Idot;" => '\u{000130}', - "Ifr;" => '\u{002111}', - "Igrave;" => '\u{0000CC}', - "Im;" => '\u{002111}', - "Imacr;" => '\u{00012A}', - "ImaginaryI;" => '\u{002148}', - "Implies;" => '\u{0021D2}', - "Int;" => '\u{00222C}', - "Integral;" => '\u{00222B}', - "Intersection;" => '\u{0022C2}', - "InvisibleComma;" => '\u{002063}', - "InvisibleTimes;" => '\u{002062}', - "Iogon;" => '\u{00012E}', - "Iopf;" => '\u{01D540}', - "Iota;" => '\u{000399}', - "Iscr;" => '\u{002110}', - "Itilde;" => '\u{000128}', - "Iukcy;" => '\u{000406}', - "Iuml;" => '\u{0000CF}', - "Jcirc;" => '\u{000134}', - "Jcy;" => '\u{000419}', - "Jfr;" => '\u{01D50D}', - "Jopf;" => '\u{01D541}', - "Jscr;" => '\u{01D4A5}', - "Jsercy;" => '\u{000408}', - "Jukcy;" => '\u{000404}', - "KHcy;" => '\u{000425}', - "KJcy;" => '\u{00040C}', - "Kappa;" => '\u{00039A}', - "Kcedil;" => '\u{000136}', - "Kcy;" => '\u{00041A}', - "Kfr;" => '\u{01D50E}', - "Kopf;" => '\u{01D542}', - "Kscr;" => '\u{01D4A6}', - "LJcy;" => '\u{000409}', - "LT;" => '\u{00003C}', - "Lacute;" => '\u{000139}', - "Lambda;" => '\u{00039B}', - "Lang;" => '\u{0027EA}', - "Laplacetrf;" => '\u{002112}', - "Larr;" => '\u{00219E}', - "Lcaron;" => '\u{00013D}', - "Lcedil;" => '\u{00013B}', - "Lcy;" => '\u{00041B}', - "LeftAngleBracket;" => '\u{0027E8}', - "LeftArrow;" => '\u{002190}', - "LeftArrowBar;" => '\u{0021E4}', - "LeftArrowRightArrow;" => '\u{0021C6}', - "LeftCeiling;" => '\u{002308}', - "LeftDoubleBracket;" => '\u{0027E6}', - "LeftDownTeeVector;" => '\u{002961}', - "LeftDownVector;" => '\u{0021C3}', - "LeftDownVectorBar;" => '\u{002959}', - "LeftFloor;" => '\u{00230A}', - "LeftRightArrow;" => '\u{002194}', - "LeftRightVector;" => '\u{00294E}', - "LeftTee;" => '\u{0022A3}', - "LeftTeeArrow;" => '\u{0021A4}', - "LeftTeeVector;" => '\u{00295A}', - "LeftTriangle;" => '\u{0022B2}', - "LeftTriangleBar;" => '\u{0029CF}', - "LeftTriangleEqual;" => '\u{0022B4}', - "LeftUpDownVector;" => '\u{002951}', - "LeftUpTeeVector;" => '\u{002960}', - "LeftUpVector;" => '\u{0021BF}', - "LeftUpVectorBar;" => '\u{002958}', - "LeftVector;" => '\u{0021BC}', - "LeftVectorBar;" => '\u{002952}', - "Leftarrow;" => '\u{0021D0}', - "Leftrightarrow;" => '\u{0021D4}', - "LessEqualGreater;" => '\u{0022DA}', - "LessFullEqual;" => '\u{002266}', - "LessGreater;" => '\u{002276}', - "LessLess;" => '\u{002AA1}', - "LessSlantEqual;" => '\u{002A7D}', - "LessTilde;" => '\u{002272}', - "Lfr;" => '\u{01D50F}', - "Ll;" => '\u{0022D8}', - "Lleftarrow;" => '\u{0021DA}', - "Lmidot;" => '\u{00013F}', - "LongLeftArrow;" => '\u{0027F5}', - "LongLeftRightArrow;" => '\u{0027F7}', - "LongRightArrow;" => '\u{0027F6}', - "Longleftarrow;" => '\u{0027F8}', - "Longleftrightarrow;" => '\u{0027FA}', - "Longrightarrow;" => '\u{0027F9}', - "Lopf;" => '\u{01D543}', - "LowerLeftArrow;" => '\u{002199}', - "LowerRightArrow;" => '\u{002198}', - "Lscr;" => '\u{002112}', - "Lsh;" => '\u{0021B0}', - "Lstrok;" => '\u{000141}', - "Lt;" => '\u{00226A}', - "Map;" => '\u{002905}', - "Mcy;" => '\u{00041C}', - "MediumSpace;" => '\u{00205F}', - "Mellintrf;" => '\u{002133}', - "Mfr;" => '\u{01D510}', - "MinusPlus;" => '\u{002213}', - "Mopf;" => '\u{01D544}', - "Mscr;" => '\u{002133}', - "Mu;" => '\u{00039C}', - "NJcy;" => '\u{00040A}', - "Nacute;" => '\u{000143}', - "Ncaron;" => '\u{000147}', - "Ncedil;" => '\u{000145}', - "Ncy;" => '\u{00041D}', - "NegativeMediumSpace;" => '\u{00200B}', - "NegativeThickSpace;" => '\u{00200B}', - "NegativeThinSpace;" => '\u{00200B}', - "NegativeVeryThinSpace;" => '\u{00200B}', - "NestedGreaterGreater;" => '\u{00226B}', - "NestedLessLess;" => '\u{00226A}', - "NewLine;" => '\u{00000A}', - "Nfr;" => '\u{01D511}', - "NoBreak;" => '\u{002060}', - "NonBreakingSpace;" => '\u{0000A0}', - "Nopf;" => '\u{002115}', - "Not;" => '\u{002AEC}', - "NotCongruent;" => '\u{002262}', - "NotCupCap;" => '\u{00226D}', - "NotDoubleVerticalBar;" => '\u{002226}', - "NotElement;" => '\u{002209}', - "NotEqual;" => '\u{002260}', - "NotExists;" => '\u{002204}', - "NotGreater;" => '\u{00226F}', - "NotGreaterEqual;" => '\u{002271}', - "NotGreaterLess;" => '\u{002279}', - "NotGreaterTilde;" => '\u{002275}', - "NotLeftTriangle;" => '\u{0022EA}', - "NotLeftTriangleEqual;" => '\u{0022EC}', - "NotLess;" => '\u{00226E}', - "NotLessEqual;" => '\u{002270}', - "NotLessGreater;" => '\u{002278}', - "NotLessTilde;" => '\u{002274}', - "NotPrecedes;" => '\u{002280}', - "NotPrecedesSlantEqual;" => '\u{0022E0}', - "NotReverseElement;" => '\u{00220C}', - "NotRightTriangle;" => '\u{0022EB}', - "NotRightTriangleEqual;" => '\u{0022ED}', - "NotSquareSubsetEqual;" => '\u{0022E2}', - "NotSquareSupersetEqual;" => '\u{0022E3}', - "NotSubsetEqual;" => '\u{002288}', - "NotSucceeds;" => '\u{002281}', - "NotSucceedsSlantEqual;" => '\u{0022E1}', - "NotSupersetEqual;" => '\u{002289}', - "NotTilde;" => '\u{002241}', - "NotTildeEqual;" => '\u{002244}', - "NotTildeFullEqual;" => '\u{002247}', - "NotTildeTilde;" => '\u{002249}', - "NotVerticalBar;" => '\u{002224}', - "Nscr;" => '\u{01D4A9}', - "Ntilde;" => '\u{0000D1}', - "Nu;" => '\u{00039D}', - "OElig;" => '\u{000152}', - "Oacute;" => '\u{0000D3}', - "Ocirc;" => '\u{0000D4}', - "Ocy;" => '\u{00041E}', - "Odblac;" => '\u{000150}', - "Ofr;" => '\u{01D512}', - "Ograve;" => '\u{0000D2}', - "Omacr;" => '\u{00014C}', - "Omega;" => '\u{0003A9}', - "Omicron;" => '\u{00039F}', - "Oopf;" => '\u{01D546}', - "OpenCurlyDoubleQuote;" => '\u{00201C}', - "OpenCurlyQuote;" => '\u{002018}', - "Or;" => '\u{002A54}', - "Oscr;" => '\u{01D4AA}', - "Oslash;" => '\u{0000D8}', - "Otilde;" => '\u{0000D5}', - "Otimes;" => '\u{002A37}', - "Ouml;" => '\u{0000D6}', - "OverBar;" => '\u{00203E}', - "OverBrace;" => '\u{0023DE}', - "OverBracket;" => '\u{0023B4}', - "OverParenthesis;" => '\u{0023DC}', - "PartialD;" => '\u{002202}', - "Pcy;" => '\u{00041F}', - "Pfr;" => '\u{01D513}', - "Phi;" => '\u{0003A6}', - "Pi;" => '\u{0003A0}', - "PlusMinus;" => '\u{0000B1}', - "Poincareplane;" => '\u{00210C}', - "Popf;" => '\u{002119}', - "Pr;" => '\u{002ABB}', - "Precedes;" => '\u{00227A}', - "PrecedesEqual;" => '\u{002AAF}', - "PrecedesSlantEqual;" => '\u{00227C}', - "PrecedesTilde;" => '\u{00227E}', - "Prime;" => '\u{002033}', - "Product;" => '\u{00220F}', - "Proportion;" => '\u{002237}', - "Proportional;" => '\u{00221D}', - "Pscr;" => '\u{01D4AB}', - "Psi;" => '\u{0003A8}', - "QUOT;" => '\u{000022}', - "Qfr;" => '\u{01D514}', - "Qopf;" => '\u{00211A}', - "Qscr;" => '\u{01D4AC}', - "RBarr;" => '\u{002910}', - "REG;" => '\u{0000AE}', - "Racute;" => '\u{000154}', - "Rang;" => '\u{0027EB}', - "Rarr;" => '\u{0021A0}', - "Rarrtl;" => '\u{002916}', - "Rcaron;" => '\u{000158}', - "Rcedil;" => '\u{000156}', - "Rcy;" => '\u{000420}', - "Re;" => '\u{00211C}', - "ReverseElement;" => '\u{00220B}', - "ReverseEquilibrium;" => '\u{0021CB}', - "ReverseUpEquilibrium;" => '\u{00296F}', - "Rfr;" => '\u{00211C}', - "Rho;" => '\u{0003A1}', - "RightAngleBracket;" => '\u{0027E9}', - "RightArrow;" => '\u{002192}', - "RightArrowBar;" => '\u{0021E5}', - "RightArrowLeftArrow;" => '\u{0021C4}', - "RightCeiling;" => '\u{002309}', - "RightDoubleBracket;" => '\u{0027E7}', - "RightDownTeeVector;" => '\u{00295D}', - "RightDownVector;" => '\u{0021C2}', - "RightDownVectorBar;" => '\u{002955}', - "RightFloor;" => '\u{00230B}', - "RightTee;" => '\u{0022A2}', - "RightTeeArrow;" => '\u{0021A6}', - "RightTeeVector;" => '\u{00295B}', - "RightTriangle;" => '\u{0022B3}', - "RightTriangleBar;" => '\u{0029D0}', - "RightTriangleEqual;" => '\u{0022B5}', - "RightUpDownVector;" => '\u{00294F}', - "RightUpTeeVector;" => '\u{00295C}', - "RightUpVector;" => '\u{0021BE}', - "RightUpVectorBar;" => '\u{002954}', - "RightVector;" => '\u{0021C0}', - "RightVectorBar;" => '\u{002953}', - "Rightarrow;" => '\u{0021D2}', - "Ropf;" => '\u{00211D}', - "RoundImplies;" => '\u{002970}', - "Rrightarrow;" => '\u{0021DB}', - "Rscr;" => '\u{00211B}', - "Rsh;" => '\u{0021B1}', - "RuleDelayed;" => '\u{0029F4}', - "SHCHcy;" => '\u{000429}', - "SHcy;" => '\u{000428}', - "SOFTcy;" => '\u{00042C}', - "Sacute;" => '\u{00015A}', - "Sc;" => '\u{002ABC}', - "Scaron;" => '\u{000160}', - "Scedil;" => '\u{00015E}', - "Scirc;" => '\u{00015C}', - "Scy;" => '\u{000421}', - "Sfr;" => '\u{01D516}', - "ShortDownArrow;" => '\u{002193}', - "ShortLeftArrow;" => '\u{002190}', - "ShortRightArrow;" => '\u{002192}', - "ShortUpArrow;" => '\u{002191}', - "Sigma;" => '\u{0003A3}', - "SmallCircle;" => '\u{002218}', - "Sopf;" => '\u{01D54A}', - "Sqrt;" => '\u{00221A}', - "Square;" => '\u{0025A1}', - "SquareIntersection;" => '\u{002293}', - "SquareSubset;" => '\u{00228F}', - "SquareSubsetEqual;" => '\u{002291}', - "SquareSuperset;" => '\u{002290}', - "SquareSupersetEqual;" => '\u{002292}', - "SquareUnion;" => '\u{002294}', - "Sscr;" => '\u{01D4AE}', - "Star;" => '\u{0022C6}', - "Sub;" => '\u{0022D0}', - "Subset;" => '\u{0022D0}', - "SubsetEqual;" => '\u{002286}', - "Succeeds;" => '\u{00227B}', - "SucceedsEqual;" => '\u{002AB0}', - "SucceedsSlantEqual;" => '\u{00227D}', - "SucceedsTilde;" => '\u{00227F}', - "SuchThat;" => '\u{00220B}', - "Sum;" => '\u{002211}', - "Sup;" => '\u{0022D1}', - "Superset;" => '\u{002283}', - "SupersetEqual;" => '\u{002287}', - "Supset;" => '\u{0022D1}', - "THORN;" => '\u{0000DE}', - "TRADE;" => '\u{002122}', - "TSHcy;" => '\u{00040B}', - "TScy;" => '\u{000426}', - "Tab;" => '\u{000009}', - "Tau;" => '\u{0003A4}', - "Tcaron;" => '\u{000164}', - "Tcedil;" => '\u{000162}', - "Tcy;" => '\u{000422}', - "Tfr;" => '\u{01D517}', - "Therefore;" => '\u{002234}', - "Theta;" => '\u{000398}', - "ThinSpace;" => '\u{002009}', - "Tilde;" => '\u{00223C}', - "TildeEqual;" => '\u{002243}', - "TildeFullEqual;" => '\u{002245}', - "TildeTilde;" => '\u{002248}', - "Topf;" => '\u{01D54B}', - "TripleDot;" => '\u{0020DB}', - "Tscr;" => '\u{01D4AF}', - "Tstrok;" => '\u{000166}', - "Uacute;" => '\u{0000DA}', - "Uarr;" => '\u{00219F}', - "Uarrocir;" => '\u{002949}', - "Ubrcy;" => '\u{00040E}', - "Ubreve;" => '\u{00016C}', - "Ucirc;" => '\u{0000DB}', - "Ucy;" => '\u{000423}', - "Udblac;" => '\u{000170}', - "Ufr;" => '\u{01D518}', - "Ugrave;" => '\u{0000D9}', - "Umacr;" => '\u{00016A}', - "UnderBar;" => '\u{00005F}', - "UnderBrace;" => '\u{0023DF}', - "UnderBracket;" => '\u{0023B5}', - "UnderParenthesis;" => '\u{0023DD}', - "Union;" => '\u{0022C3}', - "UnionPlus;" => '\u{00228E}', - "Uogon;" => '\u{000172}', - "Uopf;" => '\u{01D54C}', - "UpArrow;" => '\u{002191}', - "UpArrowBar;" => '\u{002912}', - "UpArrowDownArrow;" => '\u{0021C5}', - "UpDownArrow;" => '\u{002195}', - "UpEquilibrium;" => '\u{00296E}', - "UpTee;" => '\u{0022A5}', - "UpTeeArrow;" => '\u{0021A5}', - "Uparrow;" => '\u{0021D1}', - "Updownarrow;" => '\u{0021D5}', - "UpperLeftArrow;" => '\u{002196}', - "UpperRightArrow;" => '\u{002197}', - "Upsi;" => '\u{0003D2}', - "Upsilon;" => '\u{0003A5}', - "Uring;" => '\u{00016E}', - "Uscr;" => '\u{01D4B0}', - "Utilde;" => '\u{000168}', - "Uuml;" => '\u{0000DC}', - "VDash;" => '\u{0022AB}', - "Vbar;" => '\u{002AEB}', - "Vcy;" => '\u{000412}', - "Vdash;" => '\u{0022A9}', - "Vdashl;" => '\u{002AE6}', - "Vee;" => '\u{0022C1}', - "Verbar;" => '\u{002016}', - "Vert;" => '\u{002016}', - "VerticalBar;" => '\u{002223}', - "VerticalLine;" => '\u{00007C}', - "VerticalSeparator;" => '\u{002758}', - "VerticalTilde;" => '\u{002240}', - "VeryThinSpace;" => '\u{00200A}', - "Vfr;" => '\u{01D519}', - "Vopf;" => '\u{01D54D}', - "Vscr;" => '\u{01D4B1}', - "Vvdash;" => '\u{0022AA}', - "Wcirc;" => '\u{000174}', - "Wedge;" => '\u{0022C0}', - "Wfr;" => '\u{01D51A}', - "Wopf;" => '\u{01D54E}', - "Wscr;" => '\u{01D4B2}', - "Xfr;" => '\u{01D51B}', - "Xi;" => '\u{00039E}', - "Xopf;" => '\u{01D54F}', - "Xscr;" => '\u{01D4B3}', - "YAcy;" => '\u{00042F}', - "YIcy;" => '\u{000407}', - "YUcy;" => '\u{00042E}', - "Yacute;" => '\u{0000DD}', - "Ycirc;" => '\u{000176}', - "Ycy;" => '\u{00042B}', - "Yfr;" => '\u{01D51C}', - "Yopf;" => '\u{01D550}', - "Yscr;" => '\u{01D4B4}', - "Yuml;" => '\u{000178}', - "ZHcy;" => '\u{000416}', - "Zacute;" => '\u{000179}', - "Zcaron;" => '\u{00017D}', - "Zcy;" => '\u{000417}', - "Zdot;" => '\u{00017B}', - "ZeroWidthSpace;" => '\u{00200B}', - "Zeta;" => '\u{000396}', - "Zfr;" => '\u{002128}', - "Zopf;" => '\u{002124}', - "Zscr;" => '\u{01D4B5}', - "aacute;" => '\u{0000E1}', - "abreve;" => '\u{000103}', - "ac;" => '\u{00223E}', - "acd;" => '\u{00223F}', - "acirc;" => '\u{0000E2}', - "acute;" => '\u{0000B4}', - "acy;" => '\u{000430}', - "aelig;" => '\u{0000E6}', - "af;" => '\u{002061}', - "afr;" => '\u{01D51E}', - "agrave;" => '\u{0000E0}', - "alefsym;" => '\u{002135}', - "aleph;" => '\u{002135}', - "alpha;" => '\u{0003B1}', - "amacr;" => '\u{000101}', - "amalg;" => '\u{002A3F}', - "amp;" => '\u{000026}', - "and;" => '\u{002227}', - "andand;" => '\u{002A55}', - "andd;" => '\u{002A5C}', - "andslope;" => '\u{002A58}', - "andv;" => '\u{002A5A}', - "ang;" => '\u{002220}', - "ange;" => '\u{0029A4}', - "angle;" => '\u{002220}', - "angmsd;" => '\u{002221}', - "angmsdaa;" => '\u{0029A8}', - "angmsdab;" => '\u{0029A9}', - "angmsdac;" => '\u{0029AA}', - "angmsdad;" => '\u{0029AB}', - "angmsdae;" => '\u{0029AC}', - "angmsdaf;" => '\u{0029AD}', - "angmsdag;" => '\u{0029AE}', - "angmsdah;" => '\u{0029AF}', - "angrt;" => '\u{00221F}', - "angrtvb;" => '\u{0022BE}', - "angrtvbd;" => '\u{00299D}', - "angsph;" => '\u{002222}', - "angst;" => '\u{0000C5}', - "angzarr;" => '\u{00237C}', - "aogon;" => '\u{000105}', - "aopf;" => '\u{01D552}', - "ap;" => '\u{002248}', - "apE;" => '\u{002A70}', - "apacir;" => '\u{002A6F}', - "ape;" => '\u{00224A}', - "apid;" => '\u{00224B}', - "apos;" => '\u{000027}', - "approx;" => '\u{002248}', - "approxeq;" => '\u{00224A}', - "aring;" => '\u{0000E5}', - "ascr;" => '\u{01D4B6}', - "ast;" => '\u{00002A}', - "asymp;" => '\u{002248}', - "asympeq;" => '\u{00224D}', - "atilde;" => '\u{0000E3}', - "auml;" => '\u{0000E4}', - "awconint;" => '\u{002233}', - "awint;" => '\u{002A11}', - "bNot;" => '\u{002AED}', - "backcong;" => '\u{00224C}', - "backepsilon;" => '\u{0003F6}', - "backprime;" => '\u{002035}', - "backsim;" => '\u{00223D}', - "backsimeq;" => '\u{0022CD}', - "barvee;" => '\u{0022BD}', - "barwed;" => '\u{002305}', - "barwedge;" => '\u{002305}', - "bbrk;" => '\u{0023B5}', - "bbrktbrk;" => '\u{0023B6}', - "bcong;" => '\u{00224C}', - "bcy;" => '\u{000431}', - "bdquo;" => '\u{00201E}', - "becaus;" => '\u{002235}', - "because;" => '\u{002235}', - "bemptyv;" => '\u{0029B0}', - "bepsi;" => '\u{0003F6}', - "bernou;" => '\u{00212C}', - "beta;" => '\u{0003B2}', - "beth;" => '\u{002136}', - "between;" => '\u{00226C}', - "bfr;" => '\u{01D51F}', - "bigcap;" => '\u{0022C2}', - "bigcirc;" => '\u{0025EF}', - "bigcup;" => '\u{0022C3}', - "bigodot;" => '\u{002A00}', - "bigoplus;" => '\u{002A01}', - "bigotimes;" => '\u{002A02}', - "bigsqcup;" => '\u{002A06}', - "bigstar;" => '\u{002605}', - "bigtriangledown;" => '\u{0025BD}', - "bigtriangleup;" => '\u{0025B3}', - "biguplus;" => '\u{002A04}', - "bigvee;" => '\u{0022C1}', - "bigwedge;" => '\u{0022C0}', - "bkarow;" => '\u{00290D}', - "blacklozenge;" => '\u{0029EB}', - "blacksquare;" => '\u{0025AA}', - "blacktriangle;" => '\u{0025B4}', - "blacktriangledown;" => '\u{0025BE}', - "blacktriangleleft;" => '\u{0025C2}', - "blacktriangleright;" => '\u{0025B8}', - "blank;" => '\u{002423}', - "blk12;" => '\u{002592}', - "blk14;" => '\u{002591}', - "blk34;" => '\u{002593}', - "block;" => '\u{002588}', - "bnot;" => '\u{002310}', - "bopf;" => '\u{01D553}', - "bot;" => '\u{0022A5}', - "bottom;" => '\u{0022A5}', - "bowtie;" => '\u{0022C8}', - "boxDL;" => '\u{002557}', - "boxDR;" => '\u{002554}', - "boxDl;" => '\u{002556}', - "boxDr;" => '\u{002553}', - "boxH;" => '\u{002550}', - "boxHD;" => '\u{002566}', - "boxHU;" => '\u{002569}', - "boxHd;" => '\u{002564}', - "boxHu;" => '\u{002567}', - "boxUL;" => '\u{00255D}', - "boxUR;" => '\u{00255A}', - "boxUl;" => '\u{00255C}', - "boxUr;" => '\u{002559}', - "boxV;" => '\u{002551}', - "boxVH;" => '\u{00256C}', - "boxVL;" => '\u{002563}', - "boxVR;" => '\u{002560}', - "boxVh;" => '\u{00256B}', - "boxVl;" => '\u{002562}', - "boxVr;" => '\u{00255F}', - "boxbox;" => '\u{0029C9}', - "boxdL;" => '\u{002555}', - "boxdR;" => '\u{002552}', - "boxdl;" => '\u{002510}', - "boxdr;" => '\u{00250C}', - "boxh;" => '\u{002500}', - "boxhD;" => '\u{002565}', - "boxhU;" => '\u{002568}', - "boxhd;" => '\u{00252C}', - "boxhu;" => '\u{002534}', - "boxminus;" => '\u{00229F}', - "boxplus;" => '\u{00229E}', - "boxtimes;" => '\u{0022A0}', - "boxuL;" => '\u{00255B}', - "boxuR;" => '\u{002558}', - "boxul;" => '\u{002518}', - "boxur;" => '\u{002514}', - "boxv;" => '\u{002502}', - "boxvH;" => '\u{00256A}', - "boxvL;" => '\u{002561}', - "boxvR;" => '\u{00255E}', - "boxvh;" => '\u{00253C}', - "boxvl;" => '\u{002524}', - "boxvr;" => '\u{00251C}', - "bprime;" => '\u{002035}', - "breve;" => '\u{0002D8}', - "brvbar;" => '\u{0000A6}', - "bscr;" => '\u{01D4B7}', - "bsemi;" => '\u{00204F}', - "bsim;" => '\u{00223D}', - "bsime;" => '\u{0022CD}', - "bsol;" => '\u{00005C}', - "bsolb;" => '\u{0029C5}', - "bsolhsub;" => '\u{0027C8}', - "bull;" => '\u{002022}', - "bullet;" => '\u{002022}', - "bump;" => '\u{00224E}', - "bumpE;" => '\u{002AAE}', - "bumpe;" => '\u{00224F}', - "bumpeq;" => '\u{00224F}', - "cacute;" => '\u{000107}', - "cap;" => '\u{002229}', - "capand;" => '\u{002A44}', - "capbrcup;" => '\u{002A49}', - "capcap;" => '\u{002A4B}', - "capcup;" => '\u{002A47}', - "capdot;" => '\u{002A40}', - "caret;" => '\u{002041}', - "caron;" => '\u{0002C7}', - "ccaps;" => '\u{002A4D}', - "ccaron;" => '\u{00010D}', - "ccedil;" => '\u{0000E7}', - "ccirc;" => '\u{000109}', - "ccups;" => '\u{002A4C}', - "ccupssm;" => '\u{002A50}', - "cdot;" => '\u{00010B}', - "cedil;" => '\u{0000B8}', - "cemptyv;" => '\u{0029B2}', - "cent;" => '\u{0000A2}', - "centerdot;" => '\u{0000B7}', - "cfr;" => '\u{01D520}', - "chcy;" => '\u{000447}', - "check;" => '\u{002713}', - "checkmark;" => '\u{002713}', - "chi;" => '\u{0003C7}', - "cir;" => '\u{0025CB}', - "cirE;" => '\u{0029C3}', - "circ;" => '\u{0002C6}', - "circeq;" => '\u{002257}', - "circlearrowleft;" => '\u{0021BA}', - "circlearrowright;" => '\u{0021BB}', - "circledR;" => '\u{0000AE}', - "circledS;" => '\u{0024C8}', - "circledast;" => '\u{00229B}', - "circledcirc;" => '\u{00229A}', - "circleddash;" => '\u{00229D}', - "cire;" => '\u{002257}', - "cirfnint;" => '\u{002A10}', - "cirmid;" => '\u{002AEF}', - "cirscir;" => '\u{0029C2}', - "clubs;" => '\u{002663}', - "clubsuit;" => '\u{002663}', - "colon;" => '\u{00003A}', - "colone;" => '\u{002254}', - "coloneq;" => '\u{002254}', - "comma;" => '\u{00002C}', - "commat;" => '\u{000040}', - "comp;" => '\u{002201}', - "compfn;" => '\u{002218}', - "complement;" => '\u{002201}', - "complexes;" => '\u{002102}', - "cong;" => '\u{002245}', - "congdot;" => '\u{002A6D}', - "conint;" => '\u{00222E}', - "copf;" => '\u{01D554}', - "coprod;" => '\u{002210}', - "copy;" => '\u{0000A9}', - "copysr;" => '\u{002117}', - "crarr;" => '\u{0021B5}', - "cross;" => '\u{002717}', - "cscr;" => '\u{01D4B8}', - "csub;" => '\u{002ACF}', - "csube;" => '\u{002AD1}', - "csup;" => '\u{002AD0}', - "csupe;" => '\u{002AD2}', - "ctdot;" => '\u{0022EF}', - "cudarrl;" => '\u{002938}', - "cudarrr;" => '\u{002935}', - "cuepr;" => '\u{0022DE}', - "cuesc;" => '\u{0022DF}', - "cularr;" => '\u{0021B6}', - "cularrp;" => '\u{00293D}', - "cup;" => '\u{00222A}', - "cupbrcap;" => '\u{002A48}', - "cupcap;" => '\u{002A46}', - "cupcup;" => '\u{002A4A}', - "cupdot;" => '\u{00228D}', - "cupor;" => '\u{002A45}', - "curarr;" => '\u{0021B7}', - "curarrm;" => '\u{00293C}', - "curlyeqprec;" => '\u{0022DE}', - "curlyeqsucc;" => '\u{0022DF}', - "curlyvee;" => '\u{0022CE}', - "curlywedge;" => '\u{0022CF}', - "curren;" => '\u{0000A4}', - "curvearrowleft;" => '\u{0021B6}', - "curvearrowright;" => '\u{0021B7}', - "cuvee;" => '\u{0022CE}', - "cuwed;" => '\u{0022CF}', - "cwconint;" => '\u{002232}', - "cwint;" => '\u{002231}', - "cylcty;" => '\u{00232D}', - "dArr;" => '\u{0021D3}', - "dHar;" => '\u{002965}', - "dagger;" => '\u{002020}', - "daleth;" => '\u{002138}', - "darr;" => '\u{002193}', - "dash;" => '\u{002010}', - "dashv;" => '\u{0022A3}', - "dbkarow;" => '\u{00290F}', - "dblac;" => '\u{0002DD}', - "dcaron;" => '\u{00010F}', - "dcy;" => '\u{000434}', - "dd;" => '\u{002146}', - "ddagger;" => '\u{002021}', - "ddarr;" => '\u{0021CA}', - "ddotseq;" => '\u{002A77}', - "deg;" => '\u{0000B0}', - "delta;" => '\u{0003B4}', - "demptyv;" => '\u{0029B1}', - "dfisht;" => '\u{00297F}', - "dfr;" => '\u{01D521}', - "dharl;" => '\u{0021C3}', - "dharr;" => '\u{0021C2}', - "diam;" => '\u{0022C4}', - "diamond;" => '\u{0022C4}', - "diamondsuit;" => '\u{002666}', - "diams;" => '\u{002666}', - "die;" => '\u{0000A8}', - "digamma;" => '\u{0003DD}', - "disin;" => '\u{0022F2}', - "div;" => '\u{0000F7}', - "divide;" => '\u{0000F7}', - "divideontimes;" => '\u{0022C7}', - "divonx;" => '\u{0022C7}', - "djcy;" => '\u{000452}', - "dlcorn;" => '\u{00231E}', - "dlcrop;" => '\u{00230D}', - "dollar;" => '\u{000024}', - "dopf;" => '\u{01D555}', - "dot;" => '\u{0002D9}', - "doteq;" => '\u{002250}', - "doteqdot;" => '\u{002251}', - "dotminus;" => '\u{002238}', - "dotplus;" => '\u{002214}', - "dotsquare;" => '\u{0022A1}', - "doublebarwedge;" => '\u{002306}', - "downarrow;" => '\u{002193}', - "downdownarrows;" => '\u{0021CA}', - "downharpoonleft;" => '\u{0021C3}', - "downharpoonright;" => '\u{0021C2}', - "drbkarow;" => '\u{002910}', - "drcorn;" => '\u{00231F}', - "drcrop;" => '\u{00230C}', - "dscr;" => '\u{01D4B9}', - "dscy;" => '\u{000455}', - "dsol;" => '\u{0029F6}', - "dstrok;" => '\u{000111}', - "dtdot;" => '\u{0022F1}', - "dtri;" => '\u{0025BF}', - "dtrif;" => '\u{0025BE}', - "duarr;" => '\u{0021F5}', - "duhar;" => '\u{00296F}', - "dwangle;" => '\u{0029A6}', - "dzcy;" => '\u{00045F}', - "dzigrarr;" => '\u{0027FF}', - "eDDot;" => '\u{002A77}', - "eDot;" => '\u{002251}', - "eacute;" => '\u{0000E9}', - "easter;" => '\u{002A6E}', - "ecaron;" => '\u{00011B}', - "ecir;" => '\u{002256}', - "ecirc;" => '\u{0000EA}', - "ecolon;" => '\u{002255}', - "ecy;" => '\u{00044D}', - "edot;" => '\u{000117}', - "ee;" => '\u{002147}', - "efDot;" => '\u{002252}', - "efr;" => '\u{01D522}', - "eg;" => '\u{002A9A}', - "egrave;" => '\u{0000E8}', - "egs;" => '\u{002A96}', - "egsdot;" => '\u{002A98}', - "el;" => '\u{002A99}', - "elinters;" => '\u{0023E7}', - "ell;" => '\u{002113}', - "els;" => '\u{002A95}', - "elsdot;" => '\u{002A97}', - "emacr;" => '\u{000113}', - "empty;" => '\u{002205}', - "emptyset;" => '\u{002205}', - "emptyv;" => '\u{002205}', - "emsp;" => '\u{002003}', - "emsp13;" => '\u{002004}', - "emsp14;" => '\u{002005}', - "eng;" => '\u{00014B}', - "ensp;" => '\u{002002}', - "eogon;" => '\u{000119}', - "eopf;" => '\u{01D556}', - "epar;" => '\u{0022D5}', - "eparsl;" => '\u{0029E3}', - "eplus;" => '\u{002A71}', - "epsi;" => '\u{0003B5}', - "epsilon;" => '\u{0003B5}', - "epsiv;" => '\u{0003F5}', - "eqcirc;" => '\u{002256}', - "eqcolon;" => '\u{002255}', - "eqsim;" => '\u{002242}', - "eqslantgtr;" => '\u{002A96}', - "eqslantless;" => '\u{002A95}', - "equals;" => '\u{00003D}', - "equest;" => '\u{00225F}', - "equiv;" => '\u{002261}', - "equivDD;" => '\u{002A78}', - "eqvparsl;" => '\u{0029E5}', - "erDot;" => '\u{002253}', - "erarr;" => '\u{002971}', - "escr;" => '\u{00212F}', - "esdot;" => '\u{002250}', - "esim;" => '\u{002242}', - "eta;" => '\u{0003B7}', - "eth;" => '\u{0000F0}', - "euml;" => '\u{0000EB}', - "euro;" => '\u{0020AC}', - "excl;" => '\u{000021}', - "exist;" => '\u{002203}', - "expectation;" => '\u{002130}', - "exponentiale;" => '\u{002147}', - "fallingdotseq;" => '\u{002252}', - "fcy;" => '\u{000444}', - "female;" => '\u{002640}', - "ffilig;" => '\u{00FB03}', - "fflig;" => '\u{00FB00}', - "ffllig;" => '\u{00FB04}', - "ffr;" => '\u{01D523}', - "filig;" => '\u{00FB01}', - "flat;" => '\u{00266D}', - "fllig;" => '\u{00FB02}', - "fltns;" => '\u{0025B1}', - "fnof;" => '\u{000192}', - "fopf;" => '\u{01D557}', - "forall;" => '\u{002200}', - "fork;" => '\u{0022D4}', - "forkv;" => '\u{002AD9}', - "fpartint;" => '\u{002A0D}', - "frac12;" => '\u{0000BD}', - "frac13;" => '\u{002153}', - "frac14;" => '\u{0000BC}', - "frac15;" => '\u{002155}', - "frac16;" => '\u{002159}', - "frac18;" => '\u{00215B}', - "frac23;" => '\u{002154}', - "frac25;" => '\u{002156}', - "frac34;" => '\u{0000BE}', - "frac35;" => '\u{002157}', - "frac38;" => '\u{00215C}', - "frac45;" => '\u{002158}', - "frac56;" => '\u{00215A}', - "frac58;" => '\u{00215D}', - "frac78;" => '\u{00215E}', - "frasl;" => '\u{002044}', - "frown;" => '\u{002322}', - "fscr;" => '\u{01D4BB}', - "gE;" => '\u{002267}', - "gEl;" => '\u{002A8C}', - "gacute;" => '\u{0001F5}', - "gamma;" => '\u{0003B3}', - "gammad;" => '\u{0003DD}', - "gap;" => '\u{002A86}', - "gbreve;" => '\u{00011F}', - "gcirc;" => '\u{00011D}', - "gcy;" => '\u{000433}', - "gdot;" => '\u{000121}', - "ge;" => '\u{002265}', - "gel;" => '\u{0022DB}', - "geq;" => '\u{002265}', - "geqq;" => '\u{002267}', - "geqslant;" => '\u{002A7E}', - "ges;" => '\u{002A7E}', - "gescc;" => '\u{002AA9}', - "gesdot;" => '\u{002A80}', - "gesdoto;" => '\u{002A82}', - "gesdotol;" => '\u{002A84}', - "gesles;" => '\u{002A94}', - "gfr;" => '\u{01D524}', - "gg;" => '\u{00226B}', - "ggg;" => '\u{0022D9}', - "gimel;" => '\u{002137}', - "gjcy;" => '\u{000453}', - "gl;" => '\u{002277}', - "glE;" => '\u{002A92}', - "gla;" => '\u{002AA5}', - "glj;" => '\u{002AA4}', - "gnE;" => '\u{002269}', - "gnap;" => '\u{002A8A}', - "gnapprox;" => '\u{002A8A}', - "gne;" => '\u{002A88}', - "gneq;" => '\u{002A88}', - "gneqq;" => '\u{002269}', - "gnsim;" => '\u{0022E7}', - "gopf;" => '\u{01D558}', - "grave;" => '\u{000060}', - "gscr;" => '\u{00210A}', - "gsim;" => '\u{002273}', - "gsime;" => '\u{002A8E}', - "gsiml;" => '\u{002A90}', - "gt;" => '\u{00003E}', - "gtcc;" => '\u{002AA7}', - "gtcir;" => '\u{002A7A}', - "gtdot;" => '\u{0022D7}', - "gtlPar;" => '\u{002995}', - "gtquest;" => '\u{002A7C}', - "gtrapprox;" => '\u{002A86}', - "gtrarr;" => '\u{002978}', - "gtrdot;" => '\u{0022D7}', - "gtreqless;" => '\u{0022DB}', - "gtreqqless;" => '\u{002A8C}', - "gtrless;" => '\u{002277}', - "gtrsim;" => '\u{002273}', - "hArr;" => '\u{0021D4}', - "hairsp;" => '\u{00200A}', - "half;" => '\u{0000BD}', - "hamilt;" => '\u{00210B}', - "hardcy;" => '\u{00044A}', - "harr;" => '\u{002194}', - "harrcir;" => '\u{002948}', - "harrw;" => '\u{0021AD}', - "hbar;" => '\u{00210F}', - "hcirc;" => '\u{000125}', - "hearts;" => '\u{002665}', - "heartsuit;" => '\u{002665}', - "hellip;" => '\u{002026}', - "hercon;" => '\u{0022B9}', - "hfr;" => '\u{01D525}', - "hksearow;" => '\u{002925}', - "hkswarow;" => '\u{002926}', - "hoarr;" => '\u{0021FF}', - "homtht;" => '\u{00223B}', - "hookleftarrow;" => '\u{0021A9}', - "hookrightarrow;" => '\u{0021AA}', - "hopf;" => '\u{01D559}', - "horbar;" => '\u{002015}', - "hscr;" => '\u{01D4BD}', - "hslash;" => '\u{00210F}', - "hstrok;" => '\u{000127}', - "hybull;" => '\u{002043}', - "hyphen;" => '\u{002010}', - "iacute;" => '\u{0000ED}', - "ic;" => '\u{002063}', - "icirc;" => '\u{0000EE}', - "icy;" => '\u{000438}', - "iecy;" => '\u{000435}', - "iexcl;" => '\u{0000A1}', - "iff;" => '\u{0021D4}', - "ifr;" => '\u{01D526}', - "igrave;" => '\u{0000EC}', - "ii;" => '\u{002148}', - "iiiint;" => '\u{002A0C}', - "iiint;" => '\u{00222D}', - "iinfin;" => '\u{0029DC}', - "iiota;" => '\u{002129}', - "ijlig;" => '\u{000133}', - "imacr;" => '\u{00012B}', - "image;" => '\u{002111}', - "imagline;" => '\u{002110}', - "imagpart;" => '\u{002111}', - "imath;" => '\u{000131}', - "imof;" => '\u{0022B7}', - "imped;" => '\u{0001B5}', - "in;" => '\u{002208}', - "incare;" => '\u{002105}', - "infin;" => '\u{00221E}', - "infintie;" => '\u{0029DD}', - "inodot;" => '\u{000131}', - "int;" => '\u{00222B}', - "intcal;" => '\u{0022BA}', - "integers;" => '\u{002124}', - "intercal;" => '\u{0022BA}', - "intlarhk;" => '\u{002A17}', - "intprod;" => '\u{002A3C}', - "iocy;" => '\u{000451}', - "iogon;" => '\u{00012F}', - "iopf;" => '\u{01D55A}', - "iota;" => '\u{0003B9}', - "iprod;" => '\u{002A3C}', - "iquest;" => '\u{0000BF}', - "iscr;" => '\u{01D4BE}', - "isin;" => '\u{002208}', - "isinE;" => '\u{0022F9}', - "isindot;" => '\u{0022F5}', - "isins;" => '\u{0022F4}', - "isinsv;" => '\u{0022F3}', - "isinv;" => '\u{002208}', - "it;" => '\u{002062}', - "itilde;" => '\u{000129}', - "iukcy;" => '\u{000456}', - "iuml;" => '\u{0000EF}', - "jcirc;" => '\u{000135}', - "jcy;" => '\u{000439}', - "jfr;" => '\u{01D527}', - "jmath;" => '\u{000237}', - "jopf;" => '\u{01D55B}', - "jscr;" => '\u{01D4BF}', - "jsercy;" => '\u{000458}', - "jukcy;" => '\u{000454}', - "kappa;" => '\u{0003BA}', - "kappav;" => '\u{0003F0}', - "kcedil;" => '\u{000137}', - "kcy;" => '\u{00043A}', - "kfr;" => '\u{01D528}', - "kgreen;" => '\u{000138}', - "khcy;" => '\u{000445}', - "kjcy;" => '\u{00045C}', - "kopf;" => '\u{01D55C}', - "kscr;" => '\u{01D4C0}', - "lAarr;" => '\u{0021DA}', - "lArr;" => '\u{0021D0}', - "lAtail;" => '\u{00291B}', - "lBarr;" => '\u{00290E}', - "lE;" => '\u{002266}', - "lEg;" => '\u{002A8B}', - "lHar;" => '\u{002962}', - "lacute;" => '\u{00013A}', - "laemptyv;" => '\u{0029B4}', - "lagran;" => '\u{002112}', - "lambda;" => '\u{0003BB}', - "lang;" => '\u{0027E8}', - "langd;" => '\u{002991}', - "langle;" => '\u{0027E8}', - "lap;" => '\u{002A85}', - "laquo;" => '\u{0000AB}', - "larr;" => '\u{002190}', - "larrb;" => '\u{0021E4}', - "larrbfs;" => '\u{00291F}', - "larrfs;" => '\u{00291D}', - "larrhk;" => '\u{0021A9}', - "larrlp;" => '\u{0021AB}', - "larrpl;" => '\u{002939}', - "larrsim;" => '\u{002973}', - "larrtl;" => '\u{0021A2}', - "lat;" => '\u{002AAB}', - "latail;" => '\u{002919}', - "late;" => '\u{002AAD}', - "lbarr;" => '\u{00290C}', - "lbbrk;" => '\u{002772}', - "lbrace;" => '\u{00007B}', - "lbrack;" => '\u{00005B}', - "lbrke;" => '\u{00298B}', - "lbrksld;" => '\u{00298F}', - "lbrkslu;" => '\u{00298D}', - "lcaron;" => '\u{00013E}', - "lcedil;" => '\u{00013C}', - "lceil;" => '\u{002308}', - "lcub;" => '\u{00007B}', - "lcy;" => '\u{00043B}', - "ldca;" => '\u{002936}', - "ldquo;" => '\u{00201C}', - "ldquor;" => '\u{00201E}', - "ldrdhar;" => '\u{002967}', - "ldrushar;" => '\u{00294B}', - "ldsh;" => '\u{0021B2}', - "le;" => '\u{002264}', - "leftarrow;" => '\u{002190}', - "leftarrowtail;" => '\u{0021A2}', - "leftharpoondown;" => '\u{0021BD}', - "leftharpoonup;" => '\u{0021BC}', - "leftleftarrows;" => '\u{0021C7}', - "leftrightarrow;" => '\u{002194}', - "leftrightarrows;" => '\u{0021C6}', - "leftrightharpoons;" => '\u{0021CB}', - "leftrightsquigarrow;" => '\u{0021AD}', - "leftthreetimes;" => '\u{0022CB}', - "leg;" => '\u{0022DA}', - "leq;" => '\u{002264}', - "leqq;" => '\u{002266}', - "leqslant;" => '\u{002A7D}', - "les;" => '\u{002A7D}', - "lescc;" => '\u{002AA8}', - "lesdot;" => '\u{002A7F}', - "lesdoto;" => '\u{002A81}', - "lesdotor;" => '\u{002A83}', - "lesges;" => '\u{002A93}', - "lessapprox;" => '\u{002A85}', - "lessdot;" => '\u{0022D6}', - "lesseqgtr;" => '\u{0022DA}', - "lesseqqgtr;" => '\u{002A8B}', - "lessgtr;" => '\u{002276}', - "lesssim;" => '\u{002272}', - "lfisht;" => '\u{00297C}', - "lfloor;" => '\u{00230A}', - "lfr;" => '\u{01D529}', - "lg;" => '\u{002276}', - "lgE;" => '\u{002A91}', - "lhard;" => '\u{0021BD}', - "lharu;" => '\u{0021BC}', - "lharul;" => '\u{00296A}', - "lhblk;" => '\u{002584}', - "ljcy;" => '\u{000459}', - "ll;" => '\u{00226A}', - "llarr;" => '\u{0021C7}', - "llcorner;" => '\u{00231E}', - "llhard;" => '\u{00296B}', - "lltri;" => '\u{0025FA}', - "lmidot;" => '\u{000140}', - "lmoust;" => '\u{0023B0}', - "lmoustache;" => '\u{0023B0}', - "lnE;" => '\u{002268}', - "lnap;" => '\u{002A89}', - "lnapprox;" => '\u{002A89}', - "lne;" => '\u{002A87}', - "lneq;" => '\u{002A87}', - "lneqq;" => '\u{002268}', - "lnsim;" => '\u{0022E6}', - "loang;" => '\u{0027EC}', - "loarr;" => '\u{0021FD}', - "lobrk;" => '\u{0027E6}', - "longleftarrow;" => '\u{0027F5}', - "longleftrightarrow;" => '\u{0027F7}', - "longmapsto;" => '\u{0027FC}', - "longrightarrow;" => '\u{0027F6}', - "looparrowleft;" => '\u{0021AB}', - "looparrowright;" => '\u{0021AC}', - "lopar;" => '\u{002985}', - "lopf;" => '\u{01D55D}', - "loplus;" => '\u{002A2D}', - "lotimes;" => '\u{002A34}', - "lowast;" => '\u{002217}', - "lowbar;" => '\u{00005F}', - "loz;" => '\u{0025CA}', - "lozenge;" => '\u{0025CA}', - "lozf;" => '\u{0029EB}', - "lpar;" => '\u{000028}', - "lparlt;" => '\u{002993}', - "lrarr;" => '\u{0021C6}', - "lrcorner;" => '\u{00231F}', - "lrhar;" => '\u{0021CB}', - "lrhard;" => '\u{00296D}', - "lrm;" => '\u{00200E}', - "lrtri;" => '\u{0022BF}', - "lsaquo;" => '\u{002039}', - "lscr;" => '\u{01D4C1}', - "lsh;" => '\u{0021B0}', - "lsim;" => '\u{002272}', - "lsime;" => '\u{002A8D}', - "lsimg;" => '\u{002A8F}', - "lsqb;" => '\u{00005B}', - "lsquo;" => '\u{002018}', - "lsquor;" => '\u{00201A}', - "lstrok;" => '\u{000142}', - "lt;" => '\u{00003C}', - "ltcc;" => '\u{002AA6}', - "ltcir;" => '\u{002A79}', - "ltdot;" => '\u{0022D6}', - "lthree;" => '\u{0022CB}', - "ltimes;" => '\u{0022C9}', - "ltlarr;" => '\u{002976}', - "ltquest;" => '\u{002A7B}', - "ltrPar;" => '\u{002996}', - "ltri;" => '\u{0025C3}', - "ltrie;" => '\u{0022B4}', - "ltrif;" => '\u{0025C2}', - "lurdshar;" => '\u{00294A}', - "luruhar;" => '\u{002966}', - "mDDot;" => '\u{00223A}', - "macr;" => '\u{0000AF}', - "male;" => '\u{002642}', - "malt;" => '\u{002720}', - "maltese;" => '\u{002720}', - "map;" => '\u{0021A6}', - "mapsto;" => '\u{0021A6}', - "mapstodown;" => '\u{0021A7}', - "mapstoleft;" => '\u{0021A4}', - "mapstoup;" => '\u{0021A5}', - "marker;" => '\u{0025AE}', - "mcomma;" => '\u{002A29}', - "mcy;" => '\u{00043C}', - "mdash;" => '\u{002014}', - "measuredangle;" => '\u{002221}', - "mfr;" => '\u{01D52A}', - "mho;" => '\u{002127}', - "micro;" => '\u{0000B5}', - "mid;" => '\u{002223}', - "midast;" => '\u{00002A}', - "midcir;" => '\u{002AF0}', - "middot;" => '\u{0000B7}', - "minus;" => '\u{002212}', - "minusb;" => '\u{00229F}', - "minusd;" => '\u{002238}', - "minusdu;" => '\u{002A2A}', - "mlcp;" => '\u{002ADB}', - "mldr;" => '\u{002026}', - "mnplus;" => '\u{002213}', - "models;" => '\u{0022A7}', - "mopf;" => '\u{01D55E}', - "mp;" => '\u{002213}', - "mscr;" => '\u{01D4C2}', - "mstpos;" => '\u{00223E}', - "mu;" => '\u{0003BC}', - "multimap;" => '\u{0022B8}', - "mumap;" => '\u{0022B8}', - "nLeftarrow;" => '\u{0021CD}', - "nLeftrightarrow;" => '\u{0021CE}', - "nRightarrow;" => '\u{0021CF}', - "nVDash;" => '\u{0022AF}', - "nVdash;" => '\u{0022AE}', - "nabla;" => '\u{002207}', - "nacute;" => '\u{000144}', - "nap;" => '\u{002249}', - "napos;" => '\u{000149}', - "napprox;" => '\u{002249}', - "natur;" => '\u{00266E}', - "natural;" => '\u{00266E}', - "naturals;" => '\u{002115}', - "nbsp;" => '\u{0000A0}', - "ncap;" => '\u{002A43}', - "ncaron;" => '\u{000148}', - "ncedil;" => '\u{000146}', - "ncong;" => '\u{002247}', - "ncup;" => '\u{002A42}', - "ncy;" => '\u{00043D}', - "ndash;" => '\u{002013}', - "ne;" => '\u{002260}', - "neArr;" => '\u{0021D7}', - "nearhk;" => '\u{002924}', - "nearr;" => '\u{002197}', - "nearrow;" => '\u{002197}', - "nequiv;" => '\u{002262}', - "nesear;" => '\u{002928}', - "nexist;" => '\u{002204}', - "nexists;" => '\u{002204}', - "nfr;" => '\u{01D52B}', - "nge;" => '\u{002271}', - "ngeq;" => '\u{002271}', - "ngsim;" => '\u{002275}', - "ngt;" => '\u{00226F}', - "ngtr;" => '\u{00226F}', - "nhArr;" => '\u{0021CE}', - "nharr;" => '\u{0021AE}', - "nhpar;" => '\u{002AF2}', - "ni;" => '\u{00220B}', - "nis;" => '\u{0022FC}', - "nisd;" => '\u{0022FA}', - "niv;" => '\u{00220B}', - "njcy;" => '\u{00045A}', - "nlArr;" => '\u{0021CD}', - "nlarr;" => '\u{00219A}', - "nldr;" => '\u{002025}', - "nle;" => '\u{002270}', - "nleftarrow;" => '\u{00219A}', - "nleftrightarrow;" => '\u{0021AE}', - "nleq;" => '\u{002270}', - "nless;" => '\u{00226E}', - "nlsim;" => '\u{002274}', - "nlt;" => '\u{00226E}', - "nltri;" => '\u{0022EA}', - "nltrie;" => '\u{0022EC}', - "nmid;" => '\u{002224}', - "nopf;" => '\u{01D55F}', - "not;" => '\u{0000AC}', - "notin;" => '\u{002209}', - "notinva;" => '\u{002209}', - "notinvb;" => '\u{0022F7}', - "notinvc;" => '\u{0022F6}', - "notni;" => '\u{00220C}', - "notniva;" => '\u{00220C}', - "notnivb;" => '\u{0022FE}', - "notnivc;" => '\u{0022FD}', - "npar;" => '\u{002226}', - "nparallel;" => '\u{002226}', - "npolint;" => '\u{002A14}', - "npr;" => '\u{002280}', - "nprcue;" => '\u{0022E0}', - "nprec;" => '\u{002280}', - "nrArr;" => '\u{0021CF}', - "nrarr;" => '\u{00219B}', - "nrightarrow;" => '\u{00219B}', - "nrtri;" => '\u{0022EB}', - "nrtrie;" => '\u{0022ED}', - "nsc;" => '\u{002281}', - "nsccue;" => '\u{0022E1}', - "nscr;" => '\u{01D4C3}', - "nshortmid;" => '\u{002224}', - "nshortparallel;" => '\u{002226}', - "nsim;" => '\u{002241}', - "nsime;" => '\u{002244}', - "nsimeq;" => '\u{002244}', - "nsmid;" => '\u{002224}', - "nspar;" => '\u{002226}', - "nsqsube;" => '\u{0022E2}', - "nsqsupe;" => '\u{0022E3}', - "nsub;" => '\u{002284}', - "nsube;" => '\u{002288}', - "nsubseteq;" => '\u{002288}', - "nsucc;" => '\u{002281}', - "nsup;" => '\u{002285}', - "nsupe;" => '\u{002289}', - "nsupseteq;" => '\u{002289}', - "ntgl;" => '\u{002279}', - "ntilde;" => '\u{0000F1}', - "ntlg;" => '\u{002278}', - "ntriangleleft;" => '\u{0022EA}', - "ntrianglelefteq;" => '\u{0022EC}', - "ntriangleright;" => '\u{0022EB}', - "ntrianglerighteq;" => '\u{0022ED}', - "nu;" => '\u{0003BD}', - "num;" => '\u{000023}', - "numero;" => '\u{002116}', - "numsp;" => '\u{002007}', - "nvDash;" => '\u{0022AD}', - "nvHarr;" => '\u{002904}', - "nvdash;" => '\u{0022AC}', - "nvinfin;" => '\u{0029DE}', - "nvlArr;" => '\u{002902}', - "nvrArr;" => '\u{002903}', - "nwArr;" => '\u{0021D6}', - "nwarhk;" => '\u{002923}', - "nwarr;" => '\u{002196}', - "nwarrow;" => '\u{002196}', - "nwnear;" => '\u{002927}', - "oS;" => '\u{0024C8}', - "oacute;" => '\u{0000F3}', - "oast;" => '\u{00229B}', - "ocir;" => '\u{00229A}', - "ocirc;" => '\u{0000F4}', - "ocy;" => '\u{00043E}', - "odash;" => '\u{00229D}', - "odblac;" => '\u{000151}', - "odiv;" => '\u{002A38}', - "odot;" => '\u{002299}', - "odsold;" => '\u{0029BC}', - "oelig;" => '\u{000153}', - "ofcir;" => '\u{0029BF}', - "ofr;" => '\u{01D52C}', - "ogon;" => '\u{0002DB}', - "ograve;" => '\u{0000F2}', - "ogt;" => '\u{0029C1}', - "ohbar;" => '\u{0029B5}', - "ohm;" => '\u{0003A9}', - "oint;" => '\u{00222E}', - "olarr;" => '\u{0021BA}', - "olcir;" => '\u{0029BE}', - "olcross;" => '\u{0029BB}', - "oline;" => '\u{00203E}', - "olt;" => '\u{0029C0}', - "omacr;" => '\u{00014D}', - "omega;" => '\u{0003C9}', - "omicron;" => '\u{0003BF}', - "omid;" => '\u{0029B6}', - "ominus;" => '\u{002296}', - "oopf;" => '\u{01D560}', - "opar;" => '\u{0029B7}', - "operp;" => '\u{0029B9}', - "oplus;" => '\u{002295}', - "or;" => '\u{002228}', - "orarr;" => '\u{0021BB}', - "ord;" => '\u{002A5D}', - "order;" => '\u{002134}', - "orderof;" => '\u{002134}', - "ordf;" => '\u{0000AA}', - "ordm;" => '\u{0000BA}', - "origof;" => '\u{0022B6}', - "oror;" => '\u{002A56}', - "orslope;" => '\u{002A57}', - "orv;" => '\u{002A5B}', - "oscr;" => '\u{002134}', - "oslash;" => '\u{0000F8}', - "osol;" => '\u{002298}', - "otilde;" => '\u{0000F5}', - "otimes;" => '\u{002297}', - "otimesas;" => '\u{002A36}', - "ouml;" => '\u{0000F6}', - "ovbar;" => '\u{00233D}', - "par;" => '\u{002225}', - "para;" => '\u{0000B6}', - "parallel;" => '\u{002225}', - "parsim;" => '\u{002AF3}', - "parsl;" => '\u{002AFD}', - "part;" => '\u{002202}', - "pcy;" => '\u{00043F}', - "percnt;" => '\u{000025}', - "period;" => '\u{00002E}', - "permil;" => '\u{002030}', - "perp;" => '\u{0022A5}', - "pertenk;" => '\u{002031}', - "pfr;" => '\u{01D52D}', - "phi;" => '\u{0003C6}', - "phiv;" => '\u{0003D5}', - "phmmat;" => '\u{002133}', - "phone;" => '\u{00260E}', - "pi;" => '\u{0003C0}', - "pitchfork;" => '\u{0022D4}', - "piv;" => '\u{0003D6}', - "planck;" => '\u{00210F}', - "planckh;" => '\u{00210E}', - "plankv;" => '\u{00210F}', - "plus;" => '\u{00002B}', - "plusacir;" => '\u{002A23}', - "plusb;" => '\u{00229E}', - "pluscir;" => '\u{002A22}', - "plusdo;" => '\u{002214}', - "plusdu;" => '\u{002A25}', - "pluse;" => '\u{002A72}', - "plusmn;" => '\u{0000B1}', - "plussim;" => '\u{002A26}', - "plustwo;" => '\u{002A27}', - "pm;" => '\u{0000B1}', - "pointint;" => '\u{002A15}', - "popf;" => '\u{01D561}', - "pound;" => '\u{0000A3}', - "pr;" => '\u{00227A}', - "prE;" => '\u{002AB3}', - "prap;" => '\u{002AB7}', - "prcue;" => '\u{00227C}', - "pre;" => '\u{002AAF}', - "prec;" => '\u{00227A}', - "precapprox;" => '\u{002AB7}', - "preccurlyeq;" => '\u{00227C}', - "preceq;" => '\u{002AAF}', - "precnapprox;" => '\u{002AB9}', - "precneqq;" => '\u{002AB5}', - "precnsim;" => '\u{0022E8}', - "precsim;" => '\u{00227E}', - "prime;" => '\u{002032}', - "primes;" => '\u{002119}', - "prnE;" => '\u{002AB5}', - "prnap;" => '\u{002AB9}', - "prnsim;" => '\u{0022E8}', - "prod;" => '\u{00220F}', - "profalar;" => '\u{00232E}', - "profline;" => '\u{002312}', - "profsurf;" => '\u{002313}', - "prop;" => '\u{00221D}', - "propto;" => '\u{00221D}', - "prsim;" => '\u{00227E}', - "prurel;" => '\u{0022B0}', - "pscr;" => '\u{01D4C5}', - "psi;" => '\u{0003C8}', - "puncsp;" => '\u{002008}', - "qfr;" => '\u{01D52E}', - "qint;" => '\u{002A0C}', - "qopf;" => '\u{01D562}', - "qprime;" => '\u{002057}', - "qscr;" => '\u{01D4C6}', - "quaternions;" => '\u{00210D}', - "quatint;" => '\u{002A16}', - "quest;" => '\u{00003F}', - "questeq;" => '\u{00225F}', - "quot;" => '\u{000022}', - "rAarr;" => '\u{0021DB}', - "rArr;" => '\u{0021D2}', - "rAtail;" => '\u{00291C}', - "rBarr;" => '\u{00290F}', - "rHar;" => '\u{002964}', - "racute;" => '\u{000155}', - "radic;" => '\u{00221A}', - "raemptyv;" => '\u{0029B3}', - "rang;" => '\u{0027E9}', - "rangd;" => '\u{002992}', - "range;" => '\u{0029A5}', - "rangle;" => '\u{0027E9}', - "raquo;" => '\u{0000BB}', - "rarr;" => '\u{002192}', - "rarrap;" => '\u{002975}', - "rarrb;" => '\u{0021E5}', - "rarrbfs;" => '\u{002920}', - "rarrc;" => '\u{002933}', - "rarrfs;" => '\u{00291E}', - "rarrhk;" => '\u{0021AA}', - "rarrlp;" => '\u{0021AC}', - "rarrpl;" => '\u{002945}', - "rarrsim;" => '\u{002974}', - "rarrtl;" => '\u{0021A3}', - "rarrw;" => '\u{00219D}', - "ratail;" => '\u{00291A}', - "ratio;" => '\u{002236}', - "rationals;" => '\u{00211A}', - "rbarr;" => '\u{00290D}', - "rbbrk;" => '\u{002773}', - "rbrace;" => '\u{00007D}', - "rbrack;" => '\u{00005D}', - "rbrke;" => '\u{00298C}', - "rbrksld;" => '\u{00298E}', - "rbrkslu;" => '\u{002990}', - "rcaron;" => '\u{000159}', - "rcedil;" => '\u{000157}', - "rceil;" => '\u{002309}', - "rcub;" => '\u{00007D}', - "rcy;" => '\u{000440}', - "rdca;" => '\u{002937}', - "rdldhar;" => '\u{002969}', - "rdquo;" => '\u{00201D}', - "rdquor;" => '\u{00201D}', - "rdsh;" => '\u{0021B3}', - "real;" => '\u{00211C}', - "realine;" => '\u{00211B}', - "realpart;" => '\u{00211C}', - "reals;" => '\u{00211D}', - "rect;" => '\u{0025AD}', - "reg;" => '\u{0000AE}', - "rfisht;" => '\u{00297D}', - "rfloor;" => '\u{00230B}', - "rfr;" => '\u{01D52F}', - "rhard;" => '\u{0021C1}', - "rharu;" => '\u{0021C0}', - "rharul;" => '\u{00296C}', - "rho;" => '\u{0003C1}', - "rhov;" => '\u{0003F1}', - "rightarrow;" => '\u{002192}', - "rightarrowtail;" => '\u{0021A3}', - "rightharpoondown;" => '\u{0021C1}', - "rightharpoonup;" => '\u{0021C0}', - "rightleftarrows;" => '\u{0021C4}', - "rightleftharpoons;" => '\u{0021CC}', - "rightrightarrows;" => '\u{0021C9}', - "rightsquigarrow;" => '\u{00219D}', - "rightthreetimes;" => '\u{0022CC}', - "ring;" => '\u{0002DA}', - "risingdotseq;" => '\u{002253}', - "rlarr;" => '\u{0021C4}', - "rlhar;" => '\u{0021CC}', - "rlm;" => '\u{00200F}', - "rmoust;" => '\u{0023B1}', - "rmoustache;" => '\u{0023B1}', - "rnmid;" => '\u{002AEE}', - "roang;" => '\u{0027ED}', - "roarr;" => '\u{0021FE}', - "robrk;" => '\u{0027E7}', - "ropar;" => '\u{002986}', - "ropf;" => '\u{01D563}', - "roplus;" => '\u{002A2E}', - "rotimes;" => '\u{002A35}', - "rpar;" => '\u{000029}', - "rpargt;" => '\u{002994}', - "rppolint;" => '\u{002A12}', - "rrarr;" => '\u{0021C9}', - "rsaquo;" => '\u{00203A}', - "rscr;" => '\u{01D4C7}', - "rsh;" => '\u{0021B1}', - "rsqb;" => '\u{00005D}', - "rsquo;" => '\u{002019}', - "rsquor;" => '\u{002019}', - "rthree;" => '\u{0022CC}', - "rtimes;" => '\u{0022CA}', - "rtri;" => '\u{0025B9}', - "rtrie;" => '\u{0022B5}', - "rtrif;" => '\u{0025B8}', - "rtriltri;" => '\u{0029CE}', - "ruluhar;" => '\u{002968}', - "rx;" => '\u{00211E}', - "sacute;" => '\u{00015B}', - "sbquo;" => '\u{00201A}', - "sc;" => '\u{00227B}', - "scE;" => '\u{002AB4}', - "scap;" => '\u{002AB8}', - "scaron;" => '\u{000161}', - "sccue;" => '\u{00227D}', - "sce;" => '\u{002AB0}', - "scedil;" => '\u{00015F}', - "scirc;" => '\u{00015D}', - "scnE;" => '\u{002AB6}', - "scnap;" => '\u{002ABA}', - "scnsim;" => '\u{0022E9}', - "scpolint;" => '\u{002A13}', - "scsim;" => '\u{00227F}', - "scy;" => '\u{000441}', - "sdot;" => '\u{0022C5}', - "sdotb;" => '\u{0022A1}', - "sdote;" => '\u{002A66}', - "seArr;" => '\u{0021D8}', - "searhk;" => '\u{002925}', - "searr;" => '\u{002198}', - "searrow;" => '\u{002198}', - "sect;" => '\u{0000A7}', - "semi;" => '\u{00003B}', - "seswar;" => '\u{002929}', - "setminus;" => '\u{002216}', - "setmn;" => '\u{002216}', - "sext;" => '\u{002736}', - "sfr;" => '\u{01D530}', - "sfrown;" => '\u{002322}', - "sharp;" => '\u{00266F}', - "shchcy;" => '\u{000449}', - "shcy;" => '\u{000448}', - "shortmid;" => '\u{002223}', - "shortparallel;" => '\u{002225}', - "shy;" => '\u{0000AD}', - "sigma;" => '\u{0003C3}', - "sigmaf;" => '\u{0003C2}', - "sigmav;" => '\u{0003C2}', - "sim;" => '\u{00223C}', - "simdot;" => '\u{002A6A}', - "sime;" => '\u{002243}', - "simeq;" => '\u{002243}', - "simg;" => '\u{002A9E}', - "simgE;" => '\u{002AA0}', - "siml;" => '\u{002A9D}', - "simlE;" => '\u{002A9F}', - "simne;" => '\u{002246}', - "simplus;" => '\u{002A24}', - "simrarr;" => '\u{002972}', - "slarr;" => '\u{002190}', - "smallsetminus;" => '\u{002216}', - "smashp;" => '\u{002A33}', - "smeparsl;" => '\u{0029E4}', - "smid;" => '\u{002223}', - "smile;" => '\u{002323}', - "smt;" => '\u{002AAA}', - "smte;" => '\u{002AAC}', - "softcy;" => '\u{00044C}', - "sol;" => '\u{00002F}', - "solb;" => '\u{0029C4}', - "solbar;" => '\u{00233F}', - "sopf;" => '\u{01D564}', - "spades;" => '\u{002660}', - "spadesuit;" => '\u{002660}', - "spar;" => '\u{002225}', - "sqcap;" => '\u{002293}', - "sqcup;" => '\u{002294}', - "sqsub;" => '\u{00228F}', - "sqsube;" => '\u{002291}', - "sqsubset;" => '\u{00228F}', - "sqsubseteq;" => '\u{002291}', - "sqsup;" => '\u{002290}', - "sqsupe;" => '\u{002292}', - "sqsupset;" => '\u{002290}', - "sqsupseteq;" => '\u{002292}', - "squ;" => '\u{0025A1}', - "square;" => '\u{0025A1}', - "squarf;" => '\u{0025AA}', - "squf;" => '\u{0025AA}', - "srarr;" => '\u{002192}', - "sscr;" => '\u{01D4C8}', - "ssetmn;" => '\u{002216}', - "ssmile;" => '\u{002323}', - "sstarf;" => '\u{0022C6}', - "star;" => '\u{002606}', - "starf;" => '\u{002605}', - "straightepsilon;" => '\u{0003F5}', - "straightphi;" => '\u{0003D5}', - "strns;" => '\u{0000AF}', - "sub;" => '\u{002282}', - "subE;" => '\u{002AC5}', - "subdot;" => '\u{002ABD}', - "sube;" => '\u{002286}', - "subedot;" => '\u{002AC3}', - "submult;" => '\u{002AC1}', - "subnE;" => '\u{002ACB}', - "subne;" => '\u{00228A}', - "subplus;" => '\u{002ABF}', - "subrarr;" => '\u{002979}', - "subset;" => '\u{002282}', - "subseteq;" => '\u{002286}', - "subseteqq;" => '\u{002AC5}', - "subsetneq;" => '\u{00228A}', - "subsetneqq;" => '\u{002ACB}', - "subsim;" => '\u{002AC7}', - "subsub;" => '\u{002AD5}', - "subsup;" => '\u{002AD3}', - "succ;" => '\u{00227B}', - "succapprox;" => '\u{002AB8}', - "succcurlyeq;" => '\u{00227D}', - "succeq;" => '\u{002AB0}', - "succnapprox;" => '\u{002ABA}', - "succneqq;" => '\u{002AB6}', - "succnsim;" => '\u{0022E9}', - "succsim;" => '\u{00227F}', - "sum;" => '\u{002211}', - "sung;" => '\u{00266A}', - "sup;" => '\u{002283}', - "sup1;" => '\u{0000B9}', - "sup2;" => '\u{0000B2}', - "sup3;" => '\u{0000B3}', - "supE;" => '\u{002AC6}', - "supdot;" => '\u{002ABE}', - "supdsub;" => '\u{002AD8}', - "supe;" => '\u{002287}', - "supedot;" => '\u{002AC4}', - "suphsol;" => '\u{0027C9}', - "suphsub;" => '\u{002AD7}', - "suplarr;" => '\u{00297B}', - "supmult;" => '\u{002AC2}', - "supnE;" => '\u{002ACC}', - "supne;" => '\u{00228B}', - "supplus;" => '\u{002AC0}', - "supset;" => '\u{002283}', - "supseteq;" => '\u{002287}', - "supseteqq;" => '\u{002AC6}', - "supsetneq;" => '\u{00228B}', - "supsetneqq;" => '\u{002ACC}', - "supsim;" => '\u{002AC8}', - "supsub;" => '\u{002AD4}', - "supsup;" => '\u{002AD6}', - "swArr;" => '\u{0021D9}', - "swarhk;" => '\u{002926}', - "swarr;" => '\u{002199}', - "swarrow;" => '\u{002199}', - "swnwar;" => '\u{00292A}', - "szlig;" => '\u{0000DF}', - "target;" => '\u{002316}', - "tau;" => '\u{0003C4}', - "tbrk;" => '\u{0023B4}', - "tcaron;" => '\u{000165}', - "tcedil;" => '\u{000163}', - "tcy;" => '\u{000442}', - "tdot;" => '\u{0020DB}', - "telrec;" => '\u{002315}', - "tfr;" => '\u{01D531}', - "there4;" => '\u{002234}', - "therefore;" => '\u{002234}', - "theta;" => '\u{0003B8}', - "thetasym;" => '\u{0003D1}', - "thetav;" => '\u{0003D1}', - "thickapprox;" => '\u{002248}', - "thicksim;" => '\u{00223C}', - "thinsp;" => '\u{002009}', - "thkap;" => '\u{002248}', - "thksim;" => '\u{00223C}', - "thorn;" => '\u{0000FE}', - "tilde;" => '\u{0002DC}', - "times;" => '\u{0000D7}', - "timesb;" => '\u{0022A0}', - "timesbar;" => '\u{002A31}', - "timesd;" => '\u{002A30}', - "tint;" => '\u{00222D}', - "toea;" => '\u{002928}', - "top;" => '\u{0022A4}', - "topbot;" => '\u{002336}', - "topcir;" => '\u{002AF1}', - "topf;" => '\u{01D565}', - "topfork;" => '\u{002ADA}', - "tosa;" => '\u{002929}', - "tprime;" => '\u{002034}', - "trade;" => '\u{002122}', - "triangle;" => '\u{0025B5}', - "triangledown;" => '\u{0025BF}', - "triangleleft;" => '\u{0025C3}', - "trianglelefteq;" => '\u{0022B4}', - "triangleq;" => '\u{00225C}', - "triangleright;" => '\u{0025B9}', - "trianglerighteq;" => '\u{0022B5}', - "tridot;" => '\u{0025EC}', - "trie;" => '\u{00225C}', - "triminus;" => '\u{002A3A}', - "triplus;" => '\u{002A39}', - "trisb;" => '\u{0029CD}', - "tritime;" => '\u{002A3B}', - "trpezium;" => '\u{0023E2}', - "tscr;" => '\u{01D4C9}', - "tscy;" => '\u{000446}', - "tshcy;" => '\u{00045B}', - "tstrok;" => '\u{000167}', - "twixt;" => '\u{00226C}', - "twoheadleftarrow;" => '\u{00219E}', - "twoheadrightarrow;" => '\u{0021A0}', - "uArr;" => '\u{0021D1}', - "uHar;" => '\u{002963}', - "uacute;" => '\u{0000FA}', - "uarr;" => '\u{002191}', - "ubrcy;" => '\u{00045E}', - "ubreve;" => '\u{00016D}', - "ucirc;" => '\u{0000FB}', - "ucy;" => '\u{000443}', - "udarr;" => '\u{0021C5}', - "udblac;" => '\u{000171}', - "udhar;" => '\u{00296E}', - "ufisht;" => '\u{00297E}', - "ufr;" => '\u{01D532}', - "ugrave;" => '\u{0000F9}', - "uharl;" => '\u{0021BF}', - "uharr;" => '\u{0021BE}', - "uhblk;" => '\u{002580}', - "ulcorn;" => '\u{00231C}', - "ulcorner;" => '\u{00231C}', - "ulcrop;" => '\u{00230F}', - "ultri;" => '\u{0025F8}', - "umacr;" => '\u{00016B}', - "uml;" => '\u{0000A8}', - "uogon;" => '\u{000173}', - "uopf;" => '\u{01D566}', - "uparrow;" => '\u{002191}', - "updownarrow;" => '\u{002195}', - "upharpoonleft;" => '\u{0021BF}', - "upharpoonright;" => '\u{0021BE}', - "uplus;" => '\u{00228E}', - "upsi;" => '\u{0003C5}', - "upsih;" => '\u{0003D2}', - "upsilon;" => '\u{0003C5}', - "upuparrows;" => '\u{0021C8}', - "urcorn;" => '\u{00231D}', - "urcorner;" => '\u{00231D}', - "urcrop;" => '\u{00230E}', - "uring;" => '\u{00016F}', - "urtri;" => '\u{0025F9}', - "uscr;" => '\u{01D4CA}', - "utdot;" => '\u{0022F0}', - "utilde;" => '\u{000169}', - "utri;" => '\u{0025B5}', - "utrif;" => '\u{0025B4}', - "uuarr;" => '\u{0021C8}', - "uuml;" => '\u{0000FC}', - "uwangle;" => '\u{0029A7}', - "vArr;" => '\u{0021D5}', - "vBar;" => '\u{002AE8}', - "vBarv;" => '\u{002AE9}', - "vDash;" => '\u{0022A8}', - "vangrt;" => '\u{00299C}', - "varepsilon;" => '\u{0003F5}', - "varkappa;" => '\u{0003F0}', - "varnothing;" => '\u{002205}', - "varphi;" => '\u{0003D5}', - "varpi;" => '\u{0003D6}', - "varpropto;" => '\u{00221D}', - "varr;" => '\u{002195}', - "varrho;" => '\u{0003F1}', - "varsigma;" => '\u{0003C2}', - "vartheta;" => '\u{0003D1}', - "vartriangleleft;" => '\u{0022B2}', - "vartriangleright;" => '\u{0022B3}', - "vcy;" => '\u{000432}', - "vdash;" => '\u{0022A2}', - "vee;" => '\u{002228}', - "veebar;" => '\u{0022BB}', - "veeeq;" => '\u{00225A}', - "vellip;" => '\u{0022EE}', - "verbar;" => '\u{00007C}', - "vert;" => '\u{00007C}', - "vfr;" => '\u{01D533}', - "vltri;" => '\u{0022B2}', - "vopf;" => '\u{01D567}', - "vprop;" => '\u{00221D}', - "vrtri;" => '\u{0022B3}', - "vscr;" => '\u{01D4CB}', - "vzigzag;" => '\u{00299A}', - "wcirc;" => '\u{000175}', - "wedbar;" => '\u{002A5F}', - "wedge;" => '\u{002227}', - "wedgeq;" => '\u{002259}', - "weierp;" => '\u{002118}', - "wfr;" => '\u{01D534}', - "wopf;" => '\u{01D568}', - "wp;" => '\u{002118}', - "wr;" => '\u{002240}', - "wreath;" => '\u{002240}', - "wscr;" => '\u{01D4CC}', - "xcap;" => '\u{0022C2}', - "xcirc;" => '\u{0025EF}', - "xcup;" => '\u{0022C3}', - "xdtri;" => '\u{0025BD}', - "xfr;" => '\u{01D535}', - "xhArr;" => '\u{0027FA}', - "xharr;" => '\u{0027F7}', - "xi;" => '\u{0003BE}', - "xlArr;" => '\u{0027F8}', - "xlarr;" => '\u{0027F5}', - "xmap;" => '\u{0027FC}', - "xnis;" => '\u{0022FB}', - "xodot;" => '\u{002A00}', - "xopf;" => '\u{01D569}', - "xoplus;" => '\u{002A01}', - "xotime;" => '\u{002A02}', - "xrArr;" => '\u{0027F9}', - "xrarr;" => '\u{0027F6}', - "xscr;" => '\u{01D4CD}', - "xsqcup;" => '\u{002A06}', - "xuplus;" => '\u{002A04}', - "xutri;" => '\u{0025B3}', - "xvee;" => '\u{0022C1}', - "xwedge;" => '\u{0022C0}', - "yacute;" => '\u{0000FD}', - "yacy;" => '\u{00044F}', - "ycirc;" => '\u{000177}', - "ycy;" => '\u{00044B}', - "yen;" => '\u{0000A5}', - "yfr;" => '\u{01D536}', - "yicy;" => '\u{000457}', - "yopf;" => '\u{01D56A}', - "yscr;" => '\u{01D4CE}', - "yucy;" => '\u{00044E}', - "yuml;" => '\u{0000FF}', - "zacute;" => '\u{00017A}', - "zcaron;" => '\u{00017E}', - "zcy;" => '\u{000437}', - "zdot;" => '\u{00017C}', - "zeetrf;" => '\u{002128}', - "zeta;" => '\u{0003B6}', - "zfr;" => '\u{01D537}', - "zhcy;" => '\u{000436}', - "zigrarr;" => '\u{0021DD}', - "zopf;" => '\u{01D56B}', - "zscr;" => '\u{01D4CF}', - "zwj;" => '\u{00200D}', - "zwnj;" => '\u{00200C}', - "AElig" => '\u{0000C6}', - "AMP" => '\u{000026}', - "Aacute" => '\u{0000C1}', - "Acirc" => '\u{0000C2}', - "Agrave" => '\u{0000C0}', - "Aring" => '\u{0000C5}', - "Atilde" => '\u{0000C3}', - "Auml" => '\u{0000C4}', - "COPY" => '\u{0000A9}', - "Ccedil" => '\u{0000C7}', - "ETH" => '\u{0000D0}', - "Eacute" => '\u{0000C9}', - "Ecirc" => '\u{0000CA}', - "Egrave" => '\u{0000C8}', - "Euml" => '\u{0000CB}', - "GT" => '\u{00003E}', - "Iacute" => '\u{0000CD}', - "Icirc" => '\u{0000CE}', - "Igrave" => '\u{0000CC}', - "Iuml" => '\u{0000CF}', - "LT" => '\u{00003C}', - "Ntilde" => '\u{0000D1}', - "Oacute" => '\u{0000D3}', - "Ocirc" => '\u{0000D4}', - "Ograve" => '\u{0000D2}', - "Oslash" => '\u{0000D8}', - "Otilde" => '\u{0000D5}', - "Ouml" => '\u{0000D6}', - "QUOT" => '\u{000022}', - "REG" => '\u{0000AE}', - "THORN" => '\u{0000DE}', - "Uacute" => '\u{0000DA}', - "Ucirc" => '\u{0000DB}', - "Ugrave" => '\u{0000D9}', - "Uuml" => '\u{0000DC}', - "Yacute" => '\u{0000DD}', - "aacute" => '\u{0000E1}', - "acirc" => '\u{0000E2}', - "acute" => '\u{0000B4}', - "aelig" => '\u{0000E6}', - "agrave" => '\u{0000E0}', - "amp" => '\u{000026}', - "aring" => '\u{0000E5}', - "atilde" => '\u{0000E3}', - "auml" => '\u{0000E4}', - "brvbar" => '\u{0000A6}', - "ccedil" => '\u{0000E7}', - "cedil" => '\u{0000B8}', - "cent" => '\u{0000A2}', - "copy" => '\u{0000A9}', - "curren" => '\u{0000A4}', - "deg" => '\u{0000B0}', - "divide" => '\u{0000F7}', - "eacute" => '\u{0000E9}', - "ecirc" => '\u{0000EA}', - "egrave" => '\u{0000E8}', - "eth" => '\u{0000F0}', - "euml" => '\u{0000EB}', - "frac12" => '\u{0000BD}', - "frac14" => '\u{0000BC}', - "frac34" => '\u{0000BE}', - "gt" => '\u{00003E}', - "iacute" => '\u{0000ED}', - "icirc" => '\u{0000EE}', - "iexcl" => '\u{0000A1}', - "igrave" => '\u{0000EC}', - "iquest" => '\u{0000BF}', - "iuml" => '\u{0000EF}', - "laquo" => '\u{0000AB}', - "lt" => '\u{00003C}', - "macr" => '\u{0000AF}', - "micro" => '\u{0000B5}', - "middot" => '\u{0000B7}', - "nbsp" => '\u{0000A0}', - "not" => '\u{0000AC}', - "ntilde" => '\u{0000F1}', - "oacute" => '\u{0000F3}', - "ocirc" => '\u{0000F4}', - "ograve" => '\u{0000F2}', - "ordf" => '\u{0000AA}', - "ordm" => '\u{0000BA}', - "oslash" => '\u{0000F8}', - "otilde" => '\u{0000F5}', - "ouml" => '\u{0000F6}', - "para" => '\u{0000B6}', - "plusmn" => '\u{0000B1}', - "pound" => '\u{0000A3}', - "quot" => '\u{000022}', - "raquo" => '\u{0000BB}', - "reg" => '\u{0000AE}', - "sect" => '\u{0000A7}', - "shy" => '\u{0000AD}', - "sup1" => '\u{0000B9}', - "sup2" => '\u{0000B2}', - "sup3" => '\u{0000B3}', - "szlig" => '\u{0000DF}', - "thorn" => '\u{0000FE}', - "times" => '\u{0000D7}', - "uacute" => '\u{0000FA}', - "ucirc" => '\u{0000FB}', - "ugrave" => '\u{0000F9}', - "uml" => '\u{0000A8}', - "uuml" => '\u{0000FC}', - "yacute" => '\u{0000FD}', - "yen" => '\u{0000A5}', - "yuml" => '\u{0000FF}', - } of String => Char + "AElig".to_slice => '\u{0000C6}', + "AMP".to_slice => '\u{000026}', + "Aacute".to_slice => '\u{0000C1}', + "Abreve".to_slice => '\u{000102}', + "Acirc".to_slice => '\u{0000C2}', + "Acy".to_slice => '\u{000410}', + "Afr".to_slice => '\u{01D504}', + "Agrave".to_slice => '\u{0000C0}', + "Alpha".to_slice => '\u{000391}', + "Amacr".to_slice => '\u{000100}', + "And".to_slice => '\u{002A53}', + "Aogon".to_slice => '\u{000104}', + "Aopf".to_slice => '\u{01D538}', + "ApplyFunction".to_slice => '\u{002061}', + "Aring".to_slice => '\u{0000C5}', + "Ascr".to_slice => '\u{01D49C}', + "Assign".to_slice => '\u{002254}', + "Atilde".to_slice => '\u{0000C3}', + "Auml".to_slice => '\u{0000C4}', + "Backslash".to_slice => '\u{002216}', + "Barv".to_slice => '\u{002AE7}', + "Barwed".to_slice => '\u{002306}', + "Bcy".to_slice => '\u{000411}', + "Because".to_slice => '\u{002235}', + "Bernoullis".to_slice => '\u{00212C}', + "Beta".to_slice => '\u{000392}', + "Bfr".to_slice => '\u{01D505}', + "Bopf".to_slice => '\u{01D539}', + "Breve".to_slice => '\u{0002D8}', + "Bscr".to_slice => '\u{00212C}', + "Bumpeq".to_slice => '\u{00224E}', + "CHcy".to_slice => '\u{000427}', + "COPY".to_slice => '\u{0000A9}', + "Cacute".to_slice => '\u{000106}', + "Cap".to_slice => '\u{0022D2}', + "CapitalDifferentialD".to_slice => '\u{002145}', + "Cayleys".to_slice => '\u{00212D}', + "Ccaron".to_slice => '\u{00010C}', + "Ccedil".to_slice => '\u{0000C7}', + "Ccirc".to_slice => '\u{000108}', + "Cconint".to_slice => '\u{002230}', + "Cdot".to_slice => '\u{00010A}', + "Cedilla".to_slice => '\u{0000B8}', + "CenterDot".to_slice => '\u{0000B7}', + "Cfr".to_slice => '\u{00212D}', + "Chi".to_slice => '\u{0003A7}', + "CircleDot".to_slice => '\u{002299}', + "CircleMinus".to_slice => '\u{002296}', + "CirclePlus".to_slice => '\u{002295}', + "CircleTimes".to_slice => '\u{002297}', + "ClockwiseContourIntegral".to_slice => '\u{002232}', + "CloseCurlyDoubleQuote".to_slice => '\u{00201D}', + "CloseCurlyQuote".to_slice => '\u{002019}', + "Colon".to_slice => '\u{002237}', + "Colone".to_slice => '\u{002A74}', + "Congruent".to_slice => '\u{002261}', + "Conint".to_slice => '\u{00222F}', + "ContourIntegral".to_slice => '\u{00222E}', + "Copf".to_slice => '\u{002102}', + "Coproduct".to_slice => '\u{002210}', + "CounterClockwiseContourIntegral".to_slice => '\u{002233}', + "Cross".to_slice => '\u{002A2F}', + "Cscr".to_slice => '\u{01D49E}', + "Cup".to_slice => '\u{0022D3}', + "CupCap".to_slice => '\u{00224D}', + "DD".to_slice => '\u{002145}', + "DDotrahd".to_slice => '\u{002911}', + "DJcy".to_slice => '\u{000402}', + "DScy".to_slice => '\u{000405}', + "DZcy".to_slice => '\u{00040F}', + "Dagger".to_slice => '\u{002021}', + "Darr".to_slice => '\u{0021A1}', + "Dashv".to_slice => '\u{002AE4}', + "Dcaron".to_slice => '\u{00010E}', + "Dcy".to_slice => '\u{000414}', + "Del".to_slice => '\u{002207}', + "Delta".to_slice => '\u{000394}', + "Dfr".to_slice => '\u{01D507}', + "DiacriticalAcute".to_slice => '\u{0000B4}', + "DiacriticalDot".to_slice => '\u{0002D9}', + "DiacriticalDoubleAcute".to_slice => '\u{0002DD}', + "DiacriticalGrave".to_slice => '\u{000060}', + "DiacriticalTilde".to_slice => '\u{0002DC}', + "Diamond".to_slice => '\u{0022C4}', + "DifferentialD".to_slice => '\u{002146}', + "Dopf".to_slice => '\u{01D53B}', + "Dot".to_slice => '\u{0000A8}', + "DotDot".to_slice => '\u{0020DC}', + "DotEqual".to_slice => '\u{002250}', + "DoubleContourIntegral".to_slice => '\u{00222F}', + "DoubleDot".to_slice => '\u{0000A8}', + "DoubleDownArrow".to_slice => '\u{0021D3}', + "DoubleLeftArrow".to_slice => '\u{0021D0}', + "DoubleLeftRightArrow".to_slice => '\u{0021D4}', + "DoubleLeftTee".to_slice => '\u{002AE4}', + "DoubleLongLeftArrow".to_slice => '\u{0027F8}', + "DoubleLongLeftRightArrow".to_slice => '\u{0027FA}', + "DoubleLongRightArrow".to_slice => '\u{0027F9}', + "DoubleRightArrow".to_slice => '\u{0021D2}', + "DoubleRightTee".to_slice => '\u{0022A8}', + "DoubleUpArrow".to_slice => '\u{0021D1}', + "DoubleUpDownArrow".to_slice => '\u{0021D5}', + "DoubleVerticalBar".to_slice => '\u{002225}', + "DownArrow".to_slice => '\u{002193}', + "DownArrowBar".to_slice => '\u{002913}', + "DownArrowUpArrow".to_slice => '\u{0021F5}', + "DownBreve".to_slice => '\u{000311}', + "DownLeftRightVector".to_slice => '\u{002950}', + "DownLeftTeeVector".to_slice => '\u{00295E}', + "DownLeftVector".to_slice => '\u{0021BD}', + "DownLeftVectorBar".to_slice => '\u{002956}', + "DownRightTeeVector".to_slice => '\u{00295F}', + "DownRightVector".to_slice => '\u{0021C1}', + "DownRightVectorBar".to_slice => '\u{002957}', + "DownTee".to_slice => '\u{0022A4}', + "DownTeeArrow".to_slice => '\u{0021A7}', + "Downarrow".to_slice => '\u{0021D3}', + "Dscr".to_slice => '\u{01D49F}', + "Dstrok".to_slice => '\u{000110}', + "ENG".to_slice => '\u{00014A}', + "ETH".to_slice => '\u{0000D0}', + "Eacute".to_slice => '\u{0000C9}', + "Ecaron".to_slice => '\u{00011A}', + "Ecirc".to_slice => '\u{0000CA}', + "Ecy".to_slice => '\u{00042D}', + "Edot".to_slice => '\u{000116}', + "Efr".to_slice => '\u{01D508}', + "Egrave".to_slice => '\u{0000C8}', + "Element".to_slice => '\u{002208}', + "Emacr".to_slice => '\u{000112}', + "EmptySmallSquare".to_slice => '\u{0025FB}', + "EmptyVerySmallSquare".to_slice => '\u{0025AB}', + "Eogon".to_slice => '\u{000118}', + "Eopf".to_slice => '\u{01D53C}', + "Epsilon".to_slice => '\u{000395}', + "Equal".to_slice => '\u{002A75}', + "EqualTilde".to_slice => '\u{002242}', + "Equilibrium".to_slice => '\u{0021CC}', + "Escr".to_slice => '\u{002130}', + "Esim".to_slice => '\u{002A73}', + "Eta".to_slice => '\u{000397}', + "Euml".to_slice => '\u{0000CB}', + "Exists".to_slice => '\u{002203}', + "ExponentialE".to_slice => '\u{002147}', + "Fcy".to_slice => '\u{000424}', + "Ffr".to_slice => '\u{01D509}', + "FilledSmallSquare".to_slice => '\u{0025FC}', + "FilledVerySmallSquare".to_slice => '\u{0025AA}', + "Fopf".to_slice => '\u{01D53D}', + "ForAll".to_slice => '\u{002200}', + "Fouriertrf".to_slice => '\u{002131}', + "Fscr".to_slice => '\u{002131}', + "GJcy".to_slice => '\u{000403}', + "GT".to_slice => '\u{00003E}', + "Gamma".to_slice => '\u{000393}', + "Gammad".to_slice => '\u{0003DC}', + "Gbreve".to_slice => '\u{00011E}', + "Gcedil".to_slice => '\u{000122}', + "Gcirc".to_slice => '\u{00011C}', + "Gcy".to_slice => '\u{000413}', + "Gdot".to_slice => '\u{000120}', + "Gfr".to_slice => '\u{01D50A}', + "Gg".to_slice => '\u{0022D9}', + "Gopf".to_slice => '\u{01D53E}', + "GreaterEqual".to_slice => '\u{002265}', + "GreaterEqualLess".to_slice => '\u{0022DB}', + "GreaterFullEqual".to_slice => '\u{002267}', + "GreaterGreater".to_slice => '\u{002AA2}', + "GreaterLess".to_slice => '\u{002277}', + "GreaterSlantEqual".to_slice => '\u{002A7E}', + "GreaterTilde".to_slice => '\u{002273}', + "Gscr".to_slice => '\u{01D4A2}', + "Gt".to_slice => '\u{00226B}', + "HARDcy".to_slice => '\u{00042A}', + "Hacek".to_slice => '\u{0002C7}', + "Hat".to_slice => '\u{00005E}', + "Hcirc".to_slice => '\u{000124}', + "Hfr".to_slice => '\u{00210C}', + "HilbertSpace".to_slice => '\u{00210B}', + "Hopf".to_slice => '\u{00210D}', + "HorizontalLine".to_slice => '\u{002500}', + "Hscr".to_slice => '\u{00210B}', + "Hstrok".to_slice => '\u{000126}', + "HumpDownHump".to_slice => '\u{00224E}', + "HumpEqual".to_slice => '\u{00224F}', + "IEcy".to_slice => '\u{000415}', + "IJlig".to_slice => '\u{000132}', + "IOcy".to_slice => '\u{000401}', + "Iacute".to_slice => '\u{0000CD}', + "Icirc".to_slice => '\u{0000CE}', + "Icy".to_slice => '\u{000418}', + "Idot".to_slice => '\u{000130}', + "Ifr".to_slice => '\u{002111}', + "Igrave".to_slice => '\u{0000CC}', + "Im".to_slice => '\u{002111}', + "Imacr".to_slice => '\u{00012A}', + "ImaginaryI".to_slice => '\u{002148}', + "Implies".to_slice => '\u{0021D2}', + "Int".to_slice => '\u{00222C}', + "Integral".to_slice => '\u{00222B}', + "Intersection".to_slice => '\u{0022C2}', + "InvisibleComma".to_slice => '\u{002063}', + "InvisibleTimes".to_slice => '\u{002062}', + "Iogon".to_slice => '\u{00012E}', + "Iopf".to_slice => '\u{01D540}', + "Iota".to_slice => '\u{000399}', + "Iscr".to_slice => '\u{002110}', + "Itilde".to_slice => '\u{000128}', + "Iukcy".to_slice => '\u{000406}', + "Iuml".to_slice => '\u{0000CF}', + "Jcirc".to_slice => '\u{000134}', + "Jcy".to_slice => '\u{000419}', + "Jfr".to_slice => '\u{01D50D}', + "Jopf".to_slice => '\u{01D541}', + "Jscr".to_slice => '\u{01D4A5}', + "Jsercy".to_slice => '\u{000408}', + "Jukcy".to_slice => '\u{000404}', + "KHcy".to_slice => '\u{000425}', + "KJcy".to_slice => '\u{00040C}', + "Kappa".to_slice => '\u{00039A}', + "Kcedil".to_slice => '\u{000136}', + "Kcy".to_slice => '\u{00041A}', + "Kfr".to_slice => '\u{01D50E}', + "Kopf".to_slice => '\u{01D542}', + "Kscr".to_slice => '\u{01D4A6}', + "LJcy".to_slice => '\u{000409}', + "LT".to_slice => '\u{00003C}', + "Lacute".to_slice => '\u{000139}', + "Lambda".to_slice => '\u{00039B}', + "Lang".to_slice => '\u{0027EA}', + "Laplacetrf".to_slice => '\u{002112}', + "Larr".to_slice => '\u{00219E}', + "Lcaron".to_slice => '\u{00013D}', + "Lcedil".to_slice => '\u{00013B}', + "Lcy".to_slice => '\u{00041B}', + "LeftAngleBracket".to_slice => '\u{0027E8}', + "LeftArrow".to_slice => '\u{002190}', + "LeftArrowBar".to_slice => '\u{0021E4}', + "LeftArrowRightArrow".to_slice => '\u{0021C6}', + "LeftCeiling".to_slice => '\u{002308}', + "LeftDoubleBracket".to_slice => '\u{0027E6}', + "LeftDownTeeVector".to_slice => '\u{002961}', + "LeftDownVector".to_slice => '\u{0021C3}', + "LeftDownVectorBar".to_slice => '\u{002959}', + "LeftFloor".to_slice => '\u{00230A}', + "LeftRightArrow".to_slice => '\u{002194}', + "LeftRightVector".to_slice => '\u{00294E}', + "LeftTee".to_slice => '\u{0022A3}', + "LeftTeeArrow".to_slice => '\u{0021A4}', + "LeftTeeVector".to_slice => '\u{00295A}', + "LeftTriangle".to_slice => '\u{0022B2}', + "LeftTriangleBar".to_slice => '\u{0029CF}', + "LeftTriangleEqual".to_slice => '\u{0022B4}', + "LeftUpDownVector".to_slice => '\u{002951}', + "LeftUpTeeVector".to_slice => '\u{002960}', + "LeftUpVector".to_slice => '\u{0021BF}', + "LeftUpVectorBar".to_slice => '\u{002958}', + "LeftVector".to_slice => '\u{0021BC}', + "LeftVectorBar".to_slice => '\u{002952}', + "Leftarrow".to_slice => '\u{0021D0}', + "Leftrightarrow".to_slice => '\u{0021D4}', + "LessEqualGreater".to_slice => '\u{0022DA}', + "LessFullEqual".to_slice => '\u{002266}', + "LessGreater".to_slice => '\u{002276}', + "LessLess".to_slice => '\u{002AA1}', + "LessSlantEqual".to_slice => '\u{002A7D}', + "LessTilde".to_slice => '\u{002272}', + "Lfr".to_slice => '\u{01D50F}', + "Ll".to_slice => '\u{0022D8}', + "Lleftarrow".to_slice => '\u{0021DA}', + "Lmidot".to_slice => '\u{00013F}', + "LongLeftArrow".to_slice => '\u{0027F5}', + "LongLeftRightArrow".to_slice => '\u{0027F7}', + "LongRightArrow".to_slice => '\u{0027F6}', + "Longleftarrow".to_slice => '\u{0027F8}', + "Longleftrightarrow".to_slice => '\u{0027FA}', + "Longrightarrow".to_slice => '\u{0027F9}', + "Lopf".to_slice => '\u{01D543}', + "LowerLeftArrow".to_slice => '\u{002199}', + "LowerRightArrow".to_slice => '\u{002198}', + "Lscr".to_slice => '\u{002112}', + "Lsh".to_slice => '\u{0021B0}', + "Lstrok".to_slice => '\u{000141}', + "Lt".to_slice => '\u{00226A}', + "Map".to_slice => '\u{002905}', + "Mcy".to_slice => '\u{00041C}', + "MediumSpace".to_slice => '\u{00205F}', + "Mellintrf".to_slice => '\u{002133}', + "Mfr".to_slice => '\u{01D510}', + "MinusPlus".to_slice => '\u{002213}', + "Mopf".to_slice => '\u{01D544}', + "Mscr".to_slice => '\u{002133}', + "Mu".to_slice => '\u{00039C}', + "NJcy".to_slice => '\u{00040A}', + "Nacute".to_slice => '\u{000143}', + "Ncaron".to_slice => '\u{000147}', + "Ncedil".to_slice => '\u{000145}', + "Ncy".to_slice => '\u{00041D}', + "NegativeMediumSpace".to_slice => '\u{00200B}', + "NegativeThickSpace".to_slice => '\u{00200B}', + "NegativeThinSpace".to_slice => '\u{00200B}', + "NegativeVeryThinSpace".to_slice => '\u{00200B}', + "NestedGreaterGreater".to_slice => '\u{00226B}', + "NestedLessLess".to_slice => '\u{00226A}', + "NewLine".to_slice => '\u{00000A}', + "Nfr".to_slice => '\u{01D511}', + "NoBreak".to_slice => '\u{002060}', + "NonBreakingSpace".to_slice => '\u{0000A0}', + "Nopf".to_slice => '\u{002115}', + "Not".to_slice => '\u{002AEC}', + "NotCongruent".to_slice => '\u{002262}', + "NotCupCap".to_slice => '\u{00226D}', + "NotDoubleVerticalBar".to_slice => '\u{002226}', + "NotElement".to_slice => '\u{002209}', + "NotEqual".to_slice => '\u{002260}', + "NotExists".to_slice => '\u{002204}', + "NotGreater".to_slice => '\u{00226F}', + "NotGreaterEqual".to_slice => '\u{002271}', + "NotGreaterLess".to_slice => '\u{002279}', + "NotGreaterTilde".to_slice => '\u{002275}', + "NotLeftTriangle".to_slice => '\u{0022EA}', + "NotLeftTriangleEqual".to_slice => '\u{0022EC}', + "NotLess".to_slice => '\u{00226E}', + "NotLessEqual".to_slice => '\u{002270}', + "NotLessGreater".to_slice => '\u{002278}', + "NotLessTilde".to_slice => '\u{002274}', + "NotPrecedes".to_slice => '\u{002280}', + "NotPrecedesSlantEqual".to_slice => '\u{0022E0}', + "NotReverseElement".to_slice => '\u{00220C}', + "NotRightTriangle".to_slice => '\u{0022EB}', + "NotRightTriangleEqual".to_slice => '\u{0022ED}', + "NotSquareSubsetEqual".to_slice => '\u{0022E2}', + "NotSquareSupersetEqual".to_slice => '\u{0022E3}', + "NotSubsetEqual".to_slice => '\u{002288}', + "NotSucceeds".to_slice => '\u{002281}', + "NotSucceedsSlantEqual".to_slice => '\u{0022E1}', + "NotSupersetEqual".to_slice => '\u{002289}', + "NotTilde".to_slice => '\u{002241}', + "NotTildeEqual".to_slice => '\u{002244}', + "NotTildeFullEqual".to_slice => '\u{002247}', + "NotTildeTilde".to_slice => '\u{002249}', + "NotVerticalBar".to_slice => '\u{002224}', + "Nscr".to_slice => '\u{01D4A9}', + "Ntilde".to_slice => '\u{0000D1}', + "Nu".to_slice => '\u{00039D}', + "OElig".to_slice => '\u{000152}', + "Oacute".to_slice => '\u{0000D3}', + "Ocirc".to_slice => '\u{0000D4}', + "Ocy".to_slice => '\u{00041E}', + "Odblac".to_slice => '\u{000150}', + "Ofr".to_slice => '\u{01D512}', + "Ograve".to_slice => '\u{0000D2}', + "Omacr".to_slice => '\u{00014C}', + "Omega".to_slice => '\u{0003A9}', + "Omicron".to_slice => '\u{00039F}', + "Oopf".to_slice => '\u{01D546}', + "OpenCurlyDoubleQuote".to_slice => '\u{00201C}', + "OpenCurlyQuote".to_slice => '\u{002018}', + "Or".to_slice => '\u{002A54}', + "Oscr".to_slice => '\u{01D4AA}', + "Oslash".to_slice => '\u{0000D8}', + "Otilde".to_slice => '\u{0000D5}', + "Otimes".to_slice => '\u{002A37}', + "Ouml".to_slice => '\u{0000D6}', + "OverBar".to_slice => '\u{00203E}', + "OverBrace".to_slice => '\u{0023DE}', + "OverBracket".to_slice => '\u{0023B4}', + "OverParenthesis".to_slice => '\u{0023DC}', + "PartialD".to_slice => '\u{002202}', + "Pcy".to_slice => '\u{00041F}', + "Pfr".to_slice => '\u{01D513}', + "Phi".to_slice => '\u{0003A6}', + "Pi".to_slice => '\u{0003A0}', + "PlusMinus".to_slice => '\u{0000B1}', + "Poincareplane".to_slice => '\u{00210C}', + "Popf".to_slice => '\u{002119}', + "Pr".to_slice => '\u{002ABB}', + "Precedes".to_slice => '\u{00227A}', + "PrecedesEqual".to_slice => '\u{002AAF}', + "PrecedesSlantEqual".to_slice => '\u{00227C}', + "PrecedesTilde".to_slice => '\u{00227E}', + "Prime".to_slice => '\u{002033}', + "Product".to_slice => '\u{00220F}', + "Proportion".to_slice => '\u{002237}', + "Proportional".to_slice => '\u{00221D}', + "Pscr".to_slice => '\u{01D4AB}', + "Psi".to_slice => '\u{0003A8}', + "QUOT".to_slice => '\u{000022}', + "Qfr".to_slice => '\u{01D514}', + "Qopf".to_slice => '\u{00211A}', + "Qscr".to_slice => '\u{01D4AC}', + "RBarr".to_slice => '\u{002910}', + "REG".to_slice => '\u{0000AE}', + "Racute".to_slice => '\u{000154}', + "Rang".to_slice => '\u{0027EB}', + "Rarr".to_slice => '\u{0021A0}', + "Rarrtl".to_slice => '\u{002916}', + "Rcaron".to_slice => '\u{000158}', + "Rcedil".to_slice => '\u{000156}', + "Rcy".to_slice => '\u{000420}', + "Re".to_slice => '\u{00211C}', + "ReverseElement".to_slice => '\u{00220B}', + "ReverseEquilibrium".to_slice => '\u{0021CB}', + "ReverseUpEquilibrium".to_slice => '\u{00296F}', + "Rfr".to_slice => '\u{00211C}', + "Rho".to_slice => '\u{0003A1}', + "RightAngleBracket".to_slice => '\u{0027E9}', + "RightArrow".to_slice => '\u{002192}', + "RightArrowBar".to_slice => '\u{0021E5}', + "RightArrowLeftArrow".to_slice => '\u{0021C4}', + "RightCeiling".to_slice => '\u{002309}', + "RightDoubleBracket".to_slice => '\u{0027E7}', + "RightDownTeeVector".to_slice => '\u{00295D}', + "RightDownVector".to_slice => '\u{0021C2}', + "RightDownVectorBar".to_slice => '\u{002955}', + "RightFloor".to_slice => '\u{00230B}', + "RightTee".to_slice => '\u{0022A2}', + "RightTeeArrow".to_slice => '\u{0021A6}', + "RightTeeVector".to_slice => '\u{00295B}', + "RightTriangle".to_slice => '\u{0022B3}', + "RightTriangleBar".to_slice => '\u{0029D0}', + "RightTriangleEqual".to_slice => '\u{0022B5}', + "RightUpDownVector".to_slice => '\u{00294F}', + "RightUpTeeVector".to_slice => '\u{00295C}', + "RightUpVector".to_slice => '\u{0021BE}', + "RightUpVectorBar".to_slice => '\u{002954}', + "RightVector".to_slice => '\u{0021C0}', + "RightVectorBar".to_slice => '\u{002953}', + "Rightarrow".to_slice => '\u{0021D2}', + "Ropf".to_slice => '\u{00211D}', + "RoundImplies".to_slice => '\u{002970}', + "Rrightarrow".to_slice => '\u{0021DB}', + "Rscr".to_slice => '\u{00211B}', + "Rsh".to_slice => '\u{0021B1}', + "RuleDelayed".to_slice => '\u{0029F4}', + "SHCHcy".to_slice => '\u{000429}', + "SHcy".to_slice => '\u{000428}', + "SOFTcy".to_slice => '\u{00042C}', + "Sacute".to_slice => '\u{00015A}', + "Sc".to_slice => '\u{002ABC}', + "Scaron".to_slice => '\u{000160}', + "Scedil".to_slice => '\u{00015E}', + "Scirc".to_slice => '\u{00015C}', + "Scy".to_slice => '\u{000421}', + "Sfr".to_slice => '\u{01D516}', + "ShortDownArrow".to_slice => '\u{002193}', + "ShortLeftArrow".to_slice => '\u{002190}', + "ShortRightArrow".to_slice => '\u{002192}', + "ShortUpArrow".to_slice => '\u{002191}', + "Sigma".to_slice => '\u{0003A3}', + "SmallCircle".to_slice => '\u{002218}', + "Sopf".to_slice => '\u{01D54A}', + "Sqrt".to_slice => '\u{00221A}', + "Square".to_slice => '\u{0025A1}', + "SquareIntersection".to_slice => '\u{002293}', + "SquareSubset".to_slice => '\u{00228F}', + "SquareSubsetEqual".to_slice => '\u{002291}', + "SquareSuperset".to_slice => '\u{002290}', + "SquareSupersetEqual".to_slice => '\u{002292}', + "SquareUnion".to_slice => '\u{002294}', + "Sscr".to_slice => '\u{01D4AE}', + "Star".to_slice => '\u{0022C6}', + "Sub".to_slice => '\u{0022D0}', + "Subset".to_slice => '\u{0022D0}', + "SubsetEqual".to_slice => '\u{002286}', + "Succeeds".to_slice => '\u{00227B}', + "SucceedsEqual".to_slice => '\u{002AB0}', + "SucceedsSlantEqual".to_slice => '\u{00227D}', + "SucceedsTilde".to_slice => '\u{00227F}', + "SuchThat".to_slice => '\u{00220B}', + "Sum".to_slice => '\u{002211}', + "Sup".to_slice => '\u{0022D1}', + "Superset".to_slice => '\u{002283}', + "SupersetEqual".to_slice => '\u{002287}', + "Supset".to_slice => '\u{0022D1}', + "THORN".to_slice => '\u{0000DE}', + "TRADE".to_slice => '\u{002122}', + "TSHcy".to_slice => '\u{00040B}', + "TScy".to_slice => '\u{000426}', + "Tab".to_slice => '\u{000009}', + "Tau".to_slice => '\u{0003A4}', + "Tcaron".to_slice => '\u{000164}', + "Tcedil".to_slice => '\u{000162}', + "Tcy".to_slice => '\u{000422}', + "Tfr".to_slice => '\u{01D517}', + "Therefore".to_slice => '\u{002234}', + "Theta".to_slice => '\u{000398}', + "ThinSpace".to_slice => '\u{002009}', + "Tilde".to_slice => '\u{00223C}', + "TildeEqual".to_slice => '\u{002243}', + "TildeFullEqual".to_slice => '\u{002245}', + "TildeTilde".to_slice => '\u{002248}', + "Topf".to_slice => '\u{01D54B}', + "TripleDot".to_slice => '\u{0020DB}', + "Tscr".to_slice => '\u{01D4AF}', + "Tstrok".to_slice => '\u{000166}', + "Uacute".to_slice => '\u{0000DA}', + "Uarr".to_slice => '\u{00219F}', + "Uarrocir".to_slice => '\u{002949}', + "Ubrcy".to_slice => '\u{00040E}', + "Ubreve".to_slice => '\u{00016C}', + "Ucirc".to_slice => '\u{0000DB}', + "Ucy".to_slice => '\u{000423}', + "Udblac".to_slice => '\u{000170}', + "Ufr".to_slice => '\u{01D518}', + "Ugrave".to_slice => '\u{0000D9}', + "Umacr".to_slice => '\u{00016A}', + "UnderBar".to_slice => '\u{00005F}', + "UnderBrace".to_slice => '\u{0023DF}', + "UnderBracket".to_slice => '\u{0023B5}', + "UnderParenthesis".to_slice => '\u{0023DD}', + "Union".to_slice => '\u{0022C3}', + "UnionPlus".to_slice => '\u{00228E}', + "Uogon".to_slice => '\u{000172}', + "Uopf".to_slice => '\u{01D54C}', + "UpArrow".to_slice => '\u{002191}', + "UpArrowBar".to_slice => '\u{002912}', + "UpArrowDownArrow".to_slice => '\u{0021C5}', + "UpDownArrow".to_slice => '\u{002195}', + "UpEquilibrium".to_slice => '\u{00296E}', + "UpTee".to_slice => '\u{0022A5}', + "UpTeeArrow".to_slice => '\u{0021A5}', + "Uparrow".to_slice => '\u{0021D1}', + "Updownarrow".to_slice => '\u{0021D5}', + "UpperLeftArrow".to_slice => '\u{002196}', + "UpperRightArrow".to_slice => '\u{002197}', + "Upsi".to_slice => '\u{0003D2}', + "Upsilon".to_slice => '\u{0003A5}', + "Uring".to_slice => '\u{00016E}', + "Uscr".to_slice => '\u{01D4B0}', + "Utilde".to_slice => '\u{000168}', + "Uuml".to_slice => '\u{0000DC}', + "VDash".to_slice => '\u{0022AB}', + "Vbar".to_slice => '\u{002AEB}', + "Vcy".to_slice => '\u{000412}', + "Vdash".to_slice => '\u{0022A9}', + "Vdashl".to_slice => '\u{002AE6}', + "Vee".to_slice => '\u{0022C1}', + "Verbar".to_slice => '\u{002016}', + "Vert".to_slice => '\u{002016}', + "VerticalBar".to_slice => '\u{002223}', + "VerticalLine".to_slice => '\u{00007C}', + "VerticalSeparator".to_slice => '\u{002758}', + "VerticalTilde".to_slice => '\u{002240}', + "VeryThinSpace".to_slice => '\u{00200A}', + "Vfr".to_slice => '\u{01D519}', + "Vopf".to_slice => '\u{01D54D}', + "Vscr".to_slice => '\u{01D4B1}', + "Vvdash".to_slice => '\u{0022AA}', + "Wcirc".to_slice => '\u{000174}', + "Wedge".to_slice => '\u{0022C0}', + "Wfr".to_slice => '\u{01D51A}', + "Wopf".to_slice => '\u{01D54E}', + "Wscr".to_slice => '\u{01D4B2}', + "Xfr".to_slice => '\u{01D51B}', + "Xi".to_slice => '\u{00039E}', + "Xopf".to_slice => '\u{01D54F}', + "Xscr".to_slice => '\u{01D4B3}', + "YAcy".to_slice => '\u{00042F}', + "YIcy".to_slice => '\u{000407}', + "YUcy".to_slice => '\u{00042E}', + "Yacute".to_slice => '\u{0000DD}', + "Ycirc".to_slice => '\u{000176}', + "Ycy".to_slice => '\u{00042B}', + "Yfr".to_slice => '\u{01D51C}', + "Yopf".to_slice => '\u{01D550}', + "Yscr".to_slice => '\u{01D4B4}', + "Yuml".to_slice => '\u{000178}', + "ZHcy".to_slice => '\u{000416}', + "Zacute".to_slice => '\u{000179}', + "Zcaron".to_slice => '\u{00017D}', + "Zcy".to_slice => '\u{000417}', + "Zdot".to_slice => '\u{00017B}', + "ZeroWidthSpace".to_slice => '\u{00200B}', + "Zeta".to_slice => '\u{000396}', + "Zfr".to_slice => '\u{002128}', + "Zopf".to_slice => '\u{002124}', + "Zscr".to_slice => '\u{01D4B5}', + "aacute".to_slice => '\u{0000E1}', + "abreve".to_slice => '\u{000103}', + "ac".to_slice => '\u{00223E}', + "acd".to_slice => '\u{00223F}', + "acirc".to_slice => '\u{0000E2}', + "acute".to_slice => '\u{0000B4}', + "acy".to_slice => '\u{000430}', + "aelig".to_slice => '\u{0000E6}', + "af".to_slice => '\u{002061}', + "afr".to_slice => '\u{01D51E}', + "agrave".to_slice => '\u{0000E0}', + "alefsym".to_slice => '\u{002135}', + "aleph".to_slice => '\u{002135}', + "alpha".to_slice => '\u{0003B1}', + "amacr".to_slice => '\u{000101}', + "amalg".to_slice => '\u{002A3F}', + "amp".to_slice => '\u{000026}', + "and".to_slice => '\u{002227}', + "andand".to_slice => '\u{002A55}', + "andd".to_slice => '\u{002A5C}', + "andslope".to_slice => '\u{002A58}', + "andv".to_slice => '\u{002A5A}', + "ang".to_slice => '\u{002220}', + "ange".to_slice => '\u{0029A4}', + "angle".to_slice => '\u{002220}', + "angmsd".to_slice => '\u{002221}', + "angmsdaa".to_slice => '\u{0029A8}', + "angmsdab".to_slice => '\u{0029A9}', + "angmsdac".to_slice => '\u{0029AA}', + "angmsdad".to_slice => '\u{0029AB}', + "angmsdae".to_slice => '\u{0029AC}', + "angmsdaf".to_slice => '\u{0029AD}', + "angmsdag".to_slice => '\u{0029AE}', + "angmsdah".to_slice => '\u{0029AF}', + "angrt".to_slice => '\u{00221F}', + "angrtvb".to_slice => '\u{0022BE}', + "angrtvbd".to_slice => '\u{00299D}', + "angsph".to_slice => '\u{002222}', + "angst".to_slice => '\u{0000C5}', + "angzarr".to_slice => '\u{00237C}', + "aogon".to_slice => '\u{000105}', + "aopf".to_slice => '\u{01D552}', + "ap".to_slice => '\u{002248}', + "apE".to_slice => '\u{002A70}', + "apacir".to_slice => '\u{002A6F}', + "ape".to_slice => '\u{00224A}', + "apid".to_slice => '\u{00224B}', + "apos".to_slice => '\u{000027}', + "approx".to_slice => '\u{002248}', + "approxeq".to_slice => '\u{00224A}', + "aring".to_slice => '\u{0000E5}', + "ascr".to_slice => '\u{01D4B6}', + "ast".to_slice => '\u{00002A}', + "asymp".to_slice => '\u{002248}', + "asympeq".to_slice => '\u{00224D}', + "atilde".to_slice => '\u{0000E3}', + "auml".to_slice => '\u{0000E4}', + "awconint".to_slice => '\u{002233}', + "awint".to_slice => '\u{002A11}', + "bNot".to_slice => '\u{002AED}', + "backcong".to_slice => '\u{00224C}', + "backepsilon".to_slice => '\u{0003F6}', + "backprime".to_slice => '\u{002035}', + "backsim".to_slice => '\u{00223D}', + "backsimeq".to_slice => '\u{0022CD}', + "barvee".to_slice => '\u{0022BD}', + "barwed".to_slice => '\u{002305}', + "barwedge".to_slice => '\u{002305}', + "bbrk".to_slice => '\u{0023B5}', + "bbrktbrk".to_slice => '\u{0023B6}', + "bcong".to_slice => '\u{00224C}', + "bcy".to_slice => '\u{000431}', + "bdquo".to_slice => '\u{00201E}', + "becaus".to_slice => '\u{002235}', + "because".to_slice => '\u{002235}', + "bemptyv".to_slice => '\u{0029B0}', + "bepsi".to_slice => '\u{0003F6}', + "bernou".to_slice => '\u{00212C}', + "beta".to_slice => '\u{0003B2}', + "beth".to_slice => '\u{002136}', + "between".to_slice => '\u{00226C}', + "bfr".to_slice => '\u{01D51F}', + "bigcap".to_slice => '\u{0022C2}', + "bigcirc".to_slice => '\u{0025EF}', + "bigcup".to_slice => '\u{0022C3}', + "bigodot".to_slice => '\u{002A00}', + "bigoplus".to_slice => '\u{002A01}', + "bigotimes".to_slice => '\u{002A02}', + "bigsqcup".to_slice => '\u{002A06}', + "bigstar".to_slice => '\u{002605}', + "bigtriangledown".to_slice => '\u{0025BD}', + "bigtriangleup".to_slice => '\u{0025B3}', + "biguplus".to_slice => '\u{002A04}', + "bigvee".to_slice => '\u{0022C1}', + "bigwedge".to_slice => '\u{0022C0}', + "bkarow".to_slice => '\u{00290D}', + "blacklozenge".to_slice => '\u{0029EB}', + "blacksquare".to_slice => '\u{0025AA}', + "blacktriangle".to_slice => '\u{0025B4}', + "blacktriangledown".to_slice => '\u{0025BE}', + "blacktriangleleft".to_slice => '\u{0025C2}', + "blacktriangleright".to_slice => '\u{0025B8}', + "blank".to_slice => '\u{002423}', + "blk12".to_slice => '\u{002592}', + "blk14".to_slice => '\u{002591}', + "blk34".to_slice => '\u{002593}', + "block".to_slice => '\u{002588}', + "bnot".to_slice => '\u{002310}', + "bopf".to_slice => '\u{01D553}', + "bot".to_slice => '\u{0022A5}', + "bottom".to_slice => '\u{0022A5}', + "bowtie".to_slice => '\u{0022C8}', + "boxDL".to_slice => '\u{002557}', + "boxDR".to_slice => '\u{002554}', + "boxDl".to_slice => '\u{002556}', + "boxDr".to_slice => '\u{002553}', + "boxH".to_slice => '\u{002550}', + "boxHD".to_slice => '\u{002566}', + "boxHU".to_slice => '\u{002569}', + "boxHd".to_slice => '\u{002564}', + "boxHu".to_slice => '\u{002567}', + "boxUL".to_slice => '\u{00255D}', + "boxUR".to_slice => '\u{00255A}', + "boxUl".to_slice => '\u{00255C}', + "boxUr".to_slice => '\u{002559}', + "boxV".to_slice => '\u{002551}', + "boxVH".to_slice => '\u{00256C}', + "boxVL".to_slice => '\u{002563}', + "boxVR".to_slice => '\u{002560}', + "boxVh".to_slice => '\u{00256B}', + "boxVl".to_slice => '\u{002562}', + "boxVr".to_slice => '\u{00255F}', + "boxbox".to_slice => '\u{0029C9}', + "boxdL".to_slice => '\u{002555}', + "boxdR".to_slice => '\u{002552}', + "boxdl".to_slice => '\u{002510}', + "boxdr".to_slice => '\u{00250C}', + "boxh".to_slice => '\u{002500}', + "boxhD".to_slice => '\u{002565}', + "boxhU".to_slice => '\u{002568}', + "boxhd".to_slice => '\u{00252C}', + "boxhu".to_slice => '\u{002534}', + "boxminus".to_slice => '\u{00229F}', + "boxplus".to_slice => '\u{00229E}', + "boxtimes".to_slice => '\u{0022A0}', + "boxuL".to_slice => '\u{00255B}', + "boxuR".to_slice => '\u{002558}', + "boxul".to_slice => '\u{002518}', + "boxur".to_slice => '\u{002514}', + "boxv".to_slice => '\u{002502}', + "boxvH".to_slice => '\u{00256A}', + "boxvL".to_slice => '\u{002561}', + "boxvR".to_slice => '\u{00255E}', + "boxvh".to_slice => '\u{00253C}', + "boxvl".to_slice => '\u{002524}', + "boxvr".to_slice => '\u{00251C}', + "bprime".to_slice => '\u{002035}', + "breve".to_slice => '\u{0002D8}', + "brvbar".to_slice => '\u{0000A6}', + "bscr".to_slice => '\u{01D4B7}', + "bsemi".to_slice => '\u{00204F}', + "bsim".to_slice => '\u{00223D}', + "bsime".to_slice => '\u{0022CD}', + "bsol".to_slice => '\u{00005C}', + "bsolb".to_slice => '\u{0029C5}', + "bsolhsub".to_slice => '\u{0027C8}', + "bull".to_slice => '\u{002022}', + "bullet".to_slice => '\u{002022}', + "bump".to_slice => '\u{00224E}', + "bumpE".to_slice => '\u{002AAE}', + "bumpe".to_slice => '\u{00224F}', + "bumpeq".to_slice => '\u{00224F}', + "cacute".to_slice => '\u{000107}', + "cap".to_slice => '\u{002229}', + "capand".to_slice => '\u{002A44}', + "capbrcup".to_slice => '\u{002A49}', + "capcap".to_slice => '\u{002A4B}', + "capcup".to_slice => '\u{002A47}', + "capdot".to_slice => '\u{002A40}', + "caret".to_slice => '\u{002041}', + "caron".to_slice => '\u{0002C7}', + "ccaps".to_slice => '\u{002A4D}', + "ccaron".to_slice => '\u{00010D}', + "ccedil".to_slice => '\u{0000E7}', + "ccirc".to_slice => '\u{000109}', + "ccups".to_slice => '\u{002A4C}', + "ccupssm".to_slice => '\u{002A50}', + "cdot".to_slice => '\u{00010B}', + "cedil".to_slice => '\u{0000B8}', + "cemptyv".to_slice => '\u{0029B2}', + "cent".to_slice => '\u{0000A2}', + "centerdot".to_slice => '\u{0000B7}', + "cfr".to_slice => '\u{01D520}', + "chcy".to_slice => '\u{000447}', + "check".to_slice => '\u{002713}', + "checkmark".to_slice => '\u{002713}', + "chi".to_slice => '\u{0003C7}', + "cir".to_slice => '\u{0025CB}', + "cirE".to_slice => '\u{0029C3}', + "circ".to_slice => '\u{0002C6}', + "circeq".to_slice => '\u{002257}', + "circlearrowleft".to_slice => '\u{0021BA}', + "circlearrowright".to_slice => '\u{0021BB}', + "circledR".to_slice => '\u{0000AE}', + "circledS".to_slice => '\u{0024C8}', + "circledast".to_slice => '\u{00229B}', + "circledcirc".to_slice => '\u{00229A}', + "circleddash".to_slice => '\u{00229D}', + "cire".to_slice => '\u{002257}', + "cirfnint".to_slice => '\u{002A10}', + "cirmid".to_slice => '\u{002AEF}', + "cirscir".to_slice => '\u{0029C2}', + "clubs".to_slice => '\u{002663}', + "clubsuit".to_slice => '\u{002663}', + "colon".to_slice => '\u{00003A}', + "colone".to_slice => '\u{002254}', + "coloneq".to_slice => '\u{002254}', + "comma".to_slice => '\u{00002C}', + "commat".to_slice => '\u{000040}', + "comp".to_slice => '\u{002201}', + "compfn".to_slice => '\u{002218}', + "complement".to_slice => '\u{002201}', + "complexes".to_slice => '\u{002102}', + "cong".to_slice => '\u{002245}', + "congdot".to_slice => '\u{002A6D}', + "conint".to_slice => '\u{00222E}', + "copf".to_slice => '\u{01D554}', + "coprod".to_slice => '\u{002210}', + "copy".to_slice => '\u{0000A9}', + "copysr".to_slice => '\u{002117}', + "crarr".to_slice => '\u{0021B5}', + "cross".to_slice => '\u{002717}', + "cscr".to_slice => '\u{01D4B8}', + "csub".to_slice => '\u{002ACF}', + "csube".to_slice => '\u{002AD1}', + "csup".to_slice => '\u{002AD0}', + "csupe".to_slice => '\u{002AD2}', + "ctdot".to_slice => '\u{0022EF}', + "cudarrl".to_slice => '\u{002938}', + "cudarrr".to_slice => '\u{002935}', + "cuepr".to_slice => '\u{0022DE}', + "cuesc".to_slice => '\u{0022DF}', + "cularr".to_slice => '\u{0021B6}', + "cularrp".to_slice => '\u{00293D}', + "cup".to_slice => '\u{00222A}', + "cupbrcap".to_slice => '\u{002A48}', + "cupcap".to_slice => '\u{002A46}', + "cupcup".to_slice => '\u{002A4A}', + "cupdot".to_slice => '\u{00228D}', + "cupor".to_slice => '\u{002A45}', + "curarr".to_slice => '\u{0021B7}', + "curarrm".to_slice => '\u{00293C}', + "curlyeqprec".to_slice => '\u{0022DE}', + "curlyeqsucc".to_slice => '\u{0022DF}', + "curlyvee".to_slice => '\u{0022CE}', + "curlywedge".to_slice => '\u{0022CF}', + "curren".to_slice => '\u{0000A4}', + "curvearrowleft".to_slice => '\u{0021B6}', + "curvearrowright".to_slice => '\u{0021B7}', + "cuvee".to_slice => '\u{0022CE}', + "cuwed".to_slice => '\u{0022CF}', + "cwconint".to_slice => '\u{002232}', + "cwint".to_slice => '\u{002231}', + "cylcty".to_slice => '\u{00232D}', + "dArr".to_slice => '\u{0021D3}', + "dHar".to_slice => '\u{002965}', + "dagger".to_slice => '\u{002020}', + "daleth".to_slice => '\u{002138}', + "darr".to_slice => '\u{002193}', + "dash".to_slice => '\u{002010}', + "dashv".to_slice => '\u{0022A3}', + "dbkarow".to_slice => '\u{00290F}', + "dblac".to_slice => '\u{0002DD}', + "dcaron".to_slice => '\u{00010F}', + "dcy".to_slice => '\u{000434}', + "dd".to_slice => '\u{002146}', + "ddagger".to_slice => '\u{002021}', + "ddarr".to_slice => '\u{0021CA}', + "ddotseq".to_slice => '\u{002A77}', + "deg".to_slice => '\u{0000B0}', + "delta".to_slice => '\u{0003B4}', + "demptyv".to_slice => '\u{0029B1}', + "dfisht".to_slice => '\u{00297F}', + "dfr".to_slice => '\u{01D521}', + "dharl".to_slice => '\u{0021C3}', + "dharr".to_slice => '\u{0021C2}', + "diam".to_slice => '\u{0022C4}', + "diamond".to_slice => '\u{0022C4}', + "diamondsuit".to_slice => '\u{002666}', + "diams".to_slice => '\u{002666}', + "die".to_slice => '\u{0000A8}', + "digamma".to_slice => '\u{0003DD}', + "disin".to_slice => '\u{0022F2}', + "div".to_slice => '\u{0000F7}', + "divide".to_slice => '\u{0000F7}', + "divideontimes".to_slice => '\u{0022C7}', + "divonx".to_slice => '\u{0022C7}', + "djcy".to_slice => '\u{000452}', + "dlcorn".to_slice => '\u{00231E}', + "dlcrop".to_slice => '\u{00230D}', + "dollar".to_slice => '\u{000024}', + "dopf".to_slice => '\u{01D555}', + "dot".to_slice => '\u{0002D9}', + "doteq".to_slice => '\u{002250}', + "doteqdot".to_slice => '\u{002251}', + "dotminus".to_slice => '\u{002238}', + "dotplus".to_slice => '\u{002214}', + "dotsquare".to_slice => '\u{0022A1}', + "doublebarwedge".to_slice => '\u{002306}', + "downarrow".to_slice => '\u{002193}', + "downdownarrows".to_slice => '\u{0021CA}', + "downharpoonleft".to_slice => '\u{0021C3}', + "downharpoonright".to_slice => '\u{0021C2}', + "drbkarow".to_slice => '\u{002910}', + "drcorn".to_slice => '\u{00231F}', + "drcrop".to_slice => '\u{00230C}', + "dscr".to_slice => '\u{01D4B9}', + "dscy".to_slice => '\u{000455}', + "dsol".to_slice => '\u{0029F6}', + "dstrok".to_slice => '\u{000111}', + "dtdot".to_slice => '\u{0022F1}', + "dtri".to_slice => '\u{0025BF}', + "dtrif".to_slice => '\u{0025BE}', + "duarr".to_slice => '\u{0021F5}', + "duhar".to_slice => '\u{00296F}', + "dwangle".to_slice => '\u{0029A6}', + "dzcy".to_slice => '\u{00045F}', + "dzigrarr".to_slice => '\u{0027FF}', + "eDDot".to_slice => '\u{002A77}', + "eDot".to_slice => '\u{002251}', + "eacute".to_slice => '\u{0000E9}', + "easter".to_slice => '\u{002A6E}', + "ecaron".to_slice => '\u{00011B}', + "ecir".to_slice => '\u{002256}', + "ecirc".to_slice => '\u{0000EA}', + "ecolon".to_slice => '\u{002255}', + "ecy".to_slice => '\u{00044D}', + "edot".to_slice => '\u{000117}', + "ee".to_slice => '\u{002147}', + "efDot".to_slice => '\u{002252}', + "efr".to_slice => '\u{01D522}', + "eg".to_slice => '\u{002A9A}', + "egrave".to_slice => '\u{0000E8}', + "egs".to_slice => '\u{002A96}', + "egsdot".to_slice => '\u{002A98}', + "el".to_slice => '\u{002A99}', + "elinters".to_slice => '\u{0023E7}', + "ell".to_slice => '\u{002113}', + "els".to_slice => '\u{002A95}', + "elsdot".to_slice => '\u{002A97}', + "emacr".to_slice => '\u{000113}', + "empty".to_slice => '\u{002205}', + "emptyset".to_slice => '\u{002205}', + "emptyv".to_slice => '\u{002205}', + "emsp".to_slice => '\u{002003}', + "emsp13".to_slice => '\u{002004}', + "emsp14".to_slice => '\u{002005}', + "eng".to_slice => '\u{00014B}', + "ensp".to_slice => '\u{002002}', + "eogon".to_slice => '\u{000119}', + "eopf".to_slice => '\u{01D556}', + "epar".to_slice => '\u{0022D5}', + "eparsl".to_slice => '\u{0029E3}', + "eplus".to_slice => '\u{002A71}', + "epsi".to_slice => '\u{0003B5}', + "epsilon".to_slice => '\u{0003B5}', + "epsiv".to_slice => '\u{0003F5}', + "eqcirc".to_slice => '\u{002256}', + "eqcolon".to_slice => '\u{002255}', + "eqsim".to_slice => '\u{002242}', + "eqslantgtr".to_slice => '\u{002A96}', + "eqslantless".to_slice => '\u{002A95}', + "equals".to_slice => '\u{00003D}', + "equest".to_slice => '\u{00225F}', + "equiv".to_slice => '\u{002261}', + "equivDD".to_slice => '\u{002A78}', + "eqvparsl".to_slice => '\u{0029E5}', + "erDot".to_slice => '\u{002253}', + "erarr".to_slice => '\u{002971}', + "escr".to_slice => '\u{00212F}', + "esdot".to_slice => '\u{002250}', + "esim".to_slice => '\u{002242}', + "eta".to_slice => '\u{0003B7}', + "eth".to_slice => '\u{0000F0}', + "euml".to_slice => '\u{0000EB}', + "euro".to_slice => '\u{0020AC}', + "excl".to_slice => '\u{000021}', + "exist".to_slice => '\u{002203}', + "expectation".to_slice => '\u{002130}', + "exponentiale".to_slice => '\u{002147}', + "fallingdotseq".to_slice => '\u{002252}', + "fcy".to_slice => '\u{000444}', + "female".to_slice => '\u{002640}', + "ffilig".to_slice => '\u{00FB03}', + "fflig".to_slice => '\u{00FB00}', + "ffllig".to_slice => '\u{00FB04}', + "ffr".to_slice => '\u{01D523}', + "filig".to_slice => '\u{00FB01}', + "flat".to_slice => '\u{00266D}', + "fllig".to_slice => '\u{00FB02}', + "fltns".to_slice => '\u{0025B1}', + "fnof".to_slice => '\u{000192}', + "fopf".to_slice => '\u{01D557}', + "forall".to_slice => '\u{002200}', + "fork".to_slice => '\u{0022D4}', + "forkv".to_slice => '\u{002AD9}', + "fpartint".to_slice => '\u{002A0D}', + "frac12".to_slice => '\u{0000BD}', + "frac13".to_slice => '\u{002153}', + "frac14".to_slice => '\u{0000BC}', + "frac15".to_slice => '\u{002155}', + "frac16".to_slice => '\u{002159}', + "frac18".to_slice => '\u{00215B}', + "frac23".to_slice => '\u{002154}', + "frac25".to_slice => '\u{002156}', + "frac34".to_slice => '\u{0000BE}', + "frac35".to_slice => '\u{002157}', + "frac38".to_slice => '\u{00215C}', + "frac45".to_slice => '\u{002158}', + "frac56".to_slice => '\u{00215A}', + "frac58".to_slice => '\u{00215D}', + "frac78".to_slice => '\u{00215E}', + "frasl".to_slice => '\u{002044}', + "frown".to_slice => '\u{002322}', + "fscr".to_slice => '\u{01D4BB}', + "gE".to_slice => '\u{002267}', + "gEl".to_slice => '\u{002A8C}', + "gacute".to_slice => '\u{0001F5}', + "gamma".to_slice => '\u{0003B3}', + "gammad".to_slice => '\u{0003DD}', + "gap".to_slice => '\u{002A86}', + "gbreve".to_slice => '\u{00011F}', + "gcirc".to_slice => '\u{00011D}', + "gcy".to_slice => '\u{000433}', + "gdot".to_slice => '\u{000121}', + "ge".to_slice => '\u{002265}', + "gel".to_slice => '\u{0022DB}', + "geq".to_slice => '\u{002265}', + "geqq".to_slice => '\u{002267}', + "geqslant".to_slice => '\u{002A7E}', + "ges".to_slice => '\u{002A7E}', + "gescc".to_slice => '\u{002AA9}', + "gesdot".to_slice => '\u{002A80}', + "gesdoto".to_slice => '\u{002A82}', + "gesdotol".to_slice => '\u{002A84}', + "gesles".to_slice => '\u{002A94}', + "gfr".to_slice => '\u{01D524}', + "gg".to_slice => '\u{00226B}', + "ggg".to_slice => '\u{0022D9}', + "gimel".to_slice => '\u{002137}', + "gjcy".to_slice => '\u{000453}', + "gl".to_slice => '\u{002277}', + "glE".to_slice => '\u{002A92}', + "gla".to_slice => '\u{002AA5}', + "glj".to_slice => '\u{002AA4}', + "gnE".to_slice => '\u{002269}', + "gnap".to_slice => '\u{002A8A}', + "gnapprox".to_slice => '\u{002A8A}', + "gne".to_slice => '\u{002A88}', + "gneq".to_slice => '\u{002A88}', + "gneqq".to_slice => '\u{002269}', + "gnsim".to_slice => '\u{0022E7}', + "gopf".to_slice => '\u{01D558}', + "grave".to_slice => '\u{000060}', + "gscr".to_slice => '\u{00210A}', + "gsim".to_slice => '\u{002273}', + "gsime".to_slice => '\u{002A8E}', + "gsiml".to_slice => '\u{002A90}', + "gt".to_slice => '\u{00003E}', + "gtcc".to_slice => '\u{002AA7}', + "gtcir".to_slice => '\u{002A7A}', + "gtdot".to_slice => '\u{0022D7}', + "gtlPar".to_slice => '\u{002995}', + "gtquest".to_slice => '\u{002A7C}', + "gtrapprox".to_slice => '\u{002A86}', + "gtrarr".to_slice => '\u{002978}', + "gtrdot".to_slice => '\u{0022D7}', + "gtreqless".to_slice => '\u{0022DB}', + "gtreqqless".to_slice => '\u{002A8C}', + "gtrless".to_slice => '\u{002277}', + "gtrsim".to_slice => '\u{002273}', + "hArr".to_slice => '\u{0021D4}', + "hairsp".to_slice => '\u{00200A}', + "half".to_slice => '\u{0000BD}', + "hamilt".to_slice => '\u{00210B}', + "hardcy".to_slice => '\u{00044A}', + "harr".to_slice => '\u{002194}', + "harrcir".to_slice => '\u{002948}', + "harrw".to_slice => '\u{0021AD}', + "hbar".to_slice => '\u{00210F}', + "hcirc".to_slice => '\u{000125}', + "hearts".to_slice => '\u{002665}', + "heartsuit".to_slice => '\u{002665}', + "hellip".to_slice => '\u{002026}', + "hercon".to_slice => '\u{0022B9}', + "hfr".to_slice => '\u{01D525}', + "hksearow".to_slice => '\u{002925}', + "hkswarow".to_slice => '\u{002926}', + "hoarr".to_slice => '\u{0021FF}', + "homtht".to_slice => '\u{00223B}', + "hookleftarrow".to_slice => '\u{0021A9}', + "hookrightarrow".to_slice => '\u{0021AA}', + "hopf".to_slice => '\u{01D559}', + "horbar".to_slice => '\u{002015}', + "hscr".to_slice => '\u{01D4BD}', + "hslash".to_slice => '\u{00210F}', + "hstrok".to_slice => '\u{000127}', + "hybull".to_slice => '\u{002043}', + "hyphen".to_slice => '\u{002010}', + "iacute".to_slice => '\u{0000ED}', + "ic".to_slice => '\u{002063}', + "icirc".to_slice => '\u{0000EE}', + "icy".to_slice => '\u{000438}', + "iecy".to_slice => '\u{000435}', + "iexcl".to_slice => '\u{0000A1}', + "iff".to_slice => '\u{0021D4}', + "ifr".to_slice => '\u{01D526}', + "igrave".to_slice => '\u{0000EC}', + "ii".to_slice => '\u{002148}', + "iiiint".to_slice => '\u{002A0C}', + "iiint".to_slice => '\u{00222D}', + "iinfin".to_slice => '\u{0029DC}', + "iiota".to_slice => '\u{002129}', + "ijlig".to_slice => '\u{000133}', + "imacr".to_slice => '\u{00012B}', + "image".to_slice => '\u{002111}', + "imagline".to_slice => '\u{002110}', + "imagpart".to_slice => '\u{002111}', + "imath".to_slice => '\u{000131}', + "imof".to_slice => '\u{0022B7}', + "imped".to_slice => '\u{0001B5}', + "in".to_slice => '\u{002208}', + "incare".to_slice => '\u{002105}', + "infin".to_slice => '\u{00221E}', + "infintie".to_slice => '\u{0029DD}', + "inodot".to_slice => '\u{000131}', + "int".to_slice => '\u{00222B}', + "intcal".to_slice => '\u{0022BA}', + "integers".to_slice => '\u{002124}', + "intercal".to_slice => '\u{0022BA}', + "intlarhk".to_slice => '\u{002A17}', + "intprod".to_slice => '\u{002A3C}', + "iocy".to_slice => '\u{000451}', + "iogon".to_slice => '\u{00012F}', + "iopf".to_slice => '\u{01D55A}', + "iota".to_slice => '\u{0003B9}', + "iprod".to_slice => '\u{002A3C}', + "iquest".to_slice => '\u{0000BF}', + "iscr".to_slice => '\u{01D4BE}', + "isin".to_slice => '\u{002208}', + "isinE".to_slice => '\u{0022F9}', + "isindot".to_slice => '\u{0022F5}', + "isins".to_slice => '\u{0022F4}', + "isinsv".to_slice => '\u{0022F3}', + "isinv".to_slice => '\u{002208}', + "it".to_slice => '\u{002062}', + "itilde".to_slice => '\u{000129}', + "iukcy".to_slice => '\u{000456}', + "iuml".to_slice => '\u{0000EF}', + "jcirc".to_slice => '\u{000135}', + "jcy".to_slice => '\u{000439}', + "jfr".to_slice => '\u{01D527}', + "jmath".to_slice => '\u{000237}', + "jopf".to_slice => '\u{01D55B}', + "jscr".to_slice => '\u{01D4BF}', + "jsercy".to_slice => '\u{000458}', + "jukcy".to_slice => '\u{000454}', + "kappa".to_slice => '\u{0003BA}', + "kappav".to_slice => '\u{0003F0}', + "kcedil".to_slice => '\u{000137}', + "kcy".to_slice => '\u{00043A}', + "kfr".to_slice => '\u{01D528}', + "kgreen".to_slice => '\u{000138}', + "khcy".to_slice => '\u{000445}', + "kjcy".to_slice => '\u{00045C}', + "kopf".to_slice => '\u{01D55C}', + "kscr".to_slice => '\u{01D4C0}', + "lAarr".to_slice => '\u{0021DA}', + "lArr".to_slice => '\u{0021D0}', + "lAtail".to_slice => '\u{00291B}', + "lBarr".to_slice => '\u{00290E}', + "lE".to_slice => '\u{002266}', + "lEg".to_slice => '\u{002A8B}', + "lHar".to_slice => '\u{002962}', + "lacute".to_slice => '\u{00013A}', + "laemptyv".to_slice => '\u{0029B4}', + "lagran".to_slice => '\u{002112}', + "lambda".to_slice => '\u{0003BB}', + "lang".to_slice => '\u{0027E8}', + "langd".to_slice => '\u{002991}', + "langle".to_slice => '\u{0027E8}', + "lap".to_slice => '\u{002A85}', + "laquo".to_slice => '\u{0000AB}', + "larr".to_slice => '\u{002190}', + "larrb".to_slice => '\u{0021E4}', + "larrbfs".to_slice => '\u{00291F}', + "larrfs".to_slice => '\u{00291D}', + "larrhk".to_slice => '\u{0021A9}', + "larrlp".to_slice => '\u{0021AB}', + "larrpl".to_slice => '\u{002939}', + "larrsim".to_slice => '\u{002973}', + "larrtl".to_slice => '\u{0021A2}', + "lat".to_slice => '\u{002AAB}', + "latail".to_slice => '\u{002919}', + "late".to_slice => '\u{002AAD}', + "lbarr".to_slice => '\u{00290C}', + "lbbrk".to_slice => '\u{002772}', + "lbrace".to_slice => '\u{00007B}', + "lbrack".to_slice => '\u{00005B}', + "lbrke".to_slice => '\u{00298B}', + "lbrksld".to_slice => '\u{00298F}', + "lbrkslu".to_slice => '\u{00298D}', + "lcaron".to_slice => '\u{00013E}', + "lcedil".to_slice => '\u{00013C}', + "lceil".to_slice => '\u{002308}', + "lcub".to_slice => '\u{00007B}', + "lcy".to_slice => '\u{00043B}', + "ldca".to_slice => '\u{002936}', + "ldquo".to_slice => '\u{00201C}', + "ldquor".to_slice => '\u{00201E}', + "ldrdhar".to_slice => '\u{002967}', + "ldrushar".to_slice => '\u{00294B}', + "ldsh".to_slice => '\u{0021B2}', + "le".to_slice => '\u{002264}', + "leftarrow".to_slice => '\u{002190}', + "leftarrowtail".to_slice => '\u{0021A2}', + "leftharpoondown".to_slice => '\u{0021BD}', + "leftharpoonup".to_slice => '\u{0021BC}', + "leftleftarrows".to_slice => '\u{0021C7}', + "leftrightarrow".to_slice => '\u{002194}', + "leftrightarrows".to_slice => '\u{0021C6}', + "leftrightharpoons".to_slice => '\u{0021CB}', + "leftrightsquigarrow".to_slice => '\u{0021AD}', + "leftthreetimes".to_slice => '\u{0022CB}', + "leg".to_slice => '\u{0022DA}', + "leq".to_slice => '\u{002264}', + "leqq".to_slice => '\u{002266}', + "leqslant".to_slice => '\u{002A7D}', + "les".to_slice => '\u{002A7D}', + "lescc".to_slice => '\u{002AA8}', + "lesdot".to_slice => '\u{002A7F}', + "lesdoto".to_slice => '\u{002A81}', + "lesdotor".to_slice => '\u{002A83}', + "lesges".to_slice => '\u{002A93}', + "lessapprox".to_slice => '\u{002A85}', + "lessdot".to_slice => '\u{0022D6}', + "lesseqgtr".to_slice => '\u{0022DA}', + "lesseqqgtr".to_slice => '\u{002A8B}', + "lessgtr".to_slice => '\u{002276}', + "lesssim".to_slice => '\u{002272}', + "lfisht".to_slice => '\u{00297C}', + "lfloor".to_slice => '\u{00230A}', + "lfr".to_slice => '\u{01D529}', + "lg".to_slice => '\u{002276}', + "lgE".to_slice => '\u{002A91}', + "lhard".to_slice => '\u{0021BD}', + "lharu".to_slice => '\u{0021BC}', + "lharul".to_slice => '\u{00296A}', + "lhblk".to_slice => '\u{002584}', + "ljcy".to_slice => '\u{000459}', + "ll".to_slice => '\u{00226A}', + "llarr".to_slice => '\u{0021C7}', + "llcorner".to_slice => '\u{00231E}', + "llhard".to_slice => '\u{00296B}', + "lltri".to_slice => '\u{0025FA}', + "lmidot".to_slice => '\u{000140}', + "lmoust".to_slice => '\u{0023B0}', + "lmoustache".to_slice => '\u{0023B0}', + "lnE".to_slice => '\u{002268}', + "lnap".to_slice => '\u{002A89}', + "lnapprox".to_slice => '\u{002A89}', + "lne".to_slice => '\u{002A87}', + "lneq".to_slice => '\u{002A87}', + "lneqq".to_slice => '\u{002268}', + "lnsim".to_slice => '\u{0022E6}', + "loang".to_slice => '\u{0027EC}', + "loarr".to_slice => '\u{0021FD}', + "lobrk".to_slice => '\u{0027E6}', + "longleftarrow".to_slice => '\u{0027F5}', + "longleftrightarrow".to_slice => '\u{0027F7}', + "longmapsto".to_slice => '\u{0027FC}', + "longrightarrow".to_slice => '\u{0027F6}', + "looparrowleft".to_slice => '\u{0021AB}', + "looparrowright".to_slice => '\u{0021AC}', + "lopar".to_slice => '\u{002985}', + "lopf".to_slice => '\u{01D55D}', + "loplus".to_slice => '\u{002A2D}', + "lotimes".to_slice => '\u{002A34}', + "lowast".to_slice => '\u{002217}', + "lowbar".to_slice => '\u{00005F}', + "loz".to_slice => '\u{0025CA}', + "lozenge".to_slice => '\u{0025CA}', + "lozf".to_slice => '\u{0029EB}', + "lpar".to_slice => '\u{000028}', + "lparlt".to_slice => '\u{002993}', + "lrarr".to_slice => '\u{0021C6}', + "lrcorner".to_slice => '\u{00231F}', + "lrhar".to_slice => '\u{0021CB}', + "lrhard".to_slice => '\u{00296D}', + "lrm".to_slice => '\u{00200E}', + "lrtri".to_slice => '\u{0022BF}', + "lsaquo".to_slice => '\u{002039}', + "lscr".to_slice => '\u{01D4C1}', + "lsh".to_slice => '\u{0021B0}', + "lsim".to_slice => '\u{002272}', + "lsime".to_slice => '\u{002A8D}', + "lsimg".to_slice => '\u{002A8F}', + "lsqb".to_slice => '\u{00005B}', + "lsquo".to_slice => '\u{002018}', + "lsquor".to_slice => '\u{00201A}', + "lstrok".to_slice => '\u{000142}', + "lt".to_slice => '\u{00003C}', + "ltcc".to_slice => '\u{002AA6}', + "ltcir".to_slice => '\u{002A79}', + "ltdot".to_slice => '\u{0022D6}', + "lthree".to_slice => '\u{0022CB}', + "ltimes".to_slice => '\u{0022C9}', + "ltlarr".to_slice => '\u{002976}', + "ltquest".to_slice => '\u{002A7B}', + "ltrPar".to_slice => '\u{002996}', + "ltri".to_slice => '\u{0025C3}', + "ltrie".to_slice => '\u{0022B4}', + "ltrif".to_slice => '\u{0025C2}', + "lurdshar".to_slice => '\u{00294A}', + "luruhar".to_slice => '\u{002966}', + "mDDot".to_slice => '\u{00223A}', + "macr".to_slice => '\u{0000AF}', + "male".to_slice => '\u{002642}', + "malt".to_slice => '\u{002720}', + "maltese".to_slice => '\u{002720}', + "map".to_slice => '\u{0021A6}', + "mapsto".to_slice => '\u{0021A6}', + "mapstodown".to_slice => '\u{0021A7}', + "mapstoleft".to_slice => '\u{0021A4}', + "mapstoup".to_slice => '\u{0021A5}', + "marker".to_slice => '\u{0025AE}', + "mcomma".to_slice => '\u{002A29}', + "mcy".to_slice => '\u{00043C}', + "mdash".to_slice => '\u{002014}', + "measuredangle".to_slice => '\u{002221}', + "mfr".to_slice => '\u{01D52A}', + "mho".to_slice => '\u{002127}', + "micro".to_slice => '\u{0000B5}', + "mid".to_slice => '\u{002223}', + "midast".to_slice => '\u{00002A}', + "midcir".to_slice => '\u{002AF0}', + "middot".to_slice => '\u{0000B7}', + "minus".to_slice => '\u{002212}', + "minusb".to_slice => '\u{00229F}', + "minusd".to_slice => '\u{002238}', + "minusdu".to_slice => '\u{002A2A}', + "mlcp".to_slice => '\u{002ADB}', + "mldr".to_slice => '\u{002026}', + "mnplus".to_slice => '\u{002213}', + "models".to_slice => '\u{0022A7}', + "mopf".to_slice => '\u{01D55E}', + "mp".to_slice => '\u{002213}', + "mscr".to_slice => '\u{01D4C2}', + "mstpos".to_slice => '\u{00223E}', + "mu".to_slice => '\u{0003BC}', + "multimap".to_slice => '\u{0022B8}', + "mumap".to_slice => '\u{0022B8}', + "nLeftarrow".to_slice => '\u{0021CD}', + "nLeftrightarrow".to_slice => '\u{0021CE}', + "nRightarrow".to_slice => '\u{0021CF}', + "nVDash".to_slice => '\u{0022AF}', + "nVdash".to_slice => '\u{0022AE}', + "nabla".to_slice => '\u{002207}', + "nacute".to_slice => '\u{000144}', + "nap".to_slice => '\u{002249}', + "napos".to_slice => '\u{000149}', + "napprox".to_slice => '\u{002249}', + "natur".to_slice => '\u{00266E}', + "natural".to_slice => '\u{00266E}', + "naturals".to_slice => '\u{002115}', + "nbsp".to_slice => '\u{0000A0}', + "ncap".to_slice => '\u{002A43}', + "ncaron".to_slice => '\u{000148}', + "ncedil".to_slice => '\u{000146}', + "ncong".to_slice => '\u{002247}', + "ncup".to_slice => '\u{002A42}', + "ncy".to_slice => '\u{00043D}', + "ndash".to_slice => '\u{002013}', + "ne".to_slice => '\u{002260}', + "neArr".to_slice => '\u{0021D7}', + "nearhk".to_slice => '\u{002924}', + "nearr".to_slice => '\u{002197}', + "nearrow".to_slice => '\u{002197}', + "nequiv".to_slice => '\u{002262}', + "nesear".to_slice => '\u{002928}', + "nexist".to_slice => '\u{002204}', + "nexists".to_slice => '\u{002204}', + "nfr".to_slice => '\u{01D52B}', + "nge".to_slice => '\u{002271}', + "ngeq".to_slice => '\u{002271}', + "ngsim".to_slice => '\u{002275}', + "ngt".to_slice => '\u{00226F}', + "ngtr".to_slice => '\u{00226F}', + "nhArr".to_slice => '\u{0021CE}', + "nharr".to_slice => '\u{0021AE}', + "nhpar".to_slice => '\u{002AF2}', + "ni".to_slice => '\u{00220B}', + "nis".to_slice => '\u{0022FC}', + "nisd".to_slice => '\u{0022FA}', + "niv".to_slice => '\u{00220B}', + "njcy".to_slice => '\u{00045A}', + "nlArr".to_slice => '\u{0021CD}', + "nlarr".to_slice => '\u{00219A}', + "nldr".to_slice => '\u{002025}', + "nle".to_slice => '\u{002270}', + "nleftarrow".to_slice => '\u{00219A}', + "nleftrightarrow".to_slice => '\u{0021AE}', + "nleq".to_slice => '\u{002270}', + "nless".to_slice => '\u{00226E}', + "nlsim".to_slice => '\u{002274}', + "nlt".to_slice => '\u{00226E}', + "nltri".to_slice => '\u{0022EA}', + "nltrie".to_slice => '\u{0022EC}', + "nmid".to_slice => '\u{002224}', + "nopf".to_slice => '\u{01D55F}', + "not".to_slice => '\u{0000AC}', + "notin".to_slice => '\u{002209}', + "notinva".to_slice => '\u{002209}', + "notinvb".to_slice => '\u{0022F7}', + "notinvc".to_slice => '\u{0022F6}', + "notni".to_slice => '\u{00220C}', + "notniva".to_slice => '\u{00220C}', + "notnivb".to_slice => '\u{0022FE}', + "notnivc".to_slice => '\u{0022FD}', + "npar".to_slice => '\u{002226}', + "nparallel".to_slice => '\u{002226}', + "npolint".to_slice => '\u{002A14}', + "npr".to_slice => '\u{002280}', + "nprcue".to_slice => '\u{0022E0}', + "nprec".to_slice => '\u{002280}', + "nrArr".to_slice => '\u{0021CF}', + "nrarr".to_slice => '\u{00219B}', + "nrightarrow".to_slice => '\u{00219B}', + "nrtri".to_slice => '\u{0022EB}', + "nrtrie".to_slice => '\u{0022ED}', + "nsc".to_slice => '\u{002281}', + "nsccue".to_slice => '\u{0022E1}', + "nscr".to_slice => '\u{01D4C3}', + "nshortmid".to_slice => '\u{002224}', + "nshortparallel".to_slice => '\u{002226}', + "nsim".to_slice => '\u{002241}', + "nsime".to_slice => '\u{002244}', + "nsimeq".to_slice => '\u{002244}', + "nsmid".to_slice => '\u{002224}', + "nspar".to_slice => '\u{002226}', + "nsqsube".to_slice => '\u{0022E2}', + "nsqsupe".to_slice => '\u{0022E3}', + "nsub".to_slice => '\u{002284}', + "nsube".to_slice => '\u{002288}', + "nsubseteq".to_slice => '\u{002288}', + "nsucc".to_slice => '\u{002281}', + "nsup".to_slice => '\u{002285}', + "nsupe".to_slice => '\u{002289}', + "nsupseteq".to_slice => '\u{002289}', + "ntgl".to_slice => '\u{002279}', + "ntilde".to_slice => '\u{0000F1}', + "ntlg".to_slice => '\u{002278}', + "ntriangleleft".to_slice => '\u{0022EA}', + "ntrianglelefteq".to_slice => '\u{0022EC}', + "ntriangleright".to_slice => '\u{0022EB}', + "ntrianglerighteq".to_slice => '\u{0022ED}', + "nu".to_slice => '\u{0003BD}', + "num".to_slice => '\u{000023}', + "numero".to_slice => '\u{002116}', + "numsp".to_slice => '\u{002007}', + "nvDash".to_slice => '\u{0022AD}', + "nvHarr".to_slice => '\u{002904}', + "nvdash".to_slice => '\u{0022AC}', + "nvinfin".to_slice => '\u{0029DE}', + "nvlArr".to_slice => '\u{002902}', + "nvrArr".to_slice => '\u{002903}', + "nwArr".to_slice => '\u{0021D6}', + "nwarhk".to_slice => '\u{002923}', + "nwarr".to_slice => '\u{002196}', + "nwarrow".to_slice => '\u{002196}', + "nwnear".to_slice => '\u{002927}', + "oS".to_slice => '\u{0024C8}', + "oacute".to_slice => '\u{0000F3}', + "oast".to_slice => '\u{00229B}', + "ocir".to_slice => '\u{00229A}', + "ocirc".to_slice => '\u{0000F4}', + "ocy".to_slice => '\u{00043E}', + "odash".to_slice => '\u{00229D}', + "odblac".to_slice => '\u{000151}', + "odiv".to_slice => '\u{002A38}', + "odot".to_slice => '\u{002299}', + "odsold".to_slice => '\u{0029BC}', + "oelig".to_slice => '\u{000153}', + "ofcir".to_slice => '\u{0029BF}', + "ofr".to_slice => '\u{01D52C}', + "ogon".to_slice => '\u{0002DB}', + "ograve".to_slice => '\u{0000F2}', + "ogt".to_slice => '\u{0029C1}', + "ohbar".to_slice => '\u{0029B5}', + "ohm".to_slice => '\u{0003A9}', + "oint".to_slice => '\u{00222E}', + "olarr".to_slice => '\u{0021BA}', + "olcir".to_slice => '\u{0029BE}', + "olcross".to_slice => '\u{0029BB}', + "oline".to_slice => '\u{00203E}', + "olt".to_slice => '\u{0029C0}', + "omacr".to_slice => '\u{00014D}', + "omega".to_slice => '\u{0003C9}', + "omicron".to_slice => '\u{0003BF}', + "omid".to_slice => '\u{0029B6}', + "ominus".to_slice => '\u{002296}', + "oopf".to_slice => '\u{01D560}', + "opar".to_slice => '\u{0029B7}', + "operp".to_slice => '\u{0029B9}', + "oplus".to_slice => '\u{002295}', + "or".to_slice => '\u{002228}', + "orarr".to_slice => '\u{0021BB}', + "ord".to_slice => '\u{002A5D}', + "order".to_slice => '\u{002134}', + "orderof".to_slice => '\u{002134}', + "ordf".to_slice => '\u{0000AA}', + "ordm".to_slice => '\u{0000BA}', + "origof".to_slice => '\u{0022B6}', + "oror".to_slice => '\u{002A56}', + "orslope".to_slice => '\u{002A57}', + "orv".to_slice => '\u{002A5B}', + "oscr".to_slice => '\u{002134}', + "oslash".to_slice => '\u{0000F8}', + "osol".to_slice => '\u{002298}', + "otilde".to_slice => '\u{0000F5}', + "otimes".to_slice => '\u{002297}', + "otimesas".to_slice => '\u{002A36}', + "ouml".to_slice => '\u{0000F6}', + "ovbar".to_slice => '\u{00233D}', + "par".to_slice => '\u{002225}', + "para".to_slice => '\u{0000B6}', + "parallel".to_slice => '\u{002225}', + "parsim".to_slice => '\u{002AF3}', + "parsl".to_slice => '\u{002AFD}', + "part".to_slice => '\u{002202}', + "pcy".to_slice => '\u{00043F}', + "percnt".to_slice => '\u{000025}', + "period".to_slice => '\u{00002E}', + "permil".to_slice => '\u{002030}', + "perp".to_slice => '\u{0022A5}', + "pertenk".to_slice => '\u{002031}', + "pfr".to_slice => '\u{01D52D}', + "phi".to_slice => '\u{0003C6}', + "phiv".to_slice => '\u{0003D5}', + "phmmat".to_slice => '\u{002133}', + "phone".to_slice => '\u{00260E}', + "pi".to_slice => '\u{0003C0}', + "pitchfork".to_slice => '\u{0022D4}', + "piv".to_slice => '\u{0003D6}', + "planck".to_slice => '\u{00210F}', + "planckh".to_slice => '\u{00210E}', + "plankv".to_slice => '\u{00210F}', + "plus".to_slice => '\u{00002B}', + "plusacir".to_slice => '\u{002A23}', + "plusb".to_slice => '\u{00229E}', + "pluscir".to_slice => '\u{002A22}', + "plusdo".to_slice => '\u{002214}', + "plusdu".to_slice => '\u{002A25}', + "pluse".to_slice => '\u{002A72}', + "plusmn".to_slice => '\u{0000B1}', + "plussim".to_slice => '\u{002A26}', + "plustwo".to_slice => '\u{002A27}', + "pm".to_slice => '\u{0000B1}', + "pointint".to_slice => '\u{002A15}', + "popf".to_slice => '\u{01D561}', + "pound".to_slice => '\u{0000A3}', + "pr".to_slice => '\u{00227A}', + "prE".to_slice => '\u{002AB3}', + "prap".to_slice => '\u{002AB7}', + "prcue".to_slice => '\u{00227C}', + "pre".to_slice => '\u{002AAF}', + "prec".to_slice => '\u{00227A}', + "precapprox".to_slice => '\u{002AB7}', + "preccurlyeq".to_slice => '\u{00227C}', + "preceq".to_slice => '\u{002AAF}', + "precnapprox".to_slice => '\u{002AB9}', + "precneqq".to_slice => '\u{002AB5}', + "precnsim".to_slice => '\u{0022E8}', + "precsim".to_slice => '\u{00227E}', + "prime".to_slice => '\u{002032}', + "primes".to_slice => '\u{002119}', + "prnE".to_slice => '\u{002AB5}', + "prnap".to_slice => '\u{002AB9}', + "prnsim".to_slice => '\u{0022E8}', + "prod".to_slice => '\u{00220F}', + "profalar".to_slice => '\u{00232E}', + "profline".to_slice => '\u{002312}', + "profsurf".to_slice => '\u{002313}', + "prop".to_slice => '\u{00221D}', + "propto".to_slice => '\u{00221D}', + "prsim".to_slice => '\u{00227E}', + "prurel".to_slice => '\u{0022B0}', + "pscr".to_slice => '\u{01D4C5}', + "psi".to_slice => '\u{0003C8}', + "puncsp".to_slice => '\u{002008}', + "qfr".to_slice => '\u{01D52E}', + "qint".to_slice => '\u{002A0C}', + "qopf".to_slice => '\u{01D562}', + "qprime".to_slice => '\u{002057}', + "qscr".to_slice => '\u{01D4C6}', + "quaternions".to_slice => '\u{00210D}', + "quatint".to_slice => '\u{002A16}', + "quest".to_slice => '\u{00003F}', + "questeq".to_slice => '\u{00225F}', + "quot".to_slice => '\u{000022}', + "rAarr".to_slice => '\u{0021DB}', + "rArr".to_slice => '\u{0021D2}', + "rAtail".to_slice => '\u{00291C}', + "rBarr".to_slice => '\u{00290F}', + "rHar".to_slice => '\u{002964}', + "racute".to_slice => '\u{000155}', + "radic".to_slice => '\u{00221A}', + "raemptyv".to_slice => '\u{0029B3}', + "rang".to_slice => '\u{0027E9}', + "rangd".to_slice => '\u{002992}', + "range".to_slice => '\u{0029A5}', + "rangle".to_slice => '\u{0027E9}', + "raquo".to_slice => '\u{0000BB}', + "rarr".to_slice => '\u{002192}', + "rarrap".to_slice => '\u{002975}', + "rarrb".to_slice => '\u{0021E5}', + "rarrbfs".to_slice => '\u{002920}', + "rarrc".to_slice => '\u{002933}', + "rarrfs".to_slice => '\u{00291E}', + "rarrhk".to_slice => '\u{0021AA}', + "rarrlp".to_slice => '\u{0021AC}', + "rarrpl".to_slice => '\u{002945}', + "rarrsim".to_slice => '\u{002974}', + "rarrtl".to_slice => '\u{0021A3}', + "rarrw".to_slice => '\u{00219D}', + "ratail".to_slice => '\u{00291A}', + "ratio".to_slice => '\u{002236}', + "rationals".to_slice => '\u{00211A}', + "rbarr".to_slice => '\u{00290D}', + "rbbrk".to_slice => '\u{002773}', + "rbrace".to_slice => '\u{00007D}', + "rbrack".to_slice => '\u{00005D}', + "rbrke".to_slice => '\u{00298C}', + "rbrksld".to_slice => '\u{00298E}', + "rbrkslu".to_slice => '\u{002990}', + "rcaron".to_slice => '\u{000159}', + "rcedil".to_slice => '\u{000157}', + "rceil".to_slice => '\u{002309}', + "rcub".to_slice => '\u{00007D}', + "rcy".to_slice => '\u{000440}', + "rdca".to_slice => '\u{002937}', + "rdldhar".to_slice => '\u{002969}', + "rdquo".to_slice => '\u{00201D}', + "rdquor".to_slice => '\u{00201D}', + "rdsh".to_slice => '\u{0021B3}', + "real".to_slice => '\u{00211C}', + "realine".to_slice => '\u{00211B}', + "realpart".to_slice => '\u{00211C}', + "reals".to_slice => '\u{00211D}', + "rect".to_slice => '\u{0025AD}', + "reg".to_slice => '\u{0000AE}', + "rfisht".to_slice => '\u{00297D}', + "rfloor".to_slice => '\u{00230B}', + "rfr".to_slice => '\u{01D52F}', + "rhard".to_slice => '\u{0021C1}', + "rharu".to_slice => '\u{0021C0}', + "rharul".to_slice => '\u{00296C}', + "rho".to_slice => '\u{0003C1}', + "rhov".to_slice => '\u{0003F1}', + "rightarrow".to_slice => '\u{002192}', + "rightarrowtail".to_slice => '\u{0021A3}', + "rightharpoondown".to_slice => '\u{0021C1}', + "rightharpoonup".to_slice => '\u{0021C0}', + "rightleftarrows".to_slice => '\u{0021C4}', + "rightleftharpoons".to_slice => '\u{0021CC}', + "rightrightarrows".to_slice => '\u{0021C9}', + "rightsquigarrow".to_slice => '\u{00219D}', + "rightthreetimes".to_slice => '\u{0022CC}', + "ring".to_slice => '\u{0002DA}', + "risingdotseq".to_slice => '\u{002253}', + "rlarr".to_slice => '\u{0021C4}', + "rlhar".to_slice => '\u{0021CC}', + "rlm".to_slice => '\u{00200F}', + "rmoust".to_slice => '\u{0023B1}', + "rmoustache".to_slice => '\u{0023B1}', + "rnmid".to_slice => '\u{002AEE}', + "roang".to_slice => '\u{0027ED}', + "roarr".to_slice => '\u{0021FE}', + "robrk".to_slice => '\u{0027E7}', + "ropar".to_slice => '\u{002986}', + "ropf".to_slice => '\u{01D563}', + "roplus".to_slice => '\u{002A2E}', + "rotimes".to_slice => '\u{002A35}', + "rpar".to_slice => '\u{000029}', + "rpargt".to_slice => '\u{002994}', + "rppolint".to_slice => '\u{002A12}', + "rrarr".to_slice => '\u{0021C9}', + "rsaquo".to_slice => '\u{00203A}', + "rscr".to_slice => '\u{01D4C7}', + "rsh".to_slice => '\u{0021B1}', + "rsqb".to_slice => '\u{00005D}', + "rsquo".to_slice => '\u{002019}', + "rsquor".to_slice => '\u{002019}', + "rthree".to_slice => '\u{0022CC}', + "rtimes".to_slice => '\u{0022CA}', + "rtri".to_slice => '\u{0025B9}', + "rtrie".to_slice => '\u{0022B5}', + "rtrif".to_slice => '\u{0025B8}', + "rtriltri".to_slice => '\u{0029CE}', + "ruluhar".to_slice => '\u{002968}', + "rx".to_slice => '\u{00211E}', + "sacute".to_slice => '\u{00015B}', + "sbquo".to_slice => '\u{00201A}', + "sc".to_slice => '\u{00227B}', + "scE".to_slice => '\u{002AB4}', + "scap".to_slice => '\u{002AB8}', + "scaron".to_slice => '\u{000161}', + "sccue".to_slice => '\u{00227D}', + "sce".to_slice => '\u{002AB0}', + "scedil".to_slice => '\u{00015F}', + "scirc".to_slice => '\u{00015D}', + "scnE".to_slice => '\u{002AB6}', + "scnap".to_slice => '\u{002ABA}', + "scnsim".to_slice => '\u{0022E9}', + "scpolint".to_slice => '\u{002A13}', + "scsim".to_slice => '\u{00227F}', + "scy".to_slice => '\u{000441}', + "sdot".to_slice => '\u{0022C5}', + "sdotb".to_slice => '\u{0022A1}', + "sdote".to_slice => '\u{002A66}', + "seArr".to_slice => '\u{0021D8}', + "searhk".to_slice => '\u{002925}', + "searr".to_slice => '\u{002198}', + "searrow".to_slice => '\u{002198}', + "sect".to_slice => '\u{0000A7}', + "semi".to_slice => '\u{00003B}', + "seswar".to_slice => '\u{002929}', + "setminus".to_slice => '\u{002216}', + "setmn".to_slice => '\u{002216}', + "sext".to_slice => '\u{002736}', + "sfr".to_slice => '\u{01D530}', + "sfrown".to_slice => '\u{002322}', + "sharp".to_slice => '\u{00266F}', + "shchcy".to_slice => '\u{000449}', + "shcy".to_slice => '\u{000448}', + "shortmid".to_slice => '\u{002223}', + "shortparallel".to_slice => '\u{002225}', + "shy".to_slice => '\u{0000AD}', + "sigma".to_slice => '\u{0003C3}', + "sigmaf".to_slice => '\u{0003C2}', + "sigmav".to_slice => '\u{0003C2}', + "sim".to_slice => '\u{00223C}', + "simdot".to_slice => '\u{002A6A}', + "sime".to_slice => '\u{002243}', + "simeq".to_slice => '\u{002243}', + "simg".to_slice => '\u{002A9E}', + "simgE".to_slice => '\u{002AA0}', + "siml".to_slice => '\u{002A9D}', + "simlE".to_slice => '\u{002A9F}', + "simne".to_slice => '\u{002246}', + "simplus".to_slice => '\u{002A24}', + "simrarr".to_slice => '\u{002972}', + "slarr".to_slice => '\u{002190}', + "smallsetminus".to_slice => '\u{002216}', + "smashp".to_slice => '\u{002A33}', + "smeparsl".to_slice => '\u{0029E4}', + "smid".to_slice => '\u{002223}', + "smile".to_slice => '\u{002323}', + "smt".to_slice => '\u{002AAA}', + "smte".to_slice => '\u{002AAC}', + "softcy".to_slice => '\u{00044C}', + "sol".to_slice => '\u{00002F}', + "solb".to_slice => '\u{0029C4}', + "solbar".to_slice => '\u{00233F}', + "sopf".to_slice => '\u{01D564}', + "spades".to_slice => '\u{002660}', + "spadesuit".to_slice => '\u{002660}', + "spar".to_slice => '\u{002225}', + "sqcap".to_slice => '\u{002293}', + "sqcup".to_slice => '\u{002294}', + "sqsub".to_slice => '\u{00228F}', + "sqsube".to_slice => '\u{002291}', + "sqsubset".to_slice => '\u{00228F}', + "sqsubseteq".to_slice => '\u{002291}', + "sqsup".to_slice => '\u{002290}', + "sqsupe".to_slice => '\u{002292}', + "sqsupset".to_slice => '\u{002290}', + "sqsupseteq".to_slice => '\u{002292}', + "squ".to_slice => '\u{0025A1}', + "square".to_slice => '\u{0025A1}', + "squarf".to_slice => '\u{0025AA}', + "squf".to_slice => '\u{0025AA}', + "srarr".to_slice => '\u{002192}', + "sscr".to_slice => '\u{01D4C8}', + "ssetmn".to_slice => '\u{002216}', + "ssmile".to_slice => '\u{002323}', + "sstarf".to_slice => '\u{0022C6}', + "star".to_slice => '\u{002606}', + "starf".to_slice => '\u{002605}', + "straightepsilon".to_slice => '\u{0003F5}', + "straightphi".to_slice => '\u{0003D5}', + "strns".to_slice => '\u{0000AF}', + "sub".to_slice => '\u{002282}', + "subE".to_slice => '\u{002AC5}', + "subdot".to_slice => '\u{002ABD}', + "sube".to_slice => '\u{002286}', + "subedot".to_slice => '\u{002AC3}', + "submult".to_slice => '\u{002AC1}', + "subnE".to_slice => '\u{002ACB}', + "subne".to_slice => '\u{00228A}', + "subplus".to_slice => '\u{002ABF}', + "subrarr".to_slice => '\u{002979}', + "subset".to_slice => '\u{002282}', + "subseteq".to_slice => '\u{002286}', + "subseteqq".to_slice => '\u{002AC5}', + "subsetneq".to_slice => '\u{00228A}', + "subsetneqq".to_slice => '\u{002ACB}', + "subsim".to_slice => '\u{002AC7}', + "subsub".to_slice => '\u{002AD5}', + "subsup".to_slice => '\u{002AD3}', + "succ".to_slice => '\u{00227B}', + "succapprox".to_slice => '\u{002AB8}', + "succcurlyeq".to_slice => '\u{00227D}', + "succeq".to_slice => '\u{002AB0}', + "succnapprox".to_slice => '\u{002ABA}', + "succneqq".to_slice => '\u{002AB6}', + "succnsim".to_slice => '\u{0022E9}', + "succsim".to_slice => '\u{00227F}', + "sum".to_slice => '\u{002211}', + "sung".to_slice => '\u{00266A}', + "sup".to_slice => '\u{002283}', + "sup1".to_slice => '\u{0000B9}', + "sup2".to_slice => '\u{0000B2}', + "sup3".to_slice => '\u{0000B3}', + "supE".to_slice => '\u{002AC6}', + "supdot".to_slice => '\u{002ABE}', + "supdsub".to_slice => '\u{002AD8}', + "supe".to_slice => '\u{002287}', + "supedot".to_slice => '\u{002AC4}', + "suphsol".to_slice => '\u{0027C9}', + "suphsub".to_slice => '\u{002AD7}', + "suplarr".to_slice => '\u{00297B}', + "supmult".to_slice => '\u{002AC2}', + "supnE".to_slice => '\u{002ACC}', + "supne".to_slice => '\u{00228B}', + "supplus".to_slice => '\u{002AC0}', + "supset".to_slice => '\u{002283}', + "supseteq".to_slice => '\u{002287}', + "supseteqq".to_slice => '\u{002AC6}', + "supsetneq".to_slice => '\u{00228B}', + "supsetneqq".to_slice => '\u{002ACC}', + "supsim".to_slice => '\u{002AC8}', + "supsub".to_slice => '\u{002AD4}', + "supsup".to_slice => '\u{002AD6}', + "swArr".to_slice => '\u{0021D9}', + "swarhk".to_slice => '\u{002926}', + "swarr".to_slice => '\u{002199}', + "swarrow".to_slice => '\u{002199}', + "swnwar".to_slice => '\u{00292A}', + "szlig".to_slice => '\u{0000DF}', + "target".to_slice => '\u{002316}', + "tau".to_slice => '\u{0003C4}', + "tbrk".to_slice => '\u{0023B4}', + "tcaron".to_slice => '\u{000165}', + "tcedil".to_slice => '\u{000163}', + "tcy".to_slice => '\u{000442}', + "tdot".to_slice => '\u{0020DB}', + "telrec".to_slice => '\u{002315}', + "tfr".to_slice => '\u{01D531}', + "there4".to_slice => '\u{002234}', + "therefore".to_slice => '\u{002234}', + "theta".to_slice => '\u{0003B8}', + "thetasym".to_slice => '\u{0003D1}', + "thetav".to_slice => '\u{0003D1}', + "thickapprox".to_slice => '\u{002248}', + "thicksim".to_slice => '\u{00223C}', + "thinsp".to_slice => '\u{002009}', + "thkap".to_slice => '\u{002248}', + "thksim".to_slice => '\u{00223C}', + "thorn".to_slice => '\u{0000FE}', + "tilde".to_slice => '\u{0002DC}', + "times".to_slice => '\u{0000D7}', + "timesb".to_slice => '\u{0022A0}', + "timesbar".to_slice => '\u{002A31}', + "timesd".to_slice => '\u{002A30}', + "tint".to_slice => '\u{00222D}', + "toea".to_slice => '\u{002928}', + "top".to_slice => '\u{0022A4}', + "topbot".to_slice => '\u{002336}', + "topcir".to_slice => '\u{002AF1}', + "topf".to_slice => '\u{01D565}', + "topfork".to_slice => '\u{002ADA}', + "tosa".to_slice => '\u{002929}', + "tprime".to_slice => '\u{002034}', + "trade".to_slice => '\u{002122}', + "triangle".to_slice => '\u{0025B5}', + "triangledown".to_slice => '\u{0025BF}', + "triangleleft".to_slice => '\u{0025C3}', + "trianglelefteq".to_slice => '\u{0022B4}', + "triangleq".to_slice => '\u{00225C}', + "triangleright".to_slice => '\u{0025B9}', + "trianglerighteq".to_slice => '\u{0022B5}', + "tridot".to_slice => '\u{0025EC}', + "trie".to_slice => '\u{00225C}', + "triminus".to_slice => '\u{002A3A}', + "triplus".to_slice => '\u{002A39}', + "trisb".to_slice => '\u{0029CD}', + "tritime".to_slice => '\u{002A3B}', + "trpezium".to_slice => '\u{0023E2}', + "tscr".to_slice => '\u{01D4C9}', + "tscy".to_slice => '\u{000446}', + "tshcy".to_slice => '\u{00045B}', + "tstrok".to_slice => '\u{000167}', + "twixt".to_slice => '\u{00226C}', + "twoheadleftarrow".to_slice => '\u{00219E}', + "twoheadrightarrow".to_slice => '\u{0021A0}', + "uArr".to_slice => '\u{0021D1}', + "uHar".to_slice => '\u{002963}', + "uacute".to_slice => '\u{0000FA}', + "uarr".to_slice => '\u{002191}', + "ubrcy".to_slice => '\u{00045E}', + "ubreve".to_slice => '\u{00016D}', + "ucirc".to_slice => '\u{0000FB}', + "ucy".to_slice => '\u{000443}', + "udarr".to_slice => '\u{0021C5}', + "udblac".to_slice => '\u{000171}', + "udhar".to_slice => '\u{00296E}', + "ufisht".to_slice => '\u{00297E}', + "ufr".to_slice => '\u{01D532}', + "ugrave".to_slice => '\u{0000F9}', + "uharl".to_slice => '\u{0021BF}', + "uharr".to_slice => '\u{0021BE}', + "uhblk".to_slice => '\u{002580}', + "ulcorn".to_slice => '\u{00231C}', + "ulcorner".to_slice => '\u{00231C}', + "ulcrop".to_slice => '\u{00230F}', + "ultri".to_slice => '\u{0025F8}', + "umacr".to_slice => '\u{00016B}', + "uml".to_slice => '\u{0000A8}', + "uogon".to_slice => '\u{000173}', + "uopf".to_slice => '\u{01D566}', + "uparrow".to_slice => '\u{002191}', + "updownarrow".to_slice => '\u{002195}', + "upharpoonleft".to_slice => '\u{0021BF}', + "upharpoonright".to_slice => '\u{0021BE}', + "uplus".to_slice => '\u{00228E}', + "upsi".to_slice => '\u{0003C5}', + "upsih".to_slice => '\u{0003D2}', + "upsilon".to_slice => '\u{0003C5}', + "upuparrows".to_slice => '\u{0021C8}', + "urcorn".to_slice => '\u{00231D}', + "urcorner".to_slice => '\u{00231D}', + "urcrop".to_slice => '\u{00230E}', + "uring".to_slice => '\u{00016F}', + "urtri".to_slice => '\u{0025F9}', + "uscr".to_slice => '\u{01D4CA}', + "utdot".to_slice => '\u{0022F0}', + "utilde".to_slice => '\u{000169}', + "utri".to_slice => '\u{0025B5}', + "utrif".to_slice => '\u{0025B4}', + "uuarr".to_slice => '\u{0021C8}', + "uuml".to_slice => '\u{0000FC}', + "uwangle".to_slice => '\u{0029A7}', + "vArr".to_slice => '\u{0021D5}', + "vBar".to_slice => '\u{002AE8}', + "vBarv".to_slice => '\u{002AE9}', + "vDash".to_slice => '\u{0022A8}', + "vangrt".to_slice => '\u{00299C}', + "varepsilon".to_slice => '\u{0003F5}', + "varkappa".to_slice => '\u{0003F0}', + "varnothing".to_slice => '\u{002205}', + "varphi".to_slice => '\u{0003D5}', + "varpi".to_slice => '\u{0003D6}', + "varpropto".to_slice => '\u{00221D}', + "varr".to_slice => '\u{002195}', + "varrho".to_slice => '\u{0003F1}', + "varsigma".to_slice => '\u{0003C2}', + "vartheta".to_slice => '\u{0003D1}', + "vartriangleleft".to_slice => '\u{0022B2}', + "vartriangleright".to_slice => '\u{0022B3}', + "vcy".to_slice => '\u{000432}', + "vdash".to_slice => '\u{0022A2}', + "vee".to_slice => '\u{002228}', + "veebar".to_slice => '\u{0022BB}', + "veeeq".to_slice => '\u{00225A}', + "vellip".to_slice => '\u{0022EE}', + "verbar".to_slice => '\u{00007C}', + "vert".to_slice => '\u{00007C}', + "vfr".to_slice => '\u{01D533}', + "vltri".to_slice => '\u{0022B2}', + "vopf".to_slice => '\u{01D567}', + "vprop".to_slice => '\u{00221D}', + "vrtri".to_slice => '\u{0022B3}', + "vscr".to_slice => '\u{01D4CB}', + "vzigzag".to_slice => '\u{00299A}', + "wcirc".to_slice => '\u{000175}', + "wedbar".to_slice => '\u{002A5F}', + "wedge".to_slice => '\u{002227}', + "wedgeq".to_slice => '\u{002259}', + "weierp".to_slice => '\u{002118}', + "wfr".to_slice => '\u{01D534}', + "wopf".to_slice => '\u{01D568}', + "wp".to_slice => '\u{002118}', + "wr".to_slice => '\u{002240}', + "wreath".to_slice => '\u{002240}', + "wscr".to_slice => '\u{01D4CC}', + "xcap".to_slice => '\u{0022C2}', + "xcirc".to_slice => '\u{0025EF}', + "xcup".to_slice => '\u{0022C3}', + "xdtri".to_slice => '\u{0025BD}', + "xfr".to_slice => '\u{01D535}', + "xhArr".to_slice => '\u{0027FA}', + "xharr".to_slice => '\u{0027F7}', + "xi".to_slice => '\u{0003BE}', + "xlArr".to_slice => '\u{0027F8}', + "xlarr".to_slice => '\u{0027F5}', + "xmap".to_slice => '\u{0027FC}', + "xnis".to_slice => '\u{0022FB}', + "xodot".to_slice => '\u{002A00}', + "xopf".to_slice => '\u{01D569}', + "xoplus".to_slice => '\u{002A01}', + "xotime".to_slice => '\u{002A02}', + "xrArr".to_slice => '\u{0027F9}', + "xrarr".to_slice => '\u{0027F6}', + "xscr".to_slice => '\u{01D4CD}', + "xsqcup".to_slice => '\u{002A06}', + "xuplus".to_slice => '\u{002A04}', + "xutri".to_slice => '\u{0025B3}', + "xvee".to_slice => '\u{0022C1}', + "xwedge".to_slice => '\u{0022C0}', + "yacute".to_slice => '\u{0000FD}', + "yacy".to_slice => '\u{00044F}', + "ycirc".to_slice => '\u{000177}', + "ycy".to_slice => '\u{00044B}', + "yen".to_slice => '\u{0000A5}', + "yfr".to_slice => '\u{01D536}', + "yicy".to_slice => '\u{000457}', + "yopf".to_slice => '\u{01D56A}', + "yscr".to_slice => '\u{01D4CE}', + "yucy".to_slice => '\u{00044E}', + "yuml".to_slice => '\u{0000FF}', + "zacute".to_slice => '\u{00017A}', + "zcaron".to_slice => '\u{00017E}', + "zcy".to_slice => '\u{000437}', + "zdot".to_slice => '\u{00017C}', + "zeetrf".to_slice => '\u{002128}', + "zeta".to_slice => '\u{0003B6}', + "zfr".to_slice => '\u{01D537}', + "zhcy".to_slice => '\u{000436}', + "zigrarr".to_slice => '\u{0021DD}', + "zopf".to_slice => '\u{01D56B}', + "zscr".to_slice => '\u{01D4CF}', + "zwj".to_slice => '\u{00200D}', + "zwnj".to_slice => '\u{00200C}', + "AElig".to_slice => '\u{0000C6}', + "AMP".to_slice => '\u{000026}', + "Aacute".to_slice => '\u{0000C1}', + "Acirc".to_slice => '\u{0000C2}', + "Agrave".to_slice => '\u{0000C0}', + "Aring".to_slice => '\u{0000C5}', + "Atilde".to_slice => '\u{0000C3}', + "Auml".to_slice => '\u{0000C4}', + "COPY".to_slice => '\u{0000A9}', + "Ccedil".to_slice => '\u{0000C7}', + "ETH".to_slice => '\u{0000D0}', + "Eacute".to_slice => '\u{0000C9}', + "Ecirc".to_slice => '\u{0000CA}', + "Egrave".to_slice => '\u{0000C8}', + "Euml".to_slice => '\u{0000CB}', + "GT".to_slice => '\u{00003E}', + "Iacute".to_slice => '\u{0000CD}', + "Icirc".to_slice => '\u{0000CE}', + "Igrave".to_slice => '\u{0000CC}', + "Iuml".to_slice => '\u{0000CF}', + "LT".to_slice => '\u{00003C}', + "Ntilde".to_slice => '\u{0000D1}', + "Oacute".to_slice => '\u{0000D3}', + "Ocirc".to_slice => '\u{0000D4}', + "Ograve".to_slice => '\u{0000D2}', + "Oslash".to_slice => '\u{0000D8}', + "Otilde".to_slice => '\u{0000D5}', + "Ouml".to_slice => '\u{0000D6}', + "QUOT".to_slice => '\u{000022}', + "REG".to_slice => '\u{0000AE}', + "THORN".to_slice => '\u{0000DE}', + "Uacute".to_slice => '\u{0000DA}', + "Ucirc".to_slice => '\u{0000DB}', + "Ugrave".to_slice => '\u{0000D9}', + "Uuml".to_slice => '\u{0000DC}', + "Yacute".to_slice => '\u{0000DD}', + "aacute".to_slice => '\u{0000E1}', + "acirc".to_slice => '\u{0000E2}', + "acute".to_slice => '\u{0000B4}', + "aelig".to_slice => '\u{0000E6}', + "agrave".to_slice => '\u{0000E0}', + "amp".to_slice => '\u{000026}', + "aring".to_slice => '\u{0000E5}', + "atilde".to_slice => '\u{0000E3}', + "auml".to_slice => '\u{0000E4}', + "brvbar".to_slice => '\u{0000A6}', + "ccedil".to_slice => '\u{0000E7}', + "cedil".to_slice => '\u{0000B8}', + "cent".to_slice => '\u{0000A2}', + "copy".to_slice => '\u{0000A9}', + "curren".to_slice => '\u{0000A4}', + "deg".to_slice => '\u{0000B0}', + "divide".to_slice => '\u{0000F7}', + "eacute".to_slice => '\u{0000E9}', + "ecirc".to_slice => '\u{0000EA}', + "egrave".to_slice => '\u{0000E8}', + "eth".to_slice => '\u{0000F0}', + "euml".to_slice => '\u{0000EB}', + "frac12".to_slice => '\u{0000BD}', + "frac14".to_slice => '\u{0000BC}', + "frac34".to_slice => '\u{0000BE}', + "gt".to_slice => '\u{00003E}', + "iacute".to_slice => '\u{0000ED}', + "icirc".to_slice => '\u{0000EE}', + "iexcl".to_slice => '\u{0000A1}', + "igrave".to_slice => '\u{0000EC}', + "iquest".to_slice => '\u{0000BF}', + "iuml".to_slice => '\u{0000EF}', + "laquo".to_slice => '\u{0000AB}', + "lt".to_slice => '\u{00003C}', + "macr".to_slice => '\u{0000AF}', + "micro".to_slice => '\u{0000B5}', + "middot".to_slice => '\u{0000B7}', + "nbsp".to_slice => '\u{0000A0}', + "not".to_slice => '\u{0000AC}', + "ntilde".to_slice => '\u{0000F1}', + "oacute".to_slice => '\u{0000F3}', + "ocirc".to_slice => '\u{0000F4}', + "ograve".to_slice => '\u{0000F2}', + "ordf".to_slice => '\u{0000AA}', + "ordm".to_slice => '\u{0000BA}', + "oslash".to_slice => '\u{0000F8}', + "otilde".to_slice => '\u{0000F5}', + "ouml".to_slice => '\u{0000F6}', + "para".to_slice => '\u{0000B6}', + "plusmn".to_slice => '\u{0000B1}', + "pound".to_slice => '\u{0000A3}', + "quot".to_slice => '\u{000022}', + "raquo".to_slice => '\u{0000BB}', + "reg".to_slice => '\u{0000AE}', + "sect".to_slice => '\u{0000A7}', + "shy".to_slice => '\u{0000AD}', + "sup1".to_slice => '\u{0000B9}', + "sup2".to_slice => '\u{0000B2}', + "sup3".to_slice => '\u{0000B3}', + "szlig".to_slice => '\u{0000DF}', + "thorn".to_slice => '\u{0000FE}', + "times".to_slice => '\u{0000D7}', + "uacute".to_slice => '\u{0000FA}', + "ucirc".to_slice => '\u{0000FB}', + "ugrave".to_slice => '\u{0000F9}', + "uml".to_slice => '\u{0000A8}', + "uuml".to_slice => '\u{0000FC}', + "yacute".to_slice => '\u{0000FD}', + "yen".to_slice => '\u{0000A5}', + "yuml".to_slice => '\u{0000FF}', + } of Bytes => Char # :nodoc: DOUBLE_CHAR_ENTITIES = { - "NotEqualTilde;" => "\u{2242}\u{0338}", - "NotGreaterFullEqual;" => "\u{2267}\u{0338}", - "NotGreaterGreater;" => "\u{226B}\u{0338}", - "NotGreaterSlantEqual;" => "\u{2A7E}\u{0338}", - "NotHumpDownHump;" => "\u{224E}\u{0338}", - "NotHumpEqual;" => "\u{224F}\u{0338}", - "NotLeftTriangleBar;" => "\u{29CF}\u{0338}", - "NotLessLess;" => "\u{226A}\u{0338}", - "NotLessSlantEqual;" => "\u{2A7D}\u{0338}", - "NotNestedGreaterGreater;" => "\u{2AA2}\u{0338}", - "NotNestedLessLess;" => "\u{2AA1}\u{0338}", - "NotPrecedesEqual;" => "\u{2AAF}\u{0338}", - "NotRightTriangleBar;" => "\u{29D0}\u{0338}", - "NotSquareSubset;" => "\u{228F}\u{0338}", - "NotSquareSuperset;" => "\u{2290}\u{0338}", - "NotSubset;" => "\u{2282}\u{20D2}", - "NotSucceedsEqual;" => "\u{2AB0}\u{0338}", - "NotSucceedsTilde;" => "\u{227F}\u{0338}", - "NotSuperset;" => "\u{2283}\u{20D2}", - "ThickSpace;" => "\u{205F}\u{200A}", - "acE;" => "\u{223E}\u{0333}", - "bne;" => "\u{003D}\u{20E5}", - "bnequiv;" => "\u{2261}\u{20E5}", - "caps;" => "\u{2229}\u{FE00}", - "cups;" => "\u{222A}\u{FE00}", - "fjlig;" => "\u{0066}\u{006A}", - "gesl;" => "\u{22DB}\u{FE00}", - "gvertneqq;" => "\u{2269}\u{FE00}", - "gvnE;" => "\u{2269}\u{FE00}", - "lates;" => "\u{2AAD}\u{FE00}", - "lesg;" => "\u{22DA}\u{FE00}", - "lvertneqq;" => "\u{2268}\u{FE00}", - "lvnE;" => "\u{2268}\u{FE00}", - "nGg;" => "\u{22D9}\u{0338}", - "nGt;" => "\u{226B}\u{20D2}", - "nGtv;" => "\u{226B}\u{0338}", - "nLl;" => "\u{22D8}\u{0338}", - "nLt;" => "\u{226A}\u{20D2}", - "nLtv;" => "\u{226A}\u{0338}", - "nang;" => "\u{2220}\u{20D2}", - "napE;" => "\u{2A70}\u{0338}", - "napid;" => "\u{224B}\u{0338}", - "nbump;" => "\u{224E}\u{0338}", - "nbumpe;" => "\u{224F}\u{0338}", - "ncongdot;" => "\u{2A6D}\u{0338}", - "nedot;" => "\u{2250}\u{0338}", - "nesim;" => "\u{2242}\u{0338}", - "ngE;" => "\u{2267}\u{0338}", - "ngeqq;" => "\u{2267}\u{0338}", - "ngeqslant;" => "\u{2A7E}\u{0338}", - "nges;" => "\u{2A7E}\u{0338}", - "nlE;" => "\u{2266}\u{0338}", - "nleqq;" => "\u{2266}\u{0338}", - "nleqslant;" => "\u{2A7D}\u{0338}", - "nles;" => "\u{2A7D}\u{0338}", - "notinE;" => "\u{22F9}\u{0338}", - "notindot;" => "\u{22F5}\u{0338}", - "nparsl;" => "\u{2AFD}\u{20E5}", - "npart;" => "\u{2202}\u{0338}", - "npre;" => "\u{2AAF}\u{0338}", - "npreceq;" => "\u{2AAF}\u{0338}", - "nrarrc;" => "\u{2933}\u{0338}", - "nrarrw;" => "\u{219D}\u{0338}", - "nsce;" => "\u{2AB0}\u{0338}", - "nsubE;" => "\u{2AC5}\u{0338}", - "nsubset;" => "\u{2282}\u{20D2}", - "nsubseteqq;" => "\u{2AC5}\u{0338}", - "nsucceq;" => "\u{2AB0}\u{0338}", - "nsupE;" => "\u{2AC6}\u{0338}", - "nsupset;" => "\u{2283}\u{20D2}", - "nsupseteqq;" => "\u{2AC6}\u{0338}", - "nvap;" => "\u{224D}\u{20D2}", - "nvge;" => "\u{2265}\u{20D2}", - "nvgt;" => "\u{003E}\u{20D2}", - "nvle;" => "\u{2264}\u{20D2}", - "nvlt;" => "\u{003C}\u{20D2}", - "nvltrie;" => "\u{22B4}\u{20D2}", - "nvrtrie;" => "\u{22B5}\u{20D2}", - "nvsim;" => "\u{223C}\u{20D2}", - "race;" => "\u{223D}\u{0331}", - "smtes;" => "\u{2AAC}\u{FE00}", - "sqcaps;" => "\u{2293}\u{FE00}", - "sqcups;" => "\u{2294}\u{FE00}", - "varsubsetneq;" => "\u{228A}\u{FE00}", - "varsubsetneqq;" => "\u{2ACB}\u{FE00}", - "varsupsetneq;" => "\u{228B}\u{FE00}", - "varsupsetneqq;" => "\u{2ACC}\u{FE00}", - "vnsub;" => "\u{2282}\u{20D2}", - "vnsup;" => "\u{2283}\u{20D2}", - "vsubnE;" => "\u{2ACB}\u{FE00}", - "vsubne;" => "\u{228A}\u{FE00}", - "vsupnE;" => "\u{2ACC}\u{FE00}", - "vsupne;" => "\u{228B}\u{FE00}", - } of String => String + "NotEqualTilde".to_slice => "\u{2242}\u{0338}", + "NotGreaterFullEqual".to_slice => "\u{2267}\u{0338}", + "NotGreaterGreater".to_slice => "\u{226B}\u{0338}", + "NotGreaterSlantEqual".to_slice => "\u{2A7E}\u{0338}", + "NotHumpDownHump".to_slice => "\u{224E}\u{0338}", + "NotHumpEqual".to_slice => "\u{224F}\u{0338}", + "NotLeftTriangleBar".to_slice => "\u{29CF}\u{0338}", + "NotLessLess".to_slice => "\u{226A}\u{0338}", + "NotLessSlantEqual".to_slice => "\u{2A7D}\u{0338}", + "NotNestedGreaterGreater".to_slice => "\u{2AA2}\u{0338}", + "NotNestedLessLess".to_slice => "\u{2AA1}\u{0338}", + "NotPrecedesEqual".to_slice => "\u{2AAF}\u{0338}", + "NotRightTriangleBar".to_slice => "\u{29D0}\u{0338}", + "NotSquareSubset".to_slice => "\u{228F}\u{0338}", + "NotSquareSuperset".to_slice => "\u{2290}\u{0338}", + "NotSubset".to_slice => "\u{2282}\u{20D2}", + "NotSucceedsEqual".to_slice => "\u{2AB0}\u{0338}", + "NotSucceedsTilde".to_slice => "\u{227F}\u{0338}", + "NotSuperset".to_slice => "\u{2283}\u{20D2}", + "ThickSpace".to_slice => "\u{205F}\u{200A}", + "acE".to_slice => "\u{223E}\u{0333}", + "bne".to_slice => "\u{003D}\u{20E5}", + "bnequiv".to_slice => "\u{2261}\u{20E5}", + "caps".to_slice => "\u{2229}\u{FE00}", + "cups".to_slice => "\u{222A}\u{FE00}", + "fjlig".to_slice => "\u{0066}\u{006A}", + "gesl".to_slice => "\u{22DB}\u{FE00}", + "gvertneqq".to_slice => "\u{2269}\u{FE00}", + "gvnE".to_slice => "\u{2269}\u{FE00}", + "lates".to_slice => "\u{2AAD}\u{FE00}", + "lesg".to_slice => "\u{22DA}\u{FE00}", + "lvertneqq".to_slice => "\u{2268}\u{FE00}", + "lvnE".to_slice => "\u{2268}\u{FE00}", + "nGg".to_slice => "\u{22D9}\u{0338}", + "nGt".to_slice => "\u{226B}\u{20D2}", + "nGtv".to_slice => "\u{226B}\u{0338}", + "nLl".to_slice => "\u{22D8}\u{0338}", + "nLt".to_slice => "\u{226A}\u{20D2}", + "nLtv".to_slice => "\u{226A}\u{0338}", + "nang".to_slice => "\u{2220}\u{20D2}", + "napE".to_slice => "\u{2A70}\u{0338}", + "napid".to_slice => "\u{224B}\u{0338}", + "nbump".to_slice => "\u{224E}\u{0338}", + "nbumpe".to_slice => "\u{224F}\u{0338}", + "ncongdot".to_slice => "\u{2A6D}\u{0338}", + "nedot".to_slice => "\u{2250}\u{0338}", + "nesim".to_slice => "\u{2242}\u{0338}", + "ngE".to_slice => "\u{2267}\u{0338}", + "ngeqq".to_slice => "\u{2267}\u{0338}", + "ngeqslant".to_slice => "\u{2A7E}\u{0338}", + "nges".to_slice => "\u{2A7E}\u{0338}", + "nlE".to_slice => "\u{2266}\u{0338}", + "nleqq".to_slice => "\u{2266}\u{0338}", + "nleqslant".to_slice => "\u{2A7D}\u{0338}", + "nles".to_slice => "\u{2A7D}\u{0338}", + "notinE".to_slice => "\u{22F9}\u{0338}", + "notindot".to_slice => "\u{22F5}\u{0338}", + "nparsl".to_slice => "\u{2AFD}\u{20E5}", + "npart".to_slice => "\u{2202}\u{0338}", + "npre".to_slice => "\u{2AAF}\u{0338}", + "npreceq".to_slice => "\u{2AAF}\u{0338}", + "nrarrc".to_slice => "\u{2933}\u{0338}", + "nrarrw".to_slice => "\u{219D}\u{0338}", + "nsce".to_slice => "\u{2AB0}\u{0338}", + "nsubE".to_slice => "\u{2AC5}\u{0338}", + "nsubset".to_slice => "\u{2282}\u{20D2}", + "nsubseteqq".to_slice => "\u{2AC5}\u{0338}", + "nsucceq".to_slice => "\u{2AB0}\u{0338}", + "nsupE".to_slice => "\u{2AC6}\u{0338}", + "nsupset".to_slice => "\u{2283}\u{20D2}", + "nsupseteqq".to_slice => "\u{2AC6}\u{0338}", + "nvap".to_slice => "\u{224D}\u{20D2}", + "nvge".to_slice => "\u{2265}\u{20D2}", + "nvgt".to_slice => "\u{003E}\u{20D2}", + "nvle".to_slice => "\u{2264}\u{20D2}", + "nvlt".to_slice => "\u{003C}\u{20D2}", + "nvltrie".to_slice => "\u{22B4}\u{20D2}", + "nvrtrie".to_slice => "\u{22B5}\u{20D2}", + "nvsim".to_slice => "\u{223C}\u{20D2}", + "race".to_slice => "\u{223D}\u{0331}", + "smtes".to_slice => "\u{2AAC}\u{FE00}", + "sqcaps".to_slice => "\u{2293}\u{FE00}", + "sqcups".to_slice => "\u{2294}\u{FE00}", + "varsubsetneq".to_slice => "\u{228A}\u{FE00}", + "varsubsetneqq".to_slice => "\u{2ACB}\u{FE00}", + "varsupsetneq".to_slice => "\u{228B}\u{FE00}", + "varsupsetneqq".to_slice => "\u{2ACC}\u{FE00}", + "vnsub".to_slice => "\u{2282}\u{20D2}", + "vnsup".to_slice => "\u{2283}\u{20D2}", + "vsubnE".to_slice => "\u{2ACB}\u{FE00}", + "vsubne".to_slice => "\u{228A}\u{FE00}", + "vsupnE".to_slice => "\u{2ACC}\u{FE00}", + "vsupne".to_slice => "\u{228B}\u{FE00}", + } of Bytes => String + + # :nodoc: + MAX_ENTITY_NAME_SIZE = Math.max(SINGLE_CHAR_ENTITIES.each_key.max_of(&.size), DOUBLE_CHAR_ENTITIES.each_key.max_of(&.size)) end From 57d93972fcafdbfe260d3a06a7c363d96f2841c6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 3 Nov 2023 07:03:55 +0800 Subject: [PATCH 0748/1551] Add `Number#integer?` (#13936) --- spec/std/big/big_decimal_spec.cr | 12 ++++++++++++ spec/std/big/big_float_spec.cr | 6 ++++++ spec/std/big/big_int_spec.cr | 5 +++++ spec/std/big/big_rational_spec.cr | 9 +++++++++ spec/std/float_spec.cr | 17 +++++++++++++++++ spec/std/int_spec.cr | 7 +++++++ src/big/big_decimal.cr | 6 ++++++ src/big/big_float.cr | 3 ++- src/big/big_rational.cr | 7 +++++++ src/int.cr | 7 +++++++ src/number.cr | 16 ++++++++++++++++ 11 files changed, 94 insertions(+), 1 deletion(-) diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index dd307ee9812f..f6b0834ed2be 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -969,6 +969,18 @@ describe BigDecimal do (-1.to_big_d - BigDecimal.new(50000, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(5, 200)) end end + + describe "#integer?" do + it { BigDecimal.new(0, 0).integer?.should be_true } + it { BigDecimal.new(1, 0).integer?.should be_true } + it { BigDecimal.new(10, 0).integer?.should be_true } + it { BigDecimal.new(-10, 1).integer?.should be_true } + it { BigDecimal.new(10000, 4).integer?.should be_true } + + it { BigDecimal.new(1, 1).integer?.should be_false } + it { BigDecimal.new(13, 2).integer?.should be_false } + it { BigDecimal.new(-2400, 3).integer?.should be_false } + end end describe "#inspect" do diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index 5c98beb320ca..679f271c5f3b 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -526,6 +526,12 @@ describe "BigFloat" do end end + describe "#integer?" do + it { BigFloat.zero.integer?.should be_true } + it { 1.to_big_f.integer?.should be_true } + it { 1.2.to_big_f.integer?.should be_false } + end + it "#hash" do b = 123.to_big_f b.hash.should eq(b.to_f64.hash) diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr index f7eebc53ffb4..07575892bc08 100644 --- a/spec/std/big/big_int_spec.cr +++ b/spec/std/big/big_int_spec.cr @@ -9,6 +9,11 @@ private def it_converts_to_s(num, str, *, file = __FILE__, line = __LINE__, **op end describe "BigInt" do + describe "#integer?" do + it { BigInt.zero.integer?.should be_true } + it { 12345.to_big_i.integer?.should be_true } + end + it "creates with a value of zero" do BigInt.new.to_s.should eq("0") end diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr index 2c9b138ba357..7b28a4938966 100644 --- a/spec/std/big/big_rational_spec.cr +++ b/spec/std/big/big_rational_spec.cr @@ -393,6 +393,15 @@ describe BigRational do end end + describe "#integer?" do + it { br(0, 1).integer?.should be_true } + it { br(1, 1).integer?.should be_true } + it { br(9, 3).integer?.should be_true } + it { br(-126, 7).integer?.should be_true } + it { br(5, 2).integer?.should be_false } + it { br(7, -3).integer?.should be_false } + end + it "#hash" do b = br(10, 3) hash = b.hash diff --git a/spec/std/float_spec.cr b/spec/std/float_spec.cr index 4ddfd194313d..9a6ebecd3a2f 100644 --- a/spec/std/float_spec.cr +++ b/spec/std/float_spec.cr @@ -76,6 +76,23 @@ describe "Float" do it { 2.9.ceil.should eq(3) } end + describe "#integer?" do + it { 1.0_f32.integer?.should be_true } + it { 1.0_f64.integer?.should be_true } + + it { 1.2_f32.integer?.should be_false } + it { 1.2_f64.integer?.should be_false } + + it { Float32::MAX.integer?.should be_true } + it { Float64::MAX.integer?.should be_true } + + it { Float32::INFINITY.integer?.should be_false } + it { Float64::INFINITY.integer?.should be_false } + + it { Float32::NAN.integer?.should be_false } + it { Float64::NAN.integer?.should be_false } + end + describe "fdiv" do it { 1.0.fdiv(1).should eq 1.0 } it { 1.0.fdiv(2).should eq 0.5 } diff --git a/spec/std/int_spec.cr b/spec/std/int_spec.cr index 4f377b385206..a4740521a7ec 100644 --- a/spec/std/int_spec.cr +++ b/spec/std/int_spec.cr @@ -13,6 +13,13 @@ private macro it_converts_to_s(num, str, **opts) end describe "Int" do + describe "#integer?" do + {% for int in BUILTIN_INTEGER_TYPES %} + it { {{ int }}::MIN.integer?.should be_true } + it { {{ int }}::MAX.integer?.should be_true } + {% end %} + end + describe "**" do it "with positive Int32" do x = 2 ** 2 diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index 376192c5124b..ab42c64bf919 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -459,6 +459,12 @@ struct BigDecimal < Number BigDecimal.new(mantissa, 0) end + # :inherit: + def integer? : Bool + factor_powers_of_ten + scale == 0 + end + def round(digits : Number, base = 10, *, mode : RoundingMode = :ties_even) : BigDecimal return self if zero? diff --git a/src/big/big_float.cr b/src/big/big_float.cr index 33231b422db3..5464d948931b 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -411,7 +411,8 @@ struct BigFloat < Float end end - protected def integer? + # :inherit: + def integer? : Bool !LibGMP.mpf_integer_p(mpf).zero? end diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index 6b6a6c0bb0b2..4b81db43f46b 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -179,6 +179,13 @@ struct BigRational < Number x end + # :inherit: + def integer? : Bool + # since all `BigRational`s are canonicalized, the denominator must be + # positive and coprime with the numerator + denominator == 1 + end + # Divides the rational by (2 ** *other*) # # ``` diff --git a/src/int.cr b/src/int.cr index 4f10b543fc67..3c78877c74be 100644 --- a/src/int.cr +++ b/src/int.cr @@ -274,6 +274,13 @@ struct Int self end + # :inherit: + # + # Always returns `true` for `Int`. + def integer? : Bool + true + end + # Returns the value of raising `self` to the power of *exponent*. # # Raises `ArgumentError` if *exponent* is negative: if this is needed, diff --git a/src/number.cr b/src/number.cr index 9aae19e1eb8a..9ec542f86ef7 100644 --- a/src/number.cr +++ b/src/number.cr @@ -343,6 +343,22 @@ struct Number end end + # Returns `true` if `self` is an integer. + # + # Non-integer types may return `true` as long as `self` denotes a finite value + # without any fractional parts. + # + # ``` + # 1.integer? # => true + # 1.0.integer? # => true + # 1.2.integer? # => false + # (1 / 0).integer? # => false + # (0 / 0).integer? # => false + # ``` + def integer? : Bool + self % 1 == 0 + end + # Returns `true` if `self` is equal to zero. # # ``` From fde415806470ed1d00f62681d2ef45e67899d4d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 3 Nov 2023 11:47:52 +0100 Subject: [PATCH 0749/1551] Fix `crystal docs` check `File.exists?` for `shard.yml` (#13937) --- src/compiler/crystal/tools/doc/project_info.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/tools/doc/project_info.cr b/src/compiler/crystal/tools/doc/project_info.cr index fe92fb54c80e..4d3bc507c5aa 100644 --- a/src/compiler/crystal/tools/doc/project_info.cr +++ b/src/compiler/crystal/tools/doc/project_info.cr @@ -160,7 +160,7 @@ module Crystal::Doc end def self.read_shard_properties - return {nil, nil} unless File.readable?("shard.yml") + return {nil, nil} unless File.exists?("shard.yml") name = nil version = nil From eba50c24df72b375d42b375997f798c8aea36cf5 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 3 Nov 2023 18:48:13 +0800 Subject: [PATCH 0750/1551] Fix private type definitions with namespaced `Path`s (#13931) --- spec/compiler/semantic/private_spec.cr | 120 ++++++++---------- .../crystal/semantic/top_level_visitor.cr | 59 ++++----- 2 files changed, 79 insertions(+), 100 deletions(-) diff --git a/spec/compiler/semantic/private_spec.cr b/spec/compiler/semantic/private_spec.cr index 4f339370b02f..bc1cb7ab5e62 100644 --- a/spec/compiler/semantic/private_spec.cr +++ b/spec/compiler/semantic/private_spec.cr @@ -318,76 +318,64 @@ describe "Semantic: private" do )) { int32 } end - it "doesn't find private class from outside namespace" do - assert_error %( - class Foo - private class Bar - end - end - - Foo::Bar - ), - "private constant Foo::Bar referenced" - end - - it "doesn't find private module from outside namespace" do - assert_error %( - class Foo - private module Bar - end - end - - Foo::Bar - ), - "private constant Foo::Bar referenced" - end - - it "doesn't find private enum from outside namespace" do - assert_error %( - class Foo - private enum Bar - A - end - end - - Foo::Bar - ), - "private constant Foo::Bar referenced" - end - - it "doesn't find private alias from outside namespace" do - assert_error %( - class Foo - private alias Bar = Int32 - end - - Foo::Bar - ), - "private constant Foo::Bar referenced" - end - - it "doesn't find private lib from outside namespace" do - assert_error %( - class Foo - private lib LibBar + {% for kind, decl in { + "class" => %(class Bar; end), + "module" => %(module Bar; end), + "enum" => %(enum Bar; A; end), + "alias" => %(alias Bar = Int32), + "lib" => %(lib Bar; end), + "constant" => %(Bar = 1), + } %} + it "doesn't find private {{ kind.id }} from outside namespace" do + assert_error <<-CRYSTAL, "private constant Foo::Bar referenced" + module Foo + private {{ decl.id }} end - end - Foo::LibBar - ), - "private constant Foo::LibBar referenced" - end + Foo::Bar + CRYSTAL + end + {% end %} + + {% for kind, decl in { + "class" => %(class Foo::Bar; end), + "module" => %(module Foo::Bar; end), + "enum" => %(enum Foo::Bar; A; end), + "alias" => %(alias Foo::Bar = Int32), + "lib" => %(lib Foo::Bar; end), + "constant" => %(Foo::Bar = 1), + } %} + it "doesn't find private {{ kind.id }} from outside namespace, long name (#8831)" do + assert_error <<-CRYSTAL, "private constant Foo::Bar referenced" + private {{ decl.id }} + + Foo::Bar + CRYSTAL + end - it "doesn't find private constant from outside namespace" do - assert_error %( - class Foo - private Bar = 1 - end + it "doesn't define incorrect type in top-level namespace (#13511)" do + assert_error <<-CRYSTAL, "undefined constant Bar" + private {{ decl.id }} - Foo::Bar - ), - "private constant Foo::Bar referenced" - end + Bar + CRYSTAL + end + {% end %} + + {% for kind, decl in { + "class" => %(class ::Foo; end), + "module" => %(module ::Foo; end), + "enum" => %(enum ::Foo; A; end), + "alias" => %(alias ::Foo = Int32), + "lib" => %(lib ::Foo; end), + "constant" => %(::Foo = 1), + } %} + it "doesn't define private {{ kind.id }} with global type name" do + assert_error <<-CRYSTAL, "can't declare private type in the global namespace" + private {{ decl.id }} + CRYSTAL + end + {% end %} it "finds private type from inside namespace" do assert_type(%( diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index da6d57ed16ab..5d0cbc477260 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -802,11 +802,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor annotations = read_annotations - scope, name = lookup_type_def_name(target) - if current_type.is_a?(Program) - scope = program.check_private(target) || scope - end - + name = target.names.last + scope = lookup_type_def_scope(target, target) type = scope.types[name]? if type target.raise "already initialized constant #{type}" @@ -1172,7 +1169,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end def lookup_type_def(node : ASTNode) - scope, name = lookup_type_def_name(node) + path = node.name + scope = lookup_type_def_scope(node, path) + name = path.names.last type = scope.types[name]? if type && node.doc type.doc = node.doc @@ -1180,27 +1179,27 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor {scope, name, type} end - def lookup_type_def_name(node : ASTNode) - scope, name = lookup_type_def_name(node.name) - if current_type.is_a?(Program) - scope = program.check_private(node) || scope - end - {scope, name} - end - - def lookup_type_def_name(path : Path) - if path.names.size == 1 && !path.global? - scope = current_type - name = path.names.first - else - path = path.clone - name = path.names.pop - scope = lookup_type_def_name_creating_modules path - end - - scope = check_type_is_type_container(scope, path) + def lookup_type_def_scope(node : ASTNode, path : Path) + scope = + if path.names.size == 1 + if path.global? + if node.visibility.private? + path.raise "can't declare private type in the global namespace; drop the `private` for the top-level namespace, or drop the leading `::` for the file-private namespace" + end + program + else + if current_type.is_a?(Program) + file_module = program.check_private(node) + end + file_module || current_type + end + else + prefix = path.clone + prefix.names.pop + lookup_type_def_name_creating_modules prefix + end - {scope, name} + check_type_is_type_container(scope, path) end def check_type_is_type_container(scope, path) @@ -1239,14 +1238,6 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor target_type.as(NamedType) end - def current_type_scope(node) - scope = current_type - if scope.is_a?(Program) && node.visibility.private? - scope = program.check_private(node) || scope - end - scope - end - # Turns all finished macros into expanded nodes, and # adds them to the program def process_finished_hooks From cfbff6a02d139629bc05d61611524e8ed1422faa Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 3 Nov 2023 22:18:30 +0800 Subject: [PATCH 0751/1551] Publish `Int::Primitive#abs_unsigned` and `#neg_signed` (#13938) --- spec/std/int_spec.cr | 54 ++++++++++ src/int.cr | 240 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 274 insertions(+), 20 deletions(-) diff --git a/spec/std/int_spec.cr b/spec/std/int_spec.cr index a4740521a7ec..72a83bd64061 100644 --- a/spec/std/int_spec.cr +++ b/spec/std/int_spec.cr @@ -146,6 +146,60 @@ describe "Int" do end end + describe "#abs_unsigned" do + {% for int in Int::Signed.union_types %} + it "does for {{ int }}" do + x = {{ int }}.new(123).abs_unsigned + x.should be_a(U{{ int }}) + x.should eq(123) + + x = {{ int }}.new(-123).abs_unsigned + x.should be_a(U{{ int }}) + x.should eq(123) + end + + it "does for U{{ int }}" do + x = U{{ int }}.new(123).abs_unsigned + x.should be_a(U{{ int }}) + x.should eq(123) + end + + it "does not overflow on {{ int }}::MIN" do + x = {{ int }}::MIN.abs_unsigned + x.should be_a(U{{ int }}) + x.should eq(U{{ int }}.zero &- {{ int }}::MIN) + end + {% end %} + end + + describe "#neg_signed" do + {% for int in Int::Signed.union_types %} + it "does for {{ int }}" do + x = {{ int }}.new(123).neg_signed + x.should be_a({{ int }}) + x.should eq(-123) + + x = {{ int }}.new(-123).neg_signed + x.should be_a({{ int }}) + x.should eq(123) + + expect_raises(OverflowError) { {{ int }}::MIN.neg_signed } + end + + it "does for U{{ int }}" do + x = U{{ int }}.new(123).neg_signed + x.should be_a({{ int }}) + x.should eq(-123) + end + + it "does not overflow on {{ int }}::MIN.abs_unsigned" do + x = {{ int }}::MIN.abs_unsigned.neg_signed + x.should be_a({{ int }}) + x.should eq({{ int }}::MIN) + end + {% end %} + end + describe "gcd" do it { 14.gcd(0).should eq(14) } it { 14.gcd(1).should eq(1) } diff --git a/src/int.cr b/src/int.cr index 3c78877c74be..2abfa640763f 100644 --- a/src/int.cr +++ b/src/int.cr @@ -897,12 +897,32 @@ struct Int8 0_i8 - self end - # :nodoc: + # Returns the absolute value of `self` as an unsigned value of the same size. + # + # Returns `self` if `self` is already an `Int::Unsigned`. This method never + # overflows. + # + # ``` + # 1_u32.abs_unsigned # => 1_u32 + # 2_i32.abs_unsigned # => 2_u32 + # -3_i8.abs_unsigned # => 3_u8 + # Int16::MIN.abs_unsigned # => 32768_u16 + # ``` def abs_unsigned : UInt8 self < 0 ? 0_u8 &- self : to_u8! end - # :nodoc: + # Returns the negative of `self` as a signed value of the same size. + # + # Returns `-self` if `self` is already an `Int::Signed`. Raises + # `OverflowError` in case of overflow. + # + # ``` + # 1_i32.neg_signed # => -1_i32 + # 2_u16.neg_signed # => -2_i16 + # 128_u8.neg_signed # => -128_i8 + # Int16::MIN.neg_signed # raises OverflowError + # ``` def neg_signed : self -self end @@ -1007,12 +1027,32 @@ struct Int16 0_i16 - self end - # :nodoc: + # Returns the absolute value of `self` as an unsigned value of the same size. + # + # Returns `self` if `self` is already an `Int::Unsigned`. This method never + # overflows. + # + # ``` + # 1_u32.abs_unsigned # => 1_u32 + # 2_i32.abs_unsigned # => 2_u32 + # -3_i8.abs_unsigned # => 3_u8 + # Int16::MIN.abs_unsigned # => 32768_u16 + # ``` def abs_unsigned : UInt16 self < 0 ? 0_u16 &- self : to_u16! end - # :nodoc: + # Returns the negative of `self` as a signed value of the same size. + # + # Returns `-self` if `self` is already an `Int::Signed`. Raises + # `OverflowError` in case of overflow. + # + # ``` + # 1_i32.neg_signed # => -1_i32 + # 2_u16.neg_signed # => -2_i16 + # 128_u8.neg_signed # => -128_i8 + # Int16::MIN.neg_signed # raises OverflowError + # ``` def neg_signed : self -self end @@ -1117,12 +1157,32 @@ struct Int32 0 - self end - # :nodoc: + # Returns the absolute value of `self` as an unsigned value of the same size. + # + # Returns `self` if `self` is already an `Int::Unsigned`. This method never + # overflows. + # + # ``` + # 1_u32.abs_unsigned # => 1_u32 + # 2_i32.abs_unsigned # => 2_u32 + # -3_i8.abs_unsigned # => 3_u8 + # Int16::MIN.abs_unsigned # => 32768_u16 + # ``` def abs_unsigned : UInt32 self < 0 ? 0_u32 &- self : to_u32! end - # :nodoc: + # Returns the negative of `self` as a signed value of the same size. + # + # Returns `-self` if `self` is already an `Int::Signed`. Raises + # `OverflowError` in case of overflow. + # + # ``` + # 1_i32.neg_signed # => -1_i32 + # 2_u16.neg_signed # => -2_i16 + # 128_u8.neg_signed # => -128_i8 + # Int16::MIN.neg_signed # raises OverflowError + # ``` def neg_signed : self -self end @@ -1227,12 +1287,32 @@ struct Int64 0_i64 - self end - # :nodoc: + # Returns the absolute value of `self` as an unsigned value of the same size. + # + # Returns `self` if `self` is already an `Int::Unsigned`. This method never + # overflows. + # + # ``` + # 1_u32.abs_unsigned # => 1_u32 + # 2_i32.abs_unsigned # => 2_u32 + # -3_i8.abs_unsigned # => 3_u8 + # Int16::MIN.abs_unsigned # => 32768_u16 + # ``` def abs_unsigned : UInt64 self < 0 ? 0_u64 &- self : to_u64! end - # :nodoc: + # Returns the negative of `self` as a signed value of the same size. + # + # Returns `-self` if `self` is already an `Int::Signed`. Raises + # `OverflowError` in case of overflow. + # + # ``` + # 1_i32.neg_signed # => -1_i32 + # 2_u16.neg_signed # => -2_i16 + # 128_u8.neg_signed # => -128_i8 + # Int16::MIN.neg_signed # raises OverflowError + # ``` def neg_signed : self -self end @@ -1340,12 +1420,32 @@ struct Int128 Int128.new(0) - self end - # :nodoc: + # Returns the absolute value of `self` as an unsigned value of the same size. + # + # Returns `self` if `self` is already an `Int::Unsigned`. This method never + # overflows. + # + # ``` + # 1_u32.abs_unsigned # => 1_u32 + # 2_i32.abs_unsigned # => 2_u32 + # -3_i8.abs_unsigned # => 3_u8 + # Int16::MIN.abs_unsigned # => 32768_u16 + # ``` def abs_unsigned : UInt128 self < 0 ? UInt128.new(0) &- self : to_u128! end - # :nodoc: + # Returns the negative of `self` as a signed value of the same size. + # + # Returns `-self` if `self` is already an `Int::Signed`. Raises + # `OverflowError` in case of overflow. + # + # ``` + # 1_i32.neg_signed # => -1_i32 + # 2_u16.neg_signed # => -2_i16 + # 128_u8.neg_signed # => -128_i8 + # Int16::MIN.neg_signed # raises OverflowError + # ``` def neg_signed : self -self end @@ -1454,12 +1554,32 @@ struct UInt8 self end - # :nodoc: + # Returns the absolute value of `self` as an unsigned value of the same size. + # + # Returns `self` if `self` is already an `Int::Unsigned`. This method never + # overflows. + # + # ``` + # 1_u32.abs_unsigned # => 1_u32 + # 2_i32.abs_unsigned # => 2_u32 + # -3_i8.abs_unsigned # => 3_u8 + # Int16::MIN.abs_unsigned # => 32768_u16 + # ``` def abs_unsigned : self self end - # :nodoc: + # Returns the negative of `self` as a signed value of the same size. + # + # Returns `-self` if `self` is already an `Int::Signed`. Raises + # `OverflowError` in case of overflow. + # + # ``` + # 1_i32.neg_signed # => -1_i32 + # 2_u16.neg_signed # => -2_i16 + # 128_u8.neg_signed # => -128_i8 + # Int16::MIN.neg_signed # raises OverflowError + # ``` def neg_signed : Int8 0_i8 - self end @@ -1568,12 +1688,32 @@ struct UInt16 self end - # :nodoc: + # Returns the absolute value of `self` as an unsigned value of the same size. + # + # Returns `self` if `self` is already an `Int::Unsigned`. This method never + # overflows. + # + # ``` + # 1_u32.abs_unsigned # => 1_u32 + # 2_i32.abs_unsigned # => 2_u32 + # -3_i8.abs_unsigned # => 3_u8 + # Int16::MIN.abs_unsigned # => 32768_u16 + # ``` def abs_unsigned : self self end - # :nodoc: + # Returns the negative of `self` as a signed value of the same size. + # + # Returns `-self` if `self` is already an `Int::Signed`. Raises + # `OverflowError` in case of overflow. + # + # ``` + # 1_i32.neg_signed # => -1_i32 + # 2_u16.neg_signed # => -2_i16 + # 128_u8.neg_signed # => -128_i8 + # Int16::MIN.neg_signed # raises OverflowError + # ``` def neg_signed : Int16 0_i16 - self end @@ -1682,12 +1822,32 @@ struct UInt32 self end - # :nodoc: + # Returns the absolute value of `self` as an unsigned value of the same size. + # + # Returns `self` if `self` is already an `Int::Unsigned`. This method never + # overflows. + # + # ``` + # 1_u32.abs_unsigned # => 1_u32 + # 2_i32.abs_unsigned # => 2_u32 + # -3_i8.abs_unsigned # => 3_u8 + # Int16::MIN.abs_unsigned # => 32768_u16 + # ``` def abs_unsigned : self self end - # :nodoc: + # Returns the negative of `self` as a signed value of the same size. + # + # Returns `-self` if `self` is already an `Int::Signed`. Raises + # `OverflowError` in case of overflow. + # + # ``` + # 1_i32.neg_signed # => -1_i32 + # 2_u16.neg_signed # => -2_i16 + # 128_u8.neg_signed # => -128_i8 + # Int16::MIN.neg_signed # raises OverflowError + # ``` def neg_signed : Int32 0_i32 - self end @@ -1796,12 +1956,32 @@ struct UInt64 self end - # :nodoc: + # Returns the absolute value of `self` as an unsigned value of the same size. + # + # Returns `self` if `self` is already an `Int::Unsigned`. This method never + # overflows. + # + # ``` + # 1_u32.abs_unsigned # => 1_u32 + # 2_i32.abs_unsigned # => 2_u32 + # -3_i8.abs_unsigned # => 3_u8 + # Int16::MIN.abs_unsigned # => 32768_u16 + # ``` def abs_unsigned : self self end - # :nodoc: + # Returns the negative of `self` as a signed value of the same size. + # + # Returns `-self` if `self` is already an `Int::Signed`. Raises + # `OverflowError` in case of overflow. + # + # ``` + # 1_i32.neg_signed # => -1_i32 + # 2_u16.neg_signed # => -2_i16 + # 128_u8.neg_signed # => -128_i8 + # Int16::MIN.neg_signed # raises OverflowError + # ``` def neg_signed : Int64 0_i64 - self end @@ -1912,12 +2092,32 @@ struct UInt128 self end - # :nodoc: + # Returns the absolute value of `self` as an unsigned value of the same size. + # + # Returns `self` if `self` is already an `Int::Unsigned`. This method never + # overflows. + # + # ``` + # 1_u32.abs_unsigned # => 1_u32 + # 2_i32.abs_unsigned # => 2_u32 + # -3_i8.abs_unsigned # => 3_u8 + # Int16::MIN.abs_unsigned # => 32768_u16 + # ``` def abs_unsigned : self self end - # :nodoc: + # Returns the negative of `self` as a signed value of the same size. + # + # Returns `-self` if `self` is already an `Int::Signed`. Raises + # `OverflowError` in case of overflow. + # + # ``` + # 1_i32.neg_signed # => -1_i32 + # 2_u16.neg_signed # => -2_i16 + # 128_u8.neg_signed # => -128_i8 + # Int16::MIN.neg_signed # raises OverflowError + # ``` def neg_signed : Int128 Int128.new(0) - self end From b2708fcfdf7d644294fbab87da41abdeae96bb29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 3 Nov 2023 15:18:52 +0100 Subject: [PATCH 0752/1551] Optimize `JSON::Builder#string` with byte-based algorithm (#13915) --- src/json/builder.cr | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/json/builder.cr b/src/json/builder.cr index 893970e31f1c..d6061680970d 100644 --- a/src/json/builder.cr +++ b/src/json/builder.cr @@ -106,11 +106,11 @@ class JSON::Builder scalar(string: true) do io << '"' - start_pos = 0 - reader = Char::Reader.new(string) + cursor = start = string.to_unsafe + fin = cursor + string.bytesize - while reader.has_next? - case char = reader.current_char + while cursor < fin + case byte = cursor.value when '\\' escape = "\\\\" when '"' @@ -125,29 +125,26 @@ class JSON::Builder escape = "\\r" when '\t' escape = "\\t" - when .ascii_control? - io.write_string string.to_slice[start_pos, reader.pos - start_pos] - io << "\\u" - ord = char.ord - io << '0' if ord < 0x1000 - io << '0' if ord < 0x100 - io << '0' if ord < 0x10 - ord.to_s(io, 16) - reader.next_char - start_pos = reader.pos + when .<(0x20), 0x7F # Char#ascii_control? + io.write_string Slice.new(start, cursor - start) + io << "\\u00" + io << '0' if byte < 0x10 + byte.to_s(io, 16) + cursor += 1 + start = cursor next else - reader.next_char + cursor += 1 next end - io.write_string string.to_slice[start_pos, reader.pos - start_pos] + io.write_string Slice.new(start, cursor - start) io << escape - reader.next_char - start_pos = reader.pos + cursor += 1 + start = cursor end - io.write_string string.to_slice[start_pos, reader.pos - start_pos] + io.write_string Slice.new(start, cursor - start) io << '"' end From b57aa37431d78d1b33adb89356b4c1189920a934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 4 Nov 2023 10:39:29 +0100 Subject: [PATCH 0753/1551] Fix `ToSVisitor` for expanded string interpolation in backticks (#13943) --- spec/compiler/parser/to_s_spec.cr | 1 + src/compiler/crystal/syntax/to_s.cr | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index b2a62cf3e5c6..752bece45374 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -178,6 +178,7 @@ describe "ASTNode#to_s" do expect_to_s %q(%r{#{1}\/\0}), %q(/#{1}\/\0/) expect_to_s %q(`\n\0`), %q(`\n\u0000`) expect_to_s %q(`#{1}\n\0`), %q(`#{1}\n\u0000`) + expect_to_s Call.new(nil, "`", Call.new("String".path, "interpolation", "x".var, global: true)), %q(`#{::String.interpolation(x)}`) expect_to_s "macro foo\n{% verbatim do %}1{% end %}\nend" expect_to_s Assign.new("x".var, Expressions.new([1.int32, 2.int32] of ASTNode)), "x = (1\n2\n)" expect_to_s "foo.*" diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 4a729599b6d3..69a936c22e5a 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -511,7 +511,13 @@ module Crystal when StringInterpolation visit_interpolation exp, &.inspect_unquoted.gsub('`', "\\`") else - raise "Bug: shouldn't happen" + # This branch can be reached after the literal expander has expanded + # `StringLiteral` nodes to a call to `::String.interpolation` which means + # `exp` is a `Call`. + + @str << "\#{" + exp.accept(self) + @str << "}" end @str << '`' false From 4ea385a2d3d67b14776ac8cd5cfb9b2d68f172b6 Mon Sep 17 00:00:00 2001 From: Evan Paterakis Date: Sat, 4 Nov 2023 18:13:27 +0200 Subject: [PATCH 0754/1551] Fix spec for `String#encode` and `String.new` on DragonFlyBSD (#13944) --- spec/std/string_spec.cr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 798b547eb06e..8a372a370677 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2755,7 +2755,7 @@ describe "String" do bytes.to_a.should eq([72, 0, 101, 0, 108, 0, 108, 0, 111, 0]) end - {% unless flag?(:musl) || flag?(:freebsd) %} + {% unless flag?(:musl) || flag?(:freebsd) || flag?(:dragonfly) %} it "flushes the shift state (#11992)" do "\u{00CA}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x66]) "\u{00CA}\u{0304}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x62]) @@ -2764,7 +2764,7 @@ describe "String" do # FreeBSD iconv encoder expects ISO/IEC 10646 compatibility code points, # see https://www.ccli.gov.hk/doc/e_hkscs_2008.pdf for details. - {% if flag?(:freebsd) %} + {% if flag?(:freebsd) || flag?(:dragonfly) %} it "flushes the shift state (#11992)" do "\u{F329}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x66]) "\u{F325}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x62]) @@ -2808,7 +2808,7 @@ describe "String" do String.new(bytes, "UTF-16LE").should eq("Hello") end - {% unless flag?(:freebsd) %} + {% unless flag?(:freebsd) || flag?(:dragonfly) %} it "decodes with shift state" do String.new(Bytes[0x88, 0x66], "BIG5-HKSCS").should eq("\u{00CA}") String.new(Bytes[0x88, 0x62], "BIG5-HKSCS").should eq("\u{00CA}\u{0304}") @@ -2817,7 +2817,7 @@ describe "String" do # FreeBSD iconv decoder returns ISO/IEC 10646-1:2000 code points, # see https://www.ccli.gov.hk/doc/e_hkscs_2008.pdf for details. - {% if flag?(:freebsd) %} + {% if flag?(:freebsd) || flag?(:dragonfly) %} it "decodes with shift state" do String.new(Bytes[0x88, 0x66], "BIG5-HKSCS").should eq("\u{00CA}") String.new(Bytes[0x88, 0x62], "BIG5-HKSCS").should eq("\u{F325}") From 082ee49988e8bfaa6134f2062f6a639959d6bee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 5 Nov 2023 14:21:33 +0100 Subject: [PATCH 0755/1551] Fix infinite recursion of expanded nodes in `UnreachableVisitor` (#13922) --- src/compiler/crystal/tools/unreachable.cr | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr index f70de18a0282..cd289dc8dcee 100644 --- a/src/compiler/crystal/tools/unreachable.cr +++ b/src/compiler/crystal/tools/unreachable.cr @@ -81,7 +81,7 @@ module Crystal class UnreachableVisitor < Visitor @used_def_locations = Set(Location).new @defs : Set(Def) = Set(Def).new.compare_by_identity - @visited_defs : Set(Def) = Set(Def).new.compare_by_identity + @visited : Set(ASTNode) = Set(ASTNode).new.compare_by_identity property includes = [] of String property excludes = [] of String @@ -113,6 +113,8 @@ module Crystal end def visit(node : ExpandableNode) + return false unless @visited.add?(node) + if expanded = node.expanded expanded.accept self end @@ -132,7 +134,7 @@ module Crystal @used_def_locations << location if interested_in(location) end - if @visited_defs.add?(a_def) + if @visited.add?(a_def) a_def.body.accept(self) end end From 3a1b8e39a453f674ba88308363fda72559e3a072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 5 Nov 2023 14:21:45 +0100 Subject: [PATCH 0756/1551] Refactor `UnreachablePresenter` (#13941) --- .../crystal/tools/unreachable_spec.cr | 8 +++--- src/compiler/crystal/tools/unreachable.cr | 27 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/spec/compiler/crystal/tools/unreachable_spec.cr b/spec/compiler/crystal/tools/unreachable_spec.cr index 46a297e8359b..6fffb17e4ca6 100644 --- a/spec/compiler/crystal/tools/unreachable_spec.cr +++ b/spec/compiler/crystal/tools/unreachable_spec.cr @@ -11,9 +11,7 @@ def processed_unreachable_visitor(code) visitor.excludes << Path[Dir.current].to_posix.to_s visitor.includes << "." - process_result = visitor.process(result) - - {visitor, process_result} + visitor.process(result) end private def assert_unreachable(code, file = __FILE__, line = __LINE__) @@ -27,9 +25,9 @@ private def assert_unreachable(code, file = __FILE__, line = __LINE__) code = code.gsub('༓', "") - visitor, result = processed_unreachable_visitor(code) + defs = processed_unreachable_visitor(code) - result_location = result.defs.try &.compact_map(&.location).sort_by! do |loc| + result_location = defs.try &.compact_map(&.location).sort_by! do |loc| {loc.filename.as(String), loc.line_number, loc.column_number} end.map(&.to_s) diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr index cd289dc8dcee..5c105ac94e4d 100644 --- a/src/compiler/crystal/tools/unreachable.cr +++ b/src/compiler/crystal/tools/unreachable.cr @@ -6,7 +6,6 @@ module Crystal class Command private def unreachable config, result = compile_no_codegen "tool unreachable", path_filter: true, unreachable_command: true - format = config.output_format unreachable = UnreachableVisitor.new @@ -16,7 +15,7 @@ module Crystal unreachable.excludes.concat config.excludes.map { |path| ::Path[path].expand.to_posix.to_s } defs = unreachable.process(result) - defs.defs.sort_by! do |a_def| + defs.sort_by! do |a_def| location = a_def.location.not_nil! { location.filename.as(String), @@ -25,22 +24,26 @@ module Crystal } end - case format - when "json" - defs.to_json(STDOUT) - else - defs.to_text(STDOUT) - end + UnreachablePresenter.new(defs, format: config.output_format).to_s(STDOUT) if config.check - exit 1 unless defs.defs.empty? + exit 1 unless defs.empty? end end end - record UnreachableResult, defs : Array(Def) do + record UnreachablePresenter, defs : Array(Def), format : String? do include JSON::Serializable + def to_s(io) + case format + when "json" + to_json(STDOUT) + else + to_text(STDOUT) + end + end + def to_text(io) defs.each do |a_def| io << a_def.location << "\t" @@ -98,14 +101,14 @@ module Crystal process_type(type.metaclass) if type.metaclass != type end - def process(result : Compiler::Result) + def process(result : Compiler::Result) : Array(Def) @defs.clear result.node.accept(self) process_type(result.program) - UnreachableResult.new @defs.to_a + @defs.to_a end def visit(node) From 97d9eb482b4470be2a156c920bce89e0280795fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 5 Nov 2023 18:59:39 +0100 Subject: [PATCH 0757/1551] Print relative paths in `crystal tool unreachable` (#13929) --- src/compiler/crystal/tools/unreachable.cr | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr index 5c105ac94e4d..e03d64f1becc 100644 --- a/src/compiler/crystal/tools/unreachable.cr +++ b/src/compiler/crystal/tools/unreachable.cr @@ -44,9 +44,19 @@ module Crystal end end - def to_text(io) + def each(&) + current_dir = Dir.current defs.each do |a_def| - io << a_def.location << "\t" + location = a_def.location.not_nil! + filename = ::Path[location.filename.as(String)].relative_to(current_dir).to_s + location = Location.new(filename, location.line_number, location.column_number) + yield a_def, location + end + end + + def to_text(io) + each do |a_def, location| + io << location << "\t" io << a_def.short_reference << "\t" io << a_def.length << " lines" if annotations = a_def.all_annotations @@ -59,10 +69,10 @@ module Crystal def to_json(builder : JSON::Builder) builder.array do - defs.each do |a_def| + each do |a_def, location| builder.object do builder.field "name", a_def.short_reference - builder.field "location", a_def.location.to_s + builder.field "location", location.to_s if lines = a_def.length builder.field "lines", lines end From 85204c4e6f34a940763dbd9e29ddaab714695c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 5 Nov 2023 19:02:24 +0100 Subject: [PATCH 0758/1551] Generalize allowed values for compiler CLI `--format` option (#13940) --- src/compiler/crystal/command.cr | 36 +++++++++------------- src/compiler/crystal/tools/dependencies.cr | 6 ++-- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index cdc98b7a96f5..d19e65a60e74 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -247,8 +247,8 @@ class Crystal::Command end end - private def compile_no_codegen(command, wants_doc = false, hierarchy = false, no_cleanup = false, cursor_command = false, top_level = false, path_filter = false, unreachable_command = false) - config = create_compiler command, no_codegen: true, hierarchy: hierarchy, cursor_command: cursor_command, path_filter: path_filter, unreachable_command: unreachable_command + private def compile_no_codegen(command, wants_doc = false, hierarchy = false, no_cleanup = false, cursor_command = false, top_level = false, path_filter = false, unreachable_command = false, allowed_formats = ["text", "json"]) + config = create_compiler command, no_codegen: true, hierarchy: hierarchy, cursor_command: cursor_command, path_filter: path_filter, unreachable_command: unreachable_command, allowed_formats: allowed_formats config.compiler.no_codegen = true config.compiler.no_cleanup = no_cleanup config.compiler.wants_doc = wants_doc @@ -344,8 +344,7 @@ class Crystal::Command specified_output : Bool, hierarchy_exp : String?, cursor_location : String?, - output_format : String?, - dependency_output_format : DependencyPrinter::Format, + output_format : String, combine_rpath : Bool, includes : Array(String), excludes : Array(String), @@ -364,7 +363,8 @@ class Crystal::Command private def create_compiler(command, no_codegen = false, run = false, hierarchy = false, cursor_command = false, single_file = false, dependencies = false, - path_filter = false, unreachable_command = false) + path_filter = false, unreachable_command = false, + allowed_formats = ["text", "json"]) compiler = new_compiler compiler.progress_tracker = @progress_tracker link_flags = [] of String @@ -377,7 +377,6 @@ class Crystal::Command hierarchy_exp = nil cursor_location = nil output_format = nil - dependency_output_format = nil excludes = [] of String includes = [] of String verbose = false @@ -426,11 +425,6 @@ class Crystal::Command end if dependencies - opts.on("-f tree|flat|dot|mermaid", "--format tree|flat|dot|mermaid", "Output format tree (default), flat, dot, or mermaid") do |f| - dependency_output_format = DependencyPrinter::Format.parse?(f) - error "Invalid format: #{f}. Options are: tree, flat, dot, or mermaid" unless dependency_output_format - end - opts.on("-i ", "--include ", "Include path") do |f| includes << f end @@ -442,10 +436,10 @@ class Crystal::Command opts.on("--verbose", "Show skipped and filtered paths") do verbose = true end - else - opts.on("-f text|json", "--format text|json", "Output format text (default) or json") do |f| - output_format = f - end + end + + opts.on("-f #{allowed_formats.join("|")}", "--format #{allowed_formats.join("|")}", "Output format: #{allowed_formats[0]} (default), #{allowed_formats[1..].join(", ")}") do |f| + output_format = f end if unreachable_command @@ -597,11 +591,9 @@ class Crystal::Command end end - dependency_output_format ||= DependencyPrinter::Format::Tree - - output_format ||= "text" - unless output_format.in?("text", "json") - error "You have input an invalid format, only text and JSON are supported" + output_format ||= allowed_formats[0] + unless output_format.in?(allowed_formats) + error "You have input an invalid format: #{output_format}. Supported formats: #{allowed_formats.join(", ")}" end error "maximum number of threads cannot be lower than 1" if compiler.n_threads < 1 @@ -616,8 +608,8 @@ class Crystal::Command combine_rpath = run && !no_codegen @config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, - arguments, specified_output, hierarchy_exp, cursor_location, output_format, - dependency_output_format.not_nil!, combine_rpath, includes, excludes, verbose, check + arguments, specified_output, hierarchy_exp, cursor_location, output_format.not_nil!, + combine_rpath, includes, excludes, verbose, check end private def gather_sources(filenames) diff --git a/src/compiler/crystal/tools/dependencies.cr b/src/compiler/crystal/tools/dependencies.cr index 745e7339f0ec..cfb26fbccc43 100644 --- a/src/compiler/crystal/tools/dependencies.cr +++ b/src/compiler/crystal/tools/dependencies.cr @@ -4,9 +4,9 @@ require "../syntax/ast" class Crystal::Command private def dependencies - config = create_compiler "tool dependencies", no_codegen: true, dependencies: true + config = create_compiler "tool dependencies", no_codegen: true, dependencies: true, allowed_formats: DependencyPrinter::Format.names.map!(&.downcase) - dependency_printer = DependencyPrinter.create(STDOUT, format: config.dependency_output_format, verbose: config.verbose) + dependency_printer = DependencyPrinter.create(STDOUT, format: DependencyPrinter::Format.parse(config.output_format), verbose: config.verbose) dependency_printer.includes.concat config.includes.map { |path| ::Path[path].expand.to_s } dependency_printer.excludes.concat config.excludes.map { |path| ::Path[path].expand.to_s } @@ -21,8 +21,8 @@ end module Crystal abstract class DependencyPrinter enum Format - Flat Tree + Flat Dot Mermaid end From 28452047632eb5986c3f5d1614e23860fcf66aa6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 6 Nov 2023 19:00:23 +0800 Subject: [PATCH 0759/1551] Replace some deprecated LLVM bindings (#13953) --- src/llvm/context.cr | 4 ++-- src/llvm/lib_llvm.cr | 10 +++++----- src/llvm/type.cr | 4 ++-- src/llvm/value_methods.cr | 5 +++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/llvm/context.cr b/src/llvm/context.cr index f226edcafcba..b04dd8c85b15 100644 --- a/src/llvm/context.cr +++ b/src/llvm/context.cr @@ -100,11 +100,11 @@ class LLVM::Context end def md_string(value : String) : Value - LLVM::Value.new LibLLVM.md_string_in_context(self, value, value.bytesize) + LLVM::Value.new LibLLVM.md_string_in_context2(self, value, value.bytesize) end def md_node(values : Array(Value)) : Value - Value.new LibLLVM.md_node_in_context(self, (values.to_unsafe.as(LibLLVM::ValueRef*)), values.size) + Value.new LibLLVM.md_node_in_context2(self, (values.to_unsafe.as(LibLLVM::ValueRef*)), values.size) end def parse_ir(buf : MemoryBuffer) diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index acc60746a018..cb023a327625 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -167,7 +167,7 @@ lib LibLLVM fun const_null = LLVMConstNull(ty : TypeRef) : ValueRef fun const_pointer_null = LLVMConstPointerNull(ty : TypeRef) : ValueRef fun const_real = LLVMConstReal(real_ty : TypeRef, n : Float64) : ValueRef - fun const_real_of_string = LLVMConstRealOfString(real_type : TypeRef, value : UInt8*) : ValueRef + fun const_real_of_string_and_size = LLVMConstRealOfStringAndSize(real_ty : TypeRef, text : Char*, s_len : UInt) : ValueRef fun count_param_types = LLVMCountParamTypes(function_type : TypeRef) : UInt32 fun create_generic_value_of_int = LLVMCreateGenericValueOfInt(ty : TypeRef, n : UInt64, is_signed : Int32) : GenericValueRef fun create_generic_value_of_pointer = LLVMCreateGenericValueOfPointer(p : Void*) : GenericValueRef @@ -212,7 +212,7 @@ lib LibLLVM fun normalize_target_triple = LLVMNormalizeTargetTriple(triple : Char*) : Char* fun get_type_kind = LLVMGetTypeKind(ty : TypeRef) : LLVM::Type::Kind fun get_undef = LLVMGetUndef(ty : TypeRef) : ValueRef - fun get_value_name = LLVMGetValueName(value : ValueRef) : UInt8* + fun get_value_name2 = LLVMGetValueName2(val : ValueRef, length : SizeT*) : Char* fun get_value_kind = LLVMGetValueKind(value : ValueRef) : LLVM::Value::Kind fun initialize_x86_asm_printer = LLVMInitializeX86AsmPrinter fun initialize_x86_asm_parser = LLVMInitializeX86AsmParser @@ -254,7 +254,7 @@ lib LibLLVM fun set_target = LLVMSetTarget(mod : ModuleRef, triple : UInt8*) fun set_thread_local = LLVMSetThreadLocal(global_var : ValueRef, is_thread_local : Int32) fun is_thread_local = LLVMIsThreadLocal(global_var : ValueRef) : Int32 - fun set_value_name = LLVMSetValueName(val : ValueRef, name : UInt8*) + fun set_value_name2 = LLVMSetValueName2(val : ValueRef, name : Char*, name_len : SizeT) fun set_personality_fn = LLVMSetPersonalityFn(fn : ValueRef, personality_fn : ValueRef) fun size_of = LLVMSizeOf(ty : TypeRef) : ValueRef fun size_of_type_in_bits = LLVMSizeOfTypeInBits(ref : TargetDataRef, ty : TypeRef) : UInt64 @@ -358,8 +358,8 @@ lib LibLLVM fun const_struct_in_context = LLVMConstStructInContext(c : ContextRef, constant_vals : ValueRef*, count : UInt32, packed : Int32) : ValueRef fun get_md_kind_id_in_context = LLVMGetMDKindIDInContext(c : ContextRef, name : UInt8*, slen : UInt32) : UInt32 - fun md_node_in_context = LLVMMDNodeInContext(c : ContextRef, values : ValueRef*, count : Int32) : ValueRef - fun md_string_in_context = LLVMMDStringInContext(c : ContextRef, str : UInt8*, length : Int32) : ValueRef + fun md_node_in_context2 = LLVMMDNodeInContext2(c : ContextRef, mds : ValueRef*, count : SizeT) : ValueRef + fun md_string_in_context2 = LLVMMDStringInContext2(c : ContextRef, str : Char*, s_len : SizeT) : ValueRef fun value_as_metadata = LLVMValueAsMetadata(val : ValueRef) : MetadataRef fun metadata_as_value = LLVMMetadataAsValue(c : ContextRef, md : MetadataRef) : ValueRef diff --git a/src/llvm/type.cr b/src/llvm/type.cr index 5f9fc70099b6..dc5f127492ab 100644 --- a/src/llvm/type.cr +++ b/src/llvm/type.cr @@ -149,7 +149,7 @@ struct LLVM::Type end def const_float(value : String) : Value - Value.new LibLLVM.const_real_of_string(self, value) + Value.new LibLLVM.const_real_of_string_and_size(self, value, value.bytesize) end def const_double(value : Float64) : Value @@ -157,7 +157,7 @@ struct LLVM::Type end def const_double(string : String) : Value - Value.new LibLLVM.const_real_of_string(self, string) + Value.new LibLLVM.const_real_of_string_and_size(self, string, string.bytesize) end def const_array(values : Array(LLVM::Value)) : Value diff --git a/src/llvm/value_methods.cr b/src/llvm/value_methods.cr index e76daf3d53e0..2daad60ec198 100644 --- a/src/llvm/value_methods.cr +++ b/src/llvm/value_methods.cr @@ -3,11 +3,12 @@ module LLVM::ValueMethods end def name=(name) - LibLLVM.set_value_name(self, name) + LibLLVM.set_value_name2(self, name, name.bytesize) end def name - String.new LibLLVM.get_value_name(self) + ptr = LibLLVM.get_value_name2(self, out len) + String.new(ptr, len) end def kind From 0daa835fb53c1976b3afbcecc3068b955f01e86c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 6 Nov 2023 19:00:31 +0800 Subject: [PATCH 0760/1551] Deprecate `LLVM.start_multithreaded` and `.stop_multithreaded` (#13949) --- src/llvm.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/llvm.cr b/src/llvm.cr index a8d7a6b77818..f33c4d31fb76 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -68,6 +68,7 @@ module LLVM {% end %} end + @[Deprecated("This method has no effect")] def self.start_multithreaded : Bool if multithreaded? true @@ -76,6 +77,7 @@ module LLVM end end + @[Deprecated("This method has no effect")] def self.stop_multithreaded if multithreaded? LibLLVM.stop_multithreaded From 67c85e3b889b175bb39a4b761e32140fab718b29 Mon Sep 17 00:00:00 2001 From: Evan Paterakis Date: Mon, 6 Nov 2023 13:01:37 +0200 Subject: [PATCH 0761/1551] Fix portable shell command arguments in `Process#prepare_args` (#13942) Co-authored-by: psykose --- src/crystal/system/unix/process.cr | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 0d4b3c1a9235..762507a590d4 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -211,17 +211,13 @@ struct Crystal::System::Process def self.prepare_args(command : String, args : Enumerable(String)?, shell : Bool) : Array(String) if shell command = %(#{command} "${@}") unless command.includes?(' ') - shell_args = ["/bin/sh", "-c", command, "--"] + shell_args = ["/bin/sh", "-c", command, "sh"] if args unless command.includes?(%("${@}")) raise ArgumentError.new(%(Can't specify arguments in both command and args without including "${@}" into your command)) end - {% if flag?(:freebsd) || flag?(:dragonfly) %} - shell_args << "" - {% end %} - shell_args.concat(args) end From 6aa34ce2830ecc1695f343e7b2a488b1c77b6a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 6 Nov 2023 18:31:14 +0100 Subject: [PATCH 0762/1551] Improve performance of `JSON::Builder#string` with direct stringification (#13950) Co-authored-by: Sijawusz Pur Rahnama --- src/json/builder.cr | 91 ++++++++++++++++++++++++++++++--------------- src/json/to_json.cr | 8 +++- src/uri/json.cr | 2 +- src/uuid/json.cr | 2 +- 4 files changed, 70 insertions(+), 33 deletions(-) diff --git a/src/json/builder.cr b/src/json/builder.cr index d6061680970d..9bc074f7c6ef 100644 --- a/src/json/builder.cr +++ b/src/json/builder.cr @@ -25,6 +25,7 @@ class JSON::Builder def initialize(@io : IO) @state = [StartState.new] of State @current_indent = 0 + @escape = Escape.new(io) end # Starts a document. @@ -96,40 +97,74 @@ class JSON::Builder end end - # Writes a string. The given *value* is first converted to a `String` - # by invoking `to_s` on it. + # Writes a string with the content of `value`. + # The payload is stringified via `to_s(IO)` and escaped for JSON representation. + # + # ``` + # JSON.build do |builder| + # builder.string("foo") + # end # => %("foo") + # JSON.build do |builder| + # builder.string([1, 2]) + # end # => %("[1, 2]") + # ``` # # This method can also be used to write the name of an object field. def string(value) : Nil - string = value.to_s + string do |io| + value.to_s(io) + end + end + # Writes a string with the contents written to the yielded `IO`. + # The payload gets escaped for JSON representation. + # + # ``` + # JSON.build do |builder| + # builder.string do |io| + # io << "foo" + # io << [1, 2] + # end # => %("foo[1, 2]") + # end + # ``` + # + # This method can also be used to write the name of an object field. + def string(& : IO ->) : Nil scalar(string: true) do io << '"' + yield @escape + io << '"' + end + end + + private class Escape < IO + def initialize(@io : IO) + end - cursor = start = string.to_unsafe - fin = cursor + string.bytesize + delegate :flush, :tty?, :pos, :pos=, :seek, to: @io + + def read(slice : Bytes) + raise "" + end + + def write(slice : Bytes) : Nil + cursor = start = slice.to_unsafe + fin = cursor + slice.bytesize while cursor < fin case byte = cursor.value - when '\\' - escape = "\\\\" - when '"' - escape = "\\\"" - when '\b' - escape = "\\b" - when '\f' - escape = "\\f" - when '\n' - escape = "\\n" - when '\r' - escape = "\\r" - when '\t' - escape = "\\t" - when .<(0x20), 0x7F # Char#ascii_control? - io.write_string Slice.new(start, cursor - start) - io << "\\u00" - io << '0' if byte < 0x10 - byte.to_s(io, 16) + when '\\' then escape = "\\\\" + when '"' then escape = "\\\"" + when '\b' then escape = "\\b" + when '\f' then escape = "\\f" + when '\n' then escape = "\\n" + when '\r' then escape = "\\r" + when '\t' then escape = "\\t" + when .<(0x20), 0x7f # Char#ascii_control? + @io.write_string Slice.new(start, cursor - start) + @io << "\\u00" + @io << '0' if byte < 0x10 + byte.to_s(@io, 16) cursor += 1 start = cursor next @@ -138,15 +173,13 @@ class JSON::Builder next end - io.write_string Slice.new(start, cursor - start) - io << escape + @io.write_string Slice.new(start, cursor - start) + @io << escape cursor += 1 start = cursor end - io.write_string Slice.new(start, cursor - start) - - io << '"' + @io.write_string Slice.new(start, cursor - start) end end diff --git a/src/json/to_json.cr b/src/json/to_json.cr index 18d4df75ec2f..8a0ee613d178 100644 --- a/src/json/to_json.cr +++ b/src/json/to_json.cr @@ -165,7 +165,9 @@ end struct Time::Format def to_json(value : Time, json : JSON::Builder) : Nil - format(value).to_json(json) + json.string do |io| + format(value, io) + end end end @@ -274,7 +276,9 @@ struct Time # # See `#from_json` for reference. def to_json(json : JSON::Builder) : Nil - json.string(Time::Format::RFC_3339.format(self, fraction_digits: 0)) + json.string do |io| + Time::Format::RFC_3339.format(self, io, fraction_digits: 0) + end end end diff --git a/src/uri/json.cr b/src/uri/json.cr index 30179ffc6ed5..9767c9e98a02 100644 --- a/src/uri/json.cr +++ b/src/uri/json.cr @@ -23,6 +23,6 @@ class URI # URI.parse("http://example.com").to_json # => %("http://example.com") # ``` def to_json(builder : JSON::Builder) - builder.string to_s + builder.string self end end diff --git a/src/uuid/json.cr b/src/uuid/json.cr index 040668f4a6b8..966ee0cabfc9 100644 --- a/src/uuid/json.cr +++ b/src/uuid/json.cr @@ -33,7 +33,7 @@ struct UUID # uuid.to_json # => "\"87b3042b-9b9a-41b7-8b15-a93d3f17025e\"" # ``` def to_json(json : JSON::Builder) : Nil - json.string(to_s) + json.string(self) end # :nodoc: From e838701f16c16d200e22e07f1bd3cca77317e6bf Mon Sep 17 00:00:00 2001 From: Kostya M Date: Mon, 6 Nov 2023 20:31:41 +0300 Subject: [PATCH 0763/1551] Add incremental optimization levels (#13464) --- src/compiler/crystal/codegen/target.cr | 9 ++- src/compiler/crystal/command.cr | 14 +++-- src/compiler/crystal/compiler.cr | 77 +++++++++++++++++++++----- src/compiler/crystal/macros/macros.cr | 2 +- 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/src/compiler/crystal/codegen/target.cr b/src/compiler/crystal/codegen/target.cr index 51881c5a3571..1e731d801b58 100644 --- a/src/compiler/crystal/codegen/target.cr +++ b/src/compiler/crystal/codegen/target.cr @@ -164,7 +164,7 @@ class Crystal::Codegen::Target environment_parts.any? &.in?("gnueabihf", "musleabihf") end - def to_target_machine(cpu = "", features = "", release = false, + def to_target_machine(cpu = "", features = "", optimization_mode = Compiler::OptimizationMode::O0, code_model = LLVM::CodeModel::Default) : LLVM::TargetMachine case @architecture when "i386", "x86_64" @@ -186,7 +186,12 @@ class Crystal::Codegen::Target raise Target::Error.new("Unsupported architecture for target triple: #{self}") end - opt_level = release ? LLVM::CodeGenOptLevel::Aggressive : LLVM::CodeGenOptLevel::None + opt_level = case optimization_mode + in .o3? then LLVM::CodeGenOptLevel::Aggressive + in .o2? then LLVM::CodeGenOptLevel::Default + in .o1? then LLVM::CodeGenOptLevel::Less + in .o0? then LLVM::CodeGenOptLevel::None + end target = LLVM::Target.from_triple(self.to_s) machine = target.create_target_machine(self.to_s, cpu: cpu, features: features, opt_level: opt_level, code_model: code_model).not_nil! diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index d19e65a60e74..8341b41d9f3b 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -499,8 +499,11 @@ class Crystal::Command end unless no_codegen - opts.on("--release", "Compile in release mode") do - compiler.release = true + opts.on("--release", "Compile in release mode (-O3 --single-module)") do + compiler.release! + end + opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3") do |level| + compiler.optimization_mode = Compiler::OptimizationMode.from_value?(level.to_i) || raise Error.new("Unknown optimization mode #{level}") end end @@ -633,8 +636,11 @@ class Crystal::Command @error_trace = true compiler.show_error_trace = true end - opts.on("--release", "Compile in release mode") do - compiler.release = true + opts.on("--release", "Compile in release mode (-O3 --single-module)") do + compiler.release! + end + opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3") do |level| + compiler.optimization_mode = Compiler::OptimizationMode.from_value?(level.to_i) || raise Error.new("Unknown optimization mode #{level}") end opts.on("-s", "--stats", "Enable statistics output") do compiler.progress_tracker.stats = true diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 1099569c97aa..237783c2ed8c 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -80,14 +80,35 @@ module Crystal # the source file to compile. property prelude = "prelude" - # If `true`, runs LLVM optimizations. - property? release = false + # Optimization mode + enum OptimizationMode + # [default] no optimization, fastest compilation, slowest runtime + O0 = 0 + + # low, compilation slower than O0, runtime faster than O0 + O1 = 1 + + # middle, compilation slower than O1, runtime faster than O1 + O2 = 2 + + # high, slowest compilation, fastest runtime + # enables with --release flag + O3 = 3 + + def suffix + ".#{to_s.downcase}" + end + end + + # Sets the Optimization mode. + property optimization_mode = OptimizationMode::O0 # Sets the code model. Check LLVM docs to learn about this. property mcmodel = LLVM::CodeModel::Default # If `true`, generates a single LLVM module. By default # one LLVM module is created for each type in a program. + # --release automatically enable this option property? single_module = false # A `ProgressTracker` object which tracks compilation progress. @@ -202,6 +223,16 @@ module Crystal Result.new program, node end + # Set maximum level of optimization. + def release! + @optimization_mode = OptimizationMode::O3 + @single_module = true + end + + def release? + @optimization_mode.o3? && @single_module + end + private def new_program(sources) @program = program = Program.new program.compiler = self @@ -254,8 +285,8 @@ module Crystal private def bc_flags_changed?(output_dir) bc_flags_changed = true - current_bc_flags = "#{@codegen_target}|#{@mcpu}|#{@mattr}|#{@release}|#{@link_flags}|#{@mcmodel}" - bc_flags_filename = "#{output_dir}/bc_flags" + current_bc_flags = "#{@codegen_target}|#{@mcpu}|#{@mattr}|#{@link_flags}|#{@mcmodel}" + bc_flags_filename = "#{output_dir}/bc_flags#{optimization_mode.suffix}" if File.file?(bc_flags_filename) previous_bc_flags = File.read(bc_flags_filename).strip bc_flags_changed = previous_bc_flags != current_bc_flags @@ -266,7 +297,7 @@ module Crystal private def codegen(program, node : ASTNode, sources, output_filename) llvm_modules = @progress_tracker.stage("Codegen (crystal)") do - program.codegen node, debug: debug, single_module: @single_module || @release || @cross_compile || !@emit_targets.none? + program.codegen node, debug: debug, single_module: @single_module || @cross_compile || !@emit_targets.none? end output_dir = CacheDir.instance.directory_for(sources) @@ -319,7 +350,7 @@ module Crystal llvm_mod = unit.llvm_mod @progress_tracker.stage("Codegen (bc+obj)") do - optimize llvm_mod if @release + optimize llvm_mod unless @optimization_mode.o0? unit.emit(@emit_targets, emit_base_filename || output_filename) @@ -581,7 +612,7 @@ module Crystal end getter(target_machine : LLVM::TargetMachine) do - @codegen_target.to_target_machine(@mcpu || "", @mattr || "", @release, @mcmodel) + @codegen_target.to_target_machine(@mcpu || "", @mattr || "", @optimization_mode, @mcmodel) rescue ex : ArgumentError stderr.print colorize("Error: ").red.bold stderr.print "llc: " @@ -615,16 +646,35 @@ module Crystal registry.initialize_all builder = LLVM::PassManagerBuilder.new - builder.opt_level = 3 + case optimization_mode + in .o3? + builder.opt_level = 3 + builder.use_inliner_with_threshold = 275 + in .o2? + builder.opt_level = 2 + builder.use_inliner_with_threshold = 275 + in .o1? + builder.opt_level = 1 + builder.use_inliner_with_threshold = 150 + in .o0? + # default behaviour, no optimizations + end + builder.size_level = 0 - builder.use_inliner_with_threshold = 275 + builder end end {% else %} protected def optimize(llvm_mod) LLVM::PassBuilderOptions.new do |options| - LLVM.run_passes(llvm_mod, "default", target_machine, options) + mode = case @optimization_mode + in .o3? then "default" + in .o2? then "default" + in .o1? then "default" + in .o0? then "default" + end + LLVM.run_passes(llvm_mod, mode, target_machine, options) end end {% end %} @@ -713,6 +763,7 @@ module Crystal @name = "#{@name[0..16]}-#{::Crystal::Digest::MD5.hexdigest(@name)}" end + @name = "#{@name}#{@compiler.optimization_mode.suffix}" @object_extension = compiler.codegen_target.object_extension end @@ -730,8 +781,8 @@ module Crystal # in the cache directory. # # On a next compilation of the same project, and if the compile - # flags didn't change (a combination of the target triple, mcpu, - # release and link flags, amongst others), we check if the new + # flags didn't change (a combination of the target triple, mcpu + # and link flags, amongst others), we check if the new # `.bc` file is exactly the same as the old one. In that case # the `.o` file will also be the same, so we simply reuse the # old one. Generating an `.o` file is what takes most time. @@ -775,7 +826,7 @@ module Crystal end if must_compile - compiler.optimize llvm_mod if compiler.release? + compiler.optimize llvm_mod unless compiler.optimization_mode.o0? compiler.target_machine.emit_obj_to_file llvm_mod, temporary_object_name File.rename(temporary_object_name, object_name) else diff --git a/src/compiler/crystal/macros/macros.cr b/src/compiler/crystal/macros/macros.cr index 57c3711c4a63..42ebb6004bb3 100644 --- a/src/compiler/crystal/macros/macros.cr +++ b/src/compiler/crystal/macros/macros.cr @@ -145,7 +145,7 @@ class Crystal::Program # Although release takes longer, once the bc is cached in .crystal # the subsequent times will make program execution faster. - host_compiler.release = true + host_compiler.release! # Don't cleanup old directories after compiling: it might happen # that in doing so we remove the directory associated with the current From 2173371b4485db90a750d4a47752b2dab9e067a1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 8 Nov 2023 06:43:54 +0800 Subject: [PATCH 0764/1551] Deprecate the splat operators in macro expressions (#13939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/compiler/crystal/interpreter/compiler.cr | 6 +----- src/compiler/crystal/macros/interpreter.cr | 2 ++ src/macros.cr | 20 ++++++++++---------- src/object.cr | 6 +++--- src/regex.cr | 2 +- src/slice.cr | 2 +- src/static_array.cr | 2 +- src/syscall/aarch64-linux.cr | 2 +- src/syscall/arm-linux.cr | 2 +- src/syscall/i386-linux.cr | 2 +- src/syscall/x86_64-linux.cr | 2 +- src/xml/builder.cr | 2 +- src/yaml/builder.cr | 2 +- 13 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index eddc87d65132..1faf5db32bb6 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -3145,11 +3145,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor {% operands = instruction[:operands] || [] of Nil %} def {{name.id}}( - {% if operands.empty? %} - *, node : ASTNode? - {% else %} - {{*operands}}, *, node : ASTNode? - {% end %} + {{operands.splat(", ")}}*, node : ASTNode? ) : Nil node = @node_override || node @instructions.nodes[instructions_index] = node if node diff --git a/src/compiler/crystal/macros/interpreter.cr b/src/compiler/crystal/macros/interpreter.cr index ac07bc17bb9e..d9f5473ec9f2 100644 --- a/src/compiler/crystal/macros/interpreter.cr +++ b/src/compiler/crystal/macros/interpreter.cr @@ -511,12 +511,14 @@ module Crystal end def visit(node : Splat) + warnings.add_warning(node, "Deprecated use of splat operator. Use `#splat` instead") node.exp.accept self @last = @last.interpret("splat", [] of ASTNode, nil, nil, self, node.location) false end def visit(node : DoubleSplat) + warnings.add_warning(node, "Deprecated use of double splat operator. Use `#double_splat` instead") node.exp.accept self @last = @last.interpret("double_splat", [] of ASTNode, nil, nil, self, node.location) false diff --git a/src/macros.cr b/src/macros.cr index e8163cbec046..c01a5d35e391 100644 --- a/src/macros.cr +++ b/src/macros.cr @@ -82,16 +82,16 @@ macro record(__name name, *properties, **kwargs) {% end %} def initialize({{ - *properties.map do |field| + properties.map do |field| "@#{field.id}".id - end + end.splat }}) end {{yield}} def copy_with({{ - *properties.map do |property| + properties.map do |property| if property.is_a?(Assign) "#{property.target.id} _#{property.target.id} = @#{property.target.id}".id elsif property.is_a?(TypeDeclaration) @@ -99,10 +99,10 @@ macro record(__name name, *properties, **kwargs) else "#{property.id} _#{property.id} = @#{property.id}".id end - end + end.splat }}) self.class.new({{ - *properties.map do |property| + properties.map do |property| if property.is_a?(Assign) "_#{property.target.id}".id elsif property.is_a?(TypeDeclaration) @@ -110,13 +110,13 @@ macro record(__name name, *properties, **kwargs) else "_#{property.id}".id end - end + end.splat }}) end def clone self.class.new({{ - *properties.map do |property| + properties.map do |property| if property.is_a?(Assign) "@#{property.target.id}.clone".id elsif property.is_a?(TypeDeclaration) @@ -124,7 +124,7 @@ macro record(__name name, *properties, **kwargs) else "@#{property.id}.clone".id end - end + end.splat }}) end end @@ -150,7 +150,7 @@ macro pp!(*exps) ::print %prefix ::pp({{exp}}) {% else %} - %names = { {{*exps.map(&.stringify)}} } + %names = { {{exps.map(&.stringify).splat}} } %max_size = %names.max_of &.size { {% for exp, i in exps %} @@ -184,7 +184,7 @@ macro p!(*exps) ::print %prefix ::p({{exp}}) {% else %} - %names = { {{*exps.map(&.stringify)}} } + %names = { {{exps.map(&.stringify).splat}} } %max_size = %names.max_of &.size { {% for exp, i in exps %} diff --git a/src/object.cr b/src/object.cr index b3b06872a396..6687e19f424a 100644 --- a/src/object.cr +++ b/src/object.cr @@ -1086,7 +1086,7 @@ class Object # end # ``` macro {{macro_prefix}}property!(*names) - {{macro_prefix}}getter! \{{*names}} + {{macro_prefix}}getter! \{{names.splat}} \{% for name in names %} \{% if name.is_a?(TypeDeclaration) %} @@ -1377,8 +1377,8 @@ class Object # end # ``` macro def_equals_and_hash(*fields) - def_equals {{*fields}} - def_hash {{*fields}} + def_equals {{fields.splat}} + def_hash {{fields.splat}} end # Forwards missing methods to *delegate*. diff --git a/src/regex.cr b/src/regex.cr index 68d25f5d6be8..b1532decee64 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -403,7 +403,7 @@ class Regex str.each_byte do |byte| {% begin %} case byte.unsafe_chr - when {{*SPECIAL_CHARACTERS}} + when {{SPECIAL_CHARACTERS.splat}} result << '\\' result.write_byte byte else diff --git a/src/slice.cr b/src/slice.cr index ac83eacae99a..27584c5d1a68 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -37,7 +37,7 @@ struct Slice(T) {% if @type.name != "Slice(T)" && T < Number %} {{T}}.slice({{args.splat(", ")}}read_only: {{read_only}}) {% else %} - %ptr = Pointer(typeof({{*args}})).malloc({{args.size}}) + %ptr = Pointer(typeof({{args.splat}})).malloc({{args.size}}) {% for arg, i in args %} %ptr[{{i}}] = {{arg}} {% end %} diff --git a/src/static_array.cr b/src/static_array.cr index 0bfaa006e8b8..4cb2b186f200 100644 --- a/src/static_array.cr +++ b/src/static_array.cr @@ -50,7 +50,7 @@ struct StaticArray(T, N) # * `Number.static_array` is a convenient alternative for designating a # specific numerical item type. macro [](*args) - %array = uninitialized StaticArray(typeof({{*args}}), {{args.size}}) + %array = uninitialized StaticArray(typeof({{args.splat}}), {{args.size}}) {% for arg, i in args %} %array.to_unsafe[{{i}}] = {{arg}} {% end %} diff --git a/src/syscall/aarch64-linux.cr b/src/syscall/aarch64-linux.cr index aa0fc5dc36ef..5a61e8e7eed8 100644 --- a/src/syscall/aarch64-linux.cr +++ b/src/syscall/aarch64-linux.cr @@ -335,7 +335,7 @@ module Syscall macro def_syscall(name, return_type, *args) @[AlwaysInline] - def self.{{name.id}}({{*args}}) : {{return_type}} + def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} {% if args.size == 0 %} diff --git a/src/syscall/arm-linux.cr b/src/syscall/arm-linux.cr index 128417ca4508..97119fc4b3f3 100644 --- a/src/syscall/arm-linux.cr +++ b/src/syscall/arm-linux.cr @@ -410,7 +410,7 @@ module Syscall macro def_syscall(name, return_type, *args) @[AlwaysInline] - def self.{{name.id}}({{*args}}) : {{return_type}} + def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} {% if args.size == 0 %} diff --git a/src/syscall/i386-linux.cr b/src/syscall/i386-linux.cr index 3788005768bd..843b2d1fd856 100644 --- a/src/syscall/i386-linux.cr +++ b/src/syscall/i386-linux.cr @@ -446,7 +446,7 @@ module Syscall macro def_syscall(name, return_type, *args) @[AlwaysInline] - def self.{{name.id}}({{*args}}) : {{return_type}} + def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} {% if args.size == 0 %} diff --git a/src/syscall/x86_64-linux.cr b/src/syscall/x86_64-linux.cr index 4dfac42decec..1f01c9226658 100644 --- a/src/syscall/x86_64-linux.cr +++ b/src/syscall/x86_64-linux.cr @@ -369,7 +369,7 @@ module Syscall macro def_syscall(name, return_type, *args) @[AlwaysInline] - def self.{{name.id}}({{*args}}) : {{return_type}} + def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} {% if args.size == 0 %} diff --git a/src/xml/builder.cr b/src/xml/builder.cr index 1ebe23efcd47..54e5f45adddc 100644 --- a/src/xml/builder.cr +++ b/src/xml/builder.cr @@ -283,7 +283,7 @@ class XML::Builder end private macro call(name, *args) - ret = LibXML.xmlTextWriter{{name}}(@writer, {{*args}}) + ret = LibXML.xmlTextWriter{{name}}(@writer, {{args.splat}}) check ret, {{@def.name.stringify}} end diff --git a/src/yaml/builder.cr b/src/yaml/builder.cr index 2764580dde13..79bbd967490d 100644 --- a/src/yaml/builder.cr +++ b/src/yaml/builder.cr @@ -199,7 +199,7 @@ class YAML::Builder end private macro emit(event_name, *args) - LibYAML.yaml_{{event_name}}_event_initialize(pointerof(@event), {{*args}}) + LibYAML.yaml_{{event_name}}_event_initialize(pointerof(@event), {{args.splat}}) yaml_emit({{event_name.stringify}}) end From de7d333cecd48817adfa82576a2660c17f10a521 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 8 Nov 2023 06:44:13 +0800 Subject: [PATCH 0765/1551] Split `LibLLVM` by C headers (#13948) --- src/llvm/generic_value.cr | 4 +- src/llvm/lib_llvm.cr | 501 +----------------- src/llvm/lib_llvm/analysis.cr | 5 + src/llvm/lib_llvm/bit_writer.cr | 7 + src/llvm/lib_llvm/core.cr | 287 ++++++++++ src/llvm/lib_llvm/debug_info.cr | 125 +++++ src/llvm/lib_llvm/error.cr | 3 + src/llvm/lib_llvm/execution_engine.cr | 32 ++ src/llvm/lib_llvm/initialization.cr | 18 + src/llvm/lib_llvm/ir_reader.cr | 5 + src/llvm/lib_llvm/target.cr | 34 ++ src/llvm/lib_llvm/target_machine.cr | 24 + src/llvm/lib_llvm/transforms/pass_builder.cr | 13 + .../transforms/pass_manager_builder.cr | 17 + src/llvm/lib_llvm/types.cr | 19 + src/llvm/lib_llvm_ext.cr | 2 +- 16 files changed, 597 insertions(+), 499 deletions(-) create mode 100644 src/llvm/lib_llvm/analysis.cr create mode 100644 src/llvm/lib_llvm/bit_writer.cr create mode 100644 src/llvm/lib_llvm/core.cr create mode 100644 src/llvm/lib_llvm/debug_info.cr create mode 100644 src/llvm/lib_llvm/error.cr create mode 100644 src/llvm/lib_llvm/execution_engine.cr create mode 100644 src/llvm/lib_llvm/initialization.cr create mode 100644 src/llvm/lib_llvm/ir_reader.cr create mode 100644 src/llvm/lib_llvm/target.cr create mode 100644 src/llvm/lib_llvm/target_machine.cr create mode 100644 src/llvm/lib_llvm/transforms/pass_builder.cr create mode 100644 src/llvm/lib_llvm/transforms/pass_manager_builder.cr create mode 100644 src/llvm/lib_llvm/types.cr diff --git a/src/llvm/generic_value.cr b/src/llvm/generic_value.cr index 15f9d378c75e..e168567e88f2 100644 --- a/src/llvm/generic_value.cr +++ b/src/llvm/generic_value.cr @@ -7,11 +7,11 @@ class LLVM::GenericValue end def to_i64 : Int64 - LibLLVM.generic_value_to_int(self, signed: 1).unsafe_as(Int64) + LibLLVM.generic_value_to_int(self, is_signed: 1).to_i64! end def to_u64 : UInt64 - LibLLVM.generic_value_to_int(self, signed: 0) + LibLLVM.generic_value_to_int(self, is_signed: 0) end def to_b : Bool diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index cb023a327625..3acff22ef324 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -47,501 +47,10 @@ lib LibLLVM alias Char = LibC::Char alias Int = LibC::Int alias UInt = LibC::UInt + alias LongLong = LibC::LongLong + alias ULongLong = LibC::ULongLong + alias Double = LibC::Double alias SizeT = LibC::SizeT - - type ContextRef = Void* - type ModuleRef = Void* - type MetadataRef = Void* - type TypeRef = Void* - type ValueRef = Void* - type BasicBlockRef = Void* - type BuilderRef = Void* - type ExecutionEngineRef = Void* - type GenericValueRef = Void* - type TargetRef = Void* - type TargetDataRef = Void* - type TargetMachineRef = Void* - type MemoryBufferRef = Void* - type PassBuilderOptionsRef = Void* - type ErrorRef = Void* - type DIBuilderRef = Void* - - struct JITCompilerOptions - opt_level : UInt32 - code_model : LLVM::CodeModel - no_frame_pointer_elim : Int32 - enable_fast_isel : Int32 - end - - enum InlineAsmDialect - ATT - Intel - end - - # NOTE: the following C enums usually have different values from their C++ - # counterparts (e.g. `LLVMModuleFlagBehavior` v.s. `LLVM::Module::ModFlagBehavior`) - - enum ModuleFlagBehavior - Warning = 1 - end - - enum DWARFEmissionKind - Full = 1 - end - - fun add_case = LLVMAddCase(switch : ValueRef, onval : ValueRef, dest : BasicBlockRef) - fun add_clause = LLVMAddClause(lpad : ValueRef, clause_val : ValueRef) - fun add_function = LLVMAddFunction(module : ModuleRef, name : UInt8*, type : TypeRef) : ValueRef - fun add_global = LLVMAddGlobal(module : ModuleRef, type : TypeRef, name : UInt8*) : ValueRef - fun add_handler = LLVMAddHandler(catch_switch : ValueRef, dest : BasicBlockRef) - fun add_incoming = LLVMAddIncoming(phi_node : ValueRef, incoming_values : ValueRef*, incoming_blocks : BasicBlockRef*, count : Int32) - fun add_module_flag = LLVMAddModuleFlag(mod : ModuleRef, behavior : ModuleFlagBehavior, key : UInt8*, len : LibC::SizeT, val : MetadataRef) - fun add_target_dependent_function_attr = LLVMAddTargetDependentFunctionAttr(fn : ValueRef, a : LibC::Char*, v : LibC::Char*) - fun array_type = LLVMArrayType(element_type : TypeRef, count : UInt32) : TypeRef - fun vector_type = LLVMVectorType(element_type : TypeRef, count : UInt32) : TypeRef - fun build_va_arg = LLVMBuildVAArg(builder : BuilderRef, list : ValueRef, type : TypeRef, name : UInt8*) : ValueRef - fun build_add = LLVMBuildAdd(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_alloca = LLVMBuildAlloca(builder : BuilderRef, type : TypeRef, name : UInt8*) : ValueRef - fun build_and = LLVMBuildAnd(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_array_malloc = LLVMBuildArrayMalloc(builder : BuilderRef, type : TypeRef, val : ValueRef, name : UInt8*) : ValueRef - fun build_ashr = LLVMBuildAShr(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_atomicrmw = LLVMBuildAtomicRMW(builder : BuilderRef, op : LLVM::AtomicRMWBinOp, ptr : ValueRef, val : ValueRef, ordering : LLVM::AtomicOrdering, singlethread : Int32) : ValueRef - fun build_atomic_cmp_xchg = LLVMBuildAtomicCmpXchg(builder : BuilderRef, ptr : ValueRef, cmp : ValueRef, new : ValueRef, success_ordering : LLVM::AtomicOrdering, failure_ordering : LLVM::AtomicOrdering, single_thread : Int) : ValueRef - fun build_bit_cast = LLVMBuildBitCast(builder : BuilderRef, value : ValueRef, type : TypeRef, name : UInt8*) : ValueRef - fun build_br = LLVMBuildBr(builder : BuilderRef, block : BasicBlockRef) : ValueRef - fun build_call2 = LLVMBuildCall2(builder : BuilderRef, type : TypeRef, fn : ValueRef, args : ValueRef*, num_args : Int32, name : UInt8*) : ValueRef - fun build_catch_pad = LLVMBuildCatchPad(b : BuilderRef, parent_pad : ValueRef, args : ValueRef*, num_args : UInt, name : Char*) : ValueRef - fun build_catch_ret = LLVMBuildCatchRet(b : BuilderRef, catch_pad : ValueRef, bb : BasicBlockRef) : ValueRef - fun build_catch_switch = LLVMBuildCatchSwitch(b : BuilderRef, parent_pad : ValueRef, unwind_bb : BasicBlockRef, num_handlers : UInt, name : Char*) : ValueRef - fun build_cond = LLVMBuildCondBr(builder : BuilderRef, if : ValueRef, then : BasicBlockRef, else : BasicBlockRef) : ValueRef - fun build_exact_sdiv = LLVMBuildExactSDiv(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_extract_value = LLVMBuildExtractValue(builder : BuilderRef, agg_val : ValueRef, index : UInt32, name : UInt8*) : ValueRef - fun build_fadd = LLVMBuildFAdd(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_fcmp = LLVMBuildFCmp(builder : BuilderRef, op : LLVM::RealPredicate, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_fdiv = LLVMBuildFDiv(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_fence = LLVMBuildFence(builder : BuilderRef, ordering : LLVM::AtomicOrdering, singlethread : UInt32, name : UInt8*) : ValueRef - fun build_fmul = LLVMBuildFMul(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_fp2si = LLVMBuildFPToSI(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_fp2ui = LLVMBuildFPToUI(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_fpext = LLVMBuildFPExt(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_fptrunc = LLVMBuildFPTrunc(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_fsub = LLVMBuildFSub(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_gep2 = LLVMBuildGEP2(builder : BuilderRef, ty : TypeRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef - fun build_inbounds_gep2 = LLVMBuildInBoundsGEP2(builder : BuilderRef, ty : TypeRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt32, name : UInt8*) : ValueRef - fun build_global_string_ptr = LLVMBuildGlobalStringPtr(builder : BuilderRef, str : UInt8*, name : UInt8*) : ValueRef - fun build_icmp = LLVMBuildICmp(builder : BuilderRef, op : LLVM::IntPredicate, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_int2ptr = LLVMBuildIntToPtr(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_invoke2 = LLVMBuildInvoke2(builder : BuilderRef, ty : TypeRef, fn : ValueRef, args : ValueRef*, num_args : UInt32, then : BasicBlockRef, catch : BasicBlockRef, name : UInt8*) : ValueRef - fun build_landing_pad = LLVMBuildLandingPad(builder : BuilderRef, ty : TypeRef, pers_fn : ValueRef, num_clauses : UInt32, name : UInt8*) : ValueRef - fun build_load2 = LLVMBuildLoad2(builder : BuilderRef, ty : TypeRef, ptr : ValueRef, name : UInt8*) : ValueRef - fun build_lshr = LLVMBuildLShr(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_malloc = LLVMBuildMalloc(builder : BuilderRef, type : TypeRef, name : UInt8*) : ValueRef - fun build_mul = LLVMBuildMul(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_not = LLVMBuildNot(builder : BuilderRef, value : ValueRef, name : UInt8*) : ValueRef - fun build_or = LLVMBuildOr(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_phi = LLVMBuildPhi(builder : BuilderRef, type : TypeRef, name : UInt8*) : ValueRef - fun build_ptr2int = LLVMBuildPtrToInt(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_ret = LLVMBuildRet(builder : BuilderRef, value : ValueRef) : ValueRef - fun build_ret_void = LLVMBuildRetVoid(builder : BuilderRef) : ValueRef - fun build_sdiv = LLVMBuildSDiv(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_select = LLVMBuildSelect(builder : BuilderRef, if_value : ValueRef, then_value : ValueRef, else_value : ValueRef, name : UInt8*) : ValueRef - fun build_sext = LLVMBuildSExt(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_shl = LLVMBuildShl(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_si2fp = LLVMBuildSIToFP(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_si2fp = LLVMBuildSIToFP(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_srem = LLVMBuildSRem(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_store = LLVMBuildStore(builder : BuilderRef, value : ValueRef, ptr : ValueRef) : ValueRef - fun build_sub = LLVMBuildSub(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_switch = LLVMBuildSwitch(builder : BuilderRef, value : ValueRef, otherwise : BasicBlockRef, num_cases : UInt32) : ValueRef - fun build_trunc = LLVMBuildTrunc(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_udiv = LLVMBuildUDiv(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_ui2fp = LLVMBuildSIToFP(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_ui2fp = LLVMBuildUIToFP(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun build_unreachable = LLVMBuildUnreachable(builder : BuilderRef) : ValueRef - fun build_urem = LLVMBuildURem(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_xor = LLVMBuildXor(builder : BuilderRef, lhs : ValueRef, rhs : ValueRef, name : UInt8*) : ValueRef - fun build_zext = LLVMBuildZExt(builder : BuilderRef, val : ValueRef, dest_ty : TypeRef, name : UInt8*) : ValueRef - fun const_array = LLVMConstArray(element_type : TypeRef, constant_vals : ValueRef*, length : UInt32) : ValueRef - fun const_int = LLVMConstInt(int_type : TypeRef, value : UInt64, sign_extend : Int32) : ValueRef - fun const_int_of_arbitrary_precision = LLVMConstIntOfArbitraryPrecision(int_type : TypeRef, num_words : UInt32, words : UInt64*) : ValueRef - fun const_null = LLVMConstNull(ty : TypeRef) : ValueRef - fun const_pointer_null = LLVMConstPointerNull(ty : TypeRef) : ValueRef - fun const_real = LLVMConstReal(real_ty : TypeRef, n : Float64) : ValueRef - fun const_real_of_string_and_size = LLVMConstRealOfStringAndSize(real_ty : TypeRef, text : Char*, s_len : UInt) : ValueRef - fun count_param_types = LLVMCountParamTypes(function_type : TypeRef) : UInt32 - fun create_generic_value_of_int = LLVMCreateGenericValueOfInt(ty : TypeRef, n : UInt64, is_signed : Int32) : GenericValueRef - fun create_generic_value_of_pointer = LLVMCreateGenericValueOfPointer(p : Void*) : GenericValueRef - fun create_jit_compiler_for_module = LLVMCreateJITCompilerForModule(jit : ExecutionEngineRef*, m : ModuleRef, opt_level : Int32, error : UInt8**) : Int32 - fun create_mc_jit_compiler_for_module = LLVMCreateMCJITCompilerForModule(jit : ExecutionEngineRef*, m : ModuleRef, options : JITCompilerOptions*, options_length : UInt32, error : UInt8**) : Int32 - fun create_target_machine = LLVMCreateTargetMachine(target : TargetRef, triple : UInt8*, cpu : UInt8*, features : UInt8*, level : LLVM::CodeGenOptLevel, reloc : LLVM::RelocMode, code_model : LLVM::CodeModel) : TargetMachineRef - {% unless LibLLVM::IS_LT_120 %} - fun create_type_attribute = LLVMCreateTypeAttribute(ctx : ContextRef, kind_id : UInt, ty : TypeRef) : AttributeRef - {% end %} - fun delete_basic_block = LLVMDeleteBasicBlock(block : BasicBlockRef) - fun delete_function = LLVMDeleteFunction(fn : ValueRef) - fun dispose_message = LLVMDisposeMessage(msg : UInt8*) - fun dump_module = LLVMDumpModule(module : ModuleRef) - fun dump_value = LLVMDumpValue(val : ValueRef) - fun target_machine_emit_to_file = LLVMTargetMachineEmitToFile(t : TargetMachineRef, m : ModuleRef, filename : UInt8*, codegen : LLVM::CodeGenFileType, error_msg : UInt8**) : Int32 - fun function_type = LLVMFunctionType(return_type : TypeRef, param_types : TypeRef*, param_count : UInt32, is_var_arg : Int32) : TypeRef - fun generic_value_to_float = LLVMGenericValueToFloat(type : TypeRef, value : GenericValueRef) : Float64 - fun generic_value_to_int = LLVMGenericValueToInt(value : GenericValueRef, signed : Int32) : UInt64 - fun generic_value_to_pointer = LLVMGenericValueToPointer(value : GenericValueRef) : Void* - fun get_basic_block_name = LLVMGetBasicBlockName(basic_block : BasicBlockRef) : Char* - fun get_current_debug_location = LLVMGetCurrentDebugLocation(builder : BuilderRef) : ValueRef - {% unless LibLLVM::IS_LT_90 %} - fun get_current_debug_location2 = LLVMGetCurrentDebugLocation2(builder : BuilderRef) : MetadataRef - {% end %} - fun get_first_instruction = LLVMGetFirstInstruction(block : BasicBlockRef) : ValueRef - fun get_first_target = LLVMGetFirstTarget : TargetRef - fun get_first_basic_block = LLVMGetFirstBasicBlock(fn : ValueRef) : BasicBlockRef - fun get_insert_block = LLVMGetInsertBlock(builder : BuilderRef) : BasicBlockRef - fun get_named_function = LLVMGetNamedFunction(mod : ModuleRef, name : UInt8*) : ValueRef - fun get_named_global = LLVMGetNamedGlobal(mod : ModuleRef, name : UInt8*) : ValueRef - fun get_count_params = LLVMCountParams(fn : ValueRef) : UInt - fun get_host_cpu_name = LLVMGetHostCPUName : UInt8* - fun get_param = LLVMGetParam(fn : ValueRef, index : Int32) : ValueRef - fun get_param_types = LLVMGetParamTypes(function_type : TypeRef, dest : TypeRef*) - fun get_params = LLVMGetParams(fn : ValueRef, params : ValueRef*) - fun get_pointer_to_global = LLVMGetPointerToGlobal(ee : ExecutionEngineRef, global : ValueRef) : Void* - fun get_return_type = LLVMGetReturnType(function_type : TypeRef) : TypeRef - fun get_target_name = LLVMGetTargetName(target : TargetRef) : UInt8* - fun get_target_description = LLVMGetTargetDescription(target : TargetRef) : UInt8* - fun get_target_machine_triple = LLVMGetTargetMachineTriple(t : TargetMachineRef) : UInt8* - fun get_target_from_triple = LLVMGetTargetFromTriple(triple : UInt8*, target : TargetRef*, error_message : UInt8**) : Int32 - fun normalize_target_triple = LLVMNormalizeTargetTriple(triple : Char*) : Char* - fun get_type_kind = LLVMGetTypeKind(ty : TypeRef) : LLVM::Type::Kind - fun get_undef = LLVMGetUndef(ty : TypeRef) : ValueRef - fun get_value_name2 = LLVMGetValueName2(val : ValueRef, length : SizeT*) : Char* - fun get_value_kind = LLVMGetValueKind(value : ValueRef) : LLVM::Value::Kind - fun initialize_x86_asm_printer = LLVMInitializeX86AsmPrinter - fun initialize_x86_asm_parser = LLVMInitializeX86AsmParser - fun initialize_x86_target = LLVMInitializeX86Target - fun initialize_x86_target_info = LLVMInitializeX86TargetInfo - fun initialize_x86_target_mc = LLVMInitializeX86TargetMC - fun initialize_aarch64_asm_printer = LLVMInitializeAArch64AsmPrinter - fun initialize_aarch64_asm_parser = LLVMInitializeAArch64AsmParser - fun initialize_aarch64_target = LLVMInitializeAArch64Target - fun initialize_aarch64_target_info = LLVMInitializeAArch64TargetInfo - fun initialize_aarch64_target_mc = LLVMInitializeAArch64TargetMC - fun initialize_arm_asm_printer = LLVMInitializeARMAsmPrinter - fun initialize_arm_asm_parser = LLVMInitializeARMAsmParser - fun initialize_arm_target = LLVMInitializeARMTarget - fun initialize_arm_target_info = LLVMInitializeARMTargetInfo - fun initialize_arm_target_mc = LLVMInitializeARMTargetMC - fun initialize_webassembly_asm_printer = LLVMInitializeWebAssemblyAsmPrinter - fun initialize_webassembly_asm_parser = LLVMInitializeWebAssemblyAsmParser - fun initialize_webassembly_target = LLVMInitializeWebAssemblyTarget - fun initialize_webassembly_target_info = LLVMInitializeWebAssemblyTargetInfo - fun initialize_webassembly_target_mc = LLVMInitializeWebAssemblyTargetMC - fun is_constant = LLVMIsConstant(val : ValueRef) : Int32 - fun is_function_var_arg = LLVMIsFunctionVarArg(ty : TypeRef) : Int32 - fun module_create_with_name_in_context = LLVMModuleCreateWithNameInContext(module_id : UInt8*, context : ContextRef) : ModuleRef - fun offset_of_element = LLVMOffsetOfElement(td : TargetDataRef, struct_type : TypeRef, element : LibC::UInt) : UInt64 - fun pointer_type = LLVMPointerType(element_type : TypeRef, address_space : UInt32) : TypeRef - fun position_builder_at_end = LLVMPositionBuilderAtEnd(builder : BuilderRef, block : BasicBlockRef) - fun print_module_to_file = LLVMPrintModuleToFile(m : ModuleRef, filename : UInt8*, error_msg : UInt8**) : Int32 - fun run_function = LLVMRunFunction(ee : ExecutionEngineRef, f : ValueRef, num_args : Int32, args : GenericValueRef*) : GenericValueRef - fun set_cleanup = LLVMSetCleanup(lpad : ValueRef, val : Int32) - fun set_global_constant = LLVMSetGlobalConstant(global : ValueRef, is_constant : Int32) - fun is_global_constant = LLVMIsGlobalConstant(global : ValueRef) : Int32 - fun set_initializer = LLVMSetInitializer(global_var : ValueRef, constant_val : ValueRef) - fun get_initializer = LLVMGetInitializer(global_var : ValueRef) : ValueRef - fun set_linkage = LLVMSetLinkage(global : ValueRef, linkage : LLVM::Linkage) - fun get_linkage = LLVMGetLinkage(global : ValueRef) : LLVM::Linkage - fun set_dll_storage_class = LLVMSetDLLStorageClass(global : ValueRef, storage_class : LLVM::DLLStorageClass) - fun set_metadata = LLVMSetMetadata(value : ValueRef, kind_id : UInt32, node : ValueRef) - fun set_target = LLVMSetTarget(mod : ModuleRef, triple : UInt8*) - fun set_thread_local = LLVMSetThreadLocal(global_var : ValueRef, is_thread_local : Int32) - fun is_thread_local = LLVMIsThreadLocal(global_var : ValueRef) : Int32 - fun set_value_name2 = LLVMSetValueName2(val : ValueRef, name : Char*, name_len : SizeT) - fun set_personality_fn = LLVMSetPersonalityFn(fn : ValueRef, personality_fn : ValueRef) - fun size_of = LLVMSizeOf(ty : TypeRef) : ValueRef - fun size_of_type_in_bits = LLVMSizeOfTypeInBits(ref : TargetDataRef, ty : TypeRef) : UInt64 - fun struct_create_named = LLVMStructCreateNamed(c : ContextRef, name : UInt8*) : TypeRef - fun struct_set_body = LLVMStructSetBody(struct_type : TypeRef, element_types : TypeRef*, element_count : UInt32, packed : Int32) - fun type_of = LLVMTypeOf(val : ValueRef) : TypeRef - fun write_bitcode_to_file = LLVMWriteBitcodeToFile(module : ModuleRef, path : UInt8*) : Int32 - fun verify_module = LLVMVerifyModule(module : ModuleRef, action : LLVM::VerifierFailureAction, outmessage : UInt8**) : Int32 - fun link_in_mc_jit = LLVMLinkInMCJIT - fun start_multithreaded = LLVMStartMultithreaded : Int32 - fun stop_multithreaded = LLVMStopMultithreaded - fun is_multithreaded = LLVMIsMultithreaded : Int32 - fun get_first_function = LLVMGetFirstFunction(m : ModuleRef) : ValueRef - fun get_next_function = LLVMGetNextFunction(f : ValueRef) : ValueRef - fun get_next_basic_block = LLVMGetNextBasicBlock(bb : BasicBlockRef) : BasicBlockRef - fun get_next_instruction = LLVMGetNextInstruction(inst : ValueRef) : ValueRef - fun get_next_target = LLVMGetNextTarget(t : TargetRef) : TargetRef - fun get_default_target_triple = LLVMGetDefaultTargetTriple : UInt8* - fun print_module_to_string = LLVMPrintModuleToString(mod : ModuleRef) : UInt8* - fun print_type_to_string = LLVMPrintTypeToString(ty : TypeRef) : UInt8* - fun print_value_to_string = LLVMPrintValueToString(v : ValueRef) : UInt8* - fun get_function_call_convention = LLVMGetFunctionCallConv(fn : ValueRef) : LLVM::CallConvention - fun set_function_call_convention = LLVMSetFunctionCallConv(fn : ValueRef, cc : LLVM::CallConvention) - fun set_instruction_call_convention = LLVMSetInstructionCallConv(instr : ValueRef, cc : LLVM::CallConvention) - fun get_instruction_call_convention = LLVMGetInstructionCallConv(instr : ValueRef) : LLVM::CallConvention - fun set_ordering = LLVMSetOrdering(memory_access_inst : ValueRef, ordering : LLVM::AtomicOrdering) - fun get_int_type_width = LLVMGetIntTypeWidth(ty : TypeRef) : UInt32 - fun is_packed_struct = LLVMIsPackedStruct(ty : TypeRef) : Int32 - fun get_struct_name = LLVMGetStructName(ty : TypeRef) : UInt8* - fun get_struct_element_types = LLVMGetStructElementTypes(ty : TypeRef, dest : TypeRef*) - fun count_struct_element_types = LLVMCountStructElementTypes(ty : TypeRef) : UInt32 - fun get_element_type = LLVMGetElementType(ty : TypeRef) : TypeRef - fun get_array_length = LLVMGetArrayLength(ty : TypeRef) : UInt32 - fun get_vector_size = LLVMGetVectorSize(ty : TypeRef) : UInt32 - fun abi_size_of_type = LLVMABISizeOfType(td : TargetDataRef, ty : TypeRef) : UInt64 - fun abi_alignment_of_type = LLVMABIAlignmentOfType(td : TargetDataRef, ty : TypeRef) : UInt32 - fun get_target_machine_target = LLVMGetTargetMachineTarget(t : TargetMachineRef) : TargetRef - {% if !LibLLVM::IS_LT_130 %} - fun get_inline_asm = LLVMGetInlineAsm(t : TypeRef, asm_string : UInt8*, asm_string_len : LibC::SizeT, constraints : UInt8*, constraints_len : LibC::SizeT, has_side_effects : Int32, is_align_stack : Int32, dialect : InlineAsmDialect, can_throw : Int32) : ValueRef - {% else %} - fun get_inline_asm = LLVMGetInlineAsm(t : TypeRef, asm_string : UInt8*, asm_string_len : LibC::SizeT, constraints : UInt8*, constraints_len : LibC::SizeT, has_side_effects : Int32, is_align_stack : Int32, dialect : InlineAsmDialect) : ValueRef - {% end %} - fun create_context = LLVMContextCreate : ContextRef - fun dispose_builder = LLVMDisposeBuilder(BuilderRef) - fun dispose_target_machine = LLVMDisposeTargetMachine(TargetMachineRef) - fun dispose_generic_value = LLVMDisposeGenericValue(GenericValueRef) - fun dispose_execution_engine = LLVMDisposeExecutionEngine(ExecutionEngineRef) - fun dispose_context = LLVMContextDispose(ContextRef) - fun dispose_target_data = LLVMDisposeTargetData(TargetDataRef) - fun set_volatile = LLVMSetVolatile(value : ValueRef, volatile : UInt32) - fun set_alignment = LLVMSetAlignment(value : ValueRef, bytes : UInt32) - fun get_return_type = LLVMGetReturnType(TypeRef) : TypeRef - - fun write_bitcode_to_memory_buffer = LLVMWriteBitcodeToMemoryBuffer(mod : ModuleRef) : MemoryBufferRef - - fun dispose_memory_buffer = LLVMDisposeMemoryBuffer(buf : MemoryBufferRef) : Void - fun get_buffer_start = LLVMGetBufferStart(buf : MemoryBufferRef) : UInt8* - fun get_buffer_size = LLVMGetBufferSize(buf : MemoryBufferRef) : LibC::SizeT - - fun write_bitcode_to_fd = LLVMWriteBitcodeToFD(mod : ModuleRef, fd : LibC::Int, should_close : LibC::Int, unbuffered : LibC::Int) : LibC::Int - - fun create_target_data_layout = LLVMCreateTargetDataLayout(t : TargetMachineRef) : TargetDataRef - fun set_module_data_layout = LLVMSetModuleDataLayout(mod : ModuleRef, data : TargetDataRef) - - type AttributeRef = Void* - alias AttributeIndex = UInt - - fun get_last_enum_attribute_kind = LLVMGetLastEnumAttributeKind : UInt - fun get_enum_attribute_kind_for_name = LLVMGetEnumAttributeKindForName(name : Char*, s_len : LibC::SizeT) : UInt - fun create_enum_attribute = LLVMCreateEnumAttribute(c : ContextRef, kind_id : UInt, val : UInt64) : AttributeRef - fun add_attribute_at_index = LLVMAddAttributeAtIndex(f : ValueRef, idx : AttributeIndex, a : AttributeRef) - fun get_enum_attribute_at_index = LLVMGetEnumAttributeAtIndex(f : ValueRef, idx : AttributeIndex, kind_id : UInt) : AttributeRef - fun add_call_site_attribute = LLVMAddCallSiteAttribute(f : ValueRef, idx : AttributeIndex, value : AttributeRef) - - fun get_module_identifier = LLVMGetModuleIdentifier(m : ModuleRef, len : LibC::SizeT*) : UInt8* - fun set_module_identifier = LLVMSetModuleIdentifier(m : ModuleRef, ident : UInt8*, len : LibC::SizeT) - - fun get_module_context = LLVMGetModuleContext(m : ModuleRef) : ContextRef - fun get_global_parent = LLVMGetGlobalParent(global : ValueRef) : ModuleRef - - fun create_memory_buffer_with_contents_of_file = LLVMCreateMemoryBufferWithContentsOfFile(path : UInt8*, out_mem_buf : MemoryBufferRef*, out_message : UInt8**) : Int32 - fun parse_ir_in_context = LLVMParseIRInContext(context : ContextRef, mem_buf : MemoryBufferRef, out_m : ModuleRef*, out_message : UInt8**) : Int32 - fun context_dispose = LLVMContextDispose(ContextRef) - - fun void_type_in_context = LLVMVoidTypeInContext(ContextRef) : TypeRef - fun int1_type_in_context = LLVMInt1TypeInContext(ContextRef) : TypeRef - fun int8_type_in_context = LLVMInt8TypeInContext(ContextRef) : TypeRef - fun int16_type_in_context = LLVMInt16TypeInContext(ContextRef) : TypeRef - fun int32_type_in_context = LLVMInt32TypeInContext(ContextRef) : TypeRef - fun int64_type_in_context = LLVMInt64TypeInContext(ContextRef) : TypeRef - fun int128_type_in_context = LLVMInt128TypeInContext(ContextRef) : TypeRef - fun int_type_in_context = LLVMIntTypeInContext(ContextRef, num_bits : UInt) : TypeRef - fun float_type_in_context = LLVMFloatTypeInContext(ContextRef) : TypeRef - fun double_type_in_context = LLVMDoubleTypeInContext(ContextRef) : TypeRef - fun struct_type_in_context = LLVMStructTypeInContext(c : ContextRef, element_types : TypeRef*, element_count : UInt32, packed : Int32) : TypeRef - {% unless LibLLVM::IS_LT_150 %} - fun pointer_type_in_context = LLVMPointerTypeInContext(ContextRef, address_space : UInt) : TypeRef - {% end %} - - fun const_string_in_context = LLVMConstStringInContext(c : ContextRef, str : UInt8*, length : UInt32, dont_null_terminate : Int32) : ValueRef - fun const_struct_in_context = LLVMConstStructInContext(c : ContextRef, constant_vals : ValueRef*, count : UInt32, packed : Int32) : ValueRef - - fun get_md_kind_id_in_context = LLVMGetMDKindIDInContext(c : ContextRef, name : UInt8*, slen : UInt32) : UInt32 - fun md_node_in_context2 = LLVMMDNodeInContext2(c : ContextRef, mds : ValueRef*, count : SizeT) : ValueRef - fun md_string_in_context2 = LLVMMDStringInContext2(c : ContextRef, str : Char*, s_len : SizeT) : ValueRef - - fun value_as_metadata = LLVMValueAsMetadata(val : ValueRef) : MetadataRef - fun metadata_as_value = LLVMMetadataAsValue(c : ContextRef, md : MetadataRef) : ValueRef - - fun append_basic_block_in_context = LLVMAppendBasicBlockInContext(ctx : ContextRef, fn : ValueRef, name : UInt8*) : BasicBlockRef - fun create_builder_in_context = LLVMCreateBuilderInContext(c : ContextRef) : BuilderRef - - fun get_type_context = LLVMGetTypeContext(TypeRef) : ContextRef - - fun const_int_get_sext_value = LLVMConstIntGetSExtValue(ValueRef) : Int64 - fun const_int_get_zext_value = LLVMConstIntGetZExtValue(ValueRef) : UInt64 - - fun get_num_operands = LLVMGetNumOperands(val : ValueRef) : Int32 - fun get_operand = LLVMGetOperand(val : ValueRef, index : UInt) : ValueRef - - fun get_num_arg_operands = LLVMGetNumArgOperands(instr : ValueRef) : UInt - fun get_arg_operand = LLVMGetArgOperand(val : ValueRef, index : UInt) : ValueRef - - fun get_md_node_num_operands = LLVMGetMDNodeNumOperands(v : ValueRef) : UInt - fun get_md_node_operands = LLVMGetMDNodeOperands(v : ValueRef, dest : ValueRef*) - - fun set_instr_param_alignment = LLVMSetInstrParamAlignment(instr : ValueRef, index : UInt, align : UInt) - - fun set_param_alignment = LLVMSetParamAlignment(arg : ValueRef, align : UInt) - - {% unless LibLLVM::IS_LT_130 %} - fun run_passes = LLVMRunPasses(mod : ModuleRef, passes : UInt8*, tm : TargetMachineRef, options : PassBuilderOptionsRef) : ErrorRef - fun create_pass_builder_options = LLVMCreatePassBuilderOptions : PassBuilderOptionsRef - fun dispose_pass_builder_options = LLVMDisposePassBuilderOptions(options : PassBuilderOptionsRef) - {% end %} - - fun create_di_builder = LLVMCreateDIBuilder(m : ModuleRef) : DIBuilderRef - fun dispose_di_builder = LLVMDisposeDIBuilder(builder : DIBuilderRef) - fun di_builder_finalize = LLVMDIBuilderFinalize(builder : DIBuilderRef) - - {% if LibLLVM::IS_LT_110 %} - fun di_builder_create_compile_unit = LLVMDIBuilderCreateCompileUnit( - builder : DIBuilderRef, lang : LLVM::DwarfSourceLanguage, file_ref : MetadataRef, producer : Char*, - producer_len : SizeT, is_optimized : Int, flags : Char*, flags_len : SizeT, runtime_ver : UInt, - split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt, - split_debug_inlining : Int, debug_info_for_profiling : Int - ) : MetadataRef - {% else %} - fun di_builder_create_compile_unit = LLVMDIBuilderCreateCompileUnit( - builder : DIBuilderRef, lang : LLVM::DwarfSourceLanguage, file_ref : MetadataRef, producer : Char*, - producer_len : SizeT, is_optimized : Int, flags : Char*, flags_len : SizeT, runtime_ver : UInt, - split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt, - split_debug_inlining : Int, debug_info_for_profiling : Int, sys_root : Char*, - sys_root_len : SizeT, sdk : Char*, sdk_len : SizeT - ) : MetadataRef - {% end %} - - fun di_builder_create_file = LLVMDIBuilderCreateFile( - builder : DIBuilderRef, filename : Char*, filename_len : SizeT, - directory : Char*, directory_len : SizeT - ) : MetadataRef - - fun di_builder_create_function = LLVMDIBuilderCreateFunction( - builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, - linkage_name : Char*, linkage_name_len : SizeT, file : MetadataRef, line_no : UInt, - ty : MetadataRef, is_local_to_unit : Int, is_definition : Int, scope_line : UInt, - flags : LLVM::DIFlags, is_optimized : Int - ) : MetadataRef - - fun di_builder_create_lexical_block = LLVMDIBuilderCreateLexicalBlock( - builder : DIBuilderRef, scope : MetadataRef, file : MetadataRef, line : UInt, column : UInt - ) : MetadataRef - fun di_builder_create_lexical_block_file = LLVMDIBuilderCreateLexicalBlockFile( - builder : DIBuilderRef, scope : MetadataRef, file_scope : MetadataRef, discriminator : UInt - ) : MetadataRef - - {% unless LibLLVM::IS_LT_90 %} - fun di_builder_create_enumerator = LLVMDIBuilderCreateEnumerator( - builder : DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : Int - ) : MetadataRef - {% end %} - - fun di_builder_create_subroutine_type = LLVMDIBuilderCreateSubroutineType( - builder : DIBuilderRef, file : MetadataRef, parameter_types : MetadataRef*, - num_parameter_types : UInt, flags : LLVM::DIFlags - ) : MetadataRef - fun di_builder_create_enumeration_type = LLVMDIBuilderCreateEnumerationType( - builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, - line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, - elements : MetadataRef*, num_elements : UInt, class_ty : MetadataRef - ) : MetadataRef - fun di_builder_create_union_type = LLVMDIBuilderCreateUnionType( - builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, - line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags, - elements : MetadataRef*, num_elements : UInt, run_time_lang : UInt, unique_id : Char*, unique_id_len : SizeT - ) : MetadataRef - fun di_builder_create_array_type = LLVMDIBuilderCreateArrayType( - builder : DIBuilderRef, size : UInt64, align_in_bits : UInt32, - ty : MetadataRef, subscripts : MetadataRef*, num_subscripts : UInt - ) : MetadataRef - fun di_builder_create_unspecified_type = LLVMDIBuilderCreateUnspecifiedType(builder : DIBuilderRef, name : Char*, name_len : SizeT) : MetadataRef - fun di_builder_create_basic_type = LLVMDIBuilderCreateBasicType( - builder : DIBuilderRef, name : Char*, name_len : SizeT, size_in_bits : UInt64, - encoding : UInt, flags : LLVM::DIFlags - ) : MetadataRef - fun di_builder_create_pointer_type = LLVMDIBuilderCreatePointerType( - builder : DIBuilderRef, pointee_ty : MetadataRef, size_in_bits : UInt64, align_in_bits : UInt32, - address_space : UInt, name : Char*, name_len : SizeT - ) : MetadataRef - fun di_builder_create_struct_type = LLVMDIBuilderCreateStructType( - builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, - line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags, - derived_from : MetadataRef, elements : MetadataRef*, num_elements : UInt, - run_time_lang : UInt, v_table_holder : MetadataRef, unique_id : Char*, unique_id_len : SizeT - ) : MetadataRef - fun di_builder_create_member_type = LLVMDIBuilderCreateMemberType( - builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, - line_no : UInt, size_in_bits : UInt64, align_in_bits : UInt32, offset_in_bits : UInt64, - flags : LLVM::DIFlags, ty : MetadataRef - ) : MetadataRef - fun di_builder_create_replaceable_composite_type = LLVMDIBuilderCreateReplaceableCompositeType( - builder : DIBuilderRef, tag : UInt, name : Char*, name_len : SizeT, scope : MetadataRef, - file : MetadataRef, line : UInt, runtime_lang : UInt, size_in_bits : UInt64, align_in_bits : UInt32, - flags : LLVM::DIFlags, unique_identifier : Char*, unique_identifier_len : SizeT - ) : MetadataRef - - fun di_builder_get_or_create_subrange = LLVMDIBuilderGetOrCreateSubrange(builder : DIBuilderRef, lo : Int64, count : Int64) : MetadataRef - fun di_builder_get_or_create_array = LLVMDIBuilderGetOrCreateArray(builder : DIBuilderRef, data : MetadataRef*, length : SizeT) : MetadataRef - fun di_builder_get_or_create_type_array = LLVMDIBuilderGetOrCreateTypeArray(builder : DIBuilderRef, types : MetadataRef*, length : SizeT) : MetadataRef - - {% if LibLLVM::IS_LT_140 %} - fun di_builder_create_expression = LLVMDIBuilderCreateExpression(builder : DIBuilderRef, addr : Int64*, length : SizeT) : MetadataRef - {% else %} - fun di_builder_create_expression = LLVMDIBuilderCreateExpression(builder : DIBuilderRef, addr : UInt64*, length : SizeT) : MetadataRef - {% end %} - - fun di_builder_insert_declare_at_end = LLVMDIBuilderInsertDeclareAtEnd( - builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef, - expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef - ) : ValueRef - - fun di_builder_create_auto_variable = LLVMDIBuilderCreateAutoVariable( - builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, - line_no : UInt, ty : MetadataRef, always_preserve : Int, flags : LLVM::DIFlags, align_in_bits : UInt32 - ) : MetadataRef - fun di_builder_create_parameter_variable = LLVMDIBuilderCreateParameterVariable( - builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, arg_no : UInt, - file : MetadataRef, line_no : UInt, ty : MetadataRef, always_preserve : Int, flags : LLVM::DIFlags - ) : MetadataRef - - fun set_subprogram = LLVMSetSubprogram(func : ValueRef, sp : MetadataRef) - fun metadata_replace_all_uses_with = LLVMMetadataReplaceAllUsesWith(target_metadata : MetadataRef, replacement : MetadataRef) - - {% if LibLLVM::IS_LT_170 %} - type PassManagerRef = Void* - fun pass_manager_create = LLVMCreatePassManager : PassManagerRef - fun create_function_pass_manager_for_module = LLVMCreateFunctionPassManagerForModule(mod : ModuleRef) : PassManagerRef - fun run_pass_manager = LLVMRunPassManager(pm : PassManagerRef, m : ModuleRef) : Int32 - fun initialize_function_pass_manager = LLVMInitializeFunctionPassManager(fpm : PassManagerRef) : Int32 - fun run_function_pass_manager = LLVMRunFunctionPassManager(fpm : PassManagerRef, f : ValueRef) : Int32 - fun finalize_function_pass_manager = LLVMFinalizeFunctionPassManager(fpm : PassManagerRef) : Int32 - fun dispose_pass_manager = LLVMDisposePassManager(PassManagerRef) - - type PassRegistryRef = Void* - fun get_global_pass_registry = LLVMGetGlobalPassRegistry : PassRegistryRef - fun initialize_core = LLVMInitializeCore(r : PassRegistryRef) - fun initialize_transform_utils = LLVMInitializeTransformUtils(r : PassRegistryRef) - fun initialize_scalar_opts = LLVMInitializeScalarOpts(r : PassRegistryRef) - fun initialize_obj_c_arc_opts = LLVMInitializeObjCARCOpts(r : PassRegistryRef) - fun initialize_vectorization = LLVMInitializeVectorization(r : PassRegistryRef) - fun initialize_inst_combine = LLVMInitializeInstCombine(r : PassRegistryRef) - fun initialize_ipo = LLVMInitializeIPO(r : PassRegistryRef) - fun initialize_instrumentation = LLVMInitializeInstrumentation(r : PassRegistryRef) - fun initialize_analysis = LLVMInitializeAnalysis(r : PassRegistryRef) - fun initialize_ipa = LLVMInitializeIPA(r : PassRegistryRef) - fun initialize_code_gen = LLVMInitializeCodeGen(r : PassRegistryRef) - fun initialize_target = LLVMInitializeTarget(r : PassRegistryRef) - - type PassManagerBuilderRef = Void* - fun pass_manager_builder_create = LLVMPassManagerBuilderCreate : PassManagerBuilderRef - fun pass_manager_builder_set_opt_level = LLVMPassManagerBuilderSetOptLevel(builder : PassManagerBuilderRef, opt_level : UInt32) - fun pass_manager_builder_set_size_level = LLVMPassManagerBuilderSetSizeLevel(builder : PassManagerBuilderRef, size_level : UInt32) - fun pass_manager_builder_set_disable_unroll_loops = LLVMPassManagerBuilderSetDisableUnrollLoops(builder : PassManagerBuilderRef, value : Int32) - fun pass_manager_builder_set_disable_simplify_lib_calls = LLVMPassManagerBuilderSetDisableSimplifyLibCalls(builder : PassManagerBuilderRef, value : Int32) - fun pass_manager_builder_use_inliner_with_threshold = LLVMPassManagerBuilderUseInlinerWithThreshold(builder : PassManagerBuilderRef, threshold : UInt32) - fun pass_manager_builder_populate_function_pass_manager = LLVMPassManagerBuilderPopulateFunctionPassManager(builder : PassManagerBuilderRef, pm : PassManagerRef) - fun pass_manager_builder_populate_module_pass_manager = LLVMPassManagerBuilderPopulateModulePassManager(builder : PassManagerBuilderRef, pm : PassManagerRef) - fun dispose_pass_manager_builder = LLVMPassManagerBuilderDispose(PassManagerBuilderRef) - {% end %} end + +require "./lib_llvm/**" diff --git a/src/llvm/lib_llvm/analysis.cr b/src/llvm/lib_llvm/analysis.cr new file mode 100644 index 000000000000..a123ff981e63 --- /dev/null +++ b/src/llvm/lib_llvm/analysis.cr @@ -0,0 +1,5 @@ +require "./types" + +lib LibLLVM + fun verify_module = LLVMVerifyModule(m : ModuleRef, action : LLVM::VerifierFailureAction, out_message : Char**) : Bool +end diff --git a/src/llvm/lib_llvm/bit_writer.cr b/src/llvm/lib_llvm/bit_writer.cr new file mode 100644 index 000000000000..b581753509e5 --- /dev/null +++ b/src/llvm/lib_llvm/bit_writer.cr @@ -0,0 +1,7 @@ +require "./types" + +lib LibLLVM + fun write_bitcode_to_file = LLVMWriteBitcodeToFile(m : ModuleRef, path : Char*) : Int + fun write_bitcode_to_fd = LLVMWriteBitcodeToFD(m : ModuleRef, fd : Int, should_close : Int, unbuffered : Int) : Int + fun write_bitcode_to_memory_buffer = LLVMWriteBitcodeToMemoryBuffer(mod : ModuleRef) : MemoryBufferRef +end diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr new file mode 100644 index 000000000000..1d44cb005d98 --- /dev/null +++ b/src/llvm/lib_llvm/core.cr @@ -0,0 +1,287 @@ +require "./types" + +lib LibLLVM + # NOTE: the following C enums usually have different values from their C++ + # counterparts (e.g. `LLVMModuleFlagBehavior` v.s. `LLVM::Module::ModFlagBehavior`) + + enum InlineAsmDialect + ATT + Intel + end + + enum ModuleFlagBehavior + Warning = 1 + end + + alias AttributeIndex = UInt + + fun dispose_message = LLVMDisposeMessage(message : Char*) + + fun create_context = LLVMContextCreate : ContextRef + fun dispose_context = LLVMContextDispose(c : ContextRef) + + fun get_md_kind_id_in_context = LLVMGetMDKindIDInContext(c : ContextRef, name : Char*, s_len : UInt) : UInt + + fun get_enum_attribute_kind_for_name = LLVMGetEnumAttributeKindForName(name : Char*, s_len : SizeT) : UInt + fun get_last_enum_attribute_kind = LLVMGetLastEnumAttributeKind : UInt + fun create_enum_attribute = LLVMCreateEnumAttribute(c : ContextRef, kind_id : UInt, val : UInt64) : AttributeRef + {% unless LibLLVM::IS_LT_120 %} + fun create_type_attribute = LLVMCreateTypeAttribute(c : ContextRef, kind_id : UInt, type_ref : TypeRef) : AttributeRef + {% end %} + + fun module_create_with_name_in_context = LLVMModuleCreateWithNameInContext(module_id : Char*, c : ContextRef) : ModuleRef + fun get_module_identifier = LLVMGetModuleIdentifier(m : ModuleRef, len : SizeT*) : Char* + fun set_module_identifier = LLVMSetModuleIdentifier(m : ModuleRef, ident : Char*, len : SizeT) + fun set_target = LLVMSetTarget(m : ModuleRef, triple : Char*) + fun add_module_flag = LLVMAddModuleFlag(m : ModuleRef, behavior : ModuleFlagBehavior, key : Char*, key_len : SizeT, val : MetadataRef) + fun dump_module = LLVMDumpModule(m : ModuleRef) + fun print_module_to_file = LLVMPrintModuleToFile(m : ModuleRef, filename : Char*, error_message : Char**) : Bool + fun print_module_to_string = LLVMPrintModuleToString(m : ModuleRef) : Char* + {% if !LibLLVM::IS_LT_130 %} + fun get_inline_asm = LLVMGetInlineAsm(ty : TypeRef, asm_string : Char*, asm_string_size : SizeT, constraints : Char*, constraints_size : SizeT, has_side_effects : Bool, is_align_stack : Bool, dialect : InlineAsmDialect, can_throw : Bool) : ValueRef + {% else %} + fun get_inline_asm = LLVMGetInlineAsm(t : TypeRef, asm_string : Char*, asm_string_size : SizeT, constraints : Char*, constraints_size : SizeT, has_side_effects : Bool, is_align_stack : Bool, dialect : InlineAsmDialect) : ValueRef + {% end %} + fun get_module_context = LLVMGetModuleContext(m : ModuleRef) : ContextRef + + fun add_function = LLVMAddFunction(m : ModuleRef, name : Char*, function_ty : TypeRef) : ValueRef + fun get_named_function = LLVMGetNamedFunction(m : ModuleRef, name : Char*) : ValueRef + fun get_first_function = LLVMGetFirstFunction(m : ModuleRef) : ValueRef + fun get_next_function = LLVMGetNextFunction(fn : ValueRef) : ValueRef + + fun get_type_kind = LLVMGetTypeKind(ty : TypeRef) : LLVM::Type::Kind + fun get_type_context = LLVMGetTypeContext(ty : TypeRef) : ContextRef + fun print_type_to_string = LLVMPrintTypeToString(ty : TypeRef) : Char* + + fun int1_type_in_context = LLVMInt1TypeInContext(c : ContextRef) : TypeRef + fun int8_type_in_context = LLVMInt8TypeInContext(c : ContextRef) : TypeRef + fun int16_type_in_context = LLVMInt16TypeInContext(c : ContextRef) : TypeRef + fun int32_type_in_context = LLVMInt32TypeInContext(c : ContextRef) : TypeRef + fun int64_type_in_context = LLVMInt64TypeInContext(c : ContextRef) : TypeRef + fun int128_type_in_context = LLVMInt128TypeInContext(c : ContextRef) : TypeRef + fun int_type_in_context = LLVMIntTypeInContext(c : ContextRef, num_bits : UInt) : TypeRef + fun get_int_type_width = LLVMGetIntTypeWidth(integer_ty : TypeRef) : UInt + + fun float_type_in_context = LLVMFloatTypeInContext(c : ContextRef) : TypeRef + fun double_type_in_context = LLVMDoubleTypeInContext(c : ContextRef) : TypeRef + + fun function_type = LLVMFunctionType(return_type : TypeRef, param_types : TypeRef*, param_count : UInt, is_var_arg : Bool) : TypeRef + fun is_function_var_arg = LLVMIsFunctionVarArg(function_ty : TypeRef) : Bool + fun get_return_type = LLVMGetReturnType(function_ty : TypeRef) : TypeRef + fun count_param_types = LLVMCountParamTypes(function_ty : TypeRef) : UInt + fun get_param_types = LLVMGetParamTypes(function_ty : TypeRef, dest : TypeRef*) + + fun struct_type_in_context = LLVMStructTypeInContext(c : ContextRef, element_types : TypeRef*, element_count : UInt, packed : Bool) : TypeRef + fun struct_create_named = LLVMStructCreateNamed(c : ContextRef, name : Char*) : TypeRef + fun get_struct_name = LLVMGetStructName(ty : TypeRef) : Char* + fun struct_set_body = LLVMStructSetBody(struct_ty : TypeRef, element_types : TypeRef*, element_count : UInt, packed : Bool) + fun count_struct_element_types = LLVMCountStructElementTypes(struct_ty : TypeRef) : UInt + fun get_struct_element_types = LLVMGetStructElementTypes(struct_ty : TypeRef, dest : TypeRef*) + fun is_packed_struct = LLVMIsPackedStruct(struct_ty : TypeRef) : Bool + + fun get_element_type = LLVMGetElementType(ty : TypeRef) : TypeRef + fun array_type = LLVMArrayType(element_type : TypeRef, element_count : UInt) : TypeRef + fun get_array_length = LLVMGetArrayLength(array_ty : TypeRef) : UInt + fun pointer_type = LLVMPointerType(element_type : TypeRef, address_space : UInt) : TypeRef + {% unless LibLLVM::IS_LT_150 %} + fun pointer_type_in_context = LLVMPointerTypeInContext(c : ContextRef, address_space : UInt) : TypeRef + {% end %} + fun vector_type = LLVMVectorType(element_type : TypeRef, element_count : UInt) : TypeRef + fun get_vector_size = LLVMGetVectorSize(vector_ty : TypeRef) : UInt + + fun void_type_in_context = LLVMVoidTypeInContext(c : ContextRef) : TypeRef + + fun type_of = LLVMTypeOf(val : ValueRef) : TypeRef + fun get_value_kind = LLVMGetValueKind(val : ValueRef) : LLVM::Value::Kind + fun get_value_name2 = LLVMGetValueName2(val : ValueRef, length : SizeT*) : Char* + fun set_value_name2 = LLVMSetValueName2(val : ValueRef, name : Char*, name_len : SizeT) + fun dump_value = LLVMDumpValue(val : ValueRef) + fun print_value_to_string = LLVMPrintValueToString(val : ValueRef) : Char* + fun is_constant = LLVMIsConstant(val : ValueRef) : Bool + fun get_value_name = LLVMGetValueName(val : ValueRef) : Char* + fun set_value_name = LLVMSetValueName(val : ValueRef, name : Char*) + + fun get_operand = LLVMGetOperand(val : ValueRef, index : UInt) : ValueRef + fun get_num_operands = LLVMGetNumOperands(val : ValueRef) : Int + + fun const_null = LLVMConstNull(ty : TypeRef) : ValueRef + fun get_undef = LLVMGetUndef(ty : TypeRef) : ValueRef + fun const_pointer_null = LLVMConstPointerNull(ty : TypeRef) : ValueRef + + fun const_int = LLVMConstInt(int_ty : TypeRef, n : ULongLong, sign_extend : Bool) : ValueRef + fun const_int_of_arbitrary_precision = LLVMConstIntOfArbitraryPrecision(int_ty : TypeRef, num_words : UInt, words : UInt64*) : ValueRef + fun const_real = LLVMConstReal(real_ty : TypeRef, n : Double) : ValueRef + fun const_real_of_string = LLVMConstRealOfString(real_ty : TypeRef, text : Char*) : ValueRef + fun const_real_of_string_and_size = LLVMConstRealOfStringAndSize(real_ty : TypeRef, text : Char*, s_len : UInt) : ValueRef + fun const_int_get_zext_value = LLVMConstIntGetZExtValue(constant_val : ValueRef) : ULongLong + fun const_int_get_sext_value = LLVMConstIntGetSExtValue(constant_val : ValueRef) : LongLong + + fun const_string_in_context = LLVMConstStringInContext(c : ContextRef, str : Char*, length : UInt, dont_null_terminate : Bool) : ValueRef + fun const_struct_in_context = LLVMConstStructInContext(c : ContextRef, constant_vals : ValueRef*, count : UInt, packed : Bool) : ValueRef + fun const_array = LLVMConstArray(element_ty : TypeRef, constant_vals : ValueRef*, length : UInt) : ValueRef + + fun size_of = LLVMSizeOf(ty : TypeRef) : ValueRef + + fun get_global_parent = LLVMGetGlobalParent(global : ValueRef) : ModuleRef + fun get_linkage = LLVMGetLinkage(global : ValueRef) : LLVM::Linkage + fun set_linkage = LLVMSetLinkage(global : ValueRef, linkage : LLVM::Linkage) + fun set_dll_storage_class = LLVMSetDLLStorageClass(global : ValueRef, class : LLVM::DLLStorageClass) + + fun set_alignment = LLVMSetAlignment(v : ValueRef, bytes : UInt) + + fun add_global = LLVMAddGlobal(m : ModuleRef, ty : TypeRef, name : Char*) : ValueRef + fun get_named_global = LLVMGetNamedGlobal(m : ModuleRef, name : Char*) : ValueRef + fun get_initializer = LLVMGetInitializer(global_var : ValueRef) : ValueRef + fun set_initializer = LLVMSetInitializer(global_var : ValueRef, constant_val : ValueRef) + fun is_thread_local = LLVMIsThreadLocal(global_var : ValueRef) : Bool + fun set_thread_local = LLVMSetThreadLocal(global_var : ValueRef, is_thread_local : Bool) + fun is_global_constant = LLVMIsGlobalConstant(global_var : ValueRef) : Bool + fun set_global_constant = LLVMSetGlobalConstant(global_var : ValueRef, is_constant : Bool) + + fun delete_function = LLVMDeleteFunction(fn : ValueRef) + fun set_personality_fn = LLVMSetPersonalityFn(fn : ValueRef, personality_fn : ValueRef) + fun get_function_call_convention = LLVMGetFunctionCallConv(fn : ValueRef) : LLVM::CallConvention + fun set_function_call_convention = LLVMSetFunctionCallConv(fn : ValueRef, cc : LLVM::CallConvention) + fun add_attribute_at_index = LLVMAddAttributeAtIndex(f : ValueRef, idx : AttributeIndex, a : AttributeRef) + fun get_enum_attribute_at_index = LLVMGetEnumAttributeAtIndex(f : ValueRef, idx : AttributeIndex, kind_id : UInt) : AttributeRef + fun add_target_dependent_function_attr = LLVMAddTargetDependentFunctionAttr(fn : ValueRef, a : Char*, v : Char*) + + fun get_count_params = LLVMCountParams(fn : ValueRef) : UInt + fun get_params = LLVMGetParams(fn : ValueRef, params : ValueRef*) + fun get_param = LLVMGetParam(fn : ValueRef, index : UInt) : ValueRef + fun set_param_alignment = LLVMSetParamAlignment(arg : ValueRef, align : UInt) + + fun md_string_in_context2 = LLVMMDStringInContext2(c : ContextRef, str : Char*, s_len : SizeT) : ValueRef + fun md_node_in_context2 = LLVMMDNodeInContext2(c : ContextRef, mds : ValueRef*, count : SizeT) : ValueRef + fun metadata_as_value = LLVMMetadataAsValue(c : ContextRef, md : MetadataRef) : ValueRef + fun value_as_metadata = LLVMValueAsMetadata(val : ValueRef) : MetadataRef + fun get_md_node_num_operands = LLVMGetMDNodeNumOperands(v : ValueRef) : UInt + fun get_md_node_operands = LLVMGetMDNodeOperands(v : ValueRef, dest : ValueRef*) + fun md_string_in_context = LLVMMDStringInContext(c : ContextRef, str : Char*, s_len : UInt) : ValueRef + fun md_node_in_context = LLVMMDNodeInContext(c : ContextRef, vals : ValueRef*, count : UInt) : ValueRef + + fun get_basic_block_name = LLVMGetBasicBlockName(bb : BasicBlockRef) : Char* + fun get_first_basic_block = LLVMGetFirstBasicBlock(fn : ValueRef) : BasicBlockRef + fun get_next_basic_block = LLVMGetNextBasicBlock(bb : BasicBlockRef) : BasicBlockRef + fun append_basic_block_in_context = LLVMAppendBasicBlockInContext(c : ContextRef, fn : ValueRef, name : Char*) : BasicBlockRef + fun delete_basic_block = LLVMDeleteBasicBlock(bb : BasicBlockRef) + fun get_first_instruction = LLVMGetFirstInstruction(bb : BasicBlockRef) : ValueRef + + fun set_metadata = LLVMSetMetadata(val : ValueRef, kind_id : UInt, node : ValueRef) + fun get_next_instruction = LLVMGetNextInstruction(inst : ValueRef) : ValueRef + + fun get_num_arg_operands = LLVMGetNumArgOperands(instr : ValueRef) : UInt + fun set_instruction_call_convention = LLVMSetInstructionCallConv(instr : ValueRef, cc : LLVM::CallConvention) + fun get_instruction_call_convention = LLVMGetInstructionCallConv(instr : ValueRef) : LLVM::CallConvention + fun set_instr_param_alignment = LLVMSetInstrParamAlignment(instr : ValueRef, idx : AttributeIndex, align : UInt) + fun add_call_site_attribute = LLVMAddCallSiteAttribute(c : ValueRef, idx : AttributeIndex, a : AttributeRef) + + fun add_incoming = LLVMAddIncoming(phi_node : ValueRef, incoming_values : ValueRef*, incoming_blocks : BasicBlockRef*, count : UInt) + + fun create_builder_in_context = LLVMCreateBuilderInContext(c : ContextRef) : BuilderRef + fun position_builder_at_end = LLVMPositionBuilderAtEnd(builder : BuilderRef, block : BasicBlockRef) + fun get_insert_block = LLVMGetInsertBlock(builder : BuilderRef) : BasicBlockRef + fun dispose_builder = LLVMDisposeBuilder(builder : BuilderRef) + + {% unless LibLLVM::IS_LT_90 %} + fun get_current_debug_location2 = LLVMGetCurrentDebugLocation2(builder : BuilderRef) : MetadataRef + {% end %} + fun get_current_debug_location = LLVMGetCurrentDebugLocation(builder : BuilderRef) : ValueRef + + fun build_ret_void = LLVMBuildRetVoid(BuilderRef) : ValueRef + fun build_ret = LLVMBuildRet(BuilderRef, v : ValueRef) : ValueRef + fun build_br = LLVMBuildBr(BuilderRef, dest : BasicBlockRef) : ValueRef + fun build_cond = LLVMBuildCondBr(BuilderRef, if : ValueRef, then : BasicBlockRef, else : BasicBlockRef) : ValueRef + fun build_switch = LLVMBuildSwitch(BuilderRef, v : ValueRef, else : BasicBlockRef, num_cases : UInt) : ValueRef + fun build_invoke2 = LLVMBuildInvoke2(BuilderRef, ty : TypeRef, fn : ValueRef, args : ValueRef*, num_args : UInt, then : BasicBlockRef, catch : BasicBlockRef, name : Char*) : ValueRef + fun build_unreachable = LLVMBuildUnreachable(BuilderRef) : ValueRef + + fun build_landing_pad = LLVMBuildLandingPad(b : BuilderRef, ty : TypeRef, pers_fn : ValueRef, num_clauses : UInt, name : Char*) : ValueRef + fun build_catch_ret = LLVMBuildCatchRet(b : BuilderRef, catch_pad : ValueRef, bb : BasicBlockRef) : ValueRef + fun build_catch_pad = LLVMBuildCatchPad(b : BuilderRef, parent_pad : ValueRef, args : ValueRef*, num_args : UInt, name : Char*) : ValueRef + fun build_catch_switch = LLVMBuildCatchSwitch(b : BuilderRef, parent_pad : ValueRef, unwind_bb : BasicBlockRef, num_handlers : UInt, name : Char*) : ValueRef + + fun add_case = LLVMAddCase(switch : ValueRef, on_val : ValueRef, dest : BasicBlockRef) + fun add_clause = LLVMAddClause(landing_pad : ValueRef, clause_val : ValueRef) + fun set_cleanup = LLVMSetCleanup(landing_pad : ValueRef, val : Bool) + fun add_handler = LLVMAddHandler(catch_switch : ValueRef, dest : BasicBlockRef) + + fun get_arg_operand = LLVMGetArgOperand(funclet : ValueRef, i : UInt) : ValueRef + + fun build_add = LLVMBuildAdd(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_fadd = LLVMBuildFAdd(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_sub = LLVMBuildSub(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_fsub = LLVMBuildFSub(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_mul = LLVMBuildMul(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_fmul = LLVMBuildFMul(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_udiv = LLVMBuildUDiv(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_sdiv = LLVMBuildSDiv(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_exact_sdiv = LLVMBuildExactSDiv(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_fdiv = LLVMBuildFDiv(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_urem = LLVMBuildURem(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_srem = LLVMBuildSRem(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_shl = LLVMBuildShl(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_lshr = LLVMBuildLShr(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_ashr = LLVMBuildAShr(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_and = LLVMBuildAnd(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_or = LLVMBuildOr(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_xor = LLVMBuildXor(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_not = LLVMBuildNot(BuilderRef, value : ValueRef, name : Char*) : ValueRef + + fun build_malloc = LLVMBuildMalloc(BuilderRef, ty : TypeRef, name : Char*) : ValueRef + fun build_array_malloc = LLVMBuildArrayMalloc(BuilderRef, ty : TypeRef, val : ValueRef, name : Char*) : ValueRef + fun build_alloca = LLVMBuildAlloca(BuilderRef, ty : TypeRef, name : Char*) : ValueRef + fun build_load2 = LLVMBuildLoad2(BuilderRef, ty : TypeRef, pointer_val : ValueRef, name : Char*) : ValueRef + fun build_store = LLVMBuildStore(BuilderRef, val : ValueRef, ptr : ValueRef) : ValueRef + fun build_gep2 = LLVMBuildGEP2(b : BuilderRef, ty : TypeRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt, name : Char*) : ValueRef + fun build_inbounds_gep2 = LLVMBuildInBoundsGEP2(b : BuilderRef, ty : TypeRef, pointer : ValueRef, indices : ValueRef*, num_indices : UInt, name : Char*) : ValueRef + fun build_global_string_ptr = LLVMBuildGlobalStringPtr(b : BuilderRef, str : Char*, name : Char*) : ValueRef + fun set_volatile = LLVMSetVolatile(memory_access_inst : ValueRef, is_volatile : Bool) + fun set_ordering = LLVMSetOrdering(memory_access_inst : ValueRef, ordering : LLVM::AtomicOrdering) + + fun build_trunc = LLVMBuildTrunc(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + fun build_zext = LLVMBuildZExt(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + fun build_sext = LLVMBuildSExt(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + fun build_fp2ui = LLVMBuildFPToUI(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + fun build_fp2si = LLVMBuildFPToSI(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + fun build_ui2fp = LLVMBuildUIToFP(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + fun build_si2fp = LLVMBuildSIToFP(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + fun build_fptrunc = LLVMBuildFPTrunc(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + fun build_fpext = LLVMBuildFPExt(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + fun build_ptr2int = LLVMBuildPtrToInt(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + fun build_int2ptr = LLVMBuildIntToPtr(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + fun build_bit_cast = LLVMBuildBitCast(BuilderRef, val : ValueRef, dest_ty : TypeRef, name : Char*) : ValueRef + + fun build_icmp = LLVMBuildICmp(BuilderRef, op : LLVM::IntPredicate, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + fun build_fcmp = LLVMBuildFCmp(BuilderRef, op : LLVM::RealPredicate, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef + + fun build_phi = LLVMBuildPhi(BuilderRef, ty : TypeRef, name : Char*) : ValueRef + fun build_call2 = LLVMBuildCall2(BuilderRef, TypeRef, fn : ValueRef, args : ValueRef*, num_args : UInt, name : Char*) : ValueRef + fun build_select = LLVMBuildSelect(BuilderRef, if : ValueRef, then : ValueRef, else : ValueRef, name : Char*) : ValueRef + fun build_va_arg = LLVMBuildVAArg(BuilderRef, list : ValueRef, ty : TypeRef, name : Char*) : ValueRef + fun build_extract_value = LLVMBuildExtractValue(BuilderRef, agg_val : ValueRef, index : UInt, name : Char*) : ValueRef + fun build_fence = LLVMBuildFence(b : BuilderRef, ordering : LLVM::AtomicOrdering, single_thread : Bool, name : Char*) : ValueRef + fun build_atomicrmw = LLVMBuildAtomicRMW(b : BuilderRef, op : LLVM::AtomicRMWBinOp, ptr : ValueRef, val : ValueRef, ordering : LLVM::AtomicOrdering, single_thread : Bool) : ValueRef + fun build_atomic_cmp_xchg = LLVMBuildAtomicCmpXchg(b : BuilderRef, ptr : ValueRef, cmp : ValueRef, new : ValueRef, success_ordering : LLVM::AtomicOrdering, failure_ordering : LLVM::AtomicOrdering, single_thread : Bool) : ValueRef + + fun create_memory_buffer_with_contents_of_file = LLVMCreateMemoryBufferWithContentsOfFile(path : Char*, out_mem_buf : MemoryBufferRef*, out_message : Char**) : Bool + fun get_buffer_start = LLVMGetBufferStart(mem_buf : MemoryBufferRef) : Char* + fun get_buffer_size = LLVMGetBufferSize(mem_buf : MemoryBufferRef) : SizeT + fun dispose_memory_buffer = LLVMDisposeMemoryBuffer(mem_buf : MemoryBufferRef) + + {% if LibLLVM::IS_LT_170 %} + fun get_global_pass_registry = LLVMGetGlobalPassRegistry : PassRegistryRef + + fun pass_manager_create = LLVMCreatePassManager : PassManagerRef + fun create_function_pass_manager_for_module = LLVMCreateFunctionPassManagerForModule(m : ModuleRef) : PassManagerRef + fun run_pass_manager = LLVMRunPassManager(pm : PassManagerRef, m : ModuleRef) : Bool + fun initialize_function_pass_manager = LLVMInitializeFunctionPassManager(fpm : PassManagerRef) : Bool + fun run_function_pass_manager = LLVMRunFunctionPassManager(fpm : PassManagerRef, f : ValueRef) : Bool + fun finalize_function_pass_manager = LLVMFinalizeFunctionPassManager(fpm : PassManagerRef) : Bool + fun dispose_pass_manager = LLVMDisposePassManager(pm : PassManagerRef) + {% end %} + + fun start_multithreaded = LLVMStartMultithreaded : Bool + fun stop_multithreaded = LLVMStopMultithreaded + fun is_multithreaded = LLVMIsMultithreaded : Bool +end diff --git a/src/llvm/lib_llvm/debug_info.cr b/src/llvm/lib_llvm/debug_info.cr new file mode 100644 index 000000000000..8aeba0d6a99a --- /dev/null +++ b/src/llvm/lib_llvm/debug_info.cr @@ -0,0 +1,125 @@ +require "./types" + +lib LibLLVM + enum DWARFEmissionKind + Full = 1 + end + + fun create_di_builder = LLVMCreateDIBuilder(m : ModuleRef) : DIBuilderRef + fun dispose_di_builder = LLVMDisposeDIBuilder(builder : DIBuilderRef) + fun di_builder_finalize = LLVMDIBuilderFinalize(builder : DIBuilderRef) + + {% if LibLLVM::IS_LT_110 %} + fun di_builder_create_compile_unit = LLVMDIBuilderCreateCompileUnit( + builder : DIBuilderRef, lang : LLVM::DwarfSourceLanguage, file_ref : MetadataRef, producer : Char*, + producer_len : SizeT, is_optimized : Int, flags : Char*, flags_len : SizeT, runtime_ver : UInt, + split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt, + split_debug_inlining : Int, debug_info_for_profiling : Int + ) : MetadataRef + {% else %} + fun di_builder_create_compile_unit = LLVMDIBuilderCreateCompileUnit( + builder : DIBuilderRef, lang : LLVM::DwarfSourceLanguage, file_ref : MetadataRef, producer : Char*, + producer_len : SizeT, is_optimized : Int, flags : Char*, flags_len : SizeT, runtime_ver : UInt, + split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt, + split_debug_inlining : Int, debug_info_for_profiling : Int, sys_root : Char*, + sys_root_len : SizeT, sdk : Char*, sdk_len : SizeT + ) : MetadataRef + {% end %} + + fun di_builder_create_file = LLVMDIBuilderCreateFile( + builder : DIBuilderRef, filename : Char*, filename_len : SizeT, + directory : Char*, directory_len : SizeT + ) : MetadataRef + + fun di_builder_create_function = LLVMDIBuilderCreateFunction( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, + linkage_name : Char*, linkage_name_len : SizeT, file : MetadataRef, line_no : UInt, + ty : MetadataRef, is_local_to_unit : Int, is_definition : Int, scope_line : UInt, + flags : LLVM::DIFlags, is_optimized : Int + ) : MetadataRef + + fun di_builder_create_lexical_block = LLVMDIBuilderCreateLexicalBlock( + builder : DIBuilderRef, scope : MetadataRef, file : MetadataRef, line : UInt, column : UInt + ) : MetadataRef + fun di_builder_create_lexical_block_file = LLVMDIBuilderCreateLexicalBlockFile( + builder : DIBuilderRef, scope : MetadataRef, file_scope : MetadataRef, discriminator : UInt + ) : MetadataRef + + fun di_builder_get_or_create_type_array = LLVMDIBuilderGetOrCreateTypeArray(builder : DIBuilderRef, types : MetadataRef*, length : SizeT) : MetadataRef + + fun di_builder_create_subroutine_type = LLVMDIBuilderCreateSubroutineType( + builder : DIBuilderRef, file : MetadataRef, parameter_types : MetadataRef*, + num_parameter_types : UInt, flags : LLVM::DIFlags + ) : MetadataRef + {% unless LibLLVM::IS_LT_90 %} + fun di_builder_create_enumerator = LLVMDIBuilderCreateEnumerator( + builder : DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : Int + ) : MetadataRef + {% end %} + fun di_builder_create_enumeration_type = LLVMDIBuilderCreateEnumerationType( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, + line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, + elements : MetadataRef*, num_elements : UInt, class_ty : MetadataRef + ) : MetadataRef + fun di_builder_create_union_type = LLVMDIBuilderCreateUnionType( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, + line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags, + elements : MetadataRef*, num_elements : UInt, run_time_lang : UInt, unique_id : Char*, unique_id_len : SizeT + ) : MetadataRef + fun di_builder_create_array_type = LLVMDIBuilderCreateArrayType( + builder : DIBuilderRef, size : UInt64, align_in_bits : UInt32, + ty : MetadataRef, subscripts : MetadataRef*, num_subscripts : UInt + ) : MetadataRef + fun di_builder_create_unspecified_type = LLVMDIBuilderCreateUnspecifiedType(builder : DIBuilderRef, name : Char*, name_len : SizeT) : MetadataRef + fun di_builder_create_basic_type = LLVMDIBuilderCreateBasicType( + builder : DIBuilderRef, name : Char*, name_len : SizeT, size_in_bits : UInt64, + encoding : UInt, flags : LLVM::DIFlags + ) : MetadataRef + fun di_builder_create_pointer_type = LLVMDIBuilderCreatePointerType( + builder : DIBuilderRef, pointee_ty : MetadataRef, size_in_bits : UInt64, align_in_bits : UInt32, + address_space : UInt, name : Char*, name_len : SizeT + ) : MetadataRef + fun di_builder_create_struct_type = LLVMDIBuilderCreateStructType( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, + line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags, + derived_from : MetadataRef, elements : MetadataRef*, num_elements : UInt, + run_time_lang : UInt, v_table_holder : MetadataRef, unique_id : Char*, unique_id_len : SizeT + ) : MetadataRef + fun di_builder_create_member_type = LLVMDIBuilderCreateMemberType( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, + line_no : UInt, size_in_bits : UInt64, align_in_bits : UInt32, offset_in_bits : UInt64, + flags : LLVM::DIFlags, ty : MetadataRef + ) : MetadataRef + fun di_builder_create_replaceable_composite_type = LLVMDIBuilderCreateReplaceableCompositeType( + builder : DIBuilderRef, tag : UInt, name : Char*, name_len : SizeT, scope : MetadataRef, + file : MetadataRef, line : UInt, runtime_lang : UInt, size_in_bits : UInt64, align_in_bits : UInt32, + flags : LLVM::DIFlags, unique_identifier : Char*, unique_identifier_len : SizeT + ) : MetadataRef + + fun di_builder_get_or_create_subrange = LLVMDIBuilderGetOrCreateSubrange(builder : DIBuilderRef, lo : Int64, count : Int64) : MetadataRef + fun di_builder_get_or_create_array = LLVMDIBuilderGetOrCreateArray(builder : DIBuilderRef, data : MetadataRef*, length : SizeT) : MetadataRef + + {% if LibLLVM::IS_LT_140 %} + fun di_builder_create_expression = LLVMDIBuilderCreateExpression(builder : DIBuilderRef, addr : Int64*, length : SizeT) : MetadataRef + {% else %} + fun di_builder_create_expression = LLVMDIBuilderCreateExpression(builder : DIBuilderRef, addr : UInt64*, length : SizeT) : MetadataRef + {% end %} + + fun metadata_replace_all_uses_with = LLVMMetadataReplaceAllUsesWith(target_metadata : MetadataRef, replacement : MetadataRef) + + fun di_builder_insert_declare_at_end = LLVMDIBuilderInsertDeclareAtEnd( + builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef, + expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef + ) : ValueRef + + fun di_builder_create_auto_variable = LLVMDIBuilderCreateAutoVariable( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, + line_no : UInt, ty : MetadataRef, always_preserve : Int, flags : LLVM::DIFlags, align_in_bits : UInt32 + ) : MetadataRef + fun di_builder_create_parameter_variable = LLVMDIBuilderCreateParameterVariable( + builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, arg_no : UInt, + file : MetadataRef, line_no : UInt, ty : MetadataRef, always_preserve : Int, flags : LLVM::DIFlags + ) : MetadataRef + + fun set_subprogram = LLVMSetSubprogram(func : ValueRef, sp : MetadataRef) +end diff --git a/src/llvm/lib_llvm/error.cr b/src/llvm/lib_llvm/error.cr new file mode 100644 index 000000000000..b816a7e2088b --- /dev/null +++ b/src/llvm/lib_llvm/error.cr @@ -0,0 +1,3 @@ +lib LibLLVM + type ErrorRef = Void* +end diff --git a/src/llvm/lib_llvm/execution_engine.cr b/src/llvm/lib_llvm/execution_engine.cr new file mode 100644 index 000000000000..1c58b5fcd046 --- /dev/null +++ b/src/llvm/lib_llvm/execution_engine.cr @@ -0,0 +1,32 @@ +require "./target" +require "./target_machine" +require "./types" + +lib LibLLVM + fun link_in_mc_jit = LLVMLinkInMCJIT + + type GenericValueRef = Void* + type ExecutionEngineRef = Void* + type MCJITMemoryManagerRef = Void* + + struct MCJITCompilerOptions + opt_level : UInt + code_model : LLVM::CodeModel + no_frame_pointer_elim : Bool + enable_fast_isel : Bool + mcjmm : MCJITMemoryManagerRef + end + + fun create_generic_value_of_int = LLVMCreateGenericValueOfInt(ty : TypeRef, n : ULongLong, is_signed : Bool) : GenericValueRef + fun create_generic_value_of_pointer = LLVMCreateGenericValueOfPointer(p : Void*) : GenericValueRef + fun generic_value_to_int = LLVMGenericValueToInt(gen_val : GenericValueRef, is_signed : Bool) : ULongLong + fun generic_value_to_pointer = LLVMGenericValueToPointer(gen_val : GenericValueRef) : Void* + fun generic_value_to_float = LLVMGenericValueToFloat(ty_ref : TypeRef, gen_val : GenericValueRef) : Double + fun dispose_generic_value = LLVMDisposeGenericValue(gen_val : GenericValueRef) + + fun create_jit_compiler_for_module = LLVMCreateJITCompilerForModule(out_jit : ExecutionEngineRef*, m : ModuleRef, opt_level : UInt, error : Char**) : Bool + fun create_mc_jit_compiler_for_module = LLVMCreateMCJITCompilerForModule(out_jit : ExecutionEngineRef*, m : ModuleRef, options : MCJITCompilerOptions*, size_of_options : SizeT, out_error : Char**) : Bool + fun dispose_execution_engine = LLVMDisposeExecutionEngine(ee : ExecutionEngineRef) + fun run_function = LLVMRunFunction(ee : ExecutionEngineRef, f : ValueRef, num_args : UInt, args : GenericValueRef*) : GenericValueRef + fun get_pointer_to_global = LLVMGetPointerToGlobal(ee : ExecutionEngineRef, global : ValueRef) : Void* +end diff --git a/src/llvm/lib_llvm/initialization.cr b/src/llvm/lib_llvm/initialization.cr new file mode 100644 index 000000000000..fee9bf1aea4a --- /dev/null +++ b/src/llvm/lib_llvm/initialization.cr @@ -0,0 +1,18 @@ +{% skip_file unless LibLLVM::IS_LT_170 %} + +require "./types" + +lib LibLLVM + fun initialize_core = LLVMInitializeCore(r : PassRegistryRef) + fun initialize_transform_utils = LLVMInitializeTransformUtils(r : PassRegistryRef) + fun initialize_scalar_opts = LLVMInitializeScalarOpts(r : PassRegistryRef) + fun initialize_obj_c_arc_opts = LLVMInitializeObjCARCOpts(r : PassRegistryRef) + fun initialize_vectorization = LLVMInitializeVectorization(r : PassRegistryRef) + fun initialize_inst_combine = LLVMInitializeInstCombine(r : PassRegistryRef) + fun initialize_ipo = LLVMInitializeIPO(r : PassRegistryRef) + fun initialize_instrumentation = LLVMInitializeInstrumentation(r : PassRegistryRef) + fun initialize_analysis = LLVMInitializeAnalysis(r : PassRegistryRef) + fun initialize_ipa = LLVMInitializeIPA(r : PassRegistryRef) + fun initialize_code_gen = LLVMInitializeCodeGen(r : PassRegistryRef) + fun initialize_target = LLVMInitializeTarget(r : PassRegistryRef) +end diff --git a/src/llvm/lib_llvm/ir_reader.cr b/src/llvm/lib_llvm/ir_reader.cr new file mode 100644 index 000000000000..36cc4d5930a2 --- /dev/null +++ b/src/llvm/lib_llvm/ir_reader.cr @@ -0,0 +1,5 @@ +require "./types" + +lib LibLLVM + fun parse_ir_in_context = LLVMParseIRInContext(context_ref : ContextRef, mem_buf : MemoryBufferRef, out_m : ModuleRef*, out_message : Char**) : Bool +end diff --git a/src/llvm/lib_llvm/target.cr b/src/llvm/lib_llvm/target.cr new file mode 100644 index 000000000000..74fe09ba48fe --- /dev/null +++ b/src/llvm/lib_llvm/target.cr @@ -0,0 +1,34 @@ +require "./types" + +lib LibLLVM + type TargetDataRef = Void* + + fun initialize_aarch64_target_info = LLVMInitializeAArch64TargetInfo + fun initialize_aarch64_target = LLVMInitializeAArch64Target + fun initialize_aarch64_target_mc = LLVMInitializeAArch64TargetMC + fun initialize_aarch64_asm_printer = LLVMInitializeAArch64AsmPrinter + fun initialize_aarch64_asm_parser = LLVMInitializeAArch64AsmParser + fun initialize_arm_target_info = LLVMInitializeARMTargetInfo + fun initialize_arm_target = LLVMInitializeARMTarget + fun initialize_arm_target_mc = LLVMInitializeARMTargetMC + fun initialize_arm_asm_printer = LLVMInitializeARMAsmPrinter + fun initialize_arm_asm_parser = LLVMInitializeARMAsmParser + fun initialize_webassembly_target_info = LLVMInitializeWebAssemblyTargetInfo + fun initialize_webassembly_target = LLVMInitializeWebAssemblyTarget + fun initialize_webassembly_target_mc = LLVMInitializeWebAssemblyTargetMC + fun initialize_webassembly_asm_printer = LLVMInitializeWebAssemblyAsmPrinter + fun initialize_webassembly_asm_parser = LLVMInitializeWebAssemblyAsmParser + fun initialize_x86_target_info = LLVMInitializeX86TargetInfo + fun initialize_x86_target = LLVMInitializeX86Target + fun initialize_x86_target_mc = LLVMInitializeX86TargetMC + fun initialize_x86_asm_printer = LLVMInitializeX86AsmPrinter + fun initialize_x86_asm_parser = LLVMInitializeX86AsmParser + + fun set_module_data_layout = LLVMSetModuleDataLayout(m : ModuleRef, dl : TargetDataRef) + + fun dispose_target_data = LLVMDisposeTargetData(td : TargetDataRef) + fun size_of_type_in_bits = LLVMSizeOfTypeInBits(td : TargetDataRef, ty : TypeRef) : ULongLong + fun abi_size_of_type = LLVMABISizeOfType(td : TargetDataRef, ty : TypeRef) : ULongLong + fun abi_alignment_of_type = LLVMABIAlignmentOfType(td : TargetDataRef, ty : TypeRef) : UInt + fun offset_of_element = LLVMOffsetOfElement(td : TargetDataRef, struct_ty : TypeRef, element : UInt) : ULongLong +end diff --git a/src/llvm/lib_llvm/target_machine.cr b/src/llvm/lib_llvm/target_machine.cr new file mode 100644 index 000000000000..ce1d7c64fa97 --- /dev/null +++ b/src/llvm/lib_llvm/target_machine.cr @@ -0,0 +1,24 @@ +require "./target" +require "./types" + +lib LibLLVM + type TargetMachineRef = Void* + type TargetRef = Void* + + fun get_first_target = LLVMGetFirstTarget : TargetRef + fun get_next_target = LLVMGetNextTarget(t : TargetRef) : TargetRef + fun get_target_from_triple = LLVMGetTargetFromTriple(triple : Char*, t : TargetRef*, error_message : Char**) : Bool + fun get_target_name = LLVMGetTargetName(t : TargetRef) : Char* + fun get_target_description = LLVMGetTargetDescription(t : TargetRef) : Char* + + fun create_target_machine = LLVMCreateTargetMachine(t : TargetRef, triple : Char*, cpu : Char*, features : Char*, level : LLVM::CodeGenOptLevel, reloc : LLVM::RelocMode, code_model : LLVM::CodeModel) : TargetMachineRef + fun dispose_target_machine = LLVMDisposeTargetMachine(t : TargetMachineRef) + fun get_target_machine_target = LLVMGetTargetMachineTarget(t : TargetMachineRef) : TargetRef + fun get_target_machine_triple = LLVMGetTargetMachineTriple(t : TargetMachineRef) : Char* + fun create_target_data_layout = LLVMCreateTargetDataLayout(t : TargetMachineRef) : TargetDataRef + fun target_machine_emit_to_file = LLVMTargetMachineEmitToFile(t : TargetMachineRef, m : ModuleRef, filename : Char*, codegen : LLVM::CodeGenFileType, error_message : Char**) : Bool + + fun get_default_target_triple = LLVMGetDefaultTargetTriple : Char* + fun normalize_target_triple = LLVMNormalizeTargetTriple(triple : Char*) : Char* + fun get_host_cpu_name = LLVMGetHostCPUName : Char* +end diff --git a/src/llvm/lib_llvm/transforms/pass_builder.cr b/src/llvm/lib_llvm/transforms/pass_builder.cr new file mode 100644 index 000000000000..41a569c4f0a2 --- /dev/null +++ b/src/llvm/lib_llvm/transforms/pass_builder.cr @@ -0,0 +1,13 @@ +{% skip_file if LibLLVM::IS_LT_130 %} + +require "../target_machine" +require "../types" + +lib LibLLVM + type PassBuilderOptionsRef = Void* + + fun run_passes = LLVMRunPasses(m : ModuleRef, passes : Char*, tm : TargetMachineRef, options : PassBuilderOptionsRef) : ErrorRef + + fun create_pass_builder_options = LLVMCreatePassBuilderOptions : PassBuilderOptionsRef + fun dispose_pass_builder_options = LLVMDisposePassBuilderOptions(options : PassBuilderOptionsRef) +end diff --git a/src/llvm/lib_llvm/transforms/pass_manager_builder.cr b/src/llvm/lib_llvm/transforms/pass_manager_builder.cr new file mode 100644 index 000000000000..305d7e372755 --- /dev/null +++ b/src/llvm/lib_llvm/transforms/pass_manager_builder.cr @@ -0,0 +1,17 @@ +{% skip_file unless LibLLVM::IS_LT_170 %} + +require "../types" + +lib LibLLVM + type PassManagerBuilderRef = Void* + + fun pass_manager_builder_create = LLVMPassManagerBuilderCreate : PassManagerBuilderRef + fun dispose_pass_manager_builder = LLVMPassManagerBuilderDispose(pmb : PassManagerBuilderRef) + fun pass_manager_builder_set_opt_level = LLVMPassManagerBuilderSetOptLevel(pmb : PassManagerBuilderRef, opt_level : UInt) + fun pass_manager_builder_set_size_level = LLVMPassManagerBuilderSetSizeLevel(pmb : PassManagerBuilderRef, size_level : UInt) + fun pass_manager_builder_set_disable_unroll_loops = LLVMPassManagerBuilderSetDisableUnrollLoops(pmb : PassManagerBuilderRef, value : Bool) + fun pass_manager_builder_set_disable_simplify_lib_calls = LLVMPassManagerBuilderSetDisableSimplifyLibCalls(pmb : PassManagerBuilderRef, value : Bool) + fun pass_manager_builder_use_inliner_with_threshold = LLVMPassManagerBuilderUseInlinerWithThreshold(pmb : PassManagerBuilderRef, threshold : UInt) + fun pass_manager_builder_populate_function_pass_manager = LLVMPassManagerBuilderPopulateFunctionPassManager(pmb : PassManagerBuilderRef, pm : PassManagerRef) + fun pass_manager_builder_populate_module_pass_manager = LLVMPassManagerBuilderPopulateModulePassManager(pmb : PassManagerBuilderRef, pm : PassManagerRef) +end diff --git a/src/llvm/lib_llvm/types.cr b/src/llvm/lib_llvm/types.cr new file mode 100644 index 000000000000..0414757c57f0 --- /dev/null +++ b/src/llvm/lib_llvm/types.cr @@ -0,0 +1,19 @@ +lib LibLLVM + # LLVMBool + alias Bool = LibC::Int + + type MemoryBufferRef = Void* + type ContextRef = Void* + type ModuleRef = Void* + type TypeRef = Void* + type ValueRef = Void* + type BasicBlockRef = Void* + type MetadataRef = Void* + type BuilderRef = Void* + type DIBuilderRef = Void* + type PassManagerRef = Void* + {% if LibLLVM::IS_LT_170 %} + type PassRegistryRef = Void* + {% end %} + type AttributeRef = Void* +end diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr index 0a050bdd8c3e..f9bd71f25bde 100644 --- a/src/llvm/lib_llvm_ext.cr +++ b/src/llvm/lib_llvm_ext.cr @@ -33,5 +33,5 @@ lib LibLLVMExt name : LibC::Char*) : LibLLVM::ValueRef fun target_machine_enable_global_isel = LLVMExtTargetMachineEnableGlobalIsel(machine : LibLLVM::TargetMachineRef, enable : Bool) - fun create_mc_jit_compiler_for_module = LLVMExtCreateMCJITCompilerForModule(jit : LibLLVM::ExecutionEngineRef*, m : LibLLVM::ModuleRef, options : LibLLVM::JITCompilerOptions*, options_length : UInt32, enable_global_isel : Bool, error : UInt8**) : Int32 + fun create_mc_jit_compiler_for_module = LLVMExtCreateMCJITCompilerForModule(jit : LibLLVM::ExecutionEngineRef*, m : LibLLVM::ModuleRef, options : LibLLVM::MCJITCompilerOptions*, options_length : UInt32, enable_global_isel : Bool, error : UInt8**) : Int32 end From 0bde50b4e56beb936d9e00ddad0767c73aefb4d5 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 8 Nov 2023 18:15:32 +0800 Subject: [PATCH 0766/1551] Check for invalid integers in compiler's CLI (#13959) --- src/compiler/crystal/command.cr | 5 +++-- src/compiler/crystal/command/playground.cr | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 8341b41d9f3b..2c5492445e0b 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -503,7 +503,8 @@ class Crystal::Command compiler.release! end opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3") do |level| - compiler.optimization_mode = Compiler::OptimizationMode.from_value?(level.to_i) || raise Error.new("Unknown optimization mode #{level}") + optimization_mode = level.to_i?.try { |v| Compiler::OptimizationMode.from_value?(v) } + compiler.optimization_mode = optimization_mode || raise Error.new("Invalid optimization mode: #{level}") end end @@ -524,7 +525,7 @@ class Crystal::Command compiler.single_module = true end opts.on("--threads NUM", "Maximum number of threads to use") do |n_threads| - compiler.n_threads = n_threads.to_i + compiler.n_threads = n_threads.to_i? || raise Error.new("Invalid thread count: #{n_threads}") end unless run opts.on("--target TRIPLE", "Target triple") do |triple| diff --git a/src/compiler/crystal/command/playground.cr b/src/compiler/crystal/command/playground.cr index 5869dee9a0a2..b7a2666a1a5a 100644 --- a/src/compiler/crystal/command/playground.cr +++ b/src/compiler/crystal/command/playground.cr @@ -11,7 +11,9 @@ class Crystal::Command opts.banner = "Usage: crystal play [options] [file]\n\nOptions:" opts.on("-p PORT", "--port PORT", "Runs the playground on the specified port") do |port| - server.port = port.to_i + port = port.to_i? + raise Error.new("Invalid port number: #{port}") unless port && Socket::IPAddress.valid_port?(port) + server.port = port end opts.on("-b HOST", "--binding HOST", "Binds the playground to the specified IP") do |host| From 704ab702406945ac131df81337d7bae8a6b92c30 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 9 Nov 2023 04:15:11 +0800 Subject: [PATCH 0767/1551] Add `Crystal::System::Thread` (#13814) --- src/crystal/system/thread.cr | 131 ++++++++++++++++++++++---- src/crystal/system/unix/pthread.cr | 142 ++++++++--------------------- src/crystal/system/wasi/thread.cr | 52 +++-------- src/crystal/system/win32/thread.cr | 118 +++++------------------- src/gc/boehm.cr | 6 +- src/gc/none.cr | 6 +- 6 files changed, 189 insertions(+), 266 deletions(-) diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index 9e58962fd49b..878be639a7a3 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -1,26 +1,127 @@ +# :nodoc: +module Crystal::System::Thread + # alias Handle + + # def self.new_handle(thread_obj : ::Thread) : Handle + + # def self.current_handle : Handle + + # def self.yield_current : Nil + + # def self.current_thread : ::Thread + + # def self.current_thread=(thread : ::Thread) + + # private def system_join : Exception? + + # private def system_close + + # private def stack_address : Void* +end + +{% if flag?(:wasi) %} + require "./wasi/thread" +{% elsif flag?(:unix) %} + require "./unix/pthread" +{% elsif flag?(:win32) %} + require "./win32/thread" +{% else %} + {% raise "Thread not supported" %} +{% end %} + # :nodoc: class Thread + include Crystal::System::Thread + + # all thread objects, so the GC can see them (it doesn't scan thread locals) + protected class_getter(threads) { Thread::LinkedList(Thread).new } + + @system_handle : Crystal::System::Thread::Handle + @exception : Exception? + @detached = Atomic::Flag.new + + # Returns the Fiber representing the thread's main stack. + getter! main_fiber : Fiber + + # :nodoc: + property next : Thread? + + # :nodoc: + property previous : Thread? + + def self.unsafe_each(&) + threads.unsafe_each { |thread| yield thread } + end + # Creates and starts a new system thread. - # def initialize(&proc : ->) + def initialize(&@func : ->) + @system_handle = uninitialized Crystal::System::Thread::Handle + init_handle + end # Used once to initialize the thread object representing the main thread of # the process (that already exists). - # def initialize + def initialize + @func = ->{} + @system_handle = Crystal::System::Thread.current_handle + @main_fiber = Fiber.new(stack_address, self) - # Suspends the current thread until this thread terminates. - # def join : Nil + Thread.threads.push(self) + end - # Returns the Fiber representing the thread's main stack. - # def main_fiber + private def detach(&) + if @detached.test_and_set + yield + end + end + + # Suspends the current thread until this thread terminates. + def join : Nil + detach do + if ex = system_join + @exception ||= ex + end + end - # Yields the thread. - # def self.yield : Nil + if exception = @exception + raise exception + end + end # Returns the Thread object associated to the running system thread. - # def self.current : Thread + def self.current : Thread + Crystal::System::Thread.current_thread + end # Associates the Thread object to the running system thread. - # def self.current=(thread : Thread) + protected def self.current=(current : Thread) : Thread + Crystal::System::Thread.current_thread = current + current + end + + # Yields the currently running thread. + def self.yield : Nil + Crystal::System::Thread.yield_current + end + + # :nodoc: + getter scheduler : Crystal::Scheduler { Crystal::Scheduler.new(main_fiber) } + + protected def start + Thread.threads.push(self) + Thread.current = self + @main_fiber = fiber = Fiber.new(stack_address, self) + + begin + @func.call + rescue ex + @exception = ex + ensure + Thread.threads.delete(self) + Fiber.inactive(fiber) + detach { system_close } + end + end # Holds the GC thread handler property gc_thread_handler : Void* = Pointer(Void).null @@ -28,13 +129,3 @@ end require "./thread_linked_list" require "./thread_condition_variable" - -{% if flag?(:wasi) %} - require "./wasi/thread" -{% elsif flag?(:unix) %} - require "./unix/pthread" -{% elsif flag?(:win32) %} - require "./win32/thread" -{% else %} - {% raise "Thread not supported" %} -{% end %} diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index bde5cc7b4333..44076dedcc43 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -1,62 +1,34 @@ require "c/pthread" require "c/sched" -class Thread - # all thread objects, so the GC can see them (it doesn't scan thread locals) - protected class_getter(threads) { Thread::LinkedList(Thread).new } +module Crystal::System::Thread + alias Handle = LibC::PthreadT - @th : LibC::PthreadT - @exception : Exception? - @detached = Atomic(UInt8).new(0) - @main_fiber : Fiber? - - # :nodoc: - property next : Thread? - - # :nodoc: - property previous : Thread? - - def self.unsafe_each(&) - threads.unsafe_each { |thread| yield thread } - end - - # Starts a new system thread. - def initialize(&@func : ->) - @th = uninitialized LibC::PthreadT - - ret = GC.pthread_create(pointerof(@th), Pointer(LibC::PthreadAttrT).null, ->(data : Void*) { - (data.as(Thread)).start - Pointer(Void).null - }, self.as(Void*)) - - if ret != 0 - raise RuntimeError.from_os_error("pthread_create", Errno.new(ret)) - end + def to_unsafe + @system_handle end - # Used once to initialize the thread object representing the main thread of - # the process (that already exists). - def initialize - @func = ->{} - @th = LibC.pthread_self - @main_fiber = Fiber.new(stack_address, self) - - Thread.threads.push(self) + private def init_handle + # NOTE: the thread may start before `pthread_create` returns, so + # `@system_handle` must be set as soon as possible; we cannot use a separate + # handle and assign it to `@system_handle`, which would have been too late + ret = GC.pthread_create( + thread: pointerof(@system_handle), + attr: Pointer(LibC::PthreadAttrT).null, + start: ->(data : Void*) { data.as(::Thread).start; Pointer(Void).null }, + arg: self.as(Void*), + ) + + raise RuntimeError.from_os_error("pthread_create", Errno.new(ret)) unless ret == 0 end - private def detach(&) - if @detached.compare_and_set(0, 1).last - yield - end + def self.current_handle : Handle + LibC.pthread_self end - # Suspends the current thread until this thread terminates. - def join : Nil - detach { GC.pthread_join(@th) } - - if exception = @exception - raise exception - end + def self.yield_current : Nil + ret = LibC.sched_yield + raise RuntimeError.from_errno("sched_yield") unless ret == 0 end {% if flag?(:openbsd) %} @@ -70,68 +42,33 @@ class Thread current_key end - # Returns the Thread object associated to the running system thread. - def self.current : Thread + def self.current_thread : ::Thread if ptr = LibC.pthread_getspecific(@@current_key) - ptr.as(Thread) + ptr.as(::Thread) else - # Thread#start sets @@current as soon it starts. Thus we know - # that if @@current is not set then we are in the main thread - self.current = new + # Thread#start sets `Thread.current` as soon it starts. Thus we know + # that if `Thread.current` is not set then we are in the main thread + self.current_thread = ::Thread.new end end - # Associates the Thread object to the running system thread. - protected def self.current=(thread : Thread) : Thread + def self.current_thread=(thread : ::Thread) ret = LibC.pthread_setspecific(@@current_key, thread.as(Void*)) raise RuntimeError.from_os_error("pthread_setspecific", Errno.new(ret)) unless ret == 0 thread end {% else %} @[ThreadLocal] - @@current : Thread? - - # Returns the Thread object associated to the running system thread. - def self.current : Thread - # Thread#start sets @@current as soon it starts. Thus we know - # that if @@current is not set then we are in the main thread - @@current ||= new - end - - # Associates the Thread object to the running system thread. - protected def self.current=(@@current : Thread) : Thread - end + class_property current_thread : ::Thread { ::Thread.new } {% end %} - def self.yield : Nil - ret = LibC.sched_yield - raise RuntimeError.from_errno("sched_yield") unless ret == 0 - end - - # Returns the Fiber representing the thread's main stack. - def main_fiber : Fiber - @main_fiber.not_nil! - end - - # :nodoc: - def scheduler : Crystal::Scheduler - @scheduler ||= Crystal::Scheduler.new(main_fiber) + private def system_join : Exception? + ret = GC.pthread_join(@system_handle) + RuntimeError.from_os_error("pthread_join", Errno.new(ret)) unless ret == 0 end - protected def start - Thread.threads.push(self) - Thread.current = self - @main_fiber = fiber = Fiber.new(stack_address, self) - - begin - @func.call - rescue ex - @exception = ex - ensure - Thread.threads.delete(self) - Fiber.inactive(fiber) - detach { GC.pthread_detach(@th) } - end + private def system_close + GC.pthread_detach(@system_handle) end private def stack_address : Void* @@ -139,7 +76,7 @@ class Thread {% if flag?(:darwin) %} # FIXME: pthread_get_stacksize_np returns bogus value on macOS X 10.9.0: - address = LibC.pthread_get_stackaddr_np(@th) - LibC.pthread_get_stacksize_np(@th) + address = LibC.pthread_get_stackaddr_np(@system_handle) - LibC.pthread_get_stacksize_np(@system_handle) {% elsif flag?(:bsd) && !flag?(:openbsd) %} ret = LibC.pthread_attr_init(out attr) unless ret == 0 @@ -147,19 +84,19 @@ class Thread raise RuntimeError.from_os_error("pthread_attr_init", Errno.new(ret)) end - if LibC.pthread_attr_get_np(@th, pointerof(attr)) == 0 + if LibC.pthread_attr_get_np(@system_handle, pointerof(attr)) == 0 LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out _) end ret = LibC.pthread_attr_destroy(pointerof(attr)) raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0 {% elsif flag?(:linux) %} - if LibC.pthread_getattr_np(@th, out attr) == 0 + if LibC.pthread_getattr_np(@system_handle, out attr) == 0 LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out _) end ret = LibC.pthread_attr_destroy(pointerof(attr)) raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0 {% elsif flag?(:openbsd) %} - ret = LibC.pthread_stackseg_np(@th, out stack) + ret = LibC.pthread_stackseg_np(@system_handle, out stack) raise RuntimeError.from_os_error("pthread_stackseg_np", Errno.new(ret)) unless ret == 0 address = @@ -172,11 +109,6 @@ class Thread address end - - # :nodoc: - def to_unsafe - @th - end end # In musl (alpine) the calls to unwind API segfaults diff --git a/src/crystal/system/wasi/thread.cr b/src/crystal/system/wasi/thread.cr index 805c7fbb77a6..0e641faba785 100644 --- a/src/crystal/system/wasi/thread.cr +++ b/src/crystal/system/wasi/thread.cr @@ -1,53 +1,25 @@ -class Thread - @main_fiber : Fiber? +module Crystal::System::Thread + alias Handle = Nil - def initialize - @main_fiber = Fiber.new(stack_address, self) - - # TODO: Create thread - end - - def initialize(&func : ->) - initialize - end - - def join : Nil - raise NotImplementedError.new("Thread#join") + def self.new_handle(thread_obj : ::Thread) : Handle + raise NotImplementedError.new("Crystal::System::Thread.new_handle") end - def self.yield : Nil - raise NotImplementedError.new("Thread.yield") + def self.current_handle : Handle + nil end - @@current = Thread.new - - # Associates the Thread object to the running system thread. - protected def self.current=(@@current : Thread) : Thread - end - - # Returns the Thread object associated to the running system thread. - def self.current : Thread - @@current + def self.yield_current : Nil + raise NotImplementedError.new("Crystal::System::Thread.yield_current") end - # Create the thread object for the current thread (aka the main thread of the - # process). - # - # TODO: consider moving to `kernel.cr` or `crystal/main.cr` - self.current = new - - # Returns the Fiber representing the thread's main stack. - def main_fiber - @main_fiber.not_nil! - end + class_property current_thread : ::Thread { ::Thread.new } - # :nodoc: - def scheduler - @scheduler ||= Crystal::Scheduler.new(main_fiber) + private def system_join : Exception? + NotImplementedError.new("Crystal::System::Thread#system_join") end - protected def start - raise NotImplementedError.new("Thread#start") + private def system_close end private def stack_address : Void* diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr index badfff437ed5..9a13bfb4dc03 100644 --- a/src/crystal/system/win32/thread.cr +++ b/src/crystal/system/win32/thread.cr @@ -1,115 +1,52 @@ require "c/processthreadsapi" require "c/synchapi" -class Thread - # all thread objects, so the GC can see them (it doesn't scan thread locals) - protected class_getter(threads) { Thread::LinkedList(Thread).new } +module Crystal::System::Thread + alias Handle = LibC::HANDLE - @th : LibC::HANDLE - @exception : Exception? - @detached = Atomic(UInt8).new(0) - @main_fiber : Fiber? - - # :nodoc: - property next : Thread? - - # :nodoc: - property previous : Thread? - - def self.unsafe_each(&) - threads.unsafe_each { |thread| yield thread } + def to_unsafe + @system_handle end - # Starts a new system thread. - def initialize(&@func : ->) - @th = uninitialized LibC::HANDLE - - @th = GC.beginthreadex( + private def init_handle + @system_handle = GC.beginthreadex( security: Pointer(Void).null, stack_size: LibC::UInt.zero, - start_address: ->(data : Void*) { data.as(Thread).start; LibC::UInt.zero }, + start_address: ->(data : Void*) { data.as(::Thread).start; LibC::UInt.zero }, arglist: self.as(Void*), initflag: LibC::UInt.zero, - thrdaddr: Pointer(LibC::UInt).null) + thrdaddr: Pointer(LibC::UInt).null, + ) end - # Used once to initialize the thread object representing the main thread of - # the process (that already exists). - def initialize + def self.current_handle : Handle # `GetCurrentThread` returns a _constant_ and is only meaningful as an # argument to Win32 APIs; to uniquely identify it we must duplicate the handle - @th = uninitialized LibC::HANDLE cur_proc = LibC.GetCurrentProcess - LibC.DuplicateHandle(cur_proc, LibC.GetCurrentThread, cur_proc, pointerof(@th), 0, true, LibC::DUPLICATE_SAME_ACCESS) - - @func = ->{} - @main_fiber = Fiber.new(stack_address, self) - - Thread.threads.push(self) - end - - private def detach(&) - if @detached.compare_and_set(0, 1).last - yield + if LibC.DuplicateHandle(cur_proc, LibC.GetCurrentThread, cur_proc, out handle, 0, true, LibC::DUPLICATE_SAME_ACCESS) == 0 + raise RuntimeError.from_winerror("DuplicateHandle") end + handle end - # Suspends the current thread until this thread terminates. - def join : Nil - detach do - if LibC.WaitForSingleObject(@th, LibC::INFINITE) != LibC::WAIT_OBJECT_0 - @exception ||= RuntimeError.from_winerror("WaitForSingleObject") - end - if LibC.CloseHandle(@th) == 0 - @exception ||= RuntimeError.from_winerror("CloseHandle") - end - end - - if exception = @exception - raise exception - end - end - - @[ThreadLocal] - @@current : Thread? - - # Returns the Thread object associated to the running system thread. - def self.current : Thread - @@current ||= new - end - - # Associates the Thread object to the running system thread. - protected def self.current=(@@current : Thread) : Thread - end - - def self.yield : Nil + def self.yield_current : Nil LibC.SwitchToThread end - # Returns the Fiber representing the thread's main stack. - def main_fiber : Fiber - @main_fiber.not_nil! - end + @[ThreadLocal] + class_property current_thread : ::Thread { ::Thread.new } - # :nodoc: - def scheduler : Crystal::Scheduler - @scheduler ||= Crystal::Scheduler.new(main_fiber) + private def system_join : Exception? + if LibC.WaitForSingleObject(@system_handle, LibC::INFINITE) != LibC::WAIT_OBJECT_0 + return RuntimeError.from_winerror("WaitForSingleObject") + end + if LibC.CloseHandle(@system_handle) == 0 + return RuntimeError.from_winerror("CloseHandle") + end end - protected def start - Thread.threads.push(self) - Thread.current = self - @main_fiber = fiber = Fiber.new(stack_address, self) - - begin - @func.call - rescue ex - @exception = ex - ensure - Thread.threads.delete(self) - Fiber.inactive(fiber) - detach { LibC.CloseHandle(@th) } - end + private def system_close + LibC.CloseHandle(@system_handle) end private def stack_address : Void* @@ -124,9 +61,4 @@ class Thread low_limit {% end %} end - - # :nodoc: - def to_unsafe - @th - end end diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 28056f75aead..b4dfb0061900 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -262,10 +262,8 @@ module GC end # :nodoc: - def self.pthread_join(thread : LibC::PthreadT) : Void* - ret = LibGC.pthread_join(thread, out value) - raise RuntimeError.from_os_error("pthread_join", Errno.new(ret)) unless ret == 0 - value + def self.pthread_join(thread : LibC::PthreadT) + LibGC.pthread_join(thread, nil) end # :nodoc: diff --git a/src/gc/none.cr b/src/gc/none.cr index 4e4441f6e54e..c71ab05ccd8d 100644 --- a/src/gc/none.cr +++ b/src/gc/none.cr @@ -85,10 +85,8 @@ module GC end # :nodoc: - def self.pthread_join(thread : LibC::PthreadT) : Void* - ret = LibC.pthread_join(thread, out value) - raise RuntimeError.from_errno("pthread_join") unless ret == 0 - value + def self.pthread_join(thread : LibC::PthreadT) + LibC.pthread_join(thread, nil) end # :nodoc: From 92edee449386c97878d8156144703420f5574727 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 9 Nov 2023 04:15:35 +0800 Subject: [PATCH 0768/1551] Use `LibGMP::SI` and `UI` for size checks, not `Long` and `ULong` (#13874) --- spec/std/big/big_int_spec.cr | 2 +- src/big/big_float.cr | 51 +++---- src/big/big_int.cr | 282 +++++++++++++++++------------------ src/big/big_rational.cr | 16 +- src/big/lib_gmp.cr | 4 - src/big/number.cr | 69 ++++++++- 6 files changed, 225 insertions(+), 199 deletions(-) diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr index 07575892bc08..8a86939efbac 100644 --- a/spec/std/big/big_int_spec.cr +++ b/spec/std/big/big_int_spec.cr @@ -198,7 +198,7 @@ describe "BigInt" do it "raises if factorial of 2^64" do expect_raises ArgumentError do - (LibGMP::ULong::MAX.to_big_i + 1).factorial + (LibGMP::UI::MAX.to_big_i + 1).factorial end end diff --git a/src/big/big_float.cr b/src/big/big_float.cr index 5464d948931b..7377afda34b3 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -40,29 +40,16 @@ struct BigFloat < Float LibGMP.mpf_set(self, num) end - def initialize(num : Int8 | Int16 | Int32) - LibGMP.mpf_init_set_si(out @mpf, num) - end - - def initialize(num : UInt8 | UInt16 | UInt32) - LibGMP.mpf_init_set_ui(out @mpf, num) - end - - def initialize(num : Int64) - if LibGMP::Long == Int64 - LibGMP.mpf_init_set_si(out @mpf, num) - else - LibGMP.mpf_init(out @mpf) - LibGMP.mpf_set_z(self, num.to_big_i) - end - end - - def initialize(num : UInt64) - if LibGMP::ULong == UInt64 - LibGMP.mpf_init_set_ui(out @mpf, num) - else - LibGMP.mpf_init(out @mpf) - LibGMP.mpf_set_z(self, num.to_big_i) + def initialize(num : Int) + Int.primitive_si_ui_check(num) do |si, ui, big_i| + { + si: LibGMP.mpf_init_set_si(out @mpf, {{ si }}), + ui: LibGMP.mpf_init_set_ui(out @mpf, {{ ui }}), + big_i: begin + LibGMP.mpf_init(out @mpf) + LibGMP.mpf_set_z(self, {{ big_i }}) + end, + } end end @@ -113,16 +100,20 @@ struct BigFloat < Float LibGMP.mpf_cmp_d(self, other) unless other.nan? end - def <=>(other : Number) - if other.is_a?(Int8 | Int16 | Int32) || (LibGMP::Long == Int64 && other.is_a?(Int64)) - LibGMP.mpf_cmp_si(self, other) - elsif other.is_a?(UInt8 | UInt16 | UInt32) || (LibGMP::ULong == UInt64 && other.is_a?(UInt64)) - LibGMP.mpf_cmp_ui(self, other) - else - LibGMP.mpf_cmp(self, other.to_big_f) + def <=>(other : Int) + Int.primitive_si_ui_check(other) do |si, ui, big_i| + { + si: LibGMP.mpf_cmp_si(self, {{ si }}), + ui: LibGMP.mpf_cmp_ui(self, {{ ui }}), + big_i: self <=> {{ big_i }}, + } end end + def <=>(other : Number) + LibGMP.mpf_cmp(self, other.to_big_f) + end + def - : BigFloat BigFloat.new { |mpf| LibGMP.mpf_neg(mpf, self) } end diff --git a/src/big/big_int.cr b/src/big/big_int.cr index 9cc3f15e5890..decf7a0828ff 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -48,29 +48,33 @@ struct BigInt < Int # Creates a `BigInt` from the given *num*. def self.new(num : Int::Primitive) - if LibGMP::SI::MIN <= num <= LibGMP::UI::MAX - if num <= LibGMP::SI::MAX - LibGMP.init_set_si(out mpz1, LibGMP::SI.new!(num)) - new(mpz1) - else - LibGMP.init_set_ui(out mpz2, LibGMP::UI.new!(num)) - new(mpz2) - end - else - negative = num < 0 - num = num.abs_unsigned - capacity = (num.bit_length - 1) // (sizeof(LibGMP::MpLimb) * 8) + 1 - - # This assumes GMP wasn't built with its experimental nails support: - # https://gmplib.org/manual/Low_002dlevel-Functions - unsafe_build(capacity) do |limbs| - appender = limbs.to_unsafe.appender - limbs.size.times do - appender << LibGMP::MpLimb.new!(num) - num = num.unsafe_shr(sizeof(LibGMP::MpLimb) * 8) - end - {capacity, negative} - end + Int.primitive_si_ui_check(num) do |si, ui, _| + { + si: begin + LibGMP.init_set_si(out mpz1, {{ si }}) + new(mpz1) + end, + ui: begin + LibGMP.init_set_ui(out mpz2, {{ ui }}) + new(mpz2) + end, + big_i: begin + negative = num < 0 + num = num.abs_unsigned + capacity = (num.bit_length - 1) // (sizeof(LibGMP::MpLimb) * 8) + 1 + + # This assumes GMP wasn't built with its experimental nails support: + # https://gmplib.org/manual/Low_002dlevel-Functions + unsafe_build(capacity) do |limbs| + appender = limbs.to_unsafe.appender + limbs.size.times do + appender << LibGMP::MpLimb.new!(num) + num = num.unsafe_shr(sizeof(LibGMP::MpLimb) * 8) + end + {capacity, negative} + end + end, + } end end @@ -141,19 +145,13 @@ struct BigInt < Int LibGMP.cmp(mpz, other) end - def <=>(other : Int::Signed) - if LibC::Long::MIN <= other <= LibC::Long::MAX - LibGMP.cmp_si(mpz, other) - else - self <=> BigInt.new(other) - end - end - - def <=>(other : Int::Unsigned) - if other <= LibC::ULong::MAX - LibGMP.cmp_ui(mpz, other) - else - self <=> BigInt.new(other) + def <=>(other : Int) + Int.primitive_si_ui_check(other) do |si, ui, big_i| + { + si: LibGMP.cmp_si(self, {{ si }}), + ui: LibGMP.cmp_ui(self, {{ ui }}), + big_i: self <=> {{ big_i }}, + } end end @@ -166,12 +164,12 @@ struct BigInt < Int end def +(other : Int) : BigInt - if other < 0 - self - other.abs - elsif other <= LibGMP::ULong::MAX - BigInt.new { |mpz| LibGMP.add_ui(mpz, self, other) } - else - self + other.to_big_i + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: BigInt.new { |mpz| LibGMP.add_ui(mpz, self, {{ ui }}) }, + neg_ui: BigInt.new { |mpz| LibGMP.sub_ui(mpz, self, {{ neg_ui }}) }, + big_i: self + {{ big_i }}, + } end end @@ -184,12 +182,12 @@ struct BigInt < Int end def -(other : Int) : BigInt - if other < 0 - self + other.abs - elsif other <= LibGMP::ULong::MAX - BigInt.new { |mpz| LibGMP.sub_ui(mpz, self, other) } - else - self - other.to_big_i + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: BigInt.new { |mpz| LibGMP.sub_ui(mpz, self, {{ ui }}) }, + neg_ui: BigInt.new { |mpz| LibGMP.add_ui(mpz, self, {{ neg_ui }}) }, + big_i: self - {{ big_i }}, + } end end @@ -208,26 +206,24 @@ struct BigInt < Int def factorial : BigInt if self < 0 raise ArgumentError.new("Factorial not defined for negative values") - elsif self > LibGMP::ULong::MAX - raise ArgumentError.new("Factorial not supported for numbers bigger than 2^64") + elsif self > LibGMP::UI::MAX + raise ArgumentError.new("Factorial not supported for numbers bigger than #{LibGMP::UI::MAX}") end - BigInt.new { |mpz| LibGMP.fac_ui(mpz, self) } + BigInt.new { |mpz| LibGMP.fac_ui(mpz, LibGMP::UI.new!(self)) } end def *(other : BigInt) : BigInt BigInt.new { |mpz| LibGMP.mul(mpz, self, other) } end - def *(other : LibGMP::IntPrimitiveSigned) : BigInt - BigInt.new { |mpz| LibGMP.mul_si(mpz, self, other) } - end - - def *(other : LibGMP::IntPrimitiveUnsigned) : BigInt - BigInt.new { |mpz| LibGMP.mul_ui(mpz, self, other) } - end - def *(other : Int) : BigInt - self * other.to_big_i + Int.primitive_si_ui_check(other) do |si, ui, big_i| + { + si: BigInt.new { |mpz| LibGMP.mul_si(mpz, self, {{ si }}) }, + ui: BigInt.new { |mpz| LibGMP.mul_ui(mpz, self, {{ ui }}) }, + big_i: self * {{ big_i }}, + } + end end def &*(other) : BigInt @@ -238,19 +234,10 @@ struct BigInt < Int Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational - def //(other : Int::Unsigned) : BigInt - check_division_by_zero other - unsafe_floored_div(other) - end - def //(other : Int) : BigInt check_division_by_zero other - if other < 0 - (-self).unsafe_floored_div(-other) - else - unsafe_floored_div(other) - end + unsafe_floored_div(other) end def tdiv(other : Int) : BigInt @@ -264,12 +251,12 @@ struct BigInt < Int end def unsafe_floored_div(other : Int) : BigInt - if LibGMP::ULong == UInt32 && (other < Int32::MIN || other > UInt32::MAX) - unsafe_floored_div(other.to_big_i) - elsif other < 0 - -BigInt.new { |mpz| LibGMP.fdiv_q_ui(mpz, self, other.abs) } - else - BigInt.new { |mpz| LibGMP.fdiv_q_ui(mpz, self, other) } + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: BigInt.new { |mpz| LibGMP.fdiv_q_ui(mpz, self, {{ ui }}) }, + neg_ui: BigInt.new { |mpz| LibGMP.fdiv_q_ui(mpz, -self, {{ neg_ui }}) }, + big_i: unsafe_floored_div({{ big_i }}), + } end end @@ -278,23 +265,19 @@ struct BigInt < Int end def unsafe_truncated_div(other : Int) : BigInt - if LibGMP::ULong == UInt32 && (other < Int32::MIN || other > UInt32::MAX) - unsafe_truncated_div(other.to_big_i) - elsif other < 0 - -BigInt.new { |mpz| LibGMP.tdiv_q_ui(mpz, self, other.abs) } - else - BigInt.new { |mpz| LibGMP.tdiv_q_ui(mpz, self, other) } + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: BigInt.new { |mpz| LibGMP.tdiv_q_ui(mpz, self, {{ ui }}) }, + neg_ui: BigInt.new { |mpz| LibGMP.tdiv_q_ui(mpz, self, {{ neg_ui }}); LibGMP.neg(mpz, mpz) }, + big_i: unsafe_truncated_div({{ big_i }}), + } end end def %(other : Int) : BigInt check_division_by_zero other - if other < 0 - -(-self).unsafe_floored_mod(other.abs) - else - unsafe_floored_mod(other) - end + unsafe_floored_mod(other) end def remainder(other : Int) : BigInt @@ -303,46 +286,23 @@ struct BigInt < Int unsafe_truncated_mod(other) end - def divmod(number : BigInt) : {BigInt, BigInt} + def divmod(number : Int) : {BigInt, BigInt} check_division_by_zero number unsafe_floored_divmod(number) end - def divmod(number : LibGMP::ULong) - check_division_by_zero number - unsafe_floored_divmod(number) - end - - def divmod(number : Int::Signed) : {BigInt, BigInt} - check_division_by_zero number - if number > 0 && number <= LibC::Long::MAX - unsafe_floored_divmod(LibGMP::ULong.new(number)) - else - divmod(number.to_big_i) - end - end - - def divmod(number : Int::Unsigned) - check_division_by_zero number - if number <= LibC::ULong::MAX - unsafe_floored_divmod(LibGMP::ULong.new(number)) - else - divmod(number.to_big_i) - end - end - def unsafe_floored_mod(other : BigInt) : BigInt BigInt.new { |mpz| LibGMP.fdiv_r(mpz, self, other) } end def unsafe_floored_mod(other : Int) : BigInt - if (other < LibGMP::Long::MIN || other > LibGMP::ULong::MAX) - unsafe_floored_mod(other.to_big_i) - elsif other < 0 - -BigInt.new { |mpz| LibGMP.fdiv_r_ui(mpz, self, other.abs) } - else - BigInt.new { |mpz| LibGMP.fdiv_r_ui(mpz, self, other) } + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: BigInt.new { |mpz| LibGMP.fdiv_r_ui(mpz, self, {{ ui }}) }, + neg_ui: BigInt.new { |mpz| LibGMP.fdiv_r_ui(mpz, self, {{ neg_ui }}); LibGMP.neg(mpz, mpz) }, + big_i: unsafe_floored_mod({{ big_i }}), + } end end @@ -350,12 +310,14 @@ struct BigInt < Int BigInt.new { |mpz| LibGMP.tdiv_r(mpz, self, other) } end - def unsafe_truncated_mod(other : LibGMP::IntPrimitive) : BigInt - BigInt.new { |mpz| LibGMP.tdiv_r_ui(mpz, self, other.abs) } - end - def unsafe_truncated_mod(other : Int) : BigInt - BigInt.new { |mpz| LibGMP.tdiv_r_ui(mpz, self, other.abs.to_big_i) } + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: BigInt.new { |mpz| LibGMP.tdiv_r_ui(mpz, self, {{ ui }}) }, + neg_ui: BigInt.new { |mpz| LibGMP.tdiv_r_ui(mpz, self, {{ neg_ui }}) }, + big_i: unsafe_truncated_mod({{ big_i }}), + } + end end def unsafe_floored_divmod(number : BigInt) : {BigInt, BigInt} @@ -364,9 +326,15 @@ struct BigInt < Int {the_q, the_r} end - def unsafe_floored_divmod(number : LibGMP::ULong) : {BigInt, BigInt} + def unsafe_floored_divmod(number : Int) : {BigInt, BigInt} the_q = BigInt.new - the_r = BigInt.new { |r| LibGMP.fdiv_qr_ui(the_q, r, self, number) } + the_r = Int.primitive_ui_check(number) do |ui, neg_ui, big_i| + { + ui: BigInt.new { |r| LibGMP.fdiv_qr_ui(the_q, r, self, {{ ui }}) }, + neg_ui: BigInt.new { |r| LibGMP.fdiv_qr_ui(the_q, r, -self, {{ neg_ui }}); LibGMP.neg(r, r) }, + big_i: BigInt.new { |r| LibGMP.fdiv_qr(the_q, r, self, {{ big_i }}) }, + } + end {the_q, the_r} end @@ -376,9 +344,15 @@ struct BigInt < Int {the_q, the_r} end - def unsafe_truncated_divmod(number : LibGMP::ULong) + def unsafe_truncated_divmod(number : Int) the_q = BigInt.new - the_r = BigInt.new { |r| LibGMP.tdiv_qr_ui(the_q, r, self, number) } + the_r = Int.primitive_ui_check(number) do |ui, neg_ui, big_i| + { + ui: BigInt.new { |r| LibGMP.tdiv_qr_ui(the_q, r, self, {{ ui }}) }, + neg_ui: BigInt.new { |r| LibGMP.tdiv_qr_ui(the_q, r, self, {{ neg_ui }}); LibGMP.neg(the_q, the_q) }, + big_i: BigInt.new { |r| LibGMP.tdiv_qr(the_q, r, self, {{ big_i }}) }, + } + end {the_q, the_r} end @@ -386,17 +360,13 @@ struct BigInt < Int LibGMP.divisible_p(self, number) != 0 end - def divisible_by?(number : LibGMP::ULong) : Bool - LibGMP.divisible_ui_p(self, number) != 0 - end - def divisible_by?(number : Int) : Bool - if 0 <= number <= LibGMP::ULong::MAX - LibGMP.divisible_ui_p(self, number) != 0 - elsif LibGMP::Long::MIN < number < 0 - LibGMP.divisible_ui_p(self, number.abs) != 0 - else - divisible_by?(number.to_big_i) + Int.primitive_ui_check(number) do |ui, neg_ui, big_i| + { + ui: LibGMP.divisible_ui_p(self, {{ ui }}) != 0, + neg_ui: LibGMP.divisible_ui_p(self, {{ neg_ui }}) != 0, + big_i: divisible_by?({{ big_i }}), + } end end @@ -459,8 +429,19 @@ struct BigInt < Int # :ditto: def gcd(other : Int) : Int - result = LibGMP.gcd_ui(nil, self, other.abs.to_u64) - result == 0 ? self : result + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: begin + result = LibGMP.gcd_ui(nil, self, {{ ui }}) + result == 0 ? self : result + end, + neg_ui: begin + result = LibGMP.gcd_ui(nil, self, {{ neg_ui }}) + result == 0 ? self : result + end, + big_i: gcd({{ big_i }}), + } + end end # Returns the least common multiple of `self` and *other*. @@ -470,7 +451,13 @@ struct BigInt < Int # :ditto: def lcm(other : Int) : BigInt - BigInt.new { |mpz| LibGMP.lcm_ui(mpz, self, other.abs.to_u64) } + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: BigInt.new { |mpz| LibGMP.lcm_ui(mpz, self, {{ ui }}) }, + neg_ui: BigInt.new { |mpz| LibGMP.lcm_ui(mpz, self, {{ neg_ui }}) }, + big_i: lcm({{ big_i }}), + } + end end def bit_length : Int32 @@ -819,15 +806,12 @@ struct Int end def -(other : BigInt) : BigInt - if self < 0 - -(abs + other) - else - # The line below segfault on linux 32 bits for a (yet) unknown reason: - # - # BigInt.new { |mpz| LibGMP.ui_sub(mpz, self.to_u64, other) } - # - # So for now we do it a bit slower. - to_big_i - other + Int.primitive_ui_check(self) do |ui, neg_ui, big_i| + { + ui: BigInt.new { |mpz| LibGMP.neg(mpz, other); LibGMP.add_ui(mpz, mpz, {{ ui }}) }, + neg_ui: BigInt.new { |mpz| LibGMP.neg(mpz, other); LibGMP.sub_ui(mpz, mpz, {{ neg_ui }}) }, + big_i: {{ big_i }} - other, + } end end @@ -982,7 +966,7 @@ struct Crystal::Hasher def int(value : BigInt) # it should calculate `remainder(HASH_MODULUS)` - if LibGMP::ULong == UInt64 + if LibGMP::UI == UInt64 v = LibGMP.tdiv_ui(value, HASH_MODULUS).to_i64 value < 0 ? -v : v elsif value >= HASH_MODULUS_INT_P || value <= HASH_MODULUS_INT_N diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index 4b81db43f46b..be510e6033bb 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -102,15 +102,13 @@ struct BigRational < Number self <=> other.to_big_r end - def <=>(other : Int::Primitive) - if LibGMP::SI::MIN <= other <= LibGMP::UI::MAX - if other <= LibGMP::SI::MAX - LibGMP.mpq_cmp_si(self, LibGMP::SI.new!(other), 1) - else - LibGMP.mpq_cmp_ui(self, LibGMP::UI.new!(other), 1) - end - else - self <=> other.to_big_i + def <=>(other : Int) + Int.primitive_si_ui_check(other) do |si, ui, big_i| + { + si: LibGMP.mpq_cmp_si(self, {{ si }}, 1), + ui: LibGMP.mpq_cmp_ui(self, {{ ui }}, 1), + big_i: self <=> {{ big_i }}, + } end end diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index 52eecdf945fb..9caf408f1494 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -23,10 +23,6 @@ lib LibGMP alias Double = LibC::Double alias BitcntT = UI - alias IntPrimitiveSigned = Int8 | Int16 | Int32 | LibC::Long - alias IntPrimitiveUnsigned = UInt8 | UInt16 | UInt32 | LibC::ULong - alias IntPrimitive = IntPrimitiveSigned | IntPrimitiveUnsigned - {% if flag?(:win32) && flag?(:bits64) %} alias MpExp = LibC::Long alias MpSize = LibC::LongLong diff --git a/src/big/number.cr b/src/big/number.cr index 8cdacf5413e5..1251e8113db3 100644 --- a/src/big/number.cr +++ b/src/big/number.cr @@ -8,17 +8,18 @@ struct BigFloat self.class.new(self / other) end - def /(other : UInt8 | UInt16 | UInt32 | UInt64) : BigFloat + def /(other : Int::Primitive) : BigFloat # Division by 0 in BigFloat is not allowed, there is no BigFloat::Infinity raise DivisionByZeroError.new if other == 0 - if other.is_a?(UInt8 | UInt16 | UInt32) || (LibGMP::ULong == UInt64 && other.is_a?(UInt64)) - BigFloat.new { |mpf| LibGMP.mpf_div_ui(mpf, self, other) } - else - BigFloat.new { |mpf| LibGMP.mpf_div(mpf, self, other.to_big_f) } + Int.primitive_ui_check(other) do |ui, neg_ui, _| + { + ui: BigFloat.new { |mpf| LibGMP.mpf_div_ui(mpf, self, {{ ui }}) }, + neg_ui: BigFloat.new { |mpf| LibGMP.mpf_div_ui(mpf, self, {{ neg_ui }}); LibGMP.mpf_neg(mpf, mpf) }, + big_i: BigFloat.new { |mpf| LibGMP.mpf_div(mpf, self, BigFloat.new(other)) }, + } end end - Number.expand_div [Int8, Int16, Int32, Int64, Int128, UInt128], BigFloat Number.expand_div [Float32, Float64], BigFloat end @@ -32,6 +33,62 @@ struct BigRational Number.expand_div [Float32, Float64], BigRational end +struct Int + # :nodoc: + # Yields 3 expressions: `Call`s nodes that convert *var* into a `LibGMP::SI`, + # a `LibGMP::UI`, and a `BigInt` respectively. These expressions are not + # evaluated unless they are interpolated in *block*. + # + # *block* should return a named tuple: the value for `:si` is returned by the + # macro if *var* fits into a `LibGMP::SI`, the value for `:ui` returned if + # *var* fits into a `LibGMP::UI`, and the value for `:big_i` otherwise. + macro primitive_si_ui_check(var, &block) + {% + exps = yield( + "::LibGMP::SI.new!(#{var.id})".id, + "::LibGMP::UI.new!(#{var.id})".id, + "::BigInt.new(#{var.id})".id, + ) + %} + if ::LibGMP::SI::MIN <= {{ var }} <= ::LibGMP::UI::MAX + if {{ var }} <= ::LibGMP::SI::MAX + {{ exps[:si] }} + else + {{ exps[:ui] }} + end + else + {{ exps[:big_i] }} + end + end + + # :nodoc: + # Yields 3 expressions: `Call`s nodes that convert *var* into a `LibGMP::UI`, + # the negative of *var* into a `LibGMP::UI`, and *var* into a `BigInt`, + # respectively. These expressions are not evaluated unless they are + # interpolated in *block*. + # + # *block* should return a named tuple: the value for `:ui` is returned by the + # macro if *var* fits into a `LibGMP::UI`, the value for `:neg_ui` returned if + # the negative of *var* fits into a `LibGMP::UI`, and the value for `:big_i` + # otherwise. + macro primitive_ui_check(var, &block) + {% + exps = yield( + "::LibGMP::UI.new!(#{var.id})".id, + "::LibGMP::UI.new!((#{var.id}).abs_unsigned)".id, + "::BigInt.new(#{var.id})".id, + ) + %} + if ::LibGMP::UI::MIN <= {{ var }} <= ::LibGMP::UI::MAX + {{ exps[:ui] }} + elsif {{ var }}.responds_to?(:abs_unsigned) && {{ var }}.abs_unsigned <= ::LibGMP::UI::MAX + {{ exps[:neg_ui] }} + else + {{ exps[:big_i] }} + end + end +end + struct Int8 Number.expand_div [BigInt], BigFloat Number.expand_div [BigFloat], BigFloat From c25640d79bcbf16b015cf9439a862d4626973cf2 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Wed, 8 Nov 2023 15:16:04 -0500 Subject: [PATCH 0769/1551] Expose inherited macros in generated API docs (#13810) --- .../crystal/tools/doc/html/_macros_inherited.html | 11 +++++++++++ src/compiler/crystal/tools/doc/html/type.html | 1 + src/compiler/crystal/tools/doc/templates.cr | 4 ++++ 3 files changed, 16 insertions(+) create mode 100644 src/compiler/crystal/tools/doc/html/_macros_inherited.html diff --git a/src/compiler/crystal/tools/doc/html/_macros_inherited.html b/src/compiler/crystal/tools/doc/html/_macros_inherited.html new file mode 100644 index 000000000000..3ef7f287ca43 --- /dev/null +++ b/src/compiler/crystal/tools/doc/html/_macros_inherited.html @@ -0,0 +1,11 @@ +<% macro_groups = macros.group_by { |m| m.name } %> +<% unless macro_groups.empty? %> +

    Macros inherited from <%= ancestor.kind %> <%= ancestor.link_from(type) %>

    + <% i = 0 %> + <% macro_groups.each do |k, v| %> + + <%= v.map { |m| m.name + m.args_to_html(:highlight) } .join("
    ") %>
    + <%= k %>
    <%= ", " if i != macro_groups.size - 1 %> + <% i += 1 %> + <% end %> +<% end %> diff --git a/src/compiler/crystal/tools/doc/html/type.html b/src/compiler/crystal/tools/doc/html/type.html index aaa3d338df9e..cd8115e74f5c 100644 --- a/src/compiler/crystal/tools/doc/html/type.html +++ b/src/compiler/crystal/tools/doc/html/type.html @@ -103,6 +103,7 @@

    <%= MethodsInheritedTemplate.new(type, ancestor, ancestor.instance_methods, "Instance") %> <%= MethodsInheritedTemplate.new(type, ancestor, ancestor.constructors, "Constructor") %> <%= MethodsInheritedTemplate.new(type, ancestor, ancestor.class_methods, "Class") %> + <%= MacrosInheritedTemplate.new(type, ancestor, ancestor.macros) %> <% end %>

    diff --git a/src/compiler/crystal/tools/doc/templates.cr b/src/compiler/crystal/tools/doc/templates.cr index 6463ea637479..91ad32e1d0d1 100644 --- a/src/compiler/crystal/tools/doc/templates.cr +++ b/src/compiler/crystal/tools/doc/templates.cr @@ -49,6 +49,10 @@ module Crystal::Doc ECR.def_to_s "#{__DIR__}/html/_methods_inherited.html" end + record MacrosInheritedTemplate, type : Type, ancestor : Type, macros : Array(Macro) do + ECR.def_to_s "#{__DIR__}/html/_macros_inherited.html" + end + record OtherTypesTemplate, title : String, type : Type, other_types : Array(Type) do ECR.def_to_s "#{__DIR__}/html/_other_types.html" end From be2bff22f226b821dace68dddb3d9671502f6851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 10 Nov 2023 11:23:50 +0100 Subject: [PATCH 0770/1551] Add CSV output format to `crystal tool unreachable` (#13926) --- src/compiler/crystal/tools/unreachable.cr | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr index e03d64f1becc..f89f90e4c37d 100644 --- a/src/compiler/crystal/tools/unreachable.cr +++ b/src/compiler/crystal/tools/unreachable.cr @@ -1,11 +1,12 @@ require "../syntax/ast" require "../compiler" require "json" +require "csv" module Crystal class Command private def unreachable - config, result = compile_no_codegen "tool unreachable", path_filter: true, unreachable_command: true + config, result = compile_no_codegen "tool unreachable", path_filter: true, unreachable_command: true, allowed_formats: %w[text json csv] unreachable = UnreachableVisitor.new @@ -39,6 +40,8 @@ module Crystal case format when "json" to_json(STDOUT) + when "csv" + to_csv(STDOUT) else to_text(STDOUT) end @@ -83,6 +86,22 @@ module Crystal end end end + + def to_csv(io) + CSV.build(io) do |builder| + builder.row %w[name file line column length annotations] + each do |a_def, location| + builder.row do |row| + row << a_def.short_reference + row << location.filename + row << location.line_number + row << location.column_number + row << a_def.length + row << a_def.all_annotations.try(&.join(" ")) + end + end + end + end end # This visitor walks the entire reachable code tree and collect locations From 21816db9df4c2c4749574abbcb482e3724126b6a Mon Sep 17 00:00:00 2001 From: Postmodern Date: Fri, 10 Nov 2023 02:38:00 -0800 Subject: [PATCH 0771/1551] Add `EOL`constant (End-Of-Line) (#11303) --- src/kernel.cr | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/kernel.cr b/src/kernel.cr index 7bca29cef605..c3b3106ccae3 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -95,6 +95,13 @@ ARGV = Array.new(ARGC_UNSAFE - 1) { |i| String.new(ARGV_UNSAFE[1 + i]) } # ``` ARGF = IO::ARGF.new(ARGV, STDIN) +# The newline constant +EOL = {% if flag?(:windows) %} + "\r\n" + {% else %} + "\n" + {% end %} + # Repeatedly executes the block. # # ``` From eed093c3172b35eff92df7bfb40ac1208086b769 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 11 Nov 2023 02:08:20 +0800 Subject: [PATCH 0772/1551] Add `Int::Primitive#to_signed`, `#to_signed!`, `#to_unsigned`, `#to_unsigned!` (#13960) --- spec/std/int_spec.cr | 96 ++++++++ src/int.cr | 560 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 656 insertions(+) diff --git a/spec/std/int_spec.cr b/spec/std/int_spec.cr index 72a83bd64061..88ef12553c38 100644 --- a/spec/std/int_spec.cr +++ b/spec/std/int_spec.cr @@ -146,6 +146,102 @@ describe "Int" do end end + describe "#to_signed" do + {% for n in [8, 16, 32, 64, 128] %} + it "does for Int{{n}}" do + x = Int{{n}}.new(123).to_signed + x.should be_a(Int{{n}}) + x.should eq(123) + + Int{{n}}.new(-123).to_signed.should eq(-123) + Int{{n}}::MIN.to_signed.should eq(Int{{n}}::MIN) + Int{{n}}::MAX.to_signed.should eq(Int{{n}}::MAX) + end + + it "does for UInt{{n}}" do + x = UInt{{n}}.new(123).to_signed + x.should be_a(Int{{n}}) + x.should eq(123) + + UInt{{n}}::MIN.to_signed.should eq(0) + expect_raises(OverflowError) { UInt{{n}}::MAX.to_signed } + expect_raises(OverflowError) { (UInt{{n}}.new(Int{{n}}::MAX) + 1).to_signed } + end + {% end %} + end + + describe "#to_signed!" do + {% for n in [8, 16, 32, 64, 128] %} + it "does for Int{{n}}" do + x = Int{{n}}.new(123).to_signed! + x.should be_a(Int{{n}}) + x.should eq(123) + + Int{{n}}.new(-123).to_signed!.should eq(-123) + Int{{n}}::MIN.to_signed!.should eq(Int{{n}}::MIN) + Int{{n}}::MAX.to_signed!.should eq(Int{{n}}::MAX) + end + + it "does for UInt{{n}}" do + x = UInt{{n}}.new(123).to_signed! + x.should be_a(Int{{n}}) + x.should eq(123) + + UInt{{n}}::MIN.to_signed!.should eq(0) + UInt{{n}}::MAX.to_signed!.should eq(-1) + (UInt{{n}}::MAX - 122).to_signed!.should eq(-123) + (UInt{{n}}.new(Int{{n}}::MAX) + 1).to_signed!.should eq(Int{{n}}::MIN) + end + {% end %} + end + + describe "#to_unsigned" do + {% for n in [8, 16, 32, 64, 128] %} + it "does for Int{{n}}" do + x = Int{{n}}.new(123).to_unsigned + x.should be_a(UInt{{n}}) + x.should eq(123) + + Int{{n}}.zero.to_unsigned.should eq(UInt{{n}}::MIN) + Int{{n}}::MAX.to_unsigned.should eq(UInt{{n}}.new(Int{{n}}::MAX)) + expect_raises(OverflowError) { Int{{n}}::MIN.to_unsigned } + end + + it "does for UInt{{n}}" do + x = UInt{{n}}.new(123).to_unsigned + x.should be_a(UInt{{n}}) + x.should eq(123) + + UInt{{n}}::MIN.to_unsigned.should eq(UInt{{n}}::MIN) + UInt{{n}}::MAX.to_unsigned.should eq(UInt{{n}}::MAX) + end + {% end %} + end + + describe "#to_unsigned!" do + {% for n in [8, 16, 32, 64, 128] %} + it "does for Int{{n}}" do + x = Int{{n}}.new(123).to_unsigned! + x.should be_a(UInt{{n}}) + x.should eq(123) + + Int{{n}}.new(-123).to_unsigned!.should eq(UInt{{n}}::MAX - 122) + Int{{n}}::MIN.to_unsigned!.should eq(UInt{{n}}::MAX // 2 + 1) + Int{{n}}::MAX.to_unsigned!.should eq(UInt{{n}}::MAX // 2) + Int{{n}}.new(-1).to_unsigned!.should eq(UInt{{n}}::MAX) + end + + it "does for UInt{{n}}" do + x = UInt{{n}}.new(123).to_unsigned! + x.should be_a(UInt{{n}}) + x.should eq(123) + + UInt{{n}}::MIN.to_unsigned!.should eq(UInt{{n}}::MIN) + UInt{{n}}::MAX.to_unsigned!.should eq(UInt{{n}}::MAX) + end + {% end %} + end + describe "#abs_unsigned" do {% for int in Int::Signed.union_types %} it "does for {{ int }}" do diff --git a/src/int.cr b/src/int.cr index 2abfa640763f..6f0d3aa5881a 100644 --- a/src/int.cr +++ b/src/int.cr @@ -897,6 +897,62 @@ struct Int8 0_i8 - self end + # Returns `self` converted to a signed value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_u32.to_signed # => 1_i32 + # 2_u16.to_signed # => 2_i16 + # 3_i64.to_signed # => 3_i64 + # ``` + def to_signed : Int8 + self + end + + # Returns `self` converted to a signed value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # + # ``` + # 1_u32.to_signed! # => 1_i32 + # 65530_u16.to_signed! # => -6_i16 + # 3_i64.to_signed! # => 3_i64 + # ``` + def to_signed! : Int8 + self + end + + # Returns `self` converted to an unsigned value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_i32.to_unsigned # => 1_u32 + # 2_i16.to_unsigned # => 2_u16 + # 3_u64.to_unsigned # => 3_u64 + # ``` + def to_unsigned : UInt8 + to_u8 + end + + # Returns `self` converted to an unsigned value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # + # ``` + # 1_i32.to_unsigned! # => 1_u32 + # (-6_i16).to_unsigned! # => 65530_u16 + # 3_u64.to_unsigned! # => 3_u64 + # ``` + def to_unsigned! : UInt8 + to_u8! + end + # Returns the absolute value of `self` as an unsigned value of the same size. # # Returns `self` if `self` is already an `Int::Unsigned`. This method never @@ -1027,6 +1083,62 @@ struct Int16 0_i16 - self end + # Returns `self` converted to a signed value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_u32.to_signed # => 1_i32 + # 2_u16.to_signed # => 2_i16 + # 3_i64.to_signed # => 3_i64 + # ``` + def to_signed : Int16 + self + end + + # Returns `self` converted to a signed value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # + # ``` + # 1_u32.to_signed! # => 1_i32 + # 65530_u16.to_signed! # => -6_i16 + # 3_i64.to_signed! # => 3_i64 + # ``` + def to_signed! : Int16 + self + end + + # Returns `self` converted to an unsigned value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_i32.to_unsigned # => 1_u32 + # 2_i16.to_unsigned # => 2_u16 + # 3_u64.to_unsigned # => 3_u64 + # ``` + def to_unsigned : UInt16 + to_u16 + end + + # Returns `self` converted to an unsigned value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # + # ``` + # 1_i32.to_unsigned! # => 1_u32 + # (-6_i16).to_unsigned! # => 65530_u16 + # 3_u64.to_unsigned! # => 3_u64 + # ``` + def to_unsigned! : UInt16 + to_u16! + end + # Returns the absolute value of `self` as an unsigned value of the same size. # # Returns `self` if `self` is already an `Int::Unsigned`. This method never @@ -1157,6 +1269,62 @@ struct Int32 0 - self end + # Returns `self` converted to a signed value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_u32.to_signed # => 1_i32 + # 2_u16.to_signed # => 2_i16 + # 3_i64.to_signed # => 3_i64 + # ``` + def to_signed : Int32 + self + end + + # Returns `self` converted to a signed value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # + # ``` + # 1_u32.to_signed! # => 1_i32 + # 65530_u16.to_signed! # => -6_i16 + # 3_i64.to_signed! # => 3_i64 + # ``` + def to_signed! : Int32 + self + end + + # Returns `self` converted to an unsigned value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_i32.to_unsigned # => 1_u32 + # 2_i16.to_unsigned # => 2_u16 + # 3_u64.to_unsigned # => 3_u64 + # ``` + def to_unsigned : UInt32 + to_u32 + end + + # Returns `self` converted to an unsigned value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # + # ``` + # 1_i32.to_unsigned! # => 1_u32 + # (-6_i16).to_unsigned! # => 65530_u16 + # 3_u64.to_unsigned! # => 3_u64 + # ``` + def to_unsigned! : UInt32 + to_u32! + end + # Returns the absolute value of `self` as an unsigned value of the same size. # # Returns `self` if `self` is already an `Int::Unsigned`. This method never @@ -1287,6 +1455,62 @@ struct Int64 0_i64 - self end + # Returns `self` converted to a signed value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_u32.to_signed # => 1_i32 + # 2_u16.to_signed # => 2_i16 + # 3_i64.to_signed # => 3_i64 + # ``` + def to_signed : Int64 + self + end + + # Returns `self` converted to a signed value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # + # ``` + # 1_u32.to_signed! # => 1_i32 + # 65530_u16.to_signed! # => -6_i16 + # 3_i64.to_signed! # => 3_i64 + # ``` + def to_signed! : Int64 + self + end + + # Returns `self` converted to an unsigned value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_i32.to_unsigned # => 1_u32 + # 2_i16.to_unsigned # => 2_u16 + # 3_u64.to_unsigned # => 3_u64 + # ``` + def to_unsigned : UInt64 + to_u64 + end + + # Returns `self` converted to an unsigned value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # + # ``` + # 1_i32.to_unsigned! # => 1_u32 + # (-6_i16).to_unsigned! # => 65530_u16 + # 3_u64.to_unsigned! # => 3_u64 + # ``` + def to_unsigned! : UInt64 + to_u64! + end + # Returns the absolute value of `self` as an unsigned value of the same size. # # Returns `self` if `self` is already an `Int::Unsigned`. This method never @@ -1420,6 +1644,62 @@ struct Int128 Int128.new(0) - self end + # Returns `self` converted to a signed value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_u32.to_signed # => 1_i32 + # 2_u16.to_signed # => 2_i16 + # 3_i64.to_signed # => 3_i64 + # ``` + def to_signed : Int128 + self + end + + # Returns `self` converted to a signed value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # + # ``` + # 1_u32.to_signed! # => 1_i32 + # 65530_u16.to_signed! # => -6_i16 + # 3_i64.to_signed! # => 3_i64 + # ``` + def to_signed! : Int128 + self + end + + # Returns `self` converted to an unsigned value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_i32.to_unsigned # => 1_u32 + # 2_i16.to_unsigned # => 2_u16 + # 3_u64.to_unsigned # => 3_u64 + # ``` + def to_unsigned : UInt128 + to_u128 + end + + # Returns `self` converted to an unsigned value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # + # ``` + # 1_i32.to_unsigned! # => 1_u32 + # (-6_i16).to_unsigned! # => 65530_u16 + # 3_u64.to_unsigned! # => 3_u64 + # ``` + def to_unsigned! : UInt128 + to_u128! + end + # Returns the absolute value of `self` as an unsigned value of the same size. # # Returns `self` if `self` is already an `Int::Unsigned`. This method never @@ -1550,6 +1830,62 @@ struct UInt8 0_u8 &- self end + # Returns `self` converted to a signed value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_u32.to_signed # => 1_i32 + # 2_u16.to_signed # => 2_i16 + # 3_i64.to_signed # => 3_i64 + # ``` + def to_signed : Int8 + to_i8 + end + + # Returns `self` converted to a signed value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # + # ``` + # 1_u32.to_signed! # => 1_i32 + # 65530_u16.to_signed! # => -6_i16 + # 3_i64.to_signed! # => 3_i64 + # ``` + def to_signed! : Int8 + to_i8! + end + + # Returns `self` converted to an unsigned value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_i32.to_unsigned # => 1_u32 + # 2_i16.to_unsigned # => 2_u16 + # 3_u64.to_unsigned # => 3_u64 + # ``` + def to_unsigned : UInt8 + self + end + + # Returns `self` converted to an unsigned value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # + # ``` + # 1_i32.to_unsigned! # => 1_u32 + # (-6_i16).to_unsigned! # => 65530_u16 + # 3_u64.to_unsigned! # => 3_u64 + # ``` + def to_unsigned! : UInt8 + self + end + def abs : self self end @@ -1684,6 +2020,62 @@ struct UInt16 0_u16 &- self end + # Returns `self` converted to a signed value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_u32.to_signed # => 1_i32 + # 2_u16.to_signed # => 2_i16 + # 3_i64.to_signed # => 3_i64 + # ``` + def to_signed : Int16 + to_i16 + end + + # Returns `self` converted to a signed value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # + # ``` + # 1_u32.to_signed! # => 1_i32 + # 65530_u16.to_signed! # => -6_i16 + # 3_i64.to_signed! # => 3_i64 + # ``` + def to_signed! : Int16 + to_i16! + end + + # Returns `self` converted to an unsigned value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_i32.to_unsigned # => 1_u32 + # 2_i16.to_unsigned # => 2_u16 + # 3_u64.to_unsigned # => 3_u64 + # ``` + def to_unsigned : UInt16 + self + end + + # Returns `self` converted to an unsigned value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # + # ``` + # 1_i32.to_unsigned! # => 1_u32 + # (-6_i16).to_unsigned! # => 65530_u16 + # 3_u64.to_unsigned! # => 3_u64 + # ``` + def to_unsigned! : UInt16 + self + end + def abs : self self end @@ -1818,6 +2210,62 @@ struct UInt32 0_u32 &- self end + # Returns `self` converted to a signed value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_u32.to_signed # => 1_i32 + # 2_u16.to_signed # => 2_i16 + # 3_i64.to_signed # => 3_i64 + # ``` + def to_signed : Int32 + to_i32 + end + + # Returns `self` converted to a signed value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # + # ``` + # 1_u32.to_signed! # => 1_i32 + # 65530_u16.to_signed! # => -6_i16 + # 3_i64.to_signed! # => 3_i64 + # ``` + def to_signed! : Int32 + to_i32! + end + + # Returns `self` converted to an unsigned value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_i32.to_unsigned # => 1_u32 + # 2_i16.to_unsigned # => 2_u16 + # 3_u64.to_unsigned # => 3_u64 + # ``` + def to_unsigned : UInt32 + self + end + + # Returns `self` converted to an unsigned value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # + # ``` + # 1_i32.to_unsigned! # => 1_u32 + # (-6_i16).to_unsigned! # => 65530_u16 + # 3_u64.to_unsigned! # => 3_u64 + # ``` + def to_unsigned! : UInt32 + self + end + def abs : self self end @@ -1952,6 +2400,62 @@ struct UInt64 0_u64 &- self end + # Returns `self` converted to a signed value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_u32.to_signed # => 1_i32 + # 2_u16.to_signed # => 2_i16 + # 3_i64.to_signed # => 3_i64 + # ``` + def to_signed : Int64 + to_i64 + end + + # Returns `self` converted to a signed value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # + # ``` + # 1_u32.to_signed! # => 1_i32 + # 65530_u16.to_signed! # => -6_i16 + # 3_i64.to_signed! # => 3_i64 + # ``` + def to_signed! : Int64 + to_i64! + end + + # Returns `self` converted to an unsigned value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_i32.to_unsigned # => 1_u32 + # 2_i16.to_unsigned # => 2_u16 + # 3_u64.to_unsigned # => 3_u64 + # ``` + def to_unsigned : UInt64 + self + end + + # Returns `self` converted to an unsigned value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # + # ``` + # 1_i32.to_unsigned! # => 1_u32 + # (-6_i16).to_unsigned! # => 65530_u16 + # 3_u64.to_unsigned! # => 3_u64 + # ``` + def to_unsigned! : UInt64 + self + end + def abs : self self end @@ -2088,6 +2592,62 @@ struct UInt128 UInt128.new(0) &- self end + # Returns `self` converted to a signed value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_u32.to_signed # => 1_i32 + # 2_u16.to_signed # => 2_i16 + # 3_i64.to_signed # => 3_i64 + # ``` + def to_signed : Int128 + to_i128 + end + + # Returns `self` converted to a signed value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Signed`. + # + # ``` + # 1_u32.to_signed! # => 1_i32 + # 65530_u16.to_signed! # => -6_i16 + # 3_i64.to_signed! # => 3_i64 + # ``` + def to_signed! : Int128 + to_i128! + end + + # Returns `self` converted to an unsigned value of the same size. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # Raises `OverflowError` in case of overflow. + # + # ``` + # 1_i32.to_unsigned # => 1_u32 + # 2_i16.to_unsigned # => 2_u16 + # 3_u64.to_unsigned # => 3_u64 + # ``` + def to_unsigned : UInt128 + self + end + + # Returns `self` converted to an unsigned value of the same size, wrapping in + # case of overflow. + # + # Simply returns `self` unmodified if `self` is already an `Int::Unsigned`. + # + # ``` + # 1_i32.to_unsigned! # => 1_u32 + # (-6_i16).to_unsigned! # => 65530_u16 + # 3_u64.to_unsigned! # => 3_u64 + # ``` + def to_unsigned! : UInt128 + self + end + def abs self end From 1f592eca67c4c2b40efc2bc5147a45917a0a9e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 10 Nov 2023 19:08:36 +0100 Subject: [PATCH 0773/1551] Improve docs on initial/default values passed to `Array.new` and `Hash.new` (#13962) Co-authored-by: Quinton Miller --- src/array.cr | 9 +++++++++ src/hash.cr | 29 ++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/array.cr b/src/array.cr index df31a2699251..5ce9f2e6148b 100644 --- a/src/array.cr +++ b/src/array.cr @@ -127,12 +127,21 @@ class Array(T) # # ``` # Array.new(3, 'a') # => ['a', 'a', 'a'] + # ``` + # + # WARNING: The initial value is filled into the array as-is. It gets neither + # duplicated nor cloned. For types with reference semantics this means every + # item will point to the *same* object. # + # ``` # ary = Array.new(3, [1]) # ary # => [[1], [1], [1]] # ary[0][0] = 2 # ary # => [[2], [2], [2]] # ``` + # + # * `.new(Int, & : Int32 -> T)` is an alternative that allows using a + # different initial value for each position. def initialize(size : Int, value : T) if size < 0 raise ArgumentError.new("Negative array size: #{size}") diff --git a/src/hash.cr b/src/hash.cr index 156f2d7d6313..8d8ccea22a0b 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -266,6 +266,16 @@ class Hash(K, V) # Creates a new empty `Hash` with a *block* that handles missing keys. # # ``` + # inventory = Hash(String, Int32).new(0) + # inventory["socks"] = 3 + # inventory["pickles"] # => 0 + # ``` + # + # WARNING: When the default block is invoked on a missing key, its return + # value is *not* implicitly stored into the hash under that key. If you want + # that behaviour, you need to put it explicitly: + # + # ``` # hash = Hash(String, Int32).new do |hash, key| # hash[key] = key.size # end @@ -294,14 +304,23 @@ class Hash(K, V) # inventory["pickles"] # => 0 # ``` # - # NOTE: The default value is passed by reference: + # WARNING: When the default value gets returned on a missing key, it is *not* + # stored into the hash under that key. If you want that behaviour, please use + # the overload with a block. + # + # WARNING: The default value is returned as-is. It gets neither duplicated nor + # cloned. For types with reference semantics this means it will be exactly the + # *same* object every time. + # # ``` - # arr = [1, 2, 3] - # hash = Hash(String, Array(Int32)).new(arr) - # hash["3"][1] = 4 - # arr # => [1, 4, 3] + # hash = Hash(String, Array(Int32)).new([1]) + # hash["a"][0] = 2 + # hash["b"] # => [2] # ``` # + # * `.new(&block : (Hash(K, V), K -> V))` is an alternative with a block that + # can return a different default value for each invocation. + # # The *initial_capacity* is useful to avoid unnecessary reallocations # of the internal buffer in case of growth. If the number of elements # a hash will hold is known, the hash should be initialized with that From 4001a79a8010b6280bb71ac9fd6353b85dd970c4 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 11 Nov 2023 07:22:29 +0800 Subject: [PATCH 0774/1551] Remove `LLVMExtCreateMCJITCompilerForModule` from `llvm_ext.cc` (#13966) --- src/compiler/crystal/codegen/target.cr | 2 +- src/llvm/ext/llvm_ext.cc | 77 +------------------------- src/llvm/jit_compiler.cr | 10 +++- src/llvm/lib_llvm/execution_engine.cr | 1 + src/llvm/lib_llvm_ext.cr | 1 - 5 files changed, 13 insertions(+), 78 deletions(-) diff --git a/src/compiler/crystal/codegen/target.cr b/src/compiler/crystal/codegen/target.cr index 1e731d801b58..8817221c18b9 100644 --- a/src/compiler/crystal/codegen/target.cr +++ b/src/compiler/crystal/codegen/target.cr @@ -195,7 +195,7 @@ class Crystal::Codegen::Target target = LLVM::Target.from_triple(self.to_s) machine = target.create_target_machine(self.to_s, cpu: cpu, features: features, opt_level: opt_level, code_model: code_model).not_nil! - # We need to disable global isel until https://reviews.llvm.org/D80898 is released, + # FIXME: We need to disable global isel until https://reviews.llvm.org/D80898 is released, # or we fixed generating values for 0 sized types. # When removing this, also remove it from the ABI specs and jit compiler. # See https://github.com/crystal-lang/crystal/issues/9297#issuecomment-636512270 diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc index 513a156a838a..f821abf30703 100644 --- a/src/llvm/ext/llvm_ext.cc +++ b/src/llvm/ext/llvm_ext.cc @@ -1,8 +1,8 @@ #include #include #include -#include -#include +#include +#include using namespace llvm; @@ -15,8 +15,6 @@ using namespace llvm; #define LLVM_VERSION_LE(major, minor) \ (LLVM_VERSION_MAJOR < (major) || LLVM_VERSION_MAJOR == (major) && LLVM_VERSION_MINOR <= (minor)) -#include - #if LLVM_VERSION_GE(16, 0) #define makeArrayRef ArrayRef #endif @@ -89,75 +87,4 @@ void LLVMExtTargetMachineEnableGlobalIsel(LLVMTargetMachineRef T, LLVMBool Enabl unwrap(T)->setGlobalISel(Enable); } -// Copy paste of https://github.com/llvm/llvm-project/blob/dace8224f38a31636a02fe9c2af742222831f70c/llvm/lib/ExecutionEngine/ExecutionEngineBindings.cpp#L160-L214 -// but with a parameter to set global isel state -LLVMBool LLVMExtCreateMCJITCompilerForModule( - LLVMExecutionEngineRef *OutJIT, LLVMModuleRef M, - LLVMMCJITCompilerOptions *PassedOptions, size_t SizeOfPassedOptions, - LLVMBool EnableGlobalISel, - char **OutError) { - LLVMMCJITCompilerOptions options; - // If the user passed a larger sized options struct, then they were compiled - // against a newer LLVM. Tell them that something is wrong. - if (SizeOfPassedOptions > sizeof(options)) { - *OutError = strdup( - "Refusing to use options struct that is larger than my own; assuming " - "LLVM library mismatch."); - return 1; - } - - - // Defend against the user having an old version of the API by ensuring that - // any fields they didn't see are cleared. We must defend against fields being - // set to the bitwise equivalent of zero, and assume that this means "do the - // default" as if that option hadn't been available. - LLVMInitializeMCJITCompilerOptions(&options, sizeof(options)); - memcpy(&options, PassedOptions, SizeOfPassedOptions); - - - TargetOptions targetOptions; - targetOptions.EnableFastISel = options.EnableFastISel; - targetOptions.EnableGlobalISel = EnableGlobalISel; - std::unique_ptr Mod(unwrap(M)); - - if (Mod) - // Set function attribute "frame-pointer" based on - // NoFramePointerElim. - for (auto &F : *Mod) { - auto Attrs = F.getAttributes(); - StringRef Value = options.NoFramePointerElim ? "all" : "none"; - #if LLVM_VERSION_GE(14, 0) - Attrs = Attrs.addFnAttribute(F.getContext(), "frame-pointer", Value); - #else - Attrs = Attrs.addAttribute(F.getContext(), AttributeList::FunctionIndex, - "frame-pointer", Value); - #endif - F.setAttributes(Attrs); - } - - - std::string Error; - EngineBuilder builder(std::move(Mod)); - builder.setEngineKind(EngineKind::JIT) - .setErrorStr(&Error) - .setOptLevel((CodeGenOpt::Level)options.OptLevel) - .setTargetOptions(targetOptions); - bool JIT; - if (auto CM = unwrap(options.CodeModel, JIT)) - builder.setCodeModel(*CM); - if (options.MCJMM) - builder.setMCJITMemoryManager( - std::unique_ptr(unwrap(options.MCJMM))); - - TargetMachine* tm = builder.selectTarget(); - tm->setGlobalISel(EnableGlobalISel); - - if (ExecutionEngine *JIT = builder.create(tm)) { - *OutJIT = wrap(JIT); - return 0; - } - *OutError = strdup(Error.c_str()); - return 1; -} - } // extern "C" diff --git a/src/llvm/jit_compiler.cr b/src/llvm/jit_compiler.cr index ee0c92803102..3481212e1b93 100644 --- a/src/llvm/jit_compiler.cr +++ b/src/llvm/jit_compiler.cr @@ -5,10 +5,18 @@ class LLVM::JITCompiler mod.take_ownership { raise "Can't create two JIT compilers for the same module" } # if LibLLVM.create_jit_compiler_for_module(out @unwrap, mod, 3, out error) != 0 - if LibLLVMExt.create_mc_jit_compiler_for_module(out @unwrap, mod, nil, 0, false, out error) != 0 + if LibLLVM.create_mc_jit_compiler_for_module(out @unwrap, mod, nil, 0, out error) != 0 raise LLVM.string_and_dispose(error) end + # FIXME: We need to disable global isel until https://reviews.llvm.org/D80898 is released, + # or we fixed generating values for 0 sized types. + # When removing this, also remove it from the ABI specs and Crystal::Codegen::Target. + # See https://github.com/crystal-lang/crystal/issues/9297#issuecomment-636512270 + # for background info + target_machine = LibLLVM.get_execution_engine_target_machine(@unwrap) + LibLLVMExt.target_machine_enable_global_isel(target_machine, false) + @finalized = false end diff --git a/src/llvm/lib_llvm/execution_engine.cr b/src/llvm/lib_llvm/execution_engine.cr index 1c58b5fcd046..f9de5c10ea39 100644 --- a/src/llvm/lib_llvm/execution_engine.cr +++ b/src/llvm/lib_llvm/execution_engine.cr @@ -28,5 +28,6 @@ lib LibLLVM fun create_mc_jit_compiler_for_module = LLVMCreateMCJITCompilerForModule(out_jit : ExecutionEngineRef*, m : ModuleRef, options : MCJITCompilerOptions*, size_of_options : SizeT, out_error : Char**) : Bool fun dispose_execution_engine = LLVMDisposeExecutionEngine(ee : ExecutionEngineRef) fun run_function = LLVMRunFunction(ee : ExecutionEngineRef, f : ValueRef, num_args : UInt, args : GenericValueRef*) : GenericValueRef + fun get_execution_engine_target_machine = LLVMGetExecutionEngineTargetMachine(ee : ExecutionEngineRef) : TargetMachineRef fun get_pointer_to_global = LLVMGetPointerToGlobal(ee : ExecutionEngineRef, global : ValueRef) : Void* end diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr index f9bd71f25bde..6520cae55dfc 100644 --- a/src/llvm/lib_llvm_ext.cr +++ b/src/llvm/lib_llvm_ext.cr @@ -33,5 +33,4 @@ lib LibLLVMExt name : LibC::Char*) : LibLLVM::ValueRef fun target_machine_enable_global_isel = LLVMExtTargetMachineEnableGlobalIsel(machine : LibLLVM::TargetMachineRef, enable : Bool) - fun create_mc_jit_compiler_for_module = LLVMExtCreateMCJITCompilerForModule(jit : LibLLVM::ExecutionEngineRef*, m : LibLLVM::ModuleRef, options : LibLLVM::MCJITCompilerOptions*, options_length : UInt32, enable_global_isel : Bool, error : UInt8**) : Int32 end From 5bbb9c34ec1e10c6bd4b053cc411c03ca334eee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 13 Nov 2023 12:51:35 +0100 Subject: [PATCH 0775/1551] Improve docs for `Iterator` step-by-step iteration (#13967) --- src/iterator.cr | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/iterator.cr b/src/iterator.cr index 2fa5eecee1c5..a46c813b36b3 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -39,6 +39,46 @@ require "./enumerable" # iter.size # => 0 # ``` # +# ### Iterating step-by-step +# +# An iterator returns its next element on the method `#next`. +# Its return type is a union of the iterator's element type and the `Stop` type: +# `T | Iterator::Stop`. +# The stop type is a sentinel value which indicates that the iterator has +# reached its end. It usually needs to be handled and filtered out in order to +# use the element value for anything useful. +# Unlike `Nil` it's not an implicitly falsey type. +# +# ``` +# iter = (1..5).each +# +# # Unfiltered elements contain `Iterator::Stop` type +# iter.next + iter.next # Error: expected argument #1 to 'Int32#+' to be Int32, not (Int32 | Iterator::Stop) +# +# # Type filtering eliminates the stop type +# a = iter.next +# b = iter.next +# unless a.is_a?(Iterator::Stop) || b.is_a?(Iterator::Stop) +# a + b # => 3 +# end +# ``` +# +# `Iterator::Stop` is only present in the return type of `#next`. All other +# methods remove it from their return types. +# +# Iterators can be used to build a loop. +# +# ``` +# iter = (1..5).each +# sum = 0 +# while !(elem = iter.next).is_a?(Iterator::Stop) +# sum += elem +# end +# sum # => 15 +# ``` +# +# ### Implementing an Iterator +# # To implement an `Iterator` you need to define a `next` method that must return the next # element in the sequence or `Iterator::Stop::INSTANCE`, which signals the end of the sequence # (you can invoke `stop` inside an iterator as a shortcut). From 2543c094301e700c0489ae708309dc0e1cfebd8b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 13 Nov 2023 19:51:52 +0800 Subject: [PATCH 0776/1551] Support `BigFloat#**` for all `Int::Primitive` arguments (#13971) --- spec/std/big/big_float_spec.cr | 3 +++ src/big/big_float.cr | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index 679f271c5f3b..08d7e93bfb0b 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -186,6 +186,9 @@ describe "BigFloat" do it { ("-0.05".to_big_f ** 10.to_big_i).should be_close("0.00000000000009765625".to_big_f, 1e-12) } it { ("0.1234567890".to_big_f ** 3.to_big_i).should be_close("0.001881676371789154860897069".to_big_f, 1e-12) } + it { ("10".to_big_f ** -5).should be_close("0.00001".to_big_f, 1e-12) } + it { ("0.1".to_big_f ** -5).should be_close("100000".to_big_f, 1e-12) } + it { ("10".to_big_f ** (-5).to_big_i).should be_close("0.00001".to_big_f, 1e-12) } it { ("0.1".to_big_f ** (-5).to_big_i).should be_close("100000".to_big_f, 1e-12) } it { ("0".to_big_f ** 1.to_big_i).should eq(0.to_big_f) } diff --git a/src/big/big_float.cr b/src/big/big_float.cr index 7377afda34b3..b7be6423929f 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -171,7 +171,18 @@ struct BigFloat < Float end def **(other : Int) : BigFloat - BigFloat.new { |mpf| LibGMP.mpf_pow_ui(mpf, self, other.to_u64) } + # there is no BigFloat::Infinity + if zero? && other < 0 + raise ArgumentError.new "Cannot raise 0 to a negative power" + end + + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: BigFloat.new { |mpf| LibGMP.mpf_pow_ui(mpf, self, {{ ui }}) }, + neg_ui: BigFloat.new { |mpf| LibGMP.mpf_pow_ui(mpf, self, {{ neg_ui }}); LibGMP.mpf_ui_div(mpf, 1, mpf) }, + big_i: self ** {{ big_i }}, + } + end end def abs : BigFloat From 3a839961e4ebefe1f6d0a940cf8b02da2d60a489 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 14 Nov 2023 00:44:08 +0800 Subject: [PATCH 0777/1551] Remove `LLVMExtSetCurrentDebugLocation` from `llvm_ext.cc` for LLVM 9+ (#13965) --- .../crystal/codegen/crystal_llvm_builder.cr | 12 +++--- src/compiler/crystal/codegen/debug.cr | 6 ++- src/llvm/builder.cr | 17 ++++++++ src/llvm/di_builder.cr | 10 ++++- src/llvm/ext/llvm_ext.cc | 40 ++++--------------- src/llvm/lib_llvm/core.cr | 5 ++- src/llvm/lib_llvm/debug_info.cr | 4 ++ src/llvm/lib_llvm_ext.cr | 3 +- 8 files changed, 53 insertions(+), 44 deletions(-) diff --git a/src/compiler/crystal/codegen/crystal_llvm_builder.cr b/src/compiler/crystal/codegen/crystal_llvm_builder.cr index 9bf84dc25d7f..54116a9d265e 100644 --- a/src/compiler/crystal/codegen/crystal_llvm_builder.cr +++ b/src/compiler/crystal/codegen/crystal_llvm_builder.cr @@ -74,12 +74,12 @@ module Crystal end {% for name in %w(add add_handler alloca and ashr atomicrmw bit_cast build_catch_ret call - catch_pad catch_switch cmpxchg cond current_debug_location exact_sdiv - extract_value fadd fcmp fdiv fence fmul fp2si fp2ui fpext fptrunc fsub - global_string_pointer icmp inbounds_gep int2ptr invoke landing_pad load - lshr mul not or phi ptr2int sdiv select set_current_debug_location sext - shl si2fp srem store store_volatile sub switch trunc udiv ui2fp urem va_arg - xor zext) %} + catch_pad catch_switch clear_current_debug_location cmpxchg cond + current_debug_location exact_sdiv extract_value fadd fcmp fdiv fence fmul + fp2si fp2ui fpext fptrunc fsub global_string_pointer icmp inbounds_gep int2ptr + invoke landing_pad load lshr mul not or phi ptr2int sdiv select + set_current_debug_location sext shl si2fp srem store store_volatile sub switch + trunc udiv ui2fp urem va_arg xor zext) %} def {{name.id}}(*args, **kwargs) return llvm_nil if @end diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index f866e14e12f8..9fe5ce5c6ca9 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -463,7 +463,9 @@ module Crystal scope = get_current_debug_scope(location) if scope - builder.set_current_debug_location(location.line_number || 1, location.column_number, scope) + debug_loc = di_builder.create_debug_location(location.line_number || 1, location.column_number, scope) + # NOTE: `di_builder.context` is only necessary for LLVM 8 + builder.set_current_debug_location(debug_loc, di_builder.context) else clear_current_debug_location end @@ -472,7 +474,7 @@ module Crystal def clear_current_debug_location @current_debug_location = nil - builder.set_current_debug_location(0, 0, nil) + builder.clear_current_debug_location end def emit_fun_debug_metadata(func, fun_name, location, *, debug_types = [] of LibLLVM::MetadataRef, is_optimized = false) diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index 818316186532..3018eae6e642 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -333,10 +333,27 @@ class LLVM::Builder Value.new LibLLVM.build_va_arg(self, list, type, name) end + @[Deprecated("Call `#set_current_debug_location(metadata, context)` or `#clear_current_debug_location` instead")] def set_current_debug_location(line, column, scope, inlined_at = nil) LibLLVMExt.set_current_debug_location(self, line, column, scope, inlined_at) end + def set_current_debug_location(metadata, context : LLVM::Context) + {% if LibLLVM::IS_LT_90 %} + LibLLVM.set_current_debug_location(self, LibLLVM.metadata_as_value(context, metadata)) + {% else %} + LibLLVM.set_current_debug_location2(self, metadata) + {% end %} + end + + def clear_current_debug_location + {% if LibLLVM::IS_LT_90 %} + LibLLVMExt.clear_current_debug_location(self) + {% else %} + LibLLVM.set_current_debug_location2(self, nil) + {% end %} + end + def set_metadata(value, kind, node) LibLLVM.set_metadata(value, kind, node) end diff --git a/src/llvm/di_builder.cr b/src/llvm/di_builder.cr index 1fca2301f1dd..41bb18244c3c 100644 --- a/src/llvm/di_builder.cr +++ b/src/llvm/di_builder.cr @@ -14,6 +14,10 @@ struct LLVM::DIBuilder LibLLVM.dispose_di_builder(self) end + def context + @llvm_module.context + end + def create_compile_unit(lang : DwarfSourceLanguage, file, dir, producer, optimized, flags, runtime_version) file = create_file(file, dir) {% if LibLLVM::IS_LT_110 %} @@ -151,6 +155,10 @@ struct LLVM::DIBuilder LibLLVM.di_builder_get_or_create_subrange(self, lo, count) end + def create_debug_location(line, column, scope, inlined_at = nil) + LibLLVM.di_builder_create_debug_location(context, line, column, scope, inlined_at) + end + def end LibLLVM.di_builder_finalize(self) end @@ -191,7 +199,7 @@ struct LLVM::DIBuilder end private def extract_metadata_array(metadata : LibLLVM::MetadataRef) - metadata_as_value = LibLLVM.metadata_as_value(@llvm_module.context, metadata) + metadata_as_value = LibLLVM.metadata_as_value(context, metadata) operand_count = LibLLVM.get_md_node_num_operands(metadata_as_value).to_i operands = Pointer(LibLLVM::ValueRef).malloc(operand_count) LibLLVM.get_md_node_operands(metadata_as_value, operands) diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc index f821abf30703..acc95d63bcae 100644 --- a/src/llvm/ext/llvm_ext.cc +++ b/src/llvm/ext/llvm_ext.cc @@ -1,6 +1,5 @@ -#include +#include #include -#include #include #include @@ -9,50 +8,27 @@ using namespace llvm; #define LLVM_VERSION_GE(major, minor) \ (LLVM_VERSION_MAJOR > (major) || LLVM_VERSION_MAJOR == (major) && LLVM_VERSION_MINOR >= (minor)) -#define LLVM_VERSION_EQ(major, minor) \ - (LLVM_VERSION_MAJOR == (major) && LLVM_VERSION_MINOR == (minor)) - -#define LLVM_VERSION_LE(major, minor) \ - (LLVM_VERSION_MAJOR < (major) || LLVM_VERSION_MAJOR == (major) && LLVM_VERSION_MINOR <= (minor)) +#if !LLVM_VERSION_GE(9, 0) +#include +#endif #if LLVM_VERSION_GE(16, 0) #define makeArrayRef ArrayRef #endif -typedef DIBuilder *DIBuilderRef; -#define DIArray DINodeArray -template T *unwrapDIptr(LLVMMetadataRef v) { - return (T *)(v ? unwrap(v) : NULL); -} - extern "C" { -#if LLVM_VERSION_GE(9, 0) -#else +#if !LLVM_VERSION_GE(9, 0) LLVMMetadataRef LLVMExtDIBuilderCreateEnumerator( LLVMDIBuilderRef Dref, const char *Name, int64_t Value) { DIEnumerator *e = unwrap(Dref)->createEnumerator(Name, Value); return wrap(e); } -#endif -void LLVMExtSetCurrentDebugLocation( - LLVMBuilderRef Bref, unsigned Line, unsigned Col, LLVMMetadataRef Scope, - LLVMMetadataRef InlinedAt) { -#if LLVM_VERSION_GE(12, 0) - if (!Scope) - unwrap(Bref)->SetCurrentDebugLocation(DebugLoc()); - else - unwrap(Bref)->SetCurrentDebugLocation( - DILocation::get(unwrap(Scope)->getContext(), Line, Col, - unwrapDIptr(Scope), - unwrapDIptr(InlinedAt))); -#else - unwrap(Bref)->SetCurrentDebugLocation( - DebugLoc::get(Line, Col, Scope ? unwrap(Scope) : nullptr, - InlinedAt ? unwrap(InlinedAt) : nullptr)); -#endif +void LLVMExtClearCurrentDebugLocation(LLVMBuilderRef B) { + unwrap(B)->SetCurrentDebugLocation(DebugLoc::get(0, 0, nullptr)); } +#endif OperandBundleDef *LLVMExtBuildOperandBundleDef( const char *Name, LLVMValueRef *Inputs, unsigned NumInputs) { diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr index 1d44cb005d98..a0cfb708d515 100644 --- a/src/llvm/lib_llvm/core.cr +++ b/src/llvm/lib_llvm/core.cr @@ -183,8 +183,11 @@ lib LibLLVM fun get_insert_block = LLVMGetInsertBlock(builder : BuilderRef) : BasicBlockRef fun dispose_builder = LLVMDisposeBuilder(builder : BuilderRef) - {% unless LibLLVM::IS_LT_90 %} + {% if LibLLVM::IS_LT_90 %} + fun set_current_debug_location = LLVMSetCurrentDebugLocation(builder : BuilderRef, l : ValueRef) + {% else %} fun get_current_debug_location2 = LLVMGetCurrentDebugLocation2(builder : BuilderRef) : MetadataRef + fun set_current_debug_location2 = LLVMSetCurrentDebugLocation2(builder : BuilderRef, loc : MetadataRef) {% end %} fun get_current_debug_location = LLVMGetCurrentDebugLocation(builder : BuilderRef) : ValueRef diff --git a/src/llvm/lib_llvm/debug_info.cr b/src/llvm/lib_llvm/debug_info.cr index 8aeba0d6a99a..8482a0644ae3 100644 --- a/src/llvm/lib_llvm/debug_info.cr +++ b/src/llvm/lib_llvm/debug_info.cr @@ -45,6 +45,10 @@ lib LibLLVM builder : DIBuilderRef, scope : MetadataRef, file_scope : MetadataRef, discriminator : UInt ) : MetadataRef + fun di_builder_create_debug_location = LLVMDIBuilderCreateDebugLocation( + ctx : ContextRef, line : UInt, column : UInt, scope : MetadataRef, inlined_at : MetadataRef + ) : MetadataRef + fun di_builder_get_or_create_type_array = LLVMDIBuilderGetOrCreateTypeArray(builder : DIBuilderRef, types : MetadataRef*, length : SizeT) : MetadataRef fun di_builder_create_subroutine_type = LLVMDIBuilderCreateSubroutineType( diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr index 6520cae55dfc..4efb572b6eed 100644 --- a/src/llvm/lib_llvm_ext.cr +++ b/src/llvm/lib_llvm_ext.cr @@ -13,10 +13,9 @@ lib LibLLVMExt {% if LibLLVM::IS_LT_90 %} fun di_builder_create_enumerator = LLVMExtDIBuilderCreateEnumerator(builder : LibLLVM::DIBuilderRef, name : Char*, value : Int64) : LibLLVM::MetadataRef + fun clear_current_debug_location = LLVMExtClearCurrentDebugLocation(b : LibLLVM::BuilderRef) {% end %} - fun set_current_debug_location = LLVMExtSetCurrentDebugLocation(LibLLVM::BuilderRef, Int, Int, LibLLVM::MetadataRef, LibLLVM::MetadataRef) - fun build_operand_bundle_def = LLVMExtBuildOperandBundleDef(name : LibC::Char*, input : LibLLVM::ValueRef*, num_input : LibC::UInt) : LibLLVMExt::OperandBundleDefRef From 9648f2b393661f1d6d490baf0bc0d56826225486 Mon Sep 17 00:00:00 2001 From: Alexis Reigel Date: Fri, 17 Nov 2023 12:20:47 +0100 Subject: [PATCH 0778/1551] Fix missing param count in compilation error message (#13985) --- spec/compiler/semantic/macro_spec.cr | 18 ++++++++++++++++++ src/compiler/crystal/semantic/call_error.cr | 1 - 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/macro_spec.cr b/spec/compiler/semantic/macro_spec.cr index caeba6269f34..f71e9247461a 100644 --- a/spec/compiler/semantic/macro_spec.cr +++ b/spec/compiler/semantic/macro_spec.cr @@ -271,6 +271,24 @@ describe "Semantic: macro" do CRYSTAL end + it "errors if find macros but missing argument" do + assert_error(<<-CRYSTAL, "wrong number of arguments for macro 'foo' (given 0, expected 1)") + macro foo(x) + 1 + end + + foo + CRYSTAL + + assert_error(<<-CRYSTAL, "wrong number of arguments for macro 'foo' (given 0, expected 1)") + private macro foo(x) + 1 + end + + foo + CRYSTAL + end + describe "raise" do describe "inside macro" do describe "without node" do diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index 40c6e22424bf..aee5b9e2019b 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -902,7 +902,6 @@ class Crystal::Call macros = in_macro_target &.lookup_macros(def_name) return unless macros.is_a?(Array(Macro)) - macros = macros.reject &.visibility.private? if msg = single_def_error_message(macros, named_args) raise msg From 72e6b02feba5efa3ebce3b4c768f22ca5512370a Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Fri, 17 Nov 2023 12:21:30 +0100 Subject: [PATCH 0779/1551] Refactor leap year to use `divisible_by?` (#13982) --- src/time.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/time.cr b/src/time.cr index da662a2c9ab4..fe5a21d7c77c 100644 --- a/src/time.cr +++ b/src/time.cr @@ -1041,7 +1041,7 @@ struct Time raise ArgumentError.new "Invalid year" end - year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) + year.divisible_by?(4) && (!year.divisible_by?(100) || year.divisible_by?(400)) end # Prints this `Time` to *io*. From b223139de9386198f7059326f0112513f88ba0b5 Mon Sep 17 00:00:00 2001 From: moe Date: Mon, 20 Nov 2023 12:30:01 +0100 Subject: [PATCH 0780/1551] Fix version sorting in API docs (#13994) --- scripts/docs-versions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/docs-versions.sh b/scripts/docs-versions.sh index d31a1ac43c40..dbb586e7aba2 100755 --- a/scripts/docs-versions.sh +++ b/scripts/docs-versions.sh @@ -3,7 +3,7 @@ git tag --list | \ grep -v -E '0\.1?[0-9]\.' | \ grep '^[0-9]' | \ -tac | \ +sort -rV | \ awk ' BEGIN { print "{" From c7bd41d1601224bdfc2b065d126f4aa68bc7eeb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 20 Nov 2023 12:30:19 +0100 Subject: [PATCH 0781/1551] Use `IO.copy` in `IO#gets_to_end` (#13990) --- src/io.cr | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/io.cr b/src/io.cr index 28a3652c9e4c..c159c77776b5 100644 --- a/src/io.cr +++ b/src/io.cr @@ -563,10 +563,7 @@ abstract class IO decoder.write(str) end else - buffer = uninitialized UInt8[DEFAULT_BUFFER_SIZE] - while (read_bytes = read(buffer.to_slice)) > 0 - str.write buffer.to_slice[0, read_bytes] - end + IO.copy(self, str) end end end From cb727c1c37acef710acd3c0f2f3345a7cab76e91 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 21 Nov 2023 21:47:18 +0800 Subject: [PATCH 0782/1551] Windows: Do not close process handle in `Process#close` (#13997) --- spec/std/process_spec.cr | 4 +--- src/process.cr | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 7ce5f3849a39..94b8ec18a915 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -333,9 +333,7 @@ describe Process do {% end %} end - # TODO: this spec gives "WaitForSingleObject: The handle is invalid." - # is this because standard streams on windows aren't async? - pending_win32 "can link processes together" do + it "can link processes together" do buffer = IO::Memory.new Process.run(*stdin_to_stdout_command) do |cat| Process.run(*stdin_to_stdout_command, input: cat.output, output: buffer) do diff --git a/src/process.cr b/src/process.cr index 159b08b39eaa..eb0c165950ce 100644 --- a/src/process.cr +++ b/src/process.cr @@ -328,6 +328,7 @@ class Process Process::Status.new(@process_info.wait) ensure close + @process_info.release end # Whether the process is still registered in the system. @@ -346,7 +347,6 @@ class Process close_io @input close_io @output close_io @error - @process_info.release end # Asks this process to terminate. From f8fafc386c4fae7ac50a7577df22d4aadb788852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 21 Nov 2023 14:47:34 +0100 Subject: [PATCH 0783/1551] Fix skip spec execution on error exit (#13986) --- src/spec/dsl.cr | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/spec/dsl.cr b/src/spec/dsl.cr index 99078cc66b3c..90803fdee39c 100644 --- a/src/spec/dsl.cr +++ b/src/spec/dsl.cr @@ -205,18 +205,23 @@ module Spec def self.run @@start_time = Time.monotonic - at_exit do - log_setup - maybe_randomize - run_filters - root_context.run - rescue ex - STDERR.print "Unhandled exception: " - ex.inspect_with_backtrace(STDERR) - STDERR.flush - @@aborted = true - ensure - finish_run + at_exit do |status| + # Do not run specs if the process is exiting on an error + next unless status == 0 + + begin + log_setup + maybe_randomize + run_filters + root_context.run + rescue ex + STDERR.print "Unhandled exception: " + ex.inspect_with_backtrace(STDERR) + STDERR.flush + @@aborted = true + ensure + finish_run + end end end From 0393e08cebdce214e34f289c9c31b959e892a991 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 24 Nov 2023 00:40:01 +0800 Subject: [PATCH 0784/1551] Make `scripts/*.cr` all executable (#13999) --- scripts/generate_grapheme_break_specs.cr | 2 ++ scripts/generate_grapheme_properties.cr | 2 ++ scripts/generate_unicode_data.cr | 2 ++ scripts/generate_windows_zone_names.cr | 0 4 files changed, 6 insertions(+) mode change 100644 => 100755 scripts/generate_grapheme_break_specs.cr mode change 100644 => 100755 scripts/generate_grapheme_properties.cr mode change 100644 => 100755 scripts/generate_unicode_data.cr mode change 100644 => 100755 scripts/generate_windows_zone_names.cr diff --git a/scripts/generate_grapheme_break_specs.cr b/scripts/generate_grapheme_break_specs.cr old mode 100644 new mode 100755 index 72b868c3aa46..1a141a28776c --- a/scripts/generate_grapheme_break_specs.cr +++ b/scripts/generate_grapheme_break_specs.cr @@ -1,3 +1,5 @@ +#! /usr/bin/env crystal +# # This script generates the file spec/std/string/graphemes_break_spec.cr # that contains test cases for Unicode grapheme clusters based on the default # Grapheme_Cluster_Break Test diff --git a/scripts/generate_grapheme_properties.cr b/scripts/generate_grapheme_properties.cr old mode 100644 new mode 100755 index 39a83315a903..6cda44cbe628 --- a/scripts/generate_grapheme_properties.cr +++ b/scripts/generate_grapheme_properties.cr @@ -1,3 +1,5 @@ +#! /usr/bin/env crystal +# # This script generates the file src/string/grapheme/properties.cr # that contains compact representations of the GraphemeBreakProperty.txt and emoji-data.txt # file from the unicode specification. diff --git a/scripts/generate_unicode_data.cr b/scripts/generate_unicode_data.cr old mode 100644 new mode 100755 index 610eae8cc2dd..64a4aaf31f6d --- a/scripts/generate_unicode_data.cr +++ b/scripts/generate_unicode_data.cr @@ -1,3 +1,5 @@ +#! /usr/bin/env crystal +# # This script generates the file src/unicode/data.cr # that contains compact representations of the UnicodeData.txt # file from the unicode specification. diff --git a/scripts/generate_windows_zone_names.cr b/scripts/generate_windows_zone_names.cr old mode 100644 new mode 100755 From 60c033478af89e4ccab3d9f8cbacbad789a20e46 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 24 Nov 2023 00:40:29 +0800 Subject: [PATCH 0785/1551] Optimize `BigInt#&`, `#|`, `#^` with `Int::Primitive` arguments (#14006) --- src/big/big_int.cr | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/big/big_int.cr b/src/big/big_int.cr index decf7a0828ff..b0530c7b7592 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -385,16 +385,34 @@ struct BigInt < Int BigInt.new { |mpz| LibGMP.com(mpz, self) } end + def &(other : BigInt) : BigInt + BigInt.new { |mpz| LibGMP.and(mpz, self, other) } + end + def &(other : Int) : BigInt - BigInt.new { |mpz| LibGMP.and(mpz, self, other.to_big_i) } + ret = other.to_big_i + LibGMP.and(ret, ret, self) + ret + end + + def |(other : BigInt) : BigInt + BigInt.new { |mpz| LibGMP.ior(mpz, self, other) } end def |(other : Int) : BigInt - BigInt.new { |mpz| LibGMP.ior(mpz, self, other.to_big_i) } + ret = other.to_big_i + LibGMP.ior(ret, ret, self) + ret + end + + def ^(other : BigInt) : BigInt + BigInt.new { |mpz| LibGMP.xor(mpz, self, other) } end def ^(other : Int) : BigInt - BigInt.new { |mpz| LibGMP.xor(mpz, self, other.to_big_i) } + ret = other.to_big_i + LibGMP.xor(ret, ret, self) + ret end def >>(other : Int) : BigInt From 6562623b4b8ca480181739c2ea8bae54e4e41ac6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 24 Nov 2023 00:40:44 +0800 Subject: [PATCH 0786/1551] Add note about `Char::Reader`'s value semantics (#14008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/char/reader.cr | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/char/reader.cr b/src/char/reader.cr index cb307117cdbb..079f9f86d783 100644 --- a/src/char/reader.cr +++ b/src/char/reader.cr @@ -7,10 +7,32 @@ struct Char # Successive calls to `next_char` return the next chars in the string, # advancing `pos`. # - # Note that the null character `'\0'` will be returned in `current_char` when + # NOTE: The null character `'\0'` will be returned in `current_char` when # the end is reached (as well as when the string is empty). Thus, `has_next?` # will return `false` only when `pos` is equal to the string's bytesize, in which # case `current_char` will always be `'\0'`. + # + # NOTE: For performance reasons, `Char::Reader` has value semantics, so care + # must be taken when a reader is declared as a local variable and passed to + # another method: + # + # ``` + # def lstrip(reader) + # until reader.current_char.whitespace? + # reader.next_char + # end + # reader + # end + # + # # caller's internal state is untouched + # reader = Char::Reader.new(" abc") + # lstrip(reader) + # reader.current_char # => ' ' + # + # # to modify caller's internal state, the method must return a new reader + # reader = lstrip(reader) + # reader.current_char # => 'a' + # ``` struct Reader include Enumerable(Char) From 59a0534e191eb3f6ab83fd02381411a3f1d66c13 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 24 Nov 2023 00:40:58 +0800 Subject: [PATCH 0787/1551] Generate `src/html/entities.cr` automatically (#13998) --- .gitattributes | 2 + Makefile | 4 ++ Makefile.win | 4 ++ scripts/generate_html_entities.cr | 43 +++++++++++ scripts/html_entities.ecr | 24 +++++++ src/html/entities.cr | 114 ++---------------------------- 6 files changed, 84 insertions(+), 107 deletions(-) create mode 100755 scripts/generate_html_entities.cr create mode 100644 scripts/html_entities.ecr diff --git a/.gitattributes b/.gitattributes index 78c53472cd74..f616edb09d75 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,6 +10,8 @@ lib/** linguist-vendored # produced by scripts/generate_windows_zone_names.cr src/crystal/system/win32/zone_names.cr linguist-generated +# produced by scripts/generate_html_entities.cr +src/html/entities.cr linguist-generated # produced by scripts/generate_ssl_server_defaults.cr src/openssl/ssl/defaults.cr linguist-generated # produced by scripts/generate_grapheme_properties.cr diff --git a/Makefile b/Makefile index 2ddcc270bdab..03a1aafd34ff 100644 --- a/Makefile +++ b/Makefile @@ -161,6 +161,10 @@ generate_data: src/crystal/system/win32/zone_names.cr src/crystal/system/win32/zone_names.cr: scripts/generate_windows_zone_names.cr $(CRYSTAL) run $< +generate_data: src/html/entities.cr +src/html/entities.cr: scripts/generate_html_entities.cr scripts/html_entities.ecr + $(CRYSTAL) run $< + .PHONY: install install: $(O)/crystal man/crystal.1.gz ## Install the compiler at DESTDIR $(INSTALL) -d -m 0755 "$(BINDIR)/" diff --git a/Makefile.win b/Makefile.win index 1e1d63fdabb2..595a26f5fd00 100644 --- a/Makefile.win +++ b/Makefile.win @@ -163,6 +163,10 @@ generate_data: src\crystal\system\win32\zone_names.cr src\crystal\system\win32\zone_names.cr: scripts\generate_windows_zone_names.cr $(CRYSTAL) run $< +generate_data: src\html\entities.cr +src\html\entities.cr: scripts\generate_html_entities.cr scripts\html_entities.ecr + $(CRYSTAL) run $< + .PHONY: install install: $(O)\crystal.exe ## Install the compiler at prefix $(call MKDIR,"$(BINDIR)") diff --git a/scripts/generate_html_entities.cr b/scripts/generate_html_entities.cr new file mode 100755 index 000000000000..d63592efe19b --- /dev/null +++ b/scripts/generate_html_entities.cr @@ -0,0 +1,43 @@ +#! /usr/bin/env crystal + +require "http" +require "json" +require "ecr" + +record Entity, characters : String, codepoints : Array(Int32) do + include JSON::Serializable + include JSON::Serializable::Strict +end + +single_char_entities = [] of {String, Entity} +double_char_entities = [] of {String, Entity} + +HTTP::Client.get("https://html.spec.whatwg.org/entities.json") do |res| + Hash(String, Entity).from_json(res.body_io).each do |name, entity| + name = name.rchop(';').lchop?('&') || raise "Entity does not begin with &" + + entities = + case entity.codepoints.size + when 1; single_char_entities + when 2; double_char_entities + else raise "Unknown entity codepoint size" + end + + entities << {name, entity} + end +end + +single_char_entities.uniq!(&.first).sort_by!(&.first) +double_char_entities.uniq!(&.first).sort_by!(&.first) + +max_entity_name_size = { + single_char_entities.max_of { |name, _| name.size }, + double_char_entities.max_of { |name, _| name.size }, +}.max + +path = "#{__DIR__}/../src/html/entities.cr" +File.open(path, "w") do |file| + ECR.embed "#{__DIR__}/html_entities.ecr", file +end + +`crystal tool format #{path}` diff --git a/scripts/html_entities.ecr b/scripts/html_entities.ecr new file mode 100644 index 000000000000..3b1da396d127 --- /dev/null +++ b/scripts/html_entities.ecr @@ -0,0 +1,24 @@ +# This file was automatically generated by running: +# +# scripts/generate_html_entities.cr +# +# DO NOT EDIT + +module HTML + # :nodoc: + SINGLE_CHAR_ENTITIES = { + <%- single_char_entities.each do |name, entity| -%> + <%= name.inspect %>.to_slice => '\u{<%= "%06X" % entity.codepoints[0] %>}', + <%- end -%> + } of Bytes => Char + + # :nodoc: + DOUBLE_CHAR_ENTITIES = { + <%- double_char_entities.each do |name, entity| -%> + <%= name.inspect %>.to_slice => "\u{<%= "%04X" % entity.codepoints[0] %>}\u{<%= "%04X" % entity.codepoints[1] %>}", + <%- end -%> + } of Bytes => String + + # :nodoc: + MAX_ENTITY_NAME_SIZE = <%= max_entity_name_size %> +end diff --git a/src/html/entities.cr b/src/html/entities.cr index 3fe05fb63983..ee39c9b1d701 100644 --- a/src/html/entities.cr +++ b/src/html/entities.cr @@ -1,3 +1,9 @@ +# This file was automatically generated by running: +# +# scripts/generate_html_entities.cr +# +# DO NOT EDIT + module HTML # :nodoc: SINGLE_CHAR_ENTITIES = { @@ -2033,112 +2039,6 @@ module HTML "zscr".to_slice => '\u{01D4CF}', "zwj".to_slice => '\u{00200D}', "zwnj".to_slice => '\u{00200C}', - "AElig".to_slice => '\u{0000C6}', - "AMP".to_slice => '\u{000026}', - "Aacute".to_slice => '\u{0000C1}', - "Acirc".to_slice => '\u{0000C2}', - "Agrave".to_slice => '\u{0000C0}', - "Aring".to_slice => '\u{0000C5}', - "Atilde".to_slice => '\u{0000C3}', - "Auml".to_slice => '\u{0000C4}', - "COPY".to_slice => '\u{0000A9}', - "Ccedil".to_slice => '\u{0000C7}', - "ETH".to_slice => '\u{0000D0}', - "Eacute".to_slice => '\u{0000C9}', - "Ecirc".to_slice => '\u{0000CA}', - "Egrave".to_slice => '\u{0000C8}', - "Euml".to_slice => '\u{0000CB}', - "GT".to_slice => '\u{00003E}', - "Iacute".to_slice => '\u{0000CD}', - "Icirc".to_slice => '\u{0000CE}', - "Igrave".to_slice => '\u{0000CC}', - "Iuml".to_slice => '\u{0000CF}', - "LT".to_slice => '\u{00003C}', - "Ntilde".to_slice => '\u{0000D1}', - "Oacute".to_slice => '\u{0000D3}', - "Ocirc".to_slice => '\u{0000D4}', - "Ograve".to_slice => '\u{0000D2}', - "Oslash".to_slice => '\u{0000D8}', - "Otilde".to_slice => '\u{0000D5}', - "Ouml".to_slice => '\u{0000D6}', - "QUOT".to_slice => '\u{000022}', - "REG".to_slice => '\u{0000AE}', - "THORN".to_slice => '\u{0000DE}', - "Uacute".to_slice => '\u{0000DA}', - "Ucirc".to_slice => '\u{0000DB}', - "Ugrave".to_slice => '\u{0000D9}', - "Uuml".to_slice => '\u{0000DC}', - "Yacute".to_slice => '\u{0000DD}', - "aacute".to_slice => '\u{0000E1}', - "acirc".to_slice => '\u{0000E2}', - "acute".to_slice => '\u{0000B4}', - "aelig".to_slice => '\u{0000E6}', - "agrave".to_slice => '\u{0000E0}', - "amp".to_slice => '\u{000026}', - "aring".to_slice => '\u{0000E5}', - "atilde".to_slice => '\u{0000E3}', - "auml".to_slice => '\u{0000E4}', - "brvbar".to_slice => '\u{0000A6}', - "ccedil".to_slice => '\u{0000E7}', - "cedil".to_slice => '\u{0000B8}', - "cent".to_slice => '\u{0000A2}', - "copy".to_slice => '\u{0000A9}', - "curren".to_slice => '\u{0000A4}', - "deg".to_slice => '\u{0000B0}', - "divide".to_slice => '\u{0000F7}', - "eacute".to_slice => '\u{0000E9}', - "ecirc".to_slice => '\u{0000EA}', - "egrave".to_slice => '\u{0000E8}', - "eth".to_slice => '\u{0000F0}', - "euml".to_slice => '\u{0000EB}', - "frac12".to_slice => '\u{0000BD}', - "frac14".to_slice => '\u{0000BC}', - "frac34".to_slice => '\u{0000BE}', - "gt".to_slice => '\u{00003E}', - "iacute".to_slice => '\u{0000ED}', - "icirc".to_slice => '\u{0000EE}', - "iexcl".to_slice => '\u{0000A1}', - "igrave".to_slice => '\u{0000EC}', - "iquest".to_slice => '\u{0000BF}', - "iuml".to_slice => '\u{0000EF}', - "laquo".to_slice => '\u{0000AB}', - "lt".to_slice => '\u{00003C}', - "macr".to_slice => '\u{0000AF}', - "micro".to_slice => '\u{0000B5}', - "middot".to_slice => '\u{0000B7}', - "nbsp".to_slice => '\u{0000A0}', - "not".to_slice => '\u{0000AC}', - "ntilde".to_slice => '\u{0000F1}', - "oacute".to_slice => '\u{0000F3}', - "ocirc".to_slice => '\u{0000F4}', - "ograve".to_slice => '\u{0000F2}', - "ordf".to_slice => '\u{0000AA}', - "ordm".to_slice => '\u{0000BA}', - "oslash".to_slice => '\u{0000F8}', - "otilde".to_slice => '\u{0000F5}', - "ouml".to_slice => '\u{0000F6}', - "para".to_slice => '\u{0000B6}', - "plusmn".to_slice => '\u{0000B1}', - "pound".to_slice => '\u{0000A3}', - "quot".to_slice => '\u{000022}', - "raquo".to_slice => '\u{0000BB}', - "reg".to_slice => '\u{0000AE}', - "sect".to_slice => '\u{0000A7}', - "shy".to_slice => '\u{0000AD}', - "sup1".to_slice => '\u{0000B9}', - "sup2".to_slice => '\u{0000B2}', - "sup3".to_slice => '\u{0000B3}', - "szlig".to_slice => '\u{0000DF}', - "thorn".to_slice => '\u{0000FE}', - "times".to_slice => '\u{0000D7}', - "uacute".to_slice => '\u{0000FA}', - "ucirc".to_slice => '\u{0000FB}', - "ugrave".to_slice => '\u{0000F9}', - "uml".to_slice => '\u{0000A8}', - "uuml".to_slice => '\u{0000FC}', - "yacute".to_slice => '\u{0000FD}', - "yen".to_slice => '\u{0000A5}', - "yuml".to_slice => '\u{0000FF}', } of Bytes => Char # :nodoc: @@ -2239,5 +2139,5 @@ module HTML } of Bytes => String # :nodoc: - MAX_ENTITY_NAME_SIZE = Math.max(SINGLE_CHAR_ENTITIES.each_key.max_of(&.size), DOUBLE_CHAR_ENTITIES.each_key.max_of(&.size)) + MAX_ENTITY_NAME_SIZE = 31 end From f3b47fb49c813cbc27a1db58e28d1ab8f9ccb964 Mon Sep 17 00:00:00 2001 From: Margret Riegert Date: Fri, 24 Nov 2023 09:34:27 -0500 Subject: [PATCH 0788/1551] Remove relative path to vendored shards `markd` and `reply` (#13992) --- src/compiler/crystal/interpreter/repl_reader.cr | 2 +- src/compiler/crystal/tools/doc/generator.cr | 2 +- src/compiler/crystal/tools/playground/server.cr | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/crystal/interpreter/repl_reader.cr b/src/compiler/crystal/interpreter/repl_reader.cr index f805150dbcd7..535ec53e64a9 100644 --- a/src/compiler/crystal/interpreter/repl_reader.cr +++ b/src/compiler/crystal/interpreter/repl_reader.cr @@ -1,4 +1,4 @@ -require "../../../../lib/reply/src/reply" +require "reply" class Crystal::ReplReader < Reply::Reader KEYWORDS = %w( diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index 1299737e8f1f..a2d6699944e0 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -1,4 +1,4 @@ -require "../../../../../lib/markd/src/markd" +require "markd" require "crystal/syntax_highlighter/html" class Crystal::Doc::Generator diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr index f060778257d4..1729bd7a98b3 100644 --- a/src/compiler/crystal/tools/playground/server.cr +++ b/src/compiler/crystal/tools/playground/server.cr @@ -2,7 +2,7 @@ require "http/server" require "log" require "ecr/macros" require "compiler/crystal/tools/formatter" -require "../../../../../lib/markd/src/markd" +require "markd" module Crystal::Playground Log = ::Log.for("crystal.playground") From 31762cf49e7d0b137a4e67d5a6b42e0bdea6b377 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 24 Nov 2023 22:34:47 +0800 Subject: [PATCH 0789/1551] Add `Char::Reader#current_char?`, `#next_char?`, `#previous_char?` (#14012) --- spec/std/char/reader_spec.cr | 49 +++++++++++++++ src/char/reader.cr | 119 +++++++++++++++++++++++++++-------- 2 files changed, 142 insertions(+), 26 deletions(-) diff --git a/spec/std/char/reader_spec.cr b/spec/std/char/reader_spec.cr index 6197a3d4b6c8..9b214c4ea5a6 100644 --- a/spec/std/char/reader_spec.cr +++ b/spec/std/char/reader_spec.cr @@ -145,6 +145,55 @@ describe "Char::Reader" do reader.current_char.should eq('語') end + it "#current_char?" do + reader = Char::Reader.new("há日本語") + reader.current_char?.should eq('h') + reader.next_char + reader.current_char?.should eq('á') + reader.next_char + reader.current_char?.should eq('日') + reader.next_char + reader.current_char?.should eq('本') + reader.next_char + reader.current_char?.should eq('語') + reader.next_char + reader.current_char?.should be_nil + reader.previous_char + reader.current_char?.should eq('語') + end + + it "#next_char?" do + reader = Char::Reader.new("há日本語") + reader.next_char?.should eq('á') + reader.pos.should eq(1) + reader.next_char?.should eq('日') + reader.pos.should eq(3) + reader.next_char?.should eq('本') + reader.pos.should eq(6) + reader.next_char?.should eq('語') + reader.pos.should eq(9) + reader.next_char?.should be_nil + reader.pos.should eq(12) + reader.next_char?.should be_nil + reader.pos.should eq(12) + end + + it "#previous_char?" do + reader = Char::Reader.new("há日本語", pos: 12) + reader.previous_char?.should eq('語') + reader.pos.should eq(9) + reader.previous_char?.should eq('本') + reader.pos.should eq(6) + reader.previous_char?.should eq('日') + reader.pos.should eq(3) + reader.previous_char?.should eq('á') + reader.pos.should eq(1) + reader.previous_char?.should eq('h') + reader.pos.should eq(0) + reader.previous_char?.should be_nil + reader.pos.should eq(0) + end + it "errors if 0x80 <= first_byte < 0xC2" do assert_invalid_byte_sequence Bytes[0x80] assert_invalid_byte_sequence Bytes[0xC1] diff --git a/src/char/reader.cr b/src/char/reader.cr index 079f9f86d783..1624a35f4488 100644 --- a/src/char/reader.cr +++ b/src/char/reader.cr @@ -39,13 +39,16 @@ struct Char # Returns the reader's String. getter string : String - # Returns the current character. + # Returns the current character, or `'\0'` if the reader is at the end of + # the string. # # ``` # reader = Char::Reader.new("ab") # reader.current_char # => 'a' # reader.next_char # reader.current_char # => 'b' + # reader.next_char + # reader.current_char # => '\0' # ``` getter current_char : Char @@ -59,7 +62,7 @@ struct Char # ``` getter current_char_width : Int32 - # Returns the position of the current character. + # Returns the byte position of the current character. # # ``` # reader = Char::Reader.new("ab") @@ -93,40 +96,81 @@ struct Char decode_previous_char end - # Returns `true` if there is a character left to read. - # The terminating byte `'\0'` is considered a valid character - # by this method. + # Returns the current character. + # + # Returns `nil` if the reader is at the end of the string. + def current_char? : Char? + if has_next? + current_char + end + end + + # Returns `true` if the reader is not at the end of the string. + # + # NOTE: This only means `#next_char` will successfully increment `#pos`; if + # the reader is already at the last character, `#next_char` will return the + # terminating null byte because there isn't really a next character. # # ``` - # reader = Char::Reader.new("a") - # reader.has_next? # => true - # reader.peek_next_char # => '\0' + # reader = Char::Reader.new("ab") + # reader.has_next? # => true + # reader.next_char # => 'b' + # reader.has_next? # => true + # reader.next_char # => '\0' + # reader.has_next? # => false # ``` def has_next? : Bool @pos < @string.bytesize end - # Reads the next character in the string, - # `#pos` is incremented. Raises `IndexError` if the reader is - # at the end of the `#string`. + # Tries to read the next character in the string. + # + # If the reader is at the end of the string before or after incrementing + # `#pos`, returns `nil`. # # ``` - # reader = Char::Reader.new("ab") + # reader = Char::Reader.new("abc") + # reader.next_char? # => 'b' + # reader.next_char? # => 'c' + # reader.next_char? # => nil + # reader.current_char # => '\0' + # ``` + def next_char? : Char? + next_pos = @pos + @current_char_width + if next_pos <= @string.bytesize + @pos = next_pos + decode_current_char + current_char? + end + end + + # Reads the next character in the string. + # + # If the reader is at the end of the string after incrementing `#pos`, + # returns `'\0'`. If the reader is already at the end beforehand, raises + # `IndexError`. + # + # ``` + # reader = Char::Reader.new("abc") # reader.next_char # => 'b' + # reader.next_char # => 'c' + # reader.next_char # => '\0' + # reader.next_char # raise IndexError # ``` def next_char : Char - @pos += @current_char_width - if @pos > @string.bytesize + next_pos = @pos + @current_char_width + if next_pos <= @string.bytesize + @pos = next_pos + decode_current_char + else raise IndexError.new end - - decode_current_char end - # Returns the next character in the `#string` - # without incrementing `#pos`. - # Raises `IndexError` if the reader is at - # the end of the `#string`. + # Returns the next character in the `#string` without incrementing `#pos`. + # + # Returns `'\0'` if the reader is at the last character of the string. + # Raises `IndexError` if the reader is at the end. # # ``` # reader = Char::Reader.new("ab") @@ -145,16 +189,39 @@ struct Char end end - # Returns `true` if there are characters before - # the current one. + # Returns `true` if the reader is not at the beginning of the string. def has_previous? : Bool @pos > 0 end - # Returns the previous character, `#pos` - # is decremented. - # Raises `IndexError` if the reader is at the beginning of - # the `#string` + # Tries to read the previous character in the string. + # + # Returns `nil` if the reader is already at the beginning of the string. + # Otherwise decrements `#pos`. + # + # ``` + # reader = Char::Reader.new(at_end: "abc") + # reader.previous_char? # => 'b' + # reader.previous_char? # => 'a' + # reader.previous_char? # => nil + # ``` + def previous_char? : Char? + if has_previous? + decode_previous_char + end + end + + # Reads the previous character in the string. + # + # Raises `IndexError` if the reader is already at the beginning of the + # string. Otherwise decrements `#pos`. + # + # ``` + # reader = Char::Reader.new(at_end: "abc") + # reader.previous_char # => 'b' + # reader.previous_char # => 'a' + # reader.previous_char # raises IndexError + # ``` def previous_char : Char unless has_previous? raise IndexError.new From 12f98cf2ee004419d39c2daedbc57857565c1bae Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 25 Nov 2023 04:04:54 +0800 Subject: [PATCH 0790/1551] Add `Float32::MIN_SUBNORMAL` and `Float64::MIN_SUBNORMAL` (#13961) --- spec/std/float_printer_spec.cr | 8 ++++---- spec/std/float_spec.cr | 8 ++++---- src/float.cr | 8 ++++++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/spec/std/float_printer_spec.cr b/spec/std/float_printer_spec.cr index 3db7b344b170..b5c72d999868 100644 --- a/spec/std/float_printer_spec.cr +++ b/spec/std/float_printer_spec.cr @@ -63,8 +63,8 @@ describe "Float32#to_s" do it_converts_to_s 0xFFC0_0000_u32.unsafe_as(Float32), "NaN" it_converts_to_s Float32::MIN_POSITIVE, "1.1754944e-38" it_converts_to_s Float32::MAX, "3.4028235e+38" - it_converts_to_s hexfloat("0x1.ffffffp-127_f32"), "1.1754942e-38" # largest denormal - it_converts_to_s 1.0e-45_f32 # smallest denormal + it_converts_to_s Float32::MIN_POSITIVE.prev_float, "1.1754942e-38" # largest subnormal + it_converts_to_s Float32::MIN_SUBNORMAL, "1.0e-45" end context "Ryu f2s_test.cc BoundaryRoundEven" do @@ -347,8 +347,8 @@ describe "Float64#to_s" do it_converts_to_s 0xFFF8_0000_0000_0000_u64.unsafe_as(Float64), "NaN" it_converts_to_s Float64::MIN_POSITIVE, "2.2250738585072014e-308" it_converts_to_s Float64::MAX, "1.7976931348623157e+308" - it_converts_to_s hexfloat("0x1.fffffffffffffp-1023"), "2.225073858507201e-308" # largest denormal - it_converts_to_s 5.0e-324 # smallest denormal + it_converts_to_s Float64::MIN_POSITIVE.prev_float, "2.225073858507201e-308" # largest subnormal + it_converts_to_s Float64::MIN_SUBNORMAL, "5.0e-324" end context "Ryu d2s_test.cc LotsOfTrailingZeros" do diff --git a/spec/std/float_spec.cr b/spec/std/float_spec.cr index 9a6ebecd3a2f..b82e5cfdbfc4 100644 --- a/spec/std/float_spec.cr +++ b/spec/std/float_spec.cr @@ -225,7 +225,7 @@ describe "Float" do describe "#next_float" do it "does for f64" do - 0.0.next_float.should eq(5.0e-324) # smallest denormal (not MIN_POSITIVE) + 0.0.next_float.should eq(Float64::MIN_SUBNORMAL) 1.0.next_float.should eq(1.0000000000000002) (-1.0).next_float.should eq(-0.9999999999999999) Float64::MAX.next_float.should eq(Float64::INFINITY) @@ -235,7 +235,7 @@ describe "Float" do end it "does for f32" do - 0.0_f32.next_float.should eq(1.0e-45_f32) # smallest denormal (not MIN_POSITIVE) + 0.0_f32.next_float.should eq(Float32::MIN_SUBNORMAL) 1.0_f32.next_float.should eq(1.0000001_f32) (-1.0_f32).next_float.should eq(-0.99999994_f32) Float32::MAX.next_float.should eq(Float32::INFINITY) @@ -247,7 +247,7 @@ describe "Float" do describe "#prev_float" do it "does for f64" do - 0.0.prev_float.should eq(-5.0e-324) # smallest denormal (not MIN_POSITIVE) + 0.0.prev_float.should eq(-Float64::MIN_SUBNORMAL) 1.0.prev_float.should eq(0.9999999999999999) (-1.0).prev_float.should eq(-1.0000000000000002) Float64::MIN.prev_float.should eq(-Float64::INFINITY) @@ -257,7 +257,7 @@ describe "Float" do end it "does for f32" do - 0.0_f32.prev_float.should eq(-1.0e-45_f32) # smallest denormal (not MIN_POSITIVE) + 0.0_f32.prev_float.should eq(-Float32::MIN_SUBNORMAL) 1.0_f32.prev_float.should eq(0.99999994_f32) (-1.0_f32).prev_float.should eq(-1.0000001_f32) Float32::MIN.prev_float.should eq(-Float32::INFINITY) diff --git a/src/float.cr b/src/float.cr index 3dcbfd6f5301..a4abcf5abdf8 100644 --- a/src/float.cr +++ b/src/float.cr @@ -129,8 +129,10 @@ struct Float32 MIN_10_EXP = -37 # The maximum possible power of 10 exponent (such that 10**MAX_10_EXP is representable) MAX_10_EXP = 38 - # Smallest representable positive value + # Smallest normal positive value, whose previous representable value is subnormal MIN_POSITIVE = 1.17549435e-38_f32 + # Smallest representable positive value, whose previous representable value is zero + MIN_SUBNORMAL = 1.0e-45_f32 # Returns a `Float32` by invoking `String#to_f32` on *value*. # @@ -249,8 +251,10 @@ struct Float64 MIN_10_EXP = -307 # The maximum possible power of 10 exponent (such that 10**MAX_10_EXP is representable) MAX_10_EXP = 308 - # Smallest representable positive value + # Smallest normal positive value, whose previous representable value is subnormal MIN_POSITIVE = 2.2250738585072014e-308_f64 + # Smallest representable positive value, whose previous representable value is zero + MIN_SUBNORMAL = 5.0e-324_f64 # Returns a `Float64` by invoking `String#to_f64` on *value*. # From 8e1a9cc3237e6051ab096077134efe838fa7f6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 24 Nov 2023 21:05:10 +0100 Subject: [PATCH 0791/1551] Reformat changelog release headings (#13663) Co-authored-by: Beta Ziliani --- CHANGELOG.0.md | 931 +++++++++++++++++++++++++++---------------------- CHANGELOG.md | 663 +++++++++++++++++++---------------- 2 files changed, 877 insertions(+), 717 deletions(-) diff --git a/CHANGELOG.0.md b/CHANGELOG.0.md index 5ed22c42b213..05d3ffe5a374 100644 --- a/CHANGELOG.0.md +++ b/CHANGELOG.0.md @@ -1,29 +1,31 @@ +# Changelog (pre 1.0) Newer entries in [CHANGELOG.md](./CHANGELOG.md) -# 0.36.1 (2021-02-02) +## [0.36.1] - 2021-02-02 +[0.36.1]: https://github.com/crystal-lang/crystal/releases/0.36.1 -## Standard library +### Standard library - Fix `Enum.from_value?` for flag enum with non `Int32` base type. ([#10303](https://github.com/crystal-lang/crystal/pull/10303), thanks @straight-shoota) -### Text +#### Text - Don't raise on `String.new` with `null` pointer and bytesize 0. ([#10308](https://github.com/crystal-lang/crystal/pull/10308), thanks @asterite) -### Collections +#### Collections - Explicitly return a `Hash` in `Hash#dup` and `Hash#clone` (reverts [#9871](https://github.com/crystal-lang/crystal/pull/9871)). ([#10331](https://github.com/crystal-lang/crystal/pull/10331), thanks @straight-shoota) -### Serialization +#### Serialization - XML: fix deprecation warning. ([#10335](https://github.com/crystal-lang/crystal/pull/10335), thanks @bcardiff) -### Runtime +#### Runtime - Eager load DWARF only if `CRYSTAL_LOAD_DWARF=1`. ([#10326](https://github.com/crystal-lang/crystal/pull/10326), thanks @bcardiff) -## Compiler +### Compiler - **(performance)** Initialize right-away constants in a separate function. ([#10334](https://github.com/crystal-lang/crystal/pull/10334), thanks @asterite) - Fix incorrect casting between different union types. ([#10333](https://github.com/crystal-lang/crystal/pull/10333), thanks @asterite) @@ -31,26 +33,27 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix while condition assignment check for Not. ([#10347](https://github.com/crystal-lang/crystal/pull/10347), thanks @asterite) - Allow operators and setters-like macros names back. ([#10338](https://github.com/crystal-lang/crystal/pull/10338), thanks @asterite) -### Language semantics +#### Language semantics - Fix type check not considering virtual types. ([#10304](https://github.com/crystal-lang/crystal/pull/10304), thanks @asterite) - Use path lookup when looking up type for auto-cast match. ([#10318](https://github.com/crystal-lang/crystal/pull/10318), thanks @asterite) -# 0.36.0 (2021-01-26) +## [0.36.0] - 2021-01-26 +[0.36.0]: https://github.com/crystal-lang/crystal/releases/0.36.0 -## Language changes +### Language changes - **(breaking-change)** Reject annotations on ivars defined in base class. ([#9502](https://github.com/crystal-lang/crystal/pull/9502), thanks @waj) - **(breaking-change)** Make `**` be right associative. ([#9684](https://github.com/crystal-lang/crystal/pull/9684), thanks @asterite) -### Macros +#### Macros - **(breaking-change)** Deprecate `TypeNode#has_attribute?` in favor of `#annotation`. ([#9950](https://github.com/crystal-lang/crystal/pull/9950), thanks @HertzDevil) - Support heredoc literal in macro. ([#9467](https://github.com/crystal-lang/crystal/pull/9467), thanks @MakeNowJust) - Support `%Q`, `%i`, `%w`, and `%x` literals in macros. ([#9811](https://github.com/crystal-lang/crystal/pull/9811), thanks @toddsundsted) - Allow executing multi-assignments in macros. ([#9440](https://github.com/crystal-lang/crystal/pull/9440), thanks @MakeNowJust) -## Standard library +### Standard library - **(breaking-change)** Drop deprecated `CRC32`, `Adler32` top-level. ([#9530](https://github.com/crystal-lang/crystal/pull/9530), thanks @bcardiff) - **(breaking-change)** Drop deprecated `Flate`, `Gzip`, `Zip`, `Zlib` top-level. ([#9529](https://github.com/crystal-lang/crystal/pull/9529), thanks @bcardiff) @@ -69,11 +72,11 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix example codes in multiple places. ([#9818](https://github.com/crystal-lang/crystal/pull/9818), thanks @maiha) - Fix typos, misspelling and styling. ([#9636](https://github.com/crystal-lang/crystal/pull/9636), [#9638](https://github.com/crystal-lang/crystal/pull/9638), [#9561](https://github.com/crystal-lang/crystal/pull/9561), [#9786](https://github.com/crystal-lang/crystal/pull/9786), [#9840](https://github.com/crystal-lang/crystal/pull/9840), [#9844](https://github.com/crystal-lang/crystal/pull/9844), [#9854](https://github.com/crystal-lang/crystal/pull/9854), [#9869](https://github.com/crystal-lang/crystal/pull/9869), [#10068](https://github.com/crystal-lang/crystal/pull/10068), [#10123](https://github.com/crystal-lang/crystal/pull/10123), [#10102](https://github.com/crystal-lang/crystal/pull/10102), [#10116](https://github.com/crystal-lang/crystal/pull/10116), [#10229](https://github.com/crystal-lang/crystal/pull/10229), [#10252](https://github.com/crystal-lang/crystal/pull/10252), thanks @kubo, @m-o-e, @mgomes, @philipp-classen, @dukeraphaelng, @camreeves, @docelic, @ilmanzo, @Sija, @pxeger, @oprypin) -### Macros +#### Macros - Fix documentation for `#[]=` macro methods. ([#10025](https://github.com/crystal-lang/crystal/pull/10025), thanks @HertzDevil) -### Numeric +#### Numeric - **(breaking-change)** Move `Complex#exp`, `Complex#log`, etc. to `Math.exp`, `Math.log` overloads. ([#9739](https://github.com/crystal-lang/crystal/pull/9739), thanks @cristian-lsdb) - **(performance)** Use unchecked arithmetics in `Int#times`. ([#9511](https://github.com/crystal-lang/crystal/pull/9511), thanks @waj) @@ -88,7 +91,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add wrapped unary negation to unsigned integer types to allow `&- x` expressions. ([#9947](https://github.com/crystal-lang/crystal/pull/9947), thanks @HertzDevil) - Improve documentation of mathematical functions. ([#9994](https://github.com/crystal-lang/crystal/pull/9994), thanks @HertzDevil) -### Text +#### Text - **(breaking-change)** Fix `String#index` not working well for broken UTF-8 sequences. ([#9713](https://github.com/crystal-lang/crystal/pull/9713), thanks @asterite) - **(performance)** `String`: Don't materialize `Regex` `match[0]` if not needed. ([#9615](https://github.com/crystal-lang/crystal/pull/9615), thanks @asterite) @@ -100,7 +103,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix duplicates in `String` documentation. ([#9987](https://github.com/crystal-lang/crystal/pull/9987), thanks @Nicolab) - Add docs for substitution to `Kernel.sprintf`. ([#10227](https://github.com/crystal-lang/crystal/pull/10227), thanks @straight-shoota) -### Collections +#### Collections - **(breaking-change)** Make `Hash#reject!`, `Hash#select!`, and `Hash#compact!` consistent with `Array` and return `self`. ([#9904](https://github.com/crystal-lang/crystal/pull/9904), thanks @caspiano) - **(breaking-change)** Deprecate `Hash#delete_if` in favor of `Hash#reject!`, add `Dequeue#reject!` and `Dequeue#select!`. ([#9878](https://github.com/crystal-lang/crystal/pull/9878), thanks @caspiano) @@ -132,7 +135,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Use `Set#add` in `Set#add` documentation. ([#9441](https://github.com/crystal-lang/crystal/pull/9441), thanks @hugopl) - Improve `Hash#values_at` docs. ([#9955](https://github.com/crystal-lang/crystal/pull/9955), thanks @j8r) -### Serialization +#### Serialization - **(breaking-change)** Drop deprecated `JSON.mapping`. ([#9527](https://github.com/crystal-lang/crystal/pull/9527), thanks @bcardiff) - **(breaking-change)** Drop deprecated `YAML.mapping`. ([#9526](https://github.com/crystal-lang/crystal/pull/9526), thanks @bcardiff) @@ -152,7 +155,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Document `JSON::PullParser`. ([#9983](https://github.com/crystal-lang/crystal/pull/9983), thanks @erdnaxeli) - Clarify Serialization Converter requirements and examples. ([#10202](https://github.com/crystal-lang/crystal/pull/10202), thanks @Daniel-Worrall) -### Time +#### Time - **(breaking-change)** Drop deprecated `Time::Span.new` variants. ([#10051](https://github.com/crystal-lang/crystal/pull/10051), thanks @Sija) - **(breaking-change)** Deprecate `Time::Span#duration`, use `Time::Span#abs`. ([#10144](https://github.com/crystal-lang/crystal/pull/10144), thanks @straight-shoota) @@ -161,7 +164,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Enable pending specs. ([#10093](https://github.com/crystal-lang/crystal/pull/10093), thanks @straight-shoota) - Improve `Time::Span` arithmetic specs. ([#10143](https://github.com/crystal-lang/crystal/pull/10143), thanks @straight-shoota) -### Files +#### Files - **(breaking-change)** Make `File.size` and `FileInfo#size` be `Int64` instead of `UInt64`. ([#10015](https://github.com/crystal-lang/crystal/pull/10015), thanks @asterite) - **(breaking-change)** Fix `FileUtils.cp_r` when destination is a directory. ([#10180](https://github.com/crystal-lang/crystal/pull/10180), thanks @wonderix) @@ -176,7 +179,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Support using `Path` in `MIME` and `File::Error` APIs. ([#10034](https://github.com/crystal-lang/crystal/pull/10034), thanks @Blacksmoke16) - Windows: enable `FileUtils` specs. ([#9902](https://github.com/crystal-lang/crystal/pull/9902), thanks @oprypin) -### Networking +#### Networking - **(breaking-change)** Rename `HTTP::Params` to `URI::Params`. ([#10098](https://github.com/crystal-lang/crystal/pull/10098), thanks @straight-shoota) - **(breaking-change)** Rename `URI#full_path` to `URI#request_target`. ([#10099](https://github.com/crystal-lang/crystal/pull/10099), thanks @straight-shoota) @@ -205,14 +208,14 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix `HTTP` docs. ([#9612](https://github.com/crystal-lang/crystal/pull/9612), [#9627](https://github.com/crystal-lang/crystal/pull/9627), [#9717](https://github.com/crystal-lang/crystal/pull/9717), thanks @RX14, @asterite, @3n-k1) - Fix reference to TCP protocol in docs. ([#9457](https://github.com/crystal-lang/crystal/pull/9457), thanks @dprobinson) -### Logging +#### Logging - **(breaking-change)** Drop deprecated `Logger`. ([#9525](https://github.com/crystal-lang/crystal/pull/9525), thanks @bcardiff) - **(performance)** Add logging dispatcher to defer entry dispatch from current fiber. ([#9432](https://github.com/crystal-lang/crystal/pull/9432), thanks @waj) - Take timestamp as an argument in `Log::Entry` initializer. ([#9570](https://github.com/crystal-lang/crystal/pull/9570), thanks @Sija) - Improve docs regarding `Log.setup`. ([#9497](https://github.com/crystal-lang/crystal/pull/9497), [#9559](https://github.com/crystal-lang/crystal/pull/9559), thanks @caspiano, @jjlorenzo) -### Crypto +#### Crypto - **(security)** Fix `"force-peer"` `verify_mode` setup. ([#9668](https://github.com/crystal-lang/crystal/pull/9668), thanks @PhilAtWysdom) - **(security)** Force secure renegotiation on server to prevent Secure Client-Initiated Renegotiation vulnerability attack. ([#9815](https://github.com/crystal-lang/crystal/pull/9815), thanks @bcardiff) @@ -222,14 +225,14 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add `OpenSSL::SSL::Context#security_level`. ([#9831](https://github.com/crystal-lang/crystal/pull/9831), thanks @bcardiff) - `OpenSSL::digest` add require for `OpenSSL::Error`. ([#10173](https://github.com/crystal-lang/crystal/pull/10173), thanks @wonderix) -### Concurrency +#### Concurrency - **(breaking-change)** Hide Channel internal implementation methods. ([#9564](https://github.com/crystal-lang/crystal/pull/9564), thanks @j8r) - `Channel#close` returns `true` unless the channel was already closed. ([#9443](https://github.com/crystal-lang/crystal/pull/9443), thanks @waj) - Support splat expressions inside `spawn` macro. ([#10234](https://github.com/crystal-lang/crystal/pull/10234), thanks @HertzDevil) - Remove incorrect note about negative timeout not triggering. ([#10131](https://github.com/crystal-lang/crystal/pull/10131), thanks @straight-shoota) -### System +#### System - Fix `Process.find_executable` to check the found path is executable and add Windows support. ([#9365](https://github.com/crystal-lang/crystal/pull/9365), thanks @oprypin) - Add `Process.parse_arguments` and fix `CRYSTAL_OPTS` parsing. ([#9518](https://github.com/crystal-lang/crystal/pull/9518), thanks @MakeNowJust) @@ -238,7 +241,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Handle errors in `User`/`Group` `from_name?`/`from_id?` as not found. ([#10182](https://github.com/crystal-lang/crystal/pull/10182), thanks @wonderix) - define the `SC_PAGESIZE` constant on more platforms. ([#9821](https://github.com/crystal-lang/crystal/pull/9821), thanks @carlhoerberg) -### Runtime +#### Runtime - Fix bug with passing many args then a struct in Win64 C lib ABI. ([#9520](https://github.com/crystal-lang/crystal/pull/9520), thanks @oprypin) - Fix C ABI for AArch64. ([#9430](https://github.com/crystal-lang/crystal/pull/9430), thanks @jhass) @@ -254,7 +257,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix DWARF loading in FreeBSD. ([#10259](https://github.com/crystal-lang/crystal/pull/10259), thanks @bcardiff) - Print unhandled exceptions from main routine with `#inspect_with_backtrace`. ([#10086](https://github.com/crystal-lang/crystal/pull/10086), thanks @straight-shoota) -### Spec +#### Spec - **(breaking-change)** Add support for custom failure messages in expectations. ([#10127](https://github.com/crystal-lang/crystal/pull/10127), [#10289](https://github.com/crystal-lang/crystal/pull/10289), thanks @Fryguy, @straight-shoota) - Allow absolute file paths in `crystal spec` CLI. ([#9951](https://github.com/crystal-lang/crystal/pull/9951), thanks @KevinSjoberg) @@ -262,7 +265,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Improve duration display in `spec --profile`. ([#10044](https://github.com/crystal-lang/crystal/pull/10044), thanks @straight-shoota) - Add exception handler in spec `at_exit`. ([#10106](https://github.com/crystal-lang/crystal/pull/10106), thanks @straight-shoota) -## Compiler +### Compiler - **(performance)** Don't use init check for constants that are declared before read. ([#9801](https://github.com/crystal-lang/crystal/pull/9801), thanks @asterite) - **(performance)** Don't use init check for class vars initialized before read. ([#9995](https://github.com/crystal-lang/crystal/pull/9995), thanks @asterite) @@ -301,7 +304,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactors for compiler specs. ([#9379](https://github.com/crystal-lang/crystal/pull/9379), thanks @straight-shoota) - Windows CI: Fix and enable compiler specs. ([#9348](https://github.com/crystal-lang/crystal/pull/9348), [#9560](https://github.com/crystal-lang/crystal/pull/9560), thanks @oprypin) -### Language semantics +#### Language semantics - **(breaking-change)** Handle union restrictions with free vars properly. ([#10267](https://github.com/crystal-lang/crystal/pull/10267), thanks @HertzDevil) - **(breaking-change)** Disallow keywords as block argument name. ([#9704](https://github.com/crystal-lang/crystal/pull/9704), thanks @asterite) @@ -341,11 +344,11 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Reset free vars before attempting each overload match. ([#10271](https://github.com/crystal-lang/crystal/pull/10271), thanks @HertzDevil) - Let `offsetof` support `Tuples`. ([#10218](https://github.com/crystal-lang/crystal/pull/10218), thanks @hugopl) -## Tools +### Tools - Fetch git config correctly. ([#9640](https://github.com/crystal-lang/crystal/pull/9640), thanks @dorianmariefr) -### Formatter +#### Formatter - Avoid adding a newline after comment before end. ([#9722](https://github.com/crystal-lang/crystal/pull/9722), thanks @asterite) - Apply string pieces combination even if heredoc has no indent. ([#9475](https://github.com/crystal-lang/crystal/pull/9475), thanks @MakeNowJust) @@ -356,7 +359,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Let formatter normalize crystal language tag for code blocks in doc comments. ([#10156](https://github.com/crystal-lang/crystal/pull/10156), thanks @straight-shoota) - Fix indentation of trailing comma in call with a block. ([#10223](https://github.com/crystal-lang/crystal/pull/10223), thanks @MakeNowJust) -### Doc generator +#### Doc generator - Linkification: refactor, fix edge cases and add specs. ([#9817](https://github.com/crystal-lang/crystal/pull/9817), thanks @oprypin) - Exclude types from docs who are in nodoc namespaces. ([#9819](https://github.com/crystal-lang/crystal/pull/9819), thanks @Blacksmoke16) @@ -370,7 +373,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Support linking to section headings in same tab. ([#9826](https://github.com/crystal-lang/crystal/pull/9826), thanks @Blacksmoke16) - Do not highlight `undef` as a keyword, it's not. ([#10216](https://github.com/crystal-lang/crystal/pull/10216), thanks @rhysd) -## Others +### Others - CI improvements and housekeeping. ([#9507](https://github.com/crystal-lang/crystal/pull/9507), [#9513](https://github.com/crystal-lang/crystal/pull/9513), [#9512](https://github.com/crystal-lang/crystal/pull/9512), [#9515](https://github.com/crystal-lang/crystal/pull/9515), [#9514](https://github.com/crystal-lang/crystal/pull/9514), [#9609](https://github.com/crystal-lang/crystal/pull/9609), [#9642](https://github.com/crystal-lang/crystal/pull/9642), [#9758](https://github.com/crystal-lang/crystal/pull/9758), [#9763](https://github.com/crystal-lang/crystal/pull/9763), [#9850](https://github.com/crystal-lang/crystal/pull/9850), [#9906](https://github.com/crystal-lang/crystal/pull/9906), [#9907](https://github.com/crystal-lang/crystal/pull/9907), [#9912](https://github.com/crystal-lang/crystal/pull/9912), [#10078](https://github.com/crystal-lang/crystal/pull/10078), [#10217](https://github.com/crystal-lang/crystal/pull/10217), [#10255](https://github.com/crystal-lang/crystal/pull/10255), thanks @jhass, @bcardiff, @waj, @oprypin, @j8r, @Sija) - Add timeout to individual specs on multi-threading. ([#9865](https://github.com/crystal-lang/crystal/pull/9865), thanks @bcardiff) @@ -391,52 +394,54 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Remove Vagrantfile. ([#10033](https://github.com/crystal-lang/crystal/pull/10033), thanks @straight-shoota) - Update NOTICE's copyright year to 2021. ([#10166](https://github.com/crystal-lang/crystal/pull/10166), thanks @matiasgarciaisaia) -# 0.35.1 (2020-06-19) +## [0.35.1] - 2020-06-19 +[0.35.1]: https://github.com/crystal-lang/crystal/releases/0.35.1 -## Standard library +### Standard library -### Collections +#### Collections - Remove `Hash#each` type restriction to allow working with splats. ([#9456](https://github.com/crystal-lang/crystal/pull/9456), thanks @bcardiff) -### Networking +#### Networking - Revert `IO#write` changes in 0.35.0 and let it return Nil. ([#9469](https://github.com/crystal-lang/crystal/pull/9469), thanks @bcardiff) - Avoid leaking logging context in HTTP request handlers. ([#9494](https://github.com/crystal-lang/crystal/pull/9494), thanks @Blacksmoke16) -### Crypto +#### Crypto - Use less strict cipher compatibility for OpenSSL client context. ([#9459](https://github.com/crystal-lang/crystal/pull/9459), thanks @straight-shoota) - Fix `Digest::Base` block argument type restrictions. ([#9500](https://github.com/crystal-lang/crystal/pull/9500), thanks @straight-shoota) -### Logging +#### Logging - Fix `Log.context.set` docs for hash based data. ([#9470](https://github.com/crystal-lang/crystal/pull/9470), thanks @bcardiff) -## Compiler +### Compiler - Show warnings even if there are errors. ([#9461](https://github.com/crystal-lang/crystal/pull/9461), thanks @asterite) - Fix parsing of `{foo: X, typeof: Y}` type. ([#9453](https://github.com/crystal-lang/crystal/pull/9453), thanks @MakeNowJust) - Fix parsing of proc in hash `of` key type. ([#9458](https://github.com/crystal-lang/crystal/pull/9458), thanks @MakeNowJust) - Revert debug level information changes in specs to fix 32 bits builds. ([#9466](https://github.com/crystal-lang/crystal/pull/9466), thanks @bcardiff) -## Others +### Others - CI improvements and housekeeping. ([#9455](https://github.com/crystal-lang/crystal/pull/9455), thanks @bcardiff) - Code formatting. ([#9482](https://github.com/crystal-lang/crystal/pull/9482), thanks @MakeNowJust) -# 0.35.0 (2020-06-09) +## [0.35.0] - 2020-06-09 +[0.35.0]: https://github.com/crystal-lang/crystal/releases/0.35.0 -## Language changes +### Language changes - **(breaking-change)** Let `case when` be non-exhaustive, introduce `case in` as exhaustive. ([#9258](https://github.com/crystal-lang/crystal/pull/9258), [#9045](https://github.com/crystal-lang/crystal/pull/9045), thanks @asterite) - Allow `->@ivar.foo` and `->@@cvar.foo` expressions. ([#9268](https://github.com/crystal-lang/crystal/pull/9268), thanks @MakeNowJust) -### Macros +#### Macros - Allow executing OpAssign (`+=`, `||=`, etc.) inside macros. ([#9409](https://github.com/crystal-lang/crystal/pull/9409), thanks @asterite) -## Standard library +### Standard library - **(breaking-change)** Refactor to standardize on first argument for methods receiving `IO`. ([#9134](https://github.com/crystal-lang/crystal/pull/9134), [#9289](https://github.com/crystal-lang/crystal/pull/9289), [#9303](https://github.com/crystal-lang/crystal/pull/9303), [#9318](https://github.com/crystal-lang/crystal/pull/9318), thanks @straight-shoota, @bcardiff, @oprypin) - **(breaking-change)** Cleanup Digest and OpenSSL::Digest. ([#8426](https://github.com/crystal-lang/crystal/pull/8426), thanks @didactic-drunk) @@ -447,7 +452,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Make `NamedTuple#sorted_keys` public. ([#9263](https://github.com/crystal-lang/crystal/pull/9263), thanks @waj) - Fix example codes in multiple places. ([#9203](https://github.com/crystal-lang/crystal/pull/9203), thanks @maiha) -### Macros +#### Macros - **(breaking-change)** Remove top-level `assert_responds_to` macro. ([#9085](https://github.com/crystal-lang/crystal/pull/9085), thanks @bcardiff) - **(breaking-change)** Drop top-level `parallel` macro. ([#9097](https://github.com/crystal-lang/crystal/pull/9097), thanks @bcardiff) @@ -458,13 +463,13 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add `TypeNode` methods to check what "type" the node is. ([#9270](https://github.com/crystal-lang/crystal/pull/9270), thanks @Blacksmoke16) - Fix support `TypeNode.name(generic_args: false)` for generic instances. ([#9224](https://github.com/crystal-lang/crystal/pull/9224), thanks @Blacksmoke16) -### Numeric +#### Numeric - **(breaking-change)** Add `Int#digits`, reverse `BigInt#digits` result. ([#9383](https://github.com/crystal-lang/crystal/pull/9383), thanks @asterite) - Fix overflow checking for operations with mixed sign. ([#9403](https://github.com/crystal-lang/crystal/pull/9403), thanks @waj) - Add `BigInt#factorial` using GMP. ([#9132](https://github.com/crystal-lang/crystal/pull/9132), thanks @peheje) -### Text +#### Text - Add `String#titleize`. ([#9204](https://github.com/crystal-lang/crystal/pull/9204), thanks @hugopl) - Add `Regex#matches?` and `String#matches?`. ([#8989](https://github.com/crystal-lang/crystal/pull/8989), thanks @MakeNowJust) @@ -472,7 +477,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Improve docs examples regarding `Regex::MatchData`. ([#9010](https://github.com/crystal-lang/crystal/pull/9010), thanks @MakeNowJust) - Improve docs on `String` methods. ([#8447](https://github.com/crystal-lang/crystal/pull/8447), thanks @jan-zajic) -### Collections +#### Collections - **(breaking-change)** Add `Enumerable#first` with fallback block. ([#8999](https://github.com/crystal-lang/crystal/pull/8999), thanks @MakeNowJust) - Fix `Array#delete_at` bug with negative start index. ([#9399](https://github.com/crystal-lang/crystal/pull/9399), thanks @asterite) @@ -480,7 +485,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Make `Range#each` and `Range#reverse_each` work better with end/begin-less values. ([#9325](https://github.com/crystal-lang/crystal/pull/9325), thanks @asterite) - Improve docs on `Hash`. ([#8887](https://github.com/crystal-lang/crystal/pull/8887), thanks @rdp) -### Serialization +#### Serialization - **(breaking-change)** Deprecate `JSON.mapping` and `YAML.mapping`. ([#9272](https://github.com/crystal-lang/crystal/pull/9272), thanks @straight-shoota) - **(breaking-change)** Make `INI` a module. ([#9408](https://github.com/crystal-lang/crystal/pull/9408), thanks @j8r) @@ -494,13 +499,13 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Specify pkg-config name for `libxml2`. ([#9436](https://github.com/crystal-lang/crystal/pull/9436), thanks @Blacksmoke16) - Make YAML specs robust against libyaml 0.2.5. ([#9427](https://github.com/crystal-lang/crystal/pull/9427), thanks @jhass) -### Time +#### Time - **(breaking-change)** Support different number of fraction digits for RFC3339 time format. ([#9283](https://github.com/crystal-lang/crystal/pull/9283), thanks @waj) - Fix parsing AM/PM hours. ([#9334](https://github.com/crystal-lang/crystal/pull/9334), thanks @straight-shoota) - Improve `File.utime` precision from second to 100-nanosecond on Windows. ([#9344](https://github.com/crystal-lang/crystal/pull/9344), thanks @kubo) -### Files +#### Files - **(breaking-change)** Move `Flate`, `Gzip`, `Zip`, `Zlib` to `Compress`. ([#8886](https://github.com/crystal-lang/crystal/pull/8886), thanks @bcardiff) - **(breaking-change)** Cleanup `File` & `FileUtils`. ([#9175](https://github.com/crystal-lang/crystal/pull/9175), thanks @bcardiff) @@ -515,7 +520,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Implement `File#fsync` on Windows. ([#9257](https://github.com/crystal-lang/crystal/pull/9257), thanks @kubo) - Refactor `Path` regarding empty and `.`. ([#9137](https://github.com/crystal-lang/crystal/pull/9137), thanks @straight-shoota) -### Networking +#### Networking - **(breaking-change)** Make `IO#skip`, `IO#write` returns the number of bytes it skipped/written as `Int64`. ([#9233](https://github.com/crystal-lang/crystal/pull/9233), [#9363](https://github.com/crystal-lang/crystal/pull/9363), thanks @bcardiff) - **(breaking-change)** Improve error handling and logging in `HTTP::Server`. ([#9115](https://github.com/crystal-lang/crystal/pull/9115), [#9034](https://github.com/crystal-lang/crystal/pull/9034), thanks @waj, @straight-shoota) @@ -532,12 +537,12 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Remove `HTTP::Params::Builder#to_s`, use underlying `IO` directly. ([#9319](https://github.com/crystal-lang/crystal/pull/9319), thanks @straight-shoota) - Fixed some regular failing specs in multi-thread mode. ([#9412](https://github.com/crystal-lang/crystal/pull/9412), thanks @bcardiff) -### Crypto +#### Crypto - **(security)** Update SSL server secure defaults. ([#9026](https://github.com/crystal-lang/crystal/pull/9026), thanks @straight-shoota) - Add LibSSL `NO_TLS_V1_3` option. ([#9350](https://github.com/crystal-lang/crystal/pull/9350), thanks @lun-4) -### Logging +#### Logging - **(breaking-change)** Rename `Log::Severity::Warning` to `Warn`. Drop `Verbose`. Add `Trace` and `Notice`. ([#9293](https://github.com/crystal-lang/crystal/pull/9293), [#9107](https://github.com/crystal-lang/crystal/pull/9107), [#9316](https://github.com/crystal-lang/crystal/pull/9316), thanks @bcardiff, @paulcsmith) - **(breaking-change)** Allow local data on entries via `Log::Metadata` and redesign `Log::Context`. ([#9118](https://github.com/crystal-lang/crystal/pull/9118), [#9227](https://github.com/crystal-lang/crystal/pull/9227), [#9150](https://github.com/crystal-lang/crystal/pull/9150), [#9157](https://github.com/crystal-lang/crystal/pull/9157), thanks @bcardiff, @waj) @@ -553,11 +558,11 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Allow override context within logging calls. ([#9146](https://github.com/crystal-lang/crystal/pull/9146), thanks @bcardiff) - Check severity before backend. ([#9400](https://github.com/crystal-lang/crystal/pull/9400), thanks @asterite) -### Concurrency +#### Concurrency - **(breaking-change)** Drop `Concurrent::Future` and top-level methods `delay`, `future`, `lazy`. Use [crystal-community/future.cr](https://github.com/crystal-community/future.cr). ([#9093](https://github.com/crystal-lang/crystal/pull/9093), thanks @bcardiff) -### System +#### System - **(breaking-change)** Deprecate `Process#kill`, use `Process#signal`. ([#9006](https://github.com/crystal-lang/crystal/pull/9006), thanks @oprypin, @jan-zajic) - `Process` raises `IO::Error` (or subclasses). ([#9340](https://github.com/crystal-lang/crystal/pull/9340), thanks @waj) @@ -566,7 +571,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix compile-time checking of `dup3`/`clock_gettime` methods definition. ([#9407](https://github.com/crystal-lang/crystal/pull/9407), thanks @asterite) - Use `Int64` as portable `Process.pid` type. ([#9019](https://github.com/crystal-lang/crystal/pull/9019), thanks @oprypin) -### Runtime +#### Runtime - **(breaking-change)** Deprecate top-level `fork`. ([#9136](https://github.com/crystal-lang/crystal/pull/9136), thanks @bcardiff) - **(breaking-change)** Move `Debug` to `Crystal` namespace. ([#9176](https://github.com/crystal-lang/crystal/pull/9176), thanks @bcardiff) @@ -578,14 +583,14 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Move internal `CallStack` to `Exception::CallStack`. ([#9076](https://github.com/crystal-lang/crystal/pull/9076), thanks @bcardiff) - Specify pkg-config name for `libevent`. ([#9395](https://github.com/crystal-lang/crystal/pull/9395), thanks @jhass) -### Spec +#### Spec - Reference global Spec in `be_a` macro. ([#9066](https://github.com/crystal-lang/crystal/pull/9066), thanks @asterite) - Add `-h` short flag to spec runner. ([#9164](https://github.com/crystal-lang/crystal/pull/9164), thanks @straight-shoota) - Fix `crystal spec` file paths on Windows. ([#9234](https://github.com/crystal-lang/crystal/pull/9234), thanks @oprypin) - Refactor spec hooks. ([#9090](https://github.com/crystal-lang/crystal/pull/9090), thanks @straight-shoota) -## Compiler +### Compiler - **(breaking-change)** Improve compiler single-file run syntax to make it shebang-friendly `#!`. ([#9171](https://github.com/crystal-lang/crystal/pull/9171), thanks @RX14) - **(breaking-change)** Use `Process.quote` for `crystal env` output. ([#9428](https://github.com/crystal-lang/crystal/pull/9428), thanks @MakeNowJust) @@ -609,7 +614,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor and improvements on spec_helper. ([#9367](https://github.com/crystal-lang/crystal/pull/9367), [#9059](https://github.com/crystal-lang/crystal/pull/9059), [#9393](https://github.com/crystal-lang/crystal/pull/9393), [#9351](https://github.com/crystal-lang/crystal/pull/9351), [#9402](https://github.com/crystal-lang/crystal/pull/9402), thanks @straight-shoota, @jhass, @oprypin) - Split general ABI specs from x86_64-specific ones, run on every platform. ([#9384](https://github.com/crystal-lang/crystal/pull/9384), thanks @oprypin) -### Language semantics +#### Language semantics - Fix `RegexLiteral#to_s` output when first character of literal is whitespace. ([#9017](https://github.com/crystal-lang/crystal/pull/9017), thanks @MakeNowJust) - Fix autocasting in multidispatch. ([#9004](https://github.com/crystal-lang/crystal/pull/9004), thanks @asterite) @@ -621,21 +626,21 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Make autocasting work in default values against unions. ([#9366](https://github.com/crystal-lang/crystal/pull/9366), thanks @asterite) - Skip no closure check for non-Crystal procs. ([#9248](https://github.com/crystal-lang/crystal/pull/9248), thanks @jhass) -### Debugger +#### Debugger - Improve debugging support. ([#8538](https://github.com/crystal-lang/crystal/pull/8538), thanks @skuznetsov) - Move recent additions to `DIBuilder` to `LLVMExt`. ([#9114](https://github.com/crystal-lang/crystal/pull/9114), thanks @bcardiff) -## Tools +### Tools -### Formatter +#### Formatter - Fix formatting of regex after some comments. ([#9109](https://github.com/crystal-lang/crystal/pull/9109), thanks @MakeNowJust) - Fix formatting of `&.!`. ([#9391](https://github.com/crystal-lang/crystal/pull/9391), thanks @MakeNowJust) - Avoid crash on heredoc with interpolations. ([#9382](https://github.com/crystal-lang/crystal/pull/9382), thanks @MakeNowJust) - Refactor: code clean-up. ([#9231](https://github.com/crystal-lang/crystal/pull/9231), thanks @MakeNowJust) -### Doc generator +#### Doc generator - Fix links to methods with `String` default values. ([#9200](https://github.com/crystal-lang/crystal/pull/9200), thanks @bcardiff) - Fix syntax highlighting of heredoc. ([#9396](https://github.com/crystal-lang/crystal/pull/9396), thanks @MakeNowJust) @@ -649,12 +654,12 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor `is_crystal_repo` based on project name. ([#9070](https://github.com/crystal-lang/crystal/pull/9070), thanks @straight-shoota) - Refactor `Docs::Generator` source link generation. ([#9119](https://github.com/crystal-lang/crystal/pull/9119), [#9305](https://github.com/crystal-lang/crystal/pull/9305), thanks @straight-shoota) -### Playground +#### Playground - Allow building compiler without 'playground', to avoid dependency on sockets. ([#9031](https://github.com/crystal-lang/crystal/pull/9031), thanks @oprypin) - Add support to jquery version 3. ([#9028](https://github.com/crystal-lang/crystal/pull/9028), thanks @deiv) -## Others +### Others - CI improvements and housekeeping. ([#9012](https://github.com/crystal-lang/crystal/pull/9012), [#9129](https://github.com/crystal-lang/crystal/pull/9129), [#9242](https://github.com/crystal-lang/crystal/pull/9242), [#9370](https://github.com/crystal-lang/crystal/pull/9370), thanks @bcardiff) - Update to Shards 0.11.1. ([#9446](https://github.com/crystal-lang/crystal/pull/9446), thanks @bcardiff) @@ -670,14 +675,15 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Update README. ([#9225](https://github.com/crystal-lang/crystal/pull/9225), [#9163](https://github.com/crystal-lang/crystal/pull/9163), thanks @danimiba, @straight-shoota) - Fix LICENSE and add NOTICE file. ([#3903](https://github.com/crystal-lang/crystal/pull/3903), thanks @MakeNowJust) -# 0.34.0 (2020-04-06) +## [0.34.0] - 2020-04-06 +[0.34.0]: https://github.com/crystal-lang/crystal/releases/0.34.0 -## Language changes +### Language changes - **(breaking-change)** Exhaustive `case` expression check added, for now it produces warnings. ([#8424](https://github.com/crystal-lang/crystal/pull/8424), [#8962](https://github.com/crystal-lang/crystal/pull/8962), thanks @asterite, @Sija) - Let `Proc(T)` be used as a `Proc(Nil)`. ([#8969](https://github.com/crystal-lang/crystal/pull/8969), [#8970](https://github.com/crystal-lang/crystal/pull/8970), thanks @asterite) -## Standard library +### Standard library - **(breaking-change)** Replace `Errno`, `WinError`, `IO::Timeout` with `RuntimeError`, `IO::TimeoutError`, `IO::Error`, `File::Error`, `Socket::Error`, and subclasses. ([#8885](https://github.com/crystal-lang/crystal/pull/8885), thanks @waj) - **(breaking-change)** Replace `Logger` module in favor of `Log` module. ([#8847](https://github.com/crystal-lang/crystal/pull/8847), [#8976](https://github.com/crystal-lang/crystal/pull/8976), thanks @bcardiff) @@ -690,7 +696,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixed docs broken link to ruby's prettyprint source. ([#8915](https://github.com/crystal-lang/crystal/pull/8915), thanks @matthin) - Update `OptionParser` example to exit on `--help`. ([#8927](https://github.com/crystal-lang/crystal/pull/8927), thanks @vlazar) -### Numeric +#### Numeric - Fixed `Float32#to_s` corner-case. ([#8838](https://github.com/crystal-lang/crystal/pull/8838), thanks @toddsundsted) - Fixed make `Big*#to_u` raise on negative values. ([#8826](https://github.com/crystal-lang/crystal/pull/8826), thanks @Sija) @@ -699,7 +705,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add `Int#bit_length` and `BigInt#bit_length`. ([#8924](https://github.com/crystal-lang/crystal/pull/8924), [#8931](https://github.com/crystal-lang/crystal/pull/,8931), thanks @asterite) - Fixed docs of `Int#gcd`. ([#8894](https://github.com/crystal-lang/crystal/pull/8894), thanks @nard-tech) -### Text +#### Text - **(breaking-change)** Deprecate top-level `with_color` in favor of `Colorize.with`. ([#8892](https://github.com/crystal-lang/crystal/pull/8892), [#8958](https://github.com/crystal-lang/crystal/pull/8958), thanks @bcardiff, @oprypin) - Add overloads for `String#ljust`, `String#rjust` and `String#center` that take an `IO`. ([#8923](https://github.com/crystal-lang/crystal/pull/8923), thanks @asterite) @@ -708,14 +714,14 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Revert deprecation of `String#codepoint_at`. ([#8902](https://github.com/crystal-lang/crystal/pull/8902), thanks @vlazar) - Move `Iconv` to `Crystal` namespace. ([#8890](https://github.com/crystal-lang/crystal/pull/8890), thanks @bcardiff) -### Collections +#### Collections - Fixed make `Range#size` raise on an open range. ([#8829](https://github.com/crystal-lang/crystal/pull/8829), thanks @Sija) - Add `Enumerable#empty?`. ([#8960](https://github.com/crystal-lang/crystal/pull/8960), thanks @Sija) - **(performance)** Optimized Implementation of `Array#fill` for zero Values. ([#8903](https://github.com/crystal-lang/crystal/pull/8903), thanks @toddsundsted) - Refactor `Reflect` to an `Enumerable` private definition. ([#8884](https://github.com/crystal-lang/crystal/pull/8884), thanks @bcardiff) -### Serialization +#### Serialization - **(breaking-change)** Rename `YAML::Builder.new` with block to `YAML::Builder.build`. ([#8896](https://github.com/crystal-lang/crystal/pull/8896), thanks @straight-shoota) - Add `XML.build_fragment`. ([#8813](https://github.com/crystal-lang/crystal/pull/8813), thanks @straight-shoota) @@ -724,19 +730,19 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Call to `IO#flush` on `CSV`, `INI`, `JSON`, `XML`, and `YAML` builders. ([#8876](https://github.com/crystal-lang/crystal/pull/8876), thanks @asterite) - Add docs to `Object.from_yaml`. ([#8800](https://github.com/crystal-lang/crystal/pull/8800), thanks @wowinter13) -### Time +#### Time - **(breaking-change)** Improve `Time::Span` initialization API with mandatory named arguments. ([#8257](https://github.com/crystal-lang/crystal/pull/8257), [#8857](https://github.com/crystal-lang/crystal/pull/8857), thanks @dnamsons, @bcardiff) - Add `Time::Span#total_microseconds`. ([#8966](https://github.com/crystal-lang/crystal/pull/8966), thanks @vlazar) -### Files +#### Files - Fixed multi-thread race condition by setting `fd` to `-1` on closed `File`/`Socket`. ([#8873](https://github.com/crystal-lang/crystal/pull/8873), thanks @bcardiff) - Fixed `File.dirname` with unicode chars. ([#8911](https://github.com/crystal-lang/crystal/pull/8911), thanks @asterite) - Add `IO::Buffered#flush_on_newline` back and set it to true for non-tty. ([#8935](https://github.com/crystal-lang/crystal/pull/8935), thanks @asterite) - Forward missing methods of `IO::Hexdump` to underlying `IO`. ([#8908](https://github.com/crystal-lang/crystal/pull/8908), thanks @carlhoerberg) -### Networking +#### Networking - **(breaking-change)** Correctly support WebSocket close codes. ([#8975](https://github.com/crystal-lang/crystal/pull/8975), [#8981](https://github.com/crystal-lang/crystal/pull/8981), thanks @RX14, @Sija) - Make `HTTP::Client` return empty `body_io` if content-length is zero. ([#8503](https://github.com/crystal-lang/crystal/pull/8503), thanks @asterite) @@ -746,21 +752,21 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add type annotation to `tls` argument in `HTTP`. ([#8678](https://github.com/crystal-lang/crystal/pull/8678), thanks @j8r) - Add `Location` to `HTTP::Request` common header names. ([#8992](https://github.com/crystal-lang/crystal/pull/8992), thanks @mamantoha) -### Concurrency +#### Concurrency - Add docs on `Future` regarding exceptions. ([#8860](https://github.com/crystal-lang/crystal/pull/8860), thanks @rdp) - Disable occasionally failing `Thread` specs on musl. ([#8801](https://github.com/crystal-lang/crystal/pull/8801), thanks @straight-shoota) -### System +#### System - Fixed typo on `src/signal.cr`. ([#8805](https://github.com/crystal-lang/crystal/pull/8805), thanks @lbguilherme) -### Runtime +#### Runtime - Fixed exceptions not being inspectable when running binary from PATH. ([#8807](https://github.com/crystal-lang/crystal/pull/8807), thanks @willhbr) - Move `AtExitHandlers` to `Crystal` namespace. ([#8883](https://github.com/crystal-lang/crystal/pull/8883), thanks @bcardiff) -## Compiler +### Compiler - **(breaking-change)** Drop `disable_overflow` compiler flag. ([#8772](https://github.com/crystal-lang/crystal/pull/8772), thanks @Sija) - Fixed url in "can't infer block return type" error message. ([#8869](https://github.com/crystal-lang/crystal/pull/8869), thanks @nilium) @@ -772,36 +778,37 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add support for LLVM 10. ([#8940](https://github.com/crystal-lang/crystal/pull/8940), thanks @RX14) - Remove redundant calls to `Object.to_s` in interpolation in compiler's code. ([#8947](https://github.com/crystal-lang/crystal/pull/8947), thanks @veelenga) -### Language semantics +#### Language semantics - Type as `NoReturn` if calling method on abstract class with no concrete subclasses. ([#8870](https://github.com/crystal-lang/crystal/pull/8870), thanks @asterite) -## Tools +### Tools - Add `crystal init` name validation. ([#8737](https://github.com/crystal-lang/crystal/pull/8737), thanks @straight-shoota) -### Doc generator +#### Doc generator - Show warnings on docs command. ([#8880](https://github.com/crystal-lang/crystal/pull/8880), thanks @bcardiff) -## Others +### Others - CI improvements and housekeeping. ([#8804](https://github.com/crystal-lang/crystal/pull/8804), [#8811](https://github.com/crystal-lang/crystal/pull/8811), [#8982](https://github.com/crystal-lang/crystal/pull/8982), thanks @bcardiff, @oprypin) - Update to Shards 0.10.0. ([#8988](https://github.com/crystal-lang/crystal/pull/8988), thanks @bcardiff) - Fix `pretty_json` sample. ([#8816](https://github.com/crystal-lang/crystal/pull/8816), thanks @asterite) - Fix typos throughout the codebase. ([#8971](https://github.com/crystal-lang/crystal/pull/8971), thanks @Sija) -# 0.33.0 (2020-02-14) +## [0.33.0] - 2020-02-14 +[0.33.0]: https://github.com/crystal-lang/crystal/releases/0.33.0 -## Language changes +### Language changes - Allow `timeout` in select statements. ([#8506](https://github.com/crystal-lang/crystal/pull/8506), [#8705](https://github.com/crystal-lang/crystal/pull/8705), thanks @bcardiff, @firejox, @vlazar) -### Macros +#### Macros - Add `TypeNode#name(generic_args : BoolLiteral)` to return `TypeNode`'s name with or without type vars. ([#8483](https://github.com/crystal-lang/crystal/pull/8483), thanks @Blacksmoke16) -## Standard library +### Standard library - **(breaking-change)** Remove several previously deprecated methods and modules: `PartialComparable`, `Crypto::Bcrypt::Password#==`, `HTTP::Server::Response#respond_with_error`, `JSON::PullParser::Kind#==`, `Symbol#==(JSON::PullParser::Kind)`, `JSON::Token#type`, `String#at`, `Time.new`, `Time.now`, `Time.utc_now`, `URI.escape`, `URI.unescape`. ([#8646](https://github.com/crystal-lang/crystal/pull/8646), [#8596](https://github.com/crystal-lang/crystal/pull/8596), thanks @bcardiff, @Blacksmoke16) - Fixed docs wording. ([#8606](https://github.com/crystal-lang/crystal/pull/8606), [#8784](https://github.com/crystal-lang/crystal/pull/8784), thanks @fxn) @@ -812,32 +819,32 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixed specs of `Colorize` on dumb terminal. ([#8673](https://github.com/crystal-lang/crystal/pull/8673), thanks @oprypin) - Fixed some specs on Win32. ([#8670](https://github.com/crystal-lang/crystal/pull/8670), thanks @straight-shoota) -### Numeric +#### Numeric - Add `BigInt#unsafe_shr`. ([#8763](https://github.com/crystal-lang/crystal/pull/8763), thanks @asterite) - Refactor `Float#fdiv` to use binary primitive. ([#8662](https://github.com/crystal-lang/crystal/pull/8662), thanks @bcardiff) -### Text +#### Text - Fixed `\u0000` wrongly added on `String#sub(Hash)` replaces last char. ([#8644](https://github.com/crystal-lang/crystal/pull/8644), thanks @mimame) -### Collections +#### Collections - Fixed `Enumerable#zip` to work with union types. ([#8621](https://github.com/crystal-lang/crystal/pull/8621), thanks @asterite) - Fixed docs regarding `Hash`'s `initial_capacity`. ([#8569](https://github.com/crystal-lang/crystal/pull/8569), thanks @r00ster91) -### Serialization +#### Serialization - Improved JSON deserialization into union types. ([#8689](https://github.com/crystal-lang/crystal/pull/8689), thanks @KimBurgess) - Fixed expected error message in libxml2 error spec. ([#8699](https://github.com/crystal-lang/crystal/pull/8699), thanks @straight-shoota) - Fixed `JSON::PullParser` overflow handling. ([#8698](https://github.com/crystal-lang/crystal/pull/8698), thanks @KimBurgess) - Fixed `JSON::Any#dig?`/`YAML::Any#dig?` on non-structure values. ([#8745](https://github.com/crystal-lang/crystal/pull/8745), thanks @Sija) -### Time +#### Time - Fixed `Time#shift` over date boundaries with zone offset. ([#8742](https://github.com/crystal-lang/crystal/pull/8742), thanks @straight-shoota) -### Files +#### Files - **(breaking-change)** Deprecate `File::Info#owner`, and `File::Info#group`; use `owner_id`, and `group_id`. ([#8007](https://github.com/crystal-lang/crystal/pull/8007), thanks @j8r) - Fixed `Path.new` receiving `Path` as first argument. ([#8753](https://github.com/crystal-lang/crystal/pull/8753), thanks @straight-shoota) @@ -846,7 +853,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor `Dir.mkdir_p` to use `Path#each_parent` and make it work on Win32. ([#8668](https://github.com/crystal-lang/crystal/pull/8668), thanks @straight-shoota) - Fixed `IO::MultiWriter` specs to close file before reading/deleting it. ([#8674](https://github.com/crystal-lang/crystal/pull/8674), thanks @oprypin) -### Networking +#### Networking - Fixed invalid call to libevent and race conditions on closed `IO` when resuming readable/writable event. ([#8707](https://github.com/crystal-lang/crystal/pull/8707), [#8733](https://github.com/crystal-lang/crystal/pull/8733), thanks @bcardiff) - Fixed unexpected EOF in terminated SSL connection. ([#8540](https://github.com/crystal-lang/crystal/pull/8540), thanks @rdp) @@ -857,24 +864,24 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Remove fixed date in spec. ([#8640](https://github.com/crystal-lang/crystal/pull/8640), thanks @bcardiff) - Remove non-portable error message in `TCPServer` spec. ([#8702](https://github.com/crystal-lang/crystal/pull/8702), thanks @straight-shoota) -### Crypto +#### Crypto - Add `Crypto::Bcrypt::Password` check for invalid hash value. ([#6467](https://github.com/crystal-lang/crystal/pull/6467), thanks @miketheman) - Improve documentation for `Random::Secure`. ([#8484](https://github.com/crystal-lang/crystal/pull/8484), thanks @straight-shoota) -### Concurrency +#### Concurrency - Fixed `Future(Nil)` when the block raises. ([#8650](https://github.com/crystal-lang/crystal/pull/8650), thanks @lbguilherme) - Fixed `IO` closing in multi-thread mode. ([#8733](https://github.com/crystal-lang/crystal/pull/8733), thanks @bcardiff) - Fixed some regular failing specs in multi-thread mode. ([#8592](https://github.com/crystal-lang/crystal/pull/8592), [#8643](https://github.com/crystal-lang/crystal/pull/8643), [#8724](https://github.com/crystal-lang/crystal/pull/8724), [#8761](https://github.com/crystal-lang/crystal/pull/8761), thanks @bcardiff) - Add docs to `Fiber`. ([#8739](https://github.com/crystal-lang/crystal/pull/8739), thanks @straight-shoota) -### System +#### System - Enable `system` module for Win32 in prelude. ([#8661](https://github.com/crystal-lang/crystal/pull/8661), thanks @straight-shoota) - Handle exceptions raised at `__crystal_sigfault_handler`. ([#8743](https://github.com/crystal-lang/crystal/pull/8743), thanks @waj) -### Runtime +#### Runtime - Fixed wrongly collected exception object by the GC. Ensure `LibUnwind::Exception` struct is not atomic. ([#8728](https://github.com/crystal-lang/crystal/pull/8728), thanks @waj) - Fixed reporting of non-statement rows in DWARF backtrace. ([#8499](https://github.com/crystal-lang/crystal/pull/8499), thanks @rdp) @@ -882,12 +889,12 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Try to open stdio in non-blocking mode. ([#8787](https://github.com/crystal-lang/crystal/pull/8787), thanks @waj) - Allow `Crystal::System.print_error` to use `printf` like format. ([#8786](https://github.com/crystal-lang/crystal/pull/8786), thanks @bcardiff) -### Spec +#### Spec - **(breaking-change)** Remove previously deprecated spec method `assert`. ([#8767](https://github.com/crystal-lang/crystal/pull/8767), thanks @Blacksmoke16) - `Spec::JUnitFormatter` output and options enhancements. ([#8599](https://github.com/crystal-lang/crystal/pull/8599), [#8692](https://github.com/crystal-lang/crystal/pull/8692), thanks @Sija, @bcardiff) -## Compiler +### Compiler - **(breaking-change)** Drop support for previously deprecated comma separators in enums and other cleanups. ([#8657](https://github.com/crystal-lang/crystal/pull/8657), thanks @bcardiff) - **(breaking-change)** Drop uppercase F32 and F64 float number suffixes. ([#8782](https://github.com/crystal-lang/crystal/pull/8782), thanks @rhysd) @@ -899,17 +906,17 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixed parser bug macro with "eenum" in it. ([#8760](https://github.com/crystal-lang/crystal/pull/8760), thanks @asterite) - Change `CRYSTAL_PATH` to allow shards to override std-lib. ([#8752](https://github.com/crystal-lang/crystal/pull/8752), thanks @bcardiff) -### Language semantics +#### Language semantics - Fixed missing virtualization of `Proc` pointer. ([#8757](https://github.com/crystal-lang/crystal/pull/8757), thanks @asterite) - Fixed type of vars after `begin`/`rescue` if all `rescue` are unreachable. ([#8758](https://github.com/crystal-lang/crystal/pull/8758), thanks @asterite) - Fixed visibility propagation to macro expansions in all cases. ([#8762](https://github.com/crystal-lang/crystal/pull/8762), [#8796](https://github.com/crystal-lang/crystal/pull/8796), thanks @asterite) -## Tools +### Tools - Update `crystal init` to handle `.`. ([#8681](https://github.com/crystal-lang/crystal/pull/8681), thanks @jethrodaniel) -### Formatter +#### Formatter - Fixed indent after comment inside indexer. ([#8627](https://github.com/crystal-lang/crystal/pull/8627), thanks @asterite) - Fixed indent of comments at the end of a proc literal. ([#8778](https://github.com/crystal-lang/crystal/pull/8778), thanks @asterite) @@ -917,12 +924,12 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixed crash when formatting `exp.!`. ([#8768](https://github.com/crystal-lang/crystal/pull/8768), thanks @asterite) - Removes unnecessary escape sequences. ([#8619](https://github.com/crystal-lang/crystal/pull/8619), thanks @RX14) -### Doc generator +#### Doc generator - **(breaking-change)** Deprecate `ditto` and `nodoc` in favor of `:ditto:` and `:nodoc:`. ([#6362](https://github.com/crystal-lang/crystal/pull/6362), thanks @j8r) - Skip creation of `docs/` dir when not needed. ([#8718](https://github.com/crystal-lang/crystal/pull/8718), thanks @Sija) -## Others +### Others - CI improvements and housekeeping. ([#8580](https://github.com/crystal-lang/crystal/pull/8580), [#8597](https://github.com/crystal-lang/crystal/pull/8597), [#8679](https://github.com/crystal-lang/crystal/pull/8679), [#8779](https://github.com/crystal-lang/crystal/pull/8779), thanks @bcardiff, @j8r) - Add Windows CI using GitHub Actions. ([#8676](https://github.com/crystal-lang/crystal/pull/8676), thanks @oprypin) @@ -933,32 +940,34 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add CircleCI test summaries. ([#8617](https://github.com/crystal-lang/crystal/pull/8617), thanks @Sija) - Add helper scripts to identify working std-lib specs on Win32. ([#8664](https://github.com/crystal-lang/crystal/pull/8664), thanks @straight-shoota) -# 0.32.1 (2019-12-18) +## [0.32.1] - 2019-12-18 +[0.32.1]: https://github.com/crystal-lang/crystal/releases/0.32.1 -## Standard library +### Standard library -### Collections +#### Collections - Fixed docs of `Enumerable#each_cons_pair` and `Iterator#cons_pair`. ([#8585](https://github.com/crystal-lang/crystal/pull/8585), thanks @arcage) -### Networking +#### Networking - Fixed `HTTP::WebSocket`'s `on_close` callback is called for all errors. ([#8552](https://github.com/crystal-lang/crystal/pull/8552), thanks @stakach) - Fixed sporadic failure in specs with OpenSSL 1.1+. ([#8582](https://github.com/crystal-lang/crystal/pull/8582), thanks @rdp) -## Compiler +### Compiler -### Language semantics +#### Language semantics - Combine contiguous string literals before string interpolation. ([#8581](https://github.com/crystal-lang/crystal/pull/8581), thanks @asterite) -# 0.32.0 (2019-12-11) +## [0.32.0] - 2019-12-11 +[0.32.0]: https://github.com/crystal-lang/crystal/releases/0.32.0 -## Language changes +### Language changes - Allow boolean negation to be written also as a regular method call `expr.!`. ([#8445](https://github.com/crystal-lang/crystal/pull/8445), thanks @jan-zajic) -### Macros +#### Macros - Add `TypeNode#class_vars` to list class variables of a type in a macro. ([#8405](https://github.com/crystal-lang/crystal/pull/8405), thanks @jan-zajic) - Add `TypeNode#includers` to get an array of types a module is directly included in. ([#8133](https://github.com/crystal-lang/crystal/pull/8133), thanks @Blacksmoke16) @@ -966,7 +975,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add docs for `ArrayLiteral#reduce`. ([#8379](https://github.com/crystal-lang/crystal/pull/8379), thanks @jan-zajic) - Add `lower:` named argument to `StringLiteral#camelcase`. ([#8429](https://github.com/crystal-lang/crystal/pull/8429), thanks @Blacksmoke16) -## Standard library +### Standard library - **(breaking-change)** Remove `Readline` from std-lib. It's now available as a shard at [crystal-lang/crystal-readline](https://www.github.com/crystal-lang/crystal-readline) ([#8364](https://github.com/crystal-lang/crystal/pull/8364), thanks @ftarulla) - Move `Number#clamp` to `Comparable#clamp`. ([#8522](https://github.com/crystal-lang/crystal/pull/8522), thanks @wontruefree) @@ -978,14 +987,14 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add docs for pseudo methods. ([#8327](https://github.com/crystal-lang/crystal/pull/8327), [#8491](https://github.com/crystal-lang/crystal/pull/8491), thanks @straight-shoota) - Code cleanups. ([#8270](https://github.com/crystal-lang/crystal/pull/8270), [#8368](https://github.com/crystal-lang/crystal/pull/8368), [#8404](https://github.com/crystal-lang/crystal/pull/8404), thanks @asterite, @vlazar, @arcage) -### Numeric +#### Numeric - Fixed `%` and `Int#remainder` edge case of min int value against `-1`. ([#8321](https://github.com/crystal-lang/crystal/pull/8321), thanks @asterite) - Fixed `Int#gcd` types edge case and improve performance. ([#7996](https://github.com/crystal-lang/crystal/pull/7996), [#8419](https://github.com/crystal-lang/crystal/pull/8419), thanks @yxhuvud, @j8r) - Add `Int#bits` for accessing bit ranges. ([#8165](https://github.com/crystal-lang/crystal/pull/8165), thanks @stakach) - Allow `Number#round` with `UInt` argument. ([#8361](https://github.com/crystal-lang/crystal/pull/8361), thanks @igor-alexandrov) -### Text +#### Text - **(breaking-change)** Implement string interpolation as a call to `String.interpolation`. ([#8400](https://github.com/crystal-lang/crystal/pull/8400), thanks @asterite) - **(breaking-change)** Deprecate `String#codepoint_at`, use `char_at(index).ord`. ([#8475](https://github.com/crystal-lang/crystal/pull/8475), thanks @vlazar) @@ -996,7 +1005,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add docs in `Levenshtein` module. ([#8386](https://github.com/crystal-lang/crystal/pull/8386), thanks @katafrakt) - Add docs to `Regex::Options`. ([#8448](https://github.com/crystal-lang/crystal/pull/8448), thanks @jan-zajic) -### Collections +#### Collections - **(breaking-change)** Deprecate `Enumerable#grep`, use `Enumerable#select`. ([#8452](https://github.com/crystal-lang/crystal/pull/8452), thanks @j8r) - Fixed `Enumerable#minmax`, `#min`, `#max` for partially comparable values. ([#8490](https://github.com/crystal-lang/crystal/pull/8490), thanks @TedTran2019) @@ -1011,21 +1020,21 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add docs to `Hash.select`. ([#8391](https://github.com/crystal-lang/crystal/pull/8391), thanks @jan-zajic) - Add docs and specs to `Enumerable.reduce`. ([#8378](https://github.com/crystal-lang/crystal/pull/8378), thanks @jan-zajic) -### Serialization +#### Serialization - **(breaking-change)** Make `XML::Reader#expand` raise, introduce `XML::Reader#expand?` for former behavior. ([#8186](https://github.com/crystal-lang/crystal/pull/8186), thanks @Blacksmoke16) - Allow `JSON.mapping` & `YAML.mapping` converter attribute to be applied to `Array` and `Hash`. ([#8156](https://github.com/crystal-lang/crystal/pull/8156), thanks @rodrigopinto) - Add `use_json_discriminator` and `use_yaml_discriminator` to choose type based on property value. ([#8406](https://github.com/crystal-lang/crystal/pull/8406), thanks @asterite) - Remove return type `self` restriction from `Object.from_json` and `Object.from_yaml`. ([#8489](https://github.com/crystal-lang/crystal/pull/8489), thanks @straight-shoota) -### Files +#### Files - **(breaking-change)** Remove expand home (`~`) by default in `File.expand_path` and `Path#expand`, now opt-in argument. ([#7903](https://github.com/crystal-lang/crystal/pull/7903), thanks @didactic-drunk) - Fixed bugs in `Path` regarding `#dirname`, `#each_part`, `#each_parent`. ([#8415](https://github.com/crystal-lang/crystal/pull/8415), thanks @jan-zajic) - Fixed `GZip::Reader` and `GZip::Writer` to handle large data sizes. ([#8421](https://github.com/crystal-lang/crystal/pull/8421), thanks @straight-shoota) - Fixed `File::Info#same_file?` by providing access to 64 bit inode numbers. ([#8355](https://github.com/crystal-lang/crystal/pull/8355), thanks @didactic-drunk) -### Networking +#### Networking - Fixed `HTTP::Response#mime_type` returns `nil` on empty `Content-Type` header. ([#8464](https://github.com/crystal-lang/crystal/pull/8464), thanks @Sija) - Fixed handling of unidirectional SSL servers hang. ([#8481](https://github.com/crystal-lang/crystal/pull/8481), thanks @rdp) @@ -1033,11 +1042,11 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Updated mime type of `.js` files to `text/javascript` and include `image/webp`. ([#8342](https://github.com/crystal-lang/crystal/pull/8342), thanks @mamantoha) - Refactor websocket protocol GUID string. ([#8339](https://github.com/crystal-lang/crystal/pull/8339), thanks @vlazar) -### Crypto +#### Crypto - **(breaking-change)** Enforce single-line results of `OpenSSL::DigestBase#base64digest` via `Base64.strict_encode`. ([#8215](https://github.com/crystal-lang/crystal/pull/8215), thanks @j8r) -### Concurrency +#### Concurrency - Fixed `Channel` successful sent and raise behavior. ([#8284](https://github.com/crystal-lang/crystal/pull/8284), thanks @firejox) - Fixed `Channel#close` to be thread-safe. ([#8249](https://github.com/crystal-lang/crystal/pull/8249), thanks @firejox) @@ -1047,19 +1056,19 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add docs to `Channel#send` and `Channel#close`. ([#8356](https://github.com/crystal-lang/crystal/pull/8356), thanks @lbarasti) - Fixed `Thread#gc_thread_handler` for Windows support. ([#8519](https://github.com/crystal-lang/crystal/pull/8519), thanks @straight-shoota) -### System +#### System - Don't close pipes used for signal handlers in multi-thread mode. ([#8465](https://github.com/crystal-lang/crystal/pull/8465), thanks @waj) - Fixed thread initialization on OpenBSD. ([#8293](https://github.com/crystal-lang/crystal/pull/8293), thanks @wmoxam) - Implement fibers for win32. ([#7995](https://github.com/crystal-lang/crystal/pull/7995), [#8513](https://github.com/crystal-lang/crystal/pull/8513), thanks @straight-shoota, @firejox) -### Runtime +#### Runtime - Fixed fiber initialization on `-Dgc_none -Dpreview_mt`. ([#8280](https://github.com/crystal-lang/crystal/pull/8280), thanks @bcardiff) - Add GC profiling stats and warning bindings. ([#8281](https://github.com/crystal-lang/crystal/pull/8281), thanks @bcardiff, @benoist) - Refactor `callstack_spec`. ([#8308](https://github.com/crystal-lang/crystal/pull/8308), [#8395](https://github.com/crystal-lang/crystal/pull/8395), thanks @straight-shoota, @Sija) -### Spec +#### Spec - Fixed `--fail-fast` behaviour. ([#8453](https://github.com/crystal-lang/crystal/pull/8453), thanks @asterite) - Add before, after, and around hooks. ([#8302](https://github.com/crystal-lang/crystal/pull/8302), thanks @asterite) @@ -1068,7 +1077,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add specs for `Spec` filters. ([#8242](https://github.com/crystal-lang/crystal/pull/8242), thanks @Fryguy) - Add ability to tag specs. ([#8068](https://github.com/crystal-lang/crystal/pull/8068), thanks @Fryguy) -## Compiler +### Compiler - Fixed musl libc detection (Alpine 3.10 regression bug). ([#8330](https://github.com/crystal-lang/crystal/pull/8330), thanks @straight-shoota) - Fixed pragmas handling in macros. ([#8256](https://github.com/crystal-lang/crystal/pull/8256), thanks @asterite) @@ -1084,20 +1093,20 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Show full path of locally compiled Crystal. ([#8486](https://github.com/crystal-lang/crystal/pull/8486), thanks @rdp) - Code cleanups. ([#8460](https://github.com/crystal-lang/crystal/pull/8460), thanks @veelenga) -### Language semantics +#### Language semantics - Fixed method lookup priority when type alias of union is used. ([#8258](https://github.com/crystal-lang/crystal/pull/8258), thanks @asterite) - Fixed visibility modifiers in virtual types. ([#8562](https://github.com/crystal-lang/crystal/pull/8562), thanks @asterite) - Fixed `sizeof(Bool)`. ([#8273](https://github.com/crystal-lang/crystal/pull/8273), thanks @asterite) -## Tools +### Tools -### Formatter +#### Formatter - Fixed indent in arguments. ([#8315](https://github.com/crystal-lang/crystal/pull/8315), thanks @MakeNowJust) - Fixed crash related to parenthesis on generic types. ([#8501](https://github.com/crystal-lang/crystal/pull/8501), thanks @asterite) -### Doc generator +#### Doc generator - Fixed underscore type restriction in doc generator. ([#8331](https://github.com/crystal-lang/crystal/pull/8331), thanks @straight-shoota) - Correctly attach docs through multiple macro invocations. ([#8502](https://github.com/crystal-lang/crystal/pull/8502), thanks @asterite) @@ -1107,62 +1116,64 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add clickable anchor icon next to headings. ([#8344](https://github.com/crystal-lang/crystal/pull/8344), thanks @Blacksmoke16) - Use `&` instead of `&block` for signature of yielding method. ([#8394](https://github.com/crystal-lang/crystal/pull/8394), thanks @j8r) -### Playground +#### Playground - Do not collapse whitespaces in playground sidebar. ([#8528](https://github.com/crystal-lang/crystal/pull/8528), thanks @hugopl) -## Others +### Others - CI improvements and housekeeping. ([#8210](https://github.com/crystal-lang/crystal/pull/8210), [#8251](https://github.com/crystal-lang/crystal/pull/8251), [#8283](https://github.com/crystal-lang/crystal/pull/8283), [#8439](https://github.com/crystal-lang/crystal/pull/8439), [#8510](https://github.com/crystal-lang/crystal/pull/8510), thanks @bcardiff) - Update base docker images to `bionic` and LLVM 8.0. ([#8442](https://github.com/crystal-lang/crystal/pull/8442), thanks @bcardiff) - Repository clean-up. ([#8312](https://github.com/crystal-lang/crystal/pull/8312), [#8397](https://github.com/crystal-lang/crystal/pull/8397), thanks @bcardiff, @straight-shoota) -# 0.31.1 (2019-09-30) +## [0.31.1] - 2019-09-30 +[0.31.1]: https://github.com/crystal-lang/crystal/releases/0.31.1 -## Standard library +### Standard library -### Numeric +#### Numeric - Fixed overflow in `Random::Secure`. ([#8224](https://github.com/crystal-lang/crystal/pull/8224), thanks @oprypin) -### Networking +#### Networking - Workaround `IO::Evented#evented_write` invalid `IndexError` error. ([#8239](https://github.com/crystal-lang/crystal/pull/8239), thanks @bcardiff) -### Concurrency +#### Concurrency - Use bdw-gc upstream patch for green threads support. ([#8225](https://github.com/crystal-lang/crystal/pull/8225), thanks @bcardiff) - Refactor `Channel` to use records instead of tuples. ([#8227](https://github.com/crystal-lang/crystal/pull/8227), thanks @asterite) -### Spec +#### Spec - Add `before_suite` and `after_suite` hooks. ([#8238](https://github.com/crystal-lang/crystal/pull/8238), thanks @asterite) -## Compiler +### Compiler - Fix debug location information when emitting main code from module. ([#8234](https://github.com/crystal-lang/crystal/pull/8234), thanks @asterite) -### Language semantics +#### Language semantics - Use virtual type for `uninitialized`. ([#8221](https://github.com/crystal-lang/crystal/pull/8221), thanks @asterite) -# 0.31.0 (2019-09-23) +## [0.31.0] - 2019-09-23 +[0.31.0]: https://github.com/crystal-lang/crystal/releases/0.31.0 -## Language changes +### Language changes - Allow non-captured block args with type restriction using `& : T -> U`. ([#8117](https://github.com/crystal-lang/crystal/pull/8117), thanks @asterite) -### Macros +#### Macros - Ensure `@type` is devirtualized inside macros. ([#8149](https://github.com/crystal-lang/crystal/pull/8149), thanks @asterite) -## Standard library +### Standard library - **(breaking-change)** Remove `Markdown` from the std-lib. ([#8115](https://github.com/crystal-lang/crystal/pull/8115), thanks @asterite) - **(breaking-change)** Deprecate `OptionParser#parse!`, use `OptionParser#parse`. ([#8041](https://github.com/crystal-lang/crystal/pull/8041), thanks @didactic-drunk) - Fix example codes in multiple places. ([#8194](https://github.com/crystal-lang/crystal/pull/8194), thanks @maiha) -### Numeric +#### Numeric - **(breaking-change)** Enable overflow by default. ([#8170](https://github.com/crystal-lang/crystal/pull/8170), thanks @bcardiff) - **(breaking-change)** Make `/` the arithmetic division for all types. ([#8120](https://github.com/crystal-lang/crystal/pull/8120), thanks @bcardiff) @@ -1170,11 +1181,11 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Avoid overflow exception in `Number#round(digits, base)`. ([#8204](https://github.com/crystal-lang/crystal/pull/8204), thanks @bcardiff) - Refactor `Int#divisible_by?` for clarity. ([#8045](https://github.com/crystal-lang/crystal/pull/8045), thanks @yxhuvud) -### Text +#### Text - **(performance)** Minor `String#lchop?` ASCII-only optimization. ([#8052](https://github.com/crystal-lang/crystal/pull/8052), thanks @r00ster91) -### Collections +#### Collections - **(performance)** Array optimizations for small number of elements. ([#8048](https://github.com/crystal-lang/crystal/pull/8048), thanks @asterite) - **(performance)** Optimize `Array#*`. ([#8087](https://github.com/crystal-lang/crystal/pull/8087), thanks @asterite) @@ -1186,14 +1197,14 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add `Set#+`. ([#8121](https://github.com/crystal-lang/crystal/pull/8121), thanks @sam0x17) - Refactor `Hash` to use integer division instead of float division. ([#8104](https://github.com/crystal-lang/crystal/pull/8104), thanks @asterite) -### Serialization +#### Serialization - **(breaking-change)** Rename `XML::Type` to `XML::Node::Type`, introduce `XML::Reader::Type`. ([#8134](https://github.com/crystal-lang/crystal/pull/8134), thanks @asterite) - Fixed JSON and YAML parsing of `NamedTuple` with nilable fields. ([#8109](https://github.com/crystal-lang/crystal/pull/8109), thanks @asterite) - Fixed YAML to emit unicode characters as such. ([#8132](https://github.com/crystal-lang/crystal/pull/8132), thanks @asterite) - Fixed INI generation of empty sections. ([#8106](https://github.com/crystal-lang/crystal/pull/8106), thanks @j8r) -### Files +#### Files - **(performance)** Optimize `Path#join` by precomputing capacity if possible. ([#8078](https://github.com/crystal-lang/crystal/pull/8078), thanks @asterite) - **(performance)** Optimize `Path#join` for the case of joining one single part. ([#8082](https://github.com/crystal-lang/crystal/pull/8082), thanks @asterite) @@ -1203,7 +1214,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixed `Zip::Writer` STORED compression. ([#8142](https://github.com/crystal-lang/crystal/pull/8142), thanks @asterite) - Fixed missing check on `ARGF` if read_count is zero. ([#8177](https://github.com/crystal-lang/crystal/pull/8177), thanks @Blacksmoke16) -### Networking +#### Networking - **(breaking-change)** Replace `HTTP::Server::Response#respond_with_error` with `#respond_with_status`. ([#6988](https://github.com/crystal-lang/crystal/pull/6988), thanks @straight-shoota) - **(breaking-change)** Handle too long URIs and too large header fields in `HTTP::Request.from_io` and remove `HTTP::Request::BadRequest`. ([#8013](https://github.com/crystal-lang/crystal/pull/8013), thanks @straight-shoota) @@ -1215,7 +1226,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor `http/server_spec`. ([#8056](https://github.com/crystal-lang/crystal/pull/8056), thanks @straight-shoota) - Refactor UDP specs to use random port. ([#8139](https://github.com/crystal-lang/crystal/pull/8139), thanks @waj) -### Concurrency +#### Concurrency - Multithreading. ([#8112](https://github.com/crystal-lang/crystal/pull/8112), thanks @waj) - Delay releasing of fiber stack in multi-thread mode. ([#8138](https://github.com/crystal-lang/crystal/pull/8138), thanks @waj) @@ -1227,15 +1238,15 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix corruption of thread linked list. ([#8196](https://github.com/crystal-lang/crystal/pull/8196), thanks @waj) - Workaround compile on win32 until fibers is implemented. ([#8195](https://github.com/crystal-lang/crystal/pull/8195), thanks @straight-shoota) -### System +#### System - Increase precision of `Process.times`. ([#8097](https://github.com/crystal-lang/crystal/pull/8097), thanks @jgaskins) -### Spec +#### Spec - **(breaking-change)** Add support for `focus`. ([#8125](https://github.com/crystal-lang/crystal/pull/8125), [#8178](https://github.com/crystal-lang/crystal/pull/8178), [#8208](https://github.com/crystal-lang/crystal/pull/8208), thanks @asterite, @straight-shoota, @bcardiff) -## Compiler +### Compiler - Fixed ICE on declarations inside fun. ([#8076](https://github.com/crystal-lang/crystal/pull/8076), thanks @asterite) - Fixed missing `name_location` of some calls. ([#8192](https://github.com/crystal-lang/crystal/pull/8192), thanks @asterite) @@ -1243,7 +1254,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Improve return type mismatch error. ([#8203](https://github.com/crystal-lang/crystal/pull/8203), thanks @asterite) - Improve `for` expression error. ([#7641](https://github.com/crystal-lang/crystal/pull/7641), thanks @r00ster91) -### Language semantics +#### Language semantics - Fixed abstract def check regarding generic ancestor lookup. ([#8098](https://github.com/crystal-lang/crystal/pull/8098), thanks @asterite) - Fixed missing virtualization of type arguments in `Proc` types. ([#8159](https://github.com/crystal-lang/crystal/pull/8159), thanks @asterite) @@ -1254,71 +1265,73 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor class var and constant initialization. ([#8067](https://github.com/crystal-lang/crystal/pull/8067), [#8091](https://github.com/crystal-lang/crystal/pull/8091), thanks @waj) - Add runtime check for recursive initialization of class variables and constants. ([#8172](https://github.com/crystal-lang/crystal/pull/8172), thanks @waj) -## Tools +### Tools -### Doc generator +#### Doc generator - Fixed link to constructors of another class. ([#8110](https://github.com/crystal-lang/crystal/pull/8110), thanks @asterite) - Enable docs from previous def and/or ancestors to be inherited. ([#6989](https://github.com/crystal-lang/crystal/pull/6989), thanks @asterite) -## Others +### Others - Update CI to use 0.30.1. ([#8032](https://github.com/crystal-lang/crystal/pull/8032), thanks @bcardiff) - Use LLVM 8.0 for Linux official packages. ([#8155](https://github.com/crystal-lang/crystal/pull/8155), thanks @bcardiff, @RX14) - Update dependencies of the build process. ([#8205](https://github.com/crystal-lang/crystal/pull/8205), thanks @bcardiff) - Code cleanups. ([#8033](https://github.com/crystal-lang/crystal/pull/8033), thanks @straight-shoota) -# 0.30.1 (2019-08-12) +## [0.30.1] - 2019-08-12 +[0.30.1]: https://github.com/crystal-lang/crystal/releases/0.30.1 -## Standard library +### Standard library -### Numeric +#### Numeric - Fixed `Number#humanize` digits. ([#8027](https://github.com/crystal-lang/crystal/pull/8027), thanks @straight-shoota) -### Networking +#### Networking - Fixed TCP socket leaking after failed SSL connect in `HTTP::Client#socket`. ([#8025](https://github.com/crystal-lang/crystal/pull/8025), thanks @straight-shoota) - Honor normalized header names for HTTP requests. ([#8061](https://github.com/crystal-lang/crystal/pull/8061), thanks @asterite) -### Concurrency +#### Concurrency - Don't resume fibers directly from event loop callbacks (or support for libevent 2.1.11). ([#8058](https://github.com/crystal-lang/crystal/pull/8058), thanks @waj) -## Compiler +### Compiler - Fixed `sizeof(Nil)` and other empty types. ([#8040](https://github.com/crystal-lang/crystal/pull/8040), thanks @asterite) - Avoid internal globals of type i128 or u128. (or workaround [a llvm 128 bits bug](https://bugs.llvm.org/show_bug.cgi?id=42932)). ([#8063](https://github.com/crystal-lang/crystal/pull/8063), thanks @bcardiff, @asterite) -### Language semantics +#### Language semantics - Consider abstract method implementation in supertype for abstract method checks. ([#8035](https://github.com/crystal-lang/crystal/pull/8035), thanks @asterite) -## Tools +### Tools -### Formatter +#### Formatter - Handle consecutive macro literals when subformatting. ([#8034](https://github.com/crystal-lang/crystal/pull/8034), thanks @asterite) - Fixed crash when formatting syntax error inside macro. ([#8055](https://github.com/crystal-lang/crystal/pull/8055), thanks @asterite) -## Others +### Others - Use LLVM 6.0.1 for darwin official packages. ([#7994](https://github.com/crystal-lang/crystal/pull/7994), thanks @bcardiff) - Split std_specs in 32 bits CI. ([#8065](https://github.com/crystal-lang/crystal/pull/8065), thanks @bcardiff) -# 0.30.0 (2019-08-01) +## [0.30.0] - 2019-08-01 +[0.30.0]: https://github.com/crystal-lang/crystal/releases/0.30.0 -## Language changes +### Language changes - **(breaking-change)** Enforce abstract methods return types. ([#7956](https://github.com/crystal-lang/crystal/pull/7956), [#7999](https://github.com/crystal-lang/crystal/pull/7999), [#8010](https://github.com/crystal-lang/crystal/pull/8010), thanks @asterite) - **(breaking-change)** Don't allow ranges to span across lines. ([#7888](https://github.com/crystal-lang/crystal/pull/7888), thanks @oprypin) -### Macros +#### Macros - Add `args`/`named_args` macro methods to `Annotations`. ([#7694](https://github.com/crystal-lang/crystal/pull/7694), thanks @Blacksmoke16) - Unify `resolve` and `types` macro methods API for `Type` and `Path` for convenience. ([#7970](https://github.com/crystal-lang/crystal/pull/7970), thanks @asterite) -## Standard library +### Standard library - **(breaking-change)** Remove `UUID#to_slice` in favor of `UUID#bytes` to fix dangling pointer issues. ([#7901](https://github.com/crystal-lang/crystal/pull/7901), thanks @ysbaddaden) - **(performance)** Improve `Box` of reference types. ([#8016](https://github.com/crystal-lang/crystal/pull/8016), thanks @waj) @@ -1330,22 +1343,22 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixed `Logger` docs. ([#7898](https://github.com/crystal-lang/crystal/pull/7898), thanks @dprobinson) - Fix example codes in multiple places. ([#8003](https://github.com/crystal-lang/crystal/pull/8003), thanks @maiha) -### Numeric +#### Numeric - Fixed incorrect `Int#%` overflow. ([#7980](https://github.com/crystal-lang/crystal/pull/7980), thanks @asterite) - Fixed inconsistency between `Float#to_s` and `BigFloat#to_s`, always show `.0` for whole numbers. ([#7982](https://github.com/crystal-lang/crystal/pull/7982), thanks @Lasvad) -### Text +#### Text - Fixed unicode alternate ranges generation. ([#7924](https://github.com/crystal-lang/crystal/pull/7924), thanks @asterite) -### Collections +#### Collections - Add `Enumerable#tally`. ([#7921](https://github.com/crystal-lang/crystal/pull/7921), thanks @kachick) - Add `Enumerable#reduce?` overload with not initial value. ([#7941](https://github.com/crystal-lang/crystal/pull/7941), thanks @miketheman) - Fix specs of `Enumerable#min_by?`. ([#7919](https://github.com/crystal-lang/crystal/pull/7919), thanks @kachick) -### Serialization +#### Serialization - **(breaking-change)** JSON: use enums instead of symbols. ([#7966](https://github.com/crystal-lang/crystal/pull/7966), thanks @asterite) - Fixed YAML deserialization of String in a union type. ([#7938](https://github.com/crystal-lang/crystal/pull/7938), thanks @asterite) @@ -1353,11 +1366,11 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Allow numeric keys in JSON (ie: `Hash(Int32, String).from_json`). ([#7944](https://github.com/crystal-lang/crystal/pull/7944), thanks @asterite) - Add `alias`/`merge` methods to `YAML::Builder` and `YAML::Nodes::Builder`. ([#7949](https://github.com/crystal-lang/crystal/pull/7949), thanks @Blacksmoke16) -### Files +#### Files - Adds `File.readlink` to match `File.symlink`. ([#7858](https://github.com/crystal-lang/crystal/pull/7858), thanks @didactic-drunk) -### Networking +#### Networking - **(breaking-change)** Improve URL encoding. `URI.escape` and `URI.unescape` are renamed to `URI.encode_www_form` and `URI.decode_www_form`. Add `URI.encode` and `URI.decode`. ([#7997](https://github.com/crystal-lang/crystal/pull/7997), [#8021](https://github.com/crystal-lang/crystal/pull/8021), thanks @straight-shoota, @bcardiff) - **(performance)** HTTP protocol parsing optimizations. ([#8002](https://github.com/crystal-lang/crystal/pull/8002), [#8009](https://github.com/crystal-lang/crystal/pull/8009), thanks @asterite) @@ -1368,21 +1381,21 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Natively support [Same-site Cookies](https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1.1). ([#7864](https://github.com/crystal-lang/crystal/pull/7864), thanks @Blacksmoke16) - Allow setting buffer size for `IO::Buffered`. ([#7930](https://github.com/crystal-lang/crystal/pull/7930), thanks @carlhoerberg) -### Crypto +#### Crypto - Require openssl algorithm in pkcs5. ([#7985](https://github.com/crystal-lang/crystal/pull/7985), thanks @will) - Fixed cipher expectation in `OpenSSL::SSL::Socket` spec. ([#7871](https://github.com/crystal-lang/crystal/pull/7871), thanks @j8r) -### Concurrency +#### Concurrency - Fixed `sysconf` call on OpenBSD. ([#7879](https://github.com/crystal-lang/crystal/pull/7879), thanks @jcs) -### System +#### System - Introduce `System::User` and `System::Group`. ([#7725](https://github.com/crystal-lang/crystal/pull/7725), thanks @woodruffw, @chris-huxtable) - Add docs for `Process::Status.exit_status` (#7873). ([#8014](https://github.com/crystal-lang/crystal/pull/8014), thanks @UlisseMini) -## Compiler +### Compiler - Fixed codegen of `pointer.as(Nil)`. ([#8019](https://github.com/crystal-lang/crystal/pull/8019), thanks @asterite) - Fixed edge cases in parser and stringifier. ([#7886](https://github.com/crystal-lang/crystal/pull/7886), thanks @oprypin) @@ -1398,45 +1411,46 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Minor additions and refactors on for LLVM codegen. ([#7972](https://github.com/crystal-lang/crystal/pull/7972), thanks @bcardiff) - Add `bin/check-compiler-flag` helper script. Add `make clean_cache`. ([da3892](https://github.com/crystal-lang/crystal/commit/da38927f3a00f1e6e5ea86b96ca669533f0aa438), thanks @bcardiff) -### Language semantics +#### Language semantics - Fixed generic metaclass argument expansion. ([#7916](https://github.com/crystal-lang/crystal/pull/7916), thanks @asterite) - Fixed top-level private const not being scoped. ([#7907](https://github.com/crystal-lang/crystal/pull/7907), thanks @asterite) - Fixed enum overflow when declaring members. ([#7881](https://github.com/crystal-lang/crystal/pull/7881), thanks @asterite) - Fixed annotation lookup on generic types. ([#7891](https://github.com/crystal-lang/crystal/pull/7891), thanks @asterite) -## Tools +### Tools -### Formatter +#### Formatter - Format top-level inline macros. ([#7889](https://github.com/crystal-lang/crystal/pull/7889), [#7992](https://github.com/crystal-lang/crystal/pull/7992), thanks @asterite) -### Doc generator +#### Doc generator - Allow rendering tags on methods without any docs. ([#7952](https://github.com/crystal-lang/crystal/pull/7952), thanks @Blacksmoke16) -## Others +### Others - Update CI to use 0.29.0. ([#7863](https://github.com/crystal-lang/crystal/pull/7863), thanks @bcardiff) - Automated snap publishing. ([#7893](https://github.com/crystal-lang/crystal/pull/7893), thanks @bcardiff) - ~~Use LLVM 6.0.1 for darwin official packages.~~ ([#7994](https://github.com/crystal-lang/crystal/pull/7994), thanks @bcardiff) -# 0.29.0 (2019-06-05) +## [0.29.0] - 2019-06-05 +[0.29.0]: https://github.com/crystal-lang/crystal/releases/0.29.0 -## Standard library +### Standard library - Fix example codes in multiple places. ([#7718](https://github.com/crystal-lang/crystal/pull/7718), thanks @maiha) -### Macros +#### Macros - Fix inheritance support of `record` macro. ([#7811](https://github.com/crystal-lang/crystal/pull/7811), thanks @asterite) - Omit quotes in `puts` macro output. ([#7734](https://github.com/crystal-lang/crystal/pull/7734), thanks @asterite) -### Numeric +#### Numeric - **(performance)** Optimize `String#to_u` methods for the case of a negative number. ([#7446](https://github.com/crystal-lang/crystal/pull/7446), thanks @r00ster91) -### Text +#### Text - **(breaking-change)** Deprecate `String#at`, use `String#char_at`. ([#7633](https://github.com/crystal-lang/crystal/pull/7633), thanks @j8r) - **(breaking-change)** Change `String#to_i` to parse octals with prefix `0o` (but not `0` by default). ([#7691](https://github.com/crystal-lang/crystal/pull/7691), thanks @icy-arctic-fox) @@ -1445,28 +1459,28 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add support for unicode 12.0.0. ([#7721](https://github.com/crystal-lang/crystal/pull/7721), thanks @Blacksmoke16) - Fix `Unicode` not showing up in the API docs. ([#7720](https://github.com/crystal-lang/crystal/pull/7720), thanks @r00ster91) -### Collections +#### Collections - **(breaking-change)** Remove `Slice#pointer`. ([#7581](https://github.com/crystal-lang/crystal/pull/7581), thanks @Maroo-b) - Add sort methods to `Slice`. ([#7597](https://github.com/crystal-lang/crystal/pull/7597), thanks @Maroo-b) - Add `Slice#[]?`. ([#7701](https://github.com/crystal-lang/crystal/pull/7701), thanks @Sija) - Improve docs for `Slice#[]`. ([#7780](https://github.com/crystal-lang/crystal/pull/7780), thanks @Sija) -### Serialization +#### Serialization - YAML: let String handle numbers too. ([#7809](https://github.com/crystal-lang/crystal/pull/7809), thanks @asterite) -### Time +#### Time - Fix time format RFC 3339 to not include offset seconds. ([#7492](https://github.com/crystal-lang/crystal/pull/7492), thanks @straight-shoota) -### Files +#### Files - **(breaking-change)** Rename `File::DEVNULL` to `File::NULL`. ([#7778](https://github.com/crystal-lang/crystal/pull/7778), thanks @r00ster91) - Fix handling of files starting with `~` in `Path#expand`. ([#7768](https://github.com/crystal-lang/crystal/pull/7768), thanks @byroot) - Fix `Dir.glob(match_hidden: false)` not hiding hidden files properly. ([#7774](https://github.com/crystal-lang/crystal/pull/7774), thanks @ayazhafiz) -### Networking +#### Networking - **(breaking-change)** Let `IO#copy` return `UInt64`. ([#7660](https://github.com/crystal-lang/crystal/pull/7660), thanks @asterite) - Add support for UDP multicast. ([#7423](https://github.com/crystal-lang/crystal/pull/7423), thanks @stakach) @@ -1478,15 +1492,15 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor `StaticFileHandler` specs for `Last-Modified` header. ([#7640](https://github.com/crystal-lang/crystal/pull/7640), thanks @straight-shoota) - Refactor compression usage in handler specs. ([#7819](https://github.com/crystal-lang/crystal/pull/7819), thanks @asterite) -### Crypto +#### Crypto - **(breaking-change)** Rename `Crypto::Bcrypt::Password#==` to `#verify`. ([#7790](https://github.com/crystal-lang/crystal/pull/7790), thanks @asterite) -### Concurrency +#### Concurrency - Add docs for `Channel`. ([#7673](https://github.com/crystal-lang/crystal/pull/7673), thanks @j8r) -## Compiler +### Compiler - **(breaking-change)** Fix require relative path resolution. ([#7758](https://github.com/crystal-lang/crystal/pull/7758), thanks @asterite) - **(breaking-change)** Disallow '!' or '?' at the end of the LHS in an assignment. ([#7582](https://github.com/crystal-lang/crystal/pull/7582), thanks @Maroo-b) @@ -1504,7 +1518,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Improve wording of `pointerof(self)` parser error. ([#7542](https://github.com/crystal-lang/crystal/pull/7542), thanks @r00ster91) - Fix typo. ([#7828](https://github.com/crystal-lang/crystal/pull/7828), thanks @RX14) -### Language semantics +#### Language semantics - **(breaking-change)** Fix new/initialize lookup regarding modules. ([#7818](https://github.com/crystal-lang/crystal/pull/7818), thanks @asterite) - **(breaking-change)** Don't precompute `sizeof` on abstract structs and modules. ([#7801](https://github.com/crystal-lang/crystal/pull/7801), thanks @asterite) @@ -1524,37 +1538,38 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix edge case of abstract struct with one subclass. ([#7787](https://github.com/crystal-lang/crystal/pull/7787), thanks @asterite) - Make automatic cast work with `with ... yield`. ([#7746](https://github.com/crystal-lang/crystal/pull/7746), thanks @asterite) -## Tools +### Tools - Allow to lookup class and module implementations. ([#7742](https://github.com/crystal-lang/crystal/pull/7742), thanks @MakeNowJust) - Refactor old duplicated 'def contains_target'. ([#7739](https://github.com/crystal-lang/crystal/pull/7739), thanks @MakeNowJust) -### Formatter +#### Formatter - Don't produce unnecessary newline before named args following heredoc. ([#7695](https://github.com/crystal-lang/crystal/pull/7695), thanks @MakeNowJust) - Fix formatting of multiline call arguments. ([#7745](https://github.com/crystal-lang/crystal/pull/7745), thanks @MakeNowJust) - Fix formatting of annotations with newlines and spaces. ([#7744](https://github.com/crystal-lang/crystal/pull/7744), thanks @MakeNowJust) - Refactor code to format &.[]. ([#7699](https://github.com/crystal-lang/crystal/pull/7699), thanks @MakeNowJust) -## Others +### Others - CI improvements and housekeeping. ([#7705](https://github.com/crystal-lang/crystal/pull/7705), [#7852](https://github.com/crystal-lang/crystal/pull/7852), thanks @bcardiff) - Move VERSION inside ./src. ([#7804](https://github.com/crystal-lang/crystal/pull/7804), thanks @bcardiff) -# 0.28.0 (2019-04-17) +## [0.28.0] - 2019-04-17 +[0.28.0]: https://github.com/crystal-lang/crystal/releases/0.28.0 -## Language changes +### Language changes - **(breaking-change)** Enum declaration members can no longer be separated by a space, only by a newline, `;` or `,`, the latter being deprecated and reformatted to a newline. ([#7607](https://github.com/crystal-lang/crystal/pull/7607), [#7618](https://github.com/crystal-lang/crystal/pull/7618), thanks @asterite, and @j8r) - Add begin-less and end-less ranges: `array[5..]`. ([#7179](https://github.com/crystal-lang/crystal/pull/7179), thanks @asterite) - Add `offsetof(Type, @ivar)` expression. ([#7589](https://github.com/crystal-lang/crystal/pull/7589), thanks @malte-v) -### Macros +#### Macros - Add `Type#annotations` to list all annotations and not just the last of each kind. ([#7326](https://github.com/crystal-lang/crystal/pull/7326), thanks @Blacksmoke16) - Add `ArrayLiteral#sort_by` macro method. ([#3947](https://github.com/crystal-lang/crystal/pull/3947), thanks @jreinert) -## Standard library +### Standard library - **(breaking-change)** Allow creating `None` enum flag with `Enum.from_value`. ([#6516](https://github.com/crystal-lang/crystal/pull/6516), thanks @bew) - **(breaking-change)** Add deprecation message to `PartialComparable`. Its behaviour has been fully integrated into `Comparable`. ([#7664](https://github.com/crystal-lang/crystal/pull/7664), thanks @straight-shoota) @@ -1569,7 +1584,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor to avoid usage of the thread-local `$errno` GLIBC_PRIVATE symbol. ([#7496](https://github.com/crystal-lang/crystal/pull/7496), thanks @felixvf) - Refactor to have similar signatures across the stdlib for `#to_s` and `#inspect`. ([#7528](https://github.com/crystal-lang/crystal/pull/7528), thanks @wontruefree) -### Numeric +#### Numeric - **(breaking-change)** Add deprecation message to `Int#/`. It will return a `Float` in `0.29.0`. Use `Int#//` for integer division. ([#7639](https://github.com/crystal-lang/crystal/pull/7639), thanks @bcardiff) - Change `Number#inspect` to not show the type suffixes. ([#7525](https://github.com/crystal-lang/crystal/pull/7525), thanks @asterite) @@ -1577,7 +1592,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add `Big*#//`, `BigInt#&`-ops and missing `#floor`, `#ceil`, `#trunc`. ([#7638](https://github.com/crystal-lang/crystal/pull/7638), thanks @bcardiff) - Improve `OverflowError` message. ([#7375](https://github.com/crystal-lang/crystal/pull/7375), thanks @r00ster91) -### Text +#### Text - **(performance)** Optimize `String#compare` in case of ASCII only. ([#7352](https://github.com/crystal-lang/crystal/pull/7352), thanks @r00ster91) - Add methods for human-readable formatting of numbers: `Number#format`, `Number#humanize`, and `Int#humanize_bytes`. ([#6314](https://github.com/crystal-lang/crystal/pull/6314), thanks @straight-shoota) @@ -1586,7 +1601,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add docs to `Unicode::CaseOptions`. ([#7513](https://github.com/crystal-lang/crystal/pull/7513), thanks @r00ster91) - Improve specs and docs for `String#each_line` and `IO#each_line`. ([#7419](https://github.com/crystal-lang/crystal/pull/7419), thanks @straight-shoota) -### Collections +#### Collections - **(breaking-change)** Let `Array#sort` only use `<=>`, and let `<=>` return `nil` for partial comparability. ([#6611](https://github.com/crystal-lang/crystal/pull/6611), thanks @asterite) - **(breaking-change)** Drop `Iterator#rewind`. Implement `#cycle` by storing elements in an array. ([#7440](https://github.com/crystal-lang/crystal/pull/7440), thanks @asterite) @@ -1599,14 +1614,14 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add `Set#add?`. ([#7495](https://github.com/crystal-lang/crystal/pull/7495), thanks @Sija) - Improve documentation of `Hash` regarding ordering of items. ([#7594](https://github.com/crystal-lang/crystal/pull/7594), thanks @straight-shoota) -### Serialization +#### Serialization - **(breaking-change)** Change return type of `YAML#libyaml_version` to `SemanticVersion`. ([#7555](https://github.com/crystal-lang/crystal/pull/7555), thanks @asterite) - Fix support for libxml2 2.9.9. ([#7477](https://github.com/crystal-lang/crystal/pull/7477), thanks @asterite) - Fix support for libyaml 0.2.2. ([#7555](https://github.com/crystal-lang/crystal/pull/7555), thanks @asterite) - Add `BigDecimal.from_yaml`. ([#7398](https://github.com/crystal-lang/crystal/pull/7398), thanks @Sija) -### Time +#### Time - **(breaking-change)** Rename `Time` constructors. Deprecate `Time.now` to encourage usage `Time.utc` or `Time.local` ([#5346](https://github.com/crystal-lang/crystal/pull/5346), [#7586](https://github.com/crystal-lang/crystal/pull/7586), thanks @straight-shoota) - **(breaking-change)** Rename `Time#add_span` to `Time#shift` for changing a time instance by calendar units and handle other units. ([#6598](https://github.com/crystal-lang/crystal/pull/6598), thanks @straight-shoota) @@ -1614,12 +1629,12 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix Windows monotonic time bug. ([#7377](https://github.com/crystal-lang/crystal/pull/7377), thanks @domgetter) - Refactor `Time` methods. ([#6581](https://github.com/crystal-lang/crystal/pull/6581), thanks @straight-shoota) -### Files +#### Files - **(breaking-change)** Remove `IO#flush_on_newline` and only `sync` on `STDOUT`/`STDIN`/`STDERR` when they are TTY devices. ([#7470](https://github.com/crystal-lang/crystal/pull/7470), thanks @asterite) - Add `Path` type. ([#5635](https://github.com/crystal-lang/crystal/pull/5635), thanks @straight-shoota) -### Networking +#### Networking - **(breaking-change)** Move `HTTP::Multipart` to `MIME::Multipart`. ([#7085](https://github.com/crystal-lang/crystal/pull/7085), thanks @m1lt0n) - **(breaking-change)** Stop parsing JSON in OAuth2 errors. ([#7467](https://github.com/crystal-lang/crystal/pull/7467), thanks @asterite) @@ -1637,28 +1652,28 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix spec of `HTTP::Client` to not write server response after timeout. ([#7402](https://github.com/crystal-lang/crystal/pull/7402), thanks @asterite) - Fix spec of `TCP::Server` for musl. ([#7484](https://github.com/crystal-lang/crystal/pull/7484), thanks @straight-shoota) -### Crypto +#### Crypto - **(breaking-change)** Use `OpenSSL::Algorithm` instead of symbols for `digest`/`hexdigest`. Expose LibCrypt's `PKCS5_PBKDF2_HMAC`. ([#7264](https://github.com/crystal-lang/crystal/pull/7264), thanks @mniak) -### Concurrency +#### Concurrency - Add multi-threading ready GC when compiling with `-D preview_mt`. ([#7546](https://github.com/crystal-lang/crystal/pull/7546), thanks @bcardiff, @waj, and @ysbaddaden) - Ship patched bdw-gc for multi-threading support. ([#7622](https://github.com/crystal-lang/crystal/pull/7622), thanks @bcardiff, and @ysbaddaden) - Refactor to extract `Fiber::StackPool` from `Fiber`. ([#7417](https://github.com/crystal-lang/crystal/pull/7417), thanks @ysbaddaden) - Refactor `IO::Syscall` as `IO::Evented`. ([#7505](https://github.com/crystal-lang/crystal/pull/7505), thanks @ysbaddaden) -### System +#### System - Add command and args to `execvp` error message. ([#7511](https://github.com/crystal-lang/crystal/pull/7511), thanks @straight-shoota) - Refactor signals handling in a separate fiber. ([#7469](https://github.com/crystal-lang/crystal/pull/7469), thanks @asterite) -### Spec +#### Spec - Improve how running specs are cancelled upon `CTRL+C`. ([#7426](https://github.com/crystal-lang/crystal/pull/7426), thanks @asterite) - Allow `pending` and `it` to accept constants. ([#7646](https://github.com/crystal-lang/crystal/pull/7646), thanks @straight-shoota) -## Compiler +### Compiler - **(performance)** Avoid fork and spawn when `--threads=1`. ([#7397](https://github.com/crystal-lang/crystal/pull/7397), thanks @asterite) - Fix exception type thrown on missing require. ([#7386](https://github.com/crystal-lang/crystal/pull/7386), thanks @asterite) @@ -1675,7 +1690,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Rewrite macro spec without executing a shell command. ([#6962](https://github.com/crystal-lang/crystal/pull/6962), thanks @asterite) - Fix typo in internals. ([#7592](https://github.com/crystal-lang/crystal/pull/7592), thanks @toshokan) -### Language semantics +#### Language semantics - Fix issues with `as`, `as?` and unions and empty types. ([#7475](https://github.com/crystal-lang/crystal/pull/7475), thanks @asterite) - Fix method lookup when restrictions of instantiated and non-instantiated generic types are used. ([#7537](https://github.com/crystal-lang/crystal/pull/7537), thanks @bew) @@ -1685,29 +1700,29 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Merge procs with the same arguments type and `Nil | T` return type to `Nil` return type. ([#7527](https://github.com/crystal-lang/crystal/pull/7527), thanks @asterite) - Fix passing recursive alias to proc. ([#7568](https://github.com/crystal-lang/crystal/pull/7568), thanks @asterite) -## Tools +### Tools - Suggest the user to run the formatter in `travis.yml`. ([#7138](https://github.com/crystal-lang/crystal/pull/7138), thanks @KCErb) -### Formatter +#### Formatter - Fix formatting of `1\n.as(Int32)`. ([#7347](https://github.com/crystal-lang/crystal/pull/7347), thanks @asterite) - Fix formatting of nested array elements. ([#7450](https://github.com/crystal-lang/crystal/pull/7450), thanks @MakeNowJust) - Fix formatting of comments and enums. ([#7605](https://github.com/crystal-lang/crystal/pull/7605), thanks @asterite) - Fix CLI handling of absolute paths input. ([#7560](https://github.com/crystal-lang/crystal/pull/7560), thanks @RX14) -### Doc generator +#### Doc generator - Don't include private constants. ([#7575](https://github.com/crystal-lang/crystal/pull/7575), thanks @r00ster91) - Include Crystal built-in constants. ([#7623](https://github.com/crystal-lang/crystal/pull/7623), thanks @bcardiff) - Add compile-time flag to docs generator. ([#6668](https://github.com/crystal-lang/crystal/pull/6668), [#7438](https://github.com/crystal-lang/crystal/pull/7438), thanks @straight-shoota) - Display deprecated label when `@[Deprecated]` is used. ([#7653](https://github.com/crystal-lang/crystal/pull/7653), thanks @bcardiff) -### Playground +#### Playground - Change the font-weight used for better readability. ([#7552](https://github.com/crystal-lang/crystal/pull/7552), thanks @Maroo-b) -## Others +### Others - CI improvements and housekeeping. ([#7359](https://github.com/crystal-lang/crystal/pull/7359), [#7381](https://github.com/crystal-lang/crystal/pull/7381), [#7388](https://github.com/crystal-lang/crystal/pull/7388), [#7387](https://github.com/crystal-lang/crystal/pull/7387), [#7390](https://github.com/crystal-lang/crystal/pull/7390), [#7622](https://github.com/crystal-lang/crystal/pull/7622), thanks @bcardiff) - Smoke test linux 64 bits package using docker image recent build. ([#7389](https://github.com/crystal-lang/crystal/pull/7389), thanks @bcardiff) @@ -1717,24 +1732,26 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix GC finalization warning in `Thread` specs. ([#7403](https://github.com/crystal-lang/crystal/pull/7403), thanks @asterite) - Remove generated docs from linux packages. ([#7519](https://github.com/crystal-lang/crystal/issues/7519), thanks @straight-shoota) -# 0.27.2 (2019-02-05) +## [0.27.2] - 2019-02-05 +[0.27.2]: https://github.com/crystal-lang/crystal/releases/0.27.2 -## Standard library +### Standard library - Fixed integer overflow in main thread stack base detection. ([#7373](https://github.com/crystal-lang/crystal/pull/7373), thanks @ysbaddaden) -### Networking +#### Networking - Fixes TLS exception during shutdown. ([#7372](https://github.com/crystal-lang/crystal/pull/7372), thanks @bcardiff) - Fixed `HTTP::Client` support exception on missing Content-Type. ([#7371](https://github.com/crystal-lang/crystal/pull/7371), thanks @bew) -# 0.27.1 (2019-01-30) +## [0.27.1] - 2019-01-30 +[0.27.1]: https://github.com/crystal-lang/crystal/releases/0.27.1 -## Language changes +### Language changes - Allow trailing commas inside tuple types. ([#7182](https://github.com/crystal-lang/crystal/pull/7182), thanks @asterite) -## Standard library +### Standard library - **(performance)** Optimize generating `UUID` from `String`. ([#7030](https://github.com/crystal-lang/crystal/pull/7030), thanks @jgaskins) - **(performance)** Improve `SemanticVersion` operations. ([#7234](https://github.com/crystal-lang/crystal/pull/7234), thanks @j8r) @@ -1745,7 +1762,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add docs to discourage the use of `Bool#to_unsafe` other than for C bindings. ([#7320](https://github.com/crystal-lang/crystal/pull/7320), thanks @oprypin) - Refactor `#to_s` to be independent of the `name` method. ([#7295](https://github.com/crystal-lang/crystal/pull/7295), thanks @asterite) -### Macros +#### Macros - Fixed docs of `ArrayLiteral#unshift`. ([#7127](https://github.com/crystal-lang/crystal/pull/7127), thanks @Blacksmoke16) - Fixed `Annotation#[]` to accept `String` and `Symbol` as keys. ([#7153](https://github.com/crystal-lang/crystal/pull/7153), thanks @MakeNowJust) @@ -1754,7 +1771,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add `read_file` macro method. ([#6967](https://github.com/crystal-lang/crystal/pull/6967), [#7094](https://github.com/crystal-lang/crystal/pull/7094), thanks @Sija, @woodruffw) - Add `StringLiteral#count`. ([#7239](https://github.com/crystal-lang/crystal/pull/7239), thanks @Blacksmoke16) -### Numeric +#### Numeric - Fixed scale issues when dividing `BigDecimal`. ([#7218](https://github.com/crystal-lang/crystal/pull/7218), thanks @Sija) - Allow underscores in the `String` passed to `Big*` constructors. ([#7107](https://github.com/crystal-lang/crystal/pull/7107), thanks @Sija) @@ -1763,13 +1780,13 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add unsafe number ops `value.to_X!`/`T.new!`/`Int#&**`. ([#7226](https://github.com/crystal-lang/crystal/pull/7226), thanks @bcardiff) - Add overflow detection with preview opt-in. ([#7206](https://github.com/crystal-lang/crystal/pull/7206), thanks @bcardiff) -### Text +#### Text - Fixed `ECR` location error reported. ([#7137](https://github.com/crystal-lang/crystal/pull/7137), thanks @MakeNowJust) - Add docs to ECR. ([#7121](https://github.com/crystal-lang/crystal/pull/7121), thanks @KCErb) - Refactor `String#to_i` to avoid future overflow. ([#7172](https://github.com/crystal-lang/crystal/pull/7172), thanks @bcardiff) -### Collections +#### Collections - Fixed docs example in `Hash#from`. ([#7210](https://github.com/crystal-lang/crystal/pull/7210), thanks @r00ster91) - Fixed docs links of `Enumerable#chunks` and `Iterator#chunk`. ([#6941](https://github.com/crystal-lang/crystal/pull/6941), thanks @r00ster91) @@ -1782,19 +1799,19 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add several `Enumerable`, `Iterator` and `Array` overloads that accept a pattern. ([#7174](https://github.com/crystal-lang/crystal/pull/7174), thanks @asterite) - Add docs to hash constructors. ([#6923](https://github.com/crystal-lang/crystal/pull/6923), thanks @KCErb) -### Serialization +#### Serialization - Add conversion between JSON and YAML. ([#7232](https://github.com/crystal-lang/crystal/pull/7232), thanks @straight-shoota) - Standardize `#as_T`/`#as_T?` methods between `JSON::Any`/`YAML::Any`. ([#6556](https://github.com/crystal-lang/crystal/pull/6556), thanks @j8r) - Add `Set#from_yaml`. ([#6310](https://github.com/crystal-lang/crystal/pull/6310), thanks @kostya) -### Time +#### Time - Fixed `Time::Span` initializer and `sleep` for big seconds. ([#7221](https://github.com/crystal-lang/crystal/pull/7221), thanks @asterite) - Fixed docs to show proper use of parse. ([#7035](https://github.com/crystal-lang/crystal/pull/7035), thanks @jwoertink) - Add missing `Float#weeks` method similar to `Int#weeks`. ([#7165](https://github.com/crystal-lang/crystal/pull/7165), thanks @vlazar) -### Files +#### Files - Fix `mkstemps` support on aarch64. ([#7300](https://github.com/crystal-lang/crystal/pull/7300), thanks @silmanduin66) - Validate LibC error codes in specs involving Errno errors. ([#7087](https://github.com/crystal-lang/crystal/pull/7087), thanks @straight-shoota) @@ -1802,7 +1819,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add missing tempfile cleanup in specs. ([#7250](https://github.com/crystal-lang/crystal/pull/7250), thanks @bcardiff) - Add docs for file open modes. ([#6664](https://github.com/crystal-lang/crystal/pull/6664), thanks @r00ster91) -### Networking +#### Networking - Fixed `HTTP::Client` edge case of exception during in TLS initialization. ([#7123](https://github.com/crystal-lang/crystal/pull/7123), thanks @asterite) - Fixed `OpenSSL::SSL::Error.new` to not raise `Errno`. ([#7068](https://github.com/crystal-lang/crystal/pull/7068), thanks @straight-shoota) @@ -1815,21 +1832,21 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Improve `HTTP::Server` docs. ([#7251](https://github.com/crystal-lang/crystal/pull/7251), thanks @straight-shoota) - Refactor `OpenSSL` specs to reduce chances of failing. ([#7202](https://github.com/crystal-lang/crystal/pull/7202), thanks @bcardiff) -### Crypto +#### Crypto - Add `OpenSSL::Cipher#authenticated?` to see if the cipher supports aead. ([#7223](https://github.com/crystal-lang/crystal/pull/7223), thanks @danielwestendorf) -### System +#### System - Fixed inline ASM when compiling for ARM. ([#7041](https://github.com/crystal-lang/crystal/pull/7041), thanks @omarroth) - Implement `Crystal::System` for Win32. ([#6972](https://github.com/crystal-lang/crystal/pull/6972), thanks @markrjr) - Add `Errno#errno_message` getter. ([#6702](https://github.com/crystal-lang/crystal/pull/6702), thanks @r00ster91) -### Spec +#### Spec - Detect nesting `it` and `pending` at run-time. ([#7297](https://github.com/crystal-lang/crystal/pull/7297), thanks @MakeNowJust) -## Compiler +### Compiler - Fixed how `LLVM::Type.const_int` emit `Int128` literals. ([#7135](https://github.com/crystal-lang/crystal/pull/7135), thanks @bcardiff) - Fixed ICE related to named tuples. ([#7163](https://github.com/crystal-lang/crystal/pull/7163), thanks @asterite) @@ -1850,12 +1867,12 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor internals regarding overflow. ([#7262](https://github.com/crystal-lang/crystal/pull/7262), thanks @bcardiff) - Refactor `Crystal::Codegen::Target` and consolidate triple handling. ([#7282](https://github.com/crystal-lang/crystal/pull/7282), [#7317](https://github.com/crystal-lang/crystal/pull/7317), thanks @RX14, @bcardiff) -## Tools +### Tools - Update README template. ([#7118](https://github.com/crystal-lang/crystal/pull/7118), thanks @mamantoha) - Capitalise Crystal in CLI output. ([#7224](https://github.com/crystal-lang/crystal/pull/7224), thanks @dwightwatson) -### Formatter +#### Formatter - Fixed formatting of multiline literal elements. ([#7048](https://github.com/crystal-lang/crystal/pull/7048), thanks @MakeNowJust) - Fixed formatting of heredoc with interpolations. ([#7184](https://github.com/crystal-lang/crystal/pull/7184), thanks @MakeNowJust) @@ -1866,7 +1883,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor remove quotes from overflow symbols in formatter spec. ([#6968](https://github.com/crystal-lang/crystal/pull/6968), thanks @r00ster91) - Major rework of `crystal tool format` command. ([#7257](https://github.com/crystal-lang/crystal/pull/7257), thanks @MakeNowJust) -### Doc generator +#### Doc generator - **(security)** Prevent XSS via args. ([#7056](https://github.com/crystal-lang/crystal/pull/7056), thanks @MakeNowJust) - Fixed generation of toplevel. ([#7063](https://github.com/crystal-lang/crystal/pull/7063), thanks @MakeNowJust) @@ -1878,7 +1895,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixed missing keywords in `Doc::Highlighter`. ([#7054](https://github.com/crystal-lang/crystal/pull/7054), thanks @MakeNowJust) - Add `--format` option to docs command. ([#6982](https://github.com/crystal-lang/crystal/pull/6982), thanks @mniak) -## Others +### Others - CI improvements and housekeeping. ([#7018](https://github.com/crystal-lang/crystal/pull/7018), [#7043](https://github.com/crystal-lang/crystal/pull/7043), [#7133](https://github.com/crystal-lang/crystal/pull/7133), [#7139](https://github.com/crystal-lang/crystal/pull/7139), [#7230](https://github.com/crystal-lang/crystal/pull/7230), [#7227](https://github.com/crystal-lang/crystal/pull/7227), [#7263](https://github.com/crystal-lang/crystal/pull/7263), thanks @bcardiff) - CI split formatting check. ([#7228](https://github.com/crystal-lang/crystal/pull/7228), thanks @bcardiff) @@ -1890,17 +1907,18 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Update ISSUE_TEMPLATE to include forum. ([#7301](https://github.com/crystal-lang/crystal/pull/7301), thanks @straight-shoota) - Update LICENSE's copyright year. ([#7246](https://github.com/crystal-lang/crystal/pull/7246), thanks @matiasgarciaisaia) -# 0.27.0 (2018-11-01) +## [0.27.0] - 2018-11-01 +[0.27.0]: https://github.com/crystal-lang/crystal/releases/0.27.0 -## Language changes +### Language changes - **(breaking-change)** Disallow comma after newline in argument list. ([#6514](https://github.com/crystal-lang/crystal/pull/6514), thanks @asterite) -### Macros +#### Macros - Add `Generic#resolve` and `Generic#resolve?` macro methods. ([#6617](https://github.com/crystal-lang/crystal/pull/6617), thanks @asterite) -## Standard library +### Standard library - Fixed `v1`, `v2`, `v3`, `v4`, `v5` methods of `UUID`. ([#6952](https://github.com/crystal-lang/crystal/pull/6952), thanks @r00ster91) - Fixed multiple docs typos and phrasing in multiple places. ([#6778](https://github.com/crystal-lang/crystal/pull/6778), [#6963](https://github.com/crystal-lang/crystal/pull/6963), thanks @r00ster91) @@ -1912,11 +1930,11 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Improve docs on properties generated by `property?`. ([#6682](https://github.com/crystal-lang/crystal/pull/6682), thanks @epergo) - Add docs to top level namespace constants. ([#6971](https://github.com/crystal-lang/crystal/pull/6971), thanks @r00ster91) -### Macros +#### Macros - Fix typos in `StringLiteral#gsub` and `#tr` errors. ([#6925](https://github.com/crystal-lang/crystal/pull/6925), thanks @r00ster91) -### Numeric +#### Numeric - **(breaking-change)** Disallow `rand` with zero value. ([#6686](https://github.com/crystal-lang/crystal/pull/6686), thanks @oprypin) - **(breaking-change)** Let `==` and `!=` compare the values instead of bits when dealing with signed vs unsigned integers. ([#6689](https://github.com/crystal-lang/crystal/pull/6689), thanks @asterite) @@ -1926,13 +1944,13 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add random support for `BigInt`. ([#6687](https://github.com/crystal-lang/crystal/pull/6687), thanks @oprypin) - Add docs related to `Float::Printer::*`. ([#5438](https://github.com/crystal-lang/crystal/pull/5438), thanks @Sija) -### Text +#### Text - Add `String::Builder#chomp!` returns self. ([#6583](https://github.com/crystal-lang/crystal/pull/6583), thanks @Sija) - Add `:default` to colorize and document `ColorRGB`, `Color256`. ([#6427](https://github.com/crystal-lang/crystal/pull/6427), thanks @r00ster91) - Add `String::Formatter` support for `c` flag and improve docs. ([#6758](https://github.com/crystal-lang/crystal/pull/6758), thanks @r00ster91) -### Collections +#### Collections - **(breaking-change)** Replace `Indexable#at` with `#fetch`. Remove `Hash#fetch(key)` as alias of `Hash#[]`. ([#6296](https://github.com/crystal-lang/crystal/pull/6296), thanks @AlexWayfer) - Add `Hash/Indexable#dig/dig?`. ([#6719](https://github.com/crystal-lang/crystal/pull/6719), thanks @Sija) @@ -1941,7 +1959,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Optimize `Indexable#join` when all elements are strings. ([#6635](https://github.com/crystal-lang/crystal/pull/6635), thanks @asterite) - Optimize `Array#skip`. ([#6946](https://github.com/crystal-lang/crystal/pull/6946), thanks @asterite) -### Serialization +#### Serialization - Fixed `YAML::Schema::FailSafe.parse` and `parse_all`. ([#6790](https://github.com/crystal-lang/crystal/pull/6790), thanks @r00ster91) - Fixed order of `xmlns` and prefix in `XML::Builder#namespace`. ([#6743](https://github.com/crystal-lang/crystal/pull/6743), thanks @yeeunmariakim) @@ -1951,7 +1969,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add ability to quote values always in `CSV.build`. ([#6723](https://github.com/crystal-lang/crystal/pull/6723), thanks @maiha) - Refactor how empty properties are handled in `JSON::Serializable` and `YAML::Serializable`. ([#6539](https://github.com/crystal-lang/crystal/pull/6539), thanks @r00ster91) -### Time +#### Time - **(breaking-change)** Rename `Time#epoch` to `Time#to_unix`. Also `#epoch_ms` to `#to_unix_ms`, and `#epoch_f` to `#to_unix_f`. ([#6662](https://github.com/crystal-lang/crystal/pull/6662), thanks @straight-shoota) - Fixed spec for `Time::Location.load_local` with `TZ=nil`. ([#6740](https://github.com/crystal-lang/crystal/pull/6740), thanks @straight-shoota) @@ -1964,7 +1982,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor time specs. ([#6574](https://github.com/crystal-lang/crystal/pull/6574), thanks @straight-shoota) - Add docs for singular method aliases, add `Int#microsecond` alias. ([#6297](https://github.com/crystal-lang/crystal/pull/6297), thanks @Sija) -### Files +#### Files - **(breaking-change)** Remove `Tempfile`. Use `File.tempfile` or `File.tempname`. ([#6485](https://github.com/crystal-lang/crystal/pull/6485), thanks @straight-shoota) - Fixed missing closed status check of FDs when creating a subprocess. ([#6641](https://github.com/crystal-lang/crystal/pull/6641), thanks @Timbus) @@ -1977,7 +1995,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor `Crystal::System::FileDescriptor` to use `@fd` ivar directly. ([#6703](https://github.com/crystal-lang/crystal/pull/6703), thanks @straight-shoota) - Refactor `{Zlib,Gzip,Flate}::Reader#unbuffered_rewind` to use `check_open`. ([#6958](https://github.com/crystal-lang/crystal/pull/6958), thanks @Sija) -### Networking +#### Networking - **(breaking-change)** Remove deprecated alias `HTTP::Server#bind_ssl`. Use `HTTP::Server#bind_tls`. ([#6699](https://github.com/crystal-lang/crystal/pull/6699), thanks @straight-shoota) - Add `Socket::Address#pretty_print` and `#inspect`. ([#6704](https://github.com/crystal-lang/crystal/pull/6704), thanks @straight-shoota) @@ -1991,31 +2009,31 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Improve specs for `HTTP::Server#close`. ([#5958](https://github.com/crystal-lang/crystal/pull/5958), thanks @straight-shoota) - Improve specs for socket. ([#6711](https://github.com/crystal-lang/crystal/pull/6711), thanks @straight-shoota) -### Crypto +#### Crypto - Fixed OpenSSL bindings to work with LibreSSL. ([#6917](https://github.com/crystal-lang/crystal/pull/6917), thanks @LVMBDV) - Add support for OpenSSL 1.1.1. ([#6738](https://github.com/crystal-lang/crystal/pull/6738), thanks @ysbaddaden) -### Concurrency +#### Concurrency - Improve POSIX threads integration regarding locking, error and resource management. ([#6944](https://github.com/crystal-lang/crystal/pull/6944), thanks @ysbaddaden) - Remove unintended public methods from `Channel`. ([#6714](https://github.com/crystal-lang/crystal/pull/6714), thanks @asterite) - Refactor `Fiber`/`Scheduler` to isolate responsibilities. ([#6897](https://github.com/crystal-lang/crystal/pull/6897), thanks @ysbaddaden) - Refactor specs that relied on `Fiber.yield` behavior. ([#6953](https://github.com/crystal-lang/crystal/pull/6953), thanks @ysbaddaden) -### System +#### System - Fixed fork and signal child handlers. ([#6426](https://github.com/crystal-lang/crystal/pull/6426), thanks @ysbaddaden) - Use blocking `IO` on a TTY if it can't be reopened. ([#6660](https://github.com/crystal-lang/crystal/pull/6660), thanks @Timbus) - Refactor `Process` in preparation for Windows support. ([#6744](https://github.com/crystal-lang/crystal/pull/6744), thanks @RX14) -### Spec +#### Spec - Allow `pending` to be used without blocks. ([#6732](https://github.com/crystal-lang/crystal/pull/6732), thanks @tswicegood) - Add `be_empty` expectation. ([#6614](https://github.com/crystal-lang/crystal/pull/6614), thanks @mamantoha) - Add specs for expectation methods. ([#6512](https://github.com/crystal-lang/crystal/pull/6512), thanks @rodrigopinto) -## Compiler +### Compiler - Fixed don't "ambiguous match" if there's an exact match. ([#6618](https://github.com/crystal-lang/crystal/pull/6618), thanks @asterite) - Fixed allow annotations inside enums. ([#6713](https://github.com/crystal-lang/crystal/pull/6713), thanks @asterite) @@ -2035,79 +2053,80 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Improve parsing of `asm` operands. ([#6688](https://github.com/crystal-lang/crystal/pull/6688), thanks @RX14) - Refactor rescue block codegen for Windows. ([#6649](https://github.com/crystal-lang/crystal/pull/6649), thanks @RX14) -## Tools +### Tools - Improve installation section in README template. ([#6914](https://github.com/crystal-lang/crystal/pull/6914), [#6942](https://github.com/crystal-lang/crystal/pull/6942), thanks @r00ster91) - Improve contributors section in README template. ([#7005](https://github.com/crystal-lang/crystal/pull/7005), thanks @r00ster91) -### Formatter +#### Formatter - Fixed formatting of `{% verbatim do %} ... {% end %}` outside macro. ([#6667](https://github.com/crystal-lang/crystal/pull/6667), thanks @MakeNowJust) - Fixed formatting of `//` corner cases. ([#6927](https://github.com/crystal-lang/crystal/pull/6927), thanks @bcardiff) - Improve formatting of `asm` operands. ([#6688](https://github.com/crystal-lang/crystal/pull/6688), thanks @RX14) -### Doc generator +#### Doc generator - Add support for comments after `:nodoc:` marker. ([#6627](https://github.com/crystal-lang/crystal/pull/6627), thanks @Sija) - Fixed browser performance issue with blur filter. ([#6764](https://github.com/crystal-lang/crystal/pull/6764), thanks @girng) - Accessibility improvement in search field. ([#6926](https://github.com/crystal-lang/crystal/pull/6926), thanks @jodylecompte) -## Others +### Others - CI improvements and housekeeping. ([#6658](https://github.com/crystal-lang/crystal/pull/6658), [#6739](https://github.com/crystal-lang/crystal/pull/6739), [#6930](https://github.com/crystal-lang/crystal/pull/6930), thanks @bcardiff, @RX14) - Add `VERSION` file and support for specifying the build commit. ([#6966](https://github.com/crystal-lang/crystal/pull/6966), thanks @bcardiff) - Add support for specifying the build date. ([#6788](https://github.com/crystal-lang/crystal/pull/6788), thanks @peterhoeg) - Update Contributing section in `README.md`. ([#6911](https://github.com/crystal-lang/crystal/pull/6911), thanks @r00ster91) -# 0.26.1 (2018-08-27) +## [0.26.1] - 2018-08-27 +[0.26.1]: https://github.com/crystal-lang/crystal/releases/0.26.1 -## Language changes +### Language changes - **(breaking-change)** Make `self` to be eager evaluated when including modules. ([#6557](https://github.com/crystal-lang/crystal/pull/6557), thanks @bcardiff) -### Macros +#### Macros - Add `accepts_block?` macro method to `Def`. ([#6604](https://github.com/crystal-lang/crystal/pull/6604), thanks @willhbr) -## Standard library +### Standard library -### Macros +#### Macros - Fixed `Object#def_hash` can receive symbols. ([#6531](https://github.com/crystal-lang/crystal/pull/6531), thanks @Sija) -### Collections +#### Collections - Add `Hash#transform_keys` and `Hash#transform_values`. ([#4385](https://github.com/crystal-lang/crystal/pull/4385), thanks @deepj) -### Serialization +#### Serialization - Fixed `JSON::Serializable` and `YAML::Serializable` clashing with custom initializers. ([#6458](https://github.com/crystal-lang/crystal/pull/6458), thanks @kostya) -### Time +#### Time - Fixed docs for `Time::Format`. ([#6578](https://github.com/crystal-lang/crystal/pull/6578), thanks @straight-shoota) -### Files +#### Files - Fixed zlib handling of buffer error. ([#6610](https://github.com/crystal-lang/crystal/pull/6610), thanks @asterite) -### Networking +#### Networking - **(deprecate)** `HTTP::Server#bind_ssl` in favor of `HTTP::Server#bind_tls`. ([#6551](https://github.com/crystal-lang/crystal/pull/6551), thanks @bcardiff) - Add tls scheme to `HTTP::Server#bind`. ([#6533](https://github.com/crystal-lang/crystal/pull/6533), thanks @straight-shoota) - Fixed `HTTP::Server` crash with self-signed certificate. ([#6590](https://github.com/crystal-lang/crystal/pull/6590), thanks @bcardiff) - Refactor `HTTP::Server` specs to use free ports. ([#6530](https://github.com/crystal-lang/crystal/pull/6530), thanks @straight-shoota) -### System +#### System - Improve `STDIN`/`STDOUT`/`STDERR` handling to avoid breaking other programs. ([#6518](https://github.com/crystal-lang/crystal/pull/6518), thanks @Timbus) -### Spec +#### Spec - Fixed `DotFormatter` to flush after every spec. ([#6562](https://github.com/crystal-lang/crystal/pull/6562), thanks @asterite) - Add support for Windows. ([#6497](https://github.com/crystal-lang/crystal/pull/6497), thanks @RX14) -## Compiler +### Compiler - Fixed evaluate yield expressions in macros. ([#6587](https://github.com/crystal-lang/crystal/pull/6587), thanks @asterite) - Fixed presence check of named argument via external name. ([#6560](https://github.com/crystal-lang/crystal/pull/6560), thanks @asterite) @@ -2116,31 +2135,32 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixed parsing newline after macro control expression. ([#6607](https://github.com/crystal-lang/crystal/pull/6607), thanks @asterite) - Refactor use enum instead of hardcoded string values for emit kinds. ([#6515](https://github.com/crystal-lang/crystal/pull/6515), thanks @bew) -## Tools +### Tools -### Formatter +#### Formatter - Fixed formatting of newline before `&.method` in call. ([#6535](https://github.com/crystal-lang/crystal/pull/6535), thanks @MakeNowJust) - Fixed formatting of empty heredoc. ([#6567](https://github.com/crystal-lang/crystal/pull/6567), thanks @MakeNowJust) - Fixed formatting of string literal in interpolation. ([#6568](https://github.com/crystal-lang/crystal/pull/6568), thanks @MakeNowJust) - Fixed formatting of comments in case when. ([#6595](https://github.com/crystal-lang/crystal/pull/6595), thanks @asterite) -### Doc generator +#### Doc generator - Add Menlo font family and fix ordering. ([#6602](https://github.com/crystal-lang/crystal/pull/6602), thanks @slice) -### Playground +#### Playground - Fixed internal link. ([#6596](https://github.com/crystal-lang/crystal/pull/6596), thanks @omarroth) -## Others +### Others - CI improvements and housekeeping. ([#6550](https://github.com/crystal-lang/crystal/pull/6550), [#6612](https://github.com/crystal-lang/crystal/pull/6612), thanks @bcardiff) - Add `pkg-config` as Linux package dependency. ([distribution-scripts#16](https://github.com/crystal-lang/distribution-scripts/pull/16), thanks @bcardiff) -# 0.26.0 (2018-08-09) +## [0.26.0] - 2018-08-09 +[0.26.0]: https://github.com/crystal-lang/crystal/releases/0.26.0 -## Language changes +### Language changes - **(breaking-change)** Revert do not collapse unions for sibling types. ([#6351](https://github.com/crystal-lang/crystal/pull/6351), thanks @asterite) - **(breaking-change)** Constant lookup context in macro is now lexical. ([#5354](https://github.com/crystal-lang/crystal/pull/5354), thanks @MakeNowJust) @@ -2149,21 +2169,21 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add `&+` `&-` `&*` `&**` operators parsing. NB: No behaviour is assigned to these operators yet. ([#6329](https://github.com/crystal-lang/crystal/pull/6329), thanks @bcardiff) - Add support for empty `case` without `when`. ([#6367](https://github.com/crystal-lang/crystal/pull/6367), thanks @straight-shoota) -### Macros +#### Macros - Add `pp!` and `p!` macro methods. ([#6374](https://github.com/crystal-lang/crystal/pull/6374), [#6476](https://github.com/crystal-lang/crystal/pull/6476), thanks @straight-shoota) -## Standard library +### Standard library - Fix docs for `Pointer`. ([#6494](https://github.com/crystal-lang/crystal/pull/6494), thanks @fxn) - Fix docs of `UUID` enums. ([#6496](https://github.com/crystal-lang/crystal/pull/6496), thanks @r00ster91) -### Numeric +#### Numeric - Fixed `Random#rand(Range(Float, Float))` to return `Float`. ([#6445](https://github.com/crystal-lang/crystal/pull/6445), thanks @straight-shoota) - Add docs of big module overloads. ([#6336](https://github.com/crystal-lang/crystal/pull/6336), thanks @laginha87) -### Text +#### Text - **(breaking-change)** `String#from_utf16(pointer : Pointer(UInt16))` returns now `{String, Pointer(UInt16)}`. ([#6333](https://github.com/crystal-lang/crystal/pull/6333), thanks @straight-shoota) - Add support for unicode 11.0.0. ([#6505](https://github.com/crystal-lang/crystal/pull/6505), thanks @asterite) @@ -2171,12 +2191,12 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add `ECR.render` for rendering directly as `String`. ([#6371](https://github.com/crystal-lang/crystal/pull/6371), thanks @straight-shoota) - Fix docs for `Char` ([#6487](https://github.com/crystal-lang/crystal/pull/6487), thanks @r00ster91) -### Collections +#### Collections - Add docs for `StaticArray`. ([#6404](https://github.com/crystal-lang/crystal/pull/6404), [#6488](https://github.com/crystal-lang/crystal/pull/6488), thanks @straight-shoota, @r00ster91, @hinrik) - Refactor `Array#concat`. ([#6493](https://github.com/crystal-lang/crystal/pull/6493), thanks @fxn) -### Serialization +#### Serialization - **(breaking-change)** Add a maximum nesting level to prevent stack overflow on `YAML::Builder` and `JSON::Builder`. ([#6322](https://github.com/crystal-lang/crystal/pull/6322), thanks @asterite) - Fixed compatibility for libyaml 0.2.1 regarding document end marker `...`. ([#6287](https://github.com/crystal-lang/crystal/pull/6287), thanks @straight-shoota) @@ -2184,20 +2204,20 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixed docs for `JSON::Any`, `JSON::Serialization` and `YAML::Serialization`. ([#6460](https://github.com/crystal-lang/crystal/pull/6460), [#6491](https://github.com/crystal-lang/crystal/pull/6491), thanks @delef, @bmulvihill) -### Time +#### Time - **(breaking-change)** Make location a required argument for `Time.parse`. ([#6369](https://github.com/crystal-lang/crystal/pull/6369), thanks @straight-shoota) - Add `Time.parse!`, `Time.parse_utc`, `Time.parse_local`. ([#6369](https://github.com/crystal-lang/crystal/pull/6369), thanks @straight-shoota) - Fix docs comment missing ([#6387](https://github.com/crystal-lang/crystal/pull/6387), thanks @faustinoaq) -### Files +#### Files - **(breaking-change)** Remove `File.each_line` method that returns an iterator. Use `IO#each_line`. ([#6301](https://github.com/crystal-lang/crystal/pull/6301), thanks @asterite) - Fixed `File.join` when path separator is a component argument. ([#6328](https://github.com/crystal-lang/crystal/pull/6328), thanks @icyleaf) - Fixed `Dir.glob` can now list broken symlinks. ([#6466](https://github.com/crystal-lang/crystal/pull/6466), thanks @straight-shoota) - Add `File` and `Dir` support for Windows. ([#5623](https://github.com/crystal-lang/crystal/pull/5623), thanks @RX14) -### Networking +#### Networking - **(breaking-change)** Drop `HTTP::Server#tls` in favor of `HTTP::Server#bind_ssl`. ([#5960](https://github.com/crystal-lang/crystal/pull/5960), thanks @straight-shoota) - **(breaking-change)** Rename alias `HTTP::Handler::Proc` to `HTTP::Handler::HandlerProc`. ([#6453](https://github.com/crystal-lang/crystal/pull/6453), thanks @jwoertink) @@ -2217,25 +2237,25 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Add `application/wasm` to the default MIME types of `HTTP::StaticFileHandler`. ([#6377](https://github.com/crystal-lang/crystal/pull/6377), thanks @MakeNowJust) - Add `URI#absolute?` and `URI#relative?`. ([#6311](https://github.com/crystal-lang/crystal/pull/6311), thanks @mamantoha) -### Crypto +#### Crypto - Fixed `Crypto::Bcrypt::Password#==` was hiding `Reference#==(other)`. ([#6356](https://github.com/crystal-lang/crystal/pull/6356), thanks @straight-shoota) -### Concurrency +#### Concurrency - Fixed `Atomic#swap` with reference types. ([#6428](https://github.com/crystal-lang/crystal/pull/6428), thanks @Exilor) -### System +#### System - Fixed raise `Errno` if `Process.new` fails to exec. ([#6501](https://github.com/crystal-lang/crystal/pull/6501), thanks @straight-shoota, @lbguilherme) - Add support for `WinError` UTF-16 string messages. ([#6442](https://github.com/crystal-lang/crystal/pull/6442), thanks @straight-shoota) - Refactor platform specifics from `ENV` to `Crystal::System::Env` and implement for Windows. ([#6333](https://github.com/crystal-lang/crystal/pull/6333), [#6499](https://github.com/crystal-lang/crystal/pull/6499), thanks @straight-shoota) -### Spec +#### Spec - Add [TAP](https://testanything.org/) formatter to spec suite. ([#6286](https://github.com/crystal-lang/crystal/pull/6286), thanks @straight-shoota) -## Compiler +### Compiler - Fixed named arguments expansion from double splat clash with local variable names. ([#6378](https://github.com/crystal-lang/crystal/pull/6378), thanks @asterite) - Fixed auto assigned ivars arguments expansions when clash with keywords. ([#6379](https://github.com/crystal-lang/crystal/pull/6379), thanks @asterite) @@ -2264,92 +2284,94 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Refactor `Crystal::Call#check_visibility` and extract type methods. ([#6484](https://github.com/crystal-lang/crystal/pull/6484), thanks @asterite, @bcardiff) - Change how metaclasses are shown. Use `Foo.class` instead of `Foo:Class`. ([#6439](https://github.com/crystal-lang/crystal/pull/6439), thanks @RX14) -## Tools +### Tools - Flatten project structure created by `crystal init`. ([#6317](https://github.com/crystal-lang/crystal/pull/6317), thanks @straight-shoota) -### Formatter +#### Formatter - Fixed formatting of `{ {1}.foo, ...}` like expressions. ([#6300](https://github.com/crystal-lang/crystal/pull/6300), thanks @asterite) - Fixed formatting of `when` with numbers. Use right alignment only if all are number literals. ([#6392](https://github.com/crystal-lang/crystal/pull/6392), thanks @MakeNowJust) - Fixed formatting of comment in case's else. ([#6393](https://github.com/crystal-lang/crystal/pull/6393), thanks @MakeNowJust) - Fixed code fence when language is not crystal will not be formatted. ([#6424](https://github.com/crystal-lang/crystal/pull/6424), thanks @asterite) -### Doc generator +#### Doc generator - Add line numbers at link when there are duplicated filenames in "Defined in:" section. ([#6280](https://github.com/crystal-lang/crystal/pull/6280), [#6489](https://github.com/crystal-lang/crystal/pull/6489), thanks @r00ster91) - Fix docs navigator not scrolling into open type on page load. ([#6420](https://github.com/crystal-lang/crystal/pull/6420), thanks @soanvig) -## Others +### Others - Fixed `system_spec` does no longer emit errors messages on BSD platforms. ([#6289](https://github.com/crystal-lang/crystal/pull/6289), thanks @jcs) - Fixed compilation issue when running spec against compiler and std together. ([#6312](https://github.com/crystal-lang/crystal/pull/6312), thanks @straight-shoota) - Add support for LLVM 6.0. ([#6381](https://github.com/crystal-lang/crystal/pull/6381), [#6380](https://github.com/crystal-lang/crystal/pull/6380), [#6383](https://github.com/crystal-lang/crystal/pull/6383), thanks @felixbuenemann) - CI improvements and housekeeping. ([#6313](https://github.com/crystal-lang/crystal/pull/6313), [#6337](https://github.com/crystal-lang/crystal/pull/6337), [#6407](https://github.com/crystal-lang/crystal/pull/6407), [#6408](https://github.com/crystal-lang/crystal/pull/6408), [#6315](https://github.com/crystal-lang/crystal/pull/6315), thanks @bcardiff, @MakeNowJust, @r00ster91, @maiha) -# 0.25.1 (2018-06-27) +## [0.25.1] - 2018-06-27 +[0.25.1]: https://github.com/crystal-lang/crystal/releases/0.25.1 -## Standard library +### Standard library -### Macros +#### Macros - Fixed `Object.delegate` is now able to be used with `[]=` methods. ([#6178](https://github.com/crystal-lang/crystal/pull/6178), thanks @straight-shoota) - Fixed `p!` `pp!` are now able to be used with tuples. ([#6244](https://github.com/crystal-lang/crystal/pull/6244), thanks @bcardiff) - Add `#copy_with` method to structs generated by `record` macro. ([#5736](https://github.com/crystal-lang/crystal/pull/5736), thanks @chris-baynes) - Add docs for `ArrayLiteral#push` and `#unshift`. ([#6232](https://github.com/crystal-lang/crystal/pull/6232), thanks @MakeNowJust) -### Collections +#### Collections - Add docs for `Indexable#zip` and `#zip?` methods. ([#5734](https://github.com/crystal-lang/crystal/pull/5734), thanks @rodrigopinto) -### Serialization +#### Serialization - Add `#dup` and `#clone` for `JSON::Any` and `YAML::Any`. ([6266](https://github.com/crystal-lang/crystal/pull/6266), thanks @asterite) - Add docs example of nesting mappings to `YAML.builder`. ([#6097](https://github.com/crystal-lang/crystal/pull/6097), thanks @kalinon) -### Time +#### Time - Fixed docs regarding formatting and parsing `Time`. ([#6208](https://github.com/crystal-lang/crystal/pull/6208), [#6214](https://github.com/crystal-lang/crystal/pull/6214), thanks @r00ster91 and @straight-shoota) - Fixed `Time` internals for future Windows support. ([#6181](https://github.com/crystal-lang/crystal/pull/6181), thanks @RX14) - Add `Time::Span#microseconds`, `Int#microseconds` and `Float#microseconds`. ([#6272](https://github.com/crystal-lang/crystal/pull/6272), thanks @asterite) - Add specs. ([#6174](https://github.com/crystal-lang/crystal/pull/6174), thanks @straight-shoota) -### Files +#### Files - Fixed `File.extname` edge case. ([#6234](https://github.com/crystal-lang/crystal/pull/6234), thanks @bcardiff) - Fixed `FileInfo#flags` return value. ([#6248](https://github.com/crystal-lang/crystal/pull/6248), thanks @fgimian) -### Networking +#### Networking - Fixed `IO#write(slice : Bytes)` won't write information if slice is empty. ([#6269](https://github.com/crystal-lang/crystal/pull/6269), thanks @asterite) - Fixed docs regarding `HTTP::Server#bind_tcp` method. ([#6179](https://github.com/crystal-lang/crystal/pull/6179), [#6233](https://github.com/crystal-lang/crystal/pull/6233), thanks @straight-shoota and @MakeNowJust) - Add Etag support in `HTTP::StaticFileHandler`. ([#6145](https://github.com/crystal-lang/crystal/pull/6145), thanks @emq) -### Misc +#### Misc - Fixed `mmap` usage on OpenBSD 6.3+. ([#6250](https://github.com/crystal-lang/crystal/pull/6250), thanks @jcs) - Fixed `big/big_int`, `big/big_float`, etc are now able to be included directly. ([#6267](https://github.com/crystal-lang/crystal/pull/6267), thanks @asterite) - Refactor dependency in `Crystal::Hasher` to avoid load order issues. ([#6184](https://github.com/crystal-lang/crystal/pull/6184), thanks @ysbaddaden) -## Compiler +### Compiler - Fixed a leakage of unbounded generic type variable and show error. ([#6128](https://github.com/crystal-lang/crystal/pull/6128), thanks @asterite) - Fixed error message when lookup of library fails and lib's name contains non-alpha chars. ([#6187](https://github.com/crystal-lang/crystal/pull/6187), thanks @oprypin) - Fixed integer kind deduction for very large negative numbers. ([#6182](https://github.com/crystal-lang/crystal/pull/6182), thanks @rGradeStd) - Refactor specs tempfiles and data files usage in favor of portability ([#5951](https://github.com/crystal-lang/crystal/pull/5951), thanks @straight-shoota) - Improve formatting and information in some compiler error messages. ([#6261](https://github.com/crystal-lang/crystal/pull/6261), thanks @RX14) -## Tools +### Tools -### Formatter +#### Formatter - Fixed crash when semicolon after block paren were present. ([#6192](https://github.com/crystal-lang/crystal/pull/6192), thanks @MakeNowJust) - Fixed invalid code produced when heredoc and comma were present. ([#6222](https://github.com/crystal-lang/crystal/pull/6222), thanks @straight-shoota and @MakeNowJust) - Fixed crash when one-liner `begin`/`rescue` were present. ([#6274](https://github.com/crystal-lang/crystal/pull/6274), thanks @asterite) -### Doc generator +#### Doc generator - Fixed JSON export that prevent jumping to constant. ([#6218](https://github.com/crystal-lang/crystal/pull/6218), thanks @straight-shoota) - Fixed crash when virtual types were reached. ([#6246](https://github.com/crystal-lang/crystal/pull/6246), thanks @bcardiff) -## Misc +### Misc - CI improvements and housekeeping. ([#6193](https://github.com/crystal-lang/crystal/pull/6193), [#6211](https://github.com/crystal-lang/crystal/pull/6211), [#6209](https://github.com/crystal-lang/crystal/pull/6209), [#6221](https://github.com/crystal-lang/crystal/pull/6221), [#6260](https://github.com/crystal-lang/crystal/pull/6260), thanks @bcardiff, @kostya and @r00ster91) - Update man page. ([#6259](https://github.com/crystal-lang/crystal/pull/6259), thanks @docelic) -# 0.25.0 (2018-06-11) +## [0.25.0] - 2018-06-11 +[0.25.0]: https://github.com/crystal-lang/crystal/releases/0.25.0 -## New features and breaking changes +### New features and breaking changes - **(breaking-change)** Time zones has been added to `Time`. ([#5324](https://github.com/crystal-lang/crystal/pull/5324), [#5819](https://github.com/crystal-lang/crystal/pull/5819), thanks @straight-shoota) - **(breaking-change)** Drop `HTTP.rfc1123_date` in favor of `HTTP.format_time` and add time format implementations for ISO-8601, RFC-3339, and RFC-2822. ([#5123](https://github.com/crystal-lang/crystal/pull/5123), thanks @straight-shoota) - **(breaking-change)** `crystal deps` is removed, use `shards`. ([#5544](https://github.com/crystal-lang/crystal/pull/5544), thanks @asterite) @@ -2416,7 +2438,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Documentation generator: improved navigation, searching, rendering and SEO. ([#5229](https://github.com/crystal-lang/crystal/pull/5229), [#5795](https://github.com/crystal-lang/crystal/pull/5795), [#5990](https://github.com/crystal-lang/crystal/pull/5990), [#5657](https://github.com/crystal-lang/crystal/pull/5657), [#6073](https://github.com/crystal-lang/crystal/pull/6073), thanks @straight-shoota, @Sija and @j8r) - Playground: Add button in playground to run formatter. ([#3652](https://github.com/crystal-lang/crystal/pull/3652), thanks @samueleaton) -## Standard library bugs fixed +### Standard library bugs fixed - Fixed `String#sub` handling of negative indexes. ([#5491](https://github.com/crystal-lang/crystal/pull/5491), thanks @MakeNowJust) - Fixed `String#gsub` in non-ascii strings. ([#5350](https://github.com/crystal-lang/crystal/pull/5350), thanks @straight-shoota) - Fixed `String#dump` for UTF-8 characters higher than `\uFFFF`. ([#5668](https://github.com/crystal-lang/crystal/pull/5668), thanks @straight-shoota) @@ -2464,7 +2486,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixes missing stdarg.cr for i686-linux-musl. ([#6120](https://github.com/crystal-lang/crystal/pull/6120), thanks @bcardiff) - Spec: Fixed junit spec formatter to emit the correct XML. ([#5463](https://github.com/crystal-lang/crystal/pull/5463), thanks @hanneskaeufler) -## Compiler bugs fixed +### Compiler bugs fixed - Fixed enum generated values when a member has value 0. ([#5954](https://github.com/crystal-lang/crystal/pull/5954), thanks @bew) - Fixed compiler issue when previous compilation was interrupted. ([#5585](https://github.com/crystal-lang/crystal/pull/5585), thanks @asterite) - Fixed compiler error with an empty `ensure` block. ([#5396](https://github.com/crystal-lang/crystal/pull/5396), thanks @MakeNowJust) @@ -2503,7 +2525,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixed missing some missing rules of initializer in initializers macro methods. ([#6077](https://github.com/crystal-lang/crystal/pull/6077), thanks @asterite) - Fixed regression bug related to unreachable code. ([#6045](https://github.com/crystal-lang/crystal/pull/6045), thanks @asterite) -## Tools bugs fixed +### Tools bugs fixed - Several `crystal init` and template improvements. ([#5475](https://github.com/crystal-lang/crystal/pull/5475), [#5355](https://github.com/crystal-lang/crystal/pull/5355), [#4691](https://github.com/crystal-lang/crystal/pull/4691), [#5788](https://github.com/crystal-lang/crystal/pull/5788), [#5644](https://github.com/crystal-lang/crystal/pull/5644), [#6031](https://github.com/crystal-lang/crystal/pull/6031) thanks @woodruffw, @faustinoaq, @bew, @kostya and @MakeNowJust) - Formatter: improve formatting of method call arguments with trailing comments. ([#5492](https://github.com/crystal-lang/crystal/pull/5492), thanks @MakeNowJust) - Formatter: fix formatting of multiline statements. ([#5234](https://github.com/crystal-lang/crystal/pull/5234), [#5901](https://github.com/crystal-lang/crystal/pull/5901), [#6013](https://github.com/crystal-lang/crystal/pull/6013) thanks @MakeNowJust) @@ -2521,7 +2543,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Playground: can now be run with HTTPS. ([#5527](https://github.com/crystal-lang/crystal/pull/5527), thanks @opiation) - Playground: Pretty-print objects in inspector. ([#4601](https://github.com/crystal-lang/crystal/pull/4601), thanks @jgaskins) -## Misc +### Misc - The platform-specific parts of `File` and `IO::FileDescriptor` were moved to `Crystal::System`, as part of preparation for the Windows port. ([#5333](https://github.com/crystal-lang/crystal/pull/5333), [#5553](https://github.com/crystal-lang/crystal/pull/5553), [#5622](https://github.com/crystal-lang/crystal/pull/5622) thanks @RX14) - The platform-specific parts of `Dir` were moved to `Crystal::System`, as part of preparation for the Windows port. ([#5447](https://github.com/crystal-lang/crystal/pull/5447), thanks @RX14) - Incremental contributions regarding Windows support. ([#5422](https://github.com/crystal-lang/crystal/pull/5422), [#5524](https://github.com/crystal-lang/crystal/pull/5524), [#5533](https://github.com/crystal-lang/crystal/pull/5533), [#5538](https://github.com/crystal-lang/crystal/pull/5538), [#5539](https://github.com/crystal-lang/crystal/pull/5539), [#5580](https://github.com/crystal-lang/crystal/pull/5580), [#5947](https://github.com/crystal-lang/crystal/pull/5947) thanks @RX14 and @straight-shoota) @@ -2542,7 +2564,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Improve Ctrl-C handling of spec. ([#5719](https://github.com/crystal-lang/crystal/pull/5719), thanks @MakeNowJust) - Playground: Update to codemirror 5.38.0. ([#6166](https://github.com/crystal-lang/crystal/pull/6166), thanks @bcardiff) -# 0.24.2 (2018-03-08) +## [0.24.2] - 2018-03-08 +[0.24.2]: https://github.com/crystal-lang/crystal/releases/0.24.2 - Fixed an `Index out of bounds` raised during `at_exit` ([#5224](https://github.com/crystal-lang/crystal/issues/5224), [#5565](https://github.com/crystal-lang/crystal/issues/5565), thanks @ysbaddaden) - Re-add `Dir#each` so it complies with `Enumerable` ([#5458](https://github.com/crystal-lang/crystal/issues/5458), thanks @bcardiff) @@ -2553,9 +2576,10 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fixed CI `build` script's `LIBRARY_PATH` ([#5457](https://github.com/crystal-lang/crystal/issues/5457), [#5461](https://github.com/crystal-lang/crystal/issues/5461), thanks @bcardiff) - Fixed formatter bug with upper-cased `fun` names ([#5432](https://github.com/crystal-lang/crystal/issues/5432), [#5434](https://github.com/crystal-lang/crystal/issues/5434), thanks @bew) -# 0.24.1 (2017-12-23) +## [0.24.1] - 2017-12-23 +[0.24.1]: https://github.com/crystal-lang/crystal/releases/0.24.1 -## New features +### New features - Add ThinLTO support for faster release builds in LLVM 4.0 and above. ([#4367](https://github.com/crystal-lang/crystal/issues/4367), thanks @bcardiff) - **(breaking-change)** Add `UUID` type. `Random::Secure.uuid` has been replaced with `UUID.random`. ([#4453](https://github.com/crystal-lang/crystal/issues/4453), thanks @wontruefree) - Add a `BigDecimal` class for arbitrary precision, exact, decimal numbers. ([#4876](https://github.com/crystal-lang/crystal/issues/4876) and [#5255](https://github.com/crystal-lang/crystal/issues/5255), thanks @vegai and @Sija) @@ -2567,7 +2591,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Export a JSON representation of the documentation in the generated output. ([#4746](https://github.com/crystal-lang/crystal/issues/4746) and [#5228](https://github.com/crystal-lang/crystal/issues/5228), thanks @straight-shoota) - Make `gc/none` garbage collection compile again and allow it to be enabled using `-Dgc_none` compiler flag. ([#5314](https://github.com/crystal-lang/crystal/issues/5314), thanks @ysbaddaden) -## Standard library bugs fixed +### Standard library bugs fixed - Make `String#[]` unable to read out-of-bounds when the string ends in a unicode character. ([#5257](https://github.com/crystal-lang/crystal/issues/5257), thanks @Papierkorb) - Fix incorrect parsing of long JSON floating point values. ([#5323](https://github.com/crystal-lang/crystal/issues/5323), thanks @benoist) - Replace the default hash function with one resistant to hash DoS. ([#5146](https://github.com/crystal-lang/crystal/issues/5146), thanks @funny-falcon) @@ -2580,7 +2604,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Fix `IO#blocking=` on OpenBSD. ([#5283](https://github.com/crystal-lang/crystal/issues/5283), thanks @wmoxam) - Fix linking programs in OpenBSD. ([#5282](https://github.com/crystal-lang/crystal/issues/5282), thanks @wmoxam) -## Compiler bugs fixed +### Compiler bugs fixed - Stop incorrectly finding top-level methods when searching for a `super` method. ([#5202](https://github.com/crystal-lang/crystal/issues/5202), thanks @lbguilherme) - Fix parsing regex literals starting with a `;` directly after a call (ex `p /;/`). ([#5208](https://github.com/crystal-lang/crystal/issues/5208), thanks @MakeNowJust) - Correct a case where `Expressions#to_s` could produce invalid output, causing macro expansion to fail. ([#5226](https://github.com/crystal-lang/crystal/issues/5226), thanks @asterite) @@ -2600,13 +2624,14 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Formatter: fix formatting `$1?`. ([#5313](https://github.com/crystal-lang/crystal/issues/5313), thanks @MakeNowJust) - Formatter: ensure to insert a space between `{` and `%` characters to avoid forming `{%` macros. ([#5278](https://github.com/crystal-lang/crystal/issues/5278), thanks @MakeNowJust) -## Misc +### Misc - Fix `Makefile`, CI, and gitignore to use the new documentation path after [#4937](https://github.com/crystal-lang/crystal/issues/4937). ([#5217](https://github.com/crystal-lang/crystal/issues/5217), thanks @straight-shoota) - Miscellaneous code cleanups. ([#5318](https://github.com/crystal-lang/crystal/issues/5318), [#5341](https://github.com/crystal-lang/crystal/issues/5341) and [#5366](https://github.com/crystal-lang/crystal/issues/5366), thanks @bew and @mig-hub) - Documentation fixes. ([#5253](https://github.com/crystal-lang/crystal/issues/5253), [#5296](https://github.com/crystal-lang/crystal/issues/5296), [#5300](https://github.com/crystal-lang/crystal/issues/5300) and [#5322](https://github.com/crystal-lang/crystal/issues/5322), thanks @arcage, @icyleaf, @straight-shoota and @bew) - Fix the in-repository changelog to include 0.24.0. ([#5331](https://github.com/crystal-lang/crystal/pull/5331), thanks @sdogruyol) -# 0.24.0 (2017-10-30) +## [0.24.0] - 2017-10-30 +[0.24.0]: https://github.com/crystal-lang/crystal/releases/0.24.0 - **(breaking-change)** HTTP::Client#post_form is now HTTP::Client.post(form: ...) - **(breaking-change)** Array#reject!, Array#compact! and Array#select! now return self ([#5154](https://github.com/crystal-lang/crystal/pull/5154)) @@ -2657,7 +2682,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) - Support LLVM 5.0 ([#4821](https://github.com/crystal-lang/crystal/pull/4821)) - [Lots of bugs fixed](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.24.0) -# 0.23.1 (2017-07-01) +## [0.23.1] - 2017-07-01 +[0.23.1]: https://github.com/crystal-lang/crystal/releases/0.23.1 * Added `Random::PCG32` generator (See [#4536](https://github.com/crystal-lang/crystal/issues/4536), thanks @konovod) * WebSocket should compare "Upgrade" header value with case insensitive (See [#4617](https://github.com/crystal-lang/crystal/issues/4617), thanks @MakeNowJust) @@ -2665,7 +2691,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Explained "crystal tool expand" in crystal(1) man page (See [#4643](https://github.com/crystal-lang/crystal/issues/4643), thanks @MakeNowJust) * Explained how to detect end of file in `IO` (See [#4661](https://github.com/crystal-lang/crystal/issues/4661), thanks @oprypin) -# 0.23.0 (2017-06-27) +## [0.23.0] - 2017-06-27 +[0.23.0]: https://github.com/crystal-lang/crystal/releases/0.23.0 * **(breaking-change)** `Logger#formatter` takes a `Severity` instead of a `String` (See [#4355](https://github.com/crystal-lang/crystal/issues/4355), [#4369](https://github.com/crystal-lang/crystal/issues/4369), thanks @Sija) * **(breaking-change)** Removed `IO.select` (See [#4392](https://github.com/crystal-lang/crystal/issues/4392), thanks @RX14) @@ -2717,7 +2744,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Fixed "SSL_shutdown: Operation now in progress" error by retrying (See [#3168](https://github.com/crystal-lang/crystal/issues/3168), thanks @akzhan) * Fixed WebSocket negotiation (See [#4386](https://github.com/crystal-lang/crystal/issues/4386), thanks @RX14) -# 0.22.0 (2017-04-20) +## [0.22.0] - 2017-04-20 +[0.22.0]: https://github.com/crystal-lang/crystal/releases/0.22.0 * **(breaking-change)** Removed `Process.new(pid)` is now private (See [#4197](https://github.com/crystal-lang/crystal/issues/4197)) * **(breaking-change)** IO#peek now returns an empty slice on EOF (See [#4240](https://github.com/crystal-lang/crystal/issues/4240), [#4261](https://github.com/crystal-lang/crystal/issues/4261)) @@ -2755,7 +2783,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Fixed honor `--no-color` option in spec (See [#4306](https://github.com/crystal-lang/crystal/issues/4306), thanks @luislavena) * [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.22.0) -# 0.21.1 (2017-03-06) +## [0.21.1] - 2017-03-06 +[0.21.1]: https://github.com/crystal-lang/crystal/releases/0.21.1 * Improved lookup of abstract def implementors (see [#4052](https://github.com/crystal-lang/crystal/issues/4052)) * Improved allocation of objects without pointer instance variables using `malloc_atomic` (see [#4081](https://github.com/crystal-lang/crystal/issues/4081)) @@ -2767,7 +2796,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Fixed `ASTNode#to_s` for `Attribute` (see [#4098](https://github.com/crystal-lang/crystal/issues/4098), thanks @olbat) * [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.21.1) -# 0.21.0 (2017-02-20) +## [0.21.0] - 2017-02-20 +[0.21.0]: https://github.com/crystal-lang/crystal/releases/0.21.0 * **(breaking-change)** The compiler now reuses previous macro run compilations so `{{ run(...) }}` is only re-run if the code changes * **(breaking-change)** Spec: `assert { ... }` is now `it { ... }` (thanks @TheLonelyGhost) @@ -2801,7 +2831,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Updated CONTRIBUTING.md guidelines * [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.21.0) -# 0.20.5 (2017-01-20) +## [0.20.5] - 2017-01-20 +[0.20.5]: https://github.com/crystal-lang/crystal/releases/0.20.5 * Improved performance in `String#index`, `String#rindex` due to Rabin-Karp algorithm (thanks @MakeNowJust). * Improved performance in `Crypto::Bcrypt` (see [#3880](https://github.com/crystal-lang/crystal/issues/3880), thanks @ysbaddaden). @@ -2815,7 +2846,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * `crystal doc` recognizes `crystal-lang/crystal` in any remote (thanks @MaxLap). * [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.5) -# 0.20.4 (2017-01-06) +## [0.20.4] - 2017-01-06 +[0.20.4]: https://github.com/crystal-lang/crystal/releases/0.20.4 * **(breaking change)** A type that wants to convert itself to JSON now must override `to_json(builder : JSON::Builder)` instead of `to_json(io : IO)`. The same is true for custom JSON converters. If you are using `JSON.mapping` then your code will continue to work without changes. * **(breaking change)** Defining a `finalize` method on a struct now gives a compile error @@ -2845,7 +2877,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Lots of improvements and applied consistencies to doc comments (thanks @Sija and @maiha) * [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.4) -## 0.20.3 (2016-12-23) +### [0.20.3] - 2016-12-23 +[0.20.3]: https://github.com/crystal-lang/crystal/releases/0.20.3 * **(breaking change)** `IO#gets`, `IO#each_line`, `String#lines`, `String#each_line`, etc. now chomp lines by default. You can pass `chomp: false` to prevent automatic chomping. Note that `chomp` is `true` by default for argless `IO#gets` (read line) but `false` if args are given. * **(breaking change)** `HTTP::Handler` is now a module instead of a class (thanks @andrewhamon) @@ -2870,7 +2903,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * The `:debug` flag is now present when compiled with `--debug`, useful for doing `flag?(:debug)` in macros (thanks @luislavena) * [Many bug fixes and performance improvements](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.3) -## 0.20.1 (2016-12-05) +### [0.20.1] - 2016-12-05 +[0.20.1]: https://github.com/crystal-lang/crystal/releases/0.20.1 * **(breaking change)** `Set#merge` as renamed to `Set#merge!` * **(breaking change)** `Slice.new(size)` no longer works with non primitive integers and floats @@ -2888,7 +2922,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Support double splat in macros (`{{**...}}`) * [Some bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.1) -## 0.20.0 (2016-11-22) +### [0.20.0] - 2016-11-22 +[0.20.0]: https://github.com/crystal-lang/crystal/releases/0.20.0 * **(breaking change)** Removed `ifdef` from the language * **(breaking change)** Removed `PointerIO` @@ -2922,7 +2957,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Optimized `Array#sort` by using introsort (thanks @c910335) * [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.20.0) -## 0.19.4 (2016-10-07) +### [0.19.4 ] - 2016-10-07 +[0.19.4 ]: https://github.com/crystal-lang/crystal/releases/0.19.4 * Added support for OpenBSD (thanks @wmoxam and @ysbaddaden) * More iconv fixes for FreeBSD (thanks @ysbaddaden) @@ -2933,7 +2969,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added `Char#+(Int)` and `Char#-(Int)` * [A few bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.4) -## 0.19.3 (2016-09-30) +### [0.19.3 ] - 2016-09-30 +[0.19.3 ]: https://github.com/crystal-lang/crystal/releases/0.19.3 * `crystal eval` now accepts some flags like `--stats`, `--release` and `--help` * Added `File.chown` and `File.chmod` (thanks @ysbaddaden) @@ -2941,7 +2978,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added docs to `OAuth` and `OAuth2` * [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.3) -## 0.19.2 (2016-09-16) +### [0.19.2 ] - 2016-09-16 +[0.19.2 ]: https://github.com/crystal-lang/crystal/releases/0.19.2 * Generic type variables no longer need to be single-letter names (for example `class Gen(Foo)` is now possible) * Added syntax to denote free variables: `def foo(x : T) forall T`. The old rule of single-letter name still applies but will be removed in the future. @@ -2956,13 +2994,15 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Error messages no longer include a type trace by default, pass `--error-trace` to show the full trace (the trace is often useless and makes it harder to understand error messages) * [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.2) -## 0.19.1 (2016-09-09) +### [0.19.1 ] - 2016-09-09 +[0.19.1 ]: https://github.com/crystal-lang/crystal/releases/0.19.1 * Types (class, module, etc.) can now be marked as `private`. * Added `WeakRef` (thanks @bcardiff) * [Several bug fixes](https://github.com/crystal-lang/crystal/issues?q=is%3Aclosed+milestone%3A0.19.1) -## 0.19.0 (2016-09-02) +### [0.19.0 ] - 2016-09-02 +[0.19.0 ]: https://github.com/crystal-lang/crystal/releases/0.19.0 * **(breaking change)** Added `select` keyword * **(breaking change)** Removed $global variables. Use @@class variables instead. @@ -3005,47 +3045,55 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Unified String and Char to integer/float conversion API (thanks @jhass) * [Lots of bug fixes](https://github.com/crystal-lang/crystal/milestone/5?closed=1) -## 0.18.7 (2016-07-03) +### [0.18.7] - 2016-07-03 +[0.18.7]: https://github.com/crystal-lang/crystal/releases/0.18.7 * The `compile` command was renamed back to `build`. The `compile` command is deprecated and will be removed in a future version * Fibers now can be spawned with a name * ECR macros can now be required with just `require "ecr"` * [Several bugs fixes and enhancements](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.7+is%3Aclosed) -## 0.18.6 (2016-06-28) +### [0.18.6] - 2016-06-28 +[0.18.6]: https://github.com/crystal-lang/crystal/releases/0.18.6 * `T?` is now parsed as `Union(T, Nil)` outside the type grammar * Added `String#sub` overloads for replacing an index or range with a char or string * [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.6+is%3Aclosed) -## 0.18.5 (2016-06-27) +### [0.18.5] - 2016-06-27 +[0.18.5]: https://github.com/crystal-lang/crystal/releases/0.18.5 * Added `OpenSSL::SSL::Socket#alpn_protocol` * Added `IO#copy(src, desc, limit)` (thanks @jreinert) * Added `TypeNode#instance` macro method * [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.5+is%3Aclosed) -## 0.18.4 (2016-06-21) +### [0.18.4] - 2016-06-21 +[0.18.4]: https://github.com/crystal-lang/crystal/releases/0.18.4 * Fixed [#2887](https://github.com/crystal-lang/crystal/issues/2887) * Fix broken specs -## 0.18.3 (2016-06-21) +### [0.18.3] - 2016-06-21 +[0.18.3]: https://github.com/crystal-lang/crystal/releases/0.18.3 * `TypeNode`: added `<`, `<=`, `>` and `>=` macro methods * [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.3+is%3Aclosed) -## 0.18.2 (2016-06-16) +### [0.18.2] - 2016-06-16 +[0.18.2]: https://github.com/crystal-lang/crystal/releases/0.18.2 * Fixed building Crystal from the source tarball -## 0.18.1 (2016-06-16) +### [0.18.1] - 2016-06-16 +[0.18.1]: https://github.com/crystal-lang/crystal/releases/0.18.1 * Spec: passing `--profile` shows the slowest 10 specs (thanks @mperham) * Added `StringLiteral#>` and `StringLiteral#<` in macros * [Several bugs fixes](https://github.com/crystal-lang/crystal/issues?q=milestone%3A0.18.1+is%3Aclosed) -## 0.18.0 (2016-06-14) +### [0.18.0] - 2016-06-14 +[0.18.0]: https://github.com/crystal-lang/crystal/releases/0.18.0 * **(breaking change)** `IniFile` was renamed to `INI`, and its method `load` renamed to `parse` * **(breaking change)** `Process.getpgid` was renamed to `Process.pgid` @@ -3108,7 +3156,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * `HTTP::ErrorHandler` does not tell the client which exception occurred by default (can be enabled with a `verbose` flag) (thanks @jhass) * Several bug fixes -## 0.17.4 (2016-05-26) +### [0.17.4] - 2016-05-26 +[0.17.4]: https://github.com/crystal-lang/crystal/releases/0.17.4 * Added string literals without interpolations nor escapes: `%q{...}` and `<<-'HEREDOC'`. Also added `%Q{...}` with the same meaning as `%{...}`. * A method that uses `@type` inside a macro expression is now automatically detected as being a `macro def` @@ -3121,7 +3170,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Comparison in macros between `MacroId` and `StringLiteral` or `SymbolLiteral` now work as expected (compares the `id` representation) * Some bug fixes -## 0.17.3 (2016-05-20) +### [0.17.3] - 2016-05-20 +[0.17.3]: https://github.com/crystal-lang/crystal/releases/0.17.3 * Fixed: multiple macro runs executions didn't work well ([#2624](https://github.com/crystal-lang/crystal/issues/2624)) * Fixed incorrect formatting of underscore in unpacked block arguments @@ -3132,11 +3182,13 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Allow splat restriction in splat argument (useful for `Tuple.new`) * Allow double splat restriction in double splat argument (useful for `NamedTuple.new`) -## 0.17.2 (2016-05-18) +### [0.17.2] - 2016-05-18 +[0.17.2]: https://github.com/crystal-lang/crystal/releases/0.17.2 * Fixed crash when using pointerof of constant -## 0.17.1 (2016-05-18) +### [0.17.1] - 2016-05-18 +[0.17.1]: https://github.com/crystal-lang/crystal/releases/0.17.1 * Constants and class vars are no longer initialized before "main". Now their initialization order goes along with "main", similar to how it works in Ruby (much more intuitive) * Added syntax for unpacking block arguments: `foo { |(x, y)| ... }` @@ -3144,7 +3196,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Fixed wrong codgen for tuples/named tuples merge with pass-by-value types * Formatter: fixed incorrect format for named tuple type -## 0.17.0 (2016-05-17) +### [0.17.0] - 2016-05-17 +[0.17.0]: https://github.com/crystal-lang/crystal/releases/0.17.0 * **(breaking change)** Macro defs are now parsed like regular methods. Enclose the body with `{% begin %} .. {% end %}` if you needed that behaviour * **(breaking change)** A union of two tuples of the same size results in a tuple with the unions of the types in each position. This only affects code that later tested a tuple's type with `is_a?`, for example `tuple.is_a?({Int32, String})` @@ -3164,7 +3217,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added docs for XML (thanks @Hamdiakoguz) * Many bug fixes -## 0.16.0 (2016-05-05) +### [0.16.0] - 2016-05-05 +[0.16.0]: https://github.com/crystal-lang/crystal/releases/0.16.0 * **(breaking change)** Instance, class and global variables types must be told to the compiler, [either explicitly or through a series of syntactic rules](http://crystal-lang.org/docs/syntax_and_semantics/type_inference.html) * **(breaking change)** Non-abstract structs cannot be inherited anymore (abstract structs can), check the [docs](http://crystal-lang.org/docs/syntax_and_semantics/structs.html) to know why. In many cases you can use modules instead. @@ -3209,7 +3263,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added docs for `INIFile` (thanks @EvanHahn) * Lots of bug fixes -## 0.15.0 (2016-03-31) +### [0.15.0] - 2016-03-31 +[0.15.0]: https://github.com/crystal-lang/crystal/releases/0.15.0 * **(breaking change)** `!` has now its meaning hardcoded in the language. If you defined it for a type it won't be invoked as a method anymore. * **(breaking change)** `nil?` has now its meaning hardcoded in the language. If you defined it for a type it won't be invoked as a method anymore. @@ -3222,17 +3277,20 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Special variables (`$~` and `$?`) are now accessible after being defined in blocks ([#2194](https://github.com/crystal-lang/crystal/issues/2194)) * Some bugs and regressions fixed -## 0.14.2 (2016-03-22) +### [0.14.2] - 2016-03-22 +[0.14.2]: https://github.com/crystal-lang/crystal/releases/0.14.2 * Fixed regression with formatter ([#2348](https://github.com/crystal-lang/crystal/issues/2348)) * Fixed regression with block return types ([#2347](https://github.com/crystal-lang/crystal/issues/2347)) * Fixed regression with openssl (https://github.com/crystal-lang/crystal/commit/78c12caf2366b01f949046e78ad4dab65d0d80d4) -## 0.14.1 (2016-03-21) +### [0.14.1] - 2016-03-21 +[0.14.1]: https://github.com/crystal-lang/crystal/releases/0.14.1 * Fixed some regressions in the formatter -## 0.14.0 (2016-03-21) +### [0.14.0] - 2016-03-21 +[0.14.0]: https://github.com/crystal-lang/crystal/releases/0.14.0 * **(breaking change)** The syntax of a method argument with a default value and a type restriction is now `def foo(arg : Type = default_value)`. The old `def foo(arg = default_value : Type)` was removed. * **(breaking change)** `Enumerable#take(n)` and `Iterator#take(n)` were renamed to `first(n)` @@ -3258,7 +3316,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Instance variable declarations/initializations now correctly work in generic classes and modules * Lots of bug fixes -## 0.13.0 (2016-03-07) +### [0.13.0] - 2016-03-07 +[0.13.0]: https://github.com/crystal-lang/crystal/releases/0.13.0 * **(breaking change)** `Matrix` was moved to a separate shard: [https://github.com/Exilor/matrix](https://github.com/Exilor/Matrix) * The syntax of a method argument with a default value and a type restriction is now `def foo(arg : Type = default_value)`. Run `crystal tool format` to automatically upgrade existing code to this new syntax. The old `def foo(arg = default_value : Type)` syntax will be removed in a next release. @@ -3285,7 +3344,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added docs for `Math` and `StaticArray` (thanks @Zavydiel, @HeleneMyr) * Many bug fixes and some micro-optimizations -## 0.12.0 (2016-02-16) +### [0.12.0] - 2016-02-16 +[0.12.0]: https://github.com/crystal-lang/crystal/releases/0.12.0 * **(breaking change)** When used with a type declaration, the macros `property`, `getter`, `setter`, etc., declare instance variables with those types. * **(breaking change)** `JSON.mapping` and `YAML.mapping` declare instance variables with the given types. @@ -3307,12 +3367,14 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * `p` now accepts multiple arguments * Many bug fixes and some optimizations -## 0.11.1 (2016-01-25) +### [0.11.1] - 2016-01-25 +[0.11.1]: https://github.com/crystal-lang/crystal/releases/0.11.1 * Fixed [#2050](https://github.com/crystal-lang/crystal/issues/2050), [#2054](https://github.com/crystal-lang/crystal/issues/2054), [#2057](https://github.com/crystal-lang/crystal/issues/2057), [#2059](https://github.com/crystal-lang/crystal/issues/2059), [#2064](https://github.com/crystal-lang/crystal/issues/2064) * Fixed bug: HTTP::Server::Response headers weren't cleared after each request * Formatter would incorrectly change `property x :: Int32` to `property x = uninitialized Int32` -## 0.11.0 (2016-01-23) +### [0.11.0] - 2016-01-23 +[0.11.0]: https://github.com/crystal-lang/crystal/releases/0.11.0 * **(breaking change)** Syntax for type declarations changed from `var :: Type` to `var : Type`. The old syntax is still allowed but will be deprecated in the next version (run `crystal tool format` to automatically fix this) * **(breaking change)** Syntax for uninitialized variables, which used to be `var :: Type`, is now `var = uninitialized Type`. The old syntax is still allowed but will be deprecated in the next version (run `crystal tool format` to automatically fix this) @@ -3329,18 +3391,21 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * `URI.parse` is now faster (thanks @will) * Many bug fixes, some really old ones involving issues with order of declaration -## 0.10.2 (2016-01-13) +### [0.10.2] - 2016-01-13 +[0.10.2]: https://github.com/crystal-lang/crystal/releases/0.10.2 * Fixed Directory Traversal Vulnerability in HTTP::StaticFileHandler (thanks @MakeNowJust) -## 0.10.1 (2016-01-08) +### [0.10.1] - 2016-01-08 +[0.10.1]: https://github.com/crystal-lang/crystal/releases/0.10.1 * Added `Int#popcount` (thanks @rmosolgo) * Added `@[Naked]` attribute for omitting a method's prelude * Check that abstract methods are implemented by subtypes * Some bug fixes -## 0.10.0 (2015-12-23) +### [0.10.0] - 2015-12-23 +[0.10.0]: https://github.com/crystal-lang/crystal/releases/0.10.0 * **(breaking change)** `def` arguments must always be enclosed in parentheses * **(breaking change)** A space is now required before and after def return type restriction @@ -3365,7 +3430,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * The `delegate` macro can now delegate multiple methods to an object (thanks @elthariel) * Added basic YAML generation (thanks @porras) -## 0.9.1 (2015-10-30) +### [0.9.1] - 2015-10-30 +[0.9.1]: https://github.com/crystal-lang/crystal/releases/0.9.1 * Docs search now finds nested entries (thanks @adlerhsieh) * Many corrections and changes to the formatter, for better consistency and less obtrusion. @@ -3377,7 +3443,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added docs for `Random` and `Tempfile` (thanks @adlerhsieh) * Fixed some bugs. -## 0.9.0 (2015-10-16) +### [0.9.0] - 2015-10-16 +[0.9.0]: https://github.com/crystal-lang/crystal/releases/0.9.0 * **(breaking change)** The `CGI` module's functionality has been moved to `URI` and `HTTP::Params` * **(breaking change)** `IO#read()` is now `IO#gets_to_end`. Removed `IO#read(count)`, added `IO#skip(count)` @@ -3420,7 +3487,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added some docs for ECR, Markdown, Hash, File, Time, Time::Span, Colorize, String, SecureRandom, YAML (thanks @adlerhsieh, @chdorner, @vjdhama, @rmosolgo) * Many bug fixes -## 0.8.0 (2015-09-19) +### [0.8.0] - 2015-09-19 +[0.8.0]: https://github.com/crystal-lang/crystal/releases/0.8.0 * **(breaking change)** Renamed a couple of types: `ChannelClosed` -> `Channel::ClosedError`, `UnbufferedChannel` -> `Channel::Unbuffered`, `BufferedChannel` -> `Channel::Buffered`, @@ -3465,7 +3533,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Cast exceptions now raise `TypeCastError` instead of `Exception` (thanks @will) * Many bugs fixes -## 0.7.7 (2015-09-05) +### [0.7.7] - 2015-09-05 +[0.7.7]: https://github.com/crystal-lang/crystal/releases/0.7.7 * **(breaking change)** Reimplemented `Process.run` to allow configuring input, output and error, as well as behaving well regarding non-blocking IO (thanks @technorama) * **(breaking change)** Removed the `alias_method` macro. @@ -3511,7 +3580,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added docs for OptionParser, ENV, Regex, Enumerable, Iterator and some Array methods (thanks @porras, @will, @bjmllr, @PragTob, @decioferreira) * Lots of bug fixes and small improvements -## 0.7.6 (2015-08-13) +### [0.7.6] - 2015-08-13 +[0.7.6]: https://github.com/crystal-lang/crystal/releases/0.7.6 * **(breaking change)** removed support for trailing `while`/`until` ([read this](https://github.com/crystal-lang/crystal/wiki/FAQ#why-trailing-whileuntil-is-not-supported-unlike-ruby)) * **(breaking change)** Renamed `Enumerable#drop` to `Enumerable#skip` @@ -3534,7 +3604,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Fixed `IO#reopen` swapped semantic (thanks @technorama) * Many bug fixes and improvements -## 0.7.5 (2015-07-30) +### [0.7.5] - 2015-07-30 +[0.7.5]: https://github.com/crystal-lang/crystal/releases/0.7.5 * **(breaking change)** `0` is not a prefix for octal numbers anymore. Use `0o` * **(breaking change)** Renamed `MissingKey` to `KeyError` @@ -3575,7 +3646,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Define `$?` in `Process.run` * Lots of bug fixes and small improvements -## 0.7.4 (2015-06-23) +### [0.7.4] - 2015-06-23 +[0.7.4]: https://github.com/crystal-lang/crystal/releases/0.7.4 * Added Float module and remainder (thanks @wmoxam) * Show elapsed time in HTTP::LogHandler (thanks @zamith for the suggestion) @@ -3603,7 +3675,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * The `.crystal` cache dir is now configurable with an ENV variable (thanks @jhass) * Generic type variables names can now also be a single letter followed by a digit. -## 0.7.3 (2015-06-07) +### [0.7.3] - 2015-06-07 +[0.7.3]: https://github.com/crystal-lang/crystal/releases/0.7.3 * Added `Tuple.from_json` and `Tuple.to_json` * The `method_missing` macro now accepts a 1 argument variant that is a Call node. The 3 arguments variant will be deprecated. @@ -3620,7 +3693,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added `CSV.each_row` (both in block and iterator forms) * Important fixes to non-blocking IO logic. -## 0.7.2 (2015-05-26) +### [0.7.2] - 2015-05-26 +[0.7.2]: https://github.com/crystal-lang/crystal/releases/0.7.2 * Improved performance of Regex * Fixed lexing of octal characters and strings (thanks @rhysd) @@ -3643,12 +3717,14 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * `Tuple#class` now returns a proper `Class` (previously it returned a `Tuple` of classes) * Lots of bug fixes. -## 0.7.1 (2015-04-30) +### [0.7.1] - 2015-04-30 +[0.7.1]: https://github.com/crystal-lang/crystal/releases/0.7.1 * Fixed [#597](https://github.com/crystal-lang/crystal/issues/597). * Fixed [#599](https://github.com/crystal-lang/crystal/issues/599). -## 0.7.0 (2015-04-30) +### [0.7.0] - 2015-04-30 +[0.7.0]: https://github.com/crystal-lang/crystal/releases/0.7.0 * Crystal has evented IO by default. Added `spawn` and `Channel`. * Correctly support the X86_64 and X86 ABIs. Now bindings to C APIs that pass and return structs works perfectly fine. @@ -3681,7 +3757,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * The following types are now disallowed in generics (for now): Object, Value, Reference, Number, Int and Float. * Lots of bug fixes, enhancements and optimizations. -## 0.6.1 (2015-03-04) +### [0.6.1] - 2015-03-04 +[0.6.1]: https://github.com/crystal-lang/crystal/releases/0.6.1 * The `class` method now works in all cases. You can now compare classes with `==` and ask their `hash` value. * Block variables can now shadow local variables. @@ -3696,11 +3773,13 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Macros: added `has_attribute?` for enum types, so you can check if an enum has the Flags attribute on it. * Many more small additions and bug fixes. -## 0.6.0 (2015-02-12) +### [0.6.0] - 2015-02-12 +[0.6.0]: https://github.com/crystal-lang/crystal/releases/0.6.0 * Same as 0.5.10 -## 0.5.10 (transitional) (2015-02-12) +### [0.5.10 (transitional)] - 2015-02-12 +[0.5.10 (transitional)]: https://github.com/crystal-lang/crystal/releases/0.5.10 (transitional) * **Note**: This release makes core, breaking changes to the language, and doesn't work out of the box with its accompanying standard library. Use 0.6.0 instead. * Improved error messages related to nilable instance variables. @@ -3709,7 +3788,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * `Pointer` is now correctly considered a struct * Renamed `Function` to `Proc` -## 0.5.9 (2015-02-07) +### [0.5.9] - 2015-02-07 +[0.5.9]: https://github.com/crystal-lang/crystal/releases/0.5.9 * `Random` is now a module, with static methods that default to the `Random::MT19937` class. * Added `Random::ISAAC` engine (thanks @ysbaddaden!) @@ -3738,18 +3818,21 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added a `Logger` class (thanks @ysbaddaden!) * Lots of bugs fixed. -## 0.5.8 (2015-01-16) +### [0.5.8] - 2015-01-16 +[0.5.8]: https://github.com/crystal-lang/crystal/releases/0.5.8 * Added `Random` and `Random::MT19937` (Mersenne Twister) classes (thanks @rhysd). * Docs: removed automatic linking. To link to classes and methods surround with backticks. * Fixed [#328](https://github.com/crystal-lang/crystal/issues/328): `!=` bug. -## 0.5.7 (2015-01-02) +### [0.5.7] - 2015-01-02 +[0.5.7]: https://github.com/crystal-lang/crystal/releases/0.5.7 * Fixed: `doc` command had some hardcoded paths and didn't work * Added: `private def` at the top-level of a file is only available inside that file -## 0.5.6 (2014-31-12) +### [0.5.6] - 2014-31-12 +[0.5.6]: https://github.com/crystal-lang/crystal/releases/0.5.6 * Added a `crystal doc` command to automatically generate documentation for a project using [Markdown](http://daringfireball.net/projects/markdown/) syntax. The style is still ugly but it's quite functional. Now we only need to start documenting things :-) * Removed the old `@:` attribute syntax. @@ -3761,12 +3844,14 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added `Float#nan?`, `Float#infinite?` and `Float#finite?`. * Many other bug fixes and improvements. -## 0.5.5 (2014-12-12) +### [0.5.5] - 2014-12-12 +[0.5.5]: https://github.com/crystal-lang/crystal/releases/0.5.5 * Removed `src` and crystal compiler `libs` directory from CRYSTAL_PATH. * Several bug fixes. -## 0.5.4 (2014-12-04) +### [0.5.4] - 2014-12-04 +[0.5.4]: https://github.com/crystal-lang/crystal/releases/0.5.4 * **(breaking change)** `require "foo"` always looks up in `CRYSTAL_PATH`. `require "./foo"` looks up relative to the requiring file. * **(breaking change)** Renamed `Json` to `JSON`, `Xml` to `XML` and `Yaml` to `YAML` to follow [a convention](https://github.com/crystal-lang/crystal/issues/279). @@ -3785,7 +3870,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added short symbol notation for methods that are operators (i.e. `:+`, `:*`, `:[]`, etc.). * Added `TimeSpan#ago`, `TimeSpan#from_now`, `MonthSpan#ago` and `MonthSpan#from_now`. -## 0.5.3 (2014-11-06) +### [0.5.3] - 2014-11-06 +[0.5.3]: https://github.com/crystal-lang/crystal/releases/0.5.3 * Spec: when a `should` or `should_not` fail, the filename and line number, including the source's line, is included in the error message. * Spec: added `-l` switch to be able to run a spec defined in a line. @@ -3797,7 +3883,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * The syntax for specifying the base type of an enum, `enum Name < BaseType` has been deprecated. Use `enum Name : BaseType`. * Added `Array#<=>` and make it comparable to other arrays. -## 0.5.2 (2014-11-04) +### [0.5.2] - 2014-11-04 +[0.5.2]: https://github.com/crystal-lang/crystal/releases/0.5.2 * New command line interface to the compiler (`crystal build ...`, `crystal run ...`, `crystal spec`, etc.). The default is to compiler and run a program. * `crystal eval` without arguments reads from standard input. @@ -3829,7 +3916,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Consecutive string literals are automatically concatenated by the parser as long as there is a `\` with a newline between them. * Many bug fixes. -## 0.5.1 (2014-10-16) +### [0.5.1] - 2014-10-16 +[0.5.1]: https://github.com/crystal-lang/crystal/releases/0.5.1 * Added [json_mapping](https://github.com/crystal-lang/crystal/blob/master/spec/std/json/mapping_spec.cr) macro. * Added [Signal](https://github.com/crystal-lang/crystal/blob/master/src/signal.cr) module. @@ -3855,11 +3943,13 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Fixed [#233](https://github.com/crystal-lang/crystal/issues/233): Incorrect `no block given` message with new. * Other bug fixes. -## 0.5.0 (2014-09-24) +### [0.5.0] - 2014-09-24 +[0.5.0]: https://github.com/crystal-lang/crystal/releases/0.5.0 * String overhaul, and optimizations -## 0.4.5 (2014-09-24) +### [0.4.5] - 2014-09-24 +[0.4.5]: https://github.com/crystal-lang/crystal/releases/0.4.5 * Define backtick (`) for command execution. * Allow string literals as keys in hash literals: `{"foo": "bar"} # :: Hash(String, String)` @@ -3872,7 +3962,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * String representation includes length. * Upgraded to LLVM 3.5. -## 0.4.4 (2014-09-17) +### [0.4.4] - 2014-09-17 +[0.4.4]: https://github.com/crystal-lang/crystal/releases/0.4.4 * Fixed [#193](https://github.com/crystal-lang/crystal/issues/193): allow initializing an enum value with another's one. * The `record` macro is now variadic, so instead of `record Vec3, [x, y, z]` write `record Vec3, x, y, z`. @@ -3901,11 +3992,13 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added `IO#tty?`. * Some bug fixes. -## 0.4.3 (2014-08-14) +### [0.4.3] - 2014-08-14 +[0.4.3]: https://github.com/crystal-lang/crystal/releases/0.4.3 * Reverted a commit that introduced random crashes. -## 0.4.2 (2014-08-13) +### [0.4.2] - 2014-08-13 +[0.4.2]: https://github.com/crystal-lang/crystal/releases/0.4.2 * Fixed [#187](https://github.com/crystal-lang/crystal/issues/185): mixing `yield` and `block.call` crashes the compiler. * Added `\u` unicode escape sequences inside strings and chars (similar to Ruby). `\x` will be deprecated as it can generate strings with invalid UTF-8 byte sequences. @@ -3914,13 +4007,15 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added the `private` and `protected` visibility modifiers, with the same semantics as Ruby. The difference is that you must place them before a `def` or a macro call. * Some bug fixes. -## 0.4.1 (2014-08-09) +### [0.4.1] - 2014-08-09 +[0.4.1]: https://github.com/crystal-lang/crystal/releases/0.4.1 * Fixed [#185](https://github.com/crystal-lang/crystal/issues/185): `-e` flag stopped working. * Added a `@length` compile-time variable available inside tuples that allows to do loop unrolling. * Some bug fixes. -## 0.4.0 (2014-08-08) +### [0.4.0] - 2014-08-08 +[0.4.0]: https://github.com/crystal-lang/crystal/releases/0.4.0 * Support splats in macros. * Support splats in defs and calls. @@ -3930,7 +4025,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added `Slice(T)`, which is a struct having a pointer and a length. Use this in IO for a safe API. * Some `StaticArray` fixes and enhancements. -## 0.3.5 (2014-07-29) +### [0.3.5] - 2014-07-29 +[0.3.5]: https://github.com/crystal-lang/crystal/releases/0.3.5 * **(breaking change)** Removed the special `->` operator for pointers of structs/unions: instead of `foo->bar` use `foo.value.bar`; instead of `foo->bar = 1` use `foo.value.bar = 1`. * Added `colorize` file that provides methods to easily output bash colors. @@ -3950,7 +4046,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added `String#replace(Regex, String)` * Added a `Box(T)` class, useful for boxing value types to pass them to C as `Void*`. -## 0.3.4 (2014-07-21) +### [0.3.4] - 2014-07-21 +[0.3.4]: https://github.com/crystal-lang/crystal/releases/0.3.4 * Fixed [#165](https://github.com/crystal-lang/crystal/issues/165): restrictions with generic types didn't work for hierarchy types. * Allow using a single underscore in restrictions, useful for matching against an n-tuple or an n-function where you don't care about the types (e.g.: `def foo(x : {_, _})`. @@ -3963,7 +4060,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * Added `StaticArray::new`, `StaticArray::new(value)`, `StaticArray::new(&block)`, `StaticArray#shuffle!` and `StaticArray#map!`. * Faster `Char#to_s(io : IO)` -## 0.3.3 (2014-07-14) +### [0.3.3] - 2014-07-14 +[0.3.3]: https://github.com/crystal-lang/crystal/releases/0.3.3 * Allow implicit conversion to C types by defining a `to_unsafe` method. This removed the hardcoded rule for converting a `String` to `UInt8*` and also allows passing an `Array(T)` to an argument expecting `Pointer(T)`. * Fixed `.is_a?(Class)` not working ([#162](https://github.com/crystal-lang/crystal/issues/162)) @@ -3971,19 +4069,22 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * **(breaking change)** Make ENV#[] raise on missing key, and added ENV#[]? * **(breaking change)** Macro defs are now written like `macro def name(args) : ReturnType` instead of `def name(args) : ReturnType`, which was a bit confusing. -## 0.3.2 (2014-07-10) +### [0.3.2] - 2014-07-10 +[0.3.2]: https://github.com/crystal-lang/crystal/releases/0.3.2 * Integer literals without a suffix are inferred to be Int32, Int64 or UInt64 depending on their value. * Check that integer literals fit into their types. * Put back `Int#to_s(radix : Int)` (was renamed to `to_s_in_base` in the previous release) by also specifying a restriction in `Int#to_s(io : IO)`. * Added `expect_raises` macros in specs -## 0.3.1 (2014-07-09) +### [0.3.1] - 2014-07-09 +[0.3.1]: https://github.com/crystal-lang/crystal/releases/0.3.1 * **(breaking change)** Replaced `@name` inside macros with `@class_name`. * **(breaking change)** Instance variables inside macros now don't have the `@` symbols in their names. -## 0.3.0 (2014-07-08) +### [0.3.0] - 2014-07-08 +[0.3.0]: https://github.com/crystal-lang/crystal/releases/0.3.0 * Added `Array#each_index` * Optimized `String#*` for the case when the string has length one. @@ -4004,7 +4105,8 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) * New convention for `to_s` and `inspect`: you must override them receiving an IO object * StringBuilder and StringBuffer have been replaced by StringIO -## 0.2.0 (2014-06-24) +### [0.2.0] - 2014-06-24 +[0.2.0]: https://github.com/crystal-lang/crystal/releases/0.2.0 * Removed icr (a REPL): it is abandoned for the moment because it was done in a hacky, non-reliable way * Added very basic `String#underscore` and `String#camelcase`. @@ -4100,6 +4202,7 @@ Newer entries in [CHANGELOG.md](./CHANGELOG.md) The nice thing about this is that, using the `#` pragma for specifying the lexer's location, if you have a syntax/semantic error in the template the error points to the template :-) -## 0.1.0 (2014-06-18) +### [0.1.0] - 2014-06-18 +[0.1.0]: https://github.com/crystal-lang/crystal/releases/0.1.0 * First official release diff --git a/CHANGELOG.md b/CHANGELOG.md index 02e481aa1d26..5d2c17878c9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -# 1.10.1 (2023-10-13) +# Changelog + +## [1.10.1] (2023-10-13) + +[1.10.1]: https://github.com/crystal-lang/crystal/releases/1.10.1 ### Bugfixes @@ -11,7 +15,9 @@ - *(ci)* Fix `win.yml` ([#13876](https://github.com/crystal-lang/crystal/pull/13876), thanks @straight-shoota) -# 1.10.0 (2023-10-09) +## [1.10.0] (2023-10-09) + +[1.10.0]: https://github.com/crystal-lang/crystal/releases/1.10.0 ### Features @@ -148,7 +154,9 @@ - *(ci)* Update GH Actions ([#13748](https://github.com/crystal-lang/crystal/pull/13748), thanks @renovate) - *(ci)* Refactor `crystal_bootstrap_version` ([#13845](https://github.com/crystal-lang/crystal/pull/13845), thanks @straight-shoota) -# 1.9.2 (2023-07-19) +## [1.9.2] - 2023-07-19 + +[1.9.2]: https://github.com/crystal-lang/crystal/releases/1.9.2 ### Bugfixes @@ -156,7 +164,9 @@ - *(runtime)* Revert "Add default interrupt handlers" ([#13673](https://github.com/crystal-lang/crystal/pull/13673), thanks @straight-shoota) -# 1.9.1 (2023-07-17) +## [1.9.1] - 2023-07-17 + +[1.9.1]: https://github.com/crystal-lang/crystal/releases/1.9.1 ### Bugfixes @@ -168,7 +178,9 @@ - *(codegen)* Fix generated cc command for cross compile ([#13661](https://github.com/crystal-lang/crystal/pull/13661), thanks @fnordfish) -# 1.9.0 (2023-07-11) +## [1.9.0] - 2023-07-11 + +[1.9.0]: https://github.com/crystal-lang/crystal/releases/1.9.0 ### Breaking changes #### stdlib @@ -395,59 +407,65 @@ - *(ci)* Build Windows portable and installer packages on CI ([#13578](https://github.com/crystal-lang/crystal/pull/13578), thanks @HertzDevil) - *(ci)* Split Windows library build scripts from CI ([#13478](https://github.com/crystal-lang/crystal/pull/13478), thanks @HertzDevil) -# 1.8.2 (2023-05-08) +## [1.8.2] - 2023-05-08 + +[1.8.2]: https://github.com/crystal-lang/crystal/releases/1.8.2 -## Standard Library +### Standard Library -### Collection +#### Collection - Fix codegen bug with `Iterator::ChainIterator` ([#13412](https://github.com/crystal-lang/crystal/pull/13412), thanks @straight-shoota) -### Log +#### Log - Fix `Log::Metadata#dup` crash with 2+ entries ([#13369](https://github.com/crystal-lang/crystal/pull/13369), thanks @HertzDevil) -### Serialization +#### Serialization - Fixup for `JSON::Serializable` on certain recursively defined types ([#13430](https://github.com/crystal-lang/crystal/pull/13430), thanks @kostya) -### Text +#### Text - Fix `String#scan` with empty `Regex` match at multibyte char ([#13387](https://github.com/crystal-lang/crystal/pull/13387), thanks @HertzDevil) - **(performance)** Check subject UTF-8 validity just once for `String#gsub`, `#scan`, `#split` ([#13406](https://github.com/crystal-lang/crystal/pull/13406), thanks @HertzDevil) -## Compiler +### Compiler -### Codegen +#### Codegen - Always use 0 for offset of `StaticArray`'s `@buffer` ([#13319](https://github.com/crystal-lang/crystal/pull/13319), thanks @HertzDevil) -## Other +### Other - Backport bugfixes to release/1.8 for release 1.8.2 ([#3435](https://github.com/crystal-lang/crystal/pull/13435), thanks @straight-shoota) -# 1.8.1 (2023-04-20) +## [1.8.1] - 2023-04-20 + +[1.8.1]: https://github.com/crystal-lang/crystal/releases/1.8.1 -## Standard Library +### Standard Library -### Serialization +#### Serialization - Fix `JSON::Serializable` on certain recursively defined types ([#13344](https://github.com/crystal-lang/crystal/pull/13344), thanks @HertzDevil) -### Text +#### Text - Fix `String#gsub` with empty match at multibyte char ([#13342](https://github.com/crystal-lang/crystal/pull/13342), thanks @straight-shoota) - Fix PCRE2 `Regex` with more than 127 named capture groups ([#13349](https://github.com/crystal-lang/crystal/pull/13349), thanks @HertzDevil) -# 1.8.0 (2023-04-14) +## [1.8.0] - 2023-04-14 -## Language +[1.8.0]: https://github.com/crystal-lang/crystal/releases/1.8.0 + +### Language - The compiler uses PCRE2 to validate regex literals ([#13084](https://github.com/crystal-lang/crystal/pull/13084), thanks @straight-shoota) - Fill docs for `TupleLiteral` ([#12927](https://github.com/crystal-lang/crystal/pull/12927), thanks @straight-shoota) - Allow namespaced `Path`s as type names for `lib` ([#12903](https://github.com/crystal-lang/crystal/pull/12903), thanks @HertzDevil) -## Standard Library +### Standard Library - Fix `SyntaxHighlighter::HTML` to escape identifier values ([#13212](https://github.com/crystal-lang/crystal/pull/13212), thanks @straight-shoota) - Add workaround for `Value#not_nil!` copying the receiver ([#13264](https://github.com/crystal-lang/crystal/pull/13264), thanks @HertzDevil) @@ -457,7 +475,7 @@ - Define equality for `Process::Status` and `OAuth::RequestToken` ([#13014](https://github.com/crystal-lang/crystal/pull/13014), thanks @HertzDevil) - Fix some Linux glibc bindings ([#13242](https://github.com/crystal-lang/crystal/pull/13242), [#13249](https://github.com/crystal-lang/crystal/pull/13249), thanks @ysbaddaden, @HertzDevil) -### Collection +#### Collection - **(breaking-change)** Fix `Enum#includes?` to require all bits set ([#13229](https://github.com/crystal-lang/crystal/pull/13229), thanks @straight-shoota) - **(breaking-change)** Deprecate `Enum.flags` ([#12900](https://github.com/crystal-lang/crystal/pull/12900), thanks @straight-shoota) @@ -472,13 +490,13 @@ - Add `Enumerable#min(count)` and `#max(count)` ([#13057](https://github.com/crystal-lang/crystal/pull/13057), thanks @nthiad) - Fix `Array(T)#[]=(Int, Int, Array(T))` on shifted arrays ([#13275](https://github.com/crystal-lang/crystal/pull/13275), thanks @HertzDevil) -### Concurrency +#### Concurrency - Fix: Make sure to dup `Array` in `Channel.select_impl` ([#12827](https://github.com/crystal-lang/crystal/pull/12827), [#12962](https://github.com/crystal-lang/crystal/pull/12962), thanks @straight-shoota) - Add memory barriers on lock/unlock of SpinLock ([#13050](https://github.com/crystal-lang/crystal/pull/13050), thanks @bcardiff) - **(performance)** Avoid `Array` allocation in `Channel.select(Tuple)` ([#12960](https://github.com/crystal-lang/crystal/pull/12960), thanks @straight-shoota) -### Files +#### Files - **(breaking-change)** Deprecate `Termios` ([#12940](https://github.com/crystal-lang/crystal/pull/12940), thanks @HertzDevil) - **(breaking-change)** Windows: make `File.delete` remove symlink directories, not `Dir.delete` ([#13224](https://github.com/crystal-lang/crystal/pull/13224), thanks @HertzDevil) @@ -488,7 +506,7 @@ - Improve `File.symlink` on Windows ([#13141](https://github.com/crystal-lang/crystal/pull/13141), thanks @HertzDevil) - Implement `File.readlink` on Windows ([#13195](https://github.com/crystal-lang/crystal/pull/13195), thanks @HertzDevil) -### LLVM +#### LLVM - **(breaking-change)** Drop support for LLVM < 8 ([#12906](https://github.com/crystal-lang/crystal/pull/12906), thanks @straight-shoota) - **(breaking-change)** Support LLVM 15 ([#13173](https://github.com/crystal-lang/crystal/pull/13173), thanks @HertzDevil) @@ -496,7 +514,7 @@ - Remove `LibLLVM.has_constant?(:AttributeRef)` checks ([#13162](https://github.com/crystal-lang/crystal/pull/13162), thanks @HertzDevil) - Refactor `LLVM::Attribute#each_kind` to use `Enum#each` ([#13234](https://github.com/crystal-lang/crystal/pull/13234), thanks @straight-shoota) -### Networking +#### Networking - Fix socket specs when network not available ([#12961](https://github.com/crystal-lang/crystal/pull/12961), thanks @straight-shoota) - Fix wrong default address when binding sockets ([#13006](https://github.com/crystal-lang/crystal/pull/13006), thanks @etra0) @@ -514,7 +532,7 @@ - Fix `Socket#tty?` to `false` on Windows ([#13175](https://github.com/crystal-lang/crystal/pull/13175), thanks @Blacksmoke16) - Fix `HTTP::Server::Response#reset` for `status_message` ([#13282](https://github.com/crystal-lang/crystal/pull/13282), thanks @straight-shoota) -### Numeric +#### Numeric - Define `Math.pw2ceil` for all integer types ([#13127](https://github.com/crystal-lang/crystal/pull/13127), thanks @HertzDevil) - Workaround for more `Int128`-and-float methods on Windows with LLVM 14+ ([#13218](https://github.com/crystal-lang/crystal/pull/13218), thanks @HertzDevil) @@ -524,14 +542,14 @@ - Docs: Fix examples for `#byte_swap` with different int types ([#13154](https://github.com/crystal-lang/crystal/pull/13154), [#13180](https://github.com/crystal-lang/crystal/pull/13180), thanks @pan, @Blacksmoke16) - Make `BigRational.new(BigFloat)` exact ([#13295](https://github.com/crystal-lang/crystal/pull/13295), thanks @HertzDevil) -### Runtime +#### Runtime - Increase timeout for slow specs ([#13043](https://github.com/crystal-lang/crystal/pull/13043), thanks @straight-shoota) - Use `Crystal::System.print_error` instead of `LibC.printf` ([#13161](https://github.com/crystal-lang/crystal/pull/13161), thanks @HertzDevil) - Windows: detect stack overflows on non-main `Fiber`s ([#13220](https://github.com/crystal-lang/crystal/pull/13220), thanks @HertzDevil) - Add missing require for `Crystal::ThreadLocalValue` ([#13092](https://github.com/crystal-lang/crystal/pull/13092), thanks @Sija) -### Serialization +#### Serialization - Remove obsolete error handling in `XPathContext` ([#13038](https://github.com/crystal-lang/crystal/pull/13038), thanks @straight-shoota) - Fix JSON, YAML `use_*_discriminator` for recursive `Serializable::Strict` types ([#13238](https://github.com/crystal-lang/crystal/pull/13238), thanks @HertzDevil) @@ -539,13 +557,13 @@ - Add `from_json` for 128-bit integers ([#13041](https://github.com/crystal-lang/crystal/pull/13041), thanks @straight-shoota) - Reduce JSON, YAML serializable test types ([#13042](https://github.com/crystal-lang/crystal/pull/13042), thanks @straight-shoota) -### Specs +#### Specs - Format spec results with pretty inspect ([#11635](https://github.com/crystal-lang/crystal/pull/11635), thanks @JamesGood626) - Spec: Add `--color` option to spec runner ([#12932](https://github.com/crystal-lang/crystal/pull/12932), thanks @straight-shoota) - Add `Spec::Item#all_tags` ([#12915](https://github.com/crystal-lang/crystal/pull/12915), thanks @compumike) -### System +#### System - **(breaking-change)** Add full stub for Windows signals ([#13131](https://github.com/crystal-lang/crystal/pull/13131), thanks @HertzDevil) - **(breaking-change)** Deprecate and internalize `Process.fork` ([#12934](https://github.com/crystal-lang/crystal/pull/12934), thanks @straight-shoota) @@ -562,7 +580,7 @@ - AArch64 Android support ([#13065](https://github.com/crystal-lang/crystal/pull/13065), thanks @HertzDevil) - Windows 7 support ([#11505](https://github.com/crystal-lang/crystal/pull/11505), thanks @konovod) -### Text +#### Text - **(breaking-change)** Fix PCRE crashing on invalid UTF-8 ([#13240](https://github.com/crystal-lang/crystal/pull/13240), [#13311](https://github.com/crystal-lang/crystal/pull/13311), [#13313](https://github.com/crystal-lang/crystal/pull/13313), thanks @straight-shoota) - **(breaking-change)** Switch default regex engine to PCRE2 ([#12978](https://github.com/crystal-lang/crystal/pull/12978), thanks @straight-shoota) @@ -574,25 +592,25 @@ - Clarify behavior of strings with invalid UTF-8 byte sequences ([#13314](https://github.com/crystal-lang/crystal/pull/13314), thanks @HertzDevil) - Refer to PCRE2 in `Regex`'s summary ([#13318](https://github.com/crystal-lang/crystal/pull/13318), thanks @HertzDevil) -## Compiler +### Compiler - Escape filenames when running `crystal spec` with multiple files ([#12929](https://github.com/crystal-lang/crystal/pull/12929), thanks @HertzDevil) - Handle ARM64 MSVC paths when cross-compiling on Windows ([#13073](https://github.com/crystal-lang/crystal/pull/13073), thanks @HertzDevil) - Use relative paths to vendored shards" ([#13315](https://github.com/crystal-lang/crystal/pull/13315), thanks @straight-shoota) -### Debugger +#### Debugger - Always use 0 for offsets of lib / extern union members ([#13305](https://github.com/crystal-lang/crystal/pull/13305), thanks @HertzDevil) -### Codegen +#### Codegen - **(breaking-change)** Support LLVM 15 ([#13173](https://github.com/crystal-lang/crystal/pull/13173), thanks @HertzDevil) - Remove obsolete functions from `llvm_ext.cc` ([#13177](https://github.com/crystal-lang/crystal/pull/13177), thanks @HertzDevil) -### Generics +#### Generics - Fix type names for generic instances with empty splat type vars ([#13189](https://github.com/crystal-lang/crystal/pull/13189), thanks @HertzDevil) -### Interpreter +#### Interpreter - Fix: Interpreter `value_to_bool` for module, generic module and generic module metaclass ([#12920](https://github.com/crystal-lang/crystal/pull/12920), thanks @asterite) - Fix redundant cast in interpreter ([#12996](https://github.com/crystal-lang/crystal/pull/12996), thanks @asterite) @@ -601,13 +619,13 @@ - Add support for 128-bit literals in the interpreter ([#12859](https://github.com/crystal-lang/crystal/pull/12859), thanks @straight-shoota) - Fix interpreter `value_to_bool` for `NoReturn` ([#13290](https://github.com/crystal-lang/crystal/pull/13290), thanks @straight-shoota) -### Parser +#### Parser - Fix `x @y` and `x @@y` in def parameters when `y` is reserved ([#12922](https://github.com/crystal-lang/crystal/pull/12922), thanks @HertzDevil) - Disallow empty exponents in number literals ([#12910](https://github.com/crystal-lang/crystal/pull/12910), thanks @HertzDevil) - Stricter checks for multiple assignment syntax ([#12919](https://github.com/crystal-lang/crystal/pull/12919), thanks @HertzDevil) -### Semantic +#### Semantic - Compiler: type declaration with initial value gets the value's type ([#13025](https://github.com/crystal-lang/crystal/pull/13025), thanks @asterite) - Stricter checks for enum definitions ([#12945](https://github.com/crystal-lang/crystal/pull/12945), thanks @HertzDevil) @@ -617,11 +635,11 @@ - Refactor `SemanticVisitor` tighter `rescue` scope in `Require` visitor ([#12887](https://github.com/crystal-lang/crystal/pull/12887), thanks @straight-shoota) - Add specs for regex literal expansion ([#13253](https://github.com/crystal-lang/crystal/pull/13253), thanks @straight-shoota) -## Tools +### Tools - Fix Crystal tool cursor parsing for filenames containing `:` ([#13129](https://github.com/crystal-lang/crystal/pull/13129), thanks @HertzDevil) -### Formatter +#### Formatter - Formatter: fix end indent after comment inside begin ([#12994](https://github.com/crystal-lang/crystal/pull/12994), thanks @asterite) - Parser: remove obsolete handling of `else` inside lib struct ([#13028](https://github.com/crystal-lang/crystal/pull/13028), thanks @HertzDevil) @@ -633,7 +651,7 @@ - Formatter: Add feature flag for `method_signature_yield` ([#13215](https://github.com/crystal-lang/crystal/pull/13215), thanks @straight-shoota) - Macro interpolation: add `&` to yielding `Def`s without a block parameter ([#12952](https://github.com/crystal-lang/crystal/pull/12952), thanks @HertzDevil) -## Infrastructure +### Infrastructure - Fix `bin/crystal` print no error message when `crystal` is missing ([#12981](https://github.com/crystal-lang/crystal/pull/12981), thanks @straight-shoota) - Prevent infinitely recursive wrapper script ([#11712](https://github.com/crystal-lang/crystal/pull/11712), thanks @ThunderKey) @@ -677,16 +695,17 @@ - Makefile: Add `all` target as default before including `Makfile.local` ([#13276](https://github.com/crystal-lang/crystal/pull/13276), thanks @straight-shoota) - Update shards 0.17.3 ([#13296](https://github.com/crystal-lang/crystal/pull/13296), thanks @straight-shoota) -## Other +### Other - Do not match expectations outside specs ([#13079](https://github.com/crystal-lang/crystal/pull/13079), thanks @HertzDevil) - Enable or fix specs that already work on Windows ([#13186](https://github.com/crystal-lang/crystal/pull/13186), thanks @HertzDevil) -# 1.7.3 +## [1.7.3] - 2023-03-07 +[1.7.3]: https://github.com/crystal-lang/crystal/releases/1.7.3 -## Standard Library +### Standard Library -### Text +#### Text - Do not use `@[ThreadLocal]` for PCRE2's JIT stack ([#13056](https://github.com/crystal-lang/crystal/pull/13056), thanks @HertzDevil) - Fix `libpcre2` bindings with arch-dependent types (`SizeT`) ([#13088](https://github.com/crystal-lang/crystal/pull/13088), thanks @straight-shoota) @@ -697,57 +716,63 @@ - Fix `Regex::Option` behaviour for unnamed members ([#13155](https://github.com/crystal-lang/crystal/pull/13155), thanks @straight-shoota) - **(performance)** Improve PCRE2 match performance for JIT and interpreted ([#13146](https://github.com/crystal-lang/crystal/pull/13146), thanks @straight-shoota) -## Compiler +### Compiler -### Generics +#### Generics - Explicitly treat unbound type vars in generic class methods as free variables ([#13125](https://github.com/crystal-lang/crystal/pull/13125), thanks @HertzDevil) -## Other +### Other - [CI] Fix add PCRE2 to GHA cache for win job ([#13089](https://github.com/crystal-lang/crystal/pull/13089), thanks @straight-shoota) - [CI] Pin `use_pcre` in build environments where PCRE2 is not yet available ([#13102](https://github.com/crystal-lang/crystal/pull/13102), thanks @straight-shoota) -# 1.7.2 (2023-01-23) -## Standard Library +## [1.7.2] - 2023-01-23 + +[1.7.2]: https://github.com/crystal-lang/crystal/releases/1.7.2 +### Standard Library -### Runtime +#### Runtime - Fix: Add `Nil` return type restrictions to `load_debug_info` ([#12992](https://github.com/crystal-lang/crystal/pull/12992), thanks @straight-shoota) -## Compiler +### Compiler -### Codegen +#### Codegen - Add error handling to compiler when linker is unavailable ([#12899](https://github.com/crystal-lang/crystal/pull/12899), thanks @straight-shoota) -### Parser +#### Parser - Revert "Parser: Fix restrict grammar for name and supertype in type def (#12622)" ([#12977](https://github.com/crystal-lang/crystal/pull/12977), thanks @straight-shoota) -## Other +### Other - Update `VERSION` to `1.7.2-dev` ([#12993](https://github.com/crystal-lang/crystal/pull/12993), thanks @straight-shoota) -# 1.7.1 (2023-01-17) +## [1.7.1] - 2023-01-17 + +[1.7.1]: https://github.com/crystal-lang/crystal/releases/1.7.1 -## Tools +### Tools -### Playground +#### Playground - Fix baked-in path in playground to resolve at runtime ([#12948](https://github.com/crystal-lang/crystal/pull/12948), thanks @straight-shoota) -## Other +### Other - Update `VERSION` to 1.7.1-dev ([#12950](https://github.com/crystal-lang/crystal/pull/12950), thanks @straight-shoota) -# 1.7.0 (2023-01-09) +## [1.7.0] - 2023-01-09 -## Language +[1.7.0]: https://github.com/crystal-lang/crystal/releases/1.7.0 + +### Language - Add lib functions earlier so that they are visible in top-level macros ([#12848](https://github.com/crystal-lang/crystal/pull/12848), thanks @asterite) -## Standard Library +### Standard Library - Improve `Benchmark` docs ([#12782](https://github.com/crystal-lang/crystal/pull/12782), thanks @r00ster91, @straight-shoota) - Improve documentation for `Object#to_s` and `#inspect` ([#9974](https://github.com/crystal-lang/crystal/pull/9974), thanks @straight-shoota) @@ -758,7 +783,7 @@ - **(performance)** Eliminate `nil` from many predicate methods ([#12702](https://github.com/crystal-lang/crystal/pull/12702), thanks @HertzDevil) - examples: fix (2022-12) ([#12870](https://github.com/crystal-lang/crystal/pull/12870), thanks @maiha) -### Collection +#### Collection - Fix missed elements in `Hash#select!(keys : Enumerable)` ([#12739](https://github.com/crystal-lang/crystal/pull/12739), thanks @caspiano) - Add missing docs for `Indexable` combinations methods ([#10548](https://github.com/crystal-lang/crystal/pull/10548), thanks @keidax) @@ -770,32 +795,32 @@ - **(performance)** Use mutating collection methods ([#12644](https://github.com/crystal-lang/crystal/pull/12644), thanks @caspiano) - Fix `Enum#to_s` for flag enum containing named and unnamed members ([#12895](https://github.com/crystal-lang/crystal/pull/12895), thanks @straight-shoota) -### Concurrency +#### Concurrency - Allow the EventLoop implementation to be detected at runtime ([#12656](https://github.com/crystal-lang/crystal/pull/12656), thanks @lbguilherme) - **(performance)** Optimize uniqueness filter in `Channel.select_impl` ([#12814](https://github.com/crystal-lang/crystal/pull/12814), thanks @straight-shoota) - Implement multithreading primitives on Windows ([#11647](https://github.com/crystal-lang/crystal/pull/11647), thanks @HertzDevil) -### Crypto +#### Crypto - **(breaking-change)** Implement `Digest` class in `Digest::CRC32` and `Digest::Adler32` ([#11535](https://github.com/crystal-lang/crystal/pull/11535), thanks @BlobCodes) - Fix `OpenSSL::SSL::Context::Client#alpn_protocol=` ([#12724](https://github.com/crystal-lang/crystal/pull/12724), thanks @jaclarke) - Fix `HTTP::Client` certificate validation error on FQDN (host with trailing dot) ([#12778](https://github.com/crystal-lang/crystal/pull/12778), thanks @compumike) - Enable `arc4random(3)` on all supported BSDs and macOS/Darwin ([#12608](https://github.com/crystal-lang/crystal/pull/12608), thanks @dmgk) -### Files +#### Files - Fix: Read `UInt` in zip directory header ([#12822](https://github.com/crystal-lang/crystal/pull/12822), thanks @pbrumm) - Add `File.executable?` for Windows ([#9677](https://github.com/crystal-lang/crystal/pull/9677), thanks @nof1000) -### Macros +#### Macros - Fix `TypeNode#nilable?` for root types ([#12354](https://github.com/crystal-lang/crystal/pull/12354), thanks @HertzDevil) - Add argless `#annotations` overload ([#9326](https://github.com/crystal-lang/crystal/pull/9326), thanks @Blacksmoke16) - Add specs for addition between `ArrayLiteral` and `TupleLiteral` ([#12639](https://github.com/crystal-lang/crystal/pull/12639), thanks @caspiano) - Add `ArrayLiteral#-(other)` and `TupleLiteral#-(other)` ([#12646](https://github.com/crystal-lang/crystal/pull/12646), [#12916](https://github.com/crystal-lang/crystal/pull/12916) thanks @caspiano, @straight-shoota) -### Networking +#### Networking - **(breaking-change)** Add `HTTP::Headers#serialize` ([#12765](https://github.com/crystal-lang/crystal/pull/12765), thanks @straight-shoota) - Ensure `HTTP::Client` closes response when breaking out of block ([#12749](https://github.com/crystal-lang/crystal/pull/12749), thanks @straight-shoota) @@ -804,7 +829,7 @@ - Validate cookie name prefixes ([#10648](https://github.com/crystal-lang/crystal/pull/10648), thanks @Blacksmoke16) - `IPAddress#loopback?` should consider `::ffff:127.0.0.1/104` loopback too ([#12783](https://github.com/crystal-lang/crystal/pull/12783), thanks @carlhoerberg) -### Numeric +#### Numeric - Support new SI prefixes in `Number#humanize` ([#12761](https://github.com/crystal-lang/crystal/pull/12761), thanks @HertzDevil) - Fix `BigInt#%` for unsigned integers ([#12773](https://github.com/crystal-lang/crystal/pull/12773), thanks @straight-shoota) @@ -815,25 +840,25 @@ - Add `#bit_reverse` and `#byte_swap` for primitive integers ([#12865](https://github.com/crystal-lang/crystal/pull/12865), thanks @HertzDevil) - Fix Number comparison operator docs ([#12880](https://github.com/crystal-lang/crystal/pull/12880), thanks @fdocr) -### Runtime +#### Runtime - `Exception::CallStack`: avoid allocations in `LibC.dl_iterate_phdr` ([#12625](https://github.com/crystal-lang/crystal/pull/12625), thanks @dmgk) - Fix explicit type conversion to u64 for `GC::Stats` ([#12779](https://github.com/crystal-lang/crystal/pull/12779), thanks @straight-shoota) - Add custom `message` parameter to `#not_nil!` ([#12797](https://github.com/crystal-lang/crystal/pull/12797), thanks @straight-shoota) - Refactor specs for `Enum#to_s` using `assert_prints` ([#12882](https://github.com/crystal-lang/crystal/pull/12882), thanks @straight-shoota) -### Serialization +#### Serialization - **(performance)** Leverage `GC.malloc_atomic` for XML ([#12692](https://github.com/crystal-lang/crystal/pull/12692), thanks @HertzDevil) - Refactor libXML error handling to remove global state ([#12663](https://github.com/crystal-lang/crystal/pull/12663), [#12795](https://github.com/crystal-lang/crystal/pull/12795), thanks @straight-shoota) - Use qualified type reference `YAML::Any` ([#12688](https://github.com/crystal-lang/crystal/pull/12688), thanks @zw963) - Automatically cast Int to Float for `{JSON,YAML}::Any#as_f` ([#12835](https://github.com/crystal-lang/crystal/pull/12835), thanks @compumike) -### Specs +#### Specs - Print seed info at start and end of spec output ([#12755](https://github.com/crystal-lang/crystal/pull/12755), thanks @straight-shoota) -### System +#### System - **(breaking)** Rename `File.real_path` to `.realpath` ([#12552](https://github.com/crystal-lang/crystal/pull/12552), thanks @straight-shoota) - **(breaking-change)** Drop FreeBSD 11 compatibility code ([#12612](https://github.com/crystal-lang/crystal/pull/12612), thanks @dmgk) @@ -847,7 +872,7 @@ - Organize `Process` specs ([#12889](https://github.com/crystal-lang/crystal/pull/12889), thanks @straight-shoota) - Add tests for `Process::Status` ([#12881](https://github.com/crystal-lang/crystal/pull/12881), thanks @straight-shoota) -### Text +#### Text - Raise `IndexError` on unmatched subpattern for `MatchData#begin` and `#end` ([#12810](https://github.com/crystal-lang/crystal/pull/12810), thanks @straight-shoota) - Swap documentation for `String#split` array and block versions ([#12808](https://github.com/crystal-lang/crystal/pull/12808), thanks @hugopl) @@ -860,31 +885,31 @@ - Implement `Regex` engine on PCRE2 ([#12856](https://github.com/crystal-lang/crystal/pull/12856), [#12866](https://github.com/crystal-lang/crystal/pull/12866), [#12847](https://github.com/crystal-lang/crystal/pull/12847), thanks @straight-shoota, thanks @HertzDevil) - Add missing overloads for `String#byte_slice` ([#12809](https://github.com/crystal-lang/crystal/pull/12809), thanks @straight-shoota) -## Compiler +### Compiler - Improve error message when there are extra types ([#12734](https://github.com/crystal-lang/crystal/pull/12734), thanks @asterite) - Handle triples without libc ([#12594](https://github.com/crystal-lang/crystal/pull/12594), thanks @GeopJr) - Remove unused `Program#cache_dir` property ([#12669](https://github.com/crystal-lang/crystal/pull/12669), thanks @straight-shoota) - Fix: Unwrap nested errors in error handler for `Crystal::Error` ([#12888](https://github.com/crystal-lang/crystal/pull/12888), thanks @straight-shoota) -### Codegen +#### Codegen - Add missing specs for `->var.foo` semantics with assignments ([#9419](https://github.com/crystal-lang/crystal/pull/9419), thanks @makenowjust) - Use `File#flock_exclusive` on win32 in compiler ([#12876](https://github.com/crystal-lang/crystal/pull/12876), thanks @straight-shoota) -### Generics +#### Generics - Redefine defs when constant and number in generic arguments are equal ([#12785](https://github.com/crystal-lang/crystal/pull/12785), thanks @HertzDevil) - Fix restriction of numeral generic argument against non-free variable `Path` ([#12784](https://github.com/crystal-lang/crystal/pull/12784), thanks @HertzDevil) -### Interpreter +#### Interpreter - Interpreter: fix class var initializer that needs an upcast ([#12635](https://github.com/crystal-lang/crystal/pull/12635), thanks @asterite) - Reverting #12405 Compiler: don't always use Array for node dependencies and observers ([#12849](https://github.com/crystal-lang/crystal/pull/12849), thanks @beta-ziliani) - Match Nix loader errors in compiler spec ([#12852](https://github.com/crystal-lang/crystal/pull/12852), thanks @bcardiff) - Interpreter reply ([#12738](https://github.com/crystal-lang/crystal/pull/12738), thanks @I3oris) -### Parser +#### Parser - **(breaking-change)** Parser: Fix restrict grammar for name and supertype in type def ([#12622](https://github.com/crystal-lang/crystal/pull/12622), thanks @caspiano) - Lexer: fix global capture vars ending with zero, e.g. `$10?` ([#12701](https://github.com/crystal-lang/crystal/pull/12701), thanks @FnControlOption) @@ -901,7 +926,7 @@ - Fix warning on space before colon with anonymous block arg ([#12869](https://github.com/crystal-lang/crystal/pull/12869), thanks @straight-shoota) - Warn on missing space before colon in type declaration/restriction ([#12740](https://github.com/crystal-lang/crystal/pull/12740), thanks @straight-shoota) -### Semantic +#### Semantic - Fix: Do not merge union types in truthy filter ([#12752](https://github.com/crystal-lang/crystal/pull/12752), thanks @straight-shoota) - Fix crash when using `sizeof`, `instance_sizeof`, or `offsetof` as a type arg ([#12577](https://github.com/crystal-lang/crystal/pull/12577), thanks @keidax) @@ -912,20 +937,20 @@ - Add more specific error message for uninstantiated proc type ([#11219](https://github.com/crystal-lang/crystal/pull/11219), thanks @straight-shoota) - Add specs for `system` macro method ([#12885](https://github.com/crystal-lang/crystal/pull/12885), thanks @straight-shoota) -## Tools +### Tools -### Docs-generator +#### Docs-generator - Fix range literals causing method lookups in docs generator ([#12680](https://github.com/crystal-lang/crystal/pull/12680), thanks @caspiano) - Fix method lookup for single char class names ([#12683](https://github.com/crystal-lang/crystal/pull/12683), thanks @caspiano) -### Formatter +#### Formatter - Formatter: document stdin filename argument (`-`) ([#12620](https://github.com/crystal-lang/crystal/pull/12620), thanks @caspiano) -## Other +### Other -### Infrastructure +#### Infrastructure - [CI] Drop Alpine libreSSL 3.1 test ([#12641](https://github.com/crystal-lang/crystal/pull/12641), thanks @straight-shoota) - Bump version to 1.7.0-dev ([#12640](https://github.com/crystal-lang/crystal/pull/12640), thanks @straight-shoota) - [CI] Update GHA actions ([#12501](https://github.com/crystal-lang/crystal/pull/12501), thanks @straight-shoota) @@ -949,7 +974,7 @@ - Update `NOTICE.md` ([#12901](https://github.com/crystal-lang/crystal/pull/12901), thanks @HertzDevil) - Split pre-1.0 changelog ([#12898](https://github.com/crystal-lang/crystal/pull/12898), thanks @straight-shoota) -### Code Improvements +#### Code Improvements - Style: Remove redundant begin blocks ([#12638](https://github.com/crystal-lang/crystal/pull/12638), thanks @caspiano) - Lint: Fix variable name casing ([#12674](https://github.com/crystal-lang/crystal/pull/12674), thanks @Sija) @@ -963,51 +988,55 @@ - Couple of ameba lint issues fixed ([#12685](https://github.com/crystal-lang/crystal/pull/12685), thanks @Sija) - Use context-specific heredoc deliminators ([#12816](https://github.com/crystal-lang/crystal/pull/12816), thanks @straight-shoota) -# 1.6.2 (2022-11-03) +## [1.6.2] - 2022-11-03 + +[1.6.2]: https://github.com/crystal-lang/crystal/releases/1.6.2 -## Language +### Language - Fix `VirtualMetaclassType#implements?` to ignore base type ([#12632](https://github.com/crystal-lang/crystal/pull/12632), thanks @straight-shoota) -## Compiler +### Compiler - Compiler: handle yield expressions without a type ([#12679](https://github.com/crystal-lang/crystal/pull/12679), thanks @asterite) - Partial revert "Compiler: refactor and slightly optimize merging two types (#12436)" ([#12709](https://github.com/crystal-lang/crystal/pull/12709), thanks @caspiano) -### Semantic +#### Semantic - Compiler: ignore type filters when accepting cast for `obj` and `to` ([#12668](https://github.com/crystal-lang/crystal/pull/12668), thanks @asterite) -## Other +### Other - **(security)** CI: Update to OpenSSL 3.0.7 for bundled lib on Windows ([#12712](https://github.com/crystal-lang/crystal/pull/12712), thanks @beta-ziliani) -# 1.6.1 (2022-10-21) +## [1.6.1] - 2022-10-21 -## Compiler +[1.6.1]: https://github.com/crystal-lang/crystal/releases/1.6.1 -### Interpreter +### Compiler + +#### Interpreter - Interpreter (repl): migrate types even if their size remains the same ([#12581](https://github.com/crystal-lang/crystal/pull/12581), thanks @asterite) - Unbreak the interpreter on FreeBSD ([#12600](https://github.com/crystal-lang/crystal/pull/12600), thanks @dmgk) - Fix FFI specs on release builds ([#12601](https://github.com/crystal-lang/crystal/pull/12601), thanks @HertzDevil) - Adding welcome message to the interpreter ([#12511](https://github.com/crystal-lang/crystal/pull/12511), thanks @beta-ziliani) -### Semantic +#### Semantic - Treat single splats with same restriction as equivalent ([#12584](https://github.com/crystal-lang/crystal/pull/12584), thanks @HertzDevil) -## Tools +### Tools -### Formatter +#### Formatter - Formatter: escape backslashes in macro literals when subformatting ([#12582](https://github.com/crystal-lang/crystal/pull/12582), thanks @asterite) -### Playground +#### Playground - Fix origin validation in playground server for localhost ([#12599](https://github.com/crystal-lang/crystal/pull/12599), thanks @straight-shoota) -## Other +### Other - Fix doc typos in `Socket::IPAddress` ([#12583](https://github.com/crystal-lang/crystal/pull/12583), thanks @Blacksmoke16) - Fix building Wasm32 on Crystal 1.6 (Regression) ([#12580](https://github.com/crystal-lang/crystal/pull/12580), thanks @lbguilherme) @@ -1015,13 +1044,15 @@ - Disable failing specs on Windows CI ([#12585](https://github.com/crystal-lang/crystal/pull/12585), thanks @HertzDevil) - Detect `llvm-configXX` while building compiler ([#12602](https://github.com/crystal-lang/crystal/pull/12602), thanks @HertzDevil) -# 1.6.0 (2022-10-06) +## [1.6.0] - 2022-10-06 + +[1.6.0]: https://github.com/crystal-lang/crystal/releases/1.6.0 -## Language +### Language - Add 'wasm_import_module' option to the `@[Link]` annotation ([#11935](https://github.com/crystal-lang/crystal/pull/11935), thanks @lbguilherme) -## Standard Library +### Standard Library - Use `GC.malloc_atomic` with `GC.realloc`, not `Pointer#realloc` ([#12391](https://github.com/crystal-lang/crystal/pull/12391), thanks @HertzDevil) - Improve syntax highlighter ([#12409](https://github.com/crystal-lang/crystal/pull/12409), thanks @I3oris) @@ -1032,7 +1063,7 @@ - Document how to change base type of an enum ([#9803](https://github.com/crystal-lang/crystal/pull/9803), thanks @Blacksmoke16) - Spec: bump and document timeouts in interpreted mode ([#12430](https://github.com/crystal-lang/crystal/pull/12430), thanks @asterite) -### Collection +#### Collection - Refactor and improve `NamedTuple` deserialization from JSON and YAML ([#12008](https://github.com/crystal-lang/crystal/pull/12008), thanks @HertzDevil) - **(performance)** Optimize `BitArray#tally(hash)` ([#11909](https://github.com/crystal-lang/crystal/pull/11909), thanks @HertzDevil) @@ -1041,17 +1072,17 @@ - Support tuple metaclass indexers with non-literal arguments ([#11834](https://github.com/crystal-lang/crystal/pull/11834), thanks @HertzDevil) - Add `Indexable#index!` overloads with `offset` parameter ([#12089](https://github.com/crystal-lang/crystal/pull/12089), thanks @HertzDevil) -### Concurrency +#### Concurrency - Fix fiber clean loop on Windows ([#12300](https://github.com/crystal-lang/crystal/pull/12300), thanks @HertzDevil) - Enable `Mutex` on Windows ([#12213](https://github.com/crystal-lang/crystal/pull/12213), thanks @HertzDevil) -### Crypto +#### Crypto - Add support for Bcrypt algorithm version 2y ([#12447](https://github.com/crystal-lang/crystal/pull/12447), thanks @docelic) - Allow using `U/Int128` in `Random` ([#11977](https://github.com/crystal-lang/crystal/pull/11977), thanks @BlobCodes) -### Files +#### Files - **(breaking-change)** Define `#system_echo` and `#system_raw` on all systems ([#12352](https://github.com/crystal-lang/crystal/pull/12352), thanks @HertzDevil) - **(breaking-change)** Do not expose `Crystal::System::FileInfo` through `File::Info` ([#12385](https://github.com/crystal-lang/crystal/pull/12385), thanks @HertzDevil) @@ -1064,11 +1095,11 @@ - **(performance)** Introduce `IO::DEFAULT_BUFFER_SIZE` ([#12507](https://github.com/crystal-lang/crystal/pull/12507), thanks @straight-shoota) - Add support for `IO::FileDescriptor` staying open on finalize ([#12367](https://github.com/crystal-lang/crystal/pull/12367), thanks @refi64) -### Macros +#### Macros - Enhance `record` documentation ([#12334](https://github.com/crystal-lang/crystal/pull/12334), thanks @straight-shoota) -### Networking +#### Networking - Add `Socket::IPAddress.valid?` ([#12489](https://github.com/crystal-lang/crystal/pull/12489), [#10492](https://github.com/crystal-lang/crystal/pull/10492), thanks @straight-shoota) - Fix `HTTP::Client#exec` to abort retry when client was closed ([#12465](https://github.com/crystal-lang/crystal/pull/12465), thanks @straight-shoota) @@ -1077,7 +1108,7 @@ - `HTTP::StaticFileHandler`: Reduce max stat calls from 6 to 2 ([#12310](https://github.com/crystal-lang/crystal/pull/12310), thanks @didactic-drunk) - Add warning about concurrent requests in `HTTP::Client` ([#12527](https://github.com/crystal-lang/crystal/pull/12527), thanks @straight-shoota) -### Numeric +#### Numeric - Add full integer support to `sprintf` and `String#%` ([#10973](https://github.com/crystal-lang/crystal/pull/10973), thanks @HertzDevil) - Make `Float#to_s` ignore NaN sign bit ([#12399](https://github.com/crystal-lang/crystal/pull/12399), thanks @HertzDevil) @@ -1090,25 +1121,25 @@ - Implement the Dragonbox algorithm for `Float#to_s` ([#10913](https://github.com/crystal-lang/crystal/pull/10913), thanks @HertzDevil) - Add `U/Int128` to `isqrt` spec ([#11976](https://github.com/crystal-lang/crystal/pull/11976), thanks @BlobCodes) -### Runtime +#### Runtime - Fix: Parse DWARF5 Data16 values ([#12497](https://github.com/crystal-lang/crystal/pull/12497), thanks @stakach) - macOS: Fix call stack when executable path contains symlinks ([#12504](https://github.com/crystal-lang/crystal/pull/12504), thanks @HertzDevil) - WASM: Add support for `wasi-sdk 16`: don't rely on `__original_main` ([#12450](https://github.com/crystal-lang/crystal/pull/12450), thanks @lbguilherme) -### Serialization +#### Serialization - Fix YAML serialization class name ambiguity ([#12537](https://github.com/crystal-lang/crystal/pull/12537), thanks @hugopl) - Allow non-type converter instances in `ArrayConverter` and `HashValueConverter` ([#10638](https://github.com/crystal-lang/crystal/pull/10638), thanks @HertzDevil) - Document `after_initialize` method for `yaml` and `json` serializers ([#12530](https://github.com/crystal-lang/crystal/pull/12530), thanks @analogsalad) -### System +#### System - Add missing fields to `LibC::Passwd` on FreeBSD ([#12315](https://github.com/crystal-lang/crystal/pull/12315), thanks @dmgk) - Add platform-specific variants of `Process.parse_arguments` ([#12278](https://github.com/crystal-lang/crystal/pull/12278), thanks @HertzDevil) - Make `Dir.current` respect `$PWD` ([#12471](https://github.com/crystal-lang/crystal/pull/12471), thanks @straight-shoota) -### Text +#### Text - Fix `String` shift state specs on FreeBSD ([#12339](https://github.com/crystal-lang/crystal/pull/12339), thanks @dmgk) - Disallow mixing of sequential and named `sprintf` parameters ([#12402](https://github.com/crystal-lang/crystal/pull/12402), thanks @HertzDevil) @@ -1121,7 +1152,7 @@ - Unicode: Update to version 15.0.0 ([#12479](https://github.com/crystal-lang/crystal/pull/12479), thanks @HertzDevil) - Avoid free call in interpreted mode ([#12496](https://github.com/crystal-lang/crystal/pull/12496), thanks @straight-shoota) -## Compiler +### Compiler - Improve recursive splat expansion detection ([#11790](https://github.com/crystal-lang/crystal/pull/11790), thanks @asterite) - Compiler: fix `#to_s` for empty parameters of lib funs ([#12368](https://github.com/crystal-lang/crystal/pull/12368), thanks @HertzDevil) @@ -1144,19 +1175,19 @@ - Better call error messages ([#12469](https://github.com/crystal-lang/crystal/pull/12469), thanks @asterite) - **(performance)** Compiler optimization: avoid intermediate array when matching call arg types ([#12485](https://github.com/crystal-lang/crystal/pull/12485), thanks @asterite) -### Codegen +#### Codegen - Codegen: fix how unions are represented to not miss bytes ([#12551](https://github.com/crystal-lang/crystal/pull/12551), thanks @asterite) - Fix alignment typo in compiler comments ([#12564](https://github.com/crystal-lang/crystal/pull/12564), thanks @mdwagner) - Remove redundant code from x86_64 abi ([#12443](https://github.com/crystal-lang/crystal/pull/12443), thanks @mattrberry) - Codegen: use var pointer for `out` instead of an extra variable ([#10952](https://github.com/crystal-lang/crystal/pull/10952), thanks @asterite) -### Debugger +#### Debugger - Basic GDB formatter support ([#12209](https://github.com/crystal-lang/crystal/pull/12209), thanks @HertzDevil) - Add Visual Studio formatters for `String`, `Array`, and `Hash` ([#12212](https://github.com/crystal-lang/crystal/pull/12212), thanks @HertzDevil) -### Interpreter +#### Interpreter - Interpreter: handle the case of a def's body with no type ([#12220](https://github.com/crystal-lang/crystal/pull/12220), thanks @asterite) - Interpreter: simplify ivar initialization ([#12222](https://github.com/crystal-lang/crystal/pull/12222), thanks @asterite) @@ -1204,7 +1235,7 @@ - Interpreter: don't change compiled mode logic ([#12252](https://github.com/crystal-lang/crystal/pull/12252), thanks @asterite) - Wait more in `HTTP::Server` specs in interpreted mode ([#12420](https://github.com/crystal-lang/crystal/pull/12420), thanks @asterite) -### Parser +#### Parser - Lexer: fix index out of bounds when scanning numbers ([#12482](https://github.com/crystal-lang/crystal/pull/12482), thanks @asterite) - Fix parser to never create doc from trailing comment ([#11268](https://github.com/crystal-lang/crystal/pull/11268), thanks @straight-shoota) @@ -1215,7 +1246,7 @@ - Parser: Rename `arg*` to `param*` ([#12235](https://github.com/crystal-lang/crystal/pull/12235), thanks @potomak) - Fix test cases ([#12508](https://github.com/crystal-lang/crystal/pull/12508), thanks @potomak) -### Semantic +#### Semantic - **(breaking-change)** Allow `Union` restrictions to be ordered before all other restrictions ([#12335](https://github.com/crystal-lang/crystal/pull/12335), thanks @HertzDevil) - **(breaking-change)** Use more robust ordering between def overloads ([#10711](https://github.com/crystal-lang/crystal/pull/10711), thanks @HertzDevil) @@ -1230,24 +1261,24 @@ - **(performance)** Compiler: don't always use Array for node dependencies and observers ([#12405](https://github.com/crystal-lang/crystal/pull/12405), thanks @asterite) - Compiler: better error message for symbol against enum ([#12478](https://github.com/crystal-lang/crystal/pull/12478), thanks @asterite) -## Tools +### Tools -### Docs-generator +#### Docs-generator - Fix docs generator search constants id ([#12262](https://github.com/crystal-lang/crystal/pull/12262), thanks @GeopJr) -### Formatter +#### Formatter - Formatter: format comment after select ([#12506](https://github.com/crystal-lang/crystal/pull/12506), thanks @asterite) - Formatter: try to format macros that don't interpolate content ([#12378](https://github.com/crystal-lang/crystal/pull/12378), thanks @asterite) -### Playground +#### Playground - Playground: Fix pass bound hostname to run sessions ([#12356](https://github.com/crystal-lang/crystal/pull/12356), thanks @orangeSi) - Don't show stacktrace when playground port is already in use. ([#11844](https://github.com/crystal-lang/crystal/pull/11844), thanks @hugopl) - Indent playground code using spaces ([#12231](https://github.com/crystal-lang/crystal/pull/12231), thanks @potomak) -## Other +### Other - `bin/crystal`: Ensure `sh` compatibility ([#12486](https://github.com/crystal-lang/crystal/pull/12486), thanks @HertzDevil) - bumping version 1.6.0-dev ([#12263](https://github.com/crystal-lang/crystal/pull/12263), thanks @beta-ziliani) @@ -1265,50 +1296,54 @@ - Add icon and metadata to Windows Crystal compiler binary ([#12494](https://github.com/crystal-lang/crystal/pull/12494), thanks @HertzDevil) - Remove `spec/win32_std_spec.cr` and `spec/generate_windows_spec.sh` ([#12282](https://github.com/crystal-lang/crystal/pull/12282), [#12549](https://github.com/crystal-lang/crystal/pull/12549), thanks @HertzDevil and @straight-shoota) -# 1.5.1 (2022-09-07) +## [1.5.1] - 2022-09-07 -## Standard Library +[1.5.1]: https://github.com/crystal-lang/crystal/releases/1.5.1 + +### Standard Library - Fix `Class#nilable?` for recursive unions and root types ([#12353](https://github.com/crystal-lang/crystal/pull/12353), thanks @HertzDevil) -### Numeric +#### Numeric - Fix `Float#abs` and `Number#format` for `-0.0` ([#12424](https://github.com/crystal-lang/crystal/pull/12424), thanks @straight-shoota) -### Text +#### Text - Fix null dereference when passing empty slice to `Base64.encode` ([#12377](https://github.com/crystal-lang/crystal/pull/12377), thanks @dscottboggs) -## Compiler +### Compiler - Do not check abstract def parameter names on abstract types and modules ([#12434](https://github.com/crystal-lang/crystal/pull/12434), thanks @HertzDevil) -### Codegen +#### Codegen - Compiler/codegen: reset `@needs_value` ([#12444](https://github.com/crystal-lang/crystal/pull/12444), thanks @asterite) - Fix `homogeneous_aggregate?` check for aarch64 types ([#12445](https://github.com/crystal-lang/crystal/pull/12445), thanks @mattrberry) -### Semantic +#### Semantic - Compiler: don't eagerly check cast type ([#12272](https://github.com/crystal-lang/crystal/pull/12272), thanks @asterite) - Fix type restriction augmenter for `Union(*T)` and similar ([#12438](https://github.com/crystal-lang/crystal/pull/12438), thanks @asterite) -## Tools +### Tools -### Formatter +#### Formatter - Formatter: Fix assign followed by comment ([#12319](https://github.com/crystal-lang/crystal/pull/12319), thanks @straight-shoota) - Handle formatting annotated method parameters ([#12446](https://github.com/crystal-lang/crystal/pull/12446), thanks @Blacksmoke16) -## Other +### Other - Update distribution-scripts ([#12359](https://github.com/crystal-lang/crystal/pull/12359), thanks @straight-shoota) - Update distribution-scripts ([#12333](https://github.com/crystal-lang/crystal/pull/12333), thanks @straight-shoota) - [CI] Bumping xcode to 13.4.1 ([#12264](https://github.com/crystal-lang/crystal/pull/12264), thanks @beta-ziliani) -# 1.5.0 (2022-07-06) +## [1.5.0] - 2022-07-06 + +[1.5.0]: https://github.com/crystal-lang/crystal/releases/1.5.0 -## Language +### Language - **(breaking-change)** Warn on positional parameter mismatches for abstract def implementations ([#11915](https://github.com/crystal-lang/crystal/pull/11915), [#12167](https://github.com/crystal-lang/crystal/pull/12167), thanks @HertzDevil) - Fix `\{{...}}` syntax in macro inside comments ([#12175](https://github.com/crystal-lang/crystal/pull/12175), thanks @asterite) @@ -1318,7 +1353,7 @@ - Experimental: restriction augmenter ([#12103](https://github.com/crystal-lang/crystal/pull/12103), [#12136](https://github.com/crystal-lang/crystal/pull/12136), [#12143](https://github.com/crystal-lang/crystal/pull/12143), [#12130](https://github.com/crystal-lang/crystal/pull/12130), thanks @asterite) - Method/macro parameter annotation support ([#12044](https://github.com/crystal-lang/crystal/pull/12044), thanks @Blacksmoke16) -## Standard Library +### Standard Library - Support `Path` for `chdir` arg in `Process` methods ([#11932](https://github.com/crystal-lang/crystal/pull/11932), thanks @caspiano) - Add docs for `Enum#value` ([#11947](https://github.com/crystal-lang/crystal/pull/11947), thanks @lbguilherme) @@ -1326,26 +1361,26 @@ - Use more specific expectations in specs ([#11951](https://github.com/crystal-lang/crystal/pull/11951), thanks @HertzDevil) - Use `contain` expectations in more specs ([#11950](https://github.com/crystal-lang/crystal/pull/11950), thanks @HertzDevil) -### Collection +#### Collection - Fix `Hash#reject!` for non-equality key ([#10511](https://github.com/crystal-lang/crystal/pull/10511), thanks @straight-shoota) - Introduce `Tuple.element_type` and `NamedTuple.element_type` ([#12011](https://github.com/crystal-lang/crystal/pull/12011), thanks @HertzDevil) - Rename "take" to "first" ([#11988](https://github.com/crystal-lang/crystal/pull/11988), thanks @jmdyck) - Add spec for `Array#-` with different generic type arguments ([#12049](https://github.com/crystal-lang/crystal/pull/12049), thanks @straight-shoota) -### Concurrency +#### Concurrency - Windows: Always use `GC_set_stackbottom` on Windows ([#12186](https://github.com/crystal-lang/crystal/pull/12186), thanks @HertzDevil) - Windows: Event loop based on IOCP ([#12149](https://github.com/crystal-lang/crystal/pull/12149), thanks @straight-shoota, @wonderix, @yxhuvud, @HertzDevil) - Use enum instead of symbol for `Atomic` primitives ([#11583](https://github.com/crystal-lang/crystal/pull/11583), thanks @HertzDevil) - Allow `Enumerable(Channel)` parameter for `Channel.send_first`, `.receive_first` ([#12101](https://github.com/crystal-lang/crystal/pull/12101), thanks @carlhoerberg) -### Crypto +#### Crypto - **(performance)** Add faster `Digest#hexfinal` ([#9292](https://github.com/crystal-lang/crystal/pull/9292), thanks @didactic-drunk) - Handle OpenSSL 3.0 KTLS ctrl calls ([#12034](https://github.com/crystal-lang/crystal/pull/12034), thanks @1player) -### Files +#### Files - Fix `Path#join(Enumerable)` ([#12032](https://github.com/crystal-lang/crystal/pull/12032), thanks @straight-shoota) - Fix `Path#join` to convert argument path to base kind ([#12033](https://github.com/crystal-lang/crystal/pull/12033), thanks @straight-shoota) @@ -1355,38 +1390,38 @@ - Update file.cr ([#12024](https://github.com/crystal-lang/crystal/pull/12024), thanks @rdp) - Add `File#chown` and `#chmod` ([#11886](https://github.com/crystal-lang/crystal/pull/11886), thanks @didactic-drunk) -### Log +#### Log - Change `Log` emitters to not emit event when block output is `nil` ([#12000](https://github.com/crystal-lang/crystal/pull/12000), thanks @robacarp) -### Networking +#### Networking - Enable more networking specs on Windows ([#12176](https://github.com/crystal-lang/crystal/pull/12176), thanks @HertzDevil) - Add specs for Windows directory separators in `StaticFileHandler` paths ([#11884](https://github.com/crystal-lang/crystal/pull/11884), thanks @straight-shoota) - Add property `HTTP::Server::Response#status_message` ([#10416](https://github.com/crystal-lang/crystal/pull/10416), thanks @straight-shoota) -### Numeric +#### Numeric - Fix `Complex.multiplicative_identity` ([#12051](https://github.com/crystal-lang/crystal/pull/12051), thanks @I3oris) - Add docs for `Float`, `BigFloat` rounding methods ([#12004](https://github.com/crystal-lang/crystal/pull/12004), thanks @marksiemers) - Implement rt builtins `__ashlti3`, `__ashrti3` and `__lshrti3` for wasm32 ([#11948](https://github.com/crystal-lang/crystal/pull/11948), thanks @lbguilherme) -### Specs +#### Specs - Align `Spec::Be`, `BeClose` failure message to other messages ([#11946](https://github.com/crystal-lang/crystal/pull/11946), thanks @jgaskins) -### System +#### System - **(security)** Fix check for null byte in `File#tempfile` args ([#12076](https://github.com/crystal-lang/crystal/pull/12076), thanks @straight-shoota) - Add missing `SC_PAGESIZE` constant for `aarch64-darwin` ([#12037](https://github.com/crystal-lang/crystal/pull/12037), thanks @carlhoerberg) - Docs: Add more prominent note about path traversal in `File.tempfile` ([#12077](https://github.com/crystal-lang/crystal/pull/12077), thanks @straight-shoota) - Support `Enumerable` as argument to `File.join` ([#12102](https://github.com/crystal-lang/crystal/pull/12102), thanks @caspiano) -### Runtime +#### Runtime - Mention `#value` explicitly in `Pointer` overview. ([#12184](https://github.com/crystal-lang/crystal/pull/12184), thanks @elebow) -### Text +#### Text - Add specs for `String#char_bytesize_at` ([#11872](https://github.com/crystal-lang/crystal/pull/11872), thanks @straight-shoota) - Flush shift state for `String#encode` ([#11993](https://github.com/crystal-lang/crystal/pull/11993), thanks @HertzDevil) @@ -1394,25 +1429,25 @@ - Fix: Don't stop on null byte in `String#%` ([#12125](https://github.com/crystal-lang/crystal/pull/12125), thanks @asterite) - Add `UUID.parse?` ([#11998](https://github.com/crystal-lang/crystal/pull/11998), thanks @jgaskins) -### Time +#### Time - Fix: Better error message for `Time.parse!` when end of input is reached ([#12124](https://github.com/crystal-lang/crystal/pull/12124), thanks @asterite) -## Compiler +### Compiler - Clean up compiler warning specs ([#11916](https://github.com/crystal-lang/crystal/pull/11916), thanks @HertzDevil) - Add support for `NO_COLOR` to `Colorize` ([#11984](https://github.com/crystal-lang/crystal/pull/11984), thanks @didactic-drunk) - **(performance)** Use LLVM's new pass manager when possible ([#12116](https://github.com/crystal-lang/crystal/pull/12116), thanks @asterite) -### Macros +#### Macros - Document `Crystal::Macros::Self` and `Underscore` ([#12085](https://github.com/crystal-lang/crystal/pull/12085), thanks @HertzDevil) -### Generics +#### Generics - Allow the empty instantiation `NamedTuple()` ([#12009](https://github.com/crystal-lang/crystal/pull/12009), thanks @HertzDevil) -### Interpreter +#### Interpreter - Add missing `EXPORT` in interpreter spec ([#12201](https://github.com/crystal-lang/crystal/pull/12201), thanks @HertzDevil) - Handle escaping exceptions in pry ([#12211](https://github.com/crystal-lang/crystal/pull/12211), thanks @asterite) @@ -1433,14 +1468,14 @@ - Fix expression value of constant assignment in interpreter ([#12016](https://github.com/crystal-lang/crystal/pull/12016), thanks @beta-ziliani) - Fix: Don't link `librt` and `libdl` on GNU systems ([#12038](https://github.com/crystal-lang/crystal/pull/12038), thanks @1player) -### Parser +#### Parser - **(breaking-change)** Disallow empty parameter and argument names ([#11971](https://github.com/crystal-lang/crystal/pull/11971), thanks @HertzDevil) - Disallow duplicate free variables in defs ([#11965](https://github.com/crystal-lang/crystal/pull/11965), thanks @HertzDevil) - Disallow duplicate `fun` parameter names ([#11967](https://github.com/crystal-lang/crystal/pull/11967), thanks @HertzDevil) - Remove redundant check for EOF on `Crystal::Parser` ([#12067](https://github.com/crystal-lang/crystal/pull/12067), thanks @lbguilherme) -### Semantic +#### Semantic - Compiler: don't check ivar read forms a closure in `exp.@x` ([#12183](https://github.com/crystal-lang/crystal/pull/12183), thanks @asterite) - Compiler: raise when allocating an abstract virtual type ([#12141](https://github.com/crystal-lang/crystal/pull/12141), thanks @asterite) @@ -1449,28 +1484,28 @@ - Compiler: simpler way to compute `Def#raises?` ([#12121](https://github.com/crystal-lang/crystal/pull/12121), thanks @asterite) - Remove unused `ASTNode#unbind_all` ([#12120](https://github.com/crystal-lang/crystal/pull/12120), thanks @asterite) -### Debugger +#### Debugger - Improve the LLDB spec driver script ([#12119](https://github.com/crystal-lang/crystal/pull/12119), thanks @HertzDevil) -## Tools +### Tools -### Docs-generator +#### Docs-generator - [Docs] Adjust method sort order to sort all operators first ([#12104](https://github.com/crystal-lang/crystal/pull/12104), thanks @straight-shoota) -### Formatter +#### Formatter - Fix formatter lib-fun declaration with newlines ([#12071](https://github.com/crystal-lang/crystal/pull/12071), thanks @ftarulla) - Fix formatter alias-def with no-space before equals ([#12073](https://github.com/crystal-lang/crystal/pull/12073), thanks @ftarulla) - Fix formatter for parenthesized arg after space ([#11972](https://github.com/crystal-lang/crystal/pull/11972), thanks @straight-shoota) -### Playground +#### Playground - Playground: fix `modalContenDom` typo ([#12188](https://github.com/crystal-lang/crystal/pull/12188), thanks @HertzDevil) - Fix: Unset executable bit from js/css files in playground ([#12053](https://github.com/crystal-lang/crystal/pull/12053), thanks @carlhoerberg) -## Other +### Other - [CI] Add build compiler step to smoke tests ([#11814](https://github.com/crystal-lang/crystal/pull/11814), thanks @straight-shoota) - Add Makefile for Windows ([#11773](https://github.com/crystal-lang/crystal/pull/11773), thanks @HertzDevil) @@ -1483,32 +1518,36 @@ - [CI] Pin version of ubuntu base image for circleci jobs ([#12030](https://github.com/crystal-lang/crystal/pull/12030), thanks @straight-shoota) - Samples: avoid `Symbol` variables ([#11923](https://github.com/crystal-lang/crystal/pull/11923), thanks @HertzDevil) -# 1.4.1 (2022-04-22) +## [1.4.1] - 2022-04-22 -## Standard Library +[1.4.1]: https://github.com/crystal-lang/crystal/releases/1.4.1 -### Collection +### Standard Library + +#### Collection - Avoid compile-time error on empty `NamedTuple`s. ([#12007](https://github.com/crystal-lang/crystal/pull/12007), thanks @I3oris) -### Files +#### Files - Add missing fun def for `__xstat` ([#11985](https://github.com/crystal-lang/crystal/pull/11985), thanks @straight-shoota) -### Runtime +#### Runtime - Add `pthread` link annotations in lib bindings ([#12013](https://github.com/crystal-lang/crystal/pull/12013), thanks @straight-shoota) - Fix GC typedefs on Windows ([#11963](https://github.com/crystal-lang/crystal/pull/11963), thanks @HertzDevil) -## Compiler +### Compiler -### Semantic +#### Semantic - Compiler: remove duplicate instance vars once we know them all ([#11995](https://github.com/crystal-lang/crystal/pull/11995), thanks @asterite) -# 1.4.0 (2022-04-06) +## [1.4.0] - 2022-04-06 + +[1.4.0]: https://github.com/crystal-lang/crystal/releases/1.4.0 -## Language +### Language - Add support for `Int128` in codegen and macros ([#11576](https://github.com/crystal-lang/crystal/pull/11576), thanks @BlobCodes) - Support `ProcPointer`s with global path and top-level method references ([#11777](https://github.com/crystal-lang/crystal/pull/11777), thanks @HertzDevil) @@ -1516,7 +1555,7 @@ - Experimental: better type inference for ivars/cvars ([#11812](https://github.com/crystal-lang/crystal/pull/11812), thanks @asterite) - Support `@[Deprecated]` on constants ([#11680](https://github.com/crystal-lang/crystal/pull/11680), thanks @HertzDevil) -## Standard Library +### Standard Library - Fix compiler flags with optional arg eating following flags ([#11201](https://github.com/crystal-lang/crystal/pull/11201), thanks @yb66) - Support GNU style optional arguments in `OptionParser` ([#11546](https://github.com/crystal-lang/crystal/pull/11546), thanks @HertzDevil) @@ -1527,7 +1566,7 @@ - Add support for LLVM 14.0 ([#11905](https://github.com/crystal-lang/crystal/pull/11905), thanks @HertzDevil) - Fix code examples in doc comments (2022-03) ([#11927](https://github.com/crystal-lang/crystal/pull/11927), thanks @maiha) -### Collection +#### Collection - Remove `Iterator.of(Iterator.stop)` from implementations ([#11613](https://github.com/crystal-lang/crystal/pull/11613), thanks @asterite) - Add allow `Enumerable` arguments for `Hash#select` and `#reject` ([#11750](https://github.com/crystal-lang/crystal/pull/11750), thanks @mamantoha) @@ -1542,11 +1581,11 @@ - Improve `BitArray`'s constructors ([#11898](https://github.com/crystal-lang/crystal/pull/11898), thanks @HertzDevil) - Add overload to `Enumerable#tally` and `#tally_by` accepting a hash ([#11815](https://github.com/crystal-lang/crystal/pull/11815), thanks @mamantoha) -### Crypto +#### Crypto - Add support for Bcrypt algorithm version `2b` ([#11595](https://github.com/crystal-lang/crystal/pull/11595), thanks @docelic) -### Files +#### Files - Fix race condition in `chown` ([#11885](https://github.com/crystal-lang/crystal/pull/11885), thanks @didactic-drunk) - Add docs for `Dir#each_child` ([#11688](https://github.com/crystal-lang/crystal/pull/11688), thanks @wontruefree) @@ -1557,11 +1596,11 @@ - Fix `IO::FileDescriptor#pos` giving incorrect position after write ([#10865](https://github.com/crystal-lang/crystal/pull/10865), thanks @didactic-drunk) - Remove reference to binary file mode in `File.open` ([#11824](https://github.com/crystal-lang/crystal/pull/11824), thanks @HertzDevil) -### Macros +#### Macros - Add `#parse_type` ([#11126](https://github.com/crystal-lang/crystal/pull/11126), thanks @Blacksmoke16) -### Networking +#### Networking - **(performance)** Optimize `URI.decode` ([#11741](https://github.com/crystal-lang/crystal/pull/11741), thanks @asterite) - Fix `address_spec` expectation for Windows Server 2022 ([#11794](https://github.com/crystal-lang/crystal/pull/11794), thanks @straight-shoota) @@ -1569,7 +1608,7 @@ - Improve `URI::Params#inspect` to use hash-like literal ([#11880](https://github.com/crystal-lang/crystal/pull/11880), thanks @straight-shoota) - Use enums instead of symbols for `MIME::Multipart` and `HTTP::FormData` ([#11617](https://github.com/crystal-lang/crystal/pull/11617), thanks @HertzDevil) -### Numeric +#### Numeric - **(breaking-change)** Fix: Hide `BigDecimal::ZERO` and `BigDecimal::TEN` ([#11820](https://github.com/crystal-lang/crystal/pull/11820), thanks @lbguilherme) - **(breaking-change)** Add support for scientific notation in `BigFloat#to_s` ([#10632](https://github.com/crystal-lang/crystal/pull/10632), thanks @HertzDevil) @@ -1578,7 +1617,7 @@ - Fix E notation parsing in `BigDecimal` ([#9577](https://github.com/crystal-lang/crystal/pull/9577), thanks @stevegeek) - **(performance)** Optimize Integer decoding from bytes ([#11796](https://github.com/crystal-lang/crystal/pull/11796), thanks @carlhoerberg) -### Runtime +#### Runtime - Fix interpreter when shared library `pthread` is missing ([#11807](https://github.com/crystal-lang/crystal/pull/11807), thanks @straight-shoota) - **(performance)** Implement `Intrinsics.pause` for aarch64 ([#11742](https://github.com/crystal-lang/crystal/pull/11742), thanks @lbguilherme, @jgaskins) @@ -1587,17 +1626,17 @@ - Improve error for incompatible generic arguments for `WeakRef` ([#11911](https://github.com/crystal-lang/crystal/pull/11911), thanks @straight-shoota) - Add the wasm entrypoint defined in Crystal ([#11936](https://github.com/crystal-lang/crystal/pull/11936), thanks @lbguilherme) -### Serialization +#### Serialization - Allow passing instance method or conditional expressions to option `ignore_serialize` on `JSON::Field` ([#11804](https://github.com/crystal-lang/crystal/pull/11804), thanks @cyangle) - Implement `Iterator.from_json` and `#to_json` ([#10437](https://github.com/crystal-lang/crystal/pull/10437), thanks @wonderix) -### Specs +#### Specs - Add `file` and `line` arguments to `it_iterates` ([#11628](https://github.com/crystal-lang/crystal/pull/11628), thanks @straight-shoota) - Remove duplicate word in documentation ([#11797](https://github.com/crystal-lang/crystal/pull/11797), thanks @samueleaton) -### System +#### System - **(breaking-change)** **(security)** Fix character mappings for Windows path conversions ([#11847](https://github.com/crystal-lang/crystal/pull/11847), thanks @straight-shoota) - Add fallback for `Path.home` on Unix ([#11544](https://github.com/crystal-lang/crystal/pull/11544), thanks @HertzDevil) @@ -1606,7 +1645,7 @@ - Fix `Path` support for UNC shares ([#11827](https://github.com/crystal-lang/crystal/pull/11827), thanks @straight-shoota) - Fix regression for Linux older than 3.17: properly check that `getrandom` is available ([#11953](https://github.com/crystal-lang/crystal/pull/11953), thanks @lbguilherme) -### Text +#### Text - Fix ensure PCRE JIT mode is available before running spec ([#11533](https://github.com/crystal-lang/crystal/pull/11533), thanks @Blacksmoke16) - Add more `Colorize` overloads and fix docs ([#11832](https://github.com/crystal-lang/crystal/pull/11832), thanks @asterite) @@ -1619,12 +1658,12 @@ - Add `self` return type to `UUID` constructor methods ([#10539](https://github.com/crystal-lang/crystal/pull/10539), thanks @straight-shoota) - Fix infinite loop for certain `StringPool` initial capacities ([#11929](https://github.com/crystal-lang/crystal/pull/11929), thanks @HertzDevil) -### Time +#### Time - Add examples to `Time::Format` methods ([#11713](https://github.com/crystal-lang/crystal/pull/11713), thanks @ThunderKey) - Support day of year (`%j`) in `Time` parsers ([#11791](https://github.com/crystal-lang/crystal/pull/11791), thanks @HertzDevil) -## Compiler +### Compiler - Hello WebAssembly! (MVP implementation) ([#10870](https://github.com/crystal-lang/crystal/pull/10870), thanks @lbguilherme) - Fix compiler specs git integration for configurable default branch ([#11754](https://github.com/crystal-lang/crystal/pull/11754), thanks @yxhuvud) @@ -1642,20 +1681,20 @@ - Fix incorrect var type inside nested exception handler ([#11928](https://github.com/crystal-lang/crystal/pull/11928), thanks @asterite) - Fix: Look up return type in defining type ([#11962](https://github.com/crystal-lang/crystal/pull/11962), thanks @asterite) -### Codegen +#### Codegen - **(performance)** Codegen: Do not always request value for `Proc#call` ([#11675](https://github.com/crystal-lang/crystal/pull/11675), thanks @HertzDevil) -### Debugger +#### Debugger - Fix debug location of inlined `Proc#call` body ([#11676](https://github.com/crystal-lang/crystal/pull/11676), thanks @HertzDevil) -### Generics +#### Generics - Resolve non-type free variables in return type restrictions ([#11861](https://github.com/crystal-lang/crystal/pull/11861), thanks @HertzDevil) - Fix recursive `pointerof` detection with generic splat type variables ([#11811](https://github.com/crystal-lang/crystal/pull/11811), thanks @HertzDevil) -### Interpreter +#### Interpreter - Fix for Crystal interpreter crash ([#11717](https://github.com/crystal-lang/crystal/pull/11717), thanks @wmoxam) - Interpreter: support `Tuple#[]` with range literals ([#11783](https://github.com/crystal-lang/crystal/pull/11783), thanks @HertzDevil) @@ -1665,7 +1704,7 @@ - Improve `Crystal::Loader` errors ([#11860](https://github.com/crystal-lang/crystal/pull/11860), thanks @straight-shoota) - Enable interpreter integration specs for `YAML` ([#11801](https://github.com/crystal-lang/crystal/pull/11801), thanks @straight-shoota) -### Parser +#### Parser - Fix parser error with semicolon + newline in parenthesized `Expressions` ([#11769](https://github.com/crystal-lang/crystal/pull/11769), thanks @straight-shoota) - Fix comment indentation in `ASTNode#to_s` ([#11851](https://github.com/crystal-lang/crystal/pull/11851), thanks @FnControlOption) @@ -1674,7 +1713,7 @@ - Lexer: use `Crystal::Token::Kind` enum instead of symbols ([#11616](https://github.com/crystal-lang/crystal/pull/11616), thanks @HertzDevil) - Support `Generic` nodes with no type variables ([#11906](https://github.com/crystal-lang/crystal/pull/11906), thanks @HertzDevil) -### Semantic +#### Semantic - **(breaking-change)** Drop `skip_abstract_def_check` flag support ([#9217](https://github.com/crystal-lang/crystal/pull/9217), thanks @makenowjust) - Add error when instance variable is inherited from module and supertype ([#11768](https://github.com/crystal-lang/crystal/pull/11768), thanks @straight-shoota) @@ -1682,16 +1721,16 @@ - Fix: Prevent eager `instance_sizeof` on structs ([#11856](https://github.com/crystal-lang/crystal/pull/11856), thanks @mattrberry) - Fix: Do not consider global `Path` in def parameter restriction as free variable ([#11862](https://github.com/crystal-lang/crystal/pull/11862), thanks @HertzDevil) -## Tools +### Tools - Do not inherit from `Hash` in the compiler ([#11707](https://github.com/crystal-lang/crystal/pull/11707), thanks @HertzDevil) - Use `OptionParser` in `crystal env` ([#11720](https://github.com/crystal-lang/crystal/pull/11720), thanks @HertzDevil) -### Playground +#### Playground - Replace PNG icon with optimized SVG for playground ([#7616](https://github.com/crystal-lang/crystal/pull/7616), thanks @straight-shoota) -## Other +### Other - Update previous Crystal release - 1.3.2 ([#11715](https://github.com/crystal-lang/crystal/pull/11715), thanks @straight-shoota) - Add `scripts/release-update.sh` ([#11716](https://github.com/crystal-lang/crystal/pull/11716), thanks @straight-shoota) @@ -1710,40 +1749,46 @@ - Use `be_empty` expectations in more specs ([#11937](https://github.com/crystal-lang/crystal/pull/11937), thanks @HertzDevil) - [CI] Update distribution-scripts ([#11969](https://github.com/crystal-lang/crystal/pull/11969), thanks @straight-shoota) -# 1.3.2 (2022-01-18) +## [1.3.2] - 2022-01-18 + +[1.3.2]: https://github.com/crystal-lang/crystal/releases/1.3.2 -## Standard Library +### Standard Library -### Text +#### Text - Fix buffer overflow in `String#index` ([#11747](https://github.com/crystal-lang/crystal/pull/11747), thanks @asterite, @straight-shoota) -# 1.3.1 (2022-01-13) +## [1.3.1] - 2022-01-13 -## Standard Library +[1.3.1]: https://github.com/crystal-lang/crystal/releases/1.3.1 + +### Standard Library - Remove useless variable declarations in trailing position ([#11704](https://github.com/crystal-lang/crystal/pull/11704), thanks @HertzDevil) -### Crypto +#### Crypto - Fix for missing `BIO_*` functions in OpenSSL < 1.1.0 ([#11736](https://github.com/crystal-lang/crystal/pull/11736), thanks @daliborfilus) -### Runtime +#### Runtime - Remove string allocation from `GC_set_warn_proc` ([#11729](https://github.com/crystal-lang/crystal/pull/11729), thanks @straight-shoota) -## Tools +### Tools - Doc generator: Fix escape HTML in code span ([#11686](https://github.com/crystal-lang/crystal/pull/11686), thanks @straight-shoota) - Fix formatter error for `ProcLiteral`s with `Union` return type ([#11709](https://github.com/crystal-lang/crystal/pull/11709), thanks @HertzDevil) -## Other +### Other - Fix typos ([#11725](https://github.com/crystal-lang/crystal/pull/11725), thanks @kianmeng) -# 1.3.0 (2022-01-06) +## [1.3.0] - 2022-01-06 + +[1.3.0]: https://github.com/crystal-lang/crystal/releases/1.3.0 -## Compiler +### Compiler - Refer to `T.class` as "metaclass" in error messages, not "class" ([#11378](https://github.com/crystal-lang/crystal/pull/11378), thanks @HertzDevil) - Create `Reason` enum for exhaustive case in nil-reason check ([#11449](https://github.com/crystal-lang/crystal/pull/11449), thanks @rymiel) @@ -1758,7 +1803,7 @@ - Resolve compiler wildcard require ([#11562](https://github.com/crystal-lang/crystal/pull/11562), thanks @straight-shoota) - Compiler: use enums instead of symbols in various places ([#11607](https://github.com/crystal-lang/crystal/pull/11607), thanks @HertzDevil) -### Codegen +#### Codegen - Disable specs for `StaticArray#sort_by` on broken targets ([#11359](https://github.com/crystal-lang/crystal/pull/11359), thanks @straight-shoota) - Fix link flag behaviour on Windows MSVC ([#11424](https://github.com/crystal-lang/crystal/pull/11424), thanks @HertzDevil) @@ -1767,12 +1812,12 @@ - Fix codegen when instantiating class methods of typedefs ([#11636](https://github.com/crystal-lang/crystal/pull/11636), thanks @HertzDevil) - Add minimal load-time DLL support on Windows, support `dllimport` storage class ([#11573](https://github.com/crystal-lang/crystal/pull/11573), thanks @HertzDevil) -### Debugger +#### Debugger - Attach debug locations to auto-generated `initialize` methods ([#11313](https://github.com/crystal-lang/crystal/pull/11313), thanks @HertzDevil) - Fix debug location for `~check_proc_is_not_closure` ([#11311](https://github.com/crystal-lang/crystal/pull/11311), thanks @HertzDevil) -### Interpreter +#### Interpreter - `crystal i`, a Crystal interpreter ([#11159](https://github.com/crystal-lang/crystal/pull/11159), thanks @asterite) - Implement FFI bindings ([#11475](https://github.com/crystal-lang/crystal/pull/11475), thanks @straight-shoota) @@ -1781,7 +1826,7 @@ - Split interpreter specs into separate files ([#11578](https://github.com/crystal-lang/crystal/pull/11578), thanks @straight-shoota) - Workaround for GC issues in interpreter specs ([#11634](https://github.com/crystal-lang/crystal/pull/11634), thanks @straight-shoota) -### Parser +#### Parser - Parser: allow keyword as named argument inside macros ([#10377](https://github.com/crystal-lang/crystal/pull/10377), thanks @asterite) - Parser: add missing end location to `IsA` node ([#11351](https://github.com/crystal-lang/crystal/pull/11351), thanks @FnControlOption) @@ -1797,7 +1842,7 @@ - Unify format of "unexpected token" error ([#11473](https://github.com/crystal-lang/crystal/pull/11473), thanks @straight-shoota) - Implement lexer int128 support ([#11571](https://github.com/crystal-lang/crystal/pull/11571), thanks @BlobCodes) -### Semantic +#### Semantic - Show proper owner for `Class`'s methods in error messages ([#10590](https://github.com/crystal-lang/crystal/pull/10590), thanks @HertzDevil) - Be more strict about `ProcNotation` variable declarations ([#11372](https://github.com/crystal-lang/crystal/pull/11372), thanks @HertzDevil) @@ -1809,7 +1854,7 @@ - Remove and resolve spurious cast and its associated FIXME ([#11455](https://github.com/crystal-lang/crystal/pull/11455), thanks @rymiel) - Add pending spec for recursive abstract struct ([#11470](https://github.com/crystal-lang/crystal/pull/11470), thanks @HertzDevil) -## Language +### Language - **(breaking-change)** Require elements in 1-to-n assignments to match targets exactly ([#11145](https://github.com/crystal-lang/crystal/pull/11145), thanks @HertzDevil) - **(breaking-change)** Require right-hand side of one-to-many assignments to be `Indexable` ([#11545](https://github.com/crystal-lang/crystal/pull/11545), thanks @HertzDevil) @@ -1817,7 +1862,7 @@ - Make all AST nodes immutable through container-returning methods ([#11397](https://github.com/crystal-lang/crystal/pull/11397), thanks @HertzDevil) - Add auto upcast for integer and float values ([#11431](https://github.com/crystal-lang/crystal/pull/11431), [#11529](https://github.com/crystal-lang/crystal/pull/11529), thanks @asterite, @beta-ziliani) -## Standard Library +### Standard Library - Fix `Process::INITIAL_PWD` for non-existent path ([#10525](https://github.com/crystal-lang/crystal/pull/10525), thanks @straight-shoota) - Resolve some TODOs ([#11369](https://github.com/crystal-lang/crystal/pull/11369), thanks @straight-shoota) @@ -1828,7 +1873,7 @@ - Fix `Enum.parse` to handle case-sensitive member names ([#11659](https://github.com/crystal-lang/crystal/pull/11659), thanks @straight-shoota) - Improve docs for `Object#not_nil!` ([#11661](https://github.com/crystal-lang/crystal/pull/11661), thanks @straight-shoota) -### Collection +#### Collection - **(breaking-change)** Always use `start` as parameter in subrange-accepting methods ([#11350](https://github.com/crystal-lang/crystal/pull/11350), thanks @HertzDevil) - **(breaking-change)** Refactor `Indexable::Mutable#fill`'s overloads ([#11368](https://github.com/crystal-lang/crystal/pull/11368), thanks @HertzDevil) @@ -1841,7 +1886,7 @@ - **(performance)** Avoid reallocation in `Enumerable#each_cons` and `Iterator#cons`'s default reused array ([#10384](https://github.com/crystal-lang/crystal/pull/10384), thanks @HertzDevil) - Fix `Array#unshift` for large arrays ([#11656](https://github.com/crystal-lang/crystal/pull/11656), thanks @HertzDevil) -### Crypto +#### Crypto - Support OpenSSL on Windows ([#11477](https://github.com/crystal-lang/crystal/pull/11477), thanks @HertzDevil) - Encode OpenSSL version on Windows ([#11516](https://github.com/crystal-lang/crystal/pull/11516), thanks @HertzDevil) @@ -1849,21 +1894,21 @@ - Fix `getrandom` for interpreter ([#11624](https://github.com/crystal-lang/crystal/pull/11624), thanks @straight-shoota) - **(performance)** Use more efficient method to split `UInt32` to bytes in `Crypto::Blowfish` ([#11594](https://github.com/crystal-lang/crystal/pull/11594), thanks @BlobCodes) -### Files +#### Files - Add bindings to `__xstat`, `__fxstat` and `__lxstat` for x86_64-linux-gnu ([#11361](https://github.com/crystal-lang/crystal/pull/11361), [#11536](https://github.com/crystal-lang/crystal/pull/11536), thanks @straight-shoota) - Fix `IO::Memory#to_s` appending to itself ([#11643](https://github.com/crystal-lang/crystal/pull/11643), thanks @straight-shoota) -### LLVM +#### LLVM - Fix `LLVMExtDIBuilderCreateArrayType` argument `alignInBits` should be `UInt64` ([#11644](https://github.com/crystal-lang/crystal/pull/11644), thanks @lbguilherme) -### Log +#### Log - Add `Log.with_context` with kwargs ([#11517](https://github.com/crystal-lang/crystal/pull/11517), thanks @caspiano) - Refactor `Log::BroadcastBackend#single_backend?` ([#11530](https://github.com/crystal-lang/crystal/pull/11530), thanks @straight-shoota) -### Macros +#### Macros - Add macro methods for `Return`, `Break`, `Next`, `Yield`, and exception handlers ([#10822](https://github.com/crystal-lang/crystal/pull/10822), thanks @HertzDevil) - Add `Crystal::Macros::ProcNotation#resolve` and `#resolve?` ([#11373](https://github.com/crystal-lang/crystal/pull/11373), thanks @HertzDevil) @@ -1872,7 +1917,7 @@ - Allow incomplete range arguments for `#[](Range)` macro methods ([#11380](https://github.com/crystal-lang/crystal/pull/11380), thanks @HertzDevil) - Add macro methods for `Metaclass` nodes ([#11375](https://github.com/crystal-lang/crystal/pull/11375), thanks @HertzDevil) -### Networking +#### Networking - Datagram support for `UNIXServer` ([#11426](https://github.com/crystal-lang/crystal/pull/11426), thanks @carlhoerberg) - Fix `WebSocket#stream` flushing for not exactly buffer size, add specs ([#11299](https://github.com/crystal-lang/crystal/pull/11299), thanks @will) @@ -1883,7 +1928,7 @@ - `TCPServer`: explain how to get an ephemeral port ([#11407](https://github.com/crystal-lang/crystal/pull/11407), thanks @rdp) - Fix `HTTP::Server::Response#close` when replaced output syncs close ([#11631](https://github.com/crystal-lang/crystal/pull/11631), thanks @straight-shoota) -### Numeric +#### Numeric - **(breaking-change)** Fix `Random.rand(max : Float32)` return `Float32` ([#9946](https://github.com/crystal-lang/crystal/pull/9946), thanks @j8r) - Fix `Math` linking errors on Windows MSVC ([#11435](https://github.com/crystal-lang/crystal/pull/11435), thanks @HertzDevil) @@ -1897,23 +1942,23 @@ - Add workaround for 128-bit integer division/modulo on Windows ([#11551](https://github.com/crystal-lang/crystal/pull/11551), thanks @HertzDevil) - Reject near-boundary and NaN values for `Float`-to-`Int` conversions ([#11230](https://github.com/crystal-lang/crystal/pull/11230), thanks @HertzDevil) -### Runtime +#### Runtime - GC/Boehm: Silence GC warnings about big allocations. ([#11289](https://github.com/crystal-lang/crystal/pull/11289), thanks @yxhuvud) - Disable impossible spec on win32, previously marked as pending ([#11451](https://github.com/crystal-lang/crystal/pull/11451), thanks @straight-shoota) - Support call stacks on Windows ([#11461](https://github.com/crystal-lang/crystal/pull/11461), thanks @HertzDevil) - Make Windows PDB lookup relative to running executable ([#11493](https://github.com/crystal-lang/crystal/pull/11493), thanks @HertzDevil) -### Serialization +#### Serialization - Parses JSON `UInt64` numbers. ([#11395](https://github.com/crystal-lang/crystal/pull/11395), thanks @hugopl) - Fix `YAML::Any` deserialize with alias ([#11532](https://github.com/crystal-lang/crystal/pull/11532), thanks @straight-shoota) -### Specs +#### Specs - Use enums instead of symbols for `Spec`-related types ([#11585](https://github.com/crystal-lang/crystal/pull/11585), thanks @HertzDevil) -### System +#### System - Add native Linux syscall interface ([#10777](https://github.com/crystal-lang/crystal/pull/10777), thanks @lbguilherme) - Implement `Path.home` on Windows ([#11503](https://github.com/crystal-lang/crystal/pull/11503), thanks @HertzDevil) @@ -1922,7 +1967,7 @@ - Enable `kernel_spec.cr` on Windows CI ([#11554](https://github.com/crystal-lang/crystal/pull/11554), thanks @HertzDevil) - Fix `getrandom` syscall was blocking and didn't had proper error checking ([#11460](https://github.com/crystal-lang/crystal/pull/11460), thanks @lbguilherme) -### Text +#### Text - Regex: use `PCRE_UCP` ([#11265](https://github.com/crystal-lang/crystal/pull/11265), thanks @asterite) - Add missing `it` in `UUID` spec ([#11353](https://github.com/crystal-lang/crystal/pull/11353), thanks @darkstego) @@ -1945,7 +1990,7 @@ - Add docs to `Colorize` ([#11664](https://github.com/crystal-lang/crystal/pull/11664), thanks @straight-shoota) - Support ANSI escape sequence output on more Windows consoles ([#11622](https://github.com/crystal-lang/crystal/pull/11622), thanks @HertzDevil) -## Tools +### Tools - [docs] Fix ditto with additional lines ([#11336](https://github.com/crystal-lang/crystal/pull/11336), thanks @straight-shoota) - [docs] Compact some JSON fields for search ([#11438](https://github.com/crystal-lang/crystal/pull/11438), thanks @rymiel) @@ -1957,7 +2002,7 @@ - [formatter] Fix space between call name and parenthesized arg ([#11523](https://github.com/crystal-lang/crystal/pull/11523), thanks @straight-shoota) - [playground] Refactor `PlaygroundPage` resources list ([#11608](https://github.com/crystal-lang/crystal/pull/11608), thanks @straight-shoota) -## Other +### Other - Update previous Crystal release - 1.2.2 ([#11430](https://github.com/crystal-lang/crystal/pull/11430), thanks @straight-shoota) - Prepare 1.3.0-dev ([#11317](https://github.com/crystal-lang/crystal/pull/11317), thanks @straight-shoota) @@ -1981,9 +2026,11 @@ - [CI] Remove `libatomic_ops` ([#11598](https://github.com/crystal-lang/crystal/pull/11598), thanks @straight-shoota) - Update NOTICE Copyright year to 2022 ([#11679](https://github.com/crystal-lang/crystal/pull/11679), thanks @matiasgarciaisaia) -# 1.2.2 (2021-11-10) +## [1.2.2] - 2021-11-10 + +[1.2.2]: https://github.com/crystal-lang/crystal/releases/1.2.2 -## Compiler +### Compiler - x86_64 ABI: pass structs indirectly if there are no more available registers ([#11344](https://github.com/crystal-lang/crystal/pull/11344), thanks @ggiraldez) - Add parentheses around type name for metaclasses of unions ([#11315](https://github.com/crystal-lang/crystal/pull/11315), thanks @HertzDevil) @@ -1991,50 +2038,54 @@ - **(regression)** Add fallback for union debug type if current debug file is not set ([#11390](https://github.com/crystal-lang/crystal/pull/11390), thanks @maxfierke) - **(regression)** Add missing debug locations to constant / class variable read calls ([#11417](https://github.com/crystal-lang/crystal/pull/11417), thanks @HertzDevil) -## Standard Library +### Standard Library -### Collection +#### Collection - Fix `BitArray#toggle` when toggling empty subrange ([#11381](https://github.com/crystal-lang/crystal/pull/11381), thanks @HertzDevil) -### Crypto +#### Crypto - Update for OpenSSL 3.0.0 ([#11360](https://github.com/crystal-lang/crystal/pull/11360), thanks @straight-shoota) - Restore libressl support and add CI for that ([#11400](https://github.com/crystal-lang/crystal/pull/11400), thanks @straight-shoota) - Replace lib version comparisons by functional feature checks ([#11374](https://github.com/crystal-lang/crystal/pull/11374), thanks @straight-shoota) -### Runtime +#### Runtime - Add support for DWARF 5 ([#11399](https://github.com/crystal-lang/crystal/pull/11399), thanks @straight-shoota) - Retrieve filename of shared libs, use in stacktraces ([#11408](https://github.com/crystal-lang/crystal/pull/11408), thanks @rdp) -## Other +### Other - [CI] Fix enable nix-command as experimental feature ([#11398](https://github.com/crystal-lang/crystal/pull/11398), thanks @straight-shoota) - [CI] Fix OpenSSL 3 apk package name ([#11418](https://github.com/crystal-lang/crystal/pull/11418), thanks @straight-shoota) - Update distribution-scripts ([#11404](https://github.com/crystal-lang/crystal/pull/11404), thanks @straight-shoota) - [CI] Fix pcre download URL ([#11422](https://github.com/crystal-lang/crystal/pull/11422), thanks @straight-shoota) -# 1.2.1 (2021-10-21) +## [1.2.1] - 2021-10-21 + +[1.2.1]: https://github.com/crystal-lang/crystal/releases/1.2.1 -## Compiler +### Compiler - Adding location to the Path returned by the literal expander for regex ([#11334](https://github.com/crystal-lang/crystal/pull/11334), thanks @beta-ziliani) -## Standard Library +### Standard Library - Add support for LLVM 13 ([#11302](https://github.com/crystal-lang/crystal/pull/11302), thanks @maxfierke) -### Runtime +#### Runtime - Move the `:nodoc:` flags to the right place to hide the `__mul*` functions. ([#11326](https://github.com/crystal-lang/crystal/pull/11326), thanks @wyhaines) -## Tools +### Tools - Update markd subtree to v0.4.2 ([#11338](https://github.com/crystal-lang/crystal/pull/11338), thanks @straight-shoota) -# 1.2.0 (2021-10-13) +## [1.2.0] - 2021-10-13 -## Compiler +[1.2.0]: https://github.com/crystal-lang/crystal/releases/1.2.0 + +### Compiler - Fix variance checks between generic instances for `Proc#call` and abstract defs. ([#10899](https://github.com/crystal-lang/crystal/pull/10899), thanks @HertzDevil) - Fix `proc_spec` forcing normal compilation instead of JIT ([#10964](https://github.com/crystal-lang/crystal/pull/10964), thanks @straight-shoota) @@ -2084,7 +2135,7 @@ - Support generic module instances in `TypeNode#includers` ([#11116](https://github.com/crystal-lang/crystal/pull/11116), thanks @HertzDevil) - Reject hash literals with mixed syntax ([#11154](https://github.com/crystal-lang/crystal/pull/11154), thanks @MakeNowJust) -## Language +### Language - Make `.as?(NoReturn)` always return `nil` ([#10896](https://github.com/crystal-lang/crystal/pull/10896), thanks @HertzDevil) - Compiler: make `is_a?(union)` work correctly for virtual types ([#11176](https://github.com/crystal-lang/crystal/pull/11176), thanks @asterite) @@ -2096,7 +2147,7 @@ - Add clarification about when `instance_vars` can be called ([#11171](https://github.com/crystal-lang/crystal/pull/11171), thanks @willhbr) - Add `file_exists?` macro method ([#10540](https://github.com/crystal-lang/crystal/pull/10540), thanks @Sija) -## Standard Library +### Standard Library - **(breaking-change)** Change nonsense return types to Nil: uncategorized ([#10625](https://github.com/crystal-lang/crystal/pull/10625), thanks @oprypin) - **(breaking-change)** Change nonsense return types to Nil in formatter classes ([#10623](https://github.com/crystal-lang/crystal/pull/10623), thanks @oprypin) @@ -2107,7 +2158,7 @@ - Don't use `:nodoc:` when overriding public methods ([#11096](https://github.com/crystal-lang/crystal/pull/11096), thanks @HertzDevil) - Add internal registry implementation for win32 ([#11137](https://github.com/crystal-lang/crystal/pull/11137), thanks @straight-shoota) -### Collection +#### Collection - **(breaking-change)** Move `Array#product` to `Indexable#cartesian_product` ([#10013](https://github.com/crystal-lang/crystal/pull/10013), thanks @HertzDevil) - Disallow `Slice(T).new(Int)` where `T` is a union of primitive number types ([#10982](https://github.com/crystal-lang/crystal/pull/10982), thanks @HertzDevil) @@ -2127,13 +2178,13 @@ - Add and improve type restrictions of block arguments ([#10467](https://github.com/crystal-lang/crystal/pull/10467), [#11246](https://github.com/crystal-lang/crystal/pull/11246), [#11267](https://github.com/crystal-lang/crystal/pull/11267, [#11308](https://github.com/crystal-lang/crystal/pull/11308), thanks @caspiano, thanks @straight-shoota, thanks @HertzDevil, thanks @beta-ziliani, thanks @caspiano) - **(performance)** Optimize `#rotate!` ([#11198](https://github.com/crystal-lang/crystal/pull/11198), thanks @HertzDevil) -### Concurrency +#### Concurrency - Fix Documentation of `Fiber.timeout` ([#11271](https://github.com/crystal-lang/crystal/pull/11271), thanks @toddsundsted) - **(performance)** `Scheduler#reschedule`: Shortcut lookup for current fiber. ([#11156](https://github.com/crystal-lang/crystal/pull/11156), thanks @yxhuvud) - Add sleep support to win32 event loop ([#10605](https://github.com/crystal-lang/crystal/pull/10605), thanks @straight-shoota) -### Files +#### Files - **(breaking-change)** Change nonsense return types to Nil in IO-related methods ([#10621](https://github.com/crystal-lang/crystal/pull/10621), thanks @oprypin) - Fix `File.match?` to accept `Path` type as `path` argument ([#11075](https://github.com/crystal-lang/crystal/pull/11075), thanks @fishnibble) @@ -2144,7 +2195,7 @@ - Make `FileUtils.mv` work across filesystems ([#10783](https://github.com/crystal-lang/crystal/pull/10783), thanks @naqvis) - **(performance)** Improve performance of `Path#dirname` and `Path#extension` ([#11001](https://github.com/crystal-lang/crystal/pull/11001), thanks @BlobCodes) -### Networking +#### Networking - **(breaking-change)** Change nonsense return types to `Nil` in HTTP-related methods and `Log` ([#10624](https://github.com/crystal-lang/crystal/pull/10624), thanks @oprypin) - Fix trailing `rescue` syntax ([#11083](https://github.com/crystal-lang/crystal/pull/11083), thanks @straight-shoota) @@ -2159,7 +2210,7 @@ - Implement `Socket` for win32 ([#10784](https://github.com/crystal-lang/crystal/pull/10784), thanks @straight-shoota) - Add `URI.encode_path` and deprecate `URI.encode` ([#11248](https://github.com/crystal-lang/crystal/pull/11248), thanks @straight-shoota) -### Numeric +#### Numeric - **(breaking-change)** Refine type restriction of `Math.frexp(BigFloat)` ([#10998](https://github.com/crystal-lang/crystal/pull/10998), thanks @straight-shoota) - Fix `BigInt#to_s` emitting null bytes for certain values ([#11063](https://github.com/crystal-lang/crystal/pull/11063), thanks @HertzDevil) @@ -2174,11 +2225,11 @@ - Fix `BigDecimal` operations with floats ([#10874](https://github.com/crystal-lang/crystal/pull/10874), thanks @stakach) - Add `String#to_(u/i)128(?)` methods ([#11245](https://github.com/crystal-lang/crystal/pull/11245), thanks @BlobCodes) -### Runtime +#### Runtime - Extract `libunwind` from callstack ([#11205](https://github.com/crystal-lang/crystal/pull/11205), thanks @straight-shoota) -### Serialization +#### Serialization - **(breaking-change)** Change nonsense return types to `Nil`: JSON and YAML ([#10622](https://github.com/crystal-lang/crystal/pull/10622), thanks @oprypin) - **(breaking-change)** Add type restriction and conversion to `YAML::PullParser#location` ([#10997](https://github.com/crystal-lang/crystal/pull/10997), thanks @straight-shoota) @@ -2188,17 +2239,17 @@ - XML Namespace improvements ([#11072](https://github.com/crystal-lang/crystal/pull/11072), thanks @Blacksmoke16) - Add JSON/YAML serialization to `URI` ([#10404](https://github.com/crystal-lang/crystal/pull/10404), thanks @straight-shoota) -### Specs +#### Specs - Add missing require in `iterator_spec` ([#11148](https://github.com/crystal-lang/crystal/pull/11148), thanks @asterite) - Add missing requires to run a couple of specs standalone ([#11152](https://github.com/crystal-lang/crystal/pull/11152), thanks @asterite) - Allow `describe` without requiring an argument ([#10974](https://github.com/crystal-lang/crystal/pull/10974), thanks @straight-shoota) -### System +#### System - SystemError: Fix inconsistent signature. ([#11002](https://github.com/crystal-lang/crystal/pull/11002), thanks @yxhuvud) -### Text +#### Text - **(breaking-change)** Deprecate `String#unsafe_byte_at` ([#10559](https://github.com/crystal-lang/crystal/pull/10559), thanks @straight-shoota) - **(breaking-change)** Rename `IO#write_utf8` to `#write_string`. ([#11051](https://github.com/crystal-lang/crystal/pull/11051), thanks @straight-shoota) @@ -2210,7 +2261,7 @@ - Disallow non-UTF-8 encoding settings for `String::Builder` ([#11025](https://github.com/crystal-lang/crystal/pull/11025), thanks @HertzDevil) - Unicode: update to version 14.0.0 ([#11215](https://github.com/crystal-lang/crystal/pull/11215), thanks @Blacksmoke16) -## Tools +### Tools - Formatter: Handle `(-> )` correctly ([#10945](https://github.com/crystal-lang/crystal/pull/10945), thanks @HertzDevil) - Use [markd](https://github.com/icyleaf/markd) for markdown rendering in the compiler ([#11040](https://github.com/crystal-lang/crystal/pull/11040), thanks @straight-shoota) @@ -2222,7 +2273,7 @@ - Make `WARNING` an admonition keyword ([#10898](https://github.com/crystal-lang/crystal/pull/10898), thanks @HertzDevil) - Refactor hierarchy printers ([#10791](https://github.com/crystal-lang/crystal/pull/10791), thanks @HertzDevil) -## Other +### Other - Fix typos ([#11045](https://github.com/crystal-lang/crystal/pull/11045), [#11163](https://github.com/crystal-lang/crystal/pull/11163), [#11138](https://github.com/crystal-lang/crystal/pull/11138), hanks @toshokan, thanks @MakeNowJust, thanks @schmijos) - Update readme to point to IRC channel on libera.chat ([#11024](https://github.com/crystal-lang/crystal/pull/11024), thanks @jhass) @@ -2250,34 +2301,38 @@ - [CI] Update distribution-scripts ([#11285](https://github.com/crystal-lang/crystal/pull/11285), thanks @straight-shoota) - [CI] Remove i386 builds ([#11287](https://github.com/crystal-lang/crystal/pull/11287), thanks @straight-shoota) -# 1.1.1 (2021-07-26) +## [1.1.1] - 2021-07-26 -## Language changes +[1.1.1]: https://github.com/crystal-lang/crystal/releases/1.1.1 + +### Language changes - Revert name of top-level module to `main` ([#10993](https://github.com/crystal-lang/crystal/pull/10993), thanks @beta-ziliani) -## Standard Library +### Standard Library - Fix missing required args for `Socket::Addrinfo::Error.new` ([#10960](https://github.com/crystal-lang/crystal/pull/10960), thanks @straight-shoota) - Fix disable unnecessary spec on win32 ([#10971](https://github.com/crystal-lang/crystal/pull/10971), thanks @straight-shoota) - Remove incorrect type restrictions on index methods with offset ([#10972](https://github.com/crystal-lang/crystal/pull/10972), thanks @straight-shoota) - Fix: documentation of `#step` in `Number` and `Char` ([#10966](https://github.com/crystal-lang/crystal/pull/10966), [#11006](https://github.com/crystal-lang/crystal/pull/11006), thanks @beta-ziliani and @straight-shoota) -## Compiler +### Compiler - Fix parsing macro body with escaped backslash in literal ([#10995](https://github.com/crystal-lang/crystal/pull/10995), thanks @straight-shoota) -## Other +### Other - Updating aarch64 actions to use 1.0.0 images ([#10976](https://github.com/crystal-lang/crystal/pull/10976), thanks @beta-ziliani) -# 1.1.0 (2021-07-14) +## [1.1.0] - 2021-07-14 + +[1.1.0]: https://github.com/crystal-lang/crystal/releases/1.1.0 -## Language changes +### Language changes - Support splat expansions inside tuple and array literals. ([#10429](https://github.com/crystal-lang/crystal/pull/10429), thanks @HertzDevil) - Support breaks with values inside `while` expressions. ([#10566](https://github.com/crystal-lang/crystal/pull/10566), thanks @HertzDevil) -### Macros +#### Macros - Add `@top_level` to access the top-level scope in macros. ([#10682](https://github.com/crystal-lang/crystal/pull/10682), thanks @beta-ziliani) - Fix: preserve integer sizes in `NumberLiteral#int_bin_op`. ([#10713](https://github.com/crystal-lang/crystal/pull/10713), thanks @collidedscope) @@ -2286,11 +2341,11 @@ - Minor fixes to docs of `UnaryExpression` macro nodes. ([#10816](https://github.com/crystal-lang/crystal/pull/10816), thanks @HertzDevil) - Add macro method `ASTNode#nil?`. ([#10850](https://github.com/crystal-lang/crystal/pull/10850), [#10616](https://github.com/crystal-lang/crystal/pull/10616), thanks @straight-shoota) -## Standard library +### Standard library -### Global changes +#### Global changes -#### Windows support +##### Windows support - Port `Socket::Address` to win32 . ([#10610](https://github.com/crystal-lang/crystal/pull/10610), thanks @straight-shoota) - Port `Socket::Addrinfo` to win32. ([#10650](https://github.com/crystal-lang/crystal/pull/10650), thanks @straight-shoota) @@ -2305,7 +2360,7 @@ - Fix `Socket::Connect` error in addrinfo inherit `os_error`. ([#10782](https://github.com/crystal-lang/crystal/pull/10782), thanks @straight-shoota) - Reorganize some win32 libc bindings ([#10776](https://github.com/crystal-lang/crystal/pull/10776), thanks @straight-shoota) -#### Type annotations +##### Type annotations - Add type restriction to private `Process` constructor. ([#7040](https://github.com/crystal-lang/crystal/pull/7040), thanks @z64) - Add various return type restrictions (thanks @oprypin, @straight-shoota, and @caspiano): @@ -2319,7 +2374,7 @@ [#10858](https://github.com/crystal-lang/crystal/pull/10858), [#10905](https://github.com/crystal-lang/crystal/pull/10905) - Add type restrictions for splat-less overloads of some methods. ([#10594](https://github.com/crystal-lang/crystal/pull/10594), thanks @HertzDevil) -### Numeric +#### Numeric - Add `Number.new` overload for `String`. ([#10422](https://github.com/crystal-lang/crystal/pull/10422), thanks @Blacksmoke16) - Fix `Math.pw2ceil` for zero and 64-bit integers. ([#10555](https://github.com/crystal-lang/crystal/pull/10555), thanks @straight-shoota) @@ -2334,7 +2389,7 @@ - Add support for using big rational `#**` with unsigned ints. ([#10887](https://github.com/crystal-lang/crystal/pull/10887), thanks @stakach) - Add overflow detection to `BigFloat#to_i64` and `#to_u64`. ([#10630](https://github.com/crystal-lang/crystal/pull/10630), thanks @HertzDevil) -### Text +#### Text - **(performance)** Optimize `Levenshtein.distance`. ([#8324](https://github.com/crystal-lang/crystal/pull/8324), thanks @r00ster91) - Refactor: add private `Slice#hexdump(io : IO)` overload. ([#10496](https://github.com/crystal-lang/crystal/pull/10496), thanks @HertzDevil) @@ -2343,7 +2398,7 @@ - Fix `Base64#encode`, exclude last 3 bytes from bswap. ([#10752](https://github.com/crystal-lang/crystal/pull/10752), thanks @kostya) - Refactor: avoid union type in `Char::Reader#decode_char_at`. ([#10758](https://github.com/crystal-lang/crystal/pull/10758), thanks @asterite) -### Collections +#### Collections - Add sub/superset checking methods to `Hash`. ([#7500](https://github.com/crystal-lang/crystal/pull/7500), thanks @Sija) - Improve documentation of `Array#[](Range)`. ([#10243](https://github.com/crystal-lang/crystal/pull/10243), thanks @straight-shoota) @@ -2356,7 +2411,7 @@ - Add methods for cumulative folding and prefix sums. ([#10789](https://github.com/crystal-lang/crystal/pull/10789), thanks @HertzDevil) - Fix: Pass read-only flag to peeked slice in `IO::Memory`. ([#10891](https://github.com/crystal-lang/crystal/pull/10891), thanks @z64) -### Crypto +#### Crypto - Add methods for getting peer certificates and signatures in `OpenSSL`. ([#8005](https://github.com/crystal-lang/crystal/pull/8005), thanks @will) - Add docs for `OpenSSL::Cipher`. ([#9934](https://github.com/crystal-lang/crystal/pull/9934), thanks @sol-vin) @@ -2364,24 +2419,24 @@ - Refine documentation for `Random#urlsafe_base64`. ([#10724](https://github.com/crystal-lang/crystal/pull/10724), thanks @straight-shoota) - Fix ssl context required for `add_x509_verify_flags`. ([#10756](https://github.com/crystal-lang/crystal/pull/10756), thanks @stakach) -### Time +#### Time - Improve error handling for `load_localtime` on unix. ([#10654](https://github.com/crystal-lang/crystal/pull/10654), thanks @straight-shoota) - Fix broken call to `Time#to_s`. ([#10778](https://github.com/crystal-lang/crystal/pull/10778), thanks @straight-shoota) - Fix `Time#shift` cover date boundaries with zone offset. ([#10871](https://github.com/crystal-lang/crystal/pull/10871), thanks @straight-shoota) -### Files +#### Files - Fix and unify documentation for `puts`. ([#10614](https://github.com/crystal-lang/crystal/pull/10614), thanks @straight-shoota) - Fix `Path#sibling` return type. ([#10655](https://github.com/crystal-lang/crystal/pull/10655), thanks @Sija) - Add `Path` in `FileUtils`'s methods to match the interfaces it's wrapping. ([#10747](https://github.com/crystal-lang/crystal/pull/10747), thanks @yb66) - Fix `FileDescriptor#pos` return `Int64` on armv6 ([#10845](https://github.com/crystal-lang/crystal/pull/10845), thanks @straight-shoota) -### Fibers +#### Fibers - Clarify documentation on `Path#join` and `#==`. ([#10455](https://github.com/crystal-lang/crystal/pull/10455), thanks @straight-shoota) -### Networking +#### Networking - Add an example middleware for `remote_address`. ([#10408](https://github.com/crystal-lang/crystal/pull/10408), thanks @oprypin) - Add `OAuth2::Client#http_client`. ([#10452](https://github.com/crystal-lang/crystal/pull/10452), thanks @straight-shoota) @@ -2394,17 +2449,17 @@ - Fix `HTTP::Cookie` parse quoted cookie value. ([#10853](https://github.com/crystal-lang/crystal/pull/10853), thanks @straight-shoota) - Add `Socket::Addrinfo#inspect` ([#10775](https://github.com/crystal-lang/crystal/pull/10775), thanks @straight-shoota) -### System +#### System - Fix sentence structure in `process.cr`. ([#9259](https://github.com/crystal-lang/crystal/pull/9259), thanks @matthewmcgarvey) -### Runtime +#### Runtime - Implement segfault handler in Crystal. ([#10463](https://github.com/crystal-lang/crystal/pull/10463), thanks @maxfierke) - Improve documentation for `Pointer.malloc` and `GC` methods. ([#10644](https://github.com/crystal-lang/crystal/pull/10644), thanks @straight-shoota) - Add links to literal types in the language reference. ([#10827](https://github.com/crystal-lang/crystal/pull/10827), thanks @straight-shoota) -### Serialization +#### Serialization - Add docs for some json methods. ([#10257](https://github.com/crystal-lang/crystal/pull/10257), thanks @rdp) - Add `UUID.from_json_object_key?`. ([#10517](https://github.com/crystal-lang/crystal/pull/10517), thanks @kalinon) @@ -2414,14 +2469,14 @@ - Add `UUID` to yaml parsing. ([#10715](https://github.com/crystal-lang/crystal/pull/10715), thanks @kalinon) - Fix double flushing json/yaml builders. ([#10716](https://github.com/crystal-lang/crystal/pull/10716), thanks @matthewmcgarvey) -### Specs +#### Specs - Add spec helper `it_iterates` for iteration methods. ([#10158](https://github.com/crystal-lang/crystal/pull/10158), [#10797](https://github.com/crystal-lang/crystal/pull/10797), thanks @straight-shoota) - Add usage instructions for spec runner to compiler. ([#10046](https://github.com/crystal-lang/crystal/pull/10046), thanks @straight-shoota) - Fix: Handle invalid option errors on `crystal spec`. ([#10787](https://github.com/crystal-lang/crystal/pull/10787), thanks @hugopl) - Include `spec/**` in docs_main. ([#10863](https://github.com/crystal-lang/crystal/pull/10863), thanks @straight-shoota) -## Compiler +### Compiler - Add support for type var splats inside `Tuple` during generic parameter substitution. ([#10232](https://github.com/crystal-lang/crystal/pull/10232), thanks @HertzDevil) - Fix: consider free vars in parameters of abstract def implementations before existing types, in particular fixing the creation of empty types. ([#10503](https://github.com/crystal-lang/crystal/pull/10503), thanks @HertzDevil) @@ -2456,16 +2511,16 @@ - Allow underscore in block return type even if the type can't be computed ([#10933](https://github.com/crystal-lang/crystal/pull/10933), thanks @asterite) - Fix parser identifies call with named args as var ([#10842](https://github.com/crystal-lang/crystal/pull/10842), thanks @straight-shoota) -## Tools +### Tools -### Formatter +#### Formatter - Fix: allow trailing space in parenthesized unions. ([#10595](https://github.com/crystal-lang/crystal/pull/10595), thanks @HertzDevil) - Fix: don't consume newline after endless range literals. ([#10596](https://github.com/crystal-lang/crystal/pull/10596), thanks @HertzDevil) - Fix indentation of heredocs relative to delimiter. ([#10806](https://github.com/crystal-lang/crystal/pull/10806), thanks @HertzDevil) - Fix heredoc indent with outer indent. ([#10867](https://github.com/crystal-lang/crystal/pull/10867), thanks @straight-shoota) -### Doc generator +#### Doc generator - Fix escaping of argument lists in doc generator, expose JSON. ([#10109](https://github.com/crystal-lang/crystal/pull/10109), [#10821](https://github.com/crystal-lang/crystal/pull/10821), thanks @oprypin and @Sija) - Print named generic type arguments of type restrictions in docs. ([#10424](https://github.com/crystal-lang/crystal/pull/10424), thanks @HertzDevil) @@ -2478,7 +2533,7 @@ - Fix docs generator search use `html_id`. ([#10875](https://github.com/crystal-lang/crystal/pull/10875), thanks @straight-shoota) - Fix `--sitemap-priority`, `--sitemap-changefreq`. ([#10906](https://github.com/crystal-lang/crystal/pull/10906), thanks @HertzDevil) -## Others +### Others - CI: Update to use 1.0.0. ([#10533](https://github.com/crystal-lang/crystal/pull/10533), thanks @bcardiff) - Bump distribution-scripts. ([#10639](https://github.com/crystal-lang/crystal/pull/10639), [#10673](https://github.com/crystal-lang/crystal/pull/10673), [#10754](https://github.com/crystal-lang/crystal/pull/10754), thanks @straight-shoota and @bcardiff) @@ -2491,37 +2546,39 @@ - Update distribution-scripts for shards 0.15.0. ([#10862](https://github.com/crystal-lang/crystal/pull/10862), thanks @straight-shoota) - Add smoke tests for platforms where we don't run full tests ([#10848](https://github.com/crystal-lang/crystal/pull/10848), thanks @straight-shoota) -# 1.0.0 (2021-03-22) +## [1.0.0] - 2021-03-22 + +[1.0.0]: https://github.com/crystal-lang/crystal/releases/1.0.0 -## Language changes +### Language changes - Support `Tuple#[](Range)` with compile-time range literals. ([#10379](https://github.com/crystal-lang/crystal/pull/10379), thanks @HertzDevil) -### Macros +#### Macros - Don't use named argument key names as parameters for `method_missing` calls. ([#10388](https://github.com/crystal-lang/crystal/pull/10388), thanks @HertzDevil) -## Standard library +### Standard library - **(breaking-change)** Drop deprecated definitions. ([#10386](https://github.com/crystal-lang/crystal/pull/10386), thanks @bcardiff) - Fix example codes in multiple places. ([#10505](https://github.com/crystal-lang/crystal/pull/10505), thanks @maiha) -### Macros +#### Macros - **(breaking-change)** Always add explicit return types in getter/property macros. ([#10405](https://github.com/crystal-lang/crystal/pull/10405), thanks @Sija) -### Numeric +#### Numeric - **(breaking-change)** Change default rounding mode to `TIES_EVEN`. ([#10508](https://github.com/crystal-lang/crystal/pull/10508), thanks @straight-shoota) - Fix downcasting float infinity. ([#10420](https://github.com/crystal-lang/crystal/pull/10420), thanks @straight-shoota) - Fix `String#to_f` out of range behaviour. ([#10425](https://github.com/crystal-lang/crystal/pull/10425), thanks @straight-shoota) - Implement rounding mode for `Number#round`. ([#10413](https://github.com/crystal-lang/crystal/pull/10413), [#10360](https://github.com/crystal-lang/crystal/pull/10360), [#10479](https://github.com/crystal-lang/crystal/pull/10479), thanks @straight-shoota) -### Text +#### Text - Add missing unicode whitespace support to `String` methods. ([#10367](https://github.com/crystal-lang/crystal/pull/10367), thanks @straight-shoota) -### Collections +#### Collections - Fix `Range#==` to ignore generic type arguments. ([#10309](https://github.com/crystal-lang/crystal/pull/10309), thanks @straight-shoota) - Make `Enumerable#flat_map`, `Iterator#flat_map` work with mixed element types. ([#10329](https://github.com/crystal-lang/crystal/pull/10329), thanks @HertzDevil) @@ -2530,19 +2587,19 @@ - Fix docs examples regarding `Set#*set_of?`. ([#10285](https://github.com/crystal-lang/crystal/pull/10285), thanks @oddp) - Fix expectation on set specs. ([#10482](https://github.com/crystal-lang/crystal/pull/10482), thanks @kachick) -### Serialization +#### Serialization - **(breaking-change)** Serialize `Enum` to underscored `String` by default. ([#10431](https://github.com/crystal-lang/crystal/pull/10431), thanks @straight-shoota, @caspiano) - **(breaking-change)** Use class instead of struct for types in XML module. ([#10436](https://github.com/crystal-lang/crystal/pull/10436), thanks @hugopl) - Add `YAML::Nodes::Node#kind`. ([#10432](https://github.com/crystal-lang/crystal/pull/10432), thanks @straight-shoota) -### Files +#### Files - Let `IO::Memory` not be writable with read-only `Slice`. ([#10391](https://github.com/crystal-lang/crystal/pull/10391), thanks @straight-shoota) - Allow `Int64` values within `IO#read_at`. ([#10356](https://github.com/crystal-lang/crystal/pull/10356), thanks @Blacksmoke16) - Add `IO::Sized#remaining=(value)` to reuse an existing instance. ([#10520](https://github.com/crystal-lang/crystal/pull/10520), thanks @jgaskins) -### Networking +#### Networking - **(security)** Remove Cookie Name Decoding. ([#10442](https://github.com/crystal-lang/crystal/pull/10442), thanks @security-curious) - **(breaking-change)** Remove implicit en-/decoding for cookie values. ([#10485](https://github.com/crystal-lang/crystal/pull/10485), thanks @straight-shoota) @@ -2555,21 +2612,21 @@ - Remove implicit `path=/` from `HTTP::Cookie`. ([#10491](https://github.com/crystal-lang/crystal/pull/10491), thanks @straight-shoota) - Add `HTTP::Request#local_address`. ([#10385](https://github.com/crystal-lang/crystal/pull/10385), thanks @carlhoerberg) -### Logging +#### Logging - Close `AsyncDispatcher` on `#finalize`. ([#10390](https://github.com/crystal-lang/crystal/pull/10390), thanks @straight-shoota) -### System +#### System - Fix `Process.parse_argument` behavior against a quote in a word. ([#10337](https://github.com/crystal-lang/crystal/pull/10337), thanks @MakeNowJust) - Add aarch64 support for macOS/darwin targets. ([#10348](https://github.com/crystal-lang/crystal/pull/10348), thanks @maxfierke, @RomainFranceschini) - Add `LibC::MAP_ANONYMOUS` to x86_64-darwin to match other platforms. ([#10398](https://github.com/crystal-lang/crystal/pull/10398), thanks @sourgrasses) -### Runtime +#### Runtime - Improve error message for ELF reader on uninitialized runtime. ([#10282](https://github.com/crystal-lang/crystal/pull/10282), thanks @straight-shoota) -## Compiler +### Compiler - **(breaking-change)** Disallow surrogate halves in escape sequences of string and character literals, use `\x` for arbitrary binary values. ([#10443](https://github.com/crystal-lang/crystal/pull/10443), thanks @HertzDevil) - Fix ICE when exhaustive in-clause calls pseudo-method. ([#10382](https://github.com/crystal-lang/crystal/pull/10382), thanks @HertzDevil) @@ -2578,7 +2635,7 @@ - Support closured vars inside `Const` initializer. ([#10478](https://github.com/crystal-lang/crystal/pull/10478), thanks @RX14) - Documentation grammar fix. ([#10369](https://github.com/crystal-lang/crystal/pull/10369), thanks @szTheory) -### Language semantics +#### Language semantics - Don't fail on untyped `is_a?`. ([#10320](https://github.com/crystal-lang/crystal/pull/10320), thanks @asterite) - Fix named arguments in `super` and `previous_def` calls. ([#10400](https://github.com/crystal-lang/crystal/pull/10400), thanks @HertzDevil) @@ -2593,11 +2650,11 @@ - Accept pointer instance types on falsey conditional branches. ([#10464](https://github.com/crystal-lang/crystal/pull/10464), thanks @HertzDevil) - Match named arguments by external parameter names when checking overload cover. ([#10530](https://github.com/crystal-lang/crystal/pull/10530), thanks @HertzDevil) -### Doc generator +#### Doc generator - Detect source locations in more situations. ([#10439](https://github.com/crystal-lang/crystal/pull/10439), thanks @oprypin) -## Others +### Others - CI improvements and housekeeping. ([#10299](https://github.com/crystal-lang/crystal/pull/10299), [#10340](https://github.com/crystal-lang/crystal/pull/10340), [#10476](https://github.com/crystal-lang/crystal/pull/10476), [#10480](https://github.com/crystal-lang/crystal/pull/10480), thanks @bcardiff, @Sija, @straight-shoota) - Update distribution-scripts to use Shards v0.14.1. ([#10494](https://github.com/crystal-lang/crystal/pull/10494), thanks @bcardiff) @@ -2605,6 +2662,6 @@ - Add LLVM 11.1 to the list of supported versions. ([#10523](https://github.com/crystal-lang/crystal/pull/10523), thanks @Sija) - Fix SDL examples crashes. ([#10470](https://github.com/crystal-lang/crystal/pull/10470), thanks @megatux) -## 0.x +### 0.x Older entries in [CHANGELOG.0.md](./CHANGELOG.0.md) From 30d998e0e4fe29a2234efb374fb57d37ea466985 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 25 Nov 2023 04:05:27 +0800 Subject: [PATCH 0792/1551] Fix location for "invalid trailing comma in call" errors (#13964) --- spec/compiler/parser/parser_spec.cr | 7 ++++--- src/compiler/crystal/syntax/parser.cr | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index c70bac5fa998..0ee3eedbdd62 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2087,13 +2087,14 @@ module Crystal end ) - assert_syntax_error %( + assert_syntax_error <<-CRYSTAL, "invalid trailing comma in call", line: 2, column: 8 if 1 foo 1, end - ), "invalid trailing comma in call" + CRYSTAL + + assert_syntax_error "foo 1,", "invalid trailing comma in call", line: 1, column: 6 - assert_syntax_error "foo 1,", "invalid trailing comma in call" assert_syntax_error "def foo:String\nend", "a space is mandatory between ':' and return type" assert_syntax_error "def foo :String\nend", "a space is mandatory between ':' and return type" assert_syntax_error "def foo():String\nend", "a space is mandatory between ':' and return type" diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index e65c7a981b3b..cbc001de99ef 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -4753,7 +4753,7 @@ module Crystal location = @token.location slash_is_regex! next_token_skip_space_or_newline - raise "invalid trailing comma in call" if (@token.keyword?(:end) && !next_comes_colon_space?) || @token.type.eof? + raise "invalid trailing comma in call", location if (@token.keyword?(:end) && !next_comes_colon_space?) || @token.type.eof? else break end From f9b7226c47820d3668b0c51ea60a460d31430c5c Mon Sep 17 00:00:00 2001 From: Michael Nikitochkin Date: Sat, 25 Nov 2023 20:31:08 +0100 Subject: [PATCH 0793/1551] Allow to specify git fork of distribution-scripts in CI (#13976) --- .circleci/config.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 840e1a974f21..957f3979b110 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,10 @@ version: 2.1 parameters: + distribution-scripts-repo: + description: "Git url https://github.com/crystal-lang/distribution-scripts/" + type: string + default: "https://github.com/crystal-lang/distribution-scripts.git" distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string @@ -142,7 +146,7 @@ jobs: steps: # checkout specific distribution-scripts version to perform releases and nightly - run: | - git clone https://github.com/crystal-lang/distribution-scripts.git ~/distribution-scripts + git clone << pipeline.parameters.distribution-scripts-repo >> ~/distribution-scripts cd ~/distribution-scripts git checkout << pipeline.parameters.distribution-scripts-version >> # persist relevant information for build process From c7202d4948c6785ce6dda11be495b3dd56074837 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 26 Nov 2023 03:31:27 +0800 Subject: [PATCH 0794/1551] Fix invalid UTF-8 handling in `Char::Reader#previous_char` (#14013) --- spec/std/char/reader_spec.cr | 136 +++++++++++++++++++++++++++++++++-- src/char/reader.cr | 91 +++++++++++++++++++++-- 2 files changed, 216 insertions(+), 11 deletions(-) diff --git a/spec/std/char/reader_spec.cr b/spec/std/char/reader_spec.cr index 9b214c4ea5a6..55409ddb0963 100644 --- a/spec/std/char/reader_spec.cr +++ b/spec/std/char/reader_spec.cr @@ -1,11 +1,31 @@ require "spec" require "char/reader" -private def assert_invalid_byte_sequence(bytes) +private def assert_invalid_byte_sequence(bytes, *, file = __FILE__, line = __LINE__) reader = Char::Reader.new(String.new bytes) - reader.current_char.should eq(Char::REPLACEMENT) - reader.current_char_width.should eq(1) - reader.error.should eq(bytes[0]) + reader.current_char.should eq(Char::REPLACEMENT), file: file, line: line + reader.current_char_width.should eq(1), file: file, line: line + reader.error.should eq(bytes[0]), file: file, line: line +end + +private def assert_reads_at_end(bytes, *, file = __FILE__, line = __LINE__) + str = String.new bytes + reader = Char::Reader.new(str, pos: bytes.size) + reader.previous_char + reader.current_char.should eq(str[0]), file: file, line: line + reader.current_char_width.should eq(bytes.size), file: file, line: line + reader.pos.should eq(0), file: file, line: line + reader.error.should be_nil, file: file, line: line +end + +private def assert_invalid_byte_sequence_at_end(bytes, *, file = __FILE__, line = __LINE__) + str = String.new bytes + reader = Char::Reader.new(str, pos: bytes.size) + reader.previous_char + reader.current_char.should eq(Char::REPLACEMENT), file: file, line: line + reader.current_char_width.should eq(1), file: file, line: line + reader.pos.should eq(bytes.size - 1), file: file, line: line + reader.error.should eq(bytes[-1]), file: file, line: line end describe "Char::Reader" do @@ -242,4 +262,112 @@ describe "Char::Reader" do it "errors if fourth_byte is out of bounds" do assert_invalid_byte_sequence Bytes[0xf4, 0x8f, 0xa0] end + + describe "#previous_char" do + it "reads on valid UTF-8" do + assert_reads_at_end Bytes[0x00] + assert_reads_at_end Bytes[0x7f] + + assert_reads_at_end Bytes[0xc2, 0x80] + assert_reads_at_end Bytes[0xc2, 0xbf] + assert_reads_at_end Bytes[0xdf, 0x80] + assert_reads_at_end Bytes[0xdf, 0xbf] + + assert_reads_at_end Bytes[0xe1, 0x80, 0x80] + assert_reads_at_end Bytes[0xe1, 0x80, 0xbf] + assert_reads_at_end Bytes[0xe1, 0x9f, 0x80] + assert_reads_at_end Bytes[0xe1, 0x9f, 0xbf] + assert_reads_at_end Bytes[0xed, 0x80, 0x80] + assert_reads_at_end Bytes[0xed, 0x80, 0xbf] + assert_reads_at_end Bytes[0xed, 0x9f, 0x80] + assert_reads_at_end Bytes[0xed, 0x9f, 0xbf] + assert_reads_at_end Bytes[0xef, 0x80, 0x80] + assert_reads_at_end Bytes[0xef, 0x80, 0xbf] + assert_reads_at_end Bytes[0xef, 0x9f, 0x80] + assert_reads_at_end Bytes[0xef, 0x9f, 0xbf] + + assert_reads_at_end Bytes[0xe0, 0xa0, 0x80] + assert_reads_at_end Bytes[0xe0, 0xa0, 0xbf] + assert_reads_at_end Bytes[0xe0, 0xbf, 0x80] + assert_reads_at_end Bytes[0xe0, 0xbf, 0xbf] + assert_reads_at_end Bytes[0xe1, 0xa0, 0x80] + assert_reads_at_end Bytes[0xe1, 0xa0, 0xbf] + assert_reads_at_end Bytes[0xe1, 0xbf, 0x80] + assert_reads_at_end Bytes[0xe1, 0xbf, 0xbf] + assert_reads_at_end Bytes[0xef, 0xa0, 0x80] + assert_reads_at_end Bytes[0xef, 0xa0, 0xbf] + assert_reads_at_end Bytes[0xef, 0xbf, 0x80] + assert_reads_at_end Bytes[0xef, 0xbf, 0xbf] + + assert_reads_at_end Bytes[0xf1, 0x80, 0x80, 0x80] + assert_reads_at_end Bytes[0xf1, 0x8f, 0x80, 0x80] + assert_reads_at_end Bytes[0xf4, 0x80, 0x80, 0x80] + assert_reads_at_end Bytes[0xf4, 0x8f, 0x80, 0x80] + + assert_reads_at_end Bytes[0xf0, 0x90, 0x80, 0x80] + assert_reads_at_end Bytes[0xf0, 0xbf, 0x80, 0x80] + assert_reads_at_end Bytes[0xf3, 0x90, 0x80, 0x80] + assert_reads_at_end Bytes[0xf3, 0xbf, 0x80, 0x80] + end + + it "errors on invalid UTF-8" do + assert_invalid_byte_sequence_at_end Bytes[0x80] + assert_invalid_byte_sequence_at_end Bytes[0xbf] + assert_invalid_byte_sequence_at_end Bytes[0xc0] + assert_invalid_byte_sequence_at_end Bytes[0xff] + + assert_invalid_byte_sequence_at_end Bytes[0x00, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0x7f, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0x9f, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xbf, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xc0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xc1, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xe0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xff, 0x80] + + assert_invalid_byte_sequence_at_end Bytes[0x00, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0x7f, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0x80, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0x8f, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0x90, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xbf, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xc0, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xc1, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xc2, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xdf, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xe0, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xe0, 0x9f, 0xbf] + assert_invalid_byte_sequence_at_end Bytes[0xf0, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xff, 0x80, 0x80] + + assert_invalid_byte_sequence_at_end Bytes[0x00, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0x7f, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0x80, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0x8f, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0x90, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xbf, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xc0, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xc1, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xc2, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xdf, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xed, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xed, 0xbf, 0xbf] + assert_invalid_byte_sequence_at_end Bytes[0xf0, 0xa0, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xff, 0xa0, 0x80] + + assert_invalid_byte_sequence_at_end Bytes[0x00, 0x80, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xef, 0x80, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xf0, 0x80, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xf5, 0x80, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xff, 0x80, 0x80, 0x80] + + assert_invalid_byte_sequence_at_end Bytes[0x00, 0x90, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xef, 0x90, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xf4, 0x90, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xf5, 0x90, 0x80, 0x80] + assert_invalid_byte_sequence_at_end Bytes[0xff, 0x90, 0x80, 0x80] + end + end end diff --git a/src/char/reader.cr b/src/char/reader.cr index 1624a35f4488..bde6de53b4c3 100644 --- a/src/char/reader.cr +++ b/src/char/reader.cr @@ -331,7 +331,7 @@ struct Char end private macro invalid_byte_sequence - return yield Char::REPLACEMENT.ord.to_u32!, 1, first.to_u8 + return yield Char::REPLACEMENT.ord.to_u32!, 1, first.to_u8! end @[AlwaysInline] @@ -343,15 +343,92 @@ struct Char end end - private def decode_previous_char - return if @pos == 0 + # The reverse UTF-8 DFA transition table for reference: (contrast with + # `Unicode::UTF8_ENCODING_DFA`) + # + # accepted (initial state) + # | 1 continuation byte + # | | 2 continuation bytes; disallow overlong encodings up to U+07FF + # | | | 2 continuation bytes; disallow surrogate pairs + # | | | | 3 continuation bytes; disallow overlong encodings up to U+FFFF + # | | | | | 3 continuation bytes; disallow codepoints above U+10FFFF + # v v v v v v + # + # | 0 2 3 4 5 6 + # -----------+------------ + # 0x00..0x7F | 0 _ _ _ _ _ + # 0x80..0x8F | 2 3 5 5 _ _ + # 0x90..0x9F | 2 3 6 6 _ _ + # 0xA0..0xBF | 2 4 6 6 _ _ + # 0xC2..0xDF | _ 0 _ _ _ _ + # 0xE0..0xE0 | _ _ _ 0 _ _ + # 0xE1..0xEC | _ _ 0 0 _ _ + # 0xED..0xED | _ _ 0 _ _ _ + # 0xEE..0xEF | _ _ 0 0 _ _ + # 0xF0..0xF0 | _ _ _ _ _ 0 + # 0xF1..0xF3 | _ _ _ _ 0 0 + # 0xF4..0xF4 | _ _ _ _ 0 _ + private def decode_char_before(pos, & : UInt32, Int32, UInt8? ->) + fourth = byte_at(pos - 1) + if fourth <= 0x7f + return yield fourth, 1, nil + end - while @pos > 0 - @pos -= 1 - break if (byte_at(@pos) & 0xC0) != 0x80 + if fourth > 0xbf || pos < 2 + invalid_byte_sequence_before end - decode_char_at(@pos) do |code_point, width, error| + + third = byte_at(pos - 2) + if 0xc2 <= third <= 0xdf + return yield (third << 6) &+ (fourth &- 0x3080), 2, nil + end + + if (third & 0xc0) != 0x80 || pos < 3 + invalid_byte_sequence_before + end + + second = byte_at(pos - 3) + if second & 0xf0 == 0xe0 + if second == 0xe0 && third <= 0x9f + invalid_byte_sequence_before + end + + if second == 0xed && third >= 0xa0 + invalid_byte_sequence_before + end + + return yield (second << 12) &+ (third << 6) &+ (fourth &- 0xE2080), 3, nil + end + + if (second & 0xc0) != 0x80 || pos < 4 + invalid_byte_sequence_before + end + + first = byte_at(pos - 4) + if second <= 0x8f + unless 0xf1 <= first <= 0xf4 + invalid_byte_sequence_before + end + else + unless 0xf0 <= first <= 0xf3 + invalid_byte_sequence_before + end + end + + return yield (first << 18) &+ (second << 12) &+ (third << 6) &+ (fourth &- 0x3C82080), 4, nil + end + + private macro invalid_byte_sequence_before + return yield Char::REPLACEMENT.ord.to_u32!, 1, fourth.to_u8! + end + + @[AlwaysInline] + private def decode_previous_char + return nil if @pos == 0 + + decode_char_before(@pos) do |code_point, width, error| @current_char_width = width + @pos -= width @error = error @current_char = code_point.unsafe_chr end From 35eb34008b09e3d8e2354444dc68db4125f40bb2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 26 Nov 2023 03:31:36 +0800 Subject: [PATCH 0795/1551] Refactor some uses of the blockless `String#split` (#14001) --- src/crystal/system/unix/env.cr | 8 +++++--- src/http/formdata.cr | 8 +++----- src/openssl/x509/name.cr | 4 ++-- src/process/executable_path.cr | 2 +- src/semantic_version.cr | 2 +- src/spec/context.cr | 2 +- src/spec/dsl.cr | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/crystal/system/unix/env.cr b/src/crystal/system/unix/env.cr index 4c5451e7b491..3ec71ff46d70 100644 --- a/src/crystal/system/unix/env.cr +++ b/src/crystal/system/unix/env.cr @@ -42,9 +42,11 @@ module Crystal::System::Env while environ_ptr environ_value = environ_ptr.value if environ_value - key_value = String.new(environ_value).split('=', 2) - key = key_value[0] - value = key_value[1]? || "" + # this does `String.new(environ_value).partition('=')` without an intermediary string + key_value = Slice.new(environ_value, LibC.strlen(environ_value)) + split_index = key_value.index!(0x3d_u8) # '=' + key = String.new(key_value[0, split_index]) + value = String.new(key_value[split_index + 1..]) yield key, value environ_ptr += 1 else diff --git a/src/http/formdata.cr b/src/http/formdata.cr index 267442687864..7bb32b7519f0 100644 --- a/src/http/formdata.cr +++ b/src/http/formdata.cr @@ -139,12 +139,10 @@ module HTTP::FormData name = nil parts = content_disposition.split(';') - type = parts[0] + type = parts.shift? raise Error.new("Invalid Content-Disposition: not form-data") unless type == "form-data" - (1...parts.size).each do |i| - part = parts[i] - - key, value = part.split('=', 2) + parts.each do |part| + key, _, value = part.partition('=') key = key.strip value = value.strip if value[0] == '"' diff --git a/src/openssl/x509/name.cr b/src/openssl/x509/name.cr index 92c9c8a8a23f..dacb04d67c2c 100644 --- a/src/openssl/x509/name.cr +++ b/src/openssl/x509/name.cr @@ -14,8 +14,8 @@ module OpenSSL::X509 # ``` def self.parse(string : String) : Name new.tap do |name| - string.split('/').each do |entry| - oid, value = entry.split('=') + string.split('/') do |entry| + oid, _, value = entry.partition('=') name.add_entry(oid, value) end end diff --git a/src/process/executable_path.cr b/src/process/executable_path.cr index 20c78b77e232..bdd5776f305a 100644 --- a/src/process/executable_path.cr +++ b/src/process/executable_path.cr @@ -95,7 +95,7 @@ class Process end if path && !has_separator - path.split(PATH_DELIMITER).each do |path_entry| + path.split(PATH_DELIMITER) do |path_entry| yield Path.new(path_entry, name) end end diff --git a/src/semantic_version.cr b/src/semantic_version.cr index 308f914c63af..476360b32a0e 100644 --- a/src/semantic_version.cr +++ b/src/semantic_version.cr @@ -178,7 +178,7 @@ struct SemanticVersion # ``` def self.parse(str : String) : self identifiers = [] of String | Int32 - str.split('.').each do |val| + str.split('.') do |val| if number = val.to_i32? identifiers << number else diff --git a/src/spec/context.cr b/src/spec/context.cr index b612c095cbbc..0a1109c12b92 100644 --- a/src/spec/context.cr +++ b/src/spec/context.cr @@ -210,7 +210,7 @@ module Spec puts message = ex.is_a?(SpecError) ? ex.to_s : ex.inspect_with_backtrace - message.split('\n').each do |line| + message.split('\n') do |line| print " " puts Spec.color(line, :error) end diff --git a/src/spec/dsl.cr b/src/spec/dsl.cr index 90803fdee39c..d29fc7cf8b61 100644 --- a/src/spec/dsl.cr +++ b/src/spec/dsl.cr @@ -96,8 +96,8 @@ module Spec def self.add_split_filter(filter) if filter - r, m = filter.split('%').map &.to_i - @@split_filter = SplitFilter.new(remainder: r, quotient: m) + r, _, m = filter.partition('%') + @@split_filter = SplitFilter.new(remainder: r.to_i, quotient: m.to_i) else @@split_filter = nil end From a59b5ab4275d9996dec8567db41c2f53c265e4f0 Mon Sep 17 00:00:00 2001 From: Evan Paterakis Date: Tue, 28 Nov 2023 01:55:24 +0200 Subject: [PATCH 0796/1551] Fix `Globber.constant_entry?` matching patterns (#13955) Co-authored-by: Quinton Miller --- spec/std/dir_spec.cr | 113 +++++++++++++++++-------- src/compiler/crystal/command/format.cr | 2 +- src/compiler/crystal/command/spec.cr | 1 + src/dir/glob.cr | 2 +- 4 files changed, 80 insertions(+), 38 deletions(-) diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index 6f709f53c26c..f0721ef66abb 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -193,7 +193,7 @@ describe "Dir" do describe "glob" do it "tests glob with a single pattern" do - Dir["#{datapath}/dir/*.txt"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/*.txt"].sort.should eq [ datapath("dir", "f1.txt"), datapath("dir", "f2.txt"), datapath("dir", "g2.txt"), @@ -201,7 +201,7 @@ describe "Dir" do end it "tests glob with multiple patterns" do - Dir["#{datapath}/dir/*.txt", "#{datapath}/dir/subdir/*.txt"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/*.txt", "#{Path[datapath].to_posix}/dir/subdir/*.txt"].sort.should eq [ datapath("dir", "f1.txt"), datapath("dir", "f2.txt"), datapath("dir", "g2.txt"), @@ -211,7 +211,7 @@ describe "Dir" do it "tests glob with a single pattern with block" do result = [] of String - Dir.glob("#{datapath}/dir/*.txt") do |filename| + Dir.glob("#{Path[datapath].to_posix}/dir/*.txt") do |filename| result << filename end result.sort.should eq([ @@ -222,7 +222,7 @@ describe "Dir" do end it "tests a recursive glob" do - Dir["#{datapath}/dir/**/*.txt"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/**/*.txt"].sort.should eq [ datapath("dir", "f1.txt"), datapath("dir", "f2.txt"), datapath("dir", "g2.txt"), @@ -230,11 +230,11 @@ describe "Dir" do datapath("dir", "subdir", "subdir2", "f2.txt"), ].sort - Dir["#{datapath}/dir/**/subdir2/f2.txt"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/**/subdir2/f2.txt"].sort.should eq [ datapath("dir", "subdir", "subdir2", "f2.txt"), ].sort - Dir["#{datapath}/dir/**/subdir2/*.txt"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/**/subdir2/*.txt"].sort.should eq [ datapath("dir", "subdir", "subdir2", "f2.txt"), ].sort end @@ -274,7 +274,7 @@ describe "Dir" do end it "tests a recursive glob with '?'" do - Dir["#{datapath}/dir/f?.tx?"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/f?.tx?"].sort.should eq [ datapath("dir", "f1.txt"), datapath("dir", "f2.txt"), datapath("dir", "f3.txx"), @@ -282,7 +282,7 @@ describe "Dir" do end it "tests a recursive glob with alternation" do - Dir["#{datapath}/{dir,dir/subdir}/*.txt"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/{dir,dir/subdir}/*.txt"].sort.should eq [ datapath("dir", "f1.txt"), datapath("dir", "f2.txt"), datapath("dir", "g2.txt"), @@ -291,7 +291,7 @@ describe "Dir" do end it "tests a glob with recursion inside alternation" do - Dir["#{datapath}/dir/{**/*.txt,**/*.txx}"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/{**/*.txt,**/*.txx}"].sort.should eq [ datapath("dir", "f1.txt"), datapath("dir", "f2.txt"), datapath("dir", "f3.txx"), @@ -302,15 +302,56 @@ describe "Dir" do end it "tests a recursive glob with nested alternations" do - Dir["#{datapath}/dir/{?1.*,{f,g}2.txt}"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/{?1.*,{f,g}2.txt}"].sort.should eq [ datapath("dir", "f1.txt"), datapath("dir", "f2.txt"), datapath("dir", "g2.txt"), ].sort end + it "tests with []" do + Dir["#{Path[datapath].to_posix}/dir/[a-z][0-9].txt"].sort.should eq [ + datapath("dir", "f1.txt"), + datapath("dir", "f2.txt"), + datapath("dir", "g2.txt"), + ].sort + end + + it "tests with {}" do + Dir["#{Path[datapath].to_posix}/dir/{f,g}{1,2,3}.tx{t,x}"].sort.should eq [ + datapath("dir", "f1.txt"), + datapath("dir", "f2.txt"), + datapath("dir", "f3.txx"), + datapath("dir", "g2.txt"), + ].sort + end + + {% if flag?(:windows) %} + pending "tests with \\" + {% else %} + it "tests with \\" do + with_tempfile "glob-escape-pattern" do |path| + Dir.mkdir_p path + Dir.cd(path) do + File.touch "g1.txt" + File.touch %q(\g3) + File.touch %q(\g4*) + + Dir[%q(\\g*)].sort.should eq [ + "\\g3", + "\\g4*", + ].sort + + Dir[%q(*g?\*)].sort.should eq [ + "\\g4*", + ].sort + end + end + end + {% end %} + it "tests with *" do - Dir["#{datapath}/dir/*"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/*"].sort.should eq [ datapath("dir", "dots"), datapath("dir", "f1.txt"), datapath("dir", "f2.txt"), @@ -322,7 +363,7 @@ describe "Dir" do end it "tests with ** (same as *)" do - Dir["#{datapath}/dir/**"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/**"].sort.should eq [ datapath("dir", "dots"), datapath("dir", "f1.txt"), datapath("dir", "f2.txt"), @@ -334,7 +375,7 @@ describe "Dir" do end it "tests with */" do - Dir["#{datapath}/dir/*/"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/*/"].sort.should eq [ datapath("dir", "dots", ""), datapath("dir", "subdir", ""), datapath("dir", "subdir2", ""), @@ -350,7 +391,7 @@ describe "Dir" do end it "tests with relative path" do - Dir["#{datapath}/dir/*/"].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/*/"].sort.should eq [ datapath("dir", "dots", ""), datapath("dir", "subdir", ""), datapath("dir", "subdir2", ""), @@ -358,7 +399,7 @@ describe "Dir" do end it "tests with relative path (starts with .)" do - Dir["./#{datapath}/dir/*/"].sort.should eq [ + Dir["./#{Path[datapath].to_posix}/dir/*/"].sort.should eq [ File.join(".", "spec", "std", "data", "dir", "dots", ""), File.join(".", "spec", "std", "data", "dir", "subdir", ""), File.join(".", "spec", "std", "data", "dir", "subdir2", ""), @@ -368,7 +409,7 @@ describe "Dir" do it "tests with relative path (starts with ..)" do Dir.cd(datapath) do base_path = Path["..", "data", "dir"] - Dir["#{base_path}/*/"].sort.should eq [ + Dir["#{base_path.to_posix}/*/"].sort.should eq [ base_path.join("dots", "").to_s, base_path.join("subdir", "").to_s, base_path.join("subdir2", "").to_s, @@ -394,11 +435,11 @@ describe "Dir" do File.symlink(datapath("dir", "f1.txt"), link) File.symlink(datapath("dir", "nonexisting"), non_link) - Dir["#{path}/*_link.txt"].sort.should eq [ + Dir["#{Path[path].to_posix}/*_link.txt"].sort.should eq [ link.to_s, non_link.to_s, ].sort - Dir["#{path}/non_link.txt"].should eq [non_link.to_s] + Dir["#{Path[path].to_posix}/non_link.txt"].should eq [non_link.to_s] end end @@ -414,8 +455,8 @@ describe "Dir" do File.write(non_link, "") File.symlink(target, link_dir) - Dir.glob("#{path}/glob/*/a.txt").sort.should eq [] of String - Dir.glob("#{path}/glob/*/a.txt", follow_symlinks: true).sort.should eq [ + Dir.glob("#{Path[path].to_posix}/glob/*/a.txt").sort.should eq [] of String + Dir.glob("#{Path[path].to_posix}/glob/*/a.txt", follow_symlinks: true).sort.should eq [ File.join(path, "glob", "dir", "a.txt"), ] end @@ -435,13 +476,13 @@ describe "Dir" do end it "pattern ending with .." do - Dir["#{datapath}/dir/.."].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/.."].sort.should eq [ datapath("dir", ".."), ].sort end it "pattern ending with */.." do - Dir["#{datapath}/dir/*/.."].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/*/.."].sort.should eq [ datapath("dir", "dots", ".."), datapath("dir", "subdir", ".."), datapath("dir", "subdir2", ".."), @@ -449,13 +490,13 @@ describe "Dir" do end it "pattern ending with ." do - Dir["#{datapath}/dir/."].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/."].sort.should eq [ datapath("dir", "."), ].sort end it "pattern ending with */." do - Dir["#{datapath}/dir/*/."].sort.should eq [ + Dir["#{Path[datapath].to_posix}/dir/*/."].sort.should eq [ datapath("dir", "dots", "."), datapath("dir", "subdir", "."), datapath("dir", "subdir2", "."), @@ -464,12 +505,12 @@ describe "Dir" do context "match: :dot_files / match_hidden" do it "matches dot files" do - Dir.glob("#{datapath}/dir/dots/**/*", match: :dot_files).sort.should eq [ + Dir.glob("#{Path[datapath].to_posix}/dir/dots/**/*", match: :dot_files).sort.should eq [ datapath("dir", "dots", ".dot.hidden"), datapath("dir", "dots", ".hidden"), datapath("dir", "dots", ".hidden", "f1.txt"), ].sort - Dir.glob("#{datapath}/dir/dots/**/*", match_hidden: true).sort.should eq [ + Dir.glob("#{Path[datapath].to_posix}/dir/dots/**/*", match_hidden: true).sort.should eq [ datapath("dir", "dots", ".dot.hidden"), datapath("dir", "dots", ".hidden"), datapath("dir", "dots", ".hidden", "f1.txt"), @@ -477,13 +518,13 @@ describe "Dir" do end it "ignores hidden files" do - Dir.glob("#{datapath}/dir/dots/*", match: :none).should be_empty - Dir.glob("#{datapath}/dir/dots/*", match_hidden: false).should be_empty + Dir.glob("#{Path[datapath].to_posix}/dir/dots/*", match: :none).should be_empty + Dir.glob("#{Path[datapath].to_posix}/dir/dots/*", match_hidden: false).should be_empty end it "ignores hidden files recursively" do - Dir.glob("#{datapath}/dir/dots/**/*", match: :none).should be_empty - Dir.glob("#{datapath}/dir/dots/**/*", match_hidden: false).should be_empty + Dir.glob("#{Path[datapath].to_posix}/dir/dots/**/*", match: :none).should be_empty + Dir.glob("#{Path[datapath].to_posix}/dir/dots/**/*", match_hidden: false).should be_empty end end @@ -534,10 +575,10 @@ describe "Dir" do expected_hidden = (expected + [hidden_txt, hidden_dir, inside_hidden]).sort! expected_system_hidden = (expected_hidden + [system_hidden_txt, system_hidden_dir, inside_system_hidden]).sort! - Dir.glob("#{path}/**/*", match: :none).sort.should eq(expected) - Dir.glob("#{path}/**/*", match: :native_hidden).sort.should eq(expected_hidden) - Dir.glob("#{path}/**/*", match: :os_hidden).sort.should eq(expected) - Dir.glob("#{path}/**/*", match: File::MatchOptions[NativeHidden, OSHidden]).sort.should eq(expected_system_hidden) + Dir.glob("#{Path[path].to_posix}/**/*", match: :none).sort.should eq(expected) + Dir.glob("#{Path[path].to_posix}/**/*", match: :native_hidden).sort.should eq(expected_hidden) + Dir.glob("#{Path[path].to_posix}/**/*", match: :os_hidden).sort.should eq(expected) + Dir.glob("#{Path[path].to_posix}/**/*", match: File::MatchOptions[NativeHidden, OSHidden]).sort.should eq(expected_system_hidden) end end {% end %} @@ -550,8 +591,8 @@ describe "Dir" do ] it "posix path" do - Dir[Path.posix(datapath, "dir", "*.txt")].sort.should eq expected - Dir[[Path.posix(datapath, "dir", "*.txt")]].sort.should eq expected + Dir[Path.posix(Path[datapath].to_posix, "dir", "*.txt")].sort.should eq expected + Dir[[Path.posix(Path[datapath].to_posix, "dir", "*.txt")]].sort.should eq expected end it "windows path" do diff --git a/src/compiler/crystal/command/format.cr b/src/compiler/crystal/command/format.cr index 41dbe0157dab..ed63a26796f9 100644 --- a/src/compiler/crystal/command/format.cr +++ b/src/compiler/crystal/command/format.cr @@ -120,7 +120,7 @@ class Crystal::Command format_file filename end elsif Dir.exists?(filename) - filename = filename.chomp('/') + filename = ::Path[filename.chomp('/')].to_posix filenames = Dir["#{filename}/**/*.cr"] format_many filenames else diff --git a/src/compiler/crystal/command/spec.cr b/src/compiler/crystal/command/spec.cr index 3d4663e1cfe0..ff8f47b11859 100644 --- a/src/compiler/crystal/command/spec.cr +++ b/src/compiler/crystal/command/spec.cr @@ -60,6 +60,7 @@ class Crystal::Command locations << {file, line} else if Dir.exists?(filename) + filename = ::Path[filename].to_posix target_filenames.concat Dir["#{filename}/**/*_spec.cr"] elsif File.file?(filename) target_filenames << filename diff --git a/src/dir/glob.cr b/src/dir/glob.cr index 25ec6bcd7683..cd45f0a03baf 100644 --- a/src/dir/glob.cr +++ b/src/dir/glob.cr @@ -223,7 +223,7 @@ class Dir private def self.constant_entry?(file) file.each_char do |char| - return false if char.in?('*', '?') + return false if char.in?('*', '?', '[', '\\') end true From 9082692c0156b8924c97b568de8c8b3fb010a58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 28 Nov 2023 18:26:25 +0100 Subject: [PATCH 0797/1551] Add `IO::Error#target` (#13865) --- spec/std/file_spec.cr | 4 ++-- src/crystal/system/unix/file.cr | 6 +++--- src/crystal/system/unix/file_descriptor.cr | 10 +++++----- src/crystal/system/win32/file.cr | 4 ++-- src/crystal/system/win32/file_descriptor.cr | 18 +++++++++--------- src/file/error.cr | 14 ++++++++------ src/io/error.cr | 20 ++++++++++++++++++++ src/io/evented.cr | 4 ++-- src/io/overlapped.cr | 6 +++--- 9 files changed, 54 insertions(+), 32 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 53d28bdcee4e..a88d0e65958f 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -941,7 +941,7 @@ describe "File" do pending! "Spec cannot run as superuser" end {% end %} - expect_raises(File::AccessDeniedError) { File.read(path) } + expect_raises(File::AccessDeniedError, path) { File.read(path) } end end {% end %} @@ -958,7 +958,7 @@ describe "File" do pending! "Spec cannot run as superuser" end {% end %} - expect_raises(File::AccessDeniedError) { File.write(path, "foo") } + expect_raises(File::AccessDeniedError, path) { File.write(path, "foo") } end end diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index 665a61b22493..ca04693d0b1a 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -239,7 +239,7 @@ module Crystal::System::File sleep 0.1 end else - flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked") + flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked", target: self) end end @@ -251,7 +251,7 @@ module Crystal::System::File if errno.in?(Errno::EAGAIN, Errno::EWOULDBLOCK) false else - raise IO::Error.from_os_error("Error applying or removing file lock", errno) + raise IO::Error.from_os_error("Error applying or removing file lock", errno, target: self) end end end @@ -269,7 +269,7 @@ module Crystal::System::File end if ret != 0 - raise IO::Error.from_errno("Error syncing file") + raise IO::Error.from_errno("Error syncing file", target: self) end end end diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index d77708f314bb..d3995c205c3e 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -15,7 +15,7 @@ module Crystal::System::FileDescriptor evented_read(slice, "Error reading file") do LibC.read(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF - raise IO::Error.new "File not open for reading" + raise IO::Error.new "File not open for reading", target: self end end end @@ -25,7 +25,7 @@ module Crystal::System::FileDescriptor evented_write(slice, "Error writing file") do |slice| LibC.write(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF - raise IO::Error.new "File not open for writing" + raise IO::Error.new "File not open for writing", target: self end end end @@ -90,13 +90,13 @@ module Crystal::System::FileDescriptor seek_value = LibC.lseek(fd, offset, whence) if seek_value == -1 - raise IO::Error.from_errno "Unable to seek" + raise IO::Error.from_errno "Unable to seek", target: self end end private def system_pos pos = LibC.lseek(fd, 0, IO::Seek::Current).to_i64 - raise IO::Error.from_errno "Unable to tell" if pos == -1 + raise IO::Error.from_errno("Unable to tell", target: self) if pos == -1 pos end @@ -147,7 +147,7 @@ module Crystal::System::FileDescriptor when Errno::EINTR, Errno::EINPROGRESS # ignore else - raise IO::Error.from_errno("Error closing file") + raise IO::Error.from_errno("Error closing file", target: self) end end end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 4fceee02c6d4..43b2dd8142ec 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -493,7 +493,7 @@ module Crystal::System::File if winerror == WinError::ERROR_LOCK_VIOLATION false else - raise IO::Error.from_os_error("LockFileEx", winerror) + raise IO::Error.from_os_error("LockFileEx", winerror, target: self) end end end @@ -509,7 +509,7 @@ module Crystal::System::File private def system_fsync(flush_metadata = true) : Nil if LibC._commit(fd) != 0 - raise IO::Error.from_errno("Error syncing file") + raise IO::Error.from_errno("Error syncing file", target: self) end end end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 84cfe12c6650..6e4f42d46b17 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -15,9 +15,9 @@ module Crystal::System::FileDescriptor bytes_read = LibC._read(fd, slice, slice.size) if bytes_read == -1 if Errno.value == Errno::EBADF - raise IO::Error.new "File not open for reading" + raise IO::Error.new "File not open for reading", target: self else - raise IO::Error.from_errno("Error reading file") + raise IO::Error.from_errno("Error reading file", target: self) end end bytes_read @@ -36,9 +36,9 @@ module Crystal::System::FileDescriptor bytes_written = LibC._write(fd, slice, slice.size) if bytes_written == -1 if Errno.value == Errno::EBADF - raise IO::Error.new "File not open for writing" + raise IO::Error.new "File not open for writing", target: self else - raise IO::Error.from_errno("Error writing file") + raise IO::Error.from_errno("Error writing file", target: self) end end else @@ -106,7 +106,7 @@ module Crystal::System::FileDescriptor if file_type == LibC::FILE_TYPE_UNKNOWN error = WinError.value - raise IO::Error.from_os_error("Unable to get info", error) unless error == WinError::ERROR_SUCCESS + raise IO::Error.from_os_error("Unable to get info", error, target: self) unless error == WinError::ERROR_SUCCESS end end @@ -129,13 +129,13 @@ module Crystal::System::FileDescriptor seek_value = LibC._lseeki64(fd, offset, whence) if seek_value == -1 - raise IO::Error.from_errno "Unable to seek" + raise IO::Error.from_errno "Unable to seek", target: self end end private def system_pos pos = LibC._lseeki64(fd, 0, IO::Seek::Current) - raise IO::Error.from_errno "Unable to tell" if pos == -1 + raise IO::Error.from_errno("Unable to tell", target: self) if pos == -1 pos end @@ -165,7 +165,7 @@ module Crystal::System::FileDescriptor when Errno::EINTR # ignore else - raise IO::Error.from_errno("Error closing file") + raise IO::Error.from_errno("Error closing file", target: self) end end end @@ -204,7 +204,7 @@ module Crystal::System::FileDescriptor if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0 error = WinError.value return 0_i64 if error == WinError::ERROR_HANDLE_EOF - raise IO::Error.from_os_error "Error reading file", error + raise IO::Error.from_os_error "Error reading file", error, target: self end bytes_read.to_i64 diff --git a/src/file/error.cr b/src/file/error.cr index 79a0ec5978dc..355496488bc4 100644 --- a/src/file/error.cr +++ b/src/file/error.cr @@ -2,9 +2,12 @@ class File < IO::FileDescriptor end class File::Error < IO::Error - getter file : String getter other : String? + def file : String + target.not_nil! + end + private def self.new_from_os_error(message, os_error, **opts) case os_error when Errno::ENOENT, WinError::ERROR_FILE_NOT_FOUND, WinError::ERROR_PATH_NOT_FOUND @@ -20,6 +23,10 @@ class File::Error < IO::Error end end + def initialize(message, *, file : String | Path, @other : String? = nil) + super message, target: file + end + protected def self.build_message(message, *, file : String) : String "#{message}: '#{file.inspect_unquoted}'" end @@ -38,11 +45,6 @@ class File::Error < IO::Error end end {% end %} - - def initialize(message, *, file : String | Path, @other : String? = nil) - @file = file.to_s - super message - end end class File::NotFoundError < File::Error diff --git a/src/io/error.cr b/src/io/error.cr index b611dc523818..4c6d30952f13 100644 --- a/src/io/error.cr +++ b/src/io/error.cr @@ -1,6 +1,26 @@ class IO class Error < Exception include SystemError + + getter target : String? + + protected def self.build_message(message, *, target : File) : String + build_message(message, target: target.path) + end + + protected def self.build_message(message, *, target : Nil) : String + message + end + + protected def self.build_message(message, *, target) : String + "#{message} (#{target})" + end + + def initialize(message : String? = nil, *, target = nil) + @target = target.try(&.to_s) + + super message + end end # Raised when an `IO` operation times out. diff --git a/src/io/evented.cr b/src/io/evented.cr index c490f3b5939f..191f136466a1 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -58,7 +58,7 @@ module IO::Evented if Errno.value == Errno::EAGAIN wait_readable else - raise IO::Error.from_errno(errno_msg) + raise IO::Error.from_errno(errno_msg, target: self) end end ensure @@ -79,7 +79,7 @@ module IO::Evented if Errno.value == Errno::EAGAIN wait_writable else - raise IO::Error.from_errno(errno_msg) + raise IO::Error.from_errno(errno_msg, target: self) end end end diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index d4b9f5f0cb6f..d7ed8aa516ec 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -211,9 +211,9 @@ module IO::Overlapped when .error_io_pending? # the operation is running asynchronously; do nothing when .error_access_denied? - raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}" + raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}", target: self else - raise IO::Error.from_os_error(method, error) + raise IO::Error.from_os_error(method, error, target: self) end else operation.synchronous = true @@ -245,7 +245,7 @@ module IO::Overlapped when .wsa_io_pending? # the operation is running asynchronously; do nothing else - raise IO::Error.from_os_error(method, error) + raise IO::Error.from_os_error(method, error, target: self) end else operation.synchronous = true From ff1071c11d533b11bab8b9655fcac6f5142dc02f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 2 Dec 2023 01:03:02 +0800 Subject: [PATCH 0798/1551] Fix `File::AccessDeniedError` expectations in `File` specs (#14029) --- spec/std/file_spec.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index a88d0e65958f..3c020fee36a1 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -941,7 +941,7 @@ describe "File" do pending! "Spec cannot run as superuser" end {% end %} - expect_raises(File::AccessDeniedError, path) { File.read(path) } + expect_raises(File::AccessDeniedError, "Error opening file with mode 'r': '#{path.inspect_unquoted}'") { File.read(path) } end end {% end %} @@ -958,7 +958,7 @@ describe "File" do pending! "Spec cannot run as superuser" end {% end %} - expect_raises(File::AccessDeniedError, path) { File.write(path, "foo") } + expect_raises(File::AccessDeniedError, "Error opening file with mode 'w': '#{path.inspect_unquoted}'") { File.write(path, "foo") } end end From 123de814fdbaa1256044a798b0272c9af19e7cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 2 Dec 2023 11:53:11 +0100 Subject: [PATCH 0799/1551] Add `String#matches_full?` (#13968) --- spec/std/string_spec.cr | 27 ++++++++++++++++++++++++++ src/string.cr | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 8a372a370677..178fa81a172c 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2343,6 +2343,33 @@ describe "String" do "foo".matches?(/bar/).should eq(false) end + it "#matches_full?" do + pending! if {{ Regex::Engine.resolve.name == "Regex::PCRE" }} + "foo".matches_full?(/foo/).should be_true + "fooo".matches_full?(/foo/).should be_false + "ofoo".matches_full?(/foo/).should be_false + "pattern".matches_full?(/(\A)?pattern(\z)?/).should be_true + "_pattern_".matches_full?(/(\A)?pattern(\z)?/).should be_false + end + + it "#match_full" do + pending! if {{ Regex::Engine.resolve.name == "Regex::PCRE" }} + "foo".match_full(/foo/).not_nil![0].should eq "foo" + "fooo".match_full(/foo/).should be_nil + "ofoo".match_full(/foo/).should be_nil + "pattern".match_full(/(\A)?pattern(\z)?/).not_nil![0].should eq "pattern" + "_pattern_".match_full(/(\A)?pattern(\z)?/).should be_nil + end + + it "#match_full!" do + pending! if {{ Regex::Engine.resolve.name == "Regex::PCRE" }} + "foo".match_full!(/foo/).not_nil![0].should eq "foo" + expect_raises(Regex::Error) { "fooo".match_full!(/foo/) } + expect_raises(Regex::Error) { "ofoo".match_full!(/foo/) } + "pattern".match_full!(/(\A)?pattern(\z)?/).not_nil![0].should eq "pattern" + expect_raises(Regex::Error) { "_pattern_".match_full!(/(\A)?pattern(\z)?/) } + end + it "has size (same as size)" do "テスト".size.should eq(3) end diff --git a/src/string.cr b/src/string.cr index 3c378bd1d455..05c2665f2e4e 100644 --- a/src/string.cr +++ b/src/string.cr @@ -4687,6 +4687,48 @@ class String regex.matches? self, pos, options: options end + # Matches the regular expression *regex* against the entire string and returns + # the resulting `MatchData`. + # It also updates `$~` with the result. + # + # ``` + # "foo".match_full(/foo/) # => Regex::MatchData("foo") + # $~ # => Regex::MatchData("foo") + # "fooo".match_full(/foo/) # => nil + # $~ # raises Exception + # ``` + def match_full(regex : Regex) : Regex::MatchData? + match(regex, options: Regex::MatchOptions::ANCHORED | Regex::MatchOptions::ENDANCHORED) + end + + # Matches the regular expression *regex* against the entire string and returns + # the resulting `MatchData`. + # It also updates `$~` with the result. + # Raises `Regex::Error` if there are no matches. + # + # ``` + # "foo".match_full!(/foo/) # => Regex::MatchData("foo") + # $~ # => Regex::MatchData("foo") + # "fooo".match_full!(/foo/) # Regex::Error + # $~ # raises Exception + # ``` + def match_full!(regex : Regex) : Regex::MatchData? + match!(regex, options: Regex::MatchOptions::ANCHORED | Regex::MatchOptions::ENDANCHORED) + end + + # Returns `true` if the regular expression *regex* matches this string entirely. + # + # ``` + # "foo".matches_full?(/foo/) # => true + # "fooo".matches_full?(/foo/) # => false + # + # # `$~` is not set even if last match succeeds. + # $~ # raises Exception + # ``` + def matches_full?(regex : Regex) : Bool + matches?(regex, options: Regex::MatchOptions::ANCHORED | Regex::MatchOptions::ENDANCHORED) + end + # Searches the string for instances of *pattern*, # yielding a `Regex::MatchData` for each match. def scan(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None, &) : self From 6ac6402ef35a0f91c73a6af5baaaa0a9454ebc80 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Sun, 3 Dec 2023 06:22:34 -0500 Subject: [PATCH 0800/1551] Order macros below class methods in generated docs (#14024) --- src/compiler/crystal/tools/doc/html/type.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/tools/doc/html/type.html b/src/compiler/crystal/tools/doc/html/type.html index cd8115e74f5c..10c7e51fedd3 100644 --- a/src/compiler/crystal/tools/doc/html/type.html +++ b/src/compiler/crystal/tools/doc/html/type.html @@ -95,8 +95,8 @@

    <%= MethodSummaryTemplate.new("Constructors", type.constructors) %> <%= MethodSummaryTemplate.new(type.program? ? "Method Summary" : "Class Method Summary", type.class_methods) %> -<%= MethodSummaryTemplate.new("Instance Method Summary", type.instance_methods) %> <%= MethodSummaryTemplate.new("Macro Summary", type.macros) %> +<%= MethodSummaryTemplate.new("Instance Method Summary", type.instance_methods) %>
    <% type.ancestors.each do |ancestor| %> @@ -109,8 +109,8 @@

    <%= MethodDetailTemplate.new("Constructor Detail", type.constructors) %> <%= MethodDetailTemplate.new(type.program? ? "Method Detail" : "Class Method Detail", type.class_methods) %> -<%= MethodDetailTemplate.new("Instance Method Detail", type.instance_methods) %> <%= MethodDetailTemplate.new("Macro Detail", type.macros) %> +<%= MethodDetailTemplate.new("Instance Method Detail", type.instance_methods) %>

    From 98fa0691da7d29b4308b505f8ea6362ef11d67d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 3 Dec 2023 20:45:49 +0100 Subject: [PATCH 0801/1551] Extract `generate_data` to separate Makefile (#14015) --- Makefile | 28 +++------------------------- Makefile.win | 28 +++------------------------- scripts/generate_data.mk | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 50 deletions(-) create mode 100644 scripts/generate_data.mk diff --git a/Makefile b/Makefile index 03a1aafd34ff..7e8070d16921 100644 --- a/Makefile +++ b/Makefile @@ -139,31 +139,9 @@ llvm_ext: $(LLVM_EXT_OBJ) format: ## Format sources ./bin/crystal tool format$(if $(check), --check) src spec samples scripts -generate_data: ## Run generator scripts for Unicode, SSL config, ... (usually with `-B`/`--always-make` flag) - -generate_data: spec/std/string/graphemes_break_spec.cr -spec/std/string/graphemes_break_spec.cr: scripts/generate_grapheme_break_specs.cr - $(CRYSTAL) run $< - -generate_data: src/string/grapheme/properties.cr -src/string/grapheme/properties.cr: scripts/generate_grapheme_properties.cr - $(CRYSTAL) run $< - -generate_data: src/openssl/ssl/defaults.cr -src/openssl/ssl/defaults.cr: scripts/generate_ssl_server_defaults.cr - $(CRYSTAL) run $< - -generate_data: src/unicode/data.cr -src/unicode/data.cr: scripts/generate_unicode_data.cr - $(CRYSTAL) run $< - -generate_data: src/crystal/system/win32/zone_names.cr -src/crystal/system/win32/zone_names.cr: scripts/generate_windows_zone_names.cr - $(CRYSTAL) run $< - -generate_data: src/html/entities.cr -src/html/entities.cr: scripts/generate_html_entities.cr scripts/html_entities.ecr - $(CRYSTAL) run $< +.PHONY: generate_data +generate_data: ## Run generator scripts for Unicode, SSL config, ... + $(MAKE) -B -f scripts/generate_data.mk .PHONY: install install: $(O)/crystal man/crystal.1.gz ## Install the compiler at DESTDIR diff --git a/Makefile.win b/Makefile.win index 595a26f5fd00..2c21717f6058 100644 --- a/Makefile.win +++ b/Makefile.win @@ -141,31 +141,9 @@ llvm_ext: $(LLVM_EXT_OBJ) format: ## Format sources .\bin\crystal tool format$(if $(check), --check) src spec samples scripts -generate_data: ## Run generator scripts for Unicode, SSL config, ... (usually with `-B`/`--always-make` flag) - -generate_data: spec\std\string\graphemes_break_spec.cr -spec\std\string\graphemes_break_spec.cr: scripts\generate_grapheme_break_specs.cr - $(CRYSTAL) run $< - -generate_data: src\string\grapheme\properties.cr -src\string\grapheme\properties.cr: scripts\generate_grapheme_properties.cr - $(CRYSTAL) run $< - -generate_data: src\openssl\ssl\defaults.cr -src\openssl\ssl\defaults.cr: scripts\generate_ssl_server_defaults.cr - $(CRYSTAL) run $< - -generate_data: src\unicode\data.cr -src\unicode\data.cr: scripts\generate_unicode_data.cr - $(CRYSTAL) run $< - -generate_data: src\crystal\system\win32\zone_names.cr -src\crystal\system\win32\zone_names.cr: scripts\generate_windows_zone_names.cr - $(CRYSTAL) run $< - -generate_data: src\html\entities.cr -src\html\entities.cr: scripts\generate_html_entities.cr scripts\html_entities.ecr - $(CRYSTAL) run $< +.PHONY: generate_data +generate_data: ## Run generator scripts for Unicode, SSL config, ... + $(MAKE) -B -f scripts/generate_data.mk .PHONY: install install: $(O)\crystal.exe ## Install the compiler at prefix diff --git a/scripts/generate_data.mk b/scripts/generate_data.mk new file mode 100644 index 000000000000..ad04f0fd0c82 --- /dev/null +++ b/scripts/generate_data.mk @@ -0,0 +1,37 @@ + +## Run all data generators +## $ make -f scripts/generate_data.mk + +ifeq ($(OS),Windows_NT) + BIN_CRYSTAL=bin\crystal +else + BIN_CRYSTAL=bin/crystal +endif + +.PHONY: all +all: ## Run all generators + $(BIN_CRYSTAL) run scripts/generate_grapheme_break_specs.cr + $(BIN_CRYSTAL) run scripts/generate_grapheme_properties.cr + $(BIN_CRYSTAL) run scripts/generate_ssl_server_defaults.cr + $(BIN_CRYSTAL) run scripts/generate_unicode_data.cr + $(BIN_CRYSTAL) run scripts/generate_windows_zone_names.cr + $(BIN_CRYSTAL) run scripts/generate_html_entities.cr + +ifneq ($(OS),Windows_NT) +.PHONY: help +help: ## Show this help + @echo + @printf '\033[34mtargets:\033[0m\n' + @grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) |\ + sort |\ + awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' + @echo + @printf '\033[34moptional variables:\033[0m\n' + @grep -hE '^[a-zA-Z_-]+ \?=.*?## .*$$' $(MAKEFILE_LIST) |\ + sort |\ + awk 'BEGIN {FS = " \\?=.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' + @echo + @printf '\033[34mrecipes:\033[0m\n' + @grep -hE '^##.*$$' $(MAKEFILE_LIST) |\ + awk 'BEGIN {FS = "## "}; /^## [a-zA-Z_-]/ {printf " \033[36m%s\033[0m\n", $$2}; /^## / {printf " %s\n", $$2}' +endif From 586a6c9769427244e1c17fb8686fcd33754c934d Mon Sep 17 00:00:00 2001 From: Thomas Leitner Date: Mon, 4 Dec 2023 12:50:26 +0100 Subject: [PATCH 0802/1551] Fix documentation for `String#index!` (#14038) --- src/string.cr | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/string.cr b/src/string.cr index 05c2665f2e4e..98b7ec3884fd 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3416,9 +3416,19 @@ class String self.match(search, offset, options: options).try &.begin end - # :ditto: + # Returns the index of the _first_ occurrence of *search* in the string. If *offset* is present, + # it defines the position to start the search. # # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`. + # + # ``` + # "Hello, World".index!('o') # => 4 + # "Hello, World".index!('Z') # raises Enumerable::NotFoundError + # "Hello, World".index!("o", 5) # => 8 + # "Hello, World".index!("H", 2) # raises Enumerable::NotFoundError + # "Hello, World".index!(/[ ]+/) # => 6 + # "Hello, World".index!(/\d+/) # raises Enumerable::NotFoundError + # ``` def index!(search, offset = 0) : Int32 index(search, offset) || raise Enumerable::NotFoundError.new end From 83a342501ad083e754addecb6f82eb0c009ba26f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 4 Dec 2023 19:50:33 +0800 Subject: [PATCH 0803/1551] Support `instance_sizeof(T)` in the interpreter (#14031) --- spec/compiler/interpreter/sizeof_spec.cr | 12 ++++++++ src/compiler/crystal/interpreter/compiler.cr | 29 +++++++------------- src/compiler/crystal/interpreter/context.cr | 14 +++++++++- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/spec/compiler/interpreter/sizeof_spec.cr b/spec/compiler/interpreter/sizeof_spec.cr index 4eb5216b28e5..4c52247b9687 100644 --- a/spec/compiler/interpreter/sizeof_spec.cr +++ b/spec/compiler/interpreter/sizeof_spec.cr @@ -7,4 +7,16 @@ describe Crystal::Repl::Interpreter do interpret("sizeof(typeof(1))").should eq(4) end end + + context "instance_sizeof" do + it "interprets instance_sizeof typeof" do + interpret(<<-CRYSTAL).should eq(16) + class Foo + @x = 0_i64 + end + + instance_sizeof(typeof(Foo.new)) + CRYSTAL + end + end end diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 1faf5db32bb6..03d7430a0fe6 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -1422,6 +1422,14 @@ class Crystal::Repl::Compiler < Crystal::Visitor false end + def visit(node : InstanceSizeOf) + return false unless @wants_value + + put_i32 inner_instance_sizeof_type(node.exp), node: node + + false + end + def visit(node : TypeNode) return false unless @wants_value @@ -3361,25 +3369,8 @@ class Crystal::Repl::Compiler < Crystal::Visitor @instructions.instructions.size end - private def aligned_sizeof_type(node : ASTNode) : Int32 - @context.aligned_sizeof_type(node) - end - - private def aligned_sizeof_type(type : Type?) : Int32 - @context.aligned_sizeof_type(type) - end - - private def inner_sizeof_type(node : ASTNode) : Int32 - @context.inner_sizeof_type(node) - end - - private def inner_sizeof_type(type : Type?) : Int32 - @context.inner_sizeof_type(type) - end - - private def aligned_instance_sizeof_type(type : Type) : Int32 - @context.aligned_instance_sizeof_type(type) - end + private delegate inner_sizeof_type, aligned_sizeof_type, + inner_instance_sizeof_type, aligned_instance_sizeof_type, to: @context private def ivar_offset(type : Type, name : String) : Int32 if type.extern_union? diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr index e91b615713f2..268d67448125 100644 --- a/src/compiler/crystal/interpreter/context.cr +++ b/src/compiler/crystal/interpreter/context.cr @@ -316,7 +316,19 @@ class Crystal::Repl::Context end def aligned_instance_sizeof_type(type : Type) : Int32 - align(@program.instance_size_of(type.sizeof_type).to_i32) + align(inner_instance_sizeof_type(type)) + end + + def inner_instance_sizeof_type(node : ASTNode) : Int32 + inner_instance_sizeof_type(node.type?) + end + + def inner_instance_sizeof_type(type : Type) : Int32 + @program.instance_size_of(type.sizeof_type).to_i32 + end + + def inner_instance_sizeof_type(type : Nil) : Int32 + 0 end def offset_of(type : Type, index : Int32) : Int32 From b9095175136b60ed25a6f1249695bc20e27dbf57 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 5 Dec 2023 18:00:46 +0800 Subject: [PATCH 0804/1551] Optimize `BigInt#bit` (#13980) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/big/big_int_spec.cr | 28 ++++++++++++++++++++++++++++ src/big/big_int.cr | 6 ++++++ src/big/lib_gmp.cr | 2 ++ 3 files changed, 36 insertions(+) diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr index 8a86939efbac..68eba643a571 100644 --- a/spec/std/big/big_int_spec.cr +++ b/spec/std/big/big_int_spec.cr @@ -283,6 +283,34 @@ describe "BigInt" do -5.to_big_i.remainder(-3).should eq(-2) end + it "#bit" do + x = 123.to_big_i + x.bit(-10.to_big_i ** 99).should eq(0) + x.bit(-(2.to_big_i ** 64)).should eq(0) + x.bit(-1).should eq(0) + x.bit(0).should eq(1) + x.bit(2).should eq(0) + x.bit(3).should eq(1) + x.bit(6).should eq(1) + x.bit(7).should eq(0) + x.bit(64).should eq(0) + x.bit(2.to_big_i ** 64).should eq(0) + x.bit(10.to_big_i ** 99).should eq(0) + + x = ~(123.to_big_i) + x.bit(-10.to_big_i ** 99).should eq(0) + x.bit(-(2.to_big_i ** 64)).should eq(0) + x.bit(-1).should eq(0) + x.bit(0).should eq(0) + x.bit(2).should eq(1) + x.bit(3).should eq(0) + x.bit(6).should eq(0) + x.bit(7).should eq(1) + x.bit(64).should eq(1) + x.bit(2.to_big_i ** 64).should eq(1) + x.bit(10.to_big_i ** 99).should eq(1) + end + it "does bitwise and" do (123.to_big_i & 321).should eq(65) (BigInt.new("96238761238973286532") & 86325735648).should eq(69124358272) diff --git a/src/big/big_int.cr b/src/big/big_int.cr index b0530c7b7592..e6b5f0214297 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -385,6 +385,12 @@ struct BigInt < Int BigInt.new { |mpz| LibGMP.com(mpz, self) } end + def bit(bit : Int) + return 0 if bit < 0 + return self < 0 ? 1 : 0 if bit > LibGMP::BitcntT::MAX + LibGMP.tstbit(self, LibGMP::BitcntT.new!(bit)) + end + def &(other : BigInt) : BigInt BigInt.new { |mpz| LibGMP.and(mpz, self, other) } end diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index 9caf408f1494..3cae0de64b77 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -116,6 +116,8 @@ lib LibGMP fun xor = __gmpz_xor(rop : MPZ*, op1 : MPZ*, op2 : MPZ*) fun com = __gmpz_com(rop : MPZ*, op : MPZ*) + fun tstbit = __gmpz_tstbit(op : MPZ*, bit_index : BitcntT) : Int + fun fdiv_q_2exp = __gmpz_fdiv_q_2exp(q : MPZ*, n : MPZ*, b : BitcntT) fun mul_2exp = __gmpz_mul_2exp(rop : MPZ*, op1 : MPZ*, op2 : BitcntT) From 25d33036f72694537fefc7e9123699d9b165921f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 5 Dec 2023 18:01:04 +0800 Subject: [PATCH 0805/1551] Windows: Run specs in random order by default (#14041) --- Makefile.win | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.win b/Makefile.win index 2c21717f6058..e2b18741657e 100644 --- a/Makefile.win +++ b/Makefile.win @@ -35,7 +35,7 @@ static ?= ## Enable static linking target ?= ## Cross-compilation target interpreter ?= ## Enable interpreter feature check ?= ## Enable only check when running format -order ?= ## Enable order for spec execution (values: "default" | "random" | seed number) +order ?=random ## Enable order for spec execution (values: "default" | "random" | seed number) MAKEFLAGS += --no-builtin-rules .SUFFIXES: From 70095f4a102b57ab17e91b1f690b844dc963aaf5 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 6 Dec 2023 03:03:43 +0800 Subject: [PATCH 0806/1551] Fix `ReadInstanceVar` on typedefs (#14044) --- spec/compiler/semantic/c_type_spec.cr | 34 +++++++++++++++++++++++---- src/compiler/crystal/types.cr | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/spec/compiler/semantic/c_type_spec.cr b/spec/compiler/semantic/c_type_spec.cr index dd034ae231e7..cb2e367a9593 100644 --- a/spec/compiler/semantic/c_type_spec.cr +++ b/spec/compiler/semantic/c_type_spec.cr @@ -2,24 +2,50 @@ require "../../spec_helper" describe "Semantic: type" do it "can call methods of original type" do - assert_type(" + assert_type(<<-CRYSTAL, inject_primitives: true) { uint64 } lib Lib type X = Void* fun foo : X end Lib.foo.address - ", inject_primitives: true) { uint64 } + CRYSTAL end it "can call methods of parent type" do - assert_error(" + assert_error(<<-CRYSTAL, "undefined method 'baz'") lib Lib type X = Void* fun foo : X end Lib.foo.baz - ", "undefined method 'baz'") + CRYSTAL + end + + it "can access instance variables of original type" do + assert_type(<<-CRYSTAL) { int32 } + lib Lib + struct X + x : Int32 + end + + type Y = X + fun foo : Y + end + + Lib.foo.@x + CRYSTAL + end + + it "errors if original type doesn't support instance variables" do + assert_error(<<-CRYSTAL, "can't use instance variables inside primitive types (at Int32)") + lib Lib + type X = Int32 + fun foo : X + end + + Lib.foo.@x + CRYSTAL end end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 7d4bc9795a7f..ad9f3d391fa6 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -2700,7 +2700,7 @@ module Crystal end delegate remove_typedef, pointer?, defs, - macros, reference_like?, parents, to: typedef + macros, reference_like?, parents, lookup_instance_var, to: typedef def remove_indirection self From 375b057d908248728402cac71f9a68480affd5bd Mon Sep 17 00:00:00 2001 From: kojix2 <2xijok@gmail.com> Date: Thu, 7 Dec 2023 05:55:32 +0900 Subject: [PATCH 0807/1551] Fix a typo in compiler source (#14054) --- src/compiler/crystal/codegen/codegen.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 9890454ecfef..d41b71edd042 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -255,7 +255,7 @@ module Crystal # We need to define __crystal_malloc and __crystal_realloc as soon as possible, # to avoid some memory being allocated with plain malloc. - codgen_well_known_functions @node + codegen_well_known_functions @node initialize_predefined_constants @@ -359,7 +359,7 @@ module Crystal end end - def codgen_well_known_functions(node) + def codegen_well_known_functions(node) visitor = CodegenWellKnownFunctions.new(self) node.accept visitor end From 1d108e04fab92e1fefb29f2c76ba9d0f8472a7f9 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Wed, 6 Dec 2023 15:56:21 -0500 Subject: [PATCH 0808/1551] Strip whitespace in doc comment before determining summary line (#14049) --- spec/compiler/crystal/tools/doc/generator_spec.cr | 11 +++++++++++ src/compiler/crystal/tools/doc/generator.cr | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/compiler/crystal/tools/doc/generator_spec.cr b/spec/compiler/crystal/tools/doc/generator_spec.cr index e3b7f58c15f9..b7bbd260293b 100644 --- a/spec/compiler/crystal/tools/doc/generator_spec.cr +++ b/spec/compiler/crystal/tools/doc/generator_spec.cr @@ -163,6 +163,17 @@ describe Doc::Generator do doc_method = Doc::Method.new generator, doc_type, a_def, false doc_method.formatted_summary.should eq %(

    Some Method

    ) end + + it "should exclude whitespace before the summary line" do + program = Program.new + generator = Doc::Generator.new program, ["."] + doc_type = Doc::Type.new generator, program + + a_def = Def.new "foo" + a_def.doc = " \n\nSome Method\n\nMore Data" + doc_method = Doc::Method.new generator, doc_type, a_def, false + doc_method.formatted_summary.should eq %(

    Some Method

    ) + end end describe "#formatted_doc" do diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index a2d6699944e0..1ae596809da9 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -290,7 +290,7 @@ class Crystal::Doc::Generator end def summary(context, string) - line = fetch_doc_lines(string).lines.first? || "" + line = fetch_doc_lines(string.strip).lines.first? || "" dot_index = line =~ /\.($|\s)/ if dot_index From 9c9262874c6a9bcffaeb4fd9f67cd2cf7d0fafc8 Mon Sep 17 00:00:00 2001 From: kojix2 <2xijok@gmail.com> Date: Thu, 7 Dec 2023 05:56:33 +0900 Subject: [PATCH 0809/1551] Fix typos in src (#14053) --- src/compiler/crystal/syntax/parser.cr | 2 +- src/enum.cr | 4 ++-- src/file.cr | 2 +- src/html.cr | 2 +- src/http/headers.cr | 2 +- src/io/delimited.cr | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index cbc001de99ef..d2d7816dd6b9 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -4584,7 +4584,7 @@ module Crystal when Splat push_block_vars(node.exp) else - raise "BUG: unxpected block var: #{node} (#{node.class})" + raise "BUG: unexpected block var: #{node} (#{node.class})" end end diff --git a/src/enum.cr b/src/enum.cr index 6ee61f3f38de..8b6ca9eebbae 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -167,11 +167,11 @@ struct Enum # Returns an unambiguous `String` representation of this enum member. # In the case of a single member value, this is the fully qualified name of - # the member (equvalent to `#to_s` with the enum name as prefix). + # the member (equivalent to `#to_s` with the enum name as prefix). # In the case of multiple members (for a flags enum), it's a call to `Enum.[]` # for recreating the same value. # - # If the value can't be represented fully by named members, the remainig value + # If the value can't be represented fully by named members, the remaining value # is appended. # # ``` diff --git a/src/file.cr b/src/file.cr index c1910abc6826..c88beadb0de4 100644 --- a/src/file.cr +++ b/src/file.cr @@ -207,7 +207,7 @@ class File < IO::FileDescriptor # Returns whether the file given by *path* exists. # - # Symbolic links are dereferenced, posibly recursively. Returns `false` if a + # Symbolic links are dereferenced, possibly recursively. Returns `false` if a # symbolic link refers to a non-existent file. # # ``` diff --git a/src/html.cr b/src/html.cr index fa90c25498c9..2d1d69926153 100644 --- a/src/html.cr +++ b/src/html.cr @@ -235,7 +235,7 @@ module HTML # The entity name cannot be longer than the longest name in the lookup tables. entity_name = Slice.new(start_ptr, Math.min(ptr - start_ptr, MAX_ENTITY_NAME_SIZE)) - # If we cann't find an entity on the first try, we need to search each prefix + # If we can't find an entity on the first try, we need to search each prefix # of it, starting from the largest. while entity_name.size >= 2 case diff --git a/src/http/headers.cr b/src/http/headers.cr index 3d4fd49e34c5..dacae7e9ec88 100644 --- a/src/http/headers.cr +++ b/src/http/headers.cr @@ -315,7 +315,7 @@ struct HTTP::Headers # Serializes headers according to the HTTP protocol. # - # Prints a list of HTTP header fields in the format desribed in [RFC 7230 §3.2](https://www.rfc-editor.org/rfc/rfc7230#section-3.2), + # Prints a list of HTTP header fields in the format described in [RFC 7230 §3.2](https://www.rfc-editor.org/rfc/rfc7230#section-3.2), # with each field terminated by a CRLF sequence (`"\r\n"`). # # The serialization does *not* include a double CRLF sequence at the end. diff --git a/src/io/delimited.cr b/src/io/delimited.cr index ea1039724330..b0e235881499 100644 --- a/src/io/delimited.cr +++ b/src/io/delimited.cr @@ -192,7 +192,7 @@ class IO::Delimited < IO slice.copy_from(peek[0, index]) slice += index - # Copy the rest of the peek buffer into delimted buffer + # Copy the rest of the peek buffer into delimited buffer @delimiter_buffer.copy_from(rest) @active_delimiter_buffer = @delimiter_buffer[0, rest.size] From eebf26573fcb952abd51c564ee631eb9fd4d229b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 7 Dec 2023 04:56:45 +0800 Subject: [PATCH 0810/1551] Disable `mkfifo` spec for interpreter (#14051) --- spec/std/file_utils_spec.cr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/std/file_utils_spec.cr b/spec/std/file_utils_spec.cr index b90203b8b1a4..bc730dde5c01 100644 --- a/spec/std/file_utils_spec.cr +++ b/spec/std/file_utils_spec.cr @@ -715,7 +715,8 @@ describe "FileUtils" do end end - {% if flag?(:unix) %} + # FIXME: `Process.run` and backtick don't work in the interpreter (#12241) + {% if flag?(:unix) && !flag?(:interpreted) %} it "overwrites a destination named pipe" do with_tempfile("ln_sf_src", "ln_sf_dst_pipe_exists") do |path1, path2| test_with_string_and_path(path1, path2) do |arg1, arg2| From 142ab2ef59e9d32a95efe12647c21249720c7d81 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 7 Dec 2023 18:16:39 +0800 Subject: [PATCH 0811/1551] Interpreter: Fix element alignment of `Tuple` and `NamedTuple` casts (#14040) --- spec/compiler/interpreter/casts_spec.cr | 14 +++ src/compiler/crystal/interpreter/cast.cr | 113 +++++++++++++------ src/compiler/crystal/interpreter/compiler.cr | 4 +- test | Bin 0 -> 9832 bytes 4 files changed, 97 insertions(+), 34 deletions(-) create mode 100755 test diff --git a/spec/compiler/interpreter/casts_spec.cr b/spec/compiler/interpreter/casts_spec.cr index 6398b6526876..1d3970430479 100644 --- a/spec/compiler/interpreter/casts_spec.cr +++ b/spec/compiler/interpreter/casts_spec.cr @@ -93,6 +93,13 @@ describe Crystal::Repl::Interpreter do CRYSTAL end + it "upcasts between tuple types, respects alignment (#14036)" do + interpret(<<-CRYSTAL, prelude: "prelude").should eq("123") + a = {100, 20, 3}.as({Int32 | Int64, Int32, Int32}) + a[0].as(Int32) + a[1] + a[2] + CRYSTAL + end + it "upcasts between named tuple types, same order" do interpret(<<-CRYSTAL, prelude: "prelude").should eq((1 + 'a'.ord).to_s) a = @@ -119,6 +126,13 @@ describe Crystal::Repl::Interpreter do CRYSTAL end + it "upcasts between named tuple types, respects alignment (#14036)" do + interpret(<<-CRYSTAL, prelude: "prelude").should eq("123") + a = {x: 100, y: 20_i64, z: 3}.as({y: Int64, z: Int32, x: Int32}) + a[:x] + a[:y] + a[:z] + CRYSTAL + end + it "upcasts to module type" do interpret(<<-CRYSTAL).should eq(1) module Moo diff --git a/src/compiler/crystal/interpreter/cast.cr b/src/compiler/crystal/interpreter/cast.cr index 8fed0e3d5d5d..21745290a374 100644 --- a/src/compiler/crystal/interpreter/cast.cr +++ b/src/compiler/crystal/interpreter/cast.cr @@ -220,7 +220,7 @@ class Crystal::Repl::Compiler private def upcast_distinct(node : ASTNode, from : TupleInstanceType, to : TupleInstanceType) # If we are here it means the tuples are different - unpack_tuple(node, from, to.tuple_types) + cast_tuple(node, from, to) # Finally, we must pop the original tuple that was casted pop_from_offset aligned_sizeof_type(from), aligned_sizeof_type(to), node: nil @@ -228,7 +228,7 @@ class Crystal::Repl::Compiler private def upcast_distinct(node : ASTNode, from : NamedTupleInstanceType, to : NamedTupleInstanceType) # If we are here it means the tuples are different - unpack_named_tuple(node, from, to) + cast_named_tuple(node, from, to) # Finally, we must pop the original tuple that was casted pop_from_offset aligned_sizeof_type(from), aligned_sizeof_type(to), node: nil @@ -236,59 +236,91 @@ class Crystal::Repl::Compiler # Unpacks a tuple into a series of types. # Each of the tuple elements is upcasted to the corresponding type in `to_types`. + # Every individual element is stack-aligned. Use `#cast_tuple` instead if they + # should follow their natural alignments inside a target tuple type. # It's the caller's responsibility to pop the original, unpacked tuple, from the # stack if needed. private def unpack_tuple(node : ASTNode, from : TupleInstanceType, to_types : Array(Type)) - offset = aligned_sizeof_type(from) + from_aligned_size = aligned_sizeof_type(from) + to_element_offset = 0 to_types.each_with_index do |to_element_type, i| from_element_type = from.tuple_types[i] from_inner_size = inner_sizeof_type(from_element_type) + from_element_offset = @context.offset_of(from, i) + # Copy inner size bytes from the tuple. # The interpreter will make sure to align this value. - copy_from(offset, from_inner_size, node: nil) + # Go back `from_aligned_size` plus any elements already pushed onto the stack, + # but then move forward (subtracting) to reach the element in `from`. + copy_from(from_aligned_size - from_element_offset + to_element_offset, from_inner_size, node: nil) # Then upcast it to the target tuple element type upcast node, from_element_type, to_element_type - # Check the offset of this tuple element in `from` - current_offset = - @context.offset_of(from, i) + # Now we have element_type in front of the tuple so we must skip it + to_element_offset += aligned_sizeof_type(to_element_type) + end + end + + private def cast_tuple(node : ASTNode, from : TupleInstanceType, to : TupleInstanceType) + from_aligned_size = aligned_sizeof_type(from) + to_aligned_size = aligned_sizeof_type(to) + to_element_offset = 0 + + to.tuple_types.each_with_index do |to_element_type, i| + from_element_type = from.tuple_types[i] + + from_inner_size = inner_sizeof_type(from_element_type) + + from_element_offset = @context.offset_of(from, i) - # Check what's the next offset in `from` is - next_offset = - if i == from.tuple_types.size - 1 - aligned_sizeof_type(from) + # Copy inner size bytes from the tuple. + # The interpreter will make sure to align this value. + # Go back `from_aligned_size` plus any elements already pushed onto the stack, + # but then move forward (subtracting) to reach the element in `from`. + copy_from(from_aligned_size - from_element_offset + to_element_offset, from_inner_size, node: nil) + + # Then upcast it to the target tuple element type + upcast node, from_element_type, to_element_type + + # the new value is stack-aligned; adjust as necessary to follow the + # element's natural alignment inside the target type + next_to_offset = + if i == to.tuple_types.size - 1 + aligned_sizeof_type(to) else - @context.offset_of(from, i + 1) + @context.offset_of(to, i + 1) end + difference = next_to_offset - to_element_offset - aligned_sizeof_type(to_element_type) + if difference > 0 + push_zeros(difference, node: nil) + elsif difference < 0 + pop(-difference, node: nil) + end + # Now we have element_type in front of the tuple so we must skip it - offset += aligned_sizeof_type(to_element_type) - # But we need to access the next tuple member, so we move forward - offset -= next_offset - current_offset + to_element_offset = next_to_offset end end - private def unpack_named_tuple(node : ASTNode, from : NamedTupleInstanceType, to : NamedTupleInstanceType) - offset = aligned_sizeof_type(from) + private def cast_named_tuple(node : ASTNode, from : NamedTupleInstanceType, to : NamedTupleInstanceType) + from_aligned_size = aligned_sizeof_type(from) + to_aligned_size = aligned_sizeof_type(to) + to_element_offset = 0 - to.entries.each_with_index do |to_entry, i| - from_entry = nil - from_entry_index = nil - - from.entries.each_with_index do |other_entry, j| - if other_entry.name == to_entry.name - from_entry = other_entry - from_entry_index = j - break - end + from_entry_indices = to.entries.map_with_index do |to_entry, i| + from.entries.index! do |other_entry| + other_entry.name == to_entry.name end + end - from_entry = from_entry.not_nil! - from_entry_index = from_entry_index.not_nil! + to.entries.each_with_index do |to_entry, i| + from_entry_index = from_entry_indices[i] + from_entry = from.entries[from_entry_index] from_element_type = from_entry.type to_element_type = to_entry.type @@ -299,14 +331,31 @@ class Crystal::Repl::Compiler # Copy inner size bytes from the tuple. # The interpreter will make sure to align this value. - # Go back `offset`, but then move forward (subtracting) to reach the element in `from`. - copy_from(offset - from_element_offset, from_inner_size, node: nil) + # Go back `from_aligned_size` plus any elements already pushed onto the stack, + # but then move forward (subtracting) to reach the element in `from`. + copy_from(from_aligned_size - from_element_offset + to_element_offset, from_inner_size, node: nil) # Then upcast it to the target tuple element type upcast node, from_element_type, to_element_type + # the new value is stack-aligned; adjust as necessary to follow the + # element's natural alignment inside the target type + next_to_offset = + if i == to.entries.size - 1 + aligned_sizeof_type(to) + else + @context.offset_of(to, i + 1) + end + + difference = next_to_offset - to_element_offset - aligned_sizeof_type(to_element_type) + if difference > 0 + push_zeros(difference, node: nil) + elsif difference < 0 + pop(-difference, node: nil) + end + # Now we have element_type in front of the tuple so we must skip it - offset += aligned_sizeof_type(to_element_type) + to_element_offset = next_to_offset end end diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 03d7430a0fe6..7d56d8121bed 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -389,8 +389,8 @@ class Crystal::Repl::Compiler < Crystal::Visitor type = node.type.as(TupleInstanceType) - # A tuple potentially has the values packed (unaligned). - # The values in the stack are aligned, so we must adjust that: + # The elements inside a tuple do not follow the stack alignment. + # Each element expression is stack-aligned, so we must adjust that: # if the value in the stack has more bytes than needed, we pop # the extra ones; if it has less bytes that needed we pad the value # with zeros. diff --git a/test b/test new file mode 100755 index 0000000000000000000000000000000000000000..d634907d6244430c7f8ed367e578562f81e3dcf9 GIT binary patch literal 9832 zcmeHNeQaFC5#PJB9mh`Wb3$lnAbugCu|s?x{ zbFc$#+ys>%g_6)nrKp8aS~US$p$Z8sRjLzEo2sd55J?e5$l*f@Rgv75wy_9tXZFq9 zowqTesQ*;zSo>!8H#58Y_U*^)ocDD1Zm`>Ig2^FX5Lh~SwT1Xd5L-zZU_Q|*%HgSr zm7)a7MG~{*K8u0WqL_UyFD2Xo9PN&hT;svgvz@tSTN_YzXR;BpZ_VLJ*Ej7H?-|s^GPtQw4t>nt!Yx z1AK{CEOuOjwFq&+VqzRP)^kp2x1Q=>uG9~(tcTa%6;uzlQza?|uQP79mGtkTeTen< zN&RJFQfYSuwd+*c{k+t73Qc*hd#Ik1R1eR)hY5dOssC-l&ntK*wW}%ZZUY?0ElvIJ z0eg$=0{?8E>JR6+O_)#@En^W`jJuj$>;?=8`^^d8Ra>PYQaaXTJ6KxmE7QVdMP*Ag z`2M9%tOIp%jB(qxbn09BHu*Pn3&?Nfe5a+<-rlh#(A%YV-o9~TV6#xrF8}84 zJl5&&?CI9K0>M1g)xE*LrFXO5@88^0h;Hf(2KqM#Ztu&3b5(i+9YKF^n_|dlaIW&! z?%<|5b@ldc-KaCVZXlJqF_6qQ)UVehyQy6=fkWoXqy*I3DcsReHe@{_sUa5(*wFBZ zdmx@LGVbAw3EOBG4WbFOaC+h61375o^ZHw^;Jj`$1?PRDM!|W#dK8@3S*wEcI`%0z zufHAz=l!o=!FeCLQ^BYG)_d+ya9+nT1?T-Tt>C<0jwm?qH)9IU``>;A=Y8TR;dU5* zTFSDc&lTWytLyUlVgZi66cgGmpcH8yXlQDxXOZM6FQZiNsc)$Bdg{C_c@>o7)+o~_ zOrTh5$Z)$Np-9Yd)9RCzpgp>dnAY4L4BQgvbGuybOe|#P6x|s!B9-BR`e9166g98Q z>v4ItuTcNDYc=_v)|QpwQfSw_EiR9%zO2g_2n{E)8f-L!Mz&oGh0I8-xv_3Ixigu% zCs_x+!th94$np%}#j(8`DZ%7#zuY20f8%of#}UT#DeHn}^FxOHDI_eTUxmpUheD~< z|D0S70bnZ}H~2sezx(nkA?^fz7w{3_Y2XKd9{_#?`1gT75ByPZLeN)u7Wlis`8=`h z4hq|SD{U(lRm*RPzSN5#>;f*j>bfdCJ&Ue!K5qX3%u}?%bNNlk@Og|_FJU+bkPzDe z>0QWR9trickfs2*fIwG;^KqyXaqREQ&`((>;n~Jri9>MCO)i84(_j+T(&Rg<#i6r~>ZeN|^v%>h z@1Y4HOFZ&JL^P# zVztc~hc;1$s~5aTd^uQ&(!_sf0bTCuppRxGMMM0=1;E%1$BEceZT{UNNIzx(vPzdQz0XtL^6 z-;8r_$srBC5sn=vDLVtQA6hb5egn!U3d(Om`E9E_OT_hGu1s#Q*h34M*Yi_K8GSY+ zzDC11N&0AZHia1wONj5gmeg9)m@g#*wi~1RU_ML2^?@`)^}%~e`W*57q{wGT58-@v z9Q0a%Tyv1cy#$Hxcz2Q_T@&RU3+3p2BHcxbc(x!tOa1%%MeF@0Nl}wh*az@{0a|6T z+`e!XmD$f&p~%svMPh%WaF0QJn%;km+Pg^Q=co+dlgAs?|7U1|CXUMa(yP^YJ#f~0 z>RZ=q*QWICSzw7Q6zYZ&h=oHLBdVp6n%UUe($d%xt<@rlP;yY)Wtf>b;MS?C z)x@&dbf(?yri0Tpm`V+T-~4P_;#(gx65j^LD;Hn*HtVn zc2qb^R#ZISv8c+n1ZI)lB{1VFd%#!Ws0b_tA9W7WUVcM4qD5%8)hSJ$l%*(DW%zcHbB73o|%(2k^uFY0b{?{T$u_(E}*im*{N%>w7i4w=?UMT*p z#IgI<{g{fKdmX#?miOBi!TVq%^An1T#9wArjYSezoQr$19NZ1)-eU14Z8TjZu=f3Y zoPzbQm6?2ef$_C6laHq`zFuaQ362jkZf(c-9p_TBEG`nIR)=%K8z5(co=#YwUEpj% z!s&r;z_={JW0TC-#cQO`_;$iiDfl4ad=9XFn()^Z{rd=C&Ib$`G{Fb3+ZPLs4o*IA z`Ur0%ocHxDfZOc(@!|6?Mf&}u@1*)40)DCQ`#mYo$E`?RJT5cK1m8y)e@bS`q{GM< z@}siAr)pmEQ<>#cIFJ8aX0H;s3l-7}vcRWVUh)f>d0ZCZ@fgkn`x4<(>iGa*jDM-| zz7GMvRJ^YQzHb$H1?&g#cV8y){5oQM4d7TmZ`C}nY9$VhC={Lt|HeG{L8_lmiVIZV z6M$pfljjeQ&ohK`JcIlB0^uCbVElK4b3B9bvxIXzgYgdu=XeI=WibBOAC6}*UQIa1 zGZ?QUoZ}gccM{I=4934qIL9*>-$^*fGZ_CS;FpYhy5K}I%(QSNQ(42senvcMz(M7T zjwUmsL&7zf9CndwGN+)MRxuXJ#4PPBAf}NBp(*9k38=|TVGu*OjF>)PhK3A176nZ% z35be~fR z8jb(OfU*@#)?@Jc35X=$V}{acc!6AuIhsk4aBlQDosF4BD4GvF|34T#Uocwl+Scda z80b`X-p^Kq?G9Z=@MJ8S@AqO7OI^+*$LlZNY1y9rA?EBap)L~d<5o!sbTjZ~!S?J2 zF|Q#3wt@ZR{f|9@>j1;MC)@M=l(`G!NbD}L9&?N@;$4=@e4k~mp)nA*ulC;$vVRoV z^L?B-#@vv&eeS=73UM3BxP89wGw1t0>*E*{68f9iC$`VUGpM04GEm#+@!L-J9aJgD zGnn@fj`n!=bN|^M{a0*)+s8m45_3FH3yJ+n#!`?ew4d*Ha@JSJ|Lcl9`%}y(*pN8) zpWz1}gY8q8CFe(-q} z_WwzMu_?A^zfP;P^wsuMd-ShSmF+qH@D9ow^i%Kv9EN#lp>2Z`I~ew_de zRoLE1@iHe~5P`Er`oTc$9|gZ-_AEJbE>@?0N%R_RbQuz&Jl`l pR)Y|OO+0^i|7!tzo;!v1Bh>ML(x6fl{(0<=(^d5;rE{wNzW_U68an_0 literal 0 HcmV?d00001 From c051c81d1a29d33613a197dd8ec073abd99dda7e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 7 Dec 2023 18:16:58 +0800 Subject: [PATCH 0812/1551] Avoid `@[ThreadLocal]` on Android (#14025) --- src/crystal/system/unix/pthread.cr | 9 ++++++--- src/lib_c/aarch64-android/c/pthread.cr | 4 ++++ src/lib_c/aarch64-android/c/sys/types.cr | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index 44076dedcc43..ca16080621e3 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -31,9 +31,12 @@ module Crystal::System::Thread raise RuntimeError.from_errno("sched_yield") unless ret == 0 end - {% if flag?(:openbsd) %} - # no thread local storage (TLS) for OpenBSD, - # we use pthread's specific storage (TSS) instead: + # no thread local storage (TLS) for OpenBSD, + # we use pthread's specific storage (TSS) instead + # + # Android appears to support TLS to some degree, but executables fail with + # an underaligned TLS segment, see https://github.com/crystal-lang/crystal/issues/13951 + {% if flag?(:openbsd) || flag?(:android) %} @@current_key : LibC::PthreadKeyT @@current_key = begin diff --git a/src/lib_c/aarch64-android/c/pthread.cr b/src/lib_c/aarch64-android/c/pthread.cr index 2203ea4a5170..d761bcb3ceb2 100644 --- a/src/lib_c/aarch64-android/c/pthread.cr +++ b/src/lib_c/aarch64-android/c/pthread.cr @@ -23,7 +23,9 @@ lib LibC fun pthread_detach(__pthread : PthreadT) : Int fun pthread_getattr_np(__pthread : PthreadT, __attr : PthreadAttrT*) : Int fun pthread_equal(__lhs : PthreadT, __rhs : PthreadT) : Int + fun pthread_getspecific(__key : PthreadKeyT) : Void* fun pthread_join(__pthread : PthreadT, __return_value_ptr : Void**) : Int + fun pthread_key_create(__key_ptr : PthreadKeyT*, __key_destructor : Void* ->) : Int fun pthread_mutexattr_destroy(__attr : PthreadMutexattrT*) : Int fun pthread_mutexattr_init(__attr : PthreadMutexattrT*) : Int @@ -36,4 +38,6 @@ lib LibC fun pthread_mutex_unlock(__mutex : PthreadMutexT*) : Int fun pthread_self : PthreadT + + fun pthread_setspecific(__key : PthreadKeyT, __value : Void*) : Int end diff --git a/src/lib_c/aarch64-android/c/sys/types.cr b/src/lib_c/aarch64-android/c/sys/types.cr index 4611e72fa79a..ae4947109412 100644 --- a/src/lib_c/aarch64-android/c/sys/types.cr +++ b/src/lib_c/aarch64-android/c/sys/types.cr @@ -30,6 +30,7 @@ lib LibC end alias PthreadCondattrT = Long + alias PthreadKeyT = Int struct PthreadMutexT __private : Int32[10] From 413f2fdd0a26fae0a35197b0c20be30442afa70e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 7 Dec 2023 18:17:29 +0800 Subject: [PATCH 0813/1551] Add `Float::Primitive.parse_hexfloat`, `.parse_hexfloat?`, `#to_hexfloat` (#14027) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/interpreter_std_spec.cr | 3 +- spec/std/float_printer/hexfloat_spec.cr | 709 ++++++++++++++++++ .../shortest_spec.cr} | 4 +- spec/support/number.cr | 76 +- src/float.cr | 140 +++- src/float/printer.cr | 175 +++-- src/float/printer/hexfloat.cr | 255 +++++++ src/humanize.cr | 2 +- 8 files changed, 1209 insertions(+), 155 deletions(-) create mode 100644 spec/std/float_printer/hexfloat_spec.cr rename spec/std/{float_printer_spec.cr => float_printer/shortest_spec.cr} (99%) create mode 100644 src/float/printer/hexfloat.cr diff --git a/spec/interpreter_std_spec.cr b/spec/interpreter_std_spec.cr index 6ca5aeac2ea3..3cdcc55cbd61 100644 --- a/spec/interpreter_std_spec.cr +++ b/spec/interpreter_std_spec.cr @@ -77,8 +77,9 @@ require "./std/file/tempfile_spec.cr" require "./std/file_utils_spec.cr" require "./std/float_printer/diy_fp_spec.cr" require "./std/float_printer/grisu3_spec.cr" +require "./std/float_printer/hexfloat_spec.cr" require "./std/float_printer/ieee_spec.cr" -require "./std/float_printer_spec.cr" +require "./std/float_printer/shortest_spec.cr" require "./std/float_spec.cr" require "./std/gc_spec.cr" require "./std/hash_spec.cr" diff --git a/spec/std/float_printer/hexfloat_spec.cr b/spec/std/float_printer/hexfloat_spec.cr new file mode 100644 index 000000000000..337711490368 --- /dev/null +++ b/spec/std/float_printer/hexfloat_spec.cr @@ -0,0 +1,709 @@ +# This file contains test cases derived from Microsoft's STL: +# https://github.com/microsoft/STL/tree/main/tests/std/tests/P0067R5_charconv +# +# Original license: +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +require "spec" +require "spec/helpers/string" + +private def assert_to_s(num : F, str, *, file = __FILE__, line = __LINE__) forall F + assert_prints num.to_hexfloat, str, file: file, line: line + if num.nan? + F.parse_hexfloat(str).nan?.should be_true, file: file, line: line + else + F.parse_hexfloat(str).should eq(num), file: file, line: line + end +end + +private def assert_parse_error(type : F.class, str, err, *, file = __FILE__, line = __LINE__) forall F + expect_raises ArgumentError, "Invalid hexfloat: #{err}", file: file, line: line do + F.parse_hexfloat(str) + end + F.parse_hexfloat?(str).should be_nil, file: file, line: line +end + +describe Float64 do + describe ".parse_hexfloat" do + it { Float64.parse_hexfloat("0x123p+0").should eq(291_f64) } + it { Float64.parse_hexfloat("0x123.0p+0").should eq(291_f64) } + it { Float64.parse_hexfloat("0x123p0").should eq(291_f64) } + + it { Float64.parse_hexfloat("0x123.p0").should eq(291_f64) } + it { Float64.parse_hexfloat("0x.123p12").should eq(291_f64) } + it { Float64.parse_hexfloat("0x123.456p7").should eq(37282.6875_f64) } + + it { Float64.parse_hexfloat("+0x123p+0").should eq(291_f64) } + it { Float64.parse_hexfloat("-0x123p-0").should eq(-291_f64) } + + it { Float64.parse_hexfloat("0XABCDEFP+0").should eq(11259375_f64) } + + it { Float64.parse_hexfloat("0x1.000000000000a000p+0").should eq(1.0000000000000022) } # exact + it { Float64.parse_hexfloat("0x1.000000000000a001p+0").should eq(1.0000000000000022) } # below midpoint, round down + it { Float64.parse_hexfloat("0x1.000000000000a800p+0").should eq(1.0000000000000022) } # midpoint, round down to even + it { Float64.parse_hexfloat("0x1.000000000000a801p+0").should eq(1.0000000000000024) } # above midpoint, round up + it { Float64.parse_hexfloat("0x1.000000000000b000p+0").should eq(1.0000000000000024) } # exact + it { Float64.parse_hexfloat("0x1.000000000000b001p+0").should eq(1.0000000000000024) } # below midpoint, round down + it { Float64.parse_hexfloat("0x1.000000000000b800p+0").should eq(1.0000000000000027) } # midpoint, round up to even + it { Float64.parse_hexfloat("0x1.000000000000b801p+0").should eq(1.0000000000000027) } # above midpoint, round up + + it { Float64.parse_hexfloat("0x1.00000000000020p+0").should eq(1.0000000000000004) } # exact + it { Float64.parse_hexfloat("0x1.00000000000021p+0").should eq(1.0000000000000004) } # below midpoint, round down + it { Float64.parse_hexfloat("0x1.00000000000028p+0").should eq(1.0000000000000004) } # midpoint, round down to even + it { Float64.parse_hexfloat("0x1.00000000000029p+0").should eq(1.0000000000000007) } # above midpoint, round up + it { Float64.parse_hexfloat("0x1.00000000000030p+0").should eq(1.0000000000000007) } # exact + it { Float64.parse_hexfloat("0x1.00000000000031p+0").should eq(1.0000000000000007) } # below midpoint, round down + it { Float64.parse_hexfloat("0x1.00000000000038p+0").should eq(1.0000000000000009) } # midpoint, round up to even + it { Float64.parse_hexfloat("0x1.00000000000039p+0").should eq(1.0000000000000009) } # above midpoint, round up + + it { Float64.parse_hexfloat("0x1.000000000000a000000000000000000p+0").should eq(1.0000000000000022) } # exact + it { Float64.parse_hexfloat("0x1.000000000000a000000000000000001p+0").should eq(1.0000000000000022) } # below midpoint, round down + it { Float64.parse_hexfloat("0x1.000000000000a800000000000000000p+0").should eq(1.0000000000000022) } # midpoint, round down to even + it { Float64.parse_hexfloat("0x1.000000000000a800000000000000001p+0").should eq(1.0000000000000024) } # above midpoint, round up + it { Float64.parse_hexfloat("0x1.000000000000b000000000000000000p+0").should eq(1.0000000000000024) } # exact + it { Float64.parse_hexfloat("0x1.000000000000b000000000000000001p+0").should eq(1.0000000000000024) } # below midpoint, round down + it { Float64.parse_hexfloat("0x1.000000000000b800000000000000000p+0").should eq(1.0000000000000027) } # midpoint, round up to even + it { Float64.parse_hexfloat("0x1.000000000000b800000000000000001p+0").should eq(1.0000000000000027) } # above midpoint, round up + + it { Float64.parse_hexfloat("0x1000000000000a0000000p+0").should eq(1.2089258196146319e+24) } # exact + it { Float64.parse_hexfloat("0x1000000000000a0010000p+0").should eq(1.2089258196146319e+24) } # below midpoint, round down + it { Float64.parse_hexfloat("0x1000000000000a8000000p+0").should eq(1.2089258196146319e+24) } # midpoint, round down to even + it { Float64.parse_hexfloat("0x1000000000000a8010000p+0").should eq(1.208925819614632e+24) } # above midpoint, round up + it { Float64.parse_hexfloat("0x1000000000000b0000000p+0").should eq(1.208925819614632e+24) } # exact + it { Float64.parse_hexfloat("0x1000000000000b0010000p+0").should eq(1.208925819614632e+24) } # below midpoint, round down + it { Float64.parse_hexfloat("0x1000000000000b8000000p+0").should eq(1.2089258196146324e+24) } # midpoint, round up to even + it { Float64.parse_hexfloat("0x1000000000000b8010000p+0").should eq(1.2089258196146324e+24) } # above midpoint, round up + + it { Float64.parse_hexfloat("0x.00000000000000001000000000000a000p+0").should eq(3.388131789017209e-21) } # exact + it { Float64.parse_hexfloat("0x.00000000000000001000000000000a001p+0").should eq(3.388131789017209e-21) } # below midpoint, round down + it { Float64.parse_hexfloat("0x.00000000000000001000000000000a800p+0").should eq(3.388131789017209e-21) } # midpoint, round down to even + it { Float64.parse_hexfloat("0x.00000000000000001000000000000a801p+0").should eq(3.38813178901721e-21) } # above midpoint, round up + it { Float64.parse_hexfloat("0x.00000000000000001000000000000b000p+0").should eq(3.38813178901721e-21) } # exact + it { Float64.parse_hexfloat("0x.00000000000000001000000000000b001p+0").should eq(3.38813178901721e-21) } # below midpoint, round down + it { Float64.parse_hexfloat("0x.00000000000000001000000000000b800p+0").should eq(3.3881317890172104e-21) } # midpoint, round up to even + it { Float64.parse_hexfloat("0x.00000000000000001000000000000b801p+0").should eq(3.3881317890172104e-21) } # above midpoint, round up + + # https://www.exploringbinary.com/nondeterministic-floating-point-conversions-in-java/ + it { Float64.parse_hexfloat("0x0.0000008p-1022").should eq(6.63123685e-316) } + + describe "round-to-nearest, ties-to-even" do + it { Float64.parse_hexfloat("0x0.00000000000008p-1022").should eq(0.0) } + it { Float64.parse_hexfloat("0x0.00000000000008#{"0" * 1000}1p-1022").should eq(5.0e-324) } + + it { Float64.parse_hexfloat("0x0.ffffffffffffe8p-1022").should eq(2.2250738585072004e-308) } + it { Float64.parse_hexfloat("0x0.ffffffffffffe8#{"0" * 1000}1p-1022").should eq(Float64::MIN_POSITIVE.prev_float) } + + it { Float64.parse_hexfloat("0x1.00000000000008p+0").should eq(1.0) } + it { Float64.parse_hexfloat("0x1.00000000000008#{"0" * 1000}1p+0").should eq(1.0000000000000002) } + + it { Float64.parse_hexfloat("0x1.ffffffffffffe8p+0").should eq(1.9999999999999996) } + it { Float64.parse_hexfloat("0x1.ffffffffffffe8#{"0" * 1000}1p+0").should eq(1.9999999999999998) } + + it { Float64.parse_hexfloat("0x1.00000000000008p+1023").should eq(8.98846567431158e307) } + it { Float64.parse_hexfloat("0x1.00000000000008#{"0" * 1000}1p+1023").should eq(8.988465674311582e307) } + + it { Float64.parse_hexfloat("0x1.ffffffffffffe8p+1023").should eq(1.7976931348623155e308) } + it { Float64.parse_hexfloat("0x1.ffffffffffffe8#{"0" * 1000}1p+1023").should eq(1.7976931348623157e308) } + end + + describe "values close to zero" do + it { Float64.parse_hexfloat("0x0.7p-1074").should eq(Float64.zero) } + it { Float64.parse_hexfloat("0x0.8p-1074").should eq(Float64.zero) } + it { Float64.parse_hexfloat("0x0.9p-1074").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x0.fp-1074").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x1.0p-1074").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x1.1p-1074").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x1.7p-1074").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x1.8p-1074").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0x1.9p-1074").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0x1.fp-1074").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0x2.0p-1074").should eq(Float64::MIN_SUBNORMAL * 2) } + + it { Float64.parse_hexfloat("0x0.fp-1075").should eq(Float64.zero) } + it { Float64.parse_hexfloat("0x1.0p-1075").should eq(Float64.zero) } + it { Float64.parse_hexfloat("0x1.1p-1075").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x1.fp-1075").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x2.0p-1075").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x2.1p-1075").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x2.fp-1075").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x3.0p-1075").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0x3.1p-1075").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0x3.fp-1075").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0x4.0p-1075").should eq(Float64::MIN_SUBNORMAL * 2) } + + it { Float64.parse_hexfloat("0x1.fp-1076").should eq(Float64.zero) } + it { Float64.parse_hexfloat("0x2.0p-1076").should eq(Float64.zero) } + it { Float64.parse_hexfloat("0x2.1p-1076").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x3.fp-1076").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x4.0p-1076").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x4.1p-1076").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x5.fp-1076").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x6.0p-1076").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0x6.1p-1076").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0x7.fp-1076").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0x8.0p-1076").should eq(Float64::MIN_SUBNORMAL * 2) } + + it { Float64.parse_hexfloat("0x3.fp-1077").should eq(Float64.zero) } + it { Float64.parse_hexfloat("0x4.0p-1077").should eq(Float64.zero) } + it { Float64.parse_hexfloat("0x4.1p-1077").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x7.fp-1077").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x8.0p-1077").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0x8.1p-1077").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0xb.fp-1077").should eq(Float64::MIN_SUBNORMAL) } + it { Float64.parse_hexfloat("0xc.0p-1077").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0xc.1p-1077").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0xf.fp-1077").should eq(Float64::MIN_SUBNORMAL * 2) } + it { Float64.parse_hexfloat("0x10.0p-1077").should eq(Float64::MIN_SUBNORMAL * 2) } + end + + describe "values close to MIN_POSITIVE and MAX" do + it { Float64.parse_hexfloat("0x0.fffffffffffffp-1022").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x1.0000000000000p-1022").should eq(Float64::MIN_POSITIVE) } + + it { Float64.parse_hexfloat("0x1.ffffffffffffep-1023").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x1.fffffffffffffp-1023").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x2.0000000000000p-1023").should eq(Float64::MIN_POSITIVE) } + + it { Float64.parse_hexfloat("0x3.ffffffffffffcp-1024").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x3.ffffffffffffdp-1024").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x3.ffffffffffffep-1024").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x3.fffffffffffffp-1024").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x4.0000000000000p-1024").should eq(Float64::MIN_POSITIVE) } + + it { Float64.parse_hexfloat("0x7.ffffffffffff8p-1025").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x7.ffffffffffff9p-1025").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x7.ffffffffffffbp-1025").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x7.ffffffffffffcp-1025").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x7.ffffffffffffdp-1025").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x7.ffffffffffffep-1025").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x7.fffffffffffffp-1025").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x8.0000000000000p-1025").should eq(Float64::MIN_POSITIVE) } + + it { Float64.parse_hexfloat("0x0.fffffffffffff0p-1022").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x0.fffffffffffff1p-1022").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x0.fffffffffffff7p-1022").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x0.fffffffffffff8p-1022").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x0.fffffffffffff9p-1022").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x0.fffffffffffffbp-1022").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x0.fffffffffffffcp-1022").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x0.fffffffffffffdp-1022").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x0.ffffffffffffffp-1022").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x1.00000000000000p-1022").should eq(Float64::MIN_POSITIVE) } + + it { Float64.parse_hexfloat("0x1.ffffffffffffe0p-1023").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x1.ffffffffffffe1p-1023").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x1.ffffffffffffefp-1023").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x1.fffffffffffff0p-1023").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x1.fffffffffffff1p-1023").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x1.fffffffffffff7p-1023").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x1.fffffffffffff8p-1023").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x1.fffffffffffff9p-1023").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x1.ffffffffffffffp-1023").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x2.00000000000000p-1023").should eq(Float64::MIN_POSITIVE) } + + it { Float64.parse_hexfloat("0x3.ffffffffffffc0p-1024").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x3.ffffffffffffc1p-1024").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x3.ffffffffffffdfp-1024").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x3.ffffffffffffe0p-1024").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x3.ffffffffffffe1p-1024").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x3.ffffffffffffefp-1024").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x3.fffffffffffff0p-1024").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x3.fffffffffffff1p-1024").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x3.ffffffffffffffp-1024").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x4.00000000000000p-1024").should eq(Float64::MIN_POSITIVE) } + + it { Float64.parse_hexfloat("0x7.ffffffffffff80p-1025").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x7.ffffffffffff81p-1025").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x7.ffffffffffffbfp-1025").should eq(Float64::MIN_POSITIVE.prev_float) } + it { Float64.parse_hexfloat("0x7.ffffffffffffc0p-1025").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x7.ffffffffffffc1p-1025").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x7.ffffffffffffdfp-1025").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x7.ffffffffffffe0p-1025").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x7.ffffffffffffe1p-1025").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x7.ffffffffffffffp-1025").should eq(Float64::MIN_POSITIVE) } + it { Float64.parse_hexfloat("0x8.00000000000000p-1025").should eq(Float64::MIN_POSITIVE) } + + it { Float64.parse_hexfloat("0x1.fffffffffffffp+1023").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x2.0000000000000p+1023").should eq(Float64::INFINITY) } + + it { Float64.parse_hexfloat("0x3.ffffffffffffep+1022").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x3.fffffffffffffp+1022").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x4.0000000000000p+1022").should eq(Float64::INFINITY) } + + it { Float64.parse_hexfloat("0x7.ffffffffffffcp+1021").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x7.ffffffffffffdp+1021").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x7.ffffffffffffep+1021").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x7.fffffffffffffp+1021").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x8.0000000000000p+1021").should eq(Float64::INFINITY) } + + it { Float64.parse_hexfloat("0x0.fffffffffffff8p+1024").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x0.fffffffffffff9p+1024").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x0.fffffffffffffbp+1024").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x0.fffffffffffffcp+1024").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x0.fffffffffffffdp+1024").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x0.ffffffffffffffp+1024").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x1.00000000000000p+1024").should eq(Float64::INFINITY) } + + it { Float64.parse_hexfloat("0x1.fffffffffffff0p+1023").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x1.fffffffffffff1p+1023").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x1.fffffffffffff7p+1023").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x1.fffffffffffff8p+1023").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x1.fffffffffffff9p+1023").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x1.ffffffffffffffp+1023").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x2.00000000000000p+1023").should eq(Float64::INFINITY) } + + it { Float64.parse_hexfloat("0x3.ffffffffffffe0p+1022").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x3.ffffffffffffe1p+1022").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x3.ffffffffffffefp+1022").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x3.fffffffffffff0p+1022").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x3.fffffffffffff1p+1022").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x3.ffffffffffffffp+1022").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x4.00000000000000p+1022").should eq(Float64::INFINITY) } + + it { Float64.parse_hexfloat("0x7.ffffffffffffc0p+1021").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x7.ffffffffffffc1p+1021").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x7.ffffffffffffdfp+1021").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x7.ffffffffffffe0p+1021").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x7.ffffffffffffe1p+1021").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x7.ffffffffffffffp+1021").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x8.00000000000000p+1021").should eq(Float64::INFINITY) } + + it { Float64.parse_hexfloat("0x0.fffffffffffff80p+1024").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x0.fffffffffffff81p+1024").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x0.fffffffffffffbfp+1024").should eq(Float64::MAX) } + it { Float64.parse_hexfloat("0x0.fffffffffffffc0p+1024").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x0.fffffffffffffc1p+1024").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x0.fffffffffffffffp+1024").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("0x1.000000000000000p+1024").should eq(Float64::INFINITY) } + end + + describe "special cases" do + it { Float64.parse_hexfloat("-0x0p+0").to_s.should eq("-0.0") } # sign bit must be negative + + it { Float64.parse_hexfloat("inf").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("INF").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("infinity").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("INFINITY").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("+Infinity").should eq(Float64::INFINITY) } + it { Float64.parse_hexfloat("-iNF").should eq(-Float64::INFINITY) } + + it { Float64.parse_hexfloat("nan").nan?.should be_true } + it { Float64.parse_hexfloat("NAN").nan?.should be_true } + it { Float64.parse_hexfloat("+NaN").nan?.should be_true } + it { Float64.parse_hexfloat("-nAn").nan?.should be_true } + end + + describe "invalid hexfloats" do + it { assert_parse_error Float64, "", "expected '0'" } + it { assert_parse_error Float64, " ", "expected '0'" } + it { assert_parse_error Float64, "1", "expected '0'" } + it { assert_parse_error Float64, "0", "expected 'x' or 'X'" } + it { assert_parse_error Float64, "01", "expected 'x' or 'X'" } + it { assert_parse_error Float64, "0x", "expected at least one digit" } + it { assert_parse_error Float64, "0x.", "expected at least one digit" } + it { assert_parse_error Float64, "0xp", "expected at least one digit" } + it { assert_parse_error Float64, "0x.p", "expected at least one digit" } + it { assert_parse_error Float64, "0x1", "expected 'p' or 'P'" } + it { assert_parse_error Float64, "0x1.", "expected 'p' or 'P'" } + it { assert_parse_error Float64, "0x1.1", "expected 'p' or 'P'" } + it { assert_parse_error Float64, "0x.1", "expected 'p' or 'P'" } + it { assert_parse_error Float64, "0x1p", "empty exponent" } + it { assert_parse_error Float64, "0x1p+", "empty exponent" } + it { assert_parse_error Float64, "0x1p-", "empty exponent" } + it { assert_parse_error Float64, "0x1p2147483648", "exponent overflow" } + it { assert_parse_error Float64, "0x1p2147483650", "exponent overflow" } + it { assert_parse_error Float64, "0x1p+2147483648", "exponent overflow" } + it { assert_parse_error Float64, "0x1p+2147483650", "exponent overflow" } + it { assert_parse_error Float64, "0x1p-2147483648", "exponent overflow" } + it { assert_parse_error Float64, "0x1p-2147483650", "exponent overflow" } + it { assert_parse_error Float64, "0x1p0 ", "trailing characters" } + it { assert_parse_error Float64, "0x1p0_f32", "trailing characters" } + it { assert_parse_error Float64, "0x1p0_f64", "trailing characters" } + it { assert_parse_error Float64, "0x1p0f", "trailing characters" } + it { assert_parse_error Float64, "NaN ", "expected '0'" } + it { assert_parse_error Float64, "- Infinity", "expected '0'" } + end + end + + describe "#to_hexfloat" do + describe "special cases" do + it { assert_to_s 0.0, "0x0p+0" } + it { assert_to_s -0.0, "-0x0p+0" } + it { assert_to_s Float64::INFINITY, "Infinity" } + it { assert_to_s -Float64::INFINITY, "-Infinity" } + it { assert_to_s Float64::NAN, "NaN" } + it { assert_to_s 1.447509765625, "0x1.729p+0" } + it { assert_to_s -1.447509765625, "-0x1.729p+0" } + end + + describe "corner cases" do + it { assert_to_s 1.447265625, "0x1.728p+0" } # instead of "2.e5p-1" + it { assert_to_s Float64::MIN_SUBNORMAL, "0x0.0000000000001p-1022" } # instead of "1p-1074" + it { assert_to_s Float64::MIN_POSITIVE.prev_float, "0x0.fffffffffffffp-1022" } # max subnormal + it { assert_to_s Float64::MIN_POSITIVE, "0x1p-1022" } # min normal + it { assert_to_s Float64::MAX, "0x1.fffffffffffffp+1023" } # max normal + end + + describe "exponents" do + it { assert_to_s 1.8227805048890994e-304, "0x1p-1009" } + it { assert_to_s 1.8665272370064378e-301, "0x1p-999" } + it { assert_to_s 1.5777218104420236e-30, "0x1p-99" } + it { assert_to_s 0.001953125, "0x1p-9" } + it { assert_to_s 1.0, "0x1p+0" } + it { assert_to_s 512.0, "0x1p+9" } + it { assert_to_s 6.338253001141147e+29, "0x1p+99" } + it { assert_to_s 5.357543035931337e+300, "0x1p+999" } + it { assert_to_s 5.486124068793689e+303, "0x1p+1009" } + end + + describe "hexits" do + it { assert_to_s 1.0044444443192333, "0x1.01234567p+0" } + it { assert_to_s 1.5377777775283903, "0x1.89abcdefp+0" } + end + + describe "trimming" do + it { assert_to_s 1.0000000000000022, "0x1.000000000000ap+0" } + it { assert_to_s 1.0000000000000355, "0x1.00000000000ap+0" } + it { assert_to_s 1.0000000000005684, "0x1.0000000000ap+0" } + it { assert_to_s 1.000000000009095, "0x1.000000000ap+0" } + it { assert_to_s 1.0000000001455192, "0x1.00000000ap+0" } + it { assert_to_s 1.0000000023283064, "0x1.0000000ap+0" } + it { assert_to_s 1.000000037252903, "0x1.000000ap+0" } + it { assert_to_s 1.0000005960464478, "0x1.00000ap+0" } + it { assert_to_s 1.000009536743164, "0x1.0000ap+0" } + it { assert_to_s 1.000152587890625, "0x1.000ap+0" } + it { assert_to_s 1.00244140625, "0x1.00ap+0" } + it { assert_to_s 1.0390625, "0x1.0ap+0" } + it { assert_to_s 1.625, "0x1.ap+0" } + it { assert_to_s 1.0, "0x1p+0" } + end + end +end + +describe Float32 do + describe ".parse_hexfloat" do + it { Float32.parse_hexfloat("0x123p+0").should eq(291_f32) } + it { Float32.parse_hexfloat("0x123.0p+0").should eq(291_f32) } + it { Float32.parse_hexfloat("0x123p0").should eq(291_f32) } + + it { Float32.parse_hexfloat("0x123.p0").should eq(291_f32) } + it { Float32.parse_hexfloat("0x.123p12").should eq(291_f32) } + it { Float32.parse_hexfloat("0x123.456p7").should eq(37282.6875_f32) } + + it { Float32.parse_hexfloat("+0x123p+0").should eq(291_f32) } + it { Float32.parse_hexfloat("-0x123p-0").should eq(-291_f32) } + + it { Float32.parse_hexfloat("0XABCDEFP+0").should eq(11259375_f32) } + + it { Float32.parse_hexfloat("0x1.a0000400p+0").should eq(1.6250002_f32) } # exact + it { Float32.parse_hexfloat("0x1.a0000401p+0").should eq(1.6250002_f32) } # below midpoint, round down + it { Float32.parse_hexfloat("0x1.a0000500p+0").should eq(1.6250002_f32) } # midpoint, round down to even + it { Float32.parse_hexfloat("0x1.a0000501p+0").should eq(1.6250004_f32) } # above midpoint, round up + it { Float32.parse_hexfloat("0x1.a0000600p+0").should eq(1.6250004_f32) } # exact + it { Float32.parse_hexfloat("0x1.a0000601p+0").should eq(1.6250004_f32) } # below midpoint, round down + it { Float32.parse_hexfloat("0x1.a0000700p+0").should eq(1.6250005_f32) } # midpoint, round up to even + it { Float32.parse_hexfloat("0x1.a0000701p+0").should eq(1.6250005_f32) } # above midpoint, round up + + it { Float32.parse_hexfloat("0x1.0000040p+0").should eq(1.0000002_f32) } # exact + it { Float32.parse_hexfloat("0x1.0000041p+0").should eq(1.0000002_f32) } # below midpoint, round down + it { Float32.parse_hexfloat("0x1.0000050p+0").should eq(1.0000002_f32) } # midpoint, round down to even + it { Float32.parse_hexfloat("0x1.0000051p+0").should eq(1.0000004_f32) } # above midpoint, round up + it { Float32.parse_hexfloat("0x1.0000060p+0").should eq(1.0000004_f32) } # exact + it { Float32.parse_hexfloat("0x1.0000061p+0").should eq(1.0000004_f32) } # below midpoint, round down + it { Float32.parse_hexfloat("0x1.0000070p+0").should eq(1.0000005_f32) } # midpoint, round up to even + it { Float32.parse_hexfloat("0x1.0000071p+0").should eq(1.0000005_f32) } # above midpoint, round up + + it { Float32.parse_hexfloat("0x1.a0000400000000000000000p+0").should eq(1.6250002_f32) } # exact + it { Float32.parse_hexfloat("0x1.a0000400000000000000001p+0").should eq(1.6250002_f32) } # below midpoint, round down + it { Float32.parse_hexfloat("0x1.a0000500000000000000000p+0").should eq(1.6250002_f32) } # midpoint, round down to even + it { Float32.parse_hexfloat("0x1.a0000500000000000000001p+0").should eq(1.6250004_f32) } # above midpoint, round up + it { Float32.parse_hexfloat("0x1.a0000600000000000000000p+0").should eq(1.6250004_f32) } # exact + it { Float32.parse_hexfloat("0x1.a0000600000000000000001p+0").should eq(1.6250004_f32) } # below midpoint, round down + it { Float32.parse_hexfloat("0x1.a0000700000000000000000p+0").should eq(1.6250005_f32) } # midpoint, round up to even + it { Float32.parse_hexfloat("0x1.a0000700000000000000001p+0").should eq(1.6250005_f32) } # above midpoint, round up + + it { Float32.parse_hexfloat("0x1a0000400000000000p+0").should eq(4.796154e+20_f32) } # exact + it { Float32.parse_hexfloat("0x1a0000401000000000p+0").should eq(4.796154e+20_f32) } # below midpoint, round down + it { Float32.parse_hexfloat("0x1a0000500000000000p+0").should eq(4.796154e+20_f32) } # midpoint, round down to even + it { Float32.parse_hexfloat("0x1a0000501000000000p+0").should eq(4.7961545e+20_f32) } # above midpoint, round up + it { Float32.parse_hexfloat("0x1a0000600000000000p+0").should eq(4.7961545e+20_f32) } # exact + it { Float32.parse_hexfloat("0x1a0000601000000000p+0").should eq(4.7961545e+20_f32) } # below midpoint, round down + it { Float32.parse_hexfloat("0x1a0000700000000000p+0").should eq(4.796155e+20_f32) } # midpoint, round up to even + it { Float32.parse_hexfloat("0x1a0000701000000000p+0").should eq(4.796155e+20_f32) } # above midpoint, round up + + it { Float32.parse_hexfloat("0x.00000000000000001a0000400p+0").should eq(5.505715e-21_f32) } # exact + it { Float32.parse_hexfloat("0x.00000000000000001a0000401p+0").should eq(5.505715e-21_f32) } # below midpoint, round down + it { Float32.parse_hexfloat("0x.00000000000000001a0000500p+0").should eq(5.505715e-21_f32) } # midpoint, round down to even + it { Float32.parse_hexfloat("0x.00000000000000001a0000501p+0").should eq(5.5057154e-21_f32) } # above midpoint, round up + it { Float32.parse_hexfloat("0x.00000000000000001a0000600p+0").should eq(5.5057154e-21_f32) } # exact + it { Float32.parse_hexfloat("0x.00000000000000001a0000601p+0").should eq(5.5057154e-21_f32) } # below midpoint, round down + it { Float32.parse_hexfloat("0x.00000000000000001a0000700p+0").should eq(5.5057158e-21_f32) } # midpoint, round up to even + it { Float32.parse_hexfloat("0x.00000000000000001a0000701p+0").should eq(5.5057158e-21_f32) } # above midpoint, round up + + describe "values close to zero" do + it { Float32.parse_hexfloat("0x0.7p-149").should eq(Float32.zero) } + it { Float32.parse_hexfloat("0x0.8p-149").should eq(Float32.zero) } + it { Float32.parse_hexfloat("0x0.9p-149").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x0.fp-149").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x1.0p-149").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x1.1p-149").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x1.7p-149").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x1.8p-149").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0x1.9p-149").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0x1.fp-149").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0x2.0p-149").should eq(Float32::MIN_SUBNORMAL * 2) } + + it { Float32.parse_hexfloat("0x0.fp-150").should eq(Float32.zero) } + it { Float32.parse_hexfloat("0x1.0p-150").should eq(Float32.zero) } + it { Float32.parse_hexfloat("0x1.1p-150").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x1.fp-150").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x2.0p-150").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x2.1p-150").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x2.fp-150").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x3.0p-150").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0x3.1p-150").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0x3.fp-150").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0x4.0p-150").should eq(Float32::MIN_SUBNORMAL * 2) } + + it { Float32.parse_hexfloat("0x1.fp-151").should eq(Float32.zero) } + it { Float32.parse_hexfloat("0x2.0p-151").should eq(Float32.zero) } + it { Float32.parse_hexfloat("0x2.1p-151").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x3.fp-151").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x4.0p-151").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x4.1p-151").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x5.fp-151").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x6.0p-151").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0x6.1p-151").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0x7.fp-151").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0x8.0p-151").should eq(Float32::MIN_SUBNORMAL * 2) } + + it { Float32.parse_hexfloat("0x3.fp-152").should eq(Float32.zero) } + it { Float32.parse_hexfloat("0x4.0p-152").should eq(Float32.zero) } + it { Float32.parse_hexfloat("0x4.1p-152").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x7.fp-152").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x8.0p-152").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0x8.1p-152").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0xb.fp-152").should eq(Float32::MIN_SUBNORMAL) } + it { Float32.parse_hexfloat("0xc.0p-152").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0xc.1p-152").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0xf.fp-152").should eq(Float32::MIN_SUBNORMAL * 2) } + it { Float32.parse_hexfloat("0x10.0p-152").should eq(Float32::MIN_SUBNORMAL * 2) } + end + + describe "values close to MIN_POSITIVE and MAX" do + it { Float32.parse_hexfloat("0x7.fffffp-129").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x8.00000p-129").should eq(Float32::MIN_POSITIVE) } + + it { Float32.parse_hexfloat("0x0.fffffep-126").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x0.ffffffp-126").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x1.000000p-126").should eq(Float32::MIN_POSITIVE) } + + it { Float32.parse_hexfloat("0x1.fffffcp-127").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x1.fffffdp-127").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x1.fffffep-127").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x1.ffffffp-127").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x2.000000p-127").should eq(Float32::MIN_POSITIVE) } + + it { Float32.parse_hexfloat("0x3.fffff8p-128").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x3.fffff9p-128").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x3.fffffbp-128").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x3.fffffcp-128").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x3.fffffdp-128").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x3.fffffep-128").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x3.ffffffp-128").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x4.000000p-128").should eq(Float32::MIN_POSITIVE) } + + it { Float32.parse_hexfloat("0x7.fffff0p-129").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x7.fffff1p-129").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x7.fffff7p-129").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x7.fffff8p-129").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x7.fffff9p-129").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x7.fffffbp-129").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x7.fffffcp-129").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x7.fffffdp-129").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x7.ffffffp-129").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x8.000000p-129").should eq(Float32::MIN_POSITIVE) } + + it { Float32.parse_hexfloat("0x0.fffffe0p-126").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x0.fffffe1p-126").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x0.fffffefp-126").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x0.ffffff0p-126").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x0.ffffff1p-126").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x0.ffffff7p-126").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x0.ffffff8p-126").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x0.ffffff9p-126").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x0.fffffffp-126").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x1.0000000p-126").should eq(Float32::MIN_POSITIVE) } + + it { Float32.parse_hexfloat("0x1.fffffc0p-127").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x1.fffffc1p-127").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x1.fffffdfp-127").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x1.fffffe0p-127").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x1.fffffe1p-127").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x1.fffffefp-127").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x1.ffffff0p-127").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x1.ffffff1p-127").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x1.fffffffp-127").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x2.0000000p-127").should eq(Float32::MIN_POSITIVE) } + + it { Float32.parse_hexfloat("0x3.fffff80p-128").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x3.fffff81p-128").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x3.fffffbfp-128").should eq(Float32::MIN_POSITIVE.prev_float) } + it { Float32.parse_hexfloat("0x3.fffffc0p-128").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x3.fffffc1p-128").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x3.fffffdfp-128").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x3.fffffe0p-128").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x3.fffffe1p-128").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x3.fffffffp-128").should eq(Float32::MIN_POSITIVE) } + it { Float32.parse_hexfloat("0x4.0000000p-128").should eq(Float32::MIN_POSITIVE) } + + it { Float32.parse_hexfloat("0x0.ffffffp+128").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x1.000000p+128").should eq(Float32::INFINITY) } + + it { Float32.parse_hexfloat("0x1.fffffep+127").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x1.ffffffp+127").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x2.000000p+127").should eq(Float32::INFINITY) } + + it { Float32.parse_hexfloat("0x3.fffffcp+126").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x3.fffffdp+126").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x3.fffffep+126").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x3.ffffffp+126").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x4.000000p+126").should eq(Float32::INFINITY) } + + it { Float32.parse_hexfloat("0x7.fffff8p+125").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x7.fffff9p+125").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x7.fffffbp+125").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x7.fffffcp+125").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x7.fffffdp+125").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x7.ffffffp+125").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x8.000000p+125").should eq(Float32::INFINITY) } + + it { Float32.parse_hexfloat("0x0.ffffff0p+128").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x0.ffffff1p+128").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x0.ffffff7p+128").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x0.ffffff8p+128").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x0.ffffff9p+128").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x0.fffffffp+128").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x1.0000000p+128").should eq(Float32::INFINITY) } + + it { Float32.parse_hexfloat("0x1.fffffe0p+127").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x1.fffffe1p+127").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x1.fffffefp+127").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x1.ffffff0p+127").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x1.ffffff1p+127").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x1.fffffffp+127").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x2.0000000p+127").should eq(Float32::INFINITY) } + + it { Float32.parse_hexfloat("0x3.fffffc0p+126").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x3.fffffc1p+126").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x3.fffffdfp+126").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x3.fffffe0p+126").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x3.fffffe1p+126").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x3.fffffffp+126").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x4.0000000p+126").should eq(Float32::INFINITY) } + + it { Float32.parse_hexfloat("0x7.fffff80p+125").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x7.fffff81p+125").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x7.fffffbfp+125").should eq(Float32::MAX) } + it { Float32.parse_hexfloat("0x7.fffffc0p+125").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x7.fffffc1p+125").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x7.fffffffp+125").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("0x8.0000000p+125").should eq(Float32::INFINITY) } + end + + describe "special cases" do + it { Float32.parse_hexfloat("-0x0p+0").to_s.should eq("-0.0") } # sign bit must be negative + + it { Float32.parse_hexfloat("inf").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("INF").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("infinity").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("INFINITY").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("+Infinity").should eq(Float32::INFINITY) } + it { Float32.parse_hexfloat("-iNF").should eq(-Float32::INFINITY) } + + it { Float32.parse_hexfloat("nan").nan?.should be_true } + it { Float32.parse_hexfloat("NAN").nan?.should be_true } + it { Float32.parse_hexfloat("+NaN").nan?.should be_true } + it { Float32.parse_hexfloat("-nAn").nan?.should be_true } + end + + describe "invalid hexfloats" do + it { assert_parse_error Float32, "", "expected '0'" } + it { assert_parse_error Float32, " ", "expected '0'" } + it { assert_parse_error Float32, "1", "expected '0'" } + it { assert_parse_error Float32, "0", "expected 'x' or 'X'" } + it { assert_parse_error Float32, "01", "expected 'x' or 'X'" } + it { assert_parse_error Float32, "0x", "expected at least one digit" } + it { assert_parse_error Float32, "0x.", "expected at least one digit" } + it { assert_parse_error Float32, "0xp", "expected at least one digit" } + it { assert_parse_error Float32, "0x.p", "expected at least one digit" } + it { assert_parse_error Float32, "0x1", "expected 'p' or 'P'" } + it { assert_parse_error Float32, "0x1.", "expected 'p' or 'P'" } + it { assert_parse_error Float32, "0x1.1", "expected 'p' or 'P'" } + it { assert_parse_error Float32, "0x.1", "expected 'p' or 'P'" } + it { assert_parse_error Float32, "0x1p", "empty exponent" } + it { assert_parse_error Float32, "0x1p+", "empty exponent" } + it { assert_parse_error Float32, "0x1p-", "empty exponent" } + it { assert_parse_error Float32, "0x1p2147483648", "exponent overflow" } + it { assert_parse_error Float32, "0x1p2147483650", "exponent overflow" } + it { assert_parse_error Float32, "0x1p+2147483648", "exponent overflow" } + it { assert_parse_error Float32, "0x1p+2147483650", "exponent overflow" } + it { assert_parse_error Float32, "0x1p-2147483648", "exponent overflow" } + it { assert_parse_error Float32, "0x1p-2147483650", "exponent overflow" } + it { assert_parse_error Float32, "0x1p0 ", "trailing characters" } + it { assert_parse_error Float32, "0x1p0_f32", "trailing characters" } + it { assert_parse_error Float32, "0x1p0_f64", "trailing characters" } + it { assert_parse_error Float32, "0x1p0f", "trailing characters" } + it { assert_parse_error Float32, "NaN ", "expected '0'" } + it { assert_parse_error Float32, "- Infinity", "expected '0'" } + end + end + + describe "#to_hexfloat" do + describe "special cases" do + it { assert_to_s 0.0_f32, "0x0p+0" } + it { assert_to_s -0.0_f32, "-0x0p+0" } + it { assert_to_s Float32::INFINITY, "Infinity" } + it { assert_to_s -Float32::INFINITY, "-Infinity" } + it { assert_to_s Float32::NAN, "NaN" } + it { assert_to_s 1.4475098_f32, "0x1.729p+0" } + it { assert_to_s -1.4475098_f32, "-0x1.729p+0" } + end + + describe "corner cases" do + it { assert_to_s 1.4472656_f32, "0x1.728p+0" } # instead of "2.e5p-1" + it { assert_to_s Float32::MIN_SUBNORMAL, "0x0.000002p-126" } # instead of "1p-1074" + it { assert_to_s Float32::MIN_POSITIVE.prev_float, "0x0.fffffep-126" } # max subnormal + it { assert_to_s Float32::MIN_POSITIVE, "0x1p-126" } # min normal + it { assert_to_s Float32::MAX, "0x1.fffffep+127" } # max normal + end + + describe "exponents" do + it { assert_to_s 1.540744e-33_f32, "0x1p-109" } + it { assert_to_s 1.5777218e-30_f32, "0x1p-99" } + it { assert_to_s 0.001953125_f32, "0x1p-9" } + it { assert_to_s 1.0_f32, "0x1p+0" } + it { assert_to_s 512.0_f32, "0x1p+9" } + it { assert_to_s 6.338253e+29_f32, "0x1p+99" } + it { assert_to_s 6.490371e+32_f32, "0x1p+109" } + end + + describe "hexits" do + it { assert_to_s 1.0044403_f32, "0x1.0123p+0" } + it { assert_to_s 1.2711029_f32, "0x1.4567p+0" } + it { assert_to_s 1.5377655_f32, "0x1.89abp+0" } + it { assert_to_s 1.8044281_f32, "0x1.cdefp+0" } + end + + describe "trimming" do + it { assert_to_s 1.0000006_f32, "0x1.00000ap+0" } + it { assert_to_s 1.0000095_f32, "0x1.0000ap+0" } + it { assert_to_s 1.0001526_f32, "0x1.000ap+0" } + it { assert_to_s 1.0390625_f32, "0x1.0ap+0" } + it { assert_to_s 1.0024414_f32, "0x1.00ap+0" } + it { assert_to_s 1.625_f32, "0x1.ap+0" } + it { assert_to_s 1.0_f32, "0x1p+0" } + end + end +end diff --git a/spec/std/float_printer_spec.cr b/spec/std/float_printer/shortest_spec.cr similarity index 99% rename from spec/std/float_printer_spec.cr rename to spec/std/float_printer/shortest_spec.cr index b5c72d999868..b012e3da19ac 100644 --- a/spec/std/float_printer_spec.cr +++ b/spec/std/float_printer/shortest_spec.cr @@ -34,9 +34,9 @@ # * https://github.com/microsoft/STL/tree/main/tests/std/tests/P0067R5_charconv require "spec" -require "./spec_helper" +require "../spec_helper" require "spec/helpers/string" -require "../support/number" +require "../../support/number" # Tests that `v.to_s` is the same as the *v* literal is written in the source # code, except possibly omitting the `_f32` suffix for `Float32` literals. diff --git a/spec/support/number.cr b/spec/support/number.cr index a01687384169..4ec22f9dcf87 100644 --- a/spec/support/number.cr +++ b/spec/support/number.cr @@ -83,80 +83,14 @@ end # TODO test zero? comparisons # TODO test <=> comparisons between types -private module HexFloatConverter(F, U) - # Converts `str`, a hexadecimal floating-point literal, to an `F`. Truncates - # all unused bits in the mantissa. - def self.to_f(str : String) : F - m = str.match(/^(-?)0x([0-9A-Fa-f]+)(?:\.([0-9A-Fa-f]+))?p([+-]?)([0-9]+)#{"_?f32" if F == Float32}$/).not_nil! - - total_bits = F == Float32 ? 32 : 64 - mantissa_bits = F::MANT_DIGITS - 1 - exponent_bias = F::MAX_EXP - 1 - - is_negative = m[1] == "-" - int_part = U.new(m[2], base: 16) - frac = m[3]?.try(&.[0, (mantissa_bits + 3) // 4]) || "0" - frac_part = U.new(frac, base: 16) << (mantissa_bits - frac.size * 4) - exponent = m[5].to_i * (m[4] == "-" ? -1 : 1) - - if int_part > 1 - last_bit = U.zero - while int_part > 1 - last_bit = frac_part & 1 - frac_part |= (U.new!(1) << mantissa_bits) if int_part & 1 != 0 - frac_part >>= 1 - int_part >>= 1 - exponent += 1 - end - if last_bit != 0 - frac_part += 1 - if frac_part >= U.new!(1) << mantissa_bits - frac_part = U.new!(0) - int_part += 1 - end - end - elsif int_part == 0 - while int_part == 0 - frac_part <<= 1 - if frac_part >= U.new!(1) << mantissa_bits - frac_part &= ~(U::MAX << mantissa_bits) - int_part += 1 - end - exponent -= 1 - end - end - - exponent += exponent_bias - if exponent >= exponent_bias * 2 + 1 - F::INFINITY * (is_negative ? -1 : 1) - elsif exponent < -mantissa_bits - F.zero * (is_negative ? -1 : 1) - elsif exponent <= 0 - f = (frac_part >> (1 - exponent)) | (int_part << (mantissa_bits - 1 + exponent)) - f |= U.new!(1) << (total_bits - 1) if is_negative - f.unsafe_as(F) - else - f = frac_part - f |= U.new!(exponent) << mantissa_bits - f |= U.new!(1) << (total_bits - 1) if is_negative - f.unsafe_as(F) - end - end -end - -def hexfloat_f64(str : String) : Float64 - HexFloatConverter(Float64, UInt64).to_f(str) -end - -def hexfloat_f32(str : String) : Float32 - HexFloatConverter(Float32, UInt32).to_f(str) -end - +# Calls either `Float64.parse_hexfloat` or `Float32.parse_hexfloat`. The default +# is `Float64` unless *str* ends with `_f32`, in which case that suffix is +# stripped and `Float32` is chosen. macro hexfloat(str) {% raise "`str` must be a StringLiteral, not #{str.class_name}" unless str.is_a?(StringLiteral) %} {% if str.ends_with?("_f32") %} - hexfloat_f32({{ str }}) + ::Float32.parse_hexfloat({{ str[0...-4] }}) {% else %} - hexfloat_f64({{ str }}) + ::Float64.parse_hexfloat({{ str }}) {% end %} end diff --git a/src/float.cr b/src/float.cr index a4abcf5abdf8..d21ebe6f9a8d 100644 --- a/src/float.cr +++ b/src/float.cr @@ -154,6 +154,48 @@ struct Float32 value.to_f32! end + # Returns a `Float32` by parsing *str* as a hexadecimal-significand string, or + # `nil` if parsing fails. + # + # The string format is defined in section 5.12.3 of IEEE 754-2008, and is the + # same as the `%a` specifier for `sprintf`. Unlike e.g. `String#to_f`, + # whitespace and underscores are not allowed. Non-finite values are also + # recognized. + # + # ``` + # Float32.parse_hexfloat?("0x123.456p7") # => 37282.6875_f32 + # Float32.parse_hexfloat?("0x1.fffffep+127") # => Float32::MAX + # Float32.parse_hexfloat?("-inf") # => -Float32::INFINITY + # Float32.parse_hexfloat?("0x1") # => nil + # Float32.parse_hexfloat?("a.bp+3") # => nil + # ``` + def self.parse_hexfloat?(str : String) : self? + Float::Printer::Hexfloat(self, UInt32).to_f(str) { nil } + end + + # Returns a `Float32` by parsing *str* as a hexadecimal-significand string. + # + # The string format is defined in section 5.12.3 of IEEE 754-2008, and is the + # same as the `%a` specifier for `sprintf`. Unlike e.g. `String#to_f`, + # whitespace and underscores are not allowed. Non-finite values are also + # recognized. + # + # Raises `ArgumentError` if *str* is not a valid hexadecimal-significand + # string. + # + # ``` + # Float32.parse_hexfloat("0x123.456p7") # => 37282.6875_f32 + # Float32.parse_hexfloat("0x1.fffffep+127") # => Float32::MAX + # Float32.parse_hexfloat("-inf") # => -Float32::INFINITY + # Float32.parse_hexfloat("0x1") # Invalid hexfloat: expected 'p' or 'P' (ArgumentError) + # Float32.parse_hexfloat("a.bp+3") # Invalid hexfloat: expected '0' (ArgumentError) + # ``` + def self.parse_hexfloat(str : String) : self + Float::Printer::Hexfloat(self, UInt32).to_f(str) do |err| + raise ArgumentError.new("Invalid hexfloat: #{err}") + end + end + Number.expand_div [Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128], Float32 Number.expand_div [Float64], Float64 @@ -214,12 +256,36 @@ struct Float32 def to_s : String String.build(22) do |buffer| - Printer.print(self, buffer) + Printer.shortest(self, buffer) end end def to_s(io : IO) : Nil - Printer.print(self, io) + Printer.shortest(self, io) + end + + # Returns the hexadecimal-significand representation of `self`. + # + # The string format is defined in section 5.12.3 of IEEE 754-2008, and is the + # same as the `%a` specifier for `sprintf`. The integral part of the returned + # string is `0` if `self` is subnormal, otherwise `1`. The fractional part + # contains only significant digits. + # + # ``` + # 1234.0625_f32.to_hexfloat # => "0x1.3484p+10" + # Float32::MAX.to_hexfloat # => "0x1.fffffep+127" + # Float32::MIN_SUBNORMAL.to_hexfloat # => "0x0.000002p-126" + # ``` + def to_hexfloat : String + # the longest `Float64` strings are of the form `-0x1.234567p-127` + String.build(16) do |buffer| + Printer.hexfloat(self, buffer) + end + end + + # Writes `self`'s hexadecimal-significand representation to the given *io*. + def to_hexfloat(io : IO) : Nil + Printer.hexfloat(self, io) end def clone @@ -276,6 +342,48 @@ struct Float64 value.to_f64! end + # Returns a `Float32` by parsing *str* as a hexadecimal-significand string, or + # `nil` if parsing fails. + # + # The string format is defined in section 5.12.3 of IEEE 754-2008, and is the + # same as the `%a` specifier for `sprintf`. Unlike e.g. `String#to_f`, + # whitespace and underscores are not allowed. Non-finite values are also + # recognized. + # + # ``` + # Float64.parse_hexfloat?("0x123.456p7") # => 37282.6875 + # Float64.parse_hexfloat?("0x1.fffffep+127") # => Float64::MAX + # Float64.parse_hexfloat?("-inf") # => -Float64::INFINITY + # Float64.parse_hexfloat?("0x1") # => nil + # Float64.parse_hexfloat?("a.bp+3") # => nil + # ``` + def self.parse_hexfloat?(str : String) : self? + Float::Printer::Hexfloat(self, UInt64).to_f(str) { nil } + end + + # Returns a `Float32` by parsing *str* as a hexadecimal-significand string. + # + # The string format is defined in section 5.12.3 of IEEE 754-2008, and is the + # same as the `%a` specifier for `sprintf`. Unlike e.g. `String#to_f`, + # whitespace and underscores are not allowed. Non-finite values are also + # recognized. + # + # Raises `ArgumentError` if *str* is not a valid hexadecimal-significand + # string. + # + # ``` + # Float64.parse_hexfloat("0x123.456p7") # => 37282.6875 + # Float64.parse_hexfloat("0x1.fffffffffffffp+1023") # => Float64::MAX + # Float64.parse_hexfloat("-inf") # => -Float64::INFINITY + # Float64.parse_hexfloat("0x1") # Invalid hexfloat: expected 'p' or 'P' (ArgumentError) + # Float64.parse_hexfloat("a.bp+3") # Invalid hexfloat: expected '0' (ArgumentError) + # ``` + def self.parse_hexfloat(str : String) : self + Float::Printer::Hexfloat(self, UInt64).to_f(str) do |err| + raise ArgumentError.new("Invalid hexfloat: #{err}") + end + end + Number.expand_div [Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128], Float64 Number.expand_div [Float32], Float64 @@ -338,12 +446,36 @@ struct Float64 def to_s : String # the longest `Float64` strings are of the form `-1.2345678901234567e+123` String.build(24) do |buffer| - Printer.print(self, buffer) + Printer.shortest(self, buffer) end end def to_s(io : IO) : Nil - Printer.print(self, io) + Printer.shortest(self, io) + end + + # Returns the hexadecimal-significand representation of `self`. + # + # The string format is defined in section 5.12.3 of IEEE 754-2008, and is the + # same as the `%a` specifier for `sprintf`. The integral part of the returned + # string is `0` if `self` is subnormal, otherwise `1`. The fractional part + # contains only significant digits. + # + # ``` + # 1234.0625.to_hexfloat # => "0x1.3484p+10" + # Float64::MAX.to_hexfloat # => "0x1.fffffffffffffp+1023" + # Float64::MIN_SUBNORMAL.to_hexfloat # => "0x0.0000000000001p-1022" + # ``` + def to_hexfloat : String + # the longest `Float64` strings are of the form `-0x1.23456789abcdep-1023` + String.build(24) do |buffer| + Printer.hexfloat(self, buffer) + end + end + + # Writes `self`'s hexadecimal-significand representation to the given *io*. + def to_hexfloat(io : IO) : Nil + Printer.hexfloat(self, io) end def clone diff --git a/src/float/printer.cr b/src/float/printer.cr index 2189e365a5fe..6552f74cdb4c 100644 --- a/src/float/printer.cr +++ b/src/float/printer.cr @@ -1,21 +1,111 @@ require "./printer/*" # :nodoc: -# -# `Float::Printer` is based on the [Dragonbox](https://github.com/jk-jeon/dragonbox) -# algorithm developed by Junekey Jeon around 2020-2021. module Float::Printer extend self + BUFFER_SIZE = 17 # maximum number of decimal digits required - # Converts `Float` *v* to a string representation and prints it onto *io*. + # Writes *v*'s shortest string representation to the given *io*. + # + # Based on the [Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm + # developed by Junekey Jeon around 2020-2021. # - # It is used by `Float64#to_s` and it is probably not necessary to use - # this directly. + # It is used by `Float::Primitive#to_s` and `Number#format`. It is probably + # not necessary to use this directly. # # *point_range* designates the boundaries of scientific notation which is used # for all values whose decimal point position is outside that range. - def print(v : Float64 | Float32, io : IO, *, point_range = -3..15) : Nil + def shortest(v : Float::Primitive, io : IO, *, point_range = -3..15) : Nil + check_finite_float(v, io) do |pos_v| + if pos_v.zero? + io << "0.0" + return + end + + significand, decimal_exponent = Dragonbox.to_decimal(pos_v) + + # generate `significand.to_s` in a reasonably fast manner + str = uninitialized UInt8[BUFFER_SIZE] + ptr = str.to_unsafe + BUFFER_SIZE + while significand > 0 + ptr -= 1 + ptr.value = 48_u8 &+ significand.unsafe_mod(10).to_u8! + significand = significand.unsafe_div(10) + end + + # remove trailing zeros + buffer = str.to_slice[ptr - str.to_unsafe..] + while buffer.size > 1 && buffer.unsafe_fetch(buffer.size - 1) === '0' + buffer = buffer[..-2] + decimal_exponent += 1 + end + length = buffer.size + + point = decimal_exponent + length + + exp = point + exp_mode = !point_range.includes?(point) + point = 1 if exp_mode + + # add leading zero + io << '0' if point < 1 + + i = 0 + + # add integer part digits + if decimal_exponent > 0 && !exp_mode + # whole number but not big enough to be exp form + io.write_string buffer.to_slice[i, length - i] + i = length + (point - length).times { io << '0' } + elsif i < point + io.write_string buffer.to_slice[i, point - i] + i = point + end + + io << '.' + + # add leading zeros after point + if point < 0 + (-point).times { io << '0' } + end + + # add fractional part digits + io.write_string buffer.to_slice[i, length - i] + + # print trailing 0 if whole number or exp notation of power of ten + if (decimal_exponent >= 0 && !exp_mode) || (exp != point && length == 1) + io << '0' + end + + # exp notation + if exp != point + io << 'e' + io << '+' if exp > 0 + (exp - 1).to_s(io) + end + end + end + + # Writes *v*'s hexadecimal-significand representation to the given *io*. + # + # Used by `Float::Primitive#to_hexfloat`. + def hexfloat(v : Float64, io : IO) : Nil + check_finite_float(v, io) do + Hexfloat(Float64, UInt64).to_s(io, v) + end + end + + # :ditto: + def hexfloat(v : Float32, io : IO) : Nil + check_finite_float(v, io) do + Hexfloat(Float32, UInt32).to_s(io, v) + end + end + + # If *v* is finite, yields its absolute value, otherwise writes *v* to *io*. + private def check_finite_float(v : Float::Primitive, io : IO, &) d = IEEE.to_uint(v) if IEEE.nan?(d) @@ -28,77 +118,10 @@ module Float::Printer v = -v end - if v == 0.0 - io << "0.0" - elsif IEEE.inf?(d) + if IEEE.inf?(d) io << "Infinity" else - internal(v, io, point_range) - end - end - - private def internal(v : Float64 | Float32, io : IO, point_range) - significand, decimal_exponent = Dragonbox.to_decimal(v) - - # generate `significand.to_s` in a reasonably fast manner - str = uninitialized UInt8[BUFFER_SIZE] - ptr = str.to_unsafe + BUFFER_SIZE - while significand > 0 - ptr -= 1 - ptr.value = 48_u8 &+ significand.unsafe_mod(10).to_u8! - significand = significand.unsafe_div(10) - end - - # remove trailing zeros - buffer = str.to_slice[ptr - str.to_unsafe..] - while buffer.size > 1 && buffer.unsafe_fetch(buffer.size - 1) === '0' - buffer = buffer[..-2] - decimal_exponent += 1 - end - length = buffer.size - - point = decimal_exponent + length - - exp = point - exp_mode = !point_range.includes?(point) - point = 1 if exp_mode - - # add leading zero - io << '0' if point < 1 - - i = 0 - - # add integer part digits - if decimal_exponent > 0 && !exp_mode - # whole number but not big enough to be exp form - io.write_string buffer.to_slice[i, length - i] - i = length - (point - length).times { io << '0' } - elsif i < point - io.write_string buffer.to_slice[i, point - i] - i = point - end - - io << '.' - - # add leading zeros after point - if point < 0 - (-point).times { io << '0' } - end - - # add fractional part digits - io.write_string buffer.to_slice[i, length - i] - - # print trailing 0 if whole number or exp notation of power of ten - if (decimal_exponent >= 0 && !exp_mode) || (exp != point && length == 1) - io << '0' - end - - # exp notation - if exp != point - io << 'e' - io << '+' if exp > 0 - (exp - 1).to_s(io) + yield v end end end diff --git a/src/float/printer/hexfloat.cr b/src/float/printer/hexfloat.cr new file mode 100644 index 000000000000..1081211141c0 --- /dev/null +++ b/src/float/printer/hexfloat.cr @@ -0,0 +1,255 @@ +module Float::Printer::Hexfloat(F, U) + # IEEE defines the following grammar: + # + # ``` + # sign = /[+−]/ + # digit = /[0123456789]/ + # hexDigit = /[0123456789abcdefABCDEF]/ + # hexExpIndicator = /[Pp]/ + # hexIndicator = /0[Xx]/ + # hexSignificand = /#{hexDigit}*\.#{hexDigit}+|#{hexDigit}+\.|#{hexDigit}+/ + # decExponent = /#{hexExpIndicator}#{sign}?#{digit}+/ + # hexSequence = /#{sign}?#{hexIndicator}#{hexSignificand}#{decExponent}/ + # = /[+−]?0[Xx](?:[0-9A-Fa-f]*\.[0-9A-Fa-f]+|[0-9A-Fa-f]+\.?)[Pp][+−]?[0-9]+/ + # ``` + def self.to_f(str : String, &) + ptr = str.to_unsafe + finish = ptr + str.bytesize + + # TODO: this portion is probably common with other float parsers too + ptr, negative = parse_sign(ptr, finish) + str_bytes = Slice.new(ptr, finish - ptr) + if eq_case_insensitive?(str_bytes, "inf") || eq_case_insensitive?(str_bytes, "infinity") + return F::INFINITY * (negative ? -1 : 1) + elsif eq_case_insensitive?(str_bytes, "nan") + return F::NAN + end + + check_ch '0' + check_ch 'x', 'X' + + mantissa = U.zero + mantissa_max = ~(U::MAX << (F::MANT_DIGITS + 1)) + trailing_nonzero = false + exp_shift = 0 + + has_int_digits = false + while true + ptr, digit = parse_hex(ptr, finish) + break unless digit + has_int_digits = true + + if mantissa != 0 + exp_shift += 4 + elsif digit != 0 + exp_shift = 8 - digit.leading_zeros_count + end + + mix_digit + end + + if ptr < finish && ptr.value === '.' + ptr += 1 + has_frac_digits = false + while true + ptr, digit = parse_hex(ptr, finish) + break unless digit + has_frac_digits = true + + if mantissa == 0 + exp_shift -= 4 + if digit != 0 + exp_shift += 8 - digit.leading_zeros_count + end + end + + mix_digit + end + end + + return yield "expected at least one digit" unless has_int_digits || has_frac_digits + + check_ch 'p', 'P' + + ptr, exp_negative = parse_sign(ptr, finish) + + exp_add = 0 + has_exp_digits = false + while true + ptr, digit = parse_dec(ptr, finish) + break unless digit + has_exp_digits = true + + return yield "exponent overflow" if exp_add > Int32::MAX // 10 + exp_add &*= 10 + return yield "exponent overflow" if exp_add > Int32::MAX &- digit + exp_add &+= digit + end + + return yield "empty exponent" unless has_exp_digits + return yield "trailing characters" unless ptr == finish + + return make_float(negative, 0, 0) if mantissa == 0 + + exp_shift += F::MAX_EXP - 2 + if exp_negative + exp_shift -= exp_add + else + exp_shift += exp_add + end + + if mantissa <= (mantissa_max >> 1) + mantissa <<= F::MANT_DIGITS - (sizeof(U) * 8 - mantissa.leading_zeros_count) + 1 + end + + if exp_shift <= 0 + trailing_nonzero ||= mantissa & ~(U::MAX << (1 - exp_shift)) != 0 + mantissa >>= 1 - exp_shift + round_up = (mantissa & 0b1) != 0 && ((mantissa & 0b10) != 0 || trailing_nonzero) + mantissa >>= 1 + mantissa &+= 1 if round_up + exp_shift = mantissa > (mantissa_max >> 2) ? 1 : 0 + elsif mantissa > (mantissa_max >> 1) + round_up = (mantissa & 0b1) != 0 && ((mantissa & 0b10) != 0 || trailing_nonzero) + mantissa >>= 1 + mantissa &+= 1 if round_up + exp_shift += 1 if mantissa > (mantissa_max >> 1) + end + + return make_float(negative, 0, F::MAX_EXP * 2 - 1) if exp_shift >= F::MAX_EXP * 2 - 1 + + make_float(negative, mantissa, exp_shift) + end + + private def self.make_float(negative, mantissa, exponent) : F + u = negative ? U.new!(1) << (sizeof(U) * 8 - 1) : U.zero + u |= mantissa & ~(U::MAX << (F::MANT_DIGITS - 1)) + u |= U.new!(exponent) << (F::MANT_DIGITS - 1) + + u.unsafe_as(F) + end + + private macro check_ch(*ch) + unless ptr < finish && ptr.value.unsafe_chr.in?({{ ch }}) + return yield "expected {{ ch.map(&.stringify).join(%( or )).id }}" + end + ptr += 1 + end + + # `/[+-]?/` + private def self.parse_sign(ptr, finish) + if ptr < finish + case ptr.value + when '+' + return {ptr + 1, false} + when '-' + return {ptr + 1, true} + end + end + + {ptr, false} + end + + # `/[0-9]?/` + private def self.parse_dec(ptr, finish) + if ptr < finish + case ch = ptr.value + when 0x30..0x39 + return {ptr + 1, ch &- 0x30} + end + end + + {ptr, nil} + end + + # `/[0-9A-Fa-f]?/` + private def self.parse_hex(ptr, finish) + if ptr < finish + case ch = ptr.value + when 0x30..0x39 + return {ptr + 1, ch &- 0x30} + when 0x41..0x46 + return {ptr + 1, ch &- 0x37} + when 0x61..0x66 + return {ptr + 1, ch &- 0x57} + end + end + + {ptr, nil} + end + + # Precondition: `str.each_char.all?(&.ascii_lowercase?)` + private def self.eq_case_insensitive?(bytes : Bytes, str : String) : Bool + return false unless bytes.size == str.bytesize + ptr = str.to_unsafe + + bytes.each_with_index do |b, i| + b |= 0x20 if 0x41 <= b <= 0x5A + return false unless b == ptr[i] + end + + true + end + + private macro mix_digit + if mantissa > (mantissa_max >> 1) + trailing_nonzero ||= digit != 0 + elsif mantissa > (mantissa_max >> 2) + # 00000000 000[.... ........ .......] + mantissa <<= 1 + mantissa |= digit >> 3 + trailing_nonzero ||= digit & 0b0111 != 0 + # 00000000 00[..... ........ ......]? ??? + elsif mantissa > (mantissa_max >> 3) + # 00000000 0000[... ........ .......] + mantissa <<= 2 + mantissa |= digit >> 2 + trailing_nonzero ||= digit & 0b0011 != 0 + # 00000000 00[..... ........ .....]?? ?? + elsif mantissa > (mantissa_max >> 4) + # 00000000 00000[.. ........ .......] + mantissa <<= 3 + mantissa |= digit >> 1 + trailing_nonzero ||= digit & 0b0001 != 0 + # 00000000 00[..... ........ ....]??? ? + else + mantissa <<= 4 + mantissa |= digit + end + end + + # sign and special values are handled in `Float::Printer.check_finite_float` + @[AlwaysInline] + def self.to_s(io : IO, num : F) : Nil + u = num.unsafe_as(U) + exponent = ((u >> (F::MANT_DIGITS - 1)) & (F::MAX_EXP * 2 - 1)).to_i + mantissa = u & ~(U::MAX << (F::MANT_DIGITS - 1)) + + if exponent < 1 + exponent += 1 + io << "0x0" + else + io << "0x1" + end + + if mantissa != 0 + io << '.' + while mantissa != 0 + digit = mantissa >> (F::MANT_DIGITS - 5) + digit.to_s(io, base: 16) + mantissa <<= 4 + mantissa &= ~(U::MAX << (F::MANT_DIGITS - 1)) + end + end + + if num == 0 + io << "p+0" + else + exponent -= F::MAX_EXP - 1 + + io << 'p' + io << (exponent >= 0 ? '+' : '-') + io << exponent.abs + end + end +end diff --git a/src/humanize.cr b/src/humanize.cr index 76f4fc3b2a12..16cff9d63174 100644 --- a/src/humanize.cr +++ b/src/humanize.cr @@ -41,7 +41,7 @@ struct Number else string = String.build do |io| # Make sure to avoid scientific notation of default Float#to_s - Float::Printer.print(number.abs, io, point_range: ..) + Float::Printer.shortest(number.abs, io, point_range: ..) end _, _, decimals = string.partition(".") integer, _, _ = ("%f" % number.abs).partition(".") From 19ffbc75e18f0019cb95136899a23ef110e197dd Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 7 Dec 2023 20:04:05 +0800 Subject: [PATCH 0814/1551] Remove `test` (#14066) --- test | Bin 9832 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 test diff --git a/test b/test deleted file mode 100755 index d634907d6244430c7f8ed367e578562f81e3dcf9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9832 zcmeHNeQaFC5#PJB9mh`Wb3$lnAbugCu|s?x{ zbFc$#+ys>%g_6)nrKp8aS~US$p$Z8sRjLzEo2sd55J?e5$l*f@Rgv75wy_9tXZFq9 zowqTesQ*;zSo>!8H#58Y_U*^)ocDD1Zm`>Ig2^FX5Lh~SwT1Xd5L-zZU_Q|*%HgSr zm7)a7MG~{*K8u0WqL_UyFD2Xo9PN&hT;svgvz@tSTN_YzXR;BpZ_VLJ*Ej7H?-|s^GPtQw4t>nt!Yx z1AK{CEOuOjwFq&+VqzRP)^kp2x1Q=>uG9~(tcTa%6;uzlQza?|uQP79mGtkTeTen< zN&RJFQfYSuwd+*c{k+t73Qc*hd#Ik1R1eR)hY5dOssC-l&ntK*wW}%ZZUY?0ElvIJ z0eg$=0{?8E>JR6+O_)#@En^W`jJuj$>;?=8`^^d8Ra>PYQaaXTJ6KxmE7QVdMP*Ag z`2M9%tOIp%jB(qxbn09BHu*Pn3&?Nfe5a+<-rlh#(A%YV-o9~TV6#xrF8}84 zJl5&&?CI9K0>M1g)xE*LrFXO5@88^0h;Hf(2KqM#Ztu&3b5(i+9YKF^n_|dlaIW&! z?%<|5b@ldc-KaCVZXlJqF_6qQ)UVehyQy6=fkWoXqy*I3DcsReHe@{_sUa5(*wFBZ zdmx@LGVbAw3EOBG4WbFOaC+h61375o^ZHw^;Jj`$1?PRDM!|W#dK8@3S*wEcI`%0z zufHAz=l!o=!FeCLQ^BYG)_d+ya9+nT1?T-Tt>C<0jwm?qH)9IU``>;A=Y8TR;dU5* zTFSDc&lTWytLyUlVgZi66cgGmpcH8yXlQDxXOZM6FQZiNsc)$Bdg{C_c@>o7)+o~_ zOrTh5$Z)$Np-9Yd)9RCzpgp>dnAY4L4BQgvbGuybOe|#P6x|s!B9-BR`e9166g98Q z>v4ItuTcNDYc=_v)|QpwQfSw_EiR9%zO2g_2n{E)8f-L!Mz&oGh0I8-xv_3Ixigu% zCs_x+!th94$np%}#j(8`DZ%7#zuY20f8%of#}UT#DeHn}^FxOHDI_eTUxmpUheD~< z|D0S70bnZ}H~2sezx(nkA?^fz7w{3_Y2XKd9{_#?`1gT75ByPZLeN)u7Wlis`8=`h z4hq|SD{U(lRm*RPzSN5#>;f*j>bfdCJ&Ue!K5qX3%u}?%bNNlk@Og|_FJU+bkPzDe z>0QWR9trickfs2*fIwG;^KqyXaqREQ&`((>;n~Jri9>MCO)i84(_j+T(&Rg<#i6r~>ZeN|^v%>h z@1Y4HOFZ&JL^P# zVztc~hc;1$s~5aTd^uQ&(!_sf0bTCuppRxGMMM0=1;E%1$BEceZT{UNNIzx(vPzdQz0XtL^6 z-;8r_$srBC5sn=vDLVtQA6hb5egn!U3d(Om`E9E_OT_hGu1s#Q*h34M*Yi_K8GSY+ zzDC11N&0AZHia1wONj5gmeg9)m@g#*wi~1RU_ML2^?@`)^}%~e`W*57q{wGT58-@v z9Q0a%Tyv1cy#$Hxcz2Q_T@&RU3+3p2BHcxbc(x!tOa1%%MeF@0Nl}wh*az@{0a|6T z+`e!XmD$f&p~%svMPh%WaF0QJn%;km+Pg^Q=co+dlgAs?|7U1|CXUMa(yP^YJ#f~0 z>RZ=q*QWICSzw7Q6zYZ&h=oHLBdVp6n%UUe($d%xt<@rlP;yY)Wtf>b;MS?C z)x@&dbf(?yri0Tpm`V+T-~4P_;#(gx65j^LD;Hn*HtVn zc2qb^R#ZISv8c+n1ZI)lB{1VFd%#!Ws0b_tA9W7WUVcM4qD5%8)hSJ$l%*(DW%zcHbB73o|%(2k^uFY0b{?{T$u_(E}*im*{N%>w7i4w=?UMT*p z#IgI<{g{fKdmX#?miOBi!TVq%^An1T#9wArjYSezoQr$19NZ1)-eU14Z8TjZu=f3Y zoPzbQm6?2ef$_C6laHq`zFuaQ362jkZf(c-9p_TBEG`nIR)=%K8z5(co=#YwUEpj% z!s&r;z_={JW0TC-#cQO`_;$iiDfl4ad=9XFn()^Z{rd=C&Ib$`G{Fb3+ZPLs4o*IA z`Ur0%ocHxDfZOc(@!|6?Mf&}u@1*)40)DCQ`#mYo$E`?RJT5cK1m8y)e@bS`q{GM< z@}siAr)pmEQ<>#cIFJ8aX0H;s3l-7}vcRWVUh)f>d0ZCZ@fgkn`x4<(>iGa*jDM-| zz7GMvRJ^YQzHb$H1?&g#cV8y){5oQM4d7TmZ`C}nY9$VhC={Lt|HeG{L8_lmiVIZV z6M$pfljjeQ&ohK`JcIlB0^uCbVElK4b3B9bvxIXzgYgdu=XeI=WibBOAC6}*UQIa1 zGZ?QUoZ}gccM{I=4934qIL9*>-$^*fGZ_CS;FpYhy5K}I%(QSNQ(42senvcMz(M7T zjwUmsL&7zf9CndwGN+)MRxuXJ#4PPBAf}NBp(*9k38=|TVGu*OjF>)PhK3A176nZ% z35be~fR z8jb(OfU*@#)?@Jc35X=$V}{acc!6AuIhsk4aBlQDosF4BD4GvF|34T#Uocwl+Scda z80b`X-p^Kq?G9Z=@MJ8S@AqO7OI^+*$LlZNY1y9rA?EBap)L~d<5o!sbTjZ~!S?J2 zF|Q#3wt@ZR{f|9@>j1;MC)@M=l(`G!NbD}L9&?N@;$4=@e4k~mp)nA*ulC;$vVRoV z^L?B-#@vv&eeS=73UM3BxP89wGw1t0>*E*{68f9iC$`VUGpM04GEm#+@!L-J9aJgD zGnn@fj`n!=bN|^M{a0*)+s8m45_3FH3yJ+n#!`?ew4d*Ha@JSJ|Lcl9`%}y(*pN8) zpWz1}gY8q8CFe(-q} z_WwzMu_?A^zfP;P^wsuMd-ShSmF+qH@D9ow^i%Kv9EN#lp>2Z`I~ew_de zRoLE1@iHe~5P`Er`oTc$9|gZ-_AEJbE>@?0N%R_RbQuz&Jl`l pR)Y|OO+0^i|7!tzo;!v1Bh>ML(x6fl{(0<=(^d5;rE{wNzW_U68an_0 From 0920944e6d4c83ae9f5c18606762a4c2b7f4e492 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Fri, 8 Dec 2023 07:50:30 -0500 Subject: [PATCH 0815/1551] Expose doc comments on `ASTNode` when generating docs (#14050) --- spec/compiler/macro/macro_methods_spec.cr | 28 +++++++++++++++++++++++ src/compiler/crystal/macros.cr | 13 +++++++++++ src/compiler/crystal/macros/methods.cr | 8 +++++++ 3 files changed, 49 insertions(+) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 0f77f4ae3d54..3920d2930a79 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -224,6 +224,34 @@ module Crystal assert_macro %({{ x.is_a?(ModuleDef) }}), "false", {x: ClassDef.new("Foo".path)} end end + + describe "#doc" do + it "returns an empty string if there are no docs on the node (wants_doc = false)" do + assert_macro "{{ x.doc }}", %(""), {x: Call.new(nil, "some_call")} + end + + it "returns the call's docs if present (wants_doc = true)" do + assert_macro "{{ x.doc }}", %("Some docs"), {x: Call.new(nil, "some_call").tap { |c| c.doc = "Some docs" }} + end + + it "returns a multiline comment" do + assert_macro "{{ x.doc }}", %("Some\\nmulti\\nline\\ndocs"), {x: Call.new(nil, "some_call").tap { |c| c.doc = "Some\nmulti\nline\ndocs" }} + end + end + + describe "#doc_comment" do + it "returns an empty MacroId if there are no docs on the node (wants_doc = false)" do + assert_macro "{{ x.doc_comment }}", %(), {x: Call.new(nil, "some_call")} + end + + it "returns the call's docs if present as a MacroId (wants_doc = true)" do + assert_macro "{{ x.doc_comment }}", %(Some docs), {x: Call.new(nil, "some_call").tap { |c| c.doc = "Some docs" }} + end + + it "ensures each newline has a `#` prefix" do + assert_macro "{{ x.doc_comment }}", %(Some\n# multi\n# line\n# docs), {x: Call.new(nil, "some_call").tap { |c| c.doc = "Some\nmulti\nline\ndocs" }} + end + end end describe "number methods" do diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 8472a6626eda..e6f93d439306 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -431,6 +431,19 @@ module Crystal::Macros def warning(message : StringLiteral) : NilLiteral end + # Returns a `StringLiteral` that contains the documentation comments attached to this node, or an empty string if there are none. + # + # WARNING: The return value will be an empty string when executed outside of the `crystal docs` command. + def doc : StringLiteral + end + + # Returns a `MacroId` that contains the documentation comments attached to this node, or an empty `MacroId` if there are none. + # Each line is prefixed with a `#` character to allow the output to be used directly within another node's documentation comment. + # + # WARNING: The return value will be empty when executed outside of the `crystal docs` command. + def doc_comment : MacroId + end + # Returns `true` if this node's type is the given *type* or any of its # subclasses. # diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 90c52fa60347..ae6e20487a56 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -400,6 +400,14 @@ module Crystal interpret_check_args { symbolize } when "class_name" interpret_check_args { class_name } + when "doc" + interpret_check_args do + StringLiteral.new self.doc || "" + end + when "doc_comment" + interpret_check_args do + MacroId.new (self.doc || "").gsub("\n", "\n# ") + end when "raise" macro_raise self, args, interpreter, Crystal::MacroRaiseException when "warning" From 3a95615eab99b1c32c6e5b5dfa0af002678780b4 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 8 Dec 2023 20:50:38 +0800 Subject: [PATCH 0816/1551] Add macro methods for `ModuleDef` (#14063) --- spec/compiler/macro/macro_methods_spec.cr | 40 +++++++++++++- src/compiler/crystal/macros.cr | 45 +++++++++++++++- src/compiler/crystal/macros/methods.cr | 64 +++++++++++++++++++---- 3 files changed, 137 insertions(+), 12 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 3920d2930a79..092c738476de 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1721,7 +1721,7 @@ module Crystal describe "with an invalid type argument" do it "should raise the proper exception" do - assert_macro_error("{{x.name(generic_args: 99)}}", "named argument 'generic_args' to TypeNode#name must be a bool, not NumberLiteral") do |program| + assert_macro_error("{{x.name(generic_args: 99)}}", "named argument 'generic_args' to TypeNode#name must be a BoolLiteral, not NumberLiteral") do |program| {x: TypeNode.new(program.string)} end end @@ -3124,6 +3124,44 @@ module Crystal end end + describe ModuleDef do + module_def1 = ModuleDef.new(Path.new("Foo")) + module_def2 = ModuleDef.new(Path.new("Foo", "Bar", global: true), type_vars: %w(A B C D), splat_index: 2, body: CharLiteral.new('a')) + + it "executes kind" do + assert_macro %({{x.kind}}), %(module), {x: module_def1} + assert_macro %({{x.kind}}), %(module), {x: module_def2} + end + + it "executes name" do + assert_macro %({{x.name}}), %(Foo), {x: module_def1} + assert_macro %({{x.name}}), %(::Foo::Bar(A, B, *C, D)), {x: module_def2} + + assert_macro %({{x.name(generic_args: true)}}), %(Foo), {x: module_def1} + assert_macro %({{x.name(generic_args: true)}}), %(::Foo::Bar(A, B, *C, D)), {x: module_def2} + + assert_macro %({{x.name(generic_args: false)}}), %(Foo), {x: module_def1} + assert_macro %({{x.name(generic_args: false)}}), %(::Foo::Bar), {x: module_def2} + + assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to ModuleDef#name must be a BoolLiteral, not NumberLiteral", {x: module_def1} + end + + it "executes type_vars" do + assert_macro %({{x.type_vars}}), %([] of ::NoReturn), {x: module_def1} + assert_macro %({{x.type_vars}}), %([A, B, C, D]), {x: module_def2} + end + + it "executes splat_index" do + assert_macro %({{x.splat_index}}), %(nil), {x: module_def1} + assert_macro %({{x.splat_index}}), %(2), {x: module_def2} + end + + it "executes body" do + assert_macro %({{x.body}}), %(), {x: module_def1} + assert_macro %({{x.body}}), %('a'), {x: module_def2} + end + end + describe "env" do it "has key" do ENV["FOO"] = "foo" diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index e6f93d439306..564182fb9198 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1605,8 +1605,49 @@ module Crystal::Macros end # A module definition. - # class ModuleDef < ASTNode - # end + # + # Every module definition `node` is equivalent to: + # + # ``` + # {% begin %} + # {{ node.kind }} {{ node.name }} + # {{ node.body }} + # end + # {% end %} + # ``` + class ModuleDef < ASTNode + # Returns the keyword used to define this type. + # + # For `ModuleDef` this is always `module`. + def kind : MacroId + end + + # Returns the name of this type definition. + # + # If this node defines a generic type, and *generic_args* is true, returns a + # `Generic` whose type arguments are `MacroId`s, possibly with a `Splat` at + # the splat index. Otherwise, this method returns a `Path`. + def name(*, generic_args : BoolLiteral = true) : Path | Generic + end + + # Returns the body of this type definition. + def body : ASTNode + end + + # Returns an array of `MacroId`s of this type definition's generic type + # parameters. + # + # On a non-generic type definition, returns an empty array. + def type_vars : ArrayLiteral + end + + # Returns the splat index of this type definition's generic type parameters. + # + # Returns `nil` if this type definition isn't generic or if there isn't a + # splat parameter. + def splat_index : NumberLiteral | NilLiteral + end + end # A `while` expression class While < ASTNode diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index ae6e20487a56..3811a38ea28b 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1686,15 +1686,8 @@ module Crystal interpret_check_args { TypeNode.union_types(self) } when "name" interpret_check_args(named_params: ["generic_args"]) do - generic_args = if named_args && (generic_arg = named_args["generic_args"]?) - generic_arg - else - BoolLiteral.new true - end - - raise "named argument 'generic_args' to TypeNode#name must be a bool, not #{generic_args.class_desc}" unless generic_args.is_a?(BoolLiteral) - - MacroId.new(type.devirtualize.to_s(generic_args: generic_args.value)) + generic_args = parse_generic_args_argument(self, method, named_args, default: true) + MacroId.new(type.devirtualize.to_s(generic_args: generic_args)) end when "type_vars" interpret_check_args { TypeNode.type_vars(type) } @@ -2399,6 +2392,48 @@ module Crystal end end end + + class ModuleDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "kind" + interpret_check_args { MacroId.new("module") } + when "name" + interpret_check_args(named_params: ["generic_args"]) do + if parse_generic_args_argument(self, method, named_args, default: true) && (type_vars = @type_vars) + type_vars = type_vars.map_with_index do |type_var, i| + param = MacroId.new(type_var) + param = Splat.new(param) if i == @splat_index + param + end + Generic.new(@name, type_vars) + else + @name + end + end + when "type_vars" + interpret_check_args do + if (type_vars = @type_vars) && type_vars.present? + ArrayLiteral.map(type_vars) { |type_var| MacroId.new(type_var) } + else + empty_no_return_array + end + end + when "splat_index" + interpret_check_args do + if splat_index = @splat_index + NumberLiteral.new(splat_index) + else + NilLiteral.new + end + end + when "body" + interpret_check_args { @body } + else + super + end + end + end end private def get_named_annotation_args(object) @@ -2737,6 +2772,17 @@ private def visibility_to_symbol(visibility) Crystal::SymbolLiteral.new(visibility_name) end +private def parse_generic_args_argument(node, method, named_args, *, default) + case named_arg = named_args.try &.["generic_args"]? + when Nil + default + when Crystal::BoolLiteral + named_arg.value + else + named_arg.raise "named argument 'generic_args' to #{node.class_desc}##{method} must be a BoolLiteral, not #{named_arg.class_desc}" + end +end + private def macro_raise(node, args, interpreter, exception_type) msg = args.map do |arg| arg.accept interpreter From 4925cad105f2d274f2db7fc1d071bfdd9da67865 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 8 Dec 2023 20:50:52 +0800 Subject: [PATCH 0817/1551] Add macro methods for `Include` and `Extend` (#14064) --- spec/compiler/macro/macro_methods_spec.cr | 20 ++++++++++++++++ src/compiler/crystal/macros.cr | 28 +++++++++++++++++++---- src/compiler/crystal/macros/methods.cr | 22 ++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 092c738476de..66f47d6ad39d 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2590,6 +2590,26 @@ module Crystal end end + describe Include do + foo = Include.new("Foo".path) + bar = Include.new(Generic.new("Bar".path, ["Int32".path] of ASTNode)) + + it "executes name" do + assert_macro %({{x.name}}), "Foo", {x: foo} + assert_macro %({{x.name}}), "Bar(Int32)", {x: bar} + end + end + + describe Extend do + foo = Extend.new("Foo".path) + bar = Extend.new(Generic.new("Bar".path, ["Int32".path] of ASTNode)) + + it "executes name" do + assert_macro %({{x.name}}), "Foo", {x: foo} + assert_macro %({{x.name}}), "Bar(Int32)", {x: bar} + end + end + describe "visibility modifier methods" do node = VisibilityModifier.new(Visibility::Protected, Def.new("some_def")) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 564182fb9198..68782d9ae12f 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1851,11 +1851,31 @@ module Crystal::Macros end end - # class Include < ASTNode - # end + # An `include` statement. + # + # Every statement `node` is equivalent to: + # + # ``` + # include {{ node.name }} + # ``` + class Include < ASTNode + # Returns the name of the type being included. + def name : ASTNode + end + end - # class Extend < ASTNode - # end + # An `extend` statement. + # + # Every statement `node` is equivalent to: + # + # ``` + # extend {{ node.name }} + # ``` + class Extend < ASTNode + # Returns the name of the type being extended. + def name : ASTNode + end + end # class LibDef < ASTNode # end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 3811a38ea28b..888f41eecbba 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1527,6 +1527,28 @@ module Crystal end end + class Include + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "name" + interpret_check_args { @name } + else + super + end + end + end + + class Extend + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "name" + interpret_check_args { @name } + else + super + end + end + end + class OffsetOf def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) case method From df14c35a02f5eb9b2159597a6bd922dce0318fc8 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 9 Dec 2023 02:07:41 +0800 Subject: [PATCH 0818/1551] Fix integral part extraction in `Number#format` (#14061) --- spec/std/humanize_spec.cr | 8 ++++++++ src/humanize.cr | 11 +++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/spec/std/humanize_spec.cr b/spec/std/humanize_spec.cr index 482be02b77eb..841fde6cb2ba 100644 --- a/spec/std/humanize_spec.cr +++ b/spec/std/humanize_spec.cr @@ -1,5 +1,6 @@ require "spec" require "spec/helpers/string" +require "big" private LENGTH_UNITS = ->(magnitude : Int32, number : Float64) do case magnitude @@ -91,6 +92,13 @@ describe Number do it { assert_prints Float64::INFINITY.format, "Infinity" } it { assert_prints (-Float64::INFINITY).format, "-Infinity" } it { assert_prints Float64::NAN.format, "NaN" } + + it { assert_prints "12345.67890123456789012345".to_big_d.format, "12,345.67890123456789012345" } + + it "extracts integer part correctly (#12997)" do + assert_prints 1.9999998.format, "1.9999998" + assert_prints 1111111.999999998.format, "1,111,111.999999998" + end end describe "#humanize" do diff --git a/src/humanize.cr b/src/humanize.cr index 16cff9d63174..e0c69a37b3ad 100644 --- a/src/humanize.cr +++ b/src/humanize.cr @@ -38,21 +38,24 @@ struct Number if decimal_places && decimal_places >= 0 string = "%.*f" % {decimal_places, number.abs} + integer, _, decimals = string.partition('.') else string = String.build do |io| # Make sure to avoid scientific notation of default Float#to_s Float::Printer.shortest(number.abs, io, point_range: ..) end _, _, decimals = string.partition(".") - integer, _, _ = ("%f" % number.abs).partition(".") - string = "#{integer}.#{decimals}" + integer = "%.0f" % number.trunc.abs end + elsif number.is_a?(Int) + integer = number.abs.to_s + decimals = "" else + # TODO: optimize for BigDecimal string = number.abs.to_s + integer, _, decimals = string.partition('.') end - integer, _, decimals = string.partition('.') - int_size = integer.size dec_size = decimals.size From 23314a747d33b73c98134abd2b528c0c6cf08435 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 9 Dec 2023 07:27:58 +0800 Subject: [PATCH 0819/1551] Document `Crystal::Macros::MagicConstant` (#14070) --- src/compiler/crystal/macros.cr | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 68782d9ae12f..1c61e98a9b80 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2005,8 +2005,20 @@ module Crystal::Macros class Underscore < ASTNode end - # class MagicConstant < ASTNode + # A pseudo constant used to provide information about source code location. + # + # Usually this node is resolved by the compiler. It appears unresolved when + # used as a default parameter value: + # + # ``` + # # the `__FILE__` here is a `MagicConstant` + # def foo(file = __FILE__) + # # the `__LINE__` here becomes a `NumberLiteral` + # __LINE__ # end + # ``` + class MagicConstant < ASTNode + end # A fictitious node representing an identifier like, `foo`, `Bar` or `something_else`. # From 7f6a50093cc9fe6023352832ccc4164cab911dc8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 9 Dec 2023 00:28:23 +0100 Subject: [PATCH 0820/1551] Update GH Actions (#13801) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/linux.yml | 2 +- .github/workflows/macos.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f34bf180bd98..9ef73a9fe23c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -130,7 +130,7 @@ jobs: run: echo $GITHUB_SHA > ./docs/revision.txt - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index bfee3af4fdde..dbab5f5bc93c 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -17,12 +17,12 @@ jobs: - name: Download Crystal source uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v23 + - uses: cachix/install-nix-action@v24 with: install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | experimental-features = nix-command - - uses: cachix/cachix-action@v12 + - uses: cachix/cachix-action@v13 with: name: crystal-ci signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' From d7f251220c56aa7fa07a32c91574da49f2f1b1e9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 10 Dec 2023 03:59:51 +0800 Subject: [PATCH 0821/1551] Windows: clear `Crystal::System::Process#@completion_key` after use (#14068) --- src/io/overlapped.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index d7ed8aa516ec..de36b5bf638b 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -77,6 +77,10 @@ module IO::Overlapped case entry.dwNumberOfBytesTransferred when LibC::JOB_OBJECT_MSG_EXIT_PROCESS, LibC::JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS if fiber = completion_key.fiber + # this ensures the `::Process` doesn't keep an indirect reference to + # `::Thread.current`, as that leads to a finalization cycle + completion_key.fiber = nil + yield fiber else # the `Process` exits before a call to `#wait`; do nothing From 80f59e7ae7e6449a91c58d4013bb3d873e6dec4b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 10 Dec 2023 04:19:16 +0800 Subject: [PATCH 0822/1551] Use `#trailing_zeros_count` in `Int#gcd` (#14069) --- src/int.cr | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/int.cr b/src/int.cr index 6f0d3aa5881a..47fbcca16259 100644 --- a/src/int.cr +++ b/src/int.cr @@ -487,30 +487,23 @@ struct Int return v if u == 0 return u if v == 0 - shift = self.class.zero # Let shift := lg K, where K is the greatest power of 2 # dividing both u and v. - while (u | v) & 1 == 0 - shift &+= 1 - u = u.unsafe_shr 1 - v = v.unsafe_shr 1 - end - while u & 1 == 0 - u = u.unsafe_shr 1 - end + shift = (u | v).trailing_zeros_count + u = u.unsafe_shr(u.trailing_zeros_count) + # From here on, u is always odd. loop do # remove all factors of 2 in v -- they are not common - # note: v is not zero, so while will terminate - while v & 1 == 0 - v = v.unsafe_shr 1 - end + v = v.unsafe_shr(v.trailing_zeros_count) + # Now u and v are both odd. Swap if necessary so u <= v, # then set v = v - u (which is even). u, v = v, u if u > v v &-= u break if v.zero? end + # restore common factors of 2 u.unsafe_shl shift end From 3f578c1ceae8a25b32fd30de3b8f0396c0c2e1bc Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 10 Dec 2023 21:06:28 +0800 Subject: [PATCH 0823/1551] Fix global `Path` lookup inside macro when def has free variables (#14073) --- spec/compiler/semantic/macro_spec.cr | 13 +++++++++++++ src/compiler/crystal/macros/interpreter.cr | 2 +- src/compiler/crystal/macros/types.cr | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/macro_spec.cr b/spec/compiler/semantic/macro_spec.cr index f71e9247461a..79e7856adfae 100644 --- a/spec/compiler/semantic/macro_spec.cr +++ b/spec/compiler/semantic/macro_spec.cr @@ -1104,6 +1104,19 @@ describe "Semantic: macro" do CRYSTAL end + it "finds type for global path shared with free var" do + assert_type(<<-CRYSTAL) { int32 } + module T + end + + def foo(x : T) forall T + {{ ::T.module? ? 1 : 'a' }} + end + + foo("") + CRYSTAL + end + it "gets named arguments in double splat" do assert_type(<<-CRYSTAL) { named_tuple_of({"x": string, "y": bool}) } macro foo(**options) diff --git a/src/compiler/crystal/macros/interpreter.cr b/src/compiler/crystal/macros/interpreter.cr index d9f5473ec9f2..6b38a5066b8e 100644 --- a/src/compiler/crystal/macros/interpreter.cr +++ b/src/compiler/crystal/macros/interpreter.cr @@ -418,7 +418,7 @@ module Crystal end def resolve?(node : Path) - if node.names.size == 1 && (match = @free_vars.try &.[node.names.first]?) + if (single_name = node.single_name?) && (match = @free_vars.try &.[single_name]?) matched_type = match else matched_type = @path_lookup.lookup_path(node) diff --git a/src/compiler/crystal/macros/types.cr b/src/compiler/crystal/macros/types.cr index 3a3bfa402c57..73ae695066ba 100644 --- a/src/compiler/crystal/macros/types.cr +++ b/src/compiler/crystal/macros/types.cr @@ -129,6 +129,8 @@ module Crystal # Returns the macro type named by a given AST node in the macro language. def lookup_macro_type(name : Path) + # NOTE: `name.global?` doesn't matter since there are no namespaces for + # the AST node types if name.names.size == 1 macro_type = @macro_types[name.names.first]? end From e05f80ad47e6c249779b0c06c9860cef6c3bb7b4 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 12 Dec 2023 18:18:26 +0800 Subject: [PATCH 0824/1551] Support `LLVMSetTargetMachineGlobalISel` from LLVM 18 (#14079) --- src/llvm/ext/llvm_ext.cc | 4 +++- src/llvm/jit_compiler.cr | 2 +- src/llvm/lib_llvm.cr | 2 ++ src/llvm/lib_llvm/target_machine.cr | 3 +++ src/llvm/lib_llvm_ext.cr | 2 +- src/llvm/target_machine.cr | 3 ++- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc index acc95d63bcae..921a39dbf29d 100644 --- a/src/llvm/ext/llvm_ext.cc +++ b/src/llvm/ext/llvm_ext.cc @@ -55,12 +55,14 @@ LLVMValueRef LLVMExtBuildInvoke2( Bundles, Name)); } +#if !LLVM_VERSION_GE(18, 0) static TargetMachine *unwrap(LLVMTargetMachineRef P) { return reinterpret_cast(P); } -void LLVMExtTargetMachineEnableGlobalIsel(LLVMTargetMachineRef T, LLVMBool Enable) { +void LLVMExtSetTargetMachineGlobalISel(LLVMTargetMachineRef T, LLVMBool Enable) { unwrap(T)->setGlobalISel(Enable); } +#endif } // extern "C" diff --git a/src/llvm/jit_compiler.cr b/src/llvm/jit_compiler.cr index 3481212e1b93..33d03e697107 100644 --- a/src/llvm/jit_compiler.cr +++ b/src/llvm/jit_compiler.cr @@ -15,7 +15,7 @@ class LLVM::JITCompiler # See https://github.com/crystal-lang/crystal/issues/9297#issuecomment-636512270 # for background info target_machine = LibLLVM.get_execution_engine_target_machine(@unwrap) - LibLLVMExt.target_machine_enable_global_isel(target_machine, false) + {{ LibLLVM::IS_LT_180 ? LibLLVMExt : LibLLVM }}.set_target_machine_global_isel(target_machine, 0) @finalized = false end diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 3acff22ef324..a4c55f2473a4 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -19,6 +19,7 @@ end {% begin %} lib LibLLVM + IS_180 = {{LibLLVM::VERSION.starts_with?("18.0")}} IS_170 = {{LibLLVM::VERSION.starts_with?("17.0")}} IS_160 = {{LibLLVM::VERSION.starts_with?("16.0")}} IS_150 = {{LibLLVM::VERSION.starts_with?("15.0")}} @@ -40,6 +41,7 @@ end IS_LT_150 = {{compare_versions(LibLLVM::VERSION, "15.0.0") < 0}} IS_LT_160 = {{compare_versions(LibLLVM::VERSION, "16.0.0") < 0}} IS_LT_170 = {{compare_versions(LibLLVM::VERSION, "17.0.0") < 0}} + IS_LT_180 = {{compare_versions(LibLLVM::VERSION, "18.0.0") < 0}} end {% end %} diff --git a/src/llvm/lib_llvm/target_machine.cr b/src/llvm/lib_llvm/target_machine.cr index ce1d7c64fa97..992d2e2d72e9 100644 --- a/src/llvm/lib_llvm/target_machine.cr +++ b/src/llvm/lib_llvm/target_machine.cr @@ -16,6 +16,9 @@ lib LibLLVM fun get_target_machine_target = LLVMGetTargetMachineTarget(t : TargetMachineRef) : TargetRef fun get_target_machine_triple = LLVMGetTargetMachineTriple(t : TargetMachineRef) : Char* fun create_target_data_layout = LLVMCreateTargetDataLayout(t : TargetMachineRef) : TargetDataRef + {% unless LibLLVM::IS_LT_180 %} + fun set_target_machine_global_isel = LLVMSetTargetMachineGlobalISel(t : TargetMachineRef, enable : Bool) + {% end %} fun target_machine_emit_to_file = LLVMTargetMachineEmitToFile(t : TargetMachineRef, m : ModuleRef, filename : Char*, codegen : LLVM::CodeGenFileType, error_message : Char**) : Bool fun get_default_target_triple = LLVMGetDefaultTargetTriple : Char* diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr index 4efb572b6eed..b6999a86276e 100644 --- a/src/llvm/lib_llvm_ext.cr +++ b/src/llvm/lib_llvm_ext.cr @@ -31,5 +31,5 @@ lib LibLLVMExt bundle : LibLLVMExt::OperandBundleDefRef, name : LibC::Char*) : LibLLVM::ValueRef - fun target_machine_enable_global_isel = LLVMExtTargetMachineEnableGlobalIsel(machine : LibLLVM::TargetMachineRef, enable : Bool) + fun set_target_machine_global_isel = LLVMExtSetTargetMachineGlobalISel(t : LibLLVM::TargetMachineRef, enable : LibLLVM::Bool) end diff --git a/src/llvm/target_machine.cr b/src/llvm/target_machine.cr index 80ebc188f9a4..c3aaee21313d 100644 --- a/src/llvm/target_machine.cr +++ b/src/llvm/target_machine.cr @@ -28,7 +28,8 @@ class LLVM::TargetMachine end def enable_global_isel=(enable : Bool) - LibLLVMExt.target_machine_enable_global_isel(self, enable) + {{ LibLLVM::IS_LT_180 ? LibLLVMExt : LibLLVM }}.set_target_machine_global_isel(self, enable ? 1 : 0) + enable end private def emit_to_file(llvm_mod, filename, type) From 4b7fa0c046b1bbb1518fe0462f76855603a4383a Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Tue, 12 Dec 2023 05:18:51 -0500 Subject: [PATCH 0825/1551] Do not remove trailing comma from multi-line macro/def parameters (#14075) --- spec/compiler/formatter/formatter_spec.cr | 223 +++++++++++++++++++++- src/compiler/crystal/tools/formatter.cr | 8 +- 2 files changed, 226 insertions(+), 5 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index f7c4243e7795..4cc2ce230550 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -32,7 +32,7 @@ private def assert_format(input, output = input, strict = false, flags = nil, fi end # Check idempotency - result2 = Crystal.format(result) + result2 = Crystal.format(result, flags: flags) unless result == result2 fail "Idempotency failed:\nBefore: #{result.inspect}\nAfter: #{result2.inspect}", file: file, line: line end @@ -886,6 +886,227 @@ describe Crystal::Formatter do CRYSTAL end + context "adds trailing comma to def multi-line normal, splat, and double splat parameters" do + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + macro foo( + a, + b + ) + end + CRYSTAL + macro foo( + a, + b, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + macro foo( + a, + *b + ) + end + CRYSTAL + macro foo( + a, + *b, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + fun foo( + a : Int32, + b : Int32 + ) + end + CRYSTAL + fun foo( + a : Int32, + b : Int32, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + fun foo( + a : Int32, + ... + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a, + b + ) + end + CRYSTAL + def foo( + a, + b, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a : Int32, + b : Int32 + ) + end + CRYSTAL + def foo( + a : Int32, + b : Int32, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a : Int32, + b : Int32 = 1 + ) + end + CRYSTAL + def foo( + a : Int32, + b : Int32 = 1, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a, + b c + ) + end + CRYSTAL + def foo( + a, + b c, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a, + @[Ann] b + ) + end + CRYSTAL + def foo( + a, + @[Ann] b, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a, + @[Ann] + b + ) + end + CRYSTAL + def foo( + a, + @[Ann] + b, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a, b + ) + end + CRYSTAL + def foo( + a, b, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a, b, + c, d + ) + end + CRYSTAL + def foo( + a, b, + c, d, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a, # Foo + b # Bar + ) + end + CRYSTAL + def foo( + a, # Foo + b, # Bar + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a, + *b + ) + end + CRYSTAL + def foo( + a, + *b, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a, + **b + ) + end + CRYSTAL + def foo( + a, + **b, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a, + &block + ) + end + CRYSTAL + + assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo( + a, + ) + end + CRYSTAL + end + assert_format "1 + 2", "1 + 2" assert_format "1 &+ 2", "1 &+ 2" assert_format "1 > 2", "1 > 2" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 32168af580e9..ae16d4bec0fd 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1562,7 +1562,7 @@ module Crystal args.each_with_index do |arg, i| has_more = !last?(i, args) || double_splat || block_arg || yields || variadic - wrote_newline = format_def_arg(wrote_newline, has_more) do + wrote_newline = format_def_arg(wrote_newline, has_more, true) do if i == splat_index write_token :OP_STAR skip_space_or_newline @@ -1577,7 +1577,7 @@ module Crystal end if double_splat - wrote_newline = format_def_arg(wrote_newline, block_arg || yields) do + wrote_newline = format_def_arg(wrote_newline, block_arg || yields, true) do write_token :OP_STAR_STAR skip_space_or_newline @@ -1645,13 +1645,13 @@ module Crystal end end - def format_def_arg(wrote_newline, has_more, &) + def format_def_arg(wrote_newline, has_more, write_trailing_comma = false, &) write_indent if wrote_newline yield # Write "," before skipping spaces to prevent inserting comment between argument and comma. - write "," if has_more + write "," if has_more || (write_trailing_comma && flag?("def_trailing_comma")) just_wrote_newline = skip_space if @token.type.newline? From 8a1f13671361242dd8e20dfd7a2eb8b81c459382 Mon Sep 17 00:00:00 2001 From: Keshav Biswa Date: Tue, 12 Dec 2023 15:50:45 +0530 Subject: [PATCH 0826/1551] Change short_reference for top-level methods to `::foo` (#14071) --- spec/compiler/semantic/def_spec.cr | 2 +- spec/compiler/semantic/warnings_spec.cr | 14 +++++++------- src/compiler/crystal/semantic/warnings.cr | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/compiler/semantic/def_spec.cr b/spec/compiler/semantic/def_spec.cr index fee65d2f3b5d..96ce765bdb5b 100644 --- a/spec/compiler/semantic/def_spec.cr +++ b/spec/compiler/semantic/def_spec.cr @@ -284,7 +284,7 @@ describe "Semantic: def" do foo ), - "method top-level foo must return Int32 but it is returning Char" + "method ::foo must return Int32 but it is returning Char" end it "errors if return type doesn't match on instance method" do diff --git a/spec/compiler/semantic/warnings_spec.cr b/spec/compiler/semantic/warnings_spec.cr index 502351ce305b..6c6914c60fe5 100644 --- a/spec/compiler/semantic/warnings_spec.cr +++ b/spec/compiler/semantic/warnings_spec.cr @@ -40,7 +40,7 @@ describe "Semantic: warnings" do foo CRYSTAL - "warning in line 5\nWarning: Deprecated top-level foo. Do not use me" + "warning in line 5\nWarning: Deprecated ::foo. Do not use me" end it "deprecation reason is optional" do @@ -51,7 +51,7 @@ describe "Semantic: warnings" do foo CRYSTAL - "warning in line 5\nWarning: Deprecated top-level foo." + "warning in line 5\nWarning: Deprecated ::foo." end it "detects deprecated instance methods" do @@ -127,7 +127,7 @@ describe "Semantic: warnings" do foo(a: 2) CRYSTAL - "warning in line 5\nWarning: Deprecated top-level foo:a." + "warning in line 5\nWarning: Deprecated ::foo:a." end it "detects deprecated initialize" do @@ -277,7 +277,7 @@ describe "Semantic: warnings" do foo CRYSTAL - "warning in line 7\nWarning: Deprecated top-level foo." + "warning in line 7\nWarning: Deprecated ::foo." end it "errors if invalid argument type" do @@ -316,7 +316,7 @@ describe "Semantic: warnings" do end foo - ), "warning in line 6\nWarning: Deprecated top-level foo. Do not use me" + ), "warning in line 6\nWarning: Deprecated ::foo. Do not use me" end it "deprecation reason is optional" do @@ -326,7 +326,7 @@ describe "Semantic: warnings" do end foo - ), "warning in line 6\nWarning: Deprecated top-level foo." + ), "warning in line 6\nWarning: Deprecated ::foo." end it "detects deprecated class macros" do @@ -372,7 +372,7 @@ describe "Semantic: warnings" do end foo(a: 2) - ), "warning in line 6\nWarning: Deprecated top-level foo." + ), "warning in line 6\nWarning: Deprecated ::foo." end it "informs warnings once per call site location (a)" do diff --git a/src/compiler/crystal/semantic/warnings.cr b/src/compiler/crystal/semantic/warnings.cr index 0dae71c5cd06..a445057fa40f 100644 --- a/src/compiler/crystal/semantic/warnings.cr +++ b/src/compiler/crystal/semantic/warnings.cr @@ -76,7 +76,7 @@ module Crystal def short_reference case owner when Program - "top-level #{name}" + "::#{name}" when MetaclassType "#{owner.instance_type.to_s(generic_args: false)}.#{name}" else @@ -122,7 +122,7 @@ module Crystal def short_reference case owner when Program - "top-level #{name}" + "::#{name}" when .metaclass? "#{owner.instance_type}.#{name}" else From a3be0f5490702a61f095c323c4dce485ec5e8aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 13 Dec 2023 11:24:40 +0100 Subject: [PATCH 0827/1551] [CI] Update LLVM patch version to LLVM 17.0.6 (#14080) --- .github/workflows/llvm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 883302cf64a6..f361896768d8 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -24,7 +24,7 @@ jobs: llvm_ubuntu_version: "18.04" - llvm_version: "16.0.3" llvm_ubuntu_version: "22.04" - - llvm_version: "17.0.2" + - llvm_version: "17.0.6" llvm_ubuntu_version: "22.04" name: "LLVM ${{ matrix.llvm_version }}" steps: From 89423943373001d5d2c0accef189107f5b0a0d91 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 13 Dec 2023 18:26:01 +0800 Subject: [PATCH 0828/1551] Support debug information for 64-bit or unsigned enums (#14081) --- spec/debug/large_enums.cr | 15 ++++++++++++++ spec/debug/test.sh | 1 + src/compiler/crystal/codegen/debug.cr | 29 ++++++++++++++------------- src/llvm/di_builder.cr | 7 ++----- src/llvm/ext/llvm_ext.cc | 10 +++++---- src/llvm/lib_llvm/debug_info.cr | 18 ++++++++--------- src/llvm/lib_llvm_ext.cr | 4 +++- 7 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 spec/debug/large_enums.cr diff --git a/spec/debug/large_enums.cr b/spec/debug/large_enums.cr new file mode 100644 index 000000000000..d0c7a2e8914e --- /dev/null +++ b/spec/debug/large_enums.cr @@ -0,0 +1,15 @@ +enum SignedEnum : Int64 + X = 0x0123_4567_89ab_cdef_i64 +end + +enum UnsignedEnum : UInt64 + Y = 0xfedc_ba98_7654_3210_u64 +end + +x = SignedEnum::X +y = UnsignedEnum::Y +# print: x +# lldb-check: (SignedEnum) $0 = X +# print: y +# lldb-check: (UnsignedEnum) $1 = Y +debugger diff --git a/spec/debug/test.sh b/spec/debug/test.sh index ef8d817c457b..f2fd7cb8ac3e 100755 --- a/spec/debug/test.sh +++ b/spec/debug/test.sh @@ -43,3 +43,4 @@ $driver $SCRIPT_ROOT/top_level.cr $debugger $driver $SCRIPT_ROOT/strings.cr $debugger $driver $SCRIPT_ROOT/arrays.cr $debugger $driver $SCRIPT_ROOT/blocks.cr $debugger +$driver $SCRIPT_ROOT/large_enums.cr $debugger diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index 9fe5ce5c6ca9..58d139ef2b3c 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -140,14 +140,21 @@ module Crystal def create_debug_type(type : EnumType, original_type : Type) elements = type.types.map do |name, item| - value = if item.is_a?(Const) && (value2 = item.value).is_a?(NumberLiteral) - value2.value.to_i64 rescue value2.value.to_u64 - else - 0 - end + str_value = item.as?(Const).try &.value.as?(NumberLiteral).try &.value + + value = + if type.base_type.kind.unsigned_int? + str_value.try(&.to_u64?) || 0_u64 + else + str_value.try(&.to_i64?) || 0_i64 + end + di_builder.create_enumerator(name, value) end - di_builder.create_enumeration_type(nil, original_type.to_s, nil, 1, 32, 32, elements, get_debug_type(type.base_type)) + + size_in_bits = type.base_type.kind.bytesize + align_in_bits = align_of(type.base_type) + di_builder.create_enumeration_type(nil, original_type.to_s, nil, 1, size_in_bits, align_in_bits, elements, get_debug_type(type.base_type)) end def create_debug_type(type : InstanceVarContainer, original_type : Type) @@ -202,7 +209,7 @@ module Crystal if ivar_debug_type = get_debug_type(ivar_type) embedded_type = llvm_type(ivar_type) size = @program.target_machine.data_layout.size_in_bits(embedded_type) - align = llvm_typer.align_of(embedded_type) * 8u64 + align = align_of(ivar_type) member = di_builder.create_member_type(nil, ivar_type.to_s, nil, 1, size, align, 0, LLVM::DIFlags::Zero, ivar_debug_type) element_types << member end @@ -342,13 +349,7 @@ module Crystal end private def align_of(type) - case type - when CharType then 32 - when IntegerType then type.bits - when FloatType then type.bytes * 8 - when BoolType then 8 - else 0 # unsupported - end + @program.target_machine.data_layout.abi_alignment(llvm_type(type)) * 8 end private def declare_local(type, alloca, location, basic_block : LLVM::BasicBlock? = nil, &) diff --git a/src/llvm/di_builder.cr b/src/llvm/di_builder.cr index 41bb18244c3c..98676431de16 100644 --- a/src/llvm/di_builder.cr +++ b/src/llvm/di_builder.cr @@ -103,11 +103,8 @@ struct LLVM::DIBuilder end def create_enumerator(name, value) - {% if LibLLVM::IS_LT_90 %} - LibLLVMExt.di_builder_create_enumerator(self, name, value) - {% else %} - LibLLVM.di_builder_create_enumerator(self, name, name.bytesize, value, 0) - {% end %} + {{ LibLLVM::IS_LT_90 ? LibLLVMExt : LibLLVM }}.di_builder_create_enumerator( + self, name, name.bytesize, value.to_i64!, value.is_a?(Int::Unsigned) ? 1 : 0) end def create_enumeration_type(scope, name, file, line_number, size_in_bits, align_in_bits, elements, underlying_type) diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc index 921a39dbf29d..2962cee6d1f2 100644 --- a/src/llvm/ext/llvm_ext.cc +++ b/src/llvm/ext/llvm_ext.cc @@ -19,10 +19,12 @@ using namespace llvm; extern "C" { #if !LLVM_VERSION_GE(9, 0) -LLVMMetadataRef LLVMExtDIBuilderCreateEnumerator( - LLVMDIBuilderRef Dref, const char *Name, int64_t Value) { - DIEnumerator *e = unwrap(Dref)->createEnumerator(Name, Value); - return wrap(e); +LLVMMetadataRef LLVMExtDIBuilderCreateEnumerator(LLVMDIBuilderRef Builder, + const char *Name, size_t NameLen, + int64_t Value, + LLVMBool IsUnsigned) { + return wrap(unwrap(Builder)->createEnumerator({Name, NameLen}, Value, + IsUnsigned != 0)); } void LLVMExtClearCurrentDebugLocation(LLVMBuilderRef B) { diff --git a/src/llvm/lib_llvm/debug_info.cr b/src/llvm/lib_llvm/debug_info.cr index 8482a0644ae3..e97e8c71a177 100644 --- a/src/llvm/lib_llvm/debug_info.cr +++ b/src/llvm/lib_llvm/debug_info.cr @@ -12,16 +12,16 @@ lib LibLLVM {% if LibLLVM::IS_LT_110 %} fun di_builder_create_compile_unit = LLVMDIBuilderCreateCompileUnit( builder : DIBuilderRef, lang : LLVM::DwarfSourceLanguage, file_ref : MetadataRef, producer : Char*, - producer_len : SizeT, is_optimized : Int, flags : Char*, flags_len : SizeT, runtime_ver : UInt, + producer_len : SizeT, is_optimized : Bool, flags : Char*, flags_len : SizeT, runtime_ver : UInt, split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt, - split_debug_inlining : Int, debug_info_for_profiling : Int + split_debug_inlining : Bool, debug_info_for_profiling : Bool ) : MetadataRef {% else %} fun di_builder_create_compile_unit = LLVMDIBuilderCreateCompileUnit( builder : DIBuilderRef, lang : LLVM::DwarfSourceLanguage, file_ref : MetadataRef, producer : Char*, - producer_len : SizeT, is_optimized : Int, flags : Char*, flags_len : SizeT, runtime_ver : UInt, + producer_len : SizeT, is_optimized : Bool, flags : Char*, flags_len : SizeT, runtime_ver : UInt, split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt, - split_debug_inlining : Int, debug_info_for_profiling : Int, sys_root : Char*, + split_debug_inlining : Bool, debug_info_for_profiling : Bool, sys_root : Char*, sys_root_len : SizeT, sdk : Char*, sdk_len : SizeT ) : MetadataRef {% end %} @@ -34,8 +34,8 @@ lib LibLLVM fun di_builder_create_function = LLVMDIBuilderCreateFunction( builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, linkage_name : Char*, linkage_name_len : SizeT, file : MetadataRef, line_no : UInt, - ty : MetadataRef, is_local_to_unit : Int, is_definition : Int, scope_line : UInt, - flags : LLVM::DIFlags, is_optimized : Int + ty : MetadataRef, is_local_to_unit : Bool, is_definition : Bool, scope_line : UInt, + flags : LLVM::DIFlags, is_optimized : Bool ) : MetadataRef fun di_builder_create_lexical_block = LLVMDIBuilderCreateLexicalBlock( @@ -57,7 +57,7 @@ lib LibLLVM ) : MetadataRef {% unless LibLLVM::IS_LT_90 %} fun di_builder_create_enumerator = LLVMDIBuilderCreateEnumerator( - builder : DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : Int + builder : DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : Bool ) : MetadataRef {% end %} fun di_builder_create_enumeration_type = LLVMDIBuilderCreateEnumerationType( @@ -118,11 +118,11 @@ lib LibLLVM fun di_builder_create_auto_variable = LLVMDIBuilderCreateAutoVariable( builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, - line_no : UInt, ty : MetadataRef, always_preserve : Int, flags : LLVM::DIFlags, align_in_bits : UInt32 + line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags, align_in_bits : UInt32 ) : MetadataRef fun di_builder_create_parameter_variable = LLVMDIBuilderCreateParameterVariable( builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, arg_no : UInt, - file : MetadataRef, line_no : UInt, ty : MetadataRef, always_preserve : Int, flags : LLVM::DIFlags + file : MetadataRef, line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags ) : MetadataRef fun set_subprogram = LLVMSetSubprogram(func : ValueRef, sp : MetadataRef) diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr index b6999a86276e..0fa8f4f95bd1 100644 --- a/src/llvm/lib_llvm_ext.cr +++ b/src/llvm/lib_llvm_ext.cr @@ -9,10 +9,12 @@ lib LibLLVMExt alias Int = LibC::Int alias UInt = LibC::UInt + alias SizeT = LibC::SizeT + type OperandBundleDefRef = Void* {% if LibLLVM::IS_LT_90 %} - fun di_builder_create_enumerator = LLVMExtDIBuilderCreateEnumerator(builder : LibLLVM::DIBuilderRef, name : Char*, value : Int64) : LibLLVM::MetadataRef + fun di_builder_create_enumerator = LLVMExtDIBuilderCreateEnumerator(builder : LibLLVM::DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : LibLLVM::Bool) : LibLLVM::MetadataRef fun clear_current_debug_location = LLVMExtClearCurrentDebugLocation(b : LibLLVM::BuilderRef) {% end %} From 93c9df4fdeb459d70d5701290539c2e48a59e5ab Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 13 Dec 2023 18:26:39 +0800 Subject: [PATCH 0829/1551] Implement `sprintf "%f"` in Crystal using Ryu Printf (#14067) --- spec/std/float_printer/ryu_printf_spec.cr | 494 ++ .../float_printer/ryu_printf_test_cases.cr | 2697 ++++++++++ spec/std/sprintf_spec.cr | 52 +- src/float/printer/ryu_printf.cr | 629 +++ src/float/printer/ryu_printf_table.cr | 4415 +++++++++++++++++ src/string/formatter.cr | 103 +- 6 files changed, 8356 insertions(+), 34 deletions(-) create mode 100644 spec/std/float_printer/ryu_printf_spec.cr create mode 100644 spec/std/float_printer/ryu_printf_test_cases.cr create mode 100644 src/float/printer/ryu_printf.cr create mode 100644 src/float/printer/ryu_printf_table.cr diff --git a/spec/std/float_printer/ryu_printf_spec.cr b/spec/std/float_printer/ryu_printf_spec.cr new file mode 100644 index 000000000000..d4ba77bffd94 --- /dev/null +++ b/spec/std/float_printer/ryu_printf_spec.cr @@ -0,0 +1,494 @@ +# FIXME: this leads to an OOB on wasm32 (#13918) +{% skip_file if flag?(:wasm32) %} + +# This file contains test cases derived from: +# +# * https://github.com/ulfjack/ryu +# +# The following is their license: +# +# Copyright 2020-2021 Junekey Jeon +# +# The contents of this file may be used under the terms of +# the Apache License v2.0 with LLVM Exceptions. +# +# (See accompanying file LICENSE-Apache or copy at +# https://llvm.org/foundation/relicensing/LICENSE.txt) +# +# Alternatively, the contents of this file may be used under the terms of +# the Boost Software License, Version 1.0. +# (See accompanying file LICENSE-Boost or copy at +# https://www.boost.org/LICENSE_1_0.txt) +# +# Unless required by applicable law or agreed to in writing, this software +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. + +require "spec" +require "float/printer/ryu_printf" +require "big" +require "./ryu_printf_test_cases" + +struct BigFloat + def to_s_with_range(*, point_range : Range = -3..15) + String.build do |io| + to_s_with_range(io, point_range: point_range) + end + end + + def to_s_with_range(io : IO, *, point_range : Range = -3..15) : Nil + cstr = LibGMP.mpf_get_str(nil, out decimal_exponent, 10, 0, self) + length = LibC.strlen(cstr) + buffer = Slice.new(cstr, length) + + # add negative sign + if buffer[0]? == 45 # '-' + io << '-' + buffer = buffer[1..] + length -= 1 + end + + point = decimal_exponent + exp = point + exp_mode = !point_range.includes?(point) + point = 1 if exp_mode + + # add leading zero + io << '0' if point < 1 + + # add integer part digits + if decimal_exponent > 0 && !exp_mode + # whole number but not big enough to be exp form + io.write_string buffer[0, {decimal_exponent, length}.min] + buffer = buffer[{decimal_exponent, length}.min...] + (point - length).times { io << '0' } + elsif point > 0 + io.write_string buffer[0, point] + buffer = buffer[point...] + end + + # skip `.0000...` + unless buffer.all?(&.=== '0') + io << '.' + + # add leading zeros after point + if point < 0 + (-point).times { io << '0' } + end + + # add fractional part digits + io.write_string buffer + + # print trailing 0 if whole number or exp notation of power of ten + if (decimal_exponent >= length && !exp_mode) || ((exp != point || exp_mode) && length == 1) + io << '0' + end + end + + # exp notation + if exp_mode + io << 'e' + io << '+' if exp > 0 + (exp - 1).to_s(io) + end + end +end + +private def fixed_reference(value, precision) + if precision == 0 + value.to_big_i.to_s + else + BigFloat.new(value, 4096).to_s_with_range(point_range: ..) + end +end + +private def exp_reference(value, precision) + BigFloat.new(value, 4096).to_s_with_range(point_range: 0...0) +end + +private def ieee_parts_to_f64(sign, exponent, mantissa) + ((sign ? 1_u64 << 63 : 0_u64) | (exponent.to_u64 << 52) | mantissa.to_u64).unsafe_as(Float64) +end + +private macro expect_fixed(float, precision, string) + Float::Printer::RyuPrintf.d2fixed({{ float }}, {{ precision }}).should eq({{ string }}) +end + +private macro expect_exp(float, precision, string) + Float::Printer::RyuPrintf.d2exp({{ float }}, {{ precision }}).should eq({{ string }}) +end + +describe Float::Printer::RyuPrintf do + describe ".d2fixed" do + it "Basic" do + expect_fixed( + ieee_parts_to_f64(false, 1234, 99999), 0, + "3291009114715486435425664845573426149758869524108446525879746560") + end + + it "Zero" do + expect_fixed(0.0, 4, "0.0000") + expect_fixed(0.0, 3, "0.000") + expect_fixed(0.0, 2, "0.00") + expect_fixed(0.0, 1, "0.0") + expect_fixed(0.0, 0, "0") + end + + it "MinMax" do + expect_fixed(ieee_parts_to_f64(false, 0, 1), 1074, + "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359923797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036887186360569987307230500063874091535649843873124733972731696151400317153853980741262385655911710266585566867681870395603106249319452715914924553293054565444011274801297099995419319894090804165633245247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431936092382893458368060106011506169809753078342277318329247904982524730776375927247874656084778203734469699533647017972677717585125660551199131504891101451037862738167250955837389733598993664809941164205702637090279242767544565229087538682506419718265533447265625") + + expect_fixed(ieee_parts_to_f64(false, 2046, 0xFFFFFFFFFFFFFu64), 0, + "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368") + end + + it "AllPowersOfTen" do + {% for tc in ALL_POWERS_OF_TEN %} + expect_fixed({{ tc[0] }}, {{ tc[1] }}, fixed_reference({{ tc[0] }}, {{ tc[1] }})) + {% end %} + end + + it "RoundToEven" do + expect_fixed(0.125, 3, "0.125") + expect_fixed(0.125, 2, "0.12") + expect_fixed(0.375, 3, "0.375") + expect_fixed(0.375, 2, "0.38") + end + + it "RoundToEvenInteger" do + expect_fixed(2.5, 1, "2.5") + expect_fixed(2.5, 0, "2") + expect_fixed(3.5, 1, "3.5") + expect_fixed(3.5, 0, "4") + end + + it "NonRoundToEvenScenarios" do + expect_fixed(0.748046875, 3, "0.748") + expect_fixed(0.748046875, 2, "0.75") + expect_fixed(0.748046875, 1, "0.7") # 0.75 would round to "0.8", but this is smaller + + expect_fixed(0.2509765625, 3, "0.251") + expect_fixed(0.2509765625, 2, "0.25") + expect_fixed(0.2509765625, 1, "0.3") # 0.25 would round to "0.2", but this is larger + + expect_fixed(ieee_parts_to_f64(false, 1021, 1), 54, "0.250000000000000055511151231257827021181583404541015625") + expect_fixed(ieee_parts_to_f64(false, 1021, 1), 3, "0.250") + expect_fixed(ieee_parts_to_f64(false, 1021, 1), 2, "0.25") + expect_fixed(ieee_parts_to_f64(false, 1021, 1), 1, "0.3") # 0.25 would round to "0.2", but this is larger (again) + end + + it "VaryingPrecision" do + expect_fixed(1729.142857142857, 47, "1729.14285714285711037518922239542007446289062500000") + expect_fixed(1729.142857142857, 46, "1729.1428571428571103751892223954200744628906250000") + expect_fixed(1729.142857142857, 45, "1729.142857142857110375189222395420074462890625000") + expect_fixed(1729.142857142857, 44, "1729.14285714285711037518922239542007446289062500") + expect_fixed(1729.142857142857, 43, "1729.1428571428571103751892223954200744628906250") + expect_fixed(1729.142857142857, 42, "1729.142857142857110375189222395420074462890625") + expect_fixed(1729.142857142857, 41, "1729.14285714285711037518922239542007446289062") + expect_fixed(1729.142857142857, 40, "1729.1428571428571103751892223954200744628906") + expect_fixed(1729.142857142857, 39, "1729.142857142857110375189222395420074462891") + expect_fixed(1729.142857142857, 38, "1729.14285714285711037518922239542007446289") + expect_fixed(1729.142857142857, 37, "1729.1428571428571103751892223954200744629") + expect_fixed(1729.142857142857, 36, "1729.142857142857110375189222395420074463") + expect_fixed(1729.142857142857, 35, "1729.14285714285711037518922239542007446") + expect_fixed(1729.142857142857, 34, "1729.1428571428571103751892223954200745") + expect_fixed(1729.142857142857, 33, "1729.142857142857110375189222395420074") + expect_fixed(1729.142857142857, 32, "1729.14285714285711037518922239542007") + expect_fixed(1729.142857142857, 31, "1729.1428571428571103751892223954201") + expect_fixed(1729.142857142857, 30, "1729.142857142857110375189222395420") + expect_fixed(1729.142857142857, 29, "1729.14285714285711037518922239542") + expect_fixed(1729.142857142857, 28, "1729.1428571428571103751892223954") + expect_fixed(1729.142857142857, 27, "1729.142857142857110375189222395") + expect_fixed(1729.142857142857, 26, "1729.14285714285711037518922240") + expect_fixed(1729.142857142857, 25, "1729.1428571428571103751892224") + expect_fixed(1729.142857142857, 24, "1729.142857142857110375189222") + expect_fixed(1729.142857142857, 23, "1729.14285714285711037518922") + expect_fixed(1729.142857142857, 22, "1729.1428571428571103751892") + expect_fixed(1729.142857142857, 21, "1729.142857142857110375189") + expect_fixed(1729.142857142857, 20, "1729.14285714285711037519") + expect_fixed(1729.142857142857, 19, "1729.1428571428571103752") + expect_fixed(1729.142857142857, 18, "1729.142857142857110375") + expect_fixed(1729.142857142857, 17, "1729.14285714285711038") + expect_fixed(1729.142857142857, 16, "1729.1428571428571104") + expect_fixed(1729.142857142857, 15, "1729.142857142857110") + expect_fixed(1729.142857142857, 14, "1729.14285714285711") + expect_fixed(1729.142857142857, 13, "1729.1428571428571") + expect_fixed(1729.142857142857, 12, "1729.142857142857") + expect_fixed(1729.142857142857, 11, "1729.14285714286") + expect_fixed(1729.142857142857, 10, "1729.1428571429") + expect_fixed(1729.142857142857, 9, "1729.142857143") + expect_fixed(1729.142857142857, 8, "1729.14285714") + expect_fixed(1729.142857142857, 7, "1729.1428571") + expect_fixed(1729.142857142857, 6, "1729.142857") + expect_fixed(1729.142857142857, 5, "1729.14286") + expect_fixed(1729.142857142857, 4, "1729.1429") + expect_fixed(1729.142857142857, 3, "1729.143") + expect_fixed(1729.142857142857, 2, "1729.14") + expect_fixed(1729.142857142857, 1, "1729.1") + expect_fixed(1729.142857142857, 0, "1729") + end + + it "Carrying" do + expect_fixed(0.0009, 4, "0.0009") + expect_fixed(0.0009, 3, "0.001") + expect_fixed(0.0029, 4, "0.0029") + expect_fixed(0.0029, 3, "0.003") + expect_fixed(0.0099, 4, "0.0099") + expect_fixed(0.0099, 3, "0.010") + expect_fixed(0.0299, 4, "0.0299") + expect_fixed(0.0299, 3, "0.030") + expect_fixed(0.0999, 4, "0.0999") + expect_fixed(0.0999, 3, "0.100") + expect_fixed(0.2999, 4, "0.2999") + expect_fixed(0.2999, 3, "0.300") + expect_fixed(0.9999, 4, "0.9999") + expect_fixed(0.9999, 3, "1.000") + expect_fixed(2.9999, 4, "2.9999") + expect_fixed(2.9999, 3, "3.000") + expect_fixed(9.9999, 4, "9.9999") + expect_fixed(9.9999, 3, "10.000") + expect_fixed(29.9999, 4, "29.9999") + expect_fixed(29.9999, 3, "30.000") + expect_fixed(99.9999, 4, "99.9999") + expect_fixed(99.9999, 3, "100.000") + expect_fixed(299.9999, 4, "299.9999") + expect_fixed(299.9999, 3, "300.000") + + expect_fixed(0.09, 2, "0.09") + expect_fixed(0.09, 1, "0.1") + expect_fixed(0.29, 2, "0.29") + expect_fixed(0.29, 1, "0.3") + expect_fixed(0.99, 2, "0.99") + expect_fixed(0.99, 1, "1.0") + expect_fixed(2.99, 2, "2.99") + expect_fixed(2.99, 1, "3.0") + expect_fixed(9.99, 2, "9.99") + expect_fixed(9.99, 1, "10.0") + expect_fixed(29.99, 2, "29.99") + expect_fixed(29.99, 1, "30.0") + expect_fixed(99.99, 2, "99.99") + expect_fixed(99.99, 1, "100.0") + expect_fixed(299.99, 2, "299.99") + expect_fixed(299.99, 1, "300.0") + + expect_fixed(0.9, 1, "0.9") + expect_fixed(0.9, 0, "1") + expect_fixed(2.9, 1, "2.9") + expect_fixed(2.9, 0, "3") + expect_fixed(9.9, 1, "9.9") + expect_fixed(9.9, 0, "10") + expect_fixed(29.9, 1, "29.9") + expect_fixed(29.9, 0, "30") + expect_fixed(99.9, 1, "99.9") + expect_fixed(99.9, 0, "100") + expect_fixed(299.9, 1, "299.9") + expect_fixed(299.9, 0, "300") + end + + it "RoundingResultZero" do + expect_fixed(0.004, 3, "0.004") + expect_fixed(0.004, 2, "0.00") + expect_fixed(0.4, 1, "0.4") + expect_fixed(0.4, 0, "0") + expect_fixed(0.5, 1, "0.5") + expect_fixed(0.5, 0, "0") + end + + it "AllBinaryExponents" do + {% for tc in ALL_BINARY_EXPONENTS %} + expect_fixed({{ tc[0] }}, {{ tc[1] }}, fixed_reference({{ tc[0] }}, {{ tc[1] }})) + {% end %} + end + + it "Regression" do + expect_fixed(7.018232e-82, 6, "0.000000") + end + end + + describe ".d2exp" do + it "Basic" do + expect_exp(ieee_parts_to_f64(false, 1234, 99999), 62, + "3.29100911471548643542566484557342614975886952410844652587974656e+63") + end + + it "Zero" do + expect_exp(0.0, 4, "0.0000e+0") + expect_exp(0.0, 3, "0.000e+0") + expect_exp(0.0, 2, "0.00e+0") + expect_exp(0.0, 1, "0.0e+0") + expect_exp(0.0, 0, "0e+0") + end + + it "MinMax" do + expect_exp(ieee_parts_to_f64(false, 0, 1), 750, + "4.940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359923797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036887186360569987307230500063874091535649843873124733972731696151400317153853980741262385655911710266585566867681870395603106249319452715914924553293054565444011274801297099995419319894090804165633245247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431936092382893458368060106011506169809753078342277318329247904982524730776375927247874656084778203734469699533647017972677717585125660551199131504891101451037862738167250955837389733598993664809941164205702637090279242767544565229087538682506419718265533447265625e-324") + + expect_exp(ieee_parts_to_f64(false, 2046, 0xFFFFFFFFFFFFFu64), 308, + "1.79769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368e+308") + end + + it "AllPowersOfTen" do + {% for tc in ALL_POWERS_OF_TEN %} + expect_exp({{ tc[0] }}, {{ tc[2] }}, exp_reference({{ tc[0] }}, {{ tc[2] }})) + {% end %} + end + + it "RoundToEven" do + expect_exp(0.125, 2, "1.25e-1") + expect_exp(0.125, 1, "1.2e-1") + expect_exp(0.375, 2, "3.75e-1") + expect_exp(0.375, 1, "3.8e-1") + end + + it "RoundToEvenInteger" do + expect_exp(2.5, 1, "2.5e+0") + expect_exp(2.5, 0, "2e+0") + expect_exp(3.5, 1, "3.5e+0") + expect_exp(3.5, 0, "4e+0") + end + + it "NonRoundToEvenScenarios" do + expect_exp(0.748046875, 2, "7.48e-1") + expect_exp(0.748046875, 1, "7.5e-1") + expect_exp(0.748046875, 0, "7e-1") # 0.75 would round to "8e-1", but this is smaller + + expect_exp(0.2509765625, 2, "2.51e-1") + expect_exp(0.2509765625, 1, "2.5e-1") + expect_exp(0.2509765625, 0, "3e-1") # 0.25 would round to "2e-1", but this is larger + + expect_exp(ieee_parts_to_f64(false, 1021, 1), 53, "2.50000000000000055511151231257827021181583404541015625e-1") + expect_exp(ieee_parts_to_f64(false, 1021, 1), 2, "2.50e-1") + expect_exp(ieee_parts_to_f64(false, 1021, 1), 1, "2.5e-1") + expect_exp(ieee_parts_to_f64(false, 1021, 1), 0, "3e-1") # 0.25 would round to "2e-1", but this is larger (again) + end + + it "VaryingPrecision" do + expect_exp(1729.142857142857, 50, "1.72914285714285711037518922239542007446289062500000e+3") + expect_exp(1729.142857142857, 49, "1.7291428571428571103751892223954200744628906250000e+3") + expect_exp(1729.142857142857, 48, "1.729142857142857110375189222395420074462890625000e+3") + expect_exp(1729.142857142857, 47, "1.72914285714285711037518922239542007446289062500e+3") + expect_exp(1729.142857142857, 46, "1.7291428571428571103751892223954200744628906250e+3") + expect_exp(1729.142857142857, 45, "1.729142857142857110375189222395420074462890625e+3") + expect_exp(1729.142857142857, 44, "1.72914285714285711037518922239542007446289062e+3") + expect_exp(1729.142857142857, 43, "1.7291428571428571103751892223954200744628906e+3") + expect_exp(1729.142857142857, 42, "1.729142857142857110375189222395420074462891e+3") + expect_exp(1729.142857142857, 41, "1.72914285714285711037518922239542007446289e+3") + expect_exp(1729.142857142857, 40, "1.7291428571428571103751892223954200744629e+3") + expect_exp(1729.142857142857, 39, "1.729142857142857110375189222395420074463e+3") + expect_exp(1729.142857142857, 38, "1.72914285714285711037518922239542007446e+3") + expect_exp(1729.142857142857, 37, "1.7291428571428571103751892223954200745e+3") + expect_exp(1729.142857142857, 36, "1.729142857142857110375189222395420074e+3") + expect_exp(1729.142857142857, 35, "1.72914285714285711037518922239542007e+3") + expect_exp(1729.142857142857, 34, "1.7291428571428571103751892223954201e+3") + expect_exp(1729.142857142857, 33, "1.729142857142857110375189222395420e+3") + expect_exp(1729.142857142857, 32, "1.72914285714285711037518922239542e+3") + expect_exp(1729.142857142857, 31, "1.7291428571428571103751892223954e+3") + expect_exp(1729.142857142857, 30, "1.729142857142857110375189222395e+3") + expect_exp(1729.142857142857, 29, "1.72914285714285711037518922240e+3") + expect_exp(1729.142857142857, 28, "1.7291428571428571103751892224e+3") + expect_exp(1729.142857142857, 27, "1.729142857142857110375189222e+3") + expect_exp(1729.142857142857, 26, "1.72914285714285711037518922e+3") + expect_exp(1729.142857142857, 25, "1.7291428571428571103751892e+3") + expect_exp(1729.142857142857, 24, "1.729142857142857110375189e+3") + expect_exp(1729.142857142857, 23, "1.72914285714285711037519e+3") + expect_exp(1729.142857142857, 22, "1.7291428571428571103752e+3") + expect_exp(1729.142857142857, 21, "1.729142857142857110375e+3") + expect_exp(1729.142857142857, 20, "1.72914285714285711038e+3") + expect_exp(1729.142857142857, 19, "1.7291428571428571104e+3") + expect_exp(1729.142857142857, 18, "1.729142857142857110e+3") + expect_exp(1729.142857142857, 17, "1.72914285714285711e+3") + expect_exp(1729.142857142857, 16, "1.7291428571428571e+3") + expect_exp(1729.142857142857, 15, "1.729142857142857e+3") + expect_exp(1729.142857142857, 14, "1.72914285714286e+3") + expect_exp(1729.142857142857, 13, "1.7291428571429e+3") + expect_exp(1729.142857142857, 12, "1.729142857143e+3") + expect_exp(1729.142857142857, 11, "1.72914285714e+3") + expect_exp(1729.142857142857, 10, "1.7291428571e+3") + expect_exp(1729.142857142857, 9, "1.729142857e+3") + expect_exp(1729.142857142857, 8, "1.72914286e+3") + expect_exp(1729.142857142857, 7, "1.7291429e+3") + expect_exp(1729.142857142857, 6, "1.729143e+3") + expect_exp(1729.142857142857, 5, "1.72914e+3") + expect_exp(1729.142857142857, 4, "1.7291e+3") + expect_exp(1729.142857142857, 3, "1.729e+3") + expect_exp(1729.142857142857, 2, "1.73e+3") + expect_exp(1729.142857142857, 1, "1.7e+3") + expect_exp(1729.142857142857, 0, "2e+3") + end + + it "Carrying" do + expect_exp(2.0009, 4, "2.0009e+0") + expect_exp(2.0009, 3, "2.001e+0") + expect_exp(2.0029, 4, "2.0029e+0") + expect_exp(2.0029, 3, "2.003e+0") + expect_exp(2.0099, 4, "2.0099e+0") + expect_exp(2.0099, 3, "2.010e+0") + expect_exp(2.0299, 4, "2.0299e+0") + expect_exp(2.0299, 3, "2.030e+0") + expect_exp(2.0999, 4, "2.0999e+0") + expect_exp(2.0999, 3, "2.100e+0") + expect_exp(2.2999, 4, "2.2999e+0") + expect_exp(2.2999, 3, "2.300e+0") + expect_exp(2.9999, 4, "2.9999e+0") + expect_exp(2.9999, 3, "3.000e+0") + expect_exp(9.9999, 4, "9.9999e+0") + expect_exp(9.9999, 3, "1.000e+1") + + expect_exp(2.09, 2, "2.09e+0") + expect_exp(2.09, 1, "2.1e+0") + expect_exp(2.29, 2, "2.29e+0") + expect_exp(2.29, 1, "2.3e+0") + expect_exp(2.99, 2, "2.99e+0") + expect_exp(2.99, 1, "3.0e+0") + expect_exp(9.99, 2, "9.99e+0") + expect_exp(9.99, 1, "1.0e+1") + + expect_exp(2.9, 1, "2.9e+0") + expect_exp(2.9, 0, "3e+0") + expect_exp(9.9, 1, "9.9e+0") + expect_exp(9.9, 0, "1e+1") + end + + it "Exponents" do + expect_exp(9.99e-100, 2, "9.99e-100") + expect_exp(9.99e-99, 2, "9.99e-99") + expect_exp(9.99e-10, 2, "9.99e-10") + expect_exp(9.99e-9, 2, "9.99e-9") + expect_exp(9.99e-1, 2, "9.99e-1") + expect_exp(9.99e+0, 2, "9.99e+0") + expect_exp(9.99e+1, 2, "9.99e+1") + expect_exp(9.99e+9, 2, "9.99e+9") + expect_exp(9.99e+10, 2, "9.99e+10") + expect_exp(9.99e+99, 2, "9.99e+99") + expect_exp(9.99e+100, 2, "9.99e+100") + + expect_exp(9.99e-100, 1, "1.0e-99") + expect_exp(9.99e-99, 1, "1.0e-98") + expect_exp(9.99e-10, 1, "1.0e-9") + expect_exp(9.99e-9, 1, "1.0e-8") + expect_exp(9.99e-1, 1, "1.0e+0") + expect_exp(9.99e+0, 1, "1.0e+1") + expect_exp(9.99e+1, 1, "1.0e+2") + expect_exp(9.99e+9, 1, "1.0e+10") + expect_exp(9.99e+10, 1, "1.0e+11") + expect_exp(9.99e+99, 1, "1.0e+100") + expect_exp(9.99e+100, 1, "1.0e+101") + end + + it "AllBinaryExponents" do + {% for tc in ALL_BINARY_EXPONENTS %} + expect_exp({{ tc[0] }}, {{ tc[2] }}, exp_reference({{ tc[0] }}, {{ tc[2] }})) + {% end %} + end + + it "PrintDecimalPoint" do + # These values exercise each codepath. + expect_exp(1e+54, 0, "1e+54") + expect_exp(1e+54, 1, "1.0e+54") + expect_exp(1e-63, 0, "1e-63") + expect_exp(1e-63, 1, "1.0e-63") + expect_exp(1e+83, 0, "1e+83") + expect_exp(1e+83, 1, "1.0e+83") + end + end +end diff --git a/spec/std/float_printer/ryu_printf_test_cases.cr b/spec/std/float_printer/ryu_printf_test_cases.cr new file mode 100644 index 000000000000..6c1a3a155692 --- /dev/null +++ b/spec/std/float_printer/ryu_printf_test_cases.cr @@ -0,0 +1,2697 @@ +# Test cases used by `./ryu_printf_spec.cr` + +# The following two lists store: {value, fixed_precision, exp_precision} +# Iterate over them via macros only, otherwise the entire array literal will be +# materialized! +# The original specs contain also the full expected results which would have +# increased the file size by over 18x. Here we assume that `BigFloat#to_s` and +# `BigDecimal#to_s` are always correct, so that we don't have to write the long +# results in the source code. + +# These values test every power of ten that's within the range of doubles. +ALL_POWERS_OF_TEN = [ + {1e-323, 1073, 749}, + {1e-322, 1072, 749}, + {1e-321, 1073, 751}, + {1e-320, 1071, 750}, + {1e-319, 1070, 750}, + {1e-318, 1073, 754}, + {1e-317, 1074, 757}, + {1e-316, 1074, 757}, + {1e-315, 1074, 758}, + {1e-314, 1074, 759}, + {1e-313, 1074, 761}, + {1e-312, 1074, 761}, + {1e-311, 1074, 762}, + {1e-310, 1074, 763}, + {1e-309, 1074, 765}, + {1e-308, 1073, 764}, + {1e-307, 1072, 764}, + {1e-306, 1069, 763}, + {1e-305, 1066, 760}, + {1e-304, 1062, 757}, + {1e-303, 1059, 755}, + {1e-302, 1054, 751}, + {1e-301, 1052, 751}, + {1e-300, 1049, 749}, + {1e-299, 1046, 746}, + {1e-298, 1042, 743}, + {1e-297, 1039, 742}, + {1e-296, 1035, 739}, + {1e-295, 1032, 737}, + {1e-294, 1029, 735}, + {1e-293, 1022, 729}, + {1e-292, 1021, 729}, + {1e-291, 1019, 727}, + {1e-290, 1015, 725}, + {1e-289, 1012, 723}, + {1e-288, 1009, 721}, + {1e-287, 1006, 719}, + {1e-286, 1003, 717}, + {1e-285, 999, 714}, + {1e-284, 996, 712}, + {1e-283, 993, 709}, + {1e-282, 986, 704}, + {1e-281, 985, 704}, + {1e-280, 981, 700}, + {1e-279, 979, 700}, + {1e-278, 976, 697}, + {1e-277, 973, 695}, + {1e-276, 957, 681}, + {1e-275, 966, 690}, + {1e-274, 963, 688}, + {1e-273, 954, 681}, + {1e-272, 956, 683}, + {1e-271, 953, 681}, + {1e-270, 948, 678}, + {1e-269, 944, 674}, + {1e-268, 943, 674}, + {1e-267, 935, 667}, + {1e-266, 934, 667}, + {1e-265, 933, 667}, + {1e-264, 925, 661}, + {1e-263, 924, 661}, + {1e-262, 923, 661}, + {1e-261, 920, 658}, + {1e-260, 916, 655}, + {1e-259, 909, 650}, + {1e-258, 910, 651}, + {1e-257, 904, 646}, + {1e-256, 903, 646}, + {1e-255, 898, 643}, + {1e-254, 894, 639}, + {1e-253, 890, 637}, + {1e-252, 890, 637}, + {1e-251, 886, 635}, + {1e-250, 881, 631}, + {1e-249, 880, 631}, + {1e-248, 876, 627}, + {1e-247, 872, 625}, + {1e-246, 870, 623}, + {1e-245, 863, 617}, + {1e-244, 862, 617}, + {1e-243, 860, 616}, + {1e-242, 850, 607}, + {1e-241, 849, 607}, + {1e-240, 848, 607}, + {1e-239, 845, 606}, + {1e-238, 840, 601}, + {1e-237, 839, 601}, + {1e-236, 835, 599}, + {1e-235, 829, 593}, + {1e-234, 828, 593}, + {1e-233, 827, 593}, + {1e-232, 823, 591}, + {1e-231, 819, 587}, + {1e-230, 817, 587}, + {1e-229, 813, 584}, + {1e-228, 810, 582}, + {1e-227, 807, 579}, + {1e-226, 803, 576}, + {1e-225, 799, 573}, + {1e-224, 796, 572}, + {1e-223, 792, 568}, + {1e-222, 790, 568}, + {1e-221, 787, 566}, + {1e-220, 783, 562}, + {1e-219, 777, 558}, + {1e-218, 776, 558}, + {1e-217, 773, 556}, + {1e-216, 768, 552}, + {1e-215, 767, 552}, + {1e-214, 763, 548}, + {1e-213, 758, 544}, + {1e-212, 757, 544}, + {1e-211, 753, 542}, + {1e-210, 747, 537}, + {1e-209, 746, 537}, + {1e-208, 740, 532}, + {1e-207, 740, 532}, + {1e-206, 737, 531}, + {1e-205, 731, 526}, + {1e-204, 730, 526}, + {1e-203, 723, 520}, + {1e-202, 722, 520}, + {1e-201, 720, 518}, + {1e-200, 715, 514}, + {1e-199, 714, 514}, + {1e-198, 709, 510}, + {1e-197, 706, 508}, + {1e-196, 703, 507}, + {1e-195, 699, 504}, + {1e-194, 697, 503}, + {1e-193, 693, 500}, + {1e-192, 689, 497}, + {1e-191, 684, 493}, + {1e-190, 683, 493}, + {1e-189, 680, 491}, + {1e-188, 676, 487}, + {1e-187, 672, 485}, + {1e-186, 669, 482}, + {1e-185, 666, 480}, + {1e-184, 663, 479}, + {1e-183, 660, 477}, + {1e-182, 655, 473}, + {1e-181, 654, 473}, + {1e-180, 647, 467}, + {1e-179, 646, 467}, + {1e-178, 641, 462}, + {1e-177, 640, 462}, + {1e-176, 632, 455}, + {1e-175, 631, 455}, + {1e-174, 630, 455}, + {1e-173, 624, 451}, + {1e-172, 623, 451}, + {1e-171, 618, 446}, + {1e-170, 617, 446}, + {1e-169, 614, 445}, + {1e-168, 610, 442}, + {1e-167, 607, 440}, + {1e-166, 604, 438}, + {1e-165, 600, 435}, + {1e-164, 597, 432}, + {1e-163, 594, 430}, + {1e-162, 591, 428}, + {1e-161, 587, 426}, + {1e-160, 582, 421}, + {1e-159, 581, 421}, + {1e-158, 577, 419}, + {1e-157, 574, 416}, + {1e-156, 571, 415}, + {1e-155, 567, 412}, + {1e-154, 563, 408}, + {1e-153, 561, 408}, + {1e-152, 557, 405}, + {1e-151, 553, 401}, + {1e-150, 551, 401}, + {1e-149, 547, 397}, + {1e-148, 544, 395}, + {1e-147, 540, 392}, + {1e-146, 538, 392}, + {1e-145, 534, 388}, + {1e-144, 527, 382}, + {1e-143, 526, 382}, + {1e-142, 521, 379}, + {1e-141, 520, 379}, + {1e-140, 517, 376}, + {1e-139, 514, 375}, + {1e-138, 511, 373}, + {1e-137, 508, 370}, + {1e-136, 504, 368}, + {1e-135, 499, 364}, + {1e-134, 498, 364}, + {1e-133, 493, 360}, + {1e-132, 487, 354}, + {1e-131, 486, 354}, + {1e-130, 482, 352}, + {1e-129, 477, 347}, + {1e-128, 478, 350}, + {1e-127, 473, 346}, + {1e-126, 470, 343}, + {1e-125, 468, 343}, + {1e-124, 464, 339}, + {1e-123, 459, 336}, + {1e-122, 458, 336}, + {1e-121, 452, 330}, + {1e-120, 451, 330}, + {1e-119, 448, 329}, + {1e-118, 444, 325}, + {1e-117, 441, 324}, + {1e-116, 437, 320}, + {1e-115, 431, 316}, + {1e-114, 430, 316}, + {1e-113, 428, 314}, + {1e-112, 424, 311}, + {1e-111, 421, 310}, + {1e-110, 417, 307}, + {1e-109, 414, 304}, + {1e-108, 411, 303}, + {1e-107, 407, 300}, + {1e-106, 405, 298}, + {1e-105, 401, 295}, + {1e-104, 398, 293}, + {1e-103, 395, 291}, + {1e-102, 391, 288}, + {1e-101, 388, 287}, + {1e-100, 381, 281}, + {1e-99, 380, 281}, + {1e-98, 378, 279}, + {1e-97, 375, 278}, + {1e-96, 370, 273}, + {1e-95, 368, 272}, + {1e-94, 364, 269}, + {1e-93, 360, 266}, + {1e-92, 358, 265}, + {1e-91, 355, 264}, + {1e-90, 351, 260}, + {1e-89, 348, 259}, + {1e-88, 345, 256}, + {1e-87, 342, 255}, + {1e-86, 338, 252}, + {1e-85, 334, 248}, + {1e-84, 328, 244}, + {1e-83, 327, 244}, + {1e-82, 320, 237}, + {1e-81, 319, 237}, + {1e-80, 318, 237}, + {1e-79, 313, 233}, + {1e-78, 312, 233}, + {1e-77, 306, 228}, + {1e-76, 305, 228}, + {1e-75, 299, 223}, + {1e-74, 298, 223}, + {1e-73, 295, 221}, + {1e-72, 291, 218}, + {1e-71, 287, 215}, + {1e-70, 285, 214}, + {1e-69, 280, 210}, + {1e-68, 278, 210}, + {1e-67, 275, 207}, + {1e-66, 271, 204}, + {1e-65, 268, 202}, + {1e-64, 265, 200}, + {1e-63, 262, 199}, + {1e-62, 255, 193}, + {1e-61, 254, 193}, + {1e-60, 251, 190}, + {1e-59, 245, 186}, + {1e-58, 244, 186}, + {1e-57, 242, 184}, + {1e-56, 238, 182}, + {1e-55, 235, 179}, + {1e-54, 230, 176}, + {1e-53, 229, 176}, + {1e-52, 221, 169}, + {1e-51, 220, 169}, + {1e-50, 219, 169}, + {1e-49, 215, 165}, + {1e-48, 209, 160}, + {1e-47, 208, 160}, + {1e-46, 205, 159}, + {1e-45, 202, 156}, + {1e-44, 199, 154}, + {1e-43, 195, 152}, + {1e-42, 192, 150}, + {1e-41, 189, 148}, + {1e-40, 183, 142}, + {1e-39, 182, 142}, + {1e-38, 177, 138}, + {1e-37, 175, 138}, + {1e-36, 171, 134}, + {1e-35, 169, 134}, + {1e-34, 165, 130}, + {1e-33, 160, 127}, + {1e-32, 159, 127}, + {1e-31, 148, 117}, + {1e-30, 147, 117}, + {1e-29, 149, 119}, + {1e-28, 146, 117}, + {1e-27, 138, 111}, + {1e-26, 137, 111}, + {1e-25, 136, 111}, + {1e-24, 132, 107}, + {1e-23, 129, 105}, + {1e-22, 125, 103}, + {1e-21, 122, 100}, + {1e-20, 119, 98}, + {1e-19, 114, 94}, + {1e-18, 110, 92}, + {1e-17, 109, 92}, + {1e-16, 104, 87}, + {1e-15, 101, 86}, + {1e-14, 99, 84}, + {1e-13, 95, 82}, + {1e-12, 92, 79}, + {1e-11, 89, 77}, + {1e-10, 86, 76}, + {1e-9, 82, 73}, + {1e-8, 78, 70}, + {1e-7, 73, 65}, + {1e-6, 72, 65}, + {1e-5, 69, 64}, + {1e-4, 66, 62}, + {1e-3, 60, 57}, + {1e-2, 59, 57}, + {1e-1, 55, 54}, + {1e0, 0, 0}, + {1e1, 0, 0}, + {1e2, 0, 0}, + {1e3, 0, 0}, + {1e4, 0, 0}, + {1e5, 0, 0}, + {1e6, 0, 0}, + {1e7, 0, 0}, + {1e8, 0, 0}, + {1e9, 0, 0}, + {1e10, 0, 0}, + {1e11, 0, 0}, + {1e12, 0, 0}, + {1e13, 0, 0}, + {1e14, 0, 0}, + {1e15, 0, 0}, + {1e16, 0, 0}, + {1e17, 0, 0}, + {1e18, 0, 0}, + {1e19, 0, 0}, + {1e20, 0, 0}, + {1e21, 0, 0}, + {1e22, 0, 0}, + {1e23, 0, 22}, + {1e24, 0, 23}, + {1e25, 0, 25}, + {1e26, 0, 26}, + {1e27, 0, 27}, + {1e28, 0, 27}, + {1e29, 0, 28}, + {1e30, 0, 30}, + {1e31, 0, 30}, + {1e32, 0, 32}, + {1e33, 0, 32}, + {1e34, 0, 32}, + {1e35, 0, 34}, + {1e36, 0, 36}, + {1e37, 0, 36}, + {1e38, 0, 37}, + {1e39, 0, 38}, + {1e40, 0, 40}, + {1e41, 0, 41}, + {1e42, 0, 42}, + {1e43, 0, 43}, + {1e44, 0, 44}, + {1e45, 0, 44}, + {1e46, 0, 45}, + {1e47, 0, 47}, + {1e48, 0, 47}, + {1e49, 0, 48}, + {1e50, 0, 49}, + {1e51, 0, 50}, + {1e52, 0, 50}, + {1e53, 0, 50}, + {1e54, 0, 54}, + {1e55, 0, 55}, + {1e56, 0, 56}, + {1e57, 0, 57}, + {1e58, 0, 57}, + {1e59, 0, 58}, + {1e60, 0, 59}, + {1e61, 0, 59}, + {1e62, 0, 62}, + {1e63, 0, 63}, + {1e64, 0, 64}, + {1e65, 0, 64}, + {1e66, 0, 65}, + {1e67, 0, 66}, + {1e68, 0, 67}, + {1e69, 0, 68}, + {1e70, 0, 68}, + {1e71, 0, 71}, + {1e72, 0, 71}, + {1e73, 0, 72}, + {1e74, 0, 73}, + {1e75, 0, 74}, + {1e76, 0, 76}, + {1e77, 0, 76}, + {1e78, 0, 78}, + {1e79, 0, 78}, + {1e80, 0, 80}, + {1e81, 0, 80}, + {1e82, 0, 81}, + {1e83, 0, 83}, + {1e84, 0, 84}, + {1e85, 0, 85}, + {1e86, 0, 85}, + {1e87, 0, 86}, + {1e88, 0, 86}, + {1e89, 0, 88}, + {1e90, 0, 89}, + {1e91, 0, 90}, + {1e92, 0, 92}, + {1e93, 0, 92}, + {1e94, 0, 94}, + {1e95, 0, 94}, + {1e96, 0, 96}, + {1e97, 0, 97}, + {1e98, 0, 97}, + {1e99, 0, 98}, + {1e100, 0, 100}, + {1e101, 0, 100}, + {1e102, 0, 100}, + {1e103, 0, 103}, + {1e104, 0, 103}, + {1e105, 0, 104}, + {1e106, 0, 106}, + {1e107, 0, 106}, + {1e108, 0, 108}, + {1e109, 0, 108}, + {1e110, 0, 110}, + {1e111, 0, 110}, + {1e112, 0, 111}, + {1e113, 0, 113}, + {1e114, 0, 113}, + {1e115, 0, 113}, + {1e116, 0, 113}, + {1e117, 0, 117}, + {1e118, 0, 117}, + {1e119, 0, 118}, + {1e120, 0, 119}, + {1e121, 0, 121}, + {1e122, 0, 122}, + {1e123, 0, 122}, + {1e124, 0, 123}, + {1e125, 0, 124}, + {1e126, 0, 124}, + {1e127, 0, 126}, + {1e128, 0, 126}, + {1e129, 0, 128}, + {1e130, 0, 130}, + {1e131, 0, 130}, + {1e132, 0, 131}, + {1e133, 0, 133}, + {1e134, 0, 133}, + {1e135, 0, 134}, + {1e136, 0, 136}, + {1e137, 0, 137}, + {1e138, 0, 137}, + {1e139, 0, 137}, + {1e140, 0, 140}, + {1e141, 0, 141}, + {1e142, 0, 142}, + {1e143, 0, 143}, + {1e144, 0, 143}, + {1e145, 0, 144}, + {1e146, 0, 145}, + {1e147, 0, 146}, + {1e148, 0, 148}, + {1e149, 0, 148}, + {1e150, 0, 149}, + {1e151, 0, 151}, + {1e152, 0, 152}, + {1e153, 0, 152}, + {1e154, 0, 154}, + {1e155, 0, 155}, + {1e156, 0, 155}, + {1e157, 0, 155}, + {1e158, 0, 157}, + {1e159, 0, 158}, + {1e160, 0, 160}, + {1e161, 0, 161}, + {1e162, 0, 161}, + {1e163, 0, 161}, + {1e164, 0, 164}, + {1e165, 0, 164}, + {1e166, 0, 165}, + {1e167, 0, 167}, + {1e168, 0, 167}, + {1e169, 0, 167}, + {1e170, 0, 170}, + {1e171, 0, 170}, + {1e172, 0, 172}, + {1e173, 0, 173}, + {1e174, 0, 174}, + {1e175, 0, 174}, + {1e176, 0, 176}, + {1e177, 0, 176}, + {1e178, 0, 178}, + {1e179, 0, 178}, + {1e180, 0, 180}, + {1e181, 0, 180}, + {1e182, 0, 182}, + {1e183, 0, 182}, + {1e184, 0, 184}, + {1e185, 0, 184}, + {1e186, 0, 184}, + {1e187, 0, 186}, + {1e188, 0, 188}, + {1e189, 0, 188}, + {1e190, 0, 190}, + {1e191, 0, 190}, + {1e192, 0, 192}, + {1e193, 0, 193}, + {1e194, 0, 193}, + {1e195, 0, 194}, + {1e196, 0, 195}, + {1e197, 0, 195}, + {1e198, 0, 198}, + {1e199, 0, 199}, + {1e200, 0, 199}, + {1e201, 0, 201}, + {1e202, 0, 200}, + {1e203, 0, 202}, + {1e204, 0, 202}, + {1e205, 0, 205}, + {1e206, 0, 206}, + {1e207, 0, 206}, + {1e208, 0, 207}, + {1e209, 0, 209}, + {1e210, 0, 209}, + {1e211, 0, 210}, + {1e212, 0, 211}, + {1e213, 0, 212}, + {1e214, 0, 213}, + {1e215, 0, 214}, + {1e216, 0, 216}, + {1e217, 0, 216}, + {1e218, 0, 217}, + {1e219, 0, 218}, + {1e220, 0, 219}, + {1e221, 0, 221}, + {1e222, 0, 221}, + {1e223, 0, 221}, + {1e224, 0, 223}, + {1e225, 0, 224}, + {1e226, 0, 225}, + {1e227, 0, 225}, + {1e228, 0, 227}, + {1e229, 0, 228}, + {1e230, 0, 230}, + {1e231, 0, 231}, + {1e232, 0, 231}, + {1e233, 0, 232}, + {1e234, 0, 234}, + {1e235, 0, 235}, + {1e236, 0, 235}, + {1e237, 0, 235}, + {1e238, 0, 238}, + {1e239, 0, 238}, + {1e240, 0, 240}, + {1e241, 0, 241}, + {1e242, 0, 241}, + {1e243, 0, 243}, + {1e244, 0, 243}, + {1e245, 0, 245}, + {1e246, 0, 246}, + {1e247, 0, 246}, + {1e248, 0, 248}, + {1e249, 0, 247}, + {1e250, 0, 247}, + {1e251, 0, 251}, + {1e252, 0, 252}, + {1e253, 0, 252}, + {1e254, 0, 252}, + {1e255, 0, 254}, + {1e256, 0, 256}, + {1e257, 0, 256}, + {1e258, 0, 258}, + {1e259, 0, 258}, + {1e260, 0, 260}, + {1e261, 0, 258}, + {1e262, 0, 262}, + {1e263, 0, 262}, + {1e264, 0, 264}, + {1e265, 0, 265}, + {1e266, 0, 266}, + {1e267, 0, 266}, + {1e268, 0, 266}, + {1e269, 0, 269}, + {1e270, 0, 269}, + {1e271, 0, 270}, + {1e272, 0, 272}, + {1e273, 0, 272}, + {1e274, 0, 273}, + {1e275, 0, 274}, + {1e276, 0, 276}, + {1e277, 0, 277}, + {1e278, 0, 277}, + {1e279, 0, 279}, + {1e280, 0, 280}, + {1e281, 0, 280}, + {1e282, 0, 280}, + {1e283, 0, 282}, + {1e284, 0, 284}, + {1e285, 0, 284}, + {1e286, 0, 286}, + {1e287, 0, 287}, + {1e288, 0, 288}, + {1e289, 0, 289}, + {1e290, 0, 289}, + {1e291, 0, 290}, + {1e292, 0, 292}, + {1e293, 0, 292}, + {1e294, 0, 294}, + {1e295, 0, 294}, + {1e296, 0, 294}, + {1e297, 0, 297}, + {1e298, 0, 297}, + {1e299, 0, 299}, + {1e300, 0, 299}, + {1e301, 0, 299}, + {1e302, 0, 302}, + {1e303, 0, 303}, + {1e304, 0, 303}, + {1e305, 0, 303}, + {1e306, 0, 306}, + {1e307, 0, 306}, + {1e308, 0, 308}, +] of _ + +# These values test every binary exponent (which would be more obvious with hexfloats). +# The mantissas were randomly generated. +ALL_BINARY_EXPONENTS = [ + {8.667315560151837e-309, 1074, 765}, + {3.402496288854889e-308, 1072, 764}, + {5.674095874064163e-308, 1068, 760}, + {1.5540526173622352e-307, 1072, 765}, + {1.990320005135143e-307, 1071, 764}, + {5.107418539046847e-307, 1070, 763}, + {9.580341507134765e-307, 1068, 761}, + {2.7686246461533843e-306, 1067, 761}, + {4.123539674247709e-306, 1066, 760}, + {1.0292523314007669e-305, 1065, 760}, + {1.869562966771459e-305, 1065, 760}, + {2.83258275754601e-305, 1063, 758}, + {6.143575467580988e-305, 1061, 756}, + {1.4317575812076783e-304, 1062, 758}, + {1.8736954404407843e-304, 1061, 757}, + {3.8678085703767116e-304, 1060, 756}, + {7.635297514304952e-304, 1058, 754}, + {1.9931833457401843e-303, 1056, 753}, + {5.7104439440441904e-303, 1057, 754}, + {5.845773229837182e-303, 1056, 753}, + {1.6446596413123796e-302, 1054, 752}, + {3.571334515579776e-302, 1054, 752}, + {5.226450761688444e-302, 1053, 751}, + {1.0747997253788843e-301, 1051, 750}, + {1.90892926200005e-301, 1051, 750}, + {6.786272682763782e-301, 1047, 746}, + {1.2172372613102281e-300, 1044, 744}, + {1.7966676643662128e-300, 1042, 742}, + {5.462819694528703e-300, 1047, 747}, + {6.961706248722051e-300, 1043, 743}, + {1.5112507155402445e-299, 1045, 746}, + {2.673980873749012e-299, 1044, 745}, + {8.503369157355244e-299, 1043, 744}, + {1.3233106901096892e-298, 1042, 744}, + {2.129178977672023e-298, 1041, 743}, + {4.178648615235546e-298, 1036, 738}, + {9.216189377840373e-298, 1038, 740}, + {2.0166542228894894e-297, 1036, 739}, + {3.5449747515908894e-297, 1034, 737}, + {8.064939085743458e-297, 1036, 739}, + {2.1182119864764602e-296, 1035, 739}, + {3.820427653349436e-296, 1034, 738}, + {9.166969936802109e-296, 1033, 737}, + {1.0587007370833377e-295, 1031, 736}, + {2.5617734189585968e-295, 1030, 735}, + {6.117667163341597e-295, 1030, 735}, + {1.2442918509206014e-294, 1028, 734}, + {3.100051235126117e-294, 1027, 733}, + {5.514599643126014e-294, 1026, 732}, + {8.019518080011996e-294, 1025, 731}, + {2.2148549407571022e-293, 1022, 729}, + {4.20911452464387e-293, 1024, 731}, + {6.673970944986261e-293, 1022, 729}, + {1.746546364023377e-292, 1021, 729}, + {2.5532945426002905e-292, 1021, 729}, + {7.167199051004838e-292, 1020, 728}, + {1.4331413352279302e-291, 1018, 727}, + {1.6537479214140957e-291, 1018, 727}, + {4.290843909020547e-291, 1017, 726}, + {9.634563122144926e-291, 1014, 723}, + {2.1086057222075156e-290, 1010, 720}, + {3.4206800637910767e-290, 1013, 723}, + {6.629016758967737e-290, 1013, 723}, + {1.352094685757577e-289, 1011, 722}, + {3.065153212531983e-289, 1011, 722}, + {5.074022975467963e-289, 1006, 717}, + {1.5066401531008766e-288, 1009, 721}, + {2.8015937941494583e-288, 1008, 720}, + {5.38303645298669e-288, 1007, 719}, + {8.441234238050258e-288, 1006, 718}, + {1.7229963037415354e-287, 1004, 717}, + {4.998022788892795e-287, 1004, 717}, + {8.728697912491584e-287, 1003, 716}, + {1.9042271010497776e-286, 1002, 716}, + {2.9003728382951163e-286, 1000, 714}, + {6.030230011480532e-286, 999, 713}, + {1.2707290656410452e-285, 997, 712}, + {3.3470583063356332e-285, 998, 713}, + {6.5966578537292035e-285, 997, 712}, + {7.427398668320939e-285, 995, 710}, + {2.5312725644714646e-284, 994, 710}, + {4.7441324100905746e-284, 994, 710}, + {7.352811059453878e-284, 992, 708}, + {1.2773195914029405e-283, 990, 707}, + {3.1336514683809336e-283, 991, 708}, + {4.624339948076099e-283, 990, 707}, + {8.894087772740341e-283, 989, 706}, + {2.3953652083727512e-282, 988, 706}, + {3.761567466326268e-282, 985, 703}, + {1.2928212167160486e-281, 985, 704}, + {2.2480352182123753e-281, 982, 701}, + {3.154850939942071e-281, 983, 702}, + {6.071363984983044e-281, 982, 701}, + {2.1910202165183368e-280, 981, 701}, + {4.3370603492214e-280, 980, 700}, + {6.968471572869319e-280, 980, 700}, + {1.473331115243779e-279, 979, 700}, + {2.66044060949483e-279, 971, 692}, + {5.683973436284207e-279, 976, 697}, + {1.4089452631685327e-278, 976, 698}, + {2.2755564362864927e-278, 973, 695}, + {3.821906858286435e-278, 973, 695}, + {8.103117627864485e-278, 973, 695}, + {1.832473425427903e-277, 970, 693}, + {2.727222054531752e-277, 970, 693}, + {7.882910515028218e-277, 964, 687}, + {1.479332526434952e-276, 966, 690}, + {2.2180410620430226e-276, 968, 692}, + {5.690649743658698e-276, 966, 690}, + {1.4121156100358157e-275, 966, 691}, + {2.6685811370633676e-275, 965, 690}, + {4.6692815012344967e-275, 963, 688}, + {9.834548098669551e-275, 958, 683}, + {1.8600057734718372e-274, 962, 688}, + {2.811775675630843e-274, 961, 687}, + {6.27142702954425e-274, 959, 685}, + {1.0250975871333601e-273, 959, 686}, + {2.5253325080332935e-273, 958, 685}, + {6.913296125147658e-273, 957, 684}, + {9.747372287629775e-273, 956, 683}, + {1.9721670909138485e-272, 953, 681}, + {4.469048372679605e-272, 953, 681}, + {1.0205638445192075e-271, 952, 681}, + {1.5594946591458144e-271, 952, 681}, + {2.3721122629194564e-271, 949, 678}, + {5.551135919284155e-271, 944, 673}, + {1.0734739674446298e-270, 947, 677}, + {2.4398554262702667e-270, 948, 678}, + {6.8642158764630495e-270, 942, 672}, + {1.2055339469240307e-269, 941, 672}, + {1.5235738192618518e-269, 945, 676}, + {4.043876497442965e-269, 944, 675}, + {8.743556251142875e-269, 941, 672}, + {1.4649312749107113e-268, 941, 673}, + {3.98040680032538e-268, 941, 673}, + {8.586286660615516e-268, 938, 670}, + {1.657418018730156e-267, 939, 672}, + {3.613106834542997e-267, 937, 670}, + {5.9183241751764955e-267, 936, 669}, + {1.1798475245572565e-266, 935, 669}, + {3.084511928870661e-266, 934, 668}, + {4.1367697432326765e-266, 930, 664}, + {1.1356367562498619e-265, 933, 668}, + {1.5494624584252724e-265, 928, 663}, + {3.028575537095104e-265, 931, 666}, + {9.42740333348115e-265, 930, 665}, + {1.729174836691517e-264, 929, 665}, + {2.1531241335994103e-264, 928, 664}, + {7.094381157920946e-264, 927, 663}, + {1.037244238361319e-263, 922, 659}, + {1.6682266784627316e-263, 925, 662}, + {5.516139500818587e-263, 924, 661}, + {1.1456020464232508e-262, 919, 657}, + {2.066981873698563e-262, 919, 657}, + {2.7219184652406806e-262, 921, 659}, + {5.081283542674735e-262, 918, 656}, + {1.6876650530578208e-261, 917, 656}, + {2.276883127404377e-261, 916, 655}, + {6.29191834725416e-261, 917, 656}, + {9.114070309202437e-261, 915, 654}, + {2.5433112499173348e-260, 909, 649}, + {4.8383034142174994e-260, 914, 654}, + {9.557162041008455e-260, 913, 653}, + {2.3218163991190385e-259, 912, 653}, + {4.4322942901196377e-259, 911, 652}, + {7.248624545665186e-259, 904, 645}, + {1.4667081257360905e-258, 909, 651}, + {3.832202635359104e-258, 908, 650}, + {6.943148025853397e-258, 905, 647}, + {9.18729939902564e-258, 906, 648}, + {3.0584908958671776e-257, 905, 648}, + {3.332730679024704e-257, 901, 644}, + {7.495951055510854e-257, 902, 645}, + {1.6420266726944826e-256, 900, 644}, + {3.9297951542678785e-256, 900, 644}, + {8.581799124622545e-256, 896, 640}, + {1.3408011506316038e-255, 899, 644}, + {3.074598537137766e-255, 898, 643}, + {8.304947313449564e-255, 893, 638}, + {1.4474411243713317e-254, 893, 639}, + {1.9586457971591685e-254, 895, 641}, + {4.686715367492042e-254, 894, 640}, + {1.3394037849881335e-253, 893, 640}, + {2.3636119615931954e-253, 892, 639}, + {2.941610445695594e-253, 891, 638}, + {5.910628843543644e-253, 890, 637}, + {1.6810396848558274e-252, 889, 637}, + {3.105977744955943e-252, 887, 635}, + {5.296557380144569e-252, 886, 634}, + {1.4534181673457758e-251, 886, 635}, + {2.3938479247234097e-251, 885, 634}, + {5.13191114201037e-251, 884, 633}, + {8.74142907505523e-251, 883, 632}, + {2.0810847152471556e-250, 881, 631}, + {3.470210840000035e-250, 879, 629}, + {7.63186683615216e-250, 879, 629}, + {1.5834019337617896e-249, 872, 623}, + {2.760497402953147e-249, 876, 627}, + {4.645119455324602e-249, 877, 628}, + {9.91990200069646e-249, 874, 625}, + {2.5293584256227207e-248, 874, 626}, + {6.890528046616735e-248, 874, 626}, + {9.9700406511611e-248, 870, 622}, + {2.0741563200652303e-247, 872, 625}, + {4.6848861238967826e-247, 869, 622}, + {7.812879056117969e-247, 870, 623}, + {1.16023447415079e-246, 868, 622}, + {2.6285314099653654e-246, 867, 621}, + {7.24619526711458e-246, 866, 620}, + {1.513557579372713e-245, 864, 619}, + {2.2022598987138438e-245, 864, 619}, + {4.1410759136055036e-245, 864, 619}, + {1.0252325908734784e-244, 863, 619}, + {2.0526028348315092e-244, 861, 617}, + {3.9232668413253934e-244, 861, 617}, + {7.579615073559597e-244, 859, 615}, + {2.042372149921196e-243, 859, 616}, + {2.9353293314833334e-243, 858, 615}, + {6.372363858570045e-243, 855, 612}, + {1.575234414235322e-242, 854, 612}, + {3.3434436478293493e-242, 855, 613}, + {5.199566493700135e-242, 853, 611}, + {1.151222665835575e-241, 853, 612}, + {1.5543636774561637e-241, 850, 609}, + {4.079493099457495e-241, 850, 609}, + {8.503783454502844e-241, 850, 609}, + {2.0072820595479573e-240, 845, 605}, + {3.536438208901198e-240, 845, 605}, + {8.722809014595794e-240, 845, 605}, + {1.3749379101340066e-239, 846, 607}, + {3.05119881849728e-239, 845, 606}, + {6.2081425746751336e-239, 844, 605}, + {1.5212878641555803e-238, 841, 603}, + {2.521616635184641e-238, 842, 604}, + {4.498289788351931e-238, 841, 603}, + {7.28128014416453e-238, 834, 596}, + {2.0208837171448896e-237, 839, 602}, + {4.2234650668214423e-237, 837, 600}, + {8.084125743595665e-237, 836, 599}, + {1.6733590187416607e-236, 835, 599}, + {2.744824473945537e-236, 835, 599}, + {6.9330212812845956e-236, 832, 596}, + {9.887608848404935e-236, 831, 595}, + {1.8140520866375598e-235, 832, 597}, + {3.4634694166405886e-235, 831, 596}, + {1.0308725436052673e-234, 830, 596}, + {1.3123008280909585e-234, 829, 595}, + {3.87322423781539e-234, 825, 591}, + {9.2010739647813e-234, 827, 593}, + {1.1992483584063614e-233, 817, 584}, + {2.3452909097606e-233, 823, 590}, + {6.55832259537843e-233, 824, 591}, + {1.01023596160976e-232, 822, 590}, + {2.5973213970621353e-232, 822, 590}, + {4.955980006523515e-232, 821, 589}, + {1.1244377262803144e-231, 820, 589}, + {2.4089050126555283e-231, 819, 588}, + {3.1957993198054626e-231, 815, 584}, + {6.380419758740376e-231, 816, 585}, + {1.6095894110621704e-230, 816, 586}, + {4.032612821947131e-230, 813, 583}, + {6.0387918131443855e-230, 811, 581}, + {8.974323011245674e-230, 813, 583}, + {1.8798157375327938e-229, 812, 583}, + {6.1019603119906395e-229, 810, 581}, + {1.1326182622927228e-228, 808, 580}, + {1.3463284210193606e-228, 808, 580}, + {4.951126320251196e-228, 806, 578}, + {8.472853590985601e-228, 806, 578}, + {1.268665118648964e-227, 802, 575}, + {2.688551806792703e-227, 805, 578}, + {4.421464033750511e-227, 803, 576}, + {1.6504299596692555e-226, 803, 577}, + {2.290009142698998e-226, 799, 573}, + {4.095081668886285e-226, 800, 574}, + {1.15703960191472e-225, 799, 574}, + {1.8221726162720878e-225, 799, 574}, + {3.7536981380660996e-225, 798, 573}, + {7.40644242989838e-225, 797, 572}, + {1.5302655517853282e-224, 796, 572}, + {3.266244852085614e-224, 794, 570}, + {6.839580807204396e-224, 794, 570}, + {1.1596240640257126e-223, 792, 569}, + {2.137739829010208e-223, 792, 569}, + {5.154905434635419e-223, 790, 567}, + {7.890896642961214e-223, 790, 567}, + {2.4268259861629554e-222, 788, 566}, + {5.1376518013281177e-222, 788, 566}, + {6.49233931232799e-222, 785, 563}, + {1.5726294053843144e-221, 784, 563}, + {2.5630401696102993e-221, 783, 562}, + {7.564140571297787e-221, 784, 563}, + {1.572950734084536e-220, 783, 563}, + {3.270170075841635e-220, 782, 562}, + {3.794310430010385e-220, 781, 561}, + {7.2152364285788e-220, 780, 560}, + {2.6272169024527132e-219, 779, 560}, + {4.969214075705504e-219, 778, 559}, + {5.88301228914231e-219, 777, 558}, + {1.4743436359705467e-218, 776, 558}, + {3.4783661139951837e-218, 774, 556}, + {5.813216384022507e-218, 774, 556}, + {1.4584641434878504e-217, 769, 552}, + {2.3948318645722384e-217, 771, 554}, + {6.217084198272334e-217, 771, 554}, + {1.2105626076543997e-216, 770, 554}, + {2.7486687055906063e-216, 768, 552}, + {3.502765745049966e-216, 768, 552}, + {9.371878794082114e-216, 765, 549}, + {2.2015678401956502e-215, 766, 551}, + {3.05741111150528e-215, 765, 550}, + {5.373256321068217e-215, 764, 549}, + {1.0390415242853729e-214, 763, 549}, + {2.5621446229095083e-214, 761, 547}, + {6.657612640369708e-214, 759, 545}, + {1.0096315129023046e-213, 760, 547}, + {2.385050068827949e-213, 759, 546}, + {4.872323574333538e-213, 756, 543}, + {1.1263700063301884e-212, 757, 545}, + {1.7927923533931953e-212, 755, 543}, + {4.1945272696274144e-212, 755, 543}, + {7.22363072585383e-212, 752, 540}, + {9.64175435706941e-212, 753, 541}, + {2.805853499967036e-211, 752, 541}, + {7.509881594270143e-211, 750, 539}, + {1.4162133808983744e-210, 750, 540}, + {2.760873558001602e-210, 749, 539}, + {5.239340425140705e-210, 748, 538}, + {1.1985208897227799e-209, 747, 538}, + {1.7811558999586524e-209, 743, 534}, + {4.196264296353732e-209, 745, 536}, + {9.615178102174303e-209, 744, 535}, + {1.8427321798270692e-208, 742, 534}, + {2.9126998289538513e-208, 742, 534}, + {4.61528401072508e-208, 740, 532}, + {1.3913867725663096e-207, 735, 528}, + {1.7692162295985285e-207, 736, 529}, + {3.3309984084439476e-207, 738, 531}, + {6.851070184689407e-207, 736, 529}, + {2.3088921688496224e-206, 736, 530}, + {4.569104830644564e-206, 735, 529}, + {9.727078311637122e-206, 733, 527}, + {1.0256959546160606e-205, 733, 528}, + {3.8508135367330314e-205, 725, 520}, + {5.362873934497765e-205, 731, 526}, + {1.178267040698183e-204, 727, 523}, + {1.9633198540986484e-204, 727, 523}, + {5.684033913906095e-204, 728, 524}, + {1.0645321758200814e-203, 727, 524}, + {1.6758731287663245e-203, 726, 523}, + {4.3010538577728234e-203, 724, 521}, + {8.557459533511549e-203, 721, 518}, + {1.1995252022699291e-202, 723, 521}, + {3.224746096518046e-202, 719, 517}, + {5.329245319538759e-202, 720, 518}, + {8.264074174120558e-202, 719, 517}, + {2.6560666151344703e-201, 718, 517}, + {4.349521497293475e-201, 718, 517}, + {1.049722365714753e-200, 717, 517}, + {2.110793667228417e-200, 716, 516}, + {4.12255833883331e-200, 713, 513}, + {7.981480278624725e-200, 714, 514}, + {1.7718621999986475e-199, 711, 512}, + {4.0389599243158305e-199, 712, 513}, + {5.111949257928288e-199, 711, 512}, + {1.0079029980377604e-198, 710, 512}, + {3.3000803685506845e-198, 709, 511}, + {5.525608163454049e-198, 708, 510}, + {1.3309156466126676e-197, 706, 509}, + {2.537666484635188e-197, 704, 507}, + {4.1725845639630823e-197, 702, 505}, + {8.054891565255361e-197, 700, 503}, + {1.5502849382509784e-196, 701, 505}, + {3.36121320874639e-196, 702, 506}, + {7.484551181550847e-196, 700, 504}, + {8.735452590688936e-196, 699, 503}, + {2.178462933634283e-195, 698, 503}, + {3.537794875075355e-195, 698, 503}, + {1.2853400648125002e-194, 694, 500}, + {2.4171125083478186e-194, 694, 500}, + {5.266324045366901e-194, 691, 497}, + {8.478256274282183e-194, 692, 498}, + {1.1953794466200875e-193, 693, 500}, + {2.260950903334843e-193, 691, 498}, + {6.350686450423479e-193, 690, 497}, + {1.6719092307496788e-192, 690, 498}, + {2.8861375664964044e-192, 686, 494}, + {6.643795862244709e-192, 687, 495}, + {1.2021435877944513e-191, 687, 496}, + {1.7389964555399593e-191, 686, 495}, + {5.506102911439653e-191, 684, 493}, + {6.118268064240606e-191, 682, 491}, + {2.0188412709311976e-190, 683, 493}, + {3.623294624463911e-190, 681, 491}, + {6.109098683649796e-190, 681, 491}, + {1.0644449452139396e-189, 680, 491}, + {2.2861786222533955e-189, 679, 490}, + {5.1576905486221095e-189, 678, 489}, + {1.4282019181687436e-188, 677, 489}, + {2.5645102658000638e-188, 676, 488}, + {5.24708561795544e-188, 675, 487}, + {1.0885720130033328e-187, 674, 487}, + {2.155560898279388e-187, 673, 486}, + {3.40475602113904e-187, 669, 482}, + {8.113439450169649e-187, 669, 482}, + {1.2501335652550157e-186, 669, 483}, + {2.921645514519799e-186, 667, 481}, + {4.02036891910035e-186, 668, 482}, + {1.1406014918543049e-185, 667, 482}, + {2.234456407425393e-185, 666, 481}, + {3.119559897062789e-185, 665, 480}, + {7.840889282617378e-185, 663, 478}, + {1.913574910036887e-184, 663, 479}, + {4.522727919382536e-184, 662, 478}, + {8.399922966296771e-184, 661, 477}, + {1.6000703209667126e-183, 660, 477}, + {2.579345132582455e-183, 656, 473}, + {5.82883665073076e-183, 657, 474}, + {1.1477041919553152e-182, 655, 473}, + {2.162629407013832e-182, 653, 471}, + {3.534083085250713e-182, 655, 473}, + {1.1362518681203582e-181, 654, 473}, + {1.374874967440177e-181, 652, 471}, + {2.435650549099599e-181, 652, 471}, + {7.063226461599998e-181, 651, 470}, + {1.502554163537965e-180, 650, 470}, + {2.3571270014353365e-180, 648, 468}, + {6.74887905042332e-180, 647, 467}, + {8.516860687556732e-180, 645, 465}, + {3.055786593521296e-179, 642, 463}, + {4.069012575835782e-179, 645, 466}, + {1.0237088948578388e-178, 644, 466}, + {1.54322893356522e-178, 643, 465}, + {4.787867741637937e-178, 641, 463}, + {6.628678990891977e-178, 639, 461}, + {1.6597719647556535e-177, 638, 461}, + {3.9129616361835475e-177, 638, 461}, + {4.917433318520185e-177, 637, 460}, + {1.4065772131663602e-176, 637, 461}, + {2.9943788764426967e-176, 636, 460}, + {5.798827328485947e-176, 633, 457}, + {6.383737379183111e-176, 629, 453}, + {1.702914739406024e-175, 631, 456}, + {3.8762215209996965e-175, 632, 457}, + {6.878988216191751e-175, 631, 456}, + {1.4887562197711447e-174, 629, 455}, + {3.5651743098904664e-174, 628, 454}, + {4.061595423920378e-174, 628, 454}, + {1.080598113210515e-173, 626, 453}, + {2.610049033590998e-173, 626, 453}, + {5.479224332391463e-173, 625, 452}, + {1.045438016267349e-172, 624, 452}, + {1.6069032799845684e-172, 623, 451}, + {3.231763455627856e-172, 621, 449}, + {8.881611379300798e-172, 621, 449}, + {2.034516015869994e-171, 620, 449}, + {2.1811364732319512e-171, 618, 447}, + {8.077406891608053e-171, 618, 447}, + {1.3085617726519542e-170, 617, 447}, + {1.8431087174635782e-170, 616, 446}, + {4.132806492532133e-170, 613, 443}, + {1.2069870317233675e-169, 613, 444}, + {2.5119843525307734e-169, 610, 441}, + {4.9132134419173275e-169, 612, 443}, + {7.82202882219777e-169, 611, 442}, + {1.4528932941845978e-168, 605, 437}, + {2.1572028357831315e-168, 608, 440}, + {6.050734210571264e-168, 607, 439}, + {1.4678367854256142e-167, 605, 438}, + {3.164114305557189e-167, 606, 439}, + {5.701265996213117e-167, 605, 438}, + {1.0449784962477288e-166, 603, 437}, + {1.412310305063278e-166, 603, 437}, + {3.0165478216464075e-166, 602, 436}, + {6.935268655974538e-166, 601, 435}, + {1.5523915631659218e-165, 599, 434}, + {3.204088122440647e-165, 599, 434}, + {5.708706599661439e-165, 596, 431}, + {1.3233342513427447e-164, 594, 430}, + {2.902749268222377e-164, 594, 430}, + {4.005031318533225e-164, 594, 430}, + {1.370839408075575e-163, 593, 430}, + {2.5492926252346404e-163, 592, 429}, + {4.5041091507036546e-163, 592, 429}, + {6.727683165794e-163, 591, 428}, + {1.2036075452964588e-162, 590, 428}, + {3.694027105345616e-162, 588, 426}, + {8.037749748602946e-162, 588, 426}, + {1.5950567846572765e-161, 587, 426}, + {3.2198008776677366e-161, 581, 420}, + {4.1052288903310634e-161, 585, 424}, + {1.1247015311011619e-160, 584, 424}, + {2.8199919854660424e-160, 581, 421}, + {3.44065016480784e-160, 582, 422}, + {1.0582157294697289e-159, 581, 422}, + {1.3388582036266356e-159, 580, 421}, + {3.9808070524262983e-159, 578, 419}, + {6.84874465910972e-159, 577, 418}, + {1.5742030359655316e-158, 577, 419}, + {2.9202734088397783e-158, 576, 418}, + {4.963143223862366e-158, 575, 417}, + {1.2414975541479183e-157, 574, 417}, + {1.7875996042719942e-157, 573, 416}, + {4.442886865786028e-157, 572, 415}, + {9.640850940808092e-157, 570, 413}, + {1.3646071402129266e-156, 566, 410}, + {3.95713245503973e-156, 567, 411}, + {7.069217936658262e-156, 568, 412}, + {1.203556015715792e-155, 566, 411}, + {2.317281696978397e-155, 566, 411}, + {6.4262723679026235e-155, 565, 410}, + {9.730107236224658e-155, 563, 408}, + {2.2224325771347584e-154, 558, 404}, + {5.423112811224111e-154, 562, 408}, + {7.326435394881725e-154, 558, 404}, + {2.3801222320149625e-153, 560, 407}, + {3.0618730536691986e-153, 559, 406}, + {8.347194867235908e-153, 555, 402}, + {1.3657896771381836e-152, 555, 403}, + {2.7059739547586404e-152, 555, 403}, + {7.577545297109583e-152, 555, 403}, + {1.1796090875694487e-151, 553, 402}, + {2.794960489625111e-151, 551, 400}, + {3.2753828239853734e-151, 548, 397}, + {9.593803205082154e-151, 549, 398}, + {1.582863447572883e-150, 549, 399}, + {2.6064697688781666e-150, 546, 396}, + {9.557214378356368e-150, 546, 396}, + {1.191935964515715e-149, 546, 397}, + {2.433444775335062e-149, 545, 396}, + {6.832519569622276e-149, 535, 386}, + {8.70223166700988e-149, 544, 395}, + {2.9861508065908777e-148, 543, 395}, + {4.817811775899338e-148, 540, 392}, + {9.297672441478371e-148, 540, 392}, + {2.4845337797454605e-147, 535, 388}, + {2.885736152741721e-147, 536, 389}, + {7.338298742534727e-147, 538, 391}, + {1.6347598189352822e-146, 537, 391}, + {3.7793226434705114e-146, 536, 390}, + {4.216777346143084e-146, 535, 389}, + {1.5906736035433479e-145, 534, 389}, + {2.8672951771554432e-145, 527, 382}, + {3.4379041633188434e-145, 532, 387}, + {9.483896231013576e-145, 531, 386}, + {2.1982900216496952e-144, 530, 386}, + {4.601395928544956e-144, 528, 384}, + {7.926089617836489e-144, 528, 384}, + {1.126604874345762e-143, 527, 384}, + {3.7906989762760977e-143, 526, 383}, + {6.436458682719877e-143, 525, 382}, + {1.4199943377251188e-142, 521, 379}, + {2.9387007315399e-142, 523, 381}, + {4.321275750819566e-142, 520, 378}, + {7.47956749010095e-142, 520, 378}, + {1.598743985708575e-141, 520, 379}, + {3.8772543479398746e-141, 519, 378}, + {8.857290458084179e-141, 518, 377}, + {1.4852721614344735e-140, 517, 377}, + {3.2114166221217845e-140, 516, 376}, + {6.843053332100658e-140, 515, 375}, + {1.565107103951972e-139, 514, 375}, + {2.0617783292012132e-139, 513, 374}, + {5.660205412204873e-139, 511, 372}, + {9.624163455997459e-139, 509, 370}, + {2.1752909285251192e-138, 509, 371}, + {4.3072629120188103e-138, 509, 371}, + {8.87808969808457e-138, 507, 369}, + {1.2102860505856427e-137, 506, 369}, + {4.0538881189604875e-137, 506, 369}, + {4.942621032233825e-137, 503, 366}, + {1.5411744957634479e-136, 502, 366}, + {2.217010614359645e-136, 502, 366}, + {6.356757540897539e-136, 499, 363}, + {9.59982670654558e-136, 501, 365}, + {2.6352597315051092e-135, 500, 365}, + {4.4355852076232534e-135, 497, 362}, + {5.742999336614182e-135, 498, 363}, + {1.5533843624429324e-134, 497, 363}, + {2.38869404724781e-134, 496, 362}, + {4.887121523587299e-134, 492, 358}, + {1.2048289504187996e-133, 494, 361}, + {2.6488657681982224e-133, 491, 358}, + {5.6050315915746605e-133, 489, 356}, + {1.2786580967537967e-132, 489, 357}, + {2.084833548391119e-132, 490, 358}, + {3.2003528362030217e-132, 488, 356}, + {1.0296499233307166e-131, 488, 357}, + {2.24263947672835e-131, 486, 355}, + {2.4568729171490547e-131, 485, 354}, + {6.620060796036904e-131, 483, 352}, + {1.4197241995605936e-130, 484, 354}, + {3.6028982430553334e-130, 482, 352}, + {4.887939370696328e-130, 481, 351}, + {7.31748922552339e-130, 481, 351}, + {2.342962029692207e-129, 479, 350}, + {3.2020192787585524e-129, 479, 350}, + {6.424759263634416e-129, 478, 349}, + {1.2342695551721854e-128, 476, 348}, + {4.4587660431030846e-128, 476, 348}, + {8.593040354013083e-128, 471, 343}, + {1.2304010474281754e-127, 474, 347}, + {2.5763001930484683e-127, 473, 346}, + {3.839904229353329e-127, 469, 342}, + {1.1107053483914007e-126, 470, 344}, + {2.62113368468091e-126, 469, 343}, + {3.9436012113465104e-126, 468, 342}, + {1.1279031828486725e-125, 467, 342}, + {1.359122081875358e-125, 467, 342}, + {4.718684877818967e-125, 466, 341}, + {9.34382880965103e-125, 464, 339}, + {1.3234786366979427e-124, 463, 339}, + {3.421581321410546e-124, 463, 339}, + {7.366599240502793e-124, 461, 337}, + {1.2895722651891138e-123, 460, 337}, + {2.921936515059182e-123, 455, 332}, + {3.2696466282687586e-123, 454, 331}, + {6.399893206284219e-123, 458, 335}, + {1.4998583170769895e-122, 456, 334}, + {3.302628563203096e-122, 456, 334}, + {5.784701017386636e-122, 454, 332}, + {1.2327531669436545e-121, 454, 333}, + {3.625335804997574e-121, 451, 330}, + {6.509903526347757e-121, 451, 330}, + {1.0362549937904696e-120, 448, 328}, + {2.8372763744588855e-120, 448, 328}, + {4.695776279670974e-120, 449, 329}, + {9.262841373599042e-120, 448, 328}, + {1.864465441767582e-119, 445, 326}, + {4.010086210363826e-119, 444, 325}, + {9.843181783316012e-119, 445, 326}, + {1.8986595125046417e-118, 438, 320}, + {3.508320544508897e-118, 443, 325}, + {4.943071564858405e-118, 440, 322}, + {1.4386748780689463e-117, 441, 324}, + {2.1750535480226044e-117, 440, 323}, + {3.5554120237464135e-117, 439, 322}, + {1.2023735450313651e-116, 432, 316}, + {2.4073250149223044e-116, 437, 321}, + {4.1701370789218156e-116, 434, 318}, + {1.0102559419519476e-115, 435, 320}, + {1.5737569358633548e-115, 433, 318}, + {3.5846791147293954e-115, 433, 318}, + {6.392064227463433e-115, 428, 313}, + {1.5412187845587149e-114, 429, 315}, + {2.3399657115069453e-114, 430, 316}, + {6.407770724945315e-114, 429, 315}, + {1.1069178065391464e-113, 427, 314}, + {1.573395880167279e-113, 426, 313}, + {2.951264303968299e-113, 425, 312}, + {8.761895839903948e-113, 425, 312}, + {1.4581718353001727e-112, 421, 309}, + {3.964464541482814e-112, 423, 311}, + {7.87961036169431e-112, 422, 310}, + {1.4076373030698913e-111, 421, 310}, + {3.164529578942993e-111, 418, 307}, + {4.67946374062146e-111, 419, 308}, + {1.1022787950727772e-110, 417, 307}, + {1.670185551585791e-110, 416, 306}, + {3.270479232134614e-110, 416, 306}, + {8.778772927538465e-110, 413, 303}, + {1.4356894911231068e-109, 412, 303}, + {3.2111903570682556e-109, 407, 298}, + {4.416972490924674e-109, 412, 303}, + {1.6902188100294219e-108, 411, 303}, + {2.7486157518272163e-108, 407, 299}, + {5.3211348201503956e-108, 408, 300}, + {8.333478562393578e-108, 408, 300}, + {1.8401463566583057e-107, 407, 300}, + {5.376784423862267e-107, 405, 298}, + {6.333829374806563e-107, 405, 298}, + {1.7514400642486577e-106, 403, 297}, + {2.3358765579576565e-106, 403, 297}, + {4.724398661049588e-106, 402, 296}, + {1.2692169671992972e-105, 400, 295}, + {1.963131601289876e-105, 398, 293}, + {4.342989487256073e-105, 397, 292}, + {8.812616575391183e-105, 397, 292}, + {1.692966213667821e-104, 397, 293}, + {4.242001623031538e-104, 393, 289}, + {6.030529809392781e-104, 395, 291}, + {1.5137520077480203e-103, 394, 291}, + {4.1339493171608476e-103, 393, 290}, + {6.201035931513464e-103, 392, 289}, + {1.3967279728811321e-102, 391, 289}, + {2.6059428457609154e-102, 390, 288}, + {5.292565718610749e-102, 389, 287}, + {1.2698160651847153e-101, 388, 287}, + {2.547070634124935e-101, 384, 283}, + {4.084116507906642e-101, 384, 283}, + {6.852426249982067e-101, 380, 279}, + {1.8083260374699235e-100, 382, 282}, + {4.1041218769683544e-100, 382, 282}, + {6.059640250896112e-100, 381, 281}, + {1.4091162264655094e-99, 381, 282}, + {2.1541940438019325e-99, 380, 281}, + {4.956208739898403e-99, 379, 280}, + {8.634882168235289e-99, 374, 275}, + {2.426134041499035e-98, 377, 279}, + {5.688034203287511e-98, 375, 277}, + {7.42082070481487e-98, 371, 273}, + {1.4938910681786228e-97, 371, 274}, + {3.3198659912779945e-97, 370, 273}, + {5.663431628653758e-97, 370, 273}, + {1.7437369721819611e-96, 371, 275}, + {3.535005437982104e-96, 370, 274}, + {5.461560619344413e-96, 366, 270}, + {1.1364847491964382e-95, 362, 267}, + {2.3020007806568932e-95, 366, 271}, + {3.7529890242406666e-95, 366, 271}, + {7.38338448129336e-95, 365, 270}, + {1.3934768122159904e-94, 360, 266}, + {2.504286566562723e-94, 362, 268}, + {5.356123085643122e-94, 360, 266}, + {1.3829457207184735e-93, 357, 264}, + {2.922539729701329e-93, 358, 265}, + {4.873642795487652e-93, 359, 266}, + {1.1847214117665038e-92, 358, 266}, + {1.6641279667471598e-92, 356, 264}, + {5.8818504424690985e-92, 356, 264}, + {8.169815841110726e-92, 353, 261}, + {1.5452699464889383e-91, 354, 263}, + {3.659395376290149e-91, 353, 262}, + {8.251660766595778e-91, 350, 259}, + {1.4826450102010818e-90, 351, 261}, + {3.280747503211524e-90, 350, 260}, + {5.042127927662887e-90, 349, 259}, + {1.1071352931313988e-89, 348, 259}, + {1.8434860272299224e-89, 346, 257}, + {4.503629714546149e-89, 343, 254}, + {7.978949976696619e-89, 345, 256}, + {2.5118610569922107e-88, 344, 256}, + {2.574328439038412e-88, 342, 254}, + {7.991143060138612e-88, 341, 253}, + {1.584115281757909e-87, 341, 254}, + {2.47707398648512e-87, 340, 253}, + {5.1571453461558415e-87, 339, 252}, + {9.677861708438445e-87, 338, 251}, + {3.06005556558652e-86, 337, 251}, + {4.078986710839316e-86, 336, 250}, + {8.39906286351288e-86, 332, 246}, + {1.992717826656166e-85, 332, 247}, + {4.771839231117578e-85, 332, 247}, + {9.178454461057711e-85, 332, 247}, + {1.363704451806317e-84, 330, 246}, + {3.8533883620323424e-84, 325, 241}, + {6.032190925744832e-84, 328, 244}, + {1.5705072660535393e-83, 326, 243}, + {2.2134072641839148e-83, 326, 243}, + {4.218896315696479e-83, 326, 243}, + {1.0992259537194823e-82, 322, 240}, + {2.4840542381260296e-82, 322, 240}, + {2.689980129196105e-82, 321, 239}, + {7.169144283915297e-82, 320, 238}, + {1.1408995211686882e-81, 318, 237}, + {3.6501624740693436e-81, 320, 239}, + {6.558396567940629e-81, 319, 238}, + {9.973540436014331e-81, 317, 236}, + {2.1285263571540236e-80, 316, 236}, + {5.738667020230138e-80, 315, 235}, + {1.2858099560631301e-79, 315, 236}, + {1.5440597737151946e-79, 314, 235}, + {5.085041310658149e-79, 312, 233}, + {7.85989179025863e-79, 310, 231}, + {1.5008325529570412e-78, 308, 230}, + {3.3480454826799275e-78, 306, 228}, + {8.497568581224052e-78, 309, 231}, + {1.6394772414307023e-77, 308, 231}, + {2.901315501375183e-77, 302, 225}, + {3.82611804506936e-77, 305, 228}, + {7.235982721954788e-77, 304, 227}, + {2.6324142915079567e-76, 301, 225}, + {3.0439003831515616e-76, 302, 226}, + {8.685053970949932e-76, 299, 223}, + {1.2427314157729456e-75, 301, 226}, + {3.398379710807704e-75, 295, 220}, + {7.841658923587687e-75, 292, 217}, + {1.3291407760196483e-74, 297, 223}, + {2.902089657903194e-74, 297, 223}, + {6.8493063311021444e-74, 295, 221}, + {1.0308144847135354e-73, 295, 222}, + {1.827277123300593e-73, 292, 219}, + {4.9351506696419103e-73, 292, 219}, + {1.0781250168794166e-72, 292, 220}, + {1.7181272168700196e-72, 289, 217}, + {4.2659307529076217e-72, 290, 218}, + {8.264425689667123e-72, 289, 217}, + {1.6273802065732598e-71, 288, 217}, + {1.8350586944381438e-71, 287, 216}, + {4.348286154284214e-71, 286, 215}, + {8.6134539761559e-71, 285, 214}, + {2.5905952437250456e-70, 284, 214}, + {3.406827648187512e-70, 279, 209}, + {6.61570075213638e-70, 281, 211}, + {1.7253632008143088e-69, 281, 212}, + {3.3892588392445133e-69, 280, 211}, + {4.939790429145187e-69, 278, 209}, + {1.47761815700755e-68, 278, 210}, + {3.538839275393052e-68, 277, 209}, + {4.821856338577311e-68, 276, 208}, + {1.0037463923948035e-67, 275, 208}, + {1.8046301120677578e-67, 272, 205}, + {5.037756265273766e-67, 273, 206}, + {7.294226364184892e-67, 271, 204}, + {1.7260576914043516e-66, 271, 205}, + {2.413521126424911e-66, 270, 204}, + {6.085503028299745e-66, 268, 202}, + {1.357171943220921e-65, 267, 202}, + {3.0836891319146543e-65, 267, 202}, + {6.537134301197315e-65, 263, 198}, + {8.131245964149248e-65, 265, 200}, + {1.8559814129506474e-64, 261, 197}, + {3.449567762615896e-64, 263, 199}, + {7.688715671540768e-64, 262, 198}, + {2.007143911426273e-63, 259, 196}, + {3.990126450920154e-63, 258, 195}, + {6.173618951228879e-63, 258, 195}, + {1.4822815568311571e-62, 258, 196}, + {3.8189270099686454e-62, 256, 194}, + {4.82120756263091e-62, 254, 192}, + {1.3790405547409109e-61, 255, 194}, + {2.6993809501677147e-61, 254, 193}, + {5.23202189544879e-61, 253, 192}, + {8.267169964142634e-61, 249, 188}, + {1.9276428005917082e-60, 251, 191}, + {2.7720738716829783e-60, 246, 186}, + {6.309714925893337e-60, 248, 188}, + {1.4603881466067395e-59, 243, 184}, + {2.390974995457393e-59, 247, 188}, + {7.148682183253571e-59, 246, 187}, + {9.322651868323872e-59, 245, 186}, + {2.202049881264495e-58, 242, 184}, + {5.387261159597293e-58, 242, 184}, + {8.629622859499133e-58, 240, 182}, + {2.1518906041456637e-57, 239, 182}, + {3.3393236367487655e-57, 238, 181}, + {8.670831372094515e-57, 238, 181}, + {1.8197046818766603e-56, 234, 178}, + {2.2260313359925405e-56, 237, 181}, + {4.302660672241003e-56, 234, 178}, + {1.0540262429918154e-55, 233, 178}, + {3.0681879985386226e-55, 232, 177}, + {5.481823354294243e-55, 233, 178}, + {1.0702958332703833e-54, 231, 177}, + {2.0733146515683786e-54, 225, 171}, + {3.663911049484028e-54, 230, 176}, + {7.133428824499037e-54, 227, 173}, + {1.155049021815273e-53, 226, 173}, + {2.1292639261534582e-53, 227, 174}, + {7.116291893522144e-53, 225, 172}, + {1.563247190926505e-52, 224, 172}, + {2.8724553500903598e-52, 223, 171}, + {6.558170706952157e-52, 221, 169}, + {1.1006685169756963e-51, 220, 169}, + {1.7539896072768475e-51, 220, 169}, + {3.7433921715043595e-51, 218, 167}, + {9.95642899800938e-51, 219, 168}, + {1.1636497924322522e-50, 218, 168}, + {3.715838411594935e-50, 217, 167}, + {6.580569634177383e-50, 212, 162}, + {1.3361521745438593e-49, 206, 157}, + {1.758805859472407e-49, 213, 164}, + {3.5728259387967836e-49, 213, 164}, + {1.3532863609445875e-48, 212, 164}, + {2.3787372002095833e-48, 211, 163}, + {2.962627533001959e-48, 210, 162}, + {9.002988068912365e-48, 208, 160}, + {1.6528407152844145e-47, 208, 161}, + {2.475761918536981e-47, 206, 159}, + {4.442389874949008e-47, 200, 153}, + {1.597036901212251e-46, 205, 159}, + {2.7344299616431544e-46, 203, 157}, + {4.923161560346421e-46, 203, 157}, + {1.0179995696026282e-45, 202, 157}, + {2.0723091166540274e-45, 201, 156}, + {4.7196310508913905e-45, 199, 154}, + {8.839991027458473e-45, 198, 153}, + {1.793400495465429e-44, 197, 153}, + {4.229568359105647e-44, 197, 153}, + {6.736360002384042e-44, 196, 152}, + {1.777871026025537e-43, 194, 151}, + {3.3315696595658846e-43, 194, 151}, + {4.002301960990423e-43, 191, 148}, + {1.1156841616796605e-42, 192, 150}, + {2.1966221825446394e-42, 191, 149}, + {5.676537374800469e-42, 188, 146}, + {1.1265477503439073e-41, 189, 148}, + {2.1310740108629267e-41, 188, 147}, + {3.47261690499443e-41, 187, 146}, + {5.986279457346766e-41, 186, 145}, + {1.256777780832933e-40, 185, 145}, + {3.178582703527919e-40, 182, 142}, + {4.8131885839189464e-40, 182, 142}, + {1.050509091106282e-39, 180, 141}, + {2.3469216079446797e-39, 181, 142}, + {3.701509845820645e-39, 179, 140}, + {7.539508429995885e-39, 172, 133}, + {1.6455979868696784e-38, 176, 138}, + {2.8896506750036806e-38, 176, 138}, + {7.637447758897944e-38, 176, 138}, + {1.742931242211031e-37, 171, 134}, + {3.658831567832271e-37, 171, 134}, + {6.4153225021810435e-37, 173, 136}, + {1.2882866599344565e-36, 172, 136}, + {2.2438898905531304e-36, 171, 135}, + {5.427246992721223e-36, 170, 134}, + {1.0773562313702051e-35, 168, 133}, + {1.8138709823870135e-35, 167, 132}, + {3.486395423440646e-35, 167, 132}, + {7.444961768380648e-35, 166, 131}, + {1.5947728558224363e-34, 164, 130}, + {3.6958300841764796e-34, 163, 129}, + {4.902792957147279e-34, 162, 128}, + {1.3763280779744356e-33, 162, 129}, + {2.4567652907408955e-33, 161, 128}, + {5.775941398121507e-33, 160, 127}, + {6.76218542428814e-33, 158, 125}, + {1.9564777034032247e-32, 156, 124}, + {3.7630713133101953e-32, 153, 121}, + {5.69811750812064e-32, 156, 124}, + {1.6901389263110543e-31, 154, 123}, + {2.416607441957109e-31, 154, 123}, + {5.310587796480732e-31, 152, 121}, + {1.272832974790223e-30, 150, 120}, + {2.758607109138819e-30, 151, 121}, + {6.158392474838414e-30, 147, 117}, + {9.79111897583252e-30, 149, 119}, + {2.1005583908999636e-29, 146, 117}, + {3.268892213509477e-29, 147, 118}, + {9.861264878636652e-29, 146, 117}, + {1.9807195772041245e-28, 145, 117}, + {2.459477005766037e-28, 143, 115}, + {7.134230712364223e-28, 141, 113}, + {1.3485130668183938e-27, 142, 115}, + {1.7558778301243823e-27, 140, 113}, + {4.603883324842795e-27, 139, 112}, + {6.787292759429055e-27, 139, 112}, + {2.338893604866531e-26, 138, 112}, + {5.0522652313759764e-26, 136, 110}, + {5.453193492635159e-26, 136, 110}, + {1.0432046065743156e-25, 134, 109}, + {3.459820865051479e-25, 134, 109}, + {4.919945865610923e-25, 133, 108}, + {1.1905894569192663e-24, 131, 107}, + {3.1044959876029037e-24, 129, 105}, + {5.874569663977718e-24, 125, 101}, + {1.0765469843178544e-23, 128, 105}, + {1.6374325809042254e-23, 128, 105}, + {5.135092112079404e-23, 127, 104}, + {9.161408014517678e-23, 126, 103}, + {1.1058854487286022e-22, 125, 103}, + {2.4417307811691994e-22, 124, 102}, + {6.962426049274539e-22, 121, 99}, + {1.3365747882614801e-21, 121, 100}, + {3.1416350487962487e-21, 118, 97}, + {6.17210332953516e-21, 120, 99}, + {7.67358493865774e-21, 114, 93}, + {1.5483757149089377e-20, 118, 98}, + {4.124682451133355e-20, 117, 97}, + {9.516929012310325e-20, 115, 95}, + {1.828195094563647e-19, 115, 96}, + {4.2349766597973143e-19, 114, 95}, + {5.027552313830036e-19, 106, 87}, + {1.4064120230650801e-18, 112, 94}, + {1.9740440391270057e-18, 111, 93}, + {5.043775268437966e-18, 110, 92}, + {1.133834572888795e-17, 109, 92}, + {2.5020470223212867e-17, 108, 91}, + {3.702836080714707e-17, 107, 90}, + {6.060941731133067e-17, 104, 87}, + {1.4669586812661773e-16, 105, 89}, + {3.686977051305637e-16, 104, 88}, + {4.80324090949893e-16, 101, 85}, + {1.3780266196844444e-15, 97, 82}, + {2.1377242939848385e-15, 101, 86}, + {6.212619127432843e-15, 99, 84}, + {1.1594451093913135e-14, 98, 84}, + {2.602091364932322e-14, 98, 84}, + {3.756321509921839e-14, 97, 83}, + {8.208072800557448e-14, 96, 82}, + {1.922432441672551e-13, 95, 82}, + {3.3711192675176746e-13, 92, 79}, + {7.644553608444343e-13, 91, 78}, + {1.2428666212744918e-12, 92, 80}, + {2.218814409588626e-12, 91, 79}, + {5.927330708414901e-12, 90, 78}, + {1.1828210767544923e-11, 86, 75}, + {2.0300112761836498e-11, 88, 77}, + {2.9825408935925034e-11, 85, 74}, + {7.41800536873849e-11, 86, 75}, + {2.3280428024704825e-10, 83, 73}, + {3.067358161655257e-10, 83, 73}, + {8.26606194612068e-10, 78, 68}, + {1.1606565070959597e-9, 82, 73}, + {2.25770689068302e-9, 79, 70}, + {4.2174650747812235e-9, 78, 69}, + {1.4887528604095427e-8, 78, 70}, + {2.245874199067278e-8, 78, 70}, + {3.8693071250734114e-8, 77, 69}, + {9.867450456369697e-8, 76, 68}, + {1.2770978278302815e-7, 72, 65}, + {3.9274196153024064e-7, 69, 62}, + {7.032112886574409e-7, 71, 64}, + {1.334954758379263e-6, 72, 66}, + {3.4751715684057034e-6, 71, 65}, + {5.621298897168876e-6, 62, 56}, + {9.338413843761515e-6, 69, 63}, + {2.834895459445889e-5, 68, 63}, + {4.8806263333083725e-5, 63, 58}, + {6.981573871413016e-5, 64, 59}, + {1.5709595512811012e-4, 64, 60}, + {4.8573351579579444e-4, 60, 56}, + {9.689777316624939e-4, 63, 59}, + {1.6816816128152761e-3, 62, 59}, + {2.6297079760909843e-3, 58, 55}, + {7.23583892421345e-3, 55, 52}, + {1.0321932464352318e-2, 59, 57}, + {2.643880607664138e-2, 58, 56}, + {4.7182495515037774e-2, 57, 55}, + {1.190207776709587e-1, 54, 53}, + {2.0454888297013502e-1, 54, 53}, + {4.470011069997743e-1, 54, 53}, + {7.22094745351015e-1, 53, 52}, + {1.3799513502451215e+0, 51, 51}, + {3.417399494666547e+0, 51, 51}, + {5.281345514239382e+0, 49, 49}, + {1.3580697413368053e+1, 48, 49}, + {1.854546242122947e+1, 48, 49}, + {4.1257609000643654e+1, 46, 47}, + {1.1980413676630091e+2, 46, 48}, + {2.3573434179257086e+2, 45, 47}, + {4.514328266785161e+2, 43, 45}, + {8.043055296192766e+2, 43, 45}, + {1.24858959528734e+3, 42, 45}, + {3.5482738915027107e+3, 41, 44}, + {4.3128420357200675e+3, 40, 43}, + {1.5941359869713324e+4, 38, 42}, + {2.497338563971861e+4, 37, 41}, + {4.518139121214868e+4, 35, 39}, + {7.552218526149612e+4, 36, 40}, + {1.711305224325764e+5, 31, 36}, + {2.882980788973542e+5, 30, 35}, + {9.050663149996944e+5, 33, 38}, + {2.0409610459455398e+6, 32, 38}, + {2.6947477926234123e+6, 31, 37}, + {6.216317612802105e+6, 29, 35}, + {1.2602046337445939e+7, 29, 36}, + {2.2551536557625037e+7, 28, 35}, + {3.5688575294042416e+7, 27, 34}, + {7.100154148679338e+7, 26, 33}, + {2.3023209155038175e+8, 25, 33}, + {5.343589913228248e+8, 24, 32}, + {6.370971111337817e+8, 22, 30}, + {1.1648983464144683e+9, 21, 30}, + {3.62429343510599e+9, 21, 30}, + {7.923089394878991e+9, 20, 29}, + {1.1994134449184855e+10, 19, 29}, + {2.6327568489536655e+10, 18, 28}, + {5.744587213490791e+10, 16, 26}, + {8.696933735612083e+10, 16, 26}, + {1.6199492099573203e+11, 15, 26}, + {4.646500235357022e+11, 14, 25}, + {9.038018345501305e+11, 13, 24}, + {2.0751822109812212e+12, 11, 23}, + {2.4783388817039565e+12, 11, 23}, + {4.627429732435399e+12, 10, 22}, + {9.300399090639615e+12, 9, 21}, + {3.2555272280322004e+13, 8, 21}, + {5.961096741057368e+13, 7, 20}, + {9.42651077771202e+13, 6, 19}, + {2.553913921435299e+14, 5, 19}, + {4.472244273086706e+14, 3, 17}, + {5.835513905481408e+14, 2, 16}, + {2.0680691881101218e+15, 2, 17}, + {3.328555513255053e+15, 0, 15}, + {6.456802203838757e+15, 0, 15}, + {1.7045704215755864e+16, 0, 16}, + {2.84788661041812e+16, 0, 14}, + {5.025270207504599e+16, 0, 16}, + {1.0675866324035571e+17, 0, 17}, + {2.113981699080119e+17, 0, 17}, + {4.2645533513929344e+17, 0, 16}, + {6.872142041884154e+17, 0, 16}, + {1.1799694815132618e+18, 0, 18}, + {3.082431250837574e+18, 0, 18}, + {4.656624004411896e+18, 0, 18}, + {1.2710318822043552e+19, 0, 19}, + {2.122367359171886e+19, 0, 19}, + {5.243529562420319e+19, 0, 19}, + {1.2577828348716568e+20, 0, 20}, + {2.5306784987647436e+20, 0, 20}, + {4.170387336967606e+20, 0, 20}, + {6.974057652717262e+20, 0, 20}, + {1.9037873644969454e+21, 0, 21}, + {3.8919116102079354e+21, 0, 21}, + {7.985405687712874e+21, 0, 21}, + {1.2304624807151021e+22, 0, 22}, + {2.877579059642156e+22, 0, 22}, + {5.820875799468994e+22, 0, 22}, + {1.3414090792760231e+23, 0, 23}, + {2.8913245990717846e+23, 0, 23}, + {5.891246287064668e+23, 0, 23}, + {1.0714737361583606e+24, 0, 24}, + {1.9464410087794222e+24, 0, 24}, + {4.038971174128977e+24, 0, 24}, + {9.574063461859928e+24, 0, 24}, + {1.8851337402710216e+25, 0, 25}, + {3.1247728753733907e+25, 0, 24}, + {7.618322003647741e+25, 0, 25}, + {1.2658076075629417e+26, 0, 26}, + {2.035966265810824e+26, 0, 25}, + {3.819893374231787e+26, 0, 26}, + {1.0086763820538085e+27, 0, 27}, + {1.8722435720189532e+27, 0, 27}, + {4.199164189291375e+27, 0, 27}, + {5.655595157253245e+27, 0, 27}, + {1.300173405645727e+28, 0, 27}, + {3.435085557417992e+28, 0, 28}, + {5.665464211510795e+28, 0, 28}, + {1.0145707014004108e+29, 0, 29}, + {2.891228424386253e+29, 0, 29}, + {5.286890236526336e+29, 0, 29}, + {1.0225186710420342e+30, 0, 30}, + {2.0108243842273892e+30, 0, 29}, + {4.3265449624459286e+30, 0, 28}, + {8.843189919417627e+30, 0, 30}, + {1.9480726537977613e+31, 0, 31}, + {3.002386285018348e+31, 0, 30}, + {5.643671566392372e+31, 0, 31}, + {1.01957610305534e+32, 0, 32}, + {1.6310534536755234e+32, 0, 32}, + {3.545708028358011e+32, 0, 32}, + {1.2833888390368551e+33, 0, 33}, + {1.392377581640218e+33, 0, 33}, + {3.364225166474354e+33, 0, 33}, + {5.629829199100142e+33, 0, 33}, + {1.5854951477853974e+34, 0, 33}, + {2.9422458257062698e+34, 0, 34}, + {5.271105157627402e+34, 0, 34}, + {1.0730605396566132e+35, 0, 35}, + {2.7860337216506062e+35, 0, 35}, + {4.6519994160495565e+35, 0, 35}, + {9.613291858434125e+35, 0, 35}, + {2.4445443869856408e+36, 0, 36}, + {4.961465895993372e+36, 0, 36}, + {9.540055986377937e+36, 0, 35}, + {1.1787368324472862e+37, 0, 37}, + {2.2217886245582568e+37, 0, 37}, + {7.482803782461342e+37, 0, 37}, + {1.221681695558803e+38, 0, 38}, + {1.9229409068293893e+38, 0, 38}, + {3.977549408655239e+38, 0, 38}, + {7.032764985693427e+38, 0, 38}, + {2.579787897255991e+39, 0, 39}, + {2.997970965091339e+39, 0, 39}, + {1.0000548245536084e+40, 0, 40}, + {1.54316993761888e+40, 0, 40}, + {4.2448087869444726e+40, 0, 40}, + {7.226422026835713e+40, 0, 40}, + {1.5440832081521699e+41, 0, 39}, + {1.9018305301016465e+41, 0, 41}, + {3.769840630981528e+41, 0, 41}, + {7.966617882916526e+41, 0, 40}, + {1.6479249230626738e+42, 0, 42}, + {5.4563711872283434e+42, 0, 42}, + {8.479619741110214e+42, 0, 42}, + {2.179088893187299e+43, 0, 43}, + {2.4514764141293366e+43, 0, 43}, + {7.969690106218293e+43, 0, 41}, + {1.0174451327093896e+44, 0, 44}, + {2.3728120049330384e+44, 0, 44}, + {5.065733073087785e+44, 0, 44}, + {1.1484248148943394e+45, 0, 45}, + {1.4749512803946582e+45, 0, 45}, + {3.184462066193894e+45, 0, 45}, + {6.322074926642195e+45, 0, 45}, + {1.7884100129279887e+46, 0, 46}, + {2.325073960940094e+46, 0, 45}, + {7.785030375679882e+46, 0, 46}, + {1.4448801732473917e+47, 0, 47}, + {2.6558108692875686e+47, 0, 46}, + {4.372023657618532e+47, 0, 47}, + {1.1801773326503515e+48, 0, 48}, + {2.3529994342524252e+48, 0, 48}, + {3.427891103047345e+48, 0, 48}, + {9.31630524930085e+48, 0, 46}, + {1.9116211540697205e+49, 0, 49}, + {3.069617131966241e+49, 0, 49}, + {5.496151148596413e+49, 0, 49}, + {1.7035934506961495e+50, 0, 50}, + {2.833139550712863e+50, 0, 50}, + {6.133786732856586e+50, 0, 50}, + {1.0621093207976587e+51, 0, 51}, + {2.8455345909273967e+51, 0, 51}, + {5.385580087774829e+51, 0, 51}, + {1.0385481005999035e+52, 0, 52}, + {1.7051361668200266e+52, 0, 50}, + {4.0348216442512827e+52, 0, 52}, + {9.203615836914227e+52, 0, 52}, + {1.4419377590076046e+53, 0, 53}, + {3.7517809629812945e+53, 0, 53}, + {6.921465596389916e+53, 0, 53}, + {1.1246377895743686e+54, 0, 54}, + {1.9141569906490816e+54, 0, 54}, + {4.804705186047788e+54, 0, 54}, + {8.071435564947533e+54, 0, 54}, + {2.3128140200050004e+55, 0, 55}, + {4.439935895842503e+55, 0, 55}, + {6.395351593160262e+55, 0, 55}, + {1.6961369224103472e+56, 0, 56}, + {2.0665317001185354e+56, 0, 55}, + {4.267199288089499e+56, 0, 54}, + {1.1913532457592459e+57, 0, 57}, + {2.148060287120407e+57, 0, 57}, + {4.2134227293218824e+57, 0, 57}, + {7.35889330966464e+57, 0, 57}, + {2.4387563351268132e+58, 0, 58}, + {2.755962153622423e+58, 0, 58}, + {7.777910645414689e+58, 0, 57}, + {1.9082027100674306e+59, 0, 59}, + {2.9725481934856135e+59, 0, 59}, + {6.272978739898479e+59, 0, 59}, + {1.6068731094294698e+60, 0, 60}, + {2.976043223039824e+60, 0, 60}, + {3.489997472879309e+60, 0, 60}, + {1.0670380970822516e+61, 0, 61}, + {2.1280541721170622e+61, 0, 61}, + {4.118037303766816e+61, 0, 61}, + {9.480559641663617e+61, 0, 60}, + {1.596797376905473e+62, 0, 62}, + {2.860736632381932e+62, 0, 62}, + {6.175504515676743e+62, 0, 61}, + {1.508691188244575e+63, 0, 63}, + {2.660508539901083e+63, 0, 63}, + {4.809861739392637e+63, 0, 63}, + {1.1682993418673642e+64, 0, 64}, + {2.3441185866765202e+64, 0, 64}, + {3.485421002254914e+64, 0, 64}, + {7.273116277037653e+64, 0, 64}, + {1.354521653245005e+65, 0, 64}, + {3.8672800363603606e+65, 0, 65}, + {5.066109744077407e+65, 0, 65}, + {1.4793086587583503e+66, 0, 66}, + {2.795839034978864e+66, 0, 66}, + {6.432840401396406e+66, 0, 66}, + {7.648199062043261e+66, 0, 65}, + {1.773601417088583e+67, 0, 67}, + {4.060845372454403e+67, 0, 67}, + {5.9366267010857e+67, 0, 67}, + {2.0059924456841817e+68, 0, 68}, + {4.1783323703661896e+68, 0, 68}, + {7.479841058470886e+68, 0, 68}, + {1.1009811776563666e+69, 0, 69}, + {3.2783442635024616e+69, 0, 69}, + {6.563409273861358e+69, 0, 69}, + {1.0567882407580983e+70, 0, 70}, + {1.5374031618797994e+70, 0, 70}, + {5.338524581842552e+70, 0, 69}, + {1.0972432160602172e+71, 0, 71}, + {1.5001044647337975e+71, 0, 70}, + {2.8316543156525084e+71, 0, 71}, + {8.3236641225376745e+71, 0, 71}, + {1.405812739508858e+72, 0, 72}, + {3.4886185525806396e+72, 0, 72}, + {3.6195137423664015e+72, 0, 72}, + {9.41094888383514e+72, 0, 72}, + {1.6890382785900104e+73, 0, 73}, + {4.5033691214168135e+73, 0, 73}, + {6.278199128750284e+73, 0, 73}, + {1.1565059569236081e+74, 0, 74}, + {4.353326831627433e+74, 0, 74}, + {8.277733334111089e+74, 0, 74}, + {1.4048499964520923e+75, 0, 75}, + {2.322541569987462e+75, 0, 75}, + {4.557727968952481e+75, 0, 75}, + {1.215633227097655e+76, 0, 74}, + {1.5116664880436698e+76, 0, 76}, + {5.765280163648591e+76, 0, 76}, + {7.214835817706474e+76, 0, 76}, + {2.1100734325357315e+77, 0, 77}, + {3.9254415593380374e+77, 0, 77}, + {6.516897273419297e+77, 0, 76}, + {1.7597137621226992e+78, 0, 76}, + {3.434875728114805e+78, 0, 78}, + {4.496894013184632e+78, 0, 77}, + {1.0023515028874743e+79, 0, 79}, + {2.0460627987672047e+79, 0, 77}, + {3.676686386898502e+79, 0, 79}, + {6.987059926149719e+79, 0, 79}, + {1.795081604158087e+80, 0, 80}, + {3.8104574251556824e+80, 0, 80}, + {5.627357082833958e+80, 0, 80}, + {1.8281571967861733e+81, 0, 81}, + {3.3545136086293428e+81, 0, 81}, + {4.116922864713555e+81, 0, 81}, + {1.075543470816983e+82, 0, 82}, + {1.8609826393606468e+82, 0, 82}, + {3.903192479447499e+82, 0, 81}, + {6.473791115981879e+82, 0, 82}, + {1.538865780906402e+83, 0, 83}, + {4.6154122259230323e+83, 0, 83}, + {5.213671588000098e+83, 0, 82}, + {1.7231285500027554e+84, 0, 84}, + {3.3263411825363985e+84, 0, 82}, + {6.7438490624371245e+84, 0, 84}, + {1.5324966500321058e+85, 0, 85}, + {2.9031045606668695e+85, 0, 85}, + {4.0285461203337304e+85, 0, 85}, + {6.347438261947277e+85, 0, 85}, + {1.4849990568686154e+86, 0, 86}, + {3.144137214709148e+86, 0, 84}, + {7.35111663943684e+86, 0, 86}, + {1.5577475663863448e+87, 0, 87}, + {2.2732058810982127e+87, 0, 87}, + {4.947293841380347e+87, 0, 87}, + {1.5322047885180557e+88, 0, 87}, + {2.2054038951762965e+88, 0, 87}, + {3.4979111598588176e+88, 0, 88}, + {1.1594354499619093e+89, 0, 89}, + {1.868122415575413e+89, 0, 89}, + {3.482810444725715e+89, 0, 89}, + {7.257639265994533e+89, 0, 89}, + {1.1535270490147722e+90, 0, 90}, + {2.4967979092938603e+90, 0, 90}, + {7.0072237787891005e+90, 0, 90}, + {1.1095288700912677e+91, 0, 91}, + {2.9607930405769014e+91, 0, 91}, + {5.652005070494756e+91, 0, 91}, + {1.1255100772707936e+92, 0, 92}, + {1.7318608033666267e+92, 0, 92}, + {4.919954612222507e+92, 0, 92}, + {6.919660091186515e+92, 0, 92}, + {1.0987955187264384e+93, 0, 93}, + {2.315894257968477e+93, 0, 93}, + {5.072050876594695e+93, 0, 92}, + {1.036357099998561e+94, 0, 94}, + {3.1561899942152433e+94, 0, 94}, + {5.80588996039864e+94, 0, 94}, + {8.543176492274635e+94, 0, 94}, + {1.3894319871042075e+95, 0, 95}, + {4.778618490169982e+95, 0, 95}, + {1.0203895888996092e+96, 0, 96}, + {1.5960456887763758e+96, 0, 96}, + {3.249173807353552e+96, 0, 96}, + {4.96163817638071e+96, 0, 96}, + {1.3169653838480804e+97, 0, 96}, + {2.7600802833290057e+97, 0, 97}, + {3.678975869510067e+97, 0, 97}, + {9.316977605792739e+97, 0, 97}, + {2.636218658095782e+98, 0, 98}, + {2.8795801629202426e+98, 0, 98}, + {1.0271479449330235e+99, 0, 99}, + {1.7364389499462398e+99, 0, 99}, + {2.8311014904498133e+99, 0, 99}, + {8.717780031735018e+99, 0, 98}, + {1.2937429393963081e+100, 0, 100}, + {3.076211787425004e+100, 0, 99}, + {6.075000204770712e+100, 0, 100}, + {1.0051913192324967e+101, 0, 101}, + {1.6040435699970458e+101, 0, 101}, + {3.2033279820176996e+101, 0, 101}, + {6.504689809313382e+101, 0, 101}, + {1.3085843965991656e+102, 0, 102}, + {4.2878646530008384e+102, 0, 102}, + {6.562953660628769e+102, 0, 101}, + {1.2236285152807308e+103, 0, 103}, + {2.414637776562117e+103, 0, 103}, + {6.180397605502719e+103, 0, 103}, + {8.952357506110561e+103, 0, 102}, + {2.6739842993463814e+104, 0, 104}, + {4.9051642185904874e+104, 0, 104}, + {1.1379543402482949e+105, 0, 105}, + {1.41259541205596e+105, 0, 104}, + {3.1114274318600263e+105, 0, 105}, + {8.750317145527779e+105, 0, 104}, + {1.1436652181690534e+106, 0, 106}, + {3.411483086538129e+106, 0, 106}, + {4.073577415899245e+106, 0, 106}, + {1.1569270331282534e+107, 0, 107}, + {1.8083251599082483e+107, 0, 106}, + {3.791515819931573e+107, 0, 107}, + {9.49764100015205e+107, 0, 107}, + {1.4509958077289753e+108, 0, 108}, + {4.424174112983774e+108, 0, 108}, + {7.455899165473143e+108, 0, 108}, + {1.8319982128819878e+109, 0, 109}, + {3.0467669457405913e+109, 0, 107}, + {6.37361253846025e+109, 0, 109}, + {8.38955229182671e+109, 0, 109}, + {2.8651611621506035e+110, 0, 110}, + {6.001453332972219e+110, 0, 110}, + {8.11448033384131e+110, 0, 109}, + {2.2581394399876124e+111, 0, 110}, + {3.136912645074335e+111, 0, 110}, + {8.75100561003784e+111, 0, 111}, + {1.7339706643869121e+112, 0, 112}, + {1.934331254218702e+112, 0, 110}, + {6.254524509233588e+112, 0, 112}, + {9.375622388315127e+112, 0, 112}, + {2.4189606341952683e+113, 0, 112}, + {5.2244400989180614e+113, 0, 113}, + {6.794474678720091e+113, 0, 111}, + {2.046778382742435e+114, 0, 113}, + {4.195175792183929e+114, 0, 114}, + {7.880516976947723e+114, 0, 114}, + {1.1711035514327282e+115, 0, 115}, + {2.338338695923868e+115, 0, 115}, + {5.385799521445425e+115, 0, 115}, + {1.2991906643558943e+116, 0, 116}, + {1.7757658123869845e+116, 0, 116}, + {6.1954646341609976e+116, 0, 116}, + {8.320622375414998e+116, 0, 116}, + {1.3055545505825232e+117, 0, 117}, + {3.836650434819978e+117, 0, 116}, + {8.752670682427704e+117, 0, 117}, + {1.371103427759537e+118, 0, 118}, + {3.047042188071979e+118, 0, 117}, + {7.589006090032981e+118, 0, 117}, + {1.0473293912342299e+119, 0, 119}, + {2.0986207045386187e+119, 0, 119}, + {3.236179661386884e+119, 0, 118}, + {7.972193856244527e+119, 0, 119}, + {1.8299315060253445e+120, 0, 120}, + {4.6151785414218586e+120, 0, 120}, + {5.923044070696605e+120, 0, 120}, + {1.361457289463494e+121, 0, 120}, + {2.7540713949104843e+121, 0, 121}, + {6.706809109602002e+121, 0, 121}, + {1.6292678335806155e+122, 0, 121}, + {2.171602703883232e+122, 0, 121}, + {5.723756099896326e+122, 0, 122}, + {6.89498173085618e+122, 0, 121}, + {2.1357574596497743e+123, 0, 123}, + {2.961187277322293e+123, 0, 123}, + {9.093166616854773e+123, 0, 123}, + {1.2290156989602755e+124, 0, 122}, + {3.2889389033303796e+124, 0, 124}, + {7.62000449356734e+124, 0, 123}, + {1.6491192862717743e+125, 0, 123}, + {2.1235620018124726e+125, 0, 124}, + {5.0830874862362334e+125, 0, 125}, + {1.3230337364814056e+126, 0, 126}, + {1.8514824347085014e+126, 0, 124}, + {4.297255645889242e+126, 0, 126}, + {9.214123983353544e+126, 0, 125}, + {1.6005984240937532e+127, 0, 126}, + {4.0927181564650246e+127, 0, 127}, + {6.9786454159176675e+127, 0, 127}, + {8.88030935259018e+127, 0, 127}, + {2.0038549597024574e+128, 0, 128}, + {6.439948347184869e+128, 0, 128}, + {9.745603385424579e+128, 0, 128}, + {1.682287066188509e+129, 0, 129}, + {5.159777854912e+129, 0, 128}, + {1.0427009479036798e+130, 0, 130}, + {1.8648218159777694e+130, 0, 130}, + {2.7990719302881793e+130, 0, 130}, + {7.540407358942798e+130, 0, 130}, + {9.729616533270472e+130, 0, 130}, + {2.3271929702892176e+131, 0, 130}, + {4.8467161954318766e+131, 0, 130}, + {8.654197708736783e+131, 0, 131}, + {2.486253574248309e+132, 0, 132}, + {4.520706326672799e+132, 0, 132}, + {8.587398813646014e+132, 0, 131}, + {1.7982783689310961e+133, 0, 133}, + {3.51092292749737e+133, 0, 133}, + {8.854772868346543e+133, 0, 133}, + {1.2604934227155403e+134, 0, 132}, + {2.7086719929942504e+134, 0, 134}, + {5.9197489271133586e+134, 0, 134}, + {8.607845504713468e+134, 0, 134}, + {2.3984166963813072e+135, 0, 134}, + {3.250007960862918e+135, 0, 135}, + {7.443610248681703e+135, 0, 135}, + {1.9390729656615215e+136, 0, 136}, + {3.6046982835224656e+136, 0, 136}, + {8.883580710031506e+136, 0, 136}, + {1.0473820665170049e+137, 0, 136}, + {2.7139682262053297e+137, 0, 137}, + {4.3539205936145105e+137, 0, 137}, + {1.299944829366617e+138, 0, 137}, + {2.4557929966471053e+138, 0, 138}, + {3.448376481821885e+138, 0, 136}, + {6.779013852180732e+138, 0, 138}, + {1.801148096883475e+139, 0, 139}, + {3.791452902390207e+139, 0, 138}, + {6.474870054646572e+139, 0, 139}, + {1.6231677484748648e+140, 0, 139}, + {3.527980476981338e+140, 0, 140}, + {4.747525868091625e+140, 0, 140}, + {9.241043811243549e+140, 0, 139}, + {1.7472268158903568e+141, 0, 141}, + {5.1503502855445634e+141, 0, 141}, + {1.0693743613454128e+142, 0, 142}, + {1.727103927590088e+142, 0, 142}, + {4.6987106320234365e+142, 0, 142}, + {9.644313300615438e+142, 0, 142}, + {1.430838540572183e+143, 0, 143}, + {2.27786457822541e+143, 0, 143}, + {4.212849184362802e+143, 0, 143}, + {8.237715226546193e+143, 0, 143}, + {2.6654737215359226e+144, 0, 143}, + {6.019079138812162e+144, 0, 144}, + {1.2409084690292542e+145, 0, 145}, + {1.549900904806159e+145, 0, 145}, + {3.1738661663567816e+145, 0, 145}, + {6.996909564666351e+145, 0, 145}, + {1.4136130050003495e+146, 0, 146}, + {2.98468447673297e+146, 0, 146}, + {5.072484297164673e+146, 0, 146}, + {9.172861001485728e+146, 0, 146}, + {3.1827325058993567e+147, 0, 147}, + {4.205587083947037e+147, 0, 147}, + {8.648615720577233e+147, 0, 147}, + {2.4309692254959333e+148, 0, 148}, + {4.466309998082111e+148, 0, 148}, + {7.497564003947889e+148, 0, 148}, + {1.9029191895593583e+149, 0, 149}, + {3.8006641833677102e+149, 0, 149}, + {6.913824994082101e+149, 0, 147}, + {8.712815483801716e+149, 0, 149}, + {2.3780639727460607e+150, 0, 150}, + {4.467392649638729e+150, 0, 150}, + {8.066185078159116e+150, 0, 148}, + {1.8852448369964825e+151, 0, 150}, + {3.566152723739795e+151, 0, 151}, + {5.924851411681645e+151, 0, 151}, + {1.5060587321219467e+152, 0, 152}, + {2.8583520249281916e+152, 0, 152}, + {4.535449894907774e+152, 0, 152}, + {1.012602649962377e+153, 0, 153}, + {2.0189450716998492e+153, 0, 153}, + {6.095760398208723e+153, 0, 152}, + {1.0839728287028179e+154, 0, 154}, + {2.0160697875193073e+154, 0, 154}, + {4.539520336739885e+154, 0, 153}, + {7.982163447075079e+154, 0, 154}, + {1.1108659309948223e+155, 0, 155}, + {2.938991096845654e+155, 0, 155}, + {7.734832320107849e+155, 0, 155}, + {1.0600772804334342e+156, 0, 154}, + {2.560225316421402e+156, 0, 156}, + {6.4957894162689224e+156, 0, 153}, + {1.0469685229597166e+157, 0, 157}, + {1.8049425471684504e+157, 0, 157}, + {3.7963414617308416e+157, 0, 157}, + {9.092676802446734e+157, 0, 156}, + {2.1420954571874796e+158, 0, 158}, + {3.5406074604903996e+158, 0, 158}, + {6.059179837113682e+158, 0, 158}, + {1.7129340266632835e+159, 0, 159}, + {1.9501271083673803e+159, 0, 159}, + {5.17312771823294e+159, 0, 158}, + {9.785122654459946e+159, 0, 159}, + {2.070565985519521e+160, 0, 160}, + {5.5612314177003026e+160, 0, 160}, + {7.487464053324147e+160, 0, 160}, + {1.72170539588904e+161, 0, 161}, + {3.101655447037901e+161, 0, 160}, + {7.190773969829955e+161, 0, 160}, + {1.6233719112674835e+162, 0, 162}, + {2.8766398779168217e+162, 0, 161}, + {5.119867078891217e+162, 0, 162}, + {1.0829229605647497e+163, 0, 163}, + {2.588897361986241e+163, 0, 163}, + {4.8574461844070457e+163, 0, 162}, + {7.897885248231481e+163, 0, 162}, + {1.7757792408248608e+164, 0, 164}, + {4.5181869438995584e+164, 0, 164}, + {6.04440676037902e+164, 0, 164}, + {9.436631806093061e+164, 0, 164}, + {3.252849304270332e+165, 0, 165}, + {4.971119941681659e+165, 0, 165}, + {7.72793435885625e+165, 0, 165}, + {2.49887461307668e+166, 0, 166}, + {4.138019138142108e+166, 0, 165}, + {7.671967953519936e+166, 0, 166}, + {1.4213879533988813e+167, 0, 166}, + {4.6541532390763674e+167, 0, 166}, + {5.769302991727879e+167, 0, 167}, + {1.7540289541978555e+168, 0, 167}, + {3.454961038953494e+168, 0, 168}, + {3.923334972089952e+168, 0, 168}, + {1.270785497029221e+169, 0, 169}, + {2.376127169235011e+169, 0, 169}, + {5.282087226424358e+169, 0, 169}, + {9.538748971891788e+169, 0, 169}, + {2.1105330303243357e+170, 0, 170}, + {2.595142529235477e+170, 0, 169}, + {6.194530882320718e+170, 0, 170}, + {1.7393845792769813e+171, 0, 171}, + {2.5114408497872505e+171, 0, 171}, + {6.964312528803397e+171, 0, 171}, + {1.229619287434228e+172, 0, 171}, + {2.2726320991720394e+172, 0, 172}, + {6.1180092244552894e+172, 0, 172}, + {7.849033701858734e+172, 0, 172}, + {1.876016568195346e+173, 0, 173}, + {3.504523456271905e+173, 0, 173}, + {7.263516584915379e+173, 0, 173}, + {1.9020249607206747e+174, 0, 174}, + {3.954563518862104e+174, 0, 174}, + {4.179264564957457e+174, 0, 174}, + {1.3433838372088608e+175, 0, 175}, + {2.3932438386350752e+175, 0, 175}, + {4.2972187544143625e+175, 0, 174}, + {8.694788966088642e+175, 0, 175}, + {1.5650872395957953e+176, 0, 176}, + {2.6609127251749555e+176, 0, 176}, + {7.304131091528228e+176, 0, 176}, + {1.8749367638301195e+177, 0, 177}, + {2.525113715353648e+177, 0, 177}, + {5.287208897505958e+177, 0, 177}, + {1.2213351108095344e+178, 0, 178}, + {2.1111997723509844e+178, 0, 178}, + {5.015156723398377e+178, 0, 178}, + {7.845248843802528e+178, 0, 178}, + {1.5083269248992405e+179, 0, 179}, + {5.03076797664753e+179, 0, 178}, + {6.133803460001548e+179, 0, 179}, + {1.7956317799329456e+180, 0, 180}, + {2.805357599815183e+180, 0, 178}, + {6.516381475942179e+180, 0, 180}, + {1.0735479295792078e+181, 0, 181}, + {1.8311493838142104e+181, 0, 181}, + {5.233651721774458e+181, 0, 181}, + {7.965462598423407e+181, 0, 180}, + {1.5936468405690223e+182, 0, 181}, + {4.823391413011241e+182, 0, 182}, + {8.068434006659677e+182, 0, 182}, + {1.1652548548873181e+183, 0, 182}, + {3.145002956331295e+183, 0, 183}, + {6.761293527838664e+183, 0, 182}, + {1.4932090269565783e+184, 0, 184}, + {2.8283859855362886e+184, 0, 184}, + {4.215793138787126e+184, 0, 184}, + {1.2070899708662365e+185, 0, 185}, + {2.2700631068240273e+185, 0, 185}, + {4.0401294303115837e+185, 0, 185}, + {6.426088010351117e+185, 0, 185}, + {1.706800695515592e+186, 0, 186}, + {4.104631682494189e+186, 0, 185}, + {5.818142413455796e+186, 0, 186}, + {1.521111645270472e+187, 0, 187}, + {2.605426852604142e+187, 0, 187}, + {4.105785371952793e+187, 0, 187}, + {1.371762624713214e+188, 0, 188}, + {2.7195986230515428e+188, 0, 188}, + {4.8243077416927026e+188, 0, 187}, + {9.498678712086855e+188, 0, 188}, + {1.5645616537346162e+189, 0, 189}, + {4.111969199449788e+189, 0, 189}, + {8.157644542642784e+189, 0, 188}, + {9.397820074087553e+189, 0, 189}, + {3.0139458754847525e+190, 0, 190}, + {6.799079050215055e+190, 0, 189}, + {1.0012275116878429e+191, 0, 191}, + {1.450477696477974e+191, 0, 191}, + {3.5956024160941395e+191, 0, 191}, + {7.905240079936544e+191, 0, 191}, + {1.5954884853337538e+192, 0, 192}, + {3.104998331330946e+192, 0, 192}, + {8.903071845176859e+192, 0, 192}, + {1.3587214394332288e+193, 0, 191}, + {1.86599842632827e+193, 0, 192}, + {6.954412455796678e+193, 0, 193}, + {1.0832755189816974e+194, 0, 193}, + {1.7216892018906268e+194, 0, 193}, + {2.9919164363027124e+194, 0, 193}, + {7.751180767767667e+194, 0, 194}, + {1.4469419386503595e+195, 0, 195}, + {4.3846223856409726e+195, 0, 195}, + {5.38120316360689e+195, 0, 195}, + {1.1192297812198031e+196, 0, 196}, + {3.6733861060912077e+196, 0, 196}, + {5.367174654182726e+196, 0, 196}, + {1.1985977495349634e+197, 0, 197}, + {2.7706336697369672e+197, 0, 197}, + {3.81638203242153e+197, 0, 197}, + {7.793902438347514e+197, 0, 197}, + {1.762580890481988e+198, 0, 197}, + {4.590880368222411e+198, 0, 198}, + {8.941282866369577e+198, 0, 198}, + {1.8668922163822093e+199, 0, 199}, + {3.3015541406240128e+199, 0, 199}, + {6.66780077867008e+199, 0, 199}, + {8.582818157212463e+199, 0, 199}, + {1.7299550663482326e+200, 0, 199}, + {3.174260421239594e+200, 0, 200}, + {1.0577387790503058e+201, 0, 200}, + {1.869752072792986e+201, 0, 200}, + {2.955061165839854e+201, 0, 201}, + {9.330031602327955e+201, 0, 201}, + {1.9362841443169072e+202, 0, 200}, + {3.825314140412424e+202, 0, 202}, + {5.636134524042645e+202, 0, 202}, + {1.0455633800468254e+203, 0, 203}, + {2.394954328277626e+203, 0, 203}, + {4.637958398638087e+203, 0, 203}, + {8.55447002868069e+203, 0, 203}, + {2.2442269843101123e+204, 0, 204}, + {3.3453762259499943e+204, 0, 204}, + {6.28195345463042e+204, 0, 204}, + {1.568756700441443e+205, 0, 205}, + {2.7948083967253507e+205, 0, 203}, + {5.0694600816666435e+205, 0, 205}, + {1.5167731946556582e+206, 0, 206}, + {1.608485680821464e+206, 0, 206}, + {3.555480059704267e+206, 0, 206}, + {7.943813334385863e+206, 0, 206}, + {1.3580600028505664e+207, 0, 205}, + {5.1194339355719443e+207, 0, 207}, + {6.055737748179178e+207, 0, 207}, + {1.5368136201009418e+208, 0, 207}, + {2.3929799628231875e+208, 0, 208}, + {5.905063752643223e+208, 0, 207}, + {1.2410164169914757e+209, 0, 209}, + {2.1964388171812186e+209, 0, 209}, + {5.643355654143248e+209, 0, 209}, + {1.2832349330194718e+210, 0, 210}, + {2.4701961710739764e+210, 0, 210}, + {4.055943573502299e+210, 0, 209}, + {9.344204033720965e+210, 0, 209}, + {1.5562829932078268e+211, 0, 211}, + {4.0777821620060193e+211, 0, 210}, + {7.651488357127853e+211, 0, 210}, + {1.4653017873373999e+212, 0, 210}, + {3.1057366810904798e+212, 0, 212}, + {3.494919053072309e+212, 0, 211}, + {1.0912691038049118e+213, 0, 213}, + {1.9415837118656972e+213, 0, 213}, + {5.063717719666344e+213, 0, 213}, + {8.227221790228981e+213, 0, 213}, + {1.227440354851669e+214, 0, 214}, + {2.336960786523524e+214, 0, 214}, + {4.516795524723723e+214, 0, 214}, + {9.603333317066072e+214, 0, 214}, + {1.9527229344211296e+215, 0, 213}, + {5.881603409821294e+215, 0, 215}, + {1.3688597232290091e+216, 0, 216}, + {2.295764284659215e+216, 0, 216}, + {3.873452666452653e+216, 0, 215}, + {8.054654093095746e+216, 0, 216}, + {2.1236865355155596e+217, 0, 217}, + {2.2954536079504795e+217, 0, 217}, + {5.09388995969254e+217, 0, 217}, + {8.87462625679e+217, 0, 217}, + {2.3307227637493686e+218, 0, 218}, + {3.6312540549690455e+218, 0, 216}, + {1.0992397195630485e+219, 0, 219}, + {2.5896743857569935e+219, 0, 219}, + {5.2383553757590477e+219, 0, 219}, + {8.90027956004149e+219, 0, 219}, + {2.2251161587610878e+220, 0, 220}, + {2.6761461913428335e+220, 0, 219}, + {5.829314115768354e+220, 0, 220}, + {1.0417852442068364e+221, 0, 221}, + {2.1607520560286885e+221, 0, 221}, + {6.6122291466551174e+221, 0, 221}, + {1.0456456707732716e+222, 0, 222}, + {2.1319581917624135e+222, 0, 222}, + {3.02173423868133e+222, 0, 222}, + {8.957564546729514e+222, 0, 222}, + {1.6091267209523208e+223, 0, 223}, + {3.7212585176976675e+223, 0, 223}, + {7.024301672009426e+223, 0, 223}, + {1.3922688598771826e+224, 0, 224}, + {2.7509119604975913e+224, 0, 224}, + {6.578680864102577e+224, 0, 224}, + {8.127797721199312e+224, 0, 223}, + {2.8202379495294703e+225, 0, 225}, + {3.4957237382552115e+225, 0, 225}, + {6.721082912592893e+225, 0, 225}, + {2.196269058711357e+226, 0, 225}, + {4.702863683113725e+226, 0, 226}, + {9.307324517542515e+226, 0, 226}, + {9.775137400232788e+226, 0, 226}, + {3.311384964585959e+227, 0, 227}, + {4.296740089682654e+227, 0, 227}, + {1.3512844888942569e+228, 0, 228}, + {2.5091984687837548e+228, 0, 228}, + {4.20961732885855e+228, 0, 228}, + {6.135661532855286e+228, 0, 228}, + {1.835960873424025e+229, 0, 228}, + {4.582242303370038e+229, 0, 229}, + {5.018545601421532e+229, 0, 229}, + {1.5236616837822261e+230, 0, 229}, + {2.5667407186328553e+230, 0, 230}, + {6.564151959920961e+230, 0, 230}, + {9.49846077230578e+230, 0, 230}, + {2.2123883505536183e+231, 0, 230}, + {5.172961691829432e+231, 0, 231}, + {1.1249841891755881e+232, 0, 232}, + {1.2896696221903866e+232, 0, 232}, + {4.778497687103402e+232, 0, 232}, + {9.071740946235158e+232, 0, 232}, + {1.36899006488196e+233, 0, 233}, + {3.258574493508666e+233, 0, 233}, + {7.68435010871856e+233, 0, 233}, + {1.0486551202848853e+234, 0, 234}, + {2.5823121007464567e+234, 0, 234}, + {3.773798625179518e+234, 0, 234}, + {8.43646796917978e+234, 0, 234}, + {1.8696054522844014e+235, 0, 235}, + {2.951623859521488e+235, 0, 235}, + {5.731246293623385e+235, 0, 235}, + {1.0607775243868116e+236, 0, 235}, + {2.2867476796644975e+236, 0, 236}, + {6.151644498588618e+236, 0, 236}, + {1.5549079171535267e+237, 0, 237}, + {2.990844031338328e+237, 0, 237}, + {4.227499559860197e+237, 0, 237}, + {6.92155672717332e+237, 0, 237}, + {1.5461124618479105e+238, 0, 238}, + {2.8916888598976405e+238, 0, 238}, + {8.571703697432337e+238, 0, 238}, + {1.5154374230926383e+239, 0, 239}, + {3.991365102553957e+239, 0, 239}, + {8.307426908068283e+239, 0, 239}, + {1.2399350584739399e+240, 0, 240}, + {2.2885616582924817e+240, 0, 240}, + {5.712118804366146e+240, 0, 239}, + {1.1908490153821645e+241, 0, 241}, + {1.6045249823083896e+241, 0, 241}, + {4.212493492663987e+241, 0, 241}, + {7.52651047700803e+241, 0, 241}, + {1.9963765370119968e+242, 0, 242}, + {3.5635374969254025e+242, 0, 242}, + {5.447736670805847e+242, 0, 242}, + {1.1156495207540661e+243, 0, 243}, + {2.3644722323729203e+243, 0, 242}, + {4.9029985515120055e+243, 0, 241}, + {7.61131574093882e+243, 0, 243}, + {1.5041965738192946e+244, 0, 244}, + {3.35946042057764e+244, 0, 244}, + {5.625581093732572e+244, 0, 242}, + {1.730191634549964e+245, 0, 245}, + {3.761446787869432e+245, 0, 244}, + {8.60775465603473e+245, 0, 244}, + {1.1562529259815605e+246, 0, 246}, + {2.246394864375404e+246, 0, 246}, + {6.325983032753727e+246, 0, 246}, + {7.123352302648312e+246, 0, 246}, + {1.523998978888332e+247, 0, 247}, + {4.281781595885279e+247, 0, 247}, + {7.835530851123771e+247, 0, 247}, + {1.506816682188537e+248, 0, 248}, + {4.2240545805625663e+248, 0, 248}, + {6.21305241046745e+248, 0, 248}, + {1.548221225307829e+249, 0, 249}, + {1.8642201656825735e+249, 0, 249}, + {6.106758858401295e+249, 0, 249}, + {1.2843929885114427e+250, 0, 250}, + {1.9018081890692326e+250, 0, 250}, + {4.9450566101263624e+250, 0, 248}, + {1.0162079024060942e+251, 0, 251}, + {1.7447831757371136e+251, 0, 250}, + {4.270366017096213e+251, 0, 250}, + {6.868063596101005e+251, 0, 251}, + {1.822171630415365e+252, 0, 251}, + {1.9952782556234112e+252, 0, 252}, + {7.158784748789308e+252, 0, 252}, + {1.2301580219420288e+253, 0, 253}, + {2.8298450670726808e+253, 0, 253}, + {3.975680900562017e+253, 0, 252}, + {8.709330821451115e+253, 0, 253}, + {2.0174005971626055e+254, 0, 254}, + {2.394882217848989e+254, 0, 254}, + {7.067225914874603e+254, 0, 253}, + {1.8724413449264554e+255, 0, 255}, + {2.8751146201193473e+255, 0, 255}, + {6.421888796776797e+255, 0, 255}, + {7.523354749558753e+255, 0, 254}, + {2.2569755092325087e+256, 0, 254}, + {5.3636854859010334e+256, 0, 256}, + {7.261725338002667e+256, 0, 256}, + {1.8966691176866934e+257, 0, 257}, + {4.4518263636702484e+257, 0, 256}, + {9.255196758728659e+257, 0, 257}, + {1.8593264122501495e+258, 0, 258}, + {3.6316167357983088e+258, 0, 258}, + {6.877505680971994e+258, 0, 258}, + {8.77084305549325e+258, 0, 258}, + {1.722353804283124e+259, 0, 259}, + {3.422411361841639e+259, 0, 259}, + {7.034346313302069e+259, 0, 259}, + {2.1022143522712643e+260, 0, 258}, + {3.9689033579839334e+260, 0, 260}, + {5.348655415676104e+260, 0, 260}, + {1.6669857056073904e+261, 0, 261}, + {2.5268130061515184e+261, 0, 261}, + {7.013556395550645e+261, 0, 261}, + {1.115205775476797e+262, 0, 261}, + {1.8218885128879403e+262, 0, 262}, + {4.882195462759247e+262, 0, 262}, + {1.1647232869439797e+263, 0, 263}, + {2.4733296974221884e+263, 0, 263}, + {3.825561477223162e+263, 0, 263}, + {5.295052253043566e+263, 0, 263}, + {1.7290409059596491e+264, 0, 264}, + {2.8917911522817363e+264, 0, 264}, + {5.3515050581631755e+264, 0, 263}, + {1.0862117360156829e+265, 0, 265}, + {2.312878548854693e+265, 0, 265}, + {5.710434640774437e+265, 0, 265}, + {1.1391963969865948e+266, 0, 266}, + {1.93471671572856e+266, 0, 265}, + {4.71279659258191e+266, 0, 266}, + {9.710448034768053e+266, 0, 266}, + {1.7049690600947821e+267, 0, 267}, + {2.1264667446960467e+267, 0, 267}, + {7.398129672423321e+267, 0, 267}, + {9.763305417386079e+267, 0, 267}, + {2.0663273080075778e+268, 0, 268}, + {5.79917895558858e+268, 0, 268}, + {7.426275326973986e+268, 0, 268}, + {2.1266018116022865e+269, 0, 268}, + {4.31743642311704e+269, 0, 269}, + {8.537997866147938e+269, 0, 269}, + {1.498071428134464e+270, 0, 268}, + {2.2911327982233443e+270, 0, 269}, + {8.038363677318397e+270, 0, 270}, + {1.6367042236629719e+271, 0, 271}, + {1.7563972945923824e+271, 0, 271}, + {4.380651719659702e+271, 0, 271}, + {1.0481984706425231e+272, 0, 272}, + {2.1003524500133663e+272, 0, 272}, + {5.0546833046720617e+272, 0, 272}, + {5.467994685893837e+272, 0, 272}, + {1.2485068025844776e+273, 0, 272}, + {2.8248640371828673e+273, 0, 272}, + {8.391364185949281e+273, 0, 273}, + {1.4139675630970964e+274, 0, 273}, + {2.907211624912509e+274, 0, 274}, + {4.372977278689428e+274, 0, 274}, + {1.2067462091729205e+275, 0, 275}, + {2.224391242910452e+275, 0, 275}, + {4.317611102271228e+275, 0, 275}, + {5.865999613363234e+275, 0, 275}, + {1.8057130538588834e+276, 0, 276}, + {2.684819410822478e+276, 0, 276}, + {6.918394090981146e+276, 0, 276}, + {9.614934849906353e+276, 0, 276}, + {3.489094951176538e+277, 0, 277}, + {6.096297720785519e+277, 0, 276}, + {7.672262077744885e+277, 0, 277}, + {1.7454649363112346e+278, 0, 278}, + {4.3709703490882645e+278, 0, 278}, + {1.0726114671866103e+279, 0, 279}, + {1.3228423831361687e+279, 0, 279}, + {3.696652073770945e+279, 0, 279}, + {5.506752176655332e+279, 0, 278}, + {1.4487895211141177e+280, 0, 280}, + {2.9868446101560017e+280, 0, 279}, + {5.1539637484359395e+280, 0, 280}, + {1.3665660611345062e+281, 0, 281}, + {2.7298170376572543e+281, 0, 280}, + {4.090888763436566e+281, 0, 280}, + {7.150463385743894e+281, 0, 281}, + {1.5175210833272951e+282, 0, 282}, + {4.186412589165422e+282, 0, 282}, + {6.374560719497041e+282, 0, 282}, + {1.5603097675062575e+283, 0, 283}, + {2.303138994294191e+283, 0, 283}, + {6.845537430162203e+283, 0, 283}, + {8.140470197133932e+283, 0, 283}, + {2.8387070865957636e+284, 0, 284}, + {4.7222972051563225e+284, 0, 283}, + {7.735866498835463e+284, 0, 284}, + {1.456494620618158e+285, 0, 285}, + {3.765187536411294e+285, 0, 285}, + {5.407406833340397e+285, 0, 284}, + {1.6691777225183443e+286, 0, 286}, + {2.842291093748383e+286, 0, 285}, + {3.9527246153864666e+286, 0, 286}, + {9.117687890595329e+286, 0, 286}, + {2.6403715176860095e+287, 0, 287}, + {4.672760871330719e+287, 0, 287}, + {9.585177496788628e+287, 0, 285}, + {1.823719730845468e+288, 0, 287}, + {3.3099160671433535e+288, 0, 288}, + {8.394239974695799e+288, 0, 287}, + {1.3765053672754183e+289, 0, 289}, + {3.643679829326757e+289, 0, 289}, + {6.662084711556889e+289, 0, 289}, + {1.4882800748283324e+290, 0, 290}, + {1.8128485634074353e+290, 0, 290}, + {3.59863757645859e+290, 0, 289}, + {1.1306303530723758e+291, 0, 291}, + {1.9883184243838977e+291, 0, 291}, + {3.152458971727972e+291, 0, 291}, + {5.55344865848389e+291, 0, 289}, + {1.9381607498203374e+292, 0, 291}, + {3.364847896300297e+292, 0, 292}, + {6.842765724941906e+292, 0, 291}, + {1.249825938708993e+293, 0, 293}, + {2.392791615385826e+293, 0, 293}, + {4.152722646899415e+293, 0, 293}, + {6.790906283978802e+293, 0, 293}, + {1.3561785760749324e+294, 0, 294}, + {3.341059095259842e+294, 0, 293}, + {9.977930881477503e+294, 0, 294}, + {1.515106038250912e+295, 0, 294}, + {3.002184910089352e+295, 0, 295}, + {5.876579596327993e+295, 0, 295}, + {1.4003917488295084e+296, 0, 296}, + {3.1684232621942047e+296, 0, 296}, + {3.4747005896842154e+296, 0, 296}, + {7.669723855021269e+296, 0, 296}, + {1.3614131925096533e+297, 0, 297}, + {3.191738550947262e+297, 0, 297}, + {8.888378843987562e+297, 0, 297}, + {1.5112090835975456e+298, 0, 298}, + {2.3682327923940456e+298, 0, 298}, + {5.099780421145732e+298, 0, 298}, + {1.0673033377644726e+299, 0, 298}, + {2.378013021177001e+299, 0, 299}, + {6.554057728282088e+299, 0, 299}, + {8.318933161474208e+299, 0, 299}, + {1.3614082701057758e+300, 0, 300}, + {5.35086581101499e+300, 0, 300}, + {8.494089296675853e+300, 0, 299}, + {1.0890134027784214e+301, 0, 301}, + {2.358927877102592e+301, 0, 301}, + {8.117690363105265e+301, 0, 301}, + {9.341511426794588e+301, 0, 301}, + {2.64210043201936e+302, 0, 302}, + {6.847933389378864e+302, 0, 302}, + {1.1002556200184007e+303, 0, 303}, + {2.5611530892094995e+303, 0, 303}, + {5.076344585537476e+303, 0, 303}, + {8.304487832500949e+303, 0, 303}, + {1.1230804446665463e+304, 0, 304}, + {4.2777559220816043e+304, 0, 304}, + {6.283260967439312e+304, 0, 304}, + {1.3747215164191865e+305, 0, 305}, + {3.3096929273918684e+305, 0, 305}, + {6.171133478197849e+305, 0, 305}, + {1.2292521010494213e+306, 0, 306}, + {2.2862202954633178e+306, 0, 305}, + {3.5065941308845355e+306, 0, 306}, + {1.0940422247589228e+307, 0, 307}, + {2.0068458193014486e+307, 0, 306}, + {2.7914963723745636e+307, 0, 307}, + {6.123699292104195e+307, 0, 307}, + {1.7026387749989901e+308, 0, 308}, +] of _ diff --git a/spec/std/sprintf_spec.cr b/spec/std/sprintf_spec.cr index ad6ceda4809d..82a4af02c523 100644 --- a/spec/std/sprintf_spec.cr +++ b/spec/std/sprintf_spec.cr @@ -378,20 +378,37 @@ describe "::sprintf" do end describe "floats" do - pending_win32 "works" do - assert_sprintf "%f", 123, "123.000000" + context "fixed format" do + it "works" do + assert_sprintf "%f", 123, "123.000000" + + assert_sprintf "%12f", 123.45, " 123.450000" + assert_sprintf "%-12f", 123.45, "123.450000 " + assert_sprintf "% f", 123.45, " 123.450000" + assert_sprintf "%+f", 123, "+123.000000" + assert_sprintf "%012f", 123, "00123.000000" + assert_sprintf "%.f", 1234.56, "1235" + assert_sprintf "%.2f", 1234.5678, "1234.57" + assert_sprintf "%10.2f", 1234.5678, " 1234.57" + assert_sprintf "%*.2f", [10, 1234.5678], " 1234.57" + assert_sprintf "%*.*f", [10, 2, 1234.5678], " 1234.57" + assert_sprintf "%.2f", 2.536_f32, "2.54" + assert_sprintf "%+0*.*f", [10, 2, 2.536_f32], "+000002.54" + assert_sprintf "%#.0f", 1234.56, "1235." + assert_sprintf "%#.1f", 1234.56, "1234.6" + + expect_raises(ArgumentError, "Expected dynamic value '*' to be an Int - \"not a number\" (String)") do + sprintf("%*f", ["not a number", 2.536_f32]) + end + + assert_sprintf "%12.2f %12.2f %6.2f %.2f", [2.0, 3.0, 4.0, 5.0], " 2.00 3.00 4.00 5.00" + + assert_sprintf "%f", 1e15, "1000000000000000.000000" + end + end + pending_win32 "works for other formats" do assert_sprintf "%g", 123, "123" - assert_sprintf "%12f", 123.45, " 123.450000" - assert_sprintf "%-12f", 123.45, "123.450000 " - assert_sprintf "% f", 123.45, " 123.450000" - assert_sprintf "%+f", 123, "+123.000000" - assert_sprintf "%012f", 123, "00123.000000" - assert_sprintf "%.f", 1234.56, "1235" - assert_sprintf "%.2f", 1234.5678, "1234.57" - assert_sprintf "%10.2f", 1234.5678, " 1234.57" - assert_sprintf "%*.2f", [10, 1234.5678], " 1234.57" - assert_sprintf "%0*.2f", [10, 1234.5678], "0001234.57" assert_sprintf "%e", 123.45, "1.234500e+02" assert_sprintf "%E", 123.45, "1.234500E+02" assert_sprintf "%G", 12345678.45, "1.23457E+07" @@ -399,17 +416,6 @@ describe "::sprintf" do assert_sprintf "%A", 12345678.45, "0X1.78C29CE666666P+23" assert_sprintf "%100.50g", 123.45, " 123.4500000000000028421709430404007434844970703125" assert_sprintf "%#.12g", 12345.0, "12345.0000000" - - assert_sprintf "%.2f", 2.536_f32, "2.54" - assert_sprintf "%0*.*f", [10, 2, 2.536_f32], "0000002.54" - - expect_raises(ArgumentError, "Expected dynamic value '*' to be an Int - \"not a number\" (String)") do - sprintf("%*f", ["not a number", 2.536_f32]) - end - - assert_sprintf "%12.2f %12.2f %6.2f %.2f", [2.0, 3.0, 4.0, 5.0], " 2.00 3.00 4.00 5.00" - - assert_sprintf "%f", 1e15, "1000000000000000.000000" end [Float32, Float64].each do |float| diff --git a/src/float/printer/ryu_printf.cr b/src/float/printer/ryu_printf.cr new file mode 100644 index 000000000000..d44a2fa8739b --- /dev/null +++ b/src/float/printer/ryu_printf.cr @@ -0,0 +1,629 @@ +# FIXME: this leads to an OOB on wasm32 (#13918) +{% skip_file if flag?(:wasm32) %} + +require "./ryu_printf_table" + +# Source port of Ryu Printf's reference implementation in C. +# +# The following is their license: +# +# Copyright 2018 Ulf Adams +# +# The contents of this file may be used under the terms of the Apache License, +# Version 2.0. +# +# (See accompanying file LICENSE-Apache or copy at +# http://www.apache.org/licenses/LICENSE-2.0) +# +# Alternatively, the contents of this file may be used under the terms of +# the Boost Software License, Version 1.0. +# (See accompanying file LICENSE-Boost or copy at +# https://www.boost.org/LICENSE_1_0.txt) +# +# Unless required by applicable law or agreed to in writing, this software +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. +module Float::Printer::RyuPrintf + # Current revision: https://github.com/ulfjack/ryu/tree/75d5a85440ed356ad7b23e9e6002d71f62a6255c + + # Returns the number of decimal digits in v, which must not contain more than 9 digits. + private def self.decimal_length9(v : UInt32) : UInt32 + # Function precondition: v is not a 10-digit number. + # (f2s: 9 digits are sufficient for round-tripping.) + # (d2fixed: We print 9-digit blocks.) + case v + when .>=(100000000) then 9_u32 + when .>=(10000000) then 8_u32 + when .>=(1000000) then 7_u32 + when .>=(100000) then 6_u32 + when .>=(10000) then 5_u32 + when .>=(1000) then 4_u32 + when .>=(100) then 3_u32 + when .>=(10) then 2_u32 + else + 1_u32 + end + end + + private def self.log10_pow2(e : Int32) : UInt32 + # The first value this approximation fails for is 2^1651 which is just greater than 10^297. + (e.to_u32! &* 78913) >> 18 + end + + M_INV_5 = 14757395258967641293u64 # 5 * m_inv_5 = 1 (mod 2^64) + N_DIV_5 = 3689348814741910323u64 # #{ n | n = 0 (mod 2^64) } = 2^64 / 5 + + private def self.pow5_factor(value : UInt64) : UInt32 + count = 0_u32 + while true + value &*= M_INV_5 + break if value > N_DIV_5 + count &+= 1 + end + count + end + + # Returns true if value is divisible by 5^p. + private def self.multiple_of_power_of_5?(value : UInt64, p : UInt32) + pow5_factor(value) >= p + end + + # Returns true if value is divisible by 2^p. + private def self.multiple_of_power_of_2?(value : UInt64, p : UInt32) + value & ~(UInt64::MAX << p) == 0 + end + + private def self.umul128(a : UInt64, b : UInt64) : {UInt64, UInt64} + a_lo = a.to_u32!.to_u64! + a_hi = (a >> 32).to_u32!.to_u64! + b_lo = b.to_u32! + b_hi = (b >> 32).to_u32! + + b00 = a_lo &* b_lo + b01 = a_lo &* b_hi + b10 = a_hi &* b_lo + b11 = a_hi &* b_hi + + b00_lo = b00.to_u32! + b00_hi = (b00 >> 32).to_u32! + + mid1 = b10 &+ b00_hi + mid1_lo = mid1.to_u32! + mid1_hi = (mid1 >> 32).to_u32! + + mid2 = b01 &+ mid1_lo + mid2_lo = mid2.to_u32! + mid2_hi = (mid2 >> 32).to_u32! + + {b11 &+ mid1_hi &+ mid2_hi, (mid2_lo.to_u64! << 32) | b00_lo} + end + + private def self.shiftright128(lo : UInt64, hi : UInt64, dist : UInt32) : UInt64 + # We don't need to handle the case dist >= 64 here + (hi << (64 &- dist)) | (lo >> dist) + end + + # Returns the low 64 bits of the high 128 bits of the 256-bit product of a and b. + private def self.umul256_hi128_lo64(a_hi : UInt64, a_lo : UInt64, b_hi : UInt64, b_lo : UInt64) + b00_hi, _ = umul128(a_lo, b_lo) + b01_hi, b01_lo = umul128(a_lo, b_hi) + b10_hi, b10_lo = umul128(a_hi, b_lo) + _, b11_lo = umul128(a_hi, b_hi) + + temp1_lo = b10_lo &+ b00_hi + temp1_hi = b10_hi &+ (temp1_lo < b10_lo ? 1 : 0) + temp2_lo = b01_lo &+ temp1_lo + temp2_hi = b01_hi &+ (temp2_lo < b01_lo ? 1 : 0) + b11_lo &+ temp1_hi &+ temp2_hi + end + + private def self.uint128_mod1e9(v_hi : UInt64, v_lo : UInt64) : UInt32 + # After multiplying, we're going to shift right by 29, then truncate to uint32_t. + # This means that we need only 29 + 32 = 61 bits, so we can truncate to uint64_t before shifting. + multiplied = umul256_hi128_lo64(v_hi, v_lo, 0x89705F4136B4A597u64, 0x31680A88F8953031u64) + + # For uint32_t truncation, see the mod1e9() comment in d2s_intrinsics.h. + shifted = (multiplied >> 29).to_u32! + + v_lo.to_u32! &- 1000000000_u32 &* shifted + end + + private def self.mulshift_mod1e9(m : UInt64, mul : {UInt64, UInt64, UInt64}, j : Int32) : UInt32 + high0, low0 = umul128(m, mul[0]) + high1, low1 = umul128(m, mul[1]) + high2, low2 = umul128(m, mul[2]) + + s0high = low1 &+ high0 # 64 + c1 = s0high < low1 ? 1 : 0 + s1low = low2 &+ high1 &+ c1 # 128 + c2 = s1low < low2 ? 1 : 0 # high1 + c1 can't overflow, so compare against low2 + s1high = high2 &+ c2 # 192 + dist = (j &- 128).to_u32! # dist: [0, 52] + shiftedhigh = s1high >> dist + shiftedlow = shiftright128(s1low, s1high, dist) + uint128_mod1e9(shiftedhigh, shiftedlow) + end + + private def self.index_for_exponent(e : UInt32) + (e &+ 15) // 16 + end + + private ADDITIONAL_BITS_2 = 120 + + private def self.pow10_bits_for_index(idx : UInt32) + idx &* 16 &+ ADDITIONAL_BITS_2 + end + + private def self.length_for_index(idx : UInt32) + # +1 for ceil, +16 for mantissa, +8 to round up when dividing by 9 + (log10_pow2(16 &* idx.to_i32!) &+ 25) // 9 + end + + # Convert `digits` to a sequence of decimal digits. Append the digits to the result. + # The caller has to guarantee that: + # 10^(olength-1) <= digits < 10^olength + # e.g., by passing `olength` as `decimalLength9(digits)`. + private def self.append_n_digits(olength : UInt32, digits : UInt32, result : UInt8*) + i = 0_u32 + while digits >= 10000 + c = digits &- 10000 &* (digits // 10000) + digits //= 10000 + c0 = (c % 100) << 1 + c1 = (c // 100) << 1 + (DIGIT_TABLE + c0).copy_to(result + olength - i - 2, 2) + (DIGIT_TABLE + c1).copy_to(result + olength - i - 4, 2) + i &+= 4 + end + if digits >= 100 + c = (digits % 100) << 1 + digits //= 100 + (DIGIT_TABLE + c).copy_to(result + olength - i - 2, 2) + i &+= 2 + end + if digits >= 10 + c = digits << 1 + (DIGIT_TABLE + c).copy_to(result + olength - i - 2, 2) + else + result.value = '0'.ord.to_u8! &+ digits + end + end + + # Convert `digits` to a sequence of decimal digits. Print the first digit, followed by a decimal + # dot '.' followed by the remaining digits. The caller has to guarantee that: + # 10^(olength-1) <= digits < 10^olength + # e.g., by passing `olength` as `decimalLength9(digits)`. + private def self.append_d_digits(olength : UInt32, digits : UInt32, result : UInt8*) + i = 0_u32 + while digits >= 10000 + c = digits &- 10000 &* (digits // 10000) + digits //= 10000 + c0 = (c % 100) << 1 + c1 = (c // 100) << 1 + (DIGIT_TABLE + c0).copy_to(result + olength + 1 - i - 2, 2) + (DIGIT_TABLE + c1).copy_to(result + olength + 1 - i - 4, 2) + i &+= 4 + end + if digits >= 100 + c = (digits % 100) << 1 + digits //= 100 + (DIGIT_TABLE + c).copy_to(result + olength + 1 - i - 2, 2) + i &+= 2 + end + if digits >= 10 + c = digits << 1 + result[2] = DIGIT_TABLE[c &+ 1] + result[1] = '.'.ord.to_u8! + result[0] = DIGIT_TABLE[c] + else + result[1] = '.'.ord.to_u8! + result[0] = '0'.ord.to_u8! &+ digits + end + end + + # Convert `digits` to decimal and write the last `count` decimal digits to result. + # If `digits` contains additional digits, then those are silently ignored. + private def self.append_c_digits(count : UInt32, digits : UInt32, result : UInt8*) + i = 0_u32 + + # Copy pairs of digits from DIGIT_TABLE. + while i < count &- 1 + c = (digits % 100) << 1 + digits //= 100 + (DIGIT_TABLE + c).copy_to(result + count - i - 2, 2) + i &+= 2 + end + + # Generate the last digit if count is odd. + if i < count + c = '0'.ord.to_u8! &+ (digits % 10) + result[count - i - 1] = c + end + end + + # Convert `digits` to decimal and write the last 9 decimal digits to result. + # If `digits` contains additional digits, then those are silently ignored. + private def self.append_nine_digits(digits : UInt32, result : UInt8*) + if digits == 0 + Slice.new(result, 9).fill('0'.ord.to_u8!) + return + end + + c = digits &- 10000 &* (digits // 10000) + digits //= 10000 + c0 = (c % 100) << 1 + c1 = (c // 100) << 1 + (DIGIT_TABLE + c0).copy_to(result + 7, 2) + (DIGIT_TABLE + c1).copy_to(result + 5, 2) + + c = digits &- 10000 &* (digits // 10000) + digits //= 10000 + c0 = (c % 100) << 1 + c1 = (c // 100) << 1 + (DIGIT_TABLE + c0).copy_to(result + 3, 2) + (DIGIT_TABLE + c1).copy_to(result + 1, 2) + + result.value = '0'.ord.to_u8! &+ digits + end + + MANTISSA_BITS = Float64::MANT_DIGITS - 1 + EXPONENT_BITS = 11 + + # NOTE: in Crystal *d* must be positive and finite + private def self.extract_float(d : Float64) + bits = d.unsafe_as(UInt64) + + ieee_mantissa = bits & ~(UInt64::MAX << MANTISSA_BITS) + ieee_exponent = (bits >> MANTISSA_BITS) & ~(UInt64::MAX << EXPONENT_BITS) + + if ieee_exponent == 0 + e2 = 1 &- 1023 &- MANTISSA_BITS + m2 = ieee_mantissa + else + e2 = ieee_exponent.to_i32! &- 1023 &- MANTISSA_BITS + m2 = (1_u64 << MANTISSA_BITS) | ieee_mantissa + end + + {e2, m2} + end + + def self.d2fixed_buffered_n(d : Float64, precision : UInt32, result : UInt8*) + e2, m2 = extract_float(d) + index = 0 + nonzero = false + + if e2 >= -MANTISSA_BITS + idx = e2 < 0 ? 0_u32 : index_for_exponent(e2.to_u32!) + p10bits = pow10_bits_for_index(idx) + len = length_for_index(idx).to_i32! + + (len - 1).downto(0) do |i| + j = p10bits &- e2 + # Temporary: j is usually around 128, and by shifting a bit, we push it to 128 or above, which is + # a slightly faster code path in mulshift_mod1e9. Instead, we can just increase the multipliers. + digits = mulshift_mod1e9(m2 << 8, POW10_SPLIT[POW10_OFFSET[idx] &+ i], (j &+ 8).to_i32!) + if nonzero + append_nine_digits(digits, result + index) + index &+= 9 + elsif digits != 0 + olength = decimal_length9(digits) + append_n_digits(olength, digits, result + index) + index &+= olength + nonzero = true + end + end + end + + unless nonzero + result[index] = '0'.ord.to_u8! + index &+= 1 + end + + if precision > 0 + result[index] = '.'.ord.to_u8! + index &+= 1 + end + + if e2 >= 0 + Slice.new(result + index, precision).fill('0'.ord.to_u8!) + index &+= precision + return index + end + + idx = (0 &- e2) // 16 + blocks = precision // 9 &+ 1 + # 0 = don't round up; 1 = round up unconditionally; 2 = round up if odd. + round_up = 0 + i = 0_u32 + + if blocks <= MIN_BLOCK_2[idx] + i = blocks + Slice.new(result + index, precision).fill('0'.ord.to_u8!) + index &+= precision + elsif i < MIN_BLOCK_2[idx] + i = MIN_BLOCK_2[idx].to_u32! + Slice.new(result + index, i &* 9).fill('0'.ord.to_u8!) + index &+= i &* 9 + end + + while i < blocks + j = ADDITIONAL_BITS_2 &- e2 &- 16 &* idx + p = POW10_OFFSET_2[idx] &+ i &- MIN_BLOCK_2[idx] + + if p >= POW10_OFFSET_2[idx + 1] + # If the remaining digits are all 0, then we might as well use memset. + # No rounding required in this case. + fill = precision &- 9 &* i + Slice.new(result + index, fill).fill('0'.ord.to_u8!) + index &+= fill + break + end + + # Temporary: j is usually around 128, and by shifting a bit, we push it to 128 or above, which is + # a slightly faster code path in mulShift_mod1e9. Instead, we can just increase the multipliers. + digits = mulshift_mod1e9(m2 << 8, POW10_SPLIT_2[p], j &+ 8) + if i < blocks &- 1 + append_nine_digits(digits, result + index) + index &+= 9 + else + maximum = precision &- 9 &* i + last_digit = 0_u32 + (9 &- maximum).times do + last_digit = digits % 10 + digits //= 10 + end + + if last_digit != 5 + round_up = last_digit > 5 ? 1 : 0 + else + # Is m * 10^(additionalDigits + 1) / 2^(-e2) integer? + required_twos = 0 &- e2 &- precision.to_i32! &- 1 + trailing_zeros = required_twos <= 0 || (required_twos < 60 && multiple_of_power_of_2?(m2, required_twos.to_u32!)) + round_up = trailing_zeros ? 2 : 1 + end + + if maximum > 0 + append_c_digits(maximum, digits, result + index) + index &+= maximum + end + break + end + + i &+= 1 + end + + if round_up != 0 + round_index = index + dot_index = 0 + while true + round_index &-= 1 + c = result[round_index] + if round_index == -1 || c === '-' + result[round_index &+ 1] = '1'.ord.to_u8! + if dot_index > 0 + result[dot_index] = '0'.ord.to_u8! + result[dot_index &+ 1] = '.'.ord.to_u8! + end + result[index] = '0'.ord.to_u8! + index &+= 1 + break + end + + if c === '.' + dot_index = round_index + next + elsif c === '9' + result[round_index] = '0'.ord.to_u8! + round_up = 1 + next + else + result[round_index] = c &+ 1 unless round_up == 2 && c % 2 == 0 + break + end + end + end + + index + end + + def self.d2exp_buffered_n(d : Float64, precision : UInt32, result : UInt8*) + if d == 0 + result[0] = '0'.ord.to_u8! + index = 1 + if precision > 0 + result[index] = '.'.ord.to_u8! + index &+= 1 + Slice.new(result + index, precision).fill('0'.ord.to_u8!) + index &+= precision + end + result[index] = 'e'.ord.to_u8! + result[index + 1] = '+'.ord.to_u8! + result[index + 2] = '0'.ord.to_u8! + return index &+ 3 + end + + e2, m2 = extract_float(d) + print_decimal_digit = precision > 0 + precision &+= 1 + index = 0 + digits = 0_u32 + printed_digits = 0_u32 + available_digits = 0_u32 + exp = 0 + + if e2 >= -MANTISSA_BITS + idx = e2 < 0 ? 0_u32 : index_for_exponent(e2.to_u32!) + p10bits = pow10_bits_for_index(idx) + len = length_for_index(idx).to_i32! + + (len - 1).downto(0) do |i| + j = p10bits &- e2 + # Temporary: j is usually around 128, and by shifting a bit, we push it to 128 or above, which is + # a slightly faster code path in mulshift_mod1e9. Instead, we can just increase the multipliers. + digits = mulshift_mod1e9(m2 << 8, POW10_SPLIT[POW10_OFFSET[idx] &+ i], (j &+ 8).to_i32!) + if printed_digits != 0 + if printed_digits &+ 9 > precision + available_digits = 9_u32 + break + end + append_nine_digits(digits, result + index) + index &+= 9 + printed_digits &+= 9 + elsif digits != 0 + available_digits = decimal_length9(digits) + exp = i &* 9 &+ available_digits.to_i32! &- 1 + break if available_digits > precision + if print_decimal_digit + append_d_digits(available_digits, digits, result + index) + index &+= available_digits &+ 1 # +1 for decimal point + else + result[index] = '0'.ord.to_u8! &+ digits + index &+= 1 + end + printed_digits = available_digits + available_digits = 0_u32 + end + end + end + + if e2 < 0 && available_digits == 0 + idx = (0 &- e2) // 16 + (MIN_BLOCK_2[idx]..200).each do |i| + j = ADDITIONAL_BITS_2 &- e2 &- 16 &* idx + p = POW10_OFFSET_2[idx] &+ i &- MIN_BLOCK_2[idx] + # Temporary: j is usually around 128, and by shifting a bit, we push it to 128 or above, which is + # a slightly faster code path in mulShift_mod1e9. Instead, we can just increase the multipliers. + digits = p >= POW10_OFFSET_2[idx &+ 1] ? 0_u32 : mulshift_mod1e9(m2 << 8, POW10_SPLIT_2[p], j &+ 8) + + if printed_digits != 0 + if printed_digits &+ 9 > precision + available_digits = 9_u32 + break + end + append_nine_digits(digits, result + index) + index &+= 9 + printed_digits &+= 9 + elsif digits != 0 + available_digits = decimal_length9(digits) + exp = (-1 &- i) &* 9 &+ available_digits.to_i32! &- 1 + break if available_digits > precision + if print_decimal_digit + append_d_digits(available_digits, digits, result + index) + index &+= available_digits &+ 1 # +1 for decimal point + else + result[index] = '0'.ord.to_u8! &+ digits + index &+= 1 + end + printed_digits = available_digits + available_digits = 0_u32 + end + end + end + + maximum = precision &- printed_digits + digits = 0_u32 if available_digits == 0 + last_digit = 0_u32 + if available_digits > maximum + (available_digits &- maximum).times do + last_digit = digits % 10 + digits //= 10 + end + end + + # 0 = don't round up; 1 = round up unconditionally; 2 = round up if odd. + round_up = 0 + if last_digit != 5 + round_up = last_digit > 5 ? 1 : 0 + else + # Is m * 2^e2 * 10^(precision + 1 - exp) integer? + # precision was already increased by 1, so we don't need to write + 1 here. + rexp = precision.to_i32! &- exp + required_twos = 0 &- e2 &- rexp + trailing_zeros = required_twos <= 0 || (required_twos < 60 && multiple_of_power_of_2?(m2, required_twos.to_u32!)) + if rexp < 0 + required_fives = 0 &- rexp + trailing_zeros = trailing_zeros && multiple_of_power_of_5?(m2, required_fives.to_u32!) + end + round_up = trailing_zeros ? 2 : 1 + end + + if printed_digits != 0 + if digits == 0 + Slice.new(result + index, maximum).fill('0'.ord.to_u8!) + else + append_c_digits(maximum, digits, result + index) + end + index &+= maximum + else + if print_decimal_digit + append_d_digits(maximum, digits, result + index) + index &+= maximum &+ 1 # +1 for decimal point + else + result[index] = '0'.ord.to_u8! &+ digits + index &+= 1 + end + end + + if round_up != 0 + round_index = index + while true + round_index &-= 1 + c = result[round_index] + if round_index == -1 || c === '-' + result[round_index &+ 1] = '1'.ord.to_u8! + exp &+= 1 + break + end + + if c === '.' + next + elsif c === '9' + result[round_index] = '0'.ord.to_u8! + round_up = 1 + next + else + result[round_index] = c &+ 1 unless round_up == 2 && c % 2 == 0 + break + end + end + end + + result[index] = 'e'.ord.to_u8! + index &+= 1 + if exp < 0 + result[index] = '-'.ord.to_u8! + exp = 0 &- exp + else + result[index] = '+'.ord.to_u8! + end + index &+= 1 + + if exp >= 100 + c = exp % 10 + (DIGIT_TABLE + ((exp // 10) << 1)).copy_to(result + index, 2) + result[index &+ 2] = '0'.ord.to_u8! &+ c + index &+= 3 + elsif exp >= 10 + (DIGIT_TABLE + (exp << 1)).copy_to(result + index, 2) + index &+= 2 + else + result[index] = '0'.ord.to_u8! &+ exp + index &+= 1 + end + + index + end + + def self.d2fixed(d : Float64, precision : Int) + String.new(2000) do |buffer| + len = d2fixed_buffered_n(d, precision.to_u32, buffer) + {len, len} + end + end + + def self.d2exp(d : Float64, precision : Int) + String.new(2000) do |buffer| + len = d2exp_buffered_n(d, precision.to_u32, buffer) + {len, len} + end + end +end diff --git a/src/float/printer/ryu_printf_table.cr b/src/float/printer/ryu_printf_table.cr new file mode 100644 index 000000000000..dc733c492991 --- /dev/null +++ b/src/float/printer/ryu_printf_table.cr @@ -0,0 +1,4415 @@ +# FIXME: this leads to an OOB on wasm32 (#13918) +{% skip_file if flag?(:wasm32) %} + +module Float::Printer::RyuPrintf + {% begin %} + # A table of all two-digit numbers. This is used to speed up decimal digit + # generation by copying pairs of digits into the final output. + DIGIT_TABLE = "{% for i in 0..9 %}0{{ i }}{% end %}{% for i in 10..99 %}{{ i }}{% end %}".to_unsafe + {% end %} + + # TODO: this is needed to avoid generating lots of allocas + # in LLVM, which makes LLVM really slow. The compiler should + # try to avoid/reuse temporary allocas. + # Explanation: https://github.com/crystal-lang/crystal/issues/4516#issuecomment-306226171 + private def self.put(array : Array, values) : Nil + array << values + end + + private POW10_OFFSET = [ + 0, 2, 5, 8, 12, 16, 21, 26, 32, 39, + 46, 54, 62, 71, 80, 90, 100, 111, 122, 134, + 146, 159, 173, 187, 202, 217, 233, 249, 266, 283, + 301, 319, 338, 357, 377, 397, 418, 440, 462, 485, + 508, 532, 556, 581, 606, 632, 658, 685, 712, 740, + 769, 798, 828, 858, 889, 920, 952, 984, 1017, 1050, + 1084, 1118, 1153, 1188, + ] of UInt16 + + private POW10_SPLIT = begin + data = Array({UInt64, UInt64, UInt64}).new(1224) + put(data, {1u64, 72057594037927936u64, 0u64}) + put(data, {699646928636035157u64, 72057594u64, 0u64}) + put(data, {1u64, 0u64, 256u64}) + put(data, {11902091922964236229u64, 4722366482869u64, 0u64}) + put(data, {6760415703743915872u64, 4722u64, 0u64}) + put(data, {1u64, 0u64, 16777216u64}) + put(data, {13369850649504950658u64, 309485009821345068u64, 0u64}) + put(data, {15151142278969419334u64, 309485009u64, 0u64}) + put(data, {1u64, 0u64, 75511627776u64}) + put(data, {4635408826454083567u64, 9437866644873197963u64, 1099u64}) + put(data, {12367138975830625353u64, 20282409603651u64, 0u64}) + put(data, {7555853734021184432u64, 20282u64, 0u64}) + put(data, {1u64, 0u64, 250037927936u64}) + put(data, {5171444645924616995u64, 699646928636035156u64, 72057594u64}) + put(data, {16672297533003297786u64, 1329227995784915872u64, 0u64}) + put(data, {14479142226848862515u64, 1329227995u64, 0u64}) + put(data, {1u64, 0u64, 181645213696u64}) + put(data, {12214193123817091081u64, 11902091922964236228u64, 114366482869u64}) + put(data, {16592893013671929435u64, 6760415703743915871u64, 4722u64}) + put(data, {4549827147718617003u64, 87112285931760u64, 0u64}) + put(data, {5274510003815168971u64, 87112u64, 0u64}) + put(data, {1u64, 0u64, 44724781056u64}) + put(data, {9794971998307800535u64, 13369850649504950657u64, 209821345068u64}) + put(data, {14720142899209240169u64, 15151142278969419333u64, 309485009u64}) + put(data, {4300745446091561535u64, 5708990770823839524u64, 0u64}) + put(data, {15197156861335443364u64, 5708990770u64, 0u64}) + put(data, {1u64, 0u64, 139251286016u64}) + put(data, {13484604155038683037u64, 4635408826454083566u64, 67670423947u64}) + put(data, {8356963862052375699u64, 12367138975830625352u64, 58409603651u64}) + put(data, {5850852848337610021u64, 7555853734021184431u64, 20282u64}) + put(data, {2712780827214982050u64, 374144419156711u64, 0u64}) + put(data, {7732076577307618052u64, 374144u64, 0u64}) + put(data, {1u64, 0u64, 84280344576u64}) + put(data, {17296309485351745867u64, 5171444645924616994u64, 160903807060u64}) + put(data, {16598859101615853088u64, 16672297533003297785u64, 219784915872u64}) + put(data, {7469952526870444257u64, 14479142226848862514u64, 1329227995u64}) + put(data, {13531654022114669524u64, 6073184580144670117u64, 1u64}) + put(data, {15757609704383306943u64, 24519928653u64, 0u64}) + put(data, {9590990814237149590u64, 24u64, 0u64}) + put(data, {1u64, 0u64, 196662132736u64}) + put(data, {15408590707489433890u64, 12214193123817091080u64, 95899502532u64}) + put(data, {18332056844289122710u64, 16592893013671929434u64, 240246646623u64}) + put(data, {11114572877353986193u64, 4549827147718617002u64, 72285931760u64}) + put(data, {1703393793997526525u64, 5274510003815168970u64, 87112u64}) + put(data, {5082852056285196265u64, 1606938044258990u64, 0u64}) + put(data, {816434266573722365u64, 1606938u64, 0u64}) + put(data, {1u64, 0u64, 129530986496u64}) + put(data, {5736523019264798742u64, 9794971998307800534u64, 69797980545u64}) + put(data, {10129314776268243339u64, 14720142899209240168u64, 36233143877u64}) + put(data, {16511595775483995364u64, 4300745446091561534u64, 50823839524u64}) + put(data, {12367293405401453325u64, 15197156861335443363u64, 5708990770u64}) + put(data, {16934621733248854291u64, 13078571300009428617u64, 5u64}) + put(data, {10278280417769171336u64, 105312291668u64, 0u64}) + put(data, {5760764486226151240u64, 105u64, 0u64}) + put(data, {1u64, 0u64, 238731001856u64}) + put(data, {4128368337188369761u64, 13484604155038683036u64, 72453031918u64}) + put(data, {10240941003671005056u64, 8356963862052375698u64, 175317175368u64}) + put(data, {17933378316822368251u64, 5850852848337610020u64, 231147060143u64}) + put(data, {8346249813075698616u64, 2712780827214982049u64, 128419156711u64}) + put(data, {15906203609160902695u64, 7732076577307618051u64, 374144u64}) + put(data, {14525607416135386328u64, 6901746346790563u64, 0u64}) + put(data, {6397156777364256320u64, 6901746u64, 0u64}) + put(data, {1u64, 0u64, 34937634816u64}) + put(data, {16798760952716600048u64, 17296309485351745866u64, 249899825954u64}) + put(data, {2419982808370854967u64, 16598859101615853087u64, 50404946937u64}) + put(data, {2922947087773078956u64, 7469952526870444256u64, 165733552434u64}) + put(data, {15419220167069510190u64, 13531654022114669523u64, 77854221733u64}) + put(data, {3452124642157173416u64, 15757609704383306942u64, 24519928653u64}) + put(data, {5979700067267186899u64, 9590990814237149589u64, 24u64}) + put(data, {4913998146922579597u64, 452312848583u64, 0u64}) + put(data, {5771037749337678924u64, 452u64, 0u64}) + put(data, {1u64, 0u64, 8835301376u64}) + put(data, {3464734175350698519u64, 15408590707489433889u64, 90993782792u64}) + put(data, {9334527711335850125u64, 18332056844289122709u64, 170602522202u64}) + put(data, {7269882896518450106u64, 11114572877353986192u64, 202092341162u64}) + put(data, {1372511258182263196u64, 1703393793997526524u64, 174275541962u64}) + put(data, {7571228438575951046u64, 5082852056285196264u64, 26044258990u64}) + put(data, {2992506536646070406u64, 816434266573722364u64, 1606938u64}) + put(data, {524517896824344606u64, 29642774844752946u64, 0u64}) + put(data, {15582941400898702773u64, 29642774u64, 0u64}) + put(data, {1u64, 0u64, 214310977536u64}) + put(data, {3846112492507251066u64, 5736523019264798741u64, 104549111254u64}) + put(data, {16681117750123089487u64, 10129314776268243338u64, 62895095400u64}) + put(data, {14986314536556547267u64, 16511595775483995363u64, 163670432318u64}) + put(data, {2573712825027107389u64, 12367293405401453324u64, 137918027683u64}) + put(data, {7504855874008324928u64, 16934621733248854290u64, 84557186697u64}) + put(data, {9572138030626879787u64, 10278280417769171335u64, 105312291668u64}) + put(data, {8520676959353394843u64, 5760764486226151239u64, 105u64}) + put(data, {13448984662897903496u64, 1942668892225u64, 0u64}) + put(data, {12338883700918130648u64, 1942u64, 0u64}) + put(data, {1u64, 0u64, 156223799296u64}) + put(data, {2517285787892561600u64, 4128368337188369760u64, 146555162524u64}) + put(data, {4338831817635138103u64, 10240941003671005055u64, 36972170386u64}) + put(data, {1561495325934523196u64, 17933378316822368250u64, 161452451108u64}) + put(data, {12262635050079398786u64, 8346249813075698615u64, 3862277025u64}) + put(data, {11144065765517284188u64, 15906203609160902694u64, 163787434755u64}) + put(data, {1212260522471875711u64, 14525607416135386327u64, 242346790563u64}) + put(data, {9695352922247418869u64, 6397156777364256319u64, 6901746u64}) + put(data, {7227025834627242948u64, 127314748520905380u64, 0u64}) + put(data, {9609008238705447829u64, 127314748u64, 0u64}) + put(data, {1u64, 0u64, 74910662656u64}) + put(data, {3609144142396852269u64, 16798760952716600047u64, 31131187530u64}) + put(data, {11568848377382068865u64, 2419982808370854966u64, 224158453279u64}) + put(data, {10068303578029323957u64, 2922947087773078955u64, 211835877600u64}) + put(data, {11645070846862630231u64, 15419220167069510189u64, 190187140051u64}) + put(data, {12449386705878485055u64, 3452124642157173415u64, 149324160190u64}) + put(data, {15025619323517318418u64, 5979700067267186898u64, 199266388373u64}) + put(data, {14996237555047131272u64, 4913998146922579596u64, 196312848583u64}) + put(data, {10211005638256058413u64, 5771037749337678923u64, 452u64}) + put(data, {1014743503555840530u64, 8343699359066u64, 0u64}) + put(data, {12900897707145290678u64, 8343u64, 0u64}) + put(data, {1u64, 0u64, 33187823616u64}) + put(data, {4718003016239473662u64, 3464734175350698518u64, 149506025761u64}) + put(data, {14865830648693666725u64, 9334527711335850124u64, 144394101141u64}) + put(data, {14754517212823091778u64, 7269882896518450105u64, 252074403984u64}) + put(data, {11113946551474911901u64, 1372511258182263195u64, 232410437116u64}) + put(data, {1963520352638130630u64, 7571228438575951045u64, 252162224104u64}) + put(data, {13342587341404964200u64, 2992506536646070405u64, 50028434172u64}) + put(data, {6240392545013573291u64, 524517896824344605u64, 22844752946u64}) + put(data, {14377490861349714758u64, 15582941400898702772u64, 29642774u64}) + put(data, {1717863312631397839u64, 546812681195752981u64, 0u64}) + put(data, {3611005143890591770u64, 546812681u64, 0u64}) + put(data, {1u64, 0u64, 21208498176u64}) + put(data, {13168252824351245504u64, 3846112492507251065u64, 138904285205u64}) + put(data, {735883891883379688u64, 16681117750123089486u64, 227812409738u64}) + put(data, {10609203866866106404u64, 14986314536556547266u64, 12139521251u64}) + put(data, {12358191111890306470u64, 2573712825027107388u64, 18406839052u64}) + put(data, {15229916368406413528u64, 7504855874008324927u64, 135518906642u64}) + put(data, {7241424335568075942u64, 9572138030626879786u64, 71461906823u64}) + put(data, {6049715868779871913u64, 8520676959353394842u64, 65729070919u64}) + put(data, {2000548404719336762u64, 13448984662897903495u64, 150668892225u64}) + put(data, {1410974761895205301u64, 12338883700918130647u64, 1942u64}) + put(data, {16000132467694084868u64, 35835915874844u64, 0u64}) + put(data, {16894908866816792556u64, 35835u64, 0u64}) + put(data, {1u64, 0u64, 96136462336u64}) + put(data, {589096329272056762u64, 2517285787892561599u64, 127235208544u64}) + put(data, {7097729792403256904u64, 4338831817635138102u64, 250084648831u64}) + put(data, {8553736750439287020u64, 1561495325934523195u64, 183664758778u64}) + put(data, {2114152625261065696u64, 12262635050079398785u64, 38604121015u64}) + put(data, {9817523680007641224u64, 11144065765517284187u64, 215065716774u64}) + put(data, {13047215537500048015u64, 1212260522471875710u64, 63525586135u64}) + put(data, {16755544192002345880u64, 9695352922247418868u64, 164391777855u64}) + put(data, {6930119832670648356u64, 7227025834627242947u64, 60520905380u64}) + put(data, {14560698131901886167u64, 9609008238705447828u64, 127314748u64}) + put(data, {16408020927503338035u64, 2348542582773833227u64, 0u64}) + put(data, {14274703510609809116u64, 2348542582u64, 0u64}) + put(data, {1u64, 0u64, 239195652096u64}) + put(data, {16428432973129962470u64, 3609144142396852268u64, 54627148527u64}) + put(data, {3721112279790863774u64, 11568848377382068864u64, 171545803830u64}) + put(data, {18032764903259620753u64, 10068303578029323956u64, 45631280555u64}) + put(data, {18058455550468776079u64, 11645070846862630230u64, 167674882605u64}) + put(data, {15692090139033993190u64, 12449386705878485054u64, 210814540455u64}) + put(data, {389416944300619393u64, 15025619323517318417u64, 140812947666u64}) + put(data, {12009691357260487293u64, 14996237555047131271u64, 75553539724u64}) + put(data, {13494259174449809900u64, 10211005638256058412u64, 90055009355u64}) + put(data, {18288583400616279877u64, 1014743503555840529u64, 151699359066u64}) + put(data, {7216107869057472u64, 12900897707145290677u64, 8343u64}) + put(data, {17237061291959073878u64, 153914086704665u64, 0u64}) + put(data, {1599418782488783273u64, 153914u64, 0u64}) + put(data, {1u64, 0u64, 22255763456u64}) + put(data, {9565464987240335777u64, 4718003016239473661u64, 140805878294u64}) + put(data, {857713933775880687u64, 14865830648693666724u64, 185799843980u64}) + put(data, {4621617820081363356u64, 14754517212823091777u64, 155602488249u64}) + put(data, {9630162611715632528u64, 11113946551474911900u64, 197106442651u64}) + put(data, {9283986497984645815u64, 1963520352638130629u64, 133723303109u64}) + put(data, {8981807745082630996u64, 13342587341404964199u64, 29338292357u64}) + put(data, {18350140531565934622u64, 6240392545013573290u64, 180779405341u64}) + put(data, {4411619033127524143u64, 14377490861349714757u64, 21093125556u64}) + put(data, {1852297584111266889u64, 1717863312631397838u64, 9195752981u64}) + put(data, {11746243463811666096u64, 3611005143890591769u64, 546812681u64}) + put(data, {6335244004343789147u64, 10086913586276986678u64, 0u64}) + put(data, {5109502367228239844u64, 10086913586u64, 0u64}) + put(data, {1603272682579847821u64, 10u64, 0u64}) + put(data, {1u64, 0u64, 121713852416u64}) + put(data, {6609546910952910052u64, 13168252824351245503u64, 78039892345u64}) + put(data, {3911171343112928288u64, 735883891883379687u64, 194575126094u64}) + put(data, {5254510615100863555u64, 10609203866866106403u64, 60669938882u64}) + put(data, {3881927570803887650u64, 12358191111890306469u64, 63825615420u64}) + put(data, {6379348759607163190u64, 15229916368406413527u64, 42392558399u64}) + put(data, {14595733737222406466u64, 7241424335568075941u64, 154327955754u64}) + put(data, {14670223432002373542u64, 6049715868779871912u64, 135108449946u64}) + put(data, {4045087795619708513u64, 2000548404719336761u64, 215076489095u64}) + put(data, {12598467307137142718u64, 1410974761895205300u64, 28867368919u64}) + put(data, {734704388050777108u64, 16000132467694084867u64, 251915874844u64}) + put(data, {5682201693687285822u64, 16894908866816792555u64, 35835u64}) + put(data, {11048712694145438788u64, 661055968790248u64, 0u64}) + put(data, {17871025777010319485u64, 661055u64, 0u64}) + put(data, {1u64, 0u64, 191031934976u64}) + put(data, {15268761435931663695u64, 589096329272056761u64, 54384768703u64}) + put(data, {5016238054648555438u64, 7097729792403256903u64, 59463698998u64}) + put(data, {14236047313993899750u64, 8553736750439287019u64, 129114608443u64}) + put(data, {6957759675154690848u64, 2114152625261065695u64, 91532209025u64}) + put(data, {18439367135478514473u64, 9817523680007641223u64, 126707290971u64}) + put(data, {8539004472540641041u64, 13047215537500048014u64, 244908319870u64}) + put(data, {1908462039431738399u64, 16755544192002345879u64, 195375682548u64}) + put(data, {714690453250792146u64, 6930119832670648355u64, 148789337027u64}) + put(data, {13782189447673929633u64, 14560698131901886166u64, 11889480596u64}) + put(data, {3584742913798803164u64, 16408020927503338034u64, 118773833227u64}) + put(data, {4347581515245125291u64, 14274703510609809115u64, 2348542582u64}) + put(data, {16836742268156371392u64, 6429475823218628948u64, 2u64}) + put(data, {11764082328865615308u64, 43322963970u64, 0u64}) + put(data, {5957633711383291746u64, 43u64, 0u64}) + put(data, {1u64, 0u64, 44890587136u64}) + put(data, {9917186842884466953u64, 16428432973129962469u64, 128201721900u64}) + put(data, {4751011869809829335u64, 3721112279790863773u64, 180977558144u64}) + put(data, {11068497969931435029u64, 18032764903259620752u64, 86978950836u64}) + put(data, {17118056985122509954u64, 18058455550468776078u64, 62850669910u64}) + put(data, {14607066080907684459u64, 15692090139033993189u64, 17021110334u64}) + put(data, {11768892370493391107u64, 389416944300619392u64, 135651046673u64}) + put(data, {4043396447647747170u64, 12009691357260487292u64, 44731525255u64}) + put(data, {1670341095362518057u64, 13494259174449809899u64, 17991426092u64}) + put(data, {3190817644167043165u64, 18288583400616279876u64, 181000391185u64}) + put(data, {10425820027224322486u64, 7216107869057471u64, 25934422965u64}) + put(data, {13139964660506311565u64, 17237061291959073877u64, 58086704665u64}) + put(data, {2297772885416059937u64, 1599418782488783272u64, 153914u64}) + put(data, {7677687919964523763u64, 2839213766779714u64, 0u64}) + put(data, {14144589152747892828u64, 2839213u64, 0u64}) + put(data, {1u64, 0u64, 253518544896u64}) + put(data, {17069730341503660290u64, 9565464987240335776u64, 164046496765u64}) + put(data, {18167423787163077107u64, 857713933775880686u64, 65250538404u64}) + put(data, {3765746945827805904u64, 4621617820081363355u64, 156522052161u64}) + put(data, {10241734342430761691u64, 9630162611715632527u64, 197503285916u64}) + put(data, {13345717282537140784u64, 9283986497984645814u64, 103486904773u64}) + put(data, {9313926784816939953u64, 8981807745082630995u64, 170994763111u64}) + put(data, {550974205049535019u64, 18350140531565934621u64, 69239154346u64}) + put(data, {4494692285504086222u64, 4411619033127524142u64, 206100413253u64}) + put(data, {1134308559863725587u64, 1852297584111266888u64, 25636765134u64}) + put(data, {17587558045116130233u64, 11746243463811666095u64, 54343434265u64}) + put(data, {9817142032346161594u64, 6335244004343789146u64, 50276986678u64}) + put(data, {6071944935834172568u64, 5109502367228239843u64, 10086913586u64}) + put(data, {11564168293299416955u64, 1603272682579847820u64, 10u64}) + put(data, {12458266507226064437u64, 186070713419u64, 0u64}) + put(data, {1304432355328256915u64, 186u64, 0u64}) + put(data, {1u64, 0u64, 191358304256u64}) + put(data, {15946798815542087355u64, 6609546910952910051u64, 231212025023u64}) + put(data, {12082566083831286138u64, 3911171343112928287u64, 35284847591u64}) + put(data, {11449623684706196411u64, 5254510615100863554u64, 165210439715u64}) + put(data, {17518743620362604446u64, 3881927570803887649u64, 215345825189u64}) + put(data, {9451061563087633805u64, 6379348759607163189u64, 165791236311u64}) + put(data, {13191114787623314926u64, 14595733737222406465u64, 168795274405u64}) + put(data, {8367349876734474799u64, 14670223432002373541u64, 57219284648u64}) + put(data, {6544253801674393507u64, 4045087795619708512u64, 180682964281u64}) + put(data, {16113906253336597498u64, 12598467307137142717u64, 3039828404u64}) + put(data, {10294087136797312392u64, 734704388050777107u64, 235308032771u64}) + put(data, {9127173070014462803u64, 5682201693687285821u64, 232598951915u64}) + put(data, {16266900839595484952u64, 11048712694145438787u64, 63968790248u64}) + put(data, {3299745387370952632u64, 17871025777010319484u64, 661055u64}) + put(data, {12061115182604399189u64, 12194330274671844u64, 0u64}) + put(data, {5066801222582989646u64, 12194330u64, 0u64}) + put(data, {1u64, 0u64, 185827721216u64}) + put(data, {7568423425299591513u64, 15268761435931663694u64, 71271930809u64}) + put(data, {16561505984665207377u64, 5016238054648555437u64, 235771737671u64}) + put(data, {4329114621856906245u64, 14236047313993899749u64, 223377180907u64}) + put(data, {1477500474861899139u64, 6957759675154690847u64, 135999600095u64}) + put(data, {16891579639263969684u64, 18439367135478514472u64, 142462900359u64}) + put(data, {4684451357140027420u64, 8539004472540641040u64, 151103457934u64}) + put(data, {14727186580409080709u64, 1908462039431738398u64, 35038743447u64}) + put(data, {15864176859687308834u64, 714690453250792145u64, 214747133987u64}) + put(data, {1755486942842684438u64, 13782189447673929632u64, 50194329302u64}) + put(data, {17417077516652710041u64, 3584742913798803163u64, 219235682866u64}) + put(data, {4290982361913532783u64, 4347581515245125290u64, 84912721627u64}) + put(data, {11826659981004351409u64, 16836742268156371391u64, 2637732180u64}) + put(data, {932930645678090820u64, 11764082328865615307u64, 43322963970u64}) + put(data, {12707792781328052617u64, 5957633711383291745u64, 43u64}) + put(data, {16491596426880311906u64, 799167628880u64, 0u64}) + put(data, {3092207065214166010u64, 799u64, 0u64}) + put(data, {1u64, 0u64, 229537611776u64}) + put(data, {8142946531605512550u64, 9917186842884466952u64, 157257552869u64}) + put(data, {5328402096432654515u64, 4751011869809829334u64, 144600024477u64}) + put(data, {1932004361303814512u64, 11068497969931435028u64, 142927971728u64}) + put(data, {2511477647985517771u64, 17118056985122509953u64, 229791850638u64}) + put(data, {17451375493324716694u64, 14607066080907684458u64, 128637992933u64}) + put(data, {9489266854478998489u64, 11768892370493391106u64, 124219192960u64}) + put(data, {8803053132063235169u64, 4043396447647747169u64, 235090549372u64}) + put(data, {16198682197142616773u64, 1670341095362518056u64, 68172974571u64}) + put(data, {13696242485403414202u64, 3190817644167043164u64, 191565184836u64}) + put(data, {16409082426079859931u64, 10425820027224322485u64, 85712318911u64}) + put(data, {11653410736879597610u64, 13139964660506311564u64, 168124562517u64}) + put(data, {13589514120653213261u64, 2297772885416059936u64, 66416208296u64}) + put(data, {8032934885905905774u64, 7677687919964523762u64, 173766779714u64}) + put(data, {2753021350129449273u64, 14144589152747892827u64, 2839213u64}) + put(data, {16974897459201404133u64, 52374249726338269u64, 0u64}) + put(data, {13398576176159101589u64, 52374249u64, 0u64}) + put(data, {1u64, 0u64, 160925351936u64}) + put(data, {10284586955251725351u64, 17069730341503660289u64, 238984858016u64}) + put(data, {5294476488634150891u64, 18167423787163077106u64, 155204141550u64}) + put(data, {15833244538135063323u64, 3765746945827805903u64, 143555205531u64}) + put(data, {10348512742273116664u64, 10241734342430761690u64, 182723472783u64}) + put(data, {13658504610142595663u64, 13345717282537140783u64, 83504908982u64}) + put(data, {11956362239240850266u64, 9313926784816939952u64, 29029868371u64}) + put(data, {13415901703662731781u64, 550974205049535018u64, 46243657757u64}) + put(data, {5161774027546852762u64, 4494692285504086221u64, 72061490990u64}) + put(data, {15274384838790587711u64, 1134308559863725586u64, 175953423432u64}) + put(data, {14233354597679374929u64, 17587558045116130232u64, 90532188335u64}) + put(data, {4274656492162486921u64, 9817142032346161593u64, 227329160794u64}) + put(data, {12040276505541795046u64, 6071944935834172567u64, 140626894819u64}) + put(data, {13238307206256765457u64, 11564168293299416954u64, 75675363980u64}) + put(data, {12850161204172713271u64, 12458266507226064436u64, 186070713419u64}) + put(data, {17531777095001445154u64, 1304432355328256914u64, 186u64}) + put(data, {5623628114515245990u64, 3432398830065u64, 0u64}) + put(data, {7357116143579573377u64, 3432u64, 0u64}) + put(data, {1u64, 0u64, 227864477696u64}) + put(data, {3555734177475596582u64, 15946798815542087354u64, 31654997219u64}) + put(data, {14001876724756424382u64, 12082566083831286137u64, 66620685343u64}) + put(data, {18159905057231476140u64, 11449623684706196410u64, 33949692994u64}) + put(data, {5585207679308509467u64, 17518743620362604445u64, 53512343073u64}) + put(data, {13948697622866724672u64, 9451061563087633804u64, 65715091765u64}) + put(data, {9807691927739036432u64, 13191114787623314925u64, 165453594945u64}) + put(data, {15818010096140820918u64, 8367349876734474798u64, 96354764709u64}) + put(data, {5629845624785010943u64, 6544253801674393506u64, 189873536608u64}) + put(data, {9517635131137734707u64, 16113906253336597497u64, 19558043581u64}) + put(data, {619338244618780585u64, 10294087136797312391u64, 61494785043u64}) + put(data, {11632367007491958899u64, 9127173070014462802u64, 67881830461u64}) + put(data, {12083314261009739916u64, 16266900839595484951u64, 124178879555u64}) + put(data, {16880538609458881650u64, 3299745387370952631u64, 228653834364u64}) + put(data, {17404223674486504228u64, 12061115182604399188u64, 26274671844u64}) + put(data, {7089067015287185433u64, 5066801222582989645u64, 12194330u64}) + put(data, {2592264228029443648u64, 224945689727159819u64, 0u64}) + put(data, {13413731084370224440u64, 224945689u64, 0u64}) + put(data, {1u64, 0u64, 78410285056u64}) + put(data, {9323915941641553425u64, 7568423425299591512u64, 173897801038u64}) + put(data, {12155831029092699564u64, 16561505984665207376u64, 229234681773u64}) + put(data, {17397171276588232676u64, 4329114621856906244u64, 31080095461u64}) + put(data, {11874560617553253769u64, 1477500474861899138u64, 40915694367u64}) + put(data, {13444839516837727954u64, 16891579639263969683u64, 16253944616u64}) + put(data, {16994416043584590671u64, 4684451357140027419u64, 30798362384u64}) + put(data, {15879694502877015730u64, 14727186580409080708u64, 209859998750u64}) + put(data, {4234647645735263359u64, 15864176859687308833u64, 160095165137u64}) + put(data, {7978589901512919496u64, 1755486942842684437u64, 219944181664u64}) + put(data, {6114237175390859894u64, 17417077516652710040u64, 170232614619u64}) + put(data, {8658612872088282708u64, 4290982361913532782u64, 191641124522u64}) + put(data, {10253813330683324853u64, 11826659981004351408u64, 203050574271u64}) + put(data, {13289465061747830991u64, 932930645678090819u64, 97688890827u64}) + put(data, {4123165538545565412u64, 12707792781328052616u64, 80894011233u64}) + put(data, {7846417485927038481u64, 16491596426880311905u64, 31167628880u64}) + put(data, {10562273346358018864u64, 3092207065214166009u64, 799u64}) + put(data, {2691512658346619120u64, 14742040721959u64, 0u64}) + put(data, {751187558544605998u64, 14742u64, 0u64}) + put(data, {1u64, 0u64, 8441430016u64}) + put(data, {3757709791947931308u64, 8142946531605512549u64, 214288853256u64}) + put(data, {3452755398462519465u64, 5328402096432654514u64, 20104734166u64}) + put(data, {3105818720159874523u64, 1932004361303814511u64, 129136147476u64}) + put(data, {16859138458894499364u64, 2511477647985517770u64, 106946040961u64}) + put(data, {12271894740606233755u64, 17451375493324716693u64, 2514414186u64}) + put(data, {5429638071845793701u64, 9489266854478998488u64, 97477214466u64}) + put(data, {145278150038876889u64, 8803053132063235168u64, 40878132321u64}) + put(data, {9050266019724932450u64, 16198682197142616772u64, 92742474792u64}) + put(data, {11907016253451490866u64, 13696242485403414201u64, 181889538140u64}) + put(data, {2472757296513770735u64, 16409082426079859930u64, 140631732661u64}) + put(data, {10558733798178239360u64, 11653410736879597609u64, 32736689036u64}) + put(data, {15917322570831255850u64, 13589514120653213260u64, 242435466272u64}) + put(data, {12254334656791355238u64, 8032934885905905773u64, 91149241586u64}) + put(data, {7869542424662730262u64, 2753021350129449272u64, 221920211035u64}) + put(data, {1378558986933000253u64, 16974897459201404132u64, 233726338269u64}) + put(data, {13521405041909411105u64, 13398576176159101588u64, 52374249u64}) + put(data, {3206744593298092012u64, 966134380754314586u64, 0u64}) + put(data, {13914648122214918505u64, 966134380u64, 0u64}) + put(data, {1u64, 0u64, 1557528576u64}) + put(data, {1235541077112082496u64, 10284586955251725350u64, 242287014145u64}) + put(data, {12014985518315533846u64, 5294476488634150890u64, 207858321906u64}) + put(data, {1561535086344155741u64, 15833244538135063322u64, 218560993999u64}) + put(data, {12761747276316224577u64, 10348512742273116663u64, 47740429018u64}) + put(data, {9745594781103966137u64, 13658504610142595662u64, 176648155695u64}) + put(data, {17514238702394846785u64, 11956362239240850265u64, 42727277488u64}) + put(data, {2428898913707151713u64, 13415901703662731780u64, 205279820330u64}) + put(data, {71666709959904945u64, 5161774027546852761u64, 18828026061u64}) + put(data, {4049380135452919193u64, 15274384838790587710u64, 184771591698u64}) + put(data, {18422240861777453733u64, 14233354597679374928u64, 185231729592u64}) + put(data, {2914504416394425696u64, 4274656492162486920u64, 151652704697u64}) + put(data, {12721377795748989418u64, 12040276505541795045u64, 122717650071u64}) + put(data, {2626074459217717422u64, 13238307206256765456u64, 52696608634u64}) + put(data, {4261529925046307655u64, 12850161204172713270u64, 146950399540u64}) + put(data, {11536038685430305586u64, 17531777095001445153u64, 241304857490u64}) + put(data, {12555757789435162768u64, 5623628114515245989u64, 104398830065u64}) + put(data, {11905178684546080059u64, 7357116143579573376u64, 3432u64}) + put(data, {14032797718924543051u64, 63316582777114u64, 0u64}) + put(data, {10750340288005853484u64, 63316u64, 0u64}) + put(data, {1u64, 0u64, 186192756736u64}) + put(data, {9660290106216358253u64, 3555734177475596581u64, 121759043258u64}) + put(data, {14820142034615351103u64, 14001876724756424381u64, 186984450425u64}) + put(data, {12674041783707777619u64, 18159905057231476139u64, 157302774714u64}) + put(data, {15386686816442679994u64, 5585207679308509466u64, 140756160413u64}) + put(data, {5679510383719146248u64, 13948697622866724671u64, 237531676044u64}) + put(data, {1391101719248678506u64, 9807691927739036431u64, 46857496045u64}) + put(data, {3364596672173710517u64, 15818010096140820917u64, 162305194542u64}) + put(data, {11276509210104319732u64, 5629845624785010942u64, 249515952034u64}) + put(data, {5316312656902630164u64, 9517635131137734706u64, 135033574393u64}) + put(data, {17470981304473644647u64, 619338244618780584u64, 82630591879u64}) + put(data, {7373293636384920591u64, 11632367007491958898u64, 23655037778u64}) + put(data, {7616810902585191937u64, 12083314261009739915u64, 183915095831u64}) + put(data, {12740295655921903924u64, 16880538609458881649u64, 84943484855u64}) + put(data, {18366635945916526940u64, 17404223674486504227u64, 77384299092u64}) + put(data, {4472171448243407067u64, 7089067015287185432u64, 11140526925u64}) + put(data, {229592460858185629u64, 2592264228029443647u64, 25727159819u64}) + put(data, {12749672866417114996u64, 13413731084370224439u64, 224945689u64}) + put(data, {9452256722867098693u64, 4149515568880992958u64, 0u64}) + put(data, {16251451636418604634u64, 4149515568u64, 0u64}) + put(data, {1u64, 0u64, 88505450496u64}) + put(data, {4515791283442995454u64, 9323915941641553424u64, 80658968920u64}) + put(data, {13306155670047701346u64, 12155831029092699563u64, 4943102544u64}) + put(data, {4456930152933417601u64, 17397171276588232675u64, 130643721220u64}) + put(data, {9089157128546489637u64, 11874560617553253768u64, 147728846210u64}) + put(data, {12437332180345515840u64, 13444839516837727953u64, 27921269139u64}) + put(data, {3433060408790452524u64, 16994416043584590670u64, 132860839963u64}) + put(data, {8275594526021936172u64, 15879694502877015729u64, 33229560708u64}) + put(data, {3846512444641107689u64, 4234647645735263358u64, 21432520225u64}) + put(data, {6210962618469046250u64, 7978589901512919495u64, 152331453461u64}) + put(data, {7272858906616296575u64, 6114237175390859893u64, 110469384344u64}) + put(data, {3710743300451225347u64, 8658612872088282707u64, 176555860334u64}) + put(data, {6424677242672030600u64, 10253813330683324852u64, 67720423344u64}) + put(data, {11485842256170301862u64, 13289465061747830990u64, 136223517251u64}) + put(data, {7355797963557024308u64, 4123165538545565411u64, 97425355144u64}) + put(data, {6358188982569427273u64, 7846417485927038480u64, 249572581985u64}) + put(data, {12475094728768767402u64, 10562273346358018863u64, 39145907193u64}) + put(data, {17288154837907896183u64, 2691512658346619119u64, 150040721959u64}) + put(data, {2983850577727105262u64, 751187558544605997u64, 14742u64}) + put(data, {13918604635001185935u64, 271942652322184u64, 0u64}) + put(data, {12033220395769876327u64, 271942u64, 0u64}) + put(data, {1u64, 0u64, 101203705856u64}) + put(data, {5782377197813462997u64, 3757709791947931307u64, 178187174245u64}) + put(data, {17732139848231399226u64, 3452755398462519464u64, 111168366770u64}) + put(data, {3628839527415562921u64, 3105818720159874522u64, 202913935727u64}) + put(data, {3188692267613601004u64, 16859138458894499363u64, 149665260746u64}) + put(data, {5168130193478377352u64, 12271894740606233754u64, 216294341269u64}) + put(data, {12556227529405091290u64, 5429638071845793700u64, 96007875544u64}) + put(data, {15087090312791441192u64, 145278150038876888u64, 196490615904u64}) + put(data, {10281804758610642494u64, 9050266019724932449u64, 185645480644u64}) + put(data, {14238177586158586580u64, 11907016253451490865u64, 218134048441u64}) + put(data, {7107927498217678128u64, 2472757296513770734u64, 41572390106u64}) + put(data, {3845814658485364450u64, 10558733798178239359u64, 76862879785u64}) + put(data, {714293333681725946u64, 15917322570831255849u64, 109664308812u64}) + put(data, {16766172658649116982u64, 12254334656791355237u64, 56426608749u64}) + put(data, {812461421432632215u64, 7869542424662730261u64, 228074731832u64}) + put(data, {15218024718633799196u64, 1378558986933000252u64, 148732996836u64}) + put(data, {8110797782612805146u64, 13521405041909411104u64, 90173837972u64}) + put(data, {15941193964933529227u64, 3206744593298092011u64, 108754314586u64}) + put(data, {14144280602323277933u64, 13914648122214918504u64, 966134380u64}) + put(data, {15072402647813125245u64, 17822033662586700072u64, 0u64}) + put(data, {10822706091283369889u64, 17822033662u64, 0u64}) + put(data, {15163844593710966731u64, 17u64, 0u64}) + put(data, {1u64, 0u64, 38066978816u64}) + put(data, {2408529687792073670u64, 1235541077112082495u64, 234651333670u64}) + put(data, {3980682212356510808u64, 12014985518315533845u64, 26084650986u64}) + put(data, {4202670442792148519u64, 1561535086344155740u64, 247691815706u64}) + put(data, {9419583343154651922u64, 12761747276316224576u64, 78528309751u64}) + put(data, {16359166491570434575u64, 9745594781103966136u64, 89949448782u64}) + put(data, {12567727056384237385u64, 17514238702394846784u64, 4131670873u64}) + put(data, {2068388267923286639u64, 2428898913707151712u64, 153003885060u64}) + put(data, {5689135844565021196u64, 71666709959904944u64, 62219517337u64}) + put(data, {3104061965171139313u64, 4049380135452919192u64, 80998671678u64}) + put(data, {7955173880156328016u64, 18422240861777453732u64, 136157995600u64}) + put(data, {1445179403240833754u64, 2914504416394425695u64, 229689627272u64}) + put(data, {12538201164459126715u64, 12721377795748989417u64, 16142359781u64}) + put(data, {7580606719088482667u64, 2626074459217717421u64, 54231018000u64}) + put(data, {8168318283218819755u64, 4261529925046307654u64, 33625369910u64}) + put(data, {5249615277755961676u64, 11536038685430305585u64, 165680648993u64}) + put(data, {6312997372068219831u64, 12555757789435162767u64, 128645381029u64}) + put(data, {9183815417025176703u64, 11905178684546080058u64, 26760719488u64}) + put(data, {10683849953373876937u64, 14032797718924543050u64, 84582777114u64}) + put(data, {17175012155615667568u64, 10750340288005853483u64, 63316u64}) + put(data, {18003508288378896912u64, 1167984798111281u64, 0u64}) + put(data, {14722554560950996951u64, 1167984u64, 0u64}) + put(data, {1u64, 0u64, 37523685376u64}) + put(data, {15059324482416394930u64, 9660290106216358252u64, 189803401509u64}) + put(data, {4134778595813308312u64, 14820142034615351102u64, 171687061181u64}) + put(data, {16321118342639660948u64, 12674041783707777618u64, 26834113963u64}) + put(data, {1523550293123468805u64, 15386686816442679993u64, 63307886874u64}) + put(data, {8016371634569878509u64, 5679510383719146247u64, 15075411775u64}) + put(data, {9884220139611134110u64, 1391101719248678505u64, 181182395151u64}) + put(data, {7218073002727840414u64, 3364596672173710516u64, 254611300789u64}) + put(data, {16062235669481359233u64, 11276509210104319731u64, 50288197886u64}) + put(data, {15558048660560338002u64, 5316312656902630163u64, 168947103794u64}) + put(data, {8394398745765058609u64, 17470981304473644646u64, 114399707048u64}) + put(data, {5693296366442904274u64, 7373293636384920590u64, 139412908146u64}) + put(data, {11783494675061161358u64, 7616810902585191936u64, 113690652811u64}) + put(data, {13377293110865447894u64, 12740295655921903923u64, 35995657329u64}) + put(data, {12840734051093062130u64, 18366635945916526939u64, 24242436899u64}) + put(data, {7009868331566697505u64, 4472171448243407066u64, 63012446232u64}) + put(data, {5019690705031194477u64, 229592460858185628u64, 55691161151u64}) + put(data, {8608277240439804984u64, 12749672866417114995u64, 190512407863u64}) + put(data, {12172482590657749222u64, 9452256722867098692u64, 48880992958u64}) + put(data, {16613484892678771990u64, 16251451636418604633u64, 4149515568u64}) + put(data, {5721488662757049244u64, 2758075434182769113u64, 4u64}) + put(data, {386931106438877039u64, 76545051729u64, 0u64}) + put(data, {10054429752182825659u64, 76u64, 0u64}) + put(data, {1u64, 0u64, 16244801536u64}) + put(data, {8634592106137071313u64, 4515791283442995453u64, 171721328144u64}) + put(data, {12626356501369830731u64, 13306155670047701345u64, 227241610667u64}) + put(data, {4803333258178976933u64, 4456930152933417600u64, 136492724195u64}) + put(data, {13613083223558209297u64, 9089157128546489636u64, 209674229128u64}) + put(data, {16106967997237446989u64, 12437332180345515839u64, 78186106577u64}) + put(data, {14832921244380020170u64, 3433060408790452523u64, 177448620878u64}) + put(data, {13774024637717231397u64, 8275594526021936171u64, 126208519857u64}) + put(data, {9673012968505228885u64, 3846512444641107688u64, 199336696958u64}) + put(data, {5391832334264815667u64, 6210962618469046249u64, 117394262471u64}) + put(data, {16514436292632703088u64, 7272858906616296574u64, 83201159797u64}) + put(data, {12025036352783454153u64, 3710743300451225346u64, 180348282451u64}) + put(data, {7059867105311401050u64, 6424677242672030599u64, 206622648756u64}) + put(data, {12769210631552594670u64, 11485842256170301861u64, 227398758606u64}) + put(data, {8328873878884556145u64, 7355797963557024307u64, 16344678115u64}) + put(data, {1016565892414238685u64, 6358188982569427272u64, 47676276240u64}) + put(data, {9662978461927250281u64, 12475094728768767401u64, 239937192751u64}) + put(data, {13729967277551868112u64, 17288154837907896182u64, 45161754863u64}) + put(data, {6371593776693359475u64, 2983850577727105261u64, 136754529069u64}) + put(data, {17617208110845643245u64, 13918604635001185934u64, 70652322184u64}) + put(data, {14960960225633086797u64, 12033220395769876326u64, 271942u64}) + put(data, {12090634301321662558u64, 5016456510113118u64, 0u64}) + put(data, {9409926148478635503u64, 5016456u64, 0u64}) + put(data, {1u64, 0u64, 171313463296u64}) + put(data, {4307062684900157136u64, 5782377197813462996u64, 168961261227u64}) + put(data, {15300759383869911853u64, 17732139848231399225u64, 218196719784u64}) + put(data, {16007534237643445447u64, 3628839527415562920u64, 35172859354u64}) + put(data, {7138502295759677634u64, 3188692267613601003u64, 154280164899u64}) + put(data, {8218537071653683708u64, 5168130193478377351u64, 164680674458u64}) + put(data, {2254219416760329296u64, 12556227529405091289u64, 216817872804u64}) + put(data, {3057410459568460683u64, 15087090312791441191u64, 97557377752u64}) + put(data, {8217810929938874370u64, 10281804758610642493u64, 49771853153u64}) + put(data, {11741126472498340929u64, 14238177586158586579u64, 238385321521u64}) + put(data, {1175325363726654805u64, 7107927498217678127u64, 127208482030u64}) + put(data, {9428843070696730900u64, 3845814658485364449u64, 41038721919u64}) + put(data, {12662500978715131896u64, 714293333681725945u64, 101908896041u64}) + put(data, {6443045597035184564u64, 16766172658649116981u64, 21044043621u64}) + put(data, {1921385512639171183u64, 812461421432632214u64, 60824970773u64}) + put(data, {10469475094355551399u64, 15218024718633799195u64, 32439687228u64}) + put(data, {14679174489076953574u64, 8110797782612805145u64, 235864173856u64}) + put(data, {11853074234719825644u64, 15941193964933529226u64, 104766762987u64}) + put(data, {8270896886596139124u64, 14144280602323277932u64, 40817076584u64}) + put(data, {16532667046659118126u64, 15072402647813125244u64, 254586700072u64}) + put(data, {148341279888833483u64, 10822706091283369888u64, 17822033662u64}) + put(data, {10364629296397276041u64, 15163844593710966730u64, 17u64}) + put(data, {14265682585545771671u64, 328758493846u64, 0u64}) + put(data, {13991741872911347878u64, 328u64, 0u64}) + put(data, {1u64, 0u64, 63130566656u64}) + put(data, {14029045786848724433u64, 2408529687792073669u64, 21215793215u64}) + put(data, {4005878521026842341u64, 3980682212356510807u64, 92227827221u64}) + put(data, {3428326338640386488u64, 4202670442792148518u64, 64510636636u64}) + put(data, {1010001558294829380u64, 9419583343154651921u64, 184886832192u64}) + put(data, {2012063724327403418u64, 16359166491570434574u64, 64681297848u64}) + put(data, {10997154538851372612u64, 12567727056384237384u64, 96112127552u64}) + put(data, {1917749645489607898u64, 2068388267923286638u64, 176308408672u64}) + put(data, {9763872523711218805u64, 5689135844565021195u64, 152168271536u64}) + put(data, {15875699078454059311u64, 3104061965171139312u64, 164431250840u64}) + put(data, {10966529452671276106u64, 7955173880156328015u64, 95078343332u64}) + put(data, {18073244132105736913u64, 1445179403240833753u64, 233679697247u64}) + put(data, {4435241176994913173u64, 12538201164459126714u64, 173410945513u64}) + put(data, {5464400086219074323u64, 7580606719088482666u64, 70442805421u64}) + put(data, {2445909179323258812u64, 8168318283218819754u64, 49284582214u64}) + put(data, {873962058644121211u64, 5249615277755961675u64, 143342228273u64}) + put(data, {16675872194112650857u64, 6312997372068219830u64, 58497855631u64}) + put(data, {10680102689274800355u64, 9183815417025176702u64, 74579172666u64}) + put(data, {2370498083108897524u64, 10683849953373876936u64, 43931059274u64}) + put(data, {15354400521451334666u64, 17175012155615667567u64, 49975972139u64}) + put(data, {259991949657381021u64, 18003508288378896911u64, 112798111281u64}) + put(data, {10335286558772966917u64, 14722554560950996950u64, 1167984u64}) + put(data, {16337526653906757263u64, 21545516652742137u64, 0u64}) + put(data, {12040967163702784894u64, 21545516u64, 0u64}) + put(data, {1u64, 0u64, 108816367616u64}) + put(data, {3373309160242342187u64, 15059324482416394929u64, 62224146796u64}) + put(data, {13639841054510584221u64, 4134778595813308311u64, 82884769598u64}) + put(data, {15898855427739708031u64, 16321118342639660947u64, 185082591826u64}) + put(data, {4544387940067005419u64, 1523550293123468804u64, 7434568377u64}) + put(data, {5281598644835398575u64, 8016371634569878508u64, 105535824647u64}) + put(data, {13675642405083408835u64, 9884220139611134109u64, 180391292521u64}) + put(data, {3973392623768015721u64, 7218073002727840413u64, 243870735540u64}) + put(data, {4491285101509114191u64, 16062235669481359232u64, 19843403507u64}) + put(data, {15002304272810270500u64, 15558048660560338001u64, 102455061267u64}) + put(data, {17325098540619893468u64, 8394398745765058608u64, 14308634214u64}) + put(data, {1137212864974584822u64, 5693296366442904273u64, 638784526u64}) + put(data, {2619406097224859078u64, 11783494675061161357u64, 51725184512u64}) + put(data, {8281347529729293732u64, 13377293110865447893u64, 91696097587u64}) + put(data, {11344719666795450104u64, 12840734051093062129u64, 218380005723u64}) + put(data, {17283870506679425783u64, 7009868331566697504u64, 156272117978u64}) + put(data, {11054210518010603775u64, 5019690705031194476u64, 115466655644u64}) + put(data, {6399455551799092885u64, 8608277240439804983u64, 68659871603u64}) + put(data, {12930529916573967170u64, 12172482590657749221u64, 89900618820u64}) + put(data, {14550097052337552404u64, 16613484892678771989u64, 217310162521u64}) + put(data, {12487632712206414748u64, 5721488662757049243u64, 81020975577u64}) + put(data, {5791017277843595715u64, 386931106438877038u64, 76545051729u64}) + put(data, {10227264183449036113u64, 10054429752182825658u64, 76u64}) + put(data, {2006055278511721441u64, 1412006979354u64, 0u64}) + put(data, {128746359043876333u64, 1412u64, 0u64}) + put(data, {1u64, 0u64, 253468082176u64}) + put(data, {7408146306870995754u64, 8634592106137071312u64, 97684476157u64}) + put(data, {8299024588195267962u64, 12626356501369830730u64, 128260389217u64}) + put(data, {1497052939192040881u64, 4803333258178976932u64, 36737966720u64}) + put(data, {16771714264265803747u64, 13613083223558209296u64, 63873160484u64}) + put(data, {142988846654429432u64, 16106967997237446988u64, 43804094271u64}) + put(data, {11839838367716104145u64, 14832921244380020169u64, 43746691371u64}) + put(data, {6019646776647679765u64, 13774024637717231396u64, 232524375083u64}) + put(data, {4611972391702034948u64, 9673012968505228884u64, 233292291816u64}) + put(data, {16447182322205429545u64, 5391832334264815666u64, 126895249385u64}) + put(data, {2113477168726764245u64, 16514436292632703087u64, 2651878526u64}) + put(data, {3536261187802311516u64, 12025036352783454152u64, 135382716162u64}) + put(data, {18444381860986709854u64, 7059867105311401049u64, 165692220295u64}) + put(data, {4734315730275909838u64, 12769210631552594669u64, 51451509157u64}) + put(data, {9974936316849658174u64, 8328873878884556144u64, 72055108147u64}) + put(data, {11864423681540657642u64, 1016565892414238684u64, 169523831112u64}) + put(data, {8207245621417902667u64, 9662978461927250280u64, 118744303017u64}) + put(data, {7992526918695295028u64, 13729967277551868111u64, 237345404790u64}) + put(data, {8679354522130259987u64, 6371593776693359474u64, 142955030765u64}) + put(data, {6065763799692166461u64, 17617208110845643244u64, 102811035278u64}) + put(data, {18143341109049024976u64, 14960960225633086796u64, 94655434598u64}) + put(data, {15242492331283350570u64, 12090634301321662557u64, 136510113118u64}) + put(data, {9986352353182266963u64, 9409926148478635502u64, 5016456u64}) + put(data, {17340463289911536077u64, 92537289398950870u64, 0u64}) + put(data, {7359344614214233035u64, 92537289u64, 0u64}) + put(data, {1u64, 0u64, 212233486336u64}) + put(data, {419091135888749535u64, 4307062684900157135u64, 57829455828u64}) + put(data, {1073142712661309790u64, 15300759383869911852u64, 168867770169u64}) + put(data, {11076438902195672286u64, 16007534237643445446u64, 235386978984u64}) + put(data, {1820390940081322073u64, 7138502295759677633u64, 135445527787u64}) + put(data, {18417808973944523597u64, 8218537071653683707u64, 217122201479u64}) + put(data, {10251294197731582957u64, 2254219416760329295u64, 39165742553u64}) + put(data, {1502394029870156428u64, 3057410459568460682u64, 61445488423u64}) + put(data, {321014853559106075u64, 8217810929938874369u64, 211636487741u64}) + put(data, {2390953058510591778u64, 11741126472498340928u64, 47063714515u64}) + put(data, {10685224265907994087u64, 1175325363726654804u64, 225511138607u64}) + put(data, {5967405799190505023u64, 9428843070696730899u64, 249686435553u64}) + put(data, {11210723659228214761u64, 12662500978715131895u64, 53349278201u64}) + put(data, {12327123641078462773u64, 6443045597035184563u64, 150104158517u64}) + put(data, {1709976940107894237u64, 1921385512639171182u64, 27567551382u64}) + put(data, {16607686590938553511u64, 10469475094355551398u64, 25795759643u64}) + put(data, {18332088094272679457u64, 14679174489076953573u64, 138642556441u64}) + put(data, {2946170632136780882u64, 11853074234719825643u64, 108448366218u64}) + put(data, {4824449494694383419u64, 8270896886596139123u64, 124896237676u64}) + put(data, {17008332258693407134u64, 16532667046659118125u64, 160008041596u64}) + put(data, {1773419466622750661u64, 148341279888833482u64, 202561867680u64}) + put(data, {3892343466023784379u64, 10364629296397276040u64, 150773344202u64}) + put(data, {12001571085575422796u64, 14265682585545771670u64, 72758493846u64}) + put(data, {12933506765500977582u64, 13991741872911347877u64, 328u64}) + put(data, {11884830007749143734u64, 6064523798049u64, 0u64}) + put(data, {9662368568096205337u64, 6064u64, 0u64}) + put(data, {1u64, 0u64, 197760516096u64}) + put(data, {16801499925276664442u64, 14029045786848724432u64, 87217159109u64}) + put(data, {10492407990787637084u64, 4005878521026842340u64, 38185849943u64}) + put(data, {7673849751013230269u64, 3428326338640386487u64, 17054752294u64}) + put(data, {6046724489853072367u64, 1010001558294829379u64, 14109074193u64}) + put(data, {3723941391207507903u64, 2012063724327403417u64, 72596156942u64}) + put(data, {16844122108860347659u64, 10997154538851372611u64, 110103961416u64}) + put(data, {10622020182694668027u64, 1917749645489607897u64, 11529300590u64}) + put(data, {8741198820686854862u64, 9763872523711218804u64, 240860623371u64}) + put(data, {6855480461211306807u64, 15875699078454059310u64, 79594496752u64}) + put(data, {10005708458011566304u64, 10966529452671276105u64, 217979752527u64}) + put(data, {8932093106442919061u64, 18073244132105736912u64, 186240434905u64}) + put(data, {9062763476260756743u64, 4435241176994913172u64, 106296225722u64}) + put(data, {13664977682032775521u64, 5464400086219074322u64, 170132593002u64}) + put(data, {1078499125430623453u64, 2445909179323258811u64, 75047377578u64}) + put(data, {6554586738078431161u64, 873962058644121210u64, 182904000843u64}) + put(data, {12177313698643242883u64, 16675872194112650856u64, 126578969526u64}) + put(data, {16615072271904633953u64, 10680102689274800354u64, 200128504958u64}) + put(data, {16375404983106569285u64, 2370498083108897523u64, 111832363720u64}) + put(data, {13552251831473522729u64, 15354400521451334665u64, 15014094191u64}) + put(data, {8330500218412111874u64, 259991949657381020u64, 214560277007u64}) + put(data, {7044338079053294004u64, 10335286558772966916u64, 249885659094u64}) + put(data, {2688849443046530184u64, 16337526653906757262u64, 44652742137u64}) + put(data, {855940991879596845u64, 12040967163702784893u64, 21545516u64}) + put(data, {7344363609485825662u64, 397444631628981487u64, 0u64}) + put(data, {11602660525134634992u64, 397444631u64, 0u64}) + put(data, {1u64, 0u64, 177182867456u64}) + put(data, {16945343208344873835u64, 3373309160242342186u64, 151739417265u64}) + put(data, {9617992661337889145u64, 13639841054510584220u64, 147861878679u64}) + put(data, {18280344933262742088u64, 15898855427739708030u64, 4246351763u64}) + put(data, {5179975582362777795u64, 4544387940067005418u64, 236286316036u64}) + put(data, {1798918997870037130u64, 5281598644835398574u64, 157741358060u64}) + put(data, {6327667344756325883u64, 13675642405083408834u64, 157215398045u64}) + put(data, {18380327574124007701u64, 3973392623768015720u64, 128243473053u64}) + put(data, {18015447557304295289u64, 4491285101509114190u64, 81813276544u64}) + put(data, {10315590748073249878u64, 15002304272810270499u64, 48939195473u64}) + put(data, {7697916092577993382u64, 17325098540619893467u64, 209061648432u64}) + put(data, {3124132817942110723u64, 1137212864974584821u64, 141141998289u64}) + put(data, {7448238998520507049u64, 2619406097224859077u64, 213448932749u64}) + put(data, {13892823322374205297u64, 8281347529729293731u64, 241614998485u64}) + put(data, {11042137840046332564u64, 11344719666795450103u64, 32936960497u64}) + put(data, {10513966307445593804u64, 17283870506679425782u64, 108599249952u64}) + put(data, {9388437460943526958u64, 11054210518010603774u64, 55346915180u64}) + put(data, {10967228614677896228u64, 6399455551799092884u64, 229700965431u64}) + put(data, {2310996671540235542u64, 12930529916573967169u64, 21788762341u64}) + put(data, {4989110555003898587u64, 14550097052337552403u64, 155676955925u64}) + put(data, {16271452421983657679u64, 12487632712206414747u64, 110313931675u64}) + put(data, {9523160181437090473u64, 5791017277843595714u64, 186554421102u64}) + put(data, {13137707423765072250u64, 10227264183449036112u64, 26108748474u64}) + put(data, {16846859744221860705u64, 2006055278511721440u64, 132006979354u64}) + put(data, {7767140033449795569u64, 128746359043876332u64, 1412u64}) + put(data, {17169456915721160017u64, 26046931378436u64, 0u64}) + put(data, {17180899661833327819u64, 26046u64, 0u64}) + put(data, {1u64, 0u64, 208401596416u64}) + put(data, {17572520700934791416u64, 7408146306870995753u64, 74449891024u64}) + put(data, {17968798858233825417u64, 8299024588195267961u64, 164081155402u64}) + put(data, {15338423313945305609u64, 1497052939192040880u64, 16909196452u64}) + put(data, {17895321323836726301u64, 16771714264265803746u64, 76007751440u64}) + put(data, {814069333008965773u64, 142988846654429431u64, 201641838924u64}) + put(data, {7200328959852723947u64, 11839838367716104144u64, 36326325705u64}) + put(data, {759884557248133773u64, 6019646776647679764u64, 84250015524u64}) + put(data, {13410165861863974851u64, 4611972391702034947u64, 50891603540u64}) + put(data, {6278452420856351570u64, 16447182322205429544u64, 111114571826u64}) + put(data, {9072115382556676442u64, 2113477168726764244u64, 200191701103u64}) + put(data, {2755882551854926563u64, 3536261187802311515u64, 89999871944u64}) + put(data, {8496072611504649269u64, 18444381860986709853u64, 237256647769u64}) + put(data, {4122009033579215815u64, 4734315730275909837u64, 112540742381u64}) + put(data, {10222217724450527221u64, 9974936316849658173u64, 220643171696u64}) + put(data, {2064539481554006325u64, 11864423681540657641u64, 104444915676u64}) + put(data, {7935605886598063693u64, 8207245621417902666u64, 207433275752u64}) + put(data, {7805147585347548429u64, 7992526918695295027u64, 114470508751u64}) + put(data, {5709020905457661273u64, 8679354522130259986u64, 236328825714u64}) + put(data, {16257370307404906674u64, 6065763799692166460u64, 76983552492u64}) + put(data, {14971258192939373646u64, 18143341109049024975u64, 93826297164u64}) + put(data, {1133404845901376390u64, 15242492331283350569u64, 238541361245u64}) + put(data, {9460827548162822047u64, 9986352353182266962u64, 214940028398u64}) + put(data, {1273897659779791346u64, 17340463289911536076u64, 201398950870u64}) + put(data, {7833262224435092783u64, 7359344614214233034u64, 92537289u64}) + put(data, {3033420566713364587u64, 1707011694817242694u64, 0u64}) + put(data, {15075466825360349103u64, 1707011694u64, 0u64}) + put(data, {1u64, 0u64, 207022718976u64}) + put(data, {2484134775182816690u64, 419091135888749534u64, 44058175183u64}) + put(data, {18400539815335991277u64, 1073142712661309789u64, 198600454956u64}) + put(data, {485494064952118286u64, 11076438902195672285u64, 193098683590u64}) + put(data, {17577048805241314891u64, 1820390940081322072u64, 251998431425u64}) + put(data, {2863946907557583807u64, 18417808973944523596u64, 79555723771u64}) + put(data, {13045307417786230800u64, 10251294197731582956u64, 138081444943u64}) + put(data, {12032088871615097766u64, 1502394029870156427u64, 1017402250u64}) + put(data, {8848763446997690580u64, 321014853559106074u64, 64129613825u64}) + put(data, {10031289150307672684u64, 2390953058510591777u64, 84579247168u64}) + put(data, {11592215575498656563u64, 10685224265907994086u64, 19323493716u64}) + put(data, {15894436747956898388u64, 5967405799190505022u64, 247607734547u64}) + put(data, {2091546719588500923u64, 11210723659228214760u64, 179668254711u64}) + put(data, {5863809244813756109u64, 12327123641078462772u64, 110092698035u64}) + put(data, {11303008753675411245u64, 1709976940107894236u64, 166900304494u64}) + put(data, {13238426537506910532u64, 16607686590938553510u64, 229993784486u64}) + put(data, {17258458071023005565u64, 18332088094272679456u64, 235159712229u64}) + put(data, {8385733444777075179u64, 2946170632136780881u64, 115261533931u64}) + put(data, {9530757096163247300u64, 4824449494694383418u64, 45922023539u64}) + put(data, {14423000845391072217u64, 17008332258693407133u64, 202096137261u64}) + put(data, {10953140011159884311u64, 1773419466622750660u64, 136211004362u64}) + put(data, {12228340237948264127u64, 3892343466023784378u64, 150650606472u64}) + put(data, {11279134946966259189u64, 12001571085575422795u64, 165701126806u64}) + put(data, {14640097792684582651u64, 12933506765500977581u64, 33644277925u64}) + put(data, {6232313315128656728u64, 11884830007749143733u64, 176523798049u64}) + put(data, {16136121832933322088u64, 9662368568096205336u64, 6064u64}) + put(data, {15074767079673358271u64, 111870718431542u64, 0u64}) + put(data, {13252722804829281908u64, 111870u64, 0u64}) + put(data, {1u64, 0u64, 208910811136u64}) + put(data, {7740175894281560509u64, 16801499925276664441u64, 228568794576u64}) + put(data, {15670495392425593226u64, 10492407990787637083u64, 183416000228u64}) + put(data, {15152257626756992778u64, 7673849751013230268u64, 67327793591u64}) + put(data, {4090073428152440422u64, 6046724489853072366u64, 153201875267u64}) + put(data, {14450327772834205584u64, 3723941391207507902u64, 67913121689u64}) + put(data, {4466091895542494216u64, 16844122108860347658u64, 217575820867u64}) + put(data, {10454115378553795377u64, 10622020182694668026u64, 116473861337u64}) + put(data, {2267817233475657788u64, 8741198820686854861u64, 46371636340u64}) + put(data, {5500455702636497521u64, 6855480461211306806u64, 73542410542u64}) + put(data, {15178768299492252549u64, 10005708458011566303u64, 208484209737u64}) + put(data, {7062359872332045590u64, 8932093106442919060u64, 148491293392u64}) + put(data, {12297347290027942576u64, 9062763476260756742u64, 18740779924u64}) + put(data, {8030124596941085588u64, 13664977682032775520u64, 187058465554u64}) + put(data, {6526656990996654843u64, 1078499125430623452u64, 122355324859u64}) + put(data, {6254287345256979850u64, 6554586738078431160u64, 104660133498u64}) + put(data, {6642007136244870032u64, 12177313698643242882u64, 226900704872u64}) + put(data, {2027592955437164718u64, 16615072271904633952u64, 243887712482u64}) + put(data, {942718349157325567u64, 16375404983106569284u64, 9734669043u64}) + put(data, {14617066671884002278u64, 13552251831473522728u64, 156451597321u64}) + put(data, {6831631114396133348u64, 8330500218412111873u64, 4381874332u64}) + put(data, {14603040013386939258u64, 7044338079053294003u64, 142145762820u64}) + put(data, {9906106765319401103u64, 2688849443046530183u64, 125046400654u64}) + put(data, {1396179595609933063u64, 855940991879596844u64, 239398138749u64}) + put(data, {11524884268464976417u64, 7344363609485825661u64, 23628981487u64}) + put(data, {382929570730827274u64, 11602660525134634991u64, 397444631u64}) + put(data, {6109721884461301381u64, 7331559403129590068u64, 0u64}) + put(data, {2390514825000339691u64, 7331559403u64, 0u64}) + put(data, {6116191454763441755u64, 7u64, 0u64}) + put(data, {1u64, 0u64, 42918608896u64}) + put(data, {11598868771099176310u64, 16945343208344873834u64, 156521392426u64}) + put(data, {14449966445520085105u64, 9617992661337889144u64, 126990979484u64}) + put(data, {11675595287405614726u64, 18280344933262742087u64, 234280807038u64}) + put(data, {15860796398550489897u64, 5179975582362777794u64, 174097519594u64}) + put(data, {16180408435245829662u64, 1798918997870037129u64, 194343023534u64}) + put(data, {13756992797154950706u64, 6327667344756325882u64, 104996399554u64}) + put(data, {8830551328786758466u64, 18380327574124007700u64, 78976619368u64}) + put(data, {16699955256560951264u64, 18015447557304295288u64, 35559209294u64}) + put(data, {10038983627153402074u64, 10315590748073249877u64, 219417304867u64}) + put(data, {15085100736692127346u64, 7697916092577993381u64, 245169359579u64}) + put(data, {10007783780289711125u64, 3124132817942110722u64, 197403769845u64}) + put(data, {17596907048353602192u64, 7448238998520507048u64, 163753131461u64}) + put(data, {13530650344896573509u64, 13892823322374205296u64, 247598595491u64}) + put(data, {6337724853398437005u64, 11042137840046332563u64, 246569963255u64}) + put(data, {12768885008904063297u64, 10513966307445593803u64, 254508948214u64}) + put(data, {2759773619512884114u64, 9388437460943526957u64, 148594534654u64}) + put(data, {8434364600126655292u64, 10967228614677896227u64, 65125279380u64}) + put(data, {3843827521199949338u64, 2310996671540235541u64, 19270460225u64}) + put(data, {4661660852957808994u64, 4989110555003898586u64, 155882077203u64}) + put(data, {15298044134177324417u64, 16271452421983657678u64, 194516251547u64}) + put(data, {7747773274913338217u64, 9523160181437090472u64, 80712196546u64}) + put(data, {10348785912020632966u64, 13137707423765072249u64, 224913270096u64}) + put(data, {4175372293197190170u64, 16846859744221860704u64, 236421057504u64}) + put(data, {11326064156813083145u64, 7767140033449795568u64, 4930758124u64}) + put(data, {8100407170505981763u64, 17169456915721160016u64, 190931378436u64}) + put(data, {1706556116319916846u64, 17180899661833327818u64, 26046u64}) + put(data, {15028897280749641942u64, 480481077043500u64, 0u64}) + put(data, {1421201742071739121u64, 480481u64, 0u64}) + put(data, {1u64, 0u64, 41952608256u64}) + put(data, {8480737406125178272u64, 17572520700934791415u64, 121974090537u64}) + put(data, {10947205650755620361u64, 17968798858233825416u64, 176831497593u64}) + put(data, {868577942165647781u64, 15338423313945305608u64, 226970107312u64}) + put(data, {16017710019091388479u64, 17895321323836726300u64, 247044130786u64}) + put(data, {6610879150827623375u64, 814069333008965772u64, 208390330615u64}) + put(data, {12110095866223762092u64, 7200328959852723946u64, 20041193424u64}) + put(data, {7756802952949470775u64, 759884557248133772u64, 3726966548u64}) + put(data, {2941800790804618759u64, 13410165861863974850u64, 40340355587u64}) + put(data, {11703600274199927522u64, 6278452420856351569u64, 212491800360u64}) + put(data, {806737539257940346u64, 9072115382556676441u64, 91149396692u64}) + put(data, {14579028397110132023u64, 2755882551854926562u64, 93460573019u64}) + put(data, {14247808875344366934u64, 8496072611504649268u64, 205223454557u64}) + put(data, {9713379923695279513u64, 4122009033579215814u64, 61554147533u64}) + put(data, {2246428675703313877u64, 10222217724450527220u64, 233111918909u64}) + put(data, {3549783776592680620u64, 2064539481554006324u64, 74430190057u64}) + put(data, {12645029747929213033u64, 7935605886598063692u64, 51423117898u64}) + put(data, {16279009267476580506u64, 7805147585347548428u64, 18309486643u64}) + put(data, {343358782242907186u64, 5709020905457661272u64, 60881313810u64}) + put(data, {10077054739085890321u64, 16257370307404906673u64, 207811593532u64}) + put(data, {10526715404712173586u64, 14971258192939373645u64, 41061441999u64}) + put(data, {11438715865125144243u64, 1133404845901376389u64, 82512872489u64}) + put(data, {5040916178827294801u64, 9460827548162822046u64, 204069058130u64}) + put(data, {16643761637275849508u64, 1273897659779791345u64, 202424641996u64}) + put(data, {4852542977279030386u64, 7833262224435092782u64, 70164442058u64}) + put(data, {7883373066544387129u64, 3033420566713364586u64, 110817242694u64}) + put(data, {16699064314768500978u64, 15075466825360349102u64, 1707011694u64}) + put(data, {6805863634444817214u64, 13042063791413317777u64, 1u64}) + put(data, {2266540253968903500u64, 31488807865u64, 0u64}) + put(data, {9016913589137908810u64, 31u64, 0u64}) + put(data, {1u64, 0u64, 222134665216u64}) + put(data, {11654451024602552034u64, 2484134775182816689u64, 93997495262u64}) + put(data, {5299013208454526793u64, 18400539815335991276u64, 221026318685u64}) + put(data, {14918550373926182540u64, 485494064952118285u64, 88952853725u64}) + put(data, {6225552657491071054u64, 17577048805241314890u64, 76155254872u64}) + put(data, {10344713496596235785u64, 2863946907557583806u64, 236707187532u64}) + put(data, {12972405634433280209u64, 13045307417786230799u64, 139652260844u64}) + put(data, {12911885282402784945u64, 12032088871615097765u64, 26479692427u64}) + put(data, {6934311832970995868u64, 8848763446997690579u64, 33543797274u64}) + put(data, {9975729197003430461u64, 10031289150307672683u64, 230628415265u64}) + put(data, {1982857556803548935u64, 11592215575498656562u64, 62861639142u64}) + put(data, {2095735223386298223u64, 15894436747956898387u64, 232113382974u64}) + put(data, {7110931538347639365u64, 2091546719588500922u64, 52317877736u64}) + put(data, {15822183724630969535u64, 5863809244813756108u64, 220612737332u64}) + put(data, {16931982690156327501u64, 11303008753675411244u64, 166717656540u64}) + put(data, {6740069226761666110u64, 13238426537506910531u64, 32935582886u64}) + put(data, {3138792961008474902u64, 17258458071023005564u64, 81454591520u64}) + put(data, {12154594426971851390u64, 8385733444777075178u64, 58516663377u64}) + put(data, {15780127219221910902u64, 9530757096163247299u64, 157781872442u64}) + put(data, {16421541930960194381u64, 14423000845391072216u64, 196593770909u64}) + put(data, {7485894627196740576u64, 10953140011159884310u64, 186662899652u64}) + put(data, {8897269432694476707u64, 12228340237948264126u64, 75611443130u64}) + put(data, {17189823634941678805u64, 11279134946966259188u64, 173793641291u64}) + put(data, {9585582064286255216u64, 14640097792684582650u64, 181337854381u64}) + put(data, {12835472279575022097u64, 6232313315128656727u64, 24874740917u64}) + put(data, {6776016669542754608u64, 16136121832933322087u64, 54817204760u64}) + put(data, {18340015775620871027u64, 15074767079673358270u64, 254718431542u64}) + put(data, {5254188752292365830u64, 13252722804829281907u64, 111870u64}) + put(data, {6798802596750151183u64, 2063650512248692u64, 0u64}) + put(data, {9449320530215272000u64, 2063650u64, 0u64}) + put(data, {1u64, 0u64, 121419595776u64}) + put(data, {17110720482574968811u64, 7740175894281560508u64, 91849499257u64}) + put(data, {16172441693558688213u64, 15670495392425593225u64, 188821405531u64}) + put(data, {6234654946353717320u64, 15152257626756992777u64, 238221723324u64}) + put(data, {11180283100679445438u64, 4090073428152440421u64, 190783353838u64}) + put(data, {14852260031176961272u64, 14450327772834205583u64, 10242107326u64}) + put(data, {4481533167346438750u64, 4466091895542494215u64, 250566718730u64}) + put(data, {4269718344362365664u64, 10454115378553795376u64, 205122938618u64}) + put(data, {11520029752381101466u64, 2267817233475657787u64, 54298180301u64}) + put(data, {16778682550309368417u64, 5500455702636497520u64, 223822842678u64}) + put(data, {9687587467301363608u64, 15178768299492252548u64, 148382851295u64}) + put(data, {10093971076828497318u64, 7062359872332045589u64, 6666640532u64}) + put(data, {1913763026490934696u64, 12297347290027942575u64, 96435313926u64}) + put(data, {12701450127613557000u64, 8030124596941085587u64, 220353810784u64}) + put(data, {8974572160711134644u64, 6526656990996654842u64, 184339045596u64}) + put(data, {9890000077336694124u64, 6254287345256979849u64, 130360063928u64}) + put(data, {4292326716201059148u64, 6642007136244870031u64, 96109916034u64}) + put(data, {14644519175104337420u64, 2027592955437164717u64, 68051104864u64}) + put(data, {5051178622270136798u64, 942718349157325566u64, 40792392772u64}) + put(data, {675983118348065839u64, 14617066671884002277u64, 1370343464u64}) + put(data, {4431647660065117244u64, 6831631114396133347u64, 179791632385u64}) + put(data, {8316115180008411962u64, 14603040013386939257u64, 135537011123u64}) + put(data, {9621158095544965602u64, 9906106765319401102u64, 44075687047u64}) + put(data, {15283478958951102072u64, 1396179595609933062u64, 125624765228u64}) + put(data, {13981553073094447813u64, 11524884268464976416u64, 239020758653u64}) + put(data, {4558368743929911607u64, 382929570730827273u64, 52331208687u64}) + put(data, {15217004469858477791u64, 6109721884461301380u64, 235129590068u64}) + put(data, {11589190369996515737u64, 2390514825000339690u64, 7331559403u64}) + put(data, {3670624237398152929u64, 6116191454763441754u64, 7u64}) + put(data, {13471713758418039777u64, 135243399970u64, 0u64}) + put(data, {4489936967610296411u64, 135u64, 0u64}) + put(data, {1u64, 0u64, 106628775936u64}) + put(data, {9052049303222747950u64, 11598868771099176309u64, 120783334250u64}) + put(data, {1011330006193020538u64, 14449966445520085104u64, 71632935288u64}) + put(data, {17412075644359478612u64, 11675595287405614725u64, 194859815495u64}) + put(data, {6358678384745980468u64, 15860796398550489896u64, 137877141698u64}) + put(data, {15262353928842850919u64, 16180408435245829661u64, 250745768073u64}) + put(data, {11145257686438581736u64, 13756992797154950705u64, 20478705146u64}) + put(data, {1600562031807691890u64, 8830551328786758465u64, 120905306388u64}) + put(data, {6775147337046626724u64, 16699955256560951263u64, 85544214392u64}) + put(data, {15772127322106297822u64, 10038983627153402073u64, 165817764949u64}) + put(data, {4141472200527441474u64, 15085100736692127345u64, 2542523045u64}) + put(data, {18246007807879281267u64, 10007783780289711124u64, 168953930242u64}) + put(data, {960746958654787123u64, 17596907048353602191u64, 112733498024u64}) + put(data, {11355981212264408477u64, 13530650344896573508u64, 147343568752u64}) + put(data, {1573078209576251481u64, 6337724853398437004u64, 203692202643u64}) + put(data, {6245294478780491367u64, 12768885008904063296u64, 45149607627u64}) + put(data, {7523292955659721510u64, 2759773619512884113u64, 35457227821u64}) + put(data, {14454736751015226505u64, 8434364600126655291u64, 21208374307u64}) + put(data, {7219786377781411316u64, 3843827521199949337u64, 218252709141u64}) + put(data, {10597123082209392431u64, 4661660852957808993u64, 206829308634u64}) + put(data, {6922353544343010714u64, 15298044134177324416u64, 168420007630u64}) + put(data, {14317523356293377430u64, 7747773274913338216u64, 121561008808u64}) + put(data, {4057766168681892717u64, 10348785912020632965u64, 96226347385u64}) + put(data, {15214083611901244045u64, 4175372293197190169u64, 240613987168u64}) + put(data, {8390569016883950721u64, 11326064156813083144u64, 80439123952u64}) + put(data, {10680472538208175055u64, 8100407170505981762u64, 202092512592u64}) + put(data, {12173567833130544927u64, 1706556116319916845u64, 44814718154u64}) + put(data, {1386341248286610026u64, 15028897280749641941u64, 225077043500u64}) + put(data, {12487300952797237352u64, 1421201742071739120u64, 480481u64}) + put(data, {2614759871804869720u64, 8863311460481781u64, 0u64}) + put(data, {8494389567327729477u64, 8863311u64, 0u64}) + put(data, {1u64, 0u64, 247459741696u64}) + put(data, {6260469580539185878u64, 8480737406125178271u64, 136593449207u64}) + put(data, {17818573101084525841u64, 10947205650755620360u64, 8047085704u64}) + put(data, {2201029069927307150u64, 868577942165647780u64, 28868321800u64}) + put(data, {10397997613804897039u64, 16017710019091388478u64, 140358376476u64}) + put(data, {14269915965770103741u64, 6610879150827623374u64, 234656489612u64}) + put(data, {16776139909196366727u64, 12110095866223762091u64, 140420497130u64}) + put(data, {6246513436385199720u64, 7756802952949470774u64, 194159475340u64}) + put(data, {2926026498821554288u64, 2941800790804618758u64, 81634453442u64}) + put(data, {15725499391028340982u64, 11703600274199927521u64, 89043733329u64}) + put(data, {8576577277771450827u64, 806737539257940345u64, 226790330713u64}) + put(data, {15523351176022259335u64, 14579028397110132022u64, 52772375266u64}) + put(data, {4775158829429176134u64, 14247808875344366933u64, 198526563380u64}) + put(data, {10141817222123532462u64, 9713379923695279512u64, 244121779142u64}) + put(data, {12847658900242624586u64, 2246428675703313876u64, 52192434164u64}) + put(data, {13708197964460514655u64, 3549783776592680619u64, 76685488436u64}) + put(data, {1951540006613246932u64, 12645029747929213032u64, 12882486860u64}) + put(data, {9979297327280092199u64, 16279009267476580505u64, 88018613516u64}) + put(data, {15381307706282553684u64, 343358782242907185u64, 177546278232u64}) + put(data, {10037428657543061177u64, 10077054739085890320u64, 77570654385u64}) + put(data, {2584877324547208668u64, 10526715404712173585u64, 133620094029u64}) + put(data, {1126624732730703576u64, 11438715865125144242u64, 158273268613u64}) + put(data, {1501064139624981020u64, 5040916178827294800u64, 241902260126u64}) + put(data, {5219661484955306109u64, 16643761637275849507u64, 46263056881u64}) + put(data, {5336997298570282212u64, 4852542977279030385u64, 106427358510u64}) + put(data, {12191131175733833362u64, 7883373066544387128u64, 174905258090u64}) + put(data, {3707068178994436536u64, 16699064314768500977u64, 145368946606u64}) + put(data, {5045484691732942022u64, 6805863634444817213u64, 185122869393u64}) + put(data, {14847900542908711232u64, 2266540253968903499u64, 31488807865u64}) + put(data, {9097257915916965135u64, 9016913589137908809u64, 31u64}) + put(data, {2472027983230314217u64, 580865979874u64, 0u64}) + put(data, {15974509111133272205u64, 580u64, 0u64}) + put(data, {1u64, 0u64, 177631789056u64}) + put(data, {12099486841948187399u64, 11654451024602552033u64, 236287260081u64}) + put(data, {5319910566029976328u64, 5299013208454526792u64, 13808736236u64}) + put(data, {11549214421017285864u64, 14918550373926182539u64, 74337487885u64}) + put(data, {1998791413186046700u64, 6225552657491071053u64, 190560788042u64}) + put(data, {17075171930090011210u64, 10344713496596235784u64, 15703235518u64}) + put(data, {15158296003813501474u64, 12972405634433280208u64, 165699954703u64}) + put(data, {1360083178079384115u64, 12911885282402784944u64, 211375909797u64}) + put(data, {6167980558592741158u64, 6934311832970995867u64, 107540785363u64}) + put(data, {3630180428124865653u64, 9975729197003430460u64, 50107490923u64}) + put(data, {2276550099763657677u64, 1982857556803548934u64, 83113610034u64}) + put(data, {407006713016100655u64, 2095735223386298222u64, 186385484371u64}) + put(data, {14242579061653496002u64, 7110931538347639364u64, 204857722298u64}) + put(data, {17944493332678643704u64, 15822183724630969534u64, 44917884620u64}) + put(data, {987185901870869452u64, 16931982690156327500u64, 67365379884u64}) + put(data, {5578665155415167745u64, 6740069226761666109u64, 124170154307u64}) + put(data, {4849210377429577536u64, 3138792961008474901u64, 234658901884u64}) + put(data, {10811995403388891862u64, 12154594426971851389u64, 195855442410u64}) + put(data, {7051931074990177294u64, 15780127219221910901u64, 216890213571u64}) + put(data, {2030832259446664275u64, 16421541930960194380u64, 22405811160u64}) + put(data, {6069512651054767896u64, 7485894627196740575u64, 190482321942u64}) + put(data, {10608701253763958799u64, 8897269432694476706u64, 244931862206u64}) + put(data, {15700053443426906717u64, 17189823634941678804u64, 250519635444u64}) + put(data, {17759719234725541222u64, 9585582064286255215u64, 87695812346u64}) + put(data, {15187321568916405210u64, 12835472279575022096u64, 103367328599u64}) + put(data, {11040156458113129594u64, 6776016669542754607u64, 190994214247u64}) + put(data, {2800727824598008497u64, 18340015775620871026u64, 115284830142u64}) + put(data, {2997236166375604479u64, 5254188752292365829u64, 116368563827u64}) + put(data, {6260091886451512841u64, 6798802596750151182u64, 34512248692u64}) + put(data, {17573059315228347474u64, 9449320530215271999u64, 2063650u64}) + put(data, {7519453664590169251u64, 38067632857031246u64, 0u64}) + put(data, {15809436065653866529u64, 38067632u64, 0u64}) + put(data, {1u64, 0u64, 188927574016u64}) + put(data, {228921437623588922u64, 17110720482574968810u64, 137876709820u64}) + put(data, {2195862230003073884u64, 16172441693558688212u64, 9337981321u64}) + put(data, {960207412233973688u64, 6234654946353717319u64, 101606084361u64}) + put(data, {2464387149230492479u64, 11180283100679445437u64, 143805142629u64}) + put(data, {3631866936444955213u64, 14852260031176961271u64, 7242944399u64}) + put(data, {1578304441149380227u64, 4481533167346438749u64, 48231461895u64}) + put(data, {18190538519673445181u64, 4269718344362365663u64, 59624502064u64}) + put(data, {1271000736479934749u64, 11520029752381101465u64, 112909574203u64}) + put(data, {18292963032817745634u64, 16778682550309368416u64, 132525165168u64}) + put(data, {17168014021925537455u64, 9687587467301363607u64, 21547195268u64}) + put(data, {18046757712870378949u64, 10093971076828497317u64, 175103745301u64}) + put(data, {14857998893911743220u64, 1913763026490934695u64, 147688546991u64}) + put(data, {11933607369968684575u64, 12701450127613556999u64, 250486512531u64}) + put(data, {3483798509902859162u64, 8974572160711134643u64, 137536137978u64}) + put(data, {7378828438829845831u64, 9890000077336694123u64, 143232687497u64}) + put(data, {15791137430347699565u64, 4292326716201059147u64, 173793880975u64}) + put(data, {17044141236829932641u64, 14644519175104337419u64, 254273824941u64}) + put(data, {9075651910862456484u64, 5051178622270136797u64, 229036645118u64}) + put(data, {17811207355884564095u64, 675983118348065838u64, 227240240101u64}) + put(data, {4438638126207305937u64, 4431647660065117243u64, 121450817507u64}) + put(data, {12507972635512950185u64, 8316115180008411961u64, 142521564025u64}) + put(data, {14658269128098109408u64, 9621158095544965601u64, 6828519054u64}) + put(data, {3642436268910286111u64, 15283478958951102071u64, 32757941510u64}) + put(data, {3783099432964819561u64, 13981553073094447812u64, 9247109664u64}) + put(data, {9497579866027539638u64, 4558368743929911606u64, 132824915465u64}) + put(data, {3395179445046271361u64, 15217004469858477790u64, 234628251268u64}) + put(data, {5938502732309497276u64, 11589190369996515736u64, 90198984938u64}) + put(data, {5793671185917606255u64, 3670624237398152928u64, 34730303066u64}) + put(data, {889272970253526588u64, 13471713758418039776u64, 135243399970u64}) + put(data, {8594177504370135501u64, 4489936967610296410u64, 135u64}) + put(data, {7374354721120724712u64, 2494800386918u64, 0u64}) + put(data, {14764532643665507567u64, 2494u64, 0u64}) + put(data, {1u64, 0u64, 117490712576u64}) + put(data, {5392404173658087695u64, 9052049303222747949u64, 112054824309u64}) + put(data, {4976586473237854316u64, 1011330006193020537u64, 133943910512u64}) + put(data, {6308932742419013569u64, 17412075644359478611u64, 40344704645u64}) + put(data, {4831846642430703059u64, 6358678384745980467u64, 29827373864u64}) + put(data, {18139507855949846901u64, 15262353928842850918u64, 49604185629u64}) + put(data, {4865833876326628410u64, 11145257686438581735u64, 65086766641u64}) + put(data, {14296661839130179261u64, 1600562031807691889u64, 223367281473u64}) + put(data, {9254773150378118248u64, 6775147337046626723u64, 217855008735u64}) + put(data, {12174712433727875143u64, 15772127322106297821u64, 113224509657u64}) + put(data, {705653145340915199u64, 4141472200527441473u64, 20989118065u64}) + put(data, {17763928858962481812u64, 18246007807879281266u64, 143052082196u64}) + put(data, {3982836567612046296u64, 960746958654787122u64, 68615608975u64}) + put(data, {12730849277561967739u64, 11355981212264408476u64, 140085276740u64}) + put(data, {17314488764367235908u64, 1573078209576251480u64, 64338558092u64}) + put(data, {15951418930590301119u64, 6245294478780491366u64, 145407838528u64}) + put(data, {7193356087283467261u64, 7523292955659721509u64, 59783592849u64}) + put(data, {17592945625696089446u64, 14454736751015226504u64, 25391385403u64}) + put(data, {3554461664875361428u64, 7219786377781411315u64, 97574471193u64}) + put(data, {2213779057785318208u64, 10597123082209392430u64, 128375261537u64}) + put(data, {3880940796082421148u64, 6922353544343010713u64, 104776154496u64}) + put(data, {4528237545358141043u64, 14317523356293377429u64, 133219971944u64}) + put(data, {11681196539088147363u64, 4057766168681892716u64, 25824757125u64}) + put(data, {9835005502912643017u64, 15214083611901244044u64, 8454853657u64}) + put(data, {4964088126040986696u64, 8390569016883950720u64, 66578989576u64}) + put(data, {3355564873147047622u64, 10680472538208175054u64, 45659930434u64}) + put(data, {1853093467828272927u64, 12173567833130544926u64, 213075153709u64}) + put(data, {14755341584803008677u64, 1386341248286610025u64, 240676937941u64}) + put(data, {4701571132542556621u64, 12487300952797237351u64, 245141746416u64}) + put(data, {6128849686644853851u64, 2614759871804869719u64, 79460481781u64}) + put(data, {12026867901170202094u64, 8494389567327729476u64, 8863311u64}) + put(data, {17909760324981426303u64, 163499238157084246u64, 0u64}) + put(data, {2897692901883393664u64, 163499238u64, 0u64}) + put(data, {1u64, 0u64, 159339380736u64}) + put(data, {12323704802554838154u64, 6260469580539185877u64, 8965946783u64}) + put(data, {7135886931147821732u64, 17818573101084525840u64, 164119318024u64}) + put(data, {15341283120292884947u64, 2201029069927307149u64, 62563676580u64}) + put(data, {3092789040392634166u64, 10397997613804897038u64, 206773573694u64}) + put(data, {8811761390822097865u64, 14269915965770103740u64, 171909436366u64}) + put(data, {16870860798610218169u64, 16776139909196366726u64, 54338624171u64}) + put(data, {17452041453591904833u64, 6246513436385199719u64, 6158620214u64}) + put(data, {10314783684009874908u64, 2926026498821554287u64, 225852481030u64}) + put(data, {4932636630789274903u64, 15725499391028340981u64, 121464937185u64}) + put(data, {18143884346082124480u64, 8576577277771450826u64, 54841522553u64}) + put(data, {2823209155405527322u64, 15523351176022259334u64, 85258861878u64}) + put(data, {16195396106620226251u64, 4775158829429176133u64, 152549789013u64}) + put(data, {1150544491807648944u64, 10141817222123532461u64, 212696472984u64}) + put(data, {7767455475523884824u64, 12847658900242624585u64, 171743122900u64}) + put(data, {15204378045683991808u64, 13708197964460514654u64, 104105793195u64}) + put(data, {17239732561718805622u64, 1951540006613246931u64, 153540978792u64}) + put(data, {12886430624522800062u64, 9979297327280092198u64, 49833822361u64}) + put(data, {18162250541178258136u64, 15381307706282553683u64, 16544130097u64}) + put(data, {17028935366700158084u64, 10037428657543061176u64, 17140126480u64}) + put(data, {16075467823964198637u64, 2584877324547208667u64, 178061074449u64}) + put(data, {9803858825574498304u64, 1126624732730703575u64, 80081372850u64}) + put(data, {17464070808143041817u64, 1501064139624981019u64, 35282958416u64}) + put(data, {17682703471239266776u64, 5219661484955306108u64, 113289319203u64}) + put(data, {18147688354161351336u64, 5336997298570282211u64, 56660882545u64}) + put(data, {6663423873348080051u64, 12191131175733833361u64, 241200960568u64}) + put(data, {9417270363716235133u64, 3707068178994436535u64, 61273516273u64}) + put(data, {9295013721571344179u64, 5045484691732942021u64, 75804906301u64}) + put(data, {6199479138350037783u64, 14847900542908711231u64, 73493163339u64}) + put(data, {887603005365085688u64, 9097257915916965134u64, 226134008905u64}) + put(data, {333989628642975696u64, 2472027983230314216u64, 68865979874u64}) + put(data, {4620735991403939439u64, 15974509111133272204u64, 580u64}) + put(data, {12418523063962801201u64, 10715086071862u64, 0u64}) + put(data, {1587745622680169419u64, 10715u64, 0u64}) + put(data, {1u64, 0u64, 225655914496u64}) + put(data, {10968905082284365638u64, 12099486841948187398u64, 72288392929u64}) + put(data, {14076907092801977812u64, 5319910566029976327u64, 139626084168u64}) + put(data, {3438322122816124202u64, 11549214421017285863u64, 77108354699u64}) + put(data, {14645413324829073676u64, 1998791413186046699u64, 8925646925u64}) + put(data, {12271281439492289999u64, 17075171930090011209u64, 208821732872u64}) + put(data, {6233751789862708246u64, 15158296003813501473u64, 176073730256u64}) + put(data, {1962644459455827991u64, 1360083178079384114u64, 155334366896u64}) + put(data, {8726934184642952500u64, 6167980558592741157u64, 60196792475u64}) + put(data, {4531087719737475147u64, 3630180428124865652u64, 6123412028u64}) + put(data, {481513520412720775u64, 2276550099763657676u64, 110022063878u64}) + put(data, {992149349835802669u64, 407006713016100654u64, 68772091758u64}) + put(data, {11165474436676191361u64, 14242579061653496001u64, 190972772932u64}) + put(data, {10240785855143707184u64, 17944493332678643703u64, 76053515454u64}) + put(data, {10059329918238932466u64, 987185901870869451u64, 61302420044u64}) + put(data, {14791716450947031886u64, 5578665155415167744u64, 21262876221u64}) + put(data, {15378882314737417403u64, 4849210377429577535u64, 125586119445u64}) + put(data, {14726970229242271128u64, 10811995403388891861u64, 117382285949u64}) + put(data, {5090110549507128156u64, 7051931074990177293u64, 76110091637u64}) + put(data, {17185220781106503841u64, 2030832259446664274u64, 223329028940u64}) + put(data, {9858517691519529306u64, 6069512651054767895u64, 162575098847u64}) + put(data, {5595905546638020703u64, 10608701253763958798u64, 212851101602u64}) + put(data, {15555173226968030256u64, 15700053443426906716u64, 111962756308u64}) + put(data, {10745236628845355771u64, 17759719234725541221u64, 16823306351u64}) + put(data, {9973314042399760760u64, 15187321568916405209u64, 47598488080u64}) + put(data, {4374506813558796576u64, 11040156458113129593u64, 114151827759u64}) + put(data, {15960826480426749933u64, 2800727824598008496u64, 5162480498u64}) + put(data, {9636454862798615738u64, 2997236166375604478u64, 14339360261u64}) + put(data, {17973331528911319269u64, 6260091886451512840u64, 63952637454u64}) + put(data, {7366495200039369602u64, 17573059315228347473u64, 78407630399u64}) + put(data, {10505831326526933399u64, 7519453664590169250u64, 176857031246u64}) + put(data, {2803218632575724145u64, 15809436065653866528u64, 38067632u64}) + put(data, {8425731874431741636u64, 702223880805592151u64, 0u64}) + put(data, {14860552245711912111u64, 702223880u64, 0u64}) + put(data, {1u64, 0u64, 234012409856u64}) + put(data, {6993664200669526994u64, 228921437623588921u64, 212119037930u64}) + put(data, {4065363582031999356u64, 2195862230003073883u64, 71052052948u64}) + put(data, {6899780515342669867u64, 960207412233973687u64, 189133594695u64}) + put(data, {17713500890201844939u64, 2464387149230492478u64, 247196883901u64}) + put(data, {6445781125105107086u64, 3631866936444955212u64, 93085560055u64}) + put(data, {13563044070717478571u64, 1578304441149380226u64, 223986111069u64}) + put(data, {13167612994149348885u64, 18190538519673445180u64, 153068901087u64}) + put(data, {5505463469596727288u64, 1271000736479934748u64, 96991663513u64}) + put(data, {12125446212518819372u64, 18292963032817745633u64, 151930679904u64}) + put(data, {12537707724735421794u64, 17168014021925537454u64, 165978316695u64}) + put(data, {15173675086703777069u64, 18046757712870378948u64, 167805453733u64}) + put(data, {13535510174093048476u64, 14857998893911743219u64, 7646922151u64}) + put(data, {10698912997087096629u64, 11933607369968684574u64, 179188857095u64}) + put(data, {16952559548431933861u64, 3483798509902859161u64, 107400007091u64}) + put(data, {13528255827744249993u64, 7378828438829845830u64, 75856039275u64}) + put(data, {14122167436324771955u64, 15791137430347699564u64, 11923964747u64}) + put(data, {13071007137740038297u64, 17044141236829932640u64, 221491992075u64}) + put(data, {13011887609328904025u64, 9075651910862456483u64, 46965547485u64}) + put(data, {3116434332871336590u64, 17811207355884564094u64, 59240619054u64}) + put(data, {9050993820536772770u64, 4438638126207305936u64, 57678058555u64}) + put(data, {11993719123438634238u64, 12507972635512950184u64, 225794626361u64}) + put(data, {1414857165879849301u64, 14658269128098109407u64, 119197456865u64}) + put(data, {13819438220812375094u64, 3642436268910286110u64, 196205082231u64}) + put(data, {6073063033888264440u64, 3783099432964819560u64, 54514864836u64}) + put(data, {6828883869150720294u64, 9497579866027539637u64, 222184053046u64}) + put(data, {4548265621068768345u64, 3395179445046271360u64, 152321926878u64}) + put(data, {10422524923581371874u64, 5938502732309497275u64, 224314075544u64}) + put(data, {1858996082510682634u64, 5793671185917606254u64, 224048207584u64}) + put(data, {890276727450878316u64, 889272970253526587u64, 90465891296u64}) + put(data, {3886008133802710905u64, 8594177504370135500u64, 102399764570u64}) + put(data, {612074409233016757u64, 7374354721120724711u64, 190800386918u64}) + put(data, {3927020336901729264u64, 14764532643665507566u64, 2494u64}) + put(data, {5298603480094474942u64, 46020944252475u64, 0u64}) + put(data, {17418383752590430025u64, 46020u64, 0u64}) + put(data, {1u64, 0u64, 45292322816u64}) + put(data, {8973799690601597929u64, 5392404173658087694u64, 121269781293u64}) + put(data, {1343055462055792431u64, 4976586473237854315u64, 83342007929u64}) + put(data, {17425118728683169659u64, 6308932742419013568u64, 51261934931u64}) + put(data, {18389781726026675967u64, 4831846642430703058u64, 102983344691u64}) + put(data, {272526939565961561u64, 18139507855949846900u64, 231263777382u64}) + put(data, {11293026845930963228u64, 4865833876326628409u64, 113775023591u64}) + put(data, {13997416438903902597u64, 14296661839130179260u64, 163501702257u64}) + put(data, {6186605805999441184u64, 9254773150378118247u64, 221659992483u64}) + put(data, {4401776373281836138u64, 12174712433727875142u64, 65038253533u64}) + put(data, {16338917089754547008u64, 705653145340915198u64, 114962984513u64}) + put(data, {13337700757935003056u64, 17763928858962481811u64, 50215910002u64}) + put(data, {14612496890816348693u64, 3982836567612046295u64, 156690140722u64}) + put(data, {3219935399907691719u64, 12730849277561967738u64, 88938620316u64}) + put(data, {10887238730052330387u64, 17314488764367235907u64, 102864728152u64}) + put(data, {360256418697768294u64, 15951418930590301118u64, 37389952614u64}) + put(data, {321440824631118565u64, 7193356087283467260u64, 136953715493u64}) + put(data, {10069228080701402580u64, 17592945625696089445u64, 243192687752u64}) + put(data, {9428069607611622975u64, 3554461664875361427u64, 46120009203u64}) + put(data, {14736799017468812344u64, 2213779057785318207u64, 153210386222u64}) + put(data, {10875332567307979280u64, 3880940796082421147u64, 149245476249u64}) + put(data, {4611492910339012807u64, 4528237545358141042u64, 108633238933u64}) + put(data, {10743508637597314786u64, 11681196539088147362u64, 140533156716u64}) + put(data, {9356196315668016028u64, 9835005502912643016u64, 128269103756u64}) + put(data, {15755598617722189347u64, 4964088126040986695u64, 206181905536u64}) + put(data, {1275276394173375542u64, 3355564873147047621u64, 30100456398u64}) + put(data, {12644999363867216251u64, 1853093467828272926u64, 105799888670u64}) + put(data, {4553830511509832021u64, 14755341584803008676u64, 103254872681u64}) + put(data, {8869400642218174412u64, 4701571132542556620u64, 87332245607u64}) + put(data, {16570849151159054040u64, 6128849686644853850u64, 68651977815u64}) + put(data, {16127119334101797673u64, 12026867901170202093u64, 86970890052u64}) + put(data, {9686867250420930550u64, 17909760324981426302u64, 230157084246u64}) + put(data, {10678226869774428035u64, 2897692901883393663u64, 163499238u64}) + put(data, {7767227962910162068u64, 3016028602530220424u64, 0u64}) + put(data, {9780840471948993674u64, 3016028602u64, 0u64}) + put(data, {1u64, 0u64, 213668069376u64}) + put(data, {6288709332106746357u64, 12323704802554838153u64, 16386837205u64}) + put(data, {9066785620141948673u64, 7135886931147821731u64, 141831652624u64}) + put(data, {8442375916704414909u64, 15341283120292884946u64, 14167660429u64}) + put(data, {11604629218100425803u64, 3092789040392634165u64, 188477686542u64}) + put(data, {3877248044010875762u64, 8811761390822097864u64, 134914571196u64}) + put(data, {16435137704395217283u64, 16870860798610218168u64, 103946077062u64}) + put(data, {14994442577577813271u64, 17452041453591904832u64, 111559165543u64}) + put(data, {4410105917142436089u64, 10314783684009874907u64, 245267398767u64}) + put(data, {4632574728444936970u64, 4932636630789274902u64, 202983581941u64}) + put(data, {9117147535650050359u64, 18143884346082124479u64, 134153046474u64}) + put(data, {588939301256904809u64, 2823209155405527321u64, 69877954182u64}) + put(data, {324393982565305683u64, 16195396106620226250u64, 173062371141u64}) + put(data, {9380909186923521175u64, 1150544491807648943u64, 73421074605u64}) + put(data, {4463385697777230217u64, 7767455475523884823u64, 94824230985u64}) + put(data, {16378985502426333808u64, 15204378045683991807u64, 211934567774u64}) + put(data, {18210894922387834354u64, 17239732561718805621u64, 38698574803u64}) + put(data, {1555748035329493205u64, 12886430624522800061u64, 83984577574u64}) + put(data, {4277055533891898507u64, 18162250541178258135u64, 184923140435u64}) + put(data, {11574429772510874408u64, 17028935366700158083u64, 219871452856u64}) + put(data, {17391099253493808815u64, 16075467823964198636u64, 215531468251u64}) + put(data, {5791212393959129882u64, 9803858825574498303u64, 27946729175u64}) + put(data, {11254268231455680880u64, 17464070808143041816u64, 124958581275u64}) + put(data, {16355477587312235322u64, 17682703471239266775u64, 227983788156u64}) + put(data, {2411485149249320633u64, 18147688354161351335u64, 145361224931u64}) + put(data, {12763114642070638360u64, 6663423873348080050u64, 183510511249u64}) + put(data, {1147543073987366419u64, 9417270363716235132u64, 197503883703u64}) + put(data, {8410777835225272692u64, 9295013721571344178u64, 63336074437u64}) + put(data, {8134725822306818018u64, 6199479138350037782u64, 14048117055u64}) + put(data, {8899607004752328377u64, 887603005365085687u64, 232018105614u64}) + put(data, {690976506652396830u64, 333989628642975695u64, 140250490600u64}) + put(data, {12281570945595192074u64, 4620735991403939438u64, 54673209484u64}) + put(data, {12592957291365552899u64, 12418523063962801200u64, 219086071862u64}) + put(data, {13595807339013970272u64, 1587745622680169418u64, 10715u64}) + put(data, {9698096389749839992u64, 197658450495420u64, 0u64}) + put(data, {8310173728816391804u64, 197658u64, 0u64}) + data + end + + private POW10_OFFSET_2 = [ + 0, 2, 6, 12, 20, 29, 40, 52, 66, 80, + 95, 112, 130, 150, 170, 192, 215, 240, 265, 292, + 320, 350, 381, 413, 446, 480, 516, 552, 590, 629, + 670, 712, 755, 799, 845, 892, 940, 989, 1040, 1092, + 1145, 1199, 1254, 1311, 1369, 1428, 1488, 1550, 1613, 1678, + 1743, 1810, 1878, 1947, 2017, 2088, 2161, 2235, 2311, 2387, + 2465, 2544, 2625, 2706, 2789, 2873, 2959, 3046, 3133, + ] of UInt16 + + private MIN_BLOCK_2 = [ + 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, + 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 11, 11, 12, 12, 13, 13, + 14, 14, 15, 15, 16, 16, 17, 17, 18, 19, + 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, + 24, 25, 26, 26, 27, 27, 28, 28, 29, 29, + 30, 30, 31, 31, 32, 32, 33, 34, 0, + ] of UInt8 + + private POW10_SPLIT_2 = begin + data = Array({UInt64, UInt64, UInt64}).new(3133) + put(data, {0u64, 0u64, 3906250u64}) + put(data, {0u64, 0u64, 202000000000u64}) + put(data, {0u64, 11153727427136454656u64, 59u64}) + put(data, {0u64, 7205759403792793600u64, 59604644775u64}) + put(data, {0u64, 0u64, 167390625000u64}) + put(data, {0u64, 0u64, 232000000000u64}) + put(data, {0u64, 16777216000000000u64, 0u64}) + put(data, {0u64, 12945425605062557696u64, 909494u64}) + put(data, {0u64, 4388757836872548352u64, 182701772928u64}) + put(data, {0u64, 1152921504606846976u64, 128237915039u64}) + put(data, {0u64, 0u64, 159062500000u64}) + put(data, {0u64, 0u64, 160000000000u64}) + put(data, {0u64, 256000000000u64, 0u64}) + put(data, {0u64, 16192327041775828992u64, 13u64}) + put(data, {0u64, 15024075324038053888u64, 13877787807u64}) + put(data, {0u64, 5449091666327633920u64, 159814456755u64}) + put(data, {0u64, 2494994193563254784u64, 179295395851u64}) + put(data, {0u64, 4611686018427387904u64, 11135253906u64}) + put(data, {0u64, 0u64, 146250000000u64}) + put(data, {0u64, 0u64, 128000000000u64}) + put(data, {0u64, 3906250u64, 0u64}) + put(data, {0u64, 3906250000000000u64, 0u64}) + put(data, {0u64, 4368439412768899072u64, 211758u64}) + put(data, {0u64, 1563676642168012800u64, 46236813575u64}) + put(data, {0u64, 11532349341402398720u64, 7084767080u64}) + put(data, {0u64, 9048364970084925440u64, 104625169910u64}) + put(data, {0u64, 16609275425742389248u64, 246490512847u64}) + put(data, {0u64, 0u64, 207900390625u64}) + put(data, {0u64, 0u64, 225000000000u64}) + put(data, {11153727427136454656u64, 59u64, 0u64}) + put(data, {7205759403792793600u64, 59604644775u64, 0u64}) + put(data, {0u64, 4264412554261970152u64, 3u64}) + put(data, {0u64, 14485570586272534528u64, 3231174267u64}) + put(data, {0u64, 17827675094632103936u64, 123785264354u64}) + put(data, {0u64, 7347197909193981952u64, 226966440203u64}) + put(data, {0u64, 13677404030777688064u64, 11398292396u64}) + put(data, {0u64, 3810326759732150272u64, 172741453558u64}) + put(data, {0u64, 9943947977234055168u64, 246206558227u64}) + put(data, {0u64, 0u64, 19539062500u64}) + put(data, {0u64, 0u64, 228000000000u64}) + put(data, {12945425605062557696u64, 909494u64, 0u64}) + put(data, {4388757836872548352u64, 909494701772928u64, 0u64}) + put(data, {1152921504606846976u64, 14878706826214591391u64, 49303u64}) + put(data, {0u64, 4387341015746028192u64, 151806576313u64}) + put(data, {0u64, 651726680428265472u64, 185237838233u64}) + put(data, {0u64, 2570638187944738816u64, 153035330174u64}) + put(data, {0u64, 7419175577111756800u64, 126139354575u64}) + put(data, {0u64, 17299322326264840192u64, 207402194313u64}) + put(data, {0u64, 7990511638862102528u64, 137937798142u64}) + put(data, {0u64, 16717361816799281152u64, 254433166503u64}) + put(data, {0u64, 0u64, 167906250000u64}) + put(data, {0u64, 0u64, 16000000000u64}) + put(data, {16192327041775828992u64, 13u64, 0u64}) + put(data, {15024075324038053888u64, 13877787807u64, 0u64}) + put(data, {5449091666327633920u64, 13877787807814456755u64, 0u64}) + put(data, {2494994193563254784u64, 9707857417284919307u64, 752316384u64}) + put(data, {4611686018427387904u64, 1844515466944871826u64, 224526264005u64}) + put(data, {0u64, 15167599819856275072u64, 197099991383u64}) + put(data, {0u64, 14830185305589481472u64, 87822237233u64}) + put(data, {0u64, 6163721531743535104u64, 49803945956u64}) + put(data, {0u64, 14122847407012052992u64, 228334136013u64}) + put(data, {0u64, 335491783960035328u64, 205765601092u64}) + put(data, {0u64, 941252322120433664u64, 68018187046u64}) + put(data, {0u64, 11529215046068469760u64, 38051025390u64}) + put(data, {0u64, 0u64, 238625000000u64}) + put(data, {0u64, 0u64, 64000000000u64}) + put(data, {4368439412768899072u64, 211758u64, 0u64}) + put(data, {1563676642168012800u64, 211758236813575u64, 0u64}) + put(data, {11532349341402398720u64, 8061591463141767016u64, 11479u64}) + put(data, {9048364970084925440u64, 16628725344207857142u64, 215437019748u64}) + put(data, {16609275425742389248u64, 3555541870038531535u64, 100901445007u64}) + put(data, {0u64, 18316647450161853665u64, 143192746310u64}) + put(data, {0u64, 16709574568378075648u64, 70992947447u64}) + put(data, {0u64, 7696022835795591168u64, 247905827852u64}) + put(data, {0u64, 16664449640376041472u64, 12417202233u64}) + put(data, {0u64, 3109186955116544000u64, 57903381625u64}) + put(data, {0u64, 10515518101817131008u64, 121168549362u64}) + put(data, {0u64, 9961962375743537152u64, 242570047378u64}) + put(data, {0u64, 9223372036854775808u64, 146540039062u64}) + put(data, {0u64, 0u64, 150500000000u64}) + put(data, {14485570586272534528u64, 3231174267u64, 0u64}) + put(data, {17827675094632103936u64, 3231174267785264354u64, 0u64}) + put(data, {7347197909193981952u64, 748977172262750475u64, 175162308u64}) + put(data, {13677404030777688064u64, 15965033457315095468u64, 196040602133u64}) + put(data, {3810326759732150272u64, 16809402149066729206u64, 21865466197u64}) + put(data, {9943947977234055168u64, 7563769067065700371u64, 85911239516u64}) + put(data, {0u64, 13550322810840051428u64, 92410032742u64}) + put(data, {0u64, 8663209637545764864u64, 102734564471u64}) + put(data, {0u64, 8969247575312957440u64, 119469633535u64}) + put(data, {0u64, 6193172891660451840u64, 255486223885u64}) + put(data, {0u64, 3427954273864908800u64, 13335732575u64}) + put(data, {0u64, 10058367555266936832u64, 95185829773u64}) + put(data, {0u64, 13907115649320091648u64, 141545265197u64}) + put(data, {0u64, 0u64, 45753906250u64}) + put(data, {0u64, 0u64, 74000000000u64}) + put(data, {14878706826214591391u64, 49303u64, 0u64}) + put(data, {4387341015746028192u64, 49303806576313u64, 0u64}) + put(data, {651726680428265472u64, 14106411361315920281u64, 2672u64}) + put(data, {2570638187944738816u64, 3609034283485221502u64, 112764710092u64}) + put(data, {7419175577111756800u64, 9896072247338192335u64, 204195646140u64}) + put(data, {17299322326264840192u64, 8889095178479228297u64, 188536467151u64}) + put(data, {7990511638862102528u64, 3631796911038383102u64, 207481878815u64}) + put(data, {16717361816799281152u64, 898318840772166823u64, 31196880105u64}) + put(data, {0u64, 17293677953982795024u64, 233048697961u64}) + put(data, {0u64, 7353628266884669440u64, 105937492160u64}) + put(data, {0u64, 2404693032470315008u64, 192398640987u64}) + put(data, {0u64, 9191155893041889280u64, 91130358670u64}) + put(data, {0u64, 6353946855033798656u64, 142498253559u64}) + put(data, {0u64, 3767824038248841216u64, 247344448149u64}) + put(data, {0u64, 7205759403792793600u64, 149204254150u64}) + put(data, {0u64, 0u64, 198390625000u64}) + put(data, {0u64, 0u64, 232000000000u64}) + put(data, {9707857417284919307u64, 752316384u64, 0u64}) + put(data, {1844515466944871826u64, 752316384526264005u64, 0u64}) + put(data, {15167599819856275072u64, 17063068157692817751u64, 40783152u64}) + put(data, {14830185305589481472u64, 5385330256507239985u64, 48924990778u64}) + put(data, {6163721531743535104u64, 3373050282752075748u64, 58291939338u64}) + put(data, {14122847407012052992u64, 4116064001262906061u64, 10182853422u64}) + put(data, {335491783960035328u64, 11306582046748043076u64, 46223132276u64}) + put(data, {941252322120433664u64, 17035410946089626406u64, 116612931040u64}) + put(data, {11529215046068469760u64, 15618595715183448558u64, 224923491477u64}) + put(data, {0u64, 5141740092277295680u64, 149846685770u64}) + put(data, {0u64, 16973644291514990592u64, 74278734288u64}) + put(data, {0u64, 14625255268443750400u64, 208920143100u64}) + put(data, {0u64, 14021170507320131584u64, 252792836676u64}) + put(data, {0u64, 4451355232865091584u64, 68760089176u64}) + put(data, {0u64, 12891553933348044800u64, 88241308450u64}) + put(data, {0u64, 1152921504606846976u64, 34698852539u64}) + put(data, {0u64, 0u64, 187062500000u64}) + put(data, {0u64, 0u64, 160000000000u64}) + put(data, {8061591463141767016u64, 11479u64, 0u64}) + put(data, {16628725344207857142u64, 11479437019748u64, 0u64}) + put(data, {3555541870038531535u64, 5562205901560339855u64, 622u64}) + put(data, {18316647450161853665u64, 2106077949367544134u64, 110301527786u64}) + put(data, {16709574568378075648u64, 7496855998374373623u64, 234114170714u64}) + put(data, {7696022835795591168u64, 229183437194837004u64, 90406405378u64}) + put(data, {16664449640376041472u64, 465169186276472889u64, 2012424059u64}) + put(data, {3109186955116544000u64, 2152980561625316473u64, 123025216872u64}) + put(data, {10515518101817131008u64, 2059790725449340402u64, 104116713310u64}) + put(data, {9961962375743537152u64, 17891190926410198930u64, 94111661478u64}) + put(data, {9223372036854775808u64, 9930696175609809814u64, 166969883403u64}) + put(data, {0u64, 7276914261609005312u64, 11538344118u64}) + put(data, {0u64, 10539762974036983808u64, 182394482312u64}) + put(data, {0u64, 12851089458992250880u64, 136571361695u64}) + put(data, {0u64, 9449311677678878720u64, 159696658955u64}) + put(data, {0u64, 8699564697382289408u64, 11512248212u64}) + put(data, {0u64, 4224376450473525248u64, 148471604347u64}) + put(data, {0u64, 4611686018427387904u64, 123229003906u64}) + put(data, {0u64, 0u64, 130250000000u64}) + put(data, {0u64, 0u64, 128000000000u64}) + put(data, {748977172262750475u64, 175162308u64, 0u64}) + put(data, {15965033457315095468u64, 175162308040602133u64, 0u64}) + put(data, {16809402149066729206u64, 13756840147955779925u64, 9495567u64}) + put(data, {7563769067065700371u64, 13788447602092505948u64, 15745759798u64}) + put(data, {13550322810840051428u64, 4972540435632173670u64, 54747473242u64}) + put(data, {8663209637545764864u64, 2844874687533091959u64, 90269561957u64}) + put(data, {8969247575312957440u64, 15377573779532804095u64, 101154220965u64}) + put(data, {6193172891660451840u64, 17824715805091194381u64, 165833619944u64}) + put(data, {3427954273864908800u64, 18277569135638159711u64, 232966279779u64}) + put(data, {10058367555266936832u64, 4254645803379752845u64, 99990829008u64}) + put(data, {13907115649320091648u64, 2933643244178200621u64, 208230644811u64}) + put(data, {0u64, 17188148801879487562u64, 75159033118u64}) + put(data, {0u64, 11069762501163246592u64, 30931771413u64}) + put(data, {0u64, 11676570643941818368u64, 21600093027u64}) + put(data, {0u64, 17840016768744030208u64, 99632988162u64}) + put(data, {0u64, 16463817321652158464u64, 2967109246u64}) + put(data, {0u64, 6954191143357644800u64, 126892505325u64}) + put(data, {0u64, 5080060379673919488u64, 237376987457u64}) + put(data, {0u64, 0u64, 65275390625u64}) + put(data, {0u64, 0u64, 161000000000u64}) + put(data, {14106411361315920281u64, 2672u64, 0u64}) + put(data, {3609034283485221502u64, 2672764710092u64, 0u64}) + put(data, {9896072247338192335u64, 16433563478020213436u64, 144u64}) + put(data, {8889095178479228297u64, 4194750497955655375u64, 144890865261u64}) + put(data, {3631796911038383102u64, 2691539602252904735u64, 109227397880u64}) + put(data, {898318840772166823u64, 3775467271962795241u64, 248145908654u64}) + put(data, {17293677953982795024u64, 16980212613224918121u64, 174204668490u64}) + put(data, {7353628266884669440u64, 4172857038337333440u64, 74920499170u64}) + put(data, {2404693032470315008u64, 5936867627376461659u64, 226226211033u64}) + put(data, {9191155893041889280u64, 17856837443266866062u64, 217321838238u64}) + put(data, {6353946855033798656u64, 8956297047799810807u64, 158968021097u64}) + put(data, {3767824038248841216u64, 15356974049716912789u64, 105485521835u64}) + put(data, {7205759403792793600u64, 6923608913322982854u64, 171832503231u64}) + put(data, {0u64, 4855902993563955944u64, 191375329591u64}) + put(data, {0u64, 13835893222288330752u64, 55263239028u64}) + put(data, {0u64, 9114973913760137216u64, 116750045274u64}) + put(data, {0u64, 17937099003422310400u64, 90494123725u64}) + put(data, {0u64, 7007960010734960640u64, 205972372085u64}) + put(data, {0u64, 7683422439270776832u64, 117379902273u64}) + put(data, {0u64, 720575940379279360u64, 65416519165u64}) + put(data, {0u64, 0u64, 253039062500u64}) + put(data, {0u64, 0u64, 228000000000u64}) + put(data, {17063068157692817751u64, 40783152u64, 0u64}) + put(data, {5385330256507239985u64, 40783152924990778u64, 0u64}) + put(data, {3373050282752075748u64, 2768933352715741194u64, 2210859u64}) + put(data, {4116064001262906061u64, 15201941611824153390u64, 43150104177u64}) + put(data, {11306582046748043076u64, 1418128541727000180u64, 113824098906u64}) + put(data, {17035410946089626406u64, 5353350204565757408u64, 90076876902u64}) + put(data, {15618595715183448558u64, 1721001680354286741u64, 102290205696u64}) + put(data, {5141740092277295680u64, 637631411660453962u64, 93295688u64}) + put(data, {16973644291514990592u64, 1630012588870568400u64, 72034566068u64}) + put(data, {14625255268443750400u64, 9253063571656828156u64, 180088363159u64}) + put(data, {14021170507320131584u64, 6029146854993203780u64, 151501609581u64}) + put(data, {4451355232865091584u64, 16987401965352759896u64, 109326840705u64}) + put(data, {12891553933348044800u64, 14499131620542087970u64, 129920888905u64}) + put(data, {1152921504606846976u64, 1978417255298660539u64, 73785999500u64}) + put(data, {0u64, 5790079354402454176u64, 140107250214u64}) + put(data, {0u64, 13748918935842078720u64, 38313880830u64}) + put(data, {0u64, 18047438014740692992u64, 254745330388u64}) + put(data, {0u64, 3116889656839372800u64, 212978353575u64}) + put(data, {0u64, 15995952446606147584u64, 167168966926u64}) + put(data, {0u64, 12530140063251562496u64, 14867142319u64}) + put(data, {0u64, 16717361816799281152u64, 175679260253u64}) + put(data, {0u64, 0u64, 93906250000u64}) + put(data, {0u64, 0u64, 16000000000u64}) + put(data, {5562205901560339855u64, 622u64, 0u64}) + put(data, {2106077949367544134u64, 622301527786u64, 0u64}) + put(data, {7496855998374373623u64, 13558973353698967386u64, 33u64}) + put(data, {229183437194837004u64, 6228991722850501890u64, 33735033418u64}) + put(data, {465169186276472889u64, 16886831391703377787u64, 74337674317u64}) + put(data, {2152980561625316473u64, 1181713637872883048u64, 77915436964u64}) + put(data, {2059790725449340402u64, 12393932434925221726u64, 164064060824u64}) + put(data, {17891190926410198930u64, 10684799845419711910u64, 152671876423u64}) + put(data, {9930696175609809814u64, 4590318792215640843u64, 71579224160u64}) + put(data, {7276914261609005312u64, 6383712187366189238u64, 96248841680u64}) + put(data, {10539762974036983808u64, 1904270214927675016u64, 208346061731u64}) + put(data, {12851089458992250880u64, 3711506775113308575u64, 163103230695u64}) + put(data, {9449311677678878720u64, 8091219444738793995u64, 231201201185u64}) + put(data, {8699564697382289408u64, 39436684991068052u64, 33438625885u64}) + put(data, {4224376450473525248u64, 18025182908196512891u64, 93002137866u64}) + put(data, {4611686018427387904u64, 7853924592034603138u64, 10977147123u64}) + put(data, {0u64, 4815749283615688320u64, 243425762105u64}) + put(data, {0u64, 14242399906544287744u64, 57261062291u64}) + put(data, {0u64, 76242322576113664u64, 147772082046u64}) + put(data, {0u64, 10858088421377703936u64, 126004133104u64}) + put(data, {0u64, 14293835879041466368u64, 240588618152u64}) + put(data, {0u64, 12182236992037191680u64, 168774870395u64}) + put(data, {0u64, 11529215046068469760u64, 123660400390u64}) + put(data, {0u64, 0u64, 6625000000u64}) + put(data, {0u64, 0u64, 64000000000u64}) + put(data, {13756840147955779925u64, 9495567u64, 0u64}) + put(data, {13788447602092505948u64, 9495567745759798u64, 0u64}) + put(data, {4972540435632173670u64, 14000097438505379162u64, 514755u64}) + put(data, {2844874687533091959u64, 16451062686452429925u64, 195758946802u64}) + put(data, {15377573779532804095u64, 4009347599785716645u64, 242891813895u64}) + put(data, {17824715805091194381u64, 16544162347546196456u64, 7217347168u64}) + put(data, {18277569135638159711u64, 17674258299745817187u64, 96896860837u64}) + put(data, {4254645803379752845u64, 5215238411201214416u64, 165958123462u64}) + put(data, {2933643244178200621u64, 14253990228345322571u64, 198282718640u64}) + put(data, {17188148801879487562u64, 11214836553940194590u64, 176772710358u64}) + put(data, {11069762501163246592u64, 14620711348380590101u64, 214607957507u64}) + put(data, {11676570643941818368u64, 6638710787931587427u64, 3792590350u64}) + put(data, {17840016768744030208u64, 17320000343692853250u64, 14359885232u64}) + put(data, {16463817321652158464u64, 75147386268843646u64, 176938919100u64}) + put(data, {6954191143357644800u64, 17938801582125480173u64, 188004073747u64}) + put(data, {5080060379673919488u64, 6573358613626446145u64, 19972464382u64}) + put(data, {0u64, 8688505427903736481u64, 254356342484u64}) + put(data, {0u64, 539870168696556032u64, 212471004823u64}) + put(data, {0u64, 9002861336394465280u64, 151029266420u64}) + put(data, {0u64, 17989846818158018560u64, 244488046090u64}) + put(data, {0u64, 2700938287723315200u64, 10975231550u64}) + put(data, {0u64, 17800090499088908288u64, 62146418157u64}) + put(data, {0u64, 8809040871136690176u64, 237964944839u64}) + put(data, {0u64, 9223372036854775808u64, 199477539062u64}) + put(data, {0u64, 0u64, 246500000000u64}) + put(data, {16433563478020213436u64, 144u64, 0u64}) + put(data, {4194750497955655375u64, 144890865261u64, 0u64}) + put(data, {2691539602252904735u64, 15763656745260536568u64, 7u64}) + put(data, {3775467271962795241u64, 8787336846248645550u64, 7854549544u64}) + put(data, {16980212613224918121u64, 17584084447880694346u64, 40476362484u64}) + put(data, {4172857038337333440u64, 18041672551129683938u64, 244953235127u64}) + put(data, {5936867627376461659u64, 14025886302294509785u64, 183978041028u64}) + put(data, {17856837443266866062u64, 18430498103283160734u64, 196760344819u64}) + put(data, {8956297047799810807u64, 3292348826238025833u64, 243999119304u64}) + put(data, {15356974049716912789u64, 9211721212658275243u64, 200178478587u64}) + put(data, {6923608913322982854u64, 10233245872666307519u64, 251499368407u64}) + put(data, {4855902993563955944u64, 6200995035623311671u64, 215554745370u64}) + put(data, {13835893222288330752u64, 8480542380570450804u64, 26336156614u64}) + put(data, {9114973913760137216u64, 11870363864499900506u64, 198459731123u64}) + put(data, {17937099003422310400u64, 9301051379839581901u64, 179643493714u64}) + put(data, {7007960010734960640u64, 11456694803569638005u64, 82504211005u64}) + put(data, {7683422439270776832u64, 14327208890643983169u64, 61621068669u64}) + put(data, {720575940379279360u64, 4510081789599866365u64, 125776679550u64}) + put(data, {0u64, 13255356976020303332u64, 126244492023u64}) + put(data, {0u64, 9658806854127314944u64, 247718574341u64}) + put(data, {0u64, 13708435528809971712u64, 5523604968u64}) + put(data, {0u64, 1580190652103131136u64, 232743135779u64}) + put(data, {0u64, 16557336970347413504u64, 35085662306u64}) + put(data, {0u64, 12751520132434493440u64, 98897575035u64}) + put(data, {0u64, 9295429630892703744u64, 123691261291u64}) + put(data, {0u64, 0u64, 107503906250u64}) + put(data, {0u64, 0u64, 202000000000u64}) + put(data, {2768933352715741194u64, 2210859u64, 0u64}) + put(data, {15201941611824153390u64, 2210859150104177u64, 0u64}) + put(data, {1418128541727000180u64, 16872870088062921306u64, 119850u64}) + put(data, {5353350204565757408u64, 5112979788807802982u64, 42914680120u64}) + put(data, {1721001680354286741u64, 13742728082020150272u64, 56277175189u64}) + put(data, {637631411660453962u64, 2217110934613627976u64, 149744994782u64}) + put(data, {1630012588870568400u64, 11021433940188610484u64, 222120189824u64}) + put(data, {9253063571656828156u64, 1713669895470733463u64, 128597473131u64}) + put(data, {6029146854993203780u64, 3313382510572018285u64, 107092898231u64}) + put(data, {16987401965352759896u64, 14976595232784069505u64, 183179618825u64}) + put(data, {14499131620542087970u64, 7213172372862496841u64, 9811882854u64}) + put(data, {1978417255298660539u64, 15836474542502248588u64, 102391026857u64}) + put(data, {5790079354402454176u64, 3221099285878340134u64, 169858497005u64}) + put(data, {13748918935842078720u64, 3265814602578095358u64, 237174616142u64}) + put(data, {18047438014740692992u64, 6502528252282225364u64, 78177040164u64}) + put(data, {3116889656839372800u64, 16392476834556790183u64, 36352502762u64}) + put(data, {15995952446606147584u64, 15167629413417091342u64, 234888637949u64}) + put(data, {12530140063251562496u64, 1366763272626280111u64, 253822238838u64}) + put(data, {16717361816799281152u64, 8720523635169216093u64, 118074092385u64}) + put(data, {0u64, 9649171375767398672u64, 97472740533u64}) + put(data, {0u64, 7647980704001073152u64, 181523082628u64}) + put(data, {0u64, 13286434495608651776u64, 132414597864u64}) + put(data, {0u64, 4358271637167013888u64, 232720259057u64}) + put(data, {0u64, 15954987941890097152u64, 241236262378u64}) + put(data, {0u64, 7911135695429697536u64, 234864921629u64}) + put(data, {0u64, 7205759403792793600u64, 29428863525u64}) + put(data, {0u64, 0u64, 37390625000u64}) + put(data, {0u64, 0u64, 232000000000u64}) + put(data, {13558973353698967386u64, 33u64, 0u64}) + put(data, {6228991722850501890u64, 33735033418u64, 0u64}) + put(data, {16886831391703377787u64, 15288289344628122701u64, 1u64}) + put(data, {1181713637872883048u64, 952589339068938148u64, 1828779826u64}) + put(data, {12393932434925221726u64, 10058155040190817688u64, 50051639971u64}) + put(data, {10684799845419711910u64, 5322725640026584391u64, 163545253677u64}) + put(data, {4590318792215640843u64, 2269982385930389600u64, 45288545535u64}) + put(data, {6383712187366189238u64, 13216683679976310224u64, 255123055991u64}) + put(data, {1904270214927675016u64, 17417440642083494819u64, 119716477857u64}) + put(data, {3711506775113308575u64, 3029180749090900711u64, 161944201349u64}) + put(data, {8091219444738793995u64, 8315443826261908513u64, 133164212217u64}) + put(data, {39436684991068052u64, 1488962797247197277u64, 249450781113u64}) + put(data, {18025182908196512891u64, 18009099634999034122u64, 185080716834u64}) + put(data, {7853924592034603138u64, 8092455412807497971u64, 34976275247u64}) + put(data, {4815749283615688320u64, 17808458047236758329u64, 47438692886u64}) + put(data, {14242399906544287744u64, 3164591817527425171u64, 22965398445u64}) + put(data, {76242322576113664u64, 3314036340472350590u64, 173171552866u64}) + put(data, {10858088421377703936u64, 33234902404332784u64, 98179654270u64}) + put(data, {14293835879041466368u64, 12349284717857274280u64, 126001801667u64}) + put(data, {12182236992037191680u64, 18209607903013119355u64, 195669456065u64}) + put(data, {11529215046068469760u64, 7891549145984268038u64, 193987144822u64}) + put(data, {0u64, 7703609897518594624u64, 118427801736u64}) + put(data, {0u64, 6336912652634587136u64, 136417613529u64}) + put(data, {0u64, 4461621834659397632u64, 217343524723u64}) + put(data, {0u64, 5484660635557953536u64, 115241865004u64}) + put(data, {0u64, 15142619273265938432u64, 44297324048u64}) + put(data, {0u64, 12170977992968765440u64, 16820883035u64}) + put(data, {0u64, 1152921504606846976u64, 91659790039u64}) + put(data, {0u64, 0u64, 215062500000u64}) + put(data, {0u64, 0u64, 160000000000u64}) + put(data, {14000097438505379162u64, 514755u64, 0u64}) + put(data, {16451062686452429925u64, 514755758946802u64, 0u64}) + put(data, {4009347599785716645u64, 17812314011563521031u64, 27904u64}) + put(data, {16544162347546196456u64, 7684138864490314336u64, 965607477u64}) + put(data, {17674258299745817187u64, 9740522787420029605u64, 53416558002u64}) + put(data, {5215238411201214416u64, 6701109407732989894u64, 178528034798u64}) + put(data, {14253990228345322571u64, 16534886227502443952u64, 238363267868u64}) + put(data, {11214836553940194590u64, 8908667306968317910u64, 28896357978u64}) + put(data, {14620711348380590101u64, 7531472173477105155u64, 90482939822u64}) + put(data, {6638710787931587427u64, 11527371604834801166u64, 174408281924u64}) + put(data, {17320000343692853250u64, 15688593496691078576u64, 68624900066u64}) + put(data, {75147386268843646u64, 11394944804253312188u64, 226850480357u64}) + put(data, {17938801582125480173u64, 11182279880854372627u64, 229617721195u64}) + put(data, {6573358613626446145u64, 150579373068361470u64, 107606192607u64}) + put(data, {8688505427903736481u64, 3147220002440857300u64, 223008162924u64}) + put(data, {539870168696556032u64, 3630514817795505815u64, 108170611138u64}) + put(data, {9002861336394465280u64, 11708796588334233588u64, 194196810602u64}) + put(data, {17989846818158018560u64, 16844495466426369546u64, 106634735134u64}) + put(data, {2700938287723315200u64, 17636655472325475902u64, 30913141928u64}) + put(data, {17800090499088908288u64, 17038926655686645229u64, 168956085008u64}) + put(data, {8809040871136690176u64, 15602838456783529415u64, 16923682064u64}) + put(data, {9223372036854775808u64, 10869815869248876790u64, 16845831567u64}) + put(data, {0u64, 18407124180939800832u64, 143589253898u64}) + put(data, {0u64, 5705018517251293184u64, 10997852201u64}) + put(data, {0u64, 9660452258743058432u64, 41309269673u64}) + put(data, {0u64, 5646292272224927744u64, 169523694166u64}) + put(data, {0u64, 7410409304047484928u64, 86306086117u64}) + put(data, {0u64, 5953758707383795712u64, 229401719093u64}) + put(data, {0u64, 4611686018427387904u64, 53322753906u64}) + put(data, {0u64, 0u64, 114250000000u64}) + put(data, {0u64, 0u64, 128000000000u64}) + put(data, {15763656745260536568u64, 7u64, 0u64}) + put(data, {8787336846248645550u64, 7854549544u64, 0u64}) + put(data, {17584084447880694346u64, 7854549544476362484u64, 0u64}) + put(data, {18041672551129683938u64, 15035424419724983u64, 425795984u64}) + put(data, {14025886302294509785u64, 18280822466032836292u64, 144000815071u64}) + put(data, {18430498103283160734u64, 11524250747302615283u64, 223991005371u64}) + put(data, {3292348826238025833u64, 15212285943691810760u64, 187624730884u64}) + put(data, {9211721212658275243u64, 7951804027551297019u64, 4824659673u64}) + put(data, {10233245872666307519u64, 1706416229965221847u64, 217431068160u64}) + put(data, {6200995035623311671u64, 3406023111930700826u64, 92505009u64}) + put(data, {8480542380570450804u64, 16132696204133391302u64, 177184640882u64}) + put(data, {11870363864499900506u64, 11593846688794356915u64, 114874555213u64}) + put(data, {9301051379839581901u64, 6875759884161133906u64, 77628503688u64}) + put(data, {11456694803569638005u64, 3593593325323835965u64, 136372735690u64}) + put(data, {14327208890643983169u64, 9542049733257388925u64, 202194809084u64}) + put(data, {4510081789599866365u64, 9926551925937787518u64, 252517275552u64}) + put(data, {13255356976020303332u64, 3128491553219547895u64, 160538119458u64}) + put(data, {9658806854127314944u64, 17158408656931354885u64, 34169595866u64}) + put(data, {13708435528809971712u64, 2065169543154992616u64, 218930159197u64}) + put(data, {1580190652103131136u64, 4832622393556232739u64, 93111953065u64}) + put(data, {16557336970347413504u64, 16505930714733656162u64, 169261976984u64}) + put(data, {12751520132434493440u64, 18270988073492888699u64, 152894788296u64}) + put(data, {9295429630892703744u64, 2525111411519708523u64, 200990472248u64}) + put(data, {0u64, 16728989342518570442u64, 56136886563u64}) + put(data, {0u64, 7974052022039438336u64, 35906880329u64}) + put(data, {0u64, 5356554962386550784u64, 73432274226u64}) + put(data, {0u64, 6693869495028547584u64, 50290379426u64}) + put(data, {0u64, 8157517147199766528u64, 162362875392u64}) + put(data, {0u64, 12065776720423157760u64, 442219890u64}) + put(data, {0u64, 11997589407315001344u64, 114654087066u64}) + put(data, {0u64, 0u64, 154650390625u64}) + put(data, {0u64, 0u64, 97000000000u64}) + put(data, {16872870088062921306u64, 119850u64, 0u64}) + put(data, {5112979788807802982u64, 119850914680120u64, 0u64}) + put(data, {13742728082020150272u64, 2418433229320326037u64, 6497u64}) + put(data, {2217110934613627976u64, 1143911773589293534u64, 97131103528u64}) + put(data, {11021433940188610484u64, 9276183703610924928u64, 40062011581u64}) + put(data, {1713669895470733463u64, 3532180128827684715u64, 189502862926u64}) + put(data, {3313382510572018285u64, 8563997501322031543u64, 78191479868u64}) + put(data, {14976595232784069505u64, 14843890409658460681u64, 60464255234u64}) + put(data, {7213172372862496841u64, 9489417861634552678u64, 2804688911u64}) + put(data, {15836474542502248588u64, 1113198223322322089u64, 15514422373u64}) + put(data, {3221099285878340134u64, 11190777557146597869u64, 101060346596u64}) + put(data, {3265814602578095358u64, 17764553645932638286u64, 228606653266u64}) + put(data, {6502528252282225364u64, 14900777150991234852u64, 82963018382u64}) + put(data, {16392476834556790183u64, 17364899863357893610u64, 142807772747u64}) + put(data, {15167629413417091342u64, 15537570181590167037u64, 75941353107u64}) + put(data, {1366763272626280111u64, 5558052627121307766u64, 147842293367u64}) + put(data, {8720523635169216093u64, 12095241565795232609u64, 119301302636u64}) + put(data, {9649171375767398672u64, 2187936505958366389u64, 108655684359u64}) + put(data, {7647980704001073152u64, 12009203621325860228u64, 7118608275u64}) + put(data, {13286434495608651776u64, 14814842834750302952u64, 147651020232u64}) + put(data, {4358271637167013888u64, 5965296499605198833u64, 200803114239u64}) + put(data, {15954987941890097152u64, 4051026394962148842u64, 255323379371u64}) + put(data, {7911135695429697536u64, 16799526299141688349u64, 171219606580u64}) + put(data, {7205759403792793600u64, 9460214166646215205u64, 52910704145u64}) + put(data, {0u64, 10750736995029068008u64, 17512839237u64}) + put(data, {0u64, 5377963045376430080u64, 69582798620u64}) + put(data, {0u64, 15996910350253424640u64, 28291539960u64}) + put(data, {0u64, 13651157529655246848u64, 248867194247u64}) + put(data, {0u64, 9771305410219737088u64, 135740030732u64}) + put(data, {0u64, 12709439623416250368u64, 12529703527u64}) + put(data, {0u64, 9943947977234055168u64, 103688980102u64}) + put(data, {0u64, 0u64, 134539062500u64}) + put(data, {0u64, 0u64, 228000000000u64}) + put(data, {952589339068938148u64, 1828779826u64, 0u64}) + put(data, {10058155040190817688u64, 1828779826051639971u64, 0u64}) + put(data, {5322725640026584391u64, 371564423966525229u64, 99138353u64}) + put(data, {2269982385930389600u64, 14464859121514339583u64, 49020142547u64}) + put(data, {13216683679976310224u64, 3913119023023056247u64, 211784141584u64}) + put(data, {17417440642083494819u64, 5493396321716566945u64, 16212130607u64}) + put(data, {3029180749090900711u64, 5837454566818211973u64, 47297797611u64}) + put(data, {8315443826261908513u64, 2886670683193253881u64, 235316449046u64}) + put(data, {1488962797247197277u64, 5504823105587173817u64, 22156486731u64}) + put(data, {18009099634999034122u64, 9431834277334851106u64, 75298417058u64}) + put(data, {8092455412807497971u64, 12921661346456247087u64, 162511300760u64}) + put(data, {17808458047236758329u64, 3643076516404724246u64, 152700484665u64}) + put(data, {3164591817527425171u64, 12559396953196866477u64, 57197491573u64}) + put(data, {3314036340472350590u64, 1626880974916825698u64, 117680846273u64}) + put(data, {33234902404332784u64, 6806994170946429566u64, 193088193394u64}) + put(data, {12349284717857274280u64, 7596631230206896579u64, 114369007893u64}) + put(data, {18209607903013119355u64, 3100480253729502401u64, 21411814204u64}) + put(data, {7891549145984268038u64, 6310570748781063286u64, 60168077371u64}) + put(data, {7703609897518594624u64, 14251867077375744136u64, 59342096725u64}) + put(data, {6336912652634587136u64, 6701165793751570137u64, 85772595262u64}) + put(data, {4461621834659397632u64, 10856833140463959923u64, 62363270925u64}) + put(data, {5484660635557953536u64, 15867563727561248556u64, 13588550103u64}) + put(data, {15142619273265938432u64, 5048961008671491600u64, 215860182353u64}) + put(data, {12170977992968765440u64, 13278183119599849051u64, 81273704724u64}) + put(data, {1152921504606846976u64, 4547591784941053655u64, 20719811749u64}) + put(data, {0u64, 11815437715887182496u64, 165246525444u64}) + put(data, {0u64, 398495392178782208u64, 4640516162u64}) + put(data, {0u64, 9154841240825495552u64, 66021602478u64}) + put(data, {0u64, 1902683298245640192u64, 174496284938u64}) + put(data, {0u64, 5081900962138816512u64, 10103144668u64}) + put(data, {0u64, 3234710432358858752u64, 220275490403u64}) + put(data, {0u64, 16717361816799281152u64, 99175354003u64}) + put(data, {0u64, 0u64, 147906250000u64}) + put(data, {0u64, 0u64, 16000000000u64}) + put(data, {17812314011563521031u64, 27904u64, 0u64}) + put(data, {7684138864490314336u64, 27904965607477u64, 0u64}) + put(data, {9740522787420029605u64, 13488568028574514610u64, 1512u64}) + put(data, {6701109407732989894u64, 275784718433886190u64, 232731216738u64}) + put(data, {16534886227502443952u64, 10020568880357102364u64, 98014950319u64}) + put(data, {8908667306968317910u64, 8876397213146246746u64, 175543216127u64}) + put(data, {7531472173477105155u64, 2155905919114811310u64, 255481190457u64}) + put(data, {11527371604834801166u64, 1087100407155601220u64, 57116871894u64}) + put(data, {15688593496691078576u64, 2903498381705011170u64, 214058931831u64}) + put(data, {11394944804253312188u64, 12223476257006657765u64, 119157398962u64}) + put(data, {11182279880854372627u64, 12148657163736735595u64, 178662635975u64}) + put(data, {150579373068361470u64, 8951241323311673823u64, 199658580024u64}) + put(data, {3147220002440857300u64, 8463862715901576300u64, 56485247764u64}) + put(data, {3630514817795505815u64, 3873401978748963266u64, 20458826917u64}) + put(data, {11708796588334233588u64, 248364795947002730u64, 165209977542u64}) + put(data, {16844495466426369546u64, 10454378025404001822u64, 198013463882u64}) + put(data, {17636655472325475902u64, 6574176865628265640u64, 74566732968u64}) + put(data, {17038926655686645229u64, 16703315293848336u64, 168356386842u64}) + put(data, {15602838456783529415u64, 9896033222450013456u64, 26000905488u64}) + put(data, {10869815869248876790u64, 17311376269334085007u64, 16536465035u64}) + put(data, {18407124180939800832u64, 18378511316495639306u64, 139938451587u64}) + put(data, {5705018517251293184u64, 15120796393727584297u64, 131996301094u64}) + put(data, {9660452258743058432u64, 18253447805740347049u64, 38819700014u64}) + put(data, {5646292272224927744u64, 5842497225601731158u64, 46989521388u64}) + put(data, {7410409304047484928u64, 4369968404176723173u64, 236316722409u64}) + put(data, {5953758707383795712u64, 16142207253674488117u64, 233236896461u64}) + put(data, {4611686018427387904u64, 12124259227391928178u64, 205875070808u64}) + put(data, {0u64, 13019483264566077056u64, 88657257409u64}) + put(data, {0u64, 74901376448135168u64, 193705787602u64}) + put(data, {0u64, 13897060093813325824u64, 210004060411u64}) + put(data, {0u64, 4495486210810052608u64, 251753361137u64}) + put(data, {0u64, 14885496280087265280u64, 241243700795u64}) + put(data, {0u64, 4976477588244398080u64, 59806944370u64}) + put(data, {0u64, 11529215046068469760u64, 114269775390u64}) + put(data, {0u64, 0u64, 30625000000u64}) + put(data, {0u64, 0u64, 64000000000u64}) + put(data, {15035424419724983u64, 425795984u64, 0u64}) + put(data, {18280822466032836292u64, 425795984000815071u64, 0u64}) + put(data, {11524250747302615283u64, 10043594327130472635u64, 23082446u64}) + put(data, {15212285943691810760u64, 8336034337032909060u64, 206544464339u64}) + put(data, {7951804027551297019u64, 16717215784895280857u64, 211451897326u64}) + put(data, {1706416229965221847u64, 10968831263951212032u64, 238906242083u64}) + put(data, {3406023111930700826u64, 5536629379734406065u64, 35594621534u64}) + put(data, {16132696204133391302u64, 1618806894932332402u64, 94300141280u64}) + put(data, {11593846688794356915u64, 11363331325254998861u64, 224087755697u64}) + put(data, {6875759884161133906u64, 8775167772751754888u64, 177616007425u64}) + put(data, {3593593325323835965u64, 2898202945316114122u64, 1475702798u64}) + put(data, {9542049733257388925u64, 8868842714495185148u64, 14157111896u64}) + put(data, {9926551925937787518u64, 17052094667531999136u64, 88480780926u64}) + put(data, {3128491553219547895u64, 3658615537031138594u64, 126924395904u64}) + put(data, {17158408656931354885u64, 12486952437987190746u64, 128198333945u64}) + put(data, {2065169543154992616u64, 912079238520577629u64, 249676919048u64}) + put(data, {4832622393556232739u64, 10960072898031888041u64, 8049443914u64}) + put(data, {16505930714733656162u64, 6129550094334741912u64, 74594146742u64}) + put(data, {18270988073492888699u64, 7965724516573729480u64, 182332283576u64}) + put(data, {2525111411519708523u64, 5801761178810791992u64, 184431822791u64}) + put(data, {16728989342518570442u64, 13197466483098446115u64, 199314514103u64}) + put(data, {7974052022039438336u64, 11326268638393107273u64, 183715436091u64}) + put(data, {5356554962386550784u64, 3597339351794947378u64, 59613998253u64}) + put(data, {6693869495028547584u64, 353880726151383714u64, 173195012157u64}) + put(data, {8157517147199766528u64, 11154818162602073600u64, 61019183912u64}) + put(data, {12065776720423157760u64, 5141043976157511026u64, 40604703904u64}) + put(data, {11997589407315001344u64, 7188225141808859034u64, 160278696552u64}) + put(data, {0u64, 13894168943295705185u64, 104389674465u64}) + put(data, {0u64, 12176538069834828288u64, 225753204407u64}) + put(data, {0u64, 7994239409235165184u64, 183660091451u64}) + put(data, {0u64, 13707777025480065024u64, 59433368586u64}) + put(data, {0u64, 10120227247676719104u64, 10743100081u64}) + put(data, {0u64, 7358494763030413312u64, 177548618618u64}) + put(data, {0u64, 7656119366529843200u64, 122398904800u64}) + put(data, {0u64, 9223372036854775808u64, 224415039062u64}) + put(data, {0u64, 0u64, 86500000000u64}) + put(data, {2418433229320326037u64, 6497u64, 0u64}) + put(data, {1143911773589293534u64, 6497131103528u64, 0u64}) + put(data, {9276183703610924928u64, 3877189582299842749u64, 352u64}) + put(data, {3532180128827684715u64, 7625565791857948238u64, 96210182868u64}) + put(data, {8563997501322031543u64, 16568435163612007484u64, 212413382749u64}) + put(data, {14843890409658460681u64, 17592071940521808130u64, 93898176669u64}) + put(data, {9489417861634552678u64, 15158637878035490831u64, 157953668130u64}) + put(data, {1113198223322322089u64, 17789243229146401893u64, 34821751405u64}) + put(data, {11190777557146597869u64, 14677686051252896484u64, 109964356807u64}) + put(data, {17764553645932638286u64, 3531237481269211986u64, 199795678955u64}) + put(data, {14900777150991234852u64, 8074435404989280910u64, 235191428767u64}) + put(data, {17364899863357893610u64, 7086549341467684427u64, 159437716020u64}) + put(data, {15537570181590167037u64, 10556134770918626963u64, 52384162609u64}) + put(data, {5558052627121307766u64, 10772666134712966775u64, 49572249212u64}) + put(data, {12095241565795232609u64, 6195173298198112620u64, 124583987401u64}) + put(data, {2187936505958366389u64, 8144773843324250887u64, 201335841017u64}) + put(data, {12009203621325860228u64, 14144284817150924691u64, 249441529074u64}) + put(data, {14814842834750302952u64, 6464447844648863176u64, 242766763216u64}) + put(data, {5965296499605198833u64, 15760468443293179135u64, 208350438419u64}) + put(data, {4051026394962148842u64, 5172191224908322475u64, 19854376706u64}) + put(data, {16799526299141688349u64, 2357554307308969012u64, 2280385048u64}) + put(data, {9460214166646215205u64, 1602046917604361745u64, 24127803275u64}) + put(data, {10750736995029068008u64, 7830970218109515845u64, 139086847137u64}) + put(data, {5377963045376430080u64, 2899479134887821084u64, 161424517746u64}) + put(data, {15996910350253424640u64, 15792042302392017912u64, 114157181078u64}) + put(data, {13651157529655246848u64, 11286099112296056199u64, 150856088328u64}) + put(data, {9771305410219737088u64, 15161477829153947404u64, 8611820658u64}) + put(data, {12709439623416250368u64, 423831848142641767u64, 114821905360u64}) + put(data, {9943947977234055168u64, 9707413321046312582u64, 208022975970u64}) + put(data, {0u64, 10969483299803835620u64, 226526239930u64}) + put(data, {0u64, 4326479556120930304u64, 186594656881u64}) + put(data, {0u64, 12876227232041795584u64, 113234538926u64}) + put(data, {0u64, 16967986827791171584u64, 174698021676u64}) + put(data, {0u64, 1288146316538413056u64, 44919836409u64}) + put(data, {0u64, 13715290452691779584u64, 249069830551u64}) + put(data, {0u64, 4683743612465315840u64, 151743507385u64}) + put(data, {0u64, 0u64, 185253906250u64}) + put(data, {0u64, 0u64, 74000000000u64}) + put(data, {371564423966525229u64, 99138353u64, 0u64}) + put(data, {14464859121514339583u64, 99138353020142547u64, 0u64}) + put(data, {3913119023023056247u64, 16344805304534272784u64, 5374300u64}) + put(data, {5493396321716566945u64, 26429987091348271u64, 92886053671u64}) + put(data, {5837454566818211973u64, 8691371289609838059u64, 39001432772u64}) + put(data, {2886670683193253881u64, 12980168378493046550u64, 196471160181u64}) + put(data, {5504823105587173817u64, 14010125458129496139u64, 117703656337u64}) + put(data, {9431834277334851106u64, 17061829677031795106u64, 145759490422u64}) + put(data, {12921661346456247087u64, 2227928323072698520u64, 118924923640u64}) + put(data, {3643076516404724246u64, 7394752319272287289u64, 248120776236u64}) + put(data, {12559396953196866477u64, 8805771303577744757u64, 44400870326u64}) + put(data, {1626880974916825698u64, 16371027194302248385u64, 182477361818u64}) + put(data, {6806994170946429566u64, 9114324123731231602u64, 154887475162u64}) + put(data, {7596631230206896579u64, 14468189808746991893u64, 218494088500u64}) + put(data, {3100480253729502401u64, 2376054557800684348u64, 52784322141u64}) + put(data, {6310570748781063286u64, 12462238943546048571u64, 93128806175u64}) + put(data, {14251867077375744136u64, 15334855370842605909u64, 31675579326u64}) + put(data, {6701165793751570137u64, 7211347914013798462u64, 190831304175u64}) + put(data, {10856833140463959923u64, 13763642332572548877u64, 239390927953u64}) + put(data, {15867563727561248556u64, 16868268377740071383u64, 81746128545u64}) + put(data, {5048961008671491600u64, 1120013377627684177u64, 161914430661u64}) + put(data, {13278183119599849051u64, 15898107650717274388u64, 197060716046u64}) + put(data, {4547591784941053655u64, 12281923376333274277u64, 14861838142u64}) + put(data, {11815437715887182496u64, 6383530489286615044u64, 62665804400u64}) + put(data, {398495392178782208u64, 4253822060257126466u64, 112346051881u64}) + put(data, {9154841240825495552u64, 17614372438391501998u64, 41230600155u64}) + put(data, {1902683298245640192u64, 4309951310554333450u64, 219954877043u64}) + put(data, {5081900962138816512u64, 13106185988973773020u64, 115233642928u64}) + put(data, {3234710432358858752u64, 2070134359761960547u64, 176710487766u64}) + put(data, {16717361816799281152u64, 9399359914137865875u64, 214112222208u64}) + put(data, {0u64, 17415053284723541264u64, 509540321u64}) + put(data, {0u64, 4840502610448261120u64, 225944071930u64}) + put(data, {0u64, 5690599259712258048u64, 250262404172u64}) + put(data, {0u64, 114769594245185536u64, 76308488004u64}) + put(data, {0u64, 3150620882578178048u64, 68006221672u64}) + put(data, {0u64, 5136918324969472000u64, 104170795500u64}) + put(data, {0u64, 7205759403792793600u64, 236278472900u64}) + put(data, {0u64, 0u64, 196390625000u64}) + put(data, {0u64, 0u64, 232000000000u64}) + put(data, {13488568028574514610u64, 1512u64, 0u64}) + put(data, {275784718433886190u64, 1512731216738u64, 0u64}) + put(data, {10020568880357102364u64, 98202693831717807u64, 82u64}) + put(data, {8876397213146246746u64, 12909287260170414079u64, 82005323578u64}) + put(data, {2155905919114811310u64, 11728631949380786233u64, 58699813864u64}) + put(data, {1087100407155601220u64, 18263701925522197718u64, 232635810411u64}) + put(data, {2903498381705011170u64, 4868886449713321591u64, 107990077265u64}) + put(data, {12223476257006657765u64, 5870139507184082354u64, 81263942863u64}) + put(data, {12148657163736735595u64, 5978562500822661575u64, 207318220900u64}) + put(data, {8951241323311673823u64, 10821136839630268472u64, 100324098522u64}) + put(data, {8463862715901576300u64, 9490907630136752916u64, 218586615003u64}) + put(data, {3873401978748963266u64, 10564005678001613989u64, 219514503133u64}) + put(data, {248364795947002730u64, 5754050547468481222u64, 221572675895u64}) + put(data, {10454378025404001822u64, 3833909949855542602u64, 55311927705u64}) + put(data, {6574176865628265640u64, 15446538552665967784u64, 153207836674u64}) + put(data, {16703315293848336u64, 14924837848804399130u64, 2837358532u64}) + put(data, {9896033222450013456u64, 18140170340418344208u64, 196809077080u64}) + put(data, {17311376269334085007u64, 11380424819825208971u64, 88983380604u64}) + put(data, {18378511316495639306u64, 12416915664152252547u64, 124616934065u64}) + put(data, {15120796393727584297u64, 17195282241626289958u64, 177673122346u64}) + put(data, {18253447805740347049u64, 2649541045825281326u64, 42932158118u64}) + put(data, {5842497225601731158u64, 16577429864268509676u64, 166143631907u64}) + put(data, {4369968404176723173u64, 12051257060168107241u64, 35898664273u64}) + put(data, {16142207253674488117u64, 5363884561143470797u64, 81653299954u64}) + put(data, {12124259227391928178u64, 13054029903083620184u64, 242290776764u64}) + put(data, {13019483264566077056u64, 566314952158634945u64, 188707660379u64}) + put(data, {74901376448135168u64, 1329472079642345682u64, 91030699995u64}) + put(data, {13897060093813325824u64, 15686237486658857211u64, 219072070825u64}) + put(data, {4495486210810052608u64, 1069073549290598129u64, 169850352638u64}) + put(data, {14885496280087265280u64, 4323599065125928507u64, 254057954593u64}) + put(data, {4976477588244398080u64, 17861823329752681586u64, 33234382774u64}) + put(data, {11529215046068469760u64, 17220149985412802078u64, 182968291382u64}) + put(data, {0u64, 4344934572159429184u64, 54933506201u64}) + put(data, {0u64, 2252927464837120000u64, 153235539375u64}) + put(data, {0u64, 10910018171964489728u64, 175122131442u64}) + put(data, {0u64, 3597328585515335680u64, 242591433270u64}) + put(data, {0u64, 6972808074239148032u64, 54195011573u64}) + put(data, {0u64, 2227030015734710272u64, 245377996683u64}) + put(data, {0u64, 1152921504606846976u64, 139120727539u64}) + put(data, {0u64, 0u64, 243062500000u64}) + put(data, {0u64, 0u64, 160000000000u64}) + put(data, {10043594327130472635u64, 23082446u64, 0u64}) + put(data, {8336034337032909060u64, 23082446544464339u64, 0u64}) + put(data, {16717215784895280857u64, 17238287503805244910u64, 1251301u64}) + put(data, {10968831263951212032u64, 1434575446038410275u64, 229934489438u64}) + put(data, {5536629379734406065u64, 14009569747841241694u64, 94077768490u64}) + put(data, {1618806894932332402u64, 14938795732275951328u64, 42759460297u64}) + put(data, {11363331325254998861u64, 6687653542888983473u64, 201809833739u64}) + put(data, {8775167772751754888u64, 28238723295162625u64, 11362538425u64}) + put(data, {2898202945316114122u64, 4745270274832691214u64, 185001530824u64}) + put(data, {8868842714495185148u64, 926478968112308824u64, 200257241617u64}) + put(data, {17052094667531999136u64, 9213681606604198526u64, 17050224525u64}) + put(data, {3658615537031138594u64, 13346223820579313024u64, 141499474680u64}) + put(data, {12486952437987190746u64, 691642518601291257u64, 248723500243u64}) + put(data, {912079238520577629u64, 1153720150033789192u64, 211037494016u64}) + put(data, {10960072898031888041u64, 12089015034721780810u64, 62543294u64}) + put(data, {6129550094334741912u64, 3555868702841788854u64, 190655346818u64}) + put(data, {7965724516573729480u64, 11708406782758214328u64, 130192764028u64}) + put(data, {5801761178810791992u64, 9417497762905343943u64, 124634714003u64}) + put(data, {13197466483098446115u64, 12838336066957615287u64, 147510523576u64}) + put(data, {11326268638393107273u64, 13737708142128207419u64, 184695967592u64}) + put(data, {3597339351794947378u64, 11683434809834695853u64, 104744722650u64}) + put(data, {353880726151383714u64, 2689114340106315837u64, 218633360270u64}) + put(data, {11154818162602073600u64, 8859225263374261032u64, 142145777180u64}) + put(data, {5141043976157511026u64, 15761671984578600096u64, 28480259563u64}) + put(data, {7188225141808859034u64, 7087267079878005352u64, 235854441950u64}) + put(data, {13894168943295705185u64, 4601291730423121377u64, 222384201518u64}) + put(data, {12176538069834828288u64, 9559411037059581623u64, 46249436524u64}) + put(data, {7994239409235165184u64, 12969820289641388091u64, 108518216710u64}) + put(data, {13707777025480065024u64, 13628239920285957130u64, 6703095366u64}) + put(data, {10120227247676719104u64, 8049893933765800625u64, 70738788366u64}) + put(data, {7358494763030413312u64, 10391755948840250234u64, 14436385624u64}) + put(data, {7656119366529843200u64, 14454650777462444512u64, 88563338218u64}) + put(data, {9223372036854775808u64, 14244638523341127254u64, 234783588188u64}) + put(data, {0u64, 12246016810439753984u64, 92772203401u64}) + put(data, {0u64, 9382741764551081984u64, 137663857901u64}) + put(data, {0u64, 4608696190291148800u64, 237508639450u64}) + put(data, {0u64, 1696483666416369664u64, 218249837921u64}) + put(data, {0u64, 15416683541605384192u64, 97091966563u64}) + put(data, {0u64, 7683140964294066176u64, 99835740089u64}) + put(data, {0u64, 4611686018427387904u64, 185416503906u64}) + put(data, {0u64, 0u64, 98250000000u64}) + put(data, {0u64, 0u64, 128000000000u64}) + put(data, {3877189582299842749u64, 352u64, 0u64}) + put(data, {7625565791857948238u64, 352210182868u64, 0u64}) + put(data, {16568435163612007484u64, 1722045467931902045u64, 19u64}) + put(data, {17592071940521808130u64, 16095324008152856733u64, 19093352271u64}) + put(data, {15158637878035490831u64, 15216188060094280738u64, 79872529262u64}) + put(data, {17789243229146401893u64, 10793385929903030893u64, 110824871207u64}) + put(data, {14677686051252896484u64, 12613277226875940039u64, 39585110623u64}) + put(data, {3531237481269211986u64, 10644539625155600107u64, 95683767128u64}) + put(data, {8074435404989280910u64, 6181262895644173983u64, 88577041649u64}) + put(data, {7086549341467684427u64, 148914399627082292u64, 241335086933u64}) + put(data, {10556134770918626963u64, 14379289774887985969u64, 85008072665u64}) + put(data, {10772666134712966775u64, 11743339675582627452u64, 217779502860u64}) + put(data, {6195173298198112620u64, 7841621929809463497u64, 12636607719u64}) + put(data, {8144773843324250887u64, 11168944680251236601u64, 231425095176u64}) + put(data, {14144284817150924691u64, 6178560202529287410u64, 8605469704u64}) + put(data, {6464447844648863176u64, 13295243308201596112u64, 8334940419u64}) + put(data, {15760468443293179135u64, 17040673746172470291u64, 3720736583u64}) + put(data, {5172191224908322475u64, 14957442487039409922u64, 71923776774u64}) + put(data, {2357554307308969012u64, 17778155426506992152u64, 6810844581u64}) + put(data, {1602046917604361745u64, 14945404984219733899u64, 165963755736u64}) + put(data, {7830970218109515845u64, 11590754866058681505u64, 216810192027u64}) + put(data, {2899479134887821084u64, 6020790784469412466u64, 155628336080u64}) + put(data, {15792042302392017912u64, 7934351824569522326u64, 208326387722u64}) + put(data, {11286099112296056199u64, 5038361112172116744u64, 10430122074u64}) + put(data, {15161477829153947404u64, 3305187319649924210u64, 90273130103u64}) + put(data, {423831848142641767u64, 11470175511099161552u64, 119179174563u64}) + put(data, {9707413321046312582u64, 7308362160352048610u64, 163621799460u64}) + put(data, {10969483299803835620u64, 10666410671225576634u64, 36396187106u64}) + put(data, {4326479556120930304u64, 2181639019945820785u64, 226578227281u64}) + put(data, {12876227232041795584u64, 4615749499734847918u64, 81118266888u64}) + put(data, {16967986827791171584u64, 14076159200958497580u64, 8250220281u64}) + put(data, {1288146316538413056u64, 5470405257862074105u64, 249763070119u64}) + put(data, {13715290452691779584u64, 4565741478181339543u64, 167296551263u64}) + put(data, {4683743612465315840u64, 8901832997861862329u64, 95247509341u64}) + put(data, {0u64, 14190141170191714122u64, 93482569333u64}) + put(data, {0u64, 4240772322245764096u64, 117769249094u64}) + put(data, {0u64, 4422842195340951552u64, 70229892728u64}) + put(data, {0u64, 15448426386733137920u64, 120239762755u64}) + put(data, {0u64, 9203504548935630848u64, 67837460872u64}) + put(data, {0u64, 5936377627571912704u64, 136498922981u64}) + put(data, {0u64, 468374361246531584u64, 229321811676u64}) + put(data, {0u64, 0u64, 220025390625u64}) + put(data, {0u64, 0u64, 33000000000u64}) + put(data, {16344805304534272784u64, 5374300u64, 0u64}) + put(data, {26429987091348271u64, 5374300886053671u64, 0u64}) + put(data, {8691371289609838059u64, 8020875056524075716u64, 291341u64}) + put(data, {12980168378493046550u64, 1400288714762747253u64, 13434812508u64}) + put(data, {14010125458129496139u64, 6136037711314764689u64, 92075909803u64}) + put(data, {17061829677031795106u64, 15735488086392394102u64, 171332635270u64}) + put(data, {2227928323072698520u64, 7735094782793634552u64, 134853022518u64}) + put(data, {7394752319272287289u64, 7273689191766726188u64, 54419320328u64}) + put(data, {8805771303577744757u64, 3410634565056431030u64, 8394307481u64}) + put(data, {16371027194302248385u64, 4600927904885215898u64, 153184890870u64}) + put(data, {9114324123731231602u64, 9154871331680374746u64, 246249416801u64}) + put(data, {14468189808746991893u64, 6117978272461042996u64, 97496286569u64}) + put(data, {2376054557800684348u64, 13116904339287496285u64, 105331656266u64}) + put(data, {12462238943546048571u64, 867037205615660831u64, 74711068809u64}) + put(data, {15334855370842605909u64, 1802487145191504830u64, 137047002181u64}) + put(data, {7211347914013798462u64, 17242009718457409007u64, 69097713023u64}) + put(data, {13763642332572548877u64, 13620802355488468049u64, 127934691219u64}) + put(data, {16868268377740071383u64, 4442227880594435745u64, 147738385175u64}) + put(data, {1120013377627684177u64, 17354849212854314181u64, 23240813655u64}) + put(data, {15898107650717274388u64, 18202319179831567886u64, 87940808260u64}) + put(data, {12281923376333274277u64, 17568634016348874558u64, 68986749699u64}) + put(data, {6383530489286615044u64, 7496925598312450672u64, 3952397558u64}) + put(data, {4253822060257126466u64, 601870379496813865u64, 246406409151u64}) + put(data, {17614372438391501998u64, 11995106565680728027u64, 191032627458u64}) + put(data, {4309951310554333450u64, 16331071694764184179u64, 2650256029u64}) + put(data, {13106185988973773020u64, 9665962217000524208u64, 157885309170u64}) + put(data, {2070134359761960547u64, 13682661374415474390u64, 242523992861u64}) + put(data, {9399359914137865875u64, 6940361789924260864u64, 29741738559u64}) + put(data, {17415053284723541264u64, 9658039831644010465u64, 63376237766u64}) + put(data, {4840502610448261120u64, 6843715893910236922u64, 198523563388u64}) + put(data, {5690599259712258048u64, 47089792870595660u64, 124370998582u64}) + put(data, {114769594245185536u64, 14510386192097156932u64, 54002552742u64}) + put(data, {3150620882578178048u64, 12059931208360040296u64, 166786609611u64}) + put(data, {5136918324969472000u64, 14877013468459184620u64, 203653770180u64}) + put(data, {7205759403792793600u64, 2397668560671695044u64, 196806484516u64}) + put(data, {0u64, 2195572305559232232u64, 36129977873u64}) + put(data, {0u64, 3261686279425953792u64, 17119022213u64}) + put(data, {0u64, 9333850662059900928u64, 133176816367u64}) + put(data, {0u64, 5036522340217782272u64, 239505989058u64}) + put(data, {0u64, 2800120215143186432u64, 194273030423u64}) + put(data, {0u64, 441634238459019264u64, 23151794821u64}) + put(data, {0u64, 720575940379279360u64, 133023941040u64}) + put(data, {0u64, 0u64, 176039062500u64}) + put(data, {0u64, 0u64, 228000000000u64}) + put(data, {98202693831717807u64, 82u64, 0u64}) + put(data, {12909287260170414079u64, 82005323578u64, 0u64}) + put(data, {11728631949380786233u64, 8218347283861607400u64, 4u64}) + put(data, {18263701925522197718u64, 17896200385973633643u64, 4445517498u64}) + put(data, {4868886449713321591u64, 16333242102094352209u64, 186970154966u64}) + put(data, {5870139507184082354u64, 9981905728606788815u64, 214885426828u64}) + put(data, {5978562500822661575u64, 15219470018924839012u64, 140541120193u64}) + put(data, {10821136839630268472u64, 17152070168529617370u64, 193825049122u64}) + put(data, {9490907630136752916u64, 17841343440958328027u64, 34929815586u64}) + put(data, {10564005678001613989u64, 17291078023923990493u64, 34967181165u64}) + put(data, {5754050547468481222u64, 16744804581790759223u64, 109937351217u64}) + put(data, {3833909949855542602u64, 5001622214111594905u64, 49907737675u64}) + put(data, {15446538552665967784u64, 9676746897435398146u64, 75271138483u64}) + put(data, {14924837848804399130u64, 8109025833995118532u64, 179524577500u64}) + put(data, {18140170340418344208u64, 5495826424046694744u64, 220439591171u64}) + put(data, {11380424819825208971u64, 7890288164365705852u64, 3297929347u64}) + put(data, {12416915664152252547u64, 8616438349039895217u64, 131427733378u64}) + put(data, {17195282241626289958u64, 15787154801788760618u64, 130467098058u64}) + put(data, {2649541045825281326u64, 12418659311480782502u64, 202855823376u64}) + put(data, {16577429864268509676u64, 4486988874116669987u64, 16673216870u64}) + put(data, {12051257060168107241u64, 4828971301551875409u64, 102243240154u64}) + put(data, {5363884561143470797u64, 14769106422014442226u64, 218261779058u64}) + put(data, {13054029903083620184u64, 7763933466423188156u64, 114800634863u64}) + put(data, {566314952158634945u64, 10449097116253839963u64, 239420883676u64}) + put(data, {1329472079642345682u64, 12870692502472900571u64, 220566446689u64}) + put(data, {15686237486658857211u64, 11597479481311003817u64, 97697721638u64}) + put(data, {1069073549290598129u64, 8294994869530047486u64, 38628700622u64}) + put(data, {4323599065125928507u64, 16879315829924478241u64, 206449672572u64}) + put(data, {17861823329752681586u64, 11873324837601439670u64, 124915029544u64}) + put(data, {17220149985412802078u64, 3277599055636107318u64, 40643654229u64}) + put(data, {4344934572159429184u64, 15363467897354242201u64, 85177679000u64}) + put(data, {2252927464837120000u64, 10351182204479784367u64, 152832855263u64}) + put(data, {10910018171964489728u64, 12811517584931924466u64, 223561138711u64}) + put(data, {3597328585515335680u64, 16988930699558748726u64, 23694513759u64}) + put(data, {6972808074239148032u64, 11683499918824718325u64, 95920971778u64}) + put(data, {2227030015734710272u64, 13119300691281647499u64, 2633363799u64}) + put(data, {1152921504606846976u64, 10125549106595354099u64, 87711198715u64}) + put(data, {0u64, 17505352699870800544u64, 251548907116u64}) + put(data, {0u64, 6756039242241163264u64, 108948967071u64}) + put(data, {0u64, 3537338758766526464u64, 159366245621u64}) + put(data, {0u64, 6522626374119718912u64, 245191759518u64}) + put(data, {0u64, 4733294203482669056u64, 158353592284u64}) + put(data, {0u64, 16997710893603094528u64, 220256592392u64}) + put(data, {0u64, 16717361816799281152u64, 8921447753u64}) + put(data, {0u64, 0u64, 73906250000u64}) + put(data, {0u64, 0u64, 16000000000u64}) + put(data, {17238287503805244910u64, 1251301u64, 0u64}) + put(data, {1434575446038410275u64, 1251301934489438u64, 0u64}) + put(data, {14009569747841241694u64, 3943737498063000362u64, 67833u64}) + put(data, {14938795732275951328u64, 2870731037991212489u64, 249213790438u64}) + put(data, {6687653542888983473u64, 7389433400402095883u64, 230155622641u64}) + put(data, {28238723295162625u64, 5675049236146197433u64, 241400581987u64}) + put(data, {4745270274832691214u64, 9953779846262904264u64, 99307645035u64}) + put(data, {926478968112308824u64, 12691978937179636241u64, 107539595486u64}) + put(data, {9213681606604198526u64, 15523327331528198029u64, 222688033556u64}) + put(data, {13346223820579313024u64, 15722603279568118520u64, 20841521260u64}) + put(data, {691642518601291257u64, 11838632364171816147u64, 108852324031u64}) + put(data, {1153720150033789192u64, 7832751832367143680u64, 191641773546u64}) + put(data, {12089015034721780810u64, 12167724027162940862u64, 234424614327u64}) + put(data, {3555868702841788854u64, 4108211144748152962u64, 183659613641u64}) + put(data, {11708406782758214328u64, 7530983398136343676u64, 201222706572u64}) + put(data, {9417497762905343943u64, 1117587133956542355u64, 140408255428u64}) + put(data, {12838336066957615287u64, 17134748625149490872u64, 196060584519u64}) + put(data, {13737708142128207419u64, 4039918359454207848u64, 71928876584u64}) + put(data, {11683434809834695853u64, 1830218764589441242u64, 40219004413u64}) + put(data, {2689114340106315837u64, 637895981480825742u64, 253099216358u64}) + put(data, {8859225263374261032u64, 8246879226348334620u64, 230034580410u64}) + put(data, {15761671984578600096u64, 12389239568142583275u64, 186447064218u64}) + put(data, {7087267079878005352u64, 14041257178803154398u64, 154671622022u64}) + put(data, {4601291730423121377u64, 16312515716494630702u64, 134761178076u64}) + put(data, {9559411037059581623u64, 17088522799596987756u64, 220884303248u64}) + put(data, {12969820289641388091u64, 3588932524637852678u64, 144926370677u64}) + put(data, {13628239920285957130u64, 107218049069817414u64, 117194556422u64}) + put(data, {8049893933765800625u64, 1596707240462008334u64, 6005812302u64}) + put(data, {10391755948840250234u64, 17461913142391587672u64, 78086557672u64}) + put(data, {14454650777462444512u64, 4366474266651610090u64, 232946612208u64}) + put(data, {14244638523341127254u64, 5539304013194805084u64, 240236707044u64}) + put(data, {12246016810439753984u64, 4762470619211987849u64, 228300286272u64}) + put(data, {9382741764551081984u64, 10835638458986644717u64, 64258174049u64}) + put(data, {4608696190291148800u64, 16141642290510052058u64, 97587401137u64}) + put(data, {1696483666416369664u64, 17390568670756355425u64, 177875040181u64}) + put(data, {15416683541605384192u64, 12536768491333867107u64, 181942744616u64}) + put(data, {7683140964294066176u64, 13145148522871947193u64, 40679619581u64}) + put(data, {4611686018427387904u64, 5665349945233068642u64, 253712599929u64}) + put(data, {0u64, 17074607537751066240u64, 121307119235u64}) + put(data, {0u64, 6241525660962062336u64, 131925616329u64}) + put(data, {0u64, 1142860629783085056u64, 201338353784u64}) + put(data, {0u64, 16287527416870469632u64, 120061954598u64}) + put(data, {0u64, 9028002014738513920u64, 38882948630u64}) + put(data, {0u64, 16217462258161156096u64, 22489408969u64}) + put(data, {0u64, 11529215046068469760u64, 201879150390u64}) + put(data, {0u64, 0u64, 54625000000u64}) + put(data, {0u64, 0u64, 64000000000u64}) + put(data, {1722045467931902045u64, 19u64, 0u64}) + put(data, {16095324008152856733u64, 19093352271u64, 0u64}) + put(data, {15216188060094280738u64, 646608198162977646u64, 1u64}) + put(data, {10793385929903030893u64, 12170458846894708007u64, 1035052700u64}) + put(data, {12613277226875940039u64, 1797330480103086687u64, 156659761896u64}) + put(data, {10644539625155600107u64, 10332188564497263448u64, 232097433480u64}) + put(data, {6181262895644173983u64, 7524259485079594225u64, 136560109064u64}) + put(data, {148914399627082292u64, 62681109059153749u64, 8407890924u64}) + put(data, {14379289774887985969u64, 13480636451804037081u64, 236003397949u64}) + put(data, {11743339675582627452u64, 6948168233012789004u64, 61730786766u64}) + put(data, {7841621929809463497u64, 12015502974041806055u64, 206376660954u64}) + put(data, {11168944680251236601u64, 7343801660689004040u64, 218651361721u64}) + put(data, {6178560202529287410u64, 13670580858640731144u64, 185398108285u64}) + put(data, {13295243308201596112u64, 5605073897566574851u64, 125741083673u64}) + put(data, {17040673746172470291u64, 15387788940505247559u64, 25303851664u64}) + put(data, {14957442487039409922u64, 17565181499678113030u64, 144834173709u64}) + put(data, {17778155426506992152u64, 1893743623847493029u64, 13952210397u64}) + put(data, {14945404984219733899u64, 10243498996716269784u64, 221102660047u64}) + put(data, {11590754866058681505u64, 5619675836950314139u64, 207555301193u64}) + put(data, {6020790784469412466u64, 10224869737511515088u64, 73304643237u64}) + put(data, {7934351824569522326u64, 2574495974386198538u64, 165554291299u64}) + put(data, {5038361112172116744u64, 7825756347302873178u64, 99139563706u64}) + put(data, {3305187319649924210u64, 12071550103794656887u64, 186424235101u64}) + put(data, {11470175511099161552u64, 7195875213867606691u64, 93654400042u64}) + put(data, {7308362160352048610u64, 18271364438406891044u64, 42390089176u64}) + put(data, {10666410671225576634u64, 16966521933952564706u64, 216990492650u64}) + put(data, {2181639019945820785u64, 289920862029570129u64, 234919756997u64}) + put(data, {4615749499734847918u64, 7804199568098625032u64, 197015716641u64}) + put(data, {14076159200958497580u64, 5758118571242446585u64, 33423066506u64}) + put(data, {5470405257862074105u64, 4030788293606375591u64, 138312148233u64}) + put(data, {4565741478181339543u64, 4387716460037196127u64, 9218509471u64}) + put(data, {8901832997861862329u64, 16807506478881285981u64, 159237858585u64}) + put(data, {14190141170191714122u64, 17033060604413529717u64, 25911136751u64}) + put(data, {4240772322245764096u64, 10498418508292170054u64, 239923364065u64}) + put(data, {4422842195340951552u64, 13237752038744465016u64, 225569120407u64}) + put(data, {15448426386733137920u64, 17737618428304633155u64, 151717619975u64}) + put(data, {9203504548935630848u64, 13546183833248825736u64, 7961558221u64}) + put(data, {5936377627571912704u64, 826778452978976229u64, 205734340097u64}) + put(data, {468374361246531584u64, 13728076626990147292u64, 1044819749u64}) + put(data, {0u64, 2794860281883592225u64, 37744200525u64}) + put(data, {0u64, 8680705720425908736u64, 77151509679u64}) + put(data, {0u64, 731520517439488000u64, 175470582000u64}) + put(data, {0u64, 13120812320768917504u64, 240039655806u64}) + put(data, {0u64, 2722954908557901824u64, 126711280661u64}) + put(data, {0u64, 6860847004205973504u64, 21147611681u64}) + put(data, {0u64, 6503197861922996224u64, 33371927261u64}) + put(data, {0u64, 9223372036854775808u64, 221352539062u64}) + put(data, {0u64, 0u64, 182500000000u64}) + put(data, {8020875056524075716u64, 291341u64, 0u64}) + put(data, {1400288714762747253u64, 291341434812508u64, 0u64}) + put(data, {6136037711314764689u64, 12005656413127238315u64, 15793u64}) + put(data, {15735488086392394102u64, 4821130826186787462u64, 177650827938u64}) + put(data, {7735094782793634552u64, 14377899467066168118u64, 162261354025u64}) + put(data, {7273689191766726188u64, 16575613239625444872u64, 41779427491u64}) + put(data, {3410634565056431030u64, 4317827099179284377u64, 163898565794u64}) + put(data, {4600927904885215898u64, 1242354770412171254u64, 162234069876u64}) + put(data, {9154871331680374746u64, 994838588328896609u64, 116067348187u64}) + put(data, {6117978272461042996u64, 17283309862013060457u64, 219053930307u64}) + put(data, {13116904339287496285u64, 124242522249856586u64, 67936930105u64}) + put(data, {867037205615660831u64, 11564608014666985609u64, 57006735200u64}) + put(data, {1802487145191504830u64, 12401028575581654085u64, 96626918656u64}) + put(data, {17242009718457409007u64, 2490725392961465727u64, 672261106u64}) + put(data, {13620802355488468049u64, 1949482237120640915u64, 242135022494u64}) + put(data, {4442227880594435745u64, 15410502396166200087u64, 158105681643u64}) + put(data, {17354849212854314181u64, 15694919529799920727u64, 235835405008u64}) + put(data, {18202319179831567886u64, 10324869370171768388u64, 208850823292u64}) + put(data, {17568634016348874558u64, 1631866459122189059u64, 124559712290u64}) + put(data, {7496925598312450672u64, 172020494461226230u64, 34088463658u64}) + put(data, {601870379496813865u64, 12734610307908856767u64, 42009325249u64}) + put(data, {11995106565680728027u64, 1467513250829340930u64, 193690344608u64}) + put(data, {16331071694764184179u64, 13558759428494307997u64, 160079554052u64}) + put(data, {9665962217000524208u64, 7915355143999496434u64, 4735021821u64}) + put(data, {13682661374415474390u64, 2876370200608797469u64, 253429092262u64}) + put(data, {6940361789924260864u64, 343685370404989503u64, 166155928341u64}) + put(data, {9658039831644010465u64, 4837266557407634630u64, 21018631221u64}) + put(data, {6843715893910236922u64, 9622591415747161468u64, 53262228745u64}) + put(data, {47089792870595660u64, 16503783814424220982u64, 9521641725u64}) + put(data, {14510386192097156932u64, 5377083431343591334u64, 253894671913u64}) + put(data, {12059931208360040296u64, 16508482371299291595u64, 41291492276u64}) + put(data, {14877013468459184620u64, 10515883558812249028u64, 180894926622u64}) + put(data, {2397668560671695044u64, 63492062913405476u64, 30570067190u64}) + put(data, {2195572305559232232u64, 11571919759617799697u64, 246003441911u64}) + put(data, {3261686279425953792u64, 2956602334970088581u64, 247627315027u64}) + put(data, {9333850662059900928u64, 13604736747717849839u64, 83160277733u64}) + put(data, {5036522340217782272u64, 16573540719338151362u64, 229737514256u64}) + put(data, {2800120215143186432u64, 12620703004601168151u64, 16898453442u64}) + put(data, {441634238459019264u64, 14649407809089591941u64, 194684169680u64}) + put(data, {720575940379279360u64, 11290375247898624432u64, 208794145988u64}) + put(data, {0u64, 11020319450292874212u64, 196612052468u64}) + put(data, {0u64, 8754634933362354176u64, 244597412714u64}) + put(data, {0u64, 12976319450332528640u64, 106474589710u64}) + put(data, {0u64, 17447331119627239424u64, 14703447686u64}) + put(data, {0u64, 3665184902673858560u64, 134945821715u64}) + put(data, {0u64, 12949678516038795264u64, 19198690071u64}) + put(data, {0u64, 72057594037927936u64, 23702003479u64}) + put(data, {0u64, 0u64, 23003906250u64}) + put(data, {0u64, 0u64, 202000000000u64}) + put(data, {8218347283861607400u64, 4u64, 0u64}) + put(data, {17896200385973633643u64, 4445517498u64, 0u64}) + put(data, {16333242102094352209u64, 4445517498970154966u64, 0u64}) + put(data, {9981905728606788815u64, 9413159735776077452u64, 240991986u64}) + put(data, {15219470018924839012u64, 14279163482889998017u64, 242510288411u64}) + put(data, {17152070168529617370u64, 8693044629541194274u64, 27774075003u64}) + put(data, {17841343440958328027u64, 11863110253260222498u64, 123471250893u64}) + put(data, {17291078023923990493u64, 8319293368489531245u64, 205643100495u64}) + put(data, {16744804581790759223u64, 3376307525676489265u64, 79450989797u64}) + put(data, {5001622214111594905u64, 13205662254759912523u64, 229183029997u64}) + put(data, {9676746897435398146u64, 5276250334231686323u64, 237715880385u64}) + put(data, {8109025833995118532u64, 13790198520922745052u64, 193286026103u64}) + put(data, {5495826424046694744u64, 14195535250150996227u64, 119747568159u64}) + put(data, {7890288164365705852u64, 16425228796427004035u64, 31769541507u64}) + put(data, {8616438349039895217u64, 4295900841296269186u64, 131890413437u64}) + put(data, {15787154801788760618u64, 4533952595483946442u64, 125232881251u64}) + put(data, {12418659311480782502u64, 12885038019373447184u64, 99245786062u64}) + put(data, {4486988874116669987u64, 12140736240487831910u64, 206698499310u64}) + put(data, {4828971301551875409u64, 6927124077155322074u64, 238658150630u64}) + put(data, {14769106422014442226u64, 12477788342407819890u64, 230375520148u64}) + put(data, {7763933466423188156u64, 7980854329409711087u64, 148676422261u64}) + put(data, {10449097116253839963u64, 2062671021810827996u64, 117432642980u64}) + put(data, {12870692502472900571u64, 2739521363598172769u64, 164111817620u64}) + put(data, {11597479481311003817u64, 12897585686593465638u64, 148148509750u64}) + put(data, {8294994869530047486u64, 1127632646629044686u64, 54699179521u64}) + put(data, {16879315829924478241u64, 4833775019274666364u64, 1061129088u64}) + put(data, {11873324837601439670u64, 15867662672939849256u64, 128262039468u64}) + put(data, {3277599055636107318u64, 2092350330982953557u64, 172860187717u64}) + put(data, {15363467897354242201u64, 13330062299842493592u64, 69113426538u64}) + put(data, {10351182204479784367u64, 4479193352178519263u64, 106722624125u64}) + put(data, {12811517584931924466u64, 3149393938889064983u64, 125242817558u64}) + put(data, {16988930699558748726u64, 9736379904070620767u64, 22170728987u64}) + put(data, {11683499918824718325u64, 3816238703055069186u64, 27527810212u64}) + put(data, {13119300691281647499u64, 11598915938798661975u64, 164206878714u64}) + put(data, {10125549106595354099u64, 17821633264606555643u64, 250628778492u64}) + put(data, {17505352699870800544u64, 2514623558764574316u64, 252966112675u64}) + put(data, {6756039242241163264u64, 4976730480406253215u64, 163136318016u64}) + put(data, {3537338758766526464u64, 17276563697191611637u64, 64269789099u64}) + put(data, {6522626374119718912u64, 12524734095940998814u64, 171936564394u64}) + put(data, {4733294203482669056u64, 15331551308930355164u64, 170678967195u64}) + put(data, {16997710893603094528u64, 15417115581125943816u64, 155831125061u64}) + put(data, {16717361816799281152u64, 6010750237807115593u64, 69835763510u64}) + put(data, {0u64, 5624630987553628432u64, 54325843423u64}) + put(data, {0u64, 14881848243837640704u64, 223304911856u64}) + put(data, {0u64, 15281613886881529856u64, 240806746609u64}) + put(data, {0u64, 14057902358273196032u64, 241828417948u64}) + put(data, {0u64, 16075318494433902592u64, 156762080413u64}) + put(data, {0u64, 13891916000577716224u64, 157871444761u64}) + put(data, {0u64, 7205759403792793600u64, 25753082275u64}) + put(data, {0u64, 0u64, 163390625000u64}) + put(data, {0u64, 0u64, 232000000000u64}) + put(data, {3943737498063000362u64, 67833u64, 0u64}) + put(data, {2870731037991212489u64, 67833213790438u64, 0u64}) + put(data, {7389433400402095883u64, 4535831408134330609u64, 3677u64}) + put(data, {5675049236146197433u64, 6204770794376564579u64, 93245887913u64}) + put(data, {9953779846262904264u64, 13869812122751887467u64, 169336361298u64}) + put(data, {12691978937179636241u64, 14253229412394467550u64, 82751884021u64}) + put(data, {15523327331528198029u64, 12776557610216045332u64, 245772669114u64}) + put(data, {15722603279568118520u64, 16493640728678654060u64, 186692618575u64}) + put(data, {11838632364171816147u64, 9434398296825833151u64, 79894122055u64}) + put(data, {7832751832367143680u64, 8773374058285327850u64, 71511439756u64}) + put(data, {12167724027162940862u64, 12932015276748029367u64, 140475605560u64}) + put(data, {4108211144748152962u64, 16293958583527755209u64, 56701045952u64}) + put(data, {7530983398136343676u64, 13511893936143127948u64, 192883297264u64}) + put(data, {1117587133956542355u64, 18409936402005226436u64, 240732481237u64}) + put(data, {17134748625149490872u64, 2189663026458466887u64, 213998004652u64}) + put(data, {4039918359454207848u64, 9497725274248154664u64, 172118701870u64}) + put(data, {1830218764589441242u64, 14766925481127792125u64, 46514872718u64}) + put(data, {637895981480825742u64, 6982373971809635814u64, 142800516634u64}) + put(data, {8246879226348334620u64, 8616702383006884794u64, 26378515251u64}) + put(data, {12389239568142583275u64, 3059473300040871066u64, 51467112372u64}) + put(data, {14041257178803154398u64, 17123843157031495558u64, 180165854379u64}) + put(data, {16312515716494630702u64, 11210627174210626524u64, 171928285397u64}) + put(data, {17088522799596987756u64, 15868067138625928592u64, 213607729316u64}) + put(data, {3588932524637852678u64, 4467869511636937589u64, 164860209643u64}) + put(data, {107218049069817414u64, 10052108125844341766u64, 235242203691u64}) + put(data, {1596707240462008334u64, 7470588003218451534u64, 43544925873u64}) + put(data, {17461913142391587672u64, 2613527085490786280u64, 177404981387u64}) + put(data, {4366474266651610090u64, 3632919450036549616u64, 139141679587u64}) + put(data, {5539304013194805084u64, 179367907231218916u64, 227196940958u64}) + put(data, {4762470619211987849u64, 13553068184555874624u64, 158009723553u64}) + put(data, {10835638458986644717u64, 8798774862365584481u64, 161734713298u64}) + put(data, {16141642290510052058u64, 910911255817064881u64, 210476982541u64}) + put(data, {17390568670756355425u64, 2304331144765093813u64, 13049380598u64}) + put(data, {12536768491333867107u64, 12248937023083640360u64, 246124918041u64}) + put(data, {13145148522871947193u64, 10206039550662130685u64, 25664016206u64}) + put(data, {5665349945233068642u64, 12267881323837852537u64, 78553270512u64}) + put(data, {17074607537751066240u64, 2858642007937891971u64, 240665043179u64}) + put(data, {6241525660962062336u64, 14171330289750320841u64, 235154967293u64}) + put(data, {1142860629783085056u64, 6601103619749017720u64, 253768229354u64}) + put(data, {16287527416870469632u64, 4919573414486739494u64, 234357846544u64}) + put(data, {9028002014738513920u64, 3401998285294974486u64, 16266690609u64}) + put(data, {16217462258161156096u64, 10799436256515532233u64, 49184422696u64}) + put(data, {11529215046068469760u64, 10083786644665753398u64, 40585438612u64}) + put(data, {0u64, 6481194517685688896u64, 148546643169u64}) + put(data, {0u64, 15104161756860547072u64, 225351346258u64}) + put(data, {0u64, 9556039274244079616u64, 82818798249u64}) + put(data, {0u64, 1376343134954323968u64, 169518033927u64}) + put(data, {0u64, 15682488278596976640u64, 7074611710u64}) + put(data, {0u64, 1506454075355430912u64, 254850149393u64}) + put(data, {0u64, 1152921504606846976u64, 17081665039u64}) + put(data, {0u64, 0u64, 15062500000u64}) + put(data, {0u64, 0u64, 160000000000u64}) + put(data, {12170458846894708007u64, 1035052700u64, 0u64}) + put(data, {1797330480103086687u64, 1035052700659761896u64, 0u64}) + put(data, {10332188564497263448u64, 6172559441576707976u64, 56110319u64}) + put(data, {7524259485079594225u64, 15083329738554729992u64, 239334615117u64}) + put(data, {62681109059153749u64, 10013126833549229036u64, 77817668943u64}) + put(data, {13480636451804037081u64, 5817156823499936061u64, 79542812693u64}) + put(data, {6948168233012789004u64, 5282692560913632718u64, 21315348703u64}) + put(data, {12015502974041806055u64, 10252307034225766362u64, 223286375337u64}) + put(data, {7343801660689004040u64, 17981881283247669689u64, 169555778677u64}) + put(data, {13670580858640731144u64, 11689290159733383293u64, 117974799737u64}) + put(data, {5605073897566574851u64, 5530668968487988249u64, 121633677689u64}) + put(data, {15387788940505247559u64, 10083765740821947024u64, 121299818165u64}) + put(data, {17565181499678113030u64, 2798423656816843533u64, 181546642036u64}) + put(data, {1893743623847493029u64, 7614494481582904797u64, 116151702850u64}) + put(data, {10243498996716269784u64, 17811318500083423695u64, 66412782572u64}) + put(data, {5619675836950314139u64, 11641467412200329033u64, 236965553510u64}) + put(data, {10224869737511515088u64, 17733593025296340645u64, 102631085212u64}) + put(data, {2574495974386198538u64, 3689424000190644835u64, 156961340004u64}) + put(data, {7825756347302873178u64, 14966634145516728506u64, 100200004075u64}) + put(data, {12071550103794656887u64, 14171681941562070109u64, 235811342862u64}) + put(data, {7195875213867606691u64, 8130575762882608170u64, 14768248417u64}) + put(data, {18271364438406891044u64, 5234550794400656856u64, 97440759395u64}) + put(data, {16966521933952564706u64, 3020576149360486378u64, 99283765567u64}) + put(data, {289920862029570129u64, 3038675756589057221u64, 63163745761u64}) + put(data, {7804199568098625032u64, 15470260187120878369u64, 225164726942u64}) + put(data, {5758118571242446585u64, 3497929414841828746u64, 158838644485u64}) + put(data, {4030788293606375591u64, 9935840636861015305u64, 5189623133u64}) + put(data, {4387716460037196127u64, 3647355485153741471u64, 93538623000u64}) + put(data, {16807506478881285981u64, 766100215038272793u64, 24197723537u64}) + put(data, {17033060604413529717u64, 16128087474216800751u64, 145041530375u64}) + put(data, {10498418508292170054u64, 16216631732633731297u64, 7874305373u64}) + put(data, {13237752038744465016u64, 13760220872779997335u64, 93879105367u64}) + put(data, {17737618428304633155u64, 3826276262374222087u64, 87745943068u64}) + put(data, {13546183833248825736u64, 14938032745839181005u64, 28207422851u64}) + put(data, {826778452978976229u64, 14479259995009508865u64, 131809792377u64}) + put(data, {13728076626990147292u64, 2372033248156102437u64, 121784922257u64}) + put(data, {2794860281883592225u64, 792005346826701645u64, 145128588180u64}) + put(data, {8680705720425908736u64, 16278924527931792559u64, 148042934695u64}) + put(data, {731520517439488000u64, 17442516423538940144u64, 167882482266u64}) + put(data, {13120812320768917504u64, 13844184233048446u64, 90945560710u64}) + put(data, {2722954908557901824u64, 13486193870480782357u64, 134000750494u64}) + put(data, {6860847004205973504u64, 11931315179184648737u64, 158731088034u64}) + put(data, {6503197861922996224u64, 16492562205587485405u64, 162646797891u64}) + put(data, {9223372036854775808u64, 12128987217680380854u64, 67894063588u64}) + put(data, {0u64, 10568123814189138176u64, 228657513714u64}) + put(data, {0u64, 17007583519117541376u64, 242572899139u64}) + put(data, {0u64, 143791533903052800u64, 67921982950u64}) + put(data, {0u64, 12398714235792654336u64, 230007794954u64}) + put(data, {0u64, 9659957317919047680u64, 10672135645u64}) + put(data, {0u64, 9412523221204336640u64, 221523667335u64}) + put(data, {0u64, 4611686018427387904u64, 135510253906u64}) + put(data, {0u64, 0u64, 82250000000u64}) + put(data, {0u64, 0u64, 128000000000u64}) + put(data, {12005656413127238315u64, 15793u64, 0u64}) + put(data, {4821130826186787462u64, 15793650827938u64, 0u64}) + put(data, {14377899467066168118u64, 3237900842885170729u64, 856u64}) + put(data, {16575613239625444872u64, 7515893506498066595u64, 88175526956u64}) + put(data, {4317827099179284377u64, 7300206309181072546u64, 44407437403u64}) + put(data, {1242354770412171254u64, 5999737279837044u64, 91395744977u64}) + put(data, {994838588328896609u64, 7556839307242450651u64, 209000325246u64}) + put(data, {17283309862013060457u64, 12946035041643640643u64, 126409657079u64}) + put(data, {124242522249856586u64, 15885877642352740665u64, 247701805965u64}) + put(data, {11564608014666985609u64, 10770818348246089568u64, 141861175152u64}) + put(data, {12401028575581654085u64, 11635415503599551744u64, 112583887232u64}) + put(data, {2490725392961465727u64, 6248053924100826098u64, 128630757138u64}) + put(data, {1949482237120640915u64, 16894170802729859998u64, 18338707681u64}) + put(data, {15410502396166200087u64, 6143589029651889899u64, 225915834834u64}) + put(data, {15694919529799920727u64, 11812087701837886160u64, 210333044628u64}) + put(data, {10324869370171768388u64, 7306705080150829180u64, 148640334557u64}) + put(data, {1631866459122189059u64, 1485332570280714274u64, 221396097276u64}) + put(data, {172020494461226230u64, 18042602303295630634u64, 252080520039u64}) + put(data, {12734610307908856767u64, 13397029889257074369u64, 103978091430u64}) + put(data, {1467513250829340930u64, 9948104869613411488u64, 166726254445u64}) + put(data, {13558759428494307997u64, 10836066241170646532u64, 109539287845u64}) + put(data, {7915355143999496434u64, 18330574781234459389u64, 37587424327u64}) + put(data, {2876370200608797469u64, 666297360208433062u64, 71993702450u64}) + put(data, {343685370404989503u64, 5035352224889324309u64, 50036120052u64}) + put(data, {4837266557407634630u64, 1341745796439923765u64, 244272966991u64}) + put(data, {9622591415747161468u64, 6846932182653803785u64, 79072736185u64}) + put(data, {16503783814424220982u64, 6727685027257825533u64, 185371172937u64}) + put(data, {5377083431343591334u64, 2168538874806877737u64, 73364708536u64}) + put(data, {16508482371299291595u64, 17694936100676971444u64, 184117556727u64}) + put(data, {10515883558812249028u64, 2163944241059563294u64, 247959244408u64}) + put(data, {63492062913405476u64, 6727780864524301558u64, 120117307652u64}) + put(data, {11571919759617799697u64, 8599551977795002615u64, 4364713731u64}) + put(data, {2956602334970088581u64, 15428264807806859091u64, 3466182646u64}) + put(data, {13604736747717849839u64, 2126771385339683557u64, 246836367911u64}) + put(data, {16573540719338151362u64, 15094316562082972944u64, 39115292507u64}) + put(data, {12620703004601168151u64, 8111300598225956802u64, 91818264540u64}) + put(data, {14649407809089591941u64, 9481215200564260304u64, 220439714486u64}) + put(data, {11290375247898624432u64, 16836674128623424708u64, 182513977705u64}) + put(data, {11020319450292874212u64, 7087243115299722740u64, 105912717933u64}) + put(data, {8754634933362354176u64, 2343560867338408810u64, 109384200219u64}) + put(data, {12976319450332528640u64, 3431385749090422286u64, 27127044689u64}) + put(data, {17447331119627239424u64, 3504545517469224582u64, 81186015794u64}) + put(data, {3665184902673858560u64, 3333759805712094227u64, 50189981793u64}) + put(data, {12949678516038795264u64, 3595183476205994775u64, 97180723481u64}) + put(data, {72057594037927936u64, 14191566632569921303u64, 25194895286u64}) + put(data, {0u64, 12917427671358095562u64, 182769326368u64}) + put(data, {0u64, 3883793922738316288u64, 32700255157u64}) + put(data, {0u64, 7857281689266421760u64, 181210540890u64}) + put(data, {0u64, 15987081651486195712u64, 90425944093u64}) + put(data, {0u64, 16827562156399525888u64, 29866661432u64}) + put(data, {0u64, 7012737938513461248u64, 56912223972u64}) + put(data, {0u64, 7385903388887613440u64, 228380161285u64}) + put(data, {0u64, 0u64, 5400390625u64}) + put(data, {0u64, 0u64, 225000000000u64}) + put(data, {9413159735776077452u64, 240991986u64, 0u64}) + put(data, {14279163482889998017u64, 240991986510288411u64, 0u64}) + put(data, {8693044629541194274u64, 14135788013842776187u64, 13064201u64}) + put(data, {11863110253260222498u64, 13284322918167594445u64, 9766302603u64}) + put(data, {8319293368489531245u64, 7264587765474046287u64, 139720144588u64}) + put(data, {3376307525676489265u64, 16176482219778368741u64, 204393814091u64}) + put(data, {13205662254759912523u64, 5401983818872095469u64, 75876928858u64}) + put(data, {5276250334231686323u64, 11208857446851049921u64, 90292842129u64}) + put(data, {13790198520922745052u64, 13794690008281035639u64, 145607633379u64}) + put(data, {14195535250150996227u64, 14519782740993303071u64, 227747811643u64}) + put(data, {16425228796427004035u64, 10885858587044789123u64, 59787118999u64}) + put(data, {4295900841296269186u64, 8710500938899914621u64, 151590123576u64}) + put(data, {4533952595483946442u64, 1284182587483102819u64, 56472197202u64}) + put(data, {12885038019373447184u64, 10346074482131502030u64, 82069615677u64}) + put(data, {12140736240487831910u64, 9429804686255246574u64, 61560861821u64}) + put(data, {6927124077155322074u64, 6412022633845121254u64, 125511190736u64}) + put(data, {12477788342407819890u64, 8892351297529018260u64, 208347596443u64}) + put(data, {7980854329409711087u64, 14098160105983060597u64, 155482055329u64}) + put(data, {2062671021810827996u64, 13793833029739474340u64, 161764262790u64}) + put(data, {2739521363598172769u64, 16367653765996977044u64, 134747765186u64}) + put(data, {12897585686593465638u64, 10684788343333772342u64, 194887292288u64}) + put(data, {1127632646629044686u64, 13272681218705145345u64, 128579223536u64}) + put(data, {4833775019274666364u64, 11093568615497829248u64, 240719513490u64}) + put(data, {15867662672939849256u64, 12488220765137758124u64, 146601383559u64}) + put(data, {2092350330982953557u64, 3727114642519696453u64, 135676987804u64}) + put(data, {13330062299842493592u64, 11549865375695057514u64, 156202047289u64}) + put(data, {4479193352178519263u64, 11292809154908783229u64, 57626119456u64}) + put(data, {3149393938889064983u64, 17723904861837310998u64, 32612184410u64}) + put(data, {9736379904070620767u64, 14877674388187150875u64, 90960814807u64}) + put(data, {3816238703055069186u64, 12178961950105734308u64, 215806520344u64}) + put(data, {11598915938798661975u64, 4540604068069253114u64, 24660222850u64}) + put(data, {17821633264606555643u64, 13832478722153359868u64, 130246146639u64}) + put(data, {2514623558764574316u64, 1308046668730371491u64, 79749860174u64}) + put(data, {4976730480406253215u64, 18400531023544756800u64, 78070909351u64}) + put(data, {17276563697191611637u64, 9789823458621466539u64, 167997494785u64}) + put(data, {12524734095940998814u64, 1924870562610267306u64, 1530707393u64}) + put(data, {15331551308930355164u64, 5290016144582400923u64, 193104347442u64}) + put(data, {15417115581125943816u64, 15162883663174059077u64, 50286772349u64}) + put(data, {6010750237807115593u64, 8078086116520046390u64, 125821981570u64}) + put(data, {5624630987553628432u64, 15731407332173190623u64, 130437913925u64}) + put(data, {14881848243837640704u64, 5346389182763011056u64, 69852801300u64}) + put(data, {15281613886881529856u64, 6368422217216252401u64, 20289828338u64}) + put(data, {14057902358273196032u64, 2961453088119116188u64, 242345232860u64}) + put(data, {16075318494433902592u64, 10932141691610170525u64, 220160540693u64}) + put(data, {13891916000577716224u64, 11034016191361782553u64, 21592632588u64}) + put(data, {7205759403792793600u64, 5455325785621453219u64, 12598155216u64}) + put(data, {0u64, 7735615202566149352u64, 208295733803u64}) + put(data, {0u64, 7502396497775759360u64, 43419348540u64}) + put(data, {0u64, 1601286435751591936u64, 60406705729u64}) + put(data, {0u64, 11449383158571597824u64, 65086805911u64}) + put(data, {0u64, 13043944595690356736u64, 151620672304u64}) + put(data, {0u64, 7773494431818186752u64, 48707113653u64}) + put(data, {0u64, 9943947977234055168u64, 181421401977u64}) + put(data, {0u64, 0u64, 121539062500u64}) + put(data, {0u64, 0u64, 228000000000u64}) + put(data, {4535831408134330609u64, 3677u64, 0u64}) + put(data, {6204770794376564579u64, 3677245887913u64, 0u64}) + put(data, {13869812122751887467u64, 6343817245135589714u64, 199u64}) + put(data, {14253229412394467550u64, 17549323075660516085u64, 199343899021u64}) + put(data, {12776557610216045332u64, 3948641822109421754u64, 141951350710u64}) + put(data, {16493640728678654060u64, 1750739713693534543u64, 182214056302u64}) + put(data, {9434398296825833151u64, 962163898128633415u64, 110094907790u64}) + put(data, {8773374058285327850u64, 7967320249386531212u64, 142052159009u64}) + put(data, {12932015276748029367u64, 3018466665533383224u64, 33431909296u64}) + put(data, {16293958583527755209u64, 15076865731854945472u64, 176163631405u64}) + put(data, {13511893936143127948u64, 691187172844604400u64, 45817318529u64}) + put(data, {18409936402005226436u64, 13274492813370992341u64, 129037469331u64}) + put(data, {2189663026458466887u64, 6364168818499152300u64, 147719611697u64}) + put(data, {9497725274248154664u64, 17599380787401914158u64, 49345002282u64}) + put(data, {14766925481127792125u64, 3782323149461692814u64, 42954064344u64}) + put(data, {6982373971809635814u64, 14470163442442237466u64, 216205040148u64}) + put(data, {8616702383006884794u64, 476109872130437939u64, 20784429132u64}) + put(data, {3059473300040871066u64, 16330548844673355700u64, 76025809967u64}) + put(data, {17123843157031495558u64, 14089158961463739563u64, 47885280826u64}) + put(data, {11210627174210626524u64, 13385510793074798805u64, 58763774837u64}) + put(data, {15868067138625928592u64, 1549401308746959012u64, 117725629994u64}) + put(data, {4467869511636937589u64, 4607384943843027435u64, 42083993213u64}) + put(data, {10052108125844341766u64, 5157353797716093483u64, 125249766838u64}) + put(data, {7470588003218451534u64, 10846828782671550129u64, 182279580709u64}) + put(data, {2613527085490786280u64, 9915857350819131531u64, 37588007766u64}) + put(data, {3632919450036549616u64, 1673544973504317923u64, 86537539704u64}) + put(data, {179367907231218916u64, 14780986291622785694u64, 120090723054u64}) + put(data, {13553068184555874624u64, 8168111319515466401u64, 238801278872u64}) + put(data, {8798774862365584481u64, 16345760387859734482u64, 152442794201u64}) + put(data, {910911255817064881u64, 3177475373321281805u64, 217886105446u64}) + put(data, {2304331144765093813u64, 2558676822419554038u64, 102172251285u64}) + put(data, {12248937023083640360u64, 8813474062662382873u64, 149138706148u64}) + put(data, {10206039550662130685u64, 5426294560236228430u64, 228477779386u64}) + put(data, {12267881323837852537u64, 9919177474128333040u64, 186294160017u64}) + put(data, {2858642007937891971u64, 6197383943089627371u64, 145537719688u64}) + put(data, {14171330289750320841u64, 13673239314867423997u64, 136335960856u64}) + put(data, {6601103619749017720u64, 9309584098968723946u64, 24741227788u64}) + put(data, {4919573414486739494u64, 4647101757759615504u64, 12504673565u64}) + put(data, {3401998285294974486u64, 1405809295505096753u64, 29251919891u64}) + put(data, {10799436256515532233u64, 11332704079573859112u64, 19076209074u64}) + put(data, {10083786644665753398u64, 2960072434514044308u64, 178614347119u64}) + put(data, {6481194517685688896u64, 3887266602785432801u64, 111160465848u64}) + put(data, {15104161756860547072u64, 14545546084687849554u64, 184210729144u64}) + put(data, {9556039274244079616u64, 4617763804182385321u64, 184788515633u64}) + put(data, {1376343134954323968u64, 7857823815580249095u64, 49250329477u64}) + put(data, {15682488278596976640u64, 10939326736548364798u64, 133425973482u64}) + put(data, {1506454075355430912u64, 12262012446566951953u64, 234593022090u64}) + put(data, {1152921504606846976u64, 12555024338687723023u64, 138664725026u64}) + put(data, {0u64, 3332969632922829472u64, 34680609233u64}) + put(data, {0u64, 15535060143360327680u64, 209180680645u64}) + put(data, {0u64, 15794322927987458048u64, 197842157297u64}) + put(data, {0u64, 10571474314433921024u64, 241856211961u64}) + put(data, {0u64, 16679514427547975680u64, 249573080770u64}) + put(data, {0u64, 16925653299565166592u64, 194904198288u64}) + put(data, {0u64, 16717361816799281152u64, 144917541503u64}) + put(data, {0u64, 0u64, 127906250000u64}) + put(data, {0u64, 0u64, 16000000000u64}) + put(data, {6172559441576707976u64, 56110319u64, 0u64}) + put(data, {15083329738554729992u64, 56110319334615117u64, 0u64}) + put(data, {10013126833549229036u64, 9335385384027907407u64, 3041746u64}) + put(data, {5817156823499936061u64, 13237828406194798613u64, 210506072255u64}) + put(data, {5282692560913632718u64, 15667486867836528863u64, 191717624115u64}) + put(data, {10252307034225766362u64, 17982325043592934313u64, 51849336164u64}) + put(data, {17981881283247669689u64, 17159117626917379189u64, 100974823793u64}) + put(data, {11689290159733383293u64, 8336208968408929657u64, 113930197630u64}) + put(data, {5530668968487988249u64, 12767090573379150201u64, 126451906793u64}) + put(data, {10083765740821947024u64, 14736070002412246709u64, 233692105366u64}) + put(data, {2798423656816843533u64, 9697296975344560756u64, 150798843955u64}) + put(data, {7614494481582904797u64, 7291706381199103298u64, 51525691522u64}) + put(data, {17811318500083423695u64, 18098546597780825068u64, 130395284194u64}) + put(data, {11641467412200329033u64, 132913902678533478u64, 226981124177u64}) + put(data, {17733593025296340645u64, 1879347741692007580u64, 81007205277u64}) + put(data, {3689424000190644835u64, 4056624629214083684u64, 157101879645u64}) + put(data, {14966634145516728506u64, 14713227692042795499u64, 93219910061u64}) + put(data, {14171681941562070109u64, 7366415124022528526u64, 173797605671u64}) + put(data, {8130575762882608170u64, 825770353378039393u64, 39399334164u64}) + put(data, {5234550794400656856u64, 10244023944395357795u64, 20044765100u64}) + put(data, {3020576149360486378u64, 14302658294713551167u64, 172555329650u64}) + put(data, {3038675756589057221u64, 14246653166206862817u64, 114775348659u64}) + put(data, {15470260187120878369u64, 12404486258134291102u64, 179772312615u64}) + put(data, {3497929414841828746u64, 8887442218637942533u64, 39672448547u64}) + put(data, {9935840636861015305u64, 1186724038081863005u64, 35481789208u64}) + put(data, {3647355485153741471u64, 211331772484951576u64, 24064332439u64}) + put(data, {766100215038272793u64, 6311919513247413649u64, 151011456318u64}) + put(data, {16128087474216800751u64, 8131780018703965703u64, 62342169842u64}) + put(data, {16216631732633731297u64, 2262544347226725725u64, 242440824678u64}) + put(data, {13760220872779997335u64, 15318188749880522583u64, 102122652774u64}) + put(data, {3826276262374222087u64, 1073117094162650652u64, 102830400676u64}) + put(data, {14938032745839181005u64, 4447950380665871747u64, 164058173794u64}) + put(data, {14479259995009508865u64, 5373227185066463609u64, 98241123873u64}) + put(data, {2372033248156102437u64, 6739731406934274193u64, 33291283229u64}) + put(data, {792005346826701645u64, 12328812617001239444u64, 29365361571u64}) + put(data, {16278924527931792559u64, 3246111484407310759u64, 163668346271u64}) + put(data, {17442516423538940144u64, 3250825415176839770u64, 159175972056u64}) + put(data, {13844184233048446u64, 16146270540000862342u64, 216176227598u64}) + put(data, {13486193870480782357u64, 15686773375425916830u64, 14875291079u64}) + put(data, {11931315179184648737u64, 11920791905793880226u64, 199850381688u64}) + put(data, {16492562205587485405u64, 1853290561644080707u64, 120646227424u64}) + put(data, {12128987217680380854u64, 12157689141506159076u64, 224100467082u64}) + put(data, {10568123814189138176u64, 18100318838862562546u64, 138659069648u64}) + put(data, {17007583519117541376u64, 7171257882533475139u64, 208981220250u64}) + put(data, {143791533903052800u64, 14477550873015039462u64, 154388754668u64}) + put(data, {12398714235792654336u64, 8109481182495403274u64, 236784829605u64}) + put(data, {9659957317919047680u64, 14565395719337663965u64, 165439615855u64}) + put(data, {9412523221204336640u64, 1860318978161305991u64, 111789591684u64}) + put(data, {4611686018427387904u64, 16268646275151585618u64, 132100848093u64}) + put(data, {0u64, 13759019338835519104u64, 221881925081u64}) + put(data, {0u64, 17003783176010661888u64, 217745877932u64}) + put(data, {0u64, 18357489540307877888u64, 172921776932u64}) + put(data, {0u64, 905481790074912768u64, 36995161502u64}) + put(data, {0u64, 3638882110636294144u64, 158049086266u64}) + put(data, {0u64, 9011702854368362496u64, 58197264194u64}) + put(data, {0u64, 11529215046068469760u64, 66488525390u64}) + put(data, {0u64, 0u64, 78625000000u64}) + put(data, {0u64, 0u64, 64000000000u64}) + put(data, {3237900842885170729u64, 856u64, 0u64}) + put(data, {7515893506498066595u64, 856175526956u64, 0u64}) + put(data, {7300206309181072546u64, 7625299565768063067u64, 46u64}) + put(data, {5999737279837044u64, 13889021769065194705u64, 46413368317u64}) + put(data, {7556839307242450651u64, 14498170692313014398u64, 253752925378u64}) + put(data, {12946035041643640643u64, 1541631360972245751u64, 194785947408u64}) + put(data, {15885877642352740665u64, 9903958882920799117u64, 16083572003u64}) + put(data, {10770818348246089568u64, 15744148547788062576u64, 35536894686u64}) + put(data, {11635415503599551744u64, 17936061801321712000u64, 222853492002u64}) + put(data, {6248053924100826098u64, 9986394078324430610u64, 34972315858u64}) + put(data, {16894170802729859998u64, 13849561248103430369u64, 210541363507u64}) + put(data, {6143589029651889899u64, 12142378807953854930u64, 51750786219u64}) + put(data, {11812087701837886160u64, 2513847703931031444u64, 171658239674u64}) + put(data, {7306705080150829180u64, 1752183758129038045u64, 186136275957u64}) + put(data, {1485332570280714274u64, 15824833342220556540u64, 245094986071u64}) + put(data, {18042602303295630634u64, 8168747198299470695u64, 87857865934u64}) + put(data, {13397029889257074369u64, 17414799840149357478u64, 206442828672u64}) + put(data, {9948104869613411488u64, 83147520704167789u64, 128944058191u64}) + put(data, {10836066241170646532u64, 2383542703041471269u64, 79004507436u64}) + put(data, {18330574781234459389u64, 15540952725549257799u64, 44129212108u64}) + put(data, {666297360208433062u64, 6949835416232048690u64, 204842476735u64}) + put(data, {5035352224889324309u64, 15398868937585367540u64, 191376751332u64}) + put(data, {1341745796439923765u64, 14710915985268256079u64, 228834774357u64}) + put(data, {6846932182653803785u64, 9665704836873335737u64, 85797480353u64}) + put(data, {6727685027257825533u64, 2528789298740305993u64, 161523978909u64}) + put(data, {2168538874806877737u64, 10562914675687726264u64, 157137085942u64}) + put(data, {17694936100676971444u64, 17671658300096837111u64, 246572616751u64}) + put(data, {2163944241059563294u64, 356471401631698552u64, 47957982516u64}) + put(data, {6727780864524301558u64, 7450677157218003204u64, 52019324353u64}) + put(data, {8599551977795002615u64, 317174560787152643u64, 193403902018u64}) + put(data, {15428264807806859091u64, 7251937674440720374u64, 66017194067u64}) + put(data, {2126771385339683557u64, 1252631516699038247u64, 83393128329u64}) + put(data, {15094316562082972944u64, 10818009768860843867u64, 137067905290u64}) + put(data, {8111300598225956802u64, 12330114194950162396u64, 10586445484u64}) + put(data, {9481215200564260304u64, 15826681638261168822u64, 172668416829u64}) + put(data, {16836674128623424708u64, 14240150078499211625u64, 61857966130u64}) + put(data, {7087243115299722740u64, 10725372116242125421u64, 50771960082u64}) + put(data, {2343560867338408810u64, 8434925524647833627u64, 18581423587u64}) + put(data, {3431385749090422286u64, 17133902668520348241u64, 227457258228u64}) + put(data, {3504545517469224582u64, 15093996047981365810u64, 244928830724u64}) + put(data, {3333759805712094227u64, 6187974166976813153u64, 4818247165u64}) + put(data, {3595183476205994775u64, 13946144707720259865u64, 253335450751u64}) + put(data, {14191566632569921303u64, 9138079832881862582u64, 127756022019u64}) + put(data, {12917427671358095562u64, 6600697628576225568u64, 3495376300u64}) + put(data, {3883793922738316288u64, 8137099536646556597u64, 172357824535u64}) + put(data, {7857281689266421760u64, 14169855543453903706u64, 23441113049u64}) + put(data, {15987081651486195712u64, 3706403268650100765u64, 217768149408u64}) + put(data, {16827562156399525888u64, 14736932266877982264u64, 160200924523u64}) + put(data, {7012737938513461248u64, 18004795125138956004u64, 107798890698u64}) + put(data, {7385903388887613440u64, 9068489270661002501u64, 202976041899u64}) + put(data, {0u64, 7758835715193269217u64, 171491603788u64}) + put(data, {0u64, 16943947811135261184u64, 76420607326u64}) + put(data, {0u64, 6745843108403216384u64, 94918533251u64}) + put(data, {0u64, 12338229654069444608u64, 131365692887u64}) + put(data, {0u64, 14358176069683511296u64, 215668856769u64}) + put(data, {0u64, 7083775185760813056u64, 193778358284u64}) + put(data, {0u64, 5350276357316149248u64, 12384012222u64}) + put(data, {0u64, 9223372036854775808u64, 190290039062u64}) + put(data, {0u64, 0u64, 22500000000u64}) + put(data, {14135788013842776187u64, 13064201u64, 0u64}) + put(data, {13284322918167594445u64, 13064201766302603u64, 0u64}) + put(data, {7264587765474046287u64, 14699116688460625612u64, 708211u64}) + put(data, {16176482219778368741u64, 6684126021499623499u64, 115796840712u64}) + put(data, {5401983818872095469u64, 12614606079692508506u64, 8362347197u64}) + put(data, {11208857446851049921u64, 15358270276683001489u64, 189683839165u64}) + put(data, {13794690008281035639u64, 18077126190953408995u64, 189832573499u64}) + put(data, {14519782740993303071u64, 7864121581925945659u64, 59979962974u64}) + put(data, {10885858587044789123u64, 3518026639210514839u64, 94426314885u64}) + put(data, {8710500938899914621u64, 4698310163811252280u64, 133190712606u64}) + put(data, {1284182587483102819u64, 6101155398200416338u64, 30254695904u64}) + put(data, {10346074482131502030u64, 16049178580360033341u64, 224330744296u64}) + put(data, {9429804686255246574u64, 3167464649127375997u64, 232870027714u64}) + put(data, {6412022633845121254u64, 12778923935480989904u64, 194171708602u64}) + put(data, {8892351297529018260u64, 11875553912612980379u64, 186692746854u64}) + put(data, {14098160105983060597u64, 10628760849351697057u64, 102643775067u64}) + put(data, {13793833029739474340u64, 3408944711673234310u64, 91576186280u64}) + put(data, {16367653765996977044u64, 2102091496050506178u64, 168184799263u64}) + put(data, {10684788343333772342u64, 6254611118630245760u64, 31113954608u64}) + put(data, {13272681218705145345u64, 2647941151989776368u64, 48339063148u64}) + put(data, {11093568615497829248u64, 8855437735410157458u64, 108143545177u64}) + put(data, {12488220765137758124u64, 10184270603132180103u64, 89480054241u64}) + put(data, {3727114642519696453u64, 12079083162535627164u64, 225552090415u64}) + put(data, {11549865375695057514u64, 5952952868716156729u64, 47654808410u64}) + put(data, {11292809154908783229u64, 11958907037815852320u64, 90322710221u64}) + put(data, {17723904861837310998u64, 10101562137321697626u64, 205648293649u64}) + put(data, {14877674388187150875u64, 13633527411279258327u64, 17547606780u64}) + put(data, {12178961950105734308u64, 16555627393501768728u64, 252739075001u64}) + put(data, {4540604068069253114u64, 6359650463500280706u64, 185897482359u64}) + put(data, {13832478722153359868u64, 8093923611102181967u64, 119344757342u64}) + put(data, {1308046668730371491u64, 2848827352928635726u64, 94438772478u64}) + put(data, {18400531023544756800u64, 4686723431961561511u64, 254154435240u64}) + put(data, {9789823458621466539u64, 6245554925867652609u64, 168254067786u64}) + put(data, {1924870562610267306u64, 17527406820792516033u64, 74338572210u64}) + put(data, {5290016144582400923u64, 12119966834653692210u64, 178950162627u64}) + put(data, {15162883663174059077u64, 11606502845877928061u64, 195657024718u64}) + put(data, {8078086116520046390u64, 424311496652297090u64, 206629189780u64}) + put(data, {15731407332173190623u64, 5977664048034127173u64, 148023001972u64}) + put(data, {5346389182763011056u64, 6702712461535947028u64, 116324049817u64}) + put(data, {6368422217216252401u64, 11384349854055020018u64, 153363354770u64}) + put(data, {2961453088119116188u64, 3782955013294836188u64, 146617146842u64}) + put(data, {10932141691610170525u64, 3531805968821207061u64, 218205074402u64}) + put(data, {11034016191361782553u64, 3867566898657193228u64, 226191459585u64}) + put(data, {5455325785621453219u64, 12688734637425072080u64, 1209661221u64}) + put(data, {7735615202566149352u64, 18435982764454619691u64, 37687857682u64}) + put(data, {7502396497775759360u64, 4728836163964677692u64, 18999416628u64}) + put(data, {1601286435751591936u64, 2120012917348838977u64, 52256350722u64}) + put(data, {11449383158571597824u64, 9856965465824679831u64, 2114926130u64}) + put(data, {13043944595690356736u64, 11217197671061248816u64, 50534347168u64}) + put(data, {7773494431818186752u64, 3840562972677739189u64, 160608085504u64}) + put(data, {9943947977234055168u64, 17104366978925258617u64, 208197335u64}) + put(data, {0u64, 16177877219841993444u64, 215927229591u64}) + put(data, {0u64, 7338522384267208704u64, 151877004481u64}) + put(data, {0u64, 10935240458612244480u64, 193397822095u64}) + put(data, {0u64, 1732868046462124032u64, 143592800573u64}) + put(data, {0u64, 557965042578882560u64, 61093938965u64}) + put(data, {0u64, 10454684322475540480u64, 21030247345u64}) + put(data, {0u64, 13907115649320091648u64, 177566749572u64}) + put(data, {0u64, 0u64, 132753906250u64}) + put(data, {0u64, 0u64, 74000000000u64}) + put(data, {6343817245135589714u64, 199u64, 0u64}) + put(data, {17549323075660516085u64, 199343899021u64, 0u64}) + put(data, {3948641822109421754u64, 14876458284855834550u64, 10u64}) + put(data, {1750739713693534543u64, 10450704926982265198u64, 10806454419u64}) + put(data, {962163898128633415u64, 5385653213018257806u64, 147566533849u64}) + put(data, {7967320249386531212u64, 12735569669880147489u64, 217291956845u64}) + put(data, {3018466665533383224u64, 3619762560577729456u64, 109690396615u64}) + put(data, {15076865731854945472u64, 11123448126624084269u64, 199196227721u64}) + put(data, {691187172844604400u64, 4072715118852885633u64, 137603003331u64}) + put(data, {13274492813370992341u64, 18239087231420827283u64, 195220782328u64}) + put(data, {6364168818499152300u64, 423431461216085297u64, 248988742900u64}) + put(data, {17599380787401914158u64, 9360976716520160042u64, 244022954265u64}) + put(data, {3782323149461692814u64, 11655927117263208920u64, 25507459564u64}) + put(data, {14470163442442237466u64, 2646622721938364948u64, 236631869075u64}) + put(data, {476109872130437939u64, 4496462484548171852u64, 147143473705u64}) + put(data, {16330548844673355700u64, 13140258519803350063u64, 41243753719u64}) + put(data, {14089158961463739563u64, 13089764333320627770u64, 247712334841u64}) + put(data, {13385510793074798805u64, 6926286827289840501u64, 249709597546u64}) + put(data, {1549401308746959012u64, 4985580225290866218u64, 106375474761u64}) + put(data, {4607384943843027435u64, 10478790837359789693u64, 73270268845u64}) + put(data, {5157353797716093483u64, 10041191967455692214u64, 173568056389u64}) + put(data, {10846828782671550129u64, 5035461258013813797u64, 69544334107u64}) + put(data, {9915857350819131531u64, 14208759661559249750u64, 27272972901u64}) + put(data, {1673544973504317923u64, 12347272163241758840u64, 101770258404u64}) + put(data, {14780986291622785694u64, 3372534174410277614u64, 228669346965u64}) + put(data, {8168111319515466401u64, 17226704187274712984u64, 149182825443u64}) + put(data, {16345760387859734482u64, 4250480179449852121u64, 227933861505u64}) + put(data, {3177475373321281805u64, 4303723537755414374u64, 129230418992u64}) + put(data, {2558676822419554038u64, 8680503847344854165u64, 48233305320u64}) + put(data, {8813474062662382873u64, 8817608623911079652u64, 232470571056u64}) + put(data, {5426294560236228430u64, 5692030448698539450u64, 48478003521u64}) + put(data, {9919177474128333040u64, 16908836314686769809u64, 65308565588u64}) + put(data, {6197383943089627371u64, 6073762347067727240u64, 84916629853u64}) + put(data, {13673239314867423997u64, 10931066692585106200u64, 93329259316u64}) + put(data, {9309584098968723946u64, 14466591364061539596u64, 52592574312u64}) + put(data, {4647101757759615504u64, 4958077340960173341u64, 104784235489u64}) + put(data, {1405809295505096753u64, 4076890037156765715u64, 225268777911u64}) + put(data, {11332704079573859112u64, 14083973146609179058u64, 183221008651u64}) + put(data, {2960072434514044308u64, 2565183738039805295u64, 11763493714u64}) + put(data, {3887266602785432801u64, 1482420938751351224u64, 82139058889u64}) + put(data, {14545546084687849554u64, 2151089495335413944u64, 201080362200u64}) + put(data, {4617763804182385321u64, 3738604531753220913u64, 216116610795u64}) + put(data, {7857823815580249095u64, 14195686514836005765u64, 235202670157u64}) + put(data, {10939326736548364798u64, 17808833916231796970u64, 77769549707u64}) + put(data, {12262012446566951953u64, 1302384553035657354u64, 139965418821u64}) + put(data, {12555024338687723023u64, 1672033517974833698u64, 69070602408u64}) + put(data, {3332969632922829472u64, 11673925532927662545u64, 168090641118u64}) + put(data, {15535060143360327680u64, 3905334232240480709u64, 222632844771u64}) + put(data, {15794322927987458048u64, 17411087320267472625u64, 227211708592u64}) + put(data, {10571474314433921024u64, 16573305231063706617u64, 176943856934u64}) + put(data, {16679514427547975680u64, 15481103236037148354u64, 38898440676u64}) + put(data, {16925653299565166592u64, 907440704754420880u64, 228839232288u64}) + put(data, {16717361816799281152u64, 3224970785139077759u64, 32049192459u64}) + put(data, {0u64, 10560826509734608144u64, 11174826016u64}) + put(data, {0u64, 4700940027512659968u64, 32572503552u64}) + put(data, {0u64, 9733694683502084096u64, 254838469u64}) + put(data, {0u64, 1995535635724632064u64, 197527664646u64}) + put(data, {0u64, 10629833226245373952u64, 6108178203u64}) + put(data, {0u64, 15729384648544878592u64, 27576244413u64}) + put(data, {0u64, 7205759403792793600u64, 189852691650u64}) + put(data, {0u64, 0u64, 194390625000u64}) + put(data, {0u64, 0u64, 232000000000u64}) + put(data, {9335385384027907407u64, 3041746u64, 0u64}) + put(data, {13237828406194798613u64, 3041746506072255u64, 0u64}) + put(data, {15667486867836528863u64, 7535526066623007027u64, 164893u64}) + put(data, {17982325043592934313u64, 11302146918409311588u64, 29408501686u64}) + put(data, {17159117626917379189u64, 2480833299122194801u64, 182612690612u64}) + put(data, {8336208968408929657u64, 11513226205589330558u64, 180134486242u64}) + put(data, {12767090573379150201u64, 4073957068281936105u64, 226624133243u64}) + put(data, {14736070002412246709u64, 3729887061093812886u64, 123220849655u64}) + put(data, {9697296975344560756u64, 13616911779739451443u64, 247202197582u64}) + put(data, {7291706381199103298u64, 13039053282195777666u64, 78738174266u64}) + put(data, {18098546597780825068u64, 14490756113210417890u64, 58706848494u64}) + put(data, {132913902678533478u64, 17432486112977557585u64, 238785545462u64}) + put(data, {1879347741692007580u64, 14308820825344039837u64, 246945016965u64}) + put(data, {4056624629214083684u64, 4190949538817536349u64, 133775682731u64}) + put(data, {14713227692042795499u64, 13616552502810964397u64, 171227191829u64}) + put(data, {7366415124022528526u64, 4898145803694965031u64, 21738154790u64}) + put(data, {825770353378039393u64, 1399036321001644308u64, 38265529016u64}) + put(data, {10244023944395357795u64, 17170331128243738540u64, 184075841910u64}) + put(data, {14302658294713551167u64, 10641321388205367410u64, 118930805515u64}) + put(data, {14246653166206862817u64, 6648873641312572851u64, 11576867188u64}) + put(data, {12404486258134291102u64, 5988456964560374823u64, 116360436162u64}) + put(data, {8887442218637942533u64, 9972593758348346915u64, 194324634902u64}) + put(data, {1186724038081863005u64, 16709668921872818968u64, 22540615390u64}) + put(data, {211331772484951576u64, 6094829131503407767u64, 222905832967u64}) + put(data, {6311919513247413649u64, 4892016478899926334u64, 7330401349u64}) + put(data, {8131780018703965703u64, 13150857244079031538u64, 69265196744u64}) + put(data, {2262544347226725725u64, 12983943395318785894u64, 200712909399u64}) + put(data, {15318188749880522583u64, 15341644584614757478u64, 87703860981u64}) + put(data, {1073117094162650652u64, 7507635124856644772u64, 245831672219u64}) + put(data, {4447950380665871747u64, 11619655367084544354u64, 155406989715u64}) + put(data, {5373227185066463609u64, 11553116952478783009u64, 147629902779u64}) + put(data, {6739731406934274193u64, 17392150014233193245u64, 187626295724u64}) + put(data, {12328812617001239444u64, 8877887560294980515u64, 172942830341u64}) + put(data, {3246111484407310759u64, 18404180619915609503u64, 5481271248u64}) + put(data, {3250825415176839770u64, 10079413095288181976u64, 208997692630u64}) + put(data, {16146270540000862342u64, 14102802966539105550u64, 214546406078u64}) + put(data, {15686773375425916830u64, 13333966026135891399u64, 190764514480u64}) + put(data, {11920791905793880226u64, 12344968670173516152u64, 176722835746u64}) + put(data, {1853290561644080707u64, 10577007819804726752u64, 34669222092u64}) + put(data, {12157689141506159076u64, 15337041354031088010u64, 204573380742u64}) + put(data, {18100318838862562546u64, 14333607285614673616u64, 134831422677u64}) + put(data, {7171257882533475139u64, 17171597563219696538u64, 213777026407u64}) + put(data, {14477550873015039462u64, 2849642930482147564u64, 103930874169u64}) + put(data, {8109481182495403274u64, 14791248423979435173u64, 57154479452u64}) + put(data, {14565395719337663965u64, 13882371364576310127u64, 92801835183u64}) + put(data, {1860318978161305991u64, 11735995808941329540u64, 175752564859u64}) + put(data, {16268646275151585618u64, 11376996674339273181u64, 123636209607u64}) + put(data, {13759019338835519104u64, 9849638057168043481u64, 199616748225u64}) + put(data, {17003783176010661888u64, 18241520229279361964u64, 193533949948u64}) + put(data, {18357489540307877888u64, 1865852368526961444u64, 252988874793u64}) + put(data, {905481790074912768u64, 10601487369276448158u64, 41101148059u64}) + put(data, {3638882110636294144u64, 15999931310312762170u64, 155574707781u64}) + put(data, {9011702854368362496u64, 5773775867713013570u64, 69867358014u64}) + put(data, {11529215046068469760u64, 17726239863982547534u64, 62312997016u64}) + put(data, {0u64, 9711316695888316992u64, 152960941388u64}) + put(data, {0u64, 17872002620723724288u64, 76526451532u64}) + put(data, {0u64, 7429694208660733952u64, 76968843203u64}) + put(data, {0u64, 1782821038871019520u64, 195402764530u64}) + put(data, {0u64, 3225250234313474048u64, 242096646922u64}) + put(data, {0u64, 10009250171830927360u64, 10174841165u64}) + put(data, {0u64, 1152921504606846976u64, 77542602539u64}) + put(data, {0u64, 0u64, 43062500000u64}) + put(data, {0u64, 0u64, 160000000000u64}) + put(data, {7625299565768063067u64, 46u64, 0u64}) + put(data, {13889021769065194705u64, 46413368317u64, 0u64}) + put(data, {14498170692313014398u64, 9519880170333822146u64, 2u64}) + put(data, {1541631360972245751u64, 2285186318012886800u64, 2516073738u64}) + put(data, {9903958882920799117u64, 9706420951402272035u64, 10123880198u64}) + put(data, {15744148547788062576u64, 2369632031840402142u64, 6526186134u64}) + put(data, {17936061801321712000u64, 15599123897979399458u64, 150128458009u64}) + put(data, {9986394078324430610u64, 17579576584023912658u64, 25845630200u64}) + put(data, {13849561248103430369u64, 3480927339588501811u64, 248952990756u64}) + put(data, {12142378807953854930u64, 3547346616671294635u64, 36188701449u64}) + put(data, {2513847703931031444u64, 7705317123868384954u64, 9192302045u64}) + put(data, {1752183758129038045u64, 4969425237478353909u64, 221417706078u64}) + put(data, {15824833342220556540u64, 17043246700132217175u64, 94269393081u64}) + put(data, {8168747198299470695u64, 17053788362783499470u64, 185923916254u64}) + put(data, {17414799840149357478u64, 11102988228454224768u64, 222924487719u64}) + put(data, {83147520704167789u64, 16944305387801685839u64, 39601894197u64}) + put(data, {2383542703041471269u64, 11725142977459199276u64, 53918552635u64}) + put(data, {15540952725549257799u64, 8175984171998533324u64, 59635621274u64}) + put(data, {6949835416232048690u64, 1372352885142856895u64, 154443220990u64}) + put(data, {15398868937585367540u64, 17975093466502888164u64, 254074395398u64}) + put(data, {14710915985268256079u64, 6467823391459085653u64, 6974431769u64}) + put(data, {9665704836873335737u64, 11319386883146885025u64, 25350621408u64}) + put(data, {2528789298740305993u64, 9141999262922068637u64, 224613625192u64}) + put(data, {10562914675687726264u64, 1587330393383478774u64, 104495588773u64}) + put(data, {17671658300096837111u64, 884187548095712303u64, 165086049353u64}) + put(data, {356471401631698552u64, 488841225726377268u64, 73047931903u64}) + put(data, {7450677157218003204u64, 17462624199405856193u64, 255026500135u64}) + put(data, {317174560787152643u64, 13183677579115583554u64, 39946650754u64}) + put(data, {7251937674440720374u64, 11645015818917277779u64, 130714688593u64}) + put(data, {1252631516699038247u64, 8760523002035971977u64, 81631277572u64}) + put(data, {10818009768860843867u64, 10068817678491468042u64, 4474908903u64}) + put(data, {12330114194950162396u64, 1273658177787418284u64, 231545831700u64}) + put(data, {15826681638261168822u64, 3100019384328057661u64, 20069045148u64}) + put(data, {14240150078499211625u64, 10363063568089458738u64, 156168052387u64}) + put(data, {10725372116242125421u64, 13030756371481403666u64, 163561782801u64}) + put(data, {8434925524647833627u64, 6538878900684195299u64, 17706398718u64}) + put(data, {17133902668520348241u64, 8984884716779098868u64, 254354473335u64}) + put(data, {15093996047981365810u64, 8728727397070363908u64, 119487071576u64}) + put(data, {6187974166976813153u64, 6398650562917867005u64, 88473185260u64}) + put(data, {13946144707720259865u64, 1190873176164938879u64, 236346871542u64}) + put(data, {9138079832881862582u64, 4383628525805121795u64, 246064557364u64}) + put(data, {6600697628576225568u64, 10189374699734119852u64, 52237636978u64}) + put(data, {8137099536646556597u64, 5276291920541626391u64, 114552367109u64}) + put(data, {14169855543453903706u64, 2692252373800386521u64, 5286028358u64}) + put(data, {3706403268650100765u64, 11578684995169173920u64, 70145947293u64}) + put(data, {14736932266877982264u64, 5799408022254132587u64, 157627681771u64}) + put(data, {18004795125138956004u64, 15548569837712345290u64, 235314386538u64}) + put(data, {9068489270661002501u64, 15763030464322902955u64, 106842889659u64}) + put(data, {7758835715193269217u64, 13257749746581255500u64, 187854515593u64}) + put(data, {16943947811135261184u64, 16152470009188707678u64, 137718704053u64}) + put(data, {6745843108403216384u64, 13806790848493904003u64, 181875627153u64}) + put(data, {12338229654069444608u64, 11981226523265951191u64, 145748467631u64}) + put(data, {14358176069683511296u64, 5133628726077003713u64, 175649503591u64}) + put(data, {7083775185760813056u64, 16183955741910833164u64, 103278294570u64}) + put(data, {5350276357316149248u64, 13640425554331371454u64, 42877333998u64}) + put(data, {9223372036854775808u64, 18108120906868035862u64, 238739448950u64}) + put(data, {0u64, 6324011669895037184u64, 118981643201u64}) + put(data, {0u64, 10444437689515769856u64, 193342825359u64}) + put(data, {0u64, 12324712543665782784u64, 143566194101u64}) + put(data, {0u64, 13928941951563857920u64, 181668124005u64}) + put(data, {0u64, 3975288688270639104u64, 101755089456u64}) + put(data, {0u64, 11141905478114607104u64, 48215500831u64}) + put(data, {0u64, 4611686018427387904u64, 31604003906u64}) + put(data, {0u64, 0u64, 66250000000u64}) + put(data, {0u64, 0u64, 128000000000u64}) + put(data, {14699116688460625612u64, 708211u64, 0u64}) + put(data, {6684126021499623499u64, 708211796840712u64, 0u64}) + put(data, {12614606079692508506u64, 4398362855256705725u64, 38392u64}) + put(data, {15358270276683001489u64, 2812083125569302717u64, 248238435728u64}) + put(data, {18077126190953408995u64, 12868509142973100603u64, 144152443331u64}) + put(data, {7864121581925945659u64, 8726243776748165726u64, 195697603278u64}) + put(data, {3518026639210514839u64, 358304413426858117u64, 206473050623u64}) + put(data, {4698310163811252280u64, 3180720351566429470u64, 255019423721u64}) + put(data, {6101155398200416338u64, 14053818240400098784u64, 233172427195u64}) + put(data, {16049178580360033341u64, 7340140541492429288u64, 187761859013u64}) + put(data, {3167464649127375997u64, 1323571167904965058u64, 197397909816u64}) + put(data, {12778923935480989904u64, 14463851737583396026u64, 56071750936u64}) + put(data, {11875553912612980379u64, 15122784818916048486u64, 24784086973u64}) + put(data, {10628760849351697057u64, 13557974621377508955u64, 189819807807u64}) + put(data, {3408944711673234310u64, 17525172074563876264u64, 63734979276u64}) + put(data, {2102091496050506178u64, 15148880683074215967u64, 204950041481u64}) + put(data, {6254611118630245760u64, 6744828147558597936u64, 137821222467u64}) + put(data, {2647941151989776368u64, 9799290779647971692u64, 67365637866u64}) + put(data, {8855437735410157458u64, 11170890203898678105u64, 234531220617u64}) + put(data, {10184270603132180103u64, 7068779781287527905u64, 137605575171u64}) + put(data, {12079083162535627164u64, 14474741922505540911u64, 3383199319u64}) + put(data, {5952952868716156729u64, 17107062680405191514u64, 87784677331u64}) + put(data, {11958907037815852320u64, 2712598571300237005u64, 211927375726u64}) + put(data, {10101562137321697626u64, 3767556054903418641u64, 110147050263u64}) + put(data, {13633527411279258327u64, 18158239681706277628u64, 23204239622u64}) + put(data, {16555627393501768728u64, 10531652712128330681u64, 6984360145u64}) + put(data, {6359650463500280706u64, 9548395326934120567u64, 209570922037u64}) + put(data, {8093923611102181967u64, 15875647850297719390u64, 53517619547u64}) + put(data, {2848827352928635726u64, 8215825295203192574u64, 91860620594u64}) + put(data, {4686723431961561511u64, 12747310908260543144u64, 50445380781u64}) + put(data, {6245554925867652609u64, 77706528053613642u64, 173691033109u64}) + put(data, {17527406820792516033u64, 6024737704056756146u64, 21004212479u64}) + put(data, {12119966834653692210u64, 6819452388570089667u64, 255326601685u64}) + put(data, {11606502845877928061u64, 13695926775373186254u64, 213369683254u64}) + put(data, {424311496652297090u64, 3746531715392682132u64, 54742457678u64}) + put(data, {5977664048034127173u64, 4717376233154528116u64, 78203099891u64}) + put(data, {6702712461535947028u64, 385190957950313369u64, 243255729478u64}) + put(data, {11384349854055020018u64, 12388374310648616082u64, 70020881243u64}) + put(data, {3782955013294836188u64, 1078067332084407770u64, 91671575117u64}) + put(data, {3531805968821207061u64, 3257295319358714850u64, 77058442147u64}) + put(data, {3867566898657193228u64, 1545453099660723457u64, 163176578333u64}) + put(data, {12688734637425072080u64, 7495477664653506341u64, 29083779180u64}) + put(data, {18435982764454619691u64, 7225503732673614354u64, 108406330658u64}) + put(data, {4728836163964677692u64, 3935478326103643956u64, 34391695342u64}) + put(data, {2120012917348838977u64, 10082240682742686210u64, 238213342707u64}) + put(data, {9856965465824679831u64, 10838712705567897138u64, 243546559362u64}) + put(data, {11217197671061248816u64, 2142546572501643680u64, 130587567793u64}) + put(data, {3840562972677739189u64, 7893042119150331392u64, 177116147682u64}) + put(data, {17104366978925258617u64, 12084811642251302615u64, 226427882670u64}) + put(data, {16177877219841993444u64, 15317234482572954775u64, 174655118951u64}) + put(data, {7338522384267208704u64, 2283226355108359361u64, 103830348945u64}) + put(data, {10935240458612244480u64, 13359725152575722127u64, 145123773948u64}) + put(data, {1732868046462124032u64, 13126551011491594557u64, 252724232151u64}) + put(data, {557965042578882560u64, 3598021288691861269u64, 215711591756u64}) + put(data, {10454684322475540480u64, 16462621795896662961u64, 76195049124u64}) + put(data, {13907115649320091648u64, 14682112756964627332u64, 164892440515u64}) + put(data, {0u64, 7174112100896070218u64, 195795918927u64}) + put(data, {0u64, 5023109019590616064u64, 79388909396u64}) + put(data, {0u64, 10765223023086141440u64, 84272303285u64}) + put(data, {0u64, 8228137177297453056u64, 181583583909u64}) + put(data, {0u64, 2891199497780592640u64, 165446048210u64}) + put(data, {0u64, 15294857653247803392u64, 210156732238u64}) + put(data, {0u64, 14303432416528695296u64, 78829135894u64}) + put(data, {0u64, 0u64, 22775390625u64}) + put(data, {0u64, 0u64, 161000000000u64}) + put(data, {14876458284855834550u64, 10u64, 0u64}) + put(data, {10450704926982265198u64, 10806454419u64, 0u64}) + put(data, {5385653213018257806u64, 10806454419566533849u64, 0u64}) + put(data, {12735569669880147489u64, 17118225092618494573u64, 585819067u64}) + put(data, {3619762560577729456u64, 13385738875341807559u64, 187927980841u64}) + put(data, {11123448126624084269u64, 8272682717439277193u64, 41725642358u64}) + put(data, {4072715118852885633u64, 13402436483369350083u64, 118448463028u64}) + put(data, {18239087231420827283u64, 10946328903241612536u64, 180726547537u64}) + put(data, {423431461216085297u64, 16265808923426731252u64, 81593401678u64}) + put(data, {9360976716520160042u64, 11080374459871185177u64, 78881771268u64}) + put(data, {11655927117263208920u64, 1240761893433831916u64, 4600668303u64}) + put(data, {2646622721938364948u64, 367264070493390483u64, 143067261837u64}) + put(data, {4496462484548171852u64, 2863675693461092905u64, 141019909425u64}) + put(data, {13140258519803350063u64, 7511929581752138999u64, 49155240170u64}) + put(data, {13089764333320627770u64, 11154557789993845753u64, 234407222518u64}) + put(data, {6926286827289840501u64, 8325416539745948522u64, 246604689789u64}) + put(data, {4985580225290866218u64, 17745129874679852617u64, 125451321734u64}) + put(data, {10478790837359789693u64, 1074820986392253357u64, 134961965418u64}) + put(data, {10041191967455692214u64, 7820952682162838597u64, 106058266162u64}) + put(data, {5035461258013813797u64, 8215518006273528603u64, 50423974694u64}) + put(data, {14208759661559249750u64, 9680426791089900133u64, 38445364123u64}) + put(data, {12347272163241758840u64, 16128495723604797412u64, 155524776987u64}) + put(data, {3372534174410277614u64, 2264789053583348885u64, 27874327505u64}) + put(data, {17226704187274712984u64, 11175458488686298083u64, 209122774460u64}) + put(data, {4250480179449852121u64, 11026777810412287617u64, 188605822818u64}) + put(data, {4303723537755414374u64, 16199890034895598640u64, 98597762822u64}) + put(data, {8680503847344854165u64, 9094320719494763752u64, 6878197798u64}) + put(data, {8817608623911079652u64, 1250835564687222832u64, 38493004114u64}) + put(data, {5692030448698539450u64, 15362466642459337025u64, 82067807931u64}) + put(data, {16908836314686769809u64, 7831109835595423828u64, 187832800985u64}) + put(data, {6073762347067727240u64, 15426237284335022429u64, 217424525314u64}) + put(data, {10931066692585106200u64, 15636308361455434548u64, 2836257998u64}) + put(data, {14466591364061539596u64, 13967173875944980328u64, 206847645974u64}) + put(data, {4958077340960173341u64, 18245979923595824097u64, 22757162012u64}) + put(data, {4076890037156765715u64, 11335054479675278263u64, 28989116553u64}) + put(data, {14083973146609179058u64, 11165339882630461707u64, 137614474534u64}) + put(data, {2565183738039805295u64, 15944437408299395922u64, 38605274287u64}) + put(data, {1482420938751351224u64, 15806416348777321161u64, 175864349683u64}) + put(data, {2151089495335413944u64, 4201030477408556248u64, 243856867547u64}) + put(data, {3738604531753220913u64, 9485474942554588907u64, 219227738318u64}) + put(data, {14195686514836005765u64, 18238757647663230541u64, 206514208626u64}) + put(data, {17808833916231796970u64, 4642199687824746379u64, 114988725033u64}) + put(data, {1302384553035657354u64, 6134575894869364037u64, 41251654149u64}) + put(data, {1672033517974833698u64, 11524208547121316008u64, 5332556025u64}) + put(data, {11673925532927662545u64, 2734683241527878366u64, 249624728597u64}) + put(data, {3905334232240480709u64, 10629223456178675171u64, 21148247475u64}) + put(data, {17411087320267472625u64, 2788042336985254064u64, 179576211358u64}) + put(data, {16573305231063706617u64, 17285498758066142502u64, 158151140077u64}) + put(data, {15481103236037148354u64, 5525538192421886436u64, 237937048765u64}) + put(data, {907440704754420880u64, 11414325503043801888u64, 189299540025u64}) + put(data, {3224970785139077759u64, 7246608114685173259u64, 57618771825u64}) + put(data, {10560826509734608144u64, 1007884269852184608u64, 113392839413u64}) + put(data, {4700940027512659968u64, 13823717876510029312u64, 245054637515u64}) + put(data, {9733694683502084096u64, 12487410768239429317u64, 203749385247u64}) + put(data, {1995535635724632064u64, 3361062421598631942u64, 31676943894u64}) + put(data, {10629833226245373952u64, 17853337379088328475u64, 22182203558u64}) + put(data, {15729384648544878592u64, 11551561037491869885u64, 166967831358u64}) + put(data, {7205759403792793600u64, 11480877996635204802u64, 62626211378u64}) + put(data, {0u64, 5527488381934471912u64, 50622379643u64}) + put(data, {0u64, 11143438404407726080u64, 123299645745u64}) + put(data, {0u64, 6472279730688098304u64, 49604087006u64}) + put(data, {0u64, 4561816853579563008u64, 222350862987u64}) + put(data, {0u64, 2888714464062865408u64, 139247296587u64}) + put(data, {0u64, 16258276129784201216u64, 75156597524u64}) + put(data, {0u64, 720575940379279360u64, 20881362915u64}) + put(data, {0u64, 0u64, 227039062500u64}) + put(data, {0u64, 0u64, 228000000000u64}) + put(data, {7535526066623007027u64, 164893u64, 0u64}) + put(data, {11302146918409311588u64, 164893408501686u64, 0u64}) + put(data, {2480833299122194801u64, 16409970870640346804u64, 8938u64}) + put(data, {11513226205589330558u64, 7721907286269370594u64, 234889586303u64}) + put(data, {4073957068281936105u64, 14300743897882155131u64, 127418605432u64}) + put(data, {3729887061093812886u64, 2068482633821123575u64, 120775244880u64}) + put(data, {13616911779739451443u64, 4922882895416406094u64, 80112132668u64}) + put(data, {13039053282195777666u64, 9317632875623428410u64, 60266870016u64}) + put(data, {14490756113210417890u64, 5693844901999766254u64, 505109890u64}) + put(data, {17432486112977557585u64, 11569484900262102262u64, 130308663950u64}) + put(data, {14308820825344039837u64, 3138170119352085637u64, 142627183033u64}) + put(data, {4190949538817536349u64, 950584692575235243u64, 185170120543u64}) + put(data, {13616552502810964397u64, 8136430299747162645u64, 95051531299u64}) + put(data, {4898145803694965031u64, 6698711700804594470u64, 35441076770u64}) + put(data, {1399036321001644308u64, 17401191571004302008u64, 34363137888u64}) + put(data, {17170331128243738540u64, 4721732028538188150u64, 96943320485u64}) + put(data, {10641321388205367410u64, 2984214103553086219u64, 165255965606u64}) + put(data, {6648873641312572851u64, 13128675202005662068u64, 166161774570u64}) + put(data, {5988456964560374823u64, 14638512997670672834u64, 234711706908u64}) + put(data, {9972593758348346915u64, 12942085665769692438u64, 28793555379u64}) + put(data, {16709668921872818968u64, 14131134357119205086u64, 179701591869u64}) + put(data, {6094829131503407767u64, 8921946894736102919u64, 61766050328u64}) + put(data, {4892016478899926334u64, 5601522560505809989u64, 24483659710u64}) + put(data, {13150857244079031538u64, 8602606493507716808u64, 190303659146u64}) + put(data, {12983943395318785894u64, 8576789731078566487u64, 138466348232u64}) + put(data, {15341644584614757478u64, 17881118138842658549u64, 200464948702u64}) + put(data, {7507635124856644772u64, 11624372674432704923u64, 222969337356u64}) + put(data, {11619655367084544354u64, 6826284072848095635u64, 12630158505u64}) + put(data, {11553116952478783009u64, 1646466632033733563u64, 169370053601u64}) + put(data, {17392150014233193245u64, 17871081657060299180u64, 225089255134u64}) + put(data, {8877887560294980515u64, 15910893124677544709u64, 222968793277u64}) + put(data, {18404180619915609503u64, 11031217459450580944u64, 189862531244u64}) + put(data, {10079413095288181976u64, 13554987390037243094u64, 172598003496u64}) + put(data, {14102802966539105550u64, 15026714590903687870u64, 40734817338u64}) + put(data, {13333966026135891399u64, 4406379654994689200u64, 58814599830u64}) + put(data, {12344968670173516152u64, 13596329092861950242u64, 150238870319u64}) + put(data, {10577007819804726752u64, 284812388227373260u64, 47737058477u64}) + put(data, {15337041354031088010u64, 9285079159392309382u64, 173015439710u64}) + put(data, {14333607285614673616u64, 15046108141952711893u64, 94503345149u64}) + put(data, {17171597563219696538u64, 13795366909944958311u64, 253815651156u64}) + put(data, {2849642930482147564u64, 12909920641180059961u64, 84747848338u64}) + put(data, {14791248423979435173u64, 5333762939889788252u64, 146699848200u64}) + put(data, {13882371364576310127u64, 6411331390005944495u64, 8289143868u64}) + put(data, {11735995808941329540u64, 1447104583224217723u64, 60347558971u64}) + put(data, {11376996674339273181u64, 11940049226167932871u64, 59078447696u64}) + put(data, {9849638057168043481u64, 9772290783590472385u64, 80647271365u64}) + put(data, {18241520229279361964u64, 16351989577831528444u64, 197529756944u64}) + put(data, {1865852368526961444u64, 4376738725895725097u64, 16886443131u64}) + put(data, {10601487369276448158u64, 13851276297739812763u64, 123237263481u64}) + put(data, {15999931310312762170u64, 12641996203470333509u64, 121750879192u64}) + put(data, {5773775867713013570u64, 7707081716407945022u64, 216685323987u64}) + put(data, {17726239863982547534u64, 417638323657040024u64, 211417801737u64}) + put(data, {9711316695888316992u64, 16438047707692449100u64, 9022640218u64}) + put(data, {17872002620723724288u64, 14850108107043306316u64, 90891108351u64}) + put(data, {7429694208660733952u64, 10423290807904720835u64, 255805025973u64}) + put(data, {1782821038871019520u64, 16951162310302339314u64, 181565047726u64}) + put(data, {3225250234313474048u64, 2752437506572397322u64, 174918924350u64}) + put(data, {10009250171830927360u64, 3925815842962784589u64, 62149209936u64}) + put(data, {1152921504606846976u64, 5274166674003605291u64, 80212818903u64}) + put(data, {0u64, 5538963350863452832u64, 215285913148u64}) + put(data, {0u64, 16900671634439028736u64, 60300267804u64}) + put(data, {0u64, 2326997710751662080u64, 28916187245u64}) + put(data, {0u64, 12327726161625874432u64, 109126146798u64}) + put(data, {0u64, 5756455743825903616u64, 238668287374u64}) + put(data, {0u64, 3018537650245074944u64, 142312058091u64}) + put(data, {0u64, 16717361816799281152u64, 235163635253u64}) + put(data, {0u64, 0u64, 53906250000u64}) + put(data, {0u64, 0u64, 16000000000u64}) + put(data, {2285186318012886800u64, 2516073738u64, 0u64}) + put(data, {9706420951402272035u64, 2516073738123880198u64, 0u64}) + put(data, {2369632031840402142u64, 11997425759292732054u64, 136396630u64}) + put(data, {15599123897979399458u64, 11491152661270395161u64, 86650381753u64}) + put(data, {17579576584023912658u64, 18181063258234881272u64, 185622936633u64}) + put(data, {3480927339588501811u64, 2466921813123869732u64, 57985597414u64}) + put(data, {3547346616671294635u64, 8430880678232179465u64, 230133732099u64}) + put(data, {7705317123868384954u64, 6738034873677997533u64, 3457038957u64}) + put(data, {4969425237478353909u64, 7678250951042929246u64, 109365269602u64}) + put(data, {17043246700132217175u64, 1853560606315563193u64, 98416238818u64}) + put(data, {17053788362783499470u64, 14942676593409905118u64, 226100481721u64}) + put(data, {11102988228454224768u64, 4909892170837638183u64, 185810044121u64}) + put(data, {16944305387801685839u64, 16871149368312132405u64, 217266165787u64}) + put(data, {11725142977459199276u64, 16096130589333770811u64, 27914586839u64}) + put(data, {8175984171998533324u64, 12512479187631824282u64, 215872572987u64}) + put(data, {1372352885142856895u64, 16980304980540557310u64, 59678302855u64}) + put(data, {17975093466502888164u64, 8640919162749295366u64, 135920504177u64}) + put(data, {6467823391459085653u64, 7862382415464063513u64, 113468425166u64}) + put(data, {11319386883146885025u64, 14534157903009925344u64, 206426220604u64}) + put(data, {9141999262922068637u64, 12627464554215107944u64, 60787898278u64}) + put(data, {1587330393383478774u64, 2456849734836299173u64, 166684536225u64}) + put(data, {884187548095712303u64, 18428252197697827913u64, 161133186090u64}) + put(data, {488841225726377268u64, 7244734215936736255u64, 42998997553u64}) + put(data, {17462624199405856193u64, 14756175050504770087u64, 49392737828u64}) + put(data, {13183677579115583554u64, 6764116534566945922u64, 36799933852u64}) + put(data, {11645015818917277779u64, 1588822142405565521u64, 156366683492u64}) + put(data, {8760523002035971977u64, 17053265624843842052u64, 100086130220u64}) + put(data, {10068817678491468042u64, 16996891591759999207u64, 44924459381u64}) + put(data, {1273658177787418284u64, 8565556232370585876u64, 117921403339u64}) + put(data, {3100019384328057661u64, 14464960359145886620u64, 203464339733u64}) + put(data, {10363063568089458738u64, 5813189542048784035u64, 21784147072u64}) + put(data, {13030756371481403666u64, 9739241026882027025u64, 128315133636u64}) + put(data, {6538878900684195299u64, 18175068535675302910u64, 196527965313u64}) + put(data, {8984884716779098868u64, 10562697212061761911u64, 129985272439u64}) + put(data, {8728727397070363908u64, 4264834835660801368u64, 119572604963u64}) + put(data, {6398650562917867005u64, 13019066443690126316u64, 35231197159u64}) + put(data, {1190873176164938879u64, 1828040177823321846u64, 231705765006u64}) + put(data, {4383628525805121795u64, 11240369830376975668u64, 142099098256u64}) + put(data, {10189374699734119852u64, 8886938465302549874u64, 144609341669u64}) + put(data, {5276291920541626391u64, 9985240313589688325u64, 229481761899u64}) + put(data, {2692252373800386521u64, 722909126956573766u64, 107541300962u64}) + put(data, {11578684995169173920u64, 5493363474638452381u64, 226039188982u64}) + put(data, {5799408022254132587u64, 12410535279213120491u64, 246297795830u64}) + put(data, {15548569837712345290u64, 10543108918366869098u64, 246672776465u64}) + put(data, {15763030464322902955u64, 12953909016524823995u64, 17571543079u64}) + put(data, {13257749746581255500u64, 16505942145872588169u64, 39702232814u64}) + put(data, {16152470009188707678u64, 12428594380392015797u64, 238894788916u64}) + put(data, {13806790848493904003u64, 7528259605829768337u64, 52673755451u64}) + put(data, {11981226523265951191u64, 18147447600042811311u64, 59408107770u64}) + put(data, {5133628726077003713u64, 12021069431116183911u64, 250983775105u64}) + put(data, {16183955741910833164u64, 11819985069665662506u64, 129651663479u64}) + put(data, {13640425554331371454u64, 10401877114068152814u64, 119640762674u64}) + put(data, {18108120906868035862u64, 4611631138117837942u64, 50563886888u64}) + put(data, {6324011669895037184u64, 17200813398607252417u64, 40249997024u64}) + put(data, {10444437689515769856u64, 14100466137553658767u64, 224932457962u64}) + put(data, {12324712543665782784u64, 17887776768825509301u64, 234764387800u64}) + put(data, {13928941951563857920u64, 12632656857970087269u64, 216969698321u64}) + put(data, {3975288688270639104u64, 8923681664054686256u64, 17684817700u64}) + put(data, {11141905478114607104u64, 6213926103737837599u64, 36483753752u64}) + put(data, {4611686018427387904u64, 1233118281776157762u64, 24336857609u64}) + put(data, {0u64, 30716279628678784u64, 9066847476u64}) + put(data, {0u64, 15775734650898546688u64, 244001665132u64}) + put(data, {0u64, 976806005729918976u64, 108855204289u64}) + put(data, {0u64, 12460098853279891456u64, 193052952759u64}) + put(data, {0u64, 5635665595421687808u64, 183675463312u64}) + put(data, {0u64, 1805943450575568896u64, 144305510044u64}) + put(data, {0u64, 11529215046068469760u64, 156097900390u64}) + put(data, {0u64, 0u64, 102625000000u64}) + put(data, {0u64, 0u64, 64000000000u64}) + put(data, {4398362855256705725u64, 38392u64, 0u64}) + put(data, {2812083125569302717u64, 38392238435728u64, 0u64}) + put(data, {12868509142973100603u64, 4564018338575530435u64, 2081u64}) + put(data, {8726243776748165726u64, 16553437246451512014u64, 33247415929u64}) + put(data, {358304413426858117u64, 4339777136957372927u64, 121897363631u64}) + put(data, {3180720351566429470u64, 18439463366554654697u64, 175235259789u64}) + put(data, {14053818240400098784u64, 1370067356680643003u64, 141999605312u64}) + put(data, {7340140541492429288u64, 4210124040914115013u64, 64074271500u64}) + put(data, {1323571167904965058u64, 10692225626142609720u64, 12228231281u64}) + put(data, {14463851737583396026u64, 11592856673895384344u64, 113579626712u64}) + put(data, {15122784818916048486u64, 10284479231227406269u64, 216628450019u64}) + put(data, {13557974621377508955u64, 4961071383534266431u64, 227557522736u64}) + put(data, {17525172074563876264u64, 10960611551445686988u64, 48268940218u64}) + put(data, {15148880683074215967u64, 14616396723115619209u64, 186594175942u64}) + put(data, {6744828147558597936u64, 1025604265437492803u64, 198792356454u64}) + put(data, {9799290779647971692u64, 11711588454892179178u64, 102055598118u64}) + put(data, {11170890203898678105u64, 5580373263251565705u64, 38634886482u64}) + put(data, {7068779781287527905u64, 14109334653033148931u64, 82302512640u64}) + put(data, {14474741922505540911u64, 2899414033769399895u64, 764868564u64}) + put(data, {17107062680405191514u64, 13233457234892808147u64, 212157177549u64}) + put(data, {2712598571300237005u64, 3287946691509034862u64, 205717387154u64}) + put(data, {3767556054903418641u64, 5488480288717445911u64, 146178239947u64}) + put(data, {18158239681706277628u64, 11687233053874362630u64, 203297531112u64}) + put(data, {10531652712128330681u64, 6783772100089274577u64, 232633566173u64}) + put(data, {9548395326934120567u64, 7898291058728402485u64, 221367749022u64}) + put(data, {15875647850297719390u64, 4423684977486598491u64, 158428167216u64}) + put(data, {8215825295203192574u64, 2750833684599526706u64, 48239808443u64}) + put(data, {12747310908260543144u64, 15669689830489025709u64, 187149122992u64}) + put(data, {77706528053613642u64, 15117307274214954517u64, 176849455587u64}) + put(data, {6024737704056756146u64, 8148639818575698175u64, 227819510869u64}) + put(data, {6819452388570089667u64, 13006484426078994901u64, 85441738649u64}) + put(data, {13695926775373186254u64, 10287496057845513526u64, 153705082933u64}) + put(data, {3746531715392682132u64, 14159876032966532430u64, 53557686278u64}) + put(data, {4717376233154528116u64, 15742212196465548019u64, 6767608417u64}) + put(data, {385190957950313369u64, 2892220461917134150u64, 97853387033u64}) + put(data, {12388374310648616082u64, 7487151560715393883u64, 25156787585u64}) + put(data, {1078067332084407770u64, 7245756744165177933u64, 129405879299u64}) + put(data, {3257295319358714850u64, 3067122860671533987u64, 3392793260u64}) + put(data, {1545453099660723457u64, 8135043905834122525u64, 172166269063u64}) + put(data, {7495477664653506341u64, 14730019368921022572u64, 135441001613u64}) + put(data, {7225503732673614354u64, 495969939682055458u64, 141798515950u64}) + put(data, {3935478326103643956u64, 5617761407265775598u64, 238026886584u64}) + put(data, {10082240682742686210u64, 2087044847072781811u64, 184304539456u64}) + put(data, {10838712705567897138u64, 15929674232061203330u64, 64113138927u64}) + put(data, {2142546572501643680u64, 8658086469608285873u64, 239863549370u64}) + put(data, {7893042119150331392u64, 18369871790780313570u64, 186469355807u64}) + put(data, {12084811642251302615u64, 3545648451947416750u64, 31995832745u64}) + put(data, {15317234482572954775u64, 13347376792767929959u64, 169192209987u64}) + put(data, {2283226355108359361u64, 14482164459838203025u64, 67723562745u64}) + put(data, {13359725152575722127u64, 8899577765623565820u64, 249785079708u64}) + put(data, {13126551011491594557u64, 7095320096604405719u64, 156482447077u64}) + put(data, {3598021288691861269u64, 2968593824439315788u64, 229384638073u64}) + put(data, {16462621795896662961u64, 12621408323612585636u64, 121160927793u64}) + put(data, {14682112756964627332u64, 3954422936414648259u64, 49684207916u64}) + put(data, {7174112100896070218u64, 17143730087577690191u64, 44214369696u64}) + put(data, {5023109019590616064u64, 5033045529399041876u64, 160929363470u64}) + put(data, {10765223023086141440u64, 15857648521994521781u64, 14272841944u64}) + put(data, {8228137177297453056u64, 16655573486499109541u64, 216859644848u64}) + put(data, {2891199497780592640u64, 16652154439190075858u64, 176902900447u64}) + put(data, {15294857653247803392u64, 18016950600164130638u64, 223902715100u64}) + put(data, {14303432416528695296u64, 2086292996072613910u64, 220976700849u64}) + put(data, {0u64, 17324462585194799521u64, 177113098169u64}) + put(data, {0u64, 11079151463184927232u64, 185939160998u64}) + put(data, {0u64, 5239846817488961536u64, 166600602004u64}) + put(data, {0u64, 2778806963520143360u64, 148284052665u64}) + put(data, {0u64, 6240890740138835968u64, 185150639427u64}) + put(data, {0u64, 17250651344549707776u64, 67338319364u64}) + put(data, {0u64, 4197354852709302272u64, 4935159683u64}) + put(data, {0u64, 9223372036854775808u64, 131227539062u64}) + put(data, {0u64, 0u64, 118500000000u64}) + put(data, {17118225092618494573u64, 585819067u64, 0u64}) + put(data, {13385738875341807559u64, 585819067927980841u64, 0u64}) + put(data, {8272682717439277193u64, 5654803392547571318u64, 31757315u64}) + put(data, {13402436483369350083u64, 2931628102185393332u64, 3306547506u64}) + put(data, {10946328903241612536u64, 15964697617980212305u64, 50158923877u64}) + put(data, {16265808923426731252u64, 450380868305846606u64, 101865447992u64}) + put(data, {11080374459871185177u64, 14631133530814566148u64, 56024415195u64}) + put(data, {1240761893433831916u64, 31969822783742095u64, 219793155338u64}) + put(data, {367264070493390483u64, 10437269029385743245u64, 10001733087u64}) + put(data, {2863675693461092905u64, 15196146496377392433u64, 223565805487u64}) + put(data, {7511929581752138999u64, 4409099735137480938u64, 175823784752u64}) + put(data, {11154557789993845753u64, 10644987914903248118u64, 48239017775u64}) + put(data, {8325416539745948522u64, 3154431617534062973u64, 47577065951u64}) + put(data, {17745129874679852617u64, 11702056331247960454u64, 223171002080u64}) + put(data, {1074820986392253357u64, 15575315065965259114u64, 224634369744u64}) + put(data, {7820952682162838597u64, 10759747609480050226u64, 208844339521u64}) + put(data, {8215518006273528603u64, 12538236653960743718u64, 65583287086u64}) + put(data, {9680426791089900133u64, 17857942663978005403u64, 46679699170u64}) + put(data, {16128495723604797412u64, 11443004154750813211u64, 226968081011u64}) + put(data, {2264789053583348885u64, 4004313188770806737u64, 115620326498u64}) + put(data, {11175458488686298083u64, 17134872954824183228u64, 98217074252u64}) + put(data, {11026777810412287617u64, 2659553912986171234u64, 76928883324u64}) + put(data, {16199890034895598640u64, 9501854300969137926u64, 124144174706u64}) + put(data, {9094320719494763752u64, 14528169966301018150u64, 114515096553u64}) + put(data, {1250835564687222832u64, 18172091996515901778u64, 233787573671u64}) + put(data, {15362466642459337025u64, 1133541705604751035u64, 167985111081u64}) + put(data, {7831109835595423828u64, 18280349987988641497u64, 41061449418u64}) + put(data, {15426237284335022429u64, 9936015874712336386u64, 202990979758u64}) + put(data, {15636308361455434548u64, 15876720399740689614u64, 174538632499u64}) + put(data, {13967173875944980328u64, 8618117825152456982u64, 51860678737u64}) + put(data, {18245979923595824097u64, 8085525680745921564u64, 81467189103u64}) + put(data, {11335054479675278263u64, 8072355444669730953u64, 111438317225u64}) + put(data, {11165339882630461707u64, 9395030504766848294u64, 169437603265u64}) + put(data, {15944437408299395922u64, 3537903114058185903u64, 193509305624u64}) + put(data, {15806416348777321161u64, 2126094743961928691u64, 24191790112u64}) + put(data, {4201030477408556248u64, 289185362555601115u64, 32115255827u64}) + put(data, {9485474942554588907u64, 16909937501450129614u64, 19015676769u64}) + put(data, {18238757647663230541u64, 14449642060360499058u64, 97916689548u64}) + put(data, {4642199687824746379u64, 12433818908498244393u64, 140783316665u64}) + put(data, {6134575894869364037u64, 11884444034578008581u64, 185674038673u64}) + put(data, {11524208547121316008u64, 988625838444140793u64, 145644257002u64}) + put(data, {2734683241527878366u64, 1675370907158909973u64, 234053593514u64}) + put(data, {10629223456178675171u64, 15920186275316934067u64, 170090822038u64}) + put(data, {2788042336985254064u64, 5600921198503757726u64, 150863035027u64}) + put(data, {17285498758066142502u64, 10457357161776341741u64, 147303626546u64}) + put(data, {5525538192421886436u64, 12225356765775740093u64, 50566894467u64}) + put(data, {11414325503043801888u64, 4486633318598164537u64, 131662737918u64}) + put(data, {7246608114685173259u64, 10302486602879381361u64, 254243220879u64}) + put(data, {1007884269852184608u64, 15536428611301239541u64, 143558498917u64}) + put(data, {13823717876510029312u64, 12026126645955462603u64, 101842231482u64}) + put(data, {12487410768239429317u64, 14877968141142123551u64, 186651937631u64}) + put(data, {3361062421598631942u64, 734560801645383190u64, 95806536269u64}) + put(data, {17853337379088328475u64, 15648943144911081638u64, 77039820620u64}) + put(data, {11551561037491869885u64, 13664182862003235646u64, 76848330907u64}) + put(data, {11480877996635204802u64, 3895127525902132786u64, 155740736837u64}) + put(data, {5527488381934471912u64, 5249187334214137467u64, 69211155286u64}) + put(data, {11143438404407726080u64, 10642260063359027505u64, 86284559015u64}) + put(data, {6472279730688098304u64, 783598951897779422u64, 167576918074u64}) + put(data, {4561816853579563008u64, 5538576558607624843u64, 58042478984u64}) + put(data, {2888714464062865408u64, 15974581187564609611u64, 136300246836u64}) + put(data, {16258276129784201216u64, 7474269406918257428u64, 52865983781u64}) + put(data, {720575940379279360u64, 8045286838779138019u64, 37405180956u64}) + put(data, {0u64, 8184246376556341732u64, 28436135873u64}) + put(data, {0u64, 1493267152679331840u64, 193443668885u64}) + put(data, {0u64, 10179074811222818816u64, 149080950174u64}) + put(data, {0u64, 3892499202005008384u64, 158551808751u64}) + put(data, {0u64, 10341173215925108736u64, 239211012804u64}) + put(data, {0u64, 6230307872002015232u64, 196560596123u64}) + put(data, {0u64, 9295429630892703744u64, 155337745666u64}) + put(data, {0u64, 0u64, 2503906250u64}) + put(data, {0u64, 0u64, 202000000000u64}) + put(data, {16409970870640346804u64, 8938u64, 0u64}) + put(data, {7721907286269370594u64, 8938889586303u64, 0u64}) + put(data, {14300743897882155131u64, 10665454627995623288u64, 484u64}) + put(data, {2068482633821123575u64, 16803537892767562832u64, 228578175453u64}) + put(data, {4922882895416406094u64, 8099123106849104444u64, 221910921614u64}) + put(data, {9317632875623428410u64, 7077413686679401728u64, 142439054343u64}) + put(data, {5693844901999766254u64, 13536636358372449666u64, 7383667364u64}) + put(data, {11569484900262102262u64, 7280632235418610318u64, 164733822527u64}) + put(data, {3138170119352085637u64, 6187823673116858809u64, 63394683864u64}) + put(data, {950584692575235243u64, 8624343686231740255u64, 216335442593u64}) + put(data, {8136430299747162645u64, 806211610822132771u64, 161467526608u64}) + put(data, {6698711700804594470u64, 18388078233202190882u64, 208043704818u64}) + put(data, {17401191571004302008u64, 7628864426595573600u64, 242996819718u64}) + put(data, {4721732028538188150u64, 4530799784343874981u64, 6413561569u64}) + put(data, {2984214103553086219u64, 8561580552078486438u64, 225245615148u64}) + put(data, {13128675202005662068u64, 13349114951221999594u64, 44464124211u64}) + put(data, {14638512997670672834u64, 10029144738508991772u64, 51723656971u64}) + put(data, {12942085665769692438u64, 12601907197916268979u64, 11543681025u64}) + put(data, {14131134357119205086u64, 1329580921391066941u64, 1683150758u64}) + put(data, {8921946894736102919u64, 3198179786356761112u64, 166072076726u64}) + put(data, {5601522560505809989u64, 11406753413634654142u64, 182173373673u64}) + put(data, {8602606493507716808u64, 11131812960525182090u64, 233618361341u64}) + put(data, {8576789731078566487u64, 14299636753645227208u64, 253603456789u64}) + put(data, {17881118138842658549u64, 12964114684643663326u64, 21775184861u64}) + put(data, {11624372674432704923u64, 5019257593846306316u64, 221702786065u64}) + put(data, {6826284072848095635u64, 6929086798159998121u64, 17272094499u64}) + put(data, {1646466632033733563u64, 18359765766933703649u64, 35375626547u64}) + put(data, {17871081657060299180u64, 9993076234752063198u64, 51995284896u64}) + put(data, {15910893124677544709u64, 3257189215046584509u64, 160541725748u64}) + put(data, {11031217459450580944u64, 2905234736672690348u64, 52176572581u64}) + put(data, {13554987390037243094u64, 12064985302079670056u64, 165157493090u64}) + put(data, {15026714590903687870u64, 14315096064942799930u64, 98654044163u64}) + put(data, {4406379654994689200u64, 11943971043551974038u64, 3776022912u64}) + put(data, {13596329092861950242u64, 12472773152119929647u64, 128647483967u64}) + put(data, {284812388227373260u64, 7791259796982183085u64, 63676150387u64}) + put(data, {9285079159392309382u64, 16866829442051086686u64, 115422365039u64}) + put(data, {15046108141952711893u64, 3702498393844653053u64, 111914352656u64}) + put(data, {13795366909944958311u64, 2057239613841701716u64, 16200712840u64}) + put(data, {12909920641180059961u64, 17201969976738286226u64, 136111523182u64}) + put(data, {5333762939889788252u64, 18271566505443461640u64, 110932520660u64}) + put(data, {6411331390005944495u64, 18368509115417119804u64, 212990503604u64}) + put(data, {1447104583224217723u64, 7613923684154518587u64, 180995758874u64}) + put(data, {11940049226167932871u64, 17984805084714865232u64, 26412751629u64}) + put(data, {9772290783590472385u64, 4220802739051410373u64, 13974958237u64}) + put(data, {16351989577831528444u64, 17812459042810815760u64, 157228810174u64}) + put(data, {4376738725895725097u64, 10629526089664605307u64, 190965615339u64}) + put(data, {13851276297739812763u64, 17437443267816548473u64, 235576227763u64}) + put(data, {12641996203470333509u64, 12506371893701049304u64, 179945285693u64}) + put(data, {7707081716407945022u64, 15737221540003030739u64, 61677971778u64}) + put(data, {417638323657040024u64, 2358380859011605513u64, 66853116489u64}) + put(data, {16438047707692449100u64, 10042972713837039706u64, 73127848082u64}) + put(data, {14850108107043306316u64, 13424397272769642495u64, 146544430641u64}) + put(data, {10423290807904720835u64, 6867102315755663029u64, 49727738034u64}) + put(data, {16951162310302339314u64, 8690748404825506734u64, 178372266362u64}) + put(data, {2752437506572397322u64, 956229930815387710u64, 122471126415u64}) + put(data, {3925815842962784589u64, 7734449506297687888u64, 143051837328u64}) + put(data, {5274166674003605291u64, 16332184961683848151u64, 144419285347u64}) + put(data, {5538963350863452832u64, 15580777817612768828u64, 99885369520u64}) + put(data, {16900671634439028736u64, 17404245271944696092u64, 176844635657u64}) + put(data, {2326997710751662080u64, 13201420160494469229u64, 9943486026u64}) + put(data, {12327726161625874432u64, 16511717657124068078u64, 74715650420u64}) + put(data, {5756455743825903616u64, 14131292492116594062u64, 116895102007u64}) + put(data, {3018537650245074944u64, 18429136031865875691u64, 55766058900u64}) + put(data, {16717361816799281152u64, 2563978348305862197u64, 148999045466u64}) + put(data, {0u64, 14239974392147482896u64, 90138993544u64}) + put(data, {0u64, 11164201396098998272u64, 136771950558u64}) + put(data, {0u64, 7116971104932986880u64, 222605212570u64}) + put(data, {0u64, 12437629862867369984u64, 154385811776u64}) + put(data, {0u64, 16501893821638901760u64, 64674245265u64}) + put(data, {0u64, 10649324268870959104u64, 145894569456u64}) + put(data, {0u64, 7205759403792793600u64, 240577301025u64}) + put(data, {0u64, 0u64, 33390625000u64}) + put(data, {0u64, 0u64, 232000000000u64}) + put(data, {11997425759292732054u64, 136396630u64, 0u64}) + put(data, {11491152661270395161u64, 136396630650381753u64, 0u64}) + put(data, {18181063258234881272u64, 3016823727048309817u64, 7394076u64}) + put(data, {2466921813123869732u64, 17405973192644624358u64, 28163542341u64}) + put(data, {8430880678232179465u64, 8937219978302591747u64, 69943579697u64}) + put(data, {6738034873677997533u64, 15178463196824222317u64, 49484487665u64}) + put(data, {7678250951042929246u64, 11979404627460330594u64, 241822826138u64}) + put(data, {1853560606315563193u64, 2006448052689740002u64, 154649404826u64}) + put(data, {14942676593409905118u64, 16330465320863239865u64, 154108769766u64}) + put(data, {4909892170837638183u64, 17136208883957646553u64, 230885276298u64}) + put(data, {16871149368312132405u64, 140455118208931867u64, 138928955745u64}) + put(data, {16096130589333770811u64, 3964972929179372247u64, 97007614087u64}) + put(data, {12512479187631824282u64, 3378050330022776379u64, 135214941613u64}) + put(data, {16980304980540557310u64, 6065353437512901255u64, 173183124475u64}) + put(data, {8640919162749295366u64, 12768753059854699889u64, 251328803468u64}) + put(data, {7862382415464063513u64, 6848720690951013326u64, 140692195490u64}) + put(data, {14534157903009925344u64, 10953228058585475132u64, 162371269892u64}) + put(data, {12627464554215107944u64, 15539127852083296166u64, 4593775682u64}) + put(data, {2456849734836299173u64, 14534853647735598497u64, 66842377808u64}) + put(data, {18428252197697827913u64, 1506909603576368170u64, 80787935995u64}) + put(data, {7244734215936736255u64, 5475702579938239025u64, 251081689733u64}) + put(data, {14756175050504770087u64, 12039747373985783332u64, 133296838431u64}) + put(data, {6764116534566945922u64, 17572399137760898460u64, 31652676012u64}) + put(data, {1588822142405565521u64, 869552790852091236u64, 172952601666u64}) + put(data, {17053265624843842052u64, 4549585778048181804u64, 66047138551u64}) + put(data, {16996891591759999207u64, 4121918231767210357u64, 247246633539u64}) + put(data, {8565556232370585876u64, 1558397953312543179u64, 67223449635u64}) + put(data, {14464960359145886620u64, 6067524298738069781u64, 35084480922u64}) + put(data, {5813189542048784035u64, 5811095224555517056u64, 154328921151u64}) + put(data, {9739241026882027025u64, 6440894514158997188u64, 63315020103u64}) + put(data, {18175068535675302910u64, 4612748874388784257u64, 71349161591u64}) + put(data, {10562697212061761911u64, 9908101430749813367u64, 119250057617u64}) + put(data, {4264834835660801368u64, 15150017990912190499u64, 145537119254u64}) + put(data, {13019066443690126316u64, 17470426264690059239u64, 22821284120u64}) + put(data, {1828040177823321846u64, 9615161096851907726u64, 24947073705u64}) + put(data, {11240369830376975668u64, 9227932132124142224u64, 169521238927u64}) + put(data, {8886938465302549874u64, 4794113194321211621u64, 143500247203u64}) + put(data, {9985240313589688325u64, 391512698859146347u64, 163259889397u64}) + put(data, {722909126956573766u64, 17209658878068655842u64, 245021223945u64}) + put(data, {5493363474638452381u64, 3077364726606876150u64, 9932937477u64}) + put(data, {12410535279213120491u64, 1952989567673965814u64, 5166824276u64}) + put(data, {10543108918366869098u64, 11172860676923186449u64, 84105871776u64}) + put(data, {12953909016524823995u64, 17338078544784947239u64, 160605681990u64}) + put(data, {16505942145872588169u64, 4593380466519703278u64, 70939899121u64}) + put(data, {12428594380392015797u64, 786884753602720052u64, 241249007654u64}) + put(data, {7528259605829768337u64, 17848875822468020539u64, 38042657107u64}) + put(data, {18147447600042811311u64, 2899664567187130618u64, 83967589497u64}) + put(data, {12021069431116183911u64, 2973178834961857409u64, 121157191131u64}) + put(data, {11819985069665662506u64, 11117453141176836727u64, 219161176347u64}) + put(data, {10401877114068152814u64, 7535238370146462002u64, 27602678342u64}) + put(data, {4611631138117837942u64, 10246175467290865448u64, 70408486090u64}) + put(data, {17200813398607252417u64, 1203128834127050464u64, 202555446285u64}) + put(data, {14100466137553658767u64, 14518048959078919658u64, 13065221744u64}) + put(data, {17887776768825509301u64, 1553474987376920024u64, 112787025011u64}) + put(data, {12632656857970087269u64, 14956572380830948369u64, 115084214047u64}) + put(data, {8923681664054686256u64, 7594162606042048292u64, 31810797413u64}) + put(data, {6213926103737837599u64, 14461296147288811288u64, 101411680379u64}) + put(data, {1233118281776157762u64, 18305427728131488265u64, 123783948434u64}) + put(data, {30716279628678784u64, 10253208939347909876u64, 146992339225u64}) + put(data, {15775734650898546688u64, 6446028915490812012u64, 25555827570u64}) + put(data, {976806005729918976u64, 12986063676957432257u64, 114349439927u64}) + put(data, {12460098853279891456u64, 9769714697972762807u64, 183703975922u64}) + put(data, {5635665595421687808u64, 97429465146664592u64, 242529617295u64}) + put(data, {1805943450575568896u64, 16395571728207795868u64, 143005281661u64}) + put(data, {11529215046068469760u64, 6331668478323650406u64, 125888805724u64}) + put(data, {0u64, 18129911846294207040u64, 92343240435u64}) + put(data, {0u64, 9890094564876124160u64, 243982824490u64}) + put(data, {0u64, 12290856656987750400u64, 42536143100u64}) + put(data, {0u64, 8498454992640802816u64, 252666288674u64}) + put(data, {0u64, 5341660584200896512u64, 34460702168u64}) + put(data, {0u64, 9288674231451648000u64, 216289572000u64}) + put(data, {0u64, 1152921504606846976u64, 160503540039u64}) + put(data, {0u64, 0u64, 71062500000u64}) + put(data, {0u64, 0u64, 160000000000u64}) + put(data, {4564018338575530435u64, 2081u64, 0u64}) + put(data, {16553437246451512014u64, 2081247415929u64, 0u64}) + put(data, {4339777136957372927u64, 15212079674427582639u64, 112u64}) + put(data, {18439463366554654697u64, 10179808126814248333u64, 112824648491u64}) + put(data, {1370067356680643003u64, 6066766544199222848u64, 43551848504u64}) + put(data, {4210124040914115013u64, 6625308131806923532u64, 56328880073u64}) + put(data, {10692225626142609720u64, 9122786786400665713u64, 201359158673u64}) + put(data, {11592856673895384344u64, 11932880778639151320u64, 145494547262u64}) + put(data, {10284479231227406269u64, 3884040911779255011u64, 62646882763u64}) + put(data, {4961071383534266431u64, 13441817515637357872u64, 203210554279u64}) + put(data, {10960611551445686988u64, 11628577856022352826u64, 167728682387u64}) + put(data, {14616396723115619209u64, 13296656925520243654u64, 147630386468u64}) + put(data, {1025604265437492803u64, 5020720704545399398u64, 36720813216u64}) + put(data, {11711588454892179178u64, 14121973606499014694u64, 160272173814u64}) + put(data, {5580373263251565705u64, 3642481034345420114u64, 246765553723u64}) + put(data, {14109334653033148931u64, 9845536238569696768u64, 59197459292u64}) + put(data, {2899414033769399895u64, 17655403572195686356u64, 92533727588u64}) + put(data, {13233457234892808147u64, 8377495365136654029u64, 100957101345u64}) + put(data, {3287946691509034862u64, 13713682649609025426u64, 33454144933u64}) + put(data, {5488480288717445911u64, 1367709905452854731u64, 165743420226u64}) + put(data, {11687233053874362630u64, 9981467701727208680u64, 66074143702u64}) + put(data, {6783772100089274577u64, 6277920117543306205u64, 214541096448u64}) + put(data, {7898291058728402485u64, 9344111460418701726u64, 340326731u64}) + put(data, {4423684977486598491u64, 4918507011364617264u64, 75506545297u64}) + put(data, {2750833684599526706u64, 6554777203830755259u64, 145266632799u64}) + put(data, {15669689830489025709u64, 4198262173120265648u64, 95355335184u64}) + put(data, {15117307274214954517u64, 8080325935698446819u64, 16227588248u64}) + put(data, {8148639818575698175u64, 12797633874200091733u64, 152438035346u64}) + put(data, {13006484426078994901u64, 8376502502208665497u64, 146693761122u64}) + put(data, {10287496057845513526u64, 9891973386793349173u64, 98454091110u64}) + put(data, {14159876032966532430u64, 14877430279003795462u64, 102536244951u64}) + put(data, {15742212196465548019u64, 8759933935842067041u64, 215806507111u64}) + put(data, {2892220461917134150u64, 3753418510388703513u64, 103474876970u64}) + put(data, {7487151560715393883u64, 2961383332545305985u64, 42203473225u64}) + put(data, {7245756744165177933u64, 2497674184068629507u64, 73160536912u64}) + put(data, {3067122860671533987u64, 15244544070742305452u64, 80135399188u64}) + put(data, {8135043905834122525u64, 45953573565810823u64, 20826408390u64}) + put(data, {14730019368921022572u64, 3960077421351906445u64, 198002491148u64}) + put(data, {495969939682055458u64, 3173330011013883118u64, 12214676227u64}) + put(data, {5617761407265775598u64, 11026266219545759160u64, 3172026564u64}) + put(data, {2087044847072781811u64, 8886757764964685632u64, 196597735089u64}) + put(data, {15929674232061203330u64, 13952322129918090479u64, 177481752103u64}) + put(data, {8658086469608285873u64, 4127250666614902202u64, 39756356898u64}) + put(data, {18369871790780313570u64, 17649958504065306911u64, 34223738706u64}) + put(data, {3545648451947416750u64, 13269305359002216873u64, 82956806167u64}) + put(data, {13347376792767929959u64, 16236593433831947843u64, 23719330484u64}) + put(data, {14482164459838203025u64, 13580930396682424057u64, 180880187493u64}) + put(data, {8899577765623565820u64, 421976357197961116u64, 101736223712u64}) + put(data, {7095320096604405719u64, 2962130818798626533u64, 224022875384u64}) + put(data, {2968593824439315788u64, 8234383947306356345u64, 248160577433u64}) + put(data, {12621408323612585636u64, 4380469931801381425u64, 153446386848u64}) + put(data, {3954422936414648259u64, 15279887469027055916u64, 160237465750u64}) + put(data, {17143730087577690191u64, 8534542821713755552u64, 150828324359u64}) + put(data, {5033045529399041876u64, 7814613482565088782u64, 7462658493u64}) + put(data, {15857648521994521781u64, 13771954404705323224u64, 189423631045u64}) + put(data, {16655573486499109541u64, 4568173274762548144u64, 197746579144u64}) + put(data, {16652154439190075858u64, 8105292616250821343u64, 200247641169u64}) + put(data, {18016950600164130638u64, 2923678426777275612u64, 81439388793u64}) + put(data, {2086292996072613910u64, 1808633176918384049u64, 121158492925u64}) + put(data, {17324462585194799521u64, 18118642609460438969u64, 253098046200u64}) + put(data, {11079151463184927232u64, 18138164175864360870u64, 248982213583u64}) + put(data, {5239846817488961536u64, 4031433690465792404u64, 207983271850u64}) + put(data, {2778806963520143360u64, 5012226396942308537u64, 170218544458u64}) + put(data, {6240890740138835968u64, 7889712298793536835u64, 74271713337u64}) + put(data, {17250651344549707776u64, 13500762396543628804u64, 57427702160u64}) + put(data, {4197354852709302272u64, 501020624068841347u64, 144731877796u64}) + put(data, {9223372036854775808u64, 8370653768288261750u64, 164027160382u64}) + put(data, {0u64, 647579990023635200u64, 62453774050u64}) + put(data, {0u64, 11106569307181154304u64, 226035105381u64}) + put(data, {0u64, 10797461613892861952u64, 101602088328u64}) + put(data, {0u64, 17627230675448889344u64, 136585331566u64}) + put(data, {0u64, 12197735707942322176u64, 110955574089u64}) + put(data, {0u64, 12871287735024877568u64, 73661240577u64}) + put(data, {0u64, 4611686018427387904u64, 1697753906u64}) + put(data, {0u64, 0u64, 50250000000u64}) + put(data, {0u64, 0u64, 128000000000u64}) + put(data, {5654803392547571318u64, 31757315u64, 0u64}) + put(data, {2931628102185393332u64, 31757315306547506u64, 0u64}) + put(data, {15964697617980212305u64, 9451803574512021605u64, 1721567u64}) + put(data, {450380868305846606u64, 8662766454758138424u64, 223512383298u64}) + put(data, {14631133530814566148u64, 9207992007314947035u64, 66469609510u64}) + put(data, {31969822783742095u64, 17118602861291201802u64, 38499166246u64}) + put(data, {10437269029385743245u64, 11186560605745599967u64, 38928001320u64}) + put(data, {15196146496377392433u64, 10505549821532796847u64, 40606424665u64}) + put(data, {4409099735137480938u64, 18133667530488679216u64, 89569506996u64}) + put(data, {10644987914903248118u64, 10778135771244330799u64, 180983028086u64}) + put(data, {3154431617534062973u64, 17087985777033767391u64, 118584283910u64}) + put(data, {11702056331247960454u64, 2639185991757283040u64, 6926341565u64}) + put(data, {15575315065965259114u64, 5401720287293896400u64, 189143070559u64}) + put(data, {10759747609480050226u64, 9816495392633895233u64, 95292827843u64}) + put(data, {12538236653960743718u64, 10042051500090034990u64, 195532153281u64}) + put(data, {17857942663978005403u64, 11629689537856384738u64, 193544380702u64}) + put(data, {11443004154750813211u64, 2099086731766010483u64, 30630446733u64}) + put(data, {4004313188770806737u64, 13665537898516458594u64, 141113791719u64}) + put(data, {17134872954824183228u64, 16375672064669490764u64, 231740810293u64}) + put(data, {2659553912986171234u64, 7770550512184564348u64, 53887726961u64}) + put(data, {9501854300969137926u64, 6197048880720627314u64, 113421242387u64}) + put(data, {14528169966301018150u64, 17963594118523106281u64, 19335942692u64}) + put(data, {18172091996515901778u64, 8255454642407818663u64, 36973808388u64}) + put(data, {1133541705604751035u64, 16744201957549498409u64, 4447529092u64}) + put(data, {18280349987988641497u64, 17442505417202859722u64, 132907705006u64}) + put(data, {9936015874712336386u64, 6383975767786687150u64, 174945560113u64}) + put(data, {15876720399740689614u64, 15245442964998335795u64, 49346076019u64}) + put(data, {8618117825152456982u64, 2910016124519524433u64, 115826457119u64}) + put(data, {8085525680745921564u64, 3847913871169988463u64, 31157752290u64}) + put(data, {8072355444669730953u64, 17210451512590059177u64, 226208595828u64}) + put(data, {9395030504766848294u64, 17899408909991454145u64, 116932980445u64}) + put(data, {3537903114058185903u64, 5920601932753251608u64, 221970328901u64}) + put(data, {2126094743961928691u64, 16521781895108979744u64, 69320956473u64}) + put(data, {289185362555601115u64, 3697493405554698771u64, 57895647591u64}) + put(data, {16909937501450129614u64, 2816108280295732065u64, 103200441519u64}) + put(data, {14449642060360499058u64, 14251078772056398988u64, 175152661535u64}) + put(data, {12433818908498244393u64, 4543066550096031417u64, 31772552528u64}) + put(data, {11884444034578008581u64, 3099369389734296977u64, 80246280131u64}) + put(data, {988625838444140793u64, 5243484113636490986u64, 195168017151u64}) + put(data, {1675370907158909973u64, 6823370511605197226u64, 255284249843u64}) + put(data, {15920186275316934067u64, 11396290277624641942u64, 243369895656u64}) + put(data, {5600921198503757726u64, 15934361408437566099u64, 232617794133u64}) + put(data, {10457357161776341741u64, 14939272230935131954u64, 85863803462u64}) + put(data, {12225356765775740093u64, 7500666177940329347u64, 70809859570u64}) + put(data, {4486633318598164537u64, 4806714453065462270u64, 242406611928u64}) + put(data, {10302486602879381361u64, 11557851247268441487u64, 216260572512u64}) + put(data, {15536428611301239541u64, 10655523157206817381u64, 96626552371u64}) + put(data, {12026126645955462603u64, 14769600176490881210u64, 51577637067u64}) + put(data, {14877968141142123551u64, 16688495540925795167u64, 203800661629u64}) + put(data, {734560801645383190u64, 909793965395524173u64, 125904685156u64}) + put(data, {15648943144911081638u64, 12724590949761703756u64, 100049320029u64}) + put(data, {13664182862003235646u64, 10810739657314826395u64, 93689801457u64}) + put(data, {3895127525902132786u64, 2431218615388671301u64, 241586051371u64}) + put(data, {5249187334214137467u64, 4235001167959059286u64, 43131796625u64}) + put(data, {10642260063359027505u64, 6253317787396334247u64, 145229579873u64}) + put(data, {783598951897779422u64, 9534525563070371898u64, 97338993036u64}) + put(data, {5538576558607624843u64, 8392783992374030728u64, 140516867666u64}) + put(data, {15974581187564609611u64, 16356257019231647540u64, 82454973731u64}) + put(data, {7474269406918257428u64, 12896334001521091877u64, 35886674469u64}) + put(data, {8045286838779138019u64, 1427636373320877084u64, 37699111667u64}) + put(data, {8184246376556341732u64, 16116755731295043521u64, 243077392322u64}) + put(data, {1493267152679331840u64, 15945633911163986837u64, 194873691078u64}) + put(data, {10179074811222818816u64, 7510154241072743838u64, 198864414546u64}) + put(data, {3892499202005008384u64, 3571560509790395119u64, 82407126277u64}) + put(data, {10341173215925108736u64, 3576991649007035076u64, 5193614683u64}) + put(data, {6230307872002015232u64, 15509961892750732443u64, 91193909105u64}) + put(data, {9295429630892703744u64, 17789791359353349378u64, 113840796718u64}) + put(data, {0u64, 18331227331079738314u64, 46964386521u64}) + put(data, {0u64, 15386712883100476416u64, 217993737824u64}) + put(data, {0u64, 14082462055028752384u64, 96834115376u64}) + put(data, {0u64, 12919043128765186048u64, 48763411797u64}) + put(data, {0u64, 6125373368465096704u64, 85700342731u64}) + put(data, {0u64, 12335992698065387520u64, 203332057155u64}) + put(data, {0u64, 2774217370460225536u64, 67668735504u64}) + put(data, {0u64, 0u64, 16150390625u64}) + put(data, {0u64, 0u64, 97000000000u64}) + put(data, {10665454627995623288u64, 484u64, 0u64}) + put(data, {16803537892767562832u64, 484578175453u64, 0u64}) + put(data, {8099123106849104444u64, 4962829537462579598u64, 26u64}) + put(data, {7077413686679401728u64, 5711259460785241095u64, 26269035528u64}) + put(data, {13536636358372449666u64, 13845894607204897444u64, 8309607995u64}) + put(data, {7280632235418610318u64, 12116633056637003327u64, 59750587450u64}) + put(data, {6187823673116858809u64, 2965791047992089560u64, 58656843994u64}) + put(data, {8624343686231740255u64, 16021997451315962529u64, 218160775854u64}) + put(data, {806211610822132771u64, 3942052271663803856u64, 174868554222u64}) + put(data, {18388078233202190882u64, 15669876414782439922u64, 238213699081u64}) + put(data, {7628864426595573600u64, 10594415915406145286u64, 9849465702u64}) + put(data, {4530799784343874981u64, 10789820553031921377u64, 102574324437u64}) + put(data, {8561580552078486438u64, 3989990218583987244u64, 213584917344u64}) + put(data, {13349114951221999594u64, 2937341169808224563u64, 96216297803u64}) + put(data, {10029144738508991772u64, 16267436558584536843u64, 75159233583u64}) + put(data, {12601907197916268979u64, 16221580362814625793u64, 47881859502u64}) + put(data, {1329580921391066941u64, 9695437602320209830u64, 174879373633u64}) + put(data, {3198179786356761112u64, 10729753156793715126u64, 65525590725u64}) + put(data, {11406753413634654142u64, 2609241432056861929u64, 197581661084u64}) + put(data, {11131812960525182090u64, 8462663743997037565u64, 156141447261u64}) + put(data, {14299636753645227208u64, 14993422143908194069u64, 93458761920u64}) + put(data, {12964114684643663326u64, 1307443894537745373u64, 192812795043u64}) + put(data, {5019257593846306316u64, 10017257439419829265u64, 163070876675u64}) + put(data, {6929086798159998121u64, 16754772009970777891u64, 3543036613u64}) + put(data, {18359765766933703649u64, 11722573031602862387u64, 197908278010u64}) + put(data, {9993076234752063198u64, 7363764277467092384u64, 250635481957u64}) + put(data, {3257189215046584509u64, 6733958494847390772u64, 101399190461u64}) + put(data, {2905234736672690348u64, 8799796600227451045u64, 189365048621u64}) + put(data, {12064985302079670056u64, 10512023194742249826u64, 45477037929u64}) + put(data, {14315096064942799930u64, 4572542132337197059u64, 105569857919u64}) + put(data, {11943971043551974038u64, 12600500455757416832u64, 127247878005u64}) + put(data, {12472773152119929647u64, 7873789864743195199u64, 117683074498u64}) + put(data, {7791259796982183085u64, 15724851676325671539u64, 194426839003u64}) + put(data, {16866829442051086686u64, 8748017220462413167u64, 219852445917u64}) + put(data, {3702498393844653053u64, 14172589522760466448u64, 221474230963u64}) + put(data, {2057239613841701716u64, 9520545591489413768u64, 179768297617u64}) + put(data, {17201969976738286226u64, 12488551088392570222u64, 145516109810u64}) + put(data, {18271566505443461640u64, 1135798823651241684u64, 242677005711u64}) + put(data, {18368509115417119804u64, 11168725610120161972u64, 143061571777u64}) + put(data, {7613923684154518587u64, 9580104948718508826u64, 193605457828u64}) + put(data, {17984805084714865232u64, 16638722716909738765u64, 164519338529u64}) + put(data, {4220802739051410373u64, 15732724012348272797u64, 33901986965u64}) + put(data, {17812459042810815760u64, 12269722190021214142u64, 149852872677u64}) + put(data, {10629526089664605307u64, 13110655916311972587u64, 229665142972u64}) + put(data, {17437443267816548473u64, 6618112997062866867u64, 188710730081u64}) + put(data, {12506371893701049304u64, 8457936459015989309u64, 97358768624u64}) + put(data, {15737221540003030739u64, 3329167139937134914u64, 240458505654u64}) + put(data, {2358380859011605513u64, 5245511557216705097u64, 182180474512u64}) + put(data, {10042972713837039706u64, 5655931353280440466u64, 144284359751u64}) + put(data, {13424397272769642495u64, 604622132328697393u64, 71306608653u64}) + put(data, {6867102315755663029u64, 8673282619234652338u64, 13032776631u64}) + put(data, {8690748404825506734u64, 16929477433058445690u64, 183470179592u64}) + put(data, {956229930815387710u64, 11036952409253549455u64, 8917748810u64}) + put(data, {7734449506297687888u64, 18199392190170386320u64, 74598314388u64}) + put(data, {16332184961683848151u64, 9683116091880335715u64, 148986591027u64}) + put(data, {15580777817612768828u64, 2993913337608915120u64, 51524922775u64}) + put(data, {17404245271944696092u64, 4490779842162392585u64, 151162300367u64}) + put(data, {13201420160494469229u64, 946849923353644618u64, 207243445663u64}) + put(data, {16511717657124068078u64, 3613491058474899828u64, 159051328837u64}) + put(data, {14131292492116594062u64, 14624054199004410935u64, 69195887742u64}) + put(data, {18429136031865875691u64, 12088470271991908244u64, 126792771566u64}) + put(data, {2563978348305862197u64, 10071980927725011290u64, 238655317286u64}) + put(data, {14239974392147482896u64, 2833441711428854664u64, 38546003180u64}) + put(data, {11164201396098998272u64, 17655572411864340446u64, 236153601182u64}) + put(data, {7116971104932986880u64, 4997642792058747802u64, 158957110498u64}) + put(data, {12437629862867369984u64, 11489200787635734848u64, 226270922758u64}) + put(data, {16501893821638901760u64, 12983586226429536913u64, 6622830822u64}) + put(data, {10649324268870959104u64, 12311150768725063152u64, 230703841619u64}) + put(data, {7205759403792793600u64, 8530052476845967905u64, 83667388820u64}) + put(data, {0u64, 6282736361499820264u64, 148462415071u64}) + put(data, {0u64, 11337164765929082880u64, 223340587820u64}) + put(data, {0u64, 8343856200414134272u64, 44614588933u64}) + put(data, {0u64, 17889330377156198400u64, 5452321350u64}) + put(data, {0u64, 17730714064155312128u64, 70969782542u64}) + put(data, {0u64, 7449235258647511040u64, 14961183935u64}) + put(data, {0u64, 9943947977234055168u64, 191403823852u64}) + put(data, {0u64, 0u64, 236539062500u64}) + put(data, {0u64, 0u64, 228000000000u64}) + put(data, {3016823727048309817u64, 7394076u64, 0u64}) + put(data, {17405973192644624358u64, 7394076163542341u64, 0u64}) + put(data, {8937219978302591747u64, 12396245121240683569u64, 400833u64}) + put(data, {15178463196824222317u64, 10248996648596888561u64, 193672001794u64}) + put(data, {11979404627460330594u64, 11257495103713935002u64, 2555599221u64}) + put(data, {2006448052689740002u64, 7555396579247433114u64, 117610270032u64}) + put(data, {16330465320863239865u64, 4805022328730367462u64, 80409578869u64}) + put(data, {17136208883957646553u64, 7056637817080232586u64, 117260480782u64}) + put(data, {140455118208931867u64, 10811411483818434913u64, 14382541102u64}) + put(data, {3964972929179372247u64, 16962406704495245447u64, 46586087790u64}) + put(data, {3378050330022776379u64, 18074517319117194669u64, 110919533909u64}) + put(data, {6065353437512901255u64, 3702019776117654523u64, 85979821547u64}) + put(data, {12768753059854699889u64, 3551977551381082764u64, 235200686894u64}) + put(data, {6848720690951013326u64, 16442608985936005282u64, 46192553088u64}) + put(data, {10953228058585475132u64, 3580046275479139588u64, 128891355619u64}) + put(data, {15539127852083296166u64, 8737412692712715330u64, 227194074697u64}) + put(data, {14534853647735598497u64, 3082033243045084752u64, 73473656091u64}) + put(data, {1506909603576368170u64, 16401023756841128699u64, 27167077356u64}) + put(data, {5475702579938239025u64, 7520296082779572869u64, 236889101279u64}) + put(data, {12039747373985783332u64, 9854104766152464159u64, 223407676067u64}) + put(data, {17572399137760898460u64, 14169188802648310188u64, 163534192089u64}) + put(data, {869552790852091236u64, 2018609909210367042u64, 217768113264u64}) + put(data, {4549585778048181804u64, 8270271948267674359u64, 112109429062u64}) + put(data, {4121918231767210357u64, 12320338602894572099u64, 70448332340u64}) + put(data, {1558397953312543179u64, 17538536685990080547u64, 52667886893u64}) + put(data, {6067524298738069781u64, 15833914616956760474u64, 45950765978u64}) + put(data, {5811095224555517056u64, 6137696141415969855u64, 154858358231u64}) + put(data, {6440894514158997188u64, 9757490468419438919u64, 215332725174u64}) + put(data, {4612748874388784257u64, 3566639201356598903u64, 182528954618u64}) + put(data, {9908101430749813367u64, 9760900035773954449u64, 250193347898u64}) + put(data, {15150017990912190499u64, 3873778773990716438u64, 58529139451u64}) + put(data, {17470426264690059239u64, 2295668377270167832u64, 251209997968u64}) + put(data, {9615161096851907726u64, 1791721710912807593u64, 144124448432u64}) + put(data, {9227932132124142224u64, 10571009006922683279u64, 176097129428u64}) + put(data, {4794113194321211621u64, 9840791932778184867u64, 212573055546u64}) + put(data, {391512698859146347u64, 11525464956561274613u64, 58533470399u64}) + put(data, {17209658878068655842u64, 4435781488897895433u64, 191624796707u64}) + put(data, {3077364726606876150u64, 6395563367070996741u64, 35240464196u64}) + put(data, {1952989567673965814u64, 15538690795135662932u64, 68346704184u64}) + put(data, {11172860676923186449u64, 16294558813563371936u64, 56842354115u64}) + put(data, {17338078544784947239u64, 4942096228426070342u64, 195883329803u64}) + put(data, {4593380466519703278u64, 6910116424372647153u64, 11267911573u64}) + put(data, {786884753602720052u64, 17923400669760829478u64, 149374598161u64}) + put(data, {17848875822468020539u64, 4134686917293039955u64, 17971629497u64}) + put(data, {2899664567187130618u64, 16857102463116098681u64, 185224141826u64}) + put(data, {2973178834961857409u64, 11364321508775167451u64, 2913825355u64}) + put(data, {11117453141176836727u64, 7966947780972783899u64, 75616061103u64}) + put(data, {7535238370146462002u64, 11261055695926686278u64, 175431889104u64}) + put(data, {10246175467290865448u64, 9227040437353594058u64, 208610463052u64}) + put(data, {1203128834127050464u64, 7185344074282882061u64, 76500198864u64}) + put(data, {14518048959078919658u64, 14197856148610578032u64, 208389518282u64}) + put(data, {1553474987376920024u64, 885688687260429427u64, 202769667324u64}) + put(data, {14956572380830948369u64, 17407816160380305183u64, 252048013279u64}) + put(data, {7594162606042048292u64, 17812728703806357349u64, 223943679604u64}) + put(data, {14461296147288811288u64, 17120198191964319867u64, 116965629957u64}) + put(data, {18305427728131488265u64, 12091952048375408786u64, 5928087803u64}) + put(data, {10253208939347909876u64, 405056939269888281u64, 251655506034u64}) + put(data, {6446028915490812012u64, 12485440679452408690u64, 114021958180u64}) + put(data, {12986063676957432257u64, 8394369900823444407u64, 36676837095u64}) + put(data, {9769714697972762807u64, 2877421667354294258u64, 231455059704u64}) + put(data, {97429465146664592u64, 2676980714750756239u64, 248155985341u64}) + put(data, {16395571728207795868u64, 6119309228579057021u64, 189145119415u64}) + put(data, {6331668478323650406u64, 18203256146533333852u64, 183331728417u64}) + put(data, {18129911846294207040u64, 351919978865493747u64, 33986800493u64}) + put(data, {9890094564876124160u64, 5190010931882390570u64, 109019077620u64}) + put(data, {12290856656987750400u64, 6982466386088036604u64, 244281351056u64}) + put(data, {8498454992640802816u64, 4707293888784996898u64, 144378520261u64}) + put(data, {5341660584200896512u64, 690306801165964760u64, 197255182913u64}) + put(data, {9288674231451648000u64, 12456770961278956704u64, 65037421606u64}) + put(data, {1152921504606846976u64, 16946092489294063943u64, 38675282906u64}) + put(data, {0u64, 11098404173866185376u64, 218918649514u64}) + put(data, {0u64, 15152070965853306880u64, 170601645695u64}) + put(data, {0u64, 17370091362040414208u64, 127821395412u64}) + put(data, {0u64, 10141938552171134976u64, 212941634539u64}) + put(data, {0u64, 10586988556645826560u64, 235549795590u64}) + put(data, {0u64, 12169852093061922816u64, 6573921799u64}) + put(data, {0u64, 16717361816799281152u64, 7659729003u64}) + put(data, {0u64, 0u64, 107906250000u64}) + put(data, {0u64, 0u64, 16000000000u64}) + put(data, {15212079674427582639u64, 112u64, 0u64}) + put(data, {10179808126814248333u64, 112824648491u64, 0u64}) + put(data, {6066766544199222848u64, 2144184049294538808u64, 6u64}) + put(data, {6625308131806923532u64, 4108002197393276873u64, 6116236450u64}) + put(data, {9122786786400665713u64, 6446230217393892753u64, 162222695245u64}) + put(data, {11932880778639151320u64, 5571068025259989822u64, 77349450840u64}) + put(data, {3884040911779255011u64, 14804812668872528331u64, 88302008202u64}) + put(data, {13441817515637357872u64, 17369928488562523047u64, 138802570502u64}) + put(data, {11628577856022352826u64, 2967474173531035027u64, 6941625710u64}) + put(data, {13296656925520243654u64, 5291425437992807716u64, 110160867097u64}) + put(data, {5020720704545399398u64, 14219547193739388064u64, 25286848747u64}) + put(data, {14121973606499014694u64, 17720313647158217462u64, 235770843197u64}) + put(data, {3642481034345420114u64, 12334850628290578491u64, 61960620127u64}) + put(data, {9845536238569696768u64, 7818499847417334620u64, 95668673592u64}) + put(data, {17655403572195686356u64, 136007040922198372u64, 56423841726u64}) + put(data, {8377495365136654029u64, 8523477092112604449u64, 190007372956u64}) + put(data, {13713682649609025426u64, 367934822655966629u64, 156462058619u64}) + put(data, {1367709905452854731u64, 12964987687054730050u64, 123019945786u64}) + put(data, {9981467701727208680u64, 15267036012420885462u64, 58702833390u64}) + put(data, {6277920117543306205u64, 11142900264750765568u64, 238827627680u64}) + put(data, {9344111460418701726u64, 13680181547777718603u64, 160604057833u64}) + put(data, {4918507011364617264u64, 13001922925761426065u64, 233741604127u64}) + put(data, {6554777203830755259u64, 2397730045956515935u64, 31704835654u64}) + put(data, {4198262173120265648u64, 4482395522588406288u64, 70129981206u64}) + put(data, {8080325935698446819u64, 3255525722490493080u64, 22242991148u64}) + put(data, {12797633874200091733u64, 836222287193822098u64, 44176482403u64}) + put(data, {8376502502208665497u64, 420898743993182306u64, 99045331701u64}) + put(data, {9891973386793349173u64, 11652649973356574054u64, 245022816966u64}) + put(data, {14877430279003795462u64, 15058402726661910231u64, 198631691420u64}) + put(data, {8759933935842067041u64, 9600134495208339559u64, 156816317647u64}) + put(data, {3753418510388703513u64, 14626343323989004842u64, 207520424333u64}) + put(data, {2961383332545305985u64, 6813981265331086665u64, 141792895660u64}) + put(data, {2497674184068629507u64, 10281745288790487888u64, 172369386664u64}) + put(data, {15244544070742305452u64, 17569829347075761940u64, 168557374528u64}) + put(data, {45953573565810823u64, 7654580675237889478u64, 64952462357u64}) + put(data, {3960077421351906445u64, 16194838649686212364u64, 21414955649u64}) + put(data, {3173330011013883118u64, 6495102772252453635u64, 129877923962u64}) + put(data, {11026266219545759160u64, 14935159852819761348u64, 122352100226u64}) + put(data, {8886757764964685632u64, 17381879863441579697u64, 130809636637u64}) + put(data, {13952322129918090479u64, 9062335510435372583u64, 29942273595u64}) + put(data, {4127250666614902202u64, 7569219009130126626u64, 59491270192u64}) + put(data, {17649958504065306911u64, 12652124168176193362u64, 48410328184u64}) + put(data, {13269305359002216873u64, 8940200224697247767u64, 120685873025u64}) + put(data, {16236593433831947843u64, 5600570701927432884u64, 129484649225u64}) + put(data, {13580930396682424057u64, 2018432801986093157u64, 9303607546u64}) + put(data, {421976357197961116u64, 8235849749361824736u64, 250109419461u64}) + put(data, {2962130818798626533u64, 9705097287982370040u64, 197446466309u64}) + put(data, {8234383947306356345u64, 3517483139049842585u64, 5526114378u64}) + put(data, {4380469931801381425u64, 958281614186777760u64, 74190683143u64}) + put(data, {15279887469027055916u64, 7336473432636108950u64, 7051948550u64}) + put(data, {8534542821713755552u64, 12955383920176764423u64, 6397711021u64}) + put(data, {7814613482565088782u64, 10735469126281273789u64, 173702312769u64}) + put(data, {13771954404705323224u64, 8637888232514730693u64, 65581970947u64}) + put(data, {4568173274762548144u64, 6806336737533581000u64, 3468260859u64}) + put(data, {8105292616250821343u64, 16142569672872330321u64, 251368972253u64}) + put(data, {2923678426777275612u64, 8141285259947963513u64, 221875090455u64}) + put(data, {1808633176918384049u64, 5220241098754220797u64, 23441339958u64}) + put(data, {18118642609460438969u64, 154438799943119608u64, 54282989837u64}) + put(data, {18138164175864360870u64, 2226876628677628879u64, 13008372144u64}) + put(data, {4031433690465792404u64, 17219557081221357482u64, 176120719223u64}) + put(data, {5012226396942308537u64, 15401507148161015114u64, 119933474059u64}) + put(data, {7889712298793536835u64, 8842629766613985337u64, 11834917375u64}) + put(data, {13500762396543628804u64, 3180100571546071440u64, 255479359920u64}) + put(data, {501020624068841347u64, 7740848704392475044u64, 176172393597u64}) + put(data, {8370653768288261750u64, 2014314126623495998u64, 125419632249u64}) + put(data, {647579990023635200u64, 11209566016506885858u64, 121109196187u64}) + put(data, {11106569307181154304u64, 7117166613733441125u64, 155607671791u64}) + put(data, {10797461613892861952u64, 4197646860931880328u64, 239385822375u64}) + put(data, {17627230675448889344u64, 5487263271238026094u64, 167227554892u64}) + put(data, {12197735707942322176u64, 18148076225293562697u64, 76297465137u64}) + put(data, {12871287735024877568u64, 9127276943027950849u64, 49983809183u64}) + put(data, {4611686018427387904u64, 9691696125379324722u64, 159494790674u64}) + put(data, {0u64, 13102362262487705216u64, 18525387899u64}) + put(data, {0u64, 8929385439893192704u64, 123710280481u64}) + put(data, {0u64, 11891353410743566336u64, 33484062954u64}) + put(data, {0u64, 1587423090877399040u64, 234644631560u64}) + put(data, {0u64, 3489137423026225152u64, 8086054378u64}) + put(data, {0u64, 13046928120492326912u64, 234189146518u64}) + put(data, {0u64, 11529215046068469760u64, 150707275390u64}) + put(data, {0u64, 0u64, 126625000000u64}) + put(data, {0u64, 0u64, 64000000000u64}) + put(data, {9451803574512021605u64, 1721567u64, 0u64}) + put(data, {8662766454758138424u64, 1721567512383298u64, 0u64}) + put(data, {9207992007314947035u64, 6674960280855494694u64, 93326u64}) + put(data, {17118602861291201802u64, 16378845781483497510u64, 142361850321u64}) + put(data, {11186560605745599967u64, 17606907750956804392u64, 209887899008u64}) + put(data, {10505549821532796847u64, 13225609159240506969u64, 128954472381u64}) + put(data, {18133667530488679216u64, 2668084873338435252u64, 189716961709u64}) + put(data, {10778135771244330799u64, 14802814305275861366u64, 173144637170u64}) + put(data, {17087985777033767391u64, 8005510553372365574u64, 242802462171u64}) + put(data, {2639185991757283040u64, 12748500143273514429u64, 219433979596u64}) + put(data, {5401720287293896400u64, 10393733905569036127u64, 204691097577u64}) + put(data, {9816495392633895233u64, 603389089974790339u64, 233563445444u64}) + put(data, {10042051500090034990u64, 2033494532597735873u64, 196032709788u64}) + put(data, {11629689537856384738u64, 9204796763694620958u64, 156110235959u64}) + put(data, {2099086731766010483u64, 7826260310402107021u64, 55498993032u64}) + put(data, {13665537898516458594u64, 10122690201685169383u64, 136424262421u64}) + put(data, {16375672064669490764u64, 7438455564568110133u64, 21548752135u64}) + put(data, {7770550512184564348u64, 2805412574380520817u64, 7403239484u64}) + put(data, {6197048880720627314u64, 7250965427231182867u64, 60152081720u64}) + put(data, {17963594118523106281u64, 8136242944826085924u64, 56393075623u64}) + put(data, {8255454642407818663u64, 15357191647956011780u64, 167441066613u64}) + put(data, {16744201957549498409u64, 7369614426695395460u64, 117832515027u64}) + put(data, {17442505417202859722u64, 10886957545142526638u64, 211399507598u64}) + put(data, {6383975767786687150u64, 2030047207417538097u64, 142590183151u64}) + put(data, {15245442964998335795u64, 11557093828502314355u64, 239110049079u64}) + put(data, {2910016124519524433u64, 15201062539664128543u64, 55626511311u64}) + put(data, {3847913871169988463u64, 8846936323343880674u64, 207824051251u64}) + put(data, {17210451512590059177u64, 1485291750116245364u64, 51479593379u64}) + put(data, {17899408909991454145u64, 2076024439668322013u64, 163080517827u64}) + put(data, {5920601932753251608u64, 7029497773682748741u64, 195112541510u64}) + put(data, {16521781895108979744u64, 16333533921668749881u64, 70381069837u64}) + put(data, {3697493405554698771u64, 2065057316131928423u64, 13885442648u64}) + put(data, {2816108280295732065u64, 7800502648925570223u64, 88111946981u64}) + put(data, {14251078772056398988u64, 17011619967093802015u64, 229422866095u64}) + put(data, {4543066550096031417u64, 5368819344429198672u64, 175922201766u64}) + put(data, {3099369389734296977u64, 15598879366754275267u64, 166291044279u64}) + put(data, {5243484113636490986u64, 16393893486035835647u64, 183845616944u64}) + put(data, {6823370511605197226u64, 12042046205096920307u64, 48888714746u64}) + put(data, {11396290277624641942u64, 15437070428008474344u64, 250652800632u64}) + put(data, {15934361408437566099u64, 13704569163204647509u64, 120836845264u64}) + put(data, {14939272230935131954u64, 18192483750856993350u64, 208742926182u64}) + put(data, {7500666177940329347u64, 5152535865317963250u64, 102986216520u64}) + put(data, {4806714453065462270u64, 17512614083933854680u64, 72279319528u64}) + put(data, {11557851247268441487u64, 14481918350603613536u64, 232949360711u64}) + put(data, {10655523157206817381u64, 16124419709964004915u64, 71785066366u64}) + put(data, {14769600176490881210u64, 18088011566435813579u64, 126874106543u64}) + put(data, {16688495540925795167u64, 15008862380698848893u64, 175980553071u64}) + put(data, {909793965395524173u64, 18160498644611827812u64, 111813632059u64}) + put(data, {12724590949761703756u64, 3604680497457231965u64, 59984482604u64}) + put(data, {10810739657314826395u64, 5957615565551495921u64, 44195410121u64}) + put(data, {2431218615388671301u64, 17528455034961565995u64, 201322962986u64}) + put(data, {4235001167959059286u64, 8503772325120113809u64, 42950219451u64}) + put(data, {6253317787396334247u64, 8501492578048509537u64, 187460990421u64}) + put(data, {9534525563070371898u64, 2296237701094386060u64, 213460866836u64}) + put(data, {8392783992374030728u64, 3753593040591076946u64, 20124479295u64}) + put(data, {16356257019231647540u64, 8518075399775653155u64, 63203482686u64}) + put(data, {12896334001521091877u64, 12757855675959554597u64, 62461765792u64}) + put(data, {1427636373320877084u64, 121631169379748595u64, 160691604742u64}) + put(data, {16116755731295043521u64, 16679062494579173314u64, 6006593638u64}) + put(data, {15945633911163986837u64, 10739912744743898054u64, 102904173789u64}) + put(data, {7510154241072743838u64, 9367340677776287570u64, 221582211836u64}) + put(data, {3571560509790395119u64, 12227321512794715397u64, 252507804555u64}) + put(data, {3576991649007035076u64, 7241061891859170651u64, 139662844427u64}) + put(data, {15509961892750732443u64, 13148571323079237489u64, 11392538751u64}) + put(data, {17789791359353349378u64, 12509763434355012654u64, 127712785479u64}) + put(data, {18331227331079738314u64, 11812768946960181977u64, 71678155634u64}) + put(data, {15386712883100476416u64, 14170358803552564832u64, 114640371487u64}) + put(data, {14082462055028752384u64, 18179989524780635952u64, 31768176689u64}) + put(data, {12919043128765186048u64, 17091718978514754901u64, 49985539206u64}) + put(data, {6125373368465096704u64, 7394768384359232459u64, 134926543942u64}) + put(data, {12335992698065387520u64, 6778628272692852803u64, 70400871197u64}) + put(data, {2774217370460225536u64, 18193335045875234320u64, 29367470174u64}) + put(data, {0u64, 1378519212560967521u64, 94986262669u64}) + put(data, {0u64, 4677732610631043584u64, 141074729676u64}) + put(data, {0u64, 17296098591070486528u64, 204253580392u64}) + put(data, {0u64, 7343735382392963072u64, 104937623383u64}) + put(data, {0u64, 14525996728454217728u64, 87398104692u64}) + put(data, {0u64, 9691359370008330240u64, 116787455860u64}) + put(data, {0u64, 3044433348102455296u64, 116525369644u64}) + put(data, {0u64, 9223372036854775808u64, 44165039062u64}) + put(data, {0u64, 0u64, 214500000000u64}) + put(data, {4962829537462579598u64, 26u64, 0u64}) + put(data, {5711259460785241095u64, 26269035528u64, 0u64}) + put(data, {13845894607204897444u64, 7822291454600056379u64, 1u64}) + put(data, {12116633056637003327u64, 8201586317771250746u64, 1424047269u64}) + put(data, {2965791047992089560u64, 3278889188817135834u64, 165444608885u64}) + put(data, {16021997451315962529u64, 1710725240251040430u64, 117177748939u64}) + put(data, {3942052271663803856u64, 1850175733663425006u64, 203092738601u64}) + put(data, {15669876414782439922u64, 9147599666163914249u64, 41100298227u64}) + put(data, {10594415915406145286u64, 10221885933644344166u64, 243495892371u64}) + put(data, {10789820553031921377u64, 14901479793736678101u64, 147554129546u64}) + put(data, {3989990218583987244u64, 5181831442059703136u64, 138807810838u64}) + put(data, {2937341169808224563u64, 6396246577759793483u64, 22280907645u64}) + put(data, {16267436558584536843u64, 14167229556464870447u64, 125346741221u64}) + put(data, {16221580362814625793u64, 2969982933326311854u64, 229768007053u64}) + put(data, {9695437602320209830u64, 7892677766222018881u64, 141161003097u64}) + put(data, {10729753156793715126u64, 798698968922663621u64, 89427862919u64}) + put(data, {2609241432056861929u64, 15926812109043458972u64, 135043297557u64}) + put(data, {8462663743997037565u64, 8663842590352697437u64, 21863394214u64}) + put(data, {14993422143908194069u64, 17093523026636671168u64, 166469667847u64}) + put(data, {1307443894537745373u64, 839764004742743203u64, 7926641740u64}) + put(data, {10017257439419829265u64, 16894643909298232323u64, 76045523697u64}) + put(data, {16754772009970777891u64, 9066702926218949317u64, 241915860481u64}) + put(data, {11722573031602862387u64, 9119392417260546810u64, 1491506950u64}) + put(data, {7363764277467092384u64, 9723021096578315109u64, 6494363253u64}) + put(data, {6733958494847390772u64, 14787464248751217597u64, 117527086029u64}) + put(data, {8799796600227451045u64, 3733434565920249133u64, 205801630043u64}) + put(data, {10512023194742249826u64, 6643788868836820841u64, 91202389893u64}) + put(data, {4572542132337197059u64, 4729646697422664063u64, 133360160516u64}) + put(data, {12600500455757416832u64, 4090144564201555829u64, 4256394661u64}) + put(data, {7873789864743195199u64, 2109480737093400002u64, 165221727181u64}) + put(data, {15724851676325671539u64, 16577155033369419739u64, 205114355179u64}) + put(data, {8748017220462413167u64, 745377248603805917u64, 235898649375u64}) + put(data, {14172589522760466448u64, 11305561465807999667u64, 31040406981u64}) + put(data, {9520545591489413768u64, 2211245518782892177u64, 197612875715u64}) + put(data, {12488551088392570222u64, 14170095199249735666u64, 195119871859u64}) + put(data, {1135798823651241684u64, 17849973668116118927u64, 115768162399u64}) + put(data, {11168725610120161972u64, 9020960204585720001u64, 95967649011u64}) + put(data, {9580104948718508826u64, 10807134002871850916u64, 243489027232u64}) + put(data, {16638722716909738765u64, 3925122626254791201u64, 160585855908u64}) + put(data, {15732724012348272797u64, 17208463291312718997u64, 164212781323u64}) + put(data, {12269722190021214142u64, 5145077219589447653u64, 11932872664u64}) + put(data, {13110655916311972587u64, 17602397765035489468u64, 216278915194u64}) + put(data, {6618112997062866867u64, 16422643262490753377u64, 122954227894u64}) + put(data, {8457936459015989309u64, 2902509461400906224u64, 182890273275u64}) + put(data, {3329167139937134914u64, 3422418805967265206u64, 251157345353u64}) + put(data, {5245511557216705097u64, 4228874576277237392u64, 73185529695u64}) + put(data, {5655931353280440466u64, 2553488530807495751u64, 95229247750u64}) + put(data, {604622132328697393u64, 11546099176912486413u64, 6138424890u64}) + put(data, {8673282619234652338u64, 10460791037534167991u64, 58625915290u64}) + put(data, {16929477433058445690u64, 8127117908566000904u64, 154567080618u64}) + put(data, {11036952409253549455u64, 11541304458088287306u64, 170440571944u64}) + put(data, {18199392190170386320u64, 6249718665174839700u64, 40625655368u64}) + put(data, {9683116091880335715u64, 13102508413386290995u64, 72338797927u64}) + put(data, {2993913337608915120u64, 6274675218640661911u64, 103710288404u64}) + put(data, {4490779842162392585u64, 3404497118599817167u64, 20340150825u64}) + put(data, {946849923353644618u64, 11258566093988562335u64, 41184558158u64}) + put(data, {3613491058474899828u64, 16762592482501635397u64, 78610328090u64}) + put(data, {14624054199004410935u64, 5550125446725071998u64, 26908701959u64}) + put(data, {12088470271991908244u64, 6370033225258510318u64, 7300872903u64}) + put(data, {10071980927725011290u64, 1503521728674735398u64, 199345320193u64}) + put(data, {2833441711428854664u64, 4250415082606384364u64, 1081506076u64}) + put(data, {17655572411864340446u64, 6020091901030562974u64, 28230415463u64}) + put(data, {4997642792058747802u64, 16288222967151527138u64, 103326349835u64}) + put(data, {11489200787635734848u64, 6377016228656203782u64, 11882986336u64}) + put(data, {12983586226429536913u64, 8378856515587563750u64, 96345698742u64}) + put(data, {12311150768725063152u64, 15812881490200838483u64, 182454218721u64}) + put(data, {8530052476845967905u64, 4548570371183413652u64, 225857218023u64}) + put(data, {6282736361499820264u64, 16731431495283420383u64, 231246578493u64}) + put(data, {11337164765929082880u64, 14737727629551135532u64, 61907012718u64}) + put(data, {8343856200414134272u64, 12413722258104293893u64, 110798933815u64}) + put(data, {17889330377156198400u64, 800899742400762438u64, 55672949232u64}) + put(data, {17730714064155312128u64, 603197008376033550u64, 240043416862u64}) + put(data, {7449235258647511040u64, 6380777281587743935u64, 30032699375u64}) + put(data, {9943947977234055168u64, 10001440249018225388u64, 239345902629u64}) + put(data, {0u64, 5505914461980436708u64, 37542179162u64}) + put(data, {0u64, 1105464290051876864u64, 90298476221u64}) + put(data, {0u64, 4500443576769970176u64, 189059927339u64}) + put(data, {0u64, 2843045143185981440u64, 43243969535u64}) + put(data, {0u64, 660949699682893824u64, 255154121786u64}) + put(data, {0u64, 276549164618219520u64, 58035830155u64}) + put(data, {0u64, 4683743612465315840u64, 139014991760u64}) + put(data, {0u64, 0u64, 144253906250u64}) + put(data, {0u64, 0u64, 74000000000u64}) + put(data, {12396245121240683569u64, 400833u64, 0u64}) + put(data, {10248996648596888561u64, 400833672001794u64, 0u64}) + put(data, {11257495103713935002u64, 4370024159708535157u64, 21729u64}) + put(data, {7555396579247433114u64, 7166684413908503888u64, 225236899484u64}) + put(data, {4805022328730367462u64, 10217286283215687029u64, 156388506740u64}) + put(data, {7056637817080232586u64, 4767369911989629198u64, 116553880199u64}) + put(data, {10811411483818434913u64, 14407999214182082862u64, 135258439640u64}) + put(data, {16962406704495245447u64, 8472271297615317358u64, 216781059202u64}) + put(data, {18074517319117194669u64, 6236024012584764757u64, 130459282747u64}) + put(data, {3702019776117654523u64, 1951826556984620523u64, 59338055539u64}) + put(data, {3551977551381082764u64, 12357130551551830830u64, 115105808729u64}) + put(data, {16442608985936005282u64, 8927758011099278464u64, 89669881389u64}) + put(data, {3580046275479139588u64, 10199854049407140323u64, 45483974731u64}) + put(data, {8737412692712715330u64, 17895455027038549577u64, 75552935195u64}) + put(data, {3082033243045084752u64, 16539200343720527131u64, 27970114560u64}) + put(data, {16401023756841128699u64, 3536976106235802604u64, 896591847u64}) + put(data, {7520296082779572869u64, 16980391644793590751u64, 231191739858u64}) + put(data, {9854104766152464159u64, 10090294316609084067u64, 210920508875u64}) + put(data, {14169188802648310188u64, 17603457857266236889u64, 203546995950u64}) + put(data, {2018609909210367042u64, 11164962743035868272u64, 238954285362u64}) + put(data, {8270271948267674359u64, 1585686890718568774u64, 50605253843u64}) + put(data, {12320338602894572099u64, 10882524700472655412u64, 211085960258u64}) + put(data, {17538536685990080547u64, 2194808754940947757u64, 66589942846u64}) + put(data, {15833914616956760474u64, 274100791137209242u64, 62118980821u64}) + put(data, {6137696141415969855u64, 12203404582981010903u64, 213014859033u64}) + put(data, {9757490468419438919u64, 541940706340938166u64, 25661547888u64}) + put(data, {3566639201356598903u64, 10305434016011833594u64, 112029378664u64}) + put(data, {9760900035773954449u64, 7900783531944543546u64, 104558658697u64}) + put(data, {3873778773990716438u64, 8920818625012419323u64, 137428302333u64}) + put(data, {2295668377270167832u64, 12532363335400447632u64, 253483598546u64}) + put(data, {1791721710912807593u64, 13483507182924762800u64, 210679380777u64}) + put(data, {10571009006922683279u64, 415911049779278804u64, 41730942389u64}) + put(data, {9840791932778184867u64, 3441628281170127418u64, 181022546583u64}) + put(data, {11525464956561274613u64, 17830811568183566527u64, 151186571042u64}) + put(data, {4435781488897895433u64, 17897295813176613411u64, 34966610231u64}) + put(data, {6395563367070996741u64, 2086148701331574596u64, 55970214350u64}) + put(data, {15538690795135662932u64, 13015567826878853432u64, 206113090347u64}) + put(data, {16294558813563371936u64, 12944531121587846595u64, 43705575345u64}) + put(data, {4942096228426070342u64, 3534180912913737995u64, 177701724438u64}) + put(data, {6910116424372647153u64, 3447584022400118677u64, 22191588331u64}) + put(data, {17923400669760829478u64, 6375676813770849297u64, 235186893904u64}) + put(data, {4134686917293039955u64, 11580694081479200185u64, 80345626132u64}) + put(data, {16857102463116098681u64, 1872134358882196482u64, 20627790684u64}) + put(data, {11364321508775167451u64, 17602652840520938059u64, 92101488606u64}) + put(data, {7966947780972783899u64, 10331040597716338351u64, 222954241722u64}) + put(data, {11261055695926686278u64, 73785407041056976u64, 186560046833u64}) + put(data, {9227040437353594058u64, 17166209109167902028u64, 241003999914u64}) + put(data, {7185344074282882061u64, 8762475644006589904u64, 170930582060u64}) + put(data, {14197856148610578032u64, 8839001228645872586u64, 44475014756u64}) + put(data, {885688687260429427u64, 13558262784529110268u64, 100479163216u64}) + put(data, {17407816160380305183u64, 5640853896420358111u64, 80734994898u64}) + put(data, {17812728703806357349u64, 8459930353450835572u64, 210305791302u64}) + put(data, {17120198191964319867u64, 7643830211500171269u64, 70458613743u64}) + put(data, {12091952048375408786u64, 1308629115231236347u64, 239414372866u64}) + put(data, {405056939269888281u64, 8957268500971669618u64, 2070940926u64}) + put(data, {12485440679452408690u64, 7645679094277669412u64, 254485574498u64}) + put(data, {8394369900823444407u64, 3821107497040617191u64, 98414473094u64}) + put(data, {2877421667354294258u64, 8847137191985934072u64, 134207142652u64}) + put(data, {2676980714750756239u64, 3531126524756088253u64, 252479604268u64}) + put(data, {6119309228579057021u64, 8726915034124352183u64, 44191422752u64}) + put(data, {18203256146533333852u64, 17611136727168068641u64, 32473087011u64}) + put(data, {351919978865493747u64, 18017743272784259949u64, 35954701634u64}) + put(data, {5190010931882390570u64, 18113575006829616116u64, 66976743819u64}) + put(data, {6982466386088036604u64, 12805550441678740368u64, 139981938868u64}) + put(data, {4707293888784996898u64, 8061966093393027781u64, 180694190280u64}) + put(data, {690306801165964760u64, 11954593141554100801u64, 200437040057u64}) + put(data, {12456770961278956704u64, 14068656112359197734u64, 185648059792u64}) + put(data, {16946092489294063943u64, 895878255770467290u64, 144762663376u64}) + put(data, {11098404173866185376u64, 10319906489512197802u64, 208048565657u64}) + put(data, {15152070965853306880u64, 14551142616794302079u64, 153559443251u64}) + put(data, {17370091362040414208u64, 15933181735739307476u64, 51788819021u64}) + put(data, {10141938552171134976u64, 11524527334398983147u64, 77863739512u64}) + put(data, {10586988556645826560u64, 11828012606225556742u64, 120624745878u64}) + put(data, {12169852093061922816u64, 3556238869349799431u64, 150641197848u64}) + put(data, {16717361816799281152u64, 7403090230513381483u64, 24192784095u64}) + put(data, {0u64, 10172292854665622800u64, 223401322325u64}) + put(data, {0u64, 11240746576366182400u64, 85551441100u64}) + put(data, {0u64, 17021927826892259328u64, 204609362092u64}) + put(data, {0u64, 9046328496309141504u64, 172922760556u64}) + put(data, {0u64, 8038996803112140800u64, 108490402450u64}) + put(data, {0u64, 17098478935265509376u64, 146435794889u64}) + put(data, {0u64, 7205759403792793600u64, 201926910400u64}) + put(data, {0u64, 0u64, 192390625000u64}) + put(data, {0u64, 0u64, 232000000000u64}) + put(data, {2144184049294538808u64, 6u64, 0u64}) + put(data, {4108002197393276873u64, 6116236450u64, 0u64}) + put(data, {6446230217393892753u64, 6116236450222695245u64, 0u64}) + put(data, {5571068025259989822u64, 6240972538554414168u64, 331561842u64}) + put(data, {14804812668872528331u64, 4356262642990299018u64, 114338323799u64}) + put(data, {17369928488562523047u64, 1335108558830511366u64, 87236153471u64}) + put(data, {2967474173531035027u64, 18435704923261947246u64, 127072376379u64}) + put(data, {5291425437992807716u64, 8395401931972636441u64, 59999401566u64}) + put(data, {14219547193739388064u64, 12482665946362458347u64, 94455115650u64}) + put(data, {17720313647158217462u64, 16101242875289374781u64, 130676686676u64}) + put(data, {12334850628290578491u64, 4708983440241068127u64, 84872850125u64}) + put(data, {7818499847417334620u64, 14856666972541426744u64, 205255274503u64}) + put(data, {136007040922198372u64, 6938795288315789246u64, 7805381530u64}) + put(data, {8523477092112604449u64, 5556307628265073820u64, 154376152846u64}) + put(data, {367934822655966629u64, 1441404248927865979u64, 14301208040u64}) + put(data, {12964987687054730050u64, 16710378912353838906u64, 232078138680u64}) + put(data, {15267036012420885462u64, 18289940136919312110u64, 56905871455u64}) + put(data, {11142900264750765568u64, 10217414145292657824u64, 95991499641u64}) + put(data, {13680181547777718603u64, 12461165826430955753u64, 121553887130u64}) + put(data, {13001922925761426065u64, 662762458988270879u64, 154675521153u64}) + put(data, {2397730045956515935u64, 16488546856395302470u64, 129035928424u64}) + put(data, {4482395522588406288u64, 2612816787977180950u64, 104893845916u64}) + put(data, {3255525722490493080u64, 16446616379327454252u64, 156141641081u64}) + put(data, {836222287193822098u64, 7842178508581740643u64, 121891572860u64}) + put(data, {420898743993182306u64, 14779029861369369333u64, 124425125348u64}) + put(data, {11652649973356574054u64, 2697664446153849542u64, 228801172814u64}) + put(data, {15058402726661910231u64, 12135106444393649308u64, 78146240682u64}) + put(data, {9600134495208339559u64, 9550285041205189839u64, 170657845438u64}) + put(data, {14626343323989004842u64, 8790318168586740109u64, 190517721989u64}) + put(data, {6813981265331086665u64, 14038474217155846828u64, 133476524102u64}) + put(data, {10281745288790487888u64, 4263144264274812072u64, 70761027212u64}) + put(data, {17569829347075761940u64, 11940456333341715520u64, 140231105513u64}) + put(data, {7654580675237889478u64, 15751110736831573013u64, 233647293434u64}) + put(data, {16194838649686212364u64, 18384528705472318081u64, 250853869423u64}) + put(data, {6495102772252453635u64, 2393654818032310394u64, 111996627298u64}) + put(data, {14935159852819761348u64, 12812209822018626434u64, 98129760287u64}) + put(data, {17381879863441579697u64, 3110778569433458461u64, 31694551286u64}) + put(data, {9062335510435372583u64, 2860264756226872891u64, 246168635644u64}) + put(data, {7569219009130126626u64, 2384146980060315184u64, 252155055263u64}) + put(data, {12652124168176193362u64, 14117430062880324728u64, 159129244866u64}) + put(data, {8940200224697247767u64, 3769610173216737153u64, 194765307417u64}) + put(data, {5600570701927432884u64, 17731974340232672009u64, 25204350976u64}) + put(data, {2018432801986093157u64, 1971479303384713466u64, 961252255u64}) + put(data, {8235849749361824736u64, 3449462959779012549u64, 159106874107u64}) + put(data, {9705097287982370040u64, 13743454852043766533u64, 251186995761u64}) + put(data, {3517483139049842585u64, 7417711187131879498u64, 49745034180u64}) + put(data, {958281614186777760u64, 3650992383501007879u64, 196402114929u64}) + put(data, {7336473432636108950u64, 12838770342493958662u64, 113197920693u64}) + put(data, {12955383920176764423u64, 16025068246546338477u64, 181695991134u64}) + put(data, {10735469126281273789u64, 6579965938260177729u64, 94868720690u64}) + put(data, {8637888232514730693u64, 4742939430174291459u64, 50356700668u64}) + put(data, {6806336737533581000u64, 13062256857527449083u64, 252257115261u64}) + put(data, {16142569672872330321u64, 2301174570202439645u64, 125708106363u64}) + put(data, {8141285259947963513u64, 7638687886069412887u64, 123124746923u64}) + put(data, {5220241098754220797u64, 936322449610274358u64, 171414094100u64}) + put(data, {154438799943119608u64, 12926010544311283981u64, 20050758141u64}) + put(data, {2226876628677628879u64, 12647854908989899184u64, 253700720435u64}) + put(data, {17219557081221357482u64, 8862093163358513015u64, 51685641588u64}) + put(data, {15401507148161015114u64, 444784343917630731u64, 116480415033u64}) + put(data, {8842629766613985337u64, 11033952249213387263u64, 57024111807u64}) + put(data, {3180100571546071440u64, 18168634046363183536u64, 191598151749u64}) + put(data, {7740848704392475044u64, 3837904761417065597u64, 69984923625u64}) + put(data, {2014314126623495998u64, 111459007020906105u64, 233208053234u64}) + put(data, {11209566016506885858u64, 16191761957496794523u64, 242006042204u64}) + put(data, {7117166613733441125u64, 9856250800340378607u64, 92877757174u64}) + put(data, {4197646860931880328u64, 9491800102275105959u64, 246534308426u64}) + put(data, {5487263271238026094u64, 10777328578953608268u64, 74514551514u64}) + put(data, {18148076225293562697u64, 17424440628313779505u64, 218584240152u64}) + put(data, {9127276943027950849u64, 3285814872419755679u64, 24944580819u64}) + put(data, {9691696125379324722u64, 2824823424107240978u64, 211178124381u64}) + put(data, {13102362262487705216u64, 12271707680713669755u64, 93153133984u64}) + put(data, {8929385439893192704u64, 6951481875178001185u64, 160665250606u64}) + put(data, {11891353410743566336u64, 10202522487003824362u64, 46376840587u64}) + put(data, {1587423090877399040u64, 4834668463880990728u64, 139553079852u64}) + put(data, {3489137423026225152u64, 10871520987687904746u64, 44262087902u64}) + put(data, {13046928120492326912u64, 12057698794225322390u64, 222589346333u64}) + put(data, {11529215046068469760u64, 7263351819222681214u64, 29653649161u64}) + put(data, {0u64, 1778055686910650944u64, 9393747091u64}) + put(data, {0u64, 17108187120491986944u64, 147096388591u64}) + put(data, {0u64, 3067636961549221888u64, 239927436682u64}) + put(data, {0u64, 16702141595163557888u64, 138166296932u64}) + put(data, {0u64, 2432053749942845440u64, 100905424910u64}) + put(data, {0u64, 17791470327927144448u64, 14131841897u64}) + put(data, {0u64, 1152921504606846976u64, 105964477539u64}) + put(data, {0u64, 0u64, 99062500000u64}) + put(data, {0u64, 0u64, 160000000000u64}) + put(data, {6674960280855494694u64, 93326u64, 0u64}) + put(data, {16378845781483497510u64, 93326361850321u64, 0u64}) + put(data, {17606907750956804392u64, 4283581425266273664u64, 5059u64}) + put(data, {13225609159240506969u64, 6725911039793895357u64, 195232213414u64}) + put(data, {2668084873338435252u64, 1188689198788975021u64, 166364612368u64}) + put(data, {14802814305275861366u64, 10825527435847761650u64, 16064438970u64}) + put(data, {8005510553372365574u64, 3917696829526085083u64, 186586853018u64}) + put(data, {12748500143273514429u64, 12646861173976387276u64, 154212378770u64}) + put(data, {10393733905569036127u64, 18398576063183996905u64, 146685587717u64}) + put(data, {603389089974790339u64, 16919251228485834948u64, 5997388806u64}) + put(data, {2033494532597735873u64, 17296019588687185052u64, 6917194446u64}) + put(data, {9204796763694620958u64, 12365301604512770359u64, 206937619100u64}) + put(data, {7826260310402107021u64, 2814271599679204744u64, 156670324343u64}) + put(data, {10122690201685169383u64, 2154994415780170517u64, 119152561969u64}) + put(data, {7438455564568110133u64, 6717373824370072839u64, 49116822481u64}) + put(data, {2805412574380520817u64, 12709155755801344060u64, 209364149564u64}) + put(data, {7250965427231182867u64, 826847911966403896u64, 60688964714u64}) + put(data, {8136242944826085924u64, 2277322703890025383u64, 106044823515u64}) + put(data, {15357191647956011780u64, 2774508958389496437u64, 219123453911u64}) + put(data, {7369614426695395460u64, 245697774950120915u64, 215150406432u64}) + put(data, {10886957545142526638u64, 1268929063431863950u64, 32013319303u64}) + put(data, {2030047207417538097u64, 6735665673159411439u64, 135068788782u64}) + put(data, {11557093828502314355u64, 14734771742997073207u64, 46365141167u64}) + put(data, {15201062539664128543u64, 13683287077957612495u64, 175798773576u64}) + put(data, {8846936323343880674u64, 15370263741354826803u64, 72741772478u64}) + put(data, {1485291750116245364u64, 48035913070297507u64, 190833223667u64}) + put(data, {2076024439668322013u64, 1206547475966802115u64, 243002604032u64}) + put(data, {7029497773682748741u64, 13512340386605768006u64, 65407069u64}) + put(data, {16333533921668749881u64, 2325760467700278797u64, 93732505440u64}) + put(data, {2065057316131928423u64, 10848110652847753816u64, 96126079727u64}) + put(data, {7800502648925570223u64, 15846378960784301285u64, 239588077256u64}) + put(data, {17011619967093802015u64, 14121839924449844911u64, 200859033924u64}) + put(data, {5368819344429198672u64, 5147613424753296550u64, 68765546476u64}) + put(data, {15598879366754275267u64, 16817040482828810167u64, 236279052682u64}) + put(data, {16393893486035835647u64, 5773528746119363888u64, 138911653591u64}) + put(data, {12042046205096920307u64, 8716201595536184826u64, 215312983620u64}) + put(data, {15437070428008474344u64, 5259122109038474872u64, 68472506235u64}) + put(data, {13704569163204647509u64, 14744540084230155984u64, 123285097580u64}) + put(data, {18192483750856993350u64, 10719345477982635878u64, 108799303119u64}) + put(data, {5152535865317963250u64, 13698037261310555208u64, 207581096882u64}) + put(data, {17512614083933854680u64, 16141171632951976936u64, 178742572087u64}) + put(data, {14481918350603613536u64, 10060790174955808839u64, 55875014667u64}) + put(data, {16124419709964004915u64, 4250043307981877118u64, 11545396528u64}) + put(data, {18088011566435813579u64, 7075646198054337199u64, 48230395309u64}) + put(data, {15008862380698848893u64, 18141738384245531503u64, 173383571548u64}) + put(data, {18160498644611827812u64, 8174370508376809531u64, 92983465608u64}) + put(data, {3604680497457231965u64, 3581964982731575596u64, 136443133513u64}) + put(data, {5957615565551495921u64, 14798509948722114761u64, 73194178710u64}) + put(data, {17528455034961565995u64, 14713923334885122090u64, 150802228831u64}) + put(data, {8503772325120113809u64, 5042978054260414139u64, 95797643382u64}) + put(data, {8501492578048509537u64, 2052996319372883413u64, 118273380388u64}) + put(data, {2296237701094386060u64, 8825683007899981588u64, 36111293153u64}) + put(data, {3753593040591076946u64, 9992196755378745151u64, 225478441234u64}) + put(data, {8518075399775653155u64, 9301073417573669950u64, 18541678071u64}) + put(data, {12757855675959554597u64, 5331614769144850592u64, 247504212200u64}) + put(data, {121631169379748595u64, 14354009428310052102u64, 232289027415u64}) + put(data, {16679062494579173314u64, 5581221063029119078u64, 87778132410u64}) + put(data, {10739912744743898054u64, 1529260335339476189u64, 186302558600u64}) + put(data, {9367340677776287570u64, 16483061525949201148u64, 136082901368u64}) + put(data, {12227321512794715397u64, 14431217812333089675u64, 120893548555u64}) + put(data, {7241061891859170651u64, 3452349151135392267u64, 11782317885u64}) + put(data, {13148571323079237489u64, 9075317899834447999u64, 61187152222u64}) + put(data, {12509763434355012654u64, 2764331337978901575u64, 94491973969u64}) + put(data, {11812768946960181977u64, 1942890683708857202u64, 81149854702u64}) + put(data, {14170358803552564832u64, 165089169728028447u64, 238105324315u64}) + put(data, {18179989524780635952u64, 15193620741871233073u64, 27008949501u64}) + put(data, {17091718978514754901u64, 14995000835194145926u64, 253823647830u64}) + put(data, {7394768384359232459u64, 1788823614552255558u64, 86812880624u64}) + put(data, {6778628272692852803u64, 8384901184618498845u64, 240096972322u64}) + put(data, {18193335045875234320u64, 405511217862281310u64, 34454546404u64}) + put(data, {1378519212560967521u64, 3111530463755196557u64, 228021982807u64}) + put(data, {4677732610631043584u64, 7893558450035460812u64, 87168676404u64}) + put(data, {17296098591070486528u64, 156573858237402216u64, 52427910661u64}) + put(data, {7343735382392963072u64, 15915324019419451223u64, 5008487885u64}) + put(data, {14525996728454217728u64, 16293363012778802804u64, 205862771443u64}) + put(data, {9691359370008330240u64, 14342105318291351412u64, 243883264978u64}) + put(data, {3044433348102455296u64, 3788398842525387052u64, 210777487087u64}) + put(data, {9223372036854775808u64, 14118764407048307670u64, 239205369512u64}) + put(data, {0u64, 2705021334614720768u64, 168765379752u64}) + put(data, {0u64, 7017988973805568000u64, 168146639500u64}) + put(data, {0u64, 10956732053634154496u64, 140380445944u64}) + put(data, {0u64, 14657517938546835456u64, 248593965634u64}) + put(data, {0u64, 11268868284797157376u64, 66794585639u64}) + put(data, {0u64, 14600669991935148032u64, 39610886573u64}) + put(data, {0u64, 4611686018427387904u64, 173791503906u64}) + put(data, {0u64, 0u64, 34250000000u64}) + put(data, {0u64, 0u64, 128000000000u64}) + put(data, {8201586317771250746u64, 1424047269u64, 0u64}) + put(data, {3278889188817135834u64, 1424047269444608885u64, 0u64}) + put(data, {1710725240251040430u64, 3001188830946823627u64, 77197757u64}) + put(data, {1850175733663425006u64, 9732296932705387049u64, 189162694772u64}) + put(data, {9147599666163914249u64, 16337535782679529459u64, 116527588873u64}) + put(data, {10221885933644344166u64, 7969742269895046547u64, 9885659589u64}) + put(data, {14901479793736678101u64, 2923592083903829642u64, 197432040594u64}) + put(data, {5181831442059703136u64, 8144196241160608534u64, 146158488244u64}) + put(data, {6396246577759793483u64, 16431078457793424253u64, 180441497762u64}) + put(data, {14167229556464870447u64, 202362949592775653u64, 162890730548u64}) + put(data, {2969982933326311854u64, 8835125248522947981u64, 52010970117u64}) + put(data, {7892677766222018881u64, 7959873808777345113u64, 5478953099u64}) + put(data, {798698968922663621u64, 14929747122315126151u64, 139431505623u64}) + put(data, {15926812109043458972u64, 4310328817360515349u64, 215809343213u64}) + put(data, {8663842590352697437u64, 7294899422760201126u64, 237233663393u64}) + put(data, {17093523026636671168u64, 2047461597291187207u64, 161395457290u64}) + put(data, {839764004742743203u64, 10942374468813517900u64, 10110993115u64}) + put(data, {16894643909298232323u64, 10364795403063433969u64, 219593187308u64}) + put(data, {9066702926218949317u64, 12330859528790939137u64, 236561876684u64}) + put(data, {9119392417260546810u64, 8973160144879916806u64, 204668457234u64}) + put(data, {9723021096578315109u64, 2895354388547509877u64, 18486435986u64}) + put(data, {14787464248751217597u64, 16766844772497556429u64, 146156957475u64}) + put(data, {3733434565920249133u64, 7442407174620948827u64, 35908932476u64}) + put(data, {6643788868836820841u64, 6683013428676659077u64, 124403453701u64}) + put(data, {4729646697422664063u64, 16713703375071907588u64, 5362286883u64}) + put(data, {4090144564201555829u64, 8791044883080637861u64, 35906051675u64}) + put(data, {2109480737093400002u64, 602844107089214413u64, 91476563498u64}) + put(data, {16577155033369419739u64, 9754832281172880875u64, 42032680244u64}) + put(data, {745377248603805917u64, 10587846778003503903u64, 52528810517u64}) + put(data, {11305561465807999667u64, 17206244172922947013u64, 21573968323u64}) + put(data, {2211245518782892177u64, 11620628420699303875u64, 195932752365u64}) + put(data, {14170095199249735666u64, 17864732368219338611u64, 237629955528u64}) + put(data, {17849973668116118927u64, 4146383014621345887u64, 200968449082u64}) + put(data, {9020960204585720001u64, 11445705075042688243u64, 58224775873u64}) + put(data, {10807134002871850916u64, 7369147888966546592u64, 193620472915u64}) + put(data, {3925122626254791201u64, 9762476865090597796u64, 83399482307u64}) + put(data, {17208463291312718997u64, 5507001428194242827u64, 195529224931u64}) + put(data, {5145077219589447653u64, 11371471148365328344u64, 227298535145u64}) + put(data, {17602397765035489468u64, 3148788104946538618u64, 233616448686u64}) + put(data, {16422643262490753377u64, 3762722308424507574u64, 174170696145u64}) + put(data, {2902509461400906224u64, 1156171244825745915u64, 209203977585u64}) + put(data, {3422418805967265206u64, 14208921674868257865u64, 113062676168u64}) + put(data, {4228874576277237392u64, 7903080886897905503u64, 200770267187u64}) + put(data, {2553488530807495751u64, 6367240794154270982u64, 51428426873u64}) + put(data, {11546099176912486413u64, 1623672396662369850u64, 121345168815u64}) + put(data, {10460791037534167991u64, 18323231215381674394u64, 175088019456u64}) + put(data, {8127117908566000904u64, 9842279843006544554u64, 993304354u64}) + put(data, {11541304458088287306u64, 7376839231308610600u64, 34533551059u64}) + put(data, {6249718665174839700u64, 609751749293657672u64, 211399899256u64}) + put(data, {13102508413386290995u64, 10386457966860989799u64, 120033054708u64}) + put(data, {6274675218640661911u64, 11160336020836149780u64, 244563051014u64}) + put(data, {3404497118599817167u64, 17947559933847409193u64, 6605003027u64}) + put(data, {11258566093988562335u64, 10229787001712704590u64, 19972939173u64}) + put(data, {16762592482501635397u64, 10441677090043619866u64, 165554557864u64}) + put(data, {5550125446725071998u64, 4996681336392922375u64, 168566044449u64}) + put(data, {6370033225258510318u64, 124497102381021895u64, 33270870638u64}) + put(data, {1503521728674735398u64, 8180812057779384577u64, 110006749001u64}) + put(data, {4250415082606384364u64, 5294232873532946716u64, 73443482710u64}) + put(data, {6020091901030562974u64, 2885620189169448039u64, 86287000939u64}) + put(data, {16288222967151527138u64, 16662526875008170507u64, 107156429783u64}) + put(data, {6377016228656203782u64, 15663095032402672480u64, 215903277391u64}) + put(data, {8378856515587563750u64, 1824281504410546614u64, 79849098083u64}) + put(data, {15812881490200838483u64, 9506565509584809953u64, 99098894498u64}) + put(data, {4548570371183413652u64, 16941136942345070055u64, 162515351948u64}) + put(data, {16731431495283420383u64, 15924115693705937725u64, 140918380873u64}) + put(data, {14737727629551135532u64, 9247807690406628462u64, 73863248041u64}) + put(data, {12413722258104293893u64, 7993916633864834871u64, 169501324659u64}) + put(data, {800899742400762438u64, 1018504409177639408u64, 115433351089u64}) + put(data, {603197008376033550u64, 12097800686634130718u64, 177055213234u64}) + put(data, {6380777281587743935u64, 6221488888422637551u64, 178655823089u64}) + put(data, {10001440249018225388u64, 8229322865256080421u64, 241337267588u64}) + put(data, {5505914461980436708u64, 7927745108183101786u64, 132446112486u64}) + put(data, {1105464290051876864u64, 8488683721235326653u64, 230429763923u64}) + put(data, {4500443576769970176u64, 11165516518170922283u64, 83460172466u64}) + put(data, {2843045143185981440u64, 5463648141113596927u64, 178605283863u64}) + put(data, {660949699682893824u64, 3958440403860778042u64, 23296184959u64}) + put(data, {276549164618219520u64, 5091534813990256011u64, 127214587484u64}) + put(data, {4683743612465315840u64, 6100166970623291280u64, 92276012655u64}) + put(data, {0u64, 1913011027739012426u64, 111330690714u64}) + put(data, {0u64, 11310957650604221440u64, 154103704535u64}) + put(data, {0u64, 16303817257009020928u64, 215613168242u64}) + put(data, {0u64, 9090406322154766336u64, 114883831704u64}) + put(data, {0u64, 3003279315069566976u64, 152492791914u64}) + put(data, {0u64, 16582887146675765248u64, 106162808097u64}) + put(data, {0u64, 9691746398101307392u64, 33898960113u64}) + put(data, {0u64, 0u64, 241525390625u64}) + put(data, {0u64, 0u64, 33000000000u64}) + data + end +end diff --git a/src/string/formatter.cr b/src/string/formatter.cr index 8ff32cf2ada0..cdf5710b45f4 100644 --- a/src/string/formatter.cr +++ b/src/string/formatter.cr @@ -219,6 +219,7 @@ struct String::Formatter(A) int flags, arg when 'a', 'A', 'e', 'E', 'f', 'g', 'G' flags.type = char + flags.float = true float flags, arg else raise ArgumentError.new("Malformed format string - %#{char.inspect}") @@ -297,7 +298,6 @@ struct String::Formatter(A) end end - # We don't actually format the float ourselves, we delegate to snprintf def float(flags, arg) : Nil if arg.responds_to?(:to_f64) float = arg.is_a?(Float64) ? arg : arg.to_f64 @@ -307,13 +307,25 @@ struct String::Formatter(A) elsif float.nan? float_special("nan", 1, flags) else - format_buf = recreate_float_format_string(flags) - - len = LibC.snprintf(nil, 0, format_buf, float) + 1 - temp_buf = temp_buf(len) - LibC.snprintf(temp_buf, len, format_buf, float) - - @io.write_string Slice.new(temp_buf, len - 1) + # FIXME: wasm32 appears to run out of memory if we use Ryu Printf, which + # initializes very large lookup tables, so we always fall back to + # `LibC.snprintf` (#13918) + {% if flag?(:wasm32) %} + float_fallback(float, flags) + {% else %} + case flags.type + when 'f' + float_fixed(float, flags) + when 'e', 'E' + float_scientific(float, flags) + when 'g', 'G' + float_general(float, flags) + when 'a', 'A' + float_hex(float, flags) + else + raise "BUG: Unknown format type '#{flags.type}'" + end + {% end %} end else raise ArgumentError.new("Expected a float, not #{arg.inspect}") @@ -334,6 +346,75 @@ struct String::Formatter(A) pad(str_size, flags) if flags.right_padding? end + {% unless flag?(:wasm32) %} + # Formats floats with `%f` + private def float_fixed(float, flags) + # the longest string possible is due to `Float64::MIN_SUBNORMAL`, which + # produces `0.` followed by 1074 nonzero digits; there is also no need + # for any precision > 1074 because all trailing digits will be zeros + if precision = flags.precision + printf_precision = {precision.to_u32, 1074_u32}.min + trailing_zeros = {precision - printf_precision, 0}.max + else + # default precision for C's `%f` + printf_precision = 6_u32 + trailing_zeros = 0 + end + + printf_buf = uninitialized UInt8[1076] + printf_size = Float::Printer::RyuPrintf.d2fixed_buffered_n(float, printf_precision, printf_buf.to_unsafe) + printf_slice = printf_buf.to_slice[0, printf_size] + dot_index = printf_slice.index('.'.ord) + sign = Math.copysign(1.0, float) + + str_size = printf_size + trailing_zeros + str_size += 1 if sign < 0 || flags.plus || flags.space + str_size += 1 if flags.sharp && dot_index.nil? + + pad(str_size, flags) if flags.left_padding? && flags.padding_char != '0' + + # this preserves -0.0's sign correctly + write_plus_or_space(sign, flags) + @io << '-' if sign < 0 + + pad(str_size, flags) if flags.left_padding? && flags.padding_char == '0' + @io.write_string(printf_slice) + trailing_zeros.times { @io << '0' } + @io << '.' if flags.sharp && dot_index.nil? + + pad(str_size, flags) if flags.right_padding? + end + + # Formats floats with `%e` or `%E` + private def float_scientific(float, flags) + # TODO: implement using `Float::Printer::RyuPrintf` + float_fallback(float, flags) + end + + # Formats floats with `%g` or `%G` + private def float_general(float, flags) + # TODO: implement using `Float::Printer::RyuPrintf` + float_fallback(float, flags) + end + + # Formats floats with `%a` or `%A` + private def float_hex(float, flags) + # TODO: implement using `Float::Printer::Hexfloat` + float_fallback(float, flags) + end + {% end %} + + # Delegate to `LibC.snprintf` for float formats not yet ported to Crystal + private def float_fallback(float, flags) + format_buf = recreate_float_format_string(flags) + + len = LibC.snprintf(nil, 0, format_buf, float) + 1 + temp_buf = temp_buf(len) + LibC.snprintf(temp_buf, len, format_buf, float) + + @io.write_string Slice.new(temp_buf, len - 1) + end + # Here we rebuild the original format string, like %f or %.2g and use snprintf def recreate_float_format_string(flags) capacity = 3 # percent + type + \0 @@ -451,13 +532,13 @@ struct String::Formatter(A) end struct Flags - property space : Bool, sharp : Bool, plus : Bool, minus : Bool, zero : Bool, base : Int32 + property space : Bool, sharp : Bool, plus : Bool, minus : Bool, zero : Bool, float : Bool, base : Int32 property width : Int32, width_size : Int32 property type : Char, precision : Int32?, precision_size : Int32 property index : Int32? def initialize - @space = @sharp = @plus = @minus = @zero = false + @space = @sharp = @plus = @minus = @zero = @float = false @width = 0 @width_size = 0 @base = 10 @@ -475,7 +556,7 @@ struct String::Formatter(A) end def padding_char : Char - @zero && !right_padding? && !@precision ? '0' : ' ' + @zero && !right_padding? && (@float || !@precision) ? '0' : ' ' end end end From 6fe0ef7e323f6d7eaed9b620db16fc51d32ff77c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 13 Dec 2023 21:31:37 +0800 Subject: [PATCH 0830/1551] Add macro methods for `ClassDef`, `EnumDef`, `AnnotationDef` (#14072) --- spec/compiler/macro/macro_methods_spec.cr | 97 +++++++++++++++++ src/compiler/crystal/macros.cr | 124 +++++++++++++++++++++- src/compiler/crystal/macros/methods.cr | 103 ++++++++++++++++-- 3 files changed, 310 insertions(+), 14 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 66f47d6ad39d..9d6304e81f01 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -3144,6 +3144,60 @@ module Crystal end end + describe ClassDef do + class_def = ClassDef.new(Path.new("Foo"), abstract: true, superclass: Path.new("Parent")) + struct_def = ClassDef.new(Path.new("Foo", "Bar", global: true), type_vars: %w(A B C D), splat_index: 2, struct: true, body: CharLiteral.new('a')) + + it "executes kind" do + assert_macro %({{x.kind}}), %(class), {x: class_def} + assert_macro %({{x.kind}}), %(struct), {x: struct_def} + end + + it "executes name" do + assert_macro %({{x.name}}), %(Foo), {x: class_def} + assert_macro %({{x.name}}), %(::Foo::Bar(A, B, *C, D)), {x: struct_def} + + assert_macro %({{x.name(generic_args: true)}}), %(Foo), {x: class_def} + assert_macro %({{x.name(generic_args: true)}}), %(::Foo::Bar(A, B, *C, D)), {x: struct_def} + + assert_macro %({{x.name(generic_args: false)}}), %(Foo), {x: class_def} + assert_macro %({{x.name(generic_args: false)}}), %(::Foo::Bar), {x: struct_def} + + assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to ClassDef#name must be a BoolLiteral, not NumberLiteral", {x: class_def} + end + + it "executes superclass" do + assert_macro %({{x.superclass}}), %(Parent), {x: class_def} + assert_macro %({{x.superclass}}), %(Parent(*T)), {x: ClassDef.new(Path.new("Foo"), superclass: Generic.new(Path.new("Parent"), [Splat.new(Path.new("T"))] of ASTNode))} + assert_macro %({{x.superclass}}), %(), {x: struct_def} + end + + it "executes type_vars" do + assert_macro %({{x.type_vars}}), %([] of ::NoReturn), {x: class_def} + assert_macro %({{x.type_vars}}), %([A, B, C, D]), {x: struct_def} + end + + it "executes splat_index" do + assert_macro %({{x.splat_index}}), %(nil), {x: class_def} + assert_macro %({{x.splat_index}}), %(2), {x: struct_def} + end + + it "executes body" do + assert_macro %({{x.body}}), %(), {x: class_def} + assert_macro %({{x.body}}), %('a'), {x: struct_def} + end + + it "executes abstract?" do + assert_macro %({{x.abstract?}}), %(true), {x: class_def} + assert_macro %({{x.abstract?}}), %(false), {x: struct_def} + end + + it "executes struct?" do + assert_macro %({{x.struct?}}), %(false), {x: class_def} + assert_macro %({{x.struct?}}), %(true), {x: struct_def} + end + end + describe ModuleDef do module_def1 = ModuleDef.new(Path.new("Foo")) module_def2 = ModuleDef.new(Path.new("Foo", "Bar", global: true), type_vars: %w(A B C D), splat_index: 2, body: CharLiteral.new('a')) @@ -3182,6 +3236,49 @@ module Crystal end end + describe EnumDef do + enum_def = EnumDef.new(Path.new("Foo", "Bar", global: true), [Path.new("X")] of ASTNode, Path.global("Int32")) + + it "executes kind" do + assert_macro %({{x.kind}}), %(enum), {x: enum_def} + end + + it "executes name" do + assert_macro %({{x.name}}), %(::Foo::Bar), {x: enum_def} + assert_macro %({{x.name(generic_args: true)}}), %(::Foo::Bar), {x: enum_def} + assert_macro %({{x.name(generic_args: false)}}), %(::Foo::Bar), {x: enum_def} + assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to EnumDef#name must be a BoolLiteral, not NumberLiteral", {x: enum_def} + end + + it "executes base_type" do + assert_macro %({{x.base_type}}), %(::Int32), {x: enum_def} + assert_macro %({{x.base_type}}), %(), {x: EnumDef.new(Path.new("Baz"))} + end + + it "executes body" do + assert_macro %({{x.body}}), %(X), {x: enum_def} + end + end + + describe AnnotationDef do + annotation_def = AnnotationDef.new(Path.new("Foo", "Bar", global: true)) + + it "executes kind" do + assert_macro %({{x.kind}}), %(annotation), {x: annotation_def} + end + + it "executes name" do + assert_macro %({{x.name}}), %(::Foo::Bar), {x: annotation_def} + assert_macro %({{x.name(generic_args: true)}}), %(::Foo::Bar), {x: annotation_def} + assert_macro %({{x.name(generic_args: false)}}), %(::Foo::Bar), {x: annotation_def} + assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to AnnotationDef#name must be a BoolLiteral, not NumberLiteral", {x: annotation_def} + end + + it "executes body" do + assert_macro %({{x.body}}), %(), {x: annotation_def} + end + end + describe "env" do it "has key" do ENV["FOO"] = "foo" diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 1c61e98a9b80..ea4ac72a2b5d 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1601,7 +1601,62 @@ module Crystal::Macros end # A class definition. + # + # Every class definition `node` is equivalent to: + # + # ``` + # {% begin %} + # {% "abstract".id if node.abstract? %} {{ node.kind }} {{ node.name }} {% if superclass = node.superclass %}< {{ superclass }}{% end %} + # {{ node.body }} + # end + # {% end %} + # ``` class ClassDef < ASTNode + # Returns whether this node defines an abstract class or struct. + def abstract? : BoolLiteral + end + + # Returns the keyword used to define this type. + # + # For `ClassDef` this is either `class` or `struct`. + def kind : MacroId + end + + # Returns the name of this type definition. + # + # If this node defines a generic type, and *generic_args* is true, returns a + # `Generic` whose type arguments are `MacroId`s, possibly with a `Splat` at + # the splat index. Otherwise, this method returns a `Path`. + def name(*, generic_args : BoolLiteral = true) : Path | Generic + end + + # Returns the superclass of this type definition, or a `Nop` if one isn't + # specified. + def superclass : ASTNode + end + + # Returns the body of this type definition. + def body : ASTNode + end + + # Returns an array of `MacroId`s of this type definition's generic type + # parameters. + # + # On a non-generic type definition, returns an empty array. + def type_vars : ArrayLiteral + end + + # Returns the splat index of this type definition's generic type parameters. + # + # Returns `nil` if this type definition isn't generic or if there isn't a + # splat parameter. + def splat_index : NumberLiteral | NilLiteral + end + + # Returns `true` if this node defines a struct, `false` if this node defines + # a class. + def struct? : BoolLiteral + end end # A module definition. @@ -1649,6 +1704,72 @@ module Crystal::Macros end end + # An enum definition. + # + # ``` + # {% begin %} + # {{ node.kind }} {{ node.name }} {% if base_type = node.base_type %}: {{ base_type }}{% end %} + # {{ node.body }} + # end + # {% end %} + # ``` + class EnumDef < ASTNode + # Returns the keyword used to define this type. + # + # For `EnumDef` this is always `enum`. + def kind : MacroId + end + + # Returns the name of this type definition. + # + # *generic_args* has no effect. It exists solely to match the interface of + # other related AST nodes. + def name(*, generic_args : BoolLiteral = true) : Path + end + + # Returns the base type of this enum definition, or a `Nop` if one isn't + # specified. + def base_type : ASTNode + end + + # Returns the body of this type definition. + def body : ASTNode + end + end + + # An annotation definition. + # + # Every annotation definition `node` is equivalent to: + # + # ``` + # {% begin %} + # {{ node.kind }} {{ node.name }} + # {{ node.body }} + # end + # {% end %} + # ``` + class AnnotationDef < ASTNode + # Returns the keyword used to define this type. + # + # For `AnnotationDef` this is always `annotation`. + def kind : MacroId + end + + # Returns the name of this type definition. + # + # *generic_args* has no effect. It exists solely to match the interface of + # other related AST nodes. + def name(*, generic_args : BoolLiteral = true) : Path + end + + # Returns the body of this type definition. + # + # Currently this is always a `Nop`, because annotation definitions cannot + # contain anything at all. + def body : Nop + end + end + # A `while` expression class While < ASTNode # Returns this while's condition. @@ -1895,9 +2016,6 @@ module Crystal::Macros # class UnionDef < CStructOrUnionDef # end - # class EnumDef < ASTNode - # end - # class ExternalVar < ASTNode # end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 888f41eecbba..24d3d8bbd14d 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -2415,24 +2415,50 @@ module Crystal end end - class ModuleDef + class ClassDef def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) case method when "kind" - interpret_check_args { MacroId.new("module") } + interpret_check_args { MacroId.new(@struct ? "struct" : "class") } when "name" - interpret_check_args(named_params: ["generic_args"]) do - if parse_generic_args_argument(self, method, named_args, default: true) && (type_vars = @type_vars) - type_vars = type_vars.map_with_index do |type_var, i| - param = MacroId.new(type_var) - param = Splat.new(param) if i == @splat_index - param - end - Generic.new(@name, type_vars) + type_definition_generic_name(self, method, args, named_args, block) + when "superclass" + interpret_check_args { @superclass || Nop.new } + when "type_vars" + interpret_check_args do + if (type_vars = @type_vars) && type_vars.present? + ArrayLiteral.map(type_vars) { |type_var| MacroId.new(type_var) } + else + empty_no_return_array + end + end + when "splat_index" + interpret_check_args do + if splat_index = @splat_index + NumberLiteral.new(splat_index) else - @name + NilLiteral.new end end + when "body" + interpret_check_args { @body } + when "abstract?" + interpret_check_args { BoolLiteral.new(@abstract) } + when "struct?" + interpret_check_args { BoolLiteral.new(@struct) } + else + super + end + end + end + + class ModuleDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "kind" + interpret_check_args { MacroId.new("module") } + when "name" + type_definition_generic_name(self, method, args, named_args, block) when "type_vars" interpret_check_args do if (type_vars = @type_vars) && type_vars.present? @@ -2456,6 +2482,46 @@ module Crystal end end end + + class EnumDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "kind" + interpret_check_args { MacroId.new("enum") } + when "name" + interpret_check_args(named_params: ["generic_args"]) do + # parse the argument, but ignore it otherwise + parse_generic_args_argument(self, method, named_args, default: true) + @name + end + when "base_type" + interpret_check_args { @base_type || Nop.new } + when "body" + interpret_check_args { Expressions.from(@members) } + else + super + end + end + end + + class AnnotationDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "kind" + interpret_check_args { MacroId.new("annotation") } + when "name" + interpret_check_args(named_params: ["generic_args"]) do + # parse the argument, but ignore it otherwise + parse_generic_args_argument(self, method, named_args, default: true) + @name + end + when "body" + interpret_check_args { Nop.new } + else + super + end + end + end end private def get_named_annotation_args(object) @@ -2805,6 +2871,21 @@ private def parse_generic_args_argument(node, method, named_args, *, default) end end +private def type_definition_generic_name(node, method, args, named_args, block) + interpret_check_args(node: node, named_params: ["generic_args"]) do + if parse_generic_args_argument(node, method, named_args, default: true) && (type_vars = node.type_vars) + type_vars = type_vars.map_with_index do |type_var, i| + param = Crystal::MacroId.new(type_var) + param = Crystal::Splat.new(param) if i == node.splat_index + param + end + Crystal::Generic.new(node.name, type_vars) + else + node.name + end + end +end + private def macro_raise(node, args, interpreter, exception_type) msg = args.map do |arg| arg.accept interpreter From f5a5e5a73d0b0d6cf61d9cb1722abfc51cbf7c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 13 Dec 2023 14:31:48 +0100 Subject: [PATCH 0831/1551] Skip `Crystal::Macros` unless generating docs (#13970) --- src/compiler/crystal/macros.cr | 2 ++ src/compiler/crystal/tools/doc/type.cr | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index ea4ac72a2b5d..3c59a56a1818 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1,3 +1,5 @@ +{% skip_file unless flag?(:docs) %} + # Defines string related macro methods. # # Many `StringLiteral` methods can be called from `SymbolLiteral` and `MacroId`, diff --git a/src/compiler/crystal/tools/doc/type.cr b/src/compiler/crystal/tools/doc/type.cr index 619c32cd1fcd..9a40bd23e189 100644 --- a/src/compiler/crystal/tools/doc/type.cr +++ b/src/compiler/crystal/tools/doc/type.cr @@ -117,7 +117,7 @@ class Crystal::Doc::Type def ast_node? type = @type - type.is_a?(ClassType) && type.full_name == Crystal::Macros::ASTNode.name + type.is_a?(ClassType) && type.full_name == "Crystal::Macros::ASTNode" end def locations From 59095fff2df560d95ac08bf00adf69ca77c94d4c Mon Sep 17 00:00:00 2001 From: Hiroya Fujinami Date: Wed, 13 Dec 2023 22:32:39 +0900 Subject: [PATCH 0832/1551] Use `top_level_semantic` in doc spec instead of `semantic` (#9352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- .../compiler/crystal/tools/doc/method_spec.cr | 20 +++++++++---------- spec/compiler/crystal/tools/doc/type_spec.cr | 12 ++++------- spec/spec_helper.cr | 14 +++++++++++++ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/spec/compiler/crystal/tools/doc/method_spec.cr b/spec/compiler/crystal/tools/doc/method_spec.cr index 6353115da23e..d3178aa2951c 100644 --- a/spec/compiler/crystal/tools/doc/method_spec.cr +++ b/spec/compiler/crystal/tools/doc/method_spec.cr @@ -144,13 +144,13 @@ describe Doc::Method do describe "doc" do it "gets doc from underlying method" do - program = semantic(" + program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program class Foo # Some docs def foo end end - ", wants_doc: true).program + CRYSTAL generator = Doc::Generator.new program, [""] method = generator.type(program.types["Foo"]).lookup_method("foo").not_nil! method.doc.should eq("Some docs") @@ -181,7 +181,7 @@ describe Doc::Method do end it "inherits doc from ancestor (no extra comment)" do - program = semantic(" + program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program class Foo # Some docs def foo @@ -193,7 +193,7 @@ describe Doc::Method do super end end - ", wants_doc: true).program + CRYSTAL generator = Doc::Generator.new program, [""] method = generator.type(program.types["Bar"]).lookup_method("foo").not_nil! method.doc.should eq("Some docs") @@ -201,7 +201,7 @@ describe Doc::Method do end it "inherits doc from previous def (no extra comment)" do - program = semantic(" + program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program class Foo # Some docs def foo @@ -211,7 +211,7 @@ describe Doc::Method do previous_def end end - ", wants_doc: true).program + CRYSTAL generator = Doc::Generator.new program, [""] method = generator.type(program.types["Foo"]).lookup_method("foo").not_nil! method.doc.should eq("Some docs") @@ -219,7 +219,7 @@ describe Doc::Method do end it "inherits doc from ancestor (use :inherit:)" do - program = semantic(" + program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program class Foo # Some docs def foo @@ -232,7 +232,7 @@ describe Doc::Method do super end end - ", wants_doc: true).program + CRYSTAL generator = Doc::Generator.new program, [""] method = generator.type(program.types["Bar"]).lookup_method("foo").not_nil! method.doc.should eq("Some docs") @@ -240,7 +240,7 @@ describe Doc::Method do end it "inherits doc from ancestor (use :inherit: plus more content)" do - program = semantic(" + program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program class Foo # Some docs def foo @@ -257,7 +257,7 @@ describe Doc::Method do super end end - ", wants_doc: true).program + CRYSTAL generator = Doc::Generator.new program, [""] method = generator.type(program.types["Bar"]).lookup_method("foo").not_nil! method.doc.should eq("Before\n\nSome docs\n\nAfter") diff --git a/spec/compiler/crystal/tools/doc/type_spec.cr b/spec/compiler/crystal/tools/doc/type_spec.cr index 73184dff873b..34ab535f6d5e 100644 --- a/spec/compiler/crystal/tools/doc/type_spec.cr +++ b/spec/compiler/crystal/tools/doc/type_spec.cr @@ -2,7 +2,7 @@ require "../../../spec_helper" describe Doc::Type do it "doesn't show types for alias type" do - result = semantic(%( + program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program class Foo class Bar end @@ -11,9 +11,7 @@ describe Doc::Type do alias Alias = Foo Alias - )) - - program = result.program + CRYSTAL # Set locations to types relative to the included dir # so they are included by the doc generator @@ -30,14 +28,12 @@ describe Doc::Type do end it "finds construct when searching class method (#8095)" do - result = semantic(%( + program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program class Foo def initialize(x) end end - )) - - program = result.program + CRYSTAL generator = Doc::Generator.new program, [""] foo = generator.type(program.types["Foo"]) diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 7ca8860383de..98f39b1fc042 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -79,6 +79,20 @@ def semantic(node : ASTNode, *, warnings = nil, wants_doc = false, flags = nil) SemanticResult.new(program, node) end +def top_level_semantic(code : String, wants_doc = false, inject_primitives = false) + node = parse(code, wants_doc: wants_doc) + node = inject_primitives(node) if inject_primitives + top_level_semantic node, wants_doc: wants_doc +end + +def top_level_semantic(node : ASTNode, wants_doc = false) + program = new_program + program.wants_doc = wants_doc + node = program.normalize node + node, _ = program.top_level_semantic node + SemanticResult.new(program, node) +end + def assert_normalize(from, to, flags = nil, *, file = __FILE__, line = __LINE__) program = new_program program.flags.concat(flags.split) if flags From 6f1067e0ffdae590936521ce0ac21bb42d5e0ebe Mon Sep 17 00:00:00 2001 From: Matthew Berry Date: Wed, 13 Dec 2023 14:05:21 -0800 Subject: [PATCH 0833/1551] Add compile time string interpolation for char/num/bool constants (#12671) --- .../normalize/string_interpolation_spec.cr | 39 +++++++++++++ .../crystal/semantic/cleanup_transformer.cr | 55 ++++++++----------- 2 files changed, 63 insertions(+), 31 deletions(-) diff --git a/spec/compiler/normalize/string_interpolation_spec.cr b/spec/compiler/normalize/string_interpolation_spec.cr index f92e6c2a200f..1347792170b0 100644 --- a/spec/compiler/normalize/string_interpolation_spec.cr +++ b/spec/compiler/normalize/string_interpolation_spec.cr @@ -52,4 +52,43 @@ describe "Normalize: string interpolation" do string = node.should be_a(StringLiteral) string.value.should eq("hello world") end + + it "replaces char constant" do + result = semantic(%( + def String.interpolation(*args); ""; end + + OBJ = 'l' + + "hello wor\#{OBJ}d" + )) + node = result.node.as(Expressions).last + string = node.should be_a(StringLiteral) + string.value.should eq("hello world") + end + + it "replaces number constant" do + result = semantic(%( + def String.interpolation(*args); ""; end + + OBJ = 9_f32 + + "nine as a float: \#{OBJ}" + )) + node = result.node.as(Expressions).last + string = node.should be_a(StringLiteral) + string.value.should eq("nine as a float: 9.0") + end + + it "replaces boolean constant" do + result = semantic(%( + def String.interpolation(*args); ""; end + + OBJ = false + + "boolean false: \#{OBJ}" + )) + node = result.node.as(Expressions).last + string = node.should be_a(StringLiteral) + string.value.should eq("boolean false: false") + end end diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 70a8ffe63590..639de078fcd6 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -256,35 +256,28 @@ module Crystal end def transform(node : StringInterpolation) - # See if we can solve all the pieces to string literals. - # If that's the case, we can replace the entire interpolation - # with a single string literal. - pieces = node.expressions.dup - solve_string_interpolation_expressions(pieces) - - if pieces.all?(StringLiteral) - string = pieces.join(&.as(StringLiteral).value) - string_literal = StringLiteral.new(string).at(node) - string_literal.type = @program.string - return string_literal - end - - if expanded = node.expanded - return expanded.transform(self) - end - node - end - - private def solve_string_interpolation_expressions(pieces : Array(ASTNode)) - pieces.each_with_index do |piece, i| - replacement = solve_string_interpolation_expression(piece) - next unless replacement - - pieces[i] = replacement + string = node.expressions.join do |exp| + if !(transformed_piece = solve_string_interpolation_expression(exp)).nil? + # Valid piece, continue joining + next transformed_piece + elsif expanded = node.expanded + # Invalid piece, transform expansion and exit early + return expanded.transform(self) + else + # No expansion, return self + return node + end end + string_literal = StringLiteral.new(string).at(node) + string_literal.type = @program.string + string_literal end - private def solve_string_interpolation_expression(piece : ASTNode) : StringLiteral? + # Returns the solved piece for string interpolation, if it can find one. + # For example, this returns a String when given a StringLiteral. + private def solve_string_interpolation_expression(piece : ASTNode) : String | Char | Number::Primitive | Bool | Nil + # Check for ExpandableNode happens first in case any nodes below are + # updated to be ExpandableNodes themselves. if piece.is_a?(ExpandableNode) if expanded = piece.expanded return solve_string_interpolation_expression(expanded) @@ -294,13 +287,13 @@ module Crystal case piece when Path if target_const = piece.target_const - return solve_string_interpolation_expression(target_const.value) + solve_string_interpolation_expression(target_const.value) end - when StringLiteral - return piece + when StringLiteral then piece.value + when CharLiteral then piece.value + when NumberLiteral then piece.to_number + when BoolLiteral then piece.value end - - nil end def transform(node : ExpandableNode) From 0b251d4859ef07534d4f1c4df08cdac2e990791f Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Thu, 14 Dec 2023 09:05:44 +1100 Subject: [PATCH 0834/1551] Add `Enumerable(T)#to_set(& : T -> U) : Set(U) forall U` (#12654) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/enumerable_spec.cr | 14 ++++++++++++++ src/set.cr | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index f15ce053216e..04d46c7976a3 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -131,6 +131,20 @@ describe "Enumerable" do end end + describe "#to_set" do + context "without block" do + it "creates a Set from the unique elements of the collection" do + {1, 1, 2, 3}.to_set.should eq Set{1, 2, 3} + end + end + + context "with block" do + it "creates a Set from running the block against the collection's elements" do + {1, 2, 3, 4, 5}.to_set { |i| i // 2 }.should eq Set{0, 1, 2} + end + end + end + describe "chunk" do it "works" do [1].chunk { true }.to_a.should eq [{true, [1]}] diff --git a/src/set.cr b/src/set.cr index ca3addf72c9a..091352302c7d 100644 --- a/src/set.cr +++ b/src/set.cr @@ -495,4 +495,14 @@ module Enumerable def to_set : Set(T) Set.new(self) end + + # Returns a new `Set` with the unique results of running *block* against each + # element of the enumerable. + def to_set(&block : T -> U) : Set(U) forall U + set = Set(U).new + each do |elem| + set << yield elem + end + set + end end From 63781fae4b6c80339b600e4c5c8ce4b93b4eb3e2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 15 Dec 2023 06:24:19 +0800 Subject: [PATCH 0835/1551] Add `pending_wasm32` (#14086) --- .github/workflows/wasm32.yml | 2 +- spec/spec_helper.cr | 1 + spec/std/html_spec.cr | 4 ++-- spec/std/path_spec.cr | 2 +- spec/std/regex_spec.cr | 2 +- spec/std/spec_helper.cr | 1 + spec/std/string_spec.cr | 12 +++++------- spec/support/wasm32.cr | 19 +++++++++++++++++++ spec/wasm32_std_spec.cr | 6 +++--- 9 files changed, 34 insertions(+), 15 deletions(-) create mode 100644 spec/support/wasm32.cr diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 721ded21b861..0d4f32f4c044 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -40,7 +40,7 @@ jobs: rm wasm32-wasi-libs.tar.gz - name: Build spec/wasm32_std_spec.cr - run: bin/crystal build spec/wasm32_std_spec.cr -o wasm32_std_spec.wasm --target wasm32-wasi -Duse_pcre -Dwithout_openssl + run: bin/crystal build spec/wasm32_std_spec.cr -o wasm32_std_spec.wasm --target wasm32-wasi -Dwithout_iconv -Dwithout_openssl env: CRYSTAL_LIBRARY_PATH: ${{ github.workspace }}/wasm32-wasi-libs diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 98f39b1fc042..a1ee9f49daa1 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -8,6 +8,7 @@ require "compiler/requires" require "./support/syntax" require "./support/tempfile" require "./support/win32" +require "./support/wasm32" class Crystal::Program setter temp_var_counter diff --git a/spec/std/html_spec.cr b/spec/std/html_spec.cr index b4f374dca936..2cb5980bd658 100644 --- a/spec/std/html_spec.cr +++ b/spec/std/html_spec.cr @@ -1,4 +1,4 @@ -require "spec" +require "./spec_helper" require "html" describe "HTML" do @@ -12,7 +12,7 @@ describe "HTML" do end end - describe ".unescape" do + pending_wasm32 describe: ".unescape" do it "identity" do HTML.unescape("safe_string").should be("safe_string") end diff --git a/spec/std/path_spec.cr b/spec/std/path_spec.cr index d1ad7bbf9d81..baa7cffd37b8 100644 --- a/spec/std/path_spec.cr +++ b/spec/std/path_spec.cr @@ -931,7 +931,7 @@ describe Path do assert_paths_raw("foo..txt/", "foo.", &.stem) end - describe ".home" do + pending_wasm32 describe: ".home" do it "uses home from environment variable if set" do with_env({HOME_ENV_KEY => "foo/bar"}) do Path.home.should eq(Path.new("foo/bar")) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 0d600603c3cc..310f94ffc9b4 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -308,7 +308,7 @@ describe "Regex" do end end - it "doesn't crash with a large single line string" do + pending_wasm32 "doesn't crash with a large single line string" do str = File.read(datapath("large_single_line_string.txt")) {% if Regex::Engine.resolve.name == "Regex::PCRE" %} diff --git a/spec/std/spec_helper.cr b/spec/std/spec_helper.cr index 75f8a1fe36d3..7f4b80405826 100644 --- a/spec/std/spec_helper.cr +++ b/spec/std/spec_helper.cr @@ -2,6 +2,7 @@ require "spec" require "../support/tempfile" require "../support/fibers" require "../support/win32" +require "../support/wasm32" def datapath(*components) File.join("spec", "std", "data", *components) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 178fa81a172c..0d60b3ac2466 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2687,14 +2687,12 @@ describe "String" do end end - {% unless flag?(:wasm32) %} - it "allocates buffer of correct size (#3332)" do - String.new(255_u8) do |buffer| - LibGC.size(buffer).should be > 255 - {255, 0} - end + pending_wasm32 "allocates buffer of correct size (#3332)" do + String.new(255_u8) do |buffer| + LibGC.size(buffer).should be > 255 + {255, 0} end - {% end %} + end it "raises if returned bytesize is greater than capacity" do expect_raises ArgumentError, "Bytesize out of capacity bounds" do diff --git a/spec/support/wasm32.cr b/spec/support/wasm32.cr new file mode 100644 index 000000000000..98c9de69ea9e --- /dev/null +++ b/spec/support/wasm32.cr @@ -0,0 +1,19 @@ +require "spec" + +{% if flag?(:wasm32) %} + def pending_wasm32(description = "assert", file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block) + pending("#{description} [wasm32]", file, line, end_line) + end + + def pending_wasm32(*, describe, file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block) + pending_wasm32(describe, file, line, end_line) { } + end +{% else %} + def pending_wasm32(description = "assert", file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block) + it(description, file, line, end_line, &block) + end + + def pending_wasm32(*, describe, file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block) + describe(describe, file, line, end_line, &block) + end +{% end %} diff --git a/spec/wasm32_std_spec.cr b/spec/wasm32_std_spec.cr index bd1fd39de7a0..7d762e479b91 100644 --- a/spec/wasm32_std_spec.cr +++ b/spec/wasm32_std_spec.cr @@ -84,7 +84,7 @@ require "./std/float_printer/ieee_spec.cr" require "./std/float_spec.cr" require "./std/gc_spec.cr" require "./std/hash_spec.cr" -# require "./std/html_spec.cr" (failed to run) +require "./std/html_spec.cr" # require "./std/http/chunked_content_spec.cr" (failed linking) # require "./std/http/client/client_spec.cr" (failed linking) # require "./std/http/client/response_spec.cr" (failed linking) @@ -181,7 +181,7 @@ require "./std/named_tuple_spec.cr" # require "./std/openssl/x509/certificate_spec.cr" (failed linking) # require "./std/openssl/x509/name_spec.cr" (failed linking) require "./std/option_parser_spec.cr" -# require "./std/path_spec.cr" (failed to run) +require "./std/path_spec.cr" require "./std/pointer_spec.cr" require "./std/pp_spec.cr" require "./std/pretty_print_spec.cr" @@ -196,7 +196,7 @@ require "./std/random/secure_spec.cr" # require "./std/range_spec.cr" (failed linking) require "./std/record_spec.cr" # require "./std/reference_spec.cr" (failed to run) -# require "./std/regex_spec.cr" (failed to run) +require "./std/regex_spec.cr" require "./std/semantic_version_spec.cr" require "./std/set_spec.cr" require "./std/signal_spec.cr" From e5ab06e440ad48a2a24ef188d81d5248b0e2a621 Mon Sep 17 00:00:00 2001 From: Philip Ross Date: Fri, 15 Dec 2023 02:53:22 -0800 Subject: [PATCH 0836/1551] List spec tags (#13616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Johannes Müller --- spec/std/spec/list_tags_spec.cr | 118 ++++++++++++++++++++++++++++++++ spec/std/spec_helper.cr | 8 +-- src/spec/cli.cr | 6 ++ src/spec/dsl.cr | 60 ++++++++++++++-- 4 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 spec/std/spec/list_tags_spec.cr diff --git a/spec/std/spec/list_tags_spec.cr b/spec/std/spec/list_tags_spec.cr new file mode 100644 index 000000000000..83e06937989f --- /dev/null +++ b/spec/std/spec/list_tags_spec.cr @@ -0,0 +1,118 @@ +require "./spec_helper" + +describe Spec do + describe "list_tags" do + it "lists the count of all tags", tags: %w[slow] do + compile_and_run_source(<<-CRYSTAL, flags: %w(--no-debug), runtime_args: %w(--list-tags))[1].lines.should eq <<-OUT.lines + require "spec" + + it "untagged #1" do + end + it "untagged #2" do + end + it "untagged #3" do + end + + it "slow #1", tags: "slow" do + end + it "slow #2", tags: "slow" do + end + + it "untagged #4" do + end + + it "flakey #1", tags: "flakey" do + end + it "flakey #2, slow #3", tags: ["flakey", "slow"] do + end + + it "untagged #5" do + end + + pending "untagged #6" + + pending "untagged #7" do + end + + pending "slow #5", tags: "slow" + + describe "describe specs", tags: "describe" do + it "describe #1" do + end + it "describe #2" do + end + it "describe #3, slow #4", tags: "slow" do + end + it "describe #4, flakey #3", tags: "flakey" do + end + end + CRYSTAL + untagged: 7 + slow: 5 + describe: 4 + flakey: 3 + OUT + end + + it "lists the count of slow tags", tags: %w[slow] do + compile_and_run_source(<<-CRYSTAL, flags: %w(--no-debug), runtime_args: %w(--list-tags --tag slow))[1].lines.should eq <<-OUT.lines + require "spec" + + it "untagged #1" do + end + it "untagged #2" do + end + it "untagged #3" do + end + + it "slow #1", tags: "slow" do + end + it "slow #2", tags: "slow" do + end + + it "untagged #4" do + end + + it "flakey #1", tags: "flakey" do + end + it "flakey #2, slow #3", tags: ["flakey", "slow"] do + end + + it "untagged #5" do + end + + pending "untagged #6" + + pending "untagged #7" do + end + + pending "slow #5", tags: "slow" + + describe "describe specs", tags: "describe" do + it "describe #1" do + end + it "describe #2" do + end + it "describe #3, slow #4", tags: "slow" do + end + it "describe #4, flakey #3", tags: "flakey" do + end + end + CRYSTAL + slow: 5 + describe: 1 + flakey: 1 + OUT + end + + it "does nothing if there are no examples", tags: %w[slow] do + compile_and_run_source(<<-CRYSTAL, flags: %w(--no-debug), runtime_args: %w(--list-tags))[1].lines.should eq <<-OUT.lines + require "spec" + + describe "describe specs", tags: "describe" do + end + CRYSTAL + OUT + end + end +end diff --git a/spec/std/spec_helper.cr b/spec/std/spec_helper.cr index 7f4b80405826..18deaf0260bf 100644 --- a/spec/std/spec_helper.cr +++ b/spec/std/spec_helper.cr @@ -107,19 +107,19 @@ def compile_source(source, flags = %w(), file = __FILE__, &) end end -def compile_and_run_file(source_file, flags = %w(), file = __FILE__) +def compile_and_run_file(source_file, flags = %w(), runtime_args = %w(), file = __FILE__) compile_file(source_file, flags: flags, file: file) do |executable_file| output, error = IO::Memory.new, IO::Memory.new - status = Process.run executable_file, output: output, error: error + status = Process.run executable_file, args: runtime_args, output: output, error: error {status, output.to_s, error.to_s} end end -def compile_and_run_source(source, flags = %w(), file = __FILE__) +def compile_and_run_source(source, flags = %w(), runtime_args = %w(), file = __FILE__) with_tempfile("source_file", file: file) do |source_file| File.write(source_file, source) - compile_and_run_file(source_file, flags, file: file) + compile_and_run_file(source_file, flags, runtime_args, file: file) end end diff --git a/src/spec/cli.cr b/src/spec/cli.cr index 77856019181b..e17f59206123 100644 --- a/src/spec/cli.cr +++ b/src/spec/cli.cr @@ -22,6 +22,9 @@ module Spec # :nodoc: class_property? dry_run = false + # :nodoc: + class_property? list_tags = false + # :nodoc: def self.add_location(file, line) locations = @@locations ||= {} of String => Array(Int32) @@ -86,6 +89,9 @@ module Spec opts.on("--tag TAG", "run examples with the specified TAG, or exclude examples by adding ~ before the TAG.") do |tag| Spec.add_tag tag end + opts.on("--list-tags", "lists all the tags used.") do + Spec.list_tags = true + end opts.on("--order MODE", "run examples in random order by passing MODE as 'random' or to a specific seed by passing MODE as the seed value") do |mode| if mode.in?("default", "random") Spec.order = mode diff --git a/src/spec/dsl.cr b/src/spec/dsl.cr index d29fc7cf8b61..065b31e7aff4 100644 --- a/src/spec/dsl.cr +++ b/src/spec/dsl.cr @@ -210,21 +210,71 @@ module Spec next unless status == 0 begin - log_setup - maybe_randomize - run_filters - root_context.run + if Spec.list_tags? + execute_list_tags + else + execute_examples + end rescue ex STDERR.print "Unhandled exception: " ex.inspect_with_backtrace(STDERR) STDERR.flush @@aborted = true ensure - finish_run + finish_run unless Spec.list_tags? end end end + # :nodoc: + def self.execute_examples + log_setup + maybe_randomize + run_filters + root_context.run + end + + # :nodoc: + def self.execute_list_tags + run_filters + tag_counts = collect_tags(root_context) + print_list_tags(tag_counts) + end + + private def self.collect_tags(context) : Hash(String, Int32) + tag_counts = Hash(String, Int32).new(0) + collect_tags(tag_counts, context, Set(String).new) + tag_counts + end + + private def self.collect_tags(tag_counts, context : Context, tags) + if context.responds_to?(:tags) && (context_tags = context.tags) + tags += context_tags + end + context.children.each do |child| + collect_tags(tag_counts, child, tags) + end + end + + private def self.collect_tags(tag_counts, example : Example, tags) + if example_tags = example.tags + tags += example_tags + end + if tags.empty? + tag_counts.update("untagged") { |count| count + 1 } + else + tags.tally(tag_counts) + end + end + + private def self.print_list_tags(tag_counts : Hash(String, Int32)) : Nil + return if tag_counts.empty? + longest_name_size = tag_counts.keys.max_of(&.size) + tag_counts.to_a.sort_by! { |k, v| {-v, k} }.each do |tag_name, count| + puts "#{tag_name.rjust(longest_name_size)}: #{count}" + end + end + # :nodoc: # # Workaround for #8914 From c53dd7248171f63f06aadb5e46ef7f7c83cda084 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 15 Dec 2023 18:53:32 +0800 Subject: [PATCH 0837/1551] Support the operand bundle API from LLVM 18 (#14082) --- src/llvm/builder.cr | 22 ++++++++++---- src/llvm/ext/llvm_ext.cc | 54 ++++++++++++++++++++++------------ src/llvm/lib_llvm/core.cr | 10 +++++++ src/llvm/lib_llvm/types.cr | 1 + src/llvm/lib_llvm_ext.cr | 27 ++++++++--------- src/llvm/operand_bundle_def.cr | 4 +-- 6 files changed, 78 insertions(+), 40 deletions(-) diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index 3018eae6e642..741f9ee8eb5c 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -86,7 +86,10 @@ class LLVM::Builder # check_func(func) # check_values(args) - Value.new LibLLVMExt.build_call2(self, func.function_type, func, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, bundle, name) + bundle_ref = bundle.to_unsafe + bundles = bundle_ref ? pointerof(bundle_ref) : Pointer(Void).null.as(LibLLVM::OperandBundleRef*) + num_bundles = bundle_ref ? 1 : 0 + Value.new {{ LibLLVM::IS_LT_180 ? LibLLVMExt : LibLLVM }}.build_call_with_operand_bundles(self, func.function_type, func, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, bundles, num_bundles, name) end def call(type : LLVM::Type, func : LLVM::Function, args : Array(LLVM::Value), name : String = "") @@ -102,7 +105,10 @@ class LLVM::Builder # check_func(func) # check_values(args) - Value.new LibLLVMExt.build_call2(self, type, func, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, bundle, name) + bundle_ref = bundle.to_unsafe + bundles = bundle_ref ? pointerof(bundle_ref) : Pointer(Void).null.as(LibLLVM::OperandBundleRef*) + num_bundles = bundle_ref ? 1 : 0 + Value.new {{ LibLLVM::IS_LT_180 ? LibLLVMExt : LibLLVM }}.build_call_with_operand_bundles(self, type, func, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, bundles, num_bundles, name) end def call(type : LLVM::Type, func : LLVM::Function, args : Array(LLVM::Value), bundle : LLVM::OperandBundleDef) @@ -279,7 +285,7 @@ class LLVM::Builder end def build_operand_bundle_def(name, values : Array(LLVM::Value)) - LLVM::OperandBundleDef.new LibLLVMExt.build_operand_bundle_def(name, values.to_unsafe.as(LibLLVM::ValueRef*), values.size) + LLVM::OperandBundleDef.new {{ LibLLVM::IS_LT_180 ? LibLLVMExt : LibLLVM }}.create_operand_bundle(name, name.bytesize, values.to_unsafe.as(LibLLVM::ValueRef*), values.size) end def build_catch_ret(pad, basic_block) @@ -290,7 +296,10 @@ class LLVM::Builder def invoke(fn : LLVM::Function, args : Array(LLVM::Value), a_then, a_catch, bundle : LLVM::OperandBundleDef = LLVM::OperandBundleDef.null, name = "") # check_func(fn) - Value.new LibLLVMExt.build_invoke2 self, fn.function_type, fn, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, a_then, a_catch, bundle, name + bundle_ref = bundle.to_unsafe + bundles = bundle_ref ? pointerof(bundle_ref) : Pointer(Void).null.as(LibLLVM::OperandBundleRef*) + num_bundles = bundle_ref ? 1 : 0 + Value.new {{ LibLLVM::IS_LT_180 ? LibLLVMExt : LibLLVM }}.build_invoke_with_operand_bundles(self, fn.function_type, fn, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, a_then, a_catch, bundles, num_bundles, name) end def invoke(type : LLVM::Type, fn : LLVM::Function, args : Array(LLVM::Value), a_then, a_catch, *, name = "") @@ -304,7 +313,10 @@ class LLVM::Builder # check_type("invoke", type) # check_func(fn) - Value.new LibLLVMExt.build_invoke2 self, type, fn, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, a_then, a_catch, bundle, name + bundle_ref = bundle.to_unsafe + bundles = bundle_ref ? pointerof(bundle_ref) : Pointer(Void).null.as(LibLLVM::OperandBundleRef*) + num_bundles = bundle_ref ? 1 : 0 + Value.new {{ LibLLVM::IS_LT_180 ? LibLLVMExt : LibLLVM }}.build_invoke_with_operand_bundles(self, type, fn, (args.to_unsafe.as(LibLLVM::ValueRef*)), args.size, a_then, a_catch, bundles, num_bundles, name) end def switch(value, otherwise, cases) diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc index 2962cee6d1f2..9473d2f05803 100644 --- a/src/llvm/ext/llvm_ext.cc +++ b/src/llvm/ext/llvm_ext.cc @@ -16,6 +16,11 @@ using namespace llvm; #define makeArrayRef ArrayRef #endif +#if !LLVM_VERSION_GE(18, 0) +typedef struct LLVMOpaqueOperandBundle *LLVMOperandBundleRef; +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(OperandBundleDef, LLVMOperandBundleRef) +#endif + extern "C" { #if !LLVM_VERSION_GE(9, 0) @@ -32,30 +37,43 @@ void LLVMExtClearCurrentDebugLocation(LLVMBuilderRef B) { } #endif -OperandBundleDef *LLVMExtBuildOperandBundleDef( - const char *Name, LLVMValueRef *Inputs, unsigned NumInputs) { - return new OperandBundleDef(Name, makeArrayRef(unwrap(Inputs), NumInputs)); +#if !LLVM_VERSION_GE(18, 0) +LLVMOperandBundleRef LLVMExtCreateOperandBundle(const char *Tag, size_t TagLen, + LLVMValueRef *Args, + unsigned NumArgs) { + return wrap(new OperandBundleDef(std::string(Tag, TagLen), + makeArrayRef(unwrap(Args), NumArgs))); } -LLVMValueRef LLVMExtBuildCall2( - LLVMBuilderRef B, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, - OperandBundleDef *Bundle, const char *Name) { - unsigned Len = Bundle ? 1 : 0; - ArrayRef Bundles = makeArrayRef(Bundle, Len); +LLVMValueRef +LLVMExtBuildCallWithOperandBundles(LLVMBuilderRef B, LLVMTypeRef Ty, + LLVMValueRef Fn, LLVMValueRef *Args, + unsigned NumArgs, LLVMOperandBundleRef *Bundles, + unsigned NumBundles, const char *Name) { + FunctionType *FTy = unwrap(Ty); + SmallVector OBs; + for (auto *Bundle : makeArrayRef(Bundles, NumBundles)) { + OperandBundleDef *OB = unwrap(Bundle); + OBs.push_back(*OB); + } return wrap(unwrap(B)->CreateCall( - (llvm::FunctionType*) unwrap(Ty), unwrap(Fn), makeArrayRef(unwrap(Args), NumArgs), Bundles, Name)); + FTy, unwrap(Fn), makeArrayRef(unwrap(Args), NumArgs), OBs, Name)); } -LLVMValueRef LLVMExtBuildInvoke2( - LLVMBuilderRef B, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, - LLVMBasicBlockRef Then, LLVMBasicBlockRef Catch, OperandBundleDef *Bundle, - const char *Name) { - unsigned Len = Bundle ? 1 : 0; - ArrayRef Bundles = makeArrayRef(Bundle, Len); - return wrap(unwrap(B)->CreateInvoke((llvm::FunctionType*) unwrap(Ty), unwrap(Fn), unwrap(Then), unwrap(Catch), - makeArrayRef(unwrap(Args), NumArgs), - Bundles, Name)); +LLVMValueRef LLVMExtBuildInvokeWithOperandBundles( + LLVMBuilderRef B, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, + unsigned NumArgs, LLVMBasicBlockRef Then, LLVMBasicBlockRef Catch, + LLVMOperandBundleRef *Bundles, unsigned NumBundles, const char *Name) { + SmallVector OBs; + for (auto *Bundle : makeArrayRef(Bundles, NumBundles)) { + OperandBundleDef *OB = unwrap(Bundle); + OBs.push_back(*OB); + } + return wrap(unwrap(B)->CreateInvoke( + unwrap(Ty), unwrap(Fn), unwrap(Then), unwrap(Catch), + makeArrayRef(unwrap(Args), NumArgs), OBs, Name)); } +#endif #if !LLVM_VERSION_GE(18, 0) static TargetMachine *unwrap(LLVMTargetMachineRef P) { diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr index a0cfb708d515..5c7d7d519267 100644 --- a/src/llvm/lib_llvm/core.cr +++ b/src/llvm/lib_llvm/core.cr @@ -160,6 +160,10 @@ lib LibLLVM fun md_string_in_context = LLVMMDStringInContext(c : ContextRef, str : Char*, s_len : UInt) : ValueRef fun md_node_in_context = LLVMMDNodeInContext(c : ContextRef, vals : ValueRef*, count : UInt) : ValueRef + {% unless LibLLVM::IS_LT_180 %} + fun create_operand_bundle = LLVMCreateOperandBundle(tag : Char*, tag_len : SizeT, args : ValueRef*, num_args : UInt) : OperandBundleRef + {% end %} + fun get_basic_block_name = LLVMGetBasicBlockName(bb : BasicBlockRef) : Char* fun get_first_basic_block = LLVMGetFirstBasicBlock(fn : ValueRef) : BasicBlockRef fun get_next_basic_block = LLVMGetNextBasicBlock(bb : BasicBlockRef) : BasicBlockRef @@ -197,6 +201,9 @@ lib LibLLVM fun build_cond = LLVMBuildCondBr(BuilderRef, if : ValueRef, then : BasicBlockRef, else : BasicBlockRef) : ValueRef fun build_switch = LLVMBuildSwitch(BuilderRef, v : ValueRef, else : BasicBlockRef, num_cases : UInt) : ValueRef fun build_invoke2 = LLVMBuildInvoke2(BuilderRef, ty : TypeRef, fn : ValueRef, args : ValueRef*, num_args : UInt, then : BasicBlockRef, catch : BasicBlockRef, name : Char*) : ValueRef + {% unless LibLLVM::IS_LT_180 %} + fun build_invoke_with_operand_bundles = LLVMBuildInvokeWithOperandBundles(BuilderRef, ty : TypeRef, fn : ValueRef, args : ValueRef*, num_args : UInt, then : BasicBlockRef, catch : BasicBlockRef, bundles : OperandBundleRef*, num_bundles : UInt, name : Char*) : ValueRef + {% end %} fun build_unreachable = LLVMBuildUnreachable(BuilderRef) : ValueRef fun build_landing_pad = LLVMBuildLandingPad(b : BuilderRef, ty : TypeRef, pers_fn : ValueRef, num_clauses : UInt, name : Char*) : ValueRef @@ -260,6 +267,9 @@ lib LibLLVM fun build_phi = LLVMBuildPhi(BuilderRef, ty : TypeRef, name : Char*) : ValueRef fun build_call2 = LLVMBuildCall2(BuilderRef, TypeRef, fn : ValueRef, args : ValueRef*, num_args : UInt, name : Char*) : ValueRef + {% unless LibLLVM::IS_LT_180 %} + fun build_call_with_operand_bundles = LLVMBuildCallWithOperandBundles(BuilderRef, TypeRef, fn : ValueRef, args : ValueRef*, num_args : UInt, bundles : OperandBundleRef*, num_bundles : UInt, name : Char*) : ValueRef + {% end %} fun build_select = LLVMBuildSelect(BuilderRef, if : ValueRef, then : ValueRef, else : ValueRef, name : Char*) : ValueRef fun build_va_arg = LLVMBuildVAArg(BuilderRef, list : ValueRef, ty : TypeRef, name : Char*) : ValueRef fun build_extract_value = LLVMBuildExtractValue(BuilderRef, agg_val : ValueRef, index : UInt, name : Char*) : ValueRef diff --git a/src/llvm/lib_llvm/types.cr b/src/llvm/lib_llvm/types.cr index 0414757c57f0..a1b374f30219 100644 --- a/src/llvm/lib_llvm/types.cr +++ b/src/llvm/lib_llvm/types.cr @@ -15,5 +15,6 @@ lib LibLLVM {% if LibLLVM::IS_LT_170 %} type PassRegistryRef = Void* {% end %} + type OperandBundleRef = Void* type AttributeRef = Void* end diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr index 0fa8f4f95bd1..0ddf6b51be96 100644 --- a/src/llvm/lib_llvm_ext.cr +++ b/src/llvm/lib_llvm_ext.cr @@ -8,30 +8,27 @@ lib LibLLVMExt alias Char = LibC::Char alias Int = LibC::Int alias UInt = LibC::UInt - alias SizeT = LibC::SizeT - type OperandBundleDefRef = Void* - {% if LibLLVM::IS_LT_90 %} fun di_builder_create_enumerator = LLVMExtDIBuilderCreateEnumerator(builder : LibLLVM::DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : LibLLVM::Bool) : LibLLVM::MetadataRef fun clear_current_debug_location = LLVMExtClearCurrentDebugLocation(b : LibLLVM::BuilderRef) {% end %} - fun build_operand_bundle_def = LLVMExtBuildOperandBundleDef(name : LibC::Char*, - input : LibLLVM::ValueRef*, - num_input : LibC::UInt) : LibLLVMExt::OperandBundleDefRef + fun create_operand_bundle = LLVMExtCreateOperandBundle(tag : Char*, tag_len : SizeT, + args : LibLLVM::ValueRef*, + num_args : UInt) : LibLLVM::OperandBundleRef - fun build_call2 = LLVMExtBuildCall2(builder : LibLLVM::BuilderRef, ty : LibLLVM::TypeRef, fn : LibLLVM::ValueRef, - args : LibLLVM::ValueRef*, arg_count : LibC::UInt, - bundle : LibLLVMExt::OperandBundleDefRef, - name : LibC::Char*) : LibLLVM::ValueRef + fun build_call_with_operand_bundles = LLVMExtBuildCallWithOperandBundles(LibLLVM::BuilderRef, LibLLVM::TypeRef, fn : LibLLVM::ValueRef, + args : LibLLVM::ValueRef*, num_args : UInt, + bundles : LibLLVM::OperandBundleRef*, num_bundles : UInt, + name : Char*) : LibLLVM::ValueRef - fun build_invoke2 = LLVMExtBuildInvoke2(builder : LibLLVM::BuilderRef, ty : LibLLVM::TypeRef, fn : LibLLVM::ValueRef, - args : LibLLVM::ValueRef*, arg_count : LibC::UInt, - then : LibLLVM::BasicBlockRef, catch : LibLLVM::BasicBlockRef, - bundle : LibLLVMExt::OperandBundleDefRef, - name : LibC::Char*) : LibLLVM::ValueRef + fun build_invoke_with_operand_bundles = LLVMExtBuildInvokeWithOperandBundles(LibLLVM::BuilderRef, ty : LibLLVM::TypeRef, fn : LibLLVM::ValueRef, + args : LibLLVM::ValueRef*, num_args : UInt, + then : LibLLVM::BasicBlockRef, catch : LibLLVM::BasicBlockRef, + bundles : LibLLVM::OperandBundleRef*, num_bundles : UInt, + name : Char*) : LibLLVM::ValueRef fun set_target_machine_global_isel = LLVMExtSetTargetMachineGlobalISel(t : LibLLVM::TargetMachineRef, enable : LibLLVM::Bool) end diff --git a/src/llvm/operand_bundle_def.cr b/src/llvm/operand_bundle_def.cr index 0da61cbedc36..c74cf19f13cc 100644 --- a/src/llvm/operand_bundle_def.cr +++ b/src/llvm/operand_bundle_def.cr @@ -1,9 +1,9 @@ struct LLVM::OperandBundleDef - def initialize(@unwrap : LibLLVMExt::OperandBundleDefRef) + def initialize(@unwrap : LibLLVM::OperandBundleRef) end def self.null - LLVM::OperandBundleDef.new(Pointer(::Void).null.as(LibLLVMExt::OperandBundleDefRef)) + new(Pointer(::Void).null.as(LibLLVM::OperandBundleRef)) end def to_unsafe From 1c8a68ae210b43bc3c2a09850192307e07ec22cc Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 15 Dec 2023 22:41:02 +0800 Subject: [PATCH 0838/1551] Fix `Indexable#each_repeated_combination(n)` when `n > size` (#14092) --- spec/std/indexable_spec.cr | 6 ++++++ src/indexable.cr | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/std/indexable_spec.cr b/spec/std/indexable_spec.cr index f7c39f7ba485..75a20f14e521 100644 --- a/spec/std/indexable_spec.cr +++ b/spec/std/indexable_spec.cr @@ -1,4 +1,5 @@ require "spec" +require "spec/helpers/iterate" private class SafeIndexable include Indexable(Int32) @@ -811,5 +812,10 @@ describe Indexable do end iter.next.should be_a(Iterator::Stop) end + + describe "n > size (#14088)" do + it_iterates "#each_repeated_combination", [[1, 1, 1], [1, 1, 2], [1, 2, 2], [2, 2, 2]], SafeIndexable.new(2, 1).each_repeated_combination(3) + it_iterates "#each_repeated_combination", [[1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 2, 2], [1, 2, 2, 2], [2, 2, 2, 2]], SafeIndexable.new(2, 1).each_repeated_combination(4) + end end end diff --git a/src/indexable.cr b/src/indexable.cr index 4c69ee3015f4..3725c1552e38 100644 --- a/src/indexable.cr +++ b/src/indexable.cr @@ -1483,7 +1483,7 @@ module Indexable(T) @copy = array.dup @indices = Array.new(@size, 0) @pool = @indices.map { |i| @copy[i] } - @stop = @size > @n + @stop = false @i = @size - 1 @first = true end From a3491570cac2ce03f218a505a957389ebe3d282c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 15 Dec 2023 22:41:36 +0800 Subject: [PATCH 0839/1551] Implement `sprintf "%e"` in Crystal (#14084) --- spec/std/sprintf_spec.cr | 176 ++++++++++++++++++++++++++++++++++++++- src/string/formatter.cr | 41 ++++++++- 2 files changed, 213 insertions(+), 4 deletions(-) diff --git a/spec/std/sprintf_spec.cr b/spec/std/sprintf_spec.cr index 82a4af02c523..5a758d29551a 100644 --- a/spec/std/sprintf_spec.cr +++ b/spec/std/sprintf_spec.cr @@ -407,10 +407,182 @@ describe "::sprintf" do end end + context "scientific format" do + it "works" do + assert_sprintf "%e", 123.45, "1.234500e+2" + assert_sprintf "%E", 123.45, "1.234500E+2" + + assert_sprintf "%e", Float64::MAX, "1.797693e+308" + assert_sprintf "%e", Float64::MIN_POSITIVE, "2.225074e-308" + assert_sprintf "%e", Float64::MIN_SUBNORMAL, "4.940656e-324" + assert_sprintf "%e", 0.0, "0.000000e+0" + assert_sprintf "%e", -0.0, "-0.000000e+0" + assert_sprintf "%e", -Float64::MIN_SUBNORMAL, "-4.940656e-324" + assert_sprintf "%e", -Float64::MIN_POSITIVE, "-2.225074e-308" + assert_sprintf "%e", Float64::MIN, "-1.797693e+308" + end + + context "width specifier" do + it "sets the minimum length of the string" do + assert_sprintf "%20e", 123.45, " 1.234500e+2" + assert_sprintf "%20e", -123.45, " -1.234500e+2" + assert_sprintf "%+20e", 123.45, " +1.234500e+2" + + assert_sprintf "%12e", 123.45, " 1.234500e+2" + assert_sprintf "%12e", -123.45, "-1.234500e+2" + assert_sprintf "%+12e", 123.45, "+1.234500e+2" + + assert_sprintf "%11e", 123.45, "1.234500e+2" + assert_sprintf "%11e", -123.45, "-1.234500e+2" + assert_sprintf "%+11e", 123.45, "+1.234500e+2" + + assert_sprintf "%2e", 123.45, "1.234500e+2" + assert_sprintf "%2e", -123.45, "-1.234500e+2" + assert_sprintf "%+2e", 123.45, "+1.234500e+2" + end + + it "left-justifies on negative width" do + assert_sprintf "%*e", [-20, 123.45], "1.234500e+2 " + end + end + + context "precision specifier" do + it "sets the minimum length of the fractional part" do + assert_sprintf "%.0e", 2.0, "2e+0" + assert_sprintf "%.0e", 2.5.prev_float, "2e+0" + assert_sprintf "%.0e", 2.5, "2e+0" + assert_sprintf "%.0e", 2.5.next_float, "3e+0" + assert_sprintf "%.0e", 3.0, "3e+0" + assert_sprintf "%.0e", 3.5.prev_float, "3e+0" + assert_sprintf "%.0e", 3.5, "4e+0" + assert_sprintf "%.0e", 3.5.next_float, "4e+0" + assert_sprintf "%.0e", 4.0, "4e+0" + + assert_sprintf "%.0e", 9.5, "1e+1" + + assert_sprintf "%.100e", 1.1, "1.1000000000000000888178419700125232338905334472656250000000000000000000000000000000000000000000000000e+0" + + assert_sprintf "%.10000e", 1.0, "1.#{"0" * 10000}e+0" + + assert_sprintf "%.1000e", Float64::MIN_POSITIVE.prev_float, + "2.2250738585072008890245868760858598876504231122409594654935248025624400092282356951" \ + "787758888037591552642309780950434312085877387158357291821993020294379224223559819827" \ + "501242041788969571311791082261043971979604000454897391938079198936081525613113376149" \ + "842043271751033627391549782731594143828136275113838604094249464942286316695429105080" \ + "201815926642134996606517803095075913058719846423906068637102005108723282784678843631" \ + "944515866135041223479014792369585208321597621066375401613736583044193603714778355306" \ + "682834535634005074073040135602968046375918583163124224521599262546494300836851861719" \ + "422417646455137135420132217031370496583210154654068035397417906022589503023501937519" \ + "773030945763173210852507299305089761582519159720757232455434770912461317493580281734" \ + "466552734375000000000000000000000000000000000000000000000000000000000000000000000000" \ + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \ + "000000000000000000000000000000000000000000000000000000000000000000000000000000e-308" + end + + it "can be used with width" do + assert_sprintf "%20.12e", 123.45, " 1.234500000000e+2" + assert_sprintf "%20.12e", -123.45, " -1.234500000000e+2" + assert_sprintf "%20.12e", 0.0, " 0.000000000000e+0" + + assert_sprintf "%-20.12e", 123.45, "1.234500000000e+2 " + assert_sprintf "%-20.12e", -123.45, "-1.234500000000e+2 " + assert_sprintf "%-20.12e", 0.0, "0.000000000000e+0 " + + assert_sprintf "%8.12e", 123.45, "1.234500000000e+2" + assert_sprintf "%8.12e", -123.45, "-1.234500000000e+2" + assert_sprintf "%8.12e", 0.0, "0.000000000000e+0" + end + + it "is ignored if precision argument is negative" do + assert_sprintf "%.*e", [-2, 123.45], "1.234500e+2" + end + end + + context "sharp flag" do + it "prints a decimal point even if no digits follow" do + assert_sprintf "%#.0e", 1.0, "1.e+0" + assert_sprintf "%#.0e", 10000.0, "1.e+4" + assert_sprintf "%#.0e", 1.0e+23, "1.e+23" + assert_sprintf "%#.0e", 1.0e-100, "1.e-100" + assert_sprintf "%#.0e", 0.0, "0.e+0" + assert_sprintf "%#.0e", -0.0, "-0.e+0" + end + end + + context "plus flag" do + it "writes a plus sign for positive values" do + assert_sprintf "%+e", 123.45, "+1.234500e+2" + assert_sprintf "%+e", -123.45, "-1.234500e+2" + assert_sprintf "%+e", 0.0, "+0.000000e+0" + end + + it "writes plus sign after left space-padding" do + assert_sprintf "%+20e", 123.45, " +1.234500e+2" + assert_sprintf "%+20e", -123.45, " -1.234500e+2" + assert_sprintf "%+20e", 0.0, " +0.000000e+0" + end + + it "writes plus sign before left zero-padding" do + assert_sprintf "%+020e", 123.45, "+000000001.234500e+2" + assert_sprintf "%+020e", -123.45, "-000000001.234500e+2" + assert_sprintf "%+020e", 0.0, "+000000000.000000e+0" + end + end + + context "space flag" do + it "writes a space for positive values" do + assert_sprintf "% e", 123.45, " 1.234500e+2" + assert_sprintf "% e", -123.45, "-1.234500e+2" + assert_sprintf "% e", 0.0, " 0.000000e+0" + end + + it "writes space before left space-padding" do + assert_sprintf "% 20e", 123.45, " 1.234500e+2" + assert_sprintf "% 20e", -123.45, " -1.234500e+2" + assert_sprintf "% 20e", 0.0, " 0.000000e+0" + + assert_sprintf "% 020e", 123.45, " 000000001.234500e+2" + assert_sprintf "% 020e", -123.45, "-000000001.234500e+2" + assert_sprintf "% 020e", 0.0, " 000000000.000000e+0" + end + + it "is ignored if plus flag is also specified" do + assert_sprintf "% +e", 123.45, "+1.234500e+2" + assert_sprintf "%+ e", -123.45, "-1.234500e+2" + end + end + + context "zero flag" do + it "left-pads the result with zeros" do + assert_sprintf "%020e", 123.45, "0000000001.234500e+2" + assert_sprintf "%020e", -123.45, "-000000001.234500e+2" + assert_sprintf "%020e", 0.0, "0000000000.000000e+0" + end + + it "is ignored if string is left-justified" do + assert_sprintf "%-020e", 123.45, "1.234500e+2 " + assert_sprintf "%-020e", -123.45, "-1.234500e+2 " + assert_sprintf "%-020e", 0.0, "0.000000e+0 " + end + + it "can be used with precision" do + assert_sprintf "%020.12e", 123.45, "0001.234500000000e+2" + assert_sprintf "%020.12e", -123.45, "-001.234500000000e+2" + assert_sprintf "%020.12e", 0.0, "0000.000000000000e+0" + end + end + + context "minus flag" do + it "left-justifies the string" do + assert_sprintf "%-20e", 123.45, "1.234500e+2 " + assert_sprintf "%-20e", -123.45, "-1.234500e+2 " + assert_sprintf "%-20e", 0.0, "0.000000e+0 " + end + end + end + pending_win32 "works for other formats" do assert_sprintf "%g", 123, "123" - assert_sprintf "%e", 123.45, "1.234500e+02" - assert_sprintf "%E", 123.45, "1.234500E+02" assert_sprintf "%G", 12345678.45, "1.23457E+07" assert_sprintf "%a", 12345678.45, "0x1.78c29ce666666p+23" assert_sprintf "%A", 12345678.45, "0X1.78C29CE666666P+23" diff --git a/src/string/formatter.cr b/src/string/formatter.cr index cdf5710b45f4..4eb13ed79830 100644 --- a/src/string/formatter.cr +++ b/src/string/formatter.cr @@ -387,8 +387,45 @@ struct String::Formatter(A) # Formats floats with `%e` or `%E` private def float_scientific(float, flags) - # TODO: implement using `Float::Printer::RyuPrintf` - float_fallback(float, flags) + # the longest string possible is due to + # `Float64::MIN_POSITIVE.prev_float`, which produces `2.` followed by 766 + # nonzero digits and then `e-308`; there is also no need for any precision + # > 766 because all trailing digits will be zeros + if precision = flags.precision + printf_precision = {precision.to_u32, 766_u32}.min + trailing_zeros = {precision - printf_precision, 0}.max + else + # default precision for C's `%e` + printf_precision = 6_u32 + trailing_zeros = 0 + end + + printf_buf = uninitialized UInt8[773] + printf_size = Float::Printer::RyuPrintf.d2exp_buffered_n(float, printf_precision, printf_buf.to_unsafe) + printf_slice = printf_buf.to_slice[0, printf_size] + dot_index = printf_slice.index('.'.ord) + e_index = printf_slice.rindex!('e'.ord) + sign = Math.copysign(1.0, float) + + printf_slice[e_index] = 'E'.ord.to_u8! if flags.type == 'E' + + str_size = printf_size + trailing_zeros + str_size += 1 if sign < 0 || flags.plus || flags.space + str_size += 1 if flags.sharp && dot_index.nil? + + pad(str_size, flags) if flags.left_padding? && flags.padding_char != '0' + + # this preserves -0.0's sign correctly + write_plus_or_space(sign, flags) + @io << '-' if sign < 0 + + pad(str_size, flags) if flags.left_padding? && flags.padding_char == '0' + @io.write_string(printf_slice[0, e_index]) + trailing_zeros.times { @io << '0' } + @io << '.' if flags.sharp && dot_index.nil? + @io.write_string(printf_slice[e_index..]) + + pad(str_size, flags) if flags.right_padding? end # Formats floats with `%g` or `%G` From 3d9bc3826bc7b90564fb327052f0168d54a36404 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 16 Dec 2023 19:41:41 +0800 Subject: [PATCH 0840/1551] Add `LLVM::OperandBundleDef#dispose` (#14095) --- src/compiler/crystal/codegen/crystal_llvm_builder.cr | 6 +++++- src/compiler/crystal/codegen/llvm_builder_helper.cr | 12 ++++++++++-- src/llvm/ext/llvm_ext.cc | 4 ++++ src/llvm/lib_llvm/core.cr | 1 + src/llvm/lib_llvm_ext.cr | 2 ++ src/llvm/operand_bundle_def.cr | 4 ++++ 6 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/compiler/crystal/codegen/crystal_llvm_builder.cr b/src/compiler/crystal/codegen/crystal_llvm_builder.cr index 54116a9d265e..28fdb64fea19 100644 --- a/src/compiler/crystal/codegen/crystal_llvm_builder.cr +++ b/src/compiler/crystal/codegen/crystal_llvm_builder.cr @@ -45,7 +45,11 @@ module Crystal funclet = LLVM::OperandBundleDef.null end - call @printf, [global_string_pointer(format)] + args, bundle: funclet + begin + call @printf, [global_string_pointer(format)] + args, bundle: funclet + ensure + funclet.dispose + end end def position_at_end(block) diff --git a/src/compiler/crystal/codegen/llvm_builder_helper.cr b/src/compiler/crystal/codegen/llvm_builder_helper.cr index 088ea772552e..3a9227282aed 100644 --- a/src/compiler/crystal/codegen/llvm_builder_helper.cr +++ b/src/compiler/crystal/codegen/llvm_builder_helper.cr @@ -128,7 +128,11 @@ module Crystal funclet = LLVM::OperandBundleDef.null end - builder.call(func.type, func.func, args, bundle: funclet, name: name) + begin + builder.call(func.type, func.func, args, bundle: funclet, name: name) + ensure + funclet.dispose + end end def invoke(func : LLVMTypedFunction, args : Array(LLVM::Value), a_then, a_catch, name : String = "") @@ -138,7 +142,11 @@ module Crystal funclet = LLVM::OperandBundleDef.null end - builder.invoke(func.type, func.func, args, a_then, a_catch, bundle: funclet, name: name) + begin + builder.invoke(func.type, func.func, args, a_then, a_catch, bundle: funclet, name: name) + ensure + funclet.dispose + end end delegate ptr2int, int2ptr, and, or, not, bit_cast, diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc index 9473d2f05803..cb7885734b2b 100644 --- a/src/llvm/ext/llvm_ext.cc +++ b/src/llvm/ext/llvm_ext.cc @@ -45,6 +45,10 @@ LLVMOperandBundleRef LLVMExtCreateOperandBundle(const char *Tag, size_t TagLen, makeArrayRef(unwrap(Args), NumArgs))); } +void LLVMExtDisposeOperandBundle(LLVMOperandBundleRef Bundle) { + delete unwrap(Bundle); +} + LLVMValueRef LLVMExtBuildCallWithOperandBundles(LLVMBuilderRef B, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr index 5c7d7d519267..6eec889e2442 100644 --- a/src/llvm/lib_llvm/core.cr +++ b/src/llvm/lib_llvm/core.cr @@ -162,6 +162,7 @@ lib LibLLVM {% unless LibLLVM::IS_LT_180 %} fun create_operand_bundle = LLVMCreateOperandBundle(tag : Char*, tag_len : SizeT, args : ValueRef*, num_args : UInt) : OperandBundleRef + fun dispose_operand_bundle = LLVMDisposeOperandBundle(bundle : OperandBundleRef) {% end %} fun get_basic_block_name = LLVMGetBasicBlockName(bb : BasicBlockRef) : Char* diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr index 0ddf6b51be96..b4fe0fc2a43f 100644 --- a/src/llvm/lib_llvm_ext.cr +++ b/src/llvm/lib_llvm_ext.cr @@ -19,6 +19,8 @@ lib LibLLVMExt args : LibLLVM::ValueRef*, num_args : UInt) : LibLLVM::OperandBundleRef + fun dispose_operand_bundle = LLVMExtDisposeOperandBundle(bundle : LibLLVM::OperandBundleRef) + fun build_call_with_operand_bundles = LLVMExtBuildCallWithOperandBundles(LibLLVM::BuilderRef, LibLLVM::TypeRef, fn : LibLLVM::ValueRef, args : LibLLVM::ValueRef*, num_args : UInt, bundles : LibLLVM::OperandBundleRef*, num_bundles : UInt, diff --git a/src/llvm/operand_bundle_def.cr b/src/llvm/operand_bundle_def.cr index c74cf19f13cc..42a959c288a4 100644 --- a/src/llvm/operand_bundle_def.cr +++ b/src/llvm/operand_bundle_def.cr @@ -9,4 +9,8 @@ struct LLVM::OperandBundleDef def to_unsafe @unwrap end + + def dispose + {{ LibLLVM::IS_LT_180 ? LibLLVMExt : LibLLVM }}.dispose_operand_bundle(@unwrap) if @unwrap + end end From 756a876cfbf5f961efb7c366856b83c978f9eaef Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sun, 17 Dec 2023 18:34:27 +0100 Subject: [PATCH 0841/1551] MT: math overflow after spawning Int32::MAX + 1 fibers (#14096) --- src/crystal/scheduler.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index 0667626f787e..588a9b595962 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -191,7 +191,7 @@ class Crystal::Scheduler protected def find_target_thread if workers = @@workers - @rr_target += 1 + @rr_target &+= 1 workers[@rr_target % workers.size] else Thread.current From 8fe3c70b3dc2078b7a03d3fc7c6736dec4f42405 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 18 Dec 2023 01:34:38 +0800 Subject: [PATCH 0842/1551] Implement `sprintf "%a"` in Crystal (#14102) --- spec/std/sprintf_spec.cr | 299 +++++++++++++++++++++++++++++++++- src/float/printer.cr | 10 +- src/float/printer/hexfloat.cr | 77 ++++++++- src/string/formatter.cr | 31 +++- 4 files changed, 392 insertions(+), 25 deletions(-) diff --git a/spec/std/sprintf_spec.cr b/spec/std/sprintf_spec.cr index 5a758d29551a..f37384d3a70c 100644 --- a/spec/std/sprintf_spec.cr +++ b/spec/std/sprintf_spec.cr @@ -1,4 +1,5 @@ require "./spec_helper" +require "../support/number" require "spec/helpers/string" require "big" @@ -581,13 +582,297 @@ describe "::sprintf" do end end - pending_win32 "works for other formats" do - assert_sprintf "%g", 123, "123" - assert_sprintf "%G", 12345678.45, "1.23457E+07" - assert_sprintf "%a", 12345678.45, "0x1.78c29ce666666p+23" - assert_sprintf "%A", 12345678.45, "0X1.78C29CE666666P+23" - assert_sprintf "%100.50g", 123.45, " 123.4500000000000028421709430404007434844970703125" - assert_sprintf "%#.12g", 12345.0, "12345.0000000" + context "general format" do + pending_win32 "works" do + assert_sprintf "%g", 123, "123" + assert_sprintf "%G", 12345678.45, "1.23457E+07" + assert_sprintf "%100.50g", 123.45, " 123.4500000000000028421709430404007434844970703125" + assert_sprintf "%#.12g", 12345.0, "12345.0000000" + end + end + + context "hex format" do + it "works" do + assert_sprintf "%a", 1194684.0, "0x1.23abcp+20" + assert_sprintf "%A", 1194684.0, "0X1.23ABCP+20" + assert_sprintf "%a", 12345678.45, "0x1.78c29ce666666p+23" + assert_sprintf "%A", 12345678.45, "0X1.78C29CE666666P+23" + + assert_sprintf "%a", Float64::MAX, "0x1.fffffffffffffp+1023" + assert_sprintf "%a", Float64::MIN_POSITIVE, "0x1p-1022" + assert_sprintf "%a", Float64::MIN_SUBNORMAL, "0x0.0000000000001p-1022" + assert_sprintf "%a", 0.0, "0x0p+0" + assert_sprintf "%a", -0.0, "-0x0p+0" + assert_sprintf "%a", -Float64::MIN_SUBNORMAL, "-0x0.0000000000001p-1022" + assert_sprintf "%a", -Float64::MIN_POSITIVE, "-0x1p-1022" + assert_sprintf "%a", Float64::MIN, "-0x1.fffffffffffffp+1023" + end + + context "width specifier" do + it "sets the minimum length of the string" do + assert_sprintf "%20a", hexfloat("0x1p+0"), " 0x1p+0" + assert_sprintf "%20a", hexfloat("0x1.2p+0"), " 0x1.2p+0" + assert_sprintf "%20a", hexfloat("0x1.23p+0"), " 0x1.23p+0" + assert_sprintf "%20a", hexfloat("0x1.234p+0"), " 0x1.234p+0" + assert_sprintf "%20a", hexfloat("0x1.2345p+0"), " 0x1.2345p+0" + assert_sprintf "%20a", hexfloat("0x1.23456p+0"), " 0x1.23456p+0" + assert_sprintf "%20a", hexfloat("0x1.234567p+0"), " 0x1.234567p+0" + assert_sprintf "%20a", hexfloat("0x1.2345678p+0"), " 0x1.2345678p+0" + assert_sprintf "%20a", hexfloat("0x1.23456789p+0"), " 0x1.23456789p+0" + assert_sprintf "%20a", hexfloat("0x1.23456789ap+0"), " 0x1.23456789ap+0" + assert_sprintf "%20a", hexfloat("0x1.23456789abp+0"), " 0x1.23456789abp+0" + assert_sprintf "%20a", hexfloat("0x1.23456789abcp+0"), " 0x1.23456789abcp+0" + + assert_sprintf "%20a", hexfloat("-0x1p+0"), " -0x1p+0" + assert_sprintf "%20a", hexfloat("-0x1.2p+0"), " -0x1.2p+0" + assert_sprintf "%20a", hexfloat("-0x1.23p+0"), " -0x1.23p+0" + assert_sprintf "%20a", hexfloat("-0x1.234p+0"), " -0x1.234p+0" + assert_sprintf "%20a", hexfloat("-0x1.2345p+0"), " -0x1.2345p+0" + assert_sprintf "%20a", hexfloat("-0x1.23456p+0"), " -0x1.23456p+0" + assert_sprintf "%20a", hexfloat("-0x1.234567p+0"), " -0x1.234567p+0" + assert_sprintf "%20a", hexfloat("-0x1.2345678p+0"), " -0x1.2345678p+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789p+0"), " -0x1.23456789p+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789ap+0"), " -0x1.23456789ap+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789abp+0"), " -0x1.23456789abp+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789abcp+0"), " -0x1.23456789abcp+0" + + assert_sprintf "%+20a", 1194684.0, " +0x1.23abcp+20" + + assert_sprintf "%14a", 1194684.0, " 0x1.23abcp+20" + assert_sprintf "%14a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+14a", 1194684.0, "+0x1.23abcp+20" + + assert_sprintf "%13a", 1194684.0, "0x1.23abcp+20" + assert_sprintf "%13a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+13a", 1194684.0, "+0x1.23abcp+20" + + assert_sprintf "%2a", 1194684.0, "0x1.23abcp+20" + assert_sprintf "%2a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+2a", 1194684.0, "+0x1.23abcp+20" + end + + it "left-justifies on negative width" do + assert_sprintf "%*a", [-20, 1194684.0], "0x1.23abcp+20 " + end + end + + context "precision specifier" do + it "sets the minimum length of the fractional part" do + assert_sprintf "%.0a", 0.0, "0x0p+0" + + assert_sprintf "%.0a", (Float64::MIN_POSITIVE / 2).prev_float, "0x0p-1022" + assert_sprintf "%.0a", Float64::MIN_POSITIVE / 2, "0x0p-1022" + assert_sprintf "%.0a", (Float64::MIN_POSITIVE / 2).next_float, "0x1p-1022" + assert_sprintf "%.0a", Float64::MIN_POSITIVE.prev_float, "0x1p-1022" + assert_sprintf "%.0a", Float64::MIN_POSITIVE, "0x1p-1022" + + assert_sprintf "%.0a", 0.0625, "0x1p-4" + assert_sprintf "%.0a", 0.0625.next_float, "0x1p-4" + assert_sprintf "%.0a", 0.09375.prev_float, "0x1p-4" + assert_sprintf "%.0a", 0.09375, "0x2p-4" + assert_sprintf "%.0a", 0.09375.next_float, "0x2p-4" + assert_sprintf "%.0a", 0.125.prev_float, "0x2p-4" + assert_sprintf "%.0a", 0.125, "0x1p-3" + + assert_sprintf "%.1a", 2.0, "0x1.0p+1" + assert_sprintf "%.1a", 2.0.next_float, "0x1.0p+1" + assert_sprintf "%.1a", 2.0625.prev_float, "0x1.0p+1" + assert_sprintf "%.1a", 2.0625, "0x1.0p+1" + assert_sprintf "%.1a", 2.0625.next_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.125.prev_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.125, "0x1.1p+1" + assert_sprintf "%.1a", 2.125.next_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.1875.prev_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.1875, "0x1.2p+1" + assert_sprintf "%.1a", 2.1875.next_float, "0x1.2p+1" + assert_sprintf "%.1a", 2.25.prev_float, "0x1.2p+1" + assert_sprintf "%.1a", 2.25, "0x1.2p+1" + + assert_sprintf "%.1a", 60.0, "0x1.ep+5" + assert_sprintf "%.1a", 60.0.next_float, "0x1.ep+5" + assert_sprintf "%.1a", 61.0.prev_float, "0x1.ep+5" + assert_sprintf "%.1a", 61.0, "0x1.ep+5" + assert_sprintf "%.1a", 61.0.next_float, "0x1.fp+5" + assert_sprintf "%.1a", 62.0.prev_float, "0x1.fp+5" + assert_sprintf "%.1a", 62.0, "0x1.fp+5" + assert_sprintf "%.1a", 62.0.next_float, "0x1.fp+5" + assert_sprintf "%.1a", 63.0.prev_float, "0x1.fp+5" + assert_sprintf "%.1a", 63.0, "0x2.0p+5" + assert_sprintf "%.1a", 63.0.next_float, "0x2.0p+5" + assert_sprintf "%.1a", 64.0.prev_float, "0x2.0p+5" + assert_sprintf "%.1a", 64.0, "0x1.0p+6" + + assert_sprintf "%.4a", 65536.0, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.0.next_float, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.5.prev_float, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.5, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.5.next_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.0.prev_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.0, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.0.next_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.5.prev_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.5, "0x1.0002p+16" + assert_sprintf "%.4a", 65537.5.next_float, "0x1.0002p+16" + assert_sprintf "%.4a", 65538.0.prev_float, "0x1.0002p+16" + assert_sprintf "%.4a", 65538.0, "0x1.0002p+16" + + assert_sprintf "%.4a", 131070.0, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.0.next_float, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.5.prev_float, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.5, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.5.next_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.0.prev_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.0, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.0.next_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.5.prev_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.5, "0x2.0000p+16" + assert_sprintf "%.4a", 131071.5.next_float, "0x2.0000p+16" + assert_sprintf "%.4a", 131072.0.prev_float, "0x2.0000p+16" + assert_sprintf "%.4a", 131072.0, "0x1.0000p+17" + + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x01, "0x0.000000000000p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x07, "0x0.000000000000p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x08, "0x0.000000000000p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x09, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x0f, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x10, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x11, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x17, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x18, "0x0.000000000002p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x19, "0x0.000000000002p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x1f, "0x0.000000000002p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x20, "0x0.000000000002p-1022" + + assert_sprintf "%.17a", Float64::MAX, "0x1.fffffffffffff0000p+1023" + assert_sprintf "%.16a", Float64::MAX, "0x1.fffffffffffff000p+1023" + assert_sprintf "%.15a", Float64::MAX, "0x1.fffffffffffff00p+1023" + assert_sprintf "%.14a", Float64::MAX, "0x1.fffffffffffff0p+1023" + assert_sprintf "%.13a", Float64::MAX, "0x1.fffffffffffffp+1023" + assert_sprintf "%.12a", Float64::MAX, "0x2.000000000000p+1023" + assert_sprintf "%.11a", Float64::MAX, "0x2.00000000000p+1023" + assert_sprintf "%.10a", Float64::MAX, "0x2.0000000000p+1023" + assert_sprintf "%.9a", Float64::MAX, "0x2.000000000p+1023" + assert_sprintf "%.8a", Float64::MAX, "0x2.00000000p+1023" + assert_sprintf "%.7a", Float64::MAX, "0x2.0000000p+1023" + assert_sprintf "%.6a", Float64::MAX, "0x2.000000p+1023" + assert_sprintf "%.5a", Float64::MAX, "0x2.00000p+1023" + assert_sprintf "%.4a", Float64::MAX, "0x2.0000p+1023" + assert_sprintf "%.3a", Float64::MAX, "0x2.000p+1023" + assert_sprintf "%.2a", Float64::MAX, "0x2.00p+1023" + assert_sprintf "%.1a", Float64::MAX, "0x2.0p+1023" + assert_sprintf "%.0a", Float64::MAX, "0x2p+1023" + + assert_sprintf "%.1000a", 1194684.0, "0x1.23abc#{"0" * 995}p+20" + end + + it "can be used with width" do + assert_sprintf "%20.8a", 1194684.0, " 0x1.23abc000p+20" + assert_sprintf "%20.8a", -1194684.0, " -0x1.23abc000p+20" + assert_sprintf "%20.8a", 0.0, " 0x0.00000000p+0" + + assert_sprintf "%-20.8a", 1194684.0, "0x1.23abc000p+20 " + assert_sprintf "%-20.8a", -1194684.0, "-0x1.23abc000p+20 " + assert_sprintf "%-20.8a", 0.0, "0x0.00000000p+0 " + + assert_sprintf "%4.8a", 1194684.0, "0x1.23abc000p+20" + assert_sprintf "%4.8a", -1194684.0, "-0x1.23abc000p+20" + assert_sprintf "%4.8a", 0.0, "0x0.00000000p+0" + end + + it "is ignored if precision argument is negative" do + assert_sprintf "%.*a", [-2, 1194684.0], "0x1.23abcp+20" + end + end + + context "sharp flag" do + it "prints a decimal point even if no digits follow" do + assert_sprintf "%#a", 1.0, "0x1.p+0" + assert_sprintf "%#a", Float64::MIN_POSITIVE, "0x1.p-1022" + assert_sprintf "%#a", 2.0 ** -234, "0x1.p-234" + assert_sprintf "%#a", 2.0 ** 1021, "0x1.p+1021" + assert_sprintf "%#a", 0.0, "0x0.p+0" + assert_sprintf "%#a", -0.0, "-0x0.p+0" + + assert_sprintf "%#.0a", 1.0, "0x1.p+0" + assert_sprintf "%#.0a", Float64::MIN_POSITIVE, "0x1.p-1022" + assert_sprintf "%#.0a", 2.0 ** -234, "0x1.p-234" + assert_sprintf "%#.0a", 2.0 ** 1021, "0x1.p+1021" + assert_sprintf "%#.0a", 1194684.0, "0x1.p+20" + assert_sprintf "%#.0a", 0.0, "0x0.p+0" + assert_sprintf "%#.0a", -0.0, "-0x0.p+0" + end + end + + context "plus flag" do + it "writes a plus sign for positive values" do + assert_sprintf "%+a", 1194684.0, "+0x1.23abcp+20" + assert_sprintf "%+a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+a", 0.0, "+0x0p+0" + end + + it "writes plus sign after left space-padding" do + assert_sprintf "%+20a", 1194684.0, " +0x1.23abcp+20" + assert_sprintf "%+20a", -1194684.0, " -0x1.23abcp+20" + assert_sprintf "%+20a", 0.0, " +0x0p+0" + end + + it "writes plus sign before left zero-padding" do + assert_sprintf "%+020a", 1194684.0, "+0x0000001.23abcp+20" + assert_sprintf "%+020a", -1194684.0, "-0x0000001.23abcp+20" + assert_sprintf "%+020a", 0.0, "+0x00000000000000p+0" + end + end + + context "space flag" do + it "writes a space for positive values" do + assert_sprintf "% a", 1194684.0, " 0x1.23abcp+20" + assert_sprintf "% a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "% a", 0.0, " 0x0p+0" + end + + it "writes space before left space-padding" do + assert_sprintf "% 20a", 1194684.0, " 0x1.23abcp+20" + assert_sprintf "% 20a", -1194684.0, " -0x1.23abcp+20" + assert_sprintf "% 20a", 0.0, " 0x0p+0" + + assert_sprintf "% 020a", 1194684.0, " 0x0000001.23abcp+20" + assert_sprintf "% 020a", -1194684.0, "-0x0000001.23abcp+20" + assert_sprintf "% 020a", 0.0, " 0x00000000000000p+0" + end + + it "is ignored if plus flag is also specified" do + assert_sprintf "% +a", 1194684.0, "+0x1.23abcp+20" + assert_sprintf "%+ a", -1194684.0, "-0x1.23abcp+20" + end + end + + context "zero flag" do + it "left-pads the result with zeros" do + assert_sprintf "%020a", 1194684.0, "0x00000001.23abcp+20" + assert_sprintf "%020a", -1194684.0, "-0x0000001.23abcp+20" + assert_sprintf "%020a", 0.0, "0x000000000000000p+0" + end + + it "is ignored if string is left-justified" do + assert_sprintf "%-020a", 1194684.0, "0x1.23abcp+20 " + assert_sprintf "%-020a", -1194684.0, "-0x1.23abcp+20 " + assert_sprintf "%-020a", 0.0, "0x0p+0 " + end + + it "can be used with precision" do + assert_sprintf "%020.8a", 1194684.0, "0x00001.23abc000p+20" + assert_sprintf "%020.8a", -1194684.0, "-0x0001.23abc000p+20" + assert_sprintf "%020.8a", 0.0, "0x000000.00000000p+0" + end + end + + context "minus flag" do + it "left-justifies the string" do + assert_sprintf "%-20a", 1194684.0, "0x1.23abcp+20 " + assert_sprintf "%-20a", -1194684.0, "-0x1.23abcp+20 " + assert_sprintf "%-20a", 0.0, "0x0p+0 " + end + end end [Float32, Float64].each do |float| diff --git a/src/float/printer.cr b/src/float/printer.cr index 6552f74cdb4c..8fa64dd5072c 100644 --- a/src/float/printer.cr +++ b/src/float/printer.cr @@ -90,17 +90,17 @@ module Float::Printer # Writes *v*'s hexadecimal-significand representation to the given *io*. # - # Used by `Float::Primitive#to_hexfloat`. - def hexfloat(v : Float64, io : IO) : Nil + # Used by `Float::Primitive#to_hexfloat` and `String::Formatter#float_hex`. + def hexfloat(v : Float64, io : IO, **opts) : Nil check_finite_float(v, io) do - Hexfloat(Float64, UInt64).to_s(io, v) + Hexfloat(Float64, UInt64).to_s(io, v, **opts) end end # :ditto: - def hexfloat(v : Float32, io : IO) : Nil + def hexfloat(v : Float32, io : IO, **opts) : Nil check_finite_float(v, io) do - Hexfloat(Float32, UInt32).to_s(io, v) + Hexfloat(Float32, UInt32).to_s(io, v, **opts) end end diff --git a/src/float/printer/hexfloat.cr b/src/float/printer/hexfloat.cr index 1081211141c0..f08f4e53cd94 100644 --- a/src/float/printer/hexfloat.cr +++ b/src/float/printer/hexfloat.cr @@ -220,36 +220,97 @@ module Float::Printer::Hexfloat(F, U) # sign and special values are handled in `Float::Printer.check_finite_float` @[AlwaysInline] - def self.to_s(io : IO, num : F) : Nil + def self.to_s(io : IO, num : F, *, prefix : Bool = true, upcase : Bool = false, precision : Int? = nil, alternative : Bool = false) : Nil u = num.unsafe_as(U) exponent = ((u >> (F::MANT_DIGITS - 1)) & (F::MAX_EXP * 2 - 1)).to_i mantissa = u & ~(U::MAX << (F::MANT_DIGITS - 1)) if exponent < 1 exponent += 1 - io << "0x0" else - io << "0x1" + mantissa |= U.new!(1) << (F::MANT_DIGITS - 1) end - if mantissa != 0 - io << '.' + if precision + trailing_zeros = {(precision * 4 + 1 - F::MANT_DIGITS) // 4, 0}.max + precision -= trailing_zeros + + one_bit = mantissa.bits_set?(U.new!(1) << (F::MANT_DIGITS - 4 * precision - 1)) + half_bit = mantissa.bits_set?(U.new!(1) << (F::MANT_DIGITS - 4 * precision - 2)) + trailing_nonzero = (mantissa & ~(U::MAX << (F::MANT_DIGITS - 4 * precision - 2))) != 0 + if half_bit && (one_bit || trailing_nonzero) + mantissa &+= U.new!(1) << (F::MANT_DIGITS - 4 * precision - 2) + end + else + trailing_zeros = 0 + end + + io << (upcase ? "0X" : "0x") if prefix + io << (mantissa >> (F::MANT_DIGITS - 1)) + mantissa &= ~(U::MAX << (F::MANT_DIGITS - 1)) + + io << '.' if (precision || mantissa) != 0 || alternative + + if precision + precision.times do + digit = mantissa >> (F::MANT_DIGITS - 5) + digit.to_s(io, base: 16, upcase: upcase) + mantissa <<= 4 + mantissa &= ~(U::MAX << (F::MANT_DIGITS - 1)) + end + else while mantissa != 0 digit = mantissa >> (F::MANT_DIGITS - 5) - digit.to_s(io, base: 16) + digit.to_s(io, base: 16, upcase: upcase) mantissa <<= 4 mantissa &= ~(U::MAX << (F::MANT_DIGITS - 1)) end end + trailing_zeros.times { io << '0' } + if num == 0 - io << "p+0" + io << (upcase ? "P+0" : "p+0") else exponent -= F::MAX_EXP - 1 - io << 'p' + io << (upcase ? 'P' : 'p') io << (exponent >= 0 ? '+' : '-') io << exponent.abs end end + + def self.to_s_size(num : F, *, precision : Int? = nil, alternative : Bool = false) + u = num.unsafe_as(U) + exponent = ((u >> (F::MANT_DIGITS - 1)) & (F::MAX_EXP * 2 - 1)).to_i + mantissa = u & ~(U::MAX << (F::MANT_DIGITS - 1)) + + if exponent < 1 + exponent += 1 + end + + size = 6 # 0x0p+0 (integral part cannot be greater than 2) + + if precision + size += 1 if precision != 0 || alternative # . + size += precision + else + size += 1 if mantissa != 0 || alternative # . + while mantissa != 0 + size += 1 + mantissa <<= 4 + mantissa &= ~(U::MAX << (F::MANT_DIGITS - 1)) + end + end + + if num != 0 + exponent = (exponent - F::MAX_EXP - 1).abs + while exponent >= 10 + exponent //= 10 + size += 1 + end + end + + size + end end diff --git a/src/string/formatter.cr b/src/string/formatter.cr index 4eb13ed79830..e85322f39d2e 100644 --- a/src/string/formatter.cr +++ b/src/string/formatter.cr @@ -247,7 +247,7 @@ struct String::Formatter(A) int = arg.is_a?(Int) ? arg : arg.to_i precision = int_precision(int, flags) - base_str = int.to_s(flags.base, precision: precision, upcase: flags.type == 'X') + base_str = int.to_s(flags.base, precision: precision, upcase: flags.uppercase?) str_size = base_str.bytesize str_size += 1 if int >= 0 && (flags.plus || flags.space) str_size += 2 if flags.sharp && flags.base != 10 && int != 0 @@ -334,7 +334,7 @@ struct String::Formatter(A) # Formats infinities and not-a-numbers private def float_special(str, sign, flags) - str = str.upcase if flags.type.in?('A', 'E', 'G') + str = str.upcase if flags.uppercase? str_size = str.bytesize str_size += 1 if sign < 0 || (flags.plus || flags.space) @@ -407,7 +407,7 @@ struct String::Formatter(A) e_index = printf_slice.rindex!('e'.ord) sign = Math.copysign(1.0, float) - printf_slice[e_index] = 'E'.ord.to_u8! if flags.type == 'E' + printf_slice[e_index] = 'E'.ord.to_u8! if flags.uppercase? str_size = printf_size + trailing_zeros str_size += 1 if sign < 0 || flags.plus || flags.space @@ -436,8 +436,25 @@ struct String::Formatter(A) # Formats floats with `%a` or `%A` private def float_hex(float, flags) - # TODO: implement using `Float::Printer::Hexfloat` - float_fallback(float, flags) + sign = Math.copysign(1.0, float) + float = float.abs + + str_size = Float::Printer::Hexfloat(Float64, UInt64).to_s_size(float, + precision: flags.precision, alternative: flags.sharp) + str_size += 1 if sign < 0 || flags.plus || flags.space + + pad(str_size, flags) if flags.left_padding? && flags.padding_char != '0' + + # this preserves -0.0's sign correctly + write_plus_or_space(sign, flags) + @io << '-' if sign < 0 + + @io << (flags.uppercase? ? "0X" : "0x") + pad(str_size, flags) if flags.left_padding? && flags.padding_char == '0' + Float::Printer.hexfloat(float, @io, + prefix: false, upcase: flags.uppercase?, precision: flags.precision, alternative: flags.sharp) + + pad(str_size, flags) if flags.right_padding? end {% end %} @@ -595,5 +612,9 @@ struct String::Formatter(A) def padding_char : Char @zero && !right_padding? && (@float || !@precision) ? '0' : ' ' end + + def uppercase? : Bool + @type.ascii_uppercase? + end end end From 580bf4fc8d88274c939c293585e2fa5d5992ce74 Mon Sep 17 00:00:00 2001 From: "Brian J. Cardiff" Date: Mon, 18 Dec 2023 21:23:00 -0300 Subject: [PATCH 0843/1551] Fix time span overflow on `Int#milliseconds` and `Int#microseconds` (#14105) --- spec/std/time/span_spec.cr | 16 ++++++++++++++++ src/time/span.cr | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/spec/std/time/span_spec.cr b/spec/std/time/span_spec.cr index de9bf5c63a2b..f9c1dd83f04f 100644 --- a/spec/std/time/span_spec.cr +++ b/spec/std/time/span_spec.cr @@ -359,4 +359,20 @@ describe Time::Span do 2.weeks.should eq(14.days) 1.1.weeks.should eq(7.7.days) end + + it "can substract big amount using microseconds" do + jan_1_2k = Time.utc(2000, 1, 1) + past = Time.utc(5, 2, 3, 0, 0, 0) + delta = (past - jan_1_2k).total_microseconds.to_i64 + past2 = jan_1_2k + delta.microseconds + past2.should eq(past) + end + + it "can substract big amount using milliseconds" do + jan_1_2k = Time.utc(2000, 1, 1) + past = Time.utc(5, 2, 3, 0, 0, 0) + delta = (past - jan_1_2k).total_milliseconds.to_i64 + past2 = jan_1_2k + delta.milliseconds + past2.should eq(past) + end end diff --git a/src/time/span.cr b/src/time/span.cr index 13ea2981016d..34910a7ed92e 100644 --- a/src/time/span.cr +++ b/src/time/span.cr @@ -469,7 +469,8 @@ struct Int # Returns a `Time::Span` of `self` milliseconds. def milliseconds : Time::Span - Time::Span.new(nanoseconds: (self.to_i64 * Time::NANOSECONDS_PER_MILLISECOND)) + sec, m = self.to_i64.divmod(1_000) + Time::Span.new(seconds: sec, nanoseconds: m * 1_000_000) end # :ditto: @@ -479,7 +480,8 @@ struct Int # Returns a `Time::Span` of `self` microseconds. def microseconds : Time::Span - Time::Span.new(nanoseconds: (self.to_i64 * Time::NANOSECONDS_PER_MICROSECOND)) + sec, m = self.to_i64.divmod(1_000_000) + Time::Span.new(seconds: sec, nanoseconds: m * 1_000) end # :ditto: From e0f665e4dd61c4e09ad798c33661e797db35b816 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 19 Dec 2023 19:02:39 +0800 Subject: [PATCH 0844/1551] Windows: Use local configuration for LLVM when linking dynamically (#14101) --- src/llvm/lib_llvm.cr | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index a4c55f2473a4..ee18eddfbce3 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -1,19 +1,39 @@ {% begin %} -lib LibLLVM - LLVM_CONFIG = {{ env("LLVM_CONFIG") || `#{__DIR__}/ext/find-llvm-config`.stringify }} -end -{% end %} + {% if flag?(:win32) && flag?(:preview_dll) %} + {% config = nil %} + {% for dir in Crystal::LIBRARY_PATH.split(';') %} + {% config ||= read_file?("#{dir.id}/llvm_VERSION") %} + {% end %} -{% begin %} - {% unless flag?(:win32) %} - @[Link("stdc++")] + {% unless config %} + {% raise "Cannot determine LLVM configuration; ensure the file `llvm_VERSION` exists under `CRYSTAL_LIBRARY_PATH`" %} + {% end %} + + {% lines = config.lines.map(&.chomp) %} + {% llvm_version = lines[0] %} + {% llvm_targets = lines[1] %} + {% llvm_ldflags = lines[2] %} + + @[Link("llvm")] + lib LibLLVM + end + {% else %} + {% llvm_config = env("LLVM_CONFIG") || `#{__DIR__}/ext/find-llvm-config`.stringify %} + {% llvm_version = `#{llvm_config.id} --version`.stringify %} + {% llvm_targets = env("LLVM_TARGETS") || `#{llvm_config.id} --targets-built`.stringify %} + {% llvm_ldflags = "`#{llvm_config.id} --libs --system-libs --ldflags#{" --link-static".id if flag?(:static)}#{" 2> /dev/null".id unless flag?(:win32)}`" %} + + {% unless flag?(:win32) %} + @[Link("stdc++")] + lib LibLLVM + end + {% end %} {% end %} - @[Link(ldflags: {{"`#{LibLLVM::LLVM_CONFIG} --libs --system-libs --ldflags#{" --link-static".id if flag?(:static)}#{" 2> /dev/null".id unless flag?(:win32)}`"}})] + + @[Link(ldflags: {{ llvm_ldflags }})] lib LibLLVM - VERSION = {{`#{LibLLVM::LLVM_CONFIG} --version`.chomp.stringify.gsub(/git/, "")}} - BUILT_TARGETS = {{ ( - env("LLVM_TARGETS") || `#{LibLLVM::LLVM_CONFIG} --targets-built` - ).strip.downcase.split(' ').map(&.id.symbolize) }} + VERSION = {{ llvm_version.strip.gsub(/git/, "") }} + BUILT_TARGETS = {{ llvm_targets.strip.downcase.split(' ').map(&.id.symbolize) }} end {% end %} From 667fbf1f8684aa223e0cc6be7c9061a5f5b41f32 Mon Sep 17 00:00:00 2001 From: V Date: Tue, 19 Dec 2023 17:11:27 +0100 Subject: [PATCH 0845/1551] Fix `StaticArray#to_json` (#14104) --- spec/std/json/serialization_spec.cr | 4 ++++ src/json/to_json.cr | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/spec/std/json/serialization_spec.cr b/spec/std/json/serialization_spec.cr index 7e320b9f81cd..80fc83e13b7e 100644 --- a/spec/std/json/serialization_spec.cr +++ b/spec/std/json/serialization_spec.cr @@ -587,6 +587,10 @@ describe "JSON serialization" do [1, 2, 3].to_json.should eq("[1,2,3]") end + it "does for StaticArray" do + StaticArray[1, 2, 3].to_json.should eq("[1,2,3]") + end + it "does for Deque" do Deque.new([1, 2, 3]).to_json.should eq("[1,2,3]") end diff --git a/src/json/to_json.cr b/src/json/to_json.cr index 8a0ee613d178..7e52b5663786 100644 --- a/src/json/to_json.cr +++ b/src/json/to_json.cr @@ -98,6 +98,14 @@ class Array end end +struct StaticArray + def to_json(json : JSON::Builder) : Nil + json.array do + each &.to_json(json) + end + end +end + class Deque def to_json(json : JSON::Builder) : Nil json.array do From 60e83578a063923814ee5d7aa893b67df7f1e69c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 20 Dec 2023 23:32:30 +0800 Subject: [PATCH 0846/1551] Never use string interpolation in `Crystal::System.print_error` (#14114) --- src/crystal/scheduler.cr | 6 +++--- src/exception/call_stack/interpreter.cr | 2 +- src/raise.cr | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index 588a9b595962..0a3585fd29ff 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -135,8 +135,8 @@ class Crystal::Scheduler end private def fatal_resume_error(fiber, message) - Crystal::System.print_error "\nFATAL: #{message}: #{fiber}\n" - caller.each { |line| Crystal::System.print_error " from #{line}\n" } + Crystal::System.print_error "\nFATAL: %s: %s\n", message, fiber.to_s + caller.each { |line| Crystal::System.print_error " from %s\n", line } exit 1 end @@ -259,7 +259,7 @@ class Crystal::Scheduler if env_workers && !env_workers.empty? workers = env_workers.to_i? if !workers || workers < 1 - Crystal::System.print_error "FATAL: Invalid value for CRYSTAL_WORKERS: #{env_workers}\n" + Crystal::System.print_error "FATAL: Invalid value for CRYSTAL_WORKERS: %s\n", env_workers exit 1 end diff --git a/src/exception/call_stack/interpreter.cr b/src/exception/call_stack/interpreter.cr index 48db614f5570..db24017ce30c 100644 --- a/src/exception/call_stack/interpreter.cr +++ b/src/exception/call_stack/interpreter.cr @@ -27,7 +27,7 @@ struct Exception::CallStack def self.print_backtrace : Nil unwind.each do |frame| - Crystal::System.print_error frame.unsafe_as(String) + Crystal::System.print_error "%s\n", frame.unsafe_as(String) end end end diff --git a/src/raise.cr b/src/raise.cr index bf15257ddaf1..e870f23e725e 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -45,7 +45,7 @@ private def traverse_eh_table(leb, start, ip, actions, &) lp_start_encoding = leb.read_uint8 # @LPStart encoding if lp_start_encoding != 0xff_u8 - Crystal::System.print_error "Unexpected encoding for LPStart: #{lp_start_encoding}\n" + Crystal::System.print_error "Unexpected encoding for LPStart: 0x%x\n", lp_start_encoding LibC.exit 1 end @@ -55,7 +55,7 @@ private def traverse_eh_table(leb, start, ip, actions, &) cs_encoding = leb.read_uint8 # CS Encoding (1: uleb128, 3: uint32) if cs_encoding != 1 && cs_encoding != 3 - Crystal::System.print_error "Unexpected CS encoding: #{cs_encoding}\n" + Crystal::System.print_error "Unexpected CS encoding: 0x%x\n", cs_encoding LibC.exit 1 end @@ -218,7 +218,7 @@ end {% if flag?(:wasm32) %} def raise(exception : Exception) : NoReturn - Crystal::System.print_error "EXITING: Attempting to raise:\n#{exception.inspect_with_backtrace}" + Crystal::System.print_error "EXITING: Attempting to raise:\n%s\n", exception.inspect_with_backtrace LibIntrinsics.debugtrap LibC.exit(1) end From f1f78480ea93adf8b87d8e9efa4c670a0a6b9be4 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 20 Dec 2023 16:33:38 +0100 Subject: [PATCH 0847/1551] Move `Thread#set_current_thread` to `Fiber` (#14099) --- src/concurrent.cr | 6 ++---- src/crystal/scheduler.cr | 27 +++++++++++++++++++++------ src/crystal/system/thread.cr | 2 +- src/fiber.cr | 16 ++++++++++++++-- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/concurrent.cr b/src/concurrent.cr index 5b2747d0c581..af2f0aecf736 100644 --- a/src/concurrent.cr +++ b/src/concurrent.cr @@ -59,10 +59,8 @@ end # ``` def spawn(*, name : String? = nil, same_thread = false, &block) fiber = Fiber.new(name, &block) - if same_thread - fiber.@current_thread.set(Thread.current) - end - Crystal::Scheduler.enqueue fiber + {% if flag?(:preview_mt) %} fiber.set_current_thread if same_thread {% end %} + fiber.enqueue fiber end diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index 0a3585fd29ff..656545d05e4b 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -51,6 +51,7 @@ class Crystal::Scheduler end def self.resume(fiber : Fiber) : Nil + validate_running_thread(fiber) Thread.current.scheduler.resume(fiber) end @@ -63,9 +64,22 @@ class Crystal::Scheduler end def self.yield(fiber : Fiber) : Nil + validate_running_thread(fiber) Thread.current.scheduler.yield(fiber) end + private def self.validate_running_thread(fiber : Fiber) : Nil + {% if flag?(:preview_mt) %} + if th = fiber.get_current_thread + unless th == Thread.current + raise "BUG: tried to manually resume #{fiber} on #{Thread.current} instead of #{th}" + end + else + fiber.set_current_thread + end + {% end %} + end + {% if flag?(:preview_mt) %} def self.enqueue_free_stack(stack : Void*) : Nil Thread.current.scheduler.enqueue_free_stack(stack) @@ -76,11 +90,15 @@ class Crystal::Scheduler private getter(fiber_channel : Crystal::FiberChannel) { Crystal::FiberChannel.new } @free_stacks = Deque(Void*).new {% end %} + + @main : Fiber @lock = Crystal::SpinLock.new @sleeping = false # :nodoc: - def initialize(@main : Fiber) + def initialize(thread : Thread) + @main = thread.main_fiber + {% if flag?(:preview_mt) %} @main.set_current_thread(thread) {% end %} @current = @main @runnables = Deque(Fiber).new end @@ -95,8 +113,8 @@ class Crystal::Scheduler protected def resume(fiber : Fiber) : Nil validate_resumable(fiber) + {% if flag?(:preview_mt) %} - set_current_thread(fiber) GC.lock_read {% elsif flag?(:interpreted) %} # No need to change the stack bottom! @@ -130,10 +148,6 @@ class Crystal::Scheduler end end - private def set_current_thread(fiber) - fiber.@current_thread.set(Thread.current) - end - private def fatal_resume_error(fiber, message) Crystal::System.print_error "\nFATAL: %s: %s\n", message, fiber.to_s caller.each { |line| Crystal::System.print_error " from %s\n", line } @@ -236,6 +250,7 @@ class Crystal::Scheduler @@workers = Array(Thread).new(count) do |i| if i == 0 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 else diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index 878be639a7a3..88784ed68330 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -105,7 +105,7 @@ class Thread end # :nodoc: - getter scheduler : Crystal::Scheduler { Crystal::Scheduler.new(main_fiber) } + getter scheduler : Crystal::Scheduler { Crystal::Scheduler.new(self) } protected def start Thread.threads.push(self) diff --git a/src/fiber.cr b/src/fiber.cr index f89489c2bd13..aa2af7bf2229 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -62,7 +62,7 @@ class Fiber property name : String? @alive = true - @current_thread = Atomic(Thread?).new(nil) + {% if flag?(:preview_mt) %} @current_thread = Atomic(Thread?).new(nil) {% end %} # :nodoc: property next : Fiber? @@ -136,7 +136,7 @@ class Fiber {% end %} thread.gc_thread_handler, @stack_bottom = GC.current_thread_stack_bottom @name = "main" - @current_thread.set(thread) + {% if flag?(:preview_mt) %} @current_thread.set(thread) {% end %} Fiber.fibers.push(self) end @@ -305,4 +305,16 @@ class Fiber # Push the used section of the stack GC.push_stack @context.stack_top, @stack_bottom end + + {% if flag?(:preview_mt) %} + # :nodoc: + def set_current_thread(thread = Thread.current) : Thread + @current_thread.set(thread) + end + + # :nodoc: + def get_current_thread : Thread? + @current_thread.lazy_get + end + {% end %} end From ad75e49767be7e62d9c75ef8e3a0bec4fa88e099 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 20 Dec 2023 16:33:57 +0100 Subject: [PATCH 0848/1551] Skip indirections in `Crystal::Scheduler` (#14098) --- src/crystal/scheduler.cr | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index 656545d05e4b..a1f3e28d30a7 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -23,20 +23,19 @@ class Crystal::Scheduler end def self.enqueue(fiber : Fiber) : Nil - {% if flag?(:preview_mt) %} - th = fiber.@current_thread.lazy_get + thread = Thread.current + scheduler = thread.scheduler - if th.nil? - th = Thread.current.scheduler.find_target_thread - end + {% if flag?(:preview_mt) %} + th = fiber.@current_thread.lazy_get || scheduler.find_target_thread - if th == Thread.current - Thread.current.scheduler.enqueue(fiber) + if th == thread + scheduler.enqueue(fiber) else th.scheduler.send_fiber(fiber) end {% else %} - Thread.current.scheduler.enqueue(fiber) + scheduler.enqueue(fiber) {% end %} end @@ -60,7 +59,10 @@ class Crystal::Scheduler end def self.yield : Nil - Thread.current.scheduler.yield + # TODO: Fiber switching and libevent for wasm32 + {% unless flag?(:wasm32) %} + Thread.current.scheduler.sleep(0.seconds) + {% end %} end def self.yield(fiber : Fiber) : Nil @@ -169,9 +171,7 @@ class Crystal::Scheduler protected def reschedule : Nil loop do if runnable = @lock.sync { @runnables.shift? } - unless runnable == @current - runnable.resume - end + resume(runnable) unless runnable == @current break else @event_loop.run_once @@ -188,13 +188,6 @@ class Crystal::Scheduler reschedule end - protected def yield : Nil - # TODO: Fiber switching and libevent for wasm32 - {% unless flag?(:wasm32) %} - sleep(0.seconds) - {% end %} - end - protected def yield(fiber : Fiber) : Nil @current.resume_event.add(0.seconds) resume(fiber) @@ -216,10 +209,11 @@ class Crystal::Scheduler fiber_channel = self.fiber_channel loop do @lock.lock + if runnable = @runnables.shift? @runnables << Fiber.current @lock.unlock - runnable.resume + resume(runnable) else @sleeping = true @lock.unlock @@ -229,7 +223,7 @@ class Crystal::Scheduler @sleeping = false @runnables << Fiber.current @lock.unlock - fiber.resume + resume(fiber) end end end From b0837236045b1c4ccae98867f954015c002e8aab Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 21 Dec 2023 03:56:52 +0800 Subject: [PATCH 0849/1551] Distribute LLVM DLLs on Windows CI (#14110) --- .github/workflows/win.yml | 40 ++++++++++++++++++---- .github/workflows/win_build_portable.yml | 9 +++++ etc/win-ci/build-llvm.ps1 | 42 ++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 etc/win-ci/build-llvm.ps1 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index bf8687d32c1e..90ed0e0c980d 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -164,21 +164,21 @@ jobs: if: steps.cache-openssl-dlls.outputs.cache-hit != 'true' run: .\etc\win-ci\build-openssl.ps1 -BuildTree deps\openssl -Version 3.1.0 -Dynamic - x86_64-windows-llvm: + x86_64-windows-llvm-libs: runs-on: windows-2022 steps: - name: Enable Developer Command Prompt uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 - name: Cache LLVM - id: cache-llvm + id: cache-llvm-libs uses: actions/cache@v3 with: path: llvm key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc - name: Download LLVM - if: steps.cache-llvm.outputs.cache-hit != 'true' + if: steps.cache-llvm-libs.outputs.cache-hit != 'true' run: | iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.CI_LLVM_VERSION }}/llvm-${{ env.CI_LLVM_VERSION }}.src.tar.xz -OutFile llvm.tar.xz (Get-FileHash -Algorithm SHA256 .\llvm.tar.xz).hash -eq "D820E63BC3A6F4F833EC69A1EF49A2E81992E90BC23989F98946914B061AB6C7" @@ -187,7 +187,7 @@ jobs: mv llvm-* llvm-src - name: Download LLVM's CMake files - if: steps.cache-llvm.outputs.cache-hit != 'true' + if: steps.cache-llvm-libs.outputs.cache-hit != 'true' run: | iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.CI_LLVM_VERSION }}/cmake-${{ env.CI_LLVM_VERSION }}.src.tar.xz -OutFile cmake.tar.xz (Get-FileHash -Algorithm SHA256 .\cmake.tar.xz).hash -eq "B6D83C91F12757030D8361DEDC5DD84357B3EDB8DA406B5D0850DF8B6F7798B1" @@ -196,7 +196,7 @@ jobs: mv cmake-* cmake - name: Build LLVM - if: steps.cache-llvm.outputs.cache-hit != 'true' + if: steps.cache-llvm-libs.outputs.cache-hit != 'true' run: | mkdir llvm-build cd llvm-build @@ -204,8 +204,34 @@ jobs: cmake --build . --config Release cmake "-DCMAKE_INSTALL_PREFIX=$(pwd)\..\llvm" -P cmake_install.cmake + x86_64-windows-llvm-dlls: + runs-on: windows-2022 + steps: + - name: Disable CRLF line ending substitution + run: | + git config --global core.autocrlf false + + - name: Enable Developer Command Prompt + uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + + - name: Download Crystal source + uses: actions/checkout@v4 + + - name: Cache LLVM + id: cache-llvm-dlls + uses: actions/cache@v3 + with: + path: | + libs/llvm_VERSION + libs/llvm-dynamic.lib + dlls/LLVM-C.dll + key: llvm-dlls-${{ env.CI_LLVM_VERSION }}-${{ hashFiles('etc/win-ci/build-llvm.ps1') }}-msvc + - name: Build LLVM + if: steps.cache-llvm-dlls.outputs.cache-hit != 'true' + run: .\etc\win-ci\build-llvm.ps1 -BuildTree deps\llvm -Version ${{ env.CI_LLVM_VERSION }} -TargetsToBuild X86,AArch64 -Dynamic + x86_64-windows: - needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm] + needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm-libs, x86_64-windows-llvm-dlls] uses: ./.github/workflows/win_build_portable.yml with: release: false @@ -258,7 +284,7 @@ jobs: x86_64-windows-release: if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/')) - needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm] + needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm-libs, x86_64-windows-llvm-dlls] uses: ./.github/workflows/win_build_portable.yml with: release: true diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index f5b0ad542335..18eea4f1f317 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -93,6 +93,15 @@ jobs: path: llvm key: llvm-libs-${{ inputs.llvm_version }}-msvc fail-on-cache-miss: true + - name: Restore LLVM DLLs + uses: actions/cache/restore@v3 + with: + path: | + libs/llvm_VERSION + libs/llvm-dynamic.lib + dlls/LLVM-C.dll + key: llvm-dlls-${{ inputs.llvm_version }}-${{ hashFiles('etc/win-ci/build-llvm.ps1') }}-msvc + fail-on-cache-miss: true - name: Set up environment run: | diff --git a/etc/win-ci/build-llvm.ps1 b/etc/win-ci/build-llvm.ps1 new file mode 100644 index 000000000000..b544e9eba784 --- /dev/null +++ b/etc/win-ci/build-llvm.ps1 @@ -0,0 +1,42 @@ +param( + [Parameter(Mandatory)] [string] $BuildTree, + [Parameter(Mandatory)] [string] $Version, + [Parameter(Mandatory)] [string[]] $TargetsToBuild, + [switch] $Dynamic +) + +if (-not $Dynamic) { + Write-Host "Error: Building LLVM as a static library is not supported yet" -ForegroundColor Red + Exit 1 +} + +. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" + +[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) +Setup-Git -Path $BuildTree -Url https://github.com/llvm/llvm-project.git -Ref llvmorg-$Version + +Run-InDirectory $BuildTree\build { + $args = "-Thost=x64 -DLLVM_TARGETS_TO_BUILD=""$($TargetsToBuild -join ';')"" -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_ZSTD=OFF" + if ($Dynamic) { + $args = "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL $args" + } else { + $args = "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DLLVM_BUILD_LLVM_C_DYLIB=OFF $args" + } + & $cmake ..\llvm $args.split(' ') + & $cmake --build . --config Release --target llvm-config --target LLVM-C + if (-not $?) { + Write-Host "Error: Failed to build LLVM" -ForegroundColor Red + Exit 1 + } +} + +if ($Dynamic) { + mv -Force $BuildTree\build\Release\lib\LLVM-C.lib libs\llvm-dynamic.lib + mv -Force $BuildTree\build\Release\bin\LLVM-C.dll dlls\ +} else { + # TODO (probably never) +} + +Add-Content libs\llvm_VERSION $(& "$BuildTree\build\Release\bin\llvm-config.exe" --version) +Add-Content libs\llvm_VERSION $(& "$BuildTree\build\Release\bin\llvm-config.exe" --targets-built) +Add-Content libs\llvm_VERSION $(& "$BuildTree\build\Release\bin\llvm-config.exe" --system-libs) From 57d313b198b865a375f169923c8c80afbbd385bf Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 21 Dec 2023 03:56:59 +0800 Subject: [PATCH 0850/1551] Add `scripts/generate_llvm_version_info.cr` (#14112) --- scripts/generate_llvm_version_info.cr | 82 +++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100755 scripts/generate_llvm_version_info.cr diff --git a/scripts/generate_llvm_version_info.cr b/scripts/generate_llvm_version_info.cr new file mode 100755 index 000000000000..55f104193092 --- /dev/null +++ b/scripts/generate_llvm_version_info.cr @@ -0,0 +1,82 @@ +#! /usr/bin/env crystal +# +# This script generates the `lib/llvm_VERSION` file from LLVM-C.dll, needed for +# dynamically linking against LLVM on Windows. This is only needed when using an +# LLVM installation different from the one bundled with Crystal. + +require "c/libloaderapi" + +# The list of supported targets are hardcoded in: +# https://github.com/llvm/llvm-project/blob/main/llvm/CMakeLists.txt +LLVM_ALL_TARGETS = %w( + AArch64 + AMDGPU + ARM + AVR + BPF + Hexagon + Lanai + LoongArch + Mips + MSP430 + NVPTX + PowerPC + RISCV + Sparc + SystemZ + VE + WebAssembly + X86 + XCore + ARC + CSKY + DirectX + M68k + SPIRV + Xtensa +) + +def find_dll_in_env_path + ENV["PATH"]?.try &.split(Process::PATH_DELIMITER, remove_empty: true) do |path| + dll_path = File.join(path, "LLVM-C.dll") + return dll_path if File.exists?(File.join(path, "LLVM-C.dll")) + end +end + +unless dll_fname = ARGV.shift? || find_dll_in_env_path + abort "Error: Cannot locate LLVM-C.dll, pass its absolute path as a command-line argument or ensure it is available in the PATH environment variable" +end + +unless dll = LibC.LoadLibraryExW(dll_fname.check_no_null_byte.to_utf16, nil, 0) + abort "Error: Failed to load DLL at #{dll_fname}" +end + +begin + unless llvm_get_version = LibC.GetProcAddress(dll, "LLVMGetVersion") + abort "Error: Failed to resolve LLVMGetVersion" + end + + llvm_get_version = Proc(LibC::UInt*, LibC::UInt*, LibC::UInt*, Nil).new(llvm_get_version, Pointer(Void).null) + major = uninitialized LibC::UInt + minor = uninitialized LibC::UInt + patch = uninitialized LibC::UInt + llvm_get_version.call(pointerof(major), pointerof(minor), pointerof(patch)) + + targets_built = LLVM_ALL_TARGETS.select do |target| + LibC.GetProcAddress(dll, "LLVMInitialize#{target}Target") && LibC.GetProcAddress(dll, "LLVMInitialize#{target}TargetInfo") + end + + # The list of required system libraries are hardcoded in: + # https://github.com/llvm/llvm-project/blob/main/llvm/lib/Support/CMakeLists.txt + # There is no way to infer them from `dumpbin /dependents` alone, because that + # command lists DLLs only, whereas some of these libraries are purely static. + system_libs = %w(psapi shell32 ole32 uuid advapi32) + # https://github.com/llvm/llvm-project/commit/a5ffabce98a4b2e9d69009fa3e60f2b154100860 + system_libs << "ws2_32" if {major, minor, patch} >= {18, 0, 0} + + puts "#{major}.#{minor}.#{patch}" + puts targets_built.join(' ') + puts system_libs.join(' ', &.+ ".lib") +ensure + LibC.FreeLibrary(dll) +end From b41fda9081f6118fca23f9623010c0bd28361114 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 21 Dec 2023 03:57:10 +0800 Subject: [PATCH 0851/1551] Add `Reference.pre_initialize` and `.unsafe_construct` (#14108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/compiler/semantic/primitives_spec.cr | 62 ++++++++++++++ spec/primitives/reference_spec.cr | 85 +++++++++++++++++++ src/compiler/crystal/codegen/codegen.cr | 12 ++- src/compiler/crystal/codegen/primitives.cr | 13 +++ .../crystal/semantic/cleanup_transformer.cr | 2 +- src/compiler/crystal/semantic/main_visitor.cr | 38 +++++++++ src/primitives.cr | 55 ++++++++++++ src/reference.cr | 43 ++++++++++ 8 files changed, 305 insertions(+), 5 deletions(-) create mode 100644 spec/primitives/reference_spec.cr diff --git a/spec/compiler/semantic/primitives_spec.cr b/spec/compiler/semantic/primitives_spec.cr index b806361dc01d..048c282606d6 100644 --- a/spec/compiler/semantic/primitives_spec.cr +++ b/spec/compiler/semantic/primitives_spec.cr @@ -331,4 +331,66 @@ describe "Semantic: primitives" do end end end + + describe "Reference.pre_initialize" do + def_reference_pre_initialize = <<-CRYSTAL + class Reference + @[Primitive(:pre_initialize)] + def self.pre_initialize(address : Pointer) + {% @type %} + end + end + CRYSTAL + + it "types with reference type" do + assert_type(<<-CRYSTAL) { types["Foo"] } + #{def_reference_pre_initialize} + + class Foo + end + + x = 1 + Foo.pre_initialize(pointerof(x)) + CRYSTAL + end + + it "types with virtual reference type" do + assert_type(<<-CRYSTAL) { types["Foo"].virtual_type! } + #{def_reference_pre_initialize} + + class Foo + end + + class Bar < Foo + end + + x = 1 + Bar.as(Foo.class).pre_initialize(pointerof(x)) + CRYSTAL + end + + it "errors on uninstantiated generic type" do + assert_error <<-CRYSTAL, "Can't pre-initialize instance of generic class Foo(T) without specifying its type vars" + #{def_reference_pre_initialize} + + class Foo(T) + end + + x = 1 + Foo.pre_initialize(pointerof(x)) + CRYSTAL + end + + it "errors on abstract type" do + assert_error <<-CRYSTAL, "Can't pre-initialize abstract class Foo" + #{def_reference_pre_initialize} + + abstract class Foo + end + + x = 1 + Foo.pre_initialize(pointerof(x)) + CRYSTAL + end + end end diff --git a/spec/primitives/reference_spec.cr b/spec/primitives/reference_spec.cr new file mode 100644 index 000000000000..b73553748b65 --- /dev/null +++ b/spec/primitives/reference_spec.cr @@ -0,0 +1,85 @@ +require "spec" + +private abstract class Base +end + +private class Foo < Base + getter i : Int64 + getter str = "abc" + + def initialize(@i) + end + + def initialize(@str, @i) + end +end + +private class Bar < Base + getter x : UInt8[128] + + def initialize(@x) + end +end + +describe "Primitives: reference" do + describe ".pre_initialize" do + it "zeroes the instance data" do + bar_buffer = GC.malloc(instance_sizeof(Bar)) + Slice.new(bar_buffer.as(UInt8*), instance_sizeof(Bar)).fill(0xFF) + bar = Bar.pre_initialize(bar_buffer) + bar.x.all?(&.zero?).should be_true + end + + it "sets type ID" do + foo_buffer = GC.malloc(instance_sizeof(Foo)) + base = Foo.pre_initialize(foo_buffer).as(Base) + base.crystal_type_id.should eq(Foo.crystal_instance_type_id) + end + + it "runs inline instance initializers" do + foo_buffer = GC.malloc(instance_sizeof(Foo)) + foo = Foo.pre_initialize(foo_buffer) + foo.str.should eq("abc") + end + + it "works when address is on the stack" do + # suitably aligned, sufficient storage for type ID + i64 + ptr + # TODO: use `ReferenceStorage` instead + foo_buffer = uninitialized UInt64[3] + foo = Foo.pre_initialize(pointerof(foo_buffer)) + pointerof(foo_buffer).as(typeof(Foo.crystal_instance_type_id)*).value.should eq(Foo.crystal_instance_type_id) + foo.str.should eq("abc") + end + + # see notes in `Reference.pre_initialize` + {% if compare_versions(Crystal::VERSION, "1.2.0") >= 0 %} + it "works with virtual type" do + foo_buffer = GC.malloc(instance_sizeof(Foo)) + foo = Foo.as(Base.class).pre_initialize(foo_buffer).should be_a(Foo) + foo.str.should eq("abc") + end + {% else %} + pending! "works with virtual type" + {% end %} + + it "raises on abstract virtual type" do + expect_raises(Exception, "Can't pre-initialize abstract class Base") do + Base.as(Base.class).pre_initialize(Pointer(Void).null) + end + end + end + + describe ".unsafe_construct" do + it "constructs an object in-place" do + foo_buffer = GC.malloc(instance_sizeof(Foo)) + foo = Foo.unsafe_construct(foo_buffer, 123_i64) + foo.i.should eq(123) + foo.str.should eq("abc") + + str = String.build &.<< "def" + foo = Foo.unsafe_construct(foo_buffer, str, 789_i64) + foo.i.should eq(789) + foo.str.should be(str) + end + end +end diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index d41b71edd042..7cec0d3c2c44 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -2032,15 +2032,19 @@ module Crystal end end - memset type_ptr, int8(0), struct_type.size - run_instance_vars_initializers(type, type, type_ptr) + pre_initialize_aggregate(type, struct_type, type_ptr) + end + + def pre_initialize_aggregate(type, struct_type, ptr) + memset ptr, int8(0), struct_type.size + run_instance_vars_initializers(type, type, ptr) unless type.struct? - type_id_ptr = aggregate_index(struct_type, type_ptr, 0) + type_id_ptr = aggregate_index(struct_type, ptr, 0) store type_id(type), type_id_ptr end - @last = type_ptr + @last = ptr end def allocate_tuple(type, &) diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index 23f4f6c2a58a..9ed05c0009f2 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -25,6 +25,8 @@ class Crystal::CodeGenVisitor codegen_primitive_convert node, target_def, call_args, checked: false when "allocate" codegen_primitive_allocate node, target_def, call_args + when "pre_initialize" + codegen_primitive_pre_initialize node, target_def, call_args when "pointer_malloc" codegen_primitive_pointer_malloc node, target_def, call_args when "pointer_set" @@ -740,6 +742,17 @@ class Crystal::CodeGenVisitor @last end + def codegen_primitive_pre_initialize(node, target_def, call_args) + type = node.type + + base_type = type.is_a?(VirtualType) ? type.base_type : type + + ptr = call_args[target_def.owner.passed_as_self? ? 1 : 0] + pre_initialize_aggregate base_type, llvm_struct_type(base_type), ptr + + @last = cast_to ptr, type + end + def codegen_primitive_pointer_malloc(node, target_def, call_args) type = node.type.as(PointerInstanceType) llvm_type = llvm_embedded_type(type.element_type) diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 639de078fcd6..36e8a1b0c8bc 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -1052,7 +1052,7 @@ module Crystal # For `allocate` on a virtual abstract type we make `extra` # be a call to `raise` at runtime. Here we just replace the # "allocate" primitive with that raise call. - if node.name == "allocate" && extra + if node.name.in?("allocate", "pre_initialize") && extra return extra end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 712d9e8d72cf..717bcd24e595 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -2292,6 +2292,8 @@ module Crystal case node.name when "allocate" visit_allocate node + when "pre_initialize" + visit_pre_initialize node when "pointer_malloc" visit_pointer_malloc node when "pointer_set" @@ -2388,6 +2390,42 @@ module Crystal end end + def visit_pre_initialize(node) + instance_type = scope.instance_type + + case instance_type + when GenericClassType + node.raise "Can't pre-initialize instance of generic class #{instance_type} without specifying its type vars" + when UnionType + node.raise "Can't pre-initialize instance of a union type" + else + if instance_type.abstract? + if instance_type.virtual? + # This is the same as `.initialize` + base_type = instance_type.devirtualize + + extra = Call.new( + nil, + "raise", + StringLiteral.new("Can't pre-initialize abstract class #{base_type}"), + global: true).at(node) + extra.accept self + + # This `extra` will replace the Primitive node in CleanupTransformer later on. + node.extra = extra + node.type = @program.no_return + return + else + # If the type is not virtual then we know for sure that the type + # can't be instantiated, and we can produce a compile-time error. + node.raise "Can't pre-initialize abstract class #{instance_type}" + end + end + + node.type = instance_type + end + end + def visit_pointer_malloc(node) if scope.instance_type.is_a?(GenericClassType) node.raise "can't malloc pointer without type, use Pointer(Type).malloc(size)" diff --git a/src/primitives.cr b/src/primitives.cr index e01be8884dbb..7cb478ebf0cf 100644 --- a/src/primitives.cr +++ b/src/primitives.cr @@ -49,6 +49,61 @@ class Reference @[Primitive(:object_id)] def object_id : UInt64 end + + # Performs basic initialization so that the given *address* is ready for use + # as an object's instance data. Returns *address* cast to `self`'s type. + # + # More specifically, this is the part of object initialization that occurs + # after memory allocation and before calling one of the `#initialize` + # overloads. It zeroes the memory, sets up the type ID (necessary for dynamic + # dispatch), and then runs all inline instance variable initializers. + # + # *address* must point to a suitably aligned buffer of at least + # `instance_sizeof(self)` bytes. + # + # WARNING: This method is unsafe, as it assumes the caller is responsible for + # managing the memory at the given *address* manually. + # + # ``` + # class Foo + # getter i : Int64 + # getter str = "abc" + # + # def initialize(@i) + # end + # + # def self.alloc_with_libc(i : Int64) + # foo_buffer = LibC.malloc(instance_sizeof(Foo)) + # foo = Foo.pre_initialize(foo_buffer) + # foo.i # => 0 + # foo.str # => "abc" + # (foo || "").is_a?(Foo) # => true + # + # foo.initialize(i) # okay + # foo + # end + # end + # + # foo = Foo.alloc_with_libc(123_i64) + # foo.i # => 123 + # ``` + # + # See also: `Reference.unsafe_construct`. + @[Experimental("This API is still under development. Join the discussion about custom reference allocation at [#13481](https://github.com/crystal-lang/crystal/issues/13481).")] + @[Primitive(:pre_initialize)] + {% if compare_versions(Crystal::VERSION, "1.2.0") >= 0 %} + def self.pre_initialize(address : Pointer) + # This ensures `.pre_initialize` is instantiated for every subclass, + # otherwise all calls will refer to the sole instantiation in + # `Reference.class`. This is necessary when the receiver is a virtual + # metaclass type. Apparently this works even for primitives + \{% @type %} + end + {% else %} + # Primitives cannot have a body until 1.2.0 (#11147) + def self.pre_initialize(address : Pointer) + end + {% end %} end class Class diff --git a/src/reference.cr b/src/reference.cr index 9a032500a6e5..f70697282fa0 100644 --- a/src/reference.cr +++ b/src/reference.cr @@ -17,6 +17,49 @@ # The instance's memory is automatically freed (garbage-collected) when # the instance is no longer referred by any other entity in the program. class Reference + # Constructs an object in-place at the given *address*, forwarding *args* and + # *opts* to `#initialize`. Returns that object. + # + # This method can be used to decouple object allocation from initialization. + # For example, the instance data might come from a custom allocator, or it + # might reside on the stack using a type like `ReferenceStorage`. + # + # *address* must point to a suitably aligned buffer of at least + # `instance_sizeof(self)` bytes. + # + # WARNING: This method is unsafe, as it assumes the caller is responsible for + # managing the memory at the given *address* manually. + # + # ``` + # class Foo + # getter i : Int64 + # getter str = "abc" + # + # def initialize(@i) + # end + # + # def finalize + # puts "bye" + # end + # end + # + # foo_buffer = uninitialized ReferenceStorage(Foo) + # foo = Foo.unsafe_construct(pointerof(foo_buffer), 123_i64) + # begin + # foo # => # + # ensure + # foo.finalize if foo.responds_to?(:finalize) # prints "bye" + # end + # ``` + # + # See also: `Reference.pre_initialize`. + @[Experimental("This API is still under development. Join the discussion about custom reference allocation at [#13481](https://github.com/crystal-lang/crystal/issues/13481).")] + def self.unsafe_construct(address : Pointer, *args, **opts) : self + obj = pre_initialize(address) + obj.initialize(*args, **opts) + obj + end + # Returns `true` if this reference is the same as *other*. Invokes `same?`. def ==(other : self) same?(other) From a155936bff94539cab5a3dd57f043f3d7bdb7cfa Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Thu, 21 Dec 2023 13:57:58 +0100 Subject: [PATCH 0852/1551] Fix: can't resume a running fiber (#14128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/crystal/scheduler.cr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index a1f3e28d30a7..ce06e3c60275 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -27,7 +27,8 @@ class Crystal::Scheduler scheduler = thread.scheduler {% if flag?(:preview_mt) %} - th = fiber.@current_thread.lazy_get || scheduler.find_target_thread + th = fiber.get_current_thread + th ||= fiber.set_current_thread(scheduler.find_target_thread) if th == thread scheduler.enqueue(fiber) From 4fd55fb078c6b775b2808fba5847b493f00fa76d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 21 Dec 2023 20:58:18 +0800 Subject: [PATCH 0853/1551] `Crystal::Loader`: Skip second linker member on Windows if absent (#14111) --- src/crystal/system/win32/library_archive.cr | 29 ++++++++++----------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/crystal/system/win32/library_archive.cr b/src/crystal/system/win32/library_archive.cr index e1487fb907d9..775677938bac 100644 --- a/src/crystal/system/win32/library_archive.cr +++ b/src/crystal/system/win32/library_archive.cr @@ -29,19 +29,20 @@ module Crystal::System::LibraryArchive # magic number return unless @ar.read_string(8) == "!\n" - # first linker member - return unless read_member { |filename, _| return unless filename == "/" } - - # second linker member - return unless read_member { |filename, _| return unless filename == "/" } - - # longnames member (optional) - return if @ar.pos == file_size - return unless read_member { |filename, io| handle_standard_member(io) unless filename == "//" } - - # standard members + # first linker member's filename is `/` + # second linker member's filename is also `/` (apparently not all linkers generate this?) + # longnames member's filename is `//` (optional) + # the rest are standard members + first = true until @ar.pos == file_size - return unless read_member { |_, io| handle_standard_member(io) } + read_member do |filename, io| + if first + first = false + return unless filename == "/" + elsif !filename.in?("/", "//") + handle_standard_member(io) + end + end end end @@ -54,13 +55,11 @@ module Crystal::System::LibraryArchive size = @ar.read_string(10).rstrip(' ').to_u32 # end of header - return false unless @ar.read_string(2) == "`\n" + return unless @ar.read_string(2) == "`\n" new_pos = @ar.pos + size + (size.odd? ? 1 : 0) yield filename, IO::Sized.new(@ar, read_size: size) @ar.seek(new_pos) - - true end private def handle_standard_member(io) From 3885078aac52c0be8dc11229d3acbb5f7f9e8402 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Thu, 21 Dec 2023 09:00:17 -0500 Subject: [PATCH 0854/1551] Expose macro `Call` context via new `@caller` macro ivar (#14048) --- spec/compiler/semantic/macro_spec.cr | 32 ++++++++++++++++++++++ src/compiler/crystal/macros/interpreter.cr | 10 +++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/spec/compiler/semantic/macro_spec.cr b/spec/compiler/semantic/macro_spec.cr index 79e7856adfae..6a6a2f01ce17 100644 --- a/spec/compiler/semantic/macro_spec.cr +++ b/spec/compiler/semantic/macro_spec.cr @@ -1739,4 +1739,36 @@ describe "Semantic: macro" do {% end %} CRYSTAL end + + describe "@caller" do + it "returns an array of each call" do + assert_type(<<-CRYSTAL) { int32 } + macro test + {{@caller.size == 1 ? 1 : 'f'}} + end + + test + CRYSTAL + end + + it "provides access to the `Call` information" do + assert_type(<<-CRYSTAL) { tuple_of([int32, char] of Type) } + macro test(num) + {{@caller.first.args[0] == 1 ? 1 : 'f'}} + end + + {test(1), test(2)} + CRYSTAL + end + + it "returns nil if no stack is available" do + assert_type(<<-CRYSTAL) { char } + def test + {{(c = @caller) ? 1 : 'f'}} + end + + test + CRYSTAL + end + end end diff --git a/src/compiler/crystal/macros/interpreter.cr b/src/compiler/crystal/macros/interpreter.cr index 6b38a5066b8e..5bc7cd0117ca 100644 --- a/src/compiler/crystal/macros/interpreter.cr +++ b/src/compiler/crystal/macros/interpreter.cr @@ -69,7 +69,7 @@ module Crystal vars[macro_block_arg.name] = call_block || Nop.new end - new(program, scope, path_lookup, a_macro.location, vars, call.block, a_def, in_macro) + new(program, scope, path_lookup, a_macro.location, vars, call.block, a_def, in_macro, call) end record MacroVarKey, name : String, exps : Array(ASTNode)? @@ -77,7 +77,7 @@ module Crystal def initialize(@program : Program, @scope : Type, @path_lookup : Type, @location : Location?, @vars = {} of String => ASTNode, @block : Block? = nil, @def : Def? = nil, - @in_macro = false) + @in_macro = false, @call : Call? = nil) @str = IO::Memory.new(512) # Can't be String::Builder because of `{{debug}}` @last = Nop.new end @@ -540,6 +540,12 @@ module Crystal @last = TypeNode.new(@program) when "@def" @last = @def || NilLiteral.new + when "@caller" + @last = if call = @call + ArrayLiteral.map [call], &.itself + else + NilLiteral.new + end else node.raise "unknown macro instance var: '#{node.name}'" end From 265724dd8cdfd9fa08e51144d9cacf381c2422c6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 22 Dec 2023 01:39:27 +0800 Subject: [PATCH 0855/1551] Support `alignof` and `instance_alignof` (#14087) --- spec/compiler/codegen/sizeof_spec.cr | 101 ++++++++++++++++++ spec/compiler/formatter/formatter_spec.cr | 2 + spec/compiler/interpreter/sizeof_spec.cr | 18 ++++ spec/compiler/parser/parser_spec.cr | 6 ++ spec/compiler/semantic/sizeof_spec.cr | 26 ++--- .../syntax_highlighter/colorize_spec.cr | 4 +- .../crystal/syntax_highlighter/html_spec.cr | 4 +- src/compiler/crystal/codegen/codegen.cr | 26 ++++- .../crystal/codegen/llvm_builder_helper.cr | 8 ++ src/compiler/crystal/codegen/llvm_typer.cr | 6 +- src/compiler/crystal/interpreter/compiler.cr | 21 +++- src/compiler/crystal/interpreter/context.cr | 24 +++++ src/compiler/crystal/macros.cr | 32 ++++++ src/compiler/crystal/macros/types.cr | 2 + src/compiler/crystal/semantic/ast.cr | 2 +- src/compiler/crystal/semantic/bindings.cr | 6 ++ .../crystal/semantic/cleanup_transformer.cr | 17 +++ src/compiler/crystal/semantic/main_visitor.cr | 53 +++++++-- .../crystal/semantic/type_guess_visitor.cr | 10 +- src/compiler/crystal/semantic/type_lookup.cr | 2 +- src/compiler/crystal/syntax/ast.cr | 12 +++ src/compiler/crystal/syntax/lexer.cr | 30 +++++- src/compiler/crystal/syntax/parser.cr | 26 ++++- src/compiler/crystal/syntax/to_s.cr | 14 +++ src/compiler/crystal/syntax/token.cr | 2 + src/compiler/crystal/syntax/transformer.cr | 10 ++ src/compiler/crystal/tools/formatter.cr | 8 ++ src/llvm/lib_llvm/core.cr | 1 + src/llvm/type.cr | 9 ++ 29 files changed, 440 insertions(+), 42 deletions(-) diff --git a/spec/compiler/codegen/sizeof_spec.cr b/spec/compiler/codegen/sizeof_spec.cr index e3a0690cf06d..8139db8bc426 100644 --- a/spec/compiler/codegen/sizeof_spec.cr +++ b/spec/compiler/codegen/sizeof_spec.cr @@ -243,4 +243,105 @@ describe "Code gen: sizeof" do z)).to_i.should eq(16) end + + describe "alignof" do + it "gets alignof primitive types" do + run("alignof(Int32)").to_i.should eq(4) + run("alignof(Void)").to_i.should eq(1) + run("alignof(NoReturn)").to_i.should eq(1) + run("alignof(Nil)").to_i.should eq(1) + run("alignof(Bool)").to_i.should eq(1) + end + + it "gets alignof struct" do + run(<<-CRYSTAL).to_i.should eq(4) + struct Foo + def initialize(@x : Int8, @y : Int32, @z : Int16) + end + end + + Foo.new(1, 2, 3) + + alignof(Foo) + CRYSTAL + end + + it "gets alignof class" do + # pointer size and alignment should be identical + run(<<-CRYSTAL).to_i.should eq(sizeof(Void*)) + class Foo + def initialize(@x : Int8, @y : Int32, @z : Int16) + end + end + + Foo.new(1, 2, 3) + + alignof(Foo) + CRYSTAL + end + end + + describe "instance_alignof" do + it "gets instance_alignof class" do + run(<<-CRYSTAL).to_i.should eq(4) + class Foo + def initialize(@x : Int8, @y : Int32, @z : Int16) + end + end + + Foo.new(1, 2, 3) + + instance_alignof(Foo) + CRYSTAL + + run(<<-CRYSTAL).to_i.should eq(8) + class Foo + def initialize(@x : Int8, @y : Int64, @z : Int16) + end + end + + Foo.new(1, 2, 3) + + instance_alignof(Foo) + CRYSTAL + + run(<<-CRYSTAL).to_i.should eq(4) + class Foo + end + + Foo.new + + instance_alignof(Foo) + CRYSTAL + end + + it "gets instance_alignof a generic type with type vars" do + run(<<-CRYSTAL).to_i.should eq(4) + class Foo(T) + def initialize(@x : T) + end + end + + instance_alignof(Foo(Int32)) + CRYSTAL + + run(<<-CRYSTAL).to_i.should eq(8) + class Foo(T) + def initialize(@x : T) + end + end + + instance_alignof(Foo(Int64)) + CRYSTAL + + run(<<-CRYSTAL).to_i.should eq(4) + class Foo(T) + def initialize(@x : T) + end + end + + instance_alignof(Foo(Int8)) + CRYSTAL + end + end end diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 4cc2ce230550..4567638f0fd8 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1385,6 +1385,8 @@ describe Crystal::Formatter do assert_format "typeof( 1, 2, 3 )", "typeof(1, 2, 3)" assert_format "sizeof( Int32 )", "sizeof(Int32)" assert_format "instance_sizeof( Int32 )", "instance_sizeof(Int32)" + assert_format "alignof( Int32 )", "alignof(Int32)" + assert_format "instance_alignof( Int32 )", "instance_alignof(Int32)" assert_format "offsetof( String, @length )", "offsetof(String, @length)" assert_format "pointerof( @a )", "pointerof(@a)" diff --git a/spec/compiler/interpreter/sizeof_spec.cr b/spec/compiler/interpreter/sizeof_spec.cr index 4c52247b9687..306bb1bdd199 100644 --- a/spec/compiler/interpreter/sizeof_spec.cr +++ b/spec/compiler/interpreter/sizeof_spec.cr @@ -19,4 +19,22 @@ describe Crystal::Repl::Interpreter do CRYSTAL end end + + context "alignof" do + it "interprets alignof typeof" do + interpret("alignof(typeof(1))").should eq(4) + end + end + + context "instance_alignof" do + it "interprets instance_alignof typeof" do + interpret(<<-CRYSTAL).should eq(8) + class Foo + @x = 0_i64 + end + + instance_alignof(typeof(Foo.new)) + CRYSTAL + end + end end diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 0ee3eedbdd62..41fad5cc3a89 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -768,6 +768,8 @@ module Crystal it_parses "Foo(X, sizeof(Int32))", Generic.new("Foo".path, ["X".path, SizeOf.new("Int32".path)] of ASTNode) it_parses "Foo(X, instance_sizeof(Int32))", Generic.new("Foo".path, ["X".path, InstanceSizeOf.new("Int32".path)] of ASTNode) + it_parses "Foo(X, alignof(Int32))", Generic.new("Foo".path, ["X".path, AlignOf.new("Int32".path)] of ASTNode) + it_parses "Foo(X, instance_alignof(Int32))", Generic.new("Foo".path, ["X".path, InstanceAlignOf.new("Int32".path)] of ASTNode) it_parses "Foo(X, offsetof(Foo, @a))", Generic.new("Foo".path, ["X".path, OffsetOf.new("Foo".path, "@a".instance_var)] of ASTNode) it_parses "Foo(\n)", Generic.new("Foo".path, [] of ASTNode) @@ -1213,6 +1215,8 @@ module Crystal it_parses "sizeof(X)", SizeOf.new("X".path) it_parses "instance_sizeof(X)", InstanceSizeOf.new("X".path) + it_parses "alignof(X)", AlignOf.new("X".path) + it_parses "instance_alignof(X)", InstanceAlignOf.new("X".path) it_parses "offsetof(X, @a)", OffsetOf.new("X".path, "@a".instance_var) it_parses "offsetof(X, 1)", OffsetOf.new("X".path, 1.int32) assert_syntax_error "offsetof(X, 1.0)", "expecting an integer offset, not '1.0'" @@ -1254,6 +1258,8 @@ module Crystal # multiline pseudo methods (#8318) it_parses "sizeof(\n Int32\n)", SizeOf.new(Path.new("Int32")) it_parses "instance_sizeof(\n Int32\n)", InstanceSizeOf.new(Path.new("Int32")) + it_parses "alignof(\n Int32\n)", AlignOf.new(Path.new("Int32")) + it_parses "instance_alignof(\n Int32\n)", InstanceAlignOf.new(Path.new("Int32")) it_parses "typeof(\n 1\n)", TypeOf.new([1.int32] of ASTNode) it_parses "offsetof(\n Foo,\n @foo\n)", OffsetOf.new(Path.new("Foo"), InstanceVar.new("@foo")) it_parses "pointerof(\n foo\n)", PointerOf.new("foo".call) diff --git a/spec/compiler/semantic/sizeof_spec.cr b/spec/compiler/semantic/sizeof_spec.cr index 1cdab6a6ed3b..4b24069ebcb5 100644 --- a/spec/compiler/semantic/sizeof_spec.cr +++ b/spec/compiler/semantic/sizeof_spec.cr @@ -1,24 +1,18 @@ require "../../spec_helper" describe "Semantic: sizeof" do - it "types sizeof" do - assert_type("sizeof(Float64)") { int32 } - end - - it "types sizeof NoReturn (missing type) (#5717)" do - assert_type("x = nil; x ? sizeof(typeof(x)) : 1") { int32 } - end + {% for name in %w(sizeof instance_sizeof alignof instance_alignof).map(&.id) %} + it "types {{name}}" do + assert_type("{{name}}(Reference)") { int32 } + end - it "types instance_sizeof" do - assert_type("instance_sizeof(Reference)") { int32 } - end - - it "types instance_sizeof NoReturn (missing type) (#5717)" do - assert_type("x = nil; x ? instance_sizeof(typeof(x)) : 1") { int32 } - end + it "types {{name}} NoReturn (missing type) (#5717)" do + assert_type("x = nil; x ? {{name}}(typeof(x)) : 1") { int32 } + end + {% end %} it "errors on sizeof uninstantiated generic type (#6415)" do - assert_error "sizeof(Array)", "can't take sizeof uninstantiated generic type Array(T)" + assert_error "sizeof(Array)", "can't take size of uninstantiated generic type Array(T)" end it "gives error if using instance_sizeof on something that's not a class" do @@ -84,7 +78,7 @@ describe "Semantic: sizeof" do end it "gives error if using instance_sizeof on a generic type without type vars" do - assert_error "instance_sizeof(Array)", "can't take instance_sizeof uninstantiated generic type Array(T)" + assert_error "instance_sizeof(Array)", "can't take instance size of uninstantiated generic type Array(T)" end it "gives error if using instance_sizeof on a union type (#8349)" do diff --git a/spec/std/crystal/syntax_highlighter/colorize_spec.cr b/spec/std/crystal/syntax_highlighter/colorize_spec.cr index c989112264fc..74bc591ac51b 100644 --- a/spec/std/crystal/syntax_highlighter/colorize_spec.cr +++ b/spec/std/crystal/syntax_highlighter/colorize_spec.cr @@ -55,10 +55,10 @@ describe Crystal::SyntaxHighlighter::Colorize do def if else elsif end class module include extend while until do yield return unless next break begin lib fun type struct union enum macro out require - case when select then of rescue ensure is_a? alias sizeof + case when select then of rescue ensure is_a? alias sizeof alignof as as? typeof for in with super private asm nil? abstract pointerof - protected uninitialized instance_sizeof offsetof + protected uninitialized instance_sizeof instance_alignof offsetof annotation verbatim ).each do |kw| it_highlights kw, %(\e[91m#{kw}\e[0m) diff --git a/spec/std/crystal/syntax_highlighter/html_spec.cr b/spec/std/crystal/syntax_highlighter/html_spec.cr index 84e7c69ff410..2d8b37a4f51e 100644 --- a/spec/std/crystal/syntax_highlighter/html_spec.cr +++ b/spec/std/crystal/syntax_highlighter/html_spec.cr @@ -50,10 +50,10 @@ describe Crystal::SyntaxHighlighter::HTML do def if else elsif end class module include extend while until do yield return unless next break begin lib fun type struct union enum macro out require - case when select then of rescue ensure is_a? alias sizeof + case when select then of rescue ensure is_a? alias sizeof alignof as as? typeof for in with self super private asm nil? abstract pointerof - protected uninitialized instance_sizeof offsetof + protected uninitialized instance_sizeof instance_alignof offsetof annotation verbatim ).each do |kw| it_highlights kw, %(#{kw}) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 7cec0d3c2c44..f5c35db9d65a 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -91,7 +91,7 @@ module Crystal # We need `sizeof(Void)` to be 1 because doing # `Pointer(Void).malloc` must work like `Pointer(UInt8).malloc`, # that is, consider Void like the size of a byte. - 1 + 1_u64 else llvm_typer.size_of(llvm_typer.llvm_type(type)) end @@ -101,6 +101,20 @@ module Crystal llvm_typer.size_of(llvm_typer.llvm_struct_type(type)) end + def align_of(type) + if type.void? + # We need `alignof(Void)` to be 1 because it is effectively the smallest + # possible alignment for any type. + 1_u32 + else + llvm_typer.align_of(llvm_typer.llvm_type(type)) + end + end + + def instance_align_of(type) + llvm_typer.align_of(llvm_typer.llvm_struct_type(type)) + end + def offset_of(type, element_index) return 0_u64 if type.extern_union? || type.is_a?(StaticArrayInstanceType) llvm_typer.offset_of(llvm_typer.llvm_type(type), element_index) @@ -824,6 +838,16 @@ module Crystal false end + def visit(node : AlignOf) + @last = trunc(llvm_alignment(node.exp.type.sizeof_type), llvm_context.int32) + false + end + + def visit(node : InstanceAlignOf) + @last = trunc(llvm_struct_alignment(node.exp.type.sizeof_type), llvm_context.int32) + false + end + def visit(node : Include) node.hook_expansions.try &.each do |hook| accept hook diff --git a/src/compiler/crystal/codegen/llvm_builder_helper.cr b/src/compiler/crystal/codegen/llvm_builder_helper.cr index 3a9227282aed..dfc107526f6e 100644 --- a/src/compiler/crystal/codegen/llvm_builder_helper.cr +++ b/src/compiler/crystal/codegen/llvm_builder_helper.cr @@ -235,5 +235,13 @@ module Crystal def llvm_struct_size(type) llvm_struct_type(type).size end + + def llvm_alignment(type) + llvm_type(type).alignment + end + + def llvm_struct_alignment(type) + llvm_struct_type(type).alignment + end end end diff --git a/src/compiler/crystal/codegen/llvm_typer.cr b/src/compiler/crystal/codegen/llvm_typer.cr index 8b2cfdf65ec4..d20fbd59fa9a 100644 --- a/src/compiler/crystal/codegen/llvm_typer.cr +++ b/src/compiler/crystal/codegen/llvm_typer.cr @@ -574,7 +574,11 @@ module Crystal end def align_of(type) - @layout.abi_alignment(type) + if type.void? + 1_u32 + else + @layout.abi_alignment(type) + end end def size_t diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 7d56d8121bed..bfd1514b419f 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -1430,6 +1430,22 @@ class Crystal::Repl::Compiler < Crystal::Visitor false end + def visit(node : AlignOf) + return false unless @wants_value + + put_i32 inner_alignof_type(node.exp), node: node + + false + end + + def visit(node : InstanceAlignOf) + return false unless @wants_value + + put_i32 inner_instance_alignof_type(node.exp), node: node + + false + end + def visit(node : TypeNode) return false unless @wants_value @@ -3369,8 +3385,9 @@ class Crystal::Repl::Compiler < Crystal::Visitor @instructions.instructions.size end - private delegate inner_sizeof_type, aligned_sizeof_type, - inner_instance_sizeof_type, aligned_instance_sizeof_type, to: @context + private delegate inner_sizeof_type, inner_alignof_type, aligned_sizeof_type, + inner_instance_sizeof_type, inner_instance_alignof_type, aligned_instance_sizeof_type, + to: @context private def ivar_offset(type : Type, name : String) : Int32 if type.extern_union? diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr index 268d67448125..f4a4444e105c 100644 --- a/src/compiler/crystal/interpreter/context.cr +++ b/src/compiler/crystal/interpreter/context.cr @@ -315,6 +315,18 @@ class Crystal::Repl::Context 0 end + def inner_alignof_type(node : ASTNode) : Int32 + inner_alignof_type(node.type?) + end + + def inner_alignof_type(type : Type) : Int32 + @program.align_of(type.sizeof_type).to_i32 + end + + def inner_alignof_type(type : Nil) : Int32 + 0 + end + def aligned_instance_sizeof_type(type : Type) : Int32 align(inner_instance_sizeof_type(type)) end @@ -331,6 +343,18 @@ class Crystal::Repl::Context 0 end + def inner_instance_alignof_type(node : ASTNode) : Int32 + inner_instance_alignof_type(node.type?) + end + + def inner_instance_alignof_type(type : Type) : Int32 + @program.instance_align_of(type.sizeof_type).to_i32 + end + + def inner_instance_alignof_type(type : Nil) : Int32 + 0 + end + def offset_of(type : Type, index : Int32) : Int32 @program.offset_of(type.sizeof_type, index).to_i32 end diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 3c59a56a1818..983af69ae4b7 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1452,13 +1452,45 @@ module Crystal::Macros end # A `sizeof` expression. + # + # Every expression `node` is equivalent to: + # + # ``` + # sizeof({{ node.exp }}) + # ``` class SizeOf < UnaryExpression end # An `instance_sizeof` expression. + # + # Every expression `node` is equivalent to: + # + # ``` + # instance_sizeof({{ node.exp }}) + # ``` class InstanceSizeOf < UnaryExpression end + # A `alignof` expression. + # + # Every expression `node` is equivalent to: + # + # ``` + # alignof({{ node.exp }}) + # ``` + class AlignOf < UnaryExpression + end + + # An `instance_alignof` expression. + # + # Every expression `node` is equivalent to: + # + # ``` + # instance_alignof({{ node.exp }}) + # ``` + class InstanceAlignOf < UnaryExpression + end + # An `out` expression. class Out < UnaryExpression end diff --git a/src/compiler/crystal/macros/types.cr b/src/compiler/crystal/macros/types.cr index 73ae695066ba..068f92e3d23a 100644 --- a/src/compiler/crystal/macros/types.cr +++ b/src/compiler/crystal/macros/types.cr @@ -54,6 +54,8 @@ module Crystal @macro_types["PointerOf"] = NonGenericMacroType.new self, "PointerOf", unary_expression @macro_types["SizeOf"] = NonGenericMacroType.new self, "SizeOf", unary_expression @macro_types["InstanceSizeOf"] = NonGenericMacroType.new self, "InstanceSizeOf", unary_expression + @macro_types["AlignOf"] = NonGenericMacroType.new self, "AlignOf", unary_expression + @macro_types["InstanceAlignOf"] = NonGenericMacroType.new self, "InstanceAlignOf", unary_expression @macro_types["Out"] = NonGenericMacroType.new self, "Out", unary_expression @macro_types["Splat"] = NonGenericMacroType.new self, "Splat", unary_expression @macro_types["DoubleSplat"] = NonGenericMacroType.new self, "DoubleSplat", unary_expression diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index d976752b5777..f4fa683efe82 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -653,7 +653,7 @@ module Crystal ArrayLiteral HashLiteral RegexLiteral RangeLiteral Case StringInterpolation MacroExpression MacroIf MacroFor MacroVerbatim MultiAssign - SizeOf InstanceSizeOf OffsetOf Global Require Select) %} + SizeOf InstanceSizeOf AlignOf InstanceAlignOf OffsetOf Global Require Select) %} class {{name.id}} include ExpandableNode end diff --git a/src/compiler/crystal/semantic/bindings.cr b/src/compiler/crystal/semantic/bindings.cr index e2a24f00bdff..c5fe9f164742 100644 --- a/src/compiler/crystal/semantic/bindings.cr +++ b/src/compiler/crystal/semantic/bindings.cr @@ -600,6 +600,12 @@ module Crystal if node.is_a?(InstanceSizeOf) && (expanded = node.expanded) node = expanded end + if node.is_a?(AlignOf) && (expanded = node.expanded) + node = expanded + end + if node.is_a?(InstanceAlignOf) && (expanded = node.expanded) + node = expanded + end if node.is_a?(OffsetOf) && (expanded = node.expanded) node = expanded end diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 36e8a1b0c8bc..ad1b570f0cbd 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -996,6 +996,23 @@ module Crystal node end + def transform(node : InstanceAlignOf) + exp_type = node.exp.type? + + if exp_type + instance_type = exp_type.devirtualize + if instance_type.struct? || instance_type.module? || instance_type.metaclass? || instance_type.is_a?(UnionType) + node.exp.raise "instance_alignof can only be used with a class, but #{instance_type} is a #{instance_type.type_desc}" + end + end + + if expanded = node.expanded + return expanded.transform self + end + + node + end + def transform(node : TupleLiteral) super diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 717bcd24e595..37218e7a34dc 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -2627,6 +2627,30 @@ module Crystal end def visit(node : SizeOf) + visit_size_or_align_of(node) do |type| + @program.size_of(type.sizeof_type) + end + end + + def visit(node : InstanceSizeOf) + visit_instance_size_or_align_of(node) do |type| + @program.instance_size_of(type.sizeof_type) + end + end + + def visit(node : AlignOf) + visit_size_or_align_of(node) do |type| + @program.align_of(type.sizeof_type) + end + end + + def visit(node : InstanceAlignOf) + visit_instance_size_or_align_of(node) do |type| + @program.instance_align_of(type.sizeof_type) + end + end + + private def visit_size_or_align_of(node) @in_type_args += 1 node.exp.accept self @in_type_args -= 1 @@ -2634,15 +2658,15 @@ module Crystal type = node.exp.type? if type.is_a?(GenericType) - node.exp.raise "can't take sizeof uninstantiated generic type #{type}" + node.exp.raise "can't take #{sizeof_description(node)} of uninstantiated generic type #{type}" end - # Try to resolve the sizeof right now to a number literal - # (useful for sizeof inside as a generic type argument, but also + # Try to resolve the node right now to a number literal + # (useful for sizeof/alignof inside as a generic type argument, but also # to make it easier for LLVM to optimize things) if type && !node.exp.is_a?(TypeOf) && !(type.module? || (type.abstract? && type.struct?)) - expanded = NumberLiteral.new(@program.size_of(type.sizeof_type).to_s, :i32) + expanded = NumberLiteral.new(yield(type).to_s, :i32) expanded.type = @program.int32 node.expanded = expanded end @@ -2652,7 +2676,7 @@ module Crystal false end - def visit(node : InstanceSizeOf) + private def visit_instance_size_or_align_of(node) @in_type_args += 1 node.exp.accept self @in_type_args -= 1 @@ -2660,14 +2684,14 @@ module Crystal type = node.exp.type? if type.is_a?(GenericType) - node.exp.raise "can't take instance_sizeof uninstantiated generic type #{type}" + node.exp.raise "can't take #{sizeof_description(node)} of uninstantiated generic type #{type}" end # Try to resolve the instance_sizeof right now to a number literal - # (useful for sizeof inside as a generic type argument, but also + # (useful for instance_sizeof inside as a generic type argument, but also # to make it easier for LLVM to optimize things) if type && type.devirtualize.class? && !type.metaclass? && !type.struct? && !node.exp.is_a?(TypeOf) - expanded = NumberLiteral.new(@program.instance_size_of(type.sizeof_type).to_s, :i32) + expanded = NumberLiteral.new(yield(type).to_s, :i32) expanded.type = @program.int32 node.expanded = expanded end @@ -2677,6 +2701,19 @@ module Crystal false end + private def sizeof_description(node) + case node + in SizeOf + "size" + in AlignOf + "alignment" + in InstanceSizeOf + "instance size" + in InstanceAlignOf + "instance alignment" + end + end + def visit(node : OffsetOf) @in_type_args += 1 node.offsetof_type.accept self diff --git a/src/compiler/crystal/semantic/type_guess_visitor.cr b/src/compiler/crystal/semantic/type_guess_visitor.cr index 4eb13804f64d..693cade2906b 100644 --- a/src/compiler/crystal/semantic/type_guess_visitor.cr +++ b/src/compiler/crystal/semantic/type_guess_visitor.cr @@ -1077,6 +1077,14 @@ module Crystal @program.int32 end + def guess_type(node : AlignOf) + @program.int32 + end + + def guess_type(node : InstanceAlignOf) + @program.int32 + end + def guess_type(node : OffsetOf) @program.int32 end @@ -1275,7 +1283,7 @@ module Crystal false end - def visit(node : InstanceSizeOf | SizeOf | OffsetOf | TypeOf | PointerOf) + def visit(node : InstanceSizeOf | SizeOf | InstanceAlignOf | AlignOf | OffsetOf | TypeOf | PointerOf) false end diff --git a/src/compiler/crystal/semantic/type_lookup.cr b/src/compiler/crystal/semantic/type_lookup.cr index 0db48a0298aa..ba538ed0323d 100644 --- a/src/compiler/crystal/semantic/type_lookup.cr +++ b/src/compiler/crystal/semantic/type_lookup.cr @@ -247,7 +247,7 @@ class Crystal::Type type_var.raise "can only splat tuple type, not #{splat_type}" end next - when SizeOf, InstanceSizeOf, OffsetOf + when SizeOf, InstanceSizeOf, AlignOf, InstanceAlignOf, OffsetOf next unless @raise type_var.raise "can't use #{type_var} as a generic type argument" diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 7b37c369927f..d0be18606714 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -1202,6 +1202,18 @@ module Crystal end end + class AlignOf < UnaryExpression + def clone_without_location + AlignOf.new(@exp.clone) + end + end + + class InstanceAlignOf < UnaryExpression + def clone_without_location + InstanceAlignOf.new(@exp.clone) + end + end + class Out < UnaryExpression def clone_without_location Out.new(@exp.clone) diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index cdb0f1a33e1c..21c3ab42c804 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -586,8 +586,19 @@ module Crystal return check_ident_or_keyword(:abstract, start) end when 'l' - if char_sequence?('i', 'a', 's') - return check_ident_or_keyword(:alias, start) + if next_char == 'i' + case next_char + when 'a' + if next_char == 's' + return check_ident_or_keyword(:alias, start) + end + when 'g' + if char_sequence?('n', 'o', 'f') + return check_ident_or_keyword(:alignof, start) + end + else + # scan_ident + end end when 's' case peek_next_char @@ -719,8 +730,19 @@ module Crystal return check_ident_or_keyword(:include, start) end when 's' - if char_sequence?('t', 'a', 'n', 'c', 'e', '_', 's', 'i', 'z', 'e', 'o', 'f') - return check_ident_or_keyword(:instance_sizeof, start) + if char_sequence?('t', 'a', 'n', 'c', 'e', '_') + case next_char + when 's' + if char_sequence?('i', 'z', 'e', 'o', 'f') + return check_ident_or_keyword(:instance_sizeof, start) + end + when 'a' + if char_sequence?('l', 'i', 'g', 'n', 'o', 'f') + return check_ident_or_keyword(:instance_alignof, start) + end + else + # scan_ident + end end else # scan_ident diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index d2d7816dd6b9..751608468cd5 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -1189,6 +1189,10 @@ module Crystal check_type_declaration { parse_sizeof } when .instance_sizeof? check_type_declaration { parse_instance_sizeof } + when .alignof? + check_type_declaration { parse_alignof } + when .instance_alignof? + check_type_declaration { parse_instance_alignof } when .offsetof? check_type_declaration { parse_offsetof } when .typeof? @@ -4111,7 +4115,7 @@ module Crystal .extend?, .class?, .struct?, .module?, .enum?, .while?, .until?, .return?, .next?, .break?, .lib?, .fun?, .alias?, .pointerof?, .sizeof?, .offsetof?, .instance_sizeof?, .typeof?, .private?, .protected?, .asm?, .out?, - .self?, Keyword::IN, .end? + .self?, Keyword::IN, .end?, .alignof?, .instance_alignof? true else false @@ -4123,7 +4127,7 @@ module Crystal "extend", "class", "struct", "module", "enum", "while", "until", "return", "next", "break", "lib", "fun", "alias", "pointerof", "sizeof", "offsetof", "instance_sizeof", "typeof", "private", "protected", "asm", "out", - "self", "in", "end" + "self", "in", "end", "alignof", "instance_alignof" true else false @@ -5139,7 +5143,11 @@ module Crystal args << parse_type_splat { parse_type_arg } end - has_int = args.any? { |arg| arg.is_a?(NumberLiteral) || arg.is_a?(SizeOf) || arg.is_a?(InstanceSizeOf) || arg.is_a?(OffsetOf) } + has_int = args.any? do |arg| + arg.is_a?(NumberLiteral) || arg.is_a?(SizeOf) || arg.is_a?(InstanceSizeOf) || + arg.is_a?(AlignOf) || arg.is_a?(InstanceAlignOf) || arg.is_a?(OffsetOf) + end + if @token.type.op_minus_gt? && !has_int args = [parse_proc_type_output(args, args.first.location)] of ASTNode end @@ -5221,6 +5229,10 @@ module Crystal parse_sizeof when .keyword?(:instance_sizeof) parse_instance_sizeof + when .keyword?(:alignof) + parse_alignof + when .keyword?(:instance_alignof) + parse_instance_alignof when .keyword?(:offsetof) parse_offsetof else @@ -5875,6 +5887,14 @@ module Crystal parse_sizeof InstanceSizeOf end + def parse_alignof + parse_sizeof AlignOf + end + + def parse_instance_alignof + parse_sizeof InstanceAlignOf + end + def parse_sizeof(klass) sizeof_location = @token.location next_token_skip_space diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 69a936c22e5a..8ea364d3f991 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -1257,6 +1257,20 @@ module Crystal false end + def visit(node : AlignOf) + @str << "alignof(" + node.exp.accept(self) + @str << ')' + false + end + + def visit(node : InstanceAlignOf) + @str << "instance_alignof(" + node.exp.accept(self) + @str << ')' + false + end + def visit(node : OffsetOf) @str << "offsetof(" node.offsetof_type.accept(self) diff --git a/src/compiler/crystal/syntax/token.cr b/src/compiler/crystal/syntax/token.cr index 125ec14ee589..7b5da2091b67 100644 --- a/src/compiler/crystal/syntax/token.cr +++ b/src/compiler/crystal/syntax/token.cr @@ -5,6 +5,7 @@ module Crystal enum Keyword ABSTRACT ALIAS + ALIGNOF ANNOTATION AS AS_QUESTION @@ -27,6 +28,7 @@ module Crystal IF IN INCLUDE + INSTANCE_ALIGNOF INSTANCE_SIZEOF IS_A_QUESTION LIB diff --git a/src/compiler/crystal/syntax/transformer.cr b/src/compiler/crystal/syntax/transformer.cr index 299e1c53c6ad..1dc584ebfa17 100644 --- a/src/compiler/crystal/syntax/transformer.cr +++ b/src/compiler/crystal/syntax/transformer.cr @@ -180,6 +180,16 @@ module Crystal node end + def transform(node : AlignOf) + node.exp = node.exp.transform(self) + node + end + + def transform(node : InstanceAlignOf) + node.exp = node.exp.transform(self) + node + end + def transform(node : OffsetOf) node.offsetof_type = node.offsetof_type.transform(self) node.offset = node.offset.transform(self) diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index ae16d4bec0fd..fc3be7a4cf66 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -3959,6 +3959,14 @@ module Crystal visit Call.new(nil, "instance_sizeof", node.exp) end + def visit(node : AlignOf) + visit Call.new(nil, "alignof", node.exp) + end + + def visit(node : InstanceAlignOf) + visit Call.new(nil, "instance_alignof", node.exp) + end + def visit(node : OffsetOf) visit Call.new(nil, "offsetof", [node.offsetof_type, node.offset]) end diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr index 6eec889e2442..0645441f8a70 100644 --- a/src/llvm/lib_llvm/core.cr +++ b/src/llvm/lib_llvm/core.cr @@ -120,6 +120,7 @@ lib LibLLVM fun const_struct_in_context = LLVMConstStructInContext(c : ContextRef, constant_vals : ValueRef*, count : UInt, packed : Bool) : ValueRef fun const_array = LLVMConstArray(element_ty : TypeRef, constant_vals : ValueRef*, length : UInt) : ValueRef + fun align_of = LLVMAlignOf(ty : TypeRef) : ValueRef fun size_of = LLVMSizeOf(ty : TypeRef) : ValueRef fun get_global_parent = LLVMGetGlobalParent(global : ValueRef) : ModuleRef diff --git a/src/llvm/type.cr b/src/llvm/type.cr index dc5f127492ab..06c36ff5796d 100644 --- a/src/llvm/type.cr +++ b/src/llvm/type.cr @@ -21,6 +21,15 @@ struct LLVM::Type end end + def alignment + # Asking the alignment of void crashes the program, we definitely don't want that + if void? + context.int64.const_int(1) + else + Value.new LibLLVM.align_of(self) + end + end + def kind : LLVM::Type::Kind LibLLVM.get_type_kind(self) end From 691677f61b7d01a18c43fd0fcd78a16d4efe54b9 Mon Sep 17 00:00:00 2001 From: V Date: Thu, 21 Dec 2023 18:39:36 +0100 Subject: [PATCH 0856/1551] Change `Regex::MatchData#to_s` to return matched substring (#14115) --- spec/std/regex/match_data_spec.cr | 6 +++--- src/regex/match_data.cr | 19 +++++++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/spec/std/regex/match_data_spec.cr b/spec/std/regex/match_data_spec.cr index db035f481677..1ed6cf70c44e 100644 --- a/spec/std/regex/match_data_spec.cr +++ b/spec/std/regex/match_data_spec.cr @@ -22,9 +22,9 @@ describe "Regex::MatchData" do end it "#to_s" do - matchdata(/f(o)(x)/, "the fox").to_s.should eq(%(Regex::MatchData("fox" 1:"o" 2:"x"))) - matchdata(/f(?o)(?x)/, "the fox").to_s.should eq(%(Regex::MatchData("fox" lettero:"o" letterx:"x"))) - matchdata(/fox/, "the fox").to_s.should eq(%(Regex::MatchData("fox"))) + matchdata(/f(o)(x)/, "the fox").to_s.should eq("fox") + matchdata(/f(?o)(?x)/, "the fox").to_s.should eq("fox") + matchdata(/fox/, "the fox").to_s.should eq("fox") end it "#pretty_print" do diff --git a/src/regex/match_data.cr b/src/regex/match_data.cr index b9271d423f82..ddf746ae0d6a 100644 --- a/src/regex/match_data.cr +++ b/src/regex/match_data.cr @@ -336,10 +336,6 @@ class Regex end def inspect(io : IO) : Nil - to_s(io) - end - - def to_s(io : IO) : Nil name_table = @regex.name_table io << "Regex::MatchData(" @@ -350,6 +346,21 @@ class Regex io << ')' end + # Returns the matched substring. + # + # ``` + # "Crystal".match!(/yst/).to_s # => "yst" + # "Crystal".match!(/(y)(s)(?=t)/).to_s # => "ys" + # ``` + def to_s : String + self[0] + end + + # Prints the matched substring to *io*. + def to_s(io : IO) : Nil + io << to_s + end + def pretty_print(pp) : Nil name_table = @regex.name_table From b187d4a12c843030b9ac46068d5eca2647bae57c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 22 Dec 2023 01:39:44 +0800 Subject: [PATCH 0857/1551] Fix out-of-bounds access in `Int128::MIN.to_s(base: 2)` (#14119) --- spec/std/int_spec.cr | 1 + src/int.cr | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/std/int_spec.cr b/spec/std/int_spec.cr index 88ef12553c38..e027996a3d59 100644 --- a/spec/std/int_spec.cr +++ b/spec/std/int_spec.cr @@ -376,6 +376,7 @@ describe "Int" do it_converts_to_s 62, "10", base: 62 it_converts_to_s 97, "1z", base: 62 it_converts_to_s 3843, "ZZ", base: 62 + it_converts_to_s Int128::MIN, "-1#{"0" * 127}", base: 2 it "raises on base 1" do expect_raises(ArgumentError, "Invalid base 1") { 123.to_s(1) } diff --git a/src/int.cr b/src/int.cr index 47fbcca16259..005d2c625c7b 100644 --- a/src/int.cr +++ b/src/int.cr @@ -749,7 +749,7 @@ struct Int # representation, plus one byte for the negative sign (possibly used by the # string-returning overload). chars = uninitialized UInt8[129] - ptr_end = chars.to_unsafe + 128 + ptr_end = chars.to_unsafe + 129 ptr = ptr_end num = self From 318d0cc07277fd156cd1add0f06fc94b4d0676cc Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 22 Dec 2023 17:03:23 +0800 Subject: [PATCH 0858/1551] Implement `sprintf "%g"` in Crystal (#14123) --- spec/std/float_printer/ryu_printf_spec.cr | 947 ++++++++++ .../float_printer/ryu_printf_test_cases.cr | 1670 +++++++++++++++++ spec/std/sprintf_spec.cr | 211 ++- src/float/printer/ryu_printf.cr | 77 + src/float/printer/ryu_printf_table.cr | 527 ++++++ src/string/formatter.cr | 33 +- 6 files changed, 3458 insertions(+), 7 deletions(-) diff --git a/spec/std/float_printer/ryu_printf_spec.cr b/spec/std/float_printer/ryu_printf_spec.cr index d4ba77bffd94..4610485f2270 100644 --- a/spec/std/float_printer/ryu_printf_spec.cr +++ b/spec/std/float_printer/ryu_printf_spec.cr @@ -4,6 +4,7 @@ # This file contains test cases derived from: # # * https://github.com/ulfjack/ryu +# * https://github.com/microsoft/STL/tree/main/tests/std/tests/P0067R5_charconv # # The following is their license: # @@ -25,6 +26,7 @@ # KIND, either express or implied. require "spec" +require "../../support/number" require "float/printer/ryu_printf" require "big" require "./ryu_printf_test_cases" @@ -118,6 +120,10 @@ private macro expect_exp(float, precision, string) Float::Printer::RyuPrintf.d2exp({{ float }}, {{ precision }}).should eq({{ string }}) end +private macro expect_gen(float, precision, string, *, file = __FILE__, line = __LINE__) + Float::Printer::RyuPrintf.d2gen({{ float }}, {{ precision }}).should eq({{ string }}), file: {{ file }}, line: {{ line }} +end + describe Float::Printer::RyuPrintf do describe ".d2fixed" do it "Basic" do @@ -491,4 +497,945 @@ describe Float::Printer::RyuPrintf do expect_exp(1e+83, 1, "1.0e+83") end end + + describe ".d2gen" do + it "Basic" do + expect_gen(0.0, 4, "0") + expect_gen(1.729, 4, "1.729") + end + + it "corner cases" do + expect_gen(Float64::MIN_SUBNORMAL, 1000, + "4.940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359923797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036887186360569987307230500063874091535649843873124733972731696151400317153853980741262385655911710266585566867681870395603106249319452715914924553293054565444011274801297099995419319894090804165633245247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431936092382893458368060106011506169809753078342277318329247904982524730776375927247874656084778203734469699533647017972677717585125660551199131504891101451037862738167250955837389733598993664809941164205702637090279242767544565229087538682506419718265533447265625e-324") + expect_gen(Float64::MIN_POSITIVE.prev_float, 1000, + "2.2250738585072008890245868760858598876504231122409594654935248025624400092282356951787758888037591552642309780950434312085877387158357291821993020294379224223559819827501242041788969571311791082261043971979604000454897391938079198936081525613113376149842043271751033627391549782731594143828136275113838604094249464942286316695429105080201815926642134996606517803095075913058719846423906068637102005108723282784678843631944515866135041223479014792369585208321597621066375401613736583044193603714778355306682834535634005074073040135602968046375918583163124224521599262546494300836851861719422417646455137135420132217031370496583210154654068035397417906022589503023501937519773030945763173210852507299305089761582519159720757232455434770912461317493580281734466552734375e-308") + expect_gen(Float64::MIN_POSITIVE, 1000, + "2.225073858507201383090232717332404064219215980462331830553327416887204434813918195854283159012511020564067339731035811005152434161553460108856012385377718821130777993532002330479610147442583636071921565046942503734208375250806650616658158948720491179968591639648500635908770118304874799780887753749949451580451605050915399856582470818645113537935804992115981085766051992433352114352390148795699609591288891602992641511063466313393663477586513029371762047325631781485664350872122828637642044846811407613911477062801689853244110024161447421618567166150540154285084716752901903161322778896729707373123334086988983175067838846926092773977972858659654941091369095406136467568702398678315290680984617210924625396728515625e-308") + expect_gen(Float64::MAX, 1000, + "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368") + + expect_gen(Float64::MIN_SUBNORMAL, 6, "4.94066e-324") + expect_gen(Float64::MIN_POSITIVE.prev_float, 6, "2.22507e-308") + expect_gen(Float64::MIN_POSITIVE, 6, "2.22507e-308") + expect_gen(Float64::MAX, 6, "1.79769e+308") + end + + it "maximum-length output" do + expect_gen(hexfloat("0x1.fffffffffffffp-1022"), 1000, + "4.4501477170144022721148195934182639518696390927032912960468522194496444440421538910330590478162701758282983178260792422137401728773891892910553144148156412434867599762821265346585071045737627442980259622449029037796981144446145705102663115100318287949527959668236039986479250965780342141637013812613333119898765515451440315261253813266652951306000184917766328660755595837392240989947807556594098101021612198814605258742579179000071675999344145086087205681577915435923018910334964869420614052182892431445797605163650903606514140377217442262561590244668525767372446430075513332450079650686719491377688478005309963967709758965844137894433796621993967316936280457084866613206797017728916080020698679408551343728867675409720757232455434770912461317493580281734466552734375e-308") + expect_gen(hexfloat("0x1.fffffffffffffp-14"), 1000, + "0.000122070312499999986447472843931194574906839989125728607177734375") + end + + it "varying precision" do + expect_gen(hexfloat("0x1.b04p0"), 0, "2") + expect_gen(hexfloat("0x1.b04p0"), 1, "2") # fixed notation trims decimal point + expect_gen(hexfloat("0x1.b04p0"), 2, "1.7") + expect_gen(hexfloat("0x1.b04p0"), 3, "1.69") + expect_gen(hexfloat("0x1.b04p0"), 4, "1.688") + expect_gen(hexfloat("0x1.b04p0"), 5, "1.6885") + expect_gen(hexfloat("0x1.b04p0"), 6, "1.68848") + expect_gen(hexfloat("0x1.b04p0"), 7, "1.688477") + expect_gen(hexfloat("0x1.b04p0"), 8, "1.6884766") + expect_gen(hexfloat("0x1.b04p0"), 9, "1.68847656") + expect_gen(hexfloat("0x1.b04p0"), 10, "1.688476562") # round to even + expect_gen(hexfloat("0x1.b04p0"), 11, "1.6884765625") # exact + expect_gen(hexfloat("0x1.b04p0"), 12, "1.6884765625") # trim trailing zeros + expect_gen(hexfloat("0x1.b04p0"), 13, "1.6884765625") + + expect_gen(hexfloat("0x1.8p-15"), 0, "5e-5") + expect_gen(hexfloat("0x1.8p-15"), 1, "5e-5") # scientific notation trims decimal point + expect_gen(hexfloat("0x1.8p-15"), 2, "4.6e-5") + expect_gen(hexfloat("0x1.8p-15"), 3, "4.58e-5") + expect_gen(hexfloat("0x1.8p-15"), 4, "4.578e-5") + expect_gen(hexfloat("0x1.8p-15"), 5, "4.5776e-5") + expect_gen(hexfloat("0x1.8p-15"), 6, "4.57764e-5") + expect_gen(hexfloat("0x1.8p-15"), 7, "4.577637e-5") + expect_gen(hexfloat("0x1.8p-15"), 8, "4.5776367e-5") + expect_gen(hexfloat("0x1.8p-15"), 9, "4.57763672e-5") + expect_gen(hexfloat("0x1.8p-15"), 10, "4.577636719e-5") + expect_gen(hexfloat("0x1.8p-15"), 11, "4.5776367188e-5") # round to even + expect_gen(hexfloat("0x1.8p-15"), 12, "4.57763671875e-5") # exact + expect_gen(hexfloat("0x1.8p-15"), 13, "4.57763671875e-5") # trim trailing zeros + expect_gen(hexfloat("0x1.8p-15"), 14, "4.57763671875e-5") + end + + it "trim trailing zeros" do + expect_gen(hexfloat("0x1.80015p0"), 1, "2") # fixed notation trims decimal point + expect_gen(hexfloat("0x1.80015p0"), 2, "1.5") + expect_gen(hexfloat("0x1.80015p0"), 3, "1.5") # general trims trailing zeros + expect_gen(hexfloat("0x1.80015p0"), 4, "1.5") + expect_gen(hexfloat("0x1.80015p0"), 5, "1.5") + expect_gen(hexfloat("0x1.80015p0"), 6, "1.50002") + expect_gen(hexfloat("0x1.80015p0"), 7, "1.50002") + expect_gen(hexfloat("0x1.80015p0"), 8, "1.50002") + expect_gen(hexfloat("0x1.80015p0"), 9, "1.50002003") + expect_gen(hexfloat("0x1.80015p0"), 10, "1.500020027") + expect_gen(hexfloat("0x1.80015p0"), 11, "1.5000200272") + expect_gen(hexfloat("0x1.80015p0"), 12, "1.50002002716") + expect_gen(hexfloat("0x1.80015p0"), 13, "1.500020027161") + expect_gen(hexfloat("0x1.80015p0"), 14, "1.5000200271606") + expect_gen(hexfloat("0x1.80015p0"), 15, "1.50002002716064") + expect_gen(hexfloat("0x1.80015p0"), 16, "1.500020027160645") + expect_gen(hexfloat("0x1.80015p0"), 17, "1.5000200271606445") + expect_gen(hexfloat("0x1.80015p0"), 18, "1.50002002716064453") + expect_gen(hexfloat("0x1.80015p0"), 19, "1.500020027160644531") + expect_gen(hexfloat("0x1.80015p0"), 20, "1.5000200271606445312") # round to even + expect_gen(hexfloat("0x1.80015p0"), 21, "1.50002002716064453125") # exact + end + + it "trim trailing zeros and decimal point" do + expect_gen(hexfloat("0x1.00015p0"), 1, "1") # fixed notation trims decimal point + expect_gen(hexfloat("0x1.00015p0"), 2, "1") # general trims decimal point and trailing zeros + expect_gen(hexfloat("0x1.00015p0"), 3, "1") + expect_gen(hexfloat("0x1.00015p0"), 4, "1") + expect_gen(hexfloat("0x1.00015p0"), 5, "1") + expect_gen(hexfloat("0x1.00015p0"), 6, "1.00002") + expect_gen(hexfloat("0x1.00015p0"), 7, "1.00002") + expect_gen(hexfloat("0x1.00015p0"), 8, "1.00002") + expect_gen(hexfloat("0x1.00015p0"), 9, "1.00002003") + expect_gen(hexfloat("0x1.00015p0"), 10, "1.000020027") + expect_gen(hexfloat("0x1.00015p0"), 11, "1.0000200272") + expect_gen(hexfloat("0x1.00015p0"), 12, "1.00002002716") + expect_gen(hexfloat("0x1.00015p0"), 13, "1.000020027161") + expect_gen(hexfloat("0x1.00015p0"), 14, "1.0000200271606") + expect_gen(hexfloat("0x1.00015p0"), 15, "1.00002002716064") + expect_gen(hexfloat("0x1.00015p0"), 16, "1.000020027160645") + expect_gen(hexfloat("0x1.00015p0"), 17, "1.0000200271606445") + expect_gen(hexfloat("0x1.00015p0"), 18, "1.00002002716064453") + expect_gen(hexfloat("0x1.00015p0"), 19, "1.000020027160644531") + expect_gen(hexfloat("0x1.00015p0"), 20, "1.0000200271606445312") # round to even + expect_gen(hexfloat("0x1.00015p0"), 21, "1.00002002716064453125") # exact + end + + it "trim trailing zeros, scientific notation" do + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 1, "1e-6") # scientific notation trims decimal point + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 2, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 3, "1.3e-6") # general trims trailing zeros + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 4, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 5, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 6, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 7, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 8, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 9, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 10, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 11, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 12, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 13, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 14, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 15, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 16, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 17, "1.3e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 18, "1.30000000000000005e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 19, "1.300000000000000047e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 20, "1.3000000000000000471e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 21, "1.30000000000000004705e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 22, "1.300000000000000047052e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 23, "1.3000000000000000470517e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 24, "1.30000000000000004705166e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 25, "1.300000000000000047051664e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 26, "1.3000000000000000470516638e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 27, "1.30000000000000004705166378e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 28, "1.30000000000000004705166378e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 29, "1.3000000000000000470516637804e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 30, "1.30000000000000004705166378044e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 31, "1.30000000000000004705166378044e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 32, "1.3000000000000000470516637804397e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 33, "1.30000000000000004705166378043968e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 34, "1.300000000000000047051663780439679e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 35, "1.3000000000000000470516637804396787e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 36, "1.30000000000000004705166378043967867e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 37, "1.300000000000000047051663780439678675e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 38, "1.3000000000000000470516637804396786748e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 39, "1.30000000000000004705166378043967867484e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 40, "1.300000000000000047051663780439678674838e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 41, "1.3000000000000000470516637804396786748384e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 42, "1.30000000000000004705166378043967867483843e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 43, "1.300000000000000047051663780439678674838433e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 44, "1.3000000000000000470516637804396786748384329e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 45, "1.30000000000000004705166378043967867483843293e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 46, "1.300000000000000047051663780439678674838432926e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 47, "1.3000000000000000470516637804396786748384329258e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 48, "1.30000000000000004705166378043967867483843292575e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 49, "1.300000000000000047051663780439678674838432925753e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 50, "1.3000000000000000470516637804396786748384329257533e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 51, "1.3000000000000000470516637804396786748384329257533e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 52, "1.300000000000000047051663780439678674838432925753295e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 53, "1.3000000000000000470516637804396786748384329257532954e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 54, "1.30000000000000004705166378043967867483843292575329542e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 55, "1.300000000000000047051663780439678674838432925753295422e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 56, "1.3000000000000000470516637804396786748384329257532954216e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 57, "1.3000000000000000470516637804396786748384329257532954216e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 58, "1.3000000000000000470516637804396786748384329257532954216e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 59, "1.3000000000000000470516637804396786748384329257532954216003e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 60, "1.30000000000000004705166378043967867483843292575329542160034e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 61, "1.300000000000000047051663780439678674838432925753295421600342e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 62, "1.3000000000000000470516637804396786748384329257532954216003418e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 63, "1.3000000000000000470516637804396786748384329257532954216003418e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 64, "1.300000000000000047051663780439678674838432925753295421600341797e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 65, "1.3000000000000000470516637804396786748384329257532954216003417969e-6") + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 66, "1.30000000000000004705166378043967867483843292575329542160034179688e-6") # round to even + expect_gen(hexfloat("0x1.5cf751db94e6bp-20"), 67, "1.300000000000000047051663780439678674838432925753295421600341796875e-6") # exact + end + + it "trim trailing zeros and decimal point, scientific notation" do + expect_gen(hexfloat("0x1.92a737110e454p-19"), 1, "3e-6") # scientific notation trims decimal point + expect_gen(hexfloat("0x1.92a737110e454p-19"), 2, "3e-6") # general trims decimal point and trailing zeros + expect_gen(hexfloat("0x1.92a737110e454p-19"), 3, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 4, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 5, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 6, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 7, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 8, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 9, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 10, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 11, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 12, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 13, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 14, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 15, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 16, "3e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 17, "3.0000000000000001e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 18, "3.00000000000000008e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 19, "3.000000000000000076e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 20, "3.000000000000000076e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 21, "3.000000000000000076e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 22, "3.000000000000000076003e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 23, "3.0000000000000000760026e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 24, "3.00000000000000007600257e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 25, "3.000000000000000076002572e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 26, "3.0000000000000000760025723e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 27, "3.00000000000000007600257229e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 28, "3.000000000000000076002572291e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 29, "3.0000000000000000760025722912e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 30, "3.00000000000000007600257229123e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 31, "3.000000000000000076002572291234e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 32, "3.0000000000000000760025722912339e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 33, "3.00000000000000007600257229123386e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 34, "3.000000000000000076002572291233861e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 35, "3.0000000000000000760025722912338608e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 36, "3.00000000000000007600257229123386082e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 37, "3.000000000000000076002572291233860824e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 38, "3.0000000000000000760025722912338608239e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 39, "3.00000000000000007600257229123386082392e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 40, "3.000000000000000076002572291233860823922e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 41, "3.0000000000000000760025722912338608239224e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 42, "3.00000000000000007600257229123386082392244e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 43, "3.000000000000000076002572291233860823922441e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 44, "3.0000000000000000760025722912338608239224413e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 45, "3.00000000000000007600257229123386082392244134e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 46, "3.000000000000000076002572291233860823922441341e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 47, "3.000000000000000076002572291233860823922441341e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 48, "3.00000000000000007600257229123386082392244134098e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 49, "3.000000000000000076002572291233860823922441340983e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 50, "3.0000000000000000760025722912338608239224413409829e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 51, "3.00000000000000007600257229123386082392244134098291e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 52, "3.000000000000000076002572291233860823922441340982914e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 53, "3.000000000000000076002572291233860823922441340982914e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 54, "3.00000000000000007600257229123386082392244134098291397e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 55, "3.000000000000000076002572291233860823922441340982913971e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 56, "3.0000000000000000760025722912338608239224413409829139709e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 57, "3.00000000000000007600257229123386082392244134098291397095e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 58, "3.000000000000000076002572291233860823922441340982913970947e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 59, "3.0000000000000000760025722912338608239224413409829139709473e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 60, "3.00000000000000007600257229123386082392244134098291397094727e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 61, "3.000000000000000076002572291233860823922441340982913970947266e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 62, "3.0000000000000000760025722912338608239224413409829139709472656e-6") + expect_gen(hexfloat("0x1.92a737110e454p-19"), 63, "3.00000000000000007600257229123386082392244134098291397094726562e-6") # round to even + expect_gen(hexfloat("0x1.92a737110e454p-19"), 64, "3.000000000000000076002572291233860823922441340982913970947265625e-6") # exact + end + + it "large precision with fixed notation and scientific notation" do + expect_gen(hexfloat("0x1.ba9fbe76c8b44p+0"), 5000, "1.72900000000000009237055564881302416324615478515625") + expect_gen(hexfloat("0x1.d01ff9abb93d1p-20"), 5000, "1.729000000000000090107283613749533657255597063340246677398681640625e-6") + end + + it "transitions between fixed notation and scientific notation" do + expect_gen(5555555.0, 1, "6e+6") + expect_gen(555555.0, 1, "6e+5") + expect_gen(55555.0, 1, "6e+4") + expect_gen(5555.0, 1, "6e+3") + expect_gen(555.0, 1, "6e+2") + expect_gen(55.0, 1, "6e+1") # round to even + expect_gen(5.0, 1, "5") + expect_gen(hexfloat("0x1p-3"), 1, "0.1") # 0.125 + expect_gen(hexfloat("0x1p-6"), 1, "0.02") # 0.015625 + expect_gen(hexfloat("0x1p-9"), 1, "0.002") # 0.001953125 + expect_gen(hexfloat("0x1p-13"), 1, "0.0001") # 0.0001220703125 + expect_gen(hexfloat("0x1p-16"), 1, "2e-5") # 1.52587890625e-5 + expect_gen(hexfloat("0x1p-19"), 1, "2e-6") # 1.9073486328125e-6 + + expect_gen(5555555.0, 2, "5.6e+6") + expect_gen(555555.0, 2, "5.6e+5") + expect_gen(55555.0, 2, "5.6e+4") + expect_gen(5555.0, 2, "5.6e+3") + expect_gen(555.0, 2, "5.6e+2") # round to even + expect_gen(55.0, 2, "55") + expect_gen(5.0, 2, "5") + expect_gen(hexfloat("0x1p-3"), 2, "0.12") # round to even + expect_gen(hexfloat("0x1p-6"), 2, "0.016") + expect_gen(hexfloat("0x1p-9"), 2, "0.002") + expect_gen(hexfloat("0x1p-13"), 2, "0.00012") + expect_gen(hexfloat("0x1p-16"), 2, "1.5e-5") + expect_gen(hexfloat("0x1p-19"), 2, "1.9e-6") + + expect_gen(5555555.0, 3, "5.56e+6") + expect_gen(555555.0, 3, "5.56e+5") + expect_gen(55555.0, 3, "5.56e+4") + expect_gen(5555.0, 3, "5.56e+3") # round to even + expect_gen(555.0, 3, "555") + expect_gen(55.0, 3, "55") + expect_gen(5.0, 3, "5") + expect_gen(hexfloat("0x1p-3"), 3, "0.125") + expect_gen(hexfloat("0x1p-6"), 3, "0.0156") + expect_gen(hexfloat("0x1p-9"), 3, "0.00195") + expect_gen(hexfloat("0x1p-13"), 3, "0.000122") + expect_gen(hexfloat("0x1p-16"), 3, "1.53e-5") + expect_gen(hexfloat("0x1p-19"), 3, "1.91e-6") + + expect_gen(5555555.0, 4, "5.556e+6") + expect_gen(555555.0, 4, "5.556e+5") + expect_gen(55555.0, 4, "5.556e+4") # round to even + expect_gen(5555.0, 4, "5555") + expect_gen(555.0, 4, "555") + expect_gen(55.0, 4, "55") + expect_gen(5.0, 4, "5") + expect_gen(hexfloat("0x1p-3"), 4, "0.125") + expect_gen(hexfloat("0x1p-6"), 4, "0.01562") # round to even + expect_gen(hexfloat("0x1p-9"), 4, "0.001953") + expect_gen(hexfloat("0x1p-13"), 4, "0.0001221") + expect_gen(hexfloat("0x1p-16"), 4, "1.526e-5") + expect_gen(hexfloat("0x1p-19"), 4, "1.907e-6") + + expect_gen(5555555.0, 5, "5.5556e+6") + expect_gen(555555.0, 5, "5.5556e+5") # round to even + expect_gen(55555.0, 5, "55555") + expect_gen(5555.0, 5, "5555") + expect_gen(555.0, 5, "555") + expect_gen(55.0, 5, "55") + expect_gen(5.0, 5, "5") + expect_gen(hexfloat("0x1p-3"), 5, "0.125") + expect_gen(hexfloat("0x1p-6"), 5, "0.015625") + expect_gen(hexfloat("0x1p-9"), 5, "0.0019531") + expect_gen(hexfloat("0x1p-13"), 5, "0.00012207") + expect_gen(hexfloat("0x1p-16"), 5, "1.5259e-5") + expect_gen(hexfloat("0x1p-19"), 5, "1.9073e-6") + end + + it "tricky corner cases" do + expect_gen(999.999, 1, "1e+3") # "%.0e" is "1e+3"; X == 3 + expect_gen(999.999, 2, "1e+3") # "%.1e" is "1.0e+3"; X == 3 + expect_gen(999.999, 3, "1e+3") # "%.2e" is "1.00e+3"; X == 3 + expect_gen(999.999, 4, "1000") # "%.3e" is "1.000e+3"; X == 3 + expect_gen(999.999, 5, "1000") # "%.4e" is "1.0000e+3"; X == 3 + expect_gen(999.999, 6, "999.999") # "%.5e" is "9.99999e+2"; X == 2 + + expect_gen(999.99, 1, "1e+3") + expect_gen(999.99, 2, "1e+3") + expect_gen(999.99, 3, "1e+3") + expect_gen(999.99, 4, "1000") + expect_gen(999.99, 5, "999.99") + expect_gen(999.99, 6, "999.99") + + # C11's Standardese is slightly vague about how to perform the trial formatting in scientific notation, + # but the intention is to use precision P - 1, which is what's used when scientific notation is actually chosen. + # This example verifies this behavior. Here, P == 3 performs trial formatting with "%.2e", triggering rounding. + # That increases X to 3, forcing scientific notation to be chosen. + # If P == 3 performed trial formatting with "%.3e", rounding wouldn't happen, + # X would be 2, and fixed notation would be chosen. + expect_gen(999.9, 1, "1e+3") # "%.0e" is "1e+3"; X == 3 + expect_gen(999.9, 2, "1e+3") # "%.1e" is "1.0e+3"; X == 3 + expect_gen(999.9, 3, "1e+3") # "%.2e" is "1.00e+3"; X == 3; SPECIAL CORNER CASE + expect_gen(999.9, 4, "999.9") # "%.3e" is "9.999e+2"; X == 2 + expect_gen(999.9, 5, "999.9") # "%.4e" is "9.9990e+2"; X == 2 + expect_gen(999.9, 6, "999.9") # "%.5e" is "9.99900e+2"; X == 2 + + expect_gen(999.0, 1, "1e+3") + expect_gen(999.0, 2, "1e+3") + expect_gen(999.0, 3, "999") + expect_gen(999.0, 4, "999") + expect_gen(999.0, 5, "999") + expect_gen(999.0, 6, "999") + + expect_gen(99.9999, 1, "1e+2") + expect_gen(99.9999, 2, "1e+2") + expect_gen(99.9999, 3, "100") + expect_gen(99.9999, 4, "100") + expect_gen(99.9999, 5, "100") + expect_gen(99.9999, 6, "99.9999") + + expect_gen(99.999, 1, "1e+2") + expect_gen(99.999, 2, "1e+2") + expect_gen(99.999, 3, "100") + expect_gen(99.999, 4, "100") + expect_gen(99.999, 5, "99.999") + expect_gen(99.999, 6, "99.999") + + expect_gen(99.99, 1, "1e+2") + expect_gen(99.99, 2, "1e+2") + expect_gen(99.99, 3, "100") + expect_gen(99.99, 4, "99.99") + expect_gen(99.99, 5, "99.99") + expect_gen(99.99, 6, "99.99") + + expect_gen(99.9, 1, "1e+2") + expect_gen(99.9, 2, "1e+2") + expect_gen(99.9, 3, "99.9") + expect_gen(99.9, 4, "99.9") + expect_gen(99.9, 5, "99.9") + expect_gen(99.9, 6, "99.9") + + expect_gen(99.0, 1, "1e+2") + expect_gen(99.0, 2, "99") + expect_gen(99.0, 3, "99") + expect_gen(99.0, 4, "99") + expect_gen(99.0, 5, "99") + expect_gen(99.0, 6, "99") + + expect_gen(9.99999, 1, "1e+1") + expect_gen(9.99999, 2, "10") + expect_gen(9.99999, 3, "10") + expect_gen(9.99999, 4, "10") + expect_gen(9.99999, 5, "10") + expect_gen(9.99999, 6, "9.99999") + + expect_gen(9.9999, 1, "1e+1") + expect_gen(9.9999, 2, "10") + expect_gen(9.9999, 3, "10") + expect_gen(9.9999, 4, "10") + expect_gen(9.9999, 5, "9.9999") + expect_gen(9.9999, 6, "9.9999") + + expect_gen(9.999, 1, "1e+1") + expect_gen(9.999, 2, "10") + expect_gen(9.999, 3, "10") + expect_gen(9.999, 4, "9.999") + expect_gen(9.999, 5, "9.999") + expect_gen(9.999, 6, "9.999") + + expect_gen(9.99, 1, "1e+1") + expect_gen(9.99, 2, "10") + expect_gen(9.99, 3, "9.99") + expect_gen(9.99, 4, "9.99") + expect_gen(9.99, 5, "9.99") + expect_gen(9.99, 6, "9.99") + + expect_gen(9.9, 1, "1e+1") + expect_gen(9.9, 2, "9.9") + expect_gen(9.9, 3, "9.9") + expect_gen(9.9, 4, "9.9") + expect_gen(9.9, 5, "9.9") + expect_gen(9.9, 6, "9.9") + + expect_gen(9.0, 1, "9") + expect_gen(9.0, 2, "9") + expect_gen(9.0, 3, "9") + expect_gen(9.0, 4, "9") + expect_gen(9.0, 5, "9") + expect_gen(9.0, 6, "9") + + expect_gen(0.999999, 1, "1") + expect_gen(0.999999, 2, "1") + expect_gen(0.999999, 3, "1") + expect_gen(0.999999, 4, "1") + expect_gen(0.999999, 5, "1") + expect_gen(0.999999, 6, "0.999999") + + expect_gen(0.99999, 1, "1") + expect_gen(0.99999, 2, "1") + expect_gen(0.99999, 3, "1") + expect_gen(0.99999, 4, "1") + expect_gen(0.99999, 5, "0.99999") + expect_gen(0.99999, 6, "0.99999") + + expect_gen(0.9999, 1, "1") + expect_gen(0.9999, 2, "1") + expect_gen(0.9999, 3, "1") + expect_gen(0.9999, 4, "0.9999") + expect_gen(0.9999, 5, "0.9999") + expect_gen(0.9999, 6, "0.9999") + + expect_gen(0.999, 1, "1") + expect_gen(0.999, 2, "1") + expect_gen(0.999, 3, "0.999") + expect_gen(0.999, 4, "0.999") + expect_gen(0.999, 5, "0.999") + expect_gen(0.999, 6, "0.999") + + expect_gen(0.99, 1, "1") + expect_gen(0.99, 2, "0.99") + expect_gen(0.99, 3, "0.99") + expect_gen(0.99, 4, "0.99") + expect_gen(0.99, 5, "0.99") + expect_gen(0.99, 6, "0.99") + + expect_gen(0.9, 1, "0.9") + expect_gen(0.9, 2, "0.9") + expect_gen(0.9, 3, "0.9") + expect_gen(0.9, 4, "0.9") + expect_gen(0.9, 5, "0.9") + expect_gen(0.9, 6, "0.9") + + expect_gen(0.0999999, 1, "0.1") + expect_gen(0.0999999, 2, "0.1") + expect_gen(0.0999999, 3, "0.1") + expect_gen(0.0999999, 4, "0.1") + expect_gen(0.0999999, 5, "0.1") + expect_gen(0.0999999, 6, "0.0999999") + + expect_gen(0.099999, 1, "0.1") + expect_gen(0.099999, 2, "0.1") + expect_gen(0.099999, 3, "0.1") + expect_gen(0.099999, 4, "0.1") + expect_gen(0.099999, 5, "0.099999") + expect_gen(0.099999, 6, "0.099999") + + expect_gen(0.09999, 1, "0.1") + expect_gen(0.09999, 2, "0.1") + expect_gen(0.09999, 3, "0.1") + expect_gen(0.09999, 4, "0.09999") + expect_gen(0.09999, 5, "0.09999") + expect_gen(0.09999, 6, "0.09999") + + expect_gen(0.0999, 1, "0.1") + expect_gen(0.0999, 2, "0.1") + expect_gen(0.0999, 3, "0.0999") + expect_gen(0.0999, 4, "0.0999") + expect_gen(0.0999, 5, "0.0999") + expect_gen(0.0999, 6, "0.0999") + + expect_gen(0.099, 1, "0.1") + expect_gen(0.099, 2, "0.099") + expect_gen(0.099, 3, "0.099") + expect_gen(0.099, 4, "0.099") + expect_gen(0.099, 5, "0.099") + expect_gen(0.099, 6, "0.099") + + expect_gen(0.09, 1, "0.09") + expect_gen(0.09, 2, "0.09") + expect_gen(0.09, 3, "0.09") + expect_gen(0.09, 4, "0.09") + expect_gen(0.09, 5, "0.09") + expect_gen(0.09, 6, "0.09") + + expect_gen(0.00999999, 1, "0.01") + expect_gen(0.00999999, 2, "0.01") + expect_gen(0.00999999, 3, "0.01") + expect_gen(0.00999999, 4, "0.01") + expect_gen(0.00999999, 5, "0.01") + expect_gen(0.00999999, 6, "0.00999999") + + expect_gen(0.0099999, 1, "0.01") + expect_gen(0.0099999, 2, "0.01") + expect_gen(0.0099999, 3, "0.01") + expect_gen(0.0099999, 4, "0.01") + expect_gen(0.0099999, 5, "0.0099999") + expect_gen(0.0099999, 6, "0.0099999") + + expect_gen(0.009999, 1, "0.01") + expect_gen(0.009999, 2, "0.01") + expect_gen(0.009999, 3, "0.01") + expect_gen(0.009999, 4, "0.009999") + expect_gen(0.009999, 5, "0.009999") + expect_gen(0.009999, 6, "0.009999") + + expect_gen(0.00999, 1, "0.01") + expect_gen(0.00999, 2, "0.01") + expect_gen(0.00999, 3, "0.00999") + expect_gen(0.00999, 4, "0.00999") + expect_gen(0.00999, 5, "0.00999") + expect_gen(0.00999, 6, "0.00999") + + expect_gen(0.0099, 1, "0.01") + expect_gen(0.0099, 2, "0.0099") + expect_gen(0.0099, 3, "0.0099") + expect_gen(0.0099, 4, "0.0099") + expect_gen(0.0099, 5, "0.0099") + expect_gen(0.0099, 6, "0.0099") + + expect_gen(0.009, 1, "0.009") + expect_gen(0.009, 2, "0.009") + expect_gen(0.009, 3, "0.009") + expect_gen(0.009, 4, "0.009") + expect_gen(0.009, 5, "0.009") + expect_gen(0.009, 6, "0.009") + + expect_gen(0.000999999, 1, "0.001") + expect_gen(0.000999999, 2, "0.001") + expect_gen(0.000999999, 3, "0.001") + expect_gen(0.000999999, 4, "0.001") + expect_gen(0.000999999, 5, "0.001") + expect_gen(0.000999999, 6, "0.000999999") + + expect_gen(0.00099999, 1, "0.001") + expect_gen(0.00099999, 2, "0.001") + expect_gen(0.00099999, 3, "0.001") + expect_gen(0.00099999, 4, "0.001") + expect_gen(0.00099999, 5, "0.00099999") + expect_gen(0.00099999, 6, "0.00099999") + + expect_gen(0.0009999, 1, "0.001") + expect_gen(0.0009999, 2, "0.001") + expect_gen(0.0009999, 3, "0.001") + expect_gen(0.0009999, 4, "0.0009999") + expect_gen(0.0009999, 5, "0.0009999") + expect_gen(0.0009999, 6, "0.0009999") + + expect_gen(0.000999, 1, "0.001") + expect_gen(0.000999, 2, "0.001") + expect_gen(0.000999, 3, "0.000999") + expect_gen(0.000999, 4, "0.000999") + expect_gen(0.000999, 5, "0.000999") + expect_gen(0.000999, 6, "0.000999") + + expect_gen(0.00099, 1, "0.001") + expect_gen(0.00099, 2, "0.00099") + expect_gen(0.00099, 3, "0.00099") + expect_gen(0.00099, 4, "0.00099") + expect_gen(0.00099, 5, "0.00099") + expect_gen(0.00099, 6, "0.00099") + + expect_gen(0.0009, 1, "0.0009") + expect_gen(0.0009, 2, "0.0009") + expect_gen(0.0009, 3, "0.0009") + expect_gen(0.0009, 4, "0.0009") + expect_gen(0.0009, 5, "0.0009") + expect_gen(0.0009, 6, "0.0009") + + # Having a scientific exponent X == -5 triggers scientific notation. + # If rounding adjusts this to X == -4, then fixed notation will be selected. + expect_gen(0.0000999999, 1, "0.0001") + expect_gen(0.0000999999, 2, "0.0001") + expect_gen(0.0000999999, 3, "0.0001") + expect_gen(0.0000999999, 4, "0.0001") + expect_gen(0.0000999999, 5, "0.0001") + expect_gen(0.0000999999, 6, "9.99999e-5") + + expect_gen(0.000099999, 1, "0.0001") + expect_gen(0.000099999, 2, "0.0001") + expect_gen(0.000099999, 3, "0.0001") + expect_gen(0.000099999, 4, "0.0001") + expect_gen(0.000099999, 5, "9.9999e-5") + expect_gen(0.000099999, 6, "9.9999e-5") + + expect_gen(0.00009999, 1, "0.0001") + expect_gen(0.00009999, 2, "0.0001") + expect_gen(0.00009999, 3, "0.0001") + expect_gen(0.00009999, 4, "9.999e-5") + expect_gen(0.00009999, 5, "9.999e-5") + expect_gen(0.00009999, 6, "9.999e-5") + + expect_gen(0.0000999, 1, "0.0001") + expect_gen(0.0000999, 2, "0.0001") + expect_gen(0.0000999, 3, "9.99e-5") + expect_gen(0.0000999, 4, "9.99e-5") + expect_gen(0.0000999, 5, "9.99e-5") + expect_gen(0.0000999, 6, "9.99e-5") + + expect_gen(0.000099, 1, "0.0001") + expect_gen(0.000099, 2, "9.9e-5") + expect_gen(0.000099, 3, "9.9e-5") + expect_gen(0.000099, 4, "9.9e-5") + expect_gen(0.000099, 5, "9.9e-5") + expect_gen(0.000099, 6, "9.9e-5") + + expect_gen(0.00009, 1, "9e-5") + expect_gen(0.00009, 2, "9e-5") + expect_gen(0.00009, 3, "9e-5") + expect_gen(0.00009, 4, "9e-5") + expect_gen(0.00009, 5, "9e-5") + expect_gen(0.00009, 6, "9e-5") + + # Rounding test cases without exponent-adjusting behavior. + expect_gen(2999.999, 1, "3e+3") + expect_gen(2999.999, 2, "3e+3") + expect_gen(2999.999, 3, "3e+3") + expect_gen(2999.999, 4, "3000") + expect_gen(2999.999, 5, "3000") + expect_gen(2999.999, 6, "3000") + + expect_gen(2999.99, 1, "3e+3") + expect_gen(2999.99, 2, "3e+3") + expect_gen(2999.99, 3, "3e+3") + expect_gen(2999.99, 4, "3000") + expect_gen(2999.99, 5, "3000") + expect_gen(2999.99, 6, "2999.99") + + expect_gen(2999.9, 1, "3e+3") + expect_gen(2999.9, 2, "3e+3") + expect_gen(2999.9, 3, "3e+3") + expect_gen(2999.9, 4, "3000") + expect_gen(2999.9, 5, "2999.9") + expect_gen(2999.9, 6, "2999.9") + + expect_gen(2999.0, 1, "3e+3") + expect_gen(2999.0, 2, "3e+3") + expect_gen(2999.0, 3, "3e+3") + expect_gen(2999.0, 4, "2999") + expect_gen(2999.0, 5, "2999") + expect_gen(2999.0, 6, "2999") + + expect_gen(299.999, 1, "3e+2") + expect_gen(299.999, 2, "3e+2") + expect_gen(299.999, 3, "300") + expect_gen(299.999, 4, "300") + expect_gen(299.999, 5, "300") + expect_gen(299.999, 6, "299.999") + + expect_gen(299.99, 1, "3e+2") + expect_gen(299.99, 2, "3e+2") + expect_gen(299.99, 3, "300") + expect_gen(299.99, 4, "300") + expect_gen(299.99, 5, "299.99") + expect_gen(299.99, 6, "299.99") + + expect_gen(299.9, 1, "3e+2") + expect_gen(299.9, 2, "3e+2") + expect_gen(299.9, 3, "300") + expect_gen(299.9, 4, "299.9") + expect_gen(299.9, 5, "299.9") + expect_gen(299.9, 6, "299.9") + + expect_gen(299.0, 1, "3e+2") + expect_gen(299.0, 2, "3e+2") + expect_gen(299.0, 3, "299") + expect_gen(299.0, 4, "299") + expect_gen(299.0, 5, "299") + expect_gen(299.0, 6, "299") + + expect_gen(29.999, 1, "3e+1") + expect_gen(29.999, 2, "30") + expect_gen(29.999, 3, "30") + expect_gen(29.999, 4, "30") + expect_gen(29.999, 5, "29.999") + expect_gen(29.999, 6, "29.999") + + expect_gen(29.99, 1, "3e+1") + expect_gen(29.99, 2, "30") + expect_gen(29.99, 3, "30") + expect_gen(29.99, 4, "29.99") + expect_gen(29.99, 5, "29.99") + expect_gen(29.99, 6, "29.99") + + expect_gen(29.9, 1, "3e+1") + expect_gen(29.9, 2, "30") + expect_gen(29.9, 3, "29.9") + expect_gen(29.9, 4, "29.9") + expect_gen(29.9, 5, "29.9") + expect_gen(29.9, 6, "29.9") + + expect_gen(29.0, 1, "3e+1") + expect_gen(29.0, 2, "29") + expect_gen(29.0, 3, "29") + expect_gen(29.0, 4, "29") + expect_gen(29.0, 5, "29") + expect_gen(29.0, 6, "29") + + expect_gen(2.999, 1, "3") + expect_gen(2.999, 2, "3") + expect_gen(2.999, 3, "3") + expect_gen(2.999, 4, "2.999") + expect_gen(2.999, 5, "2.999") + expect_gen(2.999, 6, "2.999") + + expect_gen(2.99, 1, "3") + expect_gen(2.99, 2, "3") + expect_gen(2.99, 3, "2.99") + expect_gen(2.99, 4, "2.99") + expect_gen(2.99, 5, "2.99") + expect_gen(2.99, 6, "2.99") + + expect_gen(2.9, 1, "3") + expect_gen(2.9, 2, "2.9") + expect_gen(2.9, 3, "2.9") + expect_gen(2.9, 4, "2.9") + expect_gen(2.9, 5, "2.9") + expect_gen(2.9, 6, "2.9") + + expect_gen(2.0, 1, "2") + expect_gen(2.0, 2, "2") + expect_gen(2.0, 3, "2") + expect_gen(2.0, 4, "2") + expect_gen(2.0, 5, "2") + expect_gen(2.0, 6, "2") + + expect_gen(0.2999, 1, "0.3") + expect_gen(0.2999, 2, "0.3") + expect_gen(0.2999, 3, "0.3") + expect_gen(0.2999, 4, "0.2999") + expect_gen(0.2999, 5, "0.2999") + expect_gen(0.2999, 6, "0.2999") + + expect_gen(0.299, 1, "0.3") + expect_gen(0.299, 2, "0.3") + expect_gen(0.299, 3, "0.299") + expect_gen(0.299, 4, "0.299") + expect_gen(0.299, 5, "0.299") + expect_gen(0.299, 6, "0.299") + + expect_gen(0.29, 1, "0.3") + expect_gen(0.29, 2, "0.29") + expect_gen(0.29, 3, "0.29") + expect_gen(0.29, 4, "0.29") + expect_gen(0.29, 5, "0.29") + expect_gen(0.29, 6, "0.29") + + expect_gen(0.2, 1, "0.2") + expect_gen(0.2, 2, "0.2") + expect_gen(0.2, 3, "0.2") + expect_gen(0.2, 4, "0.2") + expect_gen(0.2, 5, "0.2") + expect_gen(0.2, 6, "0.2") + + expect_gen(0.02999, 1, "0.03") + expect_gen(0.02999, 2, "0.03") + expect_gen(0.02999, 3, "0.03") + expect_gen(0.02999, 4, "0.02999") + expect_gen(0.02999, 5, "0.02999") + expect_gen(0.02999, 6, "0.02999") + + expect_gen(0.0299, 1, "0.03") + expect_gen(0.0299, 2, "0.03") + expect_gen(0.0299, 3, "0.0299") + expect_gen(0.0299, 4, "0.0299") + expect_gen(0.0299, 5, "0.0299") + expect_gen(0.0299, 6, "0.0299") + + expect_gen(0.029, 1, "0.03") + expect_gen(0.029, 2, "0.029") + expect_gen(0.029, 3, "0.029") + expect_gen(0.029, 4, "0.029") + expect_gen(0.029, 5, "0.029") + expect_gen(0.029, 6, "0.029") + + expect_gen(0.02, 1, "0.02") + expect_gen(0.02, 2, "0.02") + expect_gen(0.02, 3, "0.02") + expect_gen(0.02, 4, "0.02") + expect_gen(0.02, 5, "0.02") + expect_gen(0.02, 6, "0.02") + + expect_gen(0.002999, 1, "0.003") + expect_gen(0.002999, 2, "0.003") + expect_gen(0.002999, 3, "0.003") + expect_gen(0.002999, 4, "0.002999") + expect_gen(0.002999, 5, "0.002999") + expect_gen(0.002999, 6, "0.002999") + + expect_gen(0.00299, 1, "0.003") + expect_gen(0.00299, 2, "0.003") + expect_gen(0.00299, 3, "0.00299") + expect_gen(0.00299, 4, "0.00299") + expect_gen(0.00299, 5, "0.00299") + expect_gen(0.00299, 6, "0.00299") + + expect_gen(0.0029, 1, "0.003") + expect_gen(0.0029, 2, "0.0029") + expect_gen(0.0029, 3, "0.0029") + expect_gen(0.0029, 4, "0.0029") + expect_gen(0.0029, 5, "0.0029") + expect_gen(0.0029, 6, "0.0029") + + expect_gen(0.002, 1, "0.002") + expect_gen(0.002, 2, "0.002") + expect_gen(0.002, 3, "0.002") + expect_gen(0.002, 4, "0.002") + expect_gen(0.002, 5, "0.002") + expect_gen(0.002, 6, "0.002") + + expect_gen(0.0002999, 1, "0.0003") + expect_gen(0.0002999, 2, "0.0003") + expect_gen(0.0002999, 3, "0.0003") + expect_gen(0.0002999, 4, "0.0002999") + expect_gen(0.0002999, 5, "0.0002999") + expect_gen(0.0002999, 6, "0.0002999") + + expect_gen(0.000299, 1, "0.0003") + expect_gen(0.000299, 2, "0.0003") + expect_gen(0.000299, 3, "0.000299") + expect_gen(0.000299, 4, "0.000299") + expect_gen(0.000299, 5, "0.000299") + expect_gen(0.000299, 6, "0.000299") + + expect_gen(0.00029, 1, "0.0003") + expect_gen(0.00029, 2, "0.00029") + expect_gen(0.00029, 3, "0.00029") + expect_gen(0.00029, 4, "0.00029") + expect_gen(0.00029, 5, "0.00029") + expect_gen(0.00029, 6, "0.00029") + + expect_gen(0.0002, 1, "0.0002") + expect_gen(0.0002, 2, "0.0002") + expect_gen(0.0002, 3, "0.0002") + expect_gen(0.0002, 4, "0.0002") + expect_gen(0.0002, 5, "0.0002") + expect_gen(0.0002, 6, "0.0002") + + expect_gen(0.00002999, 1, "3e-5") + expect_gen(0.00002999, 2, "3e-5") + expect_gen(0.00002999, 3, "3e-5") + expect_gen(0.00002999, 4, "2.999e-5") + expect_gen(0.00002999, 5, "2.999e-5") + expect_gen(0.00002999, 6, "2.999e-5") + + expect_gen(0.0000299, 1, "3e-5") + expect_gen(0.0000299, 2, "3e-5") + expect_gen(0.0000299, 3, "2.99e-5") + expect_gen(0.0000299, 4, "2.99e-5") + expect_gen(0.0000299, 5, "2.99e-5") + expect_gen(0.0000299, 6, "2.99e-5") + + expect_gen(0.000029, 1, "3e-5") + expect_gen(0.000029, 2, "2.9e-5") + expect_gen(0.000029, 3, "2.9e-5") + expect_gen(0.000029, 4, "2.9e-5") + expect_gen(0.000029, 5, "2.9e-5") + expect_gen(0.000029, 6, "2.9e-5") + + expect_gen(0.00002, 1, "2e-5") + expect_gen(0.00002, 2, "2e-5") + expect_gen(0.00002, 3, "2e-5") + expect_gen(0.00002, 4, "2e-5") + expect_gen(0.00002, 5, "2e-5") + expect_gen(0.00002, 6, "2e-5") + end + + it "transitions between values of the scientific exponent X" do + {% for tc in GEN_TRANSITIONS %} + expect_gen(hexfloat({{ tc[0] }}), {{ tc[1] }}, {{ tc[2] }}, file: {{ tc.filename }}, line: {{ tc.line_number }}) + {% end %} + end + + it "UCRT had trouble with rounding this value" do + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 105, "109995565999999994887854821710219658911365648587951921896774663603198787416706536331386569598149846892544") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 19, "1.099955659999999949e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 18, "1.09995565999999995e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 17, "1.0999556599999999e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 16, "1.09995566e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 15, "1.09995566e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 14, "1.09995566e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 13, "1.09995566e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 12, "1.09995566e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 11, "1.09995566e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 10, "1.09995566e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 9, "1.09995566e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 8, "1.0999557e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 7, "1.099956e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 6, "1.09996e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 5, "1.1e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 4, "1.1e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 3, "1.1e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 2, "1.1e+104") + expect_gen(hexfloat("0x1.88e2d605edc3dp+345"), 1, "1e+104") + end + + it "more cases that the UCRT had trouble with (e.g. DevCom-1093399)" do + expect_gen(hexfloat("0x1.8p+62"), 17, "6.9175290276410819e+18") + expect_gen(hexfloat("0x1.0a2742p+17"), 6, "136271") + expect_gen(hexfloat("0x1.f8b0f962cdffbp+205"), 14, "1.0137595739223e+62") + expect_gen(hexfloat("0x1.f8b0f962cdffbp+205"), 17, "1.0137595739222531e+62") + expect_gen(hexfloat("0x1.f8b0f962cdffbp+205"), 51, "1.01375957392225305727423222620636224221808910954041e+62") + expect_gen(hexfloat("0x1.f8b0f962cdffbp+205"), 55, "1.013759573922253057274232226206362242218089109540405973e+62") + end + end end diff --git a/spec/std/float_printer/ryu_printf_test_cases.cr b/spec/std/float_printer/ryu_printf_test_cases.cr index 6c1a3a155692..32448af3bc4d 100644 --- a/spec/std/float_printer/ryu_printf_test_cases.cr +++ b/spec/std/float_printer/ryu_printf_test_cases.cr @@ -2695,3 +2695,1673 @@ ALL_BINARY_EXPONENTS = [ {6.123699292104195e+307, 0, 307}, {1.7026387749989901e+308, 0, 308}, ] of _ + +# For brevity, we avoid testing all possible combinations of P and X. Instead, we test: +# * All values of P where some X can be affected by rounding. (For double, this is [1, 15].) +# * P == 25, which is arbitrary. +# * P == numeric_limits::max_exponent10 + 1. This selects fixed notation for numeric_limits::max(), +# so it's the largest interesting value of P. +# * Finally, we test the transitions around X == P - 1, ensuring that we can recognize every value of X. +GEN_TRANSITIONS = [ + {"0x1.8e757928e0c9dp-14", 1, "9e-5"}, + {"0x1.8e757928e0c9ep-14", 1, "0.0001"}, + {"0x1.f212d77318fc5p-11", 1, "0.0009"}, + {"0x1.f212d77318fc6p-11", 1, "0.001"}, + {"0x1.374bc6a7ef9dbp-7", 1, "0.009"}, + {"0x1.374bc6a7ef9dcp-7", 1, "0.01"}, + {"0x1.851eb851eb851p-4", 1, "0.09"}, + {"0x1.851eb851eb852p-4", 1, "0.1"}, + {"0x1.e666666666666p-1", 1, "0.9"}, + {"0x1.e666666666667p-1", 1, "1"}, + {"0x1.2ffffffffffffp+3", 1, "9"}, + {"0x1.3000000000000p+3", 1, "1e+1"}, + {"0x1.a1554fbdad751p-14", 2, "9.9e-5"}, + {"0x1.a1554fbdad752p-14", 2, "0.0001"}, + {"0x1.04d551d68c692p-10", 2, "0.00099"}, + {"0x1.04d551d68c693p-10", 2, "0.001"}, + {"0x1.460aa64c2f837p-7", 2, "0.0099"}, + {"0x1.460aa64c2f838p-7", 2, "0.01"}, + {"0x1.978d4fdf3b645p-4", 2, "0.099"}, + {"0x1.978d4fdf3b646p-4", 2, "0.1"}, + {"0x1.fd70a3d70a3d7p-1", 2, "0.99"}, + {"0x1.fd70a3d70a3d8p-1", 2, "1"}, + {"0x1.3e66666666666p+3", 2, "9.9"}, + {"0x1.3e66666666667p+3", 2, "10"}, + {"0x1.8dfffffffffffp+6", 2, "99"}, + {"0x1.8e00000000000p+6", 2, "1e+2"}, + {"0x1.a3387ecc8eb96p-14", 3, "9.99e-5"}, + {"0x1.a3387ecc8eb97p-14", 3, "0.0001"}, + {"0x1.06034f3fd933ep-10", 3, "0.000999"}, + {"0x1.06034f3fd933fp-10", 3, "0.001"}, + {"0x1.4784230fcf80dp-7", 3, "0.00999"}, + {"0x1.4784230fcf80ep-7", 3, "0.01"}, + {"0x1.99652bd3c3611p-4", 3, "0.0999"}, + {"0x1.99652bd3c3612p-4", 3, "0.1"}, + {"0x1.ffbe76c8b4395p-1", 3, "0.999"}, + {"0x1.ffbe76c8b4396p-1", 3, "1"}, + {"0x1.3fd70a3d70a3dp+3", 3, "9.99"}, + {"0x1.3fd70a3d70a3ep+3", 3, "10"}, + {"0x1.8fcccccccccccp+6", 3, "99.9"}, + {"0x1.8fccccccccccdp+6", 3, "100"}, + {"0x1.f3bffffffffffp+9", 3, "999"}, + {"0x1.f3c0000000000p+9", 3, "1e+3"}, + {"0x1.a368d04e0ba6ap-14", 4, "9.999e-5"}, + {"0x1.a368d04e0ba6bp-14", 4, "0.0001"}, + {"0x1.06218230c7482p-10", 4, "0.0009999"}, + {"0x1.06218230c7483p-10", 4, "0.001"}, + {"0x1.47a9e2bcf91a3p-7", 4, "0.009999"}, + {"0x1.47a9e2bcf91a4p-7", 4, "0.01"}, + {"0x1.99945b6c3760bp-4", 4, "0.09999"}, + {"0x1.99945b6c3760cp-4", 4, "0.1"}, + {"0x1.fff972474538ep-1", 4, "0.9999"}, + {"0x1.fff972474538fp-1", 4, "1"}, + {"0x1.3ffbe76c8b439p+3", 4, "9.999"}, + {"0x1.3ffbe76c8b43ap+3", 4, "10"}, + {"0x1.8ffae147ae147p+6", 4, "99.99"}, + {"0x1.8ffae147ae148p+6", 4, "100"}, + {"0x1.f3f9999999999p+9", 4, "999.9"}, + {"0x1.f3f999999999ap+9", 4, "1000"}, + {"0x1.387bfffffffffp+13", 4, "9999"}, + {"0x1.387c000000000p+13", 4, "1e+4"}, + {"0x1.a36da54164f19p-14", 5, "9.9999e-5"}, + {"0x1.a36da54164f1ap-14", 5, "0.0001"}, + {"0x1.06248748df16fp-10", 5, "0.00099999"}, + {"0x1.06248748df170p-10", 5, "0.001"}, + {"0x1.47ada91b16dcbp-7", 5, "0.0099999"}, + {"0x1.47ada91b16dccp-7", 5, "0.01"}, + {"0x1.99991361dc93ep-4", 5, "0.099999"}, + {"0x1.99991361dc93fp-4", 5, "0.1"}, + {"0x1.ffff583a53b8ep-1", 5, "0.99999"}, + {"0x1.ffff583a53b8fp-1", 5, "1"}, + {"0x1.3fff972474538p+3", 5, "9.9999"}, + {"0x1.3fff972474539p+3", 5, "10"}, + {"0x1.8fff7ced91687p+6", 5, "99.999"}, + {"0x1.8fff7ced91688p+6", 5, "100"}, + {"0x1.f3ff5c28f5c28p+9", 5, "999.99"}, + {"0x1.f3ff5c28f5c29p+9", 5, "1000"}, + {"0x1.387f999999999p+13", 5, "9999.9"}, + {"0x1.387f99999999ap+13", 5, "10000"}, + {"0x1.869f7ffffffffp+16", 5, "99999"}, + {"0x1.869f800000000p+16", 5, "1e+5"}, + {"0x1.a36e20f35445dp-14", 6, "9.99999e-5"}, + {"0x1.a36e20f35445ep-14", 6, "0.0001"}, + {"0x1.0624d49814abap-10", 6, "0.000999999"}, + {"0x1.0624d49814abbp-10", 6, "0.001"}, + {"0x1.47ae09be19d69p-7", 6, "0.00999999"}, + {"0x1.47ae09be19d6ap-7", 6, "0.01"}, + {"0x1.99998c2da04c3p-4", 6, "0.0999999"}, + {"0x1.99998c2da04c4p-4", 6, "0.1"}, + {"0x1.ffffef39085f4p-1", 6, "0.999999"}, + {"0x1.ffffef39085f5p-1", 6, "1"}, + {"0x1.3ffff583a53b8p+3", 6, "9.99999"}, + {"0x1.3ffff583a53b9p+3", 6, "10"}, + {"0x1.8ffff2e48e8a7p+6", 6, "99.9999"}, + {"0x1.8ffff2e48e8a8p+6", 6, "100"}, + {"0x1.f3ffef9db22d0p+9", 6, "999.999"}, + {"0x1.f3ffef9db22d1p+9", 6, "1000"}, + {"0x1.387ff5c28f5c2p+13", 6, "9999.99"}, + {"0x1.387ff5c28f5c3p+13", 6, "10000"}, + {"0x1.869ff33333333p+16", 6, "99999.9"}, + {"0x1.869ff33333334p+16", 6, "100000"}, + {"0x1.e847effffffffp+19", 6, "999999"}, + {"0x1.e847f00000000p+19", 6, "1e+6"}, + {"0x1.a36e2d51ec34bp-14", 7, "9.999999e-5"}, + {"0x1.a36e2d51ec34cp-14", 7, "0.0001"}, + {"0x1.0624dc5333a0ep-10", 7, "0.0009999999"}, + {"0x1.0624dc5333a0fp-10", 7, "0.001"}, + {"0x1.47ae136800892p-7", 7, "0.009999999"}, + {"0x1.47ae136800893p-7", 7, "0.01"}, + {"0x1.9999984200ab7p-4", 7, "0.09999999"}, + {"0x1.9999984200ab8p-4", 7, "0.1"}, + {"0x1.fffffe5280d65p-1", 7, "0.9999999"}, + {"0x1.fffffe5280d66p-1", 7, "1"}, + {"0x1.3ffffef39085fp+3", 7, "9.999999"}, + {"0x1.3ffffef390860p+3", 7, "10"}, + {"0x1.8ffffeb074a77p+6", 7, "99.99999"}, + {"0x1.8ffffeb074a78p+6", 7, "100"}, + {"0x1.f3fffe5c91d14p+9", 7, "999.9999"}, + {"0x1.f3fffe5c91d15p+9", 7, "1000"}, + {"0x1.387ffef9db22dp+13", 7, "9999.999"}, + {"0x1.387ffef9db22ep+13", 7, "10000"}, + {"0x1.869ffeb851eb8p+16", 7, "99999.99"}, + {"0x1.869ffeb851eb9p+16", 7, "100000"}, + {"0x1.e847fe6666666p+19", 7, "999999.9"}, + {"0x1.e847fe6666667p+19", 7, "1000000"}, + {"0x1.312cfefffffffp+23", 7, "9999999"}, + {"0x1.312cff0000000p+23", 7, "1e+7"}, + {"0x1.a36e2e8e94ffcp-14", 8, "9.9999999e-5"}, + {"0x1.a36e2e8e94ffdp-14", 8, "0.0001"}, + {"0x1.0624dd191d1fdp-10", 8, "0.00099999999"}, + {"0x1.0624dd191d1fep-10", 8, "0.001"}, + {"0x1.47ae145f6467dp-7", 8, "0.0099999999"}, + {"0x1.47ae145f6467ep-7", 8, "0.01"}, + {"0x1.999999773d81cp-4", 8, "0.099999999"}, + {"0x1.999999773d81dp-4", 8, "0.1"}, + {"0x1.ffffffd50ce23p-1", 8, "0.99999999"}, + {"0x1.ffffffd50ce24p-1", 8, "1"}, + {"0x1.3fffffe5280d6p+3", 8, "9.9999999"}, + {"0x1.3fffffe5280d7p+3", 8, "10"}, + {"0x1.8fffffde7210bp+6", 8, "99.999999"}, + {"0x1.8fffffde7210cp+6", 8, "100"}, + {"0x1.f3ffffd60e94ep+9", 8, "999.99999"}, + {"0x1.f3ffffd60e94fp+9", 8, "1000"}, + {"0x1.387fffe5c91d1p+13", 8, "9999.9999"}, + {"0x1.387fffe5c91d2p+13", 8, "10000"}, + {"0x1.869fffdf3b645p+16", 8, "99999.999"}, + {"0x1.869fffdf3b646p+16", 8, "100000"}, + {"0x1.e847ffd70a3d7p+19", 8, "999999.99"}, + {"0x1.e847ffd70a3d8p+19", 8, "1000000"}, + {"0x1.312cffe666666p+23", 8, "9999999.9"}, + {"0x1.312cffe666667p+23", 8, "10000000"}, + {"0x1.7d783fdffffffp+26", 8, "99999999"}, + {"0x1.7d783fe000000p+26", 8, "1e+8"}, + {"0x1.a36e2eae3f7a7p-14", 9, "9.99999999e-5"}, + {"0x1.a36e2eae3f7a8p-14", 9, "0.0001"}, + {"0x1.0624dd2ce7ac8p-10", 9, "0.000999999999"}, + {"0x1.0624dd2ce7ac9p-10", 9, "0.001"}, + {"0x1.47ae14782197bp-7", 9, "0.00999999999"}, + {"0x1.47ae14782197cp-7", 9, "0.01"}, + {"0x1.9999999629fd9p-4", 9, "0.0999999999"}, + {"0x1.9999999629fdap-4", 9, "0.1"}, + {"0x1.fffffffbb47d0p-1", 9, "0.999999999"}, + {"0x1.fffffffbb47d1p-1", 9, "1"}, + {"0x1.3ffffffd50ce2p+3", 9, "9.99999999"}, + {"0x1.3ffffffd50ce3p+3", 9, "10"}, + {"0x1.8ffffffca501ap+6", 9, "99.9999999"}, + {"0x1.8ffffffca501bp+6", 9, "100"}, + {"0x1.f3fffffbce421p+9", 9, "999.999999"}, + {"0x1.f3fffffbce422p+9", 9, "1000"}, + {"0x1.387ffffd60e94p+13", 9, "9999.99999"}, + {"0x1.387ffffd60e95p+13", 9, "10000"}, + {"0x1.869ffffcb923ap+16", 9, "99999.9999"}, + {"0x1.869ffffcb923bp+16", 9, "100000"}, + {"0x1.e847fffbe76c8p+19", 9, "999999.999"}, + {"0x1.e847fffbe76c9p+19", 9, "1000000"}, + {"0x1.312cfffd70a3dp+23", 9, "9999999.99"}, + {"0x1.312cfffd70a3ep+23", 9, "10000000"}, + {"0x1.7d783ffccccccp+26", 9, "99999999.9"}, + {"0x1.7d783ffcccccdp+26", 9, "100000000"}, + {"0x1.dcd64ffbfffffp+29", 9, "999999999"}, + {"0x1.dcd64ffc00000p+29", 9, "1e+9"}, + {"0x1.a36e2eb16a205p-14", 10, "9.999999999e-5"}, + {"0x1.a36e2eb16a206p-14", 10, "0.0001"}, + {"0x1.0624dd2ee2543p-10", 10, "0.0009999999999"}, + {"0x1.0624dd2ee2544p-10", 10, "0.001"}, + {"0x1.47ae147a9ae94p-7", 10, "0.009999999999"}, + {"0x1.47ae147a9ae95p-7", 10, "0.01"}, + {"0x1.9999999941a39p-4", 10, "0.09999999999"}, + {"0x1.9999999941a3ap-4", 10, "0.1"}, + {"0x1.ffffffff920c8p-1", 10, "0.9999999999"}, + {"0x1.ffffffff920c9p-1", 10, "1"}, + {"0x1.3fffffffbb47dp+3", 10, "9.999999999"}, + {"0x1.3fffffffbb47ep+3", 10, "10"}, + {"0x1.8fffffffaa19cp+6", 10, "99.99999999"}, + {"0x1.8fffffffaa19dp+6", 10, "100"}, + {"0x1.f3ffffff94a03p+9", 10, "999.9999999"}, + {"0x1.f3ffffff94a04p+9", 10, "1000"}, + {"0x1.387fffffbce42p+13", 10, "9999.999999"}, + {"0x1.387fffffbce43p+13", 10, "10000"}, + {"0x1.869fffffac1d2p+16", 10, "99999.99999"}, + {"0x1.869fffffac1d3p+16", 10, "100000"}, + {"0x1.e847ffff97247p+19", 10, "999999.9999"}, + {"0x1.e847ffff97248p+19", 10, "1000000"}, + {"0x1.312cffffbe76cp+23", 10, "9999999.999"}, + {"0x1.312cffffbe76dp+23", 10, "10000000"}, + {"0x1.7d783fffae147p+26", 10, "99999999.99"}, + {"0x1.7d783fffae148p+26", 10, "100000000"}, + {"0x1.dcd64fff99999p+29", 10, "999999999.9"}, + {"0x1.dcd64fff9999ap+29", 10, "1000000000"}, + {"0x1.2a05f1ffbffffp+33", 10, "9999999999"}, + {"0x1.2a05f1ffc0000p+33", 10, "1e+10"}, + {"0x1.a36e2eb1bb30fp-14", 11, "9.9999999999e-5"}, + {"0x1.a36e2eb1bb310p-14", 11, "0.0001"}, + {"0x1.0624dd2f14fe9p-10", 11, "0.00099999999999"}, + {"0x1.0624dd2f14feap-10", 11, "0.001"}, + {"0x1.47ae147ada3e3p-7", 11, "0.0099999999999"}, + {"0x1.47ae147ada3e4p-7", 11, "0.01"}, + {"0x1.9999999990cdcp-4", 11, "0.099999999999"}, + {"0x1.9999999990cddp-4", 11, "0.1"}, + {"0x1.fffffffff5014p-1", 11, "0.99999999999"}, + {"0x1.fffffffff5015p-1", 11, "1"}, + {"0x1.3ffffffff920cp+3", 11, "9.9999999999"}, + {"0x1.3ffffffff920dp+3", 11, "10"}, + {"0x1.8ffffffff768fp+6", 11, "99.999999999"}, + {"0x1.8ffffffff7690p+6", 11, "100"}, + {"0x1.f3fffffff5433p+9", 11, "999.99999999"}, + {"0x1.f3fffffff5434p+9", 11, "1000"}, + {"0x1.387ffffff94a0p+13", 11, "9999.9999999"}, + {"0x1.387ffffff94a1p+13", 11, "10000"}, + {"0x1.869ffffff79c8p+16", 11, "99999.999999"}, + {"0x1.869ffffff79c9p+16", 11, "100000"}, + {"0x1.e847fffff583ap+19", 11, "999999.99999"}, + {"0x1.e847fffff583bp+19", 11, "1000000"}, + {"0x1.312cfffff9724p+23", 11, "9999999.9999"}, + {"0x1.312cfffff9725p+23", 11, "10000000"}, + {"0x1.7d783ffff7cedp+26", 11, "99999999.999"}, + {"0x1.7d783ffff7ceep+26", 11, "100000000"}, + {"0x1.dcd64ffff5c28p+29", 11, "999999999.99"}, + {"0x1.dcd64ffff5c29p+29", 11, "1000000000"}, + {"0x1.2a05f1fff9999p+33", 11, "9999999999.9"}, + {"0x1.2a05f1fff999ap+33", 11, "10000000000"}, + {"0x1.74876e7ff7fffp+36", 11, "99999999999"}, + {"0x1.74876e7ff8000p+36", 11, "1e+11"}, + {"0x1.a36e2eb1c34c3p-14", 12, "9.99999999999e-5"}, + {"0x1.a36e2eb1c34c4p-14", 12, "0.0001"}, + {"0x1.0624dd2f1a0fap-10", 12, "0.000999999999999"}, + {"0x1.0624dd2f1a0fbp-10", 12, "0.001"}, + {"0x1.47ae147ae0938p-7", 12, "0.00999999999999"}, + {"0x1.47ae147ae0939p-7", 12, "0.01"}, + {"0x1.9999999998b86p-4", 12, "0.0999999999999"}, + {"0x1.9999999998b87p-4", 12, "0.1"}, + {"0x1.fffffffffee68p-1", 12, "0.999999999999"}, + {"0x1.fffffffffee69p-1", 12, "1"}, + {"0x1.3fffffffff501p+3", 12, "9.99999999999"}, + {"0x1.3fffffffff502p+3", 12, "10"}, + {"0x1.8fffffffff241p+6", 12, "99.9999999999"}, + {"0x1.8fffffffff242p+6", 12, "100"}, + {"0x1.f3fffffffeed1p+9", 12, "999.999999999"}, + {"0x1.f3fffffffeed2p+9", 12, "1000"}, + {"0x1.387fffffff543p+13", 12, "9999.99999999"}, + {"0x1.387fffffff544p+13", 12, "10000"}, + {"0x1.869fffffff294p+16", 12, "99999.9999999"}, + {"0x1.869fffffff295p+16", 12, "100000"}, + {"0x1.e847fffffef39p+19", 12, "999999.999999"}, + {"0x1.e847fffffef3ap+19", 12, "1000000"}, + {"0x1.312cffffff583p+23", 12, "9999999.99999"}, + {"0x1.312cffffff584p+23", 12, "10000000"}, + {"0x1.7d783fffff2e4p+26", 12, "99999999.9999"}, + {"0x1.7d783fffff2e5p+26", 12, "100000000"}, + {"0x1.dcd64ffffef9dp+29", 12, "999999999.999"}, + {"0x1.dcd64ffffef9ep+29", 12, "1000000000"}, + {"0x1.2a05f1ffff5c2p+33", 12, "9999999999.99"}, + {"0x1.2a05f1ffff5c3p+33", 12, "10000000000"}, + {"0x1.74876e7fff333p+36", 12, "99999999999.9"}, + {"0x1.74876e7fff334p+36", 12, "100000000000"}, + {"0x1.d1a94a1ffefffp+39", 12, "999999999999"}, + {"0x1.d1a94a1fff000p+39", 12, "1e+12"}, + {"0x1.a36e2eb1c41bbp-14", 13, "9.999999999999e-5"}, + {"0x1.a36e2eb1c41bcp-14", 13, "0.0001"}, + {"0x1.0624dd2f1a915p-10", 13, "0.0009999999999999"}, + {"0x1.0624dd2f1a916p-10", 13, "0.001"}, + {"0x1.47ae147ae135ap-7", 13, "0.009999999999999"}, + {"0x1.47ae147ae135bp-7", 13, "0.01"}, + {"0x1.9999999999831p-4", 13, "0.09999999999999"}, + {"0x1.9999999999832p-4", 13, "0.1"}, + {"0x1.ffffffffffe3dp-1", 13, "0.9999999999999"}, + {"0x1.ffffffffffe3ep-1", 13, "1"}, + {"0x1.3fffffffffee6p+3", 13, "9.999999999999"}, + {"0x1.3fffffffffee7p+3", 13, "10"}, + {"0x1.8fffffffffea0p+6", 13, "99.99999999999"}, + {"0x1.8fffffffffea1p+6", 13, "100"}, + {"0x1.f3ffffffffe48p+9", 13, "999.9999999999"}, + {"0x1.f3ffffffffe49p+9", 13, "1000"}, + {"0x1.387fffffffeedp+13", 13, "9999.999999999"}, + {"0x1.387fffffffeeep+13", 13, "10000"}, + {"0x1.869fffffffea8p+16", 13, "99999.99999999"}, + {"0x1.869fffffffea9p+16", 13, "100000"}, + {"0x1.e847ffffffe52p+19", 13, "999999.9999999"}, + {"0x1.e847ffffffe53p+19", 13, "1000000"}, + {"0x1.312cffffffef3p+23", 13, "9999999.999999"}, + {"0x1.312cffffffef4p+23", 13, "10000000"}, + {"0x1.7d783fffffeb0p+26", 13, "99999999.99999"}, + {"0x1.7d783fffffeb1p+26", 13, "100000000"}, + {"0x1.dcd64fffffe5cp+29", 13, "999999999.9999"}, + {"0x1.dcd64fffffe5dp+29", 13, "1000000000"}, + {"0x1.2a05f1ffffef9p+33", 13, "9999999999.999"}, + {"0x1.2a05f1ffffefap+33", 13, "10000000000"}, + {"0x1.74876e7fffeb8p+36", 13, "99999999999.99"}, + {"0x1.74876e7fffeb9p+36", 13, "100000000000"}, + {"0x1.d1a94a1fffe66p+39", 13, "999999999999.9"}, + {"0x1.d1a94a1fffe67p+39", 13, "1000000000000"}, + {"0x1.2309ce53ffeffp+43", 13, "9999999999999"}, + {"0x1.2309ce53fff00p+43", 13, "1e+13"}, + {"0x1.a36e2eb1c4307p-14", 14, "9.9999999999999e-5"}, + {"0x1.a36e2eb1c4308p-14", 14, "0.0001"}, + {"0x1.0624dd2f1a9e4p-10", 14, "0.00099999999999999"}, + {"0x1.0624dd2f1a9e5p-10", 14, "0.001"}, + {"0x1.47ae147ae145ep-7", 14, "0.0099999999999999"}, + {"0x1.47ae147ae145fp-7", 14, "0.01"}, + {"0x1.9999999999975p-4", 14, "0.099999999999999"}, + {"0x1.9999999999976p-4", 14, "0.1"}, + {"0x1.fffffffffffd2p-1", 14, "0.99999999999999"}, + {"0x1.fffffffffffd3p-1", 14, "1"}, + {"0x1.3ffffffffffe3p+3", 14, "9.9999999999999"}, + {"0x1.3ffffffffffe4p+3", 14, "10"}, + {"0x1.8ffffffffffdcp+6", 14, "99.999999999999"}, + {"0x1.8ffffffffffddp+6", 14, "100"}, + {"0x1.f3fffffffffd4p+9", 14, "999.99999999999"}, + {"0x1.f3fffffffffd5p+9", 14, "1000"}, + {"0x1.387ffffffffe4p+13", 14, "9999.9999999999"}, + {"0x1.387ffffffffe5p+13", 14, "10000"}, + {"0x1.869ffffffffddp+16", 14, "99999.999999999"}, + {"0x1.869ffffffffdep+16", 14, "100000"}, + {"0x1.e847fffffffd5p+19", 14, "999999.99999999"}, + {"0x1.e847fffffffd6p+19", 14, "1000000"}, + {"0x1.312cfffffffe5p+23", 14, "9999999.9999999"}, + {"0x1.312cfffffffe6p+23", 14, "10000000"}, + {"0x1.7d783ffffffdep+26", 14, "99999999.999999"}, + {"0x1.7d783ffffffdfp+26", 14, "100000000"}, + {"0x1.dcd64ffffffd6p+29", 14, "999999999.99999"}, + {"0x1.dcd64ffffffd7p+29", 14, "1000000000"}, + {"0x1.2a05f1fffffe5p+33", 14, "9999999999.9999"}, + {"0x1.2a05f1fffffe6p+33", 14, "10000000000"}, + {"0x1.74876e7ffffdfp+36", 14, "99999999999.999"}, + {"0x1.74876e7ffffe0p+36", 14, "100000000000"}, + {"0x1.d1a94a1ffffd7p+39", 14, "999999999999.99"}, + {"0x1.d1a94a1ffffd8p+39", 14, "1000000000000"}, + {"0x1.2309ce53fffe6p+43", 14, "9999999999999.9"}, + {"0x1.2309ce53fffe7p+43", 14, "10000000000000"}, + {"0x1.6bcc41e8fffdfp+46", 14, "99999999999999"}, + {"0x1.6bcc41e8fffe0p+46", 14, "1e+14"}, + {"0x1.a36e2eb1c4328p-14", 15, "9.99999999999999e-5"}, + {"0x1.a36e2eb1c4329p-14", 15, "0.0001"}, + {"0x1.0624dd2f1a9f9p-10", 15, "0.000999999999999999"}, + {"0x1.0624dd2f1a9fap-10", 15, "0.001"}, + {"0x1.47ae147ae1477p-7", 15, "0.00999999999999999"}, + {"0x1.47ae147ae1478p-7", 15, "0.01"}, + {"0x1.9999999999995p-4", 15, "0.0999999999999999"}, + {"0x1.9999999999996p-4", 15, "0.1"}, + {"0x1.ffffffffffffbp-1", 15, "0.999999999999999"}, + {"0x1.ffffffffffffcp-1", 15, "1"}, + {"0x1.3fffffffffffdp+3", 15, "9.99999999999999"}, + {"0x1.3fffffffffffep+3", 15, "10"}, + {"0x1.8fffffffffffcp+6", 15, "99.9999999999999"}, + {"0x1.8fffffffffffdp+6", 15, "100"}, + {"0x1.f3ffffffffffbp+9", 15, "999.999999999999"}, + {"0x1.f3ffffffffffcp+9", 15, "1000"}, + {"0x1.387fffffffffdp+13", 15, "9999.99999999999"}, + {"0x1.387fffffffffep+13", 15, "10000"}, + {"0x1.869fffffffffcp+16", 15, "99999.9999999999"}, + {"0x1.869fffffffffdp+16", 15, "100000"}, + {"0x1.e847ffffffffbp+19", 15, "999999.999999999"}, + {"0x1.e847ffffffffcp+19", 15, "1000000"}, + {"0x1.312cffffffffdp+23", 15, "9999999.99999999"}, + {"0x1.312cffffffffep+23", 15, "10000000"}, + {"0x1.7d783fffffffcp+26", 15, "99999999.9999999"}, + {"0x1.7d783fffffffdp+26", 15, "100000000"}, + {"0x1.dcd64fffffffbp+29", 15, "999999999.999999"}, + {"0x1.dcd64fffffffcp+29", 15, "1000000000"}, + {"0x1.2a05f1ffffffdp+33", 15, "9999999999.99999"}, + {"0x1.2a05f1ffffffep+33", 15, "10000000000"}, + {"0x1.74876e7fffffcp+36", 15, "99999999999.9999"}, + {"0x1.74876e7fffffdp+36", 15, "100000000000"}, + {"0x1.d1a94a1fffffbp+39", 15, "999999999999.999"}, + {"0x1.d1a94a1fffffcp+39", 15, "1000000000000"}, + {"0x1.2309ce53ffffdp+43", 15, "9999999999999.99"}, + {"0x1.2309ce53ffffep+43", 15, "10000000000000"}, + {"0x1.6bcc41e8ffffcp+46", 15, "99999999999999.9"}, + {"0x1.6bcc41e8ffffdp+46", 15, "100000000000000"}, + {"0x1.c6bf52633fffbp+49", 15, "999999999999999"}, + {"0x1.c6bf52633fffcp+49", 15, "1e+15"}, + {"0x1.1c37937e07fffp+53", 16, "9999999999999998"}, + {"0x1.1c37937e08000p+53", 16, "1e+16"}, + {"0x1.6345785d89fffp+56", 17, "99999999999999984"}, + {"0x1.6345785d8a000p+56", 17, "1e+17"}, + {"0x1.bc16d674ec7ffp+59", 18, "999999999999999872"}, + {"0x1.bc16d674ec800p+59", 18, "1e+18"}, + {"0x1.158e460913cffp+63", 19, "9999999999999997952"}, + {"0x1.158e460913d00p+63", 19, "1e+19"}, + {"0x1.5af1d78b58c3fp+66", 20, "99999999999999983616"}, + {"0x1.5af1d78b58c40p+66", 20, "1e+20"}, + {"0x1.b1ae4d6e2ef4fp+69", 21, "999999999999999868928"}, + {"0x1.b1ae4d6e2ef50p+69", 21, "1e+21"}, + {"0x1.0f0cf064dd591p+73", 22, "9999999999999997902848"}, + {"0x1.0f0cf064dd592p+73", 22, "1e+22"}, + {"0x1.52d02c7e14af6p+76", 23, "99999999999999991611392"}, + {"0x1.52d02c7e14af7p+76", 23, "1.0000000000000000838861e+23"}, + {"0x1.a784379d99db4p+79", 24, "999999999999999983222784"}, + {"0x1.a784379d99db5p+79", 24, "1.00000000000000011744051e+24"}, + {"0x1.a36e2eb1c432cp-14", 25, "9.999999999999999123964645e-5"}, + {"0x1.a36e2eb1c432dp-14", 25, "0.0001000000000000000047921736"}, + {"0x1.0624dd2f1a9fbp-10", 25, "0.0009999999999999998039762472"}, + {"0x1.0624dd2f1a9fcp-10", 25, "0.001000000000000000020816682"}, + {"0x1.47ae147ae147ap-7", 25, "0.009999999999999998473443341"}, + {"0x1.47ae147ae147bp-7", 25, "0.01000000000000000020816682"}, + {"0x1.9999999999999p-4", 25, "0.09999999999999999167332732"}, + {"0x1.999999999999ap-4", 25, "0.1000000000000000055511151"}, + {"0x1.fffffffffffffp-1", 25, "0.9999999999999998889776975"}, + {"0x1.0000000000000p+0", 25, "1"}, + {"0x1.3ffffffffffffp+3", 25, "9.999999999999998223643161"}, + {"0x1.4000000000000p+3", 25, "10"}, + {"0x1.8ffffffffffffp+6", 25, "99.99999999999998578914528"}, + {"0x1.9000000000000p+6", 25, "100"}, + {"0x1.f3fffffffffffp+9", 25, "999.9999999999998863131623"}, + {"0x1.f400000000000p+9", 25, "1000"}, + {"0x1.387ffffffffffp+13", 25, "9999.999999999998181010596"}, + {"0x1.3880000000000p+13", 25, "10000"}, + {"0x1.869ffffffffffp+16", 25, "99999.99999999998544808477"}, + {"0x1.86a0000000000p+16", 25, "100000"}, + {"0x1.e847fffffffffp+19", 25, "999999.9999999998835846782"}, + {"0x1.e848000000000p+19", 25, "1000000"}, + {"0x1.312cfffffffffp+23", 25, "9999999.999999998137354851"}, + {"0x1.312d000000000p+23", 25, "10000000"}, + {"0x1.7d783ffffffffp+26", 25, "99999999.99999998509883881"}, + {"0x1.7d78400000000p+26", 25, "100000000"}, + {"0x1.dcd64ffffffffp+29", 25, "999999999.9999998807907104"}, + {"0x1.dcd6500000000p+29", 25, "1000000000"}, + {"0x1.2a05f1fffffffp+33", 25, "9999999999.999998092651367"}, + {"0x1.2a05f20000000p+33", 25, "10000000000"}, + {"0x1.74876e7ffffffp+36", 25, "99999999999.99998474121094"}, + {"0x1.74876e8000000p+36", 25, "100000000000"}, + {"0x1.d1a94a1ffffffp+39", 25, "999999999999.9998779296875"}, + {"0x1.d1a94a2000000p+39", 25, "1000000000000"}, + {"0x1.2309ce53fffffp+43", 25, "9999999999999.998046875"}, + {"0x1.2309ce5400000p+43", 25, "10000000000000"}, + {"0x1.6bcc41e8fffffp+46", 25, "99999999999999.984375"}, + {"0x1.6bcc41e900000p+46", 25, "100000000000000"}, + {"0x1.c6bf52633ffffp+49", 25, "999999999999999.875"}, + {"0x1.c6bf526340000p+49", 25, "1000000000000000"}, + {"0x1.1c37937e07fffp+53", 25, "9999999999999998"}, + {"0x1.1c37937e08000p+53", 25, "10000000000000000"}, + {"0x1.6345785d89fffp+56", 25, "99999999999999984"}, + {"0x1.6345785d8a000p+56", 25, "100000000000000000"}, + {"0x1.bc16d674ec7ffp+59", 25, "999999999999999872"}, + {"0x1.bc16d674ec800p+59", 25, "1000000000000000000"}, + {"0x1.158e460913cffp+63", 25, "9999999999999997952"}, + {"0x1.158e460913d00p+63", 25, "10000000000000000000"}, + {"0x1.5af1d78b58c3fp+66", 25, "99999999999999983616"}, + {"0x1.5af1d78b58c40p+66", 25, "100000000000000000000"}, + {"0x1.b1ae4d6e2ef4fp+69", 25, "999999999999999868928"}, + {"0x1.b1ae4d6e2ef50p+69", 25, "1000000000000000000000"}, + {"0x1.0f0cf064dd591p+73", 25, "9999999999999997902848"}, + {"0x1.0f0cf064dd592p+73", 25, "10000000000000000000000"}, + {"0x1.52d02c7e14af6p+76", 25, "99999999999999991611392"}, + {"0x1.52d02c7e14af7p+76", 25, "100000000000000008388608"}, + {"0x1.a784379d99db4p+79", 25, "999999999999999983222784"}, + {"0x1.a784379d99db5p+79", 25, "1000000000000000117440512"}, + {"0x1.08b2a2c280290p+83", 25, "9999999999999998758486016"}, + {"0x1.08b2a2c280291p+83", 25, "1.000000000000000090596966e+25"}, + {"0x1.4adf4b7320334p+86", 26, "99999999999999987584860160"}, + {"0x1.4adf4b7320335p+86", 26, "1.0000000000000000476472934e+26"}, + {"0x1.9d971e4fe8401p+89", 27, "999999999999999875848601600"}, + {"0x1.9d971e4fe8402p+89", 27, "1.00000000000000001328755507e+27"}, + {"0x1.027e72f1f1281p+93", 28, "9999999999999999583119736832"}, + {"0x1.027e72f1f1282p+93", 28, "1.000000000000000178214299238e+28"}, + {"0x1.431e0fae6d721p+96", 29, "99999999999999991433150857216"}, + {"0x1.431e0fae6d722p+96", 29, "1.0000000000000000902533690163e+29"}, + {"0x1.93e5939a08ce9p+99", 30, "999999999999999879147136483328"}, + {"0x1.93e5939a08ceap+99", 30, "1.00000000000000001988462483866e+30"}, + {"0x1.f8def8808b024p+102", 31, "9999999999999999635896294965248"}, + {"0x1.f8def8808b025p+102", 31, "1.000000000000000076179620180787e+31"}, + {"0x1.3b8b5b5056e16p+106", 32, "99999999999999987351763694911488"}, + {"0x1.3b8b5b5056e17p+106", 32, "1.0000000000000000536616220439347e+32"}, + {"0x1.8a6e32246c99cp+109", 33, "999999999999999945575230987042816"}, + {"0x1.8a6e32246c99dp+109", 33, "1.00000000000000008969041906289869e+33"}, + {"0x1.ed09bead87c03p+112", 34, "9999999999999999455752309870428160"}, + {"0x1.ed09bead87c04p+112", 34, "1.000000000000000060867381447727514e+34"}, + {"0x1.3426172c74d82p+116", 35, "99999999999999996863366107917975552"}, + {"0x1.3426172c74d83p+116", 35, "1.0000000000000001531011018162752717e+35"}, + {"0x1.812f9cf7920e2p+119", 36, "999999999999999894846684784341549056"}, + {"0x1.812f9cf7920e3p+119", 36, "1.00000000000000004242063737401796198e+36"}, + {"0x1.e17b84357691bp+122", 37, "9999999999999999538762658202121142272"}, + {"0x1.e17b84357691cp+122", 37, "1.00000000000000007193542789195324457e+37"}, + {"0x1.2ced32a16a1b1p+126", 38, "99999999999999997748809823456034029568"}, + {"0x1.2ced32a16a1b2p+126", 38, "1.0000000000000001663827575493461488435e+38"}, + {"0x1.78287f49c4a1dp+129", 39, "999999999999999939709166371603178586112"}, + {"0x1.78287f49c4a1ep+129", 39, "1.00000000000000009082489382343182542438e+39"}, + {"0x1.d6329f1c35ca4p+132", 40, "9999999999999999094860208812374492184576"}, + {"0x1.d6329f1c35ca5p+132", 40, "1.000000000000000030378602842700366689075e+40"}, + {"0x1.25dfa371a19e6p+136", 41, "99999999999999981277195531206711524196352"}, + {"0x1.25dfa371a19e7p+136", 41, "1.0000000000000000062000864504077831949517e+41"}, + {"0x1.6f578c4e0a060p+139", 42, "999999999999999890143207767403382423158784"}, + {"0x1.6f578c4e0a061p+139", 42, "1.00000000000000004488571267807591678554931e+42"}, + {"0x1.cb2d6f618c878p+142", 43, "9999999999999998901432077674033824231587840"}, + {"0x1.cb2d6f618c879p+142", 43, "1.000000000000000013937211695941409913071206e+43"}, + {"0x1.1efc659cf7d4bp+146", 44, "99999999999999989014320776740338242315878400"}, + {"0x1.1efc659cf7d4cp+146", 44, "1.0000000000000000882136140530642264070186598e+44"}, + {"0x1.66bb7f0435c9ep+149", 45, "999999999999999929757289024535551219930759168"}, + {"0x1.66bb7f0435c9fp+149", 45, "1.00000000000000008821361405306422640701865984e+45"}, + {"0x1.c06a5ec5433c6p+152", 46, "9999999999999999931398190359470212947659194368"}, + {"0x1.c06a5ec5433c7p+152", 46, "1.000000000000000119904879058769961444436239974e+46"}, + {"0x1.18427b3b4a05bp+156", 47, "99999999999999984102174700855949311516153479168"}, + {"0x1.18427b3b4a05cp+156", 47, "1.0000000000000000438458430450761973546340476518e+47"}, + {"0x1.5e531a0a1c872p+159", 48, "999999999999999881586566215862833963056037363712"}, + {"0x1.5e531a0a1c873p+159", 48, "1.00000000000000004384584304507619735463404765184e+48"}, + {"0x1.b5e7e08ca3a8fp+162", 49, "9999999999999999464902769475481793196872414789632"}, + {"0x1.b5e7e08ca3a90p+162", 49, "1.000000000000000076297698410918870032949649709466e+49"}, + {"0x1.11b0ec57e6499p+166", 50, "99999999999999986860582406952576489172979654066176"}, + {"0x1.11b0ec57e649ap+166", 50, "1.0000000000000000762976984109188700329496497094656e+50"}, + {"0x1.561d276ddfdc0p+169", 51, "999999999999999993220948674361627976461708441944064"}, + {"0x1.561d276ddfdc1p+169", 51, "1.00000000000000015937444814747611208943759097698714e+51"}, + {"0x1.aba4714957d30p+172", 52, "9999999999999999932209486743616279764617084419440640"}, + {"0x1.aba4714957d31p+172", 52, "1.000000000000000126143748252853215266842414469978522e+52"}, + {"0x1.0b46c6cdd6e3ep+176", 53, "99999999999999999322094867436162797646170844194406400"}, + {"0x1.0b46c6cdd6e3fp+176", 53, "1.0000000000000002058974279999481676410708380867991962e+53"}, + {"0x1.4e1878814c9cdp+179", 54, "999999999999999908150356944127012110618056584002011136"}, + {"0x1.4e1878814c9cep+179", 54, "1.00000000000000007829154040459624384230536029988611686e+54"}, + {"0x1.a19e96a19fc40p+182", 55, "9999999999999998741221202520331657642805958408251899904"}, + {"0x1.a19e96a19fc41p+182", 55, "1.000000000000000010235067020408551149630438813532474573e+55"}, + {"0x1.05031e2503da8p+186", 56, "99999999999999987412212025203316576428059584082518999040"}, + {"0x1.05031e2503da9p+186", 56, "1.0000000000000000919028350814337823808403445971568453222e+56"}, + {"0x1.4643e5ae44d12p+189", 57, "999999999999999874122120252033165764280595840825189990400"}, + {"0x1.4643e5ae44d13p+189", 57, "1.00000000000000004834669211555365905752839484589051425587e+57"}, + {"0x1.97d4df19d6057p+192", 58, "9999999999999999438119489974413630815797154428513196965888"}, + {"0x1.97d4df19d6058p+192", 58, "1.000000000000000083191606488257757716177954646903579108966e+58"}, + {"0x1.fdca16e04b86dp+195", 59, "99999999999999997168788049560464200849936328366177157906432"}, + {"0x1.fdca16e04b86ep+195", 59, "1.0000000000000000831916064882577577161779546469035791089664e+59"}, + {"0x1.3e9e4e4c2f344p+199", 60, "999999999999999949387135297074018866963645011013410073083904"}, + {"0x1.3e9e4e4c2f345p+199", 60, "1.00000000000000012779309688531900399924939119220030212092723e+60"}, + {"0x1.8e45e1df3b015p+202", 61, "9999999999999999493871352970740188669636450110134100730839040"}, + {"0x1.8e45e1df3b016p+202", 61, "1.000000000000000092111904567670006972792241955962923711358566e+61"}, + {"0x1.f1d75a5709c1ap+205", 62, "99999999999999992084218144295482124579792562202350734542897152"}, + {"0x1.f1d75a5709c1bp+205", 62, "1.0000000000000000350219968594316117304608031779831182560487014e+62"}, + {"0x1.3726987666190p+209", 63, "999999999999999875170255276364105051932774599639662981181079552"}, + {"0x1.3726987666191p+209", 63, "1.00000000000000005785795994272696982739337868917504043817264742e+63"}, + {"0x1.84f03e93ff9f4p+212", 64, "9999999999999998751702552763641050519327745996396629811810795520"}, + {"0x1.84f03e93ff9f5p+212", 64, "1.00000000000000002132041900945439687230125787126796494677433385e+64"}, + {"0x1.e62c4e38ff872p+215", 65, "99999999999999999209038626283633850822756121694230455365568299008"}, + {"0x1.e62c4e38ff873p+215", 65, "1.0000000000000001090105172493085719645223478342449461261302864282e+65"}, + {"0x1.2fdbb0e39fb47p+219", 66, "999999999999999945322333868247445125709646570021247924665841614848"}, + {"0x1.2fdbb0e39fb48p+219", 66, "1.00000000000000013239454344660301865578130515770547444062520711578e+66"}, + {"0x1.7bd29d1c87a19p+222", 67, "9999999999999999827367757839185598317239782875580932278577147150336"}, + {"0x1.7bd29d1c87a1ap+222", 67, "1.000000000000000132394543446603018655781305157705474440625207115776e+67"}, + {"0x1.dac74463a989fp+225", 68, "99999999999999995280522225138166806691251291352861698530421623488512"}, + {"0x1.dac74463a98a0p+225", 68, "1.000000000000000072531436381529235126158374409646521955518210155479e+68"}, + {"0x1.28bc8abe49f63p+229", 69, "999999999999999880969493773293127831364996015857874003175819882528768"}, + {"0x1.28bc8abe49f64p+229", 69, "1.00000000000000007253143638152923512615837440964652195551821015547904e+69"}, + {"0x1.72ebad6ddc73cp+232", 70, "9999999999999999192818822949403492903236716946156035936442979371188224"}, + {"0x1.72ebad6ddc73dp+232", 70, "1.00000000000000007253143638152923512615837440964652195551821015547904e+70"}, + {"0x1.cfa698c95390bp+235", 71, "99999999999999991928188229494034929032367169461560359364429793711882240"}, + {"0x1.cfa698c95390cp+235", 71, "1.0000000000000000418815255642114579589914338666403382831434277118069965e+71"}, + {"0x1.21c81f7dd43a7p+239", 72, "999999999999999943801810948794571024057224129020550531544123892056457216"}, + {"0x1.21c81f7dd43a8p+239", 72, "1.00000000000000013996124017962834489392564360426012603474273153155753574e+72"}, + {"0x1.6a3a275d49491p+242", 73, "9999999999999999830336967949613257980309080240684656321838454199566729216"}, + {"0x1.6a3a275d49492p+242", 73, "1.000000000000000139961240179628344893925643604260126034742731531557535744e+73"}, + {"0x1.c4c8b1349b9b5p+245", 74, "99999999999999995164818811802792197885196090803013355167206819763650035712"}, + {"0x1.c4c8b1349b9b6p+245", 74, "1.000000000000000077190222825761537255567749372183461873719177086917190615e+74"}, + {"0x1.1afd6ec0e1411p+249", 75, "999999999999999926539781176481198923508803215199467887262646419780362305536"}, + {"0x1.1afd6ec0e1412p+249", 75, "1.00000000000000012740703670885498336625406475784479320253802064262946671821e+75"}, + {"0x1.61bcca7119915p+252", 76, "9999999999999998863663300700064420349597509066704028242075715752105414230016"}, + {"0x1.61bcca7119916p+252", 76, "1.000000000000000047060134495905469589155960140786663076427870953489824953139e+76"}, + {"0x1.ba2bfd0d5ff5bp+255", 77, "99999999999999998278261272554585856747747644714015897553975120217811154108416"}, + {"0x1.ba2bfd0d5ff5cp+255", 77, "1.0000000000000001113376562662650806108344438344331671773159907048015383651942e+77"}, + {"0x1.145b7e285bf98p+259", 78, "999999999999999802805551768538947706777722104929947493053015898505313987330048"}, + {"0x1.145b7e285bf99p+259", 78, "1.00000000000000000849362143368970297614886992459876061589499910270279690590618e+78"}, + {"0x1.59725db272f7fp+262", 79, "9999999999999999673560075006595519222746403606649979913266024618633003221909504"}, + {"0x1.59725db272f80p+262", 79, "1.000000000000000131906463232780156137771558616400048489600189025221286657051853e+79"}, + {"0x1.afcef51f0fb5ep+265", 80, "99999999999999986862573406138718939297648940722396769236245052384850852127440896"}, + {"0x1.afcef51f0fb5fp+265", 80, "1.0000000000000000002660986470836727653740240118120080909813197745348975891631309e+80"}, + {"0x1.0de1593369d1bp+269", 81, "999999999999999921281879895665782741935503249059183851809998224123064148429897728"}, + {"0x1.0de1593369d1cp+269", 81, "1.0000000000000001319064632327801561377715586164000484896001890252212866570518528e+81"}, + {"0x1.5159af8044462p+272", 82, "9999999999999999634067965630886574211027143225273567793680363843427086501542887424"}, + {"0x1.5159af8044463p+272", 82, "1.0000000000000001319064632327801561377715586164000484896001890252212866570518528e+82"}, + {"0x1.a5b01b605557ap+275", 83, "99999999999999989600692989521205793443517660497828009527517532799127744739526311936"}, + {"0x1.a5b01b605557bp+275", 83, "1.0000000000000000308066632309652569077702520400764334634608974406941398529133143654e+83"}, + {"0x1.078e111c3556cp+279", 84, "999999999999999842087036560910778345101146430939018748000886482910132485188042620928"}, + {"0x1.078e111c3556dp+279", 84, "1.00000000000000005776660989811589670243726712709606413709804186323471233401692461466e+84"}, + {"0x1.4971956342ac7p+282", 85, "9999999999999998420870365609107783451011464309390187480008864829101324851880426209280"}, + {"0x1.4971956342ac8p+282", 85, "1.00000000000000001463069523067487303097004298786465505927861078716979636425114821591e+85"}, + {"0x1.9bcdfabc13579p+285", 86, "99999999999999987659576829486359728227492574232414601025643134376206526100066373992448"}, + {"0x1.9bcdfabc1357ap+285", 86, "1.0000000000000000146306952306748730309700429878646550592786107871697963642511482159104e+86"}, + {"0x1.0160bcb58c16cp+289", 87, "999999999999999959416724456350362731491996089648451439669739009806703922950954425516032"}, + {"0x1.0160bcb58c16dp+289", 87, "1.0000000000000001802726075536484039294041836825132659181052261192590736881517295870935e+87"}, + {"0x1.41b8ebe2ef1c7p+292", 88, "9999999999999999594167244563503627314919960896484514396697390098067039229509544255160320"}, + {"0x1.41b8ebe2ef1c8p+292", 88, "1.00000000000000013610143093418879568982174616394030302241812869736859973511157455477801e+88"}, + {"0x1.922726dbaae39p+295", 89, "99999999999999999475366575191804932315794610450682175621941694731908308538307845136842752"}, + {"0x1.922726dbaae3ap+295", 89, "1.0000000000000001361014309341887956898217461639403030224181286973685997351115745547780096e+89"}, + {"0x1.f6b0f092959c7p+298", 90, "999999999999999966484112715463900049825186092620125502979674597309179755437379230686511104"}, + {"0x1.f6b0f092959c8p+298", 90, "1.00000000000000007956232486128049714315622614016691051593864399734879307522017611341417677e+90"}, + {"0x1.3a2e965b9d81cp+302", 91, "9999999999999998986371854279739417938265620640920544952042929572854117635677011010499117056"}, + {"0x1.3a2e965b9d81dp+302", 91, "1.000000000000000079562324861280497143156226140166910515938643997348793075220176113414176768e+91"}, + {"0x1.88ba3bf284e23p+305", 92, "99999999999999989863718542797394179382656206409205449520429295728541176356770110104991170560"}, + {"0x1.88ba3bf284e24p+305", 92, "1.0000000000000000433772969746191860732902933249519393117917737893361168128896811109413237555e+92"}, + {"0x1.eae8caef261acp+308", 93, "999999999999999927585207737302990649719308316264031458521789123695552773432097103028194115584"}, + {"0x1.eae8caef261adp+308", 93, "1.00000000000000004337729697461918607329029332495193931179177378933611681288968111094132375552e+93"}, + {"0x1.32d17ed577d0bp+312", 94, "9999999999999998349515363474500343108625203093137051759058013911831015418660298966976904036352"}, + {"0x1.32d17ed577d0cp+312", 94, "1.000000000000000020218879127155946988576096323214357741137776856208004004998164309358697827533e+94"}, + {"0x1.7f85de8ad5c4ep+315", 95, "99999999999999987200500490339121684640523551209383568895219648418808203449245677922989188841472"}, + {"0x1.7f85de8ad5c4fp+315", 95, "1.0000000000000000202188791271559469885760963232143577411377768562080040049981643093586978275328e+95"}, + {"0x1.df67562d8b362p+318", 96, "999999999999999931290554592897108903273579836542044509826428632996050822694739791281414264061952"}, + {"0x1.df67562d8b363p+318", 96, "1.00000000000000004986165397190889301701026848543846215157489293061198839909930581538445901535642e+96"}, + {"0x1.2ba095dc7701dp+322", 97, "9999999999999998838621148412923952577789043769834774531270429139496757921329133816401963635441664"}, + {"0x1.2ba095dc7701ep+322", 97, "1.000000000000000073575873847711249839757606215217745679924585790135175914380219020205067965615309e+97"}, + {"0x1.7688bb5394c25p+325", 98, "99999999999999999769037024514370800696612547992403838920556863966097586548129676477911932478685184"}, + {"0x1.7688bb5394c26p+325", 98, "1.0000000000000001494613774502787916725490869505114529706436029406093759632791412756310166064437658e+98"}, + {"0x1.d42aea2879f2ep+328", 99, "999999999999999967336168804116691273849533185806555472917961779471295845921727862608739868455469056"}, + {"0x1.d42aea2879f2fp+328", 99, "1.00000000000000008875297456822475820631590236227648713806838922023001592416000347129025769378100019e+99"}, + {"0x1.249ad2594c37cp+332", 100, "9999999999999998216360018871870109548898901740426374747374488505608317520357971321909184780648316928"}, + {"0x1.249ad2594c37dp+332", 100, "1.00000000000000001590289110975991804683608085639452813897813275577478387721703810608134699858568151e+100"}, + {"0x1.6dc186ef9f45cp+335", 101, "99999999999999997704951326524533662844684271992415000612999597473199345218078991130326129448151154688"}, + {"0x1.6dc186ef9f45dp+335", 101, "1.000000000000000132463024643303662302003795265805662537522543098903155152325782690415604110898191401e+101"}, + {"0x1.c931e8ab87173p+338", 102, "999999999999999977049513265245336628446842719924150006129995974731993452180789911303261294481511546880"}, + {"0x1.c931e8ab87174p+338", 102, "1.00000000000000010138032236769199716729240475662936003124403367406892281229678413459313554761485543014e+102"}, + {"0x1.1dbf316b346e7p+342", 103, "9999999999999998029863805218200118740630558685368559709703431956602923480183979986974373400948301103104"}, + {"0x1.1dbf316b346e8p+342", 103, "1.000000000000000001915675085734668736215955127265192011152803514599379324203988755961236145108180323533e+103"}, + {"0x1.652efdc6018a1p+345", 104, "99999999999999984277223943460294324649363572028252317900683525944810974325551615015019710109750015295488"}, + {"0x1.652efdc6018a2p+345", 104, "1.0000000000000000019156750857346687362159551272651920111528035145993793242039887559612361451081803235328e+104"}, + {"0x1.be7abd3781ecap+348", 105, "999999999999999938258300825281978540327027364472124478294416212538871491824599713636820527503908255301632"}, + {"0x1.be7abd3781ecbp+348", 105, "1.00000000000000006557304934618735893210488289005825954401119081665988715658337779828565176271245239176397e+105"}, + {"0x1.170cb642b133ep+352", 106, "9999999999999998873324014169198263836158851542376704520077063708904652259210884797772880334204906007166976"}, + {"0x1.170cb642b133fp+352", 106, "1.000000000000000091035999050368435010460453995175486557154545737484090289535133415215418009754161219056435e+106"}, + {"0x1.5ccfe3d35d80ep+355", 107, "99999999999999996881384047029926983435371269061279689406644211752791525136670645395254002395395884805259264"}, + {"0x1.5ccfe3d35d80fp+355", 107, "1.0000000000000001317767185770581567358293677633630497781839136108028153022579424023030440050208953427243827e+107"}, + {"0x1.b403dcc834e11p+358", 108, "999999999999999903628689227595715073763450661512695740419453520217955231010212074612338431527184250183876608"}, + {"0x1.b403dcc834e12p+358", 108, "1.00000000000000003399899171300282459494397471971289804771343071483787527172320083329274161638073344592130867e+108"}, + {"0x1.108269fd210cbp+362", 109, "9999999999999999818508707188399807864717650964328171247958398369899072554380053298205803424393137676263358464"}, + {"0x1.108269fd210ccp+362", 109, "1.000000000000000190443354695491356020360603589553140816466203348381779320578787343709225438204992480806227149e+109"}, + {"0x1.54a3047c694fdp+365", 110, "99999999999999985669538033284915564613846200056062290979362173015478401635353612148739328497990653971840106496"}, + {"0x1.54a3047c694fep+365", 110, "1.0000000000000000235693675141702558332495327950568818631299125392682816684661617325983093615924495102623141069e+110"}, + {"0x1.a9cbc59b83a3dp+368", 111, "999999999999999956819772641641815758405104477258378281795396215622882607621111488153942930947432322044748890112"}, + {"0x1.a9cbc59b83a3ep+368", 111, "1.00000000000000009031896238669869590809396111285538544446442886291368072931121197704267579223746669847987932365e+111"}, + {"0x1.0a1f5b8132466p+372", 112, "9999999999999999301199346926304397284673331501389768492615896861647229832830913903761963586894254467577228034048"}, + {"0x1.0a1f5b8132467p+372", 112, "1.000000000000000143718638284721447967969503767094188309532041921829999977987252172598168936753480449053931497062e+112"}, + {"0x1.4ca732617ed7fp+375", 113, "99999999999999984468045325579403643266646490335689226515340879189861218540142707748740732746380344583923932594176"}, + {"0x1.4ca732617ed80p+375", 113, "1.0000000000000000155594161294668430242682013969210614333697705804308337811647557032649853899150474476762062808678e+113"}, + {"0x1.9fd0fef9de8dfp+378", 114, "999999999999999878856245830528597750986812202069726098796681149605056504554092802642922939954052246206632716926976"}, + {"0x1.9fd0fef9de8e0p+378", 114, "1.00000000000000001555941612946684302426820139692106143336977058043083378116475570326498538991504744767620628086784e+114"}, + {"0x1.03e29f5c2b18bp+382", 115, "9999999999999997968343436511656505870179786851589248980528274911095901385876950622696854699774551253248885785624576"}, + {"0x1.03e29f5c2b18cp+382", 115, "1.00000000000000001555941612946684302426820139692106143336977058043083378116475570326498538991504744767620628086784e+115"}, + {"0x1.44db473335deep+385", 116, "99999999999999984057935814682588907446802322751135220511621610897383886710310719046874545396497358979515211902353408"}, + {"0x1.44db473335defp+385", 116, "1.00000000000000001555941612946684302426820139692106143336977058043083378116475570326498538991504744767620628086784e+116"}, + {"0x1.961219000356ap+388", 117, "999999999999999910571381339882270654388094495275235896417637897556636832727766595587241428345003132947573783761256448"}, + {"0x1.961219000356bp+388", 117, "1.00000000000000005055542772599503381422823703080300327902048147472223276397708540582423337710506221925241711323670118e+117"}, + {"0x1.fb969f40042c5p+391", 118, "9999999999999999665649998943273759183241515094863428494587753284228752052274941196820382078490267674695111155514343424"}, + {"0x1.fb969f40042c6p+391", 118, "1.000000000000000078552237003217586446196265537908556755541050190155351950226949167871631766857074036513385779131790131e+118"}, + {"0x1.3d3e2388029bbp+395", 119, "99999999999999994416755247254933381274972870380190006824232035607637985622760311004411949604741731366073618283536318464"}, + {"0x1.3d3e2388029bcp+395", 119, "1.0000000000000001233471318467736706573451111492774423179739601348483426482267311871474691904602929441309356445639324467e+119"}, + {"0x1.8c8dac6a0342ap+398", 120, "999999999999999980003468347394201181668805192897008518188648311830772414627428725464789434929992439754776075181077037056"}, + {"0x1.8c8dac6a0342bp+398", 120, "1.00000000000000012334713184677367065734511114927744231797396013484834264822673118714746919046029294413093564456393244672e+120"}, + {"0x1.efb1178484134p+401", 121, "9999999999999999226660029476424133913982828103448349982745235826237443211877077407917175327178722380043122474279348731904"}, + {"0x1.efb1178484135p+401", 121, "1.000000000000000037340933747145988971939327575449182038102773041037800508067149710137861337142112641505239902934219200922e+121"}, + {"0x1.35ceaeb2d28c0p+405", 122, "99999999999999983092605830803955292696544699826135736641192401589249937168415416531480248917847991520357012302290741100544"}, + {"0x1.35ceaeb2d28c1p+405", 122, "1.0000000000000000144059475872452738558311186224283126301371231493549892706912613162686325762572645608050543718329623353754e+122"}, + {"0x1.83425a5f872f1p+408", 123, "999999999999999977709969731404129670057984297594921577392083322662491290889839886077866558841507631684757522070951350501376"}, + {"0x1.83425a5f872f2p+408", 123, "1.00000000000000012449388115476870641315052159692848578837224262943248321009552560684093062850453534816594492111899528999731e+123"}, + {"0x1.e412f0f768fadp+411", 124, "9999999999999999483531874467312143214394768377282087351960514613084929070487027419252537449089020883885200422613425626021888"}, + {"0x1.e412f0f768faep+411", 124, "1.000000000000000065780316585422875715913506677195060103980178906724486442413251318535705000639324261573469961499777714198938e+124"}, + {"0x1.2e8bd69aa19ccp+415", 125, "99999999999999992486776161899288204254467086983483846143922597222529419997579302660316349376281765375153005841365553228283904"}, + {"0x1.2e8bd69aa19cdp+415", 125, "1.0000000000000001127511682408995402737031186129818006514938298848908838565590707491798855029314931308474499291951517748376371e+125"}, + {"0x1.7a2ecc414a03fp+418", 126, "999999999999999924867761618992882042544670869834838461439225972225294199975793026603163493762817653751530058413655532282839040"}, + {"0x1.7a2ecc414a040p+418", 126, "1.0000000000000000751744869165182086274714290643524082134829091023577659252424152046645411010977580354282659550388525263266775e+126"}, + {"0x1.d8ba7f519c84fp+421", 127, "9999999999999999549291066784979473595300225087383524118479625982517885450291174622154390152298057300868772377386949310916067328"}, + {"0x1.d8ba7f519c850p+421", 127, "1.000000000000000075174486916518208627471429064352408213482909102357765925242415204664541101097758035428265955038852526326677504e+127"}, + {"0x1.27748f9301d31p+425", 128, "99999999999999988278187853568579059876517857536991893086699469578820211690113881674597776370903434688204400735860037395056427008"}, + {"0x1.27748f9301d32p+425", 128, "1.000000000000000075174486916518208627471429064352408213482909102357765925242415204664541101097758035428265955038852526326677504e+128"}, + {"0x1.7151b377c247ep+428", 129, "999999999999999998217443564185241415988928868759412500436543339729940401905904649497115766142268560009777175966751665376232210432"}, + {"0x1.7151b377c247fp+428", 129, "1.00000000000000015213153026885117583895392925994540392652927486498559144857892575983196643605324751084675473411095338727712279757e+129"}, + {"0x1.cda62055b2d9dp+431", 130, "9999999999999999366518088823188676468029287122850159299994507296276799832366962053631754981778769796749861527090709766158759755776"}, + {"0x1.cda62055b2d9ep+431", 130, "1.000000000000000059783078246051615185174929025233809070873635949832200820575113093631056034106660140344568199224432354136588445286e+130"}, + {"0x1.2087d4358fc82p+435", 131, "99999999999999991202555500957231813912852864969525730182461368558677581576901282770959939099212034754106974340599870111173348163584"}, + {"0x1.2087d4358fc83p+435", 131, "1.0000000000000001090355859915447142005237291504133263272233100379140091555104798489382082484781734046124010178305769051448734331699e+131"}, + {"0x1.68a9c942f3ba3p+438", 132, "999999999999999990829567402361276563686608849982484911984092226517669151665599636201042933986541570369602253175829982724989462249472"}, + {"0x1.68a9c942f3ba4p+438", 132, "1.00000000000000014843759218793919341280276925055694013230304930837945582345877325318393001997538401602666727271549254595150142347674e+132"}, + {"0x1.c2d43b93b0a8bp+441", 133, "9999999999999998962647525310145264542169126096378117797927179774005971485896954660113106823932361029753632414520324447890822855131136"}, + {"0x1.c2d43b93b0a8cp+441", 133, "1.000000000000000022351172359476859933509840930097375956047883642890026486024234359597620351184310059501015257083762495370291854494925e+133"}, + {"0x1.19c4a53c4e697p+445", 134, "99999999999999992148203649670699315007549827372972461504375111049848301607660324472857261615145089428049364457837845490532419930947584"}, + {"0x1.19c4a53c4e698p+445", 134, "1.0000000000000001232203082222467267169441835864650272970520161752815699559718654744666680862171692247215368695891465358352595096803738e+134"}, + {"0x1.6035ce8b6203dp+448", 135, "999999999999999961829690841814939863449235336276785151445404123455100404055655690676191710164594560368702289580532071091311261383655424"}, + {"0x1.6035ce8b6203ep+448", 135, "1.00000000000000012322030822224672671694418358646502729705201617528156995597186547446666808621716922472153686958914653583525950968037376e+135"}, + {"0x1.b843422e3a84cp+451", 136, "9999999999999999295515673657285824927502456862391367223240817130898064936724137339180964349540796274981353735788091781425216117243117568"}, + {"0x1.b843422e3a84dp+451", 136, "1.000000000000000058664061270074011975546204286389730438809371354550982135205381560950477535796139358980403037585700749937680210361686426e+136"}, + {"0x1.132a095ce492fp+455", 137, "99999999999999982626157224225223890651347880611866174913584999992086598044603947229219155428043184231232124237329592070639473281441202176"}, + {"0x1.132a095ce4930p+455", 137, "1.0000000000000000328415624892049260789870125663596116955123134262587470068987879955440013156277274126839495047843224355786484906342114918e+137"}, + {"0x1.57f48bb41db7bp+458", 138, "999999999999999867577570291642776341008185558166851738411142685188442185736589176942553506549890956386646894855501223680845484378371915776"}, + {"0x1.57f48bb41db7cp+458", 138, "1.00000000000000003284156248920492607898701256635961169551231342625874700689878799554400131562772741268394950478432243557864849063421149184e+138"}, + {"0x1.adf1aea12525ap+461", 139, "9999999999999999006303687311552062886039509598054037298313768334025031499690289406628430683654582476461074168412654660604060856295398309888"}, + {"0x1.adf1aea12525bp+461", 139, "1.00000000000000003284156248920492607898701256635961169551231342625874700689878799554400131562772741268394950478432243557864849063421149184e+139"}, + {"0x1.0cb70d24b7378p+465", 140, "99999999999999984774589122793531837245072631718372054355900219626000560719712531871037976946055058163097058166404267825310912362767116664832"}, + {"0x1.0cb70d24b7379p+465", 140, "1.0000000000000000592838012408148700370636248876704532886485007448299957782847398065202329650801812456915179223729338294822969716351458240102e+140"}, + {"0x1.4fe4d06de5056p+468", 141, "999999999999999847745891227935318372450726317183720543559002196260005607197125318710379769460550581630970581664042678253109123627671166648320"}, + {"0x1.4fe4d06de5057p+468", 141, "1.00000000000000001697621923823895970414104517357310673963060103511599774406721690895826232595625511287940845423115559923645940203365089253786e+141"}, + {"0x1.a3de04895e46cp+471", 142, "9999999999999999154380224320567749051268538597394750219876417318024024619451619548095327920588323941303457306908878466464492349900630570041344"}, + {"0x1.a3de04895e46dp+471", 142, "1.000000000000000050822284840299687970479108944850983978844920802887196171441235227007838837255396019129096028744578183433129457714846837715763e+142"}, + {"0x1.066ac2d5daec3p+475", 143, "99999999999999980713061250546244445284504979165026785650181847493456749434830333705088795590158149413134549224793557721710505681023603243483136"}, + {"0x1.066ac2d5daec4p+475", 143, "1.0000000000000000237454323586511053574086579278286821874734649886702374295420205725681776282160832941293459691338401160757934131698900815734374e+143"}, + {"0x1.4805738b51a74p+478", 144, "999999999999999850453576476100176633757771418885950722696147777681701481387046784154345890364481854130945587625116484988842728082166842262552576"}, + {"0x1.4805738b51a75p+478", 144, "1.00000000000000002374543235865110535740865792782868218747346498867023742954202057256817762821608329412934596913384011607579341316989008157343744e+144"}, + {"0x1.9a06d06e26112p+481", 145, "9999999999999999890870611821409196126784806260401358945180015464725302399110258148854112806457630061296658928320953898584032761523454337112604672"}, + {"0x1.9a06d06e26113p+481", 145, "1.000000000000000127720545888181662591599189833194321066339855315263358998435004845616476670927044158128386198039074294727963824222524025159968358e+145"}, + {"0x1.00444244d7cabp+485", 146, "99999999999999993363366729972462242111019694317846182578926003895619873650143420259298512453325054533017777074930382791057905692427399713177731072"}, + {"0x1.00444244d7cacp+485", 146, "1.0000000000000001554472428293898111873833316746251581007042260690215247501398006517626897489833003885281302590804700757018759338365597434497099366e+146"}, + {"0x1.405552d60dbd6p+488", 147, "999999999999999977996382405657660174364823889467801080772253244969263939229107492426926049423260513969768268415537077468838432306731146395363835904"}, + {"0x1.405552d60dbd7p+488", 147, "1.00000000000000015544724282938981118738333167462515810070422606902152475013980065176268974898330038852813025908047007570187593383655974344970993664e+147"}, + {"0x1.906aa78b912cbp+491", 148, "9999999999999999070160382361647997691574207754048582727994641153483596148648302286926205695992445641464234721495638781756234316947997075736253956096"}, + {"0x1.906aa78b912ccp+491", 148, "1.000000000000000048976726575150520579572227003530743888745042374590168263593384756161231529247276463793113064681510276762053432918662585217102276198e+148"}, + {"0x1.f485516e7577ep+494", 149, "99999999999999993540817590396194393124038202103003539598857976719672134461054113418634276152885094407576139065595315789290943193957228310232077172736"}, + {"0x1.f485516e7577fp+494", 149, "1.0000000000000000489767265751505205795722270035307438887450423745901682635933847561612315292472764637931130646815102767620534329186625852171022761984e+149"}, + {"0x1.38d352e5096afp+498", 150, "999999999999999980835596172437374590573120014030318793091164810154100112203678582976298268616221151962702060266176005440567032331208403948233373515776"}, + {"0x1.38d352e5096b0p+498", 150, "1.00000000000000016254527724633909722790407198603145238150150498198361518257622837813612029696570198351046473870706739563119743389775288733188378066944e+150"}, + {"0x1.8708279e4bc5ap+501", 151, "9999999999999998718097875280963410081745488308296386400449607070563910699801487058804050516065326530340444532016411713261887913912817139180431292235776"}, + {"0x1.8708279e4bc5bp+501", 151, "1.000000000000000017177532387217719118039310408430545510773232844520003126278188542008262674286117318272254595954354283478693112644517300624963454946509e+151"}, + {"0x1.e8ca3185deb71p+504", 152, "99999999999999992995688547174489225212045346187000138833626956204183589249936464033154810067836651912932851030272641618719051989257594860081125951275008"}, + {"0x1.e8ca3185deb72p+504", 152, "1.000000000000000046251081359041994740012262723950726884918887272012725537537796509233834198822034251319896624504896905909193976895164417966347520091095e+152"}, + {"0x1.317e5ef3ab327p+508", 153, "999999999999999999733403004123153744855539019118436686285840188024369679522423761672919759564567158443669378824028710020392594094129030220133015859757056"}, + {"0x1.317e5ef3ab328p+508", 153, "1.00000000000000018580411642379851772548243383844759748081802852397779311158391475191657751659443552994857836154750149357559812529827058120499103278510899e+153"}, + {"0x1.7dddf6b095ff0p+511", 154, "9999999999999998880909749523179353564794021275209402095665271864523156202855291675267251053466461355407239891899450398872692753716440996292182057045458944"}, + {"0x1.7dddf6b095ff1p+511", 154, "1.000000000000000036947545688058226540980917982984268845192277855215054365934721959721651310970540832744651175368723266731433700334957340417104619244827443e+154"}, + {"0x1.dd55745cbb7ecp+514", 155, "99999999999999988809097495231793535647940212752094020956652718645231562028552916752672510534664613554072398918994503988726927537164409962921820570454589440"}, + {"0x1.dd55745cbb7edp+514", 155, "1.0000000000000000071762315409101683040806148118916031180671277214625066168048834012826660698457618933038657381329676213626008153422946922595273365367711334e+155"}, + {"0x1.2a5568b9f52f4p+518", 156, "999999999999999983359180223191721714560372275017470536367007614460468417501012554531477876945938741751237388344363105067534507348164573733465510370326085632"}, + {"0x1.2a5568b9f52f5p+518", 156, "1.0000000000000001738955907649392944307223125700105311899679684704767740119319793285409834201445239541722641866531992354280649713012055219419601197018864681e+156"}, + {"0x1.74eac2e8727b1p+521", 157, "9999999999999999833591802231917217145603722750174705363670076144604684175010125545314778769459387417512373883443631050675345073481645737334655103703260856320"}, + {"0x1.74eac2e8727b2p+521", 157, "1.000000000000000135788308656589779887489924511011919059247776299273512893045785973739082311504806911688058826991432009355958878510597332300261197835574391603e+157"}, + {"0x1.d22573a28f19dp+524", 158, "99999999999999995287335453651211007997446182781858083179085387749785952239205787068995699003416510776387310061494932420984963311567802202010637287727642443776"}, + {"0x1.d22573a28f19ep+524", 158, "1.0000000000000000748166572832305566183181036166141396500954688253482951028278766060560405376812596437133302515326044476405891300456242288735429228494750692147e+158"}, + {"0x1.2357684599702p+528", 159, "999999999999999928484693987168420772305733470059469068129930887927772406304894123616740280504746200573981670431418299523701733729688780649419062882836695482368"}, + {"0x1.2357684599703p+528", 159, "1.0000000000000001235939783819179352336555603321323631774173148044884693350022041002024739567400974580931131118996664970128849288176027116149175428383545271255e+159"}, + {"0x1.6c2d4256ffcc2p+531", 160, "9999999999999998504409802292686149877658027252303114244149773213034936348259701329824468100106056975663290938441190205280284556945232082632196709006295628251136"}, + {"0x1.6c2d4256ffcc3p+531", 160, "1.000000000000000006528407745068226556845664214888626711844884454552051177783818114251033750998886703581634247018717578519375011764854353035618454865043828139622e+160"}, + {"0x1.c73892ecbfbf3p+534", 161, "99999999999999991287595123558845961539774732109363753938694017460291665200910932548988158640591809997245115511395844372456707812265566617217918448639526895091712"}, + {"0x1.c73892ecbfbf4p+534", 161, "1.0000000000000000377458932482281488706616365128202897693308658812017626863753877105047511391965429047846952776536372901176443229789205819900982116579266812025242e+161"}, + {"0x1.1c835bd3f7d78p+538", 162, "999999999999999937849939638116397466450525159438967985375725315922685858882365002492855496964043060934899979621894213003182527093908649335762989920701551401238528"}, + {"0x1.1c835bd3f7d79p+538", 162, "1.00000000000000013764184685833990027487274786620161155328600644648083951386841041851664678142904274863449057568538036723210611886393251464443343339515181100380979e+162"}, + {"0x1.63a432c8f5cd6p+541", 163, "9999999999999999378499396381163974664505251594389679853757253159226858588823650024928554969640430609348999796218942130031825270939086493357629899207015514012385280"}, + {"0x1.63a432c8f5cd7p+541", 163, "1.000000000000000097683465414295199713188303324849082839703950220369208782871201335311888524536042811094572456472683136386321400509927741582699344700261759083295539e+163"}, + {"0x1.bc8d3f7b3340bp+544", 164, "99999999999999987391652932764487656775541389327492204364443535414407668928683046936524228593524316087103098888157864364992697772750101243698844800887746832841572352"}, + {"0x1.bc8d3f7b3340cp+544", 164, "1.0000000000000000017833499485879183651456364256030139271070152777012950284778995356204687079928429609987689703622097823564380764603162862345375318325256344740613325e+164"}, + {"0x1.15d847ad00087p+548", 165, "999999999999999899489893451833484927233458399740540420336951338855520357125044282616287570346763120896578585177704871391229197474064067196498264773607101557544845312"}, + {"0x1.15d847ad00088p+548", 165, "1.00000000000000010407680644534235180305781445146548743387707921654706969983075478862464984563892280110095935554671469332164695544656850527257679889144416739057781965e+165"}, + {"0x1.5b4e5998400a9p+551", 166, "9999999999999999404072760505352583023983296100855298230449769143938302256661863838179600254051950569374547392515068357773127490685649548117139715971745147241514401792"}, + {"0x1.5b4e5998400aap+551", 166, "1.000000000000000104076806445342351803057814451465487433877079216547069699830754788624649845638922801100959355546714693321646955446568505272576798891444167390577819648e+166"}, + {"0x1.b221effe500d3p+554", 167, "99999999999999990767336997157383960226643264180953830087855645396318233083327270285662206135844950810475381599246526426844590779296424471954140613832058419086616428544"}, + {"0x1.b221effe500d4p+554", 167, "1.0000000000000000386089942874195144027940205149135043895442382956857739101649274267019739175454317034355575090286315503039132728953670850882316679737363063240072678605e+167"}, + {"0x1.0f5535fef2084p+558", 168, "999999999999999933860494834742974562371950216430331518611692822307700646699603647625692432595845947170914554599698521475539380813444812793279458505403728617494385000448"}, + {"0x1.0f5535fef2085p+558", 168, "1.00000000000000014335749374009605424321609081339667726047678376906384717363025120577825540249501745970020046345756457913228716497728935738318387744206888403052015072051e+168"}, + {"0x1.532a837eae8a5p+561", 169, "9999999999999999338604948347429745623719502164303315186116928223077006466996036476256924325958459471709145545996985214755393808134448127932794585054037286174943850004480"}, + {"0x1.532a837eae8a6p+561", 169, "1.000000000000000101458093959025438307047262694003408112103765579712617868244121694147742808515183157194343281685991367600937608144520448465202993654735852947914997576499e+169"}, + {"0x1.a7f5245e5a2cep+564", 170, "99999999999999990034097500988648181343688772091571619991327827082671720239070003832128235741197850516622880918243995225045973534722968565889475147553730375141026248523776"}, + {"0x1.a7f5245e5a2cfp+564", 170, "1.0000000000000000344190543093124528091771377029741774747069364767506509796263144755389226581474482731849717908514742291507783172120901941964335795950030032157467525460787e+170"}, + {"0x1.08f936baf85c1p+568", 171, "999999999999999953972206729656870211732987713739100709830741553196290713284945813208338477706166412373726001850053663010587168093173889073910282723323583537144858509574144"}, + {"0x1.08f936baf85c2p+568", 171, "1.00000000000000016849713360873842380491738768503263874950059468267458475686192891275656295888291804120371477252050850605109689907695070273397240771446870268008324260691968e+171"}, + {"0x1.4b378469b6731p+571", 172, "9999999999999999110672213538405594930961077194803931018967709273006319045695491932986935814708160866077282477159626944024852218964185263418978577250945597085571816901050368"}, + {"0x1.4b378469b6732p+571", 172, "1.000000000000000082687162857105802367643627696515223533632653430883267139431135672937273166412217389671719264252326568834893006683439977269947557718010655022907888967981466e+172"}, + {"0x1.9e056584240fdp+574", 173, "99999999999999987674323305318751091818660372407342701554959442658410485759723189737097766448253582599493004440868991951600366493901423615628791772651134064568704023452975104"}, + {"0x1.9e056584240fep+574", 173, "1.0000000000000000140391862557997052178246197057012913609383004294502130454865010810818413324356568684461228576377810190619298927686313968987276777208442168971676060568308941e+173"}, + {"0x1.02c35f729689ep+578", 174, "999999999999999849284042412665072058259000527747854146471853226010883220019378060628804930891911617504691481762871699606818419373090804007799965727644765395390927070069522432"}, + {"0x1.02c35f729689fp+578", 174, "1.0000000000000000689575675368445829376798260983524370990937828305966563206422087545661867996169052854265999829294174588803003839004782611957035817185773673977598323857513513e+174"}, + {"0x1.4374374f3c2c6p+581", 175, "9999999999999999371534524623368764100273307559896873275206250678451924602685103382037576783819090846734548822294900033162112051840457868829614121240178061963384891963422539776"}, + {"0x1.4374374f3c2c7p+581", 175, "1.000000000000000112892272561680485113563991212473353689618168751513810940766774893353663173361904019010981683162726610734996776805955752633284304916763887798233613448887717069e+175"}, + {"0x1.945145230b377p+584", 176, "99999999999999986685792442259943292861266657339622078268160759437774506806920451614379548038991111093844416185619536034869697653528180058283225500691937355558043949532406874112"}, + {"0x1.945145230b378p+584", 176, "1.0000000000000000074489805020743198914419949385831538723596425413126398524678161602637198763739070584084656026027846462837254338328097731830905692411162388370965388973604392141e+176"}, + {"0x1.f965966bce055p+587", 177, "999999999999999894976135638494410321178532246433607400617214583764724024948926844967780359586710300432448450005513217535702667994787395102883917853758746611883659375731342835712"}, + {"0x1.f965966bce056p+587", 177, "1.00000000000000000744898050207431989144199493858315387235964254131263985246781616026371987637390705840846560260278464628372543383280977318309056924111623883709653889736043921408e+177"}, + {"0x1.3bdf7e0360c35p+591", 178, "9999999999999998724815666657784284071258397080036981062687289922551408594451489819085924562292709488372450194860589317860981148271829194868425875762872481668410834714055235600384"}, + {"0x1.3bdf7e0360c36p+591", 178, "1.000000000000000052438118447506283719547380015442972461056613724331806183475371886382095683088785761598872463641693217782934540168018724415173229796059235727181690706012077765427e+178"}, + {"0x1.8ad75d8438f43p+594", 179, "99999999999999998045549773481514159457876389246726271914145983150114005386328272459269439234497983649422148597943950338419997003168440244384097290815044070304544781216945608327168"}, + {"0x1.8ad75d8438f44p+594", 179, "1.0000000000000001244207391601974258445159961384186822029717676171624723130874610481714969738325916867035234413039469321816691103043530463865054866839680307513179335998546994475827e+179"}, + {"0x1.ed8d34e547313p+597", 180, "999999999999999894076352879585771044616424544896411028843275160104340698328775730445412843452412726368640312784735046105718485868083216078242264642659886674081956339558310064685056"}, + {"0x1.ed8d34e547314p+597", 180, "1.00000000000000000924854601989159844456621034165754661590752138863340650570811838930845490864250220653608187704434098914369379808621813123237387566331395871269994496970650475613389e+180"}, + {"0x1.3478410f4c7ecp+601", 181, "9999999999999999171107915076469365246063817042486381462561244058101538598046442622180212564904306224021286256366562347133135483117101991090685868467907010818055540655879490029748224"}, + {"0x1.3478410f4c7edp+601", 181, "1.000000000000000101386300532136260364526038979066455085558918371456659151611592516398888560794573790670035128452025743574074047860726063355679164479837216343594335873825060509292954e+181"}, + {"0x1.819651531f9e7p+604", 182, "99999999999999991711079150764693652460638170424863814625612440581015385980464426221802125649043062240212862563665623471331354831171019910906858684679070108180555406558794900297482240"}, + {"0x1.819651531f9e8p+604", 182, "1.0000000000000000645311987272383955965421075241028916976983595783273580932502028655627150999337451570164538278889518418019219479509228905063570489532279132912365795121776382080293274e+182"}, + {"0x1.e1fbe5a7e7861p+607", 183, "999999999999999946594872951565228338993526868219488856544571440313594706493755982886960025179093529324993666087115356131035228239552737388526279268078143523691759154905886843985723392"}, + {"0x1.e1fbe5a7e7862p+607", 183, "1.00000000000000006453119872723839559654210752410289169769835957832735809325020286556271509993374515701645382788895184180192194795092289050635704895322791329123657951217763820802932736e+183"}, + {"0x1.2d3d6f88f0b3cp+611", 184, "9999999999999998286585471758920610814449462123360860153907833022998313197373091002112049504244419016335335042852788704601485085281825842706955095829283737561469387976341354799421194240"}, + {"0x1.2d3d6f88f0b3dp+611", 184, "1.000000000000000017356668416969128693522675261749530561236844323121852738547624112492413070031884505939869763168217247533567260066374829259224741079168005384218651369268937662411885773e+184"}, + {"0x1.788ccb6b2ce0cp+614", 185, "99999999999999997961704416875371517110712945186684165206763211895744845478556111003617144611039598507860251139162957211888350975873638026151889477992007905860430885494197722591793250304"}, + {"0x1.788ccb6b2ce0dp+614", 185, "1.0000000000000001305755411616153692607693126913975972887444809356150655898338131198611379417963500685236715184979802737776185109892901762523422799769117843610616789122498189718937455821e+185"}, + {"0x1.d6affe45f818fp+617", 186, "999999999999999979617044168753715171107129451866841652067632118957448454785561110036171446110395985078602511391629572118883509758736380261518894779920079058604308854941977225917932503040"}, + {"0x1.d6affe45f8190p+617", 186, "1.00000000000000010038384176304303844283687604349144616140911117228354216282416271789614464265915925183465771707671013344587151074317941705417760293751344330057020490078825062269858296627e+186"}, + {"0x1.262dfeebbb0f9p+621", 187, "9999999999999999071569656121801212080692814968920789464627446869617922299624001453201875281811380250249693879805812353226907091680705581859236698853640605134247712274342131878495422251008"}, + {"0x1.262dfeebbb0fap+621", 187, "1.000000000000000100383841763043038442836876043491446161409111172283542162824162717896144642659159251834657717076710133445871510743179417054177602937513443300570204900788250622698582966272e+187"}, + {"0x1.6fb97ea6a9d37p+624", 188, "99999999999999986851159038200753776111576258757220550347347138989744224339004763080499610528553377966303172216135545569805454885304878641227288327493418395599568449276340570087973407686656"}, + {"0x1.6fb97ea6a9d38p+624", 188, "1.0000000000000000230930913026978715489298382248516992754305645781548421896794576888657617968679507611107823854382585741965991901131358735068760297166536901857120314314466356487589666698035e+188"}, + {"0x1.cba7de5054485p+627", 189, "999999999999999899427890566145604518678577715028104257864890027548922232647929642417149243602017175952581854816736079397763477105066203831193512563278085201938953880500051690455580595453952"}, + {"0x1.cba7de5054486p+627", 189, "1.00000000000000002309309130269787154892983822485169927543056457815484218967945768886576179686795076111078238543825857419659919011313587350687602971665369018571203143144663564875896666980352e+189"}, + {"0x1.1f48eaf234ad3p+631", 190, "9999999999999998746948504188351511126283256130633852543517551174277382412416240331274267329488304589209417486924315804379963345034522698960570091326029642051843383703107348987949033805840384"}, + {"0x1.1f48eaf234ad4p+631", 190, "1.000000000000000072559171597318778361030342428781137282456834398397210172492068907445206818174324195174062597686867572116133475316363741377149036578003932179221262451825269232080321099543347e+190"}, + {"0x1.671b25aec1d88p+634", 191, "99999999999999991426771465453187656230872897620693565997277097362163262749171300799098274999392920617156591849131877877362376266603456419227541462168315779999172318661364176545198692437590016"}, + {"0x1.671b25aec1d89p+634", 191, "1.0000000000000000725591715973187783610303424287811372824568343983972101724920689074452068181743241951740625976868675721161334753163637413771490365780039321792212624518252692320803210995433472e+191"}, + {"0x1.c0e1ef1a724eap+637", 192, "999999999999999914267714654531876562308728976206935659972770973621632627491713007990982749993929206171565918491318778773623762666034564192275414621683157799991723186613641765451986924375900160"}, + {"0x1.c0e1ef1a724ebp+637", 192, "1.00000000000000004090088020876139800128601973826629695796002171344209466349199772755436200453824519737356326184775781344763153278629790594017431218673977730337535459878294373875465426450985779e+192"}, + {"0x1.188d357087712p+641", 193, "9999999999999998636144484328400679867178126713831911407778706776934478130915991201656310481762028096907669811487431649040206546179292274931158555956605099986382706217459209761309199883223171072"}, + {"0x1.188d357087713p+641", 193, "1.000000000000000066227513319607302289081477890678169217557471861406187070692054671467037855447108395613962730519045620382433086810350574289754091699751101204052080881216804133415187732536649318e+193"}, + {"0x1.5eb082cca94d7p+644", 194, "99999999999999994465967438754696170766327875910118237148971115117854351613178134068619377108456504406004528089686414709538562749489776621177115003729674648080379472553427423904462708600804999168"}, + {"0x1.5eb082cca94d8p+644", 194, "1.0000000000000001067501262969607491495542109345371648329133920981487349222121457817273192169012895127986018803931061114781155732488348436490817389205692194451348429331109807648720412813795157606e+194"}, + {"0x1.b65ca37fd3a0dp+647", 195, "999999999999999977077764769429719196041465194188378863774447340572581797347854228894418860247909937807756600796112539971931616645685181699233267813951241073670004367049615544210109925082343145472"}, + {"0x1.b65ca37fd3a0ep+647", 195, "1.00000000000000010675012629696074914955421093453716483291339209814873492221214578172731921690128951279860188039310611147811557324883484364908173892056921944513484293311098076487204128137951576064e+195"}, + {"0x1.11f9e62fe4448p+651", 196, "9999999999999999511432924639235132053389160461186216699466583890573511723749959183278387889172340228095875448767138256706948253250552493092635735926276453993770366538373425000777236538229086224384"}, + {"0x1.11f9e62fe4449p+651", 196, "1.000000000000000158619070907973161130959309230676679220568970001179196172157862402860479359562641342794939992231903540080589155890094708429021127363216410793720778359535526853136813823898384806707e+196"}, + {"0x1.56785fbbdd55ap+654", 197, "99999999999999995114329246392351320533891604611862166994665838905735117237499591832783878891723402280958754487671382567069482532505524930926357359262764539937703665383734250007772365382290862243840"}, + {"0x1.56785fbbdd55bp+654", 197, "1.0000000000000001171239152191632315458352305937650677104445076787548271722012891059539512454335598787978695027608655971986102897770868166050696166090986577148520300183958899825249957898832895698534e+197"}, + {"0x1.ac1677aad4ab0p+657", 198, "999999999999999884751043361827625869140390227060043253747518673178360772444478643277393806310703680414274761723053117059528639544242622390941156386039240473187039308013923507098814799398756243472384"}, + {"0x1.ac1677aad4ab1p+657", 198, "1.00000000000000001753554156601940054153744186517720008614579810493634157230551319337828377152376436520490032803037453428186101110586787622758599079921605032556703399966076149305663250824706100140442e+198"}, + {"0x1.0b8e0acac4eaep+661", 199, "9999999999999998847510433618276258691403902270600432537475186731783607724444786432773938063107036804142747617230531170595286395442426223909411563860392404731870393080139235070988147993987562434723840"}, + {"0x1.0b8e0acac4eafp+661", 199, "1.000000000000000097206240488534465344975672848047494185584765763991130052222133923438817750651600776079275667814767384615260434042843028529572891447122136236995030814648864284631323133556043856163635e+199"}, + {"0x1.4e718d7d7625ap+664", 200, "99999999999999996973312221251036165947450327545502362648241750950346848435554075534196338404706251868027512415973882408182135734368278484639385041047239877871023591066789981811181813306167128854888448"}, + {"0x1.4e718d7d7625bp+664", 200, "1.0000000000000001396972799138758332401427293722449843719522151821536839081776649794711025395197801952122758490331102381264067929425631097572992384593387153897566291159758524401378248003875013787018854e+200"}, + {"0x1.a20df0dcd3af0p+667", 201, "999999999999999901747459131964173027207212836739039328294498440443382314826691065690307721857975448067474834210390258463987183104130654882031695190925872134291678628544718769301415466131339252487684096"}, + {"0x1.a20df0dcd3af1p+667", 201, "1.00000000000000003771878529305655029174179371417100792467033657856355465388439044499361904623614958929307541410908738969965553158323491481075600563001892542312879319279108086692222079999200332461008486e+201"}, + {"0x1.0548b68a044d6p+671", 202, "9999999999999999017474591319641730272072128367390393282944984404433823148266910656903077218579754480674748342103902584639871831041306548820316951909258721342916786285447187693014154661313392524876840960"}, + {"0x1.0548b68a044d7p+671", 202, "1.000000000000000119301580989711976650462542240630189082495839461435658057319010072575605840863054074028435762048305668441056540670697470767990591893474757396431061931338898125494704000308401767883525325e+202"}, + {"0x1.469ae42c8560cp+674", 203, "99999999999999998876910787506329447650934459829549922997503484884029261182361866844442696946000689845185920534555642245481492613075738123641525387194542623914743194966239051177873087980216425864602058752"}, + {"0x1.469ae42c8560dp+674", 203, "1.0000000000000001628124053612615373751136081214084190333361076656341132058174738739526654646640697992206279476158887504364704121840108339451823712339845344488589385918977339967333617071438142709626935706e+203"}, + {"0x1.98419d37a6b8fp+677", 204, "999999999999999988769107875063294476509344598295499229975034848840292611823618668444426969460006898451859205345556422454814926130757381236415253871945426239147431949662390511778730879802164258646020587520"}, + {"0x1.98419d37a6b90p+677", 204, "1.00000000000000012800374586402188879539275541678583507266389310227534908701870283285101776562325721906687419916182228484013931497336014340342894776157671280691663726345066529974243554167548426849935897395e+204"}, + {"0x1.fe52048590672p+680", 205, "9999999999999999052283250816881378851792981072012977243617198967792587267065681698004724917620567060828502090557969050236202928251957239362070375381666542984859087613894256390005080826781722527340175556608"}, + {"0x1.fe52048590673p+680", 205, "1.000000000000000016616035472855013340286026761993566398512806499527303906862635501325745128692656962574862204108809594931879803899277933669817992649871683552701273012420045469371471812176828260616688264806e+205"}, + {"0x1.3ef342d37a407p+684", 206, "99999999999999986067324092522138770313660664528439025470128525568004065464414123719036343698981660348604541103459182906031648839556284004276265549348464259679976306097717770685212259087870984958094927200256"}, + {"0x1.3ef342d37a408p+684", 206, "1.0000000000000000388935775510883884313073724929520201333430238200769129428938489676307996560787770138732646031194121329135317061140943756165401836722126894035443458626261694354456645580765594621932224066355e+206"}, + {"0x1.8eb0138858d09p+687", 207, "999999999999999896317308250394787848770759814817916230429632968559415112294082783278450680807608685563489249451555889830959531939269147157518161129230251958148679621306976052570830984318279772103403898929152"}, + {"0x1.8eb0138858d0ap+687", 207, "1.00000000000000003889357755108838843130737249295202013334302382007691294289384896763079965607877701387326460311941213291353170611409437561654018367221268940354434586262616943544566455807655946219322240663552e+207"}, + {"0x1.f25c186a6f04cp+690", 208, "9999999999999999818630698308109481982927274216983785721776674794699138106539424938898600659703096825493544616522696356805028364441642842329313746550197144253860793660984920822957311285732475861572950035529728"}, + {"0x1.f25c186a6f04dp+690", 208, "1.000000000000000095924085271365828664322017564205661694508380160683912075133755441371739246187244345197174744586554630146560575784024467000148992689405664381702612359153846788595597987579871338229149809718067e+208"}, + {"0x1.37798f428562fp+694", 209, "99999999999999989061425747836704382546929530769255207431309733449871519907009213590435672179676195243109823530484164010765664497227613801915728022751095446033285297165420831725583764136794858449981115862089728"}, + {"0x1.37798f4285630p+694", 209, "1.0000000000000000731118821832548525711161595357042050700422376244411124222377928518753634101438574126676106879996976312533490279160524304467054690825284743904393057605427758473356246157785465878147788484850483e+209"}, + {"0x1.8557f31326bbbp+697", 210, "999999999999999927113782419344605574598668153294882673458925392487194643703632279098558059466181044478400725843812838336795121561031396504666917998514458446354143529431921823271795036250068185162804696593727488"}, + {"0x1.8557f31326bbcp+697", 210, "1.00000000000000007311188218325485257111615953570420507004223762444111242223779285187536341014385741266761068799969763125334902791605243044670546908252847439043930576054277584733562461577854658781477884848504832e+210"}, + {"0x1.e6adefd7f06aap+700", 211, "9999999999999999563134023721266549739021664297767471527755878388779781994104643936539191296017163181162427182749897969201059028320356032930746282153172616351711759756540926280845609521557638656931995269719916544"}, + {"0x1.e6adefd7f06abp+700", 211, "1.00000000000000007311188218325485257111615953570420507004223762444111242223779285187536341014385741266761068799969763125334902791605243044670546908252847439043930576054277584733562461577854658781477884848504832e+211"}, + {"0x1.302cb5e6f642ap+704", 212, "99999999999999990959401044767537593501656918740576398586892792465272451027953301036534141738485988029569553038510666318680865279842887243162229186843277653306392406169861934038413548670665077684456779836676898816"}, + {"0x1.302cb5e6f642bp+704", 212, "1.0000000000000000964715781454804920905589581568896966534955675815537392668032585435196522662522856315778842819446391981199976529328557958774316372559707169414929317175205124911858373485031031322390947127876596531e+212"}, + {"0x1.7c37e360b3d35p+707", 213, "999999999999999984345037526797422397233524775199337052919583787413130412889023223627065756931830180808571031008919677160084252852199641809946030023447952696435527124027376600704816231425231719002378564135125254144"}, + {"0x1.7c37e360b3d36p+707", 213, "1.00000000000000013384709168504151532166743595078648318702089551293394221810800365015051443602577078183432203225654570510663545295974118056659350633347830502317873324868489112134617772086239360331800009567183778611e+213"}, + {"0x1.db45dc38e0c82p+710", 214, "9999999999999999544446266951486038123467425400819078260993214423089680518452271383223760211130420606034208307593944715707740128306913340586165347614418822310868858990958736965765439335377993421392542578277827477504"}, + {"0x1.db45dc38e0c83p+710", 214, "1.000000000000000074046270021743878151893871480551624733380370822725617496020411479541134964388194541424021631757495293928014972916724565063934515809466164092481450798821885313089633125087528849591751483057152773325e+214"}, + {"0x1.290ba9a38c7d1p+714", 215, "99999999999999990660396936451049407652789096389402106318690169014230827417515340183487244380298106827518051036015414262787762879627804165648934234223216948652905993920546904997130825691790753915825536773603473752064"}, + {"0x1.290ba9a38c7d2p+714", 215, "1.0000000000000000979665986870629330198032972686455681148365806988089473848554483477848867530432250375881417919571154583994631649339312112649981120190710204647603637787670876363922509633974747510822509281030267784397e+215"}, + {"0x1.734e940c6f9c5p+717", 216, "999999999999999868331443500000006287872809702943711652856965888408980452039094412644869581954932274412588254040761879473560521568747407734787588406864399290882799171293145332687119715621994096773456255662636329336832"}, + {"0x1.734e940c6f9c6p+717", 216, "1.00000000000000002142154695804195744249313474674494929417670909534229174058333036940488102934712744986295727931833093209082895047886994342159460414833548007346784224294244020182387388080564786631265270395622996207206e+216"}, + {"0x1.d022390f8b837p+720", 217, "9999999999999999601855055748251769806450047292244542376488118125689672251656359867008764503902493796828096692073033110439215789148209291468717978517470477604338250142827222541691722147321863584969741246387925089779712"}, + {"0x1.d022390f8b838p+720", 217, "1.000000000000000082657588341258737904341264764265444350704606378115616256001024752108885608304005520043104889429358553137736322042918957696317410444923912386501859471602158149478575546879109374128331283273667415166157e+217"}, + {"0x1.221563a9b7322p+724", 218, "99999999999999988670225591496504042642724870819986016981533507324097780666440272745607095564199569546663253707407016578763273303796211201720443029584092898479300433989106071698353021544403254911815982945786756526505984"}, + {"0x1.221563a9b7323p+724", 218, "1.0000000000000000826575883412587379043412647642654443507046063781156162560010247521088856083040055200431048894293585531377363220429189576963174104449239123865018594716021581494785755468791093741283312832736674151661568e+218"}, + {"0x1.6a9abc9424febp+727", 219, "999999999999999965084388885482519417592855130626093842171043595190833186399051537317196816706799625297221478016185520727674168639944850288849622355474122345476546392575499689981548348018063279122228410984187505225498624"}, + {"0x1.6a9abc9424fecp+727", 219, "1.00000000000000012184865482651747739992406797547856118688246063909054394586834915703944853883640748495839935990041623060775703984391032683214000647474050906684363049794437763597758461316612473913036557403682738514637619e+219"}, + {"0x1.c5416bb92e3e6p+730", 220, "9999999999999999964372420736895110140590976995965873111133270039707753382929110612616471611327211972294570543930316627036907428807379455975076991793273996897499632136492752791807556010476755711238558435947154812096741376"}, + {"0x1.c5416bb92e3e7p+730", 220, "1.000000000000000121848654826517477399924067975478561186882460639090543945868349157039448538836407484958399359900416230607757039843910326832140006474740509066843630497944377635977584613166124739130365574036827385146376192e+220"}, + {"0x1.1b48e353bce6fp+734", 221, "99999999999999984594354677029595135102113336853821866019036664182705300920238534632828550788829765195472628778417018121881118652493108811594893042483166843723756247249515245102456078650553656951604416706418119648563167232"}, + {"0x1.1b48e353bce70p+734", 221, "1.0000000000000000466018071748206975684050858099493768614209804580186827813230862995727677122141957123210339765959854898653172616660068980913606220974926434405874301273673162218994872058950552383264597357715602427843549594e+221"}, + {"0x1.621b1c28ac20bp+737", 222, "999999999999999886075198851200900594497923856820450300436489405065378963626525536977181948753477264027987825546533242948112401553146250111031268759363863437907536003469585205199546070383440303278127280805657005745376329728"}, + {"0x1.621b1c28ac20cp+737", 222, "1.00000000000000004660180717482069756840508580994937686142098045801868278132308629957276771221419571232103397659598548986531726166600689809136062209749264344058743012736731622189948720589505523832645973577156024278435495936e+222"}, + {"0x1.baa1e332d728ep+740", 223, "9999999999999999181805205159248599892793562474462356126333876156560397271658376894962991014456209536865970557564236923315533735757183797070971394269896194384435148282491314085395342974857632902877937717988376531531720556544"}, + {"0x1.baa1e332d728fp+740", 223, "1.00000000000000004660180717482069756840508580994937686142098045801868278132308629957276771221419571232103397659598548986531726166600689809136062209749264344058743012736731622189948720589505523832645973577156024278435495936e+223"}, + {"0x1.14a52dffc6799p+744", 224, "99999999999999996954903517948319502092964807244749211214842475260109694882873713352688654575305085714037182409224841134505892881183378706080253249519082903930108094789640533388351546084948006950326015738792668900564521713664"}, + {"0x1.14a52dffc679ap+744", 224, "1.0000000000000001750230938337165351475308153724525181102085733003813258354803349096492363229827704709554708974355472873990811497562954164756241047679956674427313454264855010352594401143043471863651256997442828324155378630656e+224"}, + {"0x1.59ce797fb817fp+747", 225, "999999999999999928454223448636526995609414612446486912536395043045051171498417578302416590307106934377352009423588636134254484622941461177838218040629861358615028052178586193608330530158506646130887048916655460323666687950848"}, + {"0x1.59ce797fb8180p+747", 225, "1.00000000000000009283347037202319909689034845245050771098451388126923428081969579920029641209088262542943126809822773697747226137851076470969547585887373208135923963504986275470907025292240033962037948280174037505158080469402e+225"}, + {"0x1.b04217dfa61dfp+750", 226, "9999999999999999613300728333138614158656013804472910722260188106898877933626732224819925546638620725877678611585164563028980399740553218842096696042786355031638703687528415058284784747112853848287855356936724432692495112994816"}, + {"0x1.b04217dfa61e0p+750", 226, "1.000000000000000092833470372023199096890348452450507710984513881269234280819695799200296412090882625429431268098227736977472261378510764709695475858873732081359239635049862754709070252922400339620379482801740375051580804694016e+226"}, + {"0x1.0e294eebc7d2bp+754", 227, "99999999999999988242803431008825880725075313724536108897092176834227990088845967645101024020764974088276981699468968789815350713138205618891818585152157755624664880897462875650012340778461641195382916742883168419985073526276096"}, + {"0x1.0e294eebc7d2cp+754", 227, "1.000000000000000092833470372023199096890348452450507710984513881269234280819695799200296412090882625429431268098227736977472261378510764709695475858873732081359239635049862754709070252922400339620379482801740375051580804694016e+227"}, + {"0x1.51b3a2a6b9c76p+757", 228, "999999999999999924509121522475246865178672200286390413373640190927670776874706901000867474584296317792102107215397297714017257980807797893073643852992008461269166974189675556141912776812173197487139230503413422370196749149011968"}, + {"0x1.51b3a2a6b9c77p+757", 228, "1.000000000000000092833470372023199096890348452450507710984513881269234280819695799200296412090882625429431268098227736977472261378510764709695475858873732081359239635049862754709070252922400339620379482801740375051580804694016e+228"}, + {"0x1.a6208b5068394p+760", 229, "9999999999999999918388610622944277578633427011520373324179896670642961784527024602806390495869308408470337715685294734193992593398889846197223766553446979093051960385337504355687757672562640543404353314227442034427503713670135808"}, + {"0x1.a6208b5068395p+760", 229, "1.000000000000000126498340141932789543232683702883331170506688619337546981608693578840182199592199886956897100274793824830163262058051358073019842260050076805377254167221900194422501748144445768047027533261405765587857615803016806e+229"}, + {"0x1.07d457124123cp+764", 230, "99999999999999988411127779858373832956786989976700226194703050524569553592790956543300452958271560395914310860351799229078805716535908585708440417158039479244754953558323062848579498254571868337516156995181495372666457581821100032"}, + {"0x1.07d457124123dp+764", 230, "1.0000000000000000995664443260051171861588155025370724028889488288828968209774953551282735695911460777349244345335409545480104615144188833823603491391090010261628425414842702426517565519668094253057090928936734531588361669158161613e+230"}, + {"0x1.49c96cd6d16cbp+767", 231, "999999999999999884111277798583738329567869899767002261947030505245695535927909565433004529582715603959143108603517992290788057165359085857084404171580394792447549535583230628485794982545718683375161569951814953726664575818211000320"}, + {"0x1.49c96cd6d16ccp+767", 231, "1.00000000000000005647541102052084141484062638198305837470056516415545656396757819718921976158945998297976816934753636209656598064460692387730516014560327977941978394030406231981856423808259127691959958830530175327240184869629512909e+231"}, + {"0x1.9c3bc80c85c7ep+770", 232, "9999999999999999185841044429711589466224211962102134844977374370276477415358432917842475759840644797632681207523216662519436418612086534611285553663849717898419964165273969667523488336530932020840491736225123136358120303938278260736"}, + {"0x1.9c3bc80c85c7fp+770", 232, "1.000000000000000056475411020520841414840626381983058374700565164155456563967578197189219761589459982979768169347536362096565980644606923877305160145603279779419783940304062319818564238082591276919599588305301753272401848696295129088e+232"}, + {"0x1.01a55d07d39cfp+774", 233, "99999999999999997374062707399103193390970327051935144057886852787877127050853725394623645022622268104986814019040754458979257737456796162759919727807229498567311142603806310797883499542489243201826933949562808949044795771481474727936"}, + {"0x1.01a55d07d39d0p+774", 233, "1.0000000000000001943667175980705238830588315677559032649033928912832653863993131025941919471948554861962682179427510579411883194280051942934817649248215877689975714640807276728847796425120893517551500029880911929089916669987624321024e+233"}, + {"0x1.420eb449c8842p+777", 234, "999999999999999841364972759543336764420226292177420345984153909836074800974071744757463152045042997962028093539001436578955132142505622028069656690022719315678435403212464369035268207172574280176140941400150227439321732144446136385536"}, + {"0x1.420eb449c8843p+777", 234, "1.00000000000000001786584517880693032373952892996666180544377340055967009368669242367582754961994924207914815574087624726007172578525540816077571080742215354233800343364659602096002392484233181596564547219412071017415669957160428424397e+234"}, + {"0x1.9292615c3aa53p+780", 235, "9999999999999999119653217272487741881479473472931169297680017061255129180591200163248089110750054956088761184197513608514017695996055364811520783369824930063422626153861170298051704942404772944919427537177384205332557191153093955289088"}, + {"0x1.9292615c3aa54p+780", 235, "1.000000000000000053166019662659649035603389457524510097335697298704389152229216559459500429134930490902572168181251209396295044513805365387316921630902040387669917039733422351344975068376283323123546378352914806721123693057035913815654e+235"}, + {"0x1.f736f9b3494e8p+783", 236, "99999999999999994020546131433094915763903576933939556328154082464128816489313932495174721468699049466761532837205133056038042458244550226238504699576640248260779350025557809411313140906763850021826347864477369777082931390365469918625792"}, + {"0x1.f736f9b3494e9p+783", 236, "1.0000000000000000531660196626596490356033894575245100973356972987043891522292165594595004291349304909025721681812512093962950445138053653873169216309020403876699170397334223513449750683762833231235463783529148067211236930570359138156544e+236"}, + {"0x1.3a825c100dd11p+787", 237, "999999999999999940205461314330949157639035769339395563281540824641288164893139324951747214686990494667615328372051330560380424582445502262385046995766402482607793500255578094113131409067638500218263478644773697770829313903654699186257920"}, + {"0x1.3a825c100dd12p+787", 237, "1.00000000000000012094235467165686896238200167043557881776819118314224974463086290016415235780369448864354627206677113669784381647262128326227604641198342313070719116342012890568408126396147021686671611817779947209130032054906464259329229e+237"}, + {"0x1.8922f31411455p+790", 238, "9999999999999999040580826428657651966904425891201589123842107529410958489455946099092661860636496958724291396331073693328877462044103460624068471125229983529879139676226679317989414380888721568885729507381685429067351125745727105048510464"}, + {"0x1.8922f31411456p+790", 238, "1.000000000000000048647597328726501040484815309997105515973531039741865112735773470079190300557012891053173894588883214242858459716550970862319646645496614871467432098154308581055701322003937530207335062364589162363111917890900665230478541e+238"}, + {"0x1.eb6bafd91596bp+793", 239, "99999999999999999081179145438220670296706622164632687453780292502155740721970192601122065475966761298087599260657287627887017431169472094235452683230716826407562484594165232135299736843791138087983021771402091458056119576436948334022754304"}, + {"0x1.eb6bafd91596cp+793", 239, "1.0000000000000001064834032030707953780025643983478841574092591544621728182518450141471599463543581691254717965711935522068467451214072207822847664586860614788592393503669648407584052755699636795348399070151574101456626400174318471207295386e+239"}, + {"0x1.33234de7ad7e2p+797", 240, "999999999999999828871535006218182557917368774264146678517764203804695831774701602620905646527100834378441867056103929979702975178097221166452191355376717763378564539746214794185426298453038162762816652692429820789419173810082174047524749312"}, + {"0x1.33234de7ad7e3p+797", 240, "1.00000000000000001394611380411992443797416585698663833111209417090968048942613054363840851307860572420979515339949701146446548847363722091034057475758294690703234774682671482523407894986432184061083215557424821369358148461498195609632794214e+240"}, + {"0x1.7fec216198ddbp+800", 241, "9999999999999999029013665253788793099400876073531433395554961906466896948352731790279067931477027903109831815934611625736079804963132210640075447162592094208400778225784148066048873590175516339020228538451571779510840981320420868670460264448"}, + {"0x1.7fec216198ddcp+800", 241, "1.00000000000000005096102956370027281398552527353113666163096016433067742095641633184190908638890670217606581066817562776141799113274522085911825143802419273576310438824281483144380948014657857618043525615061189227441394677596191250608858071e+241"}, + {"0x1.dfe729b9ff152p+803", 242, "99999999999999993251329913304315801074917514058874200397058898538348724005950180959070725179594357268399970740840405561116998262359962102302968606061220608382468313571129481157267178324335702235770533430624812081575006786082605199485453729792"}, + {"0x1.dfe729b9ff153p+803", 242, "1.0000000000000000509610295637002728139855252735311366616309601643306774209564163318419090863889067021760658106681756277614179911327452208591182514380241927357631043882428148314438094801465785761804352561506118922744139467759619125060885807104e+242"}, + {"0x1.2bf07a143f6d3p+807", 243, "999999999999999885134206960780312089454635087411784140906440513804611167700736000690226517958758320887173266104495426751070779219941381088594259909647411423049314634698686803624216704482068400828613365568502612232284516294771707790360919932928"}, + {"0x1.2bf07a143f6d4p+807", 243, "1.0000000000000000746505756498316957746327953001196155931630344001201154571357992362921494533074993280744790313201299421914675928345743408263359645135065900661507886387491188354180370195272228869449812405194846465661467225589890846083353893929e+243"}, + {"0x1.76ec98994f488p+810", 244, "9999999999999999230374806985905888264902671299533504313577592910677120255877486478106111050285065223246344191476223298391501419428679730361426008304192471516696094355087732099829807674910992980518869405586990190990569575476151831539558138249216"}, + {"0x1.76ec98994f489p+810", 244, "1.000000000000000074650575649831695774632795300119615593163034400120115457135799236292149453307499328074479031320129942191467592834574340826335964513506590066150788638749118835418037019527222886944981240519484646566146722558989084608335389392896e+244"}, + {"0x1.d4a7bebfa31aap+813", 245, "99999999999999992303748069859058882649026712995335043135775929106771202558774864781061110502850652232463441914762232983915014194286797303614260083041924715166960943550877320998298076749109929805188694055869901909905695754761518315395581382492160"}, + {"0x1.d4a7bebfa31abp+813", 245, "1.0000000000000000443279566595834743850042896660863625608019793783096347708261891185958417836517007669245101088856284197210041026562330672682972917768891214832545527981010497103310257691199981691663623805273275210727287695567143043174594742793011e+245"}, + {"0x1.24e8d737c5f0ap+817", 246, "999999999999999874521290314193434603084658115500145579580071256170942927492372459496518833579228824484684143252419893886408557657521935343280724451831297419035632090471862609843762766839539749606096764571247618309588232743975534688554349643169792"}, + {"0x1.24e8d737c5f0bp+817", 246, "1.00000000000000006858605185178205149670709417331296498669082339575801931987387721275288791937633961584448524683322963769737489479890608611472822996618309634957154147061950501040063476944577794338925746852105322146746313195853412855016020637017702e+246"}, + {"0x1.6e230d05b76cdp+820", 247, "9999999999999999521471949292288813605336325386252733424243721120057734844449743607990664678980731410286045846847437914107950925140755956518597266575720169912499958425309195700665115678820350271193610461511698595727381924297989722331966923339726848"}, + {"0x1.6e230d05b76cep+820", 247, "1.00000000000000010739900415929977487543158138487552886811297382367543459835017816340416173653576177411644546754939158645956816222718291626901773106905345613567872334664903349051200916996702558214588960931101434209903811180144584732248137771557847e+247"}, + {"0x1.c9abd04725480p+823", 248, "99999999999999992109683308321470265755404276937522223728665176967184126166393360027804741417053541441103640811181423240104047857145413152842812577527572916236425034170729678597741204746503691611405533351920096306747820855546959721533975525765152768"}, + {"0x1.c9abd04725481p+823", 248, "1.0000000000000000452982804672714174694724018463754266578375331390075701527880966423621236290806863208813091144035324684400589343419399880221545293044608804779072323450017879223338101291330293601352781840470765490885181440527870972867675035629361562e+248"}, + {"0x1.1e0b622c774d0p+827", 249, "999999999999999921096833083214702657554042769375222237286651769671841261663933600278047414170535414411036408111814232401040478571454131528428125775275729162364250341707296785977412047465036916114055333519200963067478208555469597215339755257651527680"}, + {"0x1.1e0b622c774d1p+827", 249, "1.00000000000000011981914889770544635662341729257554931016806196060900748746259446761256935802677686476347273817856341006347000780423150191839037142197197126723302154697848260414764897813382482654801189436380190070114210535117759732962415254610693325e+249"}, + {"0x1.658e3ab795204p+830", 250, "9999999999999999210968330832147026575540427693752222372866517696718412616639336002780474141705354144110364081118142324010404785714541315284281257752757291623642503417072967859774120474650369161140553335192009630674782085554695972153397552576515276800"}, + {"0x1.658e3ab795205p+830", 250, "1.000000000000000080074685734807297616809542387935483895591779922421574242302862294145664969255528574692985472165213574530984101957676027840397922292632722846259267305924245440513601592000067244461220582194881713174409325992035997306767273088415852134e+250"}, + {"0x1.bef1c9657a685p+833", 251, "99999999999999992109683308321470265755404276937522223728665176967184126166393360027804741417053541441103640811181423240104047857145413152842812577527572916236425034170729678597741204746503691611405533351920096306747820855546959721533975525765152768000"}, + {"0x1.bef1c9657a686p+833", 251, "1.0000000000000000482791152044887786249584424642234315639307542918716276461750765553721414582385299426365956593545337061049953772804316485780039629891613241094802639130808557096063636830930611787917875324597455631530231025047227172884817695222629872435e+251"}, + {"0x1.17571ddf6c813p+837", 252, "999999999999999895660376658959887464073162830405580371957831265231883984761705009259228605356936508765924557863270337660249498829658628118512958332498610172941047627432585001251621720339432063578508893731092043050369229765618973200711352404729235767296"}, + {"0x1.17571ddf6c814p+837", 252, "1.00000000000000009915202805299840901192020234216271529458839530075154219997953373740977907586572775392681935985162149558657733676402265539783429787471556208832666934163027927905794433734427088386288041203596340318724106008442396531773857522810757106893e+252"}, + {"0x1.5d2ce55747a18p+840", 253, "9999999999999999363587069377675917736425707327570073564839440723358156278052707548893386994586947577981035182609405692455150664165314335743772262409420005560181719702721238568128862437403998276353831973920663150777435958293799716241167969694049028276224"}, + {"0x1.5d2ce55747a19p+840", 253, "1.000000000000000099152028052998409011920202342162715294588395300751542199979533737409779075865727753926819359851621495586577336764022655397834297874715562088326669341630279279057944337344270883862880412035963403187241060084423965317738575228107571068928e+253"}, + {"0x1.b4781ead1989ep+843", 254, "99999999999999993635870693776759177364257073275700735648394407233581562780527075488933869945869475779810351826094056924551506641653143357437722624094200055601817197027212385681288624374039982763538319739206631507774359582937997162411679696940490282762240"}, + {"0x1.b4781ead1989fp+843", 254, "1.0000000000000000665933638299522455642646760202815737069675050550683968855446811409056910005843211547010761915334853103183648826945244110331428835479608497818649698673586481946089327186234966726173809691071839855653415672334151665790142195763670374206669e+254"}, + {"0x1.10cb132c2ff63p+847", 255, "999999999999999988452569694641453289891412847766833896677368465428848130901034909295879619908945316559292587569958465674654992927728624557883489163749540246356891129106733591931304833693638565628182306078113383272782784390994049606075766012189756664840192"}, + {"0x1.10cb132c2ff64p+847", 255, "1.00000000000000019682802072213689935488678130780614005745106603780097814328409152692204330170994755160404886480603005139121469897251738849190854085497969900771176776444517253240497919350659351759937874082230165605293953863745036153391164218332917201371136e+255"}, + {"0x1.54fdd7f73bf3bp+850", 256, "9999999999999998634272990781441856508941917717432502002131499220055701234712009387201814108283439755324388212283155142447191693008553661974684581490114449895439651479036702276471002178058655944454644452316004196046887318431202624493742403095061074555174912"}, + {"0x1.54fdd7f73bf3cp+850", 256, "1.000000000000000030127659900140542502890486539774695128832107979903274133377646232821112356269145763568243843017172782817966934136686377344688499501995571998627866456174421380026039705656229556022421593026951037828814135240285311991642941246417639734614426e+256"}, + {"0x1.aa3d4df50af0ap+853", 257, "99999999999999989676737124254345702129345072534953918593694153358511092545248999754036759991650433313959982558608696795936872226802156842691246641960827039136074540955782045812288811537593838676085587479067054324951381252255327235782798049688841391133687808"}, + {"0x1.aa3d4df50af0bp+853", 257, "1.0000000000000000301276599001405425028904865397746951288321079799032741333776462328211123562691457635682438430171727828179669341366863773446884995019955719986278664561744213800260397056562295560224215930269510378288141352402853119916429412464176397346144256e+257"}, + {"0x1.0a6650b926d66p+857", 258, "999999999999999843423255779504622828654636399579476808778874955057845642282427503428069697375447760968142218613652642015929437520555644859802053186653349748453896990911180089361627479263821919056229587496158345417793683435460456504301996197076723582025859072"}, + {"0x1.0a6650b926d67p+857", 258, "1.0000000000000000567997176316599595992098937026597263174111412691669067749626774798772613075396740496539726465033899457896865765104193391282437061184730323200812906654977415644066700237122877898747347366742071367446741997838317199184059333963234848992699351e+258"}, + {"0x1.4cffe4e7708c0p+860", 259, "9999999999999999287738405203667575368767393208115766122317814807014700953545274940077463414411382764424743897695475635254322931165011225671787143593812227771048544607458046793796444970432082673836316471673778619485458899748089618699435710767754281089234894848"}, + {"0x1.4cffe4e7708c1p+860", 259, "1.00000000000000009947501000209102695332094516327577621913759453198871900149872747516709962957251930739113873208133740654443800430839207798193203670483696883440676940041505385941567853260198096403843576650981689501005030305350597260122672083617283716271875031e+259"}, + {"0x1.a03fde214caf0p+863", 260, "99999999999999992877384052036675753687673932081157661223178148070147009535452749400774634144113827644247438976954756352543229311650112256717871435938122277710485446074580467937964449704320826738363164716737786194854588997480896186994357107677542810892348948480"}, + {"0x1.a03fde214caf1p+863", 260, "1.0000000000000000653347761057461730700321039947829362977564319217312692202698874789352289719462431012014058636189794379406368620700138868989813722357458196229463864124812040234084717254902264247074749426413290883977494204377665704549700908842933553519596981453e+260"}, + {"0x1.0427ead4cfed6p+867", 261, "999999999999999928773840520366757536876739320811576612231781480701470095354527494007746341441138276442474389769547563525432293116501122567178714359381222777104854460745804679379644497043208267383631647167377861948545889974808961869943571076775428108923489484800"}, + {"0x1.0427ead4cfed7p+867", 261, "1.00000000000000014727133745697382238992532279916575210907122218634914869521910346989171855024930599605676474792863856258975960344212154549806296696156457773045130558352244362982576806255843731910178091992569982426727153871554113560598600276880411169778142334157e+261"}, + {"0x1.4531e58a03e8bp+870", 262, "9999999999999998413748417457239315956573059294699064134960051984423986554086971036541574579178711885967582465059111638997013689862529533948250133185078807957662740116351490992011950708371166466963719380640490770210556304785160923755265983999639546733803159420928"}, + {"0x1.4531e58a03e8cp+870", 262, "1.000000000000000016172839295009583478096172712153246810967557762960541535300357884361335224964405364288190533033183963151163217246749291739532415400254564758443434909856460259558093923249299888070891356270706646876036149471101831364360543753586901544466663027507e+262"}, + {"0x1.967e5eec84e2ep+873", 263, "99999999999999987633444125558106197214507928600657449299031571134602723138702925979559301132717802373504470381136572374999373863835222106376649373485721758830170619127941133127257484131955329497127582170538059099205173427703324017329338747068854404759758535917568"}, + {"0x1.967e5eec84e2fp+873", 263, "1.0000000000000000161728392950095834780961727121532468109675577629605415353003578843613352249644053642881905330331839631511632172467492917395324154002545647584434349098564602595580939232492998880708913562707066468760361494711018313643605437535869015444666630275072e+263"}, + {"0x1.fc1df6a7a61bap+876", 264, "999999999999999932269800471352470574525516656465243420181212531991832952952360709621889896782068959956303035500093019510461530081711049334072862401016156456358397678710230902586782474091451932211122035531511013345645500354660676649720249983847887046345216426508288"}, + {"0x1.fc1df6a7a61bbp+876", 264, "1.0000000000000000441405189028952877792863913973825812745630061732834443960830236092744836676918508323988196988775476110313971129684287058746855997333340341924717806535718700452151977396352492066908144631837718580528330325099155496025739750101665730438404785611735e+264"}, + {"0x1.3d92ba28c7d14p+880", 265, "9999999999999998875215130987353436926211667600983082784284950754751883757000955497608523884181562109792963701491111829020872969270239867178277674680890053619130444887655752455354163678739330224192450644706066754627704874925587274685787599733204126473471115726422016"}, + {"0x1.3d92ba28c7d15p+880", 265, "1.000000000000000066514662589203851220238566345566048845439364901541766684709156189205002421873807206887323031553038529335584229545772237182808147199797609739694457248544197873740880792744008661586752948714224026994270538940966524193144720015430310243339530988106547e+265"}, + {"0x1.8cf768b2f9c59p+883", 266, "99999999999999988752151309873534369262116676009830827842849507547518837570009554976085238841815621097929637014911118290208729692702398671782776746808900536191304448876557524553541636787393302241924506447060667546277048749255872746857875997332041264734711157264220160"}, + {"0x1.8cf768b2f9c5ap+883", 266, "1.0000000000000000307160326911101497147150864284725007320371909363284510229073440613161724151826770077057176992722530600488848430220225870898120712534558888641381746965884733480997879077699935337532513718655005566879705286512849648482315280070083307241410471050136781e+266"}, + {"0x1.f03542dfb8370p+886", 267, "999999999999999973438224854160227305877518561122823750593712591987145964024444656694044404476868689015149167622996309190165824584023146941018349739309135463248122613459314107074039291811569329219648848907543004197890512187794469896370420793533163493423472892065087488"}, + {"0x1.f03542dfb8371p+886", 267, "1.00000000000000008799384052806007212355265429582217771348066928066975608179024346593830042588848532639628623092150981090760386146002202723860579276760264226502822677971763258912553652372841773828685389482345810917805054511477545980009263522048349795485862131796226867e+267"}, + {"0x1.362149cbd3226p+890", 268, "9999999999999999734382248541602273058775185611228237505937125919871459640244446566940444044768686890151491676229963091901658245840231469410183497393091354632481226134593141070740392918115693292196488489075430041978905121877944698963704207935331634934234728920650874880"}, + {"0x1.362149cbd3227p+890", 268, "1.000000000000000156727209932399979014157735736641790091212843293879322152449722751484854038735455308824968468900617911938066683585621355417158258584578746346096289279472623678356434862878526783727176922373007172166146564870964053742325963876653698631719710373500577382e+268"}, + {"0x1.83a99c3ec7eafp+893", 269, "99999999999999990012263082286432662256543169091523721434606031123027548865433341877772055077343404109122144711194766809100548098338386355056238620120129111010885594705399027856108106338478634741663761952135733701058809111452663635798820356028494943810497789949089153024"}, + {"0x1.83a99c3ec7eb0p+893", 269, "1.0000000000000000467538188854561279891896054313304102868413648727440164393945558946103682581803033369390768881340449502893261681846624303314743132774169798163873892798646379355869975202383523110226600782937286713851929332610623034347526380267813775487419678846392834458e+269"}, + {"0x1.e494034e79e5bp+896", 270, "999999999999999929448868435382686895890266438998271828845121223533023678802377913944250092254807900260792535316367124530669618423639576906744771616444428851364562613616119809966264354755499540137842111275831603885509059543833769773341090453584235060232375896520569913344"}, + {"0x1.e494034e79e5cp+896", 270, "1.00000000000000004675381888545612798918960543133041028684136487274401643939455589461036825818030333693907688813404495028932616818466243033147431327741697981638738927986463793558699752023835231102266007829372867138519293326106230343475263802678137754874196788463928344576e+270"}, + {"0x1.2edc82110c2f9p+900", 271, "9999999999999999529098585253973751145501342374646995204443699533752222309208135100774737254399069875964494058799026896824009283758441475916906799486389390443691279468658234350904109878520700943148057046794110173854458342872794765056233999682236635579342942941443126198272"}, + {"0x1.2edc82110c2fap+900", 271, "1.000000000000000140597779245514880863829076625196121053238359792112810647868298279143262790920699686281704370388187210896251407993480713071257946606195020588405650612863452436083584052624634527730514451908046325384940032234845130363881876085339091539549641475134254271693e+271"}, + {"0x1.7a93a2954f3b7p+903", 272, "99999999999999991537227438137387396469434575991841521388557198562770454753131655626431591234374844785939841297824578543963083245231683449577722661712772273556182341366629763489177637489755720763166395523368395578554699469776634573397170474480057796161122485794632428945408"}, + {"0x1.7a93a2954f3b8p+903", 272, "1.0000000000000000655226109574678785641174996701035524401207638566177752810893043715169471647283826068076023845848734024107112161464260868794310399431725879707910415464644008356863148267156087543642309530165922021851423530558188688205784856384929203469035026027382776109466e+272"}, + {"0x1.d9388b3aa30a5p+906", 273, "999999999999999945402341696592674884578976541955442659132610359825718694242914119314842162820675279649039207299571308833846909191138684972507989282336695782607667040225918275050684065261167516978177354790265605065466066369376850351293060923539046438669680406904714953752576"}, + {"0x1.d9388b3aa30a6p+906", 273, "1.00000000000000006552261095746787856411749967010355244012076385661777528108930437151694716472838260680760238458487340241071121614642608687943103994317258797079104154646440083568631482671560875436423095301659220218514235305581886882057848563849292034690350260273827761094656e+273"}, + {"0x1.27c35704a5e67p+910", 274, "9999999999999999213782878444176341486712719163258207029349796604673073768736360688744211624391338142173265718425108901184740478000812045911233791501695173449709921389782217629235579129702792695009666351450002856415308090320884466574359759805482716570229159677380024223137792"}, + {"0x1.27c35704a5e68p+910", 274, "1.000000000000000113570718661817960035932908921362796352516025255334597915827860472397789165491465537671027655498994239841456938928541047642200260207506944846064391348959793859940567131297385249318652392307122841033012867730395676208292655524474469910197031481071702673824154e+274"}, + {"0x1.71b42cc5cf601p+913", 275, "99999999999999995981677400789769932612359931733321583285118877944076548466448094957909476304960015890806678857380756006307062602577317320133875536163700284518967198097453618232695975663570046546450378657742479671982722077174989256760731188933351130765773907040474247261585408"}, + {"0x1.71b42cc5cf602p+913", 275, "1.0000000000000001135707186618179600359329089213627963525160252553345979158278604723977891654914655376710276554989942398414569389285410476422002602075069448460643913489597938599405671312973852493186523923071228410330128677303956762082926555244744699101970314810717026738241536e+275"}, + {"0x1.ce2137f743381p+916", 276, "999999999999999929065985077113647184161737396527299728918221484261998998431805045015355882561227083155474615188770224107393363445219598313166454392463014445014728107377484646804238281703363508693674065431485187857190091380020735839470243162305319587149880588271350432374194176"}, + {"0x1.ce2137f743382p+916", 276, "1.00000000000000005206914080024985575200918507975096414465009066497706494336250866327031140451471938616584330872891956793010241376743389786585565826915896804571450360176569078889512418143271133577699295001524362330773860894693736275201851807041808646918131451680491859334083379e+276"}, + {"0x1.20d4c2fa8a030p+920", 277, "9999999999999998060628293539774386163142897133036353131863523035469330535011014267604003606077347801451059216486208802846843131230052987604772505157670608443149526129892785047133523819740156816103551808477267524066415738131041089269219682541925527051184466597377822714075545600"}, + {"0x1.20d4c2fa8a031p+920", 277, "1.000000000000000002867878510995372324870206006461498378357342992691038565390227215968329195733322464961695831312859830401018793638548178044779976718480586605434593404010408332058769821540972204943665396181740249127519201920170711986999208107172979716368740945391491328954177946e+277"}, + {"0x1.6909f3b92c83dp+923", 278, "99999999999999996350686867959178558315902274782992576532314485486221746301240205812674342870820492799837784938001204037775189753543960218791943147793788145321066524580618236658968633362758090027700335311493754978334367629875739137498376013657689431411868208826074951744485326848"}, + {"0x1.6909f3b92c83ep+923", 278, "1.000000000000000120950908005206132550003755782356216217459937406177501872523702689493086496808675075851649777111403200470819481947873905615361612440108702062106377878623086228466020285281146118943651525382148347160045778784410673823045552018961235923118917516783716763482151977e+278"}, + {"0x1.c34c70a777a4cp+926", 279, "999999999999999932018060814468916189790076140924667674895786344599160581110141931853474815088110898427723463833733808359138380652952741502430995285503717331431522719242801594214419543296867856543673718661495390308003255801626734885371401760100025992318635002556156068237393526784"}, + {"0x1.c34c70a777a4dp+926", 279, "1.00000000000000005797329227496039376326586256854570003660522038565138810871918243694654926956848701671034100601884673643359244818290018424438474005524037381854809282549632468371548670461972003147699225647526402820936493779014936084382083526600749927951882334537452986506723249357e+279"}, + {"0x1.1a0fc668aac6fp+930", 280, "9999999999999998312538756460757341310094469988278417855282391117573785590229095277790152515038100038016294300856434658995751266289947873088679994697143921417382666342399831226135658142385861165970188884104804799869139102108086341186118549553740473625584843283014570307735223533568"}, + {"0x1.1a0fc668aac70p+930", 280, "1.000000000000000032782245982862098248570705283021493564263333577440942603197374335927934378672411793053817497581824150818701634676910695695993991101293042521124778804245620065815273272355149596490328548912510300629092601392444835652130948564826004622078785676810855105701264700211e+280"}, + {"0x1.6093b802d578bp+933", 281, "99999999999999987155954971343300695452169865566657214127525800489409136785780248940879907693753036165206704358487960288340042823857796898629319779603012221761556906824111051125390730586189881257568082051088644411534964844713587442531567367726443881446254459800333664575907082272768"}, + {"0x1.6093b802d578cp+933", 281, "1.0000000000000000327822459828620982485707052830214935642633335774409426031973743359279343786724117930538174975818241508187016346769106956959939911012930425211247788042456200658152732723551495964903285489125103006290926013924448356521309485648260046220787856768108551057012647002112e+281"}, + {"0x1.b8b8a6038ad6ep+936", 282, "999999999999999903804088967318825213331499981137556425872873119403461614925716858712626137284506647932417134384268512470460669526244514328233356457082706278317411015442012422166180499160548969358610366191211215418098239036197666670678728654776751975985792813764840337747509598224384"}, + {"0x1.b8b8a6038ad6fp+936", 282, "1.0000000000000000327822459828620982485707052830214935642633335774409426031973743359279343786724117930538174975818241508187016346769106956959939911012930425211247788042456200658152732723551495964903285489125103006290926013924448356521309485648260046220787856768108551057012647002112e+282"}, + {"0x1.137367c236c65p+940", 283, "9999999999999999553953517735361344274271821018911312812290573026184540102343798495987494338396687059809772796632907678097570555865109868753376103147668407754403581309634554796258176084383892202112976392797308495024959839786965342632596166187964530344229899589832462449290116390191104"}, + {"0x1.137367c236c66p+940", 283, "1.000000000000000161760402998405371283809910584905430702653794035478423591469031813143242620060316938175217860779379789166942599827576877063754625745503378763932146593049227709464366045549750223622046731633809385840086963748692004633583168474875257268171778539856869873655019802198016e+283"}, + {"0x1.585041b2c477ep+943", 284, "99999999999999991412234152856228705615063640528827139694410995604646009398744945688985079659553905954212916344007296353831994673829780883765420722861953317774200043854630103365810792101611701952914782080891514223497778802469744018919490624758069218767323224280852151918381000638332928"}, + {"0x1.585041b2c477fp+943", 284, "1.0000000000000000792143825084576765412568191916997109340838993423344357589751710277254453455720576452975216283329441806240683821311505209883878195732087635685354312082149188175289466707052058222577470946921779713050505718406938164854537477324437355746722631075074204221646165369264538e+284"}, + {"0x1.ae64521f7595ep+946", 285, "999999999999999980159157920520442850193109519852847211800025710561650359982538085224088616186146493844286149397221450372619320895438893697947652166455225334059372746413748147206443420891752540620587530362220273863006901551095990707698442841525909542472844588688081080376132618600579072"}, + {"0x1.ae64521f7595fp+946", 285, "1.00000000000000011223279070443675443827805574898199884151185721959203089197271534189256425536736136244860012131151842404121806920972106341853454204212660964669411736214864237430311442064302358280346694946883053711906512860389309174470551602941634425207206928044720020276077784303507866e+285"}, + {"0x1.0cfeb353a97dap+950", 286, "9999999999999998216707985798208689444911740448978652561458278997251937215943253772219178491686886515191093831000650819703008229183002900332433843156495641588976792075318750746904382211902272900011322274342879579557370290877394694632899550160573878909537749585771381335145583492791795712"}, + {"0x1.0cfeb353a97dbp+950", 286, "1.000000000000000032988611034086967485427088011504507863684758314173802572778608987891478871858632441286011738162940239840058820221151761586182408116723779059113270592707705838045111820792260957493739298004864379165430192372214831122501272116682083426312534465391728729329990708374378906e+286"}, + {"0x1.503e602893dd1p+953", 287, "99999999999999990619792356152730836086553963154052229916140006550463726206803882148974225824466616742587032512521514511820402183944087865441899383607925011898391576160220738003230766103104075699817505566251852643961429440152961412697448185630726610509727876130297437184073129291725930496"}, + {"0x1.503e602893dd2p+953", 287, "1.0000000000000000752521735249401871936142708048258363851925443970635243430154657100253910763966211992393922091755152714140104196817220558967702128769386220391563888697428719907160465407126676909922607121189796634073688250291099034543435355368070225333842863667546468484930771801934187725e+287"}, + {"0x1.a44df832b8d45p+956", 288, "999999999999999872387073568844732594315793396883459481955171199192859845878553443782612494614275161063165948315155119859042742270984643205948750027907375734949421139974074457895559885094715370199357924371226299046063388276013556261500671120207314819439877240212639876510262115462027411456"}, + {"0x1.a44df832b8d46p+956", 288, "1.00000000000000000763047353957503566051477833551171075078008666443996951063649495461113154913583918651398345555539522089568786054480958499982972526059487327108739962648660614644255098884001691739462644953639520862026701277807778772339591406460711996206948332457397785783213882528295498547e+288"}, + {"0x1.06b0bb1fb384bp+960", 289, "9999999999999998453383935746986719810759964091578092281901881061434379129269651416169086837099623559730024468671070996517137186162196548471725549813698762277218254426715681201861616643456550607603042193381925171312226633756007099691216225313273537909139560233403722802458867734978418966528"}, + {"0x1.06b0bb1fb384cp+960", 289, "1.000000000000000061727833527867156886994372310963011258310052850538813376539671558942539170944464796694310458451491261310345907854339561717382115353669872285542591021091618821861347430338137536272733859602462772449948462578903480308154011242367042019121325758318513050360889509211326015078e+289"}, + {"0x1.485ce9e7a065ep+963", 290, "99999999999999988861628156533236896225967158951884963421416105502251300564950642508203478115686284411726404918398393198344015646384363622121446705582987543928597855835557826052119881754415155586279014739104656819496782321626126403692810027353529143655542997033600043426888732064053872033792"}, + {"0x1.485ce9e7a065fp+963", 290, "1.0000000000000000617278335278671568869943723109630112583100528505388133765396715589425391709444647966943104584514912613103459078543395617173821153536698722855425910210916188218613474303381375362727338596024627724499484625789034803081540112423670420191213257583185130503608895092113260150784e+290"}, + {"0x1.9a742461887f6p+966", 291, "999999999999999957860902350346284132153551878096514283852517773229033154005572478626236537071903625148082612890986863714202457020042006419681526374965874177788623543449994485057258262661745948026767632275613049896960078961318150545418464661067991669581788285529005480705688196068853638234112"}, + {"0x1.9a742461887f7p+966", 291, "1.0000000000000000963501439203741144719413124552518435831292312096420734507177045857146400489019851872097197403049927271757270581324387468166156450132378716547939135136388269341293771528969347323547226020447460133009445904514319235623991934361333921356345049159150155735792899469254834740265e+291"}, + {"0x1.008896bcf54f9p+970", 292, "9999999999999997916738124663128877244082391855101191247204616495333847979510139501201523228758057506741180599941798275603729356851659179433605840090394772053822755792233955461707155943795194068332216685526534938121786651731816229250415901309895111103185283290657933692573660950408978352832512"}, + {"0x1.008896bcf54fap+970", 292, "1.000000000000000013256598978357416268068656108958646003563203147794249272690425321461597941803936249972737463856589209098812297465000702578455173830274673168590739531525527464686105818755821461757949620183266235258553883557363659752210756171094151856002874937683409517855128896411505572551066e+292"}, + {"0x1.40aabc6c32a38p+973", 293, "99999999999999992462348437353960485060448933957923525202610654848990348279466077292501969423268405025328970231162545648343655275306678872441733790178059478330735395060467469727994972900530063978805843953102113868000379620369084502134308975505229555772913629423636305841602377586326247764393984"}, + {"0x1.40aabc6c32a39p+973", 293, "1.0000000000000001018897135831752276855328228783380567551002997470985950625861898699981761893751884496921852254015529617141880421769346164324930097587687515538741251124463802320922619085063422837278408008355113318371039709110364744830784225871360081542766135811304559772942340169597486674581914e+293"}, + {"0x1.90d56b873f4c6p+976", 294, "999999999999999924623484373539604850604489339579235252026106548489903482794660772925019694232684050253289702311625456483436552753066788724417337901780594783307353950604674697279949729005300639788058439531021138680003796203690845021343089755052295557729136294236363058416023775863262477643939840"}, + {"0x1.90d56b873f4c7p+976", 294, "1.00000000000000006643646774124810311854715617058629245448546110737685674662788405058354489034668756980440612078356746066803774429216105089087787538737112019976077088007803912512979947260613395493988432857461329320568393596956734859073135602071926563496711812375163739351859196874045142949534106e+294"}, + {"0x1.f50ac6690f1f8p+979", 295, "9999999999999999813486777206230041577815560719820581330098483720446847883279500839884297726782854580737362697004022581572770293687044935910015528960168049498887207223940204684198896264456339658487887951484580004902758521100414464490983962613190835886243290260424727924570510530141380583845003264"}, + {"0x1.f50ac6690f1f9p+979", 295, "1.000000000000000094799064414789802772135689536787703894977332019154247399394528706115249929569488273714629404477955861504957982599979903324169982884489225283051454265972712010699769421326300617970249506383331724110819963922742649304609009273852659650414714489654692260539105607315889219865621299e+295"}, + {"0x1.3926bc01a973bp+983", 296, "99999999999999998134867772062300415778155607198205813300984837204468478832795008398842977267828545807373626970040225815727702936870449359100155289601680494988872072239402046841988962644563396584878879514845800049027585211004144644909839626131908358862432902604247279245705105301413805838450032640"}, + {"0x1.3926bc01a973cp+983", 296, "1.0000000000000001628692964312898819407481696156710913521578222074199849660344758793913420237042099630991652853444880235135665545387451491640710408775726774829490943921199269360676972982547006092431259331242559582831464310103633710179153770813728052874889457678220239413883383398969399167542938829e+296"}, + {"0x1.87706b0213d09p+986", 297, "999999999999999872436306494222877488001587945768638201521064070819504681704034606746682422062730755058478860313950798943503314266680100247159860107083281430052496520558476587831205023360193979812186512362979225814553504769848291707808207769286850569305558980974742103098278680884456943362624192512"}, + {"0x1.87706b0213d0ap+986", 297, "1.0000000000000000176528014627563797143748787807198647768394431391197448238692552430690122228834703590788220728292194112285349344027126247056154504923279794565007954563392017619494511608074472945276562227436175920488499678901058313628617924253298279283972523743983830222433085103906984300584590377e+297"}, + {"0x1.e94c85c298c4cp+989", 298, "9999999999999999595662034753429788238255624467393741467120915117996487670031669885400803025551745174706847878231119663145222863482996149222332143382301002459214758820269116923021527058285459686414683385913622455551313826420028155008403585629126369847605750170289266545852965785882018353801250996224"}, + {"0x1.e94c85c298c4dp+989", 298, "1.00000000000000007573939945016978060492419511470035540696679476643984088073534349759794414321176620068695935783532685614254758245712563448899768664642585866708011503065149183159674961578634862041384410689587293854256855313820884722488322628774701887203392973176783938990132044219319502473679297577e+298"}, + {"0x1.31cfd3999f7afp+993", 299, "99999999999999986662764669548153739894665631237058913850832890808749507601742578129378923002990117089766513181334005445210204946123879926882163649167349350899456456312724758086647517786230384722356772394775369116518164624503799012160606438304513147494189124523779646633247748770420728389479079870464"}, + {"0x1.31cfd3999f7b0p+993", 299, "1.0000000000000000525047602552044202487044685811081591549158541155118024579889081957863713750804478640437044438328838781769425232353604305756447921847867069828483872009265758037378302337947880900593689532349707999450811190389676408800746527427801424945792587888200568428381156694721963868654594005402e+299"}, + {"0x1.7e43c8800759bp+996", 300, "999999999999999903803069407426113968898218766118103141789833949572356552411722264192305659040010509526872994217248819197070144216063125530186267630296136203765329090687113225440746189048800695790727969805197112921161540803823920273299782054992133678869364753954248541633605124057805104488924519071744"}, + {"0x1.7e43c8800759cp+996", 300, "1.00000000000000005250476025520442024870446858110815915491585411551180245798890819578637137508044786404370444383288387817694252323536043057564479218478670698284838720092657580373783023379478809005936895323497079994508111903896764088007465274278014249457925878882005684283811566947219638686545940054016e+300"}, + {"0x1.ddd4baa009302p+999", 301, "9999999999999999335434075769817752248594687291161143444150379827602457335271594505111188022480979804302392841403758309930446200199225865392779725411942503595819407127350057411001629979979981746444561664911518503259454564508526643946547561925497354420113435609274102018745072331406833609642314953654272"}, + {"0x1.ddd4baa009303p+999", 301, "1.00000000000000005250476025520442024870446858110815915491585411551180245798890819578637137508044786404370444383288387817694252323536043057564479218478670698284838720092657580373783023379478809005936895323497079994508111903896764088007465274278014249457925878882005684283811566947219638686545940054016e+301"}, + {"0x1.2aa4f4a405be1p+1003", 302, "99999999999999988595886650569271721532146878831929642021471152965962304374245995240101777311515802698485322026337261211948545873374744892473124468375726771027536211745837771604509610367928220847849105179362427047829119141560667380048679757245757262098417746977035154548906385860807815060374033329553408"}, + {"0x1.2aa4f4a405be2p+1003", 302, "1.0000000000000000762970307908489492534734685515065681170160173420621138028812579448414218896469178407663974757713854876137221038784479993829181561135051983075016764985648898162653636809541460731423515105837345898689082515565906361771586320528262239050928418343985861710308373567384989920457049815751066e+302"}, + {"0x1.754e31cd072d9p+1006", 303, "999999999999999847891233648661470807691068835681842080854450367179124891914700353912936949808806064228544369161770037020638129704807338833093862397807681590830099241237075296001042588224309435545718960035602206600167779387409881325152430676383842364162444596844704620380709158981993982315347403639619584"}, + {"0x1.754e31cd072dap+1006", 303, "1.00000000000000000016176507678645643821266864623165943829549501710111749922573874786526024303421391525377977356818033741602744582056777919964339154160602606861115074612228497617725665004420052727680732706769046211266142750019705122648989826067876339144937608854729232081412795748633065546891912226327757e+303"}, + {"0x1.d2a1be4048f90p+1009", 304, "9999999999999999392535525055364621860040287220117324953190771571323204563013233902843309257440507748436856118056162172578717193742636030530235798840866882774987301441682011041067710253162440905843719802548551599076639682550821832659549112269607949805346034918662572406407604380845959862074904348138143744"}, + {"0x1.d2a1be4048f91p+1009", 304, "1.000000000000000061069977648036450690421308570451586381271912877069914542150154105446189560324377055663873935330744457574183172266871955346263203199125363859723571348076368848247742274772156963969242673880525764317658886745311919187024885294396731802364148685228327400987495476888065324730347809712740762e+304"}, + {"0x1.23a516e82d9bap+1013", 305, "99999999999999993925355250553646218600402872201173249531907715713232045630132339028433092574405077484368561180561621725787171937426360305302357988408668827749873014416820110410677102531624409058437198025485515990766396825508218326595491122696079498053460349186625724064076043808459598620749043481381437440"}, + {"0x1.23a516e82d9bbp+1013", 305, "1.0000000000000001341598327335364437930716764795154987128436143090324709936594525345433047410725728241559869294458214017639700440024369667222069771881485692090584760704212694947323250244457046880001650900559281269636558378394497607396668697348582938954618758012455694971955365001701469278440622346520965939e+305"}, + {"0x1.6c8e5ca239028p+1016", 306, "999999999999999861291040414336469543176969619010226008309262296372260241358071732580741399612641955118765084749534143455432389522994257585350220962461935904874831773666973747856549425664459851618054736334425973085267220421335152276470127823801795414563694568114532338018850013250375609552861714878501486592"}, + {"0x1.6c8e5ca239029p+1016", 306, "1.00000000000000001721606459673645482883108782501323898232889201789238067124457504798792045187545959456860613886169829106031104922553294852069693880571144065012262851466942846035699262496802832955068922417528434673006071608882921425543969463011979454650551241561798214326267086291881636286211915474912726221e+306"}, + {"0x1.c7b1f3cac7433p+1019", 307, "9999999999999999860310597602564577717002641838126363875249660735883565852672743849064846414228960666786379280392654615393353172850252103336275952370615397010730691664689375178569039851073146339641623266071126720011020169553304018596457812688561947201171488461172921822139066929851282122002676667750021070848"}, + {"0x1.c7b1f3cac7434p+1019", 307, "1.000000000000000110771079106176446000223558748615046766740669850804452929176477037232227883233150178238510771328996779623238245047056163081904969511661143497271306559270901287857258544550169416310269916879799370916936813489325651442821434713910594025670603124120052026408963372719880814847673618671502727578e+307"}, + {"0x1.1ccf385ebc89fp+1023", 308, "99999999999999981139503267596847425176765179308926185662298078548582170379439067165044410288854031049481594743364161622187121841818187648603927125262209438639553681654618823985640760188731793867961170022535129351893330180773705244319986644578003569234231285691342840034082734135647456849389933411990123839488"}, + {"0x1.1ccf385ebc8a0p+1023", 308, "1.0000000000000000109790636294404554174049230967731184633681068290315758540491149153716332897849468889906124966972117251561159028374314008832830700919814604603127166450293302718569748969958855904333838446616500117842689762621294517762809119578670745812278397017178441510529180289320787327297488571543022311834e+308"}, + {"0x1.a36e2eb1c432cp-14", 309, "9.99999999999999912396464463171241732197813689708709716796875e-5"}, + {"0x1.a36e2eb1c432dp-14", 309, "0.000100000000000000004792173602385929598312941379845142364501953125"}, + {"0x1.0624dd2f1a9fbp-10", 309, "0.00099999999999999980397624721462079833145253360271453857421875"}, + {"0x1.0624dd2f1a9fcp-10", 309, "0.001000000000000000020816681711721685132943093776702880859375"}, + {"0x1.47ae147ae147ap-7", 309, "0.0099999999999999984734433411404097569175064563751220703125"}, + {"0x1.47ae147ae147bp-7", 309, "0.01000000000000000020816681711721685132943093776702880859375"}, + {"0x1.9999999999999p-4", 309, "0.09999999999999999167332731531132594682276248931884765625"}, + {"0x1.999999999999ap-4", 309, "0.1000000000000000055511151231257827021181583404541015625"}, + {"0x1.fffffffffffffp-1", 309, "0.99999999999999988897769753748434595763683319091796875"}, + {"0x1.0000000000000p+0", 309, "1"}, + {"0x1.3ffffffffffffp+3", 309, "9.9999999999999982236431605997495353221893310546875"}, + {"0x1.4000000000000p+3", 309, "10"}, + {"0x1.8ffffffffffffp+6", 309, "99.9999999999999857891452847979962825775146484375"}, + {"0x1.9000000000000p+6", 309, "100"}, + {"0x1.f3fffffffffffp+9", 309, "999.9999999999998863131622783839702606201171875"}, + {"0x1.f400000000000p+9", 309, "1000"}, + {"0x1.387ffffffffffp+13", 309, "9999.999999999998181010596454143524169921875"}, + {"0x1.3880000000000p+13", 309, "10000"}, + {"0x1.869ffffffffffp+16", 309, "99999.999999999985448084771633148193359375"}, + {"0x1.86a0000000000p+16", 309, "100000"}, + {"0x1.e847fffffffffp+19", 309, "999999.999999999883584678173065185546875"}, + {"0x1.e848000000000p+19", 309, "1000000"}, + {"0x1.312cfffffffffp+23", 309, "9999999.99999999813735485076904296875"}, + {"0x1.312d000000000p+23", 309, "10000000"}, + {"0x1.7d783ffffffffp+26", 309, "99999999.99999998509883880615234375"}, + {"0x1.7d78400000000p+26", 309, "100000000"}, + {"0x1.dcd64ffffffffp+29", 309, "999999999.99999988079071044921875"}, + {"0x1.dcd6500000000p+29", 309, "1000000000"}, + {"0x1.2a05f1fffffffp+33", 309, "9999999999.9999980926513671875"}, + {"0x1.2a05f20000000p+33", 309, "10000000000"}, + {"0x1.74876e7ffffffp+36", 309, "99999999999.9999847412109375"}, + {"0x1.74876e8000000p+36", 309, "100000000000"}, + {"0x1.d1a94a1ffffffp+39", 309, "999999999999.9998779296875"}, + {"0x1.d1a94a2000000p+39", 309, "1000000000000"}, + {"0x1.2309ce53fffffp+43", 309, "9999999999999.998046875"}, + {"0x1.2309ce5400000p+43", 309, "10000000000000"}, + {"0x1.6bcc41e8fffffp+46", 309, "99999999999999.984375"}, + {"0x1.6bcc41e900000p+46", 309, "100000000000000"}, + {"0x1.c6bf52633ffffp+49", 309, "999999999999999.875"}, + {"0x1.c6bf526340000p+49", 309, "1000000000000000"}, + {"0x1.1c37937e07fffp+53", 309, "9999999999999998"}, + {"0x1.1c37937e08000p+53", 309, "10000000000000000"}, + {"0x1.6345785d89fffp+56", 309, "99999999999999984"}, + {"0x1.6345785d8a000p+56", 309, "100000000000000000"}, + {"0x1.bc16d674ec7ffp+59", 309, "999999999999999872"}, + {"0x1.bc16d674ec800p+59", 309, "1000000000000000000"}, + {"0x1.158e460913cffp+63", 309, "9999999999999997952"}, + {"0x1.158e460913d00p+63", 309, "10000000000000000000"}, + {"0x1.5af1d78b58c3fp+66", 309, "99999999999999983616"}, + {"0x1.5af1d78b58c40p+66", 309, "100000000000000000000"}, + {"0x1.b1ae4d6e2ef4fp+69", 309, "999999999999999868928"}, + {"0x1.b1ae4d6e2ef50p+69", 309, "1000000000000000000000"}, + {"0x1.0f0cf064dd591p+73", 309, "9999999999999997902848"}, + {"0x1.0f0cf064dd592p+73", 309, "10000000000000000000000"}, + {"0x1.52d02c7e14af6p+76", 309, "99999999999999991611392"}, + {"0x1.52d02c7e14af7p+76", 309, "100000000000000008388608"}, + {"0x1.a784379d99db4p+79", 309, "999999999999999983222784"}, + {"0x1.a784379d99db5p+79", 309, "1000000000000000117440512"}, + {"0x1.08b2a2c280290p+83", 309, "9999999999999998758486016"}, + {"0x1.08b2a2c280291p+83", 309, "10000000000000000905969664"}, + {"0x1.4adf4b7320334p+86", 309, "99999999999999987584860160"}, + {"0x1.4adf4b7320335p+86", 309, "100000000000000004764729344"}, + {"0x1.9d971e4fe8401p+89", 309, "999999999999999875848601600"}, + {"0x1.9d971e4fe8402p+89", 309, "1000000000000000013287555072"}, + {"0x1.027e72f1f1281p+93", 309, "9999999999999999583119736832"}, + {"0x1.027e72f1f1282p+93", 309, "10000000000000001782142992384"}, + {"0x1.431e0fae6d721p+96", 309, "99999999999999991433150857216"}, + {"0x1.431e0fae6d722p+96", 309, "100000000000000009025336901632"}, + {"0x1.93e5939a08ce9p+99", 309, "999999999999999879147136483328"}, + {"0x1.93e5939a08ceap+99", 309, "1000000000000000019884624838656"}, + {"0x1.f8def8808b024p+102", 309, "9999999999999999635896294965248"}, + {"0x1.f8def8808b025p+102", 309, "10000000000000000761796201807872"}, + {"0x1.3b8b5b5056e16p+106", 309, "99999999999999987351763694911488"}, + {"0x1.3b8b5b5056e17p+106", 309, "100000000000000005366162204393472"}, + {"0x1.8a6e32246c99cp+109", 309, "999999999999999945575230987042816"}, + {"0x1.8a6e32246c99dp+109", 309, "1000000000000000089690419062898688"}, + {"0x1.ed09bead87c03p+112", 309, "9999999999999999455752309870428160"}, + {"0x1.ed09bead87c04p+112", 309, "10000000000000000608673814477275136"}, + {"0x1.3426172c74d82p+116", 309, "99999999999999996863366107917975552"}, + {"0x1.3426172c74d83p+116", 309, "100000000000000015310110181627527168"}, + {"0x1.812f9cf7920e2p+119", 309, "999999999999999894846684784341549056"}, + {"0x1.812f9cf7920e3p+119", 309, "1000000000000000042420637374017961984"}, + {"0x1.e17b84357691bp+122", 309, "9999999999999999538762658202121142272"}, + {"0x1.e17b84357691cp+122", 309, "10000000000000000719354278919532445696"}, + {"0x1.2ced32a16a1b1p+126", 309, "99999999999999997748809823456034029568"}, + {"0x1.2ced32a16a1b2p+126", 309, "100000000000000016638275754934614884352"}, + {"0x1.78287f49c4a1dp+129", 309, "999999999999999939709166371603178586112"}, + {"0x1.78287f49c4a1ep+129", 309, "1000000000000000090824893823431825424384"}, + {"0x1.d6329f1c35ca4p+132", 309, "9999999999999999094860208812374492184576"}, + {"0x1.d6329f1c35ca5p+132", 309, "10000000000000000303786028427003666890752"}, + {"0x1.25dfa371a19e6p+136", 309, "99999999999999981277195531206711524196352"}, + {"0x1.25dfa371a19e7p+136", 309, "100000000000000000620008645040778319495168"}, + {"0x1.6f578c4e0a060p+139", 309, "999999999999999890143207767403382423158784"}, + {"0x1.6f578c4e0a061p+139", 309, "1000000000000000044885712678075916785549312"}, + {"0x1.cb2d6f618c878p+142", 309, "9999999999999998901432077674033824231587840"}, + {"0x1.cb2d6f618c879p+142", 309, "10000000000000000139372116959414099130712064"}, + {"0x1.1efc659cf7d4bp+146", 309, "99999999999999989014320776740338242315878400"}, + {"0x1.1efc659cf7d4cp+146", 309, "100000000000000008821361405306422640701865984"}, + {"0x1.66bb7f0435c9ep+149", 309, "999999999999999929757289024535551219930759168"}, + {"0x1.66bb7f0435c9fp+149", 309, "1000000000000000088213614053064226407018659840"}, + {"0x1.c06a5ec5433c6p+152", 309, "9999999999999999931398190359470212947659194368"}, + {"0x1.c06a5ec5433c7p+152", 309, "10000000000000001199048790587699614444362399744"}, + {"0x1.18427b3b4a05bp+156", 309, "99999999999999984102174700855949311516153479168"}, + {"0x1.18427b3b4a05cp+156", 309, "100000000000000004384584304507619735463404765184"}, + {"0x1.5e531a0a1c872p+159", 309, "999999999999999881586566215862833963056037363712"}, + {"0x1.5e531a0a1c873p+159", 309, "1000000000000000043845843045076197354634047651840"}, + {"0x1.b5e7e08ca3a8fp+162", 309, "9999999999999999464902769475481793196872414789632"}, + {"0x1.b5e7e08ca3a90p+162", 309, "10000000000000000762976984109188700329496497094656"}, + {"0x1.11b0ec57e6499p+166", 309, "99999999999999986860582406952576489172979654066176"}, + {"0x1.11b0ec57e649ap+166", 309, "100000000000000007629769841091887003294964970946560"}, + {"0x1.561d276ddfdc0p+169", 309, "999999999999999993220948674361627976461708441944064"}, + {"0x1.561d276ddfdc1p+169", 309, "1000000000000000159374448147476112089437590976987136"}, + {"0x1.aba4714957d30p+172", 309, "9999999999999999932209486743616279764617084419440640"}, + {"0x1.aba4714957d31p+172", 309, "10000000000000001261437482528532152668424144699785216"}, + {"0x1.0b46c6cdd6e3ep+176", 309, "99999999999999999322094867436162797646170844194406400"}, + {"0x1.0b46c6cdd6e3fp+176", 309, "100000000000000020589742799994816764107083808679919616"}, + {"0x1.4e1878814c9cdp+179", 309, "999999999999999908150356944127012110618056584002011136"}, + {"0x1.4e1878814c9cep+179", 309, "1000000000000000078291540404596243842305360299886116864"}, + {"0x1.a19e96a19fc40p+182", 309, "9999999999999998741221202520331657642805958408251899904"}, + {"0x1.a19e96a19fc41p+182", 309, "10000000000000000102350670204085511496304388135324745728"}, + {"0x1.05031e2503da8p+186", 309, "99999999999999987412212025203316576428059584082518999040"}, + {"0x1.05031e2503da9p+186", 309, "100000000000000009190283508143378238084034459715684532224"}, + {"0x1.4643e5ae44d12p+189", 309, "999999999999999874122120252033165764280595840825189990400"}, + {"0x1.4643e5ae44d13p+189", 309, "1000000000000000048346692115553659057528394845890514255872"}, + {"0x1.97d4df19d6057p+192", 309, "9999999999999999438119489974413630815797154428513196965888"}, + {"0x1.97d4df19d6058p+192", 309, "10000000000000000831916064882577577161779546469035791089664"}, + {"0x1.fdca16e04b86dp+195", 309, "99999999999999997168788049560464200849936328366177157906432"}, + {"0x1.fdca16e04b86ep+195", 309, "100000000000000008319160648825775771617795464690357910896640"}, + {"0x1.3e9e4e4c2f344p+199", 309, "999999999999999949387135297074018866963645011013410073083904"}, + {"0x1.3e9e4e4c2f345p+199", 309, "1000000000000000127793096885319003999249391192200302120927232"}, + {"0x1.8e45e1df3b015p+202", 309, "9999999999999999493871352970740188669636450110134100730839040"}, + {"0x1.8e45e1df3b016p+202", 309, "10000000000000000921119045676700069727922419559629237113585664"}, + {"0x1.f1d75a5709c1ap+205", 309, "99999999999999992084218144295482124579792562202350734542897152"}, + {"0x1.f1d75a5709c1bp+205", 309, "100000000000000003502199685943161173046080317798311825604870144"}, + {"0x1.3726987666190p+209", 309, "999999999999999875170255276364105051932774599639662981181079552"}, + {"0x1.3726987666191p+209", 309, "1000000000000000057857959942726969827393378689175040438172647424"}, + {"0x1.84f03e93ff9f4p+212", 309, "9999999999999998751702552763641050519327745996396629811810795520"}, + {"0x1.84f03e93ff9f5p+212", 309, "10000000000000000213204190094543968723012578712679649467743338496"}, + {"0x1.e62c4e38ff872p+215", 309, "99999999999999999209038626283633850822756121694230455365568299008"}, + {"0x1.e62c4e38ff873p+215", 309, "100000000000000010901051724930857196452234783424494612613028642816"}, + {"0x1.2fdbb0e39fb47p+219", 309, "999999999999999945322333868247445125709646570021247924665841614848"}, + {"0x1.2fdbb0e39fb48p+219", 309, "1000000000000000132394543446603018655781305157705474440625207115776"}, + {"0x1.7bd29d1c87a19p+222", 309, "9999999999999999827367757839185598317239782875580932278577147150336"}, + {"0x1.7bd29d1c87a1ap+222", 309, "10000000000000001323945434466030186557813051577054744406252071157760"}, + {"0x1.dac74463a989fp+225", 309, "99999999999999995280522225138166806691251291352861698530421623488512"}, + {"0x1.dac74463a98a0p+225", 309, "100000000000000007253143638152923512615837440964652195551821015547904"}, + {"0x1.28bc8abe49f63p+229", 309, "999999999999999880969493773293127831364996015857874003175819882528768"}, + {"0x1.28bc8abe49f64p+229", 309, "1000000000000000072531436381529235126158374409646521955518210155479040"}, + {"0x1.72ebad6ddc73cp+232", 309, "9999999999999999192818822949403492903236716946156035936442979371188224"}, + {"0x1.72ebad6ddc73dp+232", 309, "10000000000000000725314363815292351261583744096465219555182101554790400"}, + {"0x1.cfa698c95390bp+235", 309, "99999999999999991928188229494034929032367169461560359364429793711882240"}, + {"0x1.cfa698c95390cp+235", 309, "100000000000000004188152556421145795899143386664033828314342771180699648"}, + {"0x1.21c81f7dd43a7p+239", 309, "999999999999999943801810948794571024057224129020550531544123892056457216"}, + {"0x1.21c81f7dd43a8p+239", 309, "1000000000000000139961240179628344893925643604260126034742731531557535744"}, + {"0x1.6a3a275d49491p+242", 309, "9999999999999999830336967949613257980309080240684656321838454199566729216"}, + {"0x1.6a3a275d49492p+242", 309, "10000000000000001399612401796283448939256436042601260347427315315575357440"}, + {"0x1.c4c8b1349b9b5p+245", 309, "99999999999999995164818811802792197885196090803013355167206819763650035712"}, + {"0x1.c4c8b1349b9b6p+245", 309, "100000000000000007719022282576153725556774937218346187371917708691719061504"}, + {"0x1.1afd6ec0e1411p+249", 309, "999999999999999926539781176481198923508803215199467887262646419780362305536"}, + {"0x1.1afd6ec0e1412p+249", 309, "1000000000000000127407036708854983366254064757844793202538020642629466718208"}, + {"0x1.61bcca7119915p+252", 309, "9999999999999998863663300700064420349597509066704028242075715752105414230016"}, + {"0x1.61bcca7119916p+252", 309, "10000000000000000470601344959054695891559601407866630764278709534898249531392"}, + {"0x1.ba2bfd0d5ff5bp+255", 309, "99999999999999998278261272554585856747747644714015897553975120217811154108416"}, + {"0x1.ba2bfd0d5ff5cp+255", 309, "100000000000000011133765626626508061083444383443316717731599070480153836519424"}, + {"0x1.145b7e285bf98p+259", 309, "999999999999999802805551768538947706777722104929947493053015898505313987330048"}, + {"0x1.145b7e285bf99p+259", 309, "1000000000000000008493621433689702976148869924598760615894999102702796905906176"}, + {"0x1.59725db272f7fp+262", 309, "9999999999999999673560075006595519222746403606649979913266024618633003221909504"}, + {"0x1.59725db272f80p+262", 309, "10000000000000001319064632327801561377715586164000484896001890252212866570518528"}, + {"0x1.afcef51f0fb5ep+265", 309, "99999999999999986862573406138718939297648940722396769236245052384850852127440896"}, + {"0x1.afcef51f0fb5fp+265", 309, "100000000000000000026609864708367276537402401181200809098131977453489758916313088"}, + {"0x1.0de1593369d1bp+269", 309, "999999999999999921281879895665782741935503249059183851809998224123064148429897728"}, + {"0x1.0de1593369d1cp+269", 309, "1000000000000000131906463232780156137771558616400048489600189025221286657051852800"}, + {"0x1.5159af8044462p+272", 309, "9999999999999999634067965630886574211027143225273567793680363843427086501542887424"}, + {"0x1.5159af8044463p+272", 309, "10000000000000001319064632327801561377715586164000484896001890252212866570518528000"}, + {"0x1.a5b01b605557ap+275", 309, "99999999999999989600692989521205793443517660497828009527517532799127744739526311936"}, + {"0x1.a5b01b605557bp+275", 309, "100000000000000003080666323096525690777025204007643346346089744069413985291331436544"}, + {"0x1.078e111c3556cp+279", 309, "999999999999999842087036560910778345101146430939018748000886482910132485188042620928"}, + {"0x1.078e111c3556dp+279", 309, "1000000000000000057766609898115896702437267127096064137098041863234712334016924614656"}, + {"0x1.4971956342ac7p+282", 309, "9999999999999998420870365609107783451011464309390187480008864829101324851880426209280"}, + {"0x1.4971956342ac8p+282", 309, "10000000000000000146306952306748730309700429878646550592786107871697963642511482159104"}, + {"0x1.9bcdfabc13579p+285", 309, "99999999999999987659576829486359728227492574232414601025643134376206526100066373992448"}, + {"0x1.9bcdfabc1357ap+285", 309, "100000000000000001463069523067487303097004298786465505927861078716979636425114821591040"}, + {"0x1.0160bcb58c16cp+289", 309, "999999999999999959416724456350362731491996089648451439669739009806703922950954425516032"}, + {"0x1.0160bcb58c16dp+289", 309, "1000000000000000180272607553648403929404183682513265918105226119259073688151729587093504"}, + {"0x1.41b8ebe2ef1c7p+292", 309, "9999999999999999594167244563503627314919960896484514396697390098067039229509544255160320"}, + {"0x1.41b8ebe2ef1c8p+292", 309, "10000000000000001361014309341887956898217461639403030224181286973685997351115745547780096"}, + {"0x1.922726dbaae39p+295", 309, "99999999999999999475366575191804932315794610450682175621941694731908308538307845136842752"}, + {"0x1.922726dbaae3ap+295", 309, "100000000000000013610143093418879568982174616394030302241812869736859973511157455477800960"}, + {"0x1.f6b0f092959c7p+298", 309, "999999999999999966484112715463900049825186092620125502979674597309179755437379230686511104"}, + {"0x1.f6b0f092959c8p+298", 309, "1000000000000000079562324861280497143156226140166910515938643997348793075220176113414176768"}, + {"0x1.3a2e965b9d81cp+302", 309, "9999999999999998986371854279739417938265620640920544952042929572854117635677011010499117056"}, + {"0x1.3a2e965b9d81dp+302", 309, "10000000000000000795623248612804971431562261401669105159386439973487930752201761134141767680"}, + {"0x1.88ba3bf284e23p+305", 309, "99999999999999989863718542797394179382656206409205449520429295728541176356770110104991170560"}, + {"0x1.88ba3bf284e24p+305", 309, "100000000000000004337729697461918607329029332495193931179177378933611681288968111094132375552"}, + {"0x1.eae8caef261acp+308", 309, "999999999999999927585207737302990649719308316264031458521789123695552773432097103028194115584"}, + {"0x1.eae8caef261adp+308", 309, "1000000000000000043377296974619186073290293324951939311791773789336116812889681110941323755520"}, + {"0x1.32d17ed577d0bp+312", 309, "9999999999999998349515363474500343108625203093137051759058013911831015418660298966976904036352"}, + {"0x1.32d17ed577d0cp+312", 309, "10000000000000000202188791271559469885760963232143577411377768562080040049981643093586978275328"}, + {"0x1.7f85de8ad5c4ep+315", 309, "99999999999999987200500490339121684640523551209383568895219648418808203449245677922989188841472"}, + {"0x1.7f85de8ad5c4fp+315", 309, "100000000000000002021887912715594698857609632321435774113777685620800400499816430935869782753280"}, + {"0x1.df67562d8b362p+318", 309, "999999999999999931290554592897108903273579836542044509826428632996050822694739791281414264061952"}, + {"0x1.df67562d8b363p+318", 309, "1000000000000000049861653971908893017010268485438462151574892930611988399099305815384459015356416"}, + {"0x1.2ba095dc7701dp+322", 309, "9999999999999998838621148412923952577789043769834774531270429139496757921329133816401963635441664"}, + {"0x1.2ba095dc7701ep+322", 309, "10000000000000000735758738477112498397576062152177456799245857901351759143802190202050679656153088"}, + {"0x1.7688bb5394c25p+325", 309, "99999999999999999769037024514370800696612547992403838920556863966097586548129676477911932478685184"}, + {"0x1.7688bb5394c26p+325", 309, "100000000000000014946137745027879167254908695051145297064360294060937596327914127563101660644376576"}, + {"0x1.d42aea2879f2ep+328", 309, "999999999999999967336168804116691273849533185806555472917961779471295845921727862608739868455469056"}, + {"0x1.d42aea2879f2fp+328", 309, "1000000000000000088752974568224758206315902362276487138068389220230015924160003471290257693781000192"}, + {"0x1.249ad2594c37cp+332", 309, "9999999999999998216360018871870109548898901740426374747374488505608317520357971321909184780648316928"}, + {"0x1.249ad2594c37dp+332", 309, "10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104"}, + {"0x1.6dc186ef9f45cp+335", 309, "99999999999999997704951326524533662844684271992415000612999597473199345218078991130326129448151154688"}, + {"0x1.6dc186ef9f45dp+335", 309, "100000000000000013246302464330366230200379526580566253752254309890315515232578269041560411089819140096"}, + {"0x1.c931e8ab87173p+338", 309, "999999999999999977049513265245336628446842719924150006129995974731993452180789911303261294481511546880"}, + {"0x1.c931e8ab87174p+338", 309, "1000000000000000101380322367691997167292404756629360031244033674068922812296784134593135547614855430144"}, + {"0x1.1dbf316b346e7p+342", 309, "9999999999999998029863805218200118740630558685368559709703431956602923480183979986974373400948301103104"}, + {"0x1.1dbf316b346e8p+342", 309, "10000000000000000019156750857346687362159551272651920111528035145993793242039887559612361451081803235328"}, + {"0x1.652efdc6018a1p+345", 309, "99999999999999984277223943460294324649363572028252317900683525944810974325551615015019710109750015295488"}, + {"0x1.652efdc6018a2p+345", 309, "100000000000000000191567508573466873621595512726519201115280351459937932420398875596123614510818032353280"}, + {"0x1.be7abd3781ecap+348", 309, "999999999999999938258300825281978540327027364472124478294416212538871491824599713636820527503908255301632"}, + {"0x1.be7abd3781ecbp+348", 309, "1000000000000000065573049346187358932104882890058259544011190816659887156583377798285651762712452391763968"}, + {"0x1.170cb642b133ep+352", 309, "9999999999999998873324014169198263836158851542376704520077063708904652259210884797772880334204906007166976"}, + {"0x1.170cb642b133fp+352", 309, "10000000000000000910359990503684350104604539951754865571545457374840902895351334152154180097541612190564352"}, + {"0x1.5ccfe3d35d80ep+355", 309, "99999999999999996881384047029926983435371269061279689406644211752791525136670645395254002395395884805259264"}, + {"0x1.5ccfe3d35d80fp+355", 309, "100000000000000013177671857705815673582936776336304977818391361080281530225794240230304400502089534272438272"}, + {"0x1.b403dcc834e11p+358", 309, "999999999999999903628689227595715073763450661512695740419453520217955231010212074612338431527184250183876608"}, + {"0x1.b403dcc834e12p+358", 309, "1000000000000000033998991713002824594943974719712898047713430714837875271723200833292741616380733445921308672"}, + {"0x1.108269fd210cbp+362", 309, "9999999999999999818508707188399807864717650964328171247958398369899072554380053298205803424393137676263358464"}, + {"0x1.108269fd210ccp+362", 309, "10000000000000001904433546954913560203606035895531408164662033483817793205787873437092254382049924808062271488"}, + {"0x1.54a3047c694fdp+365", 309, "99999999999999985669538033284915564613846200056062290979362173015478401635353612148739328497990653971840106496"}, + {"0x1.54a3047c694fep+365", 309, "100000000000000002356936751417025583324953279505688186312991253926828166846616173259830936159244951026231410688"}, + {"0x1.a9cbc59b83a3dp+368", 309, "999999999999999956819772641641815758405104477258378281795396215622882607621111488153942930947432322044748890112"}, + {"0x1.a9cbc59b83a3ep+368", 309, "1000000000000000090318962386698695908093961112855385444464428862913680729311211977042675792237466698479879323648"}, + {"0x1.0a1f5b8132466p+372", 309, "9999999999999999301199346926304397284673331501389768492615896861647229832830913903761963586894254467577228034048"}, + {"0x1.0a1f5b8132467p+372", 309, "10000000000000001437186382847214479679695037670941883095320419218299999779872521725981689367534804490539314970624"}, + {"0x1.4ca732617ed7fp+375", 309, "99999999999999984468045325579403643266646490335689226515340879189861218540142707748740732746380344583923932594176"}, + {"0x1.4ca732617ed80p+375", 309, "100000000000000001555941612946684302426820139692106143336977058043083378116475570326498538991504744767620628086784"}, + {"0x1.9fd0fef9de8dfp+378", 309, "999999999999999878856245830528597750986812202069726098796681149605056504554092802642922939954052246206632716926976"}, + {"0x1.9fd0fef9de8e0p+378", 309, "1000000000000000015559416129466843024268201396921061433369770580430833781164755703264985389915047447676206280867840"}, + {"0x1.03e29f5c2b18bp+382", 309, "9999999999999997968343436511656505870179786851589248980528274911095901385876950622696854699774551253248885785624576"}, + {"0x1.03e29f5c2b18cp+382", 309, "10000000000000000155594161294668430242682013969210614333697705804308337811647557032649853899150474476762062808678400"}, + {"0x1.44db473335deep+385", 309, "99999999999999984057935814682588907446802322751135220511621610897383886710310719046874545396497358979515211902353408"}, + {"0x1.44db473335defp+385", 309, "100000000000000001555941612946684302426820139692106143336977058043083378116475570326498538991504744767620628086784000"}, + {"0x1.961219000356ap+388", 309, "999999999999999910571381339882270654388094495275235896417637897556636832727766595587241428345003132947573783761256448"}, + {"0x1.961219000356bp+388", 309, "1000000000000000050555427725995033814228237030803003279020481474722232763977085405824233377105062219252417113236701184"}, + {"0x1.fb969f40042c5p+391", 309, "9999999999999999665649998943273759183241515094863428494587753284228752052274941196820382078490267674695111155514343424"}, + {"0x1.fb969f40042c6p+391", 309, "10000000000000000785522370032175864461962655379085567555410501901553519502269491678716317668570740365133857791317901312"}, + {"0x1.3d3e2388029bbp+395", 309, "99999999999999994416755247254933381274972870380190006824232035607637985622760311004411949604741731366073618283536318464"}, + {"0x1.3d3e2388029bcp+395", 309, "100000000000000012334713184677367065734511114927744231797396013484834264822673118714746919046029294413093564456393244672"}, + {"0x1.8c8dac6a0342ap+398", 309, "999999999999999980003468347394201181668805192897008518188648311830772414627428725464789434929992439754776075181077037056"}, + {"0x1.8c8dac6a0342bp+398", 309, "1000000000000000123347131846773670657345111149277442317973960134848342648226731187147469190460292944130935644563932446720"}, + {"0x1.efb1178484134p+401", 309, "9999999999999999226660029476424133913982828103448349982745235826237443211877077407917175327178722380043122474279348731904"}, + {"0x1.efb1178484135p+401", 309, "10000000000000000373409337471459889719393275754491820381027730410378005080671497101378613371421126415052399029342192009216"}, + {"0x1.35ceaeb2d28c0p+405", 309, "99999999999999983092605830803955292696544699826135736641192401589249937168415416531480248917847991520357012302290741100544"}, + {"0x1.35ceaeb2d28c1p+405", 309, "100000000000000001440594758724527385583111862242831263013712314935498927069126131626863257625726456080505437183296233537536"}, + {"0x1.83425a5f872f1p+408", 309, "999999999999999977709969731404129670057984297594921577392083322662491290889839886077866558841507631684757522070951350501376"}, + {"0x1.83425a5f872f2p+408", 309, "1000000000000000124493881154768706413150521596928485788372242629432483210095525606840930628504535348165944921118995289997312"}, + {"0x1.e412f0f768fadp+411", 309, "9999999999999999483531874467312143214394768377282087351960514613084929070487027419252537449089020883885200422613425626021888"}, + {"0x1.e412f0f768faep+411", 309, "10000000000000000657803165854228757159135066771950601039801789067244864424132513185357050006393242615734699614997777141989376"}, + {"0x1.2e8bd69aa19ccp+415", 309, "99999999999999992486776161899288204254467086983483846143922597222529419997579302660316349376281765375153005841365553228283904"}, + {"0x1.2e8bd69aa19cdp+415", 309, "100000000000000011275116824089954027370311861298180065149382988489088385655907074917988550293149313084744992919515177483763712"}, + {"0x1.7a2ecc414a03fp+418", 309, "999999999999999924867761618992882042544670869834838461439225972225294199975793026603163493762817653751530058413655532282839040"}, + {"0x1.7a2ecc414a040p+418", 309, "1000000000000000075174486916518208627471429064352408213482909102357765925242415204664541101097758035428265955038852526326677504"}, + {"0x1.d8ba7f519c84fp+421", 309, "9999999999999999549291066784979473595300225087383524118479625982517885450291174622154390152298057300868772377386949310916067328"}, + {"0x1.d8ba7f519c850p+421", 309, "10000000000000000751744869165182086274714290643524082134829091023577659252424152046645411010977580354282659550388525263266775040"}, + {"0x1.27748f9301d31p+425", 309, "99999999999999988278187853568579059876517857536991893086699469578820211690113881674597776370903434688204400735860037395056427008"}, + {"0x1.27748f9301d32p+425", 309, "100000000000000007517448691651820862747142906435240821348290910235776592524241520466454110109775803542826595503885252632667750400"}, + {"0x1.7151b377c247ep+428", 309, "999999999999999998217443564185241415988928868759412500436543339729940401905904649497115766142268560009777175966751665376232210432"}, + {"0x1.7151b377c247fp+428", 309, "1000000000000000152131530268851175838953929259945403926529274864985591448578925759831966436053247510846754734110953387277122797568"}, + {"0x1.cda62055b2d9dp+431", 309, "9999999999999999366518088823188676468029287122850159299994507296276799832366962053631754981778769796749861527090709766158759755776"}, + {"0x1.cda62055b2d9ep+431", 309, "10000000000000000597830782460516151851749290252338090708736359498322008205751130936310560341066601403445681992244323541365884452864"}, + {"0x1.2087d4358fc82p+435", 309, "99999999999999991202555500957231813912852864969525730182461368558677581576901282770959939099212034754106974340599870111173348163584"}, + {"0x1.2087d4358fc83p+435", 309, "100000000000000010903558599154471420052372915041332632722331003791400915551047984893820824847817340461240101783057690514487343316992"}, + {"0x1.68a9c942f3ba3p+438", 309, "999999999999999990829567402361276563686608849982484911984092226517669151665599636201042933986541570369602253175829982724989462249472"}, + {"0x1.68a9c942f3ba4p+438", 309, "1000000000000000148437592187939193412802769250556940132303049308379455823458773253183930019975384016026667272715492545951501423476736"}, + {"0x1.c2d43b93b0a8bp+441", 309, "9999999999999998962647525310145264542169126096378117797927179774005971485896954660113106823932361029753632414520324447890822855131136"}, + {"0x1.c2d43b93b0a8cp+441", 309, "10000000000000000223511723594768599335098409300973759560478836428900264860242343595976203511843100595010152570837624953702918544949248"}, + {"0x1.19c4a53c4e697p+445", 309, "99999999999999992148203649670699315007549827372972461504375111049848301607660324472857261615145089428049364457837845490532419930947584"}, + {"0x1.19c4a53c4e698p+445", 309, "100000000000000012322030822224672671694418358646502729705201617528156995597186547446666808621716922472153686958914653583525950968037376"}, + {"0x1.6035ce8b6203dp+448", 309, "999999999999999961829690841814939863449235336276785151445404123455100404055655690676191710164594560368702289580532071091311261383655424"}, + {"0x1.6035ce8b6203ep+448", 309, "1000000000000000123220308222246726716944183586465027297052016175281569955971865474466668086217169224721536869589146535835259509680373760"}, + {"0x1.b843422e3a84cp+451", 309, "9999999999999999295515673657285824927502456862391367223240817130898064936724137339180964349540796274981353735788091781425216117243117568"}, + {"0x1.b843422e3a84dp+451", 309, "10000000000000000586640612700740119755462042863897304388093713545509821352053815609504775357961393589804030375857007499376802103616864256"}, + {"0x1.132a095ce492fp+455", 309, "99999999999999982626157224225223890651347880611866174913584999992086598044603947229219155428043184231232124237329592070639473281441202176"}, + {"0x1.132a095ce4930p+455", 309, "100000000000000003284156248920492607898701256635961169551231342625874700689878799554400131562772741268394950478432243557864849063421149184"}, + {"0x1.57f48bb41db7bp+458", 309, "999999999999999867577570291642776341008185558166851738411142685188442185736589176942553506549890956386646894855501223680845484378371915776"}, + {"0x1.57f48bb41db7cp+458", 309, "1000000000000000032841562489204926078987012566359611695512313426258747006898787995544001315627727412683949504784322435578648490634211491840"}, + {"0x1.adf1aea12525ap+461", 309, "9999999999999999006303687311552062886039509598054037298313768334025031499690289406628430683654582476461074168412654660604060856295398309888"}, + {"0x1.adf1aea12525bp+461", 309, "10000000000000000328415624892049260789870125663596116955123134262587470068987879955440013156277274126839495047843224355786484906342114918400"}, + {"0x1.0cb70d24b7378p+465", 309, "99999999999999984774589122793531837245072631718372054355900219626000560719712531871037976946055058163097058166404267825310912362767116664832"}, + {"0x1.0cb70d24b7379p+465", 309, "100000000000000005928380124081487003706362488767045328864850074482999577828473980652023296508018124569151792237293382948229697163514582401024"}, + {"0x1.4fe4d06de5056p+468", 309, "999999999999999847745891227935318372450726317183720543559002196260005607197125318710379769460550581630970581664042678253109123627671166648320"}, + {"0x1.4fe4d06de5057p+468", 309, "1000000000000000016976219238238959704141045173573106739630601035115997744067216908958262325956255112879408454231155599236459402033650892537856"}, + {"0x1.a3de04895e46cp+471", 309, "9999999999999999154380224320567749051268538597394750219876417318024024619451619548095327920588323941303457306908878466464492349900630570041344"}, + {"0x1.a3de04895e46dp+471", 309, "10000000000000000508222848402996879704791089448509839788449208028871961714412352270078388372553960191290960287445781834331294577148468377157632"}, + {"0x1.066ac2d5daec3p+475", 309, "99999999999999980713061250546244445284504979165026785650181847493456749434830333705088795590158149413134549224793557721710505681023603243483136"}, + {"0x1.066ac2d5daec4p+475", 309, "100000000000000002374543235865110535740865792782868218747346498867023742954202057256817762821608329412934596913384011607579341316989008157343744"}, + {"0x1.4805738b51a74p+478", 309, "999999999999999850453576476100176633757771418885950722696147777681701481387046784154345890364481854130945587625116484988842728082166842262552576"}, + {"0x1.4805738b51a75p+478", 309, "1000000000000000023745432358651105357408657927828682187473464988670237429542020572568177628216083294129345969133840116075793413169890081573437440"}, + {"0x1.9a06d06e26112p+481", 309, "9999999999999999890870611821409196126784806260401358945180015464725302399110258148854112806457630061296658928320953898584032761523454337112604672"}, + {"0x1.9a06d06e26113p+481", 309, "10000000000000001277205458881816625915991898331943210663398553152633589984350048456164766709270441581283861980390742947279638242225240251599683584"}, + {"0x1.00444244d7cabp+485", 309, "99999999999999993363366729972462242111019694317846182578926003895619873650143420259298512453325054533017777074930382791057905692427399713177731072"}, + {"0x1.00444244d7cacp+485", 309, "100000000000000015544724282938981118738333167462515810070422606902152475013980065176268974898330038852813025908047007570187593383655974344970993664"}, + {"0x1.405552d60dbd6p+488", 309, "999999999999999977996382405657660174364823889467801080772253244969263939229107492426926049423260513969768268415537077468838432306731146395363835904"}, + {"0x1.405552d60dbd7p+488", 309, "1000000000000000155447242829389811187383331674625158100704226069021524750139800651762689748983300388528130259080470075701875933836559743449709936640"}, + {"0x1.906aa78b912cbp+491", 309, "9999999999999999070160382361647997691574207754048582727994641153483596148648302286926205695992445641464234721495638781756234316947997075736253956096"}, + {"0x1.906aa78b912ccp+491", 309, "10000000000000000489767265751505205795722270035307438887450423745901682635933847561612315292472764637931130646815102767620534329186625852171022761984"}, + {"0x1.f485516e7577ep+494", 309, "99999999999999993540817590396194393124038202103003539598857976719672134461054113418634276152885094407576139065595315789290943193957228310232077172736"}, + {"0x1.f485516e7577fp+494", 309, "100000000000000004897672657515052057957222700353074388874504237459016826359338475616123152924727646379311306468151027676205343291866258521710227619840"}, + {"0x1.38d352e5096afp+498", 309, "999999999999999980835596172437374590573120014030318793091164810154100112203678582976298268616221151962702060266176005440567032331208403948233373515776"}, + {"0x1.38d352e5096b0p+498", 309, "1000000000000000162545277246339097227904071986031452381501504981983615182576228378136120296965701983510464738707067395631197433897752887331883780669440"}, + {"0x1.8708279e4bc5ap+501", 309, "9999999999999998718097875280963410081745488308296386400449607070563910699801487058804050516065326530340444532016411713261887913912817139180431292235776"}, + {"0x1.8708279e4bc5bp+501", 309, "10000000000000000171775323872177191180393104084305455107732328445200031262781885420082626742861173182722545959543542834786931126445173006249634549465088"}, + {"0x1.e8ca3185deb71p+504", 309, "99999999999999992995688547174489225212045346187000138833626956204183589249936464033154810067836651912932851030272641618719051989257594860081125951275008"}, + {"0x1.e8ca3185deb72p+504", 309, "100000000000000004625108135904199474001226272395072688491888727201272553753779650923383419882203425131989662450489690590919397689516441796634752009109504"}, + {"0x1.317e5ef3ab327p+508", 309, "999999999999999999733403004123153744855539019118436686285840188024369679522423761672919759564567158443669378824028710020392594094129030220133015859757056"}, + {"0x1.317e5ef3ab328p+508", 309, "1000000000000000185804116423798517725482433838447597480818028523977793111583914751916577516594435529948578361547501493575598125298270581204991032785108992"}, + {"0x1.7dddf6b095ff0p+511", 309, "9999999999999998880909749523179353564794021275209402095665271864523156202855291675267251053466461355407239891899450398872692753716440996292182057045458944"}, + {"0x1.7dddf6b095ff1p+511", 309, "10000000000000000369475456880582265409809179829842688451922778552150543659347219597216513109705408327446511753687232667314337003349573404171046192448274432"}, + {"0x1.dd55745cbb7ecp+514", 309, "99999999999999988809097495231793535647940212752094020956652718645231562028552916752672510534664613554072398918994503988726927537164409962921820570454589440"}, + {"0x1.dd55745cbb7edp+514", 309, "100000000000000000717623154091016830408061481189160311806712772146250661680488340128266606984576189330386573813296762136260081534229469225952733653677113344"}, + {"0x1.2a5568b9f52f4p+518", 309, "999999999999999983359180223191721714560372275017470536367007614460468417501012554531477876945938741751237388344363105067534507348164573733465510370326085632"}, + {"0x1.2a5568b9f52f5p+518", 309, "1000000000000000173895590764939294430722312570010531189967968470476774011931979328540983420144523954172264186653199235428064971301205521941960119701886468096"}, + {"0x1.74eac2e8727b1p+521", 309, "9999999999999999833591802231917217145603722750174705363670076144604684175010125545314778769459387417512373883443631050675345073481645737334655103703260856320"}, + {"0x1.74eac2e8727b2p+521", 309, "10000000000000001357883086565897798874899245110119190592477762992735128930457859737390823115048069116880588269914320093559588785105973323002611978355743916032"}, + {"0x1.d22573a28f19dp+524", 309, "99999999999999995287335453651211007997446182781858083179085387749785952239205787068995699003416510776387310061494932420984963311567802202010637287727642443776"}, + {"0x1.d22573a28f19ep+524", 309, "100000000000000007481665728323055661831810361661413965009546882534829510282787660605604053768125964371333025153260444764058913004562422887354292284947506921472"}, + {"0x1.2357684599702p+528", 309, "999999999999999928484693987168420772305733470059469068129930887927772406304894123616740280504746200573981670431418299523701733729688780649419062882836695482368"}, + {"0x1.2357684599703p+528", 309, "1000000000000000123593978381917935233655560332132363177417314804488469335002204100202473956740097458093113111899666497012884928817602711614917542838354527125504"}, + {"0x1.6c2d4256ffcc2p+531", 309, "9999999999999998504409802292686149877658027252303114244149773213034936348259701329824468100106056975663290938441190205280284556945232082632196709006295628251136"}, + {"0x1.6c2d4256ffcc3p+531", 309, "10000000000000000065284077450682265568456642148886267118448844545520511777838181142510337509988867035816342470187175785193750117648543530356184548650438281396224"}, + {"0x1.c73892ecbfbf3p+534", 309, "99999999999999991287595123558845961539774732109363753938694017460291665200910932548988158640591809997245115511395844372456707812265566617217918448639526895091712"}, + {"0x1.c73892ecbfbf4p+534", 309, "100000000000000003774589324822814887066163651282028976933086588120176268637538771050475113919654290478469527765363729011764432297892058199009821165792668120252416"}, + {"0x1.1c835bd3f7d78p+538", 309, "999999999999999937849939638116397466450525159438967985375725315922685858882365002492855496964043060934899979621894213003182527093908649335762989920701551401238528"}, + {"0x1.1c835bd3f7d79p+538", 309, "1000000000000000137641846858339900274872747866201611553286006446480839513868410418516646781429042748634490575685380367232106118863932514644433433395151811003809792"}, + {"0x1.63a432c8f5cd6p+541", 309, "9999999999999999378499396381163974664505251594389679853757253159226858588823650024928554969640430609348999796218942130031825270939086493357629899207015514012385280"}, + {"0x1.63a432c8f5cd7p+541", 309, "10000000000000000976834654142951997131883033248490828397039502203692087828712013353118885245360428110945724564726831363863214005099277415826993447002617590832955392"}, + {"0x1.bc8d3f7b3340bp+544", 309, "99999999999999987391652932764487656775541389327492204364443535414407668928683046936524228593524316087103098888157864364992697772750101243698844800887746832841572352"}, + {"0x1.bc8d3f7b3340cp+544", 309, "100000000000000000178334994858791836514563642560301392710701527770129502847789953562046870799284296099876897036220978235643807646031628623453753183252563447406133248"}, + {"0x1.15d847ad00087p+548", 309, "999999999999999899489893451833484927233458399740540420336951338855520357125044282616287570346763120896578585177704871391229197474064067196498264773607101557544845312"}, + {"0x1.15d847ad00088p+548", 309, "1000000000000000104076806445342351803057814451465487433877079216547069699830754788624649845638922801100959355546714693321646955446568505272576798891444167390577819648"}, + {"0x1.5b4e5998400a9p+551", 309, "9999999999999999404072760505352583023983296100855298230449769143938302256661863838179600254051950569374547392515068357773127490685649548117139715971745147241514401792"}, + {"0x1.5b4e5998400aap+551", 309, "10000000000000001040768064453423518030578144514654874338770792165470696998307547886246498456389228011009593555467146933216469554465685052725767988914441673905778196480"}, + {"0x1.b221effe500d3p+554", 309, "99999999999999990767336997157383960226643264180953830087855645396318233083327270285662206135844950810475381599246526426844590779296424471954140613832058419086616428544"}, + {"0x1.b221effe500d4p+554", 309, "100000000000000003860899428741951440279402051491350438954423829568577391016492742670197391754543170343555750902863155030391327289536708508823166797373630632400726786048"}, + {"0x1.0f5535fef2084p+558", 309, "999999999999999933860494834742974562371950216430331518611692822307700646699603647625692432595845947170914554599698521475539380813444812793279458505403728617494385000448"}, + {"0x1.0f5535fef2085p+558", 309, "1000000000000000143357493740096054243216090813396677260476783769063847173630251205778255402495017459700200463457564579132287164977289357383183877442068884030520150720512"}, + {"0x1.532a837eae8a5p+561", 309, "9999999999999999338604948347429745623719502164303315186116928223077006466996036476256924325958459471709145545996985214755393808134448127932794585054037286174943850004480"}, + {"0x1.532a837eae8a6p+561", 309, "10000000000000001014580939590254383070472626940034081121037655797126178682441216941477428085151831571943432816859913676009376081445204484652029936547358529479149975764992"}, + {"0x1.a7f5245e5a2cep+564", 309, "99999999999999990034097500988648181343688772091571619991327827082671720239070003832128235741197850516622880918243995225045973534722968565889475147553730375141026248523776"}, + {"0x1.a7f5245e5a2cfp+564", 309, "100000000000000003441905430931245280917713770297417747470693647675065097962631447553892265814744827318497179085147422915077831721209019419643357959500300321574675254607872"}, + {"0x1.08f936baf85c1p+568", 309, "999999999999999953972206729656870211732987713739100709830741553196290713284945813208338477706166412373726001850053663010587168093173889073910282723323583537144858509574144"}, + {"0x1.08f936baf85c2p+568", 309, "1000000000000000168497133608738423804917387685032638749500594682674584756861928912756562958882918041203714772520508506051096899076950702733972407714468702680083242606919680"}, + {"0x1.4b378469b6731p+571", 309, "9999999999999999110672213538405594930961077194803931018967709273006319045695491932986935814708160866077282477159626944024852218964185263418978577250945597085571816901050368"}, + {"0x1.4b378469b6732p+571", 309, "10000000000000000826871628571058023676436276965152235336326534308832671394311356729372731664122173896717192642523265688348930066834399772699475577180106550229078889679814656"}, + {"0x1.9e056584240fdp+574", 309, "99999999999999987674323305318751091818660372407342701554959442658410485759723189737097766448253582599493004440868991951600366493901423615628791772651134064568704023452975104"}, + {"0x1.9e056584240fep+574", 309, "100000000000000001403918625579970521782461970570129136093830042945021304548650108108184133243565686844612285763778101906192989276863139689872767772084421689716760605683089408"}, + {"0x1.02c35f729689ep+578", 309, "999999999999999849284042412665072058259000527747854146471853226010883220019378060628804930891911617504691481762871699606818419373090804007799965727644765395390927070069522432"}, + {"0x1.02c35f729689fp+578", 309, "1000000000000000068957567536844582937679826098352437099093782830596656320642208754566186799616905285426599982929417458880300383900478261195703581718577367397759832385751351296"}, + {"0x1.4374374f3c2c6p+581", 309, "9999999999999999371534524623368764100273307559896873275206250678451924602685103382037576783819090846734548822294900033162112051840457868829614121240178061963384891963422539776"}, + {"0x1.4374374f3c2c7p+581", 309, "10000000000000001128922725616804851135639912124733536896181687515138109407667748933536631733619040190109816831627266107349967768059557526332843049167638877982336134488877170688"}, + {"0x1.945145230b377p+584", 309, "99999999999999986685792442259943292861266657339622078268160759437774506806920451614379548038991111093844416185619536034869697653528180058283225500691937355558043949532406874112"}, + {"0x1.945145230b378p+584", 309, "100000000000000000744898050207431989144199493858315387235964254131263985246781616026371987637390705840846560260278464628372543383280977318309056924111623883709653889736043921408"}, + {"0x1.f965966bce055p+587", 309, "999999999999999894976135638494410321178532246433607400617214583764724024948926844967780359586710300432448450005513217535702667994787395102883917853758746611883659375731342835712"}, + {"0x1.f965966bce056p+587", 309, "1000000000000000007448980502074319891441994938583153872359642541312639852467816160263719876373907058408465602602784646283725433832809773183090569241116238837096538897360439214080"}, + {"0x1.3bdf7e0360c35p+591", 309, "9999999999999998724815666657784284071258397080036981062687289922551408594451489819085924562292709488372450194860589317860981148271829194868425875762872481668410834714055235600384"}, + {"0x1.3bdf7e0360c36p+591", 309, "10000000000000000524381184475062837195473800154429724610566137243318061834753718863820956830887857615988724636416932177829345401680187244151732297960592357271816907060120777654272"}, + {"0x1.8ad75d8438f43p+594", 309, "99999999999999998045549773481514159457876389246726271914145983150114005386328272459269439234497983649422148597943950338419997003168440244384097290815044070304544781216945608327168"}, + {"0x1.8ad75d8438f44p+594", 309, "100000000000000012442073916019742584451599613841868220297176761716247231308746104817149697383259168670352344130394693218166911030435304638650548668396803075131793359985469944758272"}, + {"0x1.ed8d34e547313p+597", 309, "999999999999999894076352879585771044616424544896411028843275160104340698328775730445412843452412726368640312784735046105718485868083216078242264642659886674081956339558310064685056"}, + {"0x1.ed8d34e547314p+597", 309, "1000000000000000009248546019891598444566210341657546615907521388633406505708118389308454908642502206536081877044340989143693798086218131232373875663313958712699944969706504756133888"}, + {"0x1.3478410f4c7ecp+601", 309, "9999999999999999171107915076469365246063817042486381462561244058101538598046442622180212564904306224021286256366562347133135483117101991090685868467907010818055540655879490029748224"}, + {"0x1.3478410f4c7edp+601", 309, "10000000000000001013863005321362603645260389790664550855589183714566591516115925163988885607945737906700351284520257435740740478607260633556791644798372163435943358738250605092929536"}, + {"0x1.819651531f9e7p+604", 309, "99999999999999991711079150764693652460638170424863814625612440581015385980464426221802125649043062240212862563665623471331354831171019910906858684679070108180555406558794900297482240"}, + {"0x1.819651531f9e8p+604", 309, "100000000000000006453119872723839559654210752410289169769835957832735809325020286556271509993374515701645382788895184180192194795092289050635704895322791329123657951217763820802932736"}, + {"0x1.e1fbe5a7e7861p+607", 309, "999999999999999946594872951565228338993526868219488856544571440313594706493755982886960025179093529324993666087115356131035228239552737388526279268078143523691759154905886843985723392"}, + {"0x1.e1fbe5a7e7862p+607", 309, "1000000000000000064531198727238395596542107524102891697698359578327358093250202865562715099933745157016453827888951841801921947950922890506357048953227913291236579512177638208029327360"}, + {"0x1.2d3d6f88f0b3cp+611", 309, "9999999999999998286585471758920610814449462123360860153907833022998313197373091002112049504244419016335335042852788704601485085281825842706955095829283737561469387976341354799421194240"}, + {"0x1.2d3d6f88f0b3dp+611", 309, "10000000000000000173566684169691286935226752617495305612368443231218527385476241124924130700318845059398697631682172475335672600663748292592247410791680053842186513692689376624118857728"}, + {"0x1.788ccb6b2ce0cp+614", 309, "99999999999999997961704416875371517110712945186684165206763211895744845478556111003617144611039598507860251139162957211888350975873638026151889477992007905860430885494197722591793250304"}, + {"0x1.788ccb6b2ce0dp+614", 309, "100000000000000013057554116161536926076931269139759728874448093561506558983381311986113794179635006852367151849798027377761851098929017625234227997691178436106167891224981897189374558208"}, + {"0x1.d6affe45f818fp+617", 309, "999999999999999979617044168753715171107129451866841652067632118957448454785561110036171446110395985078602511391629572118883509758736380261518894779920079058604308854941977225917932503040"}, + {"0x1.d6affe45f8190p+617", 309, "1000000000000000100383841763043038442836876043491446161409111172283542162824162717896144642659159251834657717076710133445871510743179417054177602937513443300570204900788250622698582966272"}, + {"0x1.262dfeebbb0f9p+621", 309, "9999999999999999071569656121801212080692814968920789464627446869617922299624001453201875281811380250249693879805812353226907091680705581859236698853640605134247712274342131878495422251008"}, + {"0x1.262dfeebbb0fap+621", 309, "10000000000000001003838417630430384428368760434914461614091111722835421628241627178961446426591592518346577170767101334458715107431794170541776029375134433005702049007882506226985829662720"}, + {"0x1.6fb97ea6a9d37p+624", 309, "99999999999999986851159038200753776111576258757220550347347138989744224339004763080499610528553377966303172216135545569805454885304878641227288327493418395599568449276340570087973407686656"}, + {"0x1.6fb97ea6a9d38p+624", 309, "100000000000000002309309130269787154892983822485169927543056457815484218967945768886576179686795076111078238543825857419659919011313587350687602971665369018571203143144663564875896666980352"}, + {"0x1.cba7de5054485p+627", 309, "999999999999999899427890566145604518678577715028104257864890027548922232647929642417149243602017175952581854816736079397763477105066203831193512563278085201938953880500051690455580595453952"}, + {"0x1.cba7de5054486p+627", 309, "1000000000000000023093091302697871548929838224851699275430564578154842189679457688865761796867950761110782385438258574196599190113135873506876029716653690185712031431446635648758966669803520"}, + {"0x1.1f48eaf234ad3p+631", 309, "9999999999999998746948504188351511126283256130633852543517551174277382412416240331274267329488304589209417486924315804379963345034522698960570091326029642051843383703107348987949033805840384"}, + {"0x1.1f48eaf234ad4p+631", 309, "10000000000000000725591715973187783610303424287811372824568343983972101724920689074452068181743241951740625976868675721161334753163637413771490365780039321792212624518252692320803210995433472"}, + {"0x1.671b25aec1d88p+634", 309, "99999999999999991426771465453187656230872897620693565997277097362163262749171300799098274999392920617156591849131877877362376266603456419227541462168315779999172318661364176545198692437590016"}, + {"0x1.671b25aec1d89p+634", 309, "100000000000000007255917159731877836103034242878113728245683439839721017249206890744520681817432419517406259768686757211613347531636374137714903657800393217922126245182526923208032109954334720"}, + {"0x1.c0e1ef1a724eap+637", 309, "999999999999999914267714654531876562308728976206935659972770973621632627491713007990982749993929206171565918491318778773623762666034564192275414621683157799991723186613641765451986924375900160"}, + {"0x1.c0e1ef1a724ebp+637", 309, "1000000000000000040900880208761398001286019738266296957960021713442094663491997727554362004538245197373563261847757813447631532786297905940174312186739777303375354598782943738754654264509857792"}, + {"0x1.188d357087712p+641", 309, "9999999999999998636144484328400679867178126713831911407778706776934478130915991201656310481762028096907669811487431649040206546179292274931158555956605099986382706217459209761309199883223171072"}, + {"0x1.188d357087713p+641", 309, "10000000000000000662275133196073022890814778906781692175574718614061870706920546714670378554471083956139627305190456203824330868103505742897540916997511012040520808812168041334151877325366493184"}, + {"0x1.5eb082cca94d7p+644", 309, "99999999999999994465967438754696170766327875910118237148971115117854351613178134068619377108456504406004528089686414709538562749489776621177115003729674648080379472553427423904462708600804999168"}, + {"0x1.5eb082cca94d8p+644", 309, "100000000000000010675012629696074914955421093453716483291339209814873492221214578172731921690128951279860188039310611147811557324883484364908173892056921944513484293311098076487204128137951576064"}, + {"0x1.b65ca37fd3a0dp+647", 309, "999999999999999977077764769429719196041465194188378863774447340572581797347854228894418860247909937807756600796112539971931616645685181699233267813951241073670004367049615544210109925082343145472"}, + {"0x1.b65ca37fd3a0ep+647", 309, "1000000000000000106750126296960749149554210934537164832913392098148734922212145781727319216901289512798601880393106111478115573248834843649081738920569219445134842933110980764872041281379515760640"}, + {"0x1.11f9e62fe4448p+651", 309, "9999999999999999511432924639235132053389160461186216699466583890573511723749959183278387889172340228095875448767138256706948253250552493092635735926276453993770366538373425000777236538229086224384"}, + {"0x1.11f9e62fe4449p+651", 309, "10000000000000001586190709079731611309593092306766792205689700011791961721578624028604793595626413427949399922319035400805891558900947084290211273632164107937207783595355268531368138238983848067072"}, + {"0x1.56785fbbdd55ap+654", 309, "99999999999999995114329246392351320533891604611862166994665838905735117237499591832783878891723402280958754487671382567069482532505524930926357359262764539937703665383734250007772365382290862243840"}, + {"0x1.56785fbbdd55bp+654", 309, "100000000000000011712391521916323154583523059376506771044450767875482717220128910595395124543355987879786950276086559719861028977708681660506961660909865771485203001839588998252499578988328956985344"}, + {"0x1.ac1677aad4ab0p+657", 309, "999999999999999884751043361827625869140390227060043253747518673178360772444478643277393806310703680414274761723053117059528639544242622390941156386039240473187039308013923507098814799398756243472384"}, + {"0x1.ac1677aad4ab1p+657", 309, "1000000000000000017535541566019400541537441865177200086145798104936341572305513193378283771523764365204900328030374534281861011105867876227585990799216050325567033999660761493056632508247061001404416"}, + {"0x1.0b8e0acac4eaep+661", 309, "9999999999999998847510433618276258691403902270600432537475186731783607724444786432773938063107036804142747617230531170595286395442426223909411563860392404731870393080139235070988147993987562434723840"}, + {"0x1.0b8e0acac4eafp+661", 309, "10000000000000000972062404885344653449756728480474941855847657639911300522221339234388177506516007760792756678147673846152604340428430285295728914471221362369950308146488642846313231335560438561636352"}, + {"0x1.4e718d7d7625ap+664", 309, "99999999999999996973312221251036165947450327545502362648241750950346848435554075534196338404706251868027512415973882408182135734368278484639385041047239877871023591066789981811181813306167128854888448"}, + {"0x1.4e718d7d7625bp+664", 309, "100000000000000013969727991387583324014272937224498437195221518215368390817766497947110253951978019521227584903311023812640679294256310975729923845933871538975662911597585244013782480038750137870188544"}, + {"0x1.a20df0dcd3af0p+667", 309, "999999999999999901747459131964173027207212836739039328294498440443382314826691065690307721857975448067474834210390258463987183104130654882031695190925872134291678628544718769301415466131339252487684096"}, + {"0x1.a20df0dcd3af1p+667", 309, "1000000000000000037718785293056550291741793714171007924670336578563554653884390444993619046236149589293075414109087389699655531583234914810756005630018925423128793192791080866922220799992003324610084864"}, + {"0x1.0548b68a044d6p+671", 309, "9999999999999999017474591319641730272072128367390393282944984404433823148266910656903077218579754480674748342103902584639871831041306548820316951909258721342916786285447187693014154661313392524876840960"}, + {"0x1.0548b68a044d7p+671", 309, "10000000000000001193015809897119766504625422406301890824958394614356580573190100725756058408630540740284357620483056684410565406706974707679905918934747573964310619313388981254947040003084017678835253248"}, + {"0x1.469ae42c8560cp+674", 309, "99999999999999998876910787506329447650934459829549922997503484884029261182361866844442696946000689845185920534555642245481492613075738123641525387194542623914743194966239051177873087980216425864602058752"}, + {"0x1.469ae42c8560dp+674", 309, "100000000000000016281240536126153737511360812140841903333610766563411320581747387395266546466406979922062794761588875043647041218401083394518237123398453444885893859189773399673336170714381427096269357056"}, + {"0x1.98419d37a6b8fp+677", 309, "999999999999999988769107875063294476509344598295499229975034848840292611823618668444426969460006898451859205345556422454814926130757381236415253871945426239147431949662390511778730879802164258646020587520"}, + {"0x1.98419d37a6b90p+677", 309, "1000000000000000128003745864021888795392755416785835072663893102275349087018702832851017765623257219066874199161822284840139314973360143403428947761576712806916637263450665299742435541675484268499358973952"}, + {"0x1.fe52048590672p+680", 309, "9999999999999999052283250816881378851792981072012977243617198967792587267065681698004724917620567060828502090557969050236202928251957239362070375381666542984859087613894256390005080826781722527340175556608"}, + {"0x1.fe52048590673p+680", 309, "10000000000000000166160354728550133402860267619935663985128064995273039068626355013257451286926569625748622041088095949318798038992779336698179926498716835527012730124200454693714718121768282606166882648064"}, + {"0x1.3ef342d37a407p+684", 309, "99999999999999986067324092522138770313660664528439025470128525568004065464414123719036343698981660348604541103459182906031648839556284004276265549348464259679976306097717770685212259087870984958094927200256"}, + {"0x1.3ef342d37a408p+684", 309, "100000000000000003889357755108838843130737249295202013334302382007691294289384896763079965607877701387326460311941213291353170611409437561654018367221268940354434586262616943544566455807655946219322240663552"}, + {"0x1.8eb0138858d09p+687", 309, "999999999999999896317308250394787848770759814817916230429632968559415112294082783278450680807608685563489249451555889830959531939269147157518161129230251958148679621306976052570830984318279772103403898929152"}, + {"0x1.8eb0138858d0ap+687", 309, "1000000000000000038893577551088388431307372492952020133343023820076912942893848967630799656078777013873264603119412132913531706114094375616540183672212689403544345862626169435445664558076559462193222406635520"}, + {"0x1.f25c186a6f04cp+690", 309, "9999999999999999818630698308109481982927274216983785721776674794699138106539424938898600659703096825493544616522696356805028364441642842329313746550197144253860793660984920822957311285732475861572950035529728"}, + {"0x1.f25c186a6f04dp+690", 309, "10000000000000000959240852713658286643220175642056616945083801606839120751337554413717392461872443451971747445865546301465605757840244670001489926894056643817026123591538467885955979875798713382291498097180672"}, + {"0x1.37798f428562fp+694", 309, "99999999999999989061425747836704382546929530769255207431309733449871519907009213590435672179676195243109823530484164010765664497227613801915728022751095446033285297165420831725583764136794858449981115862089728"}, + {"0x1.37798f4285630p+694", 309, "100000000000000007311188218325485257111615953570420507004223762444111242223779285187536341014385741266761068799969763125334902791605243044670546908252847439043930576054277584733562461577854658781477884848504832"}, + {"0x1.8557f31326bbbp+697", 309, "999999999999999927113782419344605574598668153294882673458925392487194643703632279098558059466181044478400725843812838336795121561031396504666917998514458446354143529431921823271795036250068185162804696593727488"}, + {"0x1.8557f31326bbcp+697", 309, "1000000000000000073111882183254852571116159535704205070042237624441112422237792851875363410143857412667610687999697631253349027916052430446705469082528474390439305760542775847335624615778546587814778848485048320"}, + {"0x1.e6adefd7f06aap+700", 309, "9999999999999999563134023721266549739021664297767471527755878388779781994104643936539191296017163181162427182749897969201059028320356032930746282153172616351711759756540926280845609521557638656931995269719916544"}, + {"0x1.e6adefd7f06abp+700", 309, "10000000000000000731118821832548525711161595357042050700422376244411124222377928518753634101438574126676106879996976312533490279160524304467054690825284743904393057605427758473356246157785465878147788484850483200"}, + {"0x1.302cb5e6f642ap+704", 309, "99999999999999990959401044767537593501656918740576398586892792465272451027953301036534141738485988029569553038510666318680865279842887243162229186843277653306392406169861934038413548670665077684456779836676898816"}, + {"0x1.302cb5e6f642bp+704", 309, "100000000000000009647157814548049209055895815688969665349556758155373926680325854351965226625228563157788428194463919811999765293285579587743163725597071694149293171752051249118583734850310313223909471278765965312"}, + {"0x1.7c37e360b3d35p+707", 309, "999999999999999984345037526797422397233524775199337052919583787413130412889023223627065756931830180808571031008919677160084252852199641809946030023447952696435527124027376600704816231425231719002378564135125254144"}, + {"0x1.7c37e360b3d36p+707", 309, "1000000000000000133847091685041515321667435950786483187020895512933942218108003650150514436025770781834322032256545705106635452959741180566593506333478305023178733248684891121346177720862393603318000095671837786112"}, + {"0x1.db45dc38e0c82p+710", 309, "9999999999999999544446266951486038123467425400819078260993214423089680518452271383223760211130420606034208307593944715707740128306913340586165347614418822310868858990958736965765439335377993421392542578277827477504"}, + {"0x1.db45dc38e0c83p+710", 309, "10000000000000000740462700217438781518938714805516247333803708227256174960204114795411349643881945414240216317574952939280149729167245650639345158094661640924814507988218853130896331250875288495917514830571527733248"}, + {"0x1.290ba9a38c7d1p+714", 309, "99999999999999990660396936451049407652789096389402106318690169014230827417515340183487244380298106827518051036015414262787762879627804165648934234223216948652905993920546904997130825691790753915825536773603473752064"}, + {"0x1.290ba9a38c7d2p+714", 309, "100000000000000009796659868706293301980329726864556811483658069880894738485544834778488675304322503758814179195711545839946316493393121126499811201907102046476036377876708763639225096339747475108225092810302677843968"}, + {"0x1.734e940c6f9c5p+717", 309, "999999999999999868331443500000006287872809702943711652856965888408980452039094412644869581954932274412588254040761879473560521568747407734787588406864399290882799171293145332687119715621994096773456255662636329336832"}, + {"0x1.734e940c6f9c6p+717", 309, "1000000000000000021421546958041957442493134746744949294176709095342291740583330369404881029347127449862957279318330932090828950478869943421594604148335480073467842242942440201823873880805647866312652703956229962072064"}, + {"0x1.d022390f8b837p+720", 309, "9999999999999999601855055748251769806450047292244542376488118125689672251656359867008764503902493796828096692073033110439215789148209291468717978517470477604338250142827222541691722147321863584969741246387925089779712"}, + {"0x1.d022390f8b838p+720", 309, "10000000000000000826575883412587379043412647642654443507046063781156162560010247521088856083040055200431048894293585531377363220429189576963174104449239123865018594716021581494785755468791093741283312832736674151661568"}, + {"0x1.221563a9b7322p+724", 309, "99999999999999988670225591496504042642724870819986016981533507324097780666440272745607095564199569546663253707407016578763273303796211201720443029584092898479300433989106071698353021544403254911815982945786756526505984"}, + {"0x1.221563a9b7323p+724", 309, "100000000000000008265758834125873790434126476426544435070460637811561625600102475210888560830400552004310488942935855313773632204291895769631741044492391238650185947160215814947857554687910937412833128327366741516615680"}, + {"0x1.6a9abc9424febp+727", 309, "999999999999999965084388885482519417592855130626093842171043595190833186399051537317196816706799625297221478016185520727674168639944850288849622355474122345476546392575499689981548348018063279122228410984187505225498624"}, + {"0x1.6a9abc9424fecp+727", 309, "1000000000000000121848654826517477399924067975478561186882460639090543945868349157039448538836407484958399359900416230607757039843910326832140006474740509066843630497944377635977584613166124739130365574036827385146376192"}, + {"0x1.c5416bb92e3e6p+730", 309, "9999999999999999964372420736895110140590976995965873111133270039707753382929110612616471611327211972294570543930316627036907428807379455975076991793273996897499632136492752791807556010476755711238558435947154812096741376"}, + {"0x1.c5416bb92e3e7p+730", 309, "10000000000000001218486548265174773999240679754785611868824606390905439458683491570394485388364074849583993599004162306077570398439103268321400064747405090668436304979443776359775846131661247391303655740368273851463761920"}, + {"0x1.1b48e353bce6fp+734", 309, "99999999999999984594354677029595135102113336853821866019036664182705300920238534632828550788829765195472628778417018121881118652493108811594893042483166843723756247249515245102456078650553656951604416706418119648563167232"}, + {"0x1.1b48e353bce70p+734", 309, "100000000000000004660180717482069756840508580994937686142098045801868278132308629957276771221419571232103397659598548986531726166600689809136062209749264344058743012736731622189948720589505523832645973577156024278435495936"}, + {"0x1.621b1c28ac20bp+737", 309, "999999999999999886075198851200900594497923856820450300436489405065378963626525536977181948753477264027987825546533242948112401553146250111031268759363863437907536003469585205199546070383440303278127280805657005745376329728"}, + {"0x1.621b1c28ac20cp+737", 309, "1000000000000000046601807174820697568405085809949376861420980458018682781323086299572767712214195712321033976595985489865317261666006898091360622097492643440587430127367316221899487205895055238326459735771560242784354959360"}, + {"0x1.baa1e332d728ep+740", 309, "9999999999999999181805205159248599892793562474462356126333876156560397271658376894962991014456209536865970557564236923315533735757183797070971394269896194384435148282491314085395342974857632902877937717988376531531720556544"}, + {"0x1.baa1e332d728fp+740", 309, "10000000000000000466018071748206975684050858099493768614209804580186827813230862995727677122141957123210339765959854898653172616660068980913606220974926434405874301273673162218994872058950552383264597357715602427843549593600"}, + {"0x1.14a52dffc6799p+744", 309, "99999999999999996954903517948319502092964807244749211214842475260109694882873713352688654575305085714037182409224841134505892881183378706080253249519082903930108094789640533388351546084948006950326015738792668900564521713664"}, + {"0x1.14a52dffc679ap+744", 309, "100000000000000017502309383371653514753081537245251811020857330038132583548033490964923632298277047095547089743554728739908114975629541647562410476799566744273134542648550103525944011430434718636512569974428283241553786306560"}, + {"0x1.59ce797fb817fp+747", 309, "999999999999999928454223448636526995609414612446486912536395043045051171498417578302416590307106934377352009423588636134254484622941461177838218040629861358615028052178586193608330530158506646130887048916655460323666687950848"}, + {"0x1.59ce797fb8180p+747", 309, "1000000000000000092833470372023199096890348452450507710984513881269234280819695799200296412090882625429431268098227736977472261378510764709695475858873732081359239635049862754709070252922400339620379482801740375051580804694016"}, + {"0x1.b04217dfa61dfp+750", 309, "9999999999999999613300728333138614158656013804472910722260188106898877933626732224819925546638620725877678611585164563028980399740553218842096696042786355031638703687528415058284784747112853848287855356936724432692495112994816"}, + {"0x1.b04217dfa61e0p+750", 309, "10000000000000000928334703720231990968903484524505077109845138812692342808196957992002964120908826254294312680982277369774722613785107647096954758588737320813592396350498627547090702529224003396203794828017403750515808046940160"}, + {"0x1.0e294eebc7d2bp+754", 309, "99999999999999988242803431008825880725075313724536108897092176834227990088845967645101024020764974088276981699468968789815350713138205618891818585152157755624664880897462875650012340778461641195382916742883168419985073526276096"}, + {"0x1.0e294eebc7d2cp+754", 309, "100000000000000009283347037202319909689034845245050771098451388126923428081969579920029641209088262542943126809822773697747226137851076470969547585887373208135923963504986275470907025292240033962037948280174037505158080469401600"}, + {"0x1.51b3a2a6b9c76p+757", 309, "999999999999999924509121522475246865178672200286390413373640190927670776874706901000867474584296317792102107215397297714017257980807797893073643852992008461269166974189675556141912776812173197487139230503413422370196749149011968"}, + {"0x1.51b3a2a6b9c77p+757", 309, "1000000000000000092833470372023199096890348452450507710984513881269234280819695799200296412090882625429431268098227736977472261378510764709695475858873732081359239635049862754709070252922400339620379482801740375051580804694016000"}, + {"0x1.a6208b5068394p+760", 309, "9999999999999999918388610622944277578633427011520373324179896670642961784527024602806390495869308408470337715685294734193992593398889846197223766553446979093051960385337504355687757672562640543404353314227442034427503713670135808"}, + {"0x1.a6208b5068395p+760", 309, "10000000000000001264983401419327895432326837028833311705066886193375469816086935788401821995921998869568971002747938248301632620580513580730198422600500768053772541672219001944225017481444457680470275332614057655878576158030168064"}, + {"0x1.07d457124123cp+764", 309, "99999999999999988411127779858373832956786989976700226194703050524569553592790956543300452958271560395914310860351799229078805716535908585708440417158039479244754953558323062848579498254571868337516156995181495372666457581821100032"}, + {"0x1.07d457124123dp+764", 309, "100000000000000009956644432600511718615881550253707240288894882888289682097749535512827356959114607773492443453354095454801046151441888338236034913910900102616284254148427024265175655196680942530570909289367345315883616691581616128"}, + {"0x1.49c96cd6d16cbp+767", 309, "999999999999999884111277798583738329567869899767002261947030505245695535927909565433004529582715603959143108603517992290788057165359085857084404171580394792447549535583230628485794982545718683375161569951814953726664575818211000320"}, + {"0x1.49c96cd6d16ccp+767", 309, "1000000000000000056475411020520841414840626381983058374700565164155456563967578197189219761589459982979768169347536362096565980644606923877305160145603279779419783940304062319818564238082591276919599588305301753272401848696295129088"}, + {"0x1.9c3bc80c85c7ep+770", 309, "9999999999999999185841044429711589466224211962102134844977374370276477415358432917842475759840644797632681207523216662519436418612086534611285553663849717898419964165273969667523488336530932020840491736225123136358120303938278260736"}, + {"0x1.9c3bc80c85c7fp+770", 309, "10000000000000000564754110205208414148406263819830583747005651641554565639675781971892197615894599829797681693475363620965659806446069238773051601456032797794197839403040623198185642380825912769195995883053017532724018486962951290880"}, + {"0x1.01a55d07d39cfp+774", 309, "99999999999999997374062707399103193390970327051935144057886852787877127050853725394623645022622268104986814019040754458979257737456796162759919727807229498567311142603806310797883499542489243201826933949562808949044795771481474727936"}, + {"0x1.01a55d07d39d0p+774", 309, "100000000000000019436671759807052388305883156775590326490339289128326538639931310259419194719485548619626821794275105794118831942800519429348176492482158776899757146408072767288477964251208935175515000298809119290899166699876243210240"}, + {"0x1.420eb449c8842p+777", 309, "999999999999999841364972759543336764420226292177420345984153909836074800974071744757463152045042997962028093539001436578955132142505622028069656690022719315678435403212464369035268207172574280176140941400150227439321732144446136385536"}, + {"0x1.420eb449c8843p+777", 309, "1000000000000000017865845178806930323739528929966661805443773400559670093686692423675827549619949242079148155740876247260071725785255408160775710807422153542338003433646596020960023924842331815965645472194120710174156699571604284243968"}, + {"0x1.9292615c3aa53p+780", 309, "9999999999999999119653217272487741881479473472931169297680017061255129180591200163248089110750054956088761184197513608514017695996055364811520783369824930063422626153861170298051704942404772944919427537177384205332557191153093955289088"}, + {"0x1.9292615c3aa54p+780", 309, "10000000000000000531660196626596490356033894575245100973356972987043891522292165594595004291349304909025721681812512093962950445138053653873169216309020403876699170397334223513449750683762833231235463783529148067211236930570359138156544"}, + {"0x1.f736f9b3494e8p+783", 309, "99999999999999994020546131433094915763903576933939556328154082464128816489313932495174721468699049466761532837205133056038042458244550226238504699576640248260779350025557809411313140906763850021826347864477369777082931390365469918625792"}, + {"0x1.f736f9b3494e9p+783", 309, "100000000000000005316601966265964903560338945752451009733569729870438915222921655945950042913493049090257216818125120939629504451380536538731692163090204038766991703973342235134497506837628332312354637835291480672112369305703591381565440"}, + {"0x1.3a825c100dd11p+787", 309, "999999999999999940205461314330949157639035769339395563281540824641288164893139324951747214686990494667615328372051330560380424582445502262385046995766402482607793500255578094113131409067638500218263478644773697770829313903654699186257920"}, + {"0x1.3a825c100dd12p+787", 309, "1000000000000000120942354671656868962382001670435578817768191183142249744630862900164152357803694488643546272066771136697843816472621283262276046411983423130707191163420128905684081263961470216866716118177799472091300320549064642593292288"}, + {"0x1.8922f31411455p+790", 309, "9999999999999999040580826428657651966904425891201589123842107529410958489455946099092661860636496958724291396331073693328877462044103460624068471125229983529879139676226679317989414380888721568885729507381685429067351125745727105048510464"}, + {"0x1.8922f31411456p+790", 309, "10000000000000000486475973287265010404848153099971055159735310397418651127357734700791903005570128910531738945888832142428584597165509708623196466454966148714674320981543085810557013220039375302073350623645891623631119178909006652304785408"}, + {"0x1.eb6bafd91596bp+793", 309, "99999999999999999081179145438220670296706622164632687453780292502155740721970192601122065475966761298087599260657287627887017431169472094235452683230716826407562484594165232135299736843791138087983021771402091458056119576436948334022754304"}, + {"0x1.eb6bafd91596cp+793", 309, "100000000000000010648340320307079537800256439834788415740925915446217281825184501414715994635435816912547179657119355220684674512140722078228476645868606147885923935036696484075840527556996367953483990701515741014566264001743184712072953856"}, + {"0x1.33234de7ad7e2p+797", 309, "999999999999999828871535006218182557917368774264146678517764203804695831774701602620905646527100834378441867056103929979702975178097221166452191355376717763378564539746214794185426298453038162762816652692429820789419173810082174047524749312"}, + {"0x1.33234de7ad7e3p+797", 309, "1000000000000000013946113804119924437974165856986638331112094170909680489426130543638408513078605724209795153399497011464465488473637220910340574757582946907032347746826714825234078949864321840610832155574248213693581484614981956096327942144"}, + {"0x1.7fec216198ddbp+800", 309, "9999999999999999029013665253788793099400876073531433395554961906466896948352731790279067931477027903109831815934611625736079804963132210640075447162592094208400778225784148066048873590175516339020228538451571779510840981320420868670460264448"}, + {"0x1.7fec216198ddcp+800", 309, "10000000000000000509610295637002728139855252735311366616309601643306774209564163318419090863889067021760658106681756277614179911327452208591182514380241927357631043882428148314438094801465785761804352561506118922744139467759619125060885807104"}, + {"0x1.dfe729b9ff152p+803", 309, "99999999999999993251329913304315801074917514058874200397058898538348724005950180959070725179594357268399970740840405561116998262359962102302968606061220608382468313571129481157267178324335702235770533430624812081575006786082605199485453729792"}, + {"0x1.dfe729b9ff153p+803", 309, "100000000000000005096102956370027281398552527353113666163096016433067742095641633184190908638890670217606581066817562776141799113274522085911825143802419273576310438824281483144380948014657857618043525615061189227441394677596191250608858071040"}, + {"0x1.2bf07a143f6d3p+807", 309, "999999999999999885134206960780312089454635087411784140906440513804611167700736000690226517958758320887173266104495426751070779219941381088594259909647411423049314634698686803624216704482068400828613365568502612232284516294771707790360919932928"}, + {"0x1.2bf07a143f6d4p+807", 309, "1000000000000000074650575649831695774632795300119615593163034400120115457135799236292149453307499328074479031320129942191467592834574340826335964513506590066150788638749118835418037019527222886944981240519484646566146722558989084608335389392896"}, + {"0x1.76ec98994f488p+810", 309, "9999999999999999230374806985905888264902671299533504313577592910677120255877486478106111050285065223246344191476223298391501419428679730361426008304192471516696094355087732099829807674910992980518869405586990190990569575476151831539558138249216"}, + {"0x1.76ec98994f489p+810", 309, "10000000000000000746505756498316957746327953001196155931630344001201154571357992362921494533074993280744790313201299421914675928345743408263359645135065900661507886387491188354180370195272228869449812405194846465661467225589890846083353893928960"}, + {"0x1.d4a7bebfa31aap+813", 309, "99999999999999992303748069859058882649026712995335043135775929106771202558774864781061110502850652232463441914762232983915014194286797303614260083041924715166960943550877320998298076749109929805188694055869901909905695754761518315395581382492160"}, + {"0x1.d4a7bebfa31abp+813", 309, "100000000000000004432795665958347438500428966608636256080197937830963477082618911859584178365170076692451010888562841972100410265623306726829729177688912148325455279810104971033102576911999816916636238052732752107272876955671430431745947427930112"}, + {"0x1.24e8d737c5f0ap+817", 309, "999999999999999874521290314193434603084658115500145579580071256170942927492372459496518833579228824484684143252419893886408557657521935343280724451831297419035632090471862609843762766839539749606096764571247618309588232743975534688554349643169792"}, + {"0x1.24e8d737c5f0bp+817", 309, "1000000000000000068586051851782051496707094173312964986690823395758019319873877212752887919376339615844485246833229637697374894798906086114728229966183096349571541470619505010400634769445777943389257468521053221467463131958534128550160206370177024"}, + {"0x1.6e230d05b76cdp+820", 309, "9999999999999999521471949292288813605336325386252733424243721120057734844449743607990664678980731410286045846847437914107950925140755956518597266575720169912499958425309195700665115678820350271193610461511698595727381924297989722331966923339726848"}, + {"0x1.6e230d05b76cep+820", 309, "10000000000000001073990041592997748754315813848755288681129738236754345983501781634041617365357617741164454675493915864595681622271829162690177310690534561356787233466490334905120091699670255821458896093110143420990381118014458473224813777155784704"}, + {"0x1.c9abd04725480p+823", 309, "99999999999999992109683308321470265755404276937522223728665176967184126166393360027804741417053541441103640811181423240104047857145413152842812577527572916236425034170729678597741204746503691611405533351920096306747820855546959721533975525765152768"}, + {"0x1.c9abd04725481p+823", 309, "100000000000000004529828046727141746947240184637542665783753313900757015278809664236212362908068632088130911440353246844005893434193998802215452930446088047790723234500178792233381012913302936013527818404707654908851814405278709728676750356293615616"}, + {"0x1.1e0b622c774d0p+827", 309, "999999999999999921096833083214702657554042769375222237286651769671841261663933600278047414170535414411036408111814232401040478571454131528428125775275729162364250341707296785977412047465036916114055333519200963067478208555469597215339755257651527680"}, + {"0x1.1e0b622c774d1p+827", 309, "1000000000000000119819148897705446356623417292575549310168061960609007487462594467612569358026776864763472738178563410063470007804231501918390371421971971267233021546978482604147648978133824826548011894363801900701142105351177597329624152546106933248"}, + {"0x1.658e3ab795204p+830", 309, "9999999999999999210968330832147026575540427693752222372866517696718412616639336002780474141705354144110364081118142324010404785714541315284281257752757291623642503417072967859774120474650369161140553335192009630674782085554695972153397552576515276800"}, + {"0x1.658e3ab795205p+830", 309, "10000000000000000800746857348072976168095423879354838955917799224215742423028622941456649692555285746929854721652135745309841019576760278403979222926327228462592673059242454405136015920000672444612205821948817131744093259920359973067672730884158521344"}, + {"0x1.bef1c9657a685p+833", 309, "99999999999999992109683308321470265755404276937522223728665176967184126166393360027804741417053541441103640811181423240104047857145413152842812577527572916236425034170729678597741204746503691611405533351920096306747820855546959721533975525765152768000"}, + {"0x1.bef1c9657a686p+833", 309, "100000000000000004827911520448877862495844246422343156393075429187162764617507655537214145823852994263659565935453370610499537728043164857800396298916132410948026391308085570960636368309306117879178753245974556315302310250472271728848176952226298724352"}, + {"0x1.17571ddf6c813p+837", 309, "999999999999999895660376658959887464073162830405580371957831265231883984761705009259228605356936508765924557863270337660249498829658628118512958332498610172941047627432585001251621720339432063578508893731092043050369229765618973200711352404729235767296"}, + {"0x1.17571ddf6c814p+837", 309, "1000000000000000099152028052998409011920202342162715294588395300751542199979533737409779075865727753926819359851621495586577336764022655397834297874715562088326669341630279279057944337344270883862880412035963403187241060084423965317738575228107571068928"}, + {"0x1.5d2ce55747a18p+840", 309, "9999999999999999363587069377675917736425707327570073564839440723358156278052707548893386994586947577981035182609405692455150664165314335743772262409420005560181719702721238568128862437403998276353831973920663150777435958293799716241167969694049028276224"}, + {"0x1.5d2ce55747a19p+840", 309, "10000000000000000991520280529984090119202023421627152945883953007515421999795337374097790758657277539268193598516214955865773367640226553978342978747155620883266693416302792790579443373442708838628804120359634031872410600844239653177385752281075710689280"}, + {"0x1.b4781ead1989ep+843", 309, "99999999999999993635870693776759177364257073275700735648394407233581562780527075488933869945869475779810351826094056924551506641653143357437722624094200055601817197027212385681288624374039982763538319739206631507774359582937997162411679696940490282762240"}, + {"0x1.b4781ead1989fp+843", 309, "100000000000000006659336382995224556426467602028157370696750505506839688554468114090569100058432115470107619153348531031836488269452441103314288354796084978186496986735864819460893271862349667261738096910718398556534156723341516657901421957636703742066688"}, + {"0x1.10cb132c2ff63p+847", 309, "999999999999999988452569694641453289891412847766833896677368465428848130901034909295879619908945316559292587569958465674654992927728624557883489163749540246356891129106733591931304833693638565628182306078113383272782784390994049606075766012189756664840192"}, + {"0x1.10cb132c2ff64p+847", 309, "1000000000000000196828020722136899354886781307806140057451066037800978143284091526922043301709947551604048864806030051391214698972517388491908540854979699007711767764445172532404979193506593517599378740822301656052939538637450361533911642183329172013711360"}, + {"0x1.54fdd7f73bf3bp+850", 309, "9999999999999998634272990781441856508941917717432502002131499220055701234712009387201814108283439755324388212283155142447191693008553661974684581490114449895439651479036702276471002178058655944454644452316004196046887318431202624493742403095061074555174912"}, + {"0x1.54fdd7f73bf3cp+850", 309, "10000000000000000301276599001405425028904865397746951288321079799032741333776462328211123562691457635682438430171727828179669341366863773446884995019955719986278664561744213800260397056562295560224215930269510378288141352402853119916429412464176397346144256"}, + {"0x1.aa3d4df50af0ap+853", 309, "99999999999999989676737124254345702129345072534953918593694153358511092545248999754036759991650433313959982558608696795936872226802156842691246641960827039136074540955782045812288811537593838676085587479067054324951381252255327235782798049688841391133687808"}, + {"0x1.aa3d4df50af0bp+853", 309, "100000000000000003012765990014054250289048653977469512883210797990327413337764623282111235626914576356824384301717278281796693413668637734468849950199557199862786645617442138002603970565622955602242159302695103782881413524028531199164294124641763973461442560"}, + {"0x1.0a6650b926d66p+857", 309, "999999999999999843423255779504622828654636399579476808778874955057845642282427503428069697375447760968142218613652642015929437520555644859802053186653349748453896990911180089361627479263821919056229587496158345417793683435460456504301996197076723582025859072"}, + {"0x1.0a6650b926d67p+857", 309, "1000000000000000056799717631659959599209893702659726317411141269166906774962677479877261307539674049653972646503389945789686576510419339128243706118473032320081290665497741564406670023712287789874734736674207136744674199783831719918405933396323484899269935104"}, + {"0x1.4cffe4e7708c0p+860", 309, "9999999999999999287738405203667575368767393208115766122317814807014700953545274940077463414411382764424743897695475635254322931165011225671787143593812227771048544607458046793796444970432082673836316471673778619485458899748089618699435710767754281089234894848"}, + {"0x1.4cffe4e7708c1p+860", 309, "10000000000000000994750100020910269533209451632757762191375945319887190014987274751670996295725193073911387320813374065444380043083920779819320367048369688344067694004150538594156785326019809640384357665098168950100503030535059726012267208361728371627187503104"}, + {"0x1.a03fde214caf0p+863", 309, "99999999999999992877384052036675753687673932081157661223178148070147009535452749400774634144113827644247438976954756352543229311650112256717871435938122277710485446074580467937964449704320826738363164716737786194854588997480896186994357107677542810892348948480"}, + {"0x1.a03fde214caf1p+863", 309, "100000000000000006533477610574617307003210399478293629775643192173126922026988747893522897194624310120140586361897943794063686207001388689898137223574581962294638641248120402340847172549022642470747494264132908839774942043776657045497009088429335535195969814528"}, + {"0x1.0427ead4cfed6p+867", 309, "999999999999999928773840520366757536876739320811576612231781480701470095354527494007746341441138276442474389769547563525432293116501122567178714359381222777104854460745804679379644497043208267383631647167377861948545889974808961869943571076775428108923489484800"}, + {"0x1.0427ead4cfed7p+867", 309, "1000000000000000147271337456973822389925322799165752109071222186349148695219103469891718550249305996056764747928638562589759603442121545498062966961564577730451305583522443629825768062558437319101780919925699824267271538715541135605986002768804111697781423341568"}, + {"0x1.4531e58a03e8bp+870", 309, "9999999999999998413748417457239315956573059294699064134960051984423986554086971036541574579178711885967582465059111638997013689862529533948250133185078807957662740116351490992011950708371166466963719380640490770210556304785160923755265983999639546733803159420928"}, + {"0x1.4531e58a03e8cp+870", 309, "10000000000000000161728392950095834780961727121532468109675577629605415353003578843613352249644053642881905330331839631511632172467492917395324154002545647584434349098564602595580939232492998880708913562707066468760361494711018313643605437535869015444666630275072"}, + {"0x1.967e5eec84e2ep+873", 309, "99999999999999987633444125558106197214507928600657449299031571134602723138702925979559301132717802373504470381136572374999373863835222106376649373485721758830170619127941133127257484131955329497127582170538059099205173427703324017329338747068854404759758535917568"}, + {"0x1.967e5eec84e2fp+873", 309, "100000000000000001617283929500958347809617271215324681096755776296054153530035788436133522496440536428819053303318396315116321724674929173953241540025456475844343490985646025955809392324929988807089135627070664687603614947110183136436054375358690154446666302750720"}, + {"0x1.fc1df6a7a61bap+876", 309, "999999999999999932269800471352470574525516656465243420181212531991832952952360709621889896782068959956303035500093019510461530081711049334072862401016156456358397678710230902586782474091451932211122035531511013345645500354660676649720249983847887046345216426508288"}, + {"0x1.fc1df6a7a61bbp+876", 309, "1000000000000000044140518902895287779286391397382581274563006173283444396083023609274483667691850832398819698877547611031397112968428705874685599733334034192471780653571870045215197739635249206690814463183771858052833032509915549602573975010166573043840478561173504"}, + {"0x1.3d92ba28c7d14p+880", 309, "9999999999999998875215130987353436926211667600983082784284950754751883757000955497608523884181562109792963701491111829020872969270239867178277674680890053619130444887655752455354163678739330224192450644706066754627704874925587274685787599733204126473471115726422016"}, + {"0x1.3d92ba28c7d15p+880", 309, "10000000000000000665146625892038512202385663455660488454393649015417666847091561892050024218738072068873230315530385293355842295457722371828081471997976097396944572485441978737408807927440086615867529487142240269942705389409665241931447200154303102433395309881065472"}, + {"0x1.8cf768b2f9c59p+883", 309, "99999999999999988752151309873534369262116676009830827842849507547518837570009554976085238841815621097929637014911118290208729692702398671782776746808900536191304448876557524553541636787393302241924506447060667546277048749255872746857875997332041264734711157264220160"}, + {"0x1.8cf768b2f9c5ap+883", 309, "100000000000000003071603269111014971471508642847250073203719093632845102290734406131617241518267700770571769927225306004888484302202258708981207125345588886413817469658847334809978790776999353375325137186550055668797052865128496484823152800700833072414104710501367808"}, + {"0x1.f03542dfb8370p+886", 309, "999999999999999973438224854160227305877518561122823750593712591987145964024444656694044404476868689015149167622996309190165824584023146941018349739309135463248122613459314107074039291811569329219648848907543004197890512187794469896370420793533163493423472892065087488"}, + {"0x1.f03542dfb8371p+886", 309, "1000000000000000087993840528060072123552654295822177713480669280669756081790243465938300425888485326396286230921509810907603861460022027238605792767602642265028226779717632589125536523728417738286853894823458109178050545114775459800092635220483497954858621317962268672"}, + {"0x1.362149cbd3226p+890", 309, "9999999999999999734382248541602273058775185611228237505937125919871459640244446566940444044768686890151491676229963091901658245840231469410183497393091354632481226134593141070740392918115693292196488489075430041978905121877944698963704207935331634934234728920650874880"}, + {"0x1.362149cbd3227p+890", 309, "10000000000000001567272099323999790141577357366417900912128432938793221524497227514848540387354553088249684689006179119380666835856213554171582585845787463460962892794726236783564348628785267837271769223730071721661465648709640537423259638766536986317197103735005773824"}, + {"0x1.83a99c3ec7eafp+893", 309, "99999999999999990012263082286432662256543169091523721434606031123027548865433341877772055077343404109122144711194766809100548098338386355056238620120129111010885594705399027856108106338478634741663761952135733701058809111452663635798820356028494943810497789949089153024"}, + {"0x1.83a99c3ec7eb0p+893", 309, "100000000000000004675381888545612798918960543133041028684136487274401643939455589461036825818030333693907688813404495028932616818466243033147431327741697981638738927986463793558699752023835231102266007829372867138519293326106230343475263802678137754874196788463928344576"}, + {"0x1.e494034e79e5bp+896", 309, "999999999999999929448868435382686895890266438998271828845121223533023678802377913944250092254807900260792535316367124530669618423639576906744771616444428851364562613616119809966264354755499540137842111275831603885509059543833769773341090453584235060232375896520569913344"}, + {"0x1.e494034e79e5cp+896", 309, "1000000000000000046753818885456127989189605431330410286841364872744016439394555894610368258180303336939076888134044950289326168184662430331474313277416979816387389279864637935586997520238352311022660078293728671385192933261062303434752638026781377548741967884639283445760"}, + {"0x1.2edc82110c2f9p+900", 309, "9999999999999999529098585253973751145501342374646995204443699533752222309208135100774737254399069875964494058799026896824009283758441475916906799486389390443691279468658234350904109878520700943148057046794110173854458342872794765056233999682236635579342942941443126198272"}, + {"0x1.2edc82110c2fap+900", 309, "10000000000000001405977792455148808638290766251961210532383597921128106478682982791432627909206996862817043703881872108962514079934807130712579466061950205884056506128634524360835840526246345277305144519080463253849400322348451303638818760853390915395496414751342542716928"}, + {"0x1.7a93a2954f3b7p+903", 309, "99999999999999991537227438137387396469434575991841521388557198562770454753131655626431591234374844785939841297824578543963083245231683449577722661712772273556182341366629763489177637489755720763166395523368395578554699469776634573397170474480057796161122485794632428945408"}, + {"0x1.7a93a2954f3b8p+903", 309, "100000000000000006552261095746787856411749967010355244012076385661777528108930437151694716472838260680760238458487340241071121614642608687943103994317258797079104154646440083568631482671560875436423095301659220218514235305581886882057848563849292034690350260273827761094656"}, + {"0x1.d9388b3aa30a5p+906", 309, "999999999999999945402341696592674884578976541955442659132610359825718694242914119314842162820675279649039207299571308833846909191138684972507989282336695782607667040225918275050684065261167516978177354790265605065466066369376850351293060923539046438669680406904714953752576"}, + {"0x1.d9388b3aa30a6p+906", 309, "1000000000000000065522610957467878564117499670103552440120763856617775281089304371516947164728382606807602384584873402410711216146426086879431039943172587970791041546464400835686314826715608754364230953016592202185142353055818868820578485638492920346903502602738277610946560"}, + {"0x1.27c35704a5e67p+910", 309, "9999999999999999213782878444176341486712719163258207029349796604673073768736360688744211624391338142173265718425108901184740478000812045911233791501695173449709921389782217629235579129702792695009666351450002856415308090320884466574359759805482716570229159677380024223137792"}, + {"0x1.27c35704a5e68p+910", 309, "10000000000000001135707186618179600359329089213627963525160252553345979158278604723977891654914655376710276554989942398414569389285410476422002602075069448460643913489597938599405671312973852493186523923071228410330128677303956762082926555244744699101970314810717026738241536"}, + {"0x1.71b42cc5cf601p+913", 309, "99999999999999995981677400789769932612359931733321583285118877944076548466448094957909476304960015890806678857380756006307062602577317320133875536163700284518967198097453618232695975663570046546450378657742479671982722077174989256760731188933351130765773907040474247261585408"}, + {"0x1.71b42cc5cf602p+913", 309, "100000000000000011357071866181796003593290892136279635251602525533459791582786047239778916549146553767102765549899423984145693892854104764220026020750694484606439134895979385994056713129738524931865239230712284103301286773039567620829265552447446991019703148107170267382415360"}, + {"0x1.ce2137f743381p+916", 309, "999999999999999929065985077113647184161737396527299728918221484261998998431805045015355882561227083155474615188770224107393363445219598313166454392463014445014728107377484646804238281703363508693674065431485187857190091380020735839470243162305319587149880588271350432374194176"}, + {"0x1.ce2137f743382p+916", 309, "1000000000000000052069140800249855752009185079750964144650090664977064943362508663270311404514719386165843308728919567930102413767433897865855658269158968045714503601765690788895124181432711335776992950015243623307738608946937362752018518070418086469181314516804918593340833792"}, + {"0x1.20d4c2fa8a030p+920", 309, "9999999999999998060628293539774386163142897133036353131863523035469330535011014267604003606077347801451059216486208802846843131230052987604772505157670608443149526129892785047133523819740156816103551808477267524066415738131041089269219682541925527051184466597377822714075545600"}, + {"0x1.20d4c2fa8a031p+920", 309, "10000000000000000028678785109953723248702060064614983783573429926910385653902272159683291957333224649616958313128598304010187936385481780447799767184805866054345934040104083320587698215409722049436653961817402491275192019201707119869992081071729797163687409453914913289541779456"}, + {"0x1.6909f3b92c83dp+923", 309, "99999999999999996350686867959178558315902274782992576532314485486221746301240205812674342870820492799837784938001204037775189753543960218791943147793788145321066524580618236658968633362758090027700335311493754978334367629875739137498376013657689431411868208826074951744485326848"}, + {"0x1.6909f3b92c83ep+923", 309, "100000000000000012095090800520613255000375578235621621745993740617750187252370268949308649680867507585164977711140320047081948194787390561536161244010870206210637787862308622846602028528114611894365152538214834716004577878441067382304555201896123592311891751678371676348215197696"}, + {"0x1.c34c70a777a4cp+926", 309, "999999999999999932018060814468916189790076140924667674895786344599160581110141931853474815088110898427723463833733808359138380652952741502430995285503717331431522719242801594214419543296867856543673718661495390308003255801626734885371401760100025992318635002556156068237393526784"}, + {"0x1.c34c70a777a4dp+926", 309, "1000000000000000057973292274960393763265862568545700036605220385651388108719182436946549269568487016710341006018846736433592448182900184244384740055240373818548092825496324683715486704619720031476992256475264028209364937790149360843820835266007499279518823345374529865067232493568"}, + {"0x1.1a0fc668aac6fp+930", 309, "9999999999999998312538756460757341310094469988278417855282391117573785590229095277790152515038100038016294300856434658995751266289947873088679994697143921417382666342399831226135658142385861165970188884104804799869139102108086341186118549553740473625584843283014570307735223533568"}, + {"0x1.1a0fc668aac70p+930", 309, "10000000000000000327822459828620982485707052830214935642633335774409426031973743359279343786724117930538174975818241508187016346769106956959939911012930425211247788042456200658152732723551495964903285489125103006290926013924448356521309485648260046220787856768108551057012647002112"}, + {"0x1.6093b802d578bp+933", 309, "99999999999999987155954971343300695452169865566657214127525800489409136785780248940879907693753036165206704358487960288340042823857796898629319779603012221761556906824111051125390730586189881257568082051088644411534964844713587442531567367726443881446254459800333664575907082272768"}, + {"0x1.6093b802d578cp+933", 309, "100000000000000003278224598286209824857070528302149356426333357744094260319737433592793437867241179305381749758182415081870163467691069569599399110129304252112477880424562006581527327235514959649032854891251030062909260139244483565213094856482600462207878567681085510570126470021120"}, + {"0x1.b8b8a6038ad6ep+936", 309, "999999999999999903804088967318825213331499981137556425872873119403461614925716858712626137284506647932417134384268512470460669526244514328233356457082706278317411015442012422166180499160548969358610366191211215418098239036197666670678728654776751975985792813764840337747509598224384"}, + {"0x1.b8b8a6038ad6fp+936", 309, "1000000000000000032782245982862098248570705283021493564263333577440942603197374335927934378672411793053817497581824150818701634676910695695993991101293042521124778804245620065815273272355149596490328548912510300629092601392444835652130948564826004622078785676810855105701264700211200"}, + {"0x1.137367c236c65p+940", 309, "9999999999999999553953517735361344274271821018911312812290573026184540102343798495987494338396687059809772796632907678097570555865109868753376103147668407754403581309634554796258176084383892202112976392797308495024959839786965342632596166187964530344229899589832462449290116390191104"}, + {"0x1.137367c236c66p+940", 309, "10000000000000001617604029984053712838099105849054307026537940354784235914690318131432426200603169381752178607793797891669425998275768770637546257455033787639321465930492277094643660455497502236220467316338093858400869637486920046335831684748752572681717785398568698736550198021980160"}, + {"0x1.585041b2c477ep+943", 309, "99999999999999991412234152856228705615063640528827139694410995604646009398744945688985079659553905954212916344007296353831994673829780883765420722861953317774200043854630103365810792101611701952914782080891514223497778802469744018919490624758069218767323224280852151918381000638332928"}, + {"0x1.585041b2c477fp+943", 309, "100000000000000007921438250845767654125681919169971093408389934233443575897517102772544534557205764529752162833294418062406838213115052098838781957320876356853543120821491881752894667070520582225774709469217797130505057184069381648545374773244373557467226310750742042216461653692645376"}, + {"0x1.ae64521f7595ep+946", 309, "999999999999999980159157920520442850193109519852847211800025710561650359982538085224088616186146493844286149397221450372619320895438893697947652166455225334059372746413748147206443420891752540620587530362220273863006901551095990707698442841525909542472844588688081080376132618600579072"}, + {"0x1.ae64521f7595fp+946", 309, "1000000000000000112232790704436754438278055748981998841511857219592030891972715341892564255367361362448600121311518424041218069209721063418534542042126609646694117362148642374303114420643023582803466949468830537119065128603893091744705516029416344252072069280447200202760777843035078656"}, + {"0x1.0cfeb353a97dap+950", 309, "9999999999999998216707985798208689444911740448978652561458278997251937215943253772219178491686886515191093831000650819703008229183002900332433843156495641588976792075318750746904382211902272900011322274342879579557370290877394694632899550160573878909537749585771381335145583492791795712"}, + {"0x1.0cfeb353a97dbp+950", 309, "10000000000000000329886110340869674854270880115045078636847583141738025727786089878914788718586324412860117381629402398400588202211517615861824081167237790591132705927077058380451118207922609574937392980048643791654301923722148311225012721166820834263125344653917287293299907083743789056"}, + {"0x1.503e602893dd1p+953", 309, "99999999999999990619792356152730836086553963154052229916140006550463726206803882148974225824466616742587032512521514511820402183944087865441899383607925011898391576160220738003230766103104075699817505566251852643961429440152961412697448185630726610509727876130297437184073129291725930496"}, + {"0x1.503e602893dd2p+953", 309, "100000000000000007525217352494018719361427080482583638519254439706352434301546571002539107639662119923939220917551527141401041968172205589677021287693862203915638886974287199071604654071266769099226071211897966340736882502910990345434353553680702253338428636675464684849307718019341877248"}, + {"0x1.a44df832b8d45p+956", 309, "999999999999999872387073568844732594315793396883459481955171199192859845878553443782612494614275161063165948315155119859042742270984643205948750027907375734949421139974074457895559885094715370199357924371226299046063388276013556261500671120207314819439877240212639876510262115462027411456"}, + {"0x1.a44df832b8d46p+956", 309, "1000000000000000007630473539575035660514778335511710750780086664439969510636494954611131549135839186513983455555395220895687860544809584999829725260594873271087399626486606146442550988840016917394626449536395208620267012778077787723395914064607119962069483324573977857832138825282954985472"}, + {"0x1.06b0bb1fb384bp+960", 309, "9999999999999998453383935746986719810759964091578092281901881061434379129269651416169086837099623559730024468671070996517137186162196548471725549813698762277218254426715681201861616643456550607603042193381925171312226633756007099691216225313273537909139560233403722802458867734978418966528"}, + {"0x1.06b0bb1fb384cp+960", 309, "10000000000000000617278335278671568869943723109630112583100528505388133765396715589425391709444647966943104584514912613103459078543395617173821153536698722855425910210916188218613474303381375362727338596024627724499484625789034803081540112423670420191213257583185130503608895092113260150784"}, + {"0x1.485ce9e7a065ep+963", 309, "99999999999999988861628156533236896225967158951884963421416105502251300564950642508203478115686284411726404918398393198344015646384363622121446705582987543928597855835557826052119881754415155586279014739104656819496782321626126403692810027353529143655542997033600043426888732064053872033792"}, + {"0x1.485ce9e7a065fp+963", 309, "100000000000000006172783352786715688699437231096301125831005285053881337653967155894253917094446479669431045845149126131034590785433956171738211535366987228554259102109161882186134743033813753627273385960246277244994846257890348030815401124236704201912132575831851305036088950921132601507840"}, + {"0x1.9a742461887f6p+966", 309, "999999999999999957860902350346284132153551878096514283852517773229033154005572478626236537071903625148082612890986863714202457020042006419681526374965874177788623543449994485057258262661745948026767632275613049896960078961318150545418464661067991669581788285529005480705688196068853638234112"}, + {"0x1.9a742461887f7p+966", 309, "1000000000000000096350143920374114471941312455251843583129231209642073450717704585714640048901985187209719740304992727175727058132438746816615645013237871654793913513638826934129377152896934732354722602044746013300944590451431923562399193436133392135634504915915015573579289946925483474026496"}, + {"0x1.008896bcf54f9p+970", 309, "9999999999999997916738124663128877244082391855101191247204616495333847979510139501201523228758057506741180599941798275603729356851659179433605840090394772053822755792233955461707155943795194068332216685526534938121786651731816229250415901309895111103185283290657933692573660950408978352832512"}, + {"0x1.008896bcf54fap+970", 309, "10000000000000000132565989783574162680686561089586460035632031477942492726904253214615979418039362499727374638565892090988122974650007025784551738302746731685907395315255274646861058187558214617579496201832662352585538835573636597522107561710941518560028749376834095178551288964115055725510656"}, + {"0x1.40aabc6c32a38p+973", 309, "99999999999999992462348437353960485060448933957923525202610654848990348279466077292501969423268405025328970231162545648343655275306678872441733790178059478330735395060467469727994972900530063978805843953102113868000379620369084502134308975505229555772913629423636305841602377586326247764393984"}, + {"0x1.40aabc6c32a39p+973", 309, "100000000000000010188971358317522768553282287833805675510029974709859506258618986999817618937518844969218522540155296171418804217693461643249300975876875155387412511244638023209226190850634228372784080083551133183710397091103647448307842258713600815427661358113045597729423401695974866745819136"}, + {"0x1.90d56b873f4c6p+976", 309, "999999999999999924623484373539604850604489339579235252026106548489903482794660772925019694232684050253289702311625456483436552753066788724417337901780594783307353950604674697279949729005300639788058439531021138680003796203690845021343089755052295557729136294236363058416023775863262477643939840"}, + {"0x1.90d56b873f4c7p+976", 309, "1000000000000000066436467741248103118547156170586292454485461107376856746627884050583544890346687569804406120783567460668037744292161050890877875387371120199760770880078039125129799472606133954939884328574613293205683935969567348590731356020719265634967118123751637393518591968740451429495341056"}, + {"0x1.f50ac6690f1f8p+979", 309, "9999999999999999813486777206230041577815560719820581330098483720446847883279500839884297726782854580737362697004022581572770293687044935910015528960168049498887207223940204684198896264456339658487887951484580004902758521100414464490983962613190835886243290260424727924570510530141380583845003264"}, + {"0x1.f50ac6690f1f9p+979", 309, "10000000000000000947990644147898027721356895367877038949773320191542473993945287061152499295694882737146294044779558615049579825999799033241699828844892252830514542659727120106997694213263006179702495063833317241108199639227426493046090092738526596504147144896546922605391056073158892198656212992"}, + {"0x1.3926bc01a973bp+983", 309, "99999999999999998134867772062300415778155607198205813300984837204468478832795008398842977267828545807373626970040225815727702936870449359100155289601680494988872072239402046841988962644563396584878879514845800049027585211004144644909839626131908358862432902604247279245705105301413805838450032640"}, + {"0x1.3926bc01a973cp+983", 309, "100000000000000016286929643128988194074816961567109135215782220741998496603447587939134202370420996309916528534448802351356655453874514916407104087757267748294909439211992693606769729825470060924312593312425595828314643101036337101791537708137280528748894576782202394138833833989693991675429388288"}, + {"0x1.87706b0213d09p+986", 309, "999999999999999872436306494222877488001587945768638201521064070819504681704034606746682422062730755058478860313950798943503314266680100247159860107083281430052496520558476587831205023360193979812186512362979225814553504769848291707808207769286850569305558980974742103098278680884456943362624192512"}, + {"0x1.87706b0213d0ap+986", 309, "1000000000000000017652801462756379714374878780719864776839443139119744823869255243069012222883470359078822072829219411228534934402712624705615450492327979456500795456339201761949451160807447294527656222743617592048849967890105831362861792425329827928397252374398383022243308510390698430058459037696"}, + {"0x1.e94c85c298c4cp+989", 309, "9999999999999999595662034753429788238255624467393741467120915117996487670031669885400803025551745174706847878231119663145222863482996149222332143382301002459214758820269116923021527058285459686414683385913622455551313826420028155008403585629126369847605750170289266545852965785882018353801250996224"}, + {"0x1.e94c85c298c4dp+989", 309, "10000000000000000757393994501697806049241951147003554069667947664398408807353434975979441432117662006869593578353268561425475824571256344889976866464258586670801150306514918315967496157863486204138441068958729385425685531382088472248832262877470188720339297317678393899013204421931950247367929757696"}, + {"0x1.31cfd3999f7afp+993", 309, "99999999999999986662764669548153739894665631237058913850832890808749507601742578129378923002990117089766513181334005445210204946123879926882163649167349350899456456312724758086647517786230384722356772394775369116518164624503799012160606438304513147494189124523779646633247748770420728389479079870464"}, + {"0x1.31cfd3999f7b0p+993", 309, "100000000000000005250476025520442024870446858110815915491585411551180245798890819578637137508044786404370444383288387817694252323536043057564479218478670698284838720092657580373783023379478809005936895323497079994508111903896764088007465274278014249457925878882005684283811566947219638686545940054016"}, + {"0x1.7e43c8800759bp+996", 309, "999999999999999903803069407426113968898218766118103141789833949572356552411722264192305659040010509526872994217248819197070144216063125530186267630296136203765329090687113225440746189048800695790727969805197112921161540803823920273299782054992133678869364753954248541633605124057805104488924519071744"}, + {"0x1.7e43c8800759cp+996", 309, "1000000000000000052504760255204420248704468581108159154915854115511802457988908195786371375080447864043704443832883878176942523235360430575644792184786706982848387200926575803737830233794788090059368953234970799945081119038967640880074652742780142494579258788820056842838115669472196386865459400540160"}, + {"0x1.ddd4baa009302p+999", 309, "9999999999999999335434075769817752248594687291161143444150379827602457335271594505111188022480979804302392841403758309930446200199225865392779725411942503595819407127350057411001629979979981746444561664911518503259454564508526643946547561925497354420113435609274102018745072331406833609642314953654272"}, + {"0x1.ddd4baa009303p+999", 309, "10000000000000000525047602552044202487044685811081591549158541155118024579889081957863713750804478640437044438328838781769425232353604305756447921847867069828483872009265758037378302337947880900593689532349707999450811190389676408800746527427801424945792587888200568428381156694721963868654594005401600"}, + {"0x1.2aa4f4a405be1p+1003", 309, "99999999999999988595886650569271721532146878831929642021471152965962304374245995240101777311515802698485322026337261211948545873374744892473124468375726771027536211745837771604509610367928220847849105179362427047829119141560667380048679757245757262098417746977035154548906385860807815060374033329553408"}, + {"0x1.2aa4f4a405be2p+1003", 309, "100000000000000007629703079084894925347346855150656811701601734206211380288125794484142188964691784076639747577138548761372210387844799938291815611350519830750167649856488981626536368095414607314235151058373458986890825155659063617715863205282622390509284183439858617103083735673849899204570498157510656"}, + {"0x1.754e31cd072d9p+1006", 309, "999999999999999847891233648661470807691068835681842080854450367179124891914700353912936949808806064228544369161770037020638129704807338833093862397807681590830099241237075296001042588224309435545718960035602206600167779387409881325152430676383842364162444596844704620380709158981993982315347403639619584"}, + {"0x1.754e31cd072dap+1006", 309, "1000000000000000000161765076786456438212668646231659438295495017101117499225738747865260243034213915253779773568180337416027445820567779199643391541606026068611150746122284976177256650044200527276807327067690462112661427500197051226489898260678763391449376088547292320814127957486330655468919122263277568"}, + {"0x1.d2a1be4048f90p+1009", 309, "9999999999999999392535525055364621860040287220117324953190771571323204563013233902843309257440507748436856118056162172578717193742636030530235798840866882774987301441682011041067710253162440905843719802548551599076639682550821832659549112269607949805346034918662572406407604380845959862074904348138143744"}, + {"0x1.d2a1be4048f91p+1009", 309, "10000000000000000610699776480364506904213085704515863812719128770699145421501541054461895603243770556638739353307444575741831722668719553462632031991253638597235713480763688482477422747721569639692426738805257643176588867453119191870248852943967318023641486852283274009874954768880653247303478097127407616"}, + {"0x1.23a516e82d9bap+1013", 309, "99999999999999993925355250553646218600402872201173249531907715713232045630132339028433092574405077484368561180561621725787171937426360305302357988408668827749873014416820110410677102531624409058437198025485515990766396825508218326595491122696079498053460349186625724064076043808459598620749043481381437440"}, + {"0x1.23a516e82d9bbp+1013", 309, "100000000000000013415983273353644379307167647951549871284361430903247099365945253454330474107257282415598692944582140176397004400243696672220697718814856920905847607042126949473232502444570468800016509005592812696365583783944976073966686973485829389546187580124556949719553650017014692784406223465209659392"}, + {"0x1.6c8e5ca239028p+1016", 309, "999999999999999861291040414336469543176969619010226008309262296372260241358071732580741399612641955118765084749534143455432389522994257585350220962461935904874831773666973747856549425664459851618054736334425973085267220421335152276470127823801795414563694568114532338018850013250375609552861714878501486592"}, + {"0x1.6c8e5ca239029p+1016", 309, "1000000000000000017216064596736454828831087825013238982328892017892380671244575047987920451875459594568606138861698291060311049225532948520696938805711440650122628514669428460356992624968028329550689224175284346730060716088829214255439694630119794546505512415617982143262670862918816362862119154749127262208"}, + {"0x1.c7b1f3cac7433p+1019", 309, "9999999999999999860310597602564577717002641838126363875249660735883565852672743849064846414228960666786379280392654615393353172850252103336275952370615397010730691664689375178569039851073146339641623266071126720011020169553304018596457812688561947201171488461172921822139066929851282122002676667750021070848"}, + {"0x1.c7b1f3cac7434p+1019", 309, "10000000000000001107710791061764460002235587486150467667406698508044529291764770372322278832331501782385107713289967796232382450470561630819049695116611434972713065592709012878572585445501694163102699168797993709169368134893256514428214347139105940256706031241200520264089633727198808148476736186715027275776"}, + {"0x1.1ccf385ebc89fp+1023", 309, "99999999999999981139503267596847425176765179308926185662298078548582170379439067165044410288854031049481594743364161622187121841818187648603927125262209438639553681654618823985640760188731793867961170022535129351893330180773705244319986644578003569234231285691342840034082734135647456849389933411990123839488"}, + {"0x1.1ccf385ebc8a0p+1023", 309, "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336"}, + {"0x1.fffffffffffffp+1023", 309, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368"}, +] of _ diff --git a/spec/std/sprintf_spec.cr b/spec/std/sprintf_spec.cr index f37384d3a70c..d2da4369827b 100644 --- a/spec/std/sprintf_spec.cr +++ b/spec/std/sprintf_spec.cr @@ -583,11 +583,212 @@ describe "::sprintf" do end context "general format" do - pending_win32 "works" do - assert_sprintf "%g", 123, "123" - assert_sprintf "%G", 12345678.45, "1.23457E+07" - assert_sprintf "%100.50g", 123.45, " 123.4500000000000028421709430404007434844970703125" - assert_sprintf "%#.12g", 12345.0, "12345.0000000" + it "works" do + assert_sprintf "%g", 123.45, "123.45" + assert_sprintf "%G", 123.45, "123.45" + + assert_sprintf "%g", 1.2345e-5, "1.2345e-5" + assert_sprintf "%G", 1.2345e-5, "1.2345E-5" + + assert_sprintf "%g", 1.2345e+25, "1.2345e+25" + assert_sprintf "%G", 1.2345e+25, "1.2345E+25" + + assert_sprintf "%g", Float64::MAX, "1.79769e+308" + assert_sprintf "%g", Float64::MIN_POSITIVE, "2.22507e-308" + assert_sprintf "%g", Float64::MIN_SUBNORMAL, "4.94066e-324" + assert_sprintf "%g", 0.0, "0" + assert_sprintf "%g", -0.0, "-0" + assert_sprintf "%g", -Float64::MIN_SUBNORMAL, "-4.94066e-324" + assert_sprintf "%g", -Float64::MIN_POSITIVE, "-2.22507e-308" + assert_sprintf "%g", Float64::MIN, "-1.79769e+308" + end + + context "width specifier" do + it "sets the minimum length of the string" do + assert_sprintf "%10g", 123.45, " 123.45" + assert_sprintf "%10g", -123.45, " -123.45" + assert_sprintf "%+10g", 123.45, " +123.45" + + assert_sprintf "%7g", 123.45, " 123.45" + assert_sprintf "%7g", -123.45, "-123.45" + assert_sprintf "%+7g", 123.45, "+123.45" + + assert_sprintf "%6g", 123.45, "123.45" + assert_sprintf "%6g", -123.45, "-123.45" + assert_sprintf "%+6g", 123.45, "+123.45" + + assert_sprintf "%2g", 123.45, "123.45" + assert_sprintf "%2g", -123.45, "-123.45" + assert_sprintf "%+2g", 123.45, "+123.45" + end + + it "left-justifies on negative width" do + assert_sprintf "%*g", [-10, 123.45], "123.45 " + end + end + + context "precision specifier" do + it "sets the precision of the value" do + assert_sprintf "%.0g", 123.45, "1e+2" + assert_sprintf "%.1g", 123.45, "1e+2" + assert_sprintf "%.2g", 123.45, "1.2e+2" + assert_sprintf "%.3g", 123.45, "123" + assert_sprintf "%.4g", 123.45, "123.5" + assert_sprintf "%.5g", 123.45, "123.45" + assert_sprintf "%.6g", 123.45, "123.45" + assert_sprintf "%.7g", 123.45, "123.45" + assert_sprintf "%.8g", 123.45, "123.45" + + assert_sprintf "%.1000g", 123.45, "123.4500000000000028421709430404007434844970703125" + + assert_sprintf "%.0g", 1.23e-45, "1e-45" + assert_sprintf "%.1g", 1.23e-45, "1e-45" + assert_sprintf "%.2g", 1.23e-45, "1.2e-45" + assert_sprintf "%.3g", 1.23e-45, "1.23e-45" + assert_sprintf "%.4g", 1.23e-45, "1.23e-45" + assert_sprintf "%.5g", 1.23e-45, "1.23e-45" + assert_sprintf "%.6g", 1.23e-45, "1.23e-45" + + assert_sprintf "%.1000g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125e-5" + end + + it "can be used with width" do + assert_sprintf "%10.1g", 123.45, " 1e+2" + assert_sprintf "%10.2g", 123.45, " 1.2e+2" + assert_sprintf "%10.3g", 123.45, " 123" + assert_sprintf "%10.4g", 123.45, " 123.5" + assert_sprintf "%10.5g", 123.45, " 123.45" + assert_sprintf "%10.1g", -123.45, " -1e+2" + assert_sprintf "%10.2g", -123.45, " -1.2e+2" + assert_sprintf "%10.3g", -123.45, " -123" + assert_sprintf "%10.4g", -123.45, " -123.5" + assert_sprintf "%10.5g", -123.45, " -123.45" + assert_sprintf "%10.5g", 0, " 0" + + assert_sprintf "%-10.1g", 123.45, "1e+2 " + assert_sprintf "%-10.2g", 123.45, "1.2e+2 " + assert_sprintf "%-10.3g", 123.45, "123 " + assert_sprintf "%-10.4g", 123.45, "123.5 " + assert_sprintf "%-10.5g", 123.45, "123.45 " + assert_sprintf "%-10.1g", -123.45, "-1e+2 " + assert_sprintf "%-10.2g", -123.45, "-1.2e+2 " + assert_sprintf "%-10.3g", -123.45, "-123 " + assert_sprintf "%-10.4g", -123.45, "-123.5 " + assert_sprintf "%-10.5g", -123.45, "-123.45 " + assert_sprintf "%-10.5g", 0, "0 " + + assert_sprintf "%3.1g", 123.45, "1e+2" + assert_sprintf "%3.2g", 123.45, "1.2e+2" + assert_sprintf "%3.3g", 123.45, "123" + assert_sprintf "%3.4g", 123.45, "123.5" + assert_sprintf "%3.5g", 123.45, "123.45" + assert_sprintf "%3.1g", -123.45, "-1e+2" + assert_sprintf "%3.2g", -123.45, "-1.2e+2" + assert_sprintf "%3.3g", -123.45, "-123" + assert_sprintf "%3.4g", -123.45, "-123.5" + assert_sprintf "%3.5g", -123.45, "-123.45" + + assert_sprintf "%1000.800g", 123.45, "#{" " * 950}123.4500000000000028421709430404007434844970703125" + end + + it "is ignored if precision argument is negative" do + assert_sprintf "%.*g", [-2, 123.45], "123.45" + end + end + + context "sharp flag" do + it "prints decimal point and trailing zeros" do + assert_sprintf "%#.0g", 12345, "1.e+4" + assert_sprintf "%#.6g", 12345, "12345.0" + assert_sprintf "%#.10g", 12345, "12345.00000" + assert_sprintf "%#.100g", 12345, "12345.#{"0" * 95}" + assert_sprintf "%#.1000g", 12345, "12345.#{"0" * 995}" + + assert_sprintf "%#.0g", 1e-5, "1.e-5" + assert_sprintf "%#.6g", 1e-5, "1.00000e-5" + assert_sprintf "%#.10g", 1e-5, "1.000000000e-5" + assert_sprintf "%#.100g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125#{"0" * 35}e-5" + assert_sprintf "%#.1000g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125#{"0" * 935}e-5" + + assert_sprintf "%#15.0g", 12345, " 1.e+4" + assert_sprintf "%#15.6g", 12345, " 12345.0" + assert_sprintf "%#15.10g", 12345, " 12345.00000" + end + end + + context "plus flag" do + it "writes a plus sign for positive values" do + assert_sprintf "%+g", 123.45, "+123.45" + assert_sprintf "%+g", -123.45, "-123.45" + assert_sprintf "%+g", 0.0, "+0" + end + + it "writes plus sign after left space-padding" do + assert_sprintf "%+10g", 123.45, " +123.45" + assert_sprintf "%+10g", -123.45, " -123.45" + assert_sprintf "%+10g", 0.0, " +0" + end + + it "writes plus sign before left zero-padding" do + assert_sprintf "%+010g", 123.45, "+000123.45" + assert_sprintf "%+010g", -123.45, "-000123.45" + assert_sprintf "%+010g", 0.0, "+000000000" + end + end + + context "space flag" do + it "writes a space for positive values" do + assert_sprintf "% g", 123.45, " 123.45" + assert_sprintf "% g", -123.45, "-123.45" + assert_sprintf "% g", 0.0, " 0" + end + + it "writes space before left space-padding" do + assert_sprintf "% 10g", 123.45, " 123.45" + assert_sprintf "% 10g", -123.45, " -123.45" + assert_sprintf "% 10g", 0.0, " 0" + + assert_sprintf "% 010g", 123.45, " 000123.45" + assert_sprintf "% 010g", -123.45, "-000123.45" + assert_sprintf "% 010g", 0.0, " 000000000" + end + + it "is ignored if plus flag is also specified" do + assert_sprintf "% +g", 123.45, "+123.45" + assert_sprintf "%+ g", -123.45, "-123.45" + end + end + + context "zero flag" do + it "left-pads the result with zeros" do + assert_sprintf "%010g", 123.45, "0000123.45" + assert_sprintf "%010g", -123.45, "-000123.45" + assert_sprintf "%010g", 0.0, "0000000000" + end + + it "is ignored if string is left-justified" do + assert_sprintf "%-010g", 123.45, "123.45 " + assert_sprintf "%-010g", -123.45, "-123.45 " + assert_sprintf "%-010g", 0.0, "0 " + end + + it "can be used with precision" do + assert_sprintf "%010.2g", 123.45, "00001.2e+2" + assert_sprintf "%010.2g", -123.45, "-0001.2e+2" + assert_sprintf "%010.2g", 0.0, "0000000000" + end + end + + context "minus flag" do + it "left-justifies the string" do + assert_sprintf "%-10g", 123.45, "123.45 " + assert_sprintf "%-10g", -123.45, "-123.45 " + assert_sprintf "%-10g", 0.0, "0 " + + assert_sprintf "%- 10g", 123.45, " 123.45 " + assert_sprintf "%- 10g", -123.45, "-123.45 " + assert_sprintf "%- 10g", 0.0, " 0 " + end end end diff --git a/src/float/printer/ryu_printf.cr b/src/float/printer/ryu_printf.cr index d44a2fa8739b..005e575c3806 100644 --- a/src/float/printer/ryu_printf.cr +++ b/src/float/printer/ryu_printf.cr @@ -613,6 +613,76 @@ module Float::Printer::RyuPrintf index end + private MAX_FIXED_PRECISION = 66_u32 + private MAX_SCI_PRECISION = 766_u32 + + # Source port of Microsoft STL's `std::to_chars` based on: + # + # * https://github.com/ulfjack/ryu/pull/185/files + # * https://github.com/microsoft/STL/blob/a8888806c6960f1687590ffd4244794c753aa819/stl/inc/charconv#L2324 + # * https://github.com/llvm/llvm-project/blob/701f64790520790f75b1f948a752472d421ddaa3/libcxx/src/include/to_chars_floating_point.h#L836 + def self.d2gen_buffered_n(d : Float64, precision : UInt32, result : UInt8*, alternative : Bool = false) + if d == 0 + result[0] = '0'.ord.to_u8! + return {1, 0} + end + + precision = precision.clamp(1_u32, 1000000_u32) + if precision <= MAX_SPECIAL_P + table_begin = SPECIAL_X.to_unsafe + (precision &- 1) * (precision &+ 10) // 2 + table_length = precision.to_i32! &+ 5 + else + table_begin = ORDINARY_X.to_unsafe + table_length = {precision, MAX_ORDINARY_P}.min.to_i32! &+ 5 + end + + bits = d.unsafe_as(UInt64) + index = 0 + while index < table_length + break if bits <= table_begin[index] + index &+= 1 + end + + sci_exp_x = index &- 5 + use_fixed_notation = precision > sci_exp_x && sci_exp_x >= -4 + + significand_last = exponent_first = exponent_last = Pointer(UInt8).null + + # Write into the local buffer. + if use_fixed_notation + effective_precision = precision &- 1 &- sci_exp_x + max_precision = MAX_FIXED_PRECISION + len = d2fixed_buffered_n(d, {effective_precision, max_precision}.min, result) + significand_last = result + len + else + effective_precision = precision &- 1 + max_precision = MAX_SCI_PRECISION + len = d2exp_buffered_n(d, {effective_precision, max_precision}.min, result) + exponent_first = result + Slice.new(result, len).fast_index('e'.ord.to_u8!, 0).not_nil! + significand_last = exponent_first + exponent_last = result + len + end + + # If we printed a decimal point followed by digits, perform zero-trimming. + if effective_precision > 0 && !alternative + while significand_last[-1] === '0' # will stop at '.' or a nonzero digit + significand_last -= 1 + end + + if significand_last[-1] === '.' + significand_last -= 1 + end + end + + # Copy the exponent to the output range. + unless use_fixed_notation + exponent_first.move_to(significand_last, exponent_last - exponent_first) + end + + extra_zeros = effective_precision > max_precision ? effective_precision &- max_precision : 0_u32 + {(significand_last - result + (exponent_last - exponent_first)).to_i32!, extra_zeros.to_i32!} + end + def self.d2fixed(d : Float64, precision : Int) String.new(2000) do |buffer| len = d2fixed_buffered_n(d, precision.to_u32, buffer) @@ -626,4 +696,11 @@ module Float::Printer::RyuPrintf {len, len} end end + + def self.d2gen(d : Float64, precision : Int) + String.new(773) do |buffer| + len, _ = d2gen_buffered_n(d, precision.to_u32, buffer) + {len, len} + end + end end diff --git a/src/float/printer/ryu_printf_table.cr b/src/float/printer/ryu_printf_table.cr index dc733c492991..6e16b939f16c 100644 --- a/src/float/printer/ryu_printf_table.cr +++ b/src/float/printer/ryu_printf_table.cr @@ -16,6 +16,533 @@ module Float::Printer::RyuPrintf array << values end + # Special constants for `%g` formatting; for details refer to + # https://github.com/ulfjack/ryu/pull/185/files or + # https://github.com/microsoft/STL/blob/a8888806c6960f1687590ffd4244794c753aa819/stl/inc/xcharconv_tables.h + + private MAX_SPECIAL_P = 15_u32 + + private SPECIAL_X = begin + data = Array(UInt64).new(195) + put(data, 0x3F18E757928E0C9Du64) + put(data, 0x3F4F212D77318FC5u64) + put(data, 0x3F8374BC6A7EF9DBu64) + put(data, 0x3FB851EB851EB851u64) + put(data, 0x3FEE666666666666u64) + put(data, 0x4022FFFFFFFFFFFFu64) + put(data, 0x3F1A1554FBDAD751u64) + put(data, 0x3F504D551D68C692u64) + put(data, 0x3F8460AA64C2F837u64) + put(data, 0x3FB978D4FDF3B645u64) + put(data, 0x3FEFD70A3D70A3D7u64) + put(data, 0x4023E66666666666u64) + put(data, 0x4058DFFFFFFFFFFFu64) + put(data, 0x3F1A3387ECC8EB96u64) + put(data, 0x3F506034F3FD933Eu64) + put(data, 0x3F84784230FCF80Du64) + put(data, 0x3FB99652BD3C3611u64) + put(data, 0x3FEFFBE76C8B4395u64) + put(data, 0x4023FD70A3D70A3Du64) + put(data, 0x4058FCCCCCCCCCCCu64) + put(data, 0x408F3BFFFFFFFFFFu64) + put(data, 0x3F1A368D04E0BA6Au64) + put(data, 0x3F506218230C7482u64) + put(data, 0x3F847A9E2BCF91A3u64) + put(data, 0x3FB99945B6C3760Bu64) + put(data, 0x3FEFFF972474538Eu64) + put(data, 0x4023FFBE76C8B439u64) + put(data, 0x4058FFAE147AE147u64) + put(data, 0x408F3F9999999999u64) + put(data, 0x40C387BFFFFFFFFFu64) + put(data, 0x3F1A36DA54164F19u64) + put(data, 0x3F506248748DF16Fu64) + put(data, 0x3F847ADA91B16DCBu64) + put(data, 0x3FB99991361DC93Eu64) + put(data, 0x3FEFFFF583A53B8Eu64) + put(data, 0x4023FFF972474538u64) + put(data, 0x4058FFF7CED91687u64) + put(data, 0x408F3FF5C28F5C28u64) + put(data, 0x40C387F999999999u64) + put(data, 0x40F869F7FFFFFFFFu64) + put(data, 0x3F1A36E20F35445Du64) + put(data, 0x3F50624D49814ABAu64) + put(data, 0x3F847AE09BE19D69u64) + put(data, 0x3FB99998C2DA04C3u64) + put(data, 0x3FEFFFFEF39085F4u64) + put(data, 0x4023FFFF583A53B8u64) + put(data, 0x4058FFFF2E48E8A7u64) + put(data, 0x408F3FFEF9DB22D0u64) + put(data, 0x40C387FF5C28F5C2u64) + put(data, 0x40F869FF33333333u64) + put(data, 0x412E847EFFFFFFFFu64) + put(data, 0x3F1A36E2D51EC34Bu64) + put(data, 0x3F50624DC5333A0Eu64) + put(data, 0x3F847AE136800892u64) + put(data, 0x3FB9999984200AB7u64) + put(data, 0x3FEFFFFFE5280D65u64) + put(data, 0x4023FFFFEF39085Fu64) + put(data, 0x4058FFFFEB074A77u64) + put(data, 0x408F3FFFE5C91D14u64) + put(data, 0x40C387FFEF9DB22Du64) + put(data, 0x40F869FFEB851EB8u64) + put(data, 0x412E847FE6666666u64) + put(data, 0x416312CFEFFFFFFFu64) + put(data, 0x3F1A36E2E8E94FFCu64) + put(data, 0x3F50624DD191D1FDu64) + put(data, 0x3F847AE145F6467Du64) + put(data, 0x3FB999999773D81Cu64) + put(data, 0x3FEFFFFFFD50CE23u64) + put(data, 0x4023FFFFFE5280D6u64) + put(data, 0x4058FFFFFDE7210Bu64) + put(data, 0x408F3FFFFD60E94Eu64) + put(data, 0x40C387FFFE5C91D1u64) + put(data, 0x40F869FFFDF3B645u64) + put(data, 0x412E847FFD70A3D7u64) + put(data, 0x416312CFFE666666u64) + put(data, 0x4197D783FDFFFFFFu64) + put(data, 0x3F1A36E2EAE3F7A7u64) + put(data, 0x3F50624DD2CE7AC8u64) + put(data, 0x3F847AE14782197Bu64) + put(data, 0x3FB9999999629FD9u64) + put(data, 0x3FEFFFFFFFBB47D0u64) + put(data, 0x4023FFFFFFD50CE2u64) + put(data, 0x4058FFFFFFCA501Au64) + put(data, 0x408F3FFFFFBCE421u64) + put(data, 0x40C387FFFFD60E94u64) + put(data, 0x40F869FFFFCB923Au64) + put(data, 0x412E847FFFBE76C8u64) + put(data, 0x416312CFFFD70A3Du64) + put(data, 0x4197D783FFCCCCCCu64) + put(data, 0x41CDCD64FFBFFFFFu64) + put(data, 0x3F1A36E2EB16A205u64) + put(data, 0x3F50624DD2EE2543u64) + put(data, 0x3F847AE147A9AE94u64) + put(data, 0x3FB9999999941A39u64) + put(data, 0x3FEFFFFFFFF920C8u64) + put(data, 0x4023FFFFFFFBB47Du64) + put(data, 0x4058FFFFFFFAA19Cu64) + put(data, 0x408F3FFFFFF94A03u64) + put(data, 0x40C387FFFFFBCE42u64) + put(data, 0x40F869FFFFFAC1D2u64) + put(data, 0x412E847FFFF97247u64) + put(data, 0x416312CFFFFBE76Cu64) + put(data, 0x4197D783FFFAE147u64) + put(data, 0x41CDCD64FFF99999u64) + put(data, 0x4202A05F1FFBFFFFu64) + put(data, 0x3F1A36E2EB1BB30Fu64) + put(data, 0x3F50624DD2F14FE9u64) + put(data, 0x3F847AE147ADA3E3u64) + put(data, 0x3FB9999999990CDCu64) + put(data, 0x3FEFFFFFFFFF5014u64) + put(data, 0x4023FFFFFFFF920Cu64) + put(data, 0x4058FFFFFFFF768Fu64) + put(data, 0x408F3FFFFFFF5433u64) + put(data, 0x40C387FFFFFF94A0u64) + put(data, 0x40F869FFFFFF79C8u64) + put(data, 0x412E847FFFFF583Au64) + put(data, 0x416312CFFFFF9724u64) + put(data, 0x4197D783FFFF7CEDu64) + put(data, 0x41CDCD64FFFF5C28u64) + put(data, 0x4202A05F1FFF9999u64) + put(data, 0x42374876E7FF7FFFu64) + put(data, 0x3F1A36E2EB1C34C3u64) + put(data, 0x3F50624DD2F1A0FAu64) + put(data, 0x3F847AE147AE0938u64) + put(data, 0x3FB9999999998B86u64) + put(data, 0x3FEFFFFFFFFFEE68u64) + put(data, 0x4023FFFFFFFFF501u64) + put(data, 0x4058FFFFFFFFF241u64) + put(data, 0x408F3FFFFFFFEED1u64) + put(data, 0x40C387FFFFFFF543u64) + put(data, 0x40F869FFFFFFF294u64) + put(data, 0x412E847FFFFFEF39u64) + put(data, 0x416312CFFFFFF583u64) + put(data, 0x4197D783FFFFF2E4u64) + put(data, 0x41CDCD64FFFFEF9Du64) + put(data, 0x4202A05F1FFFF5C2u64) + put(data, 0x42374876E7FFF333u64) + put(data, 0x426D1A94A1FFEFFFu64) + put(data, 0x3F1A36E2EB1C41BBu64) + put(data, 0x3F50624DD2F1A915u64) + put(data, 0x3F847AE147AE135Au64) + put(data, 0x3FB9999999999831u64) + put(data, 0x3FEFFFFFFFFFFE3Du64) + put(data, 0x4023FFFFFFFFFEE6u64) + put(data, 0x4058FFFFFFFFFEA0u64) + put(data, 0x408F3FFFFFFFFE48u64) + put(data, 0x40C387FFFFFFFEEDu64) + put(data, 0x40F869FFFFFFFEA8u64) + put(data, 0x412E847FFFFFFE52u64) + put(data, 0x416312CFFFFFFEF3u64) + put(data, 0x4197D783FFFFFEB0u64) + put(data, 0x41CDCD64FFFFFE5Cu64) + put(data, 0x4202A05F1FFFFEF9u64) + put(data, 0x42374876E7FFFEB8u64) + put(data, 0x426D1A94A1FFFE66u64) + put(data, 0x42A2309CE53FFEFFu64) + put(data, 0x3F1A36E2EB1C4307u64) + put(data, 0x3F50624DD2F1A9E4u64) + put(data, 0x3F847AE147AE145Eu64) + put(data, 0x3FB9999999999975u64) + put(data, 0x3FEFFFFFFFFFFFD2u64) + put(data, 0x4023FFFFFFFFFFE3u64) + put(data, 0x4058FFFFFFFFFFDCu64) + put(data, 0x408F3FFFFFFFFFD4u64) + put(data, 0x40C387FFFFFFFFE4u64) + put(data, 0x40F869FFFFFFFFDDu64) + put(data, 0x412E847FFFFFFFD5u64) + put(data, 0x416312CFFFFFFFE5u64) + put(data, 0x4197D783FFFFFFDEu64) + put(data, 0x41CDCD64FFFFFFD6u64) + put(data, 0x4202A05F1FFFFFE5u64) + put(data, 0x42374876E7FFFFDFu64) + put(data, 0x426D1A94A1FFFFD7u64) + put(data, 0x42A2309CE53FFFE6u64) + put(data, 0x42D6BCC41E8FFFDFu64) + put(data, 0x3F1A36E2EB1C4328u64) + put(data, 0x3F50624DD2F1A9F9u64) + put(data, 0x3F847AE147AE1477u64) + put(data, 0x3FB9999999999995u64) + put(data, 0x3FEFFFFFFFFFFFFBu64) + put(data, 0x4023FFFFFFFFFFFDu64) + put(data, 0x4058FFFFFFFFFFFCu64) + put(data, 0x408F3FFFFFFFFFFBu64) + put(data, 0x40C387FFFFFFFFFDu64) + put(data, 0x40F869FFFFFFFFFCu64) + put(data, 0x412E847FFFFFFFFBu64) + put(data, 0x416312CFFFFFFFFDu64) + put(data, 0x4197D783FFFFFFFCu64) + put(data, 0x41CDCD64FFFFFFFBu64) + put(data, 0x4202A05F1FFFFFFDu64) + put(data, 0x42374876E7FFFFFCu64) + put(data, 0x426D1A94A1FFFFFBu64) + put(data, 0x42A2309CE53FFFFDu64) + put(data, 0x42D6BCC41E8FFFFCu64) + put(data, 0x430C6BF52633FFFBu64) + data + end + + private MAX_ORDINARY_P = 309_u32 + + private ORDINARY_X = begin + data = Array(UInt64).new(314) + put(data, 0x3F1A36E2EB1C432Cu64) + put(data, 0x3F50624DD2F1A9FBu64) + put(data, 0x3F847AE147AE147Au64) + put(data, 0x3FB9999999999999u64) + put(data, 0x3FEFFFFFFFFFFFFFu64) + put(data, 0x4023FFFFFFFFFFFFu64) + put(data, 0x4058FFFFFFFFFFFFu64) + put(data, 0x408F3FFFFFFFFFFFu64) + put(data, 0x40C387FFFFFFFFFFu64) + put(data, 0x40F869FFFFFFFFFFu64) + put(data, 0x412E847FFFFFFFFFu64) + put(data, 0x416312CFFFFFFFFFu64) + put(data, 0x4197D783FFFFFFFFu64) + put(data, 0x41CDCD64FFFFFFFFu64) + put(data, 0x4202A05F1FFFFFFFu64) + put(data, 0x42374876E7FFFFFFu64) + put(data, 0x426D1A94A1FFFFFFu64) + put(data, 0x42A2309CE53FFFFFu64) + put(data, 0x42D6BCC41E8FFFFFu64) + put(data, 0x430C6BF52633FFFFu64) + put(data, 0x4341C37937E07FFFu64) + put(data, 0x4376345785D89FFFu64) + put(data, 0x43ABC16D674EC7FFu64) + put(data, 0x43E158E460913CFFu64) + put(data, 0x4415AF1D78B58C3Fu64) + put(data, 0x444B1AE4D6E2EF4Fu64) + put(data, 0x4480F0CF064DD591u64) + put(data, 0x44B52D02C7E14AF6u64) + put(data, 0x44EA784379D99DB4u64) + put(data, 0x45208B2A2C280290u64) + put(data, 0x4554ADF4B7320334u64) + put(data, 0x4589D971E4FE8401u64) + put(data, 0x45C027E72F1F1281u64) + put(data, 0x45F431E0FAE6D721u64) + put(data, 0x46293E5939A08CE9u64) + put(data, 0x465F8DEF8808B024u64) + put(data, 0x4693B8B5B5056E16u64) + put(data, 0x46C8A6E32246C99Cu64) + put(data, 0x46FED09BEAD87C03u64) + put(data, 0x4733426172C74D82u64) + put(data, 0x476812F9CF7920E2u64) + put(data, 0x479E17B84357691Bu64) + put(data, 0x47D2CED32A16A1B1u64) + put(data, 0x48078287F49C4A1Du64) + put(data, 0x483D6329F1C35CA4u64) + put(data, 0x48725DFA371A19E6u64) + put(data, 0x48A6F578C4E0A060u64) + put(data, 0x48DCB2D6F618C878u64) + put(data, 0x4911EFC659CF7D4Bu64) + put(data, 0x49466BB7F0435C9Eu64) + put(data, 0x497C06A5EC5433C6u64) + put(data, 0x49B18427B3B4A05Bu64) + put(data, 0x49E5E531A0A1C872u64) + put(data, 0x4A1B5E7E08CA3A8Fu64) + put(data, 0x4A511B0EC57E6499u64) + put(data, 0x4A8561D276DDFDC0u64) + put(data, 0x4ABABA4714957D30u64) + put(data, 0x4AF0B46C6CDD6E3Eu64) + put(data, 0x4B24E1878814C9CDu64) + put(data, 0x4B5A19E96A19FC40u64) + put(data, 0x4B905031E2503DA8u64) + put(data, 0x4BC4643E5AE44D12u64) + put(data, 0x4BF97D4DF19D6057u64) + put(data, 0x4C2FDCA16E04B86Du64) + put(data, 0x4C63E9E4E4C2F344u64) + put(data, 0x4C98E45E1DF3B015u64) + put(data, 0x4CCF1D75A5709C1Au64) + put(data, 0x4D03726987666190u64) + put(data, 0x4D384F03E93FF9F4u64) + put(data, 0x4D6E62C4E38FF872u64) + put(data, 0x4DA2FDBB0E39FB47u64) + put(data, 0x4DD7BD29D1C87A19u64) + put(data, 0x4E0DAC74463A989Fu64) + put(data, 0x4E428BC8ABE49F63u64) + put(data, 0x4E772EBAD6DDC73Cu64) + put(data, 0x4EACFA698C95390Bu64) + put(data, 0x4EE21C81F7DD43A7u64) + put(data, 0x4F16A3A275D49491u64) + put(data, 0x4F4C4C8B1349B9B5u64) + put(data, 0x4F81AFD6EC0E1411u64) + put(data, 0x4FB61BCCA7119915u64) + put(data, 0x4FEBA2BFD0D5FF5Bu64) + put(data, 0x502145B7E285BF98u64) + put(data, 0x50559725DB272F7Fu64) + put(data, 0x508AFCEF51F0FB5Eu64) + put(data, 0x50C0DE1593369D1Bu64) + put(data, 0x50F5159AF8044462u64) + put(data, 0x512A5B01B605557Au64) + put(data, 0x516078E111C3556Cu64) + put(data, 0x5194971956342AC7u64) + put(data, 0x51C9BCDFABC13579u64) + put(data, 0x5200160BCB58C16Cu64) + put(data, 0x52341B8EBE2EF1C7u64) + put(data, 0x526922726DBAAE39u64) + put(data, 0x529F6B0F092959C7u64) + put(data, 0x52D3A2E965B9D81Cu64) + put(data, 0x53088BA3BF284E23u64) + put(data, 0x533EAE8CAEF261ACu64) + put(data, 0x53732D17ED577D0Bu64) + put(data, 0x53A7F85DE8AD5C4Eu64) + put(data, 0x53DDF67562D8B362u64) + put(data, 0x5412BA095DC7701Du64) + put(data, 0x5447688BB5394C25u64) + put(data, 0x547D42AEA2879F2Eu64) + put(data, 0x54B249AD2594C37Cu64) + put(data, 0x54E6DC186EF9F45Cu64) + put(data, 0x551C931E8AB87173u64) + put(data, 0x5551DBF316B346E7u64) + put(data, 0x558652EFDC6018A1u64) + put(data, 0x55BBE7ABD3781ECAu64) + put(data, 0x55F170CB642B133Eu64) + put(data, 0x5625CCFE3D35D80Eu64) + put(data, 0x565B403DCC834E11u64) + put(data, 0x569108269FD210CBu64) + put(data, 0x56C54A3047C694FDu64) + put(data, 0x56FA9CBC59B83A3Du64) + put(data, 0x5730A1F5B8132466u64) + put(data, 0x5764CA732617ED7Fu64) + put(data, 0x5799FD0FEF9DE8DFu64) + put(data, 0x57D03E29F5C2B18Bu64) + put(data, 0x58044DB473335DEEu64) + put(data, 0x583961219000356Au64) + put(data, 0x586FB969F40042C5u64) + put(data, 0x58A3D3E2388029BBu64) + put(data, 0x58D8C8DAC6A0342Au64) + put(data, 0x590EFB1178484134u64) + put(data, 0x59435CEAEB2D28C0u64) + put(data, 0x59783425A5F872F1u64) + put(data, 0x59AE412F0F768FADu64) + put(data, 0x59E2E8BD69AA19CCu64) + put(data, 0x5A17A2ECC414A03Fu64) + put(data, 0x5A4D8BA7F519C84Fu64) + put(data, 0x5A827748F9301D31u64) + put(data, 0x5AB7151B377C247Eu64) + put(data, 0x5AECDA62055B2D9Du64) + put(data, 0x5B22087D4358FC82u64) + put(data, 0x5B568A9C942F3BA3u64) + put(data, 0x5B8C2D43B93B0A8Bu64) + put(data, 0x5BC19C4A53C4E697u64) + put(data, 0x5BF6035CE8B6203Du64) + put(data, 0x5C2B843422E3A84Cu64) + put(data, 0x5C6132A095CE492Fu64) + put(data, 0x5C957F48BB41DB7Bu64) + put(data, 0x5CCADF1AEA12525Au64) + put(data, 0x5D00CB70D24B7378u64) + put(data, 0x5D34FE4D06DE5056u64) + put(data, 0x5D6A3DE04895E46Cu64) + put(data, 0x5DA066AC2D5DAEC3u64) + put(data, 0x5DD4805738B51A74u64) + put(data, 0x5E09A06D06E26112u64) + put(data, 0x5E400444244D7CABu64) + put(data, 0x5E7405552D60DBD6u64) + put(data, 0x5EA906AA78B912CBu64) + put(data, 0x5EDF485516E7577Eu64) + put(data, 0x5F138D352E5096AFu64) + put(data, 0x5F48708279E4BC5Au64) + put(data, 0x5F7E8CA3185DEB71u64) + put(data, 0x5FB317E5EF3AB327u64) + put(data, 0x5FE7DDDF6B095FF0u64) + put(data, 0x601DD55745CBB7ECu64) + put(data, 0x6052A5568B9F52F4u64) + put(data, 0x60874EAC2E8727B1u64) + put(data, 0x60BD22573A28F19Du64) + put(data, 0x60F2357684599702u64) + put(data, 0x6126C2D4256FFCC2u64) + put(data, 0x615C73892ECBFBF3u64) + put(data, 0x6191C835BD3F7D78u64) + put(data, 0x61C63A432C8F5CD6u64) + put(data, 0x61FBC8D3F7B3340Bu64) + put(data, 0x62315D847AD00087u64) + put(data, 0x6265B4E5998400A9u64) + put(data, 0x629B221EFFE500D3u64) + put(data, 0x62D0F5535FEF2084u64) + put(data, 0x630532A837EAE8A5u64) + put(data, 0x633A7F5245E5A2CEu64) + put(data, 0x63708F936BAF85C1u64) + put(data, 0x63A4B378469B6731u64) + put(data, 0x63D9E056584240FDu64) + put(data, 0x64102C35F729689Eu64) + put(data, 0x6444374374F3C2C6u64) + put(data, 0x647945145230B377u64) + put(data, 0x64AF965966BCE055u64) + put(data, 0x64E3BDF7E0360C35u64) + put(data, 0x6518AD75D8438F43u64) + put(data, 0x654ED8D34E547313u64) + put(data, 0x6583478410F4C7ECu64) + put(data, 0x65B819651531F9E7u64) + put(data, 0x65EE1FBE5A7E7861u64) + put(data, 0x6622D3D6F88F0B3Cu64) + put(data, 0x665788CCB6B2CE0Cu64) + put(data, 0x668D6AFFE45F818Fu64) + put(data, 0x66C262DFEEBBB0F9u64) + put(data, 0x66F6FB97EA6A9D37u64) + put(data, 0x672CBA7DE5054485u64) + put(data, 0x6761F48EAF234AD3u64) + put(data, 0x679671B25AEC1D88u64) + put(data, 0x67CC0E1EF1A724EAu64) + put(data, 0x680188D357087712u64) + put(data, 0x6835EB082CCA94D7u64) + put(data, 0x686B65CA37FD3A0Du64) + put(data, 0x68A11F9E62FE4448u64) + put(data, 0x68D56785FBBDD55Au64) + put(data, 0x690AC1677AAD4AB0u64) + put(data, 0x6940B8E0ACAC4EAEu64) + put(data, 0x6974E718D7D7625Au64) + put(data, 0x69AA20DF0DCD3AF0u64) + put(data, 0x69E0548B68A044D6u64) + put(data, 0x6A1469AE42C8560Cu64) + put(data, 0x6A498419D37A6B8Fu64) + put(data, 0x6A7FE52048590672u64) + put(data, 0x6AB3EF342D37A407u64) + put(data, 0x6AE8EB0138858D09u64) + put(data, 0x6B1F25C186A6F04Cu64) + put(data, 0x6B537798F428562Fu64) + put(data, 0x6B88557F31326BBBu64) + put(data, 0x6BBE6ADEFD7F06AAu64) + put(data, 0x6BF302CB5E6F642Au64) + put(data, 0x6C27C37E360B3D35u64) + put(data, 0x6C5DB45DC38E0C82u64) + put(data, 0x6C9290BA9A38C7D1u64) + put(data, 0x6CC734E940C6F9C5u64) + put(data, 0x6CFD022390F8B837u64) + put(data, 0x6D3221563A9B7322u64) + put(data, 0x6D66A9ABC9424FEBu64) + put(data, 0x6D9C5416BB92E3E6u64) + put(data, 0x6DD1B48E353BCE6Fu64) + put(data, 0x6E0621B1C28AC20Bu64) + put(data, 0x6E3BAA1E332D728Eu64) + put(data, 0x6E714A52DFFC6799u64) + put(data, 0x6EA59CE797FB817Fu64) + put(data, 0x6EDB04217DFA61DFu64) + put(data, 0x6F10E294EEBC7D2Bu64) + put(data, 0x6F451B3A2A6B9C76u64) + put(data, 0x6F7A6208B5068394u64) + put(data, 0x6FB07D457124123Cu64) + put(data, 0x6FE49C96CD6D16CBu64) + put(data, 0x7019C3BC80C85C7Eu64) + put(data, 0x70501A55D07D39CFu64) + put(data, 0x708420EB449C8842u64) + put(data, 0x70B9292615C3AA53u64) + put(data, 0x70EF736F9B3494E8u64) + put(data, 0x7123A825C100DD11u64) + put(data, 0x7158922F31411455u64) + put(data, 0x718EB6BAFD91596Bu64) + put(data, 0x71C33234DE7AD7E2u64) + put(data, 0x71F7FEC216198DDBu64) + put(data, 0x722DFE729B9FF152u64) + put(data, 0x7262BF07A143F6D3u64) + put(data, 0x72976EC98994F488u64) + put(data, 0x72CD4A7BEBFA31AAu64) + put(data, 0x73024E8D737C5F0Au64) + put(data, 0x7336E230D05B76CDu64) + put(data, 0x736C9ABD04725480u64) + put(data, 0x73A1E0B622C774D0u64) + put(data, 0x73D658E3AB795204u64) + put(data, 0x740BEF1C9657A685u64) + put(data, 0x74417571DDF6C813u64) + put(data, 0x7475D2CE55747A18u64) + put(data, 0x74AB4781EAD1989Eu64) + put(data, 0x74E10CB132C2FF63u64) + put(data, 0x75154FDD7F73BF3Bu64) + put(data, 0x754AA3D4DF50AF0Au64) + put(data, 0x7580A6650B926D66u64) + put(data, 0x75B4CFFE4E7708C0u64) + put(data, 0x75EA03FDE214CAF0u64) + put(data, 0x7620427EAD4CFED6u64) + put(data, 0x7654531E58A03E8Bu64) + put(data, 0x768967E5EEC84E2Eu64) + put(data, 0x76BFC1DF6A7A61BAu64) + put(data, 0x76F3D92BA28C7D14u64) + put(data, 0x7728CF768B2F9C59u64) + put(data, 0x775F03542DFB8370u64) + put(data, 0x779362149CBD3226u64) + put(data, 0x77C83A99C3EC7EAFu64) + put(data, 0x77FE494034E79E5Bu64) + put(data, 0x7832EDC82110C2F9u64) + put(data, 0x7867A93A2954F3B7u64) + put(data, 0x789D9388B3AA30A5u64) + put(data, 0x78D27C35704A5E67u64) + put(data, 0x79071B42CC5CF601u64) + put(data, 0x793CE2137F743381u64) + put(data, 0x79720D4C2FA8A030u64) + put(data, 0x79A6909F3B92C83Du64) + put(data, 0x79DC34C70A777A4Cu64) + put(data, 0x7A11A0FC668AAC6Fu64) + put(data, 0x7A46093B802D578Bu64) + put(data, 0x7A7B8B8A6038AD6Eu64) + put(data, 0x7AB137367C236C65u64) + put(data, 0x7AE585041B2C477Eu64) + put(data, 0x7B1AE64521F7595Eu64) + put(data, 0x7B50CFEB353A97DAu64) + put(data, 0x7B8503E602893DD1u64) + put(data, 0x7BBA44DF832B8D45u64) + put(data, 0x7BF06B0BB1FB384Bu64) + put(data, 0x7C2485CE9E7A065Eu64) + put(data, 0x7C59A742461887F6u64) + put(data, 0x7C9008896BCF54F9u64) + put(data, 0x7CC40AABC6C32A38u64) + put(data, 0x7CF90D56B873F4C6u64) + put(data, 0x7D2F50AC6690F1F8u64) + put(data, 0x7D63926BC01A973Bu64) + put(data, 0x7D987706B0213D09u64) + put(data, 0x7DCE94C85C298C4Cu64) + put(data, 0x7E031CFD3999F7AFu64) + put(data, 0x7E37E43C8800759Bu64) + put(data, 0x7E6DDD4BAA009302u64) + put(data, 0x7EA2AA4F4A405BE1u64) + put(data, 0x7ED754E31CD072D9u64) + put(data, 0x7F0D2A1BE4048F90u64) + put(data, 0x7F423A516E82D9BAu64) + put(data, 0x7F76C8E5CA239028u64) + put(data, 0x7FAC7B1F3CAC7433u64) + put(data, 0x7FE1CCF385EBC89Fu64) + put(data, 0x7FEFFFFFFFFFFFFFu64) + data + end + private POW10_OFFSET = [ 0, 2, 5, 8, 12, 16, 21, 26, 32, 39, 46, 54, 62, 71, 80, 90, 100, 111, 122, 134, diff --git a/src/string/formatter.cr b/src/string/formatter.cr index e85322f39d2e..31239f42ee4d 100644 --- a/src/string/formatter.cr +++ b/src/string/formatter.cr @@ -430,8 +430,37 @@ struct String::Formatter(A) # Formats floats with `%g` or `%G` private def float_general(float, flags) - # TODO: implement using `Float::Printer::RyuPrintf` - float_fallback(float, flags) + # `d2gen_buffered_n` will always take care of trailing zeros and return + # the number of stripped digits + precision = flags.precision.try(&.to_u32) || 6_u32 + + printf_buf = uninitialized UInt8[773] + printf_size, trailing_zeros = + Float::Printer::RyuPrintf.d2gen_buffered_n(float.abs, precision, printf_buf.to_unsafe, flags.sharp) + printf_slice = printf_buf.to_slice[0, printf_size] + dot_index = printf_slice.index('.'.ord) + e_index = printf_slice.rindex('e'.ord) + sign = Math.copysign(1.0, float) + + printf_slice[e_index] = 'E'.ord.to_u8! if e_index && flags.uppercase? + + str_size = printf_size + str_size += 1 if sign < 0 || flags.plus || flags.space + str_size += (dot_index.nil? ? 1 : 0) + trailing_zeros if flags.sharp + + pad(str_size, flags) if flags.left_padding? && flags.padding_char != '0' + + # this preserves -0.0's sign correctly + write_plus_or_space(sign, flags) + @io << '-' if sign < 0 + + pad(str_size, flags) if flags.left_padding? && flags.padding_char == '0' + @io.write_string(printf_slice[0...e_index]) + trailing_zeros.times { @io << '0' } if flags.sharp + @io << '.' if flags.sharp && dot_index.nil? + @io.write_string(printf_slice[e_index..]) if e_index + + pad(str_size, flags) if flags.right_padding? end # Formats floats with `%a` or `%A` From ddee6c6c5c277f58bf1017c7b39bba1e31ae3770 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 22 Dec 2023 17:03:35 +0800 Subject: [PATCH 0859/1551] Implement most of `Crystal::System.print_error` in native Crystal (#14116) --- spec/std/crystal/system_spec.cr | 46 +++++++++ src/crystal/system/print_error.cr | 153 ++++++++++++++++++++++++++++-- 2 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 spec/std/crystal/system_spec.cr diff --git a/spec/std/crystal/system_spec.cr b/spec/std/crystal/system_spec.cr new file mode 100644 index 000000000000..06bc789055d4 --- /dev/null +++ b/spec/std/crystal/system_spec.cr @@ -0,0 +1,46 @@ +require "spec" + +private def print_error_to_s(format, *args) + io = IO::Memory.new + Crystal::System.print_error(format, *args) do |bytes| + io.write_string(bytes) + end + io.to_s +end + +describe "Crystal::System" do + describe ".print_error" do + it "works" do + print_error_to_s("abcde").should eq("abcde") + end + + it "supports %d" do + print_error_to_s("%d,%d,%d,%d,%d", 0, 1234, Int32::MAX, Int32::MIN, UInt64::MAX).should eq("0,1234,2147483647,-2147483648,-1") + end + + it "supports %u" do + print_error_to_s("%u,%u,%u,%u,%u", 0, 1234, UInt32::MAX, Int32::MIN, UInt64::MAX).should eq("0,1234,4294967295,2147483648,4294967295") + end + + it "supports %x" do + print_error_to_s("%x,%x,%x,%x,%x", 0, 0x1234, UInt32::MAX, Int32::MIN, UInt64::MAX).should eq("0,1234,ffffffff,80000000,ffffffff") + end + + it "supports %p" do + print_error_to_s("%p,%p,%p", Pointer(Void).new(0x0), Pointer(Void).new(0x1234), Pointer(Void).new(UInt64::MAX)).should eq("0x0,0x1234,0xffffffffffffffff") + end + + it "supports %s" do + print_error_to_s("%s,%s,%s", "abc\0def", "ghi".to_unsafe, Pointer(UInt8).null).should eq("abc\0def,ghi,(null)") + end + + it "supports %l width" do + values = {LibC::Long::MIN, LibC::Long::MAX, LibC::LongLong::MIN, LibC::LongLong::MAX} + print_error_to_s("%ld,%ld,%lld,%lld", *values).should eq(values.join(',')) + + values = {LibC::ULong::MIN, LibC::ULong::MAX, LibC::ULongLong::MIN, LibC::ULongLong::MAX} + print_error_to_s("%lu,%lu,%llu,%llu", *values).should eq(values.join(',')) + print_error_to_s("%lx,%lx,%llx,%llx", *values).should eq(values.join(',', &.to_s(16))) + end + end +end diff --git a/src/crystal/system/print_error.cr b/src/crystal/system/print_error.cr index 18f68d923507..3832eed5d9f4 100644 --- a/src/crystal/system/print_error.cr +++ b/src/crystal/system/print_error.cr @@ -1,15 +1,152 @@ module Crystal::System - # Prints directly to stderr without going through an IO. + # Prints directly to stderr without going through an `IO::FileDescriptor`. # This is useful for error messages from components that are required for # IO to work (fibers, scheduler, event_loop). def self.print_error(message, *args) - {% if flag?(:unix) %} - LibC.dprintf 2, message, *args - {% elsif flag?(:win32) %} - buffer = StaticArray(UInt8, 512).new(0_u8) - len = LibC.snprintf(buffer, buffer.size, message, *args) - LibC._write 2, buffer, len - {% end %} + print_error(message, *args) do |bytes| + {% if flag?(:unix) || flag?(:wasm32) %} + LibC.write 2, bytes, bytes.size + {% elsif flag?(:win32) %} + LibC._write 2, bytes, bytes.size + {% end %} + end + end + + # Minimal drop-in replacement for a C `printf` function. Yields successive + # non-empty `Bytes` to the block, which should do the actual printing. + # + # *format* only supports the `%(l|ll)?[dpsux]` format specifiers; more should + # be added only when we need them or an external library passes its own format + # string to here. *format* must also be null-terminated. + # + # Since this method may be called under low memory conditions or even with a + # corrupted heap, its implementation should be as low-level as possible, + # avoiding memory allocations. + # + # NOTE: C's `printf` is incompatible with Crystal's `sprintf`, because the + # latter does not support argument width specifiers nor `%p`. + def self.print_error(format, *args, &) + format = to_string_slice(format) + format_len = format.size + ptr = format.to_unsafe + finish = ptr + format_len + arg_index = 0 + + while ptr < finish + next_percent = ptr + while next_percent < finish && !(next_percent.value === '%') + next_percent += 1 + end + unless next_percent == ptr + yield Slice.new(ptr, next_percent - ptr) + end + + fmt_ptr = next_percent + 1 + width = 0 + if fmt_ptr.value === 'l' + width = 1 + fmt_ptr += 1 + if fmt_ptr.value === 'l' + width = 2 + fmt_ptr += 1 + end + end + + break unless fmt_ptr < finish + + case fmt_ptr.value + when 's' + read_arg(String | Pointer(UInt8)) do |arg| + yield to_string_slice(arg) + end + when 'd' + read_arg(Int::Primitive) do |arg| + to_int_slice(arg, 10, true, width) { |bytes| yield bytes } + end + when 'u' + read_arg(Int::Primitive) do |arg| + to_int_slice(arg, 10, false, width) { |bytes| yield bytes } + end + when 'x' + read_arg(Int::Primitive) do |arg| + to_int_slice(arg, 16, false, width) { |bytes| yield bytes } + end + when 'p' + read_arg(Pointer(Void)) do |arg| + # NOTE: MSVC uses `%X` rather than `0x%x`, we follow the latter on all platforms + yield "0x".to_slice + to_int_slice(arg.address, 16, false, 2) { |bytes| yield bytes } + end + else + yield Slice.new(next_percent, fmt_ptr + 1 - next_percent) + end + + ptr = fmt_ptr + 1 + end + end + + private macro read_arg(type, &block) + {{ block.args[0] }} = args[arg_index].as?({{ type }}) + if !{{ block.args[0] }}.nil? + {{ block.body }} + else + yield "(???)".to_slice + end + arg_index += 1 + end + + private def self.to_string_slice(str) + if str.is_a?(UInt8*) + if str.null? + "(null)".to_slice + else + Slice.new(str, LibC.strlen(str)) + end + else + str.to_s.to_slice + end + end + + # simplified version of `Int#internal_to_s` + private def self.to_int_slice(num, base, signed, width, &) + if num == 0 + yield "0".to_slice + return + end + + # Given sizeof(num) <= 64 bits, we need at most 20 bytes for `%d` or `%u` + # note that `chars` does not have to be null-terminated, since we are + # only yielding a `Bytes` + chars = uninitialized UInt8[20] + ptr_end = chars.to_unsafe + 20 + ptr = ptr_end + + num = case {signed, width} + when {true, 2} then LibC::LongLong.new!(num) + when {true, 1} then LibC::Long.new!(num) + when {true, 0} then LibC::Int.new!(num) + when {false, 2} then LibC::ULongLong.new!(num) + when {false, 1} then LibC::ULong.new!(num) + else LibC::UInt.new!(num) + end + + neg = num < 0 + + # do not assume Crystal constant initialization succeeds, hence not `DIGITS` + digits = "0123456789abcdef".to_unsafe + + while num != 0 + ptr -= 1 + ptr.value = digits[num.remainder(base).abs] + num = num.tdiv(base) + end + + if neg + ptr -= 1 + ptr.value = '-'.ord.to_u8 + end + + yield Slice.new(ptr, ptr_end - ptr) end def self.print_exception(message, ex) From 3ecdf161afab5f810e949560d637b76fa43841b1 Mon Sep 17 00:00:00 2001 From: Erdian718 Date: Fri, 22 Dec 2023 17:03:47 +0800 Subject: [PATCH 0860/1551] Fix UTF-8 console input on Windows (#13758) Co-authored-by: Quinton Miller --- src/crystal/system/win32/file_descriptor.cr | 71 ++++++++++++++++++- src/lib_c/x86_64-windows-msvc/c/consoleapi.cr | 8 +++ src/string/utf16.cr | 4 +- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 6e4f42d46b17..109c353c0325 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -11,7 +11,10 @@ module Crystal::System::FileDescriptor @system_blocking = true private def unbuffered_read(slice : Bytes) - if system_blocking? + handle = windows_handle + if ConsoleUtils.console?(handle) + ConsoleUtils.read(handle, slice) + elsif system_blocking? bytes_read = LibC._read(fd, slice, slice.size) if bytes_read == -1 if Errno.value == Errno::EBADF @@ -22,7 +25,6 @@ module Crystal::System::FileDescriptor end bytes_read else - handle = windows_handle overlapped_operation(handle, "ReadFile", read_timeout) do |overlapped| ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped) {ret, byte_count} @@ -276,6 +278,71 @@ module Crystal::System::FileDescriptor end end +private module ConsoleUtils + # N UTF-16 code units correspond to no more than 3*N UTF-8 code units. + # NOTE: For very large buffers, `ReadConsoleW` may fail. + private BUFFER_SIZE = 10000 + @@utf8_buffer = Slice(UInt8).new(3 * BUFFER_SIZE) + + # `@@buffer` points to part of `@@utf8_buffer`. + # It represents data that has not been read yet. + @@buffer : Bytes = @@utf8_buffer[0, 0] + + # Remaining UTF-16 code unit. + @@remaining_unit : UInt16? + + # Determines if *handle* is a console. + def self.console?(handle : LibC::HANDLE) : Bool + LibC.GetConsoleMode(handle, out _) != 0 + end + + # Reads to *slice* from the console specified by *handle*, + # and return the actual number of bytes read. + def self.read(handle : LibC::HANDLE, slice : Bytes) : Int32 + return 0 if slice.empty? + fill_buffer(handle) if @@buffer.empty? + + bytes_read = {slice.size, @@buffer.size}.min + @@buffer[0, bytes_read].copy_to(slice) + @@buffer += bytes_read + bytes_read + end + + private def self.fill_buffer(handle : LibC::HANDLE) : Nil + utf16_buffer = uninitialized UInt16[BUFFER_SIZE] + remaining_unit = @@remaining_unit + if remaining_unit + utf16_buffer[0] = remaining_unit + index = read_console(handle, utf16_buffer.to_slice + 1) + else + index = read_console(handle, utf16_buffer.to_slice) - 1 + end + + if index >= 0 && utf16_buffer[index] & 0xFC00 == 0xD800 + @@remaining_unit = utf16_buffer[index] + index -= 1 + else + @@remaining_unit = nil + end + return if index < 0 + + appender = @@utf8_buffer.to_unsafe.appender + String.each_utf16_char(utf16_buffer.to_slice[..index]) do |char| + char.each_byte do |byte| + appender << byte + end + end + @@buffer = @@utf8_buffer[0, appender.size] + end + + private def self.read_console(handle : LibC::HANDLE, slice : Slice(UInt16)) : Int32 + if 0 == LibC.ReadConsoleW(handle, slice, slice.size, out units_read, nil) + raise IO::Error.from_winerror("ReadConsoleW") + end + units_read.to_i32 + end +end + # Enable UTF-8 console I/O for the duration of program execution if LibC.IsValidCodePage(LibC::CP_UTF8) != 0 old_input_cp = LibC.GetConsoleCP diff --git a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr index 680e199be2ab..796369c65a85 100644 --- a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr @@ -14,6 +14,14 @@ lib LibC fun GetConsoleCP : DWORD fun GetConsoleOutputCP : DWORD + fun ReadConsoleW( + hConsoleInput : HANDLE, + lpBuffer : Void*, + nNumberOfCharsToRead : DWORD, + lpNumberOfCharsRead : DWORD*, + pInputControl : Void* + ) : BOOL + CTRL_C_EVENT = 0 CTRL_BREAK_EVENT = 1 diff --git a/src/string/utf16.cr b/src/string/utf16.cr index b3fdc09301af..042391cca15d 100644 --- a/src/string/utf16.cr +++ b/src/string/utf16.cr @@ -128,8 +128,10 @@ class String {string, pointer + 1} end + # :nodoc: + # # Yields each decoded char in the given slice. - private def self.each_utf16_char(slice : Slice(UInt16), &) + def self.each_utf16_char(slice : Slice(UInt16), &) i = 0 while i < slice.size byte = slice[i].to_i From ac6b56e061bc6b86dd9d8c1045074ca4b7e61500 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 22 Dec 2023 15:15:23 +0100 Subject: [PATCH 0861/1551] Workaround regular timeouts in `HTTP::Server` specs with MT (#14097) --- spec/std/http/server/server_spec.cr | 5 +---- spec/std/http/spec_helper.cr | 5 +++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index e5aa0ec261a7..f5a82a514e0b 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -45,12 +45,9 @@ describe HTTP::Server do server = HTTP::Server.new { } server.bind_unused_port - spawn do + run_server(server) do server.close - sleep 0.001 end - - server.listen end it "closes the server" do diff --git a/spec/std/http/spec_helper.cr b/spec/std/http/spec_helper.cr index 410e211e4dbe..18ec9e0bab46 100644 --- a/spec/std/http/spec_helper.cr +++ b/spec/std/http/spec_helper.cr @@ -46,6 +46,11 @@ def run_server(server, &) wait_for { server.listening? } wait_until_blocked f + {% if flag?(:preview_mt) %} + # avoids fiber synchronization issues in specs, like closing the server + # before we properly listen, ... + sleep 0.001 + {% end %} yield server_done ensure server.close unless server.closed? From 2514ac42b2a1ea0692a7a2ffe7fb37e9ef537e7b Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Fri, 22 Dec 2023 09:15:40 -0500 Subject: [PATCH 0862/1551] Add location information to implicit flag enum members (#14127) --- spec/compiler/semantic/doc_spec.cr | 18 ++++++++++++++++++ .../crystal/semantic/top_level_visitor.cr | 15 +++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/spec/compiler/semantic/doc_spec.cr b/spec/compiler/semantic/doc_spec.cr index 9b38339b6485..3338f920a938 100644 --- a/spec/compiler/semantic/doc_spec.cr +++ b/spec/compiler/semantic/doc_spec.cr @@ -353,6 +353,24 @@ describe "Semantic: doc" do a.locations.not_nil!.size.should eq(1) end + it "stores location for implicit flag enum members" do + result = semantic %( + @[Flags] + enum Foo + A = 1 + B = 2 + end + ), wants_doc: true + program = result.program + foo = program.types["Foo"] + + a_loc = foo.types["All"].locations.should_not be_nil + a_loc.should_not be_empty + + b_loc = foo.types["None"].locations.should_not be_nil + b_loc.should_not be_empty + end + it "stores doc for constant" do result = semantic %( # Hello diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 5d0cbc477260..05d8918f8383 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -655,16 +655,27 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor if enum_type.flags? unless enum_type.types.has_key?("None") - enum_type.add_constant("None", 0) + none_member = enum_type.add_constant("None", 0) + + if node_location = node.location + none_member.add_location node_location + end + define_enum_none_question_method(enum_type, node) end unless enum_type.types.has_key?("All") all_value = enum_type.base_type.kind.cast(0).as(Int::Primitive) + enum_type.types.each_value do |member| all_value |= interpret_enum_value(member.as(Const).value, enum_type.base_type) end - enum_type.add_constant("All", all_value) + + all_member = enum_type.add_constant("All", all_value) + + if node_location = node.location + all_member.add_location node_location + end end end From f362827ed2f1766c8f15d480354238a67556ca31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 22 Dec 2023 15:16:16 +0100 Subject: [PATCH 0863/1551] Add `--tallies` option to `crystal tool unreachable` (#13969) Co-authored-by: Quinton Miller --- .../crystal/tools/unreachable_spec.cr | 44 +++++++++++++--- src/compiler/crystal/command.cr | 10 +++- src/compiler/crystal/tools/unreachable.cr | 50 +++++++++++-------- 3 files changed, 73 insertions(+), 31 deletions(-) diff --git a/spec/compiler/crystal/tools/unreachable_spec.cr b/spec/compiler/crystal/tools/unreachable_spec.cr index 6fffb17e4ca6..12ed82499740 100644 --- a/spec/compiler/crystal/tools/unreachable_spec.cr +++ b/spec/compiler/crystal/tools/unreachable_spec.cr @@ -15,28 +15,37 @@ def processed_unreachable_visitor(code) end private def assert_unreachable(code, file = __FILE__, line = __LINE__) - expected_locations = [] of Location + expected = Hash(String, Int32).new code.lines.each_with_index do |line, line_number_0| - if column_number = line.index('༓') - expected_locations << Location.new(".", line_number_0 + 1, column_number + 1) + if match = line.match(/༓(?:\[(\d+)\])?/) + location = Location.new(".", line_number_0 + 1, match.begin + 1) + expected[location.to_s] = (match[1]? || 0).to_i end end - code = code.gsub('༓', "") + code = code.gsub(/༓(?:\[(\d+)\])?/, "") - defs = processed_unreachable_visitor(code) + tallies = processed_unreachable_visitor(code) - result_location = defs.try &.compact_map(&.location).sort_by! do |loc| + processed_results = [] of {Location, Int32} + tallies.each do |k, v| + location = k.location.not_nil! + next unless v.zero? || expected.has_key?(location.to_s) + processed_results << {location, v} + end + + processed_results = processed_results.sort_by! do |loc, v| {loc.filename.as(String), loc.line_number, loc.column_number} - end.map(&.to_s) + end.map { |k, v| {k.to_s, v} } - result_location.should eq(expected_locations.map(&.to_s)), file: file, line: line + processed_results.should eq(expected.to_a), file: file, line: line end # References # # ༓ marks the expected unreachable code to be found +# ༓[n] marks the expected code to be called n times # describe "unreachable" do it "finds top level methods" do @@ -424,4 +433,23 @@ describe "unreachable" do {% end %} CRYSTAL end + + it "tallies calls" do + assert_unreachable <<-CRYSTAL + ༓def foo + 1 + end + + ༓[2]def bar + 2 + end + + ༓[1]def baz + bar + end + + bar + baz + CRYSTAL + end end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 2c5492445e0b..69b4ab2d221c 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -349,7 +349,8 @@ class Crystal::Command includes : Array(String), excludes : Array(String), verbose : Bool, - check : Bool do + check : Bool, + tallies : Bool do def compile(output_filename = self.output_filename) compiler.emit_base_filename = emit_base_filename || output_filename.rchop(File.extname(output_filename)) compiler.compile sources, output_filename, combine_rpath: combine_rpath @@ -381,6 +382,7 @@ class Crystal::Command includes = [] of String verbose = false check = false + tallies = false option_parser = parse_with_crystal_opts do |opts| opts.banner = "Usage: crystal #{command} [options] [programfile] [--] [arguments]\n\nOptions:" @@ -443,6 +445,10 @@ class Crystal::Command end if unreachable_command + opts.on("--tallies", "Print reachable methods and their call counts as well") do + tallies = true + end + opts.on("--check", "Exits with error if there is any unreachable code") do |f| check = true end @@ -613,7 +619,7 @@ class Crystal::Command combine_rpath = run && !no_codegen @config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format.not_nil!, - combine_rpath, includes, excludes, verbose, check + combine_rpath, includes, excludes, verbose, check, tallies end private def gather_sources(filenames) diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr index f89f90e4c37d..a8886fecf596 100644 --- a/src/compiler/crystal/tools/unreachable.cr +++ b/src/compiler/crystal/tools/unreachable.cr @@ -15,8 +15,8 @@ module Crystal unreachable.excludes.concat CrystalPath.default_paths.map { |path| ::Path[path].expand.to_posix.to_s } unreachable.excludes.concat config.excludes.map { |path| ::Path[path].expand.to_posix.to_s } - defs = unreachable.process(result) - defs.sort_by! do |a_def| + tallies = unreachable.process(result) + tallies.sort_by! do |a_def, _| location = a_def.location.not_nil! { location.filename.as(String), @@ -25,15 +25,15 @@ module Crystal } end - UnreachablePresenter.new(defs, format: config.output_format).to_s(STDOUT) + UnreachablePresenter.new(tallies, format: config.output_format, print_tallies: config.tallies).to_s(STDOUT) if config.check - exit 1 unless defs.empty? + exit 1 if tallies.any?(&.[1].zero?) end end end - record UnreachablePresenter, defs : Array(Def), format : String? do + record UnreachablePresenter, tallies : Array({Def, Int32}), format : String?, print_tallies : Bool do include JSON::Serializable def to_s(io) @@ -49,16 +49,19 @@ module Crystal def each(&) current_dir = Dir.current - defs.each do |a_def| + tallies.each do |a_def, count| + next unless print_tallies || count.zero? + location = a_def.location.not_nil! filename = ::Path[location.filename.as(String)].relative_to(current_dir).to_s location = Location.new(filename, location.line_number, location.column_number) - yield a_def, location + yield a_def, location, count end end def to_text(io) - each do |a_def, location| + each do |a_def, location, count| + io << count << "\t" if print_tallies io << location << "\t" io << a_def.short_reference << "\t" io << a_def.length << " lines" @@ -72,13 +75,14 @@ module Crystal def to_json(builder : JSON::Builder) builder.array do - each do |a_def, location| + each do |a_def, location, count| builder.object do builder.field "name", a_def.short_reference builder.field "location", location.to_s if lines = a_def.length builder.field "lines", lines end + builder.field "count", count if print_tallies if annotations = a_def.all_annotations builder.field "annotations", annotations.map(&.to_s) end @@ -89,9 +93,14 @@ module Crystal def to_csv(io) CSV.build(io) do |builder| - builder.row %w[name file line column length annotations] - each do |a_def, location| + builder.row do |row| + row << "count" if print_tallies + row.concat %w[name file line column length annotations] + end + + each do |a_def, location, count| builder.row do |row| + row << count if print_tallies row << a_def.short_reference row << location.filename row << location.line_number @@ -111,8 +120,8 @@ module Crystal # Then it traverses all types and their defs and reports those that are not # in `@used_def_locations` (and match the filter). class UnreachableVisitor < Visitor - @used_def_locations = Set(Location).new - @defs : Set(Def) = Set(Def).new.compare_by_identity + @used_def_locations = Hash(Location, Int32).new(0) + @tallies : Hash(Def, Int32) = Hash(Def, Int32).new.compare_by_identity @visited : Set(ASTNode) = Set(ASTNode).new.compare_by_identity property includes = [] of String @@ -130,14 +139,14 @@ module Crystal process_type(type.metaclass) if type.metaclass != type end - def process(result : Compiler::Result) : Array(Def) - @defs.clear + def process(result : Compiler::Result) : Array({Def, Int32}) + @tallies.clear result.node.accept(self) process_type(result.program) - @defs.to_a + @tallies.to_a end def visit(node) @@ -163,7 +172,7 @@ module Crystal node.target_defs.try &.each do |a_def| if (location = a_def.location) - @used_def_locations << location if interested_in(location) + @used_def_locations.update(location, &.+(1)) if interested_in(location) end if @visited.add?(a_def) @@ -199,11 +208,10 @@ module Crystal check_def(previous) if previous && !a_def.calls_previous_def? - return if @used_def_locations.includes?(a_def.location) - - check_def(previous) if previous && a_def.calls_previous_def? + tally = @used_def_locations[a_def.location] + @tallies[a_def] = tally - @defs << a_def + check_def(previous) if previous && a_def.calls_previous_def? && tally == 0 end private def interested_in(location) From a3bedf968c9c60092ed222686c8f1c5d63b43a3f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 23 Dec 2023 16:50:26 +0800 Subject: [PATCH 0864/1551] Avoid double rounding in `Float#format` for nonnegative `decimal_place` (#14129) --- spec/std/humanize_spec.cr | 8 ++++++++ src/humanize.cr | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/spec/std/humanize_spec.cr b/spec/std/humanize_spec.cr index 841fde6cb2ba..c909417aca36 100644 --- a/spec/std/humanize_spec.cr +++ b/spec/std/humanize_spec.cr @@ -99,6 +99,14 @@ describe Number do assert_prints 1.9999998.format, "1.9999998" assert_prints 1111111.999999998.format, "1,111,111.999999998" end + + it "does not perform double rounding when decimal places are given" do + assert_prints 1.2345.format(decimal_places: 24), "1.234499999999999930722083" + assert_prints 1.2345.format(decimal_places: 65), "1.23449999999999993072208326339023187756538391113281250000000000000" + assert_prints 1.2345.format(decimal_places: 71), "1.23449999999999993072208326339023187756538391113281250000000000000000000" + assert_prints 1.2345.format(decimal_places: 83), "1.23449999999999993072208326339023187756538391113281250000000000000000000000000000000" + assert_prints 1.2345.format(decimal_places: 99), "1.234499999999999930722083263390231877565383911132812500000000000000000000000000000000000000000000000" + end end describe "#humanize" do diff --git a/src/humanize.cr b/src/humanize.cr index e0c69a37b3ad..bb285fe3a07d 100644 --- a/src/humanize.cr +++ b/src/humanize.cr @@ -20,7 +20,7 @@ struct Number def format(io : IO, separator = '.', delimiter = ',', decimal_places : Int? = nil, *, group : Int = 3, only_significant : Bool = false) : Nil number = self # TODO: Optimize implementation for Int - if decimal_places + if decimal_places && (decimal_places < 0 || !number.is_a?(Float)) number = number.round(decimal_places) end From 15010d39dd42bc2c10c2597ca19aac5fd0521898 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 23 Dec 2023 16:50:40 +0800 Subject: [PATCH 0865/1551] Add `ReferenceStorage` for manual allocation of references (#14106) Co-authored-by: Sijawusz Pur Rahnama --- .../semantic/reference_storage_spec.cr | 40 ++++++++++++++ spec/primitives/reference_spec.cr | 4 +- src/compiler/crystal/codegen/llvm_typer.cr | 4 ++ src/compiler/crystal/program.cr | 6 ++- src/compiler/crystal/types.cr | 28 +++++++++- src/prelude.cr | 1 + src/reference_storage.cr | 54 +++++++++++++++++++ 7 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 spec/compiler/semantic/reference_storage_spec.cr create mode 100644 src/reference_storage.cr diff --git a/spec/compiler/semantic/reference_storage_spec.cr b/spec/compiler/semantic/reference_storage_spec.cr new file mode 100644 index 000000000000..791fed054275 --- /dev/null +++ b/spec/compiler/semantic/reference_storage_spec.cr @@ -0,0 +1,40 @@ +require "../../spec_helper" + +describe "Semantic: ReferenceStorage" do + it "errors if T is a struct type" do + assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = Foo (T must be a reference type)" + struct Foo + @x = 1 + end + + ReferenceStorage(Foo) + CRYSTAL + end + + it "errors if T is a value type" do + assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = Int32 (T must be a reference type)" + ReferenceStorage(Int32) + CRYSTAL + end + + it "errors if T is a union type" do + assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = (Bar | Foo) (T must be a reference type)" + class Foo + end + + class Bar + end + + ReferenceStorage(Foo | Bar) + CRYSTAL + end + + it "errors if T is a nilable type" do + assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = (Foo | Nil) (T must be a reference type)" + class Foo + end + + ReferenceStorage(Foo?) + CRYSTAL + end +end diff --git a/spec/primitives/reference_spec.cr b/spec/primitives/reference_spec.cr index b73553748b65..52f179296ee8 100644 --- a/spec/primitives/reference_spec.cr +++ b/spec/primitives/reference_spec.cr @@ -43,9 +43,7 @@ describe "Primitives: reference" do end it "works when address is on the stack" do - # suitably aligned, sufficient storage for type ID + i64 + ptr - # TODO: use `ReferenceStorage` instead - foo_buffer = uninitialized UInt64[3] + foo_buffer = uninitialized ReferenceStorage(Foo) foo = Foo.pre_initialize(pointerof(foo_buffer)) pointerof(foo_buffer).as(typeof(Foo.crystal_instance_type_id)*).value.should eq(Foo.crystal_instance_type_id) foo.str.should eq("abc") diff --git a/src/compiler/crystal/codegen/llvm_typer.cr b/src/compiler/crystal/codegen/llvm_typer.cr index d20fbd59fa9a..d72a25ecd3cb 100644 --- a/src/compiler/crystal/codegen/llvm_typer.cr +++ b/src/compiler/crystal/codegen/llvm_typer.cr @@ -245,6 +245,10 @@ module Crystal llvm_type(type.remove_alias, wants_size) end + private def create_llvm_type(type : ReferenceStorageType, wants_size) + llvm_struct_type(type.reference_type, wants_size) + end + private def create_llvm_type(type : NonGenericModuleType | GenericClassType, wants_size) # This can only be reached if the module or generic class don't have implementors @llvm_context.int1 diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 936fc6a0a270..6d3d185774af 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -197,6 +197,10 @@ module Crystal types["Struct"] = struct_t = @struct_t = NonGenericClassType.new self, self, "Struct", value abstract_value_type(struct_t) + types["ReferenceStorage"] = @reference_storage = reference_storage = GenericReferenceStorageType.new self, self, "ReferenceStorage", value, ["T"] + reference_storage.declare_instance_var("@type_id", int32) + reference_storage.can_be_stored = false + types["Enumerable"] = @enumerable = GenericModuleType.new self, self, "Enumerable", ["T"] types["Indexable"] = @indexable = GenericModuleType.new self, self, "Indexable", ["T"] @@ -493,7 +497,7 @@ module Crystal {% for name in %w(object no_return value number reference void nil bool char int int8 int16 int32 int64 int128 uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol pointer enumerable indexable - array static_array exception tuple named_tuple proc union enum range regex crystal + array static_array reference_storage exception tuple named_tuple proc union enum range regex crystal packed_annotation thread_local_annotation no_inline_annotation always_inline_annotation naked_annotation returns_twice_annotation raises_annotation primitive_annotation call_convention_annotation diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index ad9f3d391fa6..64bcdda9a3d9 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -550,7 +550,7 @@ module Crystal def allows_instance_vars? case self when program.object, program.value, program.struct, - program.reference, program.class_type, + program.reference, program.class_type, program.reference_storage, program.number, program.int, program.float, program.tuple, program.named_tuple, program.pointer, program.static_array, @@ -2642,6 +2642,32 @@ module Crystal end end + # The non-instantiated ReferenceStorage(T) type. + class GenericReferenceStorageType < GenericClassType + @struct = true + + def new_generic_instance(program, generic_type, type_vars) + t = type_vars["T"].type + + unless t.is_a?(TypeParameter) || (t.class? && !t.struct?) + raise TypeException.new "Can't instantiate ReferenceStorage(T) with T = #{t} (T must be a reference type)" + end + + ReferenceStorageType.new program, t + end + end + + class ReferenceStorageType < GenericClassInstanceType + getter reference_type : Type + + def initialize(program, @reference_type) + t_var = Var.new("T", @reference_type) + t_var.bind_to t_var + + super(program, program.reference_storage, program.struct, {"T" => t_var} of String => ASTNode) + end + end + # A lib type, like `lib LibC`. class LibType < ModuleType getter link_annotations : Array(LinkAnnotation)? diff --git a/src/prelude.cr b/src/prelude.cr index f06f5bc87015..f84bb86cb3c1 100644 --- a/src/prelude.cr +++ b/src/prelude.cr @@ -65,6 +65,7 @@ require "raise" require "random" require "range" require "reference" +require "reference_storage" require "regex" require "set" {% unless flag?(:wasm32) %} diff --git a/src/reference_storage.cr b/src/reference_storage.cr new file mode 100644 index 000000000000..4f6e39a8cca0 --- /dev/null +++ b/src/reference_storage.cr @@ -0,0 +1,54 @@ +# a `ReferenceStorage(T)` provides the minimum storage for the instance data of +# an object of type `T`. The compiler guarantees that +# `sizeof(ReferenceStorage(T)) == instance_sizeof(T)` and +# `alignof(ReferenceStorage(T)) == instance_alignof(T)` always hold, which means +# `Pointer(ReferenceStorage(T))` and `T` are binary-compatible. +# +# `T` must be a non-union reference type. +# +# WARNING: `ReferenceStorage` is only necessary for manual memory management, +# such as creating instances of `T` with a non-default allocator. Therefore, +# this type is unsafe and no public constructors are defined. +# +# WARNING: `ReferenceStorage` is unsuitable when instances of `T` require more +# than `instance_sizeof(T)` bytes, such as `String` and `Log::Metadata`. +@[Experimental("This type's API is still under development. Join the discussion about custom reference allocation at [#13481](https://github.com/crystal-lang/crystal/issues/13481).")] +struct ReferenceStorage(T) + private def initialize + end + + # Returns whether `self` and *other* are bytewise equal. + # + # NOTE: This does not call `T#==`, so it works even if `self` or *other* does + # not represent a valid instance of `T`. If validity is guaranteed, call + # `to_reference == other.to_reference` instead to use `T#==`. + def ==(other : ReferenceStorage(T)) : Bool + to_bytes == other.to_bytes + end + + def ==(other) : Bool + false + end + + def hash(hasher) + to_bytes.hash(hasher) + end + + def to_s(io : IO) : Nil + io << "ReferenceStorage(#<" << T << ":0x" + pointerof(@type_id).address.to_s(io, 16) + io << ">)" + end + + # Returns a `T` whose instance data refers to `self`. + # + # WARNING: The caller is responsible for ensuring that the instance data is + # correctly initialized and outlives the returned `T`. + def to_reference : T + pointerof(@type_id).as(T) + end + + protected def to_bytes + Slice.new(pointerof(@type_id).as(UInt8*), instance_sizeof(T)) + end +end From 899ec69a2bc91169e871002063b37fcb6469cb65 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 23 Dec 2023 16:51:02 +0800 Subject: [PATCH 0866/1551] Support `dll` parameter in `@[Link]` (#14131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/compiler/semantic/lib_spec.cr | 50 +++++++++++++++++++++++++- src/annotations.cr | 9 ++++- src/compiler/crystal/codegen/link.cr | 53 ++++++++++++++++++++++++++-- src/compiler/crystal/compiler.cr | 23 ++++++++++++ src/compiler/crystal/ffi/lib_ffi.cr | 3 ++ src/crystal/lib_iconv.cr | 3 ++ src/gc/boehm.cr | 3 ++ src/lib_z/lib_z.cr | 3 ++ src/llvm/lib_llvm.cr | 3 ++ src/openssl/lib_crypto.cr | 4 +++ src/openssl/lib_ssl.cr | 5 +++ src/regex/lib_pcre.cr | 3 ++ src/regex/lib_pcre2.cr | 3 ++ src/xml/libxml2.cr | 3 ++ src/yaml/lib_yaml.cr | 3 ++ 15 files changed, 166 insertions(+), 5 deletions(-) diff --git a/spec/compiler/semantic/lib_spec.cr b/spec/compiler/semantic/lib_spec.cr index 1ee3c7aa99c6..92526402177d 100644 --- a/spec/compiler/semantic/lib_spec.cr +++ b/spec/compiler/semantic/lib_spec.cr @@ -345,7 +345,55 @@ describe "Semantic: lib" do lib LibFoo end ), - "unknown link argument: 'boo' (valid arguments are 'lib', 'ldflags', 'static', 'pkg_config', 'framework', and 'wasm_import_module')" + "unknown link argument: 'boo' (valid arguments are 'lib', 'ldflags', 'static', 'pkg_config', 'framework', 'wasm_import_module', and 'dll')" + end + + it "allows dll argument" do + assert_no_errors <<-CRYSTAL + @[Link(dll: "foo.dll")] + lib LibFoo + end + CRYSTAL + + assert_no_errors <<-CRYSTAL + @[Link(dll: "BAR.DLL")] + lib LibFoo + end + CRYSTAL + end + + it "errors if dll argument contains directory separators" do + assert_error <<-CRYSTAL, "'dll' link argument must not include directory separators" + @[Link(dll: "foo/bar.dll")] + lib LibFoo + end + CRYSTAL + + assert_error <<-CRYSTAL, "'dll' link argument must not include directory separators" + @[Link(dll: %q(foo\\bar.dll))] + lib LibFoo + end + CRYSTAL + end + + it "errors if dll argument does not end with '.dll'" do + assert_error <<-CRYSTAL, "'dll' link argument must use a '.dll' file extension" + @[Link(dll: "foo")] + lib LibFoo + end + CRYSTAL + + assert_error <<-CRYSTAL, "'dll' link argument must use a '.dll' file extension" + @[Link(dll: "foo.dylib")] + lib LibFoo + end + CRYSTAL + + assert_error <<-CRYSTAL, "'dll' link argument must use a '.dll' file extension" + @[Link(dll: "")] + lib LibFoo + end + CRYSTAL end it "errors if lib already specified with positional argument" do diff --git a/src/annotations.cr b/src/annotations.cr index c906f61b9b79..929ed73533f9 100644 --- a/src/annotations.cr +++ b/src/annotations.cr @@ -27,7 +27,7 @@ end annotation Flags end -# A `lib` can be marked with `@[Link(lib : String, *, ldflags : String, framework : String, pkg_config : String)]` +# A `lib` can be marked with `@[Link(lib : String, *, ldflags : String, static : Bool, framework : String, pkg_config : String, wasm_import_module : String, dll : String)]` # to declare the library that should be linked when compiling the program. # # At least one of the *lib*, *ldflags*, *framework* arguments needs to be specified. @@ -45,6 +45,13 @@ end # # `@[Link(framework: "Cocoa")]` will pass `-framework Cocoa` to the linker. # +# `@[Link(dll: "gc.dll")]` will copy `gc.dll` to any built program. The DLL name +# must use `.dll` as its file extension and cannot contain any directory +# separators. The actual DLL is searched among `CRYSTAL_LIBRARY_PATH`, the +# compiler's own directory, and `PATH` in that order; a warning is printed if +# the DLL isn't found, although it might still run correctly if the DLLs are +# available in other DLL search paths on the system. +# # When an `-l` option is passed to the linker, it will lookup the libraries in # paths passed with the `-L` option. Any paths in `CRYSTAL_LIBRARY_PATH` are # added by default. Custom paths can be passed using `ldflags`: diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index 7ef2ab85f92c..cc703ec81eef 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -5,8 +5,9 @@ module Crystal getter ldflags : String? getter framework : String? getter wasm_import_module : String? + getter dll : String? - def initialize(@lib = nil, @pkg_config = @lib, @ldflags = nil, @static = false, @framework = nil, @wasm_import_module = nil) + def initialize(@lib = nil, @pkg_config = @lib, @ldflags = nil, @static = false, @framework = nil, @wasm_import_module = nil, @dll = nil) end def static? @@ -27,6 +28,7 @@ module Crystal lib_pkg_config = nil lib_framework = nil lib_wasm_import_module = nil + lib_dll = nil count = 0 args.each do |arg| @@ -76,12 +78,21 @@ module Crystal when "wasm_import_module" named_arg.raise "'wasm_import_module' link argument must be a String" unless value.is_a?(StringLiteral) lib_wasm_import_module = value.value + when "dll" + named_arg.raise "'dll' link argument must be a String" unless value.is_a?(StringLiteral) + lib_dll = value.value + unless lib_dll.size >= 4 && lib_dll[-4..].compare(".dll", case_insensitive: true) == 0 + named_arg.raise "'dll' link argument must use a '.dll' file extension" + end + if ::Path.separators(::Path::Kind::WINDOWS).any? { |separator| lib_dll.includes?(separator) } + named_arg.raise "'dll' link argument must not include directory separators" + end else - named_arg.raise "unknown link argument: '#{named_arg.name}' (valid arguments are 'lib', 'ldflags', 'static', 'pkg_config', 'framework', and 'wasm_import_module')" + named_arg.raise "unknown link argument: '#{named_arg.name}' (valid arguments are 'lib', 'ldflags', 'static', 'pkg_config', 'framework', 'wasm_import_module', and 'dll')" end end - new(lib_name, lib_pkg_config, lib_ldflags, lib_static, lib_framework, lib_wasm_import_module) + new(lib_name, lib_pkg_config, lib_ldflags, lib_static, lib_framework, lib_wasm_import_module, lib_dll) end end @@ -221,6 +232,42 @@ module Crystal flags.join(" ") end + def each_dll_path(& : String, Bool ->) + executable_path = nil + compiler_origin = nil + paths = nil + + link_annotations.each do |ann| + next unless dll = ann.dll + + dll_path = CrystalLibraryPath.paths.each do |path| + full_path = File.join(path, dll) + break full_path if File.file?(full_path) + end + + unless dll_path + executable_path ||= Process.executable_path + compiler_origin ||= File.dirname(executable_path) if executable_path + + if compiler_origin + full_path = File.join(compiler_origin, dll) + dll_path = full_path if File.file?(full_path) + end + end + + unless dll_path + paths ||= ENV["PATH"]?.try &.split(Process::PATH_DELIMITER, remove_empty: true) + + dll_path = paths.try &.each do |path| + full_path = File.join(path, dll) + break full_path if File.file?(full_path) + end + end + + yield dll_path || dll, !dll_path.nil? + end + end + PKG_CONFIG_PATH = Process.find_executable("pkg-config") # Returns the result of running `pkg-config mod` but returns nil if diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 237783c2ed8c..283f44289468 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -321,6 +321,10 @@ module Crystal {% if flag?(:darwin) %} run_dsymutil(output_filename) unless debug.none? {% end %} + + {% if flag?(:windows) %} + copy_dlls(program, output_filename) if program.has_flag?("preview_dll") + {% end %} end CacheDir.instance.cleanup if @cleanup @@ -345,6 +349,25 @@ module Crystal end end + private def copy_dlls(program, output_filename) + not_found = nil + output_directory = File.dirname(output_filename) + + program.each_dll_path do |path, found| + if found + FileUtils.cp(path, output_directory) + else + not_found ||= [] of String + not_found << path + end + end + + if not_found + stderr << "Warning: The following DLLs are required at run time, but Crystal is unable to locate them in CRYSTAL_LIBRARY_PATH, the compiler's directory, or PATH: " + not_found.sort!.join(stderr, ", ") + end + end + private def cross_compile(program, units, output_filename) unit = units.first llvm_mod = unit.llvm_mod diff --git a/src/compiler/crystal/ffi/lib_ffi.cr b/src/compiler/crystal/ffi/lib_ffi.cr index 699a0c125468..97163c989ee5 100644 --- a/src/compiler/crystal/ffi/lib_ffi.cr +++ b/src/compiler/crystal/ffi/lib_ffi.cr @@ -1,5 +1,8 @@ module Crystal @[Link("ffi")] + {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + @[Link(dll: "libffi.dll")] + {% end %} lib LibFFI {% begin %} enum ABI diff --git a/src/crystal/lib_iconv.cr b/src/crystal/lib_iconv.cr index a180a770a67a..5f1506758454 100644 --- a/src/crystal/lib_iconv.cr +++ b/src/crystal/lib_iconv.cr @@ -5,6 +5,9 @@ require "c/stddef" {% end %} @[Link("iconv")] +{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + @[Link(dll: "libiconv.dll")] +{% end %} lib LibIconv type IconvT = Void* diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index b4dfb0061900..6c1ff5020cbf 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -25,6 +25,9 @@ @[Link("gc")] {% end %} +{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + @[Link(dll: "gc.dll")] +{% end %} lib LibGC alias Int = LibC::Int alias SizeT = LibC::SizeT diff --git a/src/lib_z/lib_z.cr b/src/lib_z/lib_z.cr index e261cde29683..1c88cb67bba8 100644 --- a/src/lib_z/lib_z.cr +++ b/src/lib_z/lib_z.cr @@ -1,4 +1,7 @@ @[Link("z")] +{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + @[Link(dll: "zlib1.dll")] +{% end %} lib LibZ alias Char = LibC::Char alias Int = LibC::Int diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index ee18eddfbce3..d5e7c2488002 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -15,6 +15,9 @@ {% llvm_ldflags = lines[2] %} @[Link("llvm")] + {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + @[Link(dll: "LLVM-C.dll")] + {% end %} lib LibLLVM end {% else %} diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr index c783aa7903a9..caca9c11c520 100644 --- a/src/openssl/lib_crypto.cr +++ b/src/openssl/lib_crypto.cr @@ -36,6 +36,10 @@ {% else %} @[Link(ldflags: "`command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'`")] {% end %} +{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + # TODO: if someone brings their own OpenSSL 1.x.y on Windows, will this have a different name? + @[Link(dll: "libcrypto-3-x64.dll")] +{% end %} lib LibCrypto alias Char = LibC::Char alias Int = LibC::Int diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr index 37a4cea3a161..27faf9bbe185 100644 --- a/src/openssl/lib_ssl.cr +++ b/src/openssl/lib_ssl.cr @@ -43,6 +43,11 @@ require "./lib_crypto" {% else %} @[Link(ldflags: "`command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'`")] {% end %} +{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + # TODO: if someone brings their own OpenSSL 1.x.y on Windows, will this have a different name? + @[Link(dll: "libssl-3-x64.dll")] + @[Link(dll: "libcrypto-3-x64.dll")] +{% end %} lib LibSSL alias Int = LibC::Int alias Char = LibC::Char diff --git a/src/regex/lib_pcre.cr b/src/regex/lib_pcre.cr index 5f110eba0ce7..647a172a9d43 100644 --- a/src/regex/lib_pcre.cr +++ b/src/regex/lib_pcre.cr @@ -1,4 +1,7 @@ @[Link("pcre")] +{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + @[Link(dll: "pcre.dll")] +{% end %} lib LibPCRE alias Int = LibC::Int diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index a04761cbe07e..71a0fd4b6639 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -1,4 +1,7 @@ @[Link("pcre2-8")] +{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + @[Link(dll: "pcre2-8.dll")] +{% end %} lib LibPCRE2 alias Int = LibC::Int diff --git a/src/xml/libxml2.cr b/src/xml/libxml2.cr index 68c9943425d2..3dff7fb6cb40 100644 --- a/src/xml/libxml2.cr +++ b/src/xml/libxml2.cr @@ -5,6 +5,9 @@ require "./html_parser_options" require "./save_options" @[Link("xml2", pkg_config: "libxml-2.0")] +{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + @[Link(dll: "libxml2.dll")] +{% end %} lib LibXML alias Int = LibC::Int diff --git a/src/yaml/lib_yaml.cr b/src/yaml/lib_yaml.cr index 4c0d329f8f36..0b4248afc793 100644 --- a/src/yaml/lib_yaml.cr +++ b/src/yaml/lib_yaml.cr @@ -1,6 +1,9 @@ require "./enums" @[Link("yaml", pkg_config: "yaml-0.1")] +{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + @[Link(dll: "yaml.dll")] +{% end %} lib LibYAML alias Int = LibC::Int From 16864334ab92a0e88942b67c68a19ad7a7703e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 24 Dec 2023 10:41:15 +0100 Subject: [PATCH 0867/1551] Update shards 0.17.4 (#14133) --- .github/workflows/win_build_portable.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 18eea4f1f317..21d849c2dcf0 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -120,7 +120,7 @@ jobs: uses: actions/checkout@v4 with: repository: crystal-lang/shards - ref: v0.17.3 + ref: v0.17.4 path: shards - name: Download molinillo release From af31a4e84869e53f1b2a906be4399884936fc1bd Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 24 Dec 2023 17:41:24 +0800 Subject: [PATCH 0868/1551] Simplify `String::Formatter` when Ryu Printf is available (#14132) --- spec/std/float_printer/ryu_printf_spec.cr | 3 +- spec/std/sprintf_spec.cr | 1452 +++++++++++---------- src/float/printer/ryu_printf.cr | 3 +- src/float/printer/ryu_printf_table.cr | 3 +- src/string/formatter.cr | 193 +-- 5 files changed, 838 insertions(+), 816 deletions(-) diff --git a/spec/std/float_printer/ryu_printf_spec.cr b/spec/std/float_printer/ryu_printf_spec.cr index 4610485f2270..02ac2944a37d 100644 --- a/spec/std/float_printer/ryu_printf_spec.cr +++ b/spec/std/float_printer/ryu_printf_spec.cr @@ -1,5 +1,4 @@ -# FIXME: this leads to an OOB on wasm32 (#13918) -{% skip_file if flag?(:wasm32) %} +{% skip_file unless String::Formatter::HAS_RYU_PRINTF %} # This file contains test cases derived from: # diff --git a/spec/std/sprintf_spec.cr b/spec/std/sprintf_spec.cr index d2da4369827b..67cc1ac1604c 100644 --- a/spec/std/sprintf_spec.cr +++ b/spec/std/sprintf_spec.cr @@ -378,798 +378,802 @@ describe "::sprintf" do assert_sprintf "1\u{0}%i\u{0}3", 2, "1\u00002\u00003" end - describe "floats" do - context "fixed format" do - it "works" do - assert_sprintf "%f", 123, "123.000000" - - assert_sprintf "%12f", 123.45, " 123.450000" - assert_sprintf "%-12f", 123.45, "123.450000 " - assert_sprintf "% f", 123.45, " 123.450000" - assert_sprintf "%+f", 123, "+123.000000" - assert_sprintf "%012f", 123, "00123.000000" - assert_sprintf "%.f", 1234.56, "1235" - assert_sprintf "%.2f", 1234.5678, "1234.57" - assert_sprintf "%10.2f", 1234.5678, " 1234.57" - assert_sprintf "%*.2f", [10, 1234.5678], " 1234.57" - assert_sprintf "%*.*f", [10, 2, 1234.5678], " 1234.57" - assert_sprintf "%.2f", 2.536_f32, "2.54" - assert_sprintf "%+0*.*f", [10, 2, 2.536_f32], "+000002.54" - assert_sprintf "%#.0f", 1234.56, "1235." - assert_sprintf "%#.1f", 1234.56, "1234.6" - - expect_raises(ArgumentError, "Expected dynamic value '*' to be an Int - \"not a number\" (String)") do - sprintf("%*f", ["not a number", 2.536_f32]) + if String::Formatter::HAS_RYU_PRINTF + describe "floats" do + context "fixed format" do + it "works" do + assert_sprintf "%f", 123, "123.000000" + + assert_sprintf "%12f", 123.45, " 123.450000" + assert_sprintf "%-12f", 123.45, "123.450000 " + assert_sprintf "% f", 123.45, " 123.450000" + assert_sprintf "%+f", 123, "+123.000000" + assert_sprintf "%012f", 123, "00123.000000" + assert_sprintf "%.f", 1234.56, "1235" + assert_sprintf "%.2f", 1234.5678, "1234.57" + assert_sprintf "%10.2f", 1234.5678, " 1234.57" + assert_sprintf "%*.2f", [10, 1234.5678], " 1234.57" + assert_sprintf "%*.*f", [10, 2, 1234.5678], " 1234.57" + assert_sprintf "%.2f", 2.536_f32, "2.54" + assert_sprintf "%+0*.*f", [10, 2, 2.536_f32], "+000002.54" + assert_sprintf "%#.0f", 1234.56, "1235." + assert_sprintf "%#.1f", 1234.56, "1234.6" + + expect_raises(ArgumentError, "Expected dynamic value '*' to be an Int - \"not a number\" (String)") do + sprintf("%*f", ["not a number", 2.536_f32]) + end + + assert_sprintf "%12.2f %12.2f %6.2f %.2f", [2.0, 3.0, 4.0, 5.0], " 2.00 3.00 4.00 5.00" + + assert_sprintf "%f", 1e15, "1000000000000000.000000" end - - assert_sprintf "%12.2f %12.2f %6.2f %.2f", [2.0, 3.0, 4.0, 5.0], " 2.00 3.00 4.00 5.00" - - assert_sprintf "%f", 1e15, "1000000000000000.000000" - end - end - - context "scientific format" do - it "works" do - assert_sprintf "%e", 123.45, "1.234500e+2" - assert_sprintf "%E", 123.45, "1.234500E+2" - - assert_sprintf "%e", Float64::MAX, "1.797693e+308" - assert_sprintf "%e", Float64::MIN_POSITIVE, "2.225074e-308" - assert_sprintf "%e", Float64::MIN_SUBNORMAL, "4.940656e-324" - assert_sprintf "%e", 0.0, "0.000000e+0" - assert_sprintf "%e", -0.0, "-0.000000e+0" - assert_sprintf "%e", -Float64::MIN_SUBNORMAL, "-4.940656e-324" - assert_sprintf "%e", -Float64::MIN_POSITIVE, "-2.225074e-308" - assert_sprintf "%e", Float64::MIN, "-1.797693e+308" end - context "width specifier" do - it "sets the minimum length of the string" do - assert_sprintf "%20e", 123.45, " 1.234500e+2" - assert_sprintf "%20e", -123.45, " -1.234500e+2" - assert_sprintf "%+20e", 123.45, " +1.234500e+2" - - assert_sprintf "%12e", 123.45, " 1.234500e+2" - assert_sprintf "%12e", -123.45, "-1.234500e+2" - assert_sprintf "%+12e", 123.45, "+1.234500e+2" - - assert_sprintf "%11e", 123.45, "1.234500e+2" - assert_sprintf "%11e", -123.45, "-1.234500e+2" - assert_sprintf "%+11e", 123.45, "+1.234500e+2" - - assert_sprintf "%2e", 123.45, "1.234500e+2" - assert_sprintf "%2e", -123.45, "-1.234500e+2" - assert_sprintf "%+2e", 123.45, "+1.234500e+2" + context "scientific format" do + it "works" do + assert_sprintf "%e", 123.45, "1.234500e+2" + assert_sprintf "%E", 123.45, "1.234500E+2" + + assert_sprintf "%e", Float64::MAX, "1.797693e+308" + assert_sprintf "%e", Float64::MIN_POSITIVE, "2.225074e-308" + assert_sprintf "%e", Float64::MIN_SUBNORMAL, "4.940656e-324" + assert_sprintf "%e", 0.0, "0.000000e+0" + assert_sprintf "%e", -0.0, "-0.000000e+0" + assert_sprintf "%e", -Float64::MIN_SUBNORMAL, "-4.940656e-324" + assert_sprintf "%e", -Float64::MIN_POSITIVE, "-2.225074e-308" + assert_sprintf "%e", Float64::MIN, "-1.797693e+308" end - it "left-justifies on negative width" do - assert_sprintf "%*e", [-20, 123.45], "1.234500e+2 " - end - end + context "width specifier" do + it "sets the minimum length of the string" do + assert_sprintf "%20e", 123.45, " 1.234500e+2" + assert_sprintf "%20e", -123.45, " -1.234500e+2" + assert_sprintf "%+20e", 123.45, " +1.234500e+2" - context "precision specifier" do - it "sets the minimum length of the fractional part" do - assert_sprintf "%.0e", 2.0, "2e+0" - assert_sprintf "%.0e", 2.5.prev_float, "2e+0" - assert_sprintf "%.0e", 2.5, "2e+0" - assert_sprintf "%.0e", 2.5.next_float, "3e+0" - assert_sprintf "%.0e", 3.0, "3e+0" - assert_sprintf "%.0e", 3.5.prev_float, "3e+0" - assert_sprintf "%.0e", 3.5, "4e+0" - assert_sprintf "%.0e", 3.5.next_float, "4e+0" - assert_sprintf "%.0e", 4.0, "4e+0" - - assert_sprintf "%.0e", 9.5, "1e+1" - - assert_sprintf "%.100e", 1.1, "1.1000000000000000888178419700125232338905334472656250000000000000000000000000000000000000000000000000e+0" - - assert_sprintf "%.10000e", 1.0, "1.#{"0" * 10000}e+0" - - assert_sprintf "%.1000e", Float64::MIN_POSITIVE.prev_float, - "2.2250738585072008890245868760858598876504231122409594654935248025624400092282356951" \ - "787758888037591552642309780950434312085877387158357291821993020294379224223559819827" \ - "501242041788969571311791082261043971979604000454897391938079198936081525613113376149" \ - "842043271751033627391549782731594143828136275113838604094249464942286316695429105080" \ - "201815926642134996606517803095075913058719846423906068637102005108723282784678843631" \ - "944515866135041223479014792369585208321597621066375401613736583044193603714778355306" \ - "682834535634005074073040135602968046375918583163124224521599262546494300836851861719" \ - "422417646455137135420132217031370496583210154654068035397417906022589503023501937519" \ - "773030945763173210852507299305089761582519159720757232455434770912461317493580281734" \ - "466552734375000000000000000000000000000000000000000000000000000000000000000000000000" \ - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \ - "000000000000000000000000000000000000000000000000000000000000000000000000000000e-308" - end + assert_sprintf "%12e", 123.45, " 1.234500e+2" + assert_sprintf "%12e", -123.45, "-1.234500e+2" + assert_sprintf "%+12e", 123.45, "+1.234500e+2" - it "can be used with width" do - assert_sprintf "%20.12e", 123.45, " 1.234500000000e+2" - assert_sprintf "%20.12e", -123.45, " -1.234500000000e+2" - assert_sprintf "%20.12e", 0.0, " 0.000000000000e+0" + assert_sprintf "%11e", 123.45, "1.234500e+2" + assert_sprintf "%11e", -123.45, "-1.234500e+2" + assert_sprintf "%+11e", 123.45, "+1.234500e+2" - assert_sprintf "%-20.12e", 123.45, "1.234500000000e+2 " - assert_sprintf "%-20.12e", -123.45, "-1.234500000000e+2 " - assert_sprintf "%-20.12e", 0.0, "0.000000000000e+0 " - - assert_sprintf "%8.12e", 123.45, "1.234500000000e+2" - assert_sprintf "%8.12e", -123.45, "-1.234500000000e+2" - assert_sprintf "%8.12e", 0.0, "0.000000000000e+0" - end - - it "is ignored if precision argument is negative" do - assert_sprintf "%.*e", [-2, 123.45], "1.234500e+2" - end - end - - context "sharp flag" do - it "prints a decimal point even if no digits follow" do - assert_sprintf "%#.0e", 1.0, "1.e+0" - assert_sprintf "%#.0e", 10000.0, "1.e+4" - assert_sprintf "%#.0e", 1.0e+23, "1.e+23" - assert_sprintf "%#.0e", 1.0e-100, "1.e-100" - assert_sprintf "%#.0e", 0.0, "0.e+0" - assert_sprintf "%#.0e", -0.0, "-0.e+0" - end - end - - context "plus flag" do - it "writes a plus sign for positive values" do - assert_sprintf "%+e", 123.45, "+1.234500e+2" - assert_sprintf "%+e", -123.45, "-1.234500e+2" - assert_sprintf "%+e", 0.0, "+0.000000e+0" - end - - it "writes plus sign after left space-padding" do - assert_sprintf "%+20e", 123.45, " +1.234500e+2" - assert_sprintf "%+20e", -123.45, " -1.234500e+2" - assert_sprintf "%+20e", 0.0, " +0.000000e+0" - end - - it "writes plus sign before left zero-padding" do - assert_sprintf "%+020e", 123.45, "+000000001.234500e+2" - assert_sprintf "%+020e", -123.45, "-000000001.234500e+2" - assert_sprintf "%+020e", 0.0, "+000000000.000000e+0" - end - end - - context "space flag" do - it "writes a space for positive values" do - assert_sprintf "% e", 123.45, " 1.234500e+2" - assert_sprintf "% e", -123.45, "-1.234500e+2" - assert_sprintf "% e", 0.0, " 0.000000e+0" - end - - it "writes space before left space-padding" do - assert_sprintf "% 20e", 123.45, " 1.234500e+2" - assert_sprintf "% 20e", -123.45, " -1.234500e+2" - assert_sprintf "% 20e", 0.0, " 0.000000e+0" - - assert_sprintf "% 020e", 123.45, " 000000001.234500e+2" - assert_sprintf "% 020e", -123.45, "-000000001.234500e+2" - assert_sprintf "% 020e", 0.0, " 000000000.000000e+0" - end - - it "is ignored if plus flag is also specified" do - assert_sprintf "% +e", 123.45, "+1.234500e+2" - assert_sprintf "%+ e", -123.45, "-1.234500e+2" - end - end - - context "zero flag" do - it "left-pads the result with zeros" do - assert_sprintf "%020e", 123.45, "0000000001.234500e+2" - assert_sprintf "%020e", -123.45, "-000000001.234500e+2" - assert_sprintf "%020e", 0.0, "0000000000.000000e+0" - end - - it "is ignored if string is left-justified" do - assert_sprintf "%-020e", 123.45, "1.234500e+2 " - assert_sprintf "%-020e", -123.45, "-1.234500e+2 " - assert_sprintf "%-020e", 0.0, "0.000000e+0 " - end - - it "can be used with precision" do - assert_sprintf "%020.12e", 123.45, "0001.234500000000e+2" - assert_sprintf "%020.12e", -123.45, "-001.234500000000e+2" - assert_sprintf "%020.12e", 0.0, "0000.000000000000e+0" - end - end + assert_sprintf "%2e", 123.45, "1.234500e+2" + assert_sprintf "%2e", -123.45, "-1.234500e+2" + assert_sprintf "%+2e", 123.45, "+1.234500e+2" + end - context "minus flag" do - it "left-justifies the string" do - assert_sprintf "%-20e", 123.45, "1.234500e+2 " - assert_sprintf "%-20e", -123.45, "-1.234500e+2 " - assert_sprintf "%-20e", 0.0, "0.000000e+0 " + it "left-justifies on negative width" do + assert_sprintf "%*e", [-20, 123.45], "1.234500e+2 " + end end - end - end - - context "general format" do - it "works" do - assert_sprintf "%g", 123.45, "123.45" - assert_sprintf "%G", 123.45, "123.45" - - assert_sprintf "%g", 1.2345e-5, "1.2345e-5" - assert_sprintf "%G", 1.2345e-5, "1.2345E-5" - - assert_sprintf "%g", 1.2345e+25, "1.2345e+25" - assert_sprintf "%G", 1.2345e+25, "1.2345E+25" - - assert_sprintf "%g", Float64::MAX, "1.79769e+308" - assert_sprintf "%g", Float64::MIN_POSITIVE, "2.22507e-308" - assert_sprintf "%g", Float64::MIN_SUBNORMAL, "4.94066e-324" - assert_sprintf "%g", 0.0, "0" - assert_sprintf "%g", -0.0, "-0" - assert_sprintf "%g", -Float64::MIN_SUBNORMAL, "-4.94066e-324" - assert_sprintf "%g", -Float64::MIN_POSITIVE, "-2.22507e-308" - assert_sprintf "%g", Float64::MIN, "-1.79769e+308" - end - - context "width specifier" do - it "sets the minimum length of the string" do - assert_sprintf "%10g", 123.45, " 123.45" - assert_sprintf "%10g", -123.45, " -123.45" - assert_sprintf "%+10g", 123.45, " +123.45" - - assert_sprintf "%7g", 123.45, " 123.45" - assert_sprintf "%7g", -123.45, "-123.45" - assert_sprintf "%+7g", 123.45, "+123.45" - assert_sprintf "%6g", 123.45, "123.45" - assert_sprintf "%6g", -123.45, "-123.45" - assert_sprintf "%+6g", 123.45, "+123.45" - - assert_sprintf "%2g", 123.45, "123.45" - assert_sprintf "%2g", -123.45, "-123.45" - assert_sprintf "%+2g", 123.45, "+123.45" + context "precision specifier" do + it "sets the minimum length of the fractional part" do + assert_sprintf "%.0e", 2.0, "2e+0" + assert_sprintf "%.0e", 2.5.prev_float, "2e+0" + assert_sprintf "%.0e", 2.5, "2e+0" + assert_sprintf "%.0e", 2.5.next_float, "3e+0" + assert_sprintf "%.0e", 3.0, "3e+0" + assert_sprintf "%.0e", 3.5.prev_float, "3e+0" + assert_sprintf "%.0e", 3.5, "4e+0" + assert_sprintf "%.0e", 3.5.next_float, "4e+0" + assert_sprintf "%.0e", 4.0, "4e+0" + + assert_sprintf "%.0e", 9.5, "1e+1" + + assert_sprintf "%.100e", 1.1, "1.1000000000000000888178419700125232338905334472656250000000000000000000000000000000000000000000000000e+0" + + assert_sprintf "%.10000e", 1.0, "1.#{"0" * 10000}e+0" + + assert_sprintf "%.1000e", Float64::MIN_POSITIVE.prev_float, + "2.2250738585072008890245868760858598876504231122409594654935248025624400092282356951" \ + "787758888037591552642309780950434312085877387158357291821993020294379224223559819827" \ + "501242041788969571311791082261043971979604000454897391938079198936081525613113376149" \ + "842043271751033627391549782731594143828136275113838604094249464942286316695429105080" \ + "201815926642134996606517803095075913058719846423906068637102005108723282784678843631" \ + "944515866135041223479014792369585208321597621066375401613736583044193603714778355306" \ + "682834535634005074073040135602968046375918583163124224521599262546494300836851861719" \ + "422417646455137135420132217031370496583210154654068035397417906022589503023501937519" \ + "773030945763173210852507299305089761582519159720757232455434770912461317493580281734" \ + "466552734375000000000000000000000000000000000000000000000000000000000000000000000000" \ + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \ + "000000000000000000000000000000000000000000000000000000000000000000000000000000e-308" + end + + it "can be used with width" do + assert_sprintf "%20.12e", 123.45, " 1.234500000000e+2" + assert_sprintf "%20.12e", -123.45, " -1.234500000000e+2" + assert_sprintf "%20.12e", 0.0, " 0.000000000000e+0" + + assert_sprintf "%-20.12e", 123.45, "1.234500000000e+2 " + assert_sprintf "%-20.12e", -123.45, "-1.234500000000e+2 " + assert_sprintf "%-20.12e", 0.0, "0.000000000000e+0 " + + assert_sprintf "%8.12e", 123.45, "1.234500000000e+2" + assert_sprintf "%8.12e", -123.45, "-1.234500000000e+2" + assert_sprintf "%8.12e", 0.0, "0.000000000000e+0" + end + + it "is ignored if precision argument is negative" do + assert_sprintf "%.*e", [-2, 123.45], "1.234500e+2" + end end - it "left-justifies on negative width" do - assert_sprintf "%*g", [-10, 123.45], "123.45 " + context "sharp flag" do + it "prints a decimal point even if no digits follow" do + assert_sprintf "%#.0e", 1.0, "1.e+0" + assert_sprintf "%#.0e", 10000.0, "1.e+4" + assert_sprintf "%#.0e", 1.0e+23, "1.e+23" + assert_sprintf "%#.0e", 1.0e-100, "1.e-100" + assert_sprintf "%#.0e", 0.0, "0.e+0" + assert_sprintf "%#.0e", -0.0, "-0.e+0" + end end - end - context "precision specifier" do - it "sets the precision of the value" do - assert_sprintf "%.0g", 123.45, "1e+2" - assert_sprintf "%.1g", 123.45, "1e+2" - assert_sprintf "%.2g", 123.45, "1.2e+2" - assert_sprintf "%.3g", 123.45, "123" - assert_sprintf "%.4g", 123.45, "123.5" - assert_sprintf "%.5g", 123.45, "123.45" - assert_sprintf "%.6g", 123.45, "123.45" - assert_sprintf "%.7g", 123.45, "123.45" - assert_sprintf "%.8g", 123.45, "123.45" - - assert_sprintf "%.1000g", 123.45, "123.4500000000000028421709430404007434844970703125" - - assert_sprintf "%.0g", 1.23e-45, "1e-45" - assert_sprintf "%.1g", 1.23e-45, "1e-45" - assert_sprintf "%.2g", 1.23e-45, "1.2e-45" - assert_sprintf "%.3g", 1.23e-45, "1.23e-45" - assert_sprintf "%.4g", 1.23e-45, "1.23e-45" - assert_sprintf "%.5g", 1.23e-45, "1.23e-45" - assert_sprintf "%.6g", 1.23e-45, "1.23e-45" - - assert_sprintf "%.1000g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125e-5" + context "plus flag" do + it "writes a plus sign for positive values" do + assert_sprintf "%+e", 123.45, "+1.234500e+2" + assert_sprintf "%+e", -123.45, "-1.234500e+2" + assert_sprintf "%+e", 0.0, "+0.000000e+0" + end + + it "writes plus sign after left space-padding" do + assert_sprintf "%+20e", 123.45, " +1.234500e+2" + assert_sprintf "%+20e", -123.45, " -1.234500e+2" + assert_sprintf "%+20e", 0.0, " +0.000000e+0" + end + + it "writes plus sign before left zero-padding" do + assert_sprintf "%+020e", 123.45, "+000000001.234500e+2" + assert_sprintf "%+020e", -123.45, "-000000001.234500e+2" + assert_sprintf "%+020e", 0.0, "+000000000.000000e+0" + end end - it "can be used with width" do - assert_sprintf "%10.1g", 123.45, " 1e+2" - assert_sprintf "%10.2g", 123.45, " 1.2e+2" - assert_sprintf "%10.3g", 123.45, " 123" - assert_sprintf "%10.4g", 123.45, " 123.5" - assert_sprintf "%10.5g", 123.45, " 123.45" - assert_sprintf "%10.1g", -123.45, " -1e+2" - assert_sprintf "%10.2g", -123.45, " -1.2e+2" - assert_sprintf "%10.3g", -123.45, " -123" - assert_sprintf "%10.4g", -123.45, " -123.5" - assert_sprintf "%10.5g", -123.45, " -123.45" - assert_sprintf "%10.5g", 0, " 0" - - assert_sprintf "%-10.1g", 123.45, "1e+2 " - assert_sprintf "%-10.2g", 123.45, "1.2e+2 " - assert_sprintf "%-10.3g", 123.45, "123 " - assert_sprintf "%-10.4g", 123.45, "123.5 " - assert_sprintf "%-10.5g", 123.45, "123.45 " - assert_sprintf "%-10.1g", -123.45, "-1e+2 " - assert_sprintf "%-10.2g", -123.45, "-1.2e+2 " - assert_sprintf "%-10.3g", -123.45, "-123 " - assert_sprintf "%-10.4g", -123.45, "-123.5 " - assert_sprintf "%-10.5g", -123.45, "-123.45 " - assert_sprintf "%-10.5g", 0, "0 " - - assert_sprintf "%3.1g", 123.45, "1e+2" - assert_sprintf "%3.2g", 123.45, "1.2e+2" - assert_sprintf "%3.3g", 123.45, "123" - assert_sprintf "%3.4g", 123.45, "123.5" - assert_sprintf "%3.5g", 123.45, "123.45" - assert_sprintf "%3.1g", -123.45, "-1e+2" - assert_sprintf "%3.2g", -123.45, "-1.2e+2" - assert_sprintf "%3.3g", -123.45, "-123" - assert_sprintf "%3.4g", -123.45, "-123.5" - assert_sprintf "%3.5g", -123.45, "-123.45" - - assert_sprintf "%1000.800g", 123.45, "#{" " * 950}123.4500000000000028421709430404007434844970703125" + context "space flag" do + it "writes a space for positive values" do + assert_sprintf "% e", 123.45, " 1.234500e+2" + assert_sprintf "% e", -123.45, "-1.234500e+2" + assert_sprintf "% e", 0.0, " 0.000000e+0" + end + + it "writes space before left space-padding" do + assert_sprintf "% 20e", 123.45, " 1.234500e+2" + assert_sprintf "% 20e", -123.45, " -1.234500e+2" + assert_sprintf "% 20e", 0.0, " 0.000000e+0" + + assert_sprintf "% 020e", 123.45, " 000000001.234500e+2" + assert_sprintf "% 020e", -123.45, "-000000001.234500e+2" + assert_sprintf "% 020e", 0.0, " 000000000.000000e+0" + end + + it "is ignored if plus flag is also specified" do + assert_sprintf "% +e", 123.45, "+1.234500e+2" + assert_sprintf "%+ e", -123.45, "-1.234500e+2" + end end - it "is ignored if precision argument is negative" do - assert_sprintf "%.*g", [-2, 123.45], "123.45" + context "zero flag" do + it "left-pads the result with zeros" do + assert_sprintf "%020e", 123.45, "0000000001.234500e+2" + assert_sprintf "%020e", -123.45, "-000000001.234500e+2" + assert_sprintf "%020e", 0.0, "0000000000.000000e+0" + end + + it "is ignored if string is left-justified" do + assert_sprintf "%-020e", 123.45, "1.234500e+2 " + assert_sprintf "%-020e", -123.45, "-1.234500e+2 " + assert_sprintf "%-020e", 0.0, "0.000000e+0 " + end + + it "can be used with precision" do + assert_sprintf "%020.12e", 123.45, "0001.234500000000e+2" + assert_sprintf "%020.12e", -123.45, "-001.234500000000e+2" + assert_sprintf "%020.12e", 0.0, "0000.000000000000e+0" + end end - end - context "sharp flag" do - it "prints decimal point and trailing zeros" do - assert_sprintf "%#.0g", 12345, "1.e+4" - assert_sprintf "%#.6g", 12345, "12345.0" - assert_sprintf "%#.10g", 12345, "12345.00000" - assert_sprintf "%#.100g", 12345, "12345.#{"0" * 95}" - assert_sprintf "%#.1000g", 12345, "12345.#{"0" * 995}" - - assert_sprintf "%#.0g", 1e-5, "1.e-5" - assert_sprintf "%#.6g", 1e-5, "1.00000e-5" - assert_sprintf "%#.10g", 1e-5, "1.000000000e-5" - assert_sprintf "%#.100g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125#{"0" * 35}e-5" - assert_sprintf "%#.1000g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125#{"0" * 935}e-5" - - assert_sprintf "%#15.0g", 12345, " 1.e+4" - assert_sprintf "%#15.6g", 12345, " 12345.0" - assert_sprintf "%#15.10g", 12345, " 12345.00000" + context "minus flag" do + it "left-justifies the string" do + assert_sprintf "%-20e", 123.45, "1.234500e+2 " + assert_sprintf "%-20e", -123.45, "-1.234500e+2 " + assert_sprintf "%-20e", 0.0, "0.000000e+0 " + end end end - context "plus flag" do - it "writes a plus sign for positive values" do - assert_sprintf "%+g", 123.45, "+123.45" - assert_sprintf "%+g", -123.45, "-123.45" - assert_sprintf "%+g", 0.0, "+0" + context "general format" do + it "works" do + assert_sprintf "%g", 123.45, "123.45" + assert_sprintf "%G", 123.45, "123.45" + + assert_sprintf "%g", 1.2345e-5, "1.2345e-5" + assert_sprintf "%G", 1.2345e-5, "1.2345E-5" + + assert_sprintf "%g", 1.2345e+25, "1.2345e+25" + assert_sprintf "%G", 1.2345e+25, "1.2345E+25" + + assert_sprintf "%g", Float64::MAX, "1.79769e+308" + assert_sprintf "%g", Float64::MIN_POSITIVE, "2.22507e-308" + assert_sprintf "%g", Float64::MIN_SUBNORMAL, "4.94066e-324" + assert_sprintf "%g", 0.0, "0" + assert_sprintf "%g", -0.0, "-0" + assert_sprintf "%g", -Float64::MIN_SUBNORMAL, "-4.94066e-324" + assert_sprintf "%g", -Float64::MIN_POSITIVE, "-2.22507e-308" + assert_sprintf "%g", Float64::MIN, "-1.79769e+308" end - it "writes plus sign after left space-padding" do - assert_sprintf "%+10g", 123.45, " +123.45" - assert_sprintf "%+10g", -123.45, " -123.45" - assert_sprintf "%+10g", 0.0, " +0" - end + context "width specifier" do + it "sets the minimum length of the string" do + assert_sprintf "%10g", 123.45, " 123.45" + assert_sprintf "%10g", -123.45, " -123.45" + assert_sprintf "%+10g", 123.45, " +123.45" - it "writes plus sign before left zero-padding" do - assert_sprintf "%+010g", 123.45, "+000123.45" - assert_sprintf "%+010g", -123.45, "-000123.45" - assert_sprintf "%+010g", 0.0, "+000000000" - end - end + assert_sprintf "%7g", 123.45, " 123.45" + assert_sprintf "%7g", -123.45, "-123.45" + assert_sprintf "%+7g", 123.45, "+123.45" - context "space flag" do - it "writes a space for positive values" do - assert_sprintf "% g", 123.45, " 123.45" - assert_sprintf "% g", -123.45, "-123.45" - assert_sprintf "% g", 0.0, " 0" - end + assert_sprintf "%6g", 123.45, "123.45" + assert_sprintf "%6g", -123.45, "-123.45" + assert_sprintf "%+6g", 123.45, "+123.45" - it "writes space before left space-padding" do - assert_sprintf "% 10g", 123.45, " 123.45" - assert_sprintf "% 10g", -123.45, " -123.45" - assert_sprintf "% 10g", 0.0, " 0" + assert_sprintf "%2g", 123.45, "123.45" + assert_sprintf "%2g", -123.45, "-123.45" + assert_sprintf "%+2g", 123.45, "+123.45" + end - assert_sprintf "% 010g", 123.45, " 000123.45" - assert_sprintf "% 010g", -123.45, "-000123.45" - assert_sprintf "% 010g", 0.0, " 000000000" + it "left-justifies on negative width" do + assert_sprintf "%*g", [-10, 123.45], "123.45 " + end end - it "is ignored if plus flag is also specified" do - assert_sprintf "% +g", 123.45, "+123.45" - assert_sprintf "%+ g", -123.45, "-123.45" + context "precision specifier" do + it "sets the precision of the value" do + assert_sprintf "%.0g", 123.45, "1e+2" + assert_sprintf "%.1g", 123.45, "1e+2" + assert_sprintf "%.2g", 123.45, "1.2e+2" + assert_sprintf "%.3g", 123.45, "123" + assert_sprintf "%.4g", 123.45, "123.5" + assert_sprintf "%.5g", 123.45, "123.45" + assert_sprintf "%.6g", 123.45, "123.45" + assert_sprintf "%.7g", 123.45, "123.45" + assert_sprintf "%.8g", 123.45, "123.45" + + assert_sprintf "%.1000g", 123.45, "123.4500000000000028421709430404007434844970703125" + + assert_sprintf "%.0g", 1.23e-45, "1e-45" + assert_sprintf "%.1g", 1.23e-45, "1e-45" + assert_sprintf "%.2g", 1.23e-45, "1.2e-45" + assert_sprintf "%.3g", 1.23e-45, "1.23e-45" + assert_sprintf "%.4g", 1.23e-45, "1.23e-45" + assert_sprintf "%.5g", 1.23e-45, "1.23e-45" + assert_sprintf "%.6g", 1.23e-45, "1.23e-45" + + assert_sprintf "%.1000g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125e-5" + end + + it "can be used with width" do + assert_sprintf "%10.1g", 123.45, " 1e+2" + assert_sprintf "%10.2g", 123.45, " 1.2e+2" + assert_sprintf "%10.3g", 123.45, " 123" + assert_sprintf "%10.4g", 123.45, " 123.5" + assert_sprintf "%10.5g", 123.45, " 123.45" + assert_sprintf "%10.1g", -123.45, " -1e+2" + assert_sprintf "%10.2g", -123.45, " -1.2e+2" + assert_sprintf "%10.3g", -123.45, " -123" + assert_sprintf "%10.4g", -123.45, " -123.5" + assert_sprintf "%10.5g", -123.45, " -123.45" + assert_sprintf "%10.5g", 0, " 0" + + assert_sprintf "%-10.1g", 123.45, "1e+2 " + assert_sprintf "%-10.2g", 123.45, "1.2e+2 " + assert_sprintf "%-10.3g", 123.45, "123 " + assert_sprintf "%-10.4g", 123.45, "123.5 " + assert_sprintf "%-10.5g", 123.45, "123.45 " + assert_sprintf "%-10.1g", -123.45, "-1e+2 " + assert_sprintf "%-10.2g", -123.45, "-1.2e+2 " + assert_sprintf "%-10.3g", -123.45, "-123 " + assert_sprintf "%-10.4g", -123.45, "-123.5 " + assert_sprintf "%-10.5g", -123.45, "-123.45 " + assert_sprintf "%-10.5g", 0, "0 " + + assert_sprintf "%3.1g", 123.45, "1e+2" + assert_sprintf "%3.2g", 123.45, "1.2e+2" + assert_sprintf "%3.3g", 123.45, "123" + assert_sprintf "%3.4g", 123.45, "123.5" + assert_sprintf "%3.5g", 123.45, "123.45" + assert_sprintf "%3.1g", -123.45, "-1e+2" + assert_sprintf "%3.2g", -123.45, "-1.2e+2" + assert_sprintf "%3.3g", -123.45, "-123" + assert_sprintf "%3.4g", -123.45, "-123.5" + assert_sprintf "%3.5g", -123.45, "-123.45" + + assert_sprintf "%1000.800g", 123.45, "#{" " * 950}123.4500000000000028421709430404007434844970703125" + end + + it "is ignored if precision argument is negative" do + assert_sprintf "%.*g", [-2, 123.45], "123.45" + end end - end - context "zero flag" do - it "left-pads the result with zeros" do - assert_sprintf "%010g", 123.45, "0000123.45" - assert_sprintf "%010g", -123.45, "-000123.45" - assert_sprintf "%010g", 0.0, "0000000000" + context "sharp flag" do + it "prints decimal point and trailing zeros" do + assert_sprintf "%#.0g", 12345, "1.e+4" + assert_sprintf "%#.6g", 12345, "12345.0" + assert_sprintf "%#.10g", 12345, "12345.00000" + assert_sprintf "%#.100g", 12345, "12345.#{"0" * 95}" + assert_sprintf "%#.1000g", 12345, "12345.#{"0" * 995}" + + assert_sprintf "%#.0g", 1e-5, "1.e-5" + assert_sprintf "%#.6g", 1e-5, "1.00000e-5" + assert_sprintf "%#.10g", 1e-5, "1.000000000e-5" + assert_sprintf "%#.100g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125#{"0" * 35}e-5" + assert_sprintf "%#.1000g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125#{"0" * 935}e-5" + + assert_sprintf "%#15.0g", 12345, " 1.e+4" + assert_sprintf "%#15.6g", 12345, " 12345.0" + assert_sprintf "%#15.10g", 12345, " 12345.00000" + end end - it "is ignored if string is left-justified" do - assert_sprintf "%-010g", 123.45, "123.45 " - assert_sprintf "%-010g", -123.45, "-123.45 " - assert_sprintf "%-010g", 0.0, "0 " + context "plus flag" do + it "writes a plus sign for positive values" do + assert_sprintf "%+g", 123.45, "+123.45" + assert_sprintf "%+g", -123.45, "-123.45" + assert_sprintf "%+g", 0.0, "+0" + end + + it "writes plus sign after left space-padding" do + assert_sprintf "%+10g", 123.45, " +123.45" + assert_sprintf "%+10g", -123.45, " -123.45" + assert_sprintf "%+10g", 0.0, " +0" + end + + it "writes plus sign before left zero-padding" do + assert_sprintf "%+010g", 123.45, "+000123.45" + assert_sprintf "%+010g", -123.45, "-000123.45" + assert_sprintf "%+010g", 0.0, "+000000000" + end end - it "can be used with precision" do - assert_sprintf "%010.2g", 123.45, "00001.2e+2" - assert_sprintf "%010.2g", -123.45, "-0001.2e+2" - assert_sprintf "%010.2g", 0.0, "0000000000" + context "space flag" do + it "writes a space for positive values" do + assert_sprintf "% g", 123.45, " 123.45" + assert_sprintf "% g", -123.45, "-123.45" + assert_sprintf "% g", 0.0, " 0" + end + + it "writes space before left space-padding" do + assert_sprintf "% 10g", 123.45, " 123.45" + assert_sprintf "% 10g", -123.45, " -123.45" + assert_sprintf "% 10g", 0.0, " 0" + + assert_sprintf "% 010g", 123.45, " 000123.45" + assert_sprintf "% 010g", -123.45, "-000123.45" + assert_sprintf "% 010g", 0.0, " 000000000" + end + + it "is ignored if plus flag is also specified" do + assert_sprintf "% +g", 123.45, "+123.45" + assert_sprintf "%+ g", -123.45, "-123.45" + end end - end - - context "minus flag" do - it "left-justifies the string" do - assert_sprintf "%-10g", 123.45, "123.45 " - assert_sprintf "%-10g", -123.45, "-123.45 " - assert_sprintf "%-10g", 0.0, "0 " - assert_sprintf "%- 10g", 123.45, " 123.45 " - assert_sprintf "%- 10g", -123.45, "-123.45 " - assert_sprintf "%- 10g", 0.0, " 0 " + context "zero flag" do + it "left-pads the result with zeros" do + assert_sprintf "%010g", 123.45, "0000123.45" + assert_sprintf "%010g", -123.45, "-000123.45" + assert_sprintf "%010g", 0.0, "0000000000" + end + + it "is ignored if string is left-justified" do + assert_sprintf "%-010g", 123.45, "123.45 " + assert_sprintf "%-010g", -123.45, "-123.45 " + assert_sprintf "%-010g", 0.0, "0 " + end + + it "can be used with precision" do + assert_sprintf "%010.2g", 123.45, "00001.2e+2" + assert_sprintf "%010.2g", -123.45, "-0001.2e+2" + assert_sprintf "%010.2g", 0.0, "0000000000" + end end - end - end - context "hex format" do - it "works" do - assert_sprintf "%a", 1194684.0, "0x1.23abcp+20" - assert_sprintf "%A", 1194684.0, "0X1.23ABCP+20" - assert_sprintf "%a", 12345678.45, "0x1.78c29ce666666p+23" - assert_sprintf "%A", 12345678.45, "0X1.78C29CE666666P+23" - - assert_sprintf "%a", Float64::MAX, "0x1.fffffffffffffp+1023" - assert_sprintf "%a", Float64::MIN_POSITIVE, "0x1p-1022" - assert_sprintf "%a", Float64::MIN_SUBNORMAL, "0x0.0000000000001p-1022" - assert_sprintf "%a", 0.0, "0x0p+0" - assert_sprintf "%a", -0.0, "-0x0p+0" - assert_sprintf "%a", -Float64::MIN_SUBNORMAL, "-0x0.0000000000001p-1022" - assert_sprintf "%a", -Float64::MIN_POSITIVE, "-0x1p-1022" - assert_sprintf "%a", Float64::MIN, "-0x1.fffffffffffffp+1023" - end - - context "width specifier" do - it "sets the minimum length of the string" do - assert_sprintf "%20a", hexfloat("0x1p+0"), " 0x1p+0" - assert_sprintf "%20a", hexfloat("0x1.2p+0"), " 0x1.2p+0" - assert_sprintf "%20a", hexfloat("0x1.23p+0"), " 0x1.23p+0" - assert_sprintf "%20a", hexfloat("0x1.234p+0"), " 0x1.234p+0" - assert_sprintf "%20a", hexfloat("0x1.2345p+0"), " 0x1.2345p+0" - assert_sprintf "%20a", hexfloat("0x1.23456p+0"), " 0x1.23456p+0" - assert_sprintf "%20a", hexfloat("0x1.234567p+0"), " 0x1.234567p+0" - assert_sprintf "%20a", hexfloat("0x1.2345678p+0"), " 0x1.2345678p+0" - assert_sprintf "%20a", hexfloat("0x1.23456789p+0"), " 0x1.23456789p+0" - assert_sprintf "%20a", hexfloat("0x1.23456789ap+0"), " 0x1.23456789ap+0" - assert_sprintf "%20a", hexfloat("0x1.23456789abp+0"), " 0x1.23456789abp+0" - assert_sprintf "%20a", hexfloat("0x1.23456789abcp+0"), " 0x1.23456789abcp+0" - - assert_sprintf "%20a", hexfloat("-0x1p+0"), " -0x1p+0" - assert_sprintf "%20a", hexfloat("-0x1.2p+0"), " -0x1.2p+0" - assert_sprintf "%20a", hexfloat("-0x1.23p+0"), " -0x1.23p+0" - assert_sprintf "%20a", hexfloat("-0x1.234p+0"), " -0x1.234p+0" - assert_sprintf "%20a", hexfloat("-0x1.2345p+0"), " -0x1.2345p+0" - assert_sprintf "%20a", hexfloat("-0x1.23456p+0"), " -0x1.23456p+0" - assert_sprintf "%20a", hexfloat("-0x1.234567p+0"), " -0x1.234567p+0" - assert_sprintf "%20a", hexfloat("-0x1.2345678p+0"), " -0x1.2345678p+0" - assert_sprintf "%20a", hexfloat("-0x1.23456789p+0"), " -0x1.23456789p+0" - assert_sprintf "%20a", hexfloat("-0x1.23456789ap+0"), " -0x1.23456789ap+0" - assert_sprintf "%20a", hexfloat("-0x1.23456789abp+0"), " -0x1.23456789abp+0" - assert_sprintf "%20a", hexfloat("-0x1.23456789abcp+0"), " -0x1.23456789abcp+0" - - assert_sprintf "%+20a", 1194684.0, " +0x1.23abcp+20" - - assert_sprintf "%14a", 1194684.0, " 0x1.23abcp+20" - assert_sprintf "%14a", -1194684.0, "-0x1.23abcp+20" - assert_sprintf "%+14a", 1194684.0, "+0x1.23abcp+20" - - assert_sprintf "%13a", 1194684.0, "0x1.23abcp+20" - assert_sprintf "%13a", -1194684.0, "-0x1.23abcp+20" - assert_sprintf "%+13a", 1194684.0, "+0x1.23abcp+20" - - assert_sprintf "%2a", 1194684.0, "0x1.23abcp+20" - assert_sprintf "%2a", -1194684.0, "-0x1.23abcp+20" - assert_sprintf "%+2a", 1194684.0, "+0x1.23abcp+20" - end + context "minus flag" do + it "left-justifies the string" do + assert_sprintf "%-10g", 123.45, "123.45 " + assert_sprintf "%-10g", -123.45, "-123.45 " + assert_sprintf "%-10g", 0.0, "0 " - it "left-justifies on negative width" do - assert_sprintf "%*a", [-20, 1194684.0], "0x1.23abcp+20 " + assert_sprintf "%- 10g", 123.45, " 123.45 " + assert_sprintf "%- 10g", -123.45, "-123.45 " + assert_sprintf "%- 10g", 0.0, " 0 " + end end end - context "precision specifier" do - it "sets the minimum length of the fractional part" do - assert_sprintf "%.0a", 0.0, "0x0p+0" - - assert_sprintf "%.0a", (Float64::MIN_POSITIVE / 2).prev_float, "0x0p-1022" - assert_sprintf "%.0a", Float64::MIN_POSITIVE / 2, "0x0p-1022" - assert_sprintf "%.0a", (Float64::MIN_POSITIVE / 2).next_float, "0x1p-1022" - assert_sprintf "%.0a", Float64::MIN_POSITIVE.prev_float, "0x1p-1022" - assert_sprintf "%.0a", Float64::MIN_POSITIVE, "0x1p-1022" - - assert_sprintf "%.0a", 0.0625, "0x1p-4" - assert_sprintf "%.0a", 0.0625.next_float, "0x1p-4" - assert_sprintf "%.0a", 0.09375.prev_float, "0x1p-4" - assert_sprintf "%.0a", 0.09375, "0x2p-4" - assert_sprintf "%.0a", 0.09375.next_float, "0x2p-4" - assert_sprintf "%.0a", 0.125.prev_float, "0x2p-4" - assert_sprintf "%.0a", 0.125, "0x1p-3" - - assert_sprintf "%.1a", 2.0, "0x1.0p+1" - assert_sprintf "%.1a", 2.0.next_float, "0x1.0p+1" - assert_sprintf "%.1a", 2.0625.prev_float, "0x1.0p+1" - assert_sprintf "%.1a", 2.0625, "0x1.0p+1" - assert_sprintf "%.1a", 2.0625.next_float, "0x1.1p+1" - assert_sprintf "%.1a", 2.125.prev_float, "0x1.1p+1" - assert_sprintf "%.1a", 2.125, "0x1.1p+1" - assert_sprintf "%.1a", 2.125.next_float, "0x1.1p+1" - assert_sprintf "%.1a", 2.1875.prev_float, "0x1.1p+1" - assert_sprintf "%.1a", 2.1875, "0x1.2p+1" - assert_sprintf "%.1a", 2.1875.next_float, "0x1.2p+1" - assert_sprintf "%.1a", 2.25.prev_float, "0x1.2p+1" - assert_sprintf "%.1a", 2.25, "0x1.2p+1" - - assert_sprintf "%.1a", 60.0, "0x1.ep+5" - assert_sprintf "%.1a", 60.0.next_float, "0x1.ep+5" - assert_sprintf "%.1a", 61.0.prev_float, "0x1.ep+5" - assert_sprintf "%.1a", 61.0, "0x1.ep+5" - assert_sprintf "%.1a", 61.0.next_float, "0x1.fp+5" - assert_sprintf "%.1a", 62.0.prev_float, "0x1.fp+5" - assert_sprintf "%.1a", 62.0, "0x1.fp+5" - assert_sprintf "%.1a", 62.0.next_float, "0x1.fp+5" - assert_sprintf "%.1a", 63.0.prev_float, "0x1.fp+5" - assert_sprintf "%.1a", 63.0, "0x2.0p+5" - assert_sprintf "%.1a", 63.0.next_float, "0x2.0p+5" - assert_sprintf "%.1a", 64.0.prev_float, "0x2.0p+5" - assert_sprintf "%.1a", 64.0, "0x1.0p+6" - - assert_sprintf "%.4a", 65536.0, "0x1.0000p+16" - assert_sprintf "%.4a", 65536.0.next_float, "0x1.0000p+16" - assert_sprintf "%.4a", 65536.5.prev_float, "0x1.0000p+16" - assert_sprintf "%.4a", 65536.5, "0x1.0000p+16" - assert_sprintf "%.4a", 65536.5.next_float, "0x1.0001p+16" - assert_sprintf "%.4a", 65537.0.prev_float, "0x1.0001p+16" - assert_sprintf "%.4a", 65537.0, "0x1.0001p+16" - assert_sprintf "%.4a", 65537.0.next_float, "0x1.0001p+16" - assert_sprintf "%.4a", 65537.5.prev_float, "0x1.0001p+16" - assert_sprintf "%.4a", 65537.5, "0x1.0002p+16" - assert_sprintf "%.4a", 65537.5.next_float, "0x1.0002p+16" - assert_sprintf "%.4a", 65538.0.prev_float, "0x1.0002p+16" - assert_sprintf "%.4a", 65538.0, "0x1.0002p+16" - - assert_sprintf "%.4a", 131070.0, "0x1.fffep+16" - assert_sprintf "%.4a", 131070.0.next_float, "0x1.fffep+16" - assert_sprintf "%.4a", 131070.5.prev_float, "0x1.fffep+16" - assert_sprintf "%.4a", 131070.5, "0x1.fffep+16" - assert_sprintf "%.4a", 131070.5.next_float, "0x1.ffffp+16" - assert_sprintf "%.4a", 131071.0.prev_float, "0x1.ffffp+16" - assert_sprintf "%.4a", 131071.0, "0x1.ffffp+16" - assert_sprintf "%.4a", 131071.0.next_float, "0x1.ffffp+16" - assert_sprintf "%.4a", 131071.5.prev_float, "0x1.ffffp+16" - assert_sprintf "%.4a", 131071.5, "0x2.0000p+16" - assert_sprintf "%.4a", 131071.5.next_float, "0x2.0000p+16" - assert_sprintf "%.4a", 131072.0.prev_float, "0x2.0000p+16" - assert_sprintf "%.4a", 131072.0, "0x1.0000p+17" - - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x01, "0x0.000000000000p-1022" - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x07, "0x0.000000000000p-1022" - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x08, "0x0.000000000000p-1022" - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x09, "0x0.000000000001p-1022" - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x0f, "0x0.000000000001p-1022" - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x10, "0x0.000000000001p-1022" - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x11, "0x0.000000000001p-1022" - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x17, "0x0.000000000001p-1022" - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x18, "0x0.000000000002p-1022" - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x19, "0x0.000000000002p-1022" - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x1f, "0x0.000000000002p-1022" - assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x20, "0x0.000000000002p-1022" - - assert_sprintf "%.17a", Float64::MAX, "0x1.fffffffffffff0000p+1023" - assert_sprintf "%.16a", Float64::MAX, "0x1.fffffffffffff000p+1023" - assert_sprintf "%.15a", Float64::MAX, "0x1.fffffffffffff00p+1023" - assert_sprintf "%.14a", Float64::MAX, "0x1.fffffffffffff0p+1023" - assert_sprintf "%.13a", Float64::MAX, "0x1.fffffffffffffp+1023" - assert_sprintf "%.12a", Float64::MAX, "0x2.000000000000p+1023" - assert_sprintf "%.11a", Float64::MAX, "0x2.00000000000p+1023" - assert_sprintf "%.10a", Float64::MAX, "0x2.0000000000p+1023" - assert_sprintf "%.9a", Float64::MAX, "0x2.000000000p+1023" - assert_sprintf "%.8a", Float64::MAX, "0x2.00000000p+1023" - assert_sprintf "%.7a", Float64::MAX, "0x2.0000000p+1023" - assert_sprintf "%.6a", Float64::MAX, "0x2.000000p+1023" - assert_sprintf "%.5a", Float64::MAX, "0x2.00000p+1023" - assert_sprintf "%.4a", Float64::MAX, "0x2.0000p+1023" - assert_sprintf "%.3a", Float64::MAX, "0x2.000p+1023" - assert_sprintf "%.2a", Float64::MAX, "0x2.00p+1023" - assert_sprintf "%.1a", Float64::MAX, "0x2.0p+1023" - assert_sprintf "%.0a", Float64::MAX, "0x2p+1023" - - assert_sprintf "%.1000a", 1194684.0, "0x1.23abc#{"0" * 995}p+20" + context "hex format" do + it "works" do + assert_sprintf "%a", 1194684.0, "0x1.23abcp+20" + assert_sprintf "%A", 1194684.0, "0X1.23ABCP+20" + assert_sprintf "%a", 12345678.45, "0x1.78c29ce666666p+23" + assert_sprintf "%A", 12345678.45, "0X1.78C29CE666666P+23" + + assert_sprintf "%a", Float64::MAX, "0x1.fffffffffffffp+1023" + assert_sprintf "%a", Float64::MIN_POSITIVE, "0x1p-1022" + assert_sprintf "%a", Float64::MIN_SUBNORMAL, "0x0.0000000000001p-1022" + assert_sprintf "%a", 0.0, "0x0p+0" + assert_sprintf "%a", -0.0, "-0x0p+0" + assert_sprintf "%a", -Float64::MIN_SUBNORMAL, "-0x0.0000000000001p-1022" + assert_sprintf "%a", -Float64::MIN_POSITIVE, "-0x1p-1022" + assert_sprintf "%a", Float64::MIN, "-0x1.fffffffffffffp+1023" end - it "can be used with width" do - assert_sprintf "%20.8a", 1194684.0, " 0x1.23abc000p+20" - assert_sprintf "%20.8a", -1194684.0, " -0x1.23abc000p+20" - assert_sprintf "%20.8a", 0.0, " 0x0.00000000p+0" - - assert_sprintf "%-20.8a", 1194684.0, "0x1.23abc000p+20 " - assert_sprintf "%-20.8a", -1194684.0, "-0x1.23abc000p+20 " - assert_sprintf "%-20.8a", 0.0, "0x0.00000000p+0 " - - assert_sprintf "%4.8a", 1194684.0, "0x1.23abc000p+20" - assert_sprintf "%4.8a", -1194684.0, "-0x1.23abc000p+20" - assert_sprintf "%4.8a", 0.0, "0x0.00000000p+0" - end - - it "is ignored if precision argument is negative" do - assert_sprintf "%.*a", [-2, 1194684.0], "0x1.23abcp+20" - end - end - - context "sharp flag" do - it "prints a decimal point even if no digits follow" do - assert_sprintf "%#a", 1.0, "0x1.p+0" - assert_sprintf "%#a", Float64::MIN_POSITIVE, "0x1.p-1022" - assert_sprintf "%#a", 2.0 ** -234, "0x1.p-234" - assert_sprintf "%#a", 2.0 ** 1021, "0x1.p+1021" - assert_sprintf "%#a", 0.0, "0x0.p+0" - assert_sprintf "%#a", -0.0, "-0x0.p+0" - - assert_sprintf "%#.0a", 1.0, "0x1.p+0" - assert_sprintf "%#.0a", Float64::MIN_POSITIVE, "0x1.p-1022" - assert_sprintf "%#.0a", 2.0 ** -234, "0x1.p-234" - assert_sprintf "%#.0a", 2.0 ** 1021, "0x1.p+1021" - assert_sprintf "%#.0a", 1194684.0, "0x1.p+20" - assert_sprintf "%#.0a", 0.0, "0x0.p+0" - assert_sprintf "%#.0a", -0.0, "-0x0.p+0" + context "width specifier" do + it "sets the minimum length of the string" do + assert_sprintf "%20a", hexfloat("0x1p+0"), " 0x1p+0" + assert_sprintf "%20a", hexfloat("0x1.2p+0"), " 0x1.2p+0" + assert_sprintf "%20a", hexfloat("0x1.23p+0"), " 0x1.23p+0" + assert_sprintf "%20a", hexfloat("0x1.234p+0"), " 0x1.234p+0" + assert_sprintf "%20a", hexfloat("0x1.2345p+0"), " 0x1.2345p+0" + assert_sprintf "%20a", hexfloat("0x1.23456p+0"), " 0x1.23456p+0" + assert_sprintf "%20a", hexfloat("0x1.234567p+0"), " 0x1.234567p+0" + assert_sprintf "%20a", hexfloat("0x1.2345678p+0"), " 0x1.2345678p+0" + assert_sprintf "%20a", hexfloat("0x1.23456789p+0"), " 0x1.23456789p+0" + assert_sprintf "%20a", hexfloat("0x1.23456789ap+0"), " 0x1.23456789ap+0" + assert_sprintf "%20a", hexfloat("0x1.23456789abp+0"), " 0x1.23456789abp+0" + assert_sprintf "%20a", hexfloat("0x1.23456789abcp+0"), " 0x1.23456789abcp+0" + + assert_sprintf "%20a", hexfloat("-0x1p+0"), " -0x1p+0" + assert_sprintf "%20a", hexfloat("-0x1.2p+0"), " -0x1.2p+0" + assert_sprintf "%20a", hexfloat("-0x1.23p+0"), " -0x1.23p+0" + assert_sprintf "%20a", hexfloat("-0x1.234p+0"), " -0x1.234p+0" + assert_sprintf "%20a", hexfloat("-0x1.2345p+0"), " -0x1.2345p+0" + assert_sprintf "%20a", hexfloat("-0x1.23456p+0"), " -0x1.23456p+0" + assert_sprintf "%20a", hexfloat("-0x1.234567p+0"), " -0x1.234567p+0" + assert_sprintf "%20a", hexfloat("-0x1.2345678p+0"), " -0x1.2345678p+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789p+0"), " -0x1.23456789p+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789ap+0"), " -0x1.23456789ap+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789abp+0"), " -0x1.23456789abp+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789abcp+0"), " -0x1.23456789abcp+0" + + assert_sprintf "%+20a", 1194684.0, " +0x1.23abcp+20" + + assert_sprintf "%14a", 1194684.0, " 0x1.23abcp+20" + assert_sprintf "%14a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+14a", 1194684.0, "+0x1.23abcp+20" + + assert_sprintf "%13a", 1194684.0, "0x1.23abcp+20" + assert_sprintf "%13a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+13a", 1194684.0, "+0x1.23abcp+20" + + assert_sprintf "%2a", 1194684.0, "0x1.23abcp+20" + assert_sprintf "%2a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+2a", 1194684.0, "+0x1.23abcp+20" + end + + it "left-justifies on negative width" do + assert_sprintf "%*a", [-20, 1194684.0], "0x1.23abcp+20 " + end end - end - context "plus flag" do - it "writes a plus sign for positive values" do - assert_sprintf "%+a", 1194684.0, "+0x1.23abcp+20" - assert_sprintf "%+a", -1194684.0, "-0x1.23abcp+20" - assert_sprintf "%+a", 0.0, "+0x0p+0" + context "precision specifier" do + it "sets the minimum length of the fractional part" do + assert_sprintf "%.0a", 0.0, "0x0p+0" + + assert_sprintf "%.0a", (Float64::MIN_POSITIVE / 2).prev_float, "0x0p-1022" + assert_sprintf "%.0a", Float64::MIN_POSITIVE / 2, "0x0p-1022" + assert_sprintf "%.0a", (Float64::MIN_POSITIVE / 2).next_float, "0x1p-1022" + assert_sprintf "%.0a", Float64::MIN_POSITIVE.prev_float, "0x1p-1022" + assert_sprintf "%.0a", Float64::MIN_POSITIVE, "0x1p-1022" + + assert_sprintf "%.0a", 0.0625, "0x1p-4" + assert_sprintf "%.0a", 0.0625.next_float, "0x1p-4" + assert_sprintf "%.0a", 0.09375.prev_float, "0x1p-4" + assert_sprintf "%.0a", 0.09375, "0x2p-4" + assert_sprintf "%.0a", 0.09375.next_float, "0x2p-4" + assert_sprintf "%.0a", 0.125.prev_float, "0x2p-4" + assert_sprintf "%.0a", 0.125, "0x1p-3" + + assert_sprintf "%.1a", 2.0, "0x1.0p+1" + assert_sprintf "%.1a", 2.0.next_float, "0x1.0p+1" + assert_sprintf "%.1a", 2.0625.prev_float, "0x1.0p+1" + assert_sprintf "%.1a", 2.0625, "0x1.0p+1" + assert_sprintf "%.1a", 2.0625.next_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.125.prev_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.125, "0x1.1p+1" + assert_sprintf "%.1a", 2.125.next_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.1875.prev_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.1875, "0x1.2p+1" + assert_sprintf "%.1a", 2.1875.next_float, "0x1.2p+1" + assert_sprintf "%.1a", 2.25.prev_float, "0x1.2p+1" + assert_sprintf "%.1a", 2.25, "0x1.2p+1" + + assert_sprintf "%.1a", 60.0, "0x1.ep+5" + assert_sprintf "%.1a", 60.0.next_float, "0x1.ep+5" + assert_sprintf "%.1a", 61.0.prev_float, "0x1.ep+5" + assert_sprintf "%.1a", 61.0, "0x1.ep+5" + assert_sprintf "%.1a", 61.0.next_float, "0x1.fp+5" + assert_sprintf "%.1a", 62.0.prev_float, "0x1.fp+5" + assert_sprintf "%.1a", 62.0, "0x1.fp+5" + assert_sprintf "%.1a", 62.0.next_float, "0x1.fp+5" + assert_sprintf "%.1a", 63.0.prev_float, "0x1.fp+5" + assert_sprintf "%.1a", 63.0, "0x2.0p+5" + assert_sprintf "%.1a", 63.0.next_float, "0x2.0p+5" + assert_sprintf "%.1a", 64.0.prev_float, "0x2.0p+5" + assert_sprintf "%.1a", 64.0, "0x1.0p+6" + + assert_sprintf "%.4a", 65536.0, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.0.next_float, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.5.prev_float, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.5, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.5.next_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.0.prev_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.0, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.0.next_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.5.prev_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.5, "0x1.0002p+16" + assert_sprintf "%.4a", 65537.5.next_float, "0x1.0002p+16" + assert_sprintf "%.4a", 65538.0.prev_float, "0x1.0002p+16" + assert_sprintf "%.4a", 65538.0, "0x1.0002p+16" + + assert_sprintf "%.4a", 131070.0, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.0.next_float, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.5.prev_float, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.5, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.5.next_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.0.prev_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.0, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.0.next_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.5.prev_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.5, "0x2.0000p+16" + assert_sprintf "%.4a", 131071.5.next_float, "0x2.0000p+16" + assert_sprintf "%.4a", 131072.0.prev_float, "0x2.0000p+16" + assert_sprintf "%.4a", 131072.0, "0x1.0000p+17" + + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x01, "0x0.000000000000p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x07, "0x0.000000000000p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x08, "0x0.000000000000p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x09, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x0f, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x10, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x11, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x17, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x18, "0x0.000000000002p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x19, "0x0.000000000002p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x1f, "0x0.000000000002p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x20, "0x0.000000000002p-1022" + + assert_sprintf "%.17a", Float64::MAX, "0x1.fffffffffffff0000p+1023" + assert_sprintf "%.16a", Float64::MAX, "0x1.fffffffffffff000p+1023" + assert_sprintf "%.15a", Float64::MAX, "0x1.fffffffffffff00p+1023" + assert_sprintf "%.14a", Float64::MAX, "0x1.fffffffffffff0p+1023" + assert_sprintf "%.13a", Float64::MAX, "0x1.fffffffffffffp+1023" + assert_sprintf "%.12a", Float64::MAX, "0x2.000000000000p+1023" + assert_sprintf "%.11a", Float64::MAX, "0x2.00000000000p+1023" + assert_sprintf "%.10a", Float64::MAX, "0x2.0000000000p+1023" + assert_sprintf "%.9a", Float64::MAX, "0x2.000000000p+1023" + assert_sprintf "%.8a", Float64::MAX, "0x2.00000000p+1023" + assert_sprintf "%.7a", Float64::MAX, "0x2.0000000p+1023" + assert_sprintf "%.6a", Float64::MAX, "0x2.000000p+1023" + assert_sprintf "%.5a", Float64::MAX, "0x2.00000p+1023" + assert_sprintf "%.4a", Float64::MAX, "0x2.0000p+1023" + assert_sprintf "%.3a", Float64::MAX, "0x2.000p+1023" + assert_sprintf "%.2a", Float64::MAX, "0x2.00p+1023" + assert_sprintf "%.1a", Float64::MAX, "0x2.0p+1023" + assert_sprintf "%.0a", Float64::MAX, "0x2p+1023" + + assert_sprintf "%.1000a", 1194684.0, "0x1.23abc#{"0" * 995}p+20" + end + + it "can be used with width" do + assert_sprintf "%20.8a", 1194684.0, " 0x1.23abc000p+20" + assert_sprintf "%20.8a", -1194684.0, " -0x1.23abc000p+20" + assert_sprintf "%20.8a", 0.0, " 0x0.00000000p+0" + + assert_sprintf "%-20.8a", 1194684.0, "0x1.23abc000p+20 " + assert_sprintf "%-20.8a", -1194684.0, "-0x1.23abc000p+20 " + assert_sprintf "%-20.8a", 0.0, "0x0.00000000p+0 " + + assert_sprintf "%4.8a", 1194684.0, "0x1.23abc000p+20" + assert_sprintf "%4.8a", -1194684.0, "-0x1.23abc000p+20" + assert_sprintf "%4.8a", 0.0, "0x0.00000000p+0" + end + + it "is ignored if precision argument is negative" do + assert_sprintf "%.*a", [-2, 1194684.0], "0x1.23abcp+20" + end end - it "writes plus sign after left space-padding" do - assert_sprintf "%+20a", 1194684.0, " +0x1.23abcp+20" - assert_sprintf "%+20a", -1194684.0, " -0x1.23abcp+20" - assert_sprintf "%+20a", 0.0, " +0x0p+0" + context "sharp flag" do + it "prints a decimal point even if no digits follow" do + assert_sprintf "%#a", 1.0, "0x1.p+0" + assert_sprintf "%#a", Float64::MIN_POSITIVE, "0x1.p-1022" + assert_sprintf "%#a", 2.0 ** -234, "0x1.p-234" + assert_sprintf "%#a", 2.0 ** 1021, "0x1.p+1021" + assert_sprintf "%#a", 0.0, "0x0.p+0" + assert_sprintf "%#a", -0.0, "-0x0.p+0" + + assert_sprintf "%#.0a", 1.0, "0x1.p+0" + assert_sprintf "%#.0a", Float64::MIN_POSITIVE, "0x1.p-1022" + assert_sprintf "%#.0a", 2.0 ** -234, "0x1.p-234" + assert_sprintf "%#.0a", 2.0 ** 1021, "0x1.p+1021" + assert_sprintf "%#.0a", 1194684.0, "0x1.p+20" + assert_sprintf "%#.0a", 0.0, "0x0.p+0" + assert_sprintf "%#.0a", -0.0, "-0x0.p+0" + end end - it "writes plus sign before left zero-padding" do - assert_sprintf "%+020a", 1194684.0, "+0x0000001.23abcp+20" - assert_sprintf "%+020a", -1194684.0, "-0x0000001.23abcp+20" - assert_sprintf "%+020a", 0.0, "+0x00000000000000p+0" + context "plus flag" do + it "writes a plus sign for positive values" do + assert_sprintf "%+a", 1194684.0, "+0x1.23abcp+20" + assert_sprintf "%+a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+a", 0.0, "+0x0p+0" + end + + it "writes plus sign after left space-padding" do + assert_sprintf "%+20a", 1194684.0, " +0x1.23abcp+20" + assert_sprintf "%+20a", -1194684.0, " -0x1.23abcp+20" + assert_sprintf "%+20a", 0.0, " +0x0p+0" + end + + it "writes plus sign before left zero-padding" do + assert_sprintf "%+020a", 1194684.0, "+0x0000001.23abcp+20" + assert_sprintf "%+020a", -1194684.0, "-0x0000001.23abcp+20" + assert_sprintf "%+020a", 0.0, "+0x00000000000000p+0" + end end - end - context "space flag" do - it "writes a space for positive values" do - assert_sprintf "% a", 1194684.0, " 0x1.23abcp+20" - assert_sprintf "% a", -1194684.0, "-0x1.23abcp+20" - assert_sprintf "% a", 0.0, " 0x0p+0" + context "space flag" do + it "writes a space for positive values" do + assert_sprintf "% a", 1194684.0, " 0x1.23abcp+20" + assert_sprintf "% a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "% a", 0.0, " 0x0p+0" + end + + it "writes space before left space-padding" do + assert_sprintf "% 20a", 1194684.0, " 0x1.23abcp+20" + assert_sprintf "% 20a", -1194684.0, " -0x1.23abcp+20" + assert_sprintf "% 20a", 0.0, " 0x0p+0" + + assert_sprintf "% 020a", 1194684.0, " 0x0000001.23abcp+20" + assert_sprintf "% 020a", -1194684.0, "-0x0000001.23abcp+20" + assert_sprintf "% 020a", 0.0, " 0x00000000000000p+0" + end + + it "is ignored if plus flag is also specified" do + assert_sprintf "% +a", 1194684.0, "+0x1.23abcp+20" + assert_sprintf "%+ a", -1194684.0, "-0x1.23abcp+20" + end end - it "writes space before left space-padding" do - assert_sprintf "% 20a", 1194684.0, " 0x1.23abcp+20" - assert_sprintf "% 20a", -1194684.0, " -0x1.23abcp+20" - assert_sprintf "% 20a", 0.0, " 0x0p+0" - - assert_sprintf "% 020a", 1194684.0, " 0x0000001.23abcp+20" - assert_sprintf "% 020a", -1194684.0, "-0x0000001.23abcp+20" - assert_sprintf "% 020a", 0.0, " 0x00000000000000p+0" + context "zero flag" do + it "left-pads the result with zeros" do + assert_sprintf "%020a", 1194684.0, "0x00000001.23abcp+20" + assert_sprintf "%020a", -1194684.0, "-0x0000001.23abcp+20" + assert_sprintf "%020a", 0.0, "0x000000000000000p+0" + end + + it "is ignored if string is left-justified" do + assert_sprintf "%-020a", 1194684.0, "0x1.23abcp+20 " + assert_sprintf "%-020a", -1194684.0, "-0x1.23abcp+20 " + assert_sprintf "%-020a", 0.0, "0x0p+0 " + end + + it "can be used with precision" do + assert_sprintf "%020.8a", 1194684.0, "0x00001.23abc000p+20" + assert_sprintf "%020.8a", -1194684.0, "-0x0001.23abc000p+20" + assert_sprintf "%020.8a", 0.0, "0x000000.00000000p+0" + end end - it "is ignored if plus flag is also specified" do - assert_sprintf "% +a", 1194684.0, "+0x1.23abcp+20" - assert_sprintf "%+ a", -1194684.0, "-0x1.23abcp+20" + context "minus flag" do + it "left-justifies the string" do + assert_sprintf "%-20a", 1194684.0, "0x1.23abcp+20 " + assert_sprintf "%-20a", -1194684.0, "-0x1.23abcp+20 " + assert_sprintf "%-20a", 0.0, "0x0p+0 " + end end end - context "zero flag" do - it "left-pads the result with zeros" do - assert_sprintf "%020a", 1194684.0, "0x00000001.23abcp+20" - assert_sprintf "%020a", -1194684.0, "-0x0000001.23abcp+20" - assert_sprintf "%020a", 0.0, "0x000000000000000p+0" + [Float32, Float64].each do |float| + it "infinities" do + pos_inf = float.new(1) / float.new(0) + neg_inf = float.new(-1) / float.new(0) + + assert_sprintf "%f", pos_inf, "inf" + assert_sprintf "%a", pos_inf, "inf" + assert_sprintf "%e", pos_inf, "inf" + assert_sprintf "%g", pos_inf, "inf" + assert_sprintf "%A", pos_inf, "INF" + assert_sprintf "%E", pos_inf, "INF" + assert_sprintf "%G", pos_inf, "INF" + + assert_sprintf "%f", neg_inf, "-inf" + assert_sprintf "%G", neg_inf, "-INF" + + assert_sprintf "%2f", pos_inf, "inf" + assert_sprintf "%4f", pos_inf, " inf" + assert_sprintf "%6f", pos_inf, " inf" + assert_sprintf "%2f", neg_inf, "-inf" + assert_sprintf "%4f", neg_inf, "-inf" + assert_sprintf "%6f", neg_inf, " -inf" + + assert_sprintf "% f", pos_inf, " inf" + assert_sprintf "% 2f", pos_inf, " inf" + assert_sprintf "% 4f", pos_inf, " inf" + assert_sprintf "% 6f", pos_inf, " inf" + assert_sprintf "% f", neg_inf, "-inf" + assert_sprintf "% 2f", neg_inf, "-inf" + assert_sprintf "% 4f", neg_inf, "-inf" + assert_sprintf "% 6f", neg_inf, " -inf" + + assert_sprintf "%+f", pos_inf, "+inf" + assert_sprintf "%+2f", pos_inf, "+inf" + assert_sprintf "%+4f", pos_inf, "+inf" + assert_sprintf "%+6f", pos_inf, " +inf" + assert_sprintf "%+f", neg_inf, "-inf" + assert_sprintf "%+2f", neg_inf, "-inf" + assert_sprintf "%+4f", neg_inf, "-inf" + assert_sprintf "%+6f", neg_inf, " -inf" + + assert_sprintf "%+ f", pos_inf, "+inf" + + assert_sprintf "%-4f", pos_inf, "inf " + assert_sprintf "%-6f", pos_inf, "inf " + assert_sprintf "%-4f", neg_inf, "-inf" + assert_sprintf "%-6f", neg_inf, "-inf " + + assert_sprintf "% -4f", pos_inf, " inf" + assert_sprintf "% -6f", pos_inf, " inf " + assert_sprintf "% -4f", neg_inf, "-inf" + assert_sprintf "% -6f", neg_inf, "-inf " + + assert_sprintf "%-+4f", pos_inf, "+inf" + assert_sprintf "%-+6f", pos_inf, "+inf " + assert_sprintf "%-+4f", neg_inf, "-inf" + assert_sprintf "%-+6f", neg_inf, "-inf " + + assert_sprintf "%-+ 6f", pos_inf, "+inf " + + assert_sprintf "%06f", pos_inf, " inf" + assert_sprintf "%-06f", pos_inf, "inf " + assert_sprintf "%06f", neg_inf, " -inf" + assert_sprintf "%-06f", neg_inf, "-inf " + + assert_sprintf "%.1f", pos_inf, "inf" + + assert_sprintf "%#f", pos_inf, "inf" end - it "is ignored if string is left-justified" do - assert_sprintf "%-020a", 1194684.0, "0x1.23abcp+20 " - assert_sprintf "%-020a", -1194684.0, "-0x1.23abcp+20 " - assert_sprintf "%-020a", 0.0, "0x0p+0 " + it "not-a-numbers" do + pos_nan = Math.copysign(float.new(0) / float.new(0), 1) + neg_nan = Math.copysign(float.new(0) / float.new(0), -1) + + assert_sprintf "%f", pos_nan, "nan" + assert_sprintf "%a", pos_nan, "nan" + assert_sprintf "%e", pos_nan, "nan" + assert_sprintf "%g", pos_nan, "nan" + assert_sprintf "%A", pos_nan, "NAN" + assert_sprintf "%E", pos_nan, "NAN" + assert_sprintf "%G", pos_nan, "NAN" + + assert_sprintf "%f", neg_nan, "nan" + assert_sprintf "%a", neg_nan, "nan" + assert_sprintf "%e", neg_nan, "nan" + assert_sprintf "%g", neg_nan, "nan" + assert_sprintf "%A", neg_nan, "NAN" + assert_sprintf "%E", neg_nan, "NAN" + assert_sprintf "%G", neg_nan, "NAN" + + assert_sprintf "%+f", pos_nan, "+nan" + assert_sprintf "%+f", neg_nan, "+nan" end - - it "can be used with precision" do - assert_sprintf "%020.8a", 1194684.0, "0x00001.23abc000p+20" - assert_sprintf "%020.8a", -1194684.0, "-0x0001.23abc000p+20" - assert_sprintf "%020.8a", 0.0, "0x000000.00000000p+0" - end - end - - context "minus flag" do - it "left-justifies the string" do - assert_sprintf "%-20a", 1194684.0, "0x1.23abcp+20 " - assert_sprintf "%-20a", -1194684.0, "-0x1.23abcp+20 " - assert_sprintf "%-20a", 0.0, "0x0p+0 " - end - end - end - - [Float32, Float64].each do |float| - it "infinities" do - pos_inf = float.new(1) / float.new(0) - neg_inf = float.new(-1) / float.new(0) - - assert_sprintf "%f", pos_inf, "inf" - assert_sprintf "%a", pos_inf, "inf" - assert_sprintf "%e", pos_inf, "inf" - assert_sprintf "%g", pos_inf, "inf" - assert_sprintf "%A", pos_inf, "INF" - assert_sprintf "%E", pos_inf, "INF" - assert_sprintf "%G", pos_inf, "INF" - - assert_sprintf "%f", neg_inf, "-inf" - assert_sprintf "%G", neg_inf, "-INF" - - assert_sprintf "%2f", pos_inf, "inf" - assert_sprintf "%4f", pos_inf, " inf" - assert_sprintf "%6f", pos_inf, " inf" - assert_sprintf "%2f", neg_inf, "-inf" - assert_sprintf "%4f", neg_inf, "-inf" - assert_sprintf "%6f", neg_inf, " -inf" - - assert_sprintf "% f", pos_inf, " inf" - assert_sprintf "% 2f", pos_inf, " inf" - assert_sprintf "% 4f", pos_inf, " inf" - assert_sprintf "% 6f", pos_inf, " inf" - assert_sprintf "% f", neg_inf, "-inf" - assert_sprintf "% 2f", neg_inf, "-inf" - assert_sprintf "% 4f", neg_inf, "-inf" - assert_sprintf "% 6f", neg_inf, " -inf" - - assert_sprintf "%+f", pos_inf, "+inf" - assert_sprintf "%+2f", pos_inf, "+inf" - assert_sprintf "%+4f", pos_inf, "+inf" - assert_sprintf "%+6f", pos_inf, " +inf" - assert_sprintf "%+f", neg_inf, "-inf" - assert_sprintf "%+2f", neg_inf, "-inf" - assert_sprintf "%+4f", neg_inf, "-inf" - assert_sprintf "%+6f", neg_inf, " -inf" - - assert_sprintf "%+ f", pos_inf, "+inf" - - assert_sprintf "%-4f", pos_inf, "inf " - assert_sprintf "%-6f", pos_inf, "inf " - assert_sprintf "%-4f", neg_inf, "-inf" - assert_sprintf "%-6f", neg_inf, "-inf " - - assert_sprintf "% -4f", pos_inf, " inf" - assert_sprintf "% -6f", pos_inf, " inf " - assert_sprintf "% -4f", neg_inf, "-inf" - assert_sprintf "% -6f", neg_inf, "-inf " - - assert_sprintf "%-+4f", pos_inf, "+inf" - assert_sprintf "%-+6f", pos_inf, "+inf " - assert_sprintf "%-+4f", neg_inf, "-inf" - assert_sprintf "%-+6f", neg_inf, "-inf " - - assert_sprintf "%-+ 6f", pos_inf, "+inf " - - assert_sprintf "%06f", pos_inf, " inf" - assert_sprintf "%-06f", pos_inf, "inf " - assert_sprintf "%06f", neg_inf, " -inf" - assert_sprintf "%-06f", neg_inf, "-inf " - - assert_sprintf "%.1f", pos_inf, "inf" - - assert_sprintf "%#f", pos_inf, "inf" - end - - it "not-a-numbers" do - pos_nan = Math.copysign(float.new(0) / float.new(0), 1) - neg_nan = Math.copysign(float.new(0) / float.new(0), -1) - - assert_sprintf "%f", pos_nan, "nan" - assert_sprintf "%a", pos_nan, "nan" - assert_sprintf "%e", pos_nan, "nan" - assert_sprintf "%g", pos_nan, "nan" - assert_sprintf "%A", pos_nan, "NAN" - assert_sprintf "%E", pos_nan, "NAN" - assert_sprintf "%G", pos_nan, "NAN" - - assert_sprintf "%f", neg_nan, "nan" - assert_sprintf "%a", neg_nan, "nan" - assert_sprintf "%e", neg_nan, "nan" - assert_sprintf "%g", neg_nan, "nan" - assert_sprintf "%A", neg_nan, "NAN" - assert_sprintf "%E", neg_nan, "NAN" - assert_sprintf "%G", neg_nan, "NAN" - - assert_sprintf "%+f", pos_nan, "+nan" - assert_sprintf "%+f", neg_nan, "+nan" end end + else + pending "floats" end context "strings" do diff --git a/src/float/printer/ryu_printf.cr b/src/float/printer/ryu_printf.cr index 005e575c3806..feffb98fdb0e 100644 --- a/src/float/printer/ryu_printf.cr +++ b/src/float/printer/ryu_printf.cr @@ -1,5 +1,4 @@ -# FIXME: this leads to an OOB on wasm32 (#13918) -{% skip_file if flag?(:wasm32) %} +{% skip_file unless String::Formatter::HAS_RYU_PRINTF %} require "./ryu_printf_table" diff --git a/src/float/printer/ryu_printf_table.cr b/src/float/printer/ryu_printf_table.cr index 6e16b939f16c..b9db3363f072 100644 --- a/src/float/printer/ryu_printf_table.cr +++ b/src/float/printer/ryu_printf_table.cr @@ -1,5 +1,4 @@ -# FIXME: this leads to an OOB on wasm32 (#13918) -{% skip_file if flag?(:wasm32) %} +{% skip_file unless String::Formatter::HAS_RYU_PRINTF %} module Float::Printer::RyuPrintf {% begin %} diff --git a/src/string/formatter.cr b/src/string/formatter.cr index 31239f42ee4d..b5f80d2aee87 100644 --- a/src/string/formatter.cr +++ b/src/string/formatter.cr @@ -2,6 +2,13 @@ require "c/stdio" # :nodoc: struct String::Formatter(A) + # FIXME: wasm32 appears to run out of memory if we use Ryu Printf, which + # initializes very large lookup tables, so we fall back to `LibC.snprintf` for + # the floating-point format specifiers (#13918) + {% begin %} + HAS_RYU_PRINTF = {{ !flag?(:wasm32) }} + {% end %} + private enum Mode None @@ -15,15 +22,19 @@ struct String::Formatter(A) Named end - @format_buf = Pointer(UInt8).null - @temp_buf = Pointer(UInt8).null + {% unless HAS_RYU_PRINTF %} + @format_buf = Pointer(UInt8).null + @format_buf_len = 0 + + @temp_buf = Pointer(UInt8).null + @temp_buf_len = 0 + {% end %} + @arg_mode : Mode = :none def initialize(string, @args : A, @io : IO) @reader = Char::Reader.new(string) @arg_index = 0 - @temp_buf_len = 0 - @format_buf_len = 0 end def format : Nil @@ -118,13 +129,17 @@ struct String::Formatter(A) next else flags.width = val - flags.width_size = size + {% unless HAS_RYU_PRINTF %} + flags.width_size = size + {% end %} break end when '*' val = consume_dynamic_value flags.width = val - flags.width_size = val.to_s.size + {% unless HAS_RYU_PRINTF %} + flags.width_size = val.to_s.size + {% end %} break else break @@ -141,16 +156,22 @@ struct String::Formatter(A) when '0'..'9' num, size = consume_number flags.precision = num - flags.precision_size = size + {% unless HAS_RYU_PRINTF %} + flags.precision_size = size + {% end %} when '*' val = consume_dynamic_value if val >= 0 flags.precision = val - flags.precision_size = val.to_s.size + {% unless HAS_RYU_PRINTF %} + flags.precision_size = val.to_s.size + {% end %} end else flags.precision = 0 - flags.precision_size = 1 + {% unless HAS_RYU_PRINTF %} + flags.precision_size = 1 + {% end %} end end flags @@ -307,12 +328,7 @@ struct String::Formatter(A) elsif float.nan? float_special("nan", 1, flags) else - # FIXME: wasm32 appears to run out of memory if we use Ryu Printf, which - # initializes very large lookup tables, so we always fall back to - # `LibC.snprintf` (#13918) - {% if flag?(:wasm32) %} - float_fallback(float, flags) - {% else %} + {% if HAS_RYU_PRINTF %} case flags.type when 'f' float_fixed(float, flags) @@ -325,6 +341,8 @@ struct String::Formatter(A) else raise "BUG: Unknown format type '#{flags.type}'" end + {% else %} + float_fallback(float, flags) {% end %} end else @@ -346,7 +364,7 @@ struct String::Formatter(A) pad(str_size, flags) if flags.right_padding? end - {% unless flag?(:wasm32) %} + {% if HAS_RYU_PRINTF %} # Formats floats with `%f` private def float_fixed(float, flags) # the longest string possible is due to `Float64::MIN_SUBNORMAL`, which @@ -485,50 +503,78 @@ struct String::Formatter(A) pad(str_size, flags) if flags.right_padding? end - {% end %} + {% else %} + # Delegate to `LibC.snprintf` for float formats if Ryu Printf is unavailable + private def float_fallback(float, flags) + format_buf = recreate_float_format_string(flags) - # Delegate to `LibC.snprintf` for float formats not yet ported to Crystal - private def float_fallback(float, flags) - format_buf = recreate_float_format_string(flags) + len = LibC.snprintf(nil, 0, format_buf, float) + 1 + temp_buf = temp_buf(len) + LibC.snprintf(temp_buf, len, format_buf, float) - len = LibC.snprintf(nil, 0, format_buf, float) + 1 - temp_buf = temp_buf(len) - LibC.snprintf(temp_buf, len, format_buf, float) + @io.write_string Slice.new(temp_buf, len - 1) + end - @io.write_string Slice.new(temp_buf, len - 1) - end + # Here we rebuild the original format string, like %f or %.2g and use snprintf + private def recreate_float_format_string(flags) + capacity = 3 # percent + type + \0 + capacity += flags.width_size + capacity += flags.precision_size + 1 # size + . + capacity += 1 if flags.sharp + capacity += 1 if flags.plus + capacity += 1 if flags.minus + capacity += 1 if flags.zero + capacity += 1 if flags.space + + format_buf = format_buf(capacity) + original_format_buf = format_buf + + io = IO::Memory.new(Bytes.new(format_buf, capacity)) + io << '%' + io << '#' if flags.sharp + io << '+' if flags.plus + io << '-' if flags.minus + io << '0' if flags.zero + io << ' ' if flags.space + io << flags.width if flags.width > 0 + if precision = flags.precision + io << '.' + io << precision if precision != 0 + end + io << flags.type + io.write_byte 0_u8 - # Here we rebuild the original format string, like %f or %.2g and use snprintf - def recreate_float_format_string(flags) - capacity = 3 # percent + type + \0 - capacity += flags.width_size - capacity += flags.precision_size + 1 # size + . - capacity += 1 if flags.sharp - capacity += 1 if flags.plus - capacity += 1 if flags.minus - capacity += 1 if flags.zero - capacity += 1 if flags.space - - format_buf = format_buf(capacity) - original_format_buf = format_buf - - io = IO::Memory.new(Bytes.new(format_buf, capacity)) - io << '%' - io << '#' if flags.sharp - io << '+' if flags.plus - io << '-' if flags.minus - io << '0' if flags.zero - io << ' ' if flags.space - io << flags.width if flags.width > 0 - if precision = flags.precision - io << '.' - io << precision if precision != 0 + original_format_buf end - io << flags.type - io.write_byte 0_u8 - original_format_buf - end + # We reuse a temporary buffer for snprintf + private def temp_buf(len) + temp_buf = @temp_buf + if temp_buf + if len > @temp_buf_len + @temp_buf_len = len + @temp_buf = temp_buf = temp_buf.realloc(len) + end + temp_buf + else + @temp_buf = Pointer(UInt8).malloc(len) + end + end + + # We reuse a temporary buffer for the float format string + private def format_buf(len) + format_buf = @format_buf + if format_buf + if len > @format_buf_len + @format_buf_len = len + @format_buf = format_buf = format_buf.realloc(len) + end + format_buf + else + @format_buf = Pointer(UInt8).malloc(len) + end + end + {% end %} # HAS_RYU_PRINTF def pad(consumed, flags) : Nil padding_char = flags.padding_char @@ -586,48 +632,23 @@ struct String::Formatter(A) @reader.next_char end - # We reuse a temporary buffer for snprintf - private def temp_buf(len) - temp_buf = @temp_buf - if temp_buf - if len > @temp_buf_len - @temp_buf_len = len - @temp_buf = temp_buf = temp_buf.realloc(len) - end - temp_buf - else - @temp_buf = Pointer(UInt8).malloc(len) - end - end - - # We reuse a temporary buffer for the float format string - private def format_buf(len) - format_buf = @format_buf - if format_buf - if len > @format_buf_len - @format_buf_len = len - @format_buf = format_buf = format_buf.realloc(len) - end - format_buf - else - @format_buf = Pointer(UInt8).malloc(len) - end - end - struct Flags property space : Bool, sharp : Bool, plus : Bool, minus : Bool, zero : Bool, float : Bool, base : Int32 - property width : Int32, width_size : Int32 - property type : Char, precision : Int32?, precision_size : Int32 + property width : Int32 + property type : Char, precision : Int32? property index : Int32? + {% unless HAS_RYU_PRINTF %} + property width_size : Int32 = 0 + property precision_size : Int32 = 0 + {% end %} + def initialize @space = @sharp = @plus = @minus = @zero = @float = false @width = 0 - @width_size = 0 @base = 10 @type = ' ' @precision = nil - @precision_size = 0 end def left_padding? : Bool From e00a0a4f3788a8f5d85484bc22e7f9930b870b2b Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Mon, 25 Dec 2023 03:29:35 +1100 Subject: [PATCH 0869/1551] Add `Enumerable(T)#to_a(& : T -> U) forall U` (#12653) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/enumerable_spec.cr | 10 ++++++++++ src/enumerable.cr | 15 ++++++++++++--- src/hash.cr | 18 ++++++++++++++++-- src/indexable.cr | 10 +++++----- src/llvm/parameter_collection.cr | 2 +- src/named_tuple.cr | 15 ++++++++++++++- src/set.cr | 13 +++++++++++++ src/tuple.cr | 22 ++++++++++++++++++++-- 8 files changed, 91 insertions(+), 14 deletions(-) diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 04d46c7976a3..0bcbac725cb5 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -131,6 +131,16 @@ describe "Enumerable" do end end + describe "to_a" do + context "with a block" do + SpecEnumerable.new.to_a { |e| e*2 }.should eq [2, 4, 6] + end + + context "without a block" do + SpecEnumerable.new.to_a.should eq [1, 2, 3] + end + end + describe "#to_set" do context "without block" do it "creates a Set from the unique elements of the collection" do diff --git a/src/enumerable.cr b/src/enumerable.cr index c7b566e647e4..6b24c833f44f 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -1997,9 +1997,18 @@ module Enumerable(T) # ``` # (1..5).to_a # => [1, 2, 3, 4, 5] # ``` - def to_a - ary = [] of T - each { |e| ary << e } + def to_a : Array(T) + to_a(&.itself) + end + + # Returns an `Array` with the results of running *block* against each element of the collection. + # + # ``` + # (1..5).to_a { |i| i * 2 } # => [2, 4, 6, 8, 10] + # ``` + def to_a(& : T -> U) : Array(U) forall U + ary = [] of U + each { |e| ary << yield e } ary end diff --git a/src/hash.cr b/src/hash.cr index 8d8ccea22a0b..082f2a0a80e0 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -2055,16 +2055,30 @@ class Hash(K, V) pp.text "{...}" unless executed end - # Returns an array of tuples with key and values belonging to this Hash. + # Returns an `Array` of `Tuple(K, V)` with key and values belonging to this Hash. # # ``` # h = {1 => 'a', 2 => 'b', 3 => 'c'} # h.to_a # => [{1, 'a'}, {2, 'b'}, {3, 'c'}] # ``` + # # The order of the array follows the order the keys were inserted in the Hash. def to_a : Array({K, V}) + to_a(&.itself) + end + + # Returns an `Array` with the results of running *block* against tuples with key and values + # belonging to this Hash. + # + # ``` + # h = {"first_name" => "foo", "last_name" => "bar"} + # h.to_a { |_k, v| v.capitalize } # => ["Foo", "Bar"] + # ``` + # + # The order of the array follows the order the keys were inserted in the Hash. + def to_a(&block : {K, V} -> U) : Array(U) forall U to_a_impl do |entry| - {entry.key, entry.value} + yield ({entry.key, entry.value}) end end diff --git a/src/indexable.cr b/src/indexable.cr index 3725c1552e38..7496a95e0df0 100644 --- a/src/indexable.cr +++ b/src/indexable.cr @@ -693,14 +693,14 @@ module Indexable(T) end end - # Returns an `Array` with all the elements in the collection. + # Returns an `Array` with the results of running *block* against each element of the collection. # # ``` - # {1, 2, 3}.to_a # => [1, 2, 3] + # {1, 2, 3}.to_a { |i| i * 2 } # => [2, 4, 6] # ``` - def to_a : Array(T) - ary = Array(T).new(size) - each { |e| ary << e } + def to_a(& : T -> U) : Array(U) forall U + ary = Array(U).new(size) + each { |e| ary << yield e } ary end diff --git a/src/llvm/parameter_collection.cr b/src/llvm/parameter_collection.cr index 937d9ec2edcc..dd9a7eae91d7 100644 --- a/src/llvm/parameter_collection.cr +++ b/src/llvm/parameter_collection.cr @@ -8,7 +8,7 @@ struct LLVM::ParameterCollection LibLLVM.get_count_params(@function).to_i end - def to_a + def to_a : Array(LLVM::Value) param_size = size() Array(LLVM::Value).build(param_size) do |buffer| LibLLVM.get_params(@function, buffer.as(LibLLVM::ValueRef*)) diff --git a/src/named_tuple.cr b/src/named_tuple.cr index 7cceb568a260..4ea9df02fd20 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -588,12 +588,25 @@ struct NamedTuple # # NOTE: `to_a` on an empty named tuple produces an `Array(Tuple(Symbol, NoReturn))` def to_a + to_a(&.itself) + end + + # Returns an `Array` with the results of running *block* against tuples with key and values belonging + # to this `NamedTuple`. + # + # ``` + # tuple = {first_name: "foo", last_name: "bar"} + # tuple.to_a(&.last.capitalize) # => ["Foo", "Bar"] + # ``` + # + # NOTE: `to_a` on an empty named tuple produces an `Array(Tuple(Symbol, NoReturn))` + def to_a(&) {% if T.size == 0 %} [] of {Symbol, NoReturn} {% else %} [ {% for key in T %} - { {{key.symbolize}}, self[{{key.symbolize}}] }, + yield({ {{key.symbolize}}, self[{{key.symbolize}}] }), {% end %} ] {% end %} diff --git a/src/set.cr b/src/set.cr index 091352302c7d..40425e9aa032 100644 --- a/src/set.cr +++ b/src/set.cr @@ -382,6 +382,19 @@ struct Set(T) @hash.keys end + # Returns an `Array` with the results of running *block* against each element of the collection. + # + # ``` + # Set{1, 2, 3, 4, 5}.to_a { |i| i // 2 } # => [0, 1, 2] + # ``` + def to_a(& : T -> U) : Array(U) forall U + array = Array(U).new(size) + @hash.each_key do |key| + array << key + end + array + end + # Alias of `#to_s`. def inspect(io : IO) : Nil to_s(io) diff --git a/src/tuple.cr b/src/tuple.cr index d67578271363..2f9cde352e4f 100644 --- a/src/tuple.cr +++ b/src/tuple.cr @@ -539,10 +539,28 @@ struct Tuple to_s end - def to_a + # Returns an `Array` with all the elements in the tuple. + # + # ``` + # {1, 2, 3, 4, 5}.to_a # => [1, 2, 3, 4, 5] + # ``` + def to_a : Array(Union(*T)) + {% if compare_versions(Crystal::VERSION, "1.1.0") < 0 %} + to_a(&.itself.as(Union(*T))) + {% else %} + to_a(&.itself) + {% end %} + end + + # Returns an `Array` with the results of running *block* against each element of the tuple. + # + # ``` + # {1, 2, 3, 4, 5}).to_a { |i| i * 2 } # => [2, 4, 6, 8, 10] + # ``` + def to_a(& : Union(*T) -> _) Array(Union(*T)).build(size) do |buffer| {% for i in 0...T.size %} - buffer[{{i}}] = self[{{i}}] + buffer[{{i}}] = yield self[{{i}}] {% end %} size end From d1863053d82fa3a570fdf72eaccbc2d8d676876b Mon Sep 17 00:00:00 2001 From: syeopite <70992037+syeopite@users.noreply.github.com> Date: Sat, 30 Dec 2023 12:58:43 +0000 Subject: [PATCH 0870/1551] Add docs and explicit type restriction for indent parameter of JSON.build (#14140) --- src/json/builder.cr | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/json/builder.cr b/src/json/builder.cr index 9bc074f7c6ef..c1dc2c39c8e8 100644 --- a/src/json/builder.cr +++ b/src/json/builder.cr @@ -430,7 +430,10 @@ module JSON # end # string # => %<{"name":"foo","values":[1,2,3]}> # ``` - def self.build(indent = nil, &) + # + # Accepts an indent parameter which can either be an `Int` (number of spaces to indent) + # or a `String`, which will prefix each level with the string a corresponding amount of times. + def self.build(indent : String | Int | Nil = nil, &) String.build do |str| build(str, indent) do |json| yield json @@ -439,7 +442,7 @@ module JSON end # Writes JSON into the given `IO`. A `JSON::Builder` is yielded to the block. - def self.build(io : IO, indent = nil, &) : Nil + def self.build(io : IO, indent : String | Int | Nil = nil, &) : Nil builder = JSON::Builder.new(io) builder.indent = indent if indent builder.document do From 6e1c631fe24062c5288423b87c79752c07ae3b5b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 30 Dec 2023 20:59:03 +0800 Subject: [PATCH 0871/1551] Do not use `pointerof(Path)` in the standard library (#14144) --- src/crystal/system/win32/path.cr | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/crystal/system/win32/path.cr b/src/crystal/system/win32/path.cr index a5e60db82576..06f9346a2bae 100644 --- a/src/crystal/system/win32/path.cr +++ b/src/crystal/system/win32/path.cr @@ -6,12 +6,16 @@ module Crystal::System::Path def self.home : String if home_path = ENV["USERPROFILE"]?.presence home_path - elsif LibC.SHGetKnownFolderPath(pointerof(LibC::FOLDERID_Profile), 0, nil, out path_ptr) == 0 - home_path, _ = String.from_utf16(path_ptr) - LibC.CoTaskMemFree(path_ptr) - home_path else - raise RuntimeError.from_winerror("SHGetKnownFolderPath") + # TODO: interpreter doesn't implement pointerof(Path)` yet + folderid = LibC::FOLDERID_Profile + if LibC.SHGetKnownFolderPath(pointerof(folderid), 0, nil, out path_ptr) == 0 + home_path, _ = String.from_utf16(path_ptr) + LibC.CoTaskMemFree(path_ptr) + home_path + else + raise RuntimeError.from_winerror("SHGetKnownFolderPath") + end end end end From 24d2c57ea2f61e51dfa6093e24ce13ec9636949c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 30 Dec 2023 13:59:47 +0100 Subject: [PATCH 0872/1551] Update distribution-scripts (#14136) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 957f3979b110..39ae49d81ee3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "e15cbd3b6b3e1bac1b16905f1b1a15ba6ae4e554" + default: "535aedb8b09d77caaa1583700f371cd04343b7e8" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From f39413637e4ae805f6ce6b95474deec243589a87 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 31 Dec 2023 12:08:03 +0100 Subject: [PATCH 0873/1551] Update GH Actions to v4 (#14120) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/aarch64.yml | 12 ++++++------ .github/workflows/interpreter.yml | 4 ++-- .github/workflows/win.yml | 6 +++--- .github/workflows/win_build_portable.yml | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index 2671fe5d61d6..da252904fa37 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -18,7 +18,7 @@ jobs: with: args: make crystal - name: Upload Crystal executable - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: crystal-aarch64-musl path: | @@ -32,7 +32,7 @@ jobs: - name: Download Crystal source uses: actions/checkout@v4 - name: Download Crystal executable - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: crystal-aarch64-musl - name: Mark downloaded compiler as executable @@ -49,7 +49,7 @@ jobs: - name: Download Crystal source uses: actions/checkout@v4 - name: Download Crystal executable - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: crystal-aarch64-musl - name: Mark downloaded compiler as executable @@ -69,7 +69,7 @@ jobs: with: args: make crystal - name: Upload Crystal executable - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: crystal-aarch64-gnu path: | @@ -83,7 +83,7 @@ jobs: - name: Download Crystal source uses: actions/checkout@v4 - name: Download Crystal executable - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: crystal-aarch64-gnu - name: Mark downloaded compiler as executable @@ -100,7 +100,7 @@ jobs: - name: Download Crystal source uses: actions/checkout@v4 - name: Download Crystal executable - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: crystal-aarch64-gnu - name: Mark downloaded compiler as executable diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index a034a9f5b410..2cd37d4b6579 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -33,7 +33,7 @@ jobs: run: make interpreter=1 release=1 - name: Upload compiler artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: crystal-interpreter path: | @@ -52,7 +52,7 @@ jobs: - uses: actions/checkout@v4 - name: Download compiler artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: crystal-interpreter path: .build/ diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 90ed0e0c980d..da66d568835a 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -252,7 +252,7 @@ jobs: uses: actions/checkout@v4 - name: Download Crystal executable - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: crystal path: build @@ -303,7 +303,7 @@ jobs: uses: actions/checkout@v4 - name: Download Crystal executable - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: crystal-release path: etc/win-ci/portable @@ -329,7 +329,7 @@ jobs: iscc.exe crystal.iss - name: Upload Crystal installer - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: crystal-installer path: etc/win-ci/Output/crystal-setup.exe diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 21d849c2dcf0..6222aaee3055 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -144,7 +144,7 @@ jobs: cp README.md crystal/ - name: Upload Crystal binaries - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ inputs.release && 'crystal-release' || 'crystal' }} path: crystal From 6d177399d437bc6cbafa9305dd69a22a8e74e559 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 31 Dec 2023 19:08:24 +0800 Subject: [PATCH 0874/1551] Fix interpreter specs on Windows (#14145) --- spec/compiler/interpreter/lib_spec.cr | 24 +++++++++++++++++---- spec/compiler/interpreter/spec_helper.cr | 1 + src/compiler/crystal/interpreter/context.cr | 5 +++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/spec/compiler/interpreter/lib_spec.cr b/spec/compiler/interpreter/lib_spec.cr index 98e1da7fd294..2c1798645645 100644 --- a/spec/compiler/interpreter/lib_spec.cr +++ b/spec/compiler/interpreter/lib_spec.cr @@ -2,6 +2,22 @@ require "./spec_helper" require "../loader/spec_helper" +private def ldflags + {% if flag?(:win32) %} + "/LIBPATH:#{SPEC_CRYSTAL_LOADER_LIB_PATH} sum.lib" + {% else %} + "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -lsum" + {% end %} +end + +private def ldflags_with_backtick + {% if flag?(:win32) %} + "/LIBPATH:#{SPEC_CRYSTAL_LOADER_LIB_PATH} `powershell.exe -C Write-Host -NoNewline sum.lib`" + {% else %} + "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -l`echo sum`" + {% end %} +end + describe Crystal::Repl::Interpreter do context "variadic calls" do before_all do @@ -11,7 +27,7 @@ describe Crystal::Repl::Interpreter do it "promotes float" do interpret(<<-CRYSTAL).should eq 3.5 - @[Link(ldflags: "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -lsum")] + @[Link(ldflags: #{ldflags.inspect})] lib LibSum fun sum_float(count : Int32, ...) : Float32 end @@ -22,7 +38,7 @@ describe Crystal::Repl::Interpreter do it "promotes int" do interpret(<<-CRYSTAL).should eq 5 - @[Link(ldflags: "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -lsum")] + @[Link(ldflags: #{ldflags.inspect})] lib LibSum fun sum_int(count : Int32, ...) : Int32 end @@ -33,7 +49,7 @@ describe Crystal::Repl::Interpreter do it "promotes enum" do interpret(<<-CRYSTAL).should eq 5 - @[Link(ldflags: "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -lsum")] + @[Link(ldflags: #{ldflags.inspect})] lib LibSum fun sum_int(count : Int32, ...) : Int32 end @@ -63,7 +79,7 @@ describe Crystal::Repl::Interpreter do it "expands ldflags" do interpret(<<-CRYSTAL).should eq 4 - @[Link(ldflags: "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -l`echo sum`")] + @[Link(ldflags: #{ldflags_with_backtick.inspect})] lib LibSum fun simple_sum_int(a : Int32, b : Int32) : Int32 end diff --git a/spec/compiler/interpreter/spec_helper.cr b/spec/compiler/interpreter/spec_helper.cr index c3b0dcaca8cd..e6aeb240674c 100644 --- a/spec/compiler/interpreter/spec_helper.cr +++ b/spec/compiler/interpreter/spec_helper.cr @@ -5,6 +5,7 @@ require "compiler/crystal/interpreter/*" def interpret(code, *, prelude = "primitives", file = __FILE__, line = __LINE__) if prelude == "primitives" context, value = interpret_with_context(code) + context.loader?.try &.close_all value.value else interpret_in_separate_process(code, prelude, file: file, line: line) diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr index f4a4444e105c..94456b4cec0e 100644 --- a/src/compiler/crystal/interpreter/context.cr +++ b/src/compiler/crystal/interpreter/context.cr @@ -46,6 +46,9 @@ class Crystal::Repl::Context def initialize(@program : Program) @program.flags << "interpreted" + {% if flag?(:win32) %} + @program.flags << "preview_dll" + {% end %} @gc_references = [] of Void* @@ -392,6 +395,8 @@ class Crystal::Repl::Context @id_to_type[id] end + getter? loader : Loader? + getter(loader : Loader) { lib_flags = program.lib_flags # Execute and expand `subcommands`. From 866d51d42bdd25839b0c0010fb96948fd795c2d9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 31 Dec 2023 19:08:33 +0800 Subject: [PATCH 0875/1551] Support `-dynamic.lib` in Windows interpreter (#14143) --- spec/compiler/loader/msvc_spec.cr | 26 ++++++++++++++++++++++++++ spec/compiler/loader/spec_helper.cr | 4 ++-- src/compiler/crystal/loader/msvc.cr | 11 ++++++++--- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/spec/compiler/loader/msvc_spec.cr b/spec/compiler/loader/msvc_spec.cr index fedb21c352c4..5893d29aee02 100644 --- a/spec/compiler/loader/msvc_spec.cr +++ b/spec/compiler/loader/msvc_spec.cr @@ -125,4 +125,30 @@ describe Crystal::Loader do end end end + + describe "lib suffix" do + before_all do + FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH) + end + + after_all do + FileUtils.rm_rf(SPEC_CRYSTAL_LOADER_LIB_PATH) + end + + it "respects -dynamic" do + build_c_dynlib(compiler_datapath("loader", "foo.c"), lib_name: "foo-dynamic") + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String) + loader.load_library?("foo").should be_true + ensure + loader.close_all if loader + end + + it "ignores -static" do + build_c_dynlib(compiler_datapath("loader", "foo.c"), lib_name: "bar-static") + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String) + loader.load_library?("bar").should be_false + ensure + loader.close_all if loader + end + end end diff --git a/spec/compiler/loader/spec_helper.cr b/spec/compiler/loader/spec_helper.cr index 282af7871f81..0db69dc19752 100644 --- a/spec/compiler/loader/spec_helper.cr +++ b/spec/compiler/loader/spec_helper.cr @@ -2,8 +2,8 @@ require "spec" SPEC_CRYSTAL_LOADER_LIB_PATH = File.join(SPEC_TEMPFILE_PATH, "loader") -def build_c_dynlib(c_filename, target_dir = SPEC_CRYSTAL_LOADER_LIB_PATH) - o_filename = File.join(target_dir, Crystal::Loader.library_filename(File.basename(c_filename, ".c"))) +def build_c_dynlib(c_filename, *, lib_name = nil, target_dir = SPEC_CRYSTAL_LOADER_LIB_PATH) + o_filename = File.join(target_dir, Crystal::Loader.library_filename(lib_name || File.basename(c_filename, ".c"))) {% if flag?(:msvc) %} o_basename = o_filename.rchop(".lib") diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index bf0b5c74a36f..d4b899db0e87 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -60,7 +60,7 @@ class Crystal::Loader result end - private def self.search_library(libname, search_paths, extra_suffix) + protected def self.search_library(libname, search_paths, extra_suffix) if ::Path::SEPARATORS.any? { |separator| libname.includes?(separator) } libname = File.expand_path(libname) library_path = library_filename(libname) @@ -106,7 +106,7 @@ class Crystal::Loader end def self.library_filename(libname : String) : String - "#{libname}.lib" + "#{libname.rchop(".lib")}.lib" end def find_symbol?(name : String) : Handle? @@ -149,8 +149,13 @@ class Crystal::Loader load_library?(libname) || raise LoadError.from_winerror "cannot find #{Loader.library_filename(libname)}" end + def load_library?(libname : String) : Bool + library_path = Loader.search_library(libname, @search_paths, "-dynamic") + !library_path.nil? && load_file?(library_path) + end + private def open_library(path : String) - # TODO: respect Crystal::LIBRARY_RPATH (#13490) + # TODO: respect Crystal::LIBRARY_RPATH (#13490), or `@[Link(dll:)]`'s search order LibC.LoadLibraryExW(System.to_wstr(path), nil, 0) end From 4b5ef59994ed776e46fdfc23e16f1aa885632f67 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Mon, 1 Jan 2024 09:53:39 -0500 Subject: [PATCH 0876/1551] Add reference to book how merging macro expansion and call docs (#14139) --- src/compiler/crystal/macros.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 983af69ae4b7..90c802bad25e 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -442,6 +442,8 @@ module Crystal::Macros # Returns a `MacroId` that contains the documentation comments attached to this node, or an empty `MacroId` if there are none. # Each line is prefixed with a `#` character to allow the output to be used directly within another node's documentation comment. # + # A common use case is combining this method with the `@caller` macro instance variable in order to allow [merging macro expansion and call comments](https://crystal-lang.org/reference/syntax_and_semantics/macros/index.html#merging-expansion-and-call-comments). + # # WARNING: The return value will be empty when executed outside of the `crystal docs` command. def doc_comment : MacroId end From 6bfc90dfaf5a3f36926b0c4d139691216fd843cf Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 1 Jan 2024 22:53:46 +0800 Subject: [PATCH 0877/1551] Support absolute paths in `CRYSTAL_INTERPRETER_LOADER_INFO` (#14147) --- src/compiler/crystal/interpreter/context.cr | 12 ++++-------- src/compiler/crystal/loader/msvc.cr | 16 +++++++++++++++- src/compiler/crystal/loader/unix.cr | 1 + 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr index 94456b4cec0e..d597810b36de 100644 --- a/src/compiler/crystal/interpreter/context.cr +++ b/src/compiler/crystal/interpreter/context.cr @@ -410,13 +410,6 @@ class Crystal::Repl::Context args.delete("-lgc") Crystal::Loader.parse(args).tap do |loader| - if ENV["CRYSTAL_INTERPRETER_LOADER_INFO"]?.presence - STDERR.puts "Crystal::Loader loaded libraries:" - loader.loaded_libraries.each do |path| - STDERR.puts " #{path}" - end - end - # FIXME: Part 2: This is a workaround for initial integration of the interpreter: # We append a handle to the current executable (i.e. the compiler program) # to the loader's handle list. This gives the loader access to all the symbols in the compiler program, @@ -426,7 +419,10 @@ class Crystal::Repl::Context loader.load_current_program_handle if ENV["CRYSTAL_INTERPRETER_LOADER_INFO"]?.presence - STDERR.puts " current program handle" + STDERR.puts "Crystal::Loader loaded libraries:" + loader.loaded_libraries.each do |path| + STDERR.puts " #{path}" + end end end } diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index d4b899db0e87..e9fdc9af5bbd 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -139,7 +139,7 @@ class Crystal::Loader return false unless handle @handles << handle - @loaded_libraries << dll + @loaded_libraries << (module_filename(handle) || dll) end true @@ -162,6 +162,7 @@ class Crystal::Loader def load_current_program_handle if LibC.GetModuleHandleExW(0, nil, out hmodule) != 0 @handles << hmodule + @loaded_libraries << (Process.executable_path || "current program handle") end end @@ -172,6 +173,19 @@ class Crystal::Loader @handles.clear end + private def module_filename(handle) + Crystal::System.retry_wstr_buffer do |buffer, small_buf| + len = LibC.GetModuleFileNameW(handle, buffer, buffer.size) + if 0 < len < buffer.size + break String.from_utf16(buffer[0, len]) + elsif small_buf && len == buffer.size + next 32767 # big enough. 32767 is the maximum total path length of UNC path. + else + break nil + end + end + end + # Returns a list of directories used as the default search paths. # # For MSVC this is simply the contents of the `LIB` environment variable, diff --git a/src/compiler/crystal/loader/unix.cr b/src/compiler/crystal/loader/unix.cr index 4c202231d7e6..39fef6e0c318 100644 --- a/src/compiler/crystal/loader/unix.cr +++ b/src/compiler/crystal/loader/unix.cr @@ -120,6 +120,7 @@ class Crystal::Loader def load_current_program_handle if program_handle = LibC.dlopen(nil, LibC::RTLD_LAZY | LibC::RTLD_GLOBAL) @handles << program_handle + @loaded_libraries << (Process.executable_path || "current program handle") end end From f3de7b43714e8ff771fcf838ed6accc27c21da55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 1 Jan 2024 20:52:06 +0100 Subject: [PATCH 0878/1551] Merge `samples/.gitignore` into `.gitignore` (#14134) --- .gitignore | 1 + Makefile | 1 - Makefile.win | 1 - samples/.gitignore | 1 - 4 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 samples/.gitignore diff --git a/.gitignore b/.gitignore index 5d74450c533f..987e8a649620 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # Build artifacts /.build/ +/samples/.build/ /docs/ /src/llvm/ext/llvm_ext.o /src/llvm/ext/llvm_ext.obj diff --git a/Makefile b/Makefile index 7e8070d16921..4682c7aebec9 100644 --- a/Makefile +++ b/Makefile @@ -182,7 +182,6 @@ install_docs: docs ## Install docs at DESTDIR cp -av docs "$(DATADIR)/docs" cp -av samples "$(DATADIR)/examples" - rm -rf "$(DATADIR)/examples/.gitignore" .PHONY: uninstall_docs uninstall_docs: ## Uninstall docs from DESTDIR diff --git a/Makefile.win b/Makefile.win index e2b18741657e..da1d9fc8328a 100644 --- a/Makefile.win +++ b/Makefile.win @@ -171,7 +171,6 @@ install_docs: docs ## Install docs at prefix $(call MKDIR,"$(DATADIR)") $(call INSTALLDIR,docs,"$(DATADIR)\docs") $(call INSTALLDIR,samples,"$(DATADIR)\examples") - $(call RM,"$(DATADIR)\examples\.gitignore") .PHONY: uninstall_docs uninstall_docs: ## Uninstall docs from prefix diff --git a/samples/.gitignore b/samples/.gitignore deleted file mode 100644 index 0e03e15f2420..000000000000 --- a/samples/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/.build/ From d15435dc822be0b18a7ff2bf823a564d9ef5cc13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 1 Jan 2024 20:52:13 +0100 Subject: [PATCH 0879/1551] Fix `make clean` to remove zipped manpages (#14135) --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 4682c7aebec9..2a7f9d43869f 100644 --- a/Makefile +++ b/Makefile @@ -229,6 +229,7 @@ man/%.gz: man/% .PHONY: clean clean: clean_crystal ## Clean up built directories and files rm -rf $(LLVM_EXT_OBJ) + rm -rf man/*.gz .PHONY: clean_crystal clean_crystal: ## Clean up crystal built files From 316422fb800826994c4623edb837f57b74f7a361 Mon Sep 17 00:00:00 2001 From: "Brian J. Cardiff" Date: Tue, 2 Jan 2024 10:34:25 -0300 Subject: [PATCH 0880/1551] Add Crystal::Repl#parse_and_interpret (#14138) * Add Crystal::Repl#parse_and_interpret * Use single EvalResult type * skip if without_interpreter --- spec/compiler/crystal/tools/repl_spec.cr | 20 ++++++++++++++++++++ src/compiler/crystal/interpreter/repl.cr | 24 +++++++++++++++++------- 2 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 spec/compiler/crystal/tools/repl_spec.cr diff --git a/spec/compiler/crystal/tools/repl_spec.cr b/spec/compiler/crystal/tools/repl_spec.cr new file mode 100644 index 000000000000..3a1e1275ef12 --- /dev/null +++ b/spec/compiler/crystal/tools/repl_spec.cr @@ -0,0 +1,20 @@ +{% skip_file if flag?(:without_interpreter) %} + +require "../../../spec_helper" + +private def success_value(result : Crystal::Repl::EvalResult) : Crystal::Repl::Value + result.warnings.infos.should be_empty + result.value.should_not be_nil +end + +describe Crystal::Repl do + it "can parse and evaluate snippets" do + repl = Crystal::Repl.new + repl.prelude = "primitives" + repl.load_prelude + + success_value(repl.parse_and_interpret("1 + 2")).value.should eq(3) + success_value(repl.parse_and_interpret("def foo; 1 + 2; end")).value.should eq(nil) + success_value(repl.parse_and_interpret("foo")).value.should eq(3) + end +end diff --git a/src/compiler/crystal/interpreter/repl.cr b/src/compiler/crystal/interpreter/repl.cr index 93b3a6cef65c..2c009a89e87d 100644 --- a/src/compiler/crystal/interpreter/repl.cr +++ b/src/compiler/crystal/interpreter/repl.cr @@ -22,15 +22,13 @@ class Crystal::Repl when "exit" break when .presence - parser = new_parser(expression) - parser.warnings.report(STDOUT) + result = parse_and_interpret(expression) + result.warnings.report(STDOUT) - node = parser.parse - next unless node + next unless result.value - value = interpret(node) print " => " - puts SyntaxHighlighter::Colorize.highlight!(value.to_s) + puts SyntaxHighlighter::Colorize.highlight!(result.value.to_s) end rescue ex : EscapingException print "Unhandled exception: " @@ -44,6 +42,18 @@ class Crystal::Repl end end + record EvalResult, value : Value?, warnings : WarningCollection + + def parse_and_interpret(expression : String) : EvalResult + parser = new_parser(expression) + + node = parser.parse + return EvalResult.new(value: nil, warnings: parser.warnings) unless node + + value = interpret(node) + return EvalResult.new(value: value, warnings: parser.warnings) + end + def run_file(filename, argv) @interpreter.argv = argv @@ -68,7 +78,7 @@ class Crystal::Repl interpret(exps) end - private def load_prelude + def load_prelude node = parse_prelude interpret_and_exit_on_error(node) From 8be53a45193465b0aef87d1623eb9de736daa53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 3 Jan 2024 11:07:39 +0100 Subject: [PATCH 0881/1551] Revert "Add compile time string interpolation for char/num/bool constants" (#14155) --- .../normalize/string_interpolation_spec.cr | 39 ------------- .../crystal/semantic/cleanup_transformer.cr | 55 +++++++++++-------- 2 files changed, 31 insertions(+), 63 deletions(-) diff --git a/spec/compiler/normalize/string_interpolation_spec.cr b/spec/compiler/normalize/string_interpolation_spec.cr index 1347792170b0..f92e6c2a200f 100644 --- a/spec/compiler/normalize/string_interpolation_spec.cr +++ b/spec/compiler/normalize/string_interpolation_spec.cr @@ -52,43 +52,4 @@ describe "Normalize: string interpolation" do string = node.should be_a(StringLiteral) string.value.should eq("hello world") end - - it "replaces char constant" do - result = semantic(%( - def String.interpolation(*args); ""; end - - OBJ = 'l' - - "hello wor\#{OBJ}d" - )) - node = result.node.as(Expressions).last - string = node.should be_a(StringLiteral) - string.value.should eq("hello world") - end - - it "replaces number constant" do - result = semantic(%( - def String.interpolation(*args); ""; end - - OBJ = 9_f32 - - "nine as a float: \#{OBJ}" - )) - node = result.node.as(Expressions).last - string = node.should be_a(StringLiteral) - string.value.should eq("nine as a float: 9.0") - end - - it "replaces boolean constant" do - result = semantic(%( - def String.interpolation(*args); ""; end - - OBJ = false - - "boolean false: \#{OBJ}" - )) - node = result.node.as(Expressions).last - string = node.should be_a(StringLiteral) - string.value.should eq("boolean false: false") - end end diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index ad1b570f0cbd..bc3084429ed3 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -256,28 +256,35 @@ module Crystal end def transform(node : StringInterpolation) - string = node.expressions.join do |exp| - if !(transformed_piece = solve_string_interpolation_expression(exp)).nil? - # Valid piece, continue joining - next transformed_piece - elsif expanded = node.expanded - # Invalid piece, transform expansion and exit early - return expanded.transform(self) - else - # No expansion, return self - return node - end + # See if we can solve all the pieces to string literals. + # If that's the case, we can replace the entire interpolation + # with a single string literal. + pieces = node.expressions.dup + solve_string_interpolation_expressions(pieces) + + if pieces.all?(StringLiteral) + string = pieces.join(&.as(StringLiteral).value) + string_literal = StringLiteral.new(string).at(node) + string_literal.type = @program.string + return string_literal + end + + if expanded = node.expanded + return expanded.transform(self) end - string_literal = StringLiteral.new(string).at(node) - string_literal.type = @program.string - string_literal + node end - # Returns the solved piece for string interpolation, if it can find one. - # For example, this returns a String when given a StringLiteral. - private def solve_string_interpolation_expression(piece : ASTNode) : String | Char | Number::Primitive | Bool | Nil - # Check for ExpandableNode happens first in case any nodes below are - # updated to be ExpandableNodes themselves. + private def solve_string_interpolation_expressions(pieces : Array(ASTNode)) + pieces.each_with_index do |piece, i| + replacement = solve_string_interpolation_expression(piece) + next unless replacement + + pieces[i] = replacement + end + end + + private def solve_string_interpolation_expression(piece : ASTNode) : StringLiteral? if piece.is_a?(ExpandableNode) if expanded = piece.expanded return solve_string_interpolation_expression(expanded) @@ -287,13 +294,13 @@ module Crystal case piece when Path if target_const = piece.target_const - solve_string_interpolation_expression(target_const.value) + return solve_string_interpolation_expression(target_const.value) end - when StringLiteral then piece.value - when CharLiteral then piece.value - when NumberLiteral then piece.to_number - when BoolLiteral then piece.value + when StringLiteral + return piece end + + nil end def transform(node : ExpandableNode) From 382041cc25b025f74938b131ac0960012faeb60f Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 3 Jan 2024 12:54:57 +0100 Subject: [PATCH 0882/1551] Fix: segfault with next boehm gc (after v8.2.4) (#14130) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/gc/boehm.cr | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 6c1ff5020cbf..8278f33b8e94 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -22,13 +22,17 @@ {% if flag?(:freebsd) || flag?(:dragonfly) %} @[Link("gc-threaded")] {% else %} - @[Link("gc")] + @[Link("gc", pkg_config: "bdw-gc")] {% end %} {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} @[Link(dll: "gc.dll")] {% end %} lib LibGC + {% unless flag?(:win32) %} + VERSION = {{ `pkg-config bdw-gc --silence-errors --modversion || printf "0.0.0"`.chomp.stringify }} + {% end %} + alias Int = LibC::Int alias SizeT = LibC::SizeT {% if flag?(:win32) && flag?(:bits64) %} @@ -96,7 +100,7 @@ lib LibGC fun push_all_eager = GC_push_all_eager(bottom : Void*, top : Void*) - {% if flag?(:preview_mt) || flag?(:win32) %} + {% if flag?(:preview_mt) || flag?(:win32) || compare_versions(VERSION, "8.2.0") >= 0 %} fun get_my_stackbottom = GC_get_my_stackbottom(sb : StackBase*) : ThreadHandle fun set_stackbottom = GC_set_stackbottom(th : ThreadHandle, sb : StackBase*) : ThreadHandle {% else %} @@ -277,10 +281,11 @@ module GC # :nodoc: def self.current_thread_stack_bottom - {% if flag?(:preview_mt) || flag?(:win32) %} + {% if LibGC.has_method?(:get_my_stackbottom) %} th = LibGC.get_my_stackbottom(out sb) {th, sb.mem_base} {% else %} + # support for legacy gc releases {Pointer(Void).null, LibGC.stackbottom} {% end %} end @@ -292,10 +297,11 @@ module GC sb.mem_base = stack_bottom LibGC.set_stackbottom(thread_handle, pointerof(sb)) end - {% elsif flag?(:win32) %} + {% elsif LibGC.has_method?(:set_stackbottom) %} # this is necessary because Boehm GC does _not_ use `GC_stackbottom` on - # Windows when pushing all threads' stacks; instead `GC_set_stackbottom` - # must be used to associate the new bottom with the running thread + # Windows when pushing all threads' stacks; it also started crashing on + # Linux with libgc after v8.2.4; instead `GC_set_stackbottom` must be used + # to associate the new bottom with the running thread def self.set_stackbottom(stack_bottom : Void*) sb = LibGC::StackBase.new sb.mem_base = stack_bottom @@ -303,6 +309,7 @@ module GC LibGC.set_stackbottom(nil, pointerof(sb)) end {% else %} + # support for legacy gc releases def self.set_stackbottom(stack_bottom : Void*) LibGC.stackbottom = stack_bottom end From 5e13683544b0a06aa41a41b5f6d22fd56b8d7bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 3 Jan 2024 12:55:21 +0100 Subject: [PATCH 0883/1551] Fix compiler error message for broken source file (#14157) --- src/compiler/crystal/command.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 69b4ab2d221c..97bf46feb663 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -627,6 +627,8 @@ class Crystal::Command filename = File.expand_path(filename) Compiler::Source.new(filename, File.read(filename)) end + rescue exc : IO::Error + error exc end private def setup_simple_compiler_options(compiler, opts) From 07c09cfae7fbe81bf9b6012de4b074c25c5bb686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 3 Jan 2024 19:44:52 +0100 Subject: [PATCH 0884/1551] Add optimization levels to manpage (#14162) --- man/crystal.1 | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/man/crystal.1 b/man/crystal.1 index 5f08ce29d07e..508b2ab44cb9 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -140,8 +140,14 @@ Don't do code generation, just parse the file. Specify filename of output. .It Fl -prelude Specify prelude to use. The default one initializes the garbage collector. You can also use --prelude=empty to use no preludes. This can be useful for checking code generation for a specific source code file. +.It Fl O Ar LEVEL +Optimization mode: 0 (default), 1, 2, 3. See +.Sy OPTIMIZATIONS +for details. .It Fl -release -Turn on optimizations for the generated code, which are disabled by default. +Compile in release mode. Equivalent to +.Fl O3 +.Fl -single-module .It Fl -error-trace Show full stack trace. Disabled by default, as the full trace usually makes error messages less readable and not always deliver relevant information. .It Fl s, -stats @@ -152,6 +158,9 @@ Print statistics about the progress for the current build. Print statistics about the execution time. .It Fl -single-module Generate a single LLVM module. +By default, one LLVM module is created for each type in a program. +.Fl -release +implies this option. .It Fl -threads Ar NUM Maximum number of threads to use for code generation. The default is 8 threads. .It Fl -target Ar TRIPLE @@ -265,8 +274,14 @@ Generate the output without any symbolic debug symbols. Define a compile-time flag. This is useful to conditionally define types, methods, or commands based on flags available at compile time. The default flags are from the target triple given with --target-triple or the hosts default, if none is given. .It Fl -error-trace Show full error trace. +.It Fl O Ar LEVEL +Optimization mode: 0 (default), 1, 2, 3. See +.Sy OPTIMIZATIONS +for details. .It Fl -release -Turn on optimizations for the generated code, which are disabled by default. +Compile in release mode. Equivalent to +.Fl O3 +.Fl -single-module .It Fl s, -stats Print statistics about the different compiler stages for the current build. Output time and used memory for each compiler process. .It Fl p, -progress @@ -326,8 +341,14 @@ Generate the output without any symbolic debug symbols. Define a compile-time flag. This is useful to conditionally define types, methods, or commands based on flags available at compile time. The default flags are from the target triple given with --target-triple or the hosts default, if none is given. .It Fl -error-trace Show full error trace. +.It Fl O Ar LEVEL +Optimization mode: 0 (default), 1, 2, 3. See +.Sy OPTIMIZATIONS +for details. .It Fl -release -Turn on optimizations for the generated code, which are disabled by default. +Compile in release mode. Equivalent to +.Fl O3 +.Fl -single-module .It Fl s, -stats Print statistics about the different compiler stages for the current build. Output time and used memory for each compiler process. .It Fl p, -progress @@ -410,6 +431,28 @@ Show help. Option --help or -h can also be added to each command for command-spe Show version. .El . +.Sh OPTIMIZATIONS +.Bl -tag -width "12345678" -compact +.Pp +The optimization level specifies the codegen effort for producing optimal code. +It's a trade-off between compilation performance (decreasing per optimization level) and runtime performance (increasing per optimization level). +.Pp +Production builds should usually have the highest optimization level. +Best results are achieved with +.Fl -release + which also implies +.Fl -single-module +.Pp +.It +.It Fl O0 +No optimization (default) +.It Fl O1 +Low optimization +.It Fl O2 +Middle optimization +.It Fl O3 +High optimization +. .Sh ENVIRONMENT VARIABLES .Bl -tag -width "12345678" -compact .Pp From 2afc7c1cda5295950e045bf00c665552eff3196c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 4 Jan 2024 18:06:46 +0800 Subject: [PATCH 0885/1551] Fix codegen error when discarding `is_a?` or `responds_to?`'s result (#14148) --- spec/compiler/codegen/is_a_spec.cr | 10 ++++++++++ spec/compiler/codegen/responds_to_spec.cr | 12 ++++++++++++ src/compiler/crystal/codegen/codegen.cr | 11 +++++++---- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/spec/compiler/codegen/is_a_spec.cr b/spec/compiler/codegen/is_a_spec.cr index 97d72378be42..86ed570afe20 100644 --- a/spec/compiler/codegen/is_a_spec.cr +++ b/spec/compiler/codegen/is_a_spec.cr @@ -45,6 +45,16 @@ describe "Codegen: is_a?" do run("1.is_a?(Object)").to_b.should be_true end + it "doesn't error if result is discarded (#14113)" do + run(<<-CRYSTAL).to_i.should eq(1) + class Foo + end + + (Foo.new || "").is_a?(Foo) + 1 + CRYSTAL + end + it "evaluate method on filtered type" do run("a = 1; a = 'a'; if a.is_a?(Char); a.ord; else; 0; end").to_i.chr.should eq('a') end diff --git a/spec/compiler/codegen/responds_to_spec.cr b/spec/compiler/codegen/responds_to_spec.cr index e10f024f3682..0384c3e0d838 100644 --- a/spec/compiler/codegen/responds_to_spec.cr +++ b/spec/compiler/codegen/responds_to_spec.cr @@ -51,6 +51,18 @@ describe "Codegen: responds_to?" do )).to_b.should be_false end + it "doesn't error if result is discarded (#14113)" do + run(<<-CRYSTAL).to_i.should eq(1) + class Foo + def foo + end + end + + (Foo.new || "").responds_to?(:foo) + 1 + CRYSTAL + end + it "works with virtual type" do run(%( class Foo diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index f5c35db9d65a..651afa44ca27 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -1471,12 +1471,15 @@ module Crystal def codegen_type_filter(node, &) accept node.obj - obj_type = node.obj.type - type_id = type_id @last, obj_type - filtered_type = yield(obj_type).not_nil! + if @needs_value + obj_type = node.obj.type + + type_id = type_id @last, obj_type + filtered_type = yield(obj_type).not_nil! - @last = match_type_id obj_type, filtered_type, type_id + @last = match_type_id obj_type, filtered_type, type_id + end false end From e1fefe19d6296152a45d1edfc99c669fa74e05df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 4 Jan 2024 11:06:54 +0100 Subject: [PATCH 0886/1551] Add `unreachable` options to manpage (#14164) --- man/crystal.1 | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/man/crystal.1 b/man/crystal.1 index 508b2ab44cb9..bfdb364d932b 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -411,9 +411,49 @@ to specify the cursor position. The format for the cursor position is file:line: .It Cm types Show type of main variables of file. .It Cm unreachable -Show methods that are never called. The output is a list of lines with columns -separated by tab. The first column is the location of the def, the second column -its reference name and the third column is the length in lines. +Show methods that are never called. The text output is a list of lines with columns +separated by tab. + +.Pp +Output fields: + +.Bl -tag -width "1234567890" -compact +.Pp +.It Cm count +sum of all calls to this method (only with +.Fl -tallies + option; otherwise skipped) +.It Cm location +pathname, line and column, all separated by colon +.It Cm name +.It Cm lines +length of the def in lines +.It Cm annotations +.El + +.Pp +Options: +.Bl -tag -width "12345678" -compact +.Pp +.It Fl D Ar FLAG, Fl -define= Ar FLAG +Define a compile-time flag. This is useful to conditionally define types, methods, or commands based on flags available at compile time. The default flags are from the target triple given with --target-triple or the hosts default, if none is given. +.It Fl f Ar FORMAT, Fl -format= Ar FORMAT +Output format 'text' (default), 'json', or 'csv'. +.It Fl -tallies +Print reachable methods and their call counts as well. +.It Fl -check +Exit with error if there is any unreachable code. +.It Fl i Ar PATH, Fl -include= Ar PATH +Include path in output. +.It Fl e Ar PATH, Fl -exclude= Ar PATH +Exclude path in output (default: +.Sy lib +). +.It Fl -error-trace +Show full error trace. +.It Fl -prelude +Specify prelude to use. The default one initializes the garbage collector. You can also use --prelude=empty to use no preludes. This can be useful for checking code generation for a specific source code file. +.El .El .Pp .It From cb333fed295e929a26979b2336e2326c26520290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 4 Jan 2024 11:07:04 +0100 Subject: [PATCH 0887/1551] Configure Renovate Bot to add label `topic:infrastructure/ci` on PRs (#14166) --- .github/renovate.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 4e18234dc0ec..39932ec1f648 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -12,5 +12,6 @@ "matchManagers": ["github-actions"], "schedule": ["after 5am and before 8am on Wednesday"] } - ] + ], + "labels": ["topic:infrastructure/ci"] } From 194ffb2b1db671052320e5221ed5cad462c49069 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:45:54 +0100 Subject: [PATCH 0888/1551] Update GH Actions (#14165) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/win.yml | 14 +++++++------- .github/workflows/win_build_portable.yml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index da66d568835a..c80dcfce8fb6 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -18,7 +18,7 @@ jobs: git config --global core.autocrlf false - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 - name: Download Crystal source uses: actions/checkout@v4 @@ -77,7 +77,7 @@ jobs: key: win-openssl-libs-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc - name: Set up NASM if: steps.cache-openssl.outputs.cache-hit != 'true' - uses: ilammy/setup-nasm@321e6ed62a1fc77024a3bd853deb33645e8b22c4 # v1.4.0 + uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1 - name: Build OpenSSL if: steps.cache-openssl.outputs.cache-hit != 'true' run: .\etc\win-ci\build-openssl.ps1 -BuildTree deps\openssl -Version 3.1.0 @@ -90,7 +90,7 @@ jobs: git config --global core.autocrlf false - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 - name: Download Crystal source uses: actions/checkout@v4 @@ -159,7 +159,7 @@ jobs: key: win-openssl-dlls-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc - name: Set up NASM if: steps.cache-openssl-dlls.outputs.cache-hit != 'true' - uses: ilammy/setup-nasm@321e6ed62a1fc77024a3bd853deb33645e8b22c4 # v1.4.0 + uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1 - name: Build OpenSSL if: steps.cache-openssl-dlls.outputs.cache-hit != 'true' run: .\etc\win-ci\build-openssl.ps1 -BuildTree deps\openssl -Version 3.1.0 -Dynamic @@ -168,7 +168,7 @@ jobs: runs-on: windows-2022 steps: - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 - name: Cache LLVM id: cache-llvm-libs @@ -212,7 +212,7 @@ jobs: git config --global core.autocrlf false - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 - name: Download Crystal source uses: actions/checkout@v4 @@ -246,7 +246,7 @@ jobs: git config --global core.autocrlf false - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 6222aaee3055..960606628a9e 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -19,7 +19,7 @@ jobs: git config --global core.autocrlf false - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1 + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 - name: Install Crystal uses: crystal-lang/install-crystal@v1 From f435981237c527701d1c7c68027a30339ecd40db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 4 Jan 2024 13:46:11 +0100 Subject: [PATCH 0889/1551] Fix formatting in manpage (#14163) --- man/crystal.1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/man/crystal.1 b/man/crystal.1 index bfdb364d932b..3a2a3d4a1b2a 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -130,7 +130,7 @@ Specify a specific CPU to generate code for. This will pass a -mcpu flag to LLVM Passing --mcpu native will pass the host CPU name to tune performance for the host. .It Fl -mattr Ar CPU Override or control specific attributes of the target, such as whether SIMD operations are enabled or not. The default set of attributes is set by the current CPU. This will pass a -mattr flag to LLVM, and is only intended to be used for cross-compilation. For a list of available attributes, invoke "llvm-as < /dev/null | llc -march=xyz -mattr=help". -.It Fl -mcmodel default|kernel|small|medium|large +.It Fl -mcmodel Ar default|kernel|small|medium|large Specifies a specific code model to generate code for. This will pass a --code-model flag to LLVM. .It Fl -no-color Disable colored output. @@ -407,7 +407,7 @@ flag. .It Cm implementations Show implementations for a given call. Use .Fl -cursor -to specify the cursor position. The format for the cursor position is file:line:column. + to specify the cursor position. The format for the cursor position is file:line:column. .It Cm types Show type of main variables of file. .It Cm unreachable @@ -493,7 +493,7 @@ Middle optimization .It Fl O3 High optimization . -.Sh ENVIRONMENT VARIABLES +.Sh ENVIRONMENT\ VARIABLES .Bl -tag -width "12345678" -compact .Pp .It From 3e05a2af1782c9951d2133d6c1951fa23c1f4ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 4 Jan 2024 13:46:23 +0100 Subject: [PATCH 0890/1551] Embed logo image into repository and upgrade to SVG (#14137) --- Makefile | 1 + README.md | 2 +- doc/assets/crystal-born-and-raised.svg | 248 +++++++++++++++++++++++++ 3 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 doc/assets/crystal-born-and-raised.svg diff --git a/Makefile b/Makefile index 2a7f9d43869f..461ee56bd5e5 100644 --- a/Makefile +++ b/Makefile @@ -127,6 +127,7 @@ samples: ## Build example programs docs: ## Generate standard library documentation $(call check_llvm_config) ./bin/crystal docs src/docs_main.cr $(DOCS_OPTIONS) --project-name=Crystal --project-version=$(CRYSTAL_VERSION) --source-refname=$(CRYSTAL_CONFIG_BUILD_COMMIT) + cp -av doc/ docs/ .PHONY: crystal crystal: $(O)/crystal ## Build the compiler diff --git a/README.md b/README.md index 67bf031ed038..70577b822fdc 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ --- -[![Crystal - Born and raised at Manas](https://cloud.githubusercontent.com/assets/209371/13291809/022e2360-daf8-11e5-8be7-d02c1c8b38fb.png)](https://manas.tech/) +[![Crystal - Born and raised at Manas](doc/assets/crystal-born-and-raised.svg)](https://manas.tech/) Crystal is a programming language with the following goals: diff --git a/doc/assets/crystal-born-and-raised.svg b/doc/assets/crystal-born-and-raised.svg new file mode 100644 index 000000000000..98e85d9b46b2 --- /dev/null +++ b/doc/assets/crystal-born-and-raised.svg @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e3200d9eb8814e92849503e646325b09647cfe9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 4 Jan 2024 13:46:33 +0100 Subject: [PATCH 0891/1551] Improvements for `github-changelog` script (#14160) --- scripts/github-changelog.cr | 47 ++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index b46adb9c656b..c660a01cd8ca 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -110,10 +110,13 @@ record PullRequest, if labels.includes?("breaking-change") io << "**[breaking]** " end + if experimental? + io << "**[experimental]** " + end if deprecated? io << "**[deprecation]** " end - io << title.sub(/^#{type}: /i, "") << " (" + io << title.sub(/^\[?(?:#{type}|#{sub_topic})(?::|\]:?) /i, "") << " (" io << "[#" << number << "](" << permalink << ")" if author = self.author io << ", thanks @" << author @@ -152,9 +155,26 @@ record PullRequest, end def topic - labels.find { |label| - label.starts_with?("topic:") && label != "topic:multithreading" - }.try(&.lchop("topic:").split(/:|\//)) + topics.fetch(0) do + STDERR.puts "Missing topic for ##{number}" + nil + end + end + + def topics + topics = labels.compact_map { |label| + label.lchop?("topic:").try(&.split(/:|\//)) + } + topics.reject! &.[0].==("multithreading") + + topics.sort_by! { |parts| + topic_priority = case parts[0] + when "tools" then 2 + when "lang" then 1 + else 0 + end + {-topic_priority, parts[0]} + } end def deprecated? @@ -165,6 +185,10 @@ record PullRequest, labels.includes?("kind:breaking") end + def experimental? + labels.includes?("experimental") + end + def feature? labels.includes?("kind:feature") end @@ -173,6 +197,10 @@ record PullRequest, labels.includes?("kind:bug") end + def chore? + labels.includes?("kind:chore") + end + def refactor? labels.includes?("kind:refactor") end @@ -196,9 +224,10 @@ record PullRequest, def type case when feature? then "feature" - when fix? then "fix" when docs? then "docs" when specs? then "specs" + when fix? then "fix" + when chore? then "chore" when performance? then "performance" when refactor? then "refactor" else nil @@ -242,16 +271,22 @@ SECTION_TITLES = { "breaking" => "Breaking changes", "feature" => "Features", "fix" => "Bugfixes", + "chore" => "Chores", "performance" => "Performance", "refactor" => "Refactor", "docs" => "Documentation", "specs" => "Specs", "infra" => "Infrastructure", - "" => "Chores", + "" => "other", } TOPIC_ORDER = %w[lang stdlib compiler tools other] +puts "## [#{milestone}] (#{Time.local.to_s("%F")})" +puts +puts "[#{milestone}]: https://github.com/crystal-lang/crystal/releases/#{milestone}" +puts + SECTION_TITLES.each do |id, title| prs = sections[id]? || next puts "### #{title}" From b8a24e7d94bd35999d0bf4eff0aac368f6be7bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 5 Jan 2024 12:02:17 +0100 Subject: [PATCH 0892/1551] Fix `String::Buffer` and `IO::Memory` capacity to grow beyond 1GB (#13989) --- spec/std/io/memory_spec.cr | 33 ++++++++++++++++++++++++++++ spec/std/string_builder_spec.cr | 39 +++++++++++++++++++++++++++++++++ src/io/memory.cr | 34 +++++++++++++++++++--------- src/string/builder.cr | 38 ++++++++++++++++++++++---------- 4 files changed, 122 insertions(+), 22 deletions(-) diff --git a/spec/std/io/memory_spec.cr b/spec/std/io/memory_spec.cr index 37ff01e3421d..7d51c09483ed 100644 --- a/spec/std/io/memory_spec.cr +++ b/spec/std/io/memory_spec.cr @@ -18,6 +18,17 @@ describe IO::Memory do io.gets_to_end.should eq(s) end + it "write raises EOFError" do + io = IO::Memory.new + initial_capacity = io.@capacity + expect_raises(IO::EOFError) do + io.write Slice.new(Pointer(UInt8).null, Int32::MAX) + end + # nothing get's written + io.bytesize.should eq 0 + io.@capacity.should eq initial_capacity + end + it "reads byte" do io = IO::Memory.new("abc") io.read_byte.should eq('a'.ord) @@ -494,4 +505,26 @@ describe IO::Memory do end end {% end %} + + it "allocates for > 1 GB", tags: %w[slow] do + io = IO::Memory.new + mbstring = "a" * 1024 * 1024 + 1024.times { io << mbstring } + + io.bytesize.should eq(1 << 30) + io.@capacity.should eq 1 << 30 + + io << mbstring + + io.bytesize.should eq (1 << 30) + (1 << 20) + io.@capacity.should eq Int32::MAX + + 1022.times { io << mbstring } + + io.write mbstring.to_slice[0..-4] + io << "a" + expect_raises(IO::EOFError) do + io << "a" + end + end end diff --git a/spec/std/string_builder_spec.cr b/spec/std/string_builder_spec.cr index 2b5f62c6c03c..250a4b0239f2 100644 --- a/spec/std/string_builder_spec.cr +++ b/spec/std/string_builder_spec.cr @@ -40,4 +40,43 @@ describe String::Builder do str.chomp!(44).to_s.should eq("a,b,c") end end + + it "raises EOFError" do + builder = String::Builder.new + initial_capacity = builder.capacity + expect_raises(IO::EOFError) do + builder.write Slice.new(Pointer(UInt8).null, Int32::MAX) + end + # nothing get's written + builder.bytesize.should eq 0 + builder.capacity.should eq initial_capacity + end + + # FIXME(wasm32): https://github.com/crystal-lang/crystal/issues/14057 + {% if flag?(:wasm32) %} + pending "allocation for > 1 GB" + {% else %} + it "allocates for > 1 GB", tags: %w[slow] do + String::Builder.build do |str| + mbstring = "a" * 1024 * 1024 + 1023.times { str << mbstring } + + str.bytesize.should eq (1 << 30) - (1 << 20) + str.capacity.should eq 1 << 30 + + str << mbstring + + str.bytesize.should eq 1 << 30 + str.capacity.should eq Int32::MAX + + 1023.times { str << mbstring } + + str.write mbstring.to_slice[0..(-4 - String::HEADER_SIZE)] + str << "a" + expect_raises(IO::EOFError) do + str << "a" + end + end + end + {% end %} end diff --git a/src/io/memory.cr b/src/io/memory.cr index d0b925df1d70..bd486f0cbdc2 100644 --- a/src/io/memory.cr +++ b/src/io/memory.cr @@ -90,11 +90,7 @@ class IO::Memory < IO return if count == 0 - new_bytesize = @pos + count - if new_bytesize > @capacity - check_resizeable - resize_to_capacity(Math.pw2ceil(new_bytesize)) - end + increase_capacity_by(count) slice.copy_to(@buffer + @pos, count) @@ -112,11 +108,7 @@ class IO::Memory < IO check_writeable check_open - new_bytesize = @pos + 1 - if new_bytesize > @capacity - check_resizeable - resize_to_capacity(Math.pw2ceil(new_bytesize)) - end + increase_capacity_by(1) (@buffer + @pos).value = byte @@ -458,6 +450,28 @@ class IO::Memory < IO end end + private def increase_capacity_by(count) + raise IO::EOFError.new if count >= Int32::MAX - bytesize + + new_bytesize = @pos + count + return if new_bytesize <= @capacity + + check_resizeable + + new_capacity = calculate_new_capacity(new_bytesize) + resize_to_capacity(new_capacity) + end + + private def calculate_new_capacity(new_bytesize : Int32) + # If the new bytesize is bigger than 1 << 30, the next power of two would + # be 1 << 31, which is out of range for Int32. + # So we limit the capacity to Int32::MAX in order to be able to use the + # range (1 << 30) < new_bytesize < Int32::MAX + return Int32::MAX if new_bytesize > 1 << 30 + + Math.pw2ceil(new_bytesize) + end + private def resize_to_capacity(capacity) @capacity = capacity @buffer = GC.realloc(@buffer, @capacity) diff --git a/src/string/builder.cr b/src/string/builder.cr index 0fb77f4ca41b..a06644b831d1 100644 --- a/src/string/builder.cr +++ b/src/string/builder.cr @@ -41,21 +41,14 @@ class String::Builder < IO return if slice.empty? count = slice.size - new_bytesize = real_bytesize + count - if new_bytesize > @capacity - resize_to_capacity(Math.pw2ceil(new_bytesize)) - end + increase_capacity_by count slice.copy_to(@buffer + real_bytesize, count) @bytesize += count end def write_byte(byte : UInt8) : Nil - new_bytesize = real_bytesize + 1 - if new_bytesize > @capacity - resize_to_capacity(Math.pw2ceil(new_bytesize)) - end - + increase_capacity_by 1 @buffer[real_bytesize] = byte @bytesize += 1 @@ -108,17 +101,18 @@ class String::Builder < IO raise "Can only invoke 'to_s' once on String::Builder" if @finished @finished = true - write_byte 0_u8 + real_bytesize = real_bytesize() + @buffer[real_bytesize] = 0_u8 + real_bytesize += 1 # Try to reclaim some memory if capacity is bigger than what we need - real_bytesize = real_bytesize() if @capacity > real_bytesize resize_to_capacity(real_bytesize) end String.set_crystal_type_id(@buffer) str = @buffer.as(String) - str.initialize_header((bytesize - 1).to_i) + str.initialize_header(bytesize) str end @@ -126,6 +120,26 @@ class String::Builder < IO @bytesize + String::HEADER_SIZE end + private def increase_capacity_by(count) + raise IO::EOFError.new if count >= Int32::MAX - real_bytesize + + new_bytesize = real_bytesize + count + return if new_bytesize <= @capacity + + new_capacity = calculate_new_capacity(new_bytesize) + resize_to_capacity(new_capacity) + end + + private def calculate_new_capacity(new_bytesize) + # If the new bytesize is bigger than 1 << 30, the next power of two would + # be 1 << 31, which is out of range for Int32. + # So we limit the capacity to Int32::MAX in order to be able to use the + # range (1 << 30) < new_bytesize < Int32::MAX + return Int32::MAX if new_bytesize > 1 << 30 + + Math.pw2ceil(new_bytesize) + end + private def resize_to_capacity(capacity) @capacity = capacity @buffer = GC.realloc(@buffer, @capacity) From 7df0b2e9d0b1c735ebc9d30e903fa037f297538c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 6 Jan 2024 23:03:39 +0100 Subject: [PATCH 0893/1551] Fix OpenSSL error handling for EOF (support for OpenSSL 3.2) (#14169) Co-authored-by: Beta Ziliani --- spec/std/openssl/ssl/socket_spec.cr | 8 +++++++- src/openssl.cr | 11 +++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/spec/std/openssl/ssl/socket_spec.cr b/spec/std/openssl/ssl/socket_spec.cr index ccf1046b6949..bbc5b11e4b9b 100644 --- a/spec/std/openssl/ssl/socket_spec.cr +++ b/spec/std/openssl/ssl/socket_spec.cr @@ -160,20 +160,26 @@ describe OpenSSL::SSL::Socket do server_context, client_context = ssl_context_pair server_context.disable_session_resume_tickets # avoid Broken pipe - server_finished_reading = Channel(String).new + server_finished_reading = Channel(String | Exception).new spawn do OpenSSL::SSL::Server.open(tcp_server, server_context, sync_close: true) do |server| server_socket = server.accept received = server_socket.gets_to_end # interprets underlying socket close as a graceful EOF server_finished_reading.send(received) end + rescue exc + server_finished_reading.send exc end + socket = TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port) socket_ssl = OpenSSL::SSL::Socket::Client.new(socket, client_context, hostname: "example.com", sync_close: true) socket_ssl.print "hello" socket_ssl.flush # needed today see #5375 socket.close # close underlying socket without gracefully shutting down SSL at all server_received = server_finished_reading.receive + if server_received.is_a?(Exception) + raise server_received + end server_received.should eq("hello") end end diff --git a/src/openssl.cr b/src/openssl.cr index 802c9a05e7d3..f34ee169b4cc 100644 --- a/src/openssl.cr +++ b/src/openssl.cr @@ -89,15 +89,14 @@ module OpenSSL when .syscall? @code, message = fetch_error_details if @code == 0 - case return_code - when 0 + errno = {% if flag?(:win32) %} WinError.wsa_value {% else %} Errno.value {% end %} + success = {% if flag?(:win32) %} errno.error_success? {% else %} errno.none? {% end %} + if success message = "Unexpected EOF" @underlying_eof = true - when -1 - cause = RuntimeError.from_errno(func || "OpenSSL") - message = "I/O error" else - message = "Unknown error" + cause = RuntimeError.from_os_error(func || "OpenSSL", os_error: errno) + message = "I/O error" end end when .ssl? From 2140607a289133926ce5edf8215252d0464fdf20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 8 Jan 2024 12:34:08 +0100 Subject: [PATCH 0894/1551] Fix `options` parameter for `String#split`, `#scan` (#14183) --- spec/std/string_spec.cr | 5 +++++ src/string.cr | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 0d60b3ac2466..ebebcd2c6561 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -1416,6 +1416,7 @@ describe "String" do it { "=".split(/\=/, 2).should eq(["", ""]) } it { "=".split(/\=/, 2, remove_empty: true).should eq([] of String) } it { ",".split(/(?:(x)|(,))/).should eq(["", ",", ""]) } + it { "ba".split(/a/, options: :anchored).should eq ["ba"] } it "keeps groups" do s = "split on the word on okay?" @@ -2322,6 +2323,10 @@ describe "String" do it "does with number and string" do "1ab4".scan(/\d+/).map(&.[0]).should eq(["1", "4"]) end + + it "options parameter" do + "ba".scan(/a/, options: :anchored).map(&.[0]).should eq [] of String + end end it "has match" do diff --git a/src/string.cr b/src/string.cr index 98b7ec3884fd..4004f0d34929 100644 --- a/src/string.cr +++ b/src/string.cr @@ -4198,7 +4198,6 @@ class String count = 0 match_offset = slice_offset = 0 - options = Regex::MatchOptions::None while match = separator.match_at_byte_index(self, match_offset, options: options) index = match.byte_begin(0) match_bytesize = match.byte_end(0) - index @@ -4744,7 +4743,6 @@ class String def scan(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None, &) : self byte_offset = 0 - options = Regex::MatchOptions::None while match = pattern.match_at_byte_index(self, byte_offset, options: options) index = match.byte_begin(0) $~ = match From 95d04fab447c48d7cb94f5937e52f6556e3ff858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 8 Jan 2024 21:05:33 +0100 Subject: [PATCH 0895/1551] Changelog for 1.11.0 (#14158) --- CHANGELOG.md | 256 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 257 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d2c17878c9d..1bedb4a96a97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,261 @@ # Changelog +## [1.11.0] (2024-01-08) + +[1.11.0]: https://github.com/crystal-lang/crystal/releases/1.11.0 + +### Features + +#### lang + +- **[breaking]** Support `alignof` and `instance_alignof` ([#14087](https://github.com/crystal-lang/crystal/pull/14087), thanks @HertzDevil) +- *(annotations)* Support `dll` parameter in `@[Link]` ([#14131](https://github.com/crystal-lang/crystal/pull/14131), thanks @HertzDevil) +- *(macros)* Expose macro `Call` context via new `@caller` macro ivar ([#14048](https://github.com/crystal-lang/crystal/pull/14048), thanks @Blacksmoke16) + +#### stdlib + +- *(collection)* Add `Enumerable#present?` ([#13866](https://github.com/crystal-lang/crystal/pull/13866), thanks @straight-shoota) +- *(collection)* Add `Enumerable#each_step` and `Iterable#each_step` ([#13610](https://github.com/crystal-lang/crystal/pull/13610), thanks @baseballlover723) +- *(collection)* Add `Enumerable(T)#to_set(& : T -> U) : Set(U) forall U` ([#12654](https://github.com/crystal-lang/crystal/pull/12654), thanks @caspiano) +- *(collection)* Add `Enumerable(T)#to_a(& : T -> U) forall U` ([#12653](https://github.com/crystal-lang/crystal/pull/12653), thanks @caspiano) +- *(files)* Add `IO::Error#target` ([#13865](https://github.com/crystal-lang/crystal/pull/13865), thanks @straight-shoota) +- *(llvm)* Add `LLVM::OperandBundleDef#dispose` ([#14095](https://github.com/crystal-lang/crystal/pull/14095), thanks @HertzDevil) +- *(llvm)* Windows: Use local configuration for LLVM when linking dynamically ([#14101](https://github.com/crystal-lang/crystal/pull/14101), thanks @HertzDevil) +- *(macros)* Add `CharLiteral#ord` ([#13910](https://github.com/crystal-lang/crystal/pull/13910), thanks @refi64) +- *(macros)* Add macro methods for `MacroIf` and `MacroFor` nodes ([#13902](https://github.com/crystal-lang/crystal/pull/13902), thanks @sbsoftware) +- *(macros)* Expose doc comments on `ASTNode` when generating docs ([#14050](https://github.com/crystal-lang/crystal/pull/14050), thanks @Blacksmoke16) +- *(macros)* Add macro methods for `ModuleDef` ([#14063](https://github.com/crystal-lang/crystal/pull/14063), thanks @HertzDevil) +- *(macros)* Add macro methods for `Include` and `Extend` ([#14064](https://github.com/crystal-lang/crystal/pull/14064), thanks @HertzDevil) +- *(macros)* Add macro methods for `ClassDef`, `EnumDef`, `AnnotationDef` ([#14072](https://github.com/crystal-lang/crystal/pull/14072), thanks @HertzDevil) +- *(numeric)* Implement `BigRational`'s rounding modes ([#13871](https://github.com/crystal-lang/crystal/pull/13871), thanks @HertzDevil) +- *(numeric)* Support full exponent range in `BigFloat#**(BigInt)` ([#13881](https://github.com/crystal-lang/crystal/pull/13881), thanks @HertzDevil) +- *(numeric)* Add `Math.fma` ([#13934](https://github.com/crystal-lang/crystal/pull/13934), thanks @HertzDevil) +- *(numeric)* Add `Number#integer?` ([#13936](https://github.com/crystal-lang/crystal/pull/13936), thanks @HertzDevil) +- *(numeric)* Publish `Int::Primitive#abs_unsigned` and `#neg_signed` ([#13938](https://github.com/crystal-lang/crystal/pull/13938), thanks @HertzDevil) +- *(numeric)* Add `Int::Primitive#to_signed`, `#to_signed!`, `#to_unsigned`, `#to_unsigned!` ([#13960](https://github.com/crystal-lang/crystal/pull/13960), thanks @HertzDevil) +- *(numeric)* Support `BigFloat#**` for all `Int::Primitive` arguments ([#13971](https://github.com/crystal-lang/crystal/pull/13971), thanks @HertzDevil) +- *(numeric)* Add `Float32::MIN_SUBNORMAL` and `Float64::MIN_SUBNORMAL` ([#13961](https://github.com/crystal-lang/crystal/pull/13961), thanks @HertzDevil) +- *(numeric)* Add `Float::Primitive.parse_hexfloat`, `.parse_hexfloat?`, `#to_hexfloat` ([#14027](https://github.com/crystal-lang/crystal/pull/14027), thanks @HertzDevil) +- *(numeric)* Implement `sprintf "%f"` in Crystal using Ryu Printf ([#14067](https://github.com/crystal-lang/crystal/pull/14067), thanks @HertzDevil) +- *(numeric)* Implement `sprintf "%e"` in Crystal ([#14084](https://github.com/crystal-lang/crystal/pull/14084), thanks @HertzDevil) +- *(numeric)* Implement `sprintf "%a"` in Crystal ([#14102](https://github.com/crystal-lang/crystal/pull/14102), thanks @HertzDevil) +- *(numeric)* Implement `sprintf "%g"` in Crystal ([#14123](https://github.com/crystal-lang/crystal/pull/14123), thanks @HertzDevil) +- *(runtime)* Add `Crystal::HOST_TRIPLE` and `TARGET_TRIPLE` ([#13823](https://github.com/crystal-lang/crystal/pull/13823), thanks @HertzDevil) +- *(runtime)* **[experimental]** Add `Reference.pre_initialize` and `.unsafe_construct` ([#14108](https://github.com/crystal-lang/crystal/pull/14108), thanks @HertzDevil) +- *(runtime)* **[experimental]** Add `ReferenceStorage` for manual allocation of references ([#14106](https://github.com/crystal-lang/crystal/pull/14106), thanks @HertzDevil) +- *(serialization)* Fix `StaticArray#to_json` ([#14104](https://github.com/crystal-lang/crystal/pull/14104), thanks @Vendicated) +- *(specs)* Add `crystal spec --dry-run` ([#13804](https://github.com/crystal-lang/crystal/pull/13804), thanks @nobodywasishere) +- *(specs)* Add `crystal spec --list-tags` ([#13616](https://github.com/crystal-lang/crystal/pull/13616), thanks @baseballlover723) +- *(system)* Respect Windows `Path` directory separators in `File.match?` ([#13912](https://github.com/crystal-lang/crystal/pull/13912), thanks @HertzDevil) +- *(text)* Support Unicode 15.1.0 ([#13812](https://github.com/crystal-lang/crystal/pull/13812), thanks @HertzDevil) +- *(text)* Add `UUID.v1`, `.v2`, `.v3`, `.v4`, `.v5` ([#13693](https://github.com/crystal-lang/crystal/pull/13693), thanks @threez) +- *(text)* Add `String` and `Char` patterns to `StringScanner` ([#13806](https://github.com/crystal-lang/crystal/pull/13806), thanks @funny-falcon) +- *(text)* Add `EOL`constant (End-Of-Line) ([#11303](https://github.com/crystal-lang/crystal/pull/11303), thanks @postmodern) +- *(text)* Add `Char::Reader#current_char?`, `#next_char?`, `#previous_char?` ([#14012](https://github.com/crystal-lang/crystal/pull/14012), thanks @HertzDevil) +- *(text)* Add `String#matches_full?` ([#13968](https://github.com/crystal-lang/crystal/pull/13968), thanks @straight-shoota) +- *(text)* Change `Regex::MatchData#to_s` to return matched substring ([#14115](https://github.com/crystal-lang/crystal/pull/14115), thanks @Vendicated) + +#### compiler + +- *(codegen)* Add incremental optimization levels ([#13464](https://github.com/crystal-lang/crystal/pull/13464), thanks @kostya) +- *(debugger)* Support debug information for 64-bit or unsigned enums ([#14081](https://github.com/crystal-lang/crystal/pull/14081), thanks @HertzDevil) +- *(interpreter)* Support `instance_sizeof(T)` in the interpreter ([#14031](https://github.com/crystal-lang/crystal/pull/14031), thanks @HertzDevil) +- *(interpreter)* Support `-dynamic.lib` in Windows interpreter ([#14143](https://github.com/crystal-lang/crystal/pull/14143), thanks @HertzDevil) +- *(interpreter)* Support absolute paths in `CRYSTAL_INTERPRETER_LOADER_INFO` ([#14147](https://github.com/crystal-lang/crystal/pull/14147), thanks @HertzDevil) +- *(interpreter)* Add `Crystal::Repl#parse_and_interpret` ([#14138](https://github.com/crystal-lang/crystal/pull/14138), thanks @bcardiff) +- *(semantic)* Change short_reference for top-level methods to `::foo` ([#14071](https://github.com/crystal-lang/crystal/pull/14071), thanks @keshavbiswa) + +#### tools + +- *(docs-generator)* Expose inherited macros in generated API docs ([#13810](https://github.com/crystal-lang/crystal/pull/13810), thanks @Blacksmoke16) +- *(docs-generator)* Order macros below class methods in generated docs ([#14024](https://github.com/crystal-lang/crystal/pull/14024), thanks @Blacksmoke16) +- *(formatter)* Do not remove trailing comma from multi-line macro/def parameters (not yet enabled) ([#14075](https://github.com/crystal-lang/crystal/pull/14075), thanks @Blacksmoke16) +- *(unreachable)* Add `--check` flag to `crystal tool unreachable` ([#13930](https://github.com/crystal-lang/crystal/pull/13930), thanks @straight-shoota) +- *(unreachable)* Add annotations to output of `crystal tool unreachable` ([#13927](https://github.com/crystal-lang/crystal/pull/13927), thanks @straight-shoota) +- *(unreachable)* Print relative paths in `crystal tool unreachable` ([#13929](https://github.com/crystal-lang/crystal/pull/13929), thanks @straight-shoota) +- *(unreachable)* Add CSV output format to `crystal tool unreachable` ([#13926](https://github.com/crystal-lang/crystal/pull/13926), thanks @straight-shoota) +- *(unreachable)* Add `--tallies` option to `crystal tool unreachable` ([#13969](https://github.com/crystal-lang/crystal/pull/13969), thanks @straight-shoota) + +### Bugfixes + +#### stdlib + +- Fix `Box(T?)` crashing on `nil` ([#13893](https://github.com/crystal-lang/crystal/pull/13893), thanks @HertzDevil) +- Fix typos in src ([#14053](https://github.com/crystal-lang/crystal/pull/14053), thanks @kojix2) +- *(collection)* Fix `Indexable#each_repeated_combination(n)` when `n > size` ([#14092](https://github.com/crystal-lang/crystal/pull/14092), thanks @HertzDevil) +- *(concurrency)* Make `Process#wait` asynchronous on Windows ([#13908](https://github.com/crystal-lang/crystal/pull/13908), thanks @HertzDevil) +- *(concurrency)* Fix math overflow after spawning `Int32::MAX + 1` fibers ([#14096](https://github.com/crystal-lang/crystal/pull/14096), thanks @ysbaddaden) +- *(concurrency)* Fix `can't resume a running fiber` ([#14128](https://github.com/crystal-lang/crystal/pull/14128), thanks @ysbaddaden) +- *(crypto)* Fix OpenSSL error handling for EOF (support for OpenSSL 3.2) ([#14169](https://github.com/crystal-lang/crystal/pull/14169), thanks @straight-shoota) +- *(files)* Fix `Globber.constant_entry?` matching patterns ([#13955](https://github.com/crystal-lang/crystal/pull/13955), thanks @GeopJr) +- *(files)* Fix `String::Buffer` and `IO::Memory` capacity to grow beyond 1GB ([#13989](https://github.com/crystal-lang/crystal/pull/13989), thanks @straight-shoota) +- *(llvm)* Fix a typo ([#13914](https://github.com/crystal-lang/crystal/pull/13914), thanks @kojix2) +- *(numeric)* Make `String#to_f(whitespace: false)` work with infinity and NaN ([#13875](https://github.com/crystal-lang/crystal/pull/13875), thanks @HertzDevil) +- *(numeric)* Use `LibGMP::SI` and `UI` for size checks, not `Long` and `ULong` ([#13874](https://github.com/crystal-lang/crystal/pull/13874), thanks @HertzDevil) +- *(numeric)* Fix integral part extraction in `Number#format` ([#14061](https://github.com/crystal-lang/crystal/pull/14061), thanks @HertzDevil) +- *(numeric)* Fix out-of-bounds access in `Int128::MIN.to_s(base: 2)` ([#14119](https://github.com/crystal-lang/crystal/pull/14119), thanks @HertzDevil) +- *(numeric)* Avoid double rounding in `Float#format` for nonnegative `decimal_place` ([#14129](https://github.com/crystal-lang/crystal/pull/14129), thanks @HertzDevil) +- *(runtime)* Avoid `@[ThreadLocal]` on Android ([#14025](https://github.com/crystal-lang/crystal/pull/14025), thanks @HertzDevil) +- *(runtime)* Never use string interpolation in `Crystal::System.print_error` ([#14114](https://github.com/crystal-lang/crystal/pull/14114), thanks @HertzDevil) +- *(runtime)* Fix segfault with next boehm gc (after v8.2.4) ([#14130](https://github.com/crystal-lang/crystal/pull/14130), thanks @ysbaddaden) +- *(specs)* Skip spec execution on error exit ([#13986](https://github.com/crystal-lang/crystal/pull/13986), thanks @straight-shoota) +- *(system)* Fix `FileUtils.ln_sf` to override special file types ([#13896](https://github.com/crystal-lang/crystal/pull/13896), thanks @straight-shoota) +- *(system)* Fix `Process.exists?` throwing errors on EPERM ([#13911](https://github.com/crystal-lang/crystal/pull/13911), thanks @refi64) +- *(system)* Fix portable shell command arguments in `Process#prepare_args` ([#13942](https://github.com/crystal-lang/crystal/pull/13942), thanks @GeopJr) +- *(system)* Windows: Do not close process handle in `Process#close` ([#13997](https://github.com/crystal-lang/crystal/pull/13997), thanks @HertzDevil) +- *(system)* Windows: clear `Crystal::System::Process#@completion_key` after use ([#14068](https://github.com/crystal-lang/crystal/pull/14068), thanks @HertzDevil) +- *(system)* Fix UTF-8 console input on Windows ([#13758](https://github.com/crystal-lang/crystal/pull/13758), thanks @erdian718) +- *(text)* Fix invalid UTF-8 handling in `Char::Reader#previous_char` ([#14013](https://github.com/crystal-lang/crystal/pull/14013), thanks @HertzDevil) +- *(text)* Fix `options` parameter for `String#split`, `#scan` ([#14183](https://github.com/crystal-lang/crystal/pull/14183), thanks @straight-shoota) +- *(time)* Fix time span overflow on `Int#milliseconds` and `Int#microseconds` ([#14105](https://github.com/crystal-lang/crystal/pull/14105), thanks @bcardiff) + +#### compiler + +- *(cli)* Remove unnecessary file check for CLI arguments ([#13853](https://github.com/crystal-lang/crystal/pull/13853), thanks @straight-shoota) +- *(cli)* Check for invalid integers in compiler's CLI ([#13959](https://github.com/crystal-lang/crystal/pull/13959), thanks @HertzDevil) +- *(cli)* Fix compiler error message for invalid source file ([#14157](https://github.com/crystal-lang/crystal/pull/14157), thanks @straight-shoota) +- *(codegen)* Fix a typo in compiler source ([#14054](https://github.com/crystal-lang/crystal/pull/14054), thanks @kojix2) +- *(codegen)* Fix codegen error when discarding `is_a?` or `responds_to?`'s result ([#14148](https://github.com/crystal-lang/crystal/pull/14148), thanks @HertzDevil) +- *(interpreter)* Fix element alignment of `Tuple` and `NamedTuple` casts ([#14040](https://github.com/crystal-lang/crystal/pull/14040), thanks @HertzDevil) +- *(interpreter)* `Crystal::Loader`: Skip second linker member on Windows if absent ([#14111](https://github.com/crystal-lang/crystal/pull/14111), thanks @HertzDevil) +- *(parser)* Support `%r` and `%x` when not followed by delimiter start ([#13933](https://github.com/crystal-lang/crystal/pull/13933), thanks @HertzDevil) +- *(parser)* Fix location of global `Path` nodes in certain constructs ([#13932](https://github.com/crystal-lang/crystal/pull/13932), thanks @HertzDevil) +- *(parser)* Fix `ToSVisitor` for expanded string interpolation in backticks ([#13943](https://github.com/crystal-lang/crystal/pull/13943), thanks @straight-shoota) +- *(parser)* Fix location for "invalid trailing comma in call" errors ([#13964](https://github.com/crystal-lang/crystal/pull/13964), thanks @HertzDevil) +- *(semantic)* Fix check for file type ([#13760](https://github.com/crystal-lang/crystal/pull/13760), thanks @straight-shoota) +- *(semantic)* Fix private type definitions with namespaced `Path`s ([#13931](https://github.com/crystal-lang/crystal/pull/13931), thanks @HertzDevil) +- *(semantic)* Fix missing param count in compilation error message ([#13985](https://github.com/crystal-lang/crystal/pull/13985), thanks @koffeinfrei) +- *(semantic)* Fix `ReadInstanceVar` on typedefs ([#14044](https://github.com/crystal-lang/crystal/pull/14044), thanks @HertzDevil) +- *(semantic)* Fix global `Path` lookup inside macro when def has free variables ([#14073](https://github.com/crystal-lang/crystal/pull/14073), thanks @HertzDevil) +- *(semantic)* Add location information to implicit flag enum members ([#14127](https://github.com/crystal-lang/crystal/pull/14127), thanks @Blacksmoke16) + +#### tools + +- *(docs-generator)* Fix `crystal docs` check `File.exists?` for `shard.yml` ([#13937](https://github.com/crystal-lang/crystal/pull/13937), thanks @straight-shoota) +- *(docs-generator)* Fix version sorting in API docs ([#13994](https://github.com/crystal-lang/crystal/pull/13994), thanks @m-o-e) +- *(docs-generator)* Strip whitespace in doc comment before determining summary line ([#14049](https://github.com/crystal-lang/crystal/pull/14049), thanks @Blacksmoke16) +- *(docs-generator)* Skip `Crystal::Macros` unless generating docs ([#13970](https://github.com/crystal-lang/crystal/pull/13970), thanks @straight-shoota) +- *(init)* Fix tool init error message when target exists but not a dir ([#13869](https://github.com/crystal-lang/crystal/pull/13869), thanks @straight-shoota) +- *(unreachable)* Fix infinite recursion of expanded nodes in `UnreachableVisitor` ([#13922](https://github.com/crystal-lang/crystal/pull/13922), thanks @straight-shoota) + +### Chores + +#### lang + +- *(macros)* **[deprecation]** Deprecate the splat operators in macro expressions ([#13939](https://github.com/crystal-lang/crystal/pull/13939), thanks @HertzDevil) + +#### stdlib + +- *(llvm)* **[deprecation]** Deprecate `LLVM.start_multithreaded` and `.stop_multithreaded` ([#13949](https://github.com/crystal-lang/crystal/pull/13949), thanks @HertzDevil) + +### Performance + +#### stdlib + +- *(concurrency)* Skip indirections in `Crystal::Scheduler` ([#14098](https://github.com/crystal-lang/crystal/pull/14098), thanks @ysbaddaden) +- *(numeric)* Optimize `BigInt#&`, `#|`, `#^` with `Int::Primitive` arguments ([#14006](https://github.com/crystal-lang/crystal/pull/14006), thanks @HertzDevil) +- *(numeric)* Optimize `BigInt#bit` ([#13980](https://github.com/crystal-lang/crystal/pull/13980), thanks @HertzDevil) +- *(numeric)* Use `#trailing_zeros_count` in `Int#gcd` ([#14069](https://github.com/crystal-lang/crystal/pull/14069), thanks @HertzDevil) +- *(serialization)* Optimize `JSON::Builder#string` with byte-based algorithm ([#13915](https://github.com/crystal-lang/crystal/pull/13915), thanks @straight-shoota) +- *(serialization)* Improve performance of `JSON::Builder#string` with direct stringification ([#13950](https://github.com/crystal-lang/crystal/pull/13950), thanks @straight-shoota) +- *(text)* Refactor `HTML.unescape` in native Crystal ([#13844](https://github.com/crystal-lang/crystal/pull/13844), thanks @straight-shoota) +- *(text)* Refactor some uses of the blockless `String#split` ([#14001](https://github.com/crystal-lang/crystal/pull/14001), thanks @HertzDevil) + +### Refactor + +#### stdlib + +- *(concurrency)* Add `Crystal::System::Thread` ([#13814](https://github.com/crystal-lang/crystal/pull/13814), thanks @HertzDevil) +- *(concurrency)* Move `Thread#set_current_thread` to `Fiber` ([#14099](https://github.com/crystal-lang/crystal/pull/14099), thanks @ysbaddaden) +- *(files)* Use `IO.copy` in `IO#gets_to_end` ([#13990](https://github.com/crystal-lang/crystal/pull/13990), thanks @straight-shoota) +- *(files)* Do not use `pointerof(Path)` in the standard library ([#14144](https://github.com/crystal-lang/crystal/pull/14144), thanks @HertzDevil) +- *(llvm)* **[deprecation]** Remove `LLVMExtSetCurrentDebugLocation` from `llvm_ext.cc` for LLVM 9+ ([#13965](https://github.com/crystal-lang/crystal/pull/13965), thanks @HertzDevil) +- *(llvm)* Replace some deprecated LLVM bindings ([#13953](https://github.com/crystal-lang/crystal/pull/13953), thanks @HertzDevil) +- *(llvm)* Split `LibLLVM` by C headers ([#13948](https://github.com/crystal-lang/crystal/pull/13948), thanks @HertzDevil) +- *(llvm)* Support `LLVMSetTargetMachineGlobalISel` from LLVM 18 ([#14079](https://github.com/crystal-lang/crystal/pull/14079), thanks @HertzDevil) +- *(llvm)* Support the operand bundle API from LLVM 18 ([#14082](https://github.com/crystal-lang/crystal/pull/14082), thanks @HertzDevil) +- *(numeric)* Simplify `String::Formatter` when Ryu Printf is available ([#14132](https://github.com/crystal-lang/crystal/pull/14132), thanks @HertzDevil) +- *(runtime)* Implement most of `Crystal::System.print_error` in native Crystal ([#14116](https://github.com/crystal-lang/crystal/pull/14116), thanks @HertzDevil) +- *(text)* Drop `Char::Reader#@end` ([#13920](https://github.com/crystal-lang/crystal/pull/13920), thanks @straight-shoota) +- *(text)* Generate `src/html/entities.cr` automatically ([#13998](https://github.com/crystal-lang/crystal/pull/13998), thanks @HertzDevil) +- *(time)* Refactor leap year to use `divisible_by?` ([#13982](https://github.com/crystal-lang/crystal/pull/13982), thanks @meatball133) + +#### compiler + +- Remove relative path to vendored shards `markd` and `reply` ([#13992](https://github.com/crystal-lang/crystal/pull/13992), thanks @nobodywasishere) +- *(cli)* Generalize allowed values for compiler CLI `--format` option ([#13940](https://github.com/crystal-lang/crystal/pull/13940), thanks @straight-shoota) +- *(parser)* Use `Char#to_i?` in lexer ([#13841](https://github.com/crystal-lang/crystal/pull/13841), thanks @straight-shoota) + +#### tools + +- *(unreachable)* Refactor `UnreachablePresenter` ([#13941](https://github.com/crystal-lang/crystal/pull/13941), thanks @straight-shoota) + +### Documentation + +#### lang + +- *(macros)* Add reference to book how merging macro expansion and call docs ([#14139](https://github.com/crystal-lang/crystal/pull/14139), thanks @Blacksmoke16) + +#### stdlib + +- *(collection)* Fix documentation of `Hash#put_if_absent` ([#13898](https://github.com/crystal-lang/crystal/pull/13898), thanks @ilmanzo) +- *(collection)* Improve docs on initial/default values passed to `Array.new` and `Hash.new` ([#13962](https://github.com/crystal-lang/crystal/pull/13962), thanks @straight-shoota) +- *(collection)* Improve docs for `Iterator` step-by-step iteration ([#13967](https://github.com/crystal-lang/crystal/pull/13967), thanks @straight-shoota) +- *(macros)* Document `Crystal::Macros::MagicConstant` ([#14070](https://github.com/crystal-lang/crystal/pull/14070), thanks @HertzDevil) +- *(serialization)* Add docs and explicit type restriction for indent parameter of `JSON.build` ([#14140](https://github.com/crystal-lang/crystal/pull/14140), thanks @syeopite) +- *(text)* Add note about `Char::Reader`'s value semantics ([#14008](https://github.com/crystal-lang/crystal/pull/14008), thanks @HertzDevil) +- *(text)* Fix documentation for `String#index!` ([#14038](https://github.com/crystal-lang/crystal/pull/14038), thanks @gettalong) + +#### compiler + +- *(cli)* Add optimization levels to manpage ([#14162](https://github.com/crystal-lang/crystal/pull/14162), thanks @straight-shoota) +- *(cli)* Add `unreachable` options to manpage ([#14164](https://github.com/crystal-lang/crystal/pull/14164), thanks @straight-shoota) +- *(cli)* Fix formatting in manpage ([#14163](https://github.com/crystal-lang/crystal/pull/14163), thanks @straight-shoota) + +### Specs + +#### stdlib + +- Add `pending_wasm32` ([#14086](https://github.com/crystal-lang/crystal/pull/14086), thanks @HertzDevil) +- *(concurrency)* Workaround regular timeouts in `HTTP::Server` specs with MT ([#14097](https://github.com/crystal-lang/crystal/pull/14097), thanks @ysbaddaden) +- *(files)* Fix `File::AccessDeniedError` expectations in `File` specs ([#14029](https://github.com/crystal-lang/crystal/pull/14029), thanks @HertzDevil) +- *(text)* Refactor specs for `HTML.unescape` ([#13842](https://github.com/crystal-lang/crystal/pull/13842), thanks @straight-shoota) +- *(text)* Fix spec for `String#encode` and `String.new` on DragonFlyBSD ([#13944](https://github.com/crystal-lang/crystal/pull/13944), thanks @GeopJr) + +#### compiler + +- *(codegen)* Remove `LLVMExtCreateMCJITCompilerForModule` from `llvm_ext.cc` ([#13966](https://github.com/crystal-lang/crystal/pull/13966), thanks @HertzDevil) +- *(interpreter)* Disable `mkfifo` spec for interpreter ([#14051](https://github.com/crystal-lang/crystal/pull/14051), thanks @HertzDevil) +- *(interpreter)* Fix interpreter specs on Windows ([#14145](https://github.com/crystal-lang/crystal/pull/14145), thanks @HertzDevil) + +#### tools + +- *(docs-generator)* Use `top_level_semantic` in doc spec instead of `semantic` ([#9352](https://github.com/crystal-lang/crystal/pull/9352), thanks @makenowjust) + +### Infrastructure + +- Changelog for 1.11.0 ([#14158](https://github.com/crystal-lang/crystal/pull/14158), thanks @straight-shoota) +- Update previous Crystal release - 1.10.0 ([#13878](https://github.com/crystal-lang/crystal/pull/13878), thanks @straight-shoota) +- Allow to specify git fork of distribution-scripts in CI ([#13976](https://github.com/crystal-lang/crystal/pull/13976), thanks @miry) +- Extract `generate_data` to separate Makefile ([#14015](https://github.com/crystal-lang/crystal/pull/14015), thanks @straight-shoota) +- Windows: Run specs in random order by default ([#14041](https://github.com/crystal-lang/crystal/pull/14041), thanks @HertzDevil) +- Update shards 0.17.4 ([#14133](https://github.com/crystal-lang/crystal/pull/14133), thanks @straight-shoota) +- Update distribution-scripts ([#14136](https://github.com/crystal-lang/crystal/pull/14136), thanks @straight-shoota) +- Update GH Actions to v4 ([#14120](https://github.com/crystal-lang/crystal/pull/14120), thanks @renovate) +- Embed logo image into repository and upgrade to SVG ([#14137](https://github.com/crystal-lang/crystal/pull/14137), thanks @straight-shoota) +- Improvements for `github-changelog` script ([#14160](https://github.com/crystal-lang/crystal/pull/14160), thanks @straight-shoota) +- Add `scripts/generate_llvm_version_info.cr` ([#14112](https://github.com/crystal-lang/crystal/pull/14112), thanks @HertzDevil) +- Fix `make clean` to remove zipped manpages ([#14135](https://github.com/crystal-lang/crystal/pull/14135), thanks @straight-shoota) +- Make `scripts/*.cr` all executable ([#13999](https://github.com/crystal-lang/crystal/pull/13999), thanks @HertzDevil) +- Reformat changelog release headings ([#13663](https://github.com/crystal-lang/crystal/pull/13663), thanks @straight-shoota) +- Merge `samples/.gitignore` into `.gitignore` ([#14134](https://github.com/crystal-lang/crystal/pull/14134), thanks @straight-shoota) +- *(ci)* Update GH Actions ([#13801](https://github.com/crystal-lang/crystal/pull/13801), thanks @renovate) +- *(ci)* Update LLVM patch version to LLVM 17.0.6 ([#14080](https://github.com/crystal-lang/crystal/pull/14080), thanks @straight-shoota) +- *(ci)* Configure Renovate Bot to add label `topic:infrastructure/ci` on PRs ([#14166](https://github.com/crystal-lang/crystal/pull/14166), thanks @straight-shoota) +- *(ci)* Update GH Actions ([#14165](https://github.com/crystal-lang/crystal/pull/14165), thanks @renovate) +- *(ci)* Distribute LLVM DLLs on Windows CI ([#14110](https://github.com/crystal-lang/crystal/pull/14110), thanks @HertzDevil) +- *(ci)* Use `CMAKE_MSVC_RUNTIME_LIBRARY` flag in win.yml ([#13900](https://github.com/crystal-lang/crystal/pull/13900), thanks @HertzDevil) + ## [1.10.1] (2023-10-13) [1.10.1]: https://github.com/crystal-lang/crystal/releases/1.10.1 diff --git a/src/VERSION b/src/VERSION index 1f724bf455d7..1cac385c6cb8 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.11.0-dev +1.11.0 From 0ee5483c04c95c9b055bc9d9a8c3b434b715db5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 9 Jan 2024 17:36:30 +0100 Subject: [PATCH 0896/1551] Update previous Crystal release 1.11.0 (#14189) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 6 +++--- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win_build_portable.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ src/VERSION | 2 +- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 39ae49d81ee3..9804bf22894c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.10.1/crystal-1.10.1-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.11.0/crystal-1.11.0-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 2cd37d4b6579..64ef777fa9d3 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.10.1-build + image: crystallang/crystal:1.11.0-build name: "Test Interpreter" steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.10.1-build + image: crystallang/crystal:1.11.0-build name: Build interpreter steps: - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.10.1-build + image: crystallang/crystal:1.11.0-build strategy: matrix: part: [0, 1, 2, 3] diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9ef73a9fe23c..c501d1b0061c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1] + crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.0] flags: [""] include: # libffi is only available starting from the 1.2.2 build images diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index f361896768d8..e8e891cab665 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -56,7 +56,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.10.1" + crystal: "1.11.0" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 07e2d7e94558..16dfe3b42e32 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.10.1-alpine + container: crystallang/crystal:1.11.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -27,7 +27,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.10.1-alpine + container: crystallang/crystal:1.11.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.10.1-alpine + container: crystallang/crystal:1.11.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index a7ad3afbbb65..ad6de969c5f5 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.10.1-alpine + container: crystallang/crystal:1.11.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.10.1-alpine + container: crystallang/crystal:1.11.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 0d4f32f4c044..eec64cc0bb39 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.10.1-build + container: crystallang/crystal:1.11.0-build steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 960606628a9e..6ea5568305b6 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -24,7 +24,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.10.1" + crystal: "1.11.0" - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index 8e9f7a5ddadf..d67c2d7f6c4e 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.10.1/crystal-1.10.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.10.1-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.11.0/crystal-1.11.0-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.11.0-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.10.1}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.11.0}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index ab90080aca13..02db8fb637f7 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.10.1/crystal-1.10.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:08k8sixhnk9ld99nyrya11rkpp34zamsg3lk9h50ppbmzfixjyyc"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.11.0/crystal-1.11.0-1-darwin-universal.tar.gz"; + sha256 = "sha256:0x3adik0rpfkgw8nszf6l52vr4m7fs7rwqf1r0m17x4kgq67daiz"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.10.1/crystal-1.10.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:08k8sixhnk9ld99nyrya11rkpp34zamsg3lk9h50ppbmzfixjyyc"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.11.0/crystal-1.11.0-1-darwin-universal.tar.gz"; + sha256 = "sha256:0x3adik0rpfkgw8nszf6l52vr4m7fs7rwqf1r0m17x4kgq67daiz"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.10.1/crystal-1.10.1-1-linux-x86_64.tar.gz"; - sha256 = "sha256:02hzslzgv0xxsal3fkbcdrnrrnzf9lraamy36p36sjf8n14v45a2"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.11.0/crystal-1.11.0-1-linux-x86_64.tar.gz"; + sha256 = "sha256:1npbn61mw6fchdlg64s7zd3k33711ifhs8n75skw8w30i4ryilhk"; }; }.${pkgs.stdenv.system}); diff --git a/src/VERSION b/src/VERSION index 1cac385c6cb8..381cf02417c4 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.11.0 +1.12.0-dev From 0b57b6cc8e4f5003267549892d23d2af5ad6309b Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 10 Jan 2024 02:37:47 +1000 Subject: [PATCH 0897/1551] Add `Signal::trap_handler?` (#14126) Co-authored-by: Quinton Miller --- spec/std/signal_spec.cr | 22 ++++++++++++++++++++++ src/crystal/system/signal.cr | 3 +++ src/crystal/system/unix/signal.cr | 6 +++++- src/crystal/system/wasi/signal.cr | 4 ++++ src/crystal/system/win32/signal.cr | 4 ++++ src/signal.cr | 18 ++++++++++++++++++ 6 files changed, 56 insertions(+), 1 deletion(-) diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr index e92b74a6370c..d1d3aa3f1643 100644 --- a/spec/std/signal_spec.cr +++ b/spec/std/signal_spec.cr @@ -29,6 +29,8 @@ describe "Signal" do sleep 0.1 end ran.should be_true + ensure + Signal::USR1.reset end it "ignores a signal" do @@ -36,6 +38,26 @@ describe "Signal" do Process.signal Signal::USR2, Process.pid end + it "allows chaining of signals" do + ran_first = false + ran_second = false + + Signal::USR1.trap { ran_first = true } + existing = Signal::USR1.trap_handler? + + Signal::USR1.trap do |signal| + existing.try &.call(signal) + ran_second = true + end + + Process.signal Signal::USR1, Process.pid + sleep 0.1 + ran_first.should be_true + ran_second.should be_true + ensure + Signal::USR1.reset + end + it "CHLD.reset sets default Crystal child handler" do Signal::CHLD.reset child = Process.new("true", shell: true) diff --git a/src/crystal/system/signal.cr b/src/crystal/system/signal.cr index b5ba591b2ec5..a5e3eca885da 100644 --- a/src/crystal/system/signal.cr +++ b/src/crystal/system/signal.cr @@ -2,6 +2,9 @@ module Crystal::System::Signal # Sets the handler for this signal to the passed function. # def self.trap(signal, handler) : Nil + # Returns any existing handler set on the signal + # def self.trap_handler?(signal) + # Resets the handler for this signal to the OS default. # def self.reset(signal) : Nil diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr index c30a2b985af2..58b1180f56ef 100644 --- a/src/crystal/system/unix/signal.cr +++ b/src/crystal/system/unix/signal.cr @@ -15,7 +15,7 @@ module Crystal::System::Signal @@pipe = IO.pipe(read_blocking: false, write_blocking: true) @@handlers = {} of ::Signal => Handler @@sigset = Sigset.new - class_setter child_handler : Handler? + class_property child_handler : Handler? @@mutex = Mutex.new(:unchecked) def self.trap(signal, handler) : Nil @@ -30,6 +30,10 @@ module Crystal::System::Signal end end + def self.trap_handler?(signal) + @@mutex.synchronize { @@handlers[signal]? } + end + def self.reset(signal) : Nil set(signal, LibC::SIG_DFL) end diff --git a/src/crystal/system/wasi/signal.cr b/src/crystal/system/wasi/signal.cr index d66b9c22c5cd..35675ce14f34 100644 --- a/src/crystal/system/wasi/signal.cr +++ b/src/crystal/system/wasi/signal.cr @@ -3,6 +3,10 @@ module Crystal::System::Signal raise NotImplementedError.new("Crystal::System::Signal.trap") end + def self.trap_handler?(signal) + raise NotImplementedError.new("Crystal::System::Signal.trap_handler?") + end + def self.reset(signal) : Nil raise NotImplementedError.new("Crystal::System::Signal.reset") end diff --git a/src/crystal/system/win32/signal.cr b/src/crystal/system/win32/signal.cr index 8f5541c7599b..d805ea4fd1ab 100644 --- a/src/crystal/system/win32/signal.cr +++ b/src/crystal/system/win32/signal.cr @@ -5,6 +5,10 @@ module Crystal::System::Signal raise NotImplementedError.new("Crystal::System::Signal.trap") end + def self.trap_handler?(signal) + raise NotImplementedError.new("Crystal::System::Signal.trap_handler?") + end + def self.reset(signal) : Nil raise NotImplementedError.new("Crystal::System::Signal.reset") end diff --git a/src/signal.cr b/src/signal.cr index 60eba5b8e7f3..2e085b92311e 100644 --- a/src/signal.cr +++ b/src/signal.cr @@ -117,6 +117,24 @@ enum Signal : Int32 Crystal::System::Signal.trap(self, handler) end + # Returns any existing handler for this signal + # + # ``` + # Signal::USR1.trap { } + # prev_handler = Signal::USR1.trap_handler? + # + # Signal::USR1.trap do |signal| + # prev_handler.try &.call(signal) + # # ... + # end + # ``` + def trap_handler? + {% if @type.has_constant?("CHLD") %} + return Crystal::System::Signal.child_handler if self == CHLD + {% end %} + Crystal::System::Signal.trap_handler?(self) + end + # Resets the handler for this signal to the OS default. # # Note that trying to reset `CHLD` will actually set the default crystal From dd5d2dd8b509ec514ad3c134aec1a4994a8415d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 10 Jan 2024 12:26:05 +0100 Subject: [PATCH 0898/1551] Bump VERSION to 1.11.1-dev (#14197) --- src/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VERSION b/src/VERSION index 1cac385c6cb8..095b66727677 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.11.0 +1.11.1-dev From bd43c5f6a806ead05b3a00afbc539fd38a01ccba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 10 Jan 2024 14:29:21 +0100 Subject: [PATCH 0899/1551] Remove pkg-config name for libgc as workaround for interpreter loader (#14198) --- src/gc/boehm.cr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 8278f33b8e94..609f71189795 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -21,6 +21,12 @@ {% if flag?(:freebsd) || flag?(:dragonfly) %} @[Link("gc-threaded")] +{% elsif flag?(:interpreted) %} + # FIXME: We're not using the pkg-config name here because that would resolve the + # lib flags for libgc including `-lpthread` which the interpreter is not able + # to load on systems with modern libc where libpthread is only available as an + # (empty) static library. + @[Link("gc")] {% else %} @[Link("gc", pkg_config: "bdw-gc")] {% end %} From 9d52e30d4cda5c77b8ad59c20859a02c3db3d2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 10 Jan 2024 16:25:45 +0100 Subject: [PATCH 0900/1551] Revert "Add `ReferenceStorage` for manual allocation of references (#14106)" (#14207) --- .../semantic/reference_storage_spec.cr | 40 -------------- spec/primitives/reference_spec.cr | 4 +- src/compiler/crystal/codegen/llvm_typer.cr | 4 -- src/compiler/crystal/program.cr | 6 +-- src/compiler/crystal/types.cr | 28 +--------- src/prelude.cr | 1 - src/reference_storage.cr | 54 ------------------- 7 files changed, 5 insertions(+), 132 deletions(-) delete mode 100644 spec/compiler/semantic/reference_storage_spec.cr delete mode 100644 src/reference_storage.cr diff --git a/spec/compiler/semantic/reference_storage_spec.cr b/spec/compiler/semantic/reference_storage_spec.cr deleted file mode 100644 index 791fed054275..000000000000 --- a/spec/compiler/semantic/reference_storage_spec.cr +++ /dev/null @@ -1,40 +0,0 @@ -require "../../spec_helper" - -describe "Semantic: ReferenceStorage" do - it "errors if T is a struct type" do - assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = Foo (T must be a reference type)" - struct Foo - @x = 1 - end - - ReferenceStorage(Foo) - CRYSTAL - end - - it "errors if T is a value type" do - assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = Int32 (T must be a reference type)" - ReferenceStorage(Int32) - CRYSTAL - end - - it "errors if T is a union type" do - assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = (Bar | Foo) (T must be a reference type)" - class Foo - end - - class Bar - end - - ReferenceStorage(Foo | Bar) - CRYSTAL - end - - it "errors if T is a nilable type" do - assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = (Foo | Nil) (T must be a reference type)" - class Foo - end - - ReferenceStorage(Foo?) - CRYSTAL - end -end diff --git a/spec/primitives/reference_spec.cr b/spec/primitives/reference_spec.cr index 52f179296ee8..b73553748b65 100644 --- a/spec/primitives/reference_spec.cr +++ b/spec/primitives/reference_spec.cr @@ -43,7 +43,9 @@ describe "Primitives: reference" do end it "works when address is on the stack" do - foo_buffer = uninitialized ReferenceStorage(Foo) + # suitably aligned, sufficient storage for type ID + i64 + ptr + # TODO: use `ReferenceStorage` instead + foo_buffer = uninitialized UInt64[3] foo = Foo.pre_initialize(pointerof(foo_buffer)) pointerof(foo_buffer).as(typeof(Foo.crystal_instance_type_id)*).value.should eq(Foo.crystal_instance_type_id) foo.str.should eq("abc") diff --git a/src/compiler/crystal/codegen/llvm_typer.cr b/src/compiler/crystal/codegen/llvm_typer.cr index d72a25ecd3cb..d20fbd59fa9a 100644 --- a/src/compiler/crystal/codegen/llvm_typer.cr +++ b/src/compiler/crystal/codegen/llvm_typer.cr @@ -245,10 +245,6 @@ module Crystal llvm_type(type.remove_alias, wants_size) end - private def create_llvm_type(type : ReferenceStorageType, wants_size) - llvm_struct_type(type.reference_type, wants_size) - end - private def create_llvm_type(type : NonGenericModuleType | GenericClassType, wants_size) # This can only be reached if the module or generic class don't have implementors @llvm_context.int1 diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 6d3d185774af..936fc6a0a270 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -197,10 +197,6 @@ module Crystal types["Struct"] = struct_t = @struct_t = NonGenericClassType.new self, self, "Struct", value abstract_value_type(struct_t) - types["ReferenceStorage"] = @reference_storage = reference_storage = GenericReferenceStorageType.new self, self, "ReferenceStorage", value, ["T"] - reference_storage.declare_instance_var("@type_id", int32) - reference_storage.can_be_stored = false - types["Enumerable"] = @enumerable = GenericModuleType.new self, self, "Enumerable", ["T"] types["Indexable"] = @indexable = GenericModuleType.new self, self, "Indexable", ["T"] @@ -497,7 +493,7 @@ module Crystal {% for name in %w(object no_return value number reference void nil bool char int int8 int16 int32 int64 int128 uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol pointer enumerable indexable - array static_array reference_storage exception tuple named_tuple proc union enum range regex crystal + array static_array exception tuple named_tuple proc union enum range regex crystal packed_annotation thread_local_annotation no_inline_annotation always_inline_annotation naked_annotation returns_twice_annotation raises_annotation primitive_annotation call_convention_annotation diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 64bcdda9a3d9..ad9f3d391fa6 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -550,7 +550,7 @@ module Crystal def allows_instance_vars? case self when program.object, program.value, program.struct, - program.reference, program.class_type, program.reference_storage, + program.reference, program.class_type, program.number, program.int, program.float, program.tuple, program.named_tuple, program.pointer, program.static_array, @@ -2642,32 +2642,6 @@ module Crystal end end - # The non-instantiated ReferenceStorage(T) type. - class GenericReferenceStorageType < GenericClassType - @struct = true - - def new_generic_instance(program, generic_type, type_vars) - t = type_vars["T"].type - - unless t.is_a?(TypeParameter) || (t.class? && !t.struct?) - raise TypeException.new "Can't instantiate ReferenceStorage(T) with T = #{t} (T must be a reference type)" - end - - ReferenceStorageType.new program, t - end - end - - class ReferenceStorageType < GenericClassInstanceType - getter reference_type : Type - - def initialize(program, @reference_type) - t_var = Var.new("T", @reference_type) - t_var.bind_to t_var - - super(program, program.reference_storage, program.struct, {"T" => t_var} of String => ASTNode) - end - end - # A lib type, like `lib LibC`. class LibType < ModuleType getter link_annotations : Array(LinkAnnotation)? diff --git a/src/prelude.cr b/src/prelude.cr index f84bb86cb3c1..f06f5bc87015 100644 --- a/src/prelude.cr +++ b/src/prelude.cr @@ -65,7 +65,6 @@ require "raise" require "random" require "range" require "reference" -require "reference_storage" require "regex" require "set" {% unless flag?(:wasm32) %} diff --git a/src/reference_storage.cr b/src/reference_storage.cr deleted file mode 100644 index 4f6e39a8cca0..000000000000 --- a/src/reference_storage.cr +++ /dev/null @@ -1,54 +0,0 @@ -# a `ReferenceStorage(T)` provides the minimum storage for the instance data of -# an object of type `T`. The compiler guarantees that -# `sizeof(ReferenceStorage(T)) == instance_sizeof(T)` and -# `alignof(ReferenceStorage(T)) == instance_alignof(T)` always hold, which means -# `Pointer(ReferenceStorage(T))` and `T` are binary-compatible. -# -# `T` must be a non-union reference type. -# -# WARNING: `ReferenceStorage` is only necessary for manual memory management, -# such as creating instances of `T` with a non-default allocator. Therefore, -# this type is unsafe and no public constructors are defined. -# -# WARNING: `ReferenceStorage` is unsuitable when instances of `T` require more -# than `instance_sizeof(T)` bytes, such as `String` and `Log::Metadata`. -@[Experimental("This type's API is still under development. Join the discussion about custom reference allocation at [#13481](https://github.com/crystal-lang/crystal/issues/13481).")] -struct ReferenceStorage(T) - private def initialize - end - - # Returns whether `self` and *other* are bytewise equal. - # - # NOTE: This does not call `T#==`, so it works even if `self` or *other* does - # not represent a valid instance of `T`. If validity is guaranteed, call - # `to_reference == other.to_reference` instead to use `T#==`. - def ==(other : ReferenceStorage(T)) : Bool - to_bytes == other.to_bytes - end - - def ==(other) : Bool - false - end - - def hash(hasher) - to_bytes.hash(hasher) - end - - def to_s(io : IO) : Nil - io << "ReferenceStorage(#<" << T << ":0x" - pointerof(@type_id).address.to_s(io, 16) - io << ">)" - end - - # Returns a `T` whose instance data refers to `self`. - # - # WARNING: The caller is responsible for ensuring that the instance data is - # correctly initialized and outlives the returned `T`. - def to_reference : T - pointerof(@type_id).as(T) - end - - protected def to_bytes - Slice.new(pointerof(@type_id).as(UInt8*), instance_sizeof(T)) - end -end From c3eb0eb62fe7a047516962497f6afabae3fd68a3 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 10 Jan 2024 23:25:37 +0100 Subject: [PATCH 0901/1551] Use per-scheduler stack pools (let's recycle) (#14100) --- src/crystal/scheduler.cr | 46 +++++++++++++++++++--------------------- src/fiber.cr | 18 ++++------------ src/fiber/stack_pool.cr | 16 +++++++++----- src/kernel.cr | 12 +---------- 4 files changed, 38 insertions(+), 54 deletions(-) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index ce06e3c60275..fa963595bf6d 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -2,6 +2,7 @@ require "crystal/system/event_loop" require "crystal/system/print_error" require "./fiber_channel" require "fiber" +require "fiber/stack_pool" require "crystal/system/thread" # :nodoc: @@ -13,6 +14,11 @@ require "crystal/system/thread" # protected and must never be called directly. class Crystal::Scheduler @event_loop = Crystal::EventLoop.create + @stack_pool = Fiber::StackPool.new + + def self.stack_pool : Fiber::StackPool + Thread.current.scheduler.@stack_pool + end def self.event_loop Thread.current.scheduler.@event_loop @@ -83,15 +89,8 @@ class Crystal::Scheduler {% end %} end - {% if flag?(:preview_mt) %} - def self.enqueue_free_stack(stack : Void*) : Nil - Thread.current.scheduler.enqueue_free_stack(stack) - end - {% end %} - {% if flag?(:preview_mt) %} private getter(fiber_channel : Crystal::FiberChannel) { Crystal::FiberChannel.new } - @free_stacks = Deque(Void*).new {% end %} @main : Fiber @@ -157,18 +156,6 @@ class Crystal::Scheduler exit 1 end - {% if flag?(:preview_mt) %} - protected def enqueue_free_stack(stack) - @free_stacks.push stack - end - - private def release_free_stacks - while stack = @free_stacks.shift? - Fiber.stack_pool.release stack - end - end - {% end %} - protected def reschedule : Nil loop do if runnable = @lock.sync { @runnables.shift? } @@ -178,10 +165,6 @@ class Crystal::Scheduler @event_loop.run_once end end - - {% if flag?(:preview_mt) %} - release_free_stacks - {% end %} end protected def sleep(time : Time::Span) : Nil @@ -207,6 +190,8 @@ class Crystal::Scheduler end def run_loop + spawn_stack_pool_collector + fiber_channel = self.fiber_channel loop do @lock.lock @@ -239,7 +224,7 @@ class Crystal::Scheduler @lock.unlock end - def self.init_workers + def self.init : Nil count = worker_count pending = Atomic(Int32).new(count - 1) @@workers = Array(Thread).new(count) do |i| @@ -281,5 +266,18 @@ class Crystal::Scheduler 4 end end + {% else %} + def self.init : Nil + {% unless flag?(:interpreted) %} + Thread.current.scheduler.spawn_stack_pool_collector + {% end %} + end {% end %} + + # Background loop to cleanup unused fiber stacks. + def spawn_stack_pool_collector + fiber = Fiber.new(name: "Stack pool collector", &->@stack_pool.collect_loop) + {% if flag?(:preview_mt) %} fiber.set_current_thread {% end %} + enqueue(fiber) + end end diff --git a/src/fiber.cr b/src/fiber.cr index aa2af7bf2229..c96184f3cf1f 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -1,6 +1,5 @@ require "crystal/system/thread_linked_list" require "./fiber/context" -require "./fiber/stack_pool" # :nodoc: @[NoInline] @@ -47,9 +46,6 @@ class Fiber # :nodoc: protected class_getter(fibers) { Thread::LinkedList(Fiber).new } - # :nodoc: - class_getter stack_pool = StackPool.new - @context : Context @stack : Void* @resume_event : Crystal::EventLoop::Event? @@ -89,10 +85,9 @@ class Fiber @context = Context.new @stack, @stack_bottom = {% if flag?(:interpreted) %} - # For interpreted mode we don't need a new stack, the stack is held by the interpreter {Pointer(Void).null, Pointer(Void).null} {% else %} - Fiber.stack_pool.checkout + Crystal::Scheduler.stack_pool.checkout {% end %} fiber_main = ->(f : Fiber) { f.run } @@ -153,14 +148,6 @@ class Fiber ex.inspect_with_backtrace(STDERR) STDERR.flush ensure - {% if flag?(:preview_mt) %} - Crystal::Scheduler.enqueue_free_stack @stack - {% elsif flag?(:interpreted) %} - # For interpreted mode we don't need a new stack, the stack is held by the interpreter - {% else %} - Fiber.stack_pool.release(@stack) - {% end %} - # Remove the current fiber from the linked list Fiber.inactive(self) @@ -170,6 +157,9 @@ class Fiber @timeout_select_action = nil @alive = false + {% unless flag?(:interpreted) %} + Crystal::Scheduler.stack_pool.release(@stack) + {% end %} Crystal::Scheduler.reschedule end diff --git a/src/fiber/stack_pool.cr b/src/fiber/stack_pool.cr index 54d03e4ffa5f..aebd82a0870f 100644 --- a/src/fiber/stack_pool.cr +++ b/src/fiber/stack_pool.cr @@ -7,14 +7,13 @@ class Fiber def initialize @deque = Deque(Void*).new - @mutex = Thread::Mutex.new end # Removes and frees at most *count* stacks from the top of the pool, # returning memory to the operating system. def collect(count = lazy_size // 2) : Nil count.times do - if stack = @mutex.synchronize { @deque.shift? } + if stack = @deque.shift? Crystal::System::Fiber.free_stack(stack, STACK_SIZE) else return @@ -22,21 +21,28 @@ class Fiber end end + def collect_loop(every = 5.seconds) : Nil + loop do + sleep every + collect + end + end + # Removes a stack from the bottom of the pool, or allocates a new one. def checkout : {Void*, Void*} - stack = @mutex.synchronize { @deque.pop? } || Crystal::System::Fiber.allocate_stack(STACK_SIZE) + stack = @deque.pop? || Crystal::System::Fiber.allocate_stack(STACK_SIZE) {stack, stack + STACK_SIZE} end # Appends a stack to the bottom of the pool. def release(stack) : Nil - @mutex.synchronize { @deque.push(stack) } + @deque.push(stack) end # Returns the approximated size of the pool. It may be equal or slightly # bigger or smaller than the actual size. def lazy_size : Int32 - @mutex.synchronize { @deque.size } + @deque.size end end end diff --git a/src/kernel.cr b/src/kernel.cr index c3b3106ccae3..d3817ee11661 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -563,14 +563,6 @@ end {% end %} {% unless flag?(:interpreted) || flag?(:wasm32) %} - # Background loop to cleanup unused fiber stacks. - spawn(name: "Fiber Clean Loop") do - loop do - sleep 5 - Fiber.stack_pool.collect - end - end - {% if flag?(:win32) %} Crystal::System::Process.start_interrupt_loop {% else %} @@ -586,7 +578,5 @@ end Exception::CallStack.load_debug_info if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "1" Exception::CallStack.setup_crash_handler - {% if flag?(:preview_mt) %} - Crystal::Scheduler.init_workers - {% end %} + Crystal::Scheduler.init {% end %} From b56c7d9db282e054401211e38b832cb3e2f79963 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 11 Jan 2024 06:25:48 +0800 Subject: [PATCH 0902/1551] Allow multiple parameters and blocks for operators ending in `=` (#14159) --- spec/compiler/parser/parser_spec.cr | 8 +++++++- src/compiler/crystal/syntax/parser.cr | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 41fad5cc3a89..2fa1a5b4c4a7 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -290,10 +290,16 @@ module Crystal assert_syntax_error "def foo=(*args); end", "setter method 'foo=' cannot have more than one parameter" assert_syntax_error "def foo=(**kwargs); end", "setter method 'foo=' cannot have more than one parameter" assert_syntax_error "def foo=(&block); end", "setter method 'foo=' cannot have a block" - assert_syntax_error "def []=(&block); end", "setter method '[]=' cannot have a block" assert_syntax_error "f.[]= do |a| end", "setter method '[]=' cannot be called with a block" assert_syntax_error "f.[]= { |bar| }", "setter method '[]=' cannot be called with a block" + # #10397 + %w(<= >= == != []= ===).each do |operator| + it_parses "def #{operator}(other, file = 1); end", Def.new(operator, ["other".arg, Arg.new("file", 1.int32)]) + it_parses "def #{operator}(*args, **opts); end", Def.new(operator, ["args".arg], splat_index: 0, double_splat: "opts".arg) + it_parses "def #{operator}(*args, **opts, &); end", Def.new(operator, ["args".arg], splat_index: 0, double_splat: "opts".arg, block_arg: Arg.new(""), block_arity: 0) + end + # #5895, #6042, #5997 %w( begin nil true false yield with abstract diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 751608468cd5..f14399212e65 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3658,8 +3658,8 @@ module Crystal end_location = token_end_location - if name.ends_with?('=') - if name != "[]=" && (params.size > 1 || found_splat || found_double_splat) + if Lexer.setter?(name) + if params.size > 1 || found_splat || found_double_splat raise "setter method '#{name}' cannot have more than one parameter" elsif found_block raise "setter method '#{name}' cannot have a block" From 79b6e730135762420fcf12ca066202a93e9ecca6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 11 Jan 2024 06:26:03 +0800 Subject: [PATCH 0903/1551] Always use `%p` for pointers in `Crystal::System.print_error` (#14186) --- src/crystal/system/unix/signal.cr | 2 +- src/exception/call_stack/libunwind.cr | 2 +- src/exception/call_stack/stackwalk.cr | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr index 58b1180f56ef..d0fdbf298391 100644 --- a/src/crystal/system/unix/signal.cr +++ b/src/crystal/system/unix/signal.cr @@ -153,7 +153,7 @@ module Crystal::System::Signal if is_stack_overflow Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n" else - Crystal::System.print_error "Invalid memory access (signal %d) at address 0x%lx\n", sig, addr + Crystal::System.print_error "Invalid memory access (signal %d) at address %p\n", sig, addr end Exception::CallStack.print_backtrace diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 220db21b71f0..81943d99f376 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -102,7 +102,7 @@ struct Exception::CallStack end private def self.print_frame(repeated_frame) - Crystal::System.print_error "[0x%llx] ", repeated_frame.ip.address.to_u64 + Crystal::System.print_error "[%p] ", repeated_frame.ip.address print_frame_location(repeated_frame) Crystal::System.print_error " (%d times)", repeated_frame.count + 1 unless repeated_frame.count == 0 Crystal::System.print_error "\n" diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index f49c87fae623..4879ed8bb95d 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -37,7 +37,7 @@ struct Exception::CallStack case exception_info.value.exceptionRecord.value.exceptionCode when LibC::EXCEPTION_ACCESS_VIOLATION addr = exception_info.value.exceptionRecord.value.exceptionInformation[1] - Crystal::System.print_error "Invalid memory access (C0000005) at address 0x%llx\n", addr + Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr) print_backtrace(exception_info) LibC._exit(1) when LibC::EXCEPTION_STACK_OVERFLOW @@ -150,7 +150,7 @@ struct Exception::CallStack end private def self.print_frame(repeated_frame) - Crystal::System.print_error "[0x%llx] ", repeated_frame.ip.address.to_u64 + Crystal::System.print_error "[%p] ", repeated_frame.ip.address print_frame_location(repeated_frame) Crystal::System.print_error " (%d times)", repeated_frame.count + 1 unless repeated_frame.count == 0 Crystal::System.print_error "\n" From 83ce2bc9223c1add3a72ecdd85c693e673e87d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 11 Jan 2024 16:46:26 +0100 Subject: [PATCH 0904/1551] Revert "Fix OpenSSL error handling for EOF (support for OpenSSL 3.2) (#14169)" (#14217) Fix OpenSSL error handling for EOF (support for OpenSSL 3.2) (#14169)" --- src/openssl.cr | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/openssl.cr b/src/openssl.cr index f34ee169b4cc..802c9a05e7d3 100644 --- a/src/openssl.cr +++ b/src/openssl.cr @@ -89,14 +89,15 @@ module OpenSSL when .syscall? @code, message = fetch_error_details if @code == 0 - errno = {% if flag?(:win32) %} WinError.wsa_value {% else %} Errno.value {% end %} - success = {% if flag?(:win32) %} errno.error_success? {% else %} errno.none? {% end %} - if success + case return_code + when 0 message = "Unexpected EOF" @underlying_eof = true - else - cause = RuntimeError.from_os_error(func || "OpenSSL", os_error: errno) + when -1 + cause = RuntimeError.from_errno(func || "OpenSSL") message = "I/O error" + else + message = "Unknown error" end end when .ssl? From 0aa30372c4aebfb681fe20483fcdd463cdb5cd57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 11 Jan 2024 21:15:37 +0100 Subject: [PATCH 0905/1551] Changelog for 1.11.1 (#14208) --- CHANGELOG.md | 20 ++++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bedb4a96a97..658d9f42f822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [1.11.1] (2024-01-11) + +[1.11.1]: https://github.com/crystal-lang/crystal/releases/1.11.1 + +### Bugfixes + +#### stdlib + +- *(crypto)* Revert "Fix OpenSSL error handling for EOF (support for OpenSSL 3.2) (#14169)" ([#14217](https://github.com/crystal-lang/crystal/pull/14217), thanks @straight-shoota) + +#### compiler + +- *(interpreter)* Remove pkg-config name for libgc as workaround for interpreter loader ([#14198](https://github.com/crystal-lang/crystal/pull/14198), thanks @straight-shoota) +- *(semantic)* Revert "Add `ReferenceStorage` for manual allocation of references (#14106)" ([#14207](https://github.com/crystal-lang/crystal/pull/14207), thanks @straight-shoota) + +### Infrastructure + +- Changelog for 1.11.1 ([#14208](https://github.com/crystal-lang/crystal/pull/14208), thanks @straight-shoota) +- Bump VERSION to 1.11.1-dev ([#14197](https://github.com/crystal-lang/crystal/pull/14197), thanks @straight-shoota) + ## [1.11.0] (2024-01-08) [1.11.0]: https://github.com/crystal-lang/crystal/releases/1.11.0 diff --git a/src/VERSION b/src/VERSION index 095b66727677..720c7384c619 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.11.1-dev +1.11.1 From 7867cd8071a3816ecc3c0fdd4a7f4c02fbd6e8aa Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 12 Jan 2024 06:19:34 -0600 Subject: [PATCH 0906/1551] Enhance docs for `Int#downto` (#14176) Co-authored-by: George Dietrich --- src/int.cr | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/int.cr b/src/int.cr index 005d2c625c7b..b36ddba14e9e 100644 --- a/src/int.cr +++ b/src/int.cr @@ -567,6 +567,20 @@ struct Int end # Calls the given block with each integer value from self down to `to`. + # + # ``` + # 3.downto(1) do |i| + # puts i + # end + # ``` + # + # Prints: + # + # ```text + # 3 + # 2 + # 1 + # ``` def downto(to, &block : self ->) : Nil return unless self >= to x = self From 6bfcfe8e9910a10c383cc51b4c34bc049d1d661f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 12 Jan 2024 20:19:48 +0800 Subject: [PATCH 0907/1551] Move most of spec runner's state into `Spec::CLI` (#14170) --- spec/compiler/interpreter/spec_helper.cr | 24 ++- spec/std/log/env_config_spec.cr | 2 +- src/compiler/crystal/command/spec.cr | 2 +- src/spec.cr | 74 +++---- src/spec/cli.cr | 223 ++++++++++---------- src/spec/context.cr | 38 ++-- src/spec/dsl.cr | 249 ++++++++++++----------- src/spec/example.cr | 10 +- src/spec/formatter.cr | 28 ++- src/spec/methods.cr | 30 +-- 10 files changed, 353 insertions(+), 327 deletions(-) diff --git a/spec/compiler/interpreter/spec_helper.cr b/spec/compiler/interpreter/spec_helper.cr index e6aeb240674c..3c6f88ab1679 100644 --- a/spec/compiler/interpreter/spec_helper.cr +++ b/spec/compiler/interpreter/spec_helper.cr @@ -24,18 +24,20 @@ end # In a nutshell, `interpret_in_separate_process` below calls this same process with an extra option that causes # the interpretation of the code from stdin, reading the output from stdout. That string is used as the result of # the program being tested. -def Spec.option_parser - option_parser = previous_def - option_parser.on("", "--interpret-code PRELUDE", "Execute interpreted code") do |prelude| - code = STDIN.gets_to_end - - repl = Crystal::Repl.new - repl.prelude = prelude - - print repl.run_code(code) - exit +class Spec::CLI + def option_parser + option_parser = previous_def + option_parser.on("", "--interpret-code PRELUDE", "Execute interpreted code") do |prelude| + code = STDIN.gets_to_end + + repl = Crystal::Repl.new + repl.prelude = prelude + + print repl.run_code(code) + exit + end + option_parser end - option_parser end def interpret_in_separate_process(code, prelude, file = __FILE__, line = __LINE__) diff --git a/spec/std/log/env_config_spec.cr b/spec/std/log/env_config_spec.cr index 7bfdfb0f239a..5ddc2941b82a 100644 --- a/spec/std/log/env_config_spec.cr +++ b/spec/std/log/env_config_spec.cr @@ -10,7 +10,7 @@ end describe "Log.setup_from_env" do after_all do # Setup logging in specs (again) since these specs perform Log.setup - Spec.log_setup + Spec.cli.log_setup end describe "backend" do diff --git a/src/compiler/crystal/command/spec.cr b/src/compiler/crystal/command/spec.cr index ff8f47b11859..ce34b2ac4155 100644 --- a/src/compiler/crystal/command/spec.cr +++ b/src/compiler/crystal/command/spec.cr @@ -24,7 +24,7 @@ class Crystal::Command puts opts puts - runtime_options = Spec.option_parser + runtime_options = Spec::CLI.new.option_parser runtime_options.banner = "Runtime options (passed to spec runner):" puts runtime_options exit diff --git a/src/spec.cr b/src/spec.cr index e9cf5d448efd..c51ee8831de4 100644 --- a/src/spec.cr +++ b/src/spec.cr @@ -91,45 +91,51 @@ require "./spec/cli" # value can be used to rerun the specs in that same order by passing the seed # value to `--order`. module Spec -end + # :nodoc: + class CLI + # :nodoc: + # + # Implement formatter configuration. + def configure_formatter(formatter, output_path = nil) + case formatter + when "junit" + junit_formatter = Spec::JUnitFormatter.file(Path.new(output_path.not_nil!)) + add_formatter(junit_formatter) + when "verbose" + override_default_formatter(Spec::VerboseFormatter.new) + when "tap" + override_default_formatter(Spec::TAPFormatter.new) + end + end -Colorize.on_tty_only! + def main(args) + Colorize.on_tty_only! -# :nodoc: -# -# Implement formatter configuration. -def Spec.configure_formatter(formatter, output_path = nil) - case formatter - when "junit" - junit_formatter = Spec::JUnitFormatter.file(Path.new(output_path.not_nil!)) - Spec.add_formatter(junit_formatter) - when "verbose" - Spec.override_default_formatter(Spec::VerboseFormatter.new) - when "tap" - Spec.override_default_formatter(Spec::TAPFormatter.new) - end -end + begin + option_parser.parse(args) + rescue e : OptionParser::InvalidOption + abort("Error: #{e.message}") + end -begin - Spec.option_parser.parse(ARGV) -rescue e : OptionParser::InvalidOption - abort("Error: #{e.message}") -end + unless args.empty? + STDERR.puts "Error: unknown argument '#{args.first}'" + exit 1 + end -unless ARGV.empty? - STDERR.puts "Error: unknown argument '#{ARGV.first}'" - exit 1 -end + if ENV["SPEC_VERBOSE"]? == "1" + override_default_formatter(Spec::VerboseFormatter.new) + end -if ENV["SPEC_VERBOSE"]? == "1" - Spec.override_default_formatter(Spec::VerboseFormatter.new) -end + add_split_filter ENV["SPEC_SPLIT"]? -Spec.add_split_filter ENV["SPEC_SPLIT"]? + {% unless flag?(:wasm32) %} + # TODO(wasm): Enable this once `Process.on_interrupt` is implemented + Process.on_interrupt { abort! } + {% end %} -{% unless flag?(:wasm32) %} - # TODO(wasm): Enable this once `Process.on_interrupt` is implemented - Process.on_interrupt { Spec.abort! } -{% end %} + run + end + end +end -Spec.run +Spec.cli.main(ARGV) diff --git a/src/spec/cli.cr b/src/spec/cli.cr index e17f59206123..4f57c3d4adbb 100644 --- a/src/spec/cli.cr +++ b/src/spec/cli.cr @@ -1,139 +1,130 @@ require "option_parser" +require "colorize" # This file is included in the compiler to add usage instructions for the # spec runner on `crystal spec --help`. module Spec # :nodoc: - class_property pattern : Regex? - - # :nodoc: - class_property line : Int32? - - # :nodoc: - class_property slowest : Int32? - - # :nodoc: - class_property? fail_fast = false - - # :nodoc: - class_property? focus = false - - # :nodoc: - class_property? dry_run = false - - # :nodoc: - class_property? list_tags = false - - # :nodoc: - def self.add_location(file, line) - locations = @@locations ||= {} of String => Array(Int32) - locations.put_if_absent(File.expand_path(file)) { [] of Int32 } << line - end + # + # Configuration for a spec runner. More global state is defined in `./dsl.cr`. + class CLI + getter pattern : Regex? + getter line : Int32? + getter slowest : Int32? + getter? fail_fast = false + property? focus = false + getter? dry_run = false + getter? list_tags = false - # :nodoc: - def self.add_tag(tag) - if anti_tag = tag.lchop?('~') - (@@anti_tags ||= Set(String).new) << anti_tag - else - (@@tags ||= Set(String).new) << tag + def add_location(file, line) + locations = @locations ||= {} of String => Array(Int32) + locations.put_if_absent(File.expand_path(file)) { [] of Int32 } << line end - end - - # :nodoc: - class_getter randomizer_seed : UInt64? - class_getter randomizer : Random::PCG32? - # :nodoc: - def self.order=(mode) - seed = - case mode - when "default" - nil - when "random" - Random::Secure.rand(1..99999).to_u64 # 5 digits or less for simplicity - when UInt64 - mode + def add_tag(tag) + if anti_tag = tag.lchop?('~') + (@anti_tags ||= Set(String).new) << anti_tag else - raise ArgumentError.new("Order must be either 'default', 'random', or a numeric seed value") + (@tags ||= Set(String).new) << tag end + end - @@randomizer_seed = seed - @@randomizer = seed ? Random::PCG32.new(seed) : nil - end + getter randomizer_seed : UInt64? + getter randomizer : Random::PCG32? - # :nodoc: - class_property option_parser : OptionParser = begin - OptionParser.new do |opts| - opts.banner = "crystal spec runner" - opts.on("-e", "--example STRING", "run examples whose full nested names include STRING") do |pattern| - Spec.pattern = Regex.new(Regex.escape(pattern)) - end - opts.on("-l", "--line LINE", "run examples whose line matches LINE") do |line| - Spec.line = line.to_i - end - opts.on("-p", "--profile", "Print the 10 slowest specs") do - Spec.slowest = 10 - end - opts.on("--fail-fast", "abort the run on first failure") do - Spec.fail_fast = true - end - opts.on("--location file:line", "run example at line 'line' in file 'file', multiple allowed") do |location| - if location =~ /\A(.+?)\:(\d+)\Z/ - Spec.add_location $1, $2.to_i + def order=(mode) + seed = + case mode + when "default" + nil + when "random" + Random::Secure.rand(1..99999).to_u64 # 5 digits or less for simplicity + when UInt64 + mode else - STDERR.puts "location #{location} must be file:line" - exit 1 + raise ArgumentError.new("Order must be either 'default', 'random', or a numeric seed value") end - end - opts.on("--tag TAG", "run examples with the specified TAG, or exclude examples by adding ~ before the TAG.") do |tag| - Spec.add_tag tag - end - opts.on("--list-tags", "lists all the tags used.") do - Spec.list_tags = true - end - opts.on("--order MODE", "run examples in random order by passing MODE as 'random' or to a specific seed by passing MODE as the seed value") do |mode| - if mode.in?("default", "random") - Spec.order = mode - elsif seed = mode.to_u64? - Spec.order = seed - else - abort("order must be either 'default', 'random', or a numeric seed value") + + @randomizer_seed = seed + @randomizer = seed ? Random::PCG32.new(seed) : nil + end + + def option_parser : OptionParser + @option_parser ||= OptionParser.new do |opts| + opts.banner = "crystal spec runner" + opts.on("-e", "--example STRING", "run examples whose full nested names include STRING") do |pattern| + @pattern = Regex.new(Regex.escape(pattern)) + end + opts.on("-l", "--line LINE", "run examples whose line matches LINE") do |line| + @line = line.to_i + end + opts.on("-p", "--profile", "Print the 10 slowest specs") do + @slowest = 10 + end + opts.on("--fail-fast", "abort the run on first failure") do + @fail_fast = true + end + opts.on("--location file:line", "run example at line 'line' in file 'file', multiple allowed") do |location| + if location =~ /\A(.+?)\:(\d+)\Z/ + add_location $1, $2.to_i + else + STDERR.puts "location #{location} must be file:line" + exit 1 + end + end + opts.on("--tag TAG", "run examples with the specified TAG, or exclude examples by adding ~ before the TAG.") do |tag| + add_tag tag + end + opts.on("--list-tags", "lists all the tags used.") do + @list_tags = true + end + opts.on("--order MODE", "run examples in random order by passing MODE as 'random' or to a specific seed by passing MODE as the seed value") do |mode| + if mode.in?("default", "random") + self.order = mode + elsif seed = mode.to_u64? + self.order = seed + else + abort("order must be either 'default', 'random', or a numeric seed value") + end + end + opts.on("--junit_output OUTPUT_PATH", "generate JUnit XML output within the given OUTPUT_PATH") do |output_path| + configure_formatter("junit", output_path) + end + opts.on("-h", "--help", "show this help") do |pattern| + puts opts + exit + end + opts.on("-v", "--verbose", "verbose output") do + configure_formatter("verbose") + end + opts.on("--tap", "Generate TAP output (Test Anything Protocol)") do + configure_formatter("tap") + end + opts.on("--color", "Enabled ANSI colored output") do + Colorize.enabled = true + end + opts.on("--no-color", "Disable ANSI colored output") do + Colorize.enabled = false + end + opts.on("--dry-run", "Pass all tests without execution") do + @dry_run = true + end + opts.unknown_args do |args| end - end - opts.on("--junit_output OUTPUT_PATH", "generate JUnit XML output within the given OUTPUT_PATH") do |output_path| - configure_formatter("junit", output_path) - end - opts.on("-h", "--help", "show this help") do |pattern| - puts opts - exit - end - opts.on("-v", "--verbose", "verbose output") do - configure_formatter("verbose") - end - opts.on("--tap", "Generate TAP output (Test Anything Protocol)") do - configure_formatter("tap") - end - opts.on("--color", "Enabled ANSI colored output") do - Colorize.enabled = true - end - opts.on("--no-color", "Disable ANSI colored output") do - Colorize.enabled = false - end - opts.on("--dry-run", "Pass all tests without execution") do - Spec.dry_run = true - end - opts.unknown_args do |args| end end + + # Blank implementation to reduce the interface of spec's option parser for + # inclusion in the compiler. This avoids depending on more of `Spec` + # module. + # The real implementation in `../spec.cr` overrides this for actual use. + def configure_formatter(formatter, output_path = nil) + end end - # :nodoc: - # - # Blank implementation to reduce the interface of spec's option parser for - # inclusion in the compiler. This avoids depending on more of `Spec` - # module. - # The real implementation in `../spec.cr` overrides this for actual use. - def self.configure_formatter(formatter, output_path = nil) + @[Deprecated("This is an internal API.")] + def self.randomizer : Random::PCG32? + @@cli.randomizer end end diff --git a/src/spec/context.cr b/src/spec/context.cr index 0a1109c12b92..c18836fe0455 100644 --- a/src/spec/context.cr +++ b/src/spec/context.cr @@ -126,13 +126,15 @@ module Spec exception : Exception? # :nodoc: - def self.root_context - RootContext.instance - end + class CLI + def root_context + RootContext.instance + end - # :nodoc: - def self.current_context : Context - RootContext.current_context + # :nodoc: + def current_context : Context + RootContext.current_context + end end # :nodoc: @@ -167,7 +169,7 @@ module Spec end def report_formatters(result) - Spec.formatters.each(&.report(result)) + Spec.cli.formatters.each(&.report(result)) end def succeeded @@ -175,8 +177,8 @@ module Spec end def finish(elapsed_time, aborted = false) - Spec.formatters.each(&.finish(elapsed_time, aborted)) - Spec.formatters.each(&.print_results(elapsed_time, aborted)) + Spec.cli.formatters.each(&.finish(elapsed_time, aborted)) + Spec.cli.formatters.each(&.print_results(elapsed_time, aborted)) end def print_results(elapsed_time, aborted = false) @@ -223,13 +225,13 @@ module Spec end end - if Spec.slowest + if Spec.cli.slowest puts results = results_for(:success) + results_for(:fail) - top_n = results.sort_by { |res| -res.elapsed.not_nil!.to_f }[0..Spec.slowest.not_nil!] + top_n = results.sort_by { |res| -res.elapsed.not_nil!.to_f }[0..Spec.cli.slowest.not_nil!] top_n_time = top_n.sum &.elapsed.not_nil!.total_seconds percent = (top_n_time * 100) / elapsed_time.total_seconds - puts "Top #{Spec.slowest} slowest examples (#{top_n_time.humanize} seconds, #{percent.round(2)}% of total time):" + puts "Top #{Spec.cli.slowest} slowest examples (#{top_n_time.humanize} seconds, #{percent.round(2)}% of total time):" top_n.each do |res| puts " #{res.description}" res_elapsed = res.elapsed.not_nil!.total_seconds.humanize @@ -252,7 +254,7 @@ module Spec puts "Aborted!".colorize.red if aborted puts "Finished in #{Spec.to_human(elapsed_time)}" puts Spec.color("#{total} examples, #{failures.size} failures, #{errors.size} errors, #{pendings.size} pending", final_status) - puts Spec.color("Only running `focus: true`", :focus) if Spec.focus? + puts Spec.color("Only running `focus: true`", :focus) if Spec.cli.focus? unless failures_and_errors.empty? puts @@ -268,13 +270,13 @@ module Spec end def print_order_message - if randomizer_seed = Spec.randomizer_seed + if randomizer_seed = Spec.cli.randomizer_seed puts Spec.color("Randomized with seed: #{randomizer_seed}", :order) end end def describe(description, file, line, end_line, focus, tags, &block) - Spec.focus = true if focus + Spec.cli.focus = true if focus context = Spec::ExampleGroup.new(@@current_context, description, file, line, end_line, focus, tags) @@current_context.children << context @@ -298,7 +300,7 @@ module Spec private def add_example(description, file, line, end_line, focus, tags, block) check_nesting_spec(file, line) do - Spec.focus = true if focus + Spec.cli.focus = true if focus @@current_context.children << Example.new(@@current_context, description, file, line, end_line, focus, tags, block) end @@ -334,12 +336,12 @@ module Spec # :nodoc: def run - Spec.formatters.each(&.push(self)) + Spec.cli.formatters.each(&.push(self)) ran = run_around_all_hooks(ExampleGroup::Procsy.new(self) { internal_run }) ran || internal_run - Spec.formatters.each(&.pop) + Spec.cli.formatters.each(&.pop) end protected def report(status : Status, description, file, line, elapsed = nil, ex = nil) diff --git a/src/spec/dsl.cr b/src/spec/dsl.cr index 065b31e7aff4..578076b86d69 100644 --- a/src/spec/dsl.cr +++ b/src/spec/dsl.cr @@ -2,6 +2,10 @@ require "colorize" require "option_parser" module Spec + # :nodoc: + # The default spec runner. + class_getter cli = CLI.new + # :nodoc: enum InfoKind Comment @@ -61,14 +65,6 @@ module Spec class NestingSpecError < SpecError end - @@aborted = false - - # :nodoc: - def self.abort! - @@aborted = true - finish_run - end - # :nodoc: def self.to_human(span : Time::Span) total_milliseconds = span.total_milliseconds @@ -92,17 +88,6 @@ module Spec record SplitFilter, remainder : Int32, quotient : Int32 - @@split_filter : SplitFilter? = nil - - def self.add_split_filter(filter) - if filter - r, _, m = filter.partition('%') - @@split_filter = SplitFilter.new(remainder: r.to_i, quotient: m.to_i) - else - @@split_filter = nil - end - end - # Instructs the spec runner to execute the given block # before each spec in the spec suite. # @@ -118,7 +103,7 @@ module Spec # # will print, just before each spec, 1 and then 2. def self.before_each(&block) - root_context.before_each(&block) + @@cli.root_context.before_each(&block) end # Instructs the spec runner to execute the given block @@ -136,7 +121,7 @@ module Spec # # will print, just after each spec, 2 and then 1. def self.after_each(&block) - root_context.after_each(&block) + @@cli.root_context.after_each(&block) end # Instructs the spec runner to execute the given block @@ -154,7 +139,7 @@ module Spec # # will print, just before the spec suite starts, 1 and then 2. def self.before_suite(&block) - root_context.before_all(&block) + @@cli.root_context.before_all(&block) end # Instructs the spec runner to execute the given block @@ -172,7 +157,7 @@ module Spec # # will print, just after the spec suite ends, 2 and then 1. def self.after_suite(&block) - root_context.after_all(&block) + @@cli.root_context.after_all(&block) end # Instructs the spec runner to execute the given block when each spec in the @@ -196,129 +181,157 @@ module Spec # it { } # ``` def self.around_each(&block : Example::Procsy ->) - root_context.around_each(&block) + @@cli.root_context.around_each(&block) end - @@start_time : Time::Span? = nil - # :nodoc: - def self.run - @@start_time = Time.monotonic - - at_exit do |status| - # Do not run specs if the process is exiting on an error - next unless status == 0 - - begin - if Spec.list_tags? - execute_list_tags - else - execute_examples + class CLI + @aborted = false + + def abort! + @aborted = true + finish_run + end + + @split_filter : SplitFilter? = nil + + def add_split_filter(filter) + if filter + r, _, m = filter.partition('%') + @split_filter = SplitFilter.new(remainder: r.to_i, quotient: m.to_i) + else + @split_filter = nil + end + end + + @start_time : Time::Span? = nil + + def run + @start_time = Time.monotonic + + at_exit do |status| + # Do not run specs if the process is exiting on an error + next unless status == 0 + + begin + if list_tags? + execute_list_tags + else + execute_examples + end + rescue ex + STDERR.print "Unhandled exception: " + ex.inspect_with_backtrace(STDERR) + STDERR.flush + @aborted = true + ensure + finish_run unless list_tags? end - rescue ex - STDERR.print "Unhandled exception: " - ex.inspect_with_backtrace(STDERR) - STDERR.flush - @@aborted = true - ensure - finish_run unless Spec.list_tags? end end - end - # :nodoc: - def self.execute_examples - log_setup - maybe_randomize - run_filters - root_context.run - end + def execute_examples + log_setup + maybe_randomize + run_filters + root_context.run + end - # :nodoc: - def self.execute_list_tags - run_filters - tag_counts = collect_tags(root_context) - print_list_tags(tag_counts) - end + def execute_list_tags + run_filters + tag_counts = collect_tags(root_context) + print_list_tags(tag_counts) + end - private def self.collect_tags(context) : Hash(String, Int32) - tag_counts = Hash(String, Int32).new(0) - collect_tags(tag_counts, context, Set(String).new) - tag_counts - end + private def collect_tags(context) : Hash(String, Int32) + tag_counts = Hash(String, Int32).new(0) + collect_tags(tag_counts, context, Set(String).new) + tag_counts + end - private def self.collect_tags(tag_counts, context : Context, tags) - if context.responds_to?(:tags) && (context_tags = context.tags) - tags += context_tags + private def collect_tags(tag_counts, context : Context, tags) + if context.responds_to?(:tags) && (context_tags = context.tags) + tags += context_tags + end + context.children.each do |child| + collect_tags(tag_counts, child, tags) + end end - context.children.each do |child| - collect_tags(tag_counts, child, tags) + + private def collect_tags(tag_counts, example : Example, tags) + if example_tags = example.tags + tags += example_tags + end + if tags.empty? + tag_counts.update("untagged") { |count| count + 1 } + else + tags.tally(tag_counts) + end end - end - private def self.collect_tags(tag_counts, example : Example, tags) - if example_tags = example.tags - tags += example_tags + private def print_list_tags(tag_counts : Hash(String, Int32)) : Nil + return if tag_counts.empty? + longest_name_size = tag_counts.keys.max_of(&.size) + tag_counts.to_a.sort_by! { |k, v| {-v, k} }.each do |tag_name, count| + puts "#{tag_name.rjust(longest_name_size)}: #{count}" + end end - if tags.empty? - tag_counts.update("untagged") { |count| count + 1 } - else - tags.tally(tag_counts) + + # :nodoc: + # + # Workaround for #8914 + private macro defined?(t) + {% if t.resolve? %} + {{ yield }} + {% end %} end - end - private def self.print_list_tags(tag_counts : Hash(String, Int32)) : Nil - return if tag_counts.empty? - longest_name_size = tag_counts.keys.max_of(&.size) - tag_counts.to_a.sort_by! { |k, v| {-v, k} }.each do |tag_name, count| - puts "#{tag_name.rjust(longest_name_size)}: #{count}" + # :nodoc: + def log_setup end - end - # :nodoc: - # - # Workaround for #8914 - private macro defined?(t) - {% if t.resolve? %} - {{ yield }} - {% end %} - end + # :nodoc: + macro finished + # :nodoc: + # + # Initialized the log module for the specs. + # If the "log" module is required it is configured to emit no entries by default. + def log_setup + defined?(::Log) do + if Log.responds_to?(:setup) + Log.setup_from_env(default_level: :none) + end + end + end + end - # :nodoc: - def self.log_setup - end + def finish_run + elapsed_time = Time.monotonic - @start_time.not_nil! + root_context.finish(elapsed_time, @aborted) + exit 1 if !root_context.succeeded || @aborted || (focus? && ENV["SPEC_FOCUS_NO_FAIL"]? != "1") + end - # :nodoc: - macro finished # :nodoc: - # - # Initialized the log module for the specs. - # If the "log" module is required it is configured to emit no entries by default. - def self.log_setup - defined?(::Log) do - if Log.responds_to?(:setup) - Log.setup_from_env(default_level: :none) - end + def maybe_randomize + if randomizer = @randomizer + root_context.randomize(randomizer) end end - end - def self.finish_run - elapsed_time = Time.monotonic - @@start_time.not_nil! - root_context.finish(elapsed_time, @@aborted) - exit 1 if !root_context.succeeded || @@aborted || (focus? && ENV["SPEC_FOCUS_NO_FAIL"]? != "1") + # :nodoc: + def run_filters + root_context.run_filters(@pattern, @line, @locations, @split_filter, @focus, @tags, @anti_tags) + end end - # :nodoc: - def self.maybe_randomize - if randomizer = @@randomizer - root_context.randomize(randomizer) - end + @[Deprecated("This is an internal API.")] + def self.finish_run + @@cli.finish_run end - # :nodoc: - def self.run_filters - root_context.run_filters(@@pattern, @@line, @@locations, @@split_filter, @@focus, @@tags, @@anti_tags) + @[Deprecated("This is an internal API.")] + def self.add_split_filter(filter) + @@cli.add_split_filter(filter) end end diff --git a/src/spec/example.cr b/src/spec/example.cr index e6ae51942a38..72bffd6c0e07 100644 --- a/src/spec/example.cr +++ b/src/spec/example.cr @@ -18,10 +18,10 @@ module Spec # :nodoc: def run - Spec.root_context.check_nesting_spec(file, line) do - Spec.formatters.each(&.before_example(description)) + Spec.cli.root_context.check_nesting_spec(file, line) do + Spec.cli.formatters.each(&.before_example(description)) - if Spec.dry_run? + if Spec.cli.dry_run? @parent.report(:success, description, file, line) return end @@ -51,12 +51,12 @@ module Spec @parent.report(:success, description, file, line, Time.monotonic - start) rescue ex : Spec::AssertionFailed @parent.report(:fail, description, file, line, Time.monotonic - start, ex) - Spec.abort! if Spec.fail_fast? + Spec.cli.abort! if Spec.cli.fail_fast? rescue ex : Spec::ExamplePending @parent.report(:pending, description, file, line, Time.monotonic - start) rescue ex @parent.report(:error, description, file, line, Time.monotonic - start, ex) - Spec.abort! if Spec.fail_fast? + Spec.cli.abort! if Spec.cli.fail_fast? ensure @parent.run_after_each_hooks end diff --git a/src/spec/formatter.cr b/src/spec/formatter.cr index 51b355ec075f..cfd88f2b5a6a 100644 --- a/src/spec/formatter.cr +++ b/src/spec/formatter.cr @@ -55,7 +55,7 @@ module Spec end def print_results(elapsed_time : Time::Span, aborted : Bool) - Spec.root_context.print_results(elapsed_time, aborted) + Spec.cli.root_context.print_results(elapsed_time, aborted) end end @@ -111,22 +111,34 @@ module Spec end def print_results(elapsed_time : Time::Span, aborted : Bool) - Spec.root_context.print_results(elapsed_time, aborted) + Spec.cli.root_context.print_results(elapsed_time, aborted) end end - @@formatters = [Spec::DotFormatter.new] of Spec::Formatter - # :nodoc: - def self.formatters - @@formatters + class CLI + @formatters = [Spec::DotFormatter.new] of Spec::Formatter + + def formatters + @formatters + end + + def override_default_formatter(formatter) + @formatters[0] = formatter + end + + def add_formatter(formatter) + @formatters << formatter + end end + @[Deprecated("This is an internal API.")] def self.override_default_formatter(formatter) - @@formatters[0] = formatter + @@cli.override_default_formatter(formatter) end + @[Deprecated("This is an internal API.")] def self.add_formatter(formatter) - @@formatters << formatter + @@cli.add_formatter(formatter) end end diff --git a/src/spec/methods.cr b/src/spec/methods.cr index f98326be0ed3..54b8de34b9bb 100644 --- a/src/spec/methods.cr +++ b/src/spec/methods.cr @@ -17,7 +17,7 @@ module Spec::Methods # # If `focus` is `true`, only this `describe`, and others marked with `focus: true`, will run. def describe(description = nil, file = __FILE__, line = __LINE__, end_line = __END_LINE__, focus : Bool = false, tags : String | Enumerable(String) | Nil = nil, &block) - Spec.root_context.describe(description.to_s, file, line, end_line, focus, tags, &block) + Spec.cli.root_context.describe(description.to_s, file, line, end_line, focus, tags, &block) end # Defines an example group that establishes a specific context, @@ -46,7 +46,7 @@ module Spec::Methods # # If `focus` is `true`, only this test, and others marked with `focus: true`, will run. def it(description = "assert", file = __FILE__, line = __LINE__, end_line = __END_LINE__, focus : Bool = false, tags : String | Enumerable(String) | Nil = nil, &block) - Spec.root_context.it(description.to_s, file, line, end_line, focus, tags, &block) + Spec.cli.root_context.it(description.to_s, file, line, end_line, focus, tags, &block) end # Defines a pending test case. @@ -72,7 +72,7 @@ module Spec::Methods # # If `focus` is `true`, only this test, and others marked with `focus: true`, will run. def pending(description = "assert", file = __FILE__, line = __LINE__, end_line = __END_LINE__, focus : Bool = false, tags : String | Enumerable(String) | Nil = nil) - Spec.root_context.pending(description.to_s, file, line, end_line, focus, tags) + Spec.cli.root_context.pending(description.to_s, file, line, end_line, focus, tags) end # Fails an example. @@ -124,10 +124,10 @@ module Spec::Methods # end # ``` def before_each(&block) - if Spec.current_context.is_a?(RootContext) + if Spec.cli.current_context.is_a?(RootContext) raise "Can't call `before_each` outside of a describe/context" end - Spec.current_context.before_each(&block) + Spec.cli.current_context.before_each(&block) end # Executes the given block after each spec in the current context runs. @@ -154,10 +154,10 @@ module Spec::Methods # end # ``` def after_each(&block) - if Spec.current_context.is_a?(RootContext) + if Spec.cli.current_context.is_a?(RootContext) raise "Can't call `after_each` outside of a describe/context" end - Spec.current_context.after_each(&block) + Spec.cli.current_context.after_each(&block) end # Executes the given block before the first spec in the current context runs. @@ -184,10 +184,10 @@ module Spec::Methods # end # ``` def before_all(&block) - if Spec.current_context.is_a?(RootContext) + if Spec.cli.current_context.is_a?(RootContext) raise "Can't call `before_all` outside of a describe/context" end - Spec.current_context.before_all(&block) + Spec.cli.current_context.before_all(&block) end # Executes the given block after the last spec in the current context runs. @@ -214,10 +214,10 @@ module Spec::Methods # end # ``` def after_all(&block) - if Spec.current_context.is_a?(RootContext) + if Spec.cli.current_context.is_a?(RootContext) raise "Can't call `after_all` outside of a describe/context" end - Spec.current_context.after_all(&block) + Spec.cli.current_context.after_all(&block) end # Executes the given block when each spec in the current context runs. @@ -252,10 +252,10 @@ module Spec::Methods # end # ``` def around_each(&block : Example::Procsy ->) - if Spec.current_context.is_a?(RootContext) + if Spec.cli.current_context.is_a?(RootContext) raise "Can't call `around_each` outside of a describe/context" end - Spec.current_context.around_each(&block) + Spec.cli.current_context.around_each(&block) end # Executes the given block when the current context runs. @@ -297,10 +297,10 @@ module Spec::Methods # end # ``` def around_all(&block : ExampleGroup::Procsy ->) - if Spec.current_context.is_a?(RootContext) + if Spec.cli.current_context.is_a?(RootContext) raise "Can't call `around_all` outside of a describe/context" end - Spec.current_context.around_all(&block) + Spec.cli.current_context.around_all(&block) end end From 11b107ba1ae5a0978b28e1381e62e88870a52f2f Mon Sep 17 00:00:00 2001 From: femto Date: Fri, 12 Jan 2024 20:19:58 +0800 Subject: [PATCH 0908/1551] Add support for `Etc/UTC` time zone identifier without tzdb (#14185) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller Co-authored-by: George Dietrich --- spec/std/time/location_spec.cr | 4 +--- src/time/location.cr | 12 ++++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/spec/std/time/location_spec.cr b/spec/std/time/location_spec.cr index bcf7595cffd8..78e27a230fde 100644 --- a/spec/std/time/location_spec.cr +++ b/spec/std/time/location_spec.cr @@ -67,9 +67,7 @@ class Time::Location with_zoneinfo do Location.load("UTC").should eq Location::UTC Location.load("").should eq Location::UTC - - # Etc/UTC could be pointing to anything - Location.load("Etc/UTC").should_not eq Location::UTC + Location.load("Etc/UTC").should eq Location::UTC end end diff --git a/src/time/location.cr b/src/time/location.cr index d84c1ab14218..7e0e8f160cb9 100644 --- a/src/time/location.cr +++ b/src/time/location.cr @@ -236,7 +236,7 @@ class Time::Location # # *name* is understood to be a location name in the IANA Time # Zone database, such as `"America/New_York"`. As special cases, - # `"UTC"` and empty string (`""`) return `Location::UTC`, and + # `"UTC"`, `"Etc/UTC"` and empty string (`""`) return `Location::UTC`, and # `"Local"` returns `Location.local`. # # The implementation uses a list of system-specific paths to look for a time @@ -277,7 +277,11 @@ class Time::Location # `Location`, unless the time zone database has been updated in between. def self.load(name : String) : Location case name - when "", "UTC" + when "", "UTC", "Etc/UTC" + # `UTC` is a special identifier, empty string represents a fallback mechanism. + # `Etc/UTC` is technically a tzdb identifier which could potentially point to anything. + # But we map it to `Location::UTC` directly for convenience which allows it to work + # without a copy of the database. UTC when "Local" local @@ -339,7 +343,7 @@ class Time::Location # The environment variable `ENV["TZ"]` is consulted for finding the time zone # to use. # - # * `"UTC"` and empty string (`""`) return `Location::UTC` + # * `"UTC"`, `"Etc/UTC"` and empty string (`""`) return `Location::UTC` # * Any other value (such as `"Europe/Berlin"`) is tried to be resolved using # `Location.load`. # * If `ENV["TZ"]` is not set, the system's local time zone data will be used @@ -348,7 +352,7 @@ class Time::Location # `Location::UTC` is returned. def self.load_local : Location case tz = ENV["TZ"]? - when "", "UTC" + when "", "UTC", "Etc/UTC" return UTC when Nil if localtime = Crystal::System::Time.load_localtime From 7ed7a74255c736ecf2045a54de0fd34dfa028dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 12 Jan 2024 15:02:54 +0100 Subject: [PATCH 0909/1551] Update previous Crystal release 1.11.1 (#14224) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 6 +++--- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win_build_portable.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9804bf22894c..548716adb511 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.11.0/crystal-1.11.0-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.11.1/crystal-1.11.1-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 64ef777fa9d3..96403e595e17 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.11.0-build + image: crystallang/crystal:1.11.1-build name: "Test Interpreter" steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.11.0-build + image: crystallang/crystal:1.11.1-build name: Build interpreter steps: - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.11.0-build + image: crystallang/crystal:1.11.1-build strategy: matrix: part: [0, 1, 2, 3] diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c501d1b0061c..f4b284f9ce74 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.0] + crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.1] flags: [""] include: # libffi is only available starting from the 1.2.2 build images diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index e8e891cab665..15fa6df8712c 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -56,7 +56,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.11.0" + crystal: "1.11.1" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 16dfe3b42e32..30b148448416 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.11.0-alpine + container: crystallang/crystal:1.11.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -27,7 +27,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.11.0-alpine + container: crystallang/crystal:1.11.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.11.0-alpine + container: crystallang/crystal:1.11.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index ad6de969c5f5..523a645085bf 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.11.0-alpine + container: crystallang/crystal:1.11.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.11.0-alpine + container: crystallang/crystal:1.11.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index eec64cc0bb39..ca3431808c01 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.11.0-build + container: crystallang/crystal:1.11.1-build steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 6ea5568305b6..77e823384ef2 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -24,7 +24,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.11.0" + crystal: "1.11.1" - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index d67c2d7f6c4e..9a3776a3b7d3 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.11.0/crystal-1.11.0-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.11.0-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.11.1/crystal-1.11.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.11.1-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.11.0}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.11.1}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 02db8fb637f7..647315af5e70 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.11.0/crystal-1.11.0-1-darwin-universal.tar.gz"; - sha256 = "sha256:0x3adik0rpfkgw8nszf6l52vr4m7fs7rwqf1r0m17x4kgq67daiz"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.11.1/crystal-1.11.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:1yfb8xzhb3hnf9dn5y0mk25va1hbmk03x0hm9da6805xllr7bq8l"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.11.0/crystal-1.11.0-1-darwin-universal.tar.gz"; - sha256 = "sha256:0x3adik0rpfkgw8nszf6l52vr4m7fs7rwqf1r0m17x4kgq67daiz"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.11.1/crystal-1.11.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:1yfb8xzhb3hnf9dn5y0mk25va1hbmk03x0hm9da6805xllr7bq8l"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.11.0/crystal-1.11.0-1-linux-x86_64.tar.gz"; - sha256 = "sha256:1npbn61mw6fchdlg64s7zd3k33711ifhs8n75skw8w30i4ryilhk"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.11.1/crystal-1.11.1-1-linux-x86_64.tar.gz"; + sha256 = "sha256:18mqp2sy5df6bkyvndg4xwgjf6lmaipz5mcr6lg9a47is2vr1i7v"; }; }.${pkgs.stdenv.system}); From 5bd7cb538e47e2639b118c7ade2e68362d85a434 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Jan 2024 00:46:00 +0800 Subject: [PATCH 0910/1551] `XML::Reader`: Disallow attributes containing null bytes (#14193) --- spec/std/xml/reader_spec.cr | 15 +++++++++++++++ src/xml/reader.cr | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/spec/std/xml/reader_spec.cr b/spec/std/xml/reader_spec.cr index 54183dd8742d..d89593620970 100644 --- a/spec/std/xml/reader_spec.cr +++ b/spec/std/xml/reader_spec.cr @@ -359,6 +359,11 @@ module XML reader.value.should eq("2") reader.read.should be_false end + + it "raises if attribute contains null byte" do + reader = Reader.new("") + expect_raises(Exception) { reader.move_to_attribute("\0") } + end end describe "#[]" do @@ -378,6 +383,11 @@ module XML reader.read # reader["id"].should eq("1") end + + it "raises if attribute contains null byte" do + reader = Reader.new("") + expect_raises(Exception) { reader["\0"] } + end end describe "#[]?" do @@ -397,6 +407,11 @@ module XML reader.read # reader["id"]?.should eq("1") end + + it "raises if attribute contains null byte" do + reader = Reader.new("") + expect_raises(Exception) { reader["\0"]? } + end end describe "#move_to_element" do diff --git a/src/xml/reader.cr b/src/xml/reader.cr index 9d1de3ed4c9e..decdd8468185 100644 --- a/src/xml/reader.cr +++ b/src/xml/reader.cr @@ -119,6 +119,7 @@ class XML::Reader # Moves to the `XML::Reader::Type::ATTRIBUTE` with the specified name. def move_to_attribute(name : String) : Bool + check_no_null_byte(name) LibXML.xmlTextReaderMoveToAttribute(@reader, name) == 1 end @@ -131,6 +132,7 @@ class XML::Reader # Gets the attribute content for the *attribute* given by name. # Returns `nil` if attribute is not found. def []?(attribute : String) : String? + check_no_null_byte(attribute) value = LibXML.xmlTextReaderGetAttribute(@reader, attribute) String.new(value) if value end @@ -200,4 +202,10 @@ class XML::Reader Error.add_errors(@errors) end end + + private def check_no_null_byte(attribute) + if attribute.byte_index(0) + raise XML::Error.new("Invalid attribute name: #{attribute.inspect} (contains null character)", 0) + end + end end From 06f2f82b156cec23d5f1dee06e9041457281c0f6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Jan 2024 00:46:15 +0800 Subject: [PATCH 0911/1551] Always call `LibXML.xmlInitParser` when requiring XML libraries (#14191) --- src/xml/libxml2.cr | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/xml/libxml2.cr b/src/xml/libxml2.cr index 3dff7fb6cb40..feea8a1ba6d7 100644 --- a/src/xml/libxml2.cr +++ b/src/xml/libxml2.cr @@ -11,9 +11,10 @@ require "./save_options" lib LibXML alias Int = LibC::Int + fun xmlInitParser + # TODO: check if other platforms also support per-thread globals {% if flag?(:win32) %} - fun xmlInitParser fun __xmlIndentTreeOutput : Int* fun __xmlTreeIndentString : UInt8** {% else %} @@ -324,9 +325,7 @@ lib LibXML fun xmlValidateNameValue(value : UInt8*) : Int end -{% if flag?(:win32) %} - LibXML.xmlInitParser -{% end %} +LibXML.xmlInitParser LibXML.xmlGcMemSetup( ->GC.free, From f5844b0fc1ad9eec10cb823f732b9e6e72ee7422 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Jan 2024 00:48:20 +0800 Subject: [PATCH 0912/1551] Reserve stack space on non-main threads for crash recovery on Windows (#14187) --- src/crystal/system/win32/thread.cr | 13 ++++++++++++- src/exception/call_stack/stackwalk.cr | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr index 9a13bfb4dc03..5dbcbabbfad9 100644 --- a/src/crystal/system/win32/thread.cr +++ b/src/crystal/system/win32/thread.cr @@ -12,13 +12,24 @@ module Crystal::System::Thread @system_handle = GC.beginthreadex( security: Pointer(Void).null, stack_size: LibC::UInt.zero, - start_address: ->(data : Void*) { data.as(::Thread).start; LibC::UInt.zero }, + start_address: ->Thread.thread_proc(Void*), arglist: self.as(Void*), initflag: LibC::UInt.zero, thrdaddr: Pointer(LibC::UInt).null, ) end + def self.thread_proc(data : Void*) : LibC::UInt + # ensure that even in the case of stack overflow there is enough reserved + # stack space for recovery (for the main thread this is done in + # `Exception::CallStack.setup_crash_handler`) + stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE + LibC.SetThreadStackGuarantee(pointerof(stack_size)) + + data.as(::Thread).start + LibC::UInt.zero + end + def self.current_handle : Handle # `GetCurrentThread` returns a _constant_ and is only meaningful as an # argument to Win32 APIs; to uniquely identify it we must duplicate the handle diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index 4879ed8bb95d..4d60e02305d0 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -51,7 +51,8 @@ struct Exception::CallStack end) # ensure that even in the case of stack overflow there is enough reserved - # stack space for recovery + # stack space for recovery (for other threads this is done in + # `Crystal::System::Thread.thread_proc`) stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE LibC.SetThreadStackGuarantee(pointerof(stack_size)) end From 3bf68ddfcc8cf253ec84a7aa27dd103180a51618 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Jan 2024 06:17:35 +0800 Subject: [PATCH 0913/1551] Remove extra newline in top-level `FunDef`'s string representation (#14212) --- spec/compiler/parser/to_s_spec.cr | 3 ++- src/compiler/crystal/syntax/to_s.cr | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index 752bece45374..75c0bac3ebee 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -143,7 +143,8 @@ describe "ASTNode#to_s" do expect_to_s %(@[Foo(1, 2, a: 1, b: 2)]) expect_to_s %(lib Foo\nend) expect_to_s %(lib LibC\n fun getchar(Int, Float)\nend) - expect_to_s %(fun foo(a : Void, b : Void, ...) : Void\n\nend) + expect_to_s %(fun foo(a : Void, b : Void, ...) : Void\nend) + expect_to_s %(fun foo\nend) expect_to_s %(lib Foo\n struct Foo\n a : Void\n b : Void\n end\nend) expect_to_s %(lib Foo\n union Foo\n a : Int\n b : Int32\n end\nend) expect_to_s %(lib Foo\n FOO = 0\nend) diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 8ea364d3f991..bf8eff6aef2d 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -1170,7 +1170,6 @@ module Crystal if body = node.body newline accept_with_indent body - newline append_indent @str << "end" end From e814029e3f27d2a7f6fe3a9d8d0222902fec6756 Mon Sep 17 00:00:00 2001 From: femto Date: Sat, 13 Jan 2024 06:18:00 +0800 Subject: [PATCH 0914/1551] Change short reference for top-level macros to `::foo` (#14203) --- spec/compiler/macro/macro_methods_spec.cr | 2 +- src/compiler/crystal/macros/methods.cr | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 9d6304e81f01..6e3af42e4d3f 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -3565,7 +3565,7 @@ module Crystal # there are no macro methods with required named parameters it "uses correct name for top-level macro methods" do - assert_macro_error %({{flag?}}), "wrong number of arguments for top-level macro 'flag?' (given 0, expected 1)" + assert_macro_error %({{flag?}}), "wrong number of arguments for macro '::flag?' (given 0, expected 1)" end end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 24d3d8bbd14d..abef63c402d3 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -300,7 +300,7 @@ module Crystal def interpret_run(node) if node.args.size == 0 - node.wrong_number_of_arguments "top-level macro 'run'", 0, "1+" + node.wrong_number_of_arguments "macro '::run'", 0, "1+" end node.args.first.accept self @@ -2841,7 +2841,7 @@ end private def full_macro_name(node, method, top_level) if top_level - "top-level macro '#{method}'" + "macro '::#{method}'" else "macro '#{node.class_desc}##{method}'" end From db8ecefc6d77df61b35a69c587c3dc1324f0222d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Jan 2024 06:18:13 +0800 Subject: [PATCH 0915/1551] Formatter: Add more whitespace around `ProcLiteral`s (#14209) --- spec/compiler/formatter/formatter_spec.cr | 8 ++++++++ src/compiler/crystal/tools/formatter.cr | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 4567638f0fd8..ce2c7e72364c 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1629,6 +1629,14 @@ describe Crystal::Formatter do assert_format "->( x : Int32 , y ) { x }", "->(x : Int32, y) { x }" assert_format "->{}" + # #13232 + assert_format "->{}", "-> { }", flags: %w[proc_literal_whitespace] + assert_format "->(){}", "-> { }", flags: %w[proc_literal_whitespace] + assert_format "->{1}", "-> { 1 }", flags: %w[proc_literal_whitespace] + assert_format "->(x : Int32) {}", "->(x : Int32) { }", flags: %w[proc_literal_whitespace] + assert_format "-> : Int32 {}", "-> : Int32 { }", flags: %w[proc_literal_whitespace] + assert_format "->do\nend", "-> do\nend", flags: %w[proc_literal_whitespace] + assert_format "-> : Int32 {}" assert_format "-> : Int32 | String { 1 }" assert_format "-> : Array(Int32) {}" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index fc3be7a4cf66..a18634fe8232 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -4256,7 +4256,7 @@ module Crystal skip_space_or_newline end - write " " unless a_def.args.empty? && !return_type + write " " if a_def.args.present? || return_type || flag?("proc_literal_whitespace") is_do = false if @token.keyword?(:do) @@ -4264,6 +4264,7 @@ module Crystal is_do = true else write_token :OP_LCURLY + write " " if a_def.body.is_a?(Nop) && flag?("proc_literal_whitespace") end skip_space From dba80384366cc0c2620682dfc4e2982430ee990a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Jan 2024 19:38:41 +0800 Subject: [PATCH 0916/1551] Fix: Always use `%p` for pointers in `Crystal::System.print_error` (#14221) --- src/exception/call_stack/libunwind.cr | 2 +- src/exception/call_stack/stackwalk.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 81943d99f376..9f17512491fe 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -102,7 +102,7 @@ struct Exception::CallStack end private def self.print_frame(repeated_frame) - Crystal::System.print_error "[%p] ", repeated_frame.ip.address + Crystal::System.print_error "[%p] ", repeated_frame.ip print_frame_location(repeated_frame) Crystal::System.print_error " (%d times)", repeated_frame.count + 1 unless repeated_frame.count == 0 Crystal::System.print_error "\n" diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index 4d60e02305d0..47b78e0ee0df 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -151,7 +151,7 @@ struct Exception::CallStack end private def self.print_frame(repeated_frame) - Crystal::System.print_error "[%p] ", repeated_frame.ip.address + Crystal::System.print_error "[%p] ", repeated_frame.ip print_frame_location(repeated_frame) Crystal::System.print_error " (%d times)", repeated_frame.count + 1 unless repeated_frame.count == 0 Crystal::System.print_error "\n" From c35b5e8b6fd0ee69f1c8ce8990bb2ea10e267f2f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Jan 2024 19:38:52 +0800 Subject: [PATCH 0917/1551] Always preserve the environment for specs that modify `ENV` (#14211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/compiler/macro/macro_methods_spec.cr | 12 ++-- spec/std/dir_spec.cr | 20 +------ spec/std/env_spec.cr | 46 ++++++--------- spec/std/process_spec.cr | 71 +++++++++++------------ 4 files changed, 59 insertions(+), 90 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 6e3af42e4d3f..13b063355c33 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1,4 +1,5 @@ require "../../spec_helper" +require "../../support/env" private def declare_class_var(container : ClassVarContainer, name, var_type : Type, annotations = nil) var = MetaTypeVar.new(name) @@ -3281,14 +3282,15 @@ module Crystal describe "env" do it "has key" do - ENV["FOO"] = "foo" - assert_macro %({{env("FOO")}}), %("foo") - ENV.delete "FOO" + with_env("FOO": "foo") do + assert_macro %({{env("FOO")}}), %("foo") + end end it "doesn't have key" do - ENV.delete "FOO" - assert_macro %({{env("FOO")}}), %(nil) + with_env("FOO": nil) do + assert_macro %({{env("FOO")}}), %(nil) + end end end diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index f0721ef66abb..f0c01d613570 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -3,25 +3,9 @@ require "../support/env" private def unset_tempdir(&) {% if flag?(:windows) %} - old_tempdirs = {ENV["TMP"]?, ENV["TEMP"]?, ENV["USERPROFILE"]?} - begin - ENV.delete("TMP") - ENV.delete("TEMP") - ENV.delete("USERPROFILE") - - yield - ensure - ENV["TMP"], ENV["TEMP"], ENV["USERPROFILE"] = old_tempdirs - end + with_env("TMP": nil, "TEMP": nil, "USERPROFILE": nil) { yield } {% else %} - begin - old_tempdir = ENV["TMPDIR"]? - ENV.delete("TMPDIR") - - yield - ensure - ENV["TMPDIR"] = old_tempdir - end + with_env("TMPDIR": nil) { yield } {% end %} end diff --git a/spec/std/env_spec.cr b/spec/std/env_spec.cr index 8342494b10f6..038bdc74b9b1 100644 --- a/spec/std/env_spec.cr +++ b/spec/std/env_spec.cr @@ -2,6 +2,23 @@ require "spec" require "./spec_helper" describe "ENV" do + # Preserves the existing environment for each spec. + # To avoid potential circular definitions, this has to use the system methods + # directly, rather than `ENV` or `with_env`. + around_each do |example| + old_env = {} of String => String + Crystal::System::Env.each { |key, value| old_env[key] = value } + + begin + example.run + ensure + keys = [] of String + Crystal::System::Env.each { |key| keys << key } + keys.each { |key| Crystal::System::Env.set(key, nil) } + old_env.each { |key, value| Crystal::System::Env.set(key, value) } + end + end + it "gets non existent key raises" do expect_raises KeyError, "Missing ENV key: \"NON-EXISTENT\"" do ENV["NON-EXISTENT"] @@ -16,8 +33,6 @@ describe "ENV" do (ENV["FOO"] = "1").should eq("1") ENV["FOO"].should eq("1") ENV["FOO"]?.should eq("1") - ensure - ENV.delete("FOO") end {% if flag?(:win32) %} @@ -25,15 +40,11 @@ describe "ENV" do (ENV["FOO"] = "1").should eq("1") ENV["Foo"].should eq("1") ENV["foo"]?.should eq("1") - ensure - ENV.delete("FOO") end {% else %} it "sets and gets case-sensitive" do ENV["FOO"] = "1" ENV["foo"]?.should be_nil - ensure - ENV.delete("FOO") end {% end %} @@ -47,16 +58,12 @@ describe "ENV" do it "sets to empty string" do (ENV["FOO_EMPTY"] = "").should eq "" ENV["FOO_EMPTY"]?.should eq "" - ensure - ENV.delete("FOO_EMPTY") end it "does has_key?" do ENV["FOO"] = "1" ENV.has_key?("NON_EXISTENT").should be_false ENV.has_key?("FOO").should be_true - ensure - ENV.delete("FOO") end it "deletes a key" do @@ -70,9 +77,6 @@ describe "ENV" do %w(FOO BAR).each { |k| ENV.keys.should_not contain(k) } ENV["FOO"] = ENV["BAR"] = "1" %w(FOO BAR).each { |k| ENV.keys.should contain(k) } - ensure - ENV.delete("FOO") - ENV.delete("BAR") end it "does not have an empty key" do @@ -86,9 +90,6 @@ describe "ENV" do ENV["FOO"] = "SOMEVALUE_1" ENV["BAR"] = "SOMEVALUE_2" [1, 2].each { |i| ENV.values.should contain("SOMEVALUE_#{i}") } - ensure - ENV.delete("FOO") - ENV.delete("BAR") end describe "[]=" do @@ -115,16 +116,12 @@ describe "ENV" do it "fetches with one argument" do ENV["1"] = "2" ENV.fetch("1").should eq("2") - ensure - ENV.delete("1") end it "fetches with default value" do ENV["1"] = "2" ENV.fetch("1", "3").should eq("2") ENV.fetch("2", "3").should eq("3") - ensure - ENV.delete("1") end it "fetches with block" do @@ -132,8 +129,6 @@ describe "ENV" do ENV.fetch("1") { |k| k + "block" }.should eq("2") ENV.fetch("2") { |k| k + "block" }.should eq("2block") ENV.fetch("3") { 4 }.should eq(4) - ensure - ENV.delete("1") end it "fetches and raises" do @@ -141,8 +136,6 @@ describe "ENV" do expect_raises KeyError, "Missing ENV key: \"2\"" do ENV.fetch("2") end - ensure - ENV.delete("1") end end @@ -162,16 +155,11 @@ describe "ENV" do "TEST_UNICODE_1" => "bar\u{d7ff}\u{10000}", "TEST_UNICODE_2" => "\u{1234}", }) - ensure - ENV.delete("TEST_UNICODE_1") - ENV.delete("TEST_UNICODE_2") end it "#to_h" do ENV["FOO"] = "foo" ENV.to_h["FOO"].should eq "foo" - ensure - ENV.delete("FOO") end {% if flag?(:win32) %} diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 94b8ec18a915..9734ec5ea99c 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -3,6 +3,7 @@ require "spec" require "process" require "./spec_helper" +require "../support/env" private def exit_code_command(code) {% if flag?(:win32) %} @@ -267,68 +268,62 @@ describe Process do end it "deletes existing environment variable" do - ENV["FOO"] = "bar" - value = Process.run(*print_env_command, env: {"FOO" => nil}) do |proc| - proc.output.gets_to_end + with_env("FOO": "bar") do + value = Process.run(*print_env_command, env: {"FOO" => nil}) do |proc| + proc.output.gets_to_end + end + value.should_not match /(*ANYCRLF)^FOO=/m end - value.should_not match /(*ANYCRLF)^FOO=/m - ensure - ENV.delete("FOO") end {% if flag?(:win32) %} it "deletes existing environment variable case-insensitive" do - ENV["FOO"] = "bar" - value = Process.run(*print_env_command, env: {"foo" => nil}) do |proc| - proc.output.gets_to_end + with_env("FOO": "bar") do + value = Process.run(*print_env_command, env: {"foo" => nil}) do |proc| + proc.output.gets_to_end + end + value.should_not match /(*ANYCRLF)^FOO=/mi end - value.should_not match /(*ANYCRLF)^FOO=/mi - ensure - ENV.delete("FOO") end {% end %} it "preserves existing environment variable" do - ENV["FOO"] = "bar" - value = Process.run(*print_env_command) do |proc| - proc.output.gets_to_end + with_env("FOO": "bar") do + value = Process.run(*print_env_command) do |proc| + proc.output.gets_to_end + end + value.should match /(*ANYCRLF)^FOO=bar$/m end - value.should match /(*ANYCRLF)^FOO=bar$/m - ensure - ENV.delete("FOO") end it "preserves and sets an environment variable" do - ENV["FOO"] = "bar" - value = Process.run(*print_env_command, env: {"FOO2" => "bar2"}) do |proc| - proc.output.gets_to_end + with_env("FOO": "bar") do + value = Process.run(*print_env_command, env: {"FOO2" => "bar2"}) do |proc| + proc.output.gets_to_end + end + value.should match /(*ANYCRLF)^FOO=bar$/m + value.should match /(*ANYCRLF)^FOO2=bar2$/m end - value.should match /(*ANYCRLF)^FOO=bar$/m - value.should match /(*ANYCRLF)^FOO2=bar2$/m - ensure - ENV.delete("FOO") end it "overrides existing environment variable" do - ENV["FOO"] = "bar" - value = Process.run(*print_env_command, env: {"FOO" => "different"}) do |proc| - proc.output.gets_to_end + with_env("FOO": "bar") do + value = Process.run(*print_env_command, env: {"FOO" => "different"}) do |proc| + proc.output.gets_to_end + end + value.should match /(*ANYCRLF)^FOO=different$/m end - value.should match /(*ANYCRLF)^FOO=different$/m - ensure - ENV.delete("FOO") end {% if flag?(:win32) %} it "overrides existing environment variable case-insensitive" do - ENV["FOO"] = "bar" - value = Process.run(*print_env_command, env: {"fOo" => "different"}) do |proc| - proc.output.gets_to_end + with_env("FOO": "bar") do + value = Process.run(*print_env_command, env: {"fOo" => "different"}) do |proc| + proc.output.gets_to_end + end + value.should_not match /(*ANYCRLF)^FOO=/m + value.should match /(*ANYCRLF)^fOo=different$/m end - value.should_not match /(*ANYCRLF)^FOO=/m - value.should match /(*ANYCRLF)^fOo=different$/m - ensure - ENV.delete("FOO") end {% end %} end From e0c5301a5eb22c1e22398e146d44dbc85b3ff998 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Jan 2024 19:38:59 +0800 Subject: [PATCH 0918/1551] Remove `T*` and `T[N]` macro interpolation behavior inside libs (#14215) --- spec/compiler/parser/to_s_spec.cr | 19 ++++++++++++++++ .../semantic/restrictions_augmenter_spec.cr | 4 ++-- src/compiler/crystal/syntax/to_s.cr | 22 ------------------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index 75c0bac3ebee..63da166aba45 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -148,6 +148,25 @@ describe "ASTNode#to_s" do expect_to_s %(lib Foo\n struct Foo\n a : Void\n b : Void\n end\nend) expect_to_s %(lib Foo\n union Foo\n a : Int\n b : Int32\n end\nend) expect_to_s %(lib Foo\n FOO = 0\nend) + expect_to_s <<-CRYSTAL, <<-CRYSTAL + lib Foo + A = Pointer(Void).new(0) + struct B + x : Void* + y : Int[1] + end + fun c(Void*) : Char[2]* + end + CRYSTAL + lib Foo + A = Pointer(Void).new(0) + struct B + x : ::Pointer(Void) + y : ::StaticArray(Int, 1) + end + fun c(::Pointer(Void)) : ::Pointer(::StaticArray(Char, 2)) + end + CRYSTAL expect_to_s %(lib LibC\n fun getch = "get.char"\nend) expect_to_s %(lib Foo::Bar\nend) expect_to_s %(enum Foo\n A = 0\n B\nend) diff --git a/spec/compiler/semantic/restrictions_augmenter_spec.cr b/spec/compiler/semantic/restrictions_augmenter_spec.cr index b3798dc257c0..5095f8613943 100644 --- a/spec/compiler/semantic/restrictions_augmenter_spec.cr +++ b/spec/compiler/semantic/restrictions_augmenter_spec.cr @@ -272,7 +272,7 @@ describe "Semantic: restrictions augmenter" do it "augments typedef" do before = <<-CRYSTAL lib LibFoo - type X = Void* + type X = Int32 end class Foo @x : LibFoo::X @@ -284,7 +284,7 @@ describe "Semantic: restrictions augmenter" do after = <<-CRYSTAL lib LibFoo - type X = Void* + type X = Int32 end class Foo @x : LibFoo::X diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index bf8eff6aef2d..f5086e346277 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -28,7 +28,6 @@ module Crystal def initialize(@str = IO::Memory.new, @macro_expansion_pragmas = nil, @emit_doc = false) @indent = 0 @inside_macro = 0 - @inside_lib = false end def visit_any(node) @@ -847,25 +846,6 @@ module Crystal def visit(node : Generic) name = node.name - if @inside_lib && (name.is_a?(Path) && name.names.size == 1) - case name.names.first - when "Pointer" - node.type_vars.first.accept self - @str << '*' - return false - when "StaticArray" - if node.type_vars.size == 2 - node.type_vars[0].accept self - @str << '[' - node.type_vars[1].accept self - @str << ']' - return false - end - else - # Not a special type - end - end - node.name.accept self printed_arg = false @@ -1131,9 +1111,7 @@ module Crystal @str << "lib " node.name.accept self newline - @inside_lib = true accept_with_indent(node.body) - @inside_lib = false append_indent @str << "end" false From bb96c5398e001f41b31431eea283a1a28198a84b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 14 Jan 2024 00:37:42 +0800 Subject: [PATCH 0919/1551] Support `@[Link]`'s DLL search order in the interpreter on Windows (#14146) --- spec/compiler/ffi/ffi_spec.cr | 32 +++++++--- spec/compiler/loader/msvc_spec.cr | 65 +++++++++++++++++---- src/compiler/crystal/codegen/link.cr | 4 ++ src/compiler/crystal/interpreter/context.cr | 23 +++++++- src/compiler/crystal/loader.cr | 19 +++--- src/compiler/crystal/loader/msvc.cr | 37 ++++++++---- src/compiler/crystal/loader/unix.cr | 14 ++++- 7 files changed, 151 insertions(+), 43 deletions(-) diff --git a/spec/compiler/ffi/ffi_spec.cr b/spec/compiler/ffi/ffi_spec.cr index ac16cfc425f2..ec644e45870d 100644 --- a/spec/compiler/ffi/ffi_spec.cr +++ b/spec/compiler/ffi/ffi_spec.cr @@ -19,6 +19,22 @@ private record TestStruct, d : Float64, p : Pointer(Void) +private def dll_search_paths + {% if flag?(:msvc) %} + [SPEC_CRYSTAL_LOADER_LIB_PATH] + {% else %} + nil + {% end %} +end + +{% if flag?(:unix) %} + class Crystal::Loader + def self.new(search_paths : Array(String), *, dll_search_paths : Nil) + new(search_paths) + end + end +{% end %} + describe Crystal::FFI::CallInterface do before_all do FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH) @@ -33,7 +49,7 @@ describe Crystal::FFI::CallInterface do it "simple call" do call_interface = Crystal::FFI::CallInterface.new Crystal::FFI::Type.sint64, [] of Crystal::FFI::Type - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH]) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: dll_search_paths) loader.load_library "sum" function_pointer = loader.find_symbol("answer") return_value = 0_i64 @@ -48,7 +64,7 @@ describe Crystal::FFI::CallInterface do Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32, ] of Crystal::FFI::Type - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH]) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: dll_search_paths) loader.load_library "sum" function_pointer = loader.find_symbol("sum") @@ -71,7 +87,7 @@ describe Crystal::FFI::CallInterface do Crystal::FFI::Type.pointer, ] of Crystal::FFI::Type - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH]) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: dll_search_paths) loader.load_library "sum" function_pointer = loader.find_symbol("sum_primitive_types") @@ -109,7 +125,7 @@ describe Crystal::FFI::CallInterface do ] call_interface = Crystal::FFI::CallInterface.new Crystal::FFI::Type.struct(struct_fields), struct_fields - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH]) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: dll_search_paths) loader.load_library "sum" function_pointer = loader.find_symbol("make_struct") @@ -145,7 +161,7 @@ describe Crystal::FFI::CallInterface do ]), ] of Crystal::FFI::Type - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH]) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: dll_search_paths) loader.load_library "sum" function_pointer = loader.find_symbol("sum_struct") @@ -183,7 +199,7 @@ describe Crystal::FFI::CallInterface do ]), ] of Crystal::FFI::Type - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH]) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: dll_search_paths) loader.load_library "sum" function_pointer = loader.find_symbol("sum_array") @@ -208,7 +224,7 @@ describe Crystal::FFI::CallInterface do it "basic" do call_interface = Crystal::FFI::CallInterface.variadic Crystal::FFI::Type.sint64, [Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32, Crystal::FFI::Type.sint32] of Crystal::FFI::Type, 1 - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH]) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: dll_search_paths) loader.load_library "sum" function_pointer = loader.find_symbol("sum_variadic") @@ -224,7 +240,7 @@ describe Crystal::FFI::CallInterface do it "zero varargs" do call_interface = Crystal::FFI::CallInterface.variadic Crystal::FFI::Type.sint64, [Crystal::FFI::Type.sint32] of Crystal::FFI::Type, 1 - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH]) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: dll_search_paths) loader.load_library "sum" function_pointer = loader.find_symbol("sum_variadic") diff --git a/spec/compiler/loader/msvc_spec.cr b/spec/compiler/loader/msvc_spec.cr index 5893d29aee02..e62ba0bd620c 100644 --- a/spec/compiler/loader/msvc_spec.cr +++ b/spec/compiler/loader/msvc_spec.cr @@ -45,7 +45,7 @@ describe Crystal::Loader do describe "#load_file?" do it "finds function symbol" do - loader = Crystal::Loader.new([] of String) + loader = Crystal::Loader.new([] of String, dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) loader.load_file?(File.join(SPEC_CRYSTAL_LOADER_LIB_PATH, Crystal::Loader.library_filename("foo"))).should be_true loader.find_symbol?("foo").should_not be_nil ensure @@ -55,7 +55,7 @@ describe Crystal::Loader do describe "#load_library?" do it "library name" do - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) loader.load_library?("foo").should be_true loader.find_symbol?("foo").should_not be_nil ensure @@ -63,7 +63,7 @@ describe Crystal::Loader do end it "full path" do - loader = Crystal::Loader.new([] of String) + loader = Crystal::Loader.new([] of String, dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) loader.load_library?(File.join(SPEC_CRYSTAL_LOADER_LIB_PATH, Crystal::Loader.library_filename("foo"))).should be_true loader.find_symbol?("foo").should_not be_nil ensure @@ -72,7 +72,7 @@ describe Crystal::Loader do it "does not implicitly find dependencies" do build_c_dynlib(compiler_datapath("loader", "bar.c")) - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) loader.load_library?("bar").should be_true loader.find_symbol?("bar").should_not be_nil loader.find_symbol?("foo").should be_nil @@ -83,23 +83,23 @@ describe Crystal::Loader do it "lookup in order" do build_c_dynlib(compiler_datapath("loader", "foo2.c")) - help_loader1 = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String) + help_loader1 = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) help_loader1.load_library?("foo").should be_true foo_address = help_loader1.find_symbol?("foo").should_not be_nil - help_loader2 = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String) + help_loader2 = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) help_loader2.load_library?("foo2").should be_true foo2_address = help_loader2.find_symbol?("foo").should_not be_nil foo_address.should_not eq foo2_address - loader1 = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String) + loader1 = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) loader1.load_library("foo") loader1.load_library("foo2") loader1.find_symbol?("foo").should eq foo_address - loader2 = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String) + loader2 = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) loader2.load_library("foo2") loader2.load_library("foo") @@ -114,18 +114,59 @@ describe Crystal::Loader do end it "does not find global symbols" do - loader = Crystal::Loader.new([] of String) + loader = Crystal::Loader.new([] of String, dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) loader.find_symbol?("__crystal_main").should be_nil end it "validate that lib handles are properly closed" do - loader = Crystal::Loader.new([] of String) + loader = Crystal::Loader.new([] of String, dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) expect_raises(Crystal::Loader::LoadError, "undefined reference to `foo'") do loader.find_symbol("foo") end end end + describe "dll_search_paths" do + it "supports an arbitrary path different from lib search path" do + with_tempfile("loader-dll_search_paths") do |path| + FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH) + FileUtils.mkdir_p(path) + + build_c_dynlib(compiler_datapath("loader", "foo.c")) + File.rename(File.join(SPEC_CRYSTAL_LOADER_LIB_PATH, "foo.dll"), File.join(path, "foo.dll")) + + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String, dll_search_paths: [path]) + loader.load_library?("foo").should be_true + ensure + loader.try &.close_all + + FileUtils.rm_rf(path) + FileUtils.rm_rf(SPEC_CRYSTAL_LOADER_LIB_PATH) + end + end + + it "doesn't load DLLs outside dll_search_path or Windows' default search paths" do + with_tempfile("loader-dll_search_paths") do |path| + FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH) + FileUtils.mkdir_p(path) + + build_c_dynlib(compiler_datapath("loader", "foo.c")) + File.rename(File.join(SPEC_CRYSTAL_LOADER_LIB_PATH, "foo.dll"), File.join(path, "foo.dll")) + + loader1 = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String, dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) + loader1.load_library?("foo").should be_false + loader2 = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String) + loader2.load_library?("foo").should be_false + ensure + loader2.try &.close_all + loader1.try &.close_all + + FileUtils.rm_rf(path) + FileUtils.rm_rf(SPEC_CRYSTAL_LOADER_LIB_PATH) + end + end + end + describe "lib suffix" do before_all do FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH) @@ -137,7 +178,7 @@ describe Crystal::Loader do it "respects -dynamic" do build_c_dynlib(compiler_datapath("loader", "foo.c"), lib_name: "foo-dynamic") - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) loader.load_library?("foo").should be_true ensure loader.close_all if loader @@ -145,7 +186,7 @@ describe Crystal::Loader do it "ignores -static" do build_c_dynlib(compiler_datapath("loader", "foo.c"), lib_name: "bar-static") - loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH] of String) + loader = Crystal::Loader.new([SPEC_CRYSTAL_LOADER_LIB_PATH], dll_search_paths: [SPEC_CRYSTAL_LOADER_LIB_PATH]) loader.load_library?("bar").should be_false ensure loader.close_all if loader diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index cc703ec81eef..d9910d64c7a8 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -232,6 +232,10 @@ module Crystal flags.join(" ") end + # Searches among CRYSTAL_LIBRARY_PATH, the compiler's directory, and PATH + # for every DLL specified in the used `@[Link]` annotations. Yields the + # absolute path and `true` if found, the base name and `false` if not found. + # The directories should match `Crystal::Repl::Context#dll_search_paths` def each_dll_path(& : String, Bool ->) executable_path = nil compiler_origin = nil diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr index d597810b36de..2d5da06dcf15 100644 --- a/src/compiler/crystal/interpreter/context.cr +++ b/src/compiler/crystal/interpreter/context.cr @@ -409,7 +409,7 @@ class Crystal::Repl::Context # (MSVC doesn't seem to have this issue) args.delete("-lgc") - Crystal::Loader.parse(args).tap do |loader| + Crystal::Loader.parse(args, dll_search_paths: dll_search_paths).tap do |loader| # FIXME: Part 2: This is a workaround for initial integration of the interpreter: # We append a handle to the current executable (i.e. the compiler program) # to the loader's handle list. This gives the loader access to all the symbols in the compiler program, @@ -427,6 +427,27 @@ class Crystal::Repl::Context end } + # Extra DLL search paths to mimic compiled code's DLL-copying behavior + # regarding `@[Link]` annotations. These directories should match the ones + # used in `Crystal::Program#each_dll_path` + private def dll_search_paths + {% if flag?(:msvc) %} + paths = CrystalLibraryPath.paths + + if executable_path = Process.executable_path + paths << File.dirname(executable_path) + end + + ENV["PATH"]?.try &.split(Process::PATH_DELIMITER, remove_empty: true) do |path| + paths << path + end + + paths + {% else %} + nil + {% end %} + end + def c_function(name : String) loader.find_symbol(name) end diff --git a/src/compiler/crystal/loader.cr b/src/compiler/crystal/loader.cr index d7d0133cc993..5a147dad590f 100644 --- a/src/compiler/crystal/loader.cr +++ b/src/compiler/crystal/loader.cr @@ -14,6 +14,7 @@ class Crystal::Loader class LoadError < Exception property args : Array(String)? property search_paths : Array(String)? + property dll_search_paths : Array(String)? def message String.build do |io| @@ -26,28 +27,26 @@ class Crystal::Loader io << "\nSearch path: " search_paths.join(io, Process::PATH_DELIMITER) end + if dll_search_paths = @dll_search_paths + io << "\nDLL search path: " + dll_search_paths.join(io, Process::PATH_DELIMITER) + end end end end - def self.new(search_paths : Array(String), libnames : Array(String), file_paths : Array(String)) : self - loader = new(search_paths) - + def load_all(libnames : Array(String), file_paths : Array(String)) file_paths.each do |path| - loader.load_file(::Path[path].expand) + load_file(::Path[path].expand) end libnames.each do |libname| - loader.load_library(libname) + load_library(libname) end - loader end getter search_paths : Array(String) getter loaded_libraries = [] of String - - def initialize(@search_paths : Array(String)) - @handles = [] of Handle - end + @handles = [] of Handle # def self.library_filename(libname : String) : String # raise NotImplementedError.new("library_filename") diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index e9fdc9af5bbd..60aca1aae333 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -5,9 +5,9 @@ require "crystal/system/win32/library_archive" # `link.exe`, using the Win32 DLL API. # # * Only dynamic libraries can be loaded. Static libraries and object files -# are unsupported. in particular, `LibC.printf` and `LibC.snprintf`are inline -# functions in `legacy-stdio_definitions.lib` since VS2015, so they are never -# found by the loader. +# are unsupported. For example, `LibC.printf` and `LibC.snprintf` are inline +# functions in `legacy_stdio_definitions.lib` since VS2015, so they are never +# found by the loader (this is why stdlib no longer uses those functions). # * Unlike the Unix counterpart, symbols in the current module do not clash with # the ones in DLLs or their corresponding import libraries. @@ -18,16 +18,28 @@ class Crystal::Loader include SystemError end + getter dll_search_paths : Array(String)? + + def initialize(@search_paths : Array(String), @dll_search_paths : Array(String)? = nil) + end + # Parses linker arguments in the style of `link.exe`. - def self.parse(args : Array(String), *, search_paths : Array(String) = default_search_paths) : self + # + # The directories in *dll_search_paths* are tried before Windows' search order + # when looking for DLLs corresponding to an import library. The compiler uses + # this to mimic `@[Link]`'s DLL-copying behavior for compiled code. + def self.parse(args : Array(String), *, search_paths : Array(String) = default_search_paths, dll_search_paths : Array(String)? = nil) : self search_paths, libnames = parse_args(args, search_paths) file_paths = [] of String begin - self.new(search_paths, libnames, file_paths) + loader = new(search_paths, dll_search_paths) + loader.load_all(libnames, file_paths) + loader rescue exc : LoadError exc.args = args exc.search_paths = search_paths + exc.dll_search_paths = dll_search_paths raise exc end end @@ -127,14 +139,19 @@ class Crystal::Loader # files, whose base names may not match the library's. Thus it is necessary # to extract this information from the library archive itself. System::LibraryArchive.imported_dlls(path).each do |dll| - # always consider the `.dll` in the same directory as the `.lib` first, - # regardless of the search order - first_path = File.join(File.dirname(path), dll) - dll = first_path if File.file?(first_path) + dll_full_path = @dll_search_paths.try &.each do |search_path| + full_path = File.join(search_path, dll) + break full_path if File.file?(full_path) + end + dll = dll_full_path || dll # TODO: `dll` is an unqualified name, e.g. `SHELL32.dll`, so the default - # DLL search order is used; consider getting rid of the cwd + # DLL search order is used if *dll_full_path* is nil; consider getting rid + # of the current working directory altogether # (https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order) + # + # Note that the compiler's directory and PATH are effectively searched + # twice when coming from the interpreter handle = open_library(dll) return false unless handle diff --git a/src/compiler/crystal/loader/unix.cr b/src/compiler/crystal/loader/unix.cr index 39fef6e0c318..46bca67341cc 100644 --- a/src/compiler/crystal/loader/unix.cr +++ b/src/compiler/crystal/loader/unix.cr @@ -35,8 +35,16 @@ class Crystal::Loader end end + def initialize(@search_paths : Array(String)) + end + # Parses linker arguments in the style of `ld`. - def self.parse(args : Array(String), *, search_paths : Array(String) = default_search_paths) : self + # + # *dll_search_paths* has no effect. (Technically speaking, `LD_LIBRARY_PATH` + # goes here and `LIBRARY_PATH` goes into *search_paths*, but there is little + # point in doing so since the same library files are used at both compile and + # run time.) + def self.parse(args : Array(String), *, search_paths : Array(String) = default_search_paths, dll_search_paths : Array(String)? = nil) : self libnames = [] of String file_paths = [] of String @@ -73,7 +81,9 @@ class Crystal::Loader search_paths = extra_search_paths + search_paths begin - self.new(search_paths, libnames, file_paths) + loader = new(search_paths) + loader.load_all(libnames, file_paths) + loader rescue exc : LoadError exc.args = args exc.search_paths = search_paths From f8b52134b39d023a29b89e5c75089a322b0c9961 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 16 Jan 2024 14:11:03 +0100 Subject: [PATCH 0920/1551] Add macro methods for lib-related nodes (#14218) --- spec/compiler/macro/macro_methods_spec.cr | 121 +++++++++++++ src/compiler/crystal/macros.cr | 200 +++++++++++++++++++--- src/compiler/crystal/macros/methods.cr | 91 ++++++++++ 3 files changed, 391 insertions(+), 21 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 13b063355c33..5142ad86c5bd 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -3280,6 +3280,127 @@ module Crystal end end + describe LibDef do + lib_def = LibDef.new(Path.new("Foo", "Bar", global: true), FunDef.new("foo")) + + it "executes kind" do + assert_macro %({{x.kind}}), %(lib), {x: lib_def} + end + + it "executes name" do + assert_macro %({{x.name}}), %(::Foo::Bar), {x: lib_def} + assert_macro %({{x.name(generic_args: true)}}), %(::Foo::Bar), {x: lib_def} + assert_macro %({{x.name(generic_args: false)}}), %(::Foo::Bar), {x: lib_def} + assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to LibDef#name must be a BoolLiteral, not NumberLiteral", {x: lib_def} + end + + it "executes body" do + assert_macro %({{x.body}}), %(fun foo), {x: lib_def} + end + end + + describe CStructOrUnionDef do + c_struct_def = CStructOrUnionDef.new("Foo", TypeDeclaration.new("x".var, "Int".path)) + c_union_def = CStructOrUnionDef.new("Bar", Include.new("Foo".path), union: true) + + it "executes kind" do + assert_macro %({{x.kind}}), %(struct), {x: c_struct_def} + assert_macro %({{x.kind}}), %(union), {x: c_union_def} + end + + it "executes name" do + assert_macro %({{x.name}}), %(Foo), {x: c_struct_def} + assert_macro %({{x.name(generic_args: true)}}), %(Foo), {x: c_struct_def} + assert_macro %({{x.name(generic_args: false)}}), %(Foo), {x: c_struct_def} + assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to CStructOrUnionDef#name must be a BoolLiteral, not NumberLiteral", {x: c_struct_def} + end + + it "executes body" do + assert_macro %({{x.body}}), %(x : Int), {x: c_struct_def} + assert_macro %({{x.body}}), %(include Foo), {x: c_union_def} + end + + it "executes union?" do + assert_macro %({{x.union?}}), %(false), {x: c_struct_def} + assert_macro %({{x.union?}}), %(true), {x: c_union_def} + end + end + + describe FunDef do + lib_fun = FunDef.new("foo") + top_level_fun = FunDef.new("bar", [Arg.new("x", restriction: "Int32".path), Arg.new("", restriction: "Char".path)], "Void".path, true, 1.int32, "y.z") + top_level_fun2 = FunDef.new("baz", body: Nop.new) + + it "executes name" do + assert_macro %({{x.name}}), %(foo), {x: lib_fun} + assert_macro %({{x.name}}), %(bar), {x: top_level_fun} + end + + it "executes real_name" do + assert_macro %({{x.real_name}}), %(), {x: lib_fun} + assert_macro %({{x.real_name}}), %("y.z"), {x: top_level_fun} + end + + it "executes args" do + assert_macro %({{x.args}}), %([]), {x: lib_fun} + assert_macro %({{x.args}}), %([x : Int32, : Char]), {x: top_level_fun} + end + + it "executes variadic?" do + assert_macro %({{x.variadic?}}), %(false), {x: lib_fun} + assert_macro %({{x.variadic?}}), %(true), {x: top_level_fun} + end + + it "executes return_type" do + assert_macro %({{x.return_type}}), %(), {x: lib_fun} + assert_macro %({{x.return_type}}), %(Void), {x: top_level_fun} + end + + it "executes body" do + assert_macro %({{x.body}}), %(), {x: lib_fun} + assert_macro %({{x.body}}), %(1), {x: top_level_fun} + assert_macro %({{x.body}}), %(), {x: top_level_fun2} + end + + it "executes has_body?" do + assert_macro %({{x.has_body?}}), %(false), {x: lib_fun} + assert_macro %({{x.has_body?}}), %(true), {x: top_level_fun} + assert_macro %({{x.has_body?}}), %(true), {x: top_level_fun2} + end + end + + describe TypeDef do + type_def = TypeDef.new("Foo", Path.new("Bar", "Baz", global: true)) + + it "executes name" do + assert_macro %({{x.name}}), %(Foo), {x: type_def} + end + + it "executes type" do + assert_macro %({{x.type}}), %(::Bar::Baz), {x: type_def} + end + end + + describe ExternalVar do + external_var1 = ExternalVar.new("foo", Path.new("Bar", "Baz")) + external_var2 = ExternalVar.new("X", Generic.new(Path.global("Pointer"), ["Char".path] of ASTNode), real_name: "y.z") + + it "executes name" do + assert_macro %({{x.name}}), %(foo), {x: external_var1} + assert_macro %({{x.name}}), %(X), {x: external_var2} + end + + it "executes real_name" do + assert_macro %({{x.real_name}}), %(), {x: external_var1} + assert_macro %({{x.real_name}}), %("y.z"), {x: external_var2} + end + + it "executes type" do + assert_macro %({{x.type}}), %(Bar::Baz), {x: external_var1} + assert_macro %({{x.type}}), %(::Pointer(Char)), {x: external_var2} + end + end + describe "env" do it "has key" do with_env("FOO": "foo") do diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 90c802bad25e..c18782dac954 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1806,6 +1806,185 @@ module Crystal::Macros end end + # A lib definition. + # + # Every lib definition `node` is equivalent to: + # + # ``` + # {% begin %} + # {{ node.kind }} {{ node.name }} + # {{ node.body }} + # end + # {% end %} + # ``` + class LibDef < ASTNode + # Returns the keyword used to define this type. + # + # For `LibDef` this is always `lib`. + def kind : MacroId + end + + # Returns the name of this type definition. + # + # *generic_args* has no effect. It exists solely to match the interface of + # other related AST nodes. + def name(*, generic_args : BoolLiteral = true) : Path + end + + # Returns the body of this type definition. + def body : ASTNode + end + end + + # A struct or union definition inside a lib. + # + # Every type definition `node` is equivalent to: + # + # ``` + # {% begin %} + # {{ node.kind }} {{ node.name }} + # {{ node.body }} + # end + # {% end %} + # ``` + class CStructOrUnionDef < ASTNode + # Returns whether this node defines a C union. + def union? : BoolLiteral + end + + # Returns the keyword used to define this type. + # + # For `CStructOrUnionDef` this is either `struct` or `union`. + def kind : MacroId + end + + # Returns the name of this type definition. + # + # *generic_args* has no effect. It exists solely to match the interface of + # other related AST nodes. + def name(*, generic_args : BoolLiteral = true) : Path + end + + # Returns the body of this type definition. + def body : ASTNode + end + end + + # A function declaration inside a lib, or a top-level C function definition. + # + # Every function `node` is equivalent to: + # + # ``` + # fun {{ node.name }} {% if real_name = node.real_name %}= {{ real_name }}{% end %}( + # {% for arg in node.args %} {{ arg }}, {% end %} + # {% if node.variadic? %} ... {% end %} + # ) {% if return_type = node.return_type %}: {{ return_type }}{% end %} + # {% if node.has_body? %} + # {{ body }} + # end + # {% end %} + # ``` + class FunDef < ASTNode + # Returns the name of the function in Crystal. + def name : MacroId + end + + # Returns the real C name of the function, if any. + def real_name : StringLiteral | Nop + end + + # Returns the parameters of the function. + # + # This does not include the variadic parameter. + def args : ArrayLiteral(Arg) + end + + # Returns whether the function is variadic. + def variadic? : BoolLiteral + end + + # Returns the return type of the function, if specified. + def return_type : ASTNode | Nop + end + + # Returns the body of the function, if any. + # + # Both top-level funs and lib funs may return a `Nop`. Instead, `#has_body?` + # can be used to distinguish between the two. + # + # ``` + # macro body_class(x) + # {{ (x.is_a?(LibDef) ? x.body : x).body.class_name }} + # end + # + # body_class(lib MyLib + # fun foo + # end) # => "Nop" + # + # body_class(fun foo + # end) # => "Nop" + # ``` + def body : ASTNode | Nop + end + + # Returns whether this function has a body. + # + # Top-level funs have a body, whereas lib funs do not. + # + # ``` + # macro has_body(x) + # {{ (x.is_a?(LibDef) ? x.body : x).has_body? }} + # end + # + # has_body(lib MyLib + # fun foo + # end) # => false + # + # has_body(fun foo + # end) # => true + # ``` + def has_body? : BoolLiteral + end + end + + # A typedef inside a lib. + # + # Every typedef `node` is equivalent to: + # + # ``` + # type {{ node.name }} = {{ node.type }} + # ``` + class TypeDef < ASTNode + # Returns the name of the typedef. + def name : Path + end + + # Returns the name of the type this typedef is equivalent to. + def type : ASTNode + end + end + + # An external variable declaration inside a lib. + # + # Every variable `node` is equivalent to: + # + # ``` + # ${{ node.name }} {% if real_name = node.real_name %}= {{ real_name }}{% end %} : {{ node.type }} + # ``` + class ExternalVar < ASTNode + # Returns the name of the variable in Crystal, without the preceding `$`. + def name : MacroId + end + + # Returns the real C name of the variable, if any. + def real_name : StringLiteral | Nop + end + + # Returns the name of the variable's type. + def type : ASTNode + end + end + # A `while` expression class While < ASTNode # Returns this while's condition. @@ -2034,27 +2213,6 @@ module Crystal::Macros end end - # class LibDef < ASTNode - # end - - # class FunDef < ASTNode - # end - - # class TypeDef < ASTNode - # end - - # abstract class CStructOrUnionDef < ASTNode - # end - - # class StructDef < CStructOrUnionDef - # end - - # class UnionDef < CStructOrUnionDef - # end - - # class ExternalVar < ASTNode - # end - # class Alias < ASTNode # end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index abef63c402d3..39ec31828328 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -2522,6 +2522,97 @@ module Crystal end end end + + class LibDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "kind" + interpret_check_args { MacroId.new("lib") } + when "name" + interpret_check_args(named_params: ["generic_args"]) do + # parse the argument, but ignore it otherwise + parse_generic_args_argument(self, method, named_args, default: true) + @name + end + when "body" + interpret_check_args { @body } + else + super + end + end + end + + class CStructOrUnionDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "kind" + interpret_check_args { MacroId.new(@union ? "union" : "struct") } + when "name" + interpret_check_args(named_params: ["generic_args"]) do + # parse the argument, but ignore it otherwise + parse_generic_args_argument(self, method, named_args, default: true) + Path.new(@name) + end + when "body" + interpret_check_args { @body } + when "union?" + interpret_check_args { BoolLiteral.new(@union) } + else + super + end + end + end + + class FunDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "name" + interpret_check_args { MacroId.new(@name) } + when "real_name" + interpret_check_args { @real_name != @name ? StringLiteral.new(@real_name) : Nop.new } + when "args" + interpret_check_args { ArrayLiteral.map(@args, &.itself) } + when "variadic?" + interpret_check_args { BoolLiteral.new(@varargs) } + when "return_type" + interpret_check_args { @return_type || Nop.new } + when "body" + interpret_check_args { @body || Nop.new } + when "has_body?" + interpret_check_args { BoolLiteral.new(!@body.nil?) } + else + super + end + end + end + + class TypeDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "name" + interpret_check_args { Path.new(@name).at(@name_location) } + when "type" + interpret_check_args { @type_spec } + else + super + end + end + end + + class ExternalVar + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "name" + interpret_check_args { MacroId.new(@name) } + when "real_name" + interpret_check_args { (real_name = @real_name) ? StringLiteral.new(real_name) : Nop.new } + when "type" + interpret_check_args { @type_spec } + else + super + end + end + end end private def get_named_annotation_args(object) From 261f46f540d398e826fdf44b31ec591fb01cf031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 16 Jan 2024 19:39:51 +0100 Subject: [PATCH 0921/1551] Fix missing `cause` parameter from `IO::Error#initialize` (#14242) --- spec/std/io/io_spec.cr | 10 ++++++++++ src/io/error.cr | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index e2c065ecebf0..2c53df75205e 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -1013,4 +1013,14 @@ describe IO do typeof(STDIN.cooked!) typeof(STDIN.raw { }) typeof(STDIN.raw!) + + describe IO::Error do + describe ".new" do + it "accepts `cause` argument (#14241)" do + cause = Exception.new("cause") + error = IO::Error.new("foo", cause: cause) + error.cause.should be cause + end + end + end end diff --git a/src/io/error.cr b/src/io/error.cr index 4c6d30952f13..ec0374040699 100644 --- a/src/io/error.cr +++ b/src/io/error.cr @@ -16,10 +16,10 @@ class IO "#{message} (#{target})" end - def initialize(message : String? = nil, *, target = nil) + def initialize(message : String? = nil, cause : Exception? = nil, *, target = nil) @target = target.try(&.to_s) - super message + super message, cause end end From 045547a1715868d8b8bca272c64fc82a2677fd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 17 Jan 2024 12:17:03 +0100 Subject: [PATCH 0922/1551] Drop flag `openbsd6.2` (#14233) --- src/crystal/system/unix/fiber.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crystal/system/unix/fiber.cr b/src/crystal/system/unix/fiber.cr index 17cbe5c07fdb..2a06b3d3d1a1 100644 --- a/src/crystal/system/unix/fiber.cr +++ b/src/crystal/system/unix/fiber.cr @@ -3,7 +3,7 @@ require "c/sys/mman" module Crystal::System::Fiber def self.allocate_stack(stack_size) : Void* flags = LibC::MAP_PRIVATE | LibC::MAP_ANON - {% if flag?(:openbsd) && !flag?(:"openbsd6.2") %} + {% if flag?(:openbsd) %} flags |= LibC::MAP_STACK {% end %} From 97ca775f2f03d18000972ad16d8f96fa15f29f10 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 11 Jan 2024 06:26:03 +0800 Subject: [PATCH 0923/1551] Always use `%p` for pointers in `Crystal::System.print_error` (#14186) --- src/crystal/system/unix/signal.cr | 2 +- src/exception/call_stack/libunwind.cr | 2 +- src/exception/call_stack/stackwalk.cr | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr index c30a2b985af2..f7c0ad31e326 100644 --- a/src/crystal/system/unix/signal.cr +++ b/src/crystal/system/unix/signal.cr @@ -149,7 +149,7 @@ module Crystal::System::Signal if is_stack_overflow Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n" else - Crystal::System.print_error "Invalid memory access (signal %d) at address 0x%lx\n", sig, addr + Crystal::System.print_error "Invalid memory access (signal %d) at address %p\n", sig, addr end Exception::CallStack.print_backtrace diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 220db21b71f0..81943d99f376 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -102,7 +102,7 @@ struct Exception::CallStack end private def self.print_frame(repeated_frame) - Crystal::System.print_error "[0x%llx] ", repeated_frame.ip.address.to_u64 + Crystal::System.print_error "[%p] ", repeated_frame.ip.address print_frame_location(repeated_frame) Crystal::System.print_error " (%d times)", repeated_frame.count + 1 unless repeated_frame.count == 0 Crystal::System.print_error "\n" diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index f49c87fae623..4879ed8bb95d 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -37,7 +37,7 @@ struct Exception::CallStack case exception_info.value.exceptionRecord.value.exceptionCode when LibC::EXCEPTION_ACCESS_VIOLATION addr = exception_info.value.exceptionRecord.value.exceptionInformation[1] - Crystal::System.print_error "Invalid memory access (C0000005) at address 0x%llx\n", addr + Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr) print_backtrace(exception_info) LibC._exit(1) when LibC::EXCEPTION_STACK_OVERFLOW @@ -150,7 +150,7 @@ struct Exception::CallStack end private def self.print_frame(repeated_frame) - Crystal::System.print_error "[0x%llx] ", repeated_frame.ip.address.to_u64 + Crystal::System.print_error "[%p] ", repeated_frame.ip.address print_frame_location(repeated_frame) Crystal::System.print_error " (%d times)", repeated_frame.count + 1 unless repeated_frame.count == 0 Crystal::System.print_error "\n" From 4e6531aaf6cfcb8aed6c783137387337481cb3c1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Jan 2024 19:38:41 +0800 Subject: [PATCH 0924/1551] Fix: Always use `%p` for pointers in `Crystal::System.print_error` (#14221) --- src/exception/call_stack/libunwind.cr | 2 +- src/exception/call_stack/stackwalk.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 81943d99f376..9f17512491fe 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -102,7 +102,7 @@ struct Exception::CallStack end private def self.print_frame(repeated_frame) - Crystal::System.print_error "[%p] ", repeated_frame.ip.address + Crystal::System.print_error "[%p] ", repeated_frame.ip print_frame_location(repeated_frame) Crystal::System.print_error " (%d times)", repeated_frame.count + 1 unless repeated_frame.count == 0 Crystal::System.print_error "\n" diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index 4879ed8bb95d..a6a400b1befd 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -150,7 +150,7 @@ struct Exception::CallStack end private def self.print_frame(repeated_frame) - Crystal::System.print_error "[%p] ", repeated_frame.ip.address + Crystal::System.print_error "[%p] ", repeated_frame.ip print_frame_location(repeated_frame) Crystal::System.print_error " (%d times)", repeated_frame.count + 1 unless repeated_frame.count == 0 Crystal::System.print_error "\n" From fda656c7134138e013e89281e74a4f3761b53fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 18 Jan 2024 14:36:11 +0100 Subject: [PATCH 0925/1551] Changelog for 1.11.2 (#14249) --- CHANGELOG.md | 16 ++++++++++++++++ src/VERSION | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 658d9f42f822..4e152e9ad2d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [1.11.2] (2024-01-18) + +[1.11.2]: https://github.com/crystal-lang/crystal/releases/1.11.2 + +### Bugfixes + +#### stdlib + +- *(files)* Fix missing `cause` parameter from `IO::Error#initialize` ([#14242](https://github.com/crystal-lang/crystal/pull/14242), thanks @straight-shoota) +- *(runtime)* Always use `%p` for pointers in `Crystal::System.print_error` ([#14186](https://github.com/crystal-lang/crystal/pull/14186), thanks @HertzDevil) +- *(runtime)* Fixup for always use `%p` for pointers in `Crystal::System.print_error` ([#14221](https://github.com/crystal-lang/crystal/pull/14221), thanks @HertzDevil) + +### Infrastructure + +- Changelog for 1.11.2 ([#14249](https://github.com/crystal-lang/crystal/pull/14249), thanks @straight-shoota) + ## [1.11.1] (2024-01-11) [1.11.1]: https://github.com/crystal-lang/crystal/releases/1.11.1 diff --git a/src/VERSION b/src/VERSION index 720c7384c619..ca7176690dd6 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.11.1 +1.11.2 From f01de65e852dfb1c21aef609525ff447e96ec66c Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 24 Jan 2024 11:18:50 +0100 Subject: [PATCH 0926/1551] MT: reduce interleaved backtraces in spawn unhandled exceptions (#14220) --- src/fiber.cr | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/fiber.cr b/src/fiber.cr index c96184f3cf1f..049b7e9bb0c0 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -140,12 +140,20 @@ class Fiber GC.unlock_read @proc.call rescue ex + io = {% if flag?(:preview_mt) %} + IO::Memory.new(4096) # PIPE_BUF + {% else %} + STDERR + {% end %} if name = @name - STDERR.print "Unhandled exception in spawn(name: #{name}): " + io << "Unhandled exception in spawn(name: " << name << "): " else - STDERR.print "Unhandled exception in spawn: " + io << "Unhandled exception in spawn: " end - ex.inspect_with_backtrace(STDERR) + ex.inspect_with_backtrace(io) + {% if flag?(:preview_mt) %} + STDERR.write(io.to_slice) + {% end %} STDERR.flush ensure # Remove the current fiber from the linked list From 724249fb54858a6eff33895b1144f00ac276ad01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 24 Jan 2024 11:19:18 +0100 Subject: [PATCH 0927/1551] Remove filtering of already mentioned PRs (#14229) --- scripts/github-changelog.cr | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index c660a01cd8ca..7397440c3676 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -262,9 +262,6 @@ array = parser.on_key! "data" do end end -changelog = File.read("CHANGELOG.md") -array.select! { |pr| pr.merged_at && !changelog.index(pr.permalink) } - sections = array.group_by(&.section) SECTION_TITLES = { From d42b67382b6a97b74ebcb96e6e30531aa1c3a26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 27 Jan 2024 15:03:59 +0100 Subject: [PATCH 0928/1551] Update previous Crystal release - 1.11.2 (#14251) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 6 +++--- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win_build_portable.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 548716adb511..2eef408ee4ad 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.11.1/crystal-1.11.1-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.11.2/crystal-1.11.2-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 96403e595e17..35cf1f916770 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.11.1-build + image: crystallang/crystal:1.11.2-build name: "Test Interpreter" steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.11.1-build + image: crystallang/crystal:1.11.2-build name: Build interpreter steps: - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.11.1-build + image: crystallang/crystal:1.11.2-build strategy: matrix: part: [0, 1, 2, 3] diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f4b284f9ce74..3d85af22e5a3 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.1] + crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2] flags: [""] include: # libffi is only available starting from the 1.2.2 build images diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 15fa6df8712c..fe8ad578163a 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -56,7 +56,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.11.1" + crystal: "1.11.2" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 30b148448416..65766c6325c2 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.11.1-alpine + container: crystallang/crystal:1.11.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -27,7 +27,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.11.1-alpine + container: crystallang/crystal:1.11.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.11.1-alpine + container: crystallang/crystal:1.11.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index 523a645085bf..d7d9c2655584 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.11.1-alpine + container: crystallang/crystal:1.11.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.11.1-alpine + container: crystallang/crystal:1.11.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index ca3431808c01..c8f2058ebea0 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.11.1-build + container: crystallang/crystal:1.11.2-build steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 77e823384ef2..d1aa2793d4ff 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -24,7 +24,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.11.1" + crystal: "1.11.2" - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index 9a3776a3b7d3..9d0b73507f24 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.11.1/crystal-1.11.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.11.1-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.11.2/crystal-1.11.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.11.2-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.11.1}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.11.2}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 647315af5e70..aef4211f280a 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.11.1/crystal-1.11.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:1yfb8xzhb3hnf9dn5y0mk25va1hbmk03x0hm9da6805xllr7bq8l"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.11.2/crystal-1.11.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:0qcdr8yl6k7il0x63z2gyqbkjp89m77nq7x1h3m80y1imfg0z1q9"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.11.1/crystal-1.11.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:1yfb8xzhb3hnf9dn5y0mk25va1hbmk03x0hm9da6805xllr7bq8l"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.11.2/crystal-1.11.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:0qcdr8yl6k7il0x63z2gyqbkjp89m77nq7x1h3m80y1imfg0z1q9"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.11.1/crystal-1.11.1-1-linux-x86_64.tar.gz"; - sha256 = "sha256:18mqp2sy5df6bkyvndg4xwgjf6lmaipz5mcr6lg9a47is2vr1i7v"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.11.2/crystal-1.11.2-1-linux-x86_64.tar.gz"; + sha256 = "sha256:01l9cq8d3p7p3ijkrg0xpchj0l21z3sjvd5f6zw1pnms647a6hdr"; }; }.${pkgs.stdenv.system}); From ac895ff483789b34079388210f1d4fc37b220227 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:04:10 +0100 Subject: [PATCH 0929/1551] Update GH Actions (#14246) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/llvm.yml | 2 +- .github/workflows/macos.yml | 4 ++-- .github/workflows/win.yml | 16 ++++++++-------- .github/workflows/win_build_portable.yml | 12 ++++++------ 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index fe8ad578163a..887ede290fe3 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -33,7 +33,7 @@ jobs: - name: Cache LLVM id: cache-llvm - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./llvm key: llvm-${{ matrix.llvm_version }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index dbab5f5bc93c..765595ce2d09 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -17,12 +17,12 @@ jobs: - name: Download Crystal source uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v24 + - uses: cachix/install-nix-action@v25 with: install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | experimental-features = nix-command - - uses: cachix/cachix-action@v13 + - uses: cachix/cachix-action@v14 with: name: crystal-ci signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index c80dcfce8fb6..9fce9f93aa44 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -25,7 +25,7 @@ jobs: - name: Cache libraries id: cache-libs - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | # openssl and llvm take much longer to build so they are cached separately libs/pcre.lib @@ -68,7 +68,7 @@ jobs: - name: Cache OpenSSL id: cache-openssl - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | libs/crypto.lib @@ -97,7 +97,7 @@ jobs: - name: Cache libraries id: cache-dlls - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | # openssl and llvm take much longer to build so they are cached separately libs/pcre-dynamic.lib @@ -149,7 +149,7 @@ jobs: - name: Cache OpenSSL id: cache-openssl-dlls - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | libs/crypto-dynamic.lib @@ -172,7 +172,7 @@ jobs: - name: Cache LLVM id: cache-llvm-libs - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: llvm key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc @@ -219,7 +219,7 @@ jobs: - name: Cache LLVM id: cache-llvm-dlls - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | libs/llvm_VERSION @@ -258,7 +258,7 @@ jobs: path: build - name: Restore LLVM - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: llvm key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc @@ -309,7 +309,7 @@ jobs: path: etc/win-ci/portable - name: Restore LLVM - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: llvm key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index d1aa2793d4ff..bab89e04685e 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@v4 - name: Restore libraries - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: | libs/pcre.lib @@ -45,7 +45,7 @@ jobs: key: win-libs-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc fail-on-cache-miss: true - name: Restore OpenSSL - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: | libs/crypto.lib @@ -54,7 +54,7 @@ jobs: key: win-openssl-libs-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc fail-on-cache-miss: true - name: Restore DLLs - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: | libs/pcre-dynamic.lib @@ -78,7 +78,7 @@ jobs: key: win-dlls-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc fail-on-cache-miss: true - name: Restore OpenSSL DLLs - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: | libs/crypto-dynamic.lib @@ -88,13 +88,13 @@ jobs: key: win-openssl-dlls-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc fail-on-cache-miss: true - name: Restore LLVM - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: llvm key: llvm-libs-${{ inputs.llvm_version }}-msvc fail-on-cache-miss: true - name: Restore LLVM DLLs - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: | libs/llvm_VERSION From 7628de67ea55801ad575623d2a9045b750d5cefa Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Sat, 27 Jan 2024 08:48:32 -0600 Subject: [PATCH 0930/1551] Add `--frame-pointers` to control preservation of frame pointers (#13860) Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Quinton Miller --- man/crystal.1 | 2 ++ src/compiler/crystal/codegen/codegen.cr | 11 ++++++++--- src/compiler/crystal/codegen/fun.cr | 6 +++++- src/compiler/crystal/command.cr | 8 ++++++++ src/compiler/crystal/compiler.cr | 12 +++++++++++- src/llvm/function.cr | 7 +++++++ src/llvm/lib_llvm/core.cr | 1 + 7 files changed, 42 insertions(+), 5 deletions(-) diff --git a/man/crystal.1 b/man/crystal.1 index 3a2a3d4a1b2a..ef23beac77c3 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -117,6 +117,8 @@ Generate the output without any symbolic debug symbols. Define a compile-time flag. This is useful to conditionally define types, methods, or commands based on flags available at compile time. The default flags are from the target triple given with --target-triple or the hosts default, if none is given. .It Fl -emit Op asm|llvm-bc|llvm-ir|obj Comma separated list of types of output for the compiler to emit. You can use this to see the generated LLVM IR, LLVM bitcode, assembly, and object files. +.It Fl -frame-pointers Op auto|always|non-leaf +Control the preservation of frame pointers. The default value, --frame-pointers=auto, will preserve frame pointers on debug builds and try to omit them on release builds (certain platforms require them to stay enabled). --frame-pointers=always will always preserve them, and non-leaf will only force their preservation on non-leaf functions. .It Fl f Ar text|json, Fl -format Ar text|json Format of output. Defaults to text. The json format can be used to get a more parser-friendly output. .It Fl -error-trace diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 651afa44ca27..a6015012eb7c 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -69,8 +69,10 @@ module Crystal end end - def codegen(node, single_module = false, debug = Debug::Default) - visitor = CodeGenVisitor.new self, node, single_module: single_module, debug: debug + def codegen(node, single_module = false, debug = Debug::Default, + frame_pointers = FramePointers::Auto) + visitor = CodeGenVisitor.new self, node, single_module: single_module, + debug: debug, frame_pointers: frame_pointers visitor.accept node visitor.process_finished_hooks visitor.finish @@ -190,7 +192,10 @@ module Crystal @c_malloc_fun : LLVMTypedFunction? @c_realloc_fun : LLVMTypedFunction? - def initialize(@program : Program, @node : ASTNode, @single_module : Bool = false, @debug = Debug::Default) + def initialize(@program : Program, @node : ASTNode, + @single_module : Bool = false, + @debug = Debug::Default, + @frame_pointers : FramePointers = :auto) @abi = @program.target_machine.abi @llvm_context = LLVM::Context.new # LLVM::Context.register(@llvm_context, "main") diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 9a8e51df3175..784eba83b9b9 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -101,9 +101,13 @@ class Crystal::CodeGenVisitor context.fun.add_attribute LLVM::Attribute::UWTable, value: @program.has_flag?("aarch64") ? LLVM::UWTableKind::Sync : LLVM::UWTableKind::Async {% end %} - if @program.has_flag?("darwin") + if @frame_pointers.always? + context.fun.add_attribute "frame-pointer", value: "all" + elsif @program.has_flag?("darwin") # Disable frame pointer elimination in Darwin, as it causes issues during stack unwind context.fun.add_target_dependent_attribute "frame-pointer", "all" + elsif @frame_pointers.non_leaf? + context.fun.add_attribute "frame-pointer", value: "non-leaf" end new_entry_block diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 97bf46feb663..6549c2768aea 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -399,6 +399,14 @@ class Crystal::Command opts.on("--no-debug", "Skip any symbolic debug info") do compiler.debug = Crystal::Debug::None end + + opts.on("--frame-pointers auto|always|non-leaf", "Control the preservation of frame pointers") do |value| + if frame_pointers = FramePointers.parse?(value) + compiler.frame_pointers = frame_pointers + else + error "Invalid value `#{value}` for frame-pointers" + end + end end opts.on("-D FLAG", "--define FLAG", "Define a compile-time flag") do |flag| diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 283f44289468..899ef242f318 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -16,6 +16,12 @@ module Crystal Default = LineNumbers end + enum FramePointers + Auto + Always + NonLeaf + end + # Main interface to the compiler. # # A Compiler parses source code, type checks it and @@ -45,6 +51,9 @@ module Crystal # code by the `flag?(...)` macro method. property flags = [] of String + # Controls generation of frame pointers. + property frame_pointers = FramePointers::Auto + # If `true`, the executable will be generated with debug code # that can be understood by `gdb` and `lldb`. property debug = Debug::Default @@ -297,7 +306,8 @@ module Crystal private def codegen(program, node : ASTNode, sources, output_filename) llvm_modules = @progress_tracker.stage("Codegen (crystal)") do - program.codegen node, debug: debug, single_module: @single_module || @cross_compile || !@emit_targets.none? + program.codegen node, debug: debug, frame_pointers: frame_pointers, + single_module: @single_module || @cross_compile || !@emit_targets.none? end output_dir = CacheDir.instance.directory_for(sources) diff --git a/src/llvm/function.cr b/src/llvm/function.cr index a586cd6fdde5..d643e74d758b 100644 --- a/src/llvm/function.cr +++ b/src/llvm/function.cr @@ -28,6 +28,13 @@ struct LLVM::Function end end + def add_attribute(attribute : String, index = AttributeIndex::FunctionIndex, *, value : String) + context = LibLLVM.get_module_context(LibLLVM.get_global_parent(self)) + attribute_ref = LibLLVM.create_string_attribute(context, attribute, attribute.bytesize, + value, value.bytesize) + LibLLVM.add_attribute_at_index(self, index, attribute_ref) + end + def add_attribute(attribute : Attribute, index = AttributeIndex::FunctionIndex, *, value) return if attribute.value == 0 diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr index 0645441f8a70..20f10c7e6e62 100644 --- a/src/llvm/lib_llvm/core.cr +++ b/src/llvm/lib_llvm/core.cr @@ -25,6 +25,7 @@ lib LibLLVM fun get_enum_attribute_kind_for_name = LLVMGetEnumAttributeKindForName(name : Char*, s_len : SizeT) : UInt fun get_last_enum_attribute_kind = LLVMGetLastEnumAttributeKind : UInt fun create_enum_attribute = LLVMCreateEnumAttribute(c : ContextRef, kind_id : UInt, val : UInt64) : AttributeRef + fun create_string_attribute = LLVMCreateStringAttribute(c : ContextRef, k : Char*, k_length : UInt, v : Char*, v_length : UInt) : AttributeRef {% unless LibLLVM::IS_LT_120 %} fun create_type_attribute = LLVMCreateTypeAttribute(c : ContextRef, kind_id : UInt, type_ref : TypeRef) : AttributeRef {% end %} From 0ff99d97deafc47cf6ef80da8c1f0ece1f9c1343 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 29 Jan 2024 19:15:46 +0800 Subject: [PATCH 0931/1551] Add macro methods for `Primitive` (#14263) --- spec/compiler/macro/macro_methods_spec.cr | 7 +++++++ src/compiler/crystal/macros.cr | 21 +++++++++++++++++++++ src/compiler/crystal/macros/methods.cr | 11 +++++++++++ 3 files changed, 39 insertions(+) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 5142ad86c5bd..516c3e408280 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2505,6 +2505,13 @@ module Crystal end end + describe Primitive do + it "executes name" do + assert_macro %({{x.name}}), %(:abc), {x: Primitive.new("abc")} + assert_macro %({{x.name}}), %(:"x.y.z"), {x: Primitive.new("x.y.z")} + end + end + describe "macro methods" do it "executes name" do assert_macro %({{x.name}}), "some_macro", {x: Macro.new("some_macro")} diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index c18782dac954..f18c3c7246ab 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1407,6 +1407,27 @@ module Crystal::Macros end end + # A fictitious node representing the body of a `Def` marked with + # `@[Primitive]`. + class Primitive < ASTNode + # Returns the name of the primitive. + # + # This is identical to the argument to the associated `@[Primitive]` + # annotation. + # + # ``` + # module Foo + # @[Primitive(:abc)] + # def foo + # end + # end + # + # {{ Foo.methods.first.body.name }} # => :abc + # ``` + def name : SymbolLiteral + end + end + # A macro definition. class Macro < ASTNode # Returns the name of this macro. diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 39ec31828328..60ecdb528509 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1459,6 +1459,17 @@ module Crystal end end + class Primitive + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "name" + interpret_check_args { SymbolLiteral.new(@name) } + else + super + end + end + end + class Macro def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) case method From ab96337f02315171a05d55645baaea3670aa7122 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 29 Jan 2024 19:15:56 +0800 Subject: [PATCH 0932/1551] Respect `NO_COLOR` in the compiler (#14260) --- src/compiler/crystal/command.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 6549c2768aea..18def74ebbd0 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -58,7 +58,7 @@ class Crystal::Command @compiler : Compiler? def initialize(@options : Array(String)) - @color = ENV["TERM"]? != "dumb" + @color = ENV["TERM"]? != "dumb" && !ENV.has_key?("NO_COLOR") @error_trace = false @progress_tracker = ProgressTracker.new end @@ -743,7 +743,7 @@ class Crystal::Command private def error(msg, exit_code = 1) # This is for the case where the main command is wrong - @color = false if ARGV.includes?("--no-color") || ENV["TERM"]? == "dumb" + @color = false if ARGV.includes?("--no-color") || ENV["TERM"]? == "dumb" || ENV.has_key?("NO_COLOR") Crystal.error msg, @color, exit_code: exit_code end From f6b81842e10bc9bb1951ef70fa896591aad3d672 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 29 Jan 2024 19:16:06 +0800 Subject: [PATCH 0933/1551] Add macro methods for `TypeOf` (#14262) --- spec/compiler/macro/macro_methods_spec.cr | 6 ++++++ src/compiler/crystal/macros.cr | 14 ++++++++++++-- src/compiler/crystal/macros/methods.cr | 11 +++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 516c3e408280..a970aff3e876 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2770,6 +2770,12 @@ module Crystal end end + describe TypeOf do + it "executes args" do + assert_macro %({{x.args}}), "[1, 'a', Foo]", {x: TypeOf.new([1.int32, CharLiteral.new('a'), "Foo".path])} + end + end + describe "case methods" do describe "when" do case_node = Case.new(1.int32, [When.new([2.int32, 3.int32] of ASTNode, 4.int32)], 5.int32, exhaustive: false) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index f18c3c7246ab..3b9ce399c7ef 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2276,8 +2276,18 @@ module Crystal::Macros end end - # class TypeOf < ASTNode - # end + # A `typeof` expression. + # + # Every expression *node* is equivalent to: + # + # ``` + # typeof({{ node.args.splat }}) + # ``` + class TypeOf < ASTNode + # Returns the arguments to this `typeof`. + def args : ArrayLiteral(ASTNode) + end + end # A macro expression, # surrounded by {{ ... }} (output = true) diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 60ecdb528509..b98306db3fc8 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -2362,6 +2362,17 @@ module Crystal end end + class TypeOf + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "args" + interpret_check_args { ArrayLiteral.map(@expressions, &.itself) } + else + super + end + end + end + class Generic def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) case method From bc0c8725cf007c7e88728ef75100fe7fad1e505b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 29 Jan 2024 20:14:49 +0100 Subject: [PATCH 0934/1551] Optimize hash lookup in `Enumerable#group_by` (#14235) Co-authored-by: Quinton Miller --- src/enumerable.cr | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/enumerable.cr b/src/enumerable.cr index 6b24c833f44f..9a1aad3debd0 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -636,11 +636,7 @@ module Enumerable(T) h = Hash(U, Array(T)).new each do |e| v = yield e - if h.has_key?(v) - h[v].push(e) - else - h[v] = [e] - end + h.put_if_absent(v) { Array(T).new } << e end h end From 57d67c2229d58f2737d00482a2f01e9179499ee3 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 30 Jan 2024 03:14:57 +0800 Subject: [PATCH 0935/1551] Fix `Colorize.enabled?`'s documentation (#14258) --- src/colorize.cr | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/colorize.cr b/src/colorize.cr index 48975c8a451f..83fd82c3935e 100644 --- a/src/colorize.cr +++ b/src/colorize.cr @@ -129,8 +129,7 @@ module Colorize # "hello".colorize.red.to_s # => "hello" # ``` # - # NOTE: This is by default disabled on non-TTY devices because they likely don't support ANSI escape codes. - # This will also be disabled if the environment variable `TERM` is "dumb" or `NO_COLOR` contains any value. + # NOTE: This is by default disabled if the environment variable `NO_COLOR` contains any value. class_property? enabled : Bool { !ENV.has_key?("NO_COLOR") } # Makes `Colorize.enabled` `true` if and only if both of `STDOUT.tty?` From 0f7c4e0c1d4864fb905037dd9c9db684acc0122f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 30 Jan 2024 03:15:05 +0800 Subject: [PATCH 0936/1551] Add macro methods for `Alias` (#14261) --- spec/compiler/macro/macro_methods_spec.cr | 12 ++++++++++++ src/compiler/crystal/macros.cr | 18 ++++++++++++++++-- src/compiler/crystal/macros/methods.cr | 13 +++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index a970aff3e876..806040a55faf 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2618,6 +2618,18 @@ module Crystal end end + describe Alias do + node = Alias.new("Foo".path, Generic.new(Path.new(["Bar", "Baz"], global: true), ["T".path] of ASTNode)) + + it "executes name" do + assert_macro %({{x.name}}), %(Foo), {x: node} + end + + it "executes type" do + assert_macro %({{x.type}}), %(::Bar::Baz(T)), {x: node} + end + end + describe "visibility modifier methods" do node = VisibilityModifier.new(Visibility::Protected, Def.new("some_def")) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 3b9ce399c7ef..2ab9f051530e 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2234,8 +2234,22 @@ module Crystal::Macros end end - # class Alias < ASTNode - # end + # An `alias` statement. + # + # Every statement `node` is equivalent to: + # + # ``` + # alias {{ node.name }} = {{ node.type }} + # ``` + class Alias < ASTNode + # Returns the name of the alias. + def name : Path + end + + # Returns the name of the type this alias is equivalent to. + def type : ASTNode + end + end # A metaclass in a type expression: `T.class` class Metaclass < ASTNode diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index b98306db3fc8..caa94c23035d 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1560,6 +1560,19 @@ module Crystal end end + class Alias + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "name" + interpret_check_args { @name } + when "type" + interpret_check_args { @value } + else + super + end + end + end + class OffsetOf def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) case method From 4b8f5c59ae23a79b281a75fadef30b04e973d005 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 30 Jan 2024 06:28:16 +0800 Subject: [PATCH 0937/1551] Do not handle inline assembly with `"intel"` flag as AT&T syntax (#14264) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/compiler/codegen/asm_spec.cr | 8 ++++++++ src/compiler/crystal/codegen/asm.cr | 2 +- src/compiler/crystal/codegen/ast.cr | 6 ++++++ src/llvm/enums.cr | 5 +++++ src/llvm/lib_llvm/core.cr | 9 ++------- src/llvm/type.cr | 6 +++--- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/spec/compiler/codegen/asm_spec.cr b/spec/compiler/codegen/asm_spec.cr index 2c6b0707699e..289c1455741e 100644 --- a/spec/compiler/codegen/asm_spec.cr +++ b/spec/compiler/codegen/asm_spec.cr @@ -44,5 +44,13 @@ describe "Code gen: asm" do c )).to_i.should eq(42) end + + it "codegens with intel dialect" do + run(<<-CRYSTAL).to_i.should eq(1234) + dst = uninitialized Int32 + asm("mov dword ptr [$0], 1234" :: "r"(pointerof(dst)) :: "intel") + dst + CRYSTAL + end {% end %} end diff --git a/src/compiler/crystal/codegen/asm.cr b/src/compiler/crystal/codegen/asm.cr index f0b3e2f088ab..107d63f19ecf 100644 --- a/src/compiler/crystal/codegen/asm.cr +++ b/src/compiler/crystal/codegen/asm.cr @@ -53,7 +53,7 @@ class Crystal::CodeGenVisitor fun_type = LLVM::Type.function(input_types, output_type) constraints = constraints.to_s - value = fun_type.inline_asm(node.text, constraints, node.volatile?, node.alignstack?, node.can_throw?) + value = fun_type.inline_asm(node.text, constraints, node.volatile?, node.alignstack?, node.can_throw?, node.dialect) value = LLVM::Function.from_value(value) asm_value = call LLVMTypedFunction.new(fun_type, value), input_values diff --git a/src/compiler/crystal/codegen/ast.cr b/src/compiler/crystal/codegen/ast.cr index 816958844dd9..ab4fa76d4e16 100644 --- a/src/compiler/crystal/codegen/ast.cr +++ b/src/compiler/crystal/codegen/ast.cr @@ -138,4 +138,10 @@ module Crystal found_extern end end + + class Asm + def dialect : LLVM::InlineAsmDialect + intel? ? LLVM::InlineAsmDialect::Intel : LLVM::InlineAsmDialect::ATT + end + end end diff --git a/src/llvm/enums.cr b/src/llvm/enums.cr index b8e06fb46f89..ac23fa711560 100644 --- a/src/llvm/enums.cr +++ b/src/llvm/enums.cr @@ -440,6 +440,11 @@ module LLVM LittleEndian = 1 << 28 end + enum InlineAsmDialect + ATT + Intel + end + struct Value enum Kind Argument diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr index 20f10c7e6e62..c865baaa55a5 100644 --- a/src/llvm/lib_llvm/core.cr +++ b/src/llvm/lib_llvm/core.cr @@ -4,11 +4,6 @@ lib LibLLVM # NOTE: the following C enums usually have different values from their C++ # counterparts (e.g. `LLVMModuleFlagBehavior` v.s. `LLVM::Module::ModFlagBehavior`) - enum InlineAsmDialect - ATT - Intel - end - enum ModuleFlagBehavior Warning = 1 end @@ -39,9 +34,9 @@ lib LibLLVM fun print_module_to_file = LLVMPrintModuleToFile(m : ModuleRef, filename : Char*, error_message : Char**) : Bool fun print_module_to_string = LLVMPrintModuleToString(m : ModuleRef) : Char* {% if !LibLLVM::IS_LT_130 %} - fun get_inline_asm = LLVMGetInlineAsm(ty : TypeRef, asm_string : Char*, asm_string_size : SizeT, constraints : Char*, constraints_size : SizeT, has_side_effects : Bool, is_align_stack : Bool, dialect : InlineAsmDialect, can_throw : Bool) : ValueRef + fun get_inline_asm = LLVMGetInlineAsm(ty : TypeRef, asm_string : Char*, asm_string_size : SizeT, constraints : Char*, constraints_size : SizeT, has_side_effects : Bool, is_align_stack : Bool, dialect : LLVM::InlineAsmDialect, can_throw : Bool) : ValueRef {% else %} - fun get_inline_asm = LLVMGetInlineAsm(t : TypeRef, asm_string : Char*, asm_string_size : SizeT, constraints : Char*, constraints_size : SizeT, has_side_effects : Bool, is_align_stack : Bool, dialect : InlineAsmDialect) : ValueRef + fun get_inline_asm = LLVMGetInlineAsm(t : TypeRef, asm_string : Char*, asm_string_size : SizeT, constraints : Char*, constraints_size : SizeT, has_side_effects : Bool, is_align_stack : Bool, dialect : LLVM::InlineAsmDialect) : ValueRef {% end %} fun get_module_context = LLVMGetModuleContext(m : ModuleRef) : ContextRef diff --git a/src/llvm/type.cr b/src/llvm/type.cr index 06c36ff5796d..c391019aef83 100644 --- a/src/llvm/type.cr +++ b/src/llvm/type.cr @@ -173,7 +173,7 @@ struct LLVM::Type Value.new LibLLVM.const_array(self, (values.to_unsafe.as(LibLLVM::ValueRef*)), values.size) end - def inline_asm(asm_string, constraints, has_side_effects = false, is_align_stack = false, can_throw = false) + def inline_asm(asm_string, constraints, has_side_effects = false, is_align_stack = false, can_throw = false, dialect : InlineAsmDialect = InlineAsmDialect::ATT) value = {% if LibLLVM::IS_LT_130 %} LibLLVM.get_inline_asm( @@ -184,7 +184,7 @@ struct LLVM::Type constraints.size, (has_side_effects ? 1 : 0), (is_align_stack ? 1 : 0), - LibLLVM::InlineAsmDialect::ATT + dialect, ) {% else %} LibLLVM.get_inline_asm( @@ -195,7 +195,7 @@ struct LLVM::Type constraints.size, (has_side_effects ? 1 : 0), (is_align_stack ? 1 : 0), - LibLLVM::InlineAsmDialect::ATT, + dialect, (can_throw ? 1 : 0) ) {% end %} From de7a0786e5e1635f6cda6176ac9ec395086f8dcf Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 30 Jan 2024 06:28:27 +0800 Subject: [PATCH 0938/1551] Use correct string size for `LLVM::Type#inline_asm` (#14265) --- spec/compiler/codegen/asm_spec.cr | 8 ++++++++ src/llvm/type.cr | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/spec/compiler/codegen/asm_spec.cr b/spec/compiler/codegen/asm_spec.cr index 289c1455741e..05eb3b895032 100644 --- a/spec/compiler/codegen/asm_spec.cr +++ b/spec/compiler/codegen/asm_spec.cr @@ -3,6 +3,14 @@ require "../../spec_helper" describe "Code gen: asm" do # TODO: arm asm tests {% if flag?(:i386) || flag?(:x86_64) %} + it "passes correct string length to LLVM" do + run <<-CRYSTAL + asm("// 😂😂 + nop + nop") + CRYSTAL + end + it "codegens without inputs" do run(%( dst = uninitialized Int32 diff --git a/src/llvm/type.cr b/src/llvm/type.cr index c391019aef83..42d1a314c118 100644 --- a/src/llvm/type.cr +++ b/src/llvm/type.cr @@ -179,9 +179,9 @@ struct LLVM::Type LibLLVM.get_inline_asm( self, asm_string, - asm_string.size, + asm_string.bytesize, constraints, - constraints.size, + constraints.bytesize, (has_side_effects ? 1 : 0), (is_align_stack ? 1 : 0), dialect, @@ -190,9 +190,9 @@ struct LLVM::Type LibLLVM.get_inline_asm( self, asm_string, - asm_string.size, + asm_string.bytesize, constraints, - constraints.size, + constraints.bytesize, (has_side_effects ? 1 : 0), (is_align_stack ? 1 : 0), dialect, From 8a2a1a83f1f454f2a424242eef2c6a55158a536c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 30 Jan 2024 20:47:10 +0800 Subject: [PATCH 0939/1551] Fix name locations of `FunDef` and `External` nodes (#14267) --- spec/compiler/parser/parser_spec.cr | 18 ++++++++++++++++++ .../crystal/semantic/top_level_visitor.cr | 1 + src/compiler/crystal/syntax/ast.cr | 9 ++++++++- src/compiler/crystal/syntax/parser.cr | 2 ++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 2fa1a5b4c4a7..9f4ec5faccf7 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2505,6 +2505,24 @@ module Crystal name_location.column_number.should eq(12) end + it "sets location of top-level fun name" do + parser = Parser.new("fun foo; end") + node = parser.parse.as(FunDef) + + name_location = node.name_location.should_not be_nil + name_location.line_number.should eq(1) + name_location.column_number.should eq(5) + end + + it "sets location of lib fun name" do + parser = Parser.new("lib Foo; fun foo; end") + node = parser.parse.as(LibDef).body.as(FunDef) + + name_location = node.name_location.should_not be_nil + name_location.line_number.should eq(1) + name_location.column_number.should eq(14) + end + it "sets correct location of proc literal" do parser = Parser.new("->(\n x : Int32,\n y : String\n) { }") node = parser.parse.as(ProcLiteral) diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 05d8918f8383..11c5707b19fa 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -918,6 +918,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end external = External.new(node.name, external_args, node.body, node.real_name).at(node) + external.name_location = node.name_location call_convention = nil process_def_annotations(external, annotations) do |annotation_type, ann| diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index d0be18606714..9c0ffcf0d7e5 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -1954,6 +1954,7 @@ module Crystal property real_name : String property doc : String? property? varargs : Bool + property name_location : Location? def initialize(@name, @args = [] of Arg, @return_type = nil, @varargs = false, @body = nil, @real_name = name) end @@ -1965,7 +1966,13 @@ module Crystal end def clone_without_location - FunDef.new(@name, @args.clone, @return_type.clone, @varargs, @body.clone, @real_name) + clone = FunDef.new(@name, @args.clone, @return_type.clone, @varargs, @body.clone, @real_name) + clone.name_location = name_location + clone + end + + def name_size + @name.size end def_equals_and_hash @name, @args, @return_type, @varargs, @body, @real_name diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index f14399212e65..e3f4c2605ed6 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -5732,6 +5732,7 @@ module Crystal with_isolated_var_scope(require_body) do next_token_skip_space_or_newline + name_location = @token.location name = if top_level check_ident else @@ -5835,6 +5836,7 @@ module Crystal end fun_def = FunDef.new name, params, return_type, varargs, body, real_name + fun_def.name_location = name_location fun_def.doc = doc fun_def.at(location).at_end(end_location) end From d88c013588e4c92d2510af21b88fd14542c5a902 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 30 Jan 2024 20:47:21 +0800 Subject: [PATCH 0940/1551] Ensure `Crystal::Visitor#visit` returns `Bool` (#14266) --- samples/compiler/visitor_example.cr | 1 + src/compiler/crystal/codegen/codegen.cr | 17 +++++++++++--- src/compiler/crystal/codegen/primitives.cr | 2 ++ src/compiler/crystal/interpreter/compiler.cr | 5 +++-- src/compiler/crystal/macros/interpreter.cr | 8 +++++-- .../crystal/semantic/cleanup_transformer.cr | 2 ++ src/compiler/crystal/semantic/main_visitor.cr | 22 ++++++++++++++++--- .../semantic/restrictions_augmenter.cr | 3 ++- src/compiler/crystal/semantic/to_s.cr | 3 +++ .../crystal/semantic/top_level_visitor.cr | 1 + .../crystal/semantic/type_guess_visitor.cr | 3 +++ src/compiler/crystal/syntax/to_s.cr | 15 +++++++++++++ src/compiler/crystal/tools/expand.cr | 4 ++++ src/compiler/crystal/tools/implementations.cr | 2 ++ .../crystal/tools/print_types_visitor.cr | 4 ++++ 15 files changed, 81 insertions(+), 11 deletions(-) diff --git a/samples/compiler/visitor_example.cr b/samples/compiler/visitor_example.cr index ec063a832efa..d9be4da91baf 100644 --- a/samples/compiler/visitor_example.cr +++ b/samples/compiler/visitor_example.cr @@ -15,6 +15,7 @@ class Counter < Crystal::Visitor def visit(node : Crystal::NumberLiteral) @count += 1 + false end def visit(node : Crystal::ASTNode) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index a6015012eb7c..e7128302a66e 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -501,18 +501,22 @@ module Crystal def visit(node : Nop) @last = llvm_nil + false end def visit(node : NilLiteral) @last = llvm_nil + false end def visit(node : BoolLiteral) @last = int1(node.value ? 1 : 0) + false end def visit(node : CharLiteral) @last = int32(node.value.ord) + false end def visit(node : NumberLiteral) @@ -542,14 +546,17 @@ module Crystal in .f64? @last = float64(node.value) end + false end def visit(node : StringLiteral) @last = build_string_constant(node.value, node.value) + false end def visit(node : SymbolLiteral) @last = int(@symbols[node.value]) + false end def visit(node : TupleLiteral) @@ -1092,7 +1099,7 @@ module Crystal request_value(value) - return if value.no_returns? + return false if value.no_returns? last = @last @@ -1106,7 +1113,7 @@ module Crystal read_class_var_ptr(target) when Var # Can't assign void - return if target.type.void? + return false if target.type.void? # If assigning to a special variable in a method that yields, # assign to that variable too. @@ -1284,6 +1291,7 @@ module Crystal else node.raise "BUG: missing context var: #{node.name}" end + false end def visit(node : Global) @@ -1292,6 +1300,7 @@ module Crystal def visit(node : ClassVar) @last = read_class_var(node) + false end def visit(node : InstanceVar) @@ -1403,7 +1412,7 @@ module Crystal unless filtered_type @last = upcast llvm_nil, resulting_type, @program.nil - return + return false end non_nilable_type = node.non_nilable_type @@ -1664,6 +1673,7 @@ module Crystal def visit(node : Unreachable) builder.unreachable + false end def check_proc_is_not_closure(value, type) @@ -2049,6 +2059,7 @@ module Crystal def unreachable(file = __FILE__, line = __LINE__) debug_codegen_log(file, line) { "Reached the unreachable!" } builder.unreachable + false end def allocate_aggregate(type) diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index 9ed05c0009f2..80ba7d832a41 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -11,6 +11,8 @@ class Crystal::CodeGenVisitor else raise "BUG: unhandled primitive in codegen visit: #{node.name}" end + + false end def codegen_primitive(call, node, target_def, call_args) diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index bfd1514b419f..04fc885fce7e 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -762,7 +762,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor # particularly when outside of a method. if is_self && !scope.is_a?(Program) && !scope.passed_as_self? put_type scope, node: node - return + return false end local_var = lookup_local_var_or_closured_var(node.name) @@ -1687,7 +1687,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor if node.upcast? upcast node.obj, obj_type, node.non_nilable_type upcast node.obj, node.non_nilable_type, node.type - return + return false end # Check if obj is a `to_type` @@ -3158,6 +3158,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor {% end %} call compiled_def, node: node + false end def visit(node : ASTNode) diff --git a/src/compiler/crystal/macros/interpreter.cr b/src/compiler/crystal/macros/interpreter.cr index 5bc7cd0117ca..1e18e92b7545 100644 --- a/src/compiler/crystal/macros/interpreter.cr +++ b/src/compiler/crystal/macros/interpreter.cr @@ -118,6 +118,7 @@ module Crystal def visit(node : MacroLiteral) @str << node.value + false end def visit(node : MacroVerbatim) @@ -135,7 +136,8 @@ module Crystal def visit(node : Var) var = @vars[node.name]? if var - return @last = var + @last = var + return false end # Try to consider the var as a top-level macro call. @@ -150,7 +152,8 @@ module Crystal # and in this case the parser has no idea about this, so the only # solution is to do it now. if value = interpret_top_level_call?(Call.new(nil, node.name)) - return @last = value + @last = value + return false end node.raise "undefined macro variable '#{node.name}'" @@ -549,6 +552,7 @@ module Crystal else node.raise "unknown macro instance var: '#{node.name}'" end + false end def visit(node : TupleLiteral) diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index bc3084429ed3..541e0f51d662 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -635,10 +635,12 @@ module Crystal if @a_def.vars.try &.[node.name]?.try &.closured? @vars << node end + false end def visit(node : InstanceVar) @vars << node + false end def visit(node : ASTNode) diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 37218e7a34dc..61d1aa508a46 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -226,6 +226,8 @@ module Crystal node.syntax_replacement = type node.bind_to type end + + false end def visit(node : Generic) @@ -353,6 +355,7 @@ module Crystal def visit(node : Self) node.type = the_self(node).instance_type + false end def visit(node : Var) @@ -394,6 +397,7 @@ module Crystal else node.raise "read before assignment to local variable '#{node.name}'" end + false end def visit(node : TypeDeclaration) @@ -628,6 +632,8 @@ module Crystal ivar.nil_reason ||= NilReason.new(node.name, :used_before_initialized, [node] of ASTNode) ivar.bind_to program.nil_var end + + false end def visit(node : ReadInstanceVar) @@ -1007,7 +1013,7 @@ module Crystal end def visit(node : Block) - return if node.visited? + return false if node.visited? node.visited = true node.context = current_non_block_context @@ -1630,6 +1636,7 @@ module Crystal ivars[node.name] = node_in_callstack(node) end end + false end def visit(node : Var) @@ -1761,7 +1768,7 @@ module Crystal comp.accept self node.syntax_replacement = comp node.bind_to comp - return + return false end if needs_type_filters? && (var = get_expression_var(node.obj)) @@ -2060,7 +2067,7 @@ module Crystal unless node.has_breaks? if endless_while node.type = program.no_return - return + return false end filter_vars TypeFilters.not(cond_type_filters) @@ -2325,6 +2332,8 @@ module Crystal else node.raise "BUG: unhandled primitive in MainVisitor: #{node.name}" end + + false end def visit_va_arg(node) @@ -3042,31 +3051,38 @@ module Crystal def visit(node : Nop) node.type = @program.nil + false end def visit(node : NilLiteral) node.type = @program.nil + false end def visit(node : BoolLiteral) node.type = program.bool + false end def visit(node : NumberLiteral) node.type = program.type_from_literal_kind node.kind + false end def visit(node : CharLiteral) node.type = program.char + false end def visit(node : SymbolLiteral) node.type = program.symbol program.symbols.add node.value + false end def visit(node : StringLiteral) node.type = program.string + false end def visit(node : RegexLiteral) diff --git a/src/compiler/crystal/semantic/restrictions_augmenter.cr b/src/compiler/crystal/semantic/restrictions_augmenter.cr index a627f27f5fe0..5b3e31056110 100644 --- a/src/compiler/crystal/semantic/restrictions_augmenter.cr +++ b/src/compiler/crystal/semantic/restrictions_augmenter.cr @@ -59,7 +59,8 @@ module Crystal def visit(node : Call) if expanded = node.expanded - return expanded.accept self + expanded.accept self + return false end node.obj.try &.accept self diff --git a/src/compiler/crystal/semantic/to_s.cr b/src/compiler/crystal/semantic/to_s.cr index 8c91426bc379..13e8203efe1d 100644 --- a/src/compiler/crystal/semantic/to_s.cr +++ b/src/compiler/crystal/semantic/to_s.cr @@ -44,14 +44,17 @@ module Crystal def visit(node : Primitive) @str << "# primitive: " @str << node.name + false end def visit(node : MetaVar) @str << node.name + false end def visit(node : MetaMacroVar) @str << node.name + false end def visit(node : TypeFilteredNode) diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 11c5707b19fa..9b7eedb2a876 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -598,6 +598,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor typed_def_type = lookup_type(node.type_spec) typed_def_type = check_allowed_in_lib node.type_spec, typed_def_type current_type.types[node.name] = TypeDefType.new @program, current_type, node.name, typed_def_type + false end end diff --git a/src/compiler/crystal/semantic/type_guess_visitor.cr b/src/compiler/crystal/semantic/type_guess_visitor.cr index 693cade2906b..612390db8e21 100644 --- a/src/compiler/crystal/semantic/type_guess_visitor.cr +++ b/src/compiler/crystal/semantic/type_guess_visitor.cr @@ -86,6 +86,7 @@ module Crystal end check_var_is_self(node) + false end def visit(node : UninitializedVar) @@ -114,6 +115,7 @@ module Crystal # TODO: can this be reached? end end + false end def visit(node : Assign) @@ -1353,6 +1355,7 @@ module Crystal if node.name == "self" @has_self = true end + false end def visit(node : ASTNode) diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index f5086e346277..3ea146d15c3e 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -49,10 +49,12 @@ module Crystal end def visit(node : Nop) + false end def visit(node : BoolLiteral) @str << (node.value ? "true" : "false") + false end def visit(node : NumberLiteral) @@ -62,6 +64,8 @@ module Crystal @str << '_' @str << node.kind.to_s end + + false end def needs_suffix?(node : NumberLiteral) @@ -83,10 +87,12 @@ module Crystal def visit(node : CharLiteral) node.value.inspect(@str) + false end def visit(node : SymbolLiteral) visit_symbol_literal_value node.value + false end def visit_symbol_literal_value(value : String) @@ -100,6 +106,7 @@ module Crystal def visit(node : StringLiteral) node.value.inspect(@str) + false end def visit(node : StringInterpolation) @@ -189,6 +196,7 @@ module Crystal def visit(node : NilLiteral) @str << "nil" + false end def visit(node : Expressions) @@ -573,6 +581,7 @@ module Crystal def visit(node : Var) @str << node.name + false end def visit(node : ProcLiteral) @@ -836,11 +845,13 @@ module Crystal def visit(node : Self) @str << "self" + false end def visit(node : Path) @str << "::" if node.global? node.names.join(@str, "::") + false end def visit(node : Generic) @@ -907,6 +918,7 @@ module Crystal def visit(node : InstanceVar) @str << node.name + false end def visit(node : ReadInstanceVar) @@ -918,6 +930,7 @@ module Crystal def visit(node : ClassVar) @str << node.name + false end def visit(node : Yield) @@ -1105,6 +1118,7 @@ module Crystal def visit(node : Global) @str << node.name + false end def visit(node : LibDef) @@ -1449,6 +1463,7 @@ module Crystal def visit(node : MagicConstant) @str << node.name + false end def visit(node : Asm) diff --git a/src/compiler/crystal/tools/expand.cr b/src/compiler/crystal/tools/expand.cr index 365fc2b3f865..792d60869bd3 100644 --- a/src/compiler/crystal/tools/expand.cr +++ b/src/compiler/crystal/tools/expand.cr @@ -144,6 +144,8 @@ module Crystal else contains_target(node) end + else + false end end @@ -156,6 +158,8 @@ module Crystal else contains_target(node) end + else + false end end diff --git a/src/compiler/crystal/tools/implementations.cr b/src/compiler/crystal/tools/implementations.cr index f4f3a390f0eb..e2dbee001346 100644 --- a/src/compiler/crystal/tools/implementations.cr +++ b/src/compiler/crystal/tools/implementations.cr @@ -114,6 +114,7 @@ module Crystal @locations << target_def.location.not_nil! end end + false end def visit(node : Path) @@ -123,6 +124,7 @@ module Crystal target.try &.locations.try &.each do |loc| @locations << loc end + false end def visit(node) diff --git a/src/compiler/crystal/tools/print_types_visitor.cr b/src/compiler/crystal/tools/print_types_visitor.cr index 6ffc533a4b0f..0744dc17febd 100644 --- a/src/compiler/crystal/tools/print_types_visitor.cr +++ b/src/compiler/crystal/tools/print_types_visitor.cr @@ -39,10 +39,12 @@ module Crystal def visit(node : Var) output_name node + false end def visit(node : Global) output_name node + false end def visit(node : TypeDeclaration) @@ -50,6 +52,7 @@ module Crystal if var.is_a?(Var) output_name var end + false end def visit(node : UninitializedVar) @@ -57,6 +60,7 @@ module Crystal if var.is_a?(Var) output_name var end + false end def output_name(node) From f66312174d0b7dfa30b8795e356c30b9c23e0649 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 30 Jan 2024 20:47:29 +0800 Subject: [PATCH 0941/1551] Add macro methods for `Asm` and `AsmOperand` (#14268) --- spec/compiler/macro/macro_methods_spec.cr | 69 +++++++++++++++++++++++ src/compiler/crystal/macros.cr | 69 +++++++++++++++++++++++ src/compiler/crystal/macros/methods.cr | 56 ++++++++++++++++++ 3 files changed, 194 insertions(+) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 806040a55faf..76d5f795903c 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -3426,6 +3426,75 @@ module Crystal end end + describe Asm do + asm1 = Asm.new("nop") + asm2 = Asm.new( + text: "foo", + outputs: [AsmOperand.new("=r", "x".var), AsmOperand.new("=r", "y".var)], + inputs: [AsmOperand.new("i", 1.int32), AsmOperand.new("r", 2.int32)], + clobbers: %w(rax memory), + volatile: true, + alignstack: true, + intel: true, + can_throw: true, + ) + + it "executes text" do + assert_macro %({{x.text}}), %("nop"), {x: asm1} + assert_macro %({{x.text}}), %("foo"), {x: asm2} + end + + it "executes outputs" do + assert_macro %({{x.outputs}}), %([] of ::NoReturn), {x: asm1} + assert_macro %({{x.outputs}}), %(["=r"(x), "=r"(y)]), {x: asm2} + end + + it "executes inputs" do + assert_macro %({{x.inputs}}), %([] of ::NoReturn), {x: asm1} + assert_macro %({{x.inputs}}), %(["i"(1), "r"(2)]), {x: asm2} + end + + it "executes clobbers" do + assert_macro %({{x.clobbers}}), %([] of ::NoReturn), {x: asm1} + assert_macro %({{x.clobbers}}), %(["rax", "memory"]), {x: asm2} + end + + it "executes volatile?" do + assert_macro %({{x.volatile?}}), %(false), {x: asm1} + assert_macro %({{x.volatile?}}), %(true), {x: asm2} + end + + it "executes alignstack?" do + assert_macro %({{x.alignstack?}}), %(false), {x: asm1} + assert_macro %({{x.alignstack?}}), %(true), {x: asm2} + end + + it "executes intel?" do + assert_macro %({{x.intel?}}), %(false), {x: asm1} + assert_macro %({{x.intel?}}), %(true), {x: asm2} + end + + it "executes can_throw?" do + assert_macro %({{x.can_throw?}}), %(false), {x: asm1} + assert_macro %({{x.can_throw?}}), %(true), {x: asm2} + end + end + + describe AsmOperand do + asm_operand1 = AsmOperand.new("=r", "x".var) + asm_operand2 = AsmOperand.new("i", 1.int32) + + it "executes constraint" do + assert_macro %({{x.constraint}}), %("=r"), {x: asm_operand1} + assert_macro %({{x.constraint}}), %("i"), {x: asm_operand2} + end + + it "executes exp" do + assert_macro %({{x.exp}}), %(x), {x: asm_operand1} + assert_macro %({{x.exp}}), %(1), {x: asm_operand2} + end + end + describe "env" do it "has key" do with_env("FOO": "foo") do diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 2ab9f051530e..3ec6243ffd62 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2377,6 +2377,75 @@ module Crystal::Macros class MagicConstant < ASTNode end + # An inline assembly expression. + # + # Every assembly `node` is equivalent to: + # + # ``` + # asm( + # {{ node.text }} : + # {{ node.outputs.splat }} : + # {{ node.inputs.splat }} : + # {{ node.clobbers.splat }} : + # {% if node.volatile? %} "volatile", {% end %} + # {% if node.alignstack? %} "alignstack", {% end %} + # {% if node.intel? %} "intel", {% end %} + # {% if node.can_throw? %} "unwind", {% end %} + # ) + # ``` + class Asm < ASTNode + # Returns the template string for this assembly expression. + def text : StringLiteral + end + + # Returns an array of output operands for this assembly expression. + def outputs : ArrayLiteral(AsmOperand) + end + + # Returns an array of input operands for this assembly expression. + def inputs : ArrayLiteral(AsmOperand) + end + + # Returns an array of clobbered register names for this assembly expression. + def clobbers : ArrayLiteral(StringLiteral) + end + + # Returns whether the assembly expression contains side effects that are + # not listed in `#outputs`, `#inputs`, and `#clobbers`. + def volatile? : BoolLiteral + end + + # Returns whether the assembly expression requires stack alignment code. + def alignstack? : BoolLiteral + end + + # Returns `true` if the template string uses the Intel syntax, `false` if it + # uses the AT&T syntax. + def intel? : BoolLiteral + end + + # Returns whether the assembly expression might unwind the stack. + def can_throw? : BoolLiteral + end + end + + # An output or input operand for an `Asm` node. + # + # Every operand `node` is equivalent to: + # + # ``` + # {{ node.constraint }}({{ node.exp }}) + # ``` + class AsmOperand < ASTNode + # Returns the constraint string of this operand. + def constraint : StringLiteral + end + + # Returns the associated output or input argument of this operand. + def exp : ASTNode + end + end + # A fictitious node representing an identifier like, `foo`, `Bar` or `something_else`. # # The parser doesn't create these nodes. Instead, you create them by invoking `id` diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index caa94c23035d..9891c4cc2b2a 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1653,6 +1653,62 @@ module Crystal end end + class Asm + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "text" + interpret_check_args { StringLiteral.new(@text) } + when "outputs" + interpret_check_args do + if outputs = @outputs + ArrayLiteral.map(outputs, &.itself) + else + empty_no_return_array + end + end + when "inputs" + interpret_check_args do + if inputs = @inputs + ArrayLiteral.map(inputs, &.itself) + else + empty_no_return_array + end + end + when "clobbers" + interpret_check_args do + if clobbers = @clobbers + ArrayLiteral.map(clobbers) { |clobber| StringLiteral.new(clobber) } + else + empty_no_return_array + end + end + when "volatile?" + interpret_check_args { BoolLiteral.new(@volatile) } + when "alignstack?" + interpret_check_args { BoolLiteral.new(@alignstack) } + when "intel?" + interpret_check_args { BoolLiteral.new(@intel) } + when "can_throw?" + interpret_check_args { BoolLiteral.new(@can_throw) } + else + super + end + end + end + + class AsmOperand + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "constraint" + interpret_check_args { StringLiteral.new(@constraint) } + when "exp" + interpret_check_args { @exp } + else + super + end + end + end + class MacroId def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) case method From 8d1c0806d2e959825a65727fdc0cc5b36a5648a5 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 30 Jan 2024 20:13:06 +0100 Subject: [PATCH 0942/1551] Thread: set name (#14257) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/thread_spec.cr | 13 ++++++++++++ src/crystal/scheduler.cr | 2 +- src/crystal/system/thread.cr | 20 ++++++++++++++++++- src/crystal/system/unix/pthread.cr | 17 ++++++++++++++++ src/crystal/system/win32/thread.cr | 7 +++++++ src/lib_c/aarch64-android/c/pthread.cr | 1 + src/lib_c/aarch64-darwin/c/pthread.cr | 1 + src/lib_c/aarch64-linux-gnu/c/pthread.cr | 1 + src/lib_c/aarch64-linux-musl/c/pthread.cr | 1 + src/lib_c/arm-linux-gnueabihf/c/pthread.cr | 1 + src/lib_c/i386-linux-gnu/c/pthread.cr | 1 + src/lib_c/i386-linux-musl/c/pthread.cr | 1 + src/lib_c/x86_64-darwin/c/pthread.cr | 1 + src/lib_c/x86_64-dragonfly/c/pthread.cr | 1 + src/lib_c/x86_64-freebsd/c/pthread.cr | 1 + src/lib_c/x86_64-linux-gnu/c/pthread.cr | 1 + src/lib_c/x86_64-linux-musl/c/pthread.cr | 1 + src/lib_c/x86_64-netbsd/c/pthread.cr | 1 + src/lib_c/x86_64-openbsd/c/pthread.cr | 1 + .../c/processthreadsapi.cr | 3 +++ 20 files changed, 74 insertions(+), 2 deletions(-) diff --git a/spec/std/thread_spec.cr b/spec/std/thread_spec.cr index 599a1968f52f..136026667137 100644 --- a/spec/std/thread_spec.cr +++ b/spec/std/thread_spec.cr @@ -49,4 +49,17 @@ describe Thread do thread.join end + + it "names the thread" do + Thread.current.name.should be_nil + name = nil + + thread = Thread.new(name: "some-name") do + name = Thread.current.name + end + thread.name.should eq("some-name") + + thread.join + name.should eq("some-name") + end end diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index fa963595bf6d..101a287d23bb 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -234,7 +234,7 @@ class Crystal::Scheduler Thread.current.scheduler.enqueue worker_loop Thread.current else - Thread.new do + Thread.new(name: "CRYSTAL-MT-#{i}") do scheduler = Thread.current.scheduler pending.sub(1) scheduler.run_loop diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index 88784ed68330..b40a7dceb32b 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -17,6 +17,8 @@ module Crystal::System::Thread # private def system_close # private def stack_address : Void* + + # private def system_name=(String) : String end {% if flag?(:wasi) %} @@ -49,12 +51,14 @@ class Thread # :nodoc: property previous : Thread? + getter name : String? + def self.unsafe_each(&) threads.unsafe_each { |thread| yield thread } end # Creates and starts a new system thread. - def initialize(&@func : ->) + def initialize(@name : String? = nil, &@func : ->) @system_handle = uninitialized Crystal::System::Thread::Handle init_handle end @@ -104,6 +108,12 @@ class Thread Crystal::System::Thread.yield_current end + # Changes the name of the current thread. + def self.name=(name : String) : String + thread = Thread.current + thread.name = name + end + # :nodoc: getter scheduler : Crystal::Scheduler { Crystal::Scheduler.new(self) } @@ -112,6 +122,10 @@ class Thread Thread.current = self @main_fiber = fiber = Fiber.new(stack_address, self) + if name = @name + self.system_name = name + end + begin @func.call rescue ex @@ -123,6 +137,10 @@ class Thread end end + protected def name=(@name : String) + self.system_name = name + end + # Holds the GC thread handler property gc_thread_handler : Void* = Pointer(Void).null end diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index ca16080621e3..5d6e2a332adc 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -112,6 +112,23 @@ module Crystal::System::Thread address end + + # Warning: must be called from the current thread itself, because Darwin + # doesn't allow to set the name of any thread but the current one! + private def system_name=(name : String) : String + {% if flag?(:darwin) %} + LibC.pthread_setname_np(name) + {% elsif flag?(:netbsd) %} + LibC.pthread_setname_np(@system_handle, name, nil) + {% elsif LibC.has_method?(:pthread_setname_np) %} + LibC.pthread_setname_np(@system_handle, name) + {% elsif LibC.has_method?(:pthread_set_name_np) %} + LibC.pthread_set_name_np(@system_handle, name) + {% else %} + {% raise "No `Crystal::System::Thread#system_name` implementation available" %} + {% end %} + name + end end # In musl (alpine) the calls to unwind API segfaults diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr index 5dbcbabbfad9..2b44f66c28ce 100644 --- a/src/crystal/system/win32/thread.cr +++ b/src/crystal/system/win32/thread.cr @@ -72,4 +72,11 @@ module Crystal::System::Thread low_limit {% end %} end + + private def system_name=(name : String) : String + {% if LibC.has_method?(:SetThreadDescription) %} + LibC.SetThreadDescription(@system_handle, System.to_wstr(name)) + {% end %} + name + end end diff --git a/src/lib_c/aarch64-android/c/pthread.cr b/src/lib_c/aarch64-android/c/pthread.cr index d761bcb3ceb2..fbab59bd07d7 100644 --- a/src/lib_c/aarch64-android/c/pthread.cr +++ b/src/lib_c/aarch64-android/c/pthread.cr @@ -38,6 +38,7 @@ lib LibC fun pthread_mutex_unlock(__mutex : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(__key : PthreadT, __name : Char*) : Int fun pthread_setspecific(__key : PthreadKeyT, __value : Void*) : Int end diff --git a/src/lib_c/aarch64-darwin/c/pthread.cr b/src/lib_c/aarch64-darwin/c/pthread.cr index dd262f7d9c12..d146e227197f 100644 --- a/src/lib_c/aarch64-darwin/c/pthread.cr +++ b/src/lib_c/aarch64-darwin/c/pthread.cr @@ -25,4 +25,5 @@ lib LibC fun pthread_mutex_trylock(x0 : PthreadMutexT*) : Int fun pthread_mutex_unlock(x0 : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(Char*) : Int end diff --git a/src/lib_c/aarch64-linux-gnu/c/pthread.cr b/src/lib_c/aarch64-linux-gnu/c/pthread.cr index 643fca7c015a..5d9f7b94e225 100644 --- a/src/lib_c/aarch64-linux-gnu/c/pthread.cr +++ b/src/lib_c/aarch64-linux-gnu/c/pthread.cr @@ -36,4 +36,5 @@ lib LibC fun pthread_mutex_trylock(mutex : PthreadMutexT*) : Int fun pthread_mutex_unlock(mutex : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(PthreadT, Char*) : Int end diff --git a/src/lib_c/aarch64-linux-musl/c/pthread.cr b/src/lib_c/aarch64-linux-musl/c/pthread.cr index c318bb5f4357..6ee1a20d90a6 100644 --- a/src/lib_c/aarch64-linux-musl/c/pthread.cr +++ b/src/lib_c/aarch64-linux-musl/c/pthread.cr @@ -27,4 +27,5 @@ lib LibC fun pthread_mutex_trylock(x0 : PthreadMutexT*) : Int fun pthread_mutex_unlock(x0 : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(PthreadT, Char*) : Int end diff --git a/src/lib_c/arm-linux-gnueabihf/c/pthread.cr b/src/lib_c/arm-linux-gnueabihf/c/pthread.cr index 643fca7c015a..5d9f7b94e225 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/pthread.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/pthread.cr @@ -36,4 +36,5 @@ lib LibC fun pthread_mutex_trylock(mutex : PthreadMutexT*) : Int fun pthread_mutex_unlock(mutex : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(PthreadT, Char*) : Int end diff --git a/src/lib_c/i386-linux-gnu/c/pthread.cr b/src/lib_c/i386-linux-gnu/c/pthread.cr index ab943b23587d..33274a6cf51d 100644 --- a/src/lib_c/i386-linux-gnu/c/pthread.cr +++ b/src/lib_c/i386-linux-gnu/c/pthread.cr @@ -35,4 +35,5 @@ lib LibC fun pthread_mutex_trylock(mutex : PthreadMutexT*) : Int fun pthread_mutex_unlock(mutex : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(PthreadT, Char*) : Int end diff --git a/src/lib_c/i386-linux-musl/c/pthread.cr b/src/lib_c/i386-linux-musl/c/pthread.cr index c318bb5f4357..6ee1a20d90a6 100644 --- a/src/lib_c/i386-linux-musl/c/pthread.cr +++ b/src/lib_c/i386-linux-musl/c/pthread.cr @@ -27,4 +27,5 @@ lib LibC fun pthread_mutex_trylock(x0 : PthreadMutexT*) : Int fun pthread_mutex_unlock(x0 : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(PthreadT, Char*) : Int end diff --git a/src/lib_c/x86_64-darwin/c/pthread.cr b/src/lib_c/x86_64-darwin/c/pthread.cr index dd262f7d9c12..d146e227197f 100644 --- a/src/lib_c/x86_64-darwin/c/pthread.cr +++ b/src/lib_c/x86_64-darwin/c/pthread.cr @@ -25,4 +25,5 @@ lib LibC fun pthread_mutex_trylock(x0 : PthreadMutexT*) : Int fun pthread_mutex_unlock(x0 : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(Char*) : Int end diff --git a/src/lib_c/x86_64-dragonfly/c/pthread.cr b/src/lib_c/x86_64-dragonfly/c/pthread.cr index 87ae8c05c358..90bf0c6285a0 100644 --- a/src/lib_c/x86_64-dragonfly/c/pthread.cr +++ b/src/lib_c/x86_64-dragonfly/c/pthread.cr @@ -29,4 +29,5 @@ lib LibC fun pthread_mutex_trylock(x0 : PthreadMutexT*) : Int fun pthread_mutex_unlock(x0 : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(PthreadT, Char*) : Int end diff --git a/src/lib_c/x86_64-freebsd/c/pthread.cr b/src/lib_c/x86_64-freebsd/c/pthread.cr index 87ae8c05c358..9cc78c2f2850 100644 --- a/src/lib_c/x86_64-freebsd/c/pthread.cr +++ b/src/lib_c/x86_64-freebsd/c/pthread.cr @@ -29,4 +29,5 @@ lib LibC fun pthread_mutex_trylock(x0 : PthreadMutexT*) : Int fun pthread_mutex_unlock(x0 : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_set_name_np(PthreadT, Char*) end diff --git a/src/lib_c/x86_64-linux-gnu/c/pthread.cr b/src/lib_c/x86_64-linux-gnu/c/pthread.cr index ab943b23587d..33274a6cf51d 100644 --- a/src/lib_c/x86_64-linux-gnu/c/pthread.cr +++ b/src/lib_c/x86_64-linux-gnu/c/pthread.cr @@ -35,4 +35,5 @@ lib LibC fun pthread_mutex_trylock(mutex : PthreadMutexT*) : Int fun pthread_mutex_unlock(mutex : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(PthreadT, Char*) : Int end diff --git a/src/lib_c/x86_64-linux-musl/c/pthread.cr b/src/lib_c/x86_64-linux-musl/c/pthread.cr index c318bb5f4357..6ee1a20d90a6 100644 --- a/src/lib_c/x86_64-linux-musl/c/pthread.cr +++ b/src/lib_c/x86_64-linux-musl/c/pthread.cr @@ -27,4 +27,5 @@ lib LibC fun pthread_mutex_trylock(x0 : PthreadMutexT*) : Int fun pthread_mutex_unlock(x0 : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(PthreadT, Char*) : Int end diff --git a/src/lib_c/x86_64-netbsd/c/pthread.cr b/src/lib_c/x86_64-netbsd/c/pthread.cr index 133d4922c57f..e0f477018777 100644 --- a/src/lib_c/x86_64-netbsd/c/pthread.cr +++ b/src/lib_c/x86_64-netbsd/c/pthread.cr @@ -35,5 +35,6 @@ lib LibC fun pthread_mutex_trylock(x0 : PthreadMutexT*) : Int fun pthread_mutex_unlock(x0 : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_setname_np(PthreadT, Char*, Void*) : Int fun pthread_setspecific(PthreadKeyT, Void*) : Int end diff --git a/src/lib_c/x86_64-openbsd/c/pthread.cr b/src/lib_c/x86_64-openbsd/c/pthread.cr index 788823c40a31..15773336f239 100644 --- a/src/lib_c/x86_64-openbsd/c/pthread.cr +++ b/src/lib_c/x86_64-openbsd/c/pthread.cr @@ -30,6 +30,7 @@ lib LibC fun pthread_mutex_trylock(x0 : PthreadMutexT*) : Int fun pthread_mutex_unlock(x0 : PthreadMutexT*) : Int fun pthread_self : PthreadT + fun pthread_set_name_np(PthreadT, Char*) fun pthread_setspecific(PthreadKeyT, Void*) : Int fun pthread_stackseg_np(PthreadT, StackT*) : Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr index 9c22d4530c81..efa684075162 100644 --- a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr @@ -50,6 +50,9 @@ lib LibC bInheritHandles : BOOL, dwCreationFlags : DWORD, lpEnvironment : Void*, lpCurrentDirectory : LPWSTR, lpStartupInfo : STARTUPINFOW*, lpProcessInformation : PROCESS_INFORMATION*) : BOOL + {% if LibC::WIN32_WINNT >= LibC::WIN32_WINNT_WIN10 %} + fun SetThreadDescription(hThread : HANDLE, lpThreadDescription : LPWSTR) : LONG + {% end %} fun SetThreadStackGuarantee(stackSizeInBytes : DWORD*) : BOOL fun GetProcessTimes(hProcess : HANDLE, lpCreationTime : FILETIME*, lpExitTime : FILETIME*, lpKernelTime : FILETIME*, lpUserTime : FILETIME*) : BOOL From e6d4998b5580456e3b9ecbffb40395f2699f3d69 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 31 Jan 2024 03:13:16 +0800 Subject: [PATCH 0943/1551] Add `ReferenceStorage` for manual allocation of references (#14270) --- .../semantic/reference_storage_spec.cr | 72 +++++++++++++++++++ spec/primitives/reference_spec.cr | 4 +- src/compiler/crystal/codegen/llvm_typer.cr | 4 ++ .../crystal/semantic/top_level_visitor.cr | 70 +++++++++++++++--- src/compiler/crystal/types.cr | 29 +++++++- src/prelude.cr | 1 + src/reference_storage.cr | 55 ++++++++++++++ 7 files changed, 221 insertions(+), 14 deletions(-) create mode 100644 spec/compiler/semantic/reference_storage_spec.cr create mode 100644 src/reference_storage.cr diff --git a/spec/compiler/semantic/reference_storage_spec.cr b/spec/compiler/semantic/reference_storage_spec.cr new file mode 100644 index 000000000000..a0c0f2343260 --- /dev/null +++ b/spec/compiler/semantic/reference_storage_spec.cr @@ -0,0 +1,72 @@ +require "../../spec_helper" + +describe "Semantic: ReferenceStorage" do + it "errors if T is a struct type" do + assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = Foo (T must be a reference type)" + @[Primitive(:ReferenceStorageType)] + struct ReferenceStorage(T) < Value + end + + struct Foo + @x = 1 + end + + ReferenceStorage(Foo) + CRYSTAL + end + + it "errors if T is a value type" do + assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = Int32 (T must be a reference type)" + @[Primitive(:ReferenceStorageType)] + struct ReferenceStorage(T) < Value + end + + ReferenceStorage(Int32) + CRYSTAL + end + + it "errors if T is a union type" do + assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = (Bar | Foo) (T must be a reference type)" + @[Primitive(:ReferenceStorageType)] + struct ReferenceStorage(T) < Value + end + + class Foo + end + + class Bar + end + + ReferenceStorage(Foo | Bar) + CRYSTAL + end + + it "errors if T is a nilable type" do + assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = (Foo | Nil) (T must be a reference type)" + @[Primitive(:ReferenceStorageType)] + struct ReferenceStorage(T) < Value + end + + class Foo + end + + ReferenceStorage(Foo?) + CRYSTAL + end + + it "allows a different name" do + assert_type(<<-CRYSTAL) { types["Foo"].metaclass } + @[Primitive(:ReferenceStorageType)] + struct MyRef(U) < Value + def u + U + end + end + + class Foo + end + + MyRef(Foo).new.u + CRYSTAL + end +end diff --git a/spec/primitives/reference_spec.cr b/spec/primitives/reference_spec.cr index b73553748b65..52f179296ee8 100644 --- a/spec/primitives/reference_spec.cr +++ b/spec/primitives/reference_spec.cr @@ -43,9 +43,7 @@ describe "Primitives: reference" do end it "works when address is on the stack" do - # suitably aligned, sufficient storage for type ID + i64 + ptr - # TODO: use `ReferenceStorage` instead - foo_buffer = uninitialized UInt64[3] + foo_buffer = uninitialized ReferenceStorage(Foo) foo = Foo.pre_initialize(pointerof(foo_buffer)) pointerof(foo_buffer).as(typeof(Foo.crystal_instance_type_id)*).value.should eq(Foo.crystal_instance_type_id) foo.str.should eq("abc") diff --git a/src/compiler/crystal/codegen/llvm_typer.cr b/src/compiler/crystal/codegen/llvm_typer.cr index d20fbd59fa9a..d72a25ecd3cb 100644 --- a/src/compiler/crystal/codegen/llvm_typer.cr +++ b/src/compiler/crystal/codegen/llvm_typer.cr @@ -245,6 +245,10 @@ module Crystal llvm_type(type.remove_alias, wants_size) end + private def create_llvm_type(type : ReferenceStorageType, wants_size) + llvm_struct_type(type.reference_type, wants_size) + end + private def create_llvm_type(type : NonGenericModuleType | GenericClassType, wants_size) # This can only be reached if the module or generic class don't have implementors @llvm_context.int1 diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 9b7eedb2a876..98ceb23df6b3 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -41,6 +41,11 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor @last_doc : String? + # special types recognized for `@[Primitive]` + private enum PrimitiveType + ReferenceStorageType + end + def visit(node : ClassDef) check_outside_exp node, "declare class" @@ -48,6 +53,27 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor annotations = read_annotations + special_type = nil + process_annotations(annotations) do |annotation_type, ann| + case annotation_type + when @program.primitive_annotation + if ann.args.size != 1 + ann.raise "expected Primitive annotation to have one argument" + end + + arg = ann.args.first + unless arg.is_a?(SymbolLiteral) + arg.raise "expected Primitive argument to be a symbol literal" + end + + value = arg.value + special_type = PrimitiveType.parse?(value) + unless special_type + arg.raise "BUG: Unknown primitive type #{value.inspect}" + end + end + end + created_new_type = false if type @@ -70,14 +96,34 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end else created_new_type = true - if type_vars = node.type_vars - type = GenericClassType.new @program, scope, name, nil, type_vars, false - type.splat_index = node.splat_index - else - type = NonGenericClassType.new @program, scope, name, nil, false + case special_type + in Nil + if type_vars = node.type_vars + type = GenericClassType.new @program, scope, name, nil, type_vars, false + type.splat_index = node.splat_index + else + type = NonGenericClassType.new @program, scope, name, nil, false + end + type.abstract = node.abstract? + type.struct = node.struct? + in .reference_storage_type? + type_vars = node.type_vars + case + when !node.struct? + node.raise "BUG: Expected ReferenceStorageType to be a struct type" + when node.abstract? + node.raise "BUG: Expected ReferenceStorageType to be a non-abstract type" + when !type_vars + node.raise "BUG: Expected ReferenceStorageType to be a generic type" + when type_vars.size != 1 + node.raise "BUG: Expected ReferenceStorageType to have a single generic type parameter" + when node.splat_index + node.raise "BUG: Expected ReferenceStorageType to have no splat parameter" + end + type = GenericReferenceStorageType.new @program, scope, name, @program.value, type_vars + type.declare_instance_var("@type_id", @program.int32) + type.can_be_stored = false end - type.abstract = node.abstract? - type.struct = node.struct? end type.private = true if node.visibility.private? @@ -133,6 +179,10 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor if superclass.struct? && !superclass.abstract? node.raise "can't extend non-abstract struct #{superclass}" end + + if type.is_a?(GenericReferenceStorageType) && superclass != @program.value + node.raise "BUG: Expected reference_storage_type to inherit from Value" + end end if created_new_type @@ -375,7 +425,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor process_def_annotations(node, annotations) do |annotation_type, ann| if annotation_type == @program.primitive_annotation - process_primitive_annotation(node, ann) + process_def_primitive_annotation(node, ann) end node.add_annotation(annotation_type, ann) @@ -460,7 +510,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor false end - private def process_primitive_annotation(node, ann) + private def process_def_primitive_annotation(node, ann) if ann.args.size != 1 ann.raise "expected Primitive annotation to have one argument" end @@ -926,7 +976,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor if annotation_type == @program.call_convention_annotation call_convention = parse_call_convention(ann, call_convention) elsif annotation_type == @program.primitive_annotation - process_primitive_annotation(external, ann) + process_def_primitive_annotation(external, ann) else ann.raise "funs can only be annotated with: NoInline, AlwaysInline, Naked, ReturnsTwice, Raises, CallConvention" end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index ad9f3d391fa6..5d903b763050 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -555,7 +555,7 @@ module Crystal program.tuple, program.named_tuple, program.pointer, program.static_array, program.union, program.enum, program.proc, - PrimitiveType + PrimitiveType, GenericReferenceStorageType false else true @@ -2642,6 +2642,33 @@ module Crystal end end + # The non-instantiated ReferenceStorage(T) type. + class GenericReferenceStorageType < GenericClassType + @struct = true + + def new_generic_instance(program, generic_type, type_vars) + type_param = self.type_vars.first + t = type_vars[type_param].type + + unless t.is_a?(TypeParameter) || (t.class? && !t.struct?) + raise TypeException.new "Can't instantiate ReferenceStorage(#{type_param}) with #{type_param} = #{t} (#{type_param} must be a reference type)" + end + + ReferenceStorageType.new program, t, self, type_param + end + end + + class ReferenceStorageType < GenericClassInstanceType + getter reference_type : Type + + def initialize(program, @reference_type, generic_type, type_param) + t_var = Var.new("T", @reference_type) + t_var.bind_to t_var + + super(program, generic_type, program.value, {type_param => t_var} of String => ASTNode) + end + end + # A lib type, like `lib LibC`. class LibType < ModuleType getter link_annotations : Array(LinkAnnotation)? diff --git a/src/prelude.cr b/src/prelude.cr index f06f5bc87015..f84bb86cb3c1 100644 --- a/src/prelude.cr +++ b/src/prelude.cr @@ -65,6 +65,7 @@ require "raise" require "random" require "range" require "reference" +require "reference_storage" require "regex" require "set" {% unless flag?(:wasm32) %} diff --git a/src/reference_storage.cr b/src/reference_storage.cr new file mode 100644 index 000000000000..2ca3edc171aa --- /dev/null +++ b/src/reference_storage.cr @@ -0,0 +1,55 @@ +# `ReferenceStorage(T)` provides the minimum storage for the instance data of +# an object of type `T`. The compiler guarantees that +# `sizeof(ReferenceStorage(T)) == instance_sizeof(T)` and +# `alignof(ReferenceStorage(T)) == instance_alignof(T)` always hold, which means +# `Pointer(ReferenceStorage(T))` and `T` are binary-compatible. +# +# `T` must be a non-union reference type. +# +# WARNING: `ReferenceStorage` is only necessary for manual memory management, +# such as creating instances of `T` with a non-default allocator. Therefore, +# this type is unsafe and no public constructors are defined. +# +# WARNING: `ReferenceStorage` is unsuitable when instances of `T` require more +# than `instance_sizeof(T)` bytes, such as `String` and `Log::Metadata`. +@[Experimental("This type's API is still under development. Join the discussion about custom reference allocation at [#13481](https://github.com/crystal-lang/crystal/issues/13481).")] +@[Primitive(:ReferenceStorageType)] +struct ReferenceStorage(T) < Value + private def initialize + end + + # Returns whether `self` and *other* are bytewise equal. + # + # NOTE: This does not call `T#==`, so it works even if `self` or *other* does + # not represent a valid instance of `T`. If validity is guaranteed, call + # `to_reference == other.to_reference` instead to use `T#==`. + def ==(other : ReferenceStorage(T)) : Bool + to_bytes == other.to_bytes + end + + def ==(other) : Bool + false + end + + def hash(hasher) + to_bytes.hash(hasher) + end + + def to_s(io : IO) : Nil + io << "ReferenceStorage(#<" << T << ":0x" + pointerof(@type_id).address.to_s(io, 16) + io << ">)" + end + + # Returns a `T` whose instance data refers to `self`. + # + # WARNING: The caller is responsible for ensuring that the instance data is + # correctly initialized and outlives the returned `T`. + def to_reference : T + pointerof(@type_id).as(T) + end + + protected def to_bytes + Slice.new(pointerof(@type_id).as(UInt8*), instance_sizeof(T)) + end +end From 9890b17d97a46522c825edb8651607f6f53fd108 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 31 Jan 2024 03:13:31 +0800 Subject: [PATCH 0944/1551] Fix end locations of `Alias` nodes (#14271) --- spec/compiler/parser/parser_spec.cr | 1 + src/compiler/crystal/syntax/parser.cr | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 9f4ec5faccf7..c9a28d87c33f 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2252,6 +2252,7 @@ module Crystal assert_end_location "class Foo; end" assert_end_location "struct Foo; end" assert_end_location "module Foo; end" + assert_end_location "alias Foo = Bar" assert_end_location "->{ }" assert_end_location "macro foo;end" assert_end_location "macro foo; 123; end" diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index e3f4c2605ed6..16a45eaf5e22 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -5854,9 +5854,10 @@ module Crystal next_token_skip_space_or_newline value = parse_bare_proc_type + end_location = value.end_location skip_space - alias_node = Alias.new(name, value) + alias_node = Alias.new(name, value).at_end(end_location) alias_node.doc = doc alias_node end From 14f69e38cadf06ff5594b562d22d3b38689c3d4d Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 31 Jan 2024 12:42:24 +0100 Subject: [PATCH 0945/1551] Interpreter: fix fiber's resumable property (#14252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Johannes Müller --- spec/interpreter_std_spec.cr | 1 + spec/std/fiber_spec.cr | 21 +++++++++++++++++++ .../crystal/interpreter/instructions.cr | 5 +++++ .../crystal/interpreter/interpreter.cr | 11 ++++++++-- .../crystal/interpreter/primitives.cr | 3 +++ src/crystal/interpreter.cr | 5 +++++ src/crystal/scheduler.cr | 8 ------- src/fiber/context.cr | 20 ++++++++++++++++-- src/fiber/context/interpreted.cr | 4 ++-- 9 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 spec/std/fiber_spec.cr diff --git a/spec/interpreter_std_spec.cr b/spec/interpreter_std_spec.cr index 3cdcc55cbd61..8638b0a83f7f 100644 --- a/spec/interpreter_std_spec.cr +++ b/spec/interpreter_std_spec.cr @@ -72,6 +72,7 @@ require "./std/env_spec.cr" require "./std/errno_spec.cr" # require "./std/exception/call_stack_spec.cr" (failed to run) # require "./std/exception_spec.cr" (failed to run) +require "./std/fiber_spec.cr" require "./std/file_spec.cr" require "./std/file/tempfile_spec.cr" require "./std/file_utils_spec.cr" diff --git a/spec/std/fiber_spec.cr b/spec/std/fiber_spec.cr new file mode 100644 index 000000000000..5c85d1a7475d --- /dev/null +++ b/spec/std/fiber_spec.cr @@ -0,0 +1,21 @@ +require "spec" + +describe Fiber do + it "#resumable?" do + done = false + resumable = nil + + fiber = spawn do + resumable = Fiber.current.resumable? + done = true + end + + fiber.resumable?.should be_true + + until done + Fiber.yield + end + + resumable.should be_false + end +end diff --git a/src/compiler/crystal/interpreter/instructions.cr b/src/compiler/crystal/interpreter/instructions.cr index 67966277bf15..f2b8abcb9a51 100644 --- a/src/compiler/crystal/interpreter/instructions.cr +++ b/src/compiler/crystal/interpreter/instructions.cr @@ -1649,6 +1649,11 @@ require "./repl" pop_values: [current_context : Void*, new_context : Void*], code: swapcontext(current_context, new_context), }, + interpreter_fiber_resumable: { + pop_values: [context : Void*], + push: true, + code: fiber_resumable(context), + }, {% if flag?(:bits64) %} interpreter_intrinsics_memcpy: { diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index 608fb6d85d1a..eca73ecae6bc 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -1154,6 +1154,7 @@ class Crystal::Repl::Interpreter nil end end + spawned_fiber.@context.resumable = 1 spawned_fiber.as(Void*) end @@ -1161,11 +1162,17 @@ class Crystal::Repl::Interpreter # current_fiber = current_context.as(Fiber*).value new_fiber = new_context.as(Fiber*).value - # We directly resume the next fiber. - # TODO: is this okay? We totally ignore the scheduler here! + # delegates the context switch to the interpreter's scheduler, so we update + # the current fiber reference, set the GC stack bottom, and so on (aka + # there's more to switching context than `Fiber.swapcontext`): new_fiber.resume end + private def fiber_resumable(context : Void*) : LibC::Long + fiber = context.as(Fiber*).value + fiber.@context.resumable + end + private def pry(ip, instructions, stack_bottom, stack) offset = (ip - instructions.instructions.to_unsafe).to_i32 node = instructions.nodes[offset]? diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr index 1d50568c5d62..968361fb6c1d 100644 --- a/src/compiler/crystal/interpreter/primitives.cr +++ b/src/compiler/crystal/interpreter/primitives.cr @@ -402,6 +402,9 @@ class Crystal::Repl::Compiler when "interpreter_fiber_swapcontext" accept_call_args(node) interpreter_fiber_swapcontext(node: node) + when "interpreter_fiber_resumable" + accept_call_args(node) + interpreter_fiber_resumable(node: node) when "interpreter_intrinsics_memcpy" accept_call_args(node) interpreter_intrinsics_memcpy(node: node) diff --git a/src/crystal/interpreter.cr b/src/crystal/interpreter.cr index 2a9a6b23c5f8..d3b3589d50cb 100644 --- a/src/crystal/interpreter.cr +++ b/src/crystal/interpreter.cr @@ -19,5 +19,10 @@ module Crystal @[Primitive(:interpreter_spawn)] def self.spawn(fiber : Fiber, fiber_main : Void*) : Void* end + + # Returns the resumable value from the interpreter's fiber. + @[Primitive(:interpreter_fiber_resumable)] + def self.fiber_resumable(context) : LibC::Long + end end end diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index 101a287d23bb..1728a9b7f335 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -125,14 +125,6 @@ class Crystal::Scheduler {% end %} current, @current = @current, fiber - - {% if flag?(:interpreted) %} - # TODO: ideally we could set this in the interpreter if the - # @context had a pointer back to the fiber. - # I also wonder why this isn't done always like that instead of in asm. - current.@context.resumable = 1 - {% end %} - Fiber.swapcontext(pointerof(current.@context), pointerof(fiber.@context)) {% if flag?(:preview_mt) %} diff --git a/src/fiber/context.cr b/src/fiber/context.cr index 28b54458fe66..eab06d886de6 100644 --- a/src/fiber/context.cr +++ b/src/fiber/context.cr @@ -7,10 +7,26 @@ class Fiber @[Extern] struct Context property stack_top : Void* - property resumable : LibC::Long + + {% if flag?(:interpreted) %} + # In interpreted mode, the interpreted fibers will be backed by a real + # fiber run by the interpreter. The fiber context is thus fake. + # + # The `stack_top` property is actually a pointer to the real Fiber + # running in the interpreter. + # + # The `resumable` property is also delegated to the real fiber. Only the + # getter is defined (so we know the real state of the fiber); we don't + # declare a setter because only the interpreter can manipulate it (in the + # `makecontext` and `swapcontext` primitives). + def resumable : LibC::Long + Crystal::Interpreter.fiber_resumable(pointerof(@stack_top)) + end + {% else %} + property resumable : LibC::Long = 0 + {% end %} def initialize(@stack_top = Pointer(Void).null) - @resumable = 0 end end diff --git a/src/fiber/context/interpreted.cr b/src/fiber/context/interpreted.cr index 55b9354a1a57..900ad6f3111c 100644 --- a/src/fiber/context/interpreted.cr +++ b/src/fiber/context/interpreted.cr @@ -5,9 +5,9 @@ require "crystal/interpreter" class Fiber # :nodoc: def makecontext(stack_ptr, fiber_main) : Nil - # In interpreted mode the stack_top variable actually points to a fiber + # In interpreted mode the stack_top variable actually points to the actual + # fiber running on the interpreter @context.stack_top = Crystal::Interpreter.spawn(self, fiber_main.pointer) - @context.resumable = 1 end # :nodoc: From 202c655008e9b191aca30701ef678e54503ef1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 31 Jan 2024 13:20:49 +0100 Subject: [PATCH 0946/1551] Add `crystal tool flags` (#14234) --- etc/completion.bash | 2 +- etc/completion.fish | 4 +- etc/completion.zsh | 7 ++ man/crystal.1 | 4 +- spec/compiler/crystal/tools/flags_spec.cr | 44 +++++++++ src/compiler/crystal/command.cr | 4 + src/compiler/crystal/tools/flags.cr | 104 ++++++++++++++++++++++ 7 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 spec/compiler/crystal/tools/flags_spec.cr create mode 100644 src/compiler/crystal/tools/flags.cr diff --git a/etc/completion.bash b/etc/completion.bash index d8731c65bff7..9263289b5b4e 100644 --- a/etc/completion.bash +++ b/etc/completion.bash @@ -66,7 +66,7 @@ _crystal() _crystal_compgen_options "${opts}" "${cur}" else if [[ "${prev}" == "tool" ]] ; then - local subcommands="context dependencies format hierarchy implementations types" + local subcommands="context dependencies flags format hierarchy implementations types" _crystal_compgen_options "${subcommands}" "${cur}" else _crystal_compgen_sources "${cur}" diff --git a/etc/completion.fish b/etc/completion.fish index 150dd37108d8..64fc6a97b45a 100644 --- a/etc/completion.fish +++ b/etc/completion.fish @@ -1,5 +1,5 @@ set -l crystal_commands init build clear_cache docs env eval i interactive play run spec tool help version -set -l tool_subcommands context expand format hierarchy implementations types +set -l tool_subcommands context expand flags format hierarchy implementations types complete -c crystal -s h -l help -d "Show help" -x @@ -173,6 +173,8 @@ complete -c crystal -n "__fish_seen_subcommand_from expand" -s p -l progress -d complete -c crystal -n "__fish_seen_subcommand_from expand" -s t -l time -d "Enable execution time output" complete -c crystal -n "__fish_seen_subcommand_from expand" -l stdin-filename -d "Source file name to be read from STDIN" +complete -c crystal -n "__fish_seen_subcommand_from tool; and not __fish_seen_subcommand_from $tool_subcommands" -a "flags" -d "print all macro 'flag?' values" -x + complete -c crystal -n "__fish_seen_subcommand_from tool; and not __fish_seen_subcommand_from $tool_subcommands" -a "format" -d "format project, directories and/or files" -x complete -c crystal -n "__fish_seen_subcommand_from format" -l check -d "Checks that formatting code produces no changes" complete -c crystal -n "__fish_seen_subcommand_from format" -s i -l include -d "Include path" diff --git a/etc/completion.zsh b/etc/completion.zsh index bf57d80208df..ffa12798ca18 100644 --- a/etc/completion.zsh +++ b/etc/completion.zsh @@ -165,6 +165,7 @@ _crystal-tool() { "context:show context for given location" "dependencies:show tree of required source files" "expand:show macro expansion for given location" + "flags:print all macro 'flag?' values" "format:format project, directories and/or files" "hierarchy:show type hierarchy" "implementations:show implementations for given call in location" @@ -211,6 +212,12 @@ _crystal-tool() { $cursor_args ;; + (flags) + _arguments \ + $programfile \ + $help_args + ;; + (format) _arguments \ $programfile \ diff --git a/man/crystal.1 b/man/crystal.1 index ef23beac77c3..15fed461c26c 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -369,7 +369,7 @@ Disable colored output. .Op -- .Op arguments .Pp -Run a tool. The available tools are: context, dependencies, format, hierarchy, implementations, and types. +Run a tool. The available tools are: context, dependencies, flags, format, hierarchy, implementations, and types. .Pp Tools: .Bl -tag -offset indent @@ -398,6 +398,8 @@ Show skipped and heads of filtered paths .El .It Cm expand Show macro expansion for given location. +.It Cm flags +Print all macro 'flag?' values .It Cm format Format project, directories and/or files with the coding style used in the standard library. You can use the .Fl -check diff --git a/spec/compiler/crystal/tools/flags_spec.cr b/spec/compiler/crystal/tools/flags_spec.cr new file mode 100644 index 000000000000..c295439e0547 --- /dev/null +++ b/spec/compiler/crystal/tools/flags_spec.cr @@ -0,0 +1,44 @@ +require "../../../spec_helper" +include Crystal + +private def parse_flags(source) + Crystal::Command::FlagsVisitor.new.tap do |visitor| + Parser.parse(source).accept(visitor) + end +end + +describe Crystal::Command::FlagsVisitor do + it "different flags" do + visitor = parse_flags <<-CRYSTAL + {% + flag?(:foo) + flag?("bar") + flag?(1) + flag?(true) + %} + CRYSTAL + visitor.flag_names.should eq %w[1 bar foo true] + end + + it "unique flags" do + visitor = parse_flags <<-CRYSTAL + {% + flag?(:foo) + flag?("foo") + flag?(:foo) + %} + CRYSTAL + visitor.flag_names.should eq %w[foo] + end + + it "only macro" do + visitor = parse_flags <<-CRYSTAL + flag?(:flag) + f.flag?(:foo) + F.flag?(:bar) + {% f.flag?(:baz) %} + {% f.flag?(:qux, other: true) %} + CRYSTAL + visitor.flag_names.should eq %w[] + end +end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 18def74ebbd0..178a307a54b8 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -41,6 +41,7 @@ class Crystal::Command Tool: context show context for given location expand show macro expansion for given location + flags print all macro `flag?` values format format project, directories and/or files hierarchy show type hierarchy dependencies show file dependency tree @@ -177,6 +178,9 @@ class Crystal::Command when "format".starts_with?(tool) options.shift format + when "flags" == tool + options.shift + flags when "expand".starts_with?(tool) options.shift expand diff --git a/src/compiler/crystal/tools/flags.cr b/src/compiler/crystal/tools/flags.cr new file mode 100644 index 000000000000..17bb15007021 --- /dev/null +++ b/src/compiler/crystal/tools/flags.cr @@ -0,0 +1,104 @@ +require "colorize" +require "../syntax/ast" + +class Crystal::Command + private def flags + OptionParser.parse(@options) do |opts| + opts.banner = "Usage: crystal tool flags [path...]\n\nOptions:" + + opts.on("-h", "--help", "Show this message") do + puts opts + exit + end + + opts.on("--no-color", "Disable colored output") do + @color = false + end + end + + visitor = FlagsVisitor.new + find_sources(options) do |source| + Parser.parse(source.code).accept(visitor) + end + visitor.flag_names.each do |flag| + puts flag + end + end + + def find_sources( + paths : Array(String), + stdin : IO = STDIN, + & : Compiler::Source -> + ) : Nil + stdin_content = nil + paths.each do |path| + if path == "-" + stdin_content ||= stdin.gets_to_end + yield Compiler::Source.new(path, stdin_content) + elsif File.file?(path) + yield Compiler::Source.new(path, File.read(path)) + elsif Dir.exists?(path) + Dir.glob(::Path[path].to_posix.join("**/*.cr")) do |dir_path| + if File.file?(dir_path) + yield Compiler::Source.new(path, File.read(dir_path)) + end + end + else + Crystal.error "file or directory does not exist: #{path}", @color, leading_error: false + end + end + end + + class FlagsVisitor < Visitor + @in_macro_expression = false + + getter all_flags = [] of ASTNode + + def initialize(@flag_name : String = "flag?") + end + + def flag_names + all_flags.map { |flag| string_flag(flag) }.uniq!.sort! + end + + private def string_flag(node) + case node + when StringLiteral, SymbolLiteral + node.value + else + node.to_s + end + end + + def visit(node) + true + end + + def visit(node : Crystal::MacroExpression | Crystal::MacroIf | Crystal::MacroFor) + @in_macro_expression = true + + true + end + + def end_visit(node : Crystal::MacroExpression | Crystal::MacroIf | Crystal::MacroFor) + @in_macro_expression = false + end + + def visit(node : Crystal::Call) + check_call(node) + true + end + + private def check_call(node) + return unless @in_macro_expression + return unless node.name == @flag_name + return unless node.obj.nil? && node.block.nil? && node.named_args.nil? + + args = node.args + return unless args.size == 1 + arg = args[0] + + all_flags << arg + end + end +end From 2f48d297feb527b1a29a3108d05da98f33a5a434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 31 Jan 2024 21:04:09 +0100 Subject: [PATCH 0947/1551] Document builtin constants (#14276) --- src/compiler/crystal/program.cr | 91 ++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 25 deletions(-) diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 936fc6a0a270..675202ee0144 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -275,36 +275,77 @@ module Crystal # Defines a predefined constant in the Crystal module, such as BUILD_DATE and VERSION. private def define_crystal_constants if build_commit = Crystal::Config.build_commit - define_crystal_string_constant "BUILD_COMMIT", build_commit + build_commit_const = define_crystal_string_constant "BUILD_COMMIT", build_commit else - define_crystal_nil_constant "BUILD_COMMIT" + build_commit_const = define_crystal_nil_constant "BUILD_COMMIT" end - - define_crystal_string_constant "BUILD_DATE", Crystal::Config.date - define_crystal_string_constant "CACHE_DIR", CacheDir.instance.dir - define_crystal_string_constant "DEFAULT_PATH", Crystal::Config.path - define_crystal_string_constant "DESCRIPTION", Crystal::Config.description - define_crystal_string_constant "PATH", Crystal::CrystalPath.default_path - define_crystal_string_constant "LIBRARY_PATH", Crystal::CrystalLibraryPath.default_path - define_crystal_string_constant "LIBRARY_RPATH", Crystal::CrystalLibraryPath.default_rpath - define_crystal_string_constant "VERSION", Crystal::Config.version - define_crystal_string_constant "LLVM_VERSION", Crystal::Config.llvm_version - define_crystal_string_constant "HOST_TRIPLE", Crystal::Config.host_target.to_s - define_crystal_string_constant "TARGET_TRIPLE", Crystal::Config.host_target.to_s - end - - private def define_crystal_string_constant(name, value) - define_crystal_constant name, StringLiteral.new(value).tap(&.set_type(string)) - end - - private def define_crystal_nil_constant(name) - define_crystal_constant name, NilLiteral.new.tap(&.set_type(self.nil)) - end - - private def define_crystal_constant(name, value) + build_commit_const.doc = <<-MD if wants_doc? + The build commit identifier of the Crystal compiler. + MD + + define_crystal_string_constant "BUILD_DATE", Crystal::Config.date, <<-MD + The build date of the Crystal compiler. + MD + define_crystal_string_constant "CACHE_DIR", CacheDir.instance.dir, <<-MD + The cache directory configured for the Crystal compiler. + + The value is defined by the environment variable `CRYSTAL_CACHE_DIR` and + defaults to the user's configured cache directory. + MD + define_crystal_string_constant "DEFAULT_PATH", Crystal::Config.path, <<-MD + The default Crystal path configured in the compiler. This value is baked + into the compiler and usually points to the accompanying version of the + standard library. + MD + define_crystal_string_constant "DESCRIPTION", Crystal::Config.description, <<-MD + Full version information of the Crystal compiler. Equivalent to `crystal --version`. + MD + define_crystal_string_constant "PATH", Crystal::CrystalPath.default_path, <<-MD + Colon-separated paths where the compiler searches for required source files. + + The value is defined by the environment variable `CRYSTAL_PATH` + and defaults to `DEFAULT_PATH`. + MD + define_crystal_string_constant "LIBRARY_PATH", Crystal::CrystalLibraryPath.default_path, <<-MD + Colon-separated paths where the compiler searches for (binary) libraries. + + The value is defined by the environment variables `CRYSTAL_LIBRARY_PATH`. + MD + define_crystal_string_constant "LIBRARY_RPATH", Crystal::CrystalLibraryPath.default_rpath, <<-MD + Colon-separated paths where the loader searches for dynamic libraries at runtime. + + The value is defined by the environment variables `CRYSTAL_LIBRARY_RPATH`. + MD + define_crystal_string_constant "VERSION", Crystal::Config.version, <<-MD + The version of the Crystal compiler. + MD + define_crystal_string_constant "LLVM_VERSION", Crystal::Config.llvm_version, <<-MD + The version of LLVM used by the Crystal compiler. + MD + define_crystal_string_constant "HOST_TRIPLE", Crystal::Config.host_target.to_s, <<-MD + The LLVM target triple of the host system (the machine that the compiler runs on). + MD + define_crystal_string_constant "TARGET_TRIPLE", Crystal::Config.host_target.to_s, <<-MD + The LLVM target triple of the target system (the machine that the compiler builds for). + MD + end + + private def define_crystal_string_constant(name, value, doc = nil) + define_crystal_constant name, StringLiteral.new(value).tap(&.set_type(string)), doc + end + + private def define_crystal_nil_constant(name, doc = nil) + define_crystal_constant name, NilLiteral.new.tap(&.set_type(self.nil)), doc + end + + private def define_crystal_constant(name, value, doc = nil) : Const crystal.types[name] = const = Const.new self, crystal, name, value const.no_init_flag = true + if doc && wants_doc? + const.doc = doc + end predefined_constants << const + const end property(target_machine : LLVM::TargetMachine) { codegen_target.to_target_machine } From b655c0f43d57e889ff3767635733396bd67455be Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 2 Feb 2024 21:22:39 +0800 Subject: [PATCH 0948/1551] Respect alignments above `alignof(Void*)` inside union values (#14279) --- spec/compiler/codegen/cast_spec.cr | 27 +++++++++ spec/compiler/codegen/sizeof_spec.cr | 10 ++++ src/compiler/crystal/codegen/codegen.cr | 4 +- src/compiler/crystal/codegen/unions.cr | 78 ++++++++++++++++++------- 4 files changed, 96 insertions(+), 23 deletions(-) diff --git a/spec/compiler/codegen/cast_spec.cr b/spec/compiler/codegen/cast_spec.cr index 32b265ea0415..fbe6cd78ac87 100644 --- a/spec/compiler/codegen/cast_spec.cr +++ b/spec/compiler/codegen/cast_spec.cr @@ -77,6 +77,33 @@ describe "Code gen: cast" do )).to_b.should be_true end + it "upcasts from union to union with different alignment" do + run(<<-CRYSTAL).to_i.should eq(1) + require "prelude" + + a = 1 || 2_i64 + a.as(Int32 | Int64 | Int128) + CRYSTAL + end + + it "downcasts from union to union with different alignment" do + run(<<-CRYSTAL).to_i.should eq(1) + require "prelude" + + a = 1 || 2_i64 || 3_i128 + a.as(Int32 | Int64) + CRYSTAL + end + + it "sidecasts from union to union with different alignment" do + run(<<-CRYSTAL).to_i.should eq(1) + require "prelude" + + a = 1 || 2_i64 + a.as(Int32 | Int128) + CRYSTAL + end + it "casts from virtual to single type" do run(%( require "prelude" diff --git a/spec/compiler/codegen/sizeof_spec.cr b/spec/compiler/codegen/sizeof_spec.cr index 8139db8bc426..e90553d0b3c1 100644 --- a/spec/compiler/codegen/sizeof_spec.cr +++ b/spec/compiler/codegen/sizeof_spec.cr @@ -279,6 +279,16 @@ describe "Code gen: sizeof" do alignof(Foo) CRYSTAL end + + it "gets alignof union" do + run("alignof(Int32 | Int8)").to_i.should eq(8) + run("alignof(Int32 | Int64)").to_i.should eq(8) + end + + it "alignof mixed union is not less than alignof its variant types" do + # NOTE: `alignof(Int128) == 16` is not guaranteed + run("alignof(Int32 | Int128) >= alignof(Int128)").to_b.should be_true + end end describe "instance_alignof" do diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index e7128302a66e..441f3cfa09ae 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -2253,11 +2253,11 @@ module Crystal res end - def memcpy(dest, src, len, align, volatile) + def memcpy(dest, src, len, align, volatile, *, src_align = align) res = call c_memcpy_fun, [dest, src, len, volatile] LibLLVM.set_instr_param_alignment(res, 1, align) - LibLLVM.set_instr_param_alignment(res, 2, align) + LibLLVM.set_instr_param_alignment(res, 2, src_align) res end diff --git a/src/compiler/crystal/codegen/unions.cr b/src/compiler/crystal/codegen/unions.cr index 1f29763d504a..7ac58ff84887 100644 --- a/src/compiler/crystal/codegen/unions.cr +++ b/src/compiler/crystal/codegen/unions.cr @@ -28,20 +28,19 @@ module Crystal @structs[llvm_name] = a_struct end - max_size = 0 + max_size = 0_u64 + max_alignment = pointer_size.to_u32! + type.expand_union_types.each do |subtype| unless subtype.void? - size = size_of(llvm_type(subtype, wants_size: true)) - max_size = size if size > max_size + llvm_type = llvm_type(subtype, wants_size: true) + max_size = {size_of(llvm_type), max_size}.max + max_alignment = {align_of(llvm_type), max_alignment}.max end end - max_size /= pointer_size.to_f - max_size = max_size.ceil.to_i - - max_size = 1 if max_size == 0 - - llvm_value_type = size_t.array(max_size) + value_size = {(max_size + (max_alignment - 1)) // max_alignment, 1_u64}.max + llvm_value_type = @llvm_context.int(max_alignment * 8).array(value_size) [@llvm_context.int32, llvm_value_type] end @@ -109,23 +108,60 @@ module Crystal store type_id(@program.void), union_type_id(struct_type, union_pointer) end - def assign_distinct_union_types(target_pointer, target_type, value_type, value) + # this is needed if `union_type` and `value_type` have different alignments, + # i.e. their `#union_value`s do not have the same offsets + def store_union_in_union(union_type, union_pointer, value_type, value) + to_llvm_type = llvm_type(union_type) + from_llvm_type = llvm_type(value_type) + union_value_type = from_llvm_type.struct_element_types[1] + + store type_id(value, value_type), union_type_id(to_llvm_type, union_pointer) + + size = @llvm_typer.size_of(union_value_type) + size = @program.bits64? ? int64(size) : int32(size) + memcpy( + cast_to_void_pointer(union_value(to_llvm_type, union_pointer)), + cast_to_void_pointer(union_value(from_llvm_type, value)), + size, + align: @llvm_typer.align_of(to_llvm_type.struct_element_types[1]), + src_align: @llvm_typer.align_of(union_value_type), + volatile: int1(0), + ) + end + + def assign_distinct_union_types(to_pointer, to_type, from_type, from_pointer) # If we have: - # - target_pointer: Pointer(A | B | C) - # - target_type: A | B | C - # - value_type: A | B - # - value: Pointer(A | B) + # - to_pointer: Pointer(A | B | C) + # - to_type: A | B | C + # - from_type: A | B + # - from_pointer: Pointer(A | B) # - # Then we: - # - load the value, we get A | B - # - cast the target pointer to Pointer(A | B) - # - store the A | B from the first pointer into the casted target pointer - casted_target_pointer = cast_to_pointer target_pointer, value_type - store load(llvm_type(value_type), value), casted_target_pointer + # Then it might happen that from_type and to_type have the same alignment. + # In this case, the two pointers are interchangeable, so we can simply: + if align_of(to_type) == align_of(from_type) + # - load the value, we get A | B + # - cast the target pointer to Pointer(A | B) + # - store the A | B from the value pointer into the casted target pointer + casted_target_pointer = cast_to_pointer to_pointer, from_type + store load(llvm_type(from_type), from_pointer), casted_target_pointer + else + # Otherwise, the type ID and the value must be stored separately + store_union_in_union to_type, to_pointer, from_type, from_pointer + end end def downcast_distinct_union_types(value, to_type : MixedUnionType, from_type : MixedUnionType) - cast_to_pointer value, to_type + # If from_type and to_type have the same alignment, we don't need a + # separate value; just cast the larger value pointer to the smaller one + if align_of(to_type) == align_of(from_type) + cast_to_pointer value, to_type + else + # This is the same as upcasting and we need that separate, newly aligned + # union value + target_pointer = alloca llvm_type(to_type) + store_union_in_union to_type, target_pointer, from_type, value + target_pointer + end end def upcast_distinct_union_types(value, to_type : MixedUnionType, from_type : MixedUnionType) From 3ba4291b0a0b8b7ae1b85cc0959d44d9c0a4d95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 2 Feb 2024 14:22:54 +0100 Subject: [PATCH 0949/1551] Fix format for `asm` with comments (#14278) Co-authored-by: Sijawusz Pur Rahnama --- spec/compiler/formatter/formatter_spec.cr | 38 +++++++++++++++++++++++ src/compiler/crystal/tools/formatter.cr | 19 +++++++++--- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index ce2c7e72364c..df25062a0931 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1695,6 +1695,44 @@ describe Crystal::Formatter do assert_format %(asm("a" : "b"(c) : "d"(e)\n : "f",\n "g")) assert_format %(asm("a" ::: "a"\n : "volatile",\n "intel")) + assert_format <<-CRYSTAL, <<-CRYSTAL + asm( + # foo + "nop" + # bar + ) + CRYSTAL + asm( + # foo + "nop" + # bar + ) + CRYSTAL + + assert_format <<-CRYSTAL, <<-CRYSTAL + asm( + # the assembly template string, following the + # syntax for LLVM's integrated assembler + "nop" : # output operands + "=r"(foo), "=r"(bar) : # input operands + "r"(1), "r"(baz) : # names of clobbered registers + "eax", "memory" : # optional flags, corresponding to the LLVM IR + # sideeffect / alignstack / inteldialect / unwind attributes + "volatile", "alignstack", "intel", "unwind" + ) + CRYSTAL + asm( + # the assembly template string, following the + # syntax for LLVM's integrated assembler + "nop" : # output operands + "=r"(foo), "=r"(bar) : # input operands + "r"(1), "r"(baz) : # names of clobbered registers + "eax", "memory" : # optional flags, corresponding to the LLVM IR + # sideeffect / alignstack / inteldialect / unwind attributes + "volatile", "alignstack", "intel", "unwind" + ) + CRYSTAL + assert_format "1 # foo\n1234 # bar", "1 # foo\n1234 # bar" assert_format "1234 # foo\n1 # bar", "1234 # foo\n1 # bar" assert_format "1#foo", "1 # foo" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index a18634fe8232..f7ebf63be7d2 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -4370,6 +4370,7 @@ module Crystal skip_space if @token.type.newline? + @indent += 2 consume_newlines has_newlines = true end @@ -4378,7 +4379,7 @@ module Crystal string = StringLiteral.new(node.text) if has_newlines - write_indent(@indent + 2, string) + write_indent(@indent, string) else indent(@column, string) end @@ -4386,8 +4387,8 @@ module Crystal skip_space if @token.type.newline? + consume_newlines if node.outputs || node.inputs - consume_newlines column += 4 write_indent(column) end @@ -4419,7 +4420,8 @@ module Crystal write_token :OP_COLON part_index += 1 end - skip_space_or_newline + skip_space + consume_newlines case part_index when 1 @@ -4457,7 +4459,9 @@ module Crystal skip_space_or_newline if has_newlines - write_line + @indent -= 2 + + write_line unless @wrote_newline write_indent end @@ -4480,7 +4484,12 @@ module Crystal end def visit_asm_parts(parts, colon_column, &) : Nil - write " " + if @wrote_newline + write_indent + else + write " " + end + column = @column parts.each_with_index do |part, i| From 3def4de48f34a95ccf02afede1372b0c2277a5a4 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 2 Feb 2024 05:23:06 -0800 Subject: [PATCH 0950/1551] fix: `build --no-codegen` output file name error (#14239) --- src/compiler/crystal/command.cr | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 178a307a54b8..b51f9405c4e6 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -372,6 +372,7 @@ class Crystal::Command allowed_formats = ["text", "json"]) compiler = new_compiler compiler.progress_tracker = @progress_tracker + compiler.no_codegen = no_codegen link_flags = [] of String filenames = [] of String has_stdin_filename = false @@ -608,7 +609,7 @@ class Crystal::Command output_filename = "#{::Path[first_filename].stem}#{output_extension}" # Check if we'll overwrite the main source file - if !no_codegen && !run && first_filename == File.expand_path(output_filename) + if !compiler.no_codegen? && !run && first_filename == File.expand_path(output_filename) error "compilation will overwrite source file '#{Crystal.relative_filename(first_filename)}', either change its extension to '.cr' or specify an output file with '-o'" end end @@ -620,7 +621,7 @@ class Crystal::Command error "maximum number of threads cannot be lower than 1" if compiler.n_threads < 1 - if !no_codegen && !run && Dir.exists?(output_filename) + if !compiler.no_codegen? && !run && Dir.exists?(output_filename) error "can't use `#{output_filename}` as output filename because it's a directory" end @@ -628,7 +629,7 @@ class Crystal::Command emit_base_filename = ::Path[sources.first.filename].stem end - combine_rpath = run && !no_codegen + combine_rpath = run && !compiler.no_codegen? @config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format.not_nil!, combine_rpath, includes, excludes, verbose, check, tallies From 54f185d0dffa48535f9975641261d5126b578964 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 3 Feb 2024 06:09:16 +0800 Subject: [PATCH 0951/1551] Allow calling `#[]=` with a block using method syntax (#14161) --- spec/compiler/formatter/formatter_spec.cr | 7 +++++++ spec/compiler/parser/parser_spec.cr | 6 +++--- src/compiler/crystal/syntax/parser.cr | 6 ------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index df25062a0931..bd992b78fa4a 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1999,6 +1999,13 @@ describe Crystal::Formatter do assert_format "foo.[] = 1" assert_format "foo.[1, 2] = 3" + %w(<= == >= != []= ===).each do |operator| + assert_format "1.#{operator} { 3 }" + assert_format "1.#{operator}() { 3 }" + assert_format "1.#{operator}(2) { 3 }" + assert_format "1.#{operator} do\nend" + end + assert_format "@foo : Int32 # comment\n\ndef foo\nend" assert_format "getter foo # comment\n\ndef foo\nend" assert_format "getter foo : Int32 # comment\n\ndef foo\nend" diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index c9a28d87c33f..ea230d4e36ac 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -290,8 +290,6 @@ module Crystal assert_syntax_error "def foo=(*args); end", "setter method 'foo=' cannot have more than one parameter" assert_syntax_error "def foo=(**kwargs); end", "setter method 'foo=' cannot have more than one parameter" assert_syntax_error "def foo=(&block); end", "setter method 'foo=' cannot have a block" - assert_syntax_error "f.[]= do |a| end", "setter method '[]=' cannot be called with a block" - assert_syntax_error "f.[]= { |bar| }", "setter method '[]=' cannot be called with a block" # #10397 %w(<= >= == != []= ===).each do |operator| @@ -630,10 +628,12 @@ module Crystal it_parses "->Foo.#{op}(Int32)", ProcPointer.new("Foo".path, op, ["Int32".path] of ASTNode) end - ["bar", "+", "-", "*", "/", "<", "<=", "==", ">", ">=", "%", "|", "&", "^", "**", "===", "=~", "!~"].each do |name| + ["bar", "+", "-", "*", "/", "<", "<=", "==", ">", ">=", "%", "|", "&", "^", "**", "===", "=~", "!=", "[]=", "!~"].each do |name| it_parses "foo.#{name}", Call.new("foo".call, name) it_parses "foo.#{name} 1, 2", Call.new("foo".call, name, 1.int32, 2.int32) it_parses "foo.#{name}(1, 2)", Call.new("foo".call, name, 1.int32, 2.int32) + it_parses "foo.#{name}(1, 2) { 3 }", Call.new("foo".call, name, args: [1.int32, 2.int32] of ASTNode, block: Block.new(body: 3.int32)) + it_parses "foo.#{name} do end", Call.new("foo".call, name, block: Block.new) end ["+", "-", "*", "/", "//", "%", "|", "&", "^", "**", "<<", ">>", "&+", "&-", "&*"].each do |op| diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 16a45eaf5e22..5d80b28ed20b 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -788,12 +788,6 @@ module Crystal end block = parse_block(block, stop_on_do: @stop_on_do) - if block || block_arg - if name == "[]=" - raise "setter method '[]=' cannot be called with a block" - end - end - atomic = Call.new atomic, name, (args || [] of ASTNode), block, block_arg, named_args atomic.has_parentheses = has_parentheses atomic.name_location = name_location From 2bce8843d8e3d82549e675519b580ab95ae1c8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 2 Feb 2024 23:09:35 +0100 Subject: [PATCH 0952/1551] Drop pinning Dwarf version 2 for android (#14243) --- src/compiler/crystal/codegen/debug.cr | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index 58d139ef2b3c..72555d074bb0 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -47,14 +47,6 @@ module Crystal "CodeView", mod.context.int32.const_int(1) ) - elsif @program.has_flag?("osx") || @program.has_flag?("android") - # DebugInfo generation in LLVM by default uses a higher version of dwarf - # than OS X currently understands. Android has the same problem. - mod.add_flag( - LibLLVM::ModuleFlagBehavior::Warning, - "Dwarf Version", - mod.context.int32.const_int(2) - ) end mod.add_flag( From 592051e89ff95211f584c9619dc7104ad77e6ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 2 Feb 2024 23:09:47 +0100 Subject: [PATCH 0953/1551] [CI] Upgrade `resource_class` for `test_preview_mt` (#14274) --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2eef408ee4ad..8c07a1005b70 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -116,6 +116,7 @@ jobs: test_preview_mt: machine: image: ubuntu-2004:202201-02 + resource_class: large environment: <<: *env TRAVIS_OS_NAME: linux From c67883c060dbb44b8b7365a2d028a0eb21636429 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 5 Feb 2024 05:16:12 +0800 Subject: [PATCH 0954/1551] Support LLVM 18.1 (#14277) --- .github/workflows/win.yml | 27 +++++---------------------- etc/win-ci/build-llvm.ps1 | 2 +- src/llvm/ext/llvm-versions.txt | 2 +- src/llvm/lib_llvm.cr | 2 +- 4 files changed, 8 insertions(+), 25 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 9fce9f93aa44..3ef4cccb8739 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -7,7 +7,7 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: - CI_LLVM_VERSION: "17.0.2" + CI_LLVM_VERSION: "18.1.0-rc1" jobs: x86_64-windows-libs: @@ -177,30 +177,13 @@ jobs: path: llvm key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc - - name: Download LLVM - if: steps.cache-llvm-libs.outputs.cache-hit != 'true' - run: | - iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.CI_LLVM_VERSION }}/llvm-${{ env.CI_LLVM_VERSION }}.src.tar.xz -OutFile llvm.tar.xz - (Get-FileHash -Algorithm SHA256 .\llvm.tar.xz).hash -eq "D820E63BC3A6F4F833EC69A1EF49A2E81992E90BC23989F98946914B061AB6C7" - 7z x llvm.tar.xz - 7z x llvm.tar - mv llvm-* llvm-src - - - name: Download LLVM's CMake files - if: steps.cache-llvm-libs.outputs.cache-hit != 'true' - run: | - iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.CI_LLVM_VERSION }}/cmake-${{ env.CI_LLVM_VERSION }}.src.tar.xz -OutFile cmake.tar.xz - (Get-FileHash -Algorithm SHA256 .\cmake.tar.xz).hash -eq "B6D83C91F12757030D8361DEDC5DD84357B3EDB8DA406B5D0850DF8B6F7798B1" - 7z x cmake.tar.xz - 7z x cmake.tar - mv cmake-* cmake - - name: Build LLVM if: steps.cache-llvm-libs.outputs.cache-hit != 'true' run: | + git clone --config core.autocrlf=false -b llvmorg-${{ env.CI_LLVM_VERSION }} --depth 1 https://github.com/llvm/llvm-project.git mkdir llvm-build cd llvm-build - cmake ..\llvm-src -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_ZSTD=OFF + cmake ..\llvm-project\llvm -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86;AArch64" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DBUILD_SHARED_LIBS=OFF -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_ZSTD=OFF cmake --build . --config Release cmake "-DCMAKE_INSTALL_PREFIX=$(pwd)\..\llvm" -P cmake_install.cmake @@ -235,7 +218,7 @@ jobs: uses: ./.github/workflows/win_build_portable.yml with: release: false - llvm_version: "17.0.2" + llvm_version: "18.1.0-rc1" x86_64-windows-test: runs-on: windows-2022 @@ -288,7 +271,7 @@ jobs: uses: ./.github/workflows/win_build_portable.yml with: release: true - llvm_version: "17.0.2" + llvm_version: "18.1.0-rc1" x86_64-windows-installer: if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/')) diff --git a/etc/win-ci/build-llvm.ps1 b/etc/win-ci/build-llvm.ps1 index b544e9eba784..9335cfe9abe4 100644 --- a/etc/win-ci/build-llvm.ps1 +++ b/etc/win-ci/build-llvm.ps1 @@ -16,7 +16,7 @@ if (-not $Dynamic) { Setup-Git -Path $BuildTree -Url https://github.com/llvm/llvm-project.git -Ref llvmorg-$Version Run-InDirectory $BuildTree\build { - $args = "-Thost=x64 -DLLVM_TARGETS_TO_BUILD=""$($TargetsToBuild -join ';')"" -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_ZSTD=OFF" + $args = "-Thost=x64 -DLLVM_TARGETS_TO_BUILD=$($TargetsToBuild -join ';') -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_ZSTD=OFF" if ($Dynamic) { $args = "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL $args" } else { diff --git a/src/llvm/ext/llvm-versions.txt b/src/llvm/ext/llvm-versions.txt index 7c8773c02212..92ae5ecbaa5a 100644 --- a/src/llvm/ext/llvm-versions.txt +++ b/src/llvm/ext/llvm-versions.txt @@ -1 +1 @@ -17.0 16.0 15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 +18.1 17.0 16.0 15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index d5e7c2488002..b68e212d2052 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -35,7 +35,7 @@ @[Link(ldflags: {{ llvm_ldflags }})] lib LibLLVM - VERSION = {{ llvm_version.strip.gsub(/git/, "") }} + VERSION = {{ llvm_version.strip.gsub(/git/, "").gsub(/rc.*/, "") }} BUILT_TARGETS = {{ llvm_targets.strip.downcase.split(' ').map(&.id.symbolize) }} end {% end %} From 9b7850b4582fe51693c4bd77bcbd33d291534d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 6 Feb 2024 10:19:12 +0100 Subject: [PATCH 0955/1551] [CI] Upgrade from old machine images approaching EOL (#14275) --- .circleci/config.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8c07a1005b70..fe4e6dabaffd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,7 +39,7 @@ defaults: jobs: test_linux: machine: - image: ubuntu-2004:202201-02 + image: default environment: <<: *env TRAVIS_OS_NAME: linux @@ -70,7 +70,7 @@ jobs: test_alpine: machine: - image: ubuntu-2004:202201-02 + image: default environment: <<: *env TRAVIS_OS_NAME: linux @@ -115,7 +115,7 @@ jobs: test_preview_mt: machine: - image: ubuntu-2004:202201-02 + image: default resource_class: large environment: <<: *env @@ -264,7 +264,7 @@ jobs: dist_linux: machine: - image: ubuntu-2004:202201-02 + image: default steps: - attach_workspace: at: /tmp/workspace @@ -347,7 +347,7 @@ jobs: dist_docker: machine: - image: ubuntu-2004:202201-02 + image: default steps: - attach_workspace: at: /tmp/workspace @@ -363,7 +363,7 @@ jobs: publish_docker: machine: - image: ubuntu-2004:202201-02 + image: default steps: - attach_workspace: at: /tmp/workspace @@ -418,7 +418,7 @@ jobs: dist_docs: machine: - image: ubuntu-2004:202201-02 + image: default steps: - attach_workspace: at: /tmp/workspace @@ -464,7 +464,7 @@ jobs: test_dist_linux_on_docker: machine: - image: ubuntu-2004:202201-02 + image: default environment: <<: *env TRAVIS_OS_NAME: linux From b6fc5d5d5f7d9aca34ab459ce9aa71cfc2f8796a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 8 Feb 2024 08:01:19 +0800 Subject: [PATCH 0956/1551] Relax `delegate`'s setter detection (#14282) --- spec/std/object_spec.cr | 36 +++++++++++++++++++++++++++++++++++- src/object.cr | 41 +++++++++++++++++++++++++++++++---------- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/spec/std/object_spec.cr b/spec/std/object_spec.cr index 3df134513ae2..43d1c009a19e 100644 --- a/spec/std/object_spec.cr +++ b/spec/std/object_spec.cr @@ -11,6 +11,8 @@ private class StringWrapper end end +private EQ_OPERATORS = %w(<= >= == != []= ===) + private class TestObject getter getter1 getter getter2 : Int32 @@ -113,6 +115,19 @@ private class TestObject {key, value} end + # NOTE: these methods are a syntax error in older versions + {% if compare_versions(Crystal::VERSION, "1.12.0-dev") >= 0 %} + {% for op in EQ_OPERATORS %} + def {{ op.id }}(*args, **opts) + [args, opts] + end + + def {{ op.id }}(*args, **opts, &) + [args, opts, yield] + end + {% end %} + {% end %} + annotation TestAnnotation end @@ -130,7 +145,10 @@ end private class DelegatedTestObject delegate :property1=, to: @test_object - delegate :[]=, to: @test_object + + {% for op in EQ_OPERATORS %} + delegate {{ op.id.symbolize }}, to: @test_object + {% end %} def initialize(@test_object : TestObject) end @@ -206,6 +224,22 @@ describe Object do delegated = DelegatedTestObject.new(test_object) (delegated["foo"] = "bar").should eq({"foo", "bar"}) end + + {% if compare_versions(Crystal::VERSION, "1.12.0-dev") >= 0 %} + {% for op in EQ_OPERATORS %} + it "forwards \#{{ op.id }} with multiple parameters" do + test_object = TestObject.new + delegated = DelegatedTestObject.new(test_object) + delegated.{{ op.id }}(1, 2, a: 3, b: 4).should eq [{1, 2}, {a: 3, b: 4}] + end + + it "forwards \#{{ op.id }} with multiple parameters and block parameter" do + test_object = TestObject.new + delegated = DelegatedTestObject.new(test_object) + delegated.{{ op.id }}(1, 2, a: 3, b: 4) { 5 }.should eq [{1, 2}, {a: 3, b: 4}, 5] + end + {% end %} + {% end %} end describe "getter" do diff --git a/src/object.cr b/src/object.cr index 6687e19f424a..ba818ac2979e 100644 --- a/src/object.cr +++ b/src/object.cr @@ -1293,17 +1293,18 @@ class Object # wrapper.capitalize # => "Hello" # ``` macro delegate(*methods, to object) - {% for method in methods %} - {% if method.id.ends_with?('=') && method.id != "[]=" %} - def {{method.id}}(arg) - {{object.id}}.{{method.id}} arg - end - {% else %} - def {{method.id}}(*args, **options) - {{object.id}}.{{method.id}}(*args, **options) - end + {% if compare_versions(Crystal::VERSION, "1.12.0-dev") >= 0 %} + {% eq_operators = %w(<= >= == != []= ===) %} + {% for method in methods %} + {% if method.id.ends_with?('=') && !eq_operators.includes?(method.id.stringify) %} + def {{method.id}}(arg) + {{object.id}}.{{method.id}} arg + end + {% else %} + def {{method.id}}(*args, **options) + {{object.id}}.{{method.id}}(*args, **options) + end - {% if method.id != "[]=" %} def {{method.id}}(*args, **options) {{object.id}}.{{method.id}}(*args, **options) do |*yield_args| yield *yield_args @@ -1311,6 +1312,26 @@ class Object end {% end %} {% end %} + {% else %} + {% for method in methods %} + {% if method.id.ends_with?('=') && method.id != "[]=" %} + def {{method.id}}(arg) + {{object.id}}.{{method.id}} arg + end + {% else %} + def {{method.id}}(*args, **options) + {{object.id}}.{{method.id}}(*args, **options) + end + + {% if method.id != "[]=" %} + def {{method.id}}(*args, **options) + {{object.id}}.{{method.id}}(*args, **options) do |*yield_args| + yield *yield_args + end + end + {% end %} + {% end %} + {% end %} {% end %} end From 1d9042b6853eed540bd89f60254a16c689e5375c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 10 Feb 2024 01:11:40 +0800 Subject: [PATCH 0957/1551] Fix stack corruption in union-to-union casts (#14289) --- spec/compiler/codegen/cast_spec.cr | 35 ++++++++++++++++++++++++++ src/compiler/crystal/codegen/unions.cr | 12 ++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/spec/compiler/codegen/cast_spec.cr b/spec/compiler/codegen/cast_spec.cr index fbe6cd78ac87..dd6653f036c9 100644 --- a/spec/compiler/codegen/cast_spec.cr +++ b/spec/compiler/codegen/cast_spec.cr @@ -104,6 +104,41 @@ describe "Code gen: cast" do CRYSTAL end + it "doesn't corrupt stack when downcasting union to union with different alignment (#14285)" do + run(<<-CRYSTAL).to_b.should be_true + struct Time2 + def initialize(@seconds : Int64) + @nanoseconds = uninitialized UInt32[3] + end + + def <(other : Time2) : Bool + @seconds < other.@seconds + end + end + + class Constraints::Range + def initialize(@min : Int128 | Time2 | Nil) + end + end + + def validate(value : Time2, constraint) : Bool + min = constraint.@min + if min.is_a?(Time2?) + if min + if value < min + return false + end + end + end + true + end + + value = Time2.new(123) + constraint = Constraints::Range.new(Time2.new(45)) + validate(value, constraint) + CRYSTAL + end + it "casts from virtual to single type" do run(%( require "prelude" diff --git a/src/compiler/crystal/codegen/unions.cr b/src/compiler/crystal/codegen/unions.cr index 7ac58ff84887..bcef273a8010 100644 --- a/src/compiler/crystal/codegen/unions.cr +++ b/src/compiler/crystal/codegen/unions.cr @@ -113,18 +113,22 @@ module Crystal def store_union_in_union(union_type, union_pointer, value_type, value) to_llvm_type = llvm_type(union_type) from_llvm_type = llvm_type(value_type) - union_value_type = from_llvm_type.struct_element_types[1] + to_value_type = to_llvm_type.struct_element_types[1] + from_value_type = from_llvm_type.struct_element_types[1] store type_id(value, value_type), union_type_id(to_llvm_type, union_pointer) - size = @llvm_typer.size_of(union_value_type) + size = { + @llvm_typer.size_of(from_value_type), + @llvm_typer.size_of(to_value_type), + }.min size = @program.bits64? ? int64(size) : int32(size) memcpy( cast_to_void_pointer(union_value(to_llvm_type, union_pointer)), cast_to_void_pointer(union_value(from_llvm_type, value)), size, - align: @llvm_typer.align_of(to_llvm_type.struct_element_types[1]), - src_align: @llvm_typer.align_of(union_value_type), + align: @llvm_typer.align_of(to_value_type), + src_align: @llvm_typer.align_of(from_value_type), volatile: int1(0), ) end From 01bf92147a08073343981316493cb097d034eb78 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 9 Feb 2024 18:11:49 +0100 Subject: [PATCH 0958/1551] Fix: don't run thread specs with the interpreter (#14287) --- spec/interpreter_std_spec.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/interpreter_std_spec.cr b/spec/interpreter_std_spec.cr index 8638b0a83f7f..79b8bab3edbc 100644 --- a/spec/interpreter_std_spec.cr +++ b/spec/interpreter_std_spec.cr @@ -235,9 +235,9 @@ require "./std/system_error_spec.cr" require "./std/system/group_spec.cr" # require "./std/system_spec.cr" (failed to run) require "./std/system/user_spec.cr" -require "./std/thread/condition_variable_spec.cr" -require "./std/thread/mutex_spec.cr" -# require "./std/thread_spec.cr" (failed to run) +# require "./std/thread/condition_variable_spec.cr" (interpreter must support threads) +# require "./std/thread/mutex_spec.cr" (interpreter must support threads) +# require "./std/thread_spec.cr" (interpreter must support threads) require "./std/time/custom_formats_spec.cr" require "./std/time/format_spec.cr" require "./std/time/location_spec.cr" From 8d3722ca98ab86f7740be24da3639fa5beefc03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 9 Feb 2024 18:12:02 +0100 Subject: [PATCH 0959/1551] Mention RFC process in contribution instructions (#14291) --- .github/ISSUE_TEMPLATE/feature-request.md | 4 ++++ CONTRIBUTING.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index 756c4cfd2816..e6665a621eaa 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -10,3 +10,7 @@ labels: kind:feature - Describe the feature you would like, optionally illustrated by examples, and how it will solve the above problem. - Describe considered alternative solutions, and the reasons why you have not proposed them as a solution here. - Does it break backward compatibility, if yes then what's the migration path? + +In case this proposal includes a substantial change to the language, we ask you to go through an [RFC process](https://github.com/crystal-lang/rfcs). + +The best place to start an open discussion about potential changes is the [Crystal forum](https://forum.crystal-lang.org/c/crystal-contrib/6). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77ad67860f4b..640c980909ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,6 +15,10 @@ there's no more room for discussion. We'll anyway close the issue after some day If something is missing from the language it might be that it's not yet implemented or that it was purposely left out. If in doubt, just ask. +Substantial changes go through an [RFC process](https://github.com/crystal-lang/rfcs). + +The best place to start an open discussion about potential changes is the [Crystal forum](https://forum.crystal-lang.org/c/crystal-contrib/6). + ### What's needed right now You can find a list of tasks that we consider suitable for a first time contribution at From a3e8d12f5af17ddbf45aeb0a59c818541fe4dd78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 9 Feb 2024 18:14:15 +0100 Subject: [PATCH 0960/1551] Drop Nikola sponsor mention from Readme (#14290) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70577b822fdc..19d8e09ee853 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Project Status Within a major version, language features won't be removed or changed in any way that could prevent a Crystal program written with that version from compiling and working. The built-in standard library might be enriched, but it will always be done with backwards compatibility in mind. -Development of the Crystal language is possible thanks to the community's effort and the continued support of [84codes](https://www.84codes.com/), [Nikola Motor Company](https://nikolamotor.com/) and every other [sponsor](https://crystal-lang.org/sponsors). +Development of the Crystal language is possible thanks to the community's effort and the continued support of [84codes](https://www.84codes.com/) and every other [sponsor](https://crystal-lang.org/sponsors). Installing ---------- From db67d71341f54d11933a33b667e141819c2af33a Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Sat, 10 Feb 2024 06:22:35 -0600 Subject: [PATCH 0961/1551] Add memory barrier to `Mutex#unlock` on aarch64 (#14272) --- src/mutex.cr | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mutex.cr b/src/mutex.cr index 6bdd9a98fd25..83dd4bb53429 100644 --- a/src/mutex.cr +++ b/src/mutex.cr @@ -107,6 +107,9 @@ class Mutex @mutex_fiber = nil end + {% if flag?(:aarch64) %} + Atomic::Ops.fence :sequentially_consistent, false + {% end %} @state.lazy_set(0) if @queue_count.get == 0 From c0270c60a685760af2b756dd65921346e172be81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 10 Feb 2024 13:22:49 +0100 Subject: [PATCH 0962/1551] Enhance changelog script to pull milestone info from GitHub (#14230) --- scripts/github-changelog.cr | 47 +++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index 7397440c3676..f2d277f258e7 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -31,6 +31,10 @@ def query_prs(api_token, repository, milestone) repository(owner: $owner, name: $repository) { milestones(query: $milestone, first: 1) { nodes { + closedAt + description + dueOn + title pullRequests(first: 300) { nodes { number @@ -81,6 +85,28 @@ module LabelNameConverter end end +record Milestone, + closed_at : Time?, + description : String, + due_on : Time?, + title : String, + pull_requests : Array(PullRequest) do + include JSON::Serializable + + @[JSON::Field(key: "dueOn")] + @due_on : Time? + + @[JSON::Field(key: "closedAt")] + @closed_at : Time? + + @[JSON::Field(key: "pullRequests", root: "nodes")] + @pull_requests : Array(PullRequest) + + def release_date + closed_at || due_on + end +end + record PullRequest, number : Int32, title : String, @@ -245,24 +271,20 @@ end response = query_prs(api_token, repository, milestone) parser = JSON::PullParser.new(response.body) -array = parser.on_key! "data" do +milestone = parser.on_key! "data" do parser.on_key! "repository" do parser.on_key! "milestones" do parser.on_key! "nodes" do parser.read_begin_array - a = parser.on_key! "pullRequests" do - parser.on_key! "nodes" do - Array(PullRequest).new(parser) - end - end + milestone = Milestone.new(parser) parser.read_end_array - a + milestone end end end end -sections = array.group_by(&.section) +sections = milestone.pull_requests.group_by(&.section) SECTION_TITLES = { "breaking" => "Breaking changes", @@ -279,9 +301,14 @@ SECTION_TITLES = { TOPIC_ORDER = %w[lang stdlib compiler tools other] -puts "## [#{milestone}] (#{Time.local.to_s("%F")})" +puts "## [#{milestone.title}] (#{milestone.release_date.try(&.to_s("%F")) || "unreleased"})" +if description = milestone.description.presence + puts + print "_", description + puts "_" +end puts -puts "[#{milestone}]: https://github.com/crystal-lang/crystal/releases/#{milestone}" +puts "[#{milestone.title}]: https://github.com/crystal-lang/crystal/releases/#{milestone.title}" puts SECTION_TITLES.each do |id, title| From 9828371b85b58489c905aa4ba911b8716255b18e Mon Sep 17 00:00:00 2001 From: Lachlan Dowding Date: Wed, 21 Feb 2024 03:14:20 +1000 Subject: [PATCH 0963/1551] Fix docs `:inherit:` pragma for `Indexable#first` (#14296) --- src/indexable.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indexable.cr b/src/indexable.cr index 7496a95e0df0..bec815ee3d7f 100644 --- a/src/indexable.cr +++ b/src/indexable.cr @@ -744,7 +744,7 @@ module Indexable(T) true end - # :inherited: + # :inherit: def first(&) size == 0 ? yield : unsafe_fetch(0) end From ee2d34c431e5128978a40d56e8b59e78faaae969 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 21 Feb 2024 01:14:40 +0800 Subject: [PATCH 0964/1551] Respect `--static` on Windows (#14292) --- .github/workflows/win_build_portable.yml | 8 +++++++- Makefile.win | 2 +- src/compiler/crystal/codegen/codegen.cr | 2 +- src/compiler/crystal/compiler.cr | 4 ++-- src/compiler/crystal/interpreter/context.cr | 3 --- src/crystal/system/win32/wmain.cr | 2 +- src/empty.cr | 2 +- src/lib_c.cr | 2 +- src/llvm/lib_llvm.cr | 2 +- src/raise.cr | 2 +- 10 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index bab89e04685e..2e859478a0f9 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -114,7 +114,13 @@ jobs: - name: Build Crystal run: | bin/crystal.bat env - make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }} + # TODO: the 1.11.2 compiler only understands `-Dpreview_dll`; remove this once the + # base compiler is updated + make -f Makefile.win -B FLAGS=-Dpreview_dll ${{ inputs.release && 'release=1' || '' }} + # TODO: 1.11.2 comes with LLVM 17's DLL and copies it to the output directory, but a + # dynamically linked compiler requires LLVM 18, so we must overwrite it; remove this + # line once the base compiler is updated + cp dlls/LLVM-C.dll .build/ - name: Download shards release uses: actions/checkout@v4 diff --git a/Makefile.win b/Makefile.win index da1d9fc8328a..2e4899e58f8d 100644 --- a/Makefile.win +++ b/Makefile.win @@ -71,7 +71,7 @@ LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell $(LLVM_CONFIG) --version)) LLVM_EXT_DIR = src\llvm\ext LLVM_EXT_OBJ = $(LLVM_EXT_DIR)\llvm_ext.obj DEPS = $(LLVM_EXT_OBJ) -CXXFLAGS += $(if $(debug),/MTd /Od,/MT) +CXXFLAGS += $(if $(static),$(if $(debug),/MTd /Od ,/MT ),$(if $(debug),/MDd /Od ,/MD )) CRYSTAL_VERSION ?= $(shell type src\VERSION) prefix ?= $(or $(ProgramW6432),$(ProgramFiles))\crystal diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 441f3cfa09ae..1535b02dc99f 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -1517,7 +1517,7 @@ module Crystal unless var var = llvm_mod.globals.add(llvm_c_return_type(type), name) var.linkage = LLVM::Linkage::External - if @program.has_flag?("win32") && @program.has_flag?("preview_dll") + if @program.has_flag?("win32") && !@program.has_flag?("static") var.dll_storage_class = LLVM::DLLStorageClass::DLLImport end var.thread_local = thread_local diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 899ef242f318..cb745a447571 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -333,7 +333,7 @@ module Crystal {% end %} {% if flag?(:windows) %} - copy_dlls(program, output_filename) if program.has_flag?("preview_dll") + copy_dlls(program, output_filename) unless static? {% end %} end @@ -442,7 +442,7 @@ module Crystal {% if flag?(:msvc) %} unless @cross_compile - extra_suffix = program.has_flag?("preview_dll") ? "-dynamic" : "-static" + extra_suffix = static? ? "-static" : "-dynamic" search_result = Loader.search_libraries(Process.parse_arguments_windows(link_args.join(' ').gsub('\n', ' ')), extra_suffix: extra_suffix) if not_found = search_result.not_found? error "Cannot locate the .lib files for the following libraries: #{not_found.join(", ")}" diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr index 2d5da06dcf15..c3b6e008f6d9 100644 --- a/src/compiler/crystal/interpreter/context.cr +++ b/src/compiler/crystal/interpreter/context.cr @@ -46,9 +46,6 @@ class Crystal::Repl::Context def initialize(@program : Program) @program.flags << "interpreted" - {% if flag?(:win32) %} - @program.flags << "preview_dll" - {% end %} @gc_references = [] of Void* diff --git a/src/crystal/system/win32/wmain.cr b/src/crystal/system/win32/wmain.cr index cad255509f9c..71383c66a88a 100644 --- a/src/crystal/system/win32/wmain.cr +++ b/src/crystal/system/win32/wmain.cr @@ -4,7 +4,7 @@ require "c/stdlib" {% begin %} # we have both `main` and `wmain`, so we must choose an unambiguous entry point - @[Link({{ flag?(:preview_dll) ? "msvcrt" : "libcmt" }}, ldflags: "/ENTRY:wmainCRTStartup")] + @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }}, ldflags: "/ENTRY:wmainCRTStartup")] {% end %} lib LibCrystalMain end diff --git a/src/empty.cr b/src/empty.cr index 22de59dcbb57..204e30da48c0 100644 --- a/src/empty.cr +++ b/src/empty.cr @@ -1,7 +1,7 @@ require "primitives" {% if flag?(:win32) %} - @[Link({{ flag?(:preview_dll) ? "msvcrt" : "libcmt" }})] # For `mainCRTStartup` + @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})] # For `mainCRTStartup` {% end %} lib LibCrystalMain @[Raises] diff --git a/src/lib_c.cr b/src/lib_c.cr index b859a4c85061..0bd8d2c2cc35 100644 --- a/src/lib_c.cr +++ b/src/lib_c.cr @@ -1,5 +1,5 @@ {% if flag?(:win32) %} - @[Link({{ flag?(:preview_dll) ? "ucrt" : "libucrt" }})] + @[Link({{ flag?(:static) ? "libucrt" : "ucrt" }})] {% end %} lib LibC alias Char = UInt8 diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index b68e212d2052..ca1907522f8c 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -1,5 +1,5 @@ {% begin %} - {% if flag?(:win32) && flag?(:preview_dll) %} + {% if flag?(:win32) && !flag?(:static) %} {% config = nil %} {% for dir in Crystal::LIBRARY_PATH.split(';') %} {% config ||= read_file?("#{dir.id}/llvm_VERSION") %} diff --git a/src/raise.cr b/src/raise.cr index e870f23e725e..ff8684795e77 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -95,7 +95,7 @@ end require "exception/lib_unwind" {% begin %} - @[Link({{ flag?(:preview_dll) ? "vcruntime" : "libvcruntime" }})] + @[Link({{ flag?(:static) ? "libvcruntime" : "vcruntime" }})] {% end %} lib LibC fun _CxxThrowException(ex : Void*, throw_info : Void*) : NoReturn From 72aea71a743bd6f7d1b908d67a21dc3953cf9fc2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 21 Feb 2024 01:15:16 +0800 Subject: [PATCH 0965/1551] Add `Crystal::Hasher.reduce_num` and `#number` (#14304) --- spec/std/crystal/hasher_spec.cr | 68 +++++++++++++++++++++++++++++++++ src/big/big_float.cr | 5 +-- src/big/big_int.cr | 2 +- src/big/big_rational.cr | 2 +- src/crystal/hasher.cr | 67 ++++++++++++++++---------------- src/float.cr | 5 --- src/int.cr | 5 --- src/number.cr | 5 +++ 8 files changed, 111 insertions(+), 48 deletions(-) diff --git a/spec/std/crystal/hasher_spec.cr b/spec/std/crystal/hasher_spec.cr index e75ad3ea9ec5..099b49af7755 100644 --- a/spec/std/crystal/hasher_spec.cr +++ b/spec/std/crystal/hasher_spec.cr @@ -260,4 +260,72 @@ describe "Crystal::Hasher" do 1_f32.hash.should eq(1.to_big_i.hash) end end + + describe ".reduce_num" do + it "reduces primitive int" do + {% for int in Int::Primitive.union_types %} + Crystal::Hasher.reduce_num({{ int }}.new(0)).should eq(0_u64) + Crystal::Hasher.reduce_num({{ int }}.new(1)).should eq(1_u64) + Crystal::Hasher.reduce_num({{ int }}::MAX).should eq(UInt64.new!({{ int }}::MAX % 0x1FFF_FFFF_FFFF_FFFF_u64)) + {% end %} + + {% for int in Int::Signed.union_types %} + Crystal::Hasher.reduce_num({{ int }}.new(-1)).should eq(UInt64::MAX) + Crystal::Hasher.reduce_num({{ int }}::MIN).should eq(UInt64::MAX - UInt64.new!({{ int }}::MAX % 0x1FFF_FFFF_FFFF_FFFF_u64)) + {% end %} + end + + it "reduces primitive float" do + {% for float in Float::Primitive.union_types %} + Crystal::Hasher.reduce_num({{ float }}.new(0)).should eq(0_u64) + Crystal::Hasher.reduce_num({{ float }}.new(1)).should eq(1_u64) + Crystal::Hasher.reduce_num({{ float }}.new(-1)).should eq(UInt64::MAX) + Crystal::Hasher.reduce_num({{ float }}::INFINITY).should eq(Crystal::Hasher::HASH_INF_PLUS) + Crystal::Hasher.reduce_num(-{{ float }}::INFINITY).should eq(Crystal::Hasher::HASH_INF_MINUS) + Crystal::Hasher.reduce_num({{ float }}::NAN).should eq(Crystal::Hasher::HASH_NAN) + + x = {{ float }}.new(2) + i = 1 + until x.infinite? + Crystal::Hasher.reduce_num(x).should eq(1_u64 << (i % 61)) + x *= 2 + i += 1 + end + + x = {{ float }}.new(0.5) + i = 1 + until x.zero? + Crystal::Hasher.reduce_num(x).should eq(1_u64 << ((-i) % 61)) + x /= 2 + i += 1 + end + {% end %} + + Crystal::Hasher.reduce_num(Float32::MAX).should eq(0x1FFF_F800_0000_003F_u64) + Crystal::Hasher.reduce_num(Float64::MAX).should eq(0x1F00_FFFF_FFFF_FFFF_u64) + end + + pending "reduces BigInt" do + Crystal::Hasher.reduce_num(0.to_big_i).should eq(0_u64) + Crystal::Hasher.reduce_num(1.to_big_i).should eq(1_u64) + Crystal::Hasher.reduce_num((-1).to_big_i).should eq(UInt64::MAX) + + (1..300).each do |i| + Crystal::Hasher.reduce_num(2.to_big_i ** i).should eq(1_u64 << (i % 61)) + end + end + + it "reduces BigFloat" do + Crystal::Hasher.reduce_num(0.to_big_f).should eq(0_u64) + Crystal::Hasher.reduce_num(1.to_big_f).should eq(1_u64) + Crystal::Hasher.reduce_num((-1).to_big_f).should eq(UInt64::MAX) + Crystal::Hasher.reduce_num(Float32::MAX.to_big_f).should eq(0x1FFF_F800_0000_003F_u64) + Crystal::Hasher.reduce_num(Float64::MAX.to_big_f).should eq(0x1F00_FFFF_FFFF_FFFF_u64) + + (1..300).each do |i| + Crystal::Hasher.reduce_num(2.to_big_f ** i).should eq(1_u64 << (i % 61)) + Crystal::Hasher.reduce_num(0.5.to_big_f ** i).should eq(1_u64 << ((-i) % 61)) + end + end + end end diff --git a/src/big/big_float.cr b/src/big/big_float.cr index b7be6423929f..2cf224d7484a 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -500,8 +500,8 @@ end # :nodoc: struct Crystal::Hasher - def float(value : BigFloat) - normalized_hash = float_normalize_wrap(value) do |value| + def self.reduce_num(value : BigFloat) + float_normalize_wrap(value) do |value| # more exact version of `Math.frexp` LibGMP.mpf_get_d_2exp(out exp, value) frac = BigFloat.new do |mpf| @@ -513,6 +513,5 @@ struct Crystal::Hasher end float_normalize_reference(value, frac, exp) end - permute(normalized_hash) end end diff --git a/src/big/big_int.cr b/src/big/big_int.cr index e6b5f0214297..1e3c3430c542 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -988,7 +988,7 @@ struct Crystal::Hasher private HASH_MODULUS_INT_P = BigInt.new((1_u64 << HASH_BITS) - 1) private HASH_MODULUS_INT_N = -BigInt.new((1_u64 << HASH_BITS) - 1) - def int(value : BigInt) + def self.reduce_num(value : BigInt) # it should calculate `remainder(HASH_MODULUS)` if LibGMP::UI == UInt64 v = LibGMP.tdiv_ui(value, HASH_MODULUS).to_i64 diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index be510e6033bb..a9466a5466a0 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -416,7 +416,7 @@ struct Crystal::Hasher private HASH_MODULUS_RAT_P = BigRational.new((1_u64 << HASH_BITS) - 1) private HASH_MODULUS_RAT_N = -BigRational.new((1_u64 << HASH_BITS) - 1) - def float(value : BigRational) + def self.reduce_num(value : BigRational) rem = value if value >= HASH_MODULUS_RAT_P || value <= HASH_MODULUS_RAT_N num = value.numerator diff --git a/src/crystal/hasher.cr b/src/crystal/hasher.cr index d5b6c863d4ca..0c80fe5a0c50 100644 --- a/src/crystal/hasher.cr +++ b/src/crystal/hasher.cr @@ -33,7 +33,7 @@ struct Crystal::Hasher # Do not output calculated hash value to user's console/form/ # html/api response, etc. Use some from digest package instead. - # Based on https://github.com/python/cpython/blob/f051e43/Python/pyhash.c#L34 + # Based on https://github.com/python/cpython/blob/371c970/Python/pyhash.c#L31 # # For numeric types, the hash of a number x is based on the reduction # of x modulo the Mersen Prime P = 2**HASH_BITS - 1. It's designed @@ -75,9 +75,9 @@ struct Crystal::Hasher private HASH_BITS = 61 private HASH_MODULUS = (1_i64 << HASH_BITS) - 1 - private HASH_NAN = 0_u64 - private HASH_INF_PLUS = 314159_u64 - private HASH_INF_MINUS = (-314159_i64).unsafe_as(UInt64) + HASH_NAN = 0_u64 + HASH_INF_PLUS = 314159_u64 + HASH_INF_MINUS = (-314159_i64).unsafe_as(UInt64) @@seed = uninitialized UInt64[2] Crystal::System::Random.random_bytes(@@seed.to_slice.to_unsafe_bytes) @@ -105,30 +105,20 @@ struct Crystal::Hasher a &+ b end - def nil - @a &+= @b - @b &+= 1 - self + def self.reduce_num(value : Int8 | Int16 | Int32) + value.to_i64.unsafe_as(UInt64) end - def bool(value) - (value ? 1 : 0).hash(self) + def self.reduce_num(value : UInt8 | UInt16 | UInt32) + value.to_u64 end - def int(value : Int8 | Int16 | Int32) - permute(value.to_i64.unsafe_as(UInt64)) + def self.reduce_num(value : Int::Unsigned) + value.remainder(HASH_MODULUS).to_u64 end - def int(value : UInt8 | UInt16 | UInt32) - permute(value.to_u64) - end - - def int(value : Int::Unsigned) - permute(value.remainder(HASH_MODULUS).to_u64) - end - - def int(value : Int) - permute(value.remainder(HASH_MODULUS).to_i64.unsafe_as(UInt64)) + def self.reduce_num(value : Int) + value.remainder(HASH_MODULUS).to_i64.unsafe_as(UInt64) end # This function is for reference implementation, and it is used for `BigFloat`. @@ -136,7 +126,7 @@ struct Crystal::Hasher # bitwise calculation. # Arguments `frac` and `exp` are result of equivalent `Math.frexp`, though # for `BigFloat` custom calculation used for more precision. - private def float_normalize_reference(value, frac, exp) + private def self.float_normalize_reference(value, frac, exp) if value < 0 frac = -frac end @@ -155,7 +145,7 @@ struct Crystal::Hasher {x, exp} end - private def float_normalize_wrap(value, &) + private def self.float_normalize_wrap(value, &) return HASH_NAN if value.nan? if value.infinite? return value > 0 ? HASH_INF_PLUS : HASH_INF_MINUS @@ -170,8 +160,8 @@ struct Crystal::Hasher (x * (value < 0 ? -1 : 1)).to_i64.unsafe_as(UInt64) end - def float(value : Float32) - normalized_hash = float_normalize_wrap(value) do |value| + def self.reduce_num(value : Float32) + float_normalize_wrap(value) do |value| # This optimized version works on every architecture where endianness # of Float32 and Int32 matches and float is IEEE754. All supported # architectures fall into this category. @@ -187,11 +177,10 @@ struct Crystal::Hasher end {mantissa.to_i64, exp} end - permute(normalized_hash) end - def float(value : Float64) - normalized_hash = float_normalize_wrap(value) do |value| + def self.reduce_num(value : Float64) + float_normalize_wrap(value) do |value| # This optimized version works on every architecture where endianness # of Float64 and Int64 matches and float is IEEE754. All supported # architectures fall into this category. @@ -208,15 +197,27 @@ struct Crystal::Hasher {mantissa.to_i64, exp} end - permute(normalized_hash) end - def float(value : Float) - normalized_hash = float_normalize_wrap(value) do |value| + def self.reduce_num(value : Float) + float_normalize_wrap(value) do |value| frac, exp = Math.frexp value float_normalize_reference(value, frac, exp) end - permute(normalized_hash) + end + + def nil + @a &+= @b + @b &+= 1 + self + end + + def bool(value) + (value ? 1 : 0).hash(self) + end + + def number(value : Number) + permute(Hasher.reduce_num(value)) end def char(value) diff --git a/src/float.cr b/src/float.cr index d21ebe6f9a8d..c7354240ea81 100644 --- a/src/float.cr +++ b/src/float.cr @@ -88,11 +88,6 @@ struct Float end end - # See `Object#hash(hasher)` - def hash(hasher) - hasher.float(self) - end - # Writes this float to the given *io* in the given *format*. # See also: `IO#write_bytes`. def to_io(io : IO, format : IO::ByteFormat) : Nil diff --git a/src/int.cr b/src/int.cr index b36ddba14e9e..35ad8c05f642 100644 --- a/src/int.cr +++ b/src/int.cr @@ -527,11 +527,6 @@ struct Int !even? end - # See `Object#hash(hasher)` - def hash(hasher) - hasher.int(self) - end - def succ : self self + 1 end diff --git a/src/number.cr b/src/number.cr index 9ec542f86ef7..f7c82aa4cded 100644 --- a/src/number.cr +++ b/src/number.cr @@ -39,6 +39,11 @@ struct Number new(1) end + # See `Object#hash(hasher)` + def hash(hasher) + hasher.number(self) + end + # Returns `self`. def + self From 0565b865f2059254840b81655c27fbf7110c67d6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 21 Feb 2024 18:21:20 +0800 Subject: [PATCH 0966/1551] Change some line endings from CRLF to LF (#14299) --- .gitattributes | 60 ++--- .github/workflows/win_build_portable.yml | 312 +++++++++++------------ spec/compiler/data/visibility.h | 14 +- 3 files changed, 193 insertions(+), 193 deletions(-) diff --git a/.gitattributes b/.gitattributes index f616edb09d75..179a307af12c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,30 +1,30 @@ -## Sources - -*.cr text eol=lf - -## Sourced-in dependencies - -lib/** linguist-vendored - -## Generated files - -# produced by scripts/generate_windows_zone_names.cr -src/crystal/system/win32/zone_names.cr linguist-generated -# produced by scripts/generate_html_entities.cr -src/html/entities.cr linguist-generated -# produced by scripts/generate_ssl_server_defaults.cr -src/openssl/ssl/defaults.cr linguist-generated -# produced by scripts/generate_grapheme_properties.cr -src/string/grapheme/properties.cr linguist-generated -# produced by scripts/generate_unicode_data.cr -src/unicode/data.cr linguist-generated -# produced by spec/generate_interpreter_spec.sh -spec/interpreter_std_spec.cr linguist-generated -# produced by scripts/generate_grapheme_break_specs.cr -spec/std/string/grapheme_break_spec.cr linguist-generated -# produced by spec/generate_wasm32_spec.sh -spec/wasm32_std_spec.cr linguist-generated - -## Syntax highlighting - -Makefile.win linguist-language=makefile +## Sources + +*.cr text eol=lf + +## Sourced-in dependencies + +lib/** linguist-vendored + +## Generated files + +# produced by scripts/generate_windows_zone_names.cr +src/crystal/system/win32/zone_names.cr linguist-generated +# produced by scripts/generate_html_entities.cr +src/html/entities.cr linguist-generated +# produced by scripts/generate_ssl_server_defaults.cr +src/openssl/ssl/defaults.cr linguist-generated +# produced by scripts/generate_grapheme_properties.cr +src/string/grapheme/properties.cr linguist-generated +# produced by scripts/generate_unicode_data.cr +src/unicode/data.cr linguist-generated +# produced by spec/generate_interpreter_spec.sh +spec/interpreter_std_spec.cr linguist-generated +# produced by scripts/generate_grapheme_break_specs.cr +spec/std/string/grapheme_break_spec.cr linguist-generated +# produced by spec/generate_wasm32_spec.sh +spec/wasm32_std_spec.cr linguist-generated + +## Syntax highlighting + +Makefile.win linguist-language=makefile diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 2e859478a0f9..5ad78feceedc 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -1,156 +1,156 @@ -name: Windows CI / Build Portable Package - -on: - workflow_call: - inputs: - release: - required: true - type: boolean - llvm_version: - required: true - type: string - -jobs: - build: - runs-on: windows-2022 - steps: - - name: Disable CRLF line ending substitution - run: | - git config --global core.autocrlf false - - - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 - - - name: Install Crystal - uses: crystal-lang/install-crystal@v1 - with: - crystal: "1.11.2" - - - name: Download Crystal source - uses: actions/checkout@v4 - - - name: Restore libraries - uses: actions/cache/restore@v4 - with: - path: | - libs/pcre.lib - libs/pcre2-8.lib - libs/iconv.lib - libs/gc.lib - libs/ffi.lib - libs/z.lib - libs/mpir.lib - libs/yaml.lib - libs/xml2.lib - key: win-libs-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc - fail-on-cache-miss: true - - name: Restore OpenSSL - uses: actions/cache/restore@v4 - with: - path: | - libs/crypto.lib - libs/ssl.lib - libs/openssl_VERSION - key: win-openssl-libs-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc - fail-on-cache-miss: true - - name: Restore DLLs - uses: actions/cache/restore@v4 - with: - path: | - libs/pcre-dynamic.lib - libs/pcre2-8-dynamic.lib - libs/iconv-dynamic.lib - libs/gc-dynamic.lib - libs/ffi-dynamic.lib - libs/z-dynamic.lib - libs/mpir-dynamic.lib - libs/yaml-dynamic.lib - libs/xml2-dynamic.lib - dlls/pcre.dll - dlls/pcre2-8.dll - dlls/libiconv.dll - dlls/gc.dll - dlls/libffi.dll - dlls/zlib1.dll - dlls/mpir.dll - dlls/yaml.dll - dlls/libxml2.dll - key: win-dlls-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc - fail-on-cache-miss: true - - name: Restore OpenSSL DLLs - uses: actions/cache/restore@v4 - with: - path: | - libs/crypto-dynamic.lib - libs/ssl-dynamic.lib - dlls/libcrypto-3-x64.dll - dlls/libssl-3-x64.dll - key: win-openssl-dlls-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc - fail-on-cache-miss: true - - name: Restore LLVM - uses: actions/cache/restore@v4 - with: - path: llvm - key: llvm-libs-${{ inputs.llvm_version }}-msvc - fail-on-cache-miss: true - - name: Restore LLVM DLLs - uses: actions/cache/restore@v4 - with: - path: | - libs/llvm_VERSION - libs/llvm-dynamic.lib - dlls/LLVM-C.dll - key: llvm-dlls-${{ inputs.llvm_version }}-${{ hashFiles('etc/win-ci/build-llvm.ps1') }}-msvc - fail-on-cache-miss: true - - - name: Set up environment - run: | - echo "CRYSTAL_LIBRARY_PATH=$(pwd)\libs" >> ${env:GITHUB_ENV} - echo "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" >> ${env:GITHUB_ENV} - - - name: Build LLVM extensions - run: make -f Makefile.win deps - - - name: Build Crystal - run: | - bin/crystal.bat env - # TODO: the 1.11.2 compiler only understands `-Dpreview_dll`; remove this once the - # base compiler is updated - make -f Makefile.win -B FLAGS=-Dpreview_dll ${{ inputs.release && 'release=1' || '' }} - # TODO: 1.11.2 comes with LLVM 17's DLL and copies it to the output directory, but a - # dynamically linked compiler requires LLVM 18, so we must overwrite it; remove this - # line once the base compiler is updated - cp dlls/LLVM-C.dll .build/ - - - name: Download shards release - uses: actions/checkout@v4 - with: - repository: crystal-lang/shards - ref: v0.17.4 - path: shards - - - name: Download molinillo release - uses: actions/checkout@v4 - with: - repository: crystal-lang/crystal-molinillo - ref: v0.2.0 - path: shards/lib/molinillo - - - name: Build shards release - working-directory: ./shards - run: ../bin/crystal.bat build ${{ inputs.release && '--release' || '' }} src/shards.cr - - - name: Gather Crystal binaries - run: | - make -f Makefile.win install prefix=crystal - mkdir crystal/lib - cp shards/shards.exe crystal/ - cp libs/* crystal/lib/ - cp dlls/* crystal/ - cp README.md crystal/ - - - name: Upload Crystal binaries - uses: actions/upload-artifact@v4 - with: - name: ${{ inputs.release && 'crystal-release' || 'crystal' }} - path: crystal +name: Windows CI / Build Portable Package + +on: + workflow_call: + inputs: + release: + required: true + type: boolean + llvm_version: + required: true + type: string + +jobs: + build: + runs-on: windows-2022 + steps: + - name: Disable CRLF line ending substitution + run: | + git config --global core.autocrlf false + + - name: Enable Developer Command Prompt + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 + + - name: Install Crystal + uses: crystal-lang/install-crystal@v1 + with: + crystal: "1.11.2" + + - name: Download Crystal source + uses: actions/checkout@v4 + + - name: Restore libraries + uses: actions/cache/restore@v4 + with: + path: | + libs/pcre.lib + libs/pcre2-8.lib + libs/iconv.lib + libs/gc.lib + libs/ffi.lib + libs/z.lib + libs/mpir.lib + libs/yaml.lib + libs/xml2.lib + key: win-libs-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore OpenSSL + uses: actions/cache/restore@v4 + with: + path: | + libs/crypto.lib + libs/ssl.lib + libs/openssl_VERSION + key: win-openssl-libs-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore DLLs + uses: actions/cache/restore@v4 + with: + path: | + libs/pcre-dynamic.lib + libs/pcre2-8-dynamic.lib + libs/iconv-dynamic.lib + libs/gc-dynamic.lib + libs/ffi-dynamic.lib + libs/z-dynamic.lib + libs/mpir-dynamic.lib + libs/yaml-dynamic.lib + libs/xml2-dynamic.lib + dlls/pcre.dll + dlls/pcre2-8.dll + dlls/libiconv.dll + dlls/gc.dll + dlls/libffi.dll + dlls/zlib1.dll + dlls/mpir.dll + dlls/yaml.dll + dlls/libxml2.dll + key: win-dlls-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore OpenSSL DLLs + uses: actions/cache/restore@v4 + with: + path: | + libs/crypto-dynamic.lib + libs/ssl-dynamic.lib + dlls/libcrypto-3-x64.dll + dlls/libssl-3-x64.dll + key: win-openssl-dlls-3.1.0-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc + fail-on-cache-miss: true + - name: Restore LLVM + uses: actions/cache/restore@v4 + with: + path: llvm + key: llvm-libs-${{ inputs.llvm_version }}-msvc + fail-on-cache-miss: true + - name: Restore LLVM DLLs + uses: actions/cache/restore@v4 + with: + path: | + libs/llvm_VERSION + libs/llvm-dynamic.lib + dlls/LLVM-C.dll + key: llvm-dlls-${{ inputs.llvm_version }}-${{ hashFiles('etc/win-ci/build-llvm.ps1') }}-msvc + fail-on-cache-miss: true + + - name: Set up environment + run: | + echo "CRYSTAL_LIBRARY_PATH=$(pwd)\libs" >> ${env:GITHUB_ENV} + echo "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" >> ${env:GITHUB_ENV} + + - name: Build LLVM extensions + run: make -f Makefile.win deps + + - name: Build Crystal + run: | + bin/crystal.bat env + # TODO: the 1.11.2 compiler only understands `-Dpreview_dll`; remove this once the + # base compiler is updated + make -f Makefile.win -B FLAGS=-Dpreview_dll ${{ inputs.release && 'release=1' || '' }} + # TODO: 1.11.2 comes with LLVM 17's DLL and copies it to the output directory, but a + # dynamically linked compiler requires LLVM 18, so we must overwrite it; remove this + # line once the base compiler is updated + cp dlls/LLVM-C.dll .build/ + + - name: Download shards release + uses: actions/checkout@v4 + with: + repository: crystal-lang/shards + ref: v0.17.4 + path: shards + + - name: Download molinillo release + uses: actions/checkout@v4 + with: + repository: crystal-lang/crystal-molinillo + ref: v0.2.0 + path: shards/lib/molinillo + + - name: Build shards release + working-directory: ./shards + run: ../bin/crystal.bat build ${{ inputs.release && '--release' || '' }} src/shards.cr + + - name: Gather Crystal binaries + run: | + make -f Makefile.win install prefix=crystal + mkdir crystal/lib + cp shards/shards.exe crystal/ + cp libs/* crystal/lib/ + cp dlls/* crystal/ + cp README.md crystal/ + + - name: Upload Crystal binaries + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.release && 'crystal-release' || 'crystal' }} + path: crystal diff --git a/spec/compiler/data/visibility.h b/spec/compiler/data/visibility.h index 1817591c23ae..51db5113b4fa 100644 --- a/spec/compiler/data/visibility.h +++ b/spec/compiler/data/visibility.h @@ -1,7 +1,7 @@ -#ifdef _WIN32 - #define EXPORT __declspec(dllexport) - #define LOCAL -#else - #define EXPORT __attribute__ ((visibility ("default"))) - #define LOCAL __attribute__ ((visibility ("hidden"))) -#endif +#ifdef _WIN32 + #define EXPORT __declspec(dllexport) + #define LOCAL +#else + #define EXPORT __attribute__ ((visibility ("default"))) + #define LOCAL __attribute__ ((visibility ("hidden"))) +#endif From 9291ea48cd984c5e2fcb4a537bb8a04c7407c8f0 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Wed, 21 Feb 2024 20:25:28 +1000 Subject: [PATCH 0967/1551] Fix spelling in `spec/std/uri/params_spec.cr` (#14302) --- spec/std/uri/params_spec.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/std/uri/params_spec.cr b/spec/std/uri/params_spec.cr index 52903309ae66..8cc57f5a6915 100644 --- a/spec/std/uri/params_spec.cr +++ b/spec/std/uri/params_spec.cr @@ -317,7 +317,7 @@ class URI end describe "#merge!" do - it "modifies the reciever" do + it "modifies the receiver" do params = Params.parse("foo=bar&foo=baz&qux=zoo") other_params = Params.parse("foo=buzz&foo=extra") @@ -364,7 +364,7 @@ class URI params.merge(other_params, replace: false).to_s.should eq("foo=bar&foo=baz&foo=buzz&foo=extra&qux=zoo") end - it "does not modify the reciever" do + it "does not modify the receiver" do params = Params.parse("foo=bar&foo=baz&qux=zoo") other_params = Params.parse("foo=buzz&foo=extra") From 9866e500142f4974f2c21b2b29c8a89dcabe41f2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 21 Feb 2024 18:25:38 +0800 Subject: [PATCH 0968/1551] Add `BigRational#%`, `#tdiv`, `#remainder` (#14306) --- spec/std/big/big_rational_spec.cr | 72 +++++++++++++++++++++++++++++-- src/big/big_rational.cr | 46 ++++++++++++++++++++ 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr index 7b28a4938966..0dcc75c10b87 100644 --- a/spec/std/big/big_rational_spec.cr +++ b/spec/std/big/big_rational_spec.cr @@ -218,10 +218,74 @@ describe BigRational do end it "#//" do - (br(10, 7) // br(3, 7)).should eq(br(9, 3)) - expect_raises(DivisionByZeroError) { br(10, 7) / br(0, 10) } - (br(10, 7) // 3).should eq(0) - (1 // br(10, 7)).should eq(0) + (br(18, 7) // br(4, 5)).should eq(br(3, 1)) + (br(-18, 7) // br(4, 5)).should eq(br(-4, 1)) + (br(18, 7) // br(-4, 5)).should eq(br(-4, 1)) + (br(-18, 7) // br(-4, 5)).should eq(br(3, 1)) + + (br(18, 5) // 2).should eq(br(1, 1)) + (br(-18, 5) // 2).should eq(br(-2, 1)) + (br(18, 5) // -2).should eq(br(-2, 1)) + (br(-18, 5) // -2).should eq(br(1, 1)) + (br(18, 5) // 2.to_big_i).should eq(br(1, 1)) + + expect_raises(DivisionByZeroError) { br(18, 7) // br(0, 1) } + expect_raises(DivisionByZeroError) { br(18, 7) // 0 } + + (8 // br(10, 7)).should eq(5) + (-8 // br(10, 7)).should eq(-6) + (8 // br(-10, 7)).should eq(-6) + (-8 // br(-10, 7)).should eq(5) + + expect_raises(DivisionByZeroError) { 8 // br(0, 7) } + end + + it "#%" do + (br(18, 7) % br(4, 5)).should eq(br(6, 35)) + (br(-18, 7) % br(4, 5)).should eq(br(22, 35)) + (br(18, 7) % br(-4, 5)).should eq(br(-22, 35)) + (br(-18, 7) % br(-4, 5)).should eq(br(-6, 35)) + + (br(18, 5) % 2).should eq(br(8, 5)) + (br(-18, 5) % 2).should eq(br(2, 5)) + (br(18, 5) % -2).should eq(br(-2, 5)) + (br(-18, 5) % -2).should eq(br(-8, 5)) + (br(18, 5) % 2.to_big_i).should eq(br(8, 5)) + + expect_raises(DivisionByZeroError) { br(18, 7) % br(0, 1) } + expect_raises(DivisionByZeroError) { br(18, 7) % 0 } + end + + it "#tdiv" do + br(18, 7).tdiv(br(4, 5)).should eq(br(3, 1)) + br(-18, 7).tdiv(br(4, 5)).should eq(br(-3, 1)) + br(18, 7).tdiv(br(-4, 5)).should eq(br(-3, 1)) + br(-18, 7).tdiv(br(-4, 5)).should eq(br(3, 1)) + + br(18, 5).tdiv(2).should eq(br(1, 1)) + br(-18, 5).tdiv(2).should eq(br(-1, 1)) + br(18, 5).tdiv(-2).should eq(br(-1, 1)) + br(-18, 5).tdiv(-2).should eq(br(1, 1)) + br(18, 5).tdiv(2.to_big_i).should eq(br(1, 1)) + + expect_raises(DivisionByZeroError) { br(18, 7).tdiv(br(0, 1)) } + expect_raises(DivisionByZeroError) { br(18, 7).tdiv(0) } + end + + it "#remainder" do + br(18, 7).remainder(br(4, 5)).should eq(br(6, 35)) + br(-18, 7).remainder(br(4, 5)).should eq(br(-6, 35)) + br(18, 7).remainder(br(-4, 5)).should eq(br(6, 35)) + br(-18, 7).remainder(br(-4, 5)).should eq(br(-6, 35)) + + br(18, 5).remainder(2).should eq(br(8, 5)) + br(-18, 5).remainder(2).should eq(br(-8, 5)) + br(18, 5).remainder(-2).should eq(br(8, 5)) + br(-18, 5).remainder(-2).should eq(br(-8, 5)) + br(18, 5).remainder(2.to_big_i).should eq(br(8, 5)) + + expect_raises(DivisionByZeroError) { br(18, 7).remainder(br(0, 1)) } + expect_raises(DivisionByZeroError) { br(18, 7).remainder(0) } end it "#- (negation)" do diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index a9466a5466a0..b14c8f4b2d4a 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -151,6 +151,52 @@ struct BigRational < Number Number.expand_div [BigInt, BigFloat, BigDecimal], BigRational + def //(other : BigRational) : BigRational + check_division_by_zero other + BigRational.new((numerator * other.denominator) // (denominator * other.numerator)) + end + + def //(other : Int) : BigRational + check_division_by_zero other + BigRational.new(numerator // (denominator * other)) + end + + def %(other : BigRational) : BigRational + check_division_by_zero other + BigRational.new( + (numerator * other.denominator) % (denominator * other.numerator), + denominator * other.denominator, + ) + end + + def %(other : Int) : BigRational + check_division_by_zero other + BigRational.new(numerator % (denominator * other), denominator) + end + + def tdiv(other : BigRational) : BigRational + check_division_by_zero other + BigRational.new((numerator * other.denominator).tdiv(denominator * other.numerator)) + end + + def tdiv(other : Int) : BigRational + check_division_by_zero other + BigRational.new(numerator.tdiv(denominator * other)) + end + + def remainder(other : BigRational) : BigRational + check_division_by_zero other + BigRational.new( + (numerator * other.denominator).remainder(denominator * other.numerator), + denominator * other.denominator, + ) + end + + def remainder(other : Int) : BigRational + check_division_by_zero other + BigRational.new(numerator.remainder(denominator * other), denominator) + end + def ceil : BigRational BigRational.new(-(-numerator // denominator)) end From 35b8878bdec01a55816816cf4ac02f7ff44a7efb Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 21 Feb 2024 19:14:30 +0100 Subject: [PATCH 0969/1551] Fix: opening/reading from fifo/chardev files are blocking the thread (#14255) --- spec/std/file_spec.cr | 59 +++++++++++++++++++++++++++++++++++++++++++ src/file.cr | 45 +++++++++++++++++++++------------ 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 3c020fee36a1..1e9a9f53ae32 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -47,6 +47,65 @@ describe "File" do end end + describe "blocking" do + it "opens regular file as blocking" do + with_tempfile("regular") do |path| + File.open(path, "w") do |file| + file.blocking.should be_true + end + + File.open(path, "w", blocking: nil) do |file| + file.blocking.should be_true + end + end + end + + {% if flag?(:unix) %} + if File.exists?("/dev/tty") + it "opens character device" do + File.open("/dev/tty", "r") do |file| + file.blocking.should be_true + end + + File.open("/dev/tty", "r", blocking: false) do |file| + file.blocking.should be_false + end + + File.open("/dev/tty", "r", blocking: nil) do |file| + file.blocking.should be_false + end + rescue File::Error + # The TTY may not be available (e.g. Docker CI) + end + end + + {% if LibC.has_method?(:mkfifo) && !flag?(:interpreted) %} + # spec is disabled when interpreted because the interpreter doesn't + # support threads + it "opens fifo file as non-blocking" do + path = File.tempname("chardev") + ret = LibC.mkfifo(path, File::DEFAULT_CREATE_PERMISSIONS) + raise RuntimeError.from_errno("mkfifo") unless ret == 0 + + # FIXME: open(2) will block when opening a fifo file until another + # thread or process also opened the file; we should pass + # O_NONBLOCK to the open(2) call itself, not afterwards + file = nil + Thread.new { file = File.new(path, "w", blocking: nil) } + + begin + File.open(path, "r", blocking: false) do |file| + file.blocking.should be_false + end + ensure + File.delete(path) + file.try(&.close) + end + end + {% end %} + {% end %} + end + it "reads entire file" do str = File.read datapath("test_file.txt") str.should eq("Hello World\n" * 20) diff --git a/src/file.cr b/src/file.cr index c88beadb0de4..a011979dcc9b 100644 --- a/src/file.cr +++ b/src/file.cr @@ -126,7 +126,7 @@ class File < IO::FileDescriptor # This constructor is provided for subclasses to be able to initialize an # `IO::FileDescriptor` with a *path* and *fd*. - private def initialize(@path, fd, blocking = false, encoding = nil, invalid = nil) + private def initialize(@path, fd : Int, blocking = false, encoding = nil, invalid = nil) self.set_encoding(encoding, invalid: invalid) if encoding super(fd, blocking) end @@ -157,10 +157,23 @@ class File < IO::FileDescriptor # # Line endings are preserved on all platforms. The `b` mode flag has no # effect; it is provided only for POSIX compatibility. - def self.new(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil) + # + # *blocking* is set to `true` by default because system event queues (e.g. + # epoll, kqueue) will always report the file descriptor of regular disk files + # as ready. + # + # *blocking* must be set to `false` on POSIX targets when the file to open + # isn't a regular file but a character device (e.g. `/dev/tty`) or fifo. These + # files depend on another process or thread to also be reading or writing, and + # system event queues will properly report readyness. + # + # *blocking* may also be set to `nil` in which case the blocking or + # non-blocking flag will be determined automatically, at the expense of an + # additional syscall. + def self.new(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, blocking = true) filename = filename.to_s - fd = Crystal::System::File.open(filename, mode, perm) - new(filename, fd, blocking: true, encoding: encoding, invalid: invalid) + fd = Crystal::System::File.open(filename, mode, perm: perm) + new(filename, fd, blocking: blocking, encoding: encoding, invalid: invalid) end getter path : String @@ -712,8 +725,8 @@ class File < IO::FileDescriptor # permissions may be set using the *perm* parameter. # # See `self.new` for what *mode* can be. - def self.open(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil) : self - new filename, mode, perm, encoding, invalid + def self.open(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, blocking = true) : self + new filename, mode, perm, encoding, invalid, blocking end # Opens the file named by *filename*. If a file is being created, its initial @@ -721,8 +734,8 @@ class File < IO::FileDescriptor # file as an argument, the file will be automatically closed when the block returns. # # See `self.new` for what *mode* can be. - def self.open(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, &) - file = new filename, mode, perm, encoding, invalid + def self.open(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, blocking = true, &) + file = new filename, mode, perm, encoding, invalid, blocking begin yield file ensure @@ -736,8 +749,8 @@ class File < IO::FileDescriptor # File.write("bar", "foo") # File.read("bar") # => "foo" # ``` - def self.read(filename : Path | String, encoding = nil, invalid = nil) : String - open(filename, "r") do |file| + def self.read(filename : Path | String, encoding = nil, invalid = nil, blocking = true) : String + open(filename, "r", blocking: blocking) do |file| if encoding file.set_encoding(encoding, invalid: invalid) file.gets_to_end @@ -765,8 +778,8 @@ class File < IO::FileDescriptor # end # array # => ["foo", "bar"] # ``` - def self.each_line(filename : Path | String, encoding = nil, invalid = nil, chomp = true, &) - open(filename, "r", encoding: encoding, invalid: invalid) do |file| + def self.each_line(filename : Path | String, encoding = nil, invalid = nil, chomp = true, blocking = true, &) + open(filename, "r", encoding: encoding, invalid: invalid, blocking: blocking) do |file| file.each_line(chomp: chomp) do |line| yield line end @@ -779,9 +792,9 @@ class File < IO::FileDescriptor # File.write("foobar", "foo\nbar") # File.read_lines("foobar") # => ["foo", "bar"] # ``` - def self.read_lines(filename : Path | String, encoding = nil, invalid = nil, chomp = true) : Array(String) + def self.read_lines(filename : Path | String, encoding = nil, invalid = nil, chomp = true, blocking = true) : Array(String) lines = [] of String - each_line(filename, encoding: encoding, invalid: invalid, chomp: chomp) do |line| + each_line(filename, encoding: encoding, invalid: invalid, chomp: chomp, blocking: blocking) do |line| lines << line end lines @@ -804,8 +817,8 @@ class File < IO::FileDescriptor # (the result of invoking `to_s` on *content*). # # See `self.new` for what *mode* can be. - def self.write(filename : Path | String, content, perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, mode = "w") - open(filename, mode, perm, encoding: encoding, invalid: invalid) do |file| + def self.write(filename : Path | String, content, perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, mode = "w", blocking = true) + open(filename, mode, perm, encoding: encoding, invalid: invalid, blocking: blocking) do |file| case content when Bytes file.sync = true From 0a2203bf2ee01035d68c460359219e61e412d0ac Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 22 Feb 2024 02:14:38 +0800 Subject: [PATCH 0970/1551] Make equality between `Complex` and other numbers exact (#14309) --- spec/std/complex_spec.cr | 11 +++++++++++ src/complex.cr | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/spec/std/complex_spec.cr b/spec/std/complex_spec.cr index fe0f1ac628ac..65add18f8533 100644 --- a/spec/std/complex_spec.cr +++ b/spec/std/complex_spec.cr @@ -1,6 +1,9 @@ require "spec" require "complex" require "../support/number" +{% unless flag?(:wasm32) %} + require "big" +{% end %} # exact equality, including component signs private def assert_complex_eq(z1 : Complex, z2 : Complex, *, file = __FILE__, line = __LINE__) @@ -49,6 +52,10 @@ describe "Complex" do c = 4.2 (a == b).should be_true (a == c).should be_false + + {% unless flag?(:wasm32) %} + (a == BigDecimal.new(53, 1)).should be_false + {% end %} end it "number == complex" do @@ -57,6 +64,10 @@ describe "Complex" do c = Complex.new(7.2, 0) (a == b).should be_true (a == c).should be_false + + {% unless flag?(:wasm32) %} + (BigDecimal.new(72, 1) == c).should be_false + {% end %} end end diff --git a/src/complex.cr b/src/complex.cr index bbe7eb54e921..90c3397e06ce 100644 --- a/src/complex.cr +++ b/src/complex.cr @@ -37,7 +37,7 @@ struct Complex # :ditto: def ==(other : Number) - self == other.to_c + @real == other && @imag.zero? end # :ditto: @@ -307,7 +307,7 @@ struct Number end def ==(other : Complex) - to_c == other + other == self end def cis : Complex From d55712b2948b3337d69b09ad3eb6b3dc3387b9c2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 22 Feb 2024 02:15:24 +0800 Subject: [PATCH 0971/1551] Ensure Windows time zone specs request `SeTimeZonePrivilege` properly (#14297) --- spec/support/time.cr | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/spec/support/time.cr b/spec/support/time.cr index 80f10f37d8cf..d550a83af2c3 100644 --- a/spec/support/time.cr +++ b/spec/support/time.cr @@ -56,8 +56,7 @@ end TokenPrivileges = 3 - SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001_u32 - SE_PRIVILEGE_ENABLED = 0x00000002_u32 + SE_PRIVILEGE_ENABLED = 0x00000002_u32 fun OpenProcessToken(processHandle : HANDLE, desiredAccess : DWORD, tokenHandle : HANDLE*) : BOOL fun GetTokenInformation(tokenHandle : HANDLE, tokenInformationClass : Int, tokenInformation : Void*, tokenInformationLength : DWORD, returnLength : DWORD*) : BOOL @@ -78,19 +77,25 @@ end raise RuntimeError.from_winerror("LookupPrivilegeValueW") end + # if the process token already has the privilege, and the privilege is already enabled, + # we don't need to do anything else if LibC.OpenProcessToken(LibC.GetCurrentProcess, LibC::TOKEN_QUERY, out token) != 0 begin LibC.GetTokenInformation(token, LibC::TokenPrivileges, nil, 0, out len) buf = Pointer(UInt8).malloc(len).as(LibC::TOKEN_PRIVILEGES*) LibC.GetTokenInformation(token, LibC::TokenPrivileges, buf, len, out _) privileges = Slice.new(pointerof(buf.value.@privileges).as(LibC::LUID_AND_ATTRIBUTES*), buf.value.privilegeCount) - return true if privileges.any? { |pr| pr.luid == time_zone_luid && pr.attributes & (LibC::SE_PRIVILEGE_ENABLED_BY_DEFAULT | LibC::SE_PRIVILEGE_ENABLED) != 0 } + # if the process token doesn't have the privilege, there is no way + # `AdjustTokenPrivileges` could grant or enable it + privilege = privileges.find(&.luid.== time_zone_luid) + return false unless privilege + return true if privilege.attributes.bits_set?(LibC::SE_PRIVILEGE_ENABLED) ensure LibC.CloseHandle(token) end end - if LibC.OpenProcessToken(LibC.GetCurrentProcess, LibC::TOKEN_ADJUST_PRIVILEGES | LibC::TOKEN_QUERY, out adjust_token) != 0 + if LibC.OpenProcessToken(LibC.GetCurrentProcess, LibC::TOKEN_ADJUST_PRIVILEGES, out adjust_token) != 0 new_privileges = LibC::TOKEN_PRIVILEGES.new( privilegeCount: 1, privileges: StaticArray[ @@ -101,7 +106,7 @@ end ], ) if LibC.AdjustTokenPrivileges(adjust_token, 0, pointerof(new_privileges), 0, nil, nil) != 0 - return true + return true if WinError.value.error_success? end end From 677cab3f5d63a4ba05de708f11d20dc42a6b7b20 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 22 Feb 2024 15:58:03 +0800 Subject: [PATCH 0972/1551] Add MSVC invalid parameter handler (#14313) --- src/exception/call_stack/stackwalk.cr | 10 ++++++++++ src/lib_c/x86_64-windows-msvc/c/stdint.cr | 1 + src/lib_c/x86_64-windows-msvc/c/stdlib.cr | 3 +++ 3 files changed, 14 insertions(+) diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index 47b78e0ee0df..2b9a03b472c7 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -55,6 +55,16 @@ struct Exception::CallStack # `Crystal::System::Thread.thread_proc`) stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE LibC.SetThreadStackGuarantee(pointerof(stack_size)) + + # this catches invalid argument checks inside the C runtime library + LibC._set_invalid_parameter_handler(->(expression, _function, _file, _line, _pReserved) do + message = expression ? String.from_utf16(expression)[0] : "(no message)" + Crystal::System.print_error "CRT invalid parameter handler invoked: %s\n", message + caller.each do |frame| + Crystal::System.print_error " from %s\n", frame + end + LibC._exit(1) + end) end {% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %} diff --git a/src/lib_c/x86_64-windows-msvc/c/stdint.cr b/src/lib_c/x86_64-windows-msvc/c/stdint.cr index 25c654659653..64a4897df6c3 100644 --- a/src/lib_c/x86_64-windows-msvc/c/stdint.cr +++ b/src/lib_c/x86_64-windows-msvc/c/stdint.cr @@ -1,3 +1,4 @@ lib LibC alias IntPtrT = Int64 + alias UIntPtrT = UInt64 end diff --git a/src/lib_c/x86_64-windows-msvc/c/stdlib.cr b/src/lib_c/x86_64-windows-msvc/c/stdlib.cr index 0e892d2e0999..a761cce57240 100644 --- a/src/lib_c/x86_64-windows-msvc/c/stdlib.cr +++ b/src/lib_c/x86_64-windows-msvc/c/stdlib.cr @@ -16,4 +16,7 @@ lib LibC fun realloc(ptr : Void*, size : SizeT) : Void* fun strtof(nptr : Char*, endptr : Char**) : Float fun strtod(nptr : Char*, endptr : Char**) : Double + + alias InvalidParameterHandler = WCHAR*, WCHAR*, WCHAR*, UInt, UIntPtrT -> + fun _set_invalid_parameter_handler(pNew : InvalidParameterHandler) : InvalidParameterHandler end From 0045aa5598cb3ea870c0d4441e498df75b8f21cb Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 22 Feb 2024 23:03:30 +0800 Subject: [PATCH 0973/1551] Don't copy DLL to output directory if file already exists (#14315) --- src/compiler/crystal/compiler.cr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index cb745a447571..416e29bf2293 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -365,7 +365,8 @@ module Crystal program.each_dll_path do |path, found| if found - FileUtils.cp(path, output_directory) + dest = File.join(output_directory, File.basename(path)) + File.copy(path, dest) unless File.exists?(dest) else not_found ||= [] of String not_found << path From 812a2bbfee6e10714965e3cc84418e2b697cf1d1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 24 Feb 2024 06:14:44 +0800 Subject: [PATCH 0974/1551] Replace some Microsoft C runtime funs with Win32 equivalents (#14316) --- spec/std/dir_spec.cr | 2 +- src/crystal/system/print_error.cr | 2 +- src/crystal/system/win32/dir.cr | 8 ++--- src/crystal/system/win32/file.cr | 29 +++++++++++------ src/crystal/system/win32/file_descriptor.cr | 33 +++++++++++--------- src/lib_c/x86_64-windows-msvc/c/direct.cr | 1 + src/lib_c/x86_64-windows-msvc/c/fileapi.cr | 7 +++++ src/lib_c/x86_64-windows-msvc/c/io.cr | 18 ++++++----- src/lib_c/x86_64-windows-msvc/c/stdio.cr | 1 + src/lib_c/x86_64-windows-msvc/c/stdlib.cr | 8 +++-- src/lib_c/x86_64-windows-msvc/c/sys/stat.cr | 1 + src/lib_c/x86_64-windows-msvc/c/sys/utime.cr | 1 + src/lib_c/x86_64-windows-msvc/c/winnt.cr | 13 +++++--- 13 files changed, 77 insertions(+), 47 deletions(-) diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index f0c01d613570..c2d86eff7bb2 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -127,7 +127,7 @@ describe "Dir" do context "path exists" do it "fails when path is a file" do - expect_raises(File::AlreadyExistsError, "Unable to create directory: '#{datapath("test_file.txt").inspect_unquoted}': File exists") do + expect_raises(File::AlreadyExistsError, "Unable to create directory: '#{datapath("test_file.txt").inspect_unquoted}'") do Dir.mkdir_p(datapath("test_file.txt")) end end diff --git a/src/crystal/system/print_error.cr b/src/crystal/system/print_error.cr index 3832eed5d9f4..390eea492097 100644 --- a/src/crystal/system/print_error.cr +++ b/src/crystal/system/print_error.cr @@ -7,7 +7,7 @@ module Crystal::System {% if flag?(:unix) || flag?(:wasm32) %} LibC.write 2, bytes, bytes.size {% elsif flag?(:win32) %} - LibC._write 2, bytes, bytes.size + LibC.WriteFile(LibC.GetStdHandle(LibC::STD_ERROR_HANDLE), bytes, bytes.size, out _, nil) {% end %} end end diff --git a/src/crystal/system/win32/dir.cr b/src/crystal/system/win32/dir.cr index 76165e75d8e2..f53b2eafdd98 100644 --- a/src/crystal/system/win32/dir.cr +++ b/src/crystal/system/win32/dir.cr @@ -147,8 +147,8 @@ module Crystal::System::Dir end def self.create(path : String, mode : Int32) : Nil - if LibC._wmkdir(System.to_wstr(path)) == -1 - raise ::File::Error.from_errno("Unable to create directory", file: path) + if LibC.CreateDirectoryW(System.to_wstr(path), nil) == 0 + raise ::File::Error.from_winerror("Unable to create directory", file: path) end end @@ -179,8 +179,8 @@ module Crystal::System::Dir end end - return true if LibC._wrmdir(win_path) == 0 + return true if LibC.RemoveDirectoryW(win_path) != 0 LibC.SetFileAttributesW(win_path, attributes) if read_only_removed - raise ::File::Error.from_errno("Unable to remove directory", file: path) + raise ::File::Error.from_winerror("Unable to remove directory", file: path) end end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 43b2dd8142ec..228704bbd48e 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -58,15 +58,16 @@ module Crystal::System::File private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions) access = if flags.bits_set? LibC::O_WRONLY - LibC::GENERIC_WRITE + LibC::FILE_GENERIC_WRITE elsif flags.bits_set? LibC::O_RDWR - LibC::GENERIC_READ | LibC::GENERIC_WRITE + LibC::FILE_GENERIC_READ | LibC::FILE_GENERIC_WRITE else - LibC::GENERIC_READ + LibC::FILE_GENERIC_READ end if flags.bits_set? LibC::O_APPEND access |= LibC::FILE_APPEND_DATA + access &= ~LibC::FILE_WRITE_DATA end if flags.bits_set? LibC::O_TRUNC @@ -277,10 +278,10 @@ module Crystal::System::File # all reparse point directories should be deleted like a directory, not just # symbolic links, so we don't care about the reparse tag here is_reparse_dir = attributes.bits_set?(LibC::FILE_ATTRIBUTE_REPARSE_POINT) && attributes.bits_set?(LibC::FILE_ATTRIBUTE_DIRECTORY) - result = is_reparse_dir ? LibC._wrmdir(win_path) : LibC._wunlink(win_path) - return true if result == 0 + result = is_reparse_dir ? LibC.RemoveDirectoryW(win_path) : LibC.DeleteFileW(win_path) + return true if result != 0 LibC.SetFileAttributesW(win_path, attributes) if read_only_removed - raise ::File::Error.from_errno("Error deleting file", file: path) + raise ::File::Error.from_winerror("Error deleting file", file: path) end private REALPATH_SYMLINK_LIMIT = 100 @@ -451,8 +452,16 @@ module Crystal::System::File end private def system_truncate(size : Int) : Nil - if LibC._chsize_s(fd, size) != 0 - raise ::File::Error.from_errno("Error truncating file", file: path) + handle = windows_handle + if LibC.SetFilePointerEx(handle, size.to_i64, out old_pos, IO::Seek::Set) == 0 + raise ::File::Error.from_winerror("Error truncating file", file: path) + end + begin + if LibC.SetEndOfFile(handle) == 0 + raise ::File::Error.from_winerror("Error truncating file", file: path) + end + ensure + LibC.SetFilePointerEx(handle, old_pos, nil, IO::Seek::Set) end end @@ -508,8 +517,8 @@ module Crystal::System::File end private def system_fsync(flush_metadata = true) : Nil - if LibC._commit(fd) != 0 - raise IO::Error.from_errno("Error syncing file", target: self) + if LibC.FlushFileBuffers(windows_handle) == 0 + raise IO::Error.from_winerror("Error syncing file", target: self) end end end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 109c353c0325..bdcf554cf1cb 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -15,12 +15,14 @@ module Crystal::System::FileDescriptor if ConsoleUtils.console?(handle) ConsoleUtils.read(handle, slice) elsif system_blocking? - bytes_read = LibC._read(fd, slice, slice.size) - if bytes_read == -1 - if Errno.value == Errno::EBADF + if LibC.ReadFile(handle, slice, slice.size, out bytes_read, nil) == 0 + case error = WinError.value + when .error_access_denied? raise IO::Error.new "File not open for reading", target: self + when .error_broken_pipe? + return 0_u32 else - raise IO::Error.from_errno("Error reading file", target: self) + raise IO::Error.from_os_error("Error reading file", error, target: self) end end bytes_read @@ -33,18 +35,20 @@ module Crystal::System::FileDescriptor end private def unbuffered_write(slice : Bytes) + handle = windows_handle until slice.empty? if system_blocking? - bytes_written = LibC._write(fd, slice, slice.size) - if bytes_written == -1 - if Errno.value == Errno::EBADF + if LibC.WriteFile(handle, slice, slice.size, out bytes_written, nil) == 0 + case error = WinError.value + when .error_access_denied? raise IO::Error.new "File not open for writing", target: self + when .error_broken_pipe? + return 0_u32 else - raise IO::Error.from_errno("Error writing file", target: self) + raise IO::Error.from_os_error("Error writing file", error, target: self) end end else - handle = windows_handle bytes_written = overlapped_operation(handle, "WriteFile", write_timeout, writing: true) do |overlapped| ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped) {ret, byte_count} @@ -128,16 +132,15 @@ module Crystal::System::FileDescriptor end private def system_seek(offset, whence : IO::Seek) : Nil - seek_value = LibC._lseeki64(fd, offset, whence) - - if seek_value == -1 - raise IO::Error.from_errno "Unable to seek", target: self + if LibC.SetFilePointerEx(windows_handle, offset, nil, whence) == 0 + raise IO::Error.from_winerror("Unable to seek", target: self) end end private def system_pos - pos = LibC._lseeki64(fd, 0, IO::Seek::Current) - raise IO::Error.from_errno("Unable to tell", target: self) if pos == -1 + if LibC.SetFilePointerEx(windows_handle, 0, out pos, IO::Seek::Current) == 0 + raise IO::Error.from_winerror("Unable to tell", target: self) + end pos end diff --git a/src/lib_c/x86_64-windows-msvc/c/direct.cr b/src/lib_c/x86_64-windows-msvc/c/direct.cr index 44867281a1cd..f9f100015c30 100644 --- a/src/lib_c/x86_64-windows-msvc/c/direct.cr +++ b/src/lib_c/x86_64-windows-msvc/c/direct.cr @@ -1,6 +1,7 @@ require "c/winnt" lib LibC + # unused fun _wmkdir(dirname : WCHAR*) : Int fun _wrmdir(dirname : WCHAR*) : Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr index 88b485c0981b..c17c0fb48a9a 100644 --- a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr @@ -71,9 +71,16 @@ lib LibC fun CreateFileW(lpFileName : LPWSTR, dwDesiredAccess : DWORD, dwShareMode : DWORD, lpSecurityAttributes : SECURITY_ATTRIBUTES*, dwCreationDisposition : DWORD, dwFlagsAndAttributes : DWORD, hTemplateFile : HANDLE) : HANDLE + fun DeleteFileW(lpFileName : LPWSTR) : BOOL fun ReadFile(hFile : HANDLE, lpBuffer : Void*, nNumberOfBytesToRead : DWORD, lpNumberOfBytesRead : DWORD*, lpOverlapped : OVERLAPPED*) : BOOL fun WriteFile(hFile : HANDLE, lpBuffer : Void*, nNumberOfBytesToWrite : DWORD, lpNumberOfBytesWritten : DWORD*, lpOverlapped : OVERLAPPED*) : BOOL + fun SetFilePointerEx(hFile : HANDLE, liDistanceToMove : LARGE_INTEGER, lpNewFilePointer : LARGE_INTEGER*, dwMoveMethod : DWORD) : BOOL + fun SetEndOfFile(hFile : HANDLE) : BOOL + fun FlushFileBuffers(hFile : HANDLE) : BOOL + + fun CreateDirectoryW(lpPathName : LPWSTR, lpSecurityAttributes : SECURITY_ATTRIBUTES*) : BOOL + fun RemoveDirectoryW(lpPathName : LPWSTR) : BOOL MAX_PATH = 260 diff --git a/src/lib_c/x86_64-windows-msvc/c/io.cr b/src/lib_c/x86_64-windows-msvc/c/io.cr index 8aaae731cfd6..e5be8964765e 100644 --- a/src/lib_c/x86_64-windows-msvc/c/io.cr +++ b/src/lib_c/x86_64-windows-msvc/c/io.cr @@ -1,22 +1,24 @@ require "c/stdint" lib LibC + fun _isatty(fd : Int) : Int + fun _close(fd : Int) : Int + fun _waccess_s(path : WCHAR*, mode : Int) : ErrnoT + fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT + fun _get_osfhandle(fd : Int) : IntPtrT + fun _dup2(fd1 : Int, fd2 : Int) : Int + fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int + fun _setmode(fd : LibC::Int, mode : LibC::Int) : LibC::Int + + # unused fun _write(fd : Int, buffer : UInt8*, count : UInt) : Int fun _read(fd : Int, buffer : UInt8*, count : UInt) : Int fun _lseeki64(fd : Int, offset : Int64, origin : Int) : Int64 - fun _isatty(fd : Int) : Int - fun _close(fd : Int) : Int fun _wopen(filename : WCHAR*, oflag : Int, ...) : Int - fun _waccess_s(path : WCHAR*, mode : Int) : ErrnoT fun _wchmod(filename : WCHAR*, pmode : Int) : Int fun _wunlink(filename : WCHAR*) : Int fun _wmktemp_s(template : WCHAR*, sizeInChars : SizeT) : ErrnoT - fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT fun _chsize_s(fd : Int, size : Int64) : ErrnoT - fun _get_osfhandle(fd : Int) : IntPtrT fun _pipe(pfds : Int*, psize : UInt, textmode : Int) : Int - fun _dup2(fd1 : Int, fd2 : Int) : Int fun _commit(fd : Int) : Int - fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int - fun _setmode(fd : LibC::Int, mode : LibC::Int) : LibC::Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/stdio.cr b/src/lib_c/x86_64-windows-msvc/c/stdio.cr index 05ca6bb612a2..f23bba8503f6 100644 --- a/src/lib_c/x86_64-windows-msvc/c/stdio.cr +++ b/src/lib_c/x86_64-windows-msvc/c/stdio.cr @@ -2,6 +2,7 @@ require "./stddef" @[Link("legacy_stdio_definitions")] lib LibC + # unused fun printf(format : Char*, ...) : Int fun rename(old : Char*, new : Char*) : Int fun snprintf(buffer : Char*, count : SizeT, format : Char*, ...) : Int diff --git a/src/lib_c/x86_64-windows-msvc/c/stdlib.cr b/src/lib_c/x86_64-windows-msvc/c/stdlib.cr index a761cce57240..63c38003fd6a 100644 --- a/src/lib_c/x86_64-windows-msvc/c/stdlib.cr +++ b/src/lib_c/x86_64-windows-msvc/c/stdlib.cr @@ -6,17 +6,19 @@ lib LibC rem : Int end - fun atof(nptr : Char*) : Double - fun div(numer : Int, denom : Int) : DivT fun exit(status : Int) : NoReturn fun _exit(status : Int) : NoReturn fun free(ptr : Void*) : Void fun malloc(size : SizeT) : Void* - fun putenv(string : Char*) : Int fun realloc(ptr : Void*, size : SizeT) : Void* fun strtof(nptr : Char*, endptr : Char**) : Float fun strtod(nptr : Char*, endptr : Char**) : Double alias InvalidParameterHandler = WCHAR*, WCHAR*, WCHAR*, UInt, UIntPtrT -> fun _set_invalid_parameter_handler(pNew : InvalidParameterHandler) : InvalidParameterHandler + + # unused + fun atof(nptr : Char*) : Double + fun div(numer : Int, denom : Int) : DivT + fun putenv(string : Char*) : Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/sys/stat.cr b/src/lib_c/x86_64-windows-msvc/c/sys/stat.cr index 6fc2f099b016..a45517026624 100644 --- a/src/lib_c/x86_64-windows-msvc/c/sys/stat.cr +++ b/src/lib_c/x86_64-windows-msvc/c/sys/stat.cr @@ -26,6 +26,7 @@ lib LibC st_ctime : Time64T end + # unused fun _wstat64(path : WCHAR*, buffer : Stat64*) : Int fun _fstat64(fd : Int, buffer : Stat64*) : Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/sys/utime.cr b/src/lib_c/x86_64-windows-msvc/c/sys/utime.cr index 188e1575a477..981da2f5b4dc 100644 --- a/src/lib_c/x86_64-windows-msvc/c/sys/utime.cr +++ b/src/lib_c/x86_64-windows-msvc/c/sys/utime.cr @@ -6,5 +6,6 @@ lib LibC modtime : Time64T end + # unused fun _wutime64(filename : WCHAR*, times : Utimbuf64*) : Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index addd17e2fa1b..0c6a0db3c986 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -21,11 +21,14 @@ lib LibC FILE_ATTRIBUTE_REPARSE_POINT = 0x400 FILE_ATTRIBUTE_SYSTEM = 0x4 - FILE_APPEND_DATA = 0x00000004 - - DELETE = 0x00010000 - FILE_READ_ATTRIBUTES = 0x80 - FILE_WRITE_ATTRIBUTES = 0x0100 + DELETE = 0x00010000_u32 + + FILE_WRITE_DATA = 0x00000002_u32 + FILE_APPEND_DATA = 0x00000004_u32 + FILE_READ_ATTRIBUTES = 0x00000080_u32 + FILE_WRITE_ATTRIBUTES = 0x00000100_u32 + FILE_GENERIC_READ = 0x00120089_u32 + FILE_GENERIC_WRITE = 0x00120116_u32 MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000 From 213e009d28111bc5ab8c48e98e2b2ab19406797e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 24 Feb 2024 20:00:01 +0800 Subject: [PATCH 0975/1551] Fix `#hash` for the `Big*` number types (#14308) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/big/big_rational_spec.cr | 6 --- spec/std/crystal/hasher_spec.cr | 83 ++++++++++++++++++++++++++++++- src/big/big_decimal.cr | 23 +++++++-- src/big/big_float.cr | 3 -- src/big/big_int.cr | 21 +++----- src/big/big_rational.cr | 20 +++----- src/big/lib_gmp.cr | 1 + src/int.cr | 26 ++++++++++ 8 files changed, 141 insertions(+), 42 deletions(-) diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr index 0dcc75c10b87..ec8ed3d0621b 100644 --- a/spec/std/big/big_rational_spec.cr +++ b/spec/std/big/big_rational_spec.cr @@ -466,12 +466,6 @@ describe BigRational do it { br(7, -3).integer?.should be_false } end - it "#hash" do - b = br(10, 3) - hash = b.hash - hash.should eq(b.to_f64.hash) - end - it "is a number" do br(10, 3).is_a?(Number).should be_true end diff --git a/spec/std/crystal/hasher_spec.cr b/spec/std/crystal/hasher_spec.cr index 099b49af7755..456f8ce844a1 100644 --- a/spec/std/crystal/hasher_spec.cr +++ b/spec/std/crystal/hasher_spec.cr @@ -305,13 +305,14 @@ describe "Crystal::Hasher" do Crystal::Hasher.reduce_num(Float64::MAX).should eq(0x1F00_FFFF_FFFF_FFFF_u64) end - pending "reduces BigInt" do + it "reduces BigInt" do Crystal::Hasher.reduce_num(0.to_big_i).should eq(0_u64) Crystal::Hasher.reduce_num(1.to_big_i).should eq(1_u64) Crystal::Hasher.reduce_num((-1).to_big_i).should eq(UInt64::MAX) (1..300).each do |i| Crystal::Hasher.reduce_num(2.to_big_i ** i).should eq(1_u64 << (i % 61)) + Crystal::Hasher.reduce_num(-(2.to_big_i ** i)).should eq(&-(1_u64 << (i % 61))) end end @@ -324,8 +325,88 @@ describe "Crystal::Hasher" do (1..300).each do |i| Crystal::Hasher.reduce_num(2.to_big_f ** i).should eq(1_u64 << (i % 61)) + Crystal::Hasher.reduce_num(-(2.to_big_f ** i)).should eq(&-(1_u64 << (i % 61))) Crystal::Hasher.reduce_num(0.5.to_big_f ** i).should eq(1_u64 << ((-i) % 61)) + Crystal::Hasher.reduce_num(-(0.5.to_big_f ** i)).should eq(&-(1_u64 << ((-i) % 61))) end end + + it "reduces BigDecimal" do + Crystal::Hasher.reduce_num(0.to_big_d).should eq(0_u64) + Crystal::Hasher.reduce_num(1.to_big_d).should eq(1_u64) + Crystal::Hasher.reduce_num((-1).to_big_d).should eq(UInt64::MAX) + + # small inverse powers of 10 + Crystal::Hasher.reduce_num(BigDecimal.new(1, 1)).should eq(0x1CCCCCCCCCCCCCCC_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 2)).should eq(0x0FAE147AE147AE14_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 3)).should eq(0x0E5E353F7CED9168_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 4)).should eq(0x14A305532617C1BD_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 5)).should eq(0x05438088509BF9C6_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 6)).should eq(0x06ED2674080F98FA_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 7)).should eq(0x1A4AEA3ECD9B28E5_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 8)).should eq(0x12A1176CAE291DB0_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 9)).should eq(0x01DCE8BE116A82F8_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 10)).should eq(0x1362E41301BDD9E5_u64) + + # a^(p-1) === 1 (mod p) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFFE_u64)).should eq(1_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFFD_u64)).should eq(10_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFFC_u64)).should eq(100_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFFB_u64)).should eq(1000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFFA_u64)).should eq(10000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFF9_u64)).should eq(100000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFF8_u64)).should eq(1000000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFF7_u64)).should eq(10000000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFF6_u64)).should eq(100000000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFF5_u64)).should eq(1000000000_u64) + + (1..300).each do |i| + Crystal::Hasher.reduce_num(2.to_big_d ** i).should eq(1_u64 << (i % 61)) + Crystal::Hasher.reduce_num(-(2.to_big_d ** i)).should eq(&-(1_u64 << (i % 61))) + Crystal::Hasher.reduce_num(0.5.to_big_d ** i).should eq(1_u64 << ((-i) % 61)) + Crystal::Hasher.reduce_num(-(0.5.to_big_d ** i)).should eq(&-(1_u64 << ((-i) % 61))) + end + end + + it "reduces BigRational" do + Crystal::Hasher.reduce_num(0.to_big_r).should eq(0_u64) + Crystal::Hasher.reduce_num(1.to_big_r).should eq(1_u64) + Crystal::Hasher.reduce_num((-1).to_big_r).should eq(UInt64::MAX) + + # inverses of small integers + Crystal::Hasher.reduce_num(BigRational.new(1, 2)).should eq(0x1000000000000000_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 3)).should eq(0x1555555555555555_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 4)).should eq(0x0800000000000000_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 5)).should eq(0x1999999999999999_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 6)).should eq(0x1AAAAAAAAAAAAAAA_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 7)).should eq(0x1B6DB6DB6DB6DB6D_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 8)).should eq(0x0400000000000000_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 9)).should eq(0x1C71C71C71C71C71_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 10)).should eq(0x1CCCCCCCCCCCCCCC_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 11)).should eq(0x1D1745D1745D1745_u64) + + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1000000000000000_u64)).should eq(2_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1555555555555555_u64)).should eq(3_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x0800000000000000_u64)).should eq(4_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1999999999999999_u64)).should eq(5_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1AAAAAAAAAAAAAAA_u64)).should eq(6_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1B6DB6DB6DB6DB6D_u64)).should eq(7_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x0400000000000000_u64)).should eq(8_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1C71C71C71C71C71_u64)).should eq(9_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1CCCCCCCCCCCCCCC_u64)).should eq(10_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1D1745D1745D1745_u64)).should eq(11_u64) + + (1..300).each do |i| + Crystal::Hasher.reduce_num(2.to_big_r ** i).should eq(1_u64 << (i % 61)) + Crystal::Hasher.reduce_num(-(2.to_big_r ** i)).should eq(&-(1_u64 << (i % 61))) + Crystal::Hasher.reduce_num(0.5.to_big_r ** i).should eq(1_u64 << ((-i) % 61)) + Crystal::Hasher.reduce_num(-(0.5.to_big_r ** i)).should eq(&-(1_u64 << ((-i) % 61))) + end + + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1FFF_FFFF_FFFF_FFFF_u64)).should eq(Crystal::Hasher::HASH_INF_PLUS) + Crystal::Hasher.reduce_num(BigRational.new(-1, 0x1FFF_FFFF_FFFF_FFFF_u64)).should eq(Crystal::Hasher::HASH_INF_MINUS) + Crystal::Hasher.reduce_num(BigRational.new(2, 0x1FFF_FFFF_FFFF_FFFF_u64)).should eq(Crystal::Hasher::HASH_INF_PLUS) + Crystal::Hasher.reduce_num(BigRational.new(-2, 0x1FFF_FFFF_FFFF_FFFF_u64)).should eq(Crystal::Hasher::HASH_INF_MINUS) + end end end diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index ab42c64bf919..a2c95fac2fff 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -753,10 +753,6 @@ struct BigDecimal < Number self end - def hash(hasher) - hasher.string(to_s) - end - # Returns the *quotient* as absolutely negative if `self` and *other* have # different signs, otherwise returns the *quotient*. def normalize_quotient(other : BigDecimal, quotient : BigInt) : BigInt @@ -879,3 +875,22 @@ class String BigDecimal.new(self) end end + +# :nodoc: +struct Crystal::Hasher + def self.reduce_num(value : BigDecimal) + v = reduce_num(value.value.abs) + + # v = UInt64.mulmod(v, 10_u64.powmod(-scale, HASH_MODULUS), HASH_MODULUS) + # TODO: consider #7516 or similar + scale = value.scale + x = 0x1ccc_cccc_cccc_cccc_u64 # 10^-1 (mod HASH_MODULUS) + while scale > 0 + v = UInt64.mulmod(v, x, HASH_MODULUS) if scale.bits_set?(1) + scale = scale.unsafe_shr(1) + x = UInt64.mulmod(x, x, HASH_MODULUS) + end + + v &* value.sign + end +end diff --git a/src/big/big_float.cr b/src/big/big_float.cr index 2cf224d7484a..cadc91282fc1 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -77,9 +77,6 @@ struct BigFloat < Float new(mpf) end - # TODO: improve this - def_hash to_f64 - def self.default_precision LibGMP.mpf_get_default_prec end diff --git a/src/big/big_int.cr b/src/big/big_int.cr index 1e3c3430c542..49738cb8bfbc 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -488,9 +488,6 @@ struct BigInt < Int LibGMP.sizeinbase(self, 2).to_i end - # TODO: check hash equality for numbers >= 2**63 - def_hash to_i64! - def to_s(base : Int = 10, *, precision : Int = 1, upcase : Bool = false) : String raise ArgumentError.new("Invalid base #{base}") unless 2 <= base <= 36 || base == 62 raise ArgumentError.new("upcase must be false for base 62") if upcase && base == 62 @@ -985,18 +982,14 @@ end # :nodoc: struct Crystal::Hasher - private HASH_MODULUS_INT_P = BigInt.new((1_u64 << HASH_BITS) - 1) - private HASH_MODULUS_INT_N = -BigInt.new((1_u64 << HASH_BITS) - 1) + private HASH_MODULUS_INT_P = BigInt.new(HASH_MODULUS) def self.reduce_num(value : BigInt) - # it should calculate `remainder(HASH_MODULUS)` - if LibGMP::UI == UInt64 - v = LibGMP.tdiv_ui(value, HASH_MODULUS).to_i64 - value < 0 ? -v : v - elsif value >= HASH_MODULUS_INT_P || value <= HASH_MODULUS_INT_N - value.unsafe_truncated_mod(HASH_MODULUS_INT_P).to_i64 - else - value.to_i64 - end + {% if LibGMP::UI == UInt64 %} + v = LibGMP.tdiv_ui(value, HASH_MODULUS) + value < 0 ? &-v : v + {% else %} + value.remainder(HASH_MODULUS_INT_P).to_u64! + {% end %} end end diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index b14c8f4b2d4a..cb4731dc4e95 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -285,9 +285,6 @@ struct BigRational < Number BigRational.new { |mpq| LibGMP.mpq_abs(mpq, self) } end - # TODO: improve this - def_hash to_f64 - # Returns the `Float64` representing this rational. def to_f : Float64 to_f64 @@ -459,18 +456,13 @@ end # :nodoc: struct Crystal::Hasher - private HASH_MODULUS_RAT_P = BigRational.new((1_u64 << HASH_BITS) - 1) - private HASH_MODULUS_RAT_N = -BigRational.new((1_u64 << HASH_BITS) - 1) - def self.reduce_num(value : BigRational) - rem = value - if value >= HASH_MODULUS_RAT_P || value <= HASH_MODULUS_RAT_N - num = value.numerator - denom = value.denominator - div = num.tdiv(denom) - floor = div.tdiv(HASH_MODULUS) - rem -= floor * HASH_MODULUS + inverse = BigInt.new do |mpz| + if LibGMP.invert(mpz, value.denominator, HASH_MODULUS_INT_P) == 0 + # inverse doesn't exist, i.e. denominator is a multiple of HASH_MODULUS + return value >= 0 ? HASH_INF_PLUS : HASH_INF_MINUS + end end - rem.to_big_f.hash + UInt64.mulmod(reduce_num(value.numerator.abs), inverse.to_u64!, HASH_MODULUS) &* value.sign end end diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index 3cae0de64b77..00834598d9d2 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -141,6 +141,7 @@ lib LibGMP fun gcd_ui = __gmpz_gcd_ui(rop : MPZ*, op1 : MPZ*, op2 : UI) : UI fun lcm = __gmpz_lcm(rop : MPZ*, op1 : MPZ*, op2 : MPZ*) fun lcm_ui = __gmpz_lcm_ui(rop : MPZ*, op1 : MPZ*, op2 : UI) + fun invert = __gmpz_invert(rop : MPZ*, op1 : MPZ*, op2 : MPZ*) : Int fun remove = __gmpz_remove(rop : MPZ*, op : MPZ*, f : MPZ*) : BitcntT # # Miscellaneous Functions diff --git a/src/int.cr b/src/int.cr index 35ad8c05f642..65a54e4e2fde 100644 --- a/src/int.cr +++ b/src/int.cr @@ -193,6 +193,32 @@ struct Int {% end %} end + # :nodoc: + # + # Computes (x * y) % z, but without intermediate overflows. + # Precondition: `0 <= x < z && y >= 0` + def self.mulmod(x, y, z) + result = zero + while y > 0 + if y.bits_set?(1) + # result = (result + x) % z + if result >= z &- x + result &-= z &- x + else + result &+= x + end + end + # x = (x + x) % z + if x >= z &- x + x &-= z &- x + else + x = x.unsafe_shl(1) + end + y = y.unsafe_shr(1) + end + result + end + # Returns the result of shifting this number's bits *count* positions to the right. # Also known as arithmetic right shift. # From 54a3279b546e0d02443d932d6f4fa3c154ea603e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 26 Feb 2024 17:25:58 +0800 Subject: [PATCH 0976/1551] Make `FileUtils.mv` work across filesystems on Windows (#14320) --- src/file_utils.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/file_utils.cr b/src/file_utils.cr index c5b1ced3c7c5..292d911b1630 100644 --- a/src/file_utils.cr +++ b/src/file_utils.cr @@ -317,7 +317,7 @@ module FileUtils # ``` def mv(src_path : Path | String, dest_path : Path | String) : Nil if error = Crystal::System::File.rename(src_path.to_s, dest_path.to_s) - raise error unless Errno.value.in?(Errno::EXDEV, Errno::EPERM) + raise error unless error.os_error.in?(Errno::EXDEV, Errno::EPERM, WinError::ERROR_NOT_SAME_DEVICE) cp_r(src_path, dest_path) rm_r(src_path) end From c6bae608c7bf0e919a4972d802e37e00ca9880eb Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 27 Feb 2024 21:16:07 +1100 Subject: [PATCH 0977/1551] Add `Process.on_terminate` (#13694) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Devonte W Co-authored-by: Johannes Müller --- spec/std/process/status_spec.cr | 10 ++++-- spec/std/process_spec.cr | 8 +++++ src/compiler/crystal/command.cr | 2 +- src/crystal/system/process.cr | 4 +++ src/crystal/system/unix/process.cr | 25 +++++++++++++ src/crystal/system/wasi/process.cr | 5 +++ src/crystal/system/win32/process.cr | 26 +++++++++++--- src/lib_c/x86_64-windows-msvc/c/consoleapi.cr | 7 ++-- src/process.cr | 36 ++++++++++++++++++- src/process/status.cr | 22 ++++++++++-- src/signal.cr | 6 ++-- src/spec.cr | 4 +-- 12 files changed, 137 insertions(+), 18 deletions(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 45f60aba4c06..470a0a1a34d9 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -149,10 +149,16 @@ describe Process::Status do it "returns Aborted" do Process::Status.new(Signal::ABRT.value).exit_reason.aborted?.should be_true - Process::Status.new(Signal::HUP.value).exit_reason.aborted?.should be_true Process::Status.new(Signal::KILL.value).exit_reason.aborted?.should be_true Process::Status.new(Signal::QUIT.value).exit_reason.aborted?.should be_true - Process::Status.new(Signal::TERM.value).exit_reason.aborted?.should be_true + end + + it "returns TerminalDisconnected" do + Process::Status.new(Signal::HUP.value).exit_reason.terminal_disconnected?.should be_true + end + + it "returns SessionEnded" do + Process::Status.new(Signal::TERM.value).exit_reason.session_ended?.should be_true end it "returns Interrupted" do diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 9734ec5ea99c..f303cdb5862f 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -348,6 +348,14 @@ describe Process do end end + describe ".on_terminate" do + it "compiles" do + typeof(Process.on_terminate { }) + typeof(Process.ignore_interrupts!) + typeof(Process.restore_interrupts!) + end + end + {% unless flag?(:win32) %} describe "#signal(Signal::KILL)" do it "kills a process" do diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index b51f9405c4e6..431c9114a6b7 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -312,7 +312,7 @@ class Crystal::Command private def exit_message(status) case status.exit_reason - when .aborted? + when .aborted?, .session_ended?, .terminal_disconnected? if status.signal_exit? signal = status.exit_signal if signal.kill? diff --git a/src/crystal/system/process.cr b/src/crystal/system/process.cr index 387447c083c2..fcc08adbbec3 100644 --- a/src/crystal/system/process.cr +++ b/src/crystal/system/process.cr @@ -46,6 +46,10 @@ struct Crystal::System::Process # previously set interrupt handler. # def self.on_interrupt(&handler : ->) + # Installs *handler* as the new handler for termination signals. Removes any + # previously set handler. + # def self.on_terminate(&handler : ::Process::ExitReason ->) + # Ignores all interrupt requests. Removes any custom interrupt handler set # def self.ignore_interrupts! diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 762507a590d4..4fd2b658cd59 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -58,10 +58,35 @@ struct Crystal::System::Process raise RuntimeError.from_errno("kill") if ret < 0 end + @[Deprecated("Use `#on_terminate` instead")] def self.on_interrupt(&handler : ->) : Nil ::Signal::INT.trap { |_signal| handler.call } end + def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil + sig_handler = Proc(::Signal, Nil).new do |signal| + int_type = case signal + when .int? + ::Process::ExitReason::Interrupted + when .hup? + ::Process::ExitReason::TerminalDisconnected + when .term? + ::Process::ExitReason::SessionEnded + else + ::Process::ExitReason::Interrupted + end + handler.call int_type + + # ignore prevents system defaults and clears registered interrupts + # hence we need to re-register + signal.ignore + Process.on_terminate &handler + end + ::Signal::INT.trap &sig_handler + ::Signal::HUP.trap &sig_handler + ::Signal::TERM.trap &sig_handler + end + def self.ignore_interrupts! : Nil ::Signal::INT.ignore end diff --git a/src/crystal/system/wasi/process.cr b/src/crystal/system/wasi/process.cr index a6f9d156c396..cae182f1ac50 100644 --- a/src/crystal/system/wasi/process.cr +++ b/src/crystal/system/wasi/process.cr @@ -48,10 +48,15 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.signal") end + @[Deprecated("Use `#on_terminate` instead")] def self.on_interrupt(&handler : ->) : Nil raise NotImplementedError.new("Process.on_interrupt") end + def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil + raise NotImplementedError.new("Process.on_terminate") + end + def self.ignore_interrupts! : Nil raise NotImplementedError.new("Process.ignore_interrupts!") end diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index b92203b38510..a8a89f442fab 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -13,10 +13,11 @@ struct Crystal::System::Process @job_object : LibC::HANDLE @completion_key = IO::Overlapped::CompletionKey.new - @@interrupt_handler : Proc(Nil)? + @@interrupt_handler : Proc(::Process::ExitReason, Nil)? @@interrupt_count = Crystal::AtomicSemaphore.new @@win32_interrupt_handler : LibC::PHANDLER_ROUTINE? @@setup_interrupt_handler = Atomic::Flag.new + @@last_interrupt = ::Process::ExitReason::Interrupted def initialize(process_info) @pid = process_info.dwProcessId @@ -150,10 +151,26 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.signal") end - def self.on_interrupt(&@@interrupt_handler : ->) : Nil + @[Deprecated("Use `#on_terminate` instead")] + def self.on_interrupt(&handler : ->) : Nil + on_terminate do |reason| + handler.call if reason.interrupted? + end + end + + def self.on_terminate(&@@interrupt_handler : ::Process::ExitReason ->) : Nil restore_interrupts! @@win32_interrupt_handler = handler = LibC::PHANDLER_ROUTINE.new do |event_type| - next 0 unless event_type.in?(LibC::CTRL_C_EVENT, LibC::CTRL_BREAK_EVENT) + @@last_interrupt = case event_type + when LibC::CTRL_C_EVENT, LibC::CTRL_BREAK_EVENT + ::Process::ExitReason::Interrupted + when LibC::CTRL_CLOSE_EVENT + ::Process::ExitReason::TerminalDisconnected + when LibC::CTRL_LOGOFF_EVENT, LibC::CTRL_SHUTDOWN_EVENT + ::Process::ExitReason::SessionEnded + else + next 0 + end @@interrupt_count.signal 1 end @@ -186,8 +203,9 @@ struct Crystal::System::Process if handler = @@interrupt_handler non_nil_handler = handler # if handler is closured it will also have the Nil type + int_type = @@last_interrupt spawn do - non_nil_handler.call + non_nil_handler.call int_type rescue ex ex.inspect_with_backtrace(STDERR) STDERR.puts("FATAL: uncaught exception while processing interrupt handler, exiting") diff --git a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr index 796369c65a85..fe2fbe381d03 100644 --- a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr @@ -22,8 +22,11 @@ lib LibC pInputControl : Void* ) : BOOL - CTRL_C_EVENT = 0 - CTRL_BREAK_EVENT = 1 + CTRL_C_EVENT = 0 + CTRL_BREAK_EVENT = 1 + CTRL_CLOSE_EVENT = 2 + CTRL_LOGOFF_EVENT = 5 + CTRL_SHUTDOWN_EVENT = 6 alias PHANDLER_ROUTINE = DWORD -> BOOL diff --git a/src/process.cr b/src/process.cr index eb0c165950ce..a1b827d73754 100644 --- a/src/process.cr +++ b/src/process.cr @@ -58,12 +58,46 @@ class Process # * On Unix-like systems, this traps `SIGINT`. # * On Windows, this captures Ctrl + C and # Ctrl + Break signals sent to a console application. + @[Deprecated("Use `#on_terminate` instead")] def self.on_interrupt(&handler : ->) : Nil Crystal::System::Process.on_interrupt(&handler) end + # Installs *handler* as the new handler for termination requests. Removes any + # previously set termination handler. + # + # The handler is executed on a fresh fiber every time an interrupt occurs. + # + # * On Unix-like systems, this traps `SIGINT`, `SIGHUP` and `SIGTERM`. + # * On Windows, this captures Ctrl + C, + # Ctrl + Break, terminal close, windows logoff + # and shutdown signals sent to a console application. + # + # ``` + # wait_channel = Channel(Nil).new + # + # Process.on_terminate do |reason| + # case reason + # when .interrupted? + # puts "terminating gracefully" + # wait_channel.close + # when .terminal_disconnected? + # puts "reloading configuration" + # when .session_ended? + # puts "terminating forcefully" + # Process.exit + # end + # end + # + # wait_channel.receive + # puts "bye" + # ``` + def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil + Crystal::System::Process.on_terminate(&handler) + end + # Ignores all interrupt requests. Removes any custom interrupt handler set - # with `#on_interrupt`. + # with `#on_terminate`. # # * On Windows, interrupts generated by Ctrl + Break # cannot be ignored in this way. diff --git a/src/process/status.cr b/src/process/status.cr index c7b78b1a4583..de29351ff12f 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -16,8 +16,8 @@ enum Process::ExitReason # The process terminated abnormally. # - # * On Unix-like systems, this corresponds to `Signal::ABRT`, `Signal::HUP`, - # `Signal::KILL`, `Signal::QUIT`, and `Signal::TERM`. + # * On Unix-like systems, this corresponds to `Signal::ABRT`, `Signal::KILL`, + # and `Signal::QUIT`. # * On Windows, this corresponds to the `NTSTATUS` value # `STATUS_FATAL_APP_EXIT`. Aborted @@ -79,6 +79,18 @@ enum Process::ExitReason # A `Process::Status` that maps to `Unknown` may map to a different value if # new enum members are added to `ExitReason`. Unknown + + # The process exited due to the user closing the terminal window or ending an ssh session. + # + # * On Unix-like systems, this corresponds to `Signal::HUP`. + # * On Windows, this corresponds to the `CTRL_CLOSE_EVENT` message. + TerminalDisconnected + + # The process exited due to the user logging off or shutting down the OS. + # + # * On Unix-like systems, this corresponds to `Signal::TERM`. + # * On Windows, this corresponds to the `CTRL_LOGOFF_EVENT` and `CTRL_SHUTDOWN_EVENT` messages. + SessionEnded end # The status of a terminated process. Returned by `Process#wait`. @@ -129,8 +141,12 @@ class Process::Status case Signal.from_value?(signal_code) when Nil ExitReason::Signal - when .abrt?, .hup?, .kill?, .quit?, .term? + when .abrt?, .kill?, .quit? ExitReason::Aborted + when .hup? + ExitReason::TerminalDisconnected + when .term? + ExitReason::SessionEnded when .int? ExitReason::Interrupted when .trap? diff --git a/src/signal.cr b/src/signal.cr index 2e085b92311e..50360b73c511 100644 --- a/src/signal.cr +++ b/src/signal.cr @@ -40,8 +40,8 @@ require "crystal/system/signal" # The standard library provides several platform-agnostic APIs to achieve tasks # that are typically solved with signals on POSIX systems: # -# * The portable API for responding to an interrupt signal (`INT.trap`) is -# `Process.on_interrupt`. +# * The portable API for responding to a termination request is +# `Process.on_terminate`. # * The portable API for sending a `TERM` or `KILL` signal to a process is # `Process#terminate`. # * The portable API for retrieving the exit signal of a process @@ -105,7 +105,7 @@ enum Signal : Int32 # check child processes using `Process.exists?`. Trying to use waitpid with a # zero or negative value won't work. # - # NOTE: `Process.on_interrupt` is preferred over `Signal::INT.trap` as a + # NOTE: `Process.on_terminate` is preferred over `Signal::INT.trap` as a # portable alternative which also works on Windows. def trap(&handler : Signal ->) : Nil {% if @type.has_constant?("CHLD") %} diff --git a/src/spec.cr b/src/spec.cr index c51ee8831de4..474aa7c5a0dc 100644 --- a/src/spec.cr +++ b/src/spec.cr @@ -129,8 +129,8 @@ module Spec add_split_filter ENV["SPEC_SPLIT"]? {% unless flag?(:wasm32) %} - # TODO(wasm): Enable this once `Process.on_interrupt` is implemented - Process.on_interrupt { abort! } + # TODO(wasm): Enable this once `Process.on_terminate` is implemented + Process.on_terminate { abort! } {% end %} run From 81fa3a96c916720fc271bbc155de8141abb83dd6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 27 Feb 2024 18:35:13 +0800 Subject: [PATCH 0978/1551] Fix System V ABI for packed structs with misaligned fields (#14324) --- spec/std/llvm/x86_64_abi_spec.cr | 24 ++++++++++++++++++++++++ src/llvm/abi/x86_64.cr | 12 +++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/spec/std/llvm/x86_64_abi_spec.cr b/spec/std/llvm/x86_64_abi_spec.cr index 2e2514e209d2..7c0344769aa3 100644 --- a/spec/std/llvm/x86_64_abi_spec.cr +++ b/spec/std/llvm/x86_64_abi_spec.cr @@ -144,6 +144,30 @@ class LLVM::ABI info.arg_types[0].should eq(ArgType.indirect(str, Attribute::ByVal)) info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) end + + test "does with packed struct containing unaligned fields (#9873)" do |abi, ctx| + str = ctx.struct([ctx.int8, ctx.int16], packed: true) + arg_types = [str] + return_type = str + + info = abi.abi_info(arg_types, return_type, true, ctx) + info.arg_types.size.should eq(1) + + info.arg_types[0].should eq(ArgType.indirect(str, Attribute::ByVal)) + info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) + end + + test "does with packed struct not containing unaligned fields" do |abi, ctx| + str = ctx.struct([ctx.int16, ctx.int8], packed: true) + arg_types = [str] + return_type = str + + info = abi.abi_info(arg_types, return_type, true, ctx) + info.arg_types.size.should eq(1) + + info.arg_types[0].should eq(ArgType.direct(str, cast: ctx.struct([ctx.int64]))) + info.return_type.should eq(ArgType.direct(str, cast: ctx.struct([ctx.int64]))) + end end {% end %} end diff --git a/src/llvm/abi/x86_64.cr b/src/llvm/abi/x86_64.cr index e1e996f178e7..5dff628655be 100644 --- a/src/llvm/abi/x86_64.cr +++ b/src/llvm/abi/x86_64.cr @@ -100,7 +100,7 @@ class LLVM::ABI::X86_64 < LLVM::ABI def classify(type) words = (size(type) + 7) // 8 reg_classes = Array.new(words, RegClass::NoClass) - if words > 4 + if words > 4 || has_misaligned_fields?(type) all_mem(reg_classes) else classify(type, reg_classes, 0, 0) @@ -275,6 +275,16 @@ class LLVM::ABI::X86_64 < LLVM::ABI size(type, 8) end + def has_misaligned_fields?(type : Type) : Bool + return false unless type.packed_struct? + offset = 0 + type.struct_element_types.each do |elem| + return true unless offset.divisible_by?(align(elem)) + offset += size(elem) + end + false + end + enum RegClass NoClass Int From e3eb178f213ed66fce06286b586b687e3a6bf65c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 28 Feb 2024 03:50:51 +0800 Subject: [PATCH 0979/1551] Fix `Proc#call` that takes and returns large extern structs by value (#14323) --- spec/compiler/codegen/extern_spec.cr | 23 +++++++++++++-- spec/std/llvm/aarch64_spec.cr | 2 +- spec/std/llvm/x86_64_abi_spec.cr | 44 +++++++++++++++++++++++----- src/compiler/crystal/codegen/call.cr | 7 +++++ src/compiler/crystal/codegen/fun.cr | 5 +++- src/llvm/abi/x86_win64.cr | 2 +- 6 files changed, 71 insertions(+), 12 deletions(-) diff --git a/spec/compiler/codegen/extern_spec.cr b/spec/compiler/codegen/extern_spec.cr index 6efd7a44c16b..e069e81a75fe 100644 --- a/spec/compiler/codegen/extern_spec.cr +++ b/spec/compiler/codegen/extern_spec.cr @@ -164,10 +164,29 @@ describe "Codegen: extern struct" do )).to_i.should eq(3) end + it "codegens proc that takes and returns large extern struct by value" do + run(<<-CRYSTAL).to_i.should eq(149) + @[Extern] + struct Foo + @unused = uninitialized Int64 + + def initialize(@x : Int32, @y : Int32, @z : Int32) + end + end + + f = ->(foo : Foo) { + Foo.new(foo.@x, foo.@y &* 2, foo.@z &* 3) + } + + foo = f.call(Foo.new(100, 20, 3)) + foo.@x &+ foo.@y &+ foo.@z + CRYSTAL + end + # These specs *should* also work for 32 bits, but for now we'll # make sure they work in 64 bits (they probably work in 32 bits too, # it's just that the specs need to be a bit different) - {% if flag?(:x86_64) %} + {% if flag?(:x86_64) || flag?(:aarch64) %} it "codegens proc that takes an extern struct with C ABI" do test_c( %( @@ -427,7 +446,7 @@ describe "Codegen: extern struct" do ), &.to_i.should eq(30)) end - pending_win32 "codegens proc that takes and returns an extern struct with sret" do + it "codegens proc that takes and returns an extern struct with sret" do test_c( %( struct Struct { diff --git a/spec/std/llvm/aarch64_spec.cr b/spec/std/llvm/aarch64_spec.cr index a7303fd2e861..41a308b480ec 100644 --- a/spec/std/llvm/aarch64_spec.cr +++ b/spec/std/llvm/aarch64_spec.cr @@ -133,7 +133,7 @@ class LLVM::ABI info.return_type.should eq(ArgType.direct(str, cast: ctx.int64.array(2))) end - test "does with structs between 64 and 128 bits" do |abi, ctx| + test "does with structs larger than 128 bits" do |abi, ctx| str = ctx.struct([ctx.int64, ctx.int64, ctx.int8]) arg_types = [str] return_type = str diff --git a/spec/std/llvm/x86_64_abi_spec.cr b/spec/std/llvm/x86_64_abi_spec.cr index 7c0344769aa3..0ba644cefa01 100644 --- a/spec/std/llvm/x86_64_abi_spec.cr +++ b/spec/std/llvm/x86_64_abi_spec.cr @@ -5,17 +5,17 @@ require "llvm" LLVM.init_x86 {% end %} -private def abi - triple = LLVM.default_target_triple.gsub(/^(.+?)-/, "x86_64-") +private def abi(win64 = false) + triple = win64 ? "x86_64-windows-msvc" : LLVM.default_target_triple.gsub(/^(.+?)-/, "x86_64-") target = LLVM::Target.from_triple(triple) machine = target.create_target_machine(triple) machine.enable_global_isel = false - LLVM::ABI::X86_64.new(machine) + win64 ? LLVM::ABI::X86_Win64.new(machine) : LLVM::ABI::X86_64.new(machine) end -private def test(msg, &block : LLVM::ABI, LLVM::Context ->) - it msg do - abi = abi() +private def test(msg, *, win64 = false, file = __FILE__, line = __LINE__, &block : LLVM::ABI, LLVM::Context ->) + it msg, file: file, line: line do + abi = abi(win64) ctx = LLVM::Context.new block.call(abi, ctx) end @@ -133,7 +133,37 @@ class LLVM::ABI info.return_type.should eq(ArgType.direct(str, cast: ctx.struct([ctx.int64, ctx.int64]))) end - test "does with structs between 64 and 128 bits" do |abi, ctx| + test "does with structs larger than 128 bits" do |abi, ctx| + str = ctx.struct([ctx.int64, ctx.int64, ctx.int8]) + arg_types = [str] + return_type = str + + info = abi.abi_info(arg_types, return_type, true, ctx) + info.arg_types.size.should eq(1) + + info.arg_types[0].should eq(ArgType.indirect(str, Attribute::ByVal)) + info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) + end + end + {% end %} + end + + describe X86_Win64 do + {% if LibLLVM::BUILT_TARGETS.includes?(:x86) %} + describe "abi_info" do + test "does with structs between 64 and 128 bits", win64: true do |abi, ctx| + str = ctx.struct([ctx.int64, ctx.int16]) + arg_types = [str] + return_type = str + + info = abi.abi_info(arg_types, return_type, true, ctx) + info.arg_types.size.should eq(1) + + info.arg_types[0].should eq(ArgType.indirect(str, Attribute::ByVal)) + info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) + end + + test "does with structs larger than 128 bits", win64: true do |abi, ctx| str = ctx.struct([ctx.int64, ctx.int64, ctx.int8]) arg_types = [str] return_type = str diff --git a/src/compiler/crystal/codegen/call.cr b/src/compiler/crystal/codegen/call.cr index d0937cfff40c..1dca1f4e4b7b 100644 --- a/src/compiler/crystal/codegen/call.cr +++ b/src/compiler/crystal/codegen/call.cr @@ -597,13 +597,20 @@ class Crystal::CodeGenVisitor abi_info = abi_info(target_def) end + sret = abi_info && sret?(abi_info) arg_offset = is_closure ? 2 : 1 + arg_offset += 1 if sret + arg_types = fun_type.try(&.arg_types) || target_def.try &.args.map &.type arg_types.try &.each_with_index do |arg_type, i| if abi_info && (abi_arg_type = abi_info.arg_types[i]?) && (attr = abi_arg_type.attr) @last.add_instruction_attribute(i + arg_offset, attr, llvm_context, abi_arg_type.type) end end + + if abi_info && sret + @last.add_instruction_attribute(1, LLVM::Attribute::StructRet, llvm_context, abi_info.return_type.type) + end end def sret?(abi_info) diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 784eba83b9b9..7d8ddf065c21 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -535,11 +535,14 @@ class Crystal::CodeGenVisitor value = pointer2 end else + abi_info = target_def.abi_info? ? abi_info(target_def) : nil + sret = abi_info && sret?(abi_info) + # If it's an extern struct on a def that must be codegened with C ABI # compatibility, and it's not passed byval, we must cast the value if target_def.c_calling_convention? && arg.type.extern? && !context.fun.attributes(index + 1).by_val? # ... unless it's passed indirectly (ie. as a pointer to memory allocated by the caller) - if target_def.abi_info? && abi_info(target_def).arg_types[index].kind.indirect? + if abi_info.try &.arg_types[index - (sret ? 1 : 0)].kind.indirect? value = declare_debug_for_function_argument(arg.name, var_type, index + 1, value, location) unless target_def.naked? context.vars[arg.name] = LLVMVar.new(value, var_type) else diff --git a/src/llvm/abi/x86_win64.cr b/src/llvm/abi/x86_win64.cr index 69e9b0823ba9..5a9a07bdb31e 100644 --- a/src/llvm/abi/x86_win64.cr +++ b/src/llvm/abi/x86_win64.cr @@ -12,7 +12,7 @@ class LLVM::ABI::X86_Win64 < LLVM::ABI::X86 when 2 then ArgType.direct(t, context.int16) when 4 then ArgType.direct(t, context.int32) when 8 then ArgType.direct(t, context.int64) - else ArgType.indirect(t, nil) + else ArgType.indirect(t, LLVM::Attribute::ByVal) end else non_struct(t, context) From 7f92639485a709f45275c42f3508e6a680ba9c9d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 29 Feb 2024 16:39:04 +0800 Subject: [PATCH 0980/1551] Do not allocate memory in the segmentation fault signal handler (#14327) Co-authored-by: Sijawusz Pur Rahnama --- src/crystal/system/unix/signal.cr | 3 +++ src/exception/call_stack/libunwind.cr | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr index d0fdbf298391..0ac7b8816d7a 100644 --- a/src/crystal/system/unix/signal.cr +++ b/src/crystal/system/unix/signal.cr @@ -135,6 +135,9 @@ module Crystal::System::Signal @@segfault_handler = LibC::SigactionHandlerT.new { |sig, info, data| # Capture fault signals (SEGV, BUS) and finish the process printing a backtrace first + # This handler must not allocate memory via the GC! Expanding the heap or + # triggering a GC cycle here could generate another SEGV + # Determine if the SEGV was inside or 'near' the top of the stack # to check for potential stack overflow. 'Near' is a small # amount larger than a typical stack frame, 4096 bytes here. diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 9f17512491fe..b8d821a5598f 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -120,7 +120,7 @@ struct Exception::CallStack end {% end %} - if frame = decode_frame(repeated_frame.ip) + if frame = unsafe_decode_frame(repeated_frame.ip) offset, sname, fname = frame Crystal::System.print_error "%s +%lld in %s", sname, offset.to_i64, fname else @@ -149,4 +149,23 @@ struct Exception::CallStack {offset, symbol, file} end end + + # variant of `.decode_frame` that returns the C strings directly instead of + # wrapping them in `String.new`, since the SIGSEGV handler cannot allocate + # memory via the GC + protected def self.unsafe_decode_frame(ip) + original_ip = ip + while LibC.dladdr(ip, out info) != 0 + offset = original_ip - info.dli_saddr + if offset == 0 + ip -= 1 + next + end + + return if info.dli_sname.null? && info.dli_fname.null? + symbol = info.dli_sname || "??".to_unsafe + file = info.dli_fname || "??".to_unsafe + return {offset, symbol, file} + end + end end From 88691867211323ed4be5e09e5008728581cd861d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 29 Feb 2024 16:39:38 +0800 Subject: [PATCH 0981/1551] Fix macro `Crystal::LIBRARY_PATH.split` when cross-compiling (#14330) --- src/crystal/system/win32/process.cr | 6 ++++++ src/llvm/lib_llvm.cr | 2 +- src/openssl/lib_crypto.cr | 2 +- src/openssl/lib_ssl.cr | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index a8a89f442fab..25ac32ca85e8 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -7,6 +7,12 @@ require "process/shell" require "crystal/atomic_semaphore" struct Crystal::System::Process + {% if host_flag?(:windows) %} + HOST_PATH_DELIMITER = ';' + {% else %} + HOST_PATH_DELIMITER = ':' + {% end %} + getter pid : LibC::DWORD @thread_id : LibC::DWORD @process_handle : LibC::HANDLE diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index ca1907522f8c..976cedc90df5 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -1,7 +1,7 @@ {% begin %} {% if flag?(:win32) && !flag?(:static) %} {% config = nil %} - {% for dir in Crystal::LIBRARY_PATH.split(';') %} + {% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %} {% config ||= read_file?("#{dir.id}/llvm_VERSION") %} {% end %} diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr index caca9c11c520..1c4731292dc3 100644 --- a/src/openssl/lib_crypto.cr +++ b/src/openssl/lib_crypto.cr @@ -3,7 +3,7 @@ {% if flag?(:win32) %} {% from_libressl = false %} {% ssl_version = nil %} - {% for dir in Crystal::LIBRARY_PATH.split(';') %} + {% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %} {% unless ssl_version %} {% config_path = "#{dir.id}\\openssl_VERSION" %} {% if config_version = read_file?(config_path) %} diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr index 27faf9bbe185..558b25017054 100644 --- a/src/openssl/lib_ssl.cr +++ b/src/openssl/lib_ssl.cr @@ -9,7 +9,7 @@ require "./lib_crypto" {% if flag?(:win32) %} {% from_libressl = false %} {% ssl_version = nil %} - {% for dir in Crystal::LIBRARY_PATH.split(';') %} + {% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %} {% unless ssl_version %} {% config_path = "#{dir.id}\\openssl_VERSION" %} {% if config_version = read_file?(config_path) %} From 5a6feca3f5313b7b7f8d774c4b8b9a20ef4595b4 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 29 Feb 2024 21:46:46 +0800 Subject: [PATCH 0982/1551] Install system dependencies in the Windows GUI installer (#14328) --- etc/win-ci/crystal.iss | 125 ++++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 53 deletions(-) diff --git a/etc/win-ci/crystal.iss b/etc/win-ci/crystal.iss index 18fdf2e79fec..edb92d89c72a 100644 --- a/etc/win-ci/crystal.iss +++ b/etc/win-ci/crystal.iss @@ -200,12 +200,12 @@ begin HasWinSDKAt(HKEY_CURRENT_USER, Win10SDK32); end; -function IsDeveloperModeEnabled: Boolean; +function HasVCRedist: Boolean; var regValue: Cardinal; begin result := False; - if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock', 'AllowDevelopmentWithoutDevLicense', regValue) then + if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X64', 'Installed', regValue) then if regValue <> 0 then result := True; end; @@ -276,14 +276,8 @@ end; procedure InitializeWizard; var updatingPage: TOutputMsgWizardPage; - - warningsPage: TOutputMsgMemoWizardPage; - isMsvcFound: Boolean; - isWinSdkFound: Boolean; - message: String; begin if GetUninstallString() <> '' then - begin updatingPage := CreateOutputMsgPage( wpSelectTasks, 'Pre-Install Checks', @@ -291,41 +285,6 @@ begin 'Setup has detected a previous installation of Crystal; it will be uninstalled before the new version is installed. ' + 'This ensures that requiring Crystal files will not pick up any leftover files from the previous version.'#13#13 + 'To use multiple Crystal installations side-by-side, the portable packages for the extra versions must be downloaded manually.'); - end; - - isMsvcFound := HasMSVC; - isWinSdkFound := HasWinSDK; - message := ''; - - if not isMsvcFound then - message := message + - '{\b WARNING:} Setup was unable to detect a copy of the Build Tools for Visual Studio 2017 or newer on this machine. ' + - 'The MSVC build tools are required to link Crystal programs into Windows executables. \line\line '; - - if not isWinSdkFound then - message := message + - '{\b WARNING:} Setup was unable to detect a copy of the Windows 10 / 11 SDK on this machine. ' + - 'The Crystal runtime relies on the Win32 libraries to interface with the Windows system. \line\line '; - - if not isMsvcFound or not isWinSdkFound then - message := message + - 'Please install the missing components using one of the following options: \line\line ' + - '\emspace\bullet\emspace https://aka.ms/vs/17/release/vs_BuildTools.exe for the build tools alone \line ' + - '\emspace\bullet\emspace https://visualstudio.microsoft.com/downloads/ for the build tools + Visual Studio 2022 \line\line ' + - 'The {\b Desktop development with C++} workload should be selected. \line\line '; - - if not IsDeveloperModeEnabled() then - message := message + - '{\b WARNING:} Developer Mode is not enabled on this machine. Please refer to ' + - 'https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development ' + - 'for instructions on how to enable Developer Mode. \line\line '; - - if message <> '' then - warningsPage := CreateOutputMsgMemoPage(wpInfoAfter, - 'Post-Install Checks', - 'Some components are missing and must be manually installed.', - 'Information', - '{\rtf1 ' + message + '}'); end; procedure CurStepChanged(CurStep: TSetupStep); @@ -335,18 +294,78 @@ var begin case CurStep of ssInstall: - begin - uninstallString := GetUninstallString(); - if uninstallString <> '' then - if not Exec(RemoveQuotes(uninstallString), '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, exitCode) then + begin + uninstallString := GetUninstallString(); + if uninstallString <> '' then + if not Exec(RemoveQuotes(uninstallString), '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, exitCode) then + begin + SuppressibleMsgBox('Failed to remove the previous Crystal installation. Setup will now exit.', mbCriticalError, MB_OK, IDOK); + Abort; + end; + end; + ssPostInstall: + begin + if (not HasVCRedist) and (IDYES = SuppressibleTaskDialogMsgBox( + 'Install Visual C++ Redistributable', + 'Setup is unable to detect a copy of the Visual C++ 2015 Redistributable (x64) or newer on this machine. ' + + 'The runtime libraries are needed by dynamically linked executables, including the compiler itself. ' + + 'If you select "Agree", the installer will proceed to download: '#13#10#13#10 + + 'https://aka.ms/vs/17/release/vc_redist.x64.exe'#13#10#13#10 + + 'and then run:'#13#10#13#10 + + 'VC_redist.x64.exe /passive /norestart'#13#10#13#10 + + 'Would you like to install the Visual C++ Redistributable now?', + mbInformation, MB_YESNO, ['Agree', 'Decline'], IDYES, IDYES)) then begin - SuppressibleMsgBox('Failed to remove the previous Crystal installation. Setup will now exit.', mbCriticalError, MB_OK, IDOK); - Abort; + DownloadTemporaryFile('https://aka.ms/vs/17/release/vc_redist.x64.exe', 'VC_redist.x64.exe', '', nil); + if not Exec(ExpandConstant('{tmp}\VC_redist.x64.exe'), '/passive /norestart', '', SW_SHOW, ewWaitUntilTerminated, exitCode) then + SuppressibleMsgBox('Failed to install the Visual C++ Redistributable.', mbError, MB_OK, IDOK); end; - end; - ssPostInstall: - if WizardIsTaskSelected('addtopath') then - EnvAddPath(ExpandConstant('{app}'), IsAdminInstallMode()); + + if (not HasMSVC) and (IDYES = SuppressibleTaskDialogMsgBox( + 'Install MSVC Build Tools', + 'Setup is unable to detect a copy of the Build Tools for Visual Studio 2017 or newer on this machine. ' + + 'The MSVC Build Tools are required to link Crystal programs into Windows executables. ' + + 'If you select "Agree", the installer will proceed to download: '#13#10#13#10 + + 'https://aka.ms/vs/17/release/vs_BuildTools.exe'#13#10#13#10 + + 'and then run:'#13#10#13#10 + + 'vs_BuildTools.exe --passive --norestart --wait --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64'#13#10#13#10 + + 'Would you like to install the MSVC Build Tools now? ' + + 'Note that you may modify your installation later using the Visual Studio Installer.', + mbInformation, MB_YESNO, ['Agree', 'Decline'], IDYES, IDYES)) then + begin + DownloadTemporaryFile('https://aka.ms/vs/17/release/vs_BuildTools.exe', 'vs_BuildTools.exe', '', nil); + if not Exec( + ExpandConstant('{tmp}\vs_BuildTools.exe'), + '--passive --norestart --wait --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64', + '', SW_SHOW, ewWaitUntilTerminated, exitCode + ) then + SuppressibleMsgBox('Failed to install the Build Tools for Visual Studio.', mbError, MB_OK, IDOK); + end; + + if (not HasWinSDK) and (IDYES = SuppressibleTaskDialogMsgBox( + 'Install Windows SDK', + 'Setup is unable to detect a copy of the Windows 10 SDK or newer on this machine. ' + + 'The Crystal runtime and standard library rely on the Win32 libraries to interface with the Windows system. ' + + 'If you select "Agree", the installer will proceed to download: '#13#10#13#10 + + 'https://aka.ms/vs/17/release/vs_BuildTools.exe'#13#10#13#10 + + 'and then run:'#13#10#13#10 + + 'vs_BuildTools.exe --passive --norestart --wait --add Microsoft.VisualStudio.Component.Windows11SDK.22621'#13#10#13#10 + + 'Would you like to install the Windows SDK now? ' + + 'Note that you may modify your installation later using the Visual Studio Installer.', + mbInformation, MB_YESNO, ['Agree', 'Decline'], IDYES, IDYES)) then + begin + DownloadTemporaryFile('https://aka.ms/vs/17/release/vs_BuildTools.exe', 'vs_BuildTools.exe', '', nil); + if not Exec( + ExpandConstant('{tmp}\vs_BuildTools.exe'), + '--passive --norestart --wait --add Microsoft.VisualStudio.Component.Windows11SDK.22621', + '', SW_SHOW, ewWaitUntilTerminated, exitCode + ) then + SuppressibleMsgBox('Failed to install the Windows SDK.', mbError, MB_OK, IDOK); + end; + + if WizardIsTaskSelected('addtopath') then + EnvAddPath(ExpandConstant('{app}'), IsAdminInstallMode()); + end; end; end; From f76a512f1376f8c3ae1c604c65d7a46c99a1a460 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 29 Feb 2024 21:46:57 +0800 Subject: [PATCH 0983/1551] Update copyright year in NOTICE.md (#14329) --- NOTICE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NOTICE.md b/NOTICE.md index 6d13be6a19ce..bb0120eb54a6 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1,6 +1,6 @@ # Crystal Programming Language -Copyright 2012-2023 Manas Technology Solutions. +Copyright 2012-2024 Manas Technology Solutions. This product includes software developed at Manas Technology Solutions (). From 3072e8fc24016b4c924816ebadb7b7af811eca4c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 1 Mar 2024 17:27:48 +0800 Subject: [PATCH 0984/1551] Fix crash stack trace decoding on macOS (#14335) --- src/exception/call_stack/libunwind.cr | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index b8d821a5598f..435a1c0f9fe6 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -110,12 +110,14 @@ struct Exception::CallStack private def self.print_frame_location(repeated_frame) {% if flag?(:debug) %} - if @@dwarf_loaded && - (name = decode_function_name(repeated_frame.ip.address)) - file, line, column = Exception::CallStack.decode_line_number(repeated_frame.ip.address) - if file && file != "??" - Crystal::System.print_error "%s at %s:%d:%d", name, file, line, column - return + if @@dwarf_loaded + pc = CallStack.decode_address(repeated_frame.ip) + if name = decode_function_name(pc) + file, line, column = Exception::CallStack.decode_line_number(pc) + if file && file != "??" + Crystal::System.print_error "%s at %s:%d:%d", name, file, line, column + return + end end end {% end %} From 770e415cf443f56e7b844dbcccfb900646d437e9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 1 Mar 2024 17:28:02 +0800 Subject: [PATCH 0985/1551] Remove the prelude from some compiler specs (#14336) --- spec/compiler/codegen/block_spec.cr | 10 ---------- spec/compiler/codegen/class_spec.cr | 2 -- spec/compiler/codegen/class_var_spec.cr | 14 -------------- spec/compiler/codegen/is_a_spec.cr | 4 ---- spec/compiler/codegen/macro_spec.cr | 18 ++++-------------- spec/compiler/codegen/pointer_spec.cr | 6 ------ spec/compiler/codegen/return_spec.cr | 4 ++-- spec/compiler/codegen/super_spec.cr | 2 -- spec/compiler/codegen/var_spec.cr | 2 -- spec/compiler/codegen/virtual_spec.cr | 2 -- spec/compiler/semantic/instance_var_spec.cr | 4 ---- spec/compiler/semantic/proc_spec.cr | 6 ------ 12 files changed, 6 insertions(+), 68 deletions(-) diff --git a/spec/compiler/codegen/block_spec.cr b/spec/compiler/codegen/block_spec.cr index d62b49f03042..0516ad634ebe 100644 --- a/spec/compiler/codegen/block_spec.cr +++ b/spec/compiler/codegen/block_spec.cr @@ -505,8 +505,6 @@ describe "Code gen: block" do it "can break without value from yielder that returns nilable (1)" do run(%( - require "prelude" - def foo yield "" @@ -522,8 +520,6 @@ describe "Code gen: block" do it "can break without value from yielder that returns nilable (2)" do run(%( - require "prelude" - def foo yield "" @@ -539,8 +535,6 @@ describe "Code gen: block" do it "break with value from yielder that returns a nilable" do run(%( - require "prelude" - def foo yield "" @@ -1046,8 +1040,6 @@ describe "Code gen: block" do it "codegens method invocation on a object of a captured block with a type that was never instantiated" do codegen(%( - require "prelude" - class Bar def initialize(@bar : NoReturn) end @@ -1076,8 +1068,6 @@ describe "Code gen: block" do it "codegens method invocation on a object of a captured block with a type that was never instantiated (2)" do codegen(%( - require "prelude" - class Bar def initialize(@bar : NoReturn) end diff --git a/spec/compiler/codegen/class_spec.cr b/spec/compiler/codegen/class_spec.cr index ac9a9c45e78c..c6c697f29e29 100644 --- a/spec/compiler/codegen/class_spec.cr +++ b/spec/compiler/codegen/class_spec.cr @@ -857,8 +857,6 @@ describe "Code gen: class" do it "codegens singleton (#718)" do run(%( - require "prelude" - class Singleton @@instance = new diff --git a/spec/compiler/codegen/class_var_spec.cr b/spec/compiler/codegen/class_var_spec.cr index 9a48ab36b51b..8dc7f9bf1915 100644 --- a/spec/compiler/codegen/class_var_spec.cr +++ b/spec/compiler/codegen/class_var_spec.cr @@ -106,8 +106,6 @@ describe "Codegen: class var" do it "uses var in class var initializer" do run(%( - require "prelude" - class Foo @@var : Int32 @@var = begin @@ -130,8 +128,6 @@ describe "Codegen: class var" do it "reads simple class var before another complex one" do run(%( - require "prelude" - class Foo @@var2 : Int32 @@var2 = @@var &+ 1 @@ -149,8 +145,6 @@ describe "Codegen: class var" do it "initializes class var of union with single type" do run(%( - require "prelude" - class Foo @@var : Int32 | String @@var = 42 @@ -241,8 +235,6 @@ describe "Codegen: class var" do it "doesn't use nilable type for initializer" do run(%( - require "prelude" - class Foo @@foo : Int32? @@foo = 42 @@ -261,8 +253,6 @@ describe "Codegen: class var" do it "codegens class var with begin and vars" do run(%( - require "prelude" - class Foo @@foo : Int32 @@foo = begin @@ -282,8 +272,6 @@ describe "Codegen: class var" do it "codegens class var with type declaration begin and vars" do run(%( - require "prelude" - class Foo @@foo : Int32 = begin a = 1 @@ -352,8 +340,6 @@ describe "Codegen: class var" do it "gets pointerof class var complex constant" do run(%( - require "prelude" - z = Foo.foo class Foo diff --git a/spec/compiler/codegen/is_a_spec.cr b/spec/compiler/codegen/is_a_spec.cr index 86ed570afe20..b728c7b81ad8 100644 --- a/spec/compiler/codegen/is_a_spec.cr +++ b/spec/compiler/codegen/is_a_spec.cr @@ -594,8 +594,6 @@ describe "Codegen: is_a?" do it "resets truthy state after visiting nodes (bug)" do run(%( - require "prelude" - a = 123 if !1.is_a?(Int32) a = 456 @@ -782,8 +780,6 @@ describe "Codegen: is_a?" do it "codegens untyped var (#4009)" do codegen(%( - require "prelude" - i = 1 1 || i.is_a?(Int32) ? "" : i )) diff --git a/spec/compiler/codegen/macro_spec.cr b/spec/compiler/codegen/macro_spec.cr index bbf64946d03e..0cae55711568 100644 --- a/spec/compiler/codegen/macro_spec.cr +++ b/spec/compiler/codegen/macro_spec.cr @@ -554,13 +554,12 @@ describe "Code gen: macro" do it "can iterate union types" do run(%( - require "prelude" class Foo def initialize @x = 1; @x = 1.1 end def foo - {{ @type.instance_vars.first.type.union_types.map(&.name).sort }}.join('-') + {{ @type.instance_vars.first.type.union_types.map(&.name).sort.join("-") }} end end Foo.new.foo @@ -613,10 +612,9 @@ describe "Code gen: macro" do it "can access type variables of a generic type" do run(%( - require "prelude" class Foo(T, K) def self.foo : String - {{ @type.type_vars.map(&.stringify) }}.join('-') + {{ @type.type_vars.map(&.stringify).join("-") }} end end Foo.foo @@ -697,8 +695,6 @@ describe "Code gen: macro" do it "executes subclasses" do run(%( - require "prelude" - class Foo end @@ -711,15 +707,12 @@ describe "Code gen: macro" do class Qux < Baz end - names = {{ Foo.subclasses.map &.name }} - names.join('-') + {{ Foo.subclasses.map(&.name).join("-") }} )).to_string.should eq("Bar-Baz") end it "executes all_subclasses" do run(%( - require "prelude" - class Foo end @@ -729,8 +722,7 @@ describe "Code gen: macro" do class Baz < Bar end - names = {{ Foo.all_subclasses.map &.name }} - names.join('-') + {{ Foo.all_subclasses.map(&.name).join("-") }} )).to_string.should eq("Bar-Baz") end @@ -813,8 +805,6 @@ describe "Code gen: macro" do it "copies base macro def to sub-subtype even after it was copied to a subtype (#448)" do run(%( - require "prelude" - class Object def class_name : String {{@type.name.stringify}} diff --git a/spec/compiler/codegen/pointer_spec.cr b/spec/compiler/codegen/pointer_spec.cr index 60b7718771c8..1230d80cb5f6 100644 --- a/spec/compiler/codegen/pointer_spec.cr +++ b/spec/compiler/codegen/pointer_spec.cr @@ -325,8 +325,6 @@ describe "Code gen: pointer" do it "does pointerof class variable with class" do run(%( - require "prelude" - class Bar def initialize(@x : Int32) end @@ -406,8 +404,6 @@ describe "Code gen: pointer" do it "uses correct llvm module for typedef metaclass (#2877)" do run(%( - require "prelude" - lib LibFoo type Foo = Void* type Bar = Void* @@ -456,8 +452,6 @@ describe "Code gen: pointer" do it "generates correct code for Pointer.malloc(0) (#2905)" do run(%( - require "prelude" - class Foo def initialize(@value : Int32) end diff --git a/spec/compiler/codegen/return_spec.cr b/spec/compiler/codegen/return_spec.cr index 07a2d2243ade..fa65b1aefde6 100644 --- a/spec/compiler/codegen/return_spec.cr +++ b/spec/compiler/codegen/return_spec.cr @@ -22,11 +22,11 @@ describe "Code gen: return" do end it "return from function with nilable type" do - run(%(require "prelude"; def foo; return Reference.new if 1 == 1; end; foo.nil?)).to_b.should be_false + run(%(def foo; return Reference.new if 1 == 1; end; foo.nil?)).to_b.should be_false end it "return from function with nilable type 2" do - run(%(require "prelude"; def foo; return Reference.new if 1 == 1; end; foo.nil?)).to_b.should be_false + run(%(def foo; return Reference.new if 1 == 1; end; foo.nil?)).to_b.should be_false end it "returns empty from function" do diff --git a/spec/compiler/codegen/super_spec.cr b/spec/compiler/codegen/super_spec.cr index 5168ea2a71e9..600a397da77c 100644 --- a/spec/compiler/codegen/super_spec.cr +++ b/spec/compiler/codegen/super_spec.cr @@ -417,8 +417,6 @@ describe "Codegen: super" do it "calls super from virtual metaclass type (#2841)" do run(%( - require "prelude" - abstract class Foo def self.bar(x : Bool) x diff --git a/spec/compiler/codegen/var_spec.cr b/spec/compiler/codegen/var_spec.cr index dc16348a2856..3888abeeffb2 100644 --- a/spec/compiler/codegen/var_spec.cr +++ b/spec/compiler/codegen/var_spec.cr @@ -113,8 +113,6 @@ describe "Code gen: var" do it "codegens assignment that can never be reached" do codegen(%( - require "prelude" - if 1 == 1 && (x = nil) z = x end diff --git a/spec/compiler/codegen/virtual_spec.cr b/spec/compiler/codegen/virtual_spec.cr index 19fe63ef60de..3540557cd31e 100644 --- a/spec/compiler/codegen/virtual_spec.cr +++ b/spec/compiler/codegen/virtual_spec.cr @@ -293,8 +293,6 @@ describe "Code gen: virtual type" do it "doesn't lookup in Value+ when virtual type is Object+" do run(%( - require "prelude" - class Object def foo !nil? diff --git a/spec/compiler/semantic/instance_var_spec.cr b/spec/compiler/semantic/instance_var_spec.cr index 3e268bf7dd8e..e8b25d9fac69 100644 --- a/spec/compiler/semantic/instance_var_spec.cr +++ b/spec/compiler/semantic/instance_var_spec.cr @@ -741,8 +741,6 @@ describe "Semantic: instance var" do it "infers type from tuple literal" do assert_type(%( - require "prelude" - class Foo def initialize @x = {1, "foo"} @@ -759,8 +757,6 @@ describe "Semantic: instance var" do it "infers type from named tuple literal" do assert_type(%( - require "prelude" - class Foo def initialize @x = {x: 1, y: "foo"} diff --git a/spec/compiler/semantic/proc_spec.cr b/spec/compiler/semantic/proc_spec.cr index 9e4179477ba6..77532e19b241 100644 --- a/spec/compiler/semantic/proc_spec.cr +++ b/spec/compiler/semantic/proc_spec.cr @@ -249,8 +249,6 @@ describe "Semantic: proc" do it "types proc literal hard type inference (1)" do assert_type(%( - require "prelude" - class Foo def initialize(@x : Int32) end @@ -543,8 +541,6 @@ describe "Semantic: proc" do it "types proc literal with a type that was never instantiated" do assert_type(%( - require "prelude" - class Foo def initialize(@x : Int32) end @@ -560,8 +556,6 @@ describe "Semantic: proc" do it "types proc pointer with a type that was never instantiated" do assert_type(%( - require "prelude" - class Foo def initialize(@x : Int32) end From ee70ebbc99a59de09ba8329f91068e89ffd7c3a1 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 1 Mar 2024 11:13:55 +0100 Subject: [PATCH 0986/1551] Fix: init schedulers before we spawn fibers (#14339) The signal and interrupt handlers spawn fibers, and should be setup after we properly start the fiber schedulers. This is working today because we lazily start schedulers for a thread, and we happen to not have recursive dependencies. --- src/kernel.cr | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/kernel.cr b/src/kernel.cr index d3817ee11661..78f84d292bfa 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -563,12 +563,6 @@ end {% end %} {% unless flag?(:interpreted) || flag?(:wasm32) %} - {% if flag?(:win32) %} - Crystal::System::Process.start_interrupt_loop - {% else %} - Crystal::System::Signal.setup_default_handlers - {% end %} - # load debug info on start up of the program is executed with CRYSTAL_LOAD_DEBUG_INFO=1 # this will make debug info available on print_frame that is used by Crystal's segfault handler # @@ -579,4 +573,10 @@ end Exception::CallStack.setup_crash_handler Crystal::Scheduler.init + + {% if flag?(:win32) %} + Crystal::System::Process.start_interrupt_loop + {% else %} + Crystal::System::Signal.setup_default_handlers + {% end %} {% end %} From 87518d6789d32a891c281f99d14b72e9aaab8956 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 1 Mar 2024 23:12:09 +0800 Subject: [PATCH 0987/1551] Never discard ivar initializer inside `.allocate` and `.pre_initialize` (#14337) --- spec/primitives/reference_spec.cr | 20 ++++++++++++++++++++ src/compiler/crystal/codegen/codegen.cr | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/spec/primitives/reference_spec.cr b/spec/primitives/reference_spec.cr index 52f179296ee8..c726d4845607 100644 --- a/spec/primitives/reference_spec.cr +++ b/spec/primitives/reference_spec.cr @@ -21,8 +21,28 @@ private class Bar < Base end end +private struct Inner +end + +private class Outer + @x = Inner.new +end + describe "Primitives: reference" do + describe ".allocate" do + it "doesn't fail on complex ivar initializer if value is discarded (#14325)" do + Outer.allocate + 1 + end + end + describe ".pre_initialize" do + it "doesn't fail on complex ivar initializer if value is discarded (#14325)" do + bar_buffer = GC.malloc(instance_sizeof(Outer)) + Outer.pre_initialize(bar_buffer) + 1 + end + it "zeroes the instance data" do bar_buffer = GC.malloc(instance_sizeof(Bar)) Slice.new(bar_buffer.as(UInt8*), instance_sizeof(Bar)).fill(0xFF) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 1535b02dc99f..c80bd579d982 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -2126,7 +2126,7 @@ module Crystal context.vars = LLVMVars.new alloca_vars init.meta_vars - accept init.value + request_value(init.value) ivar_ptr = instance_var_ptr real_type, init.name, type_ptr assign ivar_ptr, ivar.type, init.value.type, @last From 79a8d4e24affe1364fcd74643bd1d8e26ed3c475 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 1 Mar 2024 23:12:19 +0800 Subject: [PATCH 0988/1551] Enable `@[Primitive(:va_arg)]` semantic spec on Windows (#14338) --- spec/compiler/semantic/primitives_spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/compiler/semantic/primitives_spec.cr b/spec/compiler/semantic/primitives_spec.cr index 048c282606d6..35bd16938005 100644 --- a/spec/compiler/semantic/primitives_spec.cr +++ b/spec/compiler/semantic/primitives_spec.cr @@ -223,7 +223,7 @@ describe "Semantic: primitives" do ) end - pending_win32 "types va_arg primitive" do + it "types va_arg primitive" do assert_type(%( struct VaList @[Primitive(:va_arg)] From c734cc4c1b74ea261155d6231f2c716b89444e92 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 5 Mar 2024 13:42:21 +0100 Subject: [PATCH 0989/1551] Fix: Atomics and Locks (ARM, AArch64, X86) (#14293) --- spec/std/atomic_spec.cr | 66 ++++++-- src/atomic.cr | 181 +++++++++++++++++---- src/compiler/crystal/codegen/primitives.cr | 31 +++- src/crystal/rw_lock.cr | 44 +++-- src/crystal/spin_lock.cr | 21 ++- src/mutex.cr | 44 ++--- 6 files changed, 298 insertions(+), 89 deletions(-) diff --git a/spec/std/atomic_spec.cr b/spec/std/atomic_spec.cr index 8f5fe8e8b22a..4006c33b1e1c 100644 --- a/spec/std/atomic_spec.cr +++ b/spec/std/atomic_spec.cr @@ -97,49 +97,71 @@ describe Atomic do atomic.compare_and_set([1], arr2).should eq({arr1, false}) atomic.get.should be(arr1) end + + it "explicit ordering" do + atomic = Atomic.new(1) + + atomic.compare_and_set(2, 3, :acquire, :relaxed).should eq({1, false}) + atomic.get.should eq(1) + + atomic.compare_and_set(1, 3, :acquire_release, :relaxed).should eq({1, true}) + atomic.get.should eq(3) + end end it "#adds" do atomic = Atomic.new(1) atomic.add(2).should eq(1) atomic.get.should eq(3) + atomic.add(1, :relaxed).should eq(3) + atomic.get.should eq(4) end it "#sub" do atomic = Atomic.new(1) atomic.sub(2).should eq(1) atomic.get.should eq(-1) + atomic.sub(1, :relaxed).should eq(-1) + atomic.get.should eq(-2) end it "#and" do atomic = Atomic.new(5) atomic.and(3).should eq(5) atomic.get.should eq(1) + atomic.and(7, :relaxed).should eq(1) + atomic.get.should eq(1) end it "#nand" do atomic = Atomic.new(5) atomic.nand(3).should eq(5) atomic.get.should eq(-2) + atomic.nand(7, :relaxed).should eq(-2) + atomic.get.should eq(-7) end it "#or" do atomic = Atomic.new(5) atomic.or(2).should eq(5) atomic.get.should eq(7) + atomic.or(8, :relaxed).should eq(7) + atomic.get.should eq(15) end it "#xor" do atomic = Atomic.new(5) atomic.xor(3).should eq(5) atomic.get.should eq(6) + atomic.xor(5, :relaxed).should eq(6) + atomic.get.should eq(3) end it "#max with signed" do atomic = Atomic.new(5) atomic.max(2).should eq(5) atomic.get.should eq(5) - atomic.max(10).should eq(5) + atomic.max(10, :relaxed).should eq(5) atomic.get.should eq(10) end @@ -147,7 +169,7 @@ describe Atomic do atomic = Atomic.new(5_u32) atomic.max(2_u32).should eq(5_u32) atomic.get.should eq(5_u32) - atomic.max(UInt32::MAX).should eq(5_u32) + atomic.max(UInt32::MAX, :relaxed).should eq(5_u32) atomic.get.should eq(UInt32::MAX) end @@ -165,7 +187,7 @@ describe Atomic do atomic = Atomic.new(5) atomic.min(10).should eq(5) atomic.get.should eq(5) - atomic.min(2).should eq(5) + atomic.min(2, :relaxed).should eq(5) atomic.get.should eq(2) end @@ -173,7 +195,7 @@ describe Atomic do atomic = Atomic.new(UInt32::MAX) atomic.min(10_u32).should eq(UInt32::MAX) atomic.get.should eq(10_u32) - atomic.min(15_u32).should eq(10_u32) + atomic.min(15_u32, :relaxed).should eq(10_u32) atomic.get.should eq(10_u32) end @@ -187,20 +209,28 @@ describe Atomic do atomic.get.should eq(AtomicEnum::Minus) end - it "#set" do - atomic = Atomic.new(1) - atomic.set(2).should eq(2) - atomic.get.should eq(2) - end + describe "#set" do + it "with integer" do + atomic = Atomic.new(1) + atomic.set(2).should eq(2) + atomic.get.should eq(2) + end - it "#set with nil (#4062)" do - atomic = Atomic(String?).new(nil) + it "with nil (#4062)" do + atomic = Atomic(String?).new(nil) - atomic.set("foo") - atomic.get.should eq("foo") + atomic.set("foo") + atomic.get.should eq("foo") + + atomic.set(nil) + atomic.get.should eq(nil) + end - atomic.set(nil) - atomic.get.should eq(nil) + it "explicit ordering" do + atomic = Atomic.new(1) + atomic.set(0, :release).should eq(0) + atomic.get(:acquire).should eq(0) + end end it "#lazy_set" do @@ -243,6 +273,12 @@ describe Atomic do atomic.swap(arr1).should be(arr2) atomic.get.should be(arr1) end + + it "explicit ordering" do + atomic = Atomic.new(1) + atomic.swap(2, :acquire).should eq(1) + atomic.get.should eq(2) + end end end diff --git a/src/atomic.cr b/src/atomic.cr index eedf081141d2..8569b253343a 100644 --- a/src/atomic.cr +++ b/src/atomic.cr @@ -7,6 +7,37 @@ require "llvm/enums/atomic" # reference types or `Nil`, then only `#compare_and_set`, `#swap`, `#set`, # `#lazy_set`, `#get`, and `#lazy_get` are available. struct Atomic(T) + # Specifies how memory accesses, including non atomic, are to be reordered + # around atomics. Follows the C/C++ semantics: + # . + # + # By default atomics use the sequentially consistent ordering, which has the + # strongest guarantees. If all you need is to increment a counter, a relaxed + # ordering may be enough. If you need to synchronize access to other memory + # (e.g. locks) you may try the acquire/release semantics that may be faster on + # some architectures (e.g. X86) but remember that an acquire must be paired + # with a release for the ordering to be guaranteed. + enum Ordering + Relaxed = LLVM::AtomicOrdering::Monotonic + Acquire = LLVM::AtomicOrdering::Acquire + Release = LLVM::AtomicOrdering::Release + AcquireRelease = LLVM::AtomicOrdering::AcquireRelease + SequentiallyConsistent = LLVM::AtomicOrdering::SequentiallyConsistent + end + + # Adds an explicit memory barrier with the specified memory order guarantee. + # + # Atomics on weakly-ordered CPUs (e.g. ARM32) may not guarantee memory order + # of other memory accesses, and an explicit memory barrier is thus required. + # + # Notes: + # - X86 is strongly-ordered and trying to add a fence should be a NOOP; + # - AArch64 guarantees memory order and doesn't need explicit fences in + # addition to the atomics (but may need barriers in other cases). + macro fence(ordering = :sequentially_consistent) + ::Atomic::Ops.fence({{ordering}}, false) + end + # Creates an Atomic with the given initial value. def initialize(@value : T) {% if !T.union? && (T == Char || T < Int::Primitive || T < Enum) %} @@ -38,6 +69,52 @@ struct Atomic(T) Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :sequentially_consistent) end + # Compares this atomic's value with *cmp* using explicit memory orderings: + # + # * if they are equal, sets the value to *new*, and returns `{old_value, true}` + # * if they are not equal the value remains the same, and returns `{old_value, false}` + # + # Reference types are compared by `#same?`, not `#==`. + # + # ``` + # atomic = Atomic.new(0_u32) + # + # value = atomic.get(:acquire) + # loop do + # value, success = atomic.compare_and_set(value, value &+ 1, :acquire_release, :acquire) + # break if success + # end + # ``` + def compare_and_set(cmp : T, new : T, success_ordering : Ordering, failure_ordering : Ordering) : {T, Bool} + case {success_ordering, failure_ordering} + when {.relaxed?, .relaxed?} + Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :monotonic, :monotonic) + when {.acquire?, .relaxed?} + Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire, :monotonic) + when {.acquire?, .acquire?} + Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire, :acquire) + when {.release?, .relaxed?} + Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :release, :monotonic) + when {.release?, .acquire?} + Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :release, :acquire) + when {.acquire_release?, .relaxed?} + Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire_release, :monotonic) + when {.acquire_release?, .acquire?} + Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire_release, :acquire) + when {.sequentially_consistent?, .relaxed?} + Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :monotonic) + when {.sequentially_consistent?, .acquire?} + Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :acquire) + when {.sequentially_consistent?, .sequentially_consistent?} + Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :sequentially_consistent) + else + if failure_ordering.release? || failure_ordering.acquire_release? + raise ArgumentError.new("Failure ordering cannot include release semantics") + end + raise ArgumentError.new("Failure ordering shall be no stronger than success ordering") + end + end + # Performs `atomic_value &+= value`. Returns the old value. # # `T` cannot contain any reference types. @@ -47,9 +124,9 @@ struct Atomic(T) # atomic.add(2) # => 1 # atomic.get # => 3 # ``` - def add(value : T) : T + def add(value : T, ordering : Ordering = :sequentially_consistent) : T check_reference_type - Ops.atomicrmw(:add, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:add, pointerof(@value), value, ordering) end # Performs `atomic_value &-= value`. Returns the old value. @@ -61,9 +138,9 @@ struct Atomic(T) # atomic.sub(2) # => 9 # atomic.get # => 7 # ``` - def sub(value : T) : T + def sub(value : T, ordering : Ordering = :sequentially_consistent) : T check_reference_type - Ops.atomicrmw(:sub, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:sub, pointerof(@value), value, ordering) end # Performs `atomic_value &= value`. Returns the old value. @@ -75,9 +152,9 @@ struct Atomic(T) # atomic.and(3) # => 5 # atomic.get # => 1 # ``` - def and(value : T) : T + def and(value : T, ordering : Ordering = :sequentially_consistent) : T check_reference_type - Ops.atomicrmw(:and, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:and, pointerof(@value), value, ordering) end # Performs `atomic_value = ~(atomic_value & value)`. Returns the old value. @@ -89,9 +166,9 @@ struct Atomic(T) # atomic.nand(3) # => 5 # atomic.get # => -2 # ``` - def nand(value : T) : T + def nand(value : T, ordering : Ordering = :sequentially_consistent) : T check_reference_type - Ops.atomicrmw(:nand, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:nand, pointerof(@value), value, ordering) end # Performs `atomic_value |= value`. Returns the old value. @@ -103,9 +180,9 @@ struct Atomic(T) # atomic.or(2) # => 5 # atomic.get # => 7 # ``` - def or(value : T) : T + def or(value : T, ordering : Ordering = :sequentially_consistent) : T check_reference_type - Ops.atomicrmw(:or, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:or, pointerof(@value), value, ordering) end # Performs `atomic_value ^= value`. Returns the old value. @@ -117,9 +194,9 @@ struct Atomic(T) # atomic.xor(3) # => 5 # atomic.get # => 6 # ``` - def xor(value : T) : T + def xor(value : T, ordering : Ordering = :sequentially_consistent) : T check_reference_type - Ops.atomicrmw(:xor, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:xor, pointerof(@value), value, ordering) end # Performs `atomic_value = {atomic_value, value}.max`. Returns the old value. @@ -135,18 +212,18 @@ struct Atomic(T) # atomic.max(10) # => 5 # atomic.get # => 10 # ``` - def max(value : T) + def max(value : T, ordering : Ordering = :sequentially_consistent) check_reference_type {% if T < Enum %} if @value.value.is_a?(Int::Signed) - Ops.atomicrmw(:max, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:max, pointerof(@value), value, ordering) else - Ops.atomicrmw(:umax, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:umax, pointerof(@value), value, ordering) end {% elsif T < Int::Signed %} - Ops.atomicrmw(:max, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:max, pointerof(@value), value, ordering) {% else %} - Ops.atomicrmw(:umax, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:umax, pointerof(@value), value, ordering) {% end %} end @@ -163,18 +240,18 @@ struct Atomic(T) # atomic.min(3) # => 5 # atomic.get # => 3 # ``` - def min(value : T) + def min(value : T, ordering : Ordering = :sequentially_consistent) check_reference_type {% if T < Enum %} if @value.value.is_a?(Int::Signed) - Ops.atomicrmw(:min, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:min, pointerof(@value), value, ordering) else - Ops.atomicrmw(:umin, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:umin, pointerof(@value), value, ordering) end {% elsif T < Int::Signed %} - Ops.atomicrmw(:min, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:min, pointerof(@value), value, ordering) {% else %} - Ops.atomicrmw(:umin, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:umin, pointerof(@value), value, ordering) {% end %} end @@ -185,12 +262,12 @@ struct Atomic(T) # atomic.swap(10) # => 5 # atomic.get # => 10 # ``` - def swap(value : T) + def swap(value : T, ordering : Ordering = :sequentially_consistent) {% if T.union_types.all? { |t| t == Nil || t < Reference } && T != Nil %} - address = Ops.atomicrmw(:xchg, pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(value.as(Void*).address), :sequentially_consistent, false) + address = atomicrmw(:xchg, pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(value.as(Void*).address), ordering) Pointer(T).new(address).as(T) {% else %} - Ops.atomicrmw(:xchg, pointerof(@value), value, :sequentially_consistent, false) + atomicrmw(:xchg, pointerof(@value), value, ordering) {% end %} end @@ -201,8 +278,17 @@ struct Atomic(T) # atomic.set(10) # => 10 # atomic.get # => 10 # ``` - def set(value : T) : T - Ops.store(pointerof(@value), value.as(T), :sequentially_consistent, true) + def set(value : T, ordering : Ordering = :sequentially_consistent) : T + case ordering + in .relaxed? + Ops.store(pointerof(@value), value.as(T), :monotonic, true) + in .release? + Ops.store(pointerof(@value), value.as(T), :release, true) + in .sequentially_consistent? + Ops.store(pointerof(@value), value.as(T), :sequentially_consistent, true) + in .acquire?, .acquire_release? + raise ArgumentError.new("Atomic store cannot have acquire semantic") + end value end @@ -213,15 +299,28 @@ struct Atomic(T) # atomic.lazy_set(10) # => 10 # atomic.get # => 10 # ``` + # + # NOTE: use with caution, this may break atomic guarantees. def lazy_set(@value : T) : T end # Atomically returns this atomic's value. - def get : T - Ops.load(pointerof(@value), :sequentially_consistent, true) + def get(ordering : Ordering = :sequentially_consistent) : T + case ordering + in .relaxed? + Ops.load(pointerof(@value), :monotonic, true) + in .acquire? + Ops.load(pointerof(@value), :acquire, true) + in .sequentially_consistent? + Ops.load(pointerof(@value), :sequentially_consistent, true) + in .release?, .acquire_release? + raise ArgumentError.new("Atomic load cannot have release semantic") + end end # **Non-atomically** returns this atomic's value. + # + # NOTE: use with caution, this may break atomic guarantees. def lazy_get @value end @@ -232,6 +331,21 @@ struct Atomic(T) {% end %} end + private macro atomicrmw(operation, pointer, value, ordering) + case ordering + in .relaxed? + Ops.atomicrmw({{operation}}, {{pointer}}, {{value}}, :monotonic, false) + in .acquire? + Ops.atomicrmw({{operation}}, {{pointer}}, {{value}}, :acquire, false) + in .release? + Ops.atomicrmw({{operation}}, {{pointer}}, {{value}}, :release, false) + in .acquire_release? + Ops.atomicrmw({{operation}}, {{pointer}}, {{value}}, :acquire_release, false) + in .sequentially_consistent? + Ops.atomicrmw({{operation}}, {{pointer}}, {{value}}, :sequentially_consistent, false) + end + end + # :nodoc: module Ops # Defines methods that directly map to LLVM instructions related to atomic operations. @@ -279,11 +393,18 @@ struct Atomic::Flag # Atomically tries to set the flag. Only succeeds and returns `true` if the # flag wasn't previously set; returns `false` otherwise. def test_and_set : Bool - Atomic::Ops.atomicrmw(:xchg, pointerof(@value), 1_u8, :sequentially_consistent, false) == 0_u8 + ret = Atomic::Ops.atomicrmw(:xchg, pointerof(@value), 1_u8, :sequentially_consistent, false) == 0_u8 + {% if flag?(:arm) %} + Atomic::Ops.fence(:sequentially_consistent, false) if ret + {% end %} + ret end # Atomically clears the flag. def clear : Nil + {% if flag?(:arm) %} + Atomic::Ops.fence(:sequentially_consistent, false) + {% end %} Atomic::Ops.store(pointerof(@value), 0_u8, :sequentially_consistent, true) end end diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index 80ba7d832a41..eb048076de50 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -1165,8 +1165,11 @@ class Crystal::CodeGenVisitor call = check_atomic_call(call, target_def) ptr, cmp, new, success_ordering, failure_ordering = call_args - success_ordering = atomic_ordering_get_const(call.args[-2], success_ordering) - failure_ordering = atomic_ordering_get_const(call.args[-1], failure_ordering) + success_node = call.args[-2] + failure_node = call.args[-1] + success_ordering = atomic_ordering_get_const(success_node, success_ordering) + failure_ordering = atomic_ordering_get_const(failure_node, failure_ordering) + validate_atomic_cmpxchg_ordering(success_node, success_ordering, failure_node, failure_ordering) value = builder.cmpxchg(ptr, cmp, new, success_ordering, failure_ordering) value_type = node.type.as(TupleInstanceType) @@ -1192,9 +1195,14 @@ class Crystal::CodeGenVisitor call = check_atomic_call(call, target_def) ordering, _ = call_args - ordering = atomic_ordering_get_const(call.args[0], ordering) + ordering_node = call.args[0] + ordering = atomic_ordering_get_const(ordering_node, ordering) singlethread = bool_from_bool_literal(call.args[1]) + ordering_node.raise "must be atomic" if ordering.not_atomic? + ordering_node.raise "cannot be unordered" if ordering.unordered? + ordering_node.raise "must have acquire, release, acquire_release or sequentially_consistent ordering" if ordering.monotonic? + builder.fence(ordering, singlethread) llvm_nil end @@ -1288,6 +1296,23 @@ class Crystal::CodeGenVisitor end end + def validate_atomic_cmpxchg_ordering(success_node, success_ordering, failure_node, failure_ordering) + success_node.raise "must be atomic" if success_ordering.not_atomic? + success_node.raise "cannot be unordered" if success_ordering.unordered? + + failure_node.raise "must be atomic" if failure_ordering.not_atomic? + failure_node.raise "cannot be unordered" if failure_ordering.unordered? + failure_node.raise "cannot include release semantics" if failure_ordering.release? || failure_ordering.acquire_release? + + {% if LibLLVM::IS_LT_130 %} + # Atomic(T) macros enforce this rule to provide a consistent public API + # regardless of which LLVM version crystal was compiled with. The compiler, + # however, only needs to make sure that the codegen is correct for the LLVM + # version + failure_node.raise "shall be no stronger than success ordering" if failure_ordering > success_ordering + {% end %} + end + def bool_from_bool_literal(node) unless node.is_a?(BoolLiteral) node.raise "BUG: expected bool literal" diff --git a/src/crystal/rw_lock.cr b/src/crystal/rw_lock.cr index de61fd31be14..a47472a99407 100644 --- a/src/crystal/rw_lock.cr +++ b/src/crystal/rw_lock.cr @@ -1,37 +1,55 @@ # :nodoc: class Crystal::RWLock - @writer = Atomic(Int32).new(0) + private UNLOCKED = 0 + private LOCKED = 1 + + @writer = Atomic(Int32).new(UNLOCKED) @readers = Atomic(Int32).new(0) - def read_lock + def read_lock : Nil loop do - while @writer.get != 0 + while @writer.get(:relaxed) != UNLOCKED Intrinsics.pause end - @readers.add(1) + @readers.add(1, :acquire) - break if @writer.get == 0 + if @writer.get(:acquire) == UNLOCKED + {% if flag?(:arm) %} + Atomic.fence(:acquire) + {% end %} + return + end - @readers.sub(1) + @readers.sub(1, :release) end end - def read_unlock - @readers.sub(1) + def read_unlock : Nil + {% if flag?(:arm) %} + Atomic.fence(:release) + {% end %} + @readers.sub(1, :release) end - def write_lock - while @writer.swap(1) != 0 + def write_lock : Nil + while @writer.swap(LOCKED, :acquire) != UNLOCKED Intrinsics.pause end - while @readers.get != 0 + while @readers.get(:acquire) != 0 Intrinsics.pause end + + {% if flag?(:arm) %} + Atomic.fence(:acquire) + {% end %} end - def write_unlock - @writer.lazy_set(0) + def write_unlock : Nil + {% if flag?(:arm) %} + Atomic.fence(:release) + {% end %} + @writer.set(UNLOCKED, :release) end end diff --git a/src/crystal/spin_lock.cr b/src/crystal/spin_lock.cr index e3136cdf3fe9..0792ce652159 100644 --- a/src/crystal/spin_lock.cr +++ b/src/crystal/spin_lock.cr @@ -1,28 +1,31 @@ # :nodoc: class Crystal::SpinLock + private UNLOCKED = 0 + private LOCKED = 1 + {% if flag?(:preview_mt) %} - @m = Atomic(Int32).new(0) + @m = Atomic(Int32).new(UNLOCKED) {% end %} def lock {% if flag?(:preview_mt) %} - while @m.swap(1) == 1 - while @m.get == 1 + while @m.swap(LOCKED, :acquire) == LOCKED + while @m.get(:relaxed) == LOCKED Intrinsics.pause end end - {% if flag?(:aarch64) %} - Atomic::Ops.fence :sequentially_consistent, false - {% end %} + {% if flag?(:arm) %} + Atomic.fence(:acquire) + {% end %} {% end %} end def unlock {% if flag?(:preview_mt) %} - {% if flag?(:aarch64) %} - Atomic::Ops.fence :sequentially_consistent, false + {% if flag?(:arm) %} + Atomic.fence(:release) {% end %} - @m.lazy_set(0) + @m.set(UNLOCKED, :release) {% end %} end diff --git a/src/mutex.cr b/src/mutex.cr index 83dd4bb53429..38d34c14b070 100644 --- a/src/mutex.cr +++ b/src/mutex.cr @@ -16,7 +16,10 @@ require "crystal/spin_lock" # mutex from the same fiber will deadlock. Any fiber can unlock the mutex, even # if it wasn't previously locked. class Mutex - @state = Atomic(Int32).new(0) + private UNLOCKED = 0 + private LOCKED = 1 + + @state = Atomic(Int32).new(UNLOCKED) @mutex_fiber : Fiber? @lock_count = 0 @queue = Deque(Fiber).new @@ -34,7 +37,10 @@ class Mutex @[AlwaysInline] def lock : Nil - if @state.swap(1) == 0 + if @state.swap(LOCKED, :acquire) == UNLOCKED + {% if flag?(:arm) %} + Atomic.fence(:acquire) + {% end %} @mutex_fiber = Fiber.current unless @protection.unchecked? return end @@ -49,20 +55,24 @@ class Mutex end lock_slow - nil end @[NoInline] - private def lock_slow + private def lock_slow : Nil loop do break if try_lock @lock.sync do @queue_count.add(1) - if @state.get == 0 - if @state.swap(1) == 0 - @queue_count.add(-1) - @mutex_fiber = Fiber.current + + if @state.get(:relaxed) == UNLOCKED + if @state.swap(LOCKED, :acquire) == UNLOCKED + {% if flag?(:arm) %} + Atomic.fence(:acquire) + {% end %} + @queue_count.sub(1) + + @mutex_fiber = Fiber.current unless @protection.unchecked? return end end @@ -72,20 +82,21 @@ class Mutex Crystal::Scheduler.reschedule end - @mutex_fiber = Fiber.current - nil + @mutex_fiber = Fiber.current unless @protection.unchecked? end private def try_lock i = 1000 - while @state.swap(1) != 0 - while @state.get != 0 + while @state.swap(LOCKED, :acquire) != UNLOCKED + while @state.get(:relaxed) != UNLOCKED Intrinsics.pause i &-= 1 return false if i == 0 end end - + {% if flag?(:arm) %} + Atomic.fence(:acquire) + {% end %} true end @@ -107,10 +118,7 @@ class Mutex @mutex_fiber = nil end - {% if flag?(:aarch64) %} - Atomic::Ops.fence :sequentially_consistent, false - {% end %} - @state.lazy_set(0) + @state.set(UNLOCKED, :release) if @queue_count.get == 0 return @@ -127,8 +135,6 @@ class Mutex end end fiber.enqueue if fiber - - nil end def synchronize(&) From a14191f1ebd6daacad86744f7ad60bcd0c6e5a85 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 5 Mar 2024 20:42:31 +0800 Subject: [PATCH 0990/1551] Allow `--single-module` and `--threads` for `eval` and `spec` (#14341) --- src/compiler/crystal/command.cr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 431c9114a6b7..6ae970c04fbf 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -664,6 +664,12 @@ class Crystal::Command opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3") do |level| compiler.optimization_mode = Compiler::OptimizationMode.from_value?(level.to_i) || raise Error.new("Unknown optimization mode #{level}") end + opts.on("--single-module", "Generate a single LLVM module") do + compiler.single_module = true + end + opts.on("--threads NUM", "Maximum number of threads to use") do |n_threads| + compiler.n_threads = n_threads.to_i? || raise Error.new("Invalid thread count: #{n_threads}") + end opts.on("-s", "--stats", "Enable statistics output") do compiler.progress_tracker.stats = true end From d93fe821aeb6a0f6653e4742d14da1b854471099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 6 Mar 2024 11:31:57 +0100 Subject: [PATCH 0991/1551] Add `Token::Kind#unary_operator?` (#14342) --- src/compiler/crystal/syntax/parser.cr | 2 +- src/compiler/crystal/syntax/to_s.cr | 1 + src/compiler/crystal/syntax/token.cr | 7 +++++++ src/compiler/crystal/tools/formatter.cr | 15 ++++++++------- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 5d80b28ed20b..3185fdee6fdc 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -627,7 +627,7 @@ module Crystal def parse_prefix name_location = @token.location case token_type = @token.type - when .op_bang?, .op_plus?, .op_minus?, .op_tilde?, .op_amp_plus?, .op_amp_minus? + when .unary_operator? location = @token.location next_token_skip_space_or_newline check_void_expression_keyword diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 3ea146d15c3e..7735cddb3951 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -332,6 +332,7 @@ module Crystal visit_call node end + # Related: `Token::Kind#unary_operator?` UNARY_OPERATORS = {"+", "-", "~", "&+", "&-"} def visit_call(node, ignore_obj = false) diff --git a/src/compiler/crystal/syntax/token.cr b/src/compiler/crystal/syntax/token.cr index 7b5da2091b67..d26b0a0cb22a 100644 --- a/src/compiler/crystal/syntax/token.cr +++ b/src/compiler/crystal/syntax/token.cr @@ -249,6 +249,13 @@ module Crystal end end + # Returns true if the operator can be used in prefix notation. + # + # Related: `ToSVisitor::UNARY_OPERATORS` + def unary_operator? + self.in?(OP_BANG, OP_PLUS, OP_MINUS, OP_TILDE, OP_AMP_PLUS, OP_AMP_MINUS) + end + def magic? magic_dir? || magic_end_line? || magic_file? || magic_line? end diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index f7ebf63be7d2..79d7f3cf5a68 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -2541,13 +2541,14 @@ module Crystal write_token :OP_COLON_COLON if node.global? if obj - {Token::Kind::OP_BANG, Token::Kind::OP_PLUS, Token::Kind::OP_MINUS, Token::Kind::OP_TILDE, Token::Kind::OP_AMP_PLUS, Token::Kind::OP_AMP_MINUS}.each do |op| - if node.name == op.to_s && @token.type == op && node.args.empty? - write op - next_token_skip_space_or_newline - accept obj - return false - end + # This handles unary operators written in prefix notation. + # The relevant distinction is that the call has a receiver and the + # current token is not that object but a unary operator. + if @token.type.unary_operator? && node.name == @token.type.to_s && node.args.empty? + write @token.type + next_token_skip_space_or_newline + accept obj + return false end accept obj From e009a1e7e514176f2cc4933c3ada939a2f4c2926 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 6 Mar 2024 17:25:33 +0100 Subject: [PATCH 0992/1551] Fix: Crystal::RWLock should be a struct (#14345) --- src/crystal/rw_lock.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crystal/rw_lock.cr b/src/crystal/rw_lock.cr index a47472a99407..c83074180e25 100644 --- a/src/crystal/rw_lock.cr +++ b/src/crystal/rw_lock.cr @@ -1,5 +1,5 @@ # :nodoc: -class Crystal::RWLock +struct Crystal::RWLock private UNLOCKED = 0 private LOCKED = 1 From 06061585f54e97742f74e580204e2a734545f5ca Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 6 Mar 2024 17:26:08 +0100 Subject: [PATCH 0993/1551] Codegen: on demand distribution to forked processes (#14273) --- src/compiler/crystal/compiler.cr | 121 +++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 38 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 416e29bf2293..4de446ba6425 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -536,10 +536,13 @@ module Crystal wants_stats_or_progress = @progress_tracker.stats? || @progress_tracker.progress? - # If threads is 1 and no stats/progress is needed we can avoid - # fork/spawn/channels altogether. This is particularly useful for - # CI because there forking eventually leads to "out of memory" errors. - if @n_threads == 1 + # Don't start more processes than compilation units + n_threads = @n_threads.clamp(1..units.size) + + # If threads is 1 we can avoid fork/spawn/channels altogether. This is + # particularly useful for CI because there forking eventually leads to + # "out of memory" errors. + if n_threads == 1 units.each do |unit| unit.compile all_reused << unit.name if wants_stats_or_progress && unit.reused_previous_compilation? @@ -547,62 +550,104 @@ module Crystal return all_reused end - {% if !Crystal::System::Process.class.has_method?("fork") %} + {% if !LibC.has_method?("fork") %} raise "Cannot fork compiler. `Crystal::System::Process.fork` is not implemented on this system." {% elsif flag?(:preview_mt) %} raise "Cannot fork compiler in multithread mode" {% else %} - jobs_count = 0 - wait_channel = Channel(Array(String)).new(@n_threads) + workers = fork_workers(n_threads) do |input, output| + while i = input.gets(chomp: true).presence + unit = units[i.to_i] + unit.compile + result = {name: unit.name, reused: unit.reused_previous_compilation?} + output.puts result.to_json + end + end + + overqueue = 1 + indexes = Atomic(Int32).new(0) + channel = Channel(String).new(n_threads) + completed = Channel(Nil).new(n_threads) - units.each_slice(Math.max(units.size // @n_threads, 1)) do |slice| - jobs_count += 1 + workers.each do |pid, input, output| spawn do - # For stats output we want to count how many previous - # .o files were reused, mainly to detect performance regressions. - # Because we fork, we must communicate using a pipe. - reused = [] of String - if wants_stats_or_progress - pr, pw = IO.pipe - spawn do - pr.each_line do |line| - unit = JSON.parse(line) - reused << unit["name"].as_s if unit["reused"].as_bool - @progress_tracker.stage_progress += 1 - end + overqueued = 0 + + overqueue.times do + if (index = indexes.add(1)) < units.size + input.puts index + overqueued += 1 end end - codegen_process = Crystal::System::Process.fork do - pipe_w = pw - slice.each do |unit| - unit.compile - if pipe_w - unit_json = {name: unit.name, reused: unit.reused_previous_compilation?}.to_json - pipe_w.puts unit_json - end - end + while (index = indexes.add(1)) < units.size + input.puts index + + response = output.gets(chomp: true).not_nil! + channel.send response end - Process.new(codegen_process).wait - if pipe_w = pw - pipe_w.close - Fiber.yield + overqueued.times do + response = output.gets(chomp: true).not_nil! + channel.send response end - wait_channel.send reused + input << '\n' + input.close + output.close + + Process.new(pid).wait + completed.send(nil) end end - jobs_count.times do - reused = wait_channel.receive - all_reused.concat(reused) + spawn do + n_threads.times { completed.receive } + channel.close + end + + while response = channel.receive? + next unless wants_stats_or_progress + + result = JSON.parse(response) + all_reused << result["name"].as_s if result["reused"].as_bool + @progress_tracker.stage_progress += 1 end all_reused {% end %} end + private def fork_workers(n_threads) + workers = [] of {Int32, IO::FileDescriptor, IO::FileDescriptor} + + n_threads.times do + iread, iwrite = IO.pipe + oread, owrite = IO.pipe + + iwrite.flush_on_newline = true + owrite.flush_on_newline = true + + pid = Crystal::System::Process.fork do + iwrite.close + oread.close + + yield iread, owrite + + iread.close + owrite.close + exit 0 + end + + iread.close + owrite.close + + workers << {pid, iwrite, oread} + end + + workers + end + private def print_macro_run_stats(program) return unless @progress_tracker.stats? return if program.compiled_macros_cache.empty? From a9259627c6250412b8738c67498eb402f2c9b002 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 7 Mar 2024 05:02:15 +0800 Subject: [PATCH 0994/1551] x86-64 Solaris / illumos support (#14343) --- .github/workflows/smoke.yml | 2 + spec/std/exception/call_stack_spec.cr | 10 +- spec/std/io/io_spec.cr | 8 +- spec/std/llvm/llvm_spec.cr | 2 + spec/std/process_spec.cr | 15 +-- spec/std/socket/tcp_server_spec.cr | 2 +- spec/std/socket/udp_socket_spec.cr | 4 + spec/std/string_spec.cr | 4 +- src/compiler/crystal/codegen/fun.cr | 4 +- src/compiler/crystal/codegen/target.cr | 8 +- src/compiler/crystal/semantic/flags.cr | 1 + src/crystal/dwarf/strings.cr | 2 +- src/crystal/iconv.cr | 2 +- src/crystal/system/unix/dir.cr | 16 ++- src/crystal/system/unix/file_descriptor.cr | 42 +++--- src/crystal/system/unix/pthread.cr | 4 +- src/crystal/system/unix/signal.cr | 7 +- src/errno.cr | 6 + src/exception/call_stack/libunwind.cr | 2 +- src/lib_c/x86_64-solaris/c/arpa/inet.cr | 9 ++ src/lib_c/x86_64-solaris/c/dirent.cr | 23 ++++ src/lib_c/x86_64-solaris/c/dlfcn.cr | 21 +++ src/lib_c/x86_64-solaris/c/elf.cr | 23 ++++ src/lib_c/x86_64-solaris/c/errno.cr | 124 ++++++++++++++++++ src/lib_c/x86_64-solaris/c/fcntl.cr | 35 +++++ src/lib_c/x86_64-solaris/c/grp.cr | 11 ++ src/lib_c/x86_64-solaris/c/iconv.cr | 9 ++ src/lib_c/x86_64-solaris/c/link.cr | 15 +++ src/lib_c/x86_64-solaris/c/netdb.cr | 42 ++++++ src/lib_c/x86_64-solaris/c/netinet/in.cr | 70 ++++++++++ src/lib_c/x86_64-solaris/c/netinet/tcp.cr | 6 + src/lib_c/x86_64-solaris/c/pthread.cr | 33 +++++ src/lib_c/x86_64-solaris/c/pwd.cr | 16 +++ src/lib_c/x86_64-solaris/c/sched.cr | 3 + src/lib_c/x86_64-solaris/c/signal.cr | 100 +++++++++++++++ src/lib_c/x86_64-solaris/c/stdarg.cr | 10 ++ src/lib_c/x86_64-solaris/c/stddef.cr | 3 + src/lib_c/x86_64-solaris/c/stdint.cr | 10 ++ src/lib_c/x86_64-solaris/c/stdio.cr | 9 ++ src/lib_c/x86_64-solaris/c/stdlib.cr | 28 ++++ src/lib_c/x86_64-solaris/c/string.cr | 9 ++ src/lib_c/x86_64-solaris/c/sys/file.cr | 11 ++ src/lib_c/x86_64-solaris/c/sys/mman.cr | 32 +++++ src/lib_c/x86_64-solaris/c/sys/resource.cr | 25 ++++ src/lib_c/x86_64-solaris/c/sys/select.cr | 12 ++ src/lib_c/x86_64-solaris/c/sys/socket.cr | 67 ++++++++++ src/lib_c/x86_64-solaris/c/sys/stat.cr | 59 +++++++++ src/lib_c/x86_64-solaris/c/sys/time.cr | 17 +++ src/lib_c/x86_64-solaris/c/sys/types.cr | 53 ++++++++ src/lib_c/x86_64-solaris/c/sys/un.cr | 8 ++ src/lib_c/x86_64-solaris/c/sys/wait.cr | 8 ++ src/lib_c/x86_64-solaris/c/termios.cr | 141 +++++++++++++++++++++ src/lib_c/x86_64-solaris/c/time.cr | 35 +++++ src/lib_c/x86_64-solaris/c/unistd.cr | 51 ++++++++ src/llvm.cr | 3 + src/math/libm.cr | 2 +- src/socket/address.cr | 4 + 57 files changed, 1221 insertions(+), 57 deletions(-) create mode 100644 src/lib_c/x86_64-solaris/c/arpa/inet.cr create mode 100644 src/lib_c/x86_64-solaris/c/dirent.cr create mode 100644 src/lib_c/x86_64-solaris/c/dlfcn.cr create mode 100644 src/lib_c/x86_64-solaris/c/elf.cr create mode 100644 src/lib_c/x86_64-solaris/c/errno.cr create mode 100644 src/lib_c/x86_64-solaris/c/fcntl.cr create mode 100644 src/lib_c/x86_64-solaris/c/grp.cr create mode 100644 src/lib_c/x86_64-solaris/c/iconv.cr create mode 100644 src/lib_c/x86_64-solaris/c/link.cr create mode 100644 src/lib_c/x86_64-solaris/c/netdb.cr create mode 100644 src/lib_c/x86_64-solaris/c/netinet/in.cr create mode 100644 src/lib_c/x86_64-solaris/c/netinet/tcp.cr create mode 100644 src/lib_c/x86_64-solaris/c/pthread.cr create mode 100644 src/lib_c/x86_64-solaris/c/pwd.cr create mode 100644 src/lib_c/x86_64-solaris/c/sched.cr create mode 100644 src/lib_c/x86_64-solaris/c/signal.cr create mode 100644 src/lib_c/x86_64-solaris/c/stdarg.cr create mode 100644 src/lib_c/x86_64-solaris/c/stddef.cr create mode 100644 src/lib_c/x86_64-solaris/c/stdint.cr create mode 100644 src/lib_c/x86_64-solaris/c/stdio.cr create mode 100644 src/lib_c/x86_64-solaris/c/stdlib.cr create mode 100644 src/lib_c/x86_64-solaris/c/string.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/file.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/mman.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/resource.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/select.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/socket.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/stat.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/time.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/types.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/un.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/wait.cr create mode 100644 src/lib_c/x86_64-solaris/c/termios.cr create mode 100644 src/lib_c/x86_64-solaris/c/time.cr create mode 100644 src/lib_c/x86_64-solaris/c/unistd.cr diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 6f52859e3932..8deffd149dbd 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -22,6 +22,7 @@ # x86_64-linux-musl # x86_64-netbsd # x86_64-openbsd +# x86_64-solaris # x86_64-windows-msvc # ``` # @@ -58,6 +59,7 @@ jobs: - x86_64-freebsd - x86_64-netbsd - x86_64-openbsd + - x86_64-solaris steps: - name: Download Crystal source diff --git a/spec/std/exception/call_stack_spec.cr b/spec/std/exception/call_stack_spec.cr index 8ecba204eba4..c01fb0ff6b8a 100644 --- a/spec/std/exception/call_stack_spec.cr +++ b/spec/std/exception/call_stack_spec.cr @@ -64,10 +64,12 @@ describe "Backtrace" do error.to_s.should contain("Invalid memory access") end - {% unless flag?(:win32) %} - # In windows, the current working directory of the process cannot be - # removed. So we probably don't need to test that. - # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/rmdir-wrmdir?view=msvc-170#remarks + # Do not test this on platforms that cannot remove the current working + # directory of the process: + # + # Solaris: https://man.freebsd.org/cgi/man.cgi?query=rmdir&sektion=2&manpath=SunOS+5.10 + # Windows: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/rmdir-wrmdir?view=msvc-170#remarks + {% unless flag?(:win32) || flag?(:solaris) %} it "print exception with non-existing PWD", tags: %w[slow] do source_file = datapath("blank_test_file.txt") compile_file(source_file) do |executable_file| diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 2c53df75205e..048482777270 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -449,9 +449,9 @@ describe IO do str.read_fully?(slice).should be_nil end - # pipe(2) returns bidirectional file descriptors on FreeBSD, + # pipe(2) returns bidirectional file descriptors on FreeBSD and Solaris, # gate this test behind the platform flag. - {% unless flag?(:freebsd) %} + {% unless flag?(:freebsd) || flag?(:solaris) %} it "raises if trying to read to an IO not opened for reading" do IO.pipe do |r, w| expect_raises(IO::Error, "File not open for reading") do @@ -574,9 +574,9 @@ describe IO do io.read_byte.should be_nil end - # pipe(2) returns bidirectional file descriptors on FreeBSD, + # pipe(2) returns bidirectional file descriptors on FreeBSD and Solaris, # gate this test behind the platform flag. - {% unless flag?(:freebsd) %} + {% unless flag?(:freebsd) || flag?(:solaris) %} it "raises if trying to write to an IO not opened for writing" do IO.pipe do |r, w| # unless sync is used the flush on close triggers the exception again diff --git a/spec/std/llvm/llvm_spec.cr b/spec/std/llvm/llvm_spec.cr index f562a36c70bc..08edeaa5219c 100644 --- a/spec/std/llvm/llvm_spec.cr +++ b/spec/std/llvm/llvm_spec.cr @@ -30,6 +30,8 @@ describe LLVM do triple.should match(/-dragonfly/) {% elsif flag?(:netbsd) %} triple.should match(/-netbsd/) + {% elsif flag?(:solaris) %} + triple.should match(/-solaris$/) {% elsif flag?(:wasi) %} triple.should match(/-wasi/) {% else %} diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index f303cdb5862f..db3a058a7c44 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -9,14 +9,7 @@ private def exit_code_command(code) {% if flag?(:win32) %} {"cmd.exe", {"/c", "exit #{code}"}} {% else %} - case code - when 0 - {"true", [] of String} - when 1 - {"false", [] of String} - else - {"/bin/sh", {"-c", "exit #{code}"}} - end + {"/bin/sh", {"-c", "exit #{code}"}} {% end %} end @@ -470,13 +463,13 @@ describe Process do begin Process.chroot(".") puts "FAIL" - rescue ex - puts ex.inspect + rescue ex : RuntimeError + puts ex.os_error end CRYSTAL status.success?.should be_true - output.should eq("#\n") + output.should eq("EPERM\n") end {% end %} end diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr index 5743697a0c5f..bb8fd03dad5f 100644 --- a/spec/std/socket/tcp_server_spec.cr +++ b/spec/std/socket/tcp_server_spec.cr @@ -126,7 +126,7 @@ describe TCPServer, tags: "network" do end end - {% if flag?(:linux) %} + {% if flag?(:linux) || flag?(:solaris) %} pending "settings" {% else %} it "settings" do diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr index f767e2ee26db..113a4ea3cf61 100644 --- a/spec/std/socket/udp_socket_spec.cr +++ b/spec/std/socket/udp_socket_spec.cr @@ -78,6 +78,10 @@ describe UDPSocket, tags: "network" do # Darwin also has a bug that prevents selecting the "default" interface. # https://lists.apple.com/archives/darwin-kernel/2014/Mar/msg00012.html pending "joins and transmits to multicast groups" + elsif {{ flag?(:solaris) }} && family == Socket::Family::INET + # TODO: figure out why updating `multicast_loopback` produces a + # `setsockopt 18: Invalid argument` error + pending "joins and transmits to multicast groups" else it "joins and transmits to multicast groups" do udp = UDPSocket.new(family) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index ebebcd2c6561..39d4136972a9 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2785,7 +2785,7 @@ describe "String" do bytes.to_a.should eq([72, 0, 101, 0, 108, 0, 108, 0, 111, 0]) end - {% unless flag?(:musl) || flag?(:freebsd) || flag?(:dragonfly) %} + {% unless flag?(:musl) || flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) %} it "flushes the shift state (#11992)" do "\u{00CA}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x66]) "\u{00CA}\u{0304}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x62]) @@ -2838,7 +2838,7 @@ describe "String" do String.new(bytes, "UTF-16LE").should eq("Hello") end - {% unless flag?(:freebsd) || flag?(:dragonfly) %} + {% unless flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) %} it "decodes with shift state" do String.new(Bytes[0x88, 0x66], "BIG5-HKSCS").should eq("\u{00CA}") String.new(Bytes[0x88, 0x62], "BIG5-HKSCS").should eq("\u{00CA}\u{0304}") diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 7d8ddf065c21..5b7c9b224c83 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -103,8 +103,8 @@ class Crystal::CodeGenVisitor if @frame_pointers.always? context.fun.add_attribute "frame-pointer", value: "all" - elsif @program.has_flag?("darwin") - # Disable frame pointer elimination in Darwin, as it causes issues during stack unwind + elsif @program.has_flag?("darwin") || @program.has_flag?("solaris") + # Disable frame pointer elimination, as it causes issues during stack unwind context.fun.add_target_dependent_attribute "frame-pointer", "all" elsif @frame_pointers.non_leaf? context.fun.add_attribute "frame-pointer", value: "non-leaf" diff --git a/src/compiler/crystal/codegen/target.cr b/src/compiler/crystal/codegen/target.cr index 8817221c18b9..61af0d532866 100644 --- a/src/compiler/crystal/codegen/target.cr +++ b/src/compiler/crystal/codegen/target.cr @@ -70,6 +70,8 @@ class Crystal::Codegen::Target "openbsd" when .netbsd? "netbsd" + when .solaris? + "solaris" when .android? "android" else @@ -128,6 +130,10 @@ class Crystal::Codegen::Target @environment.starts_with?("linux") end + def solaris? + @environment.starts_with?("solaris") + end + def wasi? @environment.starts_with?("wasi") end @@ -137,7 +143,7 @@ class Crystal::Codegen::Target end def unix? - macos? || bsd? || linux? || wasi? + macos? || bsd? || linux? || wasi? || solaris? end def gnu? diff --git a/src/compiler/crystal/semantic/flags.cr b/src/compiler/crystal/semantic/flags.cr index adfc51310233..cf15e7cecc10 100644 --- a/src/compiler/crystal/semantic/flags.cr +++ b/src/compiler/crystal/semantic/flags.cr @@ -47,6 +47,7 @@ class Crystal::Program flags.add "netbsd" if target.netbsd? flags.add "openbsd" if target.openbsd? flags.add "dragonfly" if target.dragonfly? + flags.add "solaris" if target.solaris? flags.add "android" if target.android? flags.add "bsd" if target.bsd? diff --git a/src/crystal/dwarf/strings.cr b/src/crystal/dwarf/strings.cr index f352262ad047..764815b5fdc8 100644 --- a/src/crystal/dwarf/strings.cr +++ b/src/crystal/dwarf/strings.cr @@ -4,7 +4,7 @@ module Crystal def initialize(@io : IO::FileDescriptor, @offset : UInt32 | UInt64, size) # Read a good chunk of bytes to decode strings faster # (avoid seeking/reading the IO too many times) - @buffer = Bytes.new(Math.max(16384, size)) + @buffer = Bytes.new(Math.min(Math.max(16384, size), io.info.size - offset)) pos = @io.pos @io.read_fully(@buffer) @io.pos = pos diff --git a/src/crystal/iconv.cr b/src/crystal/iconv.cr index 593c492d4ce3..59fd9519c983 100644 --- a/src/crystal/iconv.cr +++ b/src/crystal/iconv.cr @@ -22,7 +22,7 @@ struct Crystal::Iconv original_from, original_to = from, to @skip_invalid = invalid == :skip - {% unless flag?(:freebsd) || flag?(:musl) || flag?(:dragonfly) || flag?(:netbsd) %} + {% unless flag?(:freebsd) || flag?(:musl) || flag?(:dragonfly) || flag?(:netbsd) || flag?(:solaris) %} if @skip_invalid from = "#{from}//IGNORE" to = "#{to}//IGNORE" diff --git a/src/crystal/system/unix/dir.cr b/src/crystal/system/unix/dir.cr index ddaeb34f9eea..5e66b33b65e7 100644 --- a/src/crystal/system/unix/dir.cr +++ b/src/crystal/system/unix/dir.cr @@ -14,11 +14,17 @@ module Crystal::System::Dir if entry = LibC.readdir(dir) name = String.new(entry.value.d_name.to_unsafe) - dir = case entry.value.d_type - when LibC::DT_DIR then true - when LibC::DT_UNKNOWN, LibC::DT_LNK then nil - else false - end + dir = + {% if flag?(:solaris) %} + # `d_type` is a Linux / BSD extension + nil + {% else %} + case entry.value.d_type + when LibC::DT_DIR then true + when LibC::DT_UNKNOWN, LibC::DT_LNK then nil + else false + end + {% end %} # TODO: support `st_flags & UF_HIDDEN` on BSD-like systems: https://man.freebsd.org/cgi/man.cgi?query=stat&sektion=2 # TODO: support hidden file attributes on macOS / HFS+: https://stackoverflow.com/a/15236292 diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index d3995c205c3e..cd4fcf75b98b 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -211,7 +211,7 @@ module Crystal::System::FileDescriptor private def system_raw(enable : Bool, & : ->) system_console_mode do |mode| if enable - FileDescriptor.cfmakeraw(pointerof(mode)) + mode = FileDescriptor.cfmakeraw(mode) else mode.c_iflag |= LibC::BRKINT | LibC::ISTRIP | LibC::ICRNL | LibC::IXON mode.c_oflag |= LibC::OPOST @@ -237,18 +237,21 @@ module Crystal::System::FileDescriptor @[AlwaysInline] def self.tcgetattr(fd) termios = uninitialized LibC::Termios - ret = {% if flag?(:android) && !LibC.has_method?(:tcgetattr) %} - LibC.ioctl(fd, LibC::TCGETS, pointerof(termios)) - {% else %} - LibC.tcgetattr(fd, pointerof(termios)) - {% end %} - raise IO::Error.from_errno("tcgetattr") if ret != 0 + {% if LibC.has_method?(:tcgetattr) %} + ret = LibC.tcgetattr(fd, pointerof(termios)) + raise IO::Error.from_errno("tcgetattr") if ret == -1 + {% else %} + ret = LibC.ioctl(fd, LibC::TCGETS, pointerof(termios)) + raise IO::Error.from_errno("ioctl") if ret == -1 + {% end %} termios end @[AlwaysInline] def self.tcsetattr(fd, optional_actions, termios_p) - {% if flag?(:android) && !LibC.has_method?(:tcsetattr) %} + {% if LibC.has_method?(:tcsetattr) %} + LibC.tcsetattr(fd, optional_actions, termios_p) + {% else %} optional_actions = optional_actions.value if optional_actions.is_a?(Termios::LineControl) cmd = case optional_actions when LibC::TCSANOW @@ -263,23 +266,22 @@ module Crystal::System::FileDescriptor end LibC.ioctl(fd, cmd, termios_p) - {% else %} - LibC.tcsetattr(fd, optional_actions, termios_p) {% end %} end @[AlwaysInline] - def self.cfmakeraw(termios_p) - {% if flag?(:android) && !LibC.has_method?(:cfmakeraw) %} - s.value.c_iflag &= ~(LibC::IGNBRK | LibC::BRKINT | LibC::PARMRK | LibC::ISTRIP | LibC::INLCR | LibC::IGNCR | LibC::ICRNL | LibC::IXON) - s.value.c_oflag &= ~LibC::OPOST - s.value.c_lflag &= ~(LibC::ECHO | LibC::ECHONL | LibC::ICANON | LibC::ISIG | LibC::IEXTEN) - s.value.c_cflag &= ~(LibC::CSIZE | LibC::PARENB) - s.value.c_cflag |= LibC::CS8 - s.value.c_cc[LibC::VMIN] = 1 - s.value.c_cc[LibC::VTIME] = 0 + def self.cfmakeraw(termios) + {% if LibC.has_method?(:cfmakeraw) %} + LibC.cfmakeraw(pointerof(termios)) {% else %} - LibC.cfmakeraw(termios_p) + termios.c_iflag &= ~(LibC::IGNBRK | LibC::BRKINT | LibC::PARMRK | LibC::ISTRIP | LibC::INLCR | LibC::IGNCR | LibC::ICRNL | LibC::IXON) + termios.c_oflag &= ~LibC::OPOST + termios.c_lflag &= ~(LibC::ECHO | LibC::ECHONL | LibC::ICANON | LibC::ISIG | LibC::IEXTEN) + termios.c_cflag &= ~(LibC::CSIZE | LibC::PARENB) + termios.c_cflag |= LibC::CS8 + termios.c_cc[LibC::VMIN] = 1 + termios.c_cc[LibC::VTIME] = 0 {% end %} + termios end end diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index 5d6e2a332adc..f96d0bbf75ba 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -80,7 +80,7 @@ module Crystal::System::Thread {% if flag?(:darwin) %} # FIXME: pthread_get_stacksize_np returns bogus value on macOS X 10.9.0: address = LibC.pthread_get_stackaddr_np(@system_handle) - LibC.pthread_get_stacksize_np(@system_handle) - {% elsif flag?(:bsd) && !flag?(:openbsd) %} + {% elsif (flag?(:bsd) && !flag?(:openbsd)) || flag?(:solaris) %} ret = LibC.pthread_attr_init(out attr) unless ret == 0 LibC.pthread_attr_destroy(pointerof(attr)) @@ -108,6 +108,8 @@ module Crystal::System::Thread else stack.ss_sp - stack.ss_size end + {% else %} + {% raise "No `Crystal::System::Thread#stack_address` implementation available" %} {% end %} address diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr index 0ac7b8816d7a..d97b7a9e3672 100644 --- a/src/crystal/system/unix/signal.cr +++ b/src/crystal/system/unix/signal.cr @@ -22,9 +22,12 @@ module Crystal::System::Signal @@mutex.synchronize do unless @@handlers[signal]? @@sigset << signal - LibC.signal(signal.value, ->(value : Int32) { + action = LibC::Sigaction.new + action.sa_sigaction = LibC::SigactionHandlerT.new do |value, _, _| writer.write_bytes(value) unless writer.closed? - }) + end + LibC.sigemptyset(pointerof(action.@sa_mask)) + LibC.sigaction(signal, pointerof(action), nil) end @@handlers[signal] = handler end diff --git a/src/errno.cr b/src/errno.cr index 9c050aa603c4..03ca6085eb8a 100644 --- a/src/errno.cr +++ b/src/errno.cr @@ -4,6 +4,8 @@ require "c/string" lib LibC {% if flag?(:netbsd) || flag?(:openbsd) || flag?(:android) %} fun __errno : Int* + {% elsif flag?(:solaris) %} + fun ___errno : Int* {% elsif flag?(:linux) || flag?(:dragonfly) %} fun __errno_location : Int* {% elsif flag?(:wasi) %} @@ -45,6 +47,8 @@ enum Errno def self.value : self {% if flag?(:netbsd) || flag?(:openbsd) || flag?(:android) %} Errno.new LibC.__errno.value + {% elsif flag?(:solaris) %} + Errno.new LibC.___errno.value {% elsif flag?(:linux) || flag?(:dragonfly) %} Errno.new LibC.__errno_location.value {% elsif flag?(:wasi) %} @@ -62,6 +66,8 @@ enum Errno def self.value=(errno : Errno) {% if flag?(:netbsd) || flag?(:openbsd) || flag?(:android) %} LibC.__errno.value = errno.value + {% elsif flag?(:solaris) %} + LibC.___errno.value = errno.value {% elsif flag?(:linux) || flag?(:dragonfly) %} LibC.__errno_location.value = errno.value {% elsif flag?(:darwin) || flag?(:freebsd) %} diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 435a1c0f9fe6..73a851a00339 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -3,7 +3,7 @@ require "c/stdio" require "c/string" require "../lib_unwind" -{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) %} +{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) || flag?(:solaris) %} require "./dwarf" {% else %} require "./null" diff --git a/src/lib_c/x86_64-solaris/c/arpa/inet.cr b/src/lib_c/x86_64-solaris/c/arpa/inet.cr new file mode 100644 index 000000000000..afac8795f66f --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/arpa/inet.cr @@ -0,0 +1,9 @@ +require "../netinet/in" +require "../stdint" + +lib LibC + fun htons(x0 : UInt16T) : UInt16T + fun ntohs(x0 : UInt16T) : UInt16T + fun inet_ntop(x0 : Int, x1 : Void*, x2 : Char*, x3 : SocklenT) : Char* + fun inet_pton(x0 : Int, x1 : Char*, x2 : Void*) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/dirent.cr b/src/lib_c/x86_64-solaris/c/dirent.cr new file mode 100644 index 000000000000..3e10e8b6c4e9 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/dirent.cr @@ -0,0 +1,23 @@ +require "./sys/types" + +lib LibC + struct DIR + d_fd : Int + d_loc : Int + d_size : Int + d_buf : Char* + end + + struct Dirent + d_ino : InoT + d_off : OffT + d_reclen : UShort + d_name : StaticArray(Char, 1) + end + + fun closedir(x0 : DIR*) : Int + fun opendir(x0 : Char*) : DIR* + fun readdir(x0 : DIR*) : Dirent* + fun rewinddir(x0 : DIR*) : Void + fun dirfd(dirp : DIR*) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/dlfcn.cr b/src/lib_c/x86_64-solaris/c/dlfcn.cr new file mode 100644 index 000000000000..3afc6fd37cbf --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/dlfcn.cr @@ -0,0 +1,21 @@ +lib LibC + RTLD_LAZY = 0x00001 + RTLD_NOW = 0x00002 + RTLD_GLOBAL = 0x00100 + RTLD_LOCAL = 0x00000 + RTLD_DEFAULT = Pointer(Void).new(-2) + RTLD_NEXT = Pointer(Void).new(-1) + + struct DlInfo + dli_fname : Char* + dli_fbase : Void* + dli_sname : Char* + dli_saddr : Void* + end + + fun dlclose(x0 : Void*) : Int + fun dlerror : Char* + fun dlopen(x0 : Char*, x1 : Int) : Void* + fun dlsym(x0 : Void*, x1 : Char*) : Void* + fun dladdr(x0 : Void*, x1 : DlInfo*) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/elf.cr b/src/lib_c/x86_64-solaris/c/elf.cr new file mode 100644 index 000000000000..ad53d9345c62 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/elf.cr @@ -0,0 +1,23 @@ +require "./sys/types" + +lib LibC + alias Elf_Addr = ULong + alias Elf_Half = UShort + alias Elf_Off = ULong + alias Elf_Sword = Int + alias Elf_Sxword = Long + alias Elf_Word = UInt + alias Elf_Xword = ULong + alias Elf_Versym = Elf_Half + + struct Elf_Phdr + type : Elf_Word # Segment type + flags : Elf_Word # Segment flags + offset : Elf_Off # Segment file offset + vaddr : Elf_Addr # Segment virtual address + paddr : Elf_Addr # Segment physical address + filesz : Elf_Xword # Segment size in file + memsz : Elf_Xword # Segment size in memory + align : Elf_Xword # Segment alignment + end +end diff --git a/src/lib_c/x86_64-solaris/c/errno.cr b/src/lib_c/x86_64-solaris/c/errno.cr new file mode 100644 index 000000000000..32e239c7d1a8 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/errno.cr @@ -0,0 +1,124 @@ +lib LibC + E2BIG = 7 + EACCES = 13 + EADDRINUSE = 125 + EADDRNOTAVAIL = 126 + EADV = 68 + EAFNOSUPPORT = 124 + EAGAIN = 11 + EALREADY = 149 + EBADE = 50 + EBADF = 9 + EBADFD = 81 + EBADMSG = 77 + EBADR = 51 + EBADRQC = 54 + EBADSLT = 55 + EBFONT = 57 + EBUSY = 16 + ECANCELED = 47 + ECHILD = 10 + ECHRNG = 37 + ECOMM = 70 + ECONNABORTED = 130 + ECONNREFUSED = 146 + ECONNRESET = 131 + EDEADLK = 45 + EDEADLOCK = 56 + EDESTADDRREQ = 96 + EDOM = 33 + EDQUOT = 49 + EEXIST = 17 + EFAULT = 14 + EFBIG = 27 + EHOSTDOWN = 147 + EHOSTUNREACH = 148 + EIDRM = 36 + EILSEQ = 88 + EINPROGRESS = 150 + EINTR = 4 + EINVAL = 22 + EIO = 5 + EISCONN = 133 + EISDIR = 21 + EL2HLT = 44 + EL2NSYNC = 38 + EL3HLT = 39 + EL3RST = 40 + ELIBACC = 83 + ELIBBAD = 84 + ELIBEXEC = 87 + ELIBMAX = 86 + ELIBSCN = 85 + ELNRNG = 41 + ELOCKUNMAPPED = 72 + ELOOP = 90 + EMFILE = 24 + EMLINK = 31 + EMSGSIZE = 97 + EMULTIHOP = 74 + ENAMETOOLONG = 78 + ENETDOWN = 127 + ENETRESET = 129 + ENETUNREACH = 128 + ENFILE = 23 + ENOANO = 53 + ENOBUFS = 132 + ENOCSI = 43 + ENODATA = 61 + ENODEV = 19 + ENOENT = 2 + ENOEXEC = 8 + ENOLCK = 46 + ENOLINK = 67 + ENOMEM = 12 + ENOMSG = 35 + ENONET = 64 + ENOPKG = 65 + ENOPROTOOPT = 99 + ENOSPC = 28 + ENOSR = 63 + ENOSTR = 60 + ENOSYS = 89 + ENOTACTIVE = 73 + ENOTBLK = 15 + ENOTCONN = 134 + ENOTDIR = 20 + ENOTEMPTY = 93 + ENOTRECOVERABLE = 59 + ENOTSOCK = 95 + ENOTSUP = 48 + ENOTTY = 25 + ENOTUNIQ = 80 + ENXIO = 6 + EOPNOTSUPP = 122 + EOVERFLOW = 79 + EOWNERDEAD = 58 + EPERM = 1 + EPFNOSUPPORT = 123 + EPIPE = 32 + EPROTO = 71 + EPROTONOSUPPORT = 120 + EPROTOTYPE = 98 + ERANGE = 34 + EREMCHG = 82 + EREMOTE = 66 + ERESTART = 91 + EROFS = 30 + ESHUTDOWN = 143 + ESOCKTNOSUPPORT = 121 + ESPIPE = 29 + ESRCH = 3 + ESRMNT = 69 + ESTALE = 151 + ESTRPIPE = 92 + ETIME = 62 + ETIMEDOUT = 145 + ETOOMANYREFS = 144 + ETXTBSY = 26 + EUNATCH = 42 + EUSERS = 94 + EWOULDBLOCK = LibC::EAGAIN + EXDEV = 18 + EXFULL = 52 +end diff --git a/src/lib_c/x86_64-solaris/c/fcntl.cr b/src/lib_c/x86_64-solaris/c/fcntl.cr new file mode 100644 index 000000000000..6bb34dbdd169 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/fcntl.cr @@ -0,0 +1,35 @@ +require "./sys/types" +require "./sys/stat" +require "./unistd" + +lib LibC + F_GETFD = 1 + F_SETFD = 2 + F_GETFL = 3 + F_SETFL = 4 + FD_CLOEXEC = 1 + O_CLOEXEC = 0x800000 + O_CREAT = 0x100 + O_NOFOLLOW = 0x20000 + O_TRUNC = 0x200 + O_EXCL = 0x400 + O_APPEND = 0x08 + O_NONBLOCK = 0x80 + O_SYNC = 0x10 + O_RDONLY = 0 + O_RDWR = 2 + O_WRONLY = 1 + + struct Flock + l_type : Short + l_whence : Short + l_start : OffT + l_len : OffT + l_sysid : Int + l_pid : PidT + l_pad : Long[4] + end + + fun fcntl(x0 : Int, x1 : Int, ...) : Int + fun open(x0 : Char*, x1 : Int, ...) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/grp.cr b/src/lib_c/x86_64-solaris/c/grp.cr new file mode 100644 index 000000000000..30eb6a36b14e --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/grp.cr @@ -0,0 +1,11 @@ +lib LibC + struct Group + gr_name : Char* + gr_passwd : Char* + gr_gid : GidT + gr_mem : Char** + end + + fun getgrnam_r = __posix_getgrnam_r(name : Char*, grp : Group*, buf : Char*, bufsize : SizeT, result : Group**) : Int + fun getgrgid_r = __posix_getgrgid_r(gid : GidT, grp : Group*, buf : Char*, bufsize : SizeT, result : Group**) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/iconv.cr b/src/lib_c/x86_64-solaris/c/iconv.cr new file mode 100644 index 000000000000..6814db786946 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/iconv.cr @@ -0,0 +1,9 @@ +require "./stddef" + +lib LibC + type IconvT = Void* + + fun iconv(cd : IconvT, inbuf : Char**, inbytesleft : SizeT*, outbuf : Char**, outbytesleft : SizeT*) : SizeT + fun iconv_close(cd : IconvT) : Int + fun iconv_open(tocode : Char*, fromcode : Char*) : IconvT +end diff --git a/src/lib_c/x86_64-solaris/c/link.cr b/src/lib_c/x86_64-solaris/c/link.cr new file mode 100644 index 000000000000..9f724b5e31dd --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/link.cr @@ -0,0 +1,15 @@ +require "./elf" + +lib LibC + struct DlPhdrInfo + addr : Elf_Addr + name : Char* + phdr : Elf_Phdr* + phnum : Elf_Half + adds : ULongLong + subs : ULongLong + end + + alias DlPhdrCallback = (DlPhdrInfo*, LibC::SizeT, Void*) -> LibC::Int + fun dl_iterate_phdr(callback : DlPhdrCallback, data : Void*) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/netdb.cr b/src/lib_c/x86_64-solaris/c/netdb.cr new file mode 100644 index 000000000000..74d2569b7578 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/netdb.cr @@ -0,0 +1,42 @@ +require "./netinet/in" +require "./sys/socket" +require "./stdint" + +lib LibC + AI_PASSIVE = 0x0008 + AI_CANONNAME = 0x0010 + AI_NUMERICHOST = 0x0020 + AI_NUMERICSERV = 0x0040 + AI_V4MAPPED = 0x0001 + AI_ALL = 0x0002 + AI_ADDRCONFIG = 0x0004 + EAI_ADDRFAMILY = 1 + EAI_AGAIN = 2 + EAI_BADFLAGS = 3 + EAI_FAIL = 4 + EAI_FAMILY = 5 + EAI_MEMORY = 6 + EAI_NODATA = 7 + EAI_NONAME = 8 + EAI_SERVICE = 9 + EAI_SOCKTYPE = 10 + EAI_SYSTEM = 11 + EAI_OVERFLOW = 12 + EAI_PROTOCOL = 13 + + struct Addrinfo + ai_flags : Int + ai_family : Int + ai_socktype : Int + ai_protocol : Int + ai_addrlen : SocklenT + ai_canonname : Char* + ai_addr : Sockaddr* + ai_next : Addrinfo* + end + + fun freeaddrinfo(x0 : Addrinfo*) : Void + fun gai_strerror(x0 : Int) : Char* + fun getaddrinfo(x0 : Char*, x1 : Char*, x2 : Addrinfo*, x3 : Addrinfo**) : Int + fun getnameinfo(x0 : Sockaddr*, x1 : SocklenT, x2 : Char*, x3 : SocklenT, x4 : Char*, x5 : SocklenT, x6 : Int) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/netinet/in.cr b/src/lib_c/x86_64-solaris/c/netinet/in.cr new file mode 100644 index 000000000000..9de900f3e5a9 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/netinet/in.cr @@ -0,0 +1,70 @@ +require "../sys/socket" +require "../stdint" + +lib LibC + IPPROTO_IP = 0 + IPPROTO_IPV6 = 41 + IPPROTO_ICMP = 1 + IPPROTO_RAW = 255 + IPPROTO_TCP = 6 + IPPROTO_UDP = 17 + + alias InPortT = UInt16 + alias InAddrT = UInt32 + + struct InAddr + s_addr : InAddrT # actually a union similar to `In6AddrS6Un` + end + + union In6AddrS6Un + _S6_u8 : UInt8[16] + _S6_u16 : UInt16[8] + _S6_u32 : UInt32[4] + __S6_align : UInt32 + end + + struct In6Addr + _S6_un : In6AddrS6Un + end + + struct SockaddrIn + sin_family : SaFamilyT + sin_port : InPortT + sin_addr : InAddr + sin_zero : Char[8] + end + + struct SockaddrIn6 + sin6_family : SaFamilyT + sin6_port : InPortT + sin6_flowinfo : UInt32 + sin6_addr : In6Addr + sin6_scope_id : UInt32 + __sin6_src_id : UInt32 + end + + IP_MULTICAST_IF = 0x10 + IPV6_MULTICAST_IF = 0x6 + + IP_MULTICAST_TTL = 0x11 + IPV6_MULTICAST_HOPS = 0x7 + + IP_MULTICAST_LOOP = 0x12 + IPV6_MULTICAST_LOOP = 0x8 + + IP_ADD_MEMBERSHIP = 0x13 + IPV6_JOIN_GROUP = 0x9 + + IP_DROP_MEMBERSHIP = 0x14 + IPV6_LEAVE_GROUP = 0xa + + struct IpMreq + imr_multiaddr : InAddr + imr_interface : InAddr + end + + struct Ipv6Mreq + ipv6mr_multiaddr : In6Addr + ipv6mr_interface : UInt + end +end diff --git a/src/lib_c/x86_64-solaris/c/netinet/tcp.cr b/src/lib_c/x86_64-solaris/c/netinet/tcp.cr new file mode 100644 index 000000000000..750b612fb92e --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/netinet/tcp.cr @@ -0,0 +1,6 @@ +lib LibC + TCP_NODELAY = 0x01 + TCP_KEEPIDLE = 0x22 + TCP_KEEPINTVL = 0x24 + TCP_KEEPCNT = 0x23 +end diff --git a/src/lib_c/x86_64-solaris/c/pthread.cr b/src/lib_c/x86_64-solaris/c/pthread.cr new file mode 100644 index 000000000000..538d68263739 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/pthread.cr @@ -0,0 +1,33 @@ +require "./sys/types" + +@[Link("pthread")] +lib LibC + PTHREAD_MUTEX_ERRORCHECK = 0x2 + + fun pthread_attr_destroy(x0 : PthreadAttrT*) : Int + fun pthread_attr_get_np(x0 : PthreadT, x1 : PthreadAttrT*) : Int + fun pthread_attr_getstack(x0 : PthreadAttrT*, x1 : Void**, x2 : SizeT*) : Int + fun pthread_attr_init(x0 : PthreadAttrT*) : Int + fun pthread_condattr_destroy(x0 : PthreadCondattrT*) : Int + fun pthread_condattr_init(x0 : PthreadCondattrT*) : Int + fun pthread_condattr_setclock(x0 : PthreadCondattrT*, x1 : ClockidT) : Int + fun pthread_cond_broadcast(x0 : PthreadCondT*) : Int + fun pthread_cond_destroy(x0 : PthreadCondT*) : Int + fun pthread_cond_init(x0 : PthreadCondT*, x1 : PthreadCondattrT*) : Int + fun pthread_cond_signal(x0 : PthreadCondT*) : Int + fun pthread_cond_timedwait(x0 : PthreadCondT*, x1 : PthreadMutexT*, x2 : Timespec*) : Int + fun pthread_cond_wait(x0 : PthreadCondT*, x1 : PthreadMutexT*) : Int + fun pthread_create(x0 : PthreadT*, x1 : PthreadAttrT*, x2 : Void* -> Void*, x3 : Void*) : Int + fun pthread_detach(x0 : PthreadT) : Int + fun pthread_join(x0 : PthreadT, x1 : Void**) : Int + fun pthread_mutexattr_destroy(x0 : PthreadMutexattrT*) : Int + fun pthread_mutexattr_init(x0 : PthreadMutexattrT*) : Int + fun pthread_mutexattr_settype(x0 : PthreadMutexattrT*, x1 : Int) : Int + fun pthread_mutex_destroy(x0 : PthreadMutexT*) : Int + fun pthread_mutex_init(x0 : PthreadMutexT*, x1 : PthreadMutexattrT*) : Int + fun pthread_mutex_lock(x0 : PthreadMutexT*) : Int + fun pthread_mutex_trylock(x0 : PthreadMutexT*) : Int + fun pthread_mutex_unlock(x0 : PthreadMutexT*) : Int + fun pthread_self : PthreadT + fun pthread_setname_np(PthreadT, Char*) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/pwd.cr b/src/lib_c/x86_64-solaris/c/pwd.cr new file mode 100644 index 000000000000..119f7115fdfd --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/pwd.cr @@ -0,0 +1,16 @@ +lib LibC + struct Passwd + pw_name : Char* + pw_passwd : Char* + pw_uid : UidT + pw_gid : GidT + pw_age : Char* + pw_comment : Char* + pw_gecos : Char* + pw_dir : Char* + pw_shell : Char* + end + + fun getpwnam_r = __posix_getpwnam_r(login : Char*, pwstore : Passwd*, buf : Char*, bufsize : SizeT, result : Passwd**) : Int + fun getpwuid_r = __posix_getpwuid_r(uid : UidT, pwstore : Passwd*, buf : Char*, bufsize : SizeT, result : Passwd**) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/sched.cr b/src/lib_c/x86_64-solaris/c/sched.cr new file mode 100644 index 000000000000..9d83ed18504e --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sched.cr @@ -0,0 +1,3 @@ +lib LibC + fun sched_yield : Int +end diff --git a/src/lib_c/x86_64-solaris/c/signal.cr b/src/lib_c/x86_64-solaris/c/signal.cr new file mode 100644 index 000000000000..b209436e0987 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/signal.cr @@ -0,0 +1,100 @@ +require "./sys/types" +require "./time" + +lib LibC + SIGHUP = 1 + SIGINT = 2 + SIGQUIT = 3 + SIGILL = 4 + SIGTRAP = 5 + SIGIOT = 6 + SIGABRT = 6 + SIGEMT = 7 + SIGFPE = 8 + SIGKILL = 9 + SIGBUS = 10 + SIGSEGV = 11 + SIGSYS = 12 + SIGPIPE = 13 + SIGALRM = 14 + SIGTERM = 15 + SIGUSR1 = 16 + SIGUSR2 = 17 + SIGCLD = 18 + SIGCHLD = 18 + SIGPWR = 19 + SIGWINCH = 20 + SIGURG = 21 + SIGPOLL = 22 + SIGIO = LibC::SIGPOLL + SIGSTOP = 23 + SIGTSTP = 24 + SIGCONT = 25 + SIGTTIN = 26 + SIGTTOU = 27 + SIGVTALRM = 28 + SIGPROF = 29 + SIGXCPU = 30 + SIGXFSZ = 31 + SIGWAITING = 32 + SIGLWP = 33 + SIGFREEZE = 34 + SIGTHAW = 35 + SIGCANCEL = 36 + SIGLOST = 37 + SIGXRES = 38 + SIGJVM1 = 39 + SIGJVM2 = 40 + SIGINFO = 41 + + SIGSTKSZ = 8192 + + SIG_SETMASK = 3 + + alias SighandlerT = Int -> + SIG_DFL = SighandlerT.new(Pointer(Void).new(0_u64), Pointer(Void).null) + SIG_IGN = SighandlerT.new(Pointer(Void).new(1_u64), Pointer(Void).null) + + struct SigsetT + bits : UInt[4] + end + + SA_ONSTACK = 0x00000001 + SA_SIGINFO = 0x00000008 + + struct SiginfoT + si_signo : Int + si_code : Int + si_errno : Int + si_pad : Int + si_addr : Void* + __pad : Int[58] # SI_MAXSZ (256) / sizeof(int) - ... + end + + alias SigactionHandlerT = (Int, SiginfoT*, Void*) -> + + struct Sigaction + sa_flags : Int + # Technically a union, but only one can be valid and we only use sa_sigaction + # and not sa_handler (which would be a SighandlerT) + sa_sigaction : SigactionHandlerT + sa_mask : SigsetT + end + + struct StackT + ss_sp : Void* + ss_size : SizeT + ss_flags : Int + end + + fun kill(x0 : PidT, x1 : Int) : Int + fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void + fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int + fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int + fun sigemptyset(SigsetT*) : Int + fun sigfillset(SigsetT*) : Int + fun sigaddset(SigsetT*, Int) : Int + fun sigdelset(SigsetT*, Int) : Int + fun sigismember(SigsetT*, Int) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/stdarg.cr b/src/lib_c/x86_64-solaris/c/stdarg.cr new file mode 100644 index 000000000000..fcad7714f16a --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/stdarg.cr @@ -0,0 +1,10 @@ +lib LibC + struct VaListTag + gp_offset : UInt + fp_offset : UInt + overflow_arg_area : Void* + reg_save_area : Void* + end + + type VaList = VaListTag[1] +end diff --git a/src/lib_c/x86_64-solaris/c/stddef.cr b/src/lib_c/x86_64-solaris/c/stddef.cr new file mode 100644 index 000000000000..4afcdf34d723 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/stddef.cr @@ -0,0 +1,3 @@ +lib LibC + alias SizeT = ULong +end diff --git a/src/lib_c/x86_64-solaris/c/stdint.cr b/src/lib_c/x86_64-solaris/c/stdint.cr new file mode 100644 index 000000000000..f1aae04d919e --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/stdint.cr @@ -0,0 +1,10 @@ +lib LibC + alias Int8T = SChar + alias Int16T = Short + alias Int32T = Int + alias Int64T = Long + alias UInt8T = Char + alias UInt16T = UShort + alias UInt32T = UInt + alias UInt64T = ULong +end diff --git a/src/lib_c/x86_64-solaris/c/stdio.cr b/src/lib_c/x86_64-solaris/c/stdio.cr new file mode 100644 index 000000000000..44fb5b8a1e74 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/stdio.cr @@ -0,0 +1,9 @@ +require "./sys/types" +require "./stddef" + +lib LibC + fun printf(x0 : Char*, ...) : Int + fun dprintf(fd : Int, format : Char*, ...) : Int + fun rename(x0 : Char*, x1 : Char*) : Int + fun snprintf(x0 : Char*, x1 : SizeT, x2 : Char*, ...) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/stdlib.cr b/src/lib_c/x86_64-solaris/c/stdlib.cr new file mode 100644 index 000000000000..81dbfd85291b --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/stdlib.cr @@ -0,0 +1,28 @@ +require "./stddef" +require "./sys/wait" + +lib LibC + struct DivT + quot : Int + rem : Int + end + + fun arc4random : UInt32 + fun arc4random_buf(x0 : Void*, x1 : SizeT) : Void + fun arc4random_uniform(x0 : UInt32T) : UInt32T + fun atof(x0 : Char*) : Double + fun div(x0 : Int, x1 : Int) : DivT + fun exit(x0 : Int) : NoReturn + fun free(x0 : Void*) : Void + fun getenv(x0 : Char*) : Char* + fun malloc(x0 : SizeT) : Void* + fun mkstemp(x0 : Char*) : Int + fun mkstemps(x0 : Char*, x1 : Int) : Int + fun putenv(x0 : Char*) : Int + fun realloc(x0 : Void*, x1 : SizeT) : Void* + fun realpath(x0 : Char*, x1 : Char*) : Char* + fun setenv(x0 : Char*, x1 : Char*, x2 : Int) : Int + fun strtof(x0 : Char*, x1 : Char**) : Float + fun strtod(x0 : Char*, x1 : Char**) : Double + fun unsetenv(x0 : Char*) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/string.cr b/src/lib_c/x86_64-solaris/c/string.cr new file mode 100644 index 000000000000..02e025ae4880 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/string.cr @@ -0,0 +1,9 @@ +require "./stddef" + +lib LibC + fun memchr(x0 : Void*, c : Int, n : SizeT) : Void* + fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int + fun strcmp(x0 : Char*, x1 : Char*) : Int + fun strerror(x0 : Int) : Char* + fun strlen(x0 : Char*) : SizeT +end diff --git a/src/lib_c/x86_64-solaris/c/sys/file.cr b/src/lib_c/x86_64-solaris/c/sys/file.cr new file mode 100644 index 000000000000..e2bd3fd4b816 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/file.cr @@ -0,0 +1,11 @@ +lib LibC + @[Flags] + enum FlockOp + SH = 0x1 + EX = 0x2 + NB = 0x4 + UN = 0x8 + end + + fun flock(fd : Int, op : FlockOp) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/sys/mman.cr b/src/lib_c/x86_64-solaris/c/sys/mman.cr new file mode 100644 index 000000000000..55f912792fb8 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/mman.cr @@ -0,0 +1,32 @@ +require "./types" + +lib LibC + PROT_EXEC = 0x4 + PROT_NONE = 0x0 + PROT_READ = 0x1 + PROT_WRITE = 0x2 + + MAP_FIXED = 0x10 + MAP_PRIVATE = 2 + MAP_SHARED = 1 + MAP_ANON = 0x100 + MAP_ANONYMOUS = LibC::MAP_ANON + + MAP_FAILED = Pointer(Void).new(-1) + + POSIX_MADV_DONTNEED = 4 + POSIX_MADV_NORMAL = 0 + POSIX_MADV_RANDOM = 1 + POSIX_MADV_SEQUENTIAL = 2 + POSIX_MADV_WILLNEED = 3 + MADV_DONTNEED = 4 + MADV_NORMAL = 0 + MADV_RANDOM = 1 + MADV_SEQUENTIAL = 2 + MADV_WILLNEED = 3 + + fun mmap(x0 : Void*, x1 : SizeT, x2 : Int, x3 : Int, x4 : Int, x5 : OffT) : Void* + fun mprotect(x0 : Void*, x1 : SizeT, x2 : Int) : Int + fun munmap(x0 : Void*, x1 : SizeT) : Int + fun madvise(x0 : Void*, x1 : SizeT, x2 : Int) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/sys/resource.cr b/src/lib_c/x86_64-solaris/c/sys/resource.cr new file mode 100644 index 000000000000..d52182f69bce --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/resource.cr @@ -0,0 +1,25 @@ +lib LibC + struct RUsage + ru_utime : Timeval + ru_stime : Timeval + ru_maxrss : Long + ru_ixrss : Long + ru_idrss : Long + ru_isrss : Long + ru_minflt : Long + ru_majflt : Long + ru_nswap : Long + ru_inblock : Long + ru_oublock : Long + ru_msgsnd : Long + ru_msgrcv : Long + ru_nsignals : Long + ru_nvcsw : Long + ru_nivcsw : Long + end + + RUSAGE_SELF = 0 + RUSAGE_CHILDREN = -1 + + fun getrusage(who : Int, usage : RUsage*) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/sys/select.cr b/src/lib_c/x86_64-solaris/c/sys/select.cr new file mode 100644 index 000000000000..73ff3b3da1cf --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/select.cr @@ -0,0 +1,12 @@ +require "./types" +require "./time" +require "../time" +require "../signal" + +lib LibC + struct FdSet + fds_bits : Long[1024] # FD_SETSIZE (65536) / FD_NFDBITS (64) + end + + fun select(x0 : Int, x1 : FdSet*, x2 : FdSet*, x3 : FdSet*, x4 : Timeval*) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/sys/socket.cr b/src/lib_c/x86_64-solaris/c/sys/socket.cr new file mode 100644 index 000000000000..4c2572c288ec --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/socket.cr @@ -0,0 +1,67 @@ +require "./types" + +@[Link("socket")] +lib LibC + SOCK_DGRAM = 1 + SOCK_RAW = 4 + SOCK_SEQPACKET = 6 + SOCK_STREAM = 2 + SOL_SOCKET = 0xffff + SO_BROADCAST = 0x0020 + SO_KEEPALIVE = 0x0008 + SO_LINGER = 0x0080 + SO_RCVBUF = 0x1002 + SO_REUSEADDR = 0x0004 + SO_REUSEPORT = 0x2004 + SO_SNDBUF = 0x1001 + PF_INET = LibC::AF_INET + PF_INET6 = LibC::AF_INET6 + PF_UNIX = LibC::AF_UNIX + PF_UNSPEC = LibC::AF_UNSPEC + PF_LOCAL = LibC::PF_UNIX + AF_INET = 2 + AF_INET6 = 26 + AF_UNIX = 1 + AF_UNSPEC = 0 + AF_LOCAL = LibC::AF_UNIX + SHUT_RD = 0 + SHUT_RDWR = 2 + SHUT_WR = 1 + SOCK_CLOEXEC = 0x080000 + + alias SocklenT = UInt32 + alias SaFamilyT = UInt16 + + struct Sockaddr + sa_family : SaFamilyT + sa_data : Char[14] + end + + struct SockaddrStorage + ss_family : SaFamilyT + _ss_pad1 : Char[6] # sizeof(Double) - sizeof(SaFamilyT) + _ss_align : Double + _ss_pad2 : Char[240] # SS_MAXSIZE (256) - sizeof(SaFamilyT) - sizeof(typeof(_ss_align)) - sizeof(Double) + end + + struct Linger + l_onoff : Int + l_linger : Int + end + + fun accept(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int + fun bind(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int + fun connect(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int + fun getpeername(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int + fun getsockname(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int + fun getsockopt(x0 : Int, x1 : Int, x2 : Int, x3 : Void*, x4 : SocklenT*) : Int + fun listen(x0 : Int, x1 : Int) : Int + fun recv(x0 : Int, x1 : Void*, x2 : SizeT, x3 : Int) : SSizeT + fun recvfrom(x0 : Int, x1 : Void*, x2 : SizeT, x3 : Int, x4 : Sockaddr*, x5 : SocklenT*) : SSizeT + fun send(x0 : Int, x1 : Void*, x2 : SizeT, x3 : Int) : SSizeT + fun sendto(x0 : Int, x1 : Void*, x2 : SizeT, x3 : Int, x4 : Sockaddr*, x5 : SocklenT) : SSizeT + fun setsockopt(x0 : Int, x1 : Int, x2 : Int, x3 : Void*, x4 : SocklenT) : Int + fun shutdown(x0 : Int, x1 : Int) : Int + fun socket(x0 : Int, x1 : Int, x2 : Int) : Int + fun socketpair(x0 : Int, x1 : Int, x2 : Int, x3 : Int*) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/sys/stat.cr b/src/lib_c/x86_64-solaris/c/sys/stat.cr new file mode 100644 index 000000000000..a5a1f3f1c5fc --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/stat.cr @@ -0,0 +1,59 @@ +require "./types" +require "../time" + +lib LibC + S_IFMT = 0xF000 + S_IFIFO = 0x1000 + S_IFCHR = 0x2000 + S_IFDIR = 0x4000 + S_IFBLK = 0x6000 + S_IFREG = 0x8000 + S_IFLNK = 0xA000 + S_IFSOCK = 0xC000 + S_IFDOOR = 0xD000 + S_IFPORT = 0xE000 + + S_IRWXU = 0o0700 + S_IRUSR = 0o0400 + S_IWUSR = 0o0200 + S_IXUSR = 0o0100 + S_IRWXG = 0o0070 + S_IRGRP = 0o0040 + S_IWGRP = 0o0020 + S_IXGRP = 0o0010 + S_IRWXO = 0o0007 + S_IROTH = 0o0004 + S_IWOTH = 0o0002 + S_IXOTH = 0o0001 + + S_ISUID = 0x800 + S_ISGID = 0x400 + S_ISVTX = 0x200 + + struct Stat + st_dev : DevT + st_ino : InoT + st_mode : ModeT + st_nlink : NlinkT + st_uid : UidT + st_gid : GidT + st_rdev : DevT + st_size : OffT + st_atim : Timespec + st_mtim : Timespec + st_ctim : Timespec + st_blksize : BlksizeT + st_blocks : BlkcntT + st_fstype : Char[16] + end + + fun chmod(x0 : Char*, x1 : ModeT) : Int + fun fchmod(x0 : Int, x1 : ModeT) : Int + fun fstat(x0 : Int, x1 : Stat*) : Int + fun lstat(x0 : Char*, x1 : Stat*) : Int + fun mkdir(x0 : Char*, x1 : ModeT) : Int + fun mkfifo(x0 : Char*, x1 : ModeT) : Int + fun mknod(x0 : Char*, x1 : ModeT, x2 : DevT) : Int + fun stat(x0 : Char*, x1 : Stat*) : Int + fun umask(x0 : ModeT) : ModeT +end diff --git a/src/lib_c/x86_64-solaris/c/sys/time.cr b/src/lib_c/x86_64-solaris/c/sys/time.cr new file mode 100644 index 000000000000..711894a3da7e --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/time.cr @@ -0,0 +1,17 @@ +require "./types" + +lib LibC + struct Timeval + tv_sec : TimeT + tv_usec : SusecondsT + end + + struct Timezone + tz_minuteswest : Int + tz_dsttime : Int + end + + fun gettimeofday(x0 : Timeval*, x1 : Void*) : Int + fun utimes(path : Char*, times : Timeval[2]) : Int + fun futimens(fd : Int, times : Timespec[2]) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/sys/types.cr b/src/lib_c/x86_64-solaris/c/sys/types.cr new file mode 100644 index 000000000000..b8457bffe861 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/types.cr @@ -0,0 +1,53 @@ +require "../stddef" +require "../stdint" + +lib LibC + alias BlkcntT = Long + alias BlksizeT = Int + alias ClockT = Long + alias ClockidT = Int + alias DevT = ULong + alias GidT = UInt + alias IdT = Int + alias InoT = ULong + alias ModeT = UInt + alias NlinkT = UInt + alias OffT = Long + alias PidT = Int + + struct PthreadAttrT + __pthread_attrp : Void* + end + + struct PthreadCondT + __pthread_cond_flag : UInt8[4] + __pthread_cond_type : UInt16 + __pthread_cond_magic : UInt16 + __pthread_cond_data : UInt64 + end + + struct PthreadCondattrT + __pthread_condattrp : Void* + end + + struct PthreadMutexT + __pthread_mutex_flag1 : UInt16 + __pthread_mutex_flag2 : UInt8 + __pthread_mutex_ceiling : UInt8 + __pthread_mutex_type : UInt16 + __pthread_mutex_magic : UInt16 + __pthread_mutex_lock : UInt64 # actually a union with {UInt8[8]} and {UInt32, UInt32} + __pthread_mutex_data : UInt64 + end + + struct PthreadMutexattrT + __pthread_mutexattrp : Void* + end + + alias PthreadT = UInt + + alias SSizeT = Long + alias SusecondsT = Long + alias TimeT = Long + alias UidT = UInt +end diff --git a/src/lib_c/x86_64-solaris/c/sys/un.cr b/src/lib_c/x86_64-solaris/c/sys/un.cr new file mode 100644 index 000000000000..78939cc5123e --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/un.cr @@ -0,0 +1,8 @@ +require "./socket" + +lib LibC + struct SockaddrUn + sun_family : SaFamilyT + sun_path : Char[108] + end +end diff --git a/src/lib_c/x86_64-solaris/c/sys/wait.cr b/src/lib_c/x86_64-solaris/c/sys/wait.cr new file mode 100644 index 000000000000..9ea48fec388c --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/wait.cr @@ -0,0 +1,8 @@ +require "./types" +require "../signal" + +lib LibC + WNOHANG = 0o100 + + fun waitpid(x0 : PidT, x1 : Int*, x2 : Int) : PidT +end diff --git a/src/lib_c/x86_64-solaris/c/termios.cr b/src/lib_c/x86_64-solaris/c/termios.cr new file mode 100644 index 000000000000..822a49ef632c --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/termios.cr @@ -0,0 +1,141 @@ +require "./sys/types" + +lib LibC + VINTR = 0 + VQUIT = 1 + VERASE = 2 + VKILL = 3 + VEOF = 4 + VEOL = 5 + VMIN = 4 + VTIME = 5 + VSTART = 8 + VSTOP = 9 + VSUSP = 10 + + IGNBRK = 0o000001 + BRKINT = 0o000002 + IGNPAR = 0o000004 + PARMRK = 0o000010 + INPCK = 0o000020 + ISTRIP = 0o000040 + INLCR = 0o000100 + IGNCR = 0o000200 + ICRNL = 0o000400 + IXON = 0o002000 + IXANY = 0o004000 + IXOFF = 0o010000 + + OPOST = 0o000001 + ONLCR = 0o000004 + OCRNL = 0o000010 + ONOCR = 0o000020 + ONLRET = 0o000040 + OFILL = 0o000100 + OFDEL = 0o000200 + NLDLY = 0o000400 + NL0 = 0 + NL1 = 0o000400 + CRDLY = 0o003000 + CR0 = 0 + CR1 = 0o001000 + CR2 = 0o002000 + CR3 = 0o003000 + TABDLY = 0o014000 + TAB0 = 0 + TAB1 = 0o004000 + TAB2 = 0o010000 + TAB3 = 0o014000 + BSDLY = 0o020000 + BS0 = 0 + BS1 = 0o020000 + VTDLY = 0o040000 + VT0 = 0 + VT1 = 0o040000 + FFDLY = 0o100000 + FF0 = 0 + FF1 = 0o100000 + + CSIZE = 0o000060 + CS5 = 0 + CS6 = 0o000020 + CS7 = 0o000040 + CS8 = 0o000060 + CSTOPB = 0o000100 + CREAD = 0o000200 + PARENB = 0o000400 + PARODD = 0o001000 + HUPCL = 0o002000 + CLOCAL = 0o004000 + + B0 = 0 + B50 = 1 + B75 = 2 + B110 = 3 + B134 = 4 + B150 = 5 + B200 = 6 + B300 = 7 + B600 = 8 + B1200 = 9 + B1800 = 10 + B2400 = 11 + B4800 = 12 + B9600 = 13 + B19200 = 14 + B38400 = 15 + B57600 = 16 + B76800 = 17 + B115200 = 18 + B153600 = 19 + B230400 = 20 + B307200 = 21 + B460800 = 22 + B921600 = 23 + B1000000 = 24 + B1152000 = 25 + B1500000 = 26 + B2000000 = 27 + B2500000 = 28 + B3000000 = 29 + B3500000 = 30 + B4000000 = 31 + + ISIG = 0o000001 + ICANON = 0o000002 + ECHO = 0o000010 + ECHOE = 0o000020 + ECHOK = 0o000040 + ECHONL = 0o000100 + NOFLSH = 0o000200 + TOSTOP = 0o000400 + IEXTEN = 0o100000 + + TCSANOW = 0x540E + TCSADRAIN = 0x540F + TCSAFLUSH = 0x5410 + + TCIFLUSH = 0 + TCIOFLUSH = 2 + TCOFLUSH = 1 + + TCIOFF = 2 + TCION = 3 + TCOOFF = 0 + TCOON = 1 + + alias CcT = UChar + alias SpeedT = UInt + alias TcflagT = UInt + + struct Termios + c_iflag : TcflagT + c_oflag : TcflagT + c_cflag : TcflagT + c_lflag : TcflagT + c_cc : CcT[19] + end + + fun tcgetattr(x0 : Int, x1 : Termios*) : Int + fun tcsetattr(x0 : Int, x1 : Int, x2 : Termios*) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/time.cr b/src/lib_c/x86_64-solaris/c/time.cr new file mode 100644 index 000000000000..c8fc7ea9231f --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/time.cr @@ -0,0 +1,35 @@ +require "./sys/types" + +lib LibC + CLOCK_MONOTONIC = 4 + CLOCK_REALTIME = 3 + + struct Tm + tm_sec : Int + tm_min : Int + tm_hour : Int + tm_mday : Int + tm_mon : Int + tm_year : Int + tm_wday : Int + tm_yday : Int + tm_isdst : Int + end + + struct Timespec + tv_sec : TimeT + tv_nsec : Long + end + + fun clock_gettime(x0 : ClockidT, x1 : Timespec*) : Int + fun clock_settime(x0 : ClockidT, x1 : Timespec*) : Int + fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* + fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* + fun mktime(x0 : Tm*) : TimeT + fun tzset : Void + fun timegm(x0 : Tm*) : TimeT + + $daylight : Int + $timezone : Long + $tzname : StaticArray(Char*, 2) +end diff --git a/src/lib_c/x86_64-solaris/c/unistd.cr b/src/lib_c/x86_64-solaris/c/unistd.cr new file mode 100644 index 000000000000..0c394df08b2e --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/unistd.cr @@ -0,0 +1,51 @@ +require "./sys/types" +require "./stdint" + +lib LibC + F_OK = 0 + R_OK = 4 + W_OK = 2 + X_OK = 1 + + SC_CLK_TCK = 3 + SC_NPROCESSORS_ONLN = 15 + SC_PAGESIZE = 11 + + fun chroot(dirname : Char*) : Int + fun access(x0 : Char*, x1 : Int) : Int + fun chdir(x0 : Char*) : Int + fun chown(x0 : Char*, x1 : UidT, x2 : GidT) : Int + fun fchown(x0 : Int, x1 : UidT, x2 : GidT) : Int + fun close(x0 : Int) : Int + fun dup2(x0 : Int, x1 : Int) : Int + fun _exit(x0 : Int) : NoReturn + fun execvp(x0 : Char*, x1 : Char**) : Int + fun fdatasync(fd : Int) : Int + @[ReturnsTwice] + fun fork : PidT + fun fsync(fd : Int) : Int + fun ftruncate(x0 : Int, x1 : OffT) : Int + fun getcwd(x0 : Char*, x1 : SizeT) : Char* + fun gethostname(x0 : Char*, x1 : Int) : Int + fun getpgid(pid : PidT) : PidT + fun getpid : PidT + fun getppid : PidT + fun getuid : UidT + fun setuid(uid : UidT) : Int + fun ioctl(x0 : Int, x1 : Int, ...) : Int + fun isatty(x0 : Int) : Int + fun ttyname_r = __posix_ttyname_r(fd : Int, buf : Char*, buffersize : SizeT) : Int + fun lchown(x0 : Char*, x1 : UidT, x2 : GidT) : Int + fun link(x0 : Char*, x1 : Char*) : Int + fun lockf(x0 : Int, x1 : Int, x2 : OffT) : Int + fun lseek(x0 : Int, x1 : OffT, x2 : Int) : OffT + fun pipe(x0 : Int*) : Int + fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT + fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT + fun rmdir(x0 : Char*) : Int + fun symlink(x0 : Char*, x1 : Char*) : Int + fun readlink(path : Char*, buf : Char*, size : SizeT) : SSizeT + fun sysconf(x0 : Int) : Long + fun unlink(x0 : Char*) : Int + fun write(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT +end diff --git a/src/llvm.cr b/src/llvm.cr index f33c4d31fb76..afef40bc3c95 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -100,6 +100,9 @@ module LLVM when .starts_with?("aarch64-unknown-linux-android") # remove API version "aarch64-unknown-linux-android" + when .starts_with?("x86_64-pc-solaris") + # remove API version + "x86_64-pc-solaris" else triple end diff --git a/src/math/libm.cr b/src/math/libm.cr index 59e66f12737e..b87b7bf5d07b 100644 --- a/src/math/libm.cr +++ b/src/math/libm.cr @@ -5,7 +5,7 @@ # the actual library file to the load path. `Crystal::Loader` does not support # ld scripts yet. So we just skip that for now. The libm symbols are still # available in the interpreter. -{% if (flag?(:linux) && !flag?(:musl) && !flag?(:interpreted)) || flag?(:bsd) %} +{% if (flag?(:linux) && !flag?(:musl) && !flag?(:interpreted)) || flag?(:bsd) || flag?(:solaris) %} @[Link("m")] {% end %} diff --git a/src/socket/address.cr b/src/socket/address.cr index 851180d3e9b9..20fca43544e6 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -386,6 +386,8 @@ class Socket end {% elsif flag?(:linux) %} addr.__in6_u.__u6_addr16 = bytes + {% elsif flag?(:solaris) %} + addr._S6_un._S6_u16 = bytes {% elsif flag?(:win32) %} addr.u.word = bytes {% else %} @@ -528,6 +530,8 @@ class Socket addr.s6_addr {% elsif flag?(:linux) %} addr.__in6_u.__u6_addr8 + {% elsif flag?(:solaris) %} + addr._S6_un._S6_u8 {% elsif flag?(:win32) %} addr.u.byte {% else %} From b79ea1614c37958e4e24d7673bb263a948426881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 7 Mar 2024 10:31:29 +0100 Subject: [PATCH 0995/1551] Fix formatter for white space in `a.[b]` syntax (#14346) --- spec/compiler/formatter/formatter_spec.cr | 6 ++++++ src/compiler/crystal/tools/formatter.cr | 2 ++ 2 files changed, 8 insertions(+) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index bd992b78fa4a..dc7578c736bf 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1151,6 +1151,12 @@ describe Crystal::Formatter do assert_format "foo[] =1", "foo[] = 1" assert_format "foo[ 1 , 2 ] =3", "foo[1, 2] = 3" + assert_format "foo.[]" + assert_format "foo.[ 1 , 2 ]", "foo.[1, 2]" + assert_format "foo.[ 1, 2 ]?", "foo.[1, 2]?" + assert_format "foo.[] =1", "foo.[] = 1" + assert_format "foo.[ 1 , 2 ] =3", "foo.[1, 2] = 3" + assert_format "1 || 2", "1 || 2" assert_format "a || b", "a || b" assert_format "1 && 2", "1 && 2" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 79d7f3cf5a68..0d7a6115ca71 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -2691,6 +2691,7 @@ module Crystal write "[" next_token_skip_space_or_newline format_call_args(node, false, base_indent) + skip_space_or_newline write_token :OP_RSQUARE write_token :OP_QUESTION if node.name == "[]?" return false @@ -2703,6 +2704,7 @@ module Crystal args = node.args last_arg = args.pop format_call_args(node, true, base_indent) + skip_space_or_newline write_token :OP_RSQUARE skip_space_or_newline write " =" From d15649fa39363e49322006b4b8128042bc3270ad Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 8 Mar 2024 10:22:38 +0100 Subject: [PATCH 0996/1551] Fix: Add `SA_RESTART` flag to sigaction syscall (#14351) --- src/crystal/system/unix/signal.cr | 5 +++++ src/lib_c/aarch64-android/c/signal.cr | 1 + src/lib_c/aarch64-darwin/c/signal.cr | 1 + src/lib_c/aarch64-linux-gnu/c/signal.cr | 1 + src/lib_c/aarch64-linux-musl/c/signal.cr | 1 + src/lib_c/arm-linux-gnueabihf/c/signal.cr | 1 + src/lib_c/i386-linux-gnu/c/signal.cr | 1 + src/lib_c/i386-linux-musl/c/signal.cr | 1 + src/lib_c/x86_64-darwin/c/signal.cr | 1 + src/lib_c/x86_64-dragonfly/c/signal.cr | 1 + src/lib_c/x86_64-freebsd/c/signal.cr | 1 + src/lib_c/x86_64-linux-gnu/c/signal.cr | 1 + src/lib_c/x86_64-linux-musl/c/signal.cr | 1 + src/lib_c/x86_64-netbsd/c/signal.cr | 1 + src/lib_c/x86_64-openbsd/c/signal.cr | 1 + src/lib_c/x86_64-solaris/c/signal.cr | 1 + 16 files changed, 20 insertions(+) diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr index d97b7a9e3672..1d1e885fc71d 100644 --- a/src/crystal/system/unix/signal.cr +++ b/src/crystal/system/unix/signal.cr @@ -23,6 +23,11 @@ module Crystal::System::Signal unless @@handlers[signal]? @@sigset << signal action = LibC::Sigaction.new + + # restart some interrupted syscalls (read, write, accept, ...) instead + # of returning EINTR: + action.sa_flags = LibC::SA_RESTART + action.sa_sigaction = LibC::SigactionHandlerT.new do |value, _, _| writer.write_bytes(value) unless writer.closed? end diff --git a/src/lib_c/aarch64-android/c/signal.cr b/src/lib_c/aarch64-android/c/signal.cr index e8e3ffb212e9..741c8f0efb65 100644 --- a/src/lib_c/aarch64-android/c/signal.cr +++ b/src/lib_c/aarch64-android/c/signal.cr @@ -50,6 +50,7 @@ lib LibC end SA_ONSTACK = 0x08000000 + SA_RESTART = 0x10000000 SA_SIGINFO = 0x00000004 struct SiginfoT diff --git a/src/lib_c/aarch64-darwin/c/signal.cr b/src/lib_c/aarch64-darwin/c/signal.cr index 8e3cbe8c0e6e..e58adc30289f 100644 --- a/src/lib_c/aarch64-darwin/c/signal.cr +++ b/src/lib_c/aarch64-darwin/c/signal.cr @@ -45,6 +45,7 @@ lib LibC SIG_IGN = SighandlerT.new(Pointer(Void).new(1_u64), Pointer(Void).null) SA_ONSTACK = 0x0001 + SA_RESTART = 0x0002 SA_SIGINFO = 0x0040 struct SiginfoT diff --git a/src/lib_c/aarch64-linux-gnu/c/signal.cr b/src/lib_c/aarch64-linux-gnu/c/signal.cr index d7c2a8790d2a..1f7d82eb2145 100644 --- a/src/lib_c/aarch64-linux-gnu/c/signal.cr +++ b/src/lib_c/aarch64-linux-gnu/c/signal.cr @@ -49,6 +49,7 @@ lib LibC end SA_ONSTACK = 0x08000000 + SA_RESTART = 0x10000000 SA_SIGINFO = 0x00000004 struct SiginfoT diff --git a/src/lib_c/aarch64-linux-musl/c/signal.cr b/src/lib_c/aarch64-linux-musl/c/signal.cr index 1f0411e152c7..5bfa187b14ec 100644 --- a/src/lib_c/aarch64-linux-musl/c/signal.cr +++ b/src/lib_c/aarch64-linux-musl/c/signal.cr @@ -48,6 +48,7 @@ lib LibC end SA_ONSTACK = 0x08000000 + SA_RESTART = 0x10000000 SA_SIGINFO = 0x00000004 struct SiginfoT diff --git a/src/lib_c/arm-linux-gnueabihf/c/signal.cr b/src/lib_c/arm-linux-gnueabihf/c/signal.cr index 22e4fdd561b9..d94d657e1ca8 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/signal.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/signal.cr @@ -49,6 +49,7 @@ lib LibC end SA_ONSTACK = 0x08000000 + SA_RESTART = 0x10000000 SA_SIGINFO = 0x00000004 struct SiginfoT diff --git a/src/lib_c/i386-linux-gnu/c/signal.cr b/src/lib_c/i386-linux-gnu/c/signal.cr index 2a2219968e04..11aab8bfe6bb 100644 --- a/src/lib_c/i386-linux-gnu/c/signal.cr +++ b/src/lib_c/i386-linux-gnu/c/signal.cr @@ -49,6 +49,7 @@ lib LibC end SA_ONSTACK = 0x08000000 + SA_RESTART = 0x10000000 SA_SIGINFO = 0x00000004 struct SiginfoT diff --git a/src/lib_c/i386-linux-musl/c/signal.cr b/src/lib_c/i386-linux-musl/c/signal.cr index d822d82b9d27..f2e554942b69 100644 --- a/src/lib_c/i386-linux-musl/c/signal.cr +++ b/src/lib_c/i386-linux-musl/c/signal.cr @@ -48,6 +48,7 @@ lib LibC end SA_ONSTACK = 0x08000000 + SA_RESTART = 0x10000000 SA_SIGINFO = 0x00000004 struct SiginfoT diff --git a/src/lib_c/x86_64-darwin/c/signal.cr b/src/lib_c/x86_64-darwin/c/signal.cr index 8e3cbe8c0e6e..e58adc30289f 100644 --- a/src/lib_c/x86_64-darwin/c/signal.cr +++ b/src/lib_c/x86_64-darwin/c/signal.cr @@ -45,6 +45,7 @@ lib LibC SIG_IGN = SighandlerT.new(Pointer(Void).new(1_u64), Pointer(Void).null) SA_ONSTACK = 0x0001 + SA_RESTART = 0x0002 SA_SIGINFO = 0x0040 struct SiginfoT diff --git a/src/lib_c/x86_64-dragonfly/c/signal.cr b/src/lib_c/x86_64-dragonfly/c/signal.cr index 34ee5a0d22fb..1751eeed3176 100644 --- a/src/lib_c/x86_64-dragonfly/c/signal.cr +++ b/src/lib_c/x86_64-dragonfly/c/signal.cr @@ -51,6 +51,7 @@ lib LibC end SA_ONSTACK = 0x0001 + SA_RESTART = 0x0002 SA_SIGINFO = 0x0040 struct Sigval diff --git a/src/lib_c/x86_64-freebsd/c/signal.cr b/src/lib_c/x86_64-freebsd/c/signal.cr index 369153956b45..fd8d07cfd4cc 100644 --- a/src/lib_c/x86_64-freebsd/c/signal.cr +++ b/src/lib_c/x86_64-freebsd/c/signal.cr @@ -46,6 +46,7 @@ lib LibC end SA_ONSTACK = 0x0001 + SA_RESTART = 0x0002 SA_SIGINFO = 0x0040 struct Sigval diff --git a/src/lib_c/x86_64-linux-gnu/c/signal.cr b/src/lib_c/x86_64-linux-gnu/c/signal.cr index 46b6dba5cc62..07d8e0fe1ae6 100644 --- a/src/lib_c/x86_64-linux-gnu/c/signal.cr +++ b/src/lib_c/x86_64-linux-gnu/c/signal.cr @@ -49,6 +49,7 @@ lib LibC end SA_ONSTACK = 0x08000000 + SA_RESTART = 0x10000000 SA_SIGINFO = 0x00000004 struct SiginfoT diff --git a/src/lib_c/x86_64-linux-musl/c/signal.cr b/src/lib_c/x86_64-linux-musl/c/signal.cr index 8b1b75bc0e94..bba7e0c7c21a 100644 --- a/src/lib_c/x86_64-linux-musl/c/signal.cr +++ b/src/lib_c/x86_64-linux-musl/c/signal.cr @@ -48,6 +48,7 @@ lib LibC end SA_ONSTACK = 0x08000000 + SA_RESTART = 0x10000000 SA_SIGINFO = 0x00000004 struct SiginfoT diff --git a/src/lib_c/x86_64-netbsd/c/signal.cr b/src/lib_c/x86_64-netbsd/c/signal.cr index 501772cc3637..93d42e38b093 100644 --- a/src/lib_c/x86_64-netbsd/c/signal.cr +++ b/src/lib_c/x86_64-netbsd/c/signal.cr @@ -47,6 +47,7 @@ lib LibC end SA_ONSTACK = 0x0001 + SA_RESTART = 0x0002 SA_SIGINFO = 0x0040 struct SiginfoT diff --git a/src/lib_c/x86_64-openbsd/c/signal.cr b/src/lib_c/x86_64-openbsd/c/signal.cr index e96e5b75cdf3..04aa27000219 100644 --- a/src/lib_c/x86_64-openbsd/c/signal.cr +++ b/src/lib_c/x86_64-openbsd/c/signal.cr @@ -47,6 +47,7 @@ lib LibC SIG_IGN = SighandlerT.new(Pointer(Void).new(1_u64), Pointer(Void).null) SA_ONSTACK = 0x0001 + SA_RESTART = 0x0002 SA_SIGINFO = 0x0040 struct SiginfoT diff --git a/src/lib_c/x86_64-solaris/c/signal.cr b/src/lib_c/x86_64-solaris/c/signal.cr index b209436e0987..9bde30946054 100644 --- a/src/lib_c/x86_64-solaris/c/signal.cr +++ b/src/lib_c/x86_64-solaris/c/signal.cr @@ -60,6 +60,7 @@ lib LibC end SA_ONSTACK = 0x00000001 + SA_RESTART = 0x00000004 SA_SIGINFO = 0x00000008 struct SiginfoT From 12cd09299ae86b02a1d02c94ef57840e34724acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 9 Mar 2024 09:59:21 +0100 Subject: [PATCH 0997/1551] Add `CRYSTAL_CONFIG_CC` compiler config (#14318) --- src/compiler/crystal/compiler.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 4de446ba6425..7ca2a564f9cd 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -27,8 +27,8 @@ module Crystal # A Compiler parses source code, type checks it and # optionally generates an executable. class Compiler - private DEFAULT_LINKER = ENV["CC"]? || "cc" - private MSVC_LINKER = ENV["CC"]? || "cl.exe" + private DEFAULT_LINKER = ENV["CC"]? || {{ env("CRYSTAL_CONFIG_CC") || "cc" }} + private MSVC_LINKER = ENV["CC"]? || {{ env("CRYSTAL_CONFIG_CC") || "cl.exe" }} # A source to the compiler: its filename and source code. record Source, From 8fafef2744294b8c46a7f5d7f85a5f2930be4080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 9 Mar 2024 09:59:43 +0100 Subject: [PATCH 0998/1551] Add `Lexer#wants_def_or_macro_name` (#14352) --- src/compiler/crystal/syntax/lexer.cr | 7 +++++++ src/compiler/crystal/syntax/parser.cr | 14 +++++++------- src/compiler/crystal/tools/formatter.cr | 19 ++++++++++--------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 21c3ab42c804..0f60e555cdac 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -2813,6 +2813,13 @@ module Crystal next_token.tap { @wants_symbol = true } end + def wants_def_or_macro_name(& : ->) + @wants_def_or_macro_name = true + yield + ensure + @wants_def_or_macro_name = false + end + def current_char @reader.current_char end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 3185fdee6fdc..a9454c73a11c 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -682,9 +682,9 @@ module Crystal @wants_regex = false - @wants_def_or_macro_name = true - next_token_skip_space_or_newline - @wants_def_or_macro_name = false + wants_def_or_macro_name do + next_token_skip_space_or_newline + end if @token.type.instance_var? ivar_name = @token.value.to_s @@ -6204,10 +6204,10 @@ module Crystal # cases like: def `, def /, def // # that in regular statements states for delimiters # here must be treated as method names. - @wants_def_or_macro_name = true - next_token_skip_space_or_newline - check DefOrMacroCheck1 - @wants_def_or_macro_name = false + wants_def_or_macro_name do + next_token_skip_space_or_newline + check DefOrMacroCheck1 + end @token.to_s end diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 0d7a6115ca71..17c87168414d 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1464,9 +1464,9 @@ module Crystal write_token :OP_PERIOD end - @lexer.wants_def_or_macro_name = true - skip_space_or_newline - @lexer.wants_def_or_macro_name = false + @lexer.wants_def_or_macro_name do + skip_space_or_newline + end write node.name @@ -2671,10 +2671,11 @@ module Crystal return false end - @lexer.wants_def_or_macro_name = true - next_token - @lexer.wants_def_or_macro_name = false + @lexer.wants_def_or_macro_name do + next_token + end skip_space + if (@token.type.newline?) || @wrote_newline base_indent = @indent + 2 indent(base_indent) { consume_newlines } @@ -3106,9 +3107,9 @@ module Crystal write_token :OP_AMP skip_space_or_newline write "." - @lexer.wants_def_or_macro_name = true - next_token_skip_space_or_newline - @lexer.wants_def_or_macro_name = false + @lexer.wants_def_or_macro_name do + next_token_skip_space_or_newline + end body = node.body case body From 15c66d05a67dbcbc5381cee49310ab7ed77b1a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 10 Mar 2024 13:31:32 +0100 Subject: [PATCH 0999/1551] Fix formatter on call without parentheses followed by doc comment (#14354) --- spec/compiler/formatter/formatter_spec.cr | 8 ++++++++ src/compiler/crystal/tools/formatter.cr | 2 -- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index dc7578c736bf..2b014ba69d4e 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -2893,6 +2893,14 @@ describe Crystal::Formatter do end end + # #14256 + assert_format <<-CRYSTAL + foo bar # comment + + # doc + def baz; end + CRYSTAL + # CVE-2021-42574 describe "Unicode bi-directional control characters" do ['\u202A', '\u202B', '\u202C', '\u202D', '\u202E', '\u2066', '\u2067', '\u2068', '\u2069'].each do |char| diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 17c87168414d..6182ab074bfe 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -2982,8 +2982,6 @@ module Crystal end def finish_args(has_parentheses, has_newlines, ends_with_newline, found_comment, column) - skip_space - if has_parentheses if @token.type.op_comma? next_token From 84b0cdbd11446bebbed7cbefb254e1917d450bd7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 12 Mar 2024 18:42:10 +0800 Subject: [PATCH 1000/1551] Update Windows library versions (#14355) --- .github/workflows/win.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 3ef4cccb8739..05f74b6378c6 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -7,7 +7,7 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: - CI_LLVM_VERSION: "18.1.0-rc1" + CI_LLVM_VERSION: "18.1.1" jobs: x86_64-windows-libs: @@ -40,13 +40,13 @@ jobs: key: win-libs-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc - name: Build libgc if: steps.cache-libs.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-gc.ps1 -BuildTree deps\gc -Version 8.2.2 -AtomicOpsVersion 7.8.0 + run: .\etc\win-ci\build-gc.ps1 -BuildTree deps\gc -Version 8.2.6 -AtomicOpsVersion 7.8.2 - name: Build libpcre if: steps.cache-libs.outputs.cache-hit != 'true' run: .\etc\win-ci\build-pcre.ps1 -BuildTree deps\pcre -Version 8.45 - name: Build libpcre2 if: steps.cache-libs.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.42 + run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.43 - name: Build libiconv if: steps.cache-libs.outputs.cache-hit != 'true' run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv @@ -55,7 +55,7 @@ jobs: run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.3 - name: Build zlib if: steps.cache-libs.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.2.13 + run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.3.1 - name: Build mpir if: steps.cache-libs.outputs.cache-hit != 'true' run: .\etc\win-ci\build-mpir.ps1 -BuildTree deps\mpir @@ -64,7 +64,7 @@ jobs: run: .\etc\win-ci\build-yaml.ps1 -BuildTree deps\yaml -Version 0.2.5 - name: Build libxml2 if: steps.cache-libs.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-xml2.ps1 -BuildTree deps\xml2 -Version 2.11.3 + run: .\etc\win-ci\build-xml2.ps1 -BuildTree deps\xml2 -Version 2.12.5 - name: Cache OpenSSL id: cache-openssl @@ -121,13 +121,13 @@ jobs: key: win-dlls-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc - name: Build libgc if: steps.cache-dlls.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-gc.ps1 -BuildTree deps\gc -Version 8.2.4 -AtomicOpsVersion 7.8.0 -Dynamic + run: .\etc\win-ci\build-gc.ps1 -BuildTree deps\gc -Version 8.2.6 -AtomicOpsVersion 7.8.2 -Dynamic - name: Build libpcre if: steps.cache-dlls.outputs.cache-hit != 'true' run: .\etc\win-ci\build-pcre.ps1 -BuildTree deps\pcre -Version 8.45 -Dynamic - name: Build libpcre2 if: steps.cache-dlls.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.42 -Dynamic + run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.43 -Dynamic - name: Build libiconv if: steps.cache-dlls.outputs.cache-hit != 'true' run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Dynamic @@ -136,7 +136,7 @@ jobs: run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.3 -Dynamic - name: Build zlib if: steps.cache-dlls.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.2.13 -Dynamic + run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.3.1 -Dynamic - name: Build mpir if: steps.cache-dlls.outputs.cache-hit != 'true' run: .\etc\win-ci\build-mpir.ps1 -BuildTree deps\mpir -Dynamic @@ -145,7 +145,7 @@ jobs: run: .\etc\win-ci\build-yaml.ps1 -BuildTree deps\yaml -Version 0.2.5 -Dynamic - name: Build libxml2 if: steps.cache-dlls.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-xml2.ps1 -BuildTree deps\xml2 -Version 2.11.3 -Dynamic + run: .\etc\win-ci\build-xml2.ps1 -BuildTree deps\xml2 -Version 2.12.5 -Dynamic - name: Cache OpenSSL id: cache-openssl-dlls @@ -218,7 +218,7 @@ jobs: uses: ./.github/workflows/win_build_portable.yml with: release: false - llvm_version: "18.1.0-rc1" + llvm_version: "18.1.1" x86_64-windows-test: runs-on: windows-2022 @@ -271,7 +271,7 @@ jobs: uses: ./.github/workflows/win_build_portable.yml with: release: true - llvm_version: "18.1.0-rc1" + llvm_version: "18.1.1" x86_64-windows-installer: if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/')) From 2b0b50602b9aeffc509cae053b109d751ed1092a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 13 Mar 2024 18:52:17 +0800 Subject: [PATCH 1001/1551] Skip building `llvm_ext.cc` on LLVM 18 or above (#14357) --- Makefile | 12 +++++++++--- Makefile.win | 10 ++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 461ee56bd5e5..492b0aa762df 100644 --- a/Makefile +++ b/Makefile @@ -59,10 +59,9 @@ EXPORTS_BUILD := \ CRYSTAL_CONFIG_LIBRARY_PATH=$(CRYSTAL_CONFIG_LIBRARY_PATH) SHELL = sh LLVM_CONFIG := $(shell src/llvm/ext/find-llvm-config) -LLVM_VERSION := $(if $(LLVM_CONFIG), $(shell $(LLVM_CONFIG) --version 2> /dev/null)) +LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell $(LLVM_CONFIG) --version 2> /dev/null)) LLVM_EXT_DIR = src/llvm/ext LLVM_EXT_OBJ = $(LLVM_EXT_DIR)/llvm_ext.o -DEPS = $(LLVM_EXT_OBJ) CXXFLAGS += $(if $(debug),-g -O0) CRYSTAL_VERSION ?= $(shell cat src/VERSION) @@ -80,6 +79,13 @@ else colorize = $(shell printf >&2 "\033[33m$1\033[0m\n") endif +DEPS = $(LLVM_EXT_OBJ) +ifneq ($(LLVM_VERSION),) + ifeq ($(shell test $(firstword $(subst ., ,$(LLVM_VERSION))) -ge 18; echo $$?),0) + DEPS = + endif +endif + check_llvm_config = $(eval \ check_llvm_config := $(if $(LLVM_VERSION),\ $(call colorize,Using $(LLVM_CONFIG) [version=$(LLVM_VERSION)]),\ @@ -208,7 +214,7 @@ $(O)/primitives_spec: $(O)/crystal $(DEPS) $(SOURCES) $(SPEC_SOURCES) @mkdir -p $(O) $(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/primitives_spec.cr -$(O)/interpreter_spec: deps $(SOURCES) $(SPEC_SOURCES) +$(O)/interpreter_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(eval interpreter=1) @mkdir -p $(O) $(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler/interpreter_spec.cr diff --git a/Makefile.win b/Makefile.win index 2e4899e58f8d..034dce59e4dc 100644 --- a/Makefile.win +++ b/Makefile.win @@ -70,7 +70,6 @@ LLVM_CONFIG ?= LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell $(LLVM_CONFIG) --version)) LLVM_EXT_DIR = src\llvm\ext LLVM_EXT_OBJ = $(LLVM_EXT_DIR)\llvm_ext.obj -DEPS = $(LLVM_EXT_OBJ) CXXFLAGS += $(if $(static),$(if $(debug),/MTd /Od ,/MT ),$(if $(debug),/MDd /Od ,/MD )) CRYSTAL_VERSION ?= $(shell type src\VERSION) @@ -82,6 +81,13 @@ DATADIR ?= $(prefix) colorize = $1 +DEPS = $(LLVM_EXT_OBJ) +ifneq ($(LLVM_VERSION),) + ifeq ($(shell if $(firstword $(subst ., ,$(LLVM_VERSION))) GEQ 18 echo 0),0) + DEPS = + endif +endif + check_llvm_config = $(eval \ check_llvm_config := $(if $(LLVM_VERSION),\ $(info $(call colorize,Using $(LLVM_CONFIG) [version=$(LLVM_VERSION)])),\ @@ -198,7 +204,7 @@ $(O)\primitives_spec.exe: $(O)\crystal.exe $(DEPS) $(SOURCES) $(SPEC_SOURCES) @$(call MKDIR,"$(O)") .\bin\crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o "$@" spec\primitives_spec.cr -$(O)\interpreter_spec: deps $(SOURCES) $(SPEC_SOURCES) +$(O)\interpreter_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(eval interpreter=1) @$(call MKDIR, "$(O)") .\bin\crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec\compiler\interpreter_spec.cr From eb743de5691300d1b25d211142be46ec8bf90bff Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 15 Mar 2024 11:48:01 +0100 Subject: [PATCH 1002/1551] Fix: OpenSSL 3.x reports unexpected EOF as SSL error (#14219) --- src/openssl.cr | 23 +++++++++++++++++++---- src/openssl/error.cr | 12 ++++++++++++ src/openssl/lib_crypto.cr | 6 ++++++ src/openssl/lib_ssl.cr | 5 +++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/openssl.cr b/src/openssl.cr index 802c9a05e7d3..4185518d3982 100644 --- a/src/openssl.cr +++ b/src/openssl.cr @@ -88,10 +88,19 @@ module OpenSSL message = "Raised erroneously" when .syscall? @code, message = fetch_error_details - if @code == 0 + {% if LibSSL.has_constant?(:SSL_R_UNEXPECTED_EOF_WHILE_READING) %} + if @code == 0 + # FIXME: this isn't a per the OpenSSL documentation for how to + # detect EOF, but this fixes the EOF detection spec... + message = "Unexpected EOF while reading" + @underlying_eof = true + else + cause = RuntimeError.from_errno(func || "OpenSSL") + end + {% else %} case return_code when 0 - message = "Unexpected EOF" + message = "Unexpected EOF while reading" @underlying_eof = true when -1 cause = RuntimeError.from_errno(func || "OpenSSL") @@ -99,9 +108,15 @@ module OpenSSL else message = "Unknown error" end - end + {% end %} when .ssl? - @code, message = fetch_error_details + code, message = fetch_error_details + @code = code + {% if LibSSL.has_constant?(:SSL_R_UNEXPECTED_EOF_WHILE_READING) %} + if get_reason(code) == LibSSL::SSL_R_UNEXPECTED_EOF_WHILE_READING + @underlying_eof = true + end + {% end %} else message = @error.to_s end diff --git a/src/openssl/error.cr b/src/openssl/error.cr index e902ad9bef42..ff12c33812ab 100644 --- a/src/openssl/error.cr +++ b/src/openssl/error.cr @@ -21,5 +21,17 @@ module OpenSSL message = String.new(LibCrypto.err_error_string(code, nil)) unless code == 0 {code, message || "Unknown or no error"} end + + protected def get_reason(code) + {% if LibCrypto.has_constant?(:ERR_REASON_MASK) %} + if (code & LibCrypto::ERR_SYSTEM_FLAG) != 0 + (code & LibCrypto::ERR_SYSTEM_MASK).to_i + else + (code & LibCrypto::ERR_REASON_MASK).to_i + end + {% else %} + (code & 0xFFF).to_i + {% end %} + end end end diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr index 1c4731292dc3..aef6a238f663 100644 --- a/src/openssl/lib_crypto.cr +++ b/src/openssl/lib_crypto.cr @@ -268,6 +268,12 @@ lib LibCrypto fun err_get_error = ERR_get_error : ULong fun err_error_string = ERR_error_string(e : ULong, buf : Char*) : Char* + {% if compare_versions(OPENSSL_VERSION, "3.0.0") >= 0 %} + ERR_SYSTEM_FLAG = Int32::MAX.to_u32 + 1 + ERR_SYSTEM_MASK = Int32::MAX.to_u32 + ERR_REASON_MASK = 0x7FFFFF + {% end %} + struct MD5Context a : UInt b : UInt diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr index 558b25017054..6adb3f172a3b 100644 --- a/src/openssl/lib_ssl.cr +++ b/src/openssl/lib_ssl.cr @@ -284,6 +284,11 @@ lib LibSSL fun ssl_ctx_set_security_level = SSL_CTX_set_security_level(ctx : SSLContext, level : Int) : Void fun ssl_ctx_get_security_level = SSL_CTX_get_security_level(ctx : SSLContext) : Int {% end %} + + # SSL reason codes + {% if compare_versions(OPENSSL_VERSION, "3.0.0") >= 0 %} + SSL_R_UNEXPECTED_EOF_WHILE_READING = 294 + {% end %} end {% unless compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 %} From c0cb225a91b96c62cda0e4b7a38a1c50195a0d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 18 Mar 2024 11:37:52 +0100 Subject: [PATCH 1003/1551] Move timeout properties to `Socket` and `IO::FileDescriptor` (#14367) --- src/io/evented.cr | 35 ----------------------------------- src/io/file_descriptor.cr | 18 ++++++++++++++++++ src/io/overlapped.cr | 35 ----------------------------------- src/socket.cr | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+), 70 deletions(-) diff --git a/src/io/evented.cr b/src/io/evented.cr index 191f136466a1..98215e7df86f 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -6,47 +6,12 @@ module IO::Evented @read_timed_out = false @write_timed_out = false - @read_timeout : Time::Span? - @write_timeout : Time::Span? - @readers = Crystal::ThreadLocalValue(Deque(Fiber)).new @writers = Crystal::ThreadLocalValue(Deque(Fiber)).new @read_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new @write_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new - # Returns the time to wait when reading before raising an `IO::TimeoutError`. - def read_timeout : Time::Span? - @read_timeout - end - - # Sets the time to wait when reading before raising an `IO::TimeoutError`. - def read_timeout=(timeout : Time::Span?) : ::Time::Span? - @read_timeout = timeout - end - - # Sets the number of seconds to wait when reading before raising an `IO::TimeoutError`. - def read_timeout=(read_timeout : Number) : Number - self.read_timeout = read_timeout.seconds - read_timeout - end - - # Returns the time to wait when writing before raising an `IO::TimeoutError`. - def write_timeout : Time::Span? - @write_timeout - end - - # Sets the time to wait when writing before raising an `IO::TimeoutError`. - def write_timeout=(timeout : Time::Span?) : ::Time::Span? - @write_timeout = timeout - end - - # Sets the number of seconds to wait when writing before raising an `IO::TimeoutError`. - def write_timeout=(write_timeout : Number) : Number - self.write_timeout = write_timeout.seconds - write_timeout - end - def evented_read(slice : Bytes, errno_msg : String, &) : Int32 loop do bytes_read = yield slice diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index c367d2ecaf74..14eabd31a6af 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -18,6 +18,24 @@ class IO::FileDescriptor < IO # will then fail. property? close_on_finalize : Bool + # The time to wait when reading before raising an `IO::TimeoutError`. + property read_timeout : Time::Span? + + # Sets the number of seconds to wait when reading before raising an `IO::TimeoutError`. + def read_timeout=(read_timeout : Number) : Number + self.read_timeout = read_timeout.seconds + read_timeout + end + + # Sets the time to wait when writing before raising an `IO::TimeoutError`. + property write_timeout : Time::Span? + + # Sets the number of seconds to wait when writing before raising an `IO::TimeoutError`. + def write_timeout=(write_timeout : Number) : Number + self.write_timeout = write_timeout.seconds + write_timeout + end + def initialize(fd, blocking = nil, *, @close_on_finalize = true) @volatile_fd = Atomic.new(fd) @closed = system_closed? diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index de36b5bf638b..0ab1a24fc794 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -8,41 +8,6 @@ module IO::Overlapped property fiber : Fiber? end - @read_timeout : Time::Span? - @write_timeout : Time::Span? - - # Returns the time to wait when reading before raising an `IO::TimeoutError`. - def read_timeout : Time::Span? - @read_timeout - end - - # Sets the time to wait when reading before raising an `IO::TimeoutError`. - def read_timeout=(timeout : Time::Span?) : ::Time::Span? - @read_timeout = timeout - end - - # Sets the number of seconds to wait when reading before raising an `IO::TimeoutError`. - def read_timeout=(read_timeout : Number) : Number - self.read_timeout = read_timeout.seconds - read_timeout - end - - # Returns the time to wait when writing before raising an `IO::TimeoutError`. - def write_timeout : Time::Span? - @write_timeout - end - - # Sets the time to wait when writing before raising an `IO::TimeoutError`. - def write_timeout=(timeout : Time::Span?) : ::Time::Span? - @write_timeout = timeout - end - - # Sets the number of seconds to wait when writing before raising an `IO::TimeoutError`. - def write_timeout=(write_timeout : Number) : Number - self.write_timeout = write_timeout.seconds - write_timeout - end - def self.wait_queued_completions(timeout, &) overlapped_entries = uninitialized LibC::OVERLAPPED_ENTRY[1] diff --git a/src/socket.cr b/src/socket.cr index 32a552fa34d4..068c4a33c638 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -23,6 +23,24 @@ class Socket < IO getter type : Type getter protocol : Protocol + # The time to wait when reading before raising an `IO::TimeoutError`. + property read_timeout : Time::Span? + + # Sets the number of seconds to wait when reading before raising an `IO::TimeoutError`. + def read_timeout=(read_timeout : Number) : Number + self.read_timeout = read_timeout.seconds + read_timeout + end + + # Sets the time to wait when writing before raising an `IO::TimeoutError`. + property write_timeout : Time::Span? + + # Sets the number of seconds to wait when writing before raising an `IO::TimeoutError`. + def write_timeout=(write_timeout : Number) : Number + self.write_timeout = write_timeout.seconds + write_timeout + end + # Creates a TCP socket. Consider using `TCPSocket` or `TCPServer` unless you # need full control over the socket. def self.tcp(family : Family, blocking = false) : self From c4542a66754caa8820e29b56f21fb3579cfb4d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 19 Mar 2024 12:44:14 +0100 Subject: [PATCH 1004/1551] Add `shard.yml` (#14365) Co-authored-by: Sijawusz Pur Rahnama --- lib/.shards.info | 9 +++++++++ lib/markd/.github/trafico.yml | 5 +++++ lib/markd/lib | 1 + lib/reply/CHANGELOG.md | 11 ++++++++++- lib/reply/lib | 1 + lib/reply/shard.yml | 2 +- lib/reply/spec/history_spec.cr | 2 +- lib/reply/src/reply.cr | 2 +- shard.lock | 10 ++++++++++ shard.yml | 24 ++++++++++++++++++++++++ 10 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 lib/.shards.info create mode 100644 lib/markd/.github/trafico.yml create mode 120000 lib/markd/lib create mode 120000 lib/reply/lib create mode 100644 shard.lock create mode 100644 shard.yml diff --git a/lib/.shards.info b/lib/.shards.info new file mode 100644 index 000000000000..393132512399 --- /dev/null +++ b/lib/.shards.info @@ -0,0 +1,9 @@ +--- +version: 1.0 +shards: + markd: + git: https://github.com/icyleaf/markd.git + version: 0.4.2+git.commit.5e5a75d13bfdc615f04cc7ab166ee279b3b996d3 + reply: + git: https://github.com/i3oris/reply.git + version: 0.3.1+git.commit.90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 diff --git a/lib/markd/.github/trafico.yml b/lib/markd/.github/trafico.yml new file mode 100644 index 000000000000..04d1709709b4 --- /dev/null +++ b/lib/markd/.github/trafico.yml @@ -0,0 +1,5 @@ +addWipLabel: true +reviewers: + icyleaf: + name: "icyleaf" + color: "#000000" diff --git a/lib/markd/lib b/lib/markd/lib new file mode 120000 index 000000000000..a96aa0ea9d8c --- /dev/null +++ b/lib/markd/lib @@ -0,0 +1 @@ +.. \ No newline at end of file diff --git a/lib/reply/CHANGELOG.md b/lib/reply/CHANGELOG.md index cce13877b562..e43f1433c4a8 100644 --- a/lib/reply/CHANGELOG.md +++ b/lib/reply/CHANGELOG.md @@ -1,3 +1,13 @@ +## RELPy v0.3.1 + +### Bug fixs +* Fix `REPLy` on Mac, caused by a wrong implementation of `ioctl`. Remake entirely the implementation of `Reply::Term::Size` in a more portable way. Inspired by (https://github.com/crystal-term/screen/blob/master/src/term-screen.cr.) + +### Internals +* Compute the term-size only once for each input. Fix slow performance when the size is taken from `tput` (if `ioctl` fails). +* Fix spec on windows due to '\n\r'. +* Fix typo ('p' was duplicate in 'dupplicate'). + ## RELPy v0.3.0 ### New features @@ -47,7 +57,6 @@ that becomes 'ctrl-enter' on windows. * Refactor: move word functions (`delete_word`, `move_word_forward`, etc.) from `Reader` to the `ExpressionEditor`. * Add this CHANGELOG. - ## RELPy v0.1.0 First version extracted from IC. diff --git a/lib/reply/lib b/lib/reply/lib new file mode 120000 index 000000000000..a96aa0ea9d8c --- /dev/null +++ b/lib/reply/lib @@ -0,0 +1 @@ +.. \ No newline at end of file diff --git a/lib/reply/shard.yml b/lib/reply/shard.yml index ae9551f95bdb..e6cd9dab283a 100644 --- a/lib/reply/shard.yml +++ b/lib/reply/shard.yml @@ -1,5 +1,5 @@ name: reply -version: 0.3.0 +version: 0.3.1 description: "Shard to create a REPL interface" authors: diff --git a/lib/reply/spec/history_spec.cr b/lib/reply/spec/history_spec.cr index d553ea057e42..eddfb46d710c 100644 --- a/lib/reply/spec/history_spec.cr +++ b/lib/reply/spec/history_spec.cr @@ -33,7 +33,7 @@ module Reply history.verify(ENTRIES, index: 3) end - it "submit dupplicate entry" do + it "submit duplicate entry" do history = SpecHelper.history(with: ENTRIES) history.verify(ENTRIES, index: 3) diff --git a/lib/reply/src/reply.cr b/lib/reply/src/reply.cr index fa143a91deaa..08cd80fb936e 100644 --- a/lib/reply/src/reply.cr +++ b/lib/reply/src/reply.cr @@ -1,5 +1,5 @@ require "./reader" module Reply - VERSION = "0.3.0" + VERSION = "0.3.1" end diff --git a/shard.lock b/shard.lock new file mode 100644 index 000000000000..c0d828c5acf8 --- /dev/null +++ b/shard.lock @@ -0,0 +1,10 @@ +version: 2.0 +shards: + markd: + git: https://github.com/icyleaf/markd.git + version: 0.4.2+git.commit.5e5a75d13bfdc615f04cc7ab166ee279b3b996d3 + + reply: + git: https://github.com/i3oris/reply.git + version: 0.3.1+git.commit.90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 + diff --git a/shard.yml b/shard.yml new file mode 100644 index 000000000000..0220ec13c7b6 --- /dev/null +++ b/shard.yml @@ -0,0 +1,24 @@ +name: crystal +version: 1.12.0-dev + +authors: +- Crystal Core Team + +description: | + The Crystal standard library and compiler. + +crystal: ">= 1.0" + +dependencies: + markd: + github: icyleaf/markd + commit: 5e5a75d13bfdc615f04cc7ab166ee279b3b996d3 + reply: + github: I3oris/reply + commit: 90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 + +license: Apache-2.0 + +repository: https://github.com/crystal-lang/crystal +homepage: https://crystal-lang.org/ +documentation: https://crystal-lang.org/docs From f8c39f628f52763adf9d055c343239f5e485d7dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 20 Mar 2024 11:00:26 +0100 Subject: [PATCH 1005/1551] Deprecate timeout setters with `Number` arguments (#14372) --- src/io/file_descriptor.cr | 2 ++ src/socket.cr | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 14eabd31a6af..f725caeffeba 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -22,6 +22,7 @@ class IO::FileDescriptor < IO property read_timeout : Time::Span? # Sets the number of seconds to wait when reading before raising an `IO::TimeoutError`. + @[Deprecated("Use `#read_timeout=(Time::Span?)` instead.")] def read_timeout=(read_timeout : Number) : Number self.read_timeout = read_timeout.seconds read_timeout @@ -31,6 +32,7 @@ class IO::FileDescriptor < IO property write_timeout : Time::Span? # Sets the number of seconds to wait when writing before raising an `IO::TimeoutError`. + @[Deprecated("Use `#write_timeout=(Time::Span?)` instead.")] def write_timeout=(write_timeout : Number) : Number self.write_timeout = write_timeout.seconds write_timeout diff --git a/src/socket.cr b/src/socket.cr index 068c4a33c638..bce572624743 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -27,6 +27,7 @@ class Socket < IO property read_timeout : Time::Span? # Sets the number of seconds to wait when reading before raising an `IO::TimeoutError`. + @[Deprecated("Use `#read_timeout=(Time::Span?)` instead.")] def read_timeout=(read_timeout : Number) : Number self.read_timeout = read_timeout.seconds read_timeout @@ -36,6 +37,7 @@ class Socket < IO property write_timeout : Time::Span? # Sets the number of seconds to wait when writing before raising an `IO::TimeoutError`. + @[Deprecated("Use `#write_timeout=(Time::Span?)` instead.")] def write_timeout=(write_timeout : Number) : Number self.write_timeout = write_timeout.seconds write_timeout From 2f143fbfecddb1d9f282646c3040c5a0c9d1d6d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 20 Mar 2024 11:00:40 +0100 Subject: [PATCH 1006/1551] Refactor `HTTP::Client` timeout ivars to `Time::Span` (#14371) --- src/http/client.cr | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/http/client.cr b/src/http/client.cr index 41bfa70e68ed..b641065ac930 100644 --- a/src/http/client.cr +++ b/src/http/client.cr @@ -119,10 +119,6 @@ class HTTP::Client property? compress : Bool = true @io : IO? - @dns_timeout : Float64? - @connect_timeout : Float64? - @read_timeout : Float64? - @write_timeout : Float64? @reconnect = true # Creates a new HTTP client with the given *host*, *port* and *tls* @@ -283,8 +279,9 @@ class HTTP::Client # puts "Timeout!" # end # ``` + @[Deprecated("Use `#read_timeout=(Time::Span)` instead")] def read_timeout=(read_timeout : Number) - @read_timeout = read_timeout.to_f + self.read_timeout = read_timeout.seconds end # Sets the read timeout with a `Time::Span`, to wait when reading before raising an `IO::TimeoutError`. @@ -300,21 +297,18 @@ class HTTP::Client # puts "Timeout!" # end # ``` - def read_timeout=(read_timeout : Time::Span) - self.read_timeout = read_timeout.total_seconds - end + setter read_timeout : Time::Span? # Sets the write timeout - if any chunk of request is not written # within the number of seconds provided, `IO::TimeoutError` exception is raised. + @[Deprecated("Use `#write_timeout=(Time::Span)` instead")] def write_timeout=(write_timeout : Number) - @write_timeout = write_timeout.to_f + self.write_timeout = write_timeout.seconds end # Sets the write timeout - if any chunk of request is not written # within the provided `Time::Span`, `IO::TimeoutError` exception is raised. - def write_timeout=(write_timeout : Time::Span) - self.write_timeout = write_timeout.total_seconds - end + setter write_timeout : Time::Span? # Sets the number of seconds to wait when connecting, before raising an `IO::TimeoutError`. # @@ -329,8 +323,9 @@ class HTTP::Client # puts "Timeout!" # end # ``` + @[Deprecated("Use `#connect_timeout=(Time::Span)` instead")] def connect_timeout=(connect_timeout : Number) - @connect_timeout = connect_timeout.to_f + self.connect_timeout = connect_timeout.seconds end # Sets the open timeout with a `Time::Span` to wait when connecting, before raising an `IO::TimeoutError`. @@ -346,9 +341,7 @@ class HTTP::Client # puts "Timeout!" # end # ``` - def connect_timeout=(connect_timeout : Time::Span) - self.connect_timeout = connect_timeout.total_seconds - end + setter connect_timeout : Time::Span? # **This method has no effect right now** # @@ -365,8 +358,9 @@ class HTTP::Client # puts "Timeout!" # end # ``` + @[Deprecated("Use `#dns_timeout=(Time::Span)` instead")] def dns_timeout=(dns_timeout : Number) - @dns_timeout = dns_timeout.to_f + self.dns_timeout = dns_timeout.seconds end # **This method has no effect right now** @@ -384,9 +378,7 @@ class HTTP::Client # puts "Timeout!" # end # ``` - def dns_timeout=(dns_timeout : Time::Span) - self.dns_timeout = dns_timeout.total_seconds - end + setter dns_timeout : Time::Span? # Adds a callback to execute before each request. This is usually # used to set an authorization header. Any number of callbacks From a1a4dd0fb96c9a2dca6a814bad04a96f766927e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 21 Mar 2024 12:14:13 +0100 Subject: [PATCH 1007/1551] Update vendored dependencies (#14373) --- lib/.shards.info | 2 +- lib/markd/CHANGELOG.md | 28 +- lib/markd/README.md | 25 +- lib/markd/shard.yml | 2 +- lib/markd/spec/fixtures/regression.txt | 72 +- lib/markd/spec/fixtures/spec.txt | 647 ++++++++++++++---- lib/markd/spec/markd_spec.cr | 6 +- lib/markd/spec/spec_helper.cr | 4 +- lib/markd/src/markd/node.cr | 1 + lib/markd/src/markd/parsers/inline.cr | 66 +- lib/markd/src/markd/renderer.cr | 2 +- .../src/markd/renderers/html_renderer.cr | 4 +- lib/markd/src/markd/rule.cr | 9 +- lib/markd/src/markd/rules/code_block.cr | 5 +- lib/markd/src/markd/rules/heading.cr | 11 +- lib/markd/src/markd/rules/list.cr | 11 +- lib/markd/src/markd/version.cr | 2 +- shard.lock | 2 +- shard.yml | 1 - 19 files changed, 679 insertions(+), 221 deletions(-) diff --git a/lib/.shards.info b/lib/.shards.info index 393132512399..7f03bb906410 100644 --- a/lib/.shards.info +++ b/lib/.shards.info @@ -3,7 +3,7 @@ version: 1.0 shards: markd: git: https://github.com/icyleaf/markd.git - version: 0.4.2+git.commit.5e5a75d13bfdc615f04cc7ab166ee279b3b996d3 + version: 0.5.0 reply: git: https://github.com/i3oris/reply.git version: 0.3.1+git.commit.90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 diff --git a/lib/markd/CHANGELOG.md b/lib/markd/CHANGELOG.md index e2d4626f0a9a..0f4f28cbb3a7 100644 --- a/lib/markd/CHANGELOG.md +++ b/lib/markd/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - GFM support +## [0.5.0] (2022-06-14) + +- Support CommonMark 0.29 #[50](https://github.com/icyleaf/markd/pull/50) thanks @[HertzDevil](https://github.com/HertzDevil). +- Fix typos #[47](https://github.com/icyleaf/markd/pull/47) #[49](https://github.com/icyleaf/markd/pull/49) thanks @[kianmeng](https://github.com/kianmeng), @[jsoref](https://github.com/jsoref). + ## [0.4.2] (2021-10-19) ### Added @@ -60,23 +65,24 @@ No changelog. ## [0.1.2] (2019-08-26) -- Use Crystal v0.31.0 as default complier. +- Use Crystal v0.31.0 as default compiler. ## [0.1.1] (2017-12-26) - Minor refactoring and improving speed. thanks @[straight-shoota](https://github.com/straight-shoota). -- Use Crystal v0.24.1 as default complier. +- Use Crystal v0.24.1 as default compiler. ## 0.1.0 (2017-09-22) - [initial implementation](https://github.com/icyleaf/markd/milestone/1?closed=1) -[Unreleased]: https://github.com/icyleaf/markd/compare/v0.4.2...HEAD -[0.4.2]: https://github.com/icyleaf/halite/compare/v0.4.1...v0.4.2 -[0.4.1]: https://github.com/icyleaf/halite/compare/v0.4.0...v0.4.1 -[0.4.0]: https://github.com/icyleaf/halite/compare/v0.3.0...v0.4.0 -[0.3.0]: https://github.com/icyleaf/halite/compare/v0.2.1...v0.3.0 -[0.2.1]: https://github.com/icyleaf/halite/compare/v0.2.0...v0.2.1 -[0.2.0]: https://github.com/icyleaf/halite/compare/v0.1.2...v0.2.0 -[0.1.2]: https://github.com/icyleaf/halite/compare/v0.1.1...v0.1.2 -[0.1.1]: https://github.com/icyleaf/halite/compare/v0.1.0...v0.1.1 +[Unreleased]: https://github.com/icyleaf/markd/compare/v0.5.0...HEAD +[0.5.0]: https://github.com/icyleaf/markd/compare/v0.4.2...v0.5.0 +[0.4.2]: https://github.com/icyleaf/markd/compare/v0.4.1...v0.4.2 +[0.4.1]: https://github.com/icyleaf/markd/compare/v0.4.0...v0.4.1 +[0.4.0]: https://github.com/icyleaf/markd/compare/v0.3.0...v0.4.0 +[0.3.0]: https://github.com/icyleaf/markd/compare/v0.2.1...v0.3.0 +[0.2.1]: https://github.com/icyleaf/markd/compare/v0.2.0...v0.2.1 +[0.2.0]: https://github.com/icyleaf/markd/compare/v0.1.2...v0.2.0 +[0.1.2]: https://github.com/icyleaf/markd/compare/v0.1.1...v0.1.2 +[0.1.1]: https://github.com/icyleaf/markd/compare/v0.1.0...v0.1.1 diff --git a/lib/markd/README.md b/lib/markd/README.md index ac31e460c625..07014f6b5e06 100644 --- a/lib/markd/README.md +++ b/lib/markd/README.md @@ -4,7 +4,16 @@ [![Tag](https://img.shields.io/github/tag/icyleaf/markd.svg)](https://github.com/icyleaf/markd/blob/master/CHANGELOG.md) [![Build Status](https://img.shields.io/circleci/project/github/icyleaf/markd/master.svg?style=flat)](https://circleci.com/gh/icyleaf/markd) -Yet another markdown parser built for speed, written in [Crystal](https://crystal-lang.org), Compliant to [CommonMark](http://spec.commonmark.org) specification (`v0.27`). Copy from [commonmark.js](https://github.com/jgm/commonmark.js). + +**THIS PROJECT IS LOOKING FOR MAINTAINER** + +Unfortunately, the maintainer no longer has the time and/or resources to work on markd further. This means that bugs will not be fixed and features will not be added unless someone else does so. + +If you're interested in fixing up markd, please [file an issue](https://github.com/icyleaf/markd/issues/new) let me know. + +
    + +Yet another markdown parser built for speed, written in [Crystal](https://crystal-lang.org), Compliant to [CommonMark](http://spec.commonmark.org) specification (`v0.29`). Copy from [commonmark.js](https://github.com/jgm/commonmark.js). ## Installation @@ -73,25 +82,15 @@ html = renderer.render(document) ## Performance -First of all, Markd is slower than [Crystal Built-in Markdown](https://crystal-lang.org/api/0.23.0/Markdown.html) which it is a lite version, only apply for generte Cystal documents ([#4496](https://github.com/crystal-lang/crystal/pull/4496), [#4613](https://github.com/crystal-lang/crystal/issues/4613)). - Here is the result of [a sample markdown file](benchmarks/source.md) parse at MacBook Pro Retina 2015 (2.2 GHz): ``` -Crystal Markdown 3.28k (305.29µs) (± 0.92%) fastest - Markd 305.36 ( 3.27ms) (± 5.52%) 10.73× slower +Crystal Markdown (no longer present) 3.28k (305.29µs) (± 0.92%) fastest + Markd 305.36 ( 3.27ms) (± 5.52%) 10.73× slower ``` Recently, I'm working to compare the other popular commonmark parser, the code is stored in [benchmarks](/benchmarks). -## Donate - -Markd is an open source, collaboratively funded project. If you run a business and are using Markd in a revenue-generating product, -it would make business sense to sponsor Markd development. Individual users are also welcome to make a one time donation -if Markd has helped you in your work or personal projects. - -You can donate via [Paypal](https://www.paypal.me/icyleaf/5). - ## How to Contribute Your contributions are always welcome! Please submit a pull request or create an issue to add a new question, bug or feature to the list. diff --git a/lib/markd/shard.yml b/lib/markd/shard.yml index 005d2c74ac63..b6c88716bd89 100644 --- a/lib/markd/shard.yml +++ b/lib/markd/shard.yml @@ -1,5 +1,5 @@ name: markd -version: 0.4.2 +version: 0.5.0 authors: - icyleaf diff --git a/lib/markd/spec/fixtures/regression.txt b/lib/markd/spec/fixtures/regression.txt index dd1496648009..a99620bb1231 100644 --- a/lib/markd/spec/fixtures/regression.txt +++ b/lib/markd/spec/fixtures/regression.txt @@ -15,10 +15,10 @@ bar Type 7 HTML block followed by whitespace (#98). ```````````````````````````````` example - + x . - + x ```````````````````````````````` @@ -79,3 +79,71 @@ Issue jgm/CommonMark#468 - backslash at end of link definition .

    []: test

    ```````````````````````````````` + +Issue jgm/commonmark.js#121 - punctuation set different + +```````````````````````````````` example +^_test_ +. +

    ^test

    +```````````````````````````````` + +Issue #116 - tabs before and after ATX closing heading +```````````````````````````````` example +# foo→#→ +. +

    foo

    +```````````````````````````````` + +commonmark/CommonMark#493 - escaped space not allowed in link destination. + +```````````````````````````````` example +[link](a\ b) +. +

    [link](a\ b)

    +```````````````````````````````` + +Issue #527 - meta tags in inline contexts + +```````````````````````````````` example +City: + + + +. +

    City: + + +

    +```````````````````````````````` + +Double-encoding. + +```````````````````````````````` example +[XSS](javascript&colon;alert%28'XSS'%29) +. +

    XSS

    +```````````````````````````````` + +Issue commonamrk#517 - script, pre, style close tag without +opener. + +```````````````````````````````` example + + + + + +. + + + +```````````````````````````````` + +Issue #289. + +```````````````````````````````` example +[a]( +. +

    [a](<b) c>

    +```````````````````````````````` diff --git a/lib/markd/spec/fixtures/spec.txt b/lib/markd/spec/fixtures/spec.txt index 857e92c32d3b..3913de4424d8 100644 --- a/lib/markd/spec/fixtures/spec.txt +++ b/lib/markd/spec/fixtures/spec.txt @@ -1,8 +1,8 @@ --- title: CommonMark Spec author: John MacFarlane -version: 0.27 -date: '2016-11-18' +version: 0.29 +date: '2019-04-06' license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' ... @@ -11,10 +11,12 @@ license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' ## What is Markdown? Markdown is a plain text format for writing structured documents, -based on conventions used for indicating formatting in email and -usenet posts. It was developed in 2004 by John Gruber, who wrote -the first Markdown-to-HTML converter in Perl, and it soon became -ubiquitous. In the next decade, dozens of implementations were +based on conventions for indicating formatting in email +and usenet posts. It was developed by John Gruber (with +help from Aaron Swartz) and released in 2004 in the form of a +[syntax description](http://daringfireball.net/projects/markdown/syntax) +and a Perl script (`Markdown.pl`) for converting Markdown to +HTML. In the next decade, dozens of implementations were developed in many languages. Some extended the original Markdown syntax with conventions for footnotes, tables, and other document elements. Some allowed Markdown documents to be @@ -246,7 +248,7 @@ satisfactory replacement for a spec. Because there is no unambiguous spec, implementations have diverged considerably. As a result, users are often surprised to find that -a document that renders one way on one system (say, a github wiki) +a document that renders one way on one system (say, a GitHub wiki) renders differently on another (say, converting to docbook using pandoc). To make matters worse, because nothing in Markdown counts as a "syntax error," the divergence often isn't discovered right away. @@ -312,7 +314,7 @@ form feed (`U+000C`), or carriage return (`U+000D`). characters]. A [Unicode whitespace character](@) is -any code point in the Unicode `Zs` class, or a tab (`U+0009`), +any code point in the Unicode `Zs` general category, or a tab (`U+0009`), carriage return (`U+000D`), newline (`U+000A`), or form feed (`U+000C`). @@ -326,12 +328,14 @@ that is not a [whitespace character]. An [ASCII punctuation character](@) is `!`, `"`, `#`, `$`, `%`, `&`, `'`, `(`, `)`, -`*`, `+`, `,`, `-`, `.`, `/`, `:`, `;`, `<`, `=`, `>`, `?`, `@`, -`[`, `\`, `]`, `^`, `_`, `` ` ``, `{`, `|`, `}`, or `~`. +`*`, `+`, `,`, `-`, `.`, `/` (U+0021–2F), +`:`, `;`, `<`, `=`, `>`, `?`, `@` (U+003A–0040), +`[`, `\`, `]`, `^`, `_`, `` ` `` (U+005B–0060), +`{`, `|`, `}`, or `~` (U+007B–007E). A [punctuation character](@) is an [ASCII punctuation character] or anything in -the Unicode classes `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. +the general Unicode categories `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. ## Tabs @@ -402,8 +406,8 @@ as indentation with four spaces would: Normally the `>` that begins a block quote may be followed optionally by a space, which is not considered part of the content. In the following case `>` is followed by a tab, -which is treated as if it were expanded into spaces. -Since one of theses spaces is considered part of the +which is treated as if it were expanded into three spaces. +Since one of these spaces is considered part of the delimiter, `foo` is considered to be indented six spaces inside the block quote context, so we get an indented code block starting with two spaces. @@ -481,7 +485,7 @@ We can think of a document as a sequence of quotations, lists, headings, rules, and code blocks. Some blocks (like block quotes and list items) contain other blocks; others (like headings and paragraphs) contain [inline](@) content---text, -links, emphasized text, images, code, and so on. +links, emphasized text, images, code spans, and so on. ## Precedence @@ -512,8 +516,8 @@ one block element does not affect the inline parsing of any other. ## Container blocks and leaf blocks We can divide blocks into two types: -[container block](@)s, -which can contain other blocks, and [leaf block](@)s, +[container blocks](@), +which can contain other blocks, and [leaf blocks](@), which cannot. # Leaf blocks @@ -525,7 +529,7 @@ Markdown document. A line consisting of 0-3 spaces of indentation, followed by a sequence of three or more matching `-`, `_`, or `*` characters, each followed -optionally by any number of spaces, forms a +optionally by any number of spaces or tabs, forms a [thematic break](@). ```````````````````````````````` example @@ -823,7 +827,7 @@ Contents are parsed as inlines: ```````````````````````````````` -Leading and trailing blanks are ignored in parsing inline content: +Leading and trailing [whitespace] is ignored in parsing inline content: ```````````````````````````````` example # foo @@ -1022,6 +1026,20 @@ baz* baz

    ```````````````````````````````` +The contents are the result of parsing the headings's raw +content as inlines. The heading's raw content is formed by +concatenating the lines and removing initial and final +[whitespace]. + +```````````````````````````````` example + Foo *bar +baz*→ +==== +. +

    Foo bar +baz

    +```````````````````````````````` + The underlining can be any length: @@ -1582,8 +1600,8 @@ begins with a code fence, indented no more than three spaces. The line with the opening code fence may optionally contain some text following the code fence; this is trimmed of leading and trailing -spaces and called the [info string](@). -The [info string] may not contain any backtick +whitespace and called the [info string](@). If the [info string] comes +after a backtick fence, it may not contain any backtick characters. (The reason for this restriction is that otherwise some inline code would be incorrectly interpreted as the beginning of a fenced code block.) @@ -1643,6 +1661,15 @@ With tildes:
    ```````````````````````````````` +Fewer than three backticks is not enough: + +```````````````````````````````` example +`` +foo +`` +. +

    foo

    +```````````````````````````````` The closing code fence must use the same character as the opening fence: @@ -1859,7 +1886,7 @@ Code fences (opening and closing) cannot contain internal spaces: ``` ``` aaa . -

    +

    aaa

    ```````````````````````````````` @@ -1911,9 +1938,11 @@ bar An [info string] can be provided after the opening code fence. -Opening and closing spaces will be stripped, and the first word, prefixed -with `language-`, is used as the value for the `class` attribute of the -`code` element within the enclosing `pre` element. +Although this spec doesn't mandate any particular treatment of +the info string, the first word is typically used to specify +the language of the code block. In HTML output, the language is +normally indicated by adding a class to the `code` element consisting +of `language-` followed by the language name. ```````````````````````````````` example ```ruby @@ -1962,6 +1991,18 @@ foo

    ```````````````````````````````` +[Info strings] for tilde code blocks can contain backticks and tildes: + +```````````````````````````````` example +~~~ aa ``` ~~~ +foo +~~~ +. +
    foo
    +
    +```````````````````````````````` + + Closing code fences cannot have [info strings]: ```````````````````````````````` example @@ -1980,14 +2021,15 @@ Closing code fences cannot have [info strings]: An [HTML block](@) is a group of lines that is treated as raw HTML (and will not be escaped in HTML output). -There are seven kinds of [HTML block], which can be defined -by their start and end conditions. The block begins with a line that -meets a [start condition](@) (after up to three spaces -optional indentation). It ends with the first subsequent line that -meets a matching [end condition](@), or the last line of -the document or other [container block]), if no line is encountered that meets the -[end condition]. If the first line meets both the [start condition] -and the [end condition], the block will contain just that line. +There are seven kinds of [HTML block], which can be defined by their +start and end conditions. The block begins with a line that meets a +[start condition](@) (after up to three spaces optional indentation). +It ends with the first subsequent line that meets a matching [end +condition](@), or the last line of the document, or the last line of +the [container block](#container-blocks) containing the current HTML +block, if no line is encountered that meets the [end condition]. If +the first line meets both the [start condition] and the [end +condition], the block will contain just that line. 1. **Start condition:** line begins with the string ``, or @@ -2026,11 +2068,43 @@ the string `/>`.\ **End condition:** line is followed by a [blank line]. 7. **Start condition:** line begins with a complete [open tag] -or [closing tag] (with any [tag name] other than `script`, -`style`, or `pre`) followed only by [whitespace] -or the end of the line.\ +(with any [tag name] other than `script`, +`style`, or `pre`) or a complete [closing tag], +followed only by [whitespace] or the end of the line.\ **End condition:** line is followed by a [blank line]. +HTML blocks continue until they are closed by their appropriate +[end condition], or the last line of the document or other [container +block](#container-blocks). This means any HTML **within an HTML +block** that might otherwise be recognised as a start condition will +be ignored by the parser and passed through as-is, without changing +the parser's state. + +For instance, `
    ` within a HTML block started by `` will not affect
    +the parser state; as the HTML block was started in by start condition 6, it
    +will end at any blank line. This can be surprising:
    +
    +```````````````````````````````` example
    +
    +
    +**Hello**,
    +
    +_world_.
    +
    +
    +. +
    +
    +**Hello**,
    +

    world. +

    +
    +```````````````````````````````` + +In this case, the HTML block is terminated by the newline — the `**Hello**` +text remains verbatim — and regular parsing resumes, with a paragraph, +emphasised `world` and inline and block HTML following. + All types of [HTML blocks] except type 7 may interrupt a paragraph. Blocks of type 7 may not interrupt a paragraph. (This restriction is intended to prevent unwanted interpretation @@ -2570,7 +2644,8 @@ bar However, a following blank line is needed, except at the end of -a document, and except for blocks of types 1--5, above: +a document, and except for blocks of types 1--5, [above][HTML +block]: ```````````````````````````````` example
    @@ -2716,8 +2791,8 @@ an indented code block: Fortunately, blank lines are usually not necessary and can be deleted. The exception is inside `
    ` tags, but as described
    -above, raw HTML blocks starting with `
    ` *can* contain blank
    -lines.
    +[above][HTML blocks], raw HTML blocks starting with `
    `
    +*can* contain blank lines.
     
     ## Link reference definitions
     
    @@ -2769,7 +2844,7 @@ them.
     
     ```````````````````````````````` example
     [Foo bar]:
    -
    +
     'title'
     
     [Foo bar]
    @@ -2835,6 +2910,29 @@ The link destination may not be omitted:
     

    [foo]

    ```````````````````````````````` + However, an empty link destination may be specified using + angle brackets: + +```````````````````````````````` example +[foo]: <> + +[foo] +. +

    foo

    +```````````````````````````````` + +The title must be separated from the link destination by +whitespace: + +```````````````````````````````` example +[foo]: (baz) + +[foo] +. +

    [foo]: (baz)

    +

    [foo]

    +```````````````````````````````` + Both title and destination can contain backslash escapes and literal backslashes: @@ -2992,6 +3090,25 @@ and thematic breaks, and it need not be followed by a blank line. ```````````````````````````````` +```````````````````````````````` example +[foo]: /url +bar +=== +[foo] +. +

    bar

    +

    foo

    +```````````````````````````````` + +```````````````````````````````` example +[foo]: /url +=== +[foo] +. +

    === +foo

    +```````````````````````````````` + Several [link reference definitions] can occur one after another, without intervening blank lines. @@ -3028,6 +3145,17 @@ are defined: ```````````````````````````````` +Whether something is a [link reference definition] is +independent of whether the link reference it defines is +used in the document. Thus, for example, the following +document contains just a link reference definition, and +no visible content: + +```````````````````````````````` example +[foo]: /url +. +```````````````````````````````` + ## Paragraphs @@ -3165,7 +3293,7 @@ aaa # Container blocks -A [container block] is a block that has other +A [container block](#container-blocks) is a block that has other blocks as its contents. There are two basic kinds of container blocks: [block quotes] and [list items]. [Lists] are meta-containers for [list items]. @@ -3627,9 +3755,8 @@ in some browsers.) The following rules define [list items]: 1. **Basic case.** If a sequence of lines *Ls* constitute a sequence of - blocks *Bs* starting with a [non-whitespace character] and not separated - from each other by more than one blank line, and *M* is a list - marker of width *W* followed by 1 ≤ *N* ≤ 4 spaces, then the result + blocks *Bs* starting with a [non-whitespace character], and *M* is a + list marker of width *W* followed by 1 ≤ *N* ≤ 4 spaces, then the result of prepending *M* and the following spaces to the first line of *Ls*, and indenting subsequent lines of *Ls* by *W + N* spaces, is a list item with *Bs* as its contents. The type of the list item @@ -3637,11 +3764,15 @@ The following rules define [list items]: If the list item is ordered, then it is also assigned a start number, based on the ordered list marker. - Exceptions: When the first list item in a [list] interrupts - a paragraph---that is, when it starts on a line that would - otherwise count as [paragraph continuation text]---then (a) - the lines *Ls* must not begin with a blank line, and (b) if - the list item is ordered, the start number must be 1. + Exceptions: + + 1. When the first list item in a [list] interrupts + a paragraph---that is, when it starts on a line that would + otherwise count as [paragraph continuation text]---then (a) + the lines *Ls* must not begin with a blank line, and (b) if + the list item is ordered, the start number must be 1. + 2. If any line is a [thematic break][thematic breaks] then + that line is not a list item. For example, let *Ls* be the lines @@ -3935,8 +4066,7 @@ A start number may not be negative: 2. **Item starting with indented code.** If a sequence of lines *Ls* constitute a sequence of blocks *Bs* starting with an indented code - block and not separated from each other by more than one blank line, - and *M* is a list marker of width *W* followed by + block, and *M* is a list marker of width *W* followed by one space, then the result of prepending *M* and the following space to the first line of *Ls*, and indenting subsequent lines of *Ls* by *W + 1* spaces, is a list item with *Bs* as its contents. @@ -4412,9 +4542,10 @@ continued here.

    6. **That's all.** Nothing that is not counted as a list item by rules #1--5 counts as a [list item](#list-items). -The rules for sublists follow from the general rules above. A sublist -must be indented the same number of spaces a paragraph would need to be -in order to be included in the list item. +The rules for sublists follow from the general rules +[above][List items]. A sublist must be indented the same number +of spaces a paragraph would need to be in order to be included +in the list item. So, in this case we need two spaces indent: @@ -5003,11 +5134,9 @@ item: - b - c - d - - e - - f - - g - - h -- i + - e + - f +- g .
    • a
    • @@ -5017,8 +5146,6 @@ item:
    • e
    • f
    • g
    • -
    • h
    • -
    • i
    ```````````````````````````````` @@ -5028,7 +5155,7 @@ item: 2. b - 3. c + 3. c .
    1. @@ -5043,6 +5170,49 @@ item:
    ```````````````````````````````` +Note, however, that list items may not be indented more than +three spaces. Here `- e` is treated as a paragraph continuation +line, because it is indented more than three spaces: + +```````````````````````````````` example +- a + - b + - c + - d + - e +. +
      +
    • a
    • +
    • b
    • +
    • c
    • +
    • d +- e
    • +
    +```````````````````````````````` + +And here, `3. c` is treated as in indented code block, +because it is indented four spaces and preceded by a +blank line. + +```````````````````````````````` example +1. a + + 2. b + + 3. c +. +
      +
    1. +

      a

      +
    2. +
    3. +

      b

      +
    4. +
    +
    3. c
    +
    +```````````````````````````````` + This is a loose list, because there is a blank line between two of the list items: @@ -5332,10 +5502,10 @@ Thus, for example, in

    hilo`

    ```````````````````````````````` - `hi` is parsed as code, leaving the backtick at the end as a literal backtick. + ## Backslash escapes Any ASCII punctuation character may be backslash-escaped: @@ -5369,6 +5539,7 @@ not have their usual Markdown meanings: \* not a list \# not a heading \[foo]: /url "not a reference" +\ö not a character entity .

    *not emphasized* <br/> not a tag @@ -5377,7 +5548,8 @@ not have their usual Markdown meanings: 1. not a list * not a list # not a heading -[foo]: /url "not a reference"

    +[foo]: /url "not a reference" +&ouml; not a character entity

    ```````````````````````````````` @@ -5475,13 +5647,23 @@ foo ## Entity and numeric character references -All valid HTML entity references and numeric character -references, except those occuring in code blocks and code spans, -are recognized as such and treated as equivalent to the -corresponding Unicode characters. Conforming CommonMark parsers -need not store information about whether a particular character -was represented in the source using a Unicode character or -an entity reference. +Valid HTML entity references and numeric character references +can be used in place of the corresponding Unicode character, +with the following exceptions: + +- Entity and character references are not recognized in code + blocks and code spans. + +- Entity and character references cannot stand in place of + special characters that define structural elements in + CommonMark. For example, although `*` can be used + in place of a literal `*` character, `*` cannot replace + `*` in emphasis delimiters, bullet list markers, or thematic + breaks. + +Conforming CommonMark parsers need not store information about +whether a particular character was represented in the source +using a Unicode character or an entity reference. [Entity references](@) consist of `&` + any of the valid HTML5 entity names + `;`. The @@ -5502,22 +5684,22 @@ references and their corresponding code points. [Decimal numeric character references](@) -consist of `&#` + a string of 1--8 arabic digits + `;`. A +consist of `&#` + a string of 1--7 arabic digits + `;`. A numeric character reference is parsed as the corresponding Unicode character. Invalid Unicode code points will be replaced by the REPLACEMENT CHARACTER (`U+FFFD`). For security reasons, the code point `U+0000` will also be replaced by `U+FFFD`. ```````````````````````````````` example -# Ӓ Ϡ � � +# Ӓ Ϡ � . -

    # Ӓ Ϡ � �

    +

    # Ӓ Ϡ �

    ```````````````````````````````` [Hexadecimal numeric character references](@) consist of `&#` + -either `X` or `x` + a string of 1-8 hexadecimal digits + `;`. +either `X` or `x` + a string of 1-6 hexadecimal digits + `;`. They too are parsed as the corresponding Unicode character (this time specified with a hexadecimal numeral instead of decimal). @@ -5532,9 +5714,13 @@ Here are some nonentities: ```````````````````````````````` example   &x; &#; &#x; +� +&#abcdef0; &ThisIsNotDefined; &hi?; .

    &nbsp &x; &#; &#x; +&#987654321; +&#abcdef0; &ThisIsNotDefined; &hi?;

    ```````````````````````````````` @@ -5615,6 +5801,51 @@ text in code spans and code blocks: ```````````````````````````````` +Entity and numeric character references cannot be used +in place of symbols indicating structure in CommonMark +documents. + +```````````````````````````````` example +*foo* +*foo* +. +

    *foo* +foo

    +```````````````````````````````` + +```````````````````````````````` example +* foo + +* foo +. +

    * foo

    +
      +
    • foo
    • +
    +```````````````````````````````` + +```````````````````````````````` example +foo bar +. +

    foo + +bar

    +```````````````````````````````` + +```````````````````````````````` example + foo +. +

    →foo

    +```````````````````````````````` + + +```````````````````````````````` example +[a](url "tit") +. +

    [a](url "tit")

    +```````````````````````````````` + + ## Code spans A [backtick string](@) @@ -5623,9 +5854,16 @@ preceded nor followed by a backtick. A [code span](@) begins with a backtick string and ends with a backtick string of equal length. The contents of the code span are -the characters between the two backtick strings, with leading and -trailing spaces and [line endings] removed, and -[whitespace] collapsed to single spaces. +the characters between the two backtick strings, normalized in the +following ways: + +- First, [line endings] are converted to [spaces]. +- If the resulting string both begins *and* ends with a [space] + character, but does not consist entirely of [space] + characters, a single [space] character is removed from the + front and back. This allows you to include code that begins + or ends with backtick characters, which must be separated by + whitespace from the opening or closing backtick strings. This is a simple code span: @@ -5637,10 +5875,11 @@ This is a simple code span: Here two backticks are used, because the code contains a backtick. -This example also illustrates stripping of leading and trailing spaces: +This example also illustrates stripping of a single leading and +trailing space: ```````````````````````````````` example -`` foo ` bar `` +`` foo ` bar `` .

    foo ` bar

    ```````````````````````````````` @@ -5655,58 +5894,79 @@ spaces:

    ``

    ```````````````````````````````` +Note that only *one* space is stripped: -[Line endings] are treated like spaces: +```````````````````````````````` example +` `` ` +. +

    ``

    +```````````````````````````````` + +The stripping only happens if the space is on both +sides of the string: ```````````````````````````````` example -`` -foo -`` +` a` . -

    foo

    +

    a

    ```````````````````````````````` +Only [spaces], and not [unicode whitespace] in general, are +stripped in this way: -Interior spaces and [line endings] are collapsed into -single spaces, just as they would be by a browser: +```````````````````````````````` example +` b ` +. +

     b 

    +```````````````````````````````` + +No stripping occurs if the code span contains only spaces: ```````````````````````````````` example -`foo bar - baz` +` ` +` ` . -

    foo bar baz

    +

      +

    ```````````````````````````````` -Not all [Unicode whitespace] (for instance, non-breaking space) is -collapsed, however: +[Line endings] are treated like spaces: ```````````````````````````````` example -`a  b` +`` +foo +bar +baz +`` . -

    a  b

    +

    foo bar baz

    ```````````````````````````````` +```````````````````````````````` example +`` +foo +`` +. +

    foo

    +```````````````````````````````` -Q: Why not just leave the spaces, since browsers will collapse them -anyway? A: Because we might be targeting a non-HTML format, and we -shouldn't rely on HTML-specific rendering assumptions. -(Existing implementations differ in their treatment of internal -spaces and [line endings]. Some, including `Markdown.pl` and -`showdown`, convert an internal [line ending] into a -`
    ` tag. But this makes things difficult for those who like to -hard-wrap their paragraphs, since a line break in the midst of a code -span will cause an unintended line break in the output. Others just -leave internal spaces as they are, which is fine if only HTML is being -targeted.) +Interior spaces are not collapsed: ```````````````````````````````` example -`foo `` bar` +`foo bar +baz` . -

    foo `` bar

    +

    foo bar baz

    ```````````````````````````````` +Note that browsers will typically collapse consecutive spaces +when rendering `` elements, so it is recommended that +the following CSS be used: + + code{white-space: pre-wrap;} + Note that backslash escapes do not work in code spans. All backslashes are treated literally: @@ -5722,6 +5982,19 @@ Backslash escapes are never needed, because one can always choose a string of *n* backtick characters as delimiters, where the code does not contain any strings of exactly *n* backtick characters. +```````````````````````````````` example +``foo`bar`` +. +

    foo`bar

    +```````````````````````````````` + +```````````````````````````````` example +` foo `` bar ` +. +

    foo `` bar

    +```````````````````````````````` + + Code span backticks have higher precedence than any other inline constructs except HTML tags and autolinks. Thus, for example, this is not parsed as emphasized text, since the second `*` is part of a code @@ -5796,6 +6069,15 @@ we just have literal backticks:

    `foo

    ```````````````````````````````` +The following case also illustrates the need for opening and +closing backtick strings to be equal in length: + +```````````````````````````````` example +`foo``bar`` +. +

    `foobar

    +```````````````````````````````` + ## Emphasis and strong emphasis @@ -5845,19 +6127,22 @@ for efficient parsing strategies that do not backtrack. First, some definitions. A [delimiter run](@) is either a sequence of one or more `*` characters that is not preceded or -followed by a `*` character, or a sequence of one or more `_` -characters that is not preceded or followed by a `_` character. +followed by a non-backslash-escaped `*` character, or a sequence +of one or more `_` characters that is not preceded or followed by +a non-backslash-escaped `_` character. A [left-flanking delimiter run](@) is -a [delimiter run] that is (a) not followed by [Unicode whitespace], -and (b) either not followed by a [punctuation character], or +a [delimiter run] that is (1) not followed by [Unicode whitespace], +and either (2a) not followed by a [punctuation character], or +(2b) followed by a [punctuation character] and preceded by [Unicode whitespace] or a [punctuation character]. For purposes of this definition, the beginning and the end of the line count as Unicode whitespace. A [right-flanking delimiter run](@) is -a [delimiter run] that is (a) not preceded by [Unicode whitespace], -and (b) either not preceded by a [punctuation character], or +a [delimiter run] that is (1) not preceded by [Unicode whitespace], +and either (2a) not preceded by a [punctuation character], or +(2b) preceded by a [punctuation character] and followed by [Unicode whitespace] or a [punctuation character]. For purposes of this definition, the beginning and the end of the line count as Unicode whitespace. @@ -5936,7 +6221,7 @@ The following rules define emphasis and strong emphasis: 7. A double `**` [can close strong emphasis](@) iff it is part of a [right-flanking delimiter run]. -8. A double `__` [can close strong emphasis] +8. A double `__` [can close strong emphasis] iff it is part of a [right-flanking delimiter run] and either (a) not part of a [left-flanking delimiter run] or (b) part of a [left-flanking delimiter run] @@ -5949,7 +6234,8 @@ The following rules define emphasis and strong emphasis: [delimiter runs]. If one of the delimiters can both open and close emphasis, then the sum of the lengths of the delimiter runs containing the opening and closing delimiters - must not be a multiple of 3. + must not be a multiple of 3 unless both lengths are + multiples of 3. 10. Strong emphasis begins with a delimiter that [can open strong emphasis] and ends with a delimiter that @@ -5959,7 +6245,8 @@ The following rules define emphasis and strong emphasis: [delimiter runs]. If one of the delimiters can both open and close strong emphasis, then the sum of the lengths of the delimiter runs containing the opening and closing - delimiters must not be a multiple of 3. + delimiters must not be a multiple of 3 unless both lengths + are multiples of 3. 11. A literal `*` character cannot occur at the beginning or end of `*`-delimited emphasis or `**`-delimited strong emphasis, unless it @@ -5977,7 +6264,7 @@ the following principles resolve ambiguity: `...`. 14. An interpretation `...` is always - preferred to `..`. + preferred to `...`. 15. When two potential emphasis or strong emphasis spans overlap, so that the second begins before the first ends and ends after @@ -6578,7 +6865,19 @@ is precluded by the condition that a delimiter that can both open and close (like the `*` after `foo`) cannot form emphasis if the sum of the lengths of the delimiter runs containing the opening and -closing delimiters is a multiple of 3. +closing delimiters is a multiple of 3 unless +both lengths are multiples of 3. + + +For the same reason, we don't get two consecutive +emphasis sections in this example: + +```````````````````````````````` example +*foo**bar* +. +

    foo**bar

    +```````````````````````````````` + The same condition ensures that the following cases are all strong emphasis nested inside @@ -6607,6 +6906,23 @@ omitted: ```````````````````````````````` +When the lengths of the interior closing and opening +delimiter runs are *both* multiples of 3, though, +they can match to create emphasis: + +```````````````````````````````` example +foo***bar***baz +. +

    foobarbaz

    +```````````````````````````````` + +```````````````````````````````` example +foo******bar*********baz +. +

    foobar***baz

    +```````````````````````````````` + + Indefinite levels of nesting are possible: ```````````````````````````````` example @@ -7142,13 +7458,16 @@ following rules apply: A [link destination](@) consists of either - a sequence of zero or more characters between an opening `<` and a - closing `>` that contains no spaces, line breaks, or unescaped + closing `>` that contains no line breaks or unescaped `<` or `>` characters, or -- a nonempty sequence of characters that does not include - ASCII space or control characters, and includes parentheses - only if (a) they are backslash-escaped or (b) they are part of - a balanced pair of unescaped parentheses. +- a nonempty sequence of characters that does not start with + `<`, does not include ASCII space or control characters, and + includes parentheses only if (a) they are backslash-escaped or + (b) they are part of a balanced pair of unescaped parentheses. + (Implementations may impose limits on parentheses nesting to + avoid performance issues, but at least three levels of nesting + should be supported.) A [link title](@) consists of either @@ -7161,7 +7480,8 @@ A [link title](@) consists of either backslash-escaped, or - a sequence of zero or more characters between matching parentheses - (`(...)`), including a `)` character only if it is backslash-escaped. + (`(...)`), including a `(` or `)` character only if it is + backslash-escaped. Although [link titles] may span multiple lines, they may not contain a [blank line]. @@ -7211,9 +7531,8 @@ Both the title and the destination may be omitted:

    link

    ```````````````````````````````` - -The destination cannot contain spaces or line breaks, -even if enclosed in pointy brackets: +The destination can only contain spaces if it is +enclosed in pointy brackets: ```````````````````````````````` example [link](/my uri) @@ -7221,13 +7540,14 @@ even if enclosed in pointy brackets:

    [link](/my uri)

    ```````````````````````````````` - ```````````````````````````````` example [link](
    ) . -

    [link](</my uri>)

    +

    link

    ```````````````````````````````` +The destination cannot contain line breaks, +even if enclosed in pointy brackets: ```````````````````````````````` example [link](foo @@ -7237,7 +7557,6 @@ bar) bar)

    ```````````````````````````````` - ```````````````````````````````` example [link]() @@ -7246,6 +7565,36 @@ bar>) bar>)

    ```````````````````````````````` +The destination can contain `)` if it is enclosed +in pointy brackets: + +```````````````````````````````` example +[a]() +. +

    a

    +```````````````````````````````` + +Pointy brackets that enclose links must be unescaped: + +```````````````````````````````` example +[link]() +. +

    [link](<foo>)

    +```````````````````````````````` + +These are not links, because the opening pointy bracket +is not matched properly: + +```````````````````````````````` example +[a]( +[a](c) +. +

    [a](<b)c +[a](<b)c> +[a](c)

    +```````````````````````````````` + Parentheses inside the link destination may be escaped: ```````````````````````````````` example @@ -7254,7 +7603,7 @@ Parentheses inside the link destination may be escaped:

    link

    ```````````````````````````````` -Any number parentheses are allowed without escaping, as long as they are +Any number of parentheses are allowed without escaping, as long as they are balanced: ```````````````````````````````` example @@ -7560,13 +7909,16 @@ that [matches] a [link reference definition] elsewhere in the document. A [link label](@) begins with a left bracket (`[`) and ends with the first right bracket (`]`) that is not backslash-escaped. Between these brackets there must be at least one [non-whitespace character]. -Unescaped square bracket characters are not allowed in -[link labels]. A link label can have at most 999 -characters inside the square brackets. +Unescaped square bracket characters are not allowed inside the +opening and closing square brackets of [link labels]. A link +label can have at most 999 characters inside the square +brackets. One label [matches](@) another just in case their normalized forms are equal. To normalize a -label, perform the *Unicode case fold* and collapse consecutive internal +label, strip off the opening and closing brackets, +perform the *Unicode case fold*, strip leading and trailing +[whitespace] and collapse consecutive internal [whitespace] to a single space. If there are multiple matching reference link definitions, the one that comes first in the document is used. (It is desirable in such cases to emit a warning.) @@ -8319,11 +8671,11 @@ The link labels are case-insensitive: ```````````````````````````````` -If you just want bracketed text, you can backslash-escape the -opening `!` and `[`: +If you just want a literal `!` followed by bracketed text, you can +backslash-escape the opening `[`: ```````````````````````````````` example -\!\[foo] +!\[foo] [foo]: /url "title" . @@ -8350,7 +8702,7 @@ If you want a link after a literal `!`, backslash-escape the as the link label. A [URI autolink](@) consists of `<`, followed by an -[absolute URI] not containing `<`, followed by `>`. It is parsed as +[absolute URI] followed by `>`. It is parsed as a link to the URI, with the URI as the link's label. An [absolute URI](@), @@ -8563,7 +8915,7 @@ a [single-quoted attribute value], or a [double-quoted attribute value]. An [unquoted attribute value](@) is a nonempty string of characters not -including spaces, `"`, `'`, `=`, `<`, `>`, or `` ` ``. +including [whitespace], `"`, `'`, `=`, `<`, `>`, or `` ` ``. A [single-quoted attribute value](@) consists of `'`, zero or more @@ -8684,9 +9036,13 @@ Illegal [whitespace]: ```````````````````````````````` example < a>< foo> + .

    < a>< -foo><bar/ >

    +foo><bar/ > +<foo bar=baz +bim!bop />

    ```````````````````````````````` @@ -8883,10 +9239,10 @@ bar

    Line breaks do not occur inside code spans ```````````````````````````````` example -`code +`code span` . -

    code span

    +

    code span

    ```````````````````````````````` @@ -9304,7 +9660,8 @@ just above `stack_bottom` (or the first element if `stack_bottom` is NULL). We keep track of the `openers_bottom` for each delimiter -type (`*`, `_`). Initialize this to `stack_bottom`. +type (`*`, `_`) and each length of the closing delimiter run +(modulo 3). Initialize this to `stack_bottom`. Then we repeat the following until we run out of potential closers: @@ -9336,7 +9693,7 @@ closers: of the delimiter stack. If the closing node is removed, reset `current_position` to the next element in the stack. -- If none in found: +- If none is found: + Set `openers_bottom` to the element before `current_position`. (We know that there are no openers for this kind of closer up to and diff --git a/lib/markd/spec/markd_spec.cr b/lib/markd/spec/markd_spec.cr index 3f4d2156478c..caf29fa0dd78 100644 --- a/lib/markd/spec/markd_spec.cr +++ b/lib/markd/spec/markd_spec.cr @@ -1,12 +1,12 @@ require "./spec_helper" -# Commonmark spec exapmles +# Commonmark spec examples describe_spec("fixtures/spec.txt") -# Smart punctuation exapmles +# Smart punctuation examples describe_spec("fixtures/smart_punct.txt", smart: true) -# Regression exapmles +# Regression examples describe_spec("fixtures/regression.txt") describe Markd do diff --git a/lib/markd/spec/spec_helper.cr b/lib/markd/spec/spec_helper.cr index 18287caf6ff1..06cff75dbe22 100644 --- a/lib/markd/spec/spec_helper.cr +++ b/lib/markd/spec/spec_helper.cr @@ -33,12 +33,12 @@ end def assert_section(file, section, examples, smart) describe section do examples.each do |index, example| - assert_exapmle(file, section, index, example, smart) + assert_example(file, section, index, example, smart) end end end -def assert_exapmle(file, section, index, example, smart) +def assert_example(file, section, index, example, smart) markdown = example["markdown"].gsub("→", "\t").chomp html = example["html"].gsub("→", "\t") line = example["line"].to_i diff --git a/lib/markd/src/markd/node.cr b/lib/markd/src/markd/node.cr index 06a68badf33a..1f4ee801da8a 100644 --- a/lib/markd/src/markd/node.cr +++ b/lib/markd/src/markd/node.cr @@ -59,6 +59,7 @@ module Markd property fence_length = 0 property fence_offset = 0 property? last_line_blank = false + property? last_line_checked = false property! parent : Node? property! first_child : Node? diff --git a/lib/markd/src/markd/parsers/inline.cr b/lib/markd/src/markd/parsers/inline.cr index fb2f1d28ccac..8088da91659c 100644 --- a/lib/markd/src/markd/parsers/inline.cr +++ b/lib/markd/src/markd/parsers/inline.cr @@ -123,7 +123,11 @@ module Markd::Parser while text = match(Rule::TICKS) if text.bytesize == num_ticks child = Node.new(Node::Type::Code) - child.text = @text.byte_slice(after_open_ticks, (@pos - num_ticks) - after_open_ticks).strip.gsub(Rule::WHITESPACE, " ") + child_text = @text.byte_slice(after_open_ticks, (@pos - num_ticks) - after_open_ticks).gsub(Rule::LINE_ENDING, " ") + if child_text.bytesize >= 2 && child_text[0] == ' ' && child_text[-1] == ' ' && child_text.matches?(/[^ ]/) + child_text = child_text.byte_slice(1, child_text.bytesize - 2) + end + child.text = child_text node.append_child(child) return true @@ -222,12 +226,12 @@ module Markd::Parser before_label = @pos label_size = link_label if label_size > 2 - ref_label = normalize_refernence(@text.byte_slice(before_label, label_size + 1)) + ref_label = normalize_reference(@text.byte_slice(before_label, label_size + 1)) elsif !opener.bracket_after # Empty or missing second label means to use the first label as the reference. # The reference must not contain a bracket. If we know there's a bracket, we don't even bother checking it. byte_count = start_pos - opener.index - ref_label = byte_count > 0 ? normalize_refernence(@text.byte_slice(opener.index, byte_count)) : nil + ref_label = byte_count > 0 ? normalize_reference(@text.byte_slice(opener.index, byte_count)) : nil end if label_size == 0 @@ -246,7 +250,7 @@ module Markd::Parser if matched child = Node.new(is_image ? Node::Type::Image : Node::Type::Link) - child.data["destination"] = dest + child.data["destination"] = dest.not_nil! child.data["title"] = title || "" tmp = opener.node.next? @@ -309,6 +313,7 @@ module Markd::Parser opener_found = false while opener && opener != delimiter && opener != openers_bottom[closer_char] odd_match = (closer.can_open || opener.can_close) && + closer.orig_delims % 3 != 0 && (opener.orig_delims + closer.orig_delims) % 3 == 0 if opener.char == closer.char && opener.can_open && !odd_match opener_found = true @@ -420,24 +425,29 @@ module Markd::Parser private def entity(node : Node) if char_at?(@pos) == '&' - pos = @pos + 1 - loop do - char = char_at?(pos) - pos += 1 - case char - when ';' - break - when Char::ZERO, nil - return false - else - nil + if char_at?(@pos + 1) == '#' + text = match(Rule::NUMERIC_HTML_ENTITY) || return false + text = text.byte_slice(1, text.bytesize - 2) + else + pos = @pos + 1 + loop do + char = char_at?(pos) + pos += 1 + case char + when ';' + break + when Char::ZERO, nil + return false + else + nil + end end + text = @text.byte_slice((@pos + 1), (pos - 1) - (@pos + 1)) + @pos = pos end - text = @text.byte_slice((@pos + 1), (pos - 1) - (@pos + 1)) - decoded_text = HTML.decode_entity text + decoded_text = HTML.decode_entity text node.append_child(text(decoded_text)) - @pos = pos true else false @@ -504,14 +514,14 @@ module Markd::Parser private def link_destination dest = if text = match(Rule::LINK_DESTINATION_BRACES) text[1..-2] - else + elsif char_at?(@pos) != '<' save_pos = @pos open_parens = 0 while char = char_at?(@pos) case char when '\\' @pos += 1 - @pos += 1 if char_at?(@pos) + match(Rule::ESCAPABLE) when '(' @pos += 1 open_parens += 1 @@ -530,7 +540,7 @@ module Markd::Parser @text.byte_slice(save_pos, @pos - save_pos) end - normalize_uri(Utils.decode_entities_string(dest)) + normalize_uri(Utils.decode_entities_string(dest)) if dest end private def handle_delim(char : Char, node : Node) @@ -655,16 +665,20 @@ module Markd::Parser # link url spnl + save_pos = @pos dest = link_destination - if dest.size == 0 + if !dest || (dest.size == 0 && !(@pos == save_pos + 2 && @text.byte_slice(save_pos, 2) == "<>")) @pos = startpos return 0 end before_title = @pos spnl - title = link_title + if @pos != before_title + title = link_title + end + unless title title = "" @pos = before_title @@ -686,7 +700,7 @@ module Markd::Parser return 0 end - normal_label = normalize_refernence(raw_label) + normal_label = normalize_reference(raw_label) if normal_label.empty? @pos = startpos return 0 @@ -794,11 +808,11 @@ module Markd::Parser # Normalize reference label: collapse internal whitespace # to single space, remove leading/trailing whitespace, case fold. - def normalize_refernence(text : String) + def normalize_reference(text : String) text[1..-2].strip.downcase.gsub("\n", " ") end - private RESERVED_CHARS = ['&', '+', ',', '(', ')', '#', '*', '!', '#', '$', '/', ':', ';', '?', '@', '='] + private RESERVED_CHARS = ['&', '+', ',', '(', ')', '\'', '#', '*', '!', '#', '$', '/', ':', ';', '?', '@', '='] def normalize_uri(uri : String) String.build(capacity: uri.bytesize) do |io| diff --git a/lib/markd/src/markd/renderer.cr b/lib/markd/src/markd/renderer.cr index 7e65ec1c5ba4..9abcdb932b6a 100644 --- a/lib/markd/src/markd/renderer.cr +++ b/lib/markd/src/markd/renderer.cr @@ -50,7 +50,7 @@ module Markd end def render(document : Node) - Utils.timer("renderering", @options.time) do + Utils.timer("rendering", @options.time) do walker = document.walker while event = walker.next node, entering = event diff --git a/lib/markd/src/markd/renderers/html_renderer.cr b/lib/markd/src/markd/renderers/html_renderer.cr index 334e0d2b5c42..f1149fe58217 100644 --- a/lib/markd/src/markd/renderers/html_renderer.cr +++ b/lib/markd/src/markd/renderers/html_renderer.cr @@ -173,8 +173,8 @@ module Markd end def paragraph(node : Node, entering : Bool) - if (grand_parant = node.parent?.try &.parent?) && grand_parant.type.list? - return if grand_parant.data["tight"] + if (grand_parent = node.parent?.try &.parent?) && grand_parent.type.list? + return if grand_parent.data["tight"] end if entering diff --git a/lib/markd/src/markd/rule.cr b/lib/markd/src/markd/rule.cr index 0e1611643d30..d8ef98cfd1ee 100644 --- a/lib/markd/src/markd/rule.cr +++ b/lib/markd/src/markd/rule.cr @@ -3,6 +3,8 @@ module Markd ESCAPABLE_STRING = %Q([!"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]) ESCAPED_CHAR_STRING = %Q(\\\\) + ESCAPABLE_STRING + NUMERIC_HTML_ENTITY = /^&#(?:[Xx][0-9a-fA-F]{1,6}|[0-9]{1,7});/ + TAG_NAME_STRING = %Q([A-Za-z][A-Za-z0-9-]*) ATTRIBUTE_NAME_STRING = %Q([a-zA-Z_:][a-zA-Z0-9:._-]*) UNQUOTED_VALUE_STRING = %Q([^"'=<>`\\x00-\\x20]+) @@ -40,7 +42,7 @@ module Markd /^<[?]/, /^]|$)/i, + /^<[\/]?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[123456]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|section|source|title|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?:\s|[\/]?[>]|$)/i, Regex.new("^(?:" + OPEN_TAG + "|" + CLOSE_TAG + ")\\s*$", Regex::Options::IGNORE_CASE), ] @@ -58,14 +60,15 @@ module Markd LINK_LABEL = Regex.new("^\\[(?:[^\\\\\\[\\]]|" + ESCAPED_CHAR_STRING + "|\\\\){0,}\\]") - LINK_DESTINATION_BRACES = Regex.new("^(?:[<](?:[^ <>\\t\\n\\\\\\x00]|" + ESCAPED_CHAR_STRING + "|\\\\)*[>])") + LINK_DESTINATION_BRACES = Regex.new("^(?:[<](?:[^<>\\t\\n\\\\\\x00]|" + ESCAPED_CHAR_STRING + ")*[>])") EMAIL_AUTO_LINK = /^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/ AUTO_LINK = /^<[A-Za-z][A-Za-z0-9.+-]{1,31}:[^<>\x00-\x20]*>/i WHITESPACE_CHAR = /^[ \t\n\x0b\x0c\x0d]/ WHITESPACE = /[ \t\n\x0b\x0c\x0d]+/ - PUNCTUATION = /\p{P}/ # Regex.new("[!-#%-\*,-/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]") + LINE_ENDING = /\n|\x0d|\x0d\n/ + PUNCTUATION = /[$+<=>^`|~\p{P}]/ # Regex.new("[!"#$%&'()*+,\-./:;<=>?@\[\]^_`{|}~\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]") UNSAFE_PROTOCOL = /^javascript:|vbscript:|file:|data:/i UNSAFE_DATA_PROTOCOL = /^data:image\/(?:png|gif|jpeg|webp)/i diff --git a/lib/markd/src/markd/rules/code_block.cr b/lib/markd/src/markd/rules/code_block.cr index e42c0ee224e7..f299f6bd5909 100644 --- a/lib/markd/src/markd/rules/code_block.cr +++ b/lib/markd/src/markd/rules/code_block.cr @@ -2,7 +2,7 @@ module Markd::Rule struct CodeBlock include Rule - CODE_FENCE = /^`{3,}(?!.*`)|^~{3,}(?!.*~)/ + CODE_FENCE = /^`{3,}(?!.*`)|^~{3,}/ CLOSING_CODE_FENCE = /^(?:`{3,}|~{3,})(?= *$)/ def match(parser : Parser, container : Node) : MatchValue @@ -23,8 +23,7 @@ module Markd::Rule MatchValue::Leaf elsif parser.indented && !parser.blank && (tip = parser.tip) && - !tip.type.paragraph? && - (!container.type.list? || container.data["padding"].as(Int32) >= 4) + !tip.type.paragraph? # indented parser.advance_offset(Rule::CODE_INDENT, true) parser.close_unmatched_blocks diff --git a/lib/markd/src/markd/rules/heading.cr b/lib/markd/src/markd/rules/heading.cr index 0419ec215e26..4c304dc7b813 100644 --- a/lib/markd/src/markd/rules/heading.cr +++ b/lib/markd/src/markd/rules/heading.cr @@ -15,8 +15,8 @@ module Markd::Rule container = parser.add_child(Node::Type::Heading, parser.next_nonspace) container.data["level"] = match[0].strip.size container.text = parser.line[parser.offset..-1] - .sub(/^ *#+ *$/, "") - .sub(/ +#+ *$/, "") + .sub(/^[ \t]*#+[ \t]*$/, "") + .sub(/[ \t]+#+[ \t]*$/, "") parser.advance_offset(parser.line.size - parser.offset) @@ -26,6 +26,13 @@ module Markd::Rule !parent.type.block_quote? # Setext Heading matched parser.close_unmatched_blocks + + while container.text[0]? == '[' && + (pos = parser.inline_lexer.reference(container.text, parser.refmap)) && pos > 0 + container.text = container.text.byte_slice(pos) + end + return MatchValue::None if container.text.empty? + heading = Node.new(Node::Type::Heading) heading.source_pos = container.source_pos heading.data["level"] = match[0][0] == '=' ? 1 : 2 diff --git a/lib/markd/src/markd/rules/list.cr b/lib/markd/src/markd/rules/list.cr index 0d745f67df11..93d49205e80f 100644 --- a/lib/markd/src/markd/rules/list.cr +++ b/lib/markd/src/markd/rules/list.cr @@ -66,9 +66,11 @@ module Markd::Rule end private def parse_list_marker(parser : Parser, container : Node) : Node::DataType - line = parser.line[parser.next_nonspace..-1] - empty_data = {} of String => Node::DataValue + if parser.indent >= 4 + return empty_data + end + data = { "delimiter" => 0, "marker_offset" => parser.indent, @@ -77,6 +79,8 @@ module Markd::Rule "start" => 1, } of String => Node::DataValue + line = parser.line[parser.next_nonspace..-1] + if BULLET_LIST_MARKERS.includes?(line[0]) data["type"] = "bullet" data["bullet_char"] = line[0].to_s @@ -139,7 +143,8 @@ module Markd::Rule while container return true if container.last_line_blank? - break unless container.type == Node::Type::List || container.type == Node::Type::Item + break unless !container.last_line_checked? && container.type.in?(Node::Type::List, Node::Type::Item) + container.last_line_checked = true container = container.last_child? end diff --git a/lib/markd/src/markd/version.cr b/lib/markd/src/markd/version.cr index 4bb13a7e84f9..d5062b5a4f94 100644 --- a/lib/markd/src/markd/version.cr +++ b/lib/markd/src/markd/version.cr @@ -1,3 +1,3 @@ module Markd - VERSION = "0.4.2" + VERSION = "0.5.0" end diff --git a/shard.lock b/shard.lock index c0d828c5acf8..e7f2ddc86d10 100644 --- a/shard.lock +++ b/shard.lock @@ -2,7 +2,7 @@ version: 2.0 shards: markd: git: https://github.com/icyleaf/markd.git - version: 0.4.2+git.commit.5e5a75d13bfdc615f04cc7ab166ee279b3b996d3 + version: 0.5.0 reply: git: https://github.com/i3oris/reply.git diff --git a/shard.yml b/shard.yml index 0220ec13c7b6..6403b105f589 100644 --- a/shard.yml +++ b/shard.yml @@ -12,7 +12,6 @@ crystal: ">= 1.0" dependencies: markd: github: icyleaf/markd - commit: 5e5a75d13bfdc615f04cc7ab166ee279b3b996d3 reply: github: I3oris/reply commit: 90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 From f2624eaf01c1e00a96bf25ba235f33998ec5405e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 22 Mar 2024 13:14:34 +0100 Subject: [PATCH 1008/1551] Refactor expectations with `SpecChannelStatus` to be explicit (#14378) --- spec/std/http/server/server_spec.cr | 2 +- spec/std/io/io_spec.cr | 8 ++++---- spec/std/socket/unix_server_spec.cr | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index f5a82a514e0b..c8b39c9e7e42 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -75,7 +75,7 @@ describe HTTP::Server do sleep 0.1 server.close - ch.receive.end?.should be_true + ch.receive.should eq SpecChannelStatus::End end it "reuses the TCP port (SO_REUSEPORT)" do diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 048482777270..9c6da1e926d2 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -973,11 +973,11 @@ describe IO do schedule_timeout ch - ch.receive.begin?.should be_true + ch.receive.should eq SpecChannelStatus::Begin wait_until_blocked f read.close - ch.receive.end?.should be_true + ch.receive.should eq SpecChannelStatus::End end end @@ -996,11 +996,11 @@ describe IO do schedule_timeout ch - ch.receive.begin?.should be_true + ch.receive.should eq SpecChannelStatus::Begin wait_until_blocked f write.close - ch.receive.end?.should be_true + ch.receive.should eq SpecChannelStatus::End end end end diff --git a/spec/std/socket/unix_server_spec.cr b/spec/std/socket/unix_server_spec.cr index 93dd124ed60b..098bdb3e7d53 100644 --- a/spec/std/socket/unix_server_spec.cr +++ b/spec/std/socket/unix_server_spec.cr @@ -95,13 +95,13 @@ describe UNIXServer do ch.send(:end) end - ch.receive.begin?.should be_true + ch.receive.should eq SpecChannelStatus::Begin # wait for the server to call accept wait_until_blocked f server.close - ch.receive.end?.should be_true + ch.receive.should eq SpecChannelStatus::End exception.should be_a(IO::Error) exception.try(&.message).should eq("Closed stream") @@ -136,13 +136,13 @@ describe UNIXServer do ch.send :end end - ch.receive.begin?.should be_true + ch.receive.should eq SpecChannelStatus::Begin # wait for the server to call accept wait_until_blocked f server.close - ch.receive.end?.should be_true + ch.receive.should eq SpecChannelStatus::End ret.should be_nil end From 1d6f645838cb33345dc2887b20218d0caee4ec72 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:14:44 +0100 Subject: [PATCH 1009/1551] Update cachix/install-nix-action action to v26 (#14375) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 765595ce2d09..7f27b3cc9c14 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -17,7 +17,7 @@ jobs: - name: Download Crystal source uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v25 + - uses: cachix/install-nix-action@v26 with: install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | From 7c119d11961203e3f14a2d65d50b8bfa9c64e787 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 24 Mar 2024 22:45:46 +0800 Subject: [PATCH 1010/1551] Ensure all constants only have one initializer in the interpreter (#14381) --- src/compiler/crystal/interpreter/constants.cr | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/compiler/crystal/interpreter/constants.cr b/src/compiler/crystal/interpreter/constants.cr index b5516bdee477..7326f45efaf6 100644 --- a/src/compiler/crystal/interpreter/constants.cr +++ b/src/compiler/crystal/interpreter/constants.cr @@ -41,6 +41,15 @@ class Crystal::Repl::Constants # Note that at that index the `initializer` "bit" (8 bytes) should be stored, # and only after `OFFSET_FROM_INITIALIZER` the data should be stored. def declare(const : Const, compiled_def : CompiledDef) : Int32 + # if `Compiler#get_const_index_and_compiled_def` calls itself due to a + # recursive constant initializer, e.g. `STDOUT`, the same constant might be + # declared twice; in that case, use the new initializer but keep the same + # constant index as before + if value = fetch?(const) + @data[const] = value.copy_with(compiled_def: compiled_def) + return value.index + end + type = const.value.type index = @bytesize From d3d2668427d6763acea41b0fdefe7a6d23084010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 24 Mar 2024 15:46:02 +0100 Subject: [PATCH 1011/1551] Refactor `Socket#system_receive` to return `Address` (#14384) --- src/crystal/system/unix/socket.cr | 2 +- src/crystal/system/win32/socket.cr | 2 +- src/socket.cr | 8 +++----- src/socket/udp_socket.cr | 9 ++++----- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 222ec7216fb9..345f3caedc47 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -105,7 +105,7 @@ module Crystal::System::Socket LibC.recvfrom(fd, slice, slice.size, 0, sockaddr, pointerof(addrlen)) end - {bytes_read, sockaddr, addrlen} + {bytes_read, ::Socket::Address.from(sockaddr, addrlen)} end private def system_close_read diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 88278544dbf0..18b3407206a8 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -299,7 +299,7 @@ module Crystal::System::Socket {ret, bytes_received} end - {bytes_read.to_i32, sockaddr, addrlen} + {bytes_read.to_i32, ::Socket::Address.from(sockaddr, addrlen)} end private def system_close_read diff --git a/src/socket.cr b/src/socket.cr index bce572624743..37cf03d77754 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -255,11 +255,10 @@ class Socket < IO def receive(max_message_size = 512) : {String, Address} address = nil message = String.new(max_message_size) do |buffer| - bytes_read, sockaddr, addrlen = system_receive(Slice.new(buffer, max_message_size)) - address = Address.from(sockaddr, addrlen) + bytes_read, address = system_receive(Slice.new(buffer, max_message_size)) {bytes_read, 0} end - {message, address.not_nil!} + {message, address.as(Address)} end # Receives a binary message from the previously bound address. @@ -274,8 +273,7 @@ class Socket < IO # bytes_read, client_addr = server.receive(message) # ``` def receive(message : Bytes) : {Int32, Address} - bytes_read, sockaddr, addrlen = system_receive(message) - {bytes_read, Address.from(sockaddr, addrlen)} + system_receive(message) end # Calls `shutdown(2)` with `SHUT_RD` diff --git a/src/socket/udp_socket.cr b/src/socket/udp_socket.cr index 86132c54d0c7..9175b787cfe9 100644 --- a/src/socket/udp_socket.cr +++ b/src/socket/udp_socket.cr @@ -70,11 +70,10 @@ class UDPSocket < IPSocket def receive(max_message_size = 512) : {String, IPAddress} address = nil message = String.new(max_message_size) do |buffer| - bytes_read, sockaddr, addrlen = system_receive(Slice.new(buffer, max_message_size)) - address = IPAddress.from(sockaddr, addrlen) + bytes_read, address = system_receive(Slice.new(buffer, max_message_size)) {bytes_read, 0} end - {message, address.not_nil!} + {message, address.as(IPAddress)} end # Receives a binary message from the previously bound address. @@ -89,8 +88,8 @@ class UDPSocket < IPSocket # bytes_read, client_addr = server.receive(message) # ``` def receive(message : Bytes) : {Int32, IPAddress} - bytes_read, sockaddr, addrlen = system_receive(message) - {bytes_read, IPAddress.from(sockaddr, addrlen)} + bytes_read, address = system_receive(message) + {bytes_read, address.as(IPAddress)} end # Reports whether transmitted multicast packets should be copied and sent From 2f82786be7f8fa38a78630b8bab034efd42cd16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 24 Mar 2024 15:46:18 +0100 Subject: [PATCH 1012/1551] Refactor `#system_connect` without yield (#14383) --- src/crystal/system/unix/socket.cr | 6 +++--- src/crystal/system/wasi/socket.cr | 2 +- src/crystal/system/win32/socket.cr | 18 +++++++++--------- src/socket.cr | 5 +++-- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 345f3caedc47..f65d3a69e1e7 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -22,7 +22,7 @@ module Crystal::System::Socket {% end %} end - private def system_connect(addr, timeout = nil, &) + private def system_connect(addr, timeout = nil) timeout = timeout.seconds unless timeout.is_a? ::Time::Span | Nil loop do if LibC.connect(fd, addr, addr.size) == 0 @@ -33,10 +33,10 @@ module Crystal::System::Socket return when Errno::EINPROGRESS, Errno::EALREADY wait_writable(timeout: timeout) do - return yield IO::TimeoutError.new("connect timed out") + return IO::TimeoutError.new("connect timed out") end else - return yield ::Socket::ConnectError.from_errno("connect") + return ::Socket::ConnectError.from_errno("connect") end end end diff --git a/src/crystal/system/wasi/socket.cr b/src/crystal/system/wasi/socket.cr index add7b751eada..6a3bc924dd27 100644 --- a/src/crystal/system/wasi/socket.cr +++ b/src/crystal/system/wasi/socket.cr @@ -15,7 +15,7 @@ module Crystal::System::Socket private def initialize_handle(fd) end - private def system_connect(addr, timeout = nil, &) + private def system_connect(addr, timeout = nil) raise NotImplementedError.new "Crystal::System::Socket#system_connect" end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 18b3407206a8..f6c920428f9b 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -94,20 +94,20 @@ module Crystal::System::Socket end end - private def system_connect(addr, timeout = nil, &) + private def system_connect(addr, timeout = nil) if type.stream? - system_connect_stream(addr, timeout) { |error| yield error } + system_connect_stream(addr, timeout) else - system_connect_connectionless(addr, timeout) { |error| yield error } + system_connect_connectionless(addr, timeout) end end - private def system_connect_stream(addr, timeout, &) + private def system_connect_stream(addr, timeout) address = LibC::SockaddrIn6.new address.sin6_family = family address.sin6_port = 0 unless LibC.bind(fd, pointerof(address).as(LibC::Sockaddr*), sizeof(LibC::SockaddrIn6)) == 0 - return yield ::Socket::BindError.from_wsa_error("Could not bind to '*'") + return ::Socket::BindError.from_wsa_error("Could not bind to '*'") end error = overlapped_connect(fd, "ConnectEx") do |overlapped| @@ -116,7 +116,7 @@ module Crystal::System::Socket end if error - return yield error + return error end # from https://learn.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options: @@ -128,7 +128,7 @@ module Crystal::System::Socket # > functions are to be used on the connected socket. optname = LibC::SO_UPDATE_CONNECT_CONTEXT if LibC.setsockopt(fd, LibC::SOL_SOCKET, optname, nil, 0) == LibC::SOCKET_ERROR - return yield ::Socket::Error.from_wsa_error("setsockopt #{optname}") + return ::Socket::Error.from_wsa_error("setsockopt #{optname}") end end @@ -166,10 +166,10 @@ module Crystal::System::Socket end end - private def system_connect_connectionless(addr, timeout, &) + private def system_connect_connectionless(addr, timeout) ret = LibC.connect(fd, addr, addr.size) if ret == LibC::SOCKET_ERROR - yield ::Socket::Error.from_wsa_error("connect") + ::Socket::Error.from_wsa_error("connect") end end diff --git a/src/socket.cr b/src/socket.cr index 37cf03d77754..85be116d3731 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -91,7 +91,7 @@ class Socket < IO # ``` def connect(host : String, port : Int, connect_timeout = nil) : Nil Addrinfo.resolve(host, port, @family, @type, @protocol) do |addrinfo| - connect(addrinfo, timeout: connect_timeout) { |error| error } + connect(addrinfo, timeout: connect_timeout) end end @@ -110,7 +110,8 @@ class Socket < IO # Tries to connect to a remote address. Yields an `IO::TimeoutError` or an # `Socket::ConnectError` error if the connection failed. def connect(addr, timeout = nil, &) - system_connect(addr, timeout) { |error| yield error } + result = system_connect(addr, timeout) + yield result if result.is_a?(Exception) end # Binds the socket to a local address. From 87f1517d26473221425d9f5d12aff1d908b9b2bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 24 Mar 2024 20:53:38 +0100 Subject: [PATCH 1013/1551] Add type restrictions to `#unbuffered_*` implementations (#14382) --- spec/std/io/buffered_spec.cr | 4 ++-- src/compress/gzip/reader.cr | 2 +- src/crystal/system/socket.cr | 4 ++-- src/crystal/system/unix/file_descriptor.cr | 4 ++-- src/crystal/system/unix/socket.cr | 4 ++-- src/crystal/system/wasi/socket.cr | 4 ++-- src/crystal/system/win32/file_descriptor.cr | 10 +++++----- src/crystal/system/win32/socket.cr | 4 ++-- src/file/preader.cr | 4 ++-- src/http/server/response.cr | 10 +++++----- src/io/buffered.cr | 13 +++++++++++-- src/io/file_descriptor.cr | 6 +++--- src/openssl/ssl/socket.cr | 2 +- src/socket.cr | 6 +++--- 14 files changed, 43 insertions(+), 34 deletions(-) diff --git a/spec/std/io/buffered_spec.cr b/spec/std/io/buffered_spec.cr index d8ef8ff3b979..8f200aab7f56 100644 --- a/spec/std/io/buffered_spec.cr +++ b/spec/std/io/buffered_spec.cr @@ -19,12 +19,12 @@ private class BufferedWrapper < IO io end - private def unbuffered_read(slice : Bytes) + private def unbuffered_read(slice : Bytes) : Int32 @called_unbuffered_read = true @io.read(slice) end - private def unbuffered_write(slice : Bytes) + private def unbuffered_write(slice : Bytes) : Nil @io.write(slice) end diff --git a/src/compress/gzip/reader.cr b/src/compress/gzip/reader.cr index 0371439acf77..dc4f056436d6 100644 --- a/src/compress/gzip/reader.cr +++ b/src/compress/gzip/reader.cr @@ -129,7 +129,7 @@ class Compress::Gzip::Reader < IO end # Always raises `IO::Error` because this is a read-only `IO`. - def unbuffered_write(slice : Bytes) : Nil + def unbuffered_write(slice : Bytes) : NoReturn raise IO::Error.new("Can't write to Compress::Gzip::Reader") end diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr index 552957bcff9a..8e91e18dbd4b 100644 --- a/src/crystal/system/socket.cr +++ b/src/crystal/system/socket.cr @@ -71,9 +71,9 @@ module Crystal::System::Socket # def self.fcntl(fd, cmd, arg = 0) - # private def unbuffered_read(slice : Bytes) + # private def unbuffered_read(slice : Bytes) : Int32 - # private def unbuffered_write(slice : Bytes) + # private def unbuffered_write(slice : Bytes) : Nil # private def system_close diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index cd4fcf75b98b..236ed8537da8 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -11,7 +11,7 @@ module Crystal::System::FileDescriptor @volatile_fd : Atomic(Int32) - private def unbuffered_read(slice : Bytes) + private def unbuffered_read(slice : Bytes) : Int32 evented_read(slice, "Error reading file") do LibC.read(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF @@ -21,7 +21,7 @@ module Crystal::System::FileDescriptor end end - private def unbuffered_write(slice : Bytes) + private def unbuffered_write(slice : Bytes) : Nil evented_write(slice, "Error writing file") do |slice| LibC.write(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index f65d3a69e1e7..4a66c4dd2b1d 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -250,13 +250,13 @@ module Crystal::System::Socket LibC.isatty(fd) == 1 end - private def unbuffered_read(slice : Bytes) + private def unbuffered_read(slice : Bytes) : Int32 evented_read(slice, "Error reading socket") do LibC.recv(fd, slice, slice.size, 0).to_i32 end end - private def unbuffered_write(slice : Bytes) + private def unbuffered_write(slice : Bytes) : Nil evented_write(slice, "Error writing to socket") do |slice| LibC.send(fd, slice, slice.size, 0) end diff --git a/src/crystal/system/wasi/socket.cr b/src/crystal/system/wasi/socket.cr index 6a3bc924dd27..239ab4a9fdbd 100644 --- a/src/crystal/system/wasi/socket.cr +++ b/src/crystal/system/wasi/socket.cr @@ -161,13 +161,13 @@ module Crystal::System::Socket LibC.isatty(fd) == 1 end - private def unbuffered_read(slice : Bytes) + private def unbuffered_read(slice : Bytes) : Int32 evented_read(slice, "Error reading socket") do LibC.recv(fd, slice, slice.size, 0).to_i32 end end - private def unbuffered_write(slice : Bytes) + private def unbuffered_write(slice : Bytes) : Nil evented_write(slice, "Error writing to socket") do |slice| LibC.send(fd, slice, slice.size, 0) end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index bdcf554cf1cb..bd709d28d17a 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -10,7 +10,7 @@ module Crystal::System::FileDescriptor @volatile_fd : Atomic(LibC::Int) @system_blocking = true - private def unbuffered_read(slice : Bytes) + private def unbuffered_read(slice : Bytes) : Int32 handle = windows_handle if ConsoleUtils.console?(handle) ConsoleUtils.read(handle, slice) @@ -20,21 +20,21 @@ module Crystal::System::FileDescriptor when .error_access_denied? raise IO::Error.new "File not open for reading", target: self when .error_broken_pipe? - return 0_u32 + return 0_i32 else raise IO::Error.from_os_error("Error reading file", error, target: self) end end - bytes_read + bytes_read.to_i32 else overlapped_operation(handle, "ReadFile", read_timeout) do |overlapped| ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped) {ret, byte_count} - end + end.to_i32 end end - private def unbuffered_write(slice : Bytes) + private def unbuffered_write(slice : Bytes) : Nil handle = windows_handle until slice.empty? if system_blocking? diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index f6c920428f9b..38fd890c6238 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -445,7 +445,7 @@ module Crystal::System::Socket false end - private def unbuffered_read(slice : Bytes) + private def unbuffered_read(slice : Bytes) : Int32 wsabuf = wsa_buffer(slice) bytes_read = overlapped_read(fd, "WSARecv", connreset_is_error: false) do |overlapped| @@ -457,7 +457,7 @@ module Crystal::System::Socket bytes_read.to_i32 end - private def unbuffered_write(slice : Bytes) + private def unbuffered_write(slice : Bytes) : Nil wsabuf = wsa_buffer(slice) bytes = overlapped_write(fd, "WSASend") do |overlapped| diff --git a/src/file/preader.cr b/src/file/preader.cr index 9bd9fb73d490..d366457314ce 100644 --- a/src/file/preader.cr +++ b/src/file/preader.cr @@ -14,7 +14,7 @@ class File::PReader < IO @pos = 0 end - def unbuffered_read(slice : Bytes) : Int64 + def unbuffered_read(slice : Bytes) : Int32 check_open count = slice.size @@ -24,7 +24,7 @@ class File::PReader < IO @pos += bytes_read - bytes_read + bytes_read.to_i32 end def unbuffered_write(slice : Bytes) : NoReturn diff --git a/src/http/server/response.cr b/src/http/server/response.cr index d07295914482..5c80b31cce00 100644 --- a/src/http/server/response.cr +++ b/src/http/server/response.cr @@ -248,11 +248,11 @@ class HTTP::Server @closed = false end - private def unbuffered_read(slice : Bytes) + private def unbuffered_read(slice : Bytes) : Int32 raise "Can't read from HTTP::Server::Response" end - private def unbuffered_write(slice : Bytes) + private def unbuffered_write(slice : Bytes) : Nil return if slice.empty? unless response.wrote_headers? @@ -313,15 +313,15 @@ class HTTP::Server end end - private def unbuffered_close + private def unbuffered_close : Nil @closed = true end - private def unbuffered_rewind + private def unbuffered_rewind : Nil raise "Can't rewind to HTTP::Server::Response" end - private def unbuffered_flush + private def unbuffered_flush : Nil @io.flush rescue ex : IO::Error unbuffered_close diff --git a/src/io/buffered.cr b/src/io/buffered.cr index 11d9d15827e8..0e69872a638f 100644 --- a/src/io/buffered.cr +++ b/src/io/buffered.cr @@ -17,19 +17,28 @@ module IO::Buffered # Reads at most *slice.size* bytes from the wrapped `IO` into *slice*. # Returns the number of bytes read. + # + # TODO: Add return type restriction `Int32` abstract def unbuffered_read(slice : Bytes) - # Writes at most *slice.size* bytes from *slice* into the wrapped `IO`. - # Returns the number of bytes written. + # Writes *slice* entirely into the wrapped `IO`. + # + # TODO: Add return type restriction `Nil` abstract def unbuffered_write(slice : Bytes) # Flushes the wrapped `IO`. + # + # TODO: Add return type restriction `Nil` abstract def unbuffered_flush # Closes the wrapped `IO`. + # + # TODO: Add return type restriction `Nil` abstract def unbuffered_close # Rewinds the wrapped `IO`. + # + # TODO: Add return type restriction `Nil` abstract def unbuffered_rewind # Return the buffer size used diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index f725caeffeba..2f1df859a3d0 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -262,11 +262,11 @@ class IO::FileDescriptor < IO pp.text inspect end - private def unbuffered_rewind + private def unbuffered_rewind : Nil self.pos = 0 end - private def unbuffered_close + private def unbuffered_close : Nil return if @closed # Set before the @closed state so the pending @@ -276,7 +276,7 @@ class IO::FileDescriptor < IO system_close end - private def unbuffered_flush + private def unbuffered_flush : Nil # Nothing end end diff --git a/src/openssl/ssl/socket.cr b/src/openssl/ssl/socket.cr index d5f0d88561a7..8bff5a131410 100644 --- a/src/openssl/ssl/socket.cr +++ b/src/openssl/ssl/socket.cr @@ -198,7 +198,7 @@ abstract class OpenSSL::SSL::Socket < IO end end - def unbuffered_rewind + def unbuffered_rewind : Nil raise IO::Error.new("Can't rewind OpenSSL::SSL::Socket::Client") end diff --git a/src/socket.cr b/src/socket.cr index 85be116d3731..eba19a939cba 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -424,11 +424,11 @@ class Socket < IO system_tty? end - private def unbuffered_rewind + private def unbuffered_rewind : Nil raise Socket::Error.new("Can't rewind") end - private def unbuffered_close + private def unbuffered_close : Nil return if @closed @closed = true @@ -436,7 +436,7 @@ class Socket < IO system_close end - private def unbuffered_flush + private def unbuffered_flush : Nil # Nothing end end From 5cefc9ac209c8da1e1fb50dcd9b05268d9949c86 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 25 Mar 2024 03:53:52 +0800 Subject: [PATCH 1014/1551] Fix requires for `time/time_spec.cr` and `time/format_spec.cr` (#14385) --- spec/std/time/format_spec.cr | 1 + spec/std/time/time_spec.cr | 1 + 2 files changed, 2 insertions(+) diff --git a/spec/std/time/format_spec.cr b/spec/std/time/format_spec.cr index c6f8dc9b03bf..0d84749991e2 100644 --- a/spec/std/time/format_spec.cr +++ b/spec/std/time/format_spec.cr @@ -1,4 +1,5 @@ require "../spec_helper" +require "../../support/time" require "spec/helpers/string" def parse_time(format, string) diff --git a/spec/std/time/time_spec.cr b/spec/std/time/time_spec.cr index d257bc70f2d4..bfae4900705e 100644 --- a/spec/std/time/time_spec.cr +++ b/spec/std/time/time_spec.cr @@ -1,4 +1,5 @@ require "../spec_helper" +require "../../support/time" require "spec/helpers/iterate" CALENDAR_WEEK_TEST_DATA = [ From 905039c06d5530f48894b809f263e5cff987d1d1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 25 Mar 2024 03:54:00 +0800 Subject: [PATCH 1015/1551] Add `pending_interpreted` (#14386) --- spec/generate_interpreter_spec.sh | 2 +- spec/interpreter_std_spec.cr | 35 ++++++++++++---------- spec/std/crystal/system_spec.cr | 8 +++-- spec/std/dir_spec.cr | 3 +- spec/std/file_spec.cr | 7 ++--- spec/std/file_utils_spec.cr | 6 ++-- spec/std/process_spec.cr | 3 +- spec/std/signal_spec.cr | 5 ++-- spec/std/spec_helper.cr | 4 +++ spec/std/system_spec.cr | 6 ++-- spec/std/thread/condition_variable_spec.cr | 5 ++-- spec/std/thread/mutex_spec.cr | 5 ++-- spec/std/thread_spec.cr | 5 ++-- spec/support/interpreted.cr | 26 ++++++++++++++++ spec/support/tempfile.cr | 4 +++ 15 files changed, 85 insertions(+), 39 deletions(-) create mode 100644 spec/support/interpreted.cr diff --git a/spec/generate_interpreter_spec.sh b/spec/generate_interpreter_spec.sh index f3500115a737..960ab37f48db 100755 --- a/spec/generate_interpreter_spec.sh +++ b/spec/generate_interpreter_spec.sh @@ -19,7 +19,7 @@ echo "# $(date --rfc-3339 seconds) This file is autogenerated by \`${command% }\ run_spec () { spec=$1 require="require \"./${spec##spec/}\"" - timeout 150 $CRYSTAL_BIN i $spec > /dev/null; exit=$? + timeout --signal=KILL 150 $CRYSTAL_BIN i $spec > /dev/null; exit=$? if [ $exit -eq 0 ] ; then echo "$require" >> $OUT_FILE diff --git a/spec/interpreter_std_spec.cr b/spec/interpreter_std_spec.cr index 79b8bab3edbc..0bb16eeeb2a1 100644 --- a/spec/interpreter_std_spec.cr +++ b/spec/interpreter_std_spec.cr @@ -1,4 +1,4 @@ -# 2023-04-12 22:54:02-05:00 This file is autogenerated by `spec/generate_interpreter_spec.sh std spec/interpreter_std_spec.cr` +# 2024-03-22 14:01:27+08:00 This file is autogenerated by `./spec/generate_interpreter_spec.sh std spec/interpreter_std_spec.cr 8` require "./std/array_spec.cr" require "./std/atomic_spec.cr" require "./std/base64_spec.cr" @@ -50,6 +50,7 @@ require "./std/crystal/hasher_spec.cr" require "./std/crystal/pointer_linked_list_spec.cr" require "./std/crystal/syntax_highlighter/colorize_spec.cr" require "./std/crystal/syntax_highlighter/html_spec.cr" +require "./std/crystal/system_spec.cr" require "./std/csv/csv_build_spec.cr" require "./std/csv/csv_lex_spec.cr" require "./std/csv/csv_parse_spec.cr" @@ -62,7 +63,7 @@ require "./std/digest/md5_spec.cr" require "./std/digest/sha1_spec.cr" require "./std/digest/sha256_spec.cr" require "./std/digest/sha512_spec.cr" -# require "./std/dir_spec.cr" (failed to run) +require "./std/dir_spec.cr" require "./std/double_spec.cr" require "./std/ecr/ecr_lexer_spec.cr" require "./std/ecr/ecr_spec.cr" @@ -70,8 +71,8 @@ require "./std/enumerable_spec.cr" require "./std/enum_spec.cr" require "./std/env_spec.cr" require "./std/errno_spec.cr" -# require "./std/exception/call_stack_spec.cr" (failed to run) -# require "./std/exception_spec.cr" (failed to run) +require "./std/exception/call_stack_spec.cr" +require "./std/exception_spec.cr" require "./std/fiber_spec.cr" require "./std/file_spec.cr" require "./std/file/tempfile_spec.cr" @@ -80,6 +81,7 @@ require "./std/float_printer/diy_fp_spec.cr" require "./std/float_printer/grisu3_spec.cr" require "./std/float_printer/hexfloat_spec.cr" require "./std/float_printer/ieee_spec.cr" +require "./std/float_printer/ryu_printf_spec.cr" require "./std/float_printer/shortest_spec.cr" require "./std/float_spec.cr" require "./std/gc_spec.cr" @@ -116,7 +118,7 @@ require "./std/io/argf_spec.cr" require "./std/io/buffered_spec.cr" require "./std/io/byte_format_spec.cr" require "./std/io/delimited_spec.cr" -# require "./std/io/file_descriptor_spec.cr" (failed to run) +require "./std/io/file_descriptor_spec.cr" require "./std/io/hexdump_spec.cr" require "./std/io/io_spec.cr" require "./std/io/memory_spec.cr" @@ -131,7 +133,7 @@ require "./std/json/parser_spec.cr" require "./std/json/pull_parser_spec.cr" require "./std/json/serializable_spec.cr" require "./std/json/serialization_spec.cr" -# require "./std/kernel_spec.cr" (failed to run) +require "./std/kernel_spec.cr" require "./std/levenshtein_spec.cr" # require "./std/llvm/aarch64_spec.cr" (failed to run) # require "./std/llvm/arm_abi_spec.cr" (failed to run) @@ -185,7 +187,7 @@ require "./std/pointer_spec.cr" require "./std/pp_spec.cr" require "./std/pretty_print_spec.cr" require "./std/process/find_executable_spec.cr" -# require "./std/process_spec.cr" (failed to run) +require "./std/process_spec.cr" require "./std/process/status_spec.cr" require "./std/process/utils_spec.cr" require "./std/proc_spec.cr" @@ -201,7 +203,7 @@ require "./std/regex/match_data_spec.cr" require "./std/regex_spec.cr" require "./std/semantic_version_spec.cr" require "./std/set_spec.cr" -# require "./std/signal_spec.cr" (failed to run) +require "./std/signal_spec.cr" require "./std/slice_spec.cr" require "./std/socket/address_spec.cr" require "./std/socket/addrinfo_spec.cr" @@ -215,8 +217,9 @@ require "./std/spec/context_spec.cr" require "./std/spec/expectations_spec.cr" require "./std/spec/filters_spec.cr" require "./std/spec/helpers/iterate_spec.cr" -# require "./std/spec/hooks_spec.cr" (failed to run) +require "./std/spec/hooks_spec.cr" require "./std/spec/junit_formatter_spec.cr" +require "./std/spec/list_tags_spec.cr" require "./std/spec_spec.cr" require "./std/spec/tap_formatter_spec.cr" require "./std/sprintf_spec.cr" @@ -233,16 +236,16 @@ require "./std/symbol_spec.cr" # require "./std/syscall_spec.cr" (failed to run) require "./std/system_error_spec.cr" require "./std/system/group_spec.cr" -# require "./std/system_spec.cr" (failed to run) +require "./std/system_spec.cr" require "./std/system/user_spec.cr" -# require "./std/thread/condition_variable_spec.cr" (interpreter must support threads) -# require "./std/thread/mutex_spec.cr" (interpreter must support threads) -# require "./std/thread_spec.cr" (interpreter must support threads) +require "./std/thread/condition_variable_spec.cr" +require "./std/thread/mutex_spec.cr" +require "./std/thread_spec.cr" require "./std/time/custom_formats_spec.cr" -require "./std/time/format_spec.cr" +# require "./std/time/format_spec.cr" (failed to run) require "./std/time/location_spec.cr" require "./std/time/span_spec.cr" -require "./std/time/time_spec.cr" +# require "./std/time/time_spec.cr" (failed to run) require "./std/tuple_spec.cr" require "./std/uint_spec.cr" require "./std/uri/params_spec.cr" @@ -251,7 +254,7 @@ require "./std/uri_spec.cr" require "./std/uuid/json_spec.cr" require "./std/uuid_spec.cr" require "./std/uuid/yaml_spec.cr" -# require "./std/va_list_spec.cr" (failed to run) +require "./std/va_list_spec.cr" require "./std/weak_ref_spec.cr" require "./std/winerror_spec.cr" require "./std/xml/builder_spec.cr" diff --git a/spec/std/crystal/system_spec.cr b/spec/std/crystal/system_spec.cr index 06bc789055d4..8e710f860a53 100644 --- a/spec/std/crystal/system_spec.cr +++ b/spec/std/crystal/system_spec.cr @@ -1,4 +1,4 @@ -require "spec" +require "../spec_helper" private def print_error_to_s(format, *args) io = IO::Memory.new @@ -26,7 +26,8 @@ describe "Crystal::System" do print_error_to_s("%x,%x,%x,%x,%x", 0, 0x1234, UInt32::MAX, Int32::MIN, UInt64::MAX).should eq("0,1234,ffffffff,80000000,ffffffff") end - it "supports %p" do + # TODO: investigate why this prints `(???)` + pending_interpreted "supports %p" do print_error_to_s("%p,%p,%p", Pointer(Void).new(0x0), Pointer(Void).new(0x1234), Pointer(Void).new(UInt64::MAX)).should eq("0x0,0x1234,0xffffffffffffffff") end @@ -34,7 +35,8 @@ describe "Crystal::System" do print_error_to_s("%s,%s,%s", "abc\0def", "ghi".to_unsafe, Pointer(UInt8).null).should eq("abc\0def,ghi,(null)") end - it "supports %l width" do + # BUG: missing downcast_distinct from Tuple(Int64 | UInt64, Int64 | UInt64, Int64 | UInt64, Int64 | UInt64) to Tuple(Int64, Int64, Int64, Int64) + pending_interpreted "supports %l width" do values = {LibC::Long::MIN, LibC::Long::MAX, LibC::LongLong::MIN, LibC::LongLong::MAX} print_error_to_s("%ld,%ld,%lld,%lld", *values).should eq(values.join(',')) diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index c2d86eff7bb2..439da15becd9 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -631,7 +631,8 @@ describe "Dir" do end describe ".current" do - it "matches shell" do + # can't use backtick in interpreted code (#12241) + pending_interpreted "matches shell" do Dir.current.should eq(`#{{{ flag?(:win32) ? "cmd /c cd" : "pwd" }}}`.chomp) end diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 1e9a9f53ae32..fedbf0889370 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -79,10 +79,9 @@ describe "File" do end end - {% if LibC.has_method?(:mkfifo) && !flag?(:interpreted) %} - # spec is disabled when interpreted because the interpreter doesn't - # support threads - it "opens fifo file as non-blocking" do + {% if LibC.has_method?(:mkfifo) %} + # interpreter doesn't support threads yet (#14287) + pending_interpreted "opens fifo file as non-blocking" do path = File.tempname("chardev") ret = LibC.mkfifo(path, File::DEFAULT_CREATE_PERMISSIONS) raise RuntimeError.from_errno("mkfifo") unless ret == 0 diff --git a/spec/std/file_utils_spec.cr b/spec/std/file_utils_spec.cr index bc730dde5c01..87591120ff7b 100644 --- a/spec/std/file_utils_spec.cr +++ b/spec/std/file_utils_spec.cr @@ -715,9 +715,9 @@ describe "FileUtils" do end end - # FIXME: `Process.run` and backtick don't work in the interpreter (#12241) - {% if flag?(:unix) && !flag?(:interpreted) %} - it "overwrites a destination named pipe" do + {% if flag?(:unix) %} + # can't use backtick in interpreted code (#12241) + pending_interpreted "overwrites a destination named pipe" do with_tempfile("ln_sf_src", "ln_sf_dst_pipe_exists") do |path1, path2| test_with_string_and_path(path1, path2) do |arg1, arg2| FileUtils.touch([path1]) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index db3a058a7c44..6388b88fb70c 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -54,7 +54,8 @@ private def newline {% end %} end -describe Process do +# interpreted code doesn't receive SIGCHLD for `#wait` to work (#12241) +pending_interpreted describe: Process do describe ".new" do it "raises if command doesn't exist" do expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr index d1d3aa3f1643..cae1c5e83834 100644 --- a/spec/std/signal_spec.cr +++ b/spec/std/signal_spec.cr @@ -1,9 +1,10 @@ {% skip_file if flag?(:wasm32) %} -require "spec" +require "./spec_helper" require "signal" -describe "Signal" do +# interpreted code never receives signals (#12241) +pending_interpreted describe: "Signal" do typeof(Signal::ABRT.reset) typeof(Signal::ABRT.ignore) typeof(Signal::ABRT.trap { 1 }) diff --git a/spec/std/spec_helper.cr b/spec/std/spec_helper.cr index 18deaf0260bf..69c80968b117 100644 --- a/spec/std/spec_helper.cr +++ b/spec/std/spec_helper.cr @@ -3,6 +3,7 @@ require "../support/tempfile" require "../support/fibers" require "../support/win32" require "../support/wasm32" +require "../support/interpreted" def datapath(*components) File.join("spec", "std", "data", *components) @@ -77,6 +78,9 @@ def spawn_and_check(before : Proc(_), file = __FILE__, line = __LINE__, &block : end def compile_file(source_file, *, bin_name = "executable_file", flags = %w(), file = __FILE__, &) + # can't use backtick in interpreted code (#12241) + pending_interpreted! "Unable to compile Crystal code in interpreted code" + with_temp_executable(bin_name, file: file) do |executable_file| compiler = ENV["CRYSTAL_SPEC_COMPILER_BIN"]? || "bin/crystal" args = ["build"] + flags + ["-o", executable_file, source_file] diff --git a/spec/std/system_spec.cr b/spec/std/system_spec.cr index 7a87b1a238c8..58023f0c0e59 100644 --- a/spec/std/system_spec.cr +++ b/spec/std/system_spec.cr @@ -3,7 +3,8 @@ require "system" describe System do describe "hostname" do - it "returns current hostname" do + # can't use backtick in interpreted code (#12241) + pending_interpreted "returns current hostname" do shell_hostname = `hostname`.strip pending! "`hostname` command was unsuccessful" unless $?.success? @@ -13,7 +14,8 @@ describe System do end describe "cpu_count" do - it "returns current CPU count" do + # can't use backtick in interpreted code (#12241) + pending_interpreted "returns current CPU count" do shell_cpus = {% if flag?(:win32) %} ENV["NUMBER_OF_PROCESSORS"].to_i diff --git a/spec/std/thread/condition_variable_spec.cr b/spec/std/thread/condition_variable_spec.cr index 36e1963794b3..ff9c44204bb6 100644 --- a/spec/std/thread/condition_variable_spec.cr +++ b/spec/std/thread/condition_variable_spec.cr @@ -6,9 +6,10 @@ {% skip_file %} {% end %} -require "spec" +require "../spec_helper" -describe Thread::ConditionVariable do +# interpreter doesn't support threads yet (#14287) +pending_interpreted describe: Thread::ConditionVariable do it "signals" do mutex = Thread::Mutex.new cond = Thread::ConditionVariable.new diff --git a/spec/std/thread/mutex_spec.cr b/spec/std/thread/mutex_spec.cr index d028ffdd8f8a..ff298f318329 100644 --- a/spec/std/thread/mutex_spec.cr +++ b/spec/std/thread/mutex_spec.cr @@ -6,9 +6,10 @@ {% skip_file %} {% end %} -require "spec" +require "../spec_helper" -describe Thread::Mutex do +# interpreter doesn't support threads yet (#14287) +pending_interpreted describe: Thread::Mutex do it "synchronizes" do a = 0 mutex = Thread::Mutex.new diff --git a/spec/std/thread_spec.cr b/spec/std/thread_spec.cr index 136026667137..feb55454b621 100644 --- a/spec/std/thread_spec.cr +++ b/spec/std/thread_spec.cr @@ -1,4 +1,4 @@ -require "spec" +require "./spec_helper" {% if flag?(:musl) %} # FIXME: These thread specs occasionally fail on musl/alpine based ci, so @@ -8,7 +8,8 @@ require "spec" {% skip_file %} {% end %} -describe Thread do +# interpreter doesn't support threads yet (#14287) +pending_interpreted describe: Thread do it "allows passing an argumentless fun to execute" do a = 0 thread = Thread.new { a = 1; 10 } diff --git a/spec/support/interpreted.cr b/spec/support/interpreted.cr new file mode 100644 index 000000000000..79f8a688c72b --- /dev/null +++ b/spec/support/interpreted.cr @@ -0,0 +1,26 @@ +require "spec" + +{% if flag?(:interpreted) %} + def pending_interpreted(description = "assert", file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block) + pending("#{description} [interpreted]", file, line, end_line) + end + + def pending_interpreted(*, describe, file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block) + pending_interpreted(describe, file, line, end_line) { } + end + + def pending_interpreted!(msg = "Cannot run example", file = __FILE__, line = __LINE__) + pending!(msg, file, line) + end +{% else %} + def pending_interpreted(description = "assert", file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block) + it(description, file, line, end_line, &block) + end + + def pending_interpreted(*, describe, file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block) + describe(describe, file, line, end_line, &block) + end + + def pending_interpreted!(msg = "Cannot run example", file = __FILE__, line = __LINE__) + end +{% end %} diff --git a/spec/support/tempfile.cr b/spec/support/tempfile.cr index a5e0c57d010a..a77070d90e40 100644 --- a/spec/support/tempfile.cr +++ b/spec/support/tempfile.cr @@ -1,4 +1,5 @@ require "file_utils" +require "./interpreted" {% if flag?(:msvc) %} require "crystal/system/win32/visual_studio" {% end %} @@ -47,6 +48,9 @@ def with_temp_executable(name, file = __FILE__, &) end def with_temp_c_object_file(c_code, *, filename = "temp_c", file = __FILE__, &) + # can't use backtick in interpreted code (#12241) + pending_interpreted! "Unable to compile C code in interpreted code" + obj_ext = {{ flag?(:msvc) ? ".obj" : ".o" }} with_tempfile("#{filename}.c", "#{filename}#{obj_ext}", file: file) do |c_filename, o_filename| File.write(c_filename, c_code) From f0afa88961129ea552634ff83a295c28a64d0b74 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 25 Mar 2024 18:11:42 +0800 Subject: [PATCH 1016/1551] Automatically detect MSVC tools on Windows interpreter (#14391) --- src/compiler/crystal/codegen/link.cr | 42 +++++++++++++++++++++ src/compiler/crystal/compiler.cr | 35 +++-------------- src/compiler/crystal/interpreter/context.cr | 7 ++++ 3 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index d9910d64c7a8..245d3de24fcf 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -1,3 +1,8 @@ +{% if flag?(:msvc) %} + require "crystal/system/win32/visual_studio" + require "crystal/system/win32/windows_sdk" +{% end %} + module Crystal struct LinkAnnotation getter lib : String? @@ -272,6 +277,43 @@ module Crystal end end + # Detects the current MSVC linker and the relevant linker flags that + # recreate the MSVC developer prompt's standard library paths. If both MSVC + # and the Windows SDK are available, the linker will be an absolute path and + # the linker flags will contain the `/LIBPATH`s for the system libraries. + # + # Has no effect if the host compiler is not using MSVC. + def msvc_compiler_and_flags : {String, Array(String)} + linker = Compiler::MSVC_LINKER + link_args = [] of String + + {% if flag?(:msvc) %} + if msvc_path = Crystal::System::VisualStudio.find_latest_msvc_path + if win_sdk_libpath = Crystal::System::WindowsSDK.find_win10_sdk_libpath + host_bits = {{ flag?(:aarch64) ? "ARM64" : flag?(:bits64) ? "x64" : "x86" }} + target_bits = has_flag?("aarch64") ? "arm64" : has_flag?("bits64") ? "x64" : "x86" + + # MSVC build tools and Windows SDK found; recreate `LIB` environment variable + # that is normally expected on the MSVC developer command prompt + link_args << "/LIBPATH:#{msvc_path.join("atlmfc", "lib", target_bits)}" + link_args << "/LIBPATH:#{msvc_path.join("lib", target_bits)}" + link_args << "/LIBPATH:#{win_sdk_libpath.join("ucrt", target_bits)}" + link_args << "/LIBPATH:#{win_sdk_libpath.join("um", target_bits)}" + + # use exact path for compiler instead of relying on `PATH`, unless + # explicitly overridden by `%CC%` + # (letter case shouldn't matter in most cases but being exact doesn't hurt here) + unless ENV.has_key?("CC") + target_bits = target_bits.sub("arm", "ARM") + linker = msvc_path.join("bin", "Host#{host_bits}", target_bits, "cl.exe").to_s + end + end + end + {% end %} + + {linker, link_args} + end + PKG_CONFIG_PATH = Process.find_executable("pkg-config") # Returns the result of running `pkg-config mod` but returns nil if diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 7ca2a564f9cd..16dc0a0e61a9 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -4,8 +4,6 @@ require "colorize" require "crystal/digest/md5" {% if flag?(:msvc) %} require "./loader" - require "crystal/system/win32/visual_studio" - require "crystal/system/win32/windows_sdk" {% end %} module Crystal @@ -27,8 +25,8 @@ module Crystal # A Compiler parses source code, type checks it and # optionally generates an executable. class Compiler - private DEFAULT_LINKER = ENV["CC"]? || {{ env("CRYSTAL_CONFIG_CC") || "cc" }} - private MSVC_LINKER = ENV["CC"]? || {{ env("CRYSTAL_CONFIG_CC") || "cl.exe" }} + DEFAULT_LINKER = ENV["CC"]? || {{ env("CRYSTAL_CONFIG_CC") || "cc" }} + MSVC_LINKER = ENV["CC"]? || {{ env("CRYSTAL_CONFIG_CC") || "cl.exe" }} # A source to the compiler: its filename and source code. record Source, @@ -409,32 +407,9 @@ module Crystal object_arg = Process.quote_windows(object_names) output_arg = Process.quote_windows("/Fe#{output_filename}") - linker = MSVC_LINKER - link_args = [] of String - - # if the compiler and the target both have the `msvc` flag, we are not - # cross-compiling and therefore we should attempt detecting MSVC's - # standard paths - {% if flag?(:msvc) %} - if msvc_path = Crystal::System::VisualStudio.find_latest_msvc_path - if win_sdk_libpath = Crystal::System::WindowsSDK.find_win10_sdk_libpath - host_bits = {{ flag?(:aarch64) ? "ARM64" : flag?(:bits64) ? "x64" : "x86" }} - target_bits = program.has_flag?("aarch64") ? "arm64" : program.has_flag?("bits64") ? "x64" : "x86" - - # MSVC build tools and Windows SDK found; recreate `LIB` environment variable - # that is normally expected on the MSVC developer command prompt - link_args << Process.quote_windows("/LIBPATH:#{msvc_path.join("atlmfc", "lib", target_bits)}") - link_args << Process.quote_windows("/LIBPATH:#{msvc_path.join("lib", target_bits)}") - link_args << Process.quote_windows("/LIBPATH:#{win_sdk_libpath.join("ucrt", target_bits)}") - link_args << Process.quote_windows("/LIBPATH:#{win_sdk_libpath.join("um", target_bits)}") - - # use exact path for compiler instead of relying on `PATH` - # (letter case shouldn't matter in most cases but being exact doesn't hurt here) - target_bits = target_bits.sub("arm", "ARM") - linker = Process.quote_windows(msvc_path.join("bin", "Host#{host_bits}", target_bits, "cl.exe").to_s) unless ENV.has_key?("CC") - end - end - {% end %} + linker, link_args = program.msvc_compiler_and_flags + linker = Process.quote_windows(linker) + link_args.map! { |arg| Process.quote_windows(arg) } link_args << "/DEBUG:FULL /PDBALTPATH:%_PDB%" unless debug.none? link_args << "/INCREMENTAL:NO /STACK:0x800000" diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr index c3b6e008f6d9..bc44e9302a9e 100644 --- a/src/compiler/crystal/interpreter/context.cr +++ b/src/compiler/crystal/interpreter/context.cr @@ -406,6 +406,13 @@ class Crystal::Repl::Context # (MSVC doesn't seem to have this issue) args.delete("-lgc") + # recreate the MSVC developer prompt environment, similar to how compiled + # code does it in `Compiler#linker_command` + if program.has_flag?("msvc") + _, link_args = program.msvc_compiler_and_flags + args.concat(link_args) + end + Crystal::Loader.parse(args, dll_search_paths: dll_search_paths).tap do |loader| # FIXME: Part 2: This is a workaround for initial integration of the interpreter: # We append a handle to the current executable (i.e. the compiler program) From 10e354fa0f53028e48294997411d71f324343704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 25 Mar 2024 11:12:28 +0100 Subject: [PATCH 1017/1551] Refactor and add comments to IOCP `#run_once` (#14380) --- src/crystal/system/win32/event_loop_iocp.cr | 70 ++++++++++++++------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index f7636a869af9..3889b8b25d81 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -10,6 +10,7 @@ end # :nodoc: class Crystal::Iocp::EventLoop < Crystal::EventLoop + # This is a list of resume and timeout events managed outside of IOCP. @queue = Deque(Crystal::Iocp::Event).new # Returns the base IO Completion Port @@ -34,37 +35,62 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop iocp end - # Runs the event loop. + # Runs the event loop and enqueues the fiber for the next upcoming event or + # completion. def run_once : Nil + # Pull the next upcoming event from the event queue. This determines the + # timeout for waiting on the completion port. + # OPTIMIZE: Implement @queue as a priority queue in order to avoid this + # explicit search for the lowest value and dequeue more efficient. next_event = @queue.min_by(&.wake_at) - if next_event - now = Time.monotonic + unless next_event + Crystal::System.print_error "Warning: No runnables in scheduler. Exiting program.\n" + ::exit + end - if next_event.wake_at > now - sleep_time = next_event.wake_at - now - timed_out = IO::Overlapped.wait_queued_completions(sleep_time.total_milliseconds) do |fiber| - Crystal::Scheduler.enqueue fiber - end + now = Time.monotonic - return unless timed_out - end + if next_event.wake_at > now + wait_time = next_event.wake_at - now + # There is no event ready to wake. So we wait for completions with a + # timeout for the next event wake time. - dequeue next_event + timed_out = IO::Overlapped.wait_queued_completions(wait_time.total_milliseconds) do |fiber| + # This block may run multiple times. Every single fiber gets enqueued. + Crystal::Scheduler.enqueue fiber + end - fiber = next_event.fiber + # If the wait for completion timed out we've reached the wake time and + # continue with waking `next_event`. + return unless timed_out + end - unless fiber.dead? - if next_event.timeout? && (select_action = fiber.timeout_select_action) - fiber.timeout_select_action = nil - select_action.time_expired(fiber) - else - Crystal::Scheduler.enqueue fiber - end - end + # next_event gets activated because its wake time is passed, either from the + # start or because completion wait has timed out. + + dequeue next_event + + fiber = next_event.fiber + + # If the waiting fiber was already shut down in the mean time, we can just + # abandon here. There's no need to go for the next event because the scheduler + # will just try again. + # OPTIMIZE: It might still be worth considering to start over from the top + # or call recursively, in order to ensure at least one fiber get enqueued. + # This would avoid the scheduler needing to looking at runnable again just + # to notice it's still empty. The lock involved there should typically be + # uncontested though, so it's probably not a big deal. + return if fiber.dead? + + # A timeout event needs special handling because it does not necessarily + # means to resume the fiber directly, in case a different select branch + # was already activated. + if next_event.timeout? && (select_action = fiber.timeout_select_action) + fiber.timeout_select_action = nil + select_action.time_expired(fiber) else - Crystal::System.print_error "Warning: No runnables in scheduler. Exiting program.\n" - ::exit + Crystal::Scheduler.enqueue fiber end end From 5a8574bf143ef835ee6d26337759640dea1ce06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 25 Mar 2024 11:52:19 +0100 Subject: [PATCH 1018/1551] Add `IO::FileDescriptor::Handle` (#14390) --- src/crystal/system/unix/file_descriptor.cr | 4 +++- src/crystal/system/win32/file_descriptor.cr | 5 ++++- src/io/file_descriptor.cr | 7 ++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 236ed8537da8..c5810b5ae15b 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -9,7 +9,9 @@ require "termios" module Crystal::System::FileDescriptor include IO::Evented - @volatile_fd : Atomic(Int32) + # Platform-specific type to represent a file descriptor handle to the operating + # system. + alias Handle = Int32 private def unbuffered_read(slice : Bytes) : Int32 evented_read(slice, "Error reading file") do diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index bd709d28d17a..a93618e14c60 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -7,7 +7,10 @@ require "io/overlapped" module Crystal::System::FileDescriptor include IO::Overlapped - @volatile_fd : Atomic(LibC::Int) + # Platform-specific type to represent a file descriptor handle to the operating + # system. + alias Handle = ::LibC::Int + @system_blocking = true private def unbuffered_read(slice : Bytes) : Int32 diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 2f1df859a3d0..00a846086210 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -5,9 +5,10 @@ class IO::FileDescriptor < IO include Crystal::System::FileDescriptor include IO::Buffered - # The raw file-descriptor. It is defined to be an `Int`, but its size is - # platform-specific. - def fd : Int + @volatile_fd : Atomic(Handle) + + # Returns the raw file-descriptor handle. Its type is platform-specific. + def fd : Handle @volatile_fd.get end From d2cf45ef45197a18c16b60a289d678a8095cf96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 26 Mar 2024 11:41:44 +0100 Subject: [PATCH 1019/1551] Fix `min_by?` in IOCP event loop `#run_once` (#14394) --- src/crystal/system/win32/event_loop_iocp.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 3889b8b25d81..ffae160c64eb 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -42,7 +42,7 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop # timeout for waiting on the completion port. # OPTIMIZE: Implement @queue as a priority queue in order to avoid this # explicit search for the lowest value and dequeue more efficient. - next_event = @queue.min_by(&.wake_at) + next_event = @queue.min_by?(&.wake_at) unless next_event Crystal::System.print_error "Warning: No runnables in scheduler. Exiting program.\n" From 649213c53d1204c9dfc4ad31d9c1b822015a27cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 26 Mar 2024 11:41:54 +0100 Subject: [PATCH 1020/1551] Refactor win32 `File::Descriptor#unbuffered_read`, `#unbuffered_write` (#14388) --- src/crystal/system/win32/file_descriptor.cr | 51 ++++++++++++--------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index a93618e14c60..89f54a4ef713 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -18,17 +18,7 @@ module Crystal::System::FileDescriptor if ConsoleUtils.console?(handle) ConsoleUtils.read(handle, slice) elsif system_blocking? - if LibC.ReadFile(handle, slice, slice.size, out bytes_read, nil) == 0 - case error = WinError.value - when .error_access_denied? - raise IO::Error.new "File not open for reading", target: self - when .error_broken_pipe? - return 0_i32 - else - raise IO::Error.from_os_error("Error reading file", error, target: self) - end - end - bytes_read.to_i32 + unbuffered_read_blocking(handle, slice) else overlapped_operation(handle, "ReadFile", read_timeout) do |overlapped| ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped) @@ -37,20 +27,25 @@ module Crystal::System::FileDescriptor end end + private def unbuffered_read_blocking(handle, slice) + ret = LibC.ReadFile(handle, slice, slice.size, out bytes_read, nil) + return bytes_read.to_i32 unless ret.zero? + + case error = WinError.value + when .error_access_denied? + raise IO::Error.new "File not open for reading", target: self + when .error_broken_pipe? + return 0_i32 + else + raise IO::Error.from_os_error("Error reading file", error, target: self) + end + end + private def unbuffered_write(slice : Bytes) : Nil handle = windows_handle until slice.empty? if system_blocking? - if LibC.WriteFile(handle, slice, slice.size, out bytes_written, nil) == 0 - case error = WinError.value - when .error_access_denied? - raise IO::Error.new "File not open for writing", target: self - when .error_broken_pipe? - return 0_u32 - else - raise IO::Error.from_os_error("Error writing file", error, target: self) - end - end + bytes_written = unbuffered_write_blocking(handle, slice) else bytes_written = overlapped_operation(handle, "WriteFile", write_timeout, writing: true) do |overlapped| ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped) @@ -62,6 +57,20 @@ module Crystal::System::FileDescriptor end end + private def unbuffered_write_blocking(handle, slice) + ret = LibC.WriteFile(handle, slice, slice.size, out bytes_written, nil) + return bytes_written unless ret.zero? + + case error = WinError.value + when .error_access_denied? + raise IO::Error.new "File not open for writing", target: self + when .error_broken_pipe? + return 0_u32 + else + raise IO::Error.from_os_error("Error writing file", error, target: self) + end + end + private def system_blocking? @system_blocking end From 34e9171aacc2b9433b388d858b34cca86e1ad206 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 27 Mar 2024 02:08:20 +0800 Subject: [PATCH 1021/1551] Use `Fiber::StackPool` in the interpreter (#14395) --- src/compiler/crystal/interpreter/context.cr | 12 ++++-------- src/crystal/system/unix/fiber.cr | 7 +++++-- src/crystal/system/wasi/fiber.cr | 2 +- src/crystal/system/win32/fiber.cr | 10 ++++++---- src/fiber/stack_pool.cr | 16 ++++++++++++++-- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr index bc44e9302a9e..50e36a3ff8b7 100644 --- a/src/compiler/crystal/interpreter/context.cr +++ b/src/compiler/crystal/interpreter/context.cr @@ -75,7 +75,7 @@ class Crystal::Repl::Context end # This is a stack pool, for checkout_stack. - @stack_pool = [] of UInt8* + @stack_pool = Fiber::StackPool.new(protect: false) # Mapping of types to numeric ids @type_to_id = {} of Type => Int32 @@ -106,16 +106,12 @@ class Crystal::Repl::Context # Once the block returns, the stack is returned to the pool. # The stack is not cleared after or before it's used. def checkout_stack(& : UInt8* -> _) - if @stack_pool.empty? - stack = Pointer(Void).malloc(8 * 1024 * 1024).as(UInt8*) - else - stack = @stack_pool.pop - end + stack, _ = @stack_pool.checkout begin - yield stack + yield stack.as(UInt8*) ensure - @stack_pool.push(stack) + @stack_pool.release(stack) end end diff --git a/src/crystal/system/unix/fiber.cr b/src/crystal/system/unix/fiber.cr index 2a06b3d3d1a1..317a3f7fbd41 100644 --- a/src/crystal/system/unix/fiber.cr +++ b/src/crystal/system/unix/fiber.cr @@ -1,7 +1,7 @@ require "c/sys/mman" module Crystal::System::Fiber - def self.allocate_stack(stack_size) : Void* + def self.allocate_stack(stack_size, protect) : Void* flags = LibC::MAP_PRIVATE | LibC::MAP_ANON {% if flag?(:openbsd) %} flags |= LibC::MAP_STACK @@ -14,7 +14,10 @@ module Crystal::System::Fiber LibC.madvise(pointer, stack_size, LibC::MADV_NOHUGEPAGE) {% end %} - LibC.mprotect(pointer, 4096, LibC::PROT_NONE) + if protect + LibC.mprotect(pointer, 4096, LibC::PROT_NONE) + end + pointer end diff --git a/src/crystal/system/wasi/fiber.cr b/src/crystal/system/wasi/fiber.cr index cabb06a9cd6e..516fcc10a29a 100644 --- a/src/crystal/system/wasi/fiber.cr +++ b/src/crystal/system/wasi/fiber.cr @@ -1,5 +1,5 @@ module Crystal::System::Fiber - def self.allocate_stack(stack_size) : Void* + def self.allocate_stack(stack_size, protect) : Void* LibC.malloc(stack_size) end diff --git a/src/crystal/system/win32/fiber.cr b/src/crystal/system/win32/fiber.cr index 4db7d8df9e93..9e6495ee594e 100644 --- a/src/crystal/system/win32/fiber.cr +++ b/src/crystal/system/win32/fiber.cr @@ -13,7 +13,7 @@ module Crystal::System::Fiber system_info.dwPageSize + RESERVED_STACK_SIZE end - def self.allocate_stack(stack_size) : Void* + def self.allocate_stack(stack_size, protect) : Void* unless memory_pointer = LibC.VirtualAlloc(nil, stack_size, LibC::MEM_COMMIT | LibC::MEM_RESERVE, LibC::PAGE_READWRITE) raise RuntimeError.from_winerror("VirtualAlloc") end @@ -21,9 +21,11 @@ module Crystal::System::Fiber # Detects stack overflows by guarding the top of the stack, similar to # `LibC.mprotect`. Windows will fail to allocate a new guard page for these # fiber stacks and trigger a stack overflow exception - if LibC.VirtualProtect(memory_pointer, @@total_reserved_size, LibC::PAGE_READWRITE | LibC::PAGE_GUARD, out _) == 0 - LibC.VirtualFree(memory_pointer, 0, LibC::MEM_RELEASE) - raise RuntimeError.from_winerror("VirtualProtect") + if protect + if LibC.VirtualProtect(memory_pointer, @@total_reserved_size, LibC::PAGE_READWRITE | LibC::PAGE_GUARD, out _) == 0 + LibC.VirtualFree(memory_pointer, 0, LibC::MEM_RELEASE) + raise RuntimeError.from_winerror("VirtualProtect") + end end memory_pointer diff --git a/src/fiber/stack_pool.cr b/src/fiber/stack_pool.cr index aebd82a0870f..c9ea3ceb68e0 100644 --- a/src/fiber/stack_pool.cr +++ b/src/fiber/stack_pool.cr @@ -5,10 +5,22 @@ class Fiber class StackPool STACK_SIZE = 8 * 1024 * 1024 - def initialize + # If *protect* is true, guards all top pages (pages with the lowest address + # values) in the allocated stacks; accessing them triggers an error + # condition, allowing stack overflows on non-main fibers to be detected. + # + # Interpreter stacks grow upwards (pushing values increases the stack + # pointer value) rather than downwards, so *protect* must be false. + def initialize(@protect : Bool = true) @deque = Deque(Void*).new end + def finalize + @deque.each do |stack| + Crystal::System::Fiber.free_stack(stack, STACK_SIZE) + end + end + # Removes and frees at most *count* stacks from the top of the pool, # returning memory to the operating system. def collect(count = lazy_size // 2) : Nil @@ -30,7 +42,7 @@ class Fiber # Removes a stack from the bottom of the pool, or allocates a new one. def checkout : {Void*, Void*} - stack = @deque.pop? || Crystal::System::Fiber.allocate_stack(STACK_SIZE) + stack = @deque.pop? || Crystal::System::Fiber.allocate_stack(STACK_SIZE, @protect) {stack, stack + STACK_SIZE} end From ac29625fe48494774802ce4d0ff534aff3681e07 Mon Sep 17 00:00:00 2001 From: Josh Rickard Date: Tue, 26 Mar 2024 13:09:05 -0500 Subject: [PATCH 1022/1551] Fix typo in Signal docs (#14400) --- src/signal.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/signal.cr b/src/signal.cr index 50360b73c511..e0f59a9f57d3 100644 --- a/src/signal.cr +++ b/src/signal.cr @@ -96,7 +96,7 @@ enum Signal : Int32 # # After executing this, whenever the current process receives the # corresponding signal, the passed function will be called (instead of the OS - # default). The handler will run in a signal-safe fiber thought the event + # default). The handler will run in a signal-safe fiber throughout the event # loop; there is no limit to what functions can be called, unlike raw signals # that run on the sigaltstack. # From c52b04184eb32d420c5a94491bc5c31de9117e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 26 Mar 2024 22:03:52 +0100 Subject: [PATCH 1023/1551] Revert "Refactor win32 `File::Descriptor#unbuffered_read`, `#unbuffered_write`" (#14403) --- src/crystal/system/win32/file_descriptor.cr | 51 +++++++++------------ 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 89f54a4ef713..a93618e14c60 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -18,7 +18,17 @@ module Crystal::System::FileDescriptor if ConsoleUtils.console?(handle) ConsoleUtils.read(handle, slice) elsif system_blocking? - unbuffered_read_blocking(handle, slice) + if LibC.ReadFile(handle, slice, slice.size, out bytes_read, nil) == 0 + case error = WinError.value + when .error_access_denied? + raise IO::Error.new "File not open for reading", target: self + when .error_broken_pipe? + return 0_i32 + else + raise IO::Error.from_os_error("Error reading file", error, target: self) + end + end + bytes_read.to_i32 else overlapped_operation(handle, "ReadFile", read_timeout) do |overlapped| ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped) @@ -27,25 +37,20 @@ module Crystal::System::FileDescriptor end end - private def unbuffered_read_blocking(handle, slice) - ret = LibC.ReadFile(handle, slice, slice.size, out bytes_read, nil) - return bytes_read.to_i32 unless ret.zero? - - case error = WinError.value - when .error_access_denied? - raise IO::Error.new "File not open for reading", target: self - when .error_broken_pipe? - return 0_i32 - else - raise IO::Error.from_os_error("Error reading file", error, target: self) - end - end - private def unbuffered_write(slice : Bytes) : Nil handle = windows_handle until slice.empty? if system_blocking? - bytes_written = unbuffered_write_blocking(handle, slice) + if LibC.WriteFile(handle, slice, slice.size, out bytes_written, nil) == 0 + case error = WinError.value + when .error_access_denied? + raise IO::Error.new "File not open for writing", target: self + when .error_broken_pipe? + return 0_u32 + else + raise IO::Error.from_os_error("Error writing file", error, target: self) + end + end else bytes_written = overlapped_operation(handle, "WriteFile", write_timeout, writing: true) do |overlapped| ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped) @@ -57,20 +62,6 @@ module Crystal::System::FileDescriptor end end - private def unbuffered_write_blocking(handle, slice) - ret = LibC.WriteFile(handle, slice, slice.size, out bytes_written, nil) - return bytes_written unless ret.zero? - - case error = WinError.value - when .error_access_denied? - raise IO::Error.new "File not open for writing", target: self - when .error_broken_pipe? - return 0_u32 - else - raise IO::Error.from_os_error("Error writing file", error, target: self) - end - end - private def system_blocking? @system_blocking end From 723f8296370c3c77efb6770eb7480d88c8b4d98f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 27 Mar 2024 06:14:10 +0800 Subject: [PATCH 1024/1551] Add `Spec::Formatter#should_print_summary?` (#14397) --- src/spec/context.cr | 6 ++++-- src/spec/formatter.cr | 11 ++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/spec/context.cr b/src/spec/context.cr index c18836fe0455..643d76c25b16 100644 --- a/src/spec/context.cr +++ b/src/spec/context.cr @@ -178,10 +178,12 @@ module Spec def finish(elapsed_time, aborted = false) Spec.cli.formatters.each(&.finish(elapsed_time, aborted)) - Spec.cli.formatters.each(&.print_results(elapsed_time, aborted)) + if Spec.cli.formatters.any?(&.should_print_summary?) + print_summary(elapsed_time, aborted) + end end - def print_results(elapsed_time, aborted = false) + def print_summary(elapsed_time, aborted = false) pendings = results_for(:pending) unless pendings.empty? puts diff --git a/src/spec/formatter.cr b/src/spec/formatter.cr index cfd88f2b5a6a..b7256f80971f 100644 --- a/src/spec/formatter.cr +++ b/src/spec/formatter.cr @@ -19,7 +19,8 @@ module Spec def finish(elapsed_time, aborted) end - def print_results(elapsed_time : Time::Span, aborted : Bool) + def should_print_summary? + false end end @@ -54,8 +55,8 @@ module Spec @io.puts end - def print_results(elapsed_time : Time::Span, aborted : Bool) - Spec.cli.root_context.print_results(elapsed_time, aborted) + def should_print_summary? + true end end @@ -110,8 +111,8 @@ module Spec @io.puts Spec.color(@last_description, result.kind) end - def print_results(elapsed_time : Time::Span, aborted : Bool) - Spec.cli.root_context.print_results(elapsed_time, aborted) + def should_print_summary? + true end end From 65cd41d8062ceac661c9770c5364a0de11fffdff Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 28 Mar 2024 19:41:55 +0800 Subject: [PATCH 1025/1551] Remove `spec/interpreter_std_spec.cr` (#14399) --- .gitattributes | 2 - .github/workflows/interpreter.yml | 2 +- spec/generate_interpreter_spec.sh | 36 ---- spec/interpreter_std_spec.cr | 273 ------------------------------ spec/std/llvm/aarch64_spec.cr | 7 + spec/std/llvm/arm_abi_spec.cr | 7 + spec/std/llvm/llvm_spec.cr | 7 + spec/std/llvm/type_spec.cr | 7 + spec/std/llvm/x86_64_abi_spec.cr | 7 + spec/std/llvm/x86_abi_spec.cr | 7 + spec/std/syscall_spec.cr | 2 +- 11 files changed, 44 insertions(+), 313 deletions(-) delete mode 100755 spec/generate_interpreter_spec.sh delete mode 100644 spec/interpreter_std_spec.cr diff --git a/.gitattributes b/.gitattributes index 179a307af12c..c44328dd934e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,8 +18,6 @@ src/openssl/ssl/defaults.cr linguist-generated src/string/grapheme/properties.cr linguist-generated # produced by scripts/generate_unicode_data.cr src/unicode/data.cr linguist-generated -# produced by spec/generate_interpreter_spec.sh -spec/interpreter_std_spec.cr linguist-generated # produced by scripts/generate_grapheme_break_specs.cr spec/std/string/grapheme_break_spec.cr linguist-generated # produced by spec/generate_wasm32_spec.sh diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 35cf1f916770..1788f74351d9 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -61,4 +61,4 @@ jobs: run: chmod +x .build/crystal - name: Run std_spec with interpreter - run: SPEC_SPLIT="${{ matrix.part }}%4" bin/crystal i spec/interpreter_std_spec.cr -- --junit_output .junit/interpreter-std_spec.${{ matrix.part }}.xml + run: SPEC_SPLIT="${{ matrix.part }}%4" bin/crystal i spec/std_spec.cr -- --junit_output .junit/interpreter-std_spec.${{ matrix.part }}.xml diff --git a/spec/generate_interpreter_spec.sh b/spec/generate_interpreter_spec.sh deleted file mode 100755 index 960ab37f48db..000000000000 --- a/spec/generate_interpreter_spec.sh +++ /dev/null @@ -1,36 +0,0 @@ -#! /usr/bin/env bash -set +x - -# This script iterates through each spec file and tries to run it with interpreter. -# -# USAGE: -# -# For std spec: -# $ spec/generate_interpreter_spec.sh std spec/interpreter_std_spec.cr - -export SPEC_SUITE=$1 -export OUT_FILE=$2 -export CRYSTAL_BIN=${CRYSTAL_BIN:-./bin/crystal} -job_size=${3:-64} - -command="$0 $*" -echo "# $(date --rfc-3339 seconds) This file is autogenerated by \`${command% }\`" > $OUT_FILE - -run_spec () { - spec=$1 - require="require \"./${spec##spec/}\"" - timeout --signal=KILL 150 $CRYSTAL_BIN i $spec > /dev/null; exit=$? - - if [ $exit -eq 0 ] ; then - echo "$require" >> $OUT_FILE - else - echo "# $require (failed to run)" >> $OUT_FILE - fi -} - -export SHELL=$(type -p bash) -export -f run_spec - -find "spec/$SPEC_SUITE" -type f -iname "*_spec.cr" | LC_ALL=C sort | parallel -j $job_size run_spec {} - -cat $OUT_FILE | sort | sponge $OUT_FILE diff --git a/spec/interpreter_std_spec.cr b/spec/interpreter_std_spec.cr deleted file mode 100644 index 0bb16eeeb2a1..000000000000 --- a/spec/interpreter_std_spec.cr +++ /dev/null @@ -1,273 +0,0 @@ -# 2024-03-22 14:01:27+08:00 This file is autogenerated by `./spec/generate_interpreter_spec.sh std spec/interpreter_std_spec.cr 8` -require "./std/array_spec.cr" -require "./std/atomic_spec.cr" -require "./std/base64_spec.cr" -require "./std/benchmark_spec.cr" -require "./std/big/big_decimal_spec.cr" -require "./std/big/big_float_spec.cr" -require "./std/big/big_int_spec.cr" -require "./std/big/big_rational_spec.cr" -require "./std/big/number_spec.cr" -require "./std/bit_array_spec.cr" -require "./std/bool_spec.cr" -require "./std/box_spec.cr" -require "./std/channel_spec.cr" -require "./std/char/reader_spec.cr" -require "./std/char_spec.cr" -require "./std/class_spec.cr" -require "./std/colorize_spec.cr" -require "./std/comparable_spec.cr" -require "./std/complex_spec.cr" -require "./std/compress/deflate/deflate_spec.cr" -require "./std/compress/gzip/gzip_spec.cr" -require "./std/compress/zip/zip_file_spec.cr" -require "./std/compress/zip/zip_spec.cr" -require "./std/compress/zlib/reader_spec.cr" -require "./std/compress/zlib/stress_spec.cr" -require "./std/compress/zlib/writer_spec.cr" -require "./std/concurrent/select_spec.cr" -require "./std/concurrent_spec.cr" -require "./std/crypto/bcrypt/base64_spec.cr" -require "./std/crypto/bcrypt/password_spec.cr" -require "./std/crypto/bcrypt_spec.cr" -require "./std/crypto/blowfish_spec.cr" -require "./std/crypto/subtle_spec.cr" -require "./std/crystal/compiler_rt/ashlti3_spec.cr" -require "./std/crystal/compiler_rt/ashrti3_spec.cr" -require "./std/crystal/compiler_rt/divmod128_spec.cr" -require "./std/crystal/compiler_rt/fixint_spec.cr" -require "./std/crystal/compiler_rt/float_spec.cr" -require "./std/crystal/compiler_rt/lshrti3_spec.cr" -require "./std/crystal/compiler_rt/mulodi4_spec.cr" -require "./std/crystal/compiler_rt/mulosi4_spec.cr" -require "./std/crystal/compiler_rt/muloti4_spec.cr" -require "./std/crystal/compiler_rt/multi3_spec.cr" -require "./std/crystal/compiler_rt/powidf2_spec.cr" -require "./std/crystal/compiler_rt/powisf2_spec.cr" -require "./std/crystal/digest/md5_spec.cr" -require "./std/crystal/digest/sha1_spec.cr" -require "./std/crystal/hasher_spec.cr" -require "./std/crystal/pointer_linked_list_spec.cr" -require "./std/crystal/syntax_highlighter/colorize_spec.cr" -require "./std/crystal/syntax_highlighter/html_spec.cr" -require "./std/crystal/system_spec.cr" -require "./std/csv/csv_build_spec.cr" -require "./std/csv/csv_lex_spec.cr" -require "./std/csv/csv_parse_spec.cr" -require "./std/csv/csv_spec.cr" -require "./std/deque_spec.cr" -require "./std/digest/adler32_spec.cr" -require "./std/digest/crc32_spec.cr" -require "./std/digest/io_digest_spec.cr" -require "./std/digest/md5_spec.cr" -require "./std/digest/sha1_spec.cr" -require "./std/digest/sha256_spec.cr" -require "./std/digest/sha512_spec.cr" -require "./std/dir_spec.cr" -require "./std/double_spec.cr" -require "./std/ecr/ecr_lexer_spec.cr" -require "./std/ecr/ecr_spec.cr" -require "./std/enumerable_spec.cr" -require "./std/enum_spec.cr" -require "./std/env_spec.cr" -require "./std/errno_spec.cr" -require "./std/exception/call_stack_spec.cr" -require "./std/exception_spec.cr" -require "./std/fiber_spec.cr" -require "./std/file_spec.cr" -require "./std/file/tempfile_spec.cr" -require "./std/file_utils_spec.cr" -require "./std/float_printer/diy_fp_spec.cr" -require "./std/float_printer/grisu3_spec.cr" -require "./std/float_printer/hexfloat_spec.cr" -require "./std/float_printer/ieee_spec.cr" -require "./std/float_printer/ryu_printf_spec.cr" -require "./std/float_printer/shortest_spec.cr" -require "./std/float_spec.cr" -require "./std/gc_spec.cr" -require "./std/hash_spec.cr" -require "./std/html_spec.cr" -require "./std/http/chunked_content_spec.cr" -require "./std/http/client/client_spec.cr" -require "./std/http/client/response_spec.cr" -require "./std/http/cookie_spec.cr" -require "./std/http/formdata/builder_spec.cr" -require "./std/http/formdata/parser_spec.cr" -require "./std/http/formdata_spec.cr" -require "./std/http/headers_spec.cr" -require "./std/http/http_spec.cr" -require "./std/http/params_spec.cr" -require "./std/http/request_spec.cr" -require "./std/http/server/handlers/compress_handler_spec.cr" -require "./std/http/server/handlers/error_handler_spec.cr" -require "./std/http/server/handlers/handler_spec.cr" -require "./std/http/server/handlers/log_handler_spec.cr" -require "./std/http/server/handlers/static_file_handler_spec.cr" -require "./std/http/server/handlers/websocket_handler_spec.cr" -require "./std/http/server/request_processor_spec.cr" -require "./std/http/server/response_spec.cr" -require "./std/http/server/server_spec.cr" -require "./std/http/status_spec.cr" -require "./std/http/web_socket_spec.cr" -require "./std/humanize_spec.cr" -require "./std/indexable/mutable_spec.cr" -require "./std/indexable_spec.cr" -require "./std/ini_spec.cr" -require "./std/int_spec.cr" -require "./std/io/argf_spec.cr" -require "./std/io/buffered_spec.cr" -require "./std/io/byte_format_spec.cr" -require "./std/io/delimited_spec.cr" -require "./std/io/file_descriptor_spec.cr" -require "./std/io/hexdump_spec.cr" -require "./std/io/io_spec.cr" -require "./std/io/memory_spec.cr" -require "./std/io/multi_writer_spec.cr" -require "./std/io/sized_spec.cr" -require "./std/io/stapled_spec.cr" -require "./std/iterator_spec.cr" -require "./std/json/any_spec.cr" -require "./std/json/builder_spec.cr" -require "./std/json/lexer_spec.cr" -require "./std/json/parser_spec.cr" -require "./std/json/pull_parser_spec.cr" -require "./std/json/serializable_spec.cr" -require "./std/json/serialization_spec.cr" -require "./std/kernel_spec.cr" -require "./std/levenshtein_spec.cr" -# require "./std/llvm/aarch64_spec.cr" (failed to run) -# require "./std/llvm/arm_abi_spec.cr" (failed to run) -# require "./std/llvm/llvm_spec.cr" (failed to run) -# require "./std/llvm/type_spec.cr" (failed to run) -# require "./std/llvm/x86_64_abi_spec.cr" (failed to run) -# require "./std/llvm/x86_abi_spec.cr" (failed to run) -require "./std/log/broadcast_backend_spec.cr" -require "./std/log/builder_spec.cr" -require "./std/log/context_spec.cr" -require "./std/log/dispatch_spec.cr" -require "./std/log/env_config_spec.cr" -require "./std/log/format_spec.cr" -require "./std/log/io_backend_spec.cr" -require "./std/log/log_spec.cr" -require "./std/log/main_spec.cr" -require "./std/log/metadata_spec.cr" -require "./std/log/spec_spec.cr" -require "./std/math_spec.cr" -require "./std/mime/media_type_spec.cr" -require "./std/mime/multipart/builder_spec.cr" -require "./std/mime/multipart/parser_spec.cr" -require "./std/mime/multipart_spec.cr" -require "./std/mime_spec.cr" -require "./std/mutex_spec.cr" -require "./std/named_tuple_spec.cr" -require "./std/number_spec.cr" -require "./std/oauth2/access_token_spec.cr" -require "./std/oauth2/client_spec.cr" -require "./std/oauth2/session_spec.cr" -require "./std/oauth/access_token_spec.cr" -require "./std/oauth/authorization_header_spec.cr" -require "./std/oauth/consumer_spec.cr" -require "./std/oauth/params_spec.cr" -require "./std/oauth/request_token_spec.cr" -require "./std/oauth/signature_spec.cr" -require "./std/object_spec.cr" -require "./std/openssl/cipher_spec.cr" -require "./std/openssl/digest_spec.cr" -require "./std/openssl/hmac_spec.cr" -require "./std/openssl/pkcs5_spec.cr" -require "./std/openssl/ssl/context_spec.cr" -require "./std/openssl/ssl/hostname_validation_spec.cr" -require "./std/openssl/ssl/server_spec.cr" -require "./std/openssl/ssl/socket_spec.cr" -require "./std/openssl/x509/certificate_spec.cr" -require "./std/openssl/x509/name_spec.cr" -require "./std/option_parser_spec.cr" -require "./std/path_spec.cr" -require "./std/pointer_spec.cr" -require "./std/pp_spec.cr" -require "./std/pretty_print_spec.cr" -require "./std/process/find_executable_spec.cr" -require "./std/process_spec.cr" -require "./std/process/status_spec.cr" -require "./std/process/utils_spec.cr" -require "./std/proc_spec.cr" -require "./std/raise_spec.cr" -require "./std/random/isaac_spec.cr" -require "./std/random/pcg32_spec.cr" -require "./std/random/secure_spec.cr" -require "./std/random_spec.cr" -require "./std/range_spec.cr" -require "./std/record_spec.cr" -require "./std/reference_spec.cr" -require "./std/regex/match_data_spec.cr" -require "./std/regex_spec.cr" -require "./std/semantic_version_spec.cr" -require "./std/set_spec.cr" -require "./std/signal_spec.cr" -require "./std/slice_spec.cr" -require "./std/socket/address_spec.cr" -require "./std/socket/addrinfo_spec.cr" -require "./std/socket/socket_spec.cr" -require "./std/socket/tcp_server_spec.cr" -require "./std/socket/tcp_socket_spec.cr" -require "./std/socket/udp_socket_spec.cr" -require "./std/socket/unix_server_spec.cr" -require "./std/socket/unix_socket_spec.cr" -require "./std/spec/context_spec.cr" -require "./std/spec/expectations_spec.cr" -require "./std/spec/filters_spec.cr" -require "./std/spec/helpers/iterate_spec.cr" -require "./std/spec/hooks_spec.cr" -require "./std/spec/junit_formatter_spec.cr" -require "./std/spec/list_tags_spec.cr" -require "./std/spec_spec.cr" -require "./std/spec/tap_formatter_spec.cr" -require "./std/sprintf_spec.cr" -require "./std/static_array_spec.cr" -require "./std/string_builder_spec.cr" -require "./std/string/grapheme_break_spec.cr" -require "./std/string/grapheme_spec.cr" -require "./std/string_pool_spec.cr" -require "./std/string_scanner_spec.cr" -require "./std/string_spec.cr" -require "./std/string/utf16_spec.cr" -require "./std/struct_spec.cr" -require "./std/symbol_spec.cr" -# require "./std/syscall_spec.cr" (failed to run) -require "./std/system_error_spec.cr" -require "./std/system/group_spec.cr" -require "./std/system_spec.cr" -require "./std/system/user_spec.cr" -require "./std/thread/condition_variable_spec.cr" -require "./std/thread/mutex_spec.cr" -require "./std/thread_spec.cr" -require "./std/time/custom_formats_spec.cr" -# require "./std/time/format_spec.cr" (failed to run) -require "./std/time/location_spec.cr" -require "./std/time/span_spec.cr" -# require "./std/time/time_spec.cr" (failed to run) -require "./std/tuple_spec.cr" -require "./std/uint_spec.cr" -require "./std/uri/params_spec.cr" -require "./std/uri/punycode_spec.cr" -require "./std/uri_spec.cr" -require "./std/uuid/json_spec.cr" -require "./std/uuid_spec.cr" -require "./std/uuid/yaml_spec.cr" -require "./std/va_list_spec.cr" -require "./std/weak_ref_spec.cr" -require "./std/winerror_spec.cr" -require "./std/xml/builder_spec.cr" -require "./std/xml/html_spec.cr" -require "./std/xml/reader_spec.cr" -require "./std/xml/xml_spec.cr" -require "./std/xml/xpath_spec.cr" -require "./std/yaml/any_spec.cr" -require "./std/yaml/builder_spec.cr" -require "./std/yaml/nodes/builder_spec.cr" -require "./std/yaml/schema/core_spec.cr" -require "./std/yaml/schema/fail_safe_spec.cr" -require "./std/yaml/serializable_spec.cr" -require "./std/yaml/serialization_spec.cr" -require "./std/yaml/yaml_pull_parser_spec.cr" -require "./std/yaml/yaml_spec.cr" diff --git a/spec/std/llvm/aarch64_spec.cr b/spec/std/llvm/aarch64_spec.cr index 41a308b480ec..6e2bac04dc47 100644 --- a/spec/std/llvm/aarch64_spec.cr +++ b/spec/std/llvm/aarch64_spec.cr @@ -1,4 +1,11 @@ require "spec" + +{% if flag?(:interpreted) && !flag?(:win32) %} + # TODO: figure out how to link against libstdc++ in interpreted code (#14398) + pending LLVM::ABI::AArch64 + {% skip_file %} +{% end %} + require "llvm" {% if LibLLVM::BUILT_TARGETS.includes?(:aarch64) %} diff --git a/spec/std/llvm/arm_abi_spec.cr b/spec/std/llvm/arm_abi_spec.cr index 98ae9b588a41..8132ca0a38ce 100644 --- a/spec/std/llvm/arm_abi_spec.cr +++ b/spec/std/llvm/arm_abi_spec.cr @@ -1,4 +1,11 @@ require "spec" + +{% if flag?(:interpreted) && !flag?(:win32) %} + # TODO: figure out how to link against libstdc++ in interpreted code (#14398) + pending LLVM::ABI::ARM + {% skip_file %} +{% end %} + require "llvm" {% if LibLLVM::BUILT_TARGETS.includes?(:arm) %} diff --git a/spec/std/llvm/llvm_spec.cr b/spec/std/llvm/llvm_spec.cr index 08edeaa5219c..d232931db848 100644 --- a/spec/std/llvm/llvm_spec.cr +++ b/spec/std/llvm/llvm_spec.cr @@ -1,4 +1,11 @@ require "spec" + +{% if flag?(:interpreted) && !flag?(:win32) %} + # TODO: figure out how to link against libstdc++ in interpreted code (#14398) + pending LLVM + {% skip_file %} +{% end %} + require "llvm" describe LLVM do diff --git a/spec/std/llvm/type_spec.cr b/spec/std/llvm/type_spec.cr index 94e34f226250..8c6b99662ca2 100644 --- a/spec/std/llvm/type_spec.cr +++ b/spec/std/llvm/type_spec.cr @@ -1,4 +1,11 @@ require "spec" + +{% if flag?(:interpreted) && !flag?(:win32) %} + # TODO: figure out how to link against libstdc++ in interpreted code (#14398) + pending LLVM::Type + {% skip_file %} +{% end %} + require "llvm" describe LLVM::Type do diff --git a/spec/std/llvm/x86_64_abi_spec.cr b/spec/std/llvm/x86_64_abi_spec.cr index 0ba644cefa01..8b971a679c2a 100644 --- a/spec/std/llvm/x86_64_abi_spec.cr +++ b/spec/std/llvm/x86_64_abi_spec.cr @@ -1,4 +1,11 @@ require "spec" + +{% if flag?(:interpreted) && !flag?(:win32) %} + # TODO: figure out how to link against libstdc++ in interpreted code (#14398) + pending LLVM::ABI::X86_64 + {% skip_file %} +{% end %} + require "llvm" {% if LibLLVM::BUILT_TARGETS.includes?(:x86) %} diff --git a/spec/std/llvm/x86_abi_spec.cr b/spec/std/llvm/x86_abi_spec.cr index 27d387820298..b79ebc4d4d5c 100644 --- a/spec/std/llvm/x86_abi_spec.cr +++ b/spec/std/llvm/x86_abi_spec.cr @@ -1,6 +1,13 @@ {% skip_file if flag?(:win32) %} # 32-bit windows is not supported require "spec" + +{% if flag?(:interpreted) %} + # TODO: figure out how to link against libstdc++ in interpreted code (#14398) + pending LLVM::ABI::X86 + {% skip_file %} +{% end %} + require "llvm" {% if LibLLVM::BUILT_TARGETS.includes?(:x86) %} diff --git a/spec/std/syscall_spec.cr b/spec/std/syscall_spec.cr index 61bb61f4a837..11698e80dda5 100644 --- a/spec/std/syscall_spec.cr +++ b/spec/std/syscall_spec.cr @@ -1,4 +1,4 @@ -{% skip_file unless flag?(:linux) %} +{% skip_file unless flag?(:linux) && !flag?(:interpreted) %} require "spec" require "syscall" From 9d3fcadf701fa211b90bda4128a25e54236e59eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 28 Mar 2024 22:25:53 +0100 Subject: [PATCH 1026/1551] Update shards 0.18.0 (#14411) --- .github/workflows/win_build_portable.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 5ad78feceedc..d833d9d4b1ee 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -126,7 +126,7 @@ jobs: uses: actions/checkout@v4 with: repository: crystal-lang/shards - ref: v0.17.4 + ref: v0.18.0 path: shards - name: Download molinillo release From 58456994ef424ee8700b21c264ebacbf0f2cda57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 2 Apr 2024 13:37:12 +0200 Subject: [PATCH 1027/1551] Fix `Milestone` JSON bindings in `github-changelog` helper (#14404) --- scripts/github-changelog.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index f2d277f258e7..f33238aae750 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -87,7 +87,7 @@ end record Milestone, closed_at : Time?, - description : String, + description : String?, due_on : Time?, title : String, pull_requests : Array(PullRequest) do From 2b50e43263a2a6d733c5110b37eb9ffd6f4892f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 2 Apr 2024 13:37:20 +0200 Subject: [PATCH 1028/1551] Make repository configurable for reusable `github-changelog` (#14407) --- scripts/github-changelog.cr | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index f33238aae750..af8f7566d83a 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -19,11 +19,20 @@ require "http/client" require "json" abort "Missing GITHUB_TOKEN env variable" unless ENV["GITHUB_TOKEN"]? -abort "Missing argument" unless ARGV.first? - api_token = ENV["GITHUB_TOKEN"] -repository = "crystal-lang/crystal" -milestone = ARGV.first + +case ARGV.size +when 0 + abort "Missing argument" +when 1 + repository = "crystal-lang/crystal" + milestone = ARGV.first +when 2 + repository = ARGV[0] + milestone = ARGV[1] +else + abort "Too many arguments. Usage:\n #{PROGRAM_NAME} [] " +end def query_prs(api_token, repository, milestone) query = <<-GRAPHQL @@ -308,7 +317,7 @@ if description = milestone.description.presence puts "_" end puts -puts "[#{milestone.title}]: https://github.com/crystal-lang/crystal/releases/#{milestone.title}" +puts "[#{milestone.title}]: https://github.com/#{repository}/releases/#{milestone.title}" puts SECTION_TITLES.each do |id, title| From 0bc681e9fe6e41ff900dcd20e7b582878e77643d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 2 Apr 2024 13:37:31 +0200 Subject: [PATCH 1029/1551] Use link refs for PR links in changelog (#14406) --- scripts/github-changelog.cr | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index af8f7566d83a..23a2d3280f89 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -152,13 +152,23 @@ record PullRequest, io << "**[deprecation]** " end io << title.sub(/^\[?(?:#{type}|#{sub_topic})(?::|\]:?) /i, "") << " (" - io << "[#" << number << "](" << permalink << ")" + link_ref(io) if author = self.author io << ", thanks @" << author end io << ")" end + def link_ref(io) + io << "[#" << number << "]" + end + + def print_ref_label(io) + link_ref(io) + io << ": " << permalink + io.puts + end + def <=>(other : self) sort_tuple <=> other.sort_tuple end @@ -344,5 +354,8 @@ SECTION_TITLES.each do |id, title| puts "- #{pr}" end puts + + topic_prs.each(&.print_ref_label(STDOUT)) + puts end end From 4070f591188f4cc2f1f6b94960d9bb66cbc50360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 2 Apr 2024 13:37:53 +0200 Subject: [PATCH 1030/1551] Implement pagination for GitHub API in `github-changelog` helper (#14412) --- scripts/github-changelog.cr | 55 ++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index 23a2d3280f89..765a0c281939 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -34,9 +34,9 @@ else abort "Too many arguments. Usage:\n #{PROGRAM_NAME} [] " end -def query_prs(api_token, repository, milestone) +def query_prs(api_token, repository, milestone : String, cursor : String?) query = <<-GRAPHQL - query($milestone: String, $owner: String!, $repository: String!) { + query($milestone: String, $owner: String!, $repository: String!, $cursor: String) { repository(owner: $owner, name: $repository) { milestones(query: $milestone, first: 1) { nodes { @@ -44,7 +44,7 @@ def query_prs(api_token, repository, milestone) description dueOn title - pullRequests(first: 300) { + pullRequests(first: 100, after: $cursor) { nodes { number title @@ -59,6 +59,10 @@ def query_prs(api_token, repository, milestone) } } } + pageInfo { + endCursor + hasNextPage + } } } } @@ -71,6 +75,7 @@ def query_prs(api_token, repository, milestone) owner: owner, repository: name, milestone: milestone, + cursor: cursor, } response = HTTP::Client.post("https://api.github.com/graphql", @@ -288,21 +293,45 @@ record PullRequest, end end -response = query_prs(api_token, repository, milestone) -parser = JSON::PullParser.new(response.body) -milestone = parser.on_key! "data" do - parser.on_key! "repository" do - parser.on_key! "milestones" do - parser.on_key! "nodes" do - parser.read_begin_array - milestone = Milestone.new(parser) - parser.read_end_array - milestone +def query_milestone(api_token, repository, number) + cursor = nil + milestone = nil + + while true + response = query_prs(api_token, repository, number, cursor) + + parser = JSON::PullParser.new(response.body) + m = parser.on_key! "data" do + parser.on_key! "repository" do + parser.on_key! "milestones" do + parser.on_key! "nodes" do + parser.read_begin_array + Milestone.new(parser) + ensure + parser.read_end_array + end + end end end + + if milestone + milestone.pull_requests.concat m.pull_requests + else + milestone = m + end + + json = JSON.parse(response.body) + page_info = json.dig("data", "repository", "milestones", "nodes", 0, "pullRequests", "pageInfo") + break unless page_info["hasNextPage"].as_bool + + cursor = page_info["endCursor"].as_s end + + milestone end +milestone = query_milestone(api_token, repository, milestone) + sections = milestone.pull_requests.group_by(&.section) SECTION_TITLES = { From 23d535115cfe7e0ba46c97290dcb03b0b6f1df99 Mon Sep 17 00:00:00 2001 From: summer-alice <77705195+summer-alice@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:38:03 +1000 Subject: [PATCH 1031/1551] Improve OpenSSL module documentation (#14410) Co-authored-by: Julien Portalier --- src/openssl.cr | 98 +++++++++++++++++++++++++------------- src/openssl/cipher.cr | 4 ++ src/openssl/ssl/context.cr | 20 ++++++-- 3 files changed, 84 insertions(+), 38 deletions(-) diff --git a/src/openssl.cr b/src/openssl.cr index 4185518d3982..986cd5d29fc9 100644 --- a/src/openssl.cr +++ b/src/openssl.cr @@ -1,69 +1,99 @@ require "./openssl/lib_ssl" require "./openssl/error" -# NOTE: To use `OpenSSL`, you must explicitly import it with `require "openssl"` +# The OpenSSL module allows for access to Secure Sockets Layer (SSL) and Transport Layer Security (TLS) +# encryption, as well as classes for encrypting data, decrypting data, and computing hashes. It uses +# the SSL library provided by the operating system, which may be either [OpenSSL](https://openssl.org) +# or [LibreSSL](https://www.libressl.org). # -# ## OpenSSL Integration +# WARNING: This module should not be used without first reading the [Security considerations](#security-considerations). # -# - TLS sockets need a context, potentially with keys (required for servers) and configuration. -# - TLS sockets will wrap the underlying TCP socket, and any further communication must happen through the `OpenSSL::SSL::Socket` only. +# To create secure sockets, use either `OpenSSL::SSL::Socket::Client` for client applications, or +# `OpenSSL::SSL::Socket::Server` for servers. These classes use a default context, but you can provide +# your own by supplying an `OpenSSL::SSL::Context`. For more control, consider subclassing `OpenSSL::SSL::Socket`. # -# ## Usage Example +# Hashing algorithms are provided by classes such as `Digest::SHA256` and `Digest::MD5`. If you need +# a different option, you can initialize one using the name of the digest with `OpenSSL::Digest`. +# A Hash-based Message Authentication Code (HMAC) can be computed using `HMAC` and specifying a digest +# `Algorithm`. +# +# The `OpenSSL::Cipher` class can be used for encrypting and decrypting data. +# +# NOTE: To use `OpenSSL`, you must explicitly import it using the `require "openssl"` statement. +# +# ## Security Considerations +# +# Crystal aims to provide reasonable configuration defaults in accordance with +# [Mozilla's recommendations](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29). +# However, these defaults may not be suitable for your application. It is recommended that you refer +# to the Open Worldwide Application Security Project (OWASP) cheat sheet on +# [implementing transport layer protection](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html) +# to ensure the appropriate configuration for your use. # -# Recommended ciphers can be taken from: -# - [OWASP Wiki](https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Cryptographic_Ciphers) -# - [Cipherli.st](https://cipherli.st/) -# - A full list is available at the [OpenSSL Docs](https://www.openssl.org/docs/man1.1.0/apps/ciphers.html#CIPHER-STRINGS) +# If you come across any shortcomings or spots for improvement in Crystal's configuration options, +# please don't hesitate to let us know by [opening an issue](https://github.com/crystal-lang/crystal/issues/new). # -# Do note that: -# - Crystal does its best to provide sane configuration defaults (see [Mozilla-Intermediate](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29)). -# - Linked version of OpenSSL need to be checked for supporting specific protocols and ciphers. -# - If any configurations or choices in Crystal regarding SSL settings and security are found to be lacking or need -# improvement please [open an issue](https://github.com/crystal-lang/crystal/issues/new) and let us know. +# ## Usage Example # # ### Server side # -# NOTE: For the below example to work, a key pair should be attained. +# An `SSL` server is created with a `TCPServer` and a `SSL::Context`. You can then use the +# SSL server like an ordinary TCP server. +# +# NOTE: For the below example to work, a certificate and private key should be attained. # # ``` -# require "socket" # require "openssl" +# require "socket" # -# def server -# # Bind new TCPSocket to port 5555 -# socket = TCPServer.new(5555) +# PORT = ENV["PORT"] ||= "5555" # -# context = OpenSSL::SSL::Context::Server.new -# context.private_key = "/path/to/private.key" -# context.certificate_chain = "/path/to/public.cert" +# # Bind new TCPServer to PORT +# socket = TCPServer.new(PORT.to_i) # -# puts "Server is up" +# context = OpenSSL::SSL::Context::Server.new +# context.private_key = "/path/to/private.key" +# context.certificate_chain = "/path/to/public.cert" # -# socket.accept do |client| -# puts "Got client" +# puts "Server is up. Listening on port #{PORT}." # -# bytes = Bytes.new(20) +# socket.accept do |client| +# puts "Got client" # -# ssl_socket = OpenSSL::SSL::Socket::Server.new(client, context) -# ssl_socket.read(bytes) +# bytes = Bytes.new(20) # -# puts String.new(bytes) +# OpenSSL::SSL::Socket::Server.open(client, context) do |ssl_socket| +# ssl_socket.read(bytes) # end +# +# puts "Client said: #{String.new(bytes)}" # end +# +# socket.close +# puts "Server has stopped." # ``` # # ### Client side # +# An `SSL` client is created with a `TCPSocket` and a `SSL::Context`. Unlike a SSL server, +# a client does not require a certificate or private key. +# +# NOTE: By default, closing an `SSL::Socket` does not close the underlying socket. You need to +# set `SSL::Socket#sync_close=` to true if you want this behaviour. +# # ``` -# require "socket" # require "openssl" +# require "socket" # -# def client -# socket = TCPSocket.new("127.0.0.1", 5555) +# PORT = ENV["PORT"] ||= "5555" +# +# # Bind TCPSocket to PORT and open a connection +# TCPSocket.open("127.0.0.1", PORT) do |socket| # context = OpenSSL::SSL::Context::Client.new # -# ssl_socket = OpenSSL::SSL::Socket::Client.new(socket, context) -# ssl_socket << "Testing" +# OpenSSL::SSL::Socket::Client.open(socket, context) do |ssl_socket| +# ssl_socket << "Hello from client!" +# end # end # ``` module OpenSSL diff --git a/src/openssl/cipher.cr b/src/openssl/cipher.cr index bea9a298fc97..c24c821d54dc 100644 --- a/src/openssl/cipher.cr +++ b/src/openssl/cipher.cr @@ -3,6 +3,10 @@ require "openssl" # A class which can be used to encrypt and decrypt data using a specified cipher. # +# NOTE: The ciphers available to an application are determined by the linked version of the system SSL library. +# A comprehensive list of ciphers can be found in the +# [OpenSSL Cipher documentation](https://www.openssl.org/docs/man3.0/man1/openssl-ciphers.html#CIPHER-STRINGS). +# # ``` # require "random/secure" # diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr index a46e924346a7..5db3e754e79b 100644 --- a/src/openssl/ssl/context.cr +++ b/src/openssl/ssl/context.cr @@ -294,19 +294,31 @@ abstract class OpenSSL::SSL::Context raise OpenSSL::Error.new("SSL_CTX_use_PrivateKey_file") unless ret == 1 end - # Specify a list of TLS ciphers to use or discard. + # Specify a list of TLS ciphers to use or discard for TLSv1.2 and below. # - # This affects only TLSv1.2 and below. See `#security_level=` for some - # sensible system configuration. + # See `#security_level=` for some sensible system configuration. + # + # This method does not impact TLSv1.3 ciphersuites. Use `#cipher_suites=` + # to configure those. + # + # NOTE: The ciphers available to an application are determined by the + # linked version of the system SSL library. A comprehensive list + # of ciphers can be found in the + # [OpenSSL Cipher documentation](https://www.openssl.org/docs/man3.0/man1/openssl-ciphers.html#CIPHER-STRINGS). def ciphers=(ciphers : String) ret = LibSSL.ssl_ctx_set_cipher_list(@handle, ciphers) raise OpenSSL::Error.new("SSL_CTX_set_cipher_list") if ret == 0 ciphers end - # Specify a list of TLS cipher suites to use or discard. + # Specify a list of TLS ciphersuites to use or discard for TLSv1.3. # # See `#security_level=` for some sensible system configuration. + # + # NOTE: The ciphersuites available to an application are determined by the + # linked version of the system SSL library. A comprehensive list + # of ciphersuites can be found in the + # [OpenSSL Cipher documentation](https://www.openssl.org/docs/man3.0/man1/openssl-ciphers.html#TLS-v1.3-cipher-suites). def cipher_suites=(cipher_suites : String) {% if LibSSL.has_method?(:ssl_ctx_set_ciphersuites) %} ret = LibSSL.ssl_ctx_set_ciphersuites(@handle, cipher_suites) From ca25eeccdd332b77a037315738608a01343e9759 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 3 Apr 2024 16:17:59 +0800 Subject: [PATCH 1032/1551] Use `Makefile.win` for Shards on Windows CI (#14414) --- .github/workflows/win_build_portable.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index d833d9d4b1ee..13f4e73a0d51 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -129,22 +129,15 @@ jobs: ref: v0.18.0 path: shards - - name: Download molinillo release - uses: actions/checkout@v4 - with: - repository: crystal-lang/crystal-molinillo - ref: v0.2.0 - path: shards/lib/molinillo - - name: Build shards release working-directory: ./shards - run: ../bin/crystal.bat build ${{ inputs.release && '--release' || '' }} src/shards.cr + run: make -f Makefile.win ${{ inputs.release && 'release=1' || '' }} - name: Gather Crystal binaries run: | make -f Makefile.win install prefix=crystal mkdir crystal/lib - cp shards/shards.exe crystal/ + cp shards/bin/shards.exe crystal/ cp libs/* crystal/lib/ cp dlls/* crystal/ cp README.md crystal/ From 47bb15b021608e2c45372807d616922c1103534c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 3 Apr 2024 16:18:17 +0800 Subject: [PATCH 1033/1551] Fix `Hash#update` when default block also adds given key (#14417) --- spec/std/hash_spec.cr | 11 +++++++++++ src/hash.cr | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/spec/std/hash_spec.cr b/spec/std/hash_spec.cr index 66f9c7a9adee..ed8b9931cd82 100644 --- a/spec/std/hash_spec.cr +++ b/spec/std/hash_spec.cr @@ -271,6 +271,17 @@ describe "Hash" do h[3000].should eq(3000 + 42) end + it "doesn't create a duplicate key, if key does not exist and default block adds the given key (#14416)" do + h = Hash(String, Int32).new do |h, new_key| + h[new_key] = 1 + new_key.size + end + + h.update("new key") { |v| v * 6 } + h.size.should eq(1) + h["new key"].should eq(7 * 6) + end + it "inserts a new entry using the default value as input, if key does not exist" do h = Hash(String, Int32).new(2) diff --git a/src/hash.cr b/src/hash.cr index 082f2a0a80e0..1a40a36f2107 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -1163,7 +1163,9 @@ class Hash(K, V) entry.value elsif block = @block default_value = block.call(self, key) - insert_new(key, yield default_value) + + # NOTE: can't use `#insert_new` as `block` might add arbitrary keys + upsert(key, yield default_value) default_value else raise KeyError.new "Missing hash key: #{key.inspect}" From 8ca32e8654ecf6b5249f59a5f40850e0090b8217 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 5 Apr 2024 01:13:46 +0800 Subject: [PATCH 1034/1551] Fix `Hash#put_if_absent` putting duplicate keys (#14427) --- spec/std/hash_spec.cr | 6 +++++ src/hash.cr | 54 +------------------------------------------ 2 files changed, 7 insertions(+), 53 deletions(-) diff --git a/spec/std/hash_spec.cr b/spec/std/hash_spec.cr index ed8b9931cd82..d098c8f7bf11 100644 --- a/spec/std/hash_spec.cr +++ b/spec/std/hash_spec.cr @@ -216,6 +216,12 @@ describe "Hash" do h.should eq({1 => v, 2 => ["2"]}) h[1].should be(v) end + + it "doesn't put duplicate keys (#14425)" do + h = {1 => 2} + h.put_if_absent(3) { h[3] = 4 }.should eq(4) + h.should eq({1 => 2, 3 => 4}) + end end describe "update" do diff --git a/src/hash.cr b/src/hash.cr index 1a40a36f2107..b245a201ae95 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -419,56 +419,6 @@ class Hash(K, V) end end - # Inserts a key-value pair. Assumes that the given key doesn't exist. - private def insert_new(key, value) - # Unless otherwise noted, this body should be identical to `#upsert` - - if @entries.null? - @indices_size_pow2 = 3 - @entries = malloc_entries(4) - end - - hash = key_hash(key) - - if @indices.null? - # don't call `#update_linear_scan` here - - if !entries_full? - add_entry_and_increment_size(hash, key, value) - return - end - - resize - - if @indices.null? - add_entry_and_increment_size(hash, key, value) - return - end - end - - index = fit_in_indices(hash) - - while true - entry_index = get_index(index) - - if entry_index == -1 - if entries_full? - resize - index = fit_in_indices(hash) - next - end - - set_index(index, entries_size) - add_entry_and_increment_size(hash, key, value) - return - end - - # don't call `#get_entry` and `#entry_matches?` here - - index = next_index(index) - end - end - # Tries to update a key-value-hash triplet by doing a linear scan. # Returns an old `Entry` if it was updated, otherwise `nil`. private def update_linear_scan(key, value, hash) : Entry(K, V)? @@ -1126,7 +1076,7 @@ class Hash(K, V) entry.value else value = yield key - insert_new(key, value) + upsert(key, value) value end end @@ -1163,8 +1113,6 @@ class Hash(K, V) entry.value elsif block = @block default_value = block.call(self, key) - - # NOTE: can't use `#insert_new` as `block` might add arbitrary keys upsert(key, yield default_value) default_value else From dbf7aef576842612fb026b5618b3d1be55fd400f Mon Sep 17 00:00:00 2001 From: Lachlan Dowding Date: Fri, 5 Apr 2024 18:43:58 +1100 Subject: [PATCH 1035/1551] Fix `Hash.new(initial_capacity, &block)` doc to use relevant example (#14429) --- src/hash.cr | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/hash.cr b/src/hash.cr index b245a201ae95..24a658dad21d 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -266,14 +266,21 @@ class Hash(K, V) # Creates a new empty `Hash` with a *block* that handles missing keys. # # ``` - # inventory = Hash(String, Int32).new(0) - # inventory["socks"] = 3 - # inventory["pickles"] # => 0 + # hash = Hash(String, String).new do |hash, key| + # "some default value" + # end + # + # hash.size # => 0 + # hash["foo"] = "bar" # => "bar" + # hash.size # => 1 + # hash["baz"] # => "some default value" + # hash.size # => 1 + # hash # => {"foo" => "bar"} # ``` # # WARNING: When the default block is invoked on a missing key, its return # value is *not* implicitly stored into the hash under that key. If you want - # that behaviour, you need to put it explicitly: + # that behaviour, you need to store it explicitly: # # ``` # hash = Hash(String, Int32).new do |hash, key| From 76ea33936673d5e768f94e1a07c893bdc6053fdc Mon Sep 17 00:00:00 2001 From: Lachlan Dowding Date: Fri, 5 Apr 2024 18:44:37 +1100 Subject: [PATCH 1036/1551] Fix `Pointer#+(offset: Int64)` doc parameter name typo (#14428) --- src/primitives.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/primitives.cr b/src/primitives.cr index 7cb478ebf0cf..f260f2cd718f 100644 --- a/src/primitives.cr +++ b/src/primitives.cr @@ -282,7 +282,7 @@ struct Pointer(T) end # Returns a new pointer whose address is this pointer's address - # incremented by `other * sizeof(T)`. + # incremented by `offset * sizeof(T)`. # # ``` # ptr = Pointer(Int32).new(1234) From 16751451e8618e84d66ee97b8388441e31b7fd3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 5 Apr 2024 16:04:50 +0200 Subject: [PATCH 1037/1551] Add `Nil` return type restriction to `String::Formatter#consume_substitution` (#14430) --- src/string/formatter.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/string/formatter.cr b/src/string/formatter.cr index b5f80d2aee87..0d4956dc0c05 100644 --- a/src/string/formatter.cr +++ b/src/string/formatter.cr @@ -65,7 +65,7 @@ struct String::Formatter(A) end end - private def consume_substitution + private def consume_substitution : Nil key = consume_substitution_key '}' # note: "`@io << (arg_at(key))` has no type" without this `arg` variable arg = arg_at(key) From a2c891f21414b2dd251d40207f6482ee4fb34cb6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 6 Apr 2024 18:27:55 +0800 Subject: [PATCH 1038/1551] Add single source of UTF-8 test sequences for specs (#14433) --- spec/std/char/reader_spec.cr | 104 +++-------------------------- spec/std/io/buffered_spec.cr | 24 +++---- spec/std/io/io_spec.cr | 31 +++------ spec/std/string_spec.cr | 112 +++++-------------------------- spec/support/string.cr | 125 +++++++++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 228 deletions(-) create mode 100644 spec/support/string.cr diff --git a/spec/std/char/reader_spec.cr b/spec/std/char/reader_spec.cr index 55409ddb0963..62dbe88f6e1a 100644 --- a/spec/std/char/reader_spec.cr +++ b/spec/std/char/reader_spec.cr @@ -1,5 +1,6 @@ require "spec" require "char/reader" +require "../../support/string" private def assert_invalid_byte_sequence(bytes, *, file = __FILE__, line = __LINE__) reader = Char::Reader.new(String.new bytes) @@ -8,11 +9,11 @@ private def assert_invalid_byte_sequence(bytes, *, file = __FILE__, line = __LIN reader.error.should eq(bytes[0]), file: file, line: line end -private def assert_reads_at_end(bytes, *, file = __FILE__, line = __LINE__) +private def assert_reads_at_end(bytes, char, *, file = __FILE__, line = __LINE__) str = String.new bytes reader = Char::Reader.new(str, pos: bytes.size) - reader.previous_char - reader.current_char.should eq(str[0]), file: file, line: line + reader.previous_char.should eq(char), file: file, line: line + reader.current_char.should eq(char), file: file, line: line reader.current_char_width.should eq(bytes.size), file: file, line: line reader.pos.should eq(0), file: file, line: line reader.error.should be_nil, file: file, line: line @@ -214,100 +215,17 @@ describe "Char::Reader" do reader.pos.should eq(0) end - it "errors if 0x80 <= first_byte < 0xC2" do - assert_invalid_byte_sequence Bytes[0x80] - assert_invalid_byte_sequence Bytes[0xC1] - end - - it "errors if (second_byte & 0xC0) != 0x80" do - assert_invalid_byte_sequence Bytes[0xd0] - end - - it "errors if first_byte == 0xE0 && second_byte < 0xA0" do - assert_invalid_byte_sequence Bytes[0xe0, 0x9F, 0xA0] - end - - it "errors if first_byte == 0xED && second_byte >= 0xA0" do - assert_invalid_byte_sequence Bytes[0xed, 0xB0, 0xA0] - end - - it "errors if first_byte < 0xF0 && (third_byte & 0xC0) != 0x80" do - assert_invalid_byte_sequence Bytes[0xe0, 0xA0, 0] - end - - it "errors if first_byte == 0xF0 && second_byte < 0x90" do - assert_invalid_byte_sequence Bytes[0xf0, 0x8F, 0xA0] - end - - it "errors if first_byte == 0xF4 && second_byte >= 0x90" do - assert_invalid_byte_sequence Bytes[0xf4, 0x90, 0xA0] - end - - it "errors if first_byte < 0xF5 && (fourth_byte & 0xC0) != 0x80" do - assert_invalid_byte_sequence Bytes[0xf4, 0x8F, 0xA0, 0] - end - - it "errors if first_byte >= 0xF5" do - assert_invalid_byte_sequence Bytes[0xf5, 0x8F, 0xA0, 0xA0] - end - - it "errors if second_byte is out of bounds" do - assert_invalid_byte_sequence Bytes[0xf4] - end - - it "errors if third_byte is out of bounds" do - assert_invalid_byte_sequence Bytes[0xf4, 0x8f] - end - - it "errors if fourth_byte is out of bounds" do - assert_invalid_byte_sequence Bytes[0xf4, 0x8f, 0xa0] + it "errors on invalid UTF-8" do + {% for bytes in INVALID_UTF8_BYTE_SEQUENCES %} + assert_invalid_byte_sequence Bytes{{ bytes }} + {% end %} end describe "#previous_char" do it "reads on valid UTF-8" do - assert_reads_at_end Bytes[0x00] - assert_reads_at_end Bytes[0x7f] - - assert_reads_at_end Bytes[0xc2, 0x80] - assert_reads_at_end Bytes[0xc2, 0xbf] - assert_reads_at_end Bytes[0xdf, 0x80] - assert_reads_at_end Bytes[0xdf, 0xbf] - - assert_reads_at_end Bytes[0xe1, 0x80, 0x80] - assert_reads_at_end Bytes[0xe1, 0x80, 0xbf] - assert_reads_at_end Bytes[0xe1, 0x9f, 0x80] - assert_reads_at_end Bytes[0xe1, 0x9f, 0xbf] - assert_reads_at_end Bytes[0xed, 0x80, 0x80] - assert_reads_at_end Bytes[0xed, 0x80, 0xbf] - assert_reads_at_end Bytes[0xed, 0x9f, 0x80] - assert_reads_at_end Bytes[0xed, 0x9f, 0xbf] - assert_reads_at_end Bytes[0xef, 0x80, 0x80] - assert_reads_at_end Bytes[0xef, 0x80, 0xbf] - assert_reads_at_end Bytes[0xef, 0x9f, 0x80] - assert_reads_at_end Bytes[0xef, 0x9f, 0xbf] - - assert_reads_at_end Bytes[0xe0, 0xa0, 0x80] - assert_reads_at_end Bytes[0xe0, 0xa0, 0xbf] - assert_reads_at_end Bytes[0xe0, 0xbf, 0x80] - assert_reads_at_end Bytes[0xe0, 0xbf, 0xbf] - assert_reads_at_end Bytes[0xe1, 0xa0, 0x80] - assert_reads_at_end Bytes[0xe1, 0xa0, 0xbf] - assert_reads_at_end Bytes[0xe1, 0xbf, 0x80] - assert_reads_at_end Bytes[0xe1, 0xbf, 0xbf] - assert_reads_at_end Bytes[0xef, 0xa0, 0x80] - assert_reads_at_end Bytes[0xef, 0xa0, 0xbf] - assert_reads_at_end Bytes[0xef, 0xbf, 0x80] - assert_reads_at_end Bytes[0xef, 0xbf, 0xbf] - - assert_reads_at_end Bytes[0xf1, 0x80, 0x80, 0x80] - assert_reads_at_end Bytes[0xf1, 0x8f, 0x80, 0x80] - assert_reads_at_end Bytes[0xf4, 0x80, 0x80, 0x80] - assert_reads_at_end Bytes[0xf4, 0x8f, 0x80, 0x80] - - assert_reads_at_end Bytes[0xf0, 0x90, 0x80, 0x80] - assert_reads_at_end Bytes[0xf0, 0xbf, 0x80, 0x80] - assert_reads_at_end Bytes[0xf3, 0x90, 0x80, 0x80] - assert_reads_at_end Bytes[0xf3, 0xbf, 0x80, 0x80] + {% for bytes, char in VALID_UTF8_BYTE_SEQUENCES %} + assert_reads_at_end Bytes{{ bytes }}, {{ char }} + {% end %} end it "errors on invalid UTF-8" do diff --git a/spec/std/io/buffered_spec.cr b/spec/std/io/buffered_spec.cr index 8f200aab7f56..fbf6ac638ab8 100644 --- a/spec/std/io/buffered_spec.cr +++ b/spec/std/io/buffered_spec.cr @@ -1,4 +1,5 @@ require "../spec_helper" +require "../../support/string" private class BufferedWrapper < IO include IO::Buffered @@ -176,22 +177,15 @@ describe "IO::Buffered" do io.read_char.should eq('界') io.read_char.should be_nil - io = IO::Memory.new - io.write Bytes[0xf8, 0xff, 0xff, 0xff] - io.rewind - io = BufferedWrapper.new(io) - - expect_raises(InvalidByteSequenceError) do - io.read_char - end + {% for bytes, char in VALID_UTF8_BYTE_SEQUENCES %} + BufferedWrapper.new(IO::Memory.new(Bytes{{ bytes }})).read_char.should eq({{ char }}) + {% end %} - io = IO::Memory.new - io.write_byte 0x81_u8 - io.rewind - io = BufferedWrapper.new(io) - expect_raises(InvalidByteSequenceError) do - p io.read_char - end + {% for bytes in INVALID_UTF8_BYTE_SEQUENCES %} + expect_raises(InvalidByteSequenceError) do + BufferedWrapper.new(IO::Memory.new(Bytes{{ bytes }})).read_char + end + {% end %} end it "reads byte" do diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 9c6da1e926d2..387f202d62b5 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -1,5 +1,6 @@ require "../spec_helper" require "../../support/channel" +require "../../support/string" require "spec/helpers/iterate" require "socket" @@ -338,29 +339,13 @@ describe IO do io.read_char.should eq('界') io.read_char.should be_nil - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xc4, 0x70]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xc4, 0x70, 0x00, 0x00]).read_char } - - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xf8]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xf8, 0x00, 0x00, 0x00]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0x81]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0x81, 0x00, 0x00, 0x00]).read_char } - - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xed, 0xa0, 0x80]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xed, 0xa0, 0x80, 0x00]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xed, 0xbf, 0xbf]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xed, 0xbf, 0xbf, 0x00]).read_char } - - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xc0, 0x80]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xc0, 0x80, 0x00, 0x00]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xc1, 0xbf]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xc1, 0xbf, 0x00, 0x00]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xe0, 0x80, 0x80]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xe0, 0x80, 0x80, 0x00]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xe0, 0x9f, 0xbf]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xe0, 0x9f, 0xbf, 0x00]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xf0, 0x80, 0x80, 0x80]).read_char } - expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes[0xf0, 0x8f, 0xbf, 0xbf]).read_char } + {% for bytes, char in VALID_UTF8_BYTE_SEQUENCES %} + SimpleIOMemory.new(Bytes{{ bytes }}).read_char.should eq({{ char }}) + {% end %} + + {% for bytes in INVALID_UTF8_BYTE_SEQUENCES %} + expect_raises(InvalidByteSequenceError) { SimpleIOMemory.new(Bytes{{ bytes }}).read_char } + {% end %} end it "reads byte" do diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 39d4136972a9..dd60aa90e86e 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -1,4 +1,5 @@ require "./spec_helper" +require "../support/string" require "spec/helpers/iterate" require "spec/helpers/string" @@ -2946,65 +2947,13 @@ describe "String" do "hello".valid_encoding?.should be_true "hello\u{80}\u{7FF}\u{800}\u{FFFF}\u{10000}\u{10FFFF}".valid_encoding?.should be_true - # non-starters - String.new(Bytes[0x80]).valid_encoding?.should be_false - String.new(Bytes[0x8F]).valid_encoding?.should be_false - String.new(Bytes[0x90]).valid_encoding?.should be_false - String.new(Bytes[0x9F]).valid_encoding?.should be_false - String.new(Bytes[0xA0]).valid_encoding?.should be_false - String.new(Bytes[0xAF]).valid_encoding?.should be_false - - # incomplete, 2-byte - String.new(Bytes[0xC2]).valid_encoding?.should be_false - String.new(Bytes[0xC2, 0x00]).valid_encoding?.should be_false - String.new(Bytes[0xC2, 0xC2]).valid_encoding?.should be_false - - # overlong, 2-byte - String.new(Bytes[0xC0, 0x80]).valid_encoding?.should be_false - String.new(Bytes[0xC1, 0xBF]).valid_encoding?.should be_false - String.new(Bytes[0xC2, 0x80]).valid_encoding?.should be_true - - # incomplete, 3-byte - String.new(Bytes[0xE1]).valid_encoding?.should be_false - String.new(Bytes[0xE1, 0x00]).valid_encoding?.should be_false - String.new(Bytes[0xE1, 0xC2]).valid_encoding?.should be_false - String.new(Bytes[0xE1, 0x80]).valid_encoding?.should be_false - String.new(Bytes[0xE1, 0x80, 0x00]).valid_encoding?.should be_false - String.new(Bytes[0xE1, 0x80, 0xC2]).valid_encoding?.should be_false - - # overlong, 3-byte - String.new(Bytes[0xE0, 0x80, 0x80]).valid_encoding?.should be_false - String.new(Bytes[0xE0, 0x9F, 0xBF]).valid_encoding?.should be_false - String.new(Bytes[0xE0, 0xA0, 0x80]).valid_encoding?.should be_true - - # surrogate pairs - String.new(Bytes[0xED, 0x9F, 0xBF]).valid_encoding?.should be_true - String.new(Bytes[0xED, 0xA0, 0x80]).valid_encoding?.should be_false - String.new(Bytes[0xED, 0xBF, 0xBF]).valid_encoding?.should be_false - String.new(Bytes[0xEE, 0x80, 0x80]).valid_encoding?.should be_true - - # incomplete, 4-byte - String.new(Bytes[0xF1]).valid_encoding?.should be_false - String.new(Bytes[0xF1, 0x00]).valid_encoding?.should be_false - String.new(Bytes[0xF1, 0xC2]).valid_encoding?.should be_false - String.new(Bytes[0xF1, 0x80]).valid_encoding?.should be_false - String.new(Bytes[0xF1, 0x80, 0x00]).valid_encoding?.should be_false - String.new(Bytes[0xF1, 0x80, 0xC2]).valid_encoding?.should be_false - String.new(Bytes[0xF1, 0x80, 0x80]).valid_encoding?.should be_false - String.new(Bytes[0xF1, 0x80, 0x80, 0x00]).valid_encoding?.should be_false - String.new(Bytes[0xF1, 0x80, 0x80, 0xC2]).valid_encoding?.should be_false - - # overlong, 4-byte - String.new(Bytes[0xF0, 0x80, 0x80, 0x80]).valid_encoding?.should be_false - String.new(Bytes[0xF0, 0x8F, 0xBF, 0xBF]).valid_encoding?.should be_false - String.new(Bytes[0xF0, 0x90, 0x80, 0x80]).valid_encoding?.should be_true - - # upper boundary, 4-byte - String.new(Bytes[0xF4, 0x8F, 0xBF, 0xBF]).valid_encoding?.should be_true - String.new(Bytes[0xF4, 0x90, 0x80, 0x80]).valid_encoding?.should be_false - String.new(Bytes[0xF5]).valid_encoding?.should be_false - String.new(Bytes[0xF8]).valid_encoding?.should be_false - String.new(Bytes[0xFF]).valid_encoding?.should be_false + {% for bytes in VALID_UTF8_BYTE_SEQUENCES %} + String.new(Bytes{{ bytes }}).valid_encoding?.should be_true + {% end %} + + {% for bytes in INVALID_UTF8_BYTE_SEQUENCES %} + String.new(Bytes{{ bytes }}).valid_encoding?.should be_false + {% end %} end it "scrubs" do @@ -3114,44 +3063,13 @@ end class String describe String do it ".char_bytesize_at" do - String.char_bytesize_at(Bytes[0x00, 0].to_unsafe).should eq 1 - String.char_bytesize_at(Bytes[0x7F, 0].to_unsafe).should eq 1 - String.char_bytesize_at(Bytes[0x80, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xBF, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xC2, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xC3, 0].to_unsafe).should eq 1 # malformed - - String.char_bytesize_at(Bytes[0xC2, 0x7F, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xC2, 0x80, 0].to_unsafe).should eq 2 - String.char_bytesize_at(Bytes[0xDF, 0xBF, 0].to_unsafe).should eq 2 - String.char_bytesize_at(Bytes[0xDF, 0xC0, 0].to_unsafe).should eq 1 # malformed - - String.char_bytesize_at(Bytes[0xE0, 0xA0, 0x7F, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xE0, 0x9F, 0x8F, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xE0, 0xA0, 0x80, 0].to_unsafe).should eq 3 - String.char_bytesize_at(Bytes[0xED, 0x9F, 0xBF, 0].to_unsafe).should eq 3 - String.char_bytesize_at(Bytes[0xED, 0x9F, 0xC0, 0].to_unsafe).should eq 1 # surrogate - String.char_bytesize_at(Bytes[0xED, 0xBF, 0xBF, 0].to_unsafe).should eq 1 # surrogate - String.char_bytesize_at(Bytes[0xEE, 0x80, 0x80, 0].to_unsafe).should eq 3 - String.char_bytesize_at(Bytes[0xEF, 0xBF, 0xBD, 0].to_unsafe).should eq 3 - String.char_bytesize_at(Bytes[0xEF, 0xBF, 0xBF, 0].to_unsafe).should eq 3 - String.char_bytesize_at(Bytes[0xEF, 0xBF, 0xC0, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xEF, 0xC0, 0xBF, 0].to_unsafe).should eq 1 # malformed - - String.char_bytesize_at(Bytes[0xF0, 0x90, 0x80, 0x7F, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xF0, 0x90, 0x7F, 0x80, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xF0, 0x8F, 0x80, 0x80, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xF0, 0x90, 0x80, 0x80, 0].to_unsafe).should eq 4 - String.char_bytesize_at(Bytes[0xF0, 0x9F, 0xBF, 0xBF, 0].to_unsafe).should eq 4 - String.char_bytesize_at(Bytes[0xF3, 0x90, 0x80, 0x80, 0].to_unsafe).should eq 4 - String.char_bytesize_at(Bytes[0xF4, 0x8F, 0xBD, 0xBF, 0].to_unsafe).should eq 4 - String.char_bytesize_at(Bytes[0xF4, 0x8F, 0xBF, 0xBF, 0].to_unsafe).should eq 4 - String.char_bytesize_at(Bytes[0xF4, 0x8F, 0xBF, 0xC0, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xF4, 0x8F, 0xC0, 0xBF, 0].to_unsafe).should eq 1 # malformed - String.char_bytesize_at(Bytes[0xF4, 0x90, 0xBF, 0xBF, 0].to_unsafe).should eq 1 # malformed - - String.char_bytesize_at(Bytes[0xF5, 0].to_unsafe).should eq 1 # out of codepoint range - String.char_bytesize_at(Bytes[0xFF, 0].to_unsafe).should eq 1 # out of codepoint range + {% for bytes, char in VALID_UTF8_BYTE_SEQUENCES %} + String.char_bytesize_at(Bytes[{{ bytes.splat }}, 0].to_unsafe).should eq({{ bytes.size }}) + {% end %} + + {% for bytes in INVALID_UTF8_BYTE_SEQUENCES %} + String.char_bytesize_at(Bytes[{{ bytes.splat }}, 0].to_unsafe).should eq 1 + {% end %} end end end diff --git a/spec/support/string.cr b/spec/support/string.cr new file mode 100644 index 000000000000..fb4ea708e73a --- /dev/null +++ b/spec/support/string.cr @@ -0,0 +1,125 @@ +VALID_UTF8_BYTE_SEQUENCES = { + [0x00] => '\u{0000}', + [0x7F] => '\u{007F}', + + [0xC2, 0x80] => '\u{0080}', + [0xC2, 0xBF] => '\u{00BF}', + [0xDF, 0x80] => '\u{07C0}', + [0xDF, 0xBF] => '\u{07FF}', + + [0xE0, 0xA0, 0x80] => '\u{0800}', + [0xE0, 0xA0, 0xBF] => '\u{083F}', + [0xE0, 0xBF, 0x80] => '\u{0FC0}', + [0xE0, 0xBF, 0xBF] => '\u{0FFF}', + [0xE1, 0x80, 0x80] => '\u{1000}', + [0xE1, 0x80, 0xBF] => '\u{103F}', + [0xE1, 0x9F, 0x80] => '\u{17C0}', + [0xE1, 0x9F, 0xBF] => '\u{17FF}', + [0xE1, 0xA0, 0x80] => '\u{1800}', + [0xE1, 0xA0, 0xBF] => '\u{183F}', + [0xE1, 0xBF, 0x80] => '\u{1FC0}', + [0xE1, 0xBF, 0xBF] => '\u{1FFF}', + [0xED, 0x80, 0x80] => '\u{D000}', + [0xED, 0x80, 0xBF] => '\u{D03F}', + [0xED, 0x9F, 0x80] => '\u{D7C0}', + [0xED, 0x9F, 0xBF] => '\u{D7FF}', + [0xEF, 0x80, 0x80] => '\u{F000}', + [0xEF, 0x80, 0xBF] => '\u{F03F}', + [0xEF, 0x9F, 0x80] => '\u{F7C0}', + [0xEF, 0x9F, 0xBF] => '\u{F7FF}', + [0xEF, 0xA0, 0x80] => '\u{F800}', + [0xEF, 0xA0, 0xBF] => '\u{F83F}', + [0xEF, 0xBF, 0x80] => '\u{FFC0}', + [0xEF, 0xBF, 0xBF] => '\u{FFFF}', + + [0xF0, 0x90, 0x80, 0x80] => '\u{10000}', + [0xF0, 0x90, 0x80, 0xBF] => '\u{1003F}', + [0xF0, 0x90, 0xBF, 0x80] => '\u{10FC0}', + [0xF0, 0x90, 0xBF, 0xBF] => '\u{10FFF}', + [0xF0, 0xBF, 0x80, 0x80] => '\u{3F000}', + [0xF0, 0xBF, 0x80, 0xBF] => '\u{3F03F}', + [0xF0, 0xBF, 0xBF, 0x80] => '\u{3FFC0}', + [0xF0, 0xBF, 0xBF, 0xBF] => '\u{3FFFF}', + [0xF1, 0x80, 0x80, 0x80] => '\u{40000}', + [0xF1, 0x80, 0x80, 0xBF] => '\u{4003F}', + [0xF1, 0x80, 0xBF, 0x80] => '\u{40FC0}', + [0xF1, 0x80, 0xBF, 0xBF] => '\u{40FFF}', + [0xF1, 0x8F, 0x80, 0x80] => '\u{4F000}', + [0xF1, 0x8F, 0x80, 0xBF] => '\u{4F03F}', + [0xF1, 0x8F, 0xBF, 0x80] => '\u{4FFC0}', + [0xF1, 0x8F, 0xBF, 0xBF] => '\u{4FFFF}', + [0xF3, 0x90, 0x80, 0x80] => '\u{D0000}', + [0xF3, 0xBF, 0xBF, 0xBF] => '\u{FFFFF}', + [0xF4, 0x80, 0x80, 0x80] => '\u{100000}', + [0xF4, 0x80, 0x80, 0xBF] => '\u{10003F}', + [0xF4, 0x80, 0xBF, 0x80] => '\u{100FC0}', + [0xF4, 0x80, 0xBF, 0xBF] => '\u{100FFF}', + [0xF4, 0x8F, 0x80, 0x80] => '\u{10F000}', + [0xF4, 0x8F, 0xBF, 0xBF] => '\u{10FFFF}', +} + +INVALID_UTF8_BYTE_SEQUENCES = [ + # non-starters + [0x80], + [0x8F], + [0x90], + [0x9F], + [0xA0], + [0xAF], + [0xB0], + [0xBF], + [0xFE], + [0xFF], + + # incomplete, 2-byte + [0xC2], + [0xC2, 0x00], + [0xC2, 0xC2], + + # overlong, 2-byte + [0xC0, 0x80], + [0xC1, 0xBF], + + # incomplete, 3-byte + [0xE1], + [0xE1, 0x00], + [0xE1, 0xC2], + [0xE1, 0x80], + [0xE1, 0x80, 0x00], + [0xE1, 0x80, 0xC2], + + # overlong, 3-byte + [0xE0, 0x80, 0x80], + [0xE0, 0x9F, 0xBF], + + # surrogate pairs + [0xED, 0xA0, 0x80], + [0xED, 0xBF, 0xBF], + + # incomplete, 4-byte + [0xF1], + [0xF1, 0x00], + [0xF1, 0xC2], + [0xF1, 0x80], + [0xF1, 0x80, 0x00], + [0xF1, 0x80, 0xC2], + [0xF1, 0x80, 0x80], + [0xF1, 0x80, 0x80, 0x00], + [0xF1, 0x80, 0x80, 0xC2], + + # overlong, 4-byte + [0xF0, 0x80, 0x80, 0x80], + [0xF0, 0x8F, 0xBF, 0xBF], + + # upper boundary, 4-byte + [0xF4, 0x90, 0x80, 0x80], + [0xF5], + + # 5-byte (obsolete) + [0xF8], + [0xF8, 0x80, 0x80, 0x80, 0x80], + + # 6-byte (obsolete) + [0xFC], + [0xFC, 0x80, 0x80, 0x80, 0x80, 0x80], +] From 1abc04ed04f4dcf0a2ed8b1e825bf7a0821ebab2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 6 Apr 2024 18:28:15 +0800 Subject: [PATCH 1039/1551] Move some `IO::FileDescriptor` specs to the correct file (#14431) --- spec/std/io/file_descriptor_spec.cr | 18 ++++++++++++++++++ spec/std/io/io_spec.cr | 18 ------------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/spec/std/io/file_descriptor_spec.cr b/spec/std/io/file_descriptor_spec.cr index 509cbb72eedb..10efac0eadb4 100644 --- a/spec/std/io/file_descriptor_spec.cr +++ b/spec/std/io/file_descriptor_spec.cr @@ -78,4 +78,22 @@ describe IO::FileDescriptor do p.puts "123" end end + + it "reopens" do + File.open(datapath("test_file.txt")) do |file1| + File.open(datapath("test_file.ini")) do |file2| + file2.reopen(file1) + file2.gets.should eq("Hello World") + end + end + end + + typeof(STDIN.noecho { }) + typeof(STDIN.noecho!) + typeof(STDIN.echo { }) + typeof(STDIN.echo!) + typeof(STDIN.cooked { }) + typeof(STDIN.cooked!) + typeof(STDIN.raw { }) + typeof(STDIN.raw!) end diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 387f202d62b5..b73640e61918 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -147,15 +147,6 @@ describe IO do end end - it "reopens" do - File.open(datapath("test_file.txt")) do |file1| - File.open(datapath("test_file.ini")) do |file2| - file2.reopen(file1) - file2.gets.should eq("Hello World") - end - end - end - describe "read operations" do it "does gets" do io = SimpleIOMemory.new("hello\nworld\n") @@ -990,15 +981,6 @@ describe IO do end end - typeof(STDIN.noecho { }) - typeof(STDIN.noecho!) - typeof(STDIN.echo { }) - typeof(STDIN.echo!) - typeof(STDIN.cooked { }) - typeof(STDIN.cooked!) - typeof(STDIN.raw { }) - typeof(STDIN.raw!) - describe IO::Error do describe ".new" do it "accepts `cause` argument (#14241)" do From c32e5a9640000e46573d0b606e507a8b4e560b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 6 Apr 2024 20:29:50 +0200 Subject: [PATCH 1040/1551] Add `scripts/update-changelog.sh` (#14231) --- scripts/update-changelog.sh | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100755 scripts/update-changelog.sh diff --git a/scripts/update-changelog.sh b/scripts/update-changelog.sh new file mode 100755 index 000000000000..777f65a60e3d --- /dev/null +++ b/scripts/update-changelog.sh @@ -0,0 +1,65 @@ +#! /bin/sh + +# This script automates generating changelog with `scripts/github-changelog.cr`, +# editing it into `CHANGELOG.md` and pushing it to a `changelog/$VERSION` branch. +# +# It reads the current (dev-)version from `src/VERSION` and generates the +# changelog entries for all PRs from the respective GitHub milestone via +# `scripts/github-changelog.cr`. +# The section is then inserted into `CHANGELOG.md`, overwriting any previous +# content for this milestone. +# Finally, the changes are commited and pushed to `changelog/$VERSION`. +# If the changelog section is *new*, also creates a draft PR for this branch. +# +# Usage: +# +# scripts/update-changelog.sh +# +# Requirements: +# +# - scripts/github-changelog.cr +# - git +# - grep +# - sed +# +# Environment variables: +# GITHUB_TOKEN: Access token for the GitHub API (required) + +set -eu + +VERSION=${1:-$(cat src/VERSION)} +VERSION=${VERSION%-dev} + +base_branch=$(git rev-parse --abbrev-ref HEAD) +branch="changelog/$VERSION" +current_changelog="CHANGELOG.$VERSION.md" + +echo "Generating $current_changelog..." +scripts/github-changelog.cr $VERSION > $current_changelog + +echo "Switching to branch $branch" +git switch $branch 2>/dev/null || git switch -c $branch; + +if grep --silent -E "^## \[$VERSION\]" CHANGELOG.md; then + echo "Replacing section in CHANGELOG" + + sed -i -E "/^## \[$VERSION\]/,/^## /{ + /^## \[$VERSION\]/s/.*/cat $current_changelog/e; /^## /!d + }" CHANGELOG.md + + git add CHANGELOG.md + git commit -m "Update changelog for $VERSION" + git push +else + echo "Adding new section to CHANGELOG" + + sed -i -E "2r $current_changelog" CHANGELOG.md + + git add CHANGELOG.md + git commit -m "Add changelog for $VERSION" + git push -u upstream $branch + + gh pr create --draft --base "$base_branch" \ + --body "Preview: https://github.com/crystal-lang/crystal/blob/$branch/CHANGELOG.md" \ + --label "topic:infrastructure" -t "Changelog for $VERSION" --milestone "$VERSION" +fi From 8f1d45a4cef6d19988249934c1a395e3d6a7d2ae Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 8 Apr 2024 15:31:30 +0800 Subject: [PATCH 1041/1551] Handle NaN comparisons in the interpreter (#14441) --- src/compiler/crystal/interpreter/compiler.cr | 4 ++ .../crystal/interpreter/instructions.cr | 6 ++- .../crystal/interpreter/primitives.cr | 41 ++++++++++++++++--- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 04fc885fce7e..b25b6e7e25f9 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -3367,6 +3367,10 @@ class Crystal::Repl::Compiler < Crystal::Visitor @instructions.instructions << value end + private def append(value : Enum) + append(value.value) + end + # Many times we need to jump or branch to an instruction for which we don't # know the offset/index yet. # In those cases we generate a jump to zero, but remember where that "zero" diff --git a/src/compiler/crystal/interpreter/instructions.cr b/src/compiler/crystal/interpreter/instructions.cr index f2b8abcb9a51..804726beaad4 100644 --- a/src/compiler/crystal/interpreter/instructions.cr +++ b/src/compiler/crystal/interpreter/instructions.cr @@ -951,14 +951,16 @@ require "./repl" code: a == b ? 0 : (a < b ? -1 : 1), }, cmp_f32: { + operands: [predicate : Compiler::FloatPredicate], pop_values: [a : Float32, b : Float32], push: true, - code: a == b ? 0 : (a < b ? -1 : 1), + code: predicate.compare(a, b), }, cmp_f64: { + operands: [predicate : Compiler::FloatPredicate], pop_values: [a : Float64, b : Float64], push: true, - code: a == b ? 0 : (a < b ? -1 : 1), + code: predicate.compare(a, b), }, cmp_eq: { pop_values: [cmp : Int32], diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr index 968361fb6c1d..16a0364be714 100644 --- a/src/compiler/crystal/interpreter/primitives.cr +++ b/src/compiler/crystal/interpreter/primitives.cr @@ -1325,16 +1325,17 @@ class Crystal::Repl::Compiler end private def primitive_binary_op_cmp_float(node : ASTNode, kind : NumberKind, op : String) - case kind - when .f32? then cmp_f32(node: node) - when .f64? then cmp_f64(node: node) - else - node.raise "BUG: missing handling of binary #{op} with kind #{kind}" + if predicate = FloatPredicate.from_method?(op) + case kind + when .f32? then return cmp_f32(predicate, node: node) + when .f64? then return cmp_f64(predicate, node: node) + end end - primitive_binary_op_cmp_op(node, op) + node.raise "BUG: missing handling of binary #{op} with kind #{kind}" end + # TODO: should integer comparisons also use `FloatPredicate`? private def primitive_binary_op_cmp_op(node : ASTNode, op : String) case op when "==" then cmp_eq(node: node) @@ -1348,6 +1349,34 @@ class Crystal::Repl::Compiler end end + # interpreter-exclusive flags for `cmp_f32` and `cmp_f64` + # currently compatible with `LLVM::RealPredicate` + @[Flags] + enum FloatPredicate : UInt8 + Equal + GreaterThan + LessThan + Unordered + + def self.from_method?(op : String) + case op + when "==" then Equal + when "!=" then LessThan | GreaterThan | Unordered + when "<" then LessThan + when "<=" then LessThan | Equal + when ">" then GreaterThan + when ">=" then GreaterThan | Equal + end + end + + def compare(x, y) : Bool + (equal? && x == y) || + (greater_than? && x > y) || + (less_than? && x < y) || + (unordered? && (x.nan? || y.nan?)) + end + end + # interpreter-exclusive integer unions private enum MixedNumberKind # Int64 | UInt64 From dd9551ffca1571db2c5c421068e88b7572d623ab Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 8 Apr 2024 15:31:39 +0800 Subject: [PATCH 1042/1551] Check `UInt16#to_u8` for overflow in the interpreter (#14436) --- src/compiler/crystal/interpreter/primitives.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr index 16a0364be714..be83374f6ae8 100644 --- a/src/compiler/crystal/interpreter/primitives.cr +++ b/src/compiler/crystal/interpreter/primitives.cr @@ -738,7 +738,7 @@ class Crystal::Repl::Compiler in {.u16?, .i32?} then zero_extend(6, node: node) in {.u16?, .i64?} then zero_extend(6, node: node) in {.u16?, .i128?} then zero_extend(14, node: node) - in {.u16?, .u8?} then nop + in {.u16?, .u8?} then checked ? (zero_extend(6, node: node); u64_to_u8(node: node)) : nop in {.u16?, .u16?} then nop in {.u16?, .u32?} then zero_extend(6, node: node) in {.u16?, .u64?} then zero_extend(6, node: node) From fc6c3f2dd463999413010f7943bc4c678eb61b39 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 8 Apr 2024 15:31:52 +0800 Subject: [PATCH 1043/1551] Do not remove the whitespace in `foo ()` when formatting (#14439) --- spec/compiler/formatter/formatter_spec.cr | 2 ++ src/compiler/crystal/tools/formatter.cr | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 2b014ba69d4e..a942a910dc7c 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1837,6 +1837,8 @@ describe Crystal::Formatter do assert_format "foo (1; 2)" assert_format "foo ((1) ? 2 : 3)", "foo((1) ? 2 : 3)" assert_format "foo((1..3))" + assert_format "foo ()" + assert_format "foo ( )", "foo ()" assert_format "def foo(\n\n#foo\nx,\n\n#bar\nz\n)\nend", "def foo(\n # foo\n x,\n\n # bar\n z\n)\nend" assert_format "def foo(\nx, #foo\nz #bar\n)\nend", "def foo(\n x, # foo\n z # bar\n)\nend" assert_format "a = 1;;; b = 2", "a = 1; b = 2" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 6182ab074bfe..238c0a331847 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -2774,8 +2774,13 @@ module Crystal !node.named_args && !node.block_arg && !node.block && (expressions = node.args[0].as?(Expressions)) && expressions.keyword.paren? && expressions.expressions.size == 1 - skip_space - node.args[0] = expressions.expressions[0] + # ...except do not transform `foo ()` into `foo()`, as the former is + # actually semantically equivalent to `foo(nil)` + arg = expressions.expressions[0] + unless arg.is_a?(Nop) + skip_space + node.args[0] = arg + end end if @token.type.op_lparen? From d0c30dafc2830b6169c1d65cde9b32cd9bb935ae Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 8 Apr 2024 19:58:12 +0800 Subject: [PATCH 1044/1551] Use separate names for constant and class variable internals (#14445) --- spec/compiler/codegen/class_var_spec.cr | 13 +++++++++++++ spec/llvm-ir/const-read-debug-loc.cr | 2 +- src/compiler/crystal/codegen/const.cr | 2 +- src/compiler/crystal/codegen/types.cr | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/spec/compiler/codegen/class_var_spec.cr b/spec/compiler/codegen/class_var_spec.cr index 8dc7f9bf1915..c92a4f2f0311 100644 --- a/spec/compiler/codegen/class_var_spec.cr +++ b/spec/compiler/codegen/class_var_spec.cr @@ -564,6 +564,19 @@ describe "Codegen: class var" do mod.to_s.should_not contain("x:init") end + it "doesn't error if class var shares name with const (#7865)" do + run(<<-CRYSTAL).to_string.should eq("asdfgh") + require "prelude" + + class Pattern + @@A = "asdf" + A = "\#{@@A}gh" + end + + Pattern::A + CRYSTAL + end + it "catch infinite loop in class var initializer" do run(%( require "prelude" diff --git a/spec/llvm-ir/const-read-debug-loc.cr b/spec/llvm-ir/const-read-debug-loc.cr index 49a840b22f77..f65c15d34b5d 100644 --- a/spec/llvm-ir/const-read-debug-loc.cr +++ b/spec/llvm-ir/const-read-debug-loc.cr @@ -10,7 +10,7 @@ def a_foo end THE_FOO.foo -# CHECK: call %Foo** @"~THE_FOO:read"() +# CHECK: call %Foo** @"~THE_FOO:const_read"() # CHECK-SAME: !dbg [[LOC:![0-9]+]] # CHECK: [[LOC]] = !DILocation # CHECK-SAME: line: 12 diff --git a/src/compiler/crystal/codegen/const.cr b/src/compiler/crystal/codegen/const.cr index 88a50022d54d..8ace05ff76e8 100644 --- a/src/compiler/crystal/codegen/const.cr +++ b/src/compiler/crystal/codegen/const.cr @@ -222,7 +222,7 @@ class Crystal::CodeGenVisitor return global end - read_function_name = "~#{const.llvm_name}:read" + read_function_name = "~#{const.llvm_name}:const_read" func = typed_fun?(@main_mod, read_function_name) || create_read_const_function(read_function_name, const) func = check_main_fun read_function_name, func call func diff --git a/src/compiler/crystal/codegen/types.cr b/src/compiler/crystal/codegen/types.cr index 17f6551f7cd1..654e7a281421 100644 --- a/src/compiler/crystal/codegen/types.cr +++ b/src/compiler/crystal/codegen/types.cr @@ -185,7 +185,7 @@ module Crystal property? no_init_flag = false def initialized_llvm_name - "#{llvm_name}:init" + "#{llvm_name}:const_init" end # Returns `true` if this constant's value is a simple literal, like From 39253d06c2695a1e8b0c0c780d9793355eed9082 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 8 Apr 2024 20:00:04 +0800 Subject: [PATCH 1045/1551] Fix interpreter internal overflow for `UInt128#to_f32` and `#to_f32!` (#14437) --- spec/primitives/int_spec.cr | 7 +++++++ src/compiler/crystal/interpreter/instructions.cr | 6 ++++++ src/compiler/crystal/interpreter/primitives.cr | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/spec/primitives/int_spec.cr b/spec/primitives/int_spec.cr index 603dc4f51b72..e2a62a5357dd 100644 --- a/spec/primitives/int_spec.cr +++ b/spec/primitives/int_spec.cr @@ -137,4 +137,11 @@ describe "Primitives: Int" do expect_raises(OverflowError) { Float32::MAX.to_u128.succ.to_f32 } # Float32::MAX == 2 ** 128 - 2 ** 104 end end + + describe "#to_f!" do + it "doesn't raise on overflow for UInt128#to_f32" do + UInt128::MAX.to_f32!.should eq(Float32::INFINITY) + Float32::MAX.to_u128.succ.to_f32!.should eq(Float32::MAX) + end + end end diff --git a/src/compiler/crystal/interpreter/instructions.cr b/src/compiler/crystal/interpreter/instructions.cr index 804726beaad4..af1af55301e2 100644 --- a/src/compiler/crystal/interpreter/instructions.cr +++ b/src/compiler/crystal/interpreter/instructions.cr @@ -278,6 +278,7 @@ require "./repl" u128_to_f32: { pop_values: [value : UInt128], push: true, + overflow: true, code: value.to_f32, }, u128_to_f64: { @@ -285,6 +286,11 @@ require "./repl" push: true, code: value.to_f64, }, + u128_to_f32_bang: { + pop_values: [value : UInt128], + push: true, + code: value.to_f32!, + }, f32_to_f64: { pop_values: [value : Float32], push: true, diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr index be83374f6ae8..e411229600f9 100644 --- a/src/compiler/crystal/interpreter/primitives.cr +++ b/src/compiler/crystal/interpreter/primitives.cr @@ -815,7 +815,7 @@ class Crystal::Repl::Compiler in {.u128?, .u32?} then checked ? u128_to_u32(node: node) : pop(8, node: node) in {.u128?, .u64?} then checked ? u128_to_u64(node: node) : pop(8, node: node) in {.u128?, .u128?} then nop - in {.u128?, .f32?} then u128_to_f32(node: node) + in {.u128?, .f32?} then checked ? u128_to_f32(node: node) : u128_to_f32_bang(node: node) in {.u128?, .f64?} then u128_to_f64(node: node) in {.f32?, .i8?} then f32_to_f64(node: node); checked ? f64_to_i8(node: node) : f64_to_i64_bang(node: node) in {.f32?, .i16?} then f32_to_f64(node: node); checked ? f64_to_i16(node: node) : f64_to_i64_bang(node: node) From e3499e68ec1883ce199f9c111737b969b143264f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 8 Apr 2024 18:08:00 +0200 Subject: [PATCH 1046/1551] Improve documentation for `at_exit` handler conditions (#14426) --- src/kernel.cr | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/kernel.cr b/src/kernel.cr index 78f84d292bfa..1ca8d4073243 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -491,7 +491,16 @@ def pp(**objects) pp(objects) unless objects.empty? end -# Registers the given `Proc` for execution when the program exits. +# Registers the given `Proc` for execution when the program exits regularly. +# +# A regular exit happens when either +# * the main fiber reaches the end of the program, +# * the main fiber rescues an unhandled exception, or +# * `::exit` is called. +# +# `Process.exit` does *not* trigger `at_exit` handlers, nor does external process +# termination (see `Process.on_terminate` for handling that). +# # If multiple handlers are registered, they are executed in reverse order of registration. # # ``` From 2066d7c55df04e7127cb4249dbc19a2bc2dbd2f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 8 Apr 2024 22:37:10 +0200 Subject: [PATCH 1047/1551] Update distribution-scripts (#14457) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fe4e6dabaffd..215465bc570c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "535aedb8b09d77caaa1583700f371cd04343b7e8" + default: "46c295d14e4dc3f6b7d9598c0b4dd89e93232def" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From 205edb427f1569e9803ab850f468861ac29c6d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 9 Apr 2024 14:52:57 +0200 Subject: [PATCH 1048/1551] Changelog for 1.12.0 (#14232) --- CHANGELOG.md | 370 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 370 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e152e9ad2d4..ab77a795cd7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,375 @@ # Changelog +## [1.12.0] (2024-04-09) + +[1.12.0]: https://github.com/crystal-lang/crystal/releases/1.12.0 + +### Features + +#### lang + +- Allow multiple parameters and blocks for operators ending in `=` ([#14159], thanks @HertzDevil) + +[#14159]: https://github.com/crystal-lang/crystal/pull/14159 + +#### stdlib + +- *(concurrency)* MT: reduce interleaved backtraces in spawn unhandled exceptions ([#14220], thanks @ysbaddaden) +- *(concurrency)* Fix: opening/reading from fifo/chardev files are blocking the thread ([#14255], thanks @ysbaddaden) +- *(concurrency)* Fix: Atomics and Locks (ARM, AArch64, X86) ([#14293], thanks @ysbaddaden) +- *(files)* Add `IO::FileDescriptor::Handle` ([#14390], thanks @straight-shoota) +- *(macros)* Add macro methods for lib-related nodes ([#14218], thanks @HertzDevil) +- *(macros)* Add macro methods for `Primitive` ([#14263], thanks @HertzDevil) +- *(macros)* Add macro methods for `TypeOf` ([#14262], thanks @HertzDevil) +- *(macros)* Add macro methods for `Alias` ([#14261], thanks @HertzDevil) +- *(macros)* Add macro methods for `Asm` and `AsmOperand` ([#14268], thanks @HertzDevil) +- *(macros)* Relax `delegate`'s setter detection ([#14282], thanks @HertzDevil) +- *(numeric)* Add `BigRational#%`, `#tdiv`, `#remainder` ([#14306], thanks @HertzDevil) +- *(runtime)* **[experimental]** Add `ReferenceStorage` for manual allocation of references ([#14270], thanks @HertzDevil) +- *(runtime)* Add MSVC invalid parameter handler ([#14313], thanks @HertzDevil) +- *(system)* Add `Signal#trap_handler?` ([#14126], thanks @stakach) +- *(system)* Thread: set name ([#14257], thanks @ysbaddaden) +- *(system)* Add `Process.on_terminate` ([#13694], thanks @stakach) +- *(time)* Add support for `Etc/UTC` time zone identifier without tzdb ([#14185], thanks @femto) + +[#14220]: https://github.com/crystal-lang/crystal/pull/14220 +[#14255]: https://github.com/crystal-lang/crystal/pull/14255 +[#14293]: https://github.com/crystal-lang/crystal/pull/14293 +[#14390]: https://github.com/crystal-lang/crystal/pull/14390 +[#14218]: https://github.com/crystal-lang/crystal/pull/14218 +[#14263]: https://github.com/crystal-lang/crystal/pull/14263 +[#14262]: https://github.com/crystal-lang/crystal/pull/14262 +[#14261]: https://github.com/crystal-lang/crystal/pull/14261 +[#14268]: https://github.com/crystal-lang/crystal/pull/14268 +[#14282]: https://github.com/crystal-lang/crystal/pull/14282 +[#14306]: https://github.com/crystal-lang/crystal/pull/14306 +[#14270]: https://github.com/crystal-lang/crystal/pull/14270 +[#14313]: https://github.com/crystal-lang/crystal/pull/14313 +[#14126]: https://github.com/crystal-lang/crystal/pull/14126 +[#14257]: https://github.com/crystal-lang/crystal/pull/14257 +[#13694]: https://github.com/crystal-lang/crystal/pull/13694 +[#14185]: https://github.com/crystal-lang/crystal/pull/14185 + +#### compiler + +- Add `CRYSTAL_CONFIG_CC` compiler config ([#14318], thanks @straight-shoota) +- *(cli)* Respect `NO_COLOR` in the compiler ([#14260], thanks @HertzDevil) +- *(cli)* Respect `--static` on Windows ([#14292], thanks @HertzDevil) +- *(cli)* Allow `--single-module` and `--threads` for `eval` and `spec` ([#14341], thanks @HertzDevil) +- *(codegen)* Add `--frame-pointers` to control preservation of frame pointers ([#13860], thanks @refi64) +- *(codegen)* x86-64 Solaris / illumos support ([#14343], thanks @HertzDevil) +- *(interpreter)* Support `@[Link]`'s DLL search order in the interpreter on Windows ([#14146], thanks @HertzDevil) +- *(interpreter)* Automatically detect MSVC tools on Windows interpreter ([#14391], thanks @HertzDevil) +- *(parser)* Allow calling `#[]=` with a block using method syntax ([#14161], thanks @HertzDevil) +- *(semantic)* Change short_reference for top-level macros to `::foo` ([#14203], thanks @femto) + +[#14318]: https://github.com/crystal-lang/crystal/pull/14318 +[#14260]: https://github.com/crystal-lang/crystal/pull/14260 +[#14292]: https://github.com/crystal-lang/crystal/pull/14292 +[#14341]: https://github.com/crystal-lang/crystal/pull/14341 +[#13860]: https://github.com/crystal-lang/crystal/pull/13860 +[#14343]: https://github.com/crystal-lang/crystal/pull/14343 +[#14146]: https://github.com/crystal-lang/crystal/pull/14146 +[#14391]: https://github.com/crystal-lang/crystal/pull/14391 +[#14161]: https://github.com/crystal-lang/crystal/pull/14161 +[#14203]: https://github.com/crystal-lang/crystal/pull/14203 + +#### tools + +- Add `crystal tool flags` ([#14234], thanks @straight-shoota) +- *(formatter)* Add more whitespace around `ProcLiteral`s ([#14209], thanks @HertzDevil) + +[#14234]: https://github.com/crystal-lang/crystal/pull/14234 +[#14209]: https://github.com/crystal-lang/crystal/pull/14209 + +### Bugfixes + +#### lang + +- *(macros)* Remove extra newline in top-level `FunDef`'s string representation ([#14212], thanks @HertzDevil) +- *(macros)* Remove `T*` and `T[N]` macro interpolation behavior inside libs ([#14215], thanks @HertzDevil) + +[#14212]: https://github.com/crystal-lang/crystal/pull/14212 +[#14215]: https://github.com/crystal-lang/crystal/pull/14215 + +#### stdlib + +- *(collection)* Fix `Hash#update` when default block also adds given key ([#14417], thanks @HertzDevil) +- *(collection)* Fix `Hash#put_if_absent` putting duplicate keys ([#14427], thanks @HertzDevil) +- *(concurrency)* Reserve stack space on non-main threads for crash recovery on Windows ([#14187], thanks @HertzDevil) +- *(concurrency)* Add memory barrier to `Mutex#unlock` on aarch64 ([#14272], thanks @jgaskins) +- *(concurrency)* init schedulers before we spawn fibers ([#14339], thanks @ysbaddaden) +- *(files)* Make `FileUtils.mv` work across filesystems on Windows ([#14320], thanks @HertzDevil) +- *(llvm)* Use correct string size for `LLVM::Type#inline_asm` ([#14265], thanks @HertzDevil) +- *(llvm)* Fix System V ABI for packed structs with misaligned fields ([#14324], thanks @HertzDevil) +- *(networking)* OpenSSL 3.x reports unexpected EOF as SSL error ([#14219], thanks @ysbaddaden) +- *(numeric)* Make equality between `Complex` and other numbers exact ([#14309], thanks @HertzDevil) +- *(numeric)* Fix `#hash` for the `Big*` number types ([#14308], thanks @HertzDevil) +- *(runtime)* Do not allocate memory in the segmentation fault signal handler ([#14327], thanks @HertzDevil) +- *(runtime)* Fix crash stack trace decoding on macOS ([#14335], thanks @HertzDevil) +- *(runtime)* `Crystal::RWLock` should be a struct ([#14345], thanks @ysbaddaden) +- *(runtime)* Fix `min_by?` in IOCP event loop `#run_once` ([#14394], thanks @straight-shoota) +- *(serialization)* `XML::Reader`: Disallow attributes containing null bytes ([#14193], thanks @HertzDevil) +- *(serialization)* Always call `LibXML.xmlInitParser` when requiring XML libraries ([#14191], thanks @HertzDevil) +- *(system)* Fix macro `Crystal::LIBRARY_PATH.split` when cross-compiling ([#14330], thanks @HertzDevil) +- *(system)* Add `SA_RESTART` flag to sigaction syscall ([#14351], thanks @ysbaddaden) +- *(text)* Add `Nil` return type restriction to `String::Formatter#consume_substitution` ([#14430], thanks @straight-shoota) + +[#14417]: https://github.com/crystal-lang/crystal/pull/14417 +[#14427]: https://github.com/crystal-lang/crystal/pull/14427 +[#14187]: https://github.com/crystal-lang/crystal/pull/14187 +[#14272]: https://github.com/crystal-lang/crystal/pull/14272 +[#14339]: https://github.com/crystal-lang/crystal/pull/14339 +[#14320]: https://github.com/crystal-lang/crystal/pull/14320 +[#14265]: https://github.com/crystal-lang/crystal/pull/14265 +[#14324]: https://github.com/crystal-lang/crystal/pull/14324 +[#14219]: https://github.com/crystal-lang/crystal/pull/14219 +[#14309]: https://github.com/crystal-lang/crystal/pull/14309 +[#14308]: https://github.com/crystal-lang/crystal/pull/14308 +[#14327]: https://github.com/crystal-lang/crystal/pull/14327 +[#14335]: https://github.com/crystal-lang/crystal/pull/14335 +[#14345]: https://github.com/crystal-lang/crystal/pull/14345 +[#14394]: https://github.com/crystal-lang/crystal/pull/14394 +[#14193]: https://github.com/crystal-lang/crystal/pull/14193 +[#14191]: https://github.com/crystal-lang/crystal/pull/14191 +[#14330]: https://github.com/crystal-lang/crystal/pull/14330 +[#14351]: https://github.com/crystal-lang/crystal/pull/14351 +[#14430]: https://github.com/crystal-lang/crystal/pull/14430 + +#### compiler + +- *(cli)* `build --no-codegen` output file name error ([#14239], thanks @apainintheneck) +- *(codegen)* Do not handle inline assembly with `"intel"` flag as AT&T syntax ([#14264], thanks @HertzDevil) +- *(codegen)* **[breaking]** Respect alignments above `alignof(Void*)` inside union values ([#14279], thanks @HertzDevil) +- *(codegen)* Fix stack corruption in union-to-union casts ([#14289], thanks @HertzDevil) +- *(codegen)* Don't copy DLL to output directory if file already exists ([#14315], thanks @HertzDevil) +- *(codegen)* Fix `Proc#call` that takes and returns large extern structs by value ([#14323], thanks @HertzDevil) +- *(codegen)* Never discard ivar initializer inside `.allocate` and `.pre_initialize` ([#14337], thanks @HertzDevil) +- *(codegen)* Use separate names for constant and class variable internals ([#14445], thanks @HertzDevil) +- *(interpreter)* fix fiber's resumable property ([#14252], thanks @ysbaddaden) +- *(interpreter)* Ensure all constants only have one initializer in the interpreter ([#14381], thanks @HertzDevil) +- *(interpreter)* Handle NaN comparisons in the interpreter ([#14441], thanks @HertzDevil) +- *(interpreter)* Check `UInt16#to_u8` for overflow in the interpreter ([#14436], thanks @HertzDevil) +- *(interpreter)* Fix interpreter internal overflow for `UInt128#to_f32` and `#to_f32!` ([#14437], thanks @HertzDevil) +- *(parser)* Fix name locations of `FunDef` and `External` nodes ([#14267], thanks @HertzDevil) +- *(parser)* Fix end locations of `Alias` nodes ([#14271], thanks @HertzDevil) + +[#14239]: https://github.com/crystal-lang/crystal/pull/14239 +[#14264]: https://github.com/crystal-lang/crystal/pull/14264 +[#14279]: https://github.com/crystal-lang/crystal/pull/14279 +[#14289]: https://github.com/crystal-lang/crystal/pull/14289 +[#14315]: https://github.com/crystal-lang/crystal/pull/14315 +[#14323]: https://github.com/crystal-lang/crystal/pull/14323 +[#14337]: https://github.com/crystal-lang/crystal/pull/14337 +[#14445]: https://github.com/crystal-lang/crystal/pull/14445 +[#14252]: https://github.com/crystal-lang/crystal/pull/14252 +[#14381]: https://github.com/crystal-lang/crystal/pull/14381 +[#14441]: https://github.com/crystal-lang/crystal/pull/14441 +[#14436]: https://github.com/crystal-lang/crystal/pull/14436 +[#14437]: https://github.com/crystal-lang/crystal/pull/14437 +[#14267]: https://github.com/crystal-lang/crystal/pull/14267 +[#14271]: https://github.com/crystal-lang/crystal/pull/14271 + +#### tools + +- *(formatter)* Fix format for `asm` with comments ([#14278], thanks @straight-shoota) +- *(formatter)* Fix formatter for white space in `a.[b]` syntax ([#14346], thanks @straight-shoota) +- *(formatter)* Fix formatter on call without parentheses followed by doc comment ([#14354], thanks @straight-shoota) +- *(formatter)* Do not remove the whitespace in `foo ()` when formatting ([#14439], thanks @HertzDevil) + +[#14278]: https://github.com/crystal-lang/crystal/pull/14278 +[#14346]: https://github.com/crystal-lang/crystal/pull/14346 +[#14354]: https://github.com/crystal-lang/crystal/pull/14354 +[#14439]: https://github.com/crystal-lang/crystal/pull/14439 + +### Chores + +#### stdlib + +- *(concurrency)* **[deprecation]** Drop flag `openbsd6.2` ([#14233], thanks @straight-shoota) +- *(files)* **[deprecation]** Deprecate timeout setters with `Number` arguments ([#14372], thanks @straight-shoota) + +[#14233]: https://github.com/crystal-lang/crystal/pull/14233 +[#14372]: https://github.com/crystal-lang/crystal/pull/14372 + +#### compiler + +- *(codegen)* Drop pinning Dwarf version 2 for android ([#14243], thanks @straight-shoota) + +[#14243]: https://github.com/crystal-lang/crystal/pull/14243 + +### Performance + +#### stdlib + +- *(collection)* Optimize hash lookup in `Enumerable#group_by` ([#14235], thanks @straight-shoota) +- *(concurrency)* Use per-scheduler stack pools (let's recycle) ([#14100], thanks @ysbaddaden) + +[#14235]: https://github.com/crystal-lang/crystal/pull/14235 +[#14100]: https://github.com/crystal-lang/crystal/pull/14100 + +#### compiler + +- *(codegen)* on demand distribution to forked processes ([#14273], thanks @ysbaddaden) +- *(interpreter)* Use `Fiber::StackPool` in the interpreter ([#14395], thanks @HertzDevil) + +[#14273]: https://github.com/crystal-lang/crystal/pull/14273 +[#14395]: https://github.com/crystal-lang/crystal/pull/14395 + +### Refactor + +#### stdlib + +- *(files)* Replace some Microsoft C runtime funs with Win32 equivalents ([#14316], thanks @HertzDevil) +- *(files)* Move timeout properties to `Socket` and `IO::FileDescriptor` ([#14367], thanks @straight-shoota) +- *(files)* Add type restrictions to `#unbuffered_*` implementations ([#14382], thanks @straight-shoota) +- *(networking)* Refactor `HTTP::Client` timeout ivars to `Time::Span` ([#14371], thanks @straight-shoota) +- *(networking)* Refactor `Socket#system_receive` to return `Address` ([#14384], thanks @straight-shoota) +- *(networking)* Refactor `#system_connect` without yield ([#14383], thanks @straight-shoota) +- *(numeric)* Add `Crystal::Hasher.reduce_num` and `#number` ([#14304], thanks @HertzDevil) +- *(runtime)* Refactor and add comments to IOCP `#run_once` ([#14380], thanks @straight-shoota) +- *(specs)* **[deprecation]** Move most of spec runner's state into `Spec::CLI` ([#14170], thanks @HertzDevil) +- *(specs)* Add `Spec::Formatter#should_print_summary?` ([#14397], thanks @HertzDevil) + +[#14316]: https://github.com/crystal-lang/crystal/pull/14316 +[#14367]: https://github.com/crystal-lang/crystal/pull/14367 +[#14382]: https://github.com/crystal-lang/crystal/pull/14382 +[#14371]: https://github.com/crystal-lang/crystal/pull/14371 +[#14384]: https://github.com/crystal-lang/crystal/pull/14384 +[#14383]: https://github.com/crystal-lang/crystal/pull/14383 +[#14304]: https://github.com/crystal-lang/crystal/pull/14304 +[#14380]: https://github.com/crystal-lang/crystal/pull/14380 +[#14170]: https://github.com/crystal-lang/crystal/pull/14170 +[#14397]: https://github.com/crystal-lang/crystal/pull/14397 + +#### compiler + +- Ensure `Crystal::Visitor#visit` returns `Bool` ([#14266], thanks @HertzDevil) +- *(parser)* Add `Token::Kind#unary_operator?` ([#14342], thanks @straight-shoota) +- *(parser)* Add `Lexer#wants_def_or_macro_name` ([#14352], thanks @straight-shoota) + +[#14266]: https://github.com/crystal-lang/crystal/pull/14266 +[#14342]: https://github.com/crystal-lang/crystal/pull/14342 +[#14352]: https://github.com/crystal-lang/crystal/pull/14352 + +### Documentation + +#### stdlib + +- *(collection)* Fix docs `:inherit:` pragma for `Indexable#first` ([#14296], thanks @lachlan) +- *(collection)* Fix `Hash.new(initial_capacity, &block)` doc to use relevant example ([#14429], thanks @lachlan) +- *(crypto)* Improve OpenSSL module documentation ([#14410], thanks @summer-alice) +- *(numeric)* Enhance docs for `Int#downto` ([#14176], thanks @jkthorne) +- *(runtime)* Document builtin constants ([#14276], thanks @straight-shoota) +- *(runtime)* Fix `Pointer#+(offset: Int64)` doc parameter name typo ([#14428], thanks @lachlan) +- *(runtime)* Improve documentation for `at_exit` handler conditions ([#14426], thanks @straight-shoota) +- *(system)* Fix typo in Signal docs ([#14400], thanks @joshrickard) +- *(text)* Fix `Colorize.enabled?`'s documentation ([#14258], thanks @HertzDevil) + +[#14296]: https://github.com/crystal-lang/crystal/pull/14296 +[#14429]: https://github.com/crystal-lang/crystal/pull/14429 +[#14410]: https://github.com/crystal-lang/crystal/pull/14410 +[#14176]: https://github.com/crystal-lang/crystal/pull/14176 +[#14276]: https://github.com/crystal-lang/crystal/pull/14276 +[#14428]: https://github.com/crystal-lang/crystal/pull/14428 +[#14426]: https://github.com/crystal-lang/crystal/pull/14426 +[#14400]: https://github.com/crystal-lang/crystal/pull/14400 +[#14258]: https://github.com/crystal-lang/crystal/pull/14258 + +### Specs + +#### stdlib + +- Fix spelling in `spec/std/uri/params_spec.cr` ([#14302], thanks @jbampton) +- *(files)* Refactor expectations with `SpecChannelStatus` to be explicit ([#14378], thanks @straight-shoota) +- *(files)* Move some `IO::FileDescriptor` specs to the correct file ([#14431], thanks @HertzDevil) +- *(system)* Always preserve the environment for specs that modify `ENV` ([#14211], thanks @HertzDevil) +- *(system)* Ensure Windows time zone specs request `SeTimeZonePrivilege` properly ([#14297], thanks @HertzDevil) +- *(text)* Add single source of UTF-8 test sequences for specs ([#14433], thanks @HertzDevil) +- *(time)* Fix requires for `time/time_spec.cr` and `time/format_spec.cr` ([#14385], thanks @HertzDevil) + +[#14302]: https://github.com/crystal-lang/crystal/pull/14302 +[#14378]: https://github.com/crystal-lang/crystal/pull/14378 +[#14431]: https://github.com/crystal-lang/crystal/pull/14431 +[#14211]: https://github.com/crystal-lang/crystal/pull/14211 +[#14297]: https://github.com/crystal-lang/crystal/pull/14297 +[#14433]: https://github.com/crystal-lang/crystal/pull/14433 +[#14385]: https://github.com/crystal-lang/crystal/pull/14385 + +#### compiler + +- Remove the prelude from some compiler specs ([#14336], thanks @HertzDevil) +- *(interpreter)* Fix: don't run thread specs with the interpreter ([#14287], thanks @ysbaddaden) +- *(interpreter)* Add `pending_interpreted` ([#14386], thanks @HertzDevil) +- *(interpreter)* Remove `spec/interpreter_std_spec.cr` ([#14399], thanks @HertzDevil) +- *(semantic)* Enable `@[Primitive(:va_arg)]` semantic spec on Windows ([#14338], thanks @HertzDevil) + +[#14336]: https://github.com/crystal-lang/crystal/pull/14336 +[#14287]: https://github.com/crystal-lang/crystal/pull/14287 +[#14386]: https://github.com/crystal-lang/crystal/pull/14386 +[#14399]: https://github.com/crystal-lang/crystal/pull/14399 +[#14338]: https://github.com/crystal-lang/crystal/pull/14338 + +### Infrastructure + +- Changelog for 1.12.0 ([#14232], thanks @straight-shoota) +- Remove filtering of already mentioned PRs ([#14229], thanks @straight-shoota) +- Mention RFC process in contribution instructions ([#14291], thanks @straight-shoota) +- Drop Nikola sponsor mention from Readme ([#14290], thanks @straight-shoota) +- Enhance changelog script to pull milestone info from GitHub ([#14230], thanks @straight-shoota) +- Add `shard.yml` ([#14365], thanks @straight-shoota) +- Update vendored dependencies ([#14373], thanks @straight-shoota) +- Fix `Milestone` JSON bindings in `github-changelog` helper ([#14404], thanks @straight-shoota) +- Make repository configurable for reusable `github-changelog` ([#14407], thanks @straight-shoota) +- Use link refs for PR links in changelog ([#14406], thanks @straight-shoota) +- Implement pagination for GitHub API in `github-changelog` helper ([#14412], thanks @straight-shoota) +- Add `scripts/update-changelog.sh` ([#14231], thanks @straight-shoota) +- Update distribution-scripts ([#14457], thanks @straight-shoota) +- Change some line endings from CRLF to LF ([#14299], thanks @HertzDevil) +- Update copyright year in NOTICE.md ([#14329], thanks @HertzDevil) +- Install system dependencies in the Windows GUI installer ([#14328], thanks @HertzDevil) +- Skip building `llvm_ext.cc` on LLVM 18 or above ([#14357], thanks @HertzDevil) +- *(ci)* Update previous Crystal release 1.11.0 ([#14189], thanks @straight-shoota) +- *(ci)* Update previous Crystal release 1.11.1 ([#14224], thanks @straight-shoota) +- *(ci)* Update previous Crystal release - 1.11.2 ([#14251], thanks @straight-shoota) +- *(ci)* Update GH Actions ([#14246], thanks @renovate) +- *(ci)* Upgrade `resource_class` for `test_preview_mt` ([#14274], thanks @straight-shoota) +- *(ci)* Upgrade from old machine images approaching EOL ([#14275], thanks @straight-shoota) +- *(ci)* Update Windows library versions ([#14355], thanks @HertzDevil) +- *(ci)* Update cachix/install-nix-action action to v26 ([#14375], thanks @renovate) +- *(ci)* Update shards 0.18.0 ([#14411], thanks @straight-shoota) +- *(ci)* Support LLVM 18.1 ([#14277], thanks @HertzDevil) +- *(ci)* Use `Makefile.win` for Shards on Windows CI ([#14414], thanks @HertzDevil) + +[#14232]: https://github.com/crystal-lang/crystal/pull/14232 +[#14229]: https://github.com/crystal-lang/crystal/pull/14229 +[#14291]: https://github.com/crystal-lang/crystal/pull/14291 +[#14290]: https://github.com/crystal-lang/crystal/pull/14290 +[#14230]: https://github.com/crystal-lang/crystal/pull/14230 +[#14365]: https://github.com/crystal-lang/crystal/pull/14365 +[#14373]: https://github.com/crystal-lang/crystal/pull/14373 +[#14404]: https://github.com/crystal-lang/crystal/pull/14404 +[#14407]: https://github.com/crystal-lang/crystal/pull/14407 +[#14406]: https://github.com/crystal-lang/crystal/pull/14406 +[#14412]: https://github.com/crystal-lang/crystal/pull/14412 +[#14231]: https://github.com/crystal-lang/crystal/pull/14231 +[#14457]: https://github.com/crystal-lang/crystal/pull/14457 +[#14299]: https://github.com/crystal-lang/crystal/pull/14299 +[#14329]: https://github.com/crystal-lang/crystal/pull/14329 +[#14328]: https://github.com/crystal-lang/crystal/pull/14328 +[#14357]: https://github.com/crystal-lang/crystal/pull/14357 +[#14189]: https://github.com/crystal-lang/crystal/pull/14189 +[#14224]: https://github.com/crystal-lang/crystal/pull/14224 +[#14251]: https://github.com/crystal-lang/crystal/pull/14251 +[#14246]: https://github.com/crystal-lang/crystal/pull/14246 +[#14274]: https://github.com/crystal-lang/crystal/pull/14274 +[#14275]: https://github.com/crystal-lang/crystal/pull/14275 +[#14355]: https://github.com/crystal-lang/crystal/pull/14355 +[#14375]: https://github.com/crystal-lang/crystal/pull/14375 +[#14411]: https://github.com/crystal-lang/crystal/pull/14411 +[#14277]: https://github.com/crystal-lang/crystal/pull/14277 +[#14414]: https://github.com/crystal-lang/crystal/pull/14414 + ## [1.11.2] (2024-01-18) [1.11.2]: https://github.com/crystal-lang/crystal/releases/1.11.2 From aee9ab69eccdeba472b710b38c96067e7ed60d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 9 Apr 2024 15:09:33 +0200 Subject: [PATCH 1049/1551] Update version to 1.12.0 (#14465) --- shard.yml | 2 +- src/VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shard.yml b/shard.yml index 6403b105f589..97174921b8b7 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: crystal -version: 1.12.0-dev +version: 1.12.0 authors: - Crystal Core Team diff --git a/src/VERSION b/src/VERSION index 381cf02417c4..0eed1a29efd6 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.12.0-dev +1.12.0 From 85525fcfdcd5b3ad80ddf3f1d3544a5136e4d07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 10 Apr 2024 21:57:30 +0200 Subject: [PATCH 1050/1551] Fix formatter with whitespace before closing parenthesis (#14471) --- spec/compiler/formatter/formatter_spec.cr | 15 +++++++++++++++ src/compiler/crystal/tools/formatter.cr | 1 + 2 files changed, 16 insertions(+) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index a942a910dc7c..e87b7d3b010c 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -437,6 +437,21 @@ describe Crystal::Formatter do ); end CRYSTAL + assert_format <<-CRYSTAL, <<-CRYSTAL + module M + @[MyAnn( + 1 + + )] + end + CRYSTAL + module M + @[MyAnn( + 1 + )] + end + CRYSTAL + assert_format "loop do\n 1\nrescue\n 2\nend" assert_format "loop do\n 1\n loop do\n 2\n rescue\n 3\n end\n 4\nend" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 238c0a331847..4f5331ed5adb 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -3031,6 +3031,7 @@ module Crystal ends_with_newline = true next_token end + skip_space_or_newline finish_args(true, has_newlines, ends_with_newline, found_comment, @indent) end From 4cea10199d5006000a129413f6f607697185c83a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 11 Apr 2024 14:31:41 +0200 Subject: [PATCH 1051/1551] Changelog for 1.12.1 (#14472) --- CHANGELOG.md | 18 ++++++++++++++++++ shard.yml | 2 +- src/VERSION | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab77a795cd7b..ef1a6667e788 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [1.12.1] (2024-04-11) + +[1.12.1]: https://github.com/crystal-lang/crystal/releases/1.12.1 + +### Bugfixes + +#### tools + +- *(formatter)* **[regression]** Fix formatter with whitespace before closing parenthesis ([#14471], thanks @straight-shoota) + +[#14471]: https://github.com/crystal-lang/crystal/pull/14471 + +### Infrastructure + +- Changelog for 1.12.1 ([#14472], thanks @straight-shoota) + +[#14472]: https://github.com/crystal-lang/crystal/pull/14472 + ## [1.12.0] (2024-04-09) [1.12.0]: https://github.com/crystal-lang/crystal/releases/1.12.0 diff --git a/shard.yml b/shard.yml index 97174921b8b7..d36f43548447 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: crystal -version: 1.12.0 +version: 1.12.1 authors: - Crystal Core Team diff --git a/src/VERSION b/src/VERSION index 0eed1a29efd6..f8f4f03b3dcc 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.12.0 +1.12.1 From 9d4dd9776e6d778da133709e577dd5cc0dbaa46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 12 Apr 2024 19:10:45 +0200 Subject: [PATCH 1052/1551] Update previous Crystal release 1.12.1 (#14480) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 6 +++--- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win_build_portable.yml | 2 +- bin/ci | 6 +++--- shard.yml | 2 +- shell.nix | 12 ++++++------ src/VERSION | 2 +- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 215465bc570c..1c14a482d2b4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.11.2/crystal-1.11.2-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.12.1/crystal-1.12.1-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 1788f74351d9..04f60ed40318 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.11.2-build + image: crystallang/crystal:1.12.1-build name: "Test Interpreter" steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.11.2-build + image: crystallang/crystal:1.12.1-build name: Build interpreter steps: - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.11.2-build + image: crystallang/crystal:1.12.1-build strategy: matrix: part: [0, 1, 2, 3] diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 3d85af22e5a3..f395cf657082 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2] + crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.1] flags: [""] include: # libffi is only available starting from the 1.2.2 build images diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 887ede290fe3..d8df01d13090 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -56,7 +56,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.11.2" + crystal: "1.12.1" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 65766c6325c2..7deef9042769 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.11.2-alpine + container: crystallang/crystal:1.12.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -27,7 +27,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.11.2-alpine + container: crystallang/crystal:1.12.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.11.2-alpine + container: crystallang/crystal:1.12.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index d7d9c2655584..804f6439ddfc 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.11.2-alpine + container: crystallang/crystal:1.12.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.11.2-alpine + container: crystallang/crystal:1.12.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index c8f2058ebea0..9eb58df632df 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.11.2-build + container: crystallang/crystal:1.12.1-build steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 13f4e73a0d51..5e39770ffab6 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -24,7 +24,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.11.2" + crystal: "1.12.1" - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index 9d0b73507f24..38c3cb829d81 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.11.2/crystal-1.11.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.11.2-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.12.1/crystal-1.12.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.12.1-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.11.2}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.12.1}" case $ARCH in x86_64) diff --git a/shard.yml b/shard.yml index d36f43548447..396d91bdffe2 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: crystal -version: 1.12.1 +version: 1.13.0-dev authors: - Crystal Core Team diff --git a/shell.nix b/shell.nix index aef4211f280a..6a381f036907 100644 --- a/shell.nix +++ b/shell.nix @@ -52,18 +52,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.11.2/crystal-1.11.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:0qcdr8yl6k7il0x63z2gyqbkjp89m77nq7x1h3m80y1imfg0z1q9"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.12.1/crystal-1.12.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:0f5kw9hqf01cqphmqa05icfqfmi087iyliaknpy67j95z405d0xz"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.11.2/crystal-1.11.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:0qcdr8yl6k7il0x63z2gyqbkjp89m77nq7x1h3m80y1imfg0z1q9"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.12.1/crystal-1.12.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:0f5kw9hqf01cqphmqa05icfqfmi087iyliaknpy67j95z405d0xz"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.11.2/crystal-1.11.2-1-linux-x86_64.tar.gz"; - sha256 = "sha256:01l9cq8d3p7p3ijkrg0xpchj0l21z3sjvd5f6zw1pnms647a6hdr"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.12.1/crystal-1.12.1-1-linux-x86_64.tar.gz"; + sha256 = "sha256:17w6l7cq9z3y8xd3cmqsgiyhxmbsa11d0xmlbbiy8fwv9lja03ww"; }; }.${pkgs.stdenv.system}); diff --git a/src/VERSION b/src/VERSION index f8f4f03b3dcc..a4ab692a5f77 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.12.1 +1.13.0-dev From 6128e727daf3828c147b19ccd30d99ef84fb5e75 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Fri, 12 Apr 2024 13:11:40 -0400 Subject: [PATCH 1053/1551] Ensure `Enumerable#to_a` and `Enumerable#tally` properly retain return type of `T` (#14447) --- spec/std/enumerable_spec.cr | 31 +++++++++++++++++++++++++++++-- spec/std/indexable_spec.cr | 33 +++++++++++++++++++++++++++++++++ src/enumerable.cr | 6 ++++-- src/indexable.cr | 11 +++++++++++ 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 0bcbac725cb5..4ff17d672687 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -1,6 +1,25 @@ require "spec" require "spec/helpers/iterate" +module SomeInterface; end + +private record One do + include SomeInterface +end + +private record Two do + include SomeInterface +end + +private struct InterfaceEnumerable + include Enumerable(SomeInterface) + + def each(&) + yield One.new + yield Two.new + end +end + private class SpecEnumerable include Enumerable(Int32) @@ -132,13 +151,17 @@ describe "Enumerable" do end describe "to_a" do - context "with a block" do + it "with a block" do SpecEnumerable.new.to_a { |e| e*2 }.should eq [2, 4, 6] end - context "without a block" do + it "without a block" do SpecEnumerable.new.to_a.should eq [1, 2, 3] end + + it "without a block of an interface type" do + InterfaceEnumerable.new.to_a.should eq [One.new, Two.new] + end end describe "#to_set" do @@ -1456,6 +1479,10 @@ describe "Enumerable" do {'c' => 1, 'r' => 2, 'y' => 2, 's' => 1, 't' => 1, 'a' => 1, 'l' => 1, 'u' => 1, 'b' => 1} ) end + + it "tallies an interface type" do + InterfaceEnumerable.new.tally.should eq({One.new => 1, Two.new => 1}) + end end end diff --git a/spec/std/indexable_spec.cr b/spec/std/indexable_spec.cr index 75a20f14e521..2d1ab60c0846 100644 --- a/spec/std/indexable_spec.cr +++ b/spec/std/indexable_spec.cr @@ -1,6 +1,33 @@ require "spec" require "spec/helpers/iterate" +module OtherInterface; end + +private record Three do + include OtherInterface +end + +private record Four do + include OtherInterface +end + +private struct InterfaceIndexable + include Indexable(OtherInterface) + + def size + 2 + end + + def unsafe_fetch(index : Int) : OtherInterface + case index + when 0 then Three.new + when 1 then Four.new + else + raise "" + end + end +end + private class SafeIndexable include Indexable(Int32) @@ -818,4 +845,10 @@ describe Indexable do it_iterates "#each_repeated_combination", [[1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 2, 2], [1, 2, 2, 2], [2, 2, 2, 2]], SafeIndexable.new(2, 1).each_repeated_combination(4) end end + + describe "#to_a" do + it "without a block of an interface type" do + InterfaceIndexable.new.to_a.should eq [Three.new, Four.new] + end + end end diff --git a/src/enumerable.cr b/src/enumerable.cr index 9a1aad3debd0..ff49de1ff308 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -1970,7 +1970,7 @@ module Enumerable(T) # ["a", "b", "c", "b"].tally # => {"a"=>1, "b"=>2, "c"=>1} # ``` def tally : Hash(T, Int32) - tally_by(&.itself) + tally_by(Hash(T, Int32).new, &.itself) end # Tallies the collection. Accepts a *hash* to count occurrences. @@ -1994,7 +1994,9 @@ module Enumerable(T) # (1..5).to_a # => [1, 2, 3, 4, 5] # ``` def to_a : Array(T) - to_a(&.itself) + ary = [] of T + each { |e| ary << e } + ary end # Returns an `Array` with the results of running *block* against each element of the collection. diff --git a/src/indexable.cr b/src/indexable.cr index bec815ee3d7f..d39ddaaef197 100644 --- a/src/indexable.cr +++ b/src/indexable.cr @@ -693,6 +693,17 @@ module Indexable(T) end end + # Returns an `Array` with all the elements in the collection. + # + # ``` + # {1, 2, 3}.to_a # => [1, 2, 3] + # ``` + def to_a : Array(T) + ary = Array(T).new(size) + each { |e| ary << e } + ary + end + # Returns an `Array` with the results of running *block* against each element of the collection. # # ``` From 124386841a13437641e4dbb2ceee6226bb7794c5 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Apr 2024 01:11:56 +0800 Subject: [PATCH 1054/1551] Never raise `IndexError` in `#[]?(Range)` (#14444) --- spec/std/regex/match_data_spec.cr | 1 + spec/std/slice_spec.cr | 15 +++++++++++++++ src/regex/match_data.cr | 2 +- src/slice.cr | 2 +- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/spec/std/regex/match_data_spec.cr b/spec/std/regex/match_data_spec.cr index 1ed6cf70c44e..256abe19f7bc 100644 --- a/spec/std/regex/match_data_spec.cr +++ b/spec/std/regex/match_data_spec.cr @@ -352,6 +352,7 @@ describe "Regex::MatchData" do md[1..]?.should eq(["a", "b"]) md[..]?.should eq(["ab", "a", "b"]) md[4..]?.should be_nil + md[-4..]?.should be_nil end it "can use start and count" do diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr index 4a2469f69aa7..505db8f09109 100644 --- a/spec/std/slice_spec.cr +++ b/spec/std/slice_spec.cr @@ -117,6 +117,21 @@ describe "Slice" do slice[3, -1]?.should be_nil end + it "does []? with range" do + slice = Slice.new(4) { |i| i + 1 } + slice1 = slice[1..2]? + slice1.should_not be_nil + slice1 = slice1.not_nil! + slice1.size.should eq(2) + slice1[0].should eq(2) + slice1[1].should eq(3) + + slice[4..7]?.should be_nil + slice[3..4]?.should be_nil + slice[-2..4]?.should be_nil + slice[-6..-5]?.should be_nil + end + it "does [] with start and count" do slice = Slice.new(4) { |i| i + 1 } slice1 = slice[1, 2] diff --git a/src/regex/match_data.cr b/src/regex/match_data.cr index ddf746ae0d6a..3fcbc5c424ac 100644 --- a/src/regex/match_data.cr +++ b/src/regex/match_data.cr @@ -210,7 +210,7 @@ class Regex # Like `#[](Range)`, but returns `nil` if the range's start is out of range. def []?(range : Range) : Array(String)? - self[*Indexable.range_to_index_and_count(range, size) || raise IndexError.new]? + self[*Indexable.range_to_index_and_count(range, size) || return nil]? end # Returns count or less (if there aren't enough) matches starting at the diff --git a/src/slice.cr b/src/slice.cr index 27584c5d1a68..7a27218221a2 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -272,7 +272,7 @@ struct Slice(T) # slice[1..33]? # => nil # ``` def []?(range : Range) - start, count = Indexable.range_to_index_and_count(range, size) || raise IndexError.new + start, count = Indexable.range_to_index_and_count(range, size) || return nil self[start, count]? end From 0c672246c4e088450a9b2599114d936c59f7cf39 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Apr 2024 01:12:08 +0800 Subject: [PATCH 1055/1551] Make `String#sub` raise `IndexError` if index is equal to size (#14458) --- spec/std/string_spec.cr | 12 ++++++++++++ src/string.cr | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index dd60aa90e86e..8217f55b22a6 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -1694,6 +1694,18 @@ describe "String" do "あいうえお".sub(2, "けくこ").should eq("あいけくこえお") end + it "raises if index is out of bounds" do + expect_raises(IndexError) { "hello".sub(5, 'x') } + expect_raises(IndexError) { "hello".sub(6, "") } + expect_raises(IndexError) { "hello".sub(-6, 'x') } + expect_raises(IndexError) { "hello".sub(-7, "") } + + expect_raises(IndexError) { "あいうえお".sub(5, 'x') } + expect_raises(IndexError) { "あいうえお".sub(6, "") } + expect_raises(IndexError) { "あいうえお".sub(-6, 'x') } + expect_raises(IndexError) { "あいうえお".sub(-7, "") } + end + it "subs range with char" do "hello".sub(1..2, 'a').should eq("halo") end diff --git a/src/string.cr b/src/string.cr index 4004f0d34929..0b7796d8eb34 100644 --- a/src/string.cr +++ b/src/string.cr @@ -2536,7 +2536,7 @@ class String index += size if index < 0 byte_index = char_index_to_byte_index(index) - raise IndexError.new unless byte_index + raise IndexError.new unless byte_index && byte_index < bytesize width = char_bytesize_at(byte_index) replacement_width = replacement.bytesize From 0efbf53cc2762431146eb028de95691cac7db039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 13 Apr 2024 12:01:54 +0200 Subject: [PATCH 1056/1551] Fix type def reopening type from parent namespace (#11208) --- spec/compiler/semantic/class_spec.cr | 14 ++++++++++++++ spec/compiler/semantic/module_spec.cr | 14 ++++++++++++++ src/compiler/crystal/semantic/top_level_visitor.cr | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/class_spec.cr b/spec/compiler/semantic/class_spec.cr index 88c3dd474a3f..2445a9eedf5f 100644 --- a/spec/compiler/semantic/class_spec.cr +++ b/spec/compiler/semantic/class_spec.cr @@ -383,6 +383,20 @@ describe "Semantic: class" do ") { char } end + it "type def does not reopen type from parent namespace (#11181)" do + assert_type <<-CR, inject_primitives: false { types["Baz"].types["Foo"].types["Bar"].metaclass } + class Foo::Bar + end + + module Baz + class Foo::Bar + end + end + + Baz::Foo::Bar + CR + end + it "finds in global scope if includes module" do assert_type(" class Baz diff --git a/spec/compiler/semantic/module_spec.cr b/spec/compiler/semantic/module_spec.cr index a55efaf93a3d..46de0bd0f4c0 100644 --- a/spec/compiler/semantic/module_spec.cr +++ b/spec/compiler/semantic/module_spec.cr @@ -898,6 +898,20 @@ describe "Semantic: module" do )) { types["Foo"].types["Bar"].types["Baz"].metaclass } end + it "type def does not reopen type from parent namespace (#11181)" do + assert_type <<-CR, inject_primitives: false { types["Baz"].types["Foo"].types["Bar"].metaclass } + module Foo::Bar + end + + module Baz + module Foo::Bar + end + end + + Baz::Foo::Bar + CR + end + it "correctly types type var in included module, with a restriction with a free var (bug)" do assert_type(%( module Moo(T) diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 98ceb23df6b3..1fc7119b9ffd 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -1275,7 +1275,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def lookup_type_def_name_creating_modules(path : Path) base_type = path.global? ? program : current_type - target_type = base_type.lookup_path(path).as?(Type).try &.remove_alias_if_simple + target_type = base_type.lookup_path(path, lookup_in_namespace: false).as?(Type).try &.remove_alias_if_simple unless target_type next_type = base_type From c14fc8981460d47064cb986f39d7c10cc2babaa9 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sat, 13 Apr 2024 12:02:24 +0200 Subject: [PATCH 1057/1551] Add WaitGroup synchronization primitive (#14167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller Co-authored-by: Jason Frey Co-authored-by: Sijawusz Pur Rahnama --- spec/std/wait_group_spec.cr | 185 +++++++++++++++++++++++++++++ src/crystal/pointer_linked_list.cr | 6 + src/wait_group.cr | 120 +++++++++++++++++++ 3 files changed, 311 insertions(+) create mode 100644 spec/std/wait_group_spec.cr create mode 100644 src/wait_group.cr diff --git a/spec/std/wait_group_spec.cr b/spec/std/wait_group_spec.cr new file mode 100644 index 000000000000..459af8d5c898 --- /dev/null +++ b/spec/std/wait_group_spec.cr @@ -0,0 +1,185 @@ +require "spec" +require "wait_group" + +private def block_until_pending_waiter(wg) + while wg.@waiting.empty? + Fiber.yield + end +end + +private def forge_counter(wg, value) + wg.@counter.set(value) +end + +describe WaitGroup do + describe "#add" do + it "can't decrement to a negative counter" do + wg = WaitGroup.new + wg.add(5) + wg.add(-3) + expect_raises(RuntimeError, "Negative WaitGroup counter") { wg.add(-5) } + end + + it "resumes waiters when reaching negative counter" do + wg = WaitGroup.new(1) + spawn do + block_until_pending_waiter(wg) + wg.add(-2) + rescue RuntimeError + end + expect_raises(RuntimeError, "Negative WaitGroup counter") { wg.wait } + end + + it "can't increment after reaching negative counter" do + wg = WaitGroup.new + forge_counter(wg, -1) + + # check twice, to make sure the waitgroup counter wasn't incremented back + # to a positive value! + expect_raises(RuntimeError, "Negative WaitGroup counter") { wg.add(5) } + expect_raises(RuntimeError, "Negative WaitGroup counter") { wg.add(3) } + end + end + + describe "#done" do + it "can't decrement to a negative counter" do + wg = WaitGroup.new + wg.add(1) + wg.done + expect_raises(RuntimeError, "Negative WaitGroup counter") { wg.done } + end + + it "resumes waiters when reaching negative counter" do + wg = WaitGroup.new(1) + spawn do + block_until_pending_waiter(wg) + forge_counter(wg, 0) + wg.done + rescue RuntimeError + end + expect_raises(RuntimeError, "Negative WaitGroup counter") { wg.wait } + end + end + + describe "#wait" do + it "immediately returns when counter is zero" do + channel = Channel(Nil).new(1) + + spawn do + wg = WaitGroup.new(0) + wg.wait + channel.send(nil) + end + + select + when channel.receive + # success + when timeout(1.second) + fail "expected #wait to not block the fiber" + end + end + + it "immediately raises when counter is negative" do + wg = WaitGroup.new(0) + expect_raises(RuntimeError) { wg.done } + expect_raises(RuntimeError, "Negative WaitGroup counter") { wg.wait } + end + + it "raises when counter is positive after wake up" do + wg = WaitGroup.new(1) + waiter = Fiber.current + + spawn do + block_until_pending_waiter(wg) + waiter.enqueue + end + + expect_raises(RuntimeError, "Positive WaitGroup counter (early wake up?)") { wg.wait } + end + end + + it "waits until concurrent executions are finished" do + wg1 = WaitGroup.new + wg2 = WaitGroup.new + + 8.times do + wg1.add(16) + wg2.add(16) + exited = Channel(Bool).new(16) + + 16.times do + spawn do + wg1.done + wg2.wait + exited.send(true) + end + end + + wg1.wait + + 16.times do + select + when exited.receive + fail "WaitGroup released group too soon" + else + end + wg2.done + end + + 16.times do + select + when x = exited.receive + x.should eq(true) + when timeout(1.millisecond) + fail "Expected channel to receive value" + end + end + end + end + + it "increments the counter from executing fibers" do + wg = WaitGroup.new(16) + extra = Atomic(Int32).new(0) + + 16.times do + spawn do + wg.add(2) + + 2.times do + spawn do + extra.add(1) + wg.done + end + end + + wg.done + end + end + + wg.wait + extra.get.should eq(32) + end + + # the test takes far too much time for the interpreter to complete + {% unless flag?(:interpreted) %} + it "stress add/done/wait" do + wg = WaitGroup.new + + 1000.times do + counter = Atomic(Int32).new(0) + + 2.times do + wg.add(1) + + spawn do + counter.add(1) + wg.done + end + end + + wg.wait + counter.get.should eq(2) + end + end + {% end %} +end diff --git a/src/crystal/pointer_linked_list.cr b/src/crystal/pointer_linked_list.cr index 0ce17b071bd0..03109979d662 100644 --- a/src/crystal/pointer_linked_list.cr +++ b/src/crystal/pointer_linked_list.cr @@ -80,4 +80,10 @@ struct Crystal::PointerLinkedList(T) node = _next end end + + # Iterates the list before clearing it. + def consume_each(&) : Nil + each { |node| yield node } + @head = Pointer(T).null + end end diff --git a/src/wait_group.cr b/src/wait_group.cr new file mode 100644 index 000000000000..d9ae4ae740ac --- /dev/null +++ b/src/wait_group.cr @@ -0,0 +1,120 @@ +require "fiber" +require "crystal/spin_lock" +require "crystal/pointer_linked_list" + +# Suspend execution until a collection of fibers are finished. +# +# The wait group is a declarative counter of how many concurrent fibers have +# been started. Each such fiber is expected to call `#done` to report that they +# are finished doing their work. Whenever the counter reaches zero the waiters +# will be resumed. +# +# This is a simpler and more efficient alternative to using a `Channel(Nil)` +# then looping a number of times until we received N messages to resume +# execution. +# +# Basic example: +# +# ``` +# require "wait_group" +# wg = WaitGroup.new(5) +# +# 5.times do +# spawn do +# do_something +# ensure +# wg.done # the fiber has finished +# end +# end +# +# # suspend the current fiber until the 5 fibers are done +# wg.wait +# ``` +class WaitGroup + private struct Waiting + include Crystal::PointerLinkedList::Node + + def initialize(@fiber : Fiber) + end + + def enqueue : Nil + @fiber.enqueue + end + end + + def initialize(n : Int32 = 0) + @waiting = Crystal::PointerLinkedList(Waiting).new + @lock = Crystal::SpinLock.new + @counter = Atomic(Int32).new(n) + end + + # Increments the counter by how many fibers we want to wait for. + # + # A negative value decrements the counter. When the counter reaches zero, + # all waiting fibers will be resumed. + # Raises `RuntimeError` if the counter reaches a negative value. + # + # Can be called at any time, allowing concurrent fibers to add more fibers to + # wait for, but they must always do so before calling `#done` that would + # decrement the counter, to make sure that the counter may never inadvertently + # reach zero before all fibers are done. + def add(n : Int32 = 1) : Nil + counter = @counter.get(:acquire) + + loop do + raise RuntimeError.new("Negative WaitGroup counter") if counter < 0 + + counter, success = @counter.compare_and_set(counter, counter + n, :acquire_release, :acquire) + break if success + end + + new_counter = counter + n + return if new_counter > 0 + + @lock.sync do + @waiting.consume_each do |node| + node.value.enqueue + end + end + + raise RuntimeError.new("Negative WaitGroup counter") if new_counter < 0 + end + + # Decrements the counter by one. Must be called by concurrent fibers once they + # have finished processing. When the counter reaches zero, all waiting fibers + # will be resumed. + def done : Nil + add(-1) + end + + # Suspends the current fiber until the counter reaches zero, at which point + # the fiber will be resumed. + # + # Can be called from different fibers. + def wait : Nil + return if done? + + waiting = Waiting.new(Fiber.current) + + @lock.sync do + # must check again to avoid a race condition where #done may have + # decremented the counter to zero between the above check and #wait + # acquiring the lock; we'd push the current fiber to the wait list that + # would never be resumed (oops) + return if done? + + @waiting.push(pointerof(waiting)) + end + + Crystal::Scheduler.reschedule + + return if done? + raise RuntimeError.new("Positive WaitGroup counter (early wake up?)") + end + + private def done? + counter = @counter.get(:acquire) + raise RuntimeError.new("Negative WaitGroup counter") if counter < 0 + counter == 0 + end +end From a318485117ca07865df319d872372b9003a4e285 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 13 Apr 2024 18:02:32 +0800 Subject: [PATCH 1058/1551] Move `Spec` context state into `Spec::CLI` (#14259) --- spec/std/spec/spec_helper.cr | 4 +++ src/spec/context.cr | 70 ++++++++++++++++++------------------ src/spec/example.cr | 10 +++--- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/spec/std/spec/spec_helper.cr b/spec/std/spec/spec_helper.cr index 8b7f6d860cd2..75156c9a36f6 100644 --- a/spec/std/spec/spec_helper.cr +++ b/spec/std/spec/spec_helper.cr @@ -1,6 +1,10 @@ require "../spec_helper" class FakeRootContext < Spec::RootContext + def initialize + super(Spec::CLI.new) + end + def description "root" end diff --git a/src/spec/context.cr b/src/spec/context.cr index 643d76c25b16..12501adf8360 100644 --- a/src/spec/context.cr +++ b/src/spec/context.cr @@ -6,6 +6,8 @@ module Spec # All the children, which can be `describe`/`context` or `it` getter children = [] of ExampleGroup | Example + protected abstract def cli : CLI + def randomize(randomizer) children.each do |child| child.randomize(randomizer) if child.is_a?(ExampleGroup) @@ -127,30 +129,23 @@ module Spec # :nodoc: class CLI - def root_context - RootContext.instance - end - - # :nodoc: - def current_context : Context - RootContext.current_context - end + getter root_context : RootContext { RootContext.new(self) } + property current_context : Context { root_context } end # :nodoc: # # The root context is the main interface that the spec DSL interacts with. class RootContext < Context - class_getter instance = RootContext.new - class_getter current_context : Context = @@instance - @results : Hash(Status, Array(Result)) + protected getter cli : CLI + def results_for(status : Status) @results[status] end - def initialize + def initialize(@cli : CLI) @results = Status.values.to_h { |status| {status, [] of Result} } end @@ -169,7 +164,7 @@ module Spec end def report_formatters(result) - Spec.cli.formatters.each(&.report(result)) + cli.formatters.each(&.report(result)) end def succeeded @@ -177,8 +172,8 @@ module Spec end def finish(elapsed_time, aborted = false) - Spec.cli.formatters.each(&.finish(elapsed_time, aborted)) - if Spec.cli.formatters.any?(&.should_print_summary?) + cli.formatters.each(&.finish(elapsed_time, aborted)) + if cli.formatters.any?(&.should_print_summary?) print_summary(elapsed_time, aborted) end end @@ -227,13 +222,13 @@ module Spec end end - if Spec.cli.slowest + if cli.slowest puts results = results_for(:success) + results_for(:fail) - top_n = results.sort_by { |res| -res.elapsed.not_nil!.to_f }[0..Spec.cli.slowest.not_nil!] + top_n = results.sort_by { |res| -res.elapsed.not_nil!.to_f }[0..cli.slowest.not_nil!] top_n_time = top_n.sum &.elapsed.not_nil!.total_seconds percent = (top_n_time * 100) / elapsed_time.total_seconds - puts "Top #{Spec.cli.slowest} slowest examples (#{top_n_time.humanize} seconds, #{percent.round(2)}% of total time):" + puts "Top #{cli.slowest} slowest examples (#{top_n_time.humanize} seconds, #{percent.round(2)}% of total time):" top_n.each do |res| puts " #{res.description}" res_elapsed = res.elapsed.not_nil!.total_seconds.humanize @@ -256,7 +251,7 @@ module Spec puts "Aborted!".colorize.red if aborted puts "Finished in #{Spec.to_human(elapsed_time)}" puts Spec.color("#{total} examples, #{failures.size} failures, #{errors.size} errors, #{pendings.size} pending", final_status) - puts Spec.color("Only running `focus: true`", :focus) if Spec.cli.focus? + puts Spec.color("Only running `focus: true`", :focus) if cli.focus? unless failures_and_errors.empty? puts @@ -272,23 +267,23 @@ module Spec end def print_order_message - if randomizer_seed = Spec.cli.randomizer_seed + if randomizer_seed = cli.randomizer_seed puts Spec.color("Randomized with seed: #{randomizer_seed}", :order) end end def describe(description, file, line, end_line, focus, tags, &block) - Spec.cli.focus = true if focus + cli.focus = true if focus - context = Spec::ExampleGroup.new(@@current_context, description, file, line, end_line, focus, tags) - @@current_context.children << context + context = Spec::ExampleGroup.new(cli.current_context, description, file, line, end_line, focus, tags) + cli.current_context.children << context - old_context = @@current_context - @@current_context = context + old_context = cli.current_context + cli.current_context = context begin block.call ensure - @@current_context = old_context + cli.current_context = old_context end end @@ -302,22 +297,22 @@ module Spec private def add_example(description, file, line, end_line, focus, tags, block) check_nesting_spec(file, line) do - Spec.cli.focus = true if focus - @@current_context.children << - Example.new(@@current_context, description, file, line, end_line, focus, tags, block) + cli.focus = true if focus + cli.current_context.children << + Example.new(cli.current_context, description, file, line, end_line, focus, tags, block) end end - @@spec_nesting = false + @spec_nesting = false def check_nesting_spec(file, line, &block) - raise NestingSpecError.new("Can't nest `it` or `pending`", file, line) if @@spec_nesting + raise NestingSpecError.new("Can't nest `it` or `pending`", file, line) if @spec_nesting - @@spec_nesting = true + @spec_nesting = true begin yield ensure - @@spec_nesting = false + @spec_nesting = false end end @@ -336,14 +331,19 @@ module Spec initialize_tags(tags) end + # :nodoc: + def cli : CLI + @parent.cli + end + # :nodoc: def run - Spec.cli.formatters.each(&.push(self)) + cli.formatters.each(&.push(self)) ran = run_around_all_hooks(ExampleGroup::Procsy.new(self) { internal_run }) ran || internal_run - Spec.cli.formatters.each(&.pop) + cli.formatters.each(&.pop) end protected def report(status : Status, description, file, line, elapsed = nil, ex = nil) diff --git a/src/spec/example.cr b/src/spec/example.cr index 72bffd6c0e07..5c96b6def1b2 100644 --- a/src/spec/example.cr +++ b/src/spec/example.cr @@ -18,10 +18,10 @@ module Spec # :nodoc: def run - Spec.cli.root_context.check_nesting_spec(file, line) do - Spec.cli.formatters.each(&.before_example(description)) + @parent.cli.root_context.check_nesting_spec(file, line) do + @parent.cli.formatters.each(&.before_example(description)) - if Spec.cli.dry_run? + if @parent.cli.dry_run? @parent.report(:success, description, file, line) return end @@ -51,12 +51,12 @@ module Spec @parent.report(:success, description, file, line, Time.monotonic - start) rescue ex : Spec::AssertionFailed @parent.report(:fail, description, file, line, Time.monotonic - start, ex) - Spec.cli.abort! if Spec.cli.fail_fast? + @parent.cli.abort! if @parent.cli.fail_fast? rescue ex : Spec::ExamplePending @parent.report(:pending, description, file, line, Time.monotonic - start) rescue ex @parent.report(:error, description, file, line, Time.monotonic - start, ex) - Spec.cli.abort! if Spec.cli.fail_fast? + @parent.cli.abort! if @parent.cli.fail_fast? ensure @parent.run_after_each_hooks end From 0af9bee4289860c9575b61a56f8ed2b0bcb776d8 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 15 Apr 2024 17:36:59 +0800 Subject: [PATCH 1059/1551] Remove calls to `LibC._setmode` (#14419) --- src/crystal/system/win32/file.cr | 3 --- src/crystal/system/win32/file_descriptor.cr | 1 - src/lib_c/x86_64-windows-msvc/c/io.cr | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 228704bbd48e..8203e7920a5f 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -50,9 +50,6 @@ module Crystal::System::File return {-1, Errno.value} end - # Only binary mode is supported - LibC._setmode fd, LibC::O_BINARY - {fd, Errno::NONE} end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index a93618e14c60..1f65f0ff9c52 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -222,7 +222,6 @@ module Crystal::System::FileDescriptor console_handle = false handle = windows_handle(fd) if handle != LibC::INVALID_HANDLE_VALUE - LibC._setmode fd, LibC::O_BINARY # TODO: use `out old_mode` after implementing interpreter out closured var old_mode = uninitialized LibC::DWORD if LibC.GetConsoleMode(handle, pointerof(old_mode)) != 0 diff --git a/src/lib_c/x86_64-windows-msvc/c/io.cr b/src/lib_c/x86_64-windows-msvc/c/io.cr index e5be8964765e..76c013004a14 100644 --- a/src/lib_c/x86_64-windows-msvc/c/io.cr +++ b/src/lib_c/x86_64-windows-msvc/c/io.cr @@ -8,7 +8,6 @@ lib LibC fun _get_osfhandle(fd : Int) : IntPtrT fun _dup2(fd1 : Int, fd2 : Int) : Int fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int - fun _setmode(fd : LibC::Int, mode : LibC::Int) : LibC::Int # unused fun _write(fd : Int, buffer : UInt8*, count : UInt) : Int @@ -21,4 +20,5 @@ lib LibC fun _chsize_s(fd : Int, size : Int64) : ErrnoT fun _pipe(pfds : Int*, psize : UInt, textmode : Int) : Int fun _commit(fd : Int) : Int + fun _setmode(fd : LibC::Int, mode : LibC::Int) : LibC::Int end From 2383ec1f45e4223648f8b4f513d02833f1079a8b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 15 Apr 2024 17:37:09 +0800 Subject: [PATCH 1060/1551] Implement `IO#tty?` in Win32 (#14421) --- src/crystal/system/win32/file_descriptor.cr | 2 +- src/crystal/system/win32/socket.cr | 2 +- src/lib_c/x86_64-windows-msvc/c/io.cr | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 1f65f0ff9c52..141ad2e7033d 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -148,7 +148,7 @@ module Crystal::System::FileDescriptor end private def system_tty? - LibC._isatty(fd) != 0 + LibC.GetFileType(windows_handle) == LibC::FILE_TYPE_CHAR end private def system_reopen(other : IO::FileDescriptor) diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 38fd890c6238..5621f34004da 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -442,7 +442,7 @@ module Crystal::System::Socket end private def system_tty? - false + LibC.GetFileType(LibC::HANDLE.new(fd)) == LibC::FILE_TYPE_CHAR end private def unbuffered_read(slice : Bytes) : Int32 diff --git a/src/lib_c/x86_64-windows-msvc/c/io.cr b/src/lib_c/x86_64-windows-msvc/c/io.cr index 76c013004a14..64092a1b1a8c 100644 --- a/src/lib_c/x86_64-windows-msvc/c/io.cr +++ b/src/lib_c/x86_64-windows-msvc/c/io.cr @@ -1,7 +1,6 @@ require "c/stdint" lib LibC - fun _isatty(fd : Int) : Int fun _close(fd : Int) : Int fun _waccess_s(path : WCHAR*, mode : Int) : ErrnoT fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT @@ -10,6 +9,7 @@ lib LibC fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int # unused + fun _isatty(fd : Int) : Int fun _write(fd : Int, buffer : UInt8*, count : UInt) : Int fun _read(fd : Int, buffer : UInt8*, count : UInt) : Int fun _lseeki64(fd : Int, offset : Int64, origin : Int) : Int64 From 944bee24a2ad34160b3f9a4eadaf673c53d4e91f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 15 Apr 2024 17:37:29 +0800 Subject: [PATCH 1061/1551] Allow `#fsync` and `#flock_*` on `IO::FileDescriptor` (#14432) --- src/crystal/system/unix/file.cr | 54 ------------------- src/crystal/system/unix/file_descriptor.cr | 54 +++++++++++++++++++ src/crystal/system/wasi/file.cr | 16 ------ src/crystal/system/wasi/file_descriptor.cr | 16 ++++++ src/crystal/system/win32/file.cr | 57 --------------------- src/crystal/system/win32/file_descriptor.cr | 57 +++++++++++++++++++++ 6 files changed, 127 insertions(+), 127 deletions(-) diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index ca04693d0b1a..a353cf29cd3c 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -218,58 +218,4 @@ module Crystal::System::File raise ::File::Error.from_errno("Error truncating file", file: path) end end - - private def system_flock_shared(blocking) - flock LibC::FlockOp::SH, blocking - end - - private def system_flock_exclusive(blocking) - flock LibC::FlockOp::EX, blocking - end - - private def system_flock_unlock - flock LibC::FlockOp::UN - end - - private def flock(op : LibC::FlockOp, retry : Bool) : Nil - op |= LibC::FlockOp::NB - - if retry - until flock(op) - sleep 0.1 - end - else - flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked", target: self) - end - end - - private def flock(op) : Bool - if 0 == LibC.flock(fd, op) - true - else - errno = Errno.value - if errno.in?(Errno::EAGAIN, Errno::EWOULDBLOCK) - false - else - raise IO::Error.from_os_error("Error applying or removing file lock", errno, target: self) - end - end - end - - private def system_fsync(flush_metadata = true) : Nil - ret = - if flush_metadata - LibC.fsync(fd) - else - {% if flag?(:dragonfly) %} - LibC.fsync(fd) - {% else %} - LibC.fdatasync(fd) - {% end %} - end - - if ret != 0 - raise IO::Error.from_errno("Error syncing file", target: self) - end - end end diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index c5810b5ae15b..6e82f4b7ae48 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -154,6 +154,60 @@ module Crystal::System::FileDescriptor end end + private def system_flock_shared(blocking) + flock LibC::FlockOp::SH, blocking + end + + private def system_flock_exclusive(blocking) + flock LibC::FlockOp::EX, blocking + end + + private def system_flock_unlock + flock LibC::FlockOp::UN + end + + private def flock(op : LibC::FlockOp, retry : Bool) : Nil + op |= LibC::FlockOp::NB + + if retry + until flock(op) + sleep 0.1 + end + else + flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked", target: self) + end + end + + private def flock(op) : Bool + if 0 == LibC.flock(fd, op) + true + else + errno = Errno.value + if errno.in?(Errno::EAGAIN, Errno::EWOULDBLOCK) + false + else + raise IO::Error.from_os_error("Error applying or removing file lock", errno, target: self) + end + end + end + + private def system_fsync(flush_metadata = true) : Nil + ret = + if flush_metadata + LibC.fsync(fd) + else + {% if flag?(:dragonfly) %} + LibC.fsync(fd) + {% else %} + LibC.fdatasync(fd) + {% end %} + end + + if ret != 0 + raise IO::Error.from_errno("Error syncing file", target: self) + end + end + def self.pipe(read_blocking, write_blocking) pipe_fds = uninitialized StaticArray(LibC::Int, 2) if LibC.pipe(pipe_fds) != 0 diff --git a/src/crystal/system/wasi/file.cr b/src/crystal/system/wasi/file.cr index ecf4daf48159..0d197550e3db 100644 --- a/src/crystal/system/wasi/file.cr +++ b/src/crystal/system/wasi/file.cr @@ -18,22 +18,6 @@ module Crystal::System::File raise NotImplementedError.new "Crystal::System::File.utime" end - private def system_flock_shared(blocking) - raise NotImplementedError.new "Crystal::System::File#system_flock_shared" - end - - private def system_flock_exclusive(blocking) - raise NotImplementedError.new "Crystal::System::File#system_flock_exclusive" - end - - private def system_flock_unlock - raise NotImplementedError.new "Crystal::System::File#system_flock_unlock" - end - - private def flock(op : LibC::FlockOp, blocking : Bool = true) - raise NotImplementedError.new "Crystal::System::File#flock" - end - def self.delete(path : String, *, raise_on_missing : Bool) : Bool raise NotImplementedError.new "Crystal::System::File.delete" end diff --git a/src/crystal/system/wasi/file_descriptor.cr b/src/crystal/system/wasi/file_descriptor.cr index 6eae9b8832d2..ffd15817a104 100644 --- a/src/crystal/system/wasi/file_descriptor.cr +++ b/src/crystal/system/wasi/file_descriptor.cr @@ -24,6 +24,22 @@ module Crystal::System::FileDescriptor raise NotImplementedError.new "Crystal::System::FileDescriptor#system_reopen" end + private def system_flock_shared(blocking) + raise NotImplementedError.new "Crystal::System::File#system_flock_shared" + end + + private def system_flock_exclusive(blocking) + raise NotImplementedError.new "Crystal::System::File#system_flock_exclusive" + end + + private def system_flock_unlock + raise NotImplementedError.new "Crystal::System::File#system_flock_unlock" + end + + private def flock(op : LibC::FlockOp, blocking : Bool = true) + raise NotImplementedError.new "Crystal::System::File#flock" + end + private def system_echo(enable : Bool, & : ->) raise NotImplementedError.new "Crystal::System::FileDescriptor#system_echo" end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 8203e7920a5f..d95202be5732 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -461,61 +461,4 @@ module Crystal::System::File LibC.SetFilePointerEx(handle, old_pos, nil, IO::Seek::Set) end end - - private def system_flock_shared(blocking : Bool) : Nil - flock(false, blocking) - end - - private def system_flock_exclusive(blocking : Bool) : Nil - flock(true, blocking) - end - - private def system_flock_unlock : Nil - unlock_file(windows_handle) - end - - private def flock(exclusive, retry) - flags = LibC::LOCKFILE_FAIL_IMMEDIATELY - flags |= LibC::LOCKFILE_EXCLUSIVE_LOCK if exclusive - - handle = windows_handle - if retry - until lock_file(handle, flags) - sleep 0.1 - end - else - lock_file(handle, flags) || raise IO::Error.from_winerror("Error applying file lock: file is already locked") - end - end - - private def lock_file(handle, flags) - # lpOverlapped must be provided despite the synchronous use of this method. - overlapped = LibC::OVERLAPPED.new - # lock the entire file with offset 0 in overlapped and number of bytes set to max value - if 0 != LibC.LockFileEx(handle, flags, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped)) - true - else - winerror = WinError.value - if winerror == WinError::ERROR_LOCK_VIOLATION - false - else - raise IO::Error.from_os_error("LockFileEx", winerror, target: self) - end - end - end - - private def unlock_file(handle) - # lpOverlapped must be provided despite the synchronous use of this method. - overlapped = LibC::OVERLAPPED.new - # unlock the entire file with offset 0 in overlapped and number of bytes set to max value - if 0 == LibC.UnlockFileEx(handle, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped)) - raise IO::Error.from_winerror("UnLockFileEx") - end - end - - private def system_fsync(flush_metadata = true) : Nil - if LibC.FlushFileBuffers(windows_handle) == 0 - raise IO::Error.from_winerror("Error syncing file", target: self) - end - end end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 141ad2e7033d..aeea22fa2a18 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -178,6 +178,63 @@ module Crystal::System::FileDescriptor end end + private def system_flock_shared(blocking : Bool) : Nil + flock(false, blocking) + end + + private def system_flock_exclusive(blocking : Bool) : Nil + flock(true, blocking) + end + + private def system_flock_unlock : Nil + unlock_file(windows_handle) + end + + private def flock(exclusive, retry) + flags = LibC::LOCKFILE_FAIL_IMMEDIATELY + flags |= LibC::LOCKFILE_EXCLUSIVE_LOCK if exclusive + + handle = windows_handle + if retry + until lock_file(handle, flags) + sleep 0.1 + end + else + lock_file(handle, flags) || raise IO::Error.from_winerror("Error applying file lock: file is already locked") + end + end + + private def lock_file(handle, flags) + # lpOverlapped must be provided despite the synchronous use of this method. + overlapped = LibC::OVERLAPPED.new + # lock the entire file with offset 0 in overlapped and number of bytes set to max value + if 0 != LibC.LockFileEx(handle, flags, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped)) + true + else + winerror = WinError.value + if winerror == WinError::ERROR_LOCK_VIOLATION + false + else + raise IO::Error.from_os_error("LockFileEx", winerror, target: self) + end + end + end + + private def unlock_file(handle) + # lpOverlapped must be provided despite the synchronous use of this method. + overlapped = LibC::OVERLAPPED.new + # unlock the entire file with offset 0 in overlapped and number of bytes set to max value + if 0 == LibC.UnlockFileEx(handle, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped)) + raise IO::Error.from_winerror("UnLockFileEx") + end + end + + private def system_fsync(flush_metadata = true) : Nil + if LibC.FlushFileBuffers(windows_handle) == 0 + raise IO::Error.from_winerror("Error syncing file", target: self) + end + end + private PIPE_BUFFER_SIZE = 8192 def self.pipe(read_blocking, write_blocking) From 743f0c419a8fe69376aead2ce1d161e758ada0af Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 16 Apr 2024 04:37:45 +0800 Subject: [PATCH 1062/1551] Distribute `shards.pdb` on Windows (#14415) --- .github/workflows/win_build_portable.yml | 1 + etc/win-ci/crystal.iss | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 5e39770ffab6..ceb2b550ae87 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -138,6 +138,7 @@ jobs: make -f Makefile.win install prefix=crystal mkdir crystal/lib cp shards/bin/shards.exe crystal/ + cp shards/bin/shards.pdb crystal/ cp libs/* crystal/lib/ cp dlls/* crystal/ cp README.md crystal/ diff --git a/etc/win-ci/crystal.iss b/etc/win-ci/crystal.iss index edb92d89c72a..90825498469d 100644 --- a/etc/win-ci/crystal.iss +++ b/etc/win-ci/crystal.iss @@ -80,6 +80,7 @@ Source: "portable\LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion; Componen Source: "portable\shards.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: shards Source: "portable\crystal.pdb"; DestDir: "{app}"; Flags: ignoreversion; Components: pdb +Source: "portable\shards.pdb"; DestDir: "{app}"; Flags: ignoreversion; Components: pdb and shards Source: "portable\examples\*"; DestDir: "{app}\examples"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: samples From 5d91d6b8a15d5edce761013aee3718f1803cea90 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 16 Apr 2024 04:44:27 +0800 Subject: [PATCH 1063/1551] Use wrapping arithmetic for `Int::Primitive#unsafe_chr` (#14443) --- spec/primitives/int_spec.cr | 7 +++++++ src/primitives.cr | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/spec/primitives/int_spec.cr b/spec/primitives/int_spec.cr index e2a62a5357dd..4803a7f47b03 100644 --- a/spec/primitives/int_spec.cr +++ b/spec/primitives/int_spec.cr @@ -131,6 +131,13 @@ describe "Primitives: Int" do end end + describe "#unsafe_chr" do + it "doesn't raise on overflow" do + (0x41_i64 - 0x100000000_i64).unsafe_chr.should eq('A') + 0xFFFF_FFFF_0010_FFFF_u64.unsafe_chr.should eq('\u{10FFFF}') + end + end + describe "#to_f" do it "raises on overflow for UInt128#to_f32" do expect_raises(OverflowError) { UInt128::MAX.to_f32 } diff --git a/src/primitives.cr b/src/primitives.cr index f260f2cd718f..a3594b4543d9 100644 --- a/src/primitives.cr +++ b/src/primitives.cr @@ -406,7 +406,8 @@ end struct {{int.id}} # Returns a `Char` that has the unicode codepoint of `self`, # without checking if this integer is in the range valid for - # chars (`0..0xd7ff` and `0xe000..0x10ffff`). + # chars (`0..0xd7ff` and `0xe000..0x10ffff`). In case of overflow + # a wrapping is performed. # # You should never use this method unless `chr` turns out to # be a bottleneck. @@ -414,7 +415,7 @@ end # ``` # 97.unsafe_chr # => 'a' # ``` - @[::Primitive(:convert)] + @[::Primitive(:unchecked_convert)] def unsafe_chr : Char end From a4ca7cc396e7c57da5417d9cb38b6b17aa0d9ce6 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 15 Apr 2024 22:45:18 +0200 Subject: [PATCH 1064/1551] Add `Program#size_t` and `Target#size_bit_width` (#14442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/compiler/crystal/codegen/call.cr | 9 +-- src/compiler/crystal/codegen/codegen.cr | 60 +++++++++---------- .../crystal/codegen/llvm_builder_helper.cr | 19 ++++++ src/compiler/crystal/codegen/llvm_typer.cr | 6 +- src/compiler/crystal/codegen/target.cr | 11 ++++ src/compiler/crystal/codegen/unions.cr | 3 +- src/compiler/crystal/semantic/flags.cr | 4 ++ 7 files changed, 66 insertions(+), 46 deletions(-) diff --git a/src/compiler/crystal/codegen/call.cr b/src/compiler/crystal/codegen/call.cr index 1dca1f4e4b7b..1b678232c054 100644 --- a/src/compiler/crystal/codegen/call.cr +++ b/src/compiler/crystal/codegen/call.cr @@ -239,9 +239,8 @@ class Crystal::CodeGenVisitor final_value_casted = cast_to_void_pointer final_value gep_call_arg = cast_to_void_pointer gep(llvm_type(call_arg_type), call_arg, 0, 0) size = @abi.size(abi_arg_type.type) - size = @program.bits64? ? int64(size) : int32(size) align = @abi.align(abi_arg_type.type) - memcpy(final_value_casted, gep_call_arg, size, align, int1(0)) + memcpy(final_value_casted, gep_call_arg, size_t(size), align, int1(0)) call_arg = load cast, final_value else # Keep same call arg @@ -256,9 +255,8 @@ class Crystal::CodeGenVisitor final_value_casted = cast_to_void_pointer final_value call_arg_casted = cast_to_void_pointer call_arg size = @abi.size(abi_arg_type.type) - size = @program.bits64? ? int64(size) : int32(size) align = @abi.align(abi_arg_type.type) - memcpy(final_value_casted, call_arg_casted, size, align, int1(0)) + memcpy(final_value_casted, call_arg_casted, size_t(size), align, int1(0)) final_value end @@ -532,9 +530,8 @@ class Crystal::CodeGenVisitor final_value = alloca abi_return.type final_value_casted = cast_to_void_pointer final_value size = @abi.size(abi_return.type) - size = @program.@program.bits64? ? int64(size) : int32(size) align = @abi.align(abi_return.type) - memcpy(final_value_casted, cast2, size, align, int1(0)) + memcpy(final_value_casted, cast2, size_t(size), align, int1(0)) @last = final_value end in .indirect? diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index c80bd579d982..a46d255901e5 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -2079,7 +2079,7 @@ module Crystal end def pre_initialize_aggregate(type, struct_type, ptr) - memset ptr, int8(0), struct_type.size + memset ptr, int8(0), size_t(struct_type.size) run_instance_vars_initializers(type, type, ptr) unless type.struct? @@ -2148,7 +2148,7 @@ module Crystal if malloc_fun = yield pointer = call malloc_fun, size else - pointer = call_c_malloc size + pointer = call c_malloc_fun, size_t(size) end pointer_cast pointer, type.pointer @@ -2168,10 +2168,10 @@ module Crystal if malloc_fun = yield pointer = call malloc_fun, size else - pointer = call_c_malloc size + pointer = call c_malloc_fun, size_t(size) end - memset pointer, int8(0), size + memset pointer, int8(0), size_t(size) pointer_cast pointer, type.pointer end @@ -2211,43 +2211,39 @@ module Crystal end end - # We only use C's malloc in tests that don't require the prelude, - # so they don't require the GC. Outside tests these are not used, - # and __crystal_* functions are invoked instead. - - def call_c_malloc(size) - size = trunc(size, llvm_context.int32) unless @program.bits64? - call c_malloc_fun, size - end + # Fallbacks to libc malloc and realloc when the expected __crystal_* + # functions aren't defined (e.g. empty prelude). We only use them in tests + # that don't require the prelude, so they don't require the GC. + # + # Outside tests the __crystal_* functions should have been defined and will + # be invoked instead. def c_malloc_fun malloc_fun = @c_malloc_fun = fetch_typed_fun(@main_mod, "malloc") do - size = @program.bits64? ? @main_llvm_context.int64 : @main_llvm_context.int32 - LLVM::Type.function([size], @main_llvm_context.void_pointer) + LLVM::Type.function([main_llvm_context_size_t], @main_llvm_context.void_pointer) end check_main_fun "malloc", malloc_fun end - def call_c_realloc(buffer, size) - size = trunc(size, llvm_context.int32) unless @program.bits64? - call c_realloc_fun, [buffer, size] - end - def c_realloc_fun realloc_fun = @c_realloc_fun = fetch_typed_fun(@main_mod, "realloc") do - size = @program.bits64? ? @main_llvm_context.int64 : @main_llvm_context.int32 - LLVM::Type.function([@main_llvm_context.void_pointer, size], @main_llvm_context.void_pointer) + LLVM::Type.function([@main_llvm_context.void_pointer, main_llvm_context_size_t], @main_llvm_context.void_pointer) end check_main_fun "realloc", realloc_fun end - def memset(pointer, value, size) - len_arg = @program.bits64? ? size : trunc(size, llvm_context.int32) + # can't use `#size_t` because it targets @llvm_context instead of + # @main_llvm_context which confuses LLVM that considers them as distinct + # types despite the dumped LLVM IR looking identical + private def main_llvm_context_size_t + @main_llvm_context.int(@program.size_bit_width) + end + def memset(pointer, value, size) pointer = cast_to_void_pointer pointer - res = call c_memset_fun, [pointer, value, len_arg, int1(0)] + res = call c_memset_fun, [pointer, value, size, int1(0)] LibLLVM.set_instr_param_alignment(res, 1, 4) res @@ -2266,7 +2262,7 @@ module Crystal if realloc_fun = crystal_realloc_fun call realloc_fun, [buffer, size] else - call_c_realloc buffer, size + call c_realloc_fun, [buffer, size_t(size)] end end @@ -2278,28 +2274,26 @@ module Crystal private def c_memset_fun name = {% if LibLLVM::IS_LT_150 %} - @program.bits64? ? "llvm.memset.p0i8.i64" : "llvm.memset.p0i8.i32" + "llvm.memset.p0i8.i#{@program.size_bit_width}" {% else %} - @program.bits64? ? "llvm.memset.p0.i64" : "llvm.memset.p0.i32" + "llvm.memset.p0.i#{@program.size_bit_width}" {% end %} fetch_typed_fun(@llvm_mod, name) do - len_type = @program.bits64? ? @llvm_context.int64 : @llvm_context.int32 - arg_types = [@llvm_context.void_pointer, @llvm_context.int8, len_type, @llvm_context.int1] + arg_types = [@llvm_context.void_pointer, @llvm_context.int8, size_t, @llvm_context.int1] LLVM::Type.function(arg_types, @llvm_context.void) end end private def c_memcpy_fun name = {% if LibLLVM::IS_LT_150 %} - @program.bits64? ? "llvm.memcpy.p0i8.p0i8.i64" : "llvm.memcpy.p0i8.p0i8.i32" + "llvm.memcpy.p0i8.p0i8.i#{@program.size_bit_width}" {% else %} - @program.bits64? ? "llvm.memcpy.p0.p0.i64" : "llvm.memcpy.p0.p0.i32" + "llvm.memcpy.p0.p0.i#{@program.size_bit_width}" {% end %} fetch_typed_fun(@llvm_mod, name) do - len_type = @program.bits64? ? @llvm_context.int64 : @llvm_context.int32 - arg_types = [@llvm_context.void_pointer, @llvm_context.void_pointer, len_type, @llvm_context.int1] + arg_types = [@llvm_context.void_pointer, @llvm_context.void_pointer, size_t, @llvm_context.int1] LLVM::Type.function(arg_types, @llvm_context.void) end end diff --git a/src/compiler/crystal/codegen/llvm_builder_helper.cr b/src/compiler/crystal/codegen/llvm_builder_helper.cr index dfc107526f6e..2ebdda26f31b 100644 --- a/src/compiler/crystal/codegen/llvm_builder_helper.cr +++ b/src/compiler/crystal/codegen/llvm_builder_helper.cr @@ -30,6 +30,25 @@ module Crystal int32(n) end + def size_t + llvm_context.int(@program.size_bit_width) + end + + def size_t(n) + size_t.const_int(n) + end + + def size_t(value : LLVM::Value) + case value.type.int_width <=> @program.size_bit_width + when .zero? + value + when .positive? + builder.trunc(value, size_t) + else + builder.zext(value, size_t) + end + end + def int(n, type) llvm_type(type).const_int(n) end diff --git a/src/compiler/crystal/codegen/llvm_typer.cr b/src/compiler/crystal/codegen/llvm_typer.cr index d72a25ecd3cb..9d6de8ccabeb 100644 --- a/src/compiler/crystal/codegen/llvm_typer.cr +++ b/src/compiler/crystal/codegen/llvm_typer.cr @@ -586,11 +586,7 @@ module Crystal end def size_t - if @program.bits64? - @llvm_context.int64 - else - @llvm_context.int32 - end + @llvm_context.int(@program.size_bit_width) end @pointer_size : UInt64? diff --git a/src/compiler/crystal/codegen/target.cr b/src/compiler/crystal/codegen/target.cr index 61af0d532866..59c6b267c118 100644 --- a/src/compiler/crystal/codegen/target.cr +++ b/src/compiler/crystal/codegen/target.cr @@ -58,6 +58,17 @@ class Crystal::Codegen::Target end end + def size_bit_width + case @architecture + when "aarch64", "x86_64" + 64 + when "arm", "i386", "wasm32" + 32 + else + raise "BUG: unknown Target#size_bit_width for #{@architecture} target architecture" + end + end + def os_name case self when .macos? diff --git a/src/compiler/crystal/codegen/unions.cr b/src/compiler/crystal/codegen/unions.cr index bcef273a8010..b2b63a17c5ab 100644 --- a/src/compiler/crystal/codegen/unions.cr +++ b/src/compiler/crystal/codegen/unions.cr @@ -122,11 +122,10 @@ module Crystal @llvm_typer.size_of(from_value_type), @llvm_typer.size_of(to_value_type), }.min - size = @program.bits64? ? int64(size) : int32(size) memcpy( cast_to_void_pointer(union_value(to_llvm_type, union_pointer)), cast_to_void_pointer(union_value(from_llvm_type, value)), - size, + size_t(size), align: @llvm_typer.align_of(to_value_type), src_align: @llvm_typer.align_of(from_value_type), volatile: int1(0), diff --git a/src/compiler/crystal/semantic/flags.cr b/src/compiler/crystal/semantic/flags.cr index cf15e7cecc10..0b53b779fb51 100644 --- a/src/compiler/crystal/semantic/flags.cr +++ b/src/compiler/crystal/semantic/flags.cr @@ -25,6 +25,10 @@ class Crystal::Program codegen_target.pointer_bit_width == 64 end + def size_bit_width + codegen_target.size_bit_width + end + private def flags_for_target(target) flags = Set(String).new From fc3352c5bc0738ec1face812864452fbbd5a350b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 16 Apr 2024 15:38:19 +0800 Subject: [PATCH 1065/1551] Add `Regex::MatchOptions` overload for `String#index!` (#14462) --- spec/std/string_spec.cr | 8 ++++++++ src/string.cr | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 8217f55b22a6..00310bfcbc47 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -995,6 +995,7 @@ describe "String" do describe "by regex" do it { "string 12345".index(/\d+/).should eq(7) } it { "12345".index(/\d/).should eq(0) } + it { "Hello\xFF".index(/l/, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(2) } it { "Hello, world!".index(/\d/).should be_nil } it { "abcdef".index(/[def]/).should eq(3) } it { "日本語日本語".index(/本語/).should eq(1) } @@ -1002,6 +1003,7 @@ describe "String" do describe "with offset" do it { "abcDef".index(/[A-Z]/).should eq(3) } it { "foobarbaz".index(/ba/, -5).should eq(6) } + it { "Hello\xFF".index(/l/, 3, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(3) } it { "Foo".index(/[A-Z]/, 1).should be_nil } it { "foo".index(/o/, 2).should eq(2) } it { "foo".index(//, 3).should eq(3) } @@ -1065,6 +1067,7 @@ describe "String" do describe "by regex" do it { "string 12345".index!(/\d+/).should eq(7) } it { "12345".index!(/\d/).should eq(0) } + it { "Hello\xFF".index!(/l/, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(2) } it do expect_raises(Enumerable::NotFoundError) do "Hello, world!".index!(/\d/) @@ -1074,6 +1077,7 @@ describe "String" do describe "with offset" do it { "abcDef".index!(/[A-Z]/).should eq(3) } it { "foobarbaz".index!(/ba/, -5).should eq(6) } + it { "Hello\xFF".index!(/l/, 3, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(3) } it do expect_raises(Enumerable::NotFoundError) do "Foo".index!(/[A-Z]/, 1) @@ -1142,6 +1146,7 @@ describe "String" do describe "by regex" do it { "bbbb".rindex(/b/).should eq(3) } + it { "\xFFbbb".rindex(/b/, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(3) } it { "a43b53".rindex(/\d+/).should eq(4) } it { "bbbb".rindex(/\d/).should be_nil } @@ -1153,6 +1158,7 @@ describe "String" do describe "with offset" do it { "bbbb".rindex(/b/, 2).should eq(2) } + it { "\xFFbbb".rindex(/b/, 2, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(2) } it { "abbbb".rindex(/b/, 0).should be_nil } it { "abbbb".rindex(/a/, 0).should eq(0) } it { "bbbb".rindex(/b/, -2).should eq(2) } @@ -1209,6 +1215,7 @@ describe "String" do describe "by regex" do it { "bbbb".rindex!(/b/).should eq(3) } + it { "\xFFbbb".rindex!(/b/, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(3) } it { "a43b53".rindex!(/\d+/).should eq(4) } it do expect_raises(Enumerable::NotFoundError) do @@ -1218,6 +1225,7 @@ describe "String" do describe "with offset" do it { "bbbb".rindex!(/b/, 2).should eq(2) } + it { "\xFFbbb".rindex!(/b/, 2, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(2) } it do expect_raises(Enumerable::NotFoundError) do "abbbb".rindex!(/b/, 0) diff --git a/src/string.cr b/src/string.cr index 0b7796d8eb34..a87bf541f0f6 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3433,6 +3433,11 @@ class String index(search, offset) || raise Enumerable::NotFoundError.new end + # :ditto: + def index!(search : Regex, offset = 0, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32 + index(search, offset, options: options) || raise Enumerable::NotFoundError.new + end + # Returns the index of the _last_ appearance of *search* in the string, # If *offset* is present, it defines the position to _end_ the search # (characters beyond this point are ignored). From 5297fd0d5338b0bb5060b17ac249116ed1bd705b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 16 Apr 2024 15:38:30 +0800 Subject: [PATCH 1066/1551] Allow assignment to `_` inside macro expressions (#14452) --- spec/compiler/semantic/macro_spec.cr | 25 ++++++++++++++++++++++ src/compiler/crystal/macros/interpreter.cr | 2 ++ 2 files changed, 27 insertions(+) diff --git a/spec/compiler/semantic/macro_spec.cr b/spec/compiler/semantic/macro_spec.cr index 6a6a2f01ce17..c66ee3d902f5 100644 --- a/spec/compiler/semantic/macro_spec.cr +++ b/spec/compiler/semantic/macro_spec.cr @@ -1688,6 +1688,12 @@ describe "Semantic: macro" do method.location.not_nil!.expanded_location.not_nil!.line_number.should eq(9) end + it "assigns to underscore" do + assert_no_errors <<-CRYSTAL + {% _ = 1 %} + CRYSTAL + end + it "unpacks block parameters inside macros (#13742)" do assert_no_errors <<-CRYSTAL macro foo @@ -1707,6 +1713,16 @@ describe "Semantic: macro" do CRYSTAL end + it "unpacks to underscore within block parameters inside macros" do + assert_type(<<-CRYSTAL) { bool } + {% begin %} + {% x = nil %} + {% [{1, true, 'a', ""}].each { |(_, y, _, _)| x = y } %} + {{ x }} + {% end %} + CRYSTAL + end + it "executes OpAssign (#9356)" do assert_type(<<-CRYSTAL) { int32 } {% begin %} @@ -1740,6 +1756,15 @@ describe "Semantic: macro" do CRYSTAL end + it "assigns to underscore in MultiAssign" do + assert_type(<<-CRYSTAL) { tuple_of([char, bool]) } + {% begin %} + {% _, x, *_, y = [1, 'a', "", nil, true] %} + { {{x}}, {{y}} } + {% end %} + CRYSTAL + end + describe "@caller" do it "returns an array of each call" do assert_type(<<-CRYSTAL) { int32 } diff --git a/src/compiler/crystal/macros/interpreter.cr b/src/compiler/crystal/macros/interpreter.cr index 1e18e92b7545..8db46bd118cf 100644 --- a/src/compiler/crystal/macros/interpreter.cr +++ b/src/compiler/crystal/macros/interpreter.cr @@ -297,6 +297,8 @@ module Crystal when Var node.value.accept self @vars[target.name] = @last + when Underscore + node.value.accept self else node.raise "can only assign to variables, not #{target.class_desc}" end From 5cc46ebdf6474ed6f121cd3fa95642dc1ec7ee4f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 16 Apr 2024 15:38:45 +0800 Subject: [PATCH 1067/1551] Run `primitives_spec` with the interpreter on CI (#14438) --- .github/workflows/interpreter.yml | 21 +++++++++++++++++++++ spec/primitives/float_spec.cr | 1 + spec/primitives/reference_spec.cr | 6 ++++-- spec/primitives/slice_spec.cr | 4 +++- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 04f60ed40318..6fc2326c3169 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -62,3 +62,24 @@ jobs: - name: Run std_spec with interpreter run: SPEC_SPLIT="${{ matrix.part }}%4" bin/crystal i spec/std_spec.cr -- --junit_output .junit/interpreter-std_spec.${{ matrix.part }}.xml + + test-interpreter-primitives_spec: + needs: build-interpreter + runs-on: ubuntu-22.04 + container: + image: crystallang/crystal:1.11.2-build + name: "Test primitives_spec with interpreter" + steps: + - uses: actions/checkout@v4 + + - name: Download compiler artifact + uses: actions/download-artifact@v4 + with: + name: crystal-interpreter + path: .build/ + + - name: Mark downloaded compiler as executable + run: chmod +x .build/crystal + + - name: Run primitives_spec with interpreter + run: bin/crystal i spec/primitives_spec.cr -- --junit_output .junit/interpreter-primitives_spec.xml diff --git a/spec/primitives/float_spec.cr b/spec/primitives/float_spec.cr index f236954907bf..316d439756c1 100644 --- a/spec/primitives/float_spec.cr +++ b/spec/primitives/float_spec.cr @@ -1,5 +1,6 @@ require "spec" require "../support/number" +require "../support/interpreted" describe "Primitives: Float" do {% for op in %w(== != < <= > >=) %} diff --git a/spec/primitives/reference_spec.cr b/spec/primitives/reference_spec.cr index c726d4845607..13bb024f1ba9 100644 --- a/spec/primitives/reference_spec.cr +++ b/spec/primitives/reference_spec.cr @@ -1,4 +1,5 @@ require "spec" +require "../support/interpreted" private abstract class Base end @@ -36,7 +37,8 @@ describe "Primitives: reference" do end end - describe ".pre_initialize" do + # TODO: implement in the interpreter + pending_interpreted describe: ".pre_initialize" do it "doesn't fail on complex ivar initializer if value is discarded (#14325)" do bar_buffer = GC.malloc(instance_sizeof(Outer)) Outer.pre_initialize(bar_buffer) @@ -87,7 +89,7 @@ describe "Primitives: reference" do end end - describe ".unsafe_construct" do + pending_interpreted describe: ".unsafe_construct" do it "constructs an object in-place" do foo_buffer = GC.malloc(instance_sizeof(Foo)) foo = Foo.unsafe_construct(foo_buffer, 123_i64) diff --git a/spec/primitives/slice_spec.cr b/spec/primitives/slice_spec.cr index 8e440d2ff905..546ae0de5ce1 100644 --- a/spec/primitives/slice_spec.cr +++ b/spec/primitives/slice_spec.cr @@ -1,10 +1,12 @@ require "spec" require "../support/number" +require "../support/interpreted" describe "Primitives: Slice" do describe ".literal" do + # TODO: implement in the interpreter {% for num in BUILTIN_NUMBER_TYPES %} - it {{ "creates a read-only Slice(#{num})" }} do + pending_interpreted {{ "creates a read-only Slice(#{num})" }} do slice = Slice({{ num }}).literal(0, 1, 4, 9, 16, 25) slice.should be_a(Slice({{ num }})) slice.to_a.should eq([0, 1, 4, 9, 16, 25] of {{ num }}) From 1dca7a321f6336b6170a4ace0560289734103026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 16 Apr 2024 12:54:35 +0200 Subject: [PATCH 1068/1551] Highlight regression bugfixes in changelog (#14474) --- scripts/github-changelog.cr | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index 765a0c281939..26fce69fd145 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -150,6 +150,9 @@ record PullRequest, if labels.includes?("breaking-change") io << "**[breaking]** " end + if regression? + io << "**[regression]** " + end if experimental? io << "**[experimental]** " end @@ -235,6 +238,10 @@ record PullRequest, labels.includes?("kind:breaking") end + def regression? + labels.includes?("kind:regression") + end + def experimental? labels.includes?("experimental") end From 246cd7ca44fa04a9180b4a962530deacc820fd41 Mon Sep 17 00:00:00 2001 From: Todd Sundsted Date: Tue, 16 Apr 2024 06:55:42 -0400 Subject: [PATCH 1069/1551] Support `UInt32` and `UInt64` in Log Context (#14459) Co-authored-by: Todd Sundsted --- spec/std/log/log_spec.cr | 6 ++++++ src/log/metadata.cr | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/std/log/log_spec.cr b/spec/std/log/log_spec.cr index 0a77ff9d9877..6482509f1704 100644 --- a/spec/std/log/log_spec.cr +++ b/spec/std/log/log_spec.cr @@ -129,6 +129,12 @@ describe Log do Log.context.metadata.should eq(Log::Metadata.build({a: 1, b: 2})) end + it "context supports unsigned values" do + Log.context.set a: 1_u32, b: 2_u64 + + Log.context.metadata.should eq(Log::Metadata.build({a: 1_u32, b: 2_u64})) + end + describe "emitter dsl" do it "can be used with message" do backend = Log::MemoryBackend.new diff --git a/src/log/metadata.cr b/src/log/metadata.cr index b1ea8ae38fa9..2fdcb3cdf7cf 100644 --- a/src/log/metadata.cr +++ b/src/log/metadata.cr @@ -199,7 +199,7 @@ class Log::Metadata end struct Value - Crystal.datum types: {nil: Nil, bool: Bool, i: Int32, i64: Int64, f: Float32, f64: Float64, s: String, time: Time}, hash_key_type: String, immutable: false, target_type: Log::Metadata::Value + Crystal.datum types: {nil: Nil, bool: Bool, i: Int32, i64: Int64, u: UInt32, u64: UInt64, f: Float32, f64: Float64, s: String, time: Time}, hash_key_type: String, immutable: false, target_type: Log::Metadata::Value # Creates `Log::Metadata` from the given *values*. # All keys are converted to `String` From 3ba6accf4375d9ef7e834702589861407736d7d4 Mon Sep 17 00:00:00 2001 From: Frityet Date: Tue, 16 Apr 2024 03:55:57 -0700 Subject: [PATCH 1070/1551] Fix libpcre2 version detection not working for -RC{N} versions (#14478) --- src/regex/pcre2.cr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index 19416723af24..da811225842f 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -17,7 +17,8 @@ module Regex::PCRE2 version = self.version dot = version.index('.') || raise RuntimeError.new("Invalid libpcre2 version") space = version.index(' ', dot) || raise RuntimeError.new("Invalid libpcre2 version") - {version.byte_slice(0, dot).to_i, version.byte_slice(dot + 1, space - dot - 1).to_i} + # PCRE2 versions can contain -RC{N} which would make `.to_i` fail unless strict is set to false + {version.byte_slice(0, dot).to_i, version.byte_slice(dot + 1, space - dot - 1).to_i(strict: false)} end # :nodoc: From 959f48b4acbe0a50c27486f62e220a2d620e03a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 16 Apr 2024 18:51:20 +0200 Subject: [PATCH 1071/1551] Replace `type` declarations for void pointers with `alias` in `libxml2` (#14494) Co-authored-by: Quinton Miller --- src/xml/libxml2.cr | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/xml/libxml2.cr b/src/xml/libxml2.cr index feea8a1ba6d7..e1c2b8d12372 100644 --- a/src/xml/libxml2.cr +++ b/src/xml/libxml2.cr @@ -22,8 +22,8 @@ lib LibXML $xmlTreeIndentString : UInt8* {% end %} - type Dtd = Void* - type Dict = Void* + alias Dtd = Void* + alias Dict = Void* struct NS next : NS* @@ -89,9 +89,9 @@ lib LibXML node_tab : Node** end - type InputBuffer = Void* - type XMLTextReader = Void* - type XMLTextReaderLocator = Void* + alias InputBuffer = Void* + alias XMLTextReader = Void* + alias XMLTextReaderLocator = Void* enum ParserSeverity VALIDITY_WARNING = 1 @@ -159,7 +159,7 @@ lib LibXML alias OutputWriteCallback = (Void*, UInt8*, Int) -> Int alias OutputCloseCallback = (Void*) -> Int - type SaveCtxPtr = Void* + alias SaveCtxPtr = Void* fun xmlSaveToIO(iowrite : OutputWriteCallback, ioclose : OutputCloseCallback, ioctx : Void*, encoding : UInt8*, options : XML::SaveOptions) : SaveCtxPtr fun xmlSaveTree(ctx : SaveCtxPtr, node : Node*) : LibC::Long @@ -176,7 +176,7 @@ lib LibXML error : Int end - type TextWriter = Void* + alias TextWriter = Void* fun xmlNewTextWriter(out : OutputBuffer*) : TextWriter fun xmlTextWriterStartDocument(TextWriter, version : UInt8*, encoding : UInt8*, standalone : UInt8*) : Int From 0361f1f8afdc3db9751f5d8c4e4851680f3cf79a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 17 Apr 2024 00:51:32 +0800 Subject: [PATCH 1072/1551] Allow `Atomic`s of pointer types (#14401) --- spec/std/atomic_spec.cr | 42 ++++++++++++++++++++++++++++++++++++ src/atomic.cr | 47 ++++++++++++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/spec/std/atomic_spec.cr b/spec/std/atomic_spec.cr index 4006c33b1e1c..e18b3b234a15 100644 --- a/spec/std/atomic_spec.cr +++ b/spec/std/atomic_spec.cr @@ -46,6 +46,16 @@ describe Atomic do atomic.get.should eq(AtomicEnumFlags::Three) end + it "with pointer" do + atomic = Atomic.new(Pointer(Void).null) + + atomic.compare_and_set(Pointer(Void).new(1), Pointer(Void).new(3)).should eq({Pointer(Void).null, false}) + atomic.get.should eq(Pointer(Void).null) + + atomic.compare_and_set(Pointer(Void).null, Pointer(Void).new(3)).should eq({Pointer(Void).null, true}) + atomic.get.should eq(Pointer(Void).new(3)) + end + it "with nilable reference" do atomic = Atomic(String?).new(nil) string = "hello" @@ -183,6 +193,16 @@ describe Atomic do atomic.get.should eq(AtomicEnum::Three) end + it "#max with pointer type" do + atomic = Atomic.new(Pointer(Void).new(2)) + atomic.max(Pointer(Void).new(1)).should eq(Pointer(Void).new(2)) + atomic.get.should eq(Pointer(Void).new(2)) + atomic.max(Pointer(Void).new(3)).should eq(Pointer(Void).new(2)) + atomic.get.should eq(Pointer(Void).new(3)) + atomic.max(Pointer(Void).new(UInt64::MAX)).should eq(Pointer(Void).new(3)) + atomic.get.should eq(Pointer(Void).new(UInt64::MAX)) + end + it "#min with signed" do atomic = Atomic.new(5) atomic.min(10).should eq(5) @@ -209,6 +229,16 @@ describe Atomic do atomic.get.should eq(AtomicEnum::Minus) end + it "#min with pointer type" do + atomic = Atomic.new(Pointer(Void).new(2)) + atomic.min(Pointer(Void).new(3)).should eq(Pointer(Void).new(2)) + atomic.get.should eq(Pointer(Void).new(2)) + atomic.min(Pointer(Void).new(1)).should eq(Pointer(Void).new(2)) + atomic.get.should eq(Pointer(Void).new(1)) + atomic.min(Pointer(Void).new(UInt64::MAX)).should eq(Pointer(Void).new(1)) + atomic.get.should eq(Pointer(Void).new(1)) + end + describe "#set" do it "with integer" do atomic = Atomic.new(1) @@ -216,6 +246,12 @@ describe Atomic do atomic.get.should eq(2) end + it "with pointer type" do + atomic = Atomic.new(Pointer(Void).new(1)) + atomic.set(Pointer(Void).new(3)).should eq(Pointer(Void).new(3)) + atomic.get.should eq(Pointer(Void).new(3)) + end + it "with nil (#4062)" do atomic = Atomic(String?).new(nil) @@ -246,6 +282,12 @@ describe Atomic do atomic.get.should eq(2) end + it "with pointer type" do + atomic = Atomic.new(Pointer(Void).new(1)) + atomic.swap(Pointer(Void).new(3)).should eq(Pointer(Void).new(1)) + atomic.get.should eq(Pointer(Void).new(3)) + end + it "with reference type" do atomic = Atomic.new("hello") atomic.swap("world").should eq("hello") diff --git a/src/atomic.cr b/src/atomic.cr index 8569b253343a..de8c308df971 100644 --- a/src/atomic.cr +++ b/src/atomic.cr @@ -2,10 +2,13 @@ require "llvm/enums/atomic" # A value that may be updated atomically. # -# If `T` is a non-union primitive integer type or enum type, all operations are -# supported. If `T` is a reference type, or a union type containing only -# reference types or `Nil`, then only `#compare_and_set`, `#swap`, `#set`, -# `#lazy_set`, `#get`, and `#lazy_get` are available. +# * If `T` is a reference type, or a union type containing only +# reference types or `Nil`, then only `#compare_and_set`, `#swap`, `#set`, +# `#lazy_set`, `#get`, and `#lazy_get` are available. +# * If `T` is a pointer type, then the above methods plus `#max` and `#min` are +# available. +# * If `T` is a non-union primitive integer type or enum type, then all +# operations are supported. struct Atomic(T) # Specifies how memory accesses, including non atomic, are to be reordered # around atomics. Follows the C/C++ semantics: @@ -42,10 +45,12 @@ struct Atomic(T) def initialize(@value : T) {% if !T.union? && (T == Char || T < Int::Primitive || T < Enum) %} # Support integer types, enum types, or char (because it's represented as an integer) + {% elsif T < Pointer %} + # Support pointer types {% elsif T.union_types.all? { |t| t == Nil || t < Reference } && T != Nil %} # Support reference types, or union types with only nil or reference types {% else %} - {% raise "Can only create Atomic with primitive integer types, reference types or nilable reference types, not #{T}" %} + {% raise "Can only create Atomic with primitive integer types, pointer types, reference types or nilable reference types, not #{T}" %} {% end %} end @@ -117,7 +122,7 @@ struct Atomic(T) # Performs `atomic_value &+= value`. Returns the old value. # - # `T` cannot contain any reference types. + # `T` cannot contain any pointer or reference types. # # ``` # atomic = Atomic.new(1) @@ -125,13 +130,14 @@ struct Atomic(T) # atomic.get # => 3 # ``` def add(value : T, ordering : Ordering = :sequentially_consistent) : T + check_pointer_type check_reference_type atomicrmw(:add, pointerof(@value), value, ordering) end # Performs `atomic_value &-= value`. Returns the old value. # - # `T` cannot contain any reference types. + # `T` cannot contain any pointer or reference types. # # ``` # atomic = Atomic.new(9) @@ -139,13 +145,14 @@ struct Atomic(T) # atomic.get # => 7 # ``` def sub(value : T, ordering : Ordering = :sequentially_consistent) : T + check_pointer_type check_reference_type atomicrmw(:sub, pointerof(@value), value, ordering) end # Performs `atomic_value &= value`. Returns the old value. # - # `T` cannot contain any reference types. + # `T` cannot contain any pointer or reference types. # # ``` # atomic = Atomic.new(5) @@ -153,13 +160,14 @@ struct Atomic(T) # atomic.get # => 1 # ``` def and(value : T, ordering : Ordering = :sequentially_consistent) : T + check_pointer_type check_reference_type atomicrmw(:and, pointerof(@value), value, ordering) end # Performs `atomic_value = ~(atomic_value & value)`. Returns the old value. # - # `T` cannot contain any reference types. + # `T` cannot contain any pointer or reference types. # # ``` # atomic = Atomic.new(5) @@ -167,13 +175,14 @@ struct Atomic(T) # atomic.get # => -2 # ``` def nand(value : T, ordering : Ordering = :sequentially_consistent) : T + check_pointer_type check_reference_type atomicrmw(:nand, pointerof(@value), value, ordering) end # Performs `atomic_value |= value`. Returns the old value. # - # `T` cannot contain any reference types. + # `T` cannot contain any pointer or reference types. # # ``` # atomic = Atomic.new(5) @@ -181,13 +190,14 @@ struct Atomic(T) # atomic.get # => 7 # ``` def or(value : T, ordering : Ordering = :sequentially_consistent) : T + check_pointer_type check_reference_type atomicrmw(:or, pointerof(@value), value, ordering) end # Performs `atomic_value ^= value`. Returns the old value. # - # `T` cannot contain any reference types. + # `T` cannot contain any pointer or reference types. # # ``` # atomic = Atomic.new(5) @@ -195,6 +205,7 @@ struct Atomic(T) # atomic.get # => 6 # ``` def xor(value : T, ordering : Ordering = :sequentially_consistent) : T + check_pointer_type check_reference_type atomicrmw(:xor, pointerof(@value), value, ordering) end @@ -220,6 +231,8 @@ struct Atomic(T) else atomicrmw(:umax, pointerof(@value), value, ordering) end + {% elsif T < Pointer %} + T.new(atomicrmw(:umax, pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new!(value.address), ordering)) {% elsif T < Int::Signed %} atomicrmw(:max, pointerof(@value), value, ordering) {% else %} @@ -248,6 +261,8 @@ struct Atomic(T) else atomicrmw(:umin, pointerof(@value), value, ordering) end + {% elsif T < Pointer %} + T.new(atomicrmw(:umin, pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new!(value.address), ordering)) {% elsif T < Int::Signed %} atomicrmw(:min, pointerof(@value), value, ordering) {% else %} @@ -263,7 +278,9 @@ struct Atomic(T) # atomic.get # => 10 # ``` def swap(value : T, ordering : Ordering = :sequentially_consistent) - {% if T.union_types.all? { |t| t == Nil || t < Reference } && T != Nil %} + {% if T < Pointer %} + T.new(atomicrmw(:xchg, pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new!(value.address), ordering)) + {% elsif T.union_types.all? { |t| t == Nil || t < Reference } && T != Nil %} address = atomicrmw(:xchg, pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(value.as(Void*).address), ordering) Pointer(T).new(address).as(T) {% else %} @@ -325,6 +342,12 @@ struct Atomic(T) @value end + private macro check_pointer_type + {% if T < Pointer %} + {% raise "Cannot call `#{@type}##{@def.name}` as `#{T}` is a pointer type" %} + {% end %} + end + private macro check_reference_type {% if T.union_types.all? { |t| t == Nil || t < Reference } && T != Nil %} {% raise "Cannot call `#{@type}##{@def.name}` as `#{T}` is a reference type" %} From 562fb7bf7a580fa915c5ac7330c362ac4d19a564 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 16 Apr 2024 18:51:49 +0200 Subject: [PATCH 1073/1551] Log: delete source from builder cache when finalize (#14475) --- src/log/builder.cr | 6 ++++++ src/log/log.cr | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/log/builder.cr b/src/log/builder.cr index a6ed72b08960..4aab82a94001 100644 --- a/src/log/builder.cr +++ b/src/log/builder.cr @@ -157,4 +157,10 @@ class Log::Builder end false end + + # NOTE: workaround for https://github.com/crystal-lang/crystal/pull/14473 + protected def cleanup_collected_log(log : Log) : Nil + ref = @logs.fetch(log.source) { return } + @logs.delete(log.source) if ref.value.nil? || ref.value == log + end end diff --git a/src/log/log.cr b/src/log/log.cr index c2f7f17771a7..3480cfecf33b 100644 --- a/src/log/log.cr +++ b/src/log/log.cr @@ -10,6 +10,11 @@ class Log @initial_level = level end + def finalize : Nil + # NOTE: workaround for https://github.com/crystal-lang/crystal/pull/14473 + Log.builder.cleanup_collected_log(self) + end + # :nodoc: def changed_level : Severity? @level From 14da5c0823ced17db927c2aa69e12057280d4a73 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 17 Apr 2024 16:04:28 +0800 Subject: [PATCH 1074/1551] Drop Windows CI workaround for 1.12.0-dev (#14483) --- .github/workflows/win_build_portable.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index ceb2b550ae87..bc32521451aa 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -114,13 +114,7 @@ jobs: - name: Build Crystal run: | bin/crystal.bat env - # TODO: the 1.11.2 compiler only understands `-Dpreview_dll`; remove this once the - # base compiler is updated - make -f Makefile.win -B FLAGS=-Dpreview_dll ${{ inputs.release && 'release=1' || '' }} - # TODO: 1.11.2 comes with LLVM 17's DLL and copies it to the output directory, but a - # dynamically linked compiler requires LLVM 18, so we must overwrite it; remove this - # line once the base compiler is updated - cp dlls/LLVM-C.dll .build/ + make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }} - name: Download shards release uses: actions/checkout@v4 From 433e4878c893dc2fa1c563720cd5a835c80a0506 Mon Sep 17 00:00:00 2001 From: Hugo Parente Lima Date: Wed, 17 Apr 2024 05:05:00 -0300 Subject: [PATCH 1075/1551] Relax type restriction of handlers in `HTTP::Server` constructor (#14413) --- src/http/server.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/http/server.cr b/src/http/server.cr index 53ee873ed330..7fc6f94248c2 100644 --- a/src/http/server.cr +++ b/src/http/server.cr @@ -148,12 +148,12 @@ class HTTP::Server # Creates a new HTTP server with a handler chain constructed from the *handlers* # array and the given block. - def self.new(handlers : Array(HTTP::Handler), &handler : HTTP::Handler::HandlerProc) : self + def self.new(handlers : Indexable(HTTP::Handler), &handler : HTTP::Handler::HandlerProc) : self new(HTTP::Server.build_middleware(handlers, handler)) end # Creates a new HTTP server with the *handlers* array as handler chain. - def self.new(handlers : Array(HTTP::Handler)) : self + def self.new(handlers : Indexable(HTTP::Handler)) : self new(HTTP::Server.build_middleware(handlers)) end From 54a7631eef32f7c72f8a04d4c8887a73cbcdfa6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 17 Apr 2024 10:05:16 +0200 Subject: [PATCH 1076/1551] Fix `Crystal::Path#to_macro_id` for global path (#14490) --- spec/compiler/macro/macro_methods_spec.cr | 8 ++++++++ src/compiler/crystal/macros/methods.cr | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 76d5f795903c..f25e2159b5e6 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -121,6 +121,14 @@ module Crystal it "expands macro with id call on number" do assert_macro "{{x.id}}", %(1), {x: 1.int32} end + + it "expands macro with id call on path" do + assert_macro "{{x.id}}", %(Foo), {x: Path.new("Foo")} + end + + it "expands macro with id call on global path" do + assert_macro "{{x.id}}", %(::Foo), {x: Path.new("Foo", global: true)} + end end it "executes == on numbers (true)" do diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 9891c4cc2b2a..f92c9aa766c6 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -2388,7 +2388,11 @@ module Crystal end def to_macro_id - @names.join "::" + String.build do |io| + io << "::" if global? + + @names.join(io, "::") + end end end From 4dd58147c02ab8a485ba891116f019942bfd2ecb Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 17 Apr 2024 22:11:57 +0800 Subject: [PATCH 1077/1551] Optimize `String#index(Char)` and `#rindex(Char)` for invalid UTF-8 (#14461) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller Co-authored-by: Sijawusz Pur Rahnama --- src/string.cr | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/string.cr b/src/string.cr index a87bf541f0f6..d3bc7d6998b2 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3334,7 +3334,11 @@ class String # ``` def index(search : Char, offset = 0) : Int32? # If it's ASCII we can delegate to slice - if search.ascii? && single_byte_optimizable? + if single_byte_optimizable? + # With `single_byte_optimizable?` there are only ASCII characters and invalid UTF-8 byte + # sequences and we can immediately reject any non-ASCII codepoint. + return unless search.ascii? + return to_slice.fast_index(search.ord.to_u8, offset) end @@ -3450,7 +3454,11 @@ class String # ``` def rindex(search : Char, offset = size - 1) # If it's ASCII we can delegate to slice - if search.ascii? && single_byte_optimizable? + if single_byte_optimizable? + # With `single_byte_optimizable?` there are only ASCII characters and invalid UTF-8 byte + # sequences and we can immediately reject any non-ASCII codepoint. + return unless search.ascii? + return to_slice.rindex(search.ord.to_u8, offset) end From 8d2d480e33ddc77e92add1cfa8cc38115077ca95 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 19 Apr 2024 05:11:11 +0800 Subject: [PATCH 1078/1551] Implement `File.readable?` and `.writable?` in Win32 (#14420) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/file_spec.cr | 59 +++++++++++++++++++-------- src/crystal/system/win32/file.cr | 15 ++++--- src/lib_c/x86_64-windows-msvc/c/io.cr | 2 +- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index fedbf0889370..f137478b56bc 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -31,6 +31,17 @@ private def normalize_permissions(permissions, *, directory) {% end %} end +# TODO: Find a better way to execute specs involving file permissions when +# running as a privileged user. Compiling a program and running a separate +# process would be a lot of overhead. +private def pending_if_superuser! + {% if flag?(:unix) %} + if LibC.getuid == 0 + pending! "Spec cannot run as superuser" + end + {% end %} +end + describe "File" do it "gets path" do path = datapath("test_file.txt") @@ -217,6 +228,27 @@ describe "File" do it "gives false when a component of the path is a file" do File.readable?(datapath("dir", "test_file.txt", "")).should be_false end + + # win32 doesn't have a way to make files unreadable via chmod + {% unless flag?(:win32) %} + it "gives false when the file has no read permissions" do + with_tempfile("unreadable.txt") do |path| + File.write(path, "") + File.chmod(path, 0o222) + pending_if_superuser! + File.readable?(path).should be_false + end + end + + it "gives false when the file has no permissions" do + with_tempfile("unaccessible.txt") do |path| + File.write(path, "") + File.chmod(path, 0o000) + pending_if_superuser! + File.readable?(path).should be_false + end + end + {% end %} end describe "writable?" do @@ -231,6 +263,15 @@ describe "File" do it "gives false when a component of the path is a file" do File.writable?(datapath("dir", "test_file.txt", "")).should be_false end + + it "gives false when the file has no write permissions" do + with_tempfile("readonly.txt") do |path| + File.write(path, "") + File.chmod(path, 0o444) + pending_if_superuser! + File.writable?(path).should be_false + end + end end describe "file?" do @@ -991,14 +1032,7 @@ describe "File" do with_tempfile("file.txt") do |path| File.touch(path) File.chmod(path, File::Permissions::None) - {% if flag?(:unix) %} - # TODO: Find a better way to execute this spec when running as privileged - # user. Compiling a program and running a separate process would be a - # lot of overhead. - if LibC.getuid == 0 - pending! "Spec cannot run as superuser" - end - {% end %} + pending_if_superuser! expect_raises(File::AccessDeniedError, "Error opening file with mode 'r': '#{path.inspect_unquoted}'") { File.read(path) } end end @@ -1008,14 +1042,7 @@ describe "File" do with_tempfile("file.txt") do |path| File.touch(path) File.chmod(path, File::Permissions::None) - {% if flag?(:unix) %} - # TODO: Find a better way to execute this spec when running as privileged - # user. Compiling a program and running a separate process would be a - # lot of overhead. - if LibC.getuid == 0 - pending! "Spec cannot run as superuser" - end - {% end %} + pending_if_superuser! expect_raises(File::AccessDeniedError, "Error opening file with mode 'w': '#{path.inspect_unquoted}'") { File.write(path, "foo") } end end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index d95202be5732..26a12dc62f18 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -177,23 +177,28 @@ module Crystal::System::File if follow_symlinks path = realpath?(path) || return false end - accessible?(path, 0) + accessible?(path, check_writable: false) end def self.readable?(path) : Bool - accessible?(path, 4) + accessible?(path, check_writable: false) end def self.writable?(path) : Bool - accessible?(path, 2) + accessible?(path, check_writable: true) end def self.executable?(path) : Bool LibC.GetBinaryTypeW(System.to_wstr(path), out result) != 0 end - private def self.accessible?(path, mode) - LibC._waccess_s(System.to_wstr(path), mode) == 0 + private def self.accessible?(path, *, check_writable) + attributes = LibC.GetFileAttributesW(System.to_wstr(path)) + return false if attributes == LibC::INVALID_FILE_ATTRIBUTES + return true if attributes.bits_set?(LibC::FILE_ATTRIBUTE_DIRECTORY) + return false if check_writable && attributes.bits_set?(LibC::FILE_ATTRIBUTE_READONLY) + + true end def self.chown(path : String, uid : Int32, gid : Int32, follow_symlinks : Bool) : Nil diff --git a/src/lib_c/x86_64-windows-msvc/c/io.cr b/src/lib_c/x86_64-windows-msvc/c/io.cr index 64092a1b1a8c..3d0708d211cf 100644 --- a/src/lib_c/x86_64-windows-msvc/c/io.cr +++ b/src/lib_c/x86_64-windows-msvc/c/io.cr @@ -2,7 +2,6 @@ require "c/stdint" lib LibC fun _close(fd : Int) : Int - fun _waccess_s(path : WCHAR*, mode : Int) : ErrnoT fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT fun _get_osfhandle(fd : Int) : IntPtrT fun _dup2(fd1 : Int, fd2 : Int) : Int @@ -14,6 +13,7 @@ lib LibC fun _read(fd : Int, buffer : UInt8*, count : UInt) : Int fun _lseeki64(fd : Int, offset : Int64, origin : Int) : Int64 fun _wopen(filename : WCHAR*, oflag : Int, ...) : Int + fun _waccess_s(path : WCHAR*, mode : Int) : ErrnoT fun _wchmod(filename : WCHAR*, pmode : Int) : Int fun _wunlink(filename : WCHAR*) : Int fun _wmktemp_s(template : WCHAR*, sizeInChars : SizeT) : ErrnoT From 3c7666291f18e4af57a61d79d90fdd424c79b7a4 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 19 Apr 2024 15:36:21 +0800 Subject: [PATCH 1079/1551] Add AST node methods for macro-related nodes (#14492) --- spec/compiler/macro/macro_methods_spec.cr | 29 +++++++++++ src/compiler/crystal/macros.cr | 63 ++++++++++++++++++++--- src/compiler/crystal/macros/methods.cr | 43 ++++++++++++++++ src/compiler/crystal/macros/types.cr | 11 ++-- 4 files changed, 133 insertions(+), 13 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index f25e2159b5e6..4faac909ebaa 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2554,6 +2554,17 @@ module Crystal end end + describe MacroExpression do + it "executes exp" do + assert_macro %({{x.exp}}), "nil", {x: MacroExpression.new(NilLiteral.new)} + end + + it "executes output?" do + assert_macro %({{x.output?}}), "false", {x: MacroExpression.new(NilLiteral.new, output: false)} + assert_macro %({{x.output?}}), "true", {x: MacroExpression.new(1.int32, output: true)} + end + end + describe "macro if methods" do it "executes cond" do assert_macro %({{x.cond}}), "true", {x: MacroIf.new(BoolLiteral.new(true), NilLiteral.new)} @@ -2582,6 +2593,24 @@ module Crystal end end + describe MacroLiteral do + it "executes value" do + assert_macro %({{x.value}}), "foo(1)", {x: MacroLiteral.new("foo(1)")} + assert_macro %({{x.value}}), "", {x: MacroLiteral.new("")} + end + end + + describe MacroVar do + it "executes name" do + assert_macro %({{x.name}}), "foo", {x: MacroVar.new("foo")} + end + + it "executes expressions" do + assert_macro %({{x.expressions}}), "[] of ::NoReturn", {x: MacroVar.new("foo")} + assert_macro %({{x.expressions}}), "[x, 1]", {x: MacroVar.new("bar", [Var.new("x"), 1.int32] of ASTNode)} + end + end + describe "unary expression methods" do it "executes exp" do assert_macro %({{x.exp}}), "some_call", {x: Not.new("some_call".call)} diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 3ec6243ffd62..f504f4eb6ed2 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2303,15 +2303,33 @@ module Crystal::Macros end end - # A macro expression, - # surrounded by {{ ... }} (output = true) - # or by {% ... %} (output = false) - # class MacroExpression < ASTNode - # end + # A macro expression. + # + # Every expression *node* is equivalent to: + # + # ``` + # {% if node.output? %} + # \{{ {{ node.exp }} }} + # {% else %} + # \{% {{ node.exp }} %} + # {% end %} + # ``` + class MacroExpression < ASTNode + # Returns the expression inside this node. + def exp : ASTNode + end - # Free text that is part of a macro - # class MacroLiteral < ASTNode - # end + # Returns whether this node interpolates the expression's result. + def output? : BoolLiteral + end + end + + # Free text that is part of a macro. + class MacroLiteral < ASTNode + # Returns the text of the literal. + def value : MacroId + end + end # An `if` inside a macro, e.g. # @@ -2357,6 +2375,35 @@ module Crystal::Macros end end + # A macro fresh variable. + # + # Every variable `node` is equivalent to: + # + # ``` + # {{ "%#{name}".id }}{% if expressions = node.expressions %}{{ "{#{expressions.splat}}".id }}{% end %} + # ``` + class MacroVar < ASTNode + # Returns the name of the fresh variable. + def name : MacroId + end + + # Returns the associated indices of the fresh variable. + def expressions : ArrayLiteral + end + end + + # A verbatim expression. + # + # Every expression `node` is equivalent to: + # + # ``` + # \{% verbatim do %} + # {{ node.exp }} + # \{% end %} + # ``` + class MacroVerbatim < UnaryExpression + end + # The `_` expression. May appear in code, such as an assignment target, and in # type names. class Underscore < ASTNode diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index f92c9aa766c6..61879dbe9d91 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1497,6 +1497,19 @@ module Crystal end end + class MacroExpression + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "exp" + interpret_check_args { @exp } + when "output?" + interpret_check_args { BoolLiteral.new(@output) } + else + super + end + end + end + class MacroIf def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) case method @@ -1527,6 +1540,36 @@ module Crystal end end + class MacroLiteral + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "value" + interpret_check_args { MacroId.new(@value) } + else + super + end + end + end + + class MacroVar + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "name" + interpret_check_args { MacroId.new(@name) } + when "expressions" + interpret_check_args do + if exps = @exps + ArrayLiteral.map(exps, &.itself) + else + empty_no_return_array + end + end + else + super + end + end + end + class UnaryExpression def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) case method diff --git a/src/compiler/crystal/macros/types.cr b/src/compiler/crystal/macros/types.cr index 068f92e3d23a..7a7777e8aef3 100644 --- a/src/compiler/crystal/macros/types.cr +++ b/src/compiler/crystal/macros/types.cr @@ -90,9 +90,14 @@ module Crystal @macro_types["Cast"] = NonGenericMacroType.new self, "Cast", ast_node @macro_types["NilableCast"] = NonGenericMacroType.new self, "NilableCast", ast_node @macro_types["MacroId"] = NonGenericMacroType.new self, "MacroId", ast_node + @macro_types["TypeNode"] = NonGenericMacroType.new self, "TypeNode", ast_node + + @macro_types["MacroExpression"] = NonGenericMacroType.new self, "MacroExpression", ast_node @macro_types["MacroFor"] = NonGenericMacroType.new self, "MacroFor", ast_node @macro_types["MacroIf"] = NonGenericMacroType.new self, "MacroIf", ast_node - @macro_types["TypeNode"] = NonGenericMacroType.new self, "TypeNode", ast_node + @macro_types["MacroLiteral"] = NonGenericMacroType.new self, "MacroLiteral", ast_node + @macro_types["MacroVar"] = NonGenericMacroType.new self, "MacroVar", ast_node + @macro_types["MacroVerbatim"] = NonGenericMacroType.new self, "MacroVerbatim", unary_expression # bottom type @macro_types["NoReturn"] = @macro_no_return = NoReturnMacroType.new self @@ -117,10 +122,6 @@ module Crystal @macro_types["ExternalVar"] = NonGenericMacroType.new self, "ExternalVar", ast_node @macro_types["Include"] = NonGenericMacroType.new self, "Include", ast_node @macro_types["TypeDef"] = NonGenericMacroType.new self, "TypeDef", ast_node - @macro_types["MacroExpression"] = NonGenericMacroType.new self, "MacroExpression", ast_node - @macro_types["MacroLiteral"] = NonGenericMacroType.new self, "MacroLiteral", ast_node - @macro_types["MacroVar"] = NonGenericMacroType.new self, "MacroVar", ast_node - @macro_types["MacroVerbatim"] = NonGenericMacroType.new self, "MacroVerbatim", unary_expression end # Returns the macro type for a given AST node. This association is done From 0706bc85c47c2ab3722a7be77b21a7616f3bb1cc Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 19 Apr 2024 15:36:34 +0800 Subject: [PATCH 1080/1551] Fix parsing of non-trailing `if` bodies inside macro expressions (#14505) --- spec/compiler/parser/parser_spec.cr | 4 ++++ src/compiler/crystal/syntax/parser.cr | 2 ++ 2 files changed, 6 insertions(+) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index ea230d4e36ac..a8c884c58ae6 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1103,8 +1103,12 @@ module Crystal it_parses "{% a = 1 if 2 %}", MacroExpression.new(If.new(2.int32, Assign.new("a".var, 1.int32)), output: false) it_parses "{% if 1; 2; end %}", MacroExpression.new(If.new(1.int32, 2.int32), output: false) it_parses "{%\nif 1; 2; end\n%}", MacroExpression.new(If.new(1.int32, 2.int32), output: false) + it_parses "{% if 1\n x\nend %}", MacroExpression.new(If.new(1.int32, "x".var), output: false) + it_parses "{% x if 1 %}", MacroExpression.new(If.new(1.int32, "x".var), output: false) it_parses "{% unless 1; 2; end %}", MacroExpression.new(Unless.new(1.int32, 2.int32, Nop.new), output: false) it_parses "{% unless 1; 2; else 3; end %}", MacroExpression.new(Unless.new(1.int32, 2.int32, 3.int32), output: false) + it_parses "{% unless 1\n x\nend %}", MacroExpression.new(Unless.new(1.int32, "x".var), output: false) + it_parses "{% x unless 1 %}", MacroExpression.new(Unless.new(1.int32, "x".var), output: false) it_parses "{%\n1\n2\n3\n%}", MacroExpression.new(Expressions.new([1.int32, 2.int32, 3.int32] of ASTNode), output: false) assert_syntax_error "{% unless 1; 2; elsif 3; 4; end %}" diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index a9454c73a11c..afbf13e75031 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3441,11 +3441,13 @@ module Crystal @in_macro_expression = false if !@token.type.op_percent_rcurly? && check_end + @in_macro_expression = true if is_unless node = parse_unless_after_condition cond, location else node = parse_if_after_condition cond, location, true end + @in_macro_expression = false skip_space_or_newline check :OP_PERCENT_RCURLY return MacroExpression.new(node, output: false).at_end(token_end_location) From 62de01cb003e38e12b8c88100d12b92aa1ac527b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 20 Apr 2024 16:15:11 +0800 Subject: [PATCH 1081/1551] Drop parentheses around `->` inside certain comma-separated lists (#14506) --- spec/compiler/parser/to_s_spec.cr | 24 +++++- .../semantic/restrictions_augmenter_spec.cr | 4 +- src/compiler/crystal/syntax/to_s.cr | 78 +++++++++++++++---- 3 files changed, 85 insertions(+), 21 deletions(-) diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index 63da166aba45..d4838fc7945c 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -108,8 +108,28 @@ describe "ASTNode#to_s" do expect_to_s "def foo(x, @[Foo] **args)\nend" expect_to_s "def foo(x, **args, &block)\nend" expect_to_s "def foo(@[Foo] x, @[Bar] **args, @[Baz] &block)\nend" - expect_to_s "def foo(x, **args, &block : (_ -> _))\nend" - expect_to_s "def foo(& : (->))\nend" + + # 14216 + expect_to_s "def foo(x, **args, &block : _ -> _)\nend" + expect_to_s "def foo(x, **args, &block : (_ -> _))\nend", "def foo(x, **args, &block : _ -> _)\nend" + expect_to_s "def foo(& : ->)\nend" + expect_to_s "def foo(& : (->))\nend", "def foo(& : ->)\nend" + expect_to_s "def foo(x : (T -> U) -> V, *args : (T -> U) -> V, y : (T -> U) -> V, **opts : (T -> U) -> V, & : (T -> U) -> V) : ((T -> U) -> V)\nend" + expect_to_s "foo(x : (T -> U) -> V, W)" + expect_to_s "foo[x : (T -> U) -> V, W]" + expect_to_s "foo[x : (T -> U) -> V, W] = 1" + expect_to_s "lib LibFoo\n fun foo(x : (T -> U) -> V, W) : ((T -> U) -> V)\nend" + + expect_to_s "lib LibFoo\n fun foo(x : (T -> U) | V)\nend" + expect_to_s "lib LibFoo\n fun foo(x : Foo((T -> U)))\nend" + expect_to_s "lib LibFoo\n fun foo(x : (T -> U).class)\nend" + expect_to_s "def foo(x : (T -> U) | V)\nend" + expect_to_s "def foo(x : Foo((T -> U)))\nend" + expect_to_s "def foo(x : (T -> U).class)\nend" + expect_to_s "foo(x : (T -> U) | V)" + expect_to_s "foo(x : Foo((T -> U)))" + expect_to_s "foo(x : (T -> U).class)" + expect_to_s "macro foo(@[Foo] id)\nend" expect_to_s "macro foo(**args)\nend" expect_to_s "macro foo(@[Foo] **args)\nend" diff --git a/spec/compiler/semantic/restrictions_augmenter_spec.cr b/spec/compiler/semantic/restrictions_augmenter_spec.cr index 5095f8613943..2b7250658693 100644 --- a/spec/compiler/semantic/restrictions_augmenter_spec.cr +++ b/spec/compiler/semantic/restrictions_augmenter_spec.cr @@ -45,8 +45,8 @@ describe "Semantic: restrictions augmenter" do it_augments_for_ivar "Array(String)", "::Array(::String)" it_augments_for_ivar "Tuple(Int32, Char)", "::Tuple(::Int32, ::Char)" it_augments_for_ivar "NamedTuple(a: Int32, b: Char)", "::NamedTuple(a: ::Int32, b: ::Char)" - it_augments_for_ivar "Proc(Int32, Char)", "(::Int32 -> ::Char)" - it_augments_for_ivar "Proc(Int32, Nil)", "(::Int32 -> _)" + it_augments_for_ivar "Proc(Int32, Char)", "::Int32 -> ::Char" + it_augments_for_ivar "Proc(Int32, Nil)", "::Int32 -> _" it_augments_for_ivar "Pointer(Void)", "::Pointer(::Void)" it_augments_for_ivar "StaticArray(Int32, 8)", "::StaticArray(::Int32, 8)" it_augments_for_ivar "Char | Int32 | String", "::Char | ::Int32 | ::String" diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 7735cddb3951..b94b3f6981f1 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -18,6 +18,13 @@ module Crystal @macro_expansion_pragmas : Hash(Int32, Array(Lexer::LocPragma))? @current_arg_type : DefArgType = :none + # Inside a comma-separated list of parameters or args, this becomes true and + # the outermost pair of parentheses are removed from type restrictions that + # are `ProcNotation` nodes, so `foo(x : (T, U -> V), W)` becomes + # `foo(x : T, U -> V, W)`. This is used by defs, lib funs, and calls to deal + # with the parsing rules for `->`. See #11966 and #14216 for more details. + getter? drop_parens_for_proc_notation = false + private enum DefArgType NONE SPLAT @@ -440,20 +447,20 @@ module Crystal break if exclude_last && i == node.args.size - 1 @str << ", " if printed_arg - arg.accept self + drop_parens_for_proc_notation(arg, &.accept(self)) printed_arg = true end if named_args = node.named_args named_args.each do |named_arg| @str << ", " if printed_arg - named_arg.accept self + drop_parens_for_proc_notation(named_arg, &.accept(self)) printed_arg = true end end if block_arg = node.block_arg @str << ", " if printed_arg @str << '&' - block_arg.accept self + drop_parens_for_proc_notation(block_arg, &.accept(self)) end end @@ -635,19 +642,19 @@ module Crystal node.args.each_with_index do |arg, i| @str << ", " if printed_arg @current_arg_type = :splat if node.splat_index == i - arg.accept self + drop_parens_for_proc_notation(arg, &.accept(self)) printed_arg = true end if double_splat = node.double_splat @current_arg_type = :double_splat @str << ", " if printed_arg - double_splat.accept self + drop_parens_for_proc_notation(double_splat, &.accept(self)) printed_arg = true end if block_arg = node.block_arg @current_arg_type = :block_arg @str << ", " if printed_arg - block_arg.accept self + drop_parens_for_proc_notation(block_arg, &.accept(self)) elsif node.block_arity @str << ", " if printed_arg @str << '&' @@ -680,6 +687,8 @@ module Crystal if node.args.size > 0 || node.block_arg || node.double_splat @str << '(' printed_arg = false + # NOTE: `drop_parens_for_proc_notation` needed here if macros support + # restrictions node.args.each_with_index do |arg, i| @str << ", " if printed_arg @current_arg_type = :splat if i == node.splat_index @@ -830,17 +839,24 @@ module Crystal end def visit(node : ProcNotation) - @str << '(' - if inputs = node.inputs - inputs.join(@str, ", ", &.accept self) - @str << ' ' - end - @str << "->" - if output = node.output - @str << ' ' - output.accept self + @str << '(' unless drop_parens_for_proc_notation? + + # only drop the outermost pair of parentheses; this produces + # `foo(x : (T -> U) -> V, W)`, not + # `foo(x : ((T -> U) -> V), W)` nor `foo(x : T -> U -> V, W)` + drop_parens_for_proc_notation(false) do + if inputs = node.inputs + inputs.join(@str, ", ", &.accept self) + @str << ' ' + end + @str << "->" + if output = node.output + @str << ' ' + output.accept self + end end - @str << ')' + + @str << ')' unless drop_parens_for_proc_notation? false end @@ -1147,7 +1163,9 @@ module Crystal if arg_name = arg.name.presence @str << arg_name << " : " end - arg.restriction.not_nil!.accept self + drop_parens_for_proc_notation(arg) do + arg.restriction.not_nil!.accept self + end end if node.varargs? @str << ", ..." @@ -1575,6 +1593,32 @@ module Crystal @inside_macro = old_inside_macro end + def drop_parens_for_proc_notation(drop : Bool = true, &) + old_drop_parens_for_proc_notation = @drop_parens_for_proc_notation + @drop_parens_for_proc_notation = drop + begin + yield + ensure + @drop_parens_for_proc_notation = old_drop_parens_for_proc_notation + end + end + + def drop_parens_for_proc_notation(node : ASTNode, &) + outermost_type_is_proc_notation = + case node + when Arg + # def / fun parameters + node.restriction.is_a?(ProcNotation) + when TypeDeclaration + # call arguments + node.declared_type.is_a?(ProcNotation) + else + false + end + + drop_parens_for_proc_notation(outermost_type_is_proc_notation) { yield node } + end + def to_s : String @str.to_s end From 096f89bcfa2762952677508dad5e07bacb16e8e9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 21 Apr 2024 19:09:53 +0800 Subject: [PATCH 1082/1551] Use file handles directly instead of C file descriptors on Win32 (#14501) --- spec/compiler/macro/macro_methods_spec.cr | 4 +- src/crystal/system/file.cr | 14 ++++-- src/crystal/system/process.cr | 6 +-- src/crystal/system/unix/file_descriptor.cr | 4 ++ src/crystal/system/win32/file.cr | 24 +++------ src/crystal/system/win32/file_descriptor.cr | 55 ++++++++++----------- src/crystal/system/win32/process.cr | 2 +- src/io/file_descriptor.cr | 4 +- src/lib_c/x86_64-windows-msvc/c/io.cr | 8 +-- src/lib_c/x86_64-windows-msvc/c/winbase.cr | 4 +- 10 files changed, 62 insertions(+), 63 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 4faac909ebaa..041d60cb6f35 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -3732,7 +3732,7 @@ module Crystal assert_error <<-CRYSTAL, {{read_file("#{__DIR__}/../data/build_foo")}} CRYSTAL - "No such file or directory" + "Error opening file with mode 'r'" end end @@ -3747,7 +3747,7 @@ module Crystal assert_error <<-CRYSTAL, {{read_file("spec/compiler/data/build_foo")}} CRYSTAL - "No such file or directory" + "Error opening file with mode 'r'" end end end diff --git a/src/crystal/system/file.cr b/src/crystal/system/file.cr index 89d29512c28e..75985c107fd5 100644 --- a/src/crystal/system/file.cr +++ b/src/crystal/system/file.cr @@ -49,7 +49,7 @@ module Crystal::System::File LOWER_ALPHANUM = "0123456789abcdefghijklmnopqrstuvwxyz".to_slice - def self.mktemp(prefix : String?, suffix : String?, dir : String, random : ::Random = ::Random::DEFAULT) : {LibC::Int, String} + def self.mktemp(prefix : String?, suffix : String?, dir : String, random : ::Random = ::Random::DEFAULT) : {FileDescriptor::Handle, String} mode = LibC::O_RDWR | LibC::O_CREAT | LibC::O_EXCL perm = ::File::Permissions.new(0o600) @@ -65,10 +65,10 @@ module Crystal::System::File io << suffix end - fd, errno = open(path, mode, perm) + handle, errno = open(path, mode, perm) - if errno.none? - return {fd, path} + if error_is_none?(errno) + return {handle, path} elsif error_is_file_exists?(errno) # retry next @@ -80,8 +80,12 @@ module Crystal::System::File raise ::File::AlreadyExistsError.new("Error creating temporary file", file: "#{prefix}********#{suffix}") end + private def self.error_is_none?(errno) + errno.in?(Errno::NONE, WinError::ERROR_SUCCESS) + end + private def self.error_is_file_exists?(errno) - errno.in?(Errno::EEXIST, WinError::ERROR_ALREADY_EXISTS) + errno.in?(Errno::EEXIST, WinError::ERROR_FILE_EXISTS) end # Closes the internal file descriptor without notifying libevent. diff --git a/src/crystal/system/process.cr b/src/crystal/system/process.cr index fcc08adbbec3..11bd2363cb89 100644 --- a/src/crystal/system/process.cr +++ b/src/crystal/system/process.cr @@ -84,9 +84,9 @@ struct Crystal::System::Process end module Crystal::System - ORIGINAL_STDIN = IO::FileDescriptor.new(0, blocking: true) - ORIGINAL_STDOUT = IO::FileDescriptor.new(1, blocking: true) - ORIGINAL_STDERR = IO::FileDescriptor.new(2, blocking: true) + ORIGINAL_STDIN = IO::FileDescriptor.new(Crystal::System::FileDescriptor::STDIN_HANDLE, blocking: true) + ORIGINAL_STDOUT = IO::FileDescriptor.new(Crystal::System::FileDescriptor::STDOUT_HANDLE, blocking: true) + ORIGINAL_STDERR = IO::FileDescriptor.new(Crystal::System::FileDescriptor::STDERR_HANDLE, blocking: true) end {% if flag?(:wasi) %} diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 6e82f4b7ae48..c0404e7652a2 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -13,6 +13,10 @@ module Crystal::System::FileDescriptor # system. alias Handle = Int32 + STDIN_HANDLE = 0 + STDOUT_HANDLE = 1 + STDERR_HANDLE = 2 + private def unbuffered_read(slice : Bytes) : Int32 evented_read(slice, "Error reading file") do LibC.read(fd, slice, slice.size).tap do |return_code| diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 26a12dc62f18..5db0d297ce17 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -9,7 +9,7 @@ require "c/ntifs" require "c/winioctl" module Crystal::System::File - def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions) : LibC::Int + def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions) : FileDescriptor::Handle perm = ::File::Permissions.new(perm) if perm.is_a? Int32 # Only the owner writable bit is used, since windows only supports # the read only attribute. @@ -19,15 +19,15 @@ module Crystal::System::File perm = LibC::S_IREAD end - fd, errno = open(filename, open_flag(mode), ::File::Permissions.new(perm)) - unless errno.none? - raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", errno, file: filename) + handle, error = open(filename, open_flag(mode), ::File::Permissions.new(perm)) + unless error.error_success? + raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", error, file: filename) end - fd + handle end - def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {LibC::Int, Errno} + def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {FileDescriptor::Handle, WinError} access, disposition, attributes = self.posix_to_open_opts flags, perm handle = LibC.CreateFileW( @@ -40,17 +40,7 @@ module Crystal::System::File LibC::HANDLE.null ) - if handle == LibC::INVALID_HANDLE_VALUE - return {-1, WinError.value.to_errno} - end - - fd = LibC._open_osfhandle handle, flags - - if fd == -1 - return {-1, Errno.value} - end - - {fd, Errno::NONE} + {handle.address, handle == LibC::INVALID_HANDLE_VALUE ? WinError.value : WinError::ERROR_SUCCESS} end private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index aeea22fa2a18..161032cd307b 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -9,7 +9,13 @@ module Crystal::System::FileDescriptor # Platform-specific type to represent a file descriptor handle to the operating # system. - alias Handle = ::LibC::Int + # NOTE: this should really be `LibC::HANDLE`, here it is an integer type of + # the same size so that `IO::FileDescriptor#fd` continues to return an `Int` + alias Handle = ::LibC::UIntPtrT + + STDIN_HANDLE = LibC.GetStdHandle(LibC::STD_INPUT_HANDLE).address + STDOUT_HANDLE = LibC.GetStdHandle(LibC::STD_OUTPUT_HANDLE).address + STDERR_HANDLE = LibC.GetStdHandle(LibC::STD_ERROR_HANDLE).address @system_blocking = true @@ -92,21 +98,12 @@ module Crystal::System::FileDescriptor raise NotImplementedError.new "Crystal::System::FileDescriptor.fcntl" end - private def windows_handle - FileDescriptor.windows_handle!(fd) + protected def windows_handle + FileDescriptor.windows_handle(fd) end def self.windows_handle(fd) - ret = LibC._get_osfhandle(fd) - return LibC::INVALID_HANDLE_VALUE if ret == -1 || ret == -2 - LibC::HANDLE.new(ret) - end - - def self.windows_handle!(fd) - ret = LibC._get_osfhandle(fd) - raise RuntimeError.from_errno("_get_osfhandle") if ret == -1 - raise RuntimeError.new("_get_osfhandle returned -2") if ret == -2 - LibC::HANDLE.new(ret) + LibC::HANDLE.new(fd) end def self.system_info(handle, file_type = nil) @@ -152,10 +149,11 @@ module Crystal::System::FileDescriptor end private def system_reopen(other : IO::FileDescriptor) - # Windows doesn't implement the CLOEXEC flag - if LibC._dup2(other.fd, self.fd) == -1 - raise IO::Error.from_errno("Could not reopen file descriptor") + cur_proc = LibC.GetCurrentProcess + if LibC.DuplicateHandle(cur_proc, other.windows_handle, cur_proc, out new_handle, 0, true, LibC::DUPLICATE_SAME_ACCESS) == 0 + raise IO::Error.from_winerror("Could not reopen file descriptor") end + @volatile_fd.set(new_handle.address) # Mark the handle open, since we had to have dup'd a live handle. @closed = false @@ -168,13 +166,8 @@ module Crystal::System::FileDescriptor end def file_descriptor_close - if LibC._close(fd) != 0 - case Errno.value - when Errno::EINTR - # ignore - else - raise IO::Error.from_errno("Error closing file", target: self) - end + if LibC.CloseHandle(windows_handle) == 0 + raise IO::Error.from_winerror("Error closing file", target: self) end end @@ -253,15 +246,15 @@ module Crystal::System::FileDescriptor raise IO::Error.from_winerror("CreateFileW") if r_pipe == LibC::INVALID_HANDLE_VALUE Crystal::Scheduler.event_loop.create_completion_port(r_pipe) unless read_blocking - r = IO::FileDescriptor.new(LibC._open_osfhandle(r_pipe, 0), read_blocking) - w = IO::FileDescriptor.new(LibC._open_osfhandle(w_pipe, 0), write_blocking) + r = IO::FileDescriptor.new(r_pipe.address, read_blocking) + w = IO::FileDescriptor.new(w_pipe.address, write_blocking) w.sync = true {r, w} end def self.pread(fd, buffer, offset) - handle = windows_handle!(fd) + handle = windows_handle(fd) overlapped = LibC::OVERLAPPED.new overlapped.union.offset.offset = LibC::DWORD.new(offset) @@ -276,8 +269,14 @@ module Crystal::System::FileDescriptor end def self.from_stdio(fd) + handle = case fd + when 0 then LibC.GetStdHandle(LibC::STD_INPUT_HANDLE) + when 1 then LibC.GetStdHandle(LibC::STD_OUTPUT_HANDLE) + when 2 then LibC.GetStdHandle(LibC::STD_ERROR_HANDLE) + else LibC::INVALID_HANDLE_VALUE + end + console_handle = false - handle = windows_handle(fd) if handle != LibC::INVALID_HANDLE_VALUE # TODO: use `out old_mode` after implementing interpreter out closured var old_mode = uninitialized LibC::DWORD @@ -291,7 +290,7 @@ module Crystal::System::FileDescriptor end end - io = IO::FileDescriptor.new(fd, blocking: true) + io = IO::FileDescriptor.new(handle.address, blocking: true) # Set sync or flush_on_newline as described in STDOUT and STDERR docs. # See https://crystal-lang.org/api/toplevel.html#STDERR if console_handle diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 25ac32ca85e8..6a0467040e10 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -256,7 +256,7 @@ struct Crystal::System::Process end private def self.handle_from_io(io : IO::FileDescriptor, parent_io) - source_handle = FileDescriptor.windows_handle!(io.fd) + source_handle = io.windows_handle cur_proc = LibC.GetCurrentProcess if LibC.DuplicateHandle(cur_proc, source_handle, cur_proc, out new_handle, 0, true, LibC::DUPLICATE_SAME_ACCESS) == 0 diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 00a846086210..cf5324a006d6 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -39,7 +39,7 @@ class IO::FileDescriptor < IO write_timeout end - def initialize(fd, blocking = nil, *, @close_on_finalize = true) + def initialize(fd : Handle, blocking = nil, *, @close_on_finalize = true) @volatile_fd = Atomic.new(fd) @closed = system_closed? @@ -57,7 +57,7 @@ class IO::FileDescriptor < IO end # :nodoc: - def self.from_stdio(fd) : self + def self.from_stdio(fd : Handle) : self Crystal::System::FileDescriptor.from_stdio(fd) end diff --git a/src/lib_c/x86_64-windows-msvc/c/io.cr b/src/lib_c/x86_64-windows-msvc/c/io.cr index 3d0708d211cf..75da8c18e5b9 100644 --- a/src/lib_c/x86_64-windows-msvc/c/io.cr +++ b/src/lib_c/x86_64-windows-msvc/c/io.cr @@ -1,13 +1,13 @@ require "c/stdint" lib LibC - fun _close(fd : Int) : Int fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT - fun _get_osfhandle(fd : Int) : IntPtrT - fun _dup2(fd1 : Int, fd2 : Int) : Int - fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int # unused + fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int + fun _get_osfhandle(fd : Int) : IntPtrT + fun _close(fd : Int) : Int + fun _dup2(fd1 : Int, fd2 : Int) : Int fun _isatty(fd : Int) : Int fun _write(fd : Int, buffer : UInt8*, count : UInt) : Int fun _read(fd : Int, buffer : UInt8*, count : UInt) : Int diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index 138eb04c1333..0a736a4fa89c 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -12,7 +12,9 @@ lib LibC FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000_u32 FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF_u32 - STD_ERROR_HANDLE = 0xFFFFFFF4_u32 + STD_INPUT_HANDLE = 0xFFFFFFF6_u32 + STD_OUTPUT_HANDLE = 0xFFFFFFF5_u32 + STD_ERROR_HANDLE = 0xFFFFFFF4_u32 fun FormatMessageA(dwFlags : DWORD, lpSource : Void*, dwMessageId : DWORD, dwLanguageId : DWORD, lpBuffer : LPSTR, nSize : DWORD, arguments : Void*) : DWORD From 556f2cd95abb6fa43ca7860ff2f5511daf51deb9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 22 Apr 2024 23:30:11 +0800 Subject: [PATCH 1083/1551] Fix indentation of `Select` nodes' macro interpolation (#14510) --- spec/compiler/parser/to_s_spec.cr | 18 ++++++++++++++++++ src/compiler/crystal/syntax/to_s.cr | 4 +++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index d4838fc7945c..90972d4db297 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -274,4 +274,22 @@ describe "ASTNode#to_s" do expect_to_s "def foo(x)\n yield\nend", "def foo(x, &)\n yield\nend" expect_to_s "def foo(**x)\n yield\nend", "def foo(**x, &)\n yield\nend" expect_to_s "macro foo(x)\n yield\nend" + expect_to_s <<-CRYSTAL + select + when foo + select + when bar + 1 + else + 2 + end + else + select + when baz + 3 + else + 4 + end + end + CRYSTAL end diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index b94b3f6981f1..6f072880b744 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -1373,18 +1373,20 @@ module Crystal @str << "select" newline node.whens.each do |a_when| + append_indent @str << "when " a_when.condition.accept self newline accept_with_indent a_when.body end if a_else = node.else + append_indent @str << "else" newline accept_with_indent a_else end + append_indent @str << "end" - newline false end From c428bab9d0b669f61500fbd1d8c9407523640351 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 23 Apr 2024 19:42:34 +0800 Subject: [PATCH 1084/1551] Fix `Socket#close` error handling on Windows (#14517) --- src/crystal/system/win32/socket.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 5621f34004da..5a04df9742de 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -486,11 +486,11 @@ module Crystal::System::Socket ret = LibC.closesocket(handle) if ret != 0 - case Errno.value - when Errno::EINTR, Errno::EINPROGRESS + case err = WinError.wsa_value + when WinError::WSAEINTR, WinError::WSAEINPROGRESS # ignore else - return ::Socket::Error.from_wsa_error("Error closing socket") + raise ::Socket::Error.from_os_error("Error closing socket", err) end end end From 10414fd81b66f77551a5a43939bd441a5e9e0ab5 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 23 Apr 2024 19:43:05 +0800 Subject: [PATCH 1085/1551] Make `File.readable?` and `.writable?` follow symlinks on Windows (#14514) --- spec/std/file_spec.cr | 68 ++++++++++++++++++++++++++++++++ src/crystal/system/win32/file.cr | 17 ++++---- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index f137478b56bc..fe91d1071b52 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -200,9 +200,24 @@ describe "File" do it "gives false when a component of the path is a file" do File.exists?(datapath("dir", "test_file.txt", "")).should be_false end + + it "follows symlinks" do + with_tempfile("good_symlink.txt", "bad_symlink.txt") do |good_path, bad_path| + File.symlink(File.expand_path(datapath("test_file.txt")), good_path) + File.symlink(File.expand_path(datapath("non_existing_file.txt")), bad_path) + + File.exists?(good_path).should be_true + File.exists?(bad_path).should be_false + end + end end describe "executable?" do + it "gives true" do + crystal = Process.executable_path || pending! "Unable to locate compiler executable" + File.executable?(crystal).should be_true + end + it "gives false" do File.executable?(datapath("test_file.txt")).should be_false end @@ -214,6 +229,17 @@ describe "File" do it "gives false when a component of the path is a file" do File.executable?(datapath("dir", "test_file.txt", "")).should be_false end + + it "follows symlinks" do + with_tempfile("good_symlink_x.txt", "bad_symlink_x.txt") do |good_path, bad_path| + crystal = Process.executable_path || pending! "Unable to locate compiler executable" + File.symlink(File.expand_path(crystal), good_path) + File.symlink(File.expand_path(datapath("non_existing_file.txt")), bad_path) + + File.executable?(good_path).should be_true + File.executable?(bad_path).should be_false + end + end end describe "readable?" do @@ -248,7 +274,28 @@ describe "File" do File.readable?(path).should be_false end end + + it "follows symlinks" do + with_tempfile("good_symlink_r.txt", "bad_symlink_r.txt", "unreadable.txt") do |good_path, bad_path, unreadable| + File.write(unreadable, "") + File.chmod(unreadable, 0o222) + pending_if_superuser! + + File.symlink(File.expand_path(datapath("test_file.txt")), good_path) + File.symlink(File.expand_path(unreadable), bad_path) + + File.readable?(good_path).should be_true + File.readable?(bad_path).should be_false + end + end {% end %} + + it "gives false when the symbolic link destination doesn't exist" do + with_tempfile("missing_symlink_r.txt") do |missing_path| + File.symlink(File.expand_path(datapath("non_existing_file.txt")), missing_path) + File.readable?(missing_path).should be_false + end + end end describe "writable?" do @@ -272,6 +319,27 @@ describe "File" do File.writable?(path).should be_false end end + + it "follows symlinks" do + with_tempfile("good_symlink_w.txt", "bad_symlink_w.txt", "readonly.txt") do |good_path, bad_path, readonly| + File.write(readonly, "") + File.chmod(readonly, 0o444) + pending_if_superuser! + + File.symlink(File.expand_path(datapath("test_file.txt")), good_path) + File.symlink(File.expand_path(readonly), bad_path) + + File.writable?(good_path).should be_true + File.writable?(bad_path).should be_false + end + end + + it "gives false when the symbolic link destination doesn't exist" do + with_tempfile("missing_symlink_w.txt") do |missing_path| + File.symlink(File.expand_path(datapath("non_existing_file.txt")), missing_path) + File.writable?(missing_path).should be_false + end + end end describe "file?" do diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 5db0d297ce17..459cb86d977d 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -164,25 +164,28 @@ module Crystal::System::File end def self.exists?(path, *, follow_symlinks = true) - if follow_symlinks - path = realpath?(path) || return false - end - accessible?(path, check_writable: false) + accessible?(path, check_writable: false, follow_symlinks: follow_symlinks) end def self.readable?(path) : Bool - accessible?(path, check_writable: false) + accessible?(path, check_writable: false, follow_symlinks: true) end def self.writable?(path) : Bool - accessible?(path, check_writable: true) + accessible?(path, check_writable: true, follow_symlinks: true) end def self.executable?(path) : Bool + # NOTE: this always follows symlinks: + # https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getbinarytypew#remarks LibC.GetBinaryTypeW(System.to_wstr(path), out result) != 0 end - private def self.accessible?(path, *, check_writable) + private def self.accessible?(path, *, check_writable, follow_symlinks) + if follow_symlinks + path = realpath?(path) || return false + end + attributes = LibC.GetFileAttributesW(System.to_wstr(path)) return false if attributes == LibC::INVALID_FILE_ATTRIBUTES return true if attributes.bits_set?(LibC::FILE_ATTRIBUTE_DIRECTORY) From ae2aa5225f1e5449da3aff5917b041e570ecc682 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 23 Apr 2024 19:43:19 +0800 Subject: [PATCH 1086/1551] Update `WinError#to_errno` (#14515) --- src/winerror.cr | 192 +++++++++++++++++++++++++++++------------------- 1 file changed, 116 insertions(+), 76 deletions(-) diff --git a/src/winerror.cr b/src/winerror.cr index 466855813b74..8da56d7a905e 100644 --- a/src/winerror.cr +++ b/src/winerror.cr @@ -94,82 +94,122 @@ enum WinError : UInt32 # This is only defined for some values. If no transformation is defined for # a specific value, the default result is `Errno::EINVAL`. def to_errno : Errno - # https://github.com/python/cpython/blob/master/PC/generrmap.c - # https://github.com/python/cpython/blob/master/PC/errmap.h - case self - when ERROR_FILE_NOT_FOUND then Errno::ENOENT - when ERROR_PATH_NOT_FOUND then Errno::ENOENT - when ERROR_TOO_MANY_OPEN_FILES then Errno::EMFILE - when ERROR_ACCESS_DENIED then Errno::EACCES - when ERROR_INVALID_HANDLE then Errno::EBADF - when ERROR_ARENA_TRASHED then Errno::ENOMEM - when ERROR_NOT_ENOUGH_MEMORY then Errno::ENOMEM - when ERROR_INVALID_BLOCK then Errno::ENOMEM - when ERROR_BAD_ENVIRONMENT then Errno::E2BIG - when ERROR_BAD_FORMAT then Errno::ENOEXEC - when ERROR_INVALID_DRIVE then Errno::ENOENT - when ERROR_CURRENT_DIRECTORY then Errno::EACCES - when ERROR_NOT_SAME_DEVICE then Errno::EXDEV - when ERROR_NO_MORE_FILES then Errno::ENOENT - when ERROR_WRITE_PROTECT then Errno::EACCES - when ERROR_BAD_UNIT then Errno::EACCES - when ERROR_NOT_READY then Errno::EACCES - when ERROR_BAD_COMMAND then Errno::EACCES - when ERROR_CRC then Errno::EACCES - when ERROR_BAD_LENGTH then Errno::EACCES - when ERROR_SEEK then Errno::EACCES - when ERROR_NOT_DOS_DISK then Errno::EACCES - when ERROR_SECTOR_NOT_FOUND then Errno::EACCES - when ERROR_OUT_OF_PAPER then Errno::EACCES - when ERROR_WRITE_FAULT then Errno::EACCES - when ERROR_READ_FAULT then Errno::EACCES - when ERROR_GEN_FAILURE then Errno::EACCES - when ERROR_SHARING_VIOLATION then Errno::EACCES - when ERROR_LOCK_VIOLATION then Errno::EACCES - when ERROR_WRONG_DISK then Errno::EACCES - when ERROR_SHARING_BUFFER_EXCEEDED then Errno::EACCES - when ERROR_BAD_NETPATH then Errno::ENOENT - when ERROR_NETWORK_ACCESS_DENIED then Errno::EACCES - when ERROR_BAD_NET_NAME then Errno::ENOENT - when ERROR_FILE_EXISTS then Errno::EEXIST - when ERROR_CANNOT_MAKE then Errno::EACCES - when ERROR_FAIL_I24 then Errno::EACCES - when ERROR_NO_PROC_SLOTS then Errno::EAGAIN - when ERROR_DRIVE_LOCKED then Errno::EACCES - when ERROR_BROKEN_PIPE then Errno::EPIPE - when ERROR_DISK_FULL then Errno::ENOSPC - when ERROR_INVALID_TARGET_HANDLE then Errno::EBADF - when ERROR_WAIT_NO_CHILDREN then Errno::ECHILD - when ERROR_CHILD_NOT_COMPLETE then Errno::ECHILD - when ERROR_DIRECT_ACCESS_HANDLE then Errno::EBADF - when ERROR_SEEK_ON_DEVICE then Errno::EACCES - when ERROR_DIR_NOT_EMPTY then Errno::ENOTEMPTY - when ERROR_NOT_LOCKED then Errno::EACCES - when ERROR_BAD_PATHNAME then Errno::ENOENT - when ERROR_MAX_THRDS_REACHED then Errno::EAGAIN - when ERROR_LOCK_FAILED then Errno::EACCES - when ERROR_ALREADY_EXISTS then Errno::EEXIST - when ERROR_INVALID_STARTING_CODESEG then Errno::ENOEXEC - when ERROR_INVALID_STACKSEG then Errno::ENOEXEC - when ERROR_INVALID_MODULETYPE then Errno::ENOEXEC - when ERROR_INVALID_EXE_SIGNATURE then Errno::ENOEXEC - when ERROR_EXE_MARKED_INVALID then Errno::ENOEXEC - when ERROR_BAD_EXE_FORMAT then Errno::ENOEXEC - when ERROR_ITERATED_DATA_EXCEEDS_64k then Errno::ENOEXEC - when ERROR_INVALID_MINALLOCSIZE then Errno::ENOEXEC - when ERROR_DYNLINK_FROM_INVALID_RING then Errno::ENOEXEC - when ERROR_IOPL_NOT_ENABLED then Errno::ENOEXEC - when ERROR_INVALID_SEGDPL then Errno::ENOEXEC - when ERROR_AUTODATASEG_EXCEEDS_64k then Errno::ENOEXEC - when ERROR_RING2SEG_MUST_BE_MOVABLE then Errno::ENOEXEC - when ERROR_RELOC_CHAIN_XEEDS_SEGLIM then Errno::ENOEXEC - when ERROR_INFLOOP_IN_RELOC_CHAIN then Errno::ENOEXEC - when ERROR_FILENAME_EXCED_RANGE then Errno::ENOENT - when ERROR_NESTING_NOT_ALLOWED then Errno::EAGAIN - when ERROR_NO_DATA then Errno::EPIPE - when ERROR_DIRECTORY then Errno::ENOTDIR - when ERROR_NOT_ENOUGH_QUOTA then Errno::ENOMEM - else Errno::EINVAL + # https://github.com/python/cpython/blob/1446024124fb98c3051199760380685f8a2fd127/PC/errmap.h + + # Unwrap FACILITY_WIN32 HRESULT errors. + err = self + err = WinError.new(err.value & 0xFFFF) if err.value & 0xFFFF0000_u32 == 0x80070000_u32 + + case err + when ERROR_FILE_NOT_FOUND, + ERROR_PATH_NOT_FOUND, + ERROR_INVALID_DRIVE, + ERROR_NO_MORE_FILES, + ERROR_BAD_NETPATH, + ERROR_BAD_NET_NAME, + ERROR_BAD_PATHNAME, + ERROR_FILENAME_EXCED_RANGE + Errno::ENOENT + when ERROR_BAD_ENVIRONMENT + Errno::E2BIG + when ERROR_BAD_FORMAT, + ERROR_INVALID_STARTING_CODESEG, + ERROR_INVALID_STACKSEG, + ERROR_INVALID_MODULETYPE, + ERROR_INVALID_EXE_SIGNATURE, + ERROR_EXE_MARKED_INVALID, + ERROR_BAD_EXE_FORMAT, + ERROR_ITERATED_DATA_EXCEEDS_64k, + ERROR_INVALID_MINALLOCSIZE, + ERROR_DYNLINK_FROM_INVALID_RING, + ERROR_IOPL_NOT_ENABLED, + ERROR_INVALID_SEGDPL, + ERROR_AUTODATASEG_EXCEEDS_64k, + ERROR_RING2SEG_MUST_BE_MOVABLE, + ERROR_RELOC_CHAIN_XEEDS_SEGLIM, + ERROR_INFLOOP_IN_RELOC_CHAIN + Errno::ENOEXEC + when ERROR_INVALID_HANDLE, + ERROR_INVALID_TARGET_HANDLE, + ERROR_DIRECT_ACCESS_HANDLE + Errno::EBADF + when ERROR_WAIT_NO_CHILDREN, + ERROR_CHILD_NOT_COMPLETE + Errno::ECHILD + when ERROR_NO_PROC_SLOTS, + ERROR_MAX_THRDS_REACHED, + ERROR_NESTING_NOT_ALLOWED + Errno::EAGAIN + when ERROR_ARENA_TRASHED, + ERROR_NOT_ENOUGH_MEMORY, + ERROR_INVALID_BLOCK, + ERROR_NOT_ENOUGH_QUOTA + Errno::ENOMEM + when ERROR_ACCESS_DENIED, + ERROR_CURRENT_DIRECTORY, + ERROR_WRITE_PROTECT, + ERROR_BAD_UNIT, + ERROR_NOT_READY, + ERROR_BAD_COMMAND, + ERROR_CRC, + ERROR_BAD_LENGTH, + ERROR_SEEK, + ERROR_NOT_DOS_DISK, + ERROR_SECTOR_NOT_FOUND, + ERROR_OUT_OF_PAPER, + ERROR_WRITE_FAULT, + ERROR_READ_FAULT, + ERROR_GEN_FAILURE, + ERROR_SHARING_VIOLATION, + ERROR_LOCK_VIOLATION, + ERROR_WRONG_DISK, + ERROR_SHARING_BUFFER_EXCEEDED, + ERROR_NETWORK_ACCESS_DENIED, + ERROR_CANNOT_MAKE, + ERROR_FAIL_I24, + ERROR_DRIVE_LOCKED, + ERROR_SEEK_ON_DEVICE, + ERROR_NOT_LOCKED, + ERROR_LOCK_FAILED, + WinError.new(35) + Errno::EACCES + when ERROR_FILE_EXISTS, + ERROR_ALREADY_EXISTS + Errno::EEXIST + when ERROR_NOT_SAME_DEVICE + Errno::EXDEV + when ERROR_DIRECTORY + Errno::ENOTDIR + when ERROR_TOO_MANY_OPEN_FILES + Errno::EMFILE + when ERROR_DISK_FULL + Errno::ENOSPC + when ERROR_BROKEN_PIPE, + ERROR_NO_DATA + Errno::EPIPE + when ERROR_DIR_NOT_EMPTY + Errno::ENOTEMPTY + when ERROR_NO_UNICODE_TRANSLATION + Errno::EILSEQ + when WSAEINTR + Errno::EINTR + when WSAEBADF + Errno::EBADF + when WSAEACCES + Errno::EACCES + when WSAEFAULT + Errno::EFAULT + when WSAEINVAL + Errno::EINVAL + when WSAEMFILE + Errno::EMFILE + else + # Winsock error codes (10000-11999) are errno values. + if 10000 <= err.value < 12000 + Errno.new(err.value.to_i32!) + else + Errno::EINVAL + end end end From 5b8662e3c9bd488f7980ebead4c78e2e5a451b4d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 Apr 2024 01:15:55 +0800 Subject: [PATCH 1087/1551] Make `IO::FileDescriptor#tty?` return false for NUL on Windows (#14509) --- spec/std/io/file_descriptor_spec.cr | 15 +++++++++++++++ src/crystal/system/win32/file_descriptor.cr | 2 +- src/crystal/system/win32/socket.cr | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/spec/std/io/file_descriptor_spec.cr b/spec/std/io/file_descriptor_spec.cr index 10efac0eadb4..8433e28689e2 100644 --- a/spec/std/io/file_descriptor_spec.cr +++ b/spec/std/io/file_descriptor_spec.cr @@ -22,6 +22,21 @@ describe IO::FileDescriptor do end end + describe "#tty?" do + it "returns false for null device" do + File.open(File::NULL) do |f| + f.tty?.should be_false + end + end + + it "returns false for standard streams redirected to null device", tags: %w[slow] do + code = %q(print STDIN.tty?, ' ', STDERR.tty?) + compile_source(code) do |binpath| + `#{shell_command %(#{Process.quote(binpath)} < #{File::NULL} 2> #{File::NULL})}`.should eq("false false") + end + end + end + it "closes on finalize" do pipes = [] of IO::FileDescriptor assert_finalizes("fd") do diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 161032cd307b..29ce152725e7 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -145,7 +145,7 @@ module Crystal::System::FileDescriptor end private def system_tty? - LibC.GetFileType(windows_handle) == LibC::FILE_TYPE_CHAR + LibC.GetConsoleMode(windows_handle, out _) != 0 end private def system_reopen(other : IO::FileDescriptor) diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 5a04df9742de..1e11473d72af 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -442,7 +442,7 @@ module Crystal::System::Socket end private def system_tty? - LibC.GetFileType(LibC::HANDLE.new(fd)) == LibC::FILE_TYPE_CHAR + LibC.GetConsoleMode(LibC::HANDLE.new(fd), out _) != 0 end private def unbuffered_read(slice : Bytes) : Int32 From 456f2350b61bf9e42bf737fe33ef2b5520386145 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 23 Apr 2024 19:16:25 +0200 Subject: [PATCH 1088/1551] Fix docs for `Char#ascii_number?` stating wrong minium base (#14521) --- src/char.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/char.cr b/src/char.cr index 761d4187c243..4047db2b4caa 100644 --- a/src/char.cr +++ b/src/char.cr @@ -157,7 +157,7 @@ struct Char # Returns `true` if this char is an ASCII number in specified base. # - # Base can be from 0 to 36 with digits from '0' to '9' and 'a' to 'z' or 'A' to 'Z'. + # Base can be from 2 to 36 with digits from '0' to '9' and 'a' to 'z' or 'A' to 'Z'. # # ``` # '4'.ascii_number? # => true From 6a38569bc8bf5a320a36079f69c582c23ebab189 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 Apr 2024 01:16:40 +0800 Subject: [PATCH 1089/1551] Support short blocks in `#[]` operator call's macro interpolation (#14523) --- spec/compiler/parser/to_s_spec.cr | 6 +++ src/compiler/crystal/syntax/to_s.cr | 67 ++++++++++++++++------------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index 90972d4db297..5235e516d73d 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -232,6 +232,12 @@ describe "ASTNode#to_s" do expect_to_s "1.+ do\nend" expect_to_s "1.[](2) do\nend" expect_to_s "1.[]=" + expect_to_s "1[&.foo]" + expect_to_s "1[&.foo]?" + expect_to_s "1[&.foo] = 2" + expect_to_s "1[2, x: 3, &.foo]" + expect_to_s "1[2, x: 3, &.foo]?" + expect_to_s "1[2, x: 3, &.foo] = 4" expect_to_s "1.+(a: 2)" expect_to_s "1.+(&block)" expect_to_s "1.//(2, a: 3)" diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 6f072880b744..63a23ded4fea 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -351,8 +351,23 @@ module Crystal node_obj = ignore_obj ? nil : node.obj block = node.block + short_block_call = nil + if block + # Check if this is foo &.bar + first_block_arg = block.args.first? + if first_block_arg && block.args.size == 1 && block.args.first.name.starts_with?("__arg") + block_body = block.body + if block_body.is_a?(Call) + block_obj = block_body.obj + if block_obj.is_a?(Var) && block_obj.name == first_block_arg.name + short_block_call = block_body + block = nil + end + end + end + end + need_parens = need_parens(node_obj) - call_args_need_parens = false @str << "::" if node.global? if node_obj.is_a?(ImplicitObj) @@ -365,6 +380,13 @@ module Crystal @str << "[" visit_args(node) + + if short_block_call + @str << ", " if node.args.present? || node.named_args + @str << "&." + visit_call short_block_call, ignore_obj: true + end + if node.name == "[]" @str << "]" else @@ -375,12 +397,19 @@ module Crystal @str << "[" visit_args(node, exclude_last: true) + + if short_block_call + @str << ", " if node.args.size > 1 || node.named_args + @str << "&." + visit_call short_block_call, ignore_obj: true + end + @str << "] = " node.args.last.accept self - elsif node_obj && node.name.in?(UNARY_OPERATORS) && node.args.empty? && !node.named_args && !node.block_arg && !block + elsif node_obj && node.name.in?(UNARY_OPERATORS) && node.args.empty? && !node.named_args && !node.block_arg && !block && !short_block_call @str << node.name in_parenthesis(need_parens, node_obj) - elsif node_obj && !Lexer.ident?(node.name) && node.name != "~" && node.args.size == 1 && !node.named_args && !node.block_arg && !block + elsif node_obj && !Lexer.ident?(node.name) && node.name != "~" && node.args.size == 1 && !node.named_args && !node.block_arg && !block && !short_block_call in_parenthesis(need_parens, node_obj) arg = node.args[0] @@ -399,40 +428,18 @@ module Crystal node.args.join(@str, ", ", &.accept self) else @str << node.name + in_parenthesis(node.has_parentheses? || !node.args.empty? || node.block_arg || node.named_args || short_block_call) do + visit_args(node) - call_args_need_parens = node.has_parentheses? || !node.args.empty? || node.block_arg || node.named_args - - @str << '(' if call_args_need_parens - visit_args(node) - end - end - - if block - # Check if this is foo &.bar - first_block_arg = block.args.first? - if first_block_arg && block.args.size == 1 && block.args.first.name.starts_with?("__arg") - block_body = block.body - if block_body.is_a?(Call) - block_obj = block_body.obj - if block_obj.is_a?(Var) && block_obj.name == first_block_arg.name - if node.args.empty? && !node.named_args - unless call_args_need_parens - @str << '(' - call_args_need_parens = true - end - else - @str << ", " - end + if short_block_call + @str << ", " if node.args.present? || node.named_args @str << "&." - visit_call block_body, ignore_obj: true - block = nil + visit_call short_block_call, ignore_obj: true end end end end - @str << ')' if call_args_need_parens - if block @str << ' ' block.accept self From 1f76cf61bf9e3d39604e8283387917d29994362b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 Apr 2024 06:02:20 +0800 Subject: [PATCH 1090/1551] Add `StringPool#get?` (#14508) --- spec/std/string_pool_spec.cr | 19 ++++++++ src/string_pool.cr | 89 ++++++++++++++++++++++++++++++++---- 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/spec/std/string_pool_spec.cr b/spec/std/string_pool_spec.cr index 4f9889e70fff..4a52e690ad04 100644 --- a/spec/std/string_pool_spec.cr +++ b/spec/std/string_pool_spec.cr @@ -58,6 +58,25 @@ describe StringPool do pool.size.should eq(1) end + it "#get?" do + pool = StringPool.new + str = "foo" + + pool.get?(str).should be_nil + pool.get?(str.to_slice).should be_nil + pool.get?(str.to_unsafe, str.bytesize).should be_nil + pool.get?(IO::Memory.new(str)).should be_nil + + s = pool.get(str) + + pool.get?(str).should be(s) + pool.get?(str.to_slice).should be(s) + pool.get?(str.to_unsafe, str.bytesize).should be(s) + pool.get?(IO::Memory.new(str)).should be(s) + + pool.size.should eq(1) + end + it "puts many" do pool = StringPool.new 10_000.times do |i| diff --git a/src/string_pool.cr b/src/string_pool.cr index 448196bcf622..a4f2553a8d2e 100644 --- a/src/string_pool.cr +++ b/src/string_pool.cr @@ -86,6 +86,24 @@ class StringPool get slice.to_unsafe, slice.size end + # Returns a `String` with the contents of the given *slice*, or `nil` if no + # such string exists in the pool. + # + # ``` + # require "string_pool" + # + # pool = StringPool.new + # bytes = "abc".to_slice + # pool.get?(bytes) # => nil + # pool.empty? # => true + # pool.get(bytes) # => "abc" + # pool.empty? # => false + # pool.get?(bytes) # => "abc" + # ``` + def get?(slice : Bytes) : String? + get? slice.to_unsafe, slice.size + end + # Returns a `String` with the contents given by the pointer *str* of size *len*. # # If a string with those contents was already present in the pool, that one is returned. @@ -100,12 +118,35 @@ class StringPool # ``` def get(str : UInt8*, len) : String hash = hash(str, len) - get(hash, str, len) + get(hash, str, len) do |index| + rehash if @size >= @capacity // 4 * 3 + @size += 1 + entry = String.new(str, len) + @hashes[index] = hash + @values[index] = entry + entry + end end - private def get(hash : UInt64, str : UInt8*, len) - rehash if @size >= @capacity // 4 * 3 + # Returns a `String` with the contents given by the pointer *str* of size + # *len*, or `nil` if no such string exists in the pool. + # + # ``` + # require "string_pool" + # + # pool = StringPool.new + # pool.get?("hey".to_unsafe, 3) # => nil + # pool.empty? # => true + # pool.get("hey".to_unsafe, 3) # => "hey" + # pool.empty? # => false + # pool.get?("hey".to_unsafe, 3) # => "hey" + # ``` + def get?(str : UInt8*, len) : String? + hash = hash(str, len) + get(hash, str, len) { nil } + end + private def get(hash : UInt64, str : UInt8*, len, &) mask = (@capacity - 1).to_u64 index = hash & mask next_probe_offset = 1_u64 @@ -119,11 +160,7 @@ class StringPool next_probe_offset += 1_u64 end - @size += 1 - entry = String.new(str, len) - @hashes[index] = hash - @values[index] = entry - entry + yield index end private def put_on_rehash(hash : UInt64, entry : String) @@ -157,6 +194,24 @@ class StringPool get(str.buffer, str.bytesize) end + # Returns a `String` with the contents of the given `IO::Memory`, or `nil` if + # no such string exists in the pool. + # + # ``` + # require "string_pool" + # + # pool = StringPool.new + # io = IO::Memory.new "crystal" + # pool.get?(io) # => nil + # pool.empty? # => true + # pool.get(io) # => "crystal" + # pool.empty? # => false + # pool.get?(io) # => "crystal" + # ``` + def get?(str : IO::Memory) : String? + get?(str.buffer, str.bytesize) + end + # Returns a `String` with the contents of the given string. # # If a string with those contents was already present in the pool, that one is returned. @@ -175,6 +230,24 @@ class StringPool get(str.to_unsafe, str.bytesize) end + # Returns a `String` with the contents of the given string, or `nil` if no + # such string exists in the pool. + # + # ``` + # require "string_pool" + # + # pool = StringPool.new + # string = "crystal" + # pool.get?(string) # => nil + # pool.empty? # => true + # pool.get(string) # => "crystal" + # pool.empty? # => false + # pool.get?(string) # => "crystal" + # ``` + def get?(str : String) : String? + get?(str.to_unsafe, str.bytesize) + end + # Rebuilds the hash based on the current hash values for each key, # if values of key objects have changed since they were inserted. # From d31fe62253280513c92a29d299a70d31c8423f32 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 Apr 2024 06:02:45 +0800 Subject: [PATCH 1091/1551] Fix formatting for short block inside `#[]` operator call (#14526) --- spec/compiler/formatter/formatter_spec.cr | 13 +++++++++++++ src/compiler/crystal/tools/formatter.cr | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index e87b7d3b010c..e4e76279f4d5 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -2017,6 +2017,19 @@ describe Crystal::Formatter do assert_format "foo &.@bar" assert_format "foo(&.@bar)" + assert_format "foo[&.bar]" + assert_format "foo[1, &.bar]" + assert_format "foo[x: 1, &.bar]" + assert_format "foo[&.bar]?" + assert_format "foo[1, &.bar]?" + assert_format "foo[x: 1, &.bar]?" + assert_format "foo[&.bar] = 1" + assert_format "foo[1, &.bar] = 1" + assert_format "foo[x: 1, &.bar] = 1" + assert_format "foo[&.bar] ||= 1" + assert_format "foo[1, &.bar] ||= 1" + assert_format "foo[x: 1, &.bar] ||= 1" + assert_format "foo.[]" assert_format "foo.[1]" assert_format "foo.[] = 1" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 4f5331ed5adb..a07fd3347f83 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -2599,6 +2599,14 @@ module Crystal found_comment = skip_space_or_newline write_indent if found_comment end + + # foo[&.bar] + if (block = node.block) && @token.type.op_amp? + has_args = !node.args.empty? || node.named_args + write "," if has_args + format_block(block, has_args) + end + write_token :OP_RSQUARE if node.name == "[]?" From 2eed385a74c4e42b0f58f6477980772ccc7d2a1a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 Apr 2024 06:07:25 +0800 Subject: [PATCH 1092/1551] Disallow assignments to calls with parentheses (#14527) --- spec/compiler/parser/parser_spec.cr | 9 +++++++++ src/compiler/crystal/syntax/parser.cr | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index a8c884c58ae6..0a5f5a8ab2c2 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -267,6 +267,15 @@ module Crystal assert_syntax_error "a.b(), c.d = 1" assert_syntax_error "a.b, c.d() = 1" + assert_syntax_error "a() = 1" + assert_syntax_error "a {} = 1" + assert_syntax_error "a.b() = 1" + assert_syntax_error "a.[]() = 1" + assert_syntax_error "a() += 1" + assert_syntax_error "a {} += 1" + assert_syntax_error "a.b() += 1" + assert_syntax_error "a.[]() += 1" + it_parses "def foo\n1\nend", Def.new("foo", body: 1.int32) it_parses "def downto(n)\n1\nend", Def.new("downto", ["n".arg], 1.int32) it_parses "def foo ; 1 ; end", Def.new("foo", body: 1.int32) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index afbf13e75031..df5b151e457a 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -376,6 +376,8 @@ module Crystal break when .op_eq? slash_is_regex! + break unless can_be_assigned?(atomic) + if atomic.is_a?(Call) && atomic.name == "[]" next_token_skip_space_or_newline @@ -385,8 +387,6 @@ module Crystal atomic.args << arg atomic.end_location = arg.end_location else - break unless can_be_assigned?(atomic) - if atomic.is_a?(Path) && (inside_def? || inside_fun? || @is_constant_assignment) raise "dynamic constant assignment. Constants can only be declared at the top level or inside other types." end @@ -6185,7 +6185,7 @@ module Crystal when Var, InstanceVar, ClassVar, Path, Global, Underscore true when Call - (node.obj.nil? && node.args.size == 0 && node.block.nil?) || node.name == "[]" + !node.has_parentheses? && ((node.obj.nil? && node.args.empty? && node.block.nil?) || node.name == "[]") else false end From dcccbc5a57355ab5304761da8d39f952b11ed39d Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 24 Apr 2024 11:04:12 +0200 Subject: [PATCH 1093/1551] Add compiler flags `-Os` and `-Oz` to optimize binary size (#14463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller Co-authored-by: Sijawusz Pur Rahnama --- man/crystal.1 | 4 ++ src/compiler/crystal/codegen/target.cr | 8 +-- src/compiler/crystal/command.cr | 17 ++++-- src/compiler/crystal/compiler.cr | 61 +++++++++++++++----- src/llvm/function_pass_manager.cr | 4 +- src/llvm/lib_llvm/initialization.cr | 4 +- src/llvm/lib_llvm/transforms/pass_builder.cr | 6 ++ src/llvm/module.cr | 2 +- src/llvm/module_pass_manager.cr | 2 +- src/llvm/pass_builder_options.cr | 18 ++++++ src/llvm/pass_manager_builder.cr | 2 +- src/llvm/pass_registry.cr | 25 ++++++-- 12 files changed, 117 insertions(+), 36 deletions(-) diff --git a/man/crystal.1 b/man/crystal.1 index 15fed461c26c..04f183dd11e3 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -496,6 +496,10 @@ Low optimization Middle optimization .It Fl O3 High optimization +.It Fl \!Os +Middle optimization with focus on file size +.It Fl Oz +Middle optimization aggressively focused on file size . .Sh ENVIRONMENT\ VARIABLES .Bl -tag -width "12345678" -compact diff --git a/src/compiler/crystal/codegen/target.cr b/src/compiler/crystal/codegen/target.cr index 59c6b267c118..d487005b77ba 100644 --- a/src/compiler/crystal/codegen/target.cr +++ b/src/compiler/crystal/codegen/target.cr @@ -204,10 +204,10 @@ class Crystal::Codegen::Target end opt_level = case optimization_mode - in .o3? then LLVM::CodeGenOptLevel::Aggressive - in .o2? then LLVM::CodeGenOptLevel::Default - in .o1? then LLVM::CodeGenOptLevel::Less - in .o0? then LLVM::CodeGenOptLevel::None + in .o3? then LLVM::CodeGenOptLevel::Aggressive + in .o2?, .os?, .oz? then LLVM::CodeGenOptLevel::Default + in .o1? then LLVM::CodeGenOptLevel::Less + in .o0? then LLVM::CodeGenOptLevel::None end target = LLVM::Target.from_triple(self.to_s) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 6ae970c04fbf..7644f412bb8e 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -521,9 +521,12 @@ class Crystal::Command opts.on("--release", "Compile in release mode (-O3 --single-module)") do compiler.release! end - opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3") do |level| - optimization_mode = level.to_i?.try { |v| Compiler::OptimizationMode.from_value?(v) } - compiler.optimization_mode = optimization_mode || raise Error.new("Invalid optimization mode: #{level}") + opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3, s, z") do |level| + if mode = Compiler::OptimizationMode.from_level?(level) + compiler.optimization_mode = mode + else + raise Error.new("Invalid optimization mode: O#{level}") + end end end @@ -661,8 +664,12 @@ class Crystal::Command opts.on("--release", "Compile in release mode (-O3 --single-module)") do compiler.release! end - opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3") do |level| - compiler.optimization_mode = Compiler::OptimizationMode.from_value?(level.to_i) || raise Error.new("Unknown optimization mode #{level}") + opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3, s, z") do |level| + if mode = Compiler::OptimizationMode.from_level?(level) + compiler.optimization_mode = mode + else + raise Error.new("Invalid optimization mode: O#{level}") + end end opts.on("--single-module", "Generate a single LLVM module") do compiler.single_module = true diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 16dc0a0e61a9..46bce1db1896 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -102,9 +102,27 @@ module Crystal # enables with --release flag O3 = 3 + # optimize for size, enables most O2 optimizations but aims for smaller + # code size + Os + + # optimize aggressively for size rather than speed + Oz + def suffix ".#{to_s.downcase}" end + + def self.from_level?(level : String) : self? + case level + when "0" then O0 + when "1" then O1 + when "2" then O2 + when "3" then O3 + when "s" then Os + when "z" then Oz + end + end end # Sets the Optimization mode. @@ -674,8 +692,8 @@ module Crystal exit 1 end - {% if LibLLVM::IS_LT_130 %} - protected def optimize(llvm_mod) + {% if LibLLVM::IS_LT_170 %} + private def optimize_with_pass_manager(llvm_mod) fun_pass_manager = llvm_mod.new_function_pass_manager pass_manager_builder.populate fun_pass_manager fun_pass_manager.run llvm_mod @@ -700,6 +718,8 @@ module Crystal registry.initialize_all builder = LLVM::PassManagerBuilder.new + builder.size_level = 0 + case optimization_mode in .o3? builder.opt_level = 3 @@ -712,26 +732,37 @@ module Crystal builder.use_inliner_with_threshold = 150 in .o0? # default behaviour, no optimizations + in .os? + builder.opt_level = 2 + builder.size_level = 1 + builder.use_inliner_with_threshold = 50 + in .oz? + builder.opt_level = 2 + builder.size_level = 2 + builder.use_inliner_with_threshold = 5 end - builder.size_level = 0 - builder end end - {% else %} - protected def optimize(llvm_mod) + {% end %} + + protected def optimize(llvm_mod) + {% if LibLLVM::IS_LT_130 %} + optimize_with_pass_manager(llvm_mod) + {% else %} + {% if LibLLVM::IS_LT_170 %} + # PassBuilder doesn't support Os and Oz before LLVM 17 + if @optimization_mode.os? || @optimization_mode.oz? + return optimize_with_pass_manager(llvm_mod) + end + {% end %} + LLVM::PassBuilderOptions.new do |options| - mode = case @optimization_mode - in .o3? then "default" - in .o2? then "default" - in .o1? then "default" - in .o0? then "default" - end - LLVM.run_passes(llvm_mod, mode, target_machine, options) + LLVM.run_passes(llvm_mod, "default<#{@optimization_mode}>", target_machine, options) end - end - {% end %} + {% end %} + end private def run_linker(linker_name, command, args) print_command(command, args) if verbose? diff --git a/src/llvm/function_pass_manager.cr b/src/llvm/function_pass_manager.cr index 620f24420a2e..d16e549427e8 100644 --- a/src/llvm/function_pass_manager.cr +++ b/src/llvm/function_pass_manager.cr @@ -1,4 +1,4 @@ -{% unless LibLLVM::IS_LT_130 %} +{% unless LibLLVM::IS_LT_170 %} @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] {% end %} class LLVM::FunctionPassManager @@ -34,7 +34,7 @@ class LLVM::FunctionPassManager LibLLVM.dispose_pass_manager(@unwrap) end - {% unless LibLLVM::IS_LT_130 %} + {% unless LibLLVM::IS_LT_170 %} @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] {% end %} struct Runner diff --git a/src/llvm/lib_llvm/initialization.cr b/src/llvm/lib_llvm/initialization.cr index fee9bf1aea4a..916ab6d9a16a 100644 --- a/src/llvm/lib_llvm/initialization.cr +++ b/src/llvm/lib_llvm/initialization.cr @@ -6,11 +6,11 @@ lib LibLLVM fun initialize_core = LLVMInitializeCore(r : PassRegistryRef) fun initialize_transform_utils = LLVMInitializeTransformUtils(r : PassRegistryRef) fun initialize_scalar_opts = LLVMInitializeScalarOpts(r : PassRegistryRef) - fun initialize_obj_c_arc_opts = LLVMInitializeObjCARCOpts(r : PassRegistryRef) + {% if LibLLVM::IS_LT_160 %} fun initialize_obj_c_arc_opts = LLVMInitializeObjCARCOpts(r : PassRegistryRef) {% end %} fun initialize_vectorization = LLVMInitializeVectorization(r : PassRegistryRef) fun initialize_inst_combine = LLVMInitializeInstCombine(r : PassRegistryRef) fun initialize_ipo = LLVMInitializeIPO(r : PassRegistryRef) - fun initialize_instrumentation = LLVMInitializeInstrumentation(r : PassRegistryRef) + {% if LibLLVM::IS_LT_160 %} fun initialize_instrumentation = LLVMInitializeInstrumentation(r : PassRegistryRef) {% end %} fun initialize_analysis = LLVMInitializeAnalysis(r : PassRegistryRef) fun initialize_ipa = LLVMInitializeIPA(r : PassRegistryRef) fun initialize_code_gen = LLVMInitializeCodeGen(r : PassRegistryRef) diff --git a/src/llvm/lib_llvm/transforms/pass_builder.cr b/src/llvm/lib_llvm/transforms/pass_builder.cr index 41a569c4f0a2..38a952c28f3c 100644 --- a/src/llvm/lib_llvm/transforms/pass_builder.cr +++ b/src/llvm/lib_llvm/transforms/pass_builder.cr @@ -10,4 +10,10 @@ lib LibLLVM fun create_pass_builder_options = LLVMCreatePassBuilderOptions : PassBuilderOptionsRef fun dispose_pass_builder_options = LLVMDisposePassBuilderOptions(options : PassBuilderOptionsRef) + {% unless LibLLVM::IS_LT_170 %} + fun pass_builder_options_set_inliner_threshold = LLVMPassBuilderOptionsSetInlinerThreshold(PassBuilderOptionsRef, Int) + {% end %} + fun pass_builder_options_set_loop_unrolling = LLVMPassBuilderOptionsSetLoopUnrolling(PassBuilderOptionsRef, Bool) + fun pass_builder_options_set_loop_vectorization = LLVMPassBuilderOptionsSetLoopVectorization(PassBuilderOptionsRef, Bool) + fun pass_builder_options_set_slp_vectorization = LLVMPassBuilderOptionsSetSLPVectorization(PassBuilderOptionsRef, Bool) end diff --git a/src/llvm/module.cr b/src/llvm/module.cr index e4d9dc110231..f216d485055c 100644 --- a/src/llvm/module.cr +++ b/src/llvm/module.cr @@ -84,7 +84,7 @@ class LLVM::Module self end - {% unless LibLLVM::IS_LT_130 %} + {% unless LibLLVM::IS_LT_170 %} @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] {% end %} def new_function_pass_manager diff --git a/src/llvm/module_pass_manager.cr b/src/llvm/module_pass_manager.cr index c0bcad4e6198..b02fdb21f930 100644 --- a/src/llvm/module_pass_manager.cr +++ b/src/llvm/module_pass_manager.cr @@ -1,4 +1,4 @@ -{% unless LibLLVM::IS_LT_130 %} +{% unless LibLLVM::IS_LT_170 %} @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] {% end %} class LLVM::ModulePassManager diff --git a/src/llvm/pass_builder_options.cr b/src/llvm/pass_builder_options.cr index 3134acb0b93a..82104b03bd8a 100644 --- a/src/llvm/pass_builder_options.cr +++ b/src/llvm/pass_builder_options.cr @@ -25,4 +25,22 @@ class LLVM::PassBuilderOptions LibLLVM.dispose_pass_builder_options(self) end + + {% unless LibLLVM::IS_LT_170 %} + def set_inliner_threshold(threshold : Int) + LibLLVM.pass_builder_options_set_inliner_threshold(self, threshold) + end + {% end %} + + def set_loop_unrolling(enabled : Bool) + LibLLVM.pass_builder_options_set_loop_unrolling(self, enabled) + end + + def set_loop_vectorization(enabled : Bool) + LibLLVM.pass_builder_options_set_loop_vectorization(self, enabled) + end + + def set_slp_vectorization(enabled : Bool) + LibLLVM.pass_builder_options_set_slp_vectorization(self, enabled) + end end diff --git a/src/llvm/pass_manager_builder.cr b/src/llvm/pass_manager_builder.cr index 148cd19fca35..46e716e89b2f 100644 --- a/src/llvm/pass_manager_builder.cr +++ b/src/llvm/pass_manager_builder.cr @@ -1,4 +1,4 @@ -{% unless LibLLVM::IS_LT_130 %} +{% unless LibLLVM::IS_LT_170 %} @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] {% end %} class LLVM::PassManagerBuilder diff --git a/src/llvm/pass_registry.cr b/src/llvm/pass_registry.cr index 7a1377c35716..652cfaed0442 100644 --- a/src/llvm/pass_registry.cr +++ b/src/llvm/pass_registry.cr @@ -1,4 +1,4 @@ -{% unless LibLLVM::IS_LT_130 %} +{% unless LibLLVM::IS_LT_170 %} @[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")] {% end %} struct LLVM::PassRegistry @@ -9,17 +9,32 @@ struct LLVM::PassRegistry def initialize(@unwrap : LibLLVM::PassRegistryRef) end - Inits = %w(core transform_utils scalar_opts obj_c_arc_opts vectorization inst_combine ipo instrumentation analysis ipa code_gen target) + {% begin %} + Inits = %w[ + initialize_core + initialize_transform_utils + initialize_scalar_opts + {% if LibLLVM::IS_LT_160 %} initialize_obj_c_arc_opts {% end %} + initialize_vectorization + initialize_inst_combine + initialize_ipo + {% if LibLLVM::IS_LT_160 %} initialize_instrumentation {% end %} + initialize_analysis + initialize_ipa + initialize_code_gen + initialize_target + ] + {% end %} {% for name in Inits %} - def initialize_{{name.id}} - LibLLVM.initialize_{{name.id}} self + def {{name.id}} + LibLLVM.{{name.id}} self end {% end %} def initialize_all {% for name in Inits %} - initialize_{{name.id}} + {{name.id}} {% end %} end From 82a208c7be45fe0b130557de3deed35734fb2d8b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 24 Apr 2024 17:04:30 +0800 Subject: [PATCH 1094/1551] Fix blockless `IO::FileDescriptor` echo and raw mode methods (#14529) --- src/crystal/system/file_descriptor.cr | 6 ++++ src/crystal/system/unix/file_descriptor.cr | 40 +++++++++++++-------- src/crystal/system/wasi/file_descriptor.cr | 8 +++++ src/crystal/system/win32/file_descriptor.cr | 39 ++++++++++++++------ src/io/console.cr | 8 ++--- 5 files changed, 72 insertions(+), 29 deletions(-) diff --git a/src/crystal/system/file_descriptor.cr b/src/crystal/system/file_descriptor.cr index 4784bdcf3a62..db50f1f3fd73 100644 --- a/src/crystal/system/file_descriptor.cr +++ b/src/crystal/system/file_descriptor.cr @@ -1,9 +1,15 @@ # :nodoc: module Crystal::System::FileDescriptor + # Enables input echoing if *enable* is true, disables it otherwise. + # private def system_echo(enable : Bool) + # For the duration of the block, enables input echoing if *enable* is true, # disables it otherwise. # private def system_echo(enable : Bool, & : ->) + # Enables raw mode if *enable* is true, enables cooked mode otherwise. + # private def system_raw(enable : Bool) + # For the duration of the block, enables raw mode if *enable* is true, enables # cooked mode otherwise. # private def system_raw(enable : Bool, & : ->) diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index c0404e7652a2..2a2f25951d6c 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -257,29 +257,39 @@ module Crystal::System::FileDescriptor io end + private def system_echo(enable : Bool, mode = nil) + new_mode = mode || FileDescriptor.tcgetattr(fd) + flags = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL + new_mode.c_lflag = enable ? (new_mode.c_lflag | flags) : (new_mode.c_lflag & ~flags) + if FileDescriptor.tcsetattr(fd, LibC::TCSANOW, pointerof(new_mode)) != 0 + raise IO::Error.from_errno("tcsetattr") + end + end + private def system_echo(enable : Bool, & : ->) system_console_mode do |mode| - flags = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL - mode.c_lflag = enable ? (mode.c_lflag | flags) : (mode.c_lflag & ~flags) - if FileDescriptor.tcsetattr(fd, LibC::TCSANOW, pointerof(mode)) != 0 - raise IO::Error.from_errno("tcsetattr") - end + system_echo(enable, mode) yield end end + private def system_raw(enable : Bool, mode = nil) + new_mode = mode || FileDescriptor.tcgetattr(fd) + if enable + new_mode = FileDescriptor.cfmakeraw(new_mode) + else + new_mode.c_iflag |= LibC::BRKINT | LibC::ISTRIP | LibC::ICRNL | LibC::IXON + new_mode.c_oflag |= LibC::OPOST + new_mode.c_lflag |= LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL | LibC::ICANON | LibC::ISIG | LibC::IEXTEN + end + if FileDescriptor.tcsetattr(fd, LibC::TCSANOW, pointerof(new_mode)) != 0 + raise IO::Error.from_errno("tcsetattr") + end + end + private def system_raw(enable : Bool, & : ->) system_console_mode do |mode| - if enable - mode = FileDescriptor.cfmakeraw(mode) - else - mode.c_iflag |= LibC::BRKINT | LibC::ISTRIP | LibC::ICRNL | LibC::IXON - mode.c_oflag |= LibC::OPOST - mode.c_lflag |= LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL | LibC::ICANON | LibC::ISIG | LibC::IEXTEN - end - if FileDescriptor.tcsetattr(fd, LibC::TCSANOW, pointerof(mode)) != 0 - raise IO::Error.from_errno("tcsetattr") - end + system_raw(enable, mode) yield end end diff --git a/src/crystal/system/wasi/file_descriptor.cr b/src/crystal/system/wasi/file_descriptor.cr index ffd15817a104..890e6363605c 100644 --- a/src/crystal/system/wasi/file_descriptor.cr +++ b/src/crystal/system/wasi/file_descriptor.cr @@ -40,10 +40,18 @@ module Crystal::System::FileDescriptor raise NotImplementedError.new "Crystal::System::File#flock" end + private def system_echo(enable : Bool) + raise NotImplementedError.new "Crystal::System::FileDescriptor#system_echo" + end + private def system_echo(enable : Bool, & : ->) raise NotImplementedError.new "Crystal::System::FileDescriptor#system_echo" end + private def system_raw(enable : Bool) + raise NotImplementedError.new "Crystal::System::FileDescriptor#system_raw" + end + private def system_raw(enable : Bool, & : ->) raise NotImplementedError.new "Crystal::System::FileDescriptor#system_raw" end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 29ce152725e7..29ec1e4b6bd6 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -301,41 +301,60 @@ module Crystal::System::FileDescriptor io end + private def system_echo(enable : Bool) + system_console_mode(enable, LibC::ENABLE_ECHO_INPUT, 0) + end + private def system_echo(enable : Bool, & : ->) system_console_mode(enable, LibC::ENABLE_ECHO_INPUT, 0) { yield } end + private def system_raw(enable : Bool) + system_console_mode(enable, LibC::ENABLE_VIRTUAL_TERMINAL_INPUT, LibC::ENABLE_PROCESSED_INPUT | LibC::ENABLE_LINE_INPUT | LibC::ENABLE_ECHO_INPUT) + end + private def system_raw(enable : Bool, & : ->) system_console_mode(enable, LibC::ENABLE_VIRTUAL_TERMINAL_INPUT, LibC::ENABLE_PROCESSED_INPUT | LibC::ENABLE_LINE_INPUT | LibC::ENABLE_ECHO_INPUT) { yield } end @[AlwaysInline] - private def system_console_mode(enable, on_mask, off_mask, &) + private def system_console_mode(enable, on_mask, off_mask, old_mode = nil) windows_handle = self.windows_handle - if LibC.GetConsoleMode(windows_handle, out old_mode) == 0 - raise IO::Error.from_winerror("GetConsoleMode") + unless old_mode + if LibC.GetConsoleMode(windows_handle, out mode) == 0 + raise IO::Error.from_winerror("GetConsoleMode") + end + old_mode = mode end old_on_bits = old_mode & on_mask old_off_bits = old_mode & off_mask if enable - return yield if old_on_bits == on_mask && old_off_bits == 0 + return if old_on_bits == on_mask && old_off_bits == 0 new_mode = (old_mode | on_mask) & ~off_mask else - return yield if old_on_bits == 0 && old_off_bits == off_mask + return if old_on_bits == 0 && old_off_bits == off_mask new_mode = (old_mode | off_mask) & ~on_mask end if LibC.SetConsoleMode(windows_handle, new_mode) == 0 raise IO::Error.from_winerror("SetConsoleMode") end + end + + @[AlwaysInline] + private def system_console_mode(enable, on_mask, off_mask, &) + windows_handle = self.windows_handle + if LibC.GetConsoleMode(windows_handle, out old_mode) == 0 + raise IO::Error.from_winerror("GetConsoleMode") + end - ret = yield - if LibC.GetConsoleMode(windows_handle, pointerof(old_mode)) != 0 - new_mode = (old_mode & ~on_mask & ~off_mask) | old_on_bits | old_off_bits - LibC.SetConsoleMode(windows_handle, new_mode) + begin + system_console_mode(enable, on_mask, off_mask, old_mode) + yield + ensure + LibC.SetConsoleMode(windows_handle, old_mode) end - ret end end diff --git a/src/io/console.cr b/src/io/console.cr index 5ac51b497c29..f6e2a11ee033 100644 --- a/src/io/console.cr +++ b/src/io/console.cr @@ -31,7 +31,7 @@ class IO::FileDescriptor < IO # # Raises `IO::Error` if this `IO` is not a terminal device. def noecho! : Nil - system_echo(false) { return } + system_echo(false) end # Enables character echoing on this `IO`. @@ -40,7 +40,7 @@ class IO::FileDescriptor < IO # # Raises `IO::Error` if this `IO` is not a terminal device. def echo! : Nil - system_echo(true) { return } + system_echo(true) end # Yields `self` to the given block, enables character processing for the @@ -75,7 +75,7 @@ class IO::FileDescriptor < IO # # Raises `IO::Error` if this `IO` is not a terminal device. def cooked! : Nil - system_raw(false) { return } + system_raw(false) end # Enables raw mode on this `IO`. @@ -86,7 +86,7 @@ class IO::FileDescriptor < IO # # Raises `IO::Error` if this `IO` is not a terminal device. def raw! : Nil - system_raw(true) { return } + system_raw(true) end @[Deprecated] From a726bf7a34769c90c41ed840e228289b53da049e Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sun, 28 Apr 2024 21:29:47 +0200 Subject: [PATCH 1095/1551] Add `Thread.new` yields itself (#14543) --- src/crystal/system/thread.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index b40a7dceb32b..dd93714487b9 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -58,7 +58,7 @@ class Thread end # Creates and starts a new system thread. - def initialize(@name : String? = nil, &@func : ->) + def initialize(@name : String? = nil, &@func : Thread ->) @system_handle = uninitialized Crystal::System::Thread::Handle init_handle end @@ -66,7 +66,7 @@ class Thread # Used once to initialize the thread object representing the main thread of # the process (that already exists). def initialize - @func = ->{} + @func = ->(t : Thread) {} @system_handle = Crystal::System::Thread.current_handle @main_fiber = Fiber.new(stack_address, self) @@ -127,7 +127,7 @@ class Thread end begin - @func.call + @func.call(self) rescue ex @exception = ex ensure From 5cee8a988d13437863d227a7e99305a24765f941 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sun, 28 Apr 2024 21:30:20 +0200 Subject: [PATCH 1096/1551] Add support for `Atomic(Bool)` (#14532) --- spec/std/atomic_spec.cr | 71 +++++++++++++++++++++++++++- src/atomic.cr | 100 +++++++++++++++++++++++++++++++--------- 2 files changed, 147 insertions(+), 24 deletions(-) diff --git a/spec/std/atomic_spec.cr b/spec/std/atomic_spec.cr index e18b3b234a15..963e664df681 100644 --- a/spec/std/atomic_spec.cr +++ b/spec/std/atomic_spec.cr @@ -14,8 +14,24 @@ enum AtomicEnumFlags Three end +private struct AtomicBooleans + @one = Atomic(Bool).new(false) + @two = Atomic(Bool).new(false) + @three = Atomic(Bool).new(false) +end + describe Atomic do describe "#compare_and_set" do + it "with bool" do + atomic = Atomic.new(true) + + atomic.compare_and_set(false, true).should eq({true, false}) + atomic.get.should eq(true) + + atomic.compare_and_set(true, false).should eq({true, true}) + atomic.get.should eq(false) + end + it "with integer" do atomic = Atomic.new(1) @@ -240,6 +256,12 @@ describe Atomic do end describe "#set" do + it "with bool" do + atomic = Atomic.new(false) + atomic.set(true).should eq(true) + atomic.get.should eq(true) + end + it "with integer" do atomic = Atomic.new(1) atomic.set(2).should eq(2) @@ -272,10 +294,20 @@ describe Atomic do it "#lazy_set" do atomic = Atomic.new(1) atomic.lazy_set(2).should eq(2) - atomic.get.should eq(2) + atomic.lazy_get.should eq(2) + + bool = Atomic.new(true) + bool.lazy_set(false).should eq(false) + bool.lazy_get.should eq(false) end describe "#swap" do + it "with bool" do + atomic = Atomic.new(true) + atomic.swap(false).should eq(true) + atomic.get.should eq(false) + end + it "with integer" do atomic = Atomic.new(1) atomic.swap(2).should eq(1) @@ -322,6 +354,43 @@ describe Atomic do atomic.get.should eq(2) end end + + describe "atomic bool" do + it "sizeof" do + sizeof(Atomic(Bool)).should eq(1) + sizeof(AtomicBooleans).should eq(3) + end + + it "gets and sets" do + booleans = AtomicBooleans.new + + booleans.@one.get.should eq(false) + booleans.@two.get.should eq(false) + booleans.@three.get.should eq(false) + + booleans.@two.set(true) + booleans.@one.get.should eq(false) + booleans.@two.get.should eq(true) + booleans.@three.get.should eq(false) + + booleans.@one.set(true) + booleans.@three.set(true) + booleans.@one.get.should eq(true) + booleans.@two.get.should eq(true) + booleans.@three.get.should eq(true) + + booleans.@one.set(false) + booleans.@three.set(false) + booleans.@one.get.should eq(false) + booleans.@two.get.should eq(true) + booleans.@three.get.should eq(false) + + booleans.@two.set(false) + booleans.@one.get.should eq(false) + booleans.@two.get.should eq(false) + booleans.@three.get.should eq(false) + end + end end describe Atomic::Flag do diff --git a/src/atomic.cr b/src/atomic.cr index de8c308df971..db6cd7e88b84 100644 --- a/src/atomic.cr +++ b/src/atomic.cr @@ -43,8 +43,8 @@ struct Atomic(T) # Creates an Atomic with the given initial value. def initialize(@value : T) - {% if !T.union? && (T == Char || T < Int::Primitive || T < Enum) %} - # Support integer types, enum types, or char (because it's represented as an integer) + {% if !T.union? && (T == Bool || T == Char || T < Int::Primitive || T < Enum) %} + # Support integer types, enum types, bool or char (because it's represented as an integer) {% elsif T < Pointer %} # Support pointer types {% elsif T.union_types.all? { |t| t == Nil || t < Reference } && T != Nil %} @@ -71,7 +71,7 @@ struct Atomic(T) # atomic.get # => 3 # ``` def compare_and_set(cmp : T, new : T) : {T, Bool} - Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :sequentially_consistent) + cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :sequentially_consistent, :sequentially_consistent) end # Compares this atomic's value with *cmp* using explicit memory orderings: @@ -93,25 +93,25 @@ struct Atomic(T) def compare_and_set(cmp : T, new : T, success_ordering : Ordering, failure_ordering : Ordering) : {T, Bool} case {success_ordering, failure_ordering} when {.relaxed?, .relaxed?} - Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :monotonic, :monotonic) + cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :monotonic, :monotonic) when {.acquire?, .relaxed?} - Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire, :monotonic) + cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :acquire, :monotonic) when {.acquire?, .acquire?} - Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire, :acquire) + cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :acquire, :acquire) when {.release?, .relaxed?} - Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :release, :monotonic) + cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :release, :monotonic) when {.release?, .acquire?} - Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :release, :acquire) + cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :release, :acquire) when {.acquire_release?, .relaxed?} - Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire_release, :monotonic) + cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :acquire_release, :monotonic) when {.acquire_release?, .acquire?} - Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire_release, :acquire) + cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :acquire_release, :acquire) when {.sequentially_consistent?, .relaxed?} - Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :monotonic) + cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :sequentially_consistent, :monotonic) when {.sequentially_consistent?, .acquire?} - Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :acquire) + cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :sequentially_consistent, :acquire) when {.sequentially_consistent?, .sequentially_consistent?} - Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :sequentially_consistent) + cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :sequentially_consistent, :sequentially_consistent) else if failure_ordering.release? || failure_ordering.acquire_release? raise ArgumentError.new("Failure ordering cannot include release semantics") @@ -132,6 +132,7 @@ struct Atomic(T) def add(value : T, ordering : Ordering = :sequentially_consistent) : T check_pointer_type check_reference_type + check_bool_type atomicrmw(:add, pointerof(@value), value, ordering) end @@ -147,6 +148,7 @@ struct Atomic(T) def sub(value : T, ordering : Ordering = :sequentially_consistent) : T check_pointer_type check_reference_type + check_bool_type atomicrmw(:sub, pointerof(@value), value, ordering) end @@ -162,6 +164,7 @@ struct Atomic(T) def and(value : T, ordering : Ordering = :sequentially_consistent) : T check_pointer_type check_reference_type + check_bool_type atomicrmw(:and, pointerof(@value), value, ordering) end @@ -177,6 +180,7 @@ struct Atomic(T) def nand(value : T, ordering : Ordering = :sequentially_consistent) : T check_pointer_type check_reference_type + check_bool_type atomicrmw(:nand, pointerof(@value), value, ordering) end @@ -192,6 +196,7 @@ struct Atomic(T) def or(value : T, ordering : Ordering = :sequentially_consistent) : T check_pointer_type check_reference_type + check_bool_type atomicrmw(:or, pointerof(@value), value, ordering) end @@ -207,6 +212,7 @@ struct Atomic(T) def xor(value : T, ordering : Ordering = :sequentially_consistent) : T check_pointer_type check_reference_type + check_bool_type atomicrmw(:xor, pointerof(@value), value, ordering) end @@ -225,6 +231,7 @@ struct Atomic(T) # ``` def max(value : T, ordering : Ordering = :sequentially_consistent) check_reference_type + check_bool_type {% if T < Enum %} if @value.value.is_a?(Int::Signed) atomicrmw(:max, pointerof(@value), value, ordering) @@ -255,6 +262,7 @@ struct Atomic(T) # ``` def min(value : T, ordering : Ordering = :sequentially_consistent) check_reference_type + check_bool_type {% if T < Enum %} if @value.value.is_a?(Int::Signed) atomicrmw(:min, pointerof(@value), value, ordering) @@ -284,7 +292,7 @@ struct Atomic(T) address = atomicrmw(:xchg, pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(value.as(Void*).address), ordering) Pointer(T).new(address).as(T) {% else %} - atomicrmw(:xchg, pointerof(@value), value, ordering) + cast_from atomicrmw(:xchg, as_pointer, cast_to(value), ordering) {% end %} end @@ -298,11 +306,11 @@ struct Atomic(T) def set(value : T, ordering : Ordering = :sequentially_consistent) : T case ordering in .relaxed? - Ops.store(pointerof(@value), value.as(T), :monotonic, true) + Ops.store(as_pointer, cast_to(value), :monotonic, true) in .release? - Ops.store(pointerof(@value), value.as(T), :release, true) + Ops.store(as_pointer, cast_to(value), :release, true) in .sequentially_consistent? - Ops.store(pointerof(@value), value.as(T), :sequentially_consistent, true) + Ops.store(as_pointer, cast_to(value), :sequentially_consistent, true) in .acquire?, .acquire_release? raise ArgumentError.new("Atomic store cannot have acquire semantic") end @@ -325,11 +333,11 @@ struct Atomic(T) def get(ordering : Ordering = :sequentially_consistent) : T case ordering in .relaxed? - Ops.load(pointerof(@value), :monotonic, true) + cast_from Ops.load(as_pointer, :monotonic, true) in .acquire? - Ops.load(pointerof(@value), :acquire, true) + cast_from Ops.load(as_pointer, :acquire, true) in .sequentially_consistent? - Ops.load(pointerof(@value), :sequentially_consistent, true) + cast_from Ops.load(as_pointer, :sequentially_consistent, true) in .release?, .acquire_release? raise ArgumentError.new("Atomic load cannot have release semantic") end @@ -342,6 +350,12 @@ struct Atomic(T) @value end + private macro check_bool_type + {% if T == Bool %} + {% raise "Cannot call `#{@type}##{@def.name}` for `#{T}` type" %} + {% end %} + end + private macro check_pointer_type {% if T < Pointer %} {% raise "Cannot call `#{@type}##{@def.name}` as `#{T}` is a pointer type" %} @@ -393,6 +407,46 @@ struct Atomic(T) def self.store(ptr : T*, value : T, ordering : LLVM::AtomicOrdering, volatile : Bool) : Nil forall T end end + + @[AlwaysInline] + private def as_pointer + {% if T == Bool %} + # assumes that a bool sizeof/alignof is 1 byte (and that a struct wrapping + # a single boolean ivar has a sizeof/alignof of at least 1 byte, too) so + # there is enough padding, and we can safely cast the int1 representation + # of a bool as an int8 + pointerof(@value).as(Int8*) + {% else %} + pointerof(@value) + {% end %} + end + + @[AlwaysInline] + private def cast_to(value) + {% if T == Bool %} + value.unsafe_as(Int8) + {% else %} + value.as(T) + {% end %} + end + + @[AlwaysInline] + private def cast_from(value : Tuple) + {% if T == Bool %} + {value[0].unsafe_as(Bool), value[1]} + {% else %} + value + {% end %} + end + + @[AlwaysInline] + private def cast_from(value) + {% if T == Bool %} + value.unsafe_as(Bool) + {% else %} + value + {% end %} + end end # An atomic flag, that can be set or not. @@ -410,13 +464,13 @@ end # ``` struct Atomic::Flag def initialize - @value = 0_u8 + @value = Atomic(Bool).new(false) end # Atomically tries to set the flag. Only succeeds and returns `true` if the # flag wasn't previously set; returns `false` otherwise. def test_and_set : Bool - ret = Atomic::Ops.atomicrmw(:xchg, pointerof(@value), 1_u8, :sequentially_consistent, false) == 0_u8 + ret = @value.swap(true, :sequentially_consistent) == false {% if flag?(:arm) %} Atomic::Ops.fence(:sequentially_consistent, false) if ret {% end %} @@ -428,6 +482,6 @@ struct Atomic::Flag {% if flag?(:arm) %} Atomic::Ops.fence(:sequentially_consistent, false) {% end %} - Atomic::Ops.store(pointerof(@value), 0_u8, :sequentially_consistent, true) + @value.set(false, :sequentially_consistent) end end From 6d918dfaa1c4bf0cb5c4749cbf7cb84c5444d9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 29 Apr 2024 09:36:40 +0200 Subject: [PATCH 1097/1551] Enhance documentation for regex options `NO_UTF_CHECK` (#14542) --- src/regex.cr | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/regex.cr b/src/regex.cr index b1532decee64..2b593397528b 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -194,6 +194,15 @@ require "./regex/match_data" # # PCRE2 supports other encodings, but Crystal strings are UTF-8 only, so Crystal # regular expressions are also UTF-8 only (by default). +# Crystal strings are expected to contain only valid UTF-8 encodings, but that's +# not guaranteed. There's a chance that a string *can* contain invalid bytes. +# Especially data read from external sources must not be trusted to be valid encoding. +# The regex engine demands valid UTF-8, so it checks the encoding for every +# match. This can be unnecessary if the string is already validated (for example +# via `String#valid_encoding?` or because it has already been used in a previous +# regex match). +# If that's the case, it's profitable to skip UTF-8 validation via `MatchOptions::NO_UTF_CHECK` +# (or `CompileOptions::NO_UTF_CHECK` for the pattern). # # PCRE2 optionally permits named capture groups (named subpatterns) to not be # unique. Crystal exposes the name table of a `Regex` as a @@ -263,6 +272,31 @@ class Regex ENDANCHORED = 0x8000_0000 # Do not check the pattern for valid UTF encoding. + # + # This option is potentially dangerous and must only be used when the + # pattern is guaranteed to be valid (e.g. `String#valid_encoding?`). + # Failing to do so can lead to undefined behaviour in the regex library + # and may crash the entire process. + # + # NOTE: `String` is *supposed* to be valid UTF-8, but this is not guaranteed or + # enforced. Especially data originating from external sources should not be + # trusted. + # + # UTF validation is comparatively expensive, so skipping it can produce a + # significant performance improvement. + # + # ``` + # pattern = "fo+" + # if pattern.valid_encoding? + # regex = Regex.new(pattern, options: Regex::CompileOptions::NO_UTF_CHECK) + # regex.match(foo) + # else + # raise "Invalid UTF in regex pattern" + # end + # ``` + # + # The standard library implicitly applies this option when it can be sure + # about the patterns's validity (e.g. on repeated matches in `String#gsub`). NO_UTF_CHECK = NO_UTF8_CHECK # Enable matching against subjects containing invalid UTF bytes. @@ -311,6 +345,30 @@ class Regex # Do not check subject for valid UTF encoding. # + # This option is potentially dangerous and must only be used when the + # subject is guaranteed to be valid (e.g. `String#valid_encoding?`). + # Failing to do so can lead to undefined behaviour in the regex library + # and may crash the entire process. + # + # NOTE: `String` is *supposed* to be valid UTF-8, but this is not guaranteed or + # enforced. Especially data originating from external sources should not be + # trusted. + # + # UTF validation is comparatively expensive, so skipping it can produce a + # significant performance improvement. + # + # ``` + # subject = "foo" + # if subject.valid_encoding? + # /foo/.match(subject, options: Regex::MatchOptions::NO_UTF_CHECK) + # else + # raise "Invalid UTF in regex subject" + # end + # ``` + # + # A good use case is when the same subject is matched multiple times, UTF + # validation only needs to happen once. + # # This option has no effect if the pattern was compiled with # `CompileOptions::MATCH_INVALID_UTF` when using PCRE2 10.34+. NO_UTF_CHECK From 8b9e299362d6028c8aa51f7093200683dc9028e0 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 29 Apr 2024 09:36:55 +0200 Subject: [PATCH 1098/1551] Refactor `JSON::Any#size` to use two branches instead of three (#14533) --- src/json/any.cr | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/json/any.cr b/src/json/any.cr index 057fd54e737d..61a4118ee932 100644 --- a/src/json/any.cr +++ b/src/json/any.cr @@ -73,9 +73,7 @@ struct JSON::Any # Raises if the underlying value is not an `Array` or `Hash`. def size : Int case object = @raw - when Array - object.size - when Hash + when Array, Hash object.size else raise "Expected Array or Hash for #size, not #{object.class}" From 0f35ff73a4e001e5c7e0ad423d2c2534da31a48a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 2 May 2024 03:48:38 +0800 Subject: [PATCH 1099/1551] Remove target path's forward slashes in `File.symlink` on Windows (#14522) --- spec/std/file_spec.cr | 9 +++++++++ src/crystal/system/win32/file.cr | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index fe91d1071b52..44f947997b34 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -418,6 +418,15 @@ describe "File" do File.same?(in_path, out_path, follow_symlinks: true).should be_true end end + + it "works if destination contains forward slashes (#14520)" do + with_tempfile("test_slash_dest.txt", "test_slash_link.txt") do |dest_path, link_path| + File.write(dest_path, "hello") + File.symlink("./test_slash_dest.txt", link_path) + File.same?(dest_path, link_path, follow_symlinks: true).should be_true + File.read(link_path).should eq("hello") + end + end end describe "symlink?" do diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 459cb86d977d..83d6afcf18ca 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -319,7 +319,7 @@ module Crystal::System::File end def self.symlink(old_path : String, new_path : String) : Nil - win_old_path = System.to_wstr(old_path) + win_old_path = System.to_wstr(old_path.gsub('/', '\\')) win_new_path = System.to_wstr(new_path) info = info?(old_path, true) flags = info.try(&.type.directory?) ? LibC::SYMBOLIC_LINK_FLAG_DIRECTORY : 0 From 89f2e43e3a0672a40dd291364f41f2c1771403e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 1 May 2024 21:48:52 +0200 Subject: [PATCH 1100/1551] Refactor use `IO#read_byte` instead of `#read_char` in `HTTP::ChunkedContent` (#14548) --- src/http/content.cr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/http/content.cr b/src/http/content.cr index 9bec0e66abda..828015549d3b 100644 --- a/src/http/content.cr +++ b/src/http/content.cr @@ -192,11 +192,11 @@ module HTTP end private def read_crlf - char = @io.read_char - if char == '\r' - char = @io.read_char + char = @io.read_byte + if char === '\r' + char = @io.read_byte end - if char != '\n' + unless char === '\n' raise IO::Error.new("Invalid HTTP chunked content: expected CRLF") end end From 7ecb9687dc22fecff2b31ab7024f2341106525e1 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Thu, 2 May 2024 20:38:18 +0200 Subject: [PATCH 1101/1551] Add compiler support for AVR architecture (Arduino) (#14393) Co-authored-by: Sijawusz Pur Rahnama --- spec/std/llvm/avr_spec.cr | 198 +++++++++++++++++++++++++ src/compiler/crystal/codegen/target.cr | 22 ++- src/compiler/crystal/compiler.cr | 5 +- src/compiler/crystal/semantic/flags.cr | 4 + src/intrinsics.cr | 74 +++++---- src/llvm.cr | 16 ++ src/llvm/abi/avr.cr | 82 ++++++++++ src/llvm/lib_llvm/target.cr | 6 + src/llvm/lib_llvm/target_machine.cr | 1 + src/llvm/target_data.cr | 2 +- src/llvm/target_machine.cr | 7 + 11 files changed, 382 insertions(+), 35 deletions(-) create mode 100644 spec/std/llvm/avr_spec.cr create mode 100644 src/llvm/abi/avr.cr diff --git a/spec/std/llvm/avr_spec.cr b/spec/std/llvm/avr_spec.cr new file mode 100644 index 000000000000..3c23c9bbed6e --- /dev/null +++ b/spec/std/llvm/avr_spec.cr @@ -0,0 +1,198 @@ +require "spec" + +{% if flag?(:interpreted) && !flag?(:win32) %} + # TODO: figure out how to link against libstdc++ in interpreted code (#14398) + pending LLVM::ABI::AVR + {% skip_file %} +{% end %} + +require "llvm" + +{% if LibLLVM::BUILT_TARGETS.includes?(:avr) %} + LLVM.init_avr +{% end %} + +private def abi + triple = "avr-unknown-unknown-atmega328p" + target = LLVM::Target.from_triple(triple) + machine = target.create_target_machine(triple) + machine.enable_global_isel = false + LLVM::ABI::AVR.new(machine) +end + +private def test(msg, &block : LLVM::ABI, LLVM::Context ->) + it msg do + abi = abi() + ctx = LLVM::Context.new + block.call(abi, ctx) + end +end + +class LLVM::ABI + describe AVR do + {% if LibLLVM::BUILT_TARGETS.includes?(:avr) %} + describe "align" do + test "for integer" do |abi, ctx| + abi.align(ctx.int1).should be_a(::Int32) + abi.align(ctx.int1).should eq(1) + abi.align(ctx.int8).should eq(1) + abi.align(ctx.int16).should eq(1) + abi.align(ctx.int32).should eq(1) + abi.align(ctx.int64).should eq(1) + end + + test "for pointer" do |abi, ctx| + abi.align(ctx.int8.pointer).should eq(1) + end + + test "for float" do |abi, ctx| + abi.align(ctx.float).should eq(1) + end + + test "for double" do |abi, ctx| + abi.align(ctx.double).should eq(1) + end + + test "for struct" do |abi, ctx| + abi.align(ctx.struct([ctx.int32, ctx.int64])).should eq(1) + abi.align(ctx.struct([ctx.int8, ctx.int16])).should eq(1) + end + + test "for packed struct" do |abi, ctx| + abi.align(ctx.struct([ctx.int32, ctx.int64], packed: true)).should eq(1) + end + + test "for array" do |abi, ctx| + abi.align(ctx.int16.array(10)).should eq(1) + end + end + + describe "size" do + test "for integer" do |abi, ctx| + abi.size(ctx.int1).should be_a(::Int32) + abi.size(ctx.int1).should eq(1) + abi.size(ctx.int8).should eq(1) + abi.size(ctx.int16).should eq(2) + abi.size(ctx.int32).should eq(4) + abi.size(ctx.int64).should eq(8) + end + + test "for pointer" do |abi, ctx| + abi.size(ctx.int8.pointer).should eq(2) + end + + test "for float" do |abi, ctx| + abi.size(ctx.float).should eq(4) + end + + test "for double" do |abi, ctx| + abi.size(ctx.double).should eq(8) + end + + test "for struct" do |abi, ctx| + abi.size(ctx.struct([ctx.int32, ctx.int64])).should eq(12) + abi.size(ctx.struct([ctx.int16, ctx.int8])).should eq(3) + abi.size(ctx.struct([ctx.int32, ctx.int8, ctx.int8])).should eq(6) + end + + test "for packed struct" do |abi, ctx| + abi.size(ctx.struct([ctx.int32, ctx.int8], packed: true)).should eq(5) + end + + test "for array" do |abi, ctx| + abi.size(ctx.int16.array(10)).should eq(20) + end + end + + describe "abi_info" do + {% for bits in [1, 8, 16, 32, 64] %} + test "int{{bits}}" do |abi, ctx| + arg_type = ArgType.direct(ctx.int{{bits}}) + info = abi.abi_info([ctx.int{{bits}}], ctx.int{{bits}}, true, ctx) + info.arg_types.size.should eq(1) + info.arg_types[0].should eq(arg_type) + info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.return_type.should eq(arg_type) + info.return_type.kind.should eq(LLVM::ABI::ArgKind::Direct) + end + {% end %} + + test "float" do |abi, ctx| + arg_type = ArgType.direct(ctx.float) + info = abi.abi_info([ctx.float], ctx.float, true, ctx) + info.arg_types.size.should eq(1) + info.arg_types[0].should eq(arg_type) + info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.return_type.should eq(arg_type) + info.return_type.kind.should eq(LLVM::ABI::ArgKind::Direct) + end + + test "double" do |abi, ctx| + arg_type = ArgType.direct(ctx.double) + info = abi.abi_info([ctx.double], ctx.double, true, ctx) + info.arg_types.size.should eq(1) + info.arg_types[0].should eq(arg_type) + info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.return_type.should eq(arg_type) + info.return_type.kind.should eq(LLVM::ABI::ArgKind::Direct) + end + + test "multiple arguments" do |abi, ctx| + args = 9.times.map { ctx.int16 }.to_a + info = abi.abi_info(args, ctx.int8, false, ctx) + info.arg_types.size.should eq(9) + info.arg_types.each(&.kind.should eq(LLVM::ABI::ArgKind::Direct)) + end + + test "multiple arguments above registers" do |abi, ctx| + args = 5.times.map { ctx.int32 }.to_a + info = abi.abi_info(args, ctx.int8, false, ctx) + info.arg_types.size.should eq(5) + info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[1].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[2].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[3].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[4].kind.should eq(LLVM::ABI::ArgKind::Indirect) + end + + test "struct args within 18 bytes" do |abi, ctx| + args = [ + ctx.int8, # rounded to 2 bytes + ctx.struct([ctx.int32, ctx.int32]), # 8 bytes + ctx.struct([ctx.int32, ctx.int32]), # 8 bytes + ] + info = abi.abi_info(args, ctx.void, false, ctx) + info.arg_types.size.should eq(3) + info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[1].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[2].kind.should eq(LLVM::ABI::ArgKind::Direct) + end + + test "struct args over 18 bytes" do |abi, ctx| + args = [ + ctx.int32, # 4 bytes + ctx.struct([ctx.int32, ctx.int32]), # 8 bytes + ctx.struct([ctx.int32, ctx.int32]), # 8 bytes + ] + info = abi.abi_info(args, ctx.void, false, ctx) + info.arg_types.size.should eq(3) + info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[1].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[2].kind.should eq(LLVM::ABI::ArgKind::Indirect) + end + + test "returns struct within 8 bytes" do |abi, ctx| + rty = ctx.struct([ctx.int32, ctx.int32]) + info = abi.abi_info([] of Type, rty, true, ctx) + info.return_type.kind.should eq(LLVM::ABI::ArgKind::Direct) + end + + test "returns struct over 8 bytes" do |abi, ctx| + rty = ctx.struct([ctx.int32, ctx.int32, ctx.int8]) + info = abi.abi_info([] of Type, rty, true, ctx) + info.return_type.kind.should eq(LLVM::ABI::ArgKind::Indirect) + end + end + {% end %} + end +end diff --git a/src/compiler/crystal/codegen/target.cr b/src/compiler/crystal/codegen/target.cr index d487005b77ba..223d64fe859b 100644 --- a/src/compiler/crystal/codegen/target.cr +++ b/src/compiler/crystal/codegen/target.cr @@ -51,10 +51,14 @@ class Crystal::Codegen::Target def pointer_bit_width case @architecture - when "x86_64", "aarch64" + when "aarch64", "x86_64" 64 - else + when "arm", "i386", "wasm32" 32 + when "avr" + 16 + else + raise "BUG: unknown Target#pointer_bit_width for #{@architecture} target architecture" end end @@ -64,6 +68,8 @@ class Crystal::Codegen::Target 64 when "arm", "i386", "wasm32" 32 + when "avr" + 16 else raise "BUG: unknown Target#size_bit_width for #{@architecture} target architecture" end @@ -93,6 +99,7 @@ class Crystal::Codegen::Target def executable_extension case when windows? then ".exe" + when avr? then ".elf" else "" end end @@ -181,6 +188,10 @@ class Crystal::Codegen::Target environment_parts.any? &.in?("gnueabihf", "musleabihf") end + def avr? + @architecture == "avr" + end + def to_target_machine(cpu = "", features = "", optimization_mode = Compiler::OptimizationMode::O0, code_model = LLVM::CodeModel::Default) : LLVM::TargetMachine case @architecture @@ -197,6 +208,13 @@ class Crystal::Codegen::Target if cpu.empty? && !features.includes?("fp") && armhf? features += "+vfp2" end + when "avr" + LLVM.init_avr + + if cpu.blank? + # the ABI call convention, codegen and the linker need to known the CPU model + raise Target::Error.new("AVR targets must declare a CPU model, for example --mcpu=atmega328p") + end when "wasm32" LLVM.init_webassembly else diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 46bce1db1896..9cff591f8400 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -481,10 +481,13 @@ module Crystal elsif program.has_flag? "wasm32" link_flags = @link_flags || "" {"wasm-ld", %(wasm-ld "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} -lc #{program.lib_flags}), object_names} + elsif program.has_flag? "avr" + link_flags = @link_flags || "" + link_flags += " --target=avr-unknown-unknown -mmcu=#{@mcpu} -Wl,--gc-sections" + {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names} else link_flags = @link_flags || "" link_flags += " -rdynamic" - {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names} end end diff --git a/src/compiler/crystal/semantic/flags.cr b/src/compiler/crystal/semantic/flags.cr index 0b53b779fb51..d455f1fdb0c7 100644 --- a/src/compiler/crystal/semantic/flags.cr +++ b/src/compiler/crystal/semantic/flags.cr @@ -56,6 +56,10 @@ class Crystal::Program flags.add "bsd" if target.bsd? + if target.avr? && (cpu = target_machine.cpu.presence) + flags.add cpu + end + flags end end diff --git a/src/intrinsics.cr b/src/intrinsics.cr index 9b87e2c326ea..c5ae837d8931 100644 --- a/src/intrinsics.cr +++ b/src/intrinsics.cr @@ -4,45 +4,57 @@ lib LibIntrinsics {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_debugtrap)] {% end %} fun debugtrap = "llvm.debugtrap" - {% if flag?(:bits64) %} + {% if flag?(:avr) %} {% if compare_versions(Crystal::LLVM_VERSION, "15.0.0") < 0 %} - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} - fun memcpy = "llvm.memcpy.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) - - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} - fun memmove = "llvm.memmove.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) - - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} - fun memset = "llvm.memset.p0i8.i64"(dest : Void*, val : UInt8, len : UInt64, is_volatile : Bool) + fun memcpy = "llvm.memcpy.p0i8.p0i8.i16"(dest : Void*, src : Void*, len : UInt16, is_volatile : Bool) + fun memmove = "llvm.memmove.p0i8.p0i8.i16"(dest : Void*, src : Void*, len : UInt16, is_volatile : Bool) + fun memset = "llvm.memset.p0i8.i16"(dest : Void*, val : UInt8, len : UInt16, is_volatile : Bool) {% else %} - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} - fun memcpy = "llvm.memcpy.p0.p0.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) - - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} - fun memmove = "llvm.memmove.p0.p0.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) - - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} - fun memset = "llvm.memset.p0.i64"(dest : Void*, val : UInt8, len : UInt64, is_volatile : Bool) + fun memcpy = "llvm.memcpy.p0.p0.i16"(dest : Void*, src : Void*, len : UInt16, is_volatile : Bool) + fun memmove = "llvm.memmove.p0.p0.i16"(dest : Void*, src : Void*, len : UInt16, is_volatile : Bool) + fun memset = "llvm.memset.p0.i16"(dest : Void*, val : UInt8, len : UInt16, is_volatile : Bool) {% end %} {% else %} - {% if compare_versions(Crystal::LLVM_VERSION, "15.0.0") < 0 %} - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} - fun memcpy = "llvm.memcpy.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) + {% if flag?(:bits64) %} + {% if compare_versions(Crystal::LLVM_VERSION, "15.0.0") < 0 %} + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} + fun memcpy = "llvm.memcpy.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} + fun memmove = "llvm.memmove.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} + fun memset = "llvm.memset.p0i8.i64"(dest : Void*, val : UInt8, len : UInt64, is_volatile : Bool) + {% else %} + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} + fun memcpy = "llvm.memcpy.p0.p0.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} + fun memmove = "llvm.memmove.p0.p0.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool) + + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} + fun memset = "llvm.memset.p0.i64"(dest : Void*, val : UInt8, len : UInt64, is_volatile : Bool) + {% end %} + {% else %} + {% if compare_versions(Crystal::LLVM_VERSION, "15.0.0") < 0 %} + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} + fun memcpy = "llvm.memcpy.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} - fun memmove = "llvm.memmove.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} + fun memmove = "llvm.memmove.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} - fun memset = "llvm.memset.p0i8.i32"(dest : Void*, val : UInt8, len : UInt32, is_volatile : Bool) - {% else %} - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} - fun memcpy = "llvm.memcpy.p0.p0.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} + fun memset = "llvm.memset.p0i8.i32"(dest : Void*, val : UInt8, len : UInt32, is_volatile : Bool) + {% else %} + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %} + fun memcpy = "llvm.memcpy.p0.p0.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} - fun memmove = "llvm.memmove.p0.p0.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %} + fun memmove = "llvm.memmove.p0.p0.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool) - {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} - fun memset = "llvm.memset.p0.i32"(dest : Void*, val : UInt8, len : UInt32, is_volatile : Bool) + {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %} + fun memset = "llvm.memset.p0.i32"(dest : Void*, val : UInt8, len : UInt32, is_volatile : Bool) + {% end %} {% end %} {% end %} diff --git a/src/llvm.cr b/src/llvm.cr index afef40bc3c95..6ad1bf6c796d 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -52,6 +52,22 @@ module LLVM {% end %} end + def self.init_avr : Nil + return if @@initialized_avr + @@initialized_avr = true + + {% if LibLLVM::BUILT_TARGETS.includes?(:avr) %} + LibLLVM.initialize_avr_target_info + LibLLVM.initialize_avr_target + LibLLVM.initialize_avr_target_mc + LibLLVM.initialize_avr_asm_printer + LibLLVM.initialize_avr_asm_parser + LibLLVM.link_in_mc_jit + {% else %} + raise "ERROR: LLVM was built without AVR target" + {% end %} + end + def self.init_webassembly : Nil return if @@initialized_webassembly @@initialized_webassembly = true diff --git a/src/llvm/abi/avr.cr b/src/llvm/abi/avr.cr new file mode 100644 index 000000000000..97c369dccb04 --- /dev/null +++ b/src/llvm/abi/avr.cr @@ -0,0 +1,82 @@ +require "../abi" + +class LLVM::ABI::AVR < LLVM::ABI + AVRTINY = StaticArray[ + "attiny4", + "attiny5", + "attiny9", + "attiny10", + "attiny102", + "attiny104", + "attiny20", + "attiny40", + ] + + def initialize(target_machine : TargetMachine, mcpu : String? = nil) + super target_machine + + # "Reduced Tiny" core devices only have 16 General Purpose Registers + if mcpu.in?(AVRTINY) + @rsize = 4 # values above 4 bytes are returned by memory + @rmin = 20 # 6 registers for call arguments (R25..R20) + else + @rsize = 8 # values above 8 bytes are returned by memory + @rmin = 8 # 18 registers for call arguments (R25..R8) + end + end + + def align(type : Type) : Int32 + target_data.abi_alignment(type).to_i32 + end + + def size(type : Type) : Int32 + target_data.abi_size(type).to_i32 + end + + # Follows AVR GCC, while Clang (and Rust) are incompatible, despite LLVM + # itself being compliant. + # + # - + # - + def abi_info(atys : Array(Type), rty : Type, ret_def : Bool, context : Context) : LLVM::ABI::FunctionType + ret_ty = compute_return_type(rty, ret_def, context) + arg_tys = compute_arg_types(atys, context) + FunctionType.new(arg_tys, ret_ty) + end + + # Pass in registers unless the returned type is a struct larger than 8 bytes + # (4 bytes on reduced tiny cores). + # + # Rust & Clang always return a struct _indirectly_. + private def compute_return_type(rty, ret_def, context) + if !ret_def + ArgType.direct(context.void) + elsif size(rty) > @rsize + ArgType.indirect(rty, LLVM::Attribute::StructRet) + else + # let the LLVM AVR backend handle the pw2ceil padding of structs + ArgType.direct(rty) + end + end + + # Fill the R25 -> R8 registers (R20 on reduced tiny cores), rounding odd byte + # sizes to the next even number, then pass by memory (indirect), so {i8, i32} + # are passed as R24 then R20..R23 (LSB -> MSB) for example. + # + # Rust & Clang always pass structs _indirectly_. + private def compute_arg_types(atys, context) + rn = 26 + atys.map do |aty| + bytes = size(aty) + bytes += 1 if bytes.odd? + rn -= bytes + + if bytes == 0 || rn < @rmin + ArgType.indirect(aty, LLVM::Attribute::StructRet) + else + # let the LLVM AVR backend handle the odd to next even number padding + ArgType.direct(aty) + end + end + end +end diff --git a/src/llvm/lib_llvm/target.cr b/src/llvm/lib_llvm/target.cr index 74fe09ba48fe..7e50e5d663fa 100644 --- a/src/llvm/lib_llvm/target.cr +++ b/src/llvm/lib_llvm/target.cr @@ -13,6 +13,11 @@ lib LibLLVM fun initialize_arm_target_mc = LLVMInitializeARMTargetMC fun initialize_arm_asm_printer = LLVMInitializeARMAsmPrinter fun initialize_arm_asm_parser = LLVMInitializeARMAsmParser + fun initialize_avr_target_info = LLVMInitializeAVRTargetInfo + fun initialize_avr_target = LLVMInitializeAVRTarget + fun initialize_avr_target_mc = LLVMInitializeAVRTargetMC + fun initialize_avr_asm_printer = LLVMInitializeAVRAsmPrinter + fun initialize_avr_asm_parser = LLVMInitializeAVRAsmParser fun initialize_webassembly_target_info = LLVMInitializeWebAssemblyTargetInfo fun initialize_webassembly_target = LLVMInitializeWebAssemblyTarget fun initialize_webassembly_target_mc = LLVMInitializeWebAssemblyTargetMC @@ -31,4 +36,5 @@ lib LibLLVM fun abi_size_of_type = LLVMABISizeOfType(td : TargetDataRef, ty : TypeRef) : ULongLong fun abi_alignment_of_type = LLVMABIAlignmentOfType(td : TargetDataRef, ty : TypeRef) : UInt fun offset_of_element = LLVMOffsetOfElement(td : TargetDataRef, struct_ty : TypeRef, element : UInt) : ULongLong + fun copy_string_rep_of_target_data = LLVMCopyStringRepOfTargetData(td : TargetDataRef) : Char* end diff --git a/src/llvm/lib_llvm/target_machine.cr b/src/llvm/lib_llvm/target_machine.cr index 992d2e2d72e9..ea883e091a81 100644 --- a/src/llvm/lib_llvm/target_machine.cr +++ b/src/llvm/lib_llvm/target_machine.cr @@ -15,6 +15,7 @@ lib LibLLVM fun dispose_target_machine = LLVMDisposeTargetMachine(t : TargetMachineRef) fun get_target_machine_target = LLVMGetTargetMachineTarget(t : TargetMachineRef) : TargetRef fun get_target_machine_triple = LLVMGetTargetMachineTriple(t : TargetMachineRef) : Char* + fun get_target_machine_cpu = LLVMGetTargetMachineCPU(t : TargetMachineRef) : Char* fun create_target_data_layout = LLVMCreateTargetDataLayout(t : TargetMachineRef) : TargetDataRef {% unless LibLLVM::IS_LT_180 %} fun set_target_machine_global_isel = LLVMSetTargetMachineGlobalISel(t : TargetMachineRef, enable : Bool) diff --git a/src/llvm/target_data.cr b/src/llvm/target_data.cr index 653c69d6b0b3..b6b5d6478240 100644 --- a/src/llvm/target_data.cr +++ b/src/llvm/target_data.cr @@ -30,6 +30,6 @@ struct LLVM::TargetData end def to_data_layout_string - String.new(LibLLVM.copy_string_rep_of_target_data(self)) + LLVM.string_and_dispose(LibLLVM.copy_string_rep_of_target_data(self)) end end diff --git a/src/llvm/target_machine.cr b/src/llvm/target_machine.cr index c3aaee21313d..b9de8296d5c8 100644 --- a/src/llvm/target_machine.cr +++ b/src/llvm/target_machine.cr @@ -19,6 +19,11 @@ class LLVM::TargetMachine LLVM.string_and_dispose(triple_c) end + def cpu : String + cpu_c = LibLLVM.get_target_machine_cpu(self) + LLVM.string_and_dispose(cpu_c) + end + def emit_obj_to_file(llvm_mod, filename) emit_to_file llvm_mod, filename, LLVM::CodeGenFileType::ObjectFile end @@ -53,6 +58,8 @@ class LLVM::TargetMachine ABI::AArch64.new(self) when /arm/ ABI::ARM.new(self) + when /avr/ + ABI::AVR.new(self, cpu) when /wasm32/ ABI::Wasm32.new(self) else From e1e6dcd0bd97da3711f72d396d842cc331606c2a Mon Sep 17 00:00:00 2001 From: Hiroya Fujinami Date: Sat, 4 May 2024 03:20:16 +0900 Subject: [PATCH 1102/1551] Fix `Class#===` on metaclass (#11162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/class_spec.cr | 4 ++++ src/class.cr | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/std/class_spec.cr b/spec/std/class_spec.cr index 12b1a5899256..2302e8f18214 100644 --- a/spec/std/class_spec.cr +++ b/spec/std/class_spec.cr @@ -27,6 +27,10 @@ describe Class do (Array === [1]).should be_true (Array(Int32) === [1]).should be_true (Array(Int32) === ['a']).should be_false + (Int32.class === 1).should be_false + (Int32.class === 1.5).should be_false + (Int32.class === Int32).should be_true + (Int32.class === Array).should be_true end it "casts, allowing the class to be passed in at runtime" do diff --git a/src/class.cr b/src/class.cr index 9422970e390f..4d03d3d1b715 100644 --- a/src/class.cr +++ b/src/class.cr @@ -95,7 +95,15 @@ class Class end def ===(other) - other.is_a?(self) + # This branch handles `Int32.class === 1` case. + # In this case, `@type` is `Class` and `other.is_a?(self)` means `other.is_a?(Object)` + # because type of `self` is an instance type of the scope type and the instance type of `Class` is `Object`. + # See https://github.com/crystal-lang/crystal/issues/10736. + {% if @type == Class %} + other.is_a?(Class) + {% else %} + other.is_a?(self) + {% end %} end # Returns the name of this class. From cf04c2ebddcedcad93fe6693f5ea9ff0e29dbd95 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sun, 5 May 2024 21:15:05 +0200 Subject: [PATCH 1103/1551] `Thread` owns its current fiber (instead of `Crystal::Scheduler`) (#14554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/crystal/scheduler.cr | 15 +++++---------- src/crystal/system/thread.cr | 7 +++++-- src/fiber.cr | 6 +++--- src/gc/boehm.cr | 3 +-- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index 1728a9b7f335..c86d04309b14 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -24,10 +24,6 @@ class Crystal::Scheduler Thread.current.scheduler.@event_loop end - def self.current_fiber : Fiber - Thread.current.scheduler.@current - end - def self.enqueue(fiber : Fiber) : Nil thread = Thread.current scheduler = thread.scheduler @@ -98,10 +94,9 @@ class Crystal::Scheduler @sleeping = false # :nodoc: - def initialize(thread : Thread) + def initialize(@thread : Thread) @main = thread.main_fiber {% if flag?(:preview_mt) %} @main.set_current_thread(thread) {% end %} - @current = @main @runnables = Deque(Fiber).new end @@ -124,7 +119,7 @@ class Crystal::Scheduler GC.set_stackbottom(fiber.@stack_bottom) {% end %} - current, @current = @current, fiber + current, @thread.current_fiber = @thread.current_fiber, fiber Fiber.swapcontext(pointerof(current.@context), pointerof(fiber.@context)) {% if flag?(:preview_mt) %} @@ -151,7 +146,7 @@ class Crystal::Scheduler protected def reschedule : Nil loop do if runnable = @lock.sync { @runnables.shift? } - resume(runnable) unless runnable == @current + resume(runnable) unless runnable == @thread.current_fiber break else @event_loop.run_once @@ -160,12 +155,12 @@ class Crystal::Scheduler end protected def sleep(time : Time::Span) : Nil - @current.resume_event.add(time) + @thread.current_fiber.resume_event.add(time) reschedule end protected def yield(fiber : Fiber) : Nil - @current.resume_event.add(0.seconds) + @thread.current_fiber.resume_event.add(0.seconds) resume(fiber) end diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index dd93714487b9..e6627538112d 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -45,6 +45,9 @@ class Thread # Returns the Fiber representing the thread's main stack. getter! main_fiber : Fiber + # Returns the Fiber currently running on the thread. + property! current_fiber : Fiber + # :nodoc: property next : Thread? @@ -68,7 +71,7 @@ class Thread def initialize @func = ->(t : Thread) {} @system_handle = Crystal::System::Thread.current_handle - @main_fiber = Fiber.new(stack_address, self) + @current_fiber = @main_fiber = Fiber.new(stack_address, self) Thread.threads.push(self) end @@ -120,7 +123,7 @@ class Thread protected def start Thread.threads.push(self) Thread.current = self - @main_fiber = fiber = Fiber.new(stack_address, self) + @current_fiber = @main_fiber = fiber = Fiber.new(stack_address, self) if name = @name self.system_name = name diff --git a/src/fiber.cr b/src/fiber.cr index 049b7e9bb0c0..5f6e632b2445 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -173,7 +173,7 @@ class Fiber # Returns the current fiber. def self.current : Fiber - Crystal::Scheduler.current_fiber + Thread.current.current_fiber end # The fiber's proc is currently running or didn't fully save its context. The @@ -246,11 +246,11 @@ class Fiber # The current fiber will resume after a period of time. # The timeout can be cancelled with `cancel_timeout` def self.timeout(timeout : Time::Span?, select_action : Channel::TimeoutAction? = nil) : Nil - Crystal::Scheduler.current_fiber.timeout(timeout, select_action) + Fiber.current.timeout(timeout, select_action) end def self.cancel_timeout : Nil - Crystal::Scheduler.current_fiber.cancel_timeout + Fiber.current.cancel_timeout end # Yields to the scheduler and allows it to swap execution to other diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 609f71189795..29ae825adab1 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -374,8 +374,7 @@ module GC {% if flag?(:preview_mt) %} Thread.unsafe_each do |thread| - if scheduler = thread.@scheduler - fiber = scheduler.@current + if fiber = thread.current_fiber? GC.set_stackbottom(thread.gc_thread_handler, fiber.@stack_bottom) end end From 3c95bdf6b416458cabadf05c89ffa587bfdc4e50 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sun, 5 May 2024 21:16:11 +0200 Subject: [PATCH 1104/1551] Use `Fiber#enqueue` (#14561) --- spec/std/channel_spec.cr | 4 ++-- spec/std/concurrent/select_spec.cr | 2 +- src/channel.cr | 2 +- src/crystal/system/unix/event_loop_libevent.cr | 4 ++-- src/crystal/system/win32/event_loop_iocp.cr | 4 ++-- src/io/evented.cr | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/std/channel_spec.cr b/spec/std/channel_spec.cr index 77be79f6d0d3..9d121f9d9827 100644 --- a/spec/std/channel_spec.cr +++ b/spec/std/channel_spec.cr @@ -2,8 +2,8 @@ require "spec" require "./spec_helper" private def yield_to(fiber) - Crystal::Scheduler.enqueue(Fiber.current) - Crystal::Scheduler.resume(fiber) + Fiber.current.enqueue + fiber.resume end private macro parallel(*jobs) diff --git a/spec/std/concurrent/select_spec.cr b/spec/std/concurrent/select_spec.cr index 781bf6a0a46b..f3f439ddd0b3 100644 --- a/spec/std/concurrent/select_spec.cr +++ b/spec/std/concurrent/select_spec.cr @@ -99,7 +99,7 @@ describe "select" do x = b end ensure - Crystal::Scheduler.enqueue(main) + main.enqueue end sleep diff --git a/src/channel.cr b/src/channel.cr index f5c46343dc56..beb4feaef9fd 100644 --- a/src/channel.cr +++ b/src/channel.cr @@ -744,7 +744,7 @@ class Channel(T) def time_expired(fiber : Fiber) : Nil if @select_context.try &.try_trigger - Crystal::Scheduler.enqueue fiber + fiber.enqueue end end end diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr index a928294bb262..06c0ea8b03f0 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/system/unix/event_loop_libevent.cr @@ -26,7 +26,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop # Create a new resume event for a fiber. def create_resume_event(fiber : Fiber) : Crystal::EventLoop::Event event_base.new_event(-1, LibEvent2::EventFlags::None, fiber) do |s, flags, data| - Crystal::Scheduler.enqueue data.as(Fiber) + data.as(Fiber).enqueue end end @@ -38,7 +38,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop f.timeout_select_action = nil select_action.time_expired(f) else - Crystal::Scheduler.enqueue f + f.enqueue end end end diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index ffae160c64eb..5a207cd195ec 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -58,7 +58,7 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop timed_out = IO::Overlapped.wait_queued_completions(wait_time.total_milliseconds) do |fiber| # This block may run multiple times. Every single fiber gets enqueued. - Crystal::Scheduler.enqueue fiber + fiber.enqueue end # If the wait for completion timed out we've reached the wake time and @@ -90,7 +90,7 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop fiber.timeout_select_action = nil select_action.time_expired(fiber) else - Crystal::Scheduler.enqueue fiber + fiber.enqueue end end diff --git a/src/io/evented.cr b/src/io/evented.cr index 98215e7df86f..13154e5cc4db 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -67,7 +67,7 @@ module IO::Evented @read_timed_out = timed_out if reader = @readers.get?.try &.shift? - Crystal::Scheduler.enqueue reader + reader.enqueue end end @@ -76,7 +76,7 @@ module IO::Evented @write_timed_out = timed_out if writer = @writers.get?.try &.shift? - Crystal::Scheduler.enqueue writer + writer.enqueue end end From 0d2047d6ccad71442b88186e59dfeaa860fe2c56 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 6 May 2024 13:52:17 +0200 Subject: [PATCH 1105/1551] Add `Crystal::EventLoop.current` (#14559) --- src/crystal/system/event_loop.cr | 5 +++++ src/crystal/system/win32/event_loop_iocp.cr | 4 ++-- src/crystal/system/win32/file_descriptor.cr | 4 ++-- src/crystal/system/win32/process.cr | 2 +- src/crystal/system/win32/socket.cr | 2 +- src/fiber.cr | 4 ++-- src/io/evented.cr | 4 ++-- src/io/overlapped.cr | 9 ++++++--- src/kernel.cr | 2 +- 9 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index b9efa4dc09c3..0f6351fbac24 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -2,6 +2,11 @@ abstract class Crystal::EventLoop # Creates an event loop instance # def self.create : Crystal::EventLoop + @[AlwaysInline] + def self.current : self + Crystal::Scheduler.event_loop + end + # Runs the event loop. abstract def run_once : Nil diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 5a207cd195ec..36ff4e58330b 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -126,7 +126,7 @@ class Crystal::Iocp::Event # Frees the event def free : Nil - Crystal::Scheduler.event_loop.dequeue(self) + Crystal::EventLoop.current.dequeue(self) end def delete @@ -135,6 +135,6 @@ class Crystal::Iocp::Event def add(timeout : Time::Span?) : Nil @wake_at = timeout ? Time.monotonic + timeout : Time.monotonic - Crystal::Scheduler.event_loop.enqueue(self) + Crystal::EventLoop.current.enqueue(self) end end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 29ec1e4b6bd6..63c79af04d87 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -238,13 +238,13 @@ module Crystal::System::FileDescriptor w_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless write_blocking w_pipe = LibC.CreateNamedPipeA(pipe_name, w_pipe_flags, pipe_mode, 1, PIPE_BUFFER_SIZE, PIPE_BUFFER_SIZE, 0, nil) raise IO::Error.from_winerror("CreateNamedPipeA") if w_pipe == LibC::INVALID_HANDLE_VALUE - Crystal::Scheduler.event_loop.create_completion_port(w_pipe) unless write_blocking + Crystal::EventLoop.current.create_completion_port(w_pipe) unless write_blocking r_pipe_flags = LibC::FILE_FLAG_NO_BUFFERING r_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless read_blocking r_pipe = LibC.CreateFileW(System.to_wstr(pipe_name), LibC::GENERIC_READ | LibC::FILE_WRITE_ATTRIBUTES, 0, nil, LibC::OPEN_EXISTING, r_pipe_flags, nil) raise IO::Error.from_winerror("CreateFileW") if r_pipe == LibC::INVALID_HANDLE_VALUE - Crystal::Scheduler.event_loop.create_completion_port(r_pipe) unless read_blocking + Crystal::EventLoop.current.create_completion_port(r_pipe) unless read_blocking r = IO::FileDescriptor.new(r_pipe.address, read_blocking) w = IO::FileDescriptor.new(w_pipe.address, write_blocking) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 6a0467040e10..c85be02a47e2 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -37,7 +37,7 @@ struct Crystal::System::Process LibC::JOBOBJECTINFOCLASS::AssociateCompletionPortInformation, LibC::JOBOBJECT_ASSOCIATE_COMPLETION_PORT.new( completionKey: @completion_key.as(Void*), - completionPort: Crystal::Scheduler.event_loop.iocp, + completionPort: Crystal::EventLoop.current.iocp, ), ) diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 1e11473d72af..f567cffdcee2 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -79,7 +79,7 @@ module Crystal::System::Socket raise ::Socket::Error.from_wsa_error("WSASocketW") end - Crystal::Scheduler.event_loop.create_completion_port LibC::HANDLE.new(socket) + Crystal::EventLoop.current.create_completion_port LibC::HANDLE.new(socket) socket end diff --git a/src/fiber.cr b/src/fiber.cr index 5f6e632b2445..100e343175f7 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -223,12 +223,12 @@ class Fiber # :nodoc: def resume_event : Crystal::EventLoop::Event - @resume_event ||= Crystal::Scheduler.event_loop.create_resume_event(self) + @resume_event ||= Crystal::EventLoop.current.create_resume_event(self) end # :nodoc: def timeout_event : Crystal::EventLoop::Event - @timeout_event ||= Crystal::Scheduler.event_loop.create_timeout_event(self) + @timeout_event ||= Crystal::EventLoop.current.create_timeout_event(self) end # :nodoc: diff --git a/src/io/evented.cr b/src/io/evented.cr index 13154e5cc4db..4e27fdfb7670 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -101,7 +101,7 @@ module IO::Evented end private def add_read_event(timeout = @read_timeout) : Nil - event = @read_event.get { Crystal::Scheduler.event_loop.create_fd_read_event(self) } + event = @read_event.get { Crystal::EventLoop.current.create_fd_read_event(self) } event.add timeout end @@ -126,7 +126,7 @@ module IO::Evented end private def add_write_event(timeout = @write_timeout) : Nil - event = @write_event.get { Crystal::Scheduler.event_loop.create_fd_write_event(self) } + event = @write_event.get { Crystal::EventLoop.current.create_fd_write_event(self) } event.add timeout end diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index 0ab1a24fc794..e50dd50901f8 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -16,7 +16,7 @@ module IO::Overlapped else timeout = timeout.to_u64 end - result = LibC.GetQueuedCompletionStatusEx(Crystal::Scheduler.event_loop.iocp, overlapped_entries, overlapped_entries.size, out removed, timeout, false) + result = LibC.GetQueuedCompletionStatusEx(Crystal::EventLoop.current.iocp, overlapped_entries, overlapped_entries.size, out removed, timeout, false) if result == 0 error = WinError.value if timeout && error.wait_timeout? @@ -160,11 +160,14 @@ module IO::Overlapped else timeout_event = Crystal::Iocp::Event.new(Fiber.current, Time::Span::MAX) end - Crystal::Scheduler.event_loop.enqueue(timeout_event) + # memoize event loop to make sure that we still target the same instance + # after wakeup (guaranteed by current MT model but let's be future proof) + event_loop = Crystal::EventLoop.current + event_loop.enqueue(timeout_event) Crystal::Scheduler.reschedule - Crystal::Scheduler.event_loop.dequeue(timeout_event) + event_loop.dequeue(timeout_event) end def overlapped_operation(handle, method, timeout, *, writing = false, &) diff --git a/src/kernel.cr b/src/kernel.cr index 1ca8d4073243..8b24f0be2bb5 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -562,7 +562,7 @@ end ->Crystal::System::SignalChildHandler.after_fork, # reinit event loop: - ->{ Crystal::Scheduler.event_loop.after_fork }, + ->{ Crystal::EventLoop.current.after_fork }, # more clean ups (may depend on event loop): ->Random::DEFAULT.new_seed, From ddd2d22f4d4b14e1aee2105a00cc4984bdebcd5a Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 7 May 2024 11:26:40 +0200 Subject: [PATCH 1106/1551] Fix: No need for explicit memory barriers on ARM (#14567) --- src/atomic.cr | 21 +++++---------------- src/crystal/rw_lock.cr | 13 ------------- src/crystal/spin_lock.cr | 6 ------ src/mutex.cr | 9 --------- 4 files changed, 5 insertions(+), 44 deletions(-) diff --git a/src/atomic.cr b/src/atomic.cr index db6cd7e88b84..c5d14513081b 100644 --- a/src/atomic.cr +++ b/src/atomic.cr @@ -20,6 +20,10 @@ struct Atomic(T) # (e.g. locks) you may try the acquire/release semantics that may be faster on # some architectures (e.g. X86) but remember that an acquire must be paired # with a release for the ordering to be guaranteed. + # + # The code generation always enforces the selected memory order, even on + # weak CPU architectures (e.g. ARM32), with the exception of the Relaxed + # memory order where only the operation itself is atomic. enum Ordering Relaxed = LLVM::AtomicOrdering::Monotonic Acquire = LLVM::AtomicOrdering::Acquire @@ -29,14 +33,6 @@ struct Atomic(T) end # Adds an explicit memory barrier with the specified memory order guarantee. - # - # Atomics on weakly-ordered CPUs (e.g. ARM32) may not guarantee memory order - # of other memory accesses, and an explicit memory barrier is thus required. - # - # Notes: - # - X86 is strongly-ordered and trying to add a fence should be a NOOP; - # - AArch64 guarantees memory order and doesn't need explicit fences in - # addition to the atomics (but may need barriers in other cases). macro fence(ordering = :sequentially_consistent) ::Atomic::Ops.fence({{ordering}}, false) end @@ -470,18 +466,11 @@ struct Atomic::Flag # Atomically tries to set the flag. Only succeeds and returns `true` if the # flag wasn't previously set; returns `false` otherwise. def test_and_set : Bool - ret = @value.swap(true, :sequentially_consistent) == false - {% if flag?(:arm) %} - Atomic::Ops.fence(:sequentially_consistent, false) if ret - {% end %} - ret + @value.swap(true, :sequentially_consistent) == false end # Atomically clears the flag. def clear : Nil - {% if flag?(:arm) %} - Atomic::Ops.fence(:sequentially_consistent, false) - {% end %} @value.set(false, :sequentially_consistent) end end diff --git a/src/crystal/rw_lock.cr b/src/crystal/rw_lock.cr index c83074180e25..77b7331b76fe 100644 --- a/src/crystal/rw_lock.cr +++ b/src/crystal/rw_lock.cr @@ -15,9 +15,6 @@ struct Crystal::RWLock @readers.add(1, :acquire) if @writer.get(:acquire) == UNLOCKED - {% if flag?(:arm) %} - Atomic.fence(:acquire) - {% end %} return end @@ -26,9 +23,6 @@ struct Crystal::RWLock end def read_unlock : Nil - {% if flag?(:arm) %} - Atomic.fence(:release) - {% end %} @readers.sub(1, :release) end @@ -40,16 +34,9 @@ struct Crystal::RWLock while @readers.get(:acquire) != 0 Intrinsics.pause end - - {% if flag?(:arm) %} - Atomic.fence(:acquire) - {% end %} end def write_unlock : Nil - {% if flag?(:arm) %} - Atomic.fence(:release) - {% end %} @writer.set(UNLOCKED, :release) end end diff --git a/src/crystal/spin_lock.cr b/src/crystal/spin_lock.cr index 0792ce652159..4255fcae7bbd 100644 --- a/src/crystal/spin_lock.cr +++ b/src/crystal/spin_lock.cr @@ -14,17 +14,11 @@ class Crystal::SpinLock Intrinsics.pause end end - {% if flag?(:arm) %} - Atomic.fence(:acquire) - {% end %} {% end %} end def unlock {% if flag?(:preview_mt) %} - {% if flag?(:arm) %} - Atomic.fence(:release) - {% end %} @m.set(UNLOCKED, :release) {% end %} end diff --git a/src/mutex.cr b/src/mutex.cr index 38d34c14b070..e72ede2c8261 100644 --- a/src/mutex.cr +++ b/src/mutex.cr @@ -38,9 +38,6 @@ class Mutex @[AlwaysInline] def lock : Nil if @state.swap(LOCKED, :acquire) == UNLOCKED - {% if flag?(:arm) %} - Atomic.fence(:acquire) - {% end %} @mutex_fiber = Fiber.current unless @protection.unchecked? return end @@ -67,9 +64,6 @@ class Mutex if @state.get(:relaxed) == UNLOCKED if @state.swap(LOCKED, :acquire) == UNLOCKED - {% if flag?(:arm) %} - Atomic.fence(:acquire) - {% end %} @queue_count.sub(1) @mutex_fiber = Fiber.current unless @protection.unchecked? @@ -94,9 +88,6 @@ class Mutex return false if i == 0 end end - {% if flag?(:arm) %} - Atomic.fence(:acquire) - {% end %} true end From a2264a19598dfa3b6e01ba5726d5cf0efdf64a45 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 7 May 2024 20:00:13 +0800 Subject: [PATCH 1107/1551] Add LLVM 18 to LLVM CI (#14565) --- .github/workflows/llvm.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index d8df01d13090..d803b5de4bb2 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -26,6 +26,8 @@ jobs: llvm_ubuntu_version: "22.04" - llvm_version: "17.0.6" llvm_ubuntu_version: "22.04" + - llvm_version: "18.1.4" + llvm_ubuntu_version: "18.04" name: "LLVM ${{ matrix.llvm_version }}" steps: - name: Checkout Crystal source From a23f0fbee5d2c3fa272d9b1f9efc2773bc55f064 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 7 May 2024 18:56:51 +0200 Subject: [PATCH 1108/1551] Add `Fiber.suspend` (#14560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/channel.cr | 6 +++--- src/crystal/system/win32/process.cr | 2 +- src/fiber.cr | 16 +++++++++++++++- src/io/evented.cr | 4 ++-- src/io/overlapped.cr | 2 +- src/mutex.cr | 3 ++- src/wait_group.cr | 2 +- 7 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/channel.cr b/src/channel.cr index beb4feaef9fd..dfd61ff51cc4 100644 --- a/src/channel.cr +++ b/src/channel.cr @@ -231,7 +231,7 @@ class Channel(T) @senders.push pointerof(sender) @lock.unlock - Crystal::Scheduler.reschedule + Fiber.suspend case sender.state in .delivered? @@ -308,7 +308,7 @@ class Channel(T) @receivers.push pointerof(receiver) @lock.unlock - Crystal::Scheduler.reschedule + Fiber.suspend case receiver.state in .delivered? @@ -473,7 +473,7 @@ class Channel(T) contexts = ops.map &.create_context_and_wait(shared_state) each_skip_duplicates(ops_locks, &.unlock) - Crystal::Scheduler.reschedule + Fiber.suspend contexts.each_with_index do |context, index| op = ops[index] diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index c85be02a47e2..4c2c81d6e1f6 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -81,7 +81,7 @@ struct Crystal::System::Process # stuck forever in that case? # (https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-jobobject_associate_completion_port) @completion_key.fiber = ::Fiber.current - Crystal::Scheduler.reschedule + ::Fiber.suspend # If the IOCP notification is delivered before the process fully exits, # wait for it diff --git a/src/fiber.cr b/src/fiber.cr index 100e343175f7..8d8784fe472f 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -168,7 +168,7 @@ class Fiber {% unless flag?(:interpreted) %} Crystal::Scheduler.stack_pool.release(@stack) {% end %} - Crystal::Scheduler.reschedule + Fiber.suspend end # Returns the current fiber. @@ -285,6 +285,20 @@ class Fiber Crystal::Scheduler.yield end + # Suspends execution of the current fiber indefinitely. + # + # Unlike `Fiber.yield` the current fiber is not automatically + # reenqueued and can only be resumed whith an explicit call to `#enqueue`. + # + # This is equivalent to `sleep` without a time. + # + # This method is meant to be used in concurrency primitives. It's particularly + # useful if the fiber needs to wait for something to happen (for example an IO + # event, a message is ready in a channel, etc.) which triggers a re-enqueue. + def self.suspend : Nil + Crystal::Scheduler.reschedule + end + def to_s(io : IO) : Nil io << "#<" << self.class.name << ":0x" object_id.to_s(io, 16) diff --git a/src/io/evented.cr b/src/io/evented.cr index 4e27fdfb7670..ed2c18352ff0 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -90,7 +90,7 @@ module IO::Evented readers = @readers.get { Deque(Fiber).new } readers << Fiber.current add_read_event(timeout) - Crystal::Scheduler.reschedule + Fiber.suspend if @read_timed_out @read_timed_out = false @@ -115,7 +115,7 @@ module IO::Evented writers = @writers.get { Deque(Fiber).new } writers << Fiber.current add_write_event(timeout) - Crystal::Scheduler.reschedule + Fiber.suspend if @write_timed_out @write_timed_out = false diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index e50dd50901f8..bd5dfe7f27d1 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -165,7 +165,7 @@ module IO::Overlapped event_loop = Crystal::EventLoop.current event_loop.enqueue(timeout_event) - Crystal::Scheduler.reschedule + Fiber.suspend event_loop.dequeue(timeout_event) end diff --git a/src/mutex.cr b/src/mutex.cr index e72ede2c8261..780eac468201 100644 --- a/src/mutex.cr +++ b/src/mutex.cr @@ -73,7 +73,8 @@ class Mutex @queue.push Fiber.current end - Crystal::Scheduler.reschedule + + Fiber.suspend end @mutex_fiber = Fiber.current unless @protection.unchecked? diff --git a/src/wait_group.cr b/src/wait_group.cr index d9ae4ae740ac..2fd49c593b56 100644 --- a/src/wait_group.cr +++ b/src/wait_group.cr @@ -106,7 +106,7 @@ class WaitGroup @waiting.push(pointerof(waiting)) end - Crystal::Scheduler.reschedule + Fiber.suspend return if done? raise RuntimeError.new("Positive WaitGroup counter (early wake up?)") From a3bbcf98c3b7fe45720bee5ace9fa07ae4ce5b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 8 May 2024 10:34:50 +0200 Subject: [PATCH 1109/1551] Write release version to `src/VERSION` in `update-changelog` and `release-update` (#14547) --- scripts/release-update.sh | 5 +++++ scripts/update-changelog.sh | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/scripts/release-update.sh b/scripts/release-update.sh index ec9c9570636c..d705ce357f3e 100755 --- a/scripts/release-update.sh +++ b/scripts/release-update.sh @@ -11,6 +11,11 @@ set -eu CRYSTAL_VERSION=$1 +# Write dev version for next minor release into src/VERSION +minor_branch="${CRYSTAL_VERSION%.*}" +next_minor="$((${minor_branch#*.} + 1))" +echo "${CRYSTAL_VERSION%%.*}.${next_minor}.0-dev" > src/VERSION + # Edit PREVIOUS_CRYSTAL_BASE_URL in .circleci/config.yml sed -i -E "s|[0-9.]+/crystal-[0-9.]+-[0-9]|$CRYSTAL_VERSION/crystal-$CRYSTAL_VERSION-1|g" .circleci/config.yml diff --git a/scripts/update-changelog.sh b/scripts/update-changelog.sh index 777f65a60e3d..3d044eb79062 100755 --- a/scripts/update-changelog.sh +++ b/scripts/update-changelog.sh @@ -40,6 +40,10 @@ scripts/github-changelog.cr $VERSION > $current_changelog echo "Switching to branch $branch" git switch $branch 2>/dev/null || git switch -c $branch; +# Write release version into src/VERSION +echo "${VERSION}" > src/VERSION +git add src/VERSION + if grep --silent -E "^## \[$VERSION\]" CHANGELOG.md; then echo "Replacing section in CHANGELOG" From 3eab3e4207f3baf96846b9e0a762c169ab769a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 8 May 2024 10:57:48 +0200 Subject: [PATCH 1110/1551] Add test helper for `normalize/regex_spec` (#14545) --- spec/compiler/normalize/regex_spec.cr | 50 +++++++++++++++++++++------ spec/spec_helper.cr | 8 ++++- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/spec/compiler/normalize/regex_spec.cr b/spec/compiler/normalize/regex_spec.cr index f4c6cc60f3b3..26a3dfb45109 100644 --- a/spec/compiler/normalize/regex_spec.cr +++ b/spec/compiler/normalize/regex_spec.cr @@ -1,30 +1,60 @@ require "../../spec_helper" +private def assert_expand_regex_const(from : String, to, *, flags = nil, file = __FILE__, line = __LINE__) + from_nodes = Parser.parse(from) + assert_expand(from_nodes, flags: flags, file: file, line: line) do |to_nodes, program| + const = program.types[to_nodes.to_s].should be_a(Crystal::Const), file: file, line: line + const.value.to_s.should eq(to.strip), file: file, line: line + end +end + describe "Normalize: regex literal" do + describe "StringLiteral" do + it "expands to const" do + assert_expand Parser.parse(%q(/foo/)) do |to_nodes, program| + to_nodes.to_s.should eq "$Regex:0" + end + end + + it "simple" do + assert_expand_regex_const %q(/foo/), <<-'CRYSTAL' + ::Regex.new("foo", ::Regex::Options.new(0)) + CRYSTAL + end + end + + describe "StringInterpolation" do + it "simple" do + assert_expand %q(/#{"foo".to_s}/), <<-'CRYSTAL' + ::Regex.new("#{"foo".to_s}", ::Regex::Options.new(0)) + CRYSTAL + end + end + describe "options" do it "empty" do - assert_expand %q(/#{"".to_s}/), <<-'CRYSTAL' - ::Regex.new("#{"".to_s}", ::Regex::Options.new(0)) + assert_expand_regex_const %q(//), <<-'CRYSTAL' + ::Regex.new("", ::Regex::Options.new(0)) CRYSTAL end it "i" do - assert_expand %q(/#{"".to_s}/i), <<-'CRYSTAL' - ::Regex.new("#{"".to_s}", ::Regex::Options.new(1)) + assert_expand_regex_const %q(//i), <<-'CRYSTAL' + ::Regex.new("", ::Regex::Options.new(1)) CRYSTAL end it "x" do - assert_expand %q(/#{"".to_s}/x), <<-'CRYSTAL' - ::Regex.new("#{"".to_s}", ::Regex::Options.new(8)) + assert_expand_regex_const %q(//x), <<-'CRYSTAL' + ::Regex.new("", ::Regex::Options.new(8)) CRYSTAL end it "im" do - assert_expand %q(/#{"".to_s}/im), <<-'CRYSTAL' - ::Regex.new("#{"".to_s}", ::Regex::Options.new(7)) + assert_expand_regex_const %q(//im), <<-'CRYSTAL' + ::Regex.new("", ::Regex::Options.new(7)) CRYSTAL end it "imx" do - assert_expand %q(/#{"".to_s}/imx), <<-'CRYSTAL' - ::Regex.new("#{"".to_s}", ::Regex::Options.new(15)) + assert_expand_regex_const %q(//imx), <<-'CRYSTAL' + ::Regex.new("", ::Regex::Options.new(15)) CRYSTAL end end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index a1ee9f49daa1..ca5bc61ad3c4 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -124,10 +124,16 @@ def assert_expand(from : String, to, *, flags = nil, file = __FILE__, line = __L end def assert_expand(from_nodes : ASTNode, to, *, flags = nil, file = __FILE__, line = __LINE__) + assert_expand(from_nodes, flags: flags, file: file, line: line) do |to_nodes| + to_nodes.to_s.strip.should eq(to.strip), file: file, line: line + end +end + +def assert_expand(from_nodes : ASTNode, *, flags = nil, file = __FILE__, line = __LINE__, &) program = new_program program.flags.concat(flags.split) if flags to_nodes = LiteralExpander.new(program).expand(from_nodes) - to_nodes.to_s.strip.should eq(to.strip), file: file, line: line + yield to_nodes, program end def assert_expand_second(from : String, to, *, flags = nil, file = __FILE__, line = __LINE__) From 4a4952ca7f2b0df1be771862d7742833519829d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 8 May 2024 19:30:43 +0200 Subject: [PATCH 1111/1551] Disable implicit execution of batch files (#14557) Co-authored-by: Oleh Prypin --- spec/std/process_spec.cr | 13 +++++++++++++ src/crystal/system/win32/process.cr | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 6388b88fb70c..d656e9353589 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -232,6 +232,19 @@ pending_interpreted describe: Process do output.should eq "`echo hi`\n" end + describe "does not execute batch files" do + %w[.bat .Bat .BAT .cmd .cmD .CmD].each do |ext| + it ext do + with_tempfile "process_run#{ext}" do |path| + File.write(path, "echo '#{ext}'\n") + expect_raises {{ flag?(:win32) ? File::BadExecutableError : File::AccessDeniedError }}, "Error executing process" do + Process.run(path) + end + end + end + end + end + describe "environ" do it "clears the environment" do value = Process.run(*print_env_command, clear_env: true) do |proc| diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 4c2c81d6e1f6..88d6125d9f33 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -309,6 +309,16 @@ struct Crystal::System::Process end command else + # Disable implicit execution of batch files (https://github.com/crystal-lang/crystal/issues/14536) + # + # > `CreateProcessW()` implicitly spawns `cmd.exe` when executing batch files (`.bat`, `.cmd`, etc.), even if the application didn’t specify them in the command line. + # > The problem is that the `cmd.exe` has complicated parsing rules for the command arguments, and programming language runtimes fail to escape the command arguments properly. + # > Because of this, it’s possible to inject commands if someone can control the part of command arguments of the batch file. + # https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ + if command.byte_slice?(-4, 4).try(&.downcase).in?(".bat", ".cmd") + raise ::File::Error.from_os_error("Error executing process", WinError::ERROR_BAD_EXE_FORMAT, file: command) + end + command_args = [command] command_args.concat(args) if args command_args From 440a795469c71b924c2ce2a189d7f257511e3a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 9 May 2024 14:47:52 +0200 Subject: [PATCH 1112/1551] Fix enable docs for builtin constants (#14571) --- src/compiler/crystal/program.cr | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 675202ee0144..f5abde7e7bfa 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -279,7 +279,7 @@ module Crystal else build_commit_const = define_crystal_nil_constant "BUILD_COMMIT" end - build_commit_const.doc = <<-MD if wants_doc? + build_commit_const.doc = <<-MD The build commit identifier of the Crystal compiler. MD @@ -341,9 +341,8 @@ module Crystal private def define_crystal_constant(name, value, doc = nil) : Const crystal.types[name] = const = Const.new self, crystal, name, value const.no_init_flag = true - if doc && wants_doc? - const.doc = doc - end + const.doc = doc + predefined_constants << const const end From 62a694496af1d4415c97c6f48d2982fa660b89f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 9 May 2024 14:47:59 +0200 Subject: [PATCH 1113/1551] Fix generate docs for builtins `HOST_TRIPLE` and `TARGET_TRIPLE` (#14570) --- src/compiler/crystal/tools/doc/generator.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index 1ae596809da9..d40aa8a1325e 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -227,7 +227,7 @@ class Crystal::Doc::Generator {"BUILD_COMMIT", "BUILD_DATE", "CACHE_DIR", "DEFAULT_PATH", "DESCRIPTION", "PATH", "VERSION", "LLVM_VERSION", - "LIBRARY_PATH", "LIBRARY_RPATH"}.each do |name| + "LIBRARY_PATH", "LIBRARY_RPATH", "HOST_TRIPLE", "TARGET_TRIPLE"}.each do |name| return true if type == crystal_type.types[name]? end From d83e42f60292b6c03e17c06be7b0990d95e10ea5 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Fri, 10 May 2024 08:28:36 -0400 Subject: [PATCH 1114/1551] Allow rescuing exceptions that include a module (#14553) --- spec/compiler/codegen/exception_spec.cr | 160 ++++++++++++++++++ spec/compiler/semantic/exception_spec.cr | 12 +- src/compiler/crystal/semantic/main_visitor.cr | 16 +- 3 files changed, 184 insertions(+), 4 deletions(-) diff --git a/spec/compiler/codegen/exception_spec.cr b/spec/compiler/codegen/exception_spec.cr index 70c5e331aa00..1b3bb42115ee 100644 --- a/spec/compiler/codegen/exception_spec.cr +++ b/spec/compiler/codegen/exception_spec.cr @@ -1319,4 +1319,164 @@ describe "Code gen: exception" do end )) end + + it "handles rescuing module type" do + run(%( + require "prelude" + + module Foo; end + + class Ex1 < Exception + include Foo + end + + x = 0 + begin + raise Ex1.new + rescue Foo + x = 1 + end + x + )).to_i.should eq(1) + end + + it "handles rescuing union between module type and class type" do + run(%( + require "prelude" + + module Foo; end + + abstract class BaseError < Exception; end + class Ex2 < BaseError; end + + class Ex1 < BaseError + include Foo + end + + x = 0 + begin + raise Ex1.new + rescue Foo | BaseError + x = 1 + end + x + )).to_i.should eq(1) + end + + it "handles rescuing union between module types" do + run(%( + require "prelude" + + module Foo; end + module Bar; end + + class Ex1 < Exception + include Foo + end + + class Ex2 < Exception + include Bar + end + + x = 0 + begin + raise Ex1.new + rescue Foo | Bar + x = 1 + end + x + )).to_i.should eq(1) + end + + it "does not rescue just any module" do + run(%( + require "prelude" + + module Foo; end + module Bar; end + + class Ex < Exception + include Foo + end + + x = 0 + begin + begin + raise Ex.new("oh no") + rescue Bar + x = 1 + end + rescue ex + x = 2 + end + x + )).to_i.should eq(2) + end + + it "rescues a valid union" do + run(%( + require "prelude" + + module Foo; end + module Bar; end + + class Ex < Exception + include Foo + end + + x = 0 + begin + raise Ex.new("oh no") + rescue Union(Foo, Bar) + x = 1 + end + x + )).to_i.should eq(1) + end + + it "rescues a valid nested union" do + run(%( + require "prelude" + + module Foo; end + module Bar; end + module Baz; end + + class Ex < Exception + include Foo + end + + x = 0 + begin + raise Ex.new("oh no") + rescue Union(Baz, Union(Foo, Bar)) + x = 1 + end + x + )).to_i.should eq(1) + end + + it "does not rescue just any union" do + run(%( + require "prelude" + + module Foo; end + module Bar; end + module Baz; end + + class Ex < Exception + include Foo + end + + x = 0 + begin + raise Ex.new("oh no") + rescue Union(Bar, Baz) + x = 1 + rescue + x = 2 + end + x + )).to_i.should eq(2) + end end diff --git a/spec/compiler/semantic/exception_spec.cr b/spec/compiler/semantic/exception_spec.cr index 1b5328e5edeb..7395999b0682 100644 --- a/spec/compiler/semantic/exception_spec.cr +++ b/spec/compiler/semantic/exception_spec.cr @@ -145,11 +145,19 @@ describe "Semantic: exception" do end it "errors if caught exception is not a subclass of Exception" do - assert_error "begin; rescue ex : Int32; end", "Int32 is not a subclass of Exception" + assert_error "begin; rescue ex : Int32; end", "Int32 cannot be used for `rescue`. Only subclasses of `Exception` and modules, or unions thereof, are allowed." + end + + it "errors if caught exception is a union but not all types are valid" do + assert_error "begin; rescue ex : Union(Exception, String); end", "(Exception | String) cannot be used for `rescue`. Only subclasses of `Exception` and modules, or unions thereof, are allowed." + end + + it "errors if caught exception is a nested union but not all types are valid" do + assert_error "begin; rescue ex : Union(Exception, Union(Exception, String)); end", "(Exception | String) cannot be used for `rescue`. Only subclasses of `Exception` and modules, or unions thereof, are allowed." end it "errors if caught exception is not a subclass of Exception without var" do - assert_error "begin; rescue Int32; end", "Int32 is not a subclass of Exception" + assert_error "begin; rescue Int32; end", "Int32 cannot be used for `rescue`. Only subclasses of `Exception` and modules, or unions thereof, are allowed." end assert_syntax_error "begin; rescue ex; rescue ex : Foo; end; ex", diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 61d1aa508a46..c33c64e893ff 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -2770,14 +2770,26 @@ module Crystal false end + private def allowed_type_in_rescue?(type : UnionType) : Bool + type.union_types.all? do |subtype| + allowed_type_in_rescue? subtype + end + end + + private def allowed_type_in_rescue?(type : Crystal::Type) : Bool + type.implements?(@program.exception) || type.module? + end + def visit(node : Rescue) if node_types = node.types types = node_types.map do |type| type.accept self instance_type = type.type.instance_type - unless instance_type.implements?(@program.exception) - type.raise "#{instance_type} is not a subclass of Exception" + + unless self.allowed_type_in_rescue? instance_type + type.raise "#{instance_type} cannot be used for `rescue`. Only subclasses of `Exception` and modules, or unions thereof, are allowed." end + instance_type end end From 262e3af544c4e07cd26010d1fde2d55e4ba8926b Mon Sep 17 00:00:00 2001 From: Alexander Kutsan Date: Mon, 13 May 2024 15:50:19 +0400 Subject: [PATCH 1115/1551] Set UTF-8 charset on directory listing in `HTTP::StaticFileHandler` (#14546) Co-authored-by: Sijawusz Pur Rahnama --- src/http/server/handlers/static_file_handler.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/server/handlers/static_file_handler.cr b/src/http/server/handlers/static_file_handler.cr index 12ead6963e58..cba0ff993ad2 100644 --- a/src/http/server/handlers/static_file_handler.cr +++ b/src/http/server/handlers/static_file_handler.cr @@ -86,7 +86,7 @@ class HTTP::StaticFileHandler context.response.headers["Accept-Ranges"] = "bytes" if @directory_listing && is_dir - context.response.content_type = "text/html" + context.response.content_type = "text/html; charset=utf-8" directory_listing(context.response, request_path, file_path) elsif is_file last_modified = file_info.modification_time From 085d50cd594b16317a8401abf6d5321e56467e2d Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 13 May 2024 13:50:34 +0200 Subject: [PATCH 1116/1551] Add documentation for complex methods inside Number (#14538) --- src/complex.cr | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/complex.cr b/src/complex.cr index 90c3397e06ce..65fbc9204b59 100644 --- a/src/complex.cr +++ b/src/complex.cr @@ -298,10 +298,12 @@ struct Complex end struct Number + # Returns a `Complex` object with the value of `self` as the real part. def to_c : Complex Complex.new(self, 0) end + # Returns a `Complex` object with the value of `self` as the imaginary part. def i : Complex Complex.new(0, self) end @@ -310,6 +312,9 @@ struct Number other == self end + # [Cis](https://en.wikipedia.org/wiki/Cis_(mathematics)) is a mathematical notation representing `cos x + i sin x`. + # + # Returns a `Complex` object with real part `Math.cos(self)` and imaginary part `Math.sin(self)`, where `self` represents the angle in radians. def cis : Complex Complex.new(Math.cos(self), Math.sin(self)) end From 5aad65c103191a85adaea9d2a460abf8db5bc9f7 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 13 May 2024 13:50:40 +0200 Subject: [PATCH 1117/1551] Fix doc for `Set#proper_superset_of?` (#14516) --- src/set.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/set.cr b/src/set.cr index 40425e9aa032..496d054ff74f 100644 --- a/src/set.cr +++ b/src/set.cr @@ -472,7 +472,7 @@ struct Set(T) # Returns `true` if the set is a superset of the *other* set. # - # The *other* must have the same or fewer elements than this set, and all of + # The *other* must have fewer elements than this set, and all of # elements in the *other* set must be present in this set. # # ``` From 8b2bd9829971c06bb5add3f4d83436489da967ce Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 13 May 2024 13:50:55 +0200 Subject: [PATCH 1118/1551] Fix `Set#to_a(&)` (#14519) Co-authored-by: Devonte W Co-authored-by: Sijawusz Pur Rahnama --- spec/std/set_spec.cr | 4 ++++ src/set.cr | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/std/set_spec.cr b/spec/std/set_spec.cr index 9c8a5330c573..a94e781a6060 100644 --- a/spec/std/set_spec.cr +++ b/spec/std/set_spec.cr @@ -286,6 +286,10 @@ describe "Set" do Set{1, 2, 3}.to_a.should eq([1, 2, 3]) end + it "does support giving a block to to_a" do + Set{1, 2, 3}.to_a { |x| x + 1 }.should eq([2, 3, 4]) + end + it "does to_s" do Set{1, 2, 3}.to_s.should eq("Set{1, 2, 3}") Set{"foo"}.to_s.should eq(%(Set{"foo"})) diff --git a/src/set.cr b/src/set.cr index 496d054ff74f..c998fab949a1 100644 --- a/src/set.cr +++ b/src/set.cr @@ -385,12 +385,12 @@ struct Set(T) # Returns an `Array` with the results of running *block* against each element of the collection. # # ``` - # Set{1, 2, 3, 4, 5}.to_a { |i| i // 2 } # => [0, 1, 2] + # Set{1, 2, 3, 4, 5}.to_a { |i| i // 2 } # => [0, 1, 1, 2, 2] # ``` def to_a(& : T -> U) : Array(U) forall U array = Array(U).new(size) @hash.each_key do |key| - array << key + array << yield key end array end From b76291eec08964557e22c49d66491beb1be26058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 13 May 2024 13:51:58 +0200 Subject: [PATCH 1119/1551] Add `pkg_config` names for `pcre2` and `pcre` (#14584) --- src/regex/lib_pcre.cr | 2 +- src/regex/lib_pcre2.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/regex/lib_pcre.cr b/src/regex/lib_pcre.cr index 647a172a9d43..da3ac3beb764 100644 --- a/src/regex/lib_pcre.cr +++ b/src/regex/lib_pcre.cr @@ -1,4 +1,4 @@ -@[Link("pcre")] +@[Link("pcre", pkg_config: "libpcre")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} @[Link(dll: "pcre.dll")] {% end %} diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index 71a0fd4b6639..651a1c95bef2 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -1,4 +1,4 @@ -@[Link("pcre2-8")] +@[Link("pcre2-8", pkg_config: "libpcre2-8")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} @[Link(dll: "pcre2-8.dll")] {% end %} From bf39878f56293b69f20afad7e6564f5ce96ea9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 13 May 2024 18:59:07 +0200 Subject: [PATCH 1120/1551] Fix `nix.shell` use `llvmPackages.bintools` with wrapper for rpath config (#14583) --- shell.nix | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/shell.nix b/shell.nix index 6a381f036907..fbfccdc2be5b 100644 --- a/shell.nix +++ b/shell.nix @@ -72,39 +72,39 @@ let llvm_suite = ({ llvm_16 = { llvm = pkgs.llvm_16; - extra = [ pkgs.lld_16 pkgs.lldb_16 ]; + extra = [ pkgs.llvmPackages_16.bintools pkgs.lldb_16 ]; }; llvm_15 = { llvm = pkgs.llvm_15; - extra = [ pkgs.lld_15 pkgs.lldb_15 ]; + extra = [ pkgs.llvmPackages_15.bintools pkgs.lldb_15 ]; }; llvm_14 = { llvm = pkgs.llvm_14; - extra = [ pkgs.lld_14 pkgs.lldb_14 ]; + extra = [ pkgs.llvmPackages_14.bintools pkgs.lldb_14 ]; }; llvm_13 = { llvm = pkgs.llvm_13; - extra = [ pkgs.lld_13 pkgs.lldb_13 ]; + extra = [ pkgs.llvmPackages_13.bintools pkgs.lldb_13 ]; }; llvm_12 = { llvm = pkgs.llvm_12; - extra = [ pkgs.lld_12 pkgs.lldb_12 ]; + extra = [ pkgs.llvmPackages_12.bintools pkgs.lldb_12 ]; }; llvm_11 = { llvm = pkgs.llvm_11; - extra = [ pkgs.lld_11 pkgs.lldb_11 ]; + extra = [ pkgs.llvmPackages_11.bintools pkgs.lldb_11 ]; }; llvm_10 = { llvm = pkgs.llvm_10; - extra = [ pkgs.lld_10 ]; # lldb marked as broken + extra = [ pkgs.llvmPackages_10.bintools ]; # lldb marked as broken }; llvm_9 = { llvm = pkgs.llvm_9; - extra = [ pkgs.lld_9 ]; # lldb marked as broken + extra = [ pkgs.llvmPackages_9.bintools ]; # lldb marked as broken }; llvm_8 = { llvm = pkgs.llvm_8; - extra = [ pkgs.lld_8 ]; # lldb marked as broken + extra = [ pkgs.llvmPackages_8.bintools ]; # lldb marked as broken }; }."llvm_${toString llvm}"); From 12f4e03487c342c3ea2052a923bad054fdee3118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 13 May 2024 18:59:19 +0200 Subject: [PATCH 1121/1551] Add documentation for standard streams blocking behaviour (#14577) Co-authored-by: Beta Ziliani --- src/kernel.cr | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/kernel.cr b/src/kernel.cr index 8b24f0be2bb5..8c84a197b78f 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -5,6 +5,15 @@ require "crystal/at_exit_handlers" {% end %} # The standard input file descriptor. Contains data piped to the program. +# +# On Unix systems, if the file descriptor is a TTY, the runtime duplicates it. +# So `STDIN.fd` might not be `0`. +# The reason for this is to enable non-blocking reads for concurrency. Other fibers +# can run while waiting on user input. The original file descriptor is +# inherited from the parent process. Setting it to non-blocking mode would +# reflect back, which can cause problems. +# +# On Windows, `STDIN` is always blocking. STDIN = IO::FileDescriptor.from_stdio(0) # The standard output file descriptor. @@ -22,6 +31,15 @@ STDIN = IO::FileDescriptor.from_stdio(0) # This is convenient but slower than with `flush_on_newline` set to `false`. # If you need a bit more performance and you don't care about near real-time # output you can do `STDOUT.flush_on_newline = false`. +# +# On Unix systems, if the file descriptor is a TTY, the runtime duplicates it. +# So `STDOUT.fd` might not be `1`. +# The reason for this is to enable non-blocking writes for concurrency. Other fibers +# can run while waiting on IO. The original file descriptor is +# inherited from the parent process. Setting it to non-blocking mode would +# reflect back which can cause problems. +# +# On Windows, `STDOUT` is always blocking. STDOUT = IO::FileDescriptor.from_stdio(1) # The standard error file descriptor. @@ -39,6 +57,15 @@ STDOUT = IO::FileDescriptor.from_stdio(1) # This is convenient but slower than with `flush_on_newline` set to `false`. # If you need a bit more performance and you don't care about near real-time # output you can do `STDERR.flush_on_newline = false`. +# +# On Unix systems, if the file descriptor is a TTY, the runtime duplicates it. +# So `STDERR.fd` might not be `2`. +# The reason for this is to enable non-blocking writes for concurrency. Other fibers +# can run while waiting on IO. The original file descriptor is +# inherited from the parent process. Setting it to non-blocking mode would +# reflect back which can cause problems. +# +# On Windows, `STDERR` is always blocking. STDERR = IO::FileDescriptor.from_stdio(2) # The name, the program was called with. From 8d147821172b6abc2a3e77a047c64cd98ed05aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 13 May 2024 18:59:24 +0200 Subject: [PATCH 1122/1551] Add `src/SOURCE_DATE_EPOCH` to release tree (#14574) --- Makefile | 4 ++-- Makefile.win | 4 ++-- scripts/release-update.sh | 3 +++ scripts/update-changelog.sh | 5 +++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 492b0aa762df..e56a14a27c1c 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,8 @@ SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_ou CRYSTAL_CONFIG_LIBRARY_PATH := '$$ORIGIN/../lib/crystal' CRYSTAL_CONFIG_BUILD_COMMIT ?= $(shell git rev-parse --short HEAD 2> /dev/null) CRYSTAL_CONFIG_PATH := '$$ORIGIN/../share/crystal/src' -SOURCE_DATE_EPOCH ?= $(shell (git show -s --format=%ct HEAD || stat -c "%Y" Makefile || stat -f "%m" Makefile) 2> /dev/null) +CRYSTAL_VERSION ?= $(shell cat src/VERSION) +SOURCE_DATE_EPOCH ?= $(shell (cat src/SOURCE_DATE_EPOCH || (git show -s --format=%ct HEAD || stat -c "%Y" Makefile || stat -f "%m" Makefile)) 2> /dev/null) ifeq ($(shell command -v ld.lld >/dev/null && uname -s),Linux) EXPORT_CC ?= CC="$(CC) -fuse-ld=lld" endif @@ -63,7 +64,6 @@ LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell $(LLVM_CONFIG) --version 2> /dev/nul LLVM_EXT_DIR = src/llvm/ext LLVM_EXT_OBJ = $(LLVM_EXT_DIR)/llvm_ext.o CXXFLAGS += $(if $(debug),-g -O0) -CRYSTAL_VERSION ?= $(shell cat src/VERSION) DESTDIR ?= PREFIX ?= /usr/local diff --git a/Makefile.win b/Makefile.win index 034dce59e4dc..89c0f9972a14 100644 --- a/Makefile.win +++ b/Makefile.win @@ -63,7 +63,8 @@ SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_ou CRYSTAL_CONFIG_LIBRARY_PATH := $$ORIGIN\lib CRYSTAL_CONFIG_BUILD_COMMIT := $(shell git rev-parse --short HEAD) CRYSTAL_CONFIG_PATH := $$ORIGIN\src -SOURCE_DATE_EPOCH := $(shell git show -s --format=%ct HEAD) +CRYSTAL_VERSION ?= $(shell type src\VERSION) +SOURCE_DATE_EPOCH ?= $(shell type src/SOURCE_DATE_EPOCH || git show -s --format=%ct HEAD) export_vars = $(eval export CRYSTAL_CONFIG_BUILD_COMMIT CRYSTAL_CONFIG_PATH SOURCE_DATE_EPOCH) export_build_vars = $(eval export CRYSTAL_CONFIG_LIBRARY_PATH) LLVM_CONFIG ?= @@ -71,7 +72,6 @@ LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell $(LLVM_CONFIG) --version)) LLVM_EXT_DIR = src\llvm\ext LLVM_EXT_OBJ = $(LLVM_EXT_DIR)\llvm_ext.obj CXXFLAGS += $(if $(static),$(if $(debug),/MTd /Od ,/MT ),$(if $(debug),/MDd /Od ,/MD )) -CRYSTAL_VERSION ?= $(shell type src\VERSION) prefix ?= $(or $(ProgramW6432),$(ProgramFiles))\crystal BINDIR ?= $(prefix) diff --git a/scripts/release-update.sh b/scripts/release-update.sh index d705ce357f3e..c9fa180f6578 100755 --- a/scripts/release-update.sh +++ b/scripts/release-update.sh @@ -16,6 +16,9 @@ minor_branch="${CRYSTAL_VERSION%.*}" next_minor="$((${minor_branch#*.} + 1))" echo "${CRYSTAL_VERSION%%.*}.${next_minor}.0-dev" > src/VERSION +# Remove SOURCE_DATE_EPOCH (only used in source tree of a release) +rm -f src/SOURCE_DATE_EPOCH + # Edit PREVIOUS_CRYSTAL_BASE_URL in .circleci/config.yml sed -i -E "s|[0-9.]+/crystal-[0-9.]+-[0-9]|$CRYSTAL_VERSION/crystal-$CRYSTAL_VERSION-1|g" .circleci/config.yml diff --git a/scripts/update-changelog.sh b/scripts/update-changelog.sh index 3d044eb79062..6fe0fa2839f3 100755 --- a/scripts/update-changelog.sh +++ b/scripts/update-changelog.sh @@ -44,6 +44,11 @@ git switch $branch 2>/dev/null || git switch -c $branch; echo "${VERSION}" > src/VERSION git add src/VERSION +# Write release date into src/SOURCE_DATE_EPOCH +release_date=$(head -n1 $current_changelog | grep -o -P '(?<=\()[^)]+') +echo "$(date --utc --date="${release_date}" +%s)" > src/SOURCE_DATE_EPOCH +git add src/SOURCE_DATE_EPOCH + if grep --silent -E "^## \[$VERSION\]" CHANGELOG.md; then echo "Replacing section in CHANGELOG" From 8661e70847864a9b6e5acc28ed69a69c77b13b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 13 May 2024 19:00:47 +0200 Subject: [PATCH 1123/1551] Update distribution-scripts (#14562) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1c14a482d2b4..a6fe71b0faa6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "46c295d14e4dc3f6b7d9598c0b4dd89e93232def" + default: "8c3a9f6bf64499f1e3d1838d2ae9b967d5e7f796" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From 90d1e4a7878ba6a2dec67480b78a8d72329163da Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 14 May 2024 19:12:08 +0800 Subject: [PATCH 1124/1551] Fix indentation of parenthesized `Expressions#to_s` (#14511) --- spec/compiler/parser/to_s_spec.cr | 9 +++++++-- src/compiler/crystal/syntax/to_s.cr | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index 5235e516d73d..d7d33db11e09 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -210,6 +210,9 @@ describe "ASTNode#to_s" do expect_to_s %(if (1 + 2\n3)\n 4\nend) expect_to_s "%x(whoami)", "`whoami`" expect_to_s %(begin\n ()\nend) + expect_to_s %(begin\n (1)\nend) + expect_to_s %(begin\n (@x = x).is_a?(Foo)\nend) + expect_to_s %(begin\n (1)\n 2\nend) expect_to_s %(if 1\n begin\n 2\n end\nelse\n begin\n 3\n end\nend) expect_to_s %(foo do\n begin\n bar\n end\nend) expect_to_s %q("\e\0\""), %q("\e\u0000\"") @@ -256,8 +259,10 @@ describe "ASTNode#to_s" do expect_to_s "offsetof(Foo, @bar)" expect_to_s "def foo(**options, &block)\nend" expect_to_s "macro foo\n 123\nend" - expect_to_s "if true\n( 1)\nend" - expect_to_s "begin\n( 1)\nrescue\nend" + expect_to_s "if true\n (1)\nend" + expect_to_s "if true\n (1)\n 2\nend" + expect_to_s "begin\n (1)\nrescue\nend" + expect_to_s "begin\n (1)\n 2\nrescue\nend" expect_to_s %[他.说("你好")] expect_to_s %[他.说 = "你好"] expect_to_s %[あ.い, う.え.お = 1, 2] diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 63a23ded4fea..6e7fa5d1af9d 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -223,7 +223,7 @@ module Crystal else node.expressions.each_with_index do |exp, i| unless exp.nop? - append_indent + append_indent unless node.keyword.paren? && i == 0 exp.accept self newline unless node.keyword.paren? && i == node.expressions.size - 1 end @@ -1572,7 +1572,7 @@ module Crystal def accept_with_indent(node : Expressions) with_indent do - append_indent if node.keyword.begin? + append_indent unless node.keyword.none? node.accept self end newline unless node.keyword.none? From 9ef636644100b3ad41ab6094da20dbd506e439c6 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 14 May 2024 08:12:20 -0300 Subject: [PATCH 1125/1551] Optimize JSON parsing a bit (#14366) --- spec/std/json/parser_spec.cr | 7 +++- spec/std/json/pull_parser_spec.cr | 4 +-- src/json/lexer.cr | 22 ++++++++++++- src/json/lexer/io_based.cr | 53 +++++++++++++++++++++++++++++-- src/json/lexer/string_based.cr | 22 +++++++------ src/json/token.cr | 21 ++++++++++-- 6 files changed, 110 insertions(+), 19 deletions(-) diff --git a/spec/std/json/parser_spec.cr b/spec/std/json/parser_spec.cr index 96cfd52277a2..7b8578f522c7 100644 --- a/spec/std/json/parser_spec.cr +++ b/spec/std/json/parser_spec.cr @@ -2,9 +2,13 @@ require "spec" require "json" private def it_parses(string, expected_value, file = __FILE__, line = __LINE__) - it "parses #{string}", file, line do + it "parses #{string} from String", file, line do JSON.parse(string).raw.should eq(expected_value) end + + it "parses #{string} from IO", file, line do + JSON.parse(IO::Memory.new(string)).raw.should eq(expected_value) + end end private def it_raises_on_parse(string, file = __FILE__, line = __LINE__) @@ -31,6 +35,7 @@ describe JSON::Parser do it_parses "[true]", [true] it_parses "[false]", [false] it_parses %(["hello"]), ["hello"] + it_parses %(["hello", 1]), ["hello", 1] it_parses "[0]", [0] it_parses " [ 0 ] ", [0] diff --git a/spec/std/json/pull_parser_spec.cr b/spec/std/json/pull_parser_spec.cr index 8de524e86c87..28ef4c6cf527 100644 --- a/spec/std/json/pull_parser_spec.cr +++ b/spec/std/json/pull_parser_spec.cr @@ -92,8 +92,8 @@ class JSON::PullParser end end -private def assert_pull_parse(string) - it "parses #{string}" do +private def assert_pull_parse(string, file = __FILE__, line = __LINE__) + it "parses #{string}", file, line do parser = JSON::PullParser.new string parser.assert JSON.parse(string).raw parser.kind.should eq(JSON::PullParser::Kind::EOF) diff --git a/src/json/lexer.cr b/src/json/lexer.cr index 3e61179b9844..067d595aeff3 100644 --- a/src/json/lexer.cr +++ b/src/json/lexer.cr @@ -220,8 +220,16 @@ abstract class JSON::Lexer private def consume_number number_start + # Integer values of up to 18 digits can be computed by doing math: + # no need to store a string value and later parse it. + # For larger numbers, or floats, we store the entire string and later parse it. + @token.int_value = nil + integer = 0_i64 + negative = false + if current_char == '-' append_number_char + negative = true next_char end @@ -238,13 +246,19 @@ abstract class JSON::Lexer unexpected_char else @token.kind = :int + @token.int_value = 0 number_end end when '1'..'9' append_number_char + digits = 1 + integer = (current_char - '0').to_i64 char = next_char while '0' <= char <= '9' append_number_char + digits += 1 + integer &*= 10 + integer &+= char - '0' char = next_char end @@ -255,7 +269,13 @@ abstract class JSON::Lexer consume_exponent else @token.kind = :int - number_end + # Int64::MAX is 9223372036854775807 which has 19 digits. + # With 18 digits we know the number we computed is the one we read. + if digits > 18 + number_end + else + @token.int_value = negative ? -integer : integer + end end else unexpected_char diff --git a/src/json/lexer/io_based.cr b/src/json/lexer/io_based.cr index d3989c54f0a8..f1f5346306db 100644 --- a/src/json/lexer/io_based.cr +++ b/src/json/lexer/io_based.cr @@ -2,17 +2,64 @@ class JSON::Lexer::IOBased < JSON::Lexer def initialize(@io : IO) super() - @current_char = @io.read_char || '\0' + @current_char = @io.read_byte.try(&.chr) || '\0' end private getter current_char private def next_char_no_column_increment - @current_char = @io.read_char || '\0' + @current_char = @io.read_byte.try(&.chr) || '\0' end private def consume_string - consume_string_with_buffer + peek = @io.peek + if !peek || peek.empty? + return consume_string_with_buffer + end + + pos = 0 + + while true + if pos >= peek.size + # We don't have enough data in the peek buffer to create a string: + # default to the slow method + return consume_string_with_buffer + end + + char = peek[pos] + case char + when '\\' + # If we find an escape character, go to the slow method + @column_number += pos + return consume_string_at_escape_char(peek, pos) + when '"' + break + else + if 0 <= current_char.ord < 32 + unexpected_char + else + pos += 1 + end + end + end + + @column_number += pos + @token.string_value = + if @expects_object_key + @string_pool.get(peek.to_unsafe, pos) + else + String.new(peek.to_unsafe, pos) + end + + @io.skip(pos + 1) + next_char + end + + private def consume_string_at_escape_char(peek, pos) + consume_string_with_buffer do + @buffer.write peek[0, pos] + @io.skip(pos) + end end private def number_start diff --git a/src/json/lexer/string_based.cr b/src/json/lexer/string_based.cr index 5696bc6f78b2..d8b3b64f1940 100644 --- a/src/json/lexer/string_based.cr +++ b/src/json/lexer/string_based.cr @@ -1,8 +1,9 @@ # :nodoc: class JSON::Lexer::StringBased < JSON::Lexer - def initialize(string) + def initialize(string : String) super() - @reader = Char::Reader.new(string) + @string = string + @pos = 0 @number_start = 0 end @@ -33,7 +34,7 @@ class JSON::Lexer::StringBased < JSON::Lexer if @expects_object_key start_pos += 1 end_pos = current_pos - 1 - @token.string_value = @string_pool.get(@reader.string.to_unsafe + start_pos, end_pos - start_pos) + @token.string_value = @string_pool.get(@string.to_unsafe + start_pos, end_pos - start_pos) else @token.string_value = string_range(start_pos + 1, current_pos - 1) end @@ -47,27 +48,30 @@ class JSON::Lexer::StringBased < JSON::Lexer end private def current_pos - @reader.pos + @pos end def string_range(start_pos, end_pos) : String - @reader.string.byte_slice(start_pos, end_pos - start_pos) + @string.byte_slice(start_pos, end_pos - start_pos) end def slice_range(start_pos, end_pos) : Bytes - @reader.string.to_slice[start_pos, end_pos - start_pos] + @string.to_slice[start_pos, end_pos - start_pos] end private def next_char_no_column_increment - char = @reader.next_char - if char == '\0' && @reader.pos != @reader.string.bytesize + @pos += 1 + + char = current_char + if char == '\0' && @pos != @string.bytesize unexpected_char end + char end private def current_char - @reader.current_char + @string.to_unsafe[@pos].chr end private def number_start diff --git a/src/json/token.cr b/src/json/token.cr index 436709aec233..f1862ce676f5 100644 --- a/src/json/token.cr +++ b/src/json/token.cr @@ -19,7 +19,7 @@ class JSON::Token property string_value : String def int_value : Int64 - raw_value.to_i64 + @int_value || raw_value.to_i64 rescue exc : ArgumentError raise ParseException.new(exc.message, line_number, column_number) end @@ -32,7 +32,8 @@ class JSON::Token property line_number : Int32 property column_number : Int32 - property raw_value : String + setter raw_value : String + setter int_value : Int64? def initialize @kind = :EOF @@ -40,6 +41,16 @@ class JSON::Token @column_number = 0 @string_value = "" @raw_value = "" + @int_value = nil + end + + def raw_value + case @kind + when .int? + @int_value.try(&.to_s) || @raw_value + else + @raw_value + end end def to_s(io : IO) : Nil @@ -51,7 +62,11 @@ class JSON::Token when .true? io << "true" when .int? - raw_value.to_s(io) + if int_value = @int_value + int_value.to_s(io) + else + raw_value.to_s(io) + end when .float? raw_value.to_s(io) when .string? From 361f7ea6daa44ee3028b7b9d7593e03b5478b1a1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 14 May 2024 19:12:32 +0800 Subject: [PATCH 1126/1551] Replace `Crystal::Select::When` with `Crystal::When` (#14497) --- spec/compiler/parser/parser_spec.cr | 6 +++--- src/compiler/crystal/semantic/literal_expander.cr | 2 +- src/compiler/crystal/syntax/ast.cr | 11 +++++------ src/compiler/crystal/syntax/parser.cr | 4 ++-- src/compiler/crystal/syntax/to_s.cr | 2 +- src/compiler/crystal/syntax/transformer.cr | 4 +--- src/compiler/crystal/tools/formatter.cr | 2 +- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 0a5f5a8ab2c2..22e9c5feb385 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1460,9 +1460,9 @@ module Crystal it_parses "case 1; when 2 then /foo/; end", Case.new(1.int32, [When.new([2.int32] of ASTNode, RegexLiteral.new("foo".string))], else: nil, exhaustive: false) - it_parses "select\nwhen foo\n2\nend", Select.new([Select::When.new("foo".call, 2.int32)]) - it_parses "select\nwhen foo\n2\nwhen bar\n4\nend", Select.new([Select::When.new("foo".call, 2.int32), Select::When.new("bar".call, 4.int32)]) - it_parses "select\nwhen foo\n2\nelse\n3\nend", Select.new([Select::When.new("foo".call, 2.int32)], 3.int32) + it_parses "select\nwhen foo\n2\nend", Select.new([When.new("foo".call, 2.int32)]) + it_parses "select\nwhen foo\n2\nwhen bar\n4\nend", Select.new([When.new("foo".call, 2.int32), When.new("bar".call, 4.int32)]) + it_parses "select\nwhen foo\n2\nelse\n3\nend", Select.new([When.new("foo".call, 2.int32)], 3.int32) assert_syntax_error "select\nwhen 1\n2\nend", "invalid select when expression: must be an assignment or call" diff --git a/src/compiler/crystal/semantic/literal_expander.cr b/src/compiler/crystal/semantic/literal_expander.cr index 995595d8480b..c68379b71b7a 100644 --- a/src/compiler/crystal/semantic/literal_expander.cr +++ b/src/compiler/crystal/semantic/literal_expander.cr @@ -692,7 +692,7 @@ module Crystal case_whens = [] of When node.whens.each_with_index do |a_when, index| - condition = a_when.condition + condition = a_when.conds.first case condition when Call cloned_call = condition.clone diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 9c0ffcf0d7e5..f6d314371034 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -1322,6 +1322,10 @@ module Crystal @body = Expressions.from body end + def self.new(cond : ASTNode, body : ASTNode? = nil, exhaustive = false) + new([cond] of ASTNode, body, exhaustive) + end + def accept_children(visitor) @conds.each &.accept visitor @body.accept visitor @@ -1360,8 +1364,6 @@ module Crystal end class Select < ASTNode - record When, condition : ASTNode, body : ASTNode - property whens : Array(When) property else : ASTNode? @@ -1369,10 +1371,7 @@ module Crystal end def accept_children(visitor) - @whens.each do |select_when| - select_when.condition.accept visitor - select_when.body.accept visitor - end + @whens.each &.accept visitor @else.try &.accept visitor end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index df5b151e457a..bd7c67a975b8 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -2955,7 +2955,7 @@ module Crystal next_token_skip_space skip_statement_end - whens = [] of Select::When + whens = [] of When while true case @token.value @@ -2978,7 +2978,7 @@ module Crystal body = parse_expressions skip_space_or_newline - whens << Select::When.new(condition, body) + whens << When.new(condition, body) when Keyword::ELSE if whens.size == 0 unexpected_token "expecting when" diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 6e7fa5d1af9d..271f003824b1 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -1382,7 +1382,7 @@ module Crystal node.whens.each do |a_when| append_indent @str << "when " - a_when.condition.accept self + a_when.conds.first.accept self newline accept_with_indent a_when.body end diff --git a/src/compiler/crystal/syntax/transformer.cr b/src/compiler/crystal/syntax/transformer.cr index 1dc584ebfa17..95dcfdcf17fd 100644 --- a/src/compiler/crystal/syntax/transformer.cr +++ b/src/compiler/crystal/syntax/transformer.cr @@ -230,9 +230,7 @@ module Crystal end def transform(node : Select) - node.whens.map! do |a_when| - Select::When.new(a_when.condition.transform(self), a_when.body.transform(self)) - end + transform_many node.whens if node_else = node.else node.else = node_else.transform(self) diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index a07fd3347f83..614ecc836637 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -3887,7 +3887,7 @@ module Crystal write_keyword :when skip_space_or_newline(@indent + 2) write " " - a_when.condition.accept self + a_when.conds.first.accept self found_comment = skip_space(@indent + 2) if @token.type.op_semicolon? || @token.keyword?(:then) sep = @token.type.op_semicolon? ? "; " : " then " From 2b3f9de291210ea4be1aad6f2a58e39eeb6a9333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 14 May 2024 17:46:45 +0200 Subject: [PATCH 1127/1551] Remove `SetShouldExit` in Powershell wrapper (#13769) --- bin/crystal.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/crystal.ps1 b/bin/crystal.ps1 index 16940094439f..0ff2e12066ee 100644 --- a/bin/crystal.ps1 +++ b/bin/crystal.ps1 @@ -238,8 +238,6 @@ function Exec-Process { $hnd = $Process.Handle Wait-Process -Id $Process.Id - # and return it properly too: https://stackoverflow.com/a/50202663 - $host.SetShouldExit($Process.ExitCode) Exit $Process.ExitCode } From c0aca0d8534d1f59c160c27ec749231df3c397dd Mon Sep 17 00:00:00 2001 From: Margret Riegert Date: Tue, 14 May 2024 11:47:07 -0400 Subject: [PATCH 1128/1551] Sanitize doc generation (#13786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- lib/.shards.info | 3 + lib/sanitize/.circleci/config.yml | 94 ++++ lib/sanitize/.editorconfig | 9 + lib/sanitize/.gitignore | 9 + lib/sanitize/LICENSE | 202 ++++++++ lib/sanitize/Makefile | 54 ++ lib/sanitize/README.md | 128 +++++ lib/sanitize/lib | 1 + lib/sanitize/scripts/generate-docs.sh | 18 + lib/sanitize/shard.yml | 15 + lib/sanitize/spec/html_sanitizer/basic.hrx | 70 +++ lib/sanitize/spec/html_sanitizer/class.hrx | 34 ++ .../spec/html_sanitizer/combined_policies.hrx | 42 ++ .../html_sanitizer/combined_policies_spec.cr | 11 + lib/sanitize/spec/html_sanitizer/default.hrx | 138 +++++ .../html_sanitizer/html_sanitizer_spec.cr | 102 ++++ lib/sanitize/spec/html_sanitizer/img.hrx | 46 ++ lib/sanitize/spec/html_sanitizer/links.hrx | 89 ++++ .../protocol-based-javascript.hrx | 160 ++++++ .../html_sanitizer/protocol_javascript.hrx | 67 +++ lib/sanitize/spec/html_sanitizer/url_spec.cr | 8 + lib/sanitize/spec/html_sanitizer/xss.hrx | 476 ++++++++++++++++++ lib/sanitize/spec/spec_helper.cr | 1 + lib/sanitize/spec/support/hrx.cr | 83 +++ lib/sanitize/spec/text_policy.hrx | 67 +++ lib/sanitize/spec/text_policy_spec.cr | 17 + lib/sanitize/spec/uri_sanitizer_spec.cr | 130 +++++ lib/sanitize/src/adapter/libxml2.cr | 137 +++++ lib/sanitize/src/policy.cr | 45 ++ lib/sanitize/src/policy/html_sanitizer.cr | 350 +++++++++++++ .../src/policy/html_sanitizer/safelist.cr | 70 +++ lib/sanitize/src/policy/text.cr | 23 + lib/sanitize/src/policy/whitelist.cr | 57 +++ lib/sanitize/src/processor.cr | 110 ++++ lib/sanitize/src/sanitize.cr | 5 + lib/sanitize/src/uri_sanitizer.cr | 107 ++++ shard.lock | 4 + shard.yml | 5 +- .../crystal/tools/doc/doc_renderer_spec.cr | 30 +- .../crystal/tools/doc/markd_doc_renderer.cr | 23 + 40 files changed, 3037 insertions(+), 3 deletions(-) create mode 100644 lib/sanitize/.circleci/config.yml create mode 100644 lib/sanitize/.editorconfig create mode 100644 lib/sanitize/.gitignore create mode 100644 lib/sanitize/LICENSE create mode 100644 lib/sanitize/Makefile create mode 100644 lib/sanitize/README.md create mode 120000 lib/sanitize/lib create mode 100755 lib/sanitize/scripts/generate-docs.sh create mode 100644 lib/sanitize/shard.yml create mode 100644 lib/sanitize/spec/html_sanitizer/basic.hrx create mode 100644 lib/sanitize/spec/html_sanitizer/class.hrx create mode 100644 lib/sanitize/spec/html_sanitizer/combined_policies.hrx create mode 100644 lib/sanitize/spec/html_sanitizer/combined_policies_spec.cr create mode 100644 lib/sanitize/spec/html_sanitizer/default.hrx create mode 100644 lib/sanitize/spec/html_sanitizer/html_sanitizer_spec.cr create mode 100644 lib/sanitize/spec/html_sanitizer/img.hrx create mode 100644 lib/sanitize/spec/html_sanitizer/links.hrx create mode 100644 lib/sanitize/spec/html_sanitizer/protocol-based-javascript.hrx create mode 100644 lib/sanitize/spec/html_sanitizer/protocol_javascript.hrx create mode 100644 lib/sanitize/spec/html_sanitizer/url_spec.cr create mode 100644 lib/sanitize/spec/html_sanitizer/xss.hrx create mode 100644 lib/sanitize/spec/spec_helper.cr create mode 100644 lib/sanitize/spec/support/hrx.cr create mode 100644 lib/sanitize/spec/text_policy.hrx create mode 100644 lib/sanitize/spec/text_policy_spec.cr create mode 100644 lib/sanitize/spec/uri_sanitizer_spec.cr create mode 100644 lib/sanitize/src/adapter/libxml2.cr create mode 100644 lib/sanitize/src/policy.cr create mode 100644 lib/sanitize/src/policy/html_sanitizer.cr create mode 100644 lib/sanitize/src/policy/html_sanitizer/safelist.cr create mode 100644 lib/sanitize/src/policy/text.cr create mode 100644 lib/sanitize/src/policy/whitelist.cr create mode 100644 lib/sanitize/src/processor.cr create mode 100644 lib/sanitize/src/sanitize.cr create mode 100644 lib/sanitize/src/uri_sanitizer.cr diff --git a/lib/.shards.info b/lib/.shards.info index 7f03bb906410..b15cf54122a0 100644 --- a/lib/.shards.info +++ b/lib/.shards.info @@ -7,3 +7,6 @@ shards: reply: git: https://github.com/i3oris/reply.git version: 0.3.1+git.commit.90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 + sanitize: + git: https://github.com/straight-shoota/sanitize.git + version: 0.1.0+git.commit.75c141b619c77956e88f557149566cd28876398b diff --git a/lib/sanitize/.circleci/config.yml b/lib/sanitize/.circleci/config.yml new file mode 100644 index 000000000000..df9b752af31d --- /dev/null +++ b/lib/sanitize/.circleci/config.yml @@ -0,0 +1,94 @@ +version: 2 + +dry: + restore_shards_cache: &restore_shards_cache + keys: + - shards-cache-v1-{{ .Branch }}-{{ checksum "shard.yml" }} + - shards-cache-v1-{{ .Branch }} + - shards-cache-v1 + + save_shards_cache: &save_shards_cache + key: shards-cache-v1-{{ .Branch }}-{{ checksum "shard.yml" }} + paths: + - ./shards-cache + +jobs: + test: + docker: + - image: crystallang/crystal:latest + environment: + SHARDS_CACHE_PATH: ./shards-cache + steps: + - run: crystal --version + + - checkout + + - restore_cache: *restore_shards_cache + - run: shards + - save_cache: *save_shards_cache + + - run: make test + + - run: crystal tool format --check spec src + + deploy-docs: + docker: + - image: crystallang/crystal:latest + environment: + SHARDS_CACHE_PATH: ./shards-cache + steps: + - run: crystal --version + + - checkout + + - run: scripts/generate-docs.sh + + - run: apt update && apt install -y curl rsync + - run: + command: curl https://raw.githubusercontent.com/straight-shoota/autodeploy-docs/master/autodeploy-docs.sh | bash + environment: + GIT_COMMITTER_NAME: cirlceci + GIT_COMMITTER_EMAIL: circle@circleci.com + + test-on-nightly: + docker: + - image: crystallang/crystal:nightly + environment: + SHARDS_CACHE_PATH: ./shards-cache + steps: + - run: crystal --version + + - checkout + + - restore_cache: *restore_shards_cache + - run: shards + + - run: make test + + - run: crystal tool format --check spec src + +workflows: + version: 2 + # Run tests on every single commit + ci: + jobs: + - test + # Build and depoy docs only on master branch + - deploy-docs: + requires: + - test + filters: &master-only + branches: + only: + - master + # Run tests every night using crystal nightly + nightly: + triggers: + - schedule: + cron: "0 4 * * *" + filters: + branches: + only: + - master + jobs: + - test-on-nightly diff --git a/lib/sanitize/.editorconfig b/lib/sanitize/.editorconfig new file mode 100644 index 000000000000..163eb75c8525 --- /dev/null +++ b/lib/sanitize/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.cr] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/lib/sanitize/.gitignore b/lib/sanitize/.gitignore new file mode 100644 index 000000000000..0bbd4a9f41e1 --- /dev/null +++ b/lib/sanitize/.gitignore @@ -0,0 +1,9 @@ +/docs/ +/lib/ +/bin/ +/.shards/ +*.dwarf + +# Libraries don't need dependency lock +# Dependencies will be locked in applications that use them +/shard.lock diff --git a/lib/sanitize/LICENSE b/lib/sanitize/LICENSE new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/lib/sanitize/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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/lib/sanitize/Makefile b/lib/sanitize/Makefile new file mode 100644 index 000000000000..980fc7a52014 --- /dev/null +++ b/lib/sanitize/Makefile @@ -0,0 +1,54 @@ +-include Makefile.local # for optional local options + +BUILD_TARGET ::= bin/app + +# The shards command to use +SHARDS ?= shards +# The crystal command to use +CRYSTAL ?= crystal + +SRC_SOURCES ::= $(shell find src -name '*.cr' 2>/dev/null) +LIB_SOURCES ::= $(shell find lib -name '*.cr' 2>/dev/null) +SPEC_SOURCES ::= $(shell find spec -name '*.cr' 2>/dev/null) + +.PHONY: test +test: ## Run the test suite +test: lib + $(CRYSTAL) spec + +.PHONY: format +format: ## Apply source code formatting +format: $(SRC_SOURCES) $(SPEC_SOURCES) + $(CRYSTAL) tool format src spec + +docs: ## Generate API docs +docs: $(SRC_SOURCES) lib + $(CRYSTAL) docs -o docs + +lib: shard.lock + $(SHARDS) install + +shard.lock: shard.yml + $(SHARDS) update + +.PHONY: clean +clean: ## Remove application binary +clean: + @rm -f $(BUILD_TARGET) + +.PHONY: help +help: ## Show this help + @echo + @printf '\033[34mtargets:\033[0m\n' + @grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) |\ + sort |\ + awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' + @echo + @printf '\033[34moptional variables:\033[0m\n' + @grep -hE '^[a-zA-Z_-]+ \?=.*?## .*$$' $(MAKEFILE_LIST) |\ + sort |\ + awk 'BEGIN {FS = " \\?=.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' + @echo + @printf '\033[34mrecipes:\033[0m\n' + @grep -hE '^##.*$$' $(MAKEFILE_LIST) |\ + awk 'BEGIN {FS = "## "}; /^## [a-zA-Z_-]/ {printf " \033[36m%s\033[0m\n", $$2}; /^## / {printf " %s\n", $$2}' diff --git a/lib/sanitize/README.md b/lib/sanitize/README.md new file mode 100644 index 000000000000..fdca90db33a1 --- /dev/null +++ b/lib/sanitize/README.md @@ -0,0 +1,128 @@ +# sanitize + +`sanitize` is a Crystal library for transforming HTML/XML trees. It's primarily +used to sanitize HTML from untrusted sources in order to prevent +[XSS attacks](http://en.wikipedia.org/wiki/Cross-site_scripting) and other +adversities. + +It builds on stdlib's [`XML`](https://crystal-lang.org/api/XML.html) module to +parse HTML/XML. Based on [libxml2](http://xmlsoft.org/) it's a solid parser and +turns malformed and malicious input into valid and safe markup. + +* Code: [https://github.com/straight-shoota/sanitize](https://github.com/straight-shoota/sanitize) +* API docs: [https://straight-shoota.github.io/sanitize/api/latest/](https://straight-shoota.github.io/sanitize/api/latest/) +* Issue tracker: [https://github.com/straight-shoota/sanitize/issues](https://github.com/straight-shoota/sanitize/issues) +* Shardbox: [https://shardbox.org/shards/sanitize](https://shardbox.org/shards/sanitize) + +## Installation + +1. Add the dependency to your `shard.yml`: + + ```yaml + dependencies: + sanitize: + github: straight-shoota/sanitize + ``` + +2. Run `shards install` + +## Sanitization Features + +The `Sanitize::Policy::HTMLSanitizer` policy applies the following sanitization steps. Except +for the first one (which is essential to the entire process), all can be disabled +or configured. + +* Turns malformed and malicious HTML into valid and safe markup. +* Strips HTML elements and attributes not included in the safe list. +* Sanitizes URL attributes (like `href` or `src`) with customizable sanitization + policy. +* Adds `rel="nofollow"` to all links and `rel="noopener"` to links with `target`. +* Validates values of accepted attributes `align`, `width` and `height`. +* Filters `class` attributes based on a whitelist (by default all classes are + rejected). + +## Usage + +Transformation is based on rules defined by `Sanitize::Policy` implementations. + +The recommended standard policy for HTML sanitization is `Sanitize::Policy::HTMLSanitizer.common` +which represents good defaults for most use cases. +It sanitizes user input against a known safe list of accepted elements and their +attributes. + +```crystal +require "sanitize" + +sanitizer = Sanitize::Policy::HTMLSanitizer.common +sanitizer.process(%(foo)) # => %(foo) +sanitizer.process(%(

    foo

    )) # => %(

    foo

    ) +sanitizer.process(%()) # => %() +sanitizer.process(%(
    foobar
    )) # => %(
    foobar
    ) +``` + +Sanitization should always run after any other processing (for example rendering +Markdown) and is a must when including HTML from untrusted sources into a web +page. + +### With Markd + +A typical format for user generated content is `Markdown`. Even though it has +only a very limited feature set compared to HTML, it can still produce +potentially harmful HTML and is is usually possible to embed raw HTML directly. +So Sanitization is necessary. + +The most common Markdown renderer is [markd](https://shardbox.org/shards/markd), +so here is a sample how to use it with `sanitize`: + +````crystal +sanitizer = Sanitize::Policy::HTMLSanitizer.common +# Allow classes with `language-` prefix which are used for syntax highlighting. +sanitizer.valid_classes << /language-.+/ + +markdown = <<-MD + Sanitization with [https://shardbox.org/shards/sanitize](sanitize) is not that + **difficult**. + ```cr + puts "Hello World!" + ``` +

    Hello world!

    + MD + +html = Markd.to_html(markdown) +sanitized = sanitizer.process(html) +puts sanitized +```` + +The result: + +```html +

    Sanitization with https://shardbox.org/shards/sanitize is not that +difficult.

    +
    puts "Hello World!"
    +
    +

    Hello world!

    +``` + +## Limitations + +Sanitizing CSS is not supported. Thus `style` attributes can't be accepted in a +safe way. +CSS sanitization features may be added when a CSS parsing library is available. + +## Security + +If you want to privately disclose security-issues, please contact +[straightshoota](https://keybase.io/straightshoota) on Keybase or +[straightshoota@gmail.com](mailto:straightshoota@gmail.com) (PGP: `DF2D C9E9 FFB9 6AE0 2070 D5BC F0F3 4963 7AC5 087A`). + +## Contributing + +1. Fork it ([https://github.com/straight-shoota/sanitize/fork](https://github.com/straight-shoota/sanitize/fork)) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request + +## Contributors + +- [Johannes Müller](https://github.com/straight-shoota) - creator and maintainer diff --git a/lib/sanitize/lib b/lib/sanitize/lib new file mode 120000 index 000000000000..a96aa0ea9d8c --- /dev/null +++ b/lib/sanitize/lib @@ -0,0 +1 @@ +.. \ No newline at end of file diff --git a/lib/sanitize/scripts/generate-docs.sh b/lib/sanitize/scripts/generate-docs.sh new file mode 100755 index 000000000000..5dbaf344c48d --- /dev/null +++ b/lib/sanitize/scripts/generate-docs.sh @@ -0,0 +1,18 @@ +#! /usr/bin/env bash + +set -e + +GENERATED_DOCS_DIR="./docs" + +echo -e "Building docs into ${GENERATED_DOCS_DIR}" +echo -e "Clearing ${GENERATED_DOCS_DIR} directory" +rm -rf "${GENERATED_DOCS_DIR}" + +echo -e "Running \`make docs\`..." +make docs + +echo -e "Copying README.md" + +# "{{" and "{%"" need to be escaped, otherwise Jekyll might interpret the expressions (on Github Pages) +ESCAPE_TEMPLATE='s/{{/{{"{{"}}/g; s/{\%/{{"{%"}}/g;' +sed "${ESCAPE_TEMPLATE}" README.md > "${GENERATED_DOCS_DIR}/README.md" diff --git a/lib/sanitize/shard.yml b/lib/sanitize/shard.yml new file mode 100644 index 000000000000..eb9158fc58e4 --- /dev/null +++ b/lib/sanitize/shard.yml @@ -0,0 +1,15 @@ +name: sanitize +version: 0.1.0 + +authors: + - Johannes Müller + +crystal: 0.35.0 + +license: Apache-2.0 + +documentation: https://straight-shoota.github.io/sanitize/api/latest/ + +development_dependencies: + hrx: + github: straight-shoota/hrx diff --git a/lib/sanitize/spec/html_sanitizer/basic.hrx b/lib/sanitize/spec/html_sanitizer/basic.hrx new file mode 100644 index 000000000000..fe291053b1dc --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/basic.hrx @@ -0,0 +1,70 @@ +<===> empty/document.html +<===> + + +<===> pending:skeleton/document.html + + + + + +<===> + + +<===> invalid/fragment.html +foo

    bar

    bazz
    quux
    +<===> invalid/common.html +foo

    bar

    bazz
    quux
    +<===> + + + +<===> invalid-div/fragment.html +foo

    bar

    bazz
    quux
    +<===> invalid-div/common.html +foo

    bar

    bazz quux +<===> + + +<===> basic/fragment.html +Lorem ipsum dolor sit
    amet +<===> basic/common.html +Lorem ipsum dolor sit
    amet +<===> + + +<===> malformed/fragment.html +Lorem
    dolor sit
    amet +<===> malicious/common.html +Lorem ipsum dolor sit
    amet <script>alert("hello world"); +<===> + + +<===> target="_blank"/fragment.html +foo +<===> target="_blank"/common.html +foo +<===> + + +<===> percent encoded URL/fragment.html +CI Status +<===> percent encoded URL/common.html +CI Status +<===> diff --git a/lib/sanitize/spec/html_sanitizer/class.hrx b/lib/sanitize/spec/html_sanitizer/class.hrx new file mode 100644 index 000000000000..897c1c2f4606 --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/class.hrx @@ -0,0 +1,34 @@ +<===> reject/fragment.html +
    +<===> reject/common.html +
    +<===> reject/allow-prefix.html +
    +<===> + + +<===> allow-with-prefix/fragment.html +
    +<===> allow-with-prefix/common.html +
    +<===> allow-with-prefix/allow-prefix.html +
    +<===> + + +<===> reject-non-prefix/fragment.html +
    +<===> reject-non-prefix/common.html +
    +<===> reject-non-prefix/allow-prefix.html +
    +<===> + + +<===> allow-explicit/fragment.html +
    +<===> allow-explicit/common.html +
    +<===> allow-explicit/allow-prefix.html +
    +<===> diff --git a/lib/sanitize/spec/html_sanitizer/combined_policies.hrx b/lib/sanitize/spec/html_sanitizer/combined_policies.hrx new file mode 100644 index 000000000000..01de9ae3f93f --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/combined_policies.hrx @@ -0,0 +1,42 @@ +<===> basic/fragment.html +Lorem ipsum dolor sit
    amet +<===> basic/text.html +Lorem ipsum dolor sit amet +<===> basic/inline.html +Lorem ipsum dolor sit amet +<===> basic/common.html +Lorem ipsum dolor sit
    amet +<===> + + +<===> malformed/fragment.html +Lorem dolor sit
    amet +<===> malicious/text.html +Lorem ipsum dolor sit amet <script>alert("hello world"); +<===> malicious/inline.html +Lorem ipsum dolor sit amet <script>alert("hello world"); +<===> malicious/common.html +Lorem ipsum dolor sit
    amet <script>alert("hello world"); +<===> diff --git a/lib/sanitize/spec/html_sanitizer/combined_policies_spec.cr b/lib/sanitize/spec/html_sanitizer/combined_policies_spec.cr new file mode 100644 index 000000000000..5751fba7b459 --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/combined_policies_spec.cr @@ -0,0 +1,11 @@ +require "../support/hrx" +require "../../src/processor" +require "../../src/policy/html_sanitizer" +require "../../src/policy/text" + +run_hrx_samples Path["./combined_policies.hrx"], { + "text" => Sanitize::Policy::Text.new, + "inline" => Sanitize::Policy::HTMLSanitizer.inline.no_links, + "basic" => Sanitize::Policy::HTMLSanitizer.basic, + "common" => Sanitize::Policy::HTMLSanitizer.common, +} diff --git a/lib/sanitize/spec/html_sanitizer/default.hrx b/lib/sanitize/spec/html_sanitizer/default.hrx new file mode 100644 index 000000000000..627adb8fc7fd --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/default.hrx @@ -0,0 +1,138 @@ +<===> invalid/fragment.html +foo

    bar

    bazz
    quux
    +<===> invalid/stripped.html +foo

    bar

    bazz
    quux
    +<===> invalid/escaped.html +<invalid>foo<p>bar</p>bazz</invalid>
    quux
    +<===> invalid/pruned.html +
    quux
    +<===> + + +<===> bad_argument/fragment.html +
    foo
    +<===> bad_argument/stripped.html +
    foo
    +<===> + +<==> whitewash/fragment.html +no
    foo
    bar +<==> whitewash/pruned.html +
    foo
    +<==> + + +<===> nofollow/fragment.html +Click here +<===> nofollow/stripped.html +Click here +<===> + + +<===> nofollow-rel/fragment.html +Click here +<===> nofollow-rel/stripped.html +Click here +<===> + + +<===> unprintable/fragment.html +Lo\u2029ofah ro\u2028cks! +<===> unprintable/stripped.html +Loofah rocks! +<===> + + +<===> msword/fragment.html + + +

    Foo BOLD

    +<===> msword/stripped.html + + +

    Foo BOLD

    +<===> + + +<===> entities/fragment.html +

    foo bar

    +<===> + + +<===> align/fragment.html +

    foo

    +<===> + + +<===> align-empty/fragment.html +

    foo

    +<===> align-empty/common.html +

    foo

    +<===> + + +<===> align-invalid/fragment.html +

    foo

    +<===> align-invalid/common.html +

    foo

    +<===> diff --git a/lib/sanitize/spec/html_sanitizer/html_sanitizer_spec.cr b/lib/sanitize/spec/html_sanitizer/html_sanitizer_spec.cr new file mode 100644 index 000000000000..f70a965345fa --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/html_sanitizer_spec.cr @@ -0,0 +1,102 @@ +require "../support/hrx" +require "../../src/policy/html_sanitizer" + +describe Sanitize::Policy::HTMLSanitizer do + it "removes invalid element" do + Sanitize::Policy::HTMLSanitizer.common.process("

    foobar

    ").should eq "

    foobar

    " + end + + it "inserts whitespace for removed block tag" do + Sanitize::Policy::HTMLSanitizer.common.process("

    foo

    bar
    baz

    ").should eq "

    foo bar baz

    " + end + + it "strips tag with invalid URL attribute" do + Sanitize::Policy::HTMLSanitizer.common.process(%()).should eq %() + Sanitize::Policy::HTMLSanitizer.common.process(%(foo)).should eq "foo" + end + + it "escapes URL attribute" do + Sanitize::Policy::HTMLSanitizer.common.process(%()).should eq %() + end + + it %(adds rel="noopener" on target="_blank") do + policy = Sanitize::Policy::HTMLSanitizer.common + policy.process(%(foo)).should eq(%(foo)) + policy.accepted_attributes["a"] << "target" + policy.process(%(foo)).should eq(%(foo)) + end + + it "doesn't leak configuration" do + policy = Sanitize::Policy::HTMLSanitizer.common + policy.accepted_attributes["p"] << "invalid" + policy.process(%(

    bar

    )).should eq(%(

    bar

    )) + Sanitize::Policy::HTMLSanitizer.common.process(%(

    bar

    )).should eq(%(

    bar

    )) + end + + describe "html scaffold" do + it "fragment" do + Sanitize::Policy::HTMLSanitizer.common.process("FOO

    BAR

    ").should eq "FOO

    BAR

    " + end + + it "document" do + sanitizer = Sanitize::Policy::HTMLSanitizer.common + sanitizer.accept_tag("html") + sanitizer.accept_tag("head") + sanitizer.accept_tag("body") + sanitizer.process_document("FOO

    BAR

    ").should eq "FOO

    BAR

    \n" + end + end + + describe "#transform_classes" do + it "strips classes by default" do + policy = Sanitize::Policy::HTMLSanitizer.inline + orig_attributes = {"class" => "foo bar baz"} + attributes = orig_attributes.clone + policy.transform_classes("div", attributes) + attributes.should eq Hash(String, String).new + end + + it "accepts classes" do + policy = Sanitize::Policy::HTMLSanitizer.inline + orig_attributes = {"class" => "foo bar baz"} + attributes = orig_attributes.clone + + policy.valid_classes << /fo*/ + policy.valid_classes << "bar" + policy.transform_classes("div", attributes) + attributes.should eq({"class" => "foo bar"}) + end + + it "only matches full class name" do + policy = Sanitize::Policy::HTMLSanitizer.inline + orig_attributes = {"class" => "foobar barfoo barfoobaz foo fom"} + attributes = orig_attributes.clone + + policy.valid_classes << /fo./ + policy.transform_classes("div", attributes) + attributes.should eq({"class" => "foo fom"}) + end + end + + run_hrx_samples Path["basic.hrx"], { + "common" => Sanitize::Policy::HTMLSanitizer.common, + } + run_hrx_samples Path["protocol_javascript.hrx"], { + "common" => Sanitize::Policy::HTMLSanitizer.common, + } + run_hrx_samples Path["links.hrx"], { + "common" => Sanitize::Policy::HTMLSanitizer.common, + } + run_hrx_samples Path["xss.hrx"], { + "common" => Sanitize::Policy::HTMLSanitizer.common, + } + run_hrx_samples Path["img.hrx"], { + "common" => Sanitize::Policy::HTMLSanitizer.common, + } + run_hrx_samples Path["class.hrx"], { + "common" => Sanitize::Policy::HTMLSanitizer.common, + "allow-prefix" => Sanitize::Policy::HTMLSanitizer.common.tap { |sanitizer| + sanitizer.valid_classes = Set{/allowed-.+/, "explicitly-allowed"} + }, + } +end diff --git a/lib/sanitize/spec/html_sanitizer/img.hrx b/lib/sanitize/spec/html_sanitizer/img.hrx new file mode 100644 index 000000000000..1fd81d00d687 --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/img.hrx @@ -0,0 +1,46 @@ +<===> img/fragment.html + +<===> + + +<===> img with width/fragment.html + +<===> + + +<===> img with height/fragment.html + +<===> + + +<===> img with width and height/fragment.html + +<===> + + +<===> img invalid height/fragment.html + +<===> img invalid height/common.html + +<===> + + +<===> img invalid width/fragment.html + +<===> img invalid width/common.html + +<===> + + + +<===> img invalid width and height/fragment.html + +<===> img invalid width and height/common.html + +<===> + + + +<===> img percent width and height/fragment.html + +<===> diff --git a/lib/sanitize/spec/html_sanitizer/links.hrx b/lib/sanitize/spec/html_sanitizer/links.hrx new file mode 100644 index 000000000000..104740825fab --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/links.hrx @@ -0,0 +1,89 @@ +<===> links/1/fragment.html + +<===> links/1/common.html + +<===> + + +<===> links/2/fragment.html + +<===> links/2/common.html + +<===> + + +<===> links/3/fragment.html + +<===> links/3/common.html + +<===> + + +<===> links/4/fragment.html + +<===> links/4/common.html + +<===> + + +<===> links/5/fragment.html + +<===> links/5/common.html + +<===> + + +<===> links/6/fragment.html + +<===> links/6/common.html + +<===> + + +<===> links/7/fragment.html + +<===> links/7/common.html + +<===> + + +<===> links/8/fragment.html + +<===> links/8/common.html + +<===> + + +<===> links/9/fragment.html + +<===> links/9/common.html + +<===> + + +<===> links/10/fragment.html + +<===> links/10/common.html + +<===> + + +<===> links/11/fragment.html +Red dot +<===> links/11/common.html +Red dot +<===> + + +<===> links/12/fragment.html + +<===> links/12/common.html + +<===> + + +<===> links/13/fragment.html + +<===> links/13/common.html + +<===> diff --git a/lib/sanitize/spec/html_sanitizer/protocol-based-javascript.hrx b/lib/sanitize/spec/html_sanitizer/protocol-based-javascript.hrx new file mode 100644 index 000000000000..16576ea78f80 --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/protocol-based-javascript.hrx @@ -0,0 +1,160 @@ + +<===> simple, no spaces/fragment.html +foo +<===> simple, no spaces/common.html +foo +<===> simple, no spaces/restricted.html +foo +<===> simple, no spaces/basic.html +foo +<===> simple, no spaces/relaxed.html +foo + +<===> simple, spaces before/fragment.html +foo +<===> simple, spaces before/common.html +foo +<===> simple, spaces before/restricted.html +foo +<===> simple, spaces before/basic.html +foo +<===> simple, spaces before/relaxed.html +foo + +<===> simple, spaces after/fragment.html +foo +<===> simple, spaces after/common.html +foo +<===> simple, spaces after/restricted.html +foo +<===> simple, spaces after/basic.html +foo +<===> simple, spaces after/relaxed.html +foo + +<===> simple, spaces before and after/fragment.html +foo +<===> simple, spaces before and after/common.html +foo +<===> simple, spaces before and after/restricted.html +foo +<===> simple, spaces before and after/basic.html +foo +<===> simple, spaces before and after/relaxed.html +foo + +<===> preceding colon/fragment.html +foo +<===> preceding colon/common.html +foo +<===> preceding colon/restricted.html +foo +<===> preceding colon/basic.html +foo +<===> preceding colon/relaxed.html +foo + +<===> UTF-8 encoding/fragment.html +foo +<===> UTF-8 encoding/common.html +foo +<===> UTF-8 encoding/restricted.html +foo +<===> UTF-8 encoding/basic.html +foo +<===> UTF-8 encoding/relaxed.html +foo + +<===> long UTF-8 encoding/fragment.html +foo +<===> long UTF-8 encoding/common.html +foo +<===> long UTF-8 encoding/restricted.html +foo +<===> long UTF-8 encoding/basic.html +foo +<===> long UTF-8 encoding/relaxed.html +foo + +<===> long UTF-8 encoding without semicolons/fragment.html +foo +<===> long UTF-8 encoding without semicolons/common.html +foo +<===> long UTF-8 encoding without semicolons/restricted.html +foo +<===> long UTF-8 encoding without semicolons/basic.html +foo +<===> long UTF-8 encoding without semicolons/relaxed.html +foo + +<===> hex encoding/fragment.html +foo +<===> hex encoding/common.html +foo +<===> hex encoding/restricted.html +foo +<===> hex encoding/basic.html +foo +<===> hex encoding/relaxed.html +foo + +<===> long hex encoding/fragment.html +foo +<===> long hex encoding/common.html +foo +<===> long hex encoding/restricted.html +foo +<===> long hex encoding/basic.html +foo +<===> long hex encoding/relaxed.html +foo + +<===> hex encoding without semicolons/fragment.html +foo +<===> hex encoding without semicolons/common.html +foo +<===> hex encoding without semicolons/restricted.html +foo +<===> hex encoding without semicolons/basic.html +foo +<===> hex encoding without semicolons/relaxed.html +foo + +<===> null char/fragment.html + +<===> null char/common.html +<===> null char/restricted.html +<===> null char/basic.html +<===> null char/relaxed.html +<===> invalid URL char/fragment.html + +<===> invalid URL char/common.html + +<===> invalid URL char/restricted.html + +<===> invalid URL char/basic.html + +<===> invalid URL char/relaxed.html + + +<===> spaces and entities/fragment.html + +<===> spaces and entities/common.html + +<===> spaces and entities/restricted.html + +<===> spaces and entities/basic.html + +<===> spaces and entities/relaxed.html + + +<===> protocol whitespace/fragment.html + +<===> protocol whitespace/common.html + +<===> protocol whitespace/restricted.html + +<===> protocol whitespace/basic.html + +<===> protocol whitespace/relaxed.html + diff --git a/lib/sanitize/spec/html_sanitizer/protocol_javascript.hrx b/lib/sanitize/spec/html_sanitizer/protocol_javascript.hrx new file mode 100644 index 000000000000..fc4b86c50d29 --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/protocol_javascript.hrx @@ -0,0 +1,67 @@ +<===> simple, no spaces/fragment.html +foo +<===> simple, no spaces/common.html +foo +<===> simple, spaces before/fragment.html +foo +<===> +# TODO: Maybe this should strip the a tag +<===> simple, spaces before/common.html +foo +<===> simple, spaces after/fragment.html +foo +<===> simple, spaces after/common.html +foo +<===> simple, spaces before and after/fragment.html +foo +<===> +# TODO: Maybe this should strip the a tag +<===> simple, spaces before and after/common.html +foo +<===> preceding colon/fragment.html +foo +<===> +# TODO: Maybe this should strip the a tag +<===> preceding colon/common.html +foo +<===> UTF-8 encoding/fragment.html +foo +<===> UTF-8 encoding/common.html +foo +<===> long UTF-8 encoding/fragment.html +foo +<===> long UTF-8 encoding/common.html +foo +<===> long UTF-8 encoding without semicolons/fragment.html +foo +<===> long UTF-8 encoding without semicolons/common.html +foo +<===> hex encoding/fragment.html +foo +<===> hex encoding/common.html +foo +<===> long hex encoding/fragment.html +foo +<===> long hex encoding/common.html +foo +<===> hex encoding without semicolons/fragment.html +foo +<===> hex encoding without semicolons/common.html +foo +<===> null char/fragment.html + +<===> +# TODO: Maybe this should strip the a tag +<===> null char/common.html + +<===> invalid URL char/fragment.html + +<===> +# TODO: Maybe this should strip the a tag +<===> invalid URL char/common.html + +<===> spaces and entities/fragment.html + +<===> spaces and entities/common.html + +<===> diff --git a/lib/sanitize/spec/html_sanitizer/url_spec.cr b/lib/sanitize/spec/html_sanitizer/url_spec.cr new file mode 100644 index 000000000000..5e1aade7ae90 --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/url_spec.cr @@ -0,0 +1,8 @@ +require "../support/hrx" +require "../../src/policy/html_sanitizer" + +describe "Sanitize::Policy::HTMLSanitizer" do + it "escapes URL attribute" do + Sanitize::Policy::HTMLSanitizer.common.process(%()).should eq %() + end +end diff --git a/lib/sanitize/spec/html_sanitizer/xss.hrx b/lib/sanitize/spec/html_sanitizer/xss.hrx new file mode 100644 index 000000000000..4f2e238944c7 --- /dev/null +++ b/lib/sanitize/spec/html_sanitizer/xss.hrx @@ -0,0 +1,476 @@ +<===> # Basic XSS +<===> fragment.html +test +<===> common.html +test +<===> + + +# Pending because libxml2 behaviour changed in 2.9.13 (https://gitlab.gnome.org/GNOME/libxml2/-/issues/339) +<===> pending:fragment.html +<<<>< +<===> common.html + +<===> + + +<===> fragment.html + +<===> +` +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html +
    +<===> + + +<===> fragment.html +
    +<===> common.html +
    +<===> + + +<===> fragment.html +
    +<===> common.html +
    +<===> + + +<===> fragment.html +
    +<===> common.html +
    +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html + +<===> + +<===> common.html + +<===> + + +<===> fragment.html + +<===> common.html + +<===> + + +<===> fragment.html +PT SRC="http://ha.ckers.org/xss.js"> +<===> common.html +PT SRC="http://ha.ckers.org/xss.js"> +<===> + + +<===> fragment.html + +<==> complex/text.html +Lorem ipsum dolor sit amet +<==> + + +# Pending because libxml2 behaviour changed in 2.9.13 (https://gitlab.gnome.org/GNOME/libxml2/-/issues/339) +<==> pending:html-special-chars/fragment.html +<script> +<==> pending:html-special-chars/text.html +<script> +<==> + + +<==> prune script/fragment.html + +<==> prune script/text.html +<==> + + +<==> prune style/fragment.html + +<==> prune script/text.html +<==> diff --git a/lib/sanitize/spec/text_policy_spec.cr b/lib/sanitize/spec/text_policy_spec.cr new file mode 100644 index 000000000000..8b02a154f330 --- /dev/null +++ b/lib/sanitize/spec/text_policy_spec.cr @@ -0,0 +1,17 @@ +require "./support/hrx" +require "../src/policy/text" +require "../src/processor" + +describe Sanitize::Policy::Text do + it "continues on tag" do + Sanitize::Policy::Text.new.transform_tag("foo", {} of String => String).should eq Sanitize::Policy::CONTINUE + end + + it "adds whitespace" do + Sanitize::Policy::Text.new.process("foo
    bar").should eq "foo bar" + end + + run_hrx_samples Path["./text_policy.hrx"], { + "text" => Sanitize::Policy::Text.new, + } +end diff --git a/lib/sanitize/spec/uri_sanitizer_spec.cr b/lib/sanitize/spec/uri_sanitizer_spec.cr new file mode 100644 index 000000000000..a3aa25b092ad --- /dev/null +++ b/lib/sanitize/spec/uri_sanitizer_spec.cr @@ -0,0 +1,130 @@ +require "../src/uri_sanitizer" +require "spec" +require "uri" + +private def assert_sanitize(source : String, expected : String? = source, sanitizer = Sanitize::URISanitizer.new, *, file = __FILE__, line = __LINE__) + if expected + expected = URI.parse(expected) + end + sanitizer.sanitize(URI.parse(source)).should eq(expected), file: file, line: line +end + +describe Sanitize::URISanitizer do + describe "#accepted_schemes" do + it "has default value" do + Sanitize::URISanitizer.new.accepted_schemes.should eq Set{"http", "https", "mailto", "tel"} + end + + it "accepts minimal schemes" do + assert_sanitize("http://example.com") + assert_sanitize("https://example.com") + assert_sanitize("mailto:mail@example.com") + assert_sanitize("tel:example.com") + end + + it "refutes unsafe schemes" do + assert_sanitize("javascript:alert();", nil) + assert_sanitize("ssh:git@github.com", nil) + end + + it "custom schemes" do + sanitizer = Sanitize::URISanitizer.new + sanitizer.accept_scheme "javascript" + assert_sanitize("javascript:alert();", sanitizer: sanitizer) + end + + it "can be disabled" do + sanitizer = Sanitize::URISanitizer.new + sanitizer.accepted_schemes = nil + assert_sanitize("javascript:alert();", sanitizer: sanitizer) + assert_sanitize("foo:bar", sanitizer: sanitizer) + end + end + + describe "#base_url" do + it "disabled by default" do + Sanitize::URISanitizer.new.base_url.should be_nil + assert_sanitize("foo") + end + + it "set to absolute URL" do + sanitizer = Sanitize::URISanitizer.new + sanitizer.base_url = URI.parse("https://example.com/base/") + + assert_sanitize("foo", "https://example.com/base/foo", sanitizer: sanitizer) + assert_sanitize("/foo", "https://example.com/foo", sanitizer: sanitizer) + end + + it "doesn't base fragment-only URLs" do + sanitizer = Sanitize::URISanitizer.new + sanitizer.base_url = URI.parse("https://example.com/base/") + + assert_sanitize("#foo", sanitizer: sanitizer) + assert_sanitize("#", sanitizer: sanitizer) + assert_sanitize("https:#", sanitizer: sanitizer) + assert_sanitize("?#foo", "https://example.com/base/?#foo", sanitizer: sanitizer) + assert_sanitize("/#", "https://example.com/#", sanitizer: sanitizer) + assert_sanitize("https://#", "https://#", sanitizer: sanitizer) + + sanitizer.resolve_fragment_urls = true + assert_sanitize("#foo", "https://example.com/base/#foo", sanitizer: sanitizer) + assert_sanitize("#", "https://example.com/base/#", sanitizer: sanitizer) + assert_sanitize("https:#", "https:#", sanitizer: sanitizer) + end + end + + describe "#accepted_hosts" do + it "disabled by default" do + Sanitize::URISanitizer.new.accepted_hosts.should be_nil + end + + it "restricts hosts" do + sanitizer = Sanitize::URISanitizer.new + sanitizer.accepted_hosts = Set{"foo.example.com"} + assert_sanitize("http://foo.example.com", sanitizer: sanitizer) + assert_sanitize("http://bar.example.com", nil, sanitizer: sanitizer) + assert_sanitize("http://example.com", nil, sanitizer: sanitizer) + assert_sanitize("http://foo.foo.example.com", nil, sanitizer: sanitizer) + assert_sanitize("foo", sanitizer: sanitizer) + end + + it "works with base_url" do + sanitizer = Sanitize::URISanitizer.new + sanitizer.accepted_hosts = Set{"foo.example.com"} + sanitizer.base_url = URI.parse("http://bar.example.com/") + assert_sanitize("foo", "http://bar.example.com/foo", sanitizer: sanitizer) + assert_sanitize("http://bar.example.com/foo", nil, sanitizer: sanitizer) + end + end + + describe "#rejected_hosts" do + it "disabled by default" do + Sanitize::URISanitizer.new.rejected_hosts.should be_a(Set(String)) + end + + it "restricts hosts" do + sanitizer = Sanitize::URISanitizer.new + sanitizer.rejected_hosts = Set{"bar.example.com"} + assert_sanitize("http://foo.example.com", sanitizer: sanitizer) + assert_sanitize("http://bar.example.com", nil, sanitizer: sanitizer) + assert_sanitize("http://example.com", sanitizer: sanitizer) + assert_sanitize("http://bar.bar.example.com", sanitizer: sanitizer) + assert_sanitize("foo", sanitizer: sanitizer) + end + + it "works with base_url" do + sanitizer = Sanitize::URISanitizer.new + sanitizer.rejected_hosts = Set{"foo.example.com"} + sanitizer.base_url = URI.parse("http://foo.example.com/") + assert_sanitize("foo", "http://foo.example.com/foo", sanitizer: sanitizer) + assert_sanitize("http://foo.example.com/foo", nil, sanitizer: sanitizer) + end + + it "overrides accepted_hosts" do + sanitizer = Sanitize::URISanitizer.new + sanitizer.rejected_hosts = Set{"foo.example.com"} + sanitizer.accepted_hosts = Set{"foo.example.com"} + assert_sanitize("http://foo.example.com/foo", nil, sanitizer: sanitizer) + end + end +end diff --git a/lib/sanitize/src/adapter/libxml2.cr b/lib/sanitize/src/adapter/libxml2.cr new file mode 100644 index 000000000000..51d899454c66 --- /dev/null +++ b/lib/sanitize/src/adapter/libxml2.cr @@ -0,0 +1,137 @@ +struct Sanitize::Adapter::LibXML2 + include Adapter + + def self.process(policy : Policy, html : String, fragment : Bool = false) + return "" if html.empty? + + node = parse(html, fragment) + process(policy, node, fragment) + end + + def self.process(policy : Policy, node : XML::Node, fragment : Bool = false) + build(fragment) do |builder| + process(policy, node, builder, fragment) + end + end + + def self.process(policy : Policy, node : XML::Node, builder : XML::Builder, fragment : Bool = false) + processor = Processor.new(policy, new(builder)) + visit(processor, node, fragment) + builder.end_document + builder.flush + end + + def self.parse(html : String, fragment : Bool) + if fragment + html = "#{html}" + end + + node = XML.parse_html(html, XML::HTMLParserOptions.default | XML::HTMLParserOptions::NOIMPLIED | XML::HTMLParserOptions::NODEFDTD) + end + + def self.build(fragment : Bool) + result = String.build do |io| + builder = XML::Builder.new(io) + + if fragment + builder.start_element("fragment") + end + + yield(builder) + end + + if fragment + result = "" if result == "\n" + result = result.lchop("").rchop("\n") + end + # strip trailing non-linebreak whitespace + if result.ends_with?("\n") + result + else + result.rstrip + end + end + + def self.visit(processor : Processor, node : XML::Node, fragment : Bool) + visitor = Visitor.new(processor, fragment) + visitor.visit(node) + end + + # :nodoc: + struct Visitor + @attributes = Hash(String, String).new + + def initialize(@processor : Processor, @fragment : Bool) + end + + # :nodoc: + def visit(node : XML::Node) + case node.type + when .html_document_node? + visit_children(node) + when .dtd_node? + # skip DTD + when .text_node? + visit_text(node) + when .element_node? + visit_element(node) + when .comment_node? + # skip comments + when .cdata_section_node? + # skip CDATA + else + raise "Not implemented for: #{node.type}:#{node.name}:#{node.content}" + end + end + + def visit_children(node) + node.children.each do |child| + visit(child) + end + end + + def visit_text(node) + @processor.process_text(node.content) + end + + def visit_element(node) + if @fragment && node.name.in?({"html", "body"}) + @attributes.clear + @processor.process_element(node.name, @attributes, Processor::CONTINUE) do + visit_children(node) + end + return + end + + @attributes.clear + node.attributes.each do |attribute| + @attributes[attribute.name] = attribute.content + end + + name = node.name + if namespace = node.namespace + name = "#{namespace}:#{name}" + end + + @processor.process_element(name, @attributes) do + visit_children(node) + end + end + end + + def initialize(@builder : XML::Builder) + end + + def start_tag(name : String, attributes : Hash(String, String)) : Nil + @builder.start_element(name) + @builder.attributes(attributes) + end + + def end_tag(name : String, attributes : Hash(String, String)) : Nil + @builder.end_element + end + + def write_text(text : String) : Nil + @builder.text(text) + end +end diff --git a/lib/sanitize/src/policy.cr b/lib/sanitize/src/policy.cr new file mode 100644 index 000000000000..d1ce31ccf63f --- /dev/null +++ b/lib/sanitize/src/policy.cr @@ -0,0 +1,45 @@ +# A policy defines the rules for transforming an HTML/XML tree. +# +# * `HTMLSanitizer` is a policy for HTML sanitization. +# * `Whitelist` is a whitelist-based transformer that's useful either for +# simple stripping applications or as a building block for more advanced +# sanitization policies. +# * `Text` is a policy that turns HTML into plain text. +abstract class Sanitize::Policy + # :nodoc: + alias CONTINUE = Processor::CONTINUE + # :nodoc: + alias STOP = Processor::STOP + + # Defines the string that is added when whitespace is needed when a block tag + # is stripped. + property block_whitespace = " " + + # Receives the content of a text node and returns the transformed content. + # + # If the return value is `nil`, the content is skipped. + abstract def transform_text(text : String) : String? + + # Receives the element name and attributes of an opening tag and returns the + # transformed element name (usually the same as the input name). + # + # *attributes* are transformed directly in place. + # + # Special return values: + # * `Processor::CONTINUE`: Tells the processor to strip the current tag but + # continue traversing its children. + # * `Processor::CONTINUE`: Tells the processor to skip the current tag and its + # children completely and move to the next sibling. + abstract def transform_tag(name : String, attributes : Hash(String, String)) : String | Processor::CONTINUE | Processor::STOP + + HTML_BLOCK_ELEMENTS = Set{ + "address", "article", "aside", "audio", "video", "blockquote", "br", + "canvas", "dd", "div", "dl", "fieldset", "figcaption", "figure", "footer", + "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", + "noscript", "ol", "output", "p", "pre", "section", "table", "tfoot", "ul", + } + + def block_tag?(name) + HTML_BLOCK_ELEMENTS.includes?(name) + end +end diff --git a/lib/sanitize/src/policy/html_sanitizer.cr b/lib/sanitize/src/policy/html_sanitizer.cr new file mode 100644 index 000000000000..dbcc71ce131a --- /dev/null +++ b/lib/sanitize/src/policy/html_sanitizer.cr @@ -0,0 +1,350 @@ +require "./whitelist" +require "../uri_sanitizer" + +# This policy serves as a good default configuration that should fit most +# typical use cases for HTML sanitization. +# +# ## Configurations +# It comes in three different configurations with different sets of supported +# HTML tags. +# +# They only differ in the default configuration of allowed tags and attributes. +# The transformation behaviour is otherwise the same. +# +# ### Common Configuration +# `.common`: Accepts most standard tags and thus allows using a good +# amount of HTML features (see `COMMON_SAFELIST`). +# +# This is the recommended default configuration and should work for typical use +# cases unless strong restrictions on allowed content is required. +# +# ``` +# sanitizer = Sanitize::Policy::HTMLSanitizer.common +# sanitizer.process(%(foo)) # => %(foo) +# sanitizer.process(%(

    foo

    )) # => %(

    foo

    ) +# sanitizer.process(%()) # => %() +# sanitizer.process(%(
    foobar
    )) # => %(
    foobar
    ) +# ``` +# +# NOTE: This configuration (nor any other) does not accept `<html>`, +# `<head>`, or # `<body>` tags by default. In order to use +# `#sanitized_document` they need to be added explicitly to `accepted_arguments`. +# +# ### Basic Configuration +# +# `.basic`: This set accepts some basic tags including paragraphs, headlines, +# lists, and images (see `BASIC_SAFELIST`). +# +# ``` +# sanitizer = Sanitize::Policy::HTMLSanitizer.basic +# sanitizer.process(%(foo)) # => %(foo) +# sanitizer.process(%(

    foo

    )) # => %(

    foo

    ) +# sanitizer.process(%()) # => %() +# sanitizer.process(%(
    foobar
    )) # => %(foo bar) +# ``` +# +# ### Inline Configuration +# +# `.inline`: Accepts only a limited set of inline tags (see `INLINE_SAFELIST`). +# +# ``` +# sanitizer = Sanitize::Policy::HTMLSanitizer.inline +# sanitizer.process(%(foo)) # => %(foo) +# sanitizer.process(%(

    foo

    )) # => %(foo) +# sanitizer.process(%()) # => %() +# sanitizer.process(%(
    foobar
    )) # => %(foo bar) +# ``` +# +# ## Attribute Transformations +# +# Attribute transformations are identical in all three configurations. But more +# advanced transforms won't apply if the respective attribute is not allowed in +# `accepted_tags`. +# So you can easily add additional elements and attributes to lower-tier sets +# and get the same attribute validation. For example: `.inline` doesn't include +# `<img>` tags, but when `img` is added to `accepted_attributes`, +# the policy validates img tags the same way as in `.common`. +# +# ### URL Sanitization +# +# This transformation applies to attributes that contain a URL (configurable +# through (`url_attributes`). +# +# * Makes sure the value is a valid URI (via `URI.parse`). If it does not parse, +# the attribute value is set to empty string. +# * Sanitizes the URI via `URISanitizer (configurable trough `uri_sanitizer`). +# If the sanitizer returns `nil`, the attribute value is set to empty string. +# +# The same `URISanitizer` is used for any URL attributes. +# +# ### Anchor Tags +# +# For `<a>` tags with a `href` attribute, there are two transforms: +# +# * `rel="nofollow"` is added (can be disabled with `add_rel_nofollow`). +# * `rel="noopener"` is added to links with `target` attribute (can be disabled +# with `add_rel_noopener`). +# +# Anchor tags the have neither a `href`, `name` or `id` attribute are stripped. +# +# NOTE: `name` and `id` attributes are not in any of the default sets of +# accepted attributes, so they can only be used when explicitly enabled. +# +# ### Image Tags +# +# `<img>` tags are stripped if they don't have a `src` attribute. +# +# ### Size Attributes +# +# If a tag has `width` or `height` attributes, the values are validated to be +# numerical or percent values. +# By default, these attributes are only accepted for <img> tags. +# +# ### Alignment Attribute +# +# The `align` attribute is validated against allowed values for this attribute: +# `center, left, right, justify, char`. +# If the value is invalid, the attribute is stripped. +# +# ### Classes +# +# `class` attributes are filtered to accept only classes described by +# `valid_classes`. String values need to match the class name exactly, regex +# values need to match the entire class name. +# +# `class` is accepted as a global attribute in the default configuration, but no +# values are allowed in `valid_classes`. +# +# All classes can be accepted by adding the match-all regular expression `/.*/` +# to `valid_classes`. +class Sanitize::Policy::HTMLSanitizer < Sanitize::Policy::Whitelist + # Add `rel="nofollow"` to every `<a>` tag with `href` attribute. + property add_rel_nofollow = true + + # Add `rel="noopener"` to every `<a>` tag with `href` and `target` attribute. + property add_rel_noopener = true + + # Configures the `URISanitizer` to use for sanitizing URL attributes. + property uri_sanitizer = URISanitizer.new + + # Configures which attributes are considered to contain URLs. If empty, URL + # sanitization is disabled. + # + # Default value: `Set{"src", "href", "action", "cite", "longdesc"}`. + property url_attributes : Set(String) = Set{"src", "href", "action", "cite", "longdesc"} + + # Configures which classes are valid for `class` attributes. + # + # String values need to match the class name exactly, regex + # values need to match the entire class name. + # + # Default value: empty + property valid_classes : Set(String | Regex) = Set(String | Regex).new + + def valid_classes=(classes) + valid_classes = classes.map(&.as(String | Regex)).to_set + end + + # Creates an instance which accepts a limited set of inline tags (see + # `INLINE_SAFELIST`). + def self.inline : HTMLSanitizer + new( + accepted_attributes: INLINE_SAFELIST.clone + ) + end + + # Creates an instance which accepts more basic tags including paragraphs, + # headlines, lists, and images (see `BASIC_SAFELIST`). + def self.basic : HTMLSanitizer + new( + accepted_attributes: BASIC_SAFELIST.clone + ) + end + + # Creates an instance which accepts even more standard tags and thus allows + # using a good amount of HTML features (see `COMMON_SAFELIST`). + # + # Unless you need tight restrictions on allowed content, this is the + # recommended default. + def self.common : HTMLSanitizer + new( + accepted_attributes: COMMON_SAFELIST.clone + ) + end + + # Removes anchor tag (`<a>` from the list of accepted tags). + # + # NOTE: This doesn't reject attributes with URL values for other tags. + def no_links + accepted_attributes.delete("a") + + self + end + + def accept_tag(tag : String, attributes : Set(String) = Set(String).new) + accepted_attributes[tag] = attributes + end + + def transform_attributes(tag : String, attributes : Hash(String, String)) : String | CONTINUE | STOP + transform_url_attributes(tag, attributes) + + tag_result = case tag + when "a" + transform_tag_a(attributes) + when "img" + transform_tag_img(attributes) + end + + if tag_result + return tag_result + end + + limit_numeric_or_percent(attributes, "width") + limit_numeric_or_percent(attributes, "height") + limit_enum(attributes, "align", ["center", "left", "right", "justify", "char"]) + + transform_classes(tag, attributes) + + tag + end + + def transform_tag_img(attributes) + unless attributes.has_key?("src") + return CONTINUE + end + end + + def transform_tag_a(attributes) + if href = attributes["href"]? + if add_rel_nofollow + append_attribute(attributes, "rel", "nofollow") + end + if add_rel_noopener && attributes.has_key?("target") + append_attribute(attributes, "rel", "noopener") + end + end + if !(((href = attributes["href"]?) && !href.empty?) || attributes.has_key?("id") || attributes.has_key?("tag")) + return CONTINUE + end + end + + def transform_url_attributes(tag, attributes) + all_ok = true + url_attributes.each do |key| + if value = attributes[key]? + all_ok &&= transform_url_attribute(tag, attributes, key, value) + end + end + all_ok + end + + def transform_url_attribute(tag, attributes, attribute, value) + begin + uri = URI.parse(value.strip) + rescue URI::Error + attributes[attribute] = "" + return false + end + + uri = transform_uri(tag, attributes, attribute, uri) + if uri.nil? || (uri.blank? || uri == "#") + attributes[attribute] = "" + return false + end + + attributes[attribute] = uri + true + end + + def transform_uri(tag, attributes, attribute, uri : URI) : String? + if uri_sanitizer = self.uri_sanitizer + uri = uri_sanitizer.sanitize(uri) + + return unless uri + end + + # Make sure special characters are properly encoded to avoid interpretation + # of tweaked relative paths as "javascript:" URI (for example) + if path = uri.path + uri.path = String.build do |io| + URI.encode(URI.decode(path), io) { |byte| URI.reserved?(byte) || URI.unreserved?(byte) } + end + end + + uri.to_s + end + + def transform_classes(tag, attributes) + attribute = attributes["class"]? + return unless attribute + + classes = attribute.split + classes = classes.select { |klass| valid_class?(tag, klass, valid_classes) } + if classes.empty? + attributes.delete("class") + else + attributes["class"] = classes.join(" ") + end + end + + private def limit_numeric_or_percent(attributes, attribute) + if value = attributes[attribute]? + value = value.strip + if value.ends_with?("%") + value = value.byte_slice(0, value.size - 1) + end + value.each_char do |char| + unless char.ascii_number? + attributes.delete(attribute) + break + end + end + end + end + + private def limit_enum(attributes, attribute, list) + if value = attributes[attribute]? + value = value.strip + if valid_with_list?(value, list) + attributes[attribute] = value + else + attributes.delete(attribute) + end + end + end + + def valid_class?(tag, klass, valid_classes) + valid_with_list?(klass, valid_classes) + end + + private def valid_with_list?(value, list) + list.any? { |validator| + case validator + when String + validator == value + when Regex + data = validator.match(value) + next unless data + data.byte_begin == 0 && data.byte_end == value.bytesize + end + } + end + + def append_attribute(attributes, attribute, value) + if curr_value = attributes[attribute]? + values = curr_value.split + if values.includes?(value) + return false + else + values << value + attributes[attribute] = values.join(" ") + end + else + attributes[attribute] = value + end + + true + end +end + +require "./html_sanitizer/safelist" diff --git a/lib/sanitize/src/policy/html_sanitizer/safelist.cr b/lib/sanitize/src/policy/html_sanitizer/safelist.cr new file mode 100644 index 000000000000..2d5a7edc08cd --- /dev/null +++ b/lib/sanitize/src/policy/html_sanitizer/safelist.cr @@ -0,0 +1,70 @@ +class Sanitize::Policy::HTMLSanitizer < Sanitize::Policy::Whitelist + # Only limited elements for inline text markup. + INLINE_SAFELIST = { + "a" => Set{"href", "hreflang"}, + "abbr" => Set(String).new, + "acronym" => Set(String).new, + "b" => Set(String).new, + "code" => Set(String).new, + "em" => Set(String).new, + "i" => Set(String).new, + "strong" => Set(String).new, + "*" => Set{ + "dir", + "lang", + "title", + "class", + }, + } + + # Compatible with basic Markdown features. + BASIC_SAFELIST = INLINE_SAFELIST.merge({ + "blockquote" => Set{"cite"}, + "br" => Set(String).new, + "h1" => Set(String).new, + "h2" => Set(String).new, + "h3" => Set(String).new, + "h4" => Set(String).new, + "h5" => Set(String).new, + "h6" => Set(String).new, + "hr" => Set(String).new, + "img" => Set{"alt", "src", "longdesc", "width", "height", "align"}, + "li" => Set(String).new, + "ol" => Set{"start"}, + "p" => Set{"align"}, + "pre" => Set(String).new, + "ul" => Set(String).new, + }) + + # Accepts most standard tags and thus allows using a good amount of HTML features. + COMMON_SAFELIST = BASIC_SAFELIST.merge({ + "dd" => Set(String).new, + "del" => Set{"cite"}, + "details" => Set(String).new, + "dl" => Set(String).new, + "dt" => Set(String).new, + "div" => Set(String).new, + "ins" => Set{"cite"}, + "kbd" => Set(String).new, + "q" => Set{"cite"}, + "ruby" => Set(String).new, + "rp" => Set(String).new, + "rt" => Set(String).new, + "s" => Set(String).new, + "samp" => Set(String).new, + "strike" => Set(String).new, + "sub" => Set(String).new, + "summary" => Set(String).new, + "sup" => Set(String).new, + "table" => Set(String).new, + "time" => Set{"datetime"}, + "tbody" => Set(String).new, + "td" => Set(String).new, + "tfoot" => Set(String).new, + "th" => Set(String).new, + "thead" => Set(String).new, + "tr" => Set(String).new, + "tt" => Set(String).new, + "var" => Set(String).new, + }) +end diff --git a/lib/sanitize/src/policy/text.cr b/lib/sanitize/src/policy/text.cr new file mode 100644 index 000000000000..82a2e6775de2 --- /dev/null +++ b/lib/sanitize/src/policy/text.cr @@ -0,0 +1,23 @@ +require "../policy" + +# Reduces an HTML tree to the content of its text nodes. +# It renders a plain text result, similar to copying HTML content rendered by +# a browser to a text editor. +# HTML special characters are escaped. +# +# ``` +# policy = Sanitize::Policy::Text.new +# policy.process(%(foo bar!)) # => "foo bar!" +# policy.process(%(

    foo

    bar

    )) # => "foo bar" +# policy.block_whitespace = "\n" +# policy.process(%(

    foo

    bar

    )) # => "foo\nbar" +# ``` +class Sanitize::Policy::Text < Sanitize::Policy + def transform_text(text : String) : String? + text + end + + def transform_tag(name : String, attributes : Hash(String, String)) : String | CONTINUE | STOP + CONTINUE + end +end diff --git a/lib/sanitize/src/policy/whitelist.cr b/lib/sanitize/src/policy/whitelist.cr new file mode 100644 index 000000000000..5f96a71dac9e --- /dev/null +++ b/lib/sanitize/src/policy/whitelist.cr @@ -0,0 +1,57 @@ +require "../policy" + +# This is a simple policy based on a tag and attribute whitelist. +# +# This policy accepts only `<div>` and `<p>` tags with optional `title` attributes: +# ``` +# policy = Sanitize::Policy::Whitelist.new({ +# "div" => Set{"title"}, +# "p" => Set{"title"}, +# }) +# ``` +# +# The special `*` key applies to *all* tag names and can be used to allow global +# attributes: +# +# This example is equivalent to the above. If more tag names were added, they +# would also accept `title` attributes. +# ``` +# policy = Sanitize::Policy::Whitelist.new({ +# "div" => Set(String).new, +# "p" => Set(String).new, +# "*" => Set{"title"}, +# }) +# ``` +# +# Attributes are always optional, so this policy won't enforce the presence of +# an attribute. +# +# If a tag's attribute list is empty, no attributes are allowed for this tag. +# +# Attribute values are not changed by this policy. +class Sanitize::Policy::Whitelist < Sanitize::Policy + # Mapping of accepted tag names and attributes. + property accepted_attributes : Hash(String, Set(String)) + + # Short cut to `accepted_attributes["*"]`. + getter global_attributes : Set(String) { accepted_attributes.fetch("*") { Set(String).new } } + + def initialize(@accepted_attributes : Hash(String, Set(String))) + end + + def transform_text(text : String) : String? + text + end + + def transform_tag(name : String, attributes : Hash(String, String)) : String | CONTINUE | STOP + acceptable_attributes = accepted_attributes.fetch(name) { return CONTINUE } + + attributes.reject! { |attr, _| !acceptable_attributes.includes?(attr) && !global_attributes.includes?(attr) } + + transform_attributes(name, attributes) + end + + def transform_attributes(name : String, attributes : Hash(String, String)) : String | CONTINUE | STOP + name + end +end diff --git a/lib/sanitize/src/processor.cr b/lib/sanitize/src/processor.cr new file mode 100644 index 000000000000..6d4e4ac82766 --- /dev/null +++ b/lib/sanitize/src/processor.cr @@ -0,0 +1,110 @@ +require "xml" +require "log" +require "./adapter/libxml2" + +module Sanitize + abstract class Policy + # Processes the HTML fragment *html* with this policy using the default + # adapter (`Adapter::LibXML2`). + def process(html : String | XML::Node) : String + Adapter::LibXML2.process(self, html, fragment: true) + end + + # Processes the HTML document *html* with this policy using the default + # adapter (`Adapter::LibXML2`). + def process_document(html : String | XML::Node) : String + Adapter::LibXML2.process(self, html, fragment: false) + end + end + + module Adapter + abstract def write_text(text : String) : Nil + abstract def start_tag(name : String, attributes : Hash(String, String)) : Nil + abstract def end_tag(name : String, attributes : Hash(String, String)) : Nil + end + + # A processor traverses the HTML/XML tree, applies transformations through the + # policy and passes the result to the adapter which then builds the result. + class Processor + Log = ::Log.for(self) + + # This module serves as a singleton constant that signals the processor to + # skip the current tag but continue to traverse its children. + module CONTINUE + extend self + end + + # This module serves as a singleton constant that signals the processor to + # skip the current tag and its children. + module STOP + extend self + end + + @last_text_ended_with_whitespace = true + @stripped_block_tag = false + + def initialize(@policy : Policy, @adapter : Adapter) + end + + def process_text(text : String) + text = @policy.transform_text(text) + + if @stripped_block_tag && !@last_text_ended_with_whitespace && !text.try(&.[0].whitespace?) + @adapter.write_text(@policy.block_whitespace) + end + + @stripped_block_tag = false + + if text + @adapter.write_text(text) + @last_text_ended_with_whitespace = text.[-1].whitespace? + else + @last_text_ended_with_whitespace = false + end + end + + def process_element(name : String, attributes : Hash(String, String), &) + process_element(name, attributes, @policy.transform_tag(name, attributes)) do + yield + end + end + + def process_element(orig_name : String, attributes : Hash(String, String), name, &) + case name + when STOP + Log.debug { "#{@policy.class} stopping at tag #{orig_name} #{attributes}" } + if @policy.block_tag?(orig_name) + @stripped_block_tag = true + end + return + when CONTINUE + Log.debug { "#{@policy.class} stripping tag #{orig_name} #{attributes}" } + if @policy.block_tag?(orig_name) + @stripped_block_tag = true + end + when String + @stripped_block_tag = false + @adapter.start_tag(name, attributes) + end + + yield + + case name + when CONTINUE + if @policy.block_tag?(orig_name) + @stripped_block_tag = true + end + when String + @stripped_block_tag = false + @adapter.end_tag(name, attributes) + end + end + + def reset + @last_text_ended_with_whitespace = true + @stripped_block_tag = false + end + end +end + +require "./adapter/libxml2" diff --git a/lib/sanitize/src/sanitize.cr b/lib/sanitize/src/sanitize.cr new file mode 100644 index 000000000000..a94e7c660323 --- /dev/null +++ b/lib/sanitize/src/sanitize.cr @@ -0,0 +1,5 @@ +require "./policy/*" +require "./processor" + +module Sanitize +end diff --git a/lib/sanitize/src/uri_sanitizer.cr b/lib/sanitize/src/uri_sanitizer.cr new file mode 100644 index 000000000000..cfda46736b3b --- /dev/null +++ b/lib/sanitize/src/uri_sanitizer.cr @@ -0,0 +1,107 @@ +require "uri" + +# A `URISanitizer` is used to validate and transform a URI based on specified +# rules. +class Sanitize::URISanitizer + # Specifies a whitelist of URI schemes this sanitizer accepts. + # + # If empty, no schemes are accepted (i.e. only relative URIs are valid). + # If `nil`, all schemes are accepted (this setting is potentially dangerous). + # + # Relative URIs are not affected by this setting. + property accepted_schemes : Set(String)? + + # Specifies a whitelist of hosts this sanitizer accepts. + # + # If empty, no hosts are accepted (i.e. only relative URIs are valid). + # If `nil`, all hosts are accepted (default). + # + # The blacklist `rejected_hosts` has precedence over this whitelist. + property accepted_hosts : Set(String)? + + # Specifies a blacklist of hosts this sanitizer rejects. + # + # If empty, no hosts are rejected. + # + # This blacklist has precedence over the whitelist `accepted_hosts`. + property rejected_hosts : Set(String) = Set(String).new + + # Specifies a base URL all relative URLs are resolved against. + # + # If `nil`, relative URLs are not resolved. + property base_url : URI? + + # Configures whether fragment-only URIs are resolved on `base_url`. + # + # ``` + # sanitizer = Sanitize::URISanitizer.new + # sanitizer.base_url = URI.parse("https://example.com/base/") + # sanitizer.sanitize(URI.parse("#foo")) # => "#foo" + # + # sanitizer.resolve_fragment_urls = true + # sanitizer.sanitize(URI.parse("#foo")) # => "https://example.com/base/#foo" + # ``` + property resolve_fragment_urls = false + + def initialize(@accepted_schemes : Set(String)? = Set{"http", "https", "mailto", "tel"}) + end + + # Adds *scheme* to `accepted_schemes`. + def accept_scheme(scheme : String) + schemes = self.accepted_schemes ||= Set(String).new + schemes << scheme + end + + def sanitize(uri : URI) : URI? + unless accepts_scheme?(uri.scheme) + return nil + end + + unless accepts_host?(uri.host) + return nil + end + + uri = resolve_base_url(uri) + + uri + end + + def accepts_scheme?(scheme) + if scheme.nil? + return true + end + + if accepted_schemes = self.accepted_schemes + return accepted_schemes.includes?(scheme) + end + + true + end + + def accepts_host?(host) + if host.nil? + return true + end + + return false if rejected_hosts.includes?(host) + + if accepted_hosts = self.accepted_hosts + return false unless accepted_hosts.includes?(host) + end + + true + end + + def resolve_base_url(uri) + if base_url = self.base_url + unless uri.absolute? || (!resolve_fragment_urls && fragment_url?(uri)) + uri = base_url.resolve(uri) + end + end + uri + end + + private def fragment_url?(uri) + uri.path.empty? && uri.host.nil? && uri.query.nil? && !uri.fragment.nil? + end +end diff --git a/shard.lock b/shard.lock index e7f2ddc86d10..d80148209315 100644 --- a/shard.lock +++ b/shard.lock @@ -8,3 +8,7 @@ shards: git: https://github.com/i3oris/reply.git version: 0.3.1+git.commit.90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 + sanitize: + git: https://github.com/straight-shoota/sanitize.git + version: 0.1.0+git.commit.75c141b619c77956e88f557149566cd28876398b + diff --git a/shard.yml b/shard.yml index 396d91bdffe2..cbc960c0ee15 100644 --- a/shard.yml +++ b/shard.yml @@ -2,7 +2,7 @@ name: crystal version: 1.13.0-dev authors: -- Crystal Core Team + - Crystal Core Team description: | The Crystal standard library and compiler. @@ -15,6 +15,9 @@ dependencies: reply: github: I3oris/reply commit: 90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 + sanitize: + github: straight-shoota/sanitize + commit: 75c141b619c77956e88f557149566cd28876398b license: Apache-2.0 diff --git a/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr b/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr index d8d179a05d51..65090c8185f7 100644 --- a/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr +++ b/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr @@ -374,7 +374,33 @@ describe Doc::MarkdDocRenderer do HTML end - describe "renders html" do - it_renders nil, %(

    Foo

    ), %(

    Foo

    ) + describe "renders html with sanitization" do + it_renders nil, %(

    Foo

    ), %(

    Foo

    ) + it_renders nil, %(), %() + it_renders nil, %(

    example text

    ), %(

    example text

    ) + + it_renders nil, %(```crystal\n# \n```), + %(
    # <script>alert("hello world")</script>
    ) + end + + describe "still renders tables despite sanitization" do + table_mkdn = <<-HTML + + + + + + + + + + + + + +
    column 1column 2
    data 1data 2
    data 3data 4
    + HTML + + it_renders nil, table_mkdn, table_mkdn end end diff --git a/src/compiler/crystal/tools/doc/markd_doc_renderer.cr b/src/compiler/crystal/tools/doc/markd_doc_renderer.cr index f703d7ed787b..2ea3348b2b36 100644 --- a/src/compiler/crystal/tools/doc/markd_doc_renderer.cr +++ b/src/compiler/crystal/tools/doc/markd_doc_renderer.cr @@ -1,4 +1,7 @@ +require "sanitize" + class Crystal::Doc::MarkdDocRenderer < Markd::HTMLRenderer + SANITIZER = Sanitize::Policy::HTMLSanitizer.common @anchor_map = Hash(String, Int32).new(0) def initialize(@type : Crystal::Doc::Type, options) @@ -158,4 +161,24 @@ class Crystal::Doc::MarkdDocRenderer < Markd::HTMLRenderer type.lookup_macro(name, args_count) || type.program.lookup_macro(name, args_count) end + + def text(node : Markd::Node, entering : Bool) + output(sanitize(node)) + end + + def html_block(node : Markd::Node, entering : Bool) + newline + content = @options.safe? ? "" : sanitize(node) + literal(content) + newline + end + + def html_inline(node : Markd::Node, entering : Bool) + content = @options.safe? ? "" : sanitize(node) + literal(content) + end + + def sanitize(node : Markd::Node) : String + SANITIZER.process(node.text) + end end From 9e7b857fc0ed1475cdff6184f30f8cb339445af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 14 May 2024 19:16:06 +0200 Subject: [PATCH 1129/1551] Update distribution-scripts (#14594) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a6fe71b0faa6..0ebb39b57e22 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "8c3a9f6bf64499f1e3d1838d2ae9b967d5e7f796" + default: "a9e0c6c12987b8b01b17e71c38f489e45937e1bf" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From 0e96b0ace2512aa7fa9fc512361c9bc8f396e5fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 15 May 2024 10:19:06 +0200 Subject: [PATCH 1130/1551] Use `boehmgc` package from nixpkgs in `shell.nix` (#14591) --- shell.nix | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/shell.nix b/shell.nix index fbfccdc2be5b..9dbe6c9be4b2 100644 --- a/shell.nix +++ b/shell.nix @@ -108,23 +108,8 @@ let }; }."llvm_${toString llvm}"); - boehmgc = pkgs.stdenv.mkDerivation rec { - pname = "boehm-gc"; - version = "8.2.4"; - - src = builtins.fetchTarball { - url = "https://github.com/ivmai/bdwgc/releases/download/v${version}/gc-${version}.tar.gz"; - sha256 = "0primpxl7hykfbmszf7ppbv7k1nj41f1r5m56n96q92mmzqlwybm"; - }; - - configureFlags = [ - "--disable-debug" - "--disable-dependency-tracking" - "--disable-shared" - "--enable-large-config" - ]; - - enableParallelBuilding = true; + boehmgc = pkgs.boehmgc.override { + enableLargeConfig = true; }; stdLibDeps = with pkgs; [ From 52bbe7f006db3a386f5afba23f6e15e6a7e43fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 15 May 2024 10:20:19 +0200 Subject: [PATCH 1131/1551] Simplify LLVM dependency in `shell.nix` (#14590) --- shell.nix | 46 ++++------------------------------------------ 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/shell.nix b/shell.nix index 9dbe6c9be4b2..961ed63cc47e 100644 --- a/shell.nix +++ b/shell.nix @@ -31,6 +31,7 @@ let }; pkgs = if musl then nixpkgs.pkgsMusl else nixpkgs; + llvmPackages = pkgs."llvmPackages_${toString llvm}"; genericBinary = { url, sha256 }: pkgs.stdenv.mkDerivation rec { @@ -69,45 +70,6 @@ let pkgconfig = pkgs.pkgconfig; - llvm_suite = ({ - llvm_16 = { - llvm = pkgs.llvm_16; - extra = [ pkgs.llvmPackages_16.bintools pkgs.lldb_16 ]; - }; - llvm_15 = { - llvm = pkgs.llvm_15; - extra = [ pkgs.llvmPackages_15.bintools pkgs.lldb_15 ]; - }; - llvm_14 = { - llvm = pkgs.llvm_14; - extra = [ pkgs.llvmPackages_14.bintools pkgs.lldb_14 ]; - }; - llvm_13 = { - llvm = pkgs.llvm_13; - extra = [ pkgs.llvmPackages_13.bintools pkgs.lldb_13 ]; - }; - llvm_12 = { - llvm = pkgs.llvm_12; - extra = [ pkgs.llvmPackages_12.bintools pkgs.lldb_12 ]; - }; - llvm_11 = { - llvm = pkgs.llvm_11; - extra = [ pkgs.llvmPackages_11.bintools pkgs.lldb_11 ]; - }; - llvm_10 = { - llvm = pkgs.llvm_10; - extra = [ pkgs.llvmPackages_10.bintools ]; # lldb marked as broken - }; - llvm_9 = { - llvm = pkgs.llvm_9; - extra = [ pkgs.llvmPackages_9.bintools ]; # lldb marked as broken - }; - llvm_8 = { - llvm = pkgs.llvm_8; - extra = [ pkgs.llvmPackages_8.bintools ]; # lldb marked as broken - }; - }."llvm_${toString llvm}"); - boehmgc = pkgs.boehmgc.override { enableLargeConfig = true; }; @@ -116,7 +78,7 @@ let boehmgc gmp libevent libiconv libxml2 libyaml openssl pcre2 zlib ] ++ lib.optionals stdenv.isDarwin [ libiconv ]; - tools = [ pkgs.hostname pkgs.git llvm_suite.extra ]; + tools = [ pkgs.hostname pkgs.git llvmPackages.bintools ] ++ pkgs.lib.optional (!llvmPackages.lldb.meta.broken) llvmPackages.lldb; in pkgs.stdenv.mkDerivation rec { @@ -125,11 +87,11 @@ pkgs.stdenv.mkDerivation rec { buildInputs = tools ++ stdLibDeps ++ [ latestCrystalBinary pkgconfig - llvm_suite.llvm + llvmPackages.libllvm pkgs.libffi ]; - LLVM_CONFIG = "${llvm_suite.llvm.dev}/bin/llvm-config"; + LLVM_CONFIG = "${llvmPackages.libllvm.dev}/bin/llvm-config"; MACOSX_DEPLOYMENT_TARGET = "10.11"; } From 920a2da470743ebfa59e8ee8aa8185ab57dbe14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 15 May 2024 10:21:29 +0200 Subject: [PATCH 1132/1551] Fix nixpkgs `pkg-config` name in `shell.nix` (#14593) --- shell.nix | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shell.nix b/shell.nix index 961ed63cc47e..1d839dcf318f 100644 --- a/shell.nix +++ b/shell.nix @@ -68,8 +68,6 @@ let }; }.${pkgs.stdenv.system}); - pkgconfig = pkgs.pkgconfig; - boehmgc = pkgs.boehmgc.override { enableLargeConfig = true; }; @@ -86,7 +84,7 @@ pkgs.stdenv.mkDerivation rec { buildInputs = tools ++ stdLibDeps ++ [ latestCrystalBinary - pkgconfig + pkgs.pkg-config llvmPackages.libllvm pkgs.libffi ]; From 453a1591f7175aba81871b80411324929bd96cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 15 May 2024 10:21:46 +0200 Subject: [PATCH 1133/1551] Fix `Regex#inspect` with non-literal-compatible options (#14575) --- spec/std/regex_spec.cr | 28 ++++++++++++++++++------- src/regex.cr | 46 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 310f94ffc9b4..13d301987c56 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -449,15 +449,29 @@ describe "Regex" do end describe "#inspect" do - it "with options" do - /foo/.inspect.should eq("/foo/") - /foo/im.inspect.should eq("/foo/im") - /foo/imx.inspect.should eq("/foo/imx") + context "with literal-compatible options" do + it "prints flags" do + /foo/.inspect.should eq("/foo/") + /foo/im.inspect.should eq("/foo/im") + /foo/imx.inspect.should eq("/foo/imx") + end + + it "escapes" do + %r(/).inspect.should eq("/\\//") + %r(\/).inspect.should eq("/\\//") + end end - it "escapes" do - %r(/).inspect.should eq("/\\//") - %r(\/).inspect.should eq("/\\//") + context "with non-literal-compatible options" do + it "prints flags" do + Regex.new("foo", :anchored).inspect.should eq %(Regex.new("foo", Regex::Options::ANCHORED)) + Regex.new("foo", :no_utf_check).inspect.should eq %(Regex.new("foo", Regex::Options::NO_UTF8_CHECK)) + Regex.new("foo", Regex::CompileOptions[IGNORE_CASE, ANCHORED]).inspect.should eq %(Regex.new("foo", Regex::Options[IGNORE_CASE, ANCHORED])) + end + + it "escapes" do + Regex.new(%("), :anchored).inspect.should eq %(Regex.new("\\"", Regex::Options::ANCHORED)) + end end end diff --git a/src/regex.cr b/src/regex.cr index 2b593397528b..69dd500226a9 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -590,14 +590,36 @@ class Regex nil end - # Convert to `String` in literal format. Returns the source as a `String` in - # Regex literal format, delimited in forward slashes (`/`), with any - # optional flags included. + # Prints to *io* an unambiguous string representation of this regular expression object. # + # Uses the regex literal syntax with basic option flags if sufficient (i.e. no + # other options than `IGNORE_CASE`, `MULTILINE`, or `EXTENDED` are set). + # Otherwise the syntax presents a `Regex.new` call. # ``` - # /ab+c/ix.inspect # => "/ab+c/ix" + # /ab+c/ix.inspect # => "/ab+c/ix" + # Regex.new("ab+c", :anchored).inspect # => Regex.new("ab+c", Regex::Options::ANCHORED) # ``` def inspect(io : IO) : Nil + if (options & ~CompileOptions[IGNORE_CASE, MULTILINE, EXTENDED]).none? + inspect_literal(io) + else + inspect_extensive(io) + end + end + + # Convert to `String` in literal format. Returns the source as a `String` in + # Regex literal format, delimited in forward slashes (`/`), with option flags + # included. + # + # Only `IGNORE_CASE`, `MULTILINE`, and `EXTENDED` options can be represented. + # Any other option value is ignored. Use `#inspect` instead for an unambiguous + # and correct representation. + # + # ``` + # /ab+c/ix.inspect_literal # => "/ab+c/ix" + # Regex.new("ab+c", :anchored).inspect_literal # => "/ab+c/" + # ``` + private def inspect_literal(io : IO) : Nil io << '/' Regex.append_source(source, io) io << '/' @@ -606,6 +628,22 @@ class Regex io << 'x' if options.extended? end + # Prints to *io* an extensive string representation of this regular expression object. + # The result is unambiguous and mirrors a Crystal expression to recreate an equivalent + # instance. + # + # ``` + # /ab+c/ix.inspect_literal # => Regex.new("ab+c", Regex::Options[IGNORE_CASE, EXTENDED]) + # Regex.new("ab+c", :anchored).inspect_literal # => Regex.new("ab+c", Regex::Options::ANCHORED) + # ``` + private def inspect_extensive(io : IO) : Nil + io << "Regex.new(" + source.inspect(io) + io << ", " + options.inspect(io) + io << ")" + end + # Match at character index. Matches a regular expression against `String` # *str*. Starts at the character index given by *pos* if given, otherwise at # the start of *str*. Returns a `Regex::MatchData` if *str* matched, otherwise From 5f0e390cac5199ef4789d0923d4305a7f664ce2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 15 May 2024 16:57:19 +0200 Subject: [PATCH 1134/1551] Revert "Sanitize doc generation" (#14595) --- lib/.shards.info | 3 - lib/sanitize/.circleci/config.yml | 94 ---- lib/sanitize/.editorconfig | 9 - lib/sanitize/.gitignore | 9 - lib/sanitize/LICENSE | 202 -------- lib/sanitize/Makefile | 54 -- lib/sanitize/README.md | 128 ----- lib/sanitize/lib | 1 - lib/sanitize/scripts/generate-docs.sh | 18 - lib/sanitize/shard.yml | 15 - lib/sanitize/spec/html_sanitizer/basic.hrx | 70 --- lib/sanitize/spec/html_sanitizer/class.hrx | 34 -- .../spec/html_sanitizer/combined_policies.hrx | 42 -- .../html_sanitizer/combined_policies_spec.cr | 11 - lib/sanitize/spec/html_sanitizer/default.hrx | 138 ----- .../html_sanitizer/html_sanitizer_spec.cr | 102 ---- lib/sanitize/spec/html_sanitizer/img.hrx | 46 -- lib/sanitize/spec/html_sanitizer/links.hrx | 89 ---- .../protocol-based-javascript.hrx | 160 ------ .../html_sanitizer/protocol_javascript.hrx | 67 --- lib/sanitize/spec/html_sanitizer/url_spec.cr | 8 - lib/sanitize/spec/html_sanitizer/xss.hrx | 476 ------------------ lib/sanitize/spec/spec_helper.cr | 1 - lib/sanitize/spec/support/hrx.cr | 83 --- lib/sanitize/spec/text_policy.hrx | 67 --- lib/sanitize/spec/text_policy_spec.cr | 17 - lib/sanitize/spec/uri_sanitizer_spec.cr | 130 ----- lib/sanitize/src/adapter/libxml2.cr | 137 ----- lib/sanitize/src/policy.cr | 45 -- lib/sanitize/src/policy/html_sanitizer.cr | 350 ------------- .../src/policy/html_sanitizer/safelist.cr | 70 --- lib/sanitize/src/policy/text.cr | 23 - lib/sanitize/src/policy/whitelist.cr | 57 --- lib/sanitize/src/processor.cr | 110 ---- lib/sanitize/src/sanitize.cr | 5 - lib/sanitize/src/uri_sanitizer.cr | 107 ---- shard.lock | 4 - shard.yml | 5 +- .../crystal/tools/doc/doc_renderer_spec.cr | 30 +- .../crystal/tools/doc/markd_doc_renderer.cr | 23 - 40 files changed, 3 insertions(+), 3037 deletions(-) delete mode 100644 lib/sanitize/.circleci/config.yml delete mode 100644 lib/sanitize/.editorconfig delete mode 100644 lib/sanitize/.gitignore delete mode 100644 lib/sanitize/LICENSE delete mode 100644 lib/sanitize/Makefile delete mode 100644 lib/sanitize/README.md delete mode 120000 lib/sanitize/lib delete mode 100755 lib/sanitize/scripts/generate-docs.sh delete mode 100644 lib/sanitize/shard.yml delete mode 100644 lib/sanitize/spec/html_sanitizer/basic.hrx delete mode 100644 lib/sanitize/spec/html_sanitizer/class.hrx delete mode 100644 lib/sanitize/spec/html_sanitizer/combined_policies.hrx delete mode 100644 lib/sanitize/spec/html_sanitizer/combined_policies_spec.cr delete mode 100644 lib/sanitize/spec/html_sanitizer/default.hrx delete mode 100644 lib/sanitize/spec/html_sanitizer/html_sanitizer_spec.cr delete mode 100644 lib/sanitize/spec/html_sanitizer/img.hrx delete mode 100644 lib/sanitize/spec/html_sanitizer/links.hrx delete mode 100644 lib/sanitize/spec/html_sanitizer/protocol-based-javascript.hrx delete mode 100644 lib/sanitize/spec/html_sanitizer/protocol_javascript.hrx delete mode 100644 lib/sanitize/spec/html_sanitizer/url_spec.cr delete mode 100644 lib/sanitize/spec/html_sanitizer/xss.hrx delete mode 100644 lib/sanitize/spec/spec_helper.cr delete mode 100644 lib/sanitize/spec/support/hrx.cr delete mode 100644 lib/sanitize/spec/text_policy.hrx delete mode 100644 lib/sanitize/spec/text_policy_spec.cr delete mode 100644 lib/sanitize/spec/uri_sanitizer_spec.cr delete mode 100644 lib/sanitize/src/adapter/libxml2.cr delete mode 100644 lib/sanitize/src/policy.cr delete mode 100644 lib/sanitize/src/policy/html_sanitizer.cr delete mode 100644 lib/sanitize/src/policy/html_sanitizer/safelist.cr delete mode 100644 lib/sanitize/src/policy/text.cr delete mode 100644 lib/sanitize/src/policy/whitelist.cr delete mode 100644 lib/sanitize/src/processor.cr delete mode 100644 lib/sanitize/src/sanitize.cr delete mode 100644 lib/sanitize/src/uri_sanitizer.cr diff --git a/lib/.shards.info b/lib/.shards.info index b15cf54122a0..7f03bb906410 100644 --- a/lib/.shards.info +++ b/lib/.shards.info @@ -7,6 +7,3 @@ shards: reply: git: https://github.com/i3oris/reply.git version: 0.3.1+git.commit.90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 - sanitize: - git: https://github.com/straight-shoota/sanitize.git - version: 0.1.0+git.commit.75c141b619c77956e88f557149566cd28876398b diff --git a/lib/sanitize/.circleci/config.yml b/lib/sanitize/.circleci/config.yml deleted file mode 100644 index df9b752af31d..000000000000 --- a/lib/sanitize/.circleci/config.yml +++ /dev/null @@ -1,94 +0,0 @@ -version: 2 - -dry: - restore_shards_cache: &restore_shards_cache - keys: - - shards-cache-v1-{{ .Branch }}-{{ checksum "shard.yml" }} - - shards-cache-v1-{{ .Branch }} - - shards-cache-v1 - - save_shards_cache: &save_shards_cache - key: shards-cache-v1-{{ .Branch }}-{{ checksum "shard.yml" }} - paths: - - ./shards-cache - -jobs: - test: - docker: - - image: crystallang/crystal:latest - environment: - SHARDS_CACHE_PATH: ./shards-cache - steps: - - run: crystal --version - - - checkout - - - restore_cache: *restore_shards_cache - - run: shards - - save_cache: *save_shards_cache - - - run: make test - - - run: crystal tool format --check spec src - - deploy-docs: - docker: - - image: crystallang/crystal:latest - environment: - SHARDS_CACHE_PATH: ./shards-cache - steps: - - run: crystal --version - - - checkout - - - run: scripts/generate-docs.sh - - - run: apt update && apt install -y curl rsync - - run: - command: curl https://raw.githubusercontent.com/straight-shoota/autodeploy-docs/master/autodeploy-docs.sh | bash - environment: - GIT_COMMITTER_NAME: cirlceci - GIT_COMMITTER_EMAIL: circle@circleci.com - - test-on-nightly: - docker: - - image: crystallang/crystal:nightly - environment: - SHARDS_CACHE_PATH: ./shards-cache - steps: - - run: crystal --version - - - checkout - - - restore_cache: *restore_shards_cache - - run: shards - - - run: make test - - - run: crystal tool format --check spec src - -workflows: - version: 2 - # Run tests on every single commit - ci: - jobs: - - test - # Build and depoy docs only on master branch - - deploy-docs: - requires: - - test - filters: &master-only - branches: - only: - - master - # Run tests every night using crystal nightly - nightly: - triggers: - - schedule: - cron: "0 4 * * *" - filters: - branches: - only: - - master - jobs: - - test-on-nightly diff --git a/lib/sanitize/.editorconfig b/lib/sanitize/.editorconfig deleted file mode 100644 index 163eb75c8525..000000000000 --- a/lib/sanitize/.editorconfig +++ /dev/null @@ -1,9 +0,0 @@ -root = true - -[*.cr] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -indent_style = space -indent_size = 2 -trim_trailing_whitespace = true diff --git a/lib/sanitize/.gitignore b/lib/sanitize/.gitignore deleted file mode 100644 index 0bbd4a9f41e1..000000000000 --- a/lib/sanitize/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -/docs/ -/lib/ -/bin/ -/.shards/ -*.dwarf - -# Libraries don't need dependency lock -# Dependencies will be locked in applications that use them -/shard.lock diff --git a/lib/sanitize/LICENSE b/lib/sanitize/LICENSE deleted file mode 100644 index d64569567334..000000000000 --- a/lib/sanitize/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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/lib/sanitize/Makefile b/lib/sanitize/Makefile deleted file mode 100644 index 980fc7a52014..000000000000 --- a/lib/sanitize/Makefile +++ /dev/null @@ -1,54 +0,0 @@ --include Makefile.local # for optional local options - -BUILD_TARGET ::= bin/app - -# The shards command to use -SHARDS ?= shards -# The crystal command to use -CRYSTAL ?= crystal - -SRC_SOURCES ::= $(shell find src -name '*.cr' 2>/dev/null) -LIB_SOURCES ::= $(shell find lib -name '*.cr' 2>/dev/null) -SPEC_SOURCES ::= $(shell find spec -name '*.cr' 2>/dev/null) - -.PHONY: test -test: ## Run the test suite -test: lib - $(CRYSTAL) spec - -.PHONY: format -format: ## Apply source code formatting -format: $(SRC_SOURCES) $(SPEC_SOURCES) - $(CRYSTAL) tool format src spec - -docs: ## Generate API docs -docs: $(SRC_SOURCES) lib - $(CRYSTAL) docs -o docs - -lib: shard.lock - $(SHARDS) install - -shard.lock: shard.yml - $(SHARDS) update - -.PHONY: clean -clean: ## Remove application binary -clean: - @rm -f $(BUILD_TARGET) - -.PHONY: help -help: ## Show this help - @echo - @printf '\033[34mtargets:\033[0m\n' - @grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) |\ - sort |\ - awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' - @echo - @printf '\033[34moptional variables:\033[0m\n' - @grep -hE '^[a-zA-Z_-]+ \?=.*?## .*$$' $(MAKEFILE_LIST) |\ - sort |\ - awk 'BEGIN {FS = " \\?=.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' - @echo - @printf '\033[34mrecipes:\033[0m\n' - @grep -hE '^##.*$$' $(MAKEFILE_LIST) |\ - awk 'BEGIN {FS = "## "}; /^## [a-zA-Z_-]/ {printf " \033[36m%s\033[0m\n", $$2}; /^## / {printf " %s\n", $$2}' diff --git a/lib/sanitize/README.md b/lib/sanitize/README.md deleted file mode 100644 index fdca90db33a1..000000000000 --- a/lib/sanitize/README.md +++ /dev/null @@ -1,128 +0,0 @@ -# sanitize - -`sanitize` is a Crystal library for transforming HTML/XML trees. It's primarily -used to sanitize HTML from untrusted sources in order to prevent -[XSS attacks](http://en.wikipedia.org/wiki/Cross-site_scripting) and other -adversities. - -It builds on stdlib's [`XML`](https://crystal-lang.org/api/XML.html) module to -parse HTML/XML. Based on [libxml2](http://xmlsoft.org/) it's a solid parser and -turns malformed and malicious input into valid and safe markup. - -* Code: [https://github.com/straight-shoota/sanitize](https://github.com/straight-shoota/sanitize) -* API docs: [https://straight-shoota.github.io/sanitize/api/latest/](https://straight-shoota.github.io/sanitize/api/latest/) -* Issue tracker: [https://github.com/straight-shoota/sanitize/issues](https://github.com/straight-shoota/sanitize/issues) -* Shardbox: [https://shardbox.org/shards/sanitize](https://shardbox.org/shards/sanitize) - -## Installation - -1. Add the dependency to your `shard.yml`: - - ```yaml - dependencies: - sanitize: - github: straight-shoota/sanitize - ``` - -2. Run `shards install` - -## Sanitization Features - -The `Sanitize::Policy::HTMLSanitizer` policy applies the following sanitization steps. Except -for the first one (which is essential to the entire process), all can be disabled -or configured. - -* Turns malformed and malicious HTML into valid and safe markup. -* Strips HTML elements and attributes not included in the safe list. -* Sanitizes URL attributes (like `href` or `src`) with customizable sanitization - policy. -* Adds `rel="nofollow"` to all links and `rel="noopener"` to links with `target`. -* Validates values of accepted attributes `align`, `width` and `height`. -* Filters `class` attributes based on a whitelist (by default all classes are - rejected). - -## Usage - -Transformation is based on rules defined by `Sanitize::Policy` implementations. - -The recommended standard policy for HTML sanitization is `Sanitize::Policy::HTMLSanitizer.common` -which represents good defaults for most use cases. -It sanitizes user input against a known safe list of accepted elements and their -attributes. - -```crystal -require "sanitize" - -sanitizer = Sanitize::Policy::HTMLSanitizer.common -sanitizer.process(%(foo)) # => %(foo) -sanitizer.process(%(

    foo

    )) # => %(

    foo

    ) -sanitizer.process(%()) # => %() -sanitizer.process(%(
    foobar
    )) # => %(
    foobar
    ) -``` - -Sanitization should always run after any other processing (for example rendering -Markdown) and is a must when including HTML from untrusted sources into a web -page. - -### With Markd - -A typical format for user generated content is `Markdown`. Even though it has -only a very limited feature set compared to HTML, it can still produce -potentially harmful HTML and is is usually possible to embed raw HTML directly. -So Sanitization is necessary. - -The most common Markdown renderer is [markd](https://shardbox.org/shards/markd), -so here is a sample how to use it with `sanitize`: - -````crystal -sanitizer = Sanitize::Policy::HTMLSanitizer.common -# Allow classes with `language-` prefix which are used for syntax highlighting. -sanitizer.valid_classes << /language-.+/ - -markdown = <<-MD - Sanitization with [https://shardbox.org/shards/sanitize](sanitize) is not that - **difficult**. - ```cr - puts "Hello World!" - ``` -

    Hello world!

    - MD - -html = Markd.to_html(markdown) -sanitized = sanitizer.process(html) -puts sanitized -```` - -The result: - -```html -

    Sanitization with https://shardbox.org/shards/sanitize is not that -difficult.

    -
    puts "Hello World!"
    -
    -

    Hello world!

    -``` - -## Limitations - -Sanitizing CSS is not supported. Thus `style` attributes can't be accepted in a -safe way. -CSS sanitization features may be added when a CSS parsing library is available. - -## Security - -If you want to privately disclose security-issues, please contact -[straightshoota](https://keybase.io/straightshoota) on Keybase or -[straightshoota@gmail.com](mailto:straightshoota@gmail.com) (PGP: `DF2D C9E9 FFB9 6AE0 2070 D5BC F0F3 4963 7AC5 087A`). - -## Contributing - -1. Fork it ([https://github.com/straight-shoota/sanitize/fork](https://github.com/straight-shoota/sanitize/fork)) -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Add some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create a new Pull Request - -## Contributors - -- [Johannes Müller](https://github.com/straight-shoota) - creator and maintainer diff --git a/lib/sanitize/lib b/lib/sanitize/lib deleted file mode 120000 index a96aa0ea9d8c..000000000000 --- a/lib/sanitize/lib +++ /dev/null @@ -1 +0,0 @@ -.. \ No newline at end of file diff --git a/lib/sanitize/scripts/generate-docs.sh b/lib/sanitize/scripts/generate-docs.sh deleted file mode 100755 index 5dbaf344c48d..000000000000 --- a/lib/sanitize/scripts/generate-docs.sh +++ /dev/null @@ -1,18 +0,0 @@ -#! /usr/bin/env bash - -set -e - -GENERATED_DOCS_DIR="./docs" - -echo -e "Building docs into ${GENERATED_DOCS_DIR}" -echo -e "Clearing ${GENERATED_DOCS_DIR} directory" -rm -rf "${GENERATED_DOCS_DIR}" - -echo -e "Running \`make docs\`..." -make docs - -echo -e "Copying README.md" - -# "{{" and "{%"" need to be escaped, otherwise Jekyll might interpret the expressions (on Github Pages) -ESCAPE_TEMPLATE='s/{{/{{"{{"}}/g; s/{\%/{{"{%"}}/g;' -sed "${ESCAPE_TEMPLATE}" README.md > "${GENERATED_DOCS_DIR}/README.md" diff --git a/lib/sanitize/shard.yml b/lib/sanitize/shard.yml deleted file mode 100644 index eb9158fc58e4..000000000000 --- a/lib/sanitize/shard.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: sanitize -version: 0.1.0 - -authors: - - Johannes Müller - -crystal: 0.35.0 - -license: Apache-2.0 - -documentation: https://straight-shoota.github.io/sanitize/api/latest/ - -development_dependencies: - hrx: - github: straight-shoota/hrx diff --git a/lib/sanitize/spec/html_sanitizer/basic.hrx b/lib/sanitize/spec/html_sanitizer/basic.hrx deleted file mode 100644 index fe291053b1dc..000000000000 --- a/lib/sanitize/spec/html_sanitizer/basic.hrx +++ /dev/null @@ -1,70 +0,0 @@ -<===> empty/document.html -<===> - - -<===> pending:skeleton/document.html - - - - - -<===> - - -<===> invalid/fragment.html -foo

    bar

    bazz
    quux
    -<===> invalid/common.html -foo

    bar

    bazz
    quux
    -<===> - - - -<===> invalid-div/fragment.html -foo

    bar

    bazz
    quux
    -<===> invalid-div/common.html -foo

    bar

    bazz quux -<===> - - -<===> basic/fragment.html -Lorem ipsum dolor sit
    amet -<===> basic/common.html -Lorem ipsum dolor sit
    amet -<===> - - -<===> malformed/fragment.html -Lorem dolor sit
    amet -<===> malicious/common.html -Lorem ipsum dolor sit
    amet <script>alert("hello world"); -<===> - - -<===> target="_blank"/fragment.html -foo -<===> target="_blank"/common.html -foo -<===> - - -<===> percent encoded URL/fragment.html -CI Status -<===> percent encoded URL/common.html -CI Status -<===> diff --git a/lib/sanitize/spec/html_sanitizer/class.hrx b/lib/sanitize/spec/html_sanitizer/class.hrx deleted file mode 100644 index 897c1c2f4606..000000000000 --- a/lib/sanitize/spec/html_sanitizer/class.hrx +++ /dev/null @@ -1,34 +0,0 @@ -<===> reject/fragment.html -
    -<===> reject/common.html -
    -<===> reject/allow-prefix.html -
    -<===> - - -<===> allow-with-prefix/fragment.html -
    -<===> allow-with-prefix/common.html -
    -<===> allow-with-prefix/allow-prefix.html -
    -<===> - - -<===> reject-non-prefix/fragment.html -
    -<===> reject-non-prefix/common.html -
    -<===> reject-non-prefix/allow-prefix.html -
    -<===> - - -<===> allow-explicit/fragment.html -
    -<===> allow-explicit/common.html -
    -<===> allow-explicit/allow-prefix.html -
    -<===> diff --git a/lib/sanitize/spec/html_sanitizer/combined_policies.hrx b/lib/sanitize/spec/html_sanitizer/combined_policies.hrx deleted file mode 100644 index 01de9ae3f93f..000000000000 --- a/lib/sanitize/spec/html_sanitizer/combined_policies.hrx +++ /dev/null @@ -1,42 +0,0 @@ -<===> basic/fragment.html -Lorem ipsum dolor sit
    amet -<===> basic/text.html -Lorem ipsum dolor sit amet -<===> basic/inline.html -Lorem ipsum dolor sit amet -<===> basic/common.html -Lorem ipsum dolor sit
    amet -<===> - - -<===> malformed/fragment.html -Lorem dolor sit
    amet -<===> malicious/text.html -Lorem ipsum dolor sit amet <script>alert("hello world"); -<===> malicious/inline.html -Lorem ipsum dolor sit amet <script>alert("hello world"); -<===> malicious/common.html -Lorem ipsum dolor sit
    amet <script>alert("hello world"); -<===> diff --git a/lib/sanitize/spec/html_sanitizer/combined_policies_spec.cr b/lib/sanitize/spec/html_sanitizer/combined_policies_spec.cr deleted file mode 100644 index 5751fba7b459..000000000000 --- a/lib/sanitize/spec/html_sanitizer/combined_policies_spec.cr +++ /dev/null @@ -1,11 +0,0 @@ -require "../support/hrx" -require "../../src/processor" -require "../../src/policy/html_sanitizer" -require "../../src/policy/text" - -run_hrx_samples Path["./combined_policies.hrx"], { - "text" => Sanitize::Policy::Text.new, - "inline" => Sanitize::Policy::HTMLSanitizer.inline.no_links, - "basic" => Sanitize::Policy::HTMLSanitizer.basic, - "common" => Sanitize::Policy::HTMLSanitizer.common, -} diff --git a/lib/sanitize/spec/html_sanitizer/default.hrx b/lib/sanitize/spec/html_sanitizer/default.hrx deleted file mode 100644 index 627adb8fc7fd..000000000000 --- a/lib/sanitize/spec/html_sanitizer/default.hrx +++ /dev/null @@ -1,138 +0,0 @@ -<===> invalid/fragment.html -foo

    bar

    bazz
    quux
    -<===> invalid/stripped.html -foo

    bar

    bazz
    quux
    -<===> invalid/escaped.html -<invalid>foo<p>bar</p>bazz</invalid>
    quux
    -<===> invalid/pruned.html -
    quux
    -<===> - - -<===> bad_argument/fragment.html -
    foo
    -<===> bad_argument/stripped.html -
    foo
    -<===> - -<==> whitewash/fragment.html -no
    foo
    bar -<==> whitewash/pruned.html -
    foo
    -<==> - - -<===> nofollow/fragment.html -Click here -<===> nofollow/stripped.html -Click here -<===> - - -<===> nofollow-rel/fragment.html -Click here -<===> nofollow-rel/stripped.html -Click here -<===> - - -<===> unprintable/fragment.html -Lo\u2029ofah ro\u2028cks! -<===> unprintable/stripped.html -Loofah rocks! -<===> - - -<===> msword/fragment.html - - -

    Foo BOLD

    -<===> msword/stripped.html - - -

    Foo BOLD

    -<===> - - -<===> entities/fragment.html -

    foo bar

    -<===> - - -<===> align/fragment.html -

    foo

    -<===> - - -<===> align-empty/fragment.html -

    foo

    -<===> align-empty/common.html -

    foo

    -<===> - - -<===> align-invalid/fragment.html -

    foo

    -<===> align-invalid/common.html -

    foo

    -<===> diff --git a/lib/sanitize/spec/html_sanitizer/html_sanitizer_spec.cr b/lib/sanitize/spec/html_sanitizer/html_sanitizer_spec.cr deleted file mode 100644 index f70a965345fa..000000000000 --- a/lib/sanitize/spec/html_sanitizer/html_sanitizer_spec.cr +++ /dev/null @@ -1,102 +0,0 @@ -require "../support/hrx" -require "../../src/policy/html_sanitizer" - -describe Sanitize::Policy::HTMLSanitizer do - it "removes invalid element" do - Sanitize::Policy::HTMLSanitizer.common.process("

    foobar

    ").should eq "

    foobar

    " - end - - it "inserts whitespace for removed block tag" do - Sanitize::Policy::HTMLSanitizer.common.process("

    foo

    bar
    baz

    ").should eq "

    foo bar baz

    " - end - - it "strips tag with invalid URL attribute" do - Sanitize::Policy::HTMLSanitizer.common.process(%()).should eq %() - Sanitize::Policy::HTMLSanitizer.common.process(%(foo)).should eq "foo" - end - - it "escapes URL attribute" do - Sanitize::Policy::HTMLSanitizer.common.process(%()).should eq %() - end - - it %(adds rel="noopener" on target="_blank") do - policy = Sanitize::Policy::HTMLSanitizer.common - policy.process(%(foo)).should eq(%(foo)) - policy.accepted_attributes["a"] << "target" - policy.process(%(foo)).should eq(%(foo)) - end - - it "doesn't leak configuration" do - policy = Sanitize::Policy::HTMLSanitizer.common - policy.accepted_attributes["p"] << "invalid" - policy.process(%(

    bar

    )).should eq(%(

    bar

    )) - Sanitize::Policy::HTMLSanitizer.common.process(%(

    bar

    )).should eq(%(

    bar

    )) - end - - describe "html scaffold" do - it "fragment" do - Sanitize::Policy::HTMLSanitizer.common.process("FOO

    BAR

    ").should eq "FOO

    BAR

    " - end - - it "document" do - sanitizer = Sanitize::Policy::HTMLSanitizer.common - sanitizer.accept_tag("html") - sanitizer.accept_tag("head") - sanitizer.accept_tag("body") - sanitizer.process_document("FOO

    BAR

    ").should eq "FOO

    BAR

    \n" - end - end - - describe "#transform_classes" do - it "strips classes by default" do - policy = Sanitize::Policy::HTMLSanitizer.inline - orig_attributes = {"class" => "foo bar baz"} - attributes = orig_attributes.clone - policy.transform_classes("div", attributes) - attributes.should eq Hash(String, String).new - end - - it "accepts classes" do - policy = Sanitize::Policy::HTMLSanitizer.inline - orig_attributes = {"class" => "foo bar baz"} - attributes = orig_attributes.clone - - policy.valid_classes << /fo*/ - policy.valid_classes << "bar" - policy.transform_classes("div", attributes) - attributes.should eq({"class" => "foo bar"}) - end - - it "only matches full class name" do - policy = Sanitize::Policy::HTMLSanitizer.inline - orig_attributes = {"class" => "foobar barfoo barfoobaz foo fom"} - attributes = orig_attributes.clone - - policy.valid_classes << /fo./ - policy.transform_classes("div", attributes) - attributes.should eq({"class" => "foo fom"}) - end - end - - run_hrx_samples Path["basic.hrx"], { - "common" => Sanitize::Policy::HTMLSanitizer.common, - } - run_hrx_samples Path["protocol_javascript.hrx"], { - "common" => Sanitize::Policy::HTMLSanitizer.common, - } - run_hrx_samples Path["links.hrx"], { - "common" => Sanitize::Policy::HTMLSanitizer.common, - } - run_hrx_samples Path["xss.hrx"], { - "common" => Sanitize::Policy::HTMLSanitizer.common, - } - run_hrx_samples Path["img.hrx"], { - "common" => Sanitize::Policy::HTMLSanitizer.common, - } - run_hrx_samples Path["class.hrx"], { - "common" => Sanitize::Policy::HTMLSanitizer.common, - "allow-prefix" => Sanitize::Policy::HTMLSanitizer.common.tap { |sanitizer| - sanitizer.valid_classes = Set{/allowed-.+/, "explicitly-allowed"} - }, - } -end diff --git a/lib/sanitize/spec/html_sanitizer/img.hrx b/lib/sanitize/spec/html_sanitizer/img.hrx deleted file mode 100644 index 1fd81d00d687..000000000000 --- a/lib/sanitize/spec/html_sanitizer/img.hrx +++ /dev/null @@ -1,46 +0,0 @@ -<===> img/fragment.html - -<===> - - -<===> img with width/fragment.html - -<===> - - -<===> img with height/fragment.html - -<===> - - -<===> img with width and height/fragment.html - -<===> - - -<===> img invalid height/fragment.html - -<===> img invalid height/common.html - -<===> - - -<===> img invalid width/fragment.html - -<===> img invalid width/common.html - -<===> - - - -<===> img invalid width and height/fragment.html - -<===> img invalid width and height/common.html - -<===> - - - -<===> img percent width and height/fragment.html - -<===> diff --git a/lib/sanitize/spec/html_sanitizer/links.hrx b/lib/sanitize/spec/html_sanitizer/links.hrx deleted file mode 100644 index 104740825fab..000000000000 --- a/lib/sanitize/spec/html_sanitizer/links.hrx +++ /dev/null @@ -1,89 +0,0 @@ -<===> links/1/fragment.html - -<===> links/1/common.html - -<===> - - -<===> links/2/fragment.html - -<===> links/2/common.html - -<===> - - -<===> links/3/fragment.html - -<===> links/3/common.html - -<===> - - -<===> links/4/fragment.html - -<===> links/4/common.html - -<===> - - -<===> links/5/fragment.html - -<===> links/5/common.html - -<===> - - -<===> links/6/fragment.html - -<===> links/6/common.html - -<===> - - -<===> links/7/fragment.html - -<===> links/7/common.html - -<===> - - -<===> links/8/fragment.html - -<===> links/8/common.html - -<===> - - -<===> links/9/fragment.html - -<===> links/9/common.html - -<===> - - -<===> links/10/fragment.html - -<===> links/10/common.html - -<===> - - -<===> links/11/fragment.html -Red dot -<===> links/11/common.html -Red dot -<===> - - -<===> links/12/fragment.html - -<===> links/12/common.html - -<===> - - -<===> links/13/fragment.html - -<===> links/13/common.html - -<===> diff --git a/lib/sanitize/spec/html_sanitizer/protocol-based-javascript.hrx b/lib/sanitize/spec/html_sanitizer/protocol-based-javascript.hrx deleted file mode 100644 index 16576ea78f80..000000000000 --- a/lib/sanitize/spec/html_sanitizer/protocol-based-javascript.hrx +++ /dev/null @@ -1,160 +0,0 @@ - -<===> simple, no spaces/fragment.html -foo -<===> simple, no spaces/common.html -foo -<===> simple, no spaces/restricted.html -foo -<===> simple, no spaces/basic.html -foo -<===> simple, no spaces/relaxed.html -foo - -<===> simple, spaces before/fragment.html -foo -<===> simple, spaces before/common.html -foo -<===> simple, spaces before/restricted.html -foo -<===> simple, spaces before/basic.html -foo -<===> simple, spaces before/relaxed.html -foo - -<===> simple, spaces after/fragment.html -foo -<===> simple, spaces after/common.html -foo -<===> simple, spaces after/restricted.html -foo -<===> simple, spaces after/basic.html -foo -<===> simple, spaces after/relaxed.html -foo - -<===> simple, spaces before and after/fragment.html -foo -<===> simple, spaces before and after/common.html -foo -<===> simple, spaces before and after/restricted.html -foo -<===> simple, spaces before and after/basic.html -foo -<===> simple, spaces before and after/relaxed.html -foo - -<===> preceding colon/fragment.html -foo -<===> preceding colon/common.html -foo -<===> preceding colon/restricted.html -foo -<===> preceding colon/basic.html -foo -<===> preceding colon/relaxed.html -foo - -<===> UTF-8 encoding/fragment.html -foo -<===> UTF-8 encoding/common.html -foo -<===> UTF-8 encoding/restricted.html -foo -<===> UTF-8 encoding/basic.html -foo -<===> UTF-8 encoding/relaxed.html -foo - -<===> long UTF-8 encoding/fragment.html -foo -<===> long UTF-8 encoding/common.html -foo -<===> long UTF-8 encoding/restricted.html -foo -<===> long UTF-8 encoding/basic.html -foo -<===> long UTF-8 encoding/relaxed.html -foo - -<===> long UTF-8 encoding without semicolons/fragment.html -foo -<===> long UTF-8 encoding without semicolons/common.html -foo -<===> long UTF-8 encoding without semicolons/restricted.html -foo -<===> long UTF-8 encoding without semicolons/basic.html -foo -<===> long UTF-8 encoding without semicolons/relaxed.html -foo - -<===> hex encoding/fragment.html -foo -<===> hex encoding/common.html -foo -<===> hex encoding/restricted.html -foo -<===> hex encoding/basic.html -foo -<===> hex encoding/relaxed.html -foo - -<===> long hex encoding/fragment.html -foo -<===> long hex encoding/common.html -foo -<===> long hex encoding/restricted.html -foo -<===> long hex encoding/basic.html -foo -<===> long hex encoding/relaxed.html -foo - -<===> hex encoding without semicolons/fragment.html -foo -<===> hex encoding without semicolons/common.html -foo -<===> hex encoding without semicolons/restricted.html -foo -<===> hex encoding without semicolons/basic.html -foo -<===> hex encoding without semicolons/relaxed.html -foo - -<===> null char/fragment.html - -<===> null char/common.html -<===> null char/restricted.html -<===> null char/basic.html -<===> null char/relaxed.html -<===> invalid URL char/fragment.html - -<===> invalid URL char/common.html - -<===> invalid URL char/restricted.html - -<===> invalid URL char/basic.html - -<===> invalid URL char/relaxed.html - - -<===> spaces and entities/fragment.html - -<===> spaces and entities/common.html - -<===> spaces and entities/restricted.html - -<===> spaces and entities/basic.html - -<===> spaces and entities/relaxed.html - - -<===> protocol whitespace/fragment.html - -<===> protocol whitespace/common.html - -<===> protocol whitespace/restricted.html - -<===> protocol whitespace/basic.html - -<===> protocol whitespace/relaxed.html - diff --git a/lib/sanitize/spec/html_sanitizer/protocol_javascript.hrx b/lib/sanitize/spec/html_sanitizer/protocol_javascript.hrx deleted file mode 100644 index fc4b86c50d29..000000000000 --- a/lib/sanitize/spec/html_sanitizer/protocol_javascript.hrx +++ /dev/null @@ -1,67 +0,0 @@ -<===> simple, no spaces/fragment.html -foo -<===> simple, no spaces/common.html -foo -<===> simple, spaces before/fragment.html -foo -<===> -# TODO: Maybe this should strip the a tag -<===> simple, spaces before/common.html -foo -<===> simple, spaces after/fragment.html -foo -<===> simple, spaces after/common.html -foo -<===> simple, spaces before and after/fragment.html -foo -<===> -# TODO: Maybe this should strip the a tag -<===> simple, spaces before and after/common.html -foo -<===> preceding colon/fragment.html -foo -<===> -# TODO: Maybe this should strip the a tag -<===> preceding colon/common.html -foo -<===> UTF-8 encoding/fragment.html -foo -<===> UTF-8 encoding/common.html -foo -<===> long UTF-8 encoding/fragment.html -foo -<===> long UTF-8 encoding/common.html -foo -<===> long UTF-8 encoding without semicolons/fragment.html -foo -<===> long UTF-8 encoding without semicolons/common.html -foo -<===> hex encoding/fragment.html -foo -<===> hex encoding/common.html -foo -<===> long hex encoding/fragment.html -foo -<===> long hex encoding/common.html -foo -<===> hex encoding without semicolons/fragment.html -foo -<===> hex encoding without semicolons/common.html -foo -<===> null char/fragment.html - -<===> -# TODO: Maybe this should strip the a tag -<===> null char/common.html - -<===> invalid URL char/fragment.html - -<===> -# TODO: Maybe this should strip the a tag -<===> invalid URL char/common.html - -<===> spaces and entities/fragment.html - -<===> spaces and entities/common.html - -<===> diff --git a/lib/sanitize/spec/html_sanitizer/url_spec.cr b/lib/sanitize/spec/html_sanitizer/url_spec.cr deleted file mode 100644 index 5e1aade7ae90..000000000000 --- a/lib/sanitize/spec/html_sanitizer/url_spec.cr +++ /dev/null @@ -1,8 +0,0 @@ -require "../support/hrx" -require "../../src/policy/html_sanitizer" - -describe "Sanitize::Policy::HTMLSanitizer" do - it "escapes URL attribute" do - Sanitize::Policy::HTMLSanitizer.common.process(%()).should eq %() - end -end diff --git a/lib/sanitize/spec/html_sanitizer/xss.hrx b/lib/sanitize/spec/html_sanitizer/xss.hrx deleted file mode 100644 index 4f2e238944c7..000000000000 --- a/lib/sanitize/spec/html_sanitizer/xss.hrx +++ /dev/null @@ -1,476 +0,0 @@ -<===> # Basic XSS -<===> fragment.html -test -<===> common.html -test -<===> - - -# Pending because libxml2 behaviour changed in 2.9.13 (https://gitlab.gnome.org/GNOME/libxml2/-/issues/339) -<===> pending:fragment.html -<<<>< -<===> common.html - -<===> - - -<===> fragment.html - -<===> -` -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html -
    -<===> - - -<===> fragment.html -
    -<===> common.html -
    -<===> - - -<===> fragment.html -
    -<===> common.html -
    -<===> - - -<===> fragment.html -
    -<===> common.html -
    -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html - -<===> - -<===> common.html - -<===> - - -<===> fragment.html - -<===> common.html - -<===> - - -<===> fragment.html -PT SRC="http://ha.ckers.org/xss.js"> -<===> common.html -PT SRC="http://ha.ckers.org/xss.js"> -<===> - - -<===> fragment.html - -<==> complex/text.html -Lorem ipsum dolor sit amet -<==> - - -# Pending because libxml2 behaviour changed in 2.9.13 (https://gitlab.gnome.org/GNOME/libxml2/-/issues/339) -<==> pending:html-special-chars/fragment.html -<script> -<==> pending:html-special-chars/text.html -<script> -<==> - - -<==> prune script/fragment.html - -<==> prune script/text.html -<==> - - -<==> prune style/fragment.html - -<==> prune script/text.html -<==> diff --git a/lib/sanitize/spec/text_policy_spec.cr b/lib/sanitize/spec/text_policy_spec.cr deleted file mode 100644 index 8b02a154f330..000000000000 --- a/lib/sanitize/spec/text_policy_spec.cr +++ /dev/null @@ -1,17 +0,0 @@ -require "./support/hrx" -require "../src/policy/text" -require "../src/processor" - -describe Sanitize::Policy::Text do - it "continues on tag" do - Sanitize::Policy::Text.new.transform_tag("foo", {} of String => String).should eq Sanitize::Policy::CONTINUE - end - - it "adds whitespace" do - Sanitize::Policy::Text.new.process("foo
    bar").should eq "foo bar" - end - - run_hrx_samples Path["./text_policy.hrx"], { - "text" => Sanitize::Policy::Text.new, - } -end diff --git a/lib/sanitize/spec/uri_sanitizer_spec.cr b/lib/sanitize/spec/uri_sanitizer_spec.cr deleted file mode 100644 index a3aa25b092ad..000000000000 --- a/lib/sanitize/spec/uri_sanitizer_spec.cr +++ /dev/null @@ -1,130 +0,0 @@ -require "../src/uri_sanitizer" -require "spec" -require "uri" - -private def assert_sanitize(source : String, expected : String? = source, sanitizer = Sanitize::URISanitizer.new, *, file = __FILE__, line = __LINE__) - if expected - expected = URI.parse(expected) - end - sanitizer.sanitize(URI.parse(source)).should eq(expected), file: file, line: line -end - -describe Sanitize::URISanitizer do - describe "#accepted_schemes" do - it "has default value" do - Sanitize::URISanitizer.new.accepted_schemes.should eq Set{"http", "https", "mailto", "tel"} - end - - it "accepts minimal schemes" do - assert_sanitize("http://example.com") - assert_sanitize("https://example.com") - assert_sanitize("mailto:mail@example.com") - assert_sanitize("tel:example.com") - end - - it "refutes unsafe schemes" do - assert_sanitize("javascript:alert();", nil) - assert_sanitize("ssh:git@github.com", nil) - end - - it "custom schemes" do - sanitizer = Sanitize::URISanitizer.new - sanitizer.accept_scheme "javascript" - assert_sanitize("javascript:alert();", sanitizer: sanitizer) - end - - it "can be disabled" do - sanitizer = Sanitize::URISanitizer.new - sanitizer.accepted_schemes = nil - assert_sanitize("javascript:alert();", sanitizer: sanitizer) - assert_sanitize("foo:bar", sanitizer: sanitizer) - end - end - - describe "#base_url" do - it "disabled by default" do - Sanitize::URISanitizer.new.base_url.should be_nil - assert_sanitize("foo") - end - - it "set to absolute URL" do - sanitizer = Sanitize::URISanitizer.new - sanitizer.base_url = URI.parse("https://example.com/base/") - - assert_sanitize("foo", "https://example.com/base/foo", sanitizer: sanitizer) - assert_sanitize("/foo", "https://example.com/foo", sanitizer: sanitizer) - end - - it "doesn't base fragment-only URLs" do - sanitizer = Sanitize::URISanitizer.new - sanitizer.base_url = URI.parse("https://example.com/base/") - - assert_sanitize("#foo", sanitizer: sanitizer) - assert_sanitize("#", sanitizer: sanitizer) - assert_sanitize("https:#", sanitizer: sanitizer) - assert_sanitize("?#foo", "https://example.com/base/?#foo", sanitizer: sanitizer) - assert_sanitize("/#", "https://example.com/#", sanitizer: sanitizer) - assert_sanitize("https://#", "https://#", sanitizer: sanitizer) - - sanitizer.resolve_fragment_urls = true - assert_sanitize("#foo", "https://example.com/base/#foo", sanitizer: sanitizer) - assert_sanitize("#", "https://example.com/base/#", sanitizer: sanitizer) - assert_sanitize("https:#", "https:#", sanitizer: sanitizer) - end - end - - describe "#accepted_hosts" do - it "disabled by default" do - Sanitize::URISanitizer.new.accepted_hosts.should be_nil - end - - it "restricts hosts" do - sanitizer = Sanitize::URISanitizer.new - sanitizer.accepted_hosts = Set{"foo.example.com"} - assert_sanitize("http://foo.example.com", sanitizer: sanitizer) - assert_sanitize("http://bar.example.com", nil, sanitizer: sanitizer) - assert_sanitize("http://example.com", nil, sanitizer: sanitizer) - assert_sanitize("http://foo.foo.example.com", nil, sanitizer: sanitizer) - assert_sanitize("foo", sanitizer: sanitizer) - end - - it "works with base_url" do - sanitizer = Sanitize::URISanitizer.new - sanitizer.accepted_hosts = Set{"foo.example.com"} - sanitizer.base_url = URI.parse("http://bar.example.com/") - assert_sanitize("foo", "http://bar.example.com/foo", sanitizer: sanitizer) - assert_sanitize("http://bar.example.com/foo", nil, sanitizer: sanitizer) - end - end - - describe "#rejected_hosts" do - it "disabled by default" do - Sanitize::URISanitizer.new.rejected_hosts.should be_a(Set(String)) - end - - it "restricts hosts" do - sanitizer = Sanitize::URISanitizer.new - sanitizer.rejected_hosts = Set{"bar.example.com"} - assert_sanitize("http://foo.example.com", sanitizer: sanitizer) - assert_sanitize("http://bar.example.com", nil, sanitizer: sanitizer) - assert_sanitize("http://example.com", sanitizer: sanitizer) - assert_sanitize("http://bar.bar.example.com", sanitizer: sanitizer) - assert_sanitize("foo", sanitizer: sanitizer) - end - - it "works with base_url" do - sanitizer = Sanitize::URISanitizer.new - sanitizer.rejected_hosts = Set{"foo.example.com"} - sanitizer.base_url = URI.parse("http://foo.example.com/") - assert_sanitize("foo", "http://foo.example.com/foo", sanitizer: sanitizer) - assert_sanitize("http://foo.example.com/foo", nil, sanitizer: sanitizer) - end - - it "overrides accepted_hosts" do - sanitizer = Sanitize::URISanitizer.new - sanitizer.rejected_hosts = Set{"foo.example.com"} - sanitizer.accepted_hosts = Set{"foo.example.com"} - assert_sanitize("http://foo.example.com/foo", nil, sanitizer: sanitizer) - end - end -end diff --git a/lib/sanitize/src/adapter/libxml2.cr b/lib/sanitize/src/adapter/libxml2.cr deleted file mode 100644 index 51d899454c66..000000000000 --- a/lib/sanitize/src/adapter/libxml2.cr +++ /dev/null @@ -1,137 +0,0 @@ -struct Sanitize::Adapter::LibXML2 - include Adapter - - def self.process(policy : Policy, html : String, fragment : Bool = false) - return "" if html.empty? - - node = parse(html, fragment) - process(policy, node, fragment) - end - - def self.process(policy : Policy, node : XML::Node, fragment : Bool = false) - build(fragment) do |builder| - process(policy, node, builder, fragment) - end - end - - def self.process(policy : Policy, node : XML::Node, builder : XML::Builder, fragment : Bool = false) - processor = Processor.new(policy, new(builder)) - visit(processor, node, fragment) - builder.end_document - builder.flush - end - - def self.parse(html : String, fragment : Bool) - if fragment - html = "#{html}" - end - - node = XML.parse_html(html, XML::HTMLParserOptions.default | XML::HTMLParserOptions::NOIMPLIED | XML::HTMLParserOptions::NODEFDTD) - end - - def self.build(fragment : Bool) - result = String.build do |io| - builder = XML::Builder.new(io) - - if fragment - builder.start_element("fragment") - end - - yield(builder) - end - - if fragment - result = "" if result == "\n" - result = result.lchop("").rchop("\n") - end - # strip trailing non-linebreak whitespace - if result.ends_with?("\n") - result - else - result.rstrip - end - end - - def self.visit(processor : Processor, node : XML::Node, fragment : Bool) - visitor = Visitor.new(processor, fragment) - visitor.visit(node) - end - - # :nodoc: - struct Visitor - @attributes = Hash(String, String).new - - def initialize(@processor : Processor, @fragment : Bool) - end - - # :nodoc: - def visit(node : XML::Node) - case node.type - when .html_document_node? - visit_children(node) - when .dtd_node? - # skip DTD - when .text_node? - visit_text(node) - when .element_node? - visit_element(node) - when .comment_node? - # skip comments - when .cdata_section_node? - # skip CDATA - else - raise "Not implemented for: #{node.type}:#{node.name}:#{node.content}" - end - end - - def visit_children(node) - node.children.each do |child| - visit(child) - end - end - - def visit_text(node) - @processor.process_text(node.content) - end - - def visit_element(node) - if @fragment && node.name.in?({"html", "body"}) - @attributes.clear - @processor.process_element(node.name, @attributes, Processor::CONTINUE) do - visit_children(node) - end - return - end - - @attributes.clear - node.attributes.each do |attribute| - @attributes[attribute.name] = attribute.content - end - - name = node.name - if namespace = node.namespace - name = "#{namespace}:#{name}" - end - - @processor.process_element(name, @attributes) do - visit_children(node) - end - end - end - - def initialize(@builder : XML::Builder) - end - - def start_tag(name : String, attributes : Hash(String, String)) : Nil - @builder.start_element(name) - @builder.attributes(attributes) - end - - def end_tag(name : String, attributes : Hash(String, String)) : Nil - @builder.end_element - end - - def write_text(text : String) : Nil - @builder.text(text) - end -end diff --git a/lib/sanitize/src/policy.cr b/lib/sanitize/src/policy.cr deleted file mode 100644 index d1ce31ccf63f..000000000000 --- a/lib/sanitize/src/policy.cr +++ /dev/null @@ -1,45 +0,0 @@ -# A policy defines the rules for transforming an HTML/XML tree. -# -# * `HTMLSanitizer` is a policy for HTML sanitization. -# * `Whitelist` is a whitelist-based transformer that's useful either for -# simple stripping applications or as a building block for more advanced -# sanitization policies. -# * `Text` is a policy that turns HTML into plain text. -abstract class Sanitize::Policy - # :nodoc: - alias CONTINUE = Processor::CONTINUE - # :nodoc: - alias STOP = Processor::STOP - - # Defines the string that is added when whitespace is needed when a block tag - # is stripped. - property block_whitespace = " " - - # Receives the content of a text node and returns the transformed content. - # - # If the return value is `nil`, the content is skipped. - abstract def transform_text(text : String) : String? - - # Receives the element name and attributes of an opening tag and returns the - # transformed element name (usually the same as the input name). - # - # *attributes* are transformed directly in place. - # - # Special return values: - # * `Processor::CONTINUE`: Tells the processor to strip the current tag but - # continue traversing its children. - # * `Processor::CONTINUE`: Tells the processor to skip the current tag and its - # children completely and move to the next sibling. - abstract def transform_tag(name : String, attributes : Hash(String, String)) : String | Processor::CONTINUE | Processor::STOP - - HTML_BLOCK_ELEMENTS = Set{ - "address", "article", "aside", "audio", "video", "blockquote", "br", - "canvas", "dd", "div", "dl", "fieldset", "figcaption", "figure", "footer", - "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", - "noscript", "ol", "output", "p", "pre", "section", "table", "tfoot", "ul", - } - - def block_tag?(name) - HTML_BLOCK_ELEMENTS.includes?(name) - end -end diff --git a/lib/sanitize/src/policy/html_sanitizer.cr b/lib/sanitize/src/policy/html_sanitizer.cr deleted file mode 100644 index dbcc71ce131a..000000000000 --- a/lib/sanitize/src/policy/html_sanitizer.cr +++ /dev/null @@ -1,350 +0,0 @@ -require "./whitelist" -require "../uri_sanitizer" - -# This policy serves as a good default configuration that should fit most -# typical use cases for HTML sanitization. -# -# ## Configurations -# It comes in three different configurations with different sets of supported -# HTML tags. -# -# They only differ in the default configuration of allowed tags and attributes. -# The transformation behaviour is otherwise the same. -# -# ### Common Configuration -# `.common`: Accepts most standard tags and thus allows using a good -# amount of HTML features (see `COMMON_SAFELIST`). -# -# This is the recommended default configuration and should work for typical use -# cases unless strong restrictions on allowed content is required. -# -# ``` -# sanitizer = Sanitize::Policy::HTMLSanitizer.common -# sanitizer.process(%(foo)) # => %(foo) -# sanitizer.process(%(

    foo

    )) # => %(

    foo

    ) -# sanitizer.process(%()) # => %() -# sanitizer.process(%(
    foobar
    )) # => %(
    foobar
    ) -# ``` -# -# NOTE: This configuration (nor any other) does not accept `<html>`, -# `<head>`, or # `<body>` tags by default. In order to use -# `#sanitized_document` they need to be added explicitly to `accepted_arguments`. -# -# ### Basic Configuration -# -# `.basic`: This set accepts some basic tags including paragraphs, headlines, -# lists, and images (see `BASIC_SAFELIST`). -# -# ``` -# sanitizer = Sanitize::Policy::HTMLSanitizer.basic -# sanitizer.process(%(foo)) # => %(foo) -# sanitizer.process(%(

    foo

    )) # => %(

    foo

    ) -# sanitizer.process(%()) # => %() -# sanitizer.process(%(
    foobar
    )) # => %(foo bar) -# ``` -# -# ### Inline Configuration -# -# `.inline`: Accepts only a limited set of inline tags (see `INLINE_SAFELIST`). -# -# ``` -# sanitizer = Sanitize::Policy::HTMLSanitizer.inline -# sanitizer.process(%(foo)) # => %(foo) -# sanitizer.process(%(

    foo

    )) # => %(foo) -# sanitizer.process(%()) # => %() -# sanitizer.process(%(
    foobar
    )) # => %(foo bar) -# ``` -# -# ## Attribute Transformations -# -# Attribute transformations are identical in all three configurations. But more -# advanced transforms won't apply if the respective attribute is not allowed in -# `accepted_tags`. -# So you can easily add additional elements and attributes to lower-tier sets -# and get the same attribute validation. For example: `.inline` doesn't include -# `<img>` tags, but when `img` is added to `accepted_attributes`, -# the policy validates img tags the same way as in `.common`. -# -# ### URL Sanitization -# -# This transformation applies to attributes that contain a URL (configurable -# through (`url_attributes`). -# -# * Makes sure the value is a valid URI (via `URI.parse`). If it does not parse, -# the attribute value is set to empty string. -# * Sanitizes the URI via `URISanitizer (configurable trough `uri_sanitizer`). -# If the sanitizer returns `nil`, the attribute value is set to empty string. -# -# The same `URISanitizer` is used for any URL attributes. -# -# ### Anchor Tags -# -# For `<a>` tags with a `href` attribute, there are two transforms: -# -# * `rel="nofollow"` is added (can be disabled with `add_rel_nofollow`). -# * `rel="noopener"` is added to links with `target` attribute (can be disabled -# with `add_rel_noopener`). -# -# Anchor tags the have neither a `href`, `name` or `id` attribute are stripped. -# -# NOTE: `name` and `id` attributes are not in any of the default sets of -# accepted attributes, so they can only be used when explicitly enabled. -# -# ### Image Tags -# -# `<img>` tags are stripped if they don't have a `src` attribute. -# -# ### Size Attributes -# -# If a tag has `width` or `height` attributes, the values are validated to be -# numerical or percent values. -# By default, these attributes are only accepted for <img> tags. -# -# ### Alignment Attribute -# -# The `align` attribute is validated against allowed values for this attribute: -# `center, left, right, justify, char`. -# If the value is invalid, the attribute is stripped. -# -# ### Classes -# -# `class` attributes are filtered to accept only classes described by -# `valid_classes`. String values need to match the class name exactly, regex -# values need to match the entire class name. -# -# `class` is accepted as a global attribute in the default configuration, but no -# values are allowed in `valid_classes`. -# -# All classes can be accepted by adding the match-all regular expression `/.*/` -# to `valid_classes`. -class Sanitize::Policy::HTMLSanitizer < Sanitize::Policy::Whitelist - # Add `rel="nofollow"` to every `<a>` tag with `href` attribute. - property add_rel_nofollow = true - - # Add `rel="noopener"` to every `<a>` tag with `href` and `target` attribute. - property add_rel_noopener = true - - # Configures the `URISanitizer` to use for sanitizing URL attributes. - property uri_sanitizer = URISanitizer.new - - # Configures which attributes are considered to contain URLs. If empty, URL - # sanitization is disabled. - # - # Default value: `Set{"src", "href", "action", "cite", "longdesc"}`. - property url_attributes : Set(String) = Set{"src", "href", "action", "cite", "longdesc"} - - # Configures which classes are valid for `class` attributes. - # - # String values need to match the class name exactly, regex - # values need to match the entire class name. - # - # Default value: empty - property valid_classes : Set(String | Regex) = Set(String | Regex).new - - def valid_classes=(classes) - valid_classes = classes.map(&.as(String | Regex)).to_set - end - - # Creates an instance which accepts a limited set of inline tags (see - # `INLINE_SAFELIST`). - def self.inline : HTMLSanitizer - new( - accepted_attributes: INLINE_SAFELIST.clone - ) - end - - # Creates an instance which accepts more basic tags including paragraphs, - # headlines, lists, and images (see `BASIC_SAFELIST`). - def self.basic : HTMLSanitizer - new( - accepted_attributes: BASIC_SAFELIST.clone - ) - end - - # Creates an instance which accepts even more standard tags and thus allows - # using a good amount of HTML features (see `COMMON_SAFELIST`). - # - # Unless you need tight restrictions on allowed content, this is the - # recommended default. - def self.common : HTMLSanitizer - new( - accepted_attributes: COMMON_SAFELIST.clone - ) - end - - # Removes anchor tag (`<a>` from the list of accepted tags). - # - # NOTE: This doesn't reject attributes with URL values for other tags. - def no_links - accepted_attributes.delete("a") - - self - end - - def accept_tag(tag : String, attributes : Set(String) = Set(String).new) - accepted_attributes[tag] = attributes - end - - def transform_attributes(tag : String, attributes : Hash(String, String)) : String | CONTINUE | STOP - transform_url_attributes(tag, attributes) - - tag_result = case tag - when "a" - transform_tag_a(attributes) - when "img" - transform_tag_img(attributes) - end - - if tag_result - return tag_result - end - - limit_numeric_or_percent(attributes, "width") - limit_numeric_or_percent(attributes, "height") - limit_enum(attributes, "align", ["center", "left", "right", "justify", "char"]) - - transform_classes(tag, attributes) - - tag - end - - def transform_tag_img(attributes) - unless attributes.has_key?("src") - return CONTINUE - end - end - - def transform_tag_a(attributes) - if href = attributes["href"]? - if add_rel_nofollow - append_attribute(attributes, "rel", "nofollow") - end - if add_rel_noopener && attributes.has_key?("target") - append_attribute(attributes, "rel", "noopener") - end - end - if !(((href = attributes["href"]?) && !href.empty?) || attributes.has_key?("id") || attributes.has_key?("tag")) - return CONTINUE - end - end - - def transform_url_attributes(tag, attributes) - all_ok = true - url_attributes.each do |key| - if value = attributes[key]? - all_ok &&= transform_url_attribute(tag, attributes, key, value) - end - end - all_ok - end - - def transform_url_attribute(tag, attributes, attribute, value) - begin - uri = URI.parse(value.strip) - rescue URI::Error - attributes[attribute] = "" - return false - end - - uri = transform_uri(tag, attributes, attribute, uri) - if uri.nil? || (uri.blank? || uri == "#") - attributes[attribute] = "" - return false - end - - attributes[attribute] = uri - true - end - - def transform_uri(tag, attributes, attribute, uri : URI) : String? - if uri_sanitizer = self.uri_sanitizer - uri = uri_sanitizer.sanitize(uri) - - return unless uri - end - - # Make sure special characters are properly encoded to avoid interpretation - # of tweaked relative paths as "javascript:" URI (for example) - if path = uri.path - uri.path = String.build do |io| - URI.encode(URI.decode(path), io) { |byte| URI.reserved?(byte) || URI.unreserved?(byte) } - end - end - - uri.to_s - end - - def transform_classes(tag, attributes) - attribute = attributes["class"]? - return unless attribute - - classes = attribute.split - classes = classes.select { |klass| valid_class?(tag, klass, valid_classes) } - if classes.empty? - attributes.delete("class") - else - attributes["class"] = classes.join(" ") - end - end - - private def limit_numeric_or_percent(attributes, attribute) - if value = attributes[attribute]? - value = value.strip - if value.ends_with?("%") - value = value.byte_slice(0, value.size - 1) - end - value.each_char do |char| - unless char.ascii_number? - attributes.delete(attribute) - break - end - end - end - end - - private def limit_enum(attributes, attribute, list) - if value = attributes[attribute]? - value = value.strip - if valid_with_list?(value, list) - attributes[attribute] = value - else - attributes.delete(attribute) - end - end - end - - def valid_class?(tag, klass, valid_classes) - valid_with_list?(klass, valid_classes) - end - - private def valid_with_list?(value, list) - list.any? { |validator| - case validator - when String - validator == value - when Regex - data = validator.match(value) - next unless data - data.byte_begin == 0 && data.byte_end == value.bytesize - end - } - end - - def append_attribute(attributes, attribute, value) - if curr_value = attributes[attribute]? - values = curr_value.split - if values.includes?(value) - return false - else - values << value - attributes[attribute] = values.join(" ") - end - else - attributes[attribute] = value - end - - true - end -end - -require "./html_sanitizer/safelist" diff --git a/lib/sanitize/src/policy/html_sanitizer/safelist.cr b/lib/sanitize/src/policy/html_sanitizer/safelist.cr deleted file mode 100644 index 2d5a7edc08cd..000000000000 --- a/lib/sanitize/src/policy/html_sanitizer/safelist.cr +++ /dev/null @@ -1,70 +0,0 @@ -class Sanitize::Policy::HTMLSanitizer < Sanitize::Policy::Whitelist - # Only limited elements for inline text markup. - INLINE_SAFELIST = { - "a" => Set{"href", "hreflang"}, - "abbr" => Set(String).new, - "acronym" => Set(String).new, - "b" => Set(String).new, - "code" => Set(String).new, - "em" => Set(String).new, - "i" => Set(String).new, - "strong" => Set(String).new, - "*" => Set{ - "dir", - "lang", - "title", - "class", - }, - } - - # Compatible with basic Markdown features. - BASIC_SAFELIST = INLINE_SAFELIST.merge({ - "blockquote" => Set{"cite"}, - "br" => Set(String).new, - "h1" => Set(String).new, - "h2" => Set(String).new, - "h3" => Set(String).new, - "h4" => Set(String).new, - "h5" => Set(String).new, - "h6" => Set(String).new, - "hr" => Set(String).new, - "img" => Set{"alt", "src", "longdesc", "width", "height", "align"}, - "li" => Set(String).new, - "ol" => Set{"start"}, - "p" => Set{"align"}, - "pre" => Set(String).new, - "ul" => Set(String).new, - }) - - # Accepts most standard tags and thus allows using a good amount of HTML features. - COMMON_SAFELIST = BASIC_SAFELIST.merge({ - "dd" => Set(String).new, - "del" => Set{"cite"}, - "details" => Set(String).new, - "dl" => Set(String).new, - "dt" => Set(String).new, - "div" => Set(String).new, - "ins" => Set{"cite"}, - "kbd" => Set(String).new, - "q" => Set{"cite"}, - "ruby" => Set(String).new, - "rp" => Set(String).new, - "rt" => Set(String).new, - "s" => Set(String).new, - "samp" => Set(String).new, - "strike" => Set(String).new, - "sub" => Set(String).new, - "summary" => Set(String).new, - "sup" => Set(String).new, - "table" => Set(String).new, - "time" => Set{"datetime"}, - "tbody" => Set(String).new, - "td" => Set(String).new, - "tfoot" => Set(String).new, - "th" => Set(String).new, - "thead" => Set(String).new, - "tr" => Set(String).new, - "tt" => Set(String).new, - "var" => Set(String).new, - }) -end diff --git a/lib/sanitize/src/policy/text.cr b/lib/sanitize/src/policy/text.cr deleted file mode 100644 index 82a2e6775de2..000000000000 --- a/lib/sanitize/src/policy/text.cr +++ /dev/null @@ -1,23 +0,0 @@ -require "../policy" - -# Reduces an HTML tree to the content of its text nodes. -# It renders a plain text result, similar to copying HTML content rendered by -# a browser to a text editor. -# HTML special characters are escaped. -# -# ``` -# policy = Sanitize::Policy::Text.new -# policy.process(%(foo bar!)) # => "foo bar!" -# policy.process(%(

    foo

    bar

    )) # => "foo bar" -# policy.block_whitespace = "\n" -# policy.process(%(

    foo

    bar

    )) # => "foo\nbar" -# ``` -class Sanitize::Policy::Text < Sanitize::Policy - def transform_text(text : String) : String? - text - end - - def transform_tag(name : String, attributes : Hash(String, String)) : String | CONTINUE | STOP - CONTINUE - end -end diff --git a/lib/sanitize/src/policy/whitelist.cr b/lib/sanitize/src/policy/whitelist.cr deleted file mode 100644 index 5f96a71dac9e..000000000000 --- a/lib/sanitize/src/policy/whitelist.cr +++ /dev/null @@ -1,57 +0,0 @@ -require "../policy" - -# This is a simple policy based on a tag and attribute whitelist. -# -# This policy accepts only `<div>` and `<p>` tags with optional `title` attributes: -# ``` -# policy = Sanitize::Policy::Whitelist.new({ -# "div" => Set{"title"}, -# "p" => Set{"title"}, -# }) -# ``` -# -# The special `*` key applies to *all* tag names and can be used to allow global -# attributes: -# -# This example is equivalent to the above. If more tag names were added, they -# would also accept `title` attributes. -# ``` -# policy = Sanitize::Policy::Whitelist.new({ -# "div" => Set(String).new, -# "p" => Set(String).new, -# "*" => Set{"title"}, -# }) -# ``` -# -# Attributes are always optional, so this policy won't enforce the presence of -# an attribute. -# -# If a tag's attribute list is empty, no attributes are allowed for this tag. -# -# Attribute values are not changed by this policy. -class Sanitize::Policy::Whitelist < Sanitize::Policy - # Mapping of accepted tag names and attributes. - property accepted_attributes : Hash(String, Set(String)) - - # Short cut to `accepted_attributes["*"]`. - getter global_attributes : Set(String) { accepted_attributes.fetch("*") { Set(String).new } } - - def initialize(@accepted_attributes : Hash(String, Set(String))) - end - - def transform_text(text : String) : String? - text - end - - def transform_tag(name : String, attributes : Hash(String, String)) : String | CONTINUE | STOP - acceptable_attributes = accepted_attributes.fetch(name) { return CONTINUE } - - attributes.reject! { |attr, _| !acceptable_attributes.includes?(attr) && !global_attributes.includes?(attr) } - - transform_attributes(name, attributes) - end - - def transform_attributes(name : String, attributes : Hash(String, String)) : String | CONTINUE | STOP - name - end -end diff --git a/lib/sanitize/src/processor.cr b/lib/sanitize/src/processor.cr deleted file mode 100644 index 6d4e4ac82766..000000000000 --- a/lib/sanitize/src/processor.cr +++ /dev/null @@ -1,110 +0,0 @@ -require "xml" -require "log" -require "./adapter/libxml2" - -module Sanitize - abstract class Policy - # Processes the HTML fragment *html* with this policy using the default - # adapter (`Adapter::LibXML2`). - def process(html : String | XML::Node) : String - Adapter::LibXML2.process(self, html, fragment: true) - end - - # Processes the HTML document *html* with this policy using the default - # adapter (`Adapter::LibXML2`). - def process_document(html : String | XML::Node) : String - Adapter::LibXML2.process(self, html, fragment: false) - end - end - - module Adapter - abstract def write_text(text : String) : Nil - abstract def start_tag(name : String, attributes : Hash(String, String)) : Nil - abstract def end_tag(name : String, attributes : Hash(String, String)) : Nil - end - - # A processor traverses the HTML/XML tree, applies transformations through the - # policy and passes the result to the adapter which then builds the result. - class Processor - Log = ::Log.for(self) - - # This module serves as a singleton constant that signals the processor to - # skip the current tag but continue to traverse its children. - module CONTINUE - extend self - end - - # This module serves as a singleton constant that signals the processor to - # skip the current tag and its children. - module STOP - extend self - end - - @last_text_ended_with_whitespace = true - @stripped_block_tag = false - - def initialize(@policy : Policy, @adapter : Adapter) - end - - def process_text(text : String) - text = @policy.transform_text(text) - - if @stripped_block_tag && !@last_text_ended_with_whitespace && !text.try(&.[0].whitespace?) - @adapter.write_text(@policy.block_whitespace) - end - - @stripped_block_tag = false - - if text - @adapter.write_text(text) - @last_text_ended_with_whitespace = text.[-1].whitespace? - else - @last_text_ended_with_whitespace = false - end - end - - def process_element(name : String, attributes : Hash(String, String), &) - process_element(name, attributes, @policy.transform_tag(name, attributes)) do - yield - end - end - - def process_element(orig_name : String, attributes : Hash(String, String), name, &) - case name - when STOP - Log.debug { "#{@policy.class} stopping at tag #{orig_name} #{attributes}" } - if @policy.block_tag?(orig_name) - @stripped_block_tag = true - end - return - when CONTINUE - Log.debug { "#{@policy.class} stripping tag #{orig_name} #{attributes}" } - if @policy.block_tag?(orig_name) - @stripped_block_tag = true - end - when String - @stripped_block_tag = false - @adapter.start_tag(name, attributes) - end - - yield - - case name - when CONTINUE - if @policy.block_tag?(orig_name) - @stripped_block_tag = true - end - when String - @stripped_block_tag = false - @adapter.end_tag(name, attributes) - end - end - - def reset - @last_text_ended_with_whitespace = true - @stripped_block_tag = false - end - end -end - -require "./adapter/libxml2" diff --git a/lib/sanitize/src/sanitize.cr b/lib/sanitize/src/sanitize.cr deleted file mode 100644 index a94e7c660323..000000000000 --- a/lib/sanitize/src/sanitize.cr +++ /dev/null @@ -1,5 +0,0 @@ -require "./policy/*" -require "./processor" - -module Sanitize -end diff --git a/lib/sanitize/src/uri_sanitizer.cr b/lib/sanitize/src/uri_sanitizer.cr deleted file mode 100644 index cfda46736b3b..000000000000 --- a/lib/sanitize/src/uri_sanitizer.cr +++ /dev/null @@ -1,107 +0,0 @@ -require "uri" - -# A `URISanitizer` is used to validate and transform a URI based on specified -# rules. -class Sanitize::URISanitizer - # Specifies a whitelist of URI schemes this sanitizer accepts. - # - # If empty, no schemes are accepted (i.e. only relative URIs are valid). - # If `nil`, all schemes are accepted (this setting is potentially dangerous). - # - # Relative URIs are not affected by this setting. - property accepted_schemes : Set(String)? - - # Specifies a whitelist of hosts this sanitizer accepts. - # - # If empty, no hosts are accepted (i.e. only relative URIs are valid). - # If `nil`, all hosts are accepted (default). - # - # The blacklist `rejected_hosts` has precedence over this whitelist. - property accepted_hosts : Set(String)? - - # Specifies a blacklist of hosts this sanitizer rejects. - # - # If empty, no hosts are rejected. - # - # This blacklist has precedence over the whitelist `accepted_hosts`. - property rejected_hosts : Set(String) = Set(String).new - - # Specifies a base URL all relative URLs are resolved against. - # - # If `nil`, relative URLs are not resolved. - property base_url : URI? - - # Configures whether fragment-only URIs are resolved on `base_url`. - # - # ``` - # sanitizer = Sanitize::URISanitizer.new - # sanitizer.base_url = URI.parse("https://example.com/base/") - # sanitizer.sanitize(URI.parse("#foo")) # => "#foo" - # - # sanitizer.resolve_fragment_urls = true - # sanitizer.sanitize(URI.parse("#foo")) # => "https://example.com/base/#foo" - # ``` - property resolve_fragment_urls = false - - def initialize(@accepted_schemes : Set(String)? = Set{"http", "https", "mailto", "tel"}) - end - - # Adds *scheme* to `accepted_schemes`. - def accept_scheme(scheme : String) - schemes = self.accepted_schemes ||= Set(String).new - schemes << scheme - end - - def sanitize(uri : URI) : URI? - unless accepts_scheme?(uri.scheme) - return nil - end - - unless accepts_host?(uri.host) - return nil - end - - uri = resolve_base_url(uri) - - uri - end - - def accepts_scheme?(scheme) - if scheme.nil? - return true - end - - if accepted_schemes = self.accepted_schemes - return accepted_schemes.includes?(scheme) - end - - true - end - - def accepts_host?(host) - if host.nil? - return true - end - - return false if rejected_hosts.includes?(host) - - if accepted_hosts = self.accepted_hosts - return false unless accepted_hosts.includes?(host) - end - - true - end - - def resolve_base_url(uri) - if base_url = self.base_url - unless uri.absolute? || (!resolve_fragment_urls && fragment_url?(uri)) - uri = base_url.resolve(uri) - end - end - uri - end - - private def fragment_url?(uri) - uri.path.empty? && uri.host.nil? && uri.query.nil? && !uri.fragment.nil? - end -end diff --git a/shard.lock b/shard.lock index d80148209315..e7f2ddc86d10 100644 --- a/shard.lock +++ b/shard.lock @@ -8,7 +8,3 @@ shards: git: https://github.com/i3oris/reply.git version: 0.3.1+git.commit.90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 - sanitize: - git: https://github.com/straight-shoota/sanitize.git - version: 0.1.0+git.commit.75c141b619c77956e88f557149566cd28876398b - diff --git a/shard.yml b/shard.yml index cbc960c0ee15..396d91bdffe2 100644 --- a/shard.yml +++ b/shard.yml @@ -2,7 +2,7 @@ name: crystal version: 1.13.0-dev authors: - - Crystal Core Team +- Crystal Core Team description: | The Crystal standard library and compiler. @@ -15,9 +15,6 @@ dependencies: reply: github: I3oris/reply commit: 90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 - sanitize: - github: straight-shoota/sanitize - commit: 75c141b619c77956e88f557149566cd28876398b license: Apache-2.0 diff --git a/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr b/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr index 65090c8185f7..d8d179a05d51 100644 --- a/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr +++ b/spec/compiler/crystal/tools/doc/doc_renderer_spec.cr @@ -374,33 +374,7 @@ describe Doc::MarkdDocRenderer do HTML end - describe "renders html with sanitization" do - it_renders nil, %(

    Foo

    ), %(

    Foo

    ) - it_renders nil, %(), %() - it_renders nil, %(

    example text

    ), %(

    example text

    ) - - it_renders nil, %(```crystal\n# \n```), - %(
    # <script>alert("hello world")</script>
    ) - end - - describe "still renders tables despite sanitization" do - table_mkdn = <<-HTML - - - - - - - - - - - - - -
    column 1column 2
    data 1data 2
    data 3data 4
    - HTML - - it_renders nil, table_mkdn, table_mkdn + describe "renders html" do + it_renders nil, %(

    Foo

    ), %(

    Foo

    ) end end diff --git a/src/compiler/crystal/tools/doc/markd_doc_renderer.cr b/src/compiler/crystal/tools/doc/markd_doc_renderer.cr index 2ea3348b2b36..f703d7ed787b 100644 --- a/src/compiler/crystal/tools/doc/markd_doc_renderer.cr +++ b/src/compiler/crystal/tools/doc/markd_doc_renderer.cr @@ -1,7 +1,4 @@ -require "sanitize" - class Crystal::Doc::MarkdDocRenderer < Markd::HTMLRenderer - SANITIZER = Sanitize::Policy::HTMLSanitizer.common @anchor_map = Hash(String, Int32).new(0) def initialize(@type : Crystal::Doc::Type, options) @@ -161,24 +158,4 @@ class Crystal::Doc::MarkdDocRenderer < Markd::HTMLRenderer type.lookup_macro(name, args_count) || type.program.lookup_macro(name, args_count) end - - def text(node : Markd::Node, entering : Bool) - output(sanitize(node)) - end - - def html_block(node : Markd::Node, entering : Bool) - newline - content = @options.safe? ? "" : sanitize(node) - literal(content) - newline - end - - def html_inline(node : Markd::Node, entering : Bool) - content = @options.safe? ? "" : sanitize(node) - literal(content) - end - - def sanitize(node : Markd::Node) : String - SANITIZER.process(node.text) - end end From b6c78b79ed61f50c70c5bf5e807e4bc3bbff4a58 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 19 May 2024 02:37:10 +0800 Subject: [PATCH 1135/1551] Remove `CRYSTAL_LIBRARY_RPATH` and delay-load helper (#14598) --- UPGRADING.md | 7 + spec/std/spec_helper.cr | 7 +- src/compiler/crystal/codegen/link.cr | 53 --- src/compiler/crystal/command.cr | 6 +- src/compiler/crystal/command/env.cr | 11 +- src/compiler/crystal/command/eval.cr | 2 +- src/compiler/crystal/command/spec.cr | 2 +- src/compiler/crystal/compiler.cr | 27 +- src/compiler/crystal/loader/msvc.cr | 2 +- src/compiler/crystal/macros/macros.cr | 2 +- src/compiler/crystal/program.cr | 5 - src/compiler/crystal/tools/doc/generator.cr | 2 +- src/crystal/main.cr | 13 - src/crystal/system/win32/delay_load.cr | 353 ------------------ .../x86_64-windows-msvc/c/stringapiset.cr | 2 +- 15 files changed, 24 insertions(+), 470 deletions(-) delete mode 100644 src/crystal/system/win32/delay_load.cr diff --git a/UPGRADING.md b/UPGRADING.md index ec30083c7e18..e2a3c1cebd11 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -12,6 +12,13 @@ We're only listing the most relevant changes here that could have a relevant imp The [changelog](./CHANGELOG.md) contains more information about all changes in a specific release. +## Crystal 1.13 + +* `CRYSTAL_LIBRARY_RPATH` and the `preview_win32_delay_load` feature flag have + been removed. Individual DLLs can be explicitly delay-loaded with the MSVC + toolchain by using `/DELAYLOAD` as a linker flag. Similarly RPATH can be added + with GCC or Clang toolchains by adding `-Wl,-rpath`. + ## Crystal 1.9 * The implementation of the comparison operator `#<=>` between `Big*` (`BigDecimal`, diff --git a/spec/std/spec_helper.cr b/spec/std/spec_helper.cr index 69c80968b117..8bb0a9e1a2f2 100644 --- a/spec/std/spec_helper.cr +++ b/spec/std/spec_helper.cr @@ -86,10 +86,9 @@ def compile_file(source_file, *, bin_name = "executable_file", flags = %w(), fil args = ["build"] + flags + ["-o", executable_file, source_file] output = IO::Memory.new status = Process.run(compiler, args, env: { - "CRYSTAL_PATH" => Crystal::PATH, - "CRYSTAL_LIBRARY_PATH" => Crystal::LIBRARY_PATH, - "CRYSTAL_LIBRARY_RPATH" => Crystal::LIBRARY_RPATH, - "CRYSTAL_CACHE_DIR" => Crystal::CACHE_DIR, + "CRYSTAL_PATH" => Crystal::PATH, + "CRYSTAL_LIBRARY_PATH" => Crystal::LIBRARY_PATH, + "CRYSTAL_CACHE_DIR" => Crystal::CACHE_DIR, }, output: output, error: output) unless status.success? diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index 245d3de24fcf..3601aa0fd870 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -114,59 +114,6 @@ module Crystal default_paths.join(Process::PATH_DELIMITER) end - def self.default_rpath : String - # do not call `CrystalPath.expand_paths`, as `$ORIGIN` inside this env - # variable is always expanded at run time - ENV.fetch("CRYSTAL_LIBRARY_RPATH", "") - end - - # Adds the compiler itself's RPATH to the environment for the duration of - # the block. `$ORIGIN` in the compiler's RPATH is expanded immediately, but - # `$ORIGIN`s in the existing environment variable are not expanded. For - # example, on Linux: - # - # - CRYSTAL_LIBRARY_RPATH of the compiler: `$ORIGIN/so` - # - Current $CRYSTAL_LIBRARY_RPATH: `/home/foo:$ORIGIN/mylibs` - # - Compiler's full path: `/opt/crystal` - # - Generated executable's Crystal::LIBRARY_RPATH: `/home/foo:$ORIGIN/mylibs:/opt/so` - # - # On Windows we additionally append the compiler's parent directory to the - # list, as if by appending `$ORIGIN` to the compiler's RPATH. This directory - # is effectively the first search entry on any Windows executable. Example: - # - # - CRYSTAL_LIBRARY_RPATH of the compiler: `$ORIGIN\dll` - # - Current %CRYSTAL_LIBRARY_RPATH%: `C:\bar;$ORIGIN\mylibs` - # - Compiler's full path: `C:\foo\crystal.exe` - # - Generated executable's Crystal::LIBRARY_RPATH: `C:\bar;$ORIGIN\mylibs;C:\foo\dll;C:\foo` - # - # Combining RPATHs multiple times has no effect; the `CRYSTAL_LIBRARY_RPATH` - # environment variable at compiler startup is used, not really the "current" - # one. This can happen when running a program that also uses macro `run`s. - def self.add_compiler_rpath(&) - executable_path = Process.executable_path - compiler_origin = File.dirname(executable_path) if executable_path - - current_rpaths = ORIGINAL_CRYSTAL_LIBRARY_RPATH.try &.split(Process::PATH_DELIMITER, remove_empty: true) - compiler_rpaths = Crystal::LIBRARY_RPATH.split(Process::PATH_DELIMITER, remove_empty: true) - CrystalPath.expand_paths(compiler_rpaths, compiler_origin) - - rpaths = compiler_rpaths - rpaths.concat(current_rpaths) if current_rpaths - {% if flag?(:win32) %} - rpaths << compiler_origin if compiler_origin - {% end %} - - old_env = ENV["CRYSTAL_LIBRARY_RPATH"]? - ENV["CRYSTAL_LIBRARY_RPATH"] = rpaths.join(Process::PATH_DELIMITER) - begin - yield - ensure - ENV["CRYSTAL_LIBRARY_RPATH"] = old_env - end - end - - private ORIGINAL_CRYSTAL_LIBRARY_RPATH = ENV["CRYSTAL_LIBRARY_RPATH"]? - class_getter paths : Array(String) do default_paths end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 7644f412bb8e..f8ece87e3d4b 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -349,7 +349,6 @@ class Crystal::Command hierarchy_exp : String?, cursor_location : String?, output_format : String, - combine_rpath : Bool, includes : Array(String), excludes : Array(String), verbose : Bool, @@ -357,7 +356,7 @@ class Crystal::Command tallies : Bool do def compile(output_filename = self.output_filename) compiler.emit_base_filename = emit_base_filename || output_filename.rchop(File.extname(output_filename)) - compiler.compile sources, output_filename, combine_rpath: combine_rpath + compiler.compile sources, output_filename end def top_level_semantic @@ -632,10 +631,9 @@ class Crystal::Command emit_base_filename = ::Path[sources.first.filename].stem end - combine_rpath = run && !compiler.no_codegen? @config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format.not_nil!, - combine_rpath, includes, excludes, verbose, check, tallies + includes, excludes, verbose, check, tallies end private def gather_sources(filenames) diff --git a/src/compiler/crystal/command/env.cr b/src/compiler/crystal/command/env.cr index cf450ec55f3d..c4605860bf36 100644 --- a/src/compiler/crystal/command/env.cr +++ b/src/compiler/crystal/command/env.cr @@ -18,12 +18,11 @@ class Crystal::Command end vars = { - "CRYSTAL_CACHE_DIR" => CacheDir.instance.dir, - "CRYSTAL_PATH" => CrystalPath.default_path, - "CRYSTAL_VERSION" => Config.version || "", - "CRYSTAL_LIBRARY_PATH" => CrystalLibraryPath.default_path, - "CRYSTAL_LIBRARY_RPATH" => CrystalLibraryPath.default_rpath, - "CRYSTAL_OPTS" => ENV.fetch("CRYSTAL_OPTS", ""), + "CRYSTAL_CACHE_DIR" => CacheDir.instance.dir, + "CRYSTAL_PATH" => CrystalPath.default_path, + "CRYSTAL_VERSION" => Config.version || "", + "CRYSTAL_LIBRARY_PATH" => CrystalLibraryPath.default_path, + "CRYSTAL_OPTS" => ENV.fetch("CRYSTAL_OPTS", ""), } if var_names.empty? diff --git a/src/compiler/crystal/command/eval.cr b/src/compiler/crystal/command/eval.cr index 5db37cb0cd27..507c4cbe9750 100644 --- a/src/compiler/crystal/command/eval.cr +++ b/src/compiler/crystal/command/eval.cr @@ -26,7 +26,7 @@ class Crystal::Command output_filename = Crystal.temp_executable "eval" - compiler.compile sources, output_filename, combine_rpath: true + compiler.compile sources, output_filename execute output_filename, program_args, compiler end end diff --git a/src/compiler/crystal/command/spec.cr b/src/compiler/crystal/command/spec.cr index ce34b2ac4155..fd285b653432 100644 --- a/src/compiler/crystal/command/spec.cr +++ b/src/compiler/crystal/command/spec.cr @@ -96,7 +96,7 @@ class Crystal::Command output_filename = Crystal.temp_executable "spec" ENV["CRYSTAL_SPEC_COMPILER_BIN"] ||= Process.executable_path - compiler.compile sources, output_filename, combine_rpath: true + compiler.compile sources, output_filename report_warnings execute output_filename, options, compiler, error_on_exit: warnings_fail_on_exit? end diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 9cff591f8400..12c19ef8c12a 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -198,21 +198,12 @@ module Crystal # Compiles the given *source*, with *output_filename* as the name # of the generated executable. # - # If *combine_rpath* is true, add the compiler itself's RPATH to the - # generated executable via `CrystalLibraryPath.add_compiler_rpath`. This is - # used by the `run` / `eval` / `spec` commands as well as the macro `run` - # (via `Crystal::Program#macro_compile`), and never during cross-compiling. - # # Raises `Crystal::CodeError` if there's an error in the # source code. # # Raises `InvalidByteSequenceError` if the source code is not # valid UTF-8. - def compile(source : Source | Array(Source), output_filename : String, *, combine_rpath : Bool = false) : Result - if combine_rpath - return CrystalLibraryPath.add_compiler_rpath { compile(source, output_filename, combine_rpath: false) } - end - + def compile(source : Source | Array(Source), output_filename : String) : Result source = [source] unless source.is_a?(Array) program = new_program(source) node = parse program, source @@ -443,22 +434,6 @@ module Crystal end link_args = search_result.remaining_args.concat(search_result.library_paths).map { |arg| Process.quote_windows(arg) } - - if program.has_flag?("preview_win32_delay_load") - # "LINK : warning LNK4199: /DELAYLOAD:foo.dll ignored; no imports found from foo.dll" - # it is harmless to skip this error because not all import libraries are always used, much - # less the individual DLLs they refer to - link_args << "/IGNORE:4199" - - dlls = Set(String).new - search_result.library_paths.each do |library_path| - Crystal::System::LibraryArchive.imported_dlls(library_path).each do |dll| - dlls << dll.downcase - end - end - dlls.delete "kernel32.dll" - dlls.each { |dll| link_args << "/DELAYLOAD:#{dll}" } - end end {% end %} diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index 60aca1aae333..05bf988c9218 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -172,7 +172,7 @@ class Crystal::Loader end private def open_library(path : String) - # TODO: respect Crystal::LIBRARY_RPATH (#13490), or `@[Link(dll:)]`'s search order + # TODO: respect `@[Link(dll:)]`'s search order LibC.LoadLibraryExW(System.to_wstr(path), nil, 0) end diff --git a/src/compiler/crystal/macros/macros.cr b/src/compiler/crystal/macros/macros.cr index 42ebb6004bb3..a5d5714f115b 100644 --- a/src/compiler/crystal/macros/macros.cr +++ b/src/compiler/crystal/macros/macros.cr @@ -93,7 +93,7 @@ class Crystal::Program return CompiledMacroRun.new(executable_path, elapsed_time, true) end - result = host_compiler.compile Compiler::Source.new(filename, source), executable_path, combine_rpath: true + result = host_compiler.compile Compiler::Source.new(filename, source), executable_path # Write the new files from which 'filename' depends into the cache dir # (here we store how to obtain these files, because a require might use diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index f5abde7e7bfa..b1cc99f0dfc6 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -311,11 +311,6 @@ module Crystal The value is defined by the environment variables `CRYSTAL_LIBRARY_PATH`. MD - define_crystal_string_constant "LIBRARY_RPATH", Crystal::CrystalLibraryPath.default_rpath, <<-MD - Colon-separated paths where the loader searches for dynamic libraries at runtime. - - The value is defined by the environment variables `CRYSTAL_LIBRARY_RPATH`. - MD define_crystal_string_constant "VERSION", Crystal::Config.version, <<-MD The version of the Crystal compiler. MD diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index d40aa8a1325e..635a6be65731 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -227,7 +227,7 @@ class Crystal::Doc::Generator {"BUILD_COMMIT", "BUILD_DATE", "CACHE_DIR", "DEFAULT_PATH", "DESCRIPTION", "PATH", "VERSION", "LLVM_VERSION", - "LIBRARY_PATH", "LIBRARY_RPATH", "HOST_TRIPLE", "TARGET_TRIPLE"}.each do |name| + "LIBRARY_PATH", "HOST_TRIPLE", "TARGET_TRIPLE"}.each do |name| return true if type == crystal_type.types[name]? end diff --git a/src/crystal/main.cr b/src/crystal/main.cr index 480571d06fb4..059a822c5ff4 100644 --- a/src/crystal/main.cr +++ b/src/crystal/main.cr @@ -1,17 +1,5 @@ require "process/executable_path" # Process::PATH_DELIMITER -module Crystal - {% unless Crystal.has_constant?("LIBRARY_RPATH") %} - LIBRARY_RPATH = {{ env("CRYSTAL_LIBRARY_RPATH") || "" }} - {% end %} -end - -{% if flag?(:unix) && !flag?(:darwin) %} - {% unless Crystal::LIBRARY_RPATH.empty? %} - # TODO: is there a better way to quote this? - @[Link(ldflags: {{ "'-Wl,-rpath,#{Crystal::LIBRARY_RPATH.id}'" }})] - {% end %} -{% end %} lib LibCrystalMain @[Raises] fun __crystal_main(argc : Int32, argv : UInt8**) @@ -143,7 +131,6 @@ end {% if flag?(:win32) %} require "./system/win32/wmain" - require "./system/win32/delay_load" {% end %} {% if flag?(:wasi) %} diff --git a/src/crystal/system/win32/delay_load.cr b/src/crystal/system/win32/delay_load.cr deleted file mode 100644 index 49ed467156cb..000000000000 --- a/src/crystal/system/win32/delay_load.cr +++ /dev/null @@ -1,353 +0,0 @@ -require "c/delayimp" -require "c/heapapi" - -lib LibC - $image_base = __ImageBase : IMAGE_DOS_HEADER -end - -private macro p_from_rva(rva) - pointerof(LibC.image_base).as(UInt8*) + {{ rva }} -end - -private macro print_error(format, *args) - {% if args.empty? %} - %str = {{ format }} - LibC.WriteFile(LibC.GetStdHandle(LibC::STD_ERROR_HANDLE), %str, %str.bytesize, out _, nil) - {% else %} - %buf = uninitialized LibC::CHAR[1024] - %args = uninitialized Void*[{{ args.size }}] - {% for arg, i in args %} - %args[{{ i }}] = ({{ arg }}).as(Void*) - {% end %} - %len = LibC.FormatMessageA(LibC::FORMAT_MESSAGE_FROM_STRING | LibC::FORMAT_MESSAGE_ARGUMENT_ARRAY, {{ format }}, 0, 0, %buf, %buf.size, %args) - LibC.WriteFile(LibC.GetStdHandle(LibC::STD_ERROR_HANDLE), %buf, %len, out _, nil) - {% end %} -end - -module Crystal::System::DelayLoad - @[Extern] - record InternalImgDelayDescr, - grAttrs : LibC::DWORD, - szName : LibC::LPSTR, - phmod : LibC::HMODULE*, - pIAT : LibC::IMAGE_THUNK_DATA*, - pINT : LibC::IMAGE_THUNK_DATA*, - pBoundIAT : LibC::IMAGE_THUNK_DATA*, - pUnloadIAT : LibC::IMAGE_THUNK_DATA*, - dwTimeStamp : LibC::DWORD - - @[AlwaysInline] - def self.pinh_from_image_base(hmod : LibC::HMODULE) - (hmod.as(UInt8*) + hmod.as(LibC::IMAGE_DOS_HEADER*).value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*) - end - - @[AlwaysInline] - def self.interlocked_exchange(atomic : LibC::HMODULE*, value : LibC::HMODULE) - Atomic::Ops.atomicrmw(:xchg, atomic, value, :sequentially_consistent, false) - end - - # the functions below work on null-terminated strings; they must not use any C - # runtime features nor the GC! bang methods may allocate memory - - # returns the length in character units of the null-terminated string *str* - private def self.strlen(str : LibC::WCHAR*) : Int32 - len = 0 - while str.value != 0 - len &+= 1 - str += 1 - end - len - end - - # assigns *src* to *dst*, and returns the end of the new string in *dst* - private def self.strcpy(dst : LibC::WCHAR*, src : LibC::WCHAR*) : LibC::WCHAR* - while src.value != 0 - dst.value = src.value - dst += 1 - src += 1 - end - dst - end - - # assigns the concatenation of *args* to the buffer at *buf* with the given - # *size*, possibly reallocating it, and returns the new buffer - private def self.strcat(buf : LibC::WCHAR*, size : Int32, *args : *T) : {LibC::WCHAR*, Int32} forall T - new_size = 1 - {% for i in 0...T.size %} - %len{i} = strlen(args[{{ i }}]) - new_size &+= %len{i} - {% end %} - if new_size > size - size = new_size - buf = LibC.HeapReAlloc(LibC.GetProcessHeap, 0, buf, size &* 2).as(LibC::WCHAR*) - end - - ptr = buf - {% for i in 0...T.size %} - ptr = strcpy(ptr, args[{{ i }}]) - {% end %} - ptr.value = 0 - - {buf, size} - end - - # if *str* starts with *prefix*, returns the substring with *prefix* removed, - # otherwise returns *str* unmodified - private def self.str_lchop(str : LibC::WCHAR*, prefix : LibC::WCHAR*) : LibC::WCHAR* - src = str - - while prefix.value != 0 - return src unless prefix.value == str.value - prefix += 1 - str += 1 - end - - str - end - - # given *str*, a normalized absolute path of *size* UTF-16 code units, returns - # its parent directory by replacing the last directory separator with a null - # character - private def self.dirname(str : LibC::WCHAR*, size : Int32) - ptr = str + size - 1 - - # C:\foo.exe -> C: - # C:\foo\bar.exe -> C:\foo - # C:\foo\bar\baz.exe -> C:\foo\bar - while ptr != str - if ptr.value === '\\' - ptr.value = 0 - return {str, (ptr - str).to_i32!} - end - ptr -= 1 - end - - {str, size} - end - - # effective returns `::File.dirname(::Process.executable_path).to_utf16` - private def self.get_origin! : {LibC::WCHAR*, Int32} - buf = LibC.HeapAlloc(LibC.GetProcessHeap, 0, LibC::MAX_PATH &* 2).as(LibC::WCHAR*) - len = LibC.GetModuleFileNameW(nil, buf, LibC::MAX_PATH) - return dirname(buf, len.to_i32!) unless WinError.value.error_insufficient_buffer? - - buf = LibC.HeapReAlloc(LibC.GetProcessHeap, 0, buf, 65534).as(LibC::WCHAR*) - len = LibC.GetModuleFileNameW(nil, buf, 32767) - return dirname(buf, len.to_i32!) unless WinError.value.error_insufficient_buffer? - - print_error("FATAL: Failed to get current executable path\n") - LibC.ExitProcess(1) - end - - # converts *utf8_str* to a UTF-16 string - private def self.to_utf16!(utf8_str : LibC::Char*) : LibC::WCHAR* - utf16_size = LibC.MultiByteToWideChar(LibC::CP_UTF8, 0, utf8_str, -1, nil, 0) - utf16_str = LibC.HeapAlloc(LibC.GetProcessHeap, 0, utf16_size &* 2).as(LibC::WCHAR*) - LibC.MultiByteToWideChar(LibC::CP_UTF8, 0, utf8_str, -1, utf16_str, utf16_size) - utf16_str - end - - # replaces all instances of "$ORIGIN" in *str* with the directory containing - # the running executable - # if "$ORIGIN" is not found, returns *str* unmodified without allocating - # memory - private def self.expand_origin!(str : LibC::WCHAR*) : LibC::WCHAR* - origin_prefix = UInt16.static_array(0x24, 0x4F, 0x52, 0x49, 0x47, 0x49, 0x4E, 0x00) # "$ORIGIN".to_utf16 - ptr = str - origin = Pointer(LibC::WCHAR).null - origin_size = 0 - output_size = 1 - - while ptr.value != 0 - new_ptr = str_lchop(ptr, origin_prefix.to_unsafe) - if new_ptr != ptr - origin, origin_size = get_origin! unless origin - output_size &+= origin_size - ptr = new_ptr - next - end - output_size &+= 1 - ptr += 1 - end - - return str unless origin - output = LibC.HeapAlloc(LibC.GetProcessHeap, 0, output_size &* 2).as(LibC::WCHAR*) - dst = output - ptr = str - - while ptr.value != 0 - new_ptr = str_lchop(ptr, origin_prefix.to_unsafe) - if new_ptr != ptr - dst = strcpy(dst, origin) - ptr = new_ptr - next - end - dst.value = ptr.value - dst += 1 - ptr += 1 - end - dst.value = 0 - - LibC.HeapFree(LibC.GetProcessHeap, 0, origin) - output - end - - # `dll` is an ASCII base name without directory separators, e.g. `WS2_32.dll` - def self.load_library(dll : LibC::Char*) : LibC::HMODULE - utf16_dll = to_utf16!(dll) - - {% begin %} - {% paths = Crystal::LIBRARY_RPATH.gsub(/\$\{ORIGIN\}/, "$ORIGIN").split(::Process::PATH_DELIMITER).reject(&.empty?) %} - {% unless paths.empty? %} - size = 0x40 - buf = LibC.HeapAlloc(LibC.GetProcessHeap, 0, size &* 2).as(LibC::WCHAR*) - - {% for path, i in paths %} - # TODO: can this `to_utf16` be done at compilation time? - root = to_utf16!({{ path.ends_with?("\\") ? path : path + "\\" }}.to_unsafe) - root_expanded = expand_origin!(root) - buf, size = strcat(buf, size, root_expanded, utf16_dll) - handle = LibC.LoadLibraryExW(buf, nil, LibC::LOAD_WITH_ALTERED_SEARCH_PATH) - LibC.HeapFree(LibC.GetProcessHeap, 0, root_expanded) if root_expanded != root - LibC.HeapFree(LibC.GetProcessHeap, 0, root) - - if handle - LibC.HeapFree(LibC.GetProcessHeap, 0, buf) - LibC.HeapFree(LibC.GetProcessHeap, 0, utf16_dll) - return handle - end - {% end %} - - LibC.HeapFree(LibC.GetProcessHeap, 0, buf) - {% end %} - {% end %} - - handle = LibC.LoadLibraryExW(utf16_dll, nil, 0) - LibC.HeapFree(LibC.GetProcessHeap, 0, utf16_dll) - handle - end -end - -# This is a port of the default delay-load helper function in the `DelayHlp.cpp` -# file that comes with Microsoft Visual C++, except that all user-defined hooks -# are omitted. It is called every time the program attempts to load a symbol -# from a DLL. For more details see: -# https://learn.microsoft.com/en-us/cpp/build/reference/understanding-the-helper-function -# -# It is available even when the `preview_dll` flag is absent, so that system -# DLLs such as `advapi32.dll` and shards can be delay-loaded in the usual mixed -# static/dynamic builds by passing the appropriate linker flags explicitly. -# -# The delay load helper cannot call functions from the library being loaded, as -# that leads to an infinite recursion. In particular, if `preview_dll` is in -# effect, `Crystal::System.print_error` will not work, because the C runtime -# library DLLs are also delay-loaded and `LibC.snprintf` is unavailable. If you -# want print debugging inside this function, use the `print_error` macro -# instead. Note that its format string is passed to `LibC.FormatMessageA`, which -# uses different conventions from `LibC.printf`. -# -# `kernel32.dll` is the only DLL guaranteed to be available. It cannot be -# delay-loaded and the Crystal compiler excludes it from the linker arguments. -# -# This function does _not_ work with the empty prelude yet! -fun __delayLoadHelper2(pidd : LibC::ImgDelayDescr*, ppfnIATEntry : LibC::FARPROC*) : LibC::FARPROC - # TODO: support protected delay load? (/GUARD:CF) - # DloadAcquireSectionWriteAccess - - # Set up some data we use for the hook procs but also useful for our own use - idd = Crystal::System::DelayLoad::InternalImgDelayDescr.new( - grAttrs: pidd.value.grAttrs, - szName: p_from_rva(pidd.value.rvaDLLName).as(LibC::LPSTR), - phmod: p_from_rva(pidd.value.rvaHmod).as(LibC::HMODULE*), - pIAT: p_from_rva(pidd.value.rvaIAT).as(LibC::IMAGE_THUNK_DATA*), - pINT: p_from_rva(pidd.value.rvaINT).as(LibC::IMAGE_THUNK_DATA*), - pBoundIAT: p_from_rva(pidd.value.rvaBoundIAT).as(LibC::IMAGE_THUNK_DATA*), - pUnloadIAT: p_from_rva(pidd.value.rvaUnloadIAT).as(LibC::IMAGE_THUNK_DATA*), - dwTimeStamp: pidd.value.dwTimeStamp, - ) - - dli = LibC::DelayLoadInfo.new( - cb: sizeof(LibC::DelayLoadInfo), - pidd: pidd, - ppfn: ppfnIATEntry, - szDll: idd.szName, - dlp: LibC::DelayLoadProc.new, - hmodCur: LibC::HMODULE.null, - pfnCur: LibC::FARPROC.null, - dwLastError: LibC::DWORD.zero, - ) - - if 0 == idd.grAttrs & LibC::DLAttrRva - # DloadReleaseSectionWriteAccess - print_error("FATAL: Delay load descriptor does not support RVAs\n") - LibC.ExitProcess(1) - end - - hmod = idd.phmod.value - - # Calculate the index for the IAT entry in the import address table - # N.B. The INT entries are ordered the same as the IAT entries so - # the calculation can be done on the IAT side. - iIAT = ppfnIATEntry.as(LibC::IMAGE_THUNK_DATA*) - idd.pIAT - iINT = iIAT - - pitd = idd.pINT + iINT - - import_by_name = (pitd.value.u1.ordinal & LibC::IMAGE_ORDINAL_FLAG) == 0 - dli.dlp.fImportByName = import_by_name ? 1 : 0 - - if import_by_name - image_import_by_name = p_from_rva(LibC::RVA.new!(pitd.value.u1.addressOfData)) - dli.dlp.union.szProcName = image_import_by_name + offsetof(LibC::IMAGE_IMPORT_BY_NAME, @name) - else - dli.dlp.union.dwOrdinal = LibC::DWORD.new!(pitd.value.u1.ordinal & 0xFFFF) - end - - # Check to see if we need to try to load the library. - if !hmod - unless hmod = Crystal::System::DelayLoad.load_library(dli.szDll) - # DloadReleaseSectionWriteAccess - print_error("FATAL: Cannot find the DLL named `%1`, exiting\n", dli.szDll) - LibC.ExitProcess(1) - end - - # Store the library handle. If it is already there, we infer - # that another thread got there first, and we need to do a - # FreeLibrary() to reduce the refcount - hmodT = Crystal::System::DelayLoad.interlocked_exchange(idd.phmod, hmod) - LibC.FreeLibrary(hmod) if hmodT == hmod - end - - # Go for the procedure now. - dli.hmodCur = hmod - if pidd.value.rvaBoundIAT != 0 && pidd.value.dwTimeStamp != 0 - # bound imports exist...check the timestamp from the target image - pinh = Crystal::System::DelayLoad.pinh_from_image_base(hmod) - - if pinh.value.signature == LibC::IMAGE_NT_SIGNATURE && - pinh.value.fileHeader.timeDateStamp == idd.dwTimeStamp && - hmod.address == pinh.value.optionalHeader.imageBase - # Everything is good to go, if we have a decent address - # in the bound IAT! - if pfnRet = LibC::FARPROC.new(idd.pBoundIAT[iIAT].u1.function) - ppfnIATEntry.value = pfnRet - # DloadReleaseSectionWriteAccess - return pfnRet - end - end - end - - unless pfnRet = LibC.GetProcAddress(hmod, dli.dlp.union.szProcName) - # DloadReleaseSectionWriteAccess - if import_by_name - print_error("FATAL: Cannot find the symbol named `%1` within `%2`, exiting\n", dli.dlp.union.szProcName, dli.szDll) - else - print_error("FATAL: Cannot find the symbol with the ordinal #%1!u! within `%2`, exiting\n", Pointer(Void).new(dli.dlp.union.dwOrdinal), dli.szDll) - end - LibC.ExitProcess(1) - end - - ppfnIATEntry.value = pfnRet - # DloadReleaseSectionWriteAccess - pfnRet -end diff --git a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr index 971e96fa9eb5..f60e80a59328 100644 --- a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr +++ b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr @@ -11,7 +11,7 @@ lib LibC lpDefaultChar : CHAR*, lpUsedDefaultChar : BOOL* ) : Int - # this is only for the delay-load helper, all other code should use + # this was for the now removed delay-load helper, all other code should use # `String#to_utf16` instead fun MultiByteToWideChar( codePage : UInt, dwFlags : DWORD, lpMultiByteStr : LPSTR, From 2ac4b3fb4325328d347344ae738bb38d4675b82e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 21 May 2024 03:10:30 +0800 Subject: [PATCH 1136/1551] Add macro methods for `Select` (#14600) --- spec/compiler/macro/macro_methods_spec.cr | 13 ++++++++++++ src/compiler/crystal/macros.cr | 26 +++++++++++++++++++++-- src/compiler/crystal/macros/methods.cr | 13 ++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 041d60cb6f35..493fd070744a 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2875,6 +2875,19 @@ module Crystal end end + describe Select do + it "executes whens" do + assert_macro %({{x.whens}}), "[when foo\n 1\n]", {x: Select.new([When.new("foo".call, 1.int32)])} + assert_macro %({{x.whens}}), "[when x = y\n 1\n, when bar\n]", {x: Select.new([When.new(Assign.new("x".var, "y".var), 1.int32), When.new("bar".call)])} + end + + it "executes else" do + assert_macro %({{x.else}}), "", {x: Select.new([When.new("foo".call)])} + assert_macro %({{x.else}}), "1", {x: Select.new([When.new("foo".call)], 1.int32)} + assert_macro %({{x.else}}), "nil", {x: Select.new([When.new("foo".call)], NilLiteral.new)} + end + end + describe "if methods" do if_node = If.new(1.int32, 2.int32, 3.int32) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index f504f4eb6ed2..d1db83a8366f 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1577,7 +1577,7 @@ module Crystal::Macros end end - # A `when` or `in` inside a `case`. + # A `when` or `in` inside a `case` or `select`. class When < ASTNode # Returns the conditions of this `when`. def conds : ArrayLiteral @@ -1612,8 +1612,30 @@ module Crystal::Macros end # A `select` expression. - # class Select < ASTNode + # + # Every expression `node` is equivalent to: + # + # ``` + # select + # {% for when_clause in node.whens %} + # {{ when_clause }} + # {% end %} + # {% else_clause = node.else %} + # {% unless else_clause.is_a?(Nop) %} + # else + # {{ else_clause }} + # {% end %} # end + # ``` + class Select < ASTNode + # Returns the `when`s of this `select`. + def whens : ArrayLiteral(When) + end + + # Returns the `else` of this `select`. + def else : ASTNode + end + end # Node that represents an implicit object in: # diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 61879dbe9d91..2c45f49701fd 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -2252,6 +2252,19 @@ module Crystal end end + class Select + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "whens" + interpret_check_args { ArrayLiteral.map whens, &.itself } + when "else" + interpret_check_args { self.else || Nop.new } + else + super + end + end + end + class When def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) case method From b563cbacb3ab1af88f4ba48c1877eb3823f3a69d Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 21 May 2024 19:00:41 +0200 Subject: [PATCH 1137/1551] Fix `BigRational#format` (#14525) --- spec/std/big/big_rational_spec.cr | 8 ++++++++ src/big/big_rational.cr | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr index ec8ed3d0621b..f2175e1273ae 100644 --- a/spec/std/big/big_rational_spec.cr +++ b/spec/std/big/big_rational_spec.cr @@ -478,6 +478,14 @@ describe BigRational do describe "#inspect" do it { 123.to_big_r.inspect.should eq("123") } end + + it "#format" do + br(100, 3).format.should eq("100/3") + br(1234567, 890123).format.should eq("1,234,567/890,123") + br(1234567, 890123).format(".", " ").should eq("1 234 567/890 123") + br(1234567, 890123).format(".", " ", group: 2).should eq("1 23 45 67/89 01 23") + br(1234567, 890123).format(",", ".", group: 4).should eq("123.4567/89.0123") + end end describe "BigRational Math" do diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index cb4731dc4e95..1c25adf9434d 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -360,6 +360,13 @@ struct BigRational < Number to_s io end + # :inherit: + def format(io : IO, separator = '.', delimiter = ',', decimal_places : Int? = nil, *, group : Int = 3, only_significant : Bool = false) : Nil + numerator.format(io, separator, delimiter, decimal_places, group: group, only_significant: only_significant) + io << '/' + denominator.format(io, separator, delimiter, decimal_places, group: group, only_significant: only_significant) + end + def clone self end From 881773922173d81512351da33a6613c1e60d3069 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 22 May 2024 12:42:49 +0200 Subject: [PATCH 1138/1551] Add `EventLoop#run(blocking)` and `EventLoop#interrupt` (#14568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/crystal/scheduler.cr | 2 +- src/crystal/system/event_loop.cr | 24 ++++++- src/crystal/system/unix/event_libevent.cr | 17 +++-- .../system/unix/event_loop_libevent.cr | 9 ++- src/crystal/system/unix/lib_event2.cr | 1 + src/crystal/system/wasi/event_loop.cr | 8 ++- src/crystal/system/win32/event_loop_iocp.cr | 62 +++++++++++++++---- src/io/overlapped.cr | 6 +- .../c/processthreadsapi.cr | 1 + src/lib_c/x86_64-windows-msvc/c/winnt.cr | 2 + 10 files changed, 103 insertions(+), 29 deletions(-) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index c86d04309b14..4796226ce8e9 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -149,7 +149,7 @@ class Crystal::Scheduler resume(runnable) unless runnable == @thread.current_fiber break else - @event_loop.run_once + @event_loop.run(blocking: true) end end end diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index 0f6351fbac24..b8697025d2fb 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -7,8 +7,28 @@ abstract class Crystal::EventLoop Crystal::Scheduler.event_loop end - # Runs the event loop. - abstract def run_once : Nil + # Runs the loop. + # + # Returns immediately if events are activable. Set `blocking` to false to + # return immediately if there are no activable events; set it to true to wait + # for activable events, which will block the current thread until then. + # + # Returns `true` on normal returns (e.g. has activated events, has pending + # events but blocking was false) and `false` when there are no registered + # events. + abstract def run(blocking : Bool) : Bool + + # Tells a blocking run loop to no longer wait for events to activate. It may + # for example enqueue a NOOP event with an immediate (or past) timeout. Having + # activated an event, the loop shall return, allowing the blocked thread to + # continue. + # + # Should be a NOOP when the loop isn't running or is running in a nonblocking + # mode. + # + # NOTE: we assume that multiple threads won't run the event loop at the same + # time in parallel, but this assumption may change in the future! + abstract def interrupt : Nil # Create a new resume event for a fiber. abstract def create_resume_event(fiber : Fiber) : Event diff --git a/src/crystal/system/unix/event_libevent.cr b/src/crystal/system/unix/event_libevent.cr index 852c9483809d..21d6765646d1 100644 --- a/src/crystal/system/unix/event_libevent.cr +++ b/src/crystal/system/unix/event_libevent.cr @@ -59,18 +59,23 @@ module Crystal::LibEvent Crystal::LibEvent::Event.new(event) end - def run_loop : Nil - LibEvent2.event_base_loop(@base, LibEvent2::EventLoopFlags::None) - end - - def run_once : Nil - LibEvent2.event_base_loop(@base, LibEvent2::EventLoopFlags::Once) + # NOTE: may return `true` even if no event has been triggered (e.g. + # nonblocking), but `false` means that nothing was processed. + def loop(once : Bool, nonblock : Bool) : Bool + flags = LibEvent2::EventLoopFlags::None + flags |= LibEvent2::EventLoopFlags::Once if once + flags |= LibEvent2::EventLoopFlags::NonBlock if nonblock + LibEvent2.event_base_loop(@base, flags) == 0 end def loop_break : Nil LibEvent2.event_base_loopbreak(@base) end + def loop_exit : Nil + LibEvent2.event_base_loopexit(@base, nil) + end + def new_dns_base(init = true) DnsBase.new LibEvent2.evdns_base_new(@base, init ? 1 : 0) end diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr index 06c0ea8b03f0..fe95ec0c8a3e 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/system/unix/event_loop_libevent.cr @@ -18,9 +18,12 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop end {% end %} - # Runs the event loop. - def run_once : Nil - event_base.run_once + def run(blocking : Bool) : Bool + event_base.loop(once: true, nonblock: !blocking) + end + + def interrupt : Nil + event_base.loop_exit end # Create a new resume event for a fiber. diff --git a/src/crystal/system/unix/lib_event2.cr b/src/crystal/system/unix/lib_event2.cr index 5bc8ff514818..2cd3e4635194 100644 --- a/src/crystal/system/unix/lib_event2.cr +++ b/src/crystal/system/unix/lib_event2.cr @@ -47,6 +47,7 @@ lib LibEvent2 fun event_base_dispatch(eb : EventBase) : Int fun event_base_loop(eb : EventBase, flags : EventLoopFlags) : Int fun event_base_loopbreak(eb : EventBase) : Int + fun event_base_loopexit(EventBase, LibC::Timeval*) : LibC::Int fun event_set_log_callback(callback : (Int, UInt8*) -> Nil) fun event_enable_debug_mode fun event_reinit(eb : EventBase) : Int diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr index 0f411d80709f..e1c2fc2166f1 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/system/wasi/event_loop.cr @@ -8,8 +8,12 @@ end # :nodoc: class Crystal::Wasi::EventLoop < Crystal::EventLoop # Runs the event loop. - def run_once : Nil - raise NotImplementedError.new("Crystal::Wasi::EventLoop.run_once") + def run(blocking : Bool) : Bool + raise NotImplementedError.new("Crystal::Wasi::EventLoop.run") + end + + def interrupt : Nil + raise NotImplementedError.new("Crystal::Wasi::EventLoop.interrupt") end # Create a new resume event for a fiber. diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 36ff4e58330b..ae89e85bd3ed 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -13,6 +13,10 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop # This is a list of resume and timeout events managed outside of IOCP. @queue = Deque(Crystal::Iocp::Event).new + @lock = Crystal::SpinLock.new + @interrupted = Atomic(Bool).new(false) + @blocked_thread = Atomic(Thread?).new(nil) + # Returns the base IO Completion Port getter iocp : LibC::HANDLE do create_completion_port(LibC::INVALID_HANDLE_VALUE, nil) @@ -37,33 +41,49 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop # Runs the event loop and enqueues the fiber for the next upcoming event or # completion. - def run_once : Nil + def run(blocking : Bool) : Bool # Pull the next upcoming event from the event queue. This determines the # timeout for waiting on the completion port. # OPTIMIZE: Implement @queue as a priority queue in order to avoid this # explicit search for the lowest value and dequeue more efficient. next_event = @queue.min_by?(&.wake_at) - unless next_event - Crystal::System.print_error "Warning: No runnables in scheduler. Exiting program.\n" - ::exit - end + # no registered events: nothing to wait for + return false unless next_event now = Time.monotonic if next_event.wake_at > now - wait_time = next_event.wake_at - now - # There is no event ready to wake. So we wait for completions with a - # timeout for the next event wake time. + # There is no event ready to wake. We wait for completions until the next + # event wake time, unless nonblocking or already interrupted (timeout + # immediately). + if blocking + @lock.sync do + if @interrupted.get(:acquire) + blocking = false + else + # memorize the blocked thread (so we can alert it) + @blocked_thread.set(Thread.current, :release) + end + end + end - timed_out = IO::Overlapped.wait_queued_completions(wait_time.total_milliseconds) do |fiber| + wait_time = blocking ? (next_event.wake_at - now).total_milliseconds : 0 + timed_out = IO::Overlapped.wait_queued_completions(wait_time, alertable: blocking) do |fiber| # This block may run multiple times. Every single fiber gets enqueued. fiber.enqueue end - # If the wait for completion timed out we've reached the wake time and - # continue with waking `next_event`. - return unless timed_out + @blocked_thread.set(nil, :release) + @interrupted.set(false, :release) + + # The wait for completion enqueued events. + return true unless timed_out + + # Wait for completion timed out but it may have been interrupted or we ask + # for immediate timeout (nonblocking), so we check for the next event + # readyness again: + return false if next_event.wake_at > Time.monotonic end # next_event gets activated because its wake time is passed, either from the @@ -81,7 +101,7 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop # This would avoid the scheduler needing to looking at runnable again just # to notice it's still empty. The lock involved there should typically be # uncontested though, so it's probably not a big deal. - return if fiber.dead? + return false if fiber.dead? # A timeout event needs special handling because it does not necessarily # means to resume the fiber directly, in case a different select branch @@ -92,6 +112,22 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop else fiber.enqueue end + + # We enqueued a fiber. + true + end + + def interrupt : Nil + thread = nil + + @lock.sync do + @interrupted.set(true) + thread = @blocked_thread.swap(nil, :acquire) + end + return unless thread + + # alert the thread to interrupt GetQueuedCompletionStatusEx + LibC.QueueUserAPC(->(ptr : LibC::ULONG_PTR) {}, thread, LibC::ULONG_PTR.new(0)) end def enqueue(event : Crystal::Iocp::Event) diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index bd5dfe7f27d1..e61494324f42 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -8,7 +8,7 @@ module IO::Overlapped property fiber : Fiber? end - def self.wait_queued_completions(timeout, &) + def self.wait_queued_completions(timeout, alertable = false, &) overlapped_entries = uninitialized LibC::OVERLAPPED_ENTRY[1] if timeout > UInt64::MAX @@ -16,11 +16,13 @@ module IO::Overlapped else timeout = timeout.to_u64 end - result = LibC.GetQueuedCompletionStatusEx(Crystal::EventLoop.current.iocp, overlapped_entries, overlapped_entries.size, out removed, timeout, false) + result = LibC.GetQueuedCompletionStatusEx(Crystal::EventLoop.current.iocp, overlapped_entries, overlapped_entries.size, out removed, timeout, alertable) if result == 0 error = WinError.value if timeout && error.wait_timeout? return true + elsif alertable && error.value == LibC::WAIT_IO_COMPLETION + return true else raise IO::Error.from_os_error("GetQueuedCompletionStatusEx", error) end diff --git a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr index efa684075162..d1e13eced324 100644 --- a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr @@ -57,6 +57,7 @@ lib LibC fun GetProcessTimes(hProcess : HANDLE, lpCreationTime : FILETIME*, lpExitTime : FILETIME*, lpKernelTime : FILETIME*, lpUserTime : FILETIME*) : BOOL fun SwitchToThread : BOOL + fun QueueUserAPC(pfnAPC : PAPCFUNC, hThread : HANDLE, dwData : ULONG_PTR) : DWORD PROCESS_QUERY_INFORMATION = 0x0400 end diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index 0c6a0db3c986..e1f133dcae48 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -14,6 +14,8 @@ lib LibC alias HANDLE = Void* alias HMODULE = Void* + alias PAPCFUNC = ULONG_PTR -> + INVALID_FILE_ATTRIBUTES = DWORD.new!(-1) FILE_ATTRIBUTE_DIRECTORY = 0x10 FILE_ATTRIBUTE_HIDDEN = 0x2 From eb2e70f5f450d9388490f673b50ac3936ed03bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 24 May 2024 12:37:02 +0200 Subject: [PATCH 1139/1551] Refactor win32 `System::FileDescriptor#unbuffered_{read,write}` (#14607) Co-authored-by: Sijawusz Pur Rahnama --- src/crystal/system/win32/file_descriptor.cr | 53 +++++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 63c79af04d87..53b3c6eb3d1b 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -24,17 +24,7 @@ module Crystal::System::FileDescriptor if ConsoleUtils.console?(handle) ConsoleUtils.read(handle, slice) elsif system_blocking? - if LibC.ReadFile(handle, slice, slice.size, out bytes_read, nil) == 0 - case error = WinError.value - when .error_access_denied? - raise IO::Error.new "File not open for reading", target: self - when .error_broken_pipe? - return 0_i32 - else - raise IO::Error.from_os_error("Error reading file", error, target: self) - end - end - bytes_read.to_i32 + read_blocking(handle, slice) else overlapped_operation(handle, "ReadFile", read_timeout) do |overlapped| ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped) @@ -43,20 +33,26 @@ module Crystal::System::FileDescriptor end end + private def read_blocking(handle, slice) + ret = LibC.ReadFile(handle, slice, slice.size, out bytes_read, nil) + if ret.zero? + case error = WinError.value + when .error_access_denied? + raise IO::Error.new "File not open for reading", target: self + when .error_broken_pipe? + return 0_i32 + else + raise IO::Error.from_os_error("Error reading file", error, target: self) + end + end + bytes_read.to_i32 + end + private def unbuffered_write(slice : Bytes) : Nil handle = windows_handle until slice.empty? if system_blocking? - if LibC.WriteFile(handle, slice, slice.size, out bytes_written, nil) == 0 - case error = WinError.value - when .error_access_denied? - raise IO::Error.new "File not open for writing", target: self - when .error_broken_pipe? - return 0_u32 - else - raise IO::Error.from_os_error("Error writing file", error, target: self) - end - end + bytes_written = write_blocking(handle, slice) else bytes_written = overlapped_operation(handle, "WriteFile", write_timeout, writing: true) do |overlapped| ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped) @@ -68,6 +64,21 @@ module Crystal::System::FileDescriptor end end + private def write_blocking(handle, slice) + ret = LibC.WriteFile(handle, slice, slice.size, out bytes_written, nil) + if ret.zero? + case error = WinError.value + when .error_access_denied? + raise IO::Error.new "File not open for writing", target: self + when .error_broken_pipe? + return 0_u32 + else + raise IO::Error.from_os_error("Error writing file", error, target: self) + end + end + bytes_written + end + private def system_blocking? @system_blocking end From e043a25cec92c4a15f958168e8483ad7975d42f3 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 24 May 2024 15:02:31 +0200 Subject: [PATCH 1140/1551] Rename `Crystal::System.print_error(fmt, *args, &)` to `printf` (#14617) --- spec/std/crystal/system_spec.cr | 24 ++++++++++++------------ src/crystal/system/print_error.cr | 25 +++++++++++++------------ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/spec/std/crystal/system_spec.cr b/spec/std/crystal/system_spec.cr index 8e710f860a53..29e24438ed28 100644 --- a/spec/std/crystal/system_spec.cr +++ b/spec/std/crystal/system_spec.cr @@ -1,48 +1,48 @@ require "../spec_helper" -private def print_error_to_s(format, *args) +private def printf_to_s(format, *args) io = IO::Memory.new - Crystal::System.print_error(format, *args) do |bytes| + Crystal::System.printf(format, *args) do |bytes| io.write_string(bytes) end io.to_s end describe "Crystal::System" do - describe ".print_error" do + describe ".printf" do it "works" do - print_error_to_s("abcde").should eq("abcde") + printf_to_s("abcde").should eq("abcde") end it "supports %d" do - print_error_to_s("%d,%d,%d,%d,%d", 0, 1234, Int32::MAX, Int32::MIN, UInt64::MAX).should eq("0,1234,2147483647,-2147483648,-1") + printf_to_s("%d,%d,%d,%d,%d", 0, 1234, Int32::MAX, Int32::MIN, UInt64::MAX).should eq("0,1234,2147483647,-2147483648,-1") end it "supports %u" do - print_error_to_s("%u,%u,%u,%u,%u", 0, 1234, UInt32::MAX, Int32::MIN, UInt64::MAX).should eq("0,1234,4294967295,2147483648,4294967295") + printf_to_s("%u,%u,%u,%u,%u", 0, 1234, UInt32::MAX, Int32::MIN, UInt64::MAX).should eq("0,1234,4294967295,2147483648,4294967295") end it "supports %x" do - print_error_to_s("%x,%x,%x,%x,%x", 0, 0x1234, UInt32::MAX, Int32::MIN, UInt64::MAX).should eq("0,1234,ffffffff,80000000,ffffffff") + printf_to_s("%x,%x,%x,%x,%x", 0, 0x1234, UInt32::MAX, Int32::MIN, UInt64::MAX).should eq("0,1234,ffffffff,80000000,ffffffff") end # TODO: investigate why this prints `(???)` pending_interpreted "supports %p" do - print_error_to_s("%p,%p,%p", Pointer(Void).new(0x0), Pointer(Void).new(0x1234), Pointer(Void).new(UInt64::MAX)).should eq("0x0,0x1234,0xffffffffffffffff") + printf_to_s("%p,%p,%p", Pointer(Void).new(0x0), Pointer(Void).new(0x1234), Pointer(Void).new(UInt64::MAX)).should eq("0x0,0x1234,0xffffffffffffffff") end it "supports %s" do - print_error_to_s("%s,%s,%s", "abc\0def", "ghi".to_unsafe, Pointer(UInt8).null).should eq("abc\0def,ghi,(null)") + printf_to_s("%s,%s,%s", "abc\0def", "ghi".to_unsafe, Pointer(UInt8).null).should eq("abc\0def,ghi,(null)") end # BUG: missing downcast_distinct from Tuple(Int64 | UInt64, Int64 | UInt64, Int64 | UInt64, Int64 | UInt64) to Tuple(Int64, Int64, Int64, Int64) pending_interpreted "supports %l width" do values = {LibC::Long::MIN, LibC::Long::MAX, LibC::LongLong::MIN, LibC::LongLong::MAX} - print_error_to_s("%ld,%ld,%lld,%lld", *values).should eq(values.join(',')) + printf_to_s("%ld,%ld,%lld,%lld", *values).should eq(values.join(',')) values = {LibC::ULong::MIN, LibC::ULong::MAX, LibC::ULongLong::MIN, LibC::ULongLong::MAX} - print_error_to_s("%lu,%lu,%llu,%llu", *values).should eq(values.join(',')) - print_error_to_s("%lx,%lx,%llx,%llx", *values).should eq(values.join(',', &.to_s(16))) + printf_to_s("%lu,%lu,%llu,%llu", *values).should eq(values.join(',')) + printf_to_s("%lx,%lx,%llx,%llx", *values).should eq(values.join(',', &.to_s(16))) end end end diff --git a/src/crystal/system/print_error.cr b/src/crystal/system/print_error.cr index 390eea492097..f58bef1c4ff6 100644 --- a/src/crystal/system/print_error.cr +++ b/src/crystal/system/print_error.cr @@ -3,16 +3,18 @@ module Crystal::System # This is useful for error messages from components that are required for # IO to work (fibers, scheduler, event_loop). def self.print_error(message, *args) - print_error(message, *args) do |bytes| - {% if flag?(:unix) || flag?(:wasm32) %} - LibC.write 2, bytes, bytes.size - {% elsif flag?(:win32) %} - LibC.WriteFile(LibC.GetStdHandle(LibC::STD_ERROR_HANDLE), bytes, bytes.size, out _, nil) - {% end %} - end + printf(message, *args) { |bytes| print_error(bytes) } + end + + def self.print_error(bytes : Bytes) : Nil + {% if flag?(:unix) || flag?(:wasm32) %} + LibC.write 2, bytes, bytes.size + {% elsif flag?(:win32) %} + LibC.WriteFile(LibC.GetStdHandle(LibC::STD_ERROR_HANDLE), bytes, bytes.size, out _, nil) + {% end %} end - # Minimal drop-in replacement for a C `printf` function. Yields successive + # Minimal drop-in replacement for C `printf` function. Yields successive # non-empty `Bytes` to the block, which should do the actual printing. # # *format* only supports the `%(l|ll)?[dpsux]` format specifiers; more should @@ -23,9 +25,9 @@ module Crystal::System # corrupted heap, its implementation should be as low-level as possible, # avoiding memory allocations. # - # NOTE: C's `printf` is incompatible with Crystal's `sprintf`, because the - # latter does not support argument width specifiers nor `%p`. - def self.print_error(format, *args, &) + # NOTE: Crystal's `printf` only supports a subset of C's `printf` format specifiers. + # NOTE: MSVC uses `%X` rather than `0x%x`, we follow the latter on all platforms. + def self.printf(format, *args, &) format = to_string_slice(format) format_len = format.size ptr = format.to_unsafe @@ -73,7 +75,6 @@ module Crystal::System end when 'p' read_arg(Pointer(Void)) do |arg| - # NOTE: MSVC uses `%X` rather than `0x%x`, we follow the latter on all platforms yield "0x".to_slice to_int_slice(arg.address, 16, false, 2) { |bytes| yield bytes } end From b12f17fcd89319638dd772e544c4838575f748a2 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 24 May 2024 15:02:52 +0200 Subject: [PATCH 1141/1551] Add some missing `LLVM::Context` bindings (#14612) --- src/llvm/context.cr | 26 +++++++++++++++++++++----- src/llvm/lib_llvm/core.cr | 4 ++++ src/llvm/type.cr | 6 +++--- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/llvm/context.cr b/src/llvm/context.cr index b04dd8c85b15..987e8f13ba6b 100644 --- a/src/llvm/context.cr +++ b/src/llvm/context.cr @@ -51,6 +51,10 @@ class LLVM::Context Type.new LibLLVM.int_type_in_context(self, bits) end + def half : Type + Type.new LibLLVM.half_type_in_context(self) + end + def float : Type Type.new LibLLVM.float_type_in_context(self) end @@ -59,19 +63,31 @@ class LLVM::Context Type.new LibLLVM.double_type_in_context(self) end - def pointer : Type + def x86_fp80 : Type + Type.new LibLLVM.x86_fp80_type_in_context(self) + end + + def fp128 : Type + Type.new LibLLVM.fp128_type_in_context(self) + end + + def ppc_fp128 : Type + Type.new LibLLVM.ppc_fp128_type_in_context(self) + end + + def pointer(address_space = 0) : Type {% if LibLLVM::IS_LT_150 %} {% raise "Opaque pointers are only supported on LLVM 15.0 or above" %} {% else %} - Type.new LibLLVM.pointer_type_in_context(self, 0) + Type.new LibLLVM.pointer_type_in_context(self, address_space) {% end %} end - def void_pointer : Type + def void_pointer(address_space = 0) : Type {% if LibLLVM::IS_LT_150 %} - int8.pointer + int8.pointer(address_space) {% else %} - pointer + pointer(address_space) {% end %} end diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr index c865baaa55a5..de6f04010cfa 100644 --- a/src/llvm/lib_llvm/core.cr +++ b/src/llvm/lib_llvm/core.cr @@ -58,8 +58,12 @@ lib LibLLVM fun int_type_in_context = LLVMIntTypeInContext(c : ContextRef, num_bits : UInt) : TypeRef fun get_int_type_width = LLVMGetIntTypeWidth(integer_ty : TypeRef) : UInt + fun half_type_in_context = LLVMHalfTypeInContext(c : ContextRef) : TypeRef fun float_type_in_context = LLVMFloatTypeInContext(c : ContextRef) : TypeRef fun double_type_in_context = LLVMDoubleTypeInContext(c : ContextRef) : TypeRef + fun x86_fp80_type_in_context = LLVMX86FP80TypeInContext(c : ContextRef) : TypeRef + fun fp128_type_in_context = LLVMFP128TypeInContext(c : ContextRef) : TypeRef + fun ppc_fp128_type_in_context = LLVMPPCFP128TypeInContext(c : ContextRef) : TypeRef fun function_type = LLVMFunctionType(return_type : TypeRef, param_types : TypeRef*, param_count : UInt, is_var_arg : Bool) : TypeRef fun is_function_var_arg = LLVMIsFunctionVarArg(function_ty : TypeRef) : Bool diff --git a/src/llvm/type.cr b/src/llvm/type.cr index 42d1a314c118..7d0582a9f736 100644 --- a/src/llvm/type.cr +++ b/src/llvm/type.cr @@ -50,11 +50,11 @@ struct LLVM::Type Value.new LibLLVM.get_undef(self) end - def pointer : LLVM::Type + def pointer(address_space = 0) : LLVM::Type {% if LibLLVM::IS_LT_150 %} - Type.new LibLLVM.pointer_type(self, 0) + Type.new LibLLVM.pointer_type(self, address_space) {% else %} - Type.new LibLLVM.pointer_type_in_context(LibLLVM.get_type_context(self), 0) + Type.new LibLLVM.pointer_type_in_context(LibLLVM.get_type_context(self), address_space) {% end %} end From f3be084adf80ea7bcca2fb73ac139afb67112501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 25 May 2024 11:07:37 +0200 Subject: [PATCH 1142/1551] Extract `#system_read` and `#system_write` for `FileDescriptor` and `Socket` (#14626) --- src/crystal/system/file_descriptor.cr | 4 ++++ src/crystal/system/socket.cr | 4 ++-- src/crystal/system/unix/file_descriptor.cr | 4 ++-- src/crystal/system/unix/socket.cr | 4 ++-- src/crystal/system/wasi/socket.cr | 4 ++-- src/crystal/system/win32/file_descriptor.cr | 22 +++++++++------------ src/crystal/system/win32/socket.cr | 4 ++-- src/io/evented.cr | 20 ++++++++----------- src/io/file_descriptor.cr | 10 ++++++++++ src/socket.cr | 10 ++++++++++ 10 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/crystal/system/file_descriptor.cr b/src/crystal/system/file_descriptor.cr index db50f1f3fd73..5497c3066803 100644 --- a/src/crystal/system/file_descriptor.cr +++ b/src/crystal/system/file_descriptor.cr @@ -13,6 +13,10 @@ module Crystal::System::FileDescriptor # For the duration of the block, enables raw mode if *enable* is true, enables # cooked mode otherwise. # private def system_raw(enable : Bool, & : ->) + + # private def system_read(slice : Bool) : Int32 + + # private def system_write(slice : Bool) : Int32 end {% if flag?(:wasi) %} diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr index 8e91e18dbd4b..592045f2fbce 100644 --- a/src/crystal/system/socket.cr +++ b/src/crystal/system/socket.cr @@ -71,9 +71,9 @@ module Crystal::System::Socket # def self.fcntl(fd, cmd, arg = 0) - # private def unbuffered_read(slice : Bytes) : Int32 + # private def system_read(slice : Bytes) : Int32 - # private def unbuffered_write(slice : Bytes) : Nil + # private def system_write(slice : Bytes) : Int32 # private def system_close diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 2a2f25951d6c..0501c1851522 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -17,7 +17,7 @@ module Crystal::System::FileDescriptor STDOUT_HANDLE = 1 STDERR_HANDLE = 2 - private def unbuffered_read(slice : Bytes) : Int32 + private def system_read(slice : Bytes) : Int32 evented_read(slice, "Error reading file") do LibC.read(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF @@ -27,7 +27,7 @@ module Crystal::System::FileDescriptor end end - private def unbuffered_write(slice : Bytes) : Nil + private def system_write(slice : Bytes) : Int32 evented_write(slice, "Error writing file") do |slice| LibC.write(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 4a66c4dd2b1d..0e7c79b120a1 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -250,13 +250,13 @@ module Crystal::System::Socket LibC.isatty(fd) == 1 end - private def unbuffered_read(slice : Bytes) : Int32 + private def system_read(slice : Bytes) : Int32 evented_read(slice, "Error reading socket") do LibC.recv(fd, slice, slice.size, 0).to_i32 end end - private def unbuffered_write(slice : Bytes) : Nil + private def system_write(slice : Bytes) : Int32 evented_write(slice, "Error writing to socket") do |slice| LibC.send(fd, slice, slice.size, 0) end diff --git a/src/crystal/system/wasi/socket.cr b/src/crystal/system/wasi/socket.cr index 239ab4a9fdbd..124c019520b8 100644 --- a/src/crystal/system/wasi/socket.cr +++ b/src/crystal/system/wasi/socket.cr @@ -161,13 +161,13 @@ module Crystal::System::Socket LibC.isatty(fd) == 1 end - private def unbuffered_read(slice : Bytes) : Int32 + private def system_read(slice : Bytes) : Int32 evented_read(slice, "Error reading socket") do LibC.recv(fd, slice, slice.size, 0).to_i32 end end - private def unbuffered_write(slice : Bytes) : Nil + private def system_write(slice : Bytes) : Int32 evented_write(slice, "Error writing to socket") do |slice| LibC.send(fd, slice, slice.size, 0) end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 53b3c6eb3d1b..97f1242f2ef8 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -19,7 +19,7 @@ module Crystal::System::FileDescriptor @system_blocking = true - private def unbuffered_read(slice : Bytes) : Int32 + private def system_read(slice : Bytes) : Int32 handle = windows_handle if ConsoleUtils.console?(handle) ConsoleUtils.read(handle, slice) @@ -48,19 +48,15 @@ module Crystal::System::FileDescriptor bytes_read.to_i32 end - private def unbuffered_write(slice : Bytes) : Nil + private def system_write(slice : Bytes) : Int32 handle = windows_handle - until slice.empty? - if system_blocking? - bytes_written = write_blocking(handle, slice) - else - bytes_written = overlapped_operation(handle, "WriteFile", write_timeout, writing: true) do |overlapped| - ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped) - {ret, byte_count} - end - end - - slice += bytes_written + if system_blocking? + write_blocking(handle, slice).to_i32 + else + overlapped_operation(handle, "WriteFile", write_timeout, writing: true) do |overlapped| + ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped) + {ret, byte_count} + end.to_i32 end end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index f567cffdcee2..ee3e0174a7ea 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -445,7 +445,7 @@ module Crystal::System::Socket LibC.GetConsoleMode(LibC::HANDLE.new(fd), out _) != 0 end - private def unbuffered_read(slice : Bytes) : Int32 + private def system_read(slice : Bytes) : Int32 wsabuf = wsa_buffer(slice) bytes_read = overlapped_read(fd, "WSARecv", connreset_is_error: false) do |overlapped| @@ -457,7 +457,7 @@ module Crystal::System::Socket bytes_read.to_i32 end - private def unbuffered_write(slice : Bytes) : Nil + private def system_write(slice : Bytes) : Int32 wsabuf = wsa_buffer(slice) bytes = overlapped_write(fd, "WSASend") do |overlapped| diff --git a/src/io/evented.cr b/src/io/evented.cr index ed2c18352ff0..0561fa5619f1 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -30,22 +30,18 @@ module IO::Evented resume_pending_readers end - def evented_write(slice : Bytes, errno_msg : String, &) : Nil - return if slice.empty? - + def evented_write(slice : Bytes, errno_msg : String, &) : Int32 begin loop do - # TODO: Investigate why the .to_i64 is needed as a workaround for #8230 - bytes_written = (yield slice).to_i64 + bytes_written = yield slice if bytes_written != -1 - slice += bytes_written - return if slice.size == 0 + return bytes_written.to_i32 + end + + if Errno.value == Errno::EAGAIN + wait_writable else - if Errno.value == Errno::EAGAIN - wait_writable - else - raise IO::Error.from_errno(errno_msg, target: self) - end + raise IO::Error.from_errno(errno_msg, target: self) end end ensure diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index cf5324a006d6..bdcc6cafde91 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -263,6 +263,16 @@ class IO::FileDescriptor < IO pp.text inspect end + private def unbuffered_read(slice : Bytes) : Int32 + system_read(slice) + end + + private def unbuffered_write(slice : Bytes) : Nil + until slice.empty? + slice += system_write(slice) + end + end + private def unbuffered_rewind : Nil self.pos = 0 end diff --git a/src/socket.cr b/src/socket.cr index eba19a939cba..6ec7a0551aca 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -424,6 +424,16 @@ class Socket < IO system_tty? end + private def unbuffered_read(slice : Bytes) : Int32 + system_read(slice) + end + + private def unbuffered_write(slice : Bytes) : Nil + until slice.empty? + slice += system_write(slice) + end + end + private def unbuffered_rewind : Nil raise Socket::Error.new("Can't rewind") end From 5ca7111f9d0b586cb6018c763e58ec0ee5b69f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 25 May 2024 11:08:02 +0200 Subject: [PATCH 1143/1551] Add `WaitGroup` to `docs_main.cr` (#14624) --- src/docs_main.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/docs_main.cr b/src/docs_main.cr index 661163677b7c..5769678ca131 100644 --- a/src/docs_main.cr +++ b/src/docs_main.cr @@ -58,4 +58,5 @@ require "./syscall" {% unless flag?(:win32) %} require "./system/*" {% end %} +require "./wait_group" require "./docs_pseudo_methods" From a30e3d96248a55de4524005837f944294dd14278 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sat, 25 May 2024 11:08:16 +0200 Subject: [PATCH 1144/1551] Add `Crystal::System::Time.ticks` (#14620) --- src/crystal/system/time.cr | 6 ++++++ src/crystal/system/unix/time.cr | 21 ++++++++++++++++----- src/crystal/system/win32/time.cr | 19 +++++++++---------- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/crystal/system/time.cr b/src/crystal/system/time.cr index 0e127c5f7879..c2579760ef79 100644 --- a/src/crystal/system/time.cr +++ b/src/crystal/system/time.cr @@ -3,8 +3,14 @@ module Crystal::System::Time # since `0001-01-01 00:00:00`. # def self.compute_utc_seconds_and_nanoseconds : {Int64, Int32} + # Returns the current time from the monotonic clock in `{seconds, + # nanoseconds}`. # def self.monotonic : {Int64, Int32} + # Returns the current time from the monotonic clock in nanoseconds. + # Doesn't raise nor allocates GC HEAP memory. + # def self.ticks : UInt64 + # Returns a list of paths where time zone data should be looked up. # def self.zone_sources : Enumerable(String) diff --git a/src/crystal/system/unix/time.cr b/src/crystal/system/unix/time.cr index f7963f32f83f..58ed5b60e7e1 100644 --- a/src/crystal/system/unix/time.cr +++ b/src/crystal/system/unix/time.cr @@ -16,17 +16,18 @@ require "c/time" {% end %} module Crystal::System::Time - UnixEpochInSeconds = 62135596800_i64 + UNIX_EPOCH_IN_SECONDS = 62135596800_i64 + NANOSECONDS_PER_SECOND = 1_000_000_000 def self.compute_utc_seconds_and_nanoseconds : {Int64, Int32} {% if LibC.has_method?("clock_gettime") %} ret = LibC.clock_gettime(LibC::CLOCK_REALTIME, out timespec) raise RuntimeError.from_errno("clock_gettime") unless ret == 0 - {timespec.tv_sec.to_i64 + UnixEpochInSeconds, timespec.tv_nsec.to_i} + {timespec.tv_sec.to_i64 + UNIX_EPOCH_IN_SECONDS, timespec.tv_nsec.to_i} {% else %} ret = LibC.gettimeofday(out timeval, nil) raise RuntimeError.from_errno("gettimeofday") unless ret == 0 - {timeval.tv_sec.to_i64 + UnixEpochInSeconds, timeval.tv_usec.to_i * 1_000} + {timeval.tv_sec.to_i64 + UNIX_EPOCH_IN_SECONDS, timeval.tv_usec.to_i * 1_000} {% end %} end @@ -34,8 +35,8 @@ module Crystal::System::Time {% if flag?(:darwin) %} info = mach_timebase_info total_nanoseconds = LibC.mach_absolute_time * info.numer // info.denom - seconds = total_nanoseconds // 1_000_000_000 - nanoseconds = total_nanoseconds.remainder(1_000_000_000) + seconds = total_nanoseconds // NANOSECONDS_PER_SECOND + nanoseconds = total_nanoseconds.remainder(NANOSECONDS_PER_SECOND) {seconds.to_i64, nanoseconds.to_i32} {% else %} if LibC.clock_gettime(LibC::CLOCK_MONOTONIC, out tp) == 1 @@ -45,6 +46,16 @@ module Crystal::System::Time {% end %} end + def self.ticks : UInt64 + {% if flag?(:darwin) %} + info = mach_timebase_info + LibC.mach_absolute_time &* info.numer // info.denom + {% else %} + LibC.clock_gettime(LibC::CLOCK_MONOTONIC, out tp) + tp.tv_sec.to_u64! &* NANOSECONDS_PER_SECOND &+ tp.tv_nsec.to_u64! + {% end %} + end + def self.to_timespec(time : ::Time) t = uninitialized LibC::Timespec t.tv_sec = typeof(t.tv_sec).new(time.to_unix) diff --git a/src/crystal/system/win32/time.cr b/src/crystal/system/win32/time.cr index 358cc79a4926..8ab47c8aff7c 100644 --- a/src/crystal/system/win32/time.cr +++ b/src/crystal/system/win32/time.cr @@ -53,21 +53,20 @@ module Crystal::System::Time ((filetime.dwHighDateTime.to_u64 << 32) | filetime.dwLowDateTime.to_u64).to_f64 / FILETIME_TICKS_PER_SECOND.to_f64 end - @@performance_frequency : Int64 = begin - ret = LibC.QueryPerformanceFrequency(out frequency) - if ret == 0 - raise RuntimeError.from_winerror("QueryPerformanceFrequency") - end - + private class_getter performance_frequency : Int64 do + LibC.QueryPerformanceFrequency(out frequency) frequency end def self.monotonic : {Int64, Int32} - if LibC.QueryPerformanceCounter(out ticks) == 0 - raise RuntimeError.from_winerror("QueryPerformanceCounter") - end + LibC.QueryPerformanceCounter(out ticks) + frequency = performance_frequency + {ticks // frequency, (ticks.remainder(frequency) * NANOSECONDS_PER_SECOND / frequency).to_i32} + end - {ticks // @@performance_frequency, (ticks.remainder(@@performance_frequency) * NANOSECONDS_PER_SECOND / @@performance_frequency).to_i32} + def self.ticks : UInt64 + LibC.QueryPerformanceCounter(out ticks) + ticks.to_u64! &* (NANOSECONDS_PER_SECOND // performance_frequency) end def self.load_localtime : ::Time::Location? From fcd95b8e767d91947d9258fedc69f744aaffc133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 28 May 2024 17:13:56 +0200 Subject: [PATCH 1145/1551] Drop unused slice parameters of `#evented_*` methods (#14627) --- src/crystal/system/unix/file_descriptor.cr | 4 ++-- src/crystal/system/unix/socket.cr | 12 ++++++------ src/crystal/system/wasi/socket.cr | 8 ++++---- src/io/evented.cr | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 0501c1851522..b6e7de8b7a36 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -18,7 +18,7 @@ module Crystal::System::FileDescriptor STDERR_HANDLE = 2 private def system_read(slice : Bytes) : Int32 - evented_read(slice, "Error reading file") do + evented_read("Error reading file") do LibC.read(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF raise IO::Error.new "File not open for reading", target: self @@ -28,7 +28,7 @@ module Crystal::System::FileDescriptor end private def system_write(slice : Bytes) : Int32 - evented_write(slice, "Error writing file") do |slice| + evented_write("Error writing file") do LibC.write(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF raise IO::Error.new "File not open for writing", target: self diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 0e7c79b120a1..ad0c0a41d2c3 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -80,8 +80,8 @@ module Crystal::System::Socket end private def system_send(bytes : Bytes) : Int32 - evented_send(bytes, "Error sending datagram") do |slice| - LibC.send(fd, slice.to_unsafe.as(Void*), slice.size, 0) + evented_send("Error sending datagram") do + LibC.send(fd, bytes.to_unsafe.as(Void*), bytes.size, 0) end end @@ -101,8 +101,8 @@ module Crystal::System::Socket addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrStorage)) - bytes_read = evented_read(bytes, "Error receiving datagram") do |slice| - LibC.recvfrom(fd, slice, slice.size, 0, sockaddr, pointerof(addrlen)) + bytes_read = evented_read("Error receiving datagram") do + LibC.recvfrom(fd, bytes, bytes.size, 0, sockaddr, pointerof(addrlen)) end {bytes_read, ::Socket::Address.from(sockaddr, addrlen)} @@ -251,13 +251,13 @@ module Crystal::System::Socket end private def system_read(slice : Bytes) : Int32 - evented_read(slice, "Error reading socket") do + evented_read("Error reading socket") do LibC.recv(fd, slice, slice.size, 0).to_i32 end end private def system_write(slice : Bytes) : Int32 - evented_write(slice, "Error writing to socket") do |slice| + evented_write("Error writing to socket") do LibC.send(fd, slice, slice.size, 0) end end diff --git a/src/crystal/system/wasi/socket.cr b/src/crystal/system/wasi/socket.cr index 124c019520b8..4f79f9955dad 100644 --- a/src/crystal/system/wasi/socket.cr +++ b/src/crystal/system/wasi/socket.cr @@ -34,8 +34,8 @@ module Crystal::System::Socket end private def system_send(bytes : Bytes) : Int32 - evented_send(bytes, "Error sending datagram") do |slice| - LibC.send(fd, slice.to_unsafe.as(Void*), slice.size, 0) + evented_send("Error sending datagram") do + LibC.send(fd, bytes.to_unsafe.as(Void*), bytes.size, 0) end end @@ -162,13 +162,13 @@ module Crystal::System::Socket end private def system_read(slice : Bytes) : Int32 - evented_read(slice, "Error reading socket") do + evented_read("Error reading socket") do LibC.recv(fd, slice, slice.size, 0).to_i32 end end private def system_write(slice : Bytes) : Int32 - evented_write(slice, "Error writing to socket") do |slice| + evented_write("Error writing to socket") do LibC.send(fd, slice, slice.size, 0) end end diff --git a/src/io/evented.cr b/src/io/evented.cr index 0561fa5619f1..bd68d473d66a 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -12,9 +12,9 @@ module IO::Evented @read_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new @write_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new - def evented_read(slice : Bytes, errno_msg : String, &) : Int32 + def evented_read(errno_msg : String, &) : Int32 loop do - bytes_read = yield slice + bytes_read = yield if bytes_read != -1 # `to_i32` is acceptable because `Slice#size` is an Int32 return bytes_read.to_i32 @@ -30,10 +30,10 @@ module IO::Evented resume_pending_readers end - def evented_write(slice : Bytes, errno_msg : String, &) : Int32 + def evented_write(errno_msg : String, &) : Int32 begin loop do - bytes_written = yield slice + bytes_written = yield if bytes_written != -1 return bytes_written.to_i32 end @@ -49,8 +49,8 @@ module IO::Evented end end - def evented_send(slice : Bytes, errno_msg : String, &) : Int32 - bytes_written = yield slice + def evented_send(errno_msg : String, &) : Int32 + bytes_written = yield raise Socket::Error.from_errno(errno_msg) if bytes_written == -1 # `to_i32` is acceptable because `Slice#size` is an Int32 bytes_written.to_i32 From ca7aae574244e953a552b2b6d57287fc712ba053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20H=C3=B6rberg?= Date: Wed, 29 May 2024 12:28:36 +0200 Subject: [PATCH 1146/1551] Don't pass socket file descriptors to subprocesses on Unix (`SOCK_CLOEXEC`) (#14632) Co-authored-by: Julien Portalier --- spec/std/socket/socket_spec.cr | 7 +++++++ src/crystal/system/unix/socket.cr | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index 79c5d8b2c9c5..56fb07f41db5 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -159,4 +159,11 @@ describe Socket, tags: "network" do end end end + + {% unless flag?(:win32) %} + it "closes on exec by default" do + socket = Socket.new(Socket::Family::INET, Socket::Type::STREAM, Socket::Protocol::TCP) + socket.close_on_exec?.should be_true + end + {% end %} end diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index ad0c0a41d2c3..0a6ac995c0f1 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -9,6 +9,10 @@ module Crystal::System::Socket alias Handle = Int32 private def create_handle(family, type, protocol, blocking) : Handle + {% if LibC.has_constant?(:SOCK_CLOEXEC) %} + # Forces opened sockets to be closed on `exec(2)`. + type = type.to_i | LibC::SOCK_CLOEXEC + {% end %} fd = LibC.socket(family, type, protocol) raise ::Socket::Error.from_errno("Failed to create socket") if fd == -1 fd From db612c1a22df7813dcf4517ac523d240d23735bb Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 29 May 2024 13:52:13 +0200 Subject: [PATCH 1147/1551] Fix: Don't allocate in `Fiber.unsafe_each` and `Thread.unsafe_each` (#14635) --- src/crystal/system/thread.cr | 4 +++- src/fiber.cr | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index e6627538112d..d6a6e5653276 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -57,7 +57,9 @@ class Thread getter name : String? def self.unsafe_each(&) - threads.unsafe_each { |thread| yield thread } + # nothing to iterate when @@threads is nil + don't lazily allocate in a + # method called from a GC collection callback! + @@threads.try(&.unsafe_each { |thread| yield thread }) end # Creates and starts a new system thread. diff --git a/src/fiber.cr b/src/fiber.cr index 8d8784fe472f..0d471e5a96e4 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -73,7 +73,9 @@ class Fiber # :nodoc: def self.unsafe_each(&) - fibers.unsafe_each { |fiber| yield fiber } + # nothing to iterate when @@fibers is nil + don't lazily allocate in a + # method called from a GC collection callback! + @@fibers.try(&.unsafe_each { |fiber| yield fiber }) end # Creates a new `Fiber` instance. From ed703933c6d638d3499f08f6d73c9825fe1efa0e Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 29 May 2024 13:52:13 +0200 Subject: [PATCH 1148/1551] Fix: Don't allocate in `Fiber.unsafe_each` and `Thread.unsafe_each` (#14635) --- src/crystal/system/thread.cr | 4 +++- src/fiber.cr | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index b40a7dceb32b..e134edb3ec1f 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -54,7 +54,9 @@ class Thread getter name : String? def self.unsafe_each(&) - threads.unsafe_each { |thread| yield thread } + # nothing to iterate when @@threads is nil + don't lazily allocate in a + # method called from a GC collection callback! + @@threads.try(&.unsafe_each { |thread| yield thread }) end # Creates and starts a new system thread. diff --git a/src/fiber.cr b/src/fiber.cr index 049b7e9bb0c0..e8353d20e701 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -73,7 +73,9 @@ class Fiber # :nodoc: def self.unsafe_each(&) - fibers.unsafe_each { |fiber| yield fiber } + # nothing to iterate when @@fibers is nil + don't lazily allocate in a + # method called from a GC collection callback! + @@fibers.try(&.unsafe_each { |fiber| yield fiber }) end # Creates a new `Fiber` instance. From 6244f336d3d14c9d7bf79d66cc2a3c8b9234eda4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 29 May 2024 15:30:37 +0200 Subject: [PATCH 1149/1551] Fix using `System.retry_with_buffer` with stack buffer (#14615) --- src/crystal/system/unix/group.cr | 13 ++++++++----- src/crystal/system/unix/path.cr | 11 +++++------ src/crystal/system/unix/user.cr | 14 ++++++++------ 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/crystal/system/unix/group.cr b/src/crystal/system/unix/group.cr index 61ed41cbca0b..020c76dab51b 100644 --- a/src/crystal/system/unix/group.cr +++ b/src/crystal/system/unix/group.cr @@ -14,10 +14,11 @@ module Crystal::System::Group grp = uninitialized LibC::Group grp_pointer = pointerof(grp) System.retry_with_buffer("getgrnam_r", GETGR_R_SIZE_MAX) do |buf| - LibC.getgrnam_r(groupname, grp_pointer, buf, buf.size, pointerof(grp_pointer)) + LibC.getgrnam_r(groupname, grp_pointer, buf, buf.size, pointerof(grp_pointer)).tap do + # It's not necessary to check success with `ret == 0` because `grp_pointer` will be NULL on failure + return from_struct(grp) if grp_pointer + end end - - from_struct(grp) if grp_pointer end private def from_id?(groupid : String) @@ -27,8 +28,10 @@ module Crystal::System::Group grp = uninitialized LibC::Group grp_pointer = pointerof(grp) System.retry_with_buffer("getgrgid_r", GETGR_R_SIZE_MAX) do |buf| - LibC.getgrgid_r(groupid, grp_pointer, buf, buf.size, pointerof(grp_pointer)) + LibC.getgrgid_r(groupid, grp_pointer, buf, buf.size, pointerof(grp_pointer)).tap do + # It's not necessary to check success with `ret == 0` because `grp_pointer` will be NULL on failure + return from_struct(grp) if grp_pointer + end end - from_struct(grp) if grp_pointer end end diff --git a/src/crystal/system/unix/path.cr b/src/crystal/system/unix/path.cr index ebc6c52bb18d..4392486cbf6d 100644 --- a/src/crystal/system/unix/path.cr +++ b/src/crystal/system/unix/path.cr @@ -11,14 +11,13 @@ module Crystal::System::Path pwd_pointer = pointerof(pwd) ret = nil System.retry_with_buffer("getpwuid_r", User::GETPW_R_SIZE_MAX) do |buf| - ret = LibC.getpwuid_r(id, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)) + ret = LibC.getpwuid_r(id, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)).tap do + # It's not necessary to check success with `ret == 0` because `pwd_pointer` will be NULL on failure + return String.new(pwd.pw_dir) if pwd_pointer + end end - if pwd_pointer - String.new(pwd.pw_dir) - else - raise RuntimeError.from_os_error("getpwuid_r", Errno.new(ret.not_nil!)) - end + raise RuntimeError.from_os_error("getpwuid_r", Errno.new(ret.not_nil!)) end end end diff --git a/src/crystal/system/unix/user.cr b/src/crystal/system/unix/user.cr index a74e248c995e..9695f349957c 100644 --- a/src/crystal/system/unix/user.cr +++ b/src/crystal/system/unix/user.cr @@ -17,10 +17,11 @@ module Crystal::System::User pwd = uninitialized LibC::Passwd pwd_pointer = pointerof(pwd) System.retry_with_buffer("getpwnam_r", GETPW_R_SIZE_MAX) do |buf| - LibC.getpwnam_r(username, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)) + LibC.getpwnam_r(username, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)).tap do + # It's not necessary to check success with `ret == 0` because `pwd_pointer` will be NULL on failure + return from_struct(pwd) if pwd_pointer + end end - - from_struct(pwd) if pwd_pointer end private def from_id?(id : String) @@ -30,9 +31,10 @@ module Crystal::System::User pwd = uninitialized LibC::Passwd pwd_pointer = pointerof(pwd) System.retry_with_buffer("getpwuid_r", GETPW_R_SIZE_MAX) do |buf| - LibC.getpwuid_r(id, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)) + LibC.getpwuid_r(id, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)).tap do + # It's not necessary to check success with `ret == 0` because `pwd_pointer` will be NULL on failure + return from_struct(pwd) if pwd_pointer + end end - - from_struct(pwd) if pwd_pointer end end From 28fd0dc10c8671a6b1fbd3e3c3596d1e7357f1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 29 May 2024 15:30:44 +0200 Subject: [PATCH 1150/1551] Drop `Crystal::System::Socket#system_send` (#14637) --- src/crystal/system/socket.cr | 2 -- src/crystal/system/unix/socket.cr | 6 ------ src/crystal/system/wasi/socket.cr | 6 ------ src/crystal/system/win32/socket.cr | 11 ----------- src/socket.cr | 2 +- 5 files changed, 1 insertion(+), 26 deletions(-) diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr index 592045f2fbce..03c6e4930291 100644 --- a/src/crystal/system/socket.cr +++ b/src/crystal/system/socket.cr @@ -15,8 +15,6 @@ module Crystal::System::Socket # private def system_accept - # private def system_send(bytes : Bytes) : Int32 - # private def system_send_to(bytes : Bytes, addr : ::Socket::Address) # private def system_receive(bytes) diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 0a6ac995c0f1..1a5f91b86998 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -83,12 +83,6 @@ module Crystal::System::Socket end end - private def system_send(bytes : Bytes) : Int32 - evented_send("Error sending datagram") do - LibC.send(fd, bytes.to_unsafe.as(Void*), bytes.size, 0) - end - end - private def system_send_to(bytes : Bytes, addr : ::Socket::Address) bytes_sent = LibC.sendto(fd, bytes.to_unsafe.as(Void*), bytes.size, 0, addr, addr.size) raise ::Socket::Error.from_errno("Error sending datagram to #{addr}") if bytes_sent == -1 diff --git a/src/crystal/system/wasi/socket.cr b/src/crystal/system/wasi/socket.cr index 4f79f9955dad..ecdd20f3a2f9 100644 --- a/src/crystal/system/wasi/socket.cr +++ b/src/crystal/system/wasi/socket.cr @@ -33,12 +33,6 @@ module Crystal::System::Socket (raise NotImplementedError.new "Crystal::System::Socket#system_accept").as(Int32) end - private def system_send(bytes : Bytes) : Int32 - evented_send("Error sending datagram") do - LibC.send(fd, bytes.to_unsafe.as(Void*), bytes.size, 0) - end - end - private def system_send_to(bytes : Bytes, addr : ::Socket::Address) raise NotImplementedError.new "Crystal::System::Socket#system_send_to" end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index ee3e0174a7ea..58dbb3620239 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -259,17 +259,6 @@ module Crystal::System::Socket wsabuf end - private def system_send(message : Bytes) : Int32 - wsabuf = wsa_buffer(message) - - bytes = overlapped_write(fd, "WSASend") do |overlapped| - ret = LibC.WSASend(fd, pointerof(wsabuf), 1, out bytes_sent, 0, overlapped, nil) - {ret, bytes_sent} - end - - bytes.to_i32 - end - private def system_send_to(bytes : Bytes, addr : ::Socket::Address) wsabuf = wsa_buffer(bytes) bytes_written = overlapped_write(fd, "WSASendTo") do |overlapped| diff --git a/src/socket.cr b/src/socket.cr index 6ec7a0551aca..69d5388dc952 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -226,7 +226,7 @@ class Socket < IO # sock.send(Bytes[0]) # ``` def send(message) : Int32 - system_send(message.to_slice) + system_write(message.to_slice) end # Sends a message to the specified remote address. From 04998c0c7a247153a136f1a4eecb1bbf655d1ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 31 May 2024 11:02:46 +0200 Subject: [PATCH 1151/1551] Changelog for 1.12.2 (#14640) --- CHANGELOG.md | 20 ++++++++++++++++++++ src/VERSION | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef1a6667e788..61fb131ed6e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [1.12.2] (2024-05-31) + +_Patch release with a bug fix necessary for support of latest libgc_ + +[1.12.2]: https://github.com/crystal-lang/crystal/releases/1.12.2 + +### Bugfixes + +#### stdlib + +- *(runtime)* Don't allocate in `Fiber.unsafe_each` and `Thread.unsafe_each` ([#14635], thanks @ysbaddaden) + +[#14635]: https://github.com/crystal-lang/crystal/pull/14635 + +### Infrastructure + +- Changelog for 1.12.2 ([#14640], thanks @straight-shoota) + +[#14640]: https://github.com/crystal-lang/crystal/pull/14640 + ## [1.12.1] (2024-04-11) [1.12.1]: https://github.com/crystal-lang/crystal/releases/1.12.1 diff --git a/src/VERSION b/src/VERSION index f8f4f03b3dcc..6b89d58f861a 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.12.1 +1.12.2 From 1d4fcf18c485053d2407069f4b7dd6cf2ab9773a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 31 May 2024 12:51:25 +0200 Subject: [PATCH 1152/1551] Improve API docs for `Socket#send` (#14638) --- src/socket.cr | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/socket.cr b/src/socket.cr index 69d5388dc952..0822b274e555 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -213,6 +213,10 @@ class Socket < IO end # Sends a message to a previously connected remote address. + # Returns the number of bytes sent. + # Does not guarantee that the entire message is sent. That's only the case + # when the return value is equivalent to `message.bytesize`. + # `#write` ensures the entire message is sent. # # ``` # require "socket" @@ -230,6 +234,10 @@ class Socket < IO end # Sends a message to the specified remote address. + # Returns the number of bytes sent. + # Does not guarantee that the entire message is sent. That's only the case + # when the return value is equivalent to `message.bytesize`. + # `#write` ensures the entire message is sent but it requires an established connection. # # ``` # require "socket" From 114d87adf44ba0f5135df211c5db854371cdb254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 1 Jun 2024 10:57:29 +0200 Subject: [PATCH 1153/1551] Update previous Crystal release 1.12.2 (#14647) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 8 ++++---- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win_build_portable.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ 10 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0ebb39b57e22..d473a86dcda4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.12.1/crystal-1.12.1-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.12.2/crystal-1.12.2-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 6fc2326c3169..76a4a7cfd13d 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.12.1-build + image: crystallang/crystal:1.12.2-build name: "Test Interpreter" steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.12.1-build + image: crystallang/crystal:1.12.2-build name: Build interpreter steps: - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.12.1-build + image: crystallang/crystal:1.12.2-build strategy: matrix: part: [0, 1, 2, 3] @@ -67,7 +67,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.11.2-build + image: crystallang/crystal:1.12.2-build name: "Test primitives_spec with interpreter" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f395cf657082..fe76688fbe2a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.1] + crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2] flags: [""] include: # libffi is only available starting from the 1.2.2 build images diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index d803b5de4bb2..e1744bc2c6b5 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -58,7 +58,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.12.1" + crystal: "1.12.2" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 7deef9042769..bd9f3944ba67 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.12.1-alpine + container: crystallang/crystal:1.12.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -27,7 +27,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.12.1-alpine + container: crystallang/crystal:1.12.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.12.1-alpine + container: crystallang/crystal:1.12.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index 804f6439ddfc..763f889c1c06 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.12.1-alpine + container: crystallang/crystal:1.12.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.12.1-alpine + container: crystallang/crystal:1.12.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 9eb58df632df..8c14f476acfb 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.12.1-build + container: crystallang/crystal:1.12.2-build steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index bc32521451aa..e2f0d14ee3ba 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -24,7 +24,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.12.1" + crystal: "1.12.2" - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index 38c3cb829d81..3f1e588393ad 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.12.1/crystal-1.12.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.12.1-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.12.2/crystal-1.12.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.12.2-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.12.1}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.12.2}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 1d839dcf318f..92f405ad3755 100644 --- a/shell.nix +++ b/shell.nix @@ -53,18 +53,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.12.1/crystal-1.12.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:0f5kw9hqf01cqphmqa05icfqfmi087iyliaknpy67j95z405d0xz"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.12.2/crystal-1.12.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:017lqbbavvhi34d3y3s8rqcpqwxp45apvzanlpaq7izhxhyb4h5s"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.12.1/crystal-1.12.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:0f5kw9hqf01cqphmqa05icfqfmi087iyliaknpy67j95z405d0xz"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.12.2/crystal-1.12.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:017lqbbavvhi34d3y3s8rqcpqwxp45apvzanlpaq7izhxhyb4h5s"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.12.1/crystal-1.12.1-1-linux-x86_64.tar.gz"; - sha256 = "sha256:17w6l7cq9z3y8xd3cmqsgiyhxmbsa11d0xmlbbiy8fwv9lja03ww"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.12.2/crystal-1.12.2-1-linux-x86_64.tar.gz"; + sha256 = "sha256:0p1jxpdn9vc52qvf25x25a699l2hw4rmfz5snyylq84wrqpxbfvb"; }; }.${pkgs.stdenv.system}); From cf570b8cab3cfeaf313c064ca04b83c7f577ac76 Mon Sep 17 00:00:00 2001 From: Margret Riegert Date: Sat, 1 Jun 2024 05:36:11 -0400 Subject: [PATCH 1154/1551] Decode URI component for search functionality in docs (#14645) --- src/compiler/crystal/tools/doc/html/js/doc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/tools/doc/html/js/doc.js b/src/compiler/crystal/tools/doc/html/js/doc.js index e5ed78e5ea83..d9c82f99b809 100644 --- a/src/compiler/crystal/tools/doc/html/js/doc.js +++ b/src/compiler/crystal/tools/doc/html/js/doc.js @@ -99,7 +99,7 @@ document.addEventListener('DOMContentLoaded', function() { // TODO: Add OpenSearch description var searchQuery = location.hash.substring(3); history.pushState({searchQuery: searchQuery}, "Search for " + searchQuery, location.href.replace(/#q=.*/, "")); - searchInput.value = searchQuery; + searchInput.value = decodeURIComponent(searchQuery); document.addEventListener('CrystalDocs:loaded', performSearch); } From 135e908b24ef4c7e5292c7972817cebad2c1a371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 2 Jun 2024 16:43:09 +0200 Subject: [PATCH 1155/1551] Add `EventLoop::Socket` module (#14643) --- src/crystal/system/event_loop.cr | 9 ++ src/crystal/system/event_loop/socket.cr | 66 ++++++++++ src/crystal/system/socket.cr | 30 ++++- .../system/unix/event_loop_libevent.cr | 77 ++++++++++++ src/crystal/system/unix/socket.cr | 80 +----------- src/crystal/system/wasi/event_loop.cr | 32 +++++ src/crystal/system/wasi/socket.cr | 30 +---- src/crystal/system/win32/event_loop_iocp.cr | 111 +++++++++++++++++ src/crystal/system/win32/socket.cr | 114 ++---------------- src/socket.cr | 4 +- src/socket/udp_socket.cr | 4 +- 11 files changed, 333 insertions(+), 224 deletions(-) create mode 100644 src/crystal/system/event_loop/socket.cr diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index b8697025d2fb..b1b0f4907ec2 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -45,6 +45,15 @@ abstract class Crystal::EventLoop end end +abstract class Crystal::EventLoop + # The socket module is empty by default and filled with abstract defs when + # crystal/system/socket.cr is required. + module Socket + end + + include Socket +end + {% if flag?(:wasi) %} require "./wasi/event_loop" {% elsif flag?(:unix) %} diff --git a/src/crystal/system/event_loop/socket.cr b/src/crystal/system/event_loop/socket.cr new file mode 100644 index 000000000000..e6f35478b487 --- /dev/null +++ b/src/crystal/system/event_loop/socket.cr @@ -0,0 +1,66 @@ +# This file is only required when sockets are used (`require "./event_loop/socket"` in `src/crystal/system/socket.cr`) +# +# It fills `Crystal::EventLoop::Socket` with abstract defs. + +abstract class Crystal::EventLoop + module Socket + # Reads at least one byte from the socket into *slice*. + # + # Blocks the current fiber if no data is available for reading, continuing + # when available. Otherwise returns immediately. + # + # Returns the number of bytes read (up to `slice.size`). + # Returns 0 when the socket is closed and no data available. + # + # Use `#send_to` for sending a message to a specific target address. + abstract def read(socket : ::Socket, slice : Bytes) : Int32 + + # Writes at least one byte from *slice* to the socket. + # + # Blocks the current fiber if the socket is not ready for writing, + # continuing when ready. Otherwise returns immediately. + # + # Returns the number of bytes written (up to `slice.size`). + # + # Use `#receive_from` for capturing the source address of a message. + abstract def write(socket : ::Socket, slice : Bytes) : Int32 + + # Accepts an incoming TCP connection on the socket. + # + # Blocks the current fiber if no connection is waiting, continuing when one + # becomes available. Otherwise returns immediately. + # + # Returns a handle to the socket for the new connection. + abstract def accept(socket : ::Socket) : ::Socket::Handle? + + # Opens a connection on *socket* to the target *address*. + # + # Blocks the current fiber and continues when the connection is established. + # + # Returns `IO::Error` in case of an error. The caller is responsible for + # raising it as an exception if necessary. + abstract def connect(socket : ::Socket, address : ::Socket::Addrinfo | ::Socket::Address, timeout : ::Time::Span?) : IO::Error? + + # Sends at least one byte from *slice* to the socket with a target address + # *address*. + # + # Blocks the current fiber if the socket is not ready for writing, + # continuing when ready. Otherwise returns immediately. + # + # Returns the number of bytes sent (up to `slice.size`). + abstract def send_to(socket : ::Socket, slice : Bytes, address : ::Socket::Address) : Int32 + + # Receives at least one byte from the socket into *slice*, capturing the + # source address. + # + # Blocks the current fiber if no data is available for reading, continuing + # when available. Otherwise returns immediately. + # + # Returns a tuple containing the number of bytes received (up to `slice.size`) + # and the source address. + abstract def receive_from(socket : ::Socket, slice : Bytes) : Tuple(Int32, ::Socket::Address) + + # Closes the socket. + abstract def close(socket : ::Socket) : Nil + end +end diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr index 03c6e4930291..7e7b939fbeae 100644 --- a/src/crystal/system/socket.cr +++ b/src/crystal/system/socket.cr @@ -1,3 +1,5 @@ +require "./event_loop/socket" + module Crystal::System::Socket # Creates a file descriptor / socket handle # private def create_handle(family, type, protocol, blocking) : Handle @@ -5,7 +7,9 @@ module Crystal::System::Socket # Initializes a file descriptor / socket handle for use with Crystal Socket # private def initialize_handle(fd) - # private def system_connect(addr, timeout = nil) + private def system_connect(addr, timeout = nil) + event_loop.connect(self, addr, timeout) + end # Tries to bind the socket to a local address. # Yields an `Socket::BindError` if the binding failed. @@ -13,11 +17,17 @@ module Crystal::System::Socket # private def system_listen(backlog) - # private def system_accept + private def system_accept + event_loop.accept(self) + end - # private def system_send_to(bytes : Bytes, addr : ::Socket::Address) + private def system_send_to(bytes : Bytes, addr : ::Socket::Address) + event_loop.send_to(self, bytes, addr) + end - # private def system_receive(bytes) + private def system_receive_from(bytes : Bytes) : Tuple(Int32, ::Socket::Address) + event_loop.receive_from(self, bytes) + end # private def system_close_read @@ -69,12 +79,20 @@ module Crystal::System::Socket # def self.fcntl(fd, cmd, arg = 0) - # private def system_read(slice : Bytes) : Int32 + private def system_read(slice : Bytes) : Int32 + event_loop.read(self, slice) + end - # private def system_write(slice : Bytes) : Int32 + private def system_write(slice : Bytes) : Int32 + event_loop.write(self, slice) + end # private def system_close + private def event_loop : Crystal::EventLoop::Socket + Crystal::EventLoop.current + end + # IPSocket: # private def system_local_address diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr index fe95ec0c8a3e..15d5e8b8787a 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/system/unix/event_loop_libevent.cr @@ -75,4 +75,81 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop end end end + + def read(socket : ::Socket, slice : Bytes) : Int32 + socket.evented_read("Error reading socket") do + LibC.recv(socket.fd, slice, slice.size, 0).to_i32 + end + end + + def write(socket : ::Socket, slice : Bytes) : Int32 + socket.evented_write("Error writing to socket") do + LibC.send(socket.fd, slice, slice.size, 0).to_i32 + end + end + + def receive_from(socket : ::Socket, slice : Bytes) : Tuple(Int32, ::Socket::Address) + sockaddr = Pointer(LibC::SockaddrStorage).malloc.as(LibC::Sockaddr*) + # initialize sockaddr with the initialized family of the socket + copy = sockaddr.value + copy.sa_family = socket.family + sockaddr.value = copy + + addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrStorage)) + + bytes_read = socket.evented_read("Error receiving datagram") do + LibC.recvfrom(socket.fd, slice, slice.size, 0, sockaddr, pointerof(addrlen)) + end + + {bytes_read, ::Socket::Address.from(sockaddr, addrlen)} + end + + def send_to(socket : ::Socket, slice : Bytes, addr : ::Socket::Address) : Int32 + bytes_sent = LibC.sendto(socket.fd, slice.to_unsafe.as(Void*), slice.size, 0, addr, addr.size) + raise ::Socket::Error.from_errno("Error sending datagram to #{addr}") if bytes_sent == -1 + # to_i32 is fine because string/slice sizes are an Int32 + bytes_sent.to_i32 + end + + def connect(socket : ::Socket, address : ::Socket::Addrinfo | ::Socket::Address, timeout : ::Time::Span?) : IO::Error? + loop do + if LibC.connect(socket.fd, address, address.size) == 0 + return + end + case Errno.value + when Errno::EISCONN + return + when Errno::EINPROGRESS, Errno::EALREADY + socket.wait_writable(timeout: timeout) do + return IO::TimeoutError.new("connect timed out") + end + else + return ::Socket::ConnectError.from_errno("connect") + end + end + end + + def accept(socket : ::Socket) : ::Socket::Handle? + loop do + client_fd = LibC.accept(socket.fd, nil, nil) + if client_fd == -1 + if socket.closed? + return + elsif Errno.value == Errno::EAGAIN + socket.wait_readable(raise_if_closed: false) do + raise IO::TimeoutError.new("Accept timed out") + end + return if socket.closed? + else + raise ::Socket::Error.from_errno("accept") + end + else + return client_fd + end + end + end + + def close(socket : ::Socket) : Nil + socket.evented_close + end end diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 1a5f91b86998..a263e7742301 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -26,25 +26,6 @@ module Crystal::System::Socket {% end %} end - private def system_connect(addr, timeout = nil) - timeout = timeout.seconds unless timeout.is_a? ::Time::Span | Nil - loop do - if LibC.connect(fd, addr, addr.size) == 0 - return - end - case Errno.value - when Errno::EISCONN - return - when Errno::EINPROGRESS, Errno::EALREADY - wait_writable(timeout: timeout) do - return IO::TimeoutError.new("connect timed out") - end - else - return ::Socket::ConnectError.from_errno("connect") - end - end - end - # Tries to bind the socket to a local address. # Yields an `Socket::BindError` if the binding failed. private def system_bind(addr, addrstr, &) @@ -59,53 +40,6 @@ module Crystal::System::Socket end end - private def system_accept - loop do - client_fd = LibC.accept(fd, nil, nil) - if client_fd == -1 - if closed? - return - elsif Errno.value == Errno::EAGAIN - wait_acceptable - return if closed? - else - raise ::Socket::Error.from_errno("accept") - end - else - return client_fd - end - end - end - - private def wait_acceptable - wait_readable(raise_if_closed: false) do - raise IO::TimeoutError.new("Accept timed out") - end - end - - private def system_send_to(bytes : Bytes, addr : ::Socket::Address) - bytes_sent = LibC.sendto(fd, bytes.to_unsafe.as(Void*), bytes.size, 0, addr, addr.size) - raise ::Socket::Error.from_errno("Error sending datagram to #{addr}") if bytes_sent == -1 - # to_i32 is fine because string/slice sizes are an Int32 - bytes_sent.to_i32 - end - - private def system_receive(bytes) - sockaddr = Pointer(LibC::SockaddrStorage).malloc.as(LibC::Sockaddr*) - # initialize sockaddr with the initialized family of the socket - copy = sockaddr.value - copy.sa_family = family - sockaddr.value = copy - - addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrStorage)) - - bytes_read = evented_read("Error receiving datagram") do - LibC.recvfrom(fd, bytes, bytes.size, 0, sockaddr, pointerof(addrlen)) - end - - {bytes_read, ::Socket::Address.from(sockaddr, addrlen)} - end - private def system_close_read if LibC.shutdown(fd, LibC::SHUT_RD) != 0 raise ::Socket::Error.from_errno("shutdown read") @@ -248,23 +182,11 @@ module Crystal::System::Socket LibC.isatty(fd) == 1 end - private def system_read(slice : Bytes) : Int32 - evented_read("Error reading socket") do - LibC.recv(fd, slice, slice.size, 0).to_i32 - end - end - - private def system_write(slice : Bytes) : Int32 - evented_write("Error writing to socket") do - LibC.send(fd, slice, slice.size, 0) - end - end - private def system_close # Perform libevent cleanup before LibC.close. # Using a file descriptor after it has been closed is never defined and can # always lead to undefined results. This is not specific to libevent. - evented_close + event_loop.close(self) # Clear the @volatile_fd before actually closing it in order to # reduce the chance of reading an outdated fd value diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr index e1c2fc2166f1..01094ee99a40 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/system/wasi/event_loop.cr @@ -35,6 +35,38 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop def create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::EventLoop::Event raise NotImplementedError.new("Crystal::Wasi::EventLoop.create_fd_read_event") end + + def read(socket : ::Socket, slice : Bytes) : Int32 + socket.evented_read("Error reading socket") do + LibC.recv(socket.fd, slice, slice.size, 0).to_i32 + end + end + + def write(socket : ::Socket, slice : Bytes) : Int32 + socket.evented_write("Error writing to socket") do + LibC.send(socket.fd, slice, slice.size, 0) + end + end + + def receive_from(socket : ::Socket, slice : Bytes) : Tuple(Int32, ::Socket::Address) + raise NotImplementedError.new "Crystal::Wasi::EventLoop#receive_from" + end + + def send_to(socket : ::Socket, slice : Bytes, addr : ::Socket::Address) : Int32 + raise NotImplementedError.new "Crystal::Wasi::EventLoop#send_to" + end + + def connect(socket : ::Socket, address : ::Socket::Addrinfo | ::Socket::Address, timeout : ::Time::Span | ::Nil) : IO::Error? + raise NotImplementedError.new "Crystal::Wasi::EventLoop#connect" + end + + def accept(socket : ::Socket) : ::Socket::Handle? + raise NotImplementedError.new "Crystal::Wasi::EventLoop#accept" + end + + def close(socket : ::Socket) : Nil + socket.evented_close + end end struct Crystal::Wasi::Event diff --git a/src/crystal/system/wasi/socket.cr b/src/crystal/system/wasi/socket.cr index ecdd20f3a2f9..901e8a4db1cb 100644 --- a/src/crystal/system/wasi/socket.cr +++ b/src/crystal/system/wasi/socket.cr @@ -15,10 +15,6 @@ module Crystal::System::Socket private def initialize_handle(fd) end - private def system_connect(addr, timeout = nil) - raise NotImplementedError.new "Crystal::System::Socket#system_connect" - end - # Tries to bind the socket to a local address. # Yields an `Socket::BindError` if the binding failed. private def system_bind(addr, addrstr, &) @@ -29,18 +25,6 @@ module Crystal::System::Socket raise NotImplementedError.new "Crystal::System::Socket#system_listen" end - private def system_accept - (raise NotImplementedError.new "Crystal::System::Socket#system_accept").as(Int32) - end - - private def system_send_to(bytes : Bytes, addr : ::Socket::Address) - raise NotImplementedError.new "Crystal::System::Socket#system_send_to" - end - - private def system_receive(bytes) - raise NotImplementedError.new "Crystal::System::Socket#system_receive" - end - private def system_close_read if LibC.shutdown(fd, LibC::SHUT_RD) != 0 raise ::Socket::Error.from_errno("shutdown read") @@ -155,23 +139,11 @@ module Crystal::System::Socket LibC.isatty(fd) == 1 end - private def system_read(slice : Bytes) : Int32 - evented_read("Error reading socket") do - LibC.recv(fd, slice, slice.size, 0).to_i32 - end - end - - private def system_write(slice : Bytes) : Int32 - evented_write("Error writing to socket") do - LibC.send(fd, slice, slice.size, 0) - end - end - private def system_close # Perform libevent cleanup before LibC.close. # Using a file descriptor after it has been closed is never defined and can # always lead to undefined results. This is not specific to libevent. - evented_close + event_loop.close(self) # Clear the @volatile_fd before actually closing it in order to # reduce the chance of reading an outdated fd value diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index ae89e85bd3ed..3332bb11a55c 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -148,6 +148,117 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop def create_timeout_event(fiber) : Crystal::EventLoop::Event Crystal::Iocp::Event.new(fiber, timeout: true) end + + private def wsa_buffer(bytes) + wsabuf = LibC::WSABUF.new + wsabuf.len = bytes.size + wsabuf.buf = bytes.to_unsafe + wsabuf + end + + def read(socket : ::Socket, slice : Bytes) : Int32 + wsabuf = wsa_buffer(slice) + + bytes_read = socket.wsa_overlapped_operation(socket.fd, "WSARecv", socket.read_timeout, connreset_is_error: false) do |overlapped| + flags = 0_u32 + ret = LibC.WSARecv(socket.fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), overlapped, nil) + {ret, bytes_received} + end + + bytes_read.to_i32 + end + + def write(socket : ::Socket, slice : Bytes) : Int32 + wsabuf = wsa_buffer(slice) + + bytes = socket.wsa_overlapped_operation(socket.fd, "WSASend", socket.write_timeout) do |overlapped| + ret = LibC.WSASend(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, overlapped, nil) + {ret, bytes_sent} + end + + bytes.to_i32 + end + + def send_to(socket : ::Socket, bytes : Bytes, addr : ::Socket::Address) : Int32 + wsabuf = wsa_buffer(bytes) + bytes_written = socket.wsa_overlapped_operation(socket.fd, "WSASendTo", socket.write_timeout) do |overlapped| + ret = LibC.WSASendTo(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, addr, addr.size, overlapped, nil) + {ret, bytes_sent} + end + raise ::Socket::Error.from_wsa_error("Error sending datagram to #{addr}") if bytes_written == -1 + + # to_i32 is fine because string/slice sizes are an Int32 + bytes_written.to_i32 + end + + def receive(socket : ::Socket, slice : Bytes) : Int32 + receive_from(socket, slice)[0] + end + + def receive_from(socket : ::Socket, slice : Bytes) : Tuple(Int32, ::Socket::Address) + sockaddr = Pointer(LibC::SOCKADDR_STORAGE).malloc.as(LibC::Sockaddr*) + # initialize sockaddr with the initialized family of the socket + copy = sockaddr.value + copy.sa_family = socket.family + sockaddr.value = copy + + addrlen = sizeof(LibC::SOCKADDR_STORAGE) + + wsabuf = wsa_buffer(slice) + + flags = 0_u32 + bytes_read = socket.wsa_overlapped_operation(socket.fd, "WSARecvFrom", socket.read_timeout) do |overlapped| + ret = LibC.WSARecvFrom(socket.fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), sockaddr, pointerof(addrlen), overlapped, nil) + {ret, bytes_received} + end + + {bytes_read.to_i32, ::Socket::Address.from(sockaddr, addrlen)} + end + + def connect(socket : ::Socket, address : ::Socket::Addrinfo | ::Socket::Address, timeout : ::Time::Span?) : IO::Error? + socket.overlapped_connect(socket.fd, "ConnectEx") do |overlapped| + # This is: LibC.ConnectEx(fd, address, address.size, nil, 0, nil, overlapped) + Crystal::System::Socket.connect_ex.call(socket.fd, address.to_unsafe, address.size, Pointer(Void).null, 0_u32, Pointer(UInt32).null, overlapped) + end + end + + def accept(socket : ::Socket) : ::Socket::Handle? + socket.system_accept do |client_handle| + address_size = sizeof(LibC::SOCKADDR_STORAGE) + 16 + + # buffer_size is set to zero to only accept the connection and don't receive any data. + # That will be a different operation. + # + # > If dwReceiveDataLength is zero, accepting the connection will not result in a receive operation. + # > Instead, AcceptEx completes as soon as a connection arrives, without waiting for any data. + # + # TODO: Investigate benefits from receiving data here directly. It's hard to integrate into the event loop and socket API. + buffer_size = 0 + output_buffer = Bytes.new(address_size * 2 + buffer_size) + + success = socket.overlapped_accept(socket.fd, "AcceptEx") do |overlapped| + # This is: LibC.AcceptEx(fd, client_handle, output_buffer, buffer_size, address_size, address_size, out received_bytes, overlapped) + received_bytes = uninitialized UInt32 + Crystal::System::Socket.accept_ex.call(socket.fd, client_handle, + output_buffer.to_unsafe.as(Void*), buffer_size.to_u32!, + address_size.to_u32!, address_size.to_u32!, pointerof(received_bytes), overlapped) + end + + if success + # AcceptEx does not automatically set the socket options on the accepted + # socket to match those of the listening socket, we need to ask for that + # explicitly with SO_UPDATE_ACCEPT_CONTEXT + socket.system_setsockopt client_handle, LibC::SO_UPDATE_ACCEPT_CONTEXT, socket.fd + + true + else + false + end + end + end + + def close(socket : ::Socket) : Nil + end end class Crystal::Iocp::Event diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 58dbb3620239..974d11c93d31 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -110,10 +110,7 @@ module Crystal::System::Socket return ::Socket::BindError.from_wsa_error("Could not bind to '*'") end - error = overlapped_connect(fd, "ConnectEx") do |overlapped| - # This is: LibC.ConnectEx(fd, addr, addr.size, nil, 0, nil, overlapped) - Crystal::System::Socket.connect_ex.call(fd, addr.to_unsafe, addr.size, Pointer(Void).null, 0_u32, Pointer(UInt32).null, overlapped) - end + error = event_loop.connect(self, addr, timeout) if error return error @@ -132,7 +129,8 @@ module Crystal::System::Socket end end - private def overlapped_connect(socket, method, &) + # :nodoc: + def overlapped_connect(socket, method, &) OverlappedOperation.run(socket) do |operation| result = yield operation.start @@ -185,11 +183,11 @@ module Crystal::System::Socket end end - protected def system_accept : Handle? + def system_accept(& : Handle -> Bool) : Handle? client_socket = create_handle(family, type, protocol, blocking) initialize_handle(client_socket) - if system_accept(client_socket) + if yield client_socket client_socket else LibC.closesocket(client_socket) @@ -198,30 +196,7 @@ module Crystal::System::Socket end end - protected def system_accept(client_socket : Handle) : Bool - address_size = sizeof(LibC::SOCKADDR_STORAGE) + 16 - buffer_size = 0 - output_buffer = Bytes.new(address_size * 2 + buffer_size) - - success = overlapped_accept(fd, "AcceptEx") do |overlapped| - # This is: LibC.AcceptEx(fd, client_socket, output_buffer, buffer_size, address_size, address_size, out received_bytes, overlapped) - received_bytes = uninitialized UInt32 - Crystal::System::Socket.accept_ex.call(fd, client_socket, - output_buffer.to_unsafe.as(Void*), buffer_size.to_u32!, - address_size.to_u32!, address_size.to_u32!, pointerof(received_bytes), overlapped) - end - - return false unless success - - # AcceptEx does not automatically set the socket options on the accepted - # socket to match those of the listening socket, we need to ask for that - # explicitly with SO_UPDATE_ACCEPT_CONTEXT - system_setsockopt client_socket, LibC::SO_UPDATE_ACCEPT_CONTEXT, fd - - true - end - - private def overlapped_accept(socket, method, &) + def overlapped_accept(socket, method, &) OverlappedOperation.run(socket) do |operation| result = yield operation.start @@ -252,45 +227,6 @@ module Crystal::System::Socket end end - private def wsa_buffer(bytes) - wsabuf = LibC::WSABUF.new - wsabuf.len = bytes.size - wsabuf.buf = bytes.to_unsafe - wsabuf - end - - private def system_send_to(bytes : Bytes, addr : ::Socket::Address) - wsabuf = wsa_buffer(bytes) - bytes_written = overlapped_write(fd, "WSASendTo") do |overlapped| - ret = LibC.WSASendTo(fd, pointerof(wsabuf), 1, out bytes_sent, 0, addr, addr.size, overlapped, nil) - {ret, bytes_sent} - end - raise ::Socket::Error.from_wsa_error("Error sending datagram to #{addr}") if bytes_written == -1 - - # to_i32 is fine because string/slice sizes are an Int32 - bytes_written.to_i32 - end - - private def system_receive(bytes) - sockaddr = Pointer(LibC::SOCKADDR_STORAGE).malloc.as(LibC::Sockaddr*) - # initialize sockaddr with the initialized family of the socket - copy = sockaddr.value - copy.sa_family = family - sockaddr.value = copy - - addrlen = sizeof(LibC::SOCKADDR_STORAGE) - - wsabuf = wsa_buffer(bytes) - - flags = 0_u32 - bytes_read = overlapped_read(fd, "WSARecvFrom") do |overlapped| - ret = LibC.WSARecvFrom(fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), sockaddr, pointerof(addrlen), overlapped, nil) - {ret, bytes_received} - end - - {bytes_read.to_i32, ::Socket::Address.from(sockaddr, addrlen)} - end - private def system_close_read if LibC.shutdown(fd, LibC::SH_RECEIVE) != 0 raise ::Socket::Error.from_wsa_error("shutdown read") @@ -392,7 +328,8 @@ module Crystal::System::Socket raise ::Socket::Error.from_wsa_error("getsockopt #{optname}") end - private def system_setsockopt(handle, optname, optval, level = LibC::SOL_SOCKET) + # :nodoc: + def system_setsockopt(handle, optname, optval, level = LibC::SOL_SOCKET) optsize = sizeof(typeof(optval)) ret = LibC.setsockopt(handle, level, optname, pointerof(optval).as(UInt8*), optsize) @@ -434,41 +371,6 @@ module Crystal::System::Socket LibC.GetConsoleMode(LibC::HANDLE.new(fd), out _) != 0 end - private def system_read(slice : Bytes) : Int32 - wsabuf = wsa_buffer(slice) - - bytes_read = overlapped_read(fd, "WSARecv", connreset_is_error: false) do |overlapped| - flags = 0_u32 - ret = LibC.WSARecv(fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), overlapped, nil) - {ret, bytes_received} - end - - bytes_read.to_i32 - end - - private def system_write(slice : Bytes) : Int32 - wsabuf = wsa_buffer(slice) - - bytes = overlapped_write(fd, "WSASend") do |overlapped| - ret = LibC.WSASend(fd, pointerof(wsabuf), 1, out bytes_sent, 0, overlapped, nil) - {ret, bytes_sent} - end - - bytes.to_i32 - end - - private def overlapped_write(socket, method, &) - wsa_overlapped_operation(socket, method, write_timeout) do |operation| - yield operation - end - end - - private def overlapped_read(socket, method, *, connreset_is_error = true, &) - wsa_overlapped_operation(socket, method, read_timeout, connreset_is_error) do |operation| - yield operation - end - end - def system_close handle = @volatile_fd.swap(LibC::INVALID_SOCKET) diff --git a/src/socket.cr b/src/socket.cr index 0822b274e555..dfad08d762cf 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -264,7 +264,7 @@ class Socket < IO def receive(max_message_size = 512) : {String, Address} address = nil message = String.new(max_message_size) do |buffer| - bytes_read, address = system_receive(Slice.new(buffer, max_message_size)) + bytes_read, address = system_receive_from(Slice.new(buffer, max_message_size)) {bytes_read, 0} end {message, address.as(Address)} @@ -282,7 +282,7 @@ class Socket < IO # bytes_read, client_addr = server.receive(message) # ``` def receive(message : Bytes) : {Int32, Address} - system_receive(message) + system_receive_from(message) end # Calls `shutdown(2)` with `SHUT_RD` diff --git a/src/socket/udp_socket.cr b/src/socket/udp_socket.cr index 9175b787cfe9..bba9d1aea39a 100644 --- a/src/socket/udp_socket.cr +++ b/src/socket/udp_socket.cr @@ -70,7 +70,7 @@ class UDPSocket < IPSocket def receive(max_message_size = 512) : {String, IPAddress} address = nil message = String.new(max_message_size) do |buffer| - bytes_read, address = system_receive(Slice.new(buffer, max_message_size)) + bytes_read, address = system_receive_from(Slice.new(buffer, max_message_size)) {bytes_read, 0} end {message, address.as(IPAddress)} @@ -88,7 +88,7 @@ class UDPSocket < IPSocket # bytes_read, client_addr = server.receive(message) # ``` def receive(message : Bytes) : {Int32, IPAddress} - bytes_read, address = system_receive(message) + bytes_read, address = system_receive_from(message) {bytes_read, address.as(IPAddress)} end From 4bc72027800655257b2d302ee7131b34655c5d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 4 Jun 2024 10:26:14 +0200 Subject: [PATCH 1156/1551] Fix abstract def parameter name in `LibEvent::EventLoop#send_to` (#14658) --- src/crystal/system/unix/event_loop_libevent.cr | 6 +++--- src/crystal/system/wasi/event_loop.cr | 2 +- src/crystal/system/win32/event_loop_iocp.cr | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr index 15d5e8b8787a..af1fd057be44 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/system/unix/event_loop_libevent.cr @@ -104,9 +104,9 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop {bytes_read, ::Socket::Address.from(sockaddr, addrlen)} end - def send_to(socket : ::Socket, slice : Bytes, addr : ::Socket::Address) : Int32 - bytes_sent = LibC.sendto(socket.fd, slice.to_unsafe.as(Void*), slice.size, 0, addr, addr.size) - raise ::Socket::Error.from_errno("Error sending datagram to #{addr}") if bytes_sent == -1 + def send_to(socket : ::Socket, slice : Bytes, address : ::Socket::Address) : Int32 + bytes_sent = LibC.sendto(socket.fd, slice.to_unsafe.as(Void*), slice.size, 0, address, address.size) + raise ::Socket::Error.from_errno("Error sending datagram to #{address}") if bytes_sent == -1 # to_i32 is fine because string/slice sizes are an Int32 bytes_sent.to_i32 end diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr index 01094ee99a40..abec79a39441 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/system/wasi/event_loop.cr @@ -52,7 +52,7 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop raise NotImplementedError.new "Crystal::Wasi::EventLoop#receive_from" end - def send_to(socket : ::Socket, slice : Bytes, addr : ::Socket::Address) : Int32 + def send_to(socket : ::Socket, slice : Bytes, address : ::Socket::Address) : Int32 raise NotImplementedError.new "Crystal::Wasi::EventLoop#send_to" end diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 3332bb11a55c..f433fb931c41 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -179,13 +179,13 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop bytes.to_i32 end - def send_to(socket : ::Socket, bytes : Bytes, addr : ::Socket::Address) : Int32 - wsabuf = wsa_buffer(bytes) + def send_to(socket : ::Socket, slice : Bytes, address : ::Socket::Address) : Int32 + wsabuf = wsa_buffer(slice) bytes_written = socket.wsa_overlapped_operation(socket.fd, "WSASendTo", socket.write_timeout) do |overlapped| - ret = LibC.WSASendTo(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, addr, addr.size, overlapped, nil) + ret = LibC.WSASendTo(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, address, address.size, overlapped, nil) {ret, bytes_sent} end - raise ::Socket::Error.from_wsa_error("Error sending datagram to #{addr}") if bytes_written == -1 + raise ::Socket::Error.from_wsa_error("Error sending datagram to #{address}") if bytes_written == -1 # to_i32 is fine because string/slice sizes are an Int32 bytes_written.to_i32 From 42545bc005ebd0f961997927e1c0502f19b01ace Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 4 Jun 2024 17:16:37 +0200 Subject: [PATCH 1157/1551] Don't change OpenSSL default cipher suites (#14655) --- src/openssl/ssl/context.cr | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr index 5db3e754e79b..0a789d2e5176 100644 --- a/src/openssl/ssl/context.cr +++ b/src/openssl/ssl/context.cr @@ -40,8 +40,6 @@ abstract class OpenSSL::SSL::Context # context = OpenSSL::SSL::Context::Client.new # context.add_options(OpenSSL::SSL::Options::NO_SSL_V2 | OpenSSL::SSL::Options::NO_SSL_V3) # ``` - # - # It uses `CIPHERS_OLD` compatibility level by default. def initialize(method : LibSSL::SSLMethod = Context.default_method) super(method) @@ -49,8 +47,6 @@ abstract class OpenSSL::SSL::Context {% if LibSSL.has_method?(:x509_verify_param_lookup) %} self.default_verify_param = "ssl_server" {% end %} - - self.ciphers = CIPHERS_OLD end # Returns a new TLS client context with only the given method set. @@ -128,8 +124,6 @@ abstract class OpenSSL::SSL::Context # context = OpenSSL::SSL::Context::Server.new # context.add_options(OpenSSL::SSL::Options::NO_SSL_V2 | OpenSSL::SSL::Options::NO_SSL_V3) # ``` - # - # It uses `CIPHERS_INTERMEDIATE` compatibility level by default. def initialize(method : LibSSL::SSLMethod = Context.default_method) super(method) @@ -138,8 +132,6 @@ abstract class OpenSSL::SSL::Context {% end %} set_tmp_ecdh_key(curve: LibCrypto::NID_X9_62_prime256v1) - - self.ciphers = CIPHERS_INTERMEDIATE end # Returns a new TLS server context with only the given method set. From 4dce0d5946ad7742896d062a5da0797505b4538c Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 4 Jun 2024 22:03:49 +0200 Subject: [PATCH 1158/1551] Harmonize close on exec for `Socket` & `FileDescriptor` on Windows (#14634) Co-authored-by: Sijawusz Pur Rahnama --- src/crystal/system/win32/socket.cr | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 974d11c93d31..294a9d675be0 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -354,13 +354,11 @@ module Crystal::System::Socket end private def system_close_on_exec? - flags = fcntl(LibC::F_GETFD) - (flags & LibC::FD_CLOEXEC) == LibC::FD_CLOEXEC + false end private def system_close_on_exec=(arg : Bool) - fcntl(LibC::F_SETFD, arg ? LibC::FD_CLOEXEC : 0) - arg + raise NotImplementedError.new "Crystal::System::Socket#system_close_on_exec=" if arg end def self.fcntl(fd, cmd, arg = 0) From 00b1e790eadc542a95d9f97359cc75235301141b Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 5 Jun 2024 09:29:11 +0200 Subject: [PATCH 1159/1551] Add `Crystal::System::Thread.current_thread?`, `#scheduler?` (#14660) --- src/crystal/system/thread.cr | 12 ++++++++++++ src/crystal/system/unix/pthread.cr | 10 ++++++++++ src/crystal/system/win32/thread.cr | 4 ++++ 3 files changed, 26 insertions(+) diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index d6a6e5653276..03b00f2779f4 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -10,6 +10,8 @@ module Crystal::System::Thread # def self.current_thread : ::Thread + # def self.current_thread? : ::Thread? + # def self.current_thread=(thread : ::Thread) # private def system_join : Exception? @@ -102,6 +104,11 @@ class Thread Crystal::System::Thread.current_thread end + # :nodoc: + def self.current? : Thread? + Crystal::System::Thread.current_thread? + end + # Associates the Thread object to the running system thread. protected def self.current=(current : Thread) : Thread Crystal::System::Thread.current_thread = current @@ -122,6 +129,11 @@ class Thread # :nodoc: getter scheduler : Crystal::Scheduler { Crystal::Scheduler.new(self) } + # :nodoc: + def scheduler? : ::Crystal::Scheduler? + @scheduler + end + protected def start Thread.threads.push(self) Thread.current = self diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index f96d0bbf75ba..4b357b04281c 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -55,6 +55,12 @@ module Crystal::System::Thread end end + def self.current_thread? : ::Thread? + if ptr = LibC.pthread_getspecific(@@current_key) + ptr.as(::Thread) + end + end + def self.current_thread=(thread : ::Thread) ret = LibC.pthread_setspecific(@@current_key, thread.as(Void*)) raise RuntimeError.from_os_error("pthread_setspecific", Errno.new(ret)) unless ret == 0 @@ -63,6 +69,10 @@ module Crystal::System::Thread {% else %} @[ThreadLocal] class_property current_thread : ::Thread { ::Thread.new } + + def self.current_thread? : ::Thread? + @@current_thread + end {% end %} private def system_join : Exception? diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr index 2b44f66c28ce..9507e332b422 100644 --- a/src/crystal/system/win32/thread.cr +++ b/src/crystal/system/win32/thread.cr @@ -47,6 +47,10 @@ module Crystal::System::Thread @[ThreadLocal] class_property current_thread : ::Thread { ::Thread.new } + def self.current_thread? : ::Thread? + @@current_thread + end + private def system_join : Exception? if LibC.WaitForSingleObject(@system_handle, LibC::INFINITE) != LibC::WAIT_OBJECT_0 return RuntimeError.from_winerror("WaitForSingleObject") From 239032e3df77fbbf4d31c11d9c712a86841ef6fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 5 Jun 2024 09:34:00 +0200 Subject: [PATCH 1160/1551] Add `EventLoop::FileDescriptor` module (#14639) --- src/crystal/system/event_loop.cr | 6 +++++ .../system/event_loop/file_descriptor.cr | 23 ++++++++++++++++++ src/crystal/system/file_descriptor.cr | 12 ++++++++-- .../system/unix/event_loop_libevent.cr | 24 +++++++++++++++++++ src/crystal/system/unix/file_descriptor.cr | 24 ++----------------- src/crystal/system/wasi/event_loop.cr | 24 +++++++++++++++++++ src/crystal/system/win32/event_loop_iocp.cr | 21 ++++++++++++++++ src/crystal/system/win32/file_descriptor.cr | 15 ++++-------- 8 files changed, 115 insertions(+), 34 deletions(-) create mode 100644 src/crystal/system/event_loop/file_descriptor.cr diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index b1b0f4907ec2..d2d8689d4d55 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -45,7 +45,13 @@ abstract class Crystal::EventLoop end end +require "./event_loop/file_descriptor" + abstract class Crystal::EventLoop + # The FileDescriptor interface is always needed, so we include it right in + # the main interface. + include FileDescriptor + # The socket module is empty by default and filled with abstract defs when # crystal/system/socket.cr is required. module Socket diff --git a/src/crystal/system/event_loop/file_descriptor.cr b/src/crystal/system/event_loop/file_descriptor.cr new file mode 100644 index 000000000000..a041263609d9 --- /dev/null +++ b/src/crystal/system/event_loop/file_descriptor.cr @@ -0,0 +1,23 @@ +abstract class Crystal::EventLoop + module FileDescriptor + # Reads at least one byte from the file descriptor into *slice*. + # + # Blocks the current fiber if no data is available for reading, continuing + # when available. Otherwise returns immediately. + # + # Returns the number of bytes read (up to `slice.size`). + # Returns 0 when EOF is reached. + abstract def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 + + # Writes at least one byte from *slice* to the file descriptor. + # + # Blocks the current fiber if the file descriptor isn't ready for writing, + # continuing when ready. Otherwise returns immediately. + # + # Returns the number of bytes written (up to `slice.size`). + abstract def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 + + # Closes the file descriptor resource. + abstract def close(file_descriptor : Crystal::System::FileDescriptor) : Nil + end +end diff --git a/src/crystal/system/file_descriptor.cr b/src/crystal/system/file_descriptor.cr index 5497c3066803..0180627d59ce 100644 --- a/src/crystal/system/file_descriptor.cr +++ b/src/crystal/system/file_descriptor.cr @@ -14,9 +14,17 @@ module Crystal::System::FileDescriptor # cooked mode otherwise. # private def system_raw(enable : Bool, & : ->) - # private def system_read(slice : Bool) : Int32 + private def system_read(slice : Bytes) : Int32 + event_loop.read(self, slice) + end - # private def system_write(slice : Bool) : Int32 + private def system_write(slice : Bytes) : Int32 + event_loop.write(self, slice) + end + + private def event_loop : Crystal::EventLoop::FileDescriptor + Crystal::EventLoop.current + end end {% if flag?(:wasi) %} diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr index af1fd057be44..8877b4f40273 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/system/unix/event_loop_libevent.cr @@ -76,6 +76,30 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop end end + def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 + file_descriptor.evented_read("Error reading file_descriptor") do + LibC.read(file_descriptor.fd, slice, slice.size).tap do |return_code| + if return_code == -1 && Errno.value == Errno::EBADF + raise IO::Error.new "File not open for reading", target: file_descriptor + end + end + end + end + + def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 + file_descriptor.evented_write("Error writing file_descriptor") do + LibC.write(file_descriptor.fd, slice, slice.size).tap do |return_code| + if return_code == -1 && Errno.value == Errno::EBADF + raise IO::Error.new "File not open for writing", target: file_descriptor + end + end + end + end + + def close(file_descriptor : Crystal::System::FileDescriptor) : Nil + file_descriptor.evented_close + end + def read(socket : ::Socket, slice : Bytes) : Int32 socket.evented_read("Error reading socket") do LibC.recv(socket.fd, slice, slice.size, 0).to_i32 diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index b6e7de8b7a36..4992bfbeab4c 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -17,26 +17,6 @@ module Crystal::System::FileDescriptor STDOUT_HANDLE = 1 STDERR_HANDLE = 2 - private def system_read(slice : Bytes) : Int32 - evented_read("Error reading file") do - LibC.read(fd, slice, slice.size).tap do |return_code| - if return_code == -1 && Errno.value == Errno::EBADF - raise IO::Error.new "File not open for reading", target: self - end - end - end - end - - private def system_write(slice : Bytes) : Int32 - evented_write("Error writing file") do - LibC.write(fd, slice, slice.size).tap do |return_code| - if return_code == -1 && Errno.value == Errno::EBADF - raise IO::Error.new "File not open for writing", target: self - end - end - end - end - private def system_blocking? flags = fcntl(LibC::F_GETFL) !flags.bits_set? LibC::O_NONBLOCK @@ -131,14 +111,14 @@ module Crystal::System::FileDescriptor # Mark the handle open, since we had to have dup'd a live handle. @closed = false - evented_reopen + event_loop.close(self) end private def system_close # Perform libevent cleanup before LibC.close. # Using a file descriptor after it has been closed is never defined and can # always lead to undefined results. This is not specific to libevent. - evented_close + event_loop.close(self) file_descriptor_close end diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr index abec79a39441..612b1b1634ef 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/system/wasi/event_loop.cr @@ -36,6 +36,30 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop raise NotImplementedError.new("Crystal::Wasi::EventLoop.create_fd_read_event") end + def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 + file_descriptor.evented_read("Error reading file_descriptor") do + LibC.read(file_descriptor.fd, slice, slice.size).tap do |return_code| + if return_code == -1 && Errno.value == Errno::EBADF + raise IO::Error.new "File not open for reading", target: file_descriptor + end + end + end + end + + def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 + file_descriptor.evented_write("Error writing file_descriptor") do + LibC.write(file_descriptor.fd, slice, slice.size).tap do |return_code| + if return_code == -1 && Errno.value == Errno::EBADF + raise IO::Error.new "File not open for writing", target: file_descriptor + end + end + end + end + + def close(file_descriptor : Crystal::System::FileDescriptor) : Nil + file_descriptor.evented_close + end + def read(socket : ::Socket, slice : Bytes) : Int32 socket.evented_read("Error reading socket") do LibC.recv(socket.fd, slice, slice.size, 0).to_i32 diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index f433fb931c41..5148285f9837 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -149,6 +149,27 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop Crystal::Iocp::Event.new(fiber, timeout: true) end + def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 + handle = file_descriptor.windows_handle + file_descriptor.overlapped_operation(handle, "ReadFile", file_descriptor.read_timeout) do |overlapped| + ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped) + {ret, byte_count} + end.to_i32 + end + + def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 + handle = file_descriptor.windows_handle + + file_descriptor.overlapped_operation(handle, "WriteFile", file_descriptor.write_timeout, writing: true) do |overlapped| + ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped) + {ret, byte_count} + end.to_i32 + end + + def close(file_descriptor : Crystal::System::FileDescriptor) : Nil + LibC.CancelIoEx(file_descriptor.windows_handle, nil) unless file_descriptor.system_blocking? + end + private def wsa_buffer(bytes) wsabuf = LibC::WSABUF.new wsabuf.len = bytes.size diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 97f1242f2ef8..63aee6e1ecf7 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -26,10 +26,7 @@ module Crystal::System::FileDescriptor elsif system_blocking? read_blocking(handle, slice) else - overlapped_operation(handle, "ReadFile", read_timeout) do |overlapped| - ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped) - {ret, byte_count} - end.to_i32 + event_loop.read(self, slice) end end @@ -53,10 +50,7 @@ module Crystal::System::FileDescriptor if system_blocking? write_blocking(handle, slice).to_i32 else - overlapped_operation(handle, "WriteFile", write_timeout, writing: true) do |overlapped| - ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped) - {ret, byte_count} - end.to_i32 + event_loop.write(self, slice) end end @@ -75,7 +69,8 @@ module Crystal::System::FileDescriptor bytes_written end - private def system_blocking? + # :nodoc: + def system_blocking? @system_blocking end @@ -167,7 +162,7 @@ module Crystal::System::FileDescriptor end private def system_close - LibC.CancelIoEx(windows_handle, nil) unless system_blocking? + event_loop.close(self) file_descriptor_close end From 5aa2160584a8cb325b51739f1358c9541406563a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 6 Jun 2024 09:54:16 +0200 Subject: [PATCH 1161/1551] Remove `OverlappedOperation#synchronous` (#14663) --- src/crystal/system/win32/socket.cr | 4 ++-- src/io/overlapped.cr | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 294a9d675be0..9551ba0b0420 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -144,7 +144,7 @@ module Crystal::System::Socket return ::Socket::Error.from_os_error("ConnectEx", error) end else - operation.synchronous = true + operation.done! return nil end @@ -208,7 +208,7 @@ module Crystal::System::Socket return false end else - operation.synchronous = true + operation.done! return true end diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index e61494324f42..167ea1568941 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -73,7 +73,6 @@ module IO::Overlapped property next : OverlappedOperation? property previous : OverlappedOperation? @@canceled = Thread::LinkedList(OverlappedOperation).new - property? synchronous = false def self.run(handle, &) operation = OverlappedOperation.new @@ -128,7 +127,7 @@ module IO::Overlapped case @state when .started? yield @fiber.not_nil! - @state = :done + done! when .cancelled? @@canceled.delete(self) else @@ -141,17 +140,19 @@ module IO::Overlapped when .started? handle = LibC::HANDLE.new(handle) if handle.is_a?(LibC::SOCKET) - # Microsoft documentation: - # The application must not free or reuse the OVERLAPPED structure + # https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-cancelioex + # > The application must not free or reuse the OVERLAPPED structure # associated with the canceled I/O operations until they have completed - # (this does not apply to asynchronous operations that finished - # synchronously, as nothing would be queued to the IOCP) - if !synchronous? && LibC.CancelIoEx(handle, pointerof(@overlapped)) != 0 + if LibC.CancelIoEx(handle, pointerof(@overlapped)) != 0 @state = :cancelled @@canceled.push(self) # to increase lifetime end end end + + def done! + @state = :done + end end # Returns `false` if the operation timed out. @@ -190,7 +191,7 @@ module IO::Overlapped raise IO::Error.from_os_error(method, error, target: self) end else - operation.synchronous = true + operation.done! return value end @@ -222,7 +223,7 @@ module IO::Overlapped raise IO::Error.from_os_error(method, error, target: self) end else - operation.synchronous = true + operation.done! return value end From 434b0848245bde3f2706df497830529b3832caba Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Thu, 6 Jun 2024 16:51:48 +0200 Subject: [PATCH 1162/1551] OpenSSL: don't enable ECDH curves by default (#14656) Disables the ECDH curve configuration that was limiting the curve selection to less secure curves. follow up to #14655 closes #9600 --- src/openssl/ssl/context.cr | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr index 0a789d2e5176..d49310b0ec5a 100644 --- a/src/openssl/ssl/context.cr +++ b/src/openssl/ssl/context.cr @@ -130,8 +130,6 @@ abstract class OpenSSL::SSL::Context {% if LibSSL.has_method?(:x509_verify_param_lookup) %} self.default_verify_param = "ssl_client" {% end %} - - set_tmp_ecdh_key(curve: LibCrypto::NID_X9_62_prime256v1) end # Returns a new TLS server context with only the given method set. From 7c71635a66f696eb340c3ba2873e2054bd46217c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 7 Jun 2024 10:54:22 +0200 Subject: [PATCH 1163/1551] Drop unused methods in `IO::Evented` (#14666) --- src/io/evented.cr | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/io/evented.cr b/src/io/evented.cr index bd68d473d66a..57d71254f24b 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -49,15 +49,6 @@ module IO::Evented end end - def evented_send(errno_msg : String, &) : Int32 - bytes_written = yield - raise Socket::Error.from_errno(errno_msg) if bytes_written == -1 - # `to_i32` is acceptable because `Slice#size` is an Int32 - bytes_written.to_i32 - ensure - resume_pending_writers - end - # :nodoc: def resume_read(timed_out = false) : Nil @read_timed_out = timed_out @@ -126,10 +117,6 @@ module IO::Evented event.add timeout end - def evented_reopen : Nil - evented_close - end - def evented_close : Nil @read_event.consume_each &.free From 38be359252843bc3eaa5c21fec5117b11f0cb98b Mon Sep 17 00:00:00 2001 From: "Billy.Zheng" Date: Fri, 7 Jun 2024 16:55:13 +0800 Subject: [PATCH 1164/1551] Fix LLDB `crystal_formatters.py` for Python 3 (#14665) --- etc/lldb/crystal_formatters.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/lldb/crystal_formatters.py b/etc/lldb/crystal_formatters.py index fcbc7875c543..2a66369a02a4 100644 --- a/etc/lldb/crystal_formatters.py +++ b/etc/lldb/crystal_formatters.py @@ -57,6 +57,6 @@ def CrystalString_SummaryProvider(value, dict): def __lldb_init_module(debugger, dict): - debugger.HandleCommand('type synthetic add -l crystal_formatters.CrystalArraySyntheticProvider -x "^Array\(.+\)(\s*\**)?" -w Crystal') - debugger.HandleCommand('type summary add -F crystal_formatters.CrystalString_SummaryProvider -x "^(String|\(String \| Nil\))(\s*\**)?$" -w Crystal') - debugger.HandleCommand('type category enable Crystal') + debugger.HandleCommand(r'type synthetic add -l crystal_formatters.CrystalArraySyntheticProvider -x "^Array\(.+\)(\s*\**)?" -w Crystal') + debugger.HandleCommand(r'type summary add -F crystal_formatters.CrystalString_SummaryProvider -x "^(String|\(String \| Nil\))(\s*\**)?$" -w Crystal') + debugger.HandleCommand(r'type category enable Crystal') From 0ee82d3dd066b23d85eba244b49f9206af492c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 8 Jun 2024 22:09:04 +0200 Subject: [PATCH 1165/1551] Rename `Crystal::Iocp` to `Crystal::IOCP` (#14662) --- src/crystal/system/win32/event_loop_iocp.cr | 16 ++++++++-------- src/io/overlapped.cr | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 5148285f9837..1d43613c2b7a 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -4,14 +4,14 @@ require "crystal/system/print_error" # :nodoc: abstract class Crystal::EventLoop def self.create - Crystal::Iocp::EventLoop.new + Crystal::IOCP::EventLoop.new end end # :nodoc: -class Crystal::Iocp::EventLoop < Crystal::EventLoop +class Crystal::IOCP::EventLoop < Crystal::EventLoop # This is a list of resume and timeout events managed outside of IOCP. - @queue = Deque(Crystal::Iocp::Event).new + @queue = Deque(Crystal::IOCP::Event).new @lock = Crystal::SpinLock.new @interrupted = Atomic(Bool).new(false) @@ -130,23 +130,23 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop LibC.QueueUserAPC(->(ptr : LibC::ULONG_PTR) {}, thread, LibC::ULONG_PTR.new(0)) end - def enqueue(event : Crystal::Iocp::Event) + def enqueue(event : Crystal::IOCP::Event) unless @queue.includes?(event) @queue << event end end - def dequeue(event : Crystal::Iocp::Event) + def dequeue(event : Crystal::IOCP::Event) @queue.delete(event) end # Create a new resume event for a fiber. def create_resume_event(fiber : Fiber) : Crystal::EventLoop::Event - Crystal::Iocp::Event.new(fiber) + Crystal::IOCP::Event.new(fiber) end def create_timeout_event(fiber) : Crystal::EventLoop::Event - Crystal::Iocp::Event.new(fiber, timeout: true) + Crystal::IOCP::Event.new(fiber, timeout: true) end def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 @@ -282,7 +282,7 @@ class Crystal::Iocp::EventLoop < Crystal::EventLoop end end -class Crystal::Iocp::Event +class Crystal::IOCP::Event include Crystal::EventLoop::Event getter fiber diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index 167ea1568941..20a0313c9379 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -158,10 +158,10 @@ module IO::Overlapped # Returns `false` if the operation timed out. def schedule_overlapped(timeout : Time::Span?, line = __LINE__) : Bool if timeout - timeout_event = Crystal::Iocp::Event.new(Fiber.current) + timeout_event = Crystal::IOCP::Event.new(Fiber.current) timeout_event.add(timeout) else - timeout_event = Crystal::Iocp::Event.new(Fiber.current, Time::Span::MAX) + timeout_event = Crystal::IOCP::Event.new(Fiber.current, Time::Span::MAX) end # memoize event loop to make sure that we still target the same instance # after wakeup (guaranteed by current MT model but let's be future proof) From 4973f819d5f4efe88cd6b0c2806762a3abb8404c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 8 Jun 2024 22:09:19 +0200 Subject: [PATCH 1166/1551] Fix `IO#same_content?` accepting prefix on second stream (#14664) --- spec/std/io/io_spec.cr | 24 ++++++++++++++++++++++++ src/io.cr | 5 ++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index b73640e61918..6974a9fe3466 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -461,6 +461,30 @@ describe IO do io2 = IO::Memory.new("hella") IO.same_content?(io2, io1).should be_false end + + it "refutes prefix match, one way" do + io1 = OneByOneIO.new("hello") + io2 = IO::Memory.new("hello again") + IO.same_content?(io1, io2).should be_false + end + + it "refutes prefix match, second way" do + io1 = IO::Memory.new("hello") + io2 = OneByOneIO.new("hello again") + IO.same_content?(io1, io2).should be_false + end + + it "refutes prefix match, one way" do + io1 = OneByOneIO.new("hello again") + io2 = IO::Memory.new("hello") + IO.same_content?(io1, io2).should be_false + end + + it "refutes prefix match, second way" do + io1 = IO::Memory.new("hello again") + io2 = OneByOneIO.new("hello") + IO.same_content?(io1, io2).should be_false + end end end diff --git a/src/io.cr b/src/io.cr index c159c77776b5..5276f3b7f304 100644 --- a/src/io.cr +++ b/src/io.cr @@ -1219,11 +1219,14 @@ abstract class IO while true read1 = stream1.read(buf1.to_slice) + if read1.zero? + # First stream is EOF, check if the second has more. + return stream2.read_byte.nil? + end read2 = stream2.read_fully?(buf2.to_slice[0, read1]) return false unless read2 return false if buf1.to_unsafe.memcmp(buf2.to_unsafe, read1) != 0 - return true if read1 == 0 end end From 7710660ea61893ae8c90321d794e925cd2836212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 8 Jun 2024 22:09:35 +0200 Subject: [PATCH 1167/1551] Update distribution-scripts (#14648) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d473a86dcda4..d0ad974b4f20 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "a9e0c6c12987b8b01b17e71c38f489e45937e1bf" + default: "7a013f14ed64e7e569b5e453eab02af63cf62b61" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From 1dda526bd07acce261f6192c186a3bbc0e8751ed Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sun, 9 Jun 2024 22:12:10 +0200 Subject: [PATCH 1168/1551] Use `dup3` and `pipe2` to set `O_CLOEXEC` when available (#14673) --- spec/std/io/file_descriptor_spec.cr | 59 ++++++++++++++++++++++ src/crystal/system/unix/file_descriptor.cr | 29 ++++++----- src/lib_c/aarch64-android/c/unistd.cr | 2 + src/lib_c/aarch64-linux-gnu/c/unistd.cr | 2 + src/lib_c/aarch64-linux-musl/c/unistd.cr | 2 + src/lib_c/arm-linux-gnueabihf/c/unistd.cr | 2 + src/lib_c/i386-linux-gnu/c/unistd.cr | 2 + src/lib_c/i386-linux-musl/c/unistd.cr | 2 + src/lib_c/x86_64-dragonfly/c/unistd.cr | 2 + src/lib_c/x86_64-freebsd/c/unistd.cr | 2 + src/lib_c/x86_64-linux-gnu/c/unistd.cr | 2 + src/lib_c/x86_64-linux-musl/c/unistd.cr | 2 + src/lib_c/x86_64-netbsd/c/unistd.cr | 2 + src/lib_c/x86_64-openbsd/c/unistd.cr | 2 + src/lib_c/x86_64-solaris/c/unistd.cr | 2 + 15 files changed, 100 insertions(+), 14 deletions(-) diff --git a/spec/std/io/file_descriptor_spec.cr b/spec/std/io/file_descriptor_spec.cr index 8433e28689e2..f64d5fd2d8b9 100644 --- a/spec/std/io/file_descriptor_spec.cr +++ b/spec/std/io/file_descriptor_spec.cr @@ -103,6 +103,65 @@ describe IO::FileDescriptor do end end + {% unless flag?(:win32) %} + describe "close_on_exec" do + it "sets close on exec on the reopened standard descriptors" do + unless STDIN.fd == Crystal::System::FileDescriptor::STDIN_HANDLE + STDIN.close_on_exec?.should be_true + end + + unless STDOUT.fd == Crystal::System::FileDescriptor::STDOUT_HANDLE + STDOUT.close_on_exec?.should be_true + end + + unless STDERR.fd == Crystal::System::FileDescriptor::STDERR_HANDLE + STDERR.close_on_exec?.should be_true + end + end + + it "is enabled by default (open)" do + File.open(datapath("test_file.txt")) do |file| + file.close_on_exec?.should be_true + end + end + + it "is enabled by default (pipe)" do + IO::FileDescriptor.pipe.each do |fd| + fd.close_on_exec?.should be_true + fd.close_on_exec?.should be_true + end + end + + it "can be disabled and reenabled" do + File.open(datapath("test_file.txt")) do |file| + file.close_on_exec = false + file.close_on_exec?.should be_false + + file.close_on_exec = true + file.close_on_exec?.should be_true + end + end + + it "is copied on reopen" do + File.open(datapath("test_file.txt")) do |file1| + file1.close_on_exec = true + + File.open(datapath("test_file.ini")) do |file2| + file2.reopen(file1) + file2.close_on_exec?.should be_true + end + + file1.close_on_exec = false + + File.open(datapath("test_file.ini")) do |file3| + file3.reopen(file1) + file3.close_on_exec?.should be_false + end + end + end + end + {% end %} + typeof(STDIN.noecho { }) typeof(STDIN.noecho!) typeof(STDIN.echo { }) diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 4992bfbeab4c..765f7a989f3d 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -91,21 +91,16 @@ module Crystal::System::FileDescriptor end private def system_reopen(other : IO::FileDescriptor) - {% if LibC.has_method?("dup3") %} - # dup doesn't copy the CLOEXEC flag, so copy it manually using dup3 + {% if LibC.has_method?(:dup3) %} flags = other.close_on_exec? ? LibC::O_CLOEXEC : 0 if LibC.dup3(other.fd, fd, flags) == -1 raise IO::Error.from_errno("Could not reopen file descriptor") end {% else %} - # dup doesn't copy the CLOEXEC flag, copy it manually to the new if LibC.dup2(other.fd, fd) == -1 raise IO::Error.from_errno("Could not reopen file descriptor") end - - if other.close_on_exec? - self.close_on_exec = true - end + self.close_on_exec = other.close_on_exec? {% end %} # Mark the handle open, since we had to have dup'd a live handle. @@ -194,14 +189,21 @@ module Crystal::System::FileDescriptor def self.pipe(read_blocking, write_blocking) pipe_fds = uninitialized StaticArray(LibC::Int, 2) - if LibC.pipe(pipe_fds) != 0 - raise IO::Error.from_errno("Could not create pipe") - end + + {% if LibC.has_method?(:pipe2) %} + if LibC.pipe2(pipe_fds, LibC::O_CLOEXEC) != 0 + raise IO::Error.from_errno("Could not create pipe") + end + {% else %} + if LibC.pipe(pipe_fds) != 0 + raise IO::Error.from_errno("Could not create pipe") + end + fcntl(pipe_fds[0], LibC::F_SETFD, LibC::FD_CLOEXEC) + fcntl(pipe_fds[1], LibC::F_SETFD, LibC::FD_CLOEXEC) + {% end %} r = IO::FileDescriptor.new(pipe_fds[0], read_blocking) w = IO::FileDescriptor.new(pipe_fds[1], write_blocking) - r.close_on_exec = true - w.close_on_exec = true w.sync = true {r, w} @@ -227,12 +229,11 @@ module Crystal::System::FileDescriptor ret = LibC.ttyname_r(fd, path, 256) return IO::FileDescriptor.new(fd).tap(&.flush_on_newline=(true)) unless ret == 0 - clone_fd = LibC.open(path, LibC::O_RDWR) + clone_fd = LibC.open(path, LibC::O_RDWR | LibC::O_CLOEXEC) return IO::FileDescriptor.new(fd).tap(&.flush_on_newline=(true)) if clone_fd == -1 # We don't buffer output for TTY devices to see their output right away io = IO::FileDescriptor.new(clone_fd) - io.close_on_exec = true io.sync = true io end diff --git a/src/lib_c/aarch64-android/c/unistd.cr b/src/lib_c/aarch64-android/c/unistd.cr index bb8f78e8d1d7..8d467b2ed65e 100644 --- a/src/lib_c/aarch64-android/c/unistd.cr +++ b/src/lib_c/aarch64-android/c/unistd.cr @@ -17,6 +17,7 @@ lib LibC fun fchown(__fd : Int, __owner : UidT, __group : GidT) : Int fun close(__fd : Int) : Int fun dup2(__old_fd : Int, __new_fd : Int) : Int + fun dup3(__old_fd : Int, __new_fd : Int, __flags : Int) : Int fun _exit(__status : Int) : NoReturn fun execvp(__file : Char*, __argv : Char**) : Int fun fdatasync(__fd : Int) : Int @@ -40,6 +41,7 @@ lib LibC {% end %} fun lseek(__fd : Int, __offset : OffT, __whence : Int) : OffT fun pipe(__fds : Int[2]) : Int + fun pipe2(__fds : Int[2], __flags : Int) : Int fun read(__fd : Int, __buf : Void*, __count : SizeT) : SSizeT fun pread(__fd : Int, __buf : Void*, __count : SizeT, __offset : OffT) : SSizeT fun rmdir(__path : Char*) : Int diff --git a/src/lib_c/aarch64-linux-gnu/c/unistd.cr b/src/lib_c/aarch64-linux-gnu/c/unistd.cr index 1d6cbc3d0b3a..d1cf1f8b55a8 100644 --- a/src/lib_c/aarch64-linux-gnu/c/unistd.cr +++ b/src/lib_c/aarch64-linux-gnu/c/unistd.cr @@ -17,6 +17,7 @@ lib LibC fun fchown(x0 : Int, x1 : UidT, x2 : GidT) : Int fun close(fd : Int) : Int fun dup2(fd : Int, fd2 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(status : Int) : NoReturn fun execvp(file : Char*, argv : Char**) : Int fun fdatasync(fd : Int) : Int @@ -38,6 +39,7 @@ lib LibC fun lockf(fd : Int, cmd : Int, len : OffT) : Int fun lseek(fd : Int, offset : OffT, whence : Int) : OffT fun pipe(pipedes : StaticArray(Int, 2)) : Int + fun pipe2(pipedes : StaticArray(Int, 2), flags : Int) : Int fun read(fd : Int, buf : Void*, nbytes : SizeT) : SSizeT fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(path : Char*) : Int diff --git a/src/lib_c/aarch64-linux-musl/c/unistd.cr b/src/lib_c/aarch64-linux-musl/c/unistd.cr index d9626788ffdc..574f87c3ca1b 100644 --- a/src/lib_c/aarch64-linux-musl/c/unistd.cr +++ b/src/lib_c/aarch64-linux-musl/c/unistd.cr @@ -17,6 +17,7 @@ lib LibC fun fchown(x0 : Int, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(x0 : Int, x1 : Int, x2 : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int fun fdatasync(fd : Int) : Int @@ -38,6 +39,7 @@ lib LibC fun lockf(x0 : Int, x1 : Int, x2 : OffT) : Int fun lseek(x0 : Int, x1 : OffT, x2 : Int) : OffT fun pipe(x0 : StaticArray(Int, 2)) : Int + fun pipe2(x0 : StaticArray(Int, 2), x1 : Int) : Int fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(x0 : Char*) : Int diff --git a/src/lib_c/arm-linux-gnueabihf/c/unistd.cr b/src/lib_c/arm-linux-gnueabihf/c/unistd.cr index 67dac4992d0f..3769ac2f46ee 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/unistd.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/unistd.cr @@ -17,6 +17,7 @@ lib LibC fun fchown(fd : Int, owner : UidT, group : GidT) : Int fun close(fd : Int) : Int fun dup2(fd : Int, fd2 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(status : Int) : NoReturn fun execvp(file : Char*, argv : Char**) : Int fun fdatasync(fd : Int) : Int @@ -38,6 +39,7 @@ lib LibC fun lockf(fd : Int, cmd : Int, len : OffT) : Int fun lseek(fd : Int, offset : OffT, whence : Int) : OffT fun pipe(pipedes : StaticArray(Int, 2)) : Int + fun pipe2(pipedes : StaticArray(Int, 2), flags : Int) : Int fun read(fd : Int, buf : Void*, nbytes : SizeT) : SSizeT fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(path : Char*) : Int diff --git a/src/lib_c/i386-linux-gnu/c/unistd.cr b/src/lib_c/i386-linux-gnu/c/unistd.cr index 143bb25062e7..8b2c2d477a31 100644 --- a/src/lib_c/i386-linux-gnu/c/unistd.cr +++ b/src/lib_c/i386-linux-gnu/c/unistd.cr @@ -17,6 +17,7 @@ lib LibC fun fchown(fd : Int, owner : UidT, group : GidT) : Int fun close(fd : Int) : Int fun dup2(fd : Int, fd2 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(status : Int) : NoReturn fun execvp(file : Char*, argv : Char**) : Int fun fdatasync(fd : Int) : Int @@ -38,6 +39,7 @@ lib LibC fun lockf = lockf64(fd : Int, cmd : Int, len : OffT) : Int fun lseek = lseek64(fd : Int, offset : OffT, whence : Int) : OffT fun pipe(pipedes : StaticArray(Int, 2)) : Int + fun pipe2(pipedes : StaticArray(Int, 2), flags : Int) : Int fun read(fd : Int, buf : Void*, nbytes : SizeT) : SSizeT fun pread = pread64(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(path : Char*) : Int diff --git a/src/lib_c/i386-linux-musl/c/unistd.cr b/src/lib_c/i386-linux-musl/c/unistd.cr index 2748bcf83752..3b43b95e4123 100644 --- a/src/lib_c/i386-linux-musl/c/unistd.cr +++ b/src/lib_c/i386-linux-musl/c/unistd.cr @@ -17,6 +17,7 @@ lib LibC fun fchown(x0 : Int, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(x0 : Int, x1 : Int, x2 : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int fun fdatasync(x0 : Int) : Int @@ -38,6 +39,7 @@ lib LibC fun lockf(x0 : Int, x1 : Int, x2 : OffT) : Int fun lseek(x0 : Int, x1 : OffT, x2 : Int) : OffT fun pipe(x0 : StaticArray(Int, 2)) : Int + fun pipe2(x0 : StaticArray(Int, 2), flags : Int) : Int fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(x0 : Char*) : Int diff --git a/src/lib_c/x86_64-dragonfly/c/unistd.cr b/src/lib_c/x86_64-dragonfly/c/unistd.cr index 8ce61f792735..c5419e9cb45a 100644 --- a/src/lib_c/x86_64-dragonfly/c/unistd.cr +++ b/src/lib_c/x86_64-dragonfly/c/unistd.cr @@ -16,6 +16,7 @@ lib LibC fun fchown(x0 : Int, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(x0 : Int, x1 : Int, x2 : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int @[ReturnsTwice] @@ -36,6 +37,7 @@ lib LibC fun lockf(x0 : Int, x1 : Int, x2 : OffT) : Int fun lseek(x0 : Int, x1 : OffT, x2 : Int) : OffT fun pipe(x0 : Int*) : Int + fun pipe2(x0 : Int*, x1 : Int) : Int fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(x0 : Char*) : Int diff --git a/src/lib_c/x86_64-freebsd/c/unistd.cr b/src/lib_c/x86_64-freebsd/c/unistd.cr index af5283c15d16..d2b8c1033cfa 100644 --- a/src/lib_c/x86_64-freebsd/c/unistd.cr +++ b/src/lib_c/x86_64-freebsd/c/unistd.cr @@ -16,6 +16,7 @@ lib LibC fun fchown(x0 : Int, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(x0 : Int, x1 : Int, x2 : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int fun fdatasync(fd : Int) : Int @@ -37,6 +38,7 @@ lib LibC fun lockf(x0 : Int, x1 : Int, x2 : OffT) : Int fun lseek(x0 : Int, x1 : OffT, x2 : Int) : OffT fun pipe(x0 : Int*) : Int + fun pipe2(x0 : Int*, x1 : Int) : Int fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(x0 : Char*) : Int diff --git a/src/lib_c/x86_64-linux-gnu/c/unistd.cr b/src/lib_c/x86_64-linux-gnu/c/unistd.cr index 9abd25899316..80f37f90689a 100644 --- a/src/lib_c/x86_64-linux-gnu/c/unistd.cr +++ b/src/lib_c/x86_64-linux-gnu/c/unistd.cr @@ -17,6 +17,7 @@ lib LibC fun fchown(fd : Int, owner : UidT, group : GidT) : Int fun close(fd : Int) : Int fun dup2(fd : Int, fd2 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(status : Int) : NoReturn fun execvp(file : Char*, argv : Char**) : Int fun fdatasync(fd : Int) : Int @@ -38,6 +39,7 @@ lib LibC fun lockf(fd : Int, cmd : Int, len : OffT) : Int fun lseek(fd : Int, offset : OffT, whence : Int) : OffT fun pipe(pipedes : StaticArray(Int, 2)) : Int + fun pipe2(pipedes : StaticArray(Int, 2), flags : Int) : Int fun read(fd : Int, buf : Void*, nbytes : SizeT) : SSizeT fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(path : Char*) : Int diff --git a/src/lib_c/x86_64-linux-musl/c/unistd.cr b/src/lib_c/x86_64-linux-musl/c/unistd.cr index 2748bcf83752..3b43b95e4123 100644 --- a/src/lib_c/x86_64-linux-musl/c/unistd.cr +++ b/src/lib_c/x86_64-linux-musl/c/unistd.cr @@ -17,6 +17,7 @@ lib LibC fun fchown(x0 : Int, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(x0 : Int, x1 : Int, x2 : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int fun fdatasync(x0 : Int) : Int @@ -38,6 +39,7 @@ lib LibC fun lockf(x0 : Int, x1 : Int, x2 : OffT) : Int fun lseek(x0 : Int, x1 : OffT, x2 : Int) : OffT fun pipe(x0 : StaticArray(Int, 2)) : Int + fun pipe2(x0 : StaticArray(Int, 2), flags : Int) : Int fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(x0 : Char*) : Int diff --git a/src/lib_c/x86_64-netbsd/c/unistd.cr b/src/lib_c/x86_64-netbsd/c/unistd.cr index 081a6e9f7b00..f137fb31bd45 100644 --- a/src/lib_c/x86_64-netbsd/c/unistd.cr +++ b/src/lib_c/x86_64-netbsd/c/unistd.cr @@ -16,6 +16,7 @@ lib LibC fun fchown = __posix_fchown(x0 : Int, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(x0 : Int, x1 : Int, x2 : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int fun fdatasync(x0 : Int) : Int @@ -37,6 +38,7 @@ lib LibC fun lockf(x0 : Int, x1 : Int, x2 : OffT) : Int fun lseek(x0 : Int, x1 : OffT, x2 : Int) : OffT fun pipe(x0 : Int*) : Int + fun pipe2(x0 : Int*, x1 : Int) : Int fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(x0 : Char*) : Int diff --git a/src/lib_c/x86_64-openbsd/c/unistd.cr b/src/lib_c/x86_64-openbsd/c/unistd.cr index ef48b3b5af92..ebc1446daef3 100644 --- a/src/lib_c/x86_64-openbsd/c/unistd.cr +++ b/src/lib_c/x86_64-openbsd/c/unistd.cr @@ -16,6 +16,7 @@ lib LibC fun fchown(x0 : Int, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(x0 : Int, x1 : Int, x2 : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int fun fdatasync(x0 : Int) : Int @@ -37,6 +38,7 @@ lib LibC fun lockf(x0 : Int, x1 : Int, x2 : OffT) : Int fun lseek(x0 : Int, x1 : OffT, x2 : Int) : OffT fun pipe(x0 : Int*) : Int + fun pipe2(x0 : Int*, x1 : Int) : Int fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(x0 : Char*) : Int diff --git a/src/lib_c/x86_64-solaris/c/unistd.cr b/src/lib_c/x86_64-solaris/c/unistd.cr index 0c394df08b2e..61ee084df37c 100644 --- a/src/lib_c/x86_64-solaris/c/unistd.cr +++ b/src/lib_c/x86_64-solaris/c/unistd.cr @@ -18,6 +18,7 @@ lib LibC fun fchown(x0 : Int, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(x0 : Int, x1 : Int, x2 : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int fun fdatasync(fd : Int) : Int @@ -40,6 +41,7 @@ lib LibC fun lockf(x0 : Int, x1 : Int, x2 : OffT) : Int fun lseek(x0 : Int, x1 : OffT, x2 : Int) : OffT fun pipe(x0 : Int*) : Int + fun pipe2(x0 : Int*, flags : Int) : Int fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT fun rmdir(x0 : Char*) : Int From 835b0c7ec5945c48ddd65f856cd81338ccbe7f65 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sun, 9 Jun 2024 22:12:20 +0200 Subject: [PATCH 1169/1551] OpenSSL: deprecate Mozilla's TLS Server recommendation (#14657) --- scripts/generate_ssl_server_defaults.cr | 2 ++ src/openssl/ssl/context.cr | 36 +++++++++++-------------- src/openssl/ssl/defaults.cr | 8 +++++- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/scripts/generate_ssl_server_defaults.cr b/scripts/generate_ssl_server_defaults.cr index 7383faefd2b3..7e49e7795abd 100755 --- a/scripts/generate_ssl_server_defaults.cr +++ b/scripts/generate_ssl_server_defaults.cr @@ -56,6 +56,7 @@ File.open(DEFAULTS_FILE, "w") do |file| # available at #{guidelines.href}. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. + @[Deprecated("Deprecated with no replacement. Prefer setting a security level, global system configuration, or build your own from https://ssl-config.mozilla.org")] CIPHERS_#{level.upcase} = "#{all_ciphers.join(":")}" # The list of secure ciphersuites on **#{level}** compatibility level as per Mozilla @@ -68,6 +69,7 @@ File.open(DEFAULTS_FILE, "w") do |file| # available at #{guidelines.href}. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. + @[Deprecated("Deprecated with no replacement. Prefer setting a security level, global system configuration, or build your own from https://ssl-config.mozilla.org")] CIPHER_SUITES_#{level.upcase} = "#{ciphersuites.join(":")}" CRYSTAL end diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr index d49310b0ec5a..c7d5b5a0de2a 100644 --- a/src/openssl/ssl/context.cr +++ b/src/openssl/ssl/context.cr @@ -320,36 +320,30 @@ abstract class OpenSSL::SSL::Context end # Sets the current ciphers and ciphers suites to **modern** compatibility level as per Mozilla - # recommendations. See `CIPHERS_MODERN` and `CIPHER_SUITES_MODERN`. See `#security_level=` for some - # sensible system configuration. + # recommendations. See `#security_level=` for some sensible system configuration. + # + # WARNING: Does nothing as of Crystal 1.13. + # WARNING: Didn't work as expected as of OpenSSL 1.1 (didn't configure TLSv1.2 and below). + @[Deprecated("Deprecated with no replacement. Prefer #security_level, global system configuration or build your own from https://wiki.mozilla.org/Security/Server_Side_TLS")] def set_modern_ciphers - {% if LibSSL.has_method?(:ssl_ctx_set_ciphersuites) %} - self.cipher_suites = CIPHER_SUITES_MODERN - {% else %} - self.ciphers = CIPHERS_MODERN - {% end %} end # Sets the current ciphers and ciphers suites to **intermediate** compatibility level as per Mozilla - # recommendations. See `CIPHERS_INTERMEDIATE` and `CIPHER_SUITES_INTERMEDIATE`. See `#security_level=` for some - # sensible system configuration. + # recommendations. See `#security_level=` for some sensible system configuration. + # + # WARNING: Does nothing as of Crystal 1.13. + # WARNING: Didn't work as expected as of OpenSSL 1.1 (didn't configure TLSv1.2 and below). + @[Deprecated("Deprecated with no replacement. Prefer #security_level, global system configuration or build your own from https://wiki.mozilla.org/Security/Server_Side_TLS")] def set_intermediate_ciphers - {% if LibSSL.has_method?(:ssl_ctx_set_ciphersuites) %} - self.cipher_suites = CIPHER_SUITES_INTERMEDIATE - {% else %} - self.ciphers = CIPHERS_INTERMEDIATE - {% end %} end # Sets the current ciphers and ciphers suites to **old** compatibility level as per Mozilla - # recommendations. See `CIPHERS_OLD` and `CIPHER_SUITES_OLD`. See `#security_level=` for some - # sensible system configuration. + # recommendations. See `#security_level=` for some sensible system configuration. + # + # WARNING: Does nothing as of Crystal 1.13. + # WARNING: Didn't work as expected as of OpenSSL 1.1 (didn't configure TLSv1.2 and below). + @[Deprecated("Deprecated with no replacement. Prefer #security_level, global system configuration or build your own from https://wiki.mozilla.org/Security/Server_Side_TLS")] def set_old_ciphers - {% if LibSSL.has_method?(:ssl_ctx_set_ciphersuites) %} - self.cipher_suites = CIPHER_SUITES_OLD - {% else %} - self.ciphers = CIPHERS_OLD - {% end %} end # Returns the security level used by this TLS context. diff --git a/src/openssl/ssl/defaults.cr b/src/openssl/ssl/defaults.cr index 0803a1107f46..1a33eb8e1264 100644 --- a/src/openssl/ssl/defaults.cr +++ b/src/openssl/ssl/defaults.cr @@ -1,5 +1,5 @@ # THIS FILE WAS AUTOMATICALLY GENERATED BY scripts/generate_ssl_server_defaults.cr -# on 2023-07-21 10:32:46 UTC. +# on 2024-06-07 09:20:33 UTC. abstract class OpenSSL::SSL::Context # The list of secure ciphers on **modern** compatibility level as per Mozilla @@ -19,6 +19,7 @@ abstract class OpenSSL::SSL::Context # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. + @[Deprecated("Deprecated with no replacement. Prefer setting a security level, global system configuration, or build your own from https://ssl-config.mozilla.org")] CIPHERS_MODERN = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS" # The list of secure ciphersuites on **modern** compatibility level as per Mozilla @@ -38,6 +39,7 @@ abstract class OpenSSL::SSL::Context # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. + @[Deprecated("Deprecated with no replacement. Prefer setting a security level, global system configuration, or build your own from https://ssl-config.mozilla.org")] CIPHER_SUITES_MODERN = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" # The list of secure ciphers on **intermediate** compatibility level as per Mozilla @@ -58,6 +60,7 @@ abstract class OpenSSL::SSL::Context # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. + @[Deprecated("Deprecated with no replacement. Prefer setting a security level, global system configuration, or build your own from https://ssl-config.mozilla.org")] CIPHERS_INTERMEDIATE = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS" # The list of secure ciphersuites on **intermediate** compatibility level as per Mozilla @@ -78,6 +81,7 @@ abstract class OpenSSL::SSL::Context # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. + @[Deprecated("Deprecated with no replacement. Prefer setting a security level, global system configuration, or build your own from https://ssl-config.mozilla.org")] CIPHER_SUITES_INTERMEDIATE = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" # The list of secure ciphers on **old** compatibility level as per Mozilla @@ -98,6 +102,7 @@ abstract class OpenSSL::SSL::Context # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. + @[Deprecated("Deprecated with no replacement. Prefer setting a security level, global system configuration, or build your own from https://ssl-config.mozilla.org")] CIPHERS_OLD = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS" # The list of secure ciphersuites on **old** compatibility level as per Mozilla @@ -118,5 +123,6 @@ abstract class OpenSSL::SSL::Context # available at https://ssl-config.mozilla.org/guidelines/5.7.json. # # See https://wiki.mozilla.org/Security/Server_Side_TLS for details. + @[Deprecated("Deprecated with no replacement. Prefer setting a security level, global system configuration, or build your own from https://ssl-config.mozilla.org")] CIPHER_SUITES_OLD = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" end From 897bd10f260dd540257348d0461b25d3fff97461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 10 Jun 2024 17:29:54 +0200 Subject: [PATCH 1170/1551] Unify `EventLoop.create` (#14661) --- src/crystal/system/event_loop.cr | 12 +++++++++++- src/crystal/system/unix/event_loop_libevent.cr | 7 ------- src/crystal/system/wasi/event_loop.cr | 7 ------- src/crystal/system/win32/event_loop_iocp.cr | 7 ------- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index d2d8689d4d55..46954e6034ff 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -1,6 +1,16 @@ abstract class Crystal::EventLoop # Creates an event loop instance - # def self.create : Crystal::EventLoop + def self.create : self + {% if flag?(:wasi) %} + Crystal::Wasi::EventLoop.new + {% elsif flag?(:unix) %} + Crystal::LibEvent::EventLoop.new + {% elsif flag?(:win32) %} + Crystal::IOCP::EventLoop.new + {% else %} + {% raise "Event loop not supported" %} + {% end %} + end @[AlwaysInline] def self.current : self diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr index 8877b4f40273..0ae968c7a15f 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/system/unix/event_loop_libevent.cr @@ -1,12 +1,5 @@ require "./event_libevent" -# :nodoc: -abstract class Crystal::EventLoop - def self.create - Crystal::LibEvent::EventLoop.new - end -end - # :nodoc: class Crystal::LibEvent::EventLoop < Crystal::EventLoop private getter(event_base) { Crystal::LibEvent::Event::Base.new } diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr index 612b1b1634ef..5aaf54452571 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/system/wasi/event_loop.cr @@ -1,10 +1,3 @@ -# :nodoc: -abstract class Crystal::EventLoop - def self.create - Crystal::Wasi::EventLoop.new - end -end - # :nodoc: class Crystal::Wasi::EventLoop < Crystal::EventLoop # Runs the event loop. diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 1d43613c2b7a..c25a3a68ea38 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -1,13 +1,6 @@ require "c/ioapiset" require "crystal/system/print_error" -# :nodoc: -abstract class Crystal::EventLoop - def self.create - Crystal::IOCP::EventLoop.new - end -end - # :nodoc: class Crystal::IOCP::EventLoop < Crystal::EventLoop # This is a list of resume and timeout events managed outside of IOCP. From c289f45f25266c8898412da01dd877da3f8c8601 Mon Sep 17 00:00:00 2001 From: Philipp Schulz Date: Tue, 11 Jun 2024 12:16:12 +0200 Subject: [PATCH 1171/1551] Add `TypeNode#private?`, `#public?` and `#visibility` (#11696) --- spec/compiler/macro/macro_methods_spec.cr | 57 +++++++++++++++++++++++ src/compiler/crystal/macros.cr | 12 +++++ src/compiler/crystal/macros/methods.cr | 12 +++++ 3 files changed, 81 insertions(+) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 493fd070744a..4f5ebf299677 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2345,6 +2345,63 @@ module Crystal {x: TypeNode.new(program.int32)} end end + + describe "executes private?" do + it false do + assert_macro("{{x.private?}}", "false") do |program| + klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"]) + + {x: TypeNode.new(klass)} + end + end + + it true do + assert_macro("{{x.private?}}", "true") do |program| + klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"]) + klass.private = true + + {x: TypeNode.new(klass)} + end + end + end + + describe "public?" do + it false do + assert_macro("{{x.public?}}", "false") do |program| + klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"]) + klass.private = true + + {x: TypeNode.new(klass)} + end + end + + it true do + assert_macro("{{x.public?}}", "true") do |program| + klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"]) + + {x: TypeNode.new(klass)} + end + end + end + + describe "visibility" do + it :public do + assert_macro("{{x.visibility}}", ":public") do |program| + klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"]) + + {x: TypeNode.new(klass)} + end + end + + it :private do + assert_macro("{{x.visibility}}", ":private") do |program| + klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"]) + klass.private = true + + {x: TypeNode.new(klass)} + end + end + end end describe "type declaration methods" do diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index d1db83a8366f..c0d4f6e0a071 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2824,6 +2824,18 @@ module Crystal::Macros def resolve? : TypeNode end + # Return `true` if `self` is private and `false` otherwise. + def private? : BoolLiteral + end + + # Return `true` if `self` is public and `false` otherwise. + def public? : BoolLiteral + end + + # Returns visibility of `self` as `:public` or `:private?` + def visibility : SymbolLiteral + end + # Returns `true` if *other* is an ancestor of `self`. def <(other : TypeNode) : BoolLiteral end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 2c45f49701fd..10e091e3a456 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1986,6 +1986,18 @@ module Crystal interpret_check_args { self } when "resolve?" interpret_check_args { self } + when "private?" + interpret_check_args { BoolLiteral.new(type.private?) } + when "public?" + interpret_check_args { BoolLiteral.new(!type.private?) } + when "visibility" + interpret_check_args do + if type.private? + SymbolLiteral.new("private") + else + SymbolLiteral.new("public") + end + end else super end From 2d71e3546f700b5ade54d10d455da69f78adb65f Mon Sep 17 00:00:00 2001 From: summer-alice <77705195+summer-alice@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:19:28 +1000 Subject: [PATCH 1172/1551] Add `Array#insert_all` (#14486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Array#insert_all` allows to insert all elements from an indexable at a given index. While this is already possible using range assignment, the syntax is not very obvious and is prone to unintended side-effects with an inclusive range. ```crystal arr = [1, 2, 6] # arr[2..2] = [3, 4, 5] # => [1, 2, 3, 4, 5] arr[2...2] = [3, 4, 5] # => [1, 2, 3, 4, 5, 6] ``` The new method would simplify it to: ```crystal arr = [1, 2, 6] arr.insert_all(2, [3, 4, 5]) # => [1, 2, 3, 4, 5, 6] ``` Co-authored-by: Johannes Müller --- spec/std/array_spec.cr | 70 +++++++++++++++++++++++++++++++++++ src/array.cr | 83 ++++++++++++++++++++++++++++++++---------- 2 files changed, 133 insertions(+), 20 deletions(-) diff --git a/spec/std/array_spec.cr b/spec/std/array_spec.cr index d118125ccd9a..0f44cfb6774e 100644 --- a/spec/std/array_spec.cr +++ b/spec/std/array_spec.cr @@ -873,6 +873,76 @@ describe "Array" do end end + describe "insert_all" do + it "inserts with index 0" do + a = [2, 3] + a.insert_all(0, [0, 1]).should be a + a.should eq([0, 1, 2, 3]) + end + + it "inserts with positive index" do + a = [1, 2, 5, 6] + a.insert_all(2, [3, 4]).should be a + a.should eq([1, 2, 3, 4, 5, 6]) + end + + it "inserts with index of #size" do + a = [1, 2, 3] + a.insert_all(a.size, [4, 5]).should be a + a.should eq([1, 2, 3, 4, 5]) + end + + it "inserts with negative index" do + a = [1, 2, 3] + a.insert_all(-1, [4, 5]).should be a + a.should eq([1, 2, 3, 4, 5]) + end + + it "inserts with negative index (2)" do + a = [1, 2, 5, 6] + a.insert_all(-3, [3, 4]).should be a + a.should eq([1, 2, 3, 4, 5, 6]) + end + + it "inserts when empty" do + a = [] of Int32 + a.insert_all(0, [1, 2, 3]).should be a + a.should eq([1, 2, 3]) + end + + it "inserts when other is empty" do + a = [1, 2, 3] + a.insert_all(1, [] of Int32).should be a + a.should eq([1, 2, 3]) + end + + it "raises with index greater than size" do + a = [1, 2, 3] + expect_raises IndexError do + a.insert_all(4, [4, 5]) + end + end + + it "raises with negative index greater than size" do + a = [1, 2, 3] + expect_raises IndexError do + a.insert_all(-5, [4, 5]) + end + end + + it "inserts indexable" do + a = [1, 9, 10] + a.insert_all(1, Slice.new(3, 8)).should be a + a.should eq([1, 8, 8, 8, 9, 10]) + + a.insert_all(-6, StaticArray(Int32, 3).new { |i| i + 2 }).should be a + a.should eq([1, 2, 3, 4, 8, 8, 8, 9, 10]) + + a.insert_all(4, Deque{5, 6, 7}).should be a + a.should eq([1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 9, 10]) + end + end + describe "inspect" do it { [1, 2, 3].inspect.should eq("[1, 2, 3]") } end diff --git a/src/array.cr b/src/array.cr index 5ce9f2e6148b..cef70c4704c7 100644 --- a/src/array.cr +++ b/src/array.cr @@ -745,32 +745,13 @@ class Array(T) resize_if_cant_insert(other_size) - concat_indexable(other) + insert_elements_at(other, @size) @size += other_size self end - private def concat_indexable(other : Array | Slice | StaticArray) - (@buffer + @size).copy_from(other.to_unsafe, other.size) - end - - private def concat_indexable(other : Deque) - ptr = @buffer + @size - Deque.half_slices(other) do |slice| - ptr.copy_from(slice.to_unsafe, slice.size) - ptr += slice.size - end - end - - private def concat_indexable(other) - appender = (@buffer + @size).appender - other.each do |elem| - appender << elem - end - end - # :ditto: def concat(other : Enumerable) : self left_before_resize = remaining_capacity - @size @@ -1045,6 +1026,68 @@ class Array(T) self end + # Inserts all of the elements from *other* before the element at *index*. + # + # This method shifts the element currently at *index* (if any) and any + # subsequent elements to the right, increasing their indices. If the value + # of *index* is negative, counting starts from the end of the array. + # For example, `-1` indicates insertion after the last element, `-2` before + # the last element. + # + # Raises `IndexError` if the *index* is out of bounds. + # + # ``` + # fruits = ["Apple"] + # newFruits = ["Dragonfruit", "Elderberry"] + # + # fruits.insert_all(1, newFruits) # => ["Apple", "Dragonfruit", "Elderberry"] + # fruits.insert_all(-3, ["Banana", "Cherry"]) # => ["Apple", "Banana", "Cherry", "Dragonfruit", "Elderberry"] + # + # fruits.insert_all(6, ["invalid"]) # raises IndexError + # fruits.insert_all(-7, ["indices"]) # raises IndexError + # ``` + def insert_all(index : Int, other : Indexable) : self + other_size = other.size + + return self if other_size == 0 + + if index < 0 + index += size + 1 + end + + unless 0 <= index <= size + raise IndexError.new + end + + resize_if_cant_insert(other_size) + (@buffer + index).move_to(@buffer + index + other_size, @size - index) + + insert_elements_at(other, index) + + @size += other_size + + self + end + + private def insert_elements_at(other : Array | Slice | StaticArray, index : Int) : Nil + (@buffer + index).copy_from(other.to_unsafe, other.size) + end + + private def insert_elements_at(other : Deque, index : Int) : Nil + ptr = @buffer + index + Deque.half_slices(other) do |slice| + ptr.copy_from(slice.to_unsafe, slice.size) + ptr += slice.size + end + end + + private def insert_elements_at(other, index : Int) : Nil + appender = (@buffer + index).appender + other.each do |elem| + appender << elem + end + end + def inspect(io : IO) : Nil to_s io end From 4c1f81b3ee8bb18be6f8aea8917faa785de31d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 11 Jun 2024 18:20:03 +0200 Subject: [PATCH 1173/1551] Fix `Hash#rehash` to reset `@first` (#14606) --- spec/std/hash_spec.cr | 30 ++++++++++++++++++++---------- src/hash.cr | 3 +++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/spec/std/hash_spec.cr b/spec/std/hash_spec.cr index d098c8f7bf11..684e5af9c0d1 100644 --- a/spec/std/hash_spec.cr +++ b/spec/std/hash_spec.cr @@ -1393,16 +1393,26 @@ describe "Hash" do hash.@indices_size_pow2.should eq(12) end - it "rehashes" do - a = [1] - h = {a => 0} - (10..100).each do |i| - h[[i]] = i - end - a << 2 - h[a]?.should be_nil - h.rehash - h[a].should eq(0) + describe "#rehash" do + it "rehashes" do + a = [1] + h = {a => 0} + (10..100).each do |i| + h[[i]] = i + end + a << 2 + h[a]?.should be_nil + h.rehash + h[a].should eq(0) + end + + it "resets @first (#14602)" do + h = {"a" => 1, "b" => 2} + h.delete("a") + h.rehash + # We cannot test direct equivalence here because `Hash#==(Hash)` does not depend on `@first` + h.to_s.should eq %({"b" => 2}) + end end describe "some edge cases while changing the implementation to open addressing" do diff --git a/src/hash.cr b/src/hash.cr index 24a658dad21d..929e84a1b455 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -631,6 +631,9 @@ class Hash(K, V) new_entry_index += 1 end + # Reset offset to first non-deleted entry + @first = 0 + # We have to mark entries starting from the final new index # as deleted so the GC can collect them. entries_to_clear = entries_size - new_entry_index From 5fd4595eb1c48aefb071e4c4497ca06eadba82a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 11 Jun 2024 18:21:03 +0200 Subject: [PATCH 1174/1551] Refactor `Crystal::System.retry_with_buffer` calling `#to_slice` (#14614) This is a trivial refactor, moving the call to `StaticArray#to_slice` out of the loop. On repeat iterations `buf` is a `Slice` so `#to_slice` is actually a no-op. --- src/crystal/system/unix.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/unix.cr b/src/crystal/system/unix.cr index 136472b79535..d1d089ef2f37 100644 --- a/src/crystal/system/unix.cr +++ b/src/crystal/system/unix.cr @@ -2,9 +2,9 @@ module Crystal::System def self.retry_with_buffer(function_name, max_buffer, &) initial_buf = uninitialized UInt8[1024] - buf = initial_buf + buf = initial_buf.to_slice - while (ret = yield buf.to_slice) != 0 + while (ret = yield buf) != 0 case ret when LibC::ENOENT, LibC::ESRCH, LibC::EBADF, LibC::EPERM return nil From 504fdb7e824cd4357b3ba73b108bd747a05e3c84 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 12 Jun 2024 17:20:01 +0800 Subject: [PATCH 1175/1551] Optimize `Hash` for repeated removals and insertions (#14539) --- src/hash.cr | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/hash.cr b/src/hash.cr index 929e84a1b455..8d48e1cd8c08 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -468,6 +468,8 @@ class Hash(K, V) # We found a non-empty slot, let's see if the key we have matches entry = get_entry(entry_index) if entry_matches?(entry, hash, key) + # Mark this index slot as deleted + delete_index(index) delete_entry_and_update_counts(entry_index) return entry else @@ -775,6 +777,35 @@ class Hash(K, V) end end + # Marks `@indices` at `index` as empty. Might also adjust subsequent index + # slots to ensure empty indices never appear before the natural position of + # any used index, in case of previous hash collisions. + private def delete_index(index) : Nil + # https://en.wikipedia.org/w/index.php?title=Open_addressing&oldid=1188919190#Example_pseudocode + i = index + set_index(i, -1) + + j = i + while true + j = next_index(j) + entry_index = get_index(j) + break if entry_index == -1 + + entry = get_entry(entry_index) + k = fit_in_indices(entry.hash) + + if i <= j + next if i < k && k <= j + else + next if k <= j || i < k + end + + set_index(i, entry_index) + set_index(j, -1) + i = j + end + end + # Returns the capacity of `@indices`. protected def indices_size 1 << @indices_size_pow2 @@ -822,6 +853,16 @@ class Hash(K, V) @entries[index] = value end + # Returns the index into `@indices` for an existing *entry_index* into + # `@entries`. + private def index_for_entry_index(entry_index) + index = fit_in_indices(get_entry(entry_index).hash) + until get_index(index) == entry_index + index = next_index(index) + end + index + end + # Adds an entry at the end and also increments this hash's size. private def add_entry_and_increment_size(hash, key, value) : Nil set_entry(entries_size, Entry(K, V).new(hash, key, value)) @@ -1574,8 +1615,20 @@ class Hash(K, V) # Equivalent to `Hash#reject`, but makes modification on the current object rather than returning a new one. Returns `self`. def reject!(& : K, V -> _) - each_entry_with_index do |entry, index| - delete_entry_and_update_counts(index) if yield(entry.key, entry.value) + # No indices allocated yet so we won't need `DELETED_INDEX` yet + if @indices.null? + each_entry_with_index do |entry, index| + if yield(entry.key, entry.value) + delete_entry_and_update_counts(index) + end + end + else + each_entry_with_index do |entry, index| + if yield(entry.key, entry.value) + delete_index(index_for_entry_index(index)) + delete_entry_and_update_counts(index) + end + end end self end @@ -1863,6 +1916,7 @@ class Hash(K, V) def shift(&) first_entry = first_entry? if first_entry + delete_index(index_for_entry_index(@first)) unless @indices.null? delete_entry_and_update_counts(@first) {first_entry.key, first_entry.value} else From 70fb98e5b36d1f03ac69b220ed92db7947e83013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 12 Jun 2024 11:24:36 +0200 Subject: [PATCH 1176/1551] Move `File.readable?`, `.writable?`, `.executable?` to `File::Info` (#14484) These methods provide lower-level insights into the file system. Their use cases are rare and exposure in the top-level `File` namespace is confusing due to the similarly-named methods `File.read` and `File.write`. --- src/compiler/crystal/loader/unix.cr | 2 +- src/crystal/system/unix/time.cr | 2 +- src/exception/call_stack/elf.cr | 2 +- src/file.cr | 3 ++ src/file/info.cr | 44 +++++++++++++++++++++++++++++ src/process/executable_path.cr | 2 +- src/time/location/loader.cr | 4 +-- 7 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/compiler/crystal/loader/unix.cr b/src/compiler/crystal/loader/unix.cr index 46bca67341cc..dfab9736b038 100644 --- a/src/compiler/crystal/loader/unix.cr +++ b/src/compiler/crystal/loader/unix.cr @@ -183,7 +183,7 @@ class Crystal::Loader end def self.read_ld_conf(array = [] of String, path = "/etc/ld.so.conf") : Nil - return unless File.readable?(path) + return unless File::Info.readable?(path) File.each_line(path) do |line| next if line.empty? || line.starts_with?("#") diff --git a/src/crystal/system/unix/time.cr b/src/crystal/system/unix/time.cr index 58ed5b60e7e1..2ead3bdb0fa2 100644 --- a/src/crystal/system/unix/time.cr +++ b/src/crystal/system/unix/time.cr @@ -132,7 +132,7 @@ module Crystal::System::Time private LOCALTIME = "/etc/localtime" def self.load_localtime : ::Time::Location? - if ::File.file?(LOCALTIME) && ::File.readable?(LOCALTIME) + if ::File.file?(LOCALTIME) && ::File::Info.readable?(LOCALTIME) ::File.open(LOCALTIME) do |file| ::Time::Location.read_zoneinfo("Local", file) rescue ::Time::Location::InvalidTZDataError diff --git a/src/exception/call_stack/elf.cr b/src/exception/call_stack/elf.cr index 1bb14f3a4332..efa54f41329c 100644 --- a/src/exception/call_stack/elf.cr +++ b/src/exception/call_stack/elf.cr @@ -14,7 +14,7 @@ struct Exception::CallStack protected def self.load_debug_info_impl : Nil program = Process.executable_path - return unless program && File.readable? program + return unless program && File::Info.readable? program data = DlPhdrData.new(program) phdr_callback = LibC::DlPhdrCallback.new do |info, size, data| diff --git a/src/file.cr b/src/file.cr index a011979dcc9b..ff6c68ef4d03 100644 --- a/src/file.cr +++ b/src/file.cr @@ -288,6 +288,7 @@ class File < IO::FileDescriptor # File.write("foo", "foo") # File.readable?("foo") # => true # ``` + @[Deprecated("Use `File::Info.readable?` instead")] def self.readable?(path : Path | String) : Bool Crystal::System::File.readable?(path.to_s) end @@ -298,6 +299,7 @@ class File < IO::FileDescriptor # File.write("foo", "foo") # File.writable?("foo") # => true # ``` + @[Deprecated("Use `File::Info.writable?` instead")] def self.writable?(path : Path | String) : Bool Crystal::System::File.writable?(path.to_s) end @@ -308,6 +310,7 @@ class File < IO::FileDescriptor # File.write("foo", "foo") # File.executable?("foo") # => false # ``` + @[Deprecated("Use `File::Info.executable?` instead")] def self.executable?(path : Path | String) : Bool Crystal::System::File.executable?(path.to_s) end diff --git a/src/file/info.cr b/src/file/info.cr index a7f272813a26..ea5ed2ac82f6 100644 --- a/src/file/info.cr +++ b/src/file/info.cr @@ -142,5 +142,49 @@ class File def symlink? type.symlink? end + + # Returns `true` if *path* is readable by the real user id of this process else returns `false`. + # + # ``` + # File.write("foo", "foo") + # File::Info.readable?("foo") # => true + # ``` + # + # This method returns the readable property as reported by the file system + # which provides no indication of whether `File.read` would be a valid + # operation because it applies to all file types, including directories. + def self.readable?(path : Path | String) : Bool + Crystal::System::File.readable?(path.to_s) + end + + # Returns `true` if *path* is writable by the real user id of this process else returns `false`. + # + # ``` + # File.write("foo", "foo") + # File::Info.writable?("foo") # => true + # ``` + # + # This method returns the readable property as reported by the file system + # which provides no indication of whether `File.write` would be a valid + # operation because it applies to all file types, including directories. + def self.writable?(path : Path | String) : Bool + Crystal::System::File.writable?(path.to_s) + end + + # Returns `true` if *path* is executable by the real user id of this process else returns `false`. + # + # ``` + # File.write("foo", "foo") + # File::Info.executable?("foo") # => false + # ``` + # + # This method returns the readable property as reported by the file system + # which provides no indication of whether `Process.execute` would be a valid + # operation because it applies to all file types, including directories + # (which typically *are* executable to signal it's allowed to list their + # contents). + def self.executable?(path : Path | String) : Bool + Crystal::System::File.executable?(path.to_s) + end end end diff --git a/src/process/executable_path.cr b/src/process/executable_path.cr index bdd5776f305a..1a7b0c44d55d 100644 --- a/src/process/executable_path.cr +++ b/src/process/executable_path.cr @@ -50,7 +50,7 @@ class Process # Windows doesn't have "executable" metadata for files, so it also doesn't have files that are "not executable". true {% else %} - File.executable?(path) + File::Info.executable?(path) {% end %} end diff --git a/src/time/location/loader.cr b/src/time/location/loader.cr index 8c85aadd4909..6555125e6ff7 100644 --- a/src/time/location/loader.cr +++ b/src/time/location/loader.cr @@ -92,14 +92,14 @@ class Time::Location path = File.join(source, name) end - return source if File.exists?(path) && File.file?(path) && File.readable?(path) + return source if File.exists?(path) && File.file?(path) && File::Info.readable?(path) end end # :nodoc: def self.find_android_tzdata_file(sources : Enumerable(String)) : String? sources.find do |path| - File.exists?(path) && File.file?(path) && File.readable?(path) + File.exists?(path) && File.file?(path) && File::Info.readable?(path) end end From ef04b2e96a3d400491ef51ae6dea5ba08a9e29af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 12 Jun 2024 15:13:58 +0200 Subject: [PATCH 1177/1551] Optimize `String#to_utf16` (#14671) A series of optimizations to improve performance of `String#to_utf16`: * Remove branches for impossible codepoints. `each_char` already excludes invalid codepoints, so we only have to handle the encoding as one or two UInt16. * Drop `ascii_only?` branch. The performance benefit is questionable because `ascii_only?` iterates the string. With optimizations to the regular loop, this special case doesn't provide much extra performance, so it's expendable. * Use pointer appender to avoid bounds checks on every slice assignment. It also improves convenience. * Use wrapping math operators. These operations cannot overflow, so we can use the unsafe variants for better performance. --- src/string/utf16.cr | 37 +++++++++---------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/src/string/utf16.cr b/src/string/utf16.cr index 042391cca15d..697c1b585a37 100644 --- a/src/string/utf16.cr +++ b/src/string/utf16.cr @@ -12,21 +12,6 @@ class String # "hi 𐂥".to_utf16 # => Slice[104_u16, 105_u16, 32_u16, 55296_u16, 56485_u16] # ``` def to_utf16 : Slice(UInt16) - if ascii_only? - # size == bytesize, so each char fits in one UInt16 - - # This is essentially equivalent to `to_slice.map(&.to_u16)` but also makes - # sure to allocate a null byte after the string. - slice = Slice(UInt16).new(bytesize + 1) do |i| - if i == bytesize - 0_u16 - else - to_unsafe[i].to_u16 - end - end - return slice[0, bytesize] - end - # size < bytesize, so we need to count the number of characters that are # two UInt16 wide. u16_size = 0 @@ -37,28 +22,24 @@ class String # Allocate one extra character for trailing null slice = Slice(UInt16).new(u16_size + 1) - i = 0 + appender = slice.to_unsafe.appender each_char do |char| ord = char.ord - if ord <= 0xd800 || (0xe000 <= ord < 0x1_0000) + if ord < 0x1_0000 # One UInt16 is enough - slice[i] = ord.to_u16 - elsif ord >= 0x1_0000 - # Needs surrogate pair - ord -= 0x1_0000 - slice[i] = 0xd800_u16 + ((ord >> 10) & 0x3ff) # Keep top 10 bits - i += 1 - slice[i] = 0xdc00_u16 + (ord & 0x3ff) # Keep low 10 bits + appender << ord.to_u16! else - # Invalid char: use replacement - slice[i] = 0xfffd_u16 + # Needs surrogate pair + ord &-= 0x1_0000 + appender << 0xd800_u16 &+ ((ord >> 10) & 0x3ff) # Keep top 10 bits + appender << 0xdc00_u16 &+ (ord & 0x3ff) # Keep low 10 bits end - i += 1 end # Append null byte - slice[i] = 0_u16 + appender << 0_u16 + # The trailing null is not part of the returned slice slice[0, u16_size] end From c141700e366f534bdcd9a3968574fd94324546fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 12 Jun 2024 15:14:32 +0200 Subject: [PATCH 1178/1551] Replace calls to `StaticArray(UInt8, 1)#unsafe_as(UInt8)` (#14685) It's unnecessary to use `unsafe_as` to retrieve the value of a single-item `StaticArray`. However, the entire `StaticArray` is unnecessary because we just need a slice pointing to the location of the value. The resulting machinecode should be identical in all three variants. --- src/crystal/system/unix/getrandom.cr | 6 +++--- src/crystal/system/wasi/random.cr | 6 +++--- src/crystal/system/win32/random.cr | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/crystal/system/unix/getrandom.cr b/src/crystal/system/unix/getrandom.cr index 7be835ff5ab9..229716a3d846 100644 --- a/src/crystal/system/unix/getrandom.cr +++ b/src/crystal/system/unix/getrandom.cr @@ -62,9 +62,9 @@ module Crystal::System::Random init unless @@initialized if @@getrandom_available - buf = uninitialized UInt8[1] - getrandom(buf.to_slice) - buf.unsafe_as(UInt8) + buf = uninitialized UInt8 + getrandom(pointerof(buf).to_slice(1)) + buf elsif urandom = @@urandom urandom.read_byte.not_nil! else diff --git a/src/crystal/system/wasi/random.cr b/src/crystal/system/wasi/random.cr index 72649fac3e7c..3b50ff7fdf44 100644 --- a/src/crystal/system/wasi/random.cr +++ b/src/crystal/system/wasi/random.cr @@ -7,8 +7,8 @@ module Crystal::System::Random end def self.next_u : UInt8 - buf = uninitialized UInt8[1] - random_bytes(buf.to_slice) - buf.unsafe_as(UInt8) + buf = uninitialized UInt8 + random_bytes(pointerof(buf).to_slice(1)) + buf end end diff --git a/src/crystal/system/win32/random.cr b/src/crystal/system/win32/random.cr index 03c54fa8aa40..faec72ca9b57 100644 --- a/src/crystal/system/win32/random.cr +++ b/src/crystal/system/win32/random.cr @@ -8,8 +8,8 @@ module Crystal::System::Random end def self.next_u : UInt8 - buf = uninitialized UInt8[1] - random_bytes(buf.to_slice) - buf.unsafe_as(UInt8) + buf = uninitialized UInt8 + random_bytes(pointerof(buf).to_slice(1)) + buf end end From 1a6686af61fbada0884a54199ac700e5fb1d5d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 12 Jun 2024 22:15:20 +0200 Subject: [PATCH 1179/1551] Fix create new `target_machine` for every program (#14694) --- src/compiler/crystal/compiler.cr | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 12c19ef8c12a..eec190e85eeb 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -254,7 +254,7 @@ module Crystal program.compiler = self program.filename = sources.first.filename program.codegen_target = codegen_target - program.target_machine = target_machine + program.target_machine = create_target_machine program.flags << "release" if release? program.flags << "debug" unless debug.none? program.flags << "static" if static? @@ -662,6 +662,10 @@ module Crystal end getter(target_machine : LLVM::TargetMachine) do + create_target_machine + end + + def create_target_machine @codegen_target.to_target_machine(@mcpu || "", @mattr || "", @optimization_mode, @mcmodel) rescue ex : ArgumentError stderr.print colorize("Error: ").red.bold From 7a183b38288c17f22cffe610732f08394e204ea3 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 14 Jun 2024 16:56:14 +0800 Subject: [PATCH 1180/1551] Fix overflow in `File#read_at` for large offsets on Windows (#14708) Allows `File#read_at` to work for offsets at 2 GiB or larger. --- src/crystal/system/win32/file_descriptor.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 63aee6e1ecf7..b6cc06d62a1c 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -259,8 +259,8 @@ module Crystal::System::FileDescriptor handle = windows_handle(fd) overlapped = LibC::OVERLAPPED.new - overlapped.union.offset.offset = LibC::DWORD.new(offset) - overlapped.union.offset.offsetHigh = LibC::DWORD.new(offset >> 32) + overlapped.union.offset.offset = LibC::DWORD.new!(offset) + overlapped.union.offset.offsetHigh = LibC::DWORD.new!(offset >> 32) if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0 error = WinError.value return 0_i64 if error == WinError::ERROR_HANDLE_EOF From 0c3df14ebaf7adfef74d85c9a56af3c5be7ce1c1 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:56:41 +0200 Subject: [PATCH 1181/1551] Add documentation for `HTTP::WebSocket#stream` (#14537) Co-authored-by: Beta Ziliani Co-authored-by: Julien Portalier --- src/http/web_socket.cr | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/http/web_socket.cr b/src/http/web_socket.cr index 54724d9b7bbd..e2d56b8a7610 100644 --- a/src/http/web_socket.cr +++ b/src/http/web_socket.cr @@ -97,6 +97,26 @@ class HTTP::WebSocket @ws.pong(message) end + # Streams data into io until the io is flushed and sent as a message. + # + # The method accepts a block with an `io` argument. + # The io object can call on `IO#write` and `IO#flush` method. + # The `write` method accepts `Bytes` (`Slice(UInt8)`) and sends the data in chunks of *frame_size* bytes. The `flush` method sends all the data in io and resets it. + # The remaining data in it is sent as a message when the block is finished executing. + # For further information, see the `HTTP::WebSocket::Protocol::StreamIO` class. + # + # ``` + # # Open websocket connection + # ws = HTTP::WebSocket.new("websocket.example.com", "/chat") + # + # # Open stream + # ws.stream(false) do |io| + # io.write "Hello, ".encode("UTF-8") # Sends "Hello, " to io + # io.flush # Sends "Hello, " to the socket + # io.write "world!".encode("UTF-8") # Sends "world!" to io + # end + # # Sends "world!" to the socket + # ``` def stream(binary = true, frame_size = 1024, &) check_open @ws.stream(binary: binary, frame_size: frame_size) do |io| From 542f8974a4a497134b5bee6de60f4e5ed147a763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 14 Jun 2024 10:57:31 +0200 Subject: [PATCH 1182/1551] Drop `IO::Overlapped` (#14704) Removes the module `IO::Overlapped` which is included in `IO::FileDescriptor` and `Socket`. All methods are moved to the `Crystal::IOCP` namespace. --- src/crystal/system/win32/event_loop_iocp.cr | 15 ++++++++------- src/crystal/system/win32/file_descriptor.cr | 4 +--- .../system/win32/iocp.cr} | 17 +++++++++-------- src/crystal/system/win32/process.cr | 2 +- src/crystal/system/win32/socket.cr | 12 +++++------- 5 files changed, 24 insertions(+), 26 deletions(-) rename src/{io/overlapped.cr => crystal/system/win32/iocp.cr} (92%) diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index c25a3a68ea38..d05e15162171 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -1,5 +1,6 @@ require "c/ioapiset" require "crystal/system/print_error" +require "./iocp" # :nodoc: class Crystal::IOCP::EventLoop < Crystal::EventLoop @@ -62,7 +63,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop end wait_time = blocking ? (next_event.wake_at - now).total_milliseconds : 0 - timed_out = IO::Overlapped.wait_queued_completions(wait_time, alertable: blocking) do |fiber| + timed_out = IOCP.wait_queued_completions(wait_time, alertable: blocking) do |fiber| # This block may run multiple times. Every single fiber gets enqueued. fiber.enqueue end @@ -144,7 +145,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 handle = file_descriptor.windows_handle - file_descriptor.overlapped_operation(handle, "ReadFile", file_descriptor.read_timeout) do |overlapped| + IOCP.overlapped_operation(file_descriptor, handle, "ReadFile", file_descriptor.read_timeout) do |overlapped| ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped) {ret, byte_count} end.to_i32 @@ -153,7 +154,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 handle = file_descriptor.windows_handle - file_descriptor.overlapped_operation(handle, "WriteFile", file_descriptor.write_timeout, writing: true) do |overlapped| + IOCP.overlapped_operation(file_descriptor, handle, "WriteFile", file_descriptor.write_timeout, writing: true) do |overlapped| ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped) {ret, byte_count} end.to_i32 @@ -173,7 +174,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop def read(socket : ::Socket, slice : Bytes) : Int32 wsabuf = wsa_buffer(slice) - bytes_read = socket.wsa_overlapped_operation(socket.fd, "WSARecv", socket.read_timeout, connreset_is_error: false) do |overlapped| + bytes_read = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSARecv", socket.read_timeout, connreset_is_error: false) do |overlapped| flags = 0_u32 ret = LibC.WSARecv(socket.fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), overlapped, nil) {ret, bytes_received} @@ -185,7 +186,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop def write(socket : ::Socket, slice : Bytes) : Int32 wsabuf = wsa_buffer(slice) - bytes = socket.wsa_overlapped_operation(socket.fd, "WSASend", socket.write_timeout) do |overlapped| + bytes = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSASend", socket.write_timeout) do |overlapped| ret = LibC.WSASend(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, overlapped, nil) {ret, bytes_sent} end @@ -195,7 +196,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop def send_to(socket : ::Socket, slice : Bytes, address : ::Socket::Address) : Int32 wsabuf = wsa_buffer(slice) - bytes_written = socket.wsa_overlapped_operation(socket.fd, "WSASendTo", socket.write_timeout) do |overlapped| + bytes_written = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSASendTo", socket.write_timeout) do |overlapped| ret = LibC.WSASendTo(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, address, address.size, overlapped, nil) {ret, bytes_sent} end @@ -221,7 +222,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop wsabuf = wsa_buffer(slice) flags = 0_u32 - bytes_read = socket.wsa_overlapped_operation(socket.fd, "WSARecvFrom", socket.read_timeout) do |overlapped| + bytes_read = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSARecvFrom", socket.read_timeout) do |overlapped| ret = LibC.WSARecvFrom(socket.fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), sockaddr, pointerof(addrlen), overlapped, nil) {ret, bytes_received} end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index b6cc06d62a1c..c836391e49ef 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -2,11 +2,9 @@ require "c/io" require "c/consoleapi" require "c/consoleapi2" require "c/winnls" -require "io/overlapped" +require "crystal/system/win32/iocp" module Crystal::System::FileDescriptor - include IO::Overlapped - # Platform-specific type to represent a file descriptor handle to the operating # system. # NOTE: this should really be `LibC::HANDLE`, here it is an integer type of diff --git a/src/io/overlapped.cr b/src/crystal/system/win32/iocp.cr similarity index 92% rename from src/io/overlapped.cr rename to src/crystal/system/win32/iocp.cr index 20a0313c9379..53cf704ad760 100644 --- a/src/io/overlapped.cr +++ b/src/crystal/system/win32/iocp.cr @@ -2,7 +2,8 @@ require "c/handleapi" require "crystal/system/thread_linked_list" -module IO::Overlapped +# :nodoc: +module Crystal::IOCP # :nodoc: class CompletionKey property fiber : Fiber? @@ -156,7 +157,7 @@ module IO::Overlapped end # Returns `false` if the operation timed out. - def schedule_overlapped(timeout : Time::Span?, line = __LINE__) : Bool + def self.schedule_overlapped(timeout : Time::Span?, line = __LINE__) : Bool if timeout timeout_event = Crystal::IOCP::Event.new(Fiber.current) timeout_event.add(timeout) @@ -173,7 +174,7 @@ module IO::Overlapped event_loop.dequeue(timeout_event) end - def overlapped_operation(handle, method, timeout, *, writing = false, &) + def self.overlapped_operation(target, handle, method, timeout, *, writing = false, &) OverlappedOperation.run(handle) do |operation| result, value = yield operation.start @@ -186,9 +187,9 @@ module IO::Overlapped when .error_io_pending? # the operation is running asynchronously; do nothing when .error_access_denied? - raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}", target: self + raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}", target: target else - raise IO::Error.from_os_error(method, error, target: self) + raise IO::Error.from_os_error(method, error, target: target) end else operation.done! @@ -211,7 +212,7 @@ module IO::Overlapped end end - def wsa_overlapped_operation(socket, method, timeout, connreset_is_error = true, &) + def self.wsa_overlapped_operation(target, socket, method, timeout, connreset_is_error = true, &) OverlappedOperation.run(socket) do |operation| result, value = yield operation.start @@ -220,7 +221,7 @@ module IO::Overlapped when .wsa_io_pending? # the operation is running asynchronously; do nothing else - raise IO::Error.from_os_error(method, error, target: self) + raise IO::Error.from_os_error(method, error, target: target) end else operation.done! @@ -232,7 +233,7 @@ module IO::Overlapped operation.wsa_result(socket) do |error| case error when .wsa_io_incomplete? - raise TimeoutError.new("#{method} timed out") + raise IO::TimeoutError.new("#{method} timed out") when .wsaeconnreset? return 0_u32 unless connreset_is_error end diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 88d6125d9f33..05b2ea36584e 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -17,7 +17,7 @@ struct Crystal::System::Process @thread_id : LibC::DWORD @process_handle : LibC::HANDLE @job_object : LibC::HANDLE - @completion_key = IO::Overlapped::CompletionKey.new + @completion_key = IOCP::CompletionKey.new @@interrupt_handler : Proc(::Process::ExitReason, Nil)? @@interrupt_count = Crystal::AtomicSemaphore.new diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 9551ba0b0420..a39382c252d6 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -1,10 +1,8 @@ require "c/mswsock" require "c/ioapiset" -require "io/overlapped" +require "crystal/system/win32/iocp" module Crystal::System::Socket - include IO::Overlapped - alias Handle = LibC::SOCKET # Initialize WSA @@ -131,7 +129,7 @@ module Crystal::System::Socket # :nodoc: def overlapped_connect(socket, method, &) - OverlappedOperation.run(socket) do |operation| + IOCP::OverlappedOperation.run(socket) do |operation| result = yield operation.start if result == 0 @@ -148,7 +146,7 @@ module Crystal::System::Socket return nil end - schedule_overlapped(read_timeout || 1.seconds) + IOCP.schedule_overlapped(read_timeout || 1.seconds) operation.wsa_result(socket) do |error| case error @@ -197,7 +195,7 @@ module Crystal::System::Socket end def overlapped_accept(socket, method, &) - OverlappedOperation.run(socket) do |operation| + IOCP::OverlappedOperation.run(socket) do |operation| result = yield operation.start if result == 0 @@ -212,7 +210,7 @@ module Crystal::System::Socket return true end - unless schedule_overlapped(read_timeout) + unless IOCP.schedule_overlapped(read_timeout) raise IO::TimeoutError.new("#{method} timed out") end From a26952420db07cf693505626089e9ebf3a39d71a Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 14 Jun 2024 12:44:14 +0200 Subject: [PATCH 1183/1551] Fix: use `SOCK_CLOEXEC` with `FD_CLOEXEC` fallback (#14672) Harmonizes the implementations between Darwin and other POSIX platforms for the "close on exec" behavior. When `SOCK_CLOEXEC` is available, we always use it in the `socket`, `socketpair` and `accept4` syscalls. When `SOCK_CLOEXEC` isn't available, we don't delay to `Socket#initialize_handle` anymore to set `FD_CLOEXEC` for Darwin only, but immediately call `fcntl` to set it after the above syscalls. The `accept4` syscall is non-standard but widely available: Linux, all supported BSD, except for Darwin (obviously). The patch also fixes an issue where TCP and UNIX client sockets didn't have `FD_CLOEXEC` on POSIX platforms... except for Darwin. closes #14650 --- spec/std/socket/socket_spec.cr | 3 +++ spec/std/socket/tcp_server_spec.cr | 14 ++++++++++++++ spec/std/socket/unix_server_spec.cr | 14 ++++++++++++++ spec/std/socket/unix_socket_spec.cr | 3 +++ src/crystal/system/unix/event_loop_libevent.cr | 11 ++++++++++- src/crystal/system/unix/socket.cr | 17 +++++++++-------- src/lib_c/aarch64-android/c/sys/socket.cr | 1 + src/lib_c/aarch64-linux-gnu/c/sys/socket.cr | 1 + src/lib_c/aarch64-linux-musl/c/sys/socket.cr | 1 + src/lib_c/arm-linux-gnueabihf/c/sys/socket.cr | 1 + src/lib_c/i386-linux-gnu/c/sys/socket.cr | 1 + src/lib_c/i386-linux-musl/c/sys/socket.cr | 1 + src/lib_c/x86_64-dragonfly/c/sys/socket.cr | 1 + src/lib_c/x86_64-freebsd/c/sys/socket.cr | 1 + src/lib_c/x86_64-linux-gnu/c/sys/socket.cr | 1 + src/lib_c/x86_64-linux-musl/c/sys/socket.cr | 1 + src/lib_c/x86_64-netbsd/c/sys/socket.cr | 1 + src/lib_c/x86_64-openbsd/c/sys/socket.cr | 1 + src/lib_c/x86_64-solaris/c/sys/socket.cr | 1 + src/socket/unix_socket.cr | 9 +++++++-- 20 files changed, 73 insertions(+), 11 deletions(-) diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index 56fb07f41db5..d4e7051d12bd 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -57,6 +57,9 @@ describe Socket, tags: "network" do client.family.should eq(Socket::Family::INET) client.type.should eq(Socket::Type::STREAM) client.protocol.should eq(Socket::Protocol::TCP) + {% unless flag?(:win32) %} + client.close_on_exec?.should be_true + {% end %} ensure client.close end diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr index bb8fd03dad5f..0c6113a4a7ff 100644 --- a/spec/std/socket/tcp_server_spec.cr +++ b/spec/std/socket/tcp_server_spec.cr @@ -136,4 +136,18 @@ describe TCPServer, tags: "network" do end end {% end %} + + describe "accept" do + {% unless flag?(:win32) %} + it "sets close on exec flag" do + TCPServer.open("localhost", 0) do |server| + TCPSocket.open("localhost", server.local_address.port) do |client| + server.accept? do |sock| + sock.close_on_exec?.should be_true + end + end + end + end + {% end %} + end end diff --git a/spec/std/socket/unix_server_spec.cr b/spec/std/socket/unix_server_spec.cr index 098bdb3e7d53..ca364f08667c 100644 --- a/spec/std/socket/unix_server_spec.cr +++ b/spec/std/socket/unix_server_spec.cr @@ -147,6 +147,20 @@ describe UNIXServer do ret.should be_nil end end + + {% unless flag?(:win32) %} + it "sets close on exec flag" do + with_tempfile("unix_socket-accept.sock") do |path| + UNIXServer.open(path) do |server| + UNIXSocket.open(path) do |client| + server.accept? do |sock| + sock.close_on_exec?.should be_true + end + end + end + end + end + {% end %} end # Datagram socket type is not supported on Windows yet: diff --git a/spec/std/socket/unix_socket_spec.cr b/spec/std/socket/unix_socket_spec.cr index 5968ffe381aa..24777bada67f 100644 --- a/spec/std/socket/unix_socket_spec.cr +++ b/spec/std/socket/unix_socket_spec.cr @@ -101,6 +101,9 @@ describe UNIXSocket do (left.recv_buffer_size = size).should eq(size) sizes.should contain(left.recv_buffer_size) + + left.close_on_exec?.should be_true + right.close_on_exec?.should be_true end end {% end %} diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr index 0ae968c7a15f..3d8cecf694f2 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/system/unix/event_loop_libevent.cr @@ -148,7 +148,13 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop def accept(socket : ::Socket) : ::Socket::Handle? loop do - client_fd = LibC.accept(socket.fd, nil, nil) + client_fd = + {% if LibC.has_method?(:accept4) %} + LibC.accept4(socket.fd, nil, nil, LibC::SOCK_CLOEXEC) + {% else %} + LibC.accept(socket.fd, nil, nil) + {% end %} + if client_fd == -1 if socket.closed? return @@ -161,6 +167,9 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop raise ::Socket::Error.from_errno("accept") end else + {% unless LibC.has_method?(:accept4) %} + Crystal::System::Socket.fcntl(client_fd, LibC::F_SETFD, LibC::FD_CLOEXEC) + {% end %} return client_fd end end diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index a263e7742301..ad065bf3ba23 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -9,21 +9,22 @@ module Crystal::System::Socket alias Handle = Int32 private def create_handle(family, type, protocol, blocking) : Handle + socktype = type.value {% if LibC.has_constant?(:SOCK_CLOEXEC) %} - # Forces opened sockets to be closed on `exec(2)`. - type = type.to_i | LibC::SOCK_CLOEXEC + socktype |= LibC::SOCK_CLOEXEC {% end %} - fd = LibC.socket(family, type, protocol) + + fd = LibC.socket(family, socktype, protocol) raise ::Socket::Error.from_errno("Failed to create socket") if fd == -1 + + {% unless LibC.has_constant?(:SOCK_CLOEXEC) %} + Socket.fcntl(fd, LibC::F_SETFD, LibC::FD_CLOEXEC) + {% end %} + fd end private def initialize_handle(fd) - {% unless LibC.has_constant?(:SOCK_CLOEXEC) %} - # Forces opened sockets to be closed on `exec(2)`. Only for platforms that don't - # support `SOCK_CLOEXEC` (e.g., Darwin). - LibC.fcntl(fd, LibC::F_SETFD, LibC::FD_CLOEXEC) - {% end %} end # Tries to bind the socket to a local address. diff --git a/src/lib_c/aarch64-android/c/sys/socket.cr b/src/lib_c/aarch64-android/c/sys/socket.cr index 241be043248b..d52a5c1110ab 100644 --- a/src/lib_c/aarch64-android/c/sys/socket.cr +++ b/src/lib_c/aarch64-android/c/sys/socket.cr @@ -48,6 +48,7 @@ lib LibC end fun accept(__fd : Int, __addr : Sockaddr*, __addr_length : SocklenT*) : Int + fun accept4(__fd : Int, __addr : Sockaddr*, __addr_length : SocklenT*, __flags : Int) : Int fun bind(__fd : Int, __addr : Sockaddr*, __addr_length : SocklenT) : Int fun connect(__fd : Int, __addr : Sockaddr*, __addr_length : SocklenT) : Int fun getpeername(__fd : Int, __addr : Sockaddr*, __addr_length : SocklenT*) : Int diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr b/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr index 82e48d78c9f2..7935dd8b3550 100644 --- a/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr +++ b/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr @@ -48,6 +48,7 @@ lib LibC end fun accept(fd : Int, addr : Sockaddr*, addr_len : SocklenT*) : Int + fun accept4(fd : Int, addr : Sockaddr*, addr_len : SocklenT*, flags : Int) : Int fun bind(fd : Int, addr : Sockaddr*, len : SocklenT) : Int fun connect(fd : Int, addr : Sockaddr*, len : SocklenT) : Int fun getpeername(fd : Int, addr : Sockaddr*, len : SocklenT*) : Int diff --git a/src/lib_c/aarch64-linux-musl/c/sys/socket.cr b/src/lib_c/aarch64-linux-musl/c/sys/socket.cr index 361e5b8040d6..51211386e8bd 100644 --- a/src/lib_c/aarch64-linux-musl/c/sys/socket.cr +++ b/src/lib_c/aarch64-linux-musl/c/sys/socket.cr @@ -48,6 +48,7 @@ lib LibC end fun accept(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int + fun accept4(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*, x3 : Int) : Int fun bind(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun connect(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun getpeername(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/socket.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/socket.cr index 3a61519d9baa..4a2641d3ecd3 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/sys/socket.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/sys/socket.cr @@ -48,6 +48,7 @@ lib LibC end fun accept(fd : Int, addr : Sockaddr*, addr_len : SocklenT*) : Int + fun accept4(fd : Int, addr : Sockaddr*, addr_len : SocklenT*, flags : Int) : Int fun bind(fd : Int, addr : Sockaddr*, len : SocklenT) : Int fun connect(fd : Int, addr : Sockaddr*, len : SocklenT) : Int fun getpeername(fd : Int, addr : Sockaddr*, len : SocklenT*) : Int diff --git a/src/lib_c/i386-linux-gnu/c/sys/socket.cr b/src/lib_c/i386-linux-gnu/c/sys/socket.cr index 6651736a41c0..6473b6bad757 100644 --- a/src/lib_c/i386-linux-gnu/c/sys/socket.cr +++ b/src/lib_c/i386-linux-gnu/c/sys/socket.cr @@ -48,6 +48,7 @@ lib LibC end fun accept(fd : Int, addr : Sockaddr*, addr_len : SocklenT*) : Int + fun accept4(fd : Int, addr : Sockaddr*, addr_len : SocklenT*, flags : Int) : Int fun bind(fd : Int, addr : Sockaddr*, len : SocklenT) : Int fun connect(fd : Int, addr : Sockaddr*, len : SocklenT) : Int fun getpeername(fd : Int, addr : Sockaddr*, len : SocklenT*) : Int diff --git a/src/lib_c/i386-linux-musl/c/sys/socket.cr b/src/lib_c/i386-linux-musl/c/sys/socket.cr index 361e5b8040d6..51211386e8bd 100644 --- a/src/lib_c/i386-linux-musl/c/sys/socket.cr +++ b/src/lib_c/i386-linux-musl/c/sys/socket.cr @@ -48,6 +48,7 @@ lib LibC end fun accept(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int + fun accept4(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*, x3 : Int) : Int fun bind(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun connect(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun getpeername(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int diff --git a/src/lib_c/x86_64-dragonfly/c/sys/socket.cr b/src/lib_c/x86_64-dragonfly/c/sys/socket.cr index ff439b8524d1..0d30f295ed04 100644 --- a/src/lib_c/x86_64-dragonfly/c/sys/socket.cr +++ b/src/lib_c/x86_64-dragonfly/c/sys/socket.cr @@ -51,6 +51,7 @@ lib LibC end fun accept(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int + fun accept4(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*, x3 : Int) : Int fun bind(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun connect(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun getpeername(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int diff --git a/src/lib_c/x86_64-freebsd/c/sys/socket.cr b/src/lib_c/x86_64-freebsd/c/sys/socket.cr index 8a731c8ee82c..052b897af1a7 100644 --- a/src/lib_c/x86_64-freebsd/c/sys/socket.cr +++ b/src/lib_c/x86_64-freebsd/c/sys/socket.cr @@ -51,6 +51,7 @@ lib LibC end fun accept(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int + fun accept4(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*, x3 : Int) : Int fun bind(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun connect(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun getpeername(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr b/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr index 82e48d78c9f2..7935dd8b3550 100644 --- a/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr +++ b/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr @@ -48,6 +48,7 @@ lib LibC end fun accept(fd : Int, addr : Sockaddr*, addr_len : SocklenT*) : Int + fun accept4(fd : Int, addr : Sockaddr*, addr_len : SocklenT*, flags : Int) : Int fun bind(fd : Int, addr : Sockaddr*, len : SocklenT) : Int fun connect(fd : Int, addr : Sockaddr*, len : SocklenT) : Int fun getpeername(fd : Int, addr : Sockaddr*, len : SocklenT*) : Int diff --git a/src/lib_c/x86_64-linux-musl/c/sys/socket.cr b/src/lib_c/x86_64-linux-musl/c/sys/socket.cr index 361e5b8040d6..51211386e8bd 100644 --- a/src/lib_c/x86_64-linux-musl/c/sys/socket.cr +++ b/src/lib_c/x86_64-linux-musl/c/sys/socket.cr @@ -48,6 +48,7 @@ lib LibC end fun accept(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int + fun accept4(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*, x3 : Int) : Int fun bind(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun connect(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun getpeername(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int diff --git a/src/lib_c/x86_64-netbsd/c/sys/socket.cr b/src/lib_c/x86_64-netbsd/c/sys/socket.cr index d96f245bc42a..3d196098492f 100644 --- a/src/lib_c/x86_64-netbsd/c/sys/socket.cr +++ b/src/lib_c/x86_64-netbsd/c/sys/socket.cr @@ -51,6 +51,7 @@ lib LibC end fun accept(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int + fun accept4(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*, x3 : Int) : Int fun bind(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun connect(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun getpeername(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int diff --git a/src/lib_c/x86_64-openbsd/c/sys/socket.cr b/src/lib_c/x86_64-openbsd/c/sys/socket.cr index cb2f1fa8123b..e812ddca2236 100644 --- a/src/lib_c/x86_64-openbsd/c/sys/socket.cr +++ b/src/lib_c/x86_64-openbsd/c/sys/socket.cr @@ -51,6 +51,7 @@ lib LibC end fun accept(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int + fun accept4(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*, x3 : Int) : Int fun bind(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun connect(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun getpeername(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int diff --git a/src/lib_c/x86_64-solaris/c/sys/socket.cr b/src/lib_c/x86_64-solaris/c/sys/socket.cr index 4c2572c288ec..0031c66d0da0 100644 --- a/src/lib_c/x86_64-solaris/c/sys/socket.cr +++ b/src/lib_c/x86_64-solaris/c/sys/socket.cr @@ -50,6 +50,7 @@ lib LibC end fun accept(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int + fun accept4(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*, x3 : Int) : Int fun bind(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun connect(x0 : Int, x1 : Sockaddr*, x2 : SocklenT) : Int fun getpeername(x0 : Int, x1 : Sockaddr*, x2 : SocklenT*) : Int diff --git a/src/socket/unix_socket.cr b/src/socket/unix_socket.cr index 0639dce97ca9..e672d812f631 100644 --- a/src/socket/unix_socket.cr +++ b/src/socket/unix_socket.cr @@ -75,13 +75,18 @@ class UNIXSocket < Socket socktype = type.value {% if LibC.has_constant?(:SOCK_CLOEXEC) %} - socktype |= LibC::SOCK_CLOEXEC + socktype |= LibC::SOCK_CLOEXEC {% end %} if LibC.socketpair(Family::UNIX, socktype, 0, fds) != 0 - raise Socket::Error.new("socketpair:") + raise Socket::Error.new("socketpair() failed") end + {% unless LibC.has_constant?(:SOCK_CLOEXEC) %} + Crystal::System::Socket.fcntl(fds[0], LibC::F_SETFD, LibC::FD_CLOEXEC) + Crystal::System::Socket.fcntl(fds[1], LibC::F_SETFD, LibC::FD_CLOEXEC) + {% end %} + {UNIXSocket.new(fd: fds[0], type: type), UNIXSocket.new(fd: fds[1], type: type)} {% end %} end From 47e7b16baedb30c81bc4e2e4617af40c470b91df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 14 Jun 2024 16:37:59 +0200 Subject: [PATCH 1184/1551] Add type restriction `host : String` in `TCPSocket` and `Addrinfo` (#14703) --- src/socket/addrinfo.cr | 12 ++++++------ src/socket/tcp_socket.cr | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr index 01415d9b642c..83ef561c88ac 100644 --- a/src/socket/addrinfo.cr +++ b/src/socket/addrinfo.cr @@ -30,7 +30,7 @@ class Socket # # addrinfos = Socket::Addrinfo.resolve("example.org", "http", type: Socket::Type::STREAM, protocol: Socket::Protocol::TCP) # ``` - def self.resolve(domain, service, family : Family? = nil, type : Type = nil, protocol : Protocol = Protocol::IP, timeout = nil) : Array(Addrinfo) + def self.resolve(domain : String, service, family : Family? = nil, type : Type = nil, protocol : Protocol = Protocol::IP, timeout = nil) : Array(Addrinfo) addrinfos = [] of Addrinfo getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo| @@ -56,7 +56,7 @@ class Socket # # The iteration will be stopped once the block returns something that isn't # an `Exception` (e.g. a `Socket` or `nil`). - def self.resolve(domain, service, family : Family? = nil, type : Type = nil, protocol : Protocol = Protocol::IP, timeout = nil, &) + def self.resolve(domain : String, service, family : Family? = nil, type : Type = nil, protocol : Protocol = Protocol::IP, timeout = nil, &) getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo| loop do value = yield addrinfo.not_nil! @@ -196,13 +196,13 @@ class Socket # # addrinfos = Socket::Addrinfo.tcp("example.org", 80) # ``` - def self.tcp(domain, service, family = Family::UNSPEC, timeout = nil) : Array(Addrinfo) + def self.tcp(domain : String, service, family = Family::UNSPEC, timeout = nil) : Array(Addrinfo) resolve(domain, service, family, Type::STREAM, Protocol::TCP) end # Resolves a domain for the TCP protocol with STREAM type, and yields each # possible `Addrinfo`. See `#resolve` for details. - def self.tcp(domain, service, family = Family::UNSPEC, timeout = nil, &) + def self.tcp(domain : String, service, family = Family::UNSPEC, timeout = nil, &) resolve(domain, service, family, Type::STREAM, Protocol::TCP) { |addrinfo| yield addrinfo } end @@ -215,13 +215,13 @@ class Socket # # addrinfos = Socket::Addrinfo.udp("example.org", 53) # ``` - def self.udp(domain, service, family = Family::UNSPEC, timeout = nil) : Array(Addrinfo) + def self.udp(domain : String, service, family = Family::UNSPEC, timeout = nil) : Array(Addrinfo) resolve(domain, service, family, Type::DGRAM, Protocol::UDP) end # Resolves a domain for the UDP protocol with DGRAM type, and yields each # possible `Addrinfo`. See `#resolve` for details. - def self.udp(domain, service, family = Family::UNSPEC, timeout = nil, &) + def self.udp(domain : String, service, family = Family::UNSPEC, timeout = nil, &) resolve(domain, service, family, Type::DGRAM, Protocol::UDP) { |addrinfo| yield addrinfo } end diff --git a/src/socket/tcp_socket.cr b/src/socket/tcp_socket.cr index 06e3d6f9b138..387417211a1a 100644 --- a/src/socket/tcp_socket.cr +++ b/src/socket/tcp_socket.cr @@ -26,7 +26,7 @@ class TCPSocket < IPSocket # must be in seconds (integers or floats). # # Note that `dns_timeout` is currently ignored. - def initialize(host, port, dns_timeout = nil, connect_timeout = nil, blocking = false) + def initialize(host : String, port, dns_timeout = nil, connect_timeout = nil, blocking = false) Addrinfo.tcp(host, port, timeout: dns_timeout) do |addrinfo| super(addrinfo.family, addrinfo.type, addrinfo.protocol, blocking) connect(addrinfo, timeout: connect_timeout) do |error| @@ -53,7 +53,7 @@ class TCPSocket < IPSocket # eventually closes the socket when the block returns. # # Returns the value of the block. - def self.open(host, port, &) + def self.open(host : String, port, &) sock = new(host, port) begin yield sock From c2dd54806ee8bd0489206e87020104d46dfe8ef5 Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Fri, 14 Jun 2024 11:39:28 -0300 Subject: [PATCH 1185/1551] Improve compile time error for `#sort(&block : T, T -> U)` (#14693) --- src/array.cr | 8 ++++---- src/slice.cr | 8 ++++---- src/static_array.cr | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/array.cr b/src/array.cr index cef70c4704c7..30425c6869e3 100644 --- a/src/array.cr +++ b/src/array.cr @@ -1658,7 +1658,7 @@ class Array(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def sort(&block : T, T -> U) : Array(T) forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#sort_by`?" %} {% end %} dup.sort! &block @@ -1680,7 +1680,7 @@ class Array(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def unstable_sort(&block : T, T -> U) : Array(T) forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#unstable_sort_by`?" %} {% end %} dup.unstable_sort!(&block) @@ -1701,7 +1701,7 @@ class Array(T) # :inherit: def sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#sort_by!`?" %} {% end %} to_unsafe_slice.sort!(&block) @@ -1711,7 +1711,7 @@ class Array(T) # :inherit: def unstable_sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#unstable_sort_by!`?" %} {% end %} to_unsafe_slice.unstable_sort!(&block) diff --git a/src/slice.cr b/src/slice.cr index 7a27218221a2..196a29a768dd 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -932,7 +932,7 @@ struct Slice(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def sort(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#sort_by`?" %} {% end %} dup.sort! &block @@ -954,7 +954,7 @@ struct Slice(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def unstable_sort(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#unstable_sort_by`?" %} {% end %} dup.unstable_sort!(&block) @@ -1055,7 +1055,7 @@ struct Slice(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#sort_by!`?" %} {% end %} Slice.merge_sort!(self, block) @@ -1098,7 +1098,7 @@ struct Slice(T) # Raises `ArgumentError` if for any two elements the block returns `nil`. def unstable_sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#unstable_sort_by!`?" %} {% end %} Slice.intro_sort!(to_unsafe, size, block) diff --git a/src/static_array.cr b/src/static_array.cr index 4cb2b186f200..2c09e21df166 100644 --- a/src/static_array.cr +++ b/src/static_array.cr @@ -228,7 +228,7 @@ struct StaticArray(T, N) # Raises `ArgumentError` if for any two elements the block returns `nil`.= def sort(&block : T, T -> U) : StaticArray(T, N) forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#sort_by`?" %} {% end %} ary = dup @@ -251,7 +251,7 @@ struct StaticArray(T, N) # Raises `ArgumentError` if for any two elements the block returns `nil`. def unstable_sort(&block : T, T -> U) : StaticArray(T, N) forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#unstable_sort_by`?" %} {% end %} ary = dup @@ -273,7 +273,7 @@ struct StaticArray(T, N) # :inherit: def sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#sort_by!`?" %} {% end %} to_slice.sort!(&block) @@ -283,7 +283,7 @@ struct StaticArray(T, N) # :inherit: def unstable_sort!(&block : T, T -> U) : self forall U {% unless U <= Int32? %} - {% raise "Expected block to return Int32 or Nil, not #{U}" %} + {% raise "Expected block to return Int32 or Nil, not #{U}.\nThe block is supposed to be a custom comparison operation, compatible with `Comparable#<=>`.\nDid you mean to use `#unstable_sort_by!`?" %} {% end %} to_slice.unstable_sort!(&block) From 0de7abfdfa21a0e8a4fd6f528bf62dbe558d60eb Mon Sep 17 00:00:00 2001 From: Linus Sellberg Date: Sat, 15 Jun 2024 10:11:56 +0200 Subject: [PATCH 1186/1551] Drop obsolete workaround in `Range#reverse_each` (#14709) The workaround is no longer needed since Crystal 0.36 --- src/range.cr | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/range.cr b/src/range.cr index db1b3f32902a..39d8119dff6e 100644 --- a/src/range.cr +++ b/src/range.cr @@ -163,17 +163,15 @@ struct Range(B, E) yield end_value if !@exclusive && (begin_value.nil? || !(end_value < begin_value)) current = end_value - # TODO: The macro interpolations are a workaround until #9324 is fixed. - {% if B == Nil %} while true current = current.pred - {{ "yield current".id }} + yield current end {% else %} while begin_value.nil? || begin_value < current current = current.pred - {{ "yield current".id }} + yield current end {% end %} end From 42ed5c46f2b8cbffd47d654cefff2de4abea43f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Hovs=C3=A4ter?= Date: Sat, 15 Jun 2024 16:42:16 +0200 Subject: [PATCH 1187/1551] Do not strip the macOS target triple (#14466) Starting with Xcode 15, the minimum deployment target is required in the target triple. With the release of Xcode 15, a [new linker was introduced](https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#Linking) and with it came warning messages like this: ld: warning: no platform load command found in '/Users/pascal/.cache/crystal/Users-pascal-Documents-tutorials-crystal-hello_world.cr/F-loat32.o', assuming: macOS It appears that the new linker is stricter in which target triples it considers valid. Specifically, it looks like the minimum deployment target is required. E.g., `aarch64-apple-darwin23.3.0` is valid, while `aarch64-apple-darwin` is not. See https://github.com/crystal-lang/crystal/issues/13846#issuecomment-2040146898 for details. This patch removes code which strips the minimum deployment target in `LLVM.default_target_triple` as an effort for standardization. See also: https://github.com/crystal-lang/distribution-scripts/pull/296 --- spec/std/llvm/llvm_spec.cr | 2 +- src/llvm.cr | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/spec/std/llvm/llvm_spec.cr b/spec/std/llvm/llvm_spec.cr index d232931db848..17ea96d5e261 100644 --- a/spec/std/llvm/llvm_spec.cr +++ b/spec/std/llvm/llvm_spec.cr @@ -22,7 +22,7 @@ describe LLVM do it ".default_target_triple" do triple = LLVM.default_target_triple {% if flag?(:darwin) %} - triple.should match(/-apple-macosx$/) + triple.should match(/-apple-(darwin|macosx)/) {% elsif flag?(:android) %} triple.should match(/-android$/) {% elsif flag?(:linux) %} diff --git a/src/llvm.cr b/src/llvm.cr index 6ad1bf6c796d..6fb8767cad54 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -107,12 +107,6 @@ module LLVM def self.default_target_triple : String chars = LibLLVM.get_default_target_triple case triple = string_and_dispose(chars) - when .starts_with?("x86_64-apple-macosx"), .starts_with?("x86_64-apple-darwin") - # normalize on `macosx` and remove minimum deployment target version - "x86_64-apple-macosx" - when .starts_with?("aarch64-apple-macosx"), .starts_with?("aarch64-apple-darwin") - # normalize on `macosx` and remove minimum deployment target version - "aarch64-apple-macosx" when .starts_with?("aarch64-unknown-linux-android") # remove API version "aarch64-unknown-linux-android" From ff014992554c19cf33485f9e6f37abc5d192fc4d Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sat, 15 Jun 2024 17:08:12 +0200 Subject: [PATCH 1188/1551] Add `Crystal::Tracing` for runtime tracing (#14659) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements tracing of the garbage collector and the scheduler as per #14618 Tracing is enabled by compiling with `-Dtracing` then individual tracing must be enabled at runtime with the `CRYSTAL_TRACE` environment variable that is a comma separated list of sections to enable, for example: - ` ` (empty value) or `none` to disable any tracing (default) - `gc` - `sched` - `gc,sched` - `all` to enable everything The traces are printed to the standard error by default, but this can be changed at runtime with the `CRYSTAL_TRACE_FILE` environment variable. For example `trace.log`. You can also redirect the standard error to a file (e.g. `2> trace.log` on UNIX shell). Example tracing calls: Crystal.trace :sched, "spawn", fiber: fiber Crystal.trace :gc, "malloc", size: size, atomic: 1 **Technical note:** tracing happens before the stdlib is initialized, so the implementation must rely on some `LibC` methods directly (i.e. read environment variable, write to file descriptor) and can't use the core/stdlib abstractions. Co-authored-by: Johannes Müller --- src/concurrent.cr | 2 + src/crystal/main.cr | 1 + src/crystal/scheduler.cr | 39 +++-- src/crystal/system/print_error.cr | 33 +++- src/crystal/tracing.cr | 272 ++++++++++++++++++++++++++++++ src/gc/boehm.cr | 99 ++++++++++- src/gc/none.cr | 5 + 7 files changed, 430 insertions(+), 21 deletions(-) create mode 100644 src/crystal/tracing.cr diff --git a/src/concurrent.cr b/src/concurrent.cr index af2f0aecf736..6f3a58291a22 100644 --- a/src/concurrent.cr +++ b/src/concurrent.cr @@ -1,6 +1,7 @@ require "fiber" require "channel" require "crystal/scheduler" +require "crystal/tracing" # Blocks the current fiber for the specified number of seconds. # @@ -59,6 +60,7 @@ end # ``` def spawn(*, name : String? = nil, same_thread = false, &block) fiber = Fiber.new(name, &block) + Crystal.trace :sched, "spawn", fiber: fiber {% if flag?(:preview_mt) %} fiber.set_current_thread if same_thread {% end %} fiber.enqueue fiber diff --git a/src/crystal/main.cr b/src/crystal/main.cr index 059a822c5ff4..625238229c58 100644 --- a/src/crystal/main.cr +++ b/src/crystal/main.cr @@ -34,6 +34,7 @@ module Crystal # same can be accomplished with `at_exit`. But in some cases # redefinition of C's main is needed. def self.main(&block) + {% if flag?(:tracing) %} Crystal::Tracing.init {% end %} GC.init status = diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index 4796226ce8e9..d3634e9aea6a 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -25,21 +25,23 @@ class Crystal::Scheduler end def self.enqueue(fiber : Fiber) : Nil - thread = Thread.current - scheduler = thread.scheduler + Crystal.trace :sched, "enqueue", fiber: fiber do + thread = Thread.current + scheduler = thread.scheduler - {% if flag?(:preview_mt) %} - th = fiber.get_current_thread - th ||= fiber.set_current_thread(scheduler.find_target_thread) + {% if flag?(:preview_mt) %} + th = fiber.get_current_thread + th ||= fiber.set_current_thread(scheduler.find_target_thread) - if th == thread + if th == thread + scheduler.enqueue(fiber) + else + th.scheduler.send_fiber(fiber) + end + {% else %} scheduler.enqueue(fiber) - else - th.scheduler.send_fiber(fiber) - end - {% else %} - scheduler.enqueue(fiber) - {% end %} + {% end %} + end end def self.enqueue(fibers : Enumerable(Fiber)) : Nil @@ -49,6 +51,7 @@ class Crystal::Scheduler end def self.reschedule : Nil + Crystal.trace :sched, "reschedule" Thread.current.scheduler.reschedule end @@ -58,10 +61,13 @@ class Crystal::Scheduler end def self.sleep(time : Time::Span) : Nil + Crystal.trace :sched, "sleep", for: time.total_nanoseconds.to_i64! Thread.current.scheduler.sleep(time) end def self.yield : Nil + Crystal.trace :sched, "yield" + # TODO: Fiber switching and libevent for wasm32 {% unless flag?(:wasm32) %} Thread.current.scheduler.sleep(0.seconds) @@ -109,6 +115,7 @@ class Crystal::Scheduler end protected def resume(fiber : Fiber) : Nil + Crystal.trace :sched, "resume", fiber: fiber validate_resumable(fiber) {% if flag?(:preview_mt) %} @@ -149,7 +156,9 @@ class Crystal::Scheduler resume(runnable) unless runnable == @thread.current_fiber break else - @event_loop.run(blocking: true) + Crystal.trace :sched, "event_loop" do + @event_loop.run(blocking: true) + end end end end @@ -190,7 +199,9 @@ class Crystal::Scheduler else @sleeping = true @lock.unlock - fiber = fiber_channel.receive + + Crystal.trace :sched, "mt:sleeping" + fiber = Crystal.trace(:sched, "mt:slept") { fiber_channel.receive } @lock.lock @sleeping = false diff --git a/src/crystal/system/print_error.cr b/src/crystal/system/print_error.cr index f58bef1c4ff6..796579bf256a 100644 --- a/src/crystal/system/print_error.cr +++ b/src/crystal/system/print_error.cr @@ -14,6 +14,37 @@ module Crystal::System {% end %} end + # Print a UTF-16 slice as UTF-8 directly to stderr. Useful on Windows to print + # strings returned from the unicode variant of the Win32 API. + def self.print_error(bytes : Slice(UInt16)) : Nil + utf8 = uninitialized UInt8[512] + appender = utf8.to_unsafe.appender + + String.each_utf16_char(bytes) do |char| + if appender.size > utf8.size - char.bytesize + # buffer is full (char won't fit) + print_error utf8.to_slice[0...appender.size] + appender = utf8.to_unsafe.appender + end + + char.each_byte do |byte| + appender << byte + end + end + + if appender.size > 0 + print_error utf8.to_slice[0...appender.size] + end + end + + def self.print(handle : FileDescriptor::Handle, bytes : Bytes) : Nil + {% if flag?(:unix) || flag?(:wasm32) %} + LibC.write handle, bytes, bytes.size + {% elsif flag?(:win32) %} + LibC.WriteFile(Pointer(FileDescriptor::Handle).new(handle), bytes, bytes.size, out _, nil) + {% end %} + end + # Minimal drop-in replacement for C `printf` function. Yields successive # non-empty `Bytes` to the block, which should do the actual printing. # @@ -109,7 +140,7 @@ module Crystal::System end # simplified version of `Int#internal_to_s` - private def self.to_int_slice(num, base, signed, width, &) + protected def self.to_int_slice(num, base, signed, width, &) if num == 0 yield "0".to_slice return diff --git a/src/crystal/tracing.cr b/src/crystal/tracing.cr new file mode 100644 index 000000000000..708956ad8feb --- /dev/null +++ b/src/crystal/tracing.cr @@ -0,0 +1,272 @@ +module Crystal + # :nodoc: + module Tracing + @[Flags] + enum Section + GC + Sched + + def self.from_id(slice) : self + {% begin %} + case slice + {% for name in @type.constants %} + when {{name.underscore.stringify}}.to_slice + {{name}} + {% end %} + else + None + end + {% end %} + end + + def to_id : String + {% begin %} + case self + {% for name in @type.constants %} + when {{name}} + {{name.underscore.stringify}} + {% end %} + else + "???" + end + {% end %} + end + end + end + + {% if flag?(:tracing) %} + # :nodoc: + module Tracing + # IO-like object with a fixed capacity but dynamic size within the + # buffer's capacity (i.e. `0 <= size <= N`). Stops writing to the internal + # buffer when capacity is reached; further writes are skipped. + struct BufferIO(N) + getter size : Int32 + + def initialize + @buf = uninitialized UInt8[N] + @size = 0 + end + + def write(bytes : 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) + @size = pos + n + end + + def write(string : String) : Nil + write string.to_slice + end + + def write(fiber : Fiber) : Nil + write fiber.as(Void*) + write ":" + write fiber.name || "?" + end + + def write(ptr : Pointer) : Nil + write "0x" + System.to_int_slice(ptr.address, 16, true, 2) { |bytes| write(bytes) } + end + + def write(int : Int::Signed) : Nil + System.to_int_slice(int, 10, true, 2) { |bytes| write(bytes) } + end + + def write(uint : Int::Unsigned) : Nil + System.to_int_slice(uint, 10, false, 2) { |bytes| write(bytes) } + end + + def to_slice : Bytes + Bytes.new(@buf.to_unsafe, @size) + end + end + + @@sections = Section::None + @@handle = uninitialized System::FileDescriptor::Handle + + @[AlwaysInline] + def self.enabled?(section : Section) : Bool + @@sections.includes?(section) + end + + # Setup tracing. + # + # Parses the `CRYSTAL_TRACE` environment variable to enable the sections + # to trace. See `Section`. By default no sections are enabled. + # + # Parses the `CRYSTAL_TRACE_FILE` environment variable to open the trace + # file to write to. Exits with an error message when the file can't be + # opened, created or truncated. Uses the standard error when unspecified. + # + # This should be the first thing called in main, maybe even before the GC + # itself is initialized. The function assumes neither the GC nor ENV nor + # anything is available and musn't allocate into the GC HEAP. + def self.init : Nil + @@sections = Section::None + + {% if flag?(:win32) %} + buf = uninitialized UInt16[256] + + name = UInt16.static_array({% for chr in "CRYSTAL_TRACE".chars %}{{chr.ord}}, {% end %} 0) + len = LibC.GetEnvironmentVariableW(name, buf, buf.size) + parse_sections(buf.to_slice[0...len]) if len > 0 + + name = UInt16.static_array({% for chr in "CRYSTAL_TRACE_FILE".chars %}{{chr.ord}}, {% end %} 0) + len = LibC.GetEnvironmentVariableW(name, buf, buf.size) + if len > 0 + @@handle = open_trace_file(buf.to_slice[0...len]) + else + @@handle = LibC.GetStdHandle(LibC::STD_ERROR_HANDLE).address + end + {% else %} + if ptr = LibC.getenv("CRYSTAL_TRACE") + len = LibC.strlen(ptr) + parse_sections(Slice.new(ptr, len)) if len > 0 + end + + if (ptr = LibC.getenv("CRYSTAL_TRACE_FILE")) && (LibC.strlen(ptr) > 0) + @@handle = open_trace_file(ptr) + else + @@handle = 2 + end + {% end %} + end + + private def self.open_trace_file(filename) + {% if flag?(:win32) %} + handle = LibC.CreateFileW(filename, LibC::FILE_GENERIC_WRITE, LibC::DEFAULT_SHARE_MODE, nil, LibC::CREATE_ALWAYS, LibC::FILE_ATTRIBUTE_NORMAL, LibC::HANDLE.null) + # not using LibC::INVALID_HANDLE_VALUE because it doesn't exist (yet) + return handle.address unless handle == LibC::HANDLE.new(-1) + + error = uninitialized UInt16[256] + len = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, WinError.value, 0, error, error.size, nil) + + # not using printf because filename and error are UTF-16 slices: + System.print_error "ERROR: failed to open " + System.print_error filename + System.print_error " for writing: " + System.print_error error.to_slice[0...len] + System.print_error "\n" + {% else %} + fd = LibC.open(filename, LibC::O_CREAT | LibC::O_WRONLY | LibC::O_TRUNC | LibC::O_CLOEXEC, 0o644) + return fd unless fd < 0 + + System.print_error "ERROR: failed to open %s for writing: %s\n", filename, LibC.strerror(Errno.value) + {% end %} + + LibC._exit(1) + end + + private def self.parse_sections(slice) + each_token(slice) do |token| + @@sections |= Section.from_id(token) + end + end + + private def self.each_token(slice, delim = ',', &) + while e = slice.index(delim.ord) + yield slice[0, e] + slice = slice[(e + 1)..] + end + yield slice[0..] unless slice.size == 0 + end + + # :nodoc: + # + # Formats and prints a log message to stderr. The generated message is + # limited to 512 bytes (PIPE_BUF) after which it will be truncated. Being + # below PIPE_BUF the message shall be written atomically to stderr, + # avoiding interleaved or smashed traces from multiple threads. + # + # Windows may not have the same guarantees but the buffering should limit + # these from happening. + def self.log(section : String, operation : String, time : UInt64, **metadata) : Nil + buf = BufferIO(512).new + buf.write section + buf.write "." + buf.write operation + buf.write " " + buf.write time + + {% unless flag?(:wasm32) %} + # WASM doesn't have threads (and fibers aren't implemented either) + # + # We also start to trace *before* Thread.current and other objects have + # been allocated, they're lazily allocated and since we trace GC.malloc we + # must skip the objects until they're allocated (otherwise we hit infinite + # recursion): malloc -> trace -> malloc -> trace -> ... + thread = ::Thread.current? + + buf.write " thread=" + {% if flag?(:linux) %} + buf.write Pointer(Void).new(thread ? thread.@system_handle : System::Thread.current_handle) + {% else %} + buf.write thread ? thread.@system_handle : System::Thread.current_handle + {% end %} + buf.write ":" + buf.write thread.try(&.name) || "?" + + if thread && (fiber = thread.current_fiber?) + buf.write " fiber=" + buf.write fiber + end + {% end %} + + metadata.each do |key, value| + buf.write " " + buf.write key.to_s + buf.write "=" + buf.write value + end + + buf.write "\n" + System.print(@@handle, buf.to_slice) + end + end + + def self.trace(section : Tracing::Section, operation : String, time : UInt64? = nil, **metadata, &) + if Tracing.enabled?(section) + time ||= System::Time.ticks + begin + yield + ensure + duration = System::Time.ticks - time + Tracing.log(section.to_id, operation, time, **metadata, duration: duration) + end + else + yield + end + end + + def self.trace(section : Tracing::Section, operation : String, time : UInt64? = nil, **metadata) : Nil + if Tracing.enabled?(section) + Tracing.log(section.to_id, operation, time || System::Time.ticks, **metadata) + end + end + {% else %} + # :nodoc: + module Tracing + def self.init + end + + def self.enabled?(section) + false + end + + def self.log(section : String, operation : String, time : UInt64, **metadata) + end + end + + def self.trace(section : Tracing::Section, operation : String, time : UInt64? = nil, **metadata, &) + yield + end + + def self.trace(section : Tracing::Section, operation : String, time : UInt64? = nil, **metadata) + end + {% end %} +end diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 29ae825adab1..8ccc1bb7b6e8 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -1,6 +1,7 @@ {% if flag?(:preview_mt) %} require "crystal/rw_lock" {% end %} +require "crystal/tracing" # MUSL: On musl systems, libpthread is empty. The entire library is already included in libc. # The empty library is only available for POSIX compatibility. We don't need to link it. @@ -113,7 +114,32 @@ lib LibGC $stackbottom = GC_stackbottom : Void* {% end %} - fun set_on_collection_event = GC_set_on_collection_event(cb : ->) + alias OnHeapResizeProc = Word -> + fun set_on_heap_resize = GC_set_on_heap_resize(OnHeapResizeProc) + fun get_on_heap_resize = GC_get_on_heap_resize : OnHeapResizeProc + + enum EventType + START # COLLECTION + MARK_START + MARK_END + RECLAIM_START + RECLAIM_END + END # COLLECTION + PRE_STOP_WORLD # STOPWORLD_BEGIN + POST_STOP_WORLD # STOPWORLD_END + PRE_START_WORLD # STARTWORLD_BEGIN + POST_START_WORLD # STARTWORLD_END + THREAD_SUSPENDED + THREAD_UNSUSPENDED + end + + alias OnCollectionEventProc = EventType -> + fun set_on_collection_event = GC_set_on_collection_event(cb : OnCollectionEventProc) + fun get_on_collection_event = GC_get_on_collection_event : OnCollectionEventProc + + alias OnThreadEventProc = EventType, Void* -> + fun set_on_thread_event = GC_set_on_thread_event(cb : OnThreadEventProc) + fun get_on_thread_event = GC_get_on_thread_event : OnThreadEventProc $gc_no = GC_gc_no : Word $bytes_found = GC_bytes_found : SignedWord @@ -144,17 +170,23 @@ module GC # :nodoc: def self.malloc(size : LibC::SizeT) : Void* - LibGC.malloc(size) + Crystal.trace :gc, "malloc", size: size do + LibGC.malloc(size) + end end # :nodoc: def self.malloc_atomic(size : LibC::SizeT) : Void* - LibGC.malloc_atomic(size) + Crystal.trace :gc, "malloc", size: size, atomic: 1 do + LibGC.malloc_atomic(size) + end end # :nodoc: def self.realloc(ptr : Void*, size : LibC::SizeT) : Void* - LibGC.realloc(ptr, size) + Crystal.trace :gc, "realloc", size: size do + LibGC.realloc(ptr, size) + end end def self.init : Nil @@ -166,6 +198,14 @@ module GC LibGC.set_start_callback ->do GC.lock_write end + + {% if flag?(:tracing) %} + if ::Crystal::Tracing.enabled?(:gc) + set_on_heap_resize_proc + set_on_collection_events_proc + end + {% end %} + # By default the GC warns on big allocations/reallocations. This # is of limited use and pollutes program output with warnings. LibGC.set_warn_proc ->(msg, v) do @@ -178,8 +218,53 @@ module GC end end + {% if flag?(:tracing) %} + @@on_heap_resize : LibGC::OnHeapResizeProc? + @@on_collection_event : LibGC::OnCollectionEventProc? + + @@collect_start = 0_u64 + @@mark_start = 0_u64 + @@sweep_start = 0_u64 + + private def self.set_on_heap_resize_proc : Nil + @@on_heap_resize = LibGC.get_on_heap_resize + + LibGC.set_on_heap_resize(->(new_size : LibGC::Word) { + Crystal.trace :gc, "heap_resize", size: new_size + @@on_heap_resize.try(&.call(new_size)) + }) + end + + private def self.set_on_collection_events_proc : Nil + @@on_collection_event = LibGC.get_on_collection_event + + LibGC.set_on_collection_event(->(event_type : LibGC::EventType) { + case event_type + when .start? + @@collect_start = Crystal::System::Time.ticks + when .mark_start? + @@mark_start = Crystal::System::Time.ticks + when .reclaim_start? + @@sweep_start = Crystal::System::Time.ticks + when .end? + duration = Crystal::System::Time.ticks - @@collect_start + Crystal.trace :gc, "collect", @@collect_start, duration: duration + when .mark_end? + duration = Crystal::System::Time.ticks - @@mark_start + Crystal.trace :gc, "collect:mark", @@mark_start, duration: duration + when .reclaim_end? + duration = Crystal::System::Time.ticks - @@sweep_start + Crystal.trace :gc, "collect:sweep", @@sweep_start, duration: duration + end + @@on_collection_event.try(&.call(event_type)) + }) + end + {% end %} + def self.collect - LibGC.collect + Crystal.trace :gc, "collect" do + LibGC.collect + end end def self.enable @@ -195,7 +280,9 @@ module GC end def self.free(pointer : Void*) : Nil - LibGC.free(pointer) + Crystal.trace :gc, "free" do + LibGC.free(pointer) + end end def self.add_finalizer(object : Reference) : Nil diff --git a/src/gc/none.cr b/src/gc/none.cr index c71ab05ccd8d..1121caef1bf4 100644 --- a/src/gc/none.cr +++ b/src/gc/none.cr @@ -1,6 +1,7 @@ {% if flag?(:win32) %} require "c/process" {% end %} +require "crystal/tracing" module GC def self.init @@ -8,16 +9,19 @@ module GC # :nodoc: def self.malloc(size : LibC::SizeT) : Void* + Crystal.trace :gc, "malloc", size: size LibC.malloc(size) end # :nodoc: def self.malloc_atomic(size : LibC::SizeT) : Void* + Crystal.trace :gc, "malloc", size: size, atomic: 1 LibC.malloc(size) end # :nodoc: def self.realloc(pointer : Void*, size : LibC::SizeT) : Void* + Crystal.trace :gc, "realloc", size: size LibC.realloc(pointer, size) end @@ -31,6 +35,7 @@ module GC end def self.free(pointer : Void*) : Nil + Crystal.trace :gc, "free" LibC.free(pointer) end From 6a2097d4478590b2c3a5fb0a54363ce39b82c14a Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Sun, 16 Jun 2024 04:59:12 -0400 Subject: [PATCH 1189/1551] Fix formatter to skip trailing comma for single-line parameters (#14713) --- spec/compiler/formatter/formatter_spec.cr | 35 +++++++++++++++++++++++ src/compiler/crystal/tools/formatter.cr | 4 +-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index e4e76279f4d5..7bb7a1034e72 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1120,6 +1120,41 @@ describe Crystal::Formatter do ) end CRYSTAL + + assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo(a) + end + CRYSTAL + + assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo(a, b) + end + CRYSTAL + + assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo(a, *args) + end + CRYSTAL + + assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo(a, *args, &block) + end + CRYSTAL + + assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo(a, **kwargs) + end + CRYSTAL + + assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo(a, **kwargs, &block) + end + CRYSTAL + + assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + def foo(a, &block) + end + CRYSTAL end assert_format "1 + 2", "1 + 2" diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 614ecc836637..dc14c70a90ad 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1562,7 +1562,7 @@ module Crystal args.each_with_index do |arg, i| has_more = !last?(i, args) || double_splat || block_arg || yields || variadic - wrote_newline = format_def_arg(wrote_newline, has_more, true) do + wrote_newline = format_def_arg(wrote_newline, has_more, found_first_newline && !has_more) do if i == splat_index write_token :OP_STAR skip_space_or_newline @@ -1577,7 +1577,7 @@ module Crystal end if double_splat - wrote_newline = format_def_arg(wrote_newline, block_arg || yields, true) do + wrote_newline = format_def_arg(wrote_newline, block_arg || yields, found_first_newline) do write_token :OP_STAR_STAR skip_space_or_newline From bcb5aeb5d2c432ccb1e5e2385189ed15599e8ba8 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sun, 16 Jun 2024 10:59:34 +0200 Subject: [PATCH 1190/1551] Add `Thread.sleep(Time::Span)` (#14715) Blocks the current thread for the given duration. --- src/crystal/system/thread.cr | 8 ++++++++ src/crystal/system/unix/pthread.cr | 12 ++++++++++++ src/crystal/system/wasi/thread.cr | 12 ++++++++++++ src/crystal/system/win32/thread.cr | 4 ++++ src/lib_c/aarch64-android/c/time.cr | 1 + src/lib_c/aarch64-darwin/c/time.cr | 1 + src/lib_c/aarch64-linux-gnu/c/time.cr | 1 + src/lib_c/aarch64-linux-musl/c/time.cr | 1 + src/lib_c/arm-linux-gnueabihf/c/time.cr | 1 + src/lib_c/i386-linux-gnu/c/time.cr | 1 + src/lib_c/i386-linux-musl/c/time.cr | 1 + src/lib_c/wasm32-wasi/c/time.cr | 1 + src/lib_c/x86_64-darwin/c/time.cr | 1 + src/lib_c/x86_64-dragonfly/c/time.cr | 1 + src/lib_c/x86_64-freebsd/c/time.cr | 1 + src/lib_c/x86_64-linux-gnu/c/time.cr | 1 + src/lib_c/x86_64-linux-musl/c/time.cr | 1 + src/lib_c/x86_64-netbsd/c/time.cr | 1 + src/lib_c/x86_64-openbsd/c/time.cr | 1 + src/lib_c/x86_64-solaris/c/time.cr | 1 + 20 files changed, 52 insertions(+) diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index 03b00f2779f4..d9dc6acf17dc 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -14,6 +14,8 @@ module Crystal::System::Thread # def self.current_thread=(thread : ::Thread) + # def self.sleep(time : ::Time::Span) : Nil + # private def system_join : Exception? # private def system_close @@ -99,6 +101,12 @@ class Thread end end + # Blocks the current thread for the duration of *time*. Clock precision is + # dependent on the operating system and hardware. + def self.sleep(time : Time::Span) : Nil + Crystal::System::Thread.sleep(time) + end + # Returns the Thread object associated to the running system thread. def self.current : Thread Crystal::System::Thread.current_thread diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index 4b357b04281c..d38e52ee012a 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -75,6 +75,18 @@ module Crystal::System::Thread end {% end %} + def self.sleep(time : ::Time::Span) : Nil + req = uninitialized LibC::Timespec + req.tv_sec = typeof(req.tv_sec).new(time.seconds) + req.tv_nsec = typeof(req.tv_nsec).new(time.nanoseconds) + + loop do + return if LibC.nanosleep(pointerof(req), out rem) == 0 + raise RuntimeError.from_errno("nanosleep() failed") unless Errno.value == Errno::EINTR + req = rem + end + end + private def system_join : Exception? ret = GC.pthread_join(@system_handle) RuntimeError.from_os_error("pthread_join", Errno.new(ret)) unless ret == 0 diff --git a/src/crystal/system/wasi/thread.cr b/src/crystal/system/wasi/thread.cr index 0e641faba785..6f0c0cbe8260 100644 --- a/src/crystal/system/wasi/thread.cr +++ b/src/crystal/system/wasi/thread.cr @@ -15,6 +15,18 @@ module Crystal::System::Thread class_property current_thread : ::Thread { ::Thread.new } + def self.sleep(time : ::Time::Span) : Nil + req = uninitialized LibC::Timespec + req.tv_sec = typeof(req.tv_sec).new(time.seconds) + req.tv_nsec = typeof(req.tv_nsec).new(time.nanoseconds) + + loop do + return if LibC.nanosleep(pointerof(req), out rem) == 0 + raise RuntimeError.from_errno("nanosleep() failed") unless Errno.value == Errno::EINTR + req = rem + end + end + private def system_join : Exception? NotImplementedError.new("Crystal::System::Thread#system_join") end diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr index 9507e332b422..ddfe3298b20a 100644 --- a/src/crystal/system/win32/thread.cr +++ b/src/crystal/system/win32/thread.cr @@ -51,6 +51,10 @@ module Crystal::System::Thread @@current_thread end + def self.sleep(time : ::Time::Span) : Nil + LibC.Sleep(time.total_milliseconds.to_i.clamp(1..)) + end + private def system_join : Exception? if LibC.WaitForSingleObject(@system_handle, LibC::INFINITE) != LibC::WAIT_OBJECT_0 return RuntimeError.from_winerror("WaitForSingleObject") diff --git a/src/lib_c/aarch64-android/c/time.cr b/src/lib_c/aarch64-android/c/time.cr index 3108f2e94bff..8f8b81291f0d 100644 --- a/src/lib_c/aarch64-android/c/time.cr +++ b/src/lib_c/aarch64-android/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(__t : TimeT*, __tm : Tm*) : Tm* fun localtime_r(__t : TimeT*, __tm : Tm*) : Tm* fun mktime(__tm : Tm*) : TimeT + fun nanosleep(__req : Timespec*, __rem : Timespec*) : Int fun tzset : Void fun timegm(__tm : Tm*) : TimeT diff --git a/src/lib_c/aarch64-darwin/c/time.cr b/src/lib_c/aarch64-darwin/c/time.cr index e20477a6a004..7e76fb969fbe 100644 --- a/src/lib_c/aarch64-darwin/c/time.cr +++ b/src/lib_c/aarch64-darwin/c/time.cr @@ -23,6 +23,7 @@ lib LibC fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun mktime(x0 : Tm*) : TimeT + fun nanosleep(x0 : Timespec*, x1 : Timespec*) : Int fun tzset : Void fun timegm(x0 : Tm*) : TimeT diff --git a/src/lib_c/aarch64-linux-gnu/c/time.cr b/src/lib_c/aarch64-linux-gnu/c/time.cr index b93b8e698dd9..710d477e269b 100644 --- a/src/lib_c/aarch64-linux-gnu/c/time.cr +++ b/src/lib_c/aarch64-linux-gnu/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm* fun localtime_r(timer : TimeT*, tp : Tm*) : Tm* fun mktime(tp : Tm*) : TimeT + fun nanosleep(req : Timespec*, rem : Timespec*) : Int fun tzset : Void fun timegm(tp : Tm*) : TimeT diff --git a/src/lib_c/aarch64-linux-musl/c/time.cr b/src/lib_c/aarch64-linux-musl/c/time.cr index 22fdf7a86ebf..f687c8b35db4 100644 --- a/src/lib_c/aarch64-linux-musl/c/time.cr +++ b/src/lib_c/aarch64-linux-musl/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun mktime(x0 : Tm*) : TimeT + fun nanosleep(x0 : Timespec*, x1 : Timespec*) : Int fun tzset : Void fun timegm(x0 : Tm*) : TimeT diff --git a/src/lib_c/arm-linux-gnueabihf/c/time.cr b/src/lib_c/arm-linux-gnueabihf/c/time.cr index b93b8e698dd9..710d477e269b 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/time.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm* fun localtime_r(timer : TimeT*, tp : Tm*) : Tm* fun mktime(tp : Tm*) : TimeT + fun nanosleep(req : Timespec*, rem : Timespec*) : Int fun tzset : Void fun timegm(tp : Tm*) : TimeT diff --git a/src/lib_c/i386-linux-gnu/c/time.cr b/src/lib_c/i386-linux-gnu/c/time.cr index b93b8e698dd9..710d477e269b 100644 --- a/src/lib_c/i386-linux-gnu/c/time.cr +++ b/src/lib_c/i386-linux-gnu/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm* fun localtime_r(timer : TimeT*, tp : Tm*) : Tm* fun mktime(tp : Tm*) : TimeT + fun nanosleep(req : Timespec*, rem : Timespec*) : Int fun tzset : Void fun timegm(tp : Tm*) : TimeT diff --git a/src/lib_c/i386-linux-musl/c/time.cr b/src/lib_c/i386-linux-musl/c/time.cr index 22fdf7a86ebf..f687c8b35db4 100644 --- a/src/lib_c/i386-linux-musl/c/time.cr +++ b/src/lib_c/i386-linux-musl/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun mktime(x0 : Tm*) : TimeT + fun nanosleep(x0 : Timespec*, x1 : Timespec*) : Int fun tzset : Void fun timegm(x0 : Tm*) : TimeT diff --git a/src/lib_c/wasm32-wasi/c/time.cr b/src/lib_c/wasm32-wasi/c/time.cr index 9d77b0f53fec..9965c3a7d324 100644 --- a/src/lib_c/wasm32-wasi/c/time.cr +++ b/src/lib_c/wasm32-wasi/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun mktime(x0 : Tm*) : TimeT + fun nanosleep(x0 : Timespec*, x1 : Timespec*) : Int fun timegm(x0 : Tm*) : TimeT fun futimes(fd : Int, times : Timeval[2]) : Int end diff --git a/src/lib_c/x86_64-darwin/c/time.cr b/src/lib_c/x86_64-darwin/c/time.cr index e20477a6a004..7e76fb969fbe 100644 --- a/src/lib_c/x86_64-darwin/c/time.cr +++ b/src/lib_c/x86_64-darwin/c/time.cr @@ -23,6 +23,7 @@ lib LibC fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun mktime(x0 : Tm*) : TimeT + fun nanosleep(x0 : Timespec*, x1 : Timespec*) : Int fun tzset : Void fun timegm(x0 : Tm*) : TimeT diff --git a/src/lib_c/x86_64-dragonfly/c/time.cr b/src/lib_c/x86_64-dragonfly/c/time.cr index 7b7c5a3b54b7..d4f0d2111e28 100644 --- a/src/lib_c/x86_64-dragonfly/c/time.cr +++ b/src/lib_c/x86_64-dragonfly/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun mktime(x0 : Tm*) : TimeT + fun nanosleep(x0 : Timespec*, x1 : Timespec*) : Int fun tzset : Void fun timegm(x0 : Tm*) : TimeT diff --git a/src/lib_c/x86_64-freebsd/c/time.cr b/src/lib_c/x86_64-freebsd/c/time.cr index e0a72c914d82..6b84331c8361 100644 --- a/src/lib_c/x86_64-freebsd/c/time.cr +++ b/src/lib_c/x86_64-freebsd/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun mktime(x0 : Tm*) : TimeT + fun nanosleep(x0 : Timespec*, x1 : Timespec*) : Int fun tzset : Void fun timegm(x0 : Tm*) : TimeT diff --git a/src/lib_c/x86_64-linux-gnu/c/time.cr b/src/lib_c/x86_64-linux-gnu/c/time.cr index b93b8e698dd9..710d477e269b 100644 --- a/src/lib_c/x86_64-linux-gnu/c/time.cr +++ b/src/lib_c/x86_64-linux-gnu/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm* fun localtime_r(timer : TimeT*, tp : Tm*) : Tm* fun mktime(tp : Tm*) : TimeT + fun nanosleep(req : Timespec*, rem : Timespec*) : Int fun tzset : Void fun timegm(tp : Tm*) : TimeT diff --git a/src/lib_c/x86_64-linux-musl/c/time.cr b/src/lib_c/x86_64-linux-musl/c/time.cr index 22fdf7a86ebf..f687c8b35db4 100644 --- a/src/lib_c/x86_64-linux-musl/c/time.cr +++ b/src/lib_c/x86_64-linux-musl/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun mktime(x0 : Tm*) : TimeT + fun nanosleep(x0 : Timespec*, x1 : Timespec*) : Int fun tzset : Void fun timegm(x0 : Tm*) : TimeT diff --git a/src/lib_c/x86_64-netbsd/c/time.cr b/src/lib_c/x86_64-netbsd/c/time.cr index 17fb6b2dcaa6..a0f11bb50283 100644 --- a/src/lib_c/x86_64-netbsd/c/time.cr +++ b/src/lib_c/x86_64-netbsd/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r = __gmtime_r50(x0 : TimeT*, x1 : Tm*) : Tm* fun localtime_r = __localtime_r50(x0 : TimeT*, x1 : Tm*) : Tm* fun mktime = __mktime50(x0 : Tm*) : TimeT + fun nanosleep = __nanosleep50(x0 : Timespec*, x1 : Timespec*) : Int fun tzset : Void fun timegm = __timegm50(x0 : Tm*) : TimeT diff --git a/src/lib_c/x86_64-openbsd/c/time.cr b/src/lib_c/x86_64-openbsd/c/time.cr index 704a722c2a7e..e7979bfba679 100644 --- a/src/lib_c/x86_64-openbsd/c/time.cr +++ b/src/lib_c/x86_64-openbsd/c/time.cr @@ -28,6 +28,7 @@ lib LibC fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun mktime(x0 : Tm*) : TimeT + fun nanosleep(x0 : Timespec*, x1 : Timespec*) : Int fun tzset : Void fun timegm(x0 : Tm*) : TimeT diff --git a/src/lib_c/x86_64-solaris/c/time.cr b/src/lib_c/x86_64-solaris/c/time.cr index c8fc7ea9231f..531f8e373f4b 100644 --- a/src/lib_c/x86_64-solaris/c/time.cr +++ b/src/lib_c/x86_64-solaris/c/time.cr @@ -26,6 +26,7 @@ lib LibC fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* fun mktime(x0 : Tm*) : TimeT + fun nanosleep(x0 : Timespec*, x1 : Timespec*) : Int fun tzset : Void fun timegm(x0 : Tm*) : TimeT From a82dd3ae23a14bf1be9255142d7364e192388074 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sun, 16 Jun 2024 22:31:44 +0200 Subject: [PATCH 1191/1551] Extract system implementation of `UNIXSocket.pair` as `Crystal::System::Socket.socketpair` (#14675) Co-authored-by: Sijawusz Pur Rahnama --- src/crystal/system/socket.cr | 2 ++ src/crystal/system/unix/socket.cr | 20 ++++++++++++++++++++ src/crystal/system/wasi/socket.cr | 4 ++++ src/crystal/system/win32/socket.cr | 4 ++++ src/socket/unix_socket.cr | 24 +++--------------------- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr index 7e7b939fbeae..2669b4c57bca 100644 --- a/src/crystal/system/socket.cr +++ b/src/crystal/system/socket.cr @@ -79,6 +79,8 @@ module Crystal::System::Socket # def self.fcntl(fd, cmd, arg = 0) + # def self.socketpair(type : ::Socket::Type, protocol : ::Socket::Protocol) : {Handle, Handle} + private def system_read(slice : Bytes) : Int32 event_loop.read(self, slice) end diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index ad065bf3ba23..4f010d7d29f6 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -179,6 +179,26 @@ module Crystal::System::Socket r end + def self.socketpair(type : ::Socket::Type, protocol : ::Socket::Protocol) : {Handle, Handle} + fds = uninitialized Handle[2] + socktype = type.value + + {% if LibC.has_constant?(:SOCK_CLOEXEC) %} + socktype |= LibC::SOCK_CLOEXEC + {% end %} + + if LibC.socketpair(::Socket::Family::UNIX, socktype, protocol, fds) != 0 + raise ::Socket::Error.new("socketpair() failed") + end + + {% unless LibC.has_constant?(:SOCK_CLOEXEC) %} + fcntl(fds[0], LibC::F_SETFD, LibC::FD_CLOEXEC) + fcntl(fds[1], LibC::F_SETFD, LibC::FD_CLOEXEC) + {% end %} + + {fds[0], fds[1]} + end + private def system_tty? LibC.isatty(fd) == 1 end diff --git a/src/crystal/system/wasi/socket.cr b/src/crystal/system/wasi/socket.cr index 901e8a4db1cb..712f3538d249 100644 --- a/src/crystal/system/wasi/socket.cr +++ b/src/crystal/system/wasi/socket.cr @@ -135,6 +135,10 @@ module Crystal::System::Socket r end + def self.socketpair(type : ::Socket::Type, protocol : ::Socket::Protocol) : {Handle, Handle} + raise NotImplementedError.new("Crystal::System::Socket.socketpair") + end + private def system_tty? LibC.isatty(fd) == 1 end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index a39382c252d6..623ec0ae8954 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -363,6 +363,10 @@ module Crystal::System::Socket raise NotImplementedError.new "Crystal::System::Socket.fcntl" end + def self.socketpair(type : ::Socket::Type, protocol : ::Socket::Protocol) : {Handle, Handle} + raise NotImplementedError.new("Crystal::System::Socket.socketpair") + end + private def system_tty? LibC.GetConsoleMode(LibC::HANDLE.new(fd), out _) != 0 end diff --git a/src/socket/unix_socket.cr b/src/socket/unix_socket.cr index e672d812f631..201fd8410bf7 100644 --- a/src/socket/unix_socket.cr +++ b/src/socket/unix_socket.cr @@ -68,27 +68,9 @@ class UNIXSocket < Socket # left.gets # => "message" # ``` def self.pair(type : Type = Type::STREAM) : {UNIXSocket, UNIXSocket} - {% if flag?(:wasm32) || flag?(:win32) %} - raise NotImplementedError.new "UNIXSocket.pair" - {% else %} - fds = uninitialized Int32[2] - - socktype = type.value - {% if LibC.has_constant?(:SOCK_CLOEXEC) %} - socktype |= LibC::SOCK_CLOEXEC - {% end %} - - if LibC.socketpair(Family::UNIX, socktype, 0, fds) != 0 - raise Socket::Error.new("socketpair() failed") - end - - {% unless LibC.has_constant?(:SOCK_CLOEXEC) %} - Crystal::System::Socket.fcntl(fds[0], LibC::F_SETFD, LibC::FD_CLOEXEC) - Crystal::System::Socket.fcntl(fds[1], LibC::F_SETFD, LibC::FD_CLOEXEC) - {% end %} - - {UNIXSocket.new(fd: fds[0], type: type), UNIXSocket.new(fd: fds[1], type: type)} - {% end %} + Crystal::System::Socket + .socketpair(type, Protocol::IP) + .map { |fd| UNIXSocket.new(fd: fd, type: type) } end # Creates a pair of unnamed UNIX sockets (see `pair`) and yields them to the From 09e8a01273ab7d1a3a3b0cd6457979dd7b31b645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 16 Jun 2024 23:24:41 +0200 Subject: [PATCH 1192/1551] Update distribution-scripts (#14714) Updates `distribution-scripts` dependency to https://github.com/crystal-lang/distribution-scripts/commit/fe82a34ad7855ddb432a26ef7e48c46e7b440e49 This includes the following changes: * crystal-lang/distribution-scripts#312 * crystal-lang/distribution-scripts#315 * crystal-lang/distribution-scripts#314 * crystal-lang/distribution-scripts#284 * crystal-lang/distribution-scripts#283 * crystal-lang/distribution-scripts#282 * crystal-lang/distribution-scripts#313 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d0ad974b4f20..cf6d612d61b0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "7a013f14ed64e7e569b5e453eab02af63cf62b61" + default: "fe82a34ad7855ddb432a26ef7e48c46e7b440e49" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From e5032d0e55e076fbf08548f4cad6ea7651e53d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 18 Jun 2024 09:17:35 +0200 Subject: [PATCH 1193/1551] Fix `IO::FileDescriptor.new` for closed fd (#14697) Skips any operations for detecting/setting blocking mode in `IO::FileDescriptor.new` when the file descriptor is closed. --- spec/std/io/file_descriptor_spec.cr | 11 +++++++++++ src/crystal/system/win32/file_descriptor.cr | 15 +++++++++++++-- src/io/file_descriptor.cr | 5 +++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/spec/std/io/file_descriptor_spec.cr b/spec/std/io/file_descriptor_spec.cr index f64d5fd2d8b9..e497ac1061a3 100644 --- a/spec/std/io/file_descriptor_spec.cr +++ b/spec/std/io/file_descriptor_spec.cr @@ -14,6 +14,17 @@ private def shell_command(command) end describe IO::FileDescriptor do + describe "#initialize" do + it "handles closed file descriptor gracefully" do + a, b = IO.pipe + a.close + b.close + + fd = IO::FileDescriptor.new(a.fd) + fd.closed?.should be_true + end + end + it "reopen STDIN with the right mode", tags: %w[slow] do code = %q(puts "#{STDIN.blocking} #{STDIN.info.type}") compile_source(code) do |binpath| diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index c836391e49ef..dc8d479532be 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -90,8 +90,19 @@ module Crystal::System::FileDescriptor raise NotImplementedError.new("Crystal::System::FileDescriptor#system_close_on_exec=") if close_on_exec end - private def system_closed? - false + private def system_closed? : Bool + file_type = LibC.GetFileType(windows_handle) + + if file_type == LibC::FILE_TYPE_UNKNOWN + case error = WinError.value + when .error_invalid_handle? + return true + else + raise IO::Error.from_os_error("Unable to get info", error, target: self) + end + else + false + end end def self.fcntl(fd, cmd, arg = 0) diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index bdcc6cafde91..d4459e9bbe0c 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -41,8 +41,13 @@ class IO::FileDescriptor < IO def initialize(fd : Handle, blocking = nil, *, @close_on_finalize = true) @volatile_fd = Atomic.new(fd) + @closed = true # This is necessary so we can reference `self` in `system_closed?` (in case of an exception) + + # TODO: Refactor to avoid calling `GetFileType` twice on Windows (once in `system_closed?` and once in `system_info`) @closed = system_closed? + return if @closed + if blocking.nil? blocking = case system_info.type From 245edd15476d50cb6fc28d82cc5d422819ee9496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 18 Jun 2024 09:17:50 +0200 Subject: [PATCH 1194/1551] Add specs for `Pointer::Appender` (#14719) --- spec/std/pointer/appender_spec.cr | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 spec/std/pointer/appender_spec.cr diff --git a/spec/std/pointer/appender_spec.cr b/spec/std/pointer/appender_spec.cr new file mode 100644 index 000000000000..02ca18e0188e --- /dev/null +++ b/spec/std/pointer/appender_spec.cr @@ -0,0 +1,28 @@ +require "spec" + +describe Pointer::Appender do + it ".new" do + Pointer::Appender.new(Pointer(Void).null) + end + + it "#<<" do + data = Slice(Int32).new(5) + appender = data.to_unsafe.appender + 4.times do |i| + appender << (i + 1) * 2 + end + + data.should eq Slice[2, 4, 6, 8, 0] + end + + it "#size" do + data = Slice(Int32).new(5) + appender = data.to_unsafe.appender + appender.size.should eq 0 + 4.times do |i| + appender << 0 + appender.size.should eq i + 1 + end + appender.size.should eq 4 + end +end From 1f3a4fa54c138eafec9b42eaac37454678260cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 18 Jun 2024 22:52:15 +0200 Subject: [PATCH 1195/1551] Remove calls to `Pointer.new(Int)` (#14683) --- src/crystal/system/win32/thread_mutex.cr | 2 +- src/crystal/tracing.cr | 2 +- src/lib_c/aarch64-android/c/sys/mman.cr | 2 +- src/lib_c/aarch64-darwin/c/dlfcn.cr | 2 +- src/lib_c/aarch64-darwin/c/sys/mman.cr | 2 +- src/lib_c/aarch64-linux-gnu/c/sys/mman.cr | 2 +- src/lib_c/aarch64-linux-musl/c/dlfcn.cr | 2 +- src/lib_c/aarch64-linux-musl/c/sys/mman.cr | 2 +- src/lib_c/arm-linux-gnueabihf/c/sys/mman.cr | 2 +- src/lib_c/i386-linux-gnu/c/sys/mman.cr | 2 +- src/lib_c/i386-linux-musl/c/dlfcn.cr | 2 +- src/lib_c/i386-linux-musl/c/sys/mman.cr | 2 +- src/lib_c/x86_64-darwin/c/dlfcn.cr | 2 +- src/lib_c/x86_64-darwin/c/sys/mman.cr | 2 +- src/lib_c/x86_64-dragonfly/c/dlfcn.cr | 2 +- src/lib_c/x86_64-dragonfly/c/sys/mman.cr | 2 +- src/lib_c/x86_64-freebsd/c/dlfcn.cr | 2 +- src/lib_c/x86_64-freebsd/c/sys/mman.cr | 2 +- src/lib_c/x86_64-linux-gnu/c/sys/mman.cr | 2 +- src/lib_c/x86_64-linux-musl/c/dlfcn.cr | 2 +- src/lib_c/x86_64-linux-musl/c/sys/mman.cr | 2 +- src/lib_c/x86_64-netbsd/c/dlfcn.cr | 2 +- src/lib_c/x86_64-netbsd/c/sys/mman.cr | 2 +- src/lib_c/x86_64-openbsd/c/dlfcn.cr | 2 +- src/lib_c/x86_64-openbsd/c/sys/mman.cr | 2 +- src/lib_c/x86_64-solaris/c/dlfcn.cr | 2 +- src/lib_c/x86_64-solaris/c/sys/mman.cr | 2 +- src/lib_c/x86_64-windows-msvc/c/handleapi.cr | 2 +- src/lib_c/x86_64-windows-msvc/c/winreg.cr | 14 +++++++------- 29 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/crystal/system/win32/thread_mutex.cr b/src/crystal/system/win32/thread_mutex.cr index 559af6acb4f0..44c1ab8a9679 100644 --- a/src/crystal/system/win32/thread_mutex.cr +++ b/src/crystal/system/win32/thread_mutex.cr @@ -37,7 +37,7 @@ class Thread def unlock : Nil # `owningThread` is declared as `LibC::HANDLE` for historical reasons, so # the following comparison is correct - unless @cs.owningThread == LibC::HANDLE.new(LibC.GetCurrentThreadId) + unless @cs.owningThread == LibC::HANDLE.new(LibC.GetCurrentThreadId.to_u64!) raise RuntimeError.new "Attempt to unlock a mutex locked by another thread" end LibC.LeaveCriticalSection(self) diff --git a/src/crystal/tracing.cr b/src/crystal/tracing.cr index 708956ad8feb..ad3ae184a54a 100644 --- a/src/crystal/tracing.cr +++ b/src/crystal/tracing.cr @@ -141,7 +141,7 @@ module Crystal {% if flag?(:win32) %} handle = LibC.CreateFileW(filename, LibC::FILE_GENERIC_WRITE, LibC::DEFAULT_SHARE_MODE, nil, LibC::CREATE_ALWAYS, LibC::FILE_ATTRIBUTE_NORMAL, LibC::HANDLE.null) # not using LibC::INVALID_HANDLE_VALUE because it doesn't exist (yet) - return handle.address unless handle == LibC::HANDLE.new(-1) + return handle.address unless handle == LibC::HANDLE.new(-1.to_u64!) error = uninitialized UInt16[256] len = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, WinError.value, 0, error, error.size, nil) diff --git a/src/lib_c/aarch64-android/c/sys/mman.cr b/src/lib_c/aarch64-android/c/sys/mman.cr index b38ec92b9f0e..cf8525cbf3a9 100644 --- a/src/lib_c/aarch64-android/c/sys/mman.cr +++ b/src/lib_c/aarch64-android/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x01 MAP_ANON = LibC::MAP_ANONYMOUS MAP_ANONYMOUS = 0x20 - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 4 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/aarch64-darwin/c/dlfcn.cr b/src/lib_c/aarch64-darwin/c/dlfcn.cr index e4f1dffc933e..25c53eaeba2a 100644 --- a/src/lib_c/aarch64-darwin/c/dlfcn.cr +++ b/src/lib_c/aarch64-darwin/c/dlfcn.cr @@ -4,7 +4,7 @@ lib LibC RTLD_GLOBAL = 0x8 RTLD_LOCAL = 0x4 RTLD_DEFAULT = Pointer(Void).new(-2) - RTLD_NEXT = Pointer(Void).new(-1) + RTLD_NEXT = Pointer(Void).new(-1.to_u64!) struct DlInfo dli_fname : Char* diff --git a/src/lib_c/aarch64-darwin/c/sys/mman.cr b/src/lib_c/aarch64-darwin/c/sys/mman.cr index e9e65125c3eb..dc5f1e79d6c7 100644 --- a/src/lib_c/aarch64-darwin/c/sys/mman.cr +++ b/src/lib_c/aarch64-darwin/c/sys/mman.cr @@ -9,7 +9,7 @@ lib LibC MAP_PRIVATE = 0x0002 MAP_SHARED = 0x0001 MAP_ANON = 0x1000 - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 4 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/mman.cr b/src/lib_c/aarch64-linux-gnu/c/sys/mman.cr index 8c44b210a24e..0b6e318b8d2d 100644 --- a/src/lib_c/aarch64-linux-gnu/c/sys/mman.cr +++ b/src/lib_c/aarch64-linux-gnu/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x01 MAP_ANON = LibC::MAP_ANONYMOUS MAP_ANONYMOUS = 0x20 - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 4 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/aarch64-linux-musl/c/dlfcn.cr b/src/lib_c/aarch64-linux-musl/c/dlfcn.cr index 2f48a19e8092..b0dc20d93f1a 100644 --- a/src/lib_c/aarch64-linux-musl/c/dlfcn.cr +++ b/src/lib_c/aarch64-linux-musl/c/dlfcn.cr @@ -4,7 +4,7 @@ lib LibC RTLD_GLOBAL = 256 RTLD_LOCAL = 0 RTLD_DEFAULT = Pointer(Void).new(0) - RTLD_NEXT = Pointer(Void).new(-1) + RTLD_NEXT = Pointer(Void).new(-1.to_u64!) struct DlInfo dli_fname : Char* diff --git a/src/lib_c/aarch64-linux-musl/c/sys/mman.cr b/src/lib_c/aarch64-linux-musl/c/sys/mman.cr index 4dd11dad0918..b0ce2d629a81 100644 --- a/src/lib_c/aarch64-linux-musl/c/sys/mman.cr +++ b/src/lib_c/aarch64-linux-musl/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x01 MAP_ANON = 0x20 MAP_ANONYMOUS = LibC::MAP_ANON - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 0 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/mman.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/mman.cr index 8c44b210a24e..0b6e318b8d2d 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/sys/mman.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x01 MAP_ANON = LibC::MAP_ANONYMOUS MAP_ANONYMOUS = 0x20 - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 4 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/i386-linux-gnu/c/sys/mman.cr b/src/lib_c/i386-linux-gnu/c/sys/mman.cr index 158228f6946d..2c2678495b1a 100644 --- a/src/lib_c/i386-linux-gnu/c/sys/mman.cr +++ b/src/lib_c/i386-linux-gnu/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x01 MAP_ANON = LibC::MAP_ANONYMOUS MAP_ANONYMOUS = 0x20 - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 4 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/i386-linux-musl/c/dlfcn.cr b/src/lib_c/i386-linux-musl/c/dlfcn.cr index 2f48a19e8092..b0dc20d93f1a 100644 --- a/src/lib_c/i386-linux-musl/c/dlfcn.cr +++ b/src/lib_c/i386-linux-musl/c/dlfcn.cr @@ -4,7 +4,7 @@ lib LibC RTLD_GLOBAL = 256 RTLD_LOCAL = 0 RTLD_DEFAULT = Pointer(Void).new(0) - RTLD_NEXT = Pointer(Void).new(-1) + RTLD_NEXT = Pointer(Void).new(-1.to_u64!) struct DlInfo dli_fname : Char* diff --git a/src/lib_c/i386-linux-musl/c/sys/mman.cr b/src/lib_c/i386-linux-musl/c/sys/mman.cr index 4dd11dad0918..b0ce2d629a81 100644 --- a/src/lib_c/i386-linux-musl/c/sys/mman.cr +++ b/src/lib_c/i386-linux-musl/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x01 MAP_ANON = 0x20 MAP_ANONYMOUS = LibC::MAP_ANON - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 0 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/x86_64-darwin/c/dlfcn.cr b/src/lib_c/x86_64-darwin/c/dlfcn.cr index e4f1dffc933e..25c53eaeba2a 100644 --- a/src/lib_c/x86_64-darwin/c/dlfcn.cr +++ b/src/lib_c/x86_64-darwin/c/dlfcn.cr @@ -4,7 +4,7 @@ lib LibC RTLD_GLOBAL = 0x8 RTLD_LOCAL = 0x4 RTLD_DEFAULT = Pointer(Void).new(-2) - RTLD_NEXT = Pointer(Void).new(-1) + RTLD_NEXT = Pointer(Void).new(-1.to_u64!) struct DlInfo dli_fname : Char* diff --git a/src/lib_c/x86_64-darwin/c/sys/mman.cr b/src/lib_c/x86_64-darwin/c/sys/mman.cr index 934bd88ff5ad..1d2717b0061d 100644 --- a/src/lib_c/x86_64-darwin/c/sys/mman.cr +++ b/src/lib_c/x86_64-darwin/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x0001 MAP_ANON = 0x1000 MAP_ANONYMOUS = LibC::MAP_ANON - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 4 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/x86_64-dragonfly/c/dlfcn.cr b/src/lib_c/x86_64-dragonfly/c/dlfcn.cr index 035ccf873319..fe95d81f85a1 100644 --- a/src/lib_c/x86_64-dragonfly/c/dlfcn.cr +++ b/src/lib_c/x86_64-dragonfly/c/dlfcn.cr @@ -4,7 +4,7 @@ lib LibC RTLD_GLOBAL = 0x100 RTLD_LOCAL = 0 RTLD_DEFAULT = Pointer(Void).new(-2) - RTLD_NEXT = Pointer(Void).new(-1) + RTLD_NEXT = Pointer(Void).new(-1.to_u64!) struct DlInfo dli_fname : Char* diff --git a/src/lib_c/x86_64-dragonfly/c/sys/mman.cr b/src/lib_c/x86_64-dragonfly/c/sys/mman.cr index eafa58cc00d3..06f2643d4788 100644 --- a/src/lib_c/x86_64-dragonfly/c/sys/mman.cr +++ b/src/lib_c/x86_64-dragonfly/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x0001 MAP_ANON = 0x1000 MAP_ANONYMOUS = LibC::MAP_ANON - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_NORMAL = LibC::MADV_NORMAL POSIX_MADV_RANDOM = LibC::MADV_RANDOM POSIX_MADV_SEQUENTIAL = LibC::MADV_SEQUENTIAL diff --git a/src/lib_c/x86_64-freebsd/c/dlfcn.cr b/src/lib_c/x86_64-freebsd/c/dlfcn.cr index 035ccf873319..fe95d81f85a1 100644 --- a/src/lib_c/x86_64-freebsd/c/dlfcn.cr +++ b/src/lib_c/x86_64-freebsd/c/dlfcn.cr @@ -4,7 +4,7 @@ lib LibC RTLD_GLOBAL = 0x100 RTLD_LOCAL = 0 RTLD_DEFAULT = Pointer(Void).new(-2) - RTLD_NEXT = Pointer(Void).new(-1) + RTLD_NEXT = Pointer(Void).new(-1.to_u64!) struct DlInfo dli_fname : Char* diff --git a/src/lib_c/x86_64-freebsd/c/sys/mman.cr b/src/lib_c/x86_64-freebsd/c/sys/mman.cr index 4990727db9c5..dfada5da1552 100644 --- a/src/lib_c/x86_64-freebsd/c/sys/mman.cr +++ b/src/lib_c/x86_64-freebsd/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x0001 MAP_ANON = 0x1000 MAP_ANONYMOUS = LibC::MAP_ANON - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 4 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/mman.cr b/src/lib_c/x86_64-linux-gnu/c/sys/mman.cr index 8c44b210a24e..0b6e318b8d2d 100644 --- a/src/lib_c/x86_64-linux-gnu/c/sys/mman.cr +++ b/src/lib_c/x86_64-linux-gnu/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x01 MAP_ANON = LibC::MAP_ANONYMOUS MAP_ANONYMOUS = 0x20 - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 4 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/x86_64-linux-musl/c/dlfcn.cr b/src/lib_c/x86_64-linux-musl/c/dlfcn.cr index 2f48a19e8092..b0dc20d93f1a 100644 --- a/src/lib_c/x86_64-linux-musl/c/dlfcn.cr +++ b/src/lib_c/x86_64-linux-musl/c/dlfcn.cr @@ -4,7 +4,7 @@ lib LibC RTLD_GLOBAL = 256 RTLD_LOCAL = 0 RTLD_DEFAULT = Pointer(Void).new(0) - RTLD_NEXT = Pointer(Void).new(-1) + RTLD_NEXT = Pointer(Void).new(-1.to_u64!) struct DlInfo dli_fname : Char* diff --git a/src/lib_c/x86_64-linux-musl/c/sys/mman.cr b/src/lib_c/x86_64-linux-musl/c/sys/mman.cr index 4dd11dad0918..b0ce2d629a81 100644 --- a/src/lib_c/x86_64-linux-musl/c/sys/mman.cr +++ b/src/lib_c/x86_64-linux-musl/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x01 MAP_ANON = 0x20 MAP_ANONYMOUS = LibC::MAP_ANON - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 0 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/x86_64-netbsd/c/dlfcn.cr b/src/lib_c/x86_64-netbsd/c/dlfcn.cr index cbdf854f1912..abb0c0fcc951 100644 --- a/src/lib_c/x86_64-netbsd/c/dlfcn.cr +++ b/src/lib_c/x86_64-netbsd/c/dlfcn.cr @@ -3,7 +3,7 @@ lib LibC RTLD_NOW = 2 RTLD_GLOBAL = 0x100 RTLD_LOCAL = 0x200 - RTLD_NEXT = Pointer(Void).new(-1) + RTLD_NEXT = Pointer(Void).new(-1.to_u64!) RTLD_DEFAULT = Pointer(Void).new(-2) struct DlInfo diff --git a/src/lib_c/x86_64-netbsd/c/sys/mman.cr b/src/lib_c/x86_64-netbsd/c/sys/mman.cr index 2c6675659c2f..3557a00ab788 100644 --- a/src/lib_c/x86_64-netbsd/c/sys/mman.cr +++ b/src/lib_c/x86_64-netbsd/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_FIXED = 0x0010 MAP_ANON = 0x1000 MAP_ANONYMOUS = LibC::MAP_ANON - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) MAP_STACK = 0x2000 POSIX_MADV_NORMAL = 0 POSIX_MADV_RANDOM = 1 diff --git a/src/lib_c/x86_64-openbsd/c/dlfcn.cr b/src/lib_c/x86_64-openbsd/c/dlfcn.cr index 595a6e059563..8c6bbe3fc7e6 100644 --- a/src/lib_c/x86_64-openbsd/c/dlfcn.cr +++ b/src/lib_c/x86_64-openbsd/c/dlfcn.cr @@ -4,7 +4,7 @@ lib LibC RTLD_GLOBAL = 0x100 RTLD_LOCAL = 0x000 RTLD_DEFAULT = Pointer(Void).new(-2) - RTLD_NEXT = Pointer(Void).new(-1) + RTLD_NEXT = Pointer(Void).new(-1.to_u64!) struct DlInfo dli_fname : Char* diff --git a/src/lib_c/x86_64-openbsd/c/sys/mman.cr b/src/lib_c/x86_64-openbsd/c/sys/mman.cr index 4b6714e7efa1..7c857527adbf 100644 --- a/src/lib_c/x86_64-openbsd/c/sys/mman.cr +++ b/src/lib_c/x86_64-openbsd/c/sys/mman.cr @@ -10,7 +10,7 @@ lib LibC MAP_SHARED = 0x0001 MAP_ANON = 0x1000 MAP_ANONYMOUS = LibC::MAP_ANON - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) MAP_STACK = 0x4000 POSIX_MADV_DONTNEED = 4 POSIX_MADV_NORMAL = 0 diff --git a/src/lib_c/x86_64-solaris/c/dlfcn.cr b/src/lib_c/x86_64-solaris/c/dlfcn.cr index 3afc6fd37cbf..792f4d4fcb33 100644 --- a/src/lib_c/x86_64-solaris/c/dlfcn.cr +++ b/src/lib_c/x86_64-solaris/c/dlfcn.cr @@ -4,7 +4,7 @@ lib LibC RTLD_GLOBAL = 0x00100 RTLD_LOCAL = 0x00000 RTLD_DEFAULT = Pointer(Void).new(-2) - RTLD_NEXT = Pointer(Void).new(-1) + RTLD_NEXT = Pointer(Void).new(-1.to_u64!) struct DlInfo dli_fname : Char* diff --git a/src/lib_c/x86_64-solaris/c/sys/mman.cr b/src/lib_c/x86_64-solaris/c/sys/mman.cr index 55f912792fb8..c2319455c16f 100644 --- a/src/lib_c/x86_64-solaris/c/sys/mman.cr +++ b/src/lib_c/x86_64-solaris/c/sys/mman.cr @@ -12,7 +12,7 @@ lib LibC MAP_ANON = 0x100 MAP_ANONYMOUS = LibC::MAP_ANON - MAP_FAILED = Pointer(Void).new(-1) + MAP_FAILED = Pointer(Void).new(-1.to_u64!) POSIX_MADV_DONTNEED = 4 POSIX_MADV_NORMAL = 0 diff --git a/src/lib_c/x86_64-windows-msvc/c/handleapi.cr b/src/lib_c/x86_64-windows-msvc/c/handleapi.cr index c2d02e741c27..527a5ba94a58 100644 --- a/src/lib_c/x86_64-windows-msvc/c/handleapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/handleapi.cr @@ -1,7 +1,7 @@ require "c/winnt" lib LibC - INVALID_HANDLE_VALUE = HANDLE.new(-1) + INVALID_HANDLE_VALUE = HANDLE.new(-1.to_u64!) fun CloseHandle(hObject : HANDLE) : BOOL diff --git a/src/lib_c/x86_64-windows-msvc/c/winreg.cr b/src/lib_c/x86_64-windows-msvc/c/winreg.cr index cdcdd6f1a64a..0be83b90b707 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winreg.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winreg.cr @@ -18,13 +18,13 @@ lib LibC QWORD_LITTLE_ENDIAN = QWORD end - HKEY_CLASSES_ROOT = Pointer(Void).new(0x80000000).as(HKEY) - HKEY_CURRENT_USER = Pointer(Void).new(0x80000001).as(HKEY) - HKEY_LOCAL_MACHINE = Pointer(Void).new(0x80000002).as(HKEY) - HKEY_USERS = Pointer(Void).new(0x80000003).as(HKEY) - HKEY_PERFORMANCE_DATA = Pointer(Void).new(0x80000004).as(HKEY) - HKEY_CURRENT_CONFIG = Pointer(Void).new(0x80000005).as(HKEY) - HKEY_DYN_DATA = Pointer(Void).new(0x8000006).as(HKEY) + HKEY_CLASSES_ROOT = Pointer(Void).new(0x80000000_u64).as(HKEY) + HKEY_CURRENT_USER = Pointer(Void).new(0x80000001_u64).as(HKEY) + HKEY_LOCAL_MACHINE = Pointer(Void).new(0x80000002_u64).as(HKEY) + HKEY_USERS = Pointer(Void).new(0x80000003_u64).as(HKEY) + HKEY_PERFORMANCE_DATA = Pointer(Void).new(0x80000004_u64).as(HKEY) + HKEY_CURRENT_CONFIG = Pointer(Void).new(0x80000005_u64).as(HKEY) + HKEY_DYN_DATA = Pointer(Void).new(0x8000006_u64).as(HKEY) fun RegOpenKeyExW(hKey : HKEY, lpSubKey : LPWSTR, ulOptions : DWORD, samDesired : REGSAM, phkResult : HKEY*) : LSTATUS fun RegCloseKey(hKey : HKEY) : LSTATUS From ded40c46353233a37c6086ff8a2d699c3c7ab2fb Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:54:05 +0200 Subject: [PATCH 1196/1551] Fix result formatting in code example for `Indexable#[]?` (#14721) --- src/indexable.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/indexable.cr b/src/indexable.cr index d39ddaaef197..4a3990e83870 100644 --- a/src/indexable.cr +++ b/src/indexable.cr @@ -101,8 +101,8 @@ module Indexable(T) # ary[-1]? # => 'c' # ary[-2]? # => 'b' # - # ary[3]? # nil - # ary[-4]? # nil + # ary[3]? # => nil + # ary[-4]? # => nil # ``` @[AlwaysInline] def []?(index : Int) From e0754ca20a5ad2dbf202106383c7fbf677701776 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Thu, 20 Jun 2024 08:54:56 +0200 Subject: [PATCH 1197/1551] Add process rwlock to wrap fork/exec on Darwin (#14674) In addition to the standard `O_CLOEXEC` flag to `open` (POSIX.1-2008), most modern POSIX systems implement non-standard syscalls (`accept4`, `dup3` and `pipe2`) along with the `SOCK_CLOEXEC` flag to atomically create file descriptors with the `FD_CLOEXEC` flag. A notable exception is Darwin that only implements `O_CLOEXEC`. We thus have to support falling back to `accept`, `dup2` and `pipe` that won't set `FD_CLOEXEC` or `SOCK_CLOEXEC` atomically, which creates a time window during which another thread may fork the process before `FD_CLOEXEC` is set, which will leak the file descriptor to a child process. This patch introduces a RWLock to prevent fork/exec in such situations. **Prior art**: Go does exactly that. --- .../system/unix/event_loop_libevent.cr | 15 ++++-- src/crystal/system/unix/file_descriptor.cr | 18 ++++--- src/crystal/system/unix/process.cr | 49 ++++++++++++++++++- src/crystal/system/unix/socket.cr | 43 ++++++++-------- 4 files changed, 90 insertions(+), 35 deletions(-) diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr index 3d8cecf694f2..32c9c8409b17 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/system/unix/event_loop_libevent.cr @@ -152,7 +152,17 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop {% if LibC.has_method?(:accept4) %} LibC.accept4(socket.fd, nil, nil, LibC::SOCK_CLOEXEC) {% else %} - LibC.accept(socket.fd, nil, nil) + # we may fail to set FD_CLOEXEC between `accept` and `fcntl` but we + # can't call `Crystal::System::Socket.lock_read` because the socket + # might be in blocking mode and accept would block until the socket + # receives a connection. + # + # we could lock when `socket.blocking?` is false, but another thread + # could change the socket back to blocking mode between the condition + # check and the `accept` call. + fd = LibC.accept(socket.fd, nil, nil) + Crystal::System::Socket.fcntl(fd, LibC::F_SETFD, LibC::FD_CLOEXEC) unless fd == -1 + fd {% end %} if client_fd == -1 @@ -167,9 +177,6 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop raise ::Socket::Error.from_errno("accept") end else - {% unless LibC.has_method?(:accept4) %} - Crystal::System::Socket.fcntl(client_fd, LibC::F_SETFD, LibC::FD_CLOEXEC) - {% end %} return client_fd end end diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 765f7a989f3d..0c3ece9cfff8 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -97,10 +97,12 @@ module Crystal::System::FileDescriptor raise IO::Error.from_errno("Could not reopen file descriptor") end {% else %} - if LibC.dup2(other.fd, fd) == -1 - raise IO::Error.from_errno("Could not reopen file descriptor") + Process.lock_read do + if LibC.dup2(other.fd, fd) == -1 + raise IO::Error.from_errno("Could not reopen file descriptor") + end + self.close_on_exec = other.close_on_exec? end - self.close_on_exec = other.close_on_exec? {% end %} # Mark the handle open, since we had to have dup'd a live handle. @@ -195,11 +197,13 @@ module Crystal::System::FileDescriptor raise IO::Error.from_errno("Could not create pipe") end {% else %} - if LibC.pipe(pipe_fds) != 0 - raise IO::Error.from_errno("Could not create pipe") + Process.lock_read do + if LibC.pipe(pipe_fds) != 0 + raise IO::Error.from_errno("Could not create pipe") + end + fcntl(pipe_fds[0], LibC::F_SETFD, LibC::FD_CLOEXEC) + fcntl(pipe_fds[1], LibC::F_SETFD, LibC::FD_CLOEXEC) end - fcntl(pipe_fds[0], LibC::F_SETFD, LibC::FD_CLOEXEC) - fcntl(pipe_fds[1], LibC::F_SETFD, LibC::FD_CLOEXEC) {% end %} r = IO::FileDescriptor.new(pipe_fds[0], read_blocking) diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 4fd2b658cd59..f3d5dbf3eddb 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -2,6 +2,7 @@ require "c/signal" require "c/stdlib" require "c/sys/resource" require "c/unistd" +require "crystal/rw_lock" require "file/error" struct Crystal::System::Process @@ -127,6 +128,50 @@ struct Crystal::System::Process ) end + # The RWLock is trying to protect against file descriptors leaking to + # sub-processes. + # + # There is a race condition in the POSIX standard between the creation of a + # file descriptor (`accept`, `dup`, `open`, `pipe`, `socket`) and setting the + # `FD_CLOEXEC` flag with `fcntl`. During the time window between those two + # syscalls, another thread may fork the process and exec another process, + # which will leak the file descriptor to that process. + # + # Most systems have long implemented non standard syscalls that prevent the + # race condition, except for Darwin that implements `O_CLOEXEC` but doesn't + # implement `SOCK_CLOEXEC` nor `accept4`, `dup3` or `pipe2`. + # + # NOTE: there may still be some potential leaks (e.g. calling `accept` on a + # blocking socket). + {% if LibC.has_constant?(:SOCK_CLOEXEC) && LibC.has_method?(:accept4) && LibC.has_method?(:dup3) && LibC.has_method?(:pipe2) %} + # we don't implement .lock_read so compilation will fail if we need to + # support another case, instead of silently skipping the rwlock! + + def self.lock_write(&) + yield + end + {% else %} + @@rwlock = Crystal::RWLock.new + + def self.lock_read(&) + @@rwlock.read_lock + begin + yield + ensure + @@rwlock.read_unlock + end + end + + def self.lock_write(&) + @@rwlock.write_lock + begin + yield + ensure + @@rwlock.write_unlock + end + end + {% end %} + def self.fork(*, will_exec = false) newmask = uninitialized LibC::SigsetT oldmask = uninitialized LibC::SigsetT @@ -135,7 +180,7 @@ struct Crystal::System::Process ret = LibC.pthread_sigmask(LibC::SIG_SETMASK, pointerof(newmask), pointerof(oldmask)) raise RuntimeError.from_errno("Failed to disable signals") unless ret == 0 - case pid = LibC.fork + case pid = lock_write { LibC.fork } when 0 # child: pid = nil @@ -274,7 +319,7 @@ struct Crystal::System::Process argv = command_args.map &.check_no_null_byte.to_unsafe argv << Pointer(UInt8).null - LibC.execvp(command, argv) + lock_write { LibC.execvp(command, argv) } end def self.replace(command_args, env, clear_env, input, output, error, chdir) diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 4f010d7d29f6..33ac70659b9f 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -9,19 +9,18 @@ module Crystal::System::Socket alias Handle = Int32 private def create_handle(family, type, protocol, blocking) : Handle - socktype = type.value {% if LibC.has_constant?(:SOCK_CLOEXEC) %} - socktype |= LibC::SOCK_CLOEXEC - {% end %} - - fd = LibC.socket(family, socktype, protocol) - raise ::Socket::Error.from_errno("Failed to create socket") if fd == -1 - - {% unless LibC.has_constant?(:SOCK_CLOEXEC) %} - Socket.fcntl(fd, LibC::F_SETFD, LibC::FD_CLOEXEC) + fd = LibC.socket(family, type.value | LibC::SOCK_CLOEXEC, protocol) + raise ::Socket::Error.from_errno("Failed to create socket") if fd == -1 + fd + {% else %} + Process.lock_read do + fd = LibC.socket(family, type, protocol) + raise ::Socket::Error.from_errno("Failed to create socket") if fd == -1 + Socket.fcntl(fd, LibC::F_SETFD, LibC::FD_CLOEXEC) + fd + end {% end %} - - fd end private def initialize_handle(fd) @@ -181,19 +180,19 @@ module Crystal::System::Socket def self.socketpair(type : ::Socket::Type, protocol : ::Socket::Protocol) : {Handle, Handle} fds = uninitialized Handle[2] - socktype = type.value {% if LibC.has_constant?(:SOCK_CLOEXEC) %} - socktype |= LibC::SOCK_CLOEXEC - {% end %} - - if LibC.socketpair(::Socket::Family::UNIX, socktype, protocol, fds) != 0 - raise ::Socket::Error.new("socketpair() failed") - end - - {% unless LibC.has_constant?(:SOCK_CLOEXEC) %} - fcntl(fds[0], LibC::F_SETFD, LibC::FD_CLOEXEC) - fcntl(fds[1], LibC::F_SETFD, LibC::FD_CLOEXEC) + if LibC.socketpair(::Socket::Family::UNIX, type.value | LibC::SOCK_CLOEXEC, protocol, fds) == -1 + raise ::Socket::Error.new("socketpair() failed") + end + {% else %} + Process.lock_read do + if LibC.socketpair(::Socket::Family::UNIX, type, protocol, fds) == -1 + raise ::Socket::Error.new("socketpair() failed") + end + fcntl(fds[0], LibC::F_SETFD, LibC::FD_CLOEXEC) + fcntl(fds[1], LibC::F_SETFD, LibC::FD_CLOEXEC) + end {% end %} {fds[0], fds[1]} From b14be1ebc25513f2ef8ab591288fe91e3b8a87fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 20 Jun 2024 08:56:17 +0200 Subject: [PATCH 1198/1551] Cleanup `IOCP::OverlappedOperation` (#14723) Light refactor of `IOCP::OverlappedOperation` to simplify the implementation. * Add `OverlappedOperation#to_unsafe` as standard format for passing to C functions * Add `OverlappedOperation.unbox` for the reverse * Drop unnecessary `OverlappedOperation#start` to simplify the logic --- src/crystal/system/win32/event_loop_iocp.cr | 4 +-- src/crystal/system/win32/iocp.cr | 30 +++++++++------------ src/crystal/system/win32/socket.cr | 4 +-- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index d05e15162171..25c8db41d9ff 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -233,7 +233,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop def connect(socket : ::Socket, address : ::Socket::Addrinfo | ::Socket::Address, timeout : ::Time::Span?) : IO::Error? socket.overlapped_connect(socket.fd, "ConnectEx") do |overlapped| # This is: LibC.ConnectEx(fd, address, address.size, nil, 0, nil, overlapped) - Crystal::System::Socket.connect_ex.call(socket.fd, address.to_unsafe, address.size, Pointer(Void).null, 0_u32, Pointer(UInt32).null, overlapped) + Crystal::System::Socket.connect_ex.call(socket.fd, address.to_unsafe, address.size, Pointer(Void).null, 0_u32, Pointer(UInt32).null, overlapped.to_unsafe) end end @@ -256,7 +256,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop received_bytes = uninitialized UInt32 Crystal::System::Socket.accept_ex.call(socket.fd, client_handle, output_buffer.to_unsafe.as(Void*), buffer_size.to_u32!, - address_size.to_u32!, address_size.to_u32!, pointerof(received_bytes), overlapped) + address_size.to_u32!, address_size.to_u32!, pointerof(received_bytes), overlapped.to_unsafe) end if success diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr index 53cf704ad760..780b6f1ac6f0 100644 --- a/src/crystal/system/win32/iocp.cr +++ b/src/crystal/system/win32/iocp.cr @@ -40,7 +40,8 @@ module Crystal::IOCP # I/O operations, including socket ones, do not set this field case completion_key = Pointer(Void).new(entry.lpCompletionKey).as(CompletionKey?) when Nil - OverlappedOperation.schedule(entry.lpOverlapped) { |fiber| yield fiber } + operation = OverlappedOperation.unbox(entry.lpOverlapped) + operation.schedule { |fiber| yield fiber } else case entry.dwNumberOfBytesTransferred when LibC::JOB_OBJECT_MSG_EXIT_PROCESS, LibC::JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS @@ -62,15 +63,14 @@ module Crystal::IOCP class OverlappedOperation enum State - INITIALIZED STARTED DONE CANCELLED end @overlapped = LibC::OVERLAPPED.new - @fiber : Fiber? = nil - @state : State = :initialized + @fiber = Fiber.current + @state : State = :started property next : OverlappedOperation? property previous : OverlappedOperation? @@canceled = Thread::LinkedList(OverlappedOperation).new @@ -84,22 +84,18 @@ module Crystal::IOCP end end - def self.schedule(overlapped : LibC::OVERLAPPED*, &) + def self.unbox(overlapped : LibC::OVERLAPPED*) start = overlapped.as(Pointer(UInt8)) - offsetof(OverlappedOperation, @overlapped) - operation = Box(OverlappedOperation).unbox(start.as(Pointer(Void))) - operation.schedule { |fiber| yield fiber } + Box(OverlappedOperation).unbox(start.as(Pointer(Void))) end - def start - raise Exception.new("Invalid state #{@state}") unless @state.initialized? - @fiber = Fiber.current - @state = State::STARTED + def to_unsafe pointerof(@overlapped) end def result(handle, &) raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started? - result = LibC.GetOverlappedResult(handle, pointerof(@overlapped), out bytes, 0) + result = LibC.GetOverlappedResult(handle, self, out bytes, 0) if result.zero? error = WinError.value yield error @@ -113,7 +109,7 @@ module Crystal::IOCP def wsa_result(socket, &) raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started? flags = 0_u32 - result = LibC.WSAGetOverlappedResult(socket, pointerof(@overlapped), out bytes, false, pointerof(flags)) + result = LibC.WSAGetOverlappedResult(socket, self, out bytes, false, pointerof(flags)) if result.zero? error = WinError.wsa_value yield error @@ -127,7 +123,7 @@ module Crystal::IOCP protected def schedule(&) case @state when .started? - yield @fiber.not_nil! + yield @fiber done! when .cancelled? @@canceled.delete(self) @@ -144,7 +140,7 @@ module Crystal::IOCP # https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-cancelioex # > The application must not free or reuse the OVERLAPPED structure # associated with the canceled I/O operations until they have completed - if LibC.CancelIoEx(handle, pointerof(@overlapped)) != 0 + if LibC.CancelIoEx(handle, self) != 0 @state = :cancelled @@canceled.push(self) # to increase lifetime end @@ -176,7 +172,7 @@ module Crystal::IOCP def self.overlapped_operation(target, handle, method, timeout, *, writing = false, &) OverlappedOperation.run(handle) do |operation| - result, value = yield operation.start + result, value = yield operation if result == 0 case error = WinError.value @@ -214,7 +210,7 @@ module Crystal::IOCP def self.wsa_overlapped_operation(target, socket, method, timeout, connreset_is_error = true, &) OverlappedOperation.run(socket) do |operation| - result, value = yield operation.start + result, value = yield operation if result == LibC::SOCKET_ERROR case error = WinError.wsa_value diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 623ec0ae8954..c04d3a9ad868 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -130,7 +130,7 @@ module Crystal::System::Socket # :nodoc: def overlapped_connect(socket, method, &) IOCP::OverlappedOperation.run(socket) do |operation| - result = yield operation.start + result = yield operation if result == 0 case error = WinError.wsa_value @@ -196,7 +196,7 @@ module Crystal::System::Socket def overlapped_accept(socket, method, &) IOCP::OverlappedOperation.run(socket) do |operation| - result = yield operation.start + result = yield operation if result == 0 case error = WinError.wsa_value From 2c62b19469247bbaea32a0c365122e243edb0354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 20 Jun 2024 08:56:55 +0200 Subject: [PATCH 1199/1551] Fix relative file paths in spec output on Windows (#14725) --- src/spec/context.cr | 8 +++++--- src/spec/source.cr | 10 ---------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/spec/context.cr b/src/spec/context.cr index 12501adf8360..1cc473819580 100644 --- a/src/spec/context.cr +++ b/src/spec/context.cr @@ -191,6 +191,8 @@ module Spec failures = results_for(:fail) errors = results_for(:error) + cwd = Dir.current + failures_and_errors = failures + errors unless failures_and_errors.empty? puts @@ -216,7 +218,7 @@ module Spec if ex.is_a?(SpecError) puts - puts Spec.color(" # #{Spec.relative_file(ex.file)}:#{ex.line}", :comment) + puts Spec.color(" # #{Path[ex.file].relative_to(cwd)}:#{ex.line}", :comment) end end end @@ -232,7 +234,7 @@ module Spec top_n.each do |res| puts " #{res.description}" res_elapsed = res.elapsed.not_nil!.total_seconds.humanize - puts " #{res_elapsed.colorize.bold} seconds #{Spec.relative_file(res.file)}:#{res.line}" + puts " #{res_elapsed.colorize.bold} seconds #{Path[res.file].relative_to(cwd)}:#{res.line}" end end @@ -258,7 +260,7 @@ module Spec puts "Failed examples:" puts failures_and_errors.each do |fail| - print Spec.color("crystal spec #{Spec.relative_file(fail.file)}:#{fail.line}", :error) + print Spec.color("crystal spec #{Path[fail.file].relative_to(cwd)}:#{fail.line}", :error) puts Spec.color(" # #{fail.description}", :comment) end end diff --git a/src/spec/source.cr b/src/spec/source.cr index 6db12c936ae4..cf057240abe5 100644 --- a/src/spec/source.cr +++ b/src/spec/source.cr @@ -11,14 +11,4 @@ module Spec lines = lines_cache.put_if_absent(file) { File.read_lines(file) } lines[line - 1]? end - - # :nodoc: - def self.relative_file(file) - cwd = Dir.current - if basename = file.lchop? cwd - basename.lchop '/' - else - file - end - end end From 884f3827814fe000ca80e0d88de6cf6668fb2d89 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Sat, 22 Jun 2024 15:26:46 -0400 Subject: [PATCH 1200/1551] Allow new formatter styles for trailing comma and whitespace around proc literal (#14726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation of changing the formatter style, this change accepts the new style without enforcing it. Existing code is not affected (this will happen in a follow-up). Co-authored-by: Johannes Müller --- spec/compiler/formatter/formatter_spec.cr | 50 ++++++++++++++++++++--- src/compiler/crystal/tools/formatter.cr | 7 ++-- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 7bb7a1034e72..7c332aac3b0a 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1,8 +1,8 @@ require "spec" require "../../../src/compiler/crystal/formatter" -private def assert_format(input, output = input, strict = false, flags = nil, file = __FILE__, line = __LINE__) - it "formats #{input.inspect}", file, line do +private def assert_format(input, output = input, strict = false, flags = nil, file = __FILE__, line = __LINE__, focus = false) + it "formats #{input.inspect}", file, line, focus: focus do output = "#{output}\n" unless strict result = Crystal.format(input, flags: flags) unless result == output @@ -812,7 +812,7 @@ describe Crystal::Formatter do end CRYSTAL def foo(x, - y) + y,) yield end CRYSTAL @@ -888,7 +888,7 @@ describe Crystal::Formatter do end CRYSTAL def foo( - x + x, ) yield end @@ -901,6 +901,39 @@ describe Crystal::Formatter do CRYSTAL end + # Allows trailing commas, but doesn't enforce them + assert_format <<-CRYSTAL + def foo( + a, + b + ) + end + CRYSTAL + + assert_format <<-CRYSTAL + def foo( + a, + b, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL + macro foo( + a, + *b, + ) + end + CRYSTAL + + assert_format <<-CRYSTAL + macro foo( + a, + **b, + ) + end + CRYSTAL + context "adds trailing comma to def multi-line normal, splat, and double splat parameters" do assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] macro foo( @@ -1693,6 +1726,13 @@ describe Crystal::Formatter do assert_format "-> : Int32 {}", "-> : Int32 { }", flags: %w[proc_literal_whitespace] assert_format "->do\nend", "-> do\nend", flags: %w[proc_literal_whitespace] + # Allows whitespace around proc literal, but doesn't enforce them + assert_format "-> { }" + assert_format "-> { 1 }" + assert_format "->(x : Int32) { }" + assert_format "-> : Int32 { }" + assert_format "-> do\nend" + assert_format "-> : Int32 {}" assert_format "-> : Int32 | String { 1 }" assert_format "-> : Array(Int32) {}" @@ -1703,7 +1743,7 @@ describe Crystal::Formatter do assert_format "-> : {Int32} { String }" assert_format "-> : {x: Int32, y: String} {}" assert_format "->\n:\nInt32\n{\n}", "-> : Int32 {\n}" - assert_format "->( x )\n:\nInt32 { }", "->(x) : Int32 {}" + assert_format "->( x )\n:\nInt32 { }", "->(x) : Int32 { }" assert_format "->: Int32 do\nx\nend", "-> : Int32 do\n x\nend" {:+, :-, :*, :/, :^, :>>, :<<, :|, :&, :&+, :&-, :&*, :&**}.each do |sym| diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index dc14c70a90ad..796afe0730de 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1651,7 +1651,7 @@ module Crystal yield # Write "," before skipping spaces to prevent inserting comment between argument and comma. - write "," if has_more || (write_trailing_comma && flag?("def_trailing_comma")) + write "," if has_more || (wrote_newline && @token.type.op_comma?) || (write_trailing_comma && flag?("def_trailing_comma")) just_wrote_newline = skip_space if @token.type.newline? @@ -4242,6 +4242,7 @@ module Crystal def visit(node : ProcLiteral) write_token :OP_MINUS_GT + whitespace_after_op_minus_gt = @token.type.space? skip_space_or_newline a_def = node.def @@ -4272,7 +4273,7 @@ module Crystal skip_space_or_newline end - write " " if a_def.args.present? || return_type || flag?("proc_literal_whitespace") + write " " if a_def.args.present? || return_type || flag?("proc_literal_whitespace") || whitespace_after_op_minus_gt is_do = false if @token.keyword?(:do) @@ -4280,7 +4281,7 @@ module Crystal is_do = true else write_token :OP_LCURLY - write " " if a_def.body.is_a?(Nop) && flag?("proc_literal_whitespace") + write " " if a_def.body.is_a?(Nop) && (flag?("proc_literal_whitespace") || @token.type.space?) end skip_space From a63e874948a0e26643e7c3e48ac1d7575f87dc93 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 23 Jun 2024 03:27:12 +0800 Subject: [PATCH 1201/1551] Fix ECR escape sequences containing `-` (#14739) --- spec/std/data/test_template7.ecr | 5 ++ spec/std/ecr/ecr_lexer_spec.cr | 81 ++++++++++++++++++++++++++++++++ spec/std/ecr/ecr_spec.cr | 12 +++++ src/ecr.cr | 3 +- src/ecr/lexer.cr | 36 +++++++------- 5 files changed, 118 insertions(+), 19 deletions(-) create mode 100644 spec/std/data/test_template7.ecr diff --git a/spec/std/data/test_template7.ecr b/spec/std/data/test_template7.ecr new file mode 100644 index 000000000000..0c1a9eff0806 --- /dev/null +++ b/spec/std/data/test_template7.ecr @@ -0,0 +1,5 @@ +<%% if @name %> +Greetings, <%%= @name %>! + <%-% else -%> +Greetings! +<%-% end -%> \ No newline at end of file diff --git a/spec/std/ecr/ecr_lexer_spec.cr b/spec/std/ecr/ecr_lexer_spec.cr index 05e3f5436b93..4a1968b8458f 100644 --- a/spec/std/ecr/ecr_lexer_spec.cr +++ b/spec/std/ecr/ecr_lexer_spec.cr @@ -210,6 +210,87 @@ describe "ECR::Lexer" do token.type.should eq(t :eof) end + it "lexes with <%-% %> (#14734)" do + lexer = ECR::Lexer.new("hello <%-% foo %> bar") + + token = lexer.next_token + token.type.should eq(t :string) + token.value.should eq("hello ") + token.column_number.should eq(1) + token.line_number.should eq(1) + + token = lexer.next_token + token.type.should eq(t :string) + token.value.should eq("<%- foo %>") + token.line_number.should eq(1) + token.column_number.should eq(11) + token.suppress_leading?.should be_false + token.suppress_trailing?.should be_false + + token = lexer.next_token + token.type.should eq(t :string) + token.value.should eq(" bar") + token.line_number.should eq(1) + token.column_number.should eq(18) + + token = lexer.next_token + token.type.should eq(t :eof) + end + + it "lexes with <%-%= %> (#14734)" do + lexer = ECR::Lexer.new("hello <%-%= foo %> bar") + + token = lexer.next_token + token.type.should eq(t :string) + token.value.should eq("hello ") + token.column_number.should eq(1) + token.line_number.should eq(1) + + token = lexer.next_token + token.type.should eq(t :string) + token.value.should eq("<%-= foo %>") + token.line_number.should eq(1) + token.column_number.should eq(11) + token.suppress_leading?.should be_false + token.suppress_trailing?.should be_false + + token = lexer.next_token + token.type.should eq(t :string) + token.value.should eq(" bar") + token.line_number.should eq(1) + token.column_number.should eq(19) + + token = lexer.next_token + token.type.should eq(t :eof) + end + + it "lexes with <%% -%> (#14734)" do + lexer = ECR::Lexer.new("hello <%% foo -%> bar") + + token = lexer.next_token + token.type.should eq(t :string) + token.value.should eq("hello ") + token.column_number.should eq(1) + token.line_number.should eq(1) + + token = lexer.next_token + token.type.should eq(t :string) + token.value.should eq("<% foo -%>") + token.line_number.should eq(1) + token.column_number.should eq(10) + token.suppress_leading?.should be_false + token.suppress_trailing?.should be_false + + token = lexer.next_token + token.type.should eq(t :string) + token.value.should eq(" bar") + token.line_number.should eq(1) + token.column_number.should eq(18) + + token = lexer.next_token + token.type.should eq(t :eof) + end + it "lexes with <% %> and correct location info" do lexer = ECR::Lexer.new("hi\nthere <% foo\nbar %> baz") diff --git a/spec/std/ecr/ecr_spec.cr b/spec/std/ecr/ecr_spec.cr index ce424785d805..0e35ea1dd1f1 100644 --- a/spec/std/ecr/ecr_spec.cr +++ b/spec/std/ecr/ecr_spec.cr @@ -65,6 +65,18 @@ describe "ECR" do io.to_s.should eq("string with -%") end + it "does with <%% %>" do + io = IO::Memory.new + ECR.embed "#{__DIR__}/../data/test_template7.ecr", io + io.to_s.should eq(<<-ECR) + <% if @name %> + Greetings, <%= @name %>! + <%- else -%> + Greetings! + <%- end -%> + ECR + end + it ".render" do ECR.render("#{__DIR__}/../data/test_template2.ecr").should eq("123") end diff --git a/src/ecr.cr b/src/ecr.cr index 42bee548b22c..785e47c5a762 100644 --- a/src/ecr.cr +++ b/src/ecr.cr @@ -14,7 +14,8 @@ # # A comment can be created the same as normal code: `<% # hello %>` or by the special # tag: `<%# hello %>`. An ECR tag can be inserted directly (i.e. the tag itself may be -# escaped) by using a second `%` like so: `<%% a = b %>` or `<%%= foo %>`. +# escaped) by using a second `%` like so: `<%% a = b %>` or `<%%= foo %>`. Dashes may +# also be present in those escaped tags and have no effect on the surrounding text. # # NOTE: To use `ECR`, you must explicitly import it with `require "ecr"` # diff --git a/src/ecr/lexer.cr b/src/ecr/lexer.cr index b5a30cae8e84..e32de726040f 100644 --- a/src/ecr/lexer.cr +++ b/src/ecr/lexer.cr @@ -44,12 +44,8 @@ class ECR::Lexer next_char next_char - if current_char == '-' - @token.suppress_leading = true - next_char - else - @token.suppress_leading = false - end + suppress_leading = current_char == '-' + next_char if suppress_leading case current_char when '=' @@ -64,7 +60,7 @@ class ECR::Lexer copy_location_info_to_token end - return consume_control(is_output, is_escape) + return consume_control(is_output, is_escape, suppress_leading) end else # consume string @@ -97,7 +93,7 @@ class ECR::Lexer @token end - private def consume_control(is_output, is_escape) + private def consume_control(is_output, is_escape, suppress_leading) start_pos = current_pos while true case current_char @@ -126,8 +122,7 @@ class ECR::Lexer @column_number = column_number if is_end - @token.suppress_trailing = true - setup_control_token(start_pos, is_escape) + setup_control_token(start_pos, is_escape, suppress_leading, true) raise "Expecting '>' after '-%'" if current_char != '>' next_char break @@ -135,8 +130,7 @@ class ECR::Lexer end when '%' if peek_next_char == '>' - @token.suppress_trailing = false - setup_control_token(start_pos, is_escape) + setup_control_token(start_pos, is_escape, suppress_leading, false) break end else @@ -155,12 +149,18 @@ class ECR::Lexer @token end - private def setup_control_token(start_pos, is_escape) - @token.value = if is_escape - "<%#{string_range(start_pos, current_pos + 2)}" - else - string_range(start_pos) - end + private def setup_control_token(start_pos, is_escape, suppress_leading, suppress_trailing) + @token.suppress_leading = !is_escape && suppress_leading + @token.suppress_trailing = !is_escape && suppress_trailing + @token.value = + if is_escape + head = suppress_leading ? "<%-" : "<%" + tail = string_range(start_pos, current_pos + (suppress_trailing ? 3 : 2)) + head + tail + else + string_range(start_pos) + end + next_char next_char end From 4f31615c50aa694ab46c13608080b9f551704cf7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 23 Jun 2024 03:27:46 +0800 Subject: [PATCH 1202/1551] Make `ReferenceStorage(T)` non-atomic if `T` is non-atomic (#14730) --- spec/primitives/pointer_spec.cr | 25 +++++++++++++++++++++++++ src/compiler/crystal/codegen/types.cr | 2 ++ 2 files changed, 27 insertions(+) create mode 100644 spec/primitives/pointer_spec.cr diff --git a/spec/primitives/pointer_spec.cr b/spec/primitives/pointer_spec.cr new file mode 100644 index 000000000000..1b62ec54a8d4 --- /dev/null +++ b/spec/primitives/pointer_spec.cr @@ -0,0 +1,25 @@ +require "spec" +require "../support/finalize" +require "../support/interpreted" + +private class Inner + include FinalizeCounter + + def initialize(@key : String) + end +end + +private class Outer + @inner = Inner.new("reference-storage") +end + +describe "Primitives: pointer" do + describe ".malloc" do + pending_interpreted "is non-atomic for ReferenceStorage(T) if T is non-atomic (#14692)" do + FinalizeState.reset + outer = Outer.unsafe_construct(Pointer(ReferenceStorage(Outer)).malloc(1)) + GC.collect + FinalizeState.count("reference-storage").should eq(0) + end + end +end diff --git a/src/compiler/crystal/codegen/types.cr b/src/compiler/crystal/codegen/types.cr index 654e7a281421..470fe7424dcd 100644 --- a/src/compiler/crystal/codegen/types.cr +++ b/src/compiler/crystal/codegen/types.cr @@ -69,6 +69,8 @@ module Crystal self.tuple_types.any? &.has_inner_pointers? when NamedTupleInstanceType self.entries.any? &.type.has_inner_pointers? + when ReferenceStorageType + self.reference_type.has_inner_pointers? when PrimitiveType false when EnumType From 7d8e2434edde9fde1777612500be81c2e1a3ee12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Cla=C3=9Fen?= Date: Mon, 24 Jun 2024 10:49:50 +0200 Subject: [PATCH 1203/1551] Fix docs for `CSV::Builder#row(&)` (#14736) --- src/csv/builder.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/csv/builder.cr b/src/csv/builder.cr index d0fae5cf6dfe..de53018795ad 100644 --- a/src/csv/builder.cr +++ b/src/csv/builder.cr @@ -51,7 +51,7 @@ class CSV::Builder @first_cell_in_row = true end - # Yields a `CSV::Row` to append a row. A newline is appended + # Yields a `CSV::Builder::Row` to append a row. A newline is appended # to `IO` after the block exits. def row(&) yield Row.new(self, @separator, @quote_char, @quoting) From feb612b6425ed216919c9e7ce796e6319dc5bd4a Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Mon, 24 Jun 2024 04:50:24 -0400 Subject: [PATCH 1204/1551] Add `Time::Error` (#14743) --- src/time.cr | 6 +++++- src/time/location.cr | 4 ++-- src/time/location/loader.cr | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/time.cr b/src/time.cr index fe5a21d7c77c..4b29114dd190 100644 --- a/src/time.cr +++ b/src/time.cr @@ -212,7 +212,11 @@ require "crystal/system/time" # elapsed_time # => 20.milliseconds (approximately) # ``` struct Time - class FloatingTimeConversionError < Exception + # Raised when an error occurs while performing a `Time` based operation. + class Error < Exception + end + + class FloatingTimeConversionError < Error end include Comparable(Time) diff --git a/src/time/location.cr b/src/time/location.cr index 7e0e8f160cb9..21d1e7a6e56d 100644 --- a/src/time/location.cr +++ b/src/time/location.cr @@ -51,7 +51,7 @@ class Time::Location # the time zone database. # # See `Time::Location.load` for details. - class InvalidLocationNameError < Exception + class InvalidLocationNameError < Time::Error getter name, source def initialize(@name : String, @source : String? = nil) @@ -63,7 +63,7 @@ class Time::Location # `InvalidTimezoneOffsetError` is raised if `Time::Location::Zone.new` # receives an invalid time zone offset. - class InvalidTimezoneOffsetError < Exception + class InvalidTimezoneOffsetError < Time::Error def initialize(offset : Int) super "Invalid time zone offset: #{offset}" end diff --git a/src/time/location/loader.cr b/src/time/location/loader.cr index 6555125e6ff7..6a104101405c 100644 --- a/src/time/location/loader.cr +++ b/src/time/location/loader.cr @@ -5,7 +5,7 @@ class Time::Location # time zone data. # # Details on the exact cause can be found in the error message. - class InvalidTZDataError < Exception + class InvalidTZDataError < Time::Error def self.initialize(message : String? = "Malformed time zone information", cause : Exception? = nil) super(message, cause) end From a1db18c3b8ca68dca0481b3622c04107321eb5be Mon Sep 17 00:00:00 2001 From: David Keller Date: Mon, 24 Jun 2024 20:57:21 +0200 Subject: [PATCH 1205/1551] Fix calls to `retry_with_buffer` when big buffer is necessary (#14622) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/crystal/system/unix/group.cr | 8 ++++---- src/crystal/system/unix/path.cr | 8 ++++---- src/crystal/system/unix/user.cr | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/crystal/system/unix/group.cr b/src/crystal/system/unix/group.cr index 020c76dab51b..d7d408f77608 100644 --- a/src/crystal/system/unix/group.cr +++ b/src/crystal/system/unix/group.cr @@ -12,9 +12,9 @@ module Crystal::System::Group groupname.check_no_null_byte grp = uninitialized LibC::Group - grp_pointer = pointerof(grp) + grp_pointer = Pointer(LibC::Group).null System.retry_with_buffer("getgrnam_r", GETGR_R_SIZE_MAX) do |buf| - LibC.getgrnam_r(groupname, grp_pointer, buf, buf.size, pointerof(grp_pointer)).tap do + LibC.getgrnam_r(groupname, pointerof(grp), buf, buf.size, pointerof(grp_pointer)).tap do # It's not necessary to check success with `ret == 0` because `grp_pointer` will be NULL on failure return from_struct(grp) if grp_pointer end @@ -26,9 +26,9 @@ module Crystal::System::Group return unless groupid grp = uninitialized LibC::Group - grp_pointer = pointerof(grp) + grp_pointer = Pointer(LibC::Group).null System.retry_with_buffer("getgrgid_r", GETGR_R_SIZE_MAX) do |buf| - LibC.getgrgid_r(groupid, grp_pointer, buf, buf.size, pointerof(grp_pointer)).tap do + LibC.getgrgid_r(groupid, pointerof(grp), buf, buf.size, pointerof(grp_pointer)).tap do # It's not necessary to check success with `ret == 0` because `grp_pointer` will be NULL on failure return from_struct(grp) if grp_pointer end diff --git a/src/crystal/system/unix/path.cr b/src/crystal/system/unix/path.cr index 4392486cbf6d..09588d688bc1 100644 --- a/src/crystal/system/unix/path.cr +++ b/src/crystal/system/unix/path.cr @@ -8,16 +8,16 @@ module Crystal::System::Path id = LibC.getuid pwd = uninitialized LibC::Passwd - pwd_pointer = pointerof(pwd) - ret = nil + pwd_pointer = Pointer(LibC::Passwd).null + ret = LibC::Int.new(0) System.retry_with_buffer("getpwuid_r", User::GETPW_R_SIZE_MAX) do |buf| - ret = LibC.getpwuid_r(id, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)).tap do + ret = LibC.getpwuid_r(id, pointerof(pwd), buf, buf.size, pointerof(pwd_pointer)).tap do # It's not necessary to check success with `ret == 0` because `pwd_pointer` will be NULL on failure return String.new(pwd.pw_dir) if pwd_pointer end end - raise RuntimeError.from_os_error("getpwuid_r", Errno.new(ret.not_nil!)) + raise RuntimeError.from_os_error("getpwuid_r", Errno.new(ret)) end end end diff --git a/src/crystal/system/unix/user.cr b/src/crystal/system/unix/user.cr index 9695f349957c..8e4f16e8c1c4 100644 --- a/src/crystal/system/unix/user.cr +++ b/src/crystal/system/unix/user.cr @@ -15,9 +15,9 @@ module Crystal::System::User username.check_no_null_byte pwd = uninitialized LibC::Passwd - pwd_pointer = pointerof(pwd) + pwd_pointer = Pointer(LibC::Passwd).null System.retry_with_buffer("getpwnam_r", GETPW_R_SIZE_MAX) do |buf| - LibC.getpwnam_r(username, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)).tap do + LibC.getpwnam_r(username, pointerof(pwd), buf, buf.size, pointerof(pwd_pointer)).tap do # It's not necessary to check success with `ret == 0` because `pwd_pointer` will be NULL on failure return from_struct(pwd) if pwd_pointer end @@ -29,9 +29,9 @@ module Crystal::System::User return unless id pwd = uninitialized LibC::Passwd - pwd_pointer = pointerof(pwd) + pwd_pointer = Pointer(LibC::Passwd).null System.retry_with_buffer("getpwuid_r", GETPW_R_SIZE_MAX) do |buf| - LibC.getpwuid_r(id, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)).tap do + LibC.getpwuid_r(id, pointerof(pwd), buf, buf.size, pointerof(pwd_pointer)).tap do # It's not necessary to check success with `ret == 0` because `pwd_pointer` will be NULL on failure return from_struct(pwd) if pwd_pointer end From 7e33ecc01ec62564d2cd68fde0e757fbbf509b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 25 Jun 2024 16:14:54 +0200 Subject: [PATCH 1206/1551] Refactor `IOCP::OverlappedOperation` internalize `handle` and `wait_for_completion` (#14724) * Add ivar `@handle` * Internalize `schedule_overlapped` as `wait_for_completion` * Add temporary special case for `overlapped_accept` --- src/crystal/system/win32/iocp.cr | 70 +++++++++++++++++------------- src/crystal/system/win32/socket.cr | 8 ++-- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr index 780b6f1ac6f0..ba0f11eb2af5 100644 --- a/src/crystal/system/win32/iocp.cr +++ b/src/crystal/system/win32/iocp.cr @@ -75,12 +75,19 @@ module Crystal::IOCP property previous : OverlappedOperation? @@canceled = Thread::LinkedList(OverlappedOperation).new + def initialize(@handle : LibC::HANDLE) + end + + def initialize(handle : LibC::SOCKET) + @handle = LibC::HANDLE.new(handle) + end + def self.run(handle, &) - operation = OverlappedOperation.new + operation = OverlappedOperation.new(handle) begin yield operation ensure - operation.done(handle) + operation.done end end @@ -93,9 +100,12 @@ module Crystal::IOCP pointerof(@overlapped) end - def result(handle, &) + def wait_for_result(timeout, &) + wait_for_completion(timeout) + raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started? - result = LibC.GetOverlappedResult(handle, self, out bytes, 0) + + result = LibC.GetOverlappedResult(@handle, self, out bytes, 0) if result.zero? error = WinError.value yield error @@ -106,10 +116,15 @@ module Crystal::IOCP bytes end - def wsa_result(socket, &) + def wait_for_wsa_result(timeout, &) + wait_for_completion(timeout) + wsa_result { |error| yield error } + end + + def wsa_result(&) raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started? flags = 0_u32 - result = LibC.WSAGetOverlappedResult(socket, self, out bytes, false, pointerof(flags)) + result = LibC.WSAGetOverlappedResult(LibC::SOCKET.new(@handle.address), self, out bytes, false, pointerof(flags)) if result.zero? error = WinError.wsa_value yield error @@ -132,15 +147,13 @@ module Crystal::IOCP end end - protected def done(handle) + protected def done case @state when .started? - handle = LibC::HANDLE.new(handle) if handle.is_a?(LibC::SOCKET) - # https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-cancelioex # > The application must not free or reuse the OVERLAPPED structure # associated with the canceled I/O operations until they have completed - if LibC.CancelIoEx(handle, self) != 0 + if LibC.CancelIoEx(@handle, self) != 0 @state = :cancelled @@canceled.push(self) # to increase lifetime end @@ -150,24 +163,23 @@ module Crystal::IOCP def done! @state = :done end - end - # Returns `false` if the operation timed out. - def self.schedule_overlapped(timeout : Time::Span?, line = __LINE__) : Bool - if timeout - timeout_event = Crystal::IOCP::Event.new(Fiber.current) - timeout_event.add(timeout) - else - timeout_event = Crystal::IOCP::Event.new(Fiber.current, Time::Span::MAX) - end - # memoize event loop to make sure that we still target the same instance - # after wakeup (guaranteed by current MT model but let's be future proof) - event_loop = Crystal::EventLoop.current - event_loop.enqueue(timeout_event) + def wait_for_completion(timeout) + if timeout + timeout_event = Crystal::IOCP::Event.new(Fiber.current) + timeout_event.add(timeout) + else + timeout_event = Crystal::IOCP::Event.new(Fiber.current, Time::Span::MAX) + end + # memoize event loop to make sure that we still target the same instance + # after wakeup (guaranteed by current MT model but let's be future proof) + event_loop = Crystal::EventLoop.current + event_loop.enqueue(timeout_event) - Fiber.suspend + Fiber.suspend - event_loop.dequeue(timeout_event) + event_loop.dequeue(timeout_event) + end end def self.overlapped_operation(target, handle, method, timeout, *, writing = false, &) @@ -192,9 +204,7 @@ module Crystal::IOCP return value end - schedule_overlapped(timeout) - - operation.result(handle) do |error| + operation.wait_for_result(timeout) do |error| case error when .error_io_incomplete? raise IO::TimeoutError.new("#{method} timed out") @@ -224,9 +234,7 @@ module Crystal::IOCP return value end - schedule_overlapped(timeout) - - operation.wsa_result(socket) do |error| + operation.wait_for_wsa_result(timeout) do |error| case error when .wsa_io_incomplete? raise IO::TimeoutError.new("#{method} timed out") diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index c04d3a9ad868..2a540f4df88d 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -146,9 +146,7 @@ module Crystal::System::Socket return nil end - IOCP.schedule_overlapped(read_timeout || 1.seconds) - - operation.wsa_result(socket) do |error| + operation.wait_for_wsa_result(read_timeout || 1.seconds) do |error| case error when .wsa_io_incomplete?, .wsaeconnrefused? return ::Socket::ConnectError.from_os_error(method, error) @@ -210,11 +208,11 @@ module Crystal::System::Socket return true end - unless IOCP.schedule_overlapped(read_timeout) + unless operation.wait_for_completion(read_timeout) raise IO::TimeoutError.new("#{method} timed out") end - operation.wsa_result(socket) do |error| + operation.wsa_result do |error| case error when .wsa_io_incomplete?, .wsaenotsock? return false From da8a9bda835cae3473d2977938be8e712236143c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 26 Jun 2024 00:01:34 +0200 Subject: [PATCH 1207/1551] Fix `Process.run` with closed IO (#14698) IOs passed to `Process.run` experience some indirection which blew up if an IO is closed. This patch ensures that closed IOs are handled correctly. * A closed IO that is not a `IO::FileDescriptor` is replaced with a closed file descriptor * A closed file descriptor won't be reopened in the new process --- spec/std/process_spec.cr | 8 ++++++++ src/crystal/system/unix/process.cr | 5 +++++ src/process.cr | 8 ++++++++ 3 files changed, 21 insertions(+) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index d656e9353589..f067d2f5c775 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -181,6 +181,14 @@ pending_interpreted describe: Process do $?.exit_code.should eq(0) end + it "forwards closed io" do + closed_io = IO::Memory.new + closed_io.close + Process.run(*stdin_to_stdout_command, input: closed_io) + Process.run(*stdin_to_stdout_command, output: closed_io) + Process.run(*stdin_to_stdout_command, error: closed_io) + end + it "sets working directory with string" do parent = File.dirname(Dir.current) command = {% if flag?(:win32) %} diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index f3d5dbf3eddb..83f95cc8648c 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -337,6 +337,11 @@ struct Crystal::System::Process end private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor) + if src_io.closed? + dst_io.close + return + end + src_io = to_real_fd(src_io) dst_io.reopen(src_io) diff --git a/src/process.cr b/src/process.cr index a1b827d73754..045615c814a7 100644 --- a/src/process.cr +++ b/src/process.cr @@ -294,6 +294,14 @@ class Process when IO::FileDescriptor stdio when IO + if stdio.closed? + if dst_io == STDIN + return File.open(File::NULL, "r").tap(&.close) + else + return File.open(File::NULL, "w").tap(&.close) + end + end + if dst_io == STDIN fork_io, process_io = IO.pipe(read_blocking: true) From 28342af2066f9c2ae83bcdbc4f1e62f47dd31584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 26 Jun 2024 00:02:40 +0200 Subject: [PATCH 1208/1551] Remove unnecessary calls to `#unsafe_as(UInt64)` etc. (#14686) Rewrites calls of `unsafe_as` which are casting between integer types. These conversions are not unsafe and don't need `unsafe_as`. They can be expressed with appropriate `to_uX!` calls. --- src/compiler/crystal/interpreter/compiler.cr | 2 +- src/compiler/crystal/interpreter/instructions.cr | 2 +- src/crystal/hasher.cr | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index b25b6e7e25f9..50024d8b65e3 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -3350,7 +3350,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor end private def append(value : Int8) - append value.unsafe_as(UInt8) + append value.to_u8! end private def append(value : Symbol) diff --git a/src/compiler/crystal/interpreter/instructions.cr b/src/compiler/crystal/interpreter/instructions.cr index af1af55301e2..8fae94f5ee62 100644 --- a/src/compiler/crystal/interpreter/instructions.cr +++ b/src/compiler/crystal/interpreter/instructions.cr @@ -1472,7 +1472,7 @@ require "./repl" symbol_to_s: { pop_values: [index : Int32], push: true, - code: @context.index_to_symbol(index).object_id.unsafe_as(UInt64), + code: @context.index_to_symbol(index).object_id.to_u64!, }, # >>> Symbol (1) diff --git a/src/crystal/hasher.cr b/src/crystal/hasher.cr index 0c80fe5a0c50..6d5c90853af8 100644 --- a/src/crystal/hasher.cr +++ b/src/crystal/hasher.cr @@ -77,7 +77,7 @@ struct Crystal::Hasher HASH_NAN = 0_u64 HASH_INF_PLUS = 314159_u64 - HASH_INF_MINUS = (-314159_i64).unsafe_as(UInt64) + HASH_INF_MINUS = (-314159_i64).to_u64! @@seed = uninitialized UInt64[2] Crystal::System::Random.random_bytes(@@seed.to_slice.to_unsafe_bytes) @@ -106,7 +106,7 @@ struct Crystal::Hasher end def self.reduce_num(value : Int8 | Int16 | Int32) - value.to_i64.unsafe_as(UInt64) + value.to_u64! end def self.reduce_num(value : UInt8 | UInt16 | UInt32) @@ -118,7 +118,9 @@ struct Crystal::Hasher end def self.reduce_num(value : Int) - value.remainder(HASH_MODULUS).to_i64.unsafe_as(UInt64) + # The result of `remainder(HASH_MODULUS)` is a 64-bit integer, + # and thus guaranteed to fit into `UInt64` + value.remainder(HASH_MODULUS).to_u64! end # This function is for reference implementation, and it is used for `BigFloat`. @@ -157,7 +159,7 @@ struct Crystal::Hasher exp = exp >= 0 ? exp % HASH_BITS : HASH_BITS - 1 - ((-1 - exp) % HASH_BITS) x = ((x << exp) & HASH_MODULUS) | x >> (HASH_BITS - exp) - (x * (value < 0 ? -1 : 1)).to_i64.unsafe_as(UInt64) + (x * (value < 0 ? -1 : 1)).to_u64! end def self.reduce_num(value : Float32) From b08f4a252e9dca8bd8c05c09b82dda7c0e8e38e6 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 26 Jun 2024 00:03:58 +0200 Subject: [PATCH 1209/1551] Add `StringLiteral#to_utf16` (#14676) Implements `{{ "hello".to_utf16 }}` by exposing `String#to_utf16` in the macro language. Co-authored-by: Quinton Miller --- spec/compiler/macro/macro_methods_spec.cr | 5 +++++ src/compiler/crystal/macros/methods.cr | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 4f5ebf299677..29de1a51c2be 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -591,6 +591,11 @@ module Crystal assert_macro %({{"hello world".titleize}}), %("Hello World") end + it "executes to_utf16" do + assert_macro %({{"hello".to_utf16}}), "(::Slice(::UInt16).literal(104_u16, 101_u16, 108_u16, 108_u16, 111_u16, 0_u16))[0, 5]" + assert_macro %({{"TEST 😐🐙 ±∀ の".to_utf16}}), "(::Slice(::UInt16).literal(84_u16, 69_u16, 83_u16, 84_u16, 32_u16, 55357_u16, 56848_u16, 55357_u16, 56345_u16, 32_u16, 177_u16, 8704_u16, 32_u16, 12398_u16, 0_u16))[0, 14]" + end + it "executes to_i" do assert_macro %({{"1234".to_i}}), %(1234) end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 10e091e3a456..a44bba1b76f9 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -820,6 +820,21 @@ module Crystal else raise "StringLiteral#to_i: #{@value} is not an integer" end + when "to_utf16" + interpret_check_args do + slice = @value.to_utf16 + + # include the trailing zero that isn't counted in the slice but was + # generated by String#to_utf16 so the literal can be passed to C + # functions that expect a null terminated UInt16* + args = Slice(UInt16).new(slice.to_unsafe, slice.size + 1).to_a do |codepoint| + NumberLiteral.new(codepoint).as(ASTNode) + end + literal_node = Call.new(Generic.new(Path.global("Slice"), [Path.global("UInt16")] of ASTNode), "literal", args) + + # but keep the trailing zero hidden in the exposed slice + Call.new(literal_node, "[]", [NumberLiteral.new("0", :i32), NumberLiteral.new(slice.size)] of ASTNode) + end when "tr" interpret_check_args do |first, second| raise "first argument to StringLiteral#tr must be a string, not #{first.class_desc}" unless first.is_a?(StringLiteral) From da33258d547df54220775c90039d50773439c220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 26 Jun 2024 10:49:30 +0200 Subject: [PATCH 1210/1551] Restore leading zero in exponent for `printf("%e")` and `printf("%g")` (#14695) --- spec/std/sprintf_spec.cr | 204 +++++++++++++++++++-------------------- src/string/formatter.cr | 12 ++- 2 files changed, 112 insertions(+), 104 deletions(-) diff --git a/spec/std/sprintf_spec.cr b/spec/std/sprintf_spec.cr index 67cc1ac1604c..a91ce8030915 100644 --- a/spec/std/sprintf_spec.cr +++ b/spec/std/sprintf_spec.cr @@ -411,14 +411,14 @@ describe "::sprintf" do context "scientific format" do it "works" do - assert_sprintf "%e", 123.45, "1.234500e+2" - assert_sprintf "%E", 123.45, "1.234500E+2" + assert_sprintf "%e", 123.45, "1.234500e+02" + assert_sprintf "%E", 123.45, "1.234500E+02" assert_sprintf "%e", Float64::MAX, "1.797693e+308" assert_sprintf "%e", Float64::MIN_POSITIVE, "2.225074e-308" assert_sprintf "%e", Float64::MIN_SUBNORMAL, "4.940656e-324" - assert_sprintf "%e", 0.0, "0.000000e+0" - assert_sprintf "%e", -0.0, "-0.000000e+0" + assert_sprintf "%e", 0.0, "0.000000e+00" + assert_sprintf "%e", -0.0, "-0.000000e+00" assert_sprintf "%e", -Float64::MIN_SUBNORMAL, "-4.940656e-324" assert_sprintf "%e", -Float64::MIN_POSITIVE, "-2.225074e-308" assert_sprintf "%e", Float64::MIN, "-1.797693e+308" @@ -426,45 +426,45 @@ describe "::sprintf" do context "width specifier" do it "sets the minimum length of the string" do - assert_sprintf "%20e", 123.45, " 1.234500e+2" - assert_sprintf "%20e", -123.45, " -1.234500e+2" - assert_sprintf "%+20e", 123.45, " +1.234500e+2" + assert_sprintf "%20e", 123.45, " 1.234500e+02" + assert_sprintf "%20e", -123.45, " -1.234500e+02" + assert_sprintf "%+20e", 123.45, " +1.234500e+02" - assert_sprintf "%12e", 123.45, " 1.234500e+2" - assert_sprintf "%12e", -123.45, "-1.234500e+2" - assert_sprintf "%+12e", 123.45, "+1.234500e+2" + assert_sprintf "%13e", 123.45, " 1.234500e+02" + assert_sprintf "%13e", -123.45, "-1.234500e+02" + assert_sprintf "%+13e", 123.45, "+1.234500e+02" - assert_sprintf "%11e", 123.45, "1.234500e+2" - assert_sprintf "%11e", -123.45, "-1.234500e+2" - assert_sprintf "%+11e", 123.45, "+1.234500e+2" + assert_sprintf "%12e", 123.45, "1.234500e+02" + assert_sprintf "%12e", -123.45, "-1.234500e+02" + assert_sprintf "%+12e", 123.45, "+1.234500e+02" - assert_sprintf "%2e", 123.45, "1.234500e+2" - assert_sprintf "%2e", -123.45, "-1.234500e+2" - assert_sprintf "%+2e", 123.45, "+1.234500e+2" + assert_sprintf "%2e", 123.45, "1.234500e+02" + assert_sprintf "%2e", -123.45, "-1.234500e+02" + assert_sprintf "%+2e", 123.45, "+1.234500e+02" end it "left-justifies on negative width" do - assert_sprintf "%*e", [-20, 123.45], "1.234500e+2 " + assert_sprintf "%*e", [-20, 123.45], "1.234500e+02 " end end context "precision specifier" do it "sets the minimum length of the fractional part" do - assert_sprintf "%.0e", 2.0, "2e+0" - assert_sprintf "%.0e", 2.5.prev_float, "2e+0" - assert_sprintf "%.0e", 2.5, "2e+0" - assert_sprintf "%.0e", 2.5.next_float, "3e+0" - assert_sprintf "%.0e", 3.0, "3e+0" - assert_sprintf "%.0e", 3.5.prev_float, "3e+0" - assert_sprintf "%.0e", 3.5, "4e+0" - assert_sprintf "%.0e", 3.5.next_float, "4e+0" - assert_sprintf "%.0e", 4.0, "4e+0" + assert_sprintf "%.0e", 2.0, "2e+00" + assert_sprintf "%.0e", 2.5.prev_float, "2e+00" + assert_sprintf "%.0e", 2.5, "2e+00" + assert_sprintf "%.0e", 2.5.next_float, "3e+00" + assert_sprintf "%.0e", 3.0, "3e+00" + assert_sprintf "%.0e", 3.5.prev_float, "3e+00" + assert_sprintf "%.0e", 3.5, "4e+00" + assert_sprintf "%.0e", 3.5.next_float, "4e+00" + assert_sprintf "%.0e", 4.0, "4e+00" - assert_sprintf "%.0e", 9.5, "1e+1" + assert_sprintf "%.0e", 9.5, "1e+01" - assert_sprintf "%.100e", 1.1, "1.1000000000000000888178419700125232338905334472656250000000000000000000000000000000000000000000000000e+0" + assert_sprintf "%.100e", 1.1, "1.1000000000000000888178419700125232338905334472656250000000000000000000000000000000000000000000000000e+00" - assert_sprintf "%.10000e", 1.0, "1.#{"0" * 10000}e+0" + assert_sprintf "%.10000e", 1.0, "1.#{"0" * 10000}e+00" assert_sprintf "%.1000e", Float64::MIN_POSITIVE.prev_float, "2.2250738585072008890245868760858598876504231122409594654935248025624400092282356951" \ @@ -482,103 +482,103 @@ describe "::sprintf" do end it "can be used with width" do - assert_sprintf "%20.12e", 123.45, " 1.234500000000e+2" - assert_sprintf "%20.12e", -123.45, " -1.234500000000e+2" - assert_sprintf "%20.12e", 0.0, " 0.000000000000e+0" + assert_sprintf "%20.13e", 123.45, " 1.2345000000000e+02" + assert_sprintf "%20.13e", -123.45, "-1.2345000000000e+02" + assert_sprintf "%20.13e", 0.0, " 0.0000000000000e+00" - assert_sprintf "%-20.12e", 123.45, "1.234500000000e+2 " - assert_sprintf "%-20.12e", -123.45, "-1.234500000000e+2 " - assert_sprintf "%-20.12e", 0.0, "0.000000000000e+0 " + assert_sprintf "%-20.13e", 123.45, "1.2345000000000e+02 " + assert_sprintf "%-20.13e", -123.45, "-1.2345000000000e+02" + assert_sprintf "%-20.13e", 0.0, "0.0000000000000e+00 " - assert_sprintf "%8.12e", 123.45, "1.234500000000e+2" - assert_sprintf "%8.12e", -123.45, "-1.234500000000e+2" - assert_sprintf "%8.12e", 0.0, "0.000000000000e+0" + assert_sprintf "%8.13e", 123.45, "1.2345000000000e+02" + assert_sprintf "%8.13e", -123.45, "-1.2345000000000e+02" + assert_sprintf "%8.13e", 0.0, "0.0000000000000e+00" end it "is ignored if precision argument is negative" do - assert_sprintf "%.*e", [-2, 123.45], "1.234500e+2" + assert_sprintf "%.*e", [-2, 123.45], "1.234500e+02" end end context "sharp flag" do it "prints a decimal point even if no digits follow" do - assert_sprintf "%#.0e", 1.0, "1.e+0" - assert_sprintf "%#.0e", 10000.0, "1.e+4" + assert_sprintf "%#.0e", 1.0, "1.e+00" + assert_sprintf "%#.0e", 10000.0, "1.e+04" assert_sprintf "%#.0e", 1.0e+23, "1.e+23" assert_sprintf "%#.0e", 1.0e-100, "1.e-100" - assert_sprintf "%#.0e", 0.0, "0.e+0" - assert_sprintf "%#.0e", -0.0, "-0.e+0" + assert_sprintf "%#.0e", 0.0, "0.e+00" + assert_sprintf "%#.0e", -0.0, "-0.e+00" end end context "plus flag" do it "writes a plus sign for positive values" do - assert_sprintf "%+e", 123.45, "+1.234500e+2" - assert_sprintf "%+e", -123.45, "-1.234500e+2" - assert_sprintf "%+e", 0.0, "+0.000000e+0" + assert_sprintf "%+e", 123.45, "+1.234500e+02" + assert_sprintf "%+e", -123.45, "-1.234500e+02" + assert_sprintf "%+e", 0.0, "+0.000000e+00" end it "writes plus sign after left space-padding" do - assert_sprintf "%+20e", 123.45, " +1.234500e+2" - assert_sprintf "%+20e", -123.45, " -1.234500e+2" - assert_sprintf "%+20e", 0.0, " +0.000000e+0" + assert_sprintf "%+20e", 123.45, " +1.234500e+02" + assert_sprintf "%+20e", -123.45, " -1.234500e+02" + assert_sprintf "%+20e", 0.0, " +0.000000e+00" end it "writes plus sign before left zero-padding" do - assert_sprintf "%+020e", 123.45, "+000000001.234500e+2" - assert_sprintf "%+020e", -123.45, "-000000001.234500e+2" - assert_sprintf "%+020e", 0.0, "+000000000.000000e+0" + assert_sprintf "%+020e", 123.45, "+00000001.234500e+02" + assert_sprintf "%+020e", -123.45, "-00000001.234500e+02" + assert_sprintf "%+020e", 0.0, "+00000000.000000e+00" end end context "space flag" do it "writes a space for positive values" do - assert_sprintf "% e", 123.45, " 1.234500e+2" - assert_sprintf "% e", -123.45, "-1.234500e+2" - assert_sprintf "% e", 0.0, " 0.000000e+0" + assert_sprintf "% e", 123.45, " 1.234500e+02" + assert_sprintf "% e", -123.45, "-1.234500e+02" + assert_sprintf "% e", 0.0, " 0.000000e+00" end it "writes space before left space-padding" do - assert_sprintf "% 20e", 123.45, " 1.234500e+2" - assert_sprintf "% 20e", -123.45, " -1.234500e+2" - assert_sprintf "% 20e", 0.0, " 0.000000e+0" + assert_sprintf "% 20e", 123.45, " 1.234500e+02" + assert_sprintf "% 20e", -123.45, " -1.234500e+02" + assert_sprintf "% 20e", 0.0, " 0.000000e+00" - assert_sprintf "% 020e", 123.45, " 000000001.234500e+2" - assert_sprintf "% 020e", -123.45, "-000000001.234500e+2" - assert_sprintf "% 020e", 0.0, " 000000000.000000e+0" + assert_sprintf "% 020e", 123.45, " 00000001.234500e+02" + assert_sprintf "% 020e", -123.45, "-00000001.234500e+02" + assert_sprintf "% 020e", 0.0, " 00000000.000000e+00" end it "is ignored if plus flag is also specified" do - assert_sprintf "% +e", 123.45, "+1.234500e+2" - assert_sprintf "%+ e", -123.45, "-1.234500e+2" + assert_sprintf "% +e", 123.45, "+1.234500e+02" + assert_sprintf "%+ e", -123.45, "-1.234500e+02" end end context "zero flag" do it "left-pads the result with zeros" do - assert_sprintf "%020e", 123.45, "0000000001.234500e+2" - assert_sprintf "%020e", -123.45, "-000000001.234500e+2" - assert_sprintf "%020e", 0.0, "0000000000.000000e+0" + assert_sprintf "%020e", 123.45, "000000001.234500e+02" + assert_sprintf "%020e", -123.45, "-00000001.234500e+02" + assert_sprintf "%020e", 0.0, "000000000.000000e+00" end it "is ignored if string is left-justified" do - assert_sprintf "%-020e", 123.45, "1.234500e+2 " - assert_sprintf "%-020e", -123.45, "-1.234500e+2 " - assert_sprintf "%-020e", 0.0, "0.000000e+0 " + assert_sprintf "%-020e", 123.45, "1.234500e+02 " + assert_sprintf "%-020e", -123.45, "-1.234500e+02 " + assert_sprintf "%-020e", 0.0, "0.000000e+00 " end it "can be used with precision" do - assert_sprintf "%020.12e", 123.45, "0001.234500000000e+2" - assert_sprintf "%020.12e", -123.45, "-001.234500000000e+2" - assert_sprintf "%020.12e", 0.0, "0000.000000000000e+0" + assert_sprintf "%020.12e", 123.45, "001.234500000000e+02" + assert_sprintf "%020.12e", -123.45, "-01.234500000000e+02" + assert_sprintf "%020.12e", 0.0, "000.000000000000e+00" end end context "minus flag" do it "left-justifies the string" do - assert_sprintf "%-20e", 123.45, "1.234500e+2 " - assert_sprintf "%-20e", -123.45, "-1.234500e+2 " - assert_sprintf "%-20e", 0.0, "0.000000e+0 " + assert_sprintf "%-20e", 123.45, "1.234500e+02 " + assert_sprintf "%-20e", -123.45, "-1.234500e+02 " + assert_sprintf "%-20e", 0.0, "0.000000e+00 " end end end @@ -588,8 +588,8 @@ describe "::sprintf" do assert_sprintf "%g", 123.45, "123.45" assert_sprintf "%G", 123.45, "123.45" - assert_sprintf "%g", 1.2345e-5, "1.2345e-5" - assert_sprintf "%G", 1.2345e-5, "1.2345E-5" + assert_sprintf "%g", 1.2345e-5, "1.2345e-05" + assert_sprintf "%G", 1.2345e-5, "1.2345E-05" assert_sprintf "%g", 1.2345e+25, "1.2345e+25" assert_sprintf "%G", 1.2345e+25, "1.2345E+25" @@ -630,9 +630,9 @@ describe "::sprintf" do context "precision specifier" do it "sets the precision of the value" do - assert_sprintf "%.0g", 123.45, "1e+2" - assert_sprintf "%.1g", 123.45, "1e+2" - assert_sprintf "%.2g", 123.45, "1.2e+2" + assert_sprintf "%.0g", 123.45, "1e+02" + assert_sprintf "%.1g", 123.45, "1e+02" + assert_sprintf "%.2g", 123.45, "1.2e+02" assert_sprintf "%.3g", 123.45, "123" assert_sprintf "%.4g", 123.45, "123.5" assert_sprintf "%.5g", 123.45, "123.45" @@ -650,41 +650,41 @@ describe "::sprintf" do assert_sprintf "%.5g", 1.23e-45, "1.23e-45" assert_sprintf "%.6g", 1.23e-45, "1.23e-45" - assert_sprintf "%.1000g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125e-5" + assert_sprintf "%.1000g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125e-05" end it "can be used with width" do - assert_sprintf "%10.1g", 123.45, " 1e+2" - assert_sprintf "%10.2g", 123.45, " 1.2e+2" + assert_sprintf "%10.1g", 123.45, " 1e+02" + assert_sprintf "%10.2g", 123.45, " 1.2e+02" assert_sprintf "%10.3g", 123.45, " 123" assert_sprintf "%10.4g", 123.45, " 123.5" assert_sprintf "%10.5g", 123.45, " 123.45" - assert_sprintf "%10.1g", -123.45, " -1e+2" - assert_sprintf "%10.2g", -123.45, " -1.2e+2" + assert_sprintf "%10.1g", -123.45, " -1e+02" + assert_sprintf "%10.2g", -123.45, " -1.2e+02" assert_sprintf "%10.3g", -123.45, " -123" assert_sprintf "%10.4g", -123.45, " -123.5" assert_sprintf "%10.5g", -123.45, " -123.45" assert_sprintf "%10.5g", 0, " 0" - assert_sprintf "%-10.1g", 123.45, "1e+2 " - assert_sprintf "%-10.2g", 123.45, "1.2e+2 " + assert_sprintf "%-10.1g", 123.45, "1e+02 " + assert_sprintf "%-10.2g", 123.45, "1.2e+02 " assert_sprintf "%-10.3g", 123.45, "123 " assert_sprintf "%-10.4g", 123.45, "123.5 " assert_sprintf "%-10.5g", 123.45, "123.45 " - assert_sprintf "%-10.1g", -123.45, "-1e+2 " - assert_sprintf "%-10.2g", -123.45, "-1.2e+2 " + assert_sprintf "%-10.1g", -123.45, "-1e+02 " + assert_sprintf "%-10.2g", -123.45, "-1.2e+02 " assert_sprintf "%-10.3g", -123.45, "-123 " assert_sprintf "%-10.4g", -123.45, "-123.5 " assert_sprintf "%-10.5g", -123.45, "-123.45 " assert_sprintf "%-10.5g", 0, "0 " - assert_sprintf "%3.1g", 123.45, "1e+2" - assert_sprintf "%3.2g", 123.45, "1.2e+2" + assert_sprintf "%3.1g", 123.45, "1e+02" + assert_sprintf "%3.2g", 123.45, "1.2e+02" assert_sprintf "%3.3g", 123.45, "123" assert_sprintf "%3.4g", 123.45, "123.5" assert_sprintf "%3.5g", 123.45, "123.45" - assert_sprintf "%3.1g", -123.45, "-1e+2" - assert_sprintf "%3.2g", -123.45, "-1.2e+2" + assert_sprintf "%3.1g", -123.45, "-1e+02" + assert_sprintf "%3.2g", -123.45, "-1.2e+02" assert_sprintf "%3.3g", -123.45, "-123" assert_sprintf "%3.4g", -123.45, "-123.5" assert_sprintf "%3.5g", -123.45, "-123.45" @@ -699,19 +699,19 @@ describe "::sprintf" do context "sharp flag" do it "prints decimal point and trailing zeros" do - assert_sprintf "%#.0g", 12345, "1.e+4" + assert_sprintf "%#.0g", 12345, "1.e+04" assert_sprintf "%#.6g", 12345, "12345.0" assert_sprintf "%#.10g", 12345, "12345.00000" assert_sprintf "%#.100g", 12345, "12345.#{"0" * 95}" assert_sprintf "%#.1000g", 12345, "12345.#{"0" * 995}" - assert_sprintf "%#.0g", 1e-5, "1.e-5" - assert_sprintf "%#.6g", 1e-5, "1.00000e-5" - assert_sprintf "%#.10g", 1e-5, "1.000000000e-5" - assert_sprintf "%#.100g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125#{"0" * 35}e-5" - assert_sprintf "%#.1000g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125#{"0" * 935}e-5" + assert_sprintf "%#.0g", 1e-5, "1.e-05" + assert_sprintf "%#.6g", 1e-5, "1.00000e-05" + assert_sprintf "%#.10g", 1e-5, "1.000000000e-05" + assert_sprintf "%#.100g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125#{"0" * 35}e-05" + assert_sprintf "%#.1000g", 1e-5, "1.0000000000000000818030539140313095458623138256371021270751953125#{"0" * 935}e-05" - assert_sprintf "%#15.0g", 12345, " 1.e+4" + assert_sprintf "%#15.0g", 12345, " 1.e+04" assert_sprintf "%#15.6g", 12345, " 12345.0" assert_sprintf "%#15.10g", 12345, " 12345.00000" end @@ -774,8 +774,8 @@ describe "::sprintf" do end it "can be used with precision" do - assert_sprintf "%010.2g", 123.45, "00001.2e+2" - assert_sprintf "%010.2g", -123.45, "-0001.2e+2" + assert_sprintf "%010.2g", 123.45, "0001.2e+02" + assert_sprintf "%010.2g", -123.45, "-001.2e+02" assert_sprintf "%010.2g", 0.0, "0000000000" end end diff --git a/src/string/formatter.cr b/src/string/formatter.cr index 0d4956dc0c05..60da55a2601f 100644 --- a/src/string/formatter.cr +++ b/src/string/formatter.cr @@ -430,6 +430,7 @@ struct String::Formatter(A) str_size = printf_size + trailing_zeros str_size += 1 if sign < 0 || flags.plus || flags.space str_size += 1 if flags.sharp && dot_index.nil? + str_size += 1 if printf_slice.size - e_index < 4 pad(str_size, flags) if flags.left_padding? && flags.padding_char != '0' @@ -441,7 +442,9 @@ struct String::Formatter(A) @io.write_string(printf_slice[0, e_index]) trailing_zeros.times { @io << '0' } @io << '.' if flags.sharp && dot_index.nil? - @io.write_string(printf_slice[e_index..]) + @io.write_string(printf_slice[e_index, 2]) + @io << '0' if printf_slice.size - e_index < 4 + @io.write_string(printf_slice[(e_index + 2)..]) pad(str_size, flags) if flags.right_padding? end @@ -465,6 +468,7 @@ struct String::Formatter(A) str_size = printf_size str_size += 1 if sign < 0 || flags.plus || flags.space str_size += (dot_index.nil? ? 1 : 0) + trailing_zeros if flags.sharp + str_size += 1 if printf_slice.size - e_index < 4 if e_index pad(str_size, flags) if flags.left_padding? && flags.padding_char != '0' @@ -476,7 +480,11 @@ struct String::Formatter(A) @io.write_string(printf_slice[0...e_index]) trailing_zeros.times { @io << '0' } if flags.sharp @io << '.' if flags.sharp && dot_index.nil? - @io.write_string(printf_slice[e_index..]) if e_index + if e_index + @io.write_string(printf_slice[e_index, 2]) + @io << '0' if printf_slice.size - e_index < 4 + @io.write_string(printf_slice[(e_index + 2)..]) + end pad(str_size, flags) if flags.right_padding? end From 6c344943792be2e581c54e21b317d0dae12bb0e4 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 26 Jun 2024 10:50:58 +0200 Subject: [PATCH 1211/1551] Add `Crystal::System.panic` (#14733) Prints a system error message on the standard error then exits with an error status. Raising an exception should always be preferred but there are a few cases where we can't allocate any memory (e.g. stop the world) and still need to fail when reaching a system error. --- src/crystal/system/panic.cr | 16 ++++++++++++++++ src/crystal/tracing.cr | 22 +++++++++++----------- src/errno.cr | 10 ++++++++-- src/wasi_error.cr | 5 +++++ src/winerror.cr | 12 ++++++++---- 5 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 src/crystal/system/panic.cr diff --git a/src/crystal/system/panic.cr b/src/crystal/system/panic.cr new file mode 100644 index 000000000000..192b735e4d0f --- /dev/null +++ b/src/crystal/system/panic.cr @@ -0,0 +1,16 @@ +module Crystal::System + # Prints a system error message on the standard error then exits with an error + # status. + # + # You should always prefer raising an exception, built with + # `RuntimeError.from_os_error` for example, but there are a few cases where we + # can't allocate any memory (e.g. stop the world) and still need to fail when + # reaching a system error. + def self.panic(syscall_name : String, error : Errno | WinError | WasiError) : NoReturn + System.print_error("%s failed with ", syscall_name) + error.unsafe_message { |slice| System.print_error(slice) } + System.print_error(" (%s)\n", error.to_s) + + LibC._exit(1) + end +end diff --git a/src/crystal/tracing.cr b/src/crystal/tracing.cr index ad3ae184a54a..a680bfea717f 100644 --- a/src/crystal/tracing.cr +++ b/src/crystal/tracing.cr @@ -1,3 +1,5 @@ +require "./system/panic" + module Crystal # :nodoc: module Tracing @@ -143,23 +145,21 @@ module Crystal # not using LibC::INVALID_HANDLE_VALUE because it doesn't exist (yet) return handle.address unless handle == LibC::HANDLE.new(-1.to_u64!) - error = uninitialized UInt16[256] - len = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, WinError.value, 0, error, error.size, nil) - - # not using printf because filename and error are UTF-16 slices: - System.print_error "ERROR: failed to open " - System.print_error filename - System.print_error " for writing: " - System.print_error error.to_slice[0...len] - System.print_error "\n" + syscall_name = "CreateFileW" + error = WinError.value {% else %} fd = LibC.open(filename, LibC::O_CREAT | LibC::O_WRONLY | LibC::O_TRUNC | LibC::O_CLOEXEC, 0o644) return fd unless fd < 0 - System.print_error "ERROR: failed to open %s for writing: %s\n", filename, LibC.strerror(Errno.value) + syscall_name = "open" + error = Errno.value {% end %} - LibC._exit(1) + System.print_error "ERROR: failed to open " + System.print_error filename + System.print_error " for writing\n" + + System.panic(syscall_name, Errno.value) end private def self.parse_sections(slice) diff --git a/src/errno.cr b/src/errno.cr index 03ca6085eb8a..2a68371f4a19 100644 --- a/src/errno.cr +++ b/src/errno.cr @@ -40,10 +40,16 @@ enum Errno # Convert an Errno to an error message def message : String - String.new(LibC.strerror(value)) + unsafe_message { |slice| String.new(slice) } end - # Returns the value of libc's errno. + # :nodoc: + def unsafe_message(&) + pointer = LibC.strerror(value) + yield Bytes.new(pointer, LibC.strlen(pointer)) + end + + # returns the value of libc's errno. def self.value : self {% if flag?(:netbsd) || flag?(:openbsd) || flag?(:android) %} Errno.new LibC.__errno.value diff --git a/src/wasi_error.cr b/src/wasi_error.cr index 9a04ed315463..a026de8c7ee2 100644 --- a/src/wasi_error.cr +++ b/src/wasi_error.cr @@ -83,6 +83,11 @@ enum WasiError : UInt16 end end + # :nodoc: + def unsafe_message(&) + yield message.to_slice + end + # Transforms this `WasiError` value to the equivalent `Errno` value. # # This is only defined for some values. If no transformation is defined for diff --git a/src/winerror.cr b/src/winerror.cr index 8da56d7a905e..ab978769d553 100644 --- a/src/winerror.cr +++ b/src/winerror.cr @@ -61,17 +61,21 @@ enum WinError : UInt32 # # On non-win32 platforms the result is always an empty string. def message : String - formatted_message + {% if flag?(:win32) %} + unsafe_message { |slice| String.from_utf16(slice).strip } + {% else %} + "" + {% end %} end # :nodoc: - def formatted_message : String + def unsafe_message(&) {% if flag?(:win32) %} buffer = uninitialized UInt16[256] size = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, value, 0, buffer, buffer.size, nil) - String.from_utf16(buffer.to_slice[0, size]).strip + yield buffer.to_slice[0, size] {% else %} - "" + yield "".to_slice {% end %} end From 7d8a6a0704c1dc1bed8d66737982dab294a5effd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 26 Jun 2024 16:30:39 +0200 Subject: [PATCH 1212/1551] Undocument `IO::Evented` (#14749) --- src/io/evented.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/io/evented.cr b/src/io/evented.cr index 57d71254f24b..ccc040932285 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -2,6 +2,7 @@ require "crystal/thread_local_value" +# :nodoc: module IO::Evented @read_timed_out = false @write_timed_out = false From 6c8542aa608a0cd207b136893faddbd3d5ff7b4e Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Wed, 26 Jun 2024 10:31:51 -0400 Subject: [PATCH 1213/1551] Add UUID v7 (#14732) Implementation of [RFC 9562](https://www.rfc-editor.org/rfc/rfc9562#name-uuid-version-7) Co-authored-by: Julien Portalier --- spec/std/uuid_spec.cr | 17 +++++++++++++++++ src/uuid.cr | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/spec/std/uuid_spec.cr b/spec/std/uuid_spec.cr index 12e497829b16..48cc3351a3c6 100644 --- a/spec/std/uuid_spec.cr +++ b/spec/std/uuid_spec.cr @@ -269,4 +269,21 @@ describe "UUID" do UUID.v5_x500(data).v5?.should eq(true) end end + + describe "v7" do + it "generates a v7 UUID" do + uuid = UUID.v7 + uuid.v7?.should eq true + uuid.variant.rfc9562?.should eq true + end + + pending_wasm32 "generates UUIDs that are sortable with 1ms precision" do + uuids = Array.new(10) do + sleep 1.millisecond + UUID.v7 + end + + uuids.should eq uuids.sort + end + end end diff --git a/src/uuid.cr b/src/uuid.cr index c7aaee0a605c..b1a043785472 100644 --- a/src/uuid.cr +++ b/src/uuid.cr @@ -23,6 +23,8 @@ struct UUID NCS # Reserved for RFC 4122 Specification (default). RFC4122 + # Reserved for RFC 9562 Specification (default for v7). + RFC9562 = RFC4122 # Reserved by Microsoft for backward compatibility. Microsoft # Reserved for future expansion. @@ -43,6 +45,8 @@ struct UUID V4 = 4 # SHA1 hash and namespace. V5 = 5 + # Prefixed with a UNIX timestamp with millisecond precision, filled in with randomness. + V7 = 7 end # A Domain represents a Version 2 domain (DCE security). @@ -80,7 +84,7 @@ struct UUID # do nothing when Variant::NCS @bytes[8] = (@bytes[8] & 0x7f) - when Variant::RFC4122 + when Variant::RFC4122, Variant::RFC9562 @bytes[8] = (@bytes[8] & 0x3f) | 0x80 when Variant::Microsoft @bytes[8] = (@bytes[8] & 0x1f) | 0xc0 @@ -321,6 +325,30 @@ struct UUID end {% end %} + # Generates an RFC9562-compatible v7 UUID, allowing the values to be sorted + # chronologically (with 1ms precision) by their raw or hexstring + # representation. + def self.v7(random r : Random = Random::Secure) + buffer = uninitialized UInt8[18] + value = buffer.to_slice + + # Generate the first 48 bits of the UUID with the current timestamp. We + # allocated enough room for a 64-bit timestamp to accommodate the + # NetworkEndian.encode call here, but we only need 48 bits of it so we chop + # off the first 2 bytes. + IO::ByteFormat::NetworkEndian.encode Time.utc.to_unix_ms, value + value = value[2..] + + # Fill in the rest with random bytes + r.random_bytes(value[6..]) + + # Set the version and variant + value[6] = (value[6] & 0x3F) | 0x70 + value[8] = (value[8] & 0x0F) | 0x80 + + new(value, variant: :rfc9562, version: :v7) + end + # Generates an empty UUID. # # ``` @@ -375,6 +403,7 @@ struct UUID when 3 then Version::V3 when 4 then Version::V4 when 5 then Version::V5 + when 7 then Version::V7 else Version::Unknown end end @@ -442,7 +471,7 @@ struct UUID class Error < Exception end - {% for v in %w(1 2 3 4 5) %} + {% for v in %w(1 2 3 4 5 7) %} # Returns `true` if UUID is a V{{ v.id }}, `false` otherwise. def v{{ v.id }}? variant == Variant::RFC4122 && version == Version::V{{ v.id }} From 1ac328023de8e2175309eaaa2d1194f6cc84ffa6 Mon Sep 17 00:00:00 2001 From: Anton Karankevich <83635660+anton7c3@users.noreply.github.com> Date: Wed, 26 Jun 2024 22:54:50 +0300 Subject: [PATCH 1214/1551] Allow parsing cookies with space in the value (#14455) This is an optional feature enhancement of https://datatracker.ietf.org/doc/html/rfc6265#section-5.2 --- spec/std/http/cookie_spec.cr | 61 +++++++++++++++++++++++++++++++----- src/http/cookie.cr | 22 +++++++++---- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/spec/std/http/cookie_spec.cr b/spec/std/http/cookie_spec.cr index 218bfd9c608e..1a29a3f56754 100644 --- a/spec/std/http/cookie_spec.cr +++ b/spec/std/http/cookie_spec.cr @@ -132,8 +132,8 @@ module HTTP it "raises on invalid value" do cookie = HTTP::Cookie.new("x", "") invalid_values = { - '"', ',', ';', '\\', # invalid printable ascii characters - ' ', '\r', '\t', '\n', # non-printable ascii characters + '"', ',', ';', '\\', # invalid printable ascii characters + '\r', '\t', '\n', # non-printable ascii characters }.map { |c| "foo#{c}bar" } invalid_values.each do |invalid_value| @@ -235,12 +235,6 @@ module HTTP cookie.to_set_cookie_header.should eq("key=value") end - it "parse_set_cookie with space" do - cookie = parse_set_cookie("key=value; path=/test") - parse_set_cookie("key=value;path=/test").should eq cookie - parse_set_cookie("key=value; \t\npath=/test").should eq cookie - end - it "parses key=" do cookie = parse_first_cookie("key=") cookie.name.should eq("key") @@ -285,9 +279,60 @@ module HTTP first.value.should eq("bar") second.value.should eq("baz") end + + it "parses cookie with spaces in value" do + parse_first_cookie(%[key=some value]).value.should eq "some value" + parse_first_cookie(%[key="some value"]).value.should eq "some value" + end + + it "strips spaces around value only when it's unquoted" do + parse_first_cookie(%[key= some value ]).value.should eq "some value" + parse_first_cookie(%[key=" some value "]).value.should eq " some value " + parse_first_cookie(%[key= " some value " ]).value.should eq " some value " + end end describe "parse_set_cookie" do + it "with space" do + cookie = parse_set_cookie("key=value; path=/test") + parse_set_cookie("key=value;path=/test").should eq cookie + parse_set_cookie("key=value; \t\npath=/test").should eq cookie + end + + it "parses cookie with spaces in value" do + parse_set_cookie(%[key=some value]).value.should eq "some value" + parse_set_cookie(%[key="some value"]).value.should eq "some value" + end + + it "removes leading and trailing whitespaces" do + cookie = parse_set_cookie(%[key= \tvalue \t; \t\npath=/test]) + cookie.name.should eq "key" + cookie.value.should eq "value" + cookie.path.should eq "/test" + + cookie = parse_set_cookie(%[ key\t =value \n;path=/test]) + cookie.name.should eq "key" + cookie.value.should eq "value" + cookie.path.should eq "/test" + end + + it "strips spaces around value only when it's unquoted" do + cookie = parse_set_cookie(%[key= value ; \tpath=/test]) + cookie.name.should eq "key" + cookie.value.should eq "value" + cookie.path.should eq "/test" + + cookie = parse_set_cookie(%[key=" value "; \tpath=/test]) + cookie.name.should eq "key" + cookie.value.should eq " value " + cookie.path.should eq "/test" + + cookie = parse_set_cookie(%[key= " value "\t ; \tpath=/test]) + cookie.name.should eq "key" + cookie.value.should eq " value " + cookie.path.should eq "/test" + end + it "parses path" do cookie = parse_set_cookie("key=value; path=/test") cookie.name.should eq("key") diff --git a/src/http/cookie.cr b/src/http/cookie.cr index 83b41297707e..8138249aa830 100644 --- a/src/http/cookie.cr +++ b/src/http/cookie.cr @@ -97,8 +97,8 @@ module HTTP private def validate_value(value) value.each_byte do |byte| # valid characters for cookie-value per https://tools.ietf.org/html/rfc6265#section-4.1.1 - # all printable ASCII characters except ' ', ',', '"', ';' and '\\' - if !byte.in?(0x21...0x7f) || byte.in?(0x22, 0x2c, 0x3b, 0x5c) + # all printable ASCII characters except ',', '"', ';' and '\\' + if !byte.in?(0x20...0x7f) || byte.in?(0x22, 0x2c, 0x3b, 0x5c) raise IO::Error.new("Invalid cookie value") end end @@ -196,9 +196,9 @@ module HTTP module Parser module Regex CookieName = /[^()<>@,;:\\"\/\[\]?={} \t\x00-\x1f\x7f]+/ - CookieOctet = /[!#-+\--:<-\[\]-~]/ + CookieOctet = /[!#-+\--:<-\[\]-~ ]/ CookieValue = /(?:"#{CookieOctet}*"|#{CookieOctet}*)/ - CookiePair = /(?#{CookieName})=(?#{CookieValue})/ + CookiePair = /\s*(?#{CookieName})\s*=\s*(?#{CookieValue})\s*/ DomainLabel = /[A-Za-z0-9\-]+/ DomainIp = /(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/ Time = /(?:\d{2}:\d{2}:\d{2})/ @@ -230,9 +230,11 @@ module HTTP def parse_cookies(header, &) header.scan(CookieString).each do |pair| value = pair["value"] - if value.starts_with?('"') + if value.starts_with?('"') && value.ends_with?('"') # Unwrap quoted cookie value value = value.byte_slice(1, value.bytesize - 2) + else + value = value.strip end yield Cookie.new(pair["name"], value) end @@ -251,8 +253,16 @@ module HTTP expires = parse_time(match["expires"]?) max_age = match["max_age"]?.try(&.to_i64.seconds) + # Unwrap quoted cookie value + cookie_value = match["value"] + if cookie_value.starts_with?('"') && cookie_value.ends_with?('"') + cookie_value = cookie_value.byte_slice(1, cookie_value.bytesize - 2) + else + cookie_value = cookie_value.strip + end + Cookie.new( - match["name"], match["value"], + match["name"], cookie_value, path: match["path"]?, expires: expires, domain: match["domain"]?, From b0fff7ee148f1237fe97049eda4561ec2c6b9636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 26 Jun 2024 21:55:26 +0200 Subject: [PATCH 1215/1551] Fix parser validate UTF-8 on first input byte (#14750) --- spec/compiler/crystal/tools/format_spec.cr | 6 +++--- spec/compiler/lexer/lexer_spec.cr | 9 +++++++++ src/compiler/crystal/syntax/lexer.cr | 7 +++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/spec/compiler/crystal/tools/format_spec.cr b/spec/compiler/crystal/tools/format_spec.cr index bde408afcc7b..c2d74ac45fdb 100644 --- a/spec/compiler/crystal/tools/format_spec.cr +++ b/spec/compiler/crystal/tools/format_spec.cr @@ -57,7 +57,7 @@ describe Crystal::Command::FormatCommand do format_command.run format_command.status_code.should eq(1) stdout.to_s.should be_empty - stderr.to_s.should contain("file 'STDIN' is not a valid Crystal source file: Unexpected byte 0xff at position 1, malformed UTF-8") + stderr.to_s.should contain("file 'STDIN' is not a valid Crystal source file: Unexpected byte 0xfe at position 0, malformed UTF-8") end it "formats stdin (bug)" do @@ -162,7 +162,7 @@ describe Crystal::Command::FormatCommand do format_command.status_code.should eq(1) stdout.to_s.should contain("Format #{Path[".", "format.cr"]}") stderr.to_s.should contain("syntax error in '#{Path[".", "syntax_error.cr"]}:1:3': unexpected token: EOF") - stderr.to_s.should contain("file '#{Path[".", "invalid_byte_sequence_error.cr"]}' is not a valid Crystal source file: Unexpected byte 0xff at position 1, malformed UTF-8") + stderr.to_s.should contain("file '#{Path[".", "invalid_byte_sequence_error.cr"]}' is not a valid Crystal source file: Unexpected byte 0xfe at position 0, malformed UTF-8") File.read(File.join(path, "format.cr")).should eq("if true\n 1\nend\n") end @@ -226,7 +226,7 @@ describe Crystal::Command::FormatCommand do stderr.to_s.should_not contain("not_format.cr") stderr.to_s.should contain("formatting '#{Path[".", "format.cr"]}' produced changes") stderr.to_s.should contain("syntax error in '#{Path[".", "syntax_error.cr"]}:1:3': unexpected token: EOF") - stderr.to_s.should contain("file '#{Path[".", "invalid_byte_sequence_error.cr"]}' is not a valid Crystal source file: Unexpected byte 0xff at position 1, malformed UTF-8") + stderr.to_s.should contain("file '#{Path[".", "invalid_byte_sequence_error.cr"]}' is not a valid Crystal source file: Unexpected byte 0xfe at position 0, malformed UTF-8") end end end diff --git a/spec/compiler/lexer/lexer_spec.cr b/spec/compiler/lexer/lexer_spec.cr index 6045635a603c..6813c1fe8df3 100644 --- a/spec/compiler/lexer/lexer_spec.cr +++ b/spec/compiler/lexer/lexer_spec.cr @@ -657,6 +657,15 @@ describe "Lexer" do assert_syntax_error "'\\u{DFFF}'", "invalid unicode codepoint (surrogate half)" assert_syntax_error ":+1", "unexpected token" + it "invalid byte sequence" do + expect_raises(InvalidByteSequenceError, "Unexpected byte 0xff at position 0, malformed UTF-8") do + parse "\xFF" + end + expect_raises(InvalidByteSequenceError, "Unexpected byte 0xff at position 1, malformed UTF-8") do + parse " \xFF" + end + end + assert_syntax_error "'\\1'", "invalid char escape sequence" it_lexes_string %("\\1"), String.new(Bytes[1]) diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 0f60e555cdac..dbca2448585d 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -59,6 +59,7 @@ module Crystal def initialize(string, string_pool : StringPool? = nil, warnings : WarningCollection? = nil) @warnings = warnings || WarningCollection.new @reader = Char::Reader.new(string) + check_reader_error @token = Token.new @temp_token = Token.new @line_number = 1 @@ -2754,11 +2755,13 @@ module Crystal end def next_char_no_column_increment - char = @reader.next_char + @reader.next_char.tap { check_reader_error } + end + + private def check_reader_error if error = @reader.error ::raise InvalidByteSequenceError.new("Unexpected byte 0x#{error.to_s(16)} at position #{@reader.pos}, malformed UTF-8") end - char end def next_char From 736f04c46f3ca584f856b0234961dcb074947c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 27 Jun 2024 10:18:35 +0200 Subject: [PATCH 1216/1551] Fix regression on `Socket#connect` timeout type restriction (#14755) The `timeout` parameter of `system_connect` has an implict type restriction of `Time::Span?` and we need to convert numeric arguments. This fixes a regression introduced in https://github.com/crystal-lang/crystal/pull/14643. --- src/socket.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/socket.cr b/src/socket.cr index dfad08d762cf..ca484c0140cc 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -110,6 +110,7 @@ class Socket < IO # Tries to connect to a remote address. Yields an `IO::TimeoutError` or an # `Socket::ConnectError` error if the connection failed. def connect(addr, timeout = nil, &) + timeout = timeout.seconds unless timeout.is_a?(::Time::Span?) result = system_connect(addr, timeout) yield result if result.is_a?(Exception) end From 912970ce9444245e2f00a6e0f219ba6293c92023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 27 Jun 2024 10:18:47 +0200 Subject: [PATCH 1217/1551] Drop default timeout for `Socket#connect` on Windows (#14756) IIRC the default timeout of was added when the IOCP event loop did not allow an unlimited timeout. This is possible now, and we can remove the workaround. --- src/crystal/system/win32/socket.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 2a540f4df88d..6a5d44ab5133 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -146,7 +146,7 @@ module Crystal::System::Socket return nil end - operation.wait_for_wsa_result(read_timeout || 1.seconds) do |error| + operation.wait_for_wsa_result(read_timeout) do |error| case error when .wsa_io_incomplete?, .wsaeconnrefused? return ::Socket::ConnectError.from_os_error(method, error) From 5cf6df9f8350957c892788e8ae74ca5475f62a92 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 27 Jun 2024 18:23:41 +0800 Subject: [PATCH 1218/1551] Remove incorrect uses of `describe` (#14757) Fixes a small number of specs which pass both the class name and the method name as separate arguments to `describe`. But it doesn't work like that and the second argument is treated as a file name instead. --- spec/std/benchmark_spec.cr | 126 +++---- spec/std/string_scanner_spec.cr | 568 ++++++++++++++++---------------- 2 files changed, 349 insertions(+), 345 deletions(-) diff --git a/spec/std/benchmark_spec.cr b/spec/std/benchmark_spec.cr index 1bd3738c0f3b..2f3c1fb06fd5 100644 --- a/spec/std/benchmark_spec.cr +++ b/spec/std/benchmark_spec.cr @@ -34,81 +34,83 @@ private def create_entry Benchmark::IPS::Entry.new("label", ->{ 1 + 1 }) end -describe Benchmark::IPS::Entry, "#set_cycles" do - it "sets the number of cycles needed to make 100ms" do - e = create_entry - e.set_cycles(2.seconds, 100) - e.cycles.should eq(5) - - e.set_cycles(100.milliseconds, 1) - e.cycles.should eq(1) - end - - it "sets the cycles to 1 no matter what" do - e = create_entry - e.set_cycles(2.seconds, 1) - e.cycles.should eq(1) - end +private def h_mean(mean) + create_entry.tap { |e| e.mean = mean }.human_mean end -describe Benchmark::IPS::Entry, "#calculate_stats" do - it "correctly calculates basic stats" do - e = create_entry - e.calculate_stats([2, 4, 4, 4, 5, 5, 7, 9]) +private def h_ips(seconds) + mean = 1.0 / seconds + create_entry.tap { |e| e.mean = mean }.human_iteration_time +end - e.size.should eq(8) - e.mean.should eq(5.0) - e.variance.should eq(4.0) - e.stddev.should eq(2.0) +describe Benchmark::IPS::Entry do + describe "#set_cycles" do + it "sets the number of cycles needed to make 100ms" do + e = create_entry + e.set_cycles(2.seconds, 100) + e.cycles.should eq(5) + + e.set_cycles(100.milliseconds, 1) + e.cycles.should eq(1) + end + + it "sets the cycles to 1 no matter what" do + e = create_entry + e.set_cycles(2.seconds, 1) + e.cycles.should eq(1) + end end -end -private def h_mean(mean) - create_entry.tap { |e| e.mean = mean }.human_mean -end + describe "#calculate_stats" do + it "correctly calculates basic stats" do + e = create_entry + e.calculate_stats([2, 4, 4, 4, 5, 5, 7, 9]) -describe Benchmark::IPS::Entry, "#human_mean" do - it { h_mean(0.01234567890123).should eq(" 12.35m") } - it { h_mean(0.12345678901234).should eq("123.46m") } + e.size.should eq(8) + e.mean.should eq(5.0) + e.variance.should eq(4.0) + e.stddev.should eq(2.0) + end + end - it { h_mean(1.23456789012345).should eq(" 1.23 ") } - it { h_mean(12.3456789012345).should eq(" 12.35 ") } - it { h_mean(123.456789012345).should eq("123.46 ") } + describe "#human_mean" do + it { h_mean(0.01234567890123).should eq(" 12.35m") } + it { h_mean(0.12345678901234).should eq("123.46m") } - it { h_mean(1234.56789012345).should eq(" 1.23k") } - it { h_mean(12345.6789012345).should eq(" 12.35k") } - it { h_mean(123456.789012345).should eq("123.46k") } + it { h_mean(1.23456789012345).should eq(" 1.23 ") } + it { h_mean(12.3456789012345).should eq(" 12.35 ") } + it { h_mean(123.456789012345).should eq("123.46 ") } - it { h_mean(1234567.89012345).should eq(" 1.23M") } - it { h_mean(12345678.9012345).should eq(" 12.35M") } - it { h_mean(123456789.012345).should eq("123.46M") } + it { h_mean(1234.56789012345).should eq(" 1.23k") } + it { h_mean(12345.6789012345).should eq(" 12.35k") } + it { h_mean(123456.789012345).should eq("123.46k") } - it { h_mean(1234567890.12345).should eq(" 1.23G") } - it { h_mean(12345678901.2345).should eq(" 12.35G") } - it { h_mean(123456789012.345).should eq("123.46G") } -end + it { h_mean(1234567.89012345).should eq(" 1.23M") } + it { h_mean(12345678.9012345).should eq(" 12.35M") } + it { h_mean(123456789.012345).should eq("123.46M") } -private def h_ips(seconds) - mean = 1.0 / seconds - create_entry.tap { |e| e.mean = mean }.human_iteration_time -end + it { h_mean(1234567890.12345).should eq(" 1.23G") } + it { h_mean(12345678901.2345).should eq(" 12.35G") } + it { h_mean(123456789012.345).should eq("123.46G") } + end -describe Benchmark::IPS::Entry, "#human_iteration_time" do - it { h_ips(1234.567_890_123).should eq("1,234.57s ") } - it { h_ips(123.456_789_012_3).should eq("123.46s ") } - it { h_ips(12.345_678_901_23).should eq(" 12.35s ") } - it { h_ips(1.234_567_890_123).should eq(" 1.23s ") } + describe "#human_iteration_time" do + it { h_ips(1234.567_890_123).should eq("1,234.57s ") } + it { h_ips(123.456_789_012_3).should eq("123.46s ") } + it { h_ips(12.345_678_901_23).should eq(" 12.35s ") } + it { h_ips(1.234_567_890_123).should eq(" 1.23s ") } - it { h_ips(0.123_456_789_012).should eq("123.46ms") } - it { h_ips(0.012_345_678_901).should eq(" 12.35ms") } - it { h_ips(0.001_234_567_890).should eq(" 1.23ms") } + it { h_ips(0.123_456_789_012).should eq("123.46ms") } + it { h_ips(0.012_345_678_901).should eq(" 12.35ms") } + it { h_ips(0.001_234_567_890).should eq(" 1.23ms") } - it { h_ips(0.000_123_456_789).should eq("123.46µs") } - it { h_ips(0.000_012_345_678).should eq(" 12.35µs") } - it { h_ips(0.000_001_234_567).should eq(" 1.23µs") } + it { h_ips(0.000_123_456_789).should eq("123.46µs") } + it { h_ips(0.000_012_345_678).should eq(" 12.35µs") } + it { h_ips(0.000_001_234_567).should eq(" 1.23µs") } - it { h_ips(0.000_000_123_456).should eq("123.46ns") } - it { h_ips(0.000_000_012_345).should eq(" 12.34ns") } - it { h_ips(0.000_000_001_234).should eq(" 1.23ns") } - it { h_ips(0.000_000_000_123).should eq(" 0.12ns") } + it { h_ips(0.000_000_123_456).should eq("123.46ns") } + it { h_ips(0.000_000_012_345).should eq(" 12.34ns") } + it { h_ips(0.000_000_001_234).should eq(" 1.23ns") } + it { h_ips(0.000_000_000_123).should eq(" 0.12ns") } + end end diff --git a/spec/std/string_scanner_spec.cr b/spec/std/string_scanner_spec.cr index 5513e44b7902..18a661b46638 100644 --- a/spec/std/string_scanner_spec.cr +++ b/spec/std/string_scanner_spec.cr @@ -1,356 +1,358 @@ require "spec" require "string_scanner" -describe StringScanner, "#scan" do - it "returns the string matched and advances the offset" do - s = StringScanner.new("this is a string") - s.scan(/\w+/).should eq("this") - s.scan(' ').should eq(" ") - s.scan("is ").should eq("is ") - s.scan(/\w+\s/).should eq("a ") - s.scan(/\w+/).should eq("string") +describe StringScanner do + describe "#scan" do + it "returns the string matched and advances the offset" do + s = StringScanner.new("this is a string") + s.scan(/\w+/).should eq("this") + s.scan(' ').should eq(" ") + s.scan("is ").should eq("is ") + s.scan(/\w+\s/).should eq("a ") + s.scan(/\w+/).should eq("string") + end + + it "returns nil if it can't match from the offset" do + s = StringScanner.new("test string") + s.scan(/\w+/).should_not be_nil # => "test" + s.scan(/\w+/).should be_nil + s.scan('s').should be_nil + s.scan("string").should be_nil + s.scan(/\s\w+/).should_not be_nil # => " string" + s.scan(/.*/).should_not be_nil # => "" + end end - it "returns nil if it can't match from the offset" do - s = StringScanner.new("test string") - s.scan(/\w+/).should_not be_nil # => "test" - s.scan(/\w+/).should be_nil - s.scan('s').should be_nil - s.scan("string").should be_nil - s.scan(/\s\w+/).should_not be_nil # => " string" - s.scan(/.*/).should_not be_nil # => "" + describe "#scan_until" do + it "returns the string matched and advances the offset" do + s = StringScanner.new("test string") + s.scan_until(/t /).should eq("test ") + s.offset.should eq(5) + s.scan_until("tr").should eq("str") + s.offset.should eq(8) + s.scan_until('n').should eq("in") + s.offset.should eq(10) + end + + it "returns nil if it can't match from the offset" do + s = StringScanner.new("test string") + s.offset = 8 + s.scan_until(/tr/).should be_nil + s.scan_until('r').should be_nil + s.scan_until("tr").should be_nil + end end -end - -describe StringScanner, "#scan_until" do - it "returns the string matched and advances the offset" do - s = StringScanner.new("test string") - s.scan_until(/t /).should eq("test ") - s.offset.should eq(5) - s.scan_until("tr").should eq("str") - s.offset.should eq(8) - s.scan_until('n').should eq("in") - s.offset.should eq(10) - end - - it "returns nil if it can't match from the offset" do - s = StringScanner.new("test string") - s.offset = 8 - s.scan_until(/tr/).should be_nil - s.scan_until('r').should be_nil - s.scan_until("tr").should be_nil - end -end -describe StringScanner, "#skip" do - it "advances the offset but does not returns the string matched" do - s = StringScanner.new("this is a string") + describe "#skip" do + it "advances the offset but does not returns the string matched" do + s = StringScanner.new("this is a string") - s.skip(/\w+\s/).should eq(5) - s.offset.should eq(5) - s[0]?.should_not be_nil + s.skip(/\w+\s/).should eq(5) + s.offset.should eq(5) + s[0]?.should_not be_nil - s.skip(/\d+/).should eq(nil) - s.offset.should eq(5) + s.skip(/\d+/).should eq(nil) + s.offset.should eq(5) - s.skip('i').should eq(1) - s.offset.should eq(6) + s.skip('i').should eq(1) + s.offset.should eq(6) - s.skip("s ").should eq(2) - s.offset.should eq(8) + s.skip("s ").should eq(2) + s.offset.should eq(8) - s.skip(/\w+\s/).should eq(2) - s.offset.should eq(10) + s.skip(/\w+\s/).should eq(2) + s.offset.should eq(10) - s.skip(/\w+/).should eq(6) - s.offset.should eq(16) + s.skip(/\w+/).should eq(6) + s.offset.should eq(16) + end end -end - -describe StringScanner, "#skip_until" do - it "advances the offset but does not returns the string matched" do - s = StringScanner.new("this is a string") - s.skip_until(/not/).should eq(nil) - s.offset.should eq(0) - s[0]?.should be_nil + describe "#skip_until" do + it "advances the offset but does not returns the string matched" do + s = StringScanner.new("this is a string") - s.skip_until(/\sis\s/).should eq(8) - s.offset.should eq(8) - s[0]?.should_not be_nil + s.skip_until(/not/).should eq(nil) + s.offset.should eq(0) + s[0]?.should be_nil - s.skip_until("st").should eq(4) - s.offset.should eq(12) - s[0]?.should_not be_nil + s.skip_until(/\sis\s/).should eq(8) + s.offset.should eq(8) + s[0]?.should_not be_nil - s.skip_until("ng").should eq(4) - s.offset.should eq(16) - s[0]?.should_not be_nil - end -end - -describe StringScanner, "#eos" do - it "it is true when the offset is at the end" do - s = StringScanner.new("this is a string") - s.eos?.should eq(false) - s.skip(/(\w+\s?){4}/) - s.eos?.should eq(true) - end -end + s.skip_until("st").should eq(4) + s.offset.should eq(12) + s[0]?.should_not be_nil -describe StringScanner, "#check" do - it "returns the string matched but does not advances the offset" do - s = StringScanner.new("this is a string") - s.offset = 5 - - s.check(/\w+\s/).should eq("is ") - s.offset.should eq(5) - s.check(/\w+\s/).should eq("is ") - s.offset.should eq(5) - s.check('i').should eq("i") - s.offset.should eq(5) - s.check("is ").should eq("is ") - s.offset.should eq(5) + s.skip_until("ng").should eq(4) + s.offset.should eq(16) + s[0]?.should_not be_nil + end end - it "returns nil if it can't match from the offset" do - s = StringScanner.new("test string") - s.check(/\d+/).should be_nil - s.check('0').should be_nil - s.check("01").should be_nil + describe "#eos" do + it "it is true when the offset is at the end" do + s = StringScanner.new("this is a string") + s.eos?.should eq(false) + s.skip(/(\w+\s?){4}/) + s.eos?.should eq(true) + end end -end -describe StringScanner, "#check_until" do - it "returns the string matched and advances the offset" do - s = StringScanner.new("test string") - s.check_until(/tr/).should eq("test str") - s.offset.should eq(0) - s.check_until('r').should eq("test str") - s.offset.should eq(0) - s.check_until("tr").should eq("test str") - s.offset.should eq(0) - s.check_until(/g/).should eq("test string") - s.offset.should eq(0) - s.check_until('g').should eq("test string") - s.offset.should eq(0) - s.check_until("ng").should eq("test string") - s.offset.should eq(0) + describe "#check" do + it "returns the string matched but does not advances the offset" do + s = StringScanner.new("this is a string") + s.offset = 5 + + s.check(/\w+\s/).should eq("is ") + s.offset.should eq(5) + s.check(/\w+\s/).should eq("is ") + s.offset.should eq(5) + s.check('i').should eq("i") + s.offset.should eq(5) + s.check("is ").should eq("is ") + s.offset.should eq(5) + end + + it "returns nil if it can't match from the offset" do + s = StringScanner.new("test string") + s.check(/\d+/).should be_nil + s.check('0').should be_nil + s.check("01").should be_nil + end end - it "returns nil if it can't match from the offset" do - s = StringScanner.new("test string") - s.offset = 8 - s.check_until(/tr/).should be_nil - s.check_until('r').should be_nil - s.check_until("tr").should be_nil + describe "#check_until" do + it "returns the string matched and advances the offset" do + s = StringScanner.new("test string") + s.check_until(/tr/).should eq("test str") + s.offset.should eq(0) + s.check_until('r').should eq("test str") + s.offset.should eq(0) + s.check_until("tr").should eq("test str") + s.offset.should eq(0) + s.check_until(/g/).should eq("test string") + s.offset.should eq(0) + s.check_until('g').should eq("test string") + s.offset.should eq(0) + s.check_until("ng").should eq("test string") + s.offset.should eq(0) + end + + it "returns nil if it can't match from the offset" do + s = StringScanner.new("test string") + s.offset = 8 + s.check_until(/tr/).should be_nil + s.check_until('r').should be_nil + s.check_until("tr").should be_nil + end end -end -describe StringScanner, "#rest" do - it "returns the remainder of the string from the offset" do - s = StringScanner.new("this is a string") - s.rest.should eq("this is a string") + describe "#rest" do + it "returns the remainder of the string from the offset" do + s = StringScanner.new("this is a string") + s.rest.should eq("this is a string") - s.scan(/this is a /) - s.rest.should eq("string") + s.scan(/this is a /) + s.rest.should eq("string") - s.scan(/string/) - s.rest.should eq("") + s.scan(/string/) + s.rest.should eq("") + end end -end -describe StringScanner, "#[]" do - it "allows access to subgroups of the last match" do - s = StringScanner.new("Fri Dec 12 1975 14:39") - regex = /(?\w+) (?\w+) (?\d+)/ - s.scan(regex).should eq("Fri Dec 12") - s[0].should eq("Fri Dec 12") - s[1].should eq("Fri") - s[2].should eq("Dec") - s[3].should eq("12") - s["wday"].should eq("Fri") - s["month"].should eq("Dec") - s["day"].should eq("12") - - s.scan(' ').should eq(" ") - s[0].should eq(" ") - s.scan("1975").should eq("1975") - s[0].should eq("1975") - end + describe "#[]" do + it "allows access to subgroups of the last match" do + s = StringScanner.new("Fri Dec 12 1975 14:39") + regex = /(?\w+) (?\w+) (?\d+)/ + s.scan(regex).should eq("Fri Dec 12") + s[0].should eq("Fri Dec 12") + s[1].should eq("Fri") + s[2].should eq("Dec") + s[3].should eq("12") + s["wday"].should eq("Fri") + s["month"].should eq("Dec") + s["day"].should eq("12") - it "raises when there is no last match" do - s = StringScanner.new("Fri Dec 12 1975 14:39") + s.scan(' ').should eq(" ") + s[0].should eq(" ") + s.scan("1975").should eq("1975") + s[0].should eq("1975") + end - s.scan(/this is not there/) - expect_raises(Exception, "Nil assertion failed") { s[0] } + it "raises when there is no last match" do + s = StringScanner.new("Fri Dec 12 1975 14:39") - s.scan('t') - expect_raises(Exception, "Nil assertion failed") { s[0] } + s.scan(/this is not there/) + expect_raises(Exception, "Nil assertion failed") { s[0] } - s.scan("this is not there") - expect_raises(Exception, "Nil assertion failed") { s[0] } - end + s.scan('t') + expect_raises(Exception, "Nil assertion failed") { s[0] } - it "raises when there is no subgroup" do - s = StringScanner.new("Fri Dec 12 1975 14:39") - regex = /(?\w+) (?\w+) (?\d+)/ + s.scan("this is not there") + expect_raises(Exception, "Nil assertion failed") { s[0] } + end - s.scan(regex) + it "raises when there is no subgroup" do + s = StringScanner.new("Fri Dec 12 1975 14:39") + regex = /(?\w+) (?\w+) (?\d+)/ - s[0].should_not be_nil - expect_raises(IndexError) { s[5] } - expect_raises(KeyError, "Capture group 'something' does not exist") { s["something"] } + s.scan(regex) - s.scan(' ') + s[0].should_not be_nil + expect_raises(IndexError) { s[5] } + expect_raises(KeyError, "Capture group 'something' does not exist") { s["something"] } - s[0].should_not be_nil - expect_raises(IndexError) { s[1] } - expect_raises(KeyError, "Capture group 'something' does not exist") { s["something"] } + s.scan(' ') - s.scan("1975") + s[0].should_not be_nil + expect_raises(IndexError) { s[1] } + expect_raises(KeyError, "Capture group 'something' does not exist") { s["something"] } - s[0].should_not be_nil - expect_raises(IndexError) { s[1] } - expect_raises(KeyError, "Capture group 'something' does not exist") { s["something"] } - end -end + s.scan("1975") -describe StringScanner, "#[]?" do - it "allows access to subgroups of the last match" do - s = StringScanner.new("Fri Dec 12 1975 14:39") - result = s.scan(/(?\w+) (?\w+) (?\d+)/) - - result.should eq("Fri Dec 12") - s[0]?.should eq("Fri Dec 12") - s[1]?.should eq("Fri") - s[2]?.should eq("Dec") - s[3]?.should eq("12") - s["wday"]?.should eq("Fri") - s["month"]?.should eq("Dec") - s["day"]?.should eq("12") - - s.scan(' ').should eq(" ") - s[0]?.should eq(" ") - s.scan("1975").should eq("1975") - s[0]?.should eq("1975") + s[0].should_not be_nil + expect_raises(IndexError) { s[1] } + expect_raises(KeyError, "Capture group 'something' does not exist") { s["something"] } + end end - it "returns nil when there is no last match" do - s = StringScanner.new("Fri Dec 12 1975 14:39") - s.scan(/this is not there/) + describe "#[]?" do + it "allows access to subgroups of the last match" do + s = StringScanner.new("Fri Dec 12 1975 14:39") + result = s.scan(/(?\w+) (?\w+) (?\d+)/) - s[0]?.should be_nil + result.should eq("Fri Dec 12") + s[0]?.should eq("Fri Dec 12") + s[1]?.should eq("Fri") + s[2]?.should eq("Dec") + s[3]?.should eq("12") + s["wday"]?.should eq("Fri") + s["month"]?.should eq("Dec") + s["day"]?.should eq("12") - s.scan('t') - s[0]?.should be_nil + s.scan(' ').should eq(" ") + s[0]?.should eq(" ") + s.scan("1975").should eq("1975") + s[0]?.should eq("1975") + end - s.scan("this is not there") - s[0]?.should be_nil - end + it "returns nil when there is no last match" do + s = StringScanner.new("Fri Dec 12 1975 14:39") + s.scan(/this is not there/) - it "raises when there is no subgroup" do - s = StringScanner.new("Fri Dec 12 1975 14:39") + s[0]?.should be_nil - s.scan(/(?\w+) (?\w+) (?\d+)/) + s.scan('t') + s[0]?.should be_nil - s[0]?.should_not be_nil - s[5]?.should be_nil - s["something"]?.should be_nil + s.scan("this is not there") + s[0]?.should be_nil + end - s.scan(' ') + it "raises when there is no subgroup" do + s = StringScanner.new("Fri Dec 12 1975 14:39") - s[0]?.should_not be_nil - s[1]?.should be_nil - s["something"]?.should be_nil + s.scan(/(?\w+) (?\w+) (?\d+)/) - s.scan("1975") + s[0]?.should_not be_nil + s[5]?.should be_nil + s["something"]?.should be_nil - s[0]?.should_not be_nil - s[1]?.should be_nil - s["something"]?.should be_nil - end -end + s.scan(' ') -describe StringScanner, "#string" do - it { StringScanner.new("foo").string.should eq("foo") } -end + s[0]?.should_not be_nil + s[1]?.should be_nil + s["something"]?.should be_nil -describe StringScanner, "#offset" do - it "returns the current position" do - s = StringScanner.new("this is a string") - s.offset.should eq(0) - s.scan(/\w+/) - s.offset.should eq(4) - end -end + s.scan("1975") -describe StringScanner, "#offset=" do - it "sets the current position" do - s = StringScanner.new("this is a string") - s.offset = 5 - s.scan(/\w+/).should eq("is") + s[0]?.should_not be_nil + s[1]?.should be_nil + s["something"]?.should be_nil + end end - it "raises on negative positions" do - s = StringScanner.new("this is a string") - expect_raises(IndexError) { s.offset = -2 } + describe "#string" do + it { StringScanner.new("foo").string.should eq("foo") } end -end -describe StringScanner, "#inspect" do - it "has information on the scanner" do - s = StringScanner.new("this is a string") - s.inspect.should eq(%(#)) - s.scan(/\w+\s/) - s.inspect.should eq(%(#)) - s.scan(/\w+\s/) - s.inspect.should eq(%(#)) - s.scan(/\w+\s\w+/) - s.inspect.should eq(%(#)) + describe "#offset" do + it "returns the current position" do + s = StringScanner.new("this is a string") + s.offset.should eq(0) + s.scan(/\w+/) + s.offset.should eq(4) + end end - it "works with small strings" do - s = StringScanner.new("hi") - s.inspect.should eq(%(#)) - s.scan(/\w\w/) - s.inspect.should eq(%(#)) + describe "#offset=" do + it "sets the current position" do + s = StringScanner.new("this is a string") + s.offset = 5 + s.scan(/\w+/).should eq("is") + end + + it "raises on negative positions" do + s = StringScanner.new("this is a string") + expect_raises(IndexError) { s.offset = -2 } + end end -end -describe StringScanner, "#peek" do - it "shows the next len characters without advancing the offset" do - s = StringScanner.new("this is a string") - s.offset.should eq(0) - s.peek(4).should eq("this") - s.offset.should eq(0) - s.peek(7).should eq("this is") - s.offset.should eq(0) + describe "#inspect" do + it "has information on the scanner" do + s = StringScanner.new("this is a string") + s.inspect.should eq(%(#)) + s.scan(/\w+\s/) + s.inspect.should eq(%(#)) + s.scan(/\w+\s/) + s.inspect.should eq(%(#)) + s.scan(/\w+\s\w+/) + s.inspect.should eq(%(#)) + end + + it "works with small strings" do + s = StringScanner.new("hi") + s.inspect.should eq(%(#)) + s.scan(/\w\w/) + s.inspect.should eq(%(#)) + end end -end - -describe StringScanner, "#reset" do - it "resets the scan offset to the beginning and clears the last match" do - s = StringScanner.new("this is a string") - s.scan_until(/str/) - s[0]?.should_not be_nil - s.offset.should_not eq(0) - s.reset - s[0]?.should be_nil - s.offset.should eq(0) + describe "#peek" do + it "shows the next len characters without advancing the offset" do + s = StringScanner.new("this is a string") + s.offset.should eq(0) + s.peek(4).should eq("this") + s.offset.should eq(0) + s.peek(7).should eq("this is") + s.offset.should eq(0) + end end -end -describe StringScanner, "#terminate" do - it "moves the scan offset to the end of the string and clears the last match" do - s = StringScanner.new("this is a string") - s.scan_until(/str/) - s[0]?.should_not be_nil - s.eos?.should eq(false) + describe "#reset" do + it "resets the scan offset to the beginning and clears the last match" do + s = StringScanner.new("this is a string") + s.scan_until(/str/) + s[0]?.should_not be_nil + s.offset.should_not eq(0) + + s.reset + s[0]?.should be_nil + s.offset.should eq(0) + end + end - s.terminate - s[0]?.should be_nil - s.eos?.should eq(true) + describe "#terminate" do + it "moves the scan offset to the end of the string and clears the last match" do + s = StringScanner.new("this is a string") + s.scan_until(/str/) + s[0]?.should_not be_nil + s.eos?.should eq(false) + + s.terminate + s[0]?.should be_nil + s.eos?.should eq(true) + end end end From 70ed2d0f3a80ddbdff0eb4cdb4de9f98998c6622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 1 Jul 2024 12:02:29 +0200 Subject: [PATCH 1219/1551] Fix `GC.malloc` for `gc_none` to clear memory (#14746) `GC.malloc` is supposed to clear the memory, but libc `malloc` does not do this. So we need to add it explicitly. --- src/gc/none.cr | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gc/none.cr b/src/gc/none.cr index 1121caef1bf4..640e6e8f927d 100644 --- a/src/gc/none.cr +++ b/src/gc/none.cr @@ -10,7 +10,9 @@ module GC # :nodoc: def self.malloc(size : LibC::SizeT) : Void* Crystal.trace :gc, "malloc", size: size - LibC.malloc(size) + # libc malloc is not guaranteed to return cleared memory, so we need to + # explicitly clear it. Ref: https://github.com/crystal-lang/crystal/issues/14678 + LibC.malloc(size).tap(&.clear) end # :nodoc: From 057771fdd235b2f3b7d8ea9e22673a5c683bab94 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 1 Jul 2024 12:03:14 +0200 Subject: [PATCH 1220/1551] Prefer `strerror_r` over `strerror` for thread-safe errno (#14764) Prefer the thread safe `strerror_r` C call over the thread unsafe `strerror`. --- src/errno.cr | 13 +++++++++++-- src/lib_c/aarch64-android/c/string.cr | 1 + src/lib_c/aarch64-darwin/c/string.cr | 1 + src/lib_c/aarch64-linux-gnu/c/string.cr | 1 + src/lib_c/aarch64-linux-musl/c/string.cr | 1 + src/lib_c/arm-linux-gnueabihf/c/string.cr | 1 + src/lib_c/i386-linux-gnu/c/string.cr | 1 + src/lib_c/i386-linux-musl/c/string.cr | 1 + src/lib_c/wasm32-wasi/c/string.cr | 1 + src/lib_c/x86_64-darwin/c/string.cr | 1 + src/lib_c/x86_64-dragonfly/c/string.cr | 1 + src/lib_c/x86_64-freebsd/c/string.cr | 1 + src/lib_c/x86_64-linux-gnu/c/string.cr | 1 + src/lib_c/x86_64-linux-musl/c/string.cr | 1 + src/lib_c/x86_64-netbsd/c/string.cr | 1 + src/lib_c/x86_64-openbsd/c/string.cr | 1 + src/lib_c/x86_64-solaris/c/string.cr | 1 + 17 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/errno.cr b/src/errno.cr index 2a68371f4a19..9d608c80bc1b 100644 --- a/src/errno.cr +++ b/src/errno.cr @@ -45,8 +45,17 @@ enum Errno # :nodoc: def unsafe_message(&) - pointer = LibC.strerror(value) - yield Bytes.new(pointer, LibC.strlen(pointer)) + {% if LibC.has_method?(:strerror_r) %} + buffer = uninitialized UInt8[256] + if LibC.strerror_r(value, buffer, buffer.size) == 0 + yield Bytes.new(buffer.to_unsafe, LibC.strlen(buffer)) + else + yield "(???)".to_slice + end + {% else %} + pointer = LibC.strerror(value) + yield Bytes.new(pointer, LibC.strlen(pointer)) + {% end %} end # returns the value of libc's errno. diff --git a/src/lib_c/aarch64-android/c/string.cr b/src/lib_c/aarch64-android/c/string.cr index 5133435e13dc..583a40e7c7f1 100644 --- a/src/lib_c/aarch64-android/c/string.cr +++ b/src/lib_c/aarch64-android/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(__lhs : Void*, __rhs : Void*, __n : SizeT) : Int fun strcmp(__lhs : Char*, __rhs : Char*) : Int fun strerror(__errno_value : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(__s : Char*) : SizeT end diff --git a/src/lib_c/aarch64-darwin/c/string.cr b/src/lib_c/aarch64-darwin/c/string.cr index 02e025ae4880..b9657fc871f7 100644 --- a/src/lib_c/aarch64-darwin/c/string.cr +++ b/src/lib_c/aarch64-darwin/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int fun strcmp(x0 : Char*, x1 : Char*) : Int fun strerror(x0 : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(x0 : Char*) : SizeT end diff --git a/src/lib_c/aarch64-linux-gnu/c/string.cr b/src/lib_c/aarch64-linux-gnu/c/string.cr index c583804acd98..0d012a54002b 100644 --- a/src/lib_c/aarch64-linux-gnu/c/string.cr +++ b/src/lib_c/aarch64-linux-gnu/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(s1 : Void*, s2 : Void*, n : SizeT) : Int fun strcmp(s1 : Char*, s2 : Char*) : Int fun strerror(errnum : Int) : Char* + fun strerror_r = __xpg_strerror_r(Int, Char*, SizeT) : Int fun strlen(s : Char*) : SizeT end diff --git a/src/lib_c/aarch64-linux-musl/c/string.cr b/src/lib_c/aarch64-linux-musl/c/string.cr index 02e025ae4880..b9657fc871f7 100644 --- a/src/lib_c/aarch64-linux-musl/c/string.cr +++ b/src/lib_c/aarch64-linux-musl/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int fun strcmp(x0 : Char*, x1 : Char*) : Int fun strerror(x0 : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(x0 : Char*) : SizeT end diff --git a/src/lib_c/arm-linux-gnueabihf/c/string.cr b/src/lib_c/arm-linux-gnueabihf/c/string.cr index c583804acd98..0d012a54002b 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/string.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(s1 : Void*, s2 : Void*, n : SizeT) : Int fun strcmp(s1 : Char*, s2 : Char*) : Int fun strerror(errnum : Int) : Char* + fun strerror_r = __xpg_strerror_r(Int, Char*, SizeT) : Int fun strlen(s : Char*) : SizeT end diff --git a/src/lib_c/i386-linux-gnu/c/string.cr b/src/lib_c/i386-linux-gnu/c/string.cr index c583804acd98..0d012a54002b 100644 --- a/src/lib_c/i386-linux-gnu/c/string.cr +++ b/src/lib_c/i386-linux-gnu/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(s1 : Void*, s2 : Void*, n : SizeT) : Int fun strcmp(s1 : Char*, s2 : Char*) : Int fun strerror(errnum : Int) : Char* + fun strerror_r = __xpg_strerror_r(Int, Char*, SizeT) : Int fun strlen(s : Char*) : SizeT end diff --git a/src/lib_c/i386-linux-musl/c/string.cr b/src/lib_c/i386-linux-musl/c/string.cr index 02e025ae4880..b9657fc871f7 100644 --- a/src/lib_c/i386-linux-musl/c/string.cr +++ b/src/lib_c/i386-linux-musl/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int fun strcmp(x0 : Char*, x1 : Char*) : Int fun strerror(x0 : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(x0 : Char*) : SizeT end diff --git a/src/lib_c/wasm32-wasi/c/string.cr b/src/lib_c/wasm32-wasi/c/string.cr index 5be77e03cf1c..e12128de9659 100644 --- a/src/lib_c/wasm32-wasi/c/string.cr +++ b/src/lib_c/wasm32-wasi/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int fun strcmp(x0 : Char*, x1 : Char*) : Int fun strerror(x0 : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(x0 : Char*) : ULong end diff --git a/src/lib_c/x86_64-darwin/c/string.cr b/src/lib_c/x86_64-darwin/c/string.cr index 02e025ae4880..b9657fc871f7 100644 --- a/src/lib_c/x86_64-darwin/c/string.cr +++ b/src/lib_c/x86_64-darwin/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int fun strcmp(x0 : Char*, x1 : Char*) : Int fun strerror(x0 : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(x0 : Char*) : SizeT end diff --git a/src/lib_c/x86_64-dragonfly/c/string.cr b/src/lib_c/x86_64-dragonfly/c/string.cr index 02e025ae4880..b9657fc871f7 100644 --- a/src/lib_c/x86_64-dragonfly/c/string.cr +++ b/src/lib_c/x86_64-dragonfly/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int fun strcmp(x0 : Char*, x1 : Char*) : Int fun strerror(x0 : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(x0 : Char*) : SizeT end diff --git a/src/lib_c/x86_64-freebsd/c/string.cr b/src/lib_c/x86_64-freebsd/c/string.cr index 02e025ae4880..b9657fc871f7 100644 --- a/src/lib_c/x86_64-freebsd/c/string.cr +++ b/src/lib_c/x86_64-freebsd/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int fun strcmp(x0 : Char*, x1 : Char*) : Int fun strerror(x0 : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(x0 : Char*) : SizeT end diff --git a/src/lib_c/x86_64-linux-gnu/c/string.cr b/src/lib_c/x86_64-linux-gnu/c/string.cr index c583804acd98..0d012a54002b 100644 --- a/src/lib_c/x86_64-linux-gnu/c/string.cr +++ b/src/lib_c/x86_64-linux-gnu/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(s1 : Void*, s2 : Void*, n : SizeT) : Int fun strcmp(s1 : Char*, s2 : Char*) : Int fun strerror(errnum : Int) : Char* + fun strerror_r = __xpg_strerror_r(Int, Char*, SizeT) : Int fun strlen(s : Char*) : SizeT end diff --git a/src/lib_c/x86_64-linux-musl/c/string.cr b/src/lib_c/x86_64-linux-musl/c/string.cr index 02e025ae4880..b9657fc871f7 100644 --- a/src/lib_c/x86_64-linux-musl/c/string.cr +++ b/src/lib_c/x86_64-linux-musl/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int fun strcmp(x0 : Char*, x1 : Char*) : Int fun strerror(x0 : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(x0 : Char*) : SizeT end diff --git a/src/lib_c/x86_64-netbsd/c/string.cr b/src/lib_c/x86_64-netbsd/c/string.cr index 471d1ed82b36..ff94ee456646 100644 --- a/src/lib_c/x86_64-netbsd/c/string.cr +++ b/src/lib_c/x86_64-netbsd/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int fun strcmp(x0 : Char*, x1 : Char*) : Int fun strerror(x0 : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(x0 : Char*) : ULong end diff --git a/src/lib_c/x86_64-openbsd/c/string.cr b/src/lib_c/x86_64-openbsd/c/string.cr index 471d1ed82b36..ff94ee456646 100644 --- a/src/lib_c/x86_64-openbsd/c/string.cr +++ b/src/lib_c/x86_64-openbsd/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int fun strcmp(x0 : Char*, x1 : Char*) : Int fun strerror(x0 : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(x0 : Char*) : ULong end diff --git a/src/lib_c/x86_64-solaris/c/string.cr b/src/lib_c/x86_64-solaris/c/string.cr index 02e025ae4880..b9657fc871f7 100644 --- a/src/lib_c/x86_64-solaris/c/string.cr +++ b/src/lib_c/x86_64-solaris/c/string.cr @@ -5,5 +5,6 @@ lib LibC fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int fun strcmp(x0 : Char*, x1 : Char*) : Int fun strerror(x0 : Int) : Char* + fun strerror_r(Int, Char*, SizeT) : Int fun strlen(x0 : Char*) : SizeT end From e279b3c7f90a43ff654694d9f7726a244aeda988 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 1 Jul 2024 12:04:06 +0200 Subject: [PATCH 1221/1551] Detect and error on failed codegen process (#14762) Report an exception when it occurs in a codegen forked process, otherwise detects when a codegen process terminated early (which is what happens on LLVM error). In both cases a BUG message is printed on stderr and the main process exits. --- src/compiler/crystal/compiler.cr | 34 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index eec190e85eeb..1d540e02f2e9 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -533,6 +533,9 @@ module Crystal result = {name: unit.name, reused: unit.reused_previous_compilation?} output.puts result.to_json end + rescue ex + result = {exception: {name: ex.class.name, message: ex.message, backtrace: ex.backtrace}} + output.puts result.to_json end overqueue = 1 @@ -554,13 +557,21 @@ module Crystal while (index = indexes.add(1)) < units.size input.puts index - response = output.gets(chomp: true).not_nil! - channel.send response + if response = output.gets(chomp: true) + channel.send response + else + Crystal::System.print_error "\nBUG: a codegen process failed\n" + exit 1 + end end overqueued.times do - response = output.gets(chomp: true).not_nil! - channel.send response + if response = output.gets(chomp: true) + channel.send response + else + Crystal::System.print_error "\nBUG: a codegen process failed\n" + exit 1 + end end input << '\n' @@ -578,11 +589,18 @@ module Crystal end while response = channel.receive? - next unless wants_stats_or_progress - result = JSON.parse(response) - all_reused << result["name"].as_s if result["reused"].as_bool - @progress_tracker.stage_progress += 1 + + if ex = result["exception"]? + Crystal::System.print_error "\nBUG: a codegen process failed: %s (%s)\n", ex["message"].as_s, ex["name"].as_s + ex["backtrace"].as_a?.try(&.each { |frame| Crystal::System.print_error " from %s\n", frame }) + exit 1 + end + + if wants_stats_or_progress + all_reused << result["name"].as_s if result["reused"].as_bool + @progress_tracker.stage_progress += 1 + end end all_reused From 5b500882bb51cff0f38a51735bf008923cde2219 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 1 Jul 2024 17:31:15 +0200 Subject: [PATCH 1222/1551] Fix: don't hardcode alpn protocol byte size (openssl) (#14769) For some reason OpenSSL used to negotiate the protocol by itself, without invoking the select callback, or maybe it didn't respect the total bytesize when processing the alpn string. That changed in the 3.0.14 and other bugfix releases of OpenSSL, which exposed the bug. --- src/openssl/ssl/context.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr index c7d5b5a0de2a..38e0054cba17 100644 --- a/src/openssl/ssl/context.cr +++ b/src/openssl/ssl/context.cr @@ -178,7 +178,7 @@ abstract class OpenSSL::SSL::Context {% if LibSSL.has_method?(:ssl_ctx_set_alpn_select_cb) %} alpn_cb = ->(ssl : LibSSL::SSL, o : LibC::Char**, olen : LibC::Char*, i : LibC::Char*, ilen : LibC::Int, data : Void*) { proto = Box(Bytes).unbox(data) - ret = LibSSL.ssl_select_next_proto(o, olen, proto, 2, i, ilen) + ret = LibSSL.ssl_select_next_proto(o, olen, proto, proto.size, i, ilen) if ret != LibSSL::OPENSSL_NPN_NEGOTIATED LibSSL::SSL_TLSEXT_ERR_NOACK else From 4d9a7e8410f0db731c2b39a62241c10e8f34a007 Mon Sep 17 00:00:00 2001 From: Hugo Parente Lima Date: Mon, 1 Jul 2024 19:24:46 -0300 Subject: [PATCH 1223/1551] =?UTF-8?q?Fix=20exa=C3=B6.e=20`WeakRef`=20examp?= =?UTF-8?q?le=20by=20removing=20ref.value=20call.=20(#10846)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `ref.value` call cause the GC to not collect `ref` until end of scope. Co-authored-by: Beta Ziliani --- src/weak_ref.cr | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/weak_ref.cr b/src/weak_ref.cr index b5f7468383d0..518f51ff772d 100644 --- a/src/weak_ref.cr +++ b/src/weak_ref.cr @@ -7,10 +7,14 @@ # require "weak_ref" # # ref = WeakRef.new("oof".reverse) -# p ref.value # => "foo" +# p ref # => # # GC.collect +# p ref # => # # p ref.value # => nil # ``` +# +# Note that the collection of objects is not deterministic, and depends on many subtle aspects. For instance, +# if the example above is modified to print `ref.value` in the first print, then the collector will not collect it. class WeakRef(T) @target : Void* From 53c4991c9afbca84086c48be3d63fff59df04d37 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 2 Jul 2024 00:25:40 +0200 Subject: [PATCH 1224/1551] Codegen: stats and progress issues (#14763) The `--stats` and `--progress` params had a couple issues: - codegen progress isn't updated when `--threads=1` (always the case on Windows); - only stats need to collect reused modules (progress doesn't). --- src/compiler/crystal/compiler.cr | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 1d540e02f2e9..b30b184e1023 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -505,8 +505,6 @@ module Crystal private def codegen_many_units(program, units, target_triple) all_reused = [] of String - wants_stats_or_progress = @progress_tracker.stats? || @progress_tracker.progress? - # Don't start more processes than compilation units n_threads = @n_threads.clamp(1..units.size) @@ -516,7 +514,12 @@ module Crystal if n_threads == 1 units.each do |unit| unit.compile - all_reused << unit.name if wants_stats_or_progress && unit.reused_previous_compilation? + @progress_tracker.stage_progress += 1 + end + if @progress_tracker.stats? + units.each do |unit| + all_reused << unit.name && unit.reused_previous_compilation? + end end return all_reused end @@ -597,10 +600,10 @@ module Crystal exit 1 end - if wants_stats_or_progress + if @progress_tracker.stats? all_reused << result["name"].as_s if result["reused"].as_bool - @progress_tracker.stage_progress += 1 end + @progress_tracker.stage_progress += 1 end all_reused From be46ba200c049772a625fb5c719ff7f9b198c0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 3 Jul 2024 13:34:42 +0200 Subject: [PATCH 1225/1551] Fix `IO::Delimited` reading into limited slice with peek (#14772) --- spec/std/io/delimited_spec.cr | 16 ++++++++++++++++ src/io/delimited.cr | 4 +++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/spec/std/io/delimited_spec.cr b/spec/std/io/delimited_spec.cr index 63096322237d..b41af9ee5fdb 100644 --- a/spec/std/io/delimited_spec.cr +++ b/spec/std/io/delimited_spec.cr @@ -259,6 +259,22 @@ describe "IO::Delimited" do io.gets_to_end.should eq("hello") end + it "handles the case of peek matching first byte, not having enough room, but later not matching (limted slice)" do + # not a delimiter + # --- + io = MemoryIOWithFixedPeek.new("abcdefgwijkfghhello") + # ------- --- + # peek delimiter + io.peek_size = 7 + delimited = IO::Delimited.new(io, read_delimiter: "fgh") + + delimited.peek.should eq("abcde".to_slice) + delimited.read_string(6).should eq "abcdef" + delimited.read_string(5).should eq("gwijk") + delimited.gets_to_end.should eq("") + io.gets_to_end.should eq("hello") + end + it "handles the case of peek matching first byte, not having enough room, later only partially matching" do # delimiter # ------------ diff --git a/src/io/delimited.cr b/src/io/delimited.cr index b0e235881499..4da7074b52bb 100644 --- a/src/io/delimited.cr +++ b/src/io/delimited.cr @@ -111,11 +111,13 @@ class IO::Delimited < IO next_index = @active_delimiter_buffer.index(first_byte, 1) # We read up to that new match, if any, or the entire buffer - read_bytes = next_index || @active_delimiter_buffer.size + read_bytes = Math.min(next_index || @active_delimiter_buffer.size, slice.size) slice.copy_from(@active_delimiter_buffer[0, read_bytes]) slice += read_bytes @active_delimiter_buffer += read_bytes + + return read_bytes if slice.empty? return read_bytes + read_internal(slice) end end From cdf54629e65e77489076b277c52ed78785dde588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 4 Jul 2024 11:09:58 +0200 Subject: [PATCH 1226/1551] [CI] Update to Ruby 3 in macOS circleCI runner (#14777) --- .circleci/config.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cf6d612d61b0..0c1227013673 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -296,12 +296,7 @@ jobs: command: | brew unlink python@2 || true - # We need ruby-install >= 0.8.3 - brew install ruby-install - - ruby-install ruby 2.7.3 - - brew install pkgconfig libtool + brew install ruby@3 libffi pkgconfig libtool sudo mkdir -p /opt/crystal sudo chown $(whoami) /opt/crystal/ @@ -312,7 +307,6 @@ jobs: - run: no_output_timeout: 40m command: | - echo "2.7.3" > /tmp/workspace/distribution-scripts/.ruby-version cd /tmp/workspace/distribution-scripts source build.env cd omnibus From b3412d2d40275067f5567e402583933911d3a2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 4 Jul 2024 14:24:23 +0200 Subject: [PATCH 1227/1551] Update distribution-scripts (#14776) Updates `distribution-scripts` dependency to https://github.com/crystal-lang/distribution-scripts/commit/96e431e170979125018bd4fd90111a3147477eec This includes the following changes: * crystal-lang/distribution-scripts#320 * crystal-lang/distribution-scripts#319 * Install automake --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c1227013673..190695224419 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "fe82a34ad7855ddb432a26ef7e48c46e7b440e49" + default: "96e431e170979125018bd4fd90111a3147477eec" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string @@ -296,7 +296,7 @@ jobs: command: | brew unlink python@2 || true - brew install ruby@3 libffi pkgconfig libtool + brew install ruby@3 libffi pkgconfig libtool automake sudo mkdir -p /opt/crystal sudo chown $(whoami) /opt/crystal/ From 5ab6e4b4675ed5e13a7533a839800295044c4808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 4 Jul 2024 20:25:28 +0200 Subject: [PATCH 1228/1551] Fix changelog generator increase topic priority for `infrastructure` (#14781) --- scripts/github-changelog.cr | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index 26fce69fd145..f7ae12e74dad 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -222,9 +222,10 @@ record PullRequest, topics.sort_by! { |parts| topic_priority = case parts[0] - when "tools" then 2 - when "lang" then 1 - else 0 + when "infrastructure" then 3 + when "tools" then 2 + when "lang" then 1 + else 0 end {-topic_priority, parts[0]} } From 074ec9992b99234050200b1cfcc524366ec59e36 Mon Sep 17 00:00:00 2001 From: Damir Sharipov Date: Fri, 5 Jul 2024 14:10:05 +0300 Subject: [PATCH 1229/1551] Fix JSON discriminator for Bool `false` value (#14779) --- spec/std/json/serializable_spec.cr | 13 ++++++++++--- src/json/serialization.cr | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index 042e42ff5fd5..ca74c6e73e3e 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -419,7 +419,8 @@ class JSONVariableDiscriminatorValueType use_json_discriminator "type", { 0 => JSONVariableDiscriminatorNumber, "1" => JSONVariableDiscriminatorString, - true => JSONVariableDiscriminatorBool, + true => JSONVariableDiscriminatorBoolTrue, + false => JSONVariableDiscriminatorBoolFalse, JSONVariableDiscriminatorEnumFoo::Foo => JSONVariableDiscriminatorEnum, JSONVariableDiscriminatorEnumFoo8::Foo => JSONVariableDiscriminatorEnum8, } @@ -431,7 +432,10 @@ end class JSONVariableDiscriminatorString < JSONVariableDiscriminatorValueType end -class JSONVariableDiscriminatorBool < JSONVariableDiscriminatorValueType +class JSONVariableDiscriminatorBoolTrue < JSONVariableDiscriminatorValueType +end + +class JSONVariableDiscriminatorBoolFalse < JSONVariableDiscriminatorValueType end class JSONVariableDiscriminatorEnum < JSONVariableDiscriminatorValueType @@ -1130,7 +1134,10 @@ describe "JSON mapping" do object_string.should be_a(JSONVariableDiscriminatorString) object_bool = JSONVariableDiscriminatorValueType.from_json(%({"type": true})) - object_bool.should be_a(JSONVariableDiscriminatorBool) + object_bool.should be_a(JSONVariableDiscriminatorBoolTrue) + + object_bool = JSONVariableDiscriminatorValueType.from_json(%({"type": false})) + object_bool.should be_a(JSONVariableDiscriminatorBoolFalse) object_enum = JSONVariableDiscriminatorValueType.from_json(%({"type": 4})) object_enum.should be_a(JSONVariableDiscriminatorEnum) diff --git a/src/json/serialization.cr b/src/json/serialization.cr index 610979517a18..b1eb86d15082 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -448,7 +448,7 @@ module JSON end end - unless discriminator_value + if discriminator_value.nil? raise ::JSON::SerializableError.new("Missing JSON discriminator field '{{field.id}}'", to_s, nil, *location, nil) end From 45c9e6f5a337e0991071d8953def6e2c8a519e19 Mon Sep 17 00:00:00 2001 From: kojix2 <2xijok@gmail.com> Date: Sat, 6 Jul 2024 20:30:04 +0900 Subject: [PATCH 1230/1551] Fix `Compress::Gzip` extra field (#14550) --- spec/std/compress/gzip/gzip_spec.cr | 16 +++++++++++++++- spec/std/data/test.gz | Bin 0 -> 60 bytes src/compress/gzip/header.cr | 4 ++-- 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 spec/std/data/test.gz diff --git a/spec/std/compress/gzip/gzip_spec.cr b/spec/std/compress/gzip/gzip_spec.cr index 7c0262b5328d..8ffa0624bc6d 100644 --- a/spec/std/compress/gzip/gzip_spec.cr +++ b/spec/std/compress/gzip/gzip_spec.cr @@ -1,4 +1,4 @@ -require "spec" +require "../../spec_helper" require "compress/gzip" private SAMPLE_TIME = Time.utc(2016, 1, 2) @@ -57,4 +57,18 @@ describe Compress::Gzip do gzip.rewind gzip.gets_to_end.should eq(SAMPLE_CONTENTS) end + + it "reads file with extra fields from file system" do + File.open(datapath("test.gz")) do |file| + Compress::Gzip::Reader.open(file) do |gzip| + header = gzip.header.not_nil! + header.modification_time.to_utc.should eq(Time.utc(2012, 9, 4, 22, 6, 5)) + header.os.should eq(3_u8) + header.extra.should eq(Bytes[1, 2, 3, 4, 5]) + header.name.should eq("test.txt") + header.comment.should eq("happy birthday") + gzip.gets_to_end.should eq("One\nTwo") + end + end + end end diff --git a/spec/std/data/test.gz b/spec/std/data/test.gz new file mode 100644 index 0000000000000000000000000000000000000000..dd10b17c6a6061fa00a55c905a779e45f27c340a GIT binary patch literal 60 zcmb2|=8*TTb_-x)W@TVxVrF42Ni8nXE2$`9$Ve Date: Sun, 7 Jul 2024 22:56:36 +0800 Subject: [PATCH 1231/1551] Fix macro interpolation in `NamedTuple#from` (#14790) --- spec/std/named_tuple_spec.cr | 8 ++++++++ src/named_tuple.cr | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/spec/std/named_tuple_spec.cr b/spec/std/named_tuple_spec.cr index 4097215dfca3..34619b76fea4 100644 --- a/spec/std/named_tuple_spec.cr +++ b/spec/std/named_tuple_spec.cr @@ -33,6 +33,10 @@ describe "NamedTuple" do t.should eq({"foo bar": 1, "baz qux": 2}) t.class.should eq(NamedTuple("foo bar": Int32, "baz qux": Int32)) + t = NamedTuple("\"": Int32, "\#{exit}": Int32).from({"\"" => 2, "\#{exit}" => 3}) + t.should eq({"\"": 2, "\#{exit}": 3}) + t.class.should eq(NamedTuple("\"": Int32, "\#{exit}": Int32)) + expect_raises ArgumentError do NamedTuple(foo: Int32, bar: Int32).from({:foo => 1}) end @@ -74,6 +78,10 @@ describe "NamedTuple" do t = {foo: Int32, bar: Int32}.from({"foo" => 1, :bar => 2} of String | Int32 | Symbol => Int32) t.should eq({foo: 1, bar: 2}) t.class.should eq(NamedTuple(foo: Int32, bar: Int32)) + + t = {"\"": Int32, "\#{exit}": Int32}.from({"\"" => 2, "\#{exit}" => 3}) + t.should eq({"\"": 2, "\#{exit}": 3}) + t.class.should eq(NamedTuple("\"": Int32, "\#{exit}": Int32)) end it "gets size" do diff --git a/src/named_tuple.cr b/src/named_tuple.cr index 4ea9df02fd20..d147873e5341 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -119,7 +119,7 @@ struct NamedTuple {% begin %} NamedTuple.new( {% for key, value in T %} - {{key.stringify}}: self[{{key.symbolize}}].cast(hash.fetch({{key.symbolize}}) { hash["{{key}}"] }), + {{key.stringify}}: self[{{key.symbolize}}].cast(hash.fetch({{key.symbolize}}) { hash[{{key.stringify}}] }), {% end %} ) {% end %} From c0488512e3e8d00ab967e014ec041032670deab0 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Sun, 7 Jul 2024 10:57:00 -0400 Subject: [PATCH 1232/1551] Fix regression with `NamedTuple.new` when using key with a hyphen (#14785) --- spec/std/named_tuple_spec.cr | 4 ++++ src/named_tuple.cr | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/std/named_tuple_spec.cr b/spec/std/named_tuple_spec.cr index 34619b76fea4..f94078adaec6 100644 --- a/spec/std/named_tuple_spec.cr +++ b/spec/std/named_tuple_spec.cr @@ -20,6 +20,10 @@ describe "NamedTuple" do t.class.should_not eq(NamedTuple(foo: Int32, bar: String)) end + it "does NamedTuple.new, with hyphen in key" do + NamedTuple("a-b": String).new("a-b": "foo").should eq({"a-b": "foo"}) + end + it "does NamedTuple.from" do t = NamedTuple(foo: Int32, bar: Int32).from({:foo => 1, :bar => 2}) t.should eq({foo: 1, bar: 2}) diff --git a/src/named_tuple.cr b/src/named_tuple.cr index d147873e5341..f9d606baca68 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -70,7 +70,7 @@ struct NamedTuple {% begin %} { {% for key in T %} - {{ key.stringify }}: options[{{ key.symbolize }}].as(typeof(element_type({{ key }}))), + {{ key.stringify }}: options[{{ key.symbolize }}].as(typeof(element_type({{ key.symbolize }}))), {% end %} } {% end %} From 3d007b121a53b49b79c317cddd1b2b53039e4b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Cla=C3=9Fen?= Date: Mon, 8 Jul 2024 21:19:31 +0200 Subject: [PATCH 1233/1551] Fix code example for `Process.on_terminate` (#14798) --- src/process.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/process.cr b/src/process.cr index 045615c814a7..c8364196373f 100644 --- a/src/process.cr +++ b/src/process.cr @@ -89,7 +89,7 @@ class Process # end # end # - # wait_channel.receive + # wait_channel.receive? # puts "bye" # ``` def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil From 0571f19bdbce008c2c969a4e74664ba6d082aabb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 8 Jul 2024 21:19:49 +0200 Subject: [PATCH 1234/1551] Add spec for `Compress::Gzip::Writer` with `extra` (#14788) --- spec/std/compress/gzip/gzip_spec.cr | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/spec/std/compress/gzip/gzip_spec.cr b/spec/std/compress/gzip/gzip_spec.cr index 8ffa0624bc6d..675b704436ea 100644 --- a/spec/std/compress/gzip/gzip_spec.cr +++ b/spec/std/compress/gzip/gzip_spec.cr @@ -71,4 +71,27 @@ describe Compress::Gzip do end end end + + it "writes and reads file with extra fields" do + io = IO::Memory.new + Compress::Gzip::Writer.open(io) do |gzip| + header = gzip.header + header.modification_time = Time.utc(2012, 9, 4, 22, 6, 5) + header.os = 3_u8 + header.extra = Bytes[1, 2, 3, 4, 5] + header.name = "test.txt" + header.comment = "happy birthday" + gzip << "One\nTwo" + end + io.rewind + Compress::Gzip::Reader.open(io) do |gzip| + header = gzip.header.not_nil! + header.modification_time.to_utc.should eq(Time.utc(2012, 9, 4, 22, 6, 5)) + header.os.should eq(3_u8) + header.extra.should eq(Bytes[1, 2, 3, 4, 5]) + header.name.should eq("test.txt") + header.comment.should eq("happy birthday") + gzip.gets_to_end.should eq("One\nTwo") + end + end end From 6831e441f1353455f53360820d6cafd5b8bbee35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 9 Jul 2024 21:49:43 +0200 Subject: [PATCH 1235/1551] Changelog for 1.13.0 (#14712) --- CHANGELOG.md | 440 ++++++++++++++++++++++++++++++++++++++++++ src/SOURCE_DATE_EPOCH | 1 + src/VERSION | 2 +- 3 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 src/SOURCE_DATE_EPOCH diff --git a/CHANGELOG.md b/CHANGELOG.md index 61fb131ed6e7..42a7fc10f85f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,445 @@ # Changelog +## [1.13.0] (2024-07-09) + +[1.13.0]: https://github.com/crystal-lang/crystal/releases/1.13.0 + +### Features + +#### lang + +- Allow rescuing exceptions that include a module ([#14553], thanks @Blacksmoke16) +- *(macros)* Allow assignment to `_` inside macro expressions ([#14452], thanks @HertzDevil) + +[#14553]: https://github.com/crystal-lang/crystal/pull/14553 +[#14452]: https://github.com/crystal-lang/crystal/pull/14452 + +#### stdlib + +- *(collection)* Add `Array#insert_all` ([#14486], thanks @summer-alice) +- *(collection)* Improve compile time error for `#sort(&block : T, T -> U)` ([#14693], thanks @beta-ziliani) +- *(concurrency)* Add `WaitGroup` synchronization primitive ([#14167], thanks @ysbaddaden) +- *(concurrency)* Allow `Atomic`s of pointer types ([#14401], thanks @HertzDevil) +- *(concurrency)* Add `Thread.new` yields itself ([#14543], thanks @ysbaddaden) +- *(concurrency)* Add support for `Atomic(Bool)` ([#14532], thanks @ysbaddaden) +- *(concurrency)* Add `Thread.sleep(Time::Span)` ([#14715], thanks @ysbaddaden) +- *(files)* Implement `IO#tty?` in Win32 ([#14421], thanks @HertzDevil) +- *(files)* Implement `File.readable?` and `.writable?` in Win32 ([#14420], thanks @HertzDevil) +- *(files)* Make `File.readable?` and `.writable?` follow symlinks on Windows ([#14514], thanks @HertzDevil) +- *(llvm)* Add some missing `LLVM::Context` bindings ([#14612], thanks @ysbaddaden) +- *(llvm)* Do not strip the macOS target triple ([#14466], thanks @hovsater) +- *(log)* Support `UInt32` and `UInt64` in Log Context ([#14459], thanks @toddsundsted) +- *(macros)* Add AST node methods for macro-related nodes ([#14492], thanks @HertzDevil) +- *(macros)* Support short blocks in `#[]` operator call's macro interpolation ([#14523], thanks @HertzDevil) +- *(macros)* Add macro methods for `Select` ([#14600], thanks @HertzDevil) +- *(macros)* Add `TypeNode#private?`, `#public?` and `#visibility` ([#11696], thanks @Hadeweka) +- *(macros)* Add `StringLiteral#to_utf16` ([#14676], thanks @ysbaddaden) +- *(networking)* Relax type restriction of handlers in `HTTP::Server.new` to `Indexable(HTTP::Handler)` ([#14413], thanks @hugopl) +- *(networking)* **[security]** OpenSSL: don't change default cipher suites ([#14655], thanks @ysbaddaden) +- *(networking)* Allow parsing cookies with space in the value ([#14455], thanks @anton7c3) +- *(runtime)* Add `EventLoop::Socket` module ([#14643], thanks @straight-shoota) +- *(runtime)* Add `EventLoop::FileDescriptor` module ([#14639], thanks @straight-shoota) +- *(runtime)* Add `Crystal::Tracing` for runtime tracing ([#14659], thanks @ysbaddaden) +- *(system)* **[security]** **[breaking]** Disable implicit execution of batch files on Windows ([#14557], thanks @straight-shoota) +- *(system)* Add `EventLoop#run(blocking)` and `EventLoop#interrupt` ([#14568], thanks @ysbaddaden) +- *(system)* Add `Crystal::System::Time.ticks` ([#14620], thanks @ysbaddaden) +- *(system)* Add `Crystal::System::Thread.current_thread?`, `#scheduler?` ([#14660], thanks @ysbaddaden) +- *(system)* Protect fork/exec on targets that don't support atomic CLOEXEC ([#14674], thanks @ysbaddaden) +- *(system)* Add `Crystal::System.panic` ([#14733], thanks @ysbaddaden) +- *(text)* Add `Regex::MatchOptions` overload for `String#index!` ([#14462], thanks @HertzDevil) +- *(text)* Add `StringPool#get?` ([#14508], thanks @HertzDevil) +- *(text)* Add `pkg_config` names for `pcre2` and `pcre` ([#14584], thanks @straight-shoota) +- *(text)* Add UUID v7 ([#14732], thanks @jgaskins) +- *(time)* Add `Time::Error` ([#14743], thanks @Blacksmoke16) + +[#14486]: https://github.com/crystal-lang/crystal/pull/14486 +[#14693]: https://github.com/crystal-lang/crystal/pull/14693 +[#14167]: https://github.com/crystal-lang/crystal/pull/14167 +[#14401]: https://github.com/crystal-lang/crystal/pull/14401 +[#14543]: https://github.com/crystal-lang/crystal/pull/14543 +[#14532]: https://github.com/crystal-lang/crystal/pull/14532 +[#14715]: https://github.com/crystal-lang/crystal/pull/14715 +[#14421]: https://github.com/crystal-lang/crystal/pull/14421 +[#14420]: https://github.com/crystal-lang/crystal/pull/14420 +[#14514]: https://github.com/crystal-lang/crystal/pull/14514 +[#14612]: https://github.com/crystal-lang/crystal/pull/14612 +[#14466]: https://github.com/crystal-lang/crystal/pull/14466 +[#14459]: https://github.com/crystal-lang/crystal/pull/14459 +[#14492]: https://github.com/crystal-lang/crystal/pull/14492 +[#14523]: https://github.com/crystal-lang/crystal/pull/14523 +[#14600]: https://github.com/crystal-lang/crystal/pull/14600 +[#11696]: https://github.com/crystal-lang/crystal/pull/11696 +[#14676]: https://github.com/crystal-lang/crystal/pull/14676 +[#14413]: https://github.com/crystal-lang/crystal/pull/14413 +[#14655]: https://github.com/crystal-lang/crystal/pull/14655 +[#14455]: https://github.com/crystal-lang/crystal/pull/14455 +[#14643]: https://github.com/crystal-lang/crystal/pull/14643 +[#14639]: https://github.com/crystal-lang/crystal/pull/14639 +[#14659]: https://github.com/crystal-lang/crystal/pull/14659 +[#14557]: https://github.com/crystal-lang/crystal/pull/14557 +[#14568]: https://github.com/crystal-lang/crystal/pull/14568 +[#14620]: https://github.com/crystal-lang/crystal/pull/14620 +[#14660]: https://github.com/crystal-lang/crystal/pull/14660 +[#14674]: https://github.com/crystal-lang/crystal/pull/14674 +[#14733]: https://github.com/crystal-lang/crystal/pull/14733 +[#14462]: https://github.com/crystal-lang/crystal/pull/14462 +[#14508]: https://github.com/crystal-lang/crystal/pull/14508 +[#14584]: https://github.com/crystal-lang/crystal/pull/14584 +[#14732]: https://github.com/crystal-lang/crystal/pull/14732 +[#14743]: https://github.com/crystal-lang/crystal/pull/14743 + +#### compiler + +- *(codegen)* Add compiler flags `-Os` and `-Oz` to optimize binary size ([#14463], thanks @ysbaddaden) +- *(codegen)* Add compiler support for AVR architecture (Arduino) ([#14393], thanks @ysbaddaden) + +[#14463]: https://github.com/crystal-lang/crystal/pull/14463 +[#14393]: https://github.com/crystal-lang/crystal/pull/14393 + +#### tools + +- *(formatter)* Allow new formatter styles for trailing comma and whitespace around proc literal ([#14726], thanks @Blacksmoke16) + +[#14726]: https://github.com/crystal-lang/crystal/pull/14726 + +### Bugfixes + +#### lang + +- *(macros)* Fix parsing of non-trailing `if` bodies inside macro expressions ([#14505], thanks @HertzDevil) +- *(macros)* Drop parentheses around `->` inside certain comma-separated lists ([#14506], thanks @HertzDevil) +- *(macros)* Fix indentation of `Select` nodes' macro interpolation ([#14510], thanks @HertzDevil) +- *(macros)* Fix indentation of parenthesized `Expressions#to_s` ([#14511], thanks @HertzDevil) + +[#14505]: https://github.com/crystal-lang/crystal/pull/14505 +[#14506]: https://github.com/crystal-lang/crystal/pull/14506 +[#14510]: https://github.com/crystal-lang/crystal/pull/14510 +[#14511]: https://github.com/crystal-lang/crystal/pull/14511 + +#### stdlib + +- *(collection)* **[regression]** Ensure `Enumerable#to_a` and `Enumerable#tally` properly retain return type of `T` ([#14447], thanks @Blacksmoke16) +- *(collection)* **[breaking]** Never raise `IndexError` in `#[]?(Range)` ([#14444], thanks @HertzDevil) +- *(collection)* **[breaking]** Fix `Set#to_a(&)` ([#14519], thanks @meatball133) +- *(collection)* Fix `Hash#rehash` to reset `@first` ([#14606], thanks @straight-shoota) +- *(collection)* Fix macro interpolation in `NamedTuple#from` ([#14790], thanks @HertzDevil) +- *(collection)* **[regression]** Fix regression with `NamedTuple.new` when using key with a hyphen ([#14785], thanks @Blacksmoke16) +- *(files)* Allow `#fsync` and `#flock_*` on `IO::FileDescriptor` ([#14432], thanks @HertzDevil) +- *(files)* Make `IO::FileDescriptor#tty?` return false for NUL on Windows ([#14509], thanks @HertzDevil) +- *(files)* Fix blockless `IO::FileDescriptor` echo and raw mode methods ([#14529], thanks @HertzDevil) +- *(files)* Remove target path's forward slashes in `File.symlink` on Windows ([#14522], thanks @HertzDevil) +- *(files)* Fix `IO#same_content?` accepting prefix on second stream ([#14664], thanks @straight-shoota) +- *(files)* Fix overflow in `File#read_at` for large offsets on Windows ([#14708], thanks @HertzDevil) +- *(files)* Fix `IO::FileDescriptor.new` for closed fd ([#14697], thanks @straight-shoota) +- *(files)* Fix `IO::Delimited` reading into limited slice with peek ([#14772], thanks @straight-shoota) +- *(files)* Fix `Compress::Gzip` extra field ([#14550], thanks @kojix2) +- *(log)* delete source from builder cache when finalize ([#14475], thanks @ysbaddaden) +- *(networking)* Fix `Socket#close` error handling on Windows ([#14517], thanks @HertzDevil) +- *(networking)* Set UTF-8 charset on directory listing in `HTTP::StaticFileHandler` ([#14546], thanks @alexkutsan) +- *(networking)* Don't pass socket file descriptors to subprocesses on Unix (`SOCK_CLOEXEC`) ([#14632], thanks @carlhoerberg) +- *(networking)* **[security]** OpenSSL: don't set default ECDH curve ([#14656], thanks @ysbaddaden) +- *(networking)* **[security]** OpenSSL: deprecate Mozilla's TLS Server recommendation ([#14657], thanks @ysbaddaden) +- *(networking)* use `SOCK_CLOEXEC` with `FD_CLOEXEC` fallback ([#14672], thanks @ysbaddaden) +- *(networking)* **[regression]** Fix regression on `Socket#connect` timeout type restriction ([#14755], thanks @straight-shoota) +- *(networking)* Drop default timeout for `Socket#connect` on Windows ([#14756], thanks @straight-shoota) +- *(networking)* don't hardcode alpn protocol byte size (OpenSSL) ([#14769], thanks @ysbaddaden) +- *(numeric)* Fix `BigRational#format` ([#14525], thanks @meatball133) +- *(numeric)* **[regression]** Restore leading zero in exponent for `printf("%e")` and `printf("%g")` ([#14695], thanks @straight-shoota) +- *(runtime)* Fix enable docs for builtin constants ([#14571], thanks @straight-shoota) +- *(runtime)* Fix `GC.malloc` for `gc_none` to clear memory ([#14746], thanks @straight-shoota) +- *(serialization)* Fix JSON discriminator for Bool `false` value ([#14779], thanks @dammer) +- *(specs)* Fix relative file paths in spec output ([#14725], thanks @straight-shoota) +- *(system)* Fix using `System.retry_with_buffer` with stack buffer ([#14615], thanks @straight-shoota) +- *(system)* Harmonize close on exec for `Socket` & `FileDescriptor` on Windows ([#14634], thanks @ysbaddaden) +- *(system)* Use `dup3` and `pipe2` to set `O_CLOEXEC` when available ([#14673], thanks @ysbaddaden) +- *(system)* Fix calls to `retry_with_buffer` when big buffer is necessary ([#14622], thanks @BlobCodes) +- *(system)* Fix `Process.run` with closed IO ([#14698], thanks @straight-shoota) +- *(system)* Prefer `strerror_r` over `strerror` for thread-safe errno ([#14764], thanks @ysbaddaden) +- *(text)* Make `String#sub` raise `IndexError` if index is equal to size ([#14458], thanks @HertzDevil) +- *(text)* Fix `libpcre2` version detection not working for `-RC{N}` versions ([#14478], thanks @Frityet) +- *(text)* Fix `Regex#inspect` with non-literal-compatible options ([#14575], thanks @straight-shoota) +- *(text)* Fix ECR escape sequences containing `-` ([#14739], thanks @HertzDevil) + +[#14447]: https://github.com/crystal-lang/crystal/pull/14447 +[#14444]: https://github.com/crystal-lang/crystal/pull/14444 +[#14519]: https://github.com/crystal-lang/crystal/pull/14519 +[#14606]: https://github.com/crystal-lang/crystal/pull/14606 +[#14790]: https://github.com/crystal-lang/crystal/pull/14790 +[#14785]: https://github.com/crystal-lang/crystal/pull/14785 +[#14432]: https://github.com/crystal-lang/crystal/pull/14432 +[#14509]: https://github.com/crystal-lang/crystal/pull/14509 +[#14529]: https://github.com/crystal-lang/crystal/pull/14529 +[#14522]: https://github.com/crystal-lang/crystal/pull/14522 +[#14664]: https://github.com/crystal-lang/crystal/pull/14664 +[#14708]: https://github.com/crystal-lang/crystal/pull/14708 +[#14697]: https://github.com/crystal-lang/crystal/pull/14697 +[#14772]: https://github.com/crystal-lang/crystal/pull/14772 +[#14550]: https://github.com/crystal-lang/crystal/pull/14550 +[#14475]: https://github.com/crystal-lang/crystal/pull/14475 +[#14517]: https://github.com/crystal-lang/crystal/pull/14517 +[#14546]: https://github.com/crystal-lang/crystal/pull/14546 +[#14632]: https://github.com/crystal-lang/crystal/pull/14632 +[#14656]: https://github.com/crystal-lang/crystal/pull/14656 +[#14657]: https://github.com/crystal-lang/crystal/pull/14657 +[#14672]: https://github.com/crystal-lang/crystal/pull/14672 +[#14755]: https://github.com/crystal-lang/crystal/pull/14755 +[#14756]: https://github.com/crystal-lang/crystal/pull/14756 +[#14769]: https://github.com/crystal-lang/crystal/pull/14769 +[#14525]: https://github.com/crystal-lang/crystal/pull/14525 +[#14695]: https://github.com/crystal-lang/crystal/pull/14695 +[#14571]: https://github.com/crystal-lang/crystal/pull/14571 +[#14746]: https://github.com/crystal-lang/crystal/pull/14746 +[#14779]: https://github.com/crystal-lang/crystal/pull/14779 +[#14725]: https://github.com/crystal-lang/crystal/pull/14725 +[#14615]: https://github.com/crystal-lang/crystal/pull/14615 +[#14634]: https://github.com/crystal-lang/crystal/pull/14634 +[#14673]: https://github.com/crystal-lang/crystal/pull/14673 +[#14622]: https://github.com/crystal-lang/crystal/pull/14622 +[#14698]: https://github.com/crystal-lang/crystal/pull/14698 +[#14764]: https://github.com/crystal-lang/crystal/pull/14764 +[#14458]: https://github.com/crystal-lang/crystal/pull/14458 +[#14478]: https://github.com/crystal-lang/crystal/pull/14478 +[#14575]: https://github.com/crystal-lang/crystal/pull/14575 +[#14739]: https://github.com/crystal-lang/crystal/pull/14739 + +#### compiler + +- *(codegen)* Fix create new `target_machine` for every program ([#14694], thanks @straight-shoota) +- *(codegen)* Make `ReferenceStorage(T)` non-atomic if `T` is non-atomic ([#14730], thanks @HertzDevil) +- *(codegen)* Detect and error on failed codegen process ([#14762], thanks @ysbaddaden) +- *(codegen)* Fix stats and progress issues in codegen ([#14763], thanks @ysbaddaden) +- *(debugger)* Fix LLDB `crystal_formatters.py` for Python 3 ([#14665], thanks @zw963) +- *(parser)* Disallow assignments to calls with parentheses ([#14527], thanks @HertzDevil) +- *(parser)* Fix parser validate UTF-8 on first input byte ([#14750], thanks @straight-shoota) +- *(semantic)* Fix type def reopening type from parent namespace ([#11208], thanks @straight-shoota) +- *(semantic)* Fix `Crystal::Path#to_macro_id` for global path ([#14490], thanks @straight-shoota) +- *(semantic)* Fix `Class#===` on metaclass ([#11162], thanks @makenowjust) + +[#14694]: https://github.com/crystal-lang/crystal/pull/14694 +[#14730]: https://github.com/crystal-lang/crystal/pull/14730 +[#14762]: https://github.com/crystal-lang/crystal/pull/14762 +[#14763]: https://github.com/crystal-lang/crystal/pull/14763 +[#14665]: https://github.com/crystal-lang/crystal/pull/14665 +[#14527]: https://github.com/crystal-lang/crystal/pull/14527 +[#14750]: https://github.com/crystal-lang/crystal/pull/14750 +[#11208]: https://github.com/crystal-lang/crystal/pull/11208 +[#14490]: https://github.com/crystal-lang/crystal/pull/14490 +[#11162]: https://github.com/crystal-lang/crystal/pull/11162 + +#### tools + +- *(docs-generator)* Fix generate docs for builtins `HOST_TRIPLE` and `TARGET_TRIPLE` ([#14570], thanks @straight-shoota) +- *(docs-generator)* Decode URI component for search functionality in docs ([#14645], thanks @nobodywasishere) +- *(formatter)* Fix formatting for short block inside `#[]` operator call ([#14526], thanks @HertzDevil) +- *(formatter)* Fix formatter to skip trailing comma for single-line parameters ([#14713], thanks @Blacksmoke16) + +[#14570]: https://github.com/crystal-lang/crystal/pull/14570 +[#14645]: https://github.com/crystal-lang/crystal/pull/14645 +[#14526]: https://github.com/crystal-lang/crystal/pull/14526 +[#14713]: https://github.com/crystal-lang/crystal/pull/14713 + +### Chores + +#### stdlib + +- *(collection)* Drop obsolete workaround in `Range#reverse_each` ([#14709], thanks @yxhuvud) +- *(concurrency)* Add `WaitGroup` to `docs_main.cr` ([#14624], thanks @straight-shoota) +- *(files)* **[deprecation]** Move `File.readable?`, `.writable?`, `.executable?` to `File::Info` ([#14484], thanks @straight-shoota) +- *(networking)* Drop `Crystal::System::Socket#system_send` ([#14637], thanks @straight-shoota) +- *(networking)* Add type restriction `host : String` in `TCPSocket` and `Addrinfo` ([#14703], thanks @straight-shoota) +- *(runtime)* Fix abstract def parameter name in `LibEvent::EventLoop#send_to` ([#14658], thanks @straight-shoota) +- *(runtime)* **[breaking]** Drop unused methods in `IO::Evented` ([#14666], thanks @straight-shoota) +- *(runtime)* Drop `IO::Overlapped` ([#14704], thanks @straight-shoota) + +[#14709]: https://github.com/crystal-lang/crystal/pull/14709 +[#14624]: https://github.com/crystal-lang/crystal/pull/14624 +[#14484]: https://github.com/crystal-lang/crystal/pull/14484 +[#14637]: https://github.com/crystal-lang/crystal/pull/14637 +[#14703]: https://github.com/crystal-lang/crystal/pull/14703 +[#14658]: https://github.com/crystal-lang/crystal/pull/14658 +[#14666]: https://github.com/crystal-lang/crystal/pull/14666 +[#14704]: https://github.com/crystal-lang/crystal/pull/14704 + +#### compiler + +- *(codegen)* **[breaking]** Remove `CRYSTAL_LIBRARY_RPATH` and delay-load helper ([#14598], thanks @HertzDevil) + +[#14598]: https://github.com/crystal-lang/crystal/pull/14598 + +### Performance + +#### stdlib + +- *(collection)* Optimize `Hash` for repeated removals and insertions ([#14539], thanks @HertzDevil) +- *(runtime)* Remove unnecessary explicit memory barriers on ARM ([#14567], thanks @ysbaddaden) +- *(serialization)* Optimize JSON parsing a bit ([#14366], thanks @asterite) +- *(text)* Use wrapping arithmetic for `Int::Primitive#unsafe_chr` ([#14443], thanks @HertzDevil) +- *(text)* Optimize `String#index(Char)` and `#rindex(Char)` for invalid UTF-8 ([#14461], thanks @HertzDevil) +- *(text)* Optimize `String#to_utf16` ([#14671], thanks @straight-shoota) + +[#14539]: https://github.com/crystal-lang/crystal/pull/14539 +[#14567]: https://github.com/crystal-lang/crystal/pull/14567 +[#14366]: https://github.com/crystal-lang/crystal/pull/14366 +[#14443]: https://github.com/crystal-lang/crystal/pull/14443 +[#14461]: https://github.com/crystal-lang/crystal/pull/14461 +[#14671]: https://github.com/crystal-lang/crystal/pull/14671 + +### Refactor + +#### stdlib + +- Remove unnecessary calls to `#unsafe_as(UInt64)` etc. ([#14686], thanks @straight-shoota) +- *(crypto)* Replace calls to `StaticArray(UInt8, 1)#unsafe_as(UInt8)` ([#14685], thanks @straight-shoota) +- *(files)* Remove calls to `LibC._setmode` ([#14419], thanks @HertzDevil) +- *(files)* Use file handles directly instead of C file descriptors on Win32 ([#14501], thanks @HertzDevil) +- *(files)* Refactor win32 `System::FileDescriptor#unbuffered_{read,write}` ([#14607], thanks @straight-shoota) +- *(files)* Extract `#system_read` and `#system_write` for `FileDescriptor` and `Socket` ([#14626], thanks @straight-shoota) +- *(files)* Drop unused slice parameters of `#evented_*` methods ([#14627], thanks @straight-shoota) +- *(networking)* Refactor use `IO#read_byte` instead of `#read_char` in `HTTP::ChunkedContent` ([#14548], thanks @straight-shoota) +- *(runtime)* `Thread` owns its current fiber (instead of `Crystal::Scheduler`) ([#14554], thanks @ysbaddaden) +- *(runtime)* Use `Fiber#enqueue` ([#14561], thanks @ysbaddaden) +- *(runtime)* Add `Crystal::EventLoop.current` ([#14559], thanks @ysbaddaden) +- *(runtime)* Add `Fiber.suspend` ([#14560], thanks @ysbaddaden) +- *(runtime)* Remove `OverlappedOperation#synchronous` ([#14663], thanks @straight-shoota) +- *(runtime)* Rename `Crystal::Iocp` to `Crystal::IOCP` ([#14662], thanks @straight-shoota) +- *(runtime)* Unify `EventLoop.create` ([#14661], thanks @straight-shoota) +- *(serialization)* Replace `type` declarations for void pointers with `alias` in `libxml2` ([#14494], thanks @straight-shoota) +- *(serialization)* Refactor `JSON::Any#size` to use two branches instead of three ([#14533], thanks @meatball133) +- *(specs)* Move `Spec` context state into `Spec::CLI` ([#14259], thanks @HertzDevil) +- *(system)* Update `WinError#to_errno` ([#14515], thanks @HertzDevil) +- *(system)* Rename `Crystal::System.print_error(fmt, *args, &)` to `printf` ([#14617], thanks @ysbaddaden) +- *(system)* Refactor `Crystal::System.retry_with_buffer` calling `#to_slice` ([#14614], thanks @straight-shoota) +- *(system)* Extract system implementation of `UNIXSocket.pair` as `Crystal::System::Socket.socketpair` ([#14675], thanks @ysbaddaden) +- *(system)* Remove calls to `Pointer.new(Int)` ([#14683], thanks @straight-shoota) +- *(system)* Cleanup for `IOCP::OverlappedOperation` ([#14723], thanks @straight-shoota) +- *(system)* Refactor `IOCP::OverlappedOperation` internalize `handle` and `wait_for_completion` ([#14724], thanks @straight-shoota) + +[#14686]: https://github.com/crystal-lang/crystal/pull/14686 +[#14685]: https://github.com/crystal-lang/crystal/pull/14685 +[#14419]: https://github.com/crystal-lang/crystal/pull/14419 +[#14501]: https://github.com/crystal-lang/crystal/pull/14501 +[#14607]: https://github.com/crystal-lang/crystal/pull/14607 +[#14626]: https://github.com/crystal-lang/crystal/pull/14626 +[#14627]: https://github.com/crystal-lang/crystal/pull/14627 +[#14548]: https://github.com/crystal-lang/crystal/pull/14548 +[#14554]: https://github.com/crystal-lang/crystal/pull/14554 +[#14561]: https://github.com/crystal-lang/crystal/pull/14561 +[#14559]: https://github.com/crystal-lang/crystal/pull/14559 +[#14560]: https://github.com/crystal-lang/crystal/pull/14560 +[#14663]: https://github.com/crystal-lang/crystal/pull/14663 +[#14662]: https://github.com/crystal-lang/crystal/pull/14662 +[#14661]: https://github.com/crystal-lang/crystal/pull/14661 +[#14494]: https://github.com/crystal-lang/crystal/pull/14494 +[#14533]: https://github.com/crystal-lang/crystal/pull/14533 +[#14259]: https://github.com/crystal-lang/crystal/pull/14259 +[#14515]: https://github.com/crystal-lang/crystal/pull/14515 +[#14617]: https://github.com/crystal-lang/crystal/pull/14617 +[#14614]: https://github.com/crystal-lang/crystal/pull/14614 +[#14675]: https://github.com/crystal-lang/crystal/pull/14675 +[#14683]: https://github.com/crystal-lang/crystal/pull/14683 +[#14723]: https://github.com/crystal-lang/crystal/pull/14723 +[#14724]: https://github.com/crystal-lang/crystal/pull/14724 + +#### compiler + +- *(codegen)* Add `Program#size_t` and `Target#size_bit_width` ([#14442], thanks @ysbaddaden) +- *(parser)* Replace `Crystal::Select::When` with `Crystal::When` ([#14497], thanks @HertzDevil) + +[#14442]: https://github.com/crystal-lang/crystal/pull/14442 +[#14497]: https://github.com/crystal-lang/crystal/pull/14497 + +### Documentation + +#### stdlib + +- *(collection)* Fix doc for `Set#proper_superset_of?` ([#14516], thanks @meatball133) +- *(collection)* Fix result formatting in code example for `Indexable#[]?` ([#14721], thanks @meatball133) +- *(concurrency)* Fix example for `WeakRef` by removing `ref.value` call ([#10846], thanks @hugopl) +- *(networking)* Improve API docs for `Socket#send` ([#14638], thanks @straight-shoota) +- *(networking)* Add documentation for `HTTP::WebSocket#stream` ([#14537], thanks @meatball133) +- *(numeric)* Add documentation for complex methods inside Number ([#14538], thanks @meatball133) +- *(runtime)* Add documentation for standard streams blocking behaviour ([#14577], thanks @straight-shoota) +- *(serialization)* Fix docs for `CSV::Builder#row(&)` ([#14736], thanks @philipp-classen) +- *(system)* **[breaking]** Undocument `IO::Evented` ([#14749], thanks @straight-shoota) +- *(system)* Fix code example for `Process.on_terminate` ([#14798], thanks @philipp-classen) +- *(text)* Fix docs for `Char#ascii_number?` stating wrong minium base ([#14521], thanks @meatball133) +- *(text)* Enhance documentation for regex options `NO_UTF_CHECK` ([#14542], thanks @straight-shoota) + +[#14516]: https://github.com/crystal-lang/crystal/pull/14516 +[#14721]: https://github.com/crystal-lang/crystal/pull/14721 +[#10846]: https://github.com/crystal-lang/crystal/pull/10846 +[#14638]: https://github.com/crystal-lang/crystal/pull/14638 +[#14537]: https://github.com/crystal-lang/crystal/pull/14537 +[#14538]: https://github.com/crystal-lang/crystal/pull/14538 +[#14577]: https://github.com/crystal-lang/crystal/pull/14577 +[#14736]: https://github.com/crystal-lang/crystal/pull/14736 +[#14749]: https://github.com/crystal-lang/crystal/pull/14749 +[#14798]: https://github.com/crystal-lang/crystal/pull/14798 +[#14521]: https://github.com/crystal-lang/crystal/pull/14521 +[#14542]: https://github.com/crystal-lang/crystal/pull/14542 + +### Specs + +#### stdlib + +- Remove incorrect uses of `describe` ([#14757], thanks @HertzDevil) +- *(files)* Add spec for `Compress::Gzip::Writer` with `extra` ([#14788], thanks @straight-shoota) +- *(runtime)* Add specs for `Pointer::Appender` ([#14719], thanks @straight-shoota) +- *(text)* Add test helper for `normalize/regex_spec` ([#14545], thanks @straight-shoota) + +[#14757]: https://github.com/crystal-lang/crystal/pull/14757 +[#14788]: https://github.com/crystal-lang/crystal/pull/14788 +[#14719]: https://github.com/crystal-lang/crystal/pull/14719 +[#14545]: https://github.com/crystal-lang/crystal/pull/14545 + +### Infrastructure + +- Changelog for 1.13.0 ([#14712], thanks @straight-shoota) +- Update previous Crystal release 1.12.1 ([#14480], thanks @straight-shoota) +- Highlight regression bugfixes in changelog ([#14474], thanks @straight-shoota) +- Write release version to `src/VERSION` in `update-changelog` and `release-update` ([#14547], thanks @straight-shoota) +- Fix `shell.nix` use `llvmPackages.bintools` with wrapper for rpath config ([#14583], thanks @straight-shoota) +- Add `src/SOURCE_DATE_EPOCH` to release tree ([#14574], thanks @straight-shoota) +- Update distribution-scripts ([#14562], thanks @straight-shoota) +- Update distribution-scripts ([#14594], thanks @straight-shoota) +- Use `boehmgc` package from nixpkgs in `shell.nix` ([#14591], thanks @straight-shoota) +- Simplify LLVM dependency in `shell.nix` ([#14590], thanks @straight-shoota) +- Fix nixpkgs `pkg-config` name in `shell.nix` ([#14593], thanks @straight-shoota) +- Update previous Crystal release 1.12.2 ([#14647], thanks @straight-shoota) +- Update distribution-scripts ([#14648], thanks @straight-shoota) +- Update distribution-scripts ([#14714], thanks @straight-shoota) +- Update distribution-scripts ([#14776], thanks @straight-shoota) +- Fix changelog generator increase topic priority for `infrastructure` ([#14781], thanks @straight-shoota) +- Remove `SetShouldExit` in Powershell wrapper ([#13769], thanks @straight-shoota) +- *(ci)* Run `primitives_spec` with the interpreter on CI ([#14438], thanks @HertzDevil) +- *(ci)* Add LLVM 18 to LLVM CI ([#14565], thanks @HertzDevil) +- *(ci)* Update to Ruby 3 in macOS circleCI runner ([#14777], thanks @straight-shoota) +- *(ci)* Distribute `shards.pdb` on Windows ([#14415], thanks @HertzDevil) +- *(ci)* Drop Windows CI workaround for 1.12.0-dev ([#14483], thanks @HertzDevil) + +[#14712]: https://github.com/crystal-lang/crystal/pull/14712 +[#14480]: https://github.com/crystal-lang/crystal/pull/14480 +[#14474]: https://github.com/crystal-lang/crystal/pull/14474 +[#14547]: https://github.com/crystal-lang/crystal/pull/14547 +[#14583]: https://github.com/crystal-lang/crystal/pull/14583 +[#14574]: https://github.com/crystal-lang/crystal/pull/14574 +[#14562]: https://github.com/crystal-lang/crystal/pull/14562 +[#14594]: https://github.com/crystal-lang/crystal/pull/14594 +[#14591]: https://github.com/crystal-lang/crystal/pull/14591 +[#14590]: https://github.com/crystal-lang/crystal/pull/14590 +[#14593]: https://github.com/crystal-lang/crystal/pull/14593 +[#14647]: https://github.com/crystal-lang/crystal/pull/14647 +[#14648]: https://github.com/crystal-lang/crystal/pull/14648 +[#14714]: https://github.com/crystal-lang/crystal/pull/14714 +[#14776]: https://github.com/crystal-lang/crystal/pull/14776 +[#14781]: https://github.com/crystal-lang/crystal/pull/14781 +[#13769]: https://github.com/crystal-lang/crystal/pull/13769 +[#14438]: https://github.com/crystal-lang/crystal/pull/14438 +[#14565]: https://github.com/crystal-lang/crystal/pull/14565 +[#14777]: https://github.com/crystal-lang/crystal/pull/14777 +[#14415]: https://github.com/crystal-lang/crystal/pull/14415 +[#14483]: https://github.com/crystal-lang/crystal/pull/14483 + ## [1.12.2] (2024-05-31) _Patch release with a bug fix necessary for support of latest libgc_ diff --git a/src/SOURCE_DATE_EPOCH b/src/SOURCE_DATE_EPOCH new file mode 100644 index 000000000000..ae6e55c235fe --- /dev/null +++ b/src/SOURCE_DATE_EPOCH @@ -0,0 +1 @@ +1720483200 diff --git a/src/VERSION b/src/VERSION index a4ab692a5f77..feaae22bac7e 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.13.0-dev +1.13.0 From 2204be7d920d0361056b2f5b60364039a971f8dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 11 Jul 2024 10:19:35 +0200 Subject: [PATCH 1236/1551] Revert "Optimize JSON parsing a bit (#14366)" (#14804) This reverts commit 9ef636644100b3ad41ab6094da20dbd506e439c6. --- spec/std/json/parser_spec.cr | 7 +--- spec/std/json/pull_parser_spec.cr | 4 +-- src/json/lexer.cr | 22 +------------ src/json/lexer/io_based.cr | 53 ++----------------------------- src/json/lexer/string_based.cr | 22 ++++++------- src/json/token.cr | 21 ++---------- 6 files changed, 19 insertions(+), 110 deletions(-) diff --git a/spec/std/json/parser_spec.cr b/spec/std/json/parser_spec.cr index 7b8578f522c7..96cfd52277a2 100644 --- a/spec/std/json/parser_spec.cr +++ b/spec/std/json/parser_spec.cr @@ -2,13 +2,9 @@ require "spec" require "json" private def it_parses(string, expected_value, file = __FILE__, line = __LINE__) - it "parses #{string} from String", file, line do + it "parses #{string}", file, line do JSON.parse(string).raw.should eq(expected_value) end - - it "parses #{string} from IO", file, line do - JSON.parse(IO::Memory.new(string)).raw.should eq(expected_value) - end end private def it_raises_on_parse(string, file = __FILE__, line = __LINE__) @@ -35,7 +31,6 @@ describe JSON::Parser do it_parses "[true]", [true] it_parses "[false]", [false] it_parses %(["hello"]), ["hello"] - it_parses %(["hello", 1]), ["hello", 1] it_parses "[0]", [0] it_parses " [ 0 ] ", [0] diff --git a/spec/std/json/pull_parser_spec.cr b/spec/std/json/pull_parser_spec.cr index 28ef4c6cf527..8de524e86c87 100644 --- a/spec/std/json/pull_parser_spec.cr +++ b/spec/std/json/pull_parser_spec.cr @@ -92,8 +92,8 @@ class JSON::PullParser end end -private def assert_pull_parse(string, file = __FILE__, line = __LINE__) - it "parses #{string}", file, line do +private def assert_pull_parse(string) + it "parses #{string}" do parser = JSON::PullParser.new string parser.assert JSON.parse(string).raw parser.kind.should eq(JSON::PullParser::Kind::EOF) diff --git a/src/json/lexer.cr b/src/json/lexer.cr index 067d595aeff3..3e61179b9844 100644 --- a/src/json/lexer.cr +++ b/src/json/lexer.cr @@ -220,16 +220,8 @@ abstract class JSON::Lexer private def consume_number number_start - # Integer values of up to 18 digits can be computed by doing math: - # no need to store a string value and later parse it. - # For larger numbers, or floats, we store the entire string and later parse it. - @token.int_value = nil - integer = 0_i64 - negative = false - if current_char == '-' append_number_char - negative = true next_char end @@ -246,19 +238,13 @@ abstract class JSON::Lexer unexpected_char else @token.kind = :int - @token.int_value = 0 number_end end when '1'..'9' append_number_char - digits = 1 - integer = (current_char - '0').to_i64 char = next_char while '0' <= char <= '9' append_number_char - digits += 1 - integer &*= 10 - integer &+= char - '0' char = next_char end @@ -269,13 +255,7 @@ abstract class JSON::Lexer consume_exponent else @token.kind = :int - # Int64::MAX is 9223372036854775807 which has 19 digits. - # With 18 digits we know the number we computed is the one we read. - if digits > 18 - number_end - else - @token.int_value = negative ? -integer : integer - end + number_end end else unexpected_char diff --git a/src/json/lexer/io_based.cr b/src/json/lexer/io_based.cr index f1f5346306db..d3989c54f0a8 100644 --- a/src/json/lexer/io_based.cr +++ b/src/json/lexer/io_based.cr @@ -2,64 +2,17 @@ class JSON::Lexer::IOBased < JSON::Lexer def initialize(@io : IO) super() - @current_char = @io.read_byte.try(&.chr) || '\0' + @current_char = @io.read_char || '\0' end private getter current_char private def next_char_no_column_increment - @current_char = @io.read_byte.try(&.chr) || '\0' + @current_char = @io.read_char || '\0' end private def consume_string - peek = @io.peek - if !peek || peek.empty? - return consume_string_with_buffer - end - - pos = 0 - - while true - if pos >= peek.size - # We don't have enough data in the peek buffer to create a string: - # default to the slow method - return consume_string_with_buffer - end - - char = peek[pos] - case char - when '\\' - # If we find an escape character, go to the slow method - @column_number += pos - return consume_string_at_escape_char(peek, pos) - when '"' - break - else - if 0 <= current_char.ord < 32 - unexpected_char - else - pos += 1 - end - end - end - - @column_number += pos - @token.string_value = - if @expects_object_key - @string_pool.get(peek.to_unsafe, pos) - else - String.new(peek.to_unsafe, pos) - end - - @io.skip(pos + 1) - next_char - end - - private def consume_string_at_escape_char(peek, pos) - consume_string_with_buffer do - @buffer.write peek[0, pos] - @io.skip(pos) - end + consume_string_with_buffer end private def number_start diff --git a/src/json/lexer/string_based.cr b/src/json/lexer/string_based.cr index d8b3b64f1940..5696bc6f78b2 100644 --- a/src/json/lexer/string_based.cr +++ b/src/json/lexer/string_based.cr @@ -1,9 +1,8 @@ # :nodoc: class JSON::Lexer::StringBased < JSON::Lexer - def initialize(string : String) + def initialize(string) super() - @string = string - @pos = 0 + @reader = Char::Reader.new(string) @number_start = 0 end @@ -34,7 +33,7 @@ class JSON::Lexer::StringBased < JSON::Lexer if @expects_object_key start_pos += 1 end_pos = current_pos - 1 - @token.string_value = @string_pool.get(@string.to_unsafe + start_pos, end_pos - start_pos) + @token.string_value = @string_pool.get(@reader.string.to_unsafe + start_pos, end_pos - start_pos) else @token.string_value = string_range(start_pos + 1, current_pos - 1) end @@ -48,30 +47,27 @@ class JSON::Lexer::StringBased < JSON::Lexer end private def current_pos - @pos + @reader.pos end def string_range(start_pos, end_pos) : String - @string.byte_slice(start_pos, end_pos - start_pos) + @reader.string.byte_slice(start_pos, end_pos - start_pos) end def slice_range(start_pos, end_pos) : Bytes - @string.to_slice[start_pos, end_pos - start_pos] + @reader.string.to_slice[start_pos, end_pos - start_pos] end private def next_char_no_column_increment - @pos += 1 - - char = current_char - if char == '\0' && @pos != @string.bytesize + char = @reader.next_char + if char == '\0' && @reader.pos != @reader.string.bytesize unexpected_char end - char end private def current_char - @string.to_unsafe[@pos].chr + @reader.current_char end private def number_start diff --git a/src/json/token.cr b/src/json/token.cr index f1862ce676f5..436709aec233 100644 --- a/src/json/token.cr +++ b/src/json/token.cr @@ -19,7 +19,7 @@ class JSON::Token property string_value : String def int_value : Int64 - @int_value || raw_value.to_i64 + raw_value.to_i64 rescue exc : ArgumentError raise ParseException.new(exc.message, line_number, column_number) end @@ -32,8 +32,7 @@ class JSON::Token property line_number : Int32 property column_number : Int32 - setter raw_value : String - setter int_value : Int64? + property raw_value : String def initialize @kind = :EOF @@ -41,16 +40,6 @@ class JSON::Token @column_number = 0 @string_value = "" @raw_value = "" - @int_value = nil - end - - def raw_value - case @kind - when .int? - @int_value.try(&.to_s) || @raw_value - else - @raw_value - end end def to_s(io : IO) : Nil @@ -62,11 +51,7 @@ class JSON::Token when .true? io << "true" when .int? - if int_value = @int_value - int_value.to_s(io) - else - raw_value.to_s(io) - end + raw_value.to_s(io) when .float? raw_value.to_s(io) when .string? From 0cef61e514267c265eb86f85ab250f85819e23c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 12 Jul 2024 14:42:47 +0200 Subject: [PATCH 1237/1551] Changelog for 1.13.1 (#14806) --- CHANGELOG.md | 18 ++++++++++++++++++ src/SOURCE_DATE_EPOCH | 2 +- src/VERSION | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a7fc10f85f..382f76969ec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [1.13.1] (2024-07-12) + +[1.13.1]: https://github.com/crystal-lang/crystal/releases/1.13.1 + +### Bugfixes + +#### stdlib + +- *(serialization)* **[regression]** Revert "Optimize JSON parsing a bit" ([#14804], thanks @straight-shoota) + +[#14804]: https://github.com/crystal-lang/crystal/pull/14804 + +### Infrastructure + +- Changelog for 1.13.1 ([#14806], thanks @straight-shoota) + +[#14806]: https://github.com/crystal-lang/crystal/pull/14806 + ## [1.13.0] (2024-07-09) [1.13.0]: https://github.com/crystal-lang/crystal/releases/1.13.0 diff --git a/src/SOURCE_DATE_EPOCH b/src/SOURCE_DATE_EPOCH index ae6e55c235fe..efabb39ec223 100644 --- a/src/SOURCE_DATE_EPOCH +++ b/src/SOURCE_DATE_EPOCH @@ -1 +1 @@ -1720483200 +1720742400 diff --git a/src/VERSION b/src/VERSION index feaae22bac7e..b50dd27dd92e 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.13.0 +1.13.1 From 670543afc0e29ee4be88c654af1818ef1e3302b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 22 Jul 2024 16:59:47 +0200 Subject: [PATCH 1238/1551] [CI] Update LLVM 18 for `wasm32-test` (#14821) --- .github/workflows/wasm32.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 8c14f476acfb..77285a60ebd6 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -22,14 +22,14 @@ jobs: with: wasmtime-version: "2.0.0" - - name: Install LLVM 13 + - name: Install LLVM run: | apt-get update apt-get install -y curl lsb-release wget software-properties-common gnupg curl -O https://apt.llvm.org/llvm.sh chmod +x llvm.sh - ./llvm.sh 13 - ln -s $(which wasm-ld-13) /usr/bin/wasm-ld + ./llvm.sh 18 + ln -s $(which wasm-ld-18) /usr/bin/wasm-ld - name: Download wasm32 libs run: | From 5b88c6860fcdbbdfba12af8b2b582ebc36da20d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 23 Jul 2024 20:52:34 +0200 Subject: [PATCH 1239/1551] Update github runner `macos-14` --- .github/workflows/macos.yml | 2 +- shell.nix | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 7f27b3cc9c14..64ec39dfc4dc 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -12,7 +12,7 @@ env: jobs: x86_64-darwin-test: - runs-on: macos-13 + runs-on: macos-14 steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/shell.nix b/shell.nix index 92f405ad3755..447302f14867 100644 --- a/shell.nix +++ b/shell.nix @@ -90,6 +90,4 @@ pkgs.stdenv.mkDerivation rec { ]; LLVM_CONFIG = "${llvmPackages.libllvm.dev}/bin/llvm-config"; - - MACOSX_DEPLOYMENT_TARGET = "10.11"; } From 6f2e1087d9297770062ac5a3013c464818b57561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 23 Jul 2024 20:54:04 +0200 Subject: [PATCH 1240/1551] Revert "Update github runner `macos-14`" This reverts commit 5b88c6860fcdbbdfba12af8b2b582ebc36da20d7. --- .github/workflows/macos.yml | 2 +- shell.nix | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 64ec39dfc4dc..7f27b3cc9c14 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -12,7 +12,7 @@ env: jobs: x86_64-darwin-test: - runs-on: macos-14 + runs-on: macos-13 steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/shell.nix b/shell.nix index 447302f14867..92f405ad3755 100644 --- a/shell.nix +++ b/shell.nix @@ -90,4 +90,6 @@ pkgs.stdenv.mkDerivation rec { ]; LLVM_CONFIG = "${llvmPackages.libllvm.dev}/bin/llvm-config"; + + MACOSX_DEPLOYMENT_TARGET = "10.11"; } From 405f313e071aaaa35c824632dfdbfc8fdc2a658b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 25 Jul 2024 15:28:45 +0200 Subject: [PATCH 1241/1551] [CI] Pin package repos for OpenSSL packages (#14831) This is a necessary preparatory step to update the docker image to Crystal 1.13.1 (#14810). That image is based on Alpine 3.20 which has neither OpenSSL 3.0 nor 1.1.1 in its repository. --- .github/workflows/openssl.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index bd9f3944ba67..9b7599c719a4 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -14,12 +14,12 @@ jobs: steps: - name: Download Crystal source uses: actions/checkout@v4 - - name: Uninstall openssl 1.1 - run: apk del openssl-dev + - name: Uninstall openssl + run: apk del openssl-dev libxml2-static - name: Upgrade alpine-keys run: apk upgrade alpine-keys - name: Install openssl 3.0 - run: apk add "openssl-dev=~3.0" + run: apk add "openssl-dev=~3.0" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.17/main - name: Check LibSSL version run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs @@ -34,7 +34,7 @@ jobs: - name: Uninstall openssl run: apk del openssl-dev - name: Install openssl 1.1.1 - run: apk add "openssl1.1-compat-dev=~1.1.1" + run: apk add "openssl1.1-compat-dev=~1.1.1" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.18/community - name: Check LibSSL version run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs From 17685623383d959dd7732fe7b351210acda84346 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 5 Aug 2024 16:34:08 +0800 Subject: [PATCH 1242/1551] Add docs about `Pointer`'s alignment requirement (#14853) --- src/primitives.cr | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/primitives.cr b/src/primitives.cr index a3594b4543d9..9383ba642165 100644 --- a/src/primitives.cr +++ b/src/primitives.cr @@ -237,6 +237,20 @@ struct Pointer(T) # ptr.value = 42 # ptr.value # => 42 # ``` + # + # WARNING: The pointer must be appropriately aligned, i.e. `address` must be + # a multiple of `alignof(T)`. It is undefined behavior to load from a + # misaligned pointer. Such reads should instead be done via a cast to + # `Pointer(UInt8)`, which is guaranteed to have byte alignment: + # + # ``` + # # raises SIGSEGV on X86 if `ptr` is misaligned + # x = ptr.as(UInt128*).value + # + # # okay, `ptr` can have any alignment + # x = uninitialized UInt128 + # ptr.as(UInt8*).copy_to(pointerof(x).as(UInt8*), sizeof(typeof(x))) + # ``` @[Primitive(:pointer_get)] def value : T end @@ -248,6 +262,20 @@ struct Pointer(T) # ptr.value = 42 # ptr.value # => 42 # ``` + # + # WARNING: The pointer must be appropriately aligned, i.e. `address` must be + # a multiple of `alignof(T)`. It is undefined behavior to store to a + # misaligned pointer. Such writes should instead be done via a cast to + # `Pointer(UInt8)`, which is guaranteed to have byte alignment: + # + # ``` + # # raises SIGSEGV on X86 if `ptr` is misaligned + # x = 123_u128 + # ptr.as(UInt128*).value = x + # + # # okay, `ptr` can have any alignment + # ptr.as(UInt8*).copy_from(pointerof(x).as(UInt8*), sizeof(typeof(x))) + # ``` @[Primitive(:pointer_set)] def value=(value : T) end From 43e4f1d8a2d637f9e45e832a1a3764ff59cb105b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 5 Aug 2024 18:27:22 +0800 Subject: [PATCH 1243/1551] Fix explicitly clear deleted `Hash::Entry` (#14862) --- src/hash.cr | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/hash.cr b/src/hash.cr index 8d48e1cd8c08..cfa556f921ed 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -872,7 +872,8 @@ class Hash(K, V) # Marks an entry in `@entries` at `index` as deleted # *without* modifying any counters (`@size` and `@deleted_count`). private def delete_entry(index) : Nil - set_entry(index, Entry(K, V).deleted) + # sets `Entry#@hash` to 0 and removes stale references to key and value + (@entries + index).clear end # Marks an entry in `@entries` at `index` as deleted @@ -2154,12 +2155,6 @@ class Hash(K, V) def initialize(@hash : UInt32, @key : K, @value : V) end - def self.deleted - key = uninitialized K - value = uninitialized V - new(0_u32, key, value) - end - def deleted? : Bool @hash == 0_u32 end From 30d1b010129f5c1a1b8ebdf53bf20b29492c8796 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 5 Aug 2024 18:34:23 +0800 Subject: [PATCH 1244/1551] Disable `Tuple#to_static_array` spec on AArch64 (#14844) --- spec/std/tuple_spec.cr | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/spec/std/tuple_spec.cr b/spec/std/tuple_spec.cr index 015dc436c659..ec240234d8ed 100644 --- a/spec/std/tuple_spec.cr +++ b/spec/std/tuple_spec.cr @@ -342,18 +342,23 @@ describe "Tuple" do ary.size.should eq(0) end - it "#to_static_array" do - ary = {1, 'a', true}.to_static_array - ary.should be_a(StaticArray(Int32 | Char | Bool, 3)) - ary.should eq(StaticArray[1, 'a', true]) - ary.size.should eq(3) - - ary = Tuple.new.to_static_array - ary.should be_a(StaticArray(NoReturn, 0)) - ary.size.should eq(0) - - ary = Tuple(String | Int32).new(1).to_static_array - ary.should be_a(StaticArray(String | Int32, 1)) - ary.should eq StaticArray[1.as(String | Int32)] - end + # Tuple#to_static_array don't compile on aarch64-darwin and + # aarch64-linux-musl due to a codegen error caused by LLVM < 13.0.0. + # See https://github.com/crystal-lang/crystal/issues/11358 for details. + {% unless compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 && flag?(:aarch64) && (flag?(:musl) || flag?(:darwin) || flag?(:android)) %} + it "#to_static_array" do + ary = {1, 'a', true}.to_static_array + ary.should be_a(StaticArray(Int32 | Char | Bool, 3)) + ary.should eq(StaticArray[1, 'a', true]) + ary.size.should eq(3) + + ary = Tuple.new.to_static_array + ary.should be_a(StaticArray(NoReturn, 0)) + ary.size.should eq(0) + + ary = Tuple(String | Int32).new(1).to_static_array + ary.should be_a(StaticArray(String | Int32, 1)) + ary.should eq StaticArray[1.as(String | Int32)] + end + {% end %} end From 3126ecce5041ceedf20c19079aae6a650f55b68f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 5 Aug 2024 18:35:13 +0800 Subject: [PATCH 1245/1551] Add `@[Experimental]` to `LLVM::DIBuilder` (#14854) --- src/llvm/di_builder.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/llvm/di_builder.cr b/src/llvm/di_builder.cr index 98676431de16..37be65ef8cf8 100644 --- a/src/llvm/di_builder.cr +++ b/src/llvm/di_builder.cr @@ -1,5 +1,6 @@ require "./lib_llvm" +@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")] struct LLVM::DIBuilder private DW_TAG_structure_type = 19 From 01491de5681c853c68055c70ed684500c30c69ef Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 6 Aug 2024 15:49:49 +0800 Subject: [PATCH 1246/1551] Reword `Pointer#memcmp`'s documentation (#14818) --- src/pointer.cr | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/pointer.cr b/src/pointer.cr index 2479ef0bbb77..c3ebbf3e56fc 100644 --- a/src/pointer.cr +++ b/src/pointer.cr @@ -272,18 +272,20 @@ struct Pointer(T) self end - # Compares *count* elements from this pointer and *other*, byte by byte. + # Compares *count* elements from this pointer and *other*, lexicographically. # - # Returns 0 if both pointers point to the same sequence of *count* bytes. Otherwise - # returns the difference between the first two differing bytes (treated as UInt8). + # Returns 0 if both pointers point to the same sequence of *count* bytes. + # Otherwise, if the first two differing bytes (treated as UInt8) from `self` + # and *other* are `x` and `y` respectively, returns a negative value if + # `x < y`, or a positive value if `x > y`. # # ``` # ptr1 = Pointer.malloc(4) { |i| i + 1 } # [1, 2, 3, 4] # ptr2 = Pointer.malloc(4) { |i| i + 11 } # [11, 12, 13, 14] # - # ptr1.memcmp(ptr2, 4) # => -10 - # ptr2.memcmp(ptr1, 4) # => 10 - # ptr1.memcmp(ptr1, 4) # => 0 + # ptr1.memcmp(ptr2, 4) < 0 # => true + # ptr2.memcmp(ptr1, 4) > 0 # => true + # ptr1.memcmp(ptr1, 4) == 0 # => true # ``` def memcmp(other : Pointer(T), count : Int) : Int32 LibC.memcmp(self.as(Void*), (other.as(Void*)), (count * sizeof(T))) From 2fb0bc2495422870d68a8103e1519f498e7cf3e4 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 6 Aug 2024 15:50:50 +0800 Subject: [PATCH 1247/1551] Fix misaligned stack access in the interpreter (#14843) --- src/compiler/crystal/interpreter/interpreter.cr | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index eca73ecae6bc..aa90d83f413f 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -999,14 +999,15 @@ class Crystal::Repl::Interpreter private macro stack_pop(t) %aligned_size = align(sizeof({{t}})) - %value = (stack - %aligned_size).as({{t}}*).value + %value = uninitialized {{t}} + (stack - %aligned_size).copy_to(pointerof(%value).as(UInt8*), sizeof({{t}})) stack_shrink_by(%aligned_size) %value end private macro stack_push(value) %temp = {{value}} - stack.as(Pointer(typeof({{value}}))).value = %temp + stack.copy_from(pointerof(%temp).as(UInt8*), sizeof(typeof({{value}}))) %size = sizeof(typeof({{value}})) %aligned_size = align(%size) From e6581e314b275d74641cd46abd1537fe371ea039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 6 Aug 2024 12:44:12 +0200 Subject: [PATCH 1248/1551] Update previous Crystal release 1.13.1 (#14810) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 8 ++++---- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 6 +++--- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win_build_portable.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ src/SOURCE_DATE_EPOCH | 1 - src/VERSION | 2 +- 12 files changed, 24 insertions(+), 25 deletions(-) delete mode 100644 src/SOURCE_DATE_EPOCH diff --git a/.circleci/config.yml b/.circleci/config.yml index 190695224419..2999c861fbcb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.12.2/crystal-1.12.2-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 76a4a7cfd13d..8828efe88a10 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.12.2-build + image: crystallang/crystal:1.13.1-build name: "Test Interpreter" steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.12.2-build + image: crystallang/crystal:1.13.1-build name: Build interpreter steps: - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.12.2-build + image: crystallang/crystal:1.13.1-build strategy: matrix: part: [0, 1, 2, 3] @@ -67,7 +67,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.12.2-build + image: crystallang/crystal:1.13.1-build name: "Test primitives_spec with interpreter" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index fe76688fbe2a..32761dbb8c75 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2] + crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2, 1.13.1] flags: [""] include: # libffi is only available starting from the 1.2.2 build images diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index e1744bc2c6b5..767d401138e7 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -58,7 +58,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.12.2" + crystal: "1.13.1" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 9b7599c719a4..46d440d1f6e7 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: openssl3: runs-on: ubuntu-latest name: "OpenSSL 3.0" - container: crystallang/crystal:1.12.2-alpine + container: crystallang/crystal:1.13.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -27,7 +27,7 @@ jobs: openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.12.2-alpine + container: crystallang/crystal:1.13.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: libressl34: runs-on: ubuntu-latest name: "LibreSSL 3.4" - container: crystallang/crystal:1.12.2-alpine + container: crystallang/crystal:1.13.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index 763f889c1c06..8816c31dc9b0 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.12.2-alpine + container: crystallang/crystal:1.13.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.12.2-alpine + container: crystallang/crystal:1.13.1-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 77285a60ebd6..2b446ec6726f 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.12.2-build + container: crystallang/crystal:1.13.1-build steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index e2f0d14ee3ba..6e36608d608d 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -24,7 +24,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.12.2" + crystal: "1.13.1" - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index 3f1e588393ad..74a1f228ceff 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.12.2/crystal-1.12.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.12.2-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.13.1-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.12.2}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.13.1}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 92f405ad3755..259cecf9b304 100644 --- a/shell.nix +++ b/shell.nix @@ -53,18 +53,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.12.2/crystal-1.12.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:017lqbbavvhi34d3y3s8rqcpqwxp45apvzanlpaq7izhxhyb4h5s"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:0wrfv7bgqwfi76p9s48zg4j953kvjsj5cv59slhhc62lllx926zm"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.12.2/crystal-1.12.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:017lqbbavvhi34d3y3s8rqcpqwxp45apvzanlpaq7izhxhyb4h5s"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-darwin-universal.tar.gz"; + sha256 = "sha256:0wrfv7bgqwfi76p9s48zg4j953kvjsj5cv59slhhc62lllx926zm"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.12.2/crystal-1.12.2-1-linux-x86_64.tar.gz"; - sha256 = "sha256:0p1jxpdn9vc52qvf25x25a699l2hw4rmfz5snyylq84wrqpxbfvb"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-linux-x86_64.tar.gz"; + sha256 = "sha256:1dghcv8qgjcbq1r0d2saa21xzp4h7pkan6fnmn6hpickib678g7x"; }; }.${pkgs.stdenv.system}); diff --git a/src/SOURCE_DATE_EPOCH b/src/SOURCE_DATE_EPOCH deleted file mode 100644 index efabb39ec223..000000000000 --- a/src/SOURCE_DATE_EPOCH +++ /dev/null @@ -1 +0,0 @@ -1720742400 diff --git a/src/VERSION b/src/VERSION index b50dd27dd92e..2f2e08cfa3bf 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.13.1 +1.14.0-dev From 108285f907a8b62e5d083daadee81c7cf224e9b8 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Tue, 6 Aug 2024 16:46:58 -0400 Subject: [PATCH 1249/1551] Fix doc comment above annotation with macro expansion (#14849) Makes sure that a comment followed by an annotation and a macro call is properly recognized as a doc comment for the expanded macro code. --- spec/compiler/semantic/doc_spec.cr | 42 +++++++++++++++++++ .../crystal/semantic/semantic_visitor.cr | 6 +++ 2 files changed, 48 insertions(+) diff --git a/spec/compiler/semantic/doc_spec.cr b/spec/compiler/semantic/doc_spec.cr index 3338f920a938..a2f80bcc046d 100644 --- a/spec/compiler/semantic/doc_spec.cr +++ b/spec/compiler/semantic/doc_spec.cr @@ -632,5 +632,47 @@ describe "Semantic: doc" do type = program.lookup_macros("foo").as(Array(Macro)).first type.doc.should eq("Some description") end + + it "attached to macro call" do + result = semantic %( + annotation Ann + end + + macro gen_type + class Foo; end + end + + # Some description + @[Ann] + gen_type + ), wants_doc: true + program = result.program + type = program.types["Foo"] + type.doc.should eq("Some description") + end + + it "attached to macro call that produces multiple types" do + result = semantic %( + annotation Ann + end + + class Foo + macro getter(decl) + @{{decl.var.id}} : {{decl.type.id}} + + def {{decl.var.id}} : {{decl.type.id}} + @{{decl.var.id}} + end + end + + # Some description + @[Ann] + getter name : String? + end + ), wants_doc: true + program = result.program + a_def = program.types["Foo"].lookup_defs("name").first + a_def.doc.should eq("Some description") + end end end diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index b85fdba37109..ada6d392f626 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -364,6 +364,8 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor visibility: visibility, ) + node.doc ||= annotations_doc @annotations + if node_doc = node.doc generated_nodes.accept PropagateDocVisitor.new(node_doc) end @@ -525,6 +527,10 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor end end + private def annotations_doc(annotations) + annotations.try(&.first?).try &.doc + end + def check_class_var_annotations thread_local = false process_annotations(@annotations) do |annotation_type, ann| From b06aad89afb14f08dd7428cd29cdfdea43b25c9c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 7 Aug 2024 04:48:06 +0800 Subject: [PATCH 1250/1551] Fix `ReferenceStorage(T)` atomic if `T` has no inner pointers (#14845) It turns out the fix in #14730 made all `ReferenceStorage` objects non-atomic; `Crystal::ReferenceStorageType#reference_type` returns a reference type, whose `#has_inner_pointers?` always returns true since the reference itself is a pointer. This PR fixes that again by adding a special case for `ReferenceStorage`. --- src/compiler/crystal/codegen/types.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/codegen/types.cr b/src/compiler/crystal/codegen/types.cr index 470fe7424dcd..7ce1640bb5e7 100644 --- a/src/compiler/crystal/codegen/types.cr +++ b/src/compiler/crystal/codegen/types.cr @@ -70,7 +70,7 @@ module Crystal when NamedTupleInstanceType self.entries.any? &.type.has_inner_pointers? when ReferenceStorageType - self.reference_type.has_inner_pointers? + self.reference_type.all_instance_vars.each_value.any? &.type.has_inner_pointers? when PrimitiveType false when EnumType From b954dd74c445f06bab1fe0bf97fdcd865e0623d4 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 6 Aug 2024 22:49:00 +0200 Subject: [PATCH 1251/1551] Compiler: refactor codegen (#14760) Refactors `Crystal::Compiler`: 1. extracts `#sequential_codegen`, `#parallel_codegen` and `#fork_codegen` methods; 2. merges `#codegen_many_units` into `#codegen` directly; 3. stops collecting reused units: `#fork_codegen` now updates `CompilationUnit#reused_compilation_unit?` state as reported by the forked processes, and `#print_codegen_stats` now counts & filters the reused units. Prerequisite for #14748 that will introduce `#mt_codegen`. --- src/compiler/crystal/compiler.cr | 203 +++++++++++++++---------------- 1 file changed, 98 insertions(+), 105 deletions(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index b30b184e1023..38880ee9ed64 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -208,11 +208,11 @@ module Crystal program = new_program(source) node = parse program, source node = program.semantic node, cleanup: !no_cleanup? - result = codegen program, node, source, output_filename unless @no_codegen + units = codegen program, node, source, output_filename unless @no_codegen @progress_tracker.clear print_macro_run_stats(program) - print_codegen_stats(result) + print_codegen_stats(units) Result.new program, node end @@ -331,7 +331,7 @@ module Crystal if @cross_compile cross_compile program, units, output_filename else - result = with_file_lock(output_dir) do + units = with_file_lock(output_dir) do codegen program, units, output_filename, output_dir end @@ -346,7 +346,7 @@ module Crystal CacheDir.instance.cleanup if @cleanup - result + units end private def with_file_lock(output_dir, &) @@ -469,20 +469,21 @@ module Crystal private def codegen(program, units : Array(CompilationUnit), output_filename, output_dir) object_names = units.map &.object_filename - target_triple = target_machine.triple - reused = [] of String @progress_tracker.stage("Codegen (bc+obj)") do @progress_tracker.stage_progress_total = units.size - if units.size == 1 - first_unit = units.first - first_unit.compile - reused << first_unit.name if first_unit.reused_previous_compilation? - first_unit.emit(@emit_targets, emit_base_filename || output_filename) + n_threads = @n_threads.clamp(1..units.size) + + if n_threads == 1 + sequential_codegen(units) else - reused = codegen_many_units(program, units, target_triple) + parallel_codegen(units, n_threads) + end + + if units.size == 1 + units.first.emit(@emit_targets, emit_base_filename || output_filename) end end @@ -499,115 +500,107 @@ module Crystal end end - {units, reused} + units end - private def codegen_many_units(program, units, target_triple) - all_reused = [] of String - - # Don't start more processes than compilation units - n_threads = @n_threads.clamp(1..units.size) - - # If threads is 1 we can avoid fork/spawn/channels altogether. This is - # particularly useful for CI because there forking eventually leads to - # "out of memory" errors. - if n_threads == 1 - units.each do |unit| - unit.compile - @progress_tracker.stage_progress += 1 - end - if @progress_tracker.stats? - units.each do |unit| - all_reused << unit.name && unit.reused_previous_compilation? - end - end - return all_reused + private def sequential_codegen(units) + units.each do |unit| + unit.compile + @progress_tracker.stage_progress += 1 end + end - {% if !LibC.has_method?("fork") %} - raise "Cannot fork compiler. `Crystal::System::Process.fork` is not implemented on this system." - {% elsif flag?(:preview_mt) %} - raise "Cannot fork compiler in multithread mode" + private def parallel_codegen(units, n_threads) + {% if flag?(:preview_mt) %} + raise "Cannot fork compiler in multithread mode." + {% elsif LibC.has_method?("fork") %} + fork_codegen(units, n_threads) {% else %} - workers = fork_workers(n_threads) do |input, output| - while i = input.gets(chomp: true).presence - unit = units[i.to_i] - unit.compile - result = {name: unit.name, reused: unit.reused_previous_compilation?} - output.puts result.to_json - end - rescue ex - result = {exception: {name: ex.class.name, message: ex.message, backtrace: ex.backtrace}} + raise "Cannot fork compiler. `Crystal::System::Process.fork` is not implemented on this system." + {% end %} + end + + private def fork_codegen(units, n_threads) + workers = fork_workers(n_threads) do |input, output| + while i = input.gets(chomp: true).presence + unit = units[i.to_i] + unit.compile + result = {name: unit.name, reused: unit.reused_previous_compilation?} output.puts result.to_json end + rescue ex + result = {exception: {name: ex.class.name, message: ex.message, backtrace: ex.backtrace}} + output.puts result.to_json + end - overqueue = 1 - indexes = Atomic(Int32).new(0) - channel = Channel(String).new(n_threads) - completed = Channel(Nil).new(n_threads) + overqueue = 1 + indexes = Atomic(Int32).new(0) + channel = Channel(String).new(n_threads) + completed = Channel(Nil).new(n_threads) - workers.each do |pid, input, output| - spawn do - overqueued = 0 + workers.each do |pid, input, output| + spawn do + overqueued = 0 - overqueue.times do - if (index = indexes.add(1)) < units.size - input.puts index - overqueued += 1 - end + overqueue.times do + if (index = indexes.add(1)) < units.size + input.puts index + overqueued += 1 end + end - while (index = indexes.add(1)) < units.size - input.puts index + while (index = indexes.add(1)) < units.size + input.puts index - if response = output.gets(chomp: true) - channel.send response - else - Crystal::System.print_error "\nBUG: a codegen process failed\n" - exit 1 - end + if response = output.gets(chomp: true) + channel.send response + else + Crystal::System.print_error "\nBUG: a codegen process failed\n" + exit 1 end + end - overqueued.times do - if response = output.gets(chomp: true) - channel.send response - else - Crystal::System.print_error "\nBUG: a codegen process failed\n" - exit 1 - end + overqueued.times do + if response = output.gets(chomp: true) + channel.send response + else + Crystal::System.print_error "\nBUG: a codegen process failed\n" + exit 1 end + end - input << '\n' - input.close - output.close + input << '\n' + input.close + output.close - Process.new(pid).wait - completed.send(nil) - end + Process.new(pid).wait + completed.send(nil) end + end - spawn do - n_threads.times { completed.receive } - channel.close - end + spawn do + n_threads.times { completed.receive } + channel.close + end - while response = channel.receive? - result = JSON.parse(response) + while response = channel.receive? + result = JSON.parse(response) - if ex = result["exception"]? - Crystal::System.print_error "\nBUG: a codegen process failed: %s (%s)\n", ex["message"].as_s, ex["name"].as_s - ex["backtrace"].as_a?.try(&.each { |frame| Crystal::System.print_error " from %s\n", frame }) - exit 1 - end + if ex = result["exception"]? + Crystal::System.print_error "\nBUG: a codegen process failed: %s (%s)\n", ex["message"].as_s, ex["name"].as_s + ex["backtrace"].as_a?.try(&.each { |frame| Crystal::System.print_error " from %s\n", frame }) + exit 1 + end - if @progress_tracker.stats? - all_reused << result["name"].as_s if result["reused"].as_bool + if @progress_tracker.stats? + if result["reused"].as_bool + name = result["name"].as_s + unit = units.find { |unit| unit.name == name }.not_nil! + unit.reused_previous_compilation = true end - @progress_tracker.stage_progress += 1 end - - all_reused - {% end %} + @progress_tracker.stage_progress += 1 + end end private def fork_workers(n_threads) @@ -659,24 +652,24 @@ module Crystal end end - private def print_codegen_stats(result) + private def print_codegen_stats(units) return unless @progress_tracker.stats? - return unless result + return unless units - units, reused = result + reused = units.count(&.reused_previous_compilation?) puts puts "Codegen (bc+obj):" - if units.size == reused.size + if units.size == reused puts " - all previous .o files were reused" - elsif reused.size == 0 + elsif reused == 0 puts " - no previous .o files were reused" else - puts " - #{reused.size}/#{units.size} .o files were reused" - not_reused = units.reject { |u| reused.includes?(u.name) } + puts " - #{reused}/#{units.size} .o files were reused" puts puts "These modules were not reused:" - not_reused.each do |unit| + units.each do |unit| + next if unit.reused_previous_compilation? puts " - #{unit.original_name} (#{unit.name}.bc)" end end @@ -824,7 +817,7 @@ module Crystal getter name getter original_name getter llvm_mod - getter? reused_previous_compilation = false + property? reused_previous_compilation = false getter object_extension : String def initialize(@compiler : Compiler, program : Program, @name : String, From ea48ee988efa24298145fc23996ac394c0feecde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 7 Aug 2024 16:34:23 +0200 Subject: [PATCH 1252/1551] Upgrade XCode 15.4.0 (#14794) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2999c861fbcb..9118ce51ec2c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -81,7 +81,7 @@ jobs: test_darwin: macos: - xcode: 13.4.1 + xcode: 15.4.0 environment: <<: *env TRAVIS_OS_NAME: osx From bddb53fb5177c55706ef88cd3e045bebec92af40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 7 Aug 2024 16:35:13 +0200 Subject: [PATCH 1253/1551] Refactor cancellation of `IOCP::OverlappedOperation` (#14754) When an overlapped operation gets cancelled, we still need to wait for completion of the operation (with status `ERROR_OPERATION_ABORTED`) before it can be freed. Previously we stored a reference to cancelled operations in a linked list and removed them when complete. This allows continuing executing the fiber directly after the cancellation is triggered, but it's also quite a bit of overhead. Also it makes it impossible to allocate operations on the stack. Cancellation is triggered when an operation times out. The change in this patch is that after a timeout the fiber is suspended again, expecting completion via the event loop. Then the operation can be freed. * Removes the `CANCELLED` state. It's no longer necessary, we only need to distinguish whether a fiber was woken up due to timeout or completion. A follow-up will further simplify the state handling. * Replace special timeout event and fiber scheduling logic with generic `sleep` and `suspend` * Drops `@@cancelled` linked list. * Drops the workaround from https://github.com/crystal-lang/crystal/pull/14724#issuecomment-2187401075 --- src/crystal/system/win32/iocp.cr | 88 ++++++++++++------------------ src/crystal/system/win32/socket.cr | 10 +--- 2 files changed, 39 insertions(+), 59 deletions(-) diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr index ba0f11eb2af5..add5a29c2814 100644 --- a/src/crystal/system/win32/iocp.cr +++ b/src/crystal/system/win32/iocp.cr @@ -65,15 +65,11 @@ module Crystal::IOCP enum State STARTED DONE - CANCELLED end @overlapped = LibC::OVERLAPPED.new @fiber = Fiber.current @state : State = :started - property next : OverlappedOperation? - property previous : OverlappedOperation? - @@canceled = Thread::LinkedList(OverlappedOperation).new def initialize(@handle : LibC::HANDLE) end @@ -83,12 +79,9 @@ module Crystal::IOCP end def self.run(handle, &) - operation = OverlappedOperation.new(handle) - begin - yield operation - ensure - operation.done - end + operation_storage = uninitialized ReferenceStorage(OverlappedOperation) + operation = OverlappedOperation.unsafe_construct(pointerof(operation_storage), handle) + yield operation end def self.unbox(overlapped : LibC::OVERLAPPED*) @@ -103,8 +96,6 @@ module Crystal::IOCP def wait_for_result(timeout, &) wait_for_completion(timeout) - raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started? - result = LibC.GetOverlappedResult(@handle, self, out bytes, 0) if result.zero? error = WinError.value @@ -118,11 +109,7 @@ module Crystal::IOCP def wait_for_wsa_result(timeout, &) wait_for_completion(timeout) - wsa_result { |error| yield error } - end - def wsa_result(&) - raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started? flags = 0_u32 result = LibC.WSAGetOverlappedResult(LibC::SOCKET.new(@handle.address), self, out bytes, false, pointerof(flags)) if result.zero? @@ -136,49 +123,48 @@ module Crystal::IOCP end protected def schedule(&) - case @state - when .started? - yield @fiber - done! - when .cancelled? - @@canceled.delete(self) - else - raise Exception.new("Invalid state #{@state}") - end - end - - protected def done - case @state - when .started? - # https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-cancelioex - # > The application must not free or reuse the OVERLAPPED structure - # associated with the canceled I/O operations until they have completed - if LibC.CancelIoEx(@handle, self) != 0 - @state = :cancelled - @@canceled.push(self) # to increase lifetime - end - end + done! + yield @fiber end def done! + @fiber.cancel_timeout @state = :done end + def try_cancel : Bool + # Microsoft documentation: + # The application must not free or reuse the OVERLAPPED structure + # associated with the canceled I/O operations until they have completed + # (this does not apply to asynchronous operations that finished + # synchronously, as nothing would be queued to the IOCP) + ret = LibC.CancelIoEx(@handle, self) + if ret.zero? + case error = WinError.value + when .error_not_found? + # Operation has already completed, do nothing + return false + else + raise RuntimeError.from_os_error("CancelIOEx", os_error: error) + end + end + true + end + def wait_for_completion(timeout) if timeout - timeout_event = Crystal::IOCP::Event.new(Fiber.current) - timeout_event.add(timeout) + sleep timeout else - timeout_event = Crystal::IOCP::Event.new(Fiber.current, Time::Span::MAX) + Fiber.suspend end - # memoize event loop to make sure that we still target the same instance - # after wakeup (guaranteed by current MT model but let's be future proof) - event_loop = Crystal::EventLoop.current - event_loop.enqueue(timeout_event) - - Fiber.suspend - event_loop.dequeue(timeout_event) + unless @state.done? + if try_cancel + # Wait for cancellation to complete. We must not free the operation + # until it's completed. + Fiber.suspend + end + end end end @@ -200,13 +186,12 @@ module Crystal::IOCP raise IO::Error.from_os_error(method, error, target: target) end else - operation.done! return value end operation.wait_for_result(timeout) do |error| case error - when .error_io_incomplete? + when .error_io_incomplete?, .error_operation_aborted? raise IO::TimeoutError.new("#{method} timed out") when .error_handle_eof? return 0_u32 @@ -230,13 +215,12 @@ module Crystal::IOCP raise IO::Error.from_os_error(method, error, target: target) end else - operation.done! return value end operation.wait_for_wsa_result(timeout) do |error| case error - when .wsa_io_incomplete? + when .wsa_io_incomplete?, .error_operation_aborted? raise IO::TimeoutError.new("#{method} timed out") when .wsaeconnreset? return 0_u32 unless connreset_is_error diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 6a5d44ab5133..17e4ca875dbb 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -142,7 +142,6 @@ module Crystal::System::Socket return ::Socket::Error.from_os_error("ConnectEx", error) end else - operation.done! return nil end @@ -204,18 +203,15 @@ module Crystal::System::Socket return false end else - operation.done! return true end - unless operation.wait_for_completion(read_timeout) - raise IO::TimeoutError.new("#{method} timed out") - end - - operation.wsa_result do |error| + operation.wait_for_wsa_result(read_timeout) do |error| case error when .wsa_io_incomplete?, .wsaenotsock? return false + when .error_operation_aborted? + raise IO::TimeoutError.new("#{method} timed out") end end From 8f26137180b0f44257b88e71bab8b91ebbdffd45 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 7 Aug 2024 16:35:54 +0200 Subject: [PATCH 1254/1551] Stop & start the world (undocumented API) (#14729) Add `GC.stop_world` and `GC.start_world` methods to be able to stop and restart the world at will from within Crystal. - gc/boehm: delegates to `GC_stop_world_external` and `GC_start_world_external`; - gc/none: implements its own mechanism (tested on UNIX & Windows). My use case is a [perf-tools](https://github.com/crystal-lang/perf-tools) feature for [RFC 2](https://github.com/crystal-lang/rfcs/pull/2) that must stop the world to print out runtime information of each ExecutionContext with their schedulers and fibers. See https://github.com/crystal-lang/perf-tools/pull/18 --- src/crystal/system/thread.cr | 36 +++++++++ src/crystal/system/unix/pthread.cr | 75 +++++++++++++++++++ src/crystal/system/wasi/thread.cr | 15 ++++ src/crystal/system/win32/thread.cr | 28 +++++++ src/gc/boehm.cr | 13 ++++ src/gc/none.cr | 54 +++++++++++++ src/lib_c/aarch64-android/c/signal.cr | 2 + src/lib_c/aarch64-darwin/c/signal.cr | 2 + src/lib_c/aarch64-linux-gnu/c/signal.cr | 2 + src/lib_c/aarch64-linux-musl/c/signal.cr | 2 + src/lib_c/arm-linux-gnueabihf/c/signal.cr | 2 + src/lib_c/i386-linux-gnu/c/signal.cr | 2 + src/lib_c/i386-linux-musl/c/signal.cr | 2 + src/lib_c/x86_64-darwin/c/signal.cr | 2 + src/lib_c/x86_64-dragonfly/c/signal.cr | 2 + src/lib_c/x86_64-freebsd/c/signal.cr | 54 ++++++------- src/lib_c/x86_64-linux-gnu/c/signal.cr | 2 + src/lib_c/x86_64-linux-musl/c/signal.cr | 2 + src/lib_c/x86_64-netbsd/c/signal.cr | 2 + src/lib_c/x86_64-openbsd/c/signal.cr | 2 + src/lib_c/x86_64-solaris/c/signal.cr | 2 + .../c/processthreadsapi.cr | 4 + 22 files changed, 282 insertions(+), 25 deletions(-) diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index d9dc6acf17dc..431708c5cc11 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -23,6 +23,14 @@ module Crystal::System::Thread # private def stack_address : Void* # private def system_name=(String) : String + + # def self.init_suspend_resume : Nil + + # private def system_suspend : Nil + + # private def system_wait_suspended : Nil + + # private def system_resume : Nil end {% if flag?(:wasi) %} @@ -66,6 +74,14 @@ class Thread @@threads.try(&.unsafe_each { |thread| yield thread }) end + def self.lock : Nil + threads.@mutex.lock + end + + def self.unlock : Nil + threads.@mutex.unlock + end + # Creates and starts a new system thread. def initialize(@name : String? = nil, &@func : Thread ->) @system_handle = uninitialized Crystal::System::Thread::Handle @@ -168,6 +184,26 @@ class Thread # Holds the GC thread handler property gc_thread_handler : Void* = Pointer(Void).null + + def suspend : Nil + system_suspend + end + + def wait_suspended : Nil + system_wait_suspended + end + + def resume : Nil + system_resume + end + + def self.stop_world : Nil + GC.stop_world + end + + def self.start_world : Nil + GC.start_world + end end require "./thread_linked_list" diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index d38e52ee012a..b55839ff2784 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -1,5 +1,6 @@ require "c/pthread" require "c/sched" +require "../panic" module Crystal::System::Thread alias Handle = LibC::PthreadT @@ -153,6 +154,80 @@ module Crystal::System::Thread {% end %} name end + + @suspended = Atomic(Bool).new(false) + + def self.init_suspend_resume : Nil + install_sig_suspend_signal_handler + install_sig_resume_signal_handler + end + + private def self.install_sig_suspend_signal_handler + action = LibC::Sigaction.new + action.sa_flags = LibC::SA_SIGINFO + action.sa_sigaction = LibC::SigactionHandlerT.new do |_, _, _| + # notify that the thread has been interrupted + Thread.current_thread.@suspended.set(true) + + # block all signals but SIG_RESUME + mask = LibC::SigsetT.new + LibC.sigfillset(pointerof(mask)) + LibC.sigdelset(pointerof(mask), SIG_RESUME) + + # suspend the thread until it receives the SIG_RESUME signal + LibC.sigsuspend(pointerof(mask)) + end + LibC.sigemptyset(pointerof(action.@sa_mask)) + LibC.sigaction(SIG_SUSPEND, pointerof(action), nil) + end + + private def self.install_sig_resume_signal_handler + action = LibC::Sigaction.new + action.sa_flags = 0 + action.sa_sigaction = LibC::SigactionHandlerT.new do |_, _, _| + # do nothing (a handler is still required to receive the signal) + end + LibC.sigemptyset(pointerof(action.@sa_mask)) + LibC.sigaction(SIG_RESUME, pointerof(action), nil) + end + + private def system_suspend : Nil + @suspended.set(false) + + if LibC.pthread_kill(@system_handle, SIG_SUSPEND) == -1 + System.panic("pthread_kill()", Errno.value) + end + end + + private def system_wait_suspended : Nil + until @suspended.get + Thread.yield_current + end + end + + private def system_resume : Nil + if LibC.pthread_kill(@system_handle, SIG_RESUME) == -1 + System.panic("pthread_kill()", Errno.value) + end + end + + # the suspend/resume signals follow BDWGC + + private SIG_SUSPEND = + {% if flag?(:linux) %} + LibC::SIGPWR + {% elsif LibC.has_constant?(:SIGRTMIN) %} + LibC::SIGRTMIN + 6 + {% else %} + LibC::SIGXFSZ + {% end %} + + private SIG_RESUME = + {% if LibC.has_constant?(:SIGRTMIN) %} + LibC::SIGRTMIN + 5 + {% else %} + LibC::SIGXCPU + {% end %} end # In musl (alpine) the calls to unwind API segfaults diff --git a/src/crystal/system/wasi/thread.cr b/src/crystal/system/wasi/thread.cr index 6f0c0cbe8260..1e8f6957d526 100644 --- a/src/crystal/system/wasi/thread.cr +++ b/src/crystal/system/wasi/thread.cr @@ -38,4 +38,19 @@ module Crystal::System::Thread # TODO: Implement Pointer(Void).null end + + def self.init_suspend_resume : Nil + end + + private def system_suspend : Nil + raise NotImplementedError.new("Crystal::System::Thread.system_suspend") + end + + private def system_wait_suspended : Nil + raise NotImplementedError.new("Crystal::System::Thread.system_wait_suspended") + end + + private def system_resume : Nil + raise NotImplementedError.new("Crystal::System::Thread.system_resume") + end end diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr index ddfe3298b20a..1a4f61a41738 100644 --- a/src/crystal/system/win32/thread.cr +++ b/src/crystal/system/win32/thread.cr @@ -1,5 +1,6 @@ require "c/processthreadsapi" require "c/synchapi" +require "../panic" module Crystal::System::Thread alias Handle = LibC::HANDLE @@ -87,4 +88,31 @@ module Crystal::System::Thread {% end %} name end + + def self.init_suspend_resume : Nil + end + + private def system_suspend : Nil + if LibC.SuspendThread(@system_handle) == -1 + Crystal::System.panic("SuspendThread()", WinError.value) + end + end + + private def system_wait_suspended : Nil + # context must be aligned on 16 bytes but we lack a mean to force the + # alignment on the struct, so we overallocate then realign the pointer: + local = uninitialized UInt8[sizeof(Tuple(LibC::CONTEXT, UInt8[15]))] + thread_context = Pointer(LibC::CONTEXT).new(local.to_unsafe.address &+ 15_u64 & ~15_u64) + thread_context.value.contextFlags = LibC::CONTEXT_FULL + + if LibC.GetThreadContext(@system_handle, thread_context) == -1 + Crystal::System.panic("GetThreadContext()", WinError.value) + end + end + + private def system_resume : Nil + if LibC.ResumeThread(@system_handle) == -1 + Crystal::System.panic("ResumeThread()", WinError.value) + end + end end diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 8ccc1bb7b6e8..0ce6a1366b6d 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -161,6 +161,9 @@ lib LibGC alias WarnProc = LibC::Char*, Word -> fun set_warn_proc = GC_set_warn_proc(WarnProc) $warn_proc = GC_current_warn_proc : WarnProc + + fun stop_world_external = GC_stop_world_external + fun start_world_external = GC_start_world_external end module GC @@ -470,4 +473,14 @@ module GC GC.unlock_write end {% end %} + + # :nodoc: + def self.stop_world : Nil + LibGC.stop_world_external + end + + # :nodoc: + def self.start_world : Nil + LibGC.start_world_external + end end diff --git a/src/gc/none.cr b/src/gc/none.cr index 640e6e8f927d..3943bd265ed9 100644 --- a/src/gc/none.cr +++ b/src/gc/none.cr @@ -5,6 +5,7 @@ require "crystal/tracing" module GC def self.init + Crystal::System::Thread.init_suspend_resume end # :nodoc: @@ -138,4 +139,57 @@ module GC # :nodoc: def self.push_stack(stack_top, stack_bottom) end + + # Stop and start the world. + # + # This isn't a GC-safe stop-the-world implementation (it may allocate objects + # while stopping the world), but the guarantees are enough for the purpose of + # gc_none. It could be GC-safe if Thread::LinkedList(T) became a struct, and + # Thread::Mutex either became a struct or provide low level abstraction + # methods that directly interact with syscalls (without allocating). + # + # Thread safety is guaranteed by the mutex in Thread::LinkedList: either a + # thread is starting and hasn't added itself to the list (it will block until + # it can acquire the lock), or is currently adding itself (the current thread + # will block until it can acquire the lock). + # + # In both cases there can't be a deadlock since we won't suspend another + # thread until it has successfuly added (or removed) itself to (from) the + # linked list and released the lock, and the other thread won't progress until + # it can add (or remove) itself from the list. + # + # Finally, we lock the mutex and keep it locked until we resume the world, so + # any thread waiting on the mutex will only be resumed when the world is + # resumed. + + # :nodoc: + def self.stop_world : Nil + current_thread = Thread.current + + # grab the lock (and keep it until the world is restarted) + Thread.lock + + # tell all threads to stop (async) + Thread.unsafe_each do |thread| + thread.suspend unless thread == current_thread + end + + # wait for all threads to have stopped + Thread.unsafe_each do |thread| + thread.wait_suspended unless thread == current_thread + end + end + + # :nodoc: + def self.start_world : Nil + current_thread = Thread.current + + # tell all threads to resume + Thread.unsafe_each do |thread| + thread.resume unless thread == current_thread + end + + # finally, we can release the lock + Thread.unlock + end end diff --git a/src/lib_c/aarch64-android/c/signal.cr b/src/lib_c/aarch64-android/c/signal.cr index 741c8f0efb65..27676c3f733f 100644 --- a/src/lib_c/aarch64-android/c/signal.cr +++ b/src/lib_c/aarch64-android/c/signal.cr @@ -79,6 +79,7 @@ lib LibC fun kill(__pid : PidT, __signal : Int) : Int fun pthread_sigmask(__how : Int, __new_set : SigsetT*, __old_set : SigsetT*) : Int + fun pthread_kill(__thread : PthreadT, __sig : Int) : Int fun sigaction(__signal : Int, __new_action : Sigaction*, __old_action : Sigaction*) : Int fun sigaltstack(__new_signal_stack : StackT*, __old_signal_stack : StackT*) : Int {% if ANDROID_API >= 21 %} @@ -89,5 +90,6 @@ lib LibC fun sigaddset(__set : SigsetT*, __signal : Int) : Int fun sigdelset(__set : SigsetT*, __signal : Int) : Int fun sigismember(__set : SigsetT*, __signal : Int) : Int + fun sigsuspend(__mask : SigsetT*) : Int {% end %} end diff --git a/src/lib_c/aarch64-darwin/c/signal.cr b/src/lib_c/aarch64-darwin/c/signal.cr index e58adc30289f..0034eef42834 100644 --- a/src/lib_c/aarch64-darwin/c/signal.cr +++ b/src/lib_c/aarch64-darwin/c/signal.cr @@ -77,6 +77,7 @@ lib LibC fun kill(x0 : PidT, x1 : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -85,4 +86,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/aarch64-linux-gnu/c/signal.cr b/src/lib_c/aarch64-linux-gnu/c/signal.cr index 1f7d82eb2145..7ff9fcda1b07 100644 --- a/src/lib_c/aarch64-linux-gnu/c/signal.cr +++ b/src/lib_c/aarch64-linux-gnu/c/signal.cr @@ -78,6 +78,7 @@ lib LibC fun kill(pid : PidT, sig : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(sig : Int, handler : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -86,4 +87,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/aarch64-linux-musl/c/signal.cr b/src/lib_c/aarch64-linux-musl/c/signal.cr index 5bfa187b14ec..c65fbb0ff653 100644 --- a/src/lib_c/aarch64-linux-musl/c/signal.cr +++ b/src/lib_c/aarch64-linux-musl/c/signal.cr @@ -77,6 +77,7 @@ lib LibC fun kill(x0 : PidT, x1 : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -85,4 +86,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/arm-linux-gnueabihf/c/signal.cr b/src/lib_c/arm-linux-gnueabihf/c/signal.cr index d94d657e1ca8..0113c045341c 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/signal.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/signal.cr @@ -77,6 +77,7 @@ lib LibC fun kill(pid : PidT, sig : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(sig : Int, handler : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -85,4 +86,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/i386-linux-gnu/c/signal.cr b/src/lib_c/i386-linux-gnu/c/signal.cr index 11aab8bfe6bb..1a5260073c2d 100644 --- a/src/lib_c/i386-linux-gnu/c/signal.cr +++ b/src/lib_c/i386-linux-gnu/c/signal.cr @@ -77,6 +77,7 @@ lib LibC fun kill(pid : PidT, sig : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(sig : Int, handler : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -85,4 +86,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/i386-linux-musl/c/signal.cr b/src/lib_c/i386-linux-musl/c/signal.cr index f2e554942b69..ac374b684c76 100644 --- a/src/lib_c/i386-linux-musl/c/signal.cr +++ b/src/lib_c/i386-linux-musl/c/signal.cr @@ -76,6 +76,7 @@ lib LibC fun kill(x0 : PidT, x1 : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -84,4 +85,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/x86_64-darwin/c/signal.cr b/src/lib_c/x86_64-darwin/c/signal.cr index e58adc30289f..0034eef42834 100644 --- a/src/lib_c/x86_64-darwin/c/signal.cr +++ b/src/lib_c/x86_64-darwin/c/signal.cr @@ -77,6 +77,7 @@ lib LibC fun kill(x0 : PidT, x1 : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -85,4 +86,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/x86_64-dragonfly/c/signal.cr b/src/lib_c/x86_64-dragonfly/c/signal.cr index 1751eeed3176..e362ef1fa218 100644 --- a/src/lib_c/x86_64-dragonfly/c/signal.cr +++ b/src/lib_c/x86_64-dragonfly/c/signal.cr @@ -90,6 +90,7 @@ lib LibC fun kill(x0 : PidT, x1 : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -98,4 +99,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/x86_64-freebsd/c/signal.cr b/src/lib_c/x86_64-freebsd/c/signal.cr index fd8d07cfd4cc..c79d0630511b 100644 --- a/src/lib_c/x86_64-freebsd/c/signal.cr +++ b/src/lib_c/x86_64-freebsd/c/signal.cr @@ -8,31 +8,33 @@ lib LibC SIGILL = 4 SIGTRAP = 5 SIGIOT = LibC::SIGABRT - SIGABRT = 6 - SIGFPE = 8 - SIGKILL = 9 - SIGBUS = 10 - SIGSEGV = 11 - SIGSYS = 12 - SIGPIPE = 13 - SIGALRM = 14 - SIGTERM = 15 - SIGURG = 16 - SIGSTOP = 17 - SIGTSTP = 18 - SIGCONT = 19 - SIGCHLD = 20 - SIGTTIN = 21 - SIGTTOU = 22 - SIGIO = 23 - SIGXCPU = 24 - SIGXFSZ = 25 - SIGVTALRM = 26 - SIGUSR1 = 30 - SIGUSR2 = 31 - SIGEMT = 7 - SIGINFO = 29 - SIGWINCH = 28 + SIGABRT = 6 + SIGFPE = 8 + SIGKILL = 9 + SIGBUS = 10 + SIGSEGV = 11 + SIGSYS = 12 + SIGPIPE = 13 + SIGALRM = 14 + SIGTERM = 15 + SIGURG = 16 + SIGSTOP = 17 + SIGTSTP = 18 + SIGCONT = 19 + SIGCHLD = 20 + SIGTTIN = 21 + SIGTTOU = 22 + SIGIO = 23 + SIGXCPU = 24 + SIGXFSZ = 25 + SIGVTALRM = 26 + SIGUSR1 = 30 + SIGUSR2 = 31 + SIGEMT = 7 + SIGINFO = 29 + SIGWINCH = 28 + SIGRTMIN = 65 + SIGRTMAX = 126 SIGSTKSZ = 2048 + 32768 # MINSIGSTKSZ + 32768 SIG_SETMASK = 3 @@ -85,6 +87,7 @@ lib LibC fun kill(x0 : PidT, x1 : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -93,4 +96,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/x86_64-linux-gnu/c/signal.cr b/src/lib_c/x86_64-linux-gnu/c/signal.cr index 07d8e0fe1ae6..b5ed2f8c8fb3 100644 --- a/src/lib_c/x86_64-linux-gnu/c/signal.cr +++ b/src/lib_c/x86_64-linux-gnu/c/signal.cr @@ -78,6 +78,7 @@ lib LibC fun kill(pid : PidT, sig : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(sig : Int, handler : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -86,4 +87,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/x86_64-linux-musl/c/signal.cr b/src/lib_c/x86_64-linux-musl/c/signal.cr index bba7e0c7c21a..42c2aead3e0f 100644 --- a/src/lib_c/x86_64-linux-musl/c/signal.cr +++ b/src/lib_c/x86_64-linux-musl/c/signal.cr @@ -77,6 +77,7 @@ lib LibC fun kill(x0 : PidT, x1 : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -85,4 +86,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/x86_64-netbsd/c/signal.cr b/src/lib_c/x86_64-netbsd/c/signal.cr index 93d42e38b093..0b21c5c3f839 100644 --- a/src/lib_c/x86_64-netbsd/c/signal.cr +++ b/src/lib_c/x86_64-netbsd/c/signal.cr @@ -77,6 +77,7 @@ lib LibC fun kill(x0 : PidT, x1 : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void fun sigaction = __sigaction14(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack = __sigaltstack14(x0 : StackT*, x1 : StackT*) : Int @@ -85,4 +86,5 @@ lib LibC fun sigaddset = __sigaddset14(SigsetT*, Int) : Int fun sigdelset = __sigdelset14(SigsetT*, Int) : Int fun sigismember = __sigismember14(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/x86_64-openbsd/c/signal.cr b/src/lib_c/x86_64-openbsd/c/signal.cr index 04aa27000219..1c9b86137e4a 100644 --- a/src/lib_c/x86_64-openbsd/c/signal.cr +++ b/src/lib_c/x86_64-openbsd/c/signal.cr @@ -76,6 +76,7 @@ lib LibC fun kill(x0 : PidT, x1 : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -84,4 +85,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/x86_64-solaris/c/signal.cr b/src/lib_c/x86_64-solaris/c/signal.cr index 9bde30946054..ee502aa621e4 100644 --- a/src/lib_c/x86_64-solaris/c/signal.cr +++ b/src/lib_c/x86_64-solaris/c/signal.cr @@ -90,6 +90,7 @@ lib LibC fun kill(x0 : PidT, x1 : Int) : Int fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int + fun pthread_kill(PthreadT, Int) : Int fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int @@ -98,4 +99,5 @@ lib LibC fun sigaddset(SigsetT*, Int) : Int fun sigdelset(SigsetT*, Int) : Int fun sigismember(SigsetT*, Int) : Int + fun sigsuspend(SigsetT*) : Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr index d1e13eced324..1fcaee65a01c 100644 --- a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr @@ -59,5 +59,9 @@ lib LibC fun SwitchToThread : BOOL fun QueueUserAPC(pfnAPC : PAPCFUNC, hThread : HANDLE, dwData : ULONG_PTR) : DWORD + fun GetThreadContext(hThread : HANDLE, lpContext : CONTEXT*) : DWORD + fun ResumeThread(hThread : HANDLE) : DWORD + fun SuspendThread(hThread : HANDLE) : DWORD + PROCESS_QUERY_INFORMATION = 0x0400 end From ac2ecb058a962878665bd48a2d197ea9008df8ad Mon Sep 17 00:00:00 2001 From: "WukongRework.exe BROKE" Date: Wed, 7 Aug 2024 17:14:15 -0400 Subject: [PATCH 1255/1551] Expose LLVM instruction builder for `neg` and `fneg` (#14774) --- src/llvm/builder.cr | 10 ++++++---- src/llvm/lib_llvm/core.cr | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index 741f9ee8eb5c..3f2060b32084 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -239,11 +239,13 @@ class LLVM::Builder end {% end %} - def not(value, name = "") - # check_value(value) + {% for name in %w(not neg fneg) %} + def {{name.id}}(value, name = "") + # check_value(value) - Value.new LibLLVM.build_not(self, value, name) - end + Value.new LibLLVM.build_{{name.id}}(self, value, name) + end + {% end %} def unreachable Value.new LibLLVM.build_unreachable(self) diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr index de6f04010cfa..ff3327a3f78d 100644 --- a/src/llvm/lib_llvm/core.cr +++ b/src/llvm/lib_llvm/core.cr @@ -239,6 +239,8 @@ lib LibLLVM fun build_or = LLVMBuildOr(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef fun build_xor = LLVMBuildXor(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef fun build_not = LLVMBuildNot(BuilderRef, value : ValueRef, name : Char*) : ValueRef + fun build_neg = LLVMBuildNeg(BuilderRef, value : ValueRef, name : Char*) : ValueRef + fun build_fneg = LLVMBuildFNeg(BuilderRef, value : ValueRef, name : Char*) : ValueRef fun build_malloc = LLVMBuildMalloc(BuilderRef, ty : TypeRef, name : Char*) : ValueRef fun build_array_malloc = LLVMBuildArrayMalloc(BuilderRef, ty : TypeRef, val : ValueRef, name : Char*) : ValueRef From d738ac9796a24b31782c972c82e7f694b0579d96 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 7 Aug 2024 23:15:10 +0200 Subject: [PATCH 1256/1551] Add support for negative start index in `Slice#[start, count]` (#14778) Allows `slice[-3, 3]` to return the last 3 elements of the slice, for example, similar to how Array#[-start, count] behaves, with the difference that Slice returns exactly *count* elements, while Array returns up to *count* elements. Introduces a couple changes: - negative start now returns from the end of the slice instead of returning nil/raising IndexError - negative count now raises ArgumentError instead of returning nil/raising IndexError I believe the current behavior is buggy (unexpected result, underspecified documentation), but this can be considered a breaking change. --- spec/std/slice_spec.cr | 23 +++++++++++++++++++---- src/slice.cr | 30 ++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr index 505db8f09109..1b21a4489bbd 100644 --- a/spec/std/slice_spec.cr +++ b/spec/std/slice_spec.cr @@ -104,25 +104,34 @@ describe "Slice" do it "does []? with start and count" do slice = Slice.new(4) { |i| i + 1 } + slice1 = slice[1, 2]? slice1.should_not be_nil slice1 = slice1.not_nil! slice1.size.should eq(2) + slice1.to_unsafe.should eq(slice.to_unsafe + 1) slice1[0].should eq(2) slice1[1].should eq(3) - slice[-1, 1]?.should be_nil + slice2 = slice[-1, 1]? + slice2.should_not be_nil + slice2 = slice2.not_nil! + slice2.size.should eq(1) + slice2.to_unsafe.should eq(slice.to_unsafe + 3) + slice[3, 2]?.should be_nil slice[0, 5]?.should be_nil - slice[3, -1]?.should be_nil + expect_raises(ArgumentError, "Negative count: -1") { slice[3, -1]? } end it "does []? with range" do slice = Slice.new(4) { |i| i + 1 } + slice1 = slice[1..2]? slice1.should_not be_nil slice1 = slice1.not_nil! slice1.size.should eq(2) + slice1.to_unsafe.should eq(slice.to_unsafe + 1) slice1[0].should eq(2) slice1[1].should eq(3) @@ -134,15 +143,20 @@ describe "Slice" do it "does [] with start and count" do slice = Slice.new(4) { |i| i + 1 } + slice1 = slice[1, 2] slice1.size.should eq(2) + slice1.to_unsafe.should eq(slice.to_unsafe + 1) slice1[0].should eq(2) slice1[1].should eq(3) - expect_raises(IndexError) { slice[-1, 1] } + slice2 = slice[-1, 1] + slice2.size.should eq(1) + slice2.to_unsafe.should eq(slice.to_unsafe + 3) + expect_raises(IndexError) { slice[3, 2] } expect_raises(IndexError) { slice[0, 5] } - expect_raises(IndexError) { slice[3, -1] } + expect_raises(ArgumentError, "Negative count: -1") { slice[3, -1] } end it "does empty?" do @@ -659,6 +673,7 @@ describe "Slice" do subslice = slice[2..4] subslice.read_only?.should be_false subslice.size.should eq(3) + subslice.to_unsafe.should eq(slice.to_unsafe + 2) subslice.should eq(Slice.new(3) { |i| i + 3 }) end diff --git a/src/slice.cr b/src/slice.cr index 196a29a768dd..c03544ffd859 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -222,35 +222,49 @@ struct Slice(T) end # Returns a new slice that starts at *start* elements from this slice's start, - # and of *count* size. + # and of exactly *count* size. # + # Negative *start* is added to `#size`, thus it's treated as index counting + # from the end of the array, `-1` designating the last element. + # + # Raises `ArgumentError` if *count* is negative. # Returns `nil` if the new slice falls outside this slice. # # ``` # slice = Slice.new(5) { |i| i + 10 } # slice # => Slice[10, 11, 12, 13, 14] # - # slice[1, 3]? # => Slice[11, 12, 13] - # slice[1, 33]? # => nil + # slice[1, 3]? # => Slice[11, 12, 13] + # slice[1, 33]? # => nil + # slice[-3, 2]? # => Slice[12, 13] + # slice[-3, 10]? # => nil # ``` def []?(start : Int, count : Int) : Slice(T)? - return unless 0 <= start <= @size - return unless 0 <= count <= @size - start + # we skip the calculated count because the subslice must contain exactly + # *count* elements + start, _ = Indexable.normalize_start_and_count(start, count, size) { return } + return unless count <= @size - start Slice.new(@pointer + start, count, read_only: @read_only) end # Returns a new slice that starts at *start* elements from this slice's start, - # and of *count* size. + # and of exactly *count* size. + # + # Negative *start* is added to `#size`, thus it's treated as index counting + # from the end of the array, `-1` designating the last element. # + # Raises `ArgumentError` if *count* is negative. # Raises `IndexError` if the new slice falls outside this slice. # # ``` # slice = Slice.new(5) { |i| i + 10 } # slice # => Slice[10, 11, 12, 13, 14] # - # slice[1, 3] # => Slice[11, 12, 13] - # slice[1, 33] # raises IndexError + # slice[1, 3] # => Slice[11, 12, 13] + # slice[1, 33] # raises IndexError + # slice[-3, 2] # => Slice[12, 13] + # slice[-3, 10] # raises IndexError # ``` def [](start : Int, count : Int) : Slice(T) self[start, count]? || raise IndexError.new From edce0a3627d9f5b707e6d4d1ca8c5dc7c3ead362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 7 Aug 2024 23:15:28 +0200 Subject: [PATCH 1257/1551] Enable full backtrace for exception in process spawn (#14796) If an exception raises in the code that prepares a forked process for `exec`, the error message of this exception is written through a pipe to the original process, which then raises an exception for the calling code (`Process.run`). The error only includes a message, no stack trace. So the only stack trace you get is that of the handler which reads the message from the pipe and raises in the original process. But that's not very relevant. We want to know the location of the original exception. This patch changes from sending just the exception message, to printing the entire backtrace (`inspect_with_backtrace`). --- src/crystal/system/unix/process.cr | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 83f95cc8648c..4a540fa53a3d 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -243,8 +243,9 @@ struct Crystal::System::Process writer_pipe.write_bytes(Errno.value.to_i) rescue ex writer_pipe.write_byte(0) - writer_pipe.write_bytes(ex.message.try(&.bytesize) || 0) - writer_pipe << ex.message + message = ex.inspect_with_backtrace + writer_pipe.write_bytes(message.bytesize) + writer_pipe << message writer_pipe.close ensure LibC._exit 127 From 2a8ced5e57d555089c7e47a000fb4071058391f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 8 Aug 2024 10:31:21 +0200 Subject: [PATCH 1258/1551] Refactor GitHub changelog generator print special infra (#14795) --- scripts/github-changelog.cr | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index f7ae12e74dad..2f89bd923153 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -367,32 +367,37 @@ puts puts "[#{milestone.title}]: https://github.com/#{repository}/releases/#{milestone.title}" puts +def print_items(prs) + prs.each do |pr| + puts "- #{pr}" + end + puts + + prs.each(&.print_ref_label(STDOUT)) + puts +end + SECTION_TITLES.each do |id, title| prs = sections[id]? || next puts "### #{title}" puts - topics = prs.group_by(&.primary_topic) + if id == "infra" + prs.sort_by!(&.infra_sort_tuple) + print_items prs + else + topics = prs.group_by(&.primary_topic) - topic_titles = topics.keys.sort_by! { |k| TOPIC_ORDER.index(k) || Int32::MAX } + topic_titles = topics.keys.sort_by! { |k| TOPIC_ORDER.index(k) || Int32::MAX } - topic_titles.each do |topic_title| - topic_prs = topics[topic_title]? || next + topic_titles.each do |topic_title| + topic_prs = topics[topic_title]? || next - if id == "infra" - topic_prs.sort_by!(&.infra_sort_tuple) - else - topic_prs.sort! puts "#### #{topic_title}" puts - end - topic_prs.each do |pr| - puts "- #{pr}" + topic_prs.sort! + print_items topic_prs end - puts - - topic_prs.each(&.print_ref_label(STDOUT)) - puts end end From c0cdaa2f93fdc7e00687c722ebbd5b52d119ef0b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 8 Aug 2024 16:31:34 +0800 Subject: [PATCH 1259/1551] Use `Time::Span` in `Benchmark.ips` (#14805) --- src/benchmark.cr | 16 ++++++++++++++-- src/benchmark/ips.cr | 9 ++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/benchmark.cr b/src/benchmark.cr index bd77a93ae026..a0f4933ddf2a 100644 --- a/src/benchmark.cr +++ b/src/benchmark.cr @@ -102,10 +102,10 @@ module Benchmark # to which one can report the benchmarks. See the module's description. # # The optional parameters *calculation* and *warmup* set the duration of - # those stages in seconds. For more detail on these stages see + # those stages. For more detail on these stages see # `Benchmark::IPS`. When the *interactive* parameter is `true`, results are # displayed and updated as they are calculated, otherwise all at once after they finished. - def ips(calculation = 5, warmup = 2, interactive = STDOUT.tty?, &) + def ips(calculation : Time::Span = 5.seconds, warmup : Time::Span = 2.seconds, interactive : Bool = STDOUT.tty?, &) {% if !flag?(:release) %} puts "Warning: benchmarking without the `--release` flag won't yield useful results" {% end %} @@ -117,6 +117,18 @@ module Benchmark job end + # Instruction per second interface of the `Benchmark` module. Yields a `Job` + # to which one can report the benchmarks. See the module's description. + # + # The optional parameters *calculation* and *warmup* set the duration of + # those stages in seconds. For more detail on these stages see + # `Benchmark::IPS`. When the *interactive* parameter is `true`, results are + # displayed and updated as they are calculated, otherwise all at once after they finished. + @[Deprecated("Use `#ips(Time::Span, Time::Span, Bool, &)` instead.")] + def ips(calculation = 5, warmup = 2, interactive = STDOUT.tty?, &) + ips(calculation.seconds, warmup.seconds, !!interactive) { |job| yield job } + end + # Returns the time used to execute the given block. def measure(label = "", &) : BM::Tms t0, r0 = Process.times, Time.monotonic diff --git a/src/benchmark/ips.cr b/src/benchmark/ips.cr index cb952325eca0..def5b09c7c66 100644 --- a/src/benchmark/ips.cr +++ b/src/benchmark/ips.cr @@ -20,13 +20,16 @@ module Benchmark @warmup_time : Time::Span @calculation_time : Time::Span - def initialize(calculation = 5, warmup = 2, interactive = STDOUT.tty?) + def initialize(calculation @calculation_time : Time::Span = 5.seconds, warmup @warmup_time : Time::Span = 2.seconds, interactive : Bool = STDOUT.tty?) @interactive = !!interactive - @warmup_time = warmup.seconds - @calculation_time = calculation.seconds @items = [] of Entry end + @[Deprecated("Use `.new(Time::Span, Time::Span, Bool)` instead.")] + def self.new(calculation = 5, warmup = 2, interactive = STDOUT.tty?) + new(calculation.seconds, warmup.seconds, !!interactive) + end + # Adds code to be benchmarked def report(label = "", &action) : Benchmark::IPS::Entry item = Entry.new(label, action) From 94bdba1227c1950c7ae0f3e5588b90597e832ba2 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Thu, 8 Aug 2024 04:31:53 -0400 Subject: [PATCH 1260/1551] Add `underscore_to_space` option to `String#titleize` (#14822) --- spec/std/string_spec.cr | 10 ++++++++++ src/string.cr | 35 ++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 00310bfcbc47..2ea13d52010d 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -724,6 +724,10 @@ describe "String" do it { assert_prints " spáçes before".titleize, " Spáçes Before" } it { assert_prints "testá-se múitô".titleize, "Testá-se Múitô" } it { assert_prints "iO iO".titleize(Unicode::CaseOptions::Turkic), "İo İo" } + it { assert_prints "foo_Bar".titleize, "Foo_bar" } + it { assert_prints "foo_bar".titleize, "Foo_bar" } + it { assert_prints "testá_se múitô".titleize(underscore_to_space: true), "Testá Se Múitô" } + it { assert_prints "foo_bar".titleize(underscore_to_space: true), "Foo Bar" } it "handles multi-character mappings correctly (#13533)" do assert_prints "fflİ İffl dz DZ".titleize, "Ffli̇ İffl Dz Dz" @@ -735,6 +739,12 @@ describe "String" do String.build { |io| "\xB5!\xE0\xC1\xB5?".titleize(io) }.should eq("\xB5!\xE0\xC1\xB5?".scrub) String.build { |io| "a\xA0b".titleize(io) }.should eq("A\xA0b".scrub) end + + describe "with IO" do + it { String.build { |io| "foo_Bar".titleize io }.should eq "Foo_bar" } + it { String.build { |io| "foo_bar".titleize io }.should eq "Foo_bar" } + it { String.build { |io| "foo_bar".titleize(io, underscore_to_space: true) }.should eq "Foo Bar" } + end end describe "chomp" do diff --git a/src/string.cr b/src/string.cr index d3bc7d6998b2..08bbb87fc505 100644 --- a/src/string.cr +++ b/src/string.cr @@ -1506,15 +1506,17 @@ class String end end - # Returns a new `String` with the first letter after any space converted to uppercase and every - # other letter converted to lowercase. + # Returns a new `String` with the first letter after any space converted to uppercase and every other letter converted to lowercase. + # Optionally, if *underscore_to_space* is `true`, underscores (`_`) will be converted to a space and the following letter converted to uppercase. # # ``` - # "hEllO tAb\tworld".titleize # => "Hello Tab\tWorld" - # " spaces before".titleize # => " Spaces Before" - # "x-men: the last stand".titleize # => "X-men: The Last Stand" + # "hEllO tAb\tworld".titleize # => "Hello Tab\tWorld" + # " spaces before".titleize # => " Spaces Before" + # "x-men: the last stand".titleize # => "X-men: The Last Stand" + # "foo_bar".titleize # => "Foo_bar" + # "foo_bar".titleize(underscore_to_space: true) # => "Foo Bar" # ``` - def titleize(options : Unicode::CaseOptions = :none) : String + def titleize(options : Unicode::CaseOptions = :none, *, underscore_to_space : Bool = false) : String return self if empty? if single_byte_optimizable? && (options.none? || options.ascii?) @@ -1525,9 +1527,15 @@ class String byte = to_unsafe[i] if byte < 0x80 char = byte.unsafe_chr - replaced_char = upcase_next ? char.upcase : char.downcase + replaced_char, upcase_next = if upcase_next + {char.upcase, false} + elsif underscore_to_space && '_' == char + {' ', true} + else + {char.downcase, char.ascii_whitespace?} + end + buffer[i] = replaced_char.ord.to_u8! - upcase_next = char.ascii_whitespace? else buffer[i] = byte upcase_next = false @@ -1537,26 +1545,31 @@ class String end end - String.build(bytesize) { |io| titleize io, options } + String.build(bytesize) { |io| titleize io, options, underscore_to_space: underscore_to_space } end # Writes a titleized version of `self` to the given *io*. + # Optionally, if *underscore_to_space* is `true`, underscores (`_`) will be converted to a space and the following letter converted to uppercase. # # ``` # io = IO::Memory.new # "x-men: the last stand".titleize io # io.to_s # => "X-men: The Last Stand" # ``` - def titleize(io : IO, options : Unicode::CaseOptions = :none) : Nil + def titleize(io : IO, options : Unicode::CaseOptions = :none, *, underscore_to_space : Bool = false) : Nil upcase_next = true each_char_with_index do |char, i| if upcase_next + upcase_next = false char.titlecase(options) { |c| io << c } + elsif underscore_to_space && '_' == char + upcase_next = true + io << ' ' else + upcase_next = char.whitespace? char.downcase(options) { |c| io << c } end - upcase_next = char.whitespace? end end From f1eabf5768e6cec5a92acda78e2d9ce58d3c6a0c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 8 Aug 2024 21:21:11 +0800 Subject: [PATCH 1261/1551] Always use unstable sort for simple types (#14825) When calling `#sort!` without a block, if two elements have the same binary representations whenever they compare equal using `#<=>`, then they are indistinguishable from each other and swapping is a no-op. This allows the use of unstable sorting which runs slightly faster and requires no temporary allocations, as opposed to stable sorting which allocates memory linear in the collection size. Primitive floats do not support it, as the signed zeros compare equal but have opposite sign bits. For simplicity, unions also aren't touched; either they don't have a meaningful, non-null `#<=>` defined across the variant types, or they break the criterion (e.g. `1_i32` and `1_i8` have different type IDs). `#sort` always delegates to `#sort!`. This does not affect `#sort_by!` since the projected element type alone doesn't guarantee the original elements themselves can be swapped in the same way. --- src/slice.cr | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/slice.cr b/src/slice.cr index c03544ffd859..d843ceb17c63 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -995,13 +995,23 @@ struct Slice(T) # the result could also be `[b, a]`. # # If stability is expendable, `#unstable_sort!` provides a performance - # advantage over stable sort. + # advantage over stable sort. As an optimization, if `T` is any primitive + # integer type, `Char`, any enum type, any `Pointer` instance, `Symbol`, or + # `Time::Span`, then an unstable sort is automatically used. # # Raises `ArgumentError` if the comparison between any two elements returns `nil`. def sort! : self - Slice.merge_sort!(self) + # If two values `x, y : T` have the same binary representation whenever they + # compare equal, i.e. `x <=> y == 0` implies + # `pointerof(x).memcmp(pointerof(y), 1) == 0`, then swapping the two values + # is a no-op and therefore a stable sort isn't required + {% if T.union_types.size == 1 && (T <= Int::Primitive || T <= Char || T <= Enum || T <= Pointer || T <= Symbol || T <= Time::Span) %} + unstable_sort! + {% else %} + Slice.merge_sort!(self) - self + self + {% end %} end # Sorts all elements in `self` based on the return value of the comparison From 467103dfaad8d32da89409f23a2e0f8be62c8e84 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:22:37 +0200 Subject: [PATCH 1262/1551] Update GH Actions (#14535) --- .github/workflows/macos.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 7f27b3cc9c14..c19041c3f52d 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -17,12 +17,12 @@ jobs: - name: Download Crystal source uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v26 + - uses: cachix/install-nix-action@v27 with: install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | experimental-features = nix-command - - uses: cachix/cachix-action@v14 + - uses: cachix/cachix-action@v15 with: name: crystal-ci signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' From f16794d933605b25354fee401e210c1f2e818b26 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Thu, 8 Aug 2024 09:22:57 -0400 Subject: [PATCH 1263/1551] Add JSON parsing UTF-8 spec (#14823) --- spec/std/json/parser_spec.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/std/json/parser_spec.cr b/spec/std/json/parser_spec.cr index 96cfd52277a2..0147cfa92964 100644 --- a/spec/std/json/parser_spec.cr +++ b/spec/std/json/parser_spec.cr @@ -22,6 +22,7 @@ describe JSON::Parser do it_parses "true", true it_parses "false", false it_parses "null", nil + it_parses %("\\nПривет, мир!"), "\nПривет, мир!" it_parses "[]", [] of Int32 it_parses "[1]", [1] From e4390a3e66e056002bcca57d8025444f81322a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 8 Aug 2024 18:18:46 +0200 Subject: [PATCH 1264/1551] Update distribution-scripts (#14877) Updates `distribution-scripts` dependency to https://github.com/crystal-lang/distribution-scripts/commit/da59efb2dfd70dcd7272eaecceffb636ef547427 This includes the following changes: * crystal-lang/distribution-scripts#326 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9118ce51ec2c..b3f2310d7808 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "96e431e170979125018bd4fd90111a3147477eec" + default: "da59efb2dfd70dcd7272eaecceffb636ef547427" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From 74f009346c1c8dc4187a6dd69f8ec055a7526b1d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 9 Aug 2024 03:11:26 +0800 Subject: [PATCH 1265/1551] Add `Crystal::Macros::TypeNode#has_inner_pointers?` (#14847) This allows `Pointer.malloc` and `Reference#allocate` to be implemented without compiler primitives eventually, see #13589 and #13481. This might be helpful to diagnostic tools related to the GC too. --- spec/compiler/macro/macro_methods_spec.cr | 59 +++++++++++++++++++++++ src/compiler/crystal/macros.cr | 19 ++++++++ src/compiler/crystal/macros/methods.cr | 2 + src/primitives.cr | 8 +-- 4 files changed, 82 insertions(+), 6 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 29de1a51c2be..38b08f44568a 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2407,6 +2407,65 @@ module Crystal end end end + + describe "#has_inner_pointers?" do + it "works on structs" do + assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program| + klass = NonGenericClassType.new(program, program, "SomeType", program.struct) + klass.struct = true + klass.declare_instance_var("@var", program.int32) + {x: TypeNode.new(klass)} + end + + assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program| + klass = NonGenericClassType.new(program, program, "SomeType", program.struct) + klass.struct = true + klass.declare_instance_var("@var", program.string) + {x: TypeNode.new(klass)} + end + end + + it "works on references" do + assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program| + klass = NonGenericClassType.new(program, program, "SomeType", program.reference) + {x: TypeNode.new(klass)} + end + end + + it "works on ReferenceStorage" do + assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program| + reference_storage = GenericReferenceStorageType.new program, program, "ReferenceStorage", program.struct, ["T"] + klass = NonGenericClassType.new(program, program, "SomeType", program.reference) + klass.declare_instance_var("@var", program.int32) + {x: TypeNode.new(reference_storage.instantiate([klass] of TypeVar))} + end + + assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program| + reference_storage = GenericReferenceStorageType.new program, program, "ReferenceStorage", program.struct, ["T"] + klass = NonGenericClassType.new(program, program, "SomeType", program.reference) + klass.declare_instance_var("@var", program.string) + {x: TypeNode.new(reference_storage.instantiate([klass] of TypeVar))} + end + end + + it "works on primitive values" do + assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program| + {x: TypeNode.new(program.int32)} + end + + assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program| + {x: TypeNode.new(program.void)} + end + + assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program| + {x: TypeNode.new(program.pointer_of(program.int32))} + end + + assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program| + {x: TypeNode.new(program.proc_of(program.void))} + end + end + end end describe "type declaration methods" do diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index c0d4f6e0a071..ff422ce553a2 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2853,5 +2853,24 @@ module Crystal::Macros # `self` is an ancestor of *other*. def >=(other : TypeNode) : BoolLiteral end + + # Returns whether `self` contains any inner pointers. + # + # Primitive types, except `Void`, are expected to not contain inner pointers. + # `Proc` and `Pointer` contain inner pointers. + # Unions, structs and collection types (tuples, static arrays) + # have inner pointers if any of their contained types has inner pointers. + # All other types, including classes, are expected to contain inner pointers. + # + # Types that do not have inner pointers may opt to use atomic allocations, + # i.e. `GC.malloc_atomic` rather than `GC.malloc`. The compiler ensures + # that, for any type `T`: + # + # * `Pointer(T).malloc` is atomic if and only if `T` has no inner pointers; + # * `T.allocate` is atomic if and only if `T` is a reference type and + # `ReferenceStorage(T)` has no inner pointers. + # NOTE: Like `#instance_vars` this method must be called from within a method. The result may be incorrect when used in top-level code. + def has_inner_pointers? : BoolLiteral + end end end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index a44bba1b76f9..d3a1a1cc15a6 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -2013,6 +2013,8 @@ module Crystal SymbolLiteral.new("public") end end + when "has_inner_pointers?" + interpret_check_args { BoolLiteral.new(type.has_inner_pointers?) } else super end diff --git a/src/primitives.cr b/src/primitives.cr index 9383ba642165..e033becdfbd2 100644 --- a/src/primitives.cr +++ b/src/primitives.cr @@ -206,12 +206,8 @@ struct Pointer(T) # ``` # # The implementation uses `GC.malloc` if the compiler is aware that the - # allocated type contains inner address pointers. Otherwise it uses - # `GC.malloc_atomic`. Primitive types are expected to not contain pointers, - # except `Void`. `Proc` and `Pointer` are expected to contain pointers. - # For unions, structs and collection types (tuples, static array) - # it depends on the contained types. All other types, including classes are - # expected to contain inner address pointers. + # allocated type contains inner address pointers. See + # `Crystal::Macros::TypeNode#has_inner_pointers?` for details. # # To override this implicit behaviour, `GC.malloc` and `GC.malloc_atomic` # can be used directly instead. From 4c2eaf06d05be415f8140fb61893b53fb74f6390 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 9 Aug 2024 03:13:59 +0800 Subject: [PATCH 1266/1551] Fix `File#truncate` and `#lock` for Win32 append-mode files (#14706) --- spec/std/file_spec.cr | 35 +++++++++++++++++++++ src/crystal/system/unix/file.cr | 3 ++ src/crystal/system/wasi/file.cr | 3 ++ src/crystal/system/win32/file.cr | 20 +++++++++--- src/crystal/system/win32/file_descriptor.cr | 13 ++++++-- src/file.cr | 2 +- 6 files changed, 69 insertions(+), 7 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 44f947997b34..942ae8a1143d 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -1049,6 +1049,41 @@ describe "File" do end end + it "does not overwrite existing content in append mode" do + with_tempfile("append-override.txt") do |filename| + File.write(filename, "0123456789") + + File.open(filename, "a") do |file| + file.seek(5) + file.write "abcd".to_slice + end + + File.read(filename).should eq "0123456789abcd" + end + end + + it "truncates file opened in append mode (#14702)" do + with_tempfile("truncate-append.txt") do |path| + File.write(path, "0123456789") + + File.open(path, "a") do |file| + file.truncate(4) + end + + File.read(path).should eq "0123" + end + end + + it "locks file opened in append mode (#14702)" do + with_tempfile("truncate-append.txt") do |path| + File.write(path, "0123456789") + + File.open(path, "a") do |file| + file.flock_exclusive { } + end + end + end + it "can navigate with pos" do File.open(datapath("test_file.txt")) do |file| file.pos = 3 diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index a353cf29cd3c..fafd1d0d0a16 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -24,6 +24,9 @@ module Crystal::System::File {fd, fd < 0 ? Errno.value : Errno::NONE} end + protected def system_set_mode(mode : String) + end + def self.info?(path : String, follow_symlinks : Bool) : ::File::Info? stat = uninitialized LibC::Stat if follow_symlinks diff --git a/src/crystal/system/wasi/file.cr b/src/crystal/system/wasi/file.cr index 0d197550e3db..a48463eded4e 100644 --- a/src/crystal/system/wasi/file.cr +++ b/src/crystal/system/wasi/file.cr @@ -2,6 +2,9 @@ require "../unix/file" # :nodoc: module Crystal::System::File + protected def system_set_mode(mode : String) + end + def self.chmod(path, mode) raise NotImplementedError.new "Crystal::System::File.chmod" end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 83d6afcf18ca..9039cc40a7ac 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -9,6 +9,11 @@ require "c/ntifs" require "c/winioctl" module Crystal::System::File + # On Windows we cannot rely on the system mode `FILE_APPEND_DATA` and + # keep track of append mode explicitly. When writing data, this ensures to only + # write at the end of the file. + @system_append = false + def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions) : FileDescriptor::Handle perm = ::File::Permissions.new(perm) if perm.is_a? Int32 # Only the owner writable bit is used, since windows only supports @@ -52,10 +57,9 @@ module Crystal::System::File LibC::FILE_GENERIC_READ end - if flags.bits_set? LibC::O_APPEND - access |= LibC::FILE_APPEND_DATA - access &= ~LibC::FILE_WRITE_DATA - end + # do not handle `O_APPEND`, because Win32 append mode relies on removing + # `FILE_WRITE_DATA` which breaks file truncation and locking; instead, + # simply set the end of the file as the write offset in `#write_blocking` if flags.bits_set? LibC::O_TRUNC if flags.bits_set? LibC::O_CREAT @@ -96,6 +100,14 @@ module Crystal::System::File {access, disposition, attributes} end + protected def system_set_mode(mode : String) + @system_append = true if mode.starts_with?('a') + end + + private def write_blocking(handle, slice) + write_blocking(handle, slice, pos: @system_append ? UInt64::MAX : nil) + end + NOT_FOUND_ERRORS = { WinError::ERROR_FILE_NOT_FOUND, WinError::ERROR_PATH_NOT_FOUND, diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index dc8d479532be..b39f98fbdf0c 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -52,8 +52,17 @@ module Crystal::System::FileDescriptor end end - private def write_blocking(handle, slice) - ret = LibC.WriteFile(handle, slice, slice.size, out bytes_written, nil) + private def write_blocking(handle, slice, pos = nil) + overlapped = LibC::OVERLAPPED.new + if pos + overlapped.union.offset.offset = LibC::DWORD.new!(pos) + overlapped.union.offset.offsetHigh = LibC::DWORD.new!(pos >> 32) + overlapped_ptr = pointerof(overlapped) + else + overlapped_ptr = Pointer(LibC::OVERLAPPED).null + end + + ret = LibC.WriteFile(handle, slice, slice.size, out bytes_written, overlapped_ptr) if ret.zero? case error = WinError.value when .error_access_denied? diff --git a/src/file.cr b/src/file.cr index ff6c68ef4d03..202a05ab01f0 100644 --- a/src/file.cr +++ b/src/file.cr @@ -173,7 +173,7 @@ class File < IO::FileDescriptor def self.new(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, blocking = true) filename = filename.to_s fd = Crystal::System::File.open(filename, mode, perm: perm) - new(filename, fd, blocking: blocking, encoding: encoding, invalid: invalid) + new(filename, fd, blocking: blocking, encoding: encoding, invalid: invalid).tap { |f| f.system_set_mode(mode) } end getter path : String From b9ab9968b85ce2b99a875abd4360b1a432e27a98 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Fri, 9 Aug 2024 05:00:09 -0400 Subject: [PATCH 1267/1551] Hide `Hash::Entry` from public API docs (#14881) --- src/hash.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hash.cr b/src/hash.cr index cfa556f921ed..e14b92ee482e 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -2149,6 +2149,7 @@ class Hash(K, V) hash end + # :nodoc: struct Entry(K, V) getter key, value, hash From a12ab5b769e122c9583588d7b4ae5cb4ce2162a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Cla=C3=9Fen?= Date: Tue, 13 Aug 2024 11:03:39 +0200 Subject: [PATCH 1268/1551] Fix typos in docs for `Set` and `Hash` (#14889) --- src/hash.cr | 2 +- src/set.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hash.cr b/src/hash.cr index e14b92ee482e..e2fe7dad186c 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -1055,7 +1055,7 @@ class Hash(K, V) self end - # Returns `true` of this Hash is comparing keys by `object_id`. + # Returns `true` if this Hash is comparing keys by `object_id`. # # See `compare_by_identity`. getter? compare_by_identity : Bool diff --git a/src/set.cr b/src/set.cr index c998fab949a1..1bcc5178fbb0 100644 --- a/src/set.cr +++ b/src/set.cr @@ -73,7 +73,7 @@ struct Set(T) self end - # Returns `true` of this Set is comparing objects by `object_id`. + # Returns `true` if this Set is comparing objects by `object_id`. # # See `compare_by_identity`. def compare_by_identity? : Bool From 6d89f24f13fa988458d5b5fde983a234e4a625d1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 13 Aug 2024 17:04:03 +0800 Subject: [PATCH 1269/1551] Support return types in codegen specs (#14888) --- spec/compiler/codegen/and_spec.cr | 48 ++++++++++++------------ spec/spec_helper.cr | 12 ++++++ src/compiler/crystal/codegen/codegen.cr | 50 +++++++++++++++++++++++++ src/llvm/jit_compiler.cr | 4 ++ src/llvm/lib_llvm/execution_engine.cr | 1 + 5 files changed, 91 insertions(+), 24 deletions(-) diff --git a/spec/compiler/codegen/and_spec.cr b/spec/compiler/codegen/and_spec.cr index 337cceb138eb..7aa3cdfd6c7b 100644 --- a/spec/compiler/codegen/and_spec.cr +++ b/spec/compiler/codegen/and_spec.cr @@ -2,42 +2,42 @@ require "../../spec_helper" describe "Code gen: and" do it "codegens and with bool false and false" do - run("false && false").to_b.should be_false + run("false && false", Bool).should be_false end it "codegens and with bool false and true" do - run("false && true").to_b.should be_false + run("false && true", Bool).should be_false end it "codegens and with bool true and true" do - run("true && true").to_b.should be_true + run("true && true", Bool).should be_true end it "codegens and with bool true and false" do - run("true && false").to_b.should be_false + run("true && false", Bool).should be_false end it "codegens and with bool and int 1" do - run("struct Bool; def to_i!; 0; end; end; (false && 2).to_i!").to_i.should eq(0) + run("struct Bool; def to_i!; 0; end; end; (false && 2).to_i!", Int32).should eq(0) end it "codegens and with bool and int 2" do - run("struct Bool; def to_i!; 0; end; end; (true && 2).to_i!").to_i.should eq(2) + run("struct Bool; def to_i!; 0; end; end; (true && 2).to_i!", Int32).should eq(2) end it "codegens and with primitive type other than bool" do - run("1 && 2").to_i.should eq(2) + run("1 && 2", Int32).should eq(2) end it "codegens and with primitive type other than bool with union" do - run("(1 && 1.5).to_f").to_f64.should eq(1.5) + run("(1 && 1.5).to_f", Float64).should eq(1.5) end it "codegens and with primitive type other than bool" do run(%( struct Nil; def to_i!; 0; end; end (nil && 2).to_i! - )).to_i.should eq(0) + ), Int32).should eq(0) end it "codegens and with nilable as left node 1" do @@ -47,7 +47,7 @@ describe "Code gen: and" do a = Reference.new a = nil (a && 2).to_i! - ").to_i.should eq(0) + ", Int32).should eq(0) end it "codegens and with nilable as left node 2" do @@ -56,7 +56,7 @@ describe "Code gen: and" do a = nil a = Reference.new (a && 2).to_i! - ").to_i.should eq(2) + ", Int32).should eq(2) end it "codegens and with non-false union as left node" do @@ -64,7 +64,7 @@ describe "Code gen: and" do a = 1.5 a = 1 (a && 2).to_i! - ").to_i.should eq(2) + ", Int32).should eq(2) end it "codegens and with nil union as left node 1" do @@ -73,7 +73,7 @@ describe "Code gen: and" do a = nil a = 1 (a && 2).to_i! - ").to_i.should eq(2) + ", Int32).should eq(2) end it "codegens and with nil union as left node 2" do @@ -82,7 +82,7 @@ describe "Code gen: and" do a = 1 a = nil (a && 2).to_i! - ").to_i.should eq(0) + ", Int32).should eq(0) end it "codegens and with bool union as left node 1" do @@ -91,7 +91,7 @@ describe "Code gen: and" do a = false a = 1 (a && 2).to_i! - ").to_i.should eq(2) + ", Int32).should eq(2) end it "codegens and with bool union as left node 2" do @@ -100,7 +100,7 @@ describe "Code gen: and" do a = 1 a = false (a && 2).to_i! - ").to_i.should eq(0) + ", Int32).should eq(0) end it "codegens and with bool union as left node 3" do @@ -109,7 +109,7 @@ describe "Code gen: and" do a = 1 a = true (a && 2).to_i! - ").to_i.should eq(2) + ", Int32).should eq(2) end it "codegens and with bool union as left node 1" do @@ -120,7 +120,7 @@ describe "Code gen: and" do a = nil a = 2 (a && 3).to_i! - ").to_i.should eq(3) + ", Int32).should eq(3) end it "codegens and with bool union as left node 2" do @@ -131,7 +131,7 @@ describe "Code gen: and" do a = 2 a = false (a && 3).to_i! - ").to_i.should eq(1) + ", Int32).should eq(1) end it "codegens and with bool union as left node 3" do @@ -142,7 +142,7 @@ describe "Code gen: and" do a = 2 a = true (a && 3).to_i! - ").to_i.should eq(3) + ", Int32).should eq(3) end it "codegens and with bool union as left node 4" do @@ -153,14 +153,14 @@ describe "Code gen: and" do a = true a = nil (a && 3).to_i! - ").to_i.should eq(0) + ", Int32).should eq(0) end it "codegens assign in right node, after must be nilable" do run(" a = 1 == 2 && (b = Reference.new) b.nil? - ").to_b.should be_true + ", Bool).should be_true end it "codegens assign in right node, inside if must not be nil" do @@ -173,7 +173,7 @@ describe "Code gen: and" do else 0 end - ").to_i.should eq(1) + ", Int32).should eq(1) end it "codegens assign in right node, after if must be nilable" do @@ -181,6 +181,6 @@ describe "Code gen: and" do if 1 == 2 && (b = Reference.new) end b.nil? - ").to_b.should be_true + ", Bool).should be_true end end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index ca5bc61ad3c4..31412035ff74 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -319,6 +319,18 @@ def run(code, filename = nil, inject_primitives = true, debug = Crystal::Debug:: end end +def run(code, return_type : T.class, filename : String? = nil, inject_primitives = true, debug = Crystal::Debug::None, flags = nil, *, file = __FILE__) forall T + if inject_primitives + code = %(require "primitives"\n#{code}) + end + + if code.includes?(%(require "prelude")) || flags + fail "TODO: support the prelude in typed codegen specs", file: file + else + new_program.run(code, return_type: T, filename: filename, debug: debug) + end +end + def test_c(c_code, crystal_code, *, file = __FILE__, &) with_temp_c_object_file(c_code, file: file) do |o_filename| yield run(%( diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index a46d255901e5..67882e9d75dc 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -69,6 +69,56 @@ module Crystal end end + def run(code, return_type : T.class, filename : String? = nil, debug = Debug::Default) forall T + parser = new_parser(code) + parser.filename = filename + node = parser.parse + node = normalize node + node = semantic node + evaluate node, T, debug: debug + end + + def evaluate(node, return_type : T.class, debug = Debug::Default) : T forall T + visitor = CodeGenVisitor.new self, node, single_module: true, debug: debug + visitor.accept node + visitor.process_finished_hooks + visitor.finish + + llvm_mod = visitor.modules[""].mod + llvm_mod.target = target_machine.triple + + main = visitor.typed_fun?(llvm_mod, MAIN_NAME).not_nil! + llvm_context = llvm_mod.context + + # void (*__evaluate_wrapper)(void*) + wrapper_type = LLVM::Type.function([llvm_context.void_pointer], llvm_context.void) + wrapper = llvm_mod.functions.add("__evaluate_wrapper", wrapper_type) do |func| + func.basic_blocks.append "entry" do |builder| + argc = llvm_context.int32.const_int(0) + argv = llvm_context.void_pointer.pointer.null + ret = builder.call(main.type, main.func, [argc, argv]) + unless node.type.void? || node.type.nil_type? + out_ptr = func.params[0] + {% if LibLLVM::IS_LT_150 %} + out_ptr = builder.bit_cast out_ptr, main.type.return_type.pointer + {% end %} + builder.store(ret, out_ptr) + end + builder.ret + end + end + + llvm_mod.verify + + result = uninitialized T + LLVM::JITCompiler.new(llvm_mod) do |jit| + func_ptr = jit.function_address("__evaluate_wrapper") + func = Proc(T*, Nil).new(func_ptr, Pointer(Void).null) + func.call(pointerof(result)) + end + result + end + def codegen(node, single_module = false, debug = Debug::Default, frame_pointers = FramePointers::Auto) visitor = CodeGenVisitor.new self, node, single_module: single_module, diff --git a/src/llvm/jit_compiler.cr b/src/llvm/jit_compiler.cr index 33d03e697107..4acae901f381 100644 --- a/src/llvm/jit_compiler.cr +++ b/src/llvm/jit_compiler.cr @@ -39,6 +39,10 @@ class LLVM::JITCompiler LibLLVM.get_pointer_to_global(self, value) end + def function_address(name : String) : Void* + Pointer(Void).new(LibLLVM.get_function_address(self, name.check_no_null_byte)) + end + def to_unsafe @unwrap end diff --git a/src/llvm/lib_llvm/execution_engine.cr b/src/llvm/lib_llvm/execution_engine.cr index f9de5c10ea39..bfc2e23154db 100644 --- a/src/llvm/lib_llvm/execution_engine.cr +++ b/src/llvm/lib_llvm/execution_engine.cr @@ -30,4 +30,5 @@ lib LibLLVM fun run_function = LLVMRunFunction(ee : ExecutionEngineRef, f : ValueRef, num_args : UInt, args : GenericValueRef*) : GenericValueRef fun get_execution_engine_target_machine = LLVMGetExecutionEngineTargetMachine(ee : ExecutionEngineRef) : TargetMachineRef fun get_pointer_to_global = LLVMGetPointerToGlobal(ee : ExecutionEngineRef, global : ValueRef) : Void* + fun get_function_address = LLVMGetFunctionAddress(ee : ExecutionEngineRef, name : Char*) : UInt64 end From 529fa4d66df1bb92351039ceb78fa36adf5fe097 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 13 Aug 2024 18:50:43 +0800 Subject: [PATCH 1270/1551] Add minimal LLVM OrcV2 bindings (#14887) --- src/llvm.cr | 7 +++++ src/llvm/lib_llvm/error.cr | 3 +++ src/llvm/lib_llvm/lljit.cr | 16 +++++++++++ src/llvm/lib_llvm/orc.cr | 26 ++++++++++++++++++ src/llvm/orc/jit_dylib.cr | 16 +++++++++++ src/llvm/orc/lljit.cr | 42 +++++++++++++++++++++++++++++ src/llvm/orc/lljit_builder.cr | 35 ++++++++++++++++++++++++ src/llvm/orc/thread_safe_context.cr | 30 +++++++++++++++++++++ src/llvm/orc/thread_safe_module.cr | 36 +++++++++++++++++++++++++ 9 files changed, 211 insertions(+) create mode 100644 src/llvm/lib_llvm/lljit.cr create mode 100644 src/llvm/lib_llvm/orc.cr create mode 100644 src/llvm/orc/jit_dylib.cr create mode 100644 src/llvm/orc/lljit.cr create mode 100644 src/llvm/orc/lljit_builder.cr create mode 100644 src/llvm/orc/thread_safe_context.cr create mode 100644 src/llvm/orc/thread_safe_module.cr diff --git a/src/llvm.cr b/src/llvm.cr index 6fb8767cad54..84c9dc89aa8f 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -140,6 +140,13 @@ module LLVM string end + protected def self.assert(error : LibLLVM::ErrorRef) + if error + chars = LibLLVM.get_error_message(error) + raise String.new(chars).tap { LibLLVM.dispose_error_message(chars) } + end + end + {% unless LibLLVM::IS_LT_130 %} def self.run_passes(module mod : Module, passes : String, target_machine : TargetMachine, options : PassBuilderOptions) LibLLVM.run_passes(mod, passes, target_machine, options) diff --git a/src/llvm/lib_llvm/error.cr b/src/llvm/lib_llvm/error.cr index b816a7e2088b..5a035b5f80a5 100644 --- a/src/llvm/lib_llvm/error.cr +++ b/src/llvm/lib_llvm/error.cr @@ -1,3 +1,6 @@ lib LibLLVM type ErrorRef = Void* + + fun get_error_message = LLVMGetErrorMessage(err : ErrorRef) : Char* + fun dispose_error_message = LLVMDisposeErrorMessage(err_msg : Char*) end diff --git a/src/llvm/lib_llvm/lljit.cr b/src/llvm/lib_llvm/lljit.cr new file mode 100644 index 000000000000..640973024af4 --- /dev/null +++ b/src/llvm/lib_llvm/lljit.cr @@ -0,0 +1,16 @@ +{% skip_file if LibLLVM::IS_LT_110 %} + +lib LibLLVM + alias OrcLLJITBuilderRef = Void* + alias OrcLLJITRef = Void* + + fun orc_create_lljit_builder = LLVMOrcCreateLLJITBuilder : OrcLLJITBuilderRef + fun orc_dispose_lljit_builder = LLVMOrcDisposeLLJITBuilder(builder : OrcLLJITBuilderRef) + + fun orc_create_lljit = LLVMOrcCreateLLJIT(result : OrcLLJITRef*, builder : OrcLLJITBuilderRef) : ErrorRef + fun orc_dispose_lljit = LLVMOrcDisposeLLJIT(j : OrcLLJITRef) : ErrorRef + + fun orc_lljit_get_main_jit_dylib = LLVMOrcLLJITGetMainJITDylib(j : OrcLLJITRef) : OrcJITDylibRef + fun orc_lljit_add_llvm_ir_module = LLVMOrcLLJITAddLLVMIRModule(j : OrcLLJITRef, jd : OrcJITDylibRef, tsm : OrcThreadSafeModuleRef) : ErrorRef + fun orc_lljit_lookup = LLVMOrcLLJITLookup(j : OrcLLJITRef, result : OrcExecutorAddress*, name : Char*) : ErrorRef +end diff --git a/src/llvm/lib_llvm/orc.cr b/src/llvm/lib_llvm/orc.cr new file mode 100644 index 000000000000..a1650b3dfb96 --- /dev/null +++ b/src/llvm/lib_llvm/orc.cr @@ -0,0 +1,26 @@ +{% skip_file if LibLLVM::IS_LT_110 %} + +lib LibLLVM + # OrcJITTargetAddress before LLVM 13.0 (also an alias of UInt64) + alias OrcExecutorAddress = UInt64 + alias OrcSymbolStringPoolEntryRef = Void* + alias OrcJITDylibRef = Void* + alias OrcDefinitionGeneratorRef = Void* + alias OrcSymbolPredicate = Void*, OrcSymbolStringPoolEntryRef -> Int + alias OrcThreadSafeContextRef = Void* + alias OrcThreadSafeModuleRef = Void* + + fun orc_create_dynamic_library_search_generator_for_process = LLVMOrcCreateDynamicLibrarySearchGeneratorForProcess( + result : OrcDefinitionGeneratorRef*, global_prefx : Char, + filter : OrcSymbolPredicate, filter_ctx : Void* + ) : ErrorRef + + fun orc_jit_dylib_add_generator = LLVMOrcJITDylibAddGenerator(jd : OrcJITDylibRef, dg : OrcDefinitionGeneratorRef) + + fun orc_create_new_thread_safe_context = LLVMOrcCreateNewThreadSafeContext : OrcThreadSafeContextRef + fun orc_thread_safe_context_get_context = LLVMOrcThreadSafeContextGetContext(ts_ctx : OrcThreadSafeContextRef) : ContextRef + fun orc_dispose_thread_safe_context = LLVMOrcDisposeThreadSafeContext(ts_ctx : OrcThreadSafeContextRef) + + fun orc_create_new_thread_safe_module = LLVMOrcCreateNewThreadSafeModule(m : ModuleRef, ts_ctx : OrcThreadSafeContextRef) : OrcThreadSafeModuleRef + fun orc_dispose_thread_safe_module = LLVMOrcDisposeThreadSafeModule(tsm : OrcThreadSafeModuleRef) +end diff --git a/src/llvm/orc/jit_dylib.cr b/src/llvm/orc/jit_dylib.cr new file mode 100644 index 000000000000..929dc5e5e6a4 --- /dev/null +++ b/src/llvm/orc/jit_dylib.cr @@ -0,0 +1,16 @@ +{% skip_file if LibLLVM::IS_LT_110 %} + +@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")] +class LLVM::Orc::JITDylib + protected def initialize(@unwrap : LibLLVM::OrcJITDylibRef) + end + + def to_unsafe + @unwrap + end + + def link_symbols_from_current_process : Nil + LLVM.assert LibLLVM.orc_create_dynamic_library_search_generator_for_process(out dg, 0, nil, nil) + LibLLVM.orc_jit_dylib_add_generator(self, dg) + end +end diff --git a/src/llvm/orc/lljit.cr b/src/llvm/orc/lljit.cr new file mode 100644 index 000000000000..6271dea6ea56 --- /dev/null +++ b/src/llvm/orc/lljit.cr @@ -0,0 +1,42 @@ +{% skip_file if LibLLVM::IS_LT_110 %} + +@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")] +class LLVM::Orc::LLJIT + protected def initialize(@unwrap : LibLLVM::OrcLLJITRef) + end + + def self.new(builder : LLJITBuilder) + builder.take_ownership { raise "Failed to take ownership of LLVM::Orc::LLJITBuilder" } + LLVM.assert LibLLVM.orc_create_lljit(out unwrap, builder) + new(unwrap) + end + + def to_unsafe + @unwrap + end + + def dispose : Nil + LLVM.assert LibLLVM.orc_dispose_lljit(self) + @unwrap = LibLLVM::OrcLLJITRef.null + end + + def finalize + if @unwrap + LibLLVM.orc_dispose_lljit(self) + end + end + + def main_jit_dylib : JITDylib + JITDylib.new(LibLLVM.orc_lljit_get_main_jit_dylib(self)) + end + + def add_llvm_ir_module(dylib : JITDylib, tsm : ThreadSafeModule) : Nil + tsm.take_ownership { raise "Failed to take ownership of LLVM::Orc::ThreadSafeModule" } + LLVM.assert LibLLVM.orc_lljit_add_llvm_ir_module(self, dylib, tsm) + end + + def lookup(name : String) : Void* + LLVM.assert LibLLVM.orc_lljit_lookup(self, out address, name.check_no_null_byte) + Pointer(Void).new(address) + end +end diff --git a/src/llvm/orc/lljit_builder.cr b/src/llvm/orc/lljit_builder.cr new file mode 100644 index 000000000000..8147e5947376 --- /dev/null +++ b/src/llvm/orc/lljit_builder.cr @@ -0,0 +1,35 @@ +{% skip_file if LibLLVM::IS_LT_110 %} + +@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")] +class LLVM::Orc::LLJITBuilder + protected def initialize(@unwrap : LibLLVM::OrcLLJITBuilderRef) + @dispose_on_finalize = true + end + + def self.new + new(LibLLVM.orc_create_lljit_builder) + end + + def to_unsafe + @unwrap + end + + def dispose : Nil + LibLLVM.orc_dispose_lljit_builder(self) + @unwrap = LibLLVM::OrcLLJITBuilderRef.null + end + + def finalize + if @dispose_on_finalize && @unwrap + dispose + end + end + + def take_ownership(&) : Nil + if @dispose_on_finalize + @dispose_on_finalize = false + else + yield + end + end +end diff --git a/src/llvm/orc/thread_safe_context.cr b/src/llvm/orc/thread_safe_context.cr new file mode 100644 index 000000000000..38c4ece7a50a --- /dev/null +++ b/src/llvm/orc/thread_safe_context.cr @@ -0,0 +1,30 @@ +{% skip_file if LibLLVM::IS_LT_110 %} + +@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")] +class LLVM::Orc::ThreadSafeContext + protected def initialize(@unwrap : LibLLVM::OrcThreadSafeContextRef) + end + + def self.new + new(LibLLVM.orc_create_new_thread_safe_context) + end + + def to_unsafe + @unwrap + end + + def dispose : Nil + LibLLVM.orc_dispose_thread_safe_context(self) + @unwrap = LibLLVM::OrcThreadSafeContextRef.null + end + + def finalize + if @unwrap + dispose + end + end + + def context : LLVM::Context + LLVM::Context.new(LibLLVM.orc_thread_safe_context_get_context(self), false) + end +end diff --git a/src/llvm/orc/thread_safe_module.cr b/src/llvm/orc/thread_safe_module.cr new file mode 100644 index 000000000000..5e29667fd9cd --- /dev/null +++ b/src/llvm/orc/thread_safe_module.cr @@ -0,0 +1,36 @@ +{% skip_file if LibLLVM::IS_LT_110 %} + +@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")] +class LLVM::Orc::ThreadSafeModule + protected def initialize(@unwrap : LibLLVM::OrcThreadSafeModuleRef) + @dispose_on_finalize = true + end + + def self.new(llvm_mod : LLVM::Module, ts_ctx : ThreadSafeContext) + llvm_mod.take_ownership { raise "Failed to take ownership of LLVM::Module" } + new(LibLLVM.orc_create_new_thread_safe_module(llvm_mod, ts_ctx)) + end + + def to_unsafe + @unwrap + end + + def dispose : Nil + LibLLVM.orc_dispose_thread_safe_module(self) + @unwrap = LibLLVM::OrcThreadSafeModuleRef.null + end + + def finalize + if @dispose_on_finalize && @unwrap + dispose + end + end + + def take_ownership(&) : Nil + if @dispose_on_finalize + @dispose_on_finalize = false + else + yield + end + end +end From 5dca1ba606558e28a8ca2dbbf5b47d825e135b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 13 Aug 2024 23:45:41 +0200 Subject: [PATCH 1271/1551] Move `#evented_read`, `#evented_write` into `Crystal::LibEvent::EventLoop` (#14883) This is a simple refactor, moving two methods from `IO::Evented` to `LibEvent::EventLoop`. --- .../system/unix/event_loop_libevent.cr | 47 +++++++++++++++++-- src/crystal/system/wasi/event_loop.cr | 45 ++++++++++++++++-- src/io/evented.cr | 43 ++--------------- 3 files changed, 87 insertions(+), 48 deletions(-) diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr index 32c9c8409b17..b67bad63ff2f 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/system/unix/event_loop_libevent.cr @@ -70,7 +70,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop end def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 - file_descriptor.evented_read("Error reading file_descriptor") do + evented_read(file_descriptor, "Error reading file_descriptor") do LibC.read(file_descriptor.fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF raise IO::Error.new "File not open for reading", target: file_descriptor @@ -80,7 +80,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop end def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 - file_descriptor.evented_write("Error writing file_descriptor") do + evented_write(file_descriptor, "Error writing file_descriptor") do LibC.write(file_descriptor.fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF raise IO::Error.new "File not open for writing", target: file_descriptor @@ -94,13 +94,13 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop end def read(socket : ::Socket, slice : Bytes) : Int32 - socket.evented_read("Error reading socket") do + evented_read(socket, "Error reading socket") do LibC.recv(socket.fd, slice, slice.size, 0).to_i32 end end def write(socket : ::Socket, slice : Bytes) : Int32 - socket.evented_write("Error writing to socket") do + evented_write(socket, "Error writing to socket") do LibC.send(socket.fd, slice, slice.size, 0).to_i32 end end @@ -114,7 +114,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrStorage)) - bytes_read = socket.evented_read("Error receiving datagram") do + bytes_read = evented_read(socket, "Error receiving datagram") do LibC.recvfrom(socket.fd, slice, slice.size, 0, sockaddr, pointerof(addrlen)) end @@ -185,4 +185,41 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop def close(socket : ::Socket) : Nil socket.evented_close end + + def evented_read(target, errno_msg : String, &) : Int32 + loop do + bytes_read = yield + if bytes_read != -1 + # `to_i32` is acceptable because `Slice#size` is an Int32 + return bytes_read.to_i32 + end + + if Errno.value == Errno::EAGAIN + target.wait_readable + else + raise IO::Error.from_errno(errno_msg, target: target) + end + end + ensure + target.evented_resume_pending_readers + end + + def evented_write(target, errno_msg : String, &) : Int32 + begin + loop do + bytes_written = yield + if bytes_written != -1 + return bytes_written.to_i32 + end + + if Errno.value == Errno::EAGAIN + target.wait_writable + else + raise IO::Error.from_errno(errno_msg, target: target) + end + end + ensure + target.evented_resume_pending_writers + end + end end diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr index 5aaf54452571..ba657b917154 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/system/wasi/event_loop.cr @@ -30,7 +30,7 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop end def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 - file_descriptor.evented_read("Error reading file_descriptor") do + evented_read(file_descriptor, "Error reading file_descriptor") do LibC.read(file_descriptor.fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF raise IO::Error.new "File not open for reading", target: file_descriptor @@ -40,7 +40,7 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop end def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 - file_descriptor.evented_write("Error writing file_descriptor") do + evented_write(file_descriptor, "Error writing file_descriptor") do LibC.write(file_descriptor.fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF raise IO::Error.new "File not open for writing", target: file_descriptor @@ -54,13 +54,13 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop end def read(socket : ::Socket, slice : Bytes) : Int32 - socket.evented_read("Error reading socket") do + evented_read(socket, "Error reading socket") do LibC.recv(socket.fd, slice, slice.size, 0).to_i32 end end def write(socket : ::Socket, slice : Bytes) : Int32 - socket.evented_write("Error writing to socket") do + evented_write(socket, "Error writing to socket") do LibC.send(socket.fd, slice, slice.size, 0) end end @@ -84,6 +84,43 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop def close(socket : ::Socket) : Nil socket.evented_close end + + def evented_read(target, errno_msg : String, &) : Int32 + loop do + bytes_read = yield + if bytes_read != -1 + # `to_i32` is acceptable because `Slice#size` is an Int32 + return bytes_read.to_i32 + end + + if Errno.value == Errno::EAGAIN + target.wait_readable + else + raise IO::Error.from_errno(errno_msg, target: target) + end + end + ensure + target.evented_resume_pending_readers + end + + def evented_write(target, errno_msg : String, &) : Int32 + begin + loop do + bytes_written = yield + if bytes_written != -1 + return bytes_written.to_i32 + end + + if Errno.value == Errno::EAGAIN + target.wait_writable + else + raise IO::Error.from_errno(errno_msg, target: target) + end + end + ensure + target.evented_resume_pending_writers + end + end end struct Crystal::Wasi::Event diff --git a/src/io/evented.cr b/src/io/evented.cr index ccc040932285..d2b3a66c336f 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -13,43 +13,6 @@ module IO::Evented @read_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new @write_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new - def evented_read(errno_msg : String, &) : Int32 - loop do - bytes_read = yield - if bytes_read != -1 - # `to_i32` is acceptable because `Slice#size` is an Int32 - return bytes_read.to_i32 - end - - if Errno.value == Errno::EAGAIN - wait_readable - else - raise IO::Error.from_errno(errno_msg, target: self) - end - end - ensure - resume_pending_readers - end - - def evented_write(errno_msg : String, &) : Int32 - begin - loop do - bytes_written = yield - if bytes_written != -1 - return bytes_written.to_i32 - end - - if Errno.value == Errno::EAGAIN - wait_writable - else - raise IO::Error.from_errno(errno_msg, target: self) - end - end - ensure - resume_pending_writers - end - end - # :nodoc: def resume_read(timed_out = false) : Nil @read_timed_out = timed_out @@ -132,13 +95,15 @@ module IO::Evented end end - private def resume_pending_readers + # :nodoc: + def evented_resume_pending_readers if (readers = @readers.get?) && !readers.empty? add_read_event end end - private def resume_pending_writers + # :nodoc: + def evented_resume_pending_writers if (writers = @writers.get?) && !writers.empty? add_write_event end From d54a91fd9e8a9c5ca59fc946d456ddafc38a05f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 13 Aug 2024 23:46:04 +0200 Subject: [PATCH 1272/1551] Avoid flush in finalizers for `Socket` and `IO::FileDescriptor` (#14882) Trying to flush means that it can try to write, hence call into the event loop, that may have to wait for the fd to be writable, which means calling into `epoll_wait`. This all while the world is stopped during a garbage collection run. The event loop implementation may need to allocate, or we get an error and try to raise an exception that will also try to allocate memory... again during a GC collection. For `Socket` this change has little impact becase `sync` is true by default which means every byte is sent directly without buffering. `IO::FileDescriptor` (and `File`) is buffered by default, so this may lead to loss of data if you don't properly close the file descriptor after use. --- spec/std/io/file_descriptor_spec.cr | 34 +++++++++++++++------ spec/std/socket/socket_spec.cr | 28 +++++++++++++++++ src/crystal/system/file.cr | 7 ----- src/crystal/system/file_descriptor.cr | 8 +++++ src/crystal/system/socket.cr | 8 +++++ src/crystal/system/unix/file_descriptor.cr | 10 ++++-- src/crystal/system/unix/socket.cr | 12 +++++++- src/crystal/system/win32/file_descriptor.cr | 6 ++++ src/crystal/system/win32/socket.cr | 12 +++++++- src/io/file_descriptor.cr | 13 +++++++- src/socket.cr | 10 +++++- 11 files changed, 126 insertions(+), 22 deletions(-) diff --git a/spec/std/io/file_descriptor_spec.cr b/spec/std/io/file_descriptor_spec.cr index e497ac1061a3..2e10ea99c030 100644 --- a/spec/std/io/file_descriptor_spec.cr +++ b/spec/std/io/file_descriptor_spec.cr @@ -48,17 +48,33 @@ describe IO::FileDescriptor do end end - it "closes on finalize" do - pipes = [] of IO::FileDescriptor - assert_finalizes("fd") do - a, b = IO.pipe - pipes << b - a + describe "#finalize" do + it "closes" do + pipes = [] of IO::FileDescriptor + assert_finalizes("fd") do + a, b = IO.pipe + pipes << b + a + end + + expect_raises(IO::Error) do + pipes.each do |p| + p.puts "123" + end + end end - expect_raises(IO::Error) do - pipes.each do |p| - p.puts "123" + it "does not flush" do + with_tempfile "fd-finalize-flush" do |path| + file = File.new(path, "w") + file << "foo" + file.flush + file << "bar" + file.finalize + + File.read(path).should eq "foo" + ensure + file.try(&.close) rescue nil end end end diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index d4e7051d12bd..2127e196b746 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -169,4 +169,32 @@ describe Socket, tags: "network" do socket.close_on_exec?.should be_true end {% end %} + + describe "#finalize" do + it "does not flush" do + port = unused_local_port + server = Socket.tcp(Socket::Family::INET) + server.bind("127.0.0.1", port) + server.listen + + spawn do + client = server.not_nil!.accept + client.sync = false + client << "foo" + client.flush + client << "bar" + client.finalize + ensure + client.try(&.close) rescue nil + end + + socket = Socket.tcp(Socket::Family::INET) + socket.connect(Socket::IPAddress.new("127.0.0.1", port)) + + socket.gets.should eq "foo" + ensure + socket.try &.close + server.try &.close + end + end end diff --git a/src/crystal/system/file.cr b/src/crystal/system/file.cr index 75985c107fd5..452bfb6e4ead 100644 --- a/src/crystal/system/file.cr +++ b/src/crystal/system/file.cr @@ -87,13 +87,6 @@ module Crystal::System::File private def self.error_is_file_exists?(errno) errno.in?(Errno::EEXIST, WinError::ERROR_FILE_EXISTS) end - - # Closes the internal file descriptor without notifying libevent. - # This is directly used after the fork of a process to close the - # parent's Crystal::System::Signal.@@pipe reference before re initializing - # the event loop. In the case of a fork that will exec there is even - # no need to initialize the event loop at all. - # def file_descriptor_close end {% if flag?(:wasi) %} diff --git a/src/crystal/system/file_descriptor.cr b/src/crystal/system/file_descriptor.cr index 0180627d59ce..0652ed56e52a 100644 --- a/src/crystal/system/file_descriptor.cr +++ b/src/crystal/system/file_descriptor.cr @@ -14,6 +14,14 @@ module Crystal::System::FileDescriptor # cooked mode otherwise. # private def system_raw(enable : Bool, & : ->) + # Closes the internal file descriptor without notifying the event loop. + # This is directly used after the fork of a process to close the + # parent's Crystal::System::Signal.@@pipe reference before re initializing + # the event loop. In the case of a fork that will exec there is even + # no need to initialize the event loop at all. + # Also used in `IO::FileDescriptor#finalize`. + # def file_descriptor_close + private def system_read(slice : Bytes) : Int32 event_loop.read(self, slice) end diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr index 2669b4c57bca..10f902e9f0c1 100644 --- a/src/crystal/system/socket.cr +++ b/src/crystal/system/socket.cr @@ -91,6 +91,14 @@ module Crystal::System::Socket # private def system_close + # Closes the internal handle without notifying the event loop. + # This is directly used after the fork of a process to close the + # parent's Crystal::System::Signal.@@pipe reference before re initializing + # the event loop. In the case of a fork that will exec there is even + # no need to initialize the event loop at all. + # Also used in `Socket#finalize` + # def socket_close + private def event_loop : Crystal::EventLoop::Socket Crystal::EventLoop.current end diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 0c3ece9cfff8..d235114849b4 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -120,7 +120,7 @@ module Crystal::System::FileDescriptor file_descriptor_close end - def file_descriptor_close : Nil + def file_descriptor_close(&) : Nil # Clear the @volatile_fd before actually closing it in order to # reduce the chance of reading an outdated fd value _fd = @volatile_fd.swap(-1) @@ -130,11 +130,17 @@ module Crystal::System::FileDescriptor when Errno::EINTR, Errno::EINPROGRESS # ignore else - raise IO::Error.from_errno("Error closing file", target: self) + yield end end end + def file_descriptor_close + file_descriptor_close do + raise IO::Error.from_errno("Error closing file", target: self) + end + end + private def system_flock_shared(blocking) flock LibC::FlockOp::SH, blocking end diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 33ac70659b9f..7c39e140849c 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -208,6 +208,10 @@ module Crystal::System::Socket # always lead to undefined results. This is not specific to libevent. event_loop.close(self) + socket_close + end + + private def socket_close(&) # Clear the @volatile_fd before actually closing it in order to # reduce the chance of reading an outdated fd value fd = @volatile_fd.swap(-1) @@ -219,11 +223,17 @@ module Crystal::System::Socket when Errno::EINTR, Errno::EINPROGRESS # ignore else - raise ::Socket::Error.from_errno("Error closing socket") + yield end end end + private def socket_close + socket_close do + raise ::Socket::Error.from_errno("Error closing socket") + end + end + private def system_local_address sockaddr6 = uninitialized LibC::SockaddrIn6 sockaddr = pointerof(sockaddr6).as(LibC::Sockaddr*) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index b39f98fbdf0c..d19e43b79547 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -187,6 +187,12 @@ module Crystal::System::FileDescriptor def file_descriptor_close if LibC.CloseHandle(windows_handle) == 0 + yield + end + end + + def file_descriptor_close + file_descriptor_close do raise IO::Error.from_winerror("Error closing file", target: self) end end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 17e4ca875dbb..78645d51f320 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -366,6 +366,10 @@ module Crystal::System::Socket end def system_close + socket_close + end + + private def socket_close handle = @volatile_fd.swap(LibC::INVALID_SOCKET) ret = LibC.closesocket(handle) @@ -375,11 +379,17 @@ module Crystal::System::Socket when WinError::WSAEINTR, WinError::WSAEINPROGRESS # ignore else - raise ::Socket::Error.from_os_error("Error closing socket", err) + yield err end end end + def socket_close + socket_close do |err| + raise ::Socket::Error.from_os_error("Error closing socket", err) + end + end + private def system_local_address sockaddr6 = uninitialized LibC::SockaddrIn6 sockaddr = pointerof(sockaddr6).as(LibC::Sockaddr*) diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index d4459e9bbe0c..8940a118041f 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -233,10 +233,21 @@ class IO::FileDescriptor < IO system_flock_unlock end + # Finalizes the file descriptor resource. + # + # This involves releasing the handle to the operating system, i.e. closing it. + # It does *not* implicitly call `#flush`, so data waiting in the buffer may be + # lost. + # It's recommended to always close the file descriptor explicitly via `#close` + # (or implicitly using the `.open` constructor). + # + # Resource release can be disabled with `close_on_finalize = false`. + # + # This method is a no-op if the file descriptor has already been closed. def finalize return if closed? || !close_on_finalize? - close rescue nil + file_descriptor_close { } # ignore error end def closed? : Bool diff --git a/src/socket.cr b/src/socket.cr index ca484c0140cc..1d367f805343 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -419,10 +419,18 @@ class Socket < IO self.class.fcntl fd, cmd, arg end + # Finalizes the socket resource. + # + # This involves releasing the handle to the operating system, i.e. closing it. + # It does *not* implicitly call `#flush`, so data waiting in the buffer may be + # lost. By default write buffering is disabled, though (`sync? == true`). + # It's recommended to always close the socket explicitly via `#close`. + # + # This method is a no-op if the file descriptor has already been closed. def finalize return if closed? - close rescue nil + socket_close { } # ignore error end def closed? : Bool From 78c9282c704ca1d1ce83cefb0154ad24e7371d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=C3=B6rjesson?= Date: Wed, 14 Aug 2024 10:12:20 +0200 Subject: [PATCH 1273/1551] Fix: Don't link to undocumented types in API docs (#14878) Co-authored-by: Sijawusz Pur Rahnama --- src/compiler/crystal/tools/doc/html/type.html | 10 +++++----- src/compiler/crystal/tools/doc/templates.cr | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/compiler/crystal/tools/doc/html/type.html b/src/compiler/crystal/tools/doc/html/type.html index 10c7e51fedd3..e9918c6fe429 100644 --- a/src/compiler/crystal/tools/doc/html/type.html +++ b/src/compiler/crystal/tools/doc/html/type.html @@ -45,10 +45,10 @@

    <%= type.formatted_alias_definition %> <% end %> -<%= OtherTypesTemplate.new("Included Modules", type, type.included_modules) %> -<%= OtherTypesTemplate.new("Extended Modules", type, type.extended_modules) %> -<%= OtherTypesTemplate.new("Direct Known Subclasses", type, type.subclasses) %> -<%= OtherTypesTemplate.new("Direct including types", type, type.including_types) %> +<%= OtherTypesTemplate.new("Included Modules", type, included_modules_with_docs) %> +<%= OtherTypesTemplate.new("Extended Modules", type, extended_modules_with_docs) %> +<%= OtherTypesTemplate.new("Direct Known Subclasses", type, subclasses_with_docs) %> +<%= OtherTypesTemplate.new("Direct including types", type, including_types_with_docs) %> <% if locations = type.locations %>

    @@ -99,7 +99,7 @@

    <%= MethodSummaryTemplate.new("Instance Method Summary", type.instance_methods) %>
    - <% type.ancestors.each do |ancestor| %> + <% ancestors_with_docs.each do |ancestor| %> <%= MethodsInheritedTemplate.new(type, ancestor, ancestor.instance_methods, "Instance") %> <%= MethodsInheritedTemplate.new(type, ancestor, ancestor.constructors, "Constructor") %> <%= MethodsInheritedTemplate.new(type, ancestor, ancestor.class_methods, "Class") %> diff --git a/src/compiler/crystal/tools/doc/templates.cr b/src/compiler/crystal/tools/doc/templates.cr index 91ad32e1d0d1..4aaf5ac9029e 100644 --- a/src/compiler/crystal/tools/doc/templates.cr +++ b/src/compiler/crystal/tools/doc/templates.cr @@ -30,6 +30,12 @@ module Crystal::Doc end record TypeTemplate, type : Type, types : Array(Type), project_info : ProjectInfo do + {% for method in %w[ancestors included_modules extended_modules subclasses including_types] %} + def {{method.id}}_with_docs + type.{{method.id}}.select!(&.in?(types)) + end + {% end %} + ECR.def_to_s "#{__DIR__}/html/type.html" end From 38304b351bd4723cac1637cce3e2623f5789735b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 14 Aug 2024 10:14:13 +0200 Subject: [PATCH 1274/1551] Update `LibCrypto` bindings for LibreSSL 3.5+ (#14872) * Fix libssl bindings for LibreSSL 3.5 * [CI] Add test for libreSSL 3.5 * [CI] Add test for libreSSL 3.8 --- .github/workflows/openssl.yml | 30 ++++++++++++++++++++++++++++++ src/openssl/lib_crypto.cr | 8 +++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 46d440d1f6e7..b932ce542e45 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -56,3 +56,33 @@ jobs: run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs run: bin/crystal spec --order=random spec/std/openssl/ + libressl35: + runs-on: ubuntu-latest + name: "LibreSSL 3.5" + container: crystallang/crystal:1.13.1-alpine + steps: + - name: Download Crystal source + uses: actions/checkout@v2 + - name: Uninstall openssl + run: apk del openssl-dev openssl-libs-static + - name: Install libressl 3.5 + run: apk add "libressl-dev=~3.5" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.16/community + - name: Check LibSSL version + run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' + - name: Run OpenSSL specs + run: bin/crystal spec --order=random spec/std/openssl/ + libressl38: + runs-on: ubuntu-latest + name: "LibreSSL 3.5" + container: crystallang/crystal:1.13.1-alpine + steps: + - name: Download Crystal source + uses: actions/checkout@v2 + - name: Uninstall openssl + run: apk del openssl-dev openssl-libs-static + - name: Install libressl 3.8 + run: apk add "libressl-dev=~3.8" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.20/community + - name: Check LibSSL version + run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' + - name: Run OpenSSL specs + run: bin/crystal spec --order=random spec/std/openssl/ diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr index aef6a238f663..8d450b28ff17 100644 --- a/src/openssl/lib_crypto.cr +++ b/src/openssl/lib_crypto.cr @@ -57,7 +57,10 @@ lib LibCrypto struct Bio method : Void* - callback : (Void*, Int, Char*, Int, Long, Long) -> Long + callback : BIO_callback_fn + {% if compare_versions(LIBRESSL_VERSION, "3.5.0") >= 0 %} + callback_ex : BIO_callback_fn_ex + {% end %} cb_arg : Char* init : Int shutdown : Int @@ -72,6 +75,9 @@ lib LibCrypto num_write : ULong end + alias BIO_callback_fn = (Bio*, Int, Char*, Int, Long, Long) -> Long + alias BIO_callback_fn_ex = (Bio*, Int, Char, SizeT, Int, Long, Int, SizeT*) -> Long + PKCS5_SALT_LEN = 8 EVP_MAX_KEY_LENGTH = 32 EVP_MAX_IV_LENGTH = 16 From 19becd57e49ed061fc43d921334bc252627599b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 14 Aug 2024 10:14:40 +0200 Subject: [PATCH 1275/1551] [CI] Add test for OpenSSL 3.3 (#14873) --- .github/workflows/openssl.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index b932ce542e45..8cdb888e3621 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -7,7 +7,7 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: - openssl3: + openssl3_0: runs-on: ubuntu-latest name: "OpenSSL 3.0" container: crystallang/crystal:1.13.1-alpine @@ -24,6 +24,19 @@ jobs: run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs run: bin/crystal spec --order=random spec/std/openssl/ + openssl3_3: + runs-on: ubuntu-latest + name: "OpenSSL 3.3" + container: crystallang/crystal:1.13.1-alpine + steps: + - name: Download Crystal source + uses: actions/checkout@v4 + - name: Install openssl 3.3 + run: apk add "openssl-dev=~3.3" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.20/community + - name: Check LibSSL version + run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' + - name: Run OpenSSL specs + run: bin/crystal spec --order=random spec/std/openssl/ openssl111: runs-on: ubuntu-latest name: "OpenSSL 1.1.1" From 9ecd838d1e81d25606c58e5ca945e6e5531ee140 Mon Sep 17 00:00:00 2001 From: kojix2 <2xijok@gmail.com> Date: Wed, 14 Aug 2024 23:42:48 +0900 Subject: [PATCH 1276/1551] Fix avoid linking `libpcre` when unused (#14891) --- src/regex/pcre.cr | 2 +- src/regex/pcre2.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr index e6cf6eaca7b0..c80714708a0b 100644 --- a/src/regex/pcre.cr +++ b/src/regex/pcre.cr @@ -6,7 +6,7 @@ module Regex::PCRE String.new(LibPCRE.version) end - class_getter version_number : {Int32, Int32} = begin + class_getter version_number : {Int32, Int32} do version = self.version dot = version.index('.') || raise RuntimeError.new("Invalid libpcre2 version") space = version.index(' ', dot) || raise RuntimeError.new("Invalid libpcre2 version") diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index da811225842f..abbb502eb78c 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -13,7 +13,7 @@ module Regex::PCRE2 end end - class_getter version_number : {Int32, Int32} = begin + class_getter version_number : {Int32, Int32} do version = self.version dot = version.index('.') || raise RuntimeError.new("Invalid libpcre2 version") space = version.index(' ', dot) || raise RuntimeError.new("Invalid libpcre2 version") From f0fece0a0a7a637a944b1c9bea004334148b6036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 14 Aug 2024 16:43:03 +0200 Subject: [PATCH 1277/1551] [CI] Update GitHub runner to `macos-14` (#14833) Co-authored-by: Sijawusz Pur Rahnama --- .github/workflows/macos.yml | 12 ++++++++++-- .github/workflows/smoke.yml | 1 - 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c19041c3f52d..d4c93a68aabb 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -11,8 +11,16 @@ env: CI_NIX_SHELL: true jobs: - x86_64-darwin-test: - runs-on: macos-13 + darwin-test: + runs-on: ${{ matrix.runs-on }} + name: ${{ matrix.arch }} + strategy: + matrix: + include: + - runs-on: macos-13 + arch: x86_64-darwin + - runs-on: macos-14 + arch: aarch64-darwin steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 8deffd149dbd..7ae103e528cf 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -51,7 +51,6 @@ jobs: matrix: target: - aarch64-linux-android - - aarch64-darwin - arm-linux-gnueabihf - i386-linux-gnu - i386-linux-musl From 76c6b2f58475fcdfba5d50e09589a91cb3c2a2a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 14 Aug 2024 16:43:37 +0200 Subject: [PATCH 1278/1551] Deprecate `Pointer.new(Int)` (#14875) --- src/pointer.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pointer.cr b/src/pointer.cr index c3ebbf3e56fc..06565298d376 100644 --- a/src/pointer.cr +++ b/src/pointer.cr @@ -420,6 +420,7 @@ struct Pointer(T) # ptr = Pointer(Int32).new(5678) # ptr.address # => 5678 # ``` + @[Deprecated("Call `.new(UInt64)` directly instead")] def self.new(address : Int) new address.to_u64! end From 4f310103d167e5c317088b0a40300fc25dfb8eac Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Wed, 14 Aug 2024 17:30:20 -0400 Subject: [PATCH 1279/1551] Add location info to some `MacroIf` nodes (#14885) --- spec/compiler/parser/parser_spec.cr | 90 +++++++++++++++++++++++++++ src/compiler/crystal/syntax/parser.cr | 5 +- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 22e9c5feb385..db69fa357d59 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2606,6 +2606,96 @@ module Crystal node.end_location.not_nil!.line_number.should eq(5) end + it "sets correct locations of macro if / else" do + parser = Parser.new(<<-CR) + {% if 1 == val %} + "one!" + "bar" + {% else %} + "not one" + "bar" + {% end %} + CR + + node = parser.parse.as MacroIf + + location = node.cond.location.should_not be_nil + location.line_number.should eq 1 + location = node.cond.end_location.should_not be_nil + location.line_number.should eq 1 + + location = node.then.location.should_not be_nil + location.line_number.should eq 1 + location = node.then.end_location.should_not be_nil + location.line_number.should eq 4 + + location = node.else.location.should_not be_nil + location.line_number.should eq 4 + location = node.else.end_location.should_not be_nil + location.line_number.should eq 7 + end + + it "sets correct locations of macro if / elsif" do + parser = Parser.new(<<-CR) + {% if 1 == val %} + "one!" + "bar" + {% elsif 2 == val %} + "not one" + "bar" + {% end %} + CR + + node = parser.parse.as MacroIf + + location = node.cond.location.should_not be_nil + location.line_number.should eq 1 + location = node.cond.end_location.should_not be_nil + location.line_number.should eq 1 + + location = node.then.location.should_not be_nil + location.line_number.should eq 1 + location = node.then.end_location.should_not be_nil + location.line_number.should eq 4 + + location = node.else.location.should_not be_nil + location.line_number.should eq 4 + location = node.else.end_location.should_not be_nil + location.line_number.should eq 7 + end + + it "sets correct locations of macro if / else / elsif" do + parser = Parser.new(<<-CR) + {% if 1 == val %} + "one!" + "bar" + {% elsif 2 == val %} + "not one" + "bar" + {% else %} + "biz" + "blah" + {% end %} + CR + + node = parser.parse.as MacroIf + + location = node.cond.location.should_not be_nil + location.line_number.should eq 1 + location = node.cond.end_location.should_not be_nil + location.line_number.should eq 1 + + location = node.then.location.should_not be_nil + location.line_number.should eq 1 + location = node.then.end_location.should_not be_nil + location.line_number.should eq 4 + + location = node.else.location.should_not be_nil + location.line_number.should eq 4 + location = node.else.end_location.should_not be_nil + location.line_number.should eq 10 + end + it "sets correct location of trailing ensure" do parser = Parser.new("foo ensure bar") node = parser.parse.as(ExceptionHandler) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index bd7c67a975b8..15bd221fd8b2 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3211,7 +3211,7 @@ module Crystal case @token.type when .macro_literal? - pieces << MacroLiteral.new(@token.value.to_s) + pieces << MacroLiteral.new(@token.value.to_s).at(@token.location).at_end(token_end_location) when .macro_expression_start? pieces << MacroExpression.new(parse_macro_expression) check_macro_expression_end @@ -3475,7 +3475,8 @@ module Crystal end when Keyword::ELSIF unexpected_token if is_unless - a_else = parse_macro_if(start_location, macro_state, false) + start_loc = @token.location + a_else = parse_macro_if(start_location, macro_state, false).at(start_loc) if check_end check_ident :end From 7cee8841b9602fca293a02bd0da1be37c068d47d Mon Sep 17 00:00:00 2001 From: "WukongRework.exe BROKE" Date: Wed, 14 Aug 2024 17:31:21 -0400 Subject: [PATCH 1280/1551] Add `LLVM::Builder#finalize` (#14892) `LLVM::Builder` is a class that wraps a `LibLLVM::BuilderRef`. It has a [protected `dispose` method](https://github.com/crystal-lang/crystal/blob/master/src/llvm/builder.cr#L381) which does call `LibLLVM.dispose_builder`. This is ok when a builder is created through a `LLVM::Context` as the `finalize` method disposes of all builders [here](https://github.com/crystal-lang/crystal/blob/master/src/llvm/context.cr#L147). However in some locations, when a builder is created not through a `LLVM::Context`, the builder is leaked. This can be seen through [`LLVM::BasicBlockCollection::append`](https://github.com/crystal-lang/crystal/blob/master/src/llvm/basic_block_collection.cr#L18) where a builder is created in context through the use of `LibLLVM` (not going through the bookkeeping of a `LLVM::Context`) and yielded to the resulting block. --- src/llvm/builder.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr index 3f2060b32084..b406d84145e5 100644 --- a/src/llvm/builder.cr +++ b/src/llvm/builder.cr @@ -387,6 +387,10 @@ class LLVM::Builder LibLLVM.dispose_builder(@unwrap) end + def finalize + dispose + end + # The next lines are for ease debugging when a types/values # are incorrectly used across contexts. From 93ac143279a7bde3448a385d3bfc15b0ed1d8312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 14 Aug 2024 23:32:04 +0200 Subject: [PATCH 1281/1551] Refactor interpreter stack code to avoid duplicate macro expansion (#14876) --- src/compiler/crystal/interpreter/interpreter.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index aa90d83f413f..f73cba958851 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -1000,16 +1000,16 @@ class Crystal::Repl::Interpreter private macro stack_pop(t) %aligned_size = align(sizeof({{t}})) %value = uninitialized {{t}} - (stack - %aligned_size).copy_to(pointerof(%value).as(UInt8*), sizeof({{t}})) + (stack - %aligned_size).copy_to(pointerof(%value).as(UInt8*), sizeof(typeof(%value))) stack_shrink_by(%aligned_size) %value end private macro stack_push(value) %temp = {{value}} - stack.copy_from(pointerof(%temp).as(UInt8*), sizeof(typeof({{value}}))) + %size = sizeof(typeof(%temp)) - %size = sizeof(typeof({{value}})) + stack.copy_from(pointerof(%temp).as(UInt8*), %size) %aligned_size = align(%size) stack += %size stack_grow_by(%aligned_size - %size) From 1a243ad220c7d6df8d9d33ac5f96f0bdc8335d21 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 15 Aug 2024 17:04:48 +0800 Subject: [PATCH 1282/1551] Implement `#sort_by` inside macros using `Enumerable#sort_by` (#14895) Ensures that the block in `ArrayLiteral#sort_by` and `TupleLiteral#sort_by` is called exactly once for each element, by not using `Enumerable#sort` under the hood. --- spec/compiler/macro/macro_methods_spec.cr | 14 ++++++++++---- src/compiler/crystal/macros/methods.cr | 21 +++++++++++++-------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 38b08f44568a..385e165a3504 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -928,6 +928,16 @@ module Crystal assert_macro %({{["c".id, "b", "a".id].sort}}), %([a, "b", c]) end + it "executes sort_by" do + assert_macro %({{["abc", "a", "ab"].sort_by { |x| x.size }}}), %(["a", "ab", "abc"]) + end + + it "calls block exactly once for each element in #sort_by" do + assert_macro <<-CRYSTAL, %(5) + {{ (i = 0; ["abc", "a", "ab", "abcde", "abcd"].sort_by { i += 1 }; i) }} + CRYSTAL + end + it "executes uniq" do assert_macro %({{[1, 1, 1, 2, 3, 1, 2, 3, 4].uniq}}), %([1, 2, 3, 4]) end @@ -1020,10 +1030,6 @@ module Crystal assert_macro %({{{:a => 1, :b => 3}.size}}), "2" end - it "executes sort_by" do - assert_macro %({{["abc", "a", "ab"].sort_by { |x| x.size }}}), %(["a", "ab", "abc"]) - end - it "executes empty?" do assert_macro %({{{:a => 1}.empty?}}), "false" end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index d3a1a1cc15a6..8a7aa569fa95 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -3232,12 +3232,17 @@ end private def sort_by(object, klass, block, interpreter) block_arg = block.args.first? - klass.new(object.elements.sort { |x, y| - block_arg.try { |arg| interpreter.define_var(arg.name, x) } - x_result = interpreter.accept(block.body) - block_arg.try { |arg| interpreter.define_var(arg.name, y) } - y_result = interpreter.accept(block.body) - - x_result.interpret_compare(y_result) - }) + klass.new(object.elements.sort_by do |elem| + block_arg.try { |arg| interpreter.define_var(arg.name, elem) } + result = interpreter.accept(block.body) + InterpretCompareWrapper.new(result) + end) +end + +private record InterpretCompareWrapper, node : Crystal::ASTNode do + include Comparable(self) + + def <=>(other : self) + node.interpret_compare(other.node) + end end From be4a20be82bd82fe1ba6a5af8412dbe5ffb9ede8 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 15 Aug 2024 21:42:42 +0800 Subject: [PATCH 1283/1551] Fix codegen spec for `ProcPointer` of virtual type (#14903) --- spec/compiler/codegen/proc_spec.cr | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/compiler/codegen/proc_spec.cr b/spec/compiler/codegen/proc_spec.cr index 217f2b8ba9a5..48db694429e5 100644 --- a/spec/compiler/codegen/proc_spec.cr +++ b/spec/compiler/codegen/proc_spec.cr @@ -966,7 +966,6 @@ describe "Code gen: proc" do )).to_i.should eq(1) end - # FIXME: JIT compilation of this spec is broken, forcing normal compilation (#10961) it "doesn't crash when taking a proc pointer to a virtual type (#9823)" do run(%( abstract struct Parent @@ -990,7 +989,7 @@ describe "Code gen: proc" do end Child1.new.as(Parent).get - ), flags: [] of String) + ), Proc(Int32, Int32, Int32)) end it "doesn't crash when taking a proc pointer that multidispatches on the top-level (#3822)" do From fa0240b5be832004adc1172e2183b0caac8241fd Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 16 Aug 2024 01:17:33 +0800 Subject: [PATCH 1284/1551] Support LLVM OrcV2 codegen specs (#14886) --- spec/spec_helper.cr | 2 +- src/compiler/crystal/codegen/codegen.cr | 41 ++++++++++++++++++++----- src/llvm/lib_llvm/lljit.cr | 1 + src/llvm/orc/jit_dylib.cr | 4 +-- src/llvm/orc/lljit.cr | 4 +++ 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 31412035ff74..ae7e9a5cefca 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -284,7 +284,7 @@ def create_spec_compiler compiler end -def run(code, filename = nil, inject_primitives = true, debug = Crystal::Debug::None, flags = nil, *, file = __FILE__) +def run(code, filename : String? = nil, inject_primitives = true, debug = Crystal::Debug::None, flags = nil, *, file = __FILE__) : LLVM::GenericValue | SpecRunOutput if inject_primitives code = %(require "primitives"\n#{code}) end diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 67882e9d75dc..f040f87e17f5 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -17,7 +17,7 @@ module Crystal ONCE = "__crystal_once" class Program - def run(code, filename = nil, debug = Debug::Default) + def run(code, filename : String? = nil, debug = Debug::Default) parser = new_parser(code) parser.filename = filename node = parser.parse @@ -79,7 +79,17 @@ module Crystal end def evaluate(node, return_type : T.class, debug = Debug::Default) : T forall T - visitor = CodeGenVisitor.new self, node, single_module: true, debug: debug + llvm_context = + {% if LibLLVM::IS_LT_110 %} + LLVM::Context.new + {% else %} + begin + ts_ctx = LLVM::Orc::ThreadSafeContext.new + ts_ctx.context + end + {% end %} + + visitor = CodeGenVisitor.new self, node, single_module: true, debug: debug, llvm_context: llvm_context visitor.accept node visitor.process_finished_hooks visitor.finish @@ -88,7 +98,6 @@ module Crystal llvm_mod.target = target_machine.triple main = visitor.typed_fun?(llvm_mod, MAIN_NAME).not_nil! - llvm_context = llvm_mod.context # void (*__evaluate_wrapper)(void*) wrapper_type = LLVM::Type.function([llvm_context.void_pointer], llvm_context.void) @@ -111,11 +120,27 @@ module Crystal llvm_mod.verify result = uninitialized T - LLVM::JITCompiler.new(llvm_mod) do |jit| - func_ptr = jit.function_address("__evaluate_wrapper") + + {% if LibLLVM::IS_LT_110 %} + LLVM::JITCompiler.new(llvm_mod) do |jit| + func_ptr = jit.function_address("__evaluate_wrapper") + func = Proc(T*, Nil).new(func_ptr, Pointer(Void).null) + func.call(pointerof(result)) + end + {% else %} + lljit_builder = LLVM::Orc::LLJITBuilder.new + lljit = LLVM::Orc::LLJIT.new(lljit_builder) + + dylib = lljit.main_jit_dylib + dylib.link_symbols_from_current_process(lljit.global_prefix) + tsm = LLVM::Orc::ThreadSafeModule.new(llvm_mod, ts_ctx) + lljit.add_llvm_ir_module(dylib, tsm) + + func_ptr = lljit.lookup("__evaluate_wrapper") func = Proc(T*, Nil).new(func_ptr, Pointer(Void).null) func.call(pointerof(result)) - end + {% end %} + result end @@ -245,9 +270,9 @@ module Crystal def initialize(@program : Program, @node : ASTNode, @single_module : Bool = false, @debug = Debug::Default, - @frame_pointers : FramePointers = :auto) + @frame_pointers : FramePointers = :auto, + @llvm_context : LLVM::Context = LLVM::Context.new) @abi = @program.target_machine.abi - @llvm_context = LLVM::Context.new # LLVM::Context.register(@llvm_context, "main") @llvm_mod = @llvm_context.new_module("main_module") @main_mod = @llvm_mod diff --git a/src/llvm/lib_llvm/lljit.cr b/src/llvm/lib_llvm/lljit.cr index 640973024af4..93c2089c9db0 100644 --- a/src/llvm/lib_llvm/lljit.cr +++ b/src/llvm/lib_llvm/lljit.cr @@ -11,6 +11,7 @@ lib LibLLVM fun orc_dispose_lljit = LLVMOrcDisposeLLJIT(j : OrcLLJITRef) : ErrorRef fun orc_lljit_get_main_jit_dylib = LLVMOrcLLJITGetMainJITDylib(j : OrcLLJITRef) : OrcJITDylibRef + fun orc_lljit_get_global_prefix = LLVMOrcLLJITGetGlobalPrefix(j : OrcLLJITRef) : Char fun orc_lljit_add_llvm_ir_module = LLVMOrcLLJITAddLLVMIRModule(j : OrcLLJITRef, jd : OrcJITDylibRef, tsm : OrcThreadSafeModuleRef) : ErrorRef fun orc_lljit_lookup = LLVMOrcLLJITLookup(j : OrcLLJITRef, result : OrcExecutorAddress*, name : Char*) : ErrorRef end diff --git a/src/llvm/orc/jit_dylib.cr b/src/llvm/orc/jit_dylib.cr index 929dc5e5e6a4..b1050725110b 100644 --- a/src/llvm/orc/jit_dylib.cr +++ b/src/llvm/orc/jit_dylib.cr @@ -9,8 +9,8 @@ class LLVM::Orc::JITDylib @unwrap end - def link_symbols_from_current_process : Nil - LLVM.assert LibLLVM.orc_create_dynamic_library_search_generator_for_process(out dg, 0, nil, nil) + def link_symbols_from_current_process(global_prefix : Char) : Nil + LLVM.assert LibLLVM.orc_create_dynamic_library_search_generator_for_process(out dg, global_prefix.ord.to_u8, nil, nil) LibLLVM.orc_jit_dylib_add_generator(self, dg) end end diff --git a/src/llvm/orc/lljit.cr b/src/llvm/orc/lljit.cr index 6271dea6ea56..62fcc7f0519f 100644 --- a/src/llvm/orc/lljit.cr +++ b/src/llvm/orc/lljit.cr @@ -30,6 +30,10 @@ class LLVM::Orc::LLJIT JITDylib.new(LibLLVM.orc_lljit_get_main_jit_dylib(self)) end + def global_prefix : Char + LibLLVM.orc_lljit_get_global_prefix(self).unsafe_chr + end + def add_llvm_ir_module(dylib : JITDylib, tsm : ThreadSafeModule) : Nil tsm.take_ownership { raise "Failed to take ownership of LLVM::Orc::ThreadSafeModule" } LLVM.assert LibLLVM.orc_lljit_add_llvm_ir_module(self, dylib, tsm) From f451be644a4f7c5194ca21d5367d21a772989906 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 16 Aug 2024 03:48:53 +0800 Subject: [PATCH 1285/1551] Fix misaligned store in `Bool` to union upcasts (#14906) The code path for `Nil` looks similar, but it is perfectly fine: it directly stores `[8 x i64] zeroinitializer` to the data field, whose default alignment naturally matches. --- spec/compiler/codegen/union_type_spec.cr | 19 +++++++++++++++++++ src/compiler/crystal/codegen/unions.cr | 9 ++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/spec/compiler/codegen/union_type_spec.cr b/spec/compiler/codegen/union_type_spec.cr index eb561a92dbdd..8ea7d058bff9 100644 --- a/spec/compiler/codegen/union_type_spec.cr +++ b/spec/compiler/codegen/union_type_spec.cr @@ -215,4 +215,23 @@ describe "Code gen: union type" do Union(Nil, Int32).foo )).to_string.should eq("TupleLiteral") end + + it "respects union payload alignment when upcasting Bool (#14898)" do + mod = codegen(<<-CRYSTAL) + x = uninitialized Bool | UInt8[64] + x = true + CRYSTAL + + str = mod.to_s + {% if LibLLVM::IS_LT_150 %} + str.should contain("store i512 1, i512* %2, align 8") + {% else %} + str.should contain("store i512 1, ptr %1, align 8") + {% end %} + + # an i512 store defaults to 16-byte alignment, which is undefined behavior + # as it overestimates the actual alignment of `x`'s data field (x86 in + # particular segfaults on misaligned 16-byte stores) + str.should_not contain("align 16") + end end diff --git a/src/compiler/crystal/codegen/unions.cr b/src/compiler/crystal/codegen/unions.cr index b2b63a17c5ab..fdf1d81a4c95 100644 --- a/src/compiler/crystal/codegen/unions.cr +++ b/src/compiler/crystal/codegen/unions.cr @@ -81,16 +81,19 @@ module Crystal def store_bool_in_union(target_type, union_pointer, value) struct_type = llvm_type(target_type) + union_value_type = struct_type.struct_element_types[1] store type_id(value, @program.bool), union_type_id(struct_type, union_pointer) # To store a boolean in a union - # we sign-extend it to the size in bits of the union - union_size = @llvm_typer.size_of(struct_type.struct_element_types[1]) + # we zero-extend it to the size in bits of the union + union_size = @llvm_typer.size_of(union_value_type) int_type = llvm_context.int((union_size * 8).to_i32) bool_as_extended_int = builder.zext(value, int_type) casted_value_ptr = pointer_cast(union_value(struct_type, union_pointer), int_type.pointer) - store bool_as_extended_int, casted_value_ptr + inst = store bool_as_extended_int, casted_value_ptr + set_alignment(inst, @llvm_typer.align_of(union_value_type)) + inst end def store_nil_in_union(target_type, union_pointer) From 3bf34106ca718c220629d1977e8db72e935dadad Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Fri, 16 Aug 2024 03:24:41 -0400 Subject: [PATCH 1286/1551] Fix handle empty string in `String#to_f(whitespace: false)` (#14902) Co-authored-by: Sijawusz Pur Rahnama --- spec/std/string_spec.cr | 2 ++ src/string.cr | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 2ea13d52010d..6bb4bd2c0c62 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -321,6 +321,7 @@ describe "String" do it { expect_raises(ArgumentError) { "1__234".to_i } } it { expect_raises(ArgumentError) { "1_234".to_i } } it { expect_raises(ArgumentError) { " 1234 ".to_i(whitespace: false) } } + it { expect_raises(ArgumentError) { "".to_i(whitespace: false) } } it { expect_raises(ArgumentError) { "0x123".to_i } } it { expect_raises(ArgumentError) { "0b123".to_i } } it { expect_raises(ArgumentError) { "000b123".to_i(prefix: true) } } @@ -515,6 +516,7 @@ describe "String" do "nan".to_f?(whitespace: false).try(&.nan?).should be_true " nan".to_f?(whitespace: false).should be_nil "nan ".to_f?(whitespace: false).should be_nil + expect_raises(ArgumentError) { "".to_f(whitespace: false) } "nani".to_f?(strict: true).should be_nil " INF".to_f?.should eq Float64::INFINITY "INF".to_f?.should eq Float64::INFINITY diff --git a/src/string.cr b/src/string.cr index 08bbb87fc505..cf96401253b8 100644 --- a/src/string.cr +++ b/src/string.cr @@ -752,7 +752,8 @@ class String end private def to_f_impl(whitespace : Bool = true, strict : Bool = true, &) - return unless whitespace || '0' <= self[0] <= '9' || self[0].in?('-', '+', 'i', 'I', 'n', 'N') + return unless first_char = self[0]? + return unless whitespace || '0' <= first_char <= '9' || first_char.in?('-', '+', 'i', 'I', 'n', 'N') v, endptr = yield From 7ee895fa8703aa7955b65bd9f12b8f6cf32835a9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 16 Aug 2024 21:57:53 +0800 Subject: [PATCH 1287/1551] Don't spawn subprocess if codegen spec uses flags but not the prelude (#14904) --- spec/compiler/codegen/macro_spec.cr | 5 +++++ spec/spec_helper.cr | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/spec/compiler/codegen/macro_spec.cr b/spec/compiler/codegen/macro_spec.cr index 0cae55711568..fcf1092192b4 100644 --- a/spec/compiler/codegen/macro_spec.cr +++ b/spec/compiler/codegen/macro_spec.cr @@ -1885,4 +1885,9 @@ describe "Code gen: macro" do {% end %} )).to_i.should eq(10) end + + it "accepts compile-time flags" do + run("{{ flag?(:foo) ? 1 : 0 }}", flags: %w(foo)).to_i.should eq(1) + run("{{ flag?(:foo) ? 1 : 0 }}", Int32, flags: %w(foo)).should eq(1) + end end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index ae7e9a5cefca..d3ccdf13fc87 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -294,7 +294,7 @@ def run(code, filename : String? = nil, inject_primitives = true, debug = Crysta # in the current executable!), so instead we compile # the program and run it, printing the last # expression and using that to compare the result. - if code.includes?(%(require "prelude")) || flags + if code.includes?(%(require "prelude")) ast = Parser.parse(code).as(Expressions) last = ast.expressions.last assign = Assign.new(Var.new("__tempvar"), last) @@ -315,7 +315,9 @@ def run(code, filename : String? = nil, inject_primitives = true, debug = Crysta return SpecRunOutput.new(output) end else - new_program.run(code, filename: filename, debug: debug) + program = new_program + program.flags.concat(flags) if flags + program.run(code, filename: filename, debug: debug) end end @@ -324,10 +326,12 @@ def run(code, return_type : T.class, filename : String? = nil, inject_primitives code = %(require "primitives"\n#{code}) end - if code.includes?(%(require "prelude")) || flags + if code.includes?(%(require "prelude")) fail "TODO: support the prelude in typed codegen specs", file: file else - new_program.run(code, return_type: T, filename: filename, debug: debug) + program = new_program + program.flags.concat(flags) if flags + program.run(code, return_type: T, filename: filename, debug: debug) end end From 75ced20b1417b43f83930a22e416984b9d79923f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 18 Aug 2024 19:30:00 +0200 Subject: [PATCH 1288/1551] Extract `select` from `src/channel.cr` (#14912) --- src/channel.cr | 263 +-------------------------- src/channel/select.cr | 158 ++++++++++++++++ src/channel/select/select_action.cr | 45 +++++ src/channel/select/timeout_action.cr | 66 +++++++ 4 files changed, 270 insertions(+), 262 deletions(-) create mode 100644 src/channel/select.cr create mode 100644 src/channel/select/select_action.cr create mode 100644 src/channel/select/timeout_action.cr diff --git a/src/channel.cr b/src/channel.cr index dfd61ff51cc4..4e23f8bb9b09 100644 --- a/src/channel.cr +++ b/src/channel.cr @@ -1,6 +1,7 @@ require "fiber" require "crystal/spin_lock" require "crystal/pointer_linked_list" +require "channel/select" # A `Channel` enables concurrent communication between fibers. # @@ -26,106 +27,15 @@ class Channel(T) @lock = Crystal::SpinLock.new @queue : Deque(T)? - # :nodoc: - record NotReady # :nodoc: record UseDefault - # :nodoc: - module SelectAction(S) - abstract def execute : DeliveryState - abstract def wait(context : SelectContext(S)) - abstract def wait_result_impl(context : SelectContext(S)) - abstract def unwait_impl(context : SelectContext(S)) - abstract def result : S - abstract def lock_object_id - abstract def lock - abstract def unlock - - def create_context_and_wait(shared_state) - context = SelectContext.new(shared_state, self) - self.wait(context) - context - end - - # wait_result overload allow implementors to define - # wait_result_impl with the right type and Channel.select_impl - # to allow dispatching over unions that will not happen - def wait_result(context : SelectContext) - raise "BUG: Unexpected call to #{typeof(self)}#wait_result(context : #{typeof(context)})" - end - - def wait_result(context : SelectContext(S)) - wait_result_impl(context) - end - - # idem wait_result/wait_result_impl - def unwait(context : SelectContext) - raise "BUG: Unexpected call to #{typeof(self)}#unwait(context : #{typeof(context)})" - end - - def unwait(context : SelectContext(S)) - unwait_impl(context) - end - - # Implementor that returns `Channel::UseDefault` in `#execute` - # must redefine `#default_result` - def default_result - raise "Unreachable" - end - end - - private enum SelectState - None = 0 - Active = 1 - Done = 2 - end - - private class SelectContextSharedState - @state : Atomic(SelectState) - - def initialize(value : SelectState) - @state = Atomic(SelectState).new(value) - end - - def compare_and_set(cmp : SelectState, new : SelectState) : {SelectState, Bool} - @state.compare_and_set(cmp, new) - end - end - - private class SelectContext(S) - @state : SelectContextSharedState - property action : SelectAction(S) - @activated = false - - def initialize(@state, @action : SelectAction(S)) - end - - def activated? : Bool - @activated - end - - def try_trigger : Bool - _, succeed = @state.compare_and_set(:active, :done) - if succeed - @activated = true - end - succeed - end - end - class ClosedError < Exception def initialize(msg = "Channel is closed") super(msg) end end - private enum DeliveryState - None - Delivered - Closed - end - private module SenderReceiverCloseAction def close self.state = DeliveryState::Closed @@ -398,112 +308,6 @@ class Channel(T) nil end - # :nodoc: - def self.select(*ops : SelectAction) - self.select ops - end - - # :nodoc: - def self.select(ops : Indexable(SelectAction)) - i, m = select_impl(ops, false) - raise "BUG: Blocking select returned not ready status" if m.is_a?(NotReady) - return i, m - end - - # :nodoc: - def self.non_blocking_select(*ops : SelectAction) - self.non_blocking_select ops - end - - # :nodoc: - def self.non_blocking_select(ops : Indexable(SelectAction)) - select_impl(ops, true) - end - - private def self.select_impl(ops : Indexable(SelectAction), non_blocking) - # ops_locks is a duplicate of ops that can be sorted without disturbing the - # index positions of ops - if ops.responds_to?(:unstable_sort_by!) - # If the collection type implements `unstable_sort_by!` we can dup it. - # This applies to two types: - # * `Array`: `Array#to_a` does not dup and would return the same instance, - # thus we'd be sorting ops and messing up the index positions. - # * `StaticArray`: This avoids a heap allocation because we can dup a - # static array on the stack. - ops_locks = ops.dup - elsif ops.responds_to?(:to_static_array) - # If the collection type implements `to_static_array` we can create a - # copy without allocating an array. This applies to `Tuple` types, which - # the compiler generates for `select` expressions. - ops_locks = ops.to_static_array - else - ops_locks = ops.to_a - end - - # Sort the operations by the channel they contain - # This is to avoid deadlocks between concurrent `select` calls - ops_locks.unstable_sort_by!(&.lock_object_id) - - each_skip_duplicates(ops_locks, &.lock) - - ops.each_with_index do |op, index| - state = op.execute - - case state - in .delivered? - each_skip_duplicates(ops_locks, &.unlock) - return index, op.result - in .closed? - each_skip_duplicates(ops_locks, &.unlock) - return index, op.default_result - in .none? - # do nothing - end - end - - if non_blocking - each_skip_duplicates(ops_locks, &.unlock) - return ops.size, NotReady.new - end - - # Because `channel#close` may clean up a long list, `select_context.try_trigger` may - # be called after the select return. In order to prevent invalid address access, - # the state is allocated in the heap. - shared_state = SelectContextSharedState.new(SelectState::Active) - contexts = ops.map &.create_context_and_wait(shared_state) - - each_skip_duplicates(ops_locks, &.unlock) - Fiber.suspend - - contexts.each_with_index do |context, index| - op = ops[index] - op.lock - op.unwait(context) - op.unlock - end - - contexts.each_with_index do |context, index| - if context.activated? - return index, ops[index].wait_result(context) - end - end - - raise "BUG: Fiber was awaken from select but no action was activated" - end - - private def self.each_skip_duplicates(ops_locks, &) - # Avoid deadlocks from trying to lock the same lock twice. - # `ops_lock` is sorted by `lock_object_id`, so identical onces will be in - # a row and we skip repeats while iterating. - last_lock_id = nil - ops_locks.each do |op| - if op.lock_object_id != last_lock_id - last_lock_id = op.lock_object_id - yield op - end - end - end - # :nodoc: def send_select_action(value : T) SendAction.new(self, value) @@ -699,69 +503,4 @@ class Channel(T) raise ClosedError.new end end - - # :nodoc: - class TimeoutAction - include SelectAction(Nil) - - # Total amount of time to wait - @timeout : Time::Span - @select_context : SelectContext(Nil)? - - def initialize(@timeout : Time::Span) - end - - def execute : DeliveryState - DeliveryState::None - end - - def result : Nil - nil - end - - def wait(context : SelectContext(Nil)) : Nil - @select_context = context - Fiber.timeout(@timeout, self) - end - - def wait_result_impl(context : SelectContext(Nil)) - nil - end - - def unwait_impl(context : SelectContext(Nil)) - Fiber.cancel_timeout - end - - def lock_object_id : UInt64 - self.object_id - end - - def lock - end - - def unlock - end - - def time_expired(fiber : Fiber) : Nil - if @select_context.try &.try_trigger - fiber.enqueue - end - end - end -end - -# Timeout keyword for use in `select`. -# -# ``` -# select -# when x = ch.receive -# puts "got #{x}" -# when timeout(1.seconds) -# puts "timeout" -# end -# ``` -# -# NOTE: It won't trigger if the `select` has an `else` case (i.e.: a non-blocking select). -def timeout_select_action(timeout : Time::Span) : Channel::TimeoutAction - Channel::TimeoutAction.new(timeout) end diff --git a/src/channel/select.cr b/src/channel/select.cr new file mode 100644 index 000000000000..5628fd460e6e --- /dev/null +++ b/src/channel/select.cr @@ -0,0 +1,158 @@ +class Channel(T) + # :nodoc: + record NotReady + + private enum SelectState + None = 0 + Active = 1 + Done = 2 + end + + private class SelectContextSharedState + @state : Atomic(SelectState) + + def initialize(value : SelectState) + @state = Atomic(SelectState).new(value) + end + + def compare_and_set(cmp : SelectState, new : SelectState) : {SelectState, Bool} + @state.compare_and_set(cmp, new) + end + end + + private class SelectContext(S) + @state : SelectContextSharedState + property action : SelectAction(S) + @activated = false + + def initialize(@state, @action : SelectAction(S)) + end + + def activated? : Bool + @activated + end + + def try_trigger : Bool + _, succeed = @state.compare_and_set(:active, :done) + if succeed + @activated = true + end + succeed + end + end + + private enum DeliveryState + None + Delivered + Closed + end + + # :nodoc: + def self.select(*ops : SelectAction) + self.select ops + end + + # :nodoc: + def self.select(ops : Indexable(SelectAction)) + i, m = select_impl(ops, false) + raise "BUG: Blocking select returned not ready status" if m.is_a?(NotReady) + return i, m + end + + # :nodoc: + def self.non_blocking_select(*ops : SelectAction) + self.non_blocking_select ops + end + + # :nodoc: + def self.non_blocking_select(ops : Indexable(SelectAction)) + select_impl(ops, true) + end + + private def self.select_impl(ops : Indexable(SelectAction), non_blocking) + # ops_locks is a duplicate of ops that can be sorted without disturbing the + # index positions of ops + if ops.responds_to?(:unstable_sort_by!) + # If the collection type implements `unstable_sort_by!` we can dup it. + # This applies to two types: + # * `Array`: `Array#to_a` does not dup and would return the same instance, + # thus we'd be sorting ops and messing up the index positions. + # * `StaticArray`: This avoids a heap allocation because we can dup a + # static array on the stack. + ops_locks = ops.dup + elsif ops.responds_to?(:to_static_array) + # If the collection type implements `to_static_array` we can create a + # copy without allocating an array. This applies to `Tuple` types, which + # the compiler generates for `select` expressions. + ops_locks = ops.to_static_array + else + ops_locks = ops.to_a + end + + # Sort the operations by the channel they contain + # This is to avoid deadlocks between concurrent `select` calls + ops_locks.unstable_sort_by!(&.lock_object_id) + + each_skip_duplicates(ops_locks, &.lock) + + ops.each_with_index do |op, index| + state = op.execute + + case state + in .delivered? + each_skip_duplicates(ops_locks, &.unlock) + return index, op.result + in .closed? + each_skip_duplicates(ops_locks, &.unlock) + return index, op.default_result + in .none? + # do nothing + end + end + + if non_blocking + each_skip_duplicates(ops_locks, &.unlock) + return ops.size, NotReady.new + end + + # Because `channel#close` may clean up a long list, `select_context.try_trigger` may + # be called after the select return. In order to prevent invalid address access, + # the state is allocated in the heap. + shared_state = SelectContextSharedState.new(SelectState::Active) + contexts = ops.map &.create_context_and_wait(shared_state) + + each_skip_duplicates(ops_locks, &.unlock) + Fiber.suspend + + contexts.each_with_index do |context, index| + op = ops[index] + op.lock + op.unwait(context) + op.unlock + end + + contexts.each_with_index do |context, index| + if context.activated? + return index, ops[index].wait_result(context) + end + end + + raise "BUG: Fiber was awaken from select but no action was activated" + end + + private def self.each_skip_duplicates(ops_locks, &) + # Avoid deadlocks from trying to lock the same lock twice. + # `ops_lock` is sorted by `lock_object_id`, so identical onces will be in + # a row and we skip repeats while iterating. + last_lock_id = nil + ops_locks.each do |op| + if op.lock_object_id != last_lock_id + last_lock_id = op.lock_object_id + yield op + end + end + end +end + +require "./select/select_action" +require "./select/timeout_action" diff --git a/src/channel/select/select_action.cr b/src/channel/select/select_action.cr new file mode 100644 index 000000000000..d5439fde5587 --- /dev/null +++ b/src/channel/select/select_action.cr @@ -0,0 +1,45 @@ +class Channel(T) + # :nodoc: + module SelectAction(S) + abstract def execute : DeliveryState + abstract def wait(context : SelectContext(S)) + abstract def wait_result_impl(context : SelectContext(S)) + abstract def unwait_impl(context : SelectContext(S)) + abstract def result : S + abstract def lock_object_id + abstract def lock + abstract def unlock + + def create_context_and_wait(shared_state) + context = SelectContext.new(shared_state, self) + self.wait(context) + context + end + + # wait_result overload allow implementors to define + # wait_result_impl with the right type and Channel.select_impl + # to allow dispatching over unions that will not happen + def wait_result(context : SelectContext) + raise "BUG: Unexpected call to #{typeof(self)}#wait_result(context : #{typeof(context)})" + end + + def wait_result(context : SelectContext(S)) + wait_result_impl(context) + end + + # idem wait_result/wait_result_impl + def unwait(context : SelectContext) + raise "BUG: Unexpected call to #{typeof(self)}#unwait(context : #{typeof(context)})" + end + + def unwait(context : SelectContext(S)) + unwait_impl(context) + end + + # Implementor that returns `Channel::UseDefault` in `#execute` + # must redefine `#default_result` + def default_result + raise "Unreachable" + end + end +end diff --git a/src/channel/select/timeout_action.cr b/src/channel/select/timeout_action.cr new file mode 100644 index 000000000000..9240b480db1a --- /dev/null +++ b/src/channel/select/timeout_action.cr @@ -0,0 +1,66 @@ +# Timeout keyword for use in `select`. +# +# ``` +# select +# when x = ch.receive +# puts "got #{x}" +# when timeout(1.seconds) +# puts "timeout" +# end +# ``` +# +# NOTE: It won't trigger if the `select` has an `else` case (i.e.: a non-blocking select). +def timeout_select_action(timeout : Time::Span) : Channel::TimeoutAction + Channel::TimeoutAction.new(timeout) +end + +class Channel(T) + # :nodoc: + class TimeoutAction + include SelectAction(Nil) + + # Total amount of time to wait + @timeout : Time::Span + @select_context : SelectContext(Nil)? + + def initialize(@timeout : Time::Span) + end + + def execute : DeliveryState + DeliveryState::None + end + + def result : Nil + nil + end + + def wait(context : SelectContext(Nil)) : Nil + @select_context = context + Fiber.timeout(@timeout, self) + end + + def wait_result_impl(context : SelectContext(Nil)) + nil + end + + def unwait_impl(context : SelectContext(Nil)) + Fiber.cancel_timeout + end + + def lock_object_id : UInt64 + self.object_id + end + + def lock + end + + def unlock + end + + def time_expired(fiber : Fiber) : Nil + if @select_context.try &.try_trigger + fiber.enqueue + end + end + end +end From 041e8bd8f718632367bb5a4c46736cd00447916a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 18 Aug 2024 19:37:49 +0200 Subject: [PATCH 1289/1551] Update version in `shard.yml` (#14909) --- scripts/release-update.sh | 3 +++ scripts/update-changelog.sh | 4 ++++ shard.yml | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/release-update.sh b/scripts/release-update.sh index c9fa180f6578..b6216ce3d6df 100755 --- a/scripts/release-update.sh +++ b/scripts/release-update.sh @@ -16,6 +16,9 @@ minor_branch="${CRYSTAL_VERSION%.*}" next_minor="$((${minor_branch#*.} + 1))" echo "${CRYSTAL_VERSION%%.*}.${next_minor}.0-dev" > src/VERSION +# Update shard.yml +sed -i -E "s/version: .*/version: $(cat src/VERSION)/" shard.yml + # Remove SOURCE_DATE_EPOCH (only used in source tree of a release) rm -f src/SOURCE_DATE_EPOCH diff --git a/scripts/update-changelog.sh b/scripts/update-changelog.sh index 6fe0fa2839f3..763e63670f43 100755 --- a/scripts/update-changelog.sh +++ b/scripts/update-changelog.sh @@ -44,6 +44,10 @@ git switch $branch 2>/dev/null || git switch -c $branch; echo "${VERSION}" > src/VERSION git add src/VERSION +# Update shard.yml +sed -i -E "s/version: .*/version: ${VERSION}/" shard.yml +git add shard.yml + # Write release date into src/SOURCE_DATE_EPOCH release_date=$(head -n1 $current_changelog | grep -o -P '(?<=\()[^)]+') echo "$(date --utc --date="${release_date}" +%s)" > src/SOURCE_DATE_EPOCH diff --git a/shard.yml b/shard.yml index 396d91bdffe2..85b76f49c8d8 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: crystal -version: 1.13.0-dev +version: 1.14.0-dev authors: - Crystal Core Team From f3fb7b6485ccabe4ce53f7f385a66514af6598a7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 19 Aug 2024 01:38:33 +0800 Subject: [PATCH 1290/1551] Support ARM64 Windows (#14911) Adds ARM64-specific stack unwinding so that the prelude now compiles for Windows on ARM64. For convenience, the x86-64 and ARM64 Windows bindings share the same directory using a symbolic link. The context switch reuses the existing one for AArch64 Linux, since it seems the ABIs are identical; most `Fiber` and `select` specs work, except ones that deal with closed `Channel`s for some reason. --- spec/std/concurrent/select_spec.cr | 148 ++++++++++++--------- src/exception/call_stack/stackwalk.cr | 14 +- src/lib_c/aarch64-windows-msvc | 1 + src/lib_c/x86_64-windows-msvc/c/dbghelp.cr | 1 + src/lib_c/x86_64-windows-msvc/c/winnt.cr | 134 ++++++++++++------- 5 files changed, 181 insertions(+), 117 deletions(-) create mode 120000 src/lib_c/aarch64-windows-msvc diff --git a/spec/std/concurrent/select_spec.cr b/spec/std/concurrent/select_spec.cr index f3f439ddd0b3..5285e3dd070c 100644 --- a/spec/std/concurrent/select_spec.cr +++ b/spec/std/concurrent/select_spec.cr @@ -253,19 +253,23 @@ describe "select" do end end - it "raises if channel was closed" do - ch = Channel(String).new - - spawn_and_check(->{ ch.close }) do |w| - begin - select - when m = ch.receive + {% if flag?(:win32) && flag?(:aarch64) %} + pending "raises if channel was closed" + {% else %} + it "raises if channel was closed" do + ch = Channel(String).new + + spawn_and_check(->{ ch.close }) do |w| + begin + select + when m = ch.receive + end + rescue Channel::ClosedError + w.check end - rescue Channel::ClosedError - w.check end end - end + {% end %} end context "non-blocking raise-on-close single-channel" do @@ -295,20 +299,24 @@ describe "select" do end end - it "raises if channel was closed" do - ch = Channel(String).new + {% if flag?(:win32) && flag?(:aarch64) %} + pending "raises if channel was closed" + {% else %} + it "raises if channel was closed" do + ch = Channel(String).new - spawn_and_check(->{ ch.close }) do |w| - begin - select - when m = ch.receive - else + spawn_and_check(->{ ch.close }) do |w| + begin + select + when m = ch.receive + else + end + rescue Channel::ClosedError + w.check end - rescue Channel::ClosedError - w.check end end - end + {% end %} end context "blocking raise-on-close multi-channel" do @@ -342,37 +350,41 @@ describe "select" do end end - it "raises if channel was closed (1)" do - ch = Channel(String).new - ch2 = Channel(Bool).new + {% if flag?(:win32) && flag?(:aarch64) %} + pending "raises if channel was closed" + {% else %} + it "raises if channel was closed (1)" do + ch = Channel(String).new + ch2 = Channel(Bool).new - spawn_and_check(->{ ch.close }) do |w| - begin - select - when m = ch.receive - when m = ch2.receive + spawn_and_check(->{ ch.close }) do |w| + begin + select + when m = ch.receive + when m = ch2.receive + end + rescue Channel::ClosedError + w.check end - rescue Channel::ClosedError - w.check end end - end - it "raises if channel was closed (2)" do - ch = Channel(String).new - ch2 = Channel(Bool).new + it "raises if channel was closed (2)" do + ch = Channel(String).new + ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.close }) do |w| - begin - select - when m = ch.receive - when m = ch2.receive + spawn_and_check(->{ ch2.close }) do |w| + begin + select + when m = ch.receive + when m = ch2.receive + end + rescue Channel::ClosedError + w.check end - rescue Channel::ClosedError - w.check end end - end + {% end %} end context "non-blocking raise-on-close multi-channel" do @@ -422,39 +434,43 @@ describe "select" do end end - it "raises if channel was closed (1)" do - ch = Channel(String).new - ch2 = Channel(Bool).new + {% if flag?(:win32) && flag?(:aarch64) %} + pending "raises if channel was closed" + {% else %} + it "raises if channel was closed (1)" do + ch = Channel(String).new + ch2 = Channel(Bool).new - spawn_and_check(->{ ch.close }) do |w| - begin - select - when m = ch.receive - when m = ch2.receive - else + spawn_and_check(->{ ch.close }) do |w| + begin + select + when m = ch.receive + when m = ch2.receive + else + end + rescue Channel::ClosedError + w.check end - rescue Channel::ClosedError - w.check end end - end - it "raises if channel was closed (2)" do - ch = Channel(String).new - ch2 = Channel(Bool).new + it "raises if channel was closed (2)" do + ch = Channel(String).new + ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.close }) do |w| - begin - select - when m = ch.receive - when m = ch2.receive - else + spawn_and_check(->{ ch2.close }) do |w| + begin + select + when m = ch.receive + when m = ch2.receive + else + end + rescue Channel::ClosedError + w.check end - rescue Channel::ClosedError - w.check end end - end + {% end %} end context "blocking nil-on-close single-channel" do diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index 2b9a03b472c7..b3e2ed8f479c 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -93,6 +93,8 @@ struct Exception::CallStack {% elsif flag?(:i386) %} # TODO: use WOW64_CONTEXT in place of CONTEXT {% raise "x86 not supported" %} + {% elsif flag?(:aarch64) %} + LibC::IMAGE_FILE_MACHINE_ARM64 {% else %} {% raise "Architecture not supported" %} {% end %} @@ -102,9 +104,15 @@ struct Exception::CallStack stack_frame.addrFrame.mode = LibC::ADDRESS_MODE::AddrModeFlat stack_frame.addrStack.mode = LibC::ADDRESS_MODE::AddrModeFlat - stack_frame.addrPC.offset = context.value.rip - stack_frame.addrFrame.offset = context.value.rbp - stack_frame.addrStack.offset = context.value.rsp + {% if flag?(:x86_64) %} + stack_frame.addrPC.offset = context.value.rip + stack_frame.addrFrame.offset = context.value.rbp + stack_frame.addrStack.offset = context.value.rsp + {% elsif flag?(:aarch64) %} + stack_frame.addrPC.offset = context.value.pc + stack_frame.addrFrame.offset = context.value.x[29] + stack_frame.addrStack.offset = context.value.sp + {% end %} last_frame = nil cur_proc = LibC.GetCurrentProcess diff --git a/src/lib_c/aarch64-windows-msvc b/src/lib_c/aarch64-windows-msvc new file mode 120000 index 000000000000..072348f65d09 --- /dev/null +++ b/src/lib_c/aarch64-windows-msvc @@ -0,0 +1 @@ +x86_64-windows-msvc \ No newline at end of file diff --git a/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr b/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr index af37cb0c7f0c..2c62d07d3ad8 100644 --- a/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr +++ b/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr @@ -122,6 +122,7 @@ lib LibC end IMAGE_FILE_MACHINE_AMD64 = DWORD.new!(0x8664) + IMAGE_FILE_MACHINE_ARM64 = DWORD.new!(0xAA64) alias PREAD_PROCESS_MEMORY_ROUTINE64 = HANDLE, DWORD64, Void*, DWORD, DWORD* -> BOOL alias PFUNCTION_TABLE_ACCESS_ROUTINE64 = HANDLE, DWORD64 -> Void* diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index e1f133dcae48..535ad835c87a 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -140,54 +140,84 @@ lib LibC JOB_OBJECT_MSG_EXIT_PROCESS = 7 JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS = 8 - struct CONTEXT - p1Home : DWORD64 - p2Home : DWORD64 - p3Home : DWORD64 - p4Home : DWORD64 - p5Home : DWORD64 - p6Home : DWORD64 - contextFlags : DWORD - mxCsr : DWORD - segCs : WORD - segDs : WORD - segEs : WORD - segFs : WORD - segGs : WORD - segSs : WORD - eFlags : DWORD - dr0 : DWORD64 - dr1 : DWORD64 - dr2 : DWORD64 - dr3 : DWORD64 - dr6 : DWORD64 - dr7 : DWORD64 - rax : DWORD64 - rcx : DWORD64 - rdx : DWORD64 - rbx : DWORD64 - rsp : DWORD64 - rbp : DWORD64 - rsi : DWORD64 - rdi : DWORD64 - r8 : DWORD64 - r9 : DWORD64 - r10 : DWORD64 - r11 : DWORD64 - r12 : DWORD64 - r13 : DWORD64 - r14 : DWORD64 - r15 : DWORD64 - rip : DWORD64 - fltSave : UInt8[512] # DUMMYUNIONNAME - vectorRegister : UInt8[16][26] # M128A[26] - vectorControl : DWORD64 - debugControl : DWORD64 - lastBranchToRip : DWORD64 - lastBranchFromRip : DWORD64 - lastExceptionToRip : DWORD64 - lastExceptionFromRip : DWORD64 - end + {% if flag?(:x86_64) %} + struct CONTEXT + p1Home : DWORD64 + p2Home : DWORD64 + p3Home : DWORD64 + p4Home : DWORD64 + p5Home : DWORD64 + p6Home : DWORD64 + contextFlags : DWORD + mxCsr : DWORD + segCs : WORD + segDs : WORD + segEs : WORD + segFs : WORD + segGs : WORD + segSs : WORD + eFlags : DWORD + dr0 : DWORD64 + dr1 : DWORD64 + dr2 : DWORD64 + dr3 : DWORD64 + dr6 : DWORD64 + dr7 : DWORD64 + rax : DWORD64 + rcx : DWORD64 + rdx : DWORD64 + rbx : DWORD64 + rsp : DWORD64 + rbp : DWORD64 + rsi : DWORD64 + rdi : DWORD64 + r8 : DWORD64 + r9 : DWORD64 + r10 : DWORD64 + r11 : DWORD64 + r12 : DWORD64 + r13 : DWORD64 + r14 : DWORD64 + r15 : DWORD64 + rip : DWORD64 + fltSave : UInt8[512] # DUMMYUNIONNAME + vectorRegister : UInt8[16][26] # M128A[26] + vectorControl : DWORD64 + debugControl : DWORD64 + lastBranchToRip : DWORD64 + lastBranchFromRip : DWORD64 + lastExceptionToRip : DWORD64 + lastExceptionFromRip : DWORD64 + end + {% elsif flag?(:aarch64) %} + struct ARM64_NT_NEON128_DUMMYSTRUCTNAME + low : ULongLong + high : LongLong + end + + union ARM64_NT_NEON128 + dummystructname : ARM64_NT_NEON128_DUMMYSTRUCTNAME + d : Double[2] + s : Float[4] + h : WORD[8] + b : BYTE[16] + end + + struct CONTEXT + contextFlags : DWORD + cpsr : DWORD + x : DWORD64[31] # x29 = fp, x30 = lr + sp : DWORD64 + pc : DWORD64 + v : ARM64_NT_NEON128[32] + fpcr : DWORD + fpsr : DWORD + bcr : DWORD[8] + bvr : DWORD64[8] + wcr : DWORD[8] + wvr : DWORD64[8] + end + {% end %} {% if flag?(:x86_64) %} CONTEXT_AMD64 = DWORD.new!(0x00100000) @@ -211,6 +241,14 @@ lib LibC CONTEXT_EXTENDED_REGISTERS = CONTEXT_i386 | 0x00000020 CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS + {% elsif flag?(:aarch64) %} + CONTEXT_ARM64 = DWORD.new!(0x00400000) + + CONTEXT_ARM64_CONTROL = CONTEXT_ARM64 | 0x1 + CONTEXT_ARM64_INTEGER = CONTEXT_ARM64 | 0x2 + CONTEXT_ARM64_FLOATING_POINT = CONTEXT_ARM64 | 0x4 + + CONTEXT_FULL = CONTEXT_ARM64_CONTROL | CONTEXT_ARM64_INTEGER | CONTEXT_ARM64_FLOATING_POINT {% end %} fun RtlCaptureContext(contextRecord : CONTEXT*) From 95a761e8d18752ac474f4dc4b0b47fd713352110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 19 Aug 2024 11:13:23 +0200 Subject: [PATCH 1291/1551] Revert "Fix: Don't link to undocumented types in API docs" (#14908) This reverts commit 78c9282c704ca1d1ce83cefb0154ad24e7371d28. --- src/compiler/crystal/tools/doc/html/type.html | 10 +++++----- src/compiler/crystal/tools/doc/templates.cr | 6 ------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/compiler/crystal/tools/doc/html/type.html b/src/compiler/crystal/tools/doc/html/type.html index e9918c6fe429..10c7e51fedd3 100644 --- a/src/compiler/crystal/tools/doc/html/type.html +++ b/src/compiler/crystal/tools/doc/html/type.html @@ -45,10 +45,10 @@

    <%= type.formatted_alias_definition %> <% end %> -<%= OtherTypesTemplate.new("Included Modules", type, included_modules_with_docs) %> -<%= OtherTypesTemplate.new("Extended Modules", type, extended_modules_with_docs) %> -<%= OtherTypesTemplate.new("Direct Known Subclasses", type, subclasses_with_docs) %> -<%= OtherTypesTemplate.new("Direct including types", type, including_types_with_docs) %> +<%= OtherTypesTemplate.new("Included Modules", type, type.included_modules) %> +<%= OtherTypesTemplate.new("Extended Modules", type, type.extended_modules) %> +<%= OtherTypesTemplate.new("Direct Known Subclasses", type, type.subclasses) %> +<%= OtherTypesTemplate.new("Direct including types", type, type.including_types) %> <% if locations = type.locations %>

    @@ -99,7 +99,7 @@

    <%= MethodSummaryTemplate.new("Instance Method Summary", type.instance_methods) %>
    - <% ancestors_with_docs.each do |ancestor| %> + <% type.ancestors.each do |ancestor| %> <%= MethodsInheritedTemplate.new(type, ancestor, ancestor.instance_methods, "Instance") %> <%= MethodsInheritedTemplate.new(type, ancestor, ancestor.constructors, "Constructor") %> <%= MethodsInheritedTemplate.new(type, ancestor, ancestor.class_methods, "Class") %> diff --git a/src/compiler/crystal/tools/doc/templates.cr b/src/compiler/crystal/tools/doc/templates.cr index 4aaf5ac9029e..91ad32e1d0d1 100644 --- a/src/compiler/crystal/tools/doc/templates.cr +++ b/src/compiler/crystal/tools/doc/templates.cr @@ -30,12 +30,6 @@ module Crystal::Doc end record TypeTemplate, type : Type, types : Array(Type), project_info : ProjectInfo do - {% for method in %w[ancestors included_modules extended_modules subclasses including_types] %} - def {{method.id}}_with_docs - type.{{method.id}}.select!(&.in?(types)) - end - {% end %} - ECR.def_to_s "#{__DIR__}/html/type.html" end From 12372105ae21861c28dbaad77f7c9312dd296e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 19 Aug 2024 16:36:58 +0200 Subject: [PATCH 1292/1551] CI: Refactor SSL workflow with job matrix (#14899) --- .github/workflows/openssl.yml | 107 ++++++++-------------------------- 1 file changed, 23 insertions(+), 84 deletions(-) diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 8cdb888e3621..d518c93a51de 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -7,95 +7,34 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: - openssl3_0: + libssl_test: runs-on: ubuntu-latest - name: "OpenSSL 3.0" + name: "${{ matrix.pkg }}" container: crystallang/crystal:1.13.1-alpine + strategy: + fail-fast: false + matrix: + include: + - pkg: "openssl1.1-compat-dev=~1.1.1" + repository: http://dl-cdn.alpinelinux.org/alpine/v3.18/community + - pkg: "openssl-dev=~3.0" + repository: http://dl-cdn.alpinelinux.org/alpine/v3.17/main + - pkg: "openssl-dev=~3.3" + repository: http://dl-cdn.alpinelinux.org/alpine/v3.20/main + - pkg: "libressl-dev=~3.4" + repository: http://dl-cdn.alpinelinux.org/alpine/v3.15/community + - pkg: "libressl-dev=~3.5" + repository: http://dl-cdn.alpinelinux.org/alpine/v3.16/community + - pkg: "libressl-dev=~3.8" + repository: http://dl-cdn.alpinelinux.org/alpine/v3.20/community steps: - name: Download Crystal source uses: actions/checkout@v4 - - name: Uninstall openssl - run: apk del openssl-dev libxml2-static - - name: Upgrade alpine-keys - run: apk upgrade alpine-keys - - name: Install openssl 3.0 - run: apk add "openssl-dev=~3.0" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.17/main - - name: Check LibSSL version - run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - - name: Run OpenSSL specs - run: bin/crystal spec --order=random spec/std/openssl/ - openssl3_3: - runs-on: ubuntu-latest - name: "OpenSSL 3.3" - container: crystallang/crystal:1.13.1-alpine - steps: - - name: Download Crystal source - uses: actions/checkout@v4 - - name: Install openssl 3.3 - run: apk add "openssl-dev=~3.3" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.20/community - - name: Check LibSSL version - run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - - name: Run OpenSSL specs - run: bin/crystal spec --order=random spec/std/openssl/ - openssl111: - runs-on: ubuntu-latest - name: "OpenSSL 1.1.1" - container: crystallang/crystal:1.13.1-alpine - steps: - - name: Download Crystal source - uses: actions/checkout@v4 - - name: Uninstall openssl - run: apk del openssl-dev - - name: Install openssl 1.1.1 - run: apk add "openssl1.1-compat-dev=~1.1.1" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.18/community - - name: Check LibSSL version - run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - - name: Run OpenSSL specs - run: bin/crystal spec --order=random spec/std/openssl/ - libressl34: - runs-on: ubuntu-latest - name: "LibreSSL 3.4" - container: crystallang/crystal:1.13.1-alpine - steps: - - name: Download Crystal source - uses: actions/checkout@v4 - - name: Uninstall openssl - run: apk del openssl-dev openssl-libs-static - - name: Upgrade alpine-keys - run: apk upgrade alpine-keys - - name: Install libressl 3.4 - run: apk add "libressl-dev=~3.4" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.15/community - - name: Check LibSSL version - run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - - name: Run OpenSSL specs - run: bin/crystal spec --order=random spec/std/openssl/ - libressl35: - runs-on: ubuntu-latest - name: "LibreSSL 3.5" - container: crystallang/crystal:1.13.1-alpine - steps: - - name: Download Crystal source - uses: actions/checkout@v2 - - name: Uninstall openssl - run: apk del openssl-dev openssl-libs-static - - name: Install libressl 3.5 - run: apk add "libressl-dev=~3.5" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.16/community - - name: Check LibSSL version - run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - - name: Run OpenSSL specs - run: bin/crystal spec --order=random spec/std/openssl/ - libressl38: - runs-on: ubuntu-latest - name: "LibreSSL 3.5" - container: crystallang/crystal:1.13.1-alpine - steps: - - name: Download Crystal source - uses: actions/checkout@v2 - - name: Uninstall openssl - run: apk del openssl-dev openssl-libs-static - - name: Install libressl 3.8 - run: apk add "libressl-dev=~3.8" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.20/community - - name: Check LibSSL version + - name: Uninstall openssl and conflicts + run: apk del openssl-dev openssl-libs-static libxml2-static + - name: Install ${{ matrix.pkg }} + run: apk add "${{ matrix.pkg }}" --repository=${{ matrix.repository }} + - name: Print LibSSL version run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION' - name: Run OpenSSL specs run: bin/crystal spec --order=random spec/std/openssl/ From 2fcb168588820b785a3e17467c93d2b709c53df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 19 Aug 2024 16:38:13 +0200 Subject: [PATCH 1293/1551] Add `Slice#same?` (#14728) --- spec/std/slice_spec.cr | 14 ++++++++++++++ src/slice.cr | 15 +++++++++++++++ src/spec/expectations.cr | 12 ++++++++++-- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr index 1b21a4489bbd..7624b34c852c 100644 --- a/spec/std/slice_spec.cr +++ b/spec/std/slice_spec.cr @@ -503,6 +503,20 @@ describe "Slice" do end end + it "#same?" do + slice = Slice[1, 2, 3] + + slice.should be slice + slice.should_not be slice.dup + slice.should_not be Slice[1, 2, 3] + + (slice + 1).should be slice + 1 + slice.should_not be slice + 1 + + (slice[0, 2]).should be slice[0, 2] + slice.should_not be slice[0, 2] + end + it "does macro []" do slice = Slice[1, 'a', "foo"] slice.should be_a(Slice(Int32 | Char | String)) diff --git a/src/slice.cr b/src/slice.cr index d843ceb17c63..c87816f315d9 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -859,6 +859,21 @@ struct Slice(T) {% end %} end + # Returns `true` if `self` and *other* point to the same memory, i.e. pointer + # and size are identical. + # + # ``` + # slice = Slice[1, 2, 3] + # slice.same?(slice) # => true + # slice == Slice[1, 2, 3] # => false + # slice.same?(slice + 1) # => false + # (slice + 1).same?(slice + 1) # => true + # slice.same?(slice[0, 2]) # => false + # ``` + def same?(other : self) : Bool + to_unsafe == other.to_unsafe && size == other.size + end + def to_slice : self self end diff --git a/src/spec/expectations.cr b/src/spec/expectations.cr index ac93de54975e..193f86d0de21 100644 --- a/src/spec/expectations.cr +++ b/src/spec/expectations.cr @@ -65,11 +65,19 @@ module Spec end def failure_message(actual_value) - "Expected: #{@expected_value.pretty_inspect} (object_id: #{@expected_value.object_id})\n got: #{actual_value.pretty_inspect} (object_id: #{actual_value.object_id})" + "Expected: #{@expected_value.pretty_inspect} (#{identify(@expected_value)})\n got: #{actual_value.pretty_inspect} (#{identify(actual_value)})" end def negative_failure_message(actual_value) - "Expected: value.same? #{@expected_value.pretty_inspect} (object_id: #{@expected_value.object_id})\n got: #{actual_value.pretty_inspect} (object_id: #{actual_value.object_id})" + "Expected: #{@expected_value.pretty_inspect} (#{identify(@expected_value)})\n got: #{actual_value.pretty_inspect} (#{identify(actual_value)})" + end + + private def identify(value) + if value.responds_to?(:object_id) + "object_id: #{value.object_id}" + else + value.to_unsafe + end end end From a9e04578a10c9dc351cadd1b960a8bde4eeb24d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Gw=C3=B3=C5=BAd=C5=BA?= Date: Tue, 20 Aug 2024 01:07:13 +0200 Subject: [PATCH 1294/1551] Add `HashLiteral#has_key?` and `NamedTupleLiteral#has_key?` (#14890) --- spec/compiler/macro/macro_methods_spec.cr | 14 ++++++++++++ src/compiler/crystal/macros.cr | 8 +++++++ src/compiler/crystal/macros/methods.cr | 27 ++++++++++++++--------- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 385e165a3504..9d425cb7e162 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1090,6 +1090,12 @@ module Crystal assert_macro %({{ {'z' => 6, 'a' => 9}.of_value }}), %() end + it "executes has_key?" do + assert_macro %({{ {'z' => 6, 'a' => 9}.has_key?('z') }}), %(true) + assert_macro %({{ {'z' => 6, 'a' => 9}.has_key?('x') }}), %(false) + assert_macro %({{ {'z' => nil, 'a' => 9}.has_key?('z') }}), %(true) + end + it "executes type" do assert_macro %({{ x.type }}), %(Headers), {x: HashLiteral.new([] of HashLiteral::Entry, name: Path.new("Headers"))} end @@ -1195,6 +1201,14 @@ module Crystal assert_macro %({% a = {a: 1}; a["a"] = 2 %}{{a["a"]}}), "2" end + it "executes has_key?" do + assert_macro %({{{a: 1}.has_key?("a")}}), "true" + assert_macro %({{{a: 1}.has_key?(:a)}}), "true" + assert_macro %({{{a: nil}.has_key?("a")}}), "true" + assert_macro %({{{a: nil}.has_key?("b")}}), "false" + assert_macro_error %({{{a: 1}.has_key?(true)}}), "expected 'NamedTupleLiteral#has_key?' first argument to be a SymbolLiteral, StringLiteral or MacroId, not BoolLiteral" + end + it "creates a named tuple literal with a var" do assert_macro %({% a = {a: x} %}{{a[:a]}}), "1", {x: 1.int32} end diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index ff422ce553a2..a2ea0aeb85fe 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -800,6 +800,10 @@ module Crystal::Macros def []=(key : ASTNode, value : ASTNode) : ASTNode end + # Similar to `Hash#has_hey?` + def has_key?(key : ASTNode) : BoolLiteral + end + # Returns the type specified at the end of the Hash literal, if any. # # This refers to the key type after brackets in `{} of String => Int32`. @@ -874,6 +878,10 @@ module Crystal::Macros # Adds or replaces a key. def []=(key : SymbolLiteral | StringLiteral | MacroId, value : ASTNode) : ASTNode end + + # Similar to `NamedTuple#has_key?` + def has_key?(key : SymbolLiteral | StringLiteral | MacroId) : ASTNode + end end # A range literal. diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 8a7aa569fa95..3a81015f0ffd 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -965,6 +965,10 @@ module Crystal interpret_check_args { @of.try(&.key) || Nop.new } when "of_value" interpret_check_args { @of.try(&.value) || Nop.new } + when "has_key?" + interpret_check_args do |key| + BoolLiteral.new(entries.any? &.key.==(key)) + end when "type" interpret_check_args { @name || Nop.new } when "clear" @@ -1042,11 +1046,7 @@ module Crystal when "[]" interpret_check_args do |key| case key - when SymbolLiteral - key = key.value - when MacroId - key = key.value - when StringLiteral + when SymbolLiteral, MacroId, StringLiteral key = key.value else raise "argument to [] must be a symbol or string, not #{key.class_desc}:\n\n#{key}" @@ -1058,11 +1058,7 @@ module Crystal when "[]=" interpret_check_args do |key, value| case key - when SymbolLiteral - key = key.value - when MacroId - key = key.value - when StringLiteral + when SymbolLiteral, MacroId, StringLiteral key = key.value else raise "expected 'NamedTupleLiteral#[]=' first argument to be a SymbolLiteral or MacroId, not #{key.class_desc}" @@ -1077,6 +1073,17 @@ module Crystal value end + when "has_key?" + interpret_check_args do |key| + case key + when SymbolLiteral, MacroId, StringLiteral + key = key.value + else + raise "expected 'NamedTupleLiteral#has_key?' first argument to be a SymbolLiteral, StringLiteral or MacroId, not #{key.class_desc}" + end + + BoolLiteral.new(entries.any? &.key.==(key)) + end else super end From 74279908e6e5cf848fe3138df8a9d0e1a326a243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 20 Aug 2024 01:07:21 +0200 Subject: [PATCH 1295/1551] Add `Pointer::Appender#to_slice` (#14874) --- spec/std/pointer/appender_spec.cr | 14 ++++++++++++++ src/base64.cr | 2 +- src/crystal/system/print_error.cr | 4 ++-- src/crystal/system/win32/file_descriptor.cr | 2 +- src/pointer.cr | 14 ++++++++++++++ 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/spec/std/pointer/appender_spec.cr b/spec/std/pointer/appender_spec.cr index 02ca18e0188e..54aff72c9349 100644 --- a/spec/std/pointer/appender_spec.cr +++ b/spec/std/pointer/appender_spec.cr @@ -25,4 +25,18 @@ describe Pointer::Appender do end appender.size.should eq 4 end + + it "#to_slice" do + data = Slice(Int32).new(5) + appender = data.to_unsafe.appender + appender.to_slice.should eq Slice(Int32).new(0) + appender.to_slice.to_unsafe.should eq data.to_unsafe + + 4.times do |i| + appender << (i + 1) * 2 + appender.to_slice.should eq data[0, i + 1] + end + appender.to_slice.should eq Slice[2, 4, 6, 8] + appender.to_slice.to_unsafe.should eq data.to_unsafe + end end diff --git a/src/base64.cr b/src/base64.cr index 241d00c57bda..951684afc7ef 100644 --- a/src/base64.cr +++ b/src/base64.cr @@ -163,7 +163,7 @@ module Base64 buf = Pointer(UInt8).malloc(decode_size(slice.size)) appender = buf.appender from_base64(slice) { |byte| appender << byte } - Slice.new(buf, appender.size.to_i32) + appender.to_slice end # Writes the base64-decoded version of *data* to *io*. diff --git a/src/crystal/system/print_error.cr b/src/crystal/system/print_error.cr index 796579bf256a..b55e05e51ec6 100644 --- a/src/crystal/system/print_error.cr +++ b/src/crystal/system/print_error.cr @@ -23,7 +23,7 @@ module Crystal::System String.each_utf16_char(bytes) do |char| if appender.size > utf8.size - char.bytesize # buffer is full (char won't fit) - print_error utf8.to_slice[0...appender.size] + print_error appender.to_slice appender = utf8.to_unsafe.appender end @@ -33,7 +33,7 @@ module Crystal::System end if appender.size > 0 - print_error utf8.to_slice[0...appender.size] + print_error appender.to_slice end end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index d19e43b79547..7899f75407f7 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -438,7 +438,7 @@ private module ConsoleUtils appender << byte end end - @@buffer = @@utf8_buffer[0, appender.size] + @@buffer = appender.to_slice end private def self.read_console(handle : LibC::HANDLE, slice : Slice(UInt16)) : Int32 diff --git a/src/pointer.cr b/src/pointer.cr index 06565298d376..87da18b25fa5 100644 --- a/src/pointer.cr +++ b/src/pointer.cr @@ -52,6 +52,20 @@ struct Pointer(T) def pointer @pointer end + + # Creates a slice pointing at the values appended by this instance. + # + # ``` + # slice = Slice(Int32).new(5) + # appender = slice.to_unsafe.appender + # appender << 1 + # appender << 2 + # appender << 3 + # appender.to_slice # => Slice[1, 2, 3] + # ``` + def to_slice : Slice(T) + @start.to_slice(size) + end end include Comparable(self) From ee894e0e6db90d308a1cc72c96fd99d237ef8a4a Mon Sep 17 00:00:00 2001 From: Margret Riegert Date: Mon, 19 Aug 2024 19:08:02 -0400 Subject: [PATCH 1296/1551] Add documentation for `NoReturn` and `Void` (#14817) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller Co-authored-by: Sijawusz Pur Rahnama --- src/compiler/crystal/tools/doc/generator.cr | 2 ++ src/compiler/crystal/tools/doc/type.cr | 19 +++++++++++-- src/docs_pseudo_methods.cr | 30 +++++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index 635a6be65731..a2f4db47dee0 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -217,6 +217,8 @@ class Crystal::Doc::Generator def crystal_builtin?(type) return false unless project_info.crystal_stdlib? + # TODO: Enabling this allows links to `NoReturn` to work, but has two `NoReturn`s show up in the sidebar + # return true if type.is_a?(NamedType) && {"NoReturn", "Void"}.includes?(type.name) return false unless type.is_a?(Const) || type.is_a?(NonGenericModuleType) crystal_type = @program.types["Crystal"] diff --git a/src/compiler/crystal/tools/doc/type.cr b/src/compiler/crystal/tools/doc/type.cr index 9a40bd23e189..624c8f017fe7 100644 --- a/src/compiler/crystal/tools/doc/type.cr +++ b/src/compiler/crystal/tools/doc/type.cr @@ -3,6 +3,13 @@ require "./item" class Crystal::Doc::Type include Item + PSEUDO_CLASS_PREFIX = "CRYSTAL_PSEUDO__" + PSEUDO_CLASS_NOTE = <<-DOC + + NOTE: This is a pseudo-class provided directly by the Crystal compiler. + It cannot be reopened nor overridden. + DOC + getter type : Crystal::Type def initialize(@generator : Generator, type : Crystal::Type) @@ -39,7 +46,11 @@ class Crystal::Doc::Type when Program "Top Level Namespace" when NamedType - type.name + if @generator.project_info.crystal_stdlib? + type.name.lchop(PSEUDO_CLASS_PREFIX) + else + type.name + end when NoReturnType "NoReturn" when VoidType @@ -403,7 +414,11 @@ class Crystal::Doc::Type end def doc - @type.doc + if (t = type).is_a?(NamedType) && t.name.starts_with?(PSEUDO_CLASS_PREFIX) + "#{@type.doc}#{PSEUDO_CLASS_NOTE}" + else + @type.doc + end end def lookup_path(path_or_names : Path | Array(String)) diff --git a/src/docs_pseudo_methods.cr b/src/docs_pseudo_methods.cr index d4f1fb832263..36eb1f09eaff 100644 --- a/src/docs_pseudo_methods.cr +++ b/src/docs_pseudo_methods.cr @@ -200,3 +200,33 @@ class Object def __crystal_pseudo_responds_to?(name : Symbol) : Bool end end + +# Some expressions won't return to the current scope and therefore have no return type. +# This is expressed as the special return type `NoReturn`. +# +# Typical examples for non-returning methods and keywords are `return`, `exit`, `raise`, `next`, and `break`. +# +# This is for example useful for deconstructing union types: +# +# ``` +# string = STDIN.gets +# typeof(string) # => String? +# typeof(raise "Empty input") # => NoReturn +# typeof(string || raise "Empty input") # => String +# ``` +# +# The compiler recognizes that in case string is Nil, the right hand side of the expression `string || raise` will be evaluated. +# Since `typeof(raise "Empty input")` is `NoReturn` the execution would not return to the current scope in that case. +# That leaves only `String` as resulting type of the expression. +# +# Every expression whose code paths all result in `NoReturn` will be `NoReturn` as well. +# `NoReturn` does not show up in a union type because it would essentially be included in every expression's type. +# It is only used when an expression will never return to the current scope. +# +# `NoReturn` can be explicitly set as return type of a method or function definition but will usually be inferred by the compiler. +struct CRYSTAL_PSEUDO__NoReturn +end + +# Similar in usage to `Nil`. `Void` is prefered for C lib bindings. +struct CRYSTAL_PSEUDO__Void +end From 827c59adba86135c72c7a5555979ae85314a57f7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 6 Aug 2024 15:50:50 +0800 Subject: [PATCH 1297/1551] Fix misaligned stack access in the interpreter (#14843) --- src/compiler/crystal/interpreter/interpreter.cr | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index eca73ecae6bc..aa90d83f413f 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -999,14 +999,15 @@ class Crystal::Repl::Interpreter private macro stack_pop(t) %aligned_size = align(sizeof({{t}})) - %value = (stack - %aligned_size).as({{t}}*).value + %value = uninitialized {{t}} + (stack - %aligned_size).copy_to(pointerof(%value).as(UInt8*), sizeof({{t}})) stack_shrink_by(%aligned_size) %value end private macro stack_push(value) %temp = {{value}} - stack.as(Pointer(typeof({{value}}))).value = %temp + stack.copy_from(pointerof(%temp).as(UInt8*), sizeof(typeof({{value}}))) %size = sizeof(typeof({{value}})) %aligned_size = align(%size) From a60b0766d26ee1f6005c671369432d57ec843a9c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 7 Aug 2024 04:48:06 +0800 Subject: [PATCH 1298/1551] Fix `ReferenceStorage(T)` atomic if `T` has no inner pointers (#14845) It turns out the fix in #14730 made all `ReferenceStorage` objects non-atomic; `Crystal::ReferenceStorageType#reference_type` returns a reference type, whose `#has_inner_pointers?` always returns true since the reference itself is a pointer. This PR fixes that again by adding a special case for `ReferenceStorage`. --- src/compiler/crystal/codegen/types.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/codegen/types.cr b/src/compiler/crystal/codegen/types.cr index 470fe7424dcd..7ce1640bb5e7 100644 --- a/src/compiler/crystal/codegen/types.cr +++ b/src/compiler/crystal/codegen/types.cr @@ -70,7 +70,7 @@ module Crystal when NamedTupleInstanceType self.entries.any? &.type.has_inner_pointers? when ReferenceStorageType - self.reference_type.has_inner_pointers? + self.reference_type.all_instance_vars.each_value.any? &.type.has_inner_pointers? when PrimitiveType false when EnumType From d63d459d24228a9f916b7dfb584d15689b75a05c Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Fri, 9 Aug 2024 05:00:09 -0400 Subject: [PATCH 1299/1551] Hide `Hash::Entry` from public API docs (#14881) --- src/hash.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hash.cr b/src/hash.cr index 8d48e1cd8c08..96b87c7d3e22 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -2148,6 +2148,7 @@ class Hash(K, V) hash end + # :nodoc: struct Entry(K, V) getter key, value, hash From 77314b0c5fd5e2d2e1f7e07415834acdb795b553 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 16 Aug 2024 03:48:53 +0800 Subject: [PATCH 1300/1551] Fix misaligned store in `Bool` to union upcasts (#14906) The code path for `Nil` looks similar, but it is perfectly fine: it directly stores `[8 x i64] zeroinitializer` to the data field, whose default alignment naturally matches. --- spec/compiler/codegen/union_type_spec.cr | 19 +++++++++++++++++++ src/compiler/crystal/codegen/unions.cr | 9 ++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/spec/compiler/codegen/union_type_spec.cr b/spec/compiler/codegen/union_type_spec.cr index eb561a92dbdd..8ea7d058bff9 100644 --- a/spec/compiler/codegen/union_type_spec.cr +++ b/spec/compiler/codegen/union_type_spec.cr @@ -215,4 +215,23 @@ describe "Code gen: union type" do Union(Nil, Int32).foo )).to_string.should eq("TupleLiteral") end + + it "respects union payload alignment when upcasting Bool (#14898)" do + mod = codegen(<<-CRYSTAL) + x = uninitialized Bool | UInt8[64] + x = true + CRYSTAL + + str = mod.to_s + {% if LibLLVM::IS_LT_150 %} + str.should contain("store i512 1, i512* %2, align 8") + {% else %} + str.should contain("store i512 1, ptr %1, align 8") + {% end %} + + # an i512 store defaults to 16-byte alignment, which is undefined behavior + # as it overestimates the actual alignment of `x`'s data field (x86 in + # particular segfaults on misaligned 16-byte stores) + str.should_not contain("align 16") + end end diff --git a/src/compiler/crystal/codegen/unions.cr b/src/compiler/crystal/codegen/unions.cr index b2b63a17c5ab..fdf1d81a4c95 100644 --- a/src/compiler/crystal/codegen/unions.cr +++ b/src/compiler/crystal/codegen/unions.cr @@ -81,16 +81,19 @@ module Crystal def store_bool_in_union(target_type, union_pointer, value) struct_type = llvm_type(target_type) + union_value_type = struct_type.struct_element_types[1] store type_id(value, @program.bool), union_type_id(struct_type, union_pointer) # To store a boolean in a union - # we sign-extend it to the size in bits of the union - union_size = @llvm_typer.size_of(struct_type.struct_element_types[1]) + # we zero-extend it to the size in bits of the union + union_size = @llvm_typer.size_of(union_value_type) int_type = llvm_context.int((union_size * 8).to_i32) bool_as_extended_int = builder.zext(value, int_type) casted_value_ptr = pointer_cast(union_value(struct_type, union_pointer), int_type.pointer) - store bool_as_extended_int, casted_value_ptr + inst = store bool_as_extended_int, casted_value_ptr + set_alignment(inst, @llvm_typer.align_of(union_value_type)) + inst end def store_nil_in_union(target_type, union_pointer) From 879ec124747c287605e349183a3c9143174659e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 20 Aug 2024 15:49:27 +0200 Subject: [PATCH 1301/1551] Changelog for 1.13.2 (#14914) --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ shard.yml | 2 +- src/SOURCE_DATE_EPOCH | 2 +- src/VERSION | 2 +- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 382f76969ec0..f97d0bedeb1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## [1.13.2] (2024-08-20) + +[1.13.2]: https://github.com/crystal-lang/crystal/releases/1.13.2 + +### Bugfixes + +#### stdlib + +- *(collection)* Fix explicitly clear deleted `Hash::Entry` ([#14862], thanks @HertzDevil) + +[#14862]: https://github.com/crystal-lang/crystal/pull/14862 + +#### compiler + +- *(codegen)* Fix `ReferenceStorage(T)` atomic if `T` has no inner pointers ([#14845], thanks @HertzDevil) +- *(codegen)* Fix misaligned store in `Bool` to union upcasts ([#14906], thanks @HertzDevil) +- *(interpreter)* Fix misaligned stack access in the interpreter ([#14843], thanks @HertzDevil) + +[#14845]: https://github.com/crystal-lang/crystal/pull/14845 +[#14906]: https://github.com/crystal-lang/crystal/pull/14906 +[#14843]: https://github.com/crystal-lang/crystal/pull/14843 + +### Infrastructure + +- Changelog for 1.13.2 ([#14914], thanks @straight-shoota) + +[#14914]: https://github.com/crystal-lang/crystal/pull/14914 + ## [1.13.1] (2024-07-12) [1.13.1]: https://github.com/crystal-lang/crystal/releases/1.13.1 diff --git a/shard.yml b/shard.yml index 396d91bdffe2..0dd8c2abf3a1 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: crystal -version: 1.13.0-dev +version: 1.13.2 authors: - Crystal Core Team diff --git a/src/SOURCE_DATE_EPOCH b/src/SOURCE_DATE_EPOCH index efabb39ec223..0ea6bd82d669 100644 --- a/src/SOURCE_DATE_EPOCH +++ b/src/SOURCE_DATE_EPOCH @@ -1 +1 @@ -1720742400 +1724112000 diff --git a/src/VERSION b/src/VERSION index b50dd27dd92e..61ce01b30118 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.13.1 +1.13.2 From 41f75ca500b9a3c4b2767c9c993e720bd1e64a37 Mon Sep 17 00:00:00 2001 From: Margret Riegert Date: Tue, 20 Aug 2024 10:36:45 -0400 Subject: [PATCH 1302/1551] Add `URI.from_json_object_key?` and `URI#to_json_object_key` (#14834) --- spec/std/uri/json_spec.cr | 14 ++++++++++++++ src/uri/json.cr | 14 ++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 spec/std/uri/json_spec.cr diff --git a/spec/std/uri/json_spec.cr b/spec/std/uri/json_spec.cr new file mode 100644 index 000000000000..a21f503958a5 --- /dev/null +++ b/spec/std/uri/json_spec.cr @@ -0,0 +1,14 @@ +require "spec" +require "uri/json" + +describe "URI" do + describe "serializes" do + it "#to_json" do + URI.parse("https://example.com").to_json.should eq %q("https://example.com") + end + + it "from_json_object_key?" do + URI.from_json_object_key?("https://example.com").should eq(URI.parse("https://example.com")) + end + end +end diff --git a/src/uri/json.cr b/src/uri/json.cr index 9767c9e98a02..00b58f419be5 100644 --- a/src/uri/json.cr +++ b/src/uri/json.cr @@ -25,4 +25,18 @@ class URI def to_json(builder : JSON::Builder) builder.string self end + + # Deserializes the given JSON *key* into a `URI` + # + # NOTE: `require "uri/json"` is required to opt-in to this feature. + def self.from_json_object_key?(key : String) : URI? + parse key + rescue URI::Error + nil + end + + # :nodoc: + def to_json_object_key : String + to_s + end end From 1baf3a726f76adda630d5ee4f384dd00e319a2de Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Tue, 20 Aug 2024 09:38:58 -0500 Subject: [PATCH 1303/1551] Add `WaitGroup.wait` and `WaitGroup#spawn` (#14837) This commit allows for usage of WaitGroup in a way that is significantly more readable. WaitGroup.wait do |wg| wg.spawn { http.get "/foo" } wg.spawn { http.get "/bar" } end --- spec/std/wait_group_spec.cr | 13 +++++++++++++ src/wait_group.cr | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/spec/std/wait_group_spec.cr b/spec/std/wait_group_spec.cr index 459af8d5c898..6c2f46daa562 100644 --- a/spec/std/wait_group_spec.cr +++ b/spec/std/wait_group_spec.cr @@ -160,6 +160,19 @@ describe WaitGroup do extra.get.should eq(32) end + it "takes a block to WaitGroup.wait" do + fiber_count = 10 + completed = Array.new(fiber_count) { false } + + WaitGroup.wait do |wg| + fiber_count.times do |i| + wg.spawn { completed[i] = true } + end + end + + completed.should eq [true] * 10 + end + # the test takes far too much time for the interpreter to complete {% unless flag?(:interpreted) %} it "stress add/done/wait" do diff --git a/src/wait_group.cr b/src/wait_group.cr index 2fd49c593b56..89510714c727 100644 --- a/src/wait_group.cr +++ b/src/wait_group.cr @@ -42,12 +42,46 @@ class WaitGroup end end + # Yields a `WaitGroup` instance and waits at the end of the block for all of + # the work enqueued inside it to complete. + # + # ``` + # WaitGroup.wait do |wg| + # items.each do |item| + # wg.spawn { process item } + # end + # end + # ``` + def self.wait : Nil + instance = new + yield instance + instance.wait + end + def initialize(n : Int32 = 0) @waiting = Crystal::PointerLinkedList(Waiting).new @lock = Crystal::SpinLock.new @counter = Atomic(Int32).new(n) end + # Increment the counter by 1, perform the work inside the block in a separate + # fiber, decrementing the counter after it completes or raises. Returns the + # `Fiber` that was spawned. + # + # ``` + # wg = WaitGroup.new + # wg.spawn { do_something } + # wg.wait + # ``` + def spawn(&block) : Fiber + add + ::spawn do + block.call + ensure + done + end + end + # Increments the counter by how many fibers we want to wait for. # # A negative value decrements the counter. When the counter reaches zero, From bc569acfbd6faaeb45823e468d3713c52f6c51df Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 21 Aug 2024 17:06:23 +0800 Subject: [PATCH 1304/1551] Fix internal error when calling `#is_a?` on `External` nodes (#14918) --- spec/compiler/macro/macro_methods_spec.cr | 8 ++++++++ src/compiler/crystal/macros/types.cr | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 9d425cb7e162..10ba78d5bdc6 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2654,6 +2654,14 @@ module Crystal end end + describe External do + it "executes is_a?" do + assert_macro %({{x.is_a?(External)}}), "true", {x: External.new("foo", [] of Arg, Nop.new, "foo")} + assert_macro %({{x.is_a?(Def)}}), "true", {x: External.new("foo", [] of Arg, Nop.new, "foo")} + assert_macro %({{x.is_a?(ASTNode)}}), "true", {x: External.new("foo", [] of Arg, Nop.new, "foo")} + end + end + describe Primitive do it "executes name" do assert_macro %({{x.name}}), %(:abc), {x: Primitive.new("abc")} diff --git a/src/compiler/crystal/macros/types.cr b/src/compiler/crystal/macros/types.cr index 7a7777e8aef3..3a40a9bc90aa 100644 --- a/src/compiler/crystal/macros/types.cr +++ b/src/compiler/crystal/macros/types.cr @@ -46,7 +46,8 @@ module Crystal @macro_types["Arg"] = NonGenericMacroType.new self, "Arg", ast_node @macro_types["ProcNotation"] = NonGenericMacroType.new self, "ProcNotation", ast_node - @macro_types["Def"] = NonGenericMacroType.new self, "Def", ast_node + @macro_types["Def"] = def_type = NonGenericMacroType.new self, "Def", ast_node + @macro_types["External"] = NonGenericMacroType.new self, "External", def_type @macro_types["Macro"] = NonGenericMacroType.new self, "Macro", ast_node @macro_types["UnaryExpression"] = unary_expression = NonGenericMacroType.new self, "UnaryExpression", ast_node @@ -102,7 +103,6 @@ module Crystal # bottom type @macro_types["NoReturn"] = @macro_no_return = NoReturnMacroType.new self - # unimplemented types (see https://github.com/crystal-lang/crystal/issues/3274#issuecomment-860092436) @macro_types["Self"] = NonGenericMacroType.new self, "Self", ast_node @macro_types["Underscore"] = NonGenericMacroType.new self, "Underscore", ast_node @macro_types["Select"] = NonGenericMacroType.new self, "Select", ast_node From f4022151d3f43bce605b8776c3a341637e10fea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 21 Aug 2024 17:08:09 +0200 Subject: [PATCH 1305/1551] Fix return type restriction for `ENV.fetch` (#14919) --- spec/std/env_spec.cr | 4 ++++ src/env.cr | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/std/env_spec.cr b/spec/std/env_spec.cr index 038bdc74b9b1..c48afb0ff6f9 100644 --- a/spec/std/env_spec.cr +++ b/spec/std/env_spec.cr @@ -137,6 +137,10 @@ describe "ENV" do ENV.fetch("2") end end + + it "fetches arbitrary default value" do + ENV.fetch("nonexistent", true).should be_true + end end it "handles unicode" do diff --git a/src/env.cr b/src/env.cr index b28e4014ea22..13779f3051aa 100644 --- a/src/env.cr +++ b/src/env.cr @@ -60,7 +60,7 @@ module ENV # Retrieves a value corresponding to the given *key*. Return the second argument's value # if the *key* does not exist. - def self.fetch(key, default) : String? + def self.fetch(key, default : T) : String | T forall T fetch(key) { default } end From c45d3767f6c1d5551968e9a5db01cfad883217b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 21 Aug 2024 17:09:02 +0200 Subject: [PATCH 1306/1551] Update previous Crystal release 1.13.2 (#14925) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 8 ++++---- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 2 +- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win_build_portable.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b3f2310d7808..39984bc5aadb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.13.2/crystal-1.13.2-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 8828efe88a10..ba32bb2dd2d6 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.1-build + image: crystallang/crystal:1.13.2-build name: "Test Interpreter" steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.1-build + image: crystallang/crystal:1.13.2-build name: Build interpreter steps: - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.1-build + image: crystallang/crystal:1.13.2-build strategy: matrix: part: [0, 1, 2, 3] @@ -67,7 +67,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.1-build + image: crystallang/crystal:1.13.2-build name: "Test primitives_spec with interpreter" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 32761dbb8c75..d1128ebdbca8 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2, 1.13.1] + crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2, 1.13.2] flags: [""] include: # libffi is only available starting from the 1.2.2 build images diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 767d401138e7..152e2b5294b5 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -58,7 +58,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.13.1" + crystal: "1.13.2" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index d518c93a51de..4eede5adf78c 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: libssl_test: runs-on: ubuntu-latest name: "${{ matrix.pkg }}" - container: crystallang/crystal:1.13.1-alpine + container: crystallang/crystal:1.13.2-alpine strategy: fail-fast: false matrix: diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index 8816c31dc9b0..186192288895 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.13.1-alpine + container: crystallang/crystal:1.13.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.13.1-alpine + container: crystallang/crystal:1.13.2-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 2b446ec6726f..7ce32ee2d625 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.13.1-build + container: crystallang/crystal:1.13.2-build steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 6e36608d608d..d2ed6469d264 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -24,7 +24,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.13.1" + crystal: "1.13.2" - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index 74a1f228ceff..4ca0eb96577e 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.13.1-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.13.2/crystal-1.13.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.13.2-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.13.1}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.13.2}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index 259cecf9b304..db69834a8a89 100644 --- a/shell.nix +++ b/shell.nix @@ -53,18 +53,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:0wrfv7bgqwfi76p9s48zg4j953kvjsj5cv59slhhc62lllx926zm"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.13.2/crystal-1.13.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:046zlsyrj1i769xh4jvv0a81nlqj7kiz0hliq1za86k1749kcmlz"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-darwin-universal.tar.gz"; - sha256 = "sha256:0wrfv7bgqwfi76p9s48zg4j953kvjsj5cv59slhhc62lllx926zm"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.13.2/crystal-1.13.2-1-darwin-universal.tar.gz"; + sha256 = "sha256:046zlsyrj1i769xh4jvv0a81nlqj7kiz0hliq1za86k1749kcmlz"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-linux-x86_64.tar.gz"; - sha256 = "sha256:1dghcv8qgjcbq1r0d2saa21xzp4h7pkan6fnmn6hpickib678g7x"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.13.2/crystal-1.13.2-1-linux-x86_64.tar.gz"; + sha256 = "sha256:0186q0y97135kvxa8bmzgqc24idv19jg4vglany0pkpzy8b3qs0s"; }; }.${pkgs.stdenv.system}); From bb75d3b789e40883a71a59e78dd26a9708e4d20c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 22 Aug 2024 05:34:43 +0800 Subject: [PATCH 1307/1551] Fix `SOURCE_DATE_EPOCH` in `Makefile.win` (#14922) --- Makefile.win | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.win b/Makefile.win index 89c0f9972a14..0613acc8a207 100644 --- a/Makefile.win +++ b/Makefile.win @@ -64,7 +64,7 @@ CRYSTAL_CONFIG_LIBRARY_PATH := $$ORIGIN\lib CRYSTAL_CONFIG_BUILD_COMMIT := $(shell git rev-parse --short HEAD) CRYSTAL_CONFIG_PATH := $$ORIGIN\src CRYSTAL_VERSION ?= $(shell type src\VERSION) -SOURCE_DATE_EPOCH ?= $(shell type src/SOURCE_DATE_EPOCH || git show -s --format=%ct HEAD) +SOURCE_DATE_EPOCH ?= $(or $(shell type src\SOURCE_DATE_EPOCH 2>NUL),$(shell git show -s --format=%ct HEAD)) export_vars = $(eval export CRYSTAL_CONFIG_BUILD_COMMIT CRYSTAL_CONFIG_PATH SOURCE_DATE_EPOCH) export_build_vars = $(eval export CRYSTAL_CONFIG_LIBRARY_PATH) LLVM_CONFIG ?= From 0f906a4f4374e69e2ff5348a8029ef0709606e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 22 Aug 2024 11:37:41 +0200 Subject: [PATCH 1308/1551] Fix `Expectations::Be` for module type (#14926) --- spec/std/spec/expectations_spec.cr | 23 +++++++++++++++++++++++ src/spec/expectations.cr | 10 ++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/spec/std/spec/expectations_spec.cr b/spec/std/spec/expectations_spec.cr index 4acce2bfbad9..0831bca226ca 100644 --- a/spec/std/spec/expectations_spec.cr +++ b/spec/std/spec/expectations_spec.cr @@ -1,5 +1,17 @@ require "spec" +private module MyModule; end + +private class Foo + include MyModule +end + +private record NoObjectId, to_unsafe : Int32 do + def same?(other : self) : Bool + to_unsafe == other.to_unsafe + end +end + describe "expectations" do describe "accept a custom failure message" do it { 1.should be < 3, "custom message!" } @@ -25,6 +37,17 @@ describe "expectations" do array = [1] array.should_not be [1] end + + it "works with type that does not implement `#object_id`" do + a = NoObjectId.new(1) + a.should be a + a.should_not be NoObjectId.new(2) + end + + it "works with module type (#14920)" do + a = Foo.new + a.as(MyModule).should be a.as(MyModule) + end end describe "be_a" do diff --git a/src/spec/expectations.cr b/src/spec/expectations.cr index 193f86d0de21..f50658a5d787 100644 --- a/src/spec/expectations.cr +++ b/src/spec/expectations.cr @@ -73,11 +73,13 @@ module Spec end private def identify(value) - if value.responds_to?(:object_id) - "object_id: #{value.object_id}" - else - value.to_unsafe + if value.responds_to?(:to_unsafe) + if !value.responds_to?(:object_id) + return value.to_unsafe + end end + + "object_id: #{value.object_id}" end end From eb01f2a488bfb30083b1da25cade19c99e3c4e4b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 22 Aug 2024 17:37:54 +0800 Subject: [PATCH 1309/1551] Allow returning `Proc`s from top-level funs (#14917) --- spec/compiler/codegen/proc_spec.cr | 53 +++++++++++++++++++++++++++++ src/compiler/crystal/codegen/fun.cr | 13 ++++--- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/spec/compiler/codegen/proc_spec.cr b/spec/compiler/codegen/proc_spec.cr index 48db694429e5..65b2731e5ac6 100644 --- a/spec/compiler/codegen/proc_spec.cr +++ b/spec/compiler/codegen/proc_spec.cr @@ -862,6 +862,59 @@ describe "Code gen: proc" do )) end + it "returns proc as function pointer inside top-level fun (#14691)" do + run(<<-CRYSTAL, Int32).should eq(8) + def raise(msg) + while true + end + end + + fun add : Int32, Int32 -> Int32 + ->(x : Int32, y : Int32) { x &+ y } + end + + add.call(3, 5) + CRYSTAL + end + + it "returns ProcPointer inside top-level fun (#14691)" do + run(<<-CRYSTAL, Int32).should eq(8) + def raise(msg) + while true + end + end + + fun foo(x : Int32) : Int32 + x &+ 5 + end + + fun bar : Int32 -> Int32 + ->foo(Int32) + end + + bar.call(3) + CRYSTAL + end + + it "raises if returning closure from top-level fun (#14691)" do + run(<<-CRYSTAL).to_b.should be_true + require "prelude" + + @[Raises] + fun foo(x : Int32) : -> Int32 + -> { x } + end + + begin + foo(1) + rescue + true + else + false + end + CRYSTAL + end + it "closures var on ->var.call (#8584)" do run(%( def bar(x) diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 5b7c9b224c83..616b21b79d24 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -236,17 +236,22 @@ class Crystal::CodeGenVisitor # Check if this def must use the C calling convention and the return # value must be either casted or passed by sret if target_def.c_calling_convention? && target_def.abi_info? + return_type = target_def.body.type + if return_type.proc? + @last = check_proc_is_not_closure(@last, return_type) + end + abi_info = abi_info(target_def) - ret_type = abi_info.return_type - if cast = ret_type.cast + abi_ret_type = abi_info.return_type + if cast = abi_ret_type.cast casted_last = pointer_cast @last, cast.pointer last = load cast, casted_last ret last return end - if (attr = ret_type.attr) && attr == LLVM::Attribute::StructRet - store load(llvm_type(target_def.body.type), @last), context.fun.params[0] + if (attr = abi_ret_type.attr) && attr == LLVM::Attribute::StructRet + store load(llvm_type(return_type), @last), context.fun.params[0] ret return end From a3bfa4ccc45cc3d1570ac1cf830cb68b45c32bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 22 Aug 2024 16:54:04 +0200 Subject: [PATCH 1310/1551] Fix `crystal tool dependencies` format flat (#14927) --- src/compiler/crystal/tools/dependencies.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/tools/dependencies.cr b/src/compiler/crystal/tools/dependencies.cr index cfb26fbccc43..7ddc48857ad3 100644 --- a/src/compiler/crystal/tools/dependencies.cr +++ b/src/compiler/crystal/tools/dependencies.cr @@ -124,7 +124,7 @@ module Crystal end private def print_indent - @io.print " " * @stack.size unless @stack.empty? + @io.print " " * @stack.size unless @stack.empty? || @format.flat? end end From d031bfa89cd7464579d5d360a178fa3ea96f6e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 22 Aug 2024 16:54:18 +0200 Subject: [PATCH 1311/1551] Fix `crystal tool dependencies` filters for Windows paths (#14928) --- src/compiler/crystal/tools/dependencies.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/tools/dependencies.cr b/src/compiler/crystal/tools/dependencies.cr index 7ddc48857ad3..91701285639b 100644 --- a/src/compiler/crystal/tools/dependencies.cr +++ b/src/compiler/crystal/tools/dependencies.cr @@ -8,8 +8,8 @@ class Crystal::Command dependency_printer = DependencyPrinter.create(STDOUT, format: DependencyPrinter::Format.parse(config.output_format), verbose: config.verbose) - dependency_printer.includes.concat config.includes.map { |path| ::Path[path].expand.to_s } - dependency_printer.excludes.concat config.excludes.map { |path| ::Path[path].expand.to_s } + dependency_printer.includes.concat config.includes.map { |path| ::Path[path].expand.to_posix.to_s } + dependency_printer.excludes.concat config.excludes.map { |path| ::Path[path].expand.to_posix.to_s } config.compiler.dependency_printer = dependency_printer dependency_printer.start_format From c462cd61cbba7f0f0003570ffef894821010c335 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 23 Aug 2024 17:59:21 +0800 Subject: [PATCH 1312/1551] Open non-blocking regular files as overlapped on Windows (#14921) --- spec/std/file/tempfile_spec.cr | 6 +++--- spec/std/file_spec.cr | 8 ++++++++ src/crystal/system/file.cr | 2 +- src/crystal/system/unix/file.cr | 6 +++--- src/crystal/system/win32/file.cr | 16 ++++++++++------ src/crystal/system/win32/file_descriptor.cr | 3 +-- src/file.cr | 2 +- 7 files changed, 27 insertions(+), 16 deletions(-) diff --git a/spec/std/file/tempfile_spec.cr b/spec/std/file/tempfile_spec.cr index 3ede9e52e44d..84d9cd553398 100644 --- a/spec/std/file/tempfile_spec.cr +++ b/spec/std/file/tempfile_spec.cr @@ -200,7 +200,7 @@ describe Crystal::System::File do fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14])) path.should eq Path[tempdir, "A789abcdeZ"].to_s ensure - File.from_fd(path, fd).close if fd && path + IO::FileDescriptor.new(fd).close if fd end end @@ -212,7 +212,7 @@ describe Crystal::System::File do fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22])) path.should eq File.join(tempdir, "AfghijklmZ") ensure - File.from_fd(path, fd).close if fd && path + IO::FileDescriptor.new(fd).close if fd end end @@ -223,7 +223,7 @@ describe Crystal::System::File do expect_raises(File::AlreadyExistsError, "Error creating temporary file") do fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14])) ensure - File.from_fd(path, fd).close if fd && path + IO::FileDescriptor.new(fd).close if fd end end end diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 942ae8a1143d..96dbacd73cc9 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -71,6 +71,14 @@ describe "File" do end end + it "opens regular file as non-blocking" do + with_tempfile("regular") do |path| + File.open(path, "w", blocking: false) do |file| + file.blocking.should be_false + end + end + end + {% if flag?(:unix) %} if File.exists?("/dev/tty") it "opens character device" do diff --git a/src/crystal/system/file.cr b/src/crystal/system/file.cr index 452bfb6e4ead..84dbd0fa5c98 100644 --- a/src/crystal/system/file.cr +++ b/src/crystal/system/file.cr @@ -65,7 +65,7 @@ module Crystal::System::File io << suffix end - handle, errno = open(path, mode, perm) + handle, errno = open(path, mode, perm, blocking: true) if error_is_none?(errno) return {handle, path} diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index fafd1d0d0a16..a049659e684f 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -3,10 +3,10 @@ require "file/error" # :nodoc: module Crystal::System::File - def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions) + def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions, blocking) perm = ::File::Permissions.new(perm) if perm.is_a? Int32 - fd, errno = open(filename, open_flag(mode), perm) + fd, errno = open(filename, open_flag(mode), perm, blocking) unless errno.none? raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", errno, file: filename) @@ -15,7 +15,7 @@ module Crystal::System::File fd end - def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {LibC::Int, Errno} + def self.open(filename : String, flags : Int32, perm : ::File::Permissions, blocking _blocking) : {LibC::Int, Errno} filename.check_no_null_byte flags |= LibC::O_CLOEXEC diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 9039cc40a7ac..7b7b443ce310 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -14,7 +14,7 @@ module Crystal::System::File # write at the end of the file. @system_append = false - def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions) : FileDescriptor::Handle + def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions, blocking : Bool?) : FileDescriptor::Handle perm = ::File::Permissions.new(perm) if perm.is_a? Int32 # Only the owner writable bit is used, since windows only supports # the read only attribute. @@ -24,7 +24,7 @@ module Crystal::System::File perm = LibC::S_IREAD end - handle, error = open(filename, open_flag(mode), ::File::Permissions.new(perm)) + handle, error = open(filename, open_flag(mode), ::File::Permissions.new(perm), blocking != false) unless error.error_success? raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", error, file: filename) end @@ -32,8 +32,8 @@ module Crystal::System::File handle end - def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {FileDescriptor::Handle, WinError} - access, disposition, attributes = self.posix_to_open_opts flags, perm + def self.open(filename : String, flags : Int32, perm : ::File::Permissions, blocking : Bool) : {FileDescriptor::Handle, WinError} + access, disposition, attributes = self.posix_to_open_opts flags, perm, blocking handle = LibC.CreateFileW( System.to_wstr(filename), @@ -48,7 +48,7 @@ module Crystal::System::File {handle.address, handle == LibC::INVALID_HANDLE_VALUE ? WinError.value : WinError::ERROR_SUCCESS} end - private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions) + private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions, blocking : Bool) access = if flags.bits_set? LibC::O_WRONLY LibC::FILE_GENERIC_WRITE elsif flags.bits_set? LibC::O_RDWR @@ -77,7 +77,7 @@ module Crystal::System::File disposition = LibC::OPEN_EXISTING end - attributes = LibC::FILE_ATTRIBUTE_NORMAL + attributes = 0 unless perm.owner_write? attributes |= LibC::FILE_ATTRIBUTE_READONLY end @@ -97,6 +97,10 @@ module Crystal::System::File attributes |= LibC::FILE_FLAG_RANDOM_ACCESS end + unless blocking + attributes |= LibC::FILE_FLAG_OVERLAPPED + end + {access, disposition, attributes} end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 7899f75407f7..37813307191f 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -89,6 +89,7 @@ module Crystal::System::FileDescriptor private def system_blocking_init(value) @system_blocking = value + Crystal::EventLoop.current.create_completion_port(windows_handle) unless value end private def system_close_on_exec? @@ -264,13 +265,11 @@ module Crystal::System::FileDescriptor w_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless write_blocking w_pipe = LibC.CreateNamedPipeA(pipe_name, w_pipe_flags, pipe_mode, 1, PIPE_BUFFER_SIZE, PIPE_BUFFER_SIZE, 0, nil) raise IO::Error.from_winerror("CreateNamedPipeA") if w_pipe == LibC::INVALID_HANDLE_VALUE - Crystal::EventLoop.current.create_completion_port(w_pipe) unless write_blocking r_pipe_flags = LibC::FILE_FLAG_NO_BUFFERING r_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless read_blocking r_pipe = LibC.CreateFileW(System.to_wstr(pipe_name), LibC::GENERIC_READ | LibC::FILE_WRITE_ATTRIBUTES, 0, nil, LibC::OPEN_EXISTING, r_pipe_flags, nil) raise IO::Error.from_winerror("CreateFileW") if r_pipe == LibC::INVALID_HANDLE_VALUE - Crystal::EventLoop.current.create_completion_port(r_pipe) unless read_blocking r = IO::FileDescriptor.new(r_pipe.address, read_blocking) w = IO::FileDescriptor.new(w_pipe.address, write_blocking) diff --git a/src/file.cr b/src/file.cr index 202a05ab01f0..5169a6dc703d 100644 --- a/src/file.cr +++ b/src/file.cr @@ -172,7 +172,7 @@ class File < IO::FileDescriptor # additional syscall. def self.new(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, blocking = true) filename = filename.to_s - fd = Crystal::System::File.open(filename, mode, perm: perm) + fd = Crystal::System::File.open(filename, mode, perm: perm, blocking: blocking) new(filename, fd, blocking: blocking, encoding: encoding, invalid: invalid).tap { |f| f.system_set_mode(mode) } end From d4fc67a271b42afbb91875154f803fbf17047029 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 23 Aug 2024 17:59:43 +0800 Subject: [PATCH 1313/1551] Include `Crystal::System::Group` instead of extending it (#14930) --- src/crystal/system/group.cr | 10 ++++++++++ src/crystal/system/unix/group.cr | 19 +++++++++++++++---- src/crystal/system/wasi/group.cr | 16 ++++++++++++---- src/system/group.cr | 19 ++++++++++--------- 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/crystal/system/group.cr b/src/crystal/system/group.cr index dce631e8c1ab..8a542e2cc63c 100644 --- a/src/crystal/system/group.cr +++ b/src/crystal/system/group.cr @@ -1,3 +1,13 @@ +module Crystal::System::Group + # def system_name : String + + # def system_id : String + + # def self.from_name?(groupname : String) : ::System::Group? + + # def self.from_id?(groupid : String) : ::System::Group? +end + {% if flag?(:wasi) %} require "./wasi/group" {% elsif flag?(:unix) %} diff --git a/src/crystal/system/unix/group.cr b/src/crystal/system/unix/group.cr index d7d408f77608..d4562cc7d286 100644 --- a/src/crystal/system/unix/group.cr +++ b/src/crystal/system/unix/group.cr @@ -4,11 +4,22 @@ require "../unix" module Crystal::System::Group private GETGR_R_SIZE_MAX = 1024 * 16 - private def from_struct(grp) - new(String.new(grp.gr_name), grp.gr_gid.to_s) + def initialize(@name : String, @id : String) end - private def from_name?(groupname : String) + def system_name + @name + end + + def system_id + @id + end + + private def self.from_struct(grp) + ::System::Group.new(String.new(grp.gr_name), grp.gr_gid.to_s) + end + + def self.from_name?(groupname : String) groupname.check_no_null_byte grp = uninitialized LibC::Group @@ -21,7 +32,7 @@ module Crystal::System::Group end end - private def from_id?(groupid : String) + def self.from_id?(groupid : String) groupid = groupid.to_u32? return unless groupid diff --git a/src/crystal/system/wasi/group.cr b/src/crystal/system/wasi/group.cr index 0aa09bd40aa8..c94fffa4fe6e 100644 --- a/src/crystal/system/wasi/group.cr +++ b/src/crystal/system/wasi/group.cr @@ -1,9 +1,17 @@ module Crystal::System::Group - private def from_name?(groupname : String) - raise NotImplementedError.new("Crystal::System::Group#from_name?") + def system_name + raise NotImplementedError.new("Crystal::System::Group#system_name") end - private def from_id?(groupid : String) - raise NotImplementedError.new("Crystal::System::Group#from_id?") + def system_id + raise NotImplementedError.new("Crystal::System::Group#system_id") + end + + def self.from_name?(groupname : String) + raise NotImplementedError.new("Crystal::System::Group.from_name?") + end + + def self.from_id?(groupid : String) + raise NotImplementedError.new("Crystal::System::Group.from_id?") end end diff --git a/src/system/group.cr b/src/system/group.cr index bd992e6af19d..47b9768cca52 100644 --- a/src/system/group.cr +++ b/src/system/group.cr @@ -17,19 +17,20 @@ class System::Group class NotFoundError < Exception end - extend Crystal::System::Group + include Crystal::System::Group # The group's name. - getter name : String + def name : String + system_name + end # The group's identifier. - getter id : String - - def_equals_and_hash @id - - private def initialize(@name, @id) + def id : String + system_id end + def_equals_and_hash id + # Returns the group associated with the given name. # # Raises `NotFoundError` if no such group exists. @@ -41,7 +42,7 @@ class System::Group # # Returns `nil` if no such group exists. def self.find_by?(*, name : String) : System::Group? - from_name?(name) + Crystal::System::Group.from_name?(name) end # Returns the group associated with the given ID. @@ -55,7 +56,7 @@ class System::Group # # Returns `nil` if no such group exists. def self.find_by?(*, id : String) : System::Group? - from_id?(id) + Crystal::System::Group.from_id?(id) end def to_s(io) From cc6859bd32bdaa4bbed5ef95df16f00014c7ba97 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 23 Aug 2024 17:59:52 +0800 Subject: [PATCH 1314/1551] Include `Crystal::System::User` instead of extending it (#14929) --- src/crystal/system/unix/user.cr | 35 +++++++++++++++++++++++++++++---- src/crystal/system/user.cr | 18 +++++++++++++++++ src/crystal/system/wasi/user.cr | 32 ++++++++++++++++++++++++++---- src/system/user.cr | 35 +++++++++++++++++++++------------ 4 files changed, 99 insertions(+), 21 deletions(-) diff --git a/src/crystal/system/unix/user.cr b/src/crystal/system/unix/user.cr index 8e4f16e8c1c4..c1f91d0f118c 100644 --- a/src/crystal/system/unix/user.cr +++ b/src/crystal/system/unix/user.cr @@ -4,14 +4,41 @@ require "../unix" module Crystal::System::User GETPW_R_SIZE_MAX = 1024 * 16 - private def from_struct(pwd) + def initialize(@username : String, @id : String, @group_id : String, @name : String, @home_directory : String, @shell : String) + end + + def system_username + @username + end + + def system_id + @id + end + + def system_group_id + @group_id + end + + def system_name + @name + end + + def system_home_directory + @home_directory + end + + def system_shell + @shell + end + + private def self.from_struct(pwd) username = String.new(pwd.pw_name) # `pw_gecos` is not part of POSIX and bionic for example always leaves it null user = pwd.pw_gecos ? String.new(pwd.pw_gecos).partition(',')[0] : username - new(username, pwd.pw_uid.to_s, pwd.pw_gid.to_s, user, String.new(pwd.pw_dir), String.new(pwd.pw_shell)) + ::System::User.new(username, pwd.pw_uid.to_s, pwd.pw_gid.to_s, user, String.new(pwd.pw_dir), String.new(pwd.pw_shell)) end - private def from_username?(username : String) + def self.from_username?(username : String) username.check_no_null_byte pwd = uninitialized LibC::Passwd @@ -24,7 +51,7 @@ module Crystal::System::User end end - private def from_id?(id : String) + def self.from_id?(id : String) id = id.to_u32? return unless id diff --git a/src/crystal/system/user.cr b/src/crystal/system/user.cr index ecee92c8dcb5..cb3db8cda026 100644 --- a/src/crystal/system/user.cr +++ b/src/crystal/system/user.cr @@ -1,3 +1,21 @@ +module Crystal::System::User + # def system_username : String + + # def system_id : String + + # def system_group_id : String + + # def system_name : String + + # def system_home_directory : String + + # def system_shell : String + + # def self.from_username?(username : String) : ::System::User? + + # def self.from_id?(id : String) : ::System::User? +end + {% if flag?(:wasi) %} require "./wasi/user" {% elsif flag?(:unix) %} diff --git a/src/crystal/system/wasi/user.cr b/src/crystal/system/wasi/user.cr index 06415897000e..2d1c6e91b770 100644 --- a/src/crystal/system/wasi/user.cr +++ b/src/crystal/system/wasi/user.cr @@ -1,9 +1,33 @@ module Crystal::System::User - private def from_username?(username : String) - raise NotImplementedError.new("Crystal::System::User#from_username?") + def system_username + raise NotImplementedError.new("Crystal::System::User#system_username") end - private def from_id?(id : String) - raise NotImplementedError.new("Crystal::System::User#from_id?") + def system_id + raise NotImplementedError.new("Crystal::System::User#system_id") + end + + def system_group_id + raise NotImplementedError.new("Crystal::System::User#system_group_id") + end + + def system_name + raise NotImplementedError.new("Crystal::System::User#system_name") + end + + def system_home_directory + raise NotImplementedError.new("Crystal::System::User#system_home_directory") + end + + def system_shell + raise NotImplementedError.new("Crystal::System::User#system_shell") + end + + def self.from_username?(username : String) + raise NotImplementedError.new("Crystal::System::User.from_username?") + end + + def self.from_id?(id : String) + raise NotImplementedError.new("Crystal::System::User.from_id?") end end diff --git a/src/system/user.cr b/src/system/user.cr index 7d6c250689da..01c8d11d9e1c 100644 --- a/src/system/user.cr +++ b/src/system/user.cr @@ -17,34 +17,43 @@ class System::User class NotFoundError < Exception end - extend Crystal::System::User + include Crystal::System::User # The user's username. - getter username : String + def username : String + system_username + end # The user's identifier. - getter id : String + def id : String + system_id + end # The user's primary group identifier. - getter group_id : String + def group_id : String + system_group_id + end # The user's real or full name. # # May not be present on all platforms. Returns the same value as `#username` # if neither a real nor full name is available. - getter name : String + def name : String + system_name + end # The user's home directory. - getter home_directory : String + def home_directory : String + system_home_directory + end # The user's login shell. - getter shell : String - - def_equals_and_hash @id - - private def initialize(@username, @id, @group_id, @name, @home_directory, @shell) + def shell : String + system_shell end + def_equals_and_hash id + # Returns the user associated with the given username. # # Raises `NotFoundError` if no such user exists. @@ -56,7 +65,7 @@ class System::User # # Returns `nil` if no such user exists. def self.find_by?(*, name : String) : System::User? - from_username?(name) + Crystal::System::User.from_username?(name) end # Returns the user associated with the given ID. @@ -70,7 +79,7 @@ class System::User # # Returns `nil` if no such user exists. def self.find_by?(*, id : String) : System::User? - from_id?(id) + Crystal::System::User.from_id?(id) end def to_s(io) From cc0dfc16043bbfa832d259563cd738dcf6bd8833 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Fri, 23 Aug 2024 10:59:59 -0400 Subject: [PATCH 1315/1551] Add `URI::Params::Serializable` (#14684) --- spec/std/uri/params/from_www_form_spec.cr | 151 ++++++++++++++++++++++ spec/std/uri/params/serializable_spec.cr | 133 +++++++++++++++++++ spec/std/uri/params/to_www_form_spec.cr | 60 +++++++++ src/docs_main.cr | 1 + src/uri/params/from_www_form.cr | 67 ++++++++++ src/uri/params/serializable.cr | 129 ++++++++++++++++++ src/uri/params/to_www_form.cr | 48 +++++++ 7 files changed, 589 insertions(+) create mode 100644 spec/std/uri/params/from_www_form_spec.cr create mode 100644 spec/std/uri/params/serializable_spec.cr create mode 100644 spec/std/uri/params/to_www_form_spec.cr create mode 100644 src/uri/params/from_www_form.cr create mode 100644 src/uri/params/serializable.cr create mode 100644 src/uri/params/to_www_form.cr diff --git a/spec/std/uri/params/from_www_form_spec.cr b/spec/std/uri/params/from_www_form_spec.cr new file mode 100644 index 000000000000..e0ab818c2e86 --- /dev/null +++ b/spec/std/uri/params/from_www_form_spec.cr @@ -0,0 +1,151 @@ +require "spec" +require "uri/params/serializable" + +private enum Color + Red + Green + Blue +end + +describe ".from_www_form" do + it Array do + Array(Int32).from_www_form(URI::Params.new({"values" => ["1", "2"]}), "values").should eq [1, 2] + Array(Int32).from_www_form(URI::Params.new({"values[]" => ["1", "2"]}), "values").should eq [1, 2] + Array(String).from_www_form(URI::Params.new({"values" => ["", ""]}), "values").should eq ["", ""] + end + + describe Bool do + it "a truthy value" do + Bool.from_www_form("true").should be_true + Bool.from_www_form("on").should be_true + Bool.from_www_form("yes").should be_true + Bool.from_www_form("1").should be_true + end + + it "a falsey value" do + Bool.from_www_form("false").should be_false + Bool.from_www_form("off").should be_false + Bool.from_www_form("no").should be_false + Bool.from_www_form("0").should be_false + end + + it "any other value" do + Bool.from_www_form("foo").should be_nil + Bool.from_www_form("").should be_nil + end + end + + describe String do + it "scalar string" do + String.from_www_form("John Doe").should eq "John Doe" + end + + it "with key" do + String.from_www_form(URI::Params.new({"name" => ["John Doe"]}), "name").should eq "John Doe" + end + + it "with missing key" do + String.from_www_form(URI::Params.new({"" => ["John Doe"]}), "name").should be_nil + end + + it "with alternate casing" do + String.from_www_form(URI::Params.new({"Name" => ["John Doe"]}), "name").should be_nil + end + + it "empty value" do + String.from_www_form(URI::Params.new({"name" => [""]}), "name").should eq "" + end + end + + describe Enum do + it "valid value" do + Color.from_www_form("green").should eq Color::Green + end + + it "invalid value" do + expect_raises ArgumentError do + Color.from_www_form "" + end + end + end + + describe Time do + it "valid value" do + Time.from_www_form("2016-11-16T09:55:48-03:00").to_utc.should eq(Time.utc(2016, 11, 16, 12, 55, 48)) + Time.from_www_form("2016-11-16T09:55:48-0300").to_utc.should eq(Time.utc(2016, 11, 16, 12, 55, 48)) + Time.from_www_form("20161116T095548-03:00").to_utc.should eq(Time.utc(2016, 11, 16, 12, 55, 48)) + end + + it "invalid value" do + expect_raises Time::Format::Error do + Time.from_www_form "" + end + end + end + + describe Nil do + it "valid values" do + Nil.from_www_form("").should be_nil + end + + it "invalid value" do + expect_raises ArgumentError do + Nil.from_www_form "null" + end + end + end + + describe Number do + describe Int do + it "valid numbers" do + Int64.from_www_form("123").should eq 123_i64 + UInt8.from_www_form("7").should eq 7_u8 + Int64.from_www_form("-12").should eq -12_i64 + end + + it "with whitespace" do + expect_raises ArgumentError do + Int32.from_www_form(" 123") + end + end + + it "empty value" do + expect_raises ArgumentError do + Int16.from_www_form "" + end + end + end + + describe Float do + it "valid numbers" do + Float32.from_www_form("123.0").should eq 123_f32 + Float64.from_www_form("123.0").should eq 123_f64 + end + + it "with whitespace" do + expect_raises ArgumentError do + Float64.from_www_form(" 123.0") + end + end + + it "empty value" do + expect_raises Exception do + Float64.from_www_form "" + end + end + end + end + + describe Union do + it "valid" do + String?.from_www_form(URI::Params.parse("name=John Doe"), "name").should eq "John Doe" + String?.from_www_form(URI::Params.parse("name="), "name").should eq "" + end + + it "invalid" do + expect_raises ArgumentError do + (Int32 | Float64).from_www_form(URI::Params.parse("name=John Doe"), "name") + end + end + end +end diff --git a/spec/std/uri/params/serializable_spec.cr b/spec/std/uri/params/serializable_spec.cr new file mode 100644 index 000000000000..bb1fdc7240e9 --- /dev/null +++ b/spec/std/uri/params/serializable_spec.cr @@ -0,0 +1,133 @@ +require "spec" +require "uri/params/serializable" + +private record SimpleType, page : Int32, strict : Bool, per_page : UInt8 do + include URI::Params::Serializable +end + +private record SimpleTypeDefaults, page : Int32, strict : Bool, per_page : Int32 = 10 do + include URI::Params::Serializable +end + +private record SimpleTypeNilable, page : Int32, strict : Bool, per_page : Int32? = nil do + include URI::Params::Serializable +end + +private record SimpleTypeNilableDefault, page : Int32, strict : Bool, per_page : Int32? = 20 do + include URI::Params::Serializable +end + +record Filter, status : String?, total : Float64? do + include URI::Params::Serializable +end + +record Search, filter : Filter?, limit : Int32 = 25, offset : Int32 = 0 do + include URI::Params::Serializable +end + +record GrandChild, name : String do + include URI::Params::Serializable +end + +record Child, status : String?, grand_child : GrandChild do + include URI::Params::Serializable +end + +record Parent, child : Child do + include URI::Params::Serializable +end + +module MyConverter + def self.from_www_form(params : URI::Params, name : String) + params[name].to_i * 10 + end +end + +private record ConverterType, value : Int32 do + include URI::Params::Serializable + + @[URI::Params::Field(converter: MyConverter)] + @value : Int32 +end + +class ParentType + include URI::Params::Serializable + + getter name : String +end + +class ChildType < ParentType +end + +describe URI::Params::Serializable do + describe ".from_www_form" do + it "simple type" do + SimpleType.from_www_form("page=10&strict=true&per_page=5").should eq SimpleType.new(10, true, 5) + end + + it "missing required property" do + expect_raises URI::SerializableError, "Missing required property: 'page'." do + SimpleType.from_www_form("strict=true&per_page=5") + end + end + + it "with default values" do + SimpleTypeDefaults.from_www_form("page=10&strict=off").should eq SimpleTypeDefaults.new(10, false, 10) + end + + it "with nilable values" do + SimpleTypeNilable.from_www_form("page=10&strict=true").should eq SimpleTypeNilable.new(10, true, nil) + end + + it "with nilable default" do + SimpleTypeNilableDefault.from_www_form("page=10&strict=true").should eq SimpleTypeNilableDefault.new(10, true, 20) + end + + it "with custom converter" do + ConverterType.from_www_form("value=10").should eq ConverterType.new(100) + end + + it "child type" do + ChildType.from_www_form("name=Fred").name.should eq "Fred" + end + + describe "nested type" do + it "happy path" do + Search.from_www_form("offset=10&filter[status]=active&filter[total]=3.14") + .should eq Search.new Filter.new("active", 3.14), offset: 10 + end + + it "missing nilable nested data" do + Search.from_www_form("offset=10") + .should eq Search.new Filter.new(nil, nil), offset: 10 + end + + it "missing required nested property" do + expect_raises URI::SerializableError, "Missing required property: 'child[grand_child][name]'." do + Parent.from_www_form("child[status]=active") + end + end + + it "doubly nested" do + Parent.from_www_form("child[status]=active&child[grand_child][name]=Fred") + .should eq Parent.new Child.new("active", GrandChild.new("Fred")) + end + end + end + + describe "#to_www_form" do + it "simple type" do + SimpleType.new(10, true, 5).to_www_form.should eq "page=10&strict=true&per_page=5" + end + + it "nested type path" do + Search.new(Filter.new("active", 3.14), offset: 10).to_www_form + .should eq "filter%5Bstatus%5D=active&filter%5Btotal%5D=3.14&limit=25&offset=10" + end + + it "doubly nested" do + Parent.new(Child.new("active", GrandChild.new("Fred"))).to_www_form + .should eq "child%5Bstatus%5D=active&child%5Bgrand_child%5D%5Bname%5D=Fred" + end + end +end diff --git a/spec/std/uri/params/to_www_form_spec.cr b/spec/std/uri/params/to_www_form_spec.cr new file mode 100644 index 000000000000..c10d44334de5 --- /dev/null +++ b/spec/std/uri/params/to_www_form_spec.cr @@ -0,0 +1,60 @@ +require "spec" +require "uri/params/serializable" + +private enum Color + Red + Green + BlueGreen +end + +describe "#to_www_form" do + it Number do + URI::Params.build do |builder| + 12.to_www_form builder, "value" + end.should eq "value=12" + end + + it Enum do + URI::Params.build do |builder| + Color::BlueGreen.to_www_form builder, "value" + end.should eq "value=blue_green" + end + + it String do + URI::Params.build do |builder| + "12".to_www_form builder, "value" + end.should eq "value=12" + end + + it Bool do + URI::Params.build do |builder| + false.to_www_form builder, "value" + end.should eq "value=false" + end + + it Nil do + URI::Params.build do |builder| + nil.to_www_form builder, "value" + end.should eq "value=" + end + + it Time do + URI::Params.build do |builder| + Time.utc(2024, 8, 6, 9, 48, 10).to_www_form builder, "value" + end.should eq "value=2024-08-06T09%3A48%3A10Z" + end + + describe Array do + it "of a single type" do + URI::Params.build do |builder| + [1, 2, 3].to_www_form builder, "value" + end.should eq "value=1&value=2&value=3" + end + + it "of a union of types" do + URI::Params.build do |builder| + [1, false, "foo"].to_www_form builder, "value" + end.should eq "value=1&value=false&value=foo" + end + end +end diff --git a/src/docs_main.cr b/src/docs_main.cr index 5769678ca131..e670d6d3fa83 100644 --- a/src/docs_main.cr +++ b/src/docs_main.cr @@ -52,6 +52,7 @@ require "./string_pool" require "./string_scanner" require "./unicode/unicode" require "./uri" +require "./uri/params/serializable" require "./uuid" require "./uuid/json" require "./syscall" diff --git a/src/uri/params/from_www_form.cr b/src/uri/params/from_www_form.cr new file mode 100644 index 000000000000..819c9fc9d82e --- /dev/null +++ b/src/uri/params/from_www_form.cr @@ -0,0 +1,67 @@ +# :nodoc: +def Object.from_www_form(params : URI::Params, name : String) + return unless value = params[name]? + + self.from_www_form value +end + +# :nodoc: +def Array.from_www_form(params : URI::Params, name : String) + name = if params.has_key? name + name + elsif params.has_key? "#{name}[]" + "#{name}[]" + else + return + end + + params.fetch_all(name).map { |item| T.from_www_form(item).as T } +end + +# :nodoc: +def Bool.from_www_form(value : String) + case value + when "true", "1", "yes", "on" then true + when "false", "0", "no", "off" then false + end +end + +# :nodoc: +def Number.from_www_form(value : String) + new value, whitespace: false +end + +# :nodoc: +def String.from_www_form(value : String) + value +end + +# :nodoc: +def Enum.from_www_form(value : String) + parse value +end + +# :nodoc: +def Time.from_www_form(value : String) + Time::Format::ISO_8601_DATE_TIME.parse value +end + +# :nodoc: +def Union.from_www_form(params : URI::Params, name : String) + # Process non nilable types first as they are more likely to work. + {% for type in T.sort_by { |t| t.nilable? ? 1 : 0 } %} + begin + return {{type}}.from_www_form params, name + rescue + # Noop to allow next T to be tried. + end + {% end %} + raise ArgumentError.new "Invalid #{self}: '#{params[name]}'." +end + +# :nodoc: +def Nil.from_www_form(value : String) : Nil + return if value.empty? + + raise ArgumentError.new "Invalid Nil value: '#{value}'." +end diff --git a/src/uri/params/serializable.cr b/src/uri/params/serializable.cr new file mode 100644 index 000000000000..c0d766e85242 --- /dev/null +++ b/src/uri/params/serializable.cr @@ -0,0 +1,129 @@ +require "uri" + +require "./to_www_form" +require "./from_www_form" + +struct URI::Params + annotation Field; end + + # The `URI::Params::Serializable` module automatically generates methods for `x-www-form-urlencoded` serialization when included. + # + # NOTE: To use this module, you must explicitly import it with `require "uri/params/serializable"`. + # + # ### Example + # + # ``` + # require "uri/params/serializable" + # + # struct Applicant + # include URI::Params::Serializable + # + # getter first_name : String + # getter last_name : String + # getter qualities : Array(String) + # end + # + # applicant = Applicant.from_www_form "first_name=John&last_name=Doe&qualities=kind&qualities=smart" + # applicant.first_name # => "John" + # applicant.last_name # => "Doe" + # applicant.qualities # => ["kind", "smart"] + # applicant.to_www_form # => "first_name=John&last_name=Doe&qualities=kind&qualities=smart" + # ``` + # + # ### Usage + # + # Including `URI::Params::Serializable` will create `#to_www_form` and `self.from_www_form` methods on the current class. + # By default, these methods serialize into a www form encoded string containing the value of every instance variable, the keys being the instance variable name. + # Union types are also supported, including unions with nil. + # If multiple types in a union parse correctly, it is undefined which one will be chosen. + # + # To change how individual instance variables are parsed, the annotation `URI::Params::Field` can be placed on the instance variable. + # Annotating property, getter and setter macros is also allowed. + # + # `URI::Params::Field` properties: + # * **converter**: specify an alternate type for parsing. The converter must define `.from_www_form(params : URI::Params, name : String)`. + # An example use case would be customizing the format when parsing `Time` instances, or supporting a type not natively supported. + # + # Deserialization also respects default values of variables: + # ``` + # require "uri/params/serializable" + # + # struct A + # include URI::Params::Serializable + # + # @a : Int32 + # @b : Float64 = 1.0 + # end + # + # A.from_www_form("a=1") # => A(@a=1, @b=1.0) + # ``` + module Serializable + macro included + def self.from_www_form(params : String) + new_from_www_form URI::Params.parse params + end + + # :nodoc: + # + # This is needed so that nested types can pass the name thru internally. + # Has to be public so the generated code can call it, but should be considered an implementation detail. + def self.from_www_form(params : ::URI::Params, name : String) + new_from_www_form(params, name) + end + + protected def self.new_from_www_form(params : ::URI::Params, name : String? = nil) + instance = allocate + instance.initialize(__uri_params: params, name: name) + GC.add_finalizer(instance) if instance.responds_to?(:finalize) + instance + end + + macro inherited + def self.from_www_form(params : String) + new_from_www_form URI::Params.parse params + end + + # :nodoc: + def self.from_www_form(params : ::URI::Params, name : String) + new_from_www_form(params, name) + end + end + end + + # :nodoc: + def initialize(*, __uri_params params : ::URI::Params, name : String?) + {% begin %} + {% for ivar, idx in @type.instance_vars %} + %name{idx} = name.nil? ? {{ivar.name.stringify}} : "#{name}[#{{{ivar.name.stringify}}}]" + %value{idx} = {{(ann = ivar.annotation(URI::Params::Field)) && (converter = ann["converter"]) ? converter : ivar.type}}.from_www_form params, %name{idx} + + unless %value{idx}.nil? + @{{ivar.name.id}} = %value{idx} + else + {% unless ivar.type.resolve.nilable? || ivar.has_default_value? %} + raise URI::SerializableError.new "Missing required property: '#{%name{idx}}'." + {% end %} + end + {% end %} + {% end %} + end + + def to_www_form(*, space_to_plus : Bool = true) : String + URI::Params.build(space_to_plus: space_to_plus) do |form| + {% for ivar in @type.instance_vars %} + @{{ivar.name.id}}.to_www_form form, {{ivar.name.stringify}} + {% end %} + end + end + + # :nodoc: + def to_www_form(builder : URI::Params::Builder, name : String) + {% for ivar in @type.instance_vars %} + @{{ivar.name.id}}.to_www_form builder, "#{name}[#{{{ivar.name.stringify}}}]" + {% end %} + end + end +end + +class URI::SerializableError < URI::Error +end diff --git a/src/uri/params/to_www_form.cr b/src/uri/params/to_www_form.cr new file mode 100644 index 000000000000..3a0007781e64 --- /dev/null +++ b/src/uri/params/to_www_form.cr @@ -0,0 +1,48 @@ +struct Bool + # :nodoc: + def to_www_form(builder : URI::Params::Builder, name : String) : Nil + builder.add name, to_s + end +end + +class Array + # :nodoc: + def to_www_form(builder : URI::Params::Builder, name : String) : Nil + each &.to_www_form builder, name + end +end + +class String + # :nodoc: + def to_www_form(builder : URI::Params::Builder, name : String) : Nil + builder.add name, self + end +end + +struct Number + # :nodoc: + def to_www_form(builder : URI::Params::Builder, name : String) : Nil + builder.add name, to_s + end +end + +struct Nil + # :nodoc: + def to_www_form(builder : URI::Params::Builder, name : String) : Nil + builder.add name, self + end +end + +struct Enum + # :nodoc: + def to_www_form(builder : URI::Params::Builder, name : String) : Nil + builder.add name, to_s.underscore + end +end + +struct Time + # :nodoc: + def to_www_form(builder : URI::Params::Builder, name : String) : Nil + builder.add name, to_rfc3339 + end +end From 0ad3e91668610e1b22379cc8e5c1c5fbba40d34e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 25 Aug 2024 19:15:11 +0800 Subject: [PATCH 1316/1551] Fix `String#index` and `#rindex` for `Char::REPLACEMENT` (#14937) If the string consists only of ASCII characters and invalid UTF-8 byte sequences, all the latter should correspond to `Char::REPLACEMENT`, and so `#index` and `#rindex` should detect them, but this was broken since #14461. --- spec/std/string_spec.cr | 6 ++++++ src/string.cr | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 6bb4bd2c0c62..5b70deda13c3 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -957,6 +957,7 @@ describe "String" do it { "日本語".index('本').should eq(1) } it { "bar".index('あ').should be_nil } it { "あいう_えお".index('_').should eq(3) } + it { "xyz\xFFxyz".index('\u{FFFD}').should eq(3) } describe "with offset" do it { "foobarbaz".index('a', 5).should eq(7) } @@ -964,6 +965,8 @@ describe "String" do it { "foo".index('g', 1).should be_nil } it { "foo".index('g', -20).should be_nil } it { "日本語日本語".index('本', 2).should eq(4) } + it { "xyz\xFFxyz".index('\u{FFFD}', 2).should eq(3) } + it { "xyz\xFFxyz".index('\u{FFFD}', 4).should be_nil } # Check offset type it { "foobarbaz".index('a', 5_i64).should eq(7) } @@ -1106,6 +1109,7 @@ describe "String" do it { "foobar".rindex('g').should be_nil } it { "日本語日本語".rindex('本').should eq(4) } it { "あいう_えお".rindex('_').should eq(3) } + it { "xyz\xFFxyz".rindex('\u{FFFD}').should eq(3) } describe "with offset" do it { "bbbb".rindex('b', 2).should eq(2) } @@ -1118,6 +1122,8 @@ describe "String" do it { "faobar".rindex('a', 3).should eq(1) } it { "faobarbaz".rindex('a', -3).should eq(4) } it { "日本語日本語".rindex('本', 3).should eq(1) } + it { "xyz\xFFxyz".rindex('\u{FFFD}', 4).should eq(3) } + it { "xyz\xFFxyz".rindex('\u{FFFD}', 2).should be_nil } # Check offset type it { "bbbb".rindex('b', 2_i64).should eq(2) } diff --git a/src/string.cr b/src/string.cr index cf96401253b8..35c33b903939 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3349,11 +3349,21 @@ class String def index(search : Char, offset = 0) : Int32? # If it's ASCII we can delegate to slice if single_byte_optimizable? - # With `single_byte_optimizable?` there are only ASCII characters and invalid UTF-8 byte - # sequences and we can immediately reject any non-ASCII codepoint. - return unless search.ascii? + # With `single_byte_optimizable?` there are only ASCII characters and + # invalid UTF-8 byte sequences, and we can reject anything that is neither + # ASCII nor the replacement character. + case search + when .ascii? + return to_slice.fast_index(search.ord.to_u8!, offset) + when Char::REPLACEMENT + offset.upto(bytesize - 1) do |i| + if to_unsafe[i] >= 0x80 + return i.to_i + end + end + end - return to_slice.fast_index(search.ord.to_u8, offset) + return nil end offset += size if offset < 0 @@ -3469,11 +3479,21 @@ class String def rindex(search : Char, offset = size - 1) # If it's ASCII we can delegate to slice if single_byte_optimizable? - # With `single_byte_optimizable?` there are only ASCII characters and invalid UTF-8 byte - # sequences and we can immediately reject any non-ASCII codepoint. - return unless search.ascii? + # With `single_byte_optimizable?` there are only ASCII characters and + # invalid UTF-8 byte sequences, and we can reject anything that is neither + # ASCII nor the replacement character. + case search + when .ascii? + return to_slice.rindex(search.ord.to_u8!, offset) + when Char::REPLACEMENT + offset.downto(0) do |i| + if to_unsafe[i] >= 0x80 + return i.to_i + end + end + end - return to_slice.rindex(search.ord.to_u8, offset) + return nil end offset += size if offset < 0 From 8878c8b61e85bc4d473178252bbb41d59514e7ce Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 25 Aug 2024 19:15:54 +0800 Subject: [PATCH 1317/1551] Implement `System::User` on Windows (#14933) This is for the most part a straight port of [Go's implementation](https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/os/user/lookup_windows.go), including their interpretation of primary groups on Windows (as opposed to [whatever Cygwin does](https://cygwin.com/cygwin-ug-net/ntsec.html)). --- spec/std/system/user_spec.cr | 38 ++- src/crystal/system/user.cr | 2 + src/crystal/system/win32/path.cr | 20 +- src/crystal/system/win32/user.cr | 273 ++++++++++++++++++ .../x86_64-windows-msvc/c/knownfolders.cr | 1 + src/lib_c/x86_64-windows-msvc/c/lm.cr | 59 ++++ src/lib_c/x86_64-windows-msvc/c/sddl.cr | 6 + src/lib_c/x86_64-windows-msvc/c/security.cr | 21 ++ src/lib_c/x86_64-windows-msvc/c/userenv.cr | 6 + src/lib_c/x86_64-windows-msvc/c/winbase.cr | 7 + src/lib_c/x86_64-windows-msvc/c/winnt.cr | 25 ++ 11 files changed, 437 insertions(+), 21 deletions(-) create mode 100644 src/crystal/system/win32/user.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/lm.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/sddl.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/security.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/userenv.cr diff --git a/spec/std/system/user_spec.cr b/spec/std/system/user_spec.cr index 9fea934bc227..f0cb977d014d 100644 --- a/spec/std/system/user_spec.cr +++ b/spec/std/system/user_spec.cr @@ -1,20 +1,36 @@ -{% skip_file if flag?(:win32) %} - require "spec" require "system/user" -USER_NAME = {{ `id -un`.stringify.chomp }} -USER_ID = {{ `id -u`.stringify.chomp }} +{% if flag?(:win32) %} + {% name, id = `whoami /USER /FO TABLE /NH`.stringify.chomp.split(" ") %} + USER_NAME = {{ name }} + USER_ID = {{ id }} +{% else %} + USER_NAME = {{ `id -un`.stringify.chomp }} + USER_ID = {{ `id -u`.stringify.chomp }} +{% end %} + INVALID_USER_NAME = "this_user_does_not_exist" INVALID_USER_ID = {% if flag?(:android) %}"8888"{% else %}"1234567"{% end %} +def normalized_username(username) + # on Windows, domain names are case-insensitive, so we unify the letter case + # from sources like `whoami`, `hostname`, or Win32 APIs + {% if flag?(:win32) %} + domain, _, user = username.partition('\\') + "#{domain.upcase}\\#{user}" + {% else %} + username + {% end %} +end + describe System::User do describe ".find_by(*, name)" do it "returns a user by name" do user = System::User.find_by(name: USER_NAME) user.should be_a(System::User) - user.username.should eq(USER_NAME) + normalized_username(user.username).should eq(normalized_username(USER_NAME)) user.id.should eq(USER_ID) end @@ -31,7 +47,7 @@ describe System::User do user.should be_a(System::User) user.id.should eq(USER_ID) - user.username.should eq(USER_NAME) + normalized_username(user.username).should eq(normalized_username(USER_NAME)) end it "raises on nonexistent user id" do @@ -46,7 +62,7 @@ describe System::User do user = System::User.find_by?(name: USER_NAME).not_nil! user.should be_a(System::User) - user.username.should eq(USER_NAME) + normalized_username(user.username).should eq(normalized_username(USER_NAME)) user.id.should eq(USER_ID) end @@ -62,7 +78,7 @@ describe System::User do user.should be_a(System::User) user.id.should eq(USER_ID) - user.username.should eq(USER_NAME) + normalized_username(user.username).should eq(normalized_username(USER_NAME)) end it "returns nil on nonexistent user id" do @@ -73,7 +89,8 @@ describe System::User do describe "#username" do it "is the same as the source name" do - System::User.find_by(name: USER_NAME).username.should eq(USER_NAME) + user = System::User.find_by(name: USER_NAME) + normalized_username(user.username).should eq(normalized_username(USER_NAME)) end end @@ -109,7 +126,8 @@ describe System::User do describe "#to_s" do it "returns a string representation" do - System::User.find_by(name: USER_NAME).to_s.should eq("#{USER_NAME} (#{USER_ID})") + user = System::User.find_by(name: USER_NAME) + user.to_s.should eq("#{user.username} (#{user.id})") end end end diff --git a/src/crystal/system/user.cr b/src/crystal/system/user.cr index cb3db8cda026..88766496a9d8 100644 --- a/src/crystal/system/user.cr +++ b/src/crystal/system/user.cr @@ -20,6 +20,8 @@ end require "./wasi/user" {% elsif flag?(:unix) %} require "./unix/user" +{% elsif flag?(:win32) %} + require "./win32/user" {% else %} {% raise "No Crystal::System::User implementation available" %} {% end %} diff --git a/src/crystal/system/win32/path.cr b/src/crystal/system/win32/path.cr index 06f9346a2bae..f7bb1d23191b 100644 --- a/src/crystal/system/win32/path.cr +++ b/src/crystal/system/win32/path.cr @@ -4,18 +4,16 @@ require "c/shlobj_core" module Crystal::System::Path def self.home : String - if home_path = ENV["USERPROFILE"]?.presence - home_path + ENV["USERPROFILE"]?.presence || known_folder_path(LibC::FOLDERID_Profile) + end + + def self.known_folder_path(guid : LibC::GUID) : String + if LibC.SHGetKnownFolderPath(pointerof(guid), 0, nil, out path_ptr) == 0 + path, _ = String.from_utf16(path_ptr) + LibC.CoTaskMemFree(path_ptr) + path else - # TODO: interpreter doesn't implement pointerof(Path)` yet - folderid = LibC::FOLDERID_Profile - if LibC.SHGetKnownFolderPath(pointerof(folderid), 0, nil, out path_ptr) == 0 - home_path, _ = String.from_utf16(path_ptr) - LibC.CoTaskMemFree(path_ptr) - home_path - else - raise RuntimeError.from_winerror("SHGetKnownFolderPath") - end + raise RuntimeError.from_winerror("SHGetKnownFolderPath") end end end diff --git a/src/crystal/system/win32/user.cr b/src/crystal/system/win32/user.cr new file mode 100644 index 000000000000..e5fcdbba10aa --- /dev/null +++ b/src/crystal/system/win32/user.cr @@ -0,0 +1,273 @@ +require "c/sddl" +require "c/lm" +require "c/userenv" +require "c/security" + +# This file contains source code derived from the following: +# +# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/os/user/lookup_windows.go +# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/syscall/security_windows.go +# +# The following is their license: +# +# Copyright 2009 The Go Authors. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google LLC nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +module Crystal::System::User + def initialize(@username : String, @id : String, @group_id : String, @name : String, @home_directory : String) + end + + def system_username + @username + end + + def system_id + @id + end + + def system_group_id + @group_id + end + + def system_name + @name + end + + def system_home_directory + @home_directory + end + + def system_shell + Crystal::System::User.cmd_path + end + + class_getter(cmd_path : String) do + "#{Crystal::System::Path.known_folder_path(LibC::FOLDERID_System)}\\cmd.exe" + end + + def self.from_username?(username : String) : ::System::User? + if found = name_to_sid(username) + if found.type.sid_type_user? + from_sid(found.sid) + end + end + end + + def self.from_id?(id : String) : ::System::User? + if sid = sid_from_s(id) + begin + from_sid(sid) + ensure + LibC.LocalFree(sid) + end + end + end + + private def self.from_sid(sid : LibC::SID*) : ::System::User? + canonical = sid_to_name(sid) || return + return unless canonical.type.sid_type_user? + + domain_and_user = "#{canonical.domain}\\#{canonical.name}" + full_name = lookup_full_name(canonical.name, canonical.domain, domain_and_user) || return + pgid = lookup_primary_group_id(canonical.name, canonical.domain) || return + uid = sid_to_s(sid) + home_dir = lookup_home_directory(uid, canonical.name) || return + + ::System::User.new(domain_and_user, uid, pgid, full_name, home_dir) + end + + private def self.lookup_full_name(name : String, domain : String, domain_and_user : String) : String? + if domain_joined? + domain_and_user = Crystal::System.to_wstr(domain_and_user) + Crystal::System.retry_wstr_buffer do |buffer, small_buf| + len = LibC::ULong.new(buffer.size) + if LibC.TranslateNameW(domain_and_user, LibC::EXTENDED_NAME_FORMAT::NameSamCompatible, LibC::EXTENDED_NAME_FORMAT::NameDisplay, buffer, pointerof(len)) != 0 + return String.from_utf16(buffer[0, len - 1]) + elsif small_buf && len > 0 + next len + else + break + end + end + end + + info = uninitialized LibC::USER_INFO_10* + if LibC.NetUserGetInfo(Crystal::System.to_wstr(domain), Crystal::System.to_wstr(name), 10, pointerof(info).as(LibC::BYTE**)) == LibC::NERR_Success + begin + str, _ = String.from_utf16(info.value.usri10_full_name) + return str + ensure + LibC.NetApiBufferFree(info) + end + end + + # domain worked neither as a domain nor as a server + # could be domain server unavailable + # pretend username is fullname + name + end + + # obtains the primary group SID for a user using this method: + # https://support.microsoft.com/en-us/help/297951/how-to-use-the-primarygroupid-attribute-to-find-the-primary-group-for + # The method follows this formula: domainRID + "-" + primaryGroupRID + private def self.lookup_primary_group_id(name : String, domain : String) : String? + domain_sid = name_to_sid(domain) || return + return unless domain_sid.type.sid_type_domain? + + domain_sid_str = sid_to_s(domain_sid.sid) + + # If the user has joined a domain use the RID of the default primary group + # called "Domain Users": + # https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems + # SID: S-1-5-21domain-513 + # + # The correct way to obtain the primary group of a domain user is + # probing the user primaryGroupID attribute in the server Active Directory: + # https://learn.microsoft.com/en-us/windows/win32/adschema/a-primarygroupid + # + # Note that the primary group of domain users should not be modified + # on Windows for performance reasons, even if it's possible to do that. + # The .NET Developer's Guide to Directory Services Programming - Page 409 + # https://books.google.bg/books?id=kGApqjobEfsC&lpg=PA410&ots=p7oo-eOQL7&dq=primary%20group%20RID&hl=bg&pg=PA409#v=onepage&q&f=false + return "#{domain_sid_str}-513" if domain_joined? + + # For non-domain users call NetUserGetInfo() with level 4, which + # in this case would not have any network overhead. + # The primary group should not change from RID 513 here either + # but the group will be called "None" instead: + # https://www.adampalmer.me/iodigitalsec/2013/08/10/windows-null-session-enumeration/ + # "Group 'None' (RID: 513)" + info = uninitialized LibC::USER_INFO_4* + if LibC.NetUserGetInfo(Crystal::System.to_wstr(domain), Crystal::System.to_wstr(name), 4, pointerof(info).as(LibC::BYTE**)) == LibC::NERR_Success + begin + "#{domain_sid_str}-#{info.value.usri4_primary_group_id}" + ensure + LibC.NetApiBufferFree(info) + end + end + end + + private REGISTRY_PROFILE_LIST = %q(SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList).to_utf16 + private ProfileImagePath = "ProfileImagePath".to_utf16 + + private def self.lookup_home_directory(uid : String, username : String) : String? + # If this user has logged in at least once their home path should be stored + # in the registry under the specified SID. References: + # https://social.technet.microsoft.com/wiki/contents/articles/13895.how-to-remove-a-corrupted-user-profile-from-the-registry.aspx + # https://support.asperasoft.com/hc/en-us/articles/216127438-How-to-delete-Windows-user-profiles + # + # The registry is the most reliable way to find the home path as the user + # might have decided to move it outside of the default location, + # (e.g. C:\users). Reference: + # https://answers.microsoft.com/en-us/windows/forum/windows_7-security/how-do-i-set-a-home-directory-outside-cusers-for-a/aed68262-1bf4-4a4d-93dc-7495193a440f + reg_home_dir = WindowsRegistry.open?(LibC::HKEY_LOCAL_MACHINE, REGISTRY_PROFILE_LIST) do |key_handle| + WindowsRegistry.open?(key_handle, uid.to_utf16) do |sub_handle| + WindowsRegistry.get_string(sub_handle, ProfileImagePath) + end + end + return reg_home_dir if reg_home_dir + + # If the home path does not exist in the registry, the user might + # have not logged in yet; fall back to using getProfilesDirectory(). + # Find the username based on a SID and append that to the result of + # getProfilesDirectory(). The domain is not relevant here. + # NOTE: the user has not logged in so this directory might not exist + profile_dir = Crystal::System.retry_wstr_buffer do |buffer, small_buf| + len = LibC::DWORD.new(buffer.size) + if LibC.GetProfilesDirectoryW(buffer, pointerof(len)) != 0 + break String.from_utf16(buffer[0, len - 1]) + elsif small_buf && len > 0 + next len + else + break nil + end + end + return "#{profile_dir}\\#{username}" if profile_dir + end + + private record SIDLookupResult, sid : LibC::SID*, domain : String, type : LibC::SID_NAME_USE + + private def self.name_to_sid(name : String) : SIDLookupResult? + utf16_name = Crystal::System.to_wstr(name) + + sid_size = LibC::DWORD.zero + domain_buf_size = LibC::DWORD.zero + LibC.LookupAccountNameW(nil, utf16_name, nil, pointerof(sid_size), nil, pointerof(domain_buf_size), out _) + + unless WinError.value.error_none_mapped? + sid = Pointer(UInt8).malloc(sid_size).as(LibC::SID*) + domain_buf = Slice(LibC::WCHAR).new(domain_buf_size) + if LibC.LookupAccountNameW(nil, utf16_name, sid, pointerof(sid_size), domain_buf, pointerof(domain_buf_size), out sid_type) != 0 + domain = String.from_utf16(domain_buf[..-2]) + SIDLookupResult.new(sid, domain, sid_type) + end + end + end + + private record NameLookupResult, name : String, domain : String, type : LibC::SID_NAME_USE + + private def self.sid_to_name(sid : LibC::SID*) : NameLookupResult? + name_buf_size = LibC::DWORD.zero + domain_buf_size = LibC::DWORD.zero + LibC.LookupAccountSidW(nil, sid, nil, pointerof(name_buf_size), nil, pointerof(domain_buf_size), out _) + + unless WinError.value.error_none_mapped? + name_buf = Slice(LibC::WCHAR).new(name_buf_size) + domain_buf = Slice(LibC::WCHAR).new(domain_buf_size) + if LibC.LookupAccountSidW(nil, sid, name_buf, pointerof(name_buf_size), domain_buf, pointerof(domain_buf_size), out sid_type) != 0 + name = String.from_utf16(name_buf[..-2]) + domain = String.from_utf16(domain_buf[..-2]) + NameLookupResult.new(name, domain, sid_type) + end + end + end + + private def self.domain_joined? : Bool + status = LibC.NetGetJoinInformation(nil, out domain, out type) + if status != LibC::NERR_Success + raise RuntimeError.from_os_error("NetGetJoinInformation", WinError.new(status)) + end + is_domain = type.net_setup_domain_name? + LibC.NetApiBufferFree(domain) + is_domain + end + + private def self.sid_to_s(sid : LibC::SID*) : String + if LibC.ConvertSidToStringSidW(sid, out ptr) == 0 + raise RuntimeError.from_winerror("ConvertSidToStringSidW") + end + str, _ = String.from_utf16(ptr) + LibC.LocalFree(ptr) + str + end + + private def self.sid_from_s(str : String) : LibC::SID* + status = LibC.ConvertStringSidToSidW(Crystal::System.to_wstr(str), out sid) + status != 0 ? sid : Pointer(LibC::SID).null + end +end diff --git a/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr b/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr index 04c16573cc76..6ce1831cb1e5 100644 --- a/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr +++ b/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr @@ -2,4 +2,5 @@ require "c/guiddef" lib LibC FOLDERID_Profile = GUID.new(0x5e6c858f, 0x0e22, 0x4760, UInt8.static_array(0x9a, 0xfe, 0xea, 0x33, 0x17, 0xb6, 0x71, 0x73)) + FOLDERID_System = GUID.new(0x1ac14e77, 0x02e7, 0x4e5d, UInt8.static_array(0xb7, 0x44, 0x2e, 0xb1, 0xae, 0x51, 0x98, 0xb7)) end diff --git a/src/lib_c/x86_64-windows-msvc/c/lm.cr b/src/lib_c/x86_64-windows-msvc/c/lm.cr new file mode 100644 index 000000000000..72f5affc9b55 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/lm.cr @@ -0,0 +1,59 @@ +require "c/winnt" + +@[Link("netapi32")] +lib LibC + alias NET_API_STATUS = DWORD + + NERR_Success = NET_API_STATUS.new!(0) + + enum NETSETUP_JOIN_STATUS + NetSetupUnknownStatus = 0 + NetSetupUnjoined + NetSetupWorkgroupName + NetSetupDomainName + end + + fun NetGetJoinInformation(lpServer : LPWSTR, lpNameBuffer : LPWSTR*, bufferType : NETSETUP_JOIN_STATUS*) : NET_API_STATUS + + struct USER_INFO_4 + usri4_name : LPWSTR + usri4_password : LPWSTR + usri4_password_age : DWORD + usri4_priv : DWORD + usri4_home_dir : LPWSTR + usri4_comment : LPWSTR + usri4_flags : DWORD + usri4_script_path : LPWSTR + usri4_auth_flags : DWORD + usri4_full_name : LPWSTR + usri4_usr_comment : LPWSTR + usri4_parms : LPWSTR + usri4_workstations : LPWSTR + usri4_last_logon : DWORD + usri4_last_logoff : DWORD + usri4_acct_expires : DWORD + usri4_max_storage : DWORD + usri4_units_per_week : DWORD + usri4_logon_hours : BYTE* + usri4_bad_pw_count : DWORD + usri4_num_logons : DWORD + usri4_logon_server : LPWSTR + usri4_country_code : DWORD + usri4_code_page : DWORD + usri4_user_sid : SID* + usri4_primary_group_id : DWORD + usri4_profile : LPWSTR + usri4_home_dir_drive : LPWSTR + usri4_password_expired : DWORD + end + + struct USER_INFO_10 + usri10_name : LPWSTR + usri10_comment : LPWSTR + usri10_usr_comment : LPWSTR + usri10_full_name : LPWSTR + end + + fun NetUserGetInfo(servername : LPWSTR, username : LPWSTR, level : DWORD, bufptr : BYTE**) : NET_API_STATUS + fun NetApiBufferFree(buffer : Void*) : NET_API_STATUS +end diff --git a/src/lib_c/x86_64-windows-msvc/c/sddl.cr b/src/lib_c/x86_64-windows-msvc/c/sddl.cr new file mode 100644 index 000000000000..64e1fa8b25c1 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/sddl.cr @@ -0,0 +1,6 @@ +require "c/winnt" + +lib LibC + fun ConvertSidToStringSidW(sid : SID*, stringSid : LPWSTR*) : BOOL + fun ConvertStringSidToSidW(stringSid : LPWSTR, sid : SID**) : BOOL +end diff --git a/src/lib_c/x86_64-windows-msvc/c/security.cr b/src/lib_c/x86_64-windows-msvc/c/security.cr new file mode 100644 index 000000000000..5a904c51df40 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/security.cr @@ -0,0 +1,21 @@ +require "c/winnt" + +@[Link("secur32")] +lib LibC + enum EXTENDED_NAME_FORMAT + NameUnknown = 0 + NameFullyQualifiedDN = 1 + NameSamCompatible = 2 + NameDisplay = 3 + NameUniqueId = 6 + NameCanonical = 7 + NameUserPrincipal = 8 + NameCanonicalEx = 9 + NameServicePrincipal = 10 + NameDnsDomain = 12 + NameGivenName = 13 + NameSurname = 14 + end + + fun TranslateNameW(lpAccountName : LPWSTR, accountNameFormat : EXTENDED_NAME_FORMAT, desiredNameFormat : EXTENDED_NAME_FORMAT, lpTranslatedName : LPWSTR, nSize : ULong*) : BOOLEAN +end diff --git a/src/lib_c/x86_64-windows-msvc/c/userenv.cr b/src/lib_c/x86_64-windows-msvc/c/userenv.cr new file mode 100644 index 000000000000..bb32977d79f7 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/userenv.cr @@ -0,0 +1,6 @@ +require "c/winnt" + +@[Link("userenv")] +lib LibC + fun GetProfilesDirectoryW(lpProfileDir : LPWSTR, lpcchSize : DWORD*) : BOOL +end diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index 0a736a4fa89c..7b7a8735ddf2 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -4,6 +4,10 @@ require "c/int_safe" require "c/minwinbase" lib LibC + alias HLOCAL = Void* + + fun LocalFree(hMem : HLOCAL) + FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100_u32 FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200_u32 FORMAT_MESSAGE_FROM_STRING = 0x00000400_u32 @@ -69,4 +73,7 @@ lib LibC end fun GetFileInformationByHandleEx(hFile : HANDLE, fileInformationClass : FILE_INFO_BY_HANDLE_CLASS, lpFileInformation : Void*, dwBufferSize : DWORD) : BOOL + + fun LookupAccountNameW(lpSystemName : LPWSTR, lpAccountName : LPWSTR, sid : SID*, cbSid : DWORD*, referencedDomainName : LPWSTR, cchReferencedDomainName : DWORD*, peUse : SID_NAME_USE*) : BOOL + fun LookupAccountSidW(lpSystemName : LPWSTR, sid : SID*, name : LPWSTR, cchName : DWORD*, referencedDomainName : LPWSTR, cchReferencedDomainName : DWORD*, peUse : SID_NAME_USE*) : BOOL end diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index 535ad835c87a..1db4b2def700 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -95,6 +95,31 @@ lib LibC WRITE = 0x20006 end + struct SID_IDENTIFIER_AUTHORITY + value : BYTE[6] + end + + struct SID + revision : BYTE + subAuthorityCount : BYTE + identifierAuthority : SID_IDENTIFIER_AUTHORITY + subAuthority : DWORD[1] + end + + enum SID_NAME_USE + SidTypeUser = 1 + SidTypeGroup + SidTypeDomain + SidTypeAlias + SidTypeWellKnownGroup + SidTypeDeletedAccount + SidTypeInvalid + SidTypeUnknown + SidTypeComputer + SidTypeLabel + SidTypeLogonSession + end + enum JOBOBJECTINFOCLASS AssociateCompletionPortInformation = 7 ExtendedLimitInformation = 9 From bd49e2e904a1dd0822a8343e362a50a6bc4ac719 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 26 Aug 2024 17:27:50 +0800 Subject: [PATCH 1318/1551] Optimize arithmetic between `BigFloat` and integers (#14944) --- src/big/big_float.cr | 65 ++++++++++++++++++++++++++++++++++++++++++++ src/big/lib_gmp.cr | 3 ++ src/big/number.cr | 22 --------------- 3 files changed, 68 insertions(+), 22 deletions(-) diff --git a/src/big/big_float.cr b/src/big/big_float.cr index cadc91282fc1..2c567f21eec9 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -115,18 +115,60 @@ struct BigFloat < Float BigFloat.new { |mpf| LibGMP.mpf_neg(mpf, self) } end + def +(other : Int::Primitive) : BigFloat + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: BigFloat.new { |mpf| LibGMP.mpf_add_ui(mpf, self, {{ ui }}) }, + neg_ui: BigFloat.new { |mpf| LibGMP.mpf_sub_ui(mpf, self, {{ neg_ui }}) }, + big_i: self + {{ big_i }}, + } + end + end + def +(other : Number) : BigFloat BigFloat.new { |mpf| LibGMP.mpf_add(mpf, self, other.to_big_f) } end + def -(other : Int::Primitive) : BigFloat + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: BigFloat.new { |mpf| LibGMP.mpf_sub_ui(mpf, self, {{ ui }}) }, + neg_ui: BigFloat.new { |mpf| LibGMP.mpf_add_ui(mpf, self, {{ neg_ui }}) }, + big_i: self - {{ big_i }}, + } + end + end + def -(other : Number) : BigFloat BigFloat.new { |mpf| LibGMP.mpf_sub(mpf, self, other.to_big_f) } end + def *(other : Int::Primitive) : BigFloat + Int.primitive_ui_check(other) do |ui, neg_ui, big_i| + { + ui: BigFloat.new { |mpf| LibGMP.mpf_mul_ui(mpf, self, {{ ui }}) }, + neg_ui: BigFloat.new { |mpf| LibGMP.mpf_mul_ui(mpf, self, {{ neg_ui }}); LibGMP.mpf_neg(mpf, mpf) }, + big_i: self + {{ big_i }}, + } + end + end + def *(other : Number) : BigFloat BigFloat.new { |mpf| LibGMP.mpf_mul(mpf, self, other.to_big_f) } end + def /(other : Int::Primitive) : BigFloat + # Division by 0 in BigFloat is not allowed, there is no BigFloat::Infinity + raise DivisionByZeroError.new if other == 0 + Int.primitive_ui_check(other) do |ui, neg_ui, _| + { + ui: BigFloat.new { |mpf| LibGMP.mpf_div_ui(mpf, self, {{ ui }}) }, + neg_ui: BigFloat.new { |mpf| LibGMP.mpf_div_ui(mpf, self, {{ neg_ui }}); LibGMP.mpf_neg(mpf, mpf) }, + big_i: BigFloat.new { |mpf| LibGMP.mpf_div(mpf, self, BigFloat.new(other)) }, + } + end + end + def /(other : BigFloat) : BigFloat # Division by 0 in BigFloat is not allowed, there is no BigFloat::Infinity raise DivisionByZeroError.new if other == 0 @@ -448,6 +490,29 @@ struct Int def <=>(other : BigFloat) -(other <=> self) end + + def -(other : BigFloat) : BigFloat + Int.primitive_ui_check(self) do |ui, neg_ui, _| + { + ui: BigFloat.new { |mpf| LibGMP.mpf_neg(mpf, other); LibGMP.mpf_add_ui(mpf, mpf, {{ ui }}) }, + neg_ui: BigFloat.new { |mpf| LibGMP.mpf_neg(mpf, other); LibGMP.mpf_sub_ui(mpf, mpf, {{ neg_ui }}) }, + big_i: BigFloat.new { |mpf| LibGMP.mpf_sub(mpf, BigFloat.new(self), other) }, + } + end + end + + def /(other : BigFloat) : BigFloat + # Division by 0 in BigFloat is not allowed, there is no BigFloat::Infinity + raise DivisionByZeroError.new if other == 0 + + Int.primitive_ui_check(self) do |ui, neg_ui, _| + { + ui: BigFloat.new { |mpf| LibGMP.mpf_ui_div(mpf, {{ ui }}, other) }, + neg_ui: BigFloat.new { |mpf| LibGMP.mpf_ui_div(mpf, {{ neg_ui }}, other); LibGMP.mpf_neg(mpf, mpf) }, + big_i: BigFloat.new { |mpf| LibGMP.mpf_div(mpf, BigFloat.new(self), other) }, + } + end + end end struct Float diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index 00834598d9d2..5ae18b5a4606 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -233,8 +233,11 @@ lib LibGMP # # Arithmetic fun mpf_add = __gmpf_add(rop : MPF*, op1 : MPF*, op2 : MPF*) + fun mpf_add_ui = __gmpf_add_ui(rop : MPF*, op1 : MPF*, op2 : UI) fun mpf_sub = __gmpf_sub(rop : MPF*, op1 : MPF*, op2 : MPF*) + fun mpf_sub_ui = __gmpf_sub_ui(rop : MPF*, op1 : MPF*, op2 : UI) fun mpf_mul = __gmpf_mul(rop : MPF*, op1 : MPF*, op2 : MPF*) + fun mpf_mul_ui = __gmpf_mul_ui(rop : MPF*, op1 : MPF*, op2 : UI) fun mpf_div = __gmpf_div(rop : MPF*, op1 : MPF*, op2 : MPF*) fun mpf_div_ui = __gmpf_div_ui(rop : MPF*, op1 : MPF*, op2 : UI) fun mpf_ui_div = __gmpf_ui_div(rop : MPF*, op1 : UI, op2 : MPF*) diff --git a/src/big/number.cr b/src/big/number.cr index 1251e8113db3..8761a2aa8b6c 100644 --- a/src/big/number.cr +++ b/src/big/number.cr @@ -8,18 +8,6 @@ struct BigFloat self.class.new(self / other) end - def /(other : Int::Primitive) : BigFloat - # Division by 0 in BigFloat is not allowed, there is no BigFloat::Infinity - raise DivisionByZeroError.new if other == 0 - Int.primitive_ui_check(other) do |ui, neg_ui, _| - { - ui: BigFloat.new { |mpf| LibGMP.mpf_div_ui(mpf, self, {{ ui }}) }, - neg_ui: BigFloat.new { |mpf| LibGMP.mpf_div_ui(mpf, self, {{ neg_ui }}); LibGMP.mpf_neg(mpf, mpf) }, - big_i: BigFloat.new { |mpf| LibGMP.mpf_div(mpf, self, BigFloat.new(other)) }, - } - end - end - Number.expand_div [Float32, Float64], BigFloat end @@ -91,70 +79,60 @@ end struct Int8 Number.expand_div [BigInt], BigFloat - Number.expand_div [BigFloat], BigFloat Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational end struct Int16 Number.expand_div [BigInt], BigFloat - Number.expand_div [BigFloat], BigFloat Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational end struct Int32 Number.expand_div [BigInt], BigFloat - Number.expand_div [BigFloat], BigFloat Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational end struct Int64 Number.expand_div [BigInt], BigFloat - Number.expand_div [BigFloat], BigFloat Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational end struct Int128 Number.expand_div [BigInt], BigFloat - Number.expand_div [BigFloat], BigFloat Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational end struct UInt8 Number.expand_div [BigInt], BigFloat - Number.expand_div [BigFloat], BigFloat Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational end struct UInt16 Number.expand_div [BigInt], BigFloat - Number.expand_div [BigFloat], BigFloat Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational end struct UInt32 Number.expand_div [BigInt], BigFloat - Number.expand_div [BigFloat], BigFloat Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational end struct UInt64 Number.expand_div [BigInt], BigFloat - Number.expand_div [BigFloat], BigFloat Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational end struct UInt128 Number.expand_div [BigInt], BigFloat - Number.expand_div [BigFloat], BigFloat Number.expand_div [BigDecimal], BigDecimal Number.expand_div [BigRational], BigRational end From 791b0e451766503e4a8d2b63fd1bc726b9948276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=C3=B6rjesson?= Date: Mon, 26 Aug 2024 11:29:00 +0200 Subject: [PATCH 1319/1551] Add nodoc filter to doc type methods (#14910) The doc generator is creating links to non-documented type. This patch adds filters on `Doc::Type#ancestors`, `Doc::Type#included_modules` and `Doc::Type#extended_modules` as it was already done in `Doc::Type#subclasses`. --- spec/compiler/crystal/tools/doc/type_spec.cr | 136 +++++++++++++++++++ src/compiler/crystal/tools/doc/type.cr | 3 + 2 files changed, 139 insertions(+) diff --git a/spec/compiler/crystal/tools/doc/type_spec.cr b/spec/compiler/crystal/tools/doc/type_spec.cr index 34ab535f6d5e..c5dde7d4b258 100644 --- a/spec/compiler/crystal/tools/doc/type_spec.cr +++ b/spec/compiler/crystal/tools/doc/type_spec.cr @@ -212,4 +212,140 @@ describe Doc::Type do type.macros.map(&.name).should eq ["+", "~", "foo"] end end + + describe "#subclasses" do + it "only include types with docs" do + program = semantic(<<-CRYSTAL, wants_doc: true).program + class Foo + end + + class Bar < Foo + end + + # :nodoc: + class Baz < Foo + end + + module Mod1 + class Bar < ::Foo + end + end + + # :nodoc: + module Mod2 + class Baz < ::Foo + end + end + CRYSTAL + + generator = Doc::Generator.new program, [""] + type = generator.type(program.types["Foo"]) + type.subclasses.map(&.full_name).should eq ["Bar", "Mod1::Bar"] + end + end + + describe "#ancestors" do + it "only include types with docs" do + program = semantic(<<-CRYSTAL, wants_doc: true).program + # :nodoc: + module Mod3 + class Baz + end + end + + class Mod2::Baz < Mod3::Baz + end + + module Mod1 + # :nodoc: + class Baz < Mod2::Baz + end + end + + class Baz < Mod1::Baz + end + + class Foo < Baz + end + CRYSTAL + + generator = Doc::Generator.new program, [""] + type = generator.type(program.types["Foo"]) + type.ancestors.map(&.full_name).should eq ["Baz", "Mod2::Baz"] + end + end + + describe "#included_modules" do + it "only include types with docs" do + program = semantic(<<-CRYSTAL, wants_doc: true).program + # :nodoc: + module Mod3 + module Baz + end + end + + module Mod2 + # :nodoc: + module Baz + end + end + + module Mod1 + module Baz + end + end + + module Baz + end + + class Foo + include Baz + include Mod1::Baz + include Mod2::Baz + include Mod3::Baz + end + CRYSTAL + + generator = Doc::Generator.new program, [""] + type = generator.type(program.types["Foo"]) + type.included_modules.map(&.full_name).should eq ["Baz", "Mod1::Baz"] + end + end + + describe "#included_modules" do + it "only include types with docs" do + program = semantic(<<-CRYSTAL, wants_doc: true).program + # :nodoc: + module Mod3 + module Baz + end + end + + module Mod2 + # :nodoc: + module Baz + end + end + + module Mod1 + module Baz + end + end + + module Baz + end + + class Foo + extend Baz + extend Mod1::Baz + extend Mod2::Baz + extend Mod3::Baz + end + CRYSTAL + + generator = Doc::Generator.new program, [""] + type = generator.type(program.types["Foo"]) + type.extended_modules.map(&.full_name).should eq ["Baz", "Mod1::Baz"] + end + end end diff --git a/src/compiler/crystal/tools/doc/type.cr b/src/compiler/crystal/tools/doc/type.cr index 624c8f017fe7..9b1a0a86cf7e 100644 --- a/src/compiler/crystal/tools/doc/type.cr +++ b/src/compiler/crystal/tools/doc/type.cr @@ -117,6 +117,7 @@ class Crystal::Doc::Type unless ast_node? @type.ancestors.each do |ancestor| + next unless @generator.must_include? ancestor doc_type = @generator.type(ancestor) ancestors << doc_type break if ancestor == @generator.program.object || doc_type.ast_node? @@ -258,6 +259,7 @@ class Crystal::Doc::Type included_modules = [] of Type @type.parents.try &.each do |parent| if parent.module? + next unless @generator.must_include? parent included_modules << @generator.type(parent) end end @@ -272,6 +274,7 @@ class Crystal::Doc::Type extended_modules = [] of Type @type.metaclass.parents.try &.each do |parent| if parent.module? + next unless @generator.must_include? parent extended_modules << @generator.type(parent) end end From c015ff6388bb3023e92668baccf2088bf067381b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 2 Sep 2024 20:33:21 +0800 Subject: [PATCH 1320/1551] Support non-blocking `File#read` and `#write` on Windows (#14940) --- spec/std/file_spec.cr | 16 +++++++++++++ src/crystal/system/win32/event_loop_iocp.cr | 11 ++++----- src/crystal/system/win32/iocp.cr | 26 +++++++++++++++++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 96dbacd73cc9..55a7b5d76494 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -122,6 +122,22 @@ describe "File" do end {% end %} {% end %} + + it "reads non-blocking file" do + File.open(datapath("test_file.txt"), "r", blocking: false) do |f| + f.gets_to_end.should eq("Hello World\n" * 20) + end + end + + it "writes and reads large non-blocking file" do + with_tempfile("non-blocking-io.txt") do |path| + File.open(path, "w+", blocking: false) do |f| + f.puts "Hello World\n" * 40000 + f.pos = 0 + f.gets_to_end.should eq("Hello World\n" * 40000) + end + end + end end it "reads entire file" do diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 25c8db41d9ff..6f9a921ad8d3 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -144,18 +144,15 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop end def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 - handle = file_descriptor.windows_handle - IOCP.overlapped_operation(file_descriptor, handle, "ReadFile", file_descriptor.read_timeout) do |overlapped| - ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped) + IOCP.overlapped_operation(file_descriptor, "ReadFile", file_descriptor.read_timeout) do |overlapped| + ret = LibC.ReadFile(file_descriptor.windows_handle, slice, slice.size, out byte_count, overlapped) {ret, byte_count} end.to_i32 end def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 - handle = file_descriptor.windows_handle - - IOCP.overlapped_operation(file_descriptor, handle, "WriteFile", file_descriptor.write_timeout, writing: true) do |overlapped| - ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped) + IOCP.overlapped_operation(file_descriptor, "WriteFile", file_descriptor.write_timeout, writing: true) do |overlapped| + ret = LibC.WriteFile(file_descriptor.windows_handle, slice, slice.size, out byte_count, overlapped) {ret, byte_count} end.to_i32 end diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr index add5a29c2814..af8f778290f3 100644 --- a/src/crystal/system/win32/iocp.cr +++ b/src/crystal/system/win32/iocp.cr @@ -168,8 +168,16 @@ module Crystal::IOCP end end - def self.overlapped_operation(target, handle, method, timeout, *, writing = false, &) + def self.overlapped_operation(file_descriptor, method, timeout, *, writing = false, &) + handle = file_descriptor.windows_handle + seekable = LibC.SetFilePointerEx(handle, 0, out original_offset, IO::Seek::Current) != 0 + OverlappedOperation.run(handle) do |operation| + overlapped = operation.to_unsafe + if seekable + overlapped.value.union.offset.offset = LibC::DWORD.new!(original_offset) + overlapped.value.union.offset.offsetHigh = LibC::DWORD.new!(original_offset >> 32) + end result, value = yield operation if result == 0 @@ -181,15 +189,19 @@ module Crystal::IOCP when .error_io_pending? # the operation is running asynchronously; do nothing when .error_access_denied? - raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}", target: target + raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}", target: file_descriptor else - raise IO::Error.from_os_error(method, error, target: target) + raise IO::Error.from_os_error(method, error, target: file_descriptor) end else + # operation completed synchronously; seek forward by number of bytes + # read or written if handle is seekable, since overlapped I/O doesn't do + # it automatically + LibC.SetFilePointerEx(handle, value, nil, IO::Seek::Current) if seekable return value end - operation.wait_for_result(timeout) do |error| + byte_count = operation.wait_for_result(timeout) do |error| case error when .error_io_incomplete?, .error_operation_aborted? raise IO::TimeoutError.new("#{method} timed out") @@ -200,6 +212,12 @@ module Crystal::IOCP return 0_u32 end end + + # operation completed asynchronously; seek to the original file position + # plus the number of bytes read or written (other operations might have + # moved the file pointer so we don't use `IO::Seek::Current` here) + LibC.SetFilePointerEx(handle, original_offset + byte_count, nil, IO::Seek::Set) if seekable + byte_count end end From e6b5b949f2e4bce024fc9d039de167b59d00d75a Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Mon, 2 Sep 2024 07:35:41 -0500 Subject: [PATCH 1321/1551] Optimize `Hash#transform_{keys,values}` (#14502) --- src/hash.cr | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hash.cr b/src/hash.cr index e2fe7dad186c..9b2936ddd618 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -1747,7 +1747,8 @@ class Hash(K, V) # hash.transform_keys { |key, value| key.to_s * value } # => {"a" => 1, "bb" => 2, "ccc" => 3} # ``` def transform_keys(& : K, V -> K2) : Hash(K2, V) forall K2 - each_with_object({} of K2 => V) do |(key, value), memo| + copy = Hash(K2, V).new(initial_capacity: entries_capacity) + each_with_object(copy) do |(key, value), memo| memo[yield(key, value)] = value end end @@ -1762,7 +1763,8 @@ class Hash(K, V) # hash.transform_values { |value, key| "#{key}#{value}" } # => {:a => "a1", :b => "b2", :c => "c3"} # ``` def transform_values(& : V, K -> V2) : Hash(K, V2) forall V2 - each_with_object({} of K => V2) do |(key, value), memo| + copy = Hash(K, V2).new(initial_capacity: entries_capacity) + each_with_object(copy) do |(key, value), memo| memo[key] = yield(value, key) end end From 281fc3233d06331975e491ebcbbc69521cd4c65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 3 Sep 2024 13:21:44 +0200 Subject: [PATCH 1322/1551] Add specs for `String#index`, `#rindex` search for `Char::REPLACEMENT` (#14946) Co-authored-by: Quinton Miller --- spec/std/string_spec.cr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 5b70deda13c3..2ffe5bf3d1fa 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -958,6 +958,7 @@ describe "String" do it { "bar".index('あ').should be_nil } it { "あいう_えお".index('_').should eq(3) } it { "xyz\xFFxyz".index('\u{FFFD}').should eq(3) } + it { "日\xFF語".index('\u{FFFD}').should eq(1) } describe "with offset" do it { "foobarbaz".index('a', 5).should eq(7) } @@ -967,6 +968,8 @@ describe "String" do it { "日本語日本語".index('本', 2).should eq(4) } it { "xyz\xFFxyz".index('\u{FFFD}', 2).should eq(3) } it { "xyz\xFFxyz".index('\u{FFFD}', 4).should be_nil } + it { "日本\xFF語".index('\u{FFFD}', 2).should eq(2) } + it { "日本\xFF語".index('\u{FFFD}', 3).should be_nil } # Check offset type it { "foobarbaz".index('a', 5_i64).should eq(7) } @@ -1110,6 +1113,7 @@ describe "String" do it { "日本語日本語".rindex('本').should eq(4) } it { "あいう_えお".rindex('_').should eq(3) } it { "xyz\xFFxyz".rindex('\u{FFFD}').should eq(3) } + it { "日\xFF語".rindex('\u{FFFD}').should eq(1) } describe "with offset" do it { "bbbb".rindex('b', 2).should eq(2) } @@ -1124,6 +1128,8 @@ describe "String" do it { "日本語日本語".rindex('本', 3).should eq(1) } it { "xyz\xFFxyz".rindex('\u{FFFD}', 4).should eq(3) } it { "xyz\xFFxyz".rindex('\u{FFFD}', 2).should be_nil } + it { "日本\xFF語".rindex('\u{FFFD}', 2).should eq(2) } + it { "日本\xFF語".rindex('\u{FFFD}', 1).should be_nil } # Check offset type it { "bbbb".rindex('b', 2_i64).should eq(2) } From 598931c66700432e8ec1c7b9032791bfeae6364f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 3 Sep 2024 19:21:55 +0800 Subject: [PATCH 1323/1551] Allow `^` in constant numeric expressions (#14951) --- spec/compiler/codegen/c_enum_spec.cr | 7 +++++++ src/compiler/crystal/semantic/math_interpreter.cr | 1 + 2 files changed, 8 insertions(+) diff --git a/spec/compiler/codegen/c_enum_spec.cr b/spec/compiler/codegen/c_enum_spec.cr index c5197799d2cf..75c9966c6c10 100644 --- a/spec/compiler/codegen/c_enum_spec.cr +++ b/spec/compiler/codegen/c_enum_spec.cr @@ -20,15 +20,22 @@ describe "Code gen: c enum" do end [ + {"+1", 1}, + {"-1", -1}, + {"~1", -2}, {"1 + 2", 3}, {"3 - 2", 1}, {"3 * 2", 6}, + {"1 &+ 2", 3}, + {"3 &- 2", 1}, + {"3 &* 2", 6}, # {"10 / 2", 5}, # MathInterpreter only works with Integer and 10 / 2 : Float {"10 // 2", 5}, {"1 << 3", 8}, {"100 >> 3", 12}, {"10 & 3", 2}, {"10 | 3", 11}, + {"10 ^ 3", 9}, {"(1 + 2) * 3", 9}, {"10 % 3", 1}, ].each do |(code, expected)| diff --git a/src/compiler/crystal/semantic/math_interpreter.cr b/src/compiler/crystal/semantic/math_interpreter.cr index c39d290aa1e9..d6846e420a7b 100644 --- a/src/compiler/crystal/semantic/math_interpreter.cr +++ b/src/compiler/crystal/semantic/math_interpreter.cr @@ -73,6 +73,7 @@ struct Crystal::MathInterpreter when "//" then left // right when "&" then left & right when "|" then left | right + when "^" then left ^ right when "<<" then left << right when ">>" then left >> right when "%" then left % right From 6ee4eb92f35858585b15279a061bcaba284774c5 Mon Sep 17 00:00:00 2001 From: "Brian J. Cardiff" Date: Tue, 3 Sep 2024 10:04:13 -0300 Subject: [PATCH 1324/1551] Add `Crystal::Repl::Value#runtime_type` (#14156) Co-authored-by: Sijawusz Pur Rahnama --- spec/compiler/crystal/tools/repl_spec.cr | 49 +++++++++++++++++++ .../crystal/interpreter/primitives.cr | 2 + src/compiler/crystal/interpreter/value.cr | 15 ++++++ 3 files changed, 66 insertions(+) diff --git a/spec/compiler/crystal/tools/repl_spec.cr b/spec/compiler/crystal/tools/repl_spec.cr index 3a1e1275ef12..7a387624f8fa 100644 --- a/spec/compiler/crystal/tools/repl_spec.cr +++ b/spec/compiler/crystal/tools/repl_spec.cr @@ -17,4 +17,53 @@ describe Crystal::Repl do success_value(repl.parse_and_interpret("def foo; 1 + 2; end")).value.should eq(nil) success_value(repl.parse_and_interpret("foo")).value.should eq(3) end + + describe "can return static and runtime type information for" do + it "Non Union" do + repl = Crystal::Repl.new + repl.prelude = "primitives" + repl.load_prelude + + repl_value = success_value(repl.parse_and_interpret("1")) + repl_value.type.to_s.should eq("Int32") + repl_value.runtime_type.to_s.should eq("Int32") + end + + it "MixedUnionType" do + repl = Crystal::Repl.new + repl.prelude = "primitives" + repl.load_prelude + + repl_value = success_value(repl.parse_and_interpret("1 || \"a\"")) + repl_value.type.to_s.should eq("(Int32 | String)") + repl_value.runtime_type.to_s.should eq("Int32") + end + + it "UnionType" do + repl = Crystal::Repl.new + repl.prelude = "primitives" + repl.load_prelude + + repl_value = success_value(repl.parse_and_interpret("true || 1")) + repl_value.type.to_s.should eq("(Bool | Int32)") + repl_value.runtime_type.to_s.should eq("Bool") + end + + it "VirtualType" do + repl = Crystal::Repl.new + repl.prelude = "primitives" + repl.load_prelude + + repl.parse_and_interpret <<-CRYSTAL + class Foo + end + + class Bar < Foo + end + CRYSTAL + repl_value = success_value(repl.parse_and_interpret("Bar.new || Foo.new")) + repl_value.type.to_s.should eq("Foo+") # Maybe should Foo to match typeof + repl_value.runtime_type.to_s.should eq("Bar") + end + end end diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr index e411229600f9..7ad508f8d0fc 100644 --- a/src/compiler/crystal/interpreter/primitives.cr +++ b/src/compiler/crystal/interpreter/primitives.cr @@ -87,6 +87,8 @@ class Crystal::Repl::Compiler pointer_add(inner_sizeof_type(element_type), node: node) when "class" + # Should match Crystal::Repl::Value#runtime_type + # in src/compiler/crystal/interpreter/value.cr obj = obj.not_nil! type = obj.type.remove_indirection diff --git a/src/compiler/crystal/interpreter/value.cr b/src/compiler/crystal/interpreter/value.cr index 349dff00f78b..681798bf7a32 100644 --- a/src/compiler/crystal/interpreter/value.cr +++ b/src/compiler/crystal/interpreter/value.cr @@ -67,6 +67,21 @@ struct Crystal::Repl::Value end end + def runtime_type : Crystal::Type + # Should match Crystal::Repl::Compiler#visit_primitive "class" case + # in src/compiler/crystal/interpreter/primitives.cr + case type + when Crystal::UnionType + type_id = @pointer.as(Int32*).value + context.type_from_id(type_id) + when Crystal::VirtualType + type_id = @pointer.as(Void**).value.as(Int32*).value + context.type_from_id(type_id) + else + type + end + end + # Copies the contents of this value to another pointer. def copy_to(pointer : Pointer(UInt8)) @pointer.copy_to(pointer, context.inner_sizeof_type(@type)) From 18c28f9b2f643604d839d7c03508b6420b62f65e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 4 Sep 2024 01:59:18 +0800 Subject: [PATCH 1325/1551] Support non-blocking `File#read_at` on Windows (#14958) --- spec/std/file_spec.cr | 20 +++++++------ src/crystal/system/unix/file_descriptor.cr | 6 ++-- src/crystal/system/win32/file_descriptor.cr | 33 +++++++++++---------- src/crystal/system/win32/iocp.cr | 14 +++++---- src/file/preader.cr | 2 +- 5 files changed, 42 insertions(+), 33 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 55a7b5d76494..eb740885cd69 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -1295,17 +1295,19 @@ describe "File" do it "reads at offset" do filename = datapath("test_file.txt") - File.open(filename) do |file| - file.read_at(6, 100) do |io| - io.gets_to_end.should eq("World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello Worl") - end + {true, false}.each do |blocking| + File.open(filename, blocking: blocking) do |file| + file.read_at(6, 100) do |io| + io.gets_to_end.should eq("World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello Worl") + end - file.read_at(0, 240) do |io| - io.gets_to_end.should eq(File.read(filename)) - end + file.read_at(0, 240) do |io| + io.gets_to_end.should eq(File.read(filename)) + end - file.read_at(6_i64, 5_i64) do |io| - io.gets_to_end.should eq("World") + file.read_at(6_i64, 5_i64) do |io| + io.gets_to_end.should eq("World") + end end end end diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index d235114849b4..fc8839ac9e83 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -219,11 +219,11 @@ module Crystal::System::FileDescriptor {r, w} end - def self.pread(fd, buffer, offset) - bytes_read = LibC.pread(fd, buffer, buffer.size, offset).to_i64 + def self.pread(file, buffer, offset) + bytes_read = LibC.pread(file.fd, buffer, buffer.size, offset).to_i64 if bytes_read == -1 - raise IO::Error.from_errno "Error reading file" + raise IO::Error.from_errno("Error reading file", target: file) end bytes_read diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 37813307191f..f4e9200a0488 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -120,10 +120,6 @@ module Crystal::System::FileDescriptor end protected def windows_handle - FileDescriptor.windows_handle(fd) - end - - def self.windows_handle(fd) LibC::HANDLE.new(fd) end @@ -278,19 +274,26 @@ module Crystal::System::FileDescriptor {r, w} end - def self.pread(fd, buffer, offset) - handle = windows_handle(fd) + def self.pread(file, buffer, offset) + handle = file.windows_handle - overlapped = LibC::OVERLAPPED.new - overlapped.union.offset.offset = LibC::DWORD.new!(offset) - overlapped.union.offset.offsetHigh = LibC::DWORD.new!(offset >> 32) - if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0 - error = WinError.value - return 0_i64 if error == WinError::ERROR_HANDLE_EOF - raise IO::Error.from_os_error "Error reading file", error, target: self - end + if file.system_blocking? + overlapped = LibC::OVERLAPPED.new + overlapped.union.offset.offset = LibC::DWORD.new!(offset) + overlapped.union.offset.offsetHigh = LibC::DWORD.new!(offset >> 32) + if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0 + error = WinError.value + return 0_i64 if error == WinError::ERROR_HANDLE_EOF + raise IO::Error.from_os_error "Error reading file", error, target: file + end - bytes_read.to_i64 + bytes_read.to_i64 + else + IOCP.overlapped_operation(file, "ReadFile", file.read_timeout, offset: offset) do |overlapped| + ret = LibC.ReadFile(handle, buffer, buffer.size, out byte_count, overlapped) + {ret, byte_count} + end.to_i64 + end end def self.from_stdio(fd) diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr index af8f778290f3..6f5746954277 100644 --- a/src/crystal/system/win32/iocp.cr +++ b/src/crystal/system/win32/iocp.cr @@ -168,15 +168,16 @@ module Crystal::IOCP end end - def self.overlapped_operation(file_descriptor, method, timeout, *, writing = false, &) + def self.overlapped_operation(file_descriptor, method, timeout, *, offset = nil, writing = false, &) handle = file_descriptor.windows_handle seekable = LibC.SetFilePointerEx(handle, 0, out original_offset, IO::Seek::Current) != 0 OverlappedOperation.run(handle) do |operation| overlapped = operation.to_unsafe if seekable - overlapped.value.union.offset.offset = LibC::DWORD.new!(original_offset) - overlapped.value.union.offset.offsetHigh = LibC::DWORD.new!(original_offset >> 32) + start_offset = offset || original_offset + overlapped.value.union.offset.offset = LibC::DWORD.new!(start_offset) + overlapped.value.union.offset.offsetHigh = LibC::DWORD.new!(start_offset >> 32) end result, value = yield operation @@ -215,8 +216,11 @@ module Crystal::IOCP # operation completed asynchronously; seek to the original file position # plus the number of bytes read or written (other operations might have - # moved the file pointer so we don't use `IO::Seek::Current` here) - LibC.SetFilePointerEx(handle, original_offset + byte_count, nil, IO::Seek::Set) if seekable + # moved the file pointer so we don't use `IO::Seek::Current` here), unless + # we are calling `Crystal::System::FileDescriptor.pread` + if seekable && !offset + LibC.SetFilePointerEx(handle, original_offset + byte_count, nil, IO::Seek::Set) + end byte_count end end diff --git a/src/file/preader.cr b/src/file/preader.cr index d366457314ce..9f7d09643305 100644 --- a/src/file/preader.cr +++ b/src/file/preader.cr @@ -20,7 +20,7 @@ class File::PReader < IO count = slice.size count = Math.min(count, @bytesize - @pos) - bytes_read = Crystal::System::FileDescriptor.pread(@file.fd, slice[0, count], @offset + @pos) + bytes_read = Crystal::System::FileDescriptor.pread(@file, slice[0, count], @offset + @pos) @pos += bytes_read From e9b86d00f54dcf1fc5c92e37217fc298ee225f8f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 4 Sep 2024 01:59:29 +0800 Subject: [PATCH 1326/1551] Update REPLy version (#14950) --- lib/.shards.info | 2 +- lib/reply/shard.yml | 2 +- lib/reply/spec/reader_spec.cr | 33 ++++++++++++++++++- lib/reply/spec/spec_helper.cr | 21 ++++++++++++ lib/reply/src/char_reader.cr | 25 +-------------- lib/reply/src/reader.cr | 60 ++++++++++++++++++++++++----------- lib/reply/src/term_size.cr | 3 -- shard.lock | 2 +- shard.yml | 2 +- 9 files changed, 99 insertions(+), 51 deletions(-) diff --git a/lib/.shards.info b/lib/.shards.info index 7f03bb906410..b6371e9397c4 100644 --- a/lib/.shards.info +++ b/lib/.shards.info @@ -6,4 +6,4 @@ shards: version: 0.5.0 reply: git: https://github.com/i3oris/reply.git - version: 0.3.1+git.commit.90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 + version: 0.3.1+git.commit.db423dae3dd34c6ba5e36174653a0c109117a167 diff --git a/lib/reply/shard.yml b/lib/reply/shard.yml index e6cd9dab283a..02a0d3490923 100644 --- a/lib/reply/shard.yml +++ b/lib/reply/shard.yml @@ -5,7 +5,7 @@ description: "Shard to create a REPL interface" authors: - I3oris -crystal: 1.5.0 +crystal: 1.13.0 license: MIT diff --git a/lib/reply/spec/reader_spec.cr b/lib/reply/spec/reader_spec.cr index 4e9f446f3de0..4dbc53cbb51b 100644 --- a/lib/reply/spec/reader_spec.cr +++ b/lib/reply/spec/reader_spec.cr @@ -254,7 +254,7 @@ module Reply reader.auto_completion.verify(open: true, entries: %w(hello hey), name_filter: "h", selection_pos: 0) reader.editor.verify("42.hello") - SpecHelper.send(pipe_in, "\e\t") # shit_tab + SpecHelper.send(pipe_in, "\e\t") # shift_tab reader.auto_completion.verify(open: true, entries: %w(hello hey), name_filter: "h", selection_pos: 1) reader.editor.verify("42.hey") @@ -298,6 +298,37 @@ module Reply SpecHelper.send(pipe_in, '\0') end + it "retriggers auto-completion when current word ends with ':'" do + reader = SpecHelper.reader(SpecReaderWithAutoCompletionRetrigger) + pipe_out, pipe_in = IO.pipe + + spawn do + reader.read_next(from: pipe_out) + end + + SpecHelper.send(pipe_in, "fo") + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(foo foobar), name_filter: "fo") + reader.editor.verify("foo") + + SpecHelper.send(pipe_in, ':') + SpecHelper.send(pipe_in, ':') + reader.auto_completion.verify(open: true, entries: %w(foo::foo foo::foobar foo::bar), name_filter: "foo::") + reader.editor.verify("foo::") + + SpecHelper.send(pipe_in, 'b') + SpecHelper.send(pipe_in, '\t') + reader.auto_completion.verify(open: true, entries: %w(foo::bar), name_filter: "foo::b", selection_pos: 0) + reader.editor.verify("foo::bar") + + SpecHelper.send(pipe_in, ':') + SpecHelper.send(pipe_in, ':') + reader.auto_completion.verify(open: true, entries: %w(foo::bar::foo foo::bar::foobar foo::bar::bar), name_filter: "foo::bar::") + reader.editor.verify("foo::bar::") + + SpecHelper.send(pipe_in, '\0') + end + it "uses escape" do reader = SpecHelper.reader pipe_out, pipe_in = IO.pipe diff --git a/lib/reply/spec/spec_helper.cr b/lib/reply/spec/spec_helper.cr index 432220b98f98..7e0a93052320 100644 --- a/lib/reply/spec/spec_helper.cr +++ b/lib/reply/spec/spec_helper.cr @@ -94,6 +94,27 @@ module Reply getter auto_completion end + class SpecReaderWithAutoCompletionRetrigger < Reader + def initialize + super + self.word_delimiters.delete(':') + end + + def auto_complete(current_word : String, expression_before : String) + if current_word.ends_with? "::" + return "title", ["#{current_word}foo", "#{current_word}foobar", "#{current_word}bar"] + else + return "title", %w(foo foobar bar) + end + end + + def auto_completion_retrigger_when(current_word : String) : Bool + current_word.ends_with? ':' + end + + getter auto_completion + end + module SpecHelper def self.auto_completion(returning results) results = results.clone diff --git a/lib/reply/src/char_reader.cr b/lib/reply/src/char_reader.cr index 3da5ca06d804..c4ab01ca802e 100644 --- a/lib/reply/src/char_reader.cr +++ b/lib/reply/src/char_reader.cr @@ -43,20 +43,9 @@ module Reply @slice_buffer = Bytes.new(buffer_size) end - def read_char(from io : T = STDIN) forall T - {% if flag?(:win32) && T <= IO::FileDescriptor %} - handle = LibC._get_osfhandle(io.fd) - raise RuntimeError.from_errno("_get_osfhandle") if handle == -1 - - raw(io) do - LibC.ReadConsoleA(LibC::HANDLE.new(handle), @slice_buffer, @slice_buffer.size, out nb_read, nil) - - parse_escape_sequence(@slice_buffer[0...nb_read]) - end - {% else %} + def read_char(from io : IO = STDIN) nb_read = raw(io, &.read(@slice_buffer)) parse_escape_sequence(@slice_buffer[0...nb_read]) - {% end %} end private def parse_escape_sequence(chars : Bytes) : Char | Sequence | String? @@ -184,15 +173,3 @@ module Reply end end end - -{% if flag?(:win32) %} - lib LibC - STD_INPUT_HANDLE = -10 - - fun ReadConsoleA(hConsoleInput : Void*, - lpBuffer : Void*, - nNumberOfCharsToRead : UInt32, - lpNumberOfCharsRead : UInt32*, - pInputControl : Void*) : UInt8 - end -{% end %} diff --git a/lib/reply/src/reader.cr b/lib/reply/src/reader.cr index f8bb5bbb03fd..01228cf7027a 100644 --- a/lib/reply/src/reader.cr +++ b/lib/reply/src/reader.cr @@ -168,6 +168,13 @@ module Reply @auto_completion.default_display_selected_entry(io, entry) end + # Override to retrigger auto completion when condition is met. + # + # default: `false` + def auto_completion_retrigger_when(current_word : String) : Bool + false + end + # Override to enable line re-indenting. # # This methods is called each time a character is entered. @@ -240,8 +247,11 @@ module Reply if read.is_a?(CharReader::Sequence) && (read.tab? || read.enter? || read.alt_enter? || read.shift_tab? || read.escape? || read.backspace? || read.ctrl_c?) else if @auto_completion.open? - auto_complete_insert_char(read) - @editor.update + replacement = auto_complete_insert_char(read) + # Replace the current_word by the replacement word + @editor.update do + @editor.current_word = replacement if replacement + end end end end @@ -362,12 +372,6 @@ module Reply end private def on_tab(shift_tab = false) - line = @editor.current_line - - # Retrieve the word under the cursor - word_begin, word_end = @editor.current_word_begin_end - current_word = line[word_begin..word_end] - if @auto_completion.open? if shift_tab replacement = @auto_completion.selection_previous @@ -375,15 +379,7 @@ module Reply replacement = @auto_completion.selection_next end else - # Get whole expression before cursor, allow auto-completion to deduce the receiver type - expr = @editor.expression_before_cursor(x: word_begin) - - # Compute auto-completion, return `replacement` (`nil` if no entry, full name if only one entry, or the begin match of entries otherwise) - replacement = @auto_completion.complete_on(current_word, expr) - - if replacement && @auto_completion.entries.size >= 2 - @auto_completion.open - end + replacement = compute_completions end # Replace the current_word by the replacement word @@ -405,14 +401,40 @@ module Reply @editor.move_cursor_to_end end - private def auto_complete_insert_char(char) + private def compute_completions : String? + line = @editor.current_line + + # Retrieve the word under the cursor + word_begin, word_end = @editor.current_word_begin_end + current_word = line[word_begin..word_end] + + expr = @editor.expression_before_cursor(x: word_begin) + + # Compute auto-completion, return `replacement` (`nil` if no entry, full name if only one entry, or the begin match of entries otherwise) + replacement = @auto_completion.complete_on(current_word, expr) + + if replacement + if @auto_completion.entries.size >= 2 + @auto_completion.open + else + @auto_completion.name_filter = replacement + end + end + + replacement + end + + private def auto_complete_insert_char(char) : String? if char.is_a? Char && !char.in?(@editor.word_delimiters) - @auto_completion.name_filter = @editor.current_word + @auto_completion.name_filter = current_word = @editor.current_word + + return compute_completions if auto_completion_retrigger_when(current_word + char) elsif @editor.expression_scrolled? || char.is_a?(String) @auto_completion.close else @auto_completion.clear end + nil end private def auto_complete_remove_char diff --git a/lib/reply/src/term_size.cr b/lib/reply/src/term_size.cr index fd0c60421c4f..3af381101543 100644 --- a/lib/reply/src/term_size.cr +++ b/lib/reply/src/term_size.cr @@ -120,10 +120,7 @@ end dwMaximumWindowSize : COORD end - STD_OUTPUT_HANDLE = -11 - fun GetConsoleScreenBufferInfo(hConsoleOutput : Void*, lpConsoleScreenBufferInfo : CONSOLE_SCREEN_BUFFER_INFO*) : Void - fun GetStdHandle(nStdHandle : UInt32) : Void* end {% else %} lib LibC diff --git a/shard.lock b/shard.lock index e7f2ddc86d10..697bfe23b3c3 100644 --- a/shard.lock +++ b/shard.lock @@ -6,5 +6,5 @@ shards: reply: git: https://github.com/i3oris/reply.git - version: 0.3.1+git.commit.90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 + version: 0.3.1+git.commit.db423dae3dd34c6ba5e36174653a0c109117a167 diff --git a/shard.yml b/shard.yml index 85b76f49c8d8..1b2835281466 100644 --- a/shard.yml +++ b/shard.yml @@ -14,7 +14,7 @@ dependencies: github: icyleaf/markd reply: github: I3oris/reply - commit: 90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7 + commit: db423dae3dd34c6ba5e36174653a0c109117a167 license: Apache-2.0 From d2e87322c045bd792cd7853d837136e89cc3aa3a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 4 Sep 2024 01:59:44 +0800 Subject: [PATCH 1327/1551] Simplify `Socket::Addrinfo.getaddrinfo(&)` (#14956) This private method is now directly responsible for iterating over all `LibC::Addrinfo` objects, so there is no need to store this information in the `Socket::Addrinfo` struct itself. --- src/socket/addrinfo.cr | 62 +++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr index 83ef561c88ac..c7a8ada00d86 100644 --- a/src/socket/addrinfo.cr +++ b/src/socket/addrinfo.cr @@ -10,7 +10,6 @@ class Socket getter size : Int32 @addr : LibC::SockaddrIn6 - @next : LibC::Addrinfo* # Resolves a domain that best matches the given options. # @@ -34,13 +33,10 @@ class Socket addrinfos = [] of Addrinfo getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo| - loop do - addrinfos << addrinfo.not_nil! - unless addrinfo = addrinfo.next? - return addrinfos - end - end + addrinfos << addrinfo end + + addrinfos end # Resolves a domain that best matches the given options. @@ -57,28 +53,29 @@ class Socket # The iteration will be stopped once the block returns something that isn't # an `Exception` (e.g. a `Socket` or `nil`). def self.resolve(domain : String, service, family : Family? = nil, type : Type = nil, protocol : Protocol = Protocol::IP, timeout = nil, &) + exception = nil + getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo| - loop do - value = yield addrinfo.not_nil! - - if value.is_a?(Exception) - unless addrinfo = addrinfo.try(&.next?) - if value.is_a?(Socket::ConnectError) - raise Socket::ConnectError.from_os_error("Error connecting to '#{domain}:#{service}'", value.os_error) - else - {% if flag?(:win32) && compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 %} - # FIXME: Workaround for https://github.com/crystal-lang/crystal/issues/11047 - array = StaticArray(UInt8, 0).new(0) - {% end %} - - raise value - end - end - else - return value - end + value = yield addrinfo + + if value.is_a?(Exception) + exception = value + else + return value end end + + case exception + when Socket::ConnectError + raise Socket::ConnectError.from_os_error("Error connecting to '#{domain}:#{service}'", exception.os_error) + when Exception + {% if flag?(:win32) && compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 %} + # FIXME: Workaround for https://github.com/crystal-lang/crystal/issues/11047 + array = StaticArray(UInt8, 0).new(0) + {% end %} + + raise exception + end end class Error < Socket::Error @@ -179,8 +176,12 @@ class Socket raise Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service) end + addrinfo = ptr begin - yield new(ptr) + while addrinfo + yield new(addrinfo) + addrinfo = addrinfo.value.ai_next + end ensure LibC.freeaddrinfo(ptr) end @@ -232,7 +233,6 @@ class Socket @size = addrinfo.value.ai_addrlen.to_i @addr = uninitialized LibC::SockaddrIn6 - @next = addrinfo.value.ai_next case @family when Family::INET6 @@ -263,11 +263,5 @@ class Socket def to_unsafe pointerof(@addr).as(LibC::Sockaddr*) end - - protected def next? - if addrinfo = @next - Addrinfo.new(addrinfo) - end - end end end From 73263a8f1ab0f5662dd4974420e8e8d5ab7ae989 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 4 Sep 2024 16:00:52 +0800 Subject: [PATCH 1328/1551] Support non-blocking `Process.run` standard streams on Windows (#14941) --- spec/std/process_spec.cr | 14 +++++++++ src/process.cr | 67 +++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index f067d2f5c775..57f90121c26b 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -189,6 +189,20 @@ pending_interpreted describe: Process do Process.run(*stdin_to_stdout_command, error: closed_io) end + it "forwards non-blocking file" do + with_tempfile("non-blocking-process-input.txt", "non-blocking-process-output.txt") do |in_path, out_path| + File.open(in_path, "w+", blocking: false) do |input| + File.open(out_path, "w+", blocking: false) do |output| + input.puts "hello" + input.rewind + Process.run(*stdin_to_stdout_command, input: input, output: output) + output.rewind + output.gets_to_end.chomp.should eq("hello") + end + end + end + end + it "sets working directory with string" do parent = File.dirname(Dir.current) command = {% if flag?(:win32) %} diff --git a/src/process.cr b/src/process.cr index c8364196373f..63b78bf0f716 100644 --- a/src/process.cr +++ b/src/process.cr @@ -291,33 +291,20 @@ class Process private def stdio_to_fd(stdio : Stdio, for dst_io : IO::FileDescriptor) : IO::FileDescriptor case stdio - when IO::FileDescriptor - stdio - when IO - if stdio.closed? - if dst_io == STDIN - return File.open(File::NULL, "r").tap(&.close) - else - return File.open(File::NULL, "w").tap(&.close) + in IO::FileDescriptor + # on Windows, only async pipes can be passed to child processes, async + # regular files will report an error and those require a separate pipe + # (https://github.com/crystal-lang/crystal/pull/13362#issuecomment-1519082712) + {% if flag?(:win32) %} + unless stdio.blocking || stdio.info.type.pipe? + return io_to_fd(stdio, for: dst_io) end - end - - if dst_io == STDIN - fork_io, process_io = IO.pipe(read_blocking: true) - - @wait_count += 1 - ensure_channel - spawn { copy_io(stdio, process_io, channel, close_dst: true) } - else - process_io, fork_io = IO.pipe(write_blocking: true) + {% end %} - @wait_count += 1 - ensure_channel - spawn { copy_io(process_io, stdio, channel, close_src: true) } - end - - fork_io - when Redirect::Pipe + stdio + in IO + io_to_fd(stdio, for: dst_io) + in Redirect::Pipe case dst_io when STDIN fork_io, @input = IO.pipe(read_blocking: true) @@ -330,17 +317,41 @@ class Process end fork_io - when Redirect::Inherit + in Redirect::Inherit dst_io - when Redirect::Close + in Redirect::Close if dst_io == STDIN File.open(File::NULL, "r") else File.open(File::NULL, "w") end + end + end + + private def io_to_fd(stdio : Stdio, for dst_io : IO::FileDescriptor) : IO::FileDescriptor + if stdio.closed? + if dst_io == STDIN + return File.open(File::NULL, "r").tap(&.close) + else + return File.open(File::NULL, "w").tap(&.close) + end + end + + if dst_io == STDIN + fork_io, process_io = IO.pipe(read_blocking: true) + + @wait_count += 1 + ensure_channel + spawn { copy_io(stdio, process_io, channel, close_dst: true) } else - raise "BUG: Impossible type in stdio #{stdio.class}" + process_io, fork_io = IO.pipe(write_blocking: true) + + @wait_count += 1 + ensure_channel + spawn { copy_io(process_io, stdio, channel, close_src: true) } end + + fork_io end # :nodoc: From a798ae62dfe09d9a0698a1c64fb0ab221dc354e3 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 4 Sep 2024 16:01:12 +0800 Subject: [PATCH 1329/1551] Support `IO::FileDescriptor#flock_*` on non-blocking files on Windows (#14943) --- spec/std/file_spec.cr | 66 +++++++++++---------- src/crystal/system/win32/file_descriptor.cr | 57 ++++++++++++------ 2 files changed, 74 insertions(+), 49 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index eb740885cd69..07b919bd4a6e 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -1248,46 +1248,50 @@ describe "File" do end end - it "#flock_shared" do - File.open(datapath("test_file.txt")) do |file1| - File.open(datapath("test_file.txt")) do |file2| - file1.flock_shared do - file2.flock_shared(blocking: false) { } + {true, false}.each do |blocking| + context "blocking: #{blocking}" do + it "#flock_shared" do + File.open(datapath("test_file.txt"), blocking: blocking) do |file1| + File.open(datapath("test_file.txt"), blocking: blocking) do |file2| + file1.flock_shared do + file2.flock_shared(blocking: false) { } + end + end end end - end - end - it "#flock_shared soft blocking fiber" do - File.open(datapath("test_file.txt")) do |file1| - File.open(datapath("test_file.txt")) do |file2| - done = Channel(Nil).new - file1.flock_exclusive + it "#flock_shared soft blocking fiber" do + File.open(datapath("test_file.txt"), blocking: blocking) do |file1| + File.open(datapath("test_file.txt"), blocking: blocking) do |file2| + done = Channel(Nil).new + file1.flock_exclusive - spawn do - file1.flock_unlock - done.send nil - end + spawn do + file1.flock_unlock + done.send nil + end - file2.flock_shared - done.receive + file2.flock_shared + done.receive + end + end end - end - end - it "#flock_exclusive soft blocking fiber" do - File.open(datapath("test_file.txt")) do |file1| - File.open(datapath("test_file.txt")) do |file2| - done = Channel(Nil).new - file1.flock_exclusive + it "#flock_exclusive soft blocking fiber" do + File.open(datapath("test_file.txt"), blocking: blocking) do |file1| + File.open(datapath("test_file.txt"), blocking: blocking) do |file2| + done = Channel(Nil).new + file1.flock_exclusive - spawn do - file1.flock_unlock - done.send nil - end + spawn do + file1.flock_unlock + done.send nil + end - file2.flock_exclusive - done.receive + file2.flock_exclusive + done.receive + end + end end end end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index f4e9200a0488..3c7823e62d3e 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -207,41 +207,62 @@ module Crystal::System::FileDescriptor end private def flock(exclusive, retry) - flags = LibC::LOCKFILE_FAIL_IMMEDIATELY + flags = 0_u32 + flags |= LibC::LOCKFILE_FAIL_IMMEDIATELY if !retry || system_blocking? flags |= LibC::LOCKFILE_EXCLUSIVE_LOCK if exclusive handle = windows_handle - if retry + if retry && system_blocking? until lock_file(handle, flags) sleep 0.1 end else - lock_file(handle, flags) || raise IO::Error.from_winerror("Error applying file lock: file is already locked") + lock_file(handle, flags) || raise IO::Error.from_winerror("Error applying file lock: file is already locked", target: self) end end private def lock_file(handle, flags) - # lpOverlapped must be provided despite the synchronous use of this method. - overlapped = LibC::OVERLAPPED.new - # lock the entire file with offset 0 in overlapped and number of bytes set to max value - if 0 != LibC.LockFileEx(handle, flags, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped)) - true - else - winerror = WinError.value - if winerror == WinError::ERROR_LOCK_VIOLATION - false + IOCP::OverlappedOperation.run(handle) do |operation| + result = LibC.LockFileEx(handle, flags, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, operation) + + if result == 0 + case error = WinError.value + when .error_io_pending? + # the operation is running asynchronously; do nothing + when .error_lock_violation? + # synchronous failure + return false + else + raise IO::Error.from_os_error("LockFileEx", error, target: self) + end else - raise IO::Error.from_os_error("LockFileEx", winerror, target: self) + return true end + + operation.wait_for_result(nil) do |error| + raise IO::Error.from_os_error("LockFileEx", error, target: self) + end + + true end end private def unlock_file(handle) - # lpOverlapped must be provided despite the synchronous use of this method. - overlapped = LibC::OVERLAPPED.new - # unlock the entire file with offset 0 in overlapped and number of bytes set to max value - if 0 == LibC.UnlockFileEx(handle, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped)) - raise IO::Error.from_winerror("UnLockFileEx") + IOCP::OverlappedOperation.run(handle) do |operation| + result = LibC.UnlockFileEx(handle, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, operation) + + if result == 0 + error = WinError.value + unless error.error_io_pending? + raise IO::Error.from_os_error("UnlockFileEx", error, target: self) + end + else + return + end + + operation.wait_for_result(nil) do |error| + raise IO::Error.from_os_error("UnlockFileEx", error, target: self) + end end end From 24b243bbf5af51c8fb5f1d45fc83f0ec56e4d493 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 5 Sep 2024 00:23:48 +0800 Subject: [PATCH 1330/1551] Use correct timeout for `Socket#connect` on Windows (#14961) It does not appear the use of `read_timeout` here was intended. This applies to connection-oriented sockets only. Connectionless sockets like `UDPSocket` call `Crystal::System::Socket#system_connect_connectionless` instead which ignores the timeout parameter. --- src/crystal/system/win32/event_loop_iocp.cr | 2 +- src/crystal/system/win32/socket.cr | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index 6f9a921ad8d3..d1aae09b680a 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -228,7 +228,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop end def connect(socket : ::Socket, address : ::Socket::Addrinfo | ::Socket::Address, timeout : ::Time::Span?) : IO::Error? - socket.overlapped_connect(socket.fd, "ConnectEx") do |overlapped| + socket.overlapped_connect(socket.fd, "ConnectEx", timeout) do |overlapped| # This is: LibC.ConnectEx(fd, address, address.size, nil, 0, nil, overlapped) Crystal::System::Socket.connect_ex.call(socket.fd, address.to_unsafe, address.size, Pointer(Void).null, 0_u32, Pointer(UInt32).null, overlapped.to_unsafe) end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 78645d51f320..3172be467836 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -128,7 +128,7 @@ module Crystal::System::Socket end # :nodoc: - def overlapped_connect(socket, method, &) + def overlapped_connect(socket, method, timeout, &) IOCP::OverlappedOperation.run(socket) do |operation| result = yield operation @@ -145,7 +145,7 @@ module Crystal::System::Socket return nil end - operation.wait_for_wsa_result(read_timeout) do |error| + operation.wait_for_wsa_result(timeout) do |error| case error when .wsa_io_incomplete?, .wsaeconnrefused? return ::Socket::ConnectError.from_os_error(method, error) From 1055af25d7acd7ce4f7dea6f933549d71873095b Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Wed, 4 Sep 2024 11:24:10 -0500 Subject: [PATCH 1331/1551] Make `IO::Buffered#buffer_size=` idempotent (#14855) The purpose of raising an exception here is to prevent the caller from doing something unsafe. _Changing_ the value is unsafe, but setting `buffer_size` to the same value is a safe operation. --- spec/std/io/buffered_spec.cr | 9 +++++++++ src/io/buffered.cr | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spec/std/io/buffered_spec.cr b/spec/std/io/buffered_spec.cr index fbf6ac638ab8..faf684da0e25 100644 --- a/spec/std/io/buffered_spec.cr +++ b/spec/std/io/buffered_spec.cr @@ -72,6 +72,15 @@ describe "IO::Buffered" do end end + it "can set buffer_size to the same value after first use" do + io = BufferedWrapper.new(IO::Memory.new("hello\r\nworld\n")) + io.buffer_size = 16_384 + io.gets + + io.buffer_size = 16_384 + io.buffer_size.should eq(16_384) + end + it "does gets" do io = BufferedWrapper.new(IO::Memory.new("hello\r\nworld\n")) io.gets.should eq("hello") diff --git a/src/io/buffered.cr b/src/io/buffered.cr index 0e69872a638f..8bd65210aef2 100644 --- a/src/io/buffered.cr +++ b/src/io/buffered.cr @@ -49,7 +49,7 @@ module IO::Buffered # Set the buffer size of both the read and write buffer # Cannot be changed after any of the buffers have been allocated def buffer_size=(value) - if @in_buffer || @out_buffer + if (@in_buffer || @out_buffer) && (buffer_size != value) raise ArgumentError.new("Cannot change buffer_size after buffers have been allocated") end @buffer_size = value From 95af602c46b5ed68999492c401e7f56a020038b4 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 5 Sep 2024 15:34:21 +0800 Subject: [PATCH 1332/1551] Emulate non-blocking `STDIN` console on Windows (#14947) --- src/crystal/system/file_descriptor.cr | 9 +++ src/crystal/system/win32/file_descriptor.cr | 64 ++++++++++++++++++++- src/crystal/system/win32/iocp.cr | 35 ++++++++--- src/crystal/system/win32/process.cr | 2 +- src/io/file_descriptor.cr | 8 +++ src/lib_c/x86_64-windows-msvc/c/ioapiset.cr | 8 +++ 6 files changed, 115 insertions(+), 11 deletions(-) diff --git a/src/crystal/system/file_descriptor.cr b/src/crystal/system/file_descriptor.cr index 0652ed56e52a..481e00982e25 100644 --- a/src/crystal/system/file_descriptor.cr +++ b/src/crystal/system/file_descriptor.cr @@ -22,6 +22,15 @@ module Crystal::System::FileDescriptor # Also used in `IO::FileDescriptor#finalize`. # def file_descriptor_close + # Returns `true` or `false` if this file descriptor pretends to block or not + # to block the caller thread regardless of the underlying internal file + # descriptor's implementation. Returns `nil` if nothing needs to be done, i.e. + # `#blocking` is identical to `#system_blocking?`. + # + # Currently used by console STDIN on Windows. + private def emulated_blocking? : Bool? + end + private def system_read(slice : Bytes) : Int32 event_loop.read(self, slice) end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 3c7823e62d3e..4fdc319a8b6c 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -3,6 +3,7 @@ require "c/consoleapi" require "c/consoleapi2" require "c/winnls" require "crystal/system/win32/iocp" +require "crystal/system/thread" module Crystal::System::FileDescriptor # Platform-specific type to represent a file descriptor handle to the operating @@ -76,13 +77,24 @@ module Crystal::System::FileDescriptor bytes_written end + def emulated_blocking? : Bool? + # reading from STDIN is done via a separate thread (see + # `ConsoleUtils.read_console` below) + handle = windows_handle + if LibC.GetConsoleMode(handle, out _) != 0 + if handle == LibC.GetStdHandle(LibC::STD_INPUT_HANDLE) + return false + end + end + end + # :nodoc: def system_blocking? @system_blocking end private def system_blocking=(blocking) - unless blocking == @system_blocking + unless blocking == self.blocking raise IO::Error.new("Cannot reconfigure `IO::FileDescriptor#blocking` after creation") end end @@ -339,7 +351,11 @@ module Crystal::System::FileDescriptor end end + # `blocking` must be set to `true` because the underlying handles never + # support overlapped I/O; instead, `#emulated_blocking?` should return + # `false` for `STDIN` as it uses a separate thread io = IO::FileDescriptor.new(handle.address, blocking: true) + # Set sync or flush_on_newline as described in STDOUT and STDERR docs. # See https://crystal-lang.org/api/toplevel.html#STDERR if console_handle @@ -465,11 +481,57 @@ private module ConsoleUtils end private def self.read_console(handle : LibC::HANDLE, slice : Slice(UInt16)) : Int32 + @@mtx.synchronize do + @@read_requests << ReadRequest.new( + handle: handle, + slice: slice, + iocp: Crystal::EventLoop.current.iocp, + completion_key: Crystal::IOCP::CompletionKey.new(:stdin_read, ::Fiber.current), + ) + @@read_cv.signal + end + + ::Fiber.suspend + + @@mtx.synchronize do + @@bytes_read.shift + end + end + + private def self.read_console_blocking(handle : LibC::HANDLE, slice : Slice(UInt16)) : Int32 if 0 == LibC.ReadConsoleW(handle, slice, slice.size, out units_read, nil) raise IO::Error.from_winerror("ReadConsoleW") end units_read.to_i32 end + + record ReadRequest, handle : LibC::HANDLE, slice : Slice(UInt16), iocp : LibC::HANDLE, completion_key : Crystal::IOCP::CompletionKey + + @@read_cv = ::Thread::ConditionVariable.new + @@read_requests = Deque(ReadRequest).new + @@bytes_read = Deque(Int32).new + @@mtx = ::Thread::Mutex.new + @@reader_thread = ::Thread.new { reader_loop } + + private def self.reader_loop + while true + request = @@mtx.synchronize do + loop do + if entry = @@read_requests.shift? + break entry + end + @@read_cv.wait(@@mtx) + end + end + + bytes = read_console_blocking(request.handle, request.slice) + + @@mtx.synchronize do + @@bytes_read << bytes + LibC.PostQueuedCompletionStatus(request.iocp, LibC::JOB_OBJECT_MSG_EXIT_PROCESS, request.completion_key.object_id, nil) + end + end + end end # Enable UTF-8 console I/O for the duration of program execution diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr index 6f5746954277..ba87ed123f22 100644 --- a/src/crystal/system/win32/iocp.cr +++ b/src/crystal/system/win32/iocp.cr @@ -6,7 +6,16 @@ require "crystal/system/thread_linked_list" module Crystal::IOCP # :nodoc: class CompletionKey + enum Tag + ProcessRun + StdinRead + end + property fiber : Fiber? + getter tag : Tag + + def initialize(@tag : Tag, @fiber : Fiber? = nil) + end end def self.wait_queued_completions(timeout, alertable = false, &) @@ -39,20 +48,19 @@ module Crystal::IOCP # at the moment only `::Process#wait` uses a non-nil completion key; all # I/O operations, including socket ones, do not set this field case completion_key = Pointer(Void).new(entry.lpCompletionKey).as(CompletionKey?) - when Nil + in Nil operation = OverlappedOperation.unbox(entry.lpOverlapped) operation.schedule { |fiber| yield fiber } - else - case entry.dwNumberOfBytesTransferred - when LibC::JOB_OBJECT_MSG_EXIT_PROCESS, LibC::JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS + in CompletionKey + if completion_key_valid?(completion_key, entry.dwNumberOfBytesTransferred) + # if `Process` exits before a call to `#wait`, this fiber will be + # reset already if fiber = completion_key.fiber - # this ensures the `::Process` doesn't keep an indirect reference to - # `::Thread.current`, as that leads to a finalization cycle + # this ensures existing references to `completion_key` do not keep + # an indirect reference to `::Thread.current`, as that leads to a + # finalization cycle completion_key.fiber = nil - yield fiber - else - # the `Process` exits before a call to `#wait`; do nothing end end end @@ -61,6 +69,15 @@ module Crystal::IOCP false end + private def self.completion_key_valid?(completion_key, number_of_bytes_transferred) + case completion_key.tag + in .process_run? + number_of_bytes_transferred.in?(LibC::JOB_OBJECT_MSG_EXIT_PROCESS, LibC::JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS) + in .stdin_read? + true + end + end + class OverlappedOperation enum State STARTED diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 05b2ea36584e..2c6d81720636 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -17,7 +17,7 @@ struct Crystal::System::Process @thread_id : LibC::DWORD @process_handle : LibC::HANDLE @job_object : LibC::HANDLE - @completion_key = IOCP::CompletionKey.new + @completion_key = IOCP::CompletionKey.new(:process_run) @@interrupt_handler : Proc(::Process::ExitReason, Nil)? @@interrupt_count = Crystal::AtomicSemaphore.new diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 8940a118041f..622229e43e00 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -66,7 +66,15 @@ class IO::FileDescriptor < IO Crystal::System::FileDescriptor.from_stdio(fd) end + # Returns whether I/O operations on this file descriptor block the current + # thread. If false, operations might opt to suspend the current fiber instead. + # + # This might be different from the internal file descriptor. For example, when + # `STDIN` is a terminal on Windows, this returns `false` since the underlying + # blocking reads are done on a completely separate thread. def blocking + emulated = emulated_blocking? + return emulated unless emulated.nil? system_blocking? end diff --git a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr index 1c94b66db4c8..f6d56ef5a0e6 100644 --- a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr +++ b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr @@ -21,6 +21,14 @@ lib LibC dwMilliseconds : DWORD, fAlertable : BOOL ) : BOOL + + fun PostQueuedCompletionStatus( + completionPort : HANDLE, + dwNumberOfBytesTransferred : DWORD, + dwCompletionKey : ULONG_PTR, + lpOverlapped : OVERLAPPED* + ) : BOOL + fun CancelIoEx( hFile : HANDLE, lpOverlapped : OVERLAPPED* From f6e2ab33f272167b68a64ac3ab2ca877fa714e2a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 5 Sep 2024 15:37:47 +0800 Subject: [PATCH 1333/1551] Deprecate `::sleep(Number)` (#14962) This is in line with other places in the standard library that favor `Time::Span` over number types, such as `IO` timeouts (#14368) and `Benchmark.ips` (#14805). --- samples/channel_select.cr | 2 +- samples/conway.cr | 4 ++-- samples/tcp_client.cr | 2 +- spec/std/benchmark_spec.cr | 4 ++-- spec/std/channel_spec.cr | 12 ++++++------ spec/std/http/client/client_spec.cr | 14 +++++++------- spec/std/http/server/server_spec.cr | 4 ++-- spec/std/http/spec_helper.cr | 2 +- spec/std/openssl/ssl/server_spec.cr | 2 +- spec/std/signal_spec.cr | 4 ++-- spec/support/channel.cr | 4 ++-- spec/support/retry.cr | 2 +- src/benchmark.cr | 6 +++--- src/concurrent.cr | 5 +++-- src/crystal/system/unix/file_descriptor.cr | 2 +- src/crystal/system/win32/file_descriptor.cr | 2 +- src/signal.cr | 6 +++--- 17 files changed, 39 insertions(+), 38 deletions(-) diff --git a/samples/channel_select.cr b/samples/channel_select.cr index 1ad24e1ff779..25ef96c7db16 100644 --- a/samples/channel_select.cr +++ b/samples/channel_select.cr @@ -2,7 +2,7 @@ def generator(n : T) forall T channel = Channel(T).new spawn do loop do - sleep n + sleep n.seconds channel.send n end end diff --git a/samples/conway.cr b/samples/conway.cr index b1d9d9089bb0..5178d48f9bd0 100644 --- a/samples/conway.cr +++ b/samples/conway.cr @@ -78,7 +78,7 @@ struct ConwayMap end end -PAUSE_MILLIS = 20 +PAUSE = 20.milliseconds DEFAULT_COUNT = 300 INITIAL_MAP = [ " 1 ", @@ -99,6 +99,6 @@ spawn { gets; exit } 1.upto(DEFAULT_COUNT) do |i| puts map puts "n = #{i}\tPress ENTER to exit" - sleep PAUSE_MILLIS * 0.001 + sleep PAUSE map.next end diff --git a/samples/tcp_client.cr b/samples/tcp_client.cr index 95392dc72601..f4f02d5bdf05 100644 --- a/samples/tcp_client.cr +++ b/samples/tcp_client.cr @@ -6,5 +6,5 @@ socket = TCPSocket.new "127.0.0.1", 9000 10.times do |i| socket.puts i puts "Server response: #{socket.gets}" - sleep 0.5 + sleep 0.5.seconds end diff --git a/spec/std/benchmark_spec.cr b/spec/std/benchmark_spec.cr index 2f3c1fb06fd5..8113f5f03a4c 100644 --- a/spec/std/benchmark_spec.cr +++ b/spec/std/benchmark_spec.cr @@ -13,8 +13,8 @@ describe Benchmark::IPS::Job do # test several things to avoid running a benchmark over and over again in # the specs j = Benchmark::IPS::Job.new(0.001, 0.001, interactive: false) - a = j.report("a") { sleep 0.001 } - b = j.report("b") { sleep 0.002 } + a = j.report("a") { sleep 1.milliseconds } + b = j.report("b") { sleep 2.milliseconds } j.execute diff --git a/spec/std/channel_spec.cr b/spec/std/channel_spec.cr index 9d121f9d9827..69161dd96e01 100644 --- a/spec/std/channel_spec.cr +++ b/spec/std/channel_spec.cr @@ -110,7 +110,7 @@ describe Channel do it "raises if channel is closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ sleep 0.2; ch.close }) do + spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do expect_raises Channel::ClosedError do Channel.select(ch.receive_select_action) end @@ -129,7 +129,7 @@ describe Channel do end } - spawn_and_wait(->{ sleep 0.2; ch.close }) do + spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do r = parallel p.call, p.call, p.call, p.call r.should eq({1, 1, 1, 1}) end @@ -178,7 +178,7 @@ describe Channel do it "returns nil channel is closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ sleep 0.2; ch.close }) do + spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do i, m = Channel.select(ch.receive_select_action?) m.should be_nil end @@ -191,7 +191,7 @@ describe Channel do Channel.select(ch.receive_select_action?) } - spawn_and_wait(->{ sleep 0.2; ch.close }) do + spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do r = parallel p.call, p.call, p.call, p.call r.should eq({ {0, nil}, {0, nil}, {0, nil}, {0, nil} }) end @@ -273,7 +273,7 @@ describe Channel do it "raises if channel is closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ sleep 0.2; ch.close }) do + spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do expect_raises Channel::ClosedError do Channel.select(ch.send_select_action("foo")) end @@ -292,7 +292,7 @@ describe Channel do end } - spawn_and_wait(->{ sleep 0.2; ch.close }) do + spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do r = parallel p.call, p.call, p.call, p.call r.should eq({1, 1, 1, 1}) end diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index 4c9da8db7ad7..451960a8c79f 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -6,7 +6,7 @@ require "http/server" require "http/log" require "log/spec" -private def test_server(host, port, read_time = 0, content_type = "text/plain", write_response = true, &) +private def test_server(host, port, read_time = 0.seconds, content_type = "text/plain", write_response = true, &) server = TCPServer.new(host, port) begin spawn do @@ -312,12 +312,12 @@ module HTTP end it "doesn't read the body if request was HEAD" do - resp_get = test_server("localhost", 0, 0) do |server| + resp_get = test_server("localhost", 0, 0.seconds) do |server| client = Client.new("localhost", server.local_address.port) break client.get("/") end - test_server("localhost", 0, 0) do |server| + test_server("localhost", 0, 0.seconds) do |server| client = Client.new("localhost", server.local_address.port) resp_head = client.head("/") resp_head.headers.should eq(resp_get.headers) @@ -338,7 +338,7 @@ module HTTP end it "tests read_timeout" do - test_server("localhost", 0, 0) do |server| + test_server("localhost", 0, 0.seconds) do |server| client = Client.new("localhost", server.local_address.port) client.read_timeout = 1.second client.get("/") @@ -348,7 +348,7 @@ module HTTP # it doesn't make sense to try to write because the client will already # timeout on read. Writing a response could lead on an exception in # the server if the socket is closed. - test_server("localhost", 0, 0.5, write_response: false) do |server| + test_server("localhost", 0, 0.5.seconds, write_response: false) do |server| client = Client.new("localhost", server.local_address.port) expect_raises(IO::TimeoutError, {% if flag?(:win32) %} "WSARecv timed out" {% else %} "Read timed out" {% end %}) do client.read_timeout = 0.001 @@ -362,7 +362,7 @@ module HTTP # it doesn't make sense to try to write because the client will already # timeout on read. Writing a response could lead on an exception in # the server if the socket is closed. - test_server("localhost", 0, 0, write_response: false) do |server| + test_server("localhost", 0, 0.seconds, write_response: false) do |server| client = Client.new("localhost", server.local_address.port) expect_raises(IO::TimeoutError, {% if flag?(:win32) %} "WSASend timed out" {% else %} "Write timed out" {% end %}) do client.write_timeout = 0.001 @@ -372,7 +372,7 @@ module HTTP end it "tests connect_timeout" do - test_server("localhost", 0, 0) do |server| + test_server("localhost", 0, 0.seconds) do |server| client = Client.new("localhost", server.local_address.port) client.connect_timeout = 0.5 client.get("/") diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index c8b39c9e7e42..5e1e5dab76f6 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -65,14 +65,14 @@ describe HTTP::Server do while !server.listening? Fiber.yield end - sleep 0.1 + sleep 0.1.seconds schedule_timeout ch TCPSocket.open(address.address, address.port) { } # wait before closing the server - sleep 0.1 + sleep 0.1.seconds server.close ch.receive.should eq SpecChannelStatus::End diff --git a/spec/std/http/spec_helper.cr b/spec/std/http/spec_helper.cr index 18ec9e0bab46..82b4f12d6774 100644 --- a/spec/std/http/spec_helper.cr +++ b/spec/std/http/spec_helper.cr @@ -49,7 +49,7 @@ def run_server(server, &) {% if flag?(:preview_mt) %} # avoids fiber synchronization issues in specs, like closing the server # before we properly listen, ... - sleep 0.001 + sleep 1.millisecond {% end %} yield server_done ensure diff --git a/spec/std/openssl/ssl/server_spec.cr b/spec/std/openssl/ssl/server_spec.cr index ff5e578a8ed0..2e0e413a618d 100644 --- a/spec/std/openssl/ssl/server_spec.cr +++ b/spec/std/openssl/ssl/server_spec.cr @@ -130,7 +130,7 @@ describe OpenSSL::SSL::Server do OpenSSL::SSL::Server.open tcp_server, server_context do |server| spawn do - sleep 1 + sleep 1.second OpenSSL::SSL::Socket::Client.open(TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port), client_context, hostname: "example.com") do |socket| end end diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr index cae1c5e83834..969e4dc3d742 100644 --- a/spec/std/signal_spec.cr +++ b/spec/std/signal_spec.cr @@ -27,7 +27,7 @@ pending_interpreted describe: "Signal" do Process.signal Signal::USR1, Process.pid 10.times do |i| break if ran - sleep 0.1 + sleep 0.1.seconds end ran.should be_true ensure @@ -52,7 +52,7 @@ pending_interpreted describe: "Signal" do end Process.signal Signal::USR1, Process.pid - sleep 0.1 + sleep 0.1.seconds ran_first.should be_true ran_second.should be_true ensure diff --git a/spec/support/channel.cr b/spec/support/channel.cr index 7ca8d0668797..5ec3511c89c8 100644 --- a/spec/support/channel.cr +++ b/spec/support/channel.cr @@ -10,9 +10,9 @@ def schedule_timeout(c : Channel(SpecChannelStatus)) # TODO: it's not clear why some interpreter specs # take more than 1 second in some cases. # See #12429. - sleep 5 + sleep 5.seconds {% else %} - sleep 1 + sleep 1.second {% end %} c.send(SpecChannelStatus::Timeout) end diff --git a/spec/support/retry.cr b/spec/support/retry.cr index 638804c4be81..76fca476db95 100644 --- a/spec/support/retry.cr +++ b/spec/support/retry.cr @@ -7,7 +7,7 @@ def retry(n = 5, &) if i == 0 Fiber.yield else - sleep 0.01 * (2**i) + sleep 10.milliseconds * (2**i) end else return diff --git a/src/benchmark.cr b/src/benchmark.cr index a0f4933ddf2a..14bc12ae069a 100644 --- a/src/benchmark.cr +++ b/src/benchmark.cr @@ -11,8 +11,8 @@ require "./benchmark/**" # require "benchmark" # # Benchmark.ips do |x| -# x.report("short sleep") { sleep 0.01 } -# x.report("shorter sleep") { sleep 0.001 } +# x.report("short sleep") { sleep 10.milliseconds } +# x.report("shorter sleep") { sleep 1.millisecond } # end # ``` # @@ -31,7 +31,7 @@ require "./benchmark/**" # require "benchmark" # # Benchmark.ips(warmup: 4, calculation: 10) do |x| -# x.report("sleep") { sleep 0.01 } +# x.report("sleep") { sleep 10.milliseconds } # end # ``` # diff --git a/src/concurrent.cr b/src/concurrent.cr index 6f3a58291a22..0f8805857720 100644 --- a/src/concurrent.cr +++ b/src/concurrent.cr @@ -7,6 +7,7 @@ require "crystal/tracing" # # While this fiber is waiting this time, other ready-to-execute # fibers might start their execution. +@[Deprecated("Use `::sleep(Time::Span)` instead")] def sleep(seconds : Number) : Nil if seconds < 0 raise ArgumentError.new "Sleep seconds must be positive" @@ -42,7 +43,7 @@ end # # spawn do # 6.times do -# sleep 1 +# sleep 1.second # puts 1 # end # ch.send(nil) @@ -50,7 +51,7 @@ end # # spawn do # 3.times do -# sleep 2 +# sleep 2.seconds # puts 2 # end # ch.send(nil) diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index fc8839ac9e83..56a9eee80dd5 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -158,7 +158,7 @@ module Crystal::System::FileDescriptor if retry until flock(op) - sleep 0.1 + sleep 0.1.seconds end else flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked", target: self) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 4fdc319a8b6c..d4831d9528cb 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -226,7 +226,7 @@ module Crystal::System::FileDescriptor handle = windows_handle if retry && system_blocking? until lock_file(handle, flags) - sleep 0.1 + sleep 0.1.seconds end else lock_file(handle, flags) || raise IO::Error.from_winerror("Error applying file lock: file is already locked", target: self) diff --git a/src/signal.cr b/src/signal.cr index e0f59a9f57d3..37999c76b9e1 100644 --- a/src/signal.cr +++ b/src/signal.cr @@ -8,17 +8,17 @@ require "crystal/system/signal" # # ``` # puts "Ctrl+C still has the OS default action (stops the program)" -# sleep 3 +# sleep 3.seconds # # Signal::INT.trap do # puts "Gotcha!" # end # puts "Ctrl+C will be caught from now on" -# sleep 3 +# sleep 3.seconds # # Signal::INT.reset # puts "Ctrl+C is back to the OS default action" -# sleep 3 +# sleep 3.seconds # ``` # # WARNING: An uncaught exception in a signal handler is a fatal error. From 256c555b92db30b88742b0f30144c08fd07b5ce3 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 5 Sep 2024 23:30:31 +0800 Subject: [PATCH 1334/1551] Implement `System::Group` on Windows (#14945) More examples of valid group IDs can be obtained using `whoami.exe /groups`. --- spec/std/system/group_spec.cr | 12 +++-- src/crystal/system/group.cr | 2 + src/crystal/system/win32/group.cr | 82 +++++++++++++++++++++++++++++++ src/crystal/system/win32/user.cr | 65 +++--------------------- src/crystal/system/windows.cr | 53 ++++++++++++++++++++ src/docs_main.cr | 4 +- 6 files changed, 153 insertions(+), 65 deletions(-) create mode 100644 src/crystal/system/win32/group.cr diff --git a/spec/std/system/group_spec.cr b/spec/std/system/group_spec.cr index 5c55611e4d28..ba511d03a05c 100644 --- a/spec/std/system/group_spec.cr +++ b/spec/std/system/group_spec.cr @@ -1,10 +1,14 @@ -{% skip_file if flag?(:win32) %} - require "spec" require "system/group" -GROUP_NAME = {{ `id -gn`.stringify.chomp }} -GROUP_ID = {{ `id -g`.stringify.chomp }} +{% if flag?(:win32) %} + GROUP_NAME = "BUILTIN\\Administrators" + GROUP_ID = "S-1-5-32-544" +{% else %} + GROUP_NAME = {{ `id -gn`.stringify.chomp }} + GROUP_ID = {{ `id -g`.stringify.chomp }} +{% end %} + INVALID_GROUP_NAME = "this_group_does_not_exist" INVALID_GROUP_ID = {% if flag?(:android) %}"8888"{% else %}"1234567"{% end %} diff --git a/src/crystal/system/group.cr b/src/crystal/system/group.cr index 8a542e2cc63c..6cb93739a900 100644 --- a/src/crystal/system/group.cr +++ b/src/crystal/system/group.cr @@ -12,6 +12,8 @@ end require "./wasi/group" {% elsif flag?(:unix) %} require "./unix/group" +{% elsif flag?(:win32) %} + require "./win32/group" {% else %} {% raise "No Crystal::System::Group implementation available" %} {% end %} diff --git a/src/crystal/system/win32/group.cr b/src/crystal/system/win32/group.cr new file mode 100644 index 000000000000..3b40774ac2d8 --- /dev/null +++ b/src/crystal/system/win32/group.cr @@ -0,0 +1,82 @@ +require "crystal/system/windows" + +# This file contains source code derived from the following: +# +# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/os/user/lookup_windows.go +# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/syscall/security_windows.go +# +# The following is their license: +# +# Copyright 2009 The Go Authors. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google LLC nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +module Crystal::System::Group + def initialize(@name : String, @id : String) + end + + def system_name : String + @name + end + + def system_id : String + @id + end + + def self.from_name?(groupname : String) : ::System::Group? + if found = Crystal::System.name_to_sid(groupname) + from_sid(found.sid) + end + end + + def self.from_id?(groupid : String) : ::System::Group? + if sid = Crystal::System.sid_from_s(groupid) + begin + from_sid(sid) + ensure + LibC.LocalFree(sid) + end + end + end + + private def self.from_sid(sid : LibC::SID*) : ::System::Group? + canonical = Crystal::System.sid_to_name(sid) || return + + # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/7b2aeb27-92fc-41f6-8437-deb65d950921#gt_0387e636-5654-4910-9519-1f8326cf5ec0 + # SidTypeAlias should also be treated as a group type next to SidTypeGroup + # and SidTypeWellKnownGroup: + # "alias object -> resource group: A group object..." + # + # Tests show that "Administrators" can be considered of type SidTypeAlias. + case canonical.type + when .sid_type_group?, .sid_type_well_known_group?, .sid_type_alias? + domain_and_group = canonical.domain.empty? ? canonical.name : "#{canonical.domain}\\#{canonical.name}" + gid = Crystal::System.sid_to_s(sid) + ::System::Group.new(domain_and_group, gid) + end + end +end diff --git a/src/crystal/system/win32/user.cr b/src/crystal/system/win32/user.cr index e5fcdbba10aa..4a06570c72b8 100644 --- a/src/crystal/system/win32/user.cr +++ b/src/crystal/system/win32/user.cr @@ -1,4 +1,4 @@ -require "c/sddl" +require "crystal/system/windows" require "c/lm" require "c/userenv" require "c/security" @@ -71,7 +71,7 @@ module Crystal::System::User end def self.from_username?(username : String) : ::System::User? - if found = name_to_sid(username) + if found = Crystal::System.name_to_sid(username) if found.type.sid_type_user? from_sid(found.sid) end @@ -79,7 +79,7 @@ module Crystal::System::User end def self.from_id?(id : String) : ::System::User? - if sid = sid_from_s(id) + if sid = Crystal::System.sid_from_s(id) begin from_sid(sid) ensure @@ -89,13 +89,13 @@ module Crystal::System::User end private def self.from_sid(sid : LibC::SID*) : ::System::User? - canonical = sid_to_name(sid) || return + canonical = Crystal::System.sid_to_name(sid) || return return unless canonical.type.sid_type_user? domain_and_user = "#{canonical.domain}\\#{canonical.name}" full_name = lookup_full_name(canonical.name, canonical.domain, domain_and_user) || return pgid = lookup_primary_group_id(canonical.name, canonical.domain) || return - uid = sid_to_s(sid) + uid = Crystal::System.sid_to_s(sid) home_dir = lookup_home_directory(uid, canonical.name) || return ::System::User.new(domain_and_user, uid, pgid, full_name, home_dir) @@ -136,10 +136,10 @@ module Crystal::System::User # https://support.microsoft.com/en-us/help/297951/how-to-use-the-primarygroupid-attribute-to-find-the-primary-group-for # The method follows this formula: domainRID + "-" + primaryGroupRID private def self.lookup_primary_group_id(name : String, domain : String) : String? - domain_sid = name_to_sid(domain) || return + domain_sid = Crystal::System.name_to_sid(domain) || return return unless domain_sid.type.sid_type_domain? - domain_sid_str = sid_to_s(domain_sid.sid) + domain_sid_str = Crystal::System.sid_to_s(domain_sid.sid) # If the user has joined a domain use the RID of the default primary group # called "Domain Users": @@ -210,43 +210,6 @@ module Crystal::System::User return "#{profile_dir}\\#{username}" if profile_dir end - private record SIDLookupResult, sid : LibC::SID*, domain : String, type : LibC::SID_NAME_USE - - private def self.name_to_sid(name : String) : SIDLookupResult? - utf16_name = Crystal::System.to_wstr(name) - - sid_size = LibC::DWORD.zero - domain_buf_size = LibC::DWORD.zero - LibC.LookupAccountNameW(nil, utf16_name, nil, pointerof(sid_size), nil, pointerof(domain_buf_size), out _) - - unless WinError.value.error_none_mapped? - sid = Pointer(UInt8).malloc(sid_size).as(LibC::SID*) - domain_buf = Slice(LibC::WCHAR).new(domain_buf_size) - if LibC.LookupAccountNameW(nil, utf16_name, sid, pointerof(sid_size), domain_buf, pointerof(domain_buf_size), out sid_type) != 0 - domain = String.from_utf16(domain_buf[..-2]) - SIDLookupResult.new(sid, domain, sid_type) - end - end - end - - private record NameLookupResult, name : String, domain : String, type : LibC::SID_NAME_USE - - private def self.sid_to_name(sid : LibC::SID*) : NameLookupResult? - name_buf_size = LibC::DWORD.zero - domain_buf_size = LibC::DWORD.zero - LibC.LookupAccountSidW(nil, sid, nil, pointerof(name_buf_size), nil, pointerof(domain_buf_size), out _) - - unless WinError.value.error_none_mapped? - name_buf = Slice(LibC::WCHAR).new(name_buf_size) - domain_buf = Slice(LibC::WCHAR).new(domain_buf_size) - if LibC.LookupAccountSidW(nil, sid, name_buf, pointerof(name_buf_size), domain_buf, pointerof(domain_buf_size), out sid_type) != 0 - name = String.from_utf16(name_buf[..-2]) - domain = String.from_utf16(domain_buf[..-2]) - NameLookupResult.new(name, domain, sid_type) - end - end - end - private def self.domain_joined? : Bool status = LibC.NetGetJoinInformation(nil, out domain, out type) if status != LibC::NERR_Success @@ -256,18 +219,4 @@ module Crystal::System::User LibC.NetApiBufferFree(domain) is_domain end - - private def self.sid_to_s(sid : LibC::SID*) : String - if LibC.ConvertSidToStringSidW(sid, out ptr) == 0 - raise RuntimeError.from_winerror("ConvertSidToStringSidW") - end - str, _ = String.from_utf16(ptr) - LibC.LocalFree(ptr) - str - end - - private def self.sid_from_s(str : String) : LibC::SID* - status = LibC.ConvertStringSidToSidW(Crystal::System.to_wstr(str), out sid) - status != 0 ? sid : Pointer(LibC::SID).null - end end diff --git a/src/crystal/system/windows.cr b/src/crystal/system/windows.cr index b303d4d61f6d..90b38396cf8f 100644 --- a/src/crystal/system/windows.cr +++ b/src/crystal/system/windows.cr @@ -1,3 +1,5 @@ +require "c/sddl" + # :nodoc: module Crystal::System def self.retry_wstr_buffer(&) @@ -13,4 +15,55 @@ module Crystal::System def self.to_wstr(str : String, name : String? = nil) : LibC::LPWSTR str.check_no_null_byte(name).to_utf16.to_unsafe end + + def self.sid_to_s(sid : LibC::SID*) : String + if LibC.ConvertSidToStringSidW(sid, out ptr) == 0 + raise RuntimeError.from_winerror("ConvertSidToStringSidW") + end + str, _ = String.from_utf16(ptr) + LibC.LocalFree(ptr) + str + end + + def self.sid_from_s(str : String) : LibC::SID* + status = LibC.ConvertStringSidToSidW(to_wstr(str), out sid) + status != 0 ? sid : Pointer(LibC::SID).null + end + + record SIDLookupResult, sid : LibC::SID*, domain : String, type : LibC::SID_NAME_USE + + def self.name_to_sid(name : String) : SIDLookupResult? + utf16_name = to_wstr(name) + + sid_size = LibC::DWORD.zero + domain_buf_size = LibC::DWORD.zero + LibC.LookupAccountNameW(nil, utf16_name, nil, pointerof(sid_size), nil, pointerof(domain_buf_size), out _) + + unless WinError.value.error_none_mapped? + sid = Pointer(UInt8).malloc(sid_size).as(LibC::SID*) + domain_buf = Slice(LibC::WCHAR).new(domain_buf_size) + if LibC.LookupAccountNameW(nil, utf16_name, sid, pointerof(sid_size), domain_buf, pointerof(domain_buf_size), out sid_type) != 0 + domain = String.from_utf16(domain_buf[..-2]) + SIDLookupResult.new(sid, domain, sid_type) + end + end + end + + record NameLookupResult, name : String, domain : String, type : LibC::SID_NAME_USE + + def self.sid_to_name(sid : LibC::SID*) : NameLookupResult? + name_buf_size = LibC::DWORD.zero + domain_buf_size = LibC::DWORD.zero + LibC.LookupAccountSidW(nil, sid, nil, pointerof(name_buf_size), nil, pointerof(domain_buf_size), out _) + + unless WinError.value.error_none_mapped? + name_buf = Slice(LibC::WCHAR).new(name_buf_size) + domain_buf = Slice(LibC::WCHAR).new(domain_buf_size) + if LibC.LookupAccountSidW(nil, sid, name_buf, pointerof(name_buf_size), domain_buf, pointerof(domain_buf_size), out sid_type) != 0 + name = String.from_utf16(name_buf[..-2]) + domain = String.from_utf16(domain_buf[..-2]) + NameLookupResult.new(name, domain, sid_type) + end + end + end end diff --git a/src/docs_main.cr b/src/docs_main.cr index e670d6d3fa83..ab3ee2affdbc 100644 --- a/src/docs_main.cr +++ b/src/docs_main.cr @@ -56,8 +56,6 @@ require "./uri/params/serializable" require "./uuid" require "./uuid/json" require "./syscall" -{% unless flag?(:win32) %} - require "./system/*" -{% end %} +require "./system/*" require "./wait_group" require "./docs_pseudo_methods" From 05c5eaa17ce17c60917b30cebaca022d59721e2e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 5 Sep 2024 23:31:33 +0800 Subject: [PATCH 1335/1551] Implement `Reference.pre_initialize` in the interpreter (#14968) --- spec/primitives/reference_spec.cr | 12 ++++++---- .../crystal/interpreter/instructions.cr | 10 ++++++++ .../crystal/interpreter/primitives.cr | 24 +++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/spec/primitives/reference_spec.cr b/spec/primitives/reference_spec.cr index 13bb024f1ba9..497b49155b5a 100644 --- a/spec/primitives/reference_spec.cr +++ b/spec/primitives/reference_spec.cr @@ -37,8 +37,7 @@ describe "Primitives: reference" do end end - # TODO: implement in the interpreter - pending_interpreted describe: ".pre_initialize" do + describe ".pre_initialize" do it "doesn't fail on complex ivar initializer if value is discarded (#14325)" do bar_buffer = GC.malloc(instance_sizeof(Outer)) Outer.pre_initialize(bar_buffer) @@ -55,7 +54,12 @@ describe "Primitives: reference" do it "sets type ID" do foo_buffer = GC.malloc(instance_sizeof(Foo)) base = Foo.pre_initialize(foo_buffer).as(Base) - base.crystal_type_id.should eq(Foo.crystal_instance_type_id) + base.should be_a(Foo) + base.as(typeof(Foo.crystal_instance_type_id)*).value.should eq(Foo.crystal_instance_type_id) + {% unless flag?(:interpreted) %} + # FIXME: `Object#crystal_type_id` is incorrect for virtual types in the interpreter (#14967) + base.crystal_type_id.should eq(Foo.crystal_instance_type_id) + {% end %} end it "runs inline instance initializers" do @@ -89,7 +93,7 @@ describe "Primitives: reference" do end end - pending_interpreted describe: ".unsafe_construct" do + describe ".unsafe_construct" do it "constructs an object in-place" do foo_buffer = GC.malloc(instance_sizeof(Foo)) foo = Foo.unsafe_construct(foo_buffer, 123_i64) diff --git a/src/compiler/crystal/interpreter/instructions.cr b/src/compiler/crystal/interpreter/instructions.cr index 8fae94f5ee62..6a38afd888d3 100644 --- a/src/compiler/crystal/interpreter/instructions.cr +++ b/src/compiler/crystal/interpreter/instructions.cr @@ -1276,6 +1276,16 @@ require "./repl" ptr end, }, + reset_class: { + operands: [size : Int32, type_id : Int32], + pop_values: [pointer : Pointer(UInt8)], + push: true, + code: begin + pointer.clear(size) + pointer.as(Int32*).value = type_id + pointer + end, + }, put_metaclass: { operands: [size : Int32, union_type : Bool], push: true, diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr index 7ad508f8d0fc..ca436947370e 100644 --- a/src/compiler/crystal/interpreter/primitives.cr +++ b/src/compiler/crystal/interpreter/primitives.cr @@ -178,6 +178,30 @@ class Crystal::Repl::Compiler pop(sizeof(Pointer(Void)), node: nil) end end + when "pre_initialize" + type = + if obj + discard_value(obj) + obj.type.instance_type + else + scope.instance_type + end + + accept_call_members(node) + + dup sizeof(Pointer(Void)), node: nil + reset_class(aligned_instance_sizeof_type(type), type_id(type), node: node) + + initializer_compiled_defs = @context.type_instance_var_initializers(type) + unless initializer_compiled_defs.empty? + initializer_compiled_defs.size.times do + dup sizeof(Pointer(Void)), node: nil + end + + initializer_compiled_defs.each do |compiled_def| + call compiled_def, node: nil + end + end when "tuple_indexer_known_index" unless @wants_value accept_call_members(node) From 777643886c3e7e549ce295e920b95d8d426e544c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 6 Sep 2024 06:36:14 +0800 Subject: [PATCH 1336/1551] Add `Crystal::System::Addrinfo` (#14957) Moves the platform-specific code into a separate module, so that implementations other than `LibC.getaddrinfo` can be added without cluttering the same file (e.g. Win32's `GetAddrInfoExW` from #13619). --- src/crystal/system/addrinfo.cr | 36 ++++++++++ src/crystal/system/unix/addrinfo.cr | 71 +++++++++++++++++++ src/crystal/system/wasi/addrinfo.cr | 27 +++++++ src/crystal/system/win32/addrinfo.cr | 61 ++++++++++++++++ src/socket/addrinfo.cr | 102 ++++----------------------- 5 files changed, 209 insertions(+), 88 deletions(-) create mode 100644 src/crystal/system/addrinfo.cr create mode 100644 src/crystal/system/unix/addrinfo.cr create mode 100644 src/crystal/system/wasi/addrinfo.cr create mode 100644 src/crystal/system/win32/addrinfo.cr diff --git a/src/crystal/system/addrinfo.cr b/src/crystal/system/addrinfo.cr new file mode 100644 index 000000000000..23513e6f763e --- /dev/null +++ b/src/crystal/system/addrinfo.cr @@ -0,0 +1,36 @@ +module Crystal::System::Addrinfo + # alias Handle + + # protected def initialize(addrinfo : Handle) + + # def system_ip_address : ::Socket::IPAddress + + # def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle + + # def self.next_addrinfo(addrinfo : Handle) : Handle + + # def self.free_addrinfo(addrinfo : Handle) + + def self.getaddrinfo(domain, service, family, type, protocol, timeout, & : ::Socket::Addrinfo ->) + addrinfo = root = getaddrinfo(domain, service, family, type, protocol, timeout) + + begin + while addrinfo + yield ::Socket::Addrinfo.new(addrinfo) + addrinfo = next_addrinfo(addrinfo) + end + ensure + free_addrinfo(root) + end + end +end + +{% if flag?(:wasi) %} + require "./wasi/addrinfo" +{% elsif flag?(:unix) %} + require "./unix/addrinfo" +{% elsif flag?(:win32) %} + require "./win32/addrinfo" +{% else %} + {% raise "No Crystal::System::Addrinfo implementation available" %} +{% end %} diff --git a/src/crystal/system/unix/addrinfo.cr b/src/crystal/system/unix/addrinfo.cr new file mode 100644 index 000000000000..7f1e51558397 --- /dev/null +++ b/src/crystal/system/unix/addrinfo.cr @@ -0,0 +1,71 @@ +module Crystal::System::Addrinfo + alias Handle = LibC::Addrinfo* + + @addr : LibC::SockaddrIn6 + + protected def initialize(addrinfo : Handle) + @family = ::Socket::Family.from_value(addrinfo.value.ai_family) + @type = ::Socket::Type.from_value(addrinfo.value.ai_socktype) + @protocol = ::Socket::Protocol.from_value(addrinfo.value.ai_protocol) + @size = addrinfo.value.ai_addrlen.to_i + + @addr = uninitialized LibC::SockaddrIn6 + + case @family + when ::Socket::Family::INET6 + addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1) + when ::Socket::Family::INET + addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1) + else + # TODO: (asterite) UNSPEC and UNIX unsupported? + end + end + + def system_ip_address : ::Socket::IPAddress + ::Socket::IPAddress.from(to_unsafe, size) + end + + def to_unsafe + pointerof(@addr).as(LibC::Sockaddr*) + end + + def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle + hints = LibC::Addrinfo.new + hints.ai_family = (family || ::Socket::Family::UNSPEC).to_i32 + hints.ai_socktype = type + hints.ai_protocol = protocol + hints.ai_flags = 0 + + if service.is_a?(Int) + hints.ai_flags |= LibC::AI_NUMERICSERV + end + + # On OS X < 10.12, the libsystem implementation of getaddrinfo segfaults + # if AI_NUMERICSERV is set, and servname is NULL or 0. + {% if flag?(:darwin) %} + if service.in?(0, nil) && (hints.ai_flags & LibC::AI_NUMERICSERV) + hints.ai_flags |= LibC::AI_NUMERICSERV + service = "00" + end + {% end %} + + ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr) + unless ret.zero? + if ret == LibC::EAI_SYSTEM + raise ::Socket::Addrinfo::Error.from_os_error nil, Errno.value, domain: domain + end + + error = Errno.new(ret) + raise ::Socket::Addrinfo::Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service) + end + ptr + end + + def self.next_addrinfo(addrinfo : Handle) : Handle + addrinfo.value.ai_next + end + + def self.free_addrinfo(addrinfo : Handle) + LibC.freeaddrinfo(addrinfo) + end +end diff --git a/src/crystal/system/wasi/addrinfo.cr b/src/crystal/system/wasi/addrinfo.cr new file mode 100644 index 000000000000..29ba8e0b3cfc --- /dev/null +++ b/src/crystal/system/wasi/addrinfo.cr @@ -0,0 +1,27 @@ +module Crystal::System::Addrinfo + alias Handle = NoReturn + + protected def initialize(addrinfo : Handle) + raise NotImplementedError.new("Crystal::System::Addrinfo#initialize") + end + + def system_ip_address : ::Socket::IPAddress + raise NotImplementedError.new("Crystal::System::Addrinfo#system_ip_address") + end + + def to_unsafe + raise NotImplementedError.new("Crystal::System::Addrinfo#to_unsafe") + end + + def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle + raise NotImplementedError.new("Crystal::System::Addrinfo.getaddrinfo") + end + + def self.next_addrinfo(addrinfo : Handle) : Handle + raise NotImplementedError.new("Crystal::System::Addrinfo.next_addrinfo") + end + + def self.free_addrinfo(addrinfo : Handle) + raise NotImplementedError.new("Crystal::System::Addrinfo.free_addrinfo") + end +end diff --git a/src/crystal/system/win32/addrinfo.cr b/src/crystal/system/win32/addrinfo.cr new file mode 100644 index 000000000000..b033d61f16e7 --- /dev/null +++ b/src/crystal/system/win32/addrinfo.cr @@ -0,0 +1,61 @@ +module Crystal::System::Addrinfo + alias Handle = LibC::Addrinfo* + + @addr : LibC::SockaddrIn6 + + protected def initialize(addrinfo : Handle) + @family = ::Socket::Family.from_value(addrinfo.value.ai_family) + @type = ::Socket::Type.from_value(addrinfo.value.ai_socktype) + @protocol = ::Socket::Protocol.from_value(addrinfo.value.ai_protocol) + @size = addrinfo.value.ai_addrlen.to_i + + @addr = uninitialized LibC::SockaddrIn6 + + case @family + when ::Socket::Family::INET6 + addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1) + when ::Socket::Family::INET + addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1) + else + # TODO: (asterite) UNSPEC and UNIX unsupported? + end + end + + def system_ip_address : ::Socket::IPAddress + ::Socket::IPAddress.from(to_unsafe, size) + end + + def to_unsafe + pointerof(@addr).as(LibC::Sockaddr*) + end + + def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle + hints = LibC::Addrinfo.new + hints.ai_family = (family || ::Socket::Family::UNSPEC).to_i32 + hints.ai_socktype = type + hints.ai_protocol = protocol + hints.ai_flags = 0 + + if service.is_a?(Int) + hints.ai_flags |= LibC::AI_NUMERICSERV + if service < 0 + raise ::Socket::Addrinfo::Error.from_os_error(nil, WinError::WSATYPE_NOT_FOUND, domain: domain, type: type, protocol: protocol, service: service) + end + end + + ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr) + unless ret.zero? + error = WinError.new(ret.to_u32!) + raise ::Socket::Addrinfo::Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service) + end + ptr + end + + def self.next_addrinfo(addrinfo : Handle) : Handle + addrinfo.value.ai_next + end + + def self.free_addrinfo(addrinfo : Handle) + LibC.freeaddrinfo(addrinfo) + end +end diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr index c7a8ada00d86..cdf55c912601 100644 --- a/src/socket/addrinfo.cr +++ b/src/socket/addrinfo.cr @@ -1,16 +1,17 @@ require "uri/punycode" require "./address" +require "crystal/system/addrinfo" class Socket # Domain name resolver. struct Addrinfo + include Crystal::System::Addrinfo + getter family : Family getter type : Type getter protocol : Protocol getter size : Int32 - @addr : LibC::SockaddrIn6 - # Resolves a domain that best matches the given options. # # - *domain* may be an IP address or a domain name. @@ -126,66 +127,15 @@ class Socket end private def self.getaddrinfo(domain, service, family, type, protocol, timeout, &) - {% if flag?(:wasm32) %} - raise NotImplementedError.new "Socket::Addrinfo.getaddrinfo" - {% else %} - # RFC 3986 says: - # > When a non-ASCII registered name represents an internationalized domain name - # > intended for resolution via the DNS, the name must be transformed to the IDNA - # > encoding [RFC3490] prior to name lookup. - domain = URI::Punycode.to_ascii domain - - hints = LibC::Addrinfo.new - hints.ai_family = (family || Family::UNSPEC).to_i32 - hints.ai_socktype = type - hints.ai_protocol = protocol - hints.ai_flags = 0 - - if service.is_a?(Int) - hints.ai_flags |= LibC::AI_NUMERICSERV - end - - # On OS X < 10.12, the libsystem implementation of getaddrinfo segfaults - # if AI_NUMERICSERV is set, and servname is NULL or 0. - {% if flag?(:darwin) %} - if service.in?(0, nil) && (hints.ai_flags & LibC::AI_NUMERICSERV) - hints.ai_flags |= LibC::AI_NUMERICSERV - service = "00" - end - {% end %} - {% if flag?(:win32) %} - if service.is_a?(Int) && service < 0 - raise Error.from_os_error(nil, WinError::WSATYPE_NOT_FOUND, domain: domain, type: type, protocol: protocol, service: service) - end - {% end %} - - ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr) - unless ret.zero? - {% if flag?(:unix) %} - # EAI_SYSTEM is not defined on win32 - if ret == LibC::EAI_SYSTEM - raise Error.from_os_error nil, Errno.value, domain: domain - end - {% end %} - - error = {% if flag?(:win32) %} - WinError.new(ret.to_u32!) - {% else %} - Errno.new(ret) - {% end %} - raise Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service) - end - - addrinfo = ptr - begin - while addrinfo - yield new(addrinfo) - addrinfo = addrinfo.value.ai_next - end - ensure - LibC.freeaddrinfo(ptr) - end - {% end %} + # RFC 3986 says: + # > When a non-ASCII registered name represents an internationalized domain name + # > intended for resolution via the DNS, the name must be transformed to the IDNA + # > encoding [RFC3490] prior to name lookup. + domain = URI::Punycode.to_ascii domain + + Crystal::System::Addrinfo.getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo| + yield addrinfo + end end # Resolves *domain* for the TCP protocol and returns an `Array` of possible @@ -226,29 +176,9 @@ class Socket resolve(domain, service, family, Type::DGRAM, Protocol::UDP) { |addrinfo| yield addrinfo } end - protected def initialize(addrinfo : LibC::Addrinfo*) - @family = Family.from_value(addrinfo.value.ai_family) - @type = Type.from_value(addrinfo.value.ai_socktype) - @protocol = Protocol.from_value(addrinfo.value.ai_protocol) - @size = addrinfo.value.ai_addrlen.to_i - - @addr = uninitialized LibC::SockaddrIn6 - - case @family - when Family::INET6 - addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1) - when Family::INET - addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1) - else - # TODO: (asterite) UNSPEC and UNIX unsupported? - end - end - - @ip_address : IPAddress? - # Returns an `IPAddress` matching this addrinfo. - def ip_address : Socket::IPAddress - @ip_address ||= IPAddress.from(to_unsafe, size) + getter(ip_address : Socket::IPAddress) do + system_ip_address end def inspect(io : IO) @@ -259,9 +189,5 @@ class Socket io << protocol io << ")" end - - def to_unsafe - pointerof(@addr).as(LibC::Sockaddr*) - end end end From db2ecd781422d5b4cd615d5b10874072c1310b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 6 Sep 2024 00:36:42 +0200 Subject: [PATCH 1337/1551] Fix `Range#size` return type to `Int32` (#14588) The `super` implementation `Enumerable#size` has the same type restriction already. --- src/range.cr | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/range.cr b/src/range.cr index 39d8119dff6e..e8ee24b190cb 100644 --- a/src/range.cr +++ b/src/range.cr @@ -480,7 +480,10 @@ struct Range(B, E) # (3..8).size # => 6 # (3...8).size # => 5 # ``` - def size + # + # Raises `OverflowError` if the difference is bigger than `Int32`. + # Raises `ArgumentError` if either `begin` or `end` are `nil`. + def size : Int32 b = self.begin e = self.end @@ -488,7 +491,7 @@ struct Range(B, E) if b.is_a?(Int) && e.is_a?(Int) e -= 1 if @exclusive n = e - b + 1 - n < 0 ? 0 : n + n < 0 ? 0 : n.to_i32 else if b.nil? || e.nil? raise ArgumentError.new("Can't calculate size of an open range") From 214d39ad112374910640c87e21695c9e8eb9d213 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 6 Sep 2024 14:15:19 +0800 Subject: [PATCH 1338/1551] Fix CRT static-dynamic linking conflict in specs with C sources (#14970) This fixes the `LINK : warning LNK4098: defaultlib 'LIBCMT' conflicts with use of other libs; use /NODEFAULTLIB:library` message that shows up on Windows CI while running compiler specs. --- spec/support/tempfile.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/tempfile.cr b/spec/support/tempfile.cr index a77070d90e40..ef4468040955 100644 --- a/spec/support/tempfile.cr +++ b/spec/support/tempfile.cr @@ -67,7 +67,7 @@ def with_temp_c_object_file(c_code, *, filename = "temp_c", file = __FILE__, &) end end - `#{cl} /nologo /c #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_filename}")}`.should be_truthy + `#{cl} /nologo /c /MD #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_filename}")}`.should be_truthy {% else %} `#{ENV["CC"]? || "cc"} #{Process.quote(c_filename)} -c -o #{Process.quote(o_filename)}`.should be_truthy {% end %} From a310dee1bbf30839964e798d7cd5653c5149ba3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 6 Sep 2024 08:19:38 +0200 Subject: [PATCH 1339/1551] Fix use global paths in macro bodies (#14965) Macros inject code into other scopes. Paths are resolved in the expanded scope and there can be namespace conflicts. This fixes non-global paths in macro bodies that expand into uncontrolled scopes where namespaces could clash. This is a fixup for #14282 (released in 1.12.0). --- src/crystal/pointer_linked_list.cr | 4 ++-- src/ecr/macros.cr | 2 +- src/intrinsics.cr | 34 +++++++++++++++--------------- src/json/serialization.cr | 6 +++--- src/number.cr | 8 +++---- src/object.cr | 12 +++++------ src/slice.cr | 6 +++--- src/spec/dsl.cr | 4 ++-- src/spec/helpers/iterate.cr | 8 +++---- src/static_array.cr | 2 +- src/syscall/aarch64-linux.cr | 2 +- src/syscall/arm-linux.cr | 2 +- src/syscall/i386-linux.cr | 2 +- src/syscall/x86_64-linux.cr | 2 +- src/uri/params/serializable.cr | 14 ++++++------ src/yaml/serialization.cr | 14 ++++++------ 16 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/crystal/pointer_linked_list.cr b/src/crystal/pointer_linked_list.cr index 03109979d662..cde9b0b79ddc 100644 --- a/src/crystal/pointer_linked_list.cr +++ b/src/crystal/pointer_linked_list.cr @@ -7,8 +7,8 @@ struct Crystal::PointerLinkedList(T) module Node macro included - property previous : Pointer(self) = Pointer(self).null - property next : Pointer(self) = Pointer(self).null + property previous : ::Pointer(self) = ::Pointer(self).null + property next : ::Pointer(self) = ::Pointer(self).null end end diff --git a/src/ecr/macros.cr b/src/ecr/macros.cr index 92c02cc4284a..5e051232271b 100644 --- a/src/ecr/macros.cr +++ b/src/ecr/macros.cr @@ -34,7 +34,7 @@ module ECR # ``` macro def_to_s(filename) def to_s(__io__ : IO) : Nil - ECR.embed {{filename}}, "__io__" + ::ECR.embed {{filename}}, "__io__" end end diff --git a/src/intrinsics.cr b/src/intrinsics.cr index c5ae837d8931..7cdc462ce543 100644 --- a/src/intrinsics.cr +++ b/src/intrinsics.cr @@ -179,7 +179,7 @@ end module Intrinsics macro debugtrap - LibIntrinsics.debugtrap + ::LibIntrinsics.debugtrap end def self.pause @@ -191,15 +191,15 @@ module Intrinsics end macro memcpy(dest, src, len, is_volatile) - LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, {{is_volatile}}) + ::LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, {{is_volatile}}) end macro memmove(dest, src, len, is_volatile) - LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, {{is_volatile}}) + ::LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, {{is_volatile}}) end macro memset(dest, val, len, is_volatile) - LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, {{is_volatile}}) + ::LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, {{is_volatile}}) end def self.read_cycle_counter @@ -263,43 +263,43 @@ module Intrinsics end macro countleading8(src, zero_is_undef) - LibIntrinsics.countleading8({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.countleading8({{src}}, {{zero_is_undef}}) end macro countleading16(src, zero_is_undef) - LibIntrinsics.countleading16({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.countleading16({{src}}, {{zero_is_undef}}) end macro countleading32(src, zero_is_undef) - LibIntrinsics.countleading32({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.countleading32({{src}}, {{zero_is_undef}}) end macro countleading64(src, zero_is_undef) - LibIntrinsics.countleading64({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.countleading64({{src}}, {{zero_is_undef}}) end macro countleading128(src, zero_is_undef) - LibIntrinsics.countleading128({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.countleading128({{src}}, {{zero_is_undef}}) end macro counttrailing8(src, zero_is_undef) - LibIntrinsics.counttrailing8({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.counttrailing8({{src}}, {{zero_is_undef}}) end macro counttrailing16(src, zero_is_undef) - LibIntrinsics.counttrailing16({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.counttrailing16({{src}}, {{zero_is_undef}}) end macro counttrailing32(src, zero_is_undef) - LibIntrinsics.counttrailing32({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.counttrailing32({{src}}, {{zero_is_undef}}) end macro counttrailing64(src, zero_is_undef) - LibIntrinsics.counttrailing64({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.counttrailing64({{src}}, {{zero_is_undef}}) end macro counttrailing128(src, zero_is_undef) - LibIntrinsics.counttrailing128({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.counttrailing128({{src}}, {{zero_is_undef}}) end def self.fshl8(a, b, count) : UInt8 @@ -343,14 +343,14 @@ module Intrinsics end macro va_start(ap) - LibIntrinsics.va_start({{ap}}) + ::LibIntrinsics.va_start({{ap}}) end macro va_end(ap) - LibIntrinsics.va_end({{ap}}) + ::LibIntrinsics.va_end({{ap}}) end end macro debugger - Intrinsics.debugtrap + ::Intrinsics.debugtrap end diff --git a/src/json/serialization.cr b/src/json/serialization.cr index b1eb86d15082..15d948f02f40 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -164,7 +164,7 @@ module JSON private def self.new_from_json_pull_parser(pull : ::JSON::PullParser) instance = allocate instance.initialize(__pull_for_json_serializable: pull) - GC.add_finalizer(instance) if instance.responds_to?(:finalize) + ::GC.add_finalizer(instance) if instance.responds_to?(:finalize) instance end @@ -422,8 +422,8 @@ module JSON # Try to find the discriminator while also getting the raw # string value of the parsed JSON, so then we can pass it # to the final type. - json = String.build do |io| - JSON.build(io) do |builder| + json = ::String.build do |io| + ::JSON.build(io) do |builder| builder.start_object pull.read_object do |key| if key == {{field.id.stringify}} diff --git a/src/number.cr b/src/number.cr index f7c82aa4cded..9d955c065df3 100644 --- a/src/number.cr +++ b/src/number.cr @@ -59,7 +59,7 @@ struct Number # :nodoc: macro expand_div(rhs_types, result_type) {% for rhs in rhs_types %} - @[AlwaysInline] + @[::AlwaysInline] def /(other : {{rhs}}) : {{result_type}} {{result_type}}.new(self) / {{result_type}}.new(other) end @@ -84,7 +84,7 @@ struct Number # [1, 2, 3, 4] of Int64 # : Array(Int64) # ``` macro [](*nums) - Array({{@type}}).build({{nums.size}}) do |%buffer| + ::Array({{@type}}).build({{nums.size}}) do |%buffer| {% for num, i in nums %} %buffer[{{i}}] = {{@type}}.new({{num}}) {% end %} @@ -113,7 +113,7 @@ struct Number # Slice[1_i64, 2_i64, 3_i64, 4_i64] # : Slice(Int64) # ``` macro slice(*nums, read_only = false) - %slice = Slice({{@type}}).new({{nums.size}}, read_only: {{read_only}}) + %slice = ::Slice({{@type}}).new({{nums.size}}, read_only: {{read_only}}) {% for num, i in nums %} %slice.to_unsafe[{{i}}] = {{@type}}.new!({{num}}) {% end %} @@ -139,7 +139,7 @@ struct Number # StaticArray[1_i64, 2_i64, 3_i64, 4_i64] # : StaticArray(Int64) # ``` macro static_array(*nums) - %array = uninitialized StaticArray({{@type}}, {{nums.size}}) + %array = uninitialized ::StaticArray({{@type}}, {{nums.size}}) {% for num, i in nums %} %array.to_unsafe[{{i}}] = {{@type}}.new!({{num}}) {% end %} diff --git a/src/object.cr b/src/object.cr index ba818ac2979e..800736687788 100644 --- a/src/object.cr +++ b/src/object.cr @@ -562,7 +562,7 @@ class Object def {{method_prefix}}\{{name.var.id}} : \{{name.type}} if (value = {{var_prefix}}\{{name.var.id}}).nil? - ::raise NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.var.id}} cannot be nil") + ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.var.id}} cannot be nil") else value end @@ -574,7 +574,7 @@ class Object def {{method_prefix}}\{{name.id}} if (value = {{var_prefix}}\{{name.id}}).nil? - ::raise NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.id}} cannot be nil") + ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.id}} cannot be nil") else value end @@ -1293,7 +1293,7 @@ class Object # wrapper.capitalize # => "Hello" # ``` macro delegate(*methods, to object) - {% if compare_versions(Crystal::VERSION, "1.12.0-dev") >= 0 %} + {% if compare_versions(::Crystal::VERSION, "1.12.0-dev") >= 0 %} {% eq_operators = %w(<= >= == != []= ===) %} {% for method in methods %} {% if method.id.ends_with?('=') && !eq_operators.includes?(method.id.stringify) %} @@ -1427,18 +1427,18 @@ class Object macro def_clone # Returns a copy of `self` with all instance variables cloned. def clone - \{% if @type < Reference && !@type.instance_vars.map(&.type).all? { |t| t == ::Bool || t == ::Char || t == ::Symbol || t == ::String || t < ::Number::Primitive } %} + \{% if @type < ::Reference && !@type.instance_vars.map(&.type).all? { |t| t == ::Bool || t == ::Char || t == ::Symbol || t == ::String || t < ::Number::Primitive } %} exec_recursive_clone do |hash| clone = \{{@type}}.allocate hash[object_id] = clone.object_id clone.initialize_copy(self) - GC.add_finalizer(clone) if clone.responds_to?(:finalize) + ::GC.add_finalizer(clone) if clone.responds_to?(:finalize) clone end \{% else %} clone = \{{@type}}.allocate clone.initialize_copy(self) - GC.add_finalizer(clone) if clone.responds_to?(:finalize) + ::GC.add_finalizer(clone) if clone.responds_to?(:finalize) clone \{% end %} end diff --git a/src/slice.cr b/src/slice.cr index c87816f315d9..ace008e53e05 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -34,14 +34,14 @@ struct Slice(T) macro [](*args, read_only = false) # TODO: there should be a better way to check this, probably # asking if @type was instantiated or if T is defined - {% if @type.name != "Slice(T)" && T < Number %} + {% if @type.name != "Slice(T)" && T < ::Number %} {{T}}.slice({{args.splat(", ")}}read_only: {{read_only}}) {% else %} - %ptr = Pointer(typeof({{args.splat}})).malloc({{args.size}}) + %ptr = ::Pointer(typeof({{args.splat}})).malloc({{args.size}}) {% for arg, i in args %} %ptr[{{i}}] = {{arg}} {% end %} - Slice.new(%ptr, {{args.size}}, read_only: {{read_only}}) + ::Slice.new(%ptr, {{args.size}}, read_only: {{read_only}}) {% end %} end diff --git a/src/spec/dsl.cr b/src/spec/dsl.cr index 578076b86d69..d712aa59da4f 100644 --- a/src/spec/dsl.cr +++ b/src/spec/dsl.cr @@ -298,8 +298,8 @@ module Spec # If the "log" module is required it is configured to emit no entries by default. def log_setup defined?(::Log) do - if Log.responds_to?(:setup) - Log.setup_from_env(default_level: :none) + if ::Log.responds_to?(:setup) + ::Log.setup_from_env(default_level: :none) end end end diff --git a/src/spec/helpers/iterate.cr b/src/spec/helpers/iterate.cr index be302ebb49c2..7a70f83408ca 100644 --- a/src/spec/helpers/iterate.cr +++ b/src/spec/helpers/iterate.cr @@ -47,7 +47,7 @@ module Spec::Methods # See `.it_iterates` for details. macro assert_iterates_yielding(expected, method, *, infinite = false, tuple = false) %remaining = ({{expected}}).size - %ary = [] of typeof(Enumerable.element_type({{ expected }})) + %ary = [] of typeof(::Enumerable.element_type({{ expected }})) {{ method.id }} do |{% if tuple %}*{% end %}x| if %remaining == 0 if {{ infinite }} @@ -73,11 +73,11 @@ module Spec::Methods # # See `.it_iterates` for details. macro assert_iterates_iterator(expected, method, *, infinite = false) - %ary = [] of typeof(Enumerable.element_type({{ expected }})) + %ary = [] of typeof(::Enumerable.element_type({{ expected }})) %iter = {{ method.id }} ({{ expected }}).size.times do %v = %iter.next - if %v.is_a?(Iterator::Stop) + if %v.is_a?(::Iterator::Stop) # Compare the actual value directly. Since there are less # then expected values, the expectation will fail and raise. %ary.should eq({{ expected }}) @@ -86,7 +86,7 @@ module Spec::Methods %ary << %v end unless {{ infinite }} - %iter.next.should be_a(Iterator::Stop) + %iter.next.should be_a(::Iterator::Stop) end %ary.should eq({{ expected }}) diff --git a/src/static_array.cr b/src/static_array.cr index 2c09e21df166..3d00705bc21a 100644 --- a/src/static_array.cr +++ b/src/static_array.cr @@ -50,7 +50,7 @@ struct StaticArray(T, N) # * `Number.static_array` is a convenient alternative for designating a # specific numerical item type. macro [](*args) - %array = uninitialized StaticArray(typeof({{args.splat}}), {{args.size}}) + %array = uninitialized ::StaticArray(typeof({{args.splat}}), {{args.size}}) {% for arg, i in args %} %array.to_unsafe[{{i}}] = {{arg}} {% end %} diff --git a/src/syscall/aarch64-linux.cr b/src/syscall/aarch64-linux.cr index 5a61e8e7eed8..77b891fe2a7c 100644 --- a/src/syscall/aarch64-linux.cr +++ b/src/syscall/aarch64-linux.cr @@ -334,7 +334,7 @@ module Syscall end macro def_syscall(name, return_type, *args) - @[AlwaysInline] + @[::AlwaysInline] def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} diff --git a/src/syscall/arm-linux.cr b/src/syscall/arm-linux.cr index 97119fc4b3f3..da349dd45301 100644 --- a/src/syscall/arm-linux.cr +++ b/src/syscall/arm-linux.cr @@ -409,7 +409,7 @@ module Syscall end macro def_syscall(name, return_type, *args) - @[AlwaysInline] + @[::AlwaysInline] def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} diff --git a/src/syscall/i386-linux.cr b/src/syscall/i386-linux.cr index 843b2d1fd856..a0f94a51160a 100644 --- a/src/syscall/i386-linux.cr +++ b/src/syscall/i386-linux.cr @@ -445,7 +445,7 @@ module Syscall end macro def_syscall(name, return_type, *args) - @[AlwaysInline] + @[::AlwaysInline] def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} diff --git a/src/syscall/x86_64-linux.cr b/src/syscall/x86_64-linux.cr index 1f01c9226658..5a63b6ee2e1a 100644 --- a/src/syscall/x86_64-linux.cr +++ b/src/syscall/x86_64-linux.cr @@ -368,7 +368,7 @@ module Syscall end macro def_syscall(name, return_type, *args) - @[AlwaysInline] + @[::AlwaysInline] def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} diff --git a/src/uri/params/serializable.cr b/src/uri/params/serializable.cr index c0d766e85242..54d3b970e53c 100644 --- a/src/uri/params/serializable.cr +++ b/src/uri/params/serializable.cr @@ -59,19 +59,19 @@ struct URI::Params # ``` module Serializable macro included - def self.from_www_form(params : String) - new_from_www_form URI::Params.parse params + def self.from_www_form(params : ::String) + new_from_www_form ::URI::Params.parse params end # :nodoc: # # This is needed so that nested types can pass the name thru internally. # Has to be public so the generated code can call it, but should be considered an implementation detail. - def self.from_www_form(params : ::URI::Params, name : String) + def self.from_www_form(params : ::URI::Params, name : ::String) new_from_www_form(params, name) end - protected def self.new_from_www_form(params : ::URI::Params, name : String? = nil) + protected def self.new_from_www_form(params : ::URI::Params, name : ::String? = nil) instance = allocate instance.initialize(__uri_params: params, name: name) GC.add_finalizer(instance) if instance.responds_to?(:finalize) @@ -79,12 +79,12 @@ struct URI::Params end macro inherited - def self.from_www_form(params : String) - new_from_www_form URI::Params.parse params + def self.from_www_form(params : ::String) + new_from_www_form ::URI::Params.parse params end # :nodoc: - def self.from_www_form(params : ::URI::Params, name : String) + def self.from_www_form(params : ::URI::Params, name : ::String) new_from_www_form(params, name) end end diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index d5fae8dfe9c0..4a1521469dea 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -156,11 +156,11 @@ module YAML # Define a `new` directly in the included type, # so it overloads well with other possible initializes - def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) + def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node) new_from_yaml_node(ctx, node) end - private def self.new_from_yaml_node(ctx : YAML::ParseContext, node : YAML::Nodes::Node) + private def self.new_from_yaml_node(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node) ctx.read_alias(node, self) do |obj| return obj end @@ -170,7 +170,7 @@ module YAML ctx.record_anchor(node, instance) instance.initialize(__context_for_yaml_serializable: ctx, __node_for_yaml_serializable: node) - GC.add_finalizer(instance) if instance.responds_to?(:finalize) + ::GC.add_finalizer(instance) if instance.responds_to?(:finalize) instance end @@ -178,7 +178,7 @@ module YAML # so it can compete with other possible initializes macro inherited - def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) + def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node) new_from_yaml_node(ctx, node) end end @@ -409,17 +409,17 @@ module YAML {% mapping.raise "Mapping argument must be a HashLiteral or a NamedTupleLiteral, not #{mapping.class_name.id}" %} {% end %} - def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) + def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node) ctx.read_alias(node, \{{@type}}) do |obj| return obj end - unless node.is_a?(YAML::Nodes::Mapping) + unless node.is_a?(::YAML::Nodes::Mapping) node.raise "Expected YAML mapping, not #{node.class}" end node.each do |key, value| - next unless key.is_a?(YAML::Nodes::Scalar) && value.is_a?(YAML::Nodes::Scalar) + next unless key.is_a?(::YAML::Nodes::Scalar) && value.is_a?(::YAML::Nodes::Scalar) next unless key.value == {{field.id.stringify}} discriminator_value = value.value From 9240f50795ebfc3f52d85a07d546acf173dc6379 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 6 Sep 2024 18:37:06 +0800 Subject: [PATCH 1340/1551] Fix exponent wrapping in `Math.frexp(BigFloat)` for very large values (#14971) `BigFloat`s represent their base-`256 ** sizeof(LibGMP::MpLimb)` exponent with a `LibGMP::MpExp` field, but `LibGMP.mpf_get_d_2exp` only returns the base-2 exponent as a `LibC::Long`, so values outside `(2.0.to_big_f ** -0x80000001)...(2.0.to_big_f ** 0x7FFFFFFF)` lead to an exponent overflow on Windows or 32-bit platforms: ```crystal require "big" Math.frexp(2.0.to_big_f ** 0xFFFFFFF5) # => {1.55164027193164307015e+1292913986, -10} Math.frexp(2.0.to_big_f ** -0xFFFFFFF4) # => {1.61119819150333097422e-1292913987, 13} Math.frexp(2.0.to_big_f ** 0x7FFFFFFF) # raises OverflowError ``` This patch fixes it by computing the exponent ourselves. --- spec/std/big/big_float_spec.cr | 16 +++++++++++++ src/big/big_float.cr | 41 +++++++++++++--------------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index 08d7e93bfb0b..73c6bcf06de8 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -548,7 +548,23 @@ end describe "BigFloat Math" do it ".frexp" do + Math.frexp(0.to_big_f).should eq({0.0, 0}) + Math.frexp(1.to_big_f).should eq({0.5, 1}) Math.frexp(0.2.to_big_f).should eq({0.8, -2}) + Math.frexp(2.to_big_f ** 63).should eq({0.5, 64}) + Math.frexp(2.to_big_f ** 64).should eq({0.5, 65}) + Math.frexp(2.to_big_f ** 200).should eq({0.5, 201}) + Math.frexp(2.to_big_f ** -200).should eq({0.5, -199}) + Math.frexp(2.to_big_f ** 0x7FFFFFFF).should eq({0.5, 0x80000000}) + Math.frexp(2.to_big_f ** 0x80000000).should eq({0.5, 0x80000001}) + Math.frexp(2.to_big_f ** 0xFFFFFFFF).should eq({0.5, 0x100000000}) + Math.frexp(1.75 * 2.to_big_f ** 0x123456789).should eq({0.875, 0x12345678A}) + Math.frexp(2.to_big_f ** -0x80000000).should eq({0.5, -0x7FFFFFFF}) + Math.frexp(2.to_big_f ** -0x80000001).should eq({0.5, -0x80000000}) + Math.frexp(2.to_big_f ** -0x100000000).should eq({0.5, -0xFFFFFFFF}) + Math.frexp(1.75 * 2.to_big_f ** -0x123456789).should eq({0.875, -0x123456788}) + Math.frexp(-(2.to_big_f ** 0x7FFFFFFF)).should eq({-0.5, 0x80000000}) + Math.frexp(-(2.to_big_f ** -0x100000000)).should eq({-0.5, -0xFFFFFFFF}) end it ".sqrt" do diff --git a/src/big/big_float.cr b/src/big/big_float.cr index 2c567f21eec9..ff78b7de8290 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -537,15 +537,24 @@ end module Math # Decomposes the given floating-point *value* into a normalized fraction and an integral power of two. def frexp(value : BigFloat) : {BigFloat, Int64} - LibGMP.mpf_get_d_2exp(out exp, value) # we need BigFloat frac, so will skip Float64 one. + return {BigFloat.zero, 0_i64} if value.zero? + + # We compute this ourselves since `LibGMP.mpf_get_d_2exp` only returns a + # `LibC::Long` exponent, which is not sufficient for 32-bit `LibC::Long` and + # 32-bit `LibGMP::MpExp`, e.g. on 64-bit Windows. + # Since `0.5 <= frac.abs < 1.0`, the radix point should be just above the + # most significant limb, and there should be no leading zeros in that limb. + leading_zeros = value.@mpf._mp_d[value.@mpf._mp_size.abs - 1].leading_zeros_count + exp = 8_i64 * sizeof(LibGMP::MpLimb) * value.@mpf._mp_exp - leading_zeros + frac = BigFloat.new do |mpf| - if exp >= 0 - LibGMP.mpf_div_2exp(mpf, value, exp) - else - LibGMP.mpf_mul_2exp(mpf, value, -exp) - end + # remove leading zeros in the most significant limb + LibGMP.mpf_mul_2exp(mpf, value, leading_zeros) + # reset the exponent manually + mpf.value._mp_exp = 0 end - {frac, exp.to_i64} + + {frac, exp} end # Calculates the square root of *value*. @@ -559,21 +568,3 @@ module Math BigFloat.new { |mpf| LibGMP.mpf_sqrt(mpf, value) } end end - -# :nodoc: -struct Crystal::Hasher - def self.reduce_num(value : BigFloat) - float_normalize_wrap(value) do |value| - # more exact version of `Math.frexp` - LibGMP.mpf_get_d_2exp(out exp, value) - frac = BigFloat.new do |mpf| - if exp >= 0 - LibGMP.mpf_div_2exp(mpf, value, exp) - else - LibGMP.mpf_mul_2exp(mpf, value, -exp) - end - end - float_normalize_reference(value, frac, exp) - end - end -end From 85d1ae78670b756e47c544ce34d6022ea0b6b4d1 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 6 Sep 2024 12:37:53 +0200 Subject: [PATCH 1341/1551] Fix: `Crystal::SpinLock` doesn't need to be allocated on the HEAP (#14972) The abstraction is a mere abstraction over an atomic integer and the object itself are only ever used internally of other objects, with the exception of Channel where the code explicitely accesses the ivar directly (thus not making copies). We can avoid a HEAP allocation everywhere we use them (i.e. in lots of places). --- src/crystal/spin_lock.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crystal/spin_lock.cr b/src/crystal/spin_lock.cr index 4255fcae7bbd..105c235e0c66 100644 --- a/src/crystal/spin_lock.cr +++ b/src/crystal/spin_lock.cr @@ -1,5 +1,5 @@ # :nodoc: -class Crystal::SpinLock +struct Crystal::SpinLock private UNLOCKED = 0 private LOCKED = 1 From 025f3e041693882790d8941a8ecbd989715645cb Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 6 Sep 2024 12:39:25 +0200 Subject: [PATCH 1342/1551] Fix: `#file_descriptor_close` should set `@closed` (UNIX) (#14973) Prevents the GC from trying to cleanup resources that had already been closed in signal/process pipes. --- src/crystal/system/unix/file_descriptor.cr | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 56a9eee80dd5..759802f4323e 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -121,6 +121,13 @@ module Crystal::System::FileDescriptor end def file_descriptor_close(&) : Nil + # It would usually be set by IO::Buffered#unbuffered_close but we sometimes + # close file descriptors directly (i.e. signal/process pipes) and the IO + # object wouldn't be marked as closed, leading IO::FileDescriptor#finalize + # to try to close the fd again (pointless) and lead to other issues if we + # try to do more cleanup in the finalizer (error) + @closed = true + # Clear the @volatile_fd before actually closing it in order to # reduce the chance of reading an outdated fd value _fd = @volatile_fd.swap(-1) From cf15fb2dfbd9cf92aff2f191a136c1a1c12013ca Mon Sep 17 00:00:00 2001 From: "Brian J. Cardiff" Date: Fri, 6 Sep 2024 08:59:02 -0300 Subject: [PATCH 1343/1551] Adds initial support for external commands (#14953) --- spec/primitives/external_command_spec.cr | 34 ++++++++++++++++++++++++ src/compiler/crystal/command.cr | 3 +++ 2 files changed, 37 insertions(+) create mode 100644 spec/primitives/external_command_spec.cr diff --git a/spec/primitives/external_command_spec.cr b/spec/primitives/external_command_spec.cr new file mode 100644 index 000000000000..91687f7c2d21 --- /dev/null +++ b/spec/primitives/external_command_spec.cr @@ -0,0 +1,34 @@ +{% skip_file if flag?(:interpreted) %} + +require "../spec_helper" + +describe Crystal::Command do + it "exec external commands", tags: %w[slow] do + with_temp_executable "crystal-external" do |path| + with_tempfile "crystal-external.cr" do |source_file| + File.write source_file, <<-CRYSTAL + puts ENV["CRYSTAL"]? + puts PROGRAM_NAME + puts ARGV + CRYSTAL + + Process.run(ENV["CRYSTAL_SPEC_COMPILER_BIN"]? || "bin/crystal", ["build", source_file, "-o", path]) + end + + File.exists?(path).should be_true + + process = Process.new(ENV["CRYSTAL_SPEC_COMPILER_BIN"]? || "bin/crystal", + ["external", "foo", "bar"], + output: :pipe, + env: {"PATH" => {ENV["PATH"], File.dirname(path)}.join(Process::PATH_DELIMITER)} + ) + output = process.output.gets_to_end + status = process.wait + status.success?.should be_true + lines = output.lines + lines[0].should match /crystal/ + lines[1].should match /crystal-external/ + lines[2].should eq %(["foo", "bar"]) + end + end +end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index f8ece87e3d4b..1354594706fb 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -130,6 +130,9 @@ class Crystal::Command else if command.ends_with?(".cr") error "file '#{command}' does not exist" + elsif external_command = Process.find_executable("crystal-#{command}") + options.shift + Process.exec(external_command, options, env: {"CRYSTAL" => Process.executable_path}) else error "unknown command: #{command}" end From ef306fb5aba4a03c497317ae7bf9f5da0c7a3549 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 6 Sep 2024 18:13:08 +0200 Subject: [PATCH 1344/1551] Fix: reinit event loop first after fork (UNIX) (#14975) Signal handling manipulate pipes (file descriptors) on UNIX, and messing with the evloop after fork can affect the parent process evloop in some cases. --- src/kernel.cr | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/kernel.cr b/src/kernel.cr index 8c84a197b78f..14e66bd4fade 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -584,14 +584,14 @@ end # Hooks are defined here due to load order problems. def self.after_fork_child_callbacks @@after_fork_child_callbacks ||= [ - # clean ups (don't depend on event loop): + # reinit event loop first: + ->{ Crystal::EventLoop.current.after_fork }, + + # reinit signal handling: ->Crystal::System::Signal.after_fork, ->Crystal::System::SignalChildHandler.after_fork, - # reinit event loop: - ->{ Crystal::EventLoop.current.after_fork }, - - # more clean ups (may depend on event loop): + # additional reinitialization ->Random::DEFAULT.new_seed, ] of -> Nil end From 136f85ede8c7a3985eff2e4a0871f3645876e36f Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 6 Sep 2024 18:13:49 +0200 Subject: [PATCH 1345/1551] Don't involve evloop after fork in System::Process.spawn (UNIX) (#14974) Refactors spawning child processes on UNIX that relies on fork/exec to not involve the event loop after fork and before exec. We still continue to rely on the eventloop in the parent process, of course. * Extract Crystal::System::FileDescriptor.system_pipe (UNIX) * Fix: avoid evloop after fork to report failures in Process.spawn (UNIX) * Fix: don't involve evloop in System::Process.reopen_io (UNIX) This is called after fork before exec to reopen the stdio. We can leverage some abstractions (set blocking, unset cloexec) but musn't call to methods that involve the evloop and would mess with the parent evloop. --- src/crystal/system/unix/file_descriptor.cr | 28 ++++++++++-- src/crystal/system/unix/process.cr | 53 ++++++++++++---------- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 759802f4323e..60515b701136 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -203,6 +203,14 @@ module Crystal::System::FileDescriptor end def self.pipe(read_blocking, write_blocking) + pipe_fds = system_pipe + r = IO::FileDescriptor.new(pipe_fds[0], read_blocking) + w = IO::FileDescriptor.new(pipe_fds[1], write_blocking) + w.sync = true + {r, w} + end + + def self.system_pipe : StaticArray(LibC::Int, 2) pipe_fds = uninitialized StaticArray(LibC::Int, 2) {% if LibC.has_method?(:pipe2) %} @@ -219,11 +227,7 @@ module Crystal::System::FileDescriptor end {% end %} - r = IO::FileDescriptor.new(pipe_fds[0], read_blocking) - w = IO::FileDescriptor.new(pipe_fds[1], write_blocking) - w.sync = true - - {r, w} + pipe_fds end def self.pread(file, buffer, offset) @@ -255,6 +259,20 @@ module Crystal::System::FileDescriptor io end + # Helper to write *size* values at *pointer* to a given *fd*. + def self.write_fully(fd : LibC::Int, pointer : Pointer, size : Int32 = 1) : Nil + write_fully(fd, Slice.new(pointer, size).unsafe_slice_of(UInt8)) + end + + # Helper to fully write a slice to a given *fd*. + def self.write_fully(fd : LibC::Int, slice : Slice(UInt8)) : Nil + until slice.size == 0 + size = LibC.write(fd, slice, slice.size) + break if size == -1 + slice += size + end + end + private def system_echo(enable : Bool, mode = nil) new_mode = mode || FileDescriptor.tcgetattr(fd) flags = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 4a540fa53a3d..06b18aea7b1d 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -231,44 +231,47 @@ struct Crystal::System::Process end def self.spawn(command_args, env, clear_env, input, output, error, chdir) - reader_pipe, writer_pipe = IO.pipe + r, w = FileDescriptor.system_pipe pid = self.fork(will_exec: true) if !pid + LibC.close(r) begin - reader_pipe.close - writer_pipe.close_on_exec = true self.try_replace(command_args, env, clear_env, input, output, error, chdir) - writer_pipe.write_byte(1) - writer_pipe.write_bytes(Errno.value.to_i) + byte = 1_u8 + errno = Errno.value.to_i32 + FileDescriptor.write_fully(w, pointerof(byte)) + FileDescriptor.write_fully(w, pointerof(errno)) rescue ex - writer_pipe.write_byte(0) + byte = 0_u8 message = ex.inspect_with_backtrace - writer_pipe.write_bytes(message.bytesize) - writer_pipe << message - writer_pipe.close + FileDescriptor.write_fully(w, pointerof(byte)) + FileDescriptor.write_fully(w, message.to_slice) ensure + LibC.close(w) LibC._exit 127 end end - writer_pipe.close + LibC.close(w) + reader_pipe = IO::FileDescriptor.new(r, blocking: false) + begin case reader_pipe.read_byte when nil # Pipe was closed, no error when 0 # Error message coming - message_size = reader_pipe.read_bytes(Int32) - if message_size > 0 - message = String.build(message_size) { |io| IO.copy(reader_pipe, io, message_size) } - end - reader_pipe.close + message = reader_pipe.gets_to_end raise RuntimeError.new("Error executing process: '#{command_args[0]}': #{message}") when 1 # Errno coming - errno = Errno.new(reader_pipe.read_bytes(Int32)) - self.raise_exception_from_errno(command_args[0], errno) + # can't use IO#read_bytes(Int32) because we skipped system/network + # endianness check when writing the integer while read_bytes would; + # we thus read it in the same as order as written + buf = uninitialized StaticArray(UInt8, 4) + reader_pipe.read_fully(buf.to_slice) + raise_exception_from_errno(command_args[0], Errno.new(buf.unsafe_as(Int32))) else raise RuntimeError.new("BUG: Invalid error response received from subprocess") end @@ -339,15 +342,17 @@ struct Crystal::System::Process private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor) if src_io.closed? - dst_io.close - return - end + dst_io.file_descriptor_close + else + src_io = to_real_fd(src_io) - src_io = to_real_fd(src_io) + # dst_io.reopen(src_io) + ret = LibC.dup2(src_io.fd, dst_io.fd) + raise IO::Error.from_errno("dup2") if ret == -1 - dst_io.reopen(src_io) - dst_io.blocking = true - dst_io.close_on_exec = false + dst_io.blocking = true + dst_io.close_on_exec = false + end end private def self.to_real_fd(fd : IO::FileDescriptor) From cdd9ccf460641ee17a603c67ff9d23aa1199f14f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 7 Sep 2024 17:43:48 +0800 Subject: [PATCH 1346/1551] Enable the interpreter on Windows (#14964) --- .github/workflows/win.yml | 37 +++++++++++++++++++++++- .github/workflows/win_build_portable.yml | 2 +- spec/std/http/client/client_spec.cr | 6 ++++ spec/std/http/server/server_spec.cr | 6 ++++ spec/std/http/web_socket_spec.cr | 6 ++++ spec/std/io/io_spec.cr | 33 +++++++++++---------- spec/std/oauth2/client_spec.cr | 6 ++++ spec/std/openssl/ssl/server_spec.cr | 6 ++++ spec/std/openssl/ssl/socket_spec.cr | 6 ++++ spec/std/process_spec.cr | 7 ++++- spec/std/socket/socket_spec.cr | 6 ++++ spec/std/socket/tcp_socket_spec.cr | 6 ++++ spec/std/socket/unix_server_spec.cr | 6 ++++ spec/std/socket/unix_socket_spec.cr | 6 ++++ spec/std/uuid_spec.cr | 1 + src/compiler/crystal/loader/msvc.cr | 37 ++++++++++++++++++------ src/kernel.cr | 13 +++++++++ 17 files changed, 163 insertions(+), 27 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 05f74b6378c6..568828b17bee 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -7,6 +7,7 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: + SPEC_SPLIT_DOTS: 160 CI_LLVM_VERSION: "18.1.1" jobs: @@ -266,13 +267,47 @@ jobs: run: make -f Makefile.win samples x86_64-windows-release: - if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/')) needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm-libs, x86_64-windows-llvm-dlls] uses: ./.github/workflows/win_build_portable.yml with: release: true llvm_version: "18.1.1" + x86_64-windows-test-interpreter: + runs-on: windows-2022 + needs: [x86_64-windows-release] + steps: + - name: Disable CRLF line ending substitution + run: | + git config --global core.autocrlf false + + - name: Download Crystal source + uses: actions/checkout@v4 + + - name: Download Crystal executable + uses: actions/download-artifact@v4 + with: + name: crystal-release + path: build + + - name: Restore LLVM + uses: actions/cache/restore@v4 + with: + path: llvm + key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc + fail-on-cache-miss: true + + - name: Set up environment + run: | + Add-Content $env:GITHUB_PATH "$(pwd)\build" + Add-Content $env:GITHUB_ENV "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\build\crystal.exe" + + - name: Run stdlib specs with interpreter + run: bin\crystal i spec\std_spec.cr + + - name: Run primitives specs with interpreter + run: bin\crystal i spec\primitives_spec.cr + x86_64-windows-installer: if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/')) runs-on: windows-2022 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index d2ed6469d264..12ee17da9e68 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -114,7 +114,7 @@ jobs: - name: Build Crystal run: | bin/crystal.bat env - make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }} + make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }} interpreter=1 - name: Download shards release uses: actions/checkout@v4 diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index 451960a8c79f..6bd04ab3e2f2 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -6,6 +6,12 @@ require "http/server" require "http/log" require "log/spec" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending HTTP::Client + {% skip_file %} +{% end %} + private def test_server(host, port, read_time = 0.seconds, content_type = "text/plain", write_response = true, &) server = TCPServer.new(host, port) begin diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index 5e1e5dab76f6..3980084ea414 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -4,6 +4,12 @@ require "http/client" require "../../../support/ssl" require "../../../support/channel" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending HTTP::Server + {% skip_file %} +{% end %} + # TODO: replace with `HTTP::Client.get` once it supports connecting to Unix socket (#2735) private def unix_request(path) UNIXSocket.open(path) do |io| diff --git a/spec/std/http/web_socket_spec.cr b/spec/std/http/web_socket_spec.cr index 75a54e91fb2e..164a1d067df5 100644 --- a/spec/std/http/web_socket_spec.cr +++ b/spec/std/http/web_socket_spec.cr @@ -7,6 +7,12 @@ require "../../support/fibers" require "../../support/ssl" require "../socket/spec_helper.cr" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending HTTP::WebSocket + {% skip_file %} +{% end %} + private def assert_text_packet(packet, size, final = false) assert_packet packet, HTTP::WebSocket::Protocol::Opcode::TEXT, size, final: final end diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 6974a9fe3466..c584ec81a1e8 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -816,23 +816,26 @@ describe IO do io.gets_to_end.should eq("\r\nFoo\nBar") end - it "gets ascii from socket (#9056)" do - server = TCPServer.new "localhost", 0 - sock = TCPSocket.new "localhost", server.local_address.port - begin - sock.set_encoding("ascii") - spawn do - client = server.accept - message = client.gets - client << "#{message}\n" + # TODO: Windows networking in the interpreter requires #12495 + {% unless flag?(:interpreted) || flag?(:win32) %} + it "gets ascii from socket (#9056)" do + server = TCPServer.new "localhost", 0 + sock = TCPSocket.new "localhost", server.local_address.port + begin + sock.set_encoding("ascii") + spawn do + client = server.accept + message = client.gets + client << "#{message}\n" + end + sock << "K\n" + sock.gets.should eq("K") + ensure + server.close + sock.close end - sock << "K\n" - sock.gets.should eq("K") - ensure - server.close - sock.close end - end + {% end %} end describe "encode" do diff --git a/spec/std/oauth2/client_spec.cr b/spec/std/oauth2/client_spec.cr index 3ee66e29ab49..ee445f3426e7 100644 --- a/spec/std/oauth2/client_spec.cr +++ b/spec/std/oauth2/client_spec.cr @@ -3,6 +3,12 @@ require "oauth2" require "http/server" require "../http/spec_helper" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending OAuth2::Client + {% skip_file %} +{% end %} + describe OAuth2::Client do describe "authorization uri" do it "gets with default endpoint" do diff --git a/spec/std/openssl/ssl/server_spec.cr b/spec/std/openssl/ssl/server_spec.cr index 2e0e413a618d..8618ed780a50 100644 --- a/spec/std/openssl/ssl/server_spec.cr +++ b/spec/std/openssl/ssl/server_spec.cr @@ -3,6 +3,12 @@ require "socket" require "../../spec_helper" require "../../../support/ssl" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending OpenSSL::SSL::Server + {% skip_file %} +{% end %} + describe OpenSSL::SSL::Server do it "sync_close" do TCPServer.open(0) do |tcp_server| diff --git a/spec/std/openssl/ssl/socket_spec.cr b/spec/std/openssl/ssl/socket_spec.cr index bbc5b11e4b9b..47374ce28cca 100644 --- a/spec/std/openssl/ssl/socket_spec.cr +++ b/spec/std/openssl/ssl/socket_spec.cr @@ -4,6 +4,12 @@ require "../../spec_helper" require "../../socket/spec_helper" require "../../../support/ssl" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending OpenSSL::SSL::Socket + {% skip_file %} +{% end %} + describe OpenSSL::SSL::Socket do describe OpenSSL::SSL::Socket::Server do it "auto accept client by default" do diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 57f90121c26b..d41ee0bed242 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -55,7 +55,12 @@ private def newline end # interpreted code doesn't receive SIGCHLD for `#wait` to work (#12241) -pending_interpreted describe: Process do +{% if flag?(:interpreted) && !flag?(:win32) %} + pending Process + {% skip_file %} +{% end %} + +describe Process do describe ".new" do it "raises if command doesn't exist" do expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index 2127e196b746..98555937dea3 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -2,6 +2,12 @@ require "./spec_helper" require "../../support/tempfile" require "../../support/win32" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending Socket + {% skip_file %} +{% end %} + describe Socket, tags: "network" do describe ".unix" do it "creates a unix socket" do diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index 68c00ccd2e79..f3d460f92401 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -3,6 +3,12 @@ require "./spec_helper" require "../../support/win32" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending TCPSocket + {% skip_file %} +{% end %} + describe TCPSocket, tags: "network" do describe "#connect" do each_ip_family do |family, address| diff --git a/spec/std/socket/unix_server_spec.cr b/spec/std/socket/unix_server_spec.cr index ca364f08667c..60f0279b4091 100644 --- a/spec/std/socket/unix_server_spec.cr +++ b/spec/std/socket/unix_server_spec.cr @@ -4,6 +4,12 @@ require "../../support/fibers" require "../../support/channel" require "../../support/tempfile" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending UNIXServer + {% skip_file %} +{% end %} + describe UNIXServer do describe ".new" do it "raises when path is too long" do diff --git a/spec/std/socket/unix_socket_spec.cr b/spec/std/socket/unix_socket_spec.cr index 24777bada67f..c51f37193c0e 100644 --- a/spec/std/socket/unix_socket_spec.cr +++ b/spec/std/socket/unix_socket_spec.cr @@ -2,6 +2,12 @@ require "spec" require "socket" require "../../support/tempfile" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending UNIXSocket + {% skip_file %} +{% end %} + describe UNIXSocket do it "raises when path is too long" do with_tempfile("unix_socket-too_long-#{("a" * 2048)}.sock") do |path| diff --git a/spec/std/uuid_spec.cr b/spec/std/uuid_spec.cr index 48cc3351a3c6..5d7e627031f0 100644 --- a/spec/std/uuid_spec.cr +++ b/spec/std/uuid_spec.cr @@ -1,6 +1,7 @@ require "spec" require "uuid" require "spec/helpers/string" +require "../support/wasm32" describe "UUID" do describe "#==" do diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index 05bf988c9218..772e4c5c232f 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -133,15 +133,25 @@ class Crystal::Loader end def load_file?(path : String | ::Path) : Bool + # API sets shouldn't be linked directly from linker flags, but just in case + if api_set?(path) + return load_dll?(path.to_s) + end + return false unless File.file?(path) # On Windows, each `.lib` import library may reference any number of `.dll` # files, whose base names may not match the library's. Thus it is necessary # to extract this information from the library archive itself. - System::LibraryArchive.imported_dlls(path).each do |dll| - dll_full_path = @dll_search_paths.try &.each do |search_path| - full_path = File.join(search_path, dll) - break full_path if File.file?(full_path) + System::LibraryArchive.imported_dlls(path).all? do |dll| + # API set names do not refer to physical filenames despite ending with + # `.dll`, and therefore should not use a path search: + # https://learn.microsoft.com/en-us/cpp/windows/universal-crt-deployment?view=msvc-170#local-deployment + unless api_set?(dll) + dll_full_path = @dll_search_paths.try &.each do |search_path| + full_path = File.join(search_path, dll) + break full_path if File.file?(full_path) + end end dll = dll_full_path || dll @@ -152,13 +162,16 @@ class Crystal::Loader # # Note that the compiler's directory and PATH are effectively searched # twice when coming from the interpreter - handle = open_library(dll) - return false unless handle - - @handles << handle - @loaded_libraries << (module_filename(handle) || dll) + load_dll?(dll) end + end + + private def load_dll?(dll) + handle = open_library(dll) + return false unless handle + @handles << handle + @loaded_libraries << (module_filename(handle) || dll) true end @@ -190,6 +203,12 @@ class Crystal::Loader @handles.clear end + # Returns whether *dll* names an API set according to: + # https://learn.microsoft.com/en-us/windows/win32/apiindex/windows-apisets#api-set-contract-names + private def api_set?(dll) + dll.to_s.matches?(/^(?:api-|ext-)[a-zA-Z0-9-]*l\d+-\d+-\d+\.dll$/) + end + private def module_filename(handle) Crystal::System.retry_wstr_buffer do |buffer, small_buf| len = LibC.GetModuleFileNameW(handle, buffer, buffer.size) diff --git a/src/kernel.cr b/src/kernel.cr index 14e66bd4fade..16c4a770309a 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -616,3 +616,16 @@ end Crystal::System::Signal.setup_default_handlers {% end %} {% end %} + +# This is a temporary workaround to ensure there is always something in the IOCP +# event loop being awaited, since both the interrupt loop and the fiber stack +# pool collector are disabled in interpreted code. Without this, asynchronous +# code that bypasses `Crystal::IOCP::OverlappedOperation` does not currently +# work, see https://github.com/crystal-lang/crystal/pull/14949#issuecomment-2328314463 +{% if flag?(:interpreted) && flag?(:win32) %} + spawn(name: "Interpreter idle loop") do + while true + sleep 1.day + end + end +{% end %} From bdddae759a2e1f77306e1a54523a136a0b64c078 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 9 Sep 2024 22:12:05 +0200 Subject: [PATCH 1347/1551] Add methods to Crystal::EventLoop (#14977) Add `#after_fork_before_exec` to allow an evloop to do some cleanup before exec (UNIX only). Add `#remove(io)` to allow an evloop to free resources when the IO is closed in a GC finalizer. --- src/crystal/scheduler.cr | 6 ++++++ src/crystal/system/event_loop.cr | 5 +++++ src/crystal/system/event_loop/file_descriptor.cr | 8 ++++++++ src/crystal/system/event_loop/socket.cr | 8 ++++++++ src/crystal/system/file_descriptor.cr | 4 ++++ src/crystal/system/socket.cr | 4 ++++ src/crystal/system/unix/event_loop_libevent.cr | 9 +++++++++ src/crystal/system/unix/process.cr | 3 +++ src/crystal/system/wasi/event_loop.cr | 6 ++++++ src/crystal/system/win32/event_loop_iocp.cr | 6 ++++++ src/io/file_descriptor.cr | 1 + src/socket.cr | 1 + 12 files changed, 61 insertions(+) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index d3634e9aea6a..bed98ef4d05b 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -24,6 +24,12 @@ class Crystal::Scheduler Thread.current.scheduler.@event_loop end + def self.event_loop? + if scheduler = Thread.current?.try(&.scheduler?) + scheduler.@event_loop + end + end + def self.enqueue(fiber : Fiber) : Nil Crystal.trace :sched, "enqueue", fiber: fiber do thread = Thread.current diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index 46954e6034ff..fb1042b21f96 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -17,6 +17,11 @@ abstract class Crystal::EventLoop Crystal::Scheduler.event_loop end + @[AlwaysInline] + def self.current? : self? + Crystal::Scheduler.event_loop? + end + # Runs the loop. # # Returns immediately if events are activable. Set `blocking` to false to diff --git a/src/crystal/system/event_loop/file_descriptor.cr b/src/crystal/system/event_loop/file_descriptor.cr index a041263609d9..5fb6cbb95cb0 100644 --- a/src/crystal/system/event_loop/file_descriptor.cr +++ b/src/crystal/system/event_loop/file_descriptor.cr @@ -19,5 +19,13 @@ abstract class Crystal::EventLoop # Closes the file descriptor resource. abstract def close(file_descriptor : Crystal::System::FileDescriptor) : Nil + + # Removes the file descriptor from the event loop. Can be used to free up + # memory resources associated with the file descriptor, as well as removing + # the file descriptor from kernel data structures. + # + # Called by `::IO::FileDescriptor#finalize` before closing the file + # descriptor. Errors shall be silently ignored. + abstract def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil end end diff --git a/src/crystal/system/event_loop/socket.cr b/src/crystal/system/event_loop/socket.cr index e6f35478b487..6309aed391e0 100644 --- a/src/crystal/system/event_loop/socket.cr +++ b/src/crystal/system/event_loop/socket.cr @@ -62,5 +62,13 @@ abstract class Crystal::EventLoop # Closes the socket. abstract def close(socket : ::Socket) : Nil + + # Removes the socket from the event loop. Can be used to free up memory + # resources associated with the socket, as well as removing the socket from + # kernel data structures. + # + # Called by `::Socket#finalize` before closing the socket. Errors shall be + # silently ignored. + abstract def remove(socket : ::Socket) : Nil end end diff --git a/src/crystal/system/file_descriptor.cr b/src/crystal/system/file_descriptor.cr index 481e00982e25..03868bc07034 100644 --- a/src/crystal/system/file_descriptor.cr +++ b/src/crystal/system/file_descriptor.cr @@ -39,6 +39,10 @@ module Crystal::System::FileDescriptor event_loop.write(self, slice) end + private def event_loop? : Crystal::EventLoop::FileDescriptor? + Crystal::EventLoop.current? + end + private def event_loop : Crystal::EventLoop::FileDescriptor Crystal::EventLoop.current end diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr index 10f902e9f0c1..8d5e8c9afaf0 100644 --- a/src/crystal/system/socket.cr +++ b/src/crystal/system/socket.cr @@ -99,6 +99,10 @@ module Crystal::System::Socket # Also used in `Socket#finalize` # def socket_close + private def event_loop? : Crystal::EventLoop::Socket? + Crystal::EventLoop.current? + end + private def event_loop : Crystal::EventLoop::Socket Crystal::EventLoop.current end diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr index b67bad63ff2f..4594f07ffe66 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/system/unix/event_loop_libevent.cr @@ -4,6 +4,9 @@ require "./event_libevent" class Crystal::LibEvent::EventLoop < Crystal::EventLoop private getter(event_base) { Crystal::LibEvent::Event::Base.new } + def after_fork_before_exec : Nil + end + {% unless flag?(:preview_mt) %} # Reinitializes the event loop after a fork. def after_fork : Nil @@ -93,6 +96,9 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop file_descriptor.evented_close end + def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil + end + def read(socket : ::Socket, slice : Bytes) : Int32 evented_read(socket, "Error reading socket") do LibC.recv(socket.fd, slice, slice.size, 0).to_i32 @@ -186,6 +192,9 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop socket.evented_close end + def remove(socket : ::Socket) : Nil + end + def evented_read(target, errno_msg : String, &) : Int32 loop do bytes_read = yield diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 06b18aea7b1d..420030f8ba53 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -185,6 +185,9 @@ struct Crystal::System::Process # child: pid = nil if will_exec + # notify event loop + Crystal::EventLoop.current.after_fork_before_exec + # reset signal handlers, then sigmask (inherited on exec): Crystal::System::Signal.after_fork_before_exec LibC.sigemptyset(pointerof(newmask)) diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr index ba657b917154..c804c4be27aa 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/system/wasi/event_loop.cr @@ -53,6 +53,9 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop file_descriptor.evented_close end + def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil + end + def read(socket : ::Socket, slice : Bytes) : Int32 evented_read(socket, "Error reading socket") do LibC.recv(socket.fd, slice, slice.size, 0).to_i32 @@ -85,6 +88,9 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop socket.evented_close end + def remove(socket : ::Socket) : Nil + end + def evented_read(target, errno_msg : String, &) : Int32 loop do bytes_read = yield diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index d1aae09b680a..d3cfaf98d853 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -161,6 +161,9 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop LibC.CancelIoEx(file_descriptor.windows_handle, nil) unless file_descriptor.system_blocking? end + def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil + end + private def wsa_buffer(bytes) wsabuf = LibC::WSABUF.new wsabuf.len = bytes.size @@ -271,6 +274,9 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop def close(socket : ::Socket) : Nil end + + def remove(socket : ::Socket) : Nil + end end class Crystal::IOCP::Event diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 622229e43e00..a9b303b4b58c 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -255,6 +255,7 @@ class IO::FileDescriptor < IO def finalize return if closed? || !close_on_finalize? + event_loop?.try(&.remove(self)) file_descriptor_close { } # ignore error end diff --git a/src/socket.cr b/src/socket.cr index 1d367f805343..e97deea9eb04 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -430,6 +430,7 @@ class Socket < IO def finalize return if closed? + event_loop?.try(&.remove(self)) socket_close { } # ignore error end From 849e0d7ad61448eb5b9c9cbd7e1296f41d0f88f9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 10 Sep 2024 04:12:58 +0800 Subject: [PATCH 1348/1551] Make `Crystal::IOCP::OverlappedOperation` abstract (#14987) This allows different overlapped operations to provide their own closure data, instead of putting everything in one big class, such as in https://github.com/crystal-lang/crystal/pull/14979#discussion_r1746549086. --- src/crystal/system/win32/file_descriptor.cr | 4 +- src/crystal/system/win32/iocp.cr | 121 ++++++++++++-------- src/crystal/system/win32/socket.cr | 8 +- 3 files changed, 79 insertions(+), 54 deletions(-) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index d4831d9528cb..cdd23e3ed54d 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -234,7 +234,7 @@ module Crystal::System::FileDescriptor end private def lock_file(handle, flags) - IOCP::OverlappedOperation.run(handle) do |operation| + IOCP::IOOverlappedOperation.run(handle) do |operation| result = LibC.LockFileEx(handle, flags, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, operation) if result == 0 @@ -260,7 +260,7 @@ module Crystal::System::FileDescriptor end private def unlock_file(handle) - IOCP::OverlappedOperation.run(handle) do |operation| + IOCP::IOOverlappedOperation.run(handle) do |operation| result = LibC.UnlockFileEx(handle, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, operation) if result == 0 diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr index ba87ed123f22..384784a193db 100644 --- a/src/crystal/system/win32/iocp.cr +++ b/src/crystal/system/win32/iocp.cr @@ -78,39 +78,66 @@ module Crystal::IOCP end end - class OverlappedOperation + abstract class OverlappedOperation enum State STARTED DONE end + abstract def wait_for_result(timeout, & : WinError ->) + private abstract def try_cancel : Bool + @overlapped = LibC::OVERLAPPED.new @fiber = Fiber.current @state : State = :started - def initialize(@handle : LibC::HANDLE) + def self.run(*args, **opts, &) + operation_storage = uninitialized ReferenceStorage(self) + operation = unsafe_construct(pointerof(operation_storage), *args, **opts) + yield operation end - def initialize(handle : LibC::SOCKET) - @handle = LibC::HANDLE.new(handle) + def self.unbox(overlapped : LibC::OVERLAPPED*) : self + start = overlapped.as(Pointer(UInt8)) - offsetof(self, @overlapped) + Box(self).unbox(start.as(Pointer(Void))) end - def self.run(handle, &) - operation_storage = uninitialized ReferenceStorage(OverlappedOperation) - operation = OverlappedOperation.unsafe_construct(pointerof(operation_storage), handle) - yield operation + def to_unsafe + pointerof(@overlapped) end - def self.unbox(overlapped : LibC::OVERLAPPED*) - start = overlapped.as(Pointer(UInt8)) - offsetof(OverlappedOperation, @overlapped) - Box(OverlappedOperation).unbox(start.as(Pointer(Void))) + protected def schedule(&) + done! + yield @fiber end - def to_unsafe - pointerof(@overlapped) + private def done! + @fiber.cancel_timeout + @state = :done end - def wait_for_result(timeout, &) + private def wait_for_completion(timeout) + if timeout + sleep timeout + else + Fiber.suspend + end + + unless @state.done? + if try_cancel + # Wait for cancellation to complete. We must not free the operation + # until it's completed. + Fiber.suspend + end + end + end + end + + class IOOverlappedOperation < OverlappedOperation + def initialize(@handle : LibC::HANDLE) + end + + def wait_for_result(timeout, & : WinError ->) wait_for_completion(timeout) result = LibC.GetOverlappedResult(@handle, self, out bytes, 0) @@ -124,11 +151,35 @@ module Crystal::IOCP bytes end - def wait_for_wsa_result(timeout, &) + private def try_cancel : Bool + # Microsoft documentation: + # The application must not free or reuse the OVERLAPPED structure + # associated with the canceled I/O operations until they have completed + # (this does not apply to asynchronous operations that finished + # synchronously, as nothing would be queued to the IOCP) + ret = LibC.CancelIoEx(@handle, self) + if ret.zero? + case error = WinError.value + when .error_not_found? + # Operation has already completed, do nothing + return false + else + raise RuntimeError.from_os_error("CancelIoEx", os_error: error) + end + end + true + end + end + + class WSAOverlappedOperation < OverlappedOperation + def initialize(@handle : LibC::SOCKET) + end + + def wait_for_result(timeout, & : WinError ->) wait_for_completion(timeout) flags = 0_u32 - result = LibC.WSAGetOverlappedResult(LibC::SOCKET.new(@handle.address), self, out bytes, false, pointerof(flags)) + result = LibC.WSAGetOverlappedResult(@handle, self, out bytes, false, pointerof(flags)) if result.zero? error = WinError.wsa_value yield error @@ -139,57 +190,31 @@ module Crystal::IOCP bytes end - protected def schedule(&) - done! - yield @fiber - end - - def done! - @fiber.cancel_timeout - @state = :done - end - - def try_cancel : Bool + private def try_cancel : Bool # Microsoft documentation: # The application must not free or reuse the OVERLAPPED structure # associated with the canceled I/O operations until they have completed # (this does not apply to asynchronous operations that finished # synchronously, as nothing would be queued to the IOCP) - ret = LibC.CancelIoEx(@handle, self) + ret = LibC.CancelIoEx(Pointer(Void).new(@handle), self) if ret.zero? case error = WinError.value when .error_not_found? # Operation has already completed, do nothing return false else - raise RuntimeError.from_os_error("CancelIOEx", os_error: error) + raise RuntimeError.from_os_error("CancelIoEx", os_error: error) end end true end - - def wait_for_completion(timeout) - if timeout - sleep timeout - else - Fiber.suspend - end - - unless @state.done? - if try_cancel - # Wait for cancellation to complete. We must not free the operation - # until it's completed. - Fiber.suspend - end - end - end end def self.overlapped_operation(file_descriptor, method, timeout, *, offset = nil, writing = false, &) handle = file_descriptor.windows_handle seekable = LibC.SetFilePointerEx(handle, 0, out original_offset, IO::Seek::Current) != 0 - OverlappedOperation.run(handle) do |operation| + IOOverlappedOperation.run(handle) do |operation| overlapped = operation.to_unsafe if seekable start_offset = offset || original_offset @@ -243,7 +268,7 @@ module Crystal::IOCP end def self.wsa_overlapped_operation(target, socket, method, timeout, connreset_is_error = true, &) - OverlappedOperation.run(socket) do |operation| + WSAOverlappedOperation.run(socket) do |operation| result, value = yield operation if result == LibC::SOCKET_ERROR @@ -257,7 +282,7 @@ module Crystal::IOCP return value end - operation.wait_for_wsa_result(timeout) do |error| + operation.wait_for_result(timeout) do |error| case error when .wsa_io_incomplete?, .error_operation_aborted? raise IO::TimeoutError.new("#{method} timed out") diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 3172be467836..5ed235e24574 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -129,7 +129,7 @@ module Crystal::System::Socket # :nodoc: def overlapped_connect(socket, method, timeout, &) - IOCP::OverlappedOperation.run(socket) do |operation| + IOCP::WSAOverlappedOperation.run(socket) do |operation| result = yield operation if result == 0 @@ -145,7 +145,7 @@ module Crystal::System::Socket return nil end - operation.wait_for_wsa_result(timeout) do |error| + operation.wait_for_result(timeout) do |error| case error when .wsa_io_incomplete?, .wsaeconnrefused? return ::Socket::ConnectError.from_os_error(method, error) @@ -192,7 +192,7 @@ module Crystal::System::Socket end def overlapped_accept(socket, method, &) - IOCP::OverlappedOperation.run(socket) do |operation| + IOCP::WSAOverlappedOperation.run(socket) do |operation| result = yield operation if result == 0 @@ -206,7 +206,7 @@ module Crystal::System::Socket return true end - operation.wait_for_wsa_result(read_timeout) do |error| + operation.wait_for_result(read_timeout) do |error| case error when .wsa_io_incomplete?, .wsaenotsock? return false From c8ecd9339f64a72c83f76f7c65975856ba96e3b5 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 9 Sep 2024 22:14:43 +0200 Subject: [PATCH 1349/1551] Refactor `EventLoop` interface for sleeps & select timeouts (#14980) A couple refactors related to select timeout and the signature of `Crystal::EventLoop::Event#add` that only needs to handle a nilable for `libevent` (because it caches events); other loop don't need that. Sleep and registering a select action are always setting a `Time::Span` and don't need to deal with a nilable. The exception is `IO::Evented` that keeps a cache of `libevent` events and must be able to remove the timeout in case `@read_timeout` would have been set to nil before a second wait on read. --- src/channel/select/timeout_action.cr | 8 +++++--- src/crystal/system/event_loop.cr | 2 +- src/crystal/system/unix/event_libevent.cr | 20 ++++++++++---------- src/crystal/system/wasi/event_loop.cr | 5 ++++- src/crystal/system/win32/event_loop_iocp.cr | 4 ++-- src/fiber.cr | 5 +++-- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/channel/select/timeout_action.cr b/src/channel/select/timeout_action.cr index 9240b480db1a..39986197bbdc 100644 --- a/src/channel/select/timeout_action.cr +++ b/src/channel/select/timeout_action.cr @@ -58,9 +58,11 @@ class Channel(T) end def time_expired(fiber : Fiber) : Nil - if @select_context.try &.try_trigger - fiber.enqueue - end + fiber.enqueue if time_expired? + end + + def time_expired? : Bool + @select_context.try &.try_trigger || false end end end diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index fb1042b21f96..fe973ec8c99e 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -56,7 +56,7 @@ abstract class Crystal::EventLoop abstract def free : Nil # Adds a new timeout to this event. - abstract def add(timeout : Time::Span?) : Nil + abstract def add(timeout : Time::Span) : Nil end end diff --git a/src/crystal/system/unix/event_libevent.cr b/src/crystal/system/unix/event_libevent.cr index 21d6765646d1..32578e5aba9a 100644 --- a/src/crystal/system/unix/event_libevent.cr +++ b/src/crystal/system/unix/event_libevent.cr @@ -19,16 +19,16 @@ module Crystal::LibEvent @freed = false end - def add(timeout : Time::Span?) : Nil - if timeout - timeval = LibC::Timeval.new( - tv_sec: LibC::TimeT.new(timeout.total_seconds), - tv_usec: timeout.nanoseconds // 1_000 - ) - LibEvent2.event_add(@event, pointerof(timeval)) - else - LibEvent2.event_add(@event, nil) - end + def add(timeout : Time::Span) : Nil + timeval = LibC::Timeval.new( + tv_sec: LibC::TimeT.new(timeout.total_seconds), + tv_usec: timeout.nanoseconds // 1_000 + ) + LibEvent2.event_add(@event, pointerof(timeval)) + end + + def add(timeout : Nil) : Nil + LibEvent2.event_add(@event, nil) end def free : Nil diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr index c804c4be27aa..3cce9ba8361c 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/system/wasi/event_loop.cr @@ -132,7 +132,10 @@ end struct Crystal::Wasi::Event include Crystal::EventLoop::Event - def add(timeout : Time::Span?) : Nil + def add(timeout : Time::Span) : Nil + end + + def add(timeout : Nil) : Nil end def free : Nil diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index d3cfaf98d853..d3655fdb5861 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -298,8 +298,8 @@ class Crystal::IOCP::Event free end - def add(timeout : Time::Span?) : Nil - @wake_at = timeout ? Time.monotonic + timeout : Time.monotonic + def add(timeout : Time::Span) : Nil + @wake_at = Time.monotonic + timeout Crystal::EventLoop.current.enqueue(self) end end diff --git a/src/fiber.cr b/src/fiber.cr index 0d471e5a96e4..1086ebdd3669 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -234,20 +234,21 @@ class Fiber end # :nodoc: - def timeout(timeout : Time::Span?, select_action : Channel::TimeoutAction? = nil) : Nil + def timeout(timeout : Time::Span, select_action : Channel::TimeoutAction) : Nil @timeout_select_action = select_action timeout_event.add(timeout) end # :nodoc: def cancel_timeout : Nil + return unless @timeout_select_action @timeout_select_action = nil @timeout_event.try &.delete end # The current fiber will resume after a period of time. # The timeout can be cancelled with `cancel_timeout` - def self.timeout(timeout : Time::Span?, select_action : Channel::TimeoutAction? = nil) : Nil + def self.timeout(timeout : Time::Span, select_action : Channel::TimeoutAction) : Nil Fiber.current.timeout(timeout, select_action) end From 4023f522935743ba6966b627c0976fa653739975 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 10 Sep 2024 14:25:48 +0800 Subject: [PATCH 1350/1551] Remove some uses of deprecated overloads in standard library specs (#14963) * `File.real_path` * `Benchmark::IPS::Job.new` * `File.executable?`, `.readable?`, `.writable?` * `#read_timeout=`, `#write_timeout=`, `#connect_timeout=` --- spec/compiler/semantic/warnings_spec.cr | 4 +- spec/std/benchmark_spec.cr | 2 +- spec/std/dir_spec.cr | 2 +- spec/std/file_spec.cr | 269 ++++++++++++------------ spec/std/http/client/client_spec.cr | 6 +- spec/std/http/server/server_spec.cr | 2 +- spec/std/io/io_spec.cr | 4 +- spec/std/socket/socket_spec.cr | 2 +- spec/std/socket/unix_socket_spec.cr | 4 +- 9 files changed, 149 insertions(+), 146 deletions(-) diff --git a/spec/compiler/semantic/warnings_spec.cr b/spec/compiler/semantic/warnings_spec.cr index 6c6914c60fe5..e8bbad7b7c29 100644 --- a/spec/compiler/semantic/warnings_spec.cr +++ b/spec/compiler/semantic/warnings_spec.cr @@ -234,7 +234,7 @@ describe "Semantic: warnings" do # NOTE tempfile might be created in symlinked folder # which affects how to match current dir /var/folders/... # with the real path /private/var/folders/... - path = File.real_path(path) + path = File.realpath(path) main_filename = File.join(path, "main.cr") output_filename = File.join(path, "main") @@ -416,7 +416,7 @@ describe "Semantic: warnings" do # NOTE tempfile might be created in symlinked folder # which affects how to match current dir /var/folders/... # with the real path /private/var/folders/... - path = File.real_path(path) + path = File.realpath(path) main_filename = File.join(path, "main.cr") output_filename = File.join(path, "main") diff --git a/spec/std/benchmark_spec.cr b/spec/std/benchmark_spec.cr index 8113f5f03a4c..4a46798b2436 100644 --- a/spec/std/benchmark_spec.cr +++ b/spec/std/benchmark_spec.cr @@ -12,7 +12,7 @@ describe Benchmark::IPS::Job do it "works in general / integration test" do # test several things to avoid running a benchmark over and over again in # the specs - j = Benchmark::IPS::Job.new(0.001, 0.001, interactive: false) + j = Benchmark::IPS::Job.new(1.millisecond, 1.millisecond, interactive: false) a = j.report("a") { sleep 1.milliseconds } b = j.report("b") { sleep 2.milliseconds } diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index 439da15becd9..d37483eba947 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -643,7 +643,7 @@ describe "Dir" do Dir.mkdir_p path # Resolve any symbolic links in path caused by tmpdir being a link. # For example on macOS, /tmp is a symlink to /private/tmp. - path = File.real_path(path) + path = File.realpath(path) target_path = File.join(path, "target") link_path = File.join(path, "link") diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 07b919bd4a6e..0f88b2028c2f 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -236,136 +236,6 @@ describe "File" do end end - describe "executable?" do - it "gives true" do - crystal = Process.executable_path || pending! "Unable to locate compiler executable" - File.executable?(crystal).should be_true - end - - it "gives false" do - File.executable?(datapath("test_file.txt")).should be_false - end - - it "gives false when the file doesn't exist" do - File.executable?(datapath("non_existing_file.txt")).should be_false - end - - it "gives false when a component of the path is a file" do - File.executable?(datapath("dir", "test_file.txt", "")).should be_false - end - - it "follows symlinks" do - with_tempfile("good_symlink_x.txt", "bad_symlink_x.txt") do |good_path, bad_path| - crystal = Process.executable_path || pending! "Unable to locate compiler executable" - File.symlink(File.expand_path(crystal), good_path) - File.symlink(File.expand_path(datapath("non_existing_file.txt")), bad_path) - - File.executable?(good_path).should be_true - File.executable?(bad_path).should be_false - end - end - end - - describe "readable?" do - it "gives true" do - File.readable?(datapath("test_file.txt")).should be_true - end - - it "gives false when the file doesn't exist" do - File.readable?(datapath("non_existing_file.txt")).should be_false - end - - it "gives false when a component of the path is a file" do - File.readable?(datapath("dir", "test_file.txt", "")).should be_false - end - - # win32 doesn't have a way to make files unreadable via chmod - {% unless flag?(:win32) %} - it "gives false when the file has no read permissions" do - with_tempfile("unreadable.txt") do |path| - File.write(path, "") - File.chmod(path, 0o222) - pending_if_superuser! - File.readable?(path).should be_false - end - end - - it "gives false when the file has no permissions" do - with_tempfile("unaccessible.txt") do |path| - File.write(path, "") - File.chmod(path, 0o000) - pending_if_superuser! - File.readable?(path).should be_false - end - end - - it "follows symlinks" do - with_tempfile("good_symlink_r.txt", "bad_symlink_r.txt", "unreadable.txt") do |good_path, bad_path, unreadable| - File.write(unreadable, "") - File.chmod(unreadable, 0o222) - pending_if_superuser! - - File.symlink(File.expand_path(datapath("test_file.txt")), good_path) - File.symlink(File.expand_path(unreadable), bad_path) - - File.readable?(good_path).should be_true - File.readable?(bad_path).should be_false - end - end - {% end %} - - it "gives false when the symbolic link destination doesn't exist" do - with_tempfile("missing_symlink_r.txt") do |missing_path| - File.symlink(File.expand_path(datapath("non_existing_file.txt")), missing_path) - File.readable?(missing_path).should be_false - end - end - end - - describe "writable?" do - it "gives true" do - File.writable?(datapath("test_file.txt")).should be_true - end - - it "gives false when the file doesn't exist" do - File.writable?(datapath("non_existing_file.txt")).should be_false - end - - it "gives false when a component of the path is a file" do - File.writable?(datapath("dir", "test_file.txt", "")).should be_false - end - - it "gives false when the file has no write permissions" do - with_tempfile("readonly.txt") do |path| - File.write(path, "") - File.chmod(path, 0o444) - pending_if_superuser! - File.writable?(path).should be_false - end - end - - it "follows symlinks" do - with_tempfile("good_symlink_w.txt", "bad_symlink_w.txt", "readonly.txt") do |good_path, bad_path, readonly| - File.write(readonly, "") - File.chmod(readonly, 0o444) - pending_if_superuser! - - File.symlink(File.expand_path(datapath("test_file.txt")), good_path) - File.symlink(File.expand_path(readonly), bad_path) - - File.writable?(good_path).should be_true - File.writable?(bad_path).should be_false - end - end - - it "gives false when the symbolic link destination doesn't exist" do - with_tempfile("missing_symlink_w.txt") do |missing_path| - File.symlink(File.expand_path(datapath("non_existing_file.txt")), missing_path) - File.writable?(missing_path).should be_false - end - end - end - describe "file?" do it "gives true" do File.file?(datapath("test_file.txt")).should be_true @@ -701,6 +571,139 @@ describe "File" do it "tests unequal for file and directory" do File.info(datapath("dir")).should_not eq(File.info(datapath("test_file.txt"))) end + + describe ".executable?" do + it "gives true" do + crystal = Process.executable_path || pending! "Unable to locate compiler executable" + File::Info.executable?(crystal).should be_true + File.executable?(crystal).should be_true # deprecated + end + + it "gives false" do + File::Info.executable?(datapath("test_file.txt")).should be_false + end + + it "gives false when the file doesn't exist" do + File::Info.executable?(datapath("non_existing_file.txt")).should be_false + end + + it "gives false when a component of the path is a file" do + File::Info.executable?(datapath("dir", "test_file.txt", "")).should be_false + end + + it "follows symlinks" do + with_tempfile("good_symlink_x.txt", "bad_symlink_x.txt") do |good_path, bad_path| + crystal = Process.executable_path || pending! "Unable to locate compiler executable" + File.symlink(File.expand_path(crystal), good_path) + File.symlink(File.expand_path(datapath("non_existing_file.txt")), bad_path) + + File::Info.executable?(good_path).should be_true + File::Info.executable?(bad_path).should be_false + end + end + end + + describe ".readable?" do + it "gives true" do + File::Info.readable?(datapath("test_file.txt")).should be_true + File.readable?(datapath("test_file.txt")).should be_true # deprecated + end + + it "gives false when the file doesn't exist" do + File::Info.readable?(datapath("non_existing_file.txt")).should be_false + end + + it "gives false when a component of the path is a file" do + File::Info.readable?(datapath("dir", "test_file.txt", "")).should be_false + end + + # win32 doesn't have a way to make files unreadable via chmod + {% unless flag?(:win32) %} + it "gives false when the file has no read permissions" do + with_tempfile("unreadable.txt") do |path| + File.write(path, "") + File.chmod(path, 0o222) + pending_if_superuser! + File::Info.readable?(path).should be_false + end + end + + it "gives false when the file has no permissions" do + with_tempfile("unaccessible.txt") do |path| + File.write(path, "") + File.chmod(path, 0o000) + pending_if_superuser! + File::Info.readable?(path).should be_false + end + end + + it "follows symlinks" do + with_tempfile("good_symlink_r.txt", "bad_symlink_r.txt", "unreadable.txt") do |good_path, bad_path, unreadable| + File.write(unreadable, "") + File.chmod(unreadable, 0o222) + pending_if_superuser! + + File.symlink(File.expand_path(datapath("test_file.txt")), good_path) + File.symlink(File.expand_path(unreadable), bad_path) + + File::Info.readable?(good_path).should be_true + File::Info.readable?(bad_path).should be_false + end + end + {% end %} + + it "gives false when the symbolic link destination doesn't exist" do + with_tempfile("missing_symlink_r.txt") do |missing_path| + File.symlink(File.expand_path(datapath("non_existing_file.txt")), missing_path) + File::Info.readable?(missing_path).should be_false + end + end + end + + describe ".writable?" do + it "gives true" do + File::Info.writable?(datapath("test_file.txt")).should be_true + File.writable?(datapath("test_file.txt")).should be_true # deprecated + end + + it "gives false when the file doesn't exist" do + File::Info.writable?(datapath("non_existing_file.txt")).should be_false + end + + it "gives false when a component of the path is a file" do + File::Info.writable?(datapath("dir", "test_file.txt", "")).should be_false + end + + it "gives false when the file has no write permissions" do + with_tempfile("readonly.txt") do |path| + File.write(path, "") + File.chmod(path, 0o444) + pending_if_superuser! + File::Info.writable?(path).should be_false + end + end + + it "follows symlinks" do + with_tempfile("good_symlink_w.txt", "bad_symlink_w.txt", "readonly.txt") do |good_path, bad_path, readonly| + File.write(readonly, "") + File.chmod(readonly, 0o444) + pending_if_superuser! + + File.symlink(File.expand_path(datapath("test_file.txt")), good_path) + File.symlink(File.expand_path(readonly), bad_path) + + File::Info.writable?(good_path).should be_true + File::Info.writable?(bad_path).should be_false + end + end + + it "gives false when the symbolic link destination doesn't exist" do + with_tempfile("missing_symlink_w.txt") do |missing_path| + File.symlink(File.expand_path(datapath("non_existing_file.txt")), missing_path) + File::Info.writable?(missing_path).should be_false + end + end + end end describe "size" do @@ -1372,15 +1375,15 @@ describe "File" do end it_raises_on_null_byte "readable?" do - File.readable?("foo\0bar") + File::Info.readable?("foo\0bar") end it_raises_on_null_byte "writable?" do - File.writable?("foo\0bar") + File::Info.writable?("foo\0bar") end it_raises_on_null_byte "executable?" do - File.executable?("foo\0bar") + File::Info.executable?("foo\0bar") end it_raises_on_null_byte "file?" do diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index 6bd04ab3e2f2..4cd51bf83075 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -357,7 +357,7 @@ module HTTP test_server("localhost", 0, 0.5.seconds, write_response: false) do |server| client = Client.new("localhost", server.local_address.port) expect_raises(IO::TimeoutError, {% if flag?(:win32) %} "WSARecv timed out" {% else %} "Read timed out" {% end %}) do - client.read_timeout = 0.001 + client.read_timeout = 1.millisecond client.get("/?sleep=1") end end @@ -371,7 +371,7 @@ module HTTP test_server("localhost", 0, 0.seconds, write_response: false) do |server| client = Client.new("localhost", server.local_address.port) expect_raises(IO::TimeoutError, {% if flag?(:win32) %} "WSASend timed out" {% else %} "Write timed out" {% end %}) do - client.write_timeout = 0.001 + client.write_timeout = 1.millisecond client.post("/", body: "a" * 5_000_000) end end @@ -380,7 +380,7 @@ module HTTP it "tests connect_timeout" do test_server("localhost", 0, 0.seconds) do |server| client = Client.new("localhost", server.local_address.port) - client.connect_timeout = 0.5 + client.connect_timeout = 0.5.seconds client.get("/") end end diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index 3980084ea414..3c634d755abf 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -433,7 +433,7 @@ describe HTTP::Server do begin ch.receive client = HTTP::Client.new(address.address, address.port, client_context) - client.read_timeout = client.connect_timeout = 3 + client.read_timeout = client.connect_timeout = 3.seconds client.get("/").body.should eq "ok" ensure ch.send nil diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index c584ec81a1e8..3be5c07e1479 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -105,11 +105,11 @@ describe IO do write.puts "hello" slice = Bytes.new 1024 - read.read_timeout = 1 + read.read_timeout = 1.second read.read(slice).should eq(6) expect_raises(IO::TimeoutError) do - read.read_timeout = 0.0000001 + read.read_timeout = 0.1.microseconds read.read(slice) end end diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index 98555937dea3..f4ff7c90972b 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -79,7 +79,7 @@ describe Socket, tags: "network" do server = Socket.new(Socket::Family::INET, Socket::Type::STREAM, Socket::Protocol::TCP) port = unused_local_port server.bind("0.0.0.0", port) - server.read_timeout = 0.1 + server.read_timeout = 0.1.seconds server.listen expect_raises(IO::TimeoutError) { server.accept } diff --git a/spec/std/socket/unix_socket_spec.cr b/spec/std/socket/unix_socket_spec.cr index c51f37193c0e..b3bc4931ec78 100644 --- a/spec/std/socket/unix_socket_spec.cr +++ b/spec/std/socket/unix_socket_spec.cr @@ -82,8 +82,8 @@ describe UNIXSocket do it "tests read and write timeouts" do UNIXSocket.pair do |left, right| # BUG: shrink the socket buffers first - left.write_timeout = 0.0001 - right.read_timeout = 0.0001 + left.write_timeout = 0.1.milliseconds + right.read_timeout = 0.1.milliseconds buf = ("a" * IO::DEFAULT_BUFFER_SIZE).to_slice expect_raises(IO::TimeoutError, "Write timed out") do From b5f24681038628de57bf42114cb6eb5a6acfa5d2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 10 Sep 2024 14:26:10 +0800 Subject: [PATCH 1351/1551] Fix exponent overflow in `BigFloat#to_s` for very large values (#14982) This is similar to #14971 where the base-10 exponent returned by `LibGMP.mpf_get_str` is not large enough to handle all values internally representable by GMP / MPIR. --- spec/std/big/big_float_spec.cr | 7 +++++ src/big/big_float.cr | 53 +++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index 73c6bcf06de8..1b5e4e3893fc 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -345,6 +345,13 @@ describe "BigFloat" do it { assert_prints (0.1).to_big_f.to_s, "0.100000000000000005551" } it { assert_prints Float64::MAX.to_big_f.to_s, "1.79769313486231570815e+308" } it { assert_prints Float64::MIN_POSITIVE.to_big_f.to_s, "2.22507385850720138309e-308" } + + it { (2.to_big_f ** 7133786264).to_s.should end_with("e+2147483648") } # least power of two with a base-10 exponent greater than Int32::MAX + it { (2.to_big_f ** -7133786264).to_s.should end_with("e-2147483649") } # least power of two with a base-10 exponent less than Int32::MIN + it { (10.to_big_f ** 3000000000 * 1.5).to_s.should end_with("e+3000000000") } + it { (10.to_big_f ** -3000000000 * 1.5).to_s.should end_with("e-3000000000") } + it { (10.to_big_f ** 10000000000 * 1.5).to_s.should end_with("e+10000000000") } + it { (10.to_big_f ** -10000000000 * 1.5).to_s.should end_with("e-10000000000") } end describe "#inspect" do diff --git a/src/big/big_float.cr b/src/big/big_float.cr index ff78b7de8290..f6ab46def36d 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -362,10 +362,12 @@ struct BigFloat < Float end def to_s(io : IO) : Nil - cstr = LibGMP.mpf_get_str(nil, out decimal_exponent, 10, 0, self) + cstr = LibGMP.mpf_get_str(nil, out orig_decimal_exponent, 10, 0, self) length = LibC.strlen(cstr) buffer = Slice.new(cstr, length) + decimal_exponent = fix_exponent_overflow(orig_decimal_exponent) + # add negative sign if buffer[0]? == 45 # '-' io << '-' @@ -415,6 +417,55 @@ struct BigFloat < Float end end + # The same `LibGMP::MpExp` is used in `LibGMP::MPF` to represent a + # `BigFloat`'s exponent in base `256 ** sizeof(LibGMP::MpLimb)`, and to return + # a base-10 exponent in `LibGMP.mpf_get_str`. The latter is around 9.6x the + # former when `MpLimb` is 32-bit, or around 19.3x when `MpLimb` is 64-bit. + # This means the base-10 exponent will overflow for the majority of `MpExp`'s + # domain, even though `BigFloat`s will work correctly in this exponent range + # otherwise. This method exists to recover the original exponent for `#to_s`. + # + # Note that if `MpExp` is 64-bit, which is the case for non-Windows 64-bit + # targets, then `mpf_get_str` will simply crash for values above + # `2 ** 0x1_0000_0000_0000_0080`; here `exponent10` is around 5.553e+18, and + # never overflows. Thus there is no need to check for overflow in that case. + private def fix_exponent_overflow(exponent10) + {% if LibGMP::MpExp == Int64 %} + exponent10 + {% else %} + # When `self` is non-zero, + # + # @mpf.@_mp_exp == Math.log(abs, 256.0 ** sizeof(LibGMP::MpLimb)).floor + 1 + # @mpf.@_mp_exp - 1 <= Math.log(abs, 256.0 ** sizeof(LibGMP::MpLimb)) < @mpf.@_mp_exp + # @mpf.@_mp_exp - 1 <= Math.log10(abs) / Math.log10(256.0 ** sizeof(LibGMP::MpLimb)) < @mpf.@_mp_exp + # Math.log10(abs) >= (@mpf.@_mp_exp - 1) * Math.log10(256.0 ** sizeof(LibGMP::MpLimb)) + # Math.log10(abs) < @mpf.@_mp_exp * Math.log10(256.0 ** sizeof(LibGMP::MpLimb)) + # + # And also, + # + # exponent10 == Math.log10(abs).floor + 1 + # exponent10 - 1 <= Math.log10(abs) < exponent10 + # + # When `exponent10` overflows, it differs from its real value by an + # integer multiple of `256.0 ** sizeof(LibGMP::MpExp)`. We have to recover + # the integer `overflow_n` such that: + # + # LibGMP::MpExp::MIN <= exponent10 <= LibGMP::MpExp::MAX + # Math.log10(abs) ~= exponent10 + overflow_n * 256.0 ** sizeof(LibGMP::MpExp) + # ~= @mpf.@_mp_exp * Math.log10(256.0 ** sizeof(LibGMP::MpLimb)) + # + # Because the possible intervals for the real `exponent10` are so far apart, + # it suffices to approximate `overflow_n` as follows: + # + # overflow_n ~= (@mpf.@_mp_exp * Math.log10(256.0 ** sizeof(LibGMP::MpLimb)) - exponent10) / 256.0 ** sizeof(LibGMP::MpExp) + # + # This value will be very close to an integer, which we then obtain with + # `#round`. + overflow_n = ((@mpf.@_mp_exp * Math.log10(256.0 ** sizeof(LibGMP::MpLimb)) - exponent10) / 256.0 ** sizeof(LibGMP::MpExp)) + exponent10.to_i64 + overflow_n.round.to_i64 * (256_i64 ** sizeof(LibGMP::MpExp)) + {% end %} + end + def clone self end From ce76bf573f3dadba4c936bba54cc3a4b10d410bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 10 Sep 2024 08:26:24 +0200 Subject: [PATCH 1352/1551] Add type restriction to `String#byte_index` `offset` parameter (#14981) The return type is `Int32?`, so `offset` must be `Int32` as well. --- src/string.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/string.cr b/src/string.cr index 35c33b903939..f0dbd1a1eae3 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3715,7 +3715,7 @@ class String # "Dizzy Miss Lizzy".byte_index('z'.ord, -4) # => 13 # "Dizzy Miss Lizzy".byte_index('z'.ord, -17) # => nil # ``` - def byte_index(byte : Int, offset = 0) : Int32? + def byte_index(byte : Int, offset : Int32 = 0) : Int32? offset += bytesize if offset < 0 return if offset < 0 From fdbaeb4c3dec8e1174eb22310602119cab0f5d4f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 11 Sep 2024 05:54:01 +0800 Subject: [PATCH 1353/1551] Implement floating-point manipulation functions for `BigFloat` (#11007) `BigFloat` already has an implementation of `frexp` because `hash` needs it (I think); this PR adds the remaining floating-point manipulation operations. Methods that take an additional integer accept an `Int` directly, so this takes care of the allowed conversions in #10907. `BigFloat` from GMP doesn't have the notion of signed zeros, so `copysign` is only an approximation. (As usual, MPFR floats feature signed zeros.) --- spec/std/big/big_float_spec.cr | 54 ++++++++++++++++++++++++++++++++++ src/big/big_float.cr | 52 ++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index 1b5e4e3893fc..23c782aa3de8 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -554,6 +554,48 @@ describe "BigFloat" do end describe "BigFloat Math" do + it ".ilogb" do + Math.ilogb(0.2.to_big_f).should eq(-3) + Math.ilogb(123.45.to_big_f).should eq(6) + Math.ilogb(2.to_big_f ** 1_000_000_000).should eq(1_000_000_000) + Math.ilogb(2.to_big_f ** 100_000_000_000).should eq(100_000_000_000) + Math.ilogb(2.to_big_f ** -100_000_000_000).should eq(-100_000_000_000) + expect_raises(ArgumentError) { Math.ilogb(0.to_big_f) } + end + + it ".logb" do + Math.logb(0.2.to_big_f).should eq(-3.to_big_f) + Math.logb(123.45.to_big_f).should eq(6.to_big_f) + Math.logb(2.to_big_f ** 1_000_000_000).should eq(1_000_000_000.to_big_f) + Math.logb(2.to_big_f ** 100_000_000_000).should eq(100_000_000_000.to_big_f) + Math.logb(2.to_big_f ** -100_000_000_000).should eq(-100_000_000_000.to_big_f) + expect_raises(ArgumentError) { Math.logb(0.to_big_f) } + end + + it ".ldexp" do + Math.ldexp(0.2.to_big_f, 2).should eq(0.8.to_big_f) + Math.ldexp(0.2.to_big_f, -2).should eq(0.05.to_big_f) + Math.ldexp(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000) + Math.ldexp(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000) + Math.ldexp(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000) + end + + it ".scalbn" do + Math.scalbn(0.2.to_big_f, 2).should eq(0.8.to_big_f) + Math.scalbn(0.2.to_big_f, -2).should eq(0.05.to_big_f) + Math.scalbn(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000) + Math.scalbn(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000) + Math.scalbn(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000) + end + + it ".scalbln" do + Math.scalbln(0.2.to_big_f, 2).should eq(0.8.to_big_f) + Math.scalbln(0.2.to_big_f, -2).should eq(0.05.to_big_f) + Math.scalbln(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000) + Math.scalbln(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000) + Math.scalbln(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000) + end + it ".frexp" do Math.frexp(0.to_big_f).should eq({0.0, 0}) Math.frexp(1.to_big_f).should eq({0.5, 1}) @@ -574,6 +616,18 @@ describe "BigFloat Math" do Math.frexp(-(2.to_big_f ** -0x100000000)).should eq({-0.5, -0xFFFFFFFF}) end + it ".copysign" do + Math.copysign(3.to_big_f, 2.to_big_f).should eq(3.to_big_f) + Math.copysign(3.to_big_f, 0.to_big_f).should eq(3.to_big_f) + Math.copysign(3.to_big_f, -2.to_big_f).should eq(-3.to_big_f) + Math.copysign(0.to_big_f, 2.to_big_f).should eq(0.to_big_f) + Math.copysign(0.to_big_f, 0.to_big_f).should eq(0.to_big_f) + Math.copysign(0.to_big_f, -2.to_big_f).should eq(0.to_big_f) + Math.copysign(-3.to_big_f, 2.to_big_f).should eq(3.to_big_f) + Math.copysign(-3.to_big_f, 0.to_big_f).should eq(3.to_big_f) + Math.copysign(-3.to_big_f, -2.to_big_f).should eq(-3.to_big_f) + end + it ".sqrt" do Math.sqrt(BigFloat.new("1" + "0"*48)).should eq(BigFloat.new("1" + "0"*24)) end diff --git a/src/big/big_float.cr b/src/big/big_float.cr index f6ab46def36d..5a57500fbdd7 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -586,6 +586,47 @@ class String end module Math + # Returns the unbiased base 2 exponent of the given floating-point *value*. + # + # Raises `ArgumentError` if *value* is zero. + def ilogb(value : BigFloat) : Int64 + raise ArgumentError.new "Cannot get exponent of zero" if value.zero? + leading_zeros = value.@mpf._mp_d[value.@mpf._mp_size.abs - 1].leading_zeros_count + 8_i64 * sizeof(LibGMP::MpLimb) * value.@mpf._mp_exp - leading_zeros - 1 + end + + # Returns the unbiased radix-independent exponent of the given floating-point *value*. + # + # For `BigFloat` this is equivalent to `ilogb`. + # + # Raises `ArgumentError` is *value* is zero. + def logb(value : BigFloat) : BigFloat + ilogb(value).to_big_f + end + + # Multiplies the given floating-point *value* by 2 raised to the power *exp*. + def ldexp(value : BigFloat, exp : Int) : BigFloat + BigFloat.new do |mpf| + if exp >= 0 + LibGMP.mpf_mul_2exp(mpf, value, exp.to_u64) + else + LibGMP.mpf_div_2exp(mpf, value, exp.abs.to_u64) + end + end + end + + # Returns the floating-point *value* with its exponent raised by *exp*. + # + # For `BigFloat` this is equivalent to `ldexp`. + def scalbn(value : BigFloat, exp : Int) : BigFloat + ldexp(value, exp) + end + + # :ditto: + def scalbln(value : BigFloat, exp : Int) : BigFloat + ldexp(value, exp) + end + # Decomposes the given floating-point *value* into a normalized fraction and an integral power of two. def frexp(value : BigFloat) : {BigFloat, Int64} return {BigFloat.zero, 0_i64} if value.zero? @@ -608,6 +649,17 @@ module Math {frac, exp} end + # Returns the floating-point value with the magnitude of *value1* and the sign of *value2*. + # + # `BigFloat` does not support signed zeros; if `value2 == 0`, the returned value is non-negative. + def copysign(value1 : BigFloat, value2 : BigFloat) : BigFloat + if value1.negative? != value2.negative? # opposite signs + -value1 + else + value1 + end + end + # Calculates the square root of *value*. # # ``` From 8b1a3916fb702c8f55895139d2984d256288f13a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 11 Sep 2024 05:54:21 +0800 Subject: [PATCH 1354/1551] Remove TODO in `Crystal::Loader` on Windows (#14988) This has been addressed by #14146 from outside the loader, and there is essentially only one TODO left for #11575. --- src/compiler/crystal/loader/msvc.cr | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index 772e4c5c232f..966f6ec5d246 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -185,7 +185,6 @@ class Crystal::Loader end private def open_library(path : String) - # TODO: respect `@[Link(dll:)]`'s search order LibC.LoadLibraryExW(System.to_wstr(path), nil, 0) end From dea39ad408d06f8c3d9017e1769c8726e67c012e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 13 Sep 2024 18:38:41 +0800 Subject: [PATCH 1355/1551] Async DNS resolution on Windows (#14979) Uses `GetAddrInfoExW` with IOCP --- spec/std/socket/addrinfo_spec.cr | 32 +++++++++-- src/crystal/system/addrinfo.cr | 6 +- src/crystal/system/win32/addrinfo.cr | 43 ++++++++++++--- src/crystal/system/win32/addrinfo_win7.cr | 61 +++++++++++++++++++++ src/crystal/system/win32/iocp.cr | 36 ++++++++++++ src/http/client.cr | 8 +-- src/lib_c/x86_64-windows-msvc/c/winsock2.cr | 7 +++ src/lib_c/x86_64-windows-msvc/c/ws2def.cr | 14 +++++ src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr | 20 +++++++ src/socket/addrinfo.cr | 30 ++++++---- src/socket/tcp_socket.cr | 2 +- src/winerror.cr | 5 +- 12 files changed, 233 insertions(+), 31 deletions(-) create mode 100644 src/crystal/system/win32/addrinfo_win7.cr diff --git a/spec/std/socket/addrinfo_spec.cr b/spec/std/socket/addrinfo_spec.cr index 615058472525..109eb383562b 100644 --- a/spec/std/socket/addrinfo_spec.cr +++ b/spec/std/socket/addrinfo_spec.cr @@ -22,6 +22,20 @@ describe Socket::Addrinfo, tags: "network" do end end end + + it "raises helpful message on getaddrinfo failure" do + expect_raises(Socket::Addrinfo::Error, "Hostname lookup for badhostname failed: ") do + Socket::Addrinfo.resolve("badhostname", 80, type: Socket::Type::DGRAM) + end + end + + {% if flag?(:win32) %} + it "raises timeout error" do + expect_raises(IO::TimeoutError) do + Socket::Addrinfo.resolve("badhostname", 80, type: Socket::Type::STREAM, timeout: 0.milliseconds) + end + end + {% end %} end describe ".tcp" do @@ -37,11 +51,13 @@ describe Socket::Addrinfo, tags: "network" do end end - it "raises helpful message on getaddrinfo failure" do - expect_raises(Socket::Addrinfo::Error, "Hostname lookup for badhostname failed: ") do - Socket::Addrinfo.resolve("badhostname", 80, type: Socket::Type::DGRAM) + {% if flag?(:win32) %} + it "raises timeout error" do + expect_raises(IO::TimeoutError) do + Socket::Addrinfo.tcp("badhostname", 80, timeout: 0.milliseconds) + end end - end + {% end %} end describe ".udp" do @@ -56,6 +72,14 @@ describe Socket::Addrinfo, tags: "network" do typeof(addrinfo).should eq(Socket::Addrinfo) end end + + {% if flag?(:win32) %} + it "raises timeout error" do + expect_raises(IO::TimeoutError) do + Socket::Addrinfo.udp("badhostname", 80, timeout: 0.milliseconds) + end + end + {% end %} end describe "#ip_address" do diff --git a/src/crystal/system/addrinfo.cr b/src/crystal/system/addrinfo.cr index 23513e6f763e..ff9166f3aca1 100644 --- a/src/crystal/system/addrinfo.cr +++ b/src/crystal/system/addrinfo.cr @@ -30,7 +30,11 @@ end {% elsif flag?(:unix) %} require "./unix/addrinfo" {% elsif flag?(:win32) %} - require "./win32/addrinfo" + {% if flag?(:win7) %} + require "./win32/addrinfo_win7" + {% else %} + require "./win32/addrinfo" + {% end %} {% else %} {% raise "No Crystal::System::Addrinfo implementation available" %} {% end %} diff --git a/src/crystal/system/win32/addrinfo.cr b/src/crystal/system/win32/addrinfo.cr index b033d61f16e7..91ebb1620a43 100644 --- a/src/crystal/system/win32/addrinfo.cr +++ b/src/crystal/system/win32/addrinfo.cr @@ -1,5 +1,5 @@ module Crystal::System::Addrinfo - alias Handle = LibC::Addrinfo* + alias Handle = LibC::ADDRINFOEXW* @addr : LibC::SockaddrIn6 @@ -30,7 +30,7 @@ module Crystal::System::Addrinfo end def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle - hints = LibC::Addrinfo.new + hints = LibC::ADDRINFOEXW.new hints.ai_family = (family || ::Socket::Family::UNSPEC).to_i32 hints.ai_socktype = type hints.ai_protocol = protocol @@ -43,12 +43,39 @@ module Crystal::System::Addrinfo end end - ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr) - unless ret.zero? - error = WinError.new(ret.to_u32!) - raise ::Socket::Addrinfo::Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service) + Crystal::IOCP::GetAddrInfoOverlappedOperation.run(Crystal::EventLoop.current.iocp) do |operation| + completion_routine = LibC::LPLOOKUPSERVICE_COMPLETION_ROUTINE.new do |dwError, dwBytes, lpOverlapped| + orig_operation = Crystal::IOCP::GetAddrInfoOverlappedOperation.unbox(lpOverlapped) + LibC.PostQueuedCompletionStatus(orig_operation.iocp, 0, 0, lpOverlapped) + end + + # NOTE: we handle the timeout ourselves so we don't pass a `LibC::Timeval` + # to Win32 here + result = LibC.GetAddrInfoExW( + Crystal::System.to_wstr(domain), Crystal::System.to_wstr(service.to_s), LibC::NS_DNS, nil, pointerof(hints), + out addrinfos, nil, operation, completion_routine, out cancel_handle) + + if result == 0 + return addrinfos + else + case error = WinError.new(result.to_u32!) + when .wsa_io_pending? + # used in `Crystal::IOCP::OverlappedOperation#try_cancel_getaddrinfo` + operation.cancel_handle = cancel_handle + else + raise ::Socket::Addrinfo::Error.from_os_error("GetAddrInfoExW", error, domain: domain, type: type, protocol: protocol, service: service) + end + end + + operation.wait_for_result(timeout) do |error| + case error + when .wsa_e_cancelled? + raise IO::TimeoutError.new("GetAddrInfoExW timed out") + else + raise ::Socket::Addrinfo::Error.from_os_error("GetAddrInfoExW", error, domain: domain, type: type, protocol: protocol, service: service) + end + end end - ptr end def self.next_addrinfo(addrinfo : Handle) : Handle @@ -56,6 +83,6 @@ module Crystal::System::Addrinfo end def self.free_addrinfo(addrinfo : Handle) - LibC.freeaddrinfo(addrinfo) + LibC.FreeAddrInfoExW(addrinfo) end end diff --git a/src/crystal/system/win32/addrinfo_win7.cr b/src/crystal/system/win32/addrinfo_win7.cr new file mode 100644 index 000000000000..b033d61f16e7 --- /dev/null +++ b/src/crystal/system/win32/addrinfo_win7.cr @@ -0,0 +1,61 @@ +module Crystal::System::Addrinfo + alias Handle = LibC::Addrinfo* + + @addr : LibC::SockaddrIn6 + + protected def initialize(addrinfo : Handle) + @family = ::Socket::Family.from_value(addrinfo.value.ai_family) + @type = ::Socket::Type.from_value(addrinfo.value.ai_socktype) + @protocol = ::Socket::Protocol.from_value(addrinfo.value.ai_protocol) + @size = addrinfo.value.ai_addrlen.to_i + + @addr = uninitialized LibC::SockaddrIn6 + + case @family + when ::Socket::Family::INET6 + addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1) + when ::Socket::Family::INET + addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1) + else + # TODO: (asterite) UNSPEC and UNIX unsupported? + end + end + + def system_ip_address : ::Socket::IPAddress + ::Socket::IPAddress.from(to_unsafe, size) + end + + def to_unsafe + pointerof(@addr).as(LibC::Sockaddr*) + end + + def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle + hints = LibC::Addrinfo.new + hints.ai_family = (family || ::Socket::Family::UNSPEC).to_i32 + hints.ai_socktype = type + hints.ai_protocol = protocol + hints.ai_flags = 0 + + if service.is_a?(Int) + hints.ai_flags |= LibC::AI_NUMERICSERV + if service < 0 + raise ::Socket::Addrinfo::Error.from_os_error(nil, WinError::WSATYPE_NOT_FOUND, domain: domain, type: type, protocol: protocol, service: service) + end + end + + ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr) + unless ret.zero? + error = WinError.new(ret.to_u32!) + raise ::Socket::Addrinfo::Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service) + end + ptr + end + + def self.next_addrinfo(addrinfo : Handle) : Handle + addrinfo.value.ai_next + end + + def self.free_addrinfo(addrinfo : Handle) + LibC.freeaddrinfo(addrinfo) + end +end diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr index 384784a193db..19c92c8f8725 100644 --- a/src/crystal/system/win32/iocp.cr +++ b/src/crystal/system/win32/iocp.cr @@ -210,6 +210,42 @@ module Crystal::IOCP end end + class GetAddrInfoOverlappedOperation < OverlappedOperation + getter iocp + setter cancel_handle : LibC::HANDLE = LibC::INVALID_HANDLE_VALUE + + def initialize(@iocp : LibC::HANDLE) + end + + def wait_for_result(timeout, & : WinError ->) + wait_for_completion(timeout) + + result = LibC.GetAddrInfoExOverlappedResult(self) + unless result.zero? + error = WinError.new(result.to_u32!) + yield error + + raise Socket::Addrinfo::Error.from_os_error("GetAddrInfoExOverlappedResult", error) + end + + @overlapped.union.pointer.as(LibC::ADDRINFOEXW**).value + end + + private def try_cancel : Bool + ret = LibC.GetAddrInfoExCancel(pointerof(@cancel_handle)) + unless ret.zero? + case error = WinError.new(ret.to_u32!) + when .wsa_invalid_handle? + # Operation has already completed, do nothing + return false + else + raise Socket::Addrinfo::Error.from_os_error("GetAddrInfoExCancel", error) + end + end + true + end + end + def self.overlapped_operation(file_descriptor, method, timeout, *, offset = nil, writing = false, &) handle = file_descriptor.windows_handle seekable = LibC.SetFilePointerEx(handle, 0, out original_offset, IO::Seek::Current) != 0 diff --git a/src/http/client.cr b/src/http/client.cr index b641065ac930..7324bdf7d639 100644 --- a/src/http/client.cr +++ b/src/http/client.cr @@ -343,10 +343,10 @@ class HTTP::Client # ``` setter connect_timeout : Time::Span? - # **This method has no effect right now** - # # Sets the number of seconds to wait when resolving a name, before raising an `IO::TimeoutError`. # + # NOTE: *dns_timeout* is currently only supported on Windows. + # # ``` # require "http/client" # @@ -363,10 +363,10 @@ class HTTP::Client self.dns_timeout = dns_timeout.seconds end - # **This method has no effect right now** - # # Sets the number of seconds to wait when resolving a name with a `Time::Span`, before raising an `IO::TimeoutError`. # + # NOTE: *dns_timeout* is currently only supported on Windows. + # # ``` # require "http/client" # diff --git a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr index 223c2366b072..68ce6f9ef421 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr @@ -20,6 +20,8 @@ lib LibC lpVendorInfo : Char* end + NS_DNS = 12_u32 + INVALID_SOCKET = ~SOCKET.new(0) SOCKET_ERROR = -1 @@ -111,6 +113,11 @@ lib LibC alias WSAOVERLAPPED_COMPLETION_ROUTINE = Proc(DWORD, DWORD, WSAOVERLAPPED*, DWORD, Void) + struct Timeval + tv_sec : Long + tv_usec : Long + end + struct Linger l_onoff : UShort l_linger : UShort diff --git a/src/lib_c/x86_64-windows-msvc/c/ws2def.cr b/src/lib_c/x86_64-windows-msvc/c/ws2def.cr index 9fc19857f4a3..41e0a1a408eb 100644 --- a/src/lib_c/x86_64-windows-msvc/c/ws2def.cr +++ b/src/lib_c/x86_64-windows-msvc/c/ws2def.cr @@ -208,4 +208,18 @@ lib LibC ai_addr : Sockaddr* ai_next : Addrinfo* end + + struct ADDRINFOEXW + ai_flags : Int + ai_family : Int + ai_socktype : Int + ai_protocol : Int + ai_addrlen : SizeT + ai_canonname : LPWSTR + ai_addr : Sockaddr* + ai_blob : Void* + ai_bloblen : SizeT + ai_provider : GUID* + ai_next : ADDRINFOEXW* + end end diff --git a/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr b/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr index 338063ccf6f6..3b3f61ba7fdb 100644 --- a/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr +++ b/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr @@ -17,4 +17,24 @@ lib LibC fun getaddrinfo(pNodeName : Char*, pServiceName : Char*, pHints : Addrinfo*, ppResult : Addrinfo**) : Int fun inet_ntop(family : Int, pAddr : Void*, pStringBuf : Char*, stringBufSize : SizeT) : Char* fun inet_pton(family : Int, pszAddrString : Char*, pAddrBuf : Void*) : Int + + fun FreeAddrInfoExW(pAddrInfoEx : ADDRINFOEXW*) + + alias LPLOOKUPSERVICE_COMPLETION_ROUTINE = DWORD, DWORD, WSAOVERLAPPED* -> + + fun GetAddrInfoExW( + pName : LPWSTR, + pServiceName : LPWSTR, + dwNameSpace : DWORD, + lpNspId : GUID*, + hints : ADDRINFOEXW*, + ppResult : ADDRINFOEXW**, + timeout : Timeval*, + lpOverlapped : OVERLAPPED*, + lpCompletionRoutine : LPLOOKUPSERVICE_COMPLETION_ROUTINE, + lpHandle : HANDLE*, + ) : Int + + fun GetAddrInfoExOverlappedResult(lpOverlapped : OVERLAPPED*) : Int + fun GetAddrInfoExCancel(lpHandle : HANDLE*) : Int end diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr index cdf55c912601..ef76d0e285b6 100644 --- a/src/socket/addrinfo.cr +++ b/src/socket/addrinfo.cr @@ -23,6 +23,9 @@ class Socket # specified. # - *protocol* is the intended socket protocol (e.g. `Protocol::TCP`) and # should be specified. + # - *timeout* is optional and specifies the maximum time to wait before + # `IO::TimeoutError` is raised. Currently this is only supported on + # Windows. # # Example: # ``` @@ -107,8 +110,11 @@ class Socket "Hostname lookup for #{domain} failed" end - def self.os_error_message(os_error : Errno, *, type, service, protocol, **opts) - case os_error.value + def self.os_error_message(os_error : Errno | WinError, *, type, service, protocol, **opts) + # when `EAI_NONAME` etc. is an integer then only `os_error.value` can + # match; when `EAI_NONAME` is a `WinError` then `os_error` itself can + # match + case os_error.is_a?(Errno) ? os_error.value : os_error when LibC::EAI_NONAME "No address found" when LibC::EAI_SOCKTYPE @@ -116,12 +122,14 @@ class Socket when LibC::EAI_SERVICE "The requested service #{service} is not available for the requested socket type #{type}" else - {% unless flag?(:win32) %} - # There's no need for a special win32 branch because the os_error on Windows - # is of type WinError, which wouldn't match this overload anyways. - - String.new(LibC.gai_strerror(os_error.value)) + # Win32 also has this method, but `WinError` is already sufficient + {% if LibC.has_method?(:gai_strerror) %} + if os_error.is_a?(Errno) + return String.new(LibC.gai_strerror(os_error)) + end {% end %} + + super end end end @@ -148,13 +156,13 @@ class Socket # addrinfos = Socket::Addrinfo.tcp("example.org", 80) # ``` def self.tcp(domain : String, service, family = Family::UNSPEC, timeout = nil) : Array(Addrinfo) - resolve(domain, service, family, Type::STREAM, Protocol::TCP) + resolve(domain, service, family, Type::STREAM, Protocol::TCP, timeout) end # Resolves a domain for the TCP protocol with STREAM type, and yields each # possible `Addrinfo`. See `#resolve` for details. def self.tcp(domain : String, service, family = Family::UNSPEC, timeout = nil, &) - resolve(domain, service, family, Type::STREAM, Protocol::TCP) { |addrinfo| yield addrinfo } + resolve(domain, service, family, Type::STREAM, Protocol::TCP, timeout) { |addrinfo| yield addrinfo } end # Resolves *domain* for the UDP protocol and returns an `Array` of possible @@ -167,13 +175,13 @@ class Socket # addrinfos = Socket::Addrinfo.udp("example.org", 53) # ``` def self.udp(domain : String, service, family = Family::UNSPEC, timeout = nil) : Array(Addrinfo) - resolve(domain, service, family, Type::DGRAM, Protocol::UDP) + resolve(domain, service, family, Type::DGRAM, Protocol::UDP, timeout) end # Resolves a domain for the UDP protocol with DGRAM type, and yields each # possible `Addrinfo`. See `#resolve` for details. def self.udp(domain : String, service, family = Family::UNSPEC, timeout = nil, &) - resolve(domain, service, family, Type::DGRAM, Protocol::UDP) { |addrinfo| yield addrinfo } + resolve(domain, service, family, Type::DGRAM, Protocol::UDP, timeout) { |addrinfo| yield addrinfo } end # Returns an `IPAddress` matching this addrinfo. diff --git a/src/socket/tcp_socket.cr b/src/socket/tcp_socket.cr index 387417211a1a..4edcb3d08e5f 100644 --- a/src/socket/tcp_socket.cr +++ b/src/socket/tcp_socket.cr @@ -25,7 +25,7 @@ class TCPSocket < IPSocket # connection time to the remote server with `connect_timeout`. Both values # must be in seconds (integers or floats). # - # Note that `dns_timeout` is currently ignored. + # NOTE: *dns_timeout* is currently only supported on Windows. def initialize(host : String, port, dns_timeout = nil, connect_timeout = nil, blocking = false) Addrinfo.tcp(host, port, timeout: dns_timeout) do |addrinfo| super(addrinfo.family, addrinfo.type, addrinfo.protocol, blocking) diff --git a/src/winerror.cr b/src/winerror.cr index ab978769d553..fbb2fb553873 100644 --- a/src/winerror.cr +++ b/src/winerror.cr @@ -2305,6 +2305,7 @@ enum WinError : UInt32 ERROR_STATE_CONTAINER_NAME_SIZE_LIMIT_EXCEEDED = 15818_u32 ERROR_API_UNAVAILABLE = 15841_u32 - WSA_IO_PENDING = ERROR_IO_PENDING - WSA_IO_INCOMPLETE = ERROR_IO_INCOMPLETE + WSA_IO_PENDING = ERROR_IO_PENDING + WSA_IO_INCOMPLETE = ERROR_IO_INCOMPLETE + WSA_INVALID_HANDLE = ERROR_INVALID_HANDLE end From 833d90fc7fc0e8dd1f3ccc4b3084c9c8f0849922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 13 Sep 2024 12:39:03 +0200 Subject: [PATCH 1356/1551] Add error handling for linker flag sub commands (#14932) Co-authored-by: Quinton Miller --- src/compiler/crystal/compiler.cr | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 38880ee9ed64..f25713c6385e 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -411,7 +411,24 @@ module Crystal if program.has_flag? "msvc" lib_flags = program.lib_flags # Execute and expand `subcommands`. - lib_flags = lib_flags.gsub(/`(.*?)`/) { `#{$1}` } if expand + if expand + lib_flags = lib_flags.gsub(/`(.*?)`/) do + command = $1 + begin + error_io = IO::Memory.new + output = Process.run(command, shell: true, output: :pipe, error: error_io) do |process| + process.output.gets_to_end + end + unless $?.success? + error_io.rewind + error "Error executing subcommand for linker flags: #{command.inspect}: #{error_io}" + end + output + rescue exc + error "Error executing subcommand for linker flags: #{command.inspect}: #{exc}" + end + end + end object_arg = Process.quote_windows(object_names) output_arg = Process.quote_windows("/Fe#{output_filename}") From 5125f6619f7c5ec0f9825463a87830723ccd5269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Mon, 16 Sep 2024 08:12:48 -0300 Subject: [PATCH 1357/1551] Avoid unwinding the stack on hot path in method call lookups (#15002) The method lookup code uses exceptions to retry lookups using auto-casting. This is effectively using an exception for execution control, which is not what they are intended for. On `raise`, Crystal will try to unwind the call stack and save it to be able to report the original place where the exception was thrown, and this is a very expensive operation. To avoid that, we initialize the callstack of the special `RetryLookupWithLiterals` exception class always with the same fixed value. --- src/compiler/crystal/semantic/call.cr | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index f581ea79d577..e265829a919e 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -13,6 +13,11 @@ class Crystal::Call property? uses_with_scope = false class RetryLookupWithLiterals < ::Exception + @@dummy_call_stack = Exception::CallStack.new + + def initialize + self.callstack = @@dummy_call_stack + end end def program From 849f71e2aa97453a45229df346c9127ecddaf5dc Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 16 Sep 2024 19:39:12 +0800 Subject: [PATCH 1358/1551] Drop the non-release Windows compiler artifact (#15000) Since #14964 we're building the compiler in release mode on every workflow run. We can use that build instead of the non-release build for workflow jobs that need a compiler. --- .github/workflows/win.yml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 568828b17bee..a256a6806a3f 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -214,16 +214,16 @@ jobs: if: steps.cache-llvm-dlls.outputs.cache-hit != 'true' run: .\etc\win-ci\build-llvm.ps1 -BuildTree deps\llvm -Version ${{ env.CI_LLVM_VERSION }} -TargetsToBuild X86,AArch64 -Dynamic - x86_64-windows: + x86_64-windows-release: needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm-libs, x86_64-windows-llvm-dlls] uses: ./.github/workflows/win_build_portable.yml with: - release: false + release: true llvm_version: "18.1.1" x86_64-windows-test: runs-on: windows-2022 - needs: [x86_64-windows] + needs: [x86_64-windows-release] steps: - name: Disable CRLF line ending substitution run: | @@ -238,7 +238,7 @@ jobs: - name: Download Crystal executable uses: actions/download-artifact@v4 with: - name: crystal + name: crystal-release path: build - name: Restore LLVM @@ -266,13 +266,6 @@ jobs: - name: Build samples run: make -f Makefile.win samples - x86_64-windows-release: - needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm-libs, x86_64-windows-llvm-dlls] - uses: ./.github/workflows/win_build_portable.yml - with: - release: true - llvm_version: "18.1.1" - x86_64-windows-test-interpreter: runs-on: windows-2022 needs: [x86_64-windows-release] From 9134f9f3ba27b099ebbf048b20560c473cdeb87f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 16 Sep 2024 19:56:21 +0800 Subject: [PATCH 1359/1551] Fix `Process.exec` stream redirection on Windows (#14986) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following should print the compiler help message to the file `foo.txt`: ```crystal File.open("foo.txt", "w") do |f| Process.exec("crystal", output: f) end ``` It used to work on Windows in Crystal 1.12, but is now broken since 1.13. This is because `LibC._wexecvp` only inherits file descriptors in the C runtime, not arbitrary Win32 file handles; since we stopped calling `LibC._open_osfhandle`, the C runtime knows nothing about any reopened standard streams in Win32. Thus the above merely prints the help message to the standard output. This PR creates the missing C file descriptors right before `LibC._wexecvp`. It also fixes a different regression of #14947 where reconfiguring `STDIN.blocking` always fails. Co-authored-by: Johannes Müller --- spec/std/process_spec.cr | 21 ++++++++++ src/crystal/system/win32/process.cr | 59 +++++++++++++++++++-------- src/lib_c/x86_64-windows-msvc/c/io.cr | 5 ++- 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index d41ee0bed242..01a154ccb010 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -484,6 +484,27 @@ describe Process do {% end %} describe ".exec" do + it "redirects STDIN and STDOUT to files", tags: %w[slow] do + with_tempfile("crystal-exec-stdin", "crystal-exec-stdout") do |stdin_path, stdout_path| + File.write(stdin_path, "foobar") + + status, _, _ = compile_and_run_source <<-CRYSTAL + command = #{stdin_to_stdout_command[0].inspect} + args = #{stdin_to_stdout_command[1].to_a} of String + stdin_path = #{stdin_path.inspect} + stdout_path = #{stdout_path.inspect} + File.open(stdin_path) do |input| + File.open(stdout_path, "w") do |output| + Process.exec(command, args, input: input, output: output) + end + end + CRYSTAL + + status.success?.should be_true + File.read(stdout_path).chomp.should eq("foobar") + end + end + it "gets error from exec" do expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do Process.exec("foobarbaz") diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 2c6d81720636..7031654d2299 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -326,9 +326,9 @@ struct Crystal::System::Process end private def self.try_replace(command_args, env, clear_env, input, output, error, chdir) - reopen_io(input, ORIGINAL_STDIN) - reopen_io(output, ORIGINAL_STDOUT) - reopen_io(error, ORIGINAL_STDERR) + old_input_fd = reopen_io(input, ORIGINAL_STDIN) + old_output_fd = reopen_io(output, ORIGINAL_STDOUT) + old_error_fd = reopen_io(error, ORIGINAL_STDERR) ENV.clear if clear_env env.try &.each do |key, val| @@ -351,11 +351,18 @@ struct Crystal::System::Process argv << Pointer(LibC::WCHAR).null LibC._wexecvp(command, argv) + + # exec failed; restore the original C runtime file descriptors + errno = Errno.value + LibC._dup2(old_input_fd, 0) + LibC._dup2(old_output_fd, 1) + LibC._dup2(old_error_fd, 2) + errno end def self.replace(command_args, env, clear_env, input, output, error, chdir) : NoReturn - try_replace(command_args, env, clear_env, input, output, error, chdir) - raise_exception_from_errno(command_args.is_a?(String) ? command_args : command_args[0]) + errno = try_replace(command_args, env, clear_env, input, output, error, chdir) + raise_exception_from_errno(command_args.is_a?(String) ? command_args : command_args[0], errno) end private def self.raise_exception_from_errno(command, errno = Errno.value) @@ -367,21 +374,41 @@ struct Crystal::System::Process end end + # Replaces the C standard streams' file descriptors, not Win32's, since + # `try_replace` uses the C `LibC._wexecvp` and only cares about the former. + # Returns a duplicate of the original file descriptor private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor) - src_io = to_real_fd(src_io) + unless src_io.system_blocking? + raise IO::Error.new("Non-blocking streams are not supported in `Process.exec`", target: src_io) + end - dst_io.reopen(src_io) - dst_io.blocking = true - dst_io.close_on_exec = false - end + src_fd = + case src_io + when STDIN then 0 + when STDOUT then 1 + when STDERR then 2 + else + LibC._open_osfhandle(src_io.windows_handle, 0) + end - private def self.to_real_fd(fd : IO::FileDescriptor) - case fd - when STDIN then ORIGINAL_STDIN - when STDOUT then ORIGINAL_STDOUT - when STDERR then ORIGINAL_STDERR - else fd + dst_fd = + case dst_io + when ORIGINAL_STDIN then 0 + when ORIGINAL_STDOUT then 1 + when ORIGINAL_STDERR then 2 + else + raise "BUG: Invalid destination IO" + end + + return src_fd if dst_fd == src_fd + + orig_src_fd = LibC._dup(src_fd) + + if LibC._dup2(src_fd, dst_fd) == -1 + raise IO::Error.from_errno("Failed to replace C file descriptor", target: dst_io) end + + orig_src_fd end def self.chroot(path) diff --git a/src/lib_c/x86_64-windows-msvc/c/io.cr b/src/lib_c/x86_64-windows-msvc/c/io.cr index 75da8c18e5b9..ccbaa15f2d1b 100644 --- a/src/lib_c/x86_64-windows-msvc/c/io.cr +++ b/src/lib_c/x86_64-windows-msvc/c/io.cr @@ -2,12 +2,13 @@ require "c/stdint" lib LibC fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT + fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int + fun _dup(fd : Int) : Int + fun _dup2(fd1 : Int, fd2 : Int) : Int # unused - fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int fun _get_osfhandle(fd : Int) : IntPtrT fun _close(fd : Int) : Int - fun _dup2(fd1 : Int, fd2 : Int) : Int fun _isatty(fd : Int) : Int fun _write(fd : Int, buffer : UInt8*, count : UInt) : Int fun _read(fd : Int, buffer : UInt8*, count : UInt) : Int From 5c18900fb7fa65bfb6cdb4da1f3d763700022b8c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 17 Sep 2024 18:47:36 +0800 Subject: [PATCH 1360/1551] Use Cygwin to build libiconv on Windows CI (#14999) This uses the official releases and build instructions. To compile code with this patch using a Windows Crystal compiler without this patch, either the new library files (`lib\iconv.lib`, `lib\iconv-dynamic.lib`, `iconv-2.dll`) shall be copied to that existing Crystal installation, or `CRYSTAL_LIBRARY_PATH` shall include the new `lib` directory so that the `@[Link]` annotation will pick up the new `iconv-2.dll` on program builds. Otherwise, compiled programs will continue to look for the old `libiconv.dll`, and silently break if it is not in `%PATH%` (which is hopefully rare since most of the time Crystal itself is also in `%PATH%`). Cygwin's location is currently hardcoded to `C:\cygwin64`, the default installation location for 64-bit Cygwin. Cygwin itself doesn't have native ARM64 support, but cross-compilation should be possible by simply using the x64-to-ARM64 cross tools MSVC developer prompt on an ARM64 machine. --- .github/workflows/win.yml | 20 ++++++++-- .github/workflows/win_build_portable.yml | 7 +++- etc/win-ci/build-iconv.ps1 | 47 +++++------------------- etc/win-ci/cygwin-build-iconv.sh | 32 ++++++++++++++++ src/crystal/lib_iconv.cr | 2 +- 5 files changed, 66 insertions(+), 42 deletions(-) create mode 100644 etc/win-ci/cygwin-build-iconv.sh diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index a256a6806a3f..d4b9316ef1a2 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -21,6 +21,13 @@ jobs: - name: Enable Developer Command Prompt uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 + - name: Set up Cygwin + uses: cygwin/cygwin-install-action@006ad0b0946ca6d0a3ea2d4437677fa767392401 # v4 + with: + packages: make + install-dir: C:\cygwin64 + add-to-path: false + - name: Download Crystal source uses: actions/checkout@v4 @@ -50,7 +57,7 @@ jobs: run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.43 - name: Build libiconv if: steps.cache-libs.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv + run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Version 1.17 - name: Build libffi if: steps.cache-libs.outputs.cache-hit != 'true' run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.3 @@ -93,6 +100,13 @@ jobs: - name: Enable Developer Command Prompt uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 + - name: Set up Cygwin + uses: cygwin/cygwin-install-action@006ad0b0946ca6d0a3ea2d4437677fa767392401 # v4 + with: + packages: make + install-dir: C:\cygwin64 + add-to-path: false + - name: Download Crystal source uses: actions/checkout@v4 @@ -112,7 +126,7 @@ jobs: libs/xml2-dynamic.lib dlls/pcre.dll dlls/pcre2-8.dll - dlls/libiconv.dll + dlls/iconv-2.dll dlls/gc.dll dlls/libffi.dll dlls/zlib1.dll @@ -131,7 +145,7 @@ jobs: run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.43 -Dynamic - name: Build libiconv if: steps.cache-dlls.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Dynamic + run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Version 1.17 -Dynamic - name: Build libffi if: steps.cache-dlls.outputs.cache-hit != 'true' run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.3 -Dynamic diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 12ee17da9e68..98c428ee5bad 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -23,6 +23,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 + id: install-crystal with: crystal: "1.13.2" @@ -68,7 +69,7 @@ jobs: libs/xml2-dynamic.lib dlls/pcre.dll dlls/pcre2-8.dll - dlls/libiconv.dll + dlls/iconv-2.dll dlls/gc.dll dlls/libffi.dll dlls/zlib1.dll @@ -107,6 +108,10 @@ jobs: run: | echo "CRYSTAL_LIBRARY_PATH=$(pwd)\libs" >> ${env:GITHUB_ENV} echo "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" >> ${env:GITHUB_ENV} + # NOTE: the name of the libiconv DLL has changed, so we manually copy + # the new one to the existing Crystal installation; remove after + # updating the base compiler to 1.14 + cp dlls/iconv-2.dll ${{ steps.install-crystal.outputs.path }} - name: Build LLVM extensions run: make -f Makefile.win deps diff --git a/etc/win-ci/build-iconv.ps1 b/etc/win-ci/build-iconv.ps1 index 56d0417bd729..541066c6327f 100644 --- a/etc/win-ci/build-iconv.ps1 +++ b/etc/win-ci/build-iconv.ps1 @@ -1,47 +1,20 @@ param( [Parameter(Mandatory)] [string] $BuildTree, + [Parameter(Mandatory)] [string] $Version, [switch] $Dynamic ) . "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" [void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) -Setup-Git -Path $BuildTree -Url https://github.com/pffang/libiconv-for-Windows.git -Ref 1353455a6c4e15c9db6865fd9c2bf7203b59c0ec # master@{2022-10-11} +Invoke-WebRequest "https://ftp.gnu.org/pub/gnu/libiconv/libiconv-${Version}.tar.gz" -OutFile libiconv.tar.gz +tar -xzf libiconv.tar.gz +mv libiconv-* $BuildTree +rm libiconv.tar.gz Run-InDirectory $BuildTree { - Replace-Text libiconv\include\iconv.h '__declspec (dllimport) ' '' - - echo ' - - $(MsbuildThisFileDirectory)\Override.props - - ' > 'Directory.Build.props' - - echo " - - false - - - - None - false - - - false - - - - - MultiThreadedDLL - - - " > 'Override.props' - - if ($Dynamic) { - MSBuild.exe /p:Platform=x64 /p:Configuration=Release libiconv.vcxproj - } else { - MSBuild.exe /p:Platform=x64 /p:Configuration=ReleaseStatic libiconv.vcxproj - } + $env:CHERE_INVOKING = 1 + & 'C:\cygwin64\bin\bash.exe' --login "$PSScriptRoot\cygwin-build-iconv.sh" "$Version" "$(if ($Dynamic) { 1 })" if (-not $?) { Write-Host "Error: Failed to build libiconv" -ForegroundColor Red Exit 1 @@ -49,8 +22,8 @@ Run-InDirectory $BuildTree { } if ($Dynamic) { - mv -Force $BuildTree\output\x64\Release\libiconv.lib libs\iconv-dynamic.lib - mv -Force $BuildTree\output\x64\Release\libiconv.dll dlls\ + mv -Force $BuildTree\iconv\lib\iconv.dll.lib libs\iconv-dynamic.lib + mv -Force $BuildTree\iconv\bin\iconv-2.dll dlls\ } else { - mv -Force $BuildTree\output\x64\ReleaseStatic\libiconvStatic.lib libs\iconv.lib + mv -Force $BuildTree\iconv\lib\iconv.lib libs\ } diff --git a/etc/win-ci/cygwin-build-iconv.sh b/etc/win-ci/cygwin-build-iconv.sh new file mode 100644 index 000000000000..a8507542e646 --- /dev/null +++ b/etc/win-ci/cygwin-build-iconv.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +set -eo pipefail + +Version=$1 +Dynamic=$2 + +export PATH="$(pwd)/build-aux:$PATH" +export CC="$(pwd)/build-aux/compile cl -nologo" +export CXX="$(pwd)/build-aux/compile cl -nologo" +export AR="$(pwd)/build-aux/ar-lib lib" +export LD="link" +export NM="dumpbin -symbols" +export STRIP=":" +export RANLIB=":" +if [ -n "$Dynamic" ]; then + export CFLAGS="-MD" + export CXXFLAGS="-MD" + enable_shared=yes + enable_static=no +else + export CFLAGS="-MT" + export CXXFLAGS="-MT" + enable_shared=no + enable_static=yes +fi +export CPPFLAGS="-D_WIN32_WINNT=_WIN32_WINNT_WIN7 -I$(pwd)/iconv/include" +export LDFLAGS="-L$(pwd)/iconv/lib" + +./configure --host=x86_64-w64-mingw32 --prefix="$(pwd)/iconv" --enable-shared="${enable_shared}" --enable-static="${enable_static}" +make +make install diff --git a/src/crystal/lib_iconv.cr b/src/crystal/lib_iconv.cr index 5f1506758454..07100ff9c1dc 100644 --- a/src/crystal/lib_iconv.cr +++ b/src/crystal/lib_iconv.cr @@ -6,7 +6,7 @@ require "c/stddef" @[Link("iconv")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} - @[Link(dll: "libiconv.dll")] + @[Link(dll: "iconv-2.dll")] {% end %} lib LibIconv type IconvT = Void* From f17b565cd5cd9bd96dbba85d1e94c7d2cacbf21b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 17 Sep 2024 21:17:40 +0200 Subject: [PATCH 1361/1551] Enable runners from `runs-on.com` for Aarch64 CI (#15007) https://runs-on.com is a service that provisions machines on AWS on demand to run workflow jobs. It triggers when a job is tagged as `runs-on` and you can configure what kind of machine you like this to run on. The machine specification could likely use some fine tuning (we can use other instance types, and theoretically 8GB should be sufficient). But that'll be a follow-up. For now we know that this works. We expect this to be more price-efficient setup than renting a fixed server or a CI runner service. This patch also includes an update to the latest base image. The old arm base images were using Crystal 1.0 and some outdated libraries which caused problems on the new runners (#15005). The build images are based on the docker images from [84codes/crystal](https://hub.docker.com/r/84codes/crystal). --- .github/workflows/aarch64.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index da252904fa37..85a8af2c8b37 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -8,13 +8,13 @@ concurrency: jobs: aarch64-musl-build: - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=2cpu-linux-arm64, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source uses: actions/checkout@v4 - name: Build Crystal - uses: docker://jhass/crystal:1.0.0-alpine-build + uses: docker://crystallang/crystal:1.13.2-alpine-84codes-build with: args: make crystal - name: Upload Crystal executable @@ -26,7 +26,7 @@ jobs: src/llvm/ext/llvm_ext.o aarch64-musl-test-stdlib: needs: aarch64-musl-build - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -38,12 +38,12 @@ jobs: - name: Mark downloaded compiler as executable run: chmod +x .build/crystal - name: Run stdlib specs - uses: docker://jhass/crystal:1.0.0-alpine-build + uses: docker://crystallang/crystal:1.13.2-alpine-84codes-build with: - args: make std_spec FLAGS=-Duse_pcre + args: make std_spec aarch64-musl-test-compiler: needs: aarch64-musl-build - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -55,17 +55,17 @@ jobs: - name: Mark downloaded compiler as executable run: chmod +x .build/crystal - name: Run compiler specs - uses: docker://jhass/crystal:1.0.0-alpine-build + uses: docker://crystallang/crystal:1.13.2-alpine-84codes-build with: args: make primitives_spec compiler_spec FLAGS=-Dwithout_ffi aarch64-gnu-build: - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=2cpu-linux-arm64, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source uses: actions/checkout@v4 - name: Build Crystal - uses: docker://jhass/crystal:1.0.0-build + uses: docker://crystallang/crystal:1.13.2-ubuntu-84codes-build with: args: make crystal - name: Upload Crystal executable @@ -77,7 +77,7 @@ jobs: src/llvm/ext/llvm_ext.o aarch64-gnu-test-stdlib: needs: aarch64-gnu-build - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -89,12 +89,12 @@ jobs: - name: Mark downloaded compiler as executable run: chmod +x .build/crystal - name: Run stdlib specs - uses: docker://jhass/crystal:1.0.0-build + uses: docker://crystallang/crystal:1.13.2-ubuntu-84codes-build with: args: make std_spec aarch64-gnu-test-compiler: needs: aarch64-gnu-build - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -106,6 +106,6 @@ jobs: - name: Mark downloaded compiler as executable run: chmod +x .build/crystal - name: Run compiler specs - uses: docker://jhass/crystal:1.0.0-build + uses: docker://crystallang/crystal:1.13.2-ubuntu-84codes-build with: args: make primitives_spec compiler_spec From 80b2484c3fb95cae09bce60cde3930a0df826cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 17 Sep 2024 21:17:40 +0200 Subject: [PATCH 1362/1551] Enable runners from `runs-on.com` for Aarch64 CI (#15007) https://runs-on.com is a service that provisions machines on AWS on demand to run workflow jobs. It triggers when a job is tagged as `runs-on` and you can configure what kind of machine you like this to run on. The machine specification could likely use some fine tuning (we can use other instance types, and theoretically 8GB should be sufficient). But that'll be a follow-up. For now we know that this works. We expect this to be more price-efficient setup than renting a fixed server or a CI runner service. This patch also includes an update to the latest base image. The old arm base images were using Crystal 1.0 and some outdated libraries which caused problems on the new runners (#15005). The build images are based on the docker images from [84codes/crystal](https://hub.docker.com/r/84codes/crystal). --- .github/workflows/aarch64.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index da252904fa37..85a8af2c8b37 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -8,13 +8,13 @@ concurrency: jobs: aarch64-musl-build: - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=2cpu-linux-arm64, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source uses: actions/checkout@v4 - name: Build Crystal - uses: docker://jhass/crystal:1.0.0-alpine-build + uses: docker://crystallang/crystal:1.13.2-alpine-84codes-build with: args: make crystal - name: Upload Crystal executable @@ -26,7 +26,7 @@ jobs: src/llvm/ext/llvm_ext.o aarch64-musl-test-stdlib: needs: aarch64-musl-build - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -38,12 +38,12 @@ jobs: - name: Mark downloaded compiler as executable run: chmod +x .build/crystal - name: Run stdlib specs - uses: docker://jhass/crystal:1.0.0-alpine-build + uses: docker://crystallang/crystal:1.13.2-alpine-84codes-build with: - args: make std_spec FLAGS=-Duse_pcre + args: make std_spec aarch64-musl-test-compiler: needs: aarch64-musl-build - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -55,17 +55,17 @@ jobs: - name: Mark downloaded compiler as executable run: chmod +x .build/crystal - name: Run compiler specs - uses: docker://jhass/crystal:1.0.0-alpine-build + uses: docker://crystallang/crystal:1.13.2-alpine-84codes-build with: args: make primitives_spec compiler_spec FLAGS=-Dwithout_ffi aarch64-gnu-build: - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=2cpu-linux-arm64, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source uses: actions/checkout@v4 - name: Build Crystal - uses: docker://jhass/crystal:1.0.0-build + uses: docker://crystallang/crystal:1.13.2-ubuntu-84codes-build with: args: make crystal - name: Upload Crystal executable @@ -77,7 +77,7 @@ jobs: src/llvm/ext/llvm_ext.o aarch64-gnu-test-stdlib: needs: aarch64-gnu-build - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -89,12 +89,12 @@ jobs: - name: Mark downloaded compiler as executable run: chmod +x .build/crystal - name: Run stdlib specs - uses: docker://jhass/crystal:1.0.0-build + uses: docker://crystallang/crystal:1.13.2-ubuntu-84codes-build with: args: make std_spec aarch64-gnu-test-compiler: needs: aarch64-gnu-build - runs-on: [linux, ARM64] + runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -106,6 +106,6 @@ jobs: - name: Mark downloaded compiler as executable run: chmod +x .build/crystal - name: Run compiler specs - uses: docker://jhass/crystal:1.0.0-build + uses: docker://crystallang/crystal:1.13.2-ubuntu-84codes-build with: args: make primitives_spec compiler_spec From 47cd33b259ac46533f8898bdd70fbd15fa9ab306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 6 Sep 2024 08:19:38 +0200 Subject: [PATCH 1363/1551] Fix use global paths in macro bodies (#14965) Macros inject code into other scopes. Paths are resolved in the expanded scope and there can be namespace conflicts. This fixes non-global paths in macro bodies that expand into uncontrolled scopes where namespaces could clash. This is a fixup for #14282 (released in 1.12.0). --- src/crystal/pointer_linked_list.cr | 4 ++-- src/ecr/macros.cr | 2 +- src/intrinsics.cr | 34 +++++++++++++++--------------- src/json/serialization.cr | 6 +++--- src/number.cr | 8 +++---- src/object.cr | 12 +++++------ src/slice.cr | 6 +++--- src/spec/dsl.cr | 4 ++-- src/spec/helpers/iterate.cr | 8 +++---- src/static_array.cr | 2 +- src/syscall/aarch64-linux.cr | 2 +- src/syscall/arm-linux.cr | 2 +- src/syscall/i386-linux.cr | 2 +- src/syscall/x86_64-linux.cr | 2 +- src/yaml/serialization.cr | 14 ++++++------ 15 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/crystal/pointer_linked_list.cr b/src/crystal/pointer_linked_list.cr index 03109979d662..cde9b0b79ddc 100644 --- a/src/crystal/pointer_linked_list.cr +++ b/src/crystal/pointer_linked_list.cr @@ -7,8 +7,8 @@ struct Crystal::PointerLinkedList(T) module Node macro included - property previous : Pointer(self) = Pointer(self).null - property next : Pointer(self) = Pointer(self).null + property previous : ::Pointer(self) = ::Pointer(self).null + property next : ::Pointer(self) = ::Pointer(self).null end end diff --git a/src/ecr/macros.cr b/src/ecr/macros.cr index 92c02cc4284a..5e051232271b 100644 --- a/src/ecr/macros.cr +++ b/src/ecr/macros.cr @@ -34,7 +34,7 @@ module ECR # ``` macro def_to_s(filename) def to_s(__io__ : IO) : Nil - ECR.embed {{filename}}, "__io__" + ::ECR.embed {{filename}}, "__io__" end end diff --git a/src/intrinsics.cr b/src/intrinsics.cr index c5ae837d8931..7cdc462ce543 100644 --- a/src/intrinsics.cr +++ b/src/intrinsics.cr @@ -179,7 +179,7 @@ end module Intrinsics macro debugtrap - LibIntrinsics.debugtrap + ::LibIntrinsics.debugtrap end def self.pause @@ -191,15 +191,15 @@ module Intrinsics end macro memcpy(dest, src, len, is_volatile) - LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, {{is_volatile}}) + ::LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, {{is_volatile}}) end macro memmove(dest, src, len, is_volatile) - LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, {{is_volatile}}) + ::LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, {{is_volatile}}) end macro memset(dest, val, len, is_volatile) - LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, {{is_volatile}}) + ::LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, {{is_volatile}}) end def self.read_cycle_counter @@ -263,43 +263,43 @@ module Intrinsics end macro countleading8(src, zero_is_undef) - LibIntrinsics.countleading8({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.countleading8({{src}}, {{zero_is_undef}}) end macro countleading16(src, zero_is_undef) - LibIntrinsics.countleading16({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.countleading16({{src}}, {{zero_is_undef}}) end macro countleading32(src, zero_is_undef) - LibIntrinsics.countleading32({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.countleading32({{src}}, {{zero_is_undef}}) end macro countleading64(src, zero_is_undef) - LibIntrinsics.countleading64({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.countleading64({{src}}, {{zero_is_undef}}) end macro countleading128(src, zero_is_undef) - LibIntrinsics.countleading128({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.countleading128({{src}}, {{zero_is_undef}}) end macro counttrailing8(src, zero_is_undef) - LibIntrinsics.counttrailing8({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.counttrailing8({{src}}, {{zero_is_undef}}) end macro counttrailing16(src, zero_is_undef) - LibIntrinsics.counttrailing16({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.counttrailing16({{src}}, {{zero_is_undef}}) end macro counttrailing32(src, zero_is_undef) - LibIntrinsics.counttrailing32({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.counttrailing32({{src}}, {{zero_is_undef}}) end macro counttrailing64(src, zero_is_undef) - LibIntrinsics.counttrailing64({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.counttrailing64({{src}}, {{zero_is_undef}}) end macro counttrailing128(src, zero_is_undef) - LibIntrinsics.counttrailing128({{src}}, {{zero_is_undef}}) + ::LibIntrinsics.counttrailing128({{src}}, {{zero_is_undef}}) end def self.fshl8(a, b, count) : UInt8 @@ -343,14 +343,14 @@ module Intrinsics end macro va_start(ap) - LibIntrinsics.va_start({{ap}}) + ::LibIntrinsics.va_start({{ap}}) end macro va_end(ap) - LibIntrinsics.va_end({{ap}}) + ::LibIntrinsics.va_end({{ap}}) end end macro debugger - Intrinsics.debugtrap + ::Intrinsics.debugtrap end diff --git a/src/json/serialization.cr b/src/json/serialization.cr index b1eb86d15082..15d948f02f40 100644 --- a/src/json/serialization.cr +++ b/src/json/serialization.cr @@ -164,7 +164,7 @@ module JSON private def self.new_from_json_pull_parser(pull : ::JSON::PullParser) instance = allocate instance.initialize(__pull_for_json_serializable: pull) - GC.add_finalizer(instance) if instance.responds_to?(:finalize) + ::GC.add_finalizer(instance) if instance.responds_to?(:finalize) instance end @@ -422,8 +422,8 @@ module JSON # Try to find the discriminator while also getting the raw # string value of the parsed JSON, so then we can pass it # to the final type. - json = String.build do |io| - JSON.build(io) do |builder| + json = ::String.build do |io| + ::JSON.build(io) do |builder| builder.start_object pull.read_object do |key| if key == {{field.id.stringify}} diff --git a/src/number.cr b/src/number.cr index f7c82aa4cded..9d955c065df3 100644 --- a/src/number.cr +++ b/src/number.cr @@ -59,7 +59,7 @@ struct Number # :nodoc: macro expand_div(rhs_types, result_type) {% for rhs in rhs_types %} - @[AlwaysInline] + @[::AlwaysInline] def /(other : {{rhs}}) : {{result_type}} {{result_type}}.new(self) / {{result_type}}.new(other) end @@ -84,7 +84,7 @@ struct Number # [1, 2, 3, 4] of Int64 # : Array(Int64) # ``` macro [](*nums) - Array({{@type}}).build({{nums.size}}) do |%buffer| + ::Array({{@type}}).build({{nums.size}}) do |%buffer| {% for num, i in nums %} %buffer[{{i}}] = {{@type}}.new({{num}}) {% end %} @@ -113,7 +113,7 @@ struct Number # Slice[1_i64, 2_i64, 3_i64, 4_i64] # : Slice(Int64) # ``` macro slice(*nums, read_only = false) - %slice = Slice({{@type}}).new({{nums.size}}, read_only: {{read_only}}) + %slice = ::Slice({{@type}}).new({{nums.size}}, read_only: {{read_only}}) {% for num, i in nums %} %slice.to_unsafe[{{i}}] = {{@type}}.new!({{num}}) {% end %} @@ -139,7 +139,7 @@ struct Number # StaticArray[1_i64, 2_i64, 3_i64, 4_i64] # : StaticArray(Int64) # ``` macro static_array(*nums) - %array = uninitialized StaticArray({{@type}}, {{nums.size}}) + %array = uninitialized ::StaticArray({{@type}}, {{nums.size}}) {% for num, i in nums %} %array.to_unsafe[{{i}}] = {{@type}}.new!({{num}}) {% end %} diff --git a/src/object.cr b/src/object.cr index ba818ac2979e..800736687788 100644 --- a/src/object.cr +++ b/src/object.cr @@ -562,7 +562,7 @@ class Object def {{method_prefix}}\{{name.var.id}} : \{{name.type}} if (value = {{var_prefix}}\{{name.var.id}}).nil? - ::raise NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.var.id}} cannot be nil") + ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.var.id}} cannot be nil") else value end @@ -574,7 +574,7 @@ class Object def {{method_prefix}}\{{name.id}} if (value = {{var_prefix}}\{{name.id}}).nil? - ::raise NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.id}} cannot be nil") + ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.id}} cannot be nil") else value end @@ -1293,7 +1293,7 @@ class Object # wrapper.capitalize # => "Hello" # ``` macro delegate(*methods, to object) - {% if compare_versions(Crystal::VERSION, "1.12.0-dev") >= 0 %} + {% if compare_versions(::Crystal::VERSION, "1.12.0-dev") >= 0 %} {% eq_operators = %w(<= >= == != []= ===) %} {% for method in methods %} {% if method.id.ends_with?('=') && !eq_operators.includes?(method.id.stringify) %} @@ -1427,18 +1427,18 @@ class Object macro def_clone # Returns a copy of `self` with all instance variables cloned. def clone - \{% if @type < Reference && !@type.instance_vars.map(&.type).all? { |t| t == ::Bool || t == ::Char || t == ::Symbol || t == ::String || t < ::Number::Primitive } %} + \{% if @type < ::Reference && !@type.instance_vars.map(&.type).all? { |t| t == ::Bool || t == ::Char || t == ::Symbol || t == ::String || t < ::Number::Primitive } %} exec_recursive_clone do |hash| clone = \{{@type}}.allocate hash[object_id] = clone.object_id clone.initialize_copy(self) - GC.add_finalizer(clone) if clone.responds_to?(:finalize) + ::GC.add_finalizer(clone) if clone.responds_to?(:finalize) clone end \{% else %} clone = \{{@type}}.allocate clone.initialize_copy(self) - GC.add_finalizer(clone) if clone.responds_to?(:finalize) + ::GC.add_finalizer(clone) if clone.responds_to?(:finalize) clone \{% end %} end diff --git a/src/slice.cr b/src/slice.cr index 196a29a768dd..087679d37cb7 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -34,14 +34,14 @@ struct Slice(T) macro [](*args, read_only = false) # TODO: there should be a better way to check this, probably # asking if @type was instantiated or if T is defined - {% if @type.name != "Slice(T)" && T < Number %} + {% if @type.name != "Slice(T)" && T < ::Number %} {{T}}.slice({{args.splat(", ")}}read_only: {{read_only}}) {% else %} - %ptr = Pointer(typeof({{args.splat}})).malloc({{args.size}}) + %ptr = ::Pointer(typeof({{args.splat}})).malloc({{args.size}}) {% for arg, i in args %} %ptr[{{i}}] = {{arg}} {% end %} - Slice.new(%ptr, {{args.size}}, read_only: {{read_only}}) + ::Slice.new(%ptr, {{args.size}}, read_only: {{read_only}}) {% end %} end diff --git a/src/spec/dsl.cr b/src/spec/dsl.cr index 578076b86d69..d712aa59da4f 100644 --- a/src/spec/dsl.cr +++ b/src/spec/dsl.cr @@ -298,8 +298,8 @@ module Spec # If the "log" module is required it is configured to emit no entries by default. def log_setup defined?(::Log) do - if Log.responds_to?(:setup) - Log.setup_from_env(default_level: :none) + if ::Log.responds_to?(:setup) + ::Log.setup_from_env(default_level: :none) end end end diff --git a/src/spec/helpers/iterate.cr b/src/spec/helpers/iterate.cr index be302ebb49c2..7a70f83408ca 100644 --- a/src/spec/helpers/iterate.cr +++ b/src/spec/helpers/iterate.cr @@ -47,7 +47,7 @@ module Spec::Methods # See `.it_iterates` for details. macro assert_iterates_yielding(expected, method, *, infinite = false, tuple = false) %remaining = ({{expected}}).size - %ary = [] of typeof(Enumerable.element_type({{ expected }})) + %ary = [] of typeof(::Enumerable.element_type({{ expected }})) {{ method.id }} do |{% if tuple %}*{% end %}x| if %remaining == 0 if {{ infinite }} @@ -73,11 +73,11 @@ module Spec::Methods # # See `.it_iterates` for details. macro assert_iterates_iterator(expected, method, *, infinite = false) - %ary = [] of typeof(Enumerable.element_type({{ expected }})) + %ary = [] of typeof(::Enumerable.element_type({{ expected }})) %iter = {{ method.id }} ({{ expected }}).size.times do %v = %iter.next - if %v.is_a?(Iterator::Stop) + if %v.is_a?(::Iterator::Stop) # Compare the actual value directly. Since there are less # then expected values, the expectation will fail and raise. %ary.should eq({{ expected }}) @@ -86,7 +86,7 @@ module Spec::Methods %ary << %v end unless {{ infinite }} - %iter.next.should be_a(Iterator::Stop) + %iter.next.should be_a(::Iterator::Stop) end %ary.should eq({{ expected }}) diff --git a/src/static_array.cr b/src/static_array.cr index 2c09e21df166..3d00705bc21a 100644 --- a/src/static_array.cr +++ b/src/static_array.cr @@ -50,7 +50,7 @@ struct StaticArray(T, N) # * `Number.static_array` is a convenient alternative for designating a # specific numerical item type. macro [](*args) - %array = uninitialized StaticArray(typeof({{args.splat}}), {{args.size}}) + %array = uninitialized ::StaticArray(typeof({{args.splat}}), {{args.size}}) {% for arg, i in args %} %array.to_unsafe[{{i}}] = {{arg}} {% end %} diff --git a/src/syscall/aarch64-linux.cr b/src/syscall/aarch64-linux.cr index 5a61e8e7eed8..77b891fe2a7c 100644 --- a/src/syscall/aarch64-linux.cr +++ b/src/syscall/aarch64-linux.cr @@ -334,7 +334,7 @@ module Syscall end macro def_syscall(name, return_type, *args) - @[AlwaysInline] + @[::AlwaysInline] def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} diff --git a/src/syscall/arm-linux.cr b/src/syscall/arm-linux.cr index 97119fc4b3f3..da349dd45301 100644 --- a/src/syscall/arm-linux.cr +++ b/src/syscall/arm-linux.cr @@ -409,7 +409,7 @@ module Syscall end macro def_syscall(name, return_type, *args) - @[AlwaysInline] + @[::AlwaysInline] def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} diff --git a/src/syscall/i386-linux.cr b/src/syscall/i386-linux.cr index 843b2d1fd856..a0f94a51160a 100644 --- a/src/syscall/i386-linux.cr +++ b/src/syscall/i386-linux.cr @@ -445,7 +445,7 @@ module Syscall end macro def_syscall(name, return_type, *args) - @[AlwaysInline] + @[::AlwaysInline] def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} diff --git a/src/syscall/x86_64-linux.cr b/src/syscall/x86_64-linux.cr index 1f01c9226658..5a63b6ee2e1a 100644 --- a/src/syscall/x86_64-linux.cr +++ b/src/syscall/x86_64-linux.cr @@ -368,7 +368,7 @@ module Syscall end macro def_syscall(name, return_type, *args) - @[AlwaysInline] + @[::AlwaysInline] def self.{{name.id}}({{args.splat}}) : {{return_type}} ret = uninitialized {{return_type}} diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr index d5fae8dfe9c0..4a1521469dea 100644 --- a/src/yaml/serialization.cr +++ b/src/yaml/serialization.cr @@ -156,11 +156,11 @@ module YAML # Define a `new` directly in the included type, # so it overloads well with other possible initializes - def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) + def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node) new_from_yaml_node(ctx, node) end - private def self.new_from_yaml_node(ctx : YAML::ParseContext, node : YAML::Nodes::Node) + private def self.new_from_yaml_node(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node) ctx.read_alias(node, self) do |obj| return obj end @@ -170,7 +170,7 @@ module YAML ctx.record_anchor(node, instance) instance.initialize(__context_for_yaml_serializable: ctx, __node_for_yaml_serializable: node) - GC.add_finalizer(instance) if instance.responds_to?(:finalize) + ::GC.add_finalizer(instance) if instance.responds_to?(:finalize) instance end @@ -178,7 +178,7 @@ module YAML # so it can compete with other possible initializes macro inherited - def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) + def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node) new_from_yaml_node(ctx, node) end end @@ -409,17 +409,17 @@ module YAML {% mapping.raise "Mapping argument must be a HashLiteral or a NamedTupleLiteral, not #{mapping.class_name.id}" %} {% end %} - def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) + def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node) ctx.read_alias(node, \{{@type}}) do |obj| return obj end - unless node.is_a?(YAML::Nodes::Mapping) + unless node.is_a?(::YAML::Nodes::Mapping) node.raise "Expected YAML mapping, not #{node.class}" end node.each do |key, value| - next unless key.is_a?(YAML::Nodes::Scalar) && value.is_a?(YAML::Nodes::Scalar) + next unless key.is_a?(::YAML::Nodes::Scalar) && value.is_a?(::YAML::Nodes::Scalar) next unless key.value == {{field.id.stringify}} discriminator_value = value.value From 0f257ef57bdf34f156dd7bb5bcb0624258eb07cb Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 16 Sep 2024 19:56:21 +0800 Subject: [PATCH 1364/1551] Fix `Process.exec` stream redirection on Windows (#14986) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following should print the compiler help message to the file `foo.txt`: ```crystal File.open("foo.txt", "w") do |f| Process.exec("crystal", output: f) end ``` It used to work on Windows in Crystal 1.12, but is now broken since 1.13. This is because `LibC._wexecvp` only inherits file descriptors in the C runtime, not arbitrary Win32 file handles; since we stopped calling `LibC._open_osfhandle`, the C runtime knows nothing about any reopened standard streams in Win32. Thus the above merely prints the help message to the standard output. This PR creates the missing C file descriptors right before `LibC._wexecvp`. It also fixes a different regression of #14947 where reconfiguring `STDIN.blocking` always fails. Co-authored-by: Johannes Müller --- spec/std/process_spec.cr | 21 ++++++++++ src/crystal/system/win32/process.cr | 59 +++++++++++++++++++-------- src/lib_c/x86_64-windows-msvc/c/io.cr | 5 ++- 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index f067d2f5c775..f1437656fffa 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -465,6 +465,27 @@ pending_interpreted describe: Process do {% end %} describe ".exec" do + it "redirects STDIN and STDOUT to files", tags: %w[slow] do + with_tempfile("crystal-exec-stdin", "crystal-exec-stdout") do |stdin_path, stdout_path| + File.write(stdin_path, "foobar") + + status, _, _ = compile_and_run_source <<-CRYSTAL + command = #{stdin_to_stdout_command[0].inspect} + args = #{stdin_to_stdout_command[1].to_a} of String + stdin_path = #{stdin_path.inspect} + stdout_path = #{stdout_path.inspect} + File.open(stdin_path) do |input| + File.open(stdout_path, "w") do |output| + Process.exec(command, args, input: input, output: output) + end + end + CRYSTAL + + status.success?.should be_true + File.read(stdout_path).chomp.should eq("foobar") + end + end + it "gets error from exec" do expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do Process.exec("foobarbaz") diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 05b2ea36584e..1817be9ee27f 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -326,9 +326,9 @@ struct Crystal::System::Process end private def self.try_replace(command_args, env, clear_env, input, output, error, chdir) - reopen_io(input, ORIGINAL_STDIN) - reopen_io(output, ORIGINAL_STDOUT) - reopen_io(error, ORIGINAL_STDERR) + old_input_fd = reopen_io(input, ORIGINAL_STDIN) + old_output_fd = reopen_io(output, ORIGINAL_STDOUT) + old_error_fd = reopen_io(error, ORIGINAL_STDERR) ENV.clear if clear_env env.try &.each do |key, val| @@ -351,11 +351,18 @@ struct Crystal::System::Process argv << Pointer(LibC::WCHAR).null LibC._wexecvp(command, argv) + + # exec failed; restore the original C runtime file descriptors + errno = Errno.value + LibC._dup2(old_input_fd, 0) + LibC._dup2(old_output_fd, 1) + LibC._dup2(old_error_fd, 2) + errno end def self.replace(command_args, env, clear_env, input, output, error, chdir) : NoReturn - try_replace(command_args, env, clear_env, input, output, error, chdir) - raise_exception_from_errno(command_args.is_a?(String) ? command_args : command_args[0]) + errno = try_replace(command_args, env, clear_env, input, output, error, chdir) + raise_exception_from_errno(command_args.is_a?(String) ? command_args : command_args[0], errno) end private def self.raise_exception_from_errno(command, errno = Errno.value) @@ -367,21 +374,41 @@ struct Crystal::System::Process end end + # Replaces the C standard streams' file descriptors, not Win32's, since + # `try_replace` uses the C `LibC._wexecvp` and only cares about the former. + # Returns a duplicate of the original file descriptor private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor) - src_io = to_real_fd(src_io) + unless src_io.system_blocking? + raise IO::Error.new("Non-blocking streams are not supported in `Process.exec`", target: src_io) + end - dst_io.reopen(src_io) - dst_io.blocking = true - dst_io.close_on_exec = false - end + src_fd = + case src_io + when STDIN then 0 + when STDOUT then 1 + when STDERR then 2 + else + LibC._open_osfhandle(src_io.windows_handle, 0) + end - private def self.to_real_fd(fd : IO::FileDescriptor) - case fd - when STDIN then ORIGINAL_STDIN - when STDOUT then ORIGINAL_STDOUT - when STDERR then ORIGINAL_STDERR - else fd + dst_fd = + case dst_io + when ORIGINAL_STDIN then 0 + when ORIGINAL_STDOUT then 1 + when ORIGINAL_STDERR then 2 + else + raise "BUG: Invalid destination IO" + end + + return src_fd if dst_fd == src_fd + + orig_src_fd = LibC._dup(src_fd) + + if LibC._dup2(src_fd, dst_fd) == -1 + raise IO::Error.from_errno("Failed to replace C file descriptor", target: dst_io) end + + orig_src_fd end def self.chroot(path) diff --git a/src/lib_c/x86_64-windows-msvc/c/io.cr b/src/lib_c/x86_64-windows-msvc/c/io.cr index 75da8c18e5b9..ccbaa15f2d1b 100644 --- a/src/lib_c/x86_64-windows-msvc/c/io.cr +++ b/src/lib_c/x86_64-windows-msvc/c/io.cr @@ -2,12 +2,13 @@ require "c/stdint" lib LibC fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT + fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int + fun _dup(fd : Int) : Int + fun _dup2(fd1 : Int, fd2 : Int) : Int # unused - fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int fun _get_osfhandle(fd : Int) : IntPtrT fun _close(fd : Int) : Int - fun _dup2(fd1 : Int, fd2 : Int) : Int fun _isatty(fd : Int) : Int fun _write(fd : Int, buffer : UInt8*, count : UInt) : Int fun _read(fd : Int, buffer : UInt8*, count : UInt) : Int From ced75401dac79f9abfe77e2aa6181a62c1283107 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 25 Aug 2024 19:15:11 +0800 Subject: [PATCH 1365/1551] Fix `String#index` and `#rindex` for `Char::REPLACEMENT` (#14937) If the string consists only of ASCII characters and invalid UTF-8 byte sequences, all the latter should correspond to `Char::REPLACEMENT`, and so `#index` and `#rindex` should detect them, but this was broken since #14461. --- spec/std/string_spec.cr | 6 ++++++ src/string.cr | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 00310bfcbc47..9c4f3cfc5d12 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -945,6 +945,7 @@ describe "String" do it { "日本語".index('本').should eq(1) } it { "bar".index('あ').should be_nil } it { "あいう_えお".index('_').should eq(3) } + it { "xyz\xFFxyz".index('\u{FFFD}').should eq(3) } describe "with offset" do it { "foobarbaz".index('a', 5).should eq(7) } @@ -952,6 +953,8 @@ describe "String" do it { "foo".index('g', 1).should be_nil } it { "foo".index('g', -20).should be_nil } it { "日本語日本語".index('本', 2).should eq(4) } + it { "xyz\xFFxyz".index('\u{FFFD}', 2).should eq(3) } + it { "xyz\xFFxyz".index('\u{FFFD}', 4).should be_nil } # Check offset type it { "foobarbaz".index('a', 5_i64).should eq(7) } @@ -1094,6 +1097,7 @@ describe "String" do it { "foobar".rindex('g').should be_nil } it { "日本語日本語".rindex('本').should eq(4) } it { "あいう_えお".rindex('_').should eq(3) } + it { "xyz\xFFxyz".rindex('\u{FFFD}').should eq(3) } describe "with offset" do it { "bbbb".rindex('b', 2).should eq(2) } @@ -1106,6 +1110,8 @@ describe "String" do it { "faobar".rindex('a', 3).should eq(1) } it { "faobarbaz".rindex('a', -3).should eq(4) } it { "日本語日本語".rindex('本', 3).should eq(1) } + it { "xyz\xFFxyz".rindex('\u{FFFD}', 4).should eq(3) } + it { "xyz\xFFxyz".rindex('\u{FFFD}', 2).should be_nil } # Check offset type it { "bbbb".rindex('b', 2_i64).should eq(2) } diff --git a/src/string.cr b/src/string.cr index d3bc7d6998b2..ab107c454e8c 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3335,11 +3335,21 @@ class String def index(search : Char, offset = 0) : Int32? # If it's ASCII we can delegate to slice if single_byte_optimizable? - # With `single_byte_optimizable?` there are only ASCII characters and invalid UTF-8 byte - # sequences and we can immediately reject any non-ASCII codepoint. - return unless search.ascii? + # With `single_byte_optimizable?` there are only ASCII characters and + # invalid UTF-8 byte sequences, and we can reject anything that is neither + # ASCII nor the replacement character. + case search + when .ascii? + return to_slice.fast_index(search.ord.to_u8!, offset) + when Char::REPLACEMENT + offset.upto(bytesize - 1) do |i| + if to_unsafe[i] >= 0x80 + return i.to_i + end + end + end - return to_slice.fast_index(search.ord.to_u8, offset) + return nil end offset += size if offset < 0 @@ -3455,11 +3465,21 @@ class String def rindex(search : Char, offset = size - 1) # If it's ASCII we can delegate to slice if single_byte_optimizable? - # With `single_byte_optimizable?` there are only ASCII characters and invalid UTF-8 byte - # sequences and we can immediately reject any non-ASCII codepoint. - return unless search.ascii? + # With `single_byte_optimizable?` there are only ASCII characters and + # invalid UTF-8 byte sequences, and we can reject anything that is neither + # ASCII nor the replacement character. + case search + when .ascii? + return to_slice.rindex(search.ord.to_u8!, offset) + when Char::REPLACEMENT + offset.downto(0) do |i| + if to_unsafe[i] >= 0x80 + return i.to_i + end + end + end - return to_slice.rindex(search.ord.to_u8, offset) + return nil end offset += size if offset < 0 From d14d04562125ee1f2c3985d756c5f0e4cd68a9c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 18 Sep 2024 15:43:39 +0200 Subject: [PATCH 1366/1551] Changelog for 1.13.3 (#14991) --- CHANGELOG.md | 24 ++++++++++++++++++++++++ shard.yml | 2 +- src/SOURCE_DATE_EPOCH | 2 +- src/VERSION | 2 +- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f97d0bedeb1b..341586a8fb95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [1.13.3] (2024-09-18) + +[1.13.3]: https://github.com/crystal-lang/crystal/releases/1.13.3 + +### Bugfixes + +#### stdlib + +- **[regression]** Fix use global paths in macro bodies ([#14965], thanks @straight-shoota) +- *(system)* **[regression]** Fix `Process.exec` stream redirection on Windows ([#14986], thanks @HertzDevil) +- *(text)* **[regression]** Fix `String#index` and `#rindex` for `Char::REPLACEMENT` ([#14937], thanks @HertzDevil) + +[#14965]: https://github.com/crystal-lang/crystal/pull/14965 +[#14986]: https://github.com/crystal-lang/crystal/pull/14986 +[#14937]: https://github.com/crystal-lang/crystal/pull/14937 + +### Infrastructure + +- Changelog for 1.13.3 ([#14991], thanks @straight-shoota) +- *(ci)* Enable runners from `runs-on.com` for Aarch64 CI ([#15007], thanks @straight-shoota) + +[#14991]: https://github.com/crystal-lang/crystal/pull/14991 +[#15007]: https://github.com/crystal-lang/crystal/pull/15007 + ## [1.13.2] (2024-08-20) [1.13.2]: https://github.com/crystal-lang/crystal/releases/1.13.2 diff --git a/shard.yml b/shard.yml index 0dd8c2abf3a1..6463a5681c65 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: crystal -version: 1.13.2 +version: 1.13.3 authors: - Crystal Core Team diff --git a/src/SOURCE_DATE_EPOCH b/src/SOURCE_DATE_EPOCH index 0ea6bd82d669..13a79f624e9d 100644 --- a/src/SOURCE_DATE_EPOCH +++ b/src/SOURCE_DATE_EPOCH @@ -1 +1 @@ -1724112000 +1726617600 diff --git a/src/VERSION b/src/VERSION index 61ce01b30118..01b7568230eb 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.13.2 +1.13.3 From 626e8f7c55cc573c321476b5fc4dd2e0986167bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 18 Sep 2024 23:01:49 +0200 Subject: [PATCH 1367/1551] Remove `XML::Error.errors` (#14936) Co-authored-by: Beta Ziliani --- spec/std/xml/reader_spec.cr | 10 ---------- src/xml/error.cr | 15 +-------------- src/xml/reader.cr | 4 +--- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/spec/std/xml/reader_spec.cr b/spec/std/xml/reader_spec.cr index d89593620970..4ec3d8cddc5c 100644 --- a/spec/std/xml/reader_spec.cr +++ b/spec/std/xml/reader_spec.cr @@ -577,15 +577,5 @@ module XML reader.errors.map(&.to_s).should eq ["Opening and ending tag mismatch: people line 1 and foo"] end - - it "adds errors to `XML::Error.errors` (deprecated)" do - XML::Error.errors # clear class error list - - reader = XML::Reader.new(%()) - reader.read - reader.expand? - - XML::Error.errors.try(&.map(&.to_s)).should eq ["Opening and ending tag mismatch: people line 1 and foo"] - end end end diff --git a/src/xml/error.cr b/src/xml/error.cr index 868dfeb4bd00..389aa53910c2 100644 --- a/src/xml/error.cr +++ b/src/xml/error.cr @@ -11,22 +11,9 @@ class XML::Error < Exception super(message) end - @@errors = [] of self - - # :nodoc: - protected def self.add_errors(errors) - @@errors.concat(errors) - end - @[Deprecated("This class accessor is deprecated. XML errors are accessible directly in the respective context via `XML::Reader#errors` and `XML::Node#errors`.")] def self.errors : Array(XML::Error)? - if @@errors.empty? - nil - else - errors = @@errors.dup - @@errors.clear - errors - end + {% raise "`XML::Error.errors` was removed because it leaks memory when it's not used. XML errors are accessible directly in the respective context via `XML::Reader#errors` and `XML::Node#errors`.\nSee https://github.com/crystal-lang/crystal/issues/14934 for details. " %} end def self.collect(errors, &) diff --git a/src/xml/reader.cr b/src/xml/reader.cr index decdd8468185..d4dbe91f7eeb 100644 --- a/src/xml/reader.cr +++ b/src/xml/reader.cr @@ -198,9 +198,7 @@ class XML::Reader end private def collect_errors(&) - Error.collect(@errors) { yield }.tap do - Error.add_errors(@errors) - end + Error.collect(@errors) { yield } end private def check_no_null_byte(attribute) From 62541690fe775778b9b1be2728202e71d2e76dda Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 19 Sep 2024 21:20:45 +0800 Subject: [PATCH 1368/1551] Use our own `libffi` repository on Windows CI (#14998) I forked the libffi upstream and wrote [my own `CMakeLists.txt`](https://github.com/crystal-lang/libffi/blob/441390ce33ae2d9bf2916184fe6b7207b306dd3e/CMakeLists.txt). It only handles x64 MSVC, but we could easily extend it to support ARM64 in the near future. Note that the Windows CI already uses libffi since there are interpreter tests and stdlib tests running with the interpreter. --- .github/workflows/win.yml | 4 ++-- etc/win-ci/build-ffi.ps1 | 43 +++++++++------------------------------ 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index d4b9316ef1a2..89c13959e8cb 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -60,7 +60,7 @@ jobs: run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Version 1.17 - name: Build libffi if: steps.cache-libs.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.3 + run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.4.6 - name: Build zlib if: steps.cache-libs.outputs.cache-hit != 'true' run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.3.1 @@ -148,7 +148,7 @@ jobs: run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Version 1.17 -Dynamic - name: Build libffi if: steps.cache-dlls.outputs.cache-hit != 'true' - run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.3 -Dynamic + run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.4.6 -Dynamic - name: Build zlib if: steps.cache-dlls.outputs.cache-hit != 'true' run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.3.1 -Dynamic diff --git a/etc/win-ci/build-ffi.ps1 b/etc/win-ci/build-ffi.ps1 index 4340630bea64..eb5ec1e5403c 100644 --- a/etc/win-ci/build-ffi.ps1 +++ b/etc/win-ci/build-ffi.ps1 @@ -7,40 +7,17 @@ param( . "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1" [void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force) -Setup-Git -Path $BuildTree -Url https://github.com/winlibs/libffi.git -Ref libffi-$Version +Setup-Git -Path $BuildTree -Url https://github.com/crystal-lang/libffi.git -Ref v$Version Run-InDirectory $BuildTree { + $args = "-DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF" if ($Dynamic) { - Replace-Text win32\vs16_x64\libffi\libffi.vcxproj 'StaticLibrary' 'DynamicLibrary' + $args = "-DBUILD_SHARED_LIBS=ON $args" + } else { + $args = "-DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded $args" } - - echo ' - - $(MsbuildThisFileDirectory)\Override.props - - ' > 'Directory.Build.props' - - echo " - - false - - - - $(if ($Dynamic) { - 'FFI_BUILDING_DLL;%(PreprocessorDefinitions)' - } else { - 'MultiThreaded' - }) - None - false - - - false - - - " > 'Override.props' - - MSBuild.exe /p:PlatformToolset=v143 /p:Platform=x64 /p:Configuration=Release win32\vs16_x64\libffi-msvc.sln -target:libffi:Rebuild + & $cmake . $args.split(' ') + & $cmake --build . --config Release if (-not $?) { Write-Host "Error: Failed to build libffi" -ForegroundColor Red Exit 1 @@ -48,8 +25,8 @@ Run-InDirectory $BuildTree { } if ($Dynamic) { - mv -Force $BuildTree\win32\vs16_x64\x64\Release\libffi.lib libs\ffi-dynamic.lib - mv -Force $BuildTree\win32\vs16_x64\x64\Release\libffi.dll dlls\ + mv -Force $BuildTree\Release\libffi.lib libs\ffi-dynamic.lib + mv -Force $BuildTree\Release\libffi.dll dlls\ } else { - mv -Force $BuildTree\win32\vs16_x64\x64\Release\libffi.lib libs\ffi.lib + mv -Force $BuildTree\Release\libffi.lib libs\ffi.lib } From 47d174825e674c57c0d3b2036fa6975277528614 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 19 Sep 2024 21:20:55 +0800 Subject: [PATCH 1369/1551] Support Unicode 16.0.0 (#14997) --- spec/std/string/grapheme_break_spec.cr | 454 ++++++++++--------------- src/string/grapheme/properties.cr | 103 +++--- src/unicode/data.cr | 267 ++++++++++++--- src/unicode/unicode.cr | 2 +- 4 files changed, 459 insertions(+), 367 deletions(-) diff --git a/spec/std/string/grapheme_break_spec.cr b/spec/std/string/grapheme_break_spec.cr index f1a86656ef12..2ea30c104016 100644 --- a/spec/std/string/grapheme_break_spec.cr +++ b/spec/std/string/grapheme_break_spec.cr @@ -16,8 +16,8 @@ describe "String#each_grapheme" do it_iterates_graphemes " \u0308\n", [" \u0308", '\n'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes " \u0001", [' ', '\u0001'] # ÷ [0.2] SPACE (Other) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes " \u0308\u0001", [" \u0308", '\u0001'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes " \u034F", [" \u034F"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes " \u0308\u034F", [" \u0308\u034F"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes " \u200C", [" \u200C"] # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes " \u0308\u200C", [" \u0308\u200C"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes " \u{1F1E6}", [' ', '\u{1F1E6}'] # ÷ [0.2] SPACE (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes " \u0308\u{1F1E6}", [" \u0308", '\u{1F1E6}'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes " \u0600", [' ', '\u0600'] # ÷ [0.2] SPACE (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -34,8 +34,6 @@ describe "String#each_grapheme" do it_iterates_graphemes " \u0308\uAC00", [" \u0308", '\uAC00'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes " \uAC01", [' ', '\uAC01'] # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes " \u0308\uAC01", [" \u0308", '\uAC01'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes " \u0900", [" \u0900"] # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes " \u0308\u0900", [" \u0308\u0900"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes " \u0903", [" \u0903"] # ÷ [0.2] SPACE (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes " \u0308\u0903", [" \u0308\u0903"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes " \u0904", [' ', '\u0904'] # ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -48,8 +46,8 @@ describe "String#each_grapheme" do it_iterates_graphemes " \u0308\u231A", [" \u0308", '\u231A'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes " \u0300", [" \u0300"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u0308\u0300", [" \u0308\u0300"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes " \u093C", [" \u093C"] # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes " \u0308\u093C", [" \u0308\u093C"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes " \u0900", [" \u0900"] # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes " \u0308\u0900", [" \u0308\u0900"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u094D", [" \u094D"] # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u0308\u094D", [" \u0308\u094D"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes " \u200D", [" \u200D"] # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -64,8 +62,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\r\u0308\n", ['\r', '\u0308', '\n'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\r\u0001", ['\r', '\u0001'] # ÷ [0.2] (CR) ÷ [4.0] (Control) ÷ [0.3] it_iterates_graphemes "\r\u0308\u0001", ['\r', '\u0308', '\u0001'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\r\u034F", ['\r', '\u034F'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u034F", ['\r', "\u0308\u034F"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\r\u200C", ['\r', '\u200C'] # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u200C", ['\r', "\u0308\u200C"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\r\u{1F1E6}", ['\r', '\u{1F1E6}'] # ÷ [0.2] (CR) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\r\u0308\u{1F1E6}", ['\r', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\r\u0600", ['\r', '\u0600'] # ÷ [0.2] (CR) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -82,8 +80,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\r\u0308\uAC00", ['\r', '\u0308', '\uAC00'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\r\uAC01", ['\r', '\uAC01'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\r\u0308\uAC01", ['\r', '\u0308', '\uAC01'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\r\u0900", ['\r', '\u0900'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u0900", ['\r', "\u0308\u0900"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\r\u0903", ['\r', '\u0903'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\r\u0308\u0903", ['\r', "\u0308\u0903"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\r\u0904", ['\r', '\u0904'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -96,8 +92,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\r\u0308\u231A", ['\r', '\u0308', '\u231A'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\r\u0300", ['\r', '\u0300'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\r\u0308\u0300", ['\r', "\u0308\u0300"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\r\u093C", ['\r', '\u093C'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\r\u0308\u093C", ['\r', "\u0308\u093C"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u0900", ['\r', '\u0900'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\r\u0308\u0900", ['\r', "\u0308\u0900"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\r\u094D", ['\r', '\u094D'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\r\u0308\u094D", ['\r', "\u0308\u094D"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\r\u200D", ['\r', '\u200D'] # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -112,8 +108,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\n\u0308\n", ['\n', '\u0308', '\n'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\n\u0001", ['\n', '\u0001'] # ÷ [0.2] (LF) ÷ [4.0] (Control) ÷ [0.3] it_iterates_graphemes "\n\u0308\u0001", ['\n', '\u0308', '\u0001'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\n\u034F", ['\n', '\u034F'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\n\u0308\u034F", ['\n', "\u0308\u034F"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\n\u200C", ['\n', '\u200C'] # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\n\u0308\u200C", ['\n', "\u0308\u200C"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\n\u{1F1E6}", ['\n', '\u{1F1E6}'] # ÷ [0.2] (LF) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\n\u0308\u{1F1E6}", ['\n', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\n\u0600", ['\n', '\u0600'] # ÷ [0.2] (LF) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -130,8 +126,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\n\u0308\uAC00", ['\n', '\u0308', '\uAC00'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\n\uAC01", ['\n', '\uAC01'] # ÷ [0.2] (LF) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\n\u0308\uAC01", ['\n', '\u0308', '\uAC01'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\n\u0900", ['\n', '\u0900'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\n\u0308\u0900", ['\n', "\u0308\u0900"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\n\u0903", ['\n', '\u0903'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\n\u0308\u0903", ['\n', "\u0308\u0903"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\n\u0904", ['\n', '\u0904'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -144,8 +138,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\n\u0308\u231A", ['\n', '\u0308', '\u231A'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\n\u0300", ['\n', '\u0300'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\n\u0308\u0300", ['\n', "\u0308\u0300"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\n\u093C", ['\n', '\u093C'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\n\u0308\u093C", ['\n', "\u0308\u093C"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\n\u0900", ['\n', '\u0900'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\n\u0308\u0900", ['\n', "\u0308\u0900"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\n\u094D", ['\n', '\u094D'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\n\u0308\u094D", ['\n', "\u0308\u094D"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\n\u200D", ['\n', '\u200D'] # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -160,8 +154,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0001\u0308\n", ['\u0001', '\u0308', '\n'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0001\u0001", ['\u0001', '\u0001'] # ÷ [0.2] (Control) ÷ [4.0] (Control) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u0001", ['\u0001', '\u0308', '\u0001'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0001\u034F", ['\u0001', '\u034F'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0001\u0308\u034F", ['\u0001', "\u0308\u034F"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0001\u200C", ['\u0001', '\u200C'] # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\u200C", ['\u0001', "\u0308\u200C"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u0001\u{1F1E6}", ['\u0001', '\u{1F1E6}'] # ÷ [0.2] (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u{1F1E6}", ['\u0001', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0001\u0600", ['\u0001', '\u0600'] # ÷ [0.2] (Control) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -178,8 +172,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0001\u0308\uAC00", ['\u0001', '\u0308', '\uAC00'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0001\uAC01", ['\u0001', '\uAC01'] # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\uAC01", ['\u0001', '\u0308', '\uAC01'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0001\u0900", ['\u0001', '\u0900'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0001\u0308\u0900", ['\u0001', "\u0308\u0900"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0001\u0903", ['\u0001', '\u0903'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u0903", ['\u0001', "\u0308\u0903"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0001\u0904", ['\u0001', '\u0904'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -192,62 +184,60 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0001\u0308\u231A", ['\u0001', '\u0308', '\u231A'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0001\u0300", ['\u0001', '\u0300'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u0300", ['\u0001', "\u0308\u0300"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0001\u093C", ['\u0001', '\u093C'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0001\u0308\u093C", ['\u0001', "\u0308\u093C"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0001\u0900", ['\u0001', '\u0900'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0001\u0308\u0900", ['\u0001', "\u0308\u0900"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0001\u094D", ['\u0001', '\u094D'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u094D", ['\u0001', "\u0308\u094D"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0001\u200D", ['\u0001', '\u200D'] # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u200D", ['\u0001', "\u0308\u200D"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0001\u0378", ['\u0001', '\u0378'] # ÷ [0.2] (Control) ÷ [4.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0001\u0308\u0378", ['\u0001', '\u0308', '\u0378'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\u034F ", ['\u034F', ' '] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308 ", ["\u034F\u0308", ' '] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u034F\r", ['\u034F', '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\r", ["\u034F\u0308", '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u034F\n", ['\u034F', '\n'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (LF) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\n", ["\u034F\u0308", '\n'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] - it_iterates_graphemes "\u034F\u0001", ['\u034F', '\u0001'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u0001", ["\u034F\u0308", '\u0001'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u034F\u034F", ["\u034F\u034F"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u034F", ["\u034F\u0308\u034F"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u034F\u{1F1E6}", ['\u034F', '\u{1F1E6}'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u{1F1E6}", ["\u034F\u0308", '\u{1F1E6}'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\u034F\u0600", ['\u034F', '\u0600'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u0600", ["\u034F\u0308", '\u0600'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u034F\u0A03", ["\u034F\u0A03"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u0A03", ["\u034F\u0308\u0A03"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u034F\u1100", ['\u034F', '\u1100'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u1100", ["\u034F\u0308", '\u1100'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\u034F\u1160", ['\u034F', '\u1160'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u1160", ["\u034F\u0308", '\u1160'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\u034F\u11A8", ['\u034F', '\u11A8'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u11A8", ["\u034F\u0308", '\u11A8'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\u034F\uAC00", ['\u034F', '\uAC00'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\uAC00", ["\u034F\u0308", '\uAC00'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\u034F\uAC01", ['\u034F', '\uAC01'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\uAC01", ["\u034F\u0308", '\uAC01'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u034F\u0900", ["\u034F\u0900"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u0900", ["\u034F\u0308\u0900"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u034F\u0903", ["\u034F\u0903"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u0903", ["\u034F\u0308\u0903"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u034F\u0904", ['\u034F', '\u0904'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u0904", ["\u034F\u0308", '\u0904'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u034F\u0D4E", ['\u034F', '\u0D4E'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u0D4E", ["\u034F\u0308", '\u0D4E'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u034F\u0915", ['\u034F', '\u0915'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u0915", ["\u034F\u0308", '\u0915'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] - it_iterates_graphemes "\u034F\u231A", ['\u034F', '\u231A'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u231A", ["\u034F\u0308", '\u231A'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\u034F\u0300", ["\u034F\u0300"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u0300", ["\u034F\u0308\u0300"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u034F\u093C", ["\u034F\u093C"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u093C", ["\u034F\u0308\u093C"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u034F\u094D", ["\u034F\u094D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u094D", ["\u034F\u0308\u094D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u034F\u200D", ["\u034F\u200D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u200D", ["\u034F\u0308\u200D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u034F\u0378", ['\u034F', '\u0378'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\u034F\u0308\u0378", ["\u034F\u0308", '\u0378'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u200C ", ['\u200C', ' '] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308 ", ["\u200C\u0308", ' '] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u200C\r", ['\u200C', '\r'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\r", ["\u200C\u0308", '\r'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u200C\n", ['\u200C', '\n'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\n", ["\u200C\u0308", '\n'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u200C\u0001", ['\u200C', '\u0001'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u0001", ["\u200C\u0308", '\u0001'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u200C\u200C", ["\u200C\u200C"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u200C", ["\u200C\u0308\u200C"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u200C\u{1F1E6}", ['\u200C', '\u{1F1E6}'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u{1F1E6}", ["\u200C\u0308", '\u{1F1E6}'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u200C\u0600", ['\u200C', '\u0600'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u0600", ["\u200C\u0308", '\u0600'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u200C\u0A03", ["\u200C\u0A03"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u0A03", ["\u200C\u0308\u0A03"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u200C\u1100", ['\u200C', '\u1100'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u1100", ["\u200C\u0308", '\u1100'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u200C\u1160", ['\u200C', '\u1160'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u1160", ["\u200C\u0308", '\u1160'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u200C\u11A8", ['\u200C', '\u11A8'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u11A8", ["\u200C\u0308", '\u11A8'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u200C\uAC00", ['\u200C', '\uAC00'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\uAC00", ["\u200C\u0308", '\uAC00'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u200C\uAC01", ['\u200C', '\uAC01'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\uAC01", ["\u200C\u0308", '\uAC01'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u200C\u0903", ["\u200C\u0903"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u0903", ["\u200C\u0308\u0903"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200C\u0904", ['\u200C', '\u0904'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u0904", ["\u200C\u0308", '\u0904'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200C\u0D4E", ['\u200C', '\u0D4E'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u0D4E", ["\u200C\u0308", '\u0D4E'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u200C\u0915", ['\u200C', '\u0915'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u0915", ["\u200C\u0308", '\u0915'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u200C\u231A", ['\u200C', '\u231A'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u231A", ["\u200C\u0308", '\u231A'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u200C\u0300", ["\u200C\u0300"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u0300", ["\u200C\u0308\u0300"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200C\u0900", ["\u200C\u0900"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u0900", ["\u200C\u0308\u0900"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200C\u094D", ["\u200C\u094D"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u094D", ["\u200C\u0308\u094D"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200C\u200D", ["\u200C\u200D"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u200D", ["\u200C\u0308\u200D"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200C\u0378", ['\u200C', '\u0378'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u200C\u0308\u0378", ["\u200C\u0308", '\u0378'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u{1F1E6} ", ['\u{1F1E6}', ' '] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308 ", ["\u{1F1E6}\u0308", ' '] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\r", ['\u{1F1E6}', '\r'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (CR) ÷ [0.3] @@ -256,8 +246,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u{1F1E6}\u0308\n", ["\u{1F1E6}\u0308", '\n'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0001", ['\u{1F1E6}', '\u0001'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\u0001", ["\u{1F1E6}\u0308", '\u0001'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\u034F", ["\u{1F1E6}\u034F"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\u0308\u034F", ["\u{1F1E6}\u0308\u034F"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u200C", ["\u{1F1E6}\u200C"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\u200C", ["\u{1F1E6}\u0308\u200C"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u{1F1E6}", ["\u{1F1E6}\u{1F1E6}"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\u{1F1E6}", ["\u{1F1E6}\u0308", '\u{1F1E6}'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0600", ['\u{1F1E6}', '\u0600'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -274,8 +264,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u{1F1E6}\u0308\uAC00", ["\u{1F1E6}\u0308", '\uAC00'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\uAC01", ['\u{1F1E6}', '\uAC01'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\uAC01", ["\u{1F1E6}\u0308", '\uAC01'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\u0900", ["\u{1F1E6}\u0900"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\u0308\u0900", ["\u{1F1E6}\u0308\u0900"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0903", ["\u{1F1E6}\u0903"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\u0903", ["\u{1F1E6}\u0308\u0903"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0904", ['\u{1F1E6}', '\u0904'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -288,8 +276,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u{1F1E6}\u0308\u231A", ["\u{1F1E6}\u0308", '\u231A'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0300", ["\u{1F1E6}\u0300"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\u0300", ["\u{1F1E6}\u0308\u0300"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\u093C", ["\u{1F1E6}\u093C"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u{1F1E6}\u0308\u093C", ["\u{1F1E6}\u0308\u093C"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0900", ["\u{1F1E6}\u0900"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u{1F1E6}\u0308\u0900", ["\u{1F1E6}\u0308\u0900"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u094D", ["\u{1F1E6}\u094D"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u0308\u094D", ["\u{1F1E6}\u0308\u094D"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u{1F1E6}\u200D", ["\u{1F1E6}\u200D"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -304,8 +292,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0600\u0308\n", ["\u0600\u0308", '\n'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0600\u0001", ['\u0600', '\u0001'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\u0001", ["\u0600\u0308", '\u0001'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0600\u034F", ["\u0600\u034F"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0600\u0308\u034F", ["\u0600\u0308\u034F"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0600\u200C", ["\u0600\u200C"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\u200C", ["\u0600\u0308\u200C"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u0600\u{1F1E6}", ["\u0600\u{1F1E6}"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\u{1F1E6}", ["\u0600\u0308", '\u{1F1E6}'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0600\u0600", ["\u0600\u0600"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -322,8 +310,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0600\u0308\uAC00", ["\u0600\u0308", '\uAC00'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0600\uAC01", ["\u0600\uAC01"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\uAC01", ["\u0600\u0308", '\uAC01'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0600\u0900", ["\u0600\u0900"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0600\u0308\u0900", ["\u0600\u0308\u0900"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0600\u0903", ["\u0600\u0903"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\u0903", ["\u0600\u0308\u0903"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0600\u0904", ["\u0600\u0904"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -336,8 +322,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0600\u0308\u231A", ["\u0600\u0308", '\u231A'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0600\u0300", ["\u0600\u0300"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\u0300", ["\u0600\u0308\u0300"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0600\u093C", ["\u0600\u093C"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0600\u0308\u093C", ["\u0600\u0308\u093C"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0600\u0900", ["\u0600\u0900"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0600\u0308\u0900", ["\u0600\u0308\u0900"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0600\u094D", ["\u0600\u094D"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0600\u0308\u094D", ["\u0600\u0308\u094D"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0600\u200D", ["\u0600\u200D"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -352,8 +338,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0A03\u0308\n", ["\u0A03\u0308", '\n'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0A03\u0001", ['\u0A03', '\u0001'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u0A03\u0308\u0001", ["\u0A03\u0308", '\u0001'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0A03\u034F", ["\u0A03\u034F"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0A03\u0308\u034F", ["\u0A03\u0308\u034F"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0A03\u200C", ["\u0A03\u200C"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u200C", ["\u0A03\u0308\u200C"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u0A03\u{1F1E6}", ['\u0A03', '\u{1F1E6}'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0A03\u0308\u{1F1E6}", ["\u0A03\u0308", '\u{1F1E6}'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0A03\u0600", ['\u0A03', '\u0600'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -370,8 +356,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0A03\u0308\uAC00", ["\u0A03\u0308", '\uAC00'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0A03\uAC01", ['\u0A03', '\uAC01'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0A03\u0308\uAC01", ["\u0A03\u0308", '\uAC01'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0A03\u0900", ["\u0A03\u0900"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0A03\u0308\u0900", ["\u0A03\u0308\u0900"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0A03\u0903", ["\u0A03\u0903"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0A03\u0308\u0903", ["\u0A03\u0308\u0903"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0A03\u0904", ['\u0A03', '\u0904'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -384,8 +368,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0A03\u0308\u231A", ["\u0A03\u0308", '\u231A'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0A03\u0300", ["\u0A03\u0300"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0A03\u0308\u0300", ["\u0A03\u0308\u0300"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0A03\u093C", ["\u0A03\u093C"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0A03\u0308\u093C", ["\u0A03\u0308\u093C"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0900", ["\u0A03\u0900"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0A03\u0308\u0900", ["\u0A03\u0308\u0900"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0A03\u094D", ["\u0A03\u094D"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0A03\u0308\u094D", ["\u0A03\u0308\u094D"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0A03\u200D", ["\u0A03\u200D"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -400,8 +384,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1100\u0308\n", ["\u1100\u0308", '\n'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u1100\u0001", ['\u1100', '\u0001'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\u0001", ["\u1100\u0308", '\u0001'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u1100\u034F", ["\u1100\u034F"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u1100\u0308\u034F", ["\u1100\u0308\u034F"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u1100\u200C", ["\u1100\u200C"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\u200C", ["\u1100\u0308\u200C"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u1100\u{1F1E6}", ['\u1100', '\u{1F1E6}'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\u{1F1E6}", ["\u1100\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u1100\u0600", ['\u1100', '\u0600'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -418,8 +402,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1100\u0308\uAC00", ["\u1100\u0308", '\uAC00'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u1100\uAC01", ["\u1100\uAC01"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\uAC01", ["\u1100\u0308", '\uAC01'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u1100\u0900", ["\u1100\u0900"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u1100\u0308\u0900", ["\u1100\u0308\u0900"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u1100\u0903", ["\u1100\u0903"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\u0903", ["\u1100\u0308\u0903"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u1100\u0904", ['\u1100', '\u0904'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -432,8 +414,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1100\u0308\u231A", ["\u1100\u0308", '\u231A'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u1100\u0300", ["\u1100\u0300"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\u0300", ["\u1100\u0308\u0300"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u1100\u093C", ["\u1100\u093C"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u1100\u0308\u093C", ["\u1100\u0308\u093C"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1100\u0900", ["\u1100\u0900"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1100\u0308\u0900", ["\u1100\u0308\u0900"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1100\u094D", ["\u1100\u094D"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1100\u0308\u094D", ["\u1100\u0308\u094D"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1100\u200D", ["\u1100\u200D"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -448,8 +430,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1160\u0308\n", ["\u1160\u0308", '\n'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u1160\u0001", ['\u1160', '\u0001'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\u0001", ["\u1160\u0308", '\u0001'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u1160\u034F", ["\u1160\u034F"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u1160\u0308\u034F", ["\u1160\u0308\u034F"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u1160\u200C", ["\u1160\u200C"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\u200C", ["\u1160\u0308\u200C"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u1160\u{1F1E6}", ['\u1160', '\u{1F1E6}'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\u{1F1E6}", ["\u1160\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u1160\u0600", ['\u1160', '\u0600'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -466,8 +448,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1160\u0308\uAC00", ["\u1160\u0308", '\uAC00'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u1160\uAC01", ['\u1160', '\uAC01'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\uAC01", ["\u1160\u0308", '\uAC01'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u1160\u0900", ["\u1160\u0900"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u1160\u0308\u0900", ["\u1160\u0308\u0900"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u1160\u0903", ["\u1160\u0903"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\u0903", ["\u1160\u0308\u0903"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u1160\u0904", ['\u1160', '\u0904'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -480,8 +460,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u1160\u0308\u231A", ["\u1160\u0308", '\u231A'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u1160\u0300", ["\u1160\u0300"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\u0300", ["\u1160\u0308\u0300"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u1160\u093C", ["\u1160\u093C"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u1160\u0308\u093C", ["\u1160\u0308\u093C"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1160\u0900", ["\u1160\u0900"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u1160\u0308\u0900", ["\u1160\u0308\u0900"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1160\u094D", ["\u1160\u094D"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1160\u0308\u094D", ["\u1160\u0308\u094D"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u1160\u200D", ["\u1160\u200D"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -496,8 +476,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u11A8\u0308\n", ["\u11A8\u0308", '\n'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u11A8\u0001", ['\u11A8', '\u0001'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\u0001", ["\u11A8\u0308", '\u0001'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u11A8\u034F", ["\u11A8\u034F"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u11A8\u0308\u034F", ["\u11A8\u0308\u034F"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u11A8\u200C", ["\u11A8\u200C"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\u200C", ["\u11A8\u0308\u200C"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u11A8\u{1F1E6}", ['\u11A8', '\u{1F1E6}'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\u{1F1E6}", ["\u11A8\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u11A8\u0600", ['\u11A8', '\u0600'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -514,8 +494,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u11A8\u0308\uAC00", ["\u11A8\u0308", '\uAC00'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u11A8\uAC01", ['\u11A8', '\uAC01'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\uAC01", ["\u11A8\u0308", '\uAC01'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u11A8\u0900", ["\u11A8\u0900"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u11A8\u0308\u0900", ["\u11A8\u0308\u0900"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u11A8\u0903", ["\u11A8\u0903"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\u0903", ["\u11A8\u0308\u0903"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u11A8\u0904", ['\u11A8', '\u0904'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -528,8 +506,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u11A8\u0308\u231A", ["\u11A8\u0308", '\u231A'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u11A8\u0300", ["\u11A8\u0300"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\u0300", ["\u11A8\u0308\u0300"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u11A8\u093C", ["\u11A8\u093C"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u11A8\u0308\u093C", ["\u11A8\u0308\u093C"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0900", ["\u11A8\u0900"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u11A8\u0308\u0900", ["\u11A8\u0308\u0900"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u11A8\u094D", ["\u11A8\u094D"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u11A8\u0308\u094D", ["\u11A8\u0308\u094D"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u11A8\u200D", ["\u11A8\u200D"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -544,8 +522,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC00\u0308\n", ["\uAC00\u0308", '\n'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\uAC00\u0001", ['\uAC00', '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\u0001", ["\uAC00\u0308", '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\uAC00\u034F", ["\uAC00\u034F"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\uAC00\u0308\u034F", ["\uAC00\u0308\u034F"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\uAC00\u200C", ["\uAC00\u200C"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\u200C", ["\uAC00\u0308\u200C"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\uAC00\u{1F1E6}", ['\uAC00', '\u{1F1E6}'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\u{1F1E6}", ["\uAC00\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\uAC00\u0600", ['\uAC00', '\u0600'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -562,8 +540,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC00\u0308\uAC00", ["\uAC00\u0308", '\uAC00'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\uAC00\uAC01", ['\uAC00', '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\uAC01", ["\uAC00\u0308", '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\uAC00\u0900", ["\uAC00\u0900"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\uAC00\u0308\u0900", ["\uAC00\u0308\u0900"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\uAC00\u0903", ["\uAC00\u0903"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\u0903", ["\uAC00\u0308\u0903"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\uAC00\u0904", ['\uAC00', '\u0904'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -576,8 +552,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC00\u0308\u231A", ["\uAC00\u0308", '\u231A'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\uAC00\u0300", ["\uAC00\u0300"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\u0300", ["\uAC00\u0308\u0300"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\uAC00\u093C", ["\uAC00\u093C"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\uAC00\u0308\u093C", ["\uAC00\u0308\u093C"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0900", ["\uAC00\u0900"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC00\u0308\u0900", ["\uAC00\u0308\u0900"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC00\u094D", ["\uAC00\u094D"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC00\u0308\u094D", ["\uAC00\u0308\u094D"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC00\u200D", ["\uAC00\u200D"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -592,8 +568,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC01\u0308\n", ["\uAC01\u0308", '\n'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\uAC01\u0001", ['\uAC01', '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u0001", ["\uAC01\u0308", '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\uAC01\u034F", ["\uAC01\u034F"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\uAC01\u0308\u034F", ["\uAC01\u0308\u034F"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\uAC01\u200C", ["\uAC01\u200C"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\u200C", ["\uAC01\u0308\u200C"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\uAC01\u{1F1E6}", ['\uAC01', '\u{1F1E6}'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u{1F1E6}", ["\uAC01\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\uAC01\u0600", ['\uAC01', '\u0600'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -610,8 +586,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC01\u0308\uAC00", ["\uAC01\u0308", '\uAC00'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\uAC01\uAC01", ['\uAC01', '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\uAC01", ["\uAC01\u0308", '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\uAC01\u0900", ["\uAC01\u0900"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\uAC01\u0308\u0900", ["\uAC01\u0308\u0900"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\uAC01\u0903", ["\uAC01\u0903"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u0903", ["\uAC01\u0308\u0903"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\uAC01\u0904", ['\uAC01', '\u0904'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -624,62 +598,14 @@ describe "String#each_grapheme" do it_iterates_graphemes "\uAC01\u0308\u231A", ["\uAC01\u0308", '\u231A'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\uAC01\u0300", ["\uAC01\u0300"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u0300", ["\uAC01\u0308\u0300"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\uAC01\u093C", ["\uAC01\u093C"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\uAC01\u0308\u093C", ["\uAC01\u0308\u093C"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0900", ["\uAC01\u0900"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\uAC01\u0308\u0900", ["\uAC01\u0308\u0900"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC01\u094D", ["\uAC01\u094D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u094D", ["\uAC01\u0308\u094D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC01\u200D", ["\uAC01\u200D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u200D", ["\uAC01\u0308\u200D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\uAC01\u0378", ['\uAC01', '\u0378'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\uAC01\u0308\u0378", ["\uAC01\u0308", '\u0378'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\u0900 ", ['\u0900', ' '] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308 ", ["\u0900\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u0900\r", ['\u0900', '\r'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\r", ["\u0900\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u0900\n", ['\u0900', '\n'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\n", ["\u0900\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] - it_iterates_graphemes "\u0900\u0001", ['\u0900', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u0001", ["\u0900\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0900\u034F", ["\u0900\u034F"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u034F", ["\u0900\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0900\u{1F1E6}", ['\u0900', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u{1F1E6}", ["\u0900\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\u0900\u0600", ['\u0900', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u0600", ["\u0900\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u0900\u0A03", ["\u0900\u0A03"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u0A03", ["\u0900\u0308\u0A03"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u0900\u1100", ['\u0900', '\u1100'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u1100", ["\u0900\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\u0900\u1160", ['\u0900', '\u1160'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u1160", ["\u0900\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\u0900\u11A8", ['\u0900', '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u11A8", ["\u0900\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\u0900\uAC00", ['\u0900', '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\uAC00", ["\u0900\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\u0900\uAC01", ['\u0900', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\uAC01", ["\u0900\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0900\u0900", ["\u0900\u0900"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u0900", ["\u0900\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0900\u0903", ["\u0900\u0903"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u0903", ["\u0900\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0900\u0904", ['\u0900', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u0904", ["\u0900\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0900\u0D4E", ['\u0900', '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u0D4E", ["\u0900\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0900\u0915", ['\u0900', '\u0915'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u0915", ["\u0900\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] - it_iterates_graphemes "\u0900\u231A", ['\u0900', '\u231A'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u231A", ["\u0900\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\u0900\u0300", ["\u0900\u0300"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u0300", ["\u0900\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0900\u093C", ["\u0900\u093C"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u093C", ["\u0900\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0900\u094D", ["\u0900\u094D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u094D", ["\u0900\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0900\u200D", ["\u0900\u200D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u200D", ["\u0900\u0308\u200D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0900\u0378", ['\u0900', '\u0378'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\u0900\u0308\u0378", ["\u0900\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0903 ", ['\u0903', ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0903\u0308 ", ["\u0903\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u0903\r", ['\u0903', '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] @@ -688,8 +614,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0903\u0308\n", ["\u0903\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0903\u0001", ['\u0903', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u0903\u0308\u0001", ["\u0903\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0903\u034F", ["\u0903\u034F"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u034F", ["\u0903\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0903\u200C", ["\u0903\u200C"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u200C", ["\u0903\u0308\u200C"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u0903\u{1F1E6}", ['\u0903', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0903\u0308\u{1F1E6}", ["\u0903\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0903\u0600", ['\u0903', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -706,8 +632,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0903\u0308\uAC00", ["\u0903\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0903\uAC01", ['\u0903', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0903\u0308\uAC01", ["\u0903\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0903\u0900", ["\u0903\u0900"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u0900", ["\u0903\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0903\u0903", ["\u0903\u0903"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0903\u0308\u0903", ["\u0903\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0903\u0904", ['\u0903', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -720,8 +644,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0903\u0308\u231A", ["\u0903\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0903\u0300", ["\u0903\u0300"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0903\u0308\u0300", ["\u0903\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0903\u093C", ["\u0903\u093C"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0903\u0308\u093C", ["\u0903\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0903\u0900", ["\u0903\u0900"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0903\u0308\u0900", ["\u0903\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0903\u094D", ["\u0903\u094D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0903\u0308\u094D", ["\u0903\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0903\u200D", ["\u0903\u200D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -736,8 +660,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0904\u0308\n", ["\u0904\u0308", '\n'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0904\u0001", ['\u0904', '\u0001'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u0904\u0308\u0001", ["\u0904\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0904\u034F", ["\u0904\u034F"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0904\u0308\u034F", ["\u0904\u0308\u034F"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0904\u200C", ["\u0904\u200C"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u200C", ["\u0904\u0308\u200C"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u0904\u{1F1E6}", ['\u0904', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0904\u0308\u{1F1E6}", ["\u0904\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0904\u0600", ['\u0904', '\u0600'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -754,8 +678,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0904\u0308\uAC00", ["\u0904\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0904\uAC01", ['\u0904', '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0904\u0308\uAC01", ["\u0904\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0904\u0900", ["\u0904\u0900"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0904\u0308\u0900", ["\u0904\u0308\u0900"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0904\u0903", ["\u0904\u0903"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0904\u0308\u0903", ["\u0904\u0308\u0903"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0904\u0904", ['\u0904', '\u0904'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -768,8 +690,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0904\u0308\u231A", ["\u0904\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0904\u0300", ["\u0904\u0300"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0904\u0308\u0300", ["\u0904\u0308\u0300"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0904\u093C", ["\u0904\u093C"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0904\u0308\u093C", ["\u0904\u0308\u093C"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0904\u0900", ["\u0904\u0900"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0904\u0308\u0900", ["\u0904\u0308\u0900"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0904\u094D", ["\u0904\u094D"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0904\u0308\u094D", ["\u0904\u0308\u094D"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0904\u200D", ["\u0904\u200D"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -784,8 +706,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0D4E\u0308\n", ["\u0D4E\u0308", '\n'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0D4E\u0001", ['\u0D4E', '\u0001'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u0D4E\u0308\u0001", ["\u0D4E\u0308", '\u0001'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0D4E\u034F", ["\u0D4E\u034F"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0D4E\u0308\u034F", ["\u0D4E\u0308\u034F"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u200C", ["\u0D4E\u200C"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u200C", ["\u0D4E\u0308\u200C"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u0D4E\u{1F1E6}", ["\u0D4E\u{1F1E6}"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0D4E\u0308\u{1F1E6}", ["\u0D4E\u0308", '\u{1F1E6}'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0D4E\u0600", ["\u0D4E\u0600"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -802,8 +724,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0D4E\u0308\uAC00", ["\u0D4E\u0308", '\uAC00'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0D4E\uAC01", ["\u0D4E\uAC01"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0D4E\u0308\uAC01", ["\u0D4E\u0308", '\uAC01'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0D4E\u0900", ["\u0D4E\u0900"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0D4E\u0308\u0900", ["\u0D4E\u0308\u0900"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0D4E\u0903", ["\u0D4E\u0903"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0D4E\u0308\u0903", ["\u0D4E\u0308\u0903"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0D4E\u0904", ["\u0D4E\u0904"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -816,8 +736,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0D4E\u0308\u231A", ["\u0D4E\u0308", '\u231A'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0D4E\u0300", ["\u0D4E\u0300"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0D4E\u0308\u0300", ["\u0D4E\u0308\u0300"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0D4E\u093C", ["\u0D4E\u093C"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0D4E\u0308\u093C", ["\u0D4E\u0308\u093C"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0900", ["\u0D4E\u0900"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0D4E\u0308\u0900", ["\u0D4E\u0308\u0900"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0D4E\u094D", ["\u0D4E\u094D"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0D4E\u0308\u094D", ["\u0D4E\u0308\u094D"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0D4E\u200D", ["\u0D4E\u200D"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -832,8 +752,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0915\u0308\n", ["\u0915\u0308", '\n'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0915\u0001", ['\u0915', '\u0001'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u0915\u0308\u0001", ["\u0915\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0915\u034F", ["\u0915\u034F"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0915\u0308\u034F", ["\u0915\u0308\u034F"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0915\u200C", ["\u0915\u200C"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u200C", ["\u0915\u0308\u200C"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u0915\u{1F1E6}", ['\u0915', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0915\u0308\u{1F1E6}", ["\u0915\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0915\u0600", ['\u0915', '\u0600'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -850,8 +770,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0915\u0308\uAC00", ["\u0915\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0915\uAC01", ['\u0915', '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0915\u0308\uAC01", ["\u0915\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0915\u0900", ["\u0915\u0900"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0915\u0308\u0900", ["\u0915\u0308\u0900"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0915\u0903", ["\u0915\u0903"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0915\u0308\u0903", ["\u0915\u0308\u0903"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0915\u0904", ['\u0915', '\u0904'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -864,8 +782,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0915\u0308\u231A", ["\u0915\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0915\u0300", ["\u0915\u0300"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0915\u0308\u0300", ["\u0915\u0308\u0300"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0915\u093C", ["\u0915\u093C"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0915\u0308\u093C", ["\u0915\u0308\u093C"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0915\u0900", ["\u0915\u0900"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0915\u0308\u0900", ["\u0915\u0308\u0900"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0915\u094D", ["\u0915\u094D"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0915\u0308\u094D", ["\u0915\u0308\u094D"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0915\u200D", ["\u0915\u200D"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -880,8 +798,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u231A\u0308\n", ["\u231A\u0308", '\n'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u231A\u0001", ['\u231A', '\u0001'] # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\u0001", ["\u231A\u0308", '\u0001'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u231A\u034F", ["\u231A\u034F"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u231A\u0308\u034F", ["\u231A\u0308\u034F"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u231A\u200C", ["\u231A\u200C"] # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\u200C", ["\u231A\u0308\u200C"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u231A\u{1F1E6}", ['\u231A', '\u{1F1E6}'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\u{1F1E6}", ["\u231A\u0308", '\u{1F1E6}'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u231A\u0600", ['\u231A', '\u0600'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -898,8 +816,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u231A\u0308\uAC00", ["\u231A\u0308", '\uAC00'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u231A\uAC01", ['\u231A', '\uAC01'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\uAC01", ["\u231A\u0308", '\uAC01'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u231A\u0900", ["\u231A\u0900"] # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u231A\u0308\u0900", ["\u231A\u0308\u0900"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u231A\u0903", ["\u231A\u0903"] # ÷ [0.2] WATCH (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\u0903", ["\u231A\u0308\u0903"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u231A\u0904", ['\u231A', '\u0904'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -912,8 +828,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u231A\u0308\u231A", ["\u231A\u0308", '\u231A'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u231A\u0300", ["\u231A\u0300"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\u0300", ["\u231A\u0308\u0300"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u231A\u093C", ["\u231A\u093C"] # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u231A\u0308\u093C", ["\u231A\u0308\u093C"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u231A\u0900", ["\u231A\u0900"] # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u231A\u0308\u0900", ["\u231A\u0308\u0900"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u231A\u094D", ["\u231A\u094D"] # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u231A\u0308\u094D", ["\u231A\u0308\u094D"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u231A\u200D", ["\u231A\u200D"] # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -928,8 +844,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0300\u0308\n", ["\u0300\u0308", '\n'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0300\u0001", ['\u0300', '\u0001'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u0001", ["\u0300\u0308", '\u0001'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0300\u034F", ["\u0300\u034F"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0300\u0308\u034F", ["\u0300\u0308\u034F"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0300\u200C", ["\u0300\u200C"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\u200C", ["\u0300\u0308\u200C"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u0300\u{1F1E6}", ['\u0300', '\u{1F1E6}'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u{1F1E6}", ["\u0300\u0308", '\u{1F1E6}'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0300\u0600", ['\u0300', '\u0600'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -946,8 +862,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0300\u0308\uAC00", ["\u0300\u0308", '\uAC00'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0300\uAC01", ['\u0300', '\uAC01'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\uAC01", ["\u0300\u0308", '\uAC01'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0300\u0900", ["\u0300\u0900"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0300\u0308\u0900", ["\u0300\u0308\u0900"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0300\u0903", ["\u0300\u0903"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u0903", ["\u0300\u0308\u0903"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0300\u0904", ['\u0300', '\u0904'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -960,62 +874,60 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0300\u0308\u231A", ["\u0300\u0308", '\u231A'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0300\u0300", ["\u0300\u0300"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u0300", ["\u0300\u0308\u0300"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0300\u093C", ["\u0300\u093C"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0300\u0308\u093C", ["\u0300\u0308\u093C"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0300\u0900", ["\u0300\u0900"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0300\u0308\u0900", ["\u0300\u0308\u0900"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0300\u094D", ["\u0300\u094D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u094D", ["\u0300\u0308\u094D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0300\u200D", ["\u0300\u200D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u200D", ["\u0300\u0308\u200D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0300\u0378", ['\u0300', '\u0378'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u0300\u0308\u0378", ["\u0300\u0308", '\u0378'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\u093C ", ['\u093C', ' '] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308 ", ["\u093C\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] - it_iterates_graphemes "\u093C\r", ['\u093C', '\r'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\r", ["\u093C\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] - it_iterates_graphemes "\u093C\n", ['\u093C', '\n'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\n", ["\u093C\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] - it_iterates_graphemes "\u093C\u0001", ['\u093C', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u0001", ["\u093C\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u093C\u034F", ["\u093C\u034F"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u034F", ["\u093C\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u093C\u{1F1E6}", ['\u093C', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u{1F1E6}", ["\u093C\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] - it_iterates_graphemes "\u093C\u0600", ['\u093C', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u0600", ["\u093C\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] - it_iterates_graphemes "\u093C\u0A03", ["\u093C\u0A03"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u0A03", ["\u093C\u0308\u0A03"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] - it_iterates_graphemes "\u093C\u1100", ['\u093C', '\u1100'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u1100", ["\u093C\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] - it_iterates_graphemes "\u093C\u1160", ['\u093C', '\u1160'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u1160", ["\u093C\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] - it_iterates_graphemes "\u093C\u11A8", ['\u093C', '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u11A8", ["\u093C\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] - it_iterates_graphemes "\u093C\uAC00", ['\u093C', '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\uAC00", ["\u093C\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] - it_iterates_graphemes "\u093C\uAC01", ['\u093C', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\uAC01", ["\u093C\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u093C\u0900", ["\u093C\u0900"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u0900", ["\u093C\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u093C\u0903", ["\u093C\u0903"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u0903", ["\u093C\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u093C\u0904", ['\u093C', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u0904", ["\u093C\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u093C\u0D4E", ['\u093C', '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u0D4E", ["\u093C\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u093C\u0915", ['\u093C', '\u0915'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u0915", ["\u093C\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] - it_iterates_graphemes "\u093C\u231A", ['\u093C', '\u231A'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u231A", ["\u093C\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] - it_iterates_graphemes "\u093C\u0300", ["\u093C\u0300"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u0300", ["\u093C\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u093C\u093C", ["\u093C\u093C"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u093C", ["\u093C\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u093C\u094D", ["\u093C\u094D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u094D", ["\u093C\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u093C\u200D", ["\u093C\u200D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u200D", ["\u093C\u0308\u200D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u093C\u0378", ['\u093C', '\u0378'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] - it_iterates_graphemes "\u093C\u0308\u0378", ["\u093C\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0900 ", ['\u0900', ' '] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308 ", ["\u0900\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] + it_iterates_graphemes "\u0900\r", ['\u0900', '\r'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\r", ["\u0900\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] + it_iterates_graphemes "\u0900\n", ['\u0900', '\n'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\n", ["\u0900\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] + it_iterates_graphemes "\u0900\u0001", ['\u0900', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0001", ["\u0900\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] + it_iterates_graphemes "\u0900\u200C", ["\u0900\u200C"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u200C", ["\u0900\u0308\u200C"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0900\u{1F1E6}", ['\u0900', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u{1F1E6}", ["\u0900\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] + it_iterates_graphemes "\u0900\u0600", ['\u0900', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0600", ["\u0900\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] + it_iterates_graphemes "\u0900\u0A03", ["\u0900\u0A03"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0A03", ["\u0900\u0308\u0A03"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] + it_iterates_graphemes "\u0900\u1100", ['\u0900', '\u1100'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u1100", ["\u0900\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] + it_iterates_graphemes "\u0900\u1160", ['\u0900', '\u1160'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u1160", ["\u0900\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] + it_iterates_graphemes "\u0900\u11A8", ['\u0900', '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u11A8", ["\u0900\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] + it_iterates_graphemes "\u0900\uAC00", ['\u0900', '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\uAC00", ["\u0900\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] + it_iterates_graphemes "\u0900\uAC01", ['\u0900', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\uAC01", ["\u0900\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] + it_iterates_graphemes "\u0900\u0903", ["\u0900\u0903"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0903", ["\u0900\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0904", ['\u0900', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0904", ["\u0900\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0D4E", ['\u0900', '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0D4E", ["\u0900\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] + it_iterates_graphemes "\u0900\u0915", ['\u0900', '\u0915'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0915", ["\u0900\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] + it_iterates_graphemes "\u0900\u231A", ['\u0900', '\u231A'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u231A", ["\u0900\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u0900\u0300", ["\u0900\u0300"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0300", ["\u0900\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u0900", ["\u0900\u0900"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0900", ["\u0900\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u094D", ["\u0900\u094D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u094D", ["\u0900\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u200D", ["\u0900\u200D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u200D", ["\u0900\u0308\u200D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0900\u0378", ['\u0900', '\u0378'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] + it_iterates_graphemes "\u0900\u0308\u0378", ["\u0900\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] it_iterates_graphemes "\u094D ", ['\u094D', ' '] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u094D\u0308 ", ["\u094D\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] it_iterates_graphemes "\u094D\r", ['\u094D', '\r'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] @@ -1024,8 +936,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u094D\u0308\n", ["\u094D\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u094D\u0001", ['\u094D', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u094D\u0308\u0001", ["\u094D\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u094D\u034F", ["\u094D\u034F"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u094D\u0308\u034F", ["\u094D\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u094D\u200C", ["\u094D\u200C"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u200C", ["\u094D\u0308\u200C"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u094D\u{1F1E6}", ['\u094D', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u094D\u0308\u{1F1E6}", ["\u094D\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u094D\u0600", ['\u094D', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -1042,8 +954,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u094D\u0308\uAC00", ["\u094D\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u094D\uAC01", ['\u094D', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u094D\u0308\uAC01", ["\u094D\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u094D\u0900", ["\u094D\u0900"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u094D\u0308\u0900", ["\u094D\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u094D\u0903", ["\u094D\u0903"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u094D\u0308\u0903", ["\u094D\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u094D\u0904", ['\u094D', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -1056,8 +966,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u094D\u0308\u231A", ["\u094D\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u094D\u0300", ["\u094D\u0300"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u094D\u0308\u0300", ["\u094D\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u094D\u093C", ["\u094D\u093C"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u094D\u0308\u093C", ["\u094D\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u094D\u0900", ["\u094D\u0900"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u094D\u0308\u0900", ["\u094D\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u094D\u094D", ["\u094D\u094D"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u094D\u0308\u094D", ["\u094D\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u094D\u200D", ["\u094D\u200D"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -1072,8 +982,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u200D\u0308\n", ["\u200D\u0308", '\n'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u200D\u0001", ['\u200D', '\u0001'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\u0001", ["\u200D\u0308", '\u0001'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u200D\u034F", ["\u200D\u034F"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u200D\u0308\u034F", ["\u200D\u0308\u034F"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u200D\u200C", ["\u200D\u200C"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\u200C", ["\u200D\u0308\u200C"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u200D\u{1F1E6}", ['\u200D', '\u{1F1E6}'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\u{1F1E6}", ["\u200D\u0308", '\u{1F1E6}'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u200D\u0600", ['\u200D', '\u0600'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -1090,8 +1000,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u200D\u0308\uAC00", ["\u200D\u0308", '\uAC00'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u200D\uAC01", ['\u200D', '\uAC01'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\uAC01", ["\u200D\u0308", '\uAC01'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u200D\u0900", ["\u200D\u0900"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u200D\u0308\u0900", ["\u200D\u0308\u0900"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u200D\u0903", ["\u200D\u0903"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\u0903", ["\u200D\u0308\u0903"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u200D\u0904", ['\u200D', '\u0904'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -1104,8 +1012,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u200D\u0308\u231A", ["\u200D\u0308", '\u231A'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u200D\u0300", ["\u200D\u0300"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\u0300", ["\u200D\u0308\u0300"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u200D\u093C", ["\u200D\u093C"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u200D\u0308\u093C", ["\u200D\u0308\u093C"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200D\u0900", ["\u200D\u0900"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u200D\u0308\u0900", ["\u200D\u0308\u0900"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u200D\u094D", ["\u200D\u094D"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u200D\u0308\u094D", ["\u200D\u0308\u094D"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u200D\u200D", ["\u200D\u200D"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -1120,8 +1028,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0378\u0308\n", ["\u0378\u0308", '\n'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] it_iterates_graphemes "\u0378\u0001", ['\u0378', '\u0001'] # ÷ [0.2] (Other) ÷ [5.0] (Control) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u0001", ["\u0378\u0308", '\u0001'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] - it_iterates_graphemes "\u0378\u034F", ["\u0378\u034F"] # ÷ [0.2] (Other) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] - it_iterates_graphemes "\u0378\u0308\u034F", ["\u0378\u0308\u034F"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0378\u200C", ["\u0378\u200C"] # ÷ [0.2] (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\u200C", ["\u0378\u0308\u200C"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] it_iterates_graphemes "\u0378\u{1F1E6}", ['\u0378', '\u{1F1E6}'] # ÷ [0.2] (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u{1F1E6}", ["\u0378\u0308", '\u{1F1E6}'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] it_iterates_graphemes "\u0378\u0600", ['\u0378', '\u0600'] # ÷ [0.2] (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] @@ -1138,8 +1046,6 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0378\u0308\uAC00", ["\u0378\u0308", '\uAC00'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] it_iterates_graphemes "\u0378\uAC01", ['\u0378', '\uAC01'] # ÷ [0.2] (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\uAC01", ["\u0378\u0308", '\uAC01'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] - it_iterates_graphemes "\u0378\u0900", ["\u0378\u0900"] # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] - it_iterates_graphemes "\u0378\u0308\u0900", ["\u0378\u0308\u0900"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0378\u0903", ["\u0378\u0903"] # ÷ [0.2] (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u0903", ["\u0378\u0308\u0903"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] it_iterates_graphemes "\u0378\u0904", ['\u0378', '\u0904'] # ÷ [0.2] (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] @@ -1152,8 +1058,8 @@ describe "String#each_grapheme" do it_iterates_graphemes "\u0378\u0308\u231A", ["\u0378\u0308", '\u231A'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] it_iterates_graphemes "\u0378\u0300", ["\u0378\u0300"] # ÷ [0.2] (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u0300", ["\u0378\u0308\u0300"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0378\u093C", ["\u0378\u093C"] # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] - it_iterates_graphemes "\u0378\u0308\u093C", ["\u0378\u0308\u093C"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0378\u0900", ["\u0378\u0900"] # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] + it_iterates_graphemes "\u0378\u0308\u0900", ["\u0378\u0308\u0900"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0378\u094D", ["\u0378\u094D"] # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0378\u0308\u094D", ["\u0378\u0308\u094D"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u0378\u200D", ["\u0378\u200D"] # ÷ [0.2] (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] @@ -1176,10 +1082,10 @@ describe "String#each_grapheme" do it_iterates_graphemes "a\u0308b", ["a\u0308", 'b'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] it_iterates_graphemes "a\u0903b", ["a\u0903", 'b'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] it_iterates_graphemes "a\u0600b", ['a', "\u0600b"] # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) × [9.2] LATIN SMALL LETTER B (Other) ÷ [0.3] - it_iterates_graphemes "\u{1F476}\u{1F3FF}\u{1F476}", ["\u{1F476}\u{1F3FF}", '\u{1F476}'] # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [999.0] BABY (ExtPict) ÷ [0.3] - it_iterates_graphemes "a\u{1F3FF}\u{1F476}", ["a\u{1F3FF}", '\u{1F476}'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [999.0] BABY (ExtPict) ÷ [0.3] - it_iterates_graphemes "a\u{1F3FF}\u{1F476}\u200D\u{1F6D1}", ["a\u{1F3FF}", "\u{1F476}\u200D\u{1F6D1}"] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] - it_iterates_graphemes "\u{1F476}\u{1F3FF}\u0308\u200D\u{1F476}\u{1F3FF}", ["\u{1F476}\u{1F3FF}\u0308\u200D\u{1F476}\u{1F3FF}"] # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [0.3] + it_iterates_graphemes "\u{1F476}\u{1F3FF}\u{1F476}", ["\u{1F476}\u{1F3FF}", '\u{1F476}'] # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3] + it_iterates_graphemes "a\u{1F3FF}\u{1F476}", ["a\u{1F3FF}", '\u{1F476}'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3] + it_iterates_graphemes "a\u{1F3FF}\u{1F476}\u200D\u{1F6D1}", ["a\u{1F3FF}", "\u{1F476}\u200D\u{1F6D1}"] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] + it_iterates_graphemes "\u{1F476}\u{1F3FF}\u0308\u200D\u{1F476}\u{1F3FF}", ["\u{1F476}\u{1F3FF}\u0308\u200D\u{1F476}\u{1F3FF}"] # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [0.3] it_iterates_graphemes "\u{1F6D1}\u200D\u{1F6D1}", ["\u{1F6D1}\u200D\u{1F6D1}"] # ÷ [0.2] OCTAGONAL SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] it_iterates_graphemes "a\u200D\u{1F6D1}", ["a\u200D", '\u{1F6D1}'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] it_iterates_graphemes "\u2701\u200D\u2701", ["\u2701\u200D\u2701"] # ÷ [0.2] UPPER BLADE SCISSORS (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] UPPER BLADE SCISSORS (Other) ÷ [0.3] diff --git a/src/string/grapheme/properties.cr b/src/string/grapheme/properties.cr index 65b51fba0935..4d87254b7600 100644 --- a/src/string/grapheme/properties.cr +++ b/src/string/grapheme/properties.cr @@ -58,9 +58,9 @@ struct String::Grapheme # ranges in this slice are numerically sorted. # # These ranges were taken from - # http://www.unicode.org/Public/15.1.0/ucd/auxiliary/GraphemeBreakProperty.txt + # http://www.unicode.org/Public/16.0.0/ucd/auxiliary/GraphemeBreakProperty.txt # as well as - # http://www.unicode.org/Public/15.1.0/ucd/emoji/emoji-data.txt + # http://www.unicode.org/Public/16.0.0/ucd/emoji/emoji-data.txt # ("Extended_Pictographic" only). See # https://www.unicode.org/license.html for the Unicode license agreement. @@codepoints : Array(Tuple(Int32, Int32, Property))? @@ -68,7 +68,7 @@ struct String::Grapheme # :nodoc: protected def self.codepoints @@codepoints ||= begin - data = Array(Tuple(Int32, Int32, Property)).new(1447) + data = Array(Tuple(Int32, Int32, Property)).new(1452) put(data, {0x0000, 0x0009, Property::Control}) put(data, {0x000A, 0x000A, Property::LF}) put(data, {0x000B, 0x000C, Property::Control}) @@ -105,7 +105,7 @@ struct String::Grapheme put(data, {0x0829, 0x082D, Property::Extend}) put(data, {0x0859, 0x085B, Property::Extend}) put(data, {0x0890, 0x0891, Property::Prepend}) - put(data, {0x0898, 0x089F, Property::Extend}) + put(data, {0x0897, 0x089F, Property::Extend}) put(data, {0x08CA, 0x08E1, Property::Extend}) put(data, {0x08E2, 0x08E2, Property::Prepend}) put(data, {0x08E3, 0x0902, Property::Extend}) @@ -187,14 +187,12 @@ struct String::Grapheme put(data, {0x0C82, 0x0C83, Property::SpacingMark}) put(data, {0x0CBC, 0x0CBC, Property::Extend}) put(data, {0x0CBE, 0x0CBE, Property::SpacingMark}) - put(data, {0x0CBF, 0x0CBF, Property::Extend}) - put(data, {0x0CC0, 0x0CC1, Property::SpacingMark}) + put(data, {0x0CBF, 0x0CC0, Property::Extend}) + put(data, {0x0CC1, 0x0CC1, Property::SpacingMark}) put(data, {0x0CC2, 0x0CC2, Property::Extend}) put(data, {0x0CC3, 0x0CC4, Property::SpacingMark}) - put(data, {0x0CC6, 0x0CC6, Property::Extend}) - put(data, {0x0CC7, 0x0CC8, Property::SpacingMark}) - put(data, {0x0CCA, 0x0CCB, Property::SpacingMark}) - put(data, {0x0CCC, 0x0CCD, Property::Extend}) + put(data, {0x0CC6, 0x0CC8, Property::Extend}) + put(data, {0x0CCA, 0x0CCD, Property::Extend}) put(data, {0x0CD5, 0x0CD6, Property::Extend}) put(data, {0x0CE2, 0x0CE3, Property::Extend}) put(data, {0x0CF3, 0x0CF3, Property::SpacingMark}) @@ -259,10 +257,8 @@ struct String::Grapheme put(data, {0x1160, 0x11A7, Property::V}) put(data, {0x11A8, 0x11FF, Property::T}) put(data, {0x135D, 0x135F, Property::Extend}) - put(data, {0x1712, 0x1714, Property::Extend}) - put(data, {0x1715, 0x1715, Property::SpacingMark}) - put(data, {0x1732, 0x1733, Property::Extend}) - put(data, {0x1734, 0x1734, Property::SpacingMark}) + put(data, {0x1712, 0x1715, Property::Extend}) + put(data, {0x1732, 0x1734, Property::Extend}) put(data, {0x1752, 0x1753, Property::Extend}) put(data, {0x1772, 0x1773, Property::Extend}) put(data, {0x17B4, 0x17B5, Property::Extend}) @@ -302,29 +298,23 @@ struct String::Grapheme put(data, {0x1AB0, 0x1ACE, Property::Extend}) put(data, {0x1B00, 0x1B03, Property::Extend}) put(data, {0x1B04, 0x1B04, Property::SpacingMark}) - put(data, {0x1B34, 0x1B3A, Property::Extend}) - put(data, {0x1B3B, 0x1B3B, Property::SpacingMark}) - put(data, {0x1B3C, 0x1B3C, Property::Extend}) - put(data, {0x1B3D, 0x1B41, Property::SpacingMark}) - put(data, {0x1B42, 0x1B42, Property::Extend}) - put(data, {0x1B43, 0x1B44, Property::SpacingMark}) + put(data, {0x1B34, 0x1B3D, Property::Extend}) + put(data, {0x1B3E, 0x1B41, Property::SpacingMark}) + put(data, {0x1B42, 0x1B44, Property::Extend}) put(data, {0x1B6B, 0x1B73, Property::Extend}) put(data, {0x1B80, 0x1B81, Property::Extend}) put(data, {0x1B82, 0x1B82, Property::SpacingMark}) put(data, {0x1BA1, 0x1BA1, Property::SpacingMark}) put(data, {0x1BA2, 0x1BA5, Property::Extend}) put(data, {0x1BA6, 0x1BA7, Property::SpacingMark}) - put(data, {0x1BA8, 0x1BA9, Property::Extend}) - put(data, {0x1BAA, 0x1BAA, Property::SpacingMark}) - put(data, {0x1BAB, 0x1BAD, Property::Extend}) + put(data, {0x1BA8, 0x1BAD, Property::Extend}) put(data, {0x1BE6, 0x1BE6, Property::Extend}) put(data, {0x1BE7, 0x1BE7, Property::SpacingMark}) put(data, {0x1BE8, 0x1BE9, Property::Extend}) put(data, {0x1BEA, 0x1BEC, Property::SpacingMark}) put(data, {0x1BED, 0x1BED, Property::Extend}) put(data, {0x1BEE, 0x1BEE, Property::SpacingMark}) - put(data, {0x1BEF, 0x1BF1, Property::Extend}) - put(data, {0x1BF2, 0x1BF3, Property::SpacingMark}) + put(data, {0x1BEF, 0x1BF3, Property::Extend}) put(data, {0x1C24, 0x1C2B, Property::SpacingMark}) put(data, {0x1C2C, 0x1C33, Property::Extend}) put(data, {0x1C34, 0x1C35, Property::SpacingMark}) @@ -416,7 +406,8 @@ struct String::Grapheme put(data, {0xA8FF, 0xA8FF, Property::Extend}) put(data, {0xA926, 0xA92D, Property::Extend}) put(data, {0xA947, 0xA951, Property::Extend}) - put(data, {0xA952, 0xA953, Property::SpacingMark}) + put(data, {0xA952, 0xA952, Property::SpacingMark}) + put(data, {0xA953, 0xA953, Property::Extend}) put(data, {0xA960, 0xA97C, Property::L}) put(data, {0xA980, 0xA982, Property::Extend}) put(data, {0xA983, 0xA983, Property::SpacingMark}) @@ -425,7 +416,8 @@ struct String::Grapheme put(data, {0xA9B6, 0xA9B9, Property::Extend}) put(data, {0xA9BA, 0xA9BB, Property::SpacingMark}) put(data, {0xA9BC, 0xA9BD, Property::Extend}) - put(data, {0xA9BE, 0xA9C0, Property::SpacingMark}) + put(data, {0xA9BE, 0xA9BF, Property::SpacingMark}) + put(data, {0xA9C0, 0xA9C0, Property::Extend}) put(data, {0xA9E5, 0xA9E5, Property::Extend}) put(data, {0xAA29, 0xAA2E, Property::Extend}) put(data, {0xAA2F, 0xAA30, Property::SpacingMark}) @@ -1269,8 +1261,9 @@ struct String::Grapheme put(data, {0x10A3F, 0x10A3F, Property::Extend}) put(data, {0x10AE5, 0x10AE6, Property::Extend}) put(data, {0x10D24, 0x10D27, Property::Extend}) + put(data, {0x10D69, 0x10D6D, Property::Extend}) put(data, {0x10EAB, 0x10EAC, Property::Extend}) - put(data, {0x10EFD, 0x10EFF, Property::Extend}) + put(data, {0x10EFC, 0x10EFF, Property::Extend}) put(data, {0x10F46, 0x10F50, Property::Extend}) put(data, {0x10F82, 0x10F85, Property::Extend}) put(data, {0x11000, 0x11000, Property::SpacingMark}) @@ -1298,7 +1291,8 @@ struct String::Grapheme put(data, {0x11182, 0x11182, Property::SpacingMark}) put(data, {0x111B3, 0x111B5, Property::SpacingMark}) put(data, {0x111B6, 0x111BE, Property::Extend}) - put(data, {0x111BF, 0x111C0, Property::SpacingMark}) + put(data, {0x111BF, 0x111BF, Property::SpacingMark}) + put(data, {0x111C0, 0x111C0, Property::Extend}) put(data, {0x111C2, 0x111C3, Property::Prepend}) put(data, {0x111C9, 0x111CC, Property::Extend}) put(data, {0x111CE, 0x111CE, Property::SpacingMark}) @@ -1306,9 +1300,7 @@ struct String::Grapheme put(data, {0x1122C, 0x1122E, Property::SpacingMark}) put(data, {0x1122F, 0x11231, Property::Extend}) put(data, {0x11232, 0x11233, Property::SpacingMark}) - put(data, {0x11234, 0x11234, Property::Extend}) - put(data, {0x11235, 0x11235, Property::SpacingMark}) - put(data, {0x11236, 0x11237, Property::Extend}) + put(data, {0x11234, 0x11237, Property::Extend}) put(data, {0x1123E, 0x1123E, Property::Extend}) put(data, {0x11241, 0x11241, Property::Extend}) put(data, {0x112DF, 0x112DF, Property::Extend}) @@ -1322,11 +1314,24 @@ struct String::Grapheme put(data, {0x11340, 0x11340, Property::Extend}) put(data, {0x11341, 0x11344, Property::SpacingMark}) put(data, {0x11347, 0x11348, Property::SpacingMark}) - put(data, {0x1134B, 0x1134D, Property::SpacingMark}) + put(data, {0x1134B, 0x1134C, Property::SpacingMark}) + put(data, {0x1134D, 0x1134D, Property::Extend}) put(data, {0x11357, 0x11357, Property::Extend}) put(data, {0x11362, 0x11363, Property::SpacingMark}) put(data, {0x11366, 0x1136C, Property::Extend}) put(data, {0x11370, 0x11374, Property::Extend}) + put(data, {0x113B8, 0x113B8, Property::Extend}) + put(data, {0x113B9, 0x113BA, Property::SpacingMark}) + put(data, {0x113BB, 0x113C0, Property::Extend}) + put(data, {0x113C2, 0x113C2, Property::Extend}) + put(data, {0x113C5, 0x113C5, Property::Extend}) + put(data, {0x113C7, 0x113C9, Property::Extend}) + put(data, {0x113CA, 0x113CA, Property::SpacingMark}) + put(data, {0x113CC, 0x113CD, Property::SpacingMark}) + put(data, {0x113CE, 0x113D0, Property::Extend}) + put(data, {0x113D1, 0x113D1, Property::Prepend}) + put(data, {0x113D2, 0x113D2, Property::Extend}) + put(data, {0x113E1, 0x113E2, Property::Extend}) put(data, {0x11435, 0x11437, Property::SpacingMark}) put(data, {0x11438, 0x1143F, Property::Extend}) put(data, {0x11440, 0x11441, Property::SpacingMark}) @@ -1363,10 +1368,10 @@ struct String::Grapheme put(data, {0x116AC, 0x116AC, Property::SpacingMark}) put(data, {0x116AD, 0x116AD, Property::Extend}) put(data, {0x116AE, 0x116AF, Property::SpacingMark}) - put(data, {0x116B0, 0x116B5, Property::Extend}) - put(data, {0x116B6, 0x116B6, Property::SpacingMark}) - put(data, {0x116B7, 0x116B7, Property::Extend}) - put(data, {0x1171D, 0x1171F, Property::Extend}) + put(data, {0x116B0, 0x116B7, Property::Extend}) + put(data, {0x1171D, 0x1171D, Property::Extend}) + put(data, {0x1171E, 0x1171E, Property::SpacingMark}) + put(data, {0x1171F, 0x1171F, Property::Extend}) put(data, {0x11722, 0x11725, Property::Extend}) put(data, {0x11726, 0x11726, Property::SpacingMark}) put(data, {0x11727, 0x1172B, Property::Extend}) @@ -1377,9 +1382,7 @@ struct String::Grapheme put(data, {0x11930, 0x11930, Property::Extend}) put(data, {0x11931, 0x11935, Property::SpacingMark}) put(data, {0x11937, 0x11938, Property::SpacingMark}) - put(data, {0x1193B, 0x1193C, Property::Extend}) - put(data, {0x1193D, 0x1193D, Property::SpacingMark}) - put(data, {0x1193E, 0x1193E, Property::Extend}) + put(data, {0x1193B, 0x1193E, Property::Extend}) put(data, {0x1193F, 0x1193F, Property::Prepend}) put(data, {0x11940, 0x11940, Property::SpacingMark}) put(data, {0x11941, 0x11941, Property::Prepend}) @@ -1436,28 +1439,29 @@ struct String::Grapheme put(data, {0x11F34, 0x11F35, Property::SpacingMark}) put(data, {0x11F36, 0x11F3A, Property::Extend}) put(data, {0x11F3E, 0x11F3F, Property::SpacingMark}) - put(data, {0x11F40, 0x11F40, Property::Extend}) - put(data, {0x11F41, 0x11F41, Property::SpacingMark}) - put(data, {0x11F42, 0x11F42, Property::Extend}) + put(data, {0x11F40, 0x11F42, Property::Extend}) + put(data, {0x11F5A, 0x11F5A, Property::Extend}) put(data, {0x13430, 0x1343F, Property::Control}) put(data, {0x13440, 0x13440, Property::Extend}) put(data, {0x13447, 0x13455, Property::Extend}) + put(data, {0x1611E, 0x16129, Property::Extend}) + put(data, {0x1612A, 0x1612C, Property::SpacingMark}) + put(data, {0x1612D, 0x1612F, Property::Extend}) put(data, {0x16AF0, 0x16AF4, Property::Extend}) put(data, {0x16B30, 0x16B36, Property::Extend}) + put(data, {0x16D63, 0x16D63, Property::V}) + put(data, {0x16D67, 0x16D6A, Property::V}) put(data, {0x16F4F, 0x16F4F, Property::Extend}) put(data, {0x16F51, 0x16F87, Property::SpacingMark}) put(data, {0x16F8F, 0x16F92, Property::Extend}) put(data, {0x16FE4, 0x16FE4, Property::Extend}) - put(data, {0x16FF0, 0x16FF1, Property::SpacingMark}) + put(data, {0x16FF0, 0x16FF1, Property::Extend}) put(data, {0x1BC9D, 0x1BC9E, Property::Extend}) put(data, {0x1BCA0, 0x1BCA3, Property::Control}) put(data, {0x1CF00, 0x1CF2D, Property::Extend}) put(data, {0x1CF30, 0x1CF46, Property::Extend}) - put(data, {0x1D165, 0x1D165, Property::Extend}) - put(data, {0x1D166, 0x1D166, Property::SpacingMark}) - put(data, {0x1D167, 0x1D169, Property::Extend}) - put(data, {0x1D16D, 0x1D16D, Property::SpacingMark}) - put(data, {0x1D16E, 0x1D172, Property::Extend}) + put(data, {0x1D165, 0x1D169, Property::Extend}) + put(data, {0x1D16D, 0x1D172, Property::Extend}) put(data, {0x1D173, 0x1D17A, Property::Control}) put(data, {0x1D17B, 0x1D182, Property::Extend}) put(data, {0x1D185, 0x1D18B, Property::Extend}) @@ -1479,6 +1483,7 @@ struct String::Grapheme put(data, {0x1E2AE, 0x1E2AE, Property::Extend}) put(data, {0x1E2EC, 0x1E2EF, Property::Extend}) put(data, {0x1E4EC, 0x1E4EF, Property::Extend}) + put(data, {0x1E5EE, 0x1E5EF, Property::Extend}) put(data, {0x1E8D0, 0x1E8D6, Property::Extend}) put(data, {0x1E944, 0x1E94A, Property::Extend}) put(data, {0x1F000, 0x1F0FF, Property::ExtendedPictographic}) diff --git a/src/unicode/data.cr b/src/unicode/data.cr index a02db251d0c8..ccb7d702e892 100644 --- a/src/unicode/data.cr +++ b/src/unicode/data.cr @@ -8,7 +8,7 @@ module Unicode # Most case conversions map a range to another range. # Here we store: {from, to, delta} private class_getter upcase_ranges : Array({Int32, Int32, Int32}) do - data = Array({Int32, Int32, Int32}).new(141) + data = Array({Int32, Int32, Int32}).new(144) put(data, 97, 122, -32) put(data, 181, 181, 743) put(data, 224, 246, -32) @@ -19,6 +19,7 @@ module Unicode put(data, 384, 384, 195) put(data, 405, 405, 97) put(data, 410, 410, 163) + put(data, 411, 411, 42561) put(data, 414, 414, 130) put(data, 447, 447, 56) put(data, 454, 454, -2) @@ -39,6 +40,7 @@ module Unicode put(data, 608, 608, -205) put(data, 609, 609, 42315) put(data, 611, 611, -207) + put(data, 612, 612, 42343) put(data, 613, 613, 42280) put(data, 614, 614, 42308) put(data, 616, 616, -209) @@ -147,6 +149,7 @@ module Unicode put(data, 66995, 67001, -39) put(data, 67003, 67004, -39) put(data, 68800, 68850, -64) + put(data, 68976, 68997, -32) put(data, 71872, 71903, -32) put(data, 93792, 93823, -32) put(data, 125218, 125251, -34) @@ -156,7 +159,7 @@ module Unicode # Most case conversions map a range to another range. # Here we store: {from, to, delta} private class_getter downcase_ranges : Array({Int32, Int32, Int32}) do - data = Array({Int32, Int32, Int32}).new(125) + data = Array({Int32, Int32, Int32}).new(128) put(data, 65, 90, 32) put(data, 192, 214, 32) put(data, 216, 222, 32) @@ -271,6 +274,8 @@ module Unicode put(data, 42948, 42948, -48) put(data, 42949, 42949, -42307) put(data, 42950, 42950, -35384) + put(data, 42955, 42955, -42343) + put(data, 42972, 42972, -42561) put(data, 65313, 65338, 32) put(data, 66560, 66599, 40) put(data, 66736, 66771, 40) @@ -279,6 +284,7 @@ module Unicode put(data, 66956, 66962, 39) put(data, 66964, 66965, 39) put(data, 68736, 68786, 64) + put(data, 68944, 68965, 32) put(data, 71840, 71871, 32) put(data, 93760, 93791, 32) put(data, 125184, 125217, 34) @@ -289,7 +295,7 @@ module Unicode # of uppercase/lowercase transformations # Here we store {from, to} private class_getter alternate_ranges : Array({Int32, Int32}) do - data = Array({Int32, Int32}).new(60) + data = Array({Int32, Int32}).new(62) put(data, 256, 303) put(data, 306, 311) put(data, 313, 328) @@ -326,6 +332,7 @@ module Unicode put(data, 1162, 1215) put(data, 1217, 1230) put(data, 1232, 1327) + put(data, 7305, 7306) put(data, 7680, 7829) put(data, 7840, 7935) put(data, 8579, 8580) @@ -347,8 +354,9 @@ module Unicode put(data, 42902, 42921) put(data, 42932, 42947) put(data, 42951, 42954) + put(data, 42956, 42957) put(data, 42960, 42961) - put(data, 42966, 42969) + put(data, 42966, 42971) put(data, 42997, 42998) data end @@ -363,7 +371,7 @@ module Unicode # The values are: 1..10, 11, 13, 15 private class_getter category_Lu : Array({Int32, Int32, Int32}) do - data = Array({Int32, Int32, Int32}).new(149) + data = Array({Int32, Int32, Int32}).new(152) put(data, 65, 90, 1) put(data, 192, 214, 1) put(data, 216, 222, 1) @@ -420,7 +428,8 @@ module Unicode put(data, 4256, 4293, 1) put(data, 4295, 4301, 6) put(data, 5024, 5109, 1) - put(data, 7312, 7354, 1) + put(data, 7305, 7312, 7) + put(data, 7313, 7354, 1) put(data, 7357, 7359, 1) put(data, 7680, 7828, 2) put(data, 7838, 7934, 2) @@ -469,8 +478,9 @@ module Unicode put(data, 42928, 42932, 1) put(data, 42934, 42948, 2) put(data, 42949, 42951, 1) - put(data, 42953, 42960, 7) - put(data, 42966, 42968, 2) + put(data, 42953, 42955, 2) + put(data, 42956, 42960, 4) + put(data, 42966, 42972, 2) put(data, 42997, 65313, 22316) put(data, 65314, 65338, 1) put(data, 66560, 66599, 1) @@ -480,6 +490,7 @@ module Unicode put(data, 66956, 66962, 1) put(data, 66964, 66965, 1) put(data, 68736, 68786, 1) + put(data, 68944, 68965, 1) put(data, 71840, 71871, 1) put(data, 93760, 93791, 1) put(data, 119808, 119833, 1) @@ -516,7 +527,7 @@ module Unicode data end private class_getter category_Ll : Array({Int32, Int32, Int32}) do - data = Array({Int32, Int32, Int32}).new(163) + data = Array({Int32, Int32, Int32}).new(166) put(data, 97, 122, 1) put(data, 181, 223, 42) put(data, 224, 246, 1) @@ -572,7 +583,8 @@ module Unicode put(data, 4349, 4351, 1) put(data, 5112, 5117, 1) put(data, 7296, 7304, 1) - put(data, 7424, 7467, 1) + put(data, 7306, 7424, 118) + put(data, 7425, 7467, 1) put(data, 7531, 7543, 1) put(data, 7545, 7578, 1) put(data, 7681, 7829, 2) @@ -631,7 +643,8 @@ module Unicode put(data, 42927, 42933, 6) put(data, 42935, 42947, 2) put(data, 42952, 42954, 2) - put(data, 42961, 42969, 2) + put(data, 42957, 42961, 4) + put(data, 42963, 42971, 2) put(data, 42998, 43002, 4) put(data, 43824, 43866, 1) put(data, 43872, 43880, 1) @@ -646,6 +659,7 @@ module Unicode put(data, 66995, 67001, 1) put(data, 67003, 67004, 1) put(data, 68800, 68850, 1) + put(data, 68976, 68997, 1) put(data, 71872, 71903, 1) put(data, 93792, 93823, 1) put(data, 119834, 119859, 1) @@ -694,7 +708,7 @@ module Unicode data end private class_getter category_Lm : Array({Int32, Int32, Int32}) do - data = Array({Int32, Int32, Int32}).new(54) + data = Array({Int32, Int32, Int32}).new(57) put(data, 688, 705, 1) put(data, 710, 721, 1) put(data, 736, 740, 1) @@ -739,7 +753,10 @@ module Unicode put(data, 67456, 67461, 1) put(data, 67463, 67504, 1) put(data, 67506, 67514, 1) + put(data, 68942, 68975, 33) put(data, 92992, 92995, 1) + put(data, 93504, 93506, 1) + put(data, 93547, 93548, 1) put(data, 94099, 94111, 1) put(data, 94176, 94177, 1) put(data, 94179, 110576, 16397) @@ -752,7 +769,7 @@ module Unicode data end private class_getter category_Lo : Array({Int32, Int32, Int32}) do - data = Array({Int32, Int32, Int32}).new(486) + data = Array({Int32, Int32, Int32}).new(502) put(data, 170, 186, 16) put(data, 443, 448, 5) put(data, 449, 451, 1) @@ -1052,6 +1069,7 @@ module Unicode put(data, 66640, 66717, 1) put(data, 66816, 66855, 1) put(data, 66864, 66915, 1) + put(data, 67008, 67059, 1) put(data, 67072, 67382, 1) put(data, 67392, 67413, 1) put(data, 67424, 67431, 1) @@ -1083,8 +1101,11 @@ module Unicode put(data, 68480, 68497, 1) put(data, 68608, 68680, 1) put(data, 68864, 68899, 1) - put(data, 69248, 69289, 1) + put(data, 68938, 68941, 1) + put(data, 68943, 69248, 305) + put(data, 69249, 69289, 1) put(data, 69296, 69297, 1) + put(data, 69314, 69316, 1) put(data, 69376, 69404, 1) put(data, 69415, 69424, 9) put(data, 69425, 69445, 1) @@ -1120,7 +1141,12 @@ module Unicode put(data, 70453, 70457, 1) put(data, 70461, 70480, 19) put(data, 70493, 70497, 1) - put(data, 70656, 70708, 1) + put(data, 70528, 70537, 1) + put(data, 70539, 70542, 3) + put(data, 70544, 70581, 1) + put(data, 70583, 70609, 26) + put(data, 70611, 70656, 45) + put(data, 70657, 70708, 1) put(data, 70727, 70730, 1) put(data, 70751, 70753, 1) put(data, 70784, 70831, 1) @@ -1150,6 +1176,7 @@ module Unicode put(data, 72284, 72329, 1) put(data, 72349, 72368, 19) put(data, 72369, 72440, 1) + put(data, 72640, 72672, 1) put(data, 72704, 72712, 1) put(data, 72714, 72750, 1) put(data, 72768, 72818, 50) @@ -1172,7 +1199,9 @@ module Unicode put(data, 77712, 77808, 1) put(data, 77824, 78895, 1) put(data, 78913, 78918, 1) + put(data, 78944, 82938, 1) put(data, 82944, 83526, 1) + put(data, 90368, 90397, 1) put(data, 92160, 92728, 1) put(data, 92736, 92766, 1) put(data, 92784, 92862, 1) @@ -1180,12 +1209,14 @@ module Unicode put(data, 92928, 92975, 1) put(data, 93027, 93047, 1) put(data, 93053, 93071, 1) + put(data, 93507, 93546, 1) put(data, 93952, 94026, 1) put(data, 94032, 94208, 176) put(data, 100343, 100352, 9) put(data, 100353, 101589, 1) - put(data, 101632, 101640, 1) - put(data, 110592, 110882, 1) + put(data, 101631, 101632, 1) + put(data, 101640, 110592, 8952) + put(data, 110593, 110882, 1) put(data, 110898, 110928, 30) put(data, 110929, 110930, 1) put(data, 110933, 110948, 15) @@ -1201,7 +1232,9 @@ module Unicode put(data, 123537, 123565, 1) put(data, 123584, 123627, 1) put(data, 124112, 124138, 1) - put(data, 124896, 124902, 1) + put(data, 124368, 124397, 1) + put(data, 124400, 124896, 496) + put(data, 124897, 124902, 1) put(data, 124904, 124907, 1) put(data, 124909, 124910, 1) put(data, 124912, 124926, 1) @@ -1242,7 +1275,7 @@ module Unicode data end private class_getter category_Mn : Array({Int32, Int32, Int32}) do - data = Array({Int32, Int32, Int32}).new(308) + data = Array({Int32, Int32, Int32}).new(315) put(data, 768, 879, 1) put(data, 1155, 1159, 1) put(data, 1425, 1469, 1) @@ -1266,7 +1299,7 @@ module Unicode put(data, 2085, 2087, 1) put(data, 2089, 2093, 1) put(data, 2137, 2139, 1) - put(data, 2200, 2207, 1) + put(data, 2199, 2207, 1) put(data, 2250, 2273, 1) put(data, 2275, 2306, 1) put(data, 2362, 2364, 2) @@ -1435,8 +1468,9 @@ module Unicode put(data, 68159, 68325, 166) put(data, 68326, 68900, 574) put(data, 68901, 68903, 1) + put(data, 68969, 68973, 1) put(data, 69291, 69292, 1) - put(data, 69373, 69375, 1) + put(data, 69372, 69375, 1) put(data, 69446, 69456, 1) put(data, 69506, 69509, 1) put(data, 69633, 69688, 55) @@ -1465,6 +1499,9 @@ module Unicode put(data, 70464, 70502, 38) put(data, 70503, 70508, 1) put(data, 70512, 70516, 1) + put(data, 70587, 70592, 1) + put(data, 70606, 70610, 2) + put(data, 70625, 70626, 1) put(data, 70712, 70719, 1) put(data, 70722, 70724, 1) put(data, 70726, 70750, 24) @@ -1482,8 +1519,8 @@ module Unicode put(data, 71341, 71344, 3) put(data, 71345, 71349, 1) put(data, 71351, 71453, 102) - put(data, 71454, 71455, 1) - put(data, 71458, 71461, 1) + put(data, 71455, 71458, 3) + put(data, 71459, 71461, 1) put(data, 71463, 71467, 1) put(data, 71727, 71735, 1) put(data, 71737, 71738, 1) @@ -1518,8 +1555,10 @@ module Unicode put(data, 73473, 73526, 53) put(data, 73527, 73530, 1) put(data, 73536, 73538, 2) - put(data, 78912, 78919, 7) - put(data, 78920, 78933, 1) + put(data, 73562, 78912, 5350) + put(data, 78919, 78933, 1) + put(data, 90398, 90409, 1) + put(data, 90413, 90415, 1) put(data, 92912, 92916, 1) put(data, 92976, 92982, 1) put(data, 94031, 94095, 64) @@ -1548,13 +1587,14 @@ module Unicode put(data, 123566, 123628, 62) put(data, 123629, 123631, 1) put(data, 124140, 124143, 1) + put(data, 124398, 124399, 1) put(data, 125136, 125142, 1) put(data, 125252, 125258, 1) put(data, 917760, 917999, 1) data end private class_getter category_Mc : Array({Int32, Int32, Int32}) do - data = Array({Int32, Int32, Int32}).new(158) + data = Array({Int32, Int32, Int32}).new(165) put(data, 2307, 2363, 56) put(data, 2366, 2368, 1) put(data, 2377, 2380, 1) @@ -1672,7 +1712,12 @@ module Unicode put(data, 70471, 70472, 1) put(data, 70475, 70477, 1) put(data, 70487, 70498, 11) - put(data, 70499, 70709, 210) + put(data, 70499, 70584, 85) + put(data, 70585, 70586, 1) + put(data, 70594, 70597, 3) + put(data, 70599, 70602, 1) + put(data, 70604, 70605, 1) + put(data, 70607, 70709, 102) put(data, 70710, 70711, 1) put(data, 70720, 70721, 1) put(data, 70725, 70832, 107) @@ -1687,9 +1732,10 @@ module Unicode put(data, 71227, 71228, 1) put(data, 71230, 71340, 110) put(data, 71342, 71343, 1) - put(data, 71350, 71456, 106) - put(data, 71457, 71462, 5) - put(data, 71724, 71726, 1) + put(data, 71350, 71454, 104) + put(data, 71456, 71457, 1) + put(data, 71462, 71724, 262) + put(data, 71725, 71726, 1) put(data, 71736, 71984, 248) put(data, 71985, 71989, 1) put(data, 71991, 71992, 1) @@ -1708,8 +1754,9 @@ module Unicode put(data, 73462, 73475, 13) put(data, 73524, 73525, 1) put(data, 73534, 73535, 1) - put(data, 73537, 94033, 20496) - put(data, 94034, 94087, 1) + put(data, 73537, 90410, 16873) + put(data, 90411, 90412, 1) + put(data, 94033, 94087, 1) put(data, 94192, 94193, 1) put(data, 119141, 119142, 1) put(data, 119149, 119154, 1) @@ -1725,7 +1772,7 @@ module Unicode data end private class_getter category_Nd : Array({Int32, Int32, Int32}) do - data = Array({Int32, Int32, Int32}).new(64) + data = Array({Int32, Int32, Int32}).new(71) put(data, 48, 57, 1) put(data, 1632, 1641, 1) put(data, 1776, 1785, 1) @@ -1765,6 +1812,7 @@ module Unicode put(data, 65296, 65305, 1) put(data, 66720, 66729, 1) put(data, 68912, 68921, 1) + put(data, 68928, 68937, 1) put(data, 69734, 69743, 1) put(data, 69872, 69881, 1) put(data, 69942, 69951, 1) @@ -1774,20 +1822,26 @@ module Unicode put(data, 70864, 70873, 1) put(data, 71248, 71257, 1) put(data, 71360, 71369, 1) + put(data, 71376, 71395, 1) put(data, 71472, 71481, 1) put(data, 71904, 71913, 1) put(data, 72016, 72025, 1) + put(data, 72688, 72697, 1) put(data, 72784, 72793, 1) put(data, 73040, 73049, 1) put(data, 73120, 73129, 1) put(data, 73552, 73561, 1) + put(data, 90416, 90425, 1) put(data, 92768, 92777, 1) put(data, 92864, 92873, 1) put(data, 93008, 93017, 1) + put(data, 93552, 93561, 1) + put(data, 118000, 118009, 1) put(data, 120782, 120831, 1) put(data, 123200, 123209, 1) put(data, 123632, 123641, 1) put(data, 124144, 124153, 1) + put(data, 124401, 124410, 1) put(data, 125264, 125273, 1) put(data, 130032, 130041, 1) data @@ -1951,7 +2005,7 @@ module Unicode # Most casefold conversions map a range to another range. # Here we store: {from, to, delta} private class_getter casefold_ranges : Array({Int32, Int32, Int32}) do - data = Array({Int32, Int32, Int32}).new(681) + data = Array({Int32, Int32, Int32}).new(687) put(data, 65, 90, 32) put(data, 181, 181, 775) put(data, 192, 214, 32) @@ -2276,6 +2330,7 @@ module Unicode put(data, 7302, 7302, -6204) put(data, 7303, 7303, -6180) put(data, 7304, 7304, 35267) + put(data, 7305, 7305, 1) put(data, 7312, 7354, -3008) put(data, 7357, 7359, -3008) put(data, 7680, 7680, 1) @@ -2617,9 +2672,13 @@ module Unicode put(data, 42950, 42950, -35384) put(data, 42951, 42951, 1) put(data, 42953, 42953, 1) + put(data, 42955, 42955, -42343) + put(data, 42956, 42956, 1) put(data, 42960, 42960, 1) put(data, 42966, 42966, 1) put(data, 42968, 42968, 1) + put(data, 42970, 42970, 1) + put(data, 42972, 42972, -42561) put(data, 42997, 42997, 1) put(data, 43888, 43967, -38864) put(data, 65313, 65338, 32) @@ -2630,6 +2689,7 @@ module Unicode put(data, 66956, 66962, 39) put(data, 66964, 66965, 39) put(data, 68736, 68786, 64) + put(data, 68944, 68965, 32) put(data, 71840, 71871, 32) put(data, 93760, 93791, 32) put(data, 125184, 125217, 34) @@ -2963,7 +3023,7 @@ module Unicode # guarantees that all class values are within `0..254`. # Here we store: {from, to, class} private class_getter canonical_combining_classes : Array({Int32, Int32, UInt8}) do - data = Array({Int32, Int32, UInt8}).new(392) + data = Array({Int32, Int32, UInt8}).new(399) put(data, 768, 788, 230_u8) put(data, 789, 789, 232_u8) put(data, 790, 793, 220_u8) @@ -3084,7 +3144,7 @@ module Unicode put(data, 2085, 2087, 230_u8) put(data, 2089, 2093, 230_u8) put(data, 2137, 2139, 220_u8) - put(data, 2200, 2200, 230_u8) + put(data, 2199, 2200, 230_u8) put(data, 2201, 2203, 220_u8) put(data, 2204, 2207, 230_u8) put(data, 2250, 2254, 230_u8) @@ -3273,6 +3333,7 @@ module Unicode put(data, 68325, 68325, 230_u8) put(data, 68326, 68326, 220_u8) put(data, 68900, 68903, 230_u8) + put(data, 68969, 68973, 230_u8) put(data, 69291, 69292, 230_u8) put(data, 69373, 69375, 220_u8) put(data, 69446, 69447, 220_u8) @@ -3302,6 +3363,9 @@ module Unicode put(data, 70477, 70477, 9_u8) put(data, 70502, 70508, 230_u8) put(data, 70512, 70516, 230_u8) + put(data, 70606, 70606, 9_u8) + put(data, 70607, 70607, 9_u8) + put(data, 70608, 70608, 9_u8) put(data, 70722, 70722, 9_u8) put(data, 70726, 70726, 7_u8) put(data, 70750, 70750, 230_u8) @@ -3328,6 +3392,7 @@ module Unicode put(data, 73111, 73111, 9_u8) put(data, 73537, 73537, 9_u8) put(data, 73538, 73538, 9_u8) + put(data, 90415, 90415, 9_u8) put(data, 92912, 92916, 1_u8) put(data, 92976, 92982, 230_u8) put(data, 94192, 94193, 6_u8) @@ -3353,6 +3418,8 @@ module Unicode put(data, 124140, 124141, 232_u8) put(data, 124142, 124142, 220_u8) put(data, 124143, 124143, 230_u8) + put(data, 124398, 124398, 230_u8) + put(data, 124399, 124399, 220_u8) put(data, 125136, 125142, 220_u8) put(data, 125252, 125257, 230_u8) put(data, 125258, 125258, 7_u8) @@ -3363,7 +3430,7 @@ module Unicode # transformation is always 2 codepoints, so we store them all as 2 codepoints # and 0 means end. private class_getter canonical_decompositions : Hash(Int32, {Int32, Int32}) do - data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 2061) + data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 2081) put(data, 192, 65, 768) put(data, 193, 65, 769) put(data, 194, 65, 770) @@ -4857,6 +4924,8 @@ module Unicode put(data, 64332, 1489, 1471) put(data, 64333, 1499, 1471) put(data, 64334, 1508, 1471) + put(data, 67017, 67026, 775) + put(data, 67044, 67034, 775) put(data, 69786, 69785, 69818) put(data, 69788, 69787, 69818) put(data, 69803, 69797, 69818) @@ -4864,12 +4933,30 @@ module Unicode put(data, 69935, 69938, 69927) put(data, 70475, 70471, 70462) put(data, 70476, 70471, 70487) + put(data, 70531, 70530, 70601) + put(data, 70533, 70532, 70587) + put(data, 70542, 70539, 70594) + put(data, 70545, 70544, 70601) + put(data, 70597, 70594, 70594) + put(data, 70599, 70594, 70584) + put(data, 70600, 70594, 70601) put(data, 70843, 70841, 70842) put(data, 70844, 70841, 70832) put(data, 70846, 70841, 70845) put(data, 71098, 71096, 71087) put(data, 71099, 71097, 71087) put(data, 71992, 71989, 71984) + put(data, 90401, 90398, 90398) + put(data, 90402, 90398, 90409) + put(data, 90403, 90398, 90399) + put(data, 90404, 90409, 90399) + put(data, 90405, 90398, 90400) + put(data, 90406, 90401, 90399) + put(data, 90407, 90402, 90399) + put(data, 90408, 90401, 90400) + put(data, 93544, 93543, 93543) + put(data, 93545, 93539, 93543) + put(data, 93546, 93545, 93543) put(data, 119134, 119127, 119141) put(data, 119135, 119128, 119141) put(data, 119136, 119135, 119150) @@ -8669,7 +8756,7 @@ module Unicode # codepoints. # Here we store: codepoint => {index, count} private class_getter compatibility_decompositions : Hash(Int32, {Int32, Int32}) do - data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 3796) + data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 3832) put(data, 160, 0, 1) put(data, 168, 1, 2) put(data, 170, 3, 1) @@ -11121,6 +11208,42 @@ module Unicode put(data, 67512, 2953, 1) put(data, 67513, 2954, 1) put(data, 67514, 2955, 1) + put(data, 117974, 119, 1) + put(data, 117975, 121, 1) + put(data, 117976, 248, 1) + put(data, 117977, 35, 1) + put(data, 117978, 122, 1) + put(data, 117979, 259, 1) + put(data, 117980, 124, 1) + put(data, 117981, 125, 1) + put(data, 117982, 24, 1) + put(data, 117983, 25, 1) + put(data, 117984, 126, 1) + put(data, 117985, 28, 1) + put(data, 117986, 127, 1) + put(data, 117987, 47, 1) + put(data, 117988, 128, 1) + put(data, 117989, 130, 1) + put(data, 117990, 263, 1) + put(data, 117991, 131, 1) + put(data, 117992, 264, 1) + put(data, 117993, 132, 1) + put(data, 117994, 133, 1) + put(data, 117995, 333, 1) + put(data, 117996, 134, 1) + put(data, 117997, 277, 1) + put(data, 117998, 606, 1) + put(data, 117999, 54, 1) + put(data, 118000, 229, 1) + put(data, 118001, 13, 1) + put(data, 118002, 6, 1) + put(data, 118003, 7, 1) + put(data, 118004, 17, 1) + put(data, 118005, 230, 1) + put(data, 118006, 231, 1) + put(data, 118007, 232, 1) + put(data, 118008, 233, 1) + put(data, 118009, 234, 1) put(data, 119808, 119, 1) put(data, 119809, 121, 1) put(data, 119810, 248, 1) @@ -12473,7 +12596,7 @@ module Unicode # composition exclusions. # Here we store: (first << 21 | second) => codepoint private class_getter canonical_compositions : Hash(Int64, Int32) do - data = Hash(Int64, Int32).new(initial_capacity: 941) + data = Hash(Int64, Int32).new(initial_capacity: 961) put(data, 136315648_i64, 192) put(data, 136315649_i64, 193) put(data, 136315650_i64, 194) @@ -13402,6 +13525,8 @@ module Unicode put(data, 26275229849_i64, 12537) put(data, 26277327001_i64, 12538) put(data, 26300395673_i64, 12542) + put(data, 140563710727_i64, 67017) + put(data, 140580487943_i64, 67044) put(data, 146349822138_i64, 69786) put(data, 146354016442_i64, 69788) put(data, 146374987962_i64, 69803) @@ -13409,12 +13534,30 @@ module Unicode put(data, 146670686503_i64, 69935) put(data, 147788469054_i64, 70475) put(data, 147788469079_i64, 70476) + put(data, 147912201161_i64, 70531) + put(data, 147916395451_i64, 70533) + put(data, 147931075522_i64, 70542) + put(data, 147941561289_i64, 70545) + put(data, 148046418882_i64, 70597) + put(data, 148046418872_i64, 70599) + put(data, 148046418889_i64, 70600) put(data, 148564415674_i64, 70843) put(data, 148564415664_i64, 70844) put(data, 148564415677_i64, 70846) put(data, 149099189679_i64, 71098) put(data, 149101286831_i64, 71099) put(data, 150971947312_i64, 71992) + put(data, 189578436894_i64, 90401) + put(data, 189578436905_i64, 90402) + put(data, 189578436895_i64, 90403) + put(data, 189601505567_i64, 90404) + put(data, 189578436896_i64, 90405) + put(data, 189584728351_i64, 90406) + put(data, 189586825503_i64, 90407) + put(data, 189584728352_i64, 90408) + put(data, 196173983079_i64, 93544) + put(data, 196165594471_i64, 93545) + put(data, 196178177383_i64, 93546) data end @@ -13422,7 +13565,7 @@ module Unicode # Form C (yes if absent in this table). # Here we store: {low, high, result (no or maybe)} private class_getter nfc_quick_check : Array({Int32, Int32, QuickCheckResult}) do - data = Array({Int32, Int32, QuickCheckResult}).new(117) + data = Array({Int32, Int32, QuickCheckResult}).new(124) put(data, 768, 772, QuickCheckResult::Maybe) put(data, 774, 780, QuickCheckResult::Maybe) put(data, 783, 783, QuickCheckResult::Maybe) @@ -13532,11 +13675,18 @@ module Unicode put(data, 69927, 69927, QuickCheckResult::Maybe) put(data, 70462, 70462, QuickCheckResult::Maybe) put(data, 70487, 70487, QuickCheckResult::Maybe) + put(data, 70584, 70584, QuickCheckResult::Maybe) + put(data, 70587, 70587, QuickCheckResult::Maybe) + put(data, 70594, 70594, QuickCheckResult::Maybe) + put(data, 70597, 70597, QuickCheckResult::Maybe) + put(data, 70599, 70601, QuickCheckResult::Maybe) put(data, 70832, 70832, QuickCheckResult::Maybe) put(data, 70842, 70842, QuickCheckResult::Maybe) put(data, 70845, 70845, QuickCheckResult::Maybe) put(data, 71087, 71087, QuickCheckResult::Maybe) put(data, 71984, 71984, QuickCheckResult::Maybe) + put(data, 90398, 90409, QuickCheckResult::Maybe) + put(data, 93543, 93544, QuickCheckResult::Maybe) put(data, 119134, 119140, QuickCheckResult::No) put(data, 119227, 119232, QuickCheckResult::No) put(data, 194560, 195101, QuickCheckResult::No) @@ -13547,7 +13697,7 @@ module Unicode # Form KC (yes if absent in this table). # Here we store: {low, high, result (no or maybe)} private class_getter nfkc_quick_check : Array({Int32, Int32, QuickCheckResult}) do - data = Array({Int32, Int32, QuickCheckResult}).new(436) + data = Array({Int32, Int32, QuickCheckResult}).new(445) put(data, 160, 160, QuickCheckResult::No) put(data, 168, 168, QuickCheckResult::No) put(data, 170, 170, QuickCheckResult::No) @@ -13891,11 +14041,20 @@ module Unicode put(data, 69927, 69927, QuickCheckResult::Maybe) put(data, 70462, 70462, QuickCheckResult::Maybe) put(data, 70487, 70487, QuickCheckResult::Maybe) + put(data, 70584, 70584, QuickCheckResult::Maybe) + put(data, 70587, 70587, QuickCheckResult::Maybe) + put(data, 70594, 70594, QuickCheckResult::Maybe) + put(data, 70597, 70597, QuickCheckResult::Maybe) + put(data, 70599, 70601, QuickCheckResult::Maybe) put(data, 70832, 70832, QuickCheckResult::Maybe) put(data, 70842, 70842, QuickCheckResult::Maybe) put(data, 70845, 70845, QuickCheckResult::Maybe) put(data, 71087, 71087, QuickCheckResult::Maybe) put(data, 71984, 71984, QuickCheckResult::Maybe) + put(data, 90398, 90409, QuickCheckResult::Maybe) + put(data, 93543, 93544, QuickCheckResult::Maybe) + put(data, 117974, 117999, QuickCheckResult::No) + put(data, 118000, 118009, QuickCheckResult::No) put(data, 119134, 119140, QuickCheckResult::No) put(data, 119227, 119232, QuickCheckResult::No) put(data, 119808, 119892, QuickCheckResult::No) @@ -13992,7 +14151,7 @@ module Unicode # codepoints contained here may not appear under NFD. # Here we store: {low, high} private class_getter nfd_quick_check : Array({Int32, Int32}) do - data = Array({Int32, Int32}).new(243) + data = Array({Int32, Int32}).new(253) put(data, 192, 197) put(data, 199, 207) put(data, 209, 214) @@ -14224,15 +14383,25 @@ module Unicode put(data, 64320, 64321) put(data, 64323, 64324) put(data, 64326, 64334) + put(data, 67017, 67017) + put(data, 67044, 67044) put(data, 69786, 69786) put(data, 69788, 69788) put(data, 69803, 69803) put(data, 69934, 69935) put(data, 70475, 70476) + put(data, 70531, 70531) + put(data, 70533, 70533) + put(data, 70542, 70542) + put(data, 70545, 70545) + put(data, 70597, 70597) + put(data, 70599, 70600) put(data, 70843, 70844) put(data, 70846, 70846) put(data, 71098, 71099) put(data, 71992, 71992) + put(data, 90401, 90408) + put(data, 93544, 93546) put(data, 119134, 119140) put(data, 119227, 119232) put(data, 194560, 195101) @@ -14244,7 +14413,7 @@ module Unicode # codepoints contained here may not appear under NFKD. # Here we store: {low, high} private class_getter nfkd_quick_check : Array({Int32, Int32}) do - data = Array({Int32, Int32}).new(548) + data = Array({Int32, Int32}).new(560) put(data, 160, 160) put(data, 168, 168) put(data, 170, 170) @@ -14693,6 +14862,8 @@ module Unicode put(data, 65512, 65512) put(data, 65513, 65516) put(data, 65517, 65518) + put(data, 67017, 67017) + put(data, 67044, 67044) put(data, 67457, 67461) put(data, 67463, 67504) put(data, 67506, 67514) @@ -14701,10 +14872,20 @@ module Unicode put(data, 69803, 69803) put(data, 69934, 69935) put(data, 70475, 70476) + put(data, 70531, 70531) + put(data, 70533, 70533) + put(data, 70542, 70542) + put(data, 70545, 70545) + put(data, 70597, 70597) + put(data, 70599, 70600) put(data, 70843, 70844) put(data, 70846, 70846) put(data, 71098, 71099) put(data, 71992, 71992) + put(data, 90401, 90408) + put(data, 93544, 93546) + put(data, 117974, 117999) + put(data, 118000, 118009) put(data, 119134, 119140) put(data, 119227, 119232) put(data, 119808, 119892) diff --git a/src/unicode/unicode.cr b/src/unicode/unicode.cr index 1fb4b530686b..ab49ea31368b 100644 --- a/src/unicode/unicode.cr +++ b/src/unicode/unicode.cr @@ -1,7 +1,7 @@ # Provides the `Unicode::CaseOptions` enum for special case conversions like Turkic. module Unicode # The currently supported [Unicode](https://home.unicode.org) version. - VERSION = "15.1.0" + VERSION = "16.0.0" # Case options to pass to various `Char` and `String` methods such as `upcase` or `downcase`. @[Flags] From 37da284f8d9ecab90c51b3f1aa8c125cebd4826d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 19 Sep 2024 21:21:03 +0800 Subject: [PATCH 1370/1551] Add missing `@[Link(dll:)]` annotation to MPIR (#15003) --- src/big/lib_gmp.cr | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index 5ae18b5a4606..c50b1f7f6e9b 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -1,5 +1,8 @@ {% if flag?(:win32) %} @[Link("mpir")] + {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} + @[Link(dll: "mpir.dll")] + {% end %} {% else %} @[Link("gmp")] {% end %} From c2488f6e9dea40d3225ed18a1a589db62973e482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 20 Sep 2024 11:54:06 +0200 Subject: [PATCH 1371/1551] Update previous Crystal release 1.13.3 (#15016) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 8 ++++---- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 2 +- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win_build_portable.yml | 2 +- bin/ci | 6 +++--- shell.nix | 12 ++++++------ 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 39984bc5aadb..5be7fd2cd388 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.13.2/crystal-1.13.2-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.13.3/crystal-1.13.3-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index ba32bb2dd2d6..aa28b15f9abc 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.2-build + image: crystallang/crystal:1.13.3-build name: "Test Interpreter" steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.2-build + image: crystallang/crystal:1.13.3-build name: Build interpreter steps: - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.2-build + image: crystallang/crystal:1.13.3-build strategy: matrix: part: [0, 1, 2, 3] @@ -67,7 +67,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.2-build + image: crystallang/crystal:1.13.3-build name: "Test primitives_spec with interpreter" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d1128ebdbca8..a729d5f7681d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2, 1.13.2] + crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2, 1.13.3] flags: [""] include: # libffi is only available starting from the 1.2.2 build images diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 152e2b5294b5..dab5b9bb74cd 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -58,7 +58,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.13.2" + crystal: "1.13.3" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 4eede5adf78c..30bc74844e2b 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: libssl_test: runs-on: ubuntu-latest name: "${{ matrix.pkg }}" - container: crystallang/crystal:1.13.2-alpine + container: crystallang/crystal:1.13.3-alpine strategy: fail-fast: false matrix: diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index 186192288895..9587c5fae85f 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.13.2-alpine + container: crystallang/crystal:1.13.3-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.13.2-alpine + container: crystallang/crystal:1.13.3-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 7ce32ee2d625..e35a9ad182fd 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.13.2-build + container: crystallang/crystal:1.13.3-build steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 98c428ee5bad..8f164ae5ddc8 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -25,7 +25,7 @@ jobs: uses: crystal-lang/install-crystal@v1 id: install-crystal with: - crystal: "1.13.2" + crystal: "1.13.3" - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index 4ca0eb96577e..d998373438e8 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.13.2/crystal-1.13.2-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.13.2-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.13.3/crystal-1.13.3-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.13.3-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.13.2}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.13.3}" case $ARCH in x86_64) diff --git a/shell.nix b/shell.nix index db69834a8a89..efadd688f0e3 100644 --- a/shell.nix +++ b/shell.nix @@ -53,18 +53,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.13.2/crystal-1.13.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:046zlsyrj1i769xh4jvv0a81nlqj7kiz0hliq1za86k1749kcmlz"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.13.3/crystal-1.13.3-1-darwin-universal.tar.gz"; + sha256 = "sha256:0iri1hl23kgmlibmm64wc4wdq019z544b7m2h1bl7jxs4dk2wwla"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.13.2/crystal-1.13.2-1-darwin-universal.tar.gz"; - sha256 = "sha256:046zlsyrj1i769xh4jvv0a81nlqj7kiz0hliq1za86k1749kcmlz"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.13.3/crystal-1.13.3-1-darwin-universal.tar.gz"; + sha256 = "sha256:0iri1hl23kgmlibmm64wc4wdq019z544b7m2h1bl7jxs4dk2wwla"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.13.2/crystal-1.13.2-1-linux-x86_64.tar.gz"; - sha256 = "sha256:0186q0y97135kvxa8bmzgqc24idv19jg4vglany0pkpzy8b3qs0s"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.13.3/crystal-1.13.3-1-linux-x86_64.tar.gz"; + sha256 = "sha256:1zf9b3njxx0jzn81dy6vyhkml31kjxfk4iskf13w9ysj0kwakbyz"; }; }.${pkgs.stdenv.system}); From d18482290bb5b069ed4852eda508b31f0213a98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 21 Sep 2024 11:24:07 +0200 Subject: [PATCH 1372/1551] Fix compiler artifact name in WindowsCI (#15021) Before https://github.com/crystal-lang/crystal/pull/15000 we had two different builds of the compiler in every CI run, a release build and a non-release. Now we only have a release build and there's no need to differentiate by name. Going back to `crystal` makes the install-crystal action work again. --- .github/workflows/win.yml | 6 +++--- .github/workflows/win_build_portable.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 89c13959e8cb..9025586fa991 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -252,7 +252,7 @@ jobs: - name: Download Crystal executable uses: actions/download-artifact@v4 with: - name: crystal-release + name: crystal path: build - name: Restore LLVM @@ -294,7 +294,7 @@ jobs: - name: Download Crystal executable uses: actions/download-artifact@v4 with: - name: crystal-release + name: crystal path: build - name: Restore LLVM @@ -330,7 +330,7 @@ jobs: - name: Download Crystal executable uses: actions/download-artifact@v4 with: - name: crystal-release + name: crystal path: etc/win-ci/portable - name: Restore LLVM diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 8f164ae5ddc8..d3265239c20c 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -145,5 +145,5 @@ jobs: - name: Upload Crystal binaries uses: actions/upload-artifact@v4 with: - name: ${{ inputs.release && 'crystal-release' || 'crystal' }} + name: crystal path: crystal From 5f018fcafbf020c4e965ffb401df5246d082aa59 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 21 Sep 2024 18:59:53 +0800 Subject: [PATCH 1373/1551] Reduce calls to `Crystal::Type#remove_indirection` in module dispatch (#14992) Module types are expanded into unions during Crystal's codegen phase when the receiver of a call has a module type. This patch makes this expansion occur once for the entire call, instead of once for each including type. --- src/compiler/crystal/codegen/call.cr | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/compiler/crystal/codegen/call.cr b/src/compiler/crystal/codegen/call.cr index 1b678232c054..5934ffeb0c14 100644 --- a/src/compiler/crystal/codegen/call.cr +++ b/src/compiler/crystal/codegen/call.cr @@ -340,7 +340,10 @@ class Crystal::CodeGenVisitor # Create self var if available if node_obj - new_vars["%self"] = LLVMVar.new(@last, node_obj.type, true) + # call `#remove_indirection` here so that the downcast call in + # `#visit(Var)` doesn't spend time expanding module types again and again + # (it should be the only use site of `node_obj.type`) + new_vars["%self"] = LLVMVar.new(@last, node_obj.type.remove_indirection, true) end # Get type if of args and create arg vars @@ -359,6 +362,10 @@ class Crystal::CodeGenVisitor is_super = node.super? + # call `#remove_indirection` here so that the `match_type_id` below doesn't + # spend time expanding module types again and again + owner = owner.remove_indirection unless is_super + with_cloned_context do context.vars = new_vars From c74f6bc6523510d9d8a86640b2b180bafa40649d Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sat, 21 Sep 2024 13:00:29 +0200 Subject: [PATCH 1374/1551] Compiler: enable parallel codegen with MT (#14748) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements parallel codegen of object files when MT is enabled in the compiler (`-Dpreview_mt`). It only impacts codegen for compilations with more than one compilation unit (module), that is when neither of `--single-module`, `--release` or `--cross-compile` is specified. This behavior is identical to the fork based codegen. **Advantages:** - allows parallel codegen on Windows (untested); - no need to fork many processes; - no repeated GC collections in each forked processes; - a simple Channel to distribute work efficiently (no need for IPC). The main points are increased portability and simpler logic, despite having to take care of LLVM thread safety quirks (see comments). Co-authored-by: Johannes Müller --- src/compiler/crystal/compiler.cr | 251 +++++++++++++++++++------------ src/llvm/lib_llvm/bit_reader.cr | 5 + src/llvm/module.cr | 6 + 3 files changed, 164 insertions(+), 98 deletions(-) create mode 100644 src/llvm/lib_llvm/bit_reader.cr diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index f25713c6385e..0d7ba0ff12f9 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -5,6 +5,9 @@ require "crystal/digest/md5" {% if flag?(:msvc) %} require "./loader" {% end %} +{% if flag?(:preview_mt) %} + require "wait_group" +{% end %} module Crystal @[Flags] @@ -80,7 +83,13 @@ module Crystal property? no_codegen = false # Maximum number of LLVM modules that are compiled in parallel - property n_threads : Int32 = {% if flag?(:preview_mt) || flag?(:win32) %} 1 {% else %} 8 {% end %} + property n_threads : Int32 = {% if flag?(:preview_mt) %} + ENV["CRYSTAL_WORKERS"]?.try(&.to_i?) || 4 + {% elsif flag?(:win32) %} + 1 + {% else %} + 8 + {% end %} # Default prelude file to use. This ends up adding a # `require "prelude"` (or whatever name is set here) to @@ -328,6 +337,12 @@ module Crystal CompilationUnit.new(self, program, type_name, llvm_mod, output_dir, bc_flags_changed) end + {% if LibLLVM::IS_LT_170 %} + # initialize the legacy pass manager once in the main thread/process + # before we start codegen in threads (MT) or processes (fork) + init_llvm_legacy_pass_manager unless optimization_mode.o0? + {% end %} + if @cross_compile cross_compile program, units, output_filename else @@ -391,7 +406,7 @@ module Crystal llvm_mod = unit.llvm_mod @progress_tracker.stage("Codegen (bc+obj)") do - optimize llvm_mod unless @optimization_mode.o0? + optimize llvm_mod, target_machine unless @optimization_mode.o0? unit.emit(@emit_targets, emit_base_filename || output_filename) @@ -529,7 +544,8 @@ module Crystal private def parallel_codegen(units, n_threads) {% if flag?(:preview_mt) %} - raise "Cannot fork compiler in multithread mode." + raise "LLVM isn't multithreaded and cannot fork compiler in multithread mode." unless LLVM.multithreaded? + mt_codegen(units, n_threads) {% elsif LibC.has_method?("fork") %} fork_codegen(units, n_threads) {% else %} @@ -537,6 +553,39 @@ module Crystal {% end %} end + private def mt_codegen(units, n_threads) + channel = Channel(CompilationUnit).new(n_threads * 2) + wg = WaitGroup.new + mutex = Mutex.new + + n_threads.times do + wg.spawn do + while unit = channel.receive? + unit.compile(isolate_context: true) + mutex.synchronize { @progress_tracker.stage_progress += 1 } + end + end + end + + units.each do |unit| + # We generate the bitcode in the main thread because LLVM contexts + # must be unique per compilation unit, but we share different contexts + # across many modules (or rely on the global context); trying to + # codegen in parallel would segfault! + # + # Luckily generating the bitcode is quick and once the bitcode is + # generated we don't need the global LLVM contexts anymore but can + # parse the bitcode in an isolated context and we can parallelize the + # slowest part: the optimization pass & compiling the object file. + unit.generate_bitcode + + channel.send(unit) + end + channel.close + + wg.wait + end + private def fork_codegen(units, n_threads) workers = fork_workers(n_threads) do |input, output| while i = input.gets(chomp: true).presence @@ -677,9 +726,10 @@ module Crystal puts puts "Codegen (bc+obj):" - if units.size == reused + case reused + when units.size puts " - all previous .o files were reused" - elsif reused == 0 + when .zero? puts " - no previous .o files were reused" else puts " - #{reused}/#{units.size} .o files were reused" @@ -706,61 +756,52 @@ module Crystal end {% if LibLLVM::IS_LT_170 %} + property! pass_manager_builder : LLVM::PassManagerBuilder + + private def init_llvm_legacy_pass_manager + registry = LLVM::PassRegistry.instance + registry.initialize_all + + builder = LLVM::PassManagerBuilder.new + builder.size_level = 0 + + case optimization_mode + in .o3? + builder.opt_level = 3 + builder.use_inliner_with_threshold = 275 + in .o2? + builder.opt_level = 2 + builder.use_inliner_with_threshold = 275 + in .o1? + builder.opt_level = 1 + builder.use_inliner_with_threshold = 150 + in .o0? + # default behaviour, no optimizations + in .os? + builder.opt_level = 2 + builder.size_level = 1 + builder.use_inliner_with_threshold = 50 + in .oz? + builder.opt_level = 2 + builder.size_level = 2 + builder.use_inliner_with_threshold = 5 + end + + @pass_manager_builder = builder + end + private def optimize_with_pass_manager(llvm_mod) fun_pass_manager = llvm_mod.new_function_pass_manager pass_manager_builder.populate fun_pass_manager fun_pass_manager.run llvm_mod - module_pass_manager.run llvm_mod - end - - @module_pass_manager : LLVM::ModulePassManager? - - private def module_pass_manager - @module_pass_manager ||= begin - mod_pass_manager = LLVM::ModulePassManager.new - pass_manager_builder.populate mod_pass_manager - mod_pass_manager - end - end - @pass_manager_builder : LLVM::PassManagerBuilder? - - private def pass_manager_builder - @pass_manager_builder ||= begin - registry = LLVM::PassRegistry.instance - registry.initialize_all - - builder = LLVM::PassManagerBuilder.new - builder.size_level = 0 - - case optimization_mode - in .o3? - builder.opt_level = 3 - builder.use_inliner_with_threshold = 275 - in .o2? - builder.opt_level = 2 - builder.use_inliner_with_threshold = 275 - in .o1? - builder.opt_level = 1 - builder.use_inliner_with_threshold = 150 - in .o0? - # default behaviour, no optimizations - in .os? - builder.opt_level = 2 - builder.size_level = 1 - builder.use_inliner_with_threshold = 50 - in .oz? - builder.opt_level = 2 - builder.size_level = 2 - builder.use_inliner_with_threshold = 5 - end - - builder - end + module_pass_manager = LLVM::ModulePassManager.new + pass_manager_builder.populate module_pass_manager + module_pass_manager.run llvm_mod end {% end %} - protected def optimize(llvm_mod) + protected def optimize(llvm_mod, target_machine) {% if LibLLVM::IS_LT_130 %} optimize_with_pass_manager(llvm_mod) {% else %} @@ -836,6 +877,9 @@ module Crystal getter llvm_mod property? reused_previous_compilation = false getter object_extension : String + @memory_buffer : LLVM::MemoryBuffer? + @object_name : String? + @bc_name : String? def initialize(@compiler : Compiler, program : Program, @name : String, @llvm_mod : LLVM::Module, @output_dir : String, @bc_flags_changed : Bool) @@ -865,40 +909,44 @@ module Crystal @object_extension = compiler.codegen_target.object_extension end - def compile - compile_to_object + def generate_bitcode + @memory_buffer ||= llvm_mod.write_bitcode_to_memory_buffer end - private def compile_to_object - bc_name = self.bc_name - object_name = self.object_name - temporary_object_name = self.temporary_object_name + # To compile a file we first generate a `.bc` file and then create an + # object file from it. These `.bc` files are stored in the cache + # directory. + # + # On a next compilation of the same project, and if the compile flags + # didn't change (a combination of the target triple, mcpu and link flags, + # amongst others), we check if the new `.bc` file is exactly the same as + # the old one. In that case the `.o` file will also be the same, so we + # simply reuse the old one. Generating an `.o` file is what takes most + # time. + # + # However, instead of directly generating the final `.o` file from the + # `.bc` file, we generate it to a temporary name (`.o.tmp`) and then we + # rename that file to `.o`. We do this because the compiler could be + # interrupted while the `.o` file is being generated, leading to a + # corrupted file that later would cause compilation issues. Moving a file + # is an atomic operation so no corrupted `.o` file should be generated. + def compile(isolate_context = false) + if must_compile? + isolate_module_context if isolate_context + update_bitcode_cache + compile_to_object + else + @reused_previous_compilation = true + end + dump_llvm_ir + end + + private def must_compile? + memory_buffer = generate_bitcode - # To compile a file we first generate a `.bc` file and then - # create an object file from it. These `.bc` files are stored - # in the cache directory. - # - # On a next compilation of the same project, and if the compile - # flags didn't change (a combination of the target triple, mcpu - # and link flags, amongst others), we check if the new - # `.bc` file is exactly the same as the old one. In that case - # the `.o` file will also be the same, so we simply reuse the - # old one. Generating an `.o` file is what takes most time. - # - # However, instead of directly generating the final `.o` file - # from the `.bc` file, we generate it to a temporary name (`.o.tmp`) - # and then we rename that file to `.o`. We do this because the compiler - # could be interrupted while the `.o` file is being generated, leading - # to a corrupted file that later would cause compilation issues. - # Moving a file is an atomic operation so no corrupted `.o` file should - # be generated. - - must_compile = true can_reuse_previous_compilation = compiler.emit_targets.none? && !@bc_flags_changed && File.exists?(bc_name) && File.exists?(object_name) - memory_buffer = llvm_mod.write_bitcode_to_memory_buffer - if can_reuse_previous_compilation memory_io = IO::Memory.new(memory_buffer.to_slice) changed = File.open(bc_name) { |bc_file| !IO.same_content?(bc_file, memory_io) } @@ -906,32 +954,39 @@ module Crystal # If the user cancelled a previous compilation # it might be that the .o file is empty if !changed && File.size(object_name) > 0 - must_compile = false memory_buffer.dispose - memory_buffer = nil + return false else # We need to compile, so we'll write the memory buffer to file end end - # If there's a memory buffer, it means we must create a .o from it - if memory_buffer - # Delete existing .o file. It cannot be used anymore. - File.delete?(object_name) - # Create the .bc file (for next compilations) - File.write(bc_name, memory_buffer.to_slice) - memory_buffer.dispose - end + true + end - if must_compile - compiler.optimize llvm_mod unless compiler.optimization_mode.o0? - compiler.target_machine.emit_obj_to_file llvm_mod, temporary_object_name - File.rename(temporary_object_name, object_name) - else - @reused_previous_compilation = true - end + # Parse the previously generated bitcode into the LLVM module using a + # dedicated context, so we can safely optimize & compile the module in + # multiple threads (llvm contexts can't be shared across threads). + private def isolate_module_context + @llvm_mod = LLVM::Module.parse(@memory_buffer.not_nil!, LLVM::Context.new) + end - dump_llvm_ir + private def update_bitcode_cache + return unless memory_buffer = @memory_buffer + + # Delete existing .o file. It cannot be used anymore. + File.delete?(object_name) + # Create the .bc file (for next compilations) + File.write(bc_name, memory_buffer.to_slice) + memory_buffer.dispose + end + + private def compile_to_object + temporary_object_name = self.temporary_object_name + target_machine = compiler.create_target_machine + compiler.optimize llvm_mod, target_machine unless compiler.optimization_mode.o0? + target_machine.emit_obj_to_file llvm_mod, temporary_object_name + File.rename(temporary_object_name, object_name) end private def dump_llvm_ir diff --git a/src/llvm/lib_llvm/bit_reader.cr b/src/llvm/lib_llvm/bit_reader.cr new file mode 100644 index 000000000000..9bfd271cbbe2 --- /dev/null +++ b/src/llvm/lib_llvm/bit_reader.cr @@ -0,0 +1,5 @@ +require "./types" + +lib LibLLVM + fun parse_bitcode_in_context2 = LLVMParseBitcodeInContext2(c : ContextRef, mb : MemoryBufferRef, m : ModuleRef*) : Int +end diff --git a/src/llvm/module.cr b/src/llvm/module.cr index f216d485055c..32b025bffee7 100644 --- a/src/llvm/module.cr +++ b/src/llvm/module.cr @@ -6,6 +6,12 @@ class LLVM::Module getter context : Context + def self.parse(memory_buffer : MemoryBuffer, context : Context) : self + LibLLVM.parse_bitcode_in_context2(context, memory_buffer, out module_ref) + raise "BUG: failed to parse LLVM bitcode from memory buffer" unless module_ref + new(module_ref, context) + end + def initialize(@unwrap : LibLLVM::ModuleRef, @context : Context) @owned = false end From b679b563db6cde5b633bd8bf92f32e3e476d2b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 21 Sep 2024 13:00:58 +0200 Subject: [PATCH 1375/1551] Add `Exception::CallStack.empty` (#15017) This is a follow-up on https://github.com/crystal-lang/crystal/pull/15002 which explicitly assigns a dummy callstack to `RetryLookupWithLiterals` for performance reasons. `CallStack.empty` is intended to make this use case a bit more ergonomical. It doesn't require allocating a dummy instance with fake data. Instead, it's an explicitly empty callstack. This makes this mechanism easier to re-use in other places (ref https://github.com/crystal-lang/crystal/issues/11658#issuecomment-2352649774). --- src/compiler/crystal/semantic/call.cr | 4 +--- src/exception/call_stack.cr | 7 +++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index e265829a919e..1fa4379d543e 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -13,10 +13,8 @@ class Crystal::Call property? uses_with_scope = false class RetryLookupWithLiterals < ::Exception - @@dummy_call_stack = Exception::CallStack.new - def initialize - self.callstack = @@dummy_call_stack + self.callstack = Exception::CallStack.empty end end diff --git a/src/exception/call_stack.cr b/src/exception/call_stack.cr index c80f73a6ce48..09173f2e5500 100644 --- a/src/exception/call_stack.cr +++ b/src/exception/call_stack.cr @@ -31,8 +31,11 @@ struct Exception::CallStack @callstack : Array(Void*) @backtrace : Array(String)? - def initialize - @callstack = CallStack.unwind + def initialize(@callstack : Array(Void*) = CallStack.unwind) + end + + def self.empty + new([] of Void*) end def printable_backtrace : Array(String) From eda63e656a3f0ceeeed2dee9f0fd3e5e9db245a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 24 Sep 2024 16:14:40 +0200 Subject: [PATCH 1376/1551] Cache `Exception::CallStack.empty` to avoid repeat `Array` allocation (#15025) --- src/exception/call_stack.cr | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/exception/call_stack.cr b/src/exception/call_stack.cr index 09173f2e5500..be631e19cdc7 100644 --- a/src/exception/call_stack.cr +++ b/src/exception/call_stack.cr @@ -34,9 +34,7 @@ struct Exception::CallStack def initialize(@callstack : Array(Void*) = CallStack.unwind) end - def self.empty - new([] of Void*) - end + class_getter empty = new([] of Void*) def printable_backtrace : Array(String) @backtrace ||= decode_backtrace From 6a81bb0f120ceef75a7f64fa59f81a4cdf153c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 25 Sep 2024 12:56:17 +0200 Subject: [PATCH 1377/1551] Add documentation about synchronous DNS resolution (#15027) --- src/socket/addrinfo.cr | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr index ef76d0e285b6..411c09143411 100644 --- a/src/socket/addrinfo.cr +++ b/src/socket/addrinfo.cr @@ -4,6 +4,19 @@ require "crystal/system/addrinfo" class Socket # Domain name resolver. + # + # # Query Concurrency Behaviour + # + # On most platforms, DNS queries are currently resolved synchronously. + # Calling a resolve method blocks the entire thread until it returns. + # This can cause latencies, especially in single-threaded processes. + # + # DNS queries resolve asynchronously on the following platforms: + # + # * Windows 8 and higher + # + # NOTE: Follow the discussion in [Async DNS resolution (#13619)](https://github.com/crystal-lang/crystal/issues/13619) + # for more details. struct Addrinfo include Crystal::System::Addrinfo From cde543a762d56324e1d1261154d767c84db1ebc1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 25 Sep 2024 18:56:29 +0800 Subject: [PATCH 1378/1551] Fix `Slice.literal` for multiple calls with identical signature (#15009) --- spec/primitives/slice_spec.cr | 7 ++ src/compiler/crystal/program.cr | 4 +- src/compiler/crystal/semantic/main_visitor.cr | 105 ++++++++++-------- 3 files changed, 69 insertions(+), 47 deletions(-) diff --git a/spec/primitives/slice_spec.cr b/spec/primitives/slice_spec.cr index 546ae0de5ce1..98bea774df8b 100644 --- a/spec/primitives/slice_spec.cr +++ b/spec/primitives/slice_spec.cr @@ -12,6 +12,13 @@ describe "Primitives: Slice" do slice.to_a.should eq([0, 1, 4, 9, 16, 25] of {{ num }}) slice.read_only?.should be_true end + + # TODO: these should probably return the same pointers + pending_interpreted "creates multiple literals" do + slice1 = Slice({{ num }}).literal(1, 2, 3) + slice2 = Slice({{ num }}).literal(1, 2, 3) + slice1.should eq(slice2) + end {% end %} end end diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index b1cc99f0dfc6..c262a2d9770a 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -205,6 +205,8 @@ module Crystal types["Regex"] = @regex = NonGenericClassType.new self, self, "Regex", reference types["Range"] = range = @range = GenericClassType.new self, self, "Range", struct_t, ["B", "E"] range.struct = true + types["Slice"] = slice = @slice = GenericClassType.new self, self, "Slice", struct_t, ["T"] + slice.struct = true types["Exception"] = @exception = NonGenericClassType.new self, self, "Exception", reference @@ -528,7 +530,7 @@ module Crystal {% for name in %w(object no_return value number reference void nil bool char int int8 int16 int32 int64 int128 uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol pointer enumerable indexable - array static_array exception tuple named_tuple proc union enum range regex crystal + array static_array exception tuple named_tuple proc union enum range slice regex crystal packed_annotation thread_local_annotation no_inline_annotation always_inline_annotation naked_annotation returns_twice_annotation raises_annotation primitive_annotation call_convention_annotation diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index c33c64e893ff..ea5626c37f94 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -1313,6 +1313,10 @@ module Crystal if check_special_new_call(node, obj.type?) return false end + + if check_slice_literal_call(node, obj.type?) + return false + end end args.each &.accept(self) @@ -1567,6 +1571,60 @@ module Crystal false end + def check_slice_literal_call(node, obj_type) + return false unless obj_type + return false unless obj_type.metaclass? + + instance_type = obj_type.instance_type.remove_typedef + + if node.name == "literal" + case instance_type + when GenericClassType # Slice + return false unless instance_type == @program.slice + node.raise "TODO: implement slice_literal primitive for Slice without generic arguments" + when GenericClassInstanceType # Slice(T) + return false unless instance_type.generic_type == @program.slice + + element_type = instance_type.type_vars["T"].type + kind = case element_type + when IntegerType + element_type.kind + when FloatType + element_type.kind + else + node.raise "Only slice literals of primitive integer or float types can be created" + end + + node.args.each do |arg| + arg.raise "Expected NumberLiteral, got #{arg.class_desc}" unless arg.is_a?(NumberLiteral) + arg.accept self + arg.raise "Argument out of range for a Slice(#{element_type})" unless arg.representable_in?(element_type) + end + + # create the internal constant `$Slice:n` to hold the slice contents + const_name = "$Slice:#{@program.const_slices.size}" + const_value = Nop.new + const_value.type = @program.static_array_of(element_type, node.args.size) + const = Const.new(@program, @program, const_name, const_value) + @program.types[const_name] = const + @program.const_slices << Program::ConstSliceInfo.new(const_name, kind, node.args) + + # ::Slice.new(pointerof($Slice:n.@buffer), {{ args.size }}, read_only: true) + pointer_node = PointerOf.new(ReadInstanceVar.new(Path.new(const_name).at(node), "@buffer").at(node)).at(node) + size_node = NumberLiteral.new(node.args.size.to_s, :i32).at(node) + read_only_node = NamedArgument.new("read_only", BoolLiteral.new(true).at(node)).at(node) + expanded = Call.new(Path.global("Slice").at(node), "new", [pointer_node, size_node], named_args: [read_only_node]).at(node) + + expanded.accept self + node.bind_to expanded + node.expanded = expanded + return true + end + end + + false + end + # Rewrite: # # LibFoo::Struct.new arg0: value0, argN: value0 @@ -2308,7 +2366,7 @@ module Crystal when "pointer_new" visit_pointer_new node when "slice_literal" - visit_slice_literal node + node.raise "BUG: Slice literal should have been expanded" when "argc" # Already typed when "argv" @@ -2466,51 +2524,6 @@ module Crystal node.type = scope.instance_type end - def visit_slice_literal(node) - call = self.call.not_nil! - - case slice_type = scope.instance_type - when GenericClassType # Slice - call.raise "TODO: implement slice_literal primitive for Slice without generic arguments" - when GenericClassInstanceType # Slice(T) - element_type = slice_type.type_vars["T"].type - kind = case element_type - when IntegerType - element_type.kind - when FloatType - element_type.kind - else - call.raise "Only slice literals of primitive integer or float types can be created" - end - - call.args.each do |arg| - arg.raise "Expected NumberLiteral, got #{arg.class_desc}" unless arg.is_a?(NumberLiteral) - arg.raise "Argument out of range for a Slice(#{element_type})" unless arg.representable_in?(element_type) - end - - # create the internal constant `$Slice:n` to hold the slice contents - const_name = "$Slice:#{@program.const_slices.size}" - const_value = Nop.new - const_value.type = @program.static_array_of(element_type, call.args.size) - const = Const.new(@program, @program, const_name, const_value) - @program.types[const_name] = const - @program.const_slices << Program::ConstSliceInfo.new(const_name, kind, call.args) - - # ::Slice.new(pointerof($Slice:n.@buffer), {{ args.size }}, read_only: true) - pointer_node = PointerOf.new(ReadInstanceVar.new(Path.new(const_name).at(node), "@buffer").at(node)).at(node) - size_node = NumberLiteral.new(call.args.size.to_s, :i32).at(node) - read_only_node = NamedArgument.new("read_only", BoolLiteral.new(true).at(node)).at(node) - extra = Call.new(Path.global("Slice").at(node), "new", [pointer_node, size_node], named_args: [read_only_node]).at(node) - - extra.accept self - node.extra = extra - node.type = slice_type - call.expanded = extra - else - node.raise "BUG: Unknown scope for slice_literal primitive" - end - end - def visit_struct_or_union_set(node) scope = @scope.as(NonGenericClassType) From 9df6760292f525d4a35c243f1d76e7ab534cec44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 25 Sep 2024 22:29:28 +0200 Subject: [PATCH 1379/1551] Restrict CI runners from runs-on to 8GB (#15030) --- .github/workflows/aarch64.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index 85a8af2c8b37..3df14588e0fc 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -26,7 +26,7 @@ jobs: src/llvm/ext/llvm_ext.o aarch64-musl-test-stdlib: needs: aarch64-musl-build - runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] + runs-on: [runs-on, runner=2cpu-linux-arm64, "family=m7g", ram=8, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -43,7 +43,7 @@ jobs: args: make std_spec aarch64-musl-test-compiler: needs: aarch64-musl-build - runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] + runs-on: [runs-on, runner=2cpu-linux-arm64, "family=m7g", ram=8, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -77,7 +77,7 @@ jobs: src/llvm/ext/llvm_ext.o aarch64-gnu-test-stdlib: needs: aarch64-gnu-build - runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] + runs-on: [runs-on, runner=2cpu-linux-arm64, "family=m7g", ram=8, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -94,7 +94,7 @@ jobs: args: make std_spec aarch64-gnu-test-compiler: needs: aarch64-gnu-build - runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] + runs-on: [runs-on, runner=2cpu-linux-arm64, "family=m7g", ram=8, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source From 48883e2428f5931fbc53a43b8366caffe29d1bfa Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 26 Sep 2024 16:12:08 +0800 Subject: [PATCH 1380/1551] Update `DeallocationStack` for Windows context switch (#15032) Calling `LibC.GetCurrentThreadStackLimits` outside the main fiber of a thread will continue to return the original stack top of that thread: ```crystal LibC.GetCurrentThreadStackLimits(out low_limit, out high_limit) low_limit # => 86767566848 high_limit # => 86775955456 spawn do LibC.GetCurrentThreadStackLimits(out low_limit2, out high_limit2) low_limit2 # => 86767566848 high_limit2 # => 1863570554880 end sleep 1.second ``` This doesn't affect the Crystal runtime because the function is only called once to initialize the main thread, nor the GC because [it probes the stack top using `VirtualQuery`](https://github.com/ivmai/bdwgc/blob/39394464633d64131690b56fca7379924d9a5abe/win32_threads.c#L656-L672). It may nonetheless affect other external libraries because the bad stack will most certainly contain unmapped memory. The correct way is to also update the [Win32-specific, non-NT `DeallocationStack` field](https://en.wikipedia.org/w/index.php?title=Win32_Thread_Information_Block&oldid=1194048970#Stack_information_stored_in_the_TIB), since this is the actual stack top that gets returned (see also [Wine's implementation](https://gitlab.winehq.org/wine/wine/-/blob/a4e77b33f6897d930261634c1b3ba5e4edc209f3/dlls/kernelbase/thread.c#L131-135)). --- src/fiber/context/x86_64-microsoft.cr | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/fiber/context/x86_64-microsoft.cr b/src/fiber/context/x86_64-microsoft.cr index 55d893cb8184..83f95ea7b069 100644 --- a/src/fiber/context/x86_64-microsoft.cr +++ b/src/fiber/context/x86_64-microsoft.cr @@ -4,19 +4,20 @@ class Fiber # :nodoc: def makecontext(stack_ptr, fiber_main) : Nil # A great explanation on stack contexts for win32: - # https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/supporting-windows + # https://web.archive.org/web/20220527113808/https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/supporting-windows - # 8 registers + 2 qwords for NT_TIB + 1 parameter + 10 128bit XMM registers - @context.stack_top = (stack_ptr - (11 + 10*2)).as(Void*) + # 8 registers + 3 qwords for NT_TIB + 1 parameter + 10 128bit XMM registers + @context.stack_top = (stack_ptr - (12 + 10*2)).as(Void*) @context.resumable = 1 stack_ptr[0] = fiber_main.pointer # %rbx: Initial `resume` will `ret` to this address stack_ptr[-1] = self.as(Void*) # %rcx: puts `self` as first argument for `fiber_main` - # The following two values are stored in the Thread Information Block (NT_TIB) + # The following three values are stored in the Thread Information Block (NT_TIB) # and are used by Windows to track the current stack limits - stack_ptr[-2] = @stack # %gs:0x10: Stack Limit - stack_ptr[-3] = @stack_bottom # %gs:0x08: Stack Base + stack_ptr[-2] = @stack # %gs:0x1478: Win32 DeallocationStack + stack_ptr[-3] = @stack # %gs:0x10: Stack Limit + stack_ptr[-4] = @stack_bottom # %gs:0x08: Stack Base end # :nodoc: @@ -27,6 +28,7 @@ class Fiber # %rcx , %rdx asm(" pushq %rcx + pushq %gs:0x1478 // Thread Information Block: Win32 DeallocationStack pushq %gs:0x10 // Thread Information Block: Stack Limit pushq %gs:0x08 // Thread Information Block: Stack Base pushq %rdi // push 1st argument (because of initial resume) @@ -73,6 +75,7 @@ class Fiber popq %rdi // pop 1st argument (for initial resume) popq %gs:0x08 popq %gs:0x10 + popq %gs:0x1478 popq %rcx ") {% else %} @@ -80,6 +83,7 @@ class Fiber # instructions that breaks the context switching. asm(" pushq %rcx + pushq %gs:0x1478 // Thread Information Block: Win32 DeallocationStack pushq %gs:0x10 // Thread Information Block: Stack Limit pushq %gs:0x08 // Thread Information Block: Stack Base pushq %rdi // push 1st argument (because of initial resume) @@ -126,6 +130,7 @@ class Fiber popq %rdi // pop 1st argument (for initial resume) popq %gs:0x08 popq %gs:0x10 + popq %gs:0x1478 popq %rcx " :: "r"(current_context), "r"(new_context)) {% end %} From 7c3bf20d7cba9ea509429be83181eda410f19070 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 27 Sep 2024 04:51:13 +0800 Subject: [PATCH 1381/1551] Add missing return type of `LibC.VirtualQuery` (#15036) This function is only used when `-Dwin7` is specified and its return value was unused. --- src/crystal/system/win32/thread.cr | 4 +++- src/lib_c/x86_64-windows-msvc/c/memoryapi.cr | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr index 1a4f61a41738..652f5487498f 100644 --- a/src/crystal/system/win32/thread.cr +++ b/src/crystal/system/win32/thread.cr @@ -76,7 +76,9 @@ module Crystal::System::Thread {% else %} tib = LibC.NtCurrentTeb high_limit = tib.value.stackBase - LibC.VirtualQuery(tib.value.stackLimit, out mbi, sizeof(LibC::MEMORY_BASIC_INFORMATION)) + if LibC.VirtualQuery(tib.value.stackLimit, out mbi, sizeof(LibC::MEMORY_BASIC_INFORMATION)) == 0 + raise RuntimeError.from_winerror("VirtualQuery") + end low_limit = mbi.allocationBase low_limit {% end %} diff --git a/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr b/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr index 7b0103713d8a..0ea28b8262f6 100644 --- a/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr @@ -11,5 +11,5 @@ lib LibC fun VirtualFree(lpAddress : Void*, dwSize : SizeT, dwFreeType : DWORD) : BOOL fun VirtualProtect(lpAddress : Void*, dwSize : SizeT, flNewProtect : DWORD, lpfOldProtect : DWORD*) : BOOL - fun VirtualQuery(lpAddress : Void*, lpBuffer : MEMORY_BASIC_INFORMATION*, dwLength : SizeT) + fun VirtualQuery(lpAddress : Void*, lpBuffer : MEMORY_BASIC_INFORMATION*, dwLength : SizeT) : SizeT end From d58ede5bacba23fbefed9040066aacec44ca953d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 27 Sep 2024 04:52:33 +0800 Subject: [PATCH 1382/1551] Fix Linux `getrandom` failure in interpreted code (#15035) Whereas the syscall returns the negative of the `Errno` value on failure, the `LibC` function returns -1 and then sets `Errno.value`. Crystal always assumes the former: ```cr err = Errno.new(-read_bytes.to_i) if err.in?(Errno::EINTR, Errno::EAGAIN) ::Fiber.yield else raise RuntimeError.from_os_error("getrandom", err) end ``` https://github.com/crystal-lang/crystal/blob/cde543a762d56324e1d1261154d767c84db1ebc1/src/crystal/system/unix/getrandom.cr#L105-L110 As `EPERM` equals 1 on Linux, _all_ failures are treated like `EPERM` in interpreted code, even though `EPERM` itself is not listed as an error for [`getrandom(2)`](https://man7.org/linux/man-pages/man2/getrandom.2.html), hence the "Operation not permitted". The same can probably happen on other Linux distros if you run out of entropy. --- src/crystal/system/unix/getrandom.cr | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/crystal/system/unix/getrandom.cr b/src/crystal/system/unix/getrandom.cr index 229716a3d846..e759ff0406e6 100644 --- a/src/crystal/system/unix/getrandom.cr +++ b/src/crystal/system/unix/getrandom.cr @@ -13,7 +13,10 @@ require "./syscall" # TODO: Implement syscall for interpreter def self.getrandom(buf : UInt8*, buflen : LibC::SizeT, flags : UInt32) : LibC::SSizeT - LibC.getrandom(buf, buflen, flags) + # the syscall returns the negative of errno directly, the C function + # doesn't, so we mimic the syscall behavior + read_bytes = LibC.getrandom(buf, buflen, flags) + read_bytes >= 0 ? read_bytes : LibC::SSizeT.new(-Errno.value.value) end end {% end %} From 347e7d6b95fe3b612ac8447e11c3ff58d2a26f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 27 Sep 2024 19:09:36 +0200 Subject: [PATCH 1383/1551] Increase memory for stdlib CI runners from runs-on to 16GB (#15044) #15030 reduced the runner instances for all CI jobs to machines with 8GB. This looked fine at first, but the `test-stdlib` jobs have been unstable since merging. This patch increases runners to bigger instances with more memory to bring stability. --- .github/workflows/aarch64.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index 3df14588e0fc..aec37c3860e1 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -26,7 +26,7 @@ jobs: src/llvm/ext/llvm_ext.o aarch64-musl-test-stdlib: needs: aarch64-musl-build - runs-on: [runs-on, runner=2cpu-linux-arm64, "family=m7g", ram=8, "run-id=${{ github.run_id }}"] + runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source @@ -77,7 +77,7 @@ jobs: src/llvm/ext/llvm_ext.o aarch64-gnu-test-stdlib: needs: aarch64-gnu-build - runs-on: [runs-on, runner=2cpu-linux-arm64, "family=m7g", ram=8, "run-id=${{ github.run_id }}"] + runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"] if: github.repository == 'crystal-lang/crystal' steps: - name: Download Crystal source From 7aeba1e508dc44e4bcc1d0c4bf989b7321906f0d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 28 Sep 2024 21:12:12 +0800 Subject: [PATCH 1384/1551] Fix undefined behavior in interpreter mixed union upcast (#15042) The interpreter upcasts a value to a mixed union by placing it on top of the stack, and then copying the data portion to a higher position to reserve space for the type ID. Hence, when the size of the value exceeds that of the type ID, the `copy_to` here would occur between two overlapping ranges: ```cr tmp_stack = stack stack_grow_by(union_size - from_size) (tmp_stack - from_size).copy_to(tmp_stack - from_size + type_id_bytesize, from_size) (tmp_stack - from_size).as(Int64*).value = type_id.to_i64! ``` This is undefined behavior in both ISO C and POSIX. Instead `move_to` must be used here (and most likely in a few other places too). This patch also changes the `move_to` in the tuple indexers to `move_from`, although in practice these don't exhibit unexpected behavior, because most `memcpy` implementations copy data from lower addresses to higher addresses, and these calls move data to a lower address. --- spec/compiler/interpreter/unions_spec.cr | 7 +++++++ src/compiler/crystal/interpreter/instructions.cr | 8 +++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/spec/compiler/interpreter/unions_spec.cr b/spec/compiler/interpreter/unions_spec.cr index 11bde229b44d..0fa82e8cbddb 100644 --- a/spec/compiler/interpreter/unions_spec.cr +++ b/spec/compiler/interpreter/unions_spec.cr @@ -36,6 +36,13 @@ describe Crystal::Repl::Interpreter do CRYSTAL end + it "returns large union type (#15041)" do + interpret(<<-CRYSTAL).should eq(4_i64) + a = {3_i64, 4_i64} || nil + a.is_a?(Tuple) ? a[1] : 5_i64 + CRYSTAL + end + it "put and remove from union in local var" do interpret(<<-CRYSTAL).should eq(3) a = 1 == 1 ? 2 : true diff --git a/src/compiler/crystal/interpreter/instructions.cr b/src/compiler/crystal/interpreter/instructions.cr index 6a38afd888d3..23428df03b90 100644 --- a/src/compiler/crystal/interpreter/instructions.cr +++ b/src/compiler/crystal/interpreter/instructions.cr @@ -1309,7 +1309,7 @@ require "./repl" code: begin tmp_stack = stack stack_grow_by(union_size - from_size) - (tmp_stack - from_size).copy_to(tmp_stack - from_size + type_id_bytesize, from_size) + (tmp_stack - from_size).move_to(tmp_stack - from_size + type_id_bytesize, from_size) (tmp_stack - from_size).as(Int64*).value = type_id.to_i64! end, disassemble: { @@ -1319,6 +1319,8 @@ require "./repl" put_reference_type_in_union: { operands: [union_size : Int32], code: begin + # `copy_to` here is valid only when `from_size <= type_id_bytesize`, + # which is always true from_size = sizeof(Pointer(UInt8)) reference = (stack - from_size).as(UInt8**).value type_id = @@ -1462,7 +1464,7 @@ require "./repl" tuple_indexer_known_index: { operands: [tuple_size : Int32, offset : Int32, value_size : Int32], code: begin - (stack - tuple_size).copy_from(stack - tuple_size + offset, value_size) + (stack - tuple_size).move_from(stack - tuple_size + offset, value_size) aligned_value_size = align(value_size) stack_shrink_by(tuple_size - value_size) stack_grow_by(aligned_value_size - value_size) @@ -1474,7 +1476,7 @@ require "./repl" }, tuple_copy_element: { operands: [tuple_size : Int32, old_offset : Int32, new_offset : Int32, element_size : Int32], - code: (stack - tuple_size + new_offset).copy_from(stack - tuple_size + old_offset, element_size), + code: (stack - tuple_size + new_offset).move_from(stack - tuple_size + old_offset, element_size), }, # >>> Tuples (3) From 8692740e69108ba5b088d8987a5cc2c7382a7ad5 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 30 Sep 2024 03:33:01 +0800 Subject: [PATCH 1385/1551] Support LLVM 19.1 (#14842) --- .github/workflows/llvm.yml | 16 +++++++++------- spec/compiler/codegen/debug_spec.cr | 2 -- src/compiler/crystal/codegen/debug.cr | 16 ++++++++++++++++ src/intrinsics.cr | 9 +++++++-- src/llvm/context.cr | 6 +++++- src/llvm/di_builder.cr | 6 +++++- src/llvm/ext/llvm-versions.txt | 2 +- src/llvm/lib_llvm.cr | 1 + src/llvm/lib_llvm/core.cr | 6 +++++- src/llvm/lib_llvm/debug_info.cr | 15 +++++++++++---- src/llvm/lib_llvm/types.cr | 1 + 11 files changed, 61 insertions(+), 19 deletions(-) diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index dab5b9bb74cd..796b26a66c08 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -17,17 +17,19 @@ jobs: matrix: include: - llvm_version: "13.0.0" - llvm_ubuntu_version: "20.04" + llvm_filename: "clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz" - llvm_version: "14.0.0" - llvm_ubuntu_version: "18.04" + llvm_filename: "clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz" - llvm_version: "15.0.6" - llvm_ubuntu_version: "18.04" + llvm_filename: "clang+llvm-15.0.6-x86_64-linux-gnu-ubuntu-18.04.tar.xz" - llvm_version: "16.0.3" - llvm_ubuntu_version: "22.04" + llvm_filename: "clang+llvm-16.0.3-x86_64-linux-gnu-ubuntu-22.04.tar.xz" - llvm_version: "17.0.6" - llvm_ubuntu_version: "22.04" + llvm_filename: "clang+llvm-17.0.6-x86_64-linux-gnu-ubuntu-22.04.tar.xz" - llvm_version: "18.1.4" - llvm_ubuntu_version: "18.04" + llvm_filename: "clang+llvm-18.1.4-x86_64-linux-gnu-ubuntu-18.04.tar.xz" + - llvm_version: "19.1.0" + llvm_filename: "LLVM-19.1.0-Linux-X64.tar.xz" name: "LLVM ${{ matrix.llvm_version }}" steps: - name: Checkout Crystal source @@ -44,7 +46,7 @@ jobs: - name: Install LLVM ${{ matrix.llvm_version }} run: | mkdir -p llvm - curl -L "https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ matrix.llvm_version }}/clang+llvm-${{ matrix.llvm_version }}-x86_64-linux-gnu-ubuntu-${{ matrix.llvm_ubuntu_version }}.tar.xz" > llvm.tar.xz + curl -L "https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ matrix.llvm_version }}/${{ matrix.llvm_filename }}" > llvm.tar.xz tar x --xz -C llvm --strip-components=1 -f llvm.tar.xz if: steps.cache-llvm.outputs.cache-hit != 'true' diff --git a/spec/compiler/codegen/debug_spec.cr b/spec/compiler/codegen/debug_spec.cr index 4a57056fc7a3..0032fcb64b4c 100644 --- a/spec/compiler/codegen/debug_spec.cr +++ b/spec/compiler/codegen/debug_spec.cr @@ -160,8 +160,6 @@ describe "Code gen: debug" do it "has debug info in closure inside if (#5593)" do codegen(%( - require "prelude" - def foo if true && true yield 1 diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index 72555d074bb0..9a03420ba203 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -367,6 +367,16 @@ module Crystal old_debug_location = @current_debug_location set_current_debug_location location if builder.current_debug_location != llvm_nil && (ptr = alloca) + # FIXME: When debug records are used instead of debug intrinsics, it + # seems inserting them into an empty BasicBlock will instead place them + # in a totally different (next?) function where the variable doesn't + # exist, leading to a "function-local metadata used in wrong function" + # validation error. This might happen when e.g. all variables inside a + # block are closured. Ideally every debug record should immediately + # follow the variable it declares. + {% unless LibLLVM::IS_LT_190 %} + call(do_nothing_fun) if block.instructions.empty? + {% end %} di_builder.insert_declare_at_end(ptr, var, expr, builder.current_debug_location_metadata, block) set_current_debug_location old_debug_location true @@ -376,6 +386,12 @@ module Crystal end end + private def do_nothing_fun + fetch_typed_fun(@llvm_mod, "llvm.donothing") do + LLVM::Type.function([] of LLVM::Type, @llvm_context.void) + end + end + # Emit debug info for toplevel variables. Used for the main module and all # required files. def emit_vars_debug_info(vars) diff --git a/src/intrinsics.cr b/src/intrinsics.cr index 7cdc462ce543..dc83ab91c884 100644 --- a/src/intrinsics.cr +++ b/src/intrinsics.cr @@ -163,8 +163,13 @@ lib LibIntrinsics {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr128)] {% end %} fun fshr128 = "llvm.fshr.i128"(a : UInt128, b : UInt128, count : UInt128) : UInt128 - fun va_start = "llvm.va_start"(ap : Void*) - fun va_end = "llvm.va_end"(ap : Void*) + {% if compare_versions(Crystal::LLVM_VERSION, "19.1.0") < 0 %} + fun va_start = "llvm.va_start"(ap : Void*) + fun va_end = "llvm.va_end"(ap : Void*) + {% else %} + fun va_start = "llvm.va_start.p0"(ap : Void*) + fun va_end = "llvm.va_end.p0"(ap : Void*) + {% end %} {% if flag?(:i386) || flag?(:x86_64) %} {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_pause)] {% end %} diff --git a/src/llvm/context.cr b/src/llvm/context.cr index 987e8f13ba6b..84c96610a96f 100644 --- a/src/llvm/context.cr +++ b/src/llvm/context.cr @@ -108,7 +108,11 @@ class LLVM::Context end def const_string(string : String) : Value - Value.new LibLLVM.const_string_in_context(self, string, string.bytesize, 0) + {% if LibLLVM::IS_LT_190 %} + Value.new LibLLVM.const_string_in_context(self, string, string.bytesize, 0) + {% else %} + Value.new LibLLVM.const_string_in_context2(self, string, string.bytesize, 0) + {% end %} end def const_struct(values : Array(LLVM::Value), packed = false) : Value diff --git a/src/llvm/di_builder.cr b/src/llvm/di_builder.cr index 37be65ef8cf8..7a06a7041349 100644 --- a/src/llvm/di_builder.cr +++ b/src/llvm/di_builder.cr @@ -96,7 +96,11 @@ struct LLVM::DIBuilder end def insert_declare_at_end(storage, var_info, expr, dl : LibLLVM::MetadataRef, block) - LibLLVM.di_builder_insert_declare_at_end(self, storage, var_info, expr, dl, block) + {% if LibLLVM::IS_LT_190 %} + LibLLVM.di_builder_insert_declare_at_end(self, storage, var_info, expr, dl, block) + {% else %} + LibLLVM.di_builder_insert_declare_record_at_end(self, storage, var_info, expr, dl, block) + {% end %} end def get_or_create_array(elements : Array(LibLLVM::MetadataRef)) diff --git a/src/llvm/ext/llvm-versions.txt b/src/llvm/ext/llvm-versions.txt index 92ae5ecbaa5a..6f4d3d4816d0 100644 --- a/src/llvm/ext/llvm-versions.txt +++ b/src/llvm/ext/llvm-versions.txt @@ -1 +1 @@ -18.1 17.0 16.0 15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 +19.1 18.1 17.0 16.0 15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0 diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 976cedc90df5..4c7ed49e7900 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -65,6 +65,7 @@ IS_LT_160 = {{compare_versions(LibLLVM::VERSION, "16.0.0") < 0}} IS_LT_170 = {{compare_versions(LibLLVM::VERSION, "17.0.0") < 0}} IS_LT_180 = {{compare_versions(LibLLVM::VERSION, "18.0.0") < 0}} + IS_LT_190 = {{compare_versions(LibLLVM::VERSION, "19.0.0") < 0}} end {% end %} diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr index ff3327a3f78d..1796bd00a0ee 100644 --- a/src/llvm/lib_llvm/core.cr +++ b/src/llvm/lib_llvm/core.cr @@ -116,7 +116,11 @@ lib LibLLVM fun const_int_get_zext_value = LLVMConstIntGetZExtValue(constant_val : ValueRef) : ULongLong fun const_int_get_sext_value = LLVMConstIntGetSExtValue(constant_val : ValueRef) : LongLong - fun const_string_in_context = LLVMConstStringInContext(c : ContextRef, str : Char*, length : UInt, dont_null_terminate : Bool) : ValueRef + {% if LibLLVM::IS_LT_190 %} + fun const_string_in_context = LLVMConstStringInContext(c : ContextRef, str : Char*, length : UInt, dont_null_terminate : Bool) : ValueRef + {% else %} + fun const_string_in_context2 = LLVMConstStringInContext2(c : ContextRef, str : Char*, length : SizeT, dont_null_terminate : Bool) : ValueRef + {% end %} fun const_struct_in_context = LLVMConstStructInContext(c : ContextRef, constant_vals : ValueRef*, count : UInt, packed : Bool) : ValueRef fun const_array = LLVMConstArray(element_ty : TypeRef, constant_vals : ValueRef*, length : UInt) : ValueRef diff --git a/src/llvm/lib_llvm/debug_info.cr b/src/llvm/lib_llvm/debug_info.cr index e97e8c71a177..e6155b317eb5 100644 --- a/src/llvm/lib_llvm/debug_info.cr +++ b/src/llvm/lib_llvm/debug_info.cr @@ -111,10 +111,17 @@ lib LibLLVM fun metadata_replace_all_uses_with = LLVMMetadataReplaceAllUsesWith(target_metadata : MetadataRef, replacement : MetadataRef) - fun di_builder_insert_declare_at_end = LLVMDIBuilderInsertDeclareAtEnd( - builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef, - expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef - ) : ValueRef + {% if LibLLVM::IS_LT_190 %} + fun di_builder_insert_declare_at_end = LLVMDIBuilderInsertDeclareAtEnd( + builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef, + expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef + ) : ValueRef + {% else %} + fun di_builder_insert_declare_record_at_end = LLVMDIBuilderInsertDeclareRecordAtEnd( + builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef, + expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef + ) : DbgRecordRef + {% end %} fun di_builder_create_auto_variable = LLVMDIBuilderCreateAutoVariable( builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, diff --git a/src/llvm/lib_llvm/types.cr b/src/llvm/lib_llvm/types.cr index a1b374f30219..532078394794 100644 --- a/src/llvm/lib_llvm/types.cr +++ b/src/llvm/lib_llvm/types.cr @@ -17,4 +17,5 @@ lib LibLLVM {% end %} type OperandBundleRef = Void* type AttributeRef = Void* + type DbgRecordRef = Void* end From 2821fbb7283ae1f440532eb076e8b2cfb1b1b8eb Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 30 Sep 2024 03:34:18 +0800 Subject: [PATCH 1386/1551] Fix race condition in `pthread_create` handle initialization (#15043) The thread routine may be started before `GC.pthread_create` returns; `Thread#start` then calls `Crystal::System::Thread#stack_address`, which may call `LibC.pthread_getattr_np` before `GC.pthread_create` initializes the `@system_handle` instance variable, hence the segmentation fault. (On musl-libc, [the underlying non-atomic store occurs right before `LibC.pthread_create` returns](https://git.musl-libc.org/cgit/musl/tree/src/thread/pthread_create.c?id=dd1e63c3638d5f9afb857fccf6ce1415ca5f1b8b#n389).) Thus there needs to be some kind of synchronization. --- spec/std/thread/condition_variable_spec.cr | 8 -------- spec/std/thread/mutex_spec.cr | 8 -------- spec/std/thread_spec.cr | 8 -------- src/crystal/system/unix/pthread.cr | 23 ++++++++++++++++++---- 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/spec/std/thread/condition_variable_spec.cr b/spec/std/thread/condition_variable_spec.cr index ff9c44204bb6..1bf78f797357 100644 --- a/spec/std/thread/condition_variable_spec.cr +++ b/spec/std/thread/condition_variable_spec.cr @@ -1,11 +1,3 @@ -{% if flag?(:musl) %} - # FIXME: These thread specs occasionally fail on musl/alpine based ci, so - # they're disabled for now to reduce noise. - # See https://github.com/crystal-lang/crystal/issues/8738 - pending Thread::ConditionVariable - {% skip_file %} -{% end %} - require "../spec_helper" # interpreter doesn't support threads yet (#14287) diff --git a/spec/std/thread/mutex_spec.cr b/spec/std/thread/mutex_spec.cr index ff298f318329..99f3c5d385c3 100644 --- a/spec/std/thread/mutex_spec.cr +++ b/spec/std/thread/mutex_spec.cr @@ -1,11 +1,3 @@ -{% if flag?(:musl) %} - # FIXME: These thread specs occasionally fail on musl/alpine based ci, so - # they're disabled for now to reduce noise. - # See https://github.com/crystal-lang/crystal/issues/8738 - pending Thread::Mutex - {% skip_file %} -{% end %} - require "../spec_helper" # interpreter doesn't support threads yet (#14287) diff --git a/spec/std/thread_spec.cr b/spec/std/thread_spec.cr index feb55454b621..5a43c7e429d1 100644 --- a/spec/std/thread_spec.cr +++ b/spec/std/thread_spec.cr @@ -1,13 +1,5 @@ require "./spec_helper" -{% if flag?(:musl) %} - # FIXME: These thread specs occasionally fail on musl/alpine based ci, so - # they're disabled for now to reduce noise. - # See https://github.com/crystal-lang/crystal/issues/8738 - pending Thread - {% skip_file %} -{% end %} - # interpreter doesn't support threads yet (#14287) pending_interpreted describe: Thread do it "allows passing an argumentless fun to execute" do diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index b55839ff2784..952843f4c2b0 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -9,20 +9,35 @@ module Crystal::System::Thread @system_handle end + protected setter system_handle + private def init_handle - # NOTE: the thread may start before `pthread_create` returns, so - # `@system_handle` must be set as soon as possible; we cannot use a separate - # handle and assign it to `@system_handle`, which would have been too late + # NOTE: `@system_handle` needs to be set here too, not just in + # `.thread_proc`, since the current thread might progress first; the value + # of `LibC.pthread_self` inside the new thread must be equal to this + # `@system_handle` after `pthread_create` returns ret = GC.pthread_create( thread: pointerof(@system_handle), attr: Pointer(LibC::PthreadAttrT).null, - start: ->(data : Void*) { data.as(::Thread).start; Pointer(Void).null }, + start: ->Thread.thread_proc(Void*), arg: self.as(Void*), ) raise RuntimeError.from_os_error("pthread_create", Errno.new(ret)) unless ret == 0 end + def self.thread_proc(data : Void*) : Void* + th = data.as(::Thread) + + # `#start` calls `#stack_address`, which might read `@system_handle` before + # `GC.pthread_create` updates it in the original thread that spawned the + # current one, so we also assign to it here + th.system_handle = current_handle + + th.start + Pointer(Void).null + end + def self.current_handle : Handle LibC.pthread_self end From 46ca8fb923b7dbe85c901dfdd573fd4a56223e8e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 30 Sep 2024 19:34:36 +0800 Subject: [PATCH 1387/1551] Fix main stack top detection on musl-libc (#15047) Stack overflow detection relies on the current thread's stack bounds. On Linux this is obtained using `LibC.pthread_getattr_np` and `LibC.pthread_attr_getstack`. However, on musl-libc the internal stack size is always [hardcoded](https://git.musl-libc.org/cgit/musl/tree/src/thread/pthread_create.c?id=dd1e63c3638d5f9afb857fccf6ce1415ca5f1b8b#n267) to a very small [default](https://git.musl-libc.org/cgit/musl/tree/src/internal/pthread_impl.h?id=dd1e63c3638d5f9afb857fccf6ce1415ca5f1b8b#n197) of [128 KiB](https://git.musl-libc.org/cgit/musl/tree/src/thread/default_attr.c?id=dd1e63c3638d5f9afb857fccf6ce1415ca5f1b8b#n3), and [`pthread_getattr_np` returns this value unmodified](https://git.musl-libc.org/cgit/musl/tree/src/thread/pthread_getattr_np.c?id=dd1e63c3638d5f9afb857fccf6ce1415ca5f1b8b#n13), presumably for the main thread as well. The result is that the main thread has an entirely incorrect `@stack`, as it respects `ulimit -s`, and that non-main threads are really that small, as we don't pass any `attr` to `GC.pthread_create` on thread creation. [This is also mentioned on Ruby's bug tracker](https://bugs.ruby-lang.org/issues/14387#change-70914). --- spec/std/kernel_spec.cr | 23 ++++++--------- src/crystal/system/unix/pthread.cr | 29 +++++++++++++++++-- .../aarch64-linux-musl/c/sys/resource.cr | 11 +++++++ src/lib_c/i386-linux-musl/c/sys/resource.cr | 11 +++++++ src/lib_c/x86_64-linux-musl/c/sys/resource.cr | 11 +++++++ 5 files changed, 68 insertions(+), 17 deletions(-) diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index 149e6385ac97..f41529af901a 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -254,16 +254,12 @@ describe "hardware exception" do error.should_not contain("Stack overflow") end - {% if flag?(:musl) %} - # FIXME: Pending as mitigation for https://github.com/crystal-lang/crystal/issues/7482 - pending "detects stack overflow on the main stack" - {% else %} - it "detects stack overflow on the main stack", tags: %w[slow] do - # This spec can take some time under FreeBSD where - # the default stack size is 0.5G. Setting a - # smaller stack size with `ulimit -s 8192` - # will address this. - status, _, error = compile_and_run_source <<-'CRYSTAL' + it "detects stack overflow on the main stack", tags: %w[slow] do + # This spec can take some time under FreeBSD where + # the default stack size is 0.5G. Setting a + # smaller stack size with `ulimit -s 8192` + # will address this. + status, _, error = compile_and_run_source <<-'CRYSTAL' def foo y = StaticArray(Int8, 512).new(0) foo @@ -271,10 +267,9 @@ describe "hardware exception" do foo CRYSTAL - status.success?.should be_false - error.should contain("Stack overflow") - end - {% end %} + status.success?.should be_false + error.should contain("Stack overflow") + end it "detects stack overflow on a fiber stack", tags: %w[slow] do status, _, error = compile_and_run_source <<-'CRYSTAL' diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index 952843f4c2b0..50a0fc56e818 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -131,11 +131,26 @@ module Crystal::System::Thread ret = LibC.pthread_attr_destroy(pointerof(attr)) raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0 {% elsif flag?(:linux) %} - if LibC.pthread_getattr_np(@system_handle, out attr) == 0 - LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out _) - end + ret = LibC.pthread_getattr_np(@system_handle, out attr) + raise RuntimeError.from_os_error("pthread_getattr_np", Errno.new(ret)) unless ret == 0 + + LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out stack_size) + ret = LibC.pthread_attr_destroy(pointerof(attr)) raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0 + + # with musl-libc, the main thread does not respect `rlimit -Ss` and + # instead returns the same default stack size as non-default threads, so + # we obtain the rlimit to correct the stack address manually + {% if flag?(:musl) %} + if Thread.current_is_main? + if LibC.getrlimit(LibC::RLIMIT_STACK, out rlim) == 0 + address = address + stack_size - rlim.rlim_cur + else + raise RuntimeError.from_errno("getrlimit") + end + end + {% end %} {% elsif flag?(:openbsd) %} ret = LibC.pthread_stackseg_np(@system_handle, out stack) raise RuntimeError.from_os_error("pthread_stackseg_np", Errno.new(ret)) unless ret == 0 @@ -153,6 +168,14 @@ module Crystal::System::Thread address end + {% if flag?(:musl) %} + @@main_handle : Handle = current_handle + + def self.current_is_main? + current_handle == @@main_handle + end + {% end %} + # Warning: must be called from the current thread itself, because Darwin # doesn't allow to set the name of any thread but the current one! private def system_name=(name : String) : String diff --git a/src/lib_c/aarch64-linux-musl/c/sys/resource.cr b/src/lib_c/aarch64-linux-musl/c/sys/resource.cr index 7f550c37a622..daa583ac5895 100644 --- a/src/lib_c/aarch64-linux-musl/c/sys/resource.cr +++ b/src/lib_c/aarch64-linux-musl/c/sys/resource.cr @@ -1,4 +1,15 @@ lib LibC + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + fun getrlimit(Int, Rlimit*) : Int + + RLIMIT_STACK = 3 + struct RUsage ru_utime : Timeval ru_stime : Timeval diff --git a/src/lib_c/i386-linux-musl/c/sys/resource.cr b/src/lib_c/i386-linux-musl/c/sys/resource.cr index 7f550c37a622..daa583ac5895 100644 --- a/src/lib_c/i386-linux-musl/c/sys/resource.cr +++ b/src/lib_c/i386-linux-musl/c/sys/resource.cr @@ -1,4 +1,15 @@ lib LibC + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + fun getrlimit(Int, Rlimit*) : Int + + RLIMIT_STACK = 3 + struct RUsage ru_utime : Timeval ru_stime : Timeval diff --git a/src/lib_c/x86_64-linux-musl/c/sys/resource.cr b/src/lib_c/x86_64-linux-musl/c/sys/resource.cr index 7f550c37a622..daa583ac5895 100644 --- a/src/lib_c/x86_64-linux-musl/c/sys/resource.cr +++ b/src/lib_c/x86_64-linux-musl/c/sys/resource.cr @@ -1,4 +1,15 @@ lib LibC + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + fun getrlimit(Int, Rlimit*) : Int + + RLIMIT_STACK = 3 + struct RUsage ru_utime : Timeval ru_stime : Timeval From 1f6b51d9b6e88b22f29581e817fc4e7c15677a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=C3=B6rjesson?= Date: Fri, 4 Oct 2024 07:50:21 +0200 Subject: [PATCH 1388/1551] Fix copy-paste-typo in spec describe (#15054) --- spec/compiler/crystal/tools/doc/type_spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/compiler/crystal/tools/doc/type_spec.cr b/spec/compiler/crystal/tools/doc/type_spec.cr index c5dde7d4b258..6533955464fe 100644 --- a/spec/compiler/crystal/tools/doc/type_spec.cr +++ b/spec/compiler/crystal/tools/doc/type_spec.cr @@ -312,7 +312,7 @@ describe Doc::Type do end end - describe "#included_modules" do + describe "#extended_modules" do it "only include types with docs" do program = semantic(<<-CRYSTAL, wants_doc: true).program # :nodoc: From a9b26ff381ef91f94b3b35d39babcb891d7219ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 7 Oct 2024 17:03:41 +0200 Subject: [PATCH 1389/1551] Revert "Add nodoc filter to doc type methods (#14910)" (#15064) * Revert "Fix copy-paste-typo in spec describe (#15054)" This reverts commit 1f6b51d9b6e88b22f29581e817fc4e7c15677a60. * Revert "Add nodoc filter to doc type methods (#14910)" This reverts commit 791b0e451766503e4a8d2b63fd1bc726b9948276. --- spec/compiler/crystal/tools/doc/type_spec.cr | 136 ------------------- src/compiler/crystal/tools/doc/type.cr | 3 - 2 files changed, 139 deletions(-) diff --git a/spec/compiler/crystal/tools/doc/type_spec.cr b/spec/compiler/crystal/tools/doc/type_spec.cr index 6533955464fe..34ab535f6d5e 100644 --- a/spec/compiler/crystal/tools/doc/type_spec.cr +++ b/spec/compiler/crystal/tools/doc/type_spec.cr @@ -212,140 +212,4 @@ describe Doc::Type do type.macros.map(&.name).should eq ["+", "~", "foo"] end end - - describe "#subclasses" do - it "only include types with docs" do - program = semantic(<<-CRYSTAL, wants_doc: true).program - class Foo - end - - class Bar < Foo - end - - # :nodoc: - class Baz < Foo - end - - module Mod1 - class Bar < ::Foo - end - end - - # :nodoc: - module Mod2 - class Baz < ::Foo - end - end - CRYSTAL - - generator = Doc::Generator.new program, [""] - type = generator.type(program.types["Foo"]) - type.subclasses.map(&.full_name).should eq ["Bar", "Mod1::Bar"] - end - end - - describe "#ancestors" do - it "only include types with docs" do - program = semantic(<<-CRYSTAL, wants_doc: true).program - # :nodoc: - module Mod3 - class Baz - end - end - - class Mod2::Baz < Mod3::Baz - end - - module Mod1 - # :nodoc: - class Baz < Mod2::Baz - end - end - - class Baz < Mod1::Baz - end - - class Foo < Baz - end - CRYSTAL - - generator = Doc::Generator.new program, [""] - type = generator.type(program.types["Foo"]) - type.ancestors.map(&.full_name).should eq ["Baz", "Mod2::Baz"] - end - end - - describe "#included_modules" do - it "only include types with docs" do - program = semantic(<<-CRYSTAL, wants_doc: true).program - # :nodoc: - module Mod3 - module Baz - end - end - - module Mod2 - # :nodoc: - module Baz - end - end - - module Mod1 - module Baz - end - end - - module Baz - end - - class Foo - include Baz - include Mod1::Baz - include Mod2::Baz - include Mod3::Baz - end - CRYSTAL - - generator = Doc::Generator.new program, [""] - type = generator.type(program.types["Foo"]) - type.included_modules.map(&.full_name).should eq ["Baz", "Mod1::Baz"] - end - end - - describe "#extended_modules" do - it "only include types with docs" do - program = semantic(<<-CRYSTAL, wants_doc: true).program - # :nodoc: - module Mod3 - module Baz - end - end - - module Mod2 - # :nodoc: - module Baz - end - end - - module Mod1 - module Baz - end - end - - module Baz - end - - class Foo - extend Baz - extend Mod1::Baz - extend Mod2::Baz - extend Mod3::Baz - end - CRYSTAL - - generator = Doc::Generator.new program, [""] - type = generator.type(program.types["Foo"]) - type.extended_modules.map(&.full_name).should eq ["Baz", "Mod1::Baz"] - end - end end diff --git a/src/compiler/crystal/tools/doc/type.cr b/src/compiler/crystal/tools/doc/type.cr index 9b1a0a86cf7e..624c8f017fe7 100644 --- a/src/compiler/crystal/tools/doc/type.cr +++ b/src/compiler/crystal/tools/doc/type.cr @@ -117,7 +117,6 @@ class Crystal::Doc::Type unless ast_node? @type.ancestors.each do |ancestor| - next unless @generator.must_include? ancestor doc_type = @generator.type(ancestor) ancestors << doc_type break if ancestor == @generator.program.object || doc_type.ast_node? @@ -259,7 +258,6 @@ class Crystal::Doc::Type included_modules = [] of Type @type.parents.try &.each do |parent| if parent.module? - next unless @generator.must_include? parent included_modules << @generator.type(parent) end end @@ -274,7 +272,6 @@ class Crystal::Doc::Type extended_modules = [] of Type @type.metaclass.parents.try &.each do |parent| if parent.module? - next unless @generator.must_include? parent extended_modules << @generator.type(parent) end end From 401eb47bf373f11b2da6e382c306ead36615e1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 7 Oct 2024 22:31:17 +0200 Subject: [PATCH 1390/1551] Fix `crystal tool unreachable` & co visiting circular hierarchies (#15065) Avoid visiting circular hierarchies created by type aliases. --- spec/compiler/crystal/tools/unreachable_spec.cr | 8 ++++++++ src/compiler/crystal/tools/typed_def_processor.cr | 9 +++++++++ src/compiler/crystal/tools/unreachable.cr | 9 +++++++++ 3 files changed, 26 insertions(+) diff --git a/spec/compiler/crystal/tools/unreachable_spec.cr b/spec/compiler/crystal/tools/unreachable_spec.cr index 12ed82499740..f94277348e6c 100644 --- a/spec/compiler/crystal/tools/unreachable_spec.cr +++ b/spec/compiler/crystal/tools/unreachable_spec.cr @@ -112,6 +112,14 @@ describe "unreachable" do CRYSTAL end + it "handles circular hierarchy references (#14034)" do + assert_unreachable <<-CRYSTAL + class Foo + alias Bar = Foo + end + CRYSTAL + end + it "finds initializer" do assert_unreachable <<-CRYSTAL class Foo diff --git a/src/compiler/crystal/tools/typed_def_processor.cr b/src/compiler/crystal/tools/typed_def_processor.cr index 2ba2441d7902..a0a911a6a618 100644 --- a/src/compiler/crystal/tools/typed_def_processor.cr +++ b/src/compiler/crystal/tools/typed_def_processor.cr @@ -17,6 +17,15 @@ module Crystal::TypedDefProcessor end private def process_type(type : Type) : Nil + # Avoid visiting circular hierarchies. There's no use in processing + # alias types anyway. + # For example: + # + # struct Foo + # alias Bar = Foo + # end + return if type.is_a?(AliasType) || type.is_a?(TypeDefType) + if type.is_a?(NamedType) || type.is_a?(Program) || type.is_a?(FileModule) type.types?.try &.each_value do |inner_type| process_type inner_type diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr index a8886fecf596..733a94518899 100644 --- a/src/compiler/crystal/tools/unreachable.cr +++ b/src/compiler/crystal/tools/unreachable.cr @@ -128,6 +128,15 @@ module Crystal property excludes = [] of String def process_type(type) + # Avoid visiting circular hierarchies. There's no use in processing + # alias types anyway. + # For example: + # + # struct Foo + # alias Bar = Foo + # end + return if type.is_a?(AliasType) || type.is_a?(TypeDefType) + if type.is_a?(ModuleType) track_unused_defs type end From dddc0dc8a0585de82efe3c5e233803c770918a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 8 Oct 2024 17:06:18 +0200 Subject: [PATCH 1391/1551] Fix `TopLevelVisitor` adding existing `ClassDef` type to current scope (#15067) When a type is reopened, the compiler adds it as nested type to the current scope (i.e. where it's reopened). That's wrong because the reopened type is already scoped to the location of the original location and this could lead to cycles. For example, the following program would crash the compiler with an infinite recursion: ```cr class Foo alias Bar = Foo class Bar end end ``` This fix makes sure to only add newly defined types to the current scope, not reopened ones. All other visitor methods which defined types already do this, only `ClassDef` was unconditionally adding to the current scope. --- spec/compiler/semantic/alias_spec.cr | 16 ++++++++++++++++ .../crystal/semantic/top_level_visitor.cr | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/alias_spec.cr b/spec/compiler/semantic/alias_spec.cr index faf3b81b8e92..3af2f24e5e84 100644 --- a/spec/compiler/semantic/alias_spec.cr +++ b/spec/compiler/semantic/alias_spec.cr @@ -178,6 +178,22 @@ describe "Semantic: alias" do Bar.bar )) { int32 } end + + it "reopens #{type} through alias within itself" do + assert_type <<-CRYSTAL { int32 } + #{type} Foo + alias Bar = Foo + + #{type} Bar + def self.bar + 1 + end + end + end + + Foo.bar + CRYSTAL + end end %w(class struct).each do |type| diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 1fc7119b9ffd..3654e24ff7a5 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -193,9 +193,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor if superclass.is_a?(GenericClassInstanceType) superclass.generic_type.add_subclass(type) end + scope.types[name] = type end - scope.types[name] = type node.resolved_type = type process_annotations(annotations) do |annotation_type, ann| From c5abee0eb53a770263aa9e1e0bdead7b7904b54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 9 Oct 2024 13:35:05 +0200 Subject: [PATCH 1392/1551] Add `uri/json` to `docs_main` (#15069) --- src/docs_main.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/docs_main.cr b/src/docs_main.cr index ab3ee2affdbc..1fec70580a04 100644 --- a/src/docs_main.cr +++ b/src/docs_main.cr @@ -52,6 +52,7 @@ require "./string_pool" require "./string_scanner" require "./unicode/unicode" require "./uri" +require "./uri/json" require "./uri/params/serializable" require "./uuid" require "./uuid/json" From dacd97bccc80b41c7d6c448cfad19d37184766e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 9 Oct 2024 14:09:40 +0200 Subject: [PATCH 1393/1551] Changelog for 1.14.0 (#14969) --- CHANGELOG.md | 354 ++++++++++++++++++++++++++++++++++++++++++ shard.yml | 2 +- src/SOURCE_DATE_EPOCH | 1 + src/VERSION | 2 +- 4 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 src/SOURCE_DATE_EPOCH diff --git a/CHANGELOG.md b/CHANGELOG.md index 341586a8fb95..76272bb1679b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,359 @@ # Changelog +## [1.14.0] (2024-10-09) + +[1.14.0]: https://github.com/crystal-lang/crystal/releases/1.14.0 + +### Features + +#### lang + +- Allow `^` in constant numeric expressions ([#14951], thanks @HertzDevil) + +[#14951]: https://github.com/crystal-lang/crystal/pull/14951 + +#### stdlib + +- Add support for Windows on aarch64 ([#14911], thanks @HertzDevil) +- *(collection)* **[breaking]** Add support for negative start index in `Slice#[start, count]` ([#14778], thanks @ysbaddaden) +- *(collection)* Add `Slice#same?` ([#14728], thanks @straight-shoota) +- *(concurrency)* Add `WaitGroup.wait` and `WaitGroup#spawn` ([#14837], thanks @jgaskins) +- *(concurrency)* Open non-blocking regular files as overlapped on Windows ([#14921], thanks @HertzDevil) +- *(concurrency)* Support non-blocking `File#read` and `#write` on Windows ([#14940], thanks @HertzDevil) +- *(concurrency)* Support non-blocking `File#read_at` on Windows ([#14958], thanks @HertzDevil) +- *(concurrency)* Support non-blocking `Process.run` standard streams on Windows ([#14941], thanks @HertzDevil) +- *(concurrency)* Support `IO::FileDescriptor#flock_*` on non-blocking files on Windows ([#14943], thanks @HertzDevil) +- *(concurrency)* Emulate non-blocking `STDIN` console on Windows ([#14947], thanks @HertzDevil) +- *(concurrency)* Async DNS resolution on Windows ([#14979], thanks @HertzDevil) +- *(crypto)* Update `LibCrypto` bindings for LibreSSL 3.5+ ([#14872], thanks @straight-shoota) +- *(llvm)* Expose LLVM instruction builder for `neg` and `fneg` ([#14774], thanks @JarnaChao09) +- *(llvm)* **[experimental]** Add minimal LLVM OrcV2 bindings ([#14887], thanks @HertzDevil) +- *(llvm)* Add `LLVM::Builder#finalize` ([#14892], thanks @JarnaChao09) +- *(llvm)* Support LLVM 19.1 ([#14842], thanks @HertzDevil) +- *(macros)* Add `Crystal::Macros::TypeNode#has_inner_pointers?` ([#14847], thanks @HertzDevil) +- *(macros)* Add `HashLiteral#has_key?` and `NamedTupleLiteral#has_key?` ([#14890], thanks @kamil-gwozdz) +- *(numeric)* Implement floating-point manipulation functions for `BigFloat` ([#11007], thanks @HertzDevil) +- *(runtime)* Stop & start the world (undocumented API) ([#14729], thanks @ysbaddaden) +- *(runtime)* Add `Pointer::Appender#to_slice` ([#14874], thanks @straight-shoota) +- *(serialization)* Add `URI.from_json_object_key?` and `URI#to_json_object_key` ([#14834], thanks @nobodywasishere) +- *(serialization)* Add `URI::Params::Serializable` ([#14684], thanks @Blacksmoke16) +- *(system)* Enable full backtrace for exception in process spawn ([#14796], thanks @straight-shoota) +- *(system)* Implement `System::User` on Windows ([#14933], thanks @HertzDevil) +- *(system)* Implement `System::Group` on Windows ([#14945], thanks @HertzDevil) +- *(system)* Add methods to `Crystal::EventLoop` ([#14977], thanks @ysbaddaden) +- *(text)* Add `underscore_to_space` option to `String#titleize` ([#14822], thanks @Blacksmoke16) +- *(text)* Support Unicode 16.0.0 ([#14997], thanks @HertzDevil) + +[#14911]: https://github.com/crystal-lang/crystal/pull/14911 +[#14778]: https://github.com/crystal-lang/crystal/pull/14778 +[#14728]: https://github.com/crystal-lang/crystal/pull/14728 +[#14837]: https://github.com/crystal-lang/crystal/pull/14837 +[#14921]: https://github.com/crystal-lang/crystal/pull/14921 +[#14940]: https://github.com/crystal-lang/crystal/pull/14940 +[#14958]: https://github.com/crystal-lang/crystal/pull/14958 +[#14941]: https://github.com/crystal-lang/crystal/pull/14941 +[#14943]: https://github.com/crystal-lang/crystal/pull/14943 +[#14947]: https://github.com/crystal-lang/crystal/pull/14947 +[#14979]: https://github.com/crystal-lang/crystal/pull/14979 +[#14872]: https://github.com/crystal-lang/crystal/pull/14872 +[#14774]: https://github.com/crystal-lang/crystal/pull/14774 +[#14887]: https://github.com/crystal-lang/crystal/pull/14887 +[#14892]: https://github.com/crystal-lang/crystal/pull/14892 +[#14842]: https://github.com/crystal-lang/crystal/pull/14842 +[#14847]: https://github.com/crystal-lang/crystal/pull/14847 +[#14890]: https://github.com/crystal-lang/crystal/pull/14890 +[#11007]: https://github.com/crystal-lang/crystal/pull/11007 +[#14729]: https://github.com/crystal-lang/crystal/pull/14729 +[#14874]: https://github.com/crystal-lang/crystal/pull/14874 +[#14834]: https://github.com/crystal-lang/crystal/pull/14834 +[#14684]: https://github.com/crystal-lang/crystal/pull/14684 +[#14796]: https://github.com/crystal-lang/crystal/pull/14796 +[#14933]: https://github.com/crystal-lang/crystal/pull/14933 +[#14945]: https://github.com/crystal-lang/crystal/pull/14945 +[#14977]: https://github.com/crystal-lang/crystal/pull/14977 +[#14822]: https://github.com/crystal-lang/crystal/pull/14822 +[#14997]: https://github.com/crystal-lang/crystal/pull/14997 + +#### compiler + +- *(cli)* Adds initial support for external commands ([#14953], thanks @bcardiff) +- *(interpreter)* Add `Crystal::Repl::Value#runtime_type` ([#14156], thanks @bcardiff) +- *(interpreter)* Implement `Reference.pre_initialize` in the interpreter ([#14968], thanks @HertzDevil) +- *(interpreter)* Enable the interpreter on Windows ([#14964], thanks @HertzDevil) + +[#14953]: https://github.com/crystal-lang/crystal/pull/14953 +[#14156]: https://github.com/crystal-lang/crystal/pull/14156 +[#14968]: https://github.com/crystal-lang/crystal/pull/14968 +[#14964]: https://github.com/crystal-lang/crystal/pull/14964 + +### Bugfixes + +#### lang + +- Fix `Slice.literal` for multiple calls with identical signature ([#15009], thanks @HertzDevil) +- *(macros)* Add location info to some `MacroIf` nodes ([#14885], thanks @Blacksmoke16) + +[#15009]: https://github.com/crystal-lang/crystal/pull/15009 +[#14885]: https://github.com/crystal-lang/crystal/pull/14885 + +#### stdlib + +- *(collection)* Fix `Range#size` return type to `Int32` ([#14588], thanks @straight-shoota) +- *(concurrency)* Update `DeallocationStack` for Windows context switch ([#15032], thanks @HertzDevil) +- *(concurrency)* Fix race condition in `pthread_create` handle initialization ([#15043], thanks @HertzDevil) +- *(files)* **[regression]** Fix `File#truncate` and `#lock` for Win32 append-mode files ([#14706], thanks @HertzDevil) +- *(files)* **[breaking]** Avoid flush in finalizers for `Socket` and `IO::FileDescriptor` ([#14882], thanks @straight-shoota) +- *(files)* Make `IO::Buffered#buffer_size=` idempotent ([#14855], thanks @jgaskins) +- *(macros)* Implement `#sort_by` inside macros using `Enumerable#sort_by` ([#14895], thanks @HertzDevil) +- *(macros)* Fix internal error when calling `#is_a?` on `External` nodes ([#14918], thanks @HertzDevil) +- *(networking)* Use correct timeout for `Socket#connect` on Windows ([#14961], thanks @HertzDevil) +- *(numeric)* Fix handle empty string in `String#to_f(whitespace: false)` ([#14902], thanks @Blacksmoke16) +- *(numeric)* Fix exponent wrapping in `Math.frexp(BigFloat)` for very large values ([#14971], thanks @HertzDevil) +- *(numeric)* Fix exponent overflow in `BigFloat#to_s` for very large values ([#14982], thanks @HertzDevil) +- *(numeric)* Add missing `@[Link(dll:)]` annotation to MPIR ([#15003], thanks @HertzDevil) +- *(runtime)* Add missing return type of `LibC.VirtualQuery` ([#15036], thanks @HertzDevil) +- *(runtime)* Fix main stack top detection on musl-libc ([#15047], thanks @HertzDevil) +- *(serialization)* **[breaking]** Remove `XML::Error.errors` ([#14936], thanks @straight-shoota) +- *(specs)* **[regression]** Fix `Expectations::Be` for module type ([#14926], thanks @straight-shoota) +- *(system)* Fix return type restriction for `ENV.fetch` ([#14919], thanks @straight-shoota) +- *(system)* `#file_descriptor_close` should set `@closed` (UNIX) ([#14973], thanks @ysbaddaden) +- *(system)* reinit event loop first after fork (UNIX) ([#14975], thanks @ysbaddaden) +- *(text)* Fix avoid linking `libpcre` when unused ([#14891], thanks @kojix2) +- *(text)* Add type restriction to `String#byte_index` `offset` parameter ([#14981], thanks @straight-shoota) + +[#14588]: https://github.com/crystal-lang/crystal/pull/14588 +[#15032]: https://github.com/crystal-lang/crystal/pull/15032 +[#15043]: https://github.com/crystal-lang/crystal/pull/15043 +[#14706]: https://github.com/crystal-lang/crystal/pull/14706 +[#14882]: https://github.com/crystal-lang/crystal/pull/14882 +[#14855]: https://github.com/crystal-lang/crystal/pull/14855 +[#14895]: https://github.com/crystal-lang/crystal/pull/14895 +[#14918]: https://github.com/crystal-lang/crystal/pull/14918 +[#14961]: https://github.com/crystal-lang/crystal/pull/14961 +[#14902]: https://github.com/crystal-lang/crystal/pull/14902 +[#14971]: https://github.com/crystal-lang/crystal/pull/14971 +[#14982]: https://github.com/crystal-lang/crystal/pull/14982 +[#15003]: https://github.com/crystal-lang/crystal/pull/15003 +[#15036]: https://github.com/crystal-lang/crystal/pull/15036 +[#15047]: https://github.com/crystal-lang/crystal/pull/15047 +[#14936]: https://github.com/crystal-lang/crystal/pull/14936 +[#14926]: https://github.com/crystal-lang/crystal/pull/14926 +[#14919]: https://github.com/crystal-lang/crystal/pull/14919 +[#14973]: https://github.com/crystal-lang/crystal/pull/14973 +[#14975]: https://github.com/crystal-lang/crystal/pull/14975 +[#14891]: https://github.com/crystal-lang/crystal/pull/14891 +[#14981]: https://github.com/crystal-lang/crystal/pull/14981 + +#### compiler + +- *(cli)* Add error handling for linker flag sub commands ([#14932], thanks @straight-shoota) +- *(codegen)* Allow returning `Proc`s from top-level funs ([#14917], thanks @HertzDevil) +- *(codegen)* Fix CRT static-dynamic linking conflict in specs with C sources ([#14970], thanks @HertzDevil) +- *(interpreter)* Fix Linux `getrandom` failure in interpreted code ([#15035], thanks @HertzDevil) +- *(interpreter)* Fix undefined behavior in interpreter mixed union upcast ([#15042], thanks @HertzDevil) +- *(semantic)* Fix `TopLevelVisitor` adding existing `ClassDef` type to current scope ([#15067], thanks @straight-shoota) + +[#14932]: https://github.com/crystal-lang/crystal/pull/14932 +[#14917]: https://github.com/crystal-lang/crystal/pull/14917 +[#14970]: https://github.com/crystal-lang/crystal/pull/14970 +[#15035]: https://github.com/crystal-lang/crystal/pull/15035 +[#15042]: https://github.com/crystal-lang/crystal/pull/15042 +[#15067]: https://github.com/crystal-lang/crystal/pull/15067 + +#### tools + +- *(dependencies)* Fix `crystal tool dependencies` format flat ([#14927], thanks @straight-shoota) +- *(dependencies)* Fix `crystal tool dependencies` filters for Windows paths ([#14928], thanks @straight-shoota) +- *(docs-generator)* Fix doc comment above annotation with macro expansion ([#14849], thanks @Blacksmoke16) +- *(unreachable)* Fix `crystal tool unreachable` & co visiting circular hierarchies ([#15065], thanks @straight-shoota) + +[#14927]: https://github.com/crystal-lang/crystal/pull/14927 +[#14928]: https://github.com/crystal-lang/crystal/pull/14928 +[#14849]: https://github.com/crystal-lang/crystal/pull/14849 +[#15065]: https://github.com/crystal-lang/crystal/pull/15065 + +### Chores + +#### stdlib + +- **[deprecation]** Use `Time::Span` in `Benchmark.ips` ([#14805], thanks @HertzDevil) +- **[deprecation]** Deprecate `::sleep(Number)` ([#14962], thanks @HertzDevil) +- *(runtime)* **[deprecation]** Deprecate `Pointer.new(Int)` ([#14875], thanks @straight-shoota) + +[#14805]: https://github.com/crystal-lang/crystal/pull/14805 +[#14962]: https://github.com/crystal-lang/crystal/pull/14962 +[#14875]: https://github.com/crystal-lang/crystal/pull/14875 + +#### compiler + +- *(interpreter)* Remove TODO in `Crystal::Loader` on Windows ([#14988], thanks @HertzDevil) +- *(interpreter:repl)* Update REPLy version ([#14950], thanks @HertzDevil) + +[#14988]: https://github.com/crystal-lang/crystal/pull/14988 +[#14950]: https://github.com/crystal-lang/crystal/pull/14950 + +### Performance + +#### stdlib + +- *(collection)* Always use unstable sort for simple types ([#14825], thanks @HertzDevil) +- *(collection)* Optimize `Hash#transform_{keys,values}` ([#14502], thanks @jgaskins) +- *(numeric)* Optimize arithmetic between `BigFloat` and integers ([#14944], thanks @HertzDevil) +- *(runtime)* **[regression]** Cache `Exception::CallStack.empty` to avoid repeat `Array` allocation ([#15025], thanks @straight-shoota) + +[#14825]: https://github.com/crystal-lang/crystal/pull/14825 +[#14502]: https://github.com/crystal-lang/crystal/pull/14502 +[#14944]: https://github.com/crystal-lang/crystal/pull/14944 +[#15025]: https://github.com/crystal-lang/crystal/pull/15025 + +#### compiler + +- Avoid unwinding the stack on hot path in method call lookups ([#15002], thanks @ggiraldez) +- *(codegen)* Reduce calls to `Crystal::Type#remove_indirection` in module dispatch ([#14992], thanks @HertzDevil) +- *(codegen)* Compiler: enable parallel codegen with MT ([#14748], thanks @ysbaddaden) + +[#15002]: https://github.com/crystal-lang/crystal/pull/15002 +[#14992]: https://github.com/crystal-lang/crystal/pull/14992 +[#14748]: https://github.com/crystal-lang/crystal/pull/14748 + +### Refactor + +#### stdlib + +- *(concurrency)* Extract `select` from `src/channel.cr` ([#14912], thanks @straight-shoota) +- *(concurrency)* Make `Crystal::IOCP::OverlappedOperation` abstract ([#14987], thanks @HertzDevil) +- *(files)* Move `#evented_read`, `#evented_write` into `Crystal::LibEvent::EventLoop` ([#14883], thanks @straight-shoota) +- *(networking)* Simplify `Socket::Addrinfo.getaddrinfo(&)` ([#14956], thanks @HertzDevil) +- *(networking)* Add `Crystal::System::Addrinfo` ([#14957], thanks @HertzDevil) +- *(runtime)* Add `Exception::CallStack.empty` ([#15017], thanks @straight-shoota) +- *(system)* Refactor cancellation of `IOCP::OverlappedOperation` ([#14754], thanks @straight-shoota) +- *(system)* Include `Crystal::System::Group` instead of extending it ([#14930], thanks @HertzDevil) +- *(system)* Include `Crystal::System::User` instead of extending it ([#14929], thanks @HertzDevil) +- *(system)* Fix: `Crystal::SpinLock` doesn't need to be allocated on the HEAP ([#14972], thanks @ysbaddaden) +- *(system)* Don't involve evloop after fork in `System::Process.spawn` (UNIX) ([#14974], thanks @ysbaddaden) +- *(system)* Refactor `EventLoop` interface for sleeps & select timeouts ([#14980], thanks @ysbaddaden) + +[#14912]: https://github.com/crystal-lang/crystal/pull/14912 +[#14987]: https://github.com/crystal-lang/crystal/pull/14987 +[#14883]: https://github.com/crystal-lang/crystal/pull/14883 +[#14956]: https://github.com/crystal-lang/crystal/pull/14956 +[#14957]: https://github.com/crystal-lang/crystal/pull/14957 +[#15017]: https://github.com/crystal-lang/crystal/pull/15017 +[#14754]: https://github.com/crystal-lang/crystal/pull/14754 +[#14930]: https://github.com/crystal-lang/crystal/pull/14930 +[#14929]: https://github.com/crystal-lang/crystal/pull/14929 +[#14972]: https://github.com/crystal-lang/crystal/pull/14972 +[#14974]: https://github.com/crystal-lang/crystal/pull/14974 +[#14980]: https://github.com/crystal-lang/crystal/pull/14980 + +#### compiler + +- *(codegen)* Compiler: refactor codegen ([#14760], thanks @ysbaddaden) +- *(interpreter)* Refactor interpreter stack code to avoid duplicate macro expansion ([#14876], thanks @straight-shoota) + +[#14760]: https://github.com/crystal-lang/crystal/pull/14760 +[#14876]: https://github.com/crystal-lang/crystal/pull/14876 + +### Documentation + +#### stdlib + +- *(collection)* **[breaking]** Hide `Hash::Entry` from public API docs ([#14881], thanks @Blacksmoke16) +- *(collection)* Fix typos in docs for `Set` and `Hash` ([#14889], thanks @philipp-classen) +- *(llvm)* Add `@[Experimental]` to `LLVM::DIBuilder` ([#14854], thanks @HertzDevil) +- *(networking)* Add documentation about synchronous DNS resolution ([#15027], thanks @straight-shoota) +- *(networking)* Add `uri/json` to `docs_main` ([#15069], thanks @straight-shoota) +- *(runtime)* Add docs about `Pointer`'s alignment requirement ([#14853], thanks @HertzDevil) +- *(runtime)* Reword `Pointer#memcmp`'s documentation ([#14818], thanks @HertzDevil) +- *(runtime)* Add documentation for `NoReturn` and `Void` ([#14817], thanks @nobodywasishere) + +[#14881]: https://github.com/crystal-lang/crystal/pull/14881 +[#14889]: https://github.com/crystal-lang/crystal/pull/14889 +[#14854]: https://github.com/crystal-lang/crystal/pull/14854 +[#15027]: https://github.com/crystal-lang/crystal/pull/15027 +[#15069]: https://github.com/crystal-lang/crystal/pull/15069 +[#14853]: https://github.com/crystal-lang/crystal/pull/14853 +[#14818]: https://github.com/crystal-lang/crystal/pull/14818 +[#14817]: https://github.com/crystal-lang/crystal/pull/14817 + +### Specs + +#### stdlib + +- Remove some uses of deprecated overloads in standard library specs ([#14963], thanks @HertzDevil) +- *(collection)* Disable `Tuple#to_static_array` spec on AArch64 ([#14844], thanks @HertzDevil) +- *(serialization)* Add JSON parsing UTF-8 spec ([#14823], thanks @Blacksmoke16) +- *(text)* Add specs for `String#index`, `#rindex` search for `Char::REPLACEMENT` ([#14946], thanks @straight-shoota) + +[#14963]: https://github.com/crystal-lang/crystal/pull/14963 +[#14844]: https://github.com/crystal-lang/crystal/pull/14844 +[#14823]: https://github.com/crystal-lang/crystal/pull/14823 +[#14946]: https://github.com/crystal-lang/crystal/pull/14946 + +#### compiler + +- *(codegen)* Support return types in codegen specs ([#14888], thanks @HertzDevil) +- *(codegen)* Fix codegen spec for `ProcPointer` of virtual type ([#14903], thanks @HertzDevil) +- *(codegen)* Support LLVM OrcV2 codegen specs ([#14886], thanks @HertzDevil) +- *(codegen)* Don't spawn subprocess if codegen spec uses flags but not the prelude ([#14904], thanks @HertzDevil) + +[#14888]: https://github.com/crystal-lang/crystal/pull/14888 +[#14903]: https://github.com/crystal-lang/crystal/pull/14903 +[#14886]: https://github.com/crystal-lang/crystal/pull/14886 +[#14904]: https://github.com/crystal-lang/crystal/pull/14904 + +### Infrastructure + +- Changelog for 1.14.0 ([#14969], thanks @straight-shoota) +- Update previous Crystal release 1.13.1 ([#14810], thanks @straight-shoota) +- Refactor GitHub changelog generator print special infra ([#14795], thanks @straight-shoota) +- Update distribution-scripts ([#14877], thanks @straight-shoota) +- Update version in `shard.yml` ([#14909], thanks @straight-shoota) +- Merge `release/1.13`@1.13.2 ([#14924], thanks @straight-shoota) +- Update previous Crystal release 1.13.2 ([#14925], thanks @straight-shoota) +- Merge `release/1.13`@1.13.3 ([#15012], thanks @straight-shoota) +- Update previous Crystal release 1.13.3 ([#15016], thanks @straight-shoota) +- **[regression]** Fix `SOURCE_DATE_EPOCH` in `Makefile.win` ([#14922], thanks @HertzDevil) +- *(ci)* Update actions/checkout action to v4 - autoclosed ([#14896], thanks @renovate) +- *(ci)* Update LLVM 18 for `wasm32-test` ([#14821], thanks @straight-shoota) +- *(ci)* Pin package repos for OpenSSL packages ([#14831], thanks @straight-shoota) +- *(ci)* Upgrade XCode 15.4.0 ([#14794], thanks @straight-shoota) +- *(ci)* Update GH Actions ([#14535], thanks @renovate) +- *(ci)* Add test for OpenSSL 3.3 ([#14873], thanks @straight-shoota) +- *(ci)* Update GitHub runner to `macos-14` ([#14833], thanks @straight-shoota) +- *(ci)* Refactor SSL workflow with job matrix ([#14899], thanks @straight-shoota) +- *(ci)* Drop the non-release Windows compiler artifact ([#15000], thanks @HertzDevil) +- *(ci)* Fix compiler artifact name in WindowsCI ([#15021], thanks @straight-shoota) +- *(ci)* Restrict CI runners from runs-on to 8GB ([#15030], thanks @straight-shoota) +- *(ci)* Increase memory for stdlib CI runners from runs-on to 16GB ([#15044], thanks @straight-shoota) +- *(ci)* Use Cygwin to build libiconv on Windows CI ([#14999], thanks @HertzDevil) +- *(ci)* Use our own `libffi` repository on Windows CI ([#14998], thanks @HertzDevil) + +[#14969]: https://github.com/crystal-lang/crystal/pull/14969 +[#14810]: https://github.com/crystal-lang/crystal/pull/14810 +[#14795]: https://github.com/crystal-lang/crystal/pull/14795 +[#14877]: https://github.com/crystal-lang/crystal/pull/14877 +[#14909]: https://github.com/crystal-lang/crystal/pull/14909 +[#14924]: https://github.com/crystal-lang/crystal/pull/14924 +[#14925]: https://github.com/crystal-lang/crystal/pull/14925 +[#15012]: https://github.com/crystal-lang/crystal/pull/15012 +[#15016]: https://github.com/crystal-lang/crystal/pull/15016 +[#14922]: https://github.com/crystal-lang/crystal/pull/14922 +[#14896]: https://github.com/crystal-lang/crystal/pull/14896 +[#14821]: https://github.com/crystal-lang/crystal/pull/14821 +[#14831]: https://github.com/crystal-lang/crystal/pull/14831 +[#14794]: https://github.com/crystal-lang/crystal/pull/14794 +[#14535]: https://github.com/crystal-lang/crystal/pull/14535 +[#14873]: https://github.com/crystal-lang/crystal/pull/14873 +[#14833]: https://github.com/crystal-lang/crystal/pull/14833 +[#14899]: https://github.com/crystal-lang/crystal/pull/14899 +[#15000]: https://github.com/crystal-lang/crystal/pull/15000 +[#15021]: https://github.com/crystal-lang/crystal/pull/15021 +[#15030]: https://github.com/crystal-lang/crystal/pull/15030 +[#15044]: https://github.com/crystal-lang/crystal/pull/15044 +[#14999]: https://github.com/crystal-lang/crystal/pull/14999 +[#14998]: https://github.com/crystal-lang/crystal/pull/14998 + ## [1.13.3] (2024-09-18) [1.13.3]: https://github.com/crystal-lang/crystal/releases/1.13.3 diff --git a/shard.yml b/shard.yml index 1b2835281466..2d43b601771e 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: crystal -version: 1.14.0-dev +version: 1.14.0 authors: - Crystal Core Team diff --git a/src/SOURCE_DATE_EPOCH b/src/SOURCE_DATE_EPOCH new file mode 100644 index 000000000000..e20d25c81b56 --- /dev/null +++ b/src/SOURCE_DATE_EPOCH @@ -0,0 +1 @@ +1728432000 diff --git a/src/VERSION b/src/VERSION index 2f2e08cfa3bf..850e742404bb 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.14.0-dev +1.14.0 From 32b9e6e1c9e984e76548e782d887b3abf6d6df29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 10 Oct 2024 09:39:47 +0200 Subject: [PATCH 1394/1551] Update previous Crystal release 1.14.0 (#15071) --- .circleci/config.yml | 2 +- .github/workflows/interpreter.yml | 8 ++++---- .github/workflows/linux.yml | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/openssl.yml | 2 +- .github/workflows/regex-engine.yml | 4 ++-- .github/workflows/wasm32.yml | 2 +- .github/workflows/win_build_portable.yml | 2 +- bin/ci | 6 +++--- shard.yml | 2 +- shell.nix | 12 ++++++------ src/SOURCE_DATE_EPOCH | 1 - src/VERSION | 2 +- 13 files changed, 23 insertions(+), 24 deletions(-) delete mode 100644 src/SOURCE_DATE_EPOCH diff --git a/.circleci/config.yml b/.circleci/config.yml index 5be7fd2cd388..574d390f3bc3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ parameters: previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string - default: "https://github.com/crystal-lang/crystal/releases/download/1.13.3/crystal-1.13.3-1" + default: "https://github.com/crystal-lang/crystal/releases/download/1.14.0/crystal-1.14.0-1" defaults: environment: &env diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index aa28b15f9abc..3c74afdd329e 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ jobs: test-interpreter_spec: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.3-build + image: crystallang/crystal:1.14.0-build name: "Test Interpreter" steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: build-interpreter: runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.3-build + image: crystallang/crystal:1.14.0-build name: Build interpreter steps: - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.3-build + image: crystallang/crystal:1.14.0-build strategy: matrix: part: [0, 1, 2, 3] @@ -67,7 +67,7 @@ jobs: needs: build-interpreter runs-on: ubuntu-22.04 container: - image: crystallang/crystal:1.13.3-build + image: crystallang/crystal:1.14.0-build name: "Test primitives_spec with interpreter" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a729d5f7681d..4bdcc3e0c11e 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2, 1.13.3] + crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2, 1.13.3, 1.14.0] flags: [""] include: # libffi is only available starting from the 1.2.2 build images diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 796b26a66c08..65d0744575b9 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -60,7 +60,7 @@ jobs: - name: Install Crystal uses: crystal-lang/install-crystal@v1 with: - crystal: "1.13.3" + crystal: "1.14.0" - name: Build libllvm_ext run: make -B deps diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 30bc74844e2b..7321abddf788 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -10,7 +10,7 @@ jobs: libssl_test: runs-on: ubuntu-latest name: "${{ matrix.pkg }}" - container: crystallang/crystal:1.13.3-alpine + container: crystallang/crystal:1.14.0-alpine strategy: fail-fast: false matrix: diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index 9587c5fae85f..e7ee002103b4 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -10,7 +10,7 @@ jobs: pcre: runs-on: ubuntu-latest name: "PCRE" - container: crystallang/crystal:1.13.3-alpine + container: crystallang/crystal:1.14.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 @@ -25,7 +25,7 @@ jobs: pcre2: runs-on: ubuntu-latest name: "PCRE2" - container: crystallang/crystal:1.13.3-alpine + container: crystallang/crystal:1.14.0-alpine steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index e35a9ad182fd..d0012b67c40f 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -12,7 +12,7 @@ env: jobs: wasm32-test: runs-on: ubuntu-latest - container: crystallang/crystal:1.13.3-build + container: crystallang/crystal:1.14.0-build steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index d3265239c20c..889f4d80c629 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -25,7 +25,7 @@ jobs: uses: crystal-lang/install-crystal@v1 id: install-crystal with: - crystal: "1.13.3" + crystal: "1.14.0" - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index d998373438e8..03d8a20a19e4 100755 --- a/bin/ci +++ b/bin/ci @@ -135,8 +135,8 @@ format() { prepare_build() { on_linux verify_linux_environment - on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.13.3/crystal-1.13.3-1-darwin-universal.tar.gz -o ~/crystal.tar.gz - on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.13.3-1 crystal;popd' + on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.14.0/crystal-1.14.0-1-darwin-universal.tar.gz -o ~/crystal.tar.gz + on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.14.0-1 crystal;popd' # These commands may take a few minutes to run due to the large size of the repositories. # This restriction has been made on GitHub's request because updating shallow @@ -189,7 +189,7 @@ with_build_env() { on_linux verify_linux_environment - export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.13.3}" + export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.14.0}" case $ARCH in x86_64) diff --git a/shard.yml b/shard.yml index 2d43b601771e..4ddf0dcfb0df 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: crystal -version: 1.14.0 +version: 1.15.0-dev authors: - Crystal Core Team diff --git a/shell.nix b/shell.nix index efadd688f0e3..6501b4a0c577 100644 --- a/shell.nix +++ b/shell.nix @@ -53,18 +53,18 @@ let # Hashes obtained using `nix-prefetch-url --unpack ` latestCrystalBinary = genericBinary ({ x86_64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.13.3/crystal-1.13.3-1-darwin-universal.tar.gz"; - sha256 = "sha256:0iri1hl23kgmlibmm64wc4wdq019z544b7m2h1bl7jxs4dk2wwla"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.14.0/crystal-1.14.0-1-darwin-universal.tar.gz"; + sha256 = "sha256:09mp3mngj4wik4v2bffpms3x8dksmrcy0a7hs4cg8b13hrfdrgww"; }; aarch64-darwin = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.13.3/crystal-1.13.3-1-darwin-universal.tar.gz"; - sha256 = "sha256:0iri1hl23kgmlibmm64wc4wdq019z544b7m2h1bl7jxs4dk2wwla"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.14.0/crystal-1.14.0-1-darwin-universal.tar.gz"; + sha256 = "sha256:09mp3mngj4wik4v2bffpms3x8dksmrcy0a7hs4cg8b13hrfdrgww"; }; x86_64-linux = { - url = "https://github.com/crystal-lang/crystal/releases/download/1.13.3/crystal-1.13.3-1-linux-x86_64.tar.gz"; - sha256 = "sha256:1zf9b3njxx0jzn81dy6vyhkml31kjxfk4iskf13w9ysj0kwakbyz"; + url = "https://github.com/crystal-lang/crystal/releases/download/1.14.0/crystal-1.14.0-1-linux-x86_64.tar.gz"; + sha256 = "sha256:0p5b22ivggf9xlw91cbhib7n4lzd8is1shd3480jjp14rn1kiy5z"; }; }.${pkgs.stdenv.system}); diff --git a/src/SOURCE_DATE_EPOCH b/src/SOURCE_DATE_EPOCH deleted file mode 100644 index e20d25c81b56..000000000000 --- a/src/SOURCE_DATE_EPOCH +++ /dev/null @@ -1 +0,0 @@ -1728432000 diff --git a/src/VERSION b/src/VERSION index 850e742404bb..9a4866bbcede 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.14.0 +1.15.0-dev From f3d49d715fc0b525c0953434096d6146a49fde5b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 10 Oct 2024 18:14:26 +0800 Subject: [PATCH 1395/1551] Do not over-commit fiber stacks on Windows (#15037) Whenever a new fiber is spawned on Windows, currently Crystal allocates a fully committed 8 MB range of virtual memory. This commit charge stays until the stack becomes unused and reaped, even when most of the stack goes unused. Thus it is "only" possible to spawn several thousand fibers concurrently before the system runs out of virtual memory, depending on the total size of RAM and page files. With this PR, for every fresh fiber stack, only the guard pages plus one extra initial page are committed. Spawning 100,000 idle fibers now consumes just around 7.4 GB of virtual memory, instead of 800 GB. Committed pages are also reset after a stack is returned to a pool and before it is retrieved again; this should be reasonably first, as decommitting pages doesn't alter the page contents. Note that the guard pages reside immediately above the normal committed pages, not at the top of the whole reserved range. This is required for proper stack overflow detection. --- spec/std/process_spec.cr | 8 ++++ src/crystal/system/fiber.cr | 8 ++-- src/crystal/system/unix/fiber.cr | 3 ++ src/crystal/system/wasi/fiber.cr | 3 ++ src/crystal/system/win32/fiber.cr | 65 ++++++++++++++++++++------- src/fiber/context/x86_64-microsoft.cr | 6 ++- src/fiber/stack_pool.cr | 6 ++- 7 files changed, 78 insertions(+), 21 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 01a154ccb010..8347804cadc5 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -172,6 +172,14 @@ describe Process do error.to_s.should eq("hello#{newline}") end + it "sends long output and error to IO" do + output = IO::Memory.new + error = IO::Memory.new + Process.run(*shell_command("echo #{"." * 8000}"), output: output, error: error) + output.to_s.should eq("." * 8000 + newline) + error.to_s.should be_empty + end + it "controls process in block" do value = Process.run(*stdin_to_stdout_command, error: :inherit) do |proc| proc.input.puts "hello" diff --git a/src/crystal/system/fiber.cr b/src/crystal/system/fiber.cr index 1cc47e2917e1..1f15d2fe5535 100644 --- a/src/crystal/system/fiber.cr +++ b/src/crystal/system/fiber.cr @@ -1,12 +1,12 @@ module Crystal::System::Fiber # Allocates memory for a stack. - # def self.allocate_stack(stack_size : Int) : Void* + # def self.allocate_stack(stack_size : Int, protect : Bool) : Void* + + # Prepares an existing, unused stack for use again. + # def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil # Frees memory of a stack. # def self.free_stack(stack : Void*, stack_size : Int) : Nil - - # Determines location of the top of the main process fiber's stack. - # def self.main_fiber_stack(stack_bottom : Void*) : Void* end {% if flag?(:wasi) %} diff --git a/src/crystal/system/unix/fiber.cr b/src/crystal/system/unix/fiber.cr index 317a3f7fbd41..42153b28bed2 100644 --- a/src/crystal/system/unix/fiber.cr +++ b/src/crystal/system/unix/fiber.cr @@ -21,6 +21,9 @@ module Crystal::System::Fiber pointer end + def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil + end + def self.free_stack(stack : Void*, stack_size) : Nil LibC.munmap(stack, stack_size) end diff --git a/src/crystal/system/wasi/fiber.cr b/src/crystal/system/wasi/fiber.cr index 516fcc10a29a..8461bb15d00c 100644 --- a/src/crystal/system/wasi/fiber.cr +++ b/src/crystal/system/wasi/fiber.cr @@ -3,6 +3,9 @@ module Crystal::System::Fiber LibC.malloc(stack_size) end + def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil + end + def self.free_stack(stack : Void*, stack_size) : Nil LibC.free(stack) end diff --git a/src/crystal/system/win32/fiber.cr b/src/crystal/system/win32/fiber.cr index 9e6495ee594e..05fd230a9cac 100644 --- a/src/crystal/system/win32/fiber.cr +++ b/src/crystal/system/win32/fiber.cr @@ -7,28 +7,63 @@ module Crystal::System::Fiber # overflow RESERVED_STACK_SIZE = LibC::DWORD.new(0x10000) - # the reserved stack size, plus the size of a single page - @@total_reserved_size : LibC::DWORD = begin - LibC.GetNativeSystemInfo(out system_info) - system_info.dwPageSize + RESERVED_STACK_SIZE - end - def self.allocate_stack(stack_size, protect) : Void* - unless memory_pointer = LibC.VirtualAlloc(nil, stack_size, LibC::MEM_COMMIT | LibC::MEM_RESERVE, LibC::PAGE_READWRITE) - raise RuntimeError.from_winerror("VirtualAlloc") + if stack_top = LibC.VirtualAlloc(nil, stack_size, LibC::MEM_RESERVE, LibC::PAGE_READWRITE) + if protect + if commit_and_guard(stack_top, stack_size) + return stack_top + end + else + # for the interpreter, the stack is just ordinary memory so the entire + # range is committed + if LibC.VirtualAlloc(stack_top, stack_size, LibC::MEM_COMMIT, LibC::PAGE_READWRITE) + return stack_top + end + end + + # failure + LibC.VirtualFree(stack_top, 0, LibC::MEM_RELEASE) end - # Detects stack overflows by guarding the top of the stack, similar to - # `LibC.mprotect`. Windows will fail to allocate a new guard page for these - # fiber stacks and trigger a stack overflow exception + raise RuntimeError.from_winerror("VirtualAlloc") + end + + def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil if protect - if LibC.VirtualProtect(memory_pointer, @@total_reserved_size, LibC::PAGE_READWRITE | LibC::PAGE_GUARD, out _) == 0 - LibC.VirtualFree(memory_pointer, 0, LibC::MEM_RELEASE) - raise RuntimeError.from_winerror("VirtualProtect") + if LibC.VirtualFree(stack, 0, LibC::MEM_DECOMMIT) == 0 + raise RuntimeError.from_winerror("VirtualFree") + end + unless commit_and_guard(stack, stack_size) + raise RuntimeError.from_winerror("VirtualAlloc") end end + end + + # Commits the bottommost page and sets up the guard pages above it, in the + # same manner as each thread's main stack. When the stack hits a guard page + # for the first time, a page fault is generated, the page's guard status is + # reset, and Windows checks if a reserved page is available above. On success, + # a new guard page is committed, and on failure, a stack overflow exception is + # triggered after the `RESERVED_STACK_SIZE` portion is made available. + private def self.commit_and_guard(stack_top, stack_size) + stack_bottom = stack_top + stack_size + + LibC.GetNativeSystemInfo(out system_info) + stack_commit_size = system_info.dwPageSize + stack_commit_top = stack_bottom - stack_commit_size + unless LibC.VirtualAlloc(stack_commit_top, stack_commit_size, LibC::MEM_COMMIT, LibC::PAGE_READWRITE) + return false + end + + # the reserved stack size, plus a final guard page for when the stack + # overflow handler itself overflows the stack + stack_guard_size = system_info.dwPageSize + RESERVED_STACK_SIZE + stack_guard_top = stack_commit_top - stack_guard_size + unless LibC.VirtualAlloc(stack_guard_top, stack_guard_size, LibC::MEM_COMMIT, LibC::PAGE_READWRITE | LibC::PAGE_GUARD) + return false + end - memory_pointer + true end def self.free_stack(stack : Void*, stack_size) : Nil diff --git a/src/fiber/context/x86_64-microsoft.cr b/src/fiber/context/x86_64-microsoft.cr index 83f95ea7b069..08576fc348aa 100644 --- a/src/fiber/context/x86_64-microsoft.cr +++ b/src/fiber/context/x86_64-microsoft.cr @@ -10,13 +10,17 @@ class Fiber @context.stack_top = (stack_ptr - (12 + 10*2)).as(Void*) @context.resumable = 1 + # actual stack top, not including guard pages and reserved pages + LibC.GetNativeSystemInfo(out system_info) + stack_top = @stack_bottom - system_info.dwPageSize + stack_ptr[0] = fiber_main.pointer # %rbx: Initial `resume` will `ret` to this address stack_ptr[-1] = self.as(Void*) # %rcx: puts `self` as first argument for `fiber_main` # The following three values are stored in the Thread Information Block (NT_TIB) # and are used by Windows to track the current stack limits stack_ptr[-2] = @stack # %gs:0x1478: Win32 DeallocationStack - stack_ptr[-3] = @stack # %gs:0x10: Stack Limit + stack_ptr[-3] = stack_top # %gs:0x10: Stack Limit stack_ptr[-4] = @stack_bottom # %gs:0x08: Stack Base end diff --git a/src/fiber/stack_pool.cr b/src/fiber/stack_pool.cr index c9ea3ceb68e0..8f809335f46c 100644 --- a/src/fiber/stack_pool.cr +++ b/src/fiber/stack_pool.cr @@ -42,7 +42,11 @@ class Fiber # Removes a stack from the bottom of the pool, or allocates a new one. def checkout : {Void*, Void*} - stack = @deque.pop? || Crystal::System::Fiber.allocate_stack(STACK_SIZE, @protect) + if stack = @deque.pop? + Crystal::System::Fiber.reset_stack(stack, STACK_SIZE, @protect) + else + stack = Crystal::System::Fiber.allocate_stack(STACK_SIZE, @protect) + end {stack, stack + STACK_SIZE} end From 0606cf05bbc2f651296e685ea06eae100c74f394 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Thu, 10 Oct 2024 03:15:10 -0700 Subject: [PATCH 1396/1551] Implement `codecov` format for `unreachable` tool (#15059) --- src/compiler/crystal/tools/unreachable.cr | 29 ++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr index 733a94518899..8455a4186882 100644 --- a/src/compiler/crystal/tools/unreachable.cr +++ b/src/compiler/crystal/tools/unreachable.cr @@ -6,7 +6,7 @@ require "csv" module Crystal class Command private def unreachable - config, result = compile_no_codegen "tool unreachable", path_filter: true, unreachable_command: true, allowed_formats: %w[text json csv] + config, result = compile_no_codegen "tool unreachable", path_filter: true, unreachable_command: true, allowed_formats: %w[text json csv codecov] unreachable = UnreachableVisitor.new @@ -42,6 +42,8 @@ module Crystal to_json(STDOUT) when "csv" to_csv(STDOUT) + when "codecov" + to_codecov(STDOUT) else to_text(STDOUT) end @@ -111,6 +113,31 @@ module Crystal end end end + + # https://docs.codecov.com/docs/codecov-custom-coverage-format + def to_codecov(io) + hits = Hash(String, Hash(Int32, Int32)).new { |hash, key| hash[key] = Hash(Int32, Int32).new(0) } + + each do |a_def, location, count| + hits[location.filename][location.line_number] = count + end + + JSON.build io do |builder| + builder.object do + builder.string "coverage" + builder.object do + hits.each do |filename, line_coverage| + builder.string filename + builder.object do + line_coverage.each do |line, count| + builder.field line, count + end + end + end + end + end + end + end end # This visitor walks the entire reachable code tree and collect locations From f237af03d7e39f2ab65627f7e3e44451f124ca63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=C3=B6rjesson?= Date: Thu, 10 Oct 2024 12:17:10 +0200 Subject: [PATCH 1397/1551] Add `Iterator(T).empty` (#15039) --- spec/std/iterator_spec.cr | 7 +++++++ src/iterator.cr | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/spec/std/iterator_spec.cr b/spec/std/iterator_spec.cr index a07b8bedb191..b7f000a871cb 100644 --- a/spec/std/iterator_spec.cr +++ b/spec/std/iterator_spec.cr @@ -33,6 +33,13 @@ private class MockIterator end describe Iterator do + describe "Iterator.empty" do + it "creates empty iterator" do + iter = Iterator(String).empty + iter.next.should be_a(Iterator::Stop) + end + end + describe "Iterator.of" do it "creates singleton" do iter = Iterator.of(42) diff --git a/src/iterator.cr b/src/iterator.cr index a46c813b36b3..6a1513ef2130 100644 --- a/src/iterator.cr +++ b/src/iterator.cr @@ -144,6 +144,19 @@ module Iterator(T) Stop::INSTANCE end + # Returns an empty iterator. + def self.empty + EmptyIterator(T).new + end + + private struct EmptyIterator(T) + include Iterator(T) + + def next + stop + end + end + def self.of(element : T) SingletonIterator(T).new(element) end From b3a052c293e14b27cba241812de78dcb425c38e1 Mon Sep 17 00:00:00 2001 From: Roberto Alsina Date: Thu, 10 Oct 2024 10:13:40 -0300 Subject: [PATCH 1398/1551] Add `Regex::CompileOptions::MULTILINE_ONLY` (#14870) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Crystal's `Regex` conflates the pcre options `MULTILINE` and `DOT_ALL` into `CompilerOptions::MULTILINE`. This patch adds an option to use `MULTILINE` without `DOTALL`. To keep backwards compatibility, the behaviour of `MULTILINE` in crystal must not be changed, so the new option is added as `MULTILINE_ONLY`. --------- Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Johannes Müller --- spec/std/regex_spec.cr | 7 +++++++ src/regex.cr | 5 +++++ src/regex/pcre.cr | 3 ++- src/regex/pcre2.cr | 3 ++- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index 13d301987c56..af03cb2c79b8 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -250,6 +250,13 @@ describe "Regex" do end end + describe "multiline_only" do + it "anchor" do + ((/^foo.*$/m).match("foo\nbar")).try(&.[](0)).should eq "foo\nbar" + ((Regex.new("^foo.*?", Regex::Options::MULTILINE_ONLY)).match("foo\nbar")).try(&.[](0)).should eq "foo" + end + end + describe "extended" do it "ignores white space" do /foo bar/.matches?("foobar").should be_false diff --git a/src/regex.cr b/src/regex.cr index 69dd500226a9..c71ac9cd673a 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -240,12 +240,17 @@ class Regex # flag that activates both behaviours, so here we do the same by # mapping `MULTILINE` to `PCRE_MULTILINE | PCRE_DOTALL`. # The same applies for PCRE2 except that the native values are 0x200 and 0x400. + # + # For the behaviour of `PCRE_MULTILINE` use `MULTILINE_ONLY`. # Multiline matching. # # Equivalent to `MULTILINE | DOTALL` in PCRE and PCRE2. MULTILINE = 0x0000_0006 + # Equivalent to `MULTILINE` in PCRE and PCRE2. + MULTILINE_ONLY = 0x0000_0004 + DOTALL = 0x0000_0002 # Ignore white space and `#` comments. diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr index c80714708a0b..19decbb66712 100644 --- a/src/regex/pcre.cr +++ b/src/regex/pcre.cr @@ -36,7 +36,8 @@ module Regex::PCRE if options.includes?(option) flag |= case option when .ignore_case? then LibPCRE::CASELESS - when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE + when .multiline? then LibPCRE::MULTILINE | LibPCRE::DOTALL + when .multiline_only? then LibPCRE::MULTILINE when .dotall? then LibPCRE::DOTALL when .extended? then LibPCRE::EXTENDED when .anchored? then LibPCRE::ANCHORED diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index abbb502eb78c..b56a4ea68839 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -67,7 +67,8 @@ module Regex::PCRE2 if options.includes?(option) flag |= case option when .ignore_case? then LibPCRE2::CASELESS - when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE + when .multiline? then LibPCRE2::MULTILINE | LibPCRE2::DOTALL + when .multiline_only? then LibPCRE2::MULTILINE when .dotall? then LibPCRE2::DOTALL when .extended? then LibPCRE2::EXTENDED when .anchored? then LibPCRE2::ANCHORED From 8b4fe412c487cba7cabce8e37c815616a03a0270 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Thu, 10 Oct 2024 15:14:41 +0200 Subject: [PATCH 1399/1551] Assume `getrandom` on Linux (#15040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `getrandom(2)` syscall was added in 2017 and at the time we couldn't expect the glibc 2.25 to be widely available, but we're in 2024 now, and even Ubuntu 18.04 LTS that is now EOL had a compatible glibc release (2.27). I assume musl-libc also added the symbol at the same time. We can simplify the implementation to assume `getrandom` is available, which avoids the initial check, initialization and fallback to urandom. We still fallback to urandom at compile time when targeting android api level < 28 (we support 24+). An issue is that executables will now expect glibc 2.25+ (for example), though the interpreter already did. We also expect kernel 2.6.18 to be compatible, but `getrandom` was added in 3.17 which means it depends on how the libc symbol is implemented —does it fallback to urandom, does it fail? Related to #15034. Co-authored-by: Johannes Müller --- src/crystal/system/random.cr | 7 +- src/crystal/system/unix/getrandom.cr | 118 +++--------------- src/crystal/system/unix/urandom.cr | 2 - src/lib_c/aarch64-android/c/sys/random.cr | 7 ++ src/lib_c/aarch64-linux-gnu/c/sys/random.cr | 5 + src/lib_c/aarch64-linux-musl/c/sys/random.cr | 5 + src/lib_c/arm-linux-gnueabihf/c/sys/random.cr | 5 + src/lib_c/i386-linux-gnu/c/sys/random.cr | 5 + src/lib_c/i386-linux-musl/c/sys/random.cr | 5 + src/lib_c/x86_64-linux-gnu/c/sys/random.cr | 5 + src/lib_c/x86_64-linux-musl/c/sys/random.cr | 5 + src/random/secure.cr | 2 +- 12 files changed, 68 insertions(+), 103 deletions(-) create mode 100644 src/lib_c/aarch64-android/c/sys/random.cr create mode 100644 src/lib_c/aarch64-linux-gnu/c/sys/random.cr create mode 100644 src/lib_c/aarch64-linux-musl/c/sys/random.cr create mode 100644 src/lib_c/arm-linux-gnueabihf/c/sys/random.cr create mode 100644 src/lib_c/i386-linux-gnu/c/sys/random.cr create mode 100644 src/lib_c/i386-linux-musl/c/sys/random.cr create mode 100644 src/lib_c/x86_64-linux-gnu/c/sys/random.cr create mode 100644 src/lib_c/x86_64-linux-musl/c/sys/random.cr diff --git a/src/crystal/system/random.cr b/src/crystal/system/random.cr index 1a5b3c8f4677..ccf9d6dfa344 100644 --- a/src/crystal/system/random.cr +++ b/src/crystal/system/random.cr @@ -13,7 +13,12 @@ end {% if flag?(:wasi) %} require "./wasi/random" {% elsif flag?(:linux) %} - require "./unix/getrandom" + require "c/sys/random" + \{% if LibC.has_method?(:getrandom) %} + require "./unix/getrandom" + \{% else %} + require "./unix/urandom" + \{% end %} {% elsif flag?(:bsd) || flag?(:darwin) %} require "./unix/arc4random" {% elsif flag?(:unix) %} diff --git a/src/crystal/system/unix/getrandom.cr b/src/crystal/system/unix/getrandom.cr index e759ff0406e6..6ad217c7cbf2 100644 --- a/src/crystal/system/unix/getrandom.cr +++ b/src/crystal/system/unix/getrandom.cr @@ -1,119 +1,39 @@ -{% skip_file unless flag?(:linux) %} - -require "c/unistd" -require "./syscall" - -{% if flag?(:interpreted) %} - lib LibC - fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : LibC::SSizeT - end - - module Crystal::System::Syscall - GRND_NONBLOCK = 1u32 - - # TODO: Implement syscall for interpreter - def self.getrandom(buf : UInt8*, buflen : LibC::SizeT, flags : UInt32) : LibC::SSizeT - # the syscall returns the negative of errno directly, the C function - # doesn't, so we mimic the syscall behavior - read_bytes = LibC.getrandom(buf, buflen, flags) - read_bytes >= 0 ? read_bytes : LibC::SSizeT.new(-Errno.value.value) - end - end -{% end %} +require "c/sys/random" module Crystal::System::Random - @@initialized = false - @@getrandom_available = false - @@urandom : ::File? - - private def self.init - @@initialized = true - - if has_sys_getrandom - @@getrandom_available = true - else - urandom = ::File.open("/dev/urandom", "r") - return unless urandom.info.type.character_device? - - urandom.close_on_exec = true - urandom.read_buffering = false # don't buffer bytes - @@urandom = urandom - end - end - - private def self.has_sys_getrandom - sys_getrandom(Bytes.new(16)) - true - rescue - false - end - # Reads n random bytes using the Linux `getrandom(2)` syscall. - def self.random_bytes(buf : Bytes) : Nil - init unless @@initialized - - if @@getrandom_available - getrandom(buf) - elsif urandom = @@urandom - urandom.read_fully(buf) - else - raise "Failed to access secure source to generate random bytes!" - end + def self.random_bytes(buffer : Bytes) : Nil + getrandom(buffer) end def self.next_u : UInt8 - init unless @@initialized - - if @@getrandom_available - buf = uninitialized UInt8 - getrandom(pointerof(buf).to_slice(1)) - buf - elsif urandom = @@urandom - urandom.read_byte.not_nil! - else - raise "Failed to access secure source to generate random bytes!" - end + buffer = uninitialized UInt8 + getrandom(pointerof(buffer).to_slice(1)) + buffer end # Reads n random bytes using the Linux `getrandom(2)` syscall. - private def self.getrandom(buf) + private def self.getrandom(buffer) # getrandom(2) may only read up to 256 bytes at once without being # interrupted or returning early chunk_size = 256 - while buf.size > 0 - if buf.size < chunk_size - chunk_size = buf.size - end + while buffer.size > 0 + read_bytes = 0 - read_bytes = sys_getrandom(buf[0, chunk_size]) + loop do + # pass GRND_NONBLOCK flag so that it fails with EAGAIN if the requested + # entropy was not available + read_bytes = LibC.getrandom(buffer, buffer.size.clamp(..chunk_size), LibC::GRND_NONBLOCK) + break unless read_bytes == -1 - buf += read_bytes - end - end + err = Errno.value + raise RuntimeError.from_os_error("getrandom", err) unless err.in?(Errno::EINTR, Errno::EAGAIN) - # Low-level wrapper for the `getrandom(2)` syscall, returns the number of - # bytes read or the errno as a negative number if an error occurred (or the - # syscall isn't available). The GRND_NONBLOCK=1 flag is passed as last argument, - # so that it returns -EAGAIN if the requested entropy was not available. - # - # We use the kernel syscall instead of the `getrandom` C function so any - # binary compiled for Linux will always use getrandom if the kernel is 3.17+ - # and silently fallback to read from /dev/urandom if not (so it's more - # portable). - private def self.sys_getrandom(buf : Bytes) - loop do - read_bytes = Syscall.getrandom(buf.to_unsafe, LibC::SizeT.new(buf.size), Syscall::GRND_NONBLOCK) - if read_bytes < 0 - err = Errno.new(-read_bytes.to_i) - if err.in?(Errno::EINTR, Errno::EAGAIN) - ::Fiber.yield - else - raise RuntimeError.from_os_error("getrandom", err) - end - else - return read_bytes + ::Fiber.yield end + + buffer += read_bytes end end end diff --git a/src/crystal/system/unix/urandom.cr b/src/crystal/system/unix/urandom.cr index 7ac025f43e6b..fe81129a8ade 100644 --- a/src/crystal/system/unix/urandom.cr +++ b/src/crystal/system/unix/urandom.cr @@ -1,5 +1,3 @@ -{% skip_file unless flag?(:unix) && !flag?(:netbsd) && !flag?(:openbsd) && !flag?(:linux) %} - module Crystal::System::Random @@initialized = false @@urandom : ::File? diff --git a/src/lib_c/aarch64-android/c/sys/random.cr b/src/lib_c/aarch64-android/c/sys/random.cr new file mode 100644 index 000000000000..77e193958ff2 --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/random.cr @@ -0,0 +1,7 @@ +lib LibC + {% if ANDROID_API >= 28 %} + GRND_NONBLOCK = 1_u32 + + fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT + {% end %} +end diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/random.cr b/src/lib_c/aarch64-linux-gnu/c/sys/random.cr new file mode 100644 index 000000000000..2c74de96abfb --- /dev/null +++ b/src/lib_c/aarch64-linux-gnu/c/sys/random.cr @@ -0,0 +1,5 @@ +lib LibC + GRND_NONBLOCK = 1_u32 + + fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT +end diff --git a/src/lib_c/aarch64-linux-musl/c/sys/random.cr b/src/lib_c/aarch64-linux-musl/c/sys/random.cr new file mode 100644 index 000000000000..2c74de96abfb --- /dev/null +++ b/src/lib_c/aarch64-linux-musl/c/sys/random.cr @@ -0,0 +1,5 @@ +lib LibC + GRND_NONBLOCK = 1_u32 + + fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT +end diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/random.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/random.cr new file mode 100644 index 000000000000..2c74de96abfb --- /dev/null +++ b/src/lib_c/arm-linux-gnueabihf/c/sys/random.cr @@ -0,0 +1,5 @@ +lib LibC + GRND_NONBLOCK = 1_u32 + + fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT +end diff --git a/src/lib_c/i386-linux-gnu/c/sys/random.cr b/src/lib_c/i386-linux-gnu/c/sys/random.cr new file mode 100644 index 000000000000..2c74de96abfb --- /dev/null +++ b/src/lib_c/i386-linux-gnu/c/sys/random.cr @@ -0,0 +1,5 @@ +lib LibC + GRND_NONBLOCK = 1_u32 + + fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT +end diff --git a/src/lib_c/i386-linux-musl/c/sys/random.cr b/src/lib_c/i386-linux-musl/c/sys/random.cr new file mode 100644 index 000000000000..2c74de96abfb --- /dev/null +++ b/src/lib_c/i386-linux-musl/c/sys/random.cr @@ -0,0 +1,5 @@ +lib LibC + GRND_NONBLOCK = 1_u32 + + fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT +end diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/random.cr b/src/lib_c/x86_64-linux-gnu/c/sys/random.cr new file mode 100644 index 000000000000..2c74de96abfb --- /dev/null +++ b/src/lib_c/x86_64-linux-gnu/c/sys/random.cr @@ -0,0 +1,5 @@ +lib LibC + GRND_NONBLOCK = 1_u32 + + fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT +end diff --git a/src/lib_c/x86_64-linux-musl/c/sys/random.cr b/src/lib_c/x86_64-linux-musl/c/sys/random.cr new file mode 100644 index 000000000000..2c74de96abfb --- /dev/null +++ b/src/lib_c/x86_64-linux-musl/c/sys/random.cr @@ -0,0 +1,5 @@ +lib LibC + GRND_NONBLOCK = 1_u32 + + fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT +end diff --git a/src/random/secure.cr b/src/random/secure.cr index 1722b5e6e884..a6b9df03063f 100644 --- a/src/random/secure.cr +++ b/src/random/secure.cr @@ -12,7 +12,7 @@ require "crystal/system/random" # ``` # # On BSD-based systems and macOS/Darwin, it uses [`arc4random`](https://man.openbsd.org/arc4random), -# on Linux [`getrandom`](http://man7.org/linux/man-pages/man2/getrandom.2.html) (if the kernel supports it), +# on Linux [`getrandom`](http://man7.org/linux/man-pages/man2/getrandom.2.html), # on Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom), # and falls back to reading from `/dev/urandom` on UNIX systems. module Random::Secure From 991f9d0036e1e80297f2d934a8a3dc6dda1c36ce Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Thu, 10 Oct 2024 09:53:25 -0700 Subject: [PATCH 1400/1551] Enable pending formatter features (#14718) --- spec/compiler/formatter/formatter_spec.cr | 344 +++++------------- spec/llvm-ir/pass-closure-to-c-debug-loc.cr | 2 +- spec/llvm-ir/proc-call-debug-loc.cr | 2 +- spec/std/benchmark_spec.cr | 2 +- spec/std/channel_spec.cr | 100 ++--- spec/std/concurrent/select_spec.cr | 64 ++-- .../http/server/handlers/log_handler_spec.cr | 2 +- spec/std/openssl/ssl/socket_spec.cr | 4 +- spec/std/proc_spec.cr | 16 +- src/compiler/crystal/command/format.cr | 2 +- src/compiler/crystal/compiler.cr | 2 +- src/compiler/crystal/ffi/lib_ffi.cr | 8 +- .../crystal/interpreter/closure_context.cr | 2 +- .../crystal/interpreter/compiled_def.cr | 2 +- src/compiler/crystal/interpreter/compiler.cr | 4 +- .../crystal/interpreter/interpreter.cr | 2 +- .../crystal/interpreter/lib_function.cr | 2 +- src/compiler/crystal/program.cr | 2 +- src/compiler/crystal/semantic/main_visitor.cr | 4 +- src/compiler/crystal/semantic/type_merge.cr | 4 +- src/compiler/crystal/tools/formatter.cr | 12 +- src/compiler/crystal/tools/init.cr | 2 +- src/compiler/crystal/util.cr | 2 +- src/crystal/system/thread.cr | 2 +- src/crystal/system/win32/event_loop_iocp.cr | 2 +- src/crystal/system/win32/file_descriptor.cr | 2 +- src/crystal/system/win32/socket.cr | 2 +- src/gc/boehm.cr | 4 +- src/kernel.cr | 2 +- src/lib_c/x86_64-windows-msvc/c/consoleapi.cr | 2 +- src/lib_c/x86_64-windows-msvc/c/dbghelp.cr | 2 +- src/lib_c/x86_64-windows-msvc/c/fileapi.cr | 4 +- src/lib_c/x86_64-windows-msvc/c/ioapiset.cr | 14 +- .../x86_64-windows-msvc/c/stringapiset.cr | 4 +- src/lib_c/x86_64-windows-msvc/c/winsock2.cr | 24 +- src/llvm/lib_llvm/debug_info.cr | 42 +-- src/llvm/lib_llvm/orc.cr | 2 +- src/proc.cr | 2 +- src/random/isaac.cr | 2 +- src/wait_group.cr | 2 +- 40 files changed, 272 insertions(+), 428 deletions(-) diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 7c332aac3b0a..02d140088c2d 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -203,8 +203,8 @@ describe Crystal::Formatter do assert_format "def foo ( x , y , ) \n end", "def foo(x, y)\nend" assert_format "def foo ( x , y ,\n) \n end", "def foo(x, y)\nend" assert_format "def foo ( x ,\n y ) \n end", "def foo(x,\n y)\nend" - assert_format "def foo (\nx ,\n y ) \n end", "def foo(\n x,\n y\n)\nend" - assert_format "class Foo\ndef foo (\nx ,\n y ) \n end\nend", "class Foo\n def foo(\n x,\n y\n )\n end\nend" + assert_format "def foo (\nx ,\n y ) \n end", "def foo(\n x,\n y,\n)\nend" + assert_format "class Foo\ndef foo (\nx ,\n y ) \n end\nend", "class Foo\n def foo(\n x,\n y,\n )\n end\nend" assert_format "def foo ( @x) \n end", "def foo(@x)\nend" assert_format "def foo ( @x, @y) \n end", "def foo(@x, @y)\nend" assert_format "def foo ( @@x) \n end", "def foo(@@x)\nend" @@ -277,7 +277,7 @@ describe Crystal::Formatter do assert_format "def foo(@[AnnOne] @[AnnTwo] &block : Int32 -> ); end", "def foo(@[AnnOne] @[AnnTwo] &block : Int32 ->); end" assert_format <<-CRYSTAL def foo( - @[MyAnn] bar + @[MyAnn] bar, ); end CRYSTAL @@ -321,14 +321,14 @@ describe Crystal::Formatter do ); end CRYSTAL def foo( - @[MyAnn] bar + @[MyAnn] bar, ); end CRYSTAL assert_format <<-CRYSTAL def foo( @[MyAnn] - bar + bar, ); end CRYSTAL @@ -336,7 +336,7 @@ describe Crystal::Formatter do def foo( @[MyAnn] @[MyAnn] - bar + bar, ); end CRYSTAL @@ -345,7 +345,7 @@ describe Crystal::Formatter do @[MyAnn] @[MyAnn] bar, - @[MyAnn] baz + @[MyAnn] baz, ); end CRYSTAL @@ -355,7 +355,7 @@ describe Crystal::Formatter do @[MyAnn] bar, - @[MyAnn] baz + @[MyAnn] baz, ); end CRYSTAL @@ -367,7 +367,7 @@ describe Crystal::Formatter do CRYSTAL def foo( @[MyAnn] - bar + bar, ); end CRYSTAL @@ -379,7 +379,7 @@ describe Crystal::Formatter do CRYSTAL def foo( @[MyAnn] - bar + bar, ); end CRYSTAL @@ -391,7 +391,7 @@ describe Crystal::Formatter do @[MyAnn] @[MyAnn] baz, @[MyAnn] @[MyAnn] - biz + biz, ); end CRYSTAL @@ -405,7 +405,7 @@ describe Crystal::Formatter do @[MyAnn] @[MyAnn] - biz + biz, ); end CRYSTAL @@ -433,7 +433,7 @@ describe Crystal::Formatter do @[MyAnn] @[MyAnn] - biz + biz, ); end CRYSTAL @@ -568,7 +568,7 @@ describe Crystal::Formatter do assert_format "with foo yield bar" context "adds `&` to yielding methods that don't have a block parameter (#8764)" do - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo yield end @@ -578,7 +578,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo() yield end @@ -588,7 +588,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( ) yield @@ -600,7 +600,7 @@ describe Crystal::Formatter do CRYSTAL # #13091 - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo # bar yield end @@ -610,7 +610,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo(x) yield end @@ -620,7 +620,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo(x ,) yield end @@ -630,7 +630,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo(x, y) yield @@ -642,7 +642,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo(x, y,) yield @@ -654,7 +654,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo(x ) yield @@ -666,7 +666,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo(x, ) yield @@ -678,7 +678,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( x) yield @@ -691,7 +691,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( x, y) yield @@ -704,7 +704,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( x, y) @@ -719,7 +719,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( x, ) @@ -734,7 +734,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo(a, **b) yield end @@ -744,172 +744,9 @@ describe Crystal::Formatter do end CRYSTAL - assert_format "macro f\n yield\n {{ yield }}\nend", flags: %w[method_signature_yield] + assert_format "macro f\n yield\n {{ yield }}\nend" end - context "does not add `&` without flag `method_signature_yield`" do - assert_format <<-CRYSTAL - def foo - yield - end - CRYSTAL - - assert_format <<-CRYSTAL, <<-CRYSTAL - def foo() - yield - end - CRYSTAL - def foo - yield - end - CRYSTAL - - assert_format <<-CRYSTAL, <<-CRYSTAL - def foo( - ) - yield - end - CRYSTAL - def foo - yield - end - CRYSTAL - - # #13091 - assert_format <<-CRYSTAL - def foo # bar - yield - end - CRYSTAL - - assert_format <<-CRYSTAL - def foo(x) - yield - end - CRYSTAL - - assert_format <<-CRYSTAL, <<-CRYSTAL - def foo(x ,) - yield - end - CRYSTAL - def foo(x) - yield - end - CRYSTAL - - assert_format <<-CRYSTAL - def foo(x, - y) - yield - end - CRYSTAL - - assert_format <<-CRYSTAL, <<-CRYSTAL - def foo(x, - y,) - yield - end - CRYSTAL - def foo(x, - y,) - yield - end - CRYSTAL - - assert_format <<-CRYSTAL, <<-CRYSTAL - def foo(x - ) - yield - end - CRYSTAL - def foo(x) - yield - end - CRYSTAL - - assert_format <<-CRYSTAL, <<-CRYSTAL - def foo(x, - ) - yield - end - CRYSTAL - def foo(x) - yield - end - CRYSTAL - - assert_format <<-CRYSTAL, <<-CRYSTAL - def foo( - x) - yield - end - CRYSTAL - def foo( - x - ) - yield - end - CRYSTAL - - assert_format <<-CRYSTAL, <<-CRYSTAL - def foo( - x, y) - yield - end - CRYSTAL - def foo( - x, y - ) - yield - end - CRYSTAL - - assert_format <<-CRYSTAL, <<-CRYSTAL - def foo( - x, - y) - yield - end - CRYSTAL - def foo( - x, - y - ) - yield - end - CRYSTAL - - assert_format <<-CRYSTAL, <<-CRYSTAL - def foo( - x, - ) - yield - end - CRYSTAL - def foo( - x, - ) - yield - end - CRYSTAL - - assert_format <<-CRYSTAL - def foo(a, **b) - yield - end - CRYSTAL - end - - # Allows trailing commas, but doesn't enforce them - assert_format <<-CRYSTAL - def foo( - a, - b - ) - end - CRYSTAL - assert_format <<-CRYSTAL def foo( a, @@ -935,7 +772,7 @@ describe Crystal::Formatter do CRYSTAL context "adds trailing comma to def multi-line normal, splat, and double splat parameters" do - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL macro foo( a, b @@ -949,7 +786,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL macro foo( a, *b @@ -963,7 +800,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL fun foo( a : Int32, b : Int32 @@ -977,7 +814,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL fun foo( a : Int32, ... @@ -985,7 +822,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( a, b @@ -999,7 +836,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( a : Int32, b : Int32 @@ -1013,7 +850,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( a : Int32, b : Int32 = 1 @@ -1027,7 +864,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( a, b c @@ -1041,7 +878,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( a, @[Ann] b @@ -1055,7 +892,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( a, @[Ann] @@ -1071,7 +908,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( a, b ) @@ -1083,7 +920,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( a, b, c, d @@ -1097,7 +934,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( a, # Foo b # Bar @@ -1111,7 +948,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( a, *b @@ -1125,7 +962,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL, <<-CRYSTAL def foo( a, **b @@ -1139,7 +976,7 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL def foo( a, &block @@ -1147,44 +984,44 @@ describe Crystal::Formatter do end CRYSTAL - assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL def foo( a, ) end CRYSTAL - assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL def foo(a) end CRYSTAL - assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL def foo(a, b) end CRYSTAL - assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL def foo(a, *args) end CRYSTAL - assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL def foo(a, *args, &block) end CRYSTAL - assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL def foo(a, **kwargs) end CRYSTAL - assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL def foo(a, **kwargs, &block) end CRYSTAL - assert_format <<-CRYSTAL, flags: %w[def_trailing_comma] + assert_format <<-CRYSTAL def foo(a, &block) end CRYSTAL @@ -1709,22 +1546,23 @@ describe Crystal::Formatter do assert_format "foo = 1\n->foo.[](Int32)" assert_format "foo = 1\n->foo.[]=(Int32)" - assert_format "->{ x }" - assert_format "->{\nx\n}", "->{\n x\n}" - assert_format "->do\nx\nend", "->do\n x\nend" - assert_format "->( ){ x }", "->{ x }" - assert_format "->() do x end", "->do x end" + assert_format "->{ x }", "-> { x }" + assert_format "->{\nx\n}", "-> {\n x\n}" + assert_format "->do\nx\nend", "-> do\n x\nend" + assert_format "->( ){ x }", "-> { x }" + assert_format "->() do x end", "-> do x end" assert_format "->( x , y ) { x }", "->(x, y) { x }" assert_format "->( x : Int32 , y ) { x }", "->(x : Int32, y) { x }" - assert_format "->{}" + assert_format "->{ x }", "-> { x }" # #13232 - assert_format "->{}", "-> { }", flags: %w[proc_literal_whitespace] - assert_format "->(){}", "-> { }", flags: %w[proc_literal_whitespace] - assert_format "->{1}", "-> { 1 }", flags: %w[proc_literal_whitespace] - assert_format "->(x : Int32) {}", "->(x : Int32) { }", flags: %w[proc_literal_whitespace] - assert_format "-> : Int32 {}", "-> : Int32 { }", flags: %w[proc_literal_whitespace] - assert_format "->do\nend", "-> do\nend", flags: %w[proc_literal_whitespace] + assert_format "->{}", "-> { }" + assert_format "->(){}", "-> { }" + assert_format "->{1}", "-> { 1 }" + assert_format "->(x : Int32) {}", "->(x : Int32) { }" + assert_format "-> : Int32 {}", "-> : Int32 { }" + assert_format "->do\nend", "-> do\nend" + assert_format "-> : Int32 {}", "-> : Int32 { }" # Allows whitespace around proc literal, but doesn't enforce them assert_format "-> { }" @@ -1733,15 +1571,15 @@ describe Crystal::Formatter do assert_format "-> : Int32 { }" assert_format "-> do\nend" - assert_format "-> : Int32 {}" + assert_format "-> : Int32 { }" assert_format "-> : Int32 | String { 1 }" - assert_format "-> : Array(Int32) {}" - assert_format "-> : Int32? {}" - assert_format "-> : Int32* {}" - assert_format "-> : Int32[1] {}" - assert_format "-> : {Int32, String} {}" + assert_format "-> : Array(Int32) {}", "-> : Array(Int32) { }" + assert_format "-> : Int32? {}", "-> : Int32? { }" + assert_format "-> : Int32* {}", "-> : Int32* { }" + assert_format "-> : Int32[1] {}", "-> : Int32[1] { }" + assert_format "-> : {Int32, String} {}", "-> : {Int32, String} { }" assert_format "-> : {Int32} { String }" - assert_format "-> : {x: Int32, y: String} {}" + assert_format "-> : {x: Int32, y: String} {}", "-> : {x: Int32, y: String} { }" assert_format "->\n:\nInt32\n{\n}", "-> : Int32 {\n}" assert_format "->( x )\n:\nInt32 { }", "->(x) : Int32 { }" assert_format "->: Int32 do\nx\nend", "-> : Int32 do\n x\nend" @@ -1929,18 +1767,18 @@ describe Crystal::Formatter do assert_format "foo((1..3))" assert_format "foo ()" assert_format "foo ( )", "foo ()" - assert_format "def foo(\n\n#foo\nx,\n\n#bar\nz\n)\nend", "def foo(\n # foo\n x,\n\n # bar\n z\n)\nend" - assert_format "def foo(\nx, #foo\nz #bar\n)\nend", "def foo(\n x, # foo\n z # bar\n)\nend" + assert_format "def foo(\n\n#foo\nx,\n\n#bar\nz\n)\nend", "def foo(\n # foo\n x,\n\n # bar\n z,\n)\nend" + assert_format "def foo(\nx, #foo\nz #bar\n)\nend", "def foo(\n x, # foo\n z, # bar\n)\nend" assert_format "a = 1;;; b = 2", "a = 1; b = 2" assert_format "a = 1\n;\nb = 2", "a = 1\nb = 2" assert_format "foo do\n # bar\nend" assert_format "abstract def foo\nabstract def bar" - assert_format "if 1\n ->{ 1 }\nend" + assert_format "if 1\n ->{ 1 }\nend", "if 1\n -> { 1 }\nend" assert_format "foo.bar do\n baz\n .b\nend" assert_format "coco.lala\nfoo\n .bar" assert_format "foo.bar = \n1", "foo.bar =\n 1" assert_format "foo.bar += \n1", "foo.bar +=\n 1" - assert_format "->{}" + assert_format "->{}", "-> { }" assert_format "foo &.[a] = 1" assert_format "[\n # foo\n 1,\n\n # bar\n 2,\n]" assert_format "[c.x]\n .foo" @@ -1948,11 +1786,11 @@ describe Crystal::Formatter do assert_format "bar = foo([\n 1,\n 2,\n 3,\n])" assert_format "foo({\n 1 => 2,\n 3 => 4,\n 5 => 6,\n})" assert_format "bar = foo({\n 1 => 2,\n 3 => 4,\n 5 => 6,\n })", "bar = foo({\n 1 => 2,\n 3 => 4,\n 5 => 6,\n})" - assert_format "foo(->{\n 1 + 2\n})" - assert_format "bar = foo(->{\n 1 + 2\n})" - assert_format "foo(->do\n 1 + 2\nend)" - assert_format "bar = foo(->do\n 1 + 2\nend)" - assert_format "bar = foo(->{\n 1 + 2\n})" + assert_format "foo(->{\n 1 + 2\n})", "foo(-> {\n 1 + 2\n})" + assert_format "bar = foo(->{\n 1 + 2\n})", "bar = foo(-> {\n 1 + 2\n})" + assert_format "foo(->do\n 1 + 2\nend)", "foo(-> do\n 1 + 2\nend)" + assert_format "bar = foo(->do\n 1 + 2\nend)", "bar = foo(-> do\n 1 + 2\nend)" + assert_format "bar = foo(->{\n 1 + 2\n})", "bar = foo(-> {\n 1 + 2\n})" assert_format "case 1\nwhen 2\n 3\n # foo\nelse\n 4\n # bar\nend" assert_format "1 #=> 2", "1 # => 2" assert_format "1 #=>2", "1 # => 2" @@ -2273,11 +2111,11 @@ describe Crystal::Formatter do assert_format "def foo(a,\n *b)\nend" assert_format "def foo(a, # comment\n *b)\nend", "def foo(a, # comment\n *b)\nend" assert_format "def foo(a,\n **b)\nend" - assert_format "def foo(\n **a\n)\n 1\nend" + assert_format "def foo(\n **a\n)\n 1\nend", "def foo(\n **a,\n)\n 1\nend" assert_format "def foo(**a,)\n 1\nend", "def foo(**a)\n 1\nend" - assert_format "def foo(\n **a # comment\n)\n 1\nend" - assert_format "def foo(\n **a\n # comment\n)\n 1\nend" - assert_format "def foo(\n **a\n\n # comment\n)\n 1\nend" + assert_format "def foo(\n **a # comment\n)\n 1\nend", "def foo(\n **a, # comment\n)\n 1\nend" + assert_format "def foo(\n **a\n # comment\n)\n 1\nend", "def foo(\n **a,\n # comment\n)\n 1\nend" + assert_format "def foo(\n **a\n\n # comment\n)\n 1\nend", "def foo(\n **a,\n\n # comment\n)\n 1\nend" assert_format "def foo(**b, # comment\n &block)\nend" assert_format "def foo(a, **b, # comment\n &block)\nend" @@ -2332,7 +2170,7 @@ describe Crystal::Formatter do assert_format "alias X = ((Y, Z) ->)" - assert_format "def x(@y = ->(z) {})\nend" + assert_format "def x(@y = ->(z) {})\nend", "def x(@y = ->(z) { })\nend" assert_format "class X; annotation FooAnnotation ; end ; end", "class X\n annotation FooAnnotation; end\nend" assert_format "class X\n annotation FooAnnotation \n end \n end", "class X\n annotation FooAnnotation\n end\nend" @@ -2742,13 +2580,19 @@ describe Crystal::Formatter do assert_format "a &.a.!" assert_format "a &.!.!" - assert_format <<-CRYSTAL + assert_format <<-CRYSTAL, <<-CRYSTAL ->{ # first comment puts "hi" # second comment } CRYSTAL + -> { + # first comment + puts "hi" + # second comment + } + CRYSTAL # #9014 assert_format <<-CRYSTAL diff --git a/spec/llvm-ir/pass-closure-to-c-debug-loc.cr b/spec/llvm-ir/pass-closure-to-c-debug-loc.cr index a6031798b607..6891ae6ae92f 100644 --- a/spec/llvm-ir/pass-closure-to-c-debug-loc.cr +++ b/spec/llvm-ir/pass-closure-to-c-debug-loc.cr @@ -8,7 +8,7 @@ def raise(msg) end x = 1 -f = ->{ x } +f = -> { x } Foo.foo(f) # CHECK: define internal i8* @"~check_proc_is_not_closure"(%"->" %0) diff --git a/spec/llvm-ir/proc-call-debug-loc.cr b/spec/llvm-ir/proc-call-debug-loc.cr index e83c814f723b..61f02249a9a9 100644 --- a/spec/llvm-ir/proc-call-debug-loc.cr +++ b/spec/llvm-ir/proc-call-debug-loc.cr @@ -1,4 +1,4 @@ -x = ->{} +x = -> { } x.call # CHECK: extractvalue %"->" %{{[0-9]+}}, 0 # CHECK-SAME: !dbg [[LOC:![0-9]+]] diff --git a/spec/std/benchmark_spec.cr b/spec/std/benchmark_spec.cr index 4a46798b2436..63124881c262 100644 --- a/spec/std/benchmark_spec.cr +++ b/spec/std/benchmark_spec.cr @@ -31,7 +31,7 @@ describe Benchmark::IPS::Job do end private def create_entry - Benchmark::IPS::Entry.new("label", ->{ 1 + 1 }) + Benchmark::IPS::Entry.new("label", -> { 1 + 1 }) end private def h_mean(mean) diff --git a/spec/std/channel_spec.cr b/spec/std/channel_spec.cr index 69161dd96e01..a24790dd8dea 100644 --- a/spec/std/channel_spec.cr +++ b/spec/std/channel_spec.cr @@ -82,7 +82,7 @@ describe Channel do context "receive raise-on-close single-channel" do it "types" do ch = Channel(String).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.select(ch.receive_select_action) typeof(i).should eq(Int32) typeof(m).should eq(String) @@ -92,7 +92,7 @@ describe Channel do it "types nilable channel" do # Yes, although it is discouraged ch = Channel(Nil).new - spawn_and_wait(->{ ch.send nil }) do + spawn_and_wait(-> { ch.send nil }) do i, m = Channel.select(ch.receive_select_action) typeof(i).should eq(Int32) typeof(m).should eq(Nil) @@ -101,7 +101,7 @@ describe Channel do it "raises if channel was closed" do ch = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do expect_raises Channel::ClosedError do Channel.select(ch.receive_select_action) end @@ -110,7 +110,7 @@ describe Channel do it "raises if channel is closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do + spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do expect_raises Channel::ClosedError do Channel.select(ch.receive_select_action) end @@ -120,7 +120,7 @@ describe Channel do it "awakes all waiting selects" do ch = Channel(String).new - p = ->{ + p = -> { begin Channel.select(ch.receive_select_action) 0 @@ -129,7 +129,7 @@ describe Channel do end } - spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do + spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do r = parallel p.call, p.call, p.call, p.call r.should eq({1, 1, 1, 1}) end @@ -140,7 +140,7 @@ describe Channel do it "types" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.select(ch.receive_select_action, ch2.receive_select_action) typeof(i).should eq(Int32) typeof(m).should eq(String | Bool) @@ -151,7 +151,7 @@ describe Channel do context "receive nil-on-close single-channel" do it "types" do ch = Channel(String).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.select(ch.receive_select_action?) typeof(i).should eq(Int32) typeof(m).should eq(String | Nil) @@ -161,7 +161,7 @@ describe Channel do it "types nilable channel" do # Yes, although it is discouraged ch = Channel(Nil).new - spawn_and_wait(->{ ch.send nil }) do + spawn_and_wait(-> { ch.send nil }) do i, m = Channel.select(ch.receive_select_action?) typeof(i).should eq(Int32) typeof(m).should eq(Nil) @@ -170,7 +170,7 @@ describe Channel do it "returns nil if channel was closed" do ch = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do i, m = Channel.select(ch.receive_select_action?) m.should be_nil end @@ -178,7 +178,7 @@ describe Channel do it "returns nil channel is closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do + spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do i, m = Channel.select(ch.receive_select_action?) m.should be_nil end @@ -187,11 +187,11 @@ describe Channel do it "awakes all waiting selects" do ch = Channel(String).new - p = ->{ + p = -> { Channel.select(ch.receive_select_action?) } - spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do + spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do r = parallel p.call, p.call, p.call, p.call r.should eq({ {0, nil}, {0, nil}, {0, nil}, {0, nil} }) end @@ -202,7 +202,7 @@ describe Channel do it "types" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.select(ch.receive_select_action?, ch2.receive_select_action?) typeof(i).should eq(Int32) typeof(m).should eq(String | Bool | Nil) @@ -212,7 +212,7 @@ describe Channel do it "returns index of closed channel" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_wait(->{ ch2.close }) do + spawn_and_wait(-> { ch2.close }) do i, m = Channel.select(ch.receive_select_action?, ch2.receive_select_action?) i.should eq(1) m.should eq(nil) @@ -224,7 +224,7 @@ describe Channel do it "raises if receive channel was closed and receive? channel was not ready" do ch = Channel(String).new ch2 = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do expect_raises Channel::ClosedError do Channel.select(ch.receive_select_action, ch2.receive_select_action?) end @@ -234,7 +234,7 @@ describe Channel do it "returns nil if receive channel was not ready and receive? channel was closed" do ch = Channel(String).new ch2 = Channel(String).new - spawn_and_wait(->{ ch2.close }) do + spawn_and_wait(-> { ch2.close }) do i, m = Channel.select(ch.receive_select_action, ch2.receive_select_action?) i.should eq(1) m.should eq(nil) @@ -245,7 +245,7 @@ describe Channel do context "send raise-on-close single-channel" do it "types" do ch = Channel(String).new - spawn_and_wait(->{ ch.receive }) do + spawn_and_wait(-> { ch.receive }) do i, m = Channel.select(ch.send_select_action("foo")) typeof(i).should eq(Int32) typeof(m).should eq(Nil) @@ -255,7 +255,7 @@ describe Channel do it "types nilable channel" do # Yes, although it is discouraged ch = Channel(Nil).new - spawn_and_wait(->{ ch.receive }) do + spawn_and_wait(-> { ch.receive }) do i, m = Channel.select(ch.send_select_action(nil)) typeof(i).should eq(Int32) typeof(m).should eq(Nil) @@ -264,7 +264,7 @@ describe Channel do it "raises if channel was closed" do ch = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do expect_raises Channel::ClosedError do Channel.select(ch.send_select_action("foo")) end @@ -273,7 +273,7 @@ describe Channel do it "raises if channel is closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do + spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do expect_raises Channel::ClosedError do Channel.select(ch.send_select_action("foo")) end @@ -283,7 +283,7 @@ describe Channel do it "awakes all waiting selects" do ch = Channel(String).new - p = ->{ + p = -> { begin Channel.select(ch.send_select_action("foo")) 0 @@ -292,7 +292,7 @@ describe Channel do end } - spawn_and_wait(->{ sleep 0.2.seconds; ch.close }) do + spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do r = parallel p.call, p.call, p.call, p.call r.should eq({1, 1, 1, 1}) end @@ -303,7 +303,7 @@ describe Channel do it "types" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_wait(->{ ch.receive }) do + spawn_and_wait(-> { ch.receive }) do i, m = Channel.select(ch.send_select_action("foo"), ch2.send_select_action(true)) typeof(i).should eq(Int32) typeof(m).should eq(Nil) @@ -314,7 +314,7 @@ describe Channel do context "timeout" do it "types" do ch = Channel(String).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.select(ch.receive_select_action, timeout_select_action(0.1.seconds)) typeof(i).should eq(Int32) typeof(m).should eq(String?) @@ -323,7 +323,7 @@ describe Channel do it "triggers timeout" do ch = Channel(String).new - spawn_and_wait(->{}) do + spawn_and_wait(-> { }) do i, m = Channel.select(ch.receive_select_action, timeout_select_action(0.1.seconds)) i.should eq(1) @@ -333,7 +333,7 @@ describe Channel do it "triggers timeout (reverse order)" do ch = Channel(String).new - spawn_and_wait(->{}) do + spawn_and_wait(-> { }) do i, m = Channel.select(timeout_select_action(0.1.seconds), ch.receive_select_action) i.should eq(0) @@ -343,7 +343,7 @@ describe Channel do it "triggers timeout (same fiber multiple times)" do ch = Channel(String).new - spawn_and_wait(->{}) do + spawn_and_wait(-> { }) do 3.times do i, m = Channel.select(ch.receive_select_action, timeout_select_action(0.1.seconds)) @@ -355,7 +355,7 @@ describe Channel do it "allows receiving while waiting" do ch = Channel(String).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.select(ch.receive_select_action, timeout_select_action(1.seconds)) i.should eq(0) m.should eq("foo") @@ -364,7 +364,7 @@ describe Channel do it "allows receiving while waiting (reverse order)" do ch = Channel(String).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.select(timeout_select_action(1.seconds), ch.receive_select_action) i.should eq(1) m.should eq("foo") @@ -373,7 +373,7 @@ describe Channel do it "allows receiving while waiting (same fiber multiple times)" do ch = Channel(String).new - spawn_and_wait(->{ 3.times { ch.send "foo" } }) do + spawn_and_wait(-> { 3.times { ch.send "foo" } }) do 3.times do i, m = Channel.select(ch.receive_select_action, timeout_select_action(1.seconds)) i.should eq(0) @@ -384,7 +384,7 @@ describe Channel do it "negative amounts should not trigger timeout" do ch = Channel(String).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.select(ch.receive_select_action, timeout_select_action(-1.seconds)) i.should eq(0) @@ -394,7 +394,7 @@ describe Channel do it "send raise-on-close raises if channel was closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do expect_raises Channel::ClosedError do Channel.select(ch.send_select_action("foo"), timeout_select_action(0.1.seconds)) end @@ -403,7 +403,7 @@ describe Channel do it "receive raise-on-close raises if channel was closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do expect_raises Channel::ClosedError do Channel.select(ch.receive_select_action, timeout_select_action(0.1.seconds)) end @@ -412,7 +412,7 @@ describe Channel do it "receive nil-on-close returns index of closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do i, m = Channel.select(ch.receive_select_action?, timeout_select_action(0.1.seconds)) i.should eq(0) @@ -426,7 +426,7 @@ describe Channel do context "receive raise-on-close single-channel" do it "types" do ch = Channel(String).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.non_blocking_select(ch.receive_select_action) typeof(i).should eq(Int32) typeof(m).should eq(String | Channel::NotReady) @@ -438,7 +438,7 @@ describe Channel do it "types" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.non_blocking_select(ch.receive_select_action, ch2.receive_select_action) typeof(i).should eq(Int32) typeof(m).should eq(String | Bool | Channel::NotReady) @@ -449,7 +449,7 @@ describe Channel do context "receive nil-on-close single-channel" do it "types" do ch = Channel(String).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.non_blocking_select(ch.receive_select_action?) typeof(i).should eq(Int32) typeof(m).should eq(String | Nil | Channel::NotReady) @@ -458,7 +458,7 @@ describe Channel do it "returns nil if channel was closed" do ch = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do i, m = Channel.non_blocking_select(ch.receive_select_action?) m.should be_nil end @@ -470,7 +470,7 @@ describe Channel do ch = Channel(String).new ch2 = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do expect_raises Channel::ClosedError do Channel.non_blocking_select(ch.receive_select_action, ch2.receive_select_action?) end @@ -480,7 +480,7 @@ describe Channel do it "returns nil if receive channel was not ready and receive? channel was closed" do ch = Channel(String).new ch2 = Channel(String).new - spawn_and_wait(->{ ch2.close }) do + spawn_and_wait(-> { ch2.close }) do i, m = Channel.non_blocking_select(ch.receive_select_action, ch2.receive_select_action?) i.should eq(1) m.should eq(nil) @@ -491,7 +491,7 @@ describe Channel do context "send raise-on-close single-channel" do it "types" do ch = Channel(String).new - spawn_and_wait(->{ ch.receive }) do + spawn_and_wait(-> { ch.receive }) do i, m = Channel.non_blocking_select(ch.send_select_action("foo")) typeof(i).should eq(Int32) typeof(m).should eq(Nil | Channel::NotReady) @@ -503,7 +503,7 @@ describe Channel do it "types" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_wait(->{ ch.receive }) do + spawn_and_wait(-> { ch.receive }) do i, m = Channel.non_blocking_select(ch.send_select_action("foo"), ch2.send_select_action(true)) typeof(i).should eq(Int32) typeof(m).should eq(Nil | Channel::NotReady) @@ -514,7 +514,7 @@ describe Channel do context "timeout" do it "types" do ch = Channel(String).new - spawn_and_wait(->{ ch.send "foo" }) do + spawn_and_wait(-> { ch.send "foo" }) do i, m = Channel.non_blocking_select(ch.receive_select_action, timeout_select_action(0.1.seconds)) typeof(i).should eq(Int32) typeof(m).should eq(String | Nil | Channel::NotReady) @@ -523,7 +523,7 @@ describe Channel do it "should not trigger timeout" do ch = Channel(String).new - spawn_and_wait(->{}) do + spawn_and_wait(-> { }) do i, m = Channel.non_blocking_select(ch.receive_select_action, timeout_select_action(0.1.seconds)) i.should eq(2) @@ -533,7 +533,7 @@ describe Channel do it "negative amounts should not trigger timeout" do ch = Channel(String).new - spawn_and_wait(->{}) do + spawn_and_wait(-> { }) do i, m = Channel.non_blocking_select(ch.receive_select_action, timeout_select_action(-1.seconds)) i.should eq(2) @@ -543,7 +543,7 @@ describe Channel do it "send raise-on-close raises if channel was closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do expect_raises Channel::ClosedError do Channel.non_blocking_select(ch.send_select_action("foo"), timeout_select_action(0.1.seconds)) end @@ -552,7 +552,7 @@ describe Channel do it "receive raise-on-close raises if channel was closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do expect_raises Channel::ClosedError do Channel.non_blocking_select(ch.receive_select_action, timeout_select_action(0.1.seconds)) end @@ -561,7 +561,7 @@ describe Channel do it "receive nil-on-close returns index of closed while waiting" do ch = Channel(String).new - spawn_and_wait(->{ ch.close }) do + spawn_and_wait(-> { ch.close }) do i, m = Channel.non_blocking_select(ch.receive_select_action?, timeout_select_action(0.1.seconds)) i.should eq(0) @@ -573,7 +573,7 @@ describe Channel do it "returns correct index for array argument" do ch = [Channel(String).new, Channel(String).new, Channel(String).new] channels = [ch[0], ch[2], ch[1]] # shuffle around to get non-sequential lock_object_ids - spawn_and_wait(->{ channels[0].send "foo" }) do + spawn_and_wait(-> { channels[0].send "foo" }) do i, m = Channel.non_blocking_select(channels.map(&.receive_select_action)) i.should eq(0) diff --git a/spec/std/concurrent/select_spec.cr b/spec/std/concurrent/select_spec.cr index 5285e3dd070c..4f84734a20ad 100644 --- a/spec/std/concurrent/select_spec.cr +++ b/spec/std/concurrent/select_spec.cr @@ -243,7 +243,7 @@ describe "select" do it "types and exec when" do ch = Channel(String).new - spawn_and_check(->{ ch.send "foo" }) do |w| + spawn_and_check(-> { ch.send "foo" }) do |w| select when m = ch.receive w.check @@ -259,7 +259,7 @@ describe "select" do it "raises if channel was closed" do ch = Channel(String).new - spawn_and_check(->{ ch.close }) do |w| + spawn_and_check(-> { ch.close }) do |w| begin select when m = ch.receive @@ -276,7 +276,7 @@ describe "select" do it "types and exec when if message was ready" do ch = Channel(String).new - spawn_and_check(->{ ch.send "foo" }) do |w| + spawn_and_check(-> { ch.send "foo" }) do |w| select when m = ch.receive w.check @@ -290,7 +290,7 @@ describe "select" do it "exec else if no message was ready" do ch = Channel(String).new - spawn_and_check(->{ nil }) do |w| + spawn_and_check(-> { nil }) do |w| select when m = ch.receive else @@ -305,7 +305,7 @@ describe "select" do it "raises if channel was closed" do ch = Channel(String).new - spawn_and_check(->{ ch.close }) do |w| + spawn_and_check(-> { ch.close }) do |w| begin select when m = ch.receive @@ -324,7 +324,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch.send "foo" }) do |w| + spawn_and_check(-> { ch.send "foo" }) do |w| select when m = ch.receive w.check @@ -339,7 +339,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.send true }) do |w| + spawn_and_check(-> { ch2.send true }) do |w| select when m = ch.receive when m = ch2.receive @@ -357,7 +357,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch.close }) do |w| + spawn_and_check(-> { ch.close }) do |w| begin select when m = ch.receive @@ -373,7 +373,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.close }) do |w| + spawn_and_check(-> { ch2.close }) do |w| begin select when m = ch.receive @@ -392,7 +392,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch.send "foo" }) do |w| + spawn_and_check(-> { ch.send "foo" }) do |w| select when m = ch.receive w.check @@ -408,7 +408,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.send true }) do |w| + spawn_and_check(-> { ch2.send true }) do |w| select when m = ch.receive when m = ch2.receive @@ -424,7 +424,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ nil }) do |w| + spawn_and_check(-> { nil }) do |w| select when m = ch.receive when m = ch2.receive @@ -441,7 +441,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch.close }) do |w| + spawn_and_check(-> { ch.close }) do |w| begin select when m = ch.receive @@ -458,7 +458,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.close }) do |w| + spawn_and_check(-> { ch2.close }) do |w| begin select when m = ch.receive @@ -477,7 +477,7 @@ describe "select" do it "types and exec when" do ch = Channel(String).new - spawn_and_check(->{ ch.send "foo" }) do |w| + spawn_and_check(-> { ch.send "foo" }) do |w| select when m = ch.receive? w.check @@ -490,7 +490,7 @@ describe "select" do it "types and exec when with nil if channel was closed" do ch = Channel(String).new - spawn_and_check(->{ ch.close }) do |w| + spawn_and_check(-> { ch.close }) do |w| select when m = ch.receive? w.check @@ -506,7 +506,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch.send "foo" }) do |w| + spawn_and_check(-> { ch.send "foo" }) do |w| select when m = ch.receive? w.check @@ -521,7 +521,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.send true }) do |w| + spawn_and_check(-> { ch2.send true }) do |w| select when m = ch.receive? when m = ch2.receive? @@ -536,7 +536,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch.close }) do |w| + spawn_and_check(-> { ch.close }) do |w| select when m = ch.receive? w.check @@ -551,7 +551,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.close }) do |w| + spawn_and_check(-> { ch2.close }) do |w| select when m = ch.receive? when m = ch2.receive? @@ -566,7 +566,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch.close }) do |w| + spawn_and_check(-> { ch.close }) do |w| select when m = ch.receive? w.check @@ -581,7 +581,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.close }) do |w| + spawn_and_check(-> { ch2.close }) do |w| select when m = ch.receive? when m = ch2.receive? @@ -597,7 +597,7 @@ describe "select" do it "types and exec when" do ch = Channel(String).new - spawn_and_check(->{ ch.send "foo" }) do |w| + spawn_and_check(-> { ch.send "foo" }) do |w| select when m = ch.receive? w.check @@ -611,7 +611,7 @@ describe "select" do it "exec else if no message was ready" do ch = Channel(String).new - spawn_and_check(->{ nil }) do |w| + spawn_and_check(-> { nil }) do |w| select when m = ch.receive? else @@ -623,7 +623,7 @@ describe "select" do it "types and exec when with nil if channel was closed" do ch = Channel(String).new - spawn_and_check(->{ ch.close }) do |w| + spawn_and_check(-> { ch.close }) do |w| select when m = ch.receive? w.check @@ -640,7 +640,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch.send "foo" }) do |w| + spawn_and_check(-> { ch.send "foo" }) do |w| select when m = ch.receive? w.check @@ -656,7 +656,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.send true }) do |w| + spawn_and_check(-> { ch2.send true }) do |w| select when m = ch.receive? when m = ch2.receive? @@ -672,7 +672,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch.close }) do |w| + spawn_and_check(-> { ch.close }) do |w| select when m = ch.receive? w.check @@ -688,7 +688,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.close }) do |w| + spawn_and_check(-> { ch2.close }) do |w| select when m = ch.receive? when m = ch2.receive? @@ -704,7 +704,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch.close }) do |w| + spawn_and_check(-> { ch.close }) do |w| select when m = ch.receive? w.check @@ -720,7 +720,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ ch2.close }) do |w| + spawn_and_check(-> { ch2.close }) do |w| select when m = ch.receive? when m = ch2.receive? @@ -736,7 +736,7 @@ describe "select" do ch = Channel(String).new ch2 = Channel(Bool).new - spawn_and_check(->{ nil }) do |w| + spawn_and_check(-> { nil }) do |w| select when m = ch.receive? when m = ch2.receive? diff --git a/spec/std/http/server/handlers/log_handler_spec.cr b/spec/std/http/server/handlers/log_handler_spec.cr index 1f94649f09a8..3f33120e03d6 100644 --- a/spec/std/http/server/handlers/log_handler_spec.cr +++ b/spec/std/http/server/handlers/log_handler_spec.cr @@ -28,7 +28,7 @@ describe HTTP::LogHandler do backend = Log::MemoryBackend.new log = Log.new("custom", backend, :info) handler = HTTP::LogHandler.new(log) - handler.next = ->(ctx : HTTP::Server::Context) {} + handler.next = ->(ctx : HTTP::Server::Context) { } handler.call(context) logs = Log::EntriesChecker.new(backend.entries) diff --git a/spec/std/openssl/ssl/socket_spec.cr b/spec/std/openssl/ssl/socket_spec.cr index 47374ce28cca..ed1150407122 100644 --- a/spec/std/openssl/ssl/socket_spec.cr +++ b/spec/std/openssl/ssl/socket_spec.cr @@ -75,7 +75,7 @@ describe OpenSSL::SSL::Socket do server_tests: ->(client : Server) { client.cipher.should_not be_empty }, - client_tests: ->(client : Client) {} + client_tests: ->(client : Client) { } ) end @@ -84,7 +84,7 @@ describe OpenSSL::SSL::Socket do server_tests: ->(client : Server) { client.tls_version.should contain "TLS" }, - client_tests: ->(client : Client) {} + client_tests: ->(client : Client) { } ) end diff --git a/spec/std/proc_spec.cr b/spec/std/proc_spec.cr index 87bea44c0422..f378d768fbef 100644 --- a/spec/std/proc_spec.cr +++ b/spec/std/proc_spec.cr @@ -28,19 +28,19 @@ describe "Proc" do end it "gets pointer" do - f = ->{ 1 } + f = -> { 1 } f.pointer.address.should be > 0 end it "gets closure data for non-closure" do - f = ->{ 1 } + f = -> { 1 } f.closure_data.address.should eq(0) f.closure?.should be_false end it "gets closure data for closure" do a = 1 - f = ->{ a } + f = -> { a } f.closure_data.address.should be > 0 f.closure?.should be_true end @@ -53,19 +53,19 @@ describe "Proc" do end it "does ==" do - func = ->{ 1 } + func = -> { 1 } func.should eq(func) - func2 = ->{ 1 } + func2 = -> { 1 } func2.should_not eq(func) end it "clones" do - func = ->{ 1 } + func = -> { 1 } func.clone.should eq(func) end it "#arity" do - f = ->(x : Int32, y : Int32) {} + f = ->(x : Int32, y : Int32) { } f.arity.should eq(2) end @@ -89,5 +89,5 @@ describe "Proc" do f2.call('r').should eq(2) end - typeof(->{ 1 }.hash) + typeof(-> { 1 }.hash) end diff --git a/src/compiler/crystal/command/format.cr b/src/compiler/crystal/command/format.cr index ed63a26796f9..9d0431b3e3bb 100644 --- a/src/compiler/crystal/command/format.cr +++ b/src/compiler/crystal/command/format.cr @@ -78,7 +78,7 @@ class Crystal::Command @show_backtrace : Bool = false, @color : Bool = true, # stdio is injectable for testing - @stdin : IO = STDIN, @stdout : IO = STDOUT, @stderr : IO = STDERR + @stdin : IO = STDIN, @stdout : IO = STDOUT, @stderr : IO = STDERR, ) @format_stdin = files.size == 1 && files[0] == "-" diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 0d7ba0ff12f9..f620fe2fb312 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -669,7 +669,7 @@ module Crystal end end - private def fork_workers(n_threads) + private def fork_workers(n_threads, &) workers = [] of {Int32, IO::FileDescriptor, IO::FileDescriptor} n_threads.times do diff --git a/src/compiler/crystal/ffi/lib_ffi.cr b/src/compiler/crystal/ffi/lib_ffi.cr index 97163c989ee5..2d08cf4e18dd 100644 --- a/src/compiler/crystal/ffi/lib_ffi.cr +++ b/src/compiler/crystal/ffi/lib_ffi.cr @@ -147,7 +147,7 @@ module Crystal abi : ABI, nargs : LibC::UInt, rtype : Type*, - atypes : Type** + atypes : Type**, ) : Status fun prep_cif_var = ffi_prep_cif_var( @@ -156,7 +156,7 @@ module Crystal nfixedargs : LibC::UInt, varntotalargs : LibC::UInt, rtype : Type*, - atypes : Type** + atypes : Type**, ) : Status @[Raises] @@ -164,7 +164,7 @@ module Crystal cif : Cif*, fn : Void*, rvalue : Void*, - avalue : Void** + avalue : Void**, ) : Void fun closure_alloc = ffi_closure_alloc(size : LibC::SizeT, code : Void**) : Closure* @@ -174,7 +174,7 @@ module Crystal cif : Cif*, fun : ClosureFun, user_data : Void*, - code_loc : Void* + code_loc : Void*, ) : Status end end diff --git a/src/compiler/crystal/interpreter/closure_context.cr b/src/compiler/crystal/interpreter/closure_context.cr index 5df87d884363..4e633ae104b4 100644 --- a/src/compiler/crystal/interpreter/closure_context.cr +++ b/src/compiler/crystal/interpreter/closure_context.cr @@ -20,7 +20,7 @@ class Crystal::Repl @vars : Hash(String, {Int32, Type}), @self_type : Type?, @parent : ClosureContext?, - @bytesize : Int32 + @bytesize : Int32, ) end end diff --git a/src/compiler/crystal/interpreter/compiled_def.cr b/src/compiler/crystal/interpreter/compiled_def.cr index 8bfc3252fcb9..f9d3d48088bd 100644 --- a/src/compiler/crystal/interpreter/compiled_def.cr +++ b/src/compiler/crystal/interpreter/compiled_def.cr @@ -26,7 +26,7 @@ class Crystal::Repl @owner : Type, @args_bytesize : Int32, @instructions : CompiledInstructions = CompiledInstructions.new, - @local_vars = LocalVars.new(context) + @local_vars = LocalVars.new(context), ) end end diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 50024d8b65e3..ea278876c44f 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -103,7 +103,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor @instructions : CompiledInstructions = CompiledInstructions.new, scope : Type? = nil, @def = nil, - @top_level = true + @top_level = true, ) @scope = scope || @context.program @@ -138,7 +138,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor context : Context, compiled_def : CompiledDef, top_level : Bool, - scope : Type = compiled_def.owner + scope : Type = compiled_def.owner, ) new( context: context, diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index f73cba958851..e26a6751c176 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -113,7 +113,7 @@ class Crystal::Repl::Interpreter def initialize( @context : Context, # TODO: what if the stack is exhausted? - @stack : UInt8* = Pointer(Void).malloc(8 * 1024 * 1024).as(UInt8*) + @stack : UInt8* = Pointer(Void).malloc(8 * 1024 * 1024).as(UInt8*), ) @local_vars = LocalVars.new(@context) @argv = [] of String diff --git a/src/compiler/crystal/interpreter/lib_function.cr b/src/compiler/crystal/interpreter/lib_function.cr index 54ac2ac297cf..e1898869227e 100644 --- a/src/compiler/crystal/interpreter/lib_function.cr +++ b/src/compiler/crystal/interpreter/lib_function.cr @@ -19,7 +19,7 @@ class Crystal::Repl::LibFunction @def : External, @symbol : Void*, @call_interface : FFI::CallInterface, - @args_bytesizes : Array(Int32) + @args_bytesizes : Array(Int32), ) end end diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index c262a2d9770a..bab4e22b9fba 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -506,7 +506,7 @@ module Crystal recorded_requires << RecordedRequire.new(filename, relative_to) end - def run_requires(node : Require, filenames) : Nil + def run_requires(node : Require, filenames, &) : Nil dependency_printer = compiler.try(&.dependency_printer) filenames.each do |filename| diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index ea5626c37f94..905d5bac8cb1 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -2672,7 +2672,7 @@ module Crystal end end - private def visit_size_or_align_of(node) + private def visit_size_or_align_of(node, &) @in_type_args += 1 node.exp.accept self @in_type_args -= 1 @@ -2698,7 +2698,7 @@ module Crystal false end - private def visit_instance_size_or_align_of(node) + private def visit_instance_size_or_align_of(node, &) @in_type_args += 1 node.exp.accept self @in_type_args -= 1 diff --git a/src/compiler/crystal/semantic/type_merge.cr b/src/compiler/crystal/semantic/type_merge.cr index d68cdeb38a99..874949dd516d 100644 --- a/src/compiler/crystal/semantic/type_merge.cr +++ b/src/compiler/crystal/semantic/type_merge.cr @@ -207,7 +207,7 @@ module Crystal def self.least_common_ancestor( type1 : MetaclassType | GenericClassInstanceMetaclassType, - type2 : MetaclassType | GenericClassInstanceMetaclassType + type2 : MetaclassType | GenericClassInstanceMetaclassType, ) return nil unless unifiable_metaclass?(type1) && unifiable_metaclass?(type2) @@ -225,7 +225,7 @@ module Crystal def self.least_common_ancestor( type1 : NonGenericModuleType | GenericModuleInstanceType | GenericClassType, - type2 : NonGenericModuleType | GenericModuleInstanceType | GenericClassType + type2 : NonGenericModuleType | GenericModuleInstanceType | GenericClassType, ) return type2 if type1.implements?(type2) return type1 if type2.implements?(type1) diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 796afe0730de..7ea32627078e 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1476,7 +1476,7 @@ module Crystal # this formats `def foo # ...` to `def foo(&) # ...` for yielding # methods before consuming the comment line if node.block_arity && node.args.empty? && !node.block_arg && !node.double_splat - write "(&)" if flag?("method_signature_yield") + write "(&)" end skip_space consume_newline: false @@ -1523,7 +1523,7 @@ module Crystal end def format_def_args(node : Def | Macro) - yields = node.is_a?(Def) && !node.block_arity.nil? && flag?("method_signature_yield") + yields = node.is_a?(Def) && !node.block_arity.nil? format_def_args node.args, node.block_arg, node.splat_index, false, node.double_splat, yields end @@ -1651,7 +1651,7 @@ module Crystal yield # Write "," before skipping spaces to prevent inserting comment between argument and comma. - write "," if has_more || (wrote_newline && @token.type.op_comma?) || (write_trailing_comma && flag?("def_trailing_comma")) + write "," if has_more || (wrote_newline && @token.type.op_comma?) || write_trailing_comma just_wrote_newline = skip_space if @token.type.newline? @@ -1681,7 +1681,7 @@ module Crystal elsif @token.type.op_rparen? && has_more && !just_wrote_newline # if we found a `)` and there are still more parameters to write, it # must have been a missing `&` for a def that yields - write " " if flag?("method_signature_yield") + write " " end just_wrote_newline @@ -4273,7 +4273,7 @@ module Crystal skip_space_or_newline end - write " " if a_def.args.present? || return_type || flag?("proc_literal_whitespace") || whitespace_after_op_minus_gt + write " " is_do = false if @token.keyword?(:do) @@ -4281,7 +4281,7 @@ module Crystal is_do = true else write_token :OP_LCURLY - write " " if a_def.body.is_a?(Nop) && (flag?("proc_literal_whitespace") || @token.type.space?) + write " " if a_def.body.is_a?(Nop) end skip_space diff --git a/src/compiler/crystal/tools/init.cr b/src/compiler/crystal/tools/init.cr index 96b004eec2fd..01b2e137c578 100644 --- a/src/compiler/crystal/tools/init.cr +++ b/src/compiler/crystal/tools/init.cr @@ -157,7 +157,7 @@ module Crystal @github_name = "none", @silent = false, @force = false, - @skip_existing = false + @skip_existing = false, ) end diff --git a/src/compiler/crystal/util.cr b/src/compiler/crystal/util.cr index c33bfa5d0d42..d0de6f226f36 100644 --- a/src/compiler/crystal/util.cr +++ b/src/compiler/crystal/util.cr @@ -41,7 +41,7 @@ module Crystal source : String | Array(String), highlight_line_number = nil, color = false, - line_number_start = 1 + line_number_start = 1, ) source = source.lines if source.is_a? String line_number_padding = (source.size + line_number_start).to_s.chars.size diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index 431708c5cc11..0d6f5077633a 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -91,7 +91,7 @@ class Thread # Used once to initialize the thread object representing the main thread of # the process (that already exists). def initialize - @func = ->(t : Thread) {} + @func = ->(t : Thread) { } @system_handle = Crystal::System::Thread.current_handle @current_fiber = @main_fiber = Fiber.new(stack_address, self) diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index d3655fdb5861..ade1862a780c 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -121,7 +121,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop return unless thread # alert the thread to interrupt GetQueuedCompletionStatusEx - LibC.QueueUserAPC(->(ptr : LibC::ULONG_PTR) {}, thread, LibC::ULONG_PTR.new(0)) + LibC.QueueUserAPC(->(ptr : LibC::ULONG_PTR) { }, thread, LibC::ULONG_PTR.new(0)) end def enqueue(event : Crystal::IOCP::Event) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index cdd23e3ed54d..1f277505302a 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -194,7 +194,7 @@ module Crystal::System::FileDescriptor file_descriptor_close end - def file_descriptor_close + def file_descriptor_close(&) if LibC.CloseHandle(windows_handle) == 0 yield end diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr index 5ed235e24574..bfb82581204b 100644 --- a/src/crystal/system/win32/socket.cr +++ b/src/crystal/system/win32/socket.cr @@ -369,7 +369,7 @@ module Crystal::System::Socket socket_close end - private def socket_close + private def socket_close(&) handle = @volatile_fd.swap(LibC::INVALID_SOCKET) ret = LibC.closesocket(handle) diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 0ce6a1366b6d..41c0f43f2a8c 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -198,7 +198,7 @@ module GC {% end %} LibGC.init - LibGC.set_start_callback ->do + LibGC.set_start_callback -> do GC.lock_write end @@ -449,7 +449,7 @@ module GC @@curr_push_other_roots = block @@prev_push_other_roots = LibGC.get_push_other_roots - LibGC.set_push_other_roots ->do + LibGC.set_push_other_roots -> do @@curr_push_other_roots.try(&.call) @@prev_push_other_roots.try(&.call) end diff --git a/src/kernel.cr b/src/kernel.cr index 16c4a770309a..ac241161c16d 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -585,7 +585,7 @@ end def self.after_fork_child_callbacks @@after_fork_child_callbacks ||= [ # reinit event loop first: - ->{ Crystal::EventLoop.current.after_fork }, + -> { Crystal::EventLoop.current.after_fork }, # reinit signal handling: ->Crystal::System::Signal.after_fork, diff --git a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr index fe2fbe381d03..7f7160a6448b 100644 --- a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr @@ -19,7 +19,7 @@ lib LibC lpBuffer : Void*, nNumberOfCharsToRead : DWORD, lpNumberOfCharsRead : DWORD*, - pInputControl : Void* + pInputControl : Void*, ) : BOOL CTRL_C_EVENT = 0 diff --git a/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr b/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr index 2c62d07d3ad8..abd9e0b36104 100644 --- a/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr +++ b/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr @@ -132,6 +132,6 @@ lib LibC fun StackWalk64( machineType : DWORD, hProcess : HANDLE, hThread : HANDLE, stackFrame : STACKFRAME64*, contextRecord : Void*, readMemoryRoutine : PREAD_PROCESS_MEMORY_ROUTINE64, functionTableAccessRoutine : PFUNCTION_TABLE_ACCESS_ROUTINE64, - getModuleBaseRoutine : PGET_MODULE_BASE_ROUTINE64, translateAddress : PTRANSLATE_ADDRESS_ROUTINE64 + getModuleBaseRoutine : PGET_MODULE_BASE_ROUTINE64, translateAddress : PTRANSLATE_ADDRESS_ROUTINE64, ) : BOOL end diff --git a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr index c17c0fb48a9a..94714b557cbe 100644 --- a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr @@ -107,14 +107,14 @@ lib LibC dwReserved : DWORD, nNumberOfBytesToLockLow : DWORD, nNumberOfBytesToLockHigh : DWORD, - lpOverlapped : OVERLAPPED* + lpOverlapped : OVERLAPPED*, ) : BOOL fun UnlockFileEx( hFile : HANDLE, dwReserved : DWORD, nNumberOfBytesToUnlockLow : DWORD, nNumberOfBytesToUnlockHigh : DWORD, - lpOverlapped : OVERLAPPED* + lpOverlapped : OVERLAPPED*, ) : BOOL fun SetFileTime(hFile : HANDLE, lpCreationTime : FILETIME*, lpLastAccessTime : FILETIME*, lpLastWriteTime : FILETIME*) : BOOL diff --git a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr index f6d56ef5a0e6..d6632e329f6b 100644 --- a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr +++ b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr @@ -3,14 +3,14 @@ lib LibC hFile : HANDLE, lpOverlapped : OVERLAPPED*, lpNumberOfBytesTransferred : DWORD*, - bWait : BOOL + bWait : BOOL, ) : BOOL fun CreateIoCompletionPort( fileHandle : HANDLE, existingCompletionPort : HANDLE, completionKey : ULong*, - numberOfConcurrentThreads : DWORD + numberOfConcurrentThreads : DWORD, ) : HANDLE fun GetQueuedCompletionStatusEx( @@ -19,22 +19,22 @@ lib LibC ulCount : ULong, ulNumEntriesRemoved : ULong*, dwMilliseconds : DWORD, - fAlertable : BOOL + fAlertable : BOOL, ) : BOOL fun PostQueuedCompletionStatus( completionPort : HANDLE, dwNumberOfBytesTransferred : DWORD, dwCompletionKey : ULONG_PTR, - lpOverlapped : OVERLAPPED* + lpOverlapped : OVERLAPPED*, ) : BOOL fun CancelIoEx( hFile : HANDLE, - lpOverlapped : OVERLAPPED* + lpOverlapped : OVERLAPPED*, ) : BOOL fun CancelIo( - hFile : HANDLE + hFile : HANDLE, ) : BOOL fun DeviceIoControl( @@ -45,6 +45,6 @@ lib LibC lpOutBuffer : Void*, nOutBufferSize : DWORD, lpBytesReturned : DWORD*, - lpOverlapped : OVERLAPPED* + lpOverlapped : OVERLAPPED*, ) : BOOL end diff --git a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr index f60e80a59328..c22bd1dfab31 100644 --- a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr +++ b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr @@ -8,13 +8,13 @@ lib LibC fun WideCharToMultiByte( codePage : UInt, dwFlags : DWORD, lpWideCharStr : LPWSTR, cchWideChar : Int, lpMultiByteStr : LPSTR, cbMultiByte : Int, - lpDefaultChar : CHAR*, lpUsedDefaultChar : BOOL* + lpDefaultChar : CHAR*, lpUsedDefaultChar : BOOL*, ) : Int # this was for the now removed delay-load helper, all other code should use # `String#to_utf16` instead fun MultiByteToWideChar( codePage : UInt, dwFlags : DWORD, lpMultiByteStr : LPSTR, - cbMultiByte : Int, lpWideCharStr : LPWSTR, cchWideChar : Int + cbMultiByte : Int, lpWideCharStr : LPWSTR, cchWideChar : Int, ) : Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr index 68ce6f9ef421..21ae8baba852 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr @@ -154,7 +154,7 @@ lib LibC addr : Sockaddr*, addrlen : Int*, lpfnCondition : LPCONDITIONPROC, - dwCallbackData : DWORD* + dwCallbackData : DWORD*, ) : SOCKET fun WSAConnect( @@ -164,21 +164,21 @@ lib LibC lpCallerData : WSABUF*, lpCalleeData : WSABUF*, lpSQOS : LPQOS, - lpGQOS : LPQOS + lpGQOS : LPQOS, ) fun WSACreateEvent : WSAEVENT fun WSAEventSelect( s : SOCKET, hEventObject : WSAEVENT, - lNetworkEvents : Long + lNetworkEvents : Long, ) : Int fun WSAGetOverlappedResult( s : SOCKET, lpOverlapped : WSAOVERLAPPED*, lpcbTransfer : DWORD*, fWait : BOOL, - lpdwFlags : DWORD* + lpdwFlags : DWORD*, ) : BOOL fun WSAIoctl( s : SOCKET, @@ -189,7 +189,7 @@ lib LibC cbOutBuffer : DWORD, lpcbBytesReturned : DWORD*, lpOverlapped : WSAOVERLAPPED*, - lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE* + lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*, ) : Int fun WSARecv( s : SOCKET, @@ -198,7 +198,7 @@ lib LibC lpNumberOfBytesRecvd : DWORD*, lpFlags : DWORD*, lpOverlapped : WSAOVERLAPPED*, - lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE* + lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*, ) : Int fun WSARecvFrom( s : SOCKET, @@ -209,10 +209,10 @@ lib LibC lpFrom : Sockaddr*, lpFromlen : Int*, lpOverlapped : WSAOVERLAPPED*, - lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE* + lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*, ) : Int fun WSAResetEvent( - hEvent : WSAEVENT + hEvent : WSAEVENT, ) : BOOL fun WSASend( s : SOCKET, @@ -221,7 +221,7 @@ lib LibC lpNumberOfBytesSent : DWORD*, dwFlags : DWORD, lpOverlapped : WSAOVERLAPPED*, - lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE* + lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*, ) : Int fun WSASendTo( s : SOCKET, @@ -232,7 +232,7 @@ lib LibC lpTo : Sockaddr*, iTolen : Int, lpOverlapped : WSAOVERLAPPED*, - lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE* + lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*, ) : Int fun WSASocketW( af : Int, @@ -240,13 +240,13 @@ lib LibC protocol : Int, lpProtocolInfo : WSAPROTOCOL_INFOW*, g : GROUP, - dwFlags : DWORD + dwFlags : DWORD, ) : SOCKET fun WSAWaitForMultipleEvents( cEvents : DWORD, lphEvents : WSAEVENT*, fWaitAll : BOOL, dwTimeout : DWORD, - fAlertable : BOOL + fAlertable : BOOL, ) : DWORD end diff --git a/src/llvm/lib_llvm/debug_info.cr b/src/llvm/lib_llvm/debug_info.cr index e6155b317eb5..15d2eca3ebd6 100644 --- a/src/llvm/lib_llvm/debug_info.cr +++ b/src/llvm/lib_llvm/debug_info.cr @@ -14,7 +14,7 @@ lib LibLLVM builder : DIBuilderRef, lang : LLVM::DwarfSourceLanguage, file_ref : MetadataRef, producer : Char*, producer_len : SizeT, is_optimized : Bool, flags : Char*, flags_len : SizeT, runtime_ver : UInt, split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt, - split_debug_inlining : Bool, debug_info_for_profiling : Bool + split_debug_inlining : Bool, debug_info_for_profiling : Bool, ) : MetadataRef {% else %} fun di_builder_create_compile_unit = LLVMDIBuilderCreateCompileUnit( @@ -22,82 +22,82 @@ lib LibLLVM producer_len : SizeT, is_optimized : Bool, flags : Char*, flags_len : SizeT, runtime_ver : UInt, split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt, split_debug_inlining : Bool, debug_info_for_profiling : Bool, sys_root : Char*, - sys_root_len : SizeT, sdk : Char*, sdk_len : SizeT + sys_root_len : SizeT, sdk : Char*, sdk_len : SizeT, ) : MetadataRef {% end %} fun di_builder_create_file = LLVMDIBuilderCreateFile( builder : DIBuilderRef, filename : Char*, filename_len : SizeT, - directory : Char*, directory_len : SizeT + directory : Char*, directory_len : SizeT, ) : MetadataRef fun di_builder_create_function = LLVMDIBuilderCreateFunction( builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, linkage_name : Char*, linkage_name_len : SizeT, file : MetadataRef, line_no : UInt, ty : MetadataRef, is_local_to_unit : Bool, is_definition : Bool, scope_line : UInt, - flags : LLVM::DIFlags, is_optimized : Bool + flags : LLVM::DIFlags, is_optimized : Bool, ) : MetadataRef fun di_builder_create_lexical_block = LLVMDIBuilderCreateLexicalBlock( - builder : DIBuilderRef, scope : MetadataRef, file : MetadataRef, line : UInt, column : UInt + builder : DIBuilderRef, scope : MetadataRef, file : MetadataRef, line : UInt, column : UInt, ) : MetadataRef fun di_builder_create_lexical_block_file = LLVMDIBuilderCreateLexicalBlockFile( - builder : DIBuilderRef, scope : MetadataRef, file_scope : MetadataRef, discriminator : UInt + builder : DIBuilderRef, scope : MetadataRef, file_scope : MetadataRef, discriminator : UInt, ) : MetadataRef fun di_builder_create_debug_location = LLVMDIBuilderCreateDebugLocation( - ctx : ContextRef, line : UInt, column : UInt, scope : MetadataRef, inlined_at : MetadataRef + ctx : ContextRef, line : UInt, column : UInt, scope : MetadataRef, inlined_at : MetadataRef, ) : MetadataRef fun di_builder_get_or_create_type_array = LLVMDIBuilderGetOrCreateTypeArray(builder : DIBuilderRef, types : MetadataRef*, length : SizeT) : MetadataRef fun di_builder_create_subroutine_type = LLVMDIBuilderCreateSubroutineType( builder : DIBuilderRef, file : MetadataRef, parameter_types : MetadataRef*, - num_parameter_types : UInt, flags : LLVM::DIFlags + num_parameter_types : UInt, flags : LLVM::DIFlags, ) : MetadataRef {% unless LibLLVM::IS_LT_90 %} fun di_builder_create_enumerator = LLVMDIBuilderCreateEnumerator( - builder : DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : Bool + builder : DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : Bool, ) : MetadataRef {% end %} fun di_builder_create_enumeration_type = LLVMDIBuilderCreateEnumerationType( builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, - elements : MetadataRef*, num_elements : UInt, class_ty : MetadataRef + elements : MetadataRef*, num_elements : UInt, class_ty : MetadataRef, ) : MetadataRef fun di_builder_create_union_type = LLVMDIBuilderCreateUnionType( builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags, - elements : MetadataRef*, num_elements : UInt, run_time_lang : UInt, unique_id : Char*, unique_id_len : SizeT + elements : MetadataRef*, num_elements : UInt, run_time_lang : UInt, unique_id : Char*, unique_id_len : SizeT, ) : MetadataRef fun di_builder_create_array_type = LLVMDIBuilderCreateArrayType( builder : DIBuilderRef, size : UInt64, align_in_bits : UInt32, - ty : MetadataRef, subscripts : MetadataRef*, num_subscripts : UInt + ty : MetadataRef, subscripts : MetadataRef*, num_subscripts : UInt, ) : MetadataRef fun di_builder_create_unspecified_type = LLVMDIBuilderCreateUnspecifiedType(builder : DIBuilderRef, name : Char*, name_len : SizeT) : MetadataRef fun di_builder_create_basic_type = LLVMDIBuilderCreateBasicType( builder : DIBuilderRef, name : Char*, name_len : SizeT, size_in_bits : UInt64, - encoding : UInt, flags : LLVM::DIFlags + encoding : UInt, flags : LLVM::DIFlags, ) : MetadataRef fun di_builder_create_pointer_type = LLVMDIBuilderCreatePointerType( builder : DIBuilderRef, pointee_ty : MetadataRef, size_in_bits : UInt64, align_in_bits : UInt32, - address_space : UInt, name : Char*, name_len : SizeT + address_space : UInt, name : Char*, name_len : SizeT, ) : MetadataRef fun di_builder_create_struct_type = LLVMDIBuilderCreateStructType( builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags, derived_from : MetadataRef, elements : MetadataRef*, num_elements : UInt, - run_time_lang : UInt, v_table_holder : MetadataRef, unique_id : Char*, unique_id_len : SizeT + run_time_lang : UInt, v_table_holder : MetadataRef, unique_id : Char*, unique_id_len : SizeT, ) : MetadataRef fun di_builder_create_member_type = LLVMDIBuilderCreateMemberType( builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, line_no : UInt, size_in_bits : UInt64, align_in_bits : UInt32, offset_in_bits : UInt64, - flags : LLVM::DIFlags, ty : MetadataRef + flags : LLVM::DIFlags, ty : MetadataRef, ) : MetadataRef fun di_builder_create_replaceable_composite_type = LLVMDIBuilderCreateReplaceableCompositeType( builder : DIBuilderRef, tag : UInt, name : Char*, name_len : SizeT, scope : MetadataRef, file : MetadataRef, line : UInt, runtime_lang : UInt, size_in_bits : UInt64, align_in_bits : UInt32, - flags : LLVM::DIFlags, unique_identifier : Char*, unique_identifier_len : SizeT + flags : LLVM::DIFlags, unique_identifier : Char*, unique_identifier_len : SizeT, ) : MetadataRef fun di_builder_get_or_create_subrange = LLVMDIBuilderGetOrCreateSubrange(builder : DIBuilderRef, lo : Int64, count : Int64) : MetadataRef @@ -114,22 +114,22 @@ lib LibLLVM {% if LibLLVM::IS_LT_190 %} fun di_builder_insert_declare_at_end = LLVMDIBuilderInsertDeclareAtEnd( builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef, - expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef + expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef, ) : ValueRef {% else %} fun di_builder_insert_declare_record_at_end = LLVMDIBuilderInsertDeclareRecordAtEnd( builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef, - expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef + expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef, ) : DbgRecordRef {% end %} fun di_builder_create_auto_variable = LLVMDIBuilderCreateAutoVariable( builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef, - line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags, align_in_bits : UInt32 + line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags, align_in_bits : UInt32, ) : MetadataRef fun di_builder_create_parameter_variable = LLVMDIBuilderCreateParameterVariable( builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, arg_no : UInt, - file : MetadataRef, line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags + file : MetadataRef, line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags, ) : MetadataRef fun set_subprogram = LLVMSetSubprogram(func : ValueRef, sp : MetadataRef) diff --git a/src/llvm/lib_llvm/orc.cr b/src/llvm/lib_llvm/orc.cr index a1650b3dfb96..278a9c4aab5d 100644 --- a/src/llvm/lib_llvm/orc.cr +++ b/src/llvm/lib_llvm/orc.cr @@ -12,7 +12,7 @@ lib LibLLVM fun orc_create_dynamic_library_search_generator_for_process = LLVMOrcCreateDynamicLibrarySearchGeneratorForProcess( result : OrcDefinitionGeneratorRef*, global_prefx : Char, - filter : OrcSymbolPredicate, filter_ctx : Void* + filter : OrcSymbolPredicate, filter_ctx : Void*, ) : ErrorRef fun orc_jit_dylib_add_generator = LLVMOrcJITDylibAddGenerator(jd : OrcJITDylibRef, dg : OrcDefinitionGeneratorRef) diff --git a/src/proc.cr b/src/proc.cr index fca714517dbf..69c0ebf5cd0e 100644 --- a/src/proc.cr +++ b/src/proc.cr @@ -3,7 +3,7 @@ # # ``` # # A proc without arguments -# ->{ 1 } # Proc(Int32) +# -> { 1 } # Proc(Int32) # # # A proc with one argument # ->(x : Int32) { x.to_s } # Proc(Int32, String) diff --git a/src/random/isaac.cr b/src/random/isaac.cr index c877cb9dbae9..294d439fb82d 100644 --- a/src/random/isaac.cr +++ b/src/random/isaac.cr @@ -61,7 +61,7 @@ class Random::ISAAC a = b = c = d = e = f = g = h = 0x9e3779b9_u32 - mix = ->{ + mix = -> { a ^= b << 11; d &+= a; b &+= c b ^= c >> 2; e &+= b; c &+= d c ^= d << 8; f &+= c; d &+= e diff --git a/src/wait_group.cr b/src/wait_group.cr index 89510714c727..c1ebe67bf508 100644 --- a/src/wait_group.cr +++ b/src/wait_group.cr @@ -52,7 +52,7 @@ class WaitGroup # end # end # ``` - def self.wait : Nil + def self.wait(&) : Nil instance = new yield instance instance.wait From d5600eb6dcc2ec67d1a7ac53cc3c0154221edcb8 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 11 Oct 2024 19:55:29 +0800 Subject: [PATCH 1401/1551] Basic MinGW-w64 cross-compilation support (#15070) Resolves part of #6170. These series of patches allow `--cross-compile --target=x86_64-windows-gnu` to mostly work: * The `@[ThreadLocal]` annotation and its corresponding LLVM attribute seem to break when targetting `x86_64-windows-gnu`, so Win32 TLS is used instead. This is only needed for `Thread.current`. * Since MinGW uses `libgcc`, and Crystal relies on the underlying C++ ABI to raise exceptions, we use the Itanium ABI's `_Unwind_*` functions, along with a thin personality function wrapper. ([GCC itself does the same](https://github.com/gcc-mirror/gcc/blob/68afc7acf609be2b19ec05c8393c2ffc7f4adb4a/libgcc/unwind-c.c#L238-L246). See also [Language-specific handler](https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-170#language-specific-handler) from the Windows x64 ABI docs.) * MinGW binaries now come with DWARF debug information, so they work under GDB, maybe under LLDB, and probably poorly under the Visual Studio Debugger. * There is no need to mangle symbol names the same way MSVC binaries do. * `--cross-compile` now prints ld-style linker flags, rather than MSVC ones. This is still incomplete and includes remnants of the MSVC toolchain like the `/ENTRY` flag; they will be fixed later once we get to native compiler builds. * `src/lib_c/x86_64-windows-gnu` is now a symlink to `src/lib_c/x86_64-windows-msvc`, since both toolchains are targetting the same Win32 APIs. (This is not Cygwin nor MSYS2's MSYS environment.) * Lib funs now use the Win32 C ABI, instead of the SysV ABI. * On MinGW we use GMP proper, and there is no need for MPIR. After building a local compiler, `bin\crystal build --cross-compile --target=x86_64-windows-gnu` will generate an object file suitable for linking under MinGW-w64. At a minimum, this object file depends on Boehm GC and libiconv, although they can be skipped using `-Dgc_none` and `-Dwithout_iconv` respectively. Then we could use MSYS2's UCRT64 environment to link the final executable: ``` $ pacman -Sy mingw-w64-ucrt-x86_64-gc mingw-w64-ucrt-x86_64-pcre2 mingw-w64-ucrt-x86_64-libiconv mingw-w64-ucrt-x86_64-gmp $ cc test.obj `pkg-config bdw-gc iconv libpcre2-8 gmp --libs` -lDbgHelp -lole32 ``` Stack traces do not work correctly yet. Also note that MSYS2's DLL names are different from the ones distributed with MSVC Crystal, and that cross-compilation never copies the DLL dependencies to the output directory. To make the executable run outside MSYS2, use `dumpbin /dependents` from the MSVC developer prompt to obtain the dependencies, then copy them manually from the MSYS2 `/ucrt64/bin` folder. --- spec/std/big/big_float_spec.cr | 44 ++++++++++++----- src/big/big_int.cr | 4 +- src/big/lib_gmp.cr | 29 +++++++----- src/compiler/crystal/codegen/codegen.cr | 4 +- src/compiler/crystal/codegen/debug.cr | 2 +- src/compiler/crystal/codegen/exception.cr | 17 +++---- src/compiler/crystal/codegen/link.cr | 2 +- src/crystal/system/win32/thread.cr | 47 +++++++++++++++++-- src/exception/call_stack.cr | 3 ++ src/exception/call_stack/stackwalk.cr | 27 +++++++++++ src/exception/lib_unwind.cr | 8 +++- src/lib_c/x86_64-windows-gnu | 1 + .../c/processthreadsapi.cr | 6 +++ src/llvm/target_machine.cr | 2 +- src/raise.cr | 29 ++++++++++-- 15 files changed, 173 insertions(+), 52 deletions(-) create mode 120000 src/lib_c/x86_64-windows-gnu diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index 23c782aa3de8..4aee9eee51e8 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -350,8 +350,11 @@ describe "BigFloat" do it { (2.to_big_f ** -7133786264).to_s.should end_with("e-2147483649") } # least power of two with a base-10 exponent less than Int32::MIN it { (10.to_big_f ** 3000000000 * 1.5).to_s.should end_with("e+3000000000") } it { (10.to_big_f ** -3000000000 * 1.5).to_s.should end_with("e-3000000000") } - it { (10.to_big_f ** 10000000000 * 1.5).to_s.should end_with("e+10000000000") } - it { (10.to_big_f ** -10000000000 * 1.5).to_s.should end_with("e-10000000000") } + + {% unless flag?(:win32) && flag?(:gnu) %} + it { (10.to_big_f ** 10000000000 * 1.5).to_s.should end_with("e+10000000000") } + it { (10.to_big_f ** -10000000000 * 1.5).to_s.should end_with("e-10000000000") } + {% end %} end describe "#inspect" do @@ -558,8 +561,12 @@ describe "BigFloat Math" do Math.ilogb(0.2.to_big_f).should eq(-3) Math.ilogb(123.45.to_big_f).should eq(6) Math.ilogb(2.to_big_f ** 1_000_000_000).should eq(1_000_000_000) - Math.ilogb(2.to_big_f ** 100_000_000_000).should eq(100_000_000_000) - Math.ilogb(2.to_big_f ** -100_000_000_000).should eq(-100_000_000_000) + + {% unless flag?(:win32) && flag?(:gnu) %} + Math.ilogb(2.to_big_f ** 100_000_000_000).should eq(100_000_000_000) + Math.ilogb(2.to_big_f ** -100_000_000_000).should eq(-100_000_000_000) + {% end %} + expect_raises(ArgumentError) { Math.ilogb(0.to_big_f) } end @@ -567,8 +574,12 @@ describe "BigFloat Math" do Math.logb(0.2.to_big_f).should eq(-3.to_big_f) Math.logb(123.45.to_big_f).should eq(6.to_big_f) Math.logb(2.to_big_f ** 1_000_000_000).should eq(1_000_000_000.to_big_f) - Math.logb(2.to_big_f ** 100_000_000_000).should eq(100_000_000_000.to_big_f) - Math.logb(2.to_big_f ** -100_000_000_000).should eq(-100_000_000_000.to_big_f) + + {% unless flag?(:win32) && flag?(:gnu) %} + Math.logb(2.to_big_f ** 100_000_000_000).should eq(100_000_000_000.to_big_f) + Math.logb(2.to_big_f ** -100_000_000_000).should eq(-100_000_000_000.to_big_f) + {% end %} + expect_raises(ArgumentError) { Math.logb(0.to_big_f) } end @@ -576,24 +587,33 @@ describe "BigFloat Math" do Math.ldexp(0.2.to_big_f, 2).should eq(0.8.to_big_f) Math.ldexp(0.2.to_big_f, -2).should eq(0.05.to_big_f) Math.ldexp(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000) - Math.ldexp(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000) - Math.ldexp(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000) + + {% unless flag?(:win32) && flag?(:gnu) %} + Math.ldexp(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000) + Math.ldexp(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000) + {% end %} end it ".scalbn" do Math.scalbn(0.2.to_big_f, 2).should eq(0.8.to_big_f) Math.scalbn(0.2.to_big_f, -2).should eq(0.05.to_big_f) Math.scalbn(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000) - Math.scalbn(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000) - Math.scalbn(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000) + + {% unless flag?(:win32) && flag?(:gnu) %} + Math.scalbn(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000) + Math.scalbn(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000) + {% end %} end it ".scalbln" do Math.scalbln(0.2.to_big_f, 2).should eq(0.8.to_big_f) Math.scalbln(0.2.to_big_f, -2).should eq(0.05.to_big_f) Math.scalbln(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000) - Math.scalbln(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000) - Math.scalbln(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000) + + {% unless flag?(:win32) && flag?(:gnu) %} + Math.scalbln(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000) + Math.scalbln(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000) + {% end %} end it ".frexp" do diff --git a/src/big/big_int.cr b/src/big/big_int.cr index 49738cb8bfbc..c306a490a412 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -659,7 +659,7 @@ struct BigInt < Int {% for n in [8, 16, 32, 64, 128] %} def to_i{{n}} : Int{{n}} \{% if Int{{n}} == LibGMP::SI %} - LibGMP.{{ flag?(:win32) ? "fits_si_p".id : "fits_slong_p".id }}(self) != 0 ? LibGMP.get_si(self) : raise OverflowError.new + LibGMP.{{ flag?(:win32) && !flag?(:gnu) ? "fits_si_p".id : "fits_slong_p".id }}(self) != 0 ? LibGMP.get_si(self) : raise OverflowError.new \{% elsif Int{{n}}::MAX.is_a?(NumberLiteral) && Int{{n}}::MAX < LibGMP::SI::MAX %} LibGMP::SI.new(self).to_i{{n}} \{% else %} @@ -669,7 +669,7 @@ struct BigInt < Int def to_u{{n}} : UInt{{n}} \{% if UInt{{n}} == LibGMP::UI %} - LibGMP.{{ flag?(:win32) ? "fits_ui_p".id : "fits_ulong_p".id }}(self) != 0 ? LibGMP.get_ui(self) : raise OverflowError.new + LibGMP.{{ flag?(:win32) && !flag?(:gnu) ? "fits_ui_p".id : "fits_ulong_p".id }}(self) != 0 ? LibGMP.get_ui(self) : raise OverflowError.new \{% elsif UInt{{n}}::MAX.is_a?(NumberLiteral) && UInt{{n}}::MAX < LibGMP::UI::MAX %} LibGMP::UI.new(self).to_u{{n}} \{% else %} diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index c50b1f7f6e9b..7368cb0e9fb6 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -1,4 +1,4 @@ -{% if flag?(:win32) %} +{% if flag?(:win32) && !flag?(:gnu) %} @[Link("mpir")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} @[Link(dll: "mpir.dll")] @@ -14,7 +14,7 @@ lib LibGMP # MPIR uses its own `mpir_si` and `mpir_ui` typedefs in places where GMP uses # the LibC types, when the function name has `si` or `ui`; we follow this # distinction - {% if flag?(:win32) && flag?(:bits64) %} + {% if flag?(:win32) && !flag?(:gnu) && flag?(:bits64) %} alias SI = LibC::LongLong alias UI = LibC::ULongLong {% else %} @@ -26,17 +26,19 @@ lib LibGMP alias Double = LibC::Double alias BitcntT = UI - {% if flag?(:win32) && flag?(:bits64) %} - alias MpExp = LibC::Long + alias MpExp = LibC::Long + + {% if flag?(:win32) && !flag?(:gnu) %} alias MpSize = LibC::LongLong - alias MpLimb = LibC::ULongLong - {% elsif flag?(:bits64) %} - alias MpExp = Int64 - alias MpSize = LibC::Long - alias MpLimb = LibC::ULong {% else %} - alias MpExp = Int32 alias MpSize = LibC::Long + {% end %} + + # NOTE: this assumes GMP is configured by build time to define + # `_LONG_LONG_LIMB=1` on Windows + {% if flag?(:win32) %} + alias MpLimb = LibC::ULongLong + {% else %} alias MpLimb = LibC::ULong {% end %} @@ -149,11 +151,12 @@ lib LibGMP # # Miscellaneous Functions - fun fits_ulong_p = __gmpz_fits_ulong_p(op : MPZ*) : Int - fun fits_slong_p = __gmpz_fits_slong_p(op : MPZ*) : Int - {% if flag?(:win32) %} + {% if flag?(:win32) && !flag?(:gnu) %} fun fits_ui_p = __gmpz_fits_ui_p(op : MPZ*) : Int fun fits_si_p = __gmpz_fits_si_p(op : MPZ*) : Int + {% else %} + fun fits_ulong_p = __gmpz_fits_ulong_p(op : MPZ*) : Int + fun fits_slong_p = __gmpz_fits_slong_p(op : MPZ*) : Int {% end %} # # Special Functions diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index f040f87e17f5..c4844df9a5e8 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -285,7 +285,7 @@ module Crystal @main = @llvm_mod.functions.add(MAIN_NAME, main_type) @fun_types = { {@llvm_mod, MAIN_NAME} => main_type } - if @program.has_flag? "windows" + if @program.has_flag?("msvc") @personality_name = "__CxxFrameHandler3" @main.personality_function = windows_personality_fun.func else @@ -2488,7 +2488,7 @@ module Crystal end def self.safe_mangling(program, name) - if program.has_flag?("windows") + if program.has_flag?("msvc") String.build do |str| name.each_char do |char| if char.ascii_alphanumeric? || char == '_' diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index 9a03420ba203..dd4b6c361905 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -40,7 +40,7 @@ module Crystal def push_debug_info_metadata(mod) di_builder(mod).end - if @program.has_flag?("windows") + if @program.has_flag?("msvc") # Windows uses CodeView instead of DWARF mod.add_flag( LibLLVM::ModuleFlagBehavior::Warning, diff --git a/src/compiler/crystal/codegen/exception.cr b/src/compiler/crystal/codegen/exception.cr index 9a33e1337550..944ac99fce7d 100644 --- a/src/compiler/crystal/codegen/exception.cr +++ b/src/compiler/crystal/codegen/exception.cr @@ -60,9 +60,9 @@ class Crystal::CodeGenVisitor # # Note we codegen the ensure body three times! In practice this isn't a big deal, since ensure bodies are typically small. - windows = @program.has_flag? "windows" + msvc = @program.has_flag?("msvc") - context.fun.personality_function = windows_personality_fun.func if windows + context.fun.personality_function = windows_personality_fun.func if msvc # This is the block which is entered when the body raises an exception rescue_block = new_block "rescue" @@ -109,7 +109,7 @@ class Crystal::CodeGenVisitor old_catch_pad = @catch_pad - if windows + if msvc # Windows structured exception handling must enter a catch_switch instruction # which decides which catch body block to enter. Crystal only ever generates one catch body # which is used for all exceptions. For more information on how structured exception handling works in LLVM, @@ -138,7 +138,8 @@ class Crystal::CodeGenVisitor caught_exception = load exception_llvm_type, caught_exception_ptr exception_type_id = type_id(caught_exception, exception_type) else - # Unwind exception handling code - used on non-windows platforms - is a lot simpler. + # Unwind exception handling code - used on non-MSVC platforms (essentially the Itanium + # C++ ABI) - is a lot simpler. # First we generate the landing pad instruction, this returns a tuple of the libunwind # exception object and the type ID of the exception. This tuple is set up in the crystal # personality function in raise.cr @@ -188,7 +189,7 @@ class Crystal::CodeGenVisitor # If the rescue restriction matches, codegen the rescue block. position_at_end this_rescue_block - # On windows, we are "inside" the catchpad block. It's difficult to track when to catch_ret when + # On MSVC, we are "inside" the catchpad block. It's difficult to track when to catch_ret when # codegenning the entire rescue body, so we catch_ret early and execute the rescue bodies "outside" the # rescue block. if catch_pad = @catch_pad @@ -248,7 +249,7 @@ class Crystal::CodeGenVisitor # Codegen catchswitch+pad or landing pad as described above. # This code is simpler because we never need to extract the exception type - if windows + if msvc rescue_ensure_body = new_block "rescue_ensure_body" catch_switch = builder.catch_switch(old_catch_pad || LLVM::Value.null, @rescue_block || LLVM::BasicBlock.null, 1) builder.add_handler catch_switch, rescue_ensure_body @@ -283,8 +284,8 @@ class Crystal::CodeGenVisitor end def codegen_re_raise(node, unwind_ex_obj) - if @program.has_flag? "windows" - # On windows we can re-raise by calling _CxxThrowException with two null arguments + if @program.has_flag?("msvc") + # On the MSVC C++ ABI we can re-raise by calling _CxxThrowException with two null arguments call windows_throw_fun, [llvm_context.void_pointer.null, llvm_context.void_pointer.null] unreachable else diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index 3601aa0fd870..81a1a96f4445 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -121,7 +121,7 @@ module Crystal class Program def lib_flags - has_flag?("windows") ? lib_flags_windows : lib_flags_posix + has_flag?("msvc") ? lib_flags_windows : lib_flags_posix end private def lib_flags_windows diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr index 652f5487498f..9cb60f01ced8 100644 --- a/src/crystal/system/win32/thread.cr +++ b/src/crystal/system/win32/thread.cr @@ -45,12 +45,49 @@ module Crystal::System::Thread LibC.SwitchToThread end - @[ThreadLocal] - class_property current_thread : ::Thread { ::Thread.new } + # MinGW does not support TLS correctly + {% if flag?(:gnu) %} + @@current_key : LibC::DWORD = begin + current_key = LibC.TlsAlloc + if current_key == LibC::TLS_OUT_OF_INDEXES + Crystal::System.panic("TlsAlloc()", WinError.value) + end + current_key + end - def self.current_thread? : ::Thread? - @@current_thread - end + def self.current_thread : ::Thread + th = current_thread? + return th if th + + # Thread#start sets `Thread.current` as soon it starts. Thus we know + # that if `Thread.current` is not set then we are in the main thread + self.current_thread = ::Thread.new + end + + def self.current_thread? : ::Thread? + ptr = LibC.TlsGetValue(@@current_key) + err = WinError.value + unless err == WinError::ERROR_SUCCESS + Crystal::System.panic("TlsGetValue()", err) + end + + ptr.as(::Thread?) + end + + def self.current_thread=(thread : ::Thread) + if LibC.TlsSetValue(@@current_key, thread.as(Void*)) == 0 + Crystal::System.panic("TlsSetValue()", WinError.value) + end + thread + end + {% else %} + @[ThreadLocal] + class_property current_thread : ::Thread { ::Thread.new } + + def self.current_thread? : ::Thread? + @@current_thread + end + {% end %} def self.sleep(time : ::Time::Span) : Nil LibC.Sleep(time.total_milliseconds.to_i.clamp(1..)) diff --git a/src/exception/call_stack.cr b/src/exception/call_stack.cr index be631e19cdc7..44a281570c1c 100644 --- a/src/exception/call_stack.cr +++ b/src/exception/call_stack.cr @@ -2,6 +2,9 @@ require "./call_stack/interpreter" {% elsif flag?(:win32) %} require "./call_stack/stackwalk" + {% if flag?(:gnu) %} + require "./lib_unwind" + {% end %} {% elsif flag?(:wasm32) %} require "./call_stack/null" {% else %} diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index b3e2ed8f479c..6ac59fa6db48 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -168,6 +168,33 @@ struct Exception::CallStack end end + # TODO: needed only if `__crystal_raise` fails, check if this actually works + {% if flag?(:gnu) %} + def self.print_backtrace : Nil + backtrace_fn = ->(context : LibUnwind::Context, data : Void*) do + last_frame = data.as(RepeatedFrame*) + + ip = {% if flag?(:arm) %} + Pointer(Void).new(__crystal_unwind_get_ip(context)) + {% else %} + Pointer(Void).new(LibUnwind.get_ip(context)) + {% end %} + + if last_frame.value.ip == ip + last_frame.value.incr + else + print_frame(last_frame.value) unless last_frame.value.ip.address == 0 + last_frame.value = RepeatedFrame.new ip + end + LibUnwind::ReasonCode::NO_REASON + end + + rf = RepeatedFrame.new(Pointer(Void).null) + LibUnwind.backtrace(backtrace_fn, pointerof(rf).as(Void*)) + print_frame(rf) + end + {% end %} + private def self.print_frame(repeated_frame) Crystal::System.print_error "[%p] ", repeated_frame.ip print_frame_location(repeated_frame) diff --git a/src/exception/lib_unwind.cr b/src/exception/lib_unwind.cr index 7c9c6fd75ec5..83350c12fe3a 100644 --- a/src/exception/lib_unwind.cr +++ b/src/exception/lib_unwind.cr @@ -113,8 +113,12 @@ lib LibUnwind struct Exception exception_class : LibC::SizeT exception_cleanup : LibC::SizeT - private1 : UInt64 - private2 : UInt64 + {% if flag?(:win32) && flag?(:gnu) %} + private_ : UInt64[6] + {% else %} + private1 : UInt64 + private2 : UInt64 + {% end %} exception_object : Void* exception_type_id : Int32 end diff --git a/src/lib_c/x86_64-windows-gnu b/src/lib_c/x86_64-windows-gnu new file mode 120000 index 000000000000..072348f65d09 --- /dev/null +++ b/src/lib_c/x86_64-windows-gnu @@ -0,0 +1 @@ +x86_64-windows-msvc \ No newline at end of file diff --git a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr index 1fcaee65a01c..22001cfc1632 100644 --- a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr @@ -63,5 +63,11 @@ lib LibC fun ResumeThread(hThread : HANDLE) : DWORD fun SuspendThread(hThread : HANDLE) : DWORD + TLS_OUT_OF_INDEXES = 0xFFFFFFFF_u32 + + fun TlsAlloc : DWORD + fun TlsGetValue(dwTlsIndex : DWORD) : Void* + fun TlsSetValue(dwTlsIndex : DWORD, lpTlsValue : Void*) : BOOL + PROCESS_QUERY_INFORMATION = 0x0400 end diff --git a/src/llvm/target_machine.cr b/src/llvm/target_machine.cr index b9de8296d5c8..6e31836ef7f2 100644 --- a/src/llvm/target_machine.cr +++ b/src/llvm/target_machine.cr @@ -48,7 +48,7 @@ class LLVM::TargetMachine def abi triple = self.triple case triple - when /x86_64.+windows-msvc/ + when /x86_64.+windows-(?:msvc|gnu)/ ABI::X86_Win64.new(self) when /x86_64|amd64/ ABI::X86_64.new(self) diff --git a/src/raise.cr b/src/raise.cr index ff8684795e77..a8e06a3c3930 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -91,7 +91,7 @@ end {% if flag?(:interpreted) %} # interpreter does not need `__crystal_personality` -{% elsif flag?(:win32) %} +{% elsif flag?(:win32) && !flag?(:gnu) %} require "exception/lib_unwind" {% begin %} @@ -181,8 +181,10 @@ end 0u64 end {% else %} - # :nodoc: - fun __crystal_personality(version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*) : LibUnwind::ReasonCode + {% mingw = flag?(:windows) && flag?(:gnu) %} + fun {{ mingw ? "__crystal_personality_imp".id : "__crystal_personality".id }}( + version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*, + ) : LibUnwind::ReasonCode start = LibUnwind.get_region_start(context) ip = LibUnwind.get_ip(context) lsd = LibUnwind.get_language_specific_data(context) @@ -197,9 +199,26 @@ end return LibUnwind::ReasonCode::CONTINUE_UNWIND end + + {% if mingw %} + lib LibC + alias EXCEPTION_DISPOSITION = Int + alias DISPATCHER_CONTEXT = Void + end + + lib LibUnwind + alias PersonalityFn = Int32, Action, UInt64, Exception*, Void* -> ReasonCode + + fun _GCC_specific_handler(ms_exc : LibC::EXCEPTION_RECORD64*, this_frame : Void*, ms_orig_context : LibC::CONTEXT*, ms_disp : LibC::DISPATCHER_CONTEXT*, gcc_per : PersonalityFn) : LibC::EXCEPTION_DISPOSITION + end + + fun __crystal_personality(ms_exc : LibC::EXCEPTION_RECORD64*, this_frame : Void*, ms_orig_context : LibC::CONTEXT*, ms_disp : LibC::DISPATCHER_CONTEXT*) : LibC::EXCEPTION_DISPOSITION + LibUnwind._GCC_specific_handler(ms_exc, this_frame, ms_orig_context, ms_disp, ->__crystal_personality_imp) + end + {% end %} {% end %} -{% unless flag?(:interpreted) || flag?(:win32) || flag?(:wasm32) %} +{% unless flag?(:interpreted) || (flag?(:win32) && !flag?(:gnu)) || flag?(:wasm32) %} # :nodoc: @[Raises] fun __crystal_raise(unwind_ex : LibUnwind::Exception*) : NoReturn @@ -244,7 +263,7 @@ def raise(message : String) : NoReturn raise Exception.new(message) end -{% if flag?(:win32) %} +{% if flag?(:win32) && !flag?(:gnu) %} # :nodoc: {% if flag?(:interpreted) %} @[Primitive(:interpreter_raise_without_backtrace)] {% end %} def raise_without_backtrace(exception : Exception) : NoReturn From d1c5072f09274c78443f74f60bb2e96565bb0a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 11 Oct 2024 13:55:40 +0200 Subject: [PATCH 1402/1551] Extract `deploy_api_docs` job into its own Workflow (#15022) --- .github/workflows/docs.yml | 43 +++++++++++++++++++++++++++++++++++++ .github/workflows/linux.yml | 33 ---------------------------- 2 files changed, 43 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000000..57147238552e --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,43 @@ +name: Docs + +on: + push: + branches: + - master + +env: + TRAVIS_OS_NAME: linux + +jobs: + deploy_api_docs: + if: github.repository_owner == 'crystal-lang' + env: + ARCH: x86_64 + ARCH_CMD: linux64 + runs-on: ubuntu-latest + steps: + - name: Download Crystal source + uses: actions/checkout@v4 + + - name: Prepare System + run: bin/ci prepare_system + + - name: Prepare Build + run: bin/ci prepare_build + + - name: Build docs + run: bin/ci with_build_env 'make crystal docs threads=1' + + - name: Set revision + run: echo $GITHUB_SHA > ./docs/revision.txt + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Deploy API docs to S3 + run: | + aws s3 sync ./docs s3://crystal-api/api/master --delete diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4bdcc3e0c11e..ad65fa005259 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -106,36 +106,3 @@ jobs: - name: Check Format run: bin/ci format - - deploy_api_docs: - if: github.repository_owner == 'crystal-lang' && github.event_name == 'push' && github.ref == 'refs/heads/master' - env: - ARCH: x86_64 - ARCH_CMD: linux64 - runs-on: ubuntu-latest - steps: - - name: Download Crystal source - uses: actions/checkout@v4 - - - name: Prepare System - run: bin/ci prepare_system - - - name: Prepare Build - run: bin/ci prepare_build - - - name: Build docs - run: bin/ci with_build_env 'make crystal docs threads=1' - - - name: Set revision - run: echo $GITHUB_SHA > ./docs/revision.txt - - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: us-east-1 - - - name: Deploy API docs to S3 - run: | - aws s3 sync ./docs s3://crystal-api/api/master --delete From 4daf48631ed48133f724c4501fa71d0223a4c49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 14 Oct 2024 11:10:47 +0200 Subject: [PATCH 1403/1551] Replace uses of `AliasType#types?` by `Type#lookup_name` (#15068) Introduces `Type#lookup_name` as a dedicated mechanism for looking up a name in a namespace. This allows us to drop the override of `AliasType#types?` which delegated to the aliased type. Thus cyclic hierarchies are no longer possible and we can drop all code (as far as I could find) that was necessary only for handling that. --- spec/compiler/semantic/did_you_mean_spec.cr | 13 +++++++++++++ src/compiler/crystal/codegen/link.cr | 2 -- .../crystal/semantic/abstract_def_checker.cr | 4 ---- src/compiler/crystal/semantic/new.cr | 2 -- src/compiler/crystal/semantic/path_lookup.cr | 2 +- .../crystal/semantic/recursive_struct_checker.cr | 5 ----- src/compiler/crystal/semantic/suggestions.cr | 4 ++-- .../semantic/type_declaration_processor.cr | 10 +++------- src/compiler/crystal/tools/doc/generator.cr | 7 ------- .../crystal/tools/typed_def_processor.cr | 9 --------- src/compiler/crystal/tools/unreachable.cr | 9 --------- src/compiler/crystal/types.cr | 16 ++++++---------- 12 files changed, 25 insertions(+), 58 deletions(-) diff --git a/spec/compiler/semantic/did_you_mean_spec.cr b/spec/compiler/semantic/did_you_mean_spec.cr index cd3f0856ebcb..1c74ebf74c2f 100644 --- a/spec/compiler/semantic/did_you_mean_spec.cr +++ b/spec/compiler/semantic/did_you_mean_spec.cr @@ -75,6 +75,19 @@ describe "Semantic: did you mean" do "Did you mean 'Foo::Bar'?" end + it "says did you mean for nested class via alias" do + assert_error <<-CRYSTAL, "Did you mean 'Boo::Bar'?" + class Foo + class Bar + end + end + + alias Boo = Foo + + Boo::Baz.new + CRYSTAL + end + it "says did you mean finds most similar in def" do assert_error " def barbaza diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index 81a1a96f4445..33248e93e19b 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -292,8 +292,6 @@ module Crystal private def add_link_annotations(types, annotations) types.try &.each_value do |type| - next if type.is_a?(AliasType) || type.is_a?(TypeDefType) - if type.is_a?(LibType) && type.used? && (link_annotations = type.link_annotations) annotations.concat link_annotations end diff --git a/src/compiler/crystal/semantic/abstract_def_checker.cr b/src/compiler/crystal/semantic/abstract_def_checker.cr index 2a7ccdc05d2a..6d1aa58447a1 100644 --- a/src/compiler/crystal/semantic/abstract_def_checker.cr +++ b/src/compiler/crystal/semantic/abstract_def_checker.cr @@ -24,7 +24,6 @@ # ``` class Crystal::AbstractDefChecker def initialize(@program : Program) - @all_checked = Set(Type).new end def run @@ -41,9 +40,6 @@ class Crystal::AbstractDefChecker end def check_single(type) - return if @all_checked.includes?(type) - @all_checked << type - if type.abstract? || type.module? type.defs.try &.each_value do |defs_with_metadata| defs_with_metadata.each do |def_with_metadata| diff --git a/src/compiler/crystal/semantic/new.cr b/src/compiler/crystal/semantic/new.cr index de8ae55312a0..43a0a631e2c6 100644 --- a/src/compiler/crystal/semantic/new.cr +++ b/src/compiler/crystal/semantic/new.cr @@ -22,8 +22,6 @@ module Crystal end def define_default_new(type) - return if type.is_a?(AliasType) || type.is_a?(TypeDefType) - type.types?.try &.each_value do |type| define_default_new_single(type) end diff --git a/src/compiler/crystal/semantic/path_lookup.cr b/src/compiler/crystal/semantic/path_lookup.cr index b2d66879d253..72cab053984b 100644 --- a/src/compiler/crystal/semantic/path_lookup.cr +++ b/src/compiler/crystal/semantic/path_lookup.cr @@ -71,7 +71,7 @@ module Crystal # precedence than ancestors and the enclosing namespace. def lookup_path_item(name : String, lookup_self, lookup_in_namespace, include_private, location) : Type | ASTNode | Nil # First search in our types - type = types?.try &.[name]? + type = lookup_name(name) if type if type.private? && !include_private return nil diff --git a/src/compiler/crystal/semantic/recursive_struct_checker.cr b/src/compiler/crystal/semantic/recursive_struct_checker.cr index e7f64913789f..888730e342bb 100644 --- a/src/compiler/crystal/semantic/recursive_struct_checker.cr +++ b/src/compiler/crystal/semantic/recursive_struct_checker.cr @@ -14,10 +14,8 @@ # Because the type of `Test.@test` would be: `Test | Nil`. class Crystal::RecursiveStructChecker @program : Program - @all_checked : Set(Type) def initialize(@program) - @all_checked = Set(Type).new end def run @@ -34,9 +32,6 @@ class Crystal::RecursiveStructChecker end def check_single(type) - has_not_been_checked = @all_checked.add?(type) - return unless has_not_been_checked - if struct?(type) target = type checked = Set(Type).new diff --git a/src/compiler/crystal/semantic/suggestions.cr b/src/compiler/crystal/semantic/suggestions.cr index 8f4a69d963bc..e9e05612007f 100644 --- a/src/compiler/crystal/semantic/suggestions.cr +++ b/src/compiler/crystal/semantic/suggestions.cr @@ -13,10 +13,10 @@ module Crystal type = self names.each_with_index do |name, idx| previous_type = type - type = previous_type.types?.try &.[name]? + type = previous_type.lookup_name(name) unless type best_match = Levenshtein.find(name.downcase) do |finder| - previous_type.types?.try &.each_key do |type_name| + previous_type.remove_alias.types?.try &.each_key do |type_name| finder.test(type_name.downcase, type_name) end end diff --git a/src/compiler/crystal/semantic/type_declaration_processor.cr b/src/compiler/crystal/semantic/type_declaration_processor.cr index 65451741fac3..0e6008b2fa78 100644 --- a/src/compiler/crystal/semantic/type_declaration_processor.cr +++ b/src/compiler/crystal/semantic/type_declaration_processor.cr @@ -621,14 +621,10 @@ struct Crystal::TypeDeclarationProcessor end private def remove_duplicate_instance_vars_declarations - # All the types that we checked for duplicate variables - duplicates_checked = Set(Type).new - remove_duplicate_instance_vars_declarations(@program, duplicates_checked) + remove_duplicate_instance_vars_declarations(@program) end - private def remove_duplicate_instance_vars_declarations(type : Type, duplicates_checked : Set(Type)) - return unless duplicates_checked.add?(type) - + private def remove_duplicate_instance_vars_declarations(type : Type) # If a class has an instance variable that already exists in a superclass, remove it. # Ideally we should process instance variables in a top-down fashion, but it's tricky # with modules and multiple-inheritance. Removing duplicates at the end is maybe @@ -650,7 +646,7 @@ struct Crystal::TypeDeclarationProcessor end type.types?.try &.each_value do |nested_type| - remove_duplicate_instance_vars_declarations(nested_type, duplicates_checked) + remove_duplicate_instance_vars_declarations(nested_type) end end diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index a2f4db47dee0..4c5988cccae5 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -251,13 +251,6 @@ class Crystal::Doc::Generator def collect_subtypes(parent) types = [] of Type - # AliasType has defined `types?` to be the types - # of the aliased type, but for docs we don't want - # to list the nested types for aliases. - if parent.is_a?(AliasType) - return types - end - parent.types?.try &.each_value do |type| case type when Const, LibType diff --git a/src/compiler/crystal/tools/typed_def_processor.cr b/src/compiler/crystal/tools/typed_def_processor.cr index a0a911a6a618..2ba2441d7902 100644 --- a/src/compiler/crystal/tools/typed_def_processor.cr +++ b/src/compiler/crystal/tools/typed_def_processor.cr @@ -17,15 +17,6 @@ module Crystal::TypedDefProcessor end private def process_type(type : Type) : Nil - # Avoid visiting circular hierarchies. There's no use in processing - # alias types anyway. - # For example: - # - # struct Foo - # alias Bar = Foo - # end - return if type.is_a?(AliasType) || type.is_a?(TypeDefType) - if type.is_a?(NamedType) || type.is_a?(Program) || type.is_a?(FileModule) type.types?.try &.each_value do |inner_type| process_type inner_type diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr index 8455a4186882..4ba681240385 100644 --- a/src/compiler/crystal/tools/unreachable.cr +++ b/src/compiler/crystal/tools/unreachable.cr @@ -155,15 +155,6 @@ module Crystal property excludes = [] of String def process_type(type) - # Avoid visiting circular hierarchies. There's no use in processing - # alias types anyway. - # For example: - # - # struct Foo - # alias Bar = Foo - # end - return if type.is_a?(AliasType) || type.is_a?(TypeDefType) - if type.is_a?(ModuleType) track_unused_defs type end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 5d903b763050..053f6c9d8ac3 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -373,6 +373,10 @@ module Crystal nil end + def lookup_name(name) + types?.try(&.[name]?) + end + def parents nil end @@ -2756,17 +2760,9 @@ module Crystal delegate lookup_defs, lookup_defs_with_modules, lookup_first_def, lookup_macro, lookup_macros, to: aliased_type - def types? + def lookup_name(name) process_value - if aliased_type = @aliased_type - aliased_type.types? - else - nil - end - end - - def types - types?.not_nil! + @aliased_type.try(&.lookup_name(name)) end def remove_alias From 59fdcf419c308e036a353c4e2ec7fe0d2496250d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 14 Oct 2024 17:11:30 +0800 Subject: [PATCH 1404/1551] Fix `find-llvm-config` to ignore `LLVM_CONFIG`'s escape sequences (#15076) If the `LLVM_CONFIG` environment variable is already set and points to a valid executable, percent signs and backslashes will be specially interpreted according to `printf`'s rules. This is most likely to happen on MSYS2, since the Windows host might have set `LLVM_CONFIG` to support static LLVM builds: ```sh $ LLVM_CONFIG='C:\Users\nicet\llvm\18\bin\llvm-config.exe' src/llvm/ext/find-llvm-config src/llvm/ext/find-llvm-config: line 19: printf: missing unicode digit for \U C:\Users icet\llvmin\llvm-config.exe ``` --- src/llvm/ext/find-llvm-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm/ext/find-llvm-config b/src/llvm/ext/find-llvm-config index 40be636e1b23..5f40a1f2e6a3 100755 --- a/src/llvm/ext/find-llvm-config +++ b/src/llvm/ext/find-llvm-config @@ -16,7 +16,7 @@ if ! LLVM_CONFIG=$(command -v "$LLVM_CONFIG"); then fi if [ "$LLVM_CONFIG" ]; then - printf "$LLVM_CONFIG" + printf %s "$LLVM_CONFIG" else printf "Error: Could not find location of llvm-config. Please specify path in environment variable LLVM_CONFIG.\n" >&2 printf "Supported LLVM versions: $(cat "$(dirname $0)/llvm-versions.txt" | sed 's/\.0//g')\n" >&2 From 9f3dba5c75e1ae463abf69c4090e5b83cf013104 Mon Sep 17 00:00:00 2001 From: kojix2 <2xijok@gmail.com> Date: Tue, 15 Oct 2024 18:58:58 +0900 Subject: [PATCH 1405/1551] Fix various typos (#15080) --- spec/std/http/request_spec.cr | 2 +- spec/std/io/delimited_spec.cr | 2 +- spec/std/regex_spec.cr | 2 +- spec/std/time/span_spec.cr | 4 ++-- spec/std/yaml/serializable_spec.cr | 2 +- src/channel/select.cr | 2 +- src/compiler/crystal/types.cr | 4 ++-- src/crystal/system/win32/event_loop_iocp.cr | 2 +- src/docs_pseudo_methods.cr | 2 +- src/file.cr | 2 +- src/gc/none.cr | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/std/http/request_spec.cr b/spec/std/http/request_spec.cr index f997ca8998bc..1a378a39d20a 100644 --- a/spec/std/http/request_spec.cr +++ b/spec/std/http/request_spec.cr @@ -454,7 +454,7 @@ module HTTP request.form_params["test"].should eq("foobar") end - it "returns ignors invalid content-type" do + it "ignores invalid content-type" do request = Request.new("POST", "/form", nil, HTTP::Params.encode({"test" => "foobar"})) request.form_params?.should eq(nil) request.form_params.size.should eq(0) diff --git a/spec/std/io/delimited_spec.cr b/spec/std/io/delimited_spec.cr index b41af9ee5fdb..c1e06bf40dc0 100644 --- a/spec/std/io/delimited_spec.cr +++ b/spec/std/io/delimited_spec.cr @@ -259,7 +259,7 @@ describe "IO::Delimited" do io.gets_to_end.should eq("hello") end - it "handles the case of peek matching first byte, not having enough room, but later not matching (limted slice)" do + it "handles the case of peek matching first byte, not having enough room, but later not matching (limited slice)" do # not a delimiter # --- io = MemoryIOWithFixedPeek.new("abcdefgwijkfghhello") diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index af03cb2c79b8..230976d6ad3e 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -433,7 +433,7 @@ describe "Regex" do }) end - it "alpanumeric" do + it "alphanumeric" do /(?)/.name_table.should eq({1 => "f1"}) end diff --git a/spec/std/time/span_spec.cr b/spec/std/time/span_spec.cr index f9c1dd83f04f..ec49e38651cc 100644 --- a/spec/std/time/span_spec.cr +++ b/spec/std/time/span_spec.cr @@ -360,7 +360,7 @@ describe Time::Span do 1.1.weeks.should eq(7.7.days) end - it "can substract big amount using microseconds" do + it "can subtract big amount using microseconds" do jan_1_2k = Time.utc(2000, 1, 1) past = Time.utc(5, 2, 3, 0, 0, 0) delta = (past - jan_1_2k).total_microseconds.to_i64 @@ -368,7 +368,7 @@ describe Time::Span do past2.should eq(past) end - it "can substract big amount using milliseconds" do + it "can subtract big amount using milliseconds" do jan_1_2k = Time.utc(2000, 1, 1) past = Time.utc(5, 2, 3, 0, 0, 0) delta = (past - jan_1_2k).total_milliseconds.to_i64 diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 7d13f4318350..a48f0c754425 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -1001,7 +1001,7 @@ describe "YAML::Serializable" do yaml = YAMLAttrWithPresenceAndIgnoreSerialize.from_yaml(%({"last_name": null})) yaml.last_name_present?.should be_true - # libyaml 0.2.5 removes traling space for empty scalar nodes + # libyaml 0.2.5 removes trailing space for empty scalar nodes if YAML.libyaml_version >= SemanticVersion.new(0, 2, 5) yaml.to_yaml.should eq("---\nlast_name:\n") else diff --git a/src/channel/select.cr b/src/channel/select.cr index 5628fd460e6e..05db47a79a4c 100644 --- a/src/channel/select.cr +++ b/src/channel/select.cr @@ -142,7 +142,7 @@ class Channel(T) private def self.each_skip_duplicates(ops_locks, &) # Avoid deadlocks from trying to lock the same lock twice. - # `ops_lock` is sorted by `lock_object_id`, so identical onces will be in + # `ops_lock` is sorted by `lock_object_id`, so identical ones will be in # a row and we skip repeats while iterating. last_lock_id = nil ops_locks.each do |op| diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 053f6c9d8ac3..3a2a759b3158 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -1393,10 +1393,10 @@ module Crystal # Float64 mantissa has 52 bits case kind when .i8?, .u8?, .i16?, .u16? - # Less than 23 bits, so convertable to Float32 and Float64 without precision loss + # Less than 23 bits, so convertible to Float32 and Float64 without precision loss true when .i32?, .u32? - # Less than 52 bits, so convertable to Float64 without precision loss + # Less than 52 bits, so convertible to Float64 without precision loss other_type.kind.f64? else false diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr index ade1862a780c..3089e36edfeb 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/system/win32/event_loop_iocp.cr @@ -76,7 +76,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop # Wait for completion timed out but it may have been interrupted or we ask # for immediate timeout (nonblocking), so we check for the next event - # readyness again: + # readiness again: return false if next_event.wake_at > Time.monotonic end diff --git a/src/docs_pseudo_methods.cr b/src/docs_pseudo_methods.cr index 36eb1f09eaff..d789f4a9ecc8 100644 --- a/src/docs_pseudo_methods.cr +++ b/src/docs_pseudo_methods.cr @@ -227,6 +227,6 @@ end struct CRYSTAL_PSEUDO__NoReturn end -# Similar in usage to `Nil`. `Void` is prefered for C lib bindings. +# Similar in usage to `Nil`. `Void` is preferred for C lib bindings. struct CRYSTAL_PSEUDO__Void end diff --git a/src/file.cr b/src/file.cr index 5169a6dc703d..1d12a01f4209 100644 --- a/src/file.cr +++ b/src/file.cr @@ -165,7 +165,7 @@ class File < IO::FileDescriptor # *blocking* must be set to `false` on POSIX targets when the file to open # isn't a regular file but a character device (e.g. `/dev/tty`) or fifo. These # files depend on another process or thread to also be reading or writing, and - # system event queues will properly report readyness. + # system event queues will properly report readiness. # # *blocking* may also be set to `nil` in which case the blocking or # non-blocking flag will be determined automatically, at the expense of an diff --git a/src/gc/none.cr b/src/gc/none.cr index 3943bd265ed9..ce84027e6e69 100644 --- a/src/gc/none.cr +++ b/src/gc/none.cr @@ -154,7 +154,7 @@ module GC # will block until it can acquire the lock). # # In both cases there can't be a deadlock since we won't suspend another - # thread until it has successfuly added (or removed) itself to (from) the + # thread until it has successfully added (or removed) itself to (from) the # linked list and released the lock, and the other thread won't progress until # it can add (or remove) itself from the list. # From 5fd469cc2467a2ac466da2d2a2985b0ffca9a339 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Tue, 15 Oct 2024 13:25:22 -0400 Subject: [PATCH 1406/1551] Improve man and shell completion for tools (#15082) --- etc/completion.bash | 2 +- etc/completion.fish | 17 ++++++++++++++- etc/completion.zsh | 38 ++++++++++++++++++++++++++++----- man/crystal.1 | 4 ++-- src/compiler/crystal/command.cr | 4 ++-- 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/etc/completion.bash b/etc/completion.bash index 9263289b5b4e..b64bd110a205 100644 --- a/etc/completion.bash +++ b/etc/completion.bash @@ -66,7 +66,7 @@ _crystal() _crystal_compgen_options "${opts}" "${cur}" else if [[ "${prev}" == "tool" ]] ; then - local subcommands="context dependencies flags format hierarchy implementations types" + local subcommands="context dependencies expand flags format hierarchy implementations types unreachable" _crystal_compgen_options "${subcommands}" "${cur}" else _crystal_compgen_sources "${cur}" diff --git a/etc/completion.fish b/etc/completion.fish index 64fc6a97b45a..a74d6ecf3cac 100644 --- a/etc/completion.fish +++ b/etc/completion.fish @@ -1,5 +1,5 @@ set -l crystal_commands init build clear_cache docs env eval i interactive play run spec tool help version -set -l tool_subcommands context expand flags format hierarchy implementations types +set -l tool_subcommands context dependencies expand flags format hierarchy implementations types unreachable complete -c crystal -s h -l help -d "Show help" -x @@ -206,6 +206,21 @@ complete -c crystal -n "__fish_seen_subcommand_from implementations" -s p -l pro complete -c crystal -n "__fish_seen_subcommand_from implementations" -s t -l time -d "Enable execution time output" complete -c crystal -n "__fish_seen_subcommand_from implementations" -l stdin-filename -d "Source file name to be read from STDIN" +complete -c crystal -n "__fish_seen_subcommand_from tool; and not __fish_seen_subcommand_from $tool_subcommands" -a "unreachable" -d "show methods that are never called" -x +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s D -l define -d "Define a compile-time flag" +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s f -l format -d "Output format text (default), json, csv, codecov" -a "text json csv codecov" -f +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l tallies -d "Print reachable methods and their call counts as well" +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l check -d "Exits with error if there is any unreachable code" +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l error-trace -d "Show full error trace" +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s i -l include -d "Include path" +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s e -l exclude -d "Exclude path (default: lib)" +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l no-color -d "Disable colored output" +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l prelude -d "Use given file as prelude" +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s s -l stats -d "Enable statistics output" +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s p -l progress -d "Enable progress output" +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s t -l time -d "Enable execution time output" +complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l stdin-filename -d "Source file name to be read from STDIN" + complete -c crystal -n "__fish_seen_subcommand_from tool; and not __fish_seen_subcommand_from $tool_subcommands" -a "types" -d "show type of main variables" -x complete -c crystal -n "__fish_seen_subcommand_from types" -s D -l define -d "Define a compile-time flag" complete -c crystal -n "__fish_seen_subcommand_from types" -s f -l format -d "Output format text (default) or json" -a "text json" -f diff --git a/etc/completion.zsh b/etc/completion.zsh index ffa12798ca18..0d9ff58a67c2 100644 --- a/etc/completion.zsh +++ b/etc/completion.zsh @@ -41,7 +41,8 @@ local -a exec_args; exec_args=( '(\*)'{-D+,--define=}'[define a compile-time flag]:' \ '(--error-trace)--error-trace[show full error trace]' \ '(-s --stats)'{-s,--stats}'[enable statistics output]' \ - '(-t --time)'{-t,--time}'[enable execution time output]' + '(-t --time)'{-t,--time}'[enable execution time output]' \ + '(-p --progress)'{-p,--progress}'[enable progress output]' ) local -a format_args; format_args=( @@ -61,11 +62,15 @@ local -a cursor_args; cursor_args=( '(-c --cursor)'{-c,--cursor}'[cursor location with LOC as path/to/file.cr:line:column]:LOC' ) -local -a include_exclude_args; cursor_args=( +local -a include_exclude_args; include_exclude_args=( '(-i --include)'{-i,--include}'[Include path in output]' \ '(-i --exclude)'{-i,--exclude}'[Exclude path in output]' ) +local -a stdin_filename_args; stdin_filename_args=( + '(--stdin-filename)--stdin-filename[source file name to be read from STDIN]' +) + local -a programfile; programfile='*:Crystal File:_files -g "*.cr(.)"' # TODO make 'emit' allow completion with more than one @@ -170,6 +175,7 @@ _crystal-tool() { "hierarchy:show type hierarchy" "implementations:show implementations for given call in location" "types:show type of main variables" + "unreachable:show methods that are never called" ) _describe -t commands 'Crystal tool command' commands @@ -187,6 +193,7 @@ _crystal-tool() { $exec_args \ $format_args \ $prelude_args \ + $stdin_filename_args \ $cursor_args ;; @@ -198,6 +205,7 @@ _crystal-tool() { $exec_args \ '(-f --format)'{-f,--format}'[output format 'tree' (default), 'flat', 'dot', or 'mermaid']:' \ $prelude_args \ + $stdin_filename_args \ $include_exclude_args ;; @@ -209,12 +217,14 @@ _crystal-tool() { $exec_args \ $format_args \ $prelude_args \ + $stdin_filename_args \ $cursor_args ;; (flags) _arguments \ $programfile \ + $no_color_args \ $help_args ;; @@ -223,8 +233,9 @@ _crystal-tool() { $programfile \ $help_args \ $no_color_args \ - $format_args \ + $include_exclude_args \ '(--check)--check[checks that formatting code produces no changes]' \ + '(--show-backtrace)--show-backtrace[show backtrace on a bug (used only for debugging)]' ;; (hierarchy) @@ -235,6 +246,7 @@ _crystal-tool() { $exec_args \ $format_args \ $prelude_args \ + $stdin_filename_args \ '(-e)-e[filter types by NAME regex]:' ;; @@ -246,7 +258,22 @@ _crystal-tool() { $exec_args \ $format_args \ $prelude_args \ - $cursor_args + $cursor_args \ + $stdin_filename_args + ;; + + (unreachable) + _arguments \ + $programfile \ + $help_args \ + $no_color_args \ + $exec_args \ + $include_exclude_args \ + '(-f --format)'{-f,--format}'[output format: text (default), json, csv, codecov]:' \ + $prelude_args \ + '(--check)--check[exits with error if there is any unreachable code]' \ + '(--tallies)--tallies[print reachable methods and their call counts as well]' \ + $stdin_filename_args ;; (types) @@ -256,7 +283,8 @@ _crystal-tool() { $no_color_args \ $exec_args \ $format_args \ - $prelude_args + $prelude_args \ + $stdin_filename_args ;; esac ;; diff --git a/man/crystal.1 b/man/crystal.1 index 04f183dd11e3..9134b8fcc8ef 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -369,7 +369,7 @@ Disable colored output. .Op -- .Op arguments .Pp -Run a tool. The available tools are: context, dependencies, flags, format, hierarchy, implementations, and types. +Run a tool. The available tools are: context, dependencies, expand, flags, format, hierarchy, implementations, types, and unreachable. .Pp Tools: .Bl -tag -offset indent @@ -442,7 +442,7 @@ Options: .It Fl D Ar FLAG, Fl -define= Ar FLAG Define a compile-time flag. This is useful to conditionally define types, methods, or commands based on flags available at compile time. The default flags are from the target triple given with --target-triple or the hosts default, if none is given. .It Fl f Ar FORMAT, Fl -format= Ar FORMAT -Output format 'text' (default), 'json', or 'csv'. +Output format 'text' (default), 'json', 'codecov', or 'csv'. .It Fl -tallies Print reachable methods and their call counts as well. .It Fl -check diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 1354594706fb..571c965352e0 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -40,14 +40,14 @@ class Crystal::Command Tool: context show context for given location + dependencies show file dependency tree expand show macro expansion for given location flags print all macro `flag?` values format format project, directories and/or files hierarchy show type hierarchy - dependencies show file dependency tree implementations show implementations for given call in location - unreachable show methods that are never called types show type of main variables + unreachable show methods that are never called --help, -h show this help USAGE From a088858197d04414b27dce8e48b6ba9a0f94d9c9 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 18 Oct 2024 14:49:00 +0800 Subject: [PATCH 1407/1551] Fix `Complex#/` edge cases (#15086) --- spec/std/complex_spec.cr | 6 ++++++ src/complex.cr | 26 ++++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/spec/std/complex_spec.cr b/spec/std/complex_spec.cr index 65add18f8533..2b90239d0796 100644 --- a/spec/std/complex_spec.cr +++ b/spec/std/complex_spec.cr @@ -265,6 +265,12 @@ describe "Complex" do it "complex / complex" do ((Complex.new(4, 6.2))/(Complex.new(0.5, 2.7))).should eq(Complex.new(2.485411140583554, -1.0212201591511936)) ((Complex.new(4.1, 6.0))/(Complex.new(10, 2.2))).should eq(Complex.new(0.5169782525753529, 0.48626478443342236)) + + (1.to_c / -1.to_c).should eq(-1.to_c) + assert_complex_nan 1.to_c / Float64::NAN + + (1.to_c / 0.to_c).real.abs.should eq(Float64::INFINITY) + (1.to_c / 0.to_c).imag.nan?.should be_true end it "complex / number" do diff --git a/src/complex.cr b/src/complex.cr index 65fbc9204b59..e2a5830b395a 100644 --- a/src/complex.cr +++ b/src/complex.cr @@ -237,14 +237,28 @@ struct Complex # Divides `self` by *other*. def /(other : Complex) : Complex - if other.real <= other.imag - r = other.real / other.imag - d = other.imag + r * other.real - Complex.new((@real * r + @imag) / d, (@imag * r - @real) / d) - else + if other.real.nan? || other.imag.nan? + Complex.new(Float64::NAN, Float64::NAN) + elsif other.imag.abs < other.real.abs r = other.imag / other.real d = other.real + r * other.imag - Complex.new((@real + @imag * r) / d, (@imag - @real * r) / d) + + if d.nan? || d == 0 + Complex.new(Float64::NAN, Float64::NAN) + else + Complex.new((@real + @imag * r) / d, (@imag - @real * r) / d) + end + elsif other.imag == 0 # other.real == 0 + Complex.new(@real / other.real, @imag / other.real) + else # 0 < other.real.abs <= other.imag.abs + r = other.real / other.imag + d = other.imag + r * other.real + + if d.nan? || d == 0 + Complex.new(Float64::NAN, Float64::NAN) + else + Complex.new((@real * r + @imag) / d, (@imag * r - @real) / d) + end end end From 126f037f9278f705666737807b736d67ed42e38b Mon Sep 17 00:00:00 2001 From: Lachlan Dowding Date: Fri, 18 Oct 2024 16:49:15 +1000 Subject: [PATCH 1408/1551] Fix `Number#humanize` printing of `(-)Infinity` and `NaN` (#15090) --- spec/std/humanize_spec.cr | 8 ++++++++ src/humanize.cr | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/spec/std/humanize_spec.cr b/spec/std/humanize_spec.cr index c909417aca36..d24d2017cb28 100644 --- a/spec/std/humanize_spec.cr +++ b/spec/std/humanize_spec.cr @@ -207,6 +207,14 @@ describe Number do it { assert_prints 1.0e+34.humanize, "10,000Q" } it { assert_prints 1.0e+35.humanize, "100,000Q" } + it { assert_prints Float32::INFINITY.humanize, "Infinity" } + it { assert_prints (-Float32::INFINITY).humanize, "-Infinity" } + it { assert_prints Float32::NAN.humanize, "NaN" } + + it { assert_prints Float64::INFINITY.humanize, "Infinity" } + it { assert_prints (-Float64::INFINITY).humanize, "-Infinity" } + it { assert_prints Float64::NAN.humanize, "NaN" } + it { assert_prints 1_234.567_890_123.humanize(precision: 2, significant: false), "1.23k" } it { assert_prints 123.456_789_012_3.humanize(precision: 2, significant: false), "123.46" } it { assert_prints 12.345_678_901_23.humanize(precision: 2, significant: false), "12.35" } diff --git a/src/humanize.cr b/src/humanize.cr index bb285fe3a07d..db9d84c64889 100644 --- a/src/humanize.cr +++ b/src/humanize.cr @@ -216,7 +216,7 @@ struct Number # # See `Int#humanize_bytes` to format a file size. def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, &prefixes : (Int32, Float64) -> {Int32, _} | {Int32, _, Bool}) : Nil - if zero? + if zero? || (responds_to?(:infinite?) && self.infinite?) || (responds_to?(:nan?) && self.nan?) digits = 0 else log = Math.log10(abs) From fa2583815e2b60be34778c127d2eae33cf7aa246 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sat, 19 Oct 2024 11:34:25 +0200 Subject: [PATCH 1409/1551] Fix failing specs on FreeBSD (#15093) --- spec/std/signal_spec.cr | 24 ++++++++++++++---------- spec/std/socket/socket_spec.cr | 23 +++++++++++++---------- spec/std/socket/udp_socket_spec.cr | 3 +++ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr index 969e4dc3d742..e27373e3be21 100644 --- a/spec/std/signal_spec.cr +++ b/spec/std/signal_spec.cr @@ -19,44 +19,48 @@ pending_interpreted describe: "Signal" do end {% unless flag?(:win32) %} + # can't use SIGUSR1/SIGUSR2 on FreeBSD because Boehm uses them to suspend/resume threads + signal1 = {% if flag?(:freebsd) %} Signal.new(LibC::SIGRTMAX - 1) {% else %} Signal::USR1 {% end %} + signal2 = {% if flag?(:freebsd) %} Signal.new(LibC::SIGRTMAX - 2) {% else %} Signal::USR2 {% end %} + it "runs a signal handler" do ran = false - Signal::USR1.trap do + signal1.trap do ran = true end - Process.signal Signal::USR1, Process.pid + Process.signal signal1, Process.pid 10.times do |i| break if ran sleep 0.1.seconds end ran.should be_true ensure - Signal::USR1.reset + signal1.reset end it "ignores a signal" do - Signal::USR2.ignore - Process.signal Signal::USR2, Process.pid + signal2.ignore + Process.signal signal2, Process.pid end it "allows chaining of signals" do ran_first = false ran_second = false - Signal::USR1.trap { ran_first = true } - existing = Signal::USR1.trap_handler? + signal1.trap { ran_first = true } + existing = signal1.trap_handler? - Signal::USR1.trap do |signal| + signal1.trap do |signal| existing.try &.call(signal) ran_second = true end - Process.signal Signal::USR1, Process.pid + Process.signal signal1, Process.pid sleep 0.1.seconds ran_first.should be_true ran_second.should be_true ensure - Signal::USR1.reset + signal1.reset end it "CHLD.reset sets default Crystal child handler" do diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index f4ff7c90972b..65f7ed72a453 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -24,16 +24,19 @@ describe Socket, tags: "network" do sock.type.should eq(Socket::Type::DGRAM) {% end %} - error = expect_raises(Socket::Error) do - TCPSocket.new(family: :unix) - end - error.os_error.should eq({% if flag?(:win32) %} - WinError::WSAEPROTONOSUPPORT - {% elsif flag?(:wasi) %} - WasiError::PROTONOSUPPORT - {% else %} - Errno.new(LibC::EPROTONOSUPPORT) - {% end %}) + {% unless flag?(:freebsd) %} + # for some reason this doesn't fail on freebsd + error = expect_raises(Socket::Error) do + TCPSocket.new(family: :unix) + end + error.os_error.should eq({% if flag?(:win32) %} + WinError::WSAEPROTONOSUPPORT + {% elsif flag?(:wasi) %} + WasiError::PROTONOSUPPORT + {% else %} + Errno.new(LibC::EPROTONOSUPPORT) + {% end %}) + {% end %} end end diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr index 113a4ea3cf61..6e4b607b80ea 100644 --- a/spec/std/socket/udp_socket_spec.cr +++ b/spec/std/socket/udp_socket_spec.cr @@ -82,6 +82,9 @@ describe UDPSocket, tags: "network" do # TODO: figure out why updating `multicast_loopback` produces a # `setsockopt 18: Invalid argument` error pending "joins and transmits to multicast groups" + elsif {{ flag?(:freebsd) }} && family == Socket::Family::INET6 + # FIXME: fails with "Error sending datagram to [ipv6]:port: Network is unreachable" + pending "joins and transmits to multicast groups" else it "joins and transmits to multicast groups" do udp = UDPSocket.new(family) From 3a78e8a34e984d0d821728c830e9513a72270364 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 19 Oct 2024 17:36:35 +0800 Subject: [PATCH 1410/1551] Support building from a MinGW-w64-based compiler (#15077) This is a continuation of #15070 that allows a compiler built with MinGW-w64 to itself build programs correctly. Resolves part of #6170. * Because linker flags for GCC may now be executed on a Windows environment, we use the correct form of argument quoting. We also drop `-rdynamic` since that only makes sense for ELF executables. * Targetting `x86_64-windows-gnu`, including normal compilations from such a Crystal compiler, will not copy dependent DLLs to the output directory. Crystal itself and programs built under MSYS2 will just work as long as the proper environment is used. You are on your own here, although `ldd` exists on MSYS2 so that you don't need the MSVC build tools for this. * The correct GCC compiler flag to select `wmain` over `main` as the C entry point is `-municode`. (The system entry point is presumably `_start` now.) * `legacy_stdio_definitions.obj` doesn't exist on MinGW-w64, so we disable it outside MSVC. * For build command lines that are too long on Windows, we use GCC's response file support. To build a MinGW-w64 compiler: ```cmd @REM on the MSVC developer prompt make -fMakefile.win crystal bin\crystal build --cross-compile --target=x86_64-windows-gnu src\compiler\crystal.cr -Dwithout_interpreter ``` ```sh # on MSYS2's UCRT64 environment pacman -Sy \ mingw-w64-ucrt-x86_64-gc mingw-w64-ucrt-x86_64-pcre2 mingw-w64-ucrt-x86_64-libiconv \ mingw-w64-ucrt-x86_64-zlib mingw-w64-ucrt-x86_64-openssl mingw-w64-ucrt-x86_64-llvm cc crystal.obj -o crystal \ $(pkg-config bdw-gc libpcre2-8 iconv zlib openssl --libs) \ $(llvm-config --libs --system-libs --ldflags) \ -lDbgHelp -lole32 -lWS2_32 export CRYSTAL_PATH='lib;$ORIGIN\src' export CRYSTAL_LIBRARY_PATH='' ``` Now you can run or build a considerable number of files from here, such as `./crystal.exe samples/2048.cr` and `./crystal.exe spec spec/std/regex_spec.cr`. Notable omissions are OpenSSL and LLVM, as fixing their version detection macros is a bit complicated. The interpreter is not supported. Most likely, `Crystal::Loader` would have a GCC-style `.parse`, but the rest of the functionality would be identical to the MSVC `LoadLibraryExW`-based loader. ~~Also, some invocations like `./crystal.exe spec spec/std/json` will fail since the whole command line string is too long. Similar to MSVC, [GCC also handles response files starting with `@`](https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html), so this can be implemented later; a workaround is to use `--single-module`.~~ For reference, here are all the useful MSYS2 packages and their corresponding pkg-config names: | MSYS2 package name | pkg-config name | |-|-| | mingw-w64-ucrt-x86_64-gc | bdw-gc | | mingw-w64-ucrt-x86_64-pcre2 | libpcre2-8 | | mingw-w64-ucrt-x86_64-libiconv | iconv | | mingw-w64-ucrt-x86_64-gmp | gmp | | mingw-w64-ucrt-x86_64-zlib | zlib | | mingw-w64-ucrt-x86_64-libxml2 | libxml-2.0 | | mingw-w64-ucrt-x86_64-libyaml | yaml-0.1 | | mingw-w64-ucrt-x86_64-openssl | openssl | | mingw-w64-ucrt-x86_64-libffi | libffi | | mingw-w64-ucrt-x86_64-llvm | _(use llvm-config instead)_ | --- src/compiler/crystal/codegen/link.cr | 26 +++++--- src/compiler/crystal/compiler.cr | 78 ++++++++++++++++-------- src/crystal/system/win32/wmain.cr | 7 ++- src/lib_c/x86_64-windows-msvc/c/stdio.cr | 4 +- 4 files changed, 80 insertions(+), 35 deletions(-) diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index 33248e93e19b..b2b827916cbf 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -120,18 +120,18 @@ module Crystal end class Program - def lib_flags - has_flag?("msvc") ? lib_flags_windows : lib_flags_posix + def lib_flags(cross_compiling : Bool = false) + has_flag?("msvc") ? lib_flags_windows(cross_compiling) : lib_flags_posix(cross_compiling) end - private def lib_flags_windows + private def lib_flags_windows(cross_compiling) flags = [] of String # Add CRYSTAL_LIBRARY_PATH locations, so the linker preferentially # searches user-given library paths. if has_flag?("msvc") CrystalLibraryPath.paths.each do |path| - flags << Process.quote_windows("/LIBPATH:#{path}") + flags << quote_flag("/LIBPATH:#{path}", cross_compiling) end end @@ -141,14 +141,14 @@ module Crystal end if libname = ann.lib - flags << Process.quote_windows("#{libname}.lib") + flags << quote_flag("#{libname}.lib", cross_compiling) end end flags.join(" ") end - private def lib_flags_posix + private def lib_flags_posix(cross_compiling) flags = [] of String static_build = has_flag?("static") @@ -158,7 +158,7 @@ module Crystal # Add CRYSTAL_LIBRARY_PATH locations, so the linker preferentially # searches user-given library paths. CrystalLibraryPath.paths.each do |path| - flags << Process.quote_posix("-L#{path}") + flags << quote_flag("-L#{path}", cross_compiling) end link_annotations.reverse_each do |ann| @@ -173,17 +173,25 @@ module Crystal elsif (lib_name = ann.lib) && (flag = pkg_config(lib_name, static_build)) flags << flag elsif (lib_name = ann.lib) - flags << Process.quote_posix("-l#{lib_name}") + flags << quote_flag("-l#{lib_name}", cross_compiling) end if framework = ann.framework - flags << "-framework" << Process.quote_posix(framework) + flags << "-framework" << quote_flag(framework, cross_compiling) end end flags.join(" ") end + private def quote_flag(flag, cross_compiling) + if cross_compiling + has_flag?("windows") ? Process.quote_windows(flag) : Process.quote_posix(flag) + else + Process.quote(flag) + end + end + # Searches among CRYSTAL_LIBRARY_PATH, the compiler's directory, and PATH # for every DLL specified in the used `@[Link]` annotations. Yields the # absolute path and `true` if found, the base name and `false` if not found. diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index f620fe2fb312..aa11ef1dc47e 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -354,7 +354,7 @@ module Crystal run_dsymutil(output_filename) unless debug.none? {% end %} - {% if flag?(:windows) %} + {% if flag?(:msvc) %} copy_dlls(program, output_filename) unless static? {% end %} end @@ -424,26 +424,8 @@ module Crystal private def linker_command(program : Program, object_names, output_filename, output_dir, expand = false) if program.has_flag? "msvc" - lib_flags = program.lib_flags - # Execute and expand `subcommands`. - if expand - lib_flags = lib_flags.gsub(/`(.*?)`/) do - command = $1 - begin - error_io = IO::Memory.new - output = Process.run(command, shell: true, output: :pipe, error: error_io) do |process| - process.output.gets_to_end - end - unless $?.success? - error_io.rewind - error "Error executing subcommand for linker flags: #{command.inspect}: #{error_io}" - end - output - rescue exc - error "Error executing subcommand for linker flags: #{command.inspect}: #{exc}" - end - end - end + lib_flags = program.lib_flags(@cross_compile) + lib_flags = expand_lib_flags(lib_flags) if expand object_arg = Process.quote_windows(object_names) output_arg = Process.quote_windows("/Fe#{output_filename}") @@ -487,15 +469,63 @@ module Crystal {linker, cmd, nil} elsif program.has_flag? "wasm32" link_flags = @link_flags || "" - {"wasm-ld", %(wasm-ld "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} -lc #{program.lib_flags}), object_names} + {"wasm-ld", %(wasm-ld "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} -lc #{program.lib_flags(@cross_compile)}), object_names} elsif program.has_flag? "avr" link_flags = @link_flags || "" link_flags += " --target=avr-unknown-unknown -mmcu=#{@mcpu} -Wl,--gc-sections" - {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names} + {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags(@cross_compile)}), object_names} + elsif program.has_flag?("win32") && program.has_flag?("gnu") + link_flags = @link_flags || "" + lib_flags = program.lib_flags(@cross_compile) + lib_flags = expand_lib_flags(lib_flags) if expand + cmd = %(#{DEFAULT_LINKER} #{Process.quote_windows(object_names)} -o #{Process.quote_windows(output_filename)} #{link_flags} #{lib_flags}) + + if cmd.size > 32000 + # The command line would be too big, pass the args through a file instead. + # GCC response file does not interpret those args as shell-escaped + # arguments, we must rebuild the whole command line + args_filename = "#{output_dir}/linker_args.txt" + File.open(args_filename, "w") do |f| + object_names.each do |object_name| + f << object_name.gsub(GCC_RESPONSE_FILE_TR) << ' ' + end + f << "-o " << output_filename.gsub(GCC_RESPONSE_FILE_TR) << ' ' + f << link_flags << ' ' << lib_flags + end + cmd = "#{DEFAULT_LINKER} #{Process.quote_windows("@" + args_filename)}" + end + + {DEFAULT_LINKER, cmd, nil} else link_flags = @link_flags || "" link_flags += " -rdynamic" - {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names} + {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags(@cross_compile)}), object_names} + end + end + + private GCC_RESPONSE_FILE_TR = { + " ": %q(\ ), + "'": %q(\'), + "\"": %q(\"), + "\\": "\\\\", + } + + private def expand_lib_flags(lib_flags) + lib_flags.gsub(/`(.*?)`/) do + command = $1 + begin + error_io = IO::Memory.new + output = Process.run(command, shell: true, output: :pipe, error: error_io) do |process| + process.output.gets_to_end + end + unless $?.success? + error_io.rewind + error "Error executing subcommand for linker flags: #{command.inspect}: #{error_io}" + end + output.chomp + rescue exc + error "Error executing subcommand for linker flags: #{command.inspect}: #{exc}" + end end end diff --git a/src/crystal/system/win32/wmain.cr b/src/crystal/system/win32/wmain.cr index 71383c66a88a..3dd64f9c1b92 100644 --- a/src/crystal/system/win32/wmain.cr +++ b/src/crystal/system/win32/wmain.cr @@ -4,7 +4,12 @@ require "c/stdlib" {% begin %} # we have both `main` and `wmain`, so we must choose an unambiguous entry point - @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }}, ldflags: "/ENTRY:wmainCRTStartup")] + @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})] + {% if flag?(:msvc) %} + @[Link(ldflags: "/ENTRY:wmainCRTStartup")] + {% elsif flag?(:gnu) %} + @[Link(ldflags: "-municode")] + {% end %} {% end %} lib LibCrystalMain end diff --git a/src/lib_c/x86_64-windows-msvc/c/stdio.cr b/src/lib_c/x86_64-windows-msvc/c/stdio.cr index f23bba8503f6..ddfa97235d87 100644 --- a/src/lib_c/x86_64-windows-msvc/c/stdio.cr +++ b/src/lib_c/x86_64-windows-msvc/c/stdio.cr @@ -1,6 +1,8 @@ require "./stddef" -@[Link("legacy_stdio_definitions")] +{% if flag?(:msvc) %} + @[Link("legacy_stdio_definitions")] +{% end %} lib LibC # unused fun printf(format : Char*, ...) : Int From 95044dcb109b964917dc9594f20cd0af75c4d343 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 20 Oct 2024 03:08:54 +0800 Subject: [PATCH 1411/1551] Make `bin/crystal` work on MSYS2 (#15094) Normally, [MSYS2 automatically translates path lists](https://www.msys2.org/docs/filesystem-paths/) between Windows and Unix formats in the environment variables, as long as the variable looks like a Unix path list. You could try this even with an MSVC-built compiler: ```sh $ /c/crystal/crystal.exe env CRYSTAL_PATH lib;C:\crystal\src $ CRYSTAL_PATH='/c/crystal/src' /c/crystal/crystal.exe env CRYSTAL_PATH C:/crystal/src $ CRYSTAL_PATH='/c/crystal/src:/d/e:/foo/bar' /c/crystal/crystal.exe env CRYSTAL_PATH C:\crystal\src;D:\e;C:\msys64\foo\bar ``` `bin/crystal` defaults `CRYSTAL_PATH` to `lib:.../src` if it is not already set in the current environment. The problem is that `lib:.../src` doesn't look like a Unix path list [according to MSYS2](https://github.com/msys2/msys2-runtime/blob/2bfb7739dadf6a27f9b4c006adfd69944f3df2f1/winsup/cygwin/msys2_path_conv.cc#L339), so no conversion is done: ```sh $ CRYSTAL_PATH='lib:/c/Users/nicet/crystal/crystal/src' /c/crystal/crystal.exe env CRYSTAL_PATH lib:/c/Users/nicet/crystal/crystal/src ``` Turning the `lib` into `./lib` seems to trick MSYS2 into performing the conversion: ```sh $ CRYSTAL_PATH='./lib:/c/Users/nicet/crystal/crystal/src' /c/crystal/crystal.exe env CRYSTAL_PATH .\lib;C:\Users\nicet\crystal\crystal\src ``` Cygwin does not appear to do this automatically and one probably has to use `cygpath -p` directly. --- bin/crystal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/crystal b/bin/crystal index 3f7ceb1b88f4..2282cbefec80 100755 --- a/bin/crystal +++ b/bin/crystal @@ -137,7 +137,7 @@ SCRIPT_ROOT="$(dirname "$SCRIPT_PATH")" CRYSTAL_ROOT="$(dirname "$SCRIPT_ROOT")" CRYSTAL_DIR="$CRYSTAL_ROOT/.build" -export CRYSTAL_PATH="${CRYSTAL_PATH:-lib:$CRYSTAL_ROOT/src}" +export CRYSTAL_PATH="${CRYSTAL_PATH:-./lib:$CRYSTAL_ROOT/src}" if [ -n "${CRYSTAL_PATH##*"$CRYSTAL_ROOT"/src*}" ]; then __warning_msg "CRYSTAL_PATH env variable does not contain $CRYSTAL_ROOT/src" fi From 796a0d2cfdeac49b372ae53550010ede226ee4f8 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 20 Oct 2024 03:09:07 +0800 Subject: [PATCH 1412/1551] Fix libiconv build on Windows (#15095) --- etc/win-ci/cygwin-build-iconv.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/etc/win-ci/cygwin-build-iconv.sh b/etc/win-ci/cygwin-build-iconv.sh index a8507542e646..204427be66fa 100644 --- a/etc/win-ci/cygwin-build-iconv.sh +++ b/etc/win-ci/cygwin-build-iconv.sh @@ -23,8 +23,12 @@ else export CXXFLAGS="-MT" enable_shared=no enable_static=yes + # GNU libiconv appears to define `BUILDING_DLL` unconditionally, so the static + # library contains `/EXPORT` directives that make any executable also export + # the iconv symbols, which we don't want + find . '(' -name '*.h' -or -name '*.h.build.in' ')' -print0 | xargs -0 -i sed -i 's/__declspec(dllexport)//' '{}' fi -export CPPFLAGS="-D_WIN32_WINNT=_WIN32_WINNT_WIN7 -I$(pwd)/iconv/include" +export CPPFLAGS="-O2 -D_WIN32_WINNT=_WIN32_WINNT_WIN7 -I$(pwd)/iconv/include" export LDFLAGS="-L$(pwd)/iconv/lib" ./configure --host=x86_64-w64-mingw32 --prefix="$(pwd)/iconv" --enable-shared="${enable_shared}" --enable-static="${enable_static}" From 57017f6de66d88436bd80862c23a1ed0bff69648 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sat, 19 Oct 2024 21:09:29 +0200 Subject: [PATCH 1413/1551] Fix Deadlock with parallel stop-world/fork calls in MT (#15096) Trying to stop the world from a thread while threads are forking leads to a deadlock situation in glibc (at least) because we disable the reception of *all* signals while we fork. Since the suspend and resume signals are handled directly and not processed through the event loop (for obvious reasons) we can safely keep these signals enabled. Apparently it's even safer. --- src/crystal/system/unix/process.cr | 7 +++++++ src/crystal/system/unix/pthread.cr | 19 ++++++++++++++++++- src/gc/boehm.cr | 14 ++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 420030f8ba53..0eb58231900e 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -176,7 +176,14 @@ struct Crystal::System::Process newmask = uninitialized LibC::SigsetT oldmask = uninitialized LibC::SigsetT + # block signals while we fork, so the child process won't forward signals it + # may receive to the parent through the signal pipe, but make sure to not + # block stop-the-world signals as it appears to create deadlocks in glibc + # for example; this is safe because these signal handlers musn't be + # registered through `Signal.trap` but directly through `sigaction`. LibC.sigfillset(pointerof(newmask)) + LibC.sigdelset(pointerof(newmask), System::Thread.sig_suspend) + LibC.sigdelset(pointerof(newmask), System::Thread.sig_resume) ret = LibC.pthread_sigmask(LibC::SIG_SETMASK, pointerof(newmask), pointerof(oldmask)) raise RuntimeError.from_errno("Failed to disable signals") unless ret == 0 diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index 50a0fc56e818..bbdfcbc3d41c 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -249,7 +249,8 @@ module Crystal::System::Thread end end - # the suspend/resume signals follow BDWGC + # the suspend/resume signals try to follow BDWGC but aren't exact (e.g. it may + # use SIGUSR1 and SIGUSR2 on FreeBSD instead of SIGRT). private SIG_SUSPEND = {% if flag?(:linux) %} @@ -266,6 +267,22 @@ module Crystal::System::Thread {% else %} LibC::SIGXCPU {% end %} + + def self.sig_suspend : ::Signal + if GC.responds_to?(:sig_suspend) + GC.sig_suspend + else + ::Signal.new(SIG_SUSPEND) + end + end + + def self.sig_resume : ::Signal + if GC.responds_to?(:sig_resume) + GC.sig_resume + else + ::Signal.new(SIG_RESUME) + end + end end # In musl (alpine) the calls to unwind API segfaults diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 41c0f43f2a8c..33d6466d792b 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -164,6 +164,8 @@ lib LibGC fun stop_world_external = GC_stop_world_external fun start_world_external = GC_start_world_external + fun get_suspend_signal = GC_get_suspend_signal : Int + fun get_thr_restart_signal = GC_get_thr_restart_signal : Int end module GC @@ -483,4 +485,16 @@ module GC def self.start_world : Nil LibGC.start_world_external end + + {% if flag?(:unix) %} + # :nodoc: + def self.sig_suspend : Signal + Signal.new(LibGC.get_suspend_signal) + end + + # :nodoc: + def self.sig_resume : Signal + Signal.new(LibGC.get_thr_restart_signal) + end + {% end %} end From 7cd5f738ed56aea8e1e9d08ac87f22630c273ee6 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Sun, 20 Oct 2024 07:46:08 -0400 Subject: [PATCH 1414/1551] Better handle explicit chunked encoding responses (#15092) --- spec/std/http/server/response_spec.cr | 9 +++++++++ src/http/server/response.cr | 6 ++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/spec/std/http/server/response_spec.cr b/spec/std/http/server/response_spec.cr index 99e462151f6b..c5d775e48b8d 100644 --- a/spec/std/http/server/response_spec.cr +++ b/spec/std/http/server/response_spec.cr @@ -76,6 +76,15 @@ describe HTTP::Server::Response do io.to_s.should eq("HTTP/1.1 304 Not Modified\r\nContent-Length: 5\r\n\r\n") end + it "allow explicitly configuring a `Transfer-Encoding` response" do + io = IO::Memory.new + response = Response.new(io) + response.headers["Transfer-Encoding"] = "chunked" + response.print "Hello" + response.close + io.to_s.should eq("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n0\r\n\r\n") + end + it "prints less then buffer's size" do io = IO::Memory.new response = Response.new(io) diff --git a/src/http/server/response.cr b/src/http/server/response.cr index 5c80b31cce00..4dd6968ac560 100644 --- a/src/http/server/response.cr +++ b/src/http/server/response.cr @@ -255,7 +255,9 @@ class HTTP::Server private def unbuffered_write(slice : Bytes) : Nil return if slice.empty? - unless response.wrote_headers? + if response.headers["Transfer-Encoding"]? == "chunked" + @chunked = true + elsif !response.wrote_headers? if response.version != "HTTP/1.0" && !response.headers.has_key?("Content-Length") response.headers["Transfer-Encoding"] = "chunked" @chunked = true @@ -289,7 +291,7 @@ class HTTP::Server status = response.status set_content_length = !(status.not_modified? || status.no_content? || status.informational?) - if !response.wrote_headers? && !response.headers.has_key?("Content-Length") && set_content_length + if !response.wrote_headers? && !response.headers.has_key?("Transfer-Encoding") && !response.headers.has_key?("Content-Length") && set_content_length response.content_length = @out_count end From 8f49ab909dd2c9bfcfe3c2a0745bfcbaa870fc5a Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Sun, 20 Oct 2024 13:11:17 -0400 Subject: [PATCH 1415/1551] Add location information to more MacroIf related nodes (#15100) The most significant addition is when you have a `MacroIf` node like: ```cr {% if 1 == 2 %} {{2 * 2}} {% end %} ``` The `then` block of it is parsed as an `Expressions` that consists of `[MaccroLiteral, MacroExpression, MacroLiteral]`. I added location information to the nested `MacroExpression` so it knows it's on line 2 instead of line 1, which is what you'd get if you did `node.then.location` since that's the location of the first `MacroLiteral`. This is essentially my workaround for https://github.com/crystal-lang/crystal/issues/14884#issuecomment-2423904262. This PR also handles `Begin` and `MacroIf` nodes when nested in another node. --- spec/compiler/parser/parser_spec.cr | 110 ++++++++++++++++++++++++++ src/compiler/crystal/syntax/parser.cr | 11 +-- 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index db69fa357d59..09569b88f003 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2696,6 +2696,116 @@ module Crystal location.line_number.should eq 10 end + it "sets the correct location for MacroExpressions in a MacroIf" do + parser = Parser.new(<<-CR) + {% if 1 == 2 %} + {{2 * 2}} + {% else %} + {% + 1 + 1 + 2 + 2 + %} + {% end %} + CR + + node = parser.parse.should be_a MacroIf + location = node.location.should_not be_nil + location.line_number.should eq 1 + location.column_number.should eq 3 + + then_node = node.then.should be_a Expressions + then_node_location = then_node.location.should_not be_nil + then_node_location.line_number.should eq 1 + then_node_location = then_node.end_location.should_not be_nil + then_node_location.line_number.should eq 3 + + then_node_location = then_node.expressions[1].location.should_not be_nil + then_node_location.line_number.should eq 2 + then_node_location = then_node.expressions[1].end_location.should_not be_nil + then_node_location.line_number.should eq 2 + + else_node = node.else.should be_a Expressions + else_node_location = else_node.location.should_not be_nil + else_node_location.line_number.should eq 3 + else_node_location = else_node.end_location.should_not be_nil + else_node_location.line_number.should eq 8 + + else_node = node.else.should be_a Expressions + else_node_location = else_node.expressions[1].location.should_not be_nil + else_node_location.line_number.should eq 4 + else_node_location = else_node.expressions[1].end_location.should_not be_nil + else_node_location.line_number.should eq 7 + end + + it "sets correct location of Begin within another node" do + parser = Parser.new(<<-CR) + macro finished + {% begin %} + {{2 * 2}} + {% + 1 + 1 + 2 + 2 + %} + {% end %} + end + CR + + node = parser.parse.should be_a Macro + node = node.body.should be_a Expressions + node = node.expressions[1].should be_a MacroIf + + location = node.location.should_not be_nil + location.line_number.should eq 2 + location = node.end_location.should_not be_nil + location.line_number.should eq 8 + end + + it "sets correct location of MacroIf within another node" do + parser = Parser.new(<<-CR) + macro finished + {% if false %} + {{2 * 2}} + {% + 1 + 1 + 2 + 2 + %} + {% end %} + end + CR + + node = parser.parse.should be_a Macro + node = node.body.should be_a Expressions + node = node.expressions[1].should be_a MacroIf + + location = node.location.should_not be_nil + location.line_number.should eq 2 + location = node.end_location.should_not be_nil + location.line_number.should eq 8 + end + + it "sets correct location of MacroIf (unless) within another node" do + parser = Parser.new(<<-CR) + macro finished + {% unless false %} + {{2 * 2}} + {% + 1 + 1 + 2 + 2 + %} + {% end %} + end + CR + + node = parser.parse.should be_a Macro + node = node.body.should be_a Expressions + node = node.expressions[1].should be_a MacroIf + + location = node.location.should_not be_nil + location.line_number.should eq 2 + location = node.end_location.should_not be_nil + location.line_number.should eq 8 + end + it "sets correct location of trailing ensure" do parser = Parser.new("foo ensure bar") node = parser.parse.as(ExceptionHandler) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 15bd221fd8b2..1f0b6160a363 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3213,7 +3213,7 @@ module Crystal when .macro_literal? pieces << MacroLiteral.new(@token.value.to_s).at(@token.location).at_end(token_end_location) when .macro_expression_start? - pieces << MacroExpression.new(parse_macro_expression) + pieces << MacroExpression.new(parse_macro_expression).at(@token.location).at_end(token_end_location) check_macro_expression_end skip_whitespace = check_macro_skip_whitespace when .macro_control_start? @@ -3341,6 +3341,7 @@ module Crystal end def parse_macro_control(start_location, macro_state = Token::MacroState.default) + location = @token.location next_token_skip_space_or_newline case @token.value @@ -3385,9 +3386,9 @@ module Crystal return MacroFor.new(vars, exp, body).at_end(token_end_location) when Keyword::IF - return parse_macro_if(start_location, macro_state) + return parse_macro_if(start_location, macro_state).at(location) when Keyword::UNLESS - return parse_macro_if(start_location, macro_state, is_unless: true) + return parse_macro_if(start_location, macro_state, is_unless: true).at(location) when Keyword::BEGIN next_token_skip_space check :OP_PERCENT_RCURLY @@ -3400,7 +3401,7 @@ module Crystal next_token_skip_space check :OP_PERCENT_RCURLY - return MacroIf.new(BoolLiteral.new(true), body).at_end(token_end_location) + return MacroIf.new(BoolLiteral.new(true), body).at(location).at_end(token_end_location) when Keyword::ELSE, Keyword::ELSIF, Keyword::END return nil when Keyword::VERBATIM @@ -3428,7 +3429,7 @@ module Crystal exps = parse_expressions @in_macro_expression = false - MacroExpression.new(exps, output: false).at_end(token_end_location) + MacroExpression.new(exps, output: false).at(location).at_end(token_end_location) end def parse_macro_if(start_location, macro_state, check_end = true, is_unless = false) From b10f1713997facff15a905249ecc78bcfeb8ca43 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 21 Oct 2024 19:58:46 +0800 Subject: [PATCH 1416/1551] Make `Makefile` work on MSYS2 (#15102) * Uses `llvm-config` instead of `llvm_VERSION` for the `x86_64-windows-gnu` target. Also invokes the `find-llvm-config` script using `sh` explicitly and ensures the script returns a Windows path. (This pretty much means a MinGW-w64-based compiler will require the entire MSYS2 environment to work.) * Increases the stack size from the default 2 MiB to 8 MiB, consistent with builds using the MSVC toolchain. * `Makefile` and the `bin/crystal` script now recognize `.build/crystal.exe` as the local compiler under MSYS2. * If `.build/crystal.exe` already exists and we are on Windows, `Makefile` now builds a temporary executable first, just like in `Makefile.win`. * Ensures `Makefile` doesn't interpret backslashes in `llvm-config.exe`'s path as escape sequences. * Adds a separate `install_dlls` target for installing the compiler's dependent DLLs to the given prefix's `bin` directory. This is only needed if the installation is to be distributed outside MSYS2. * Detects the presence of `lld` which can be installed using the `mingw-w64-ucrt-x86_64-lld` package. With this patch, cross-compiling from MSVC-based Crystal will no longer work until #15091 is merged. Instead it can be done from Linux, including WSL, assuming the host and target LLVM versions are identical (right now MSYS2 has 18.1.8): ```sh # on Linux make clean crystal && make -B target=x86_64-windows-gnu # on MSYS2's UCRT64 environment pacman -Sy \ mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-pkgconf \ mingw-w64-ucrt-x86_64-gc mingw-w64-ucrt-x86_64-pcre2 mingw-w64-ucrt-x86_64-libiconv \ mingw-w64-ucrt-x86_64-zlib mingw-w64-ucrt-x86_64-llvm cc .build/crystal.obj -o .build/crystal.exe \ $(pkg-config bdw-gc libpcre2-8 iconv zlib --libs) \ $(llvm-config --libs --system-libs --ldflags) \ -lDbgHelp -lole32 -lWS2_32 -Wl,--stack,0x800000 ``` --- Makefile | 44 +++++++++++++++++++++++--------- bin/crystal | 15 ++++++++--- src/compiler/crystal/compiler.cr | 1 + src/llvm/ext/find-llvm-config | 9 ++++++- src/llvm/lib_llvm.cr | 4 +-- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index e56a14a27c1c..b39c089bef99 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,8 @@ CRYSTAL_CONFIG_BUILD_COMMIT ?= $(shell git rev-parse --short HEAD 2> /dev/null) CRYSTAL_CONFIG_PATH := '$$ORIGIN/../share/crystal/src' CRYSTAL_VERSION ?= $(shell cat src/VERSION) SOURCE_DATE_EPOCH ?= $(shell (cat src/SOURCE_DATE_EPOCH || (git show -s --format=%ct HEAD || stat -c "%Y" Makefile || stat -f "%m" Makefile)) 2> /dev/null) -ifeq ($(shell command -v ld.lld >/dev/null && uname -s),Linux) +check_lld := command -v ld.lld >/dev/null && case "$$(uname -s)" in MINGW32*|MINGW64*|Linux) echo 1;; esac +ifeq ($(shell $(check_lld)),1) EXPORT_CC ?= CC="$(CC) -fuse-ld=lld" endif EXPORTS := \ @@ -60,11 +61,20 @@ EXPORTS_BUILD := \ CRYSTAL_CONFIG_LIBRARY_PATH=$(CRYSTAL_CONFIG_LIBRARY_PATH) SHELL = sh LLVM_CONFIG := $(shell src/llvm/ext/find-llvm-config) -LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell $(LLVM_CONFIG) --version 2> /dev/null)) +LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell "$(LLVM_CONFIG)" --version 2> /dev/null)) LLVM_EXT_DIR = src/llvm/ext LLVM_EXT_OBJ = $(LLVM_EXT_DIR)/llvm_ext.o CXXFLAGS += $(if $(debug),-g -O0) +# MSYS2 support (native Windows should use `Makefile.win` instead) +ifeq ($(OS),Windows_NT) + CRYSTAL_BIN := crystal.exe + WINDOWS := 1 +else + CRYSTAL_BIN := crystal + WINDOWS := +endif + DESTDIR ?= PREFIX ?= /usr/local BINDIR ?= $(DESTDIR)$(PREFIX)/bin @@ -74,9 +84,9 @@ DATADIR ?= $(DESTDIR)$(PREFIX)/share/crystal INSTALL ?= /usr/bin/install ifeq ($(or $(TERM),$(TERM),dumb),dumb) - colorize = $(shell printf >&2 "$1") + colorize = $(shell printf "%s" "$1" >&2) else - colorize = $(shell printf >&2 "\033[33m$1\033[0m\n") + colorize = $(shell printf "\033[33m%s\033[0m\n" "$1" >&2) endif DEPS = $(LLVM_EXT_OBJ) @@ -119,7 +129,7 @@ interpreter_spec: $(O)/interpreter_spec ## Run interpreter specs .PHONY: smoke_test smoke_test: ## Build specs as a smoke test -smoke_test: $(O)/std_spec $(O)/compiler_spec $(O)/crystal +smoke_test: $(O)/std_spec $(O)/compiler_spec $(O)/$(CRYSTAL_BIN) .PHONY: all_spec all_spec: $(O)/all_spec ## Run all specs (note: this builds a huge program; `test` recipe builds individual binaries and is recommended for reduced resource usage) @@ -136,7 +146,7 @@ docs: ## Generate standard library documentation cp -av doc/ docs/ .PHONY: crystal -crystal: $(O)/crystal ## Build the compiler +crystal: $(O)/$(CRYSTAL_BIN) ## Build the compiler .PHONY: deps llvm_ext deps: $(DEPS) ## Build dependencies @@ -151,9 +161,9 @@ generate_data: ## Run generator scripts for Unicode, SSL config, ... $(MAKE) -B -f scripts/generate_data.mk .PHONY: install -install: $(O)/crystal man/crystal.1.gz ## Install the compiler at DESTDIR +install: $(O)/$(CRYSTAL_BIN) man/crystal.1.gz ## Install the compiler at DESTDIR $(INSTALL) -d -m 0755 "$(BINDIR)/" - $(INSTALL) -m 0755 "$(O)/crystal" "$(BINDIR)/crystal" + $(INSTALL) -m 0755 "$(O)/$(CRYSTAL_BIN)" "$(BINDIR)/$(CRYSTAL_BIN)" $(INSTALL) -d -m 0755 $(DATADIR) cp -av src "$(DATADIR)/src" @@ -171,9 +181,16 @@ install: $(O)/crystal man/crystal.1.gz ## Install the compiler at DESTDIR $(INSTALL) -d -m 0755 "$(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/" $(INSTALL) -m 644 etc/completion.fish "$(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/crystal.fish" +ifeq ($(WINDOWS),1) +.PHONY: install_dlls +install_dlls: $(O)/$(CRYSTAL_BIN) ## Install the compiler's dependent DLLs at DESTDIR (Windows only) + $(INSTALL) -d -m 0755 "$(BINDIR)/" + @ldd $(O)/$(CRYSTAL_BIN) | grep -iv ' => /c/windows/system32' | sed 's/.* => //; s/ (.*//' | xargs -t -i $(INSTALL) -m 0755 '{}' "$(BINDIR)/" +endif + .PHONY: uninstall uninstall: ## Uninstall the compiler from DESTDIR - rm -f "$(BINDIR)/crystal" + rm -f "$(BINDIR)/$(CRYSTAL_BIN)" rm -rf "$(DATADIR)/src" @@ -210,7 +227,7 @@ $(O)/compiler_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES) @mkdir -p $(O) $(EXPORT_CC) $(EXPORTS) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler_spec.cr --release -$(O)/primitives_spec: $(O)/crystal $(DEPS) $(SOURCES) $(SPEC_SOURCES) +$(O)/primitives_spec: $(O)/$(CRYSTAL_BIN) $(DEPS) $(SOURCES) $(SPEC_SOURCES) @mkdir -p $(O) $(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/primitives_spec.cr @@ -219,12 +236,15 @@ $(O)/interpreter_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES) @mkdir -p $(O) $(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler/interpreter_spec.cr -$(O)/crystal: $(DEPS) $(SOURCES) +$(O)/$(CRYSTAL_BIN): $(DEPS) $(SOURCES) $(call check_llvm_config) @mkdir -p $(O) @# NOTE: USE_PCRE1 is only used for testing compatibility with legacy environments that don't provide libpcre2. @# Newly built compilers should never be distributed with libpcre to ensure syntax consistency. - $(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $@ src/compiler/crystal.cr -D without_openssl -D without_zlib $(if $(USE_PCRE1),-D use_pcre,-D use_pcre2) + $(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $(if $(WINDOWS),$(O)/crystal-next.exe,$@) src/compiler/crystal.cr -D without_openssl -D without_zlib $(if $(USE_PCRE1),-D use_pcre,-D use_pcre2) + @# NOTE: on MSYS2 it is not possible to overwrite a running program, so the compiler must be first built with + @# a different filename and then moved to the final destination. + $(if $(WINDOWS),mv $(O)/crystal-next.exe $@) $(LLVM_EXT_OBJ): $(LLVM_EXT_DIR)/llvm_ext.cc $(call check_llvm_config) diff --git a/bin/crystal b/bin/crystal index 2282cbefec80..e8abdff30ee8 100755 --- a/bin/crystal +++ b/bin/crystal @@ -184,9 +184,18 @@ fi # with symlinks resolved as well (see https://github.com/crystal-lang/crystal/issues/12969). cd "$(realpath "$(pwd)")" -if [ -x "$CRYSTAL_DIR/crystal" ]; then - __warning_msg "Using compiled compiler at ${CRYSTAL_DIR#"$PWD/"}/crystal" - exec "$CRYSTAL_DIR/crystal" "$@" +case "$(uname -s)" in + CYGWIN*|MSYS_NT*|MINGW32_NT*|MINGW64_NT*) + CRYSTAL_BIN="crystal.exe" + ;; + *) + CRYSTAL_BIN="crystal" + ;; +esac + +if [ -x "$CRYSTAL_DIR/${CRYSTAL_BIN}" ]; then + __warning_msg "Using compiled compiler at ${CRYSTAL_DIR#"$PWD/"}/${CRYSTAL_BIN}" + exec "$CRYSTAL_DIR/${CRYSTAL_BIN}" "$@" elif !($PARENT_CRYSTAL_EXISTS); then __error_msg 'You need to have a crystal executable in your path! or set CRYSTAL env variable' exit 1 diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index aa11ef1dc47e..6c7664bacc25 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -476,6 +476,7 @@ module Crystal {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags(@cross_compile)}), object_names} elsif program.has_flag?("win32") && program.has_flag?("gnu") link_flags = @link_flags || "" + link_flags += " -Wl,--stack,0x800000" lib_flags = program.lib_flags(@cross_compile) lib_flags = expand_lib_flags(lib_flags) if expand cmd = %(#{DEFAULT_LINKER} #{Process.quote_windows(object_names)} -o #{Process.quote_windows(output_filename)} #{link_flags} #{lib_flags}) diff --git a/src/llvm/ext/find-llvm-config b/src/llvm/ext/find-llvm-config index 5f40a1f2e6a3..5aa381aaf13b 100755 --- a/src/llvm/ext/find-llvm-config +++ b/src/llvm/ext/find-llvm-config @@ -16,7 +16,14 @@ if ! LLVM_CONFIG=$(command -v "$LLVM_CONFIG"); then fi if [ "$LLVM_CONFIG" ]; then - printf %s "$LLVM_CONFIG" + case "$(uname -s)" in + MINGW32_NT*|MINGW64_NT*) + printf "%s" "$(cygpath -w "$LLVM_CONFIG")" + ;; + *) + printf "%s" "$LLVM_CONFIG" + ;; + esac else printf "Error: Could not find location of llvm-config. Please specify path in environment variable LLVM_CONFIG.\n" >&2 printf "Supported LLVM versions: $(cat "$(dirname $0)/llvm-versions.txt" | sed 's/\.0//g')\n" >&2 diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 4c7ed49e7900..8b6856631b55 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -1,5 +1,5 @@ {% begin %} - {% if flag?(:win32) && !flag?(:static) %} + {% if flag?(:msvc) && !flag?(:static) %} {% config = nil %} {% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %} {% config ||= read_file?("#{dir.id}/llvm_VERSION") %} @@ -21,7 +21,7 @@ lib LibLLVM end {% else %} - {% llvm_config = env("LLVM_CONFIG") || `#{__DIR__}/ext/find-llvm-config`.stringify %} + {% llvm_config = env("LLVM_CONFIG") || `sh #{__DIR__}/ext/find-llvm-config`.stringify %} {% llvm_version = `#{llvm_config.id} --version`.stringify %} {% llvm_targets = env("LLVM_TARGETS") || `#{llvm_config.id} --targets-built`.stringify %} {% llvm_ldflags = "`#{llvm_config.id} --libs --system-libs --ldflags#{" --link-static".id if flag?(:static)}#{" 2> /dev/null".id unless flag?(:win32)}`" %} From cf801e7cd1c31d5524303c18f7e8057eb684d5c6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 22 Oct 2024 04:16:59 +0800 Subject: [PATCH 1417/1551] Restrict GitHub token permissions of CI workflows (#15087) --- .github/workflows/aarch64.yml | 2 ++ .github/workflows/docs.yml | 2 ++ .github/workflows/interpreter.yml | 2 ++ .github/workflows/linux.yml | 2 ++ .github/workflows/llvm.yml | 2 ++ .github/workflows/macos.yml | 2 ++ .github/workflows/openssl.yml | 2 ++ .github/workflows/regex-engine.yml | 2 ++ .github/workflows/smoke.yml | 2 ++ .github/workflows/wasm32.yml | 2 ++ .github/workflows/win.yml | 2 ++ .github/workflows/win_build_portable.yml | 2 ++ 12 files changed, 24 insertions(+) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index aec37c3860e1..14e7c3d9f564 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -2,6 +2,8 @@ name: AArch64 CI on: [push, pull_request] +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 57147238552e..9e576303f479 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,6 +5,8 @@ on: branches: - master +permissions: {} + env: TRAVIS_OS_NAME: linux diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 3c74afdd329e..103dc766509b 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -2,6 +2,8 @@ name: Interpreter Test on: [push, pull_request] +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ad65fa005259..79c3f143d303 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -2,6 +2,8 @@ name: Linux CI on: [push, pull_request] +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 65d0744575b9..8caebb3c9c95 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -2,6 +2,8 @@ name: LLVM CI on: [push, pull_request] +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d4c93a68aabb..8ae3ac28209e 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -2,6 +2,8 @@ name: macOS CI on: [push, pull_request] +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 7321abddf788..611413e7e678 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -2,6 +2,8 @@ name: OpenSSL CI on: [push, pull_request] +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml index e7ee002103b4..26b406b84d3f 100644 --- a/.github/workflows/regex-engine.yml +++ b/.github/workflows/regex-engine.yml @@ -2,6 +2,8 @@ name: Regex Engine CI on: [push, pull_request] +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 7ae103e528cf..5a83a26e815a 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -31,6 +31,8 @@ name: Smoke tests on: [push, pull_request] +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index d0012b67c40f..2bb03f6cc5a3 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -2,6 +2,8 @@ name: WebAssembly CI on: [push, pull_request] +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 9025586fa991..03aac8e2f0b1 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -2,6 +2,8 @@ name: Windows CI on: [push, pull_request] +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index 889f4d80c629..a81b9e8083ed 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -10,6 +10,8 @@ on: required: true type: string +permissions: {} + jobs: build: runs-on: windows-2022 From 6b390b0617eb41632c1cb3b5c184dcb1487e3bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 22 Oct 2024 13:18:54 +0200 Subject: [PATCH 1418/1551] Fix `UNIXSocket#receive` (#15107) --- spec/std/socket/unix_socket_spec.cr | 24 ++++++++++++++++++++++++ src/socket/unix_socket.cr | 6 +++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/spec/std/socket/unix_socket_spec.cr b/spec/std/socket/unix_socket_spec.cr index b3bc4931ec78..7e5eda4e2b65 100644 --- a/spec/std/socket/unix_socket_spec.cr +++ b/spec/std/socket/unix_socket_spec.cr @@ -63,6 +63,30 @@ describe UNIXSocket do end end + it "#send, #receive" do + with_tempfile("unix_socket-receive.sock") do |path| + UNIXServer.open(path) do |server| + UNIXSocket.open(path) do |client| + server.accept do |sock| + client.send "ping" + message, address = sock.receive + message.should eq("ping") + typeof(address).should eq(Socket::UNIXAddress) + address.path.should eq "" + + sock.send "pong" + message, address = client.receive + message.should eq("pong") + typeof(address).should eq(Socket::UNIXAddress) + # The value of path seems to be system-specific. Some implementations + # return the socket path, others an empty path. + ["", path].should contain address.path + end + end + end + end + end + # `LibC.socketpair` is not supported in Winsock 2.0 yet: # https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/#unsupportedunavailable {% unless flag?(:win32) %} diff --git a/src/socket/unix_socket.cr b/src/socket/unix_socket.cr index 201fd8410bf7..d5ce5857c907 100644 --- a/src/socket/unix_socket.cr +++ b/src/socket/unix_socket.cr @@ -97,8 +97,8 @@ class UNIXSocket < Socket UNIXAddress.new(path.to_s) end - def receive - bytes_read, sockaddr, addrlen = recvfrom - {bytes_read, UNIXAddress.from(sockaddr, addrlen)} + def receive(max_message_size = 512) : {String, UNIXAddress} + message, address = super(max_message_size) + {message, address.as(UNIXAddress)} end end From 5f72133db005325db80312759c56730ee9a09c03 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 22 Oct 2024 19:19:24 +0800 Subject: [PATCH 1419/1551] Refactor uses of `LibC.dladdr` inside `Exception::CallStack` (#15108) The upcoming MinGW-w64 support for call stacks relies on `Exception::CallStack.unsafe_decode_frame` pointing to a stack-allocated string, so in order to avoid a dangling reference into the unused portion of the stack, this patch converts all the relevant methods into yielding methods. --- src/exception/call_stack/libunwind.cr | 66 ++++++++++++++------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 73a851a00339..1542d52cc736 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -122,32 +122,18 @@ struct Exception::CallStack end {% end %} - if frame = unsafe_decode_frame(repeated_frame.ip) - offset, sname, fname = frame + unsafe_decode_frame(repeated_frame.ip) do |offset, sname, fname| Crystal::System.print_error "%s +%lld in %s", sname, offset.to_i64, fname - else - Crystal::System.print_error "???" + return end - end - protected def self.decode_frame(ip, original_ip = ip) - if LibC.dladdr(ip, out info) != 0 - offset = original_ip - info.dli_saddr + Crystal::System.print_error "???" + end - if offset == 0 - return decode_frame(ip - 1, original_ip) - end - return if info.dli_sname.null? && info.dli_fname.null? - if info.dli_sname.null? - symbol = "??" - else - symbol = String.new(info.dli_sname) - end - if info.dli_fname.null? - file = "??" - else - file = String.new(info.dli_fname) - end + protected def self.decode_frame(ip) + decode_frame(ip) do |offset, symbol, file| + symbol = symbol ? String.new(symbol) : "??" + file = file ? String.new(file) : "??" {offset, symbol, file} end end @@ -155,19 +141,35 @@ struct Exception::CallStack # variant of `.decode_frame` that returns the C strings directly instead of # wrapping them in `String.new`, since the SIGSEGV handler cannot allocate # memory via the GC - protected def self.unsafe_decode_frame(ip) + protected def self.unsafe_decode_frame(ip, &) + decode_frame(ip) do |offset, symbol, file| + symbol ||= "??".to_unsafe + file ||= "??".to_unsafe + yield offset, symbol, file + end + end + + private def self.decode_frame(ip, &) original_ip = ip - while LibC.dladdr(ip, out info) != 0 - offset = original_ip - info.dli_saddr - if offset == 0 - ip -= 1 - next + while true + retry = dladdr(ip) do |file, symbol, address| + offset = original_ip - address + if offset == 0 + ip -= 1 + true + elsif symbol.null? && file.null? + false + else + return yield offset, symbol, file + end end + break unless retry + end + end - return if info.dli_sname.null? && info.dli_fname.null? - symbol = info.dli_sname || "??".to_unsafe - file = info.dli_fname || "??".to_unsafe - return {offset, symbol, file} + private def self.dladdr(ip, &) + if LibC.dladdr(ip, out info) != 0 + yield info.dli_fname, info.dli_sname, info.dli_saddr end end end From 89b1a92a531969f2a3d0d02a0359f8b94578c629 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 23 Oct 2024 04:36:17 +0800 Subject: [PATCH 1420/1551] Use official Apt respositories for LLVM CI (#15103) Co-authored-by: Oleh Prypin --- .github/workflows/llvm.yml | 46 ++++++++++---------------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 8caebb3c9c95..a69383319542 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -13,51 +13,29 @@ env: jobs: llvm_test: - runs-on: ubuntu-22.04 + runs-on: ${{ matrix.runs-on }} strategy: fail-fast: false matrix: include: - - llvm_version: "13.0.0" - llvm_filename: "clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz" - - llvm_version: "14.0.0" - llvm_filename: "clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz" - - llvm_version: "15.0.6" - llvm_filename: "clang+llvm-15.0.6-x86_64-linux-gnu-ubuntu-18.04.tar.xz" - - llvm_version: "16.0.3" - llvm_filename: "clang+llvm-16.0.3-x86_64-linux-gnu-ubuntu-22.04.tar.xz" - - llvm_version: "17.0.6" - llvm_filename: "clang+llvm-17.0.6-x86_64-linux-gnu-ubuntu-22.04.tar.xz" - - llvm_version: "18.1.4" - llvm_filename: "clang+llvm-18.1.4-x86_64-linux-gnu-ubuntu-18.04.tar.xz" - - llvm_version: "19.1.0" - llvm_filename: "LLVM-19.1.0-Linux-X64.tar.xz" + - {llvm_version: 13, runs-on: ubuntu-22.04, codename: jammy} + - {llvm_version: 14, runs-on: ubuntu-22.04, codename: jammy} + - {llvm_version: 15, runs-on: ubuntu-22.04, codename: jammy} + - {llvm_version: 16, runs-on: ubuntu-22.04, codename: jammy} + - {llvm_version: 17, runs-on: ubuntu-24.04, codename: noble} + - {llvm_version: 18, runs-on: ubuntu-24.04, codename: noble} + - {llvm_version: 19, runs-on: ubuntu-24.04, codename: noble} name: "LLVM ${{ matrix.llvm_version }}" steps: - name: Checkout Crystal source uses: actions/checkout@v4 - - name: Cache LLVM - id: cache-llvm - uses: actions/cache@v4 - with: - path: ./llvm - key: llvm-${{ matrix.llvm_version }} - if: "${{ !env.ACT }}" - - name: Install LLVM ${{ matrix.llvm_version }} run: | - mkdir -p llvm - curl -L "https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ matrix.llvm_version }}/${{ matrix.llvm_filename }}" > llvm.tar.xz - tar x --xz -C llvm --strip-components=1 -f llvm.tar.xz - if: steps.cache-llvm.outputs.cache-hit != 'true' - - - name: Set up LLVM - run: | - sudo apt-get install -y libtinfo5 - echo "PATH=$(pwd)/llvm/bin:$PATH" >> $GITHUB_ENV - echo "LLVM_CONFIG=$(pwd)/llvm/bin/llvm-config" >> $GITHUB_ENV - echo "LD_LIBRARY_PATH=$(pwd)/llvm/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV + sudo apt remove 'llvm-*' 'libllvm*' + wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc + sudo apt-add-repository -y deb http://apt.llvm.org/${{ matrix.codename }}/ llvm-toolchain-${{ matrix.codename }}-${{ matrix.llvm_version }} main + sudo apt install -y llvm-${{ matrix.llvm_version }}-dev lld - name: Install Crystal uses: crystal-lang/install-crystal@v1 From 823739796219bb1994b02b1b3db99061ef38f409 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 23 Oct 2024 04:36:46 +0800 Subject: [PATCH 1421/1551] Drop LLVM Apt installer script on WebAssembly CI (#15109) --- .github/workflows/wasm32.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml index 2bb03f6cc5a3..9a6472ca2d6e 100644 --- a/.github/workflows/wasm32.yml +++ b/.github/workflows/wasm32.yml @@ -13,7 +13,7 @@ env: jobs: wasm32-test: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: crystallang/crystal:1.14.0-build steps: - name: Download Crystal source @@ -27,10 +27,11 @@ jobs: - name: Install LLVM run: | apt-get update - apt-get install -y curl lsb-release wget software-properties-common gnupg - curl -O https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - ./llvm.sh 18 + apt-get remove -y 'llvm-*' 'libllvm*' + apt-get install -y curl software-properties-common + wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc + apt-add-repository -y deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main + apt-get install -y llvm-18-dev lld-18 ln -s $(which wasm-ld-18) /usr/bin/wasm-ld - name: Download wasm32 libs From 9d8c2e4a1a7521949324a30aa6774495620fa5f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Wed, 23 Oct 2024 04:38:09 -0400 Subject: [PATCH 1422/1551] Inline `ASTNode` bindings dependencies and observers (#15098) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every `ASTNode` contains two arrays used for type inference and checking: dependencies and observers. By default, these are created lazily, but most active (ie. effectively typed) `ASTNode`s end up creating them. Furthermore, on average both these lists contain less than 2 elements each. This PR replaces both `Array(ASTNode)?` references in `ASTNode` by inlined structs that can hold two elements and a tail array for the cases where more links are needed. This reduces the number of allocations, bytes allocated, number of instructions executed and running time. Some numbers from compiling the Crystal compiler itself without running codegen (since type binding occurs in the semantic phase anyway). * Running time (measured with hyperfine running with `GC_DONT_GC=1`): ~6% reduction Before: ``` Benchmark 1: ./self-semantic-only.sh Time (mean ± σ): 3.398 s ± 0.152 s [User: 2.264 s, System: 0.470 s] Range (min … max): 3.029 s … 3.575 s 10 runs ``` After: ``` Benchmark 1: ./self-semantic-only.sh Time (mean ± σ): 3.180 s ± 0.095 s [User: 2.153 s, System: 0.445 s] Range (min … max): 3.046 s … 3.345 s 10 runs ``` * Memory (as reported by the compiler itself, with GC): ~9.6% reduction Before: ``` Parse: 00:00:00.000038590 ( 1.05MB) Semantic (top level): 00:00:00.483357706 ( 174.13MB) Semantic (new): 00:00:00.002156811 ( 174.13MB) Semantic (type declarations): 00:00:00.038313066 ( 174.13MB) Semantic (abstract def check): 00:00:00.014283169 ( 190.13MB) Semantic (restrictions augmenter): 00:00:00.010672651 ( 206.13MB) Semantic (ivars initializers): 00:00:04.660611385 (1250.07MB) Semantic (cvars initializers): 00:00:00.008343907 (1250.07MB) Semantic (main): 00:00:00.780627942 (1346.07MB) Semantic (cleanup): 00:00:00.000961914 (1346.07MB) Semantic (recursive struct check): 00:00:00.001121766 (1346.07MB) ``` After: ``` Parse: 00:00:00.000044417 ( 1.05MB) Semantic (top level): 00:00:00.546445955 ( 190.03MB) Semantic (new): 00:00:00.002488975 ( 190.03MB) Semantic (type declarations): 00:00:00.040234541 ( 206.03MB) Semantic (abstract def check): 00:00:00.015473723 ( 222.03MB) Semantic (restrictions augmenter): 00:00:00.010828366 ( 222.03MB) Semantic (ivars initializers): 00:00:03.324639987 (1135.96MB) Semantic (cvars initializers): 00:00:00.007359853 (1135.96MB) Semantic (main): 00:00:01.806822202 (1217.96MB) Semantic (cleanup): 00:00:00.000626975 (1217.96MB) Semantic (recursive struct check): 00:00:00.001435494 (1217.96MB) ``` * Callgrind stats: - Instruction refs: 17,477,865,704 -> 16,712,610,033 (~4.4% reduction) - Estimated cycles: 26,835,733,874 -> 26,154,926,143 (~2.5% reduction) - `GC_malloc_kind` call count: 35,161,616 -> 25,684,997 (~27% reduction) Co-authored-by: Oleh Prypin Co-authored-by: Johannes Müller --- src/compiler/crystal/semantic/bindings.cr | 111 ++++++++++++++---- src/compiler/crystal/semantic/call_error.cr | 3 +- .../crystal/semantic/cleanup_transformer.cr | 5 +- src/compiler/crystal/semantic/filters.cr | 2 +- src/compiler/crystal/semantic/main_visitor.cr | 4 +- src/compiler/crystal/semantic/type_merge.cr | 10 +- 6 files changed, 97 insertions(+), 38 deletions(-) diff --git a/src/compiler/crystal/semantic/bindings.cr b/src/compiler/crystal/semantic/bindings.cr index c5fe9f164742..a7dacb8668c9 100644 --- a/src/compiler/crystal/semantic/bindings.cr +++ b/src/compiler/crystal/semantic/bindings.cr @@ -1,7 +1,77 @@ module Crystal + # Specialized container for ASTNodes to use for bindings tracking. + # + # The average number of elements in both dependencies and observers is below 2 + # for ASTNodes. This struct inlines the first two elements saving up 4 + # allocations per node (two arrays, with a header and buffer for each) but we + # need to pay a slight extra cost in memory upfront: a total of 6 pointers (48 + # bytes) vs 2 pointers (16 bytes). The other downside is that since this is a + # struct, we need to be careful with mutation. + struct SmallNodeList + include Enumerable(ASTNode) + + @first : ASTNode? + @second : ASTNode? + @tail : Array(ASTNode)? + + def each(& : ASTNode ->) + yield @first || return + yield @second || return + @tail.try(&.each { |node| yield node }) + end + + def size + if @first.nil? + 0 + elsif @second.nil? + 1 + elsif (tail = @tail).nil? + 2 + else + 2 + tail.size + end + end + + def push(node : ASTNode) : self + if @first.nil? + @first = node + elsif @second.nil? + @second = node + elsif (tail = @tail).nil? + @tail = [node] of ASTNode + else + tail.push(node) + end + self + end + + def reject!(& : ASTNode ->) : self + if first = @first + if second = @second + if tail = @tail + tail.reject! { |node| yield node } + end + if yield second + @second = tail.try &.shift? + end + end + if yield first + @first = @second + @second = tail.try &.shift? + end + end + self + end + + def concat(nodes : Enumerable(ASTNode)) : self + nodes.each { |node| self.push(node) } + self + end + end + class ASTNode - property! dependencies : Array(ASTNode) - property observers : Array(ASTNode)? + getter dependencies : SmallNodeList = SmallNodeList.new + @observers : SmallNodeList = SmallNodeList.new property enclosing_call : Call? @dirty = false @@ -107,8 +177,8 @@ module Crystal end def bind_to(node : ASTNode) : Nil - bind(node) do |dependencies| - dependencies.push node + bind(node) do + @dependencies.push node node.add_observer self end end @@ -116,8 +186,8 @@ module Crystal def bind_to(nodes : Indexable) : Nil return if nodes.empty? - bind do |dependencies| - dependencies.concat nodes + bind do + @dependencies.concat nodes nodes.each &.add_observer self end end @@ -130,9 +200,7 @@ module Crystal raise_frozen_type freeze_type, from_type, from end - dependencies = @dependencies ||= [] of ASTNode - - yield dependencies + yield new_type = type_from_dependencies new_type = map_type(new_type) if new_type @@ -150,7 +218,7 @@ module Crystal end def type_from_dependencies : Type? - Type.merge dependencies + Type.merge @dependencies end def unbind_from(nodes : Nil) @@ -158,18 +226,17 @@ module Crystal end def unbind_from(node : ASTNode) - @dependencies.try &.reject! &.same?(node) + @dependencies.reject! &.same?(node) node.remove_observer self end - def unbind_from(nodes : Array(ASTNode)) - @dependencies.try &.reject! { |dep| nodes.any? &.same?(dep) } + def unbind_from(nodes : Enumerable(ASTNode)) + @dependencies.reject! { |dep| nodes.any? &.same?(dep) } nodes.each &.remove_observer self end def add_observer(observer) - observers = @observers ||= [] of ASTNode - observers.push observer + @observers.push observer end def remove_observer(observer) @@ -269,16 +336,10 @@ module Crystal visited = Set(ASTNode).new.compare_by_identity owner_trace << node if node.type?.try &.includes_type?(owner) visited.add node - while deps = node.dependencies? - dependencies = deps.select { |dep| dep.type? && dep.type.includes_type?(owner) && !visited.includes?(dep) } - if dependencies.size > 0 - node = dependencies.first - nil_reason = node.nil_reason if node.is_a?(MetaTypeVar) - owner_trace << node if node - visited.add node - else - break - end + while node = node.dependencies.find { |dep| dep.type? && dep.type.includes_type?(owner) && !visited.includes?(dep) } + nil_reason = node.nil_reason if node.is_a?(MetaTypeVar) + owner_trace << node if node + visited.add node end MethodTraceException.new(owner, owner_trace, nil_reason, program.show_error_trace?) diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index aee5b9e2019b..d19be20afbad 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -643,8 +643,7 @@ class Crystal::Call if obj.is_a?(InstanceVar) scope = self.scope ivar = scope.lookup_instance_var(obj.name) - deps = ivar.dependencies? - if deps && deps.size == 1 && deps.first.same?(program.nil_var) + if ivar.dependencies.size == 1 && ivar.dependencies.first.same?(program.nil_var) similar_name = scope.lookup_similar_instance_var_name(ivar.name) if similar_name msg << colorize(" (#{ivar.name} was never assigned a value, did you mean #{similar_name}?)").yellow.bold diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 541e0f51d662..054c7871bd8e 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -1090,10 +1090,7 @@ module Crystal node = super unless node.type? - if dependencies = node.dependencies? - node.unbind_from node.dependencies - end - + node.unbind_from node.dependencies node.bind_to node.expressions end diff --git a/src/compiler/crystal/semantic/filters.cr b/src/compiler/crystal/semantic/filters.cr index 66d1a728804b..7dd253fc2292 100644 --- a/src/compiler/crystal/semantic/filters.cr +++ b/src/compiler/crystal/semantic/filters.cr @@ -1,7 +1,7 @@ module Crystal class TypeFilteredNode < ASTNode def initialize(@filter : TypeFilter, @node : ASTNode) - @dependencies = [@node] of ASTNode + @dependencies.push @node node.add_observer self update(@node) end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 905d5bac8cb1..efd76f76f056 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -373,7 +373,7 @@ module Crystal var.bind_to(@program.nil_var) var.nil_if_read = false - meta_var.bind_to(@program.nil_var) unless meta_var.dependencies.try &.any? &.same?(@program.nil_var) + meta_var.bind_to(@program.nil_var) unless meta_var.dependencies.any? &.same?(@program.nil_var) node.bind_to(@program.nil_var) end @@ -1283,7 +1283,7 @@ module Crystal # It can happen that this call is inside an ArrayLiteral or HashLiteral, # was expanded but isn't bound to the expansion because the call (together # with its expansion) was cloned. - if (expanded = node.expanded) && (!node.dependencies? || !node.type?) + if (expanded = node.expanded) && (node.dependencies.empty? || !node.type?) node.bind_to(expanded) end diff --git a/src/compiler/crystal/semantic/type_merge.cr b/src/compiler/crystal/semantic/type_merge.cr index 874949dd516d..67e9f1b61911 100644 --- a/src/compiler/crystal/semantic/type_merge.cr +++ b/src/compiler/crystal/semantic/type_merge.cr @@ -17,7 +17,7 @@ module Crystal end end - def type_merge(nodes : Array(ASTNode)) : Type? + def type_merge(nodes : Enumerable(ASTNode)) : Type? case nodes.size when 0 nil @@ -25,8 +25,10 @@ module Crystal nodes.first.type? when 2 # Merging two types is the most common case, so we optimize it - first, second = nodes - type_merge(first.type?, second.type?) + # We use `#each_cons_pair` to avoid any intermediate allocation + nodes.each_cons_pair do |first, second| + return type_merge(first.type?, second.type?) + end else combined_union_of compact_types(nodes, &.type?) end @@ -161,7 +163,7 @@ module Crystal end class Type - def self.merge(nodes : Array(ASTNode)) : Type? + def self.merge(nodes : Enumerable(ASTNode)) : Type? nodes.find(&.type?).try &.type.program.type_merge(nodes) end From f2a6628672557ff95b645f67fee8dd6b3092f667 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 23 Oct 2024 16:38:35 +0800 Subject: [PATCH 1423/1551] Add CI workflow for cross-compiling Crystal on MSYS2 (#15110) Cross-compiles a MinGW-w64-based Crystal compiler from Ubuntu, then links it on MSYS2's UCRT64 environment. Resolves part of #6170. The artifact includes the compiler, all dependent DLLs, and the source code only. It is not a complete installation since it is missing e.g. the documentation and the licenses, but it is sufficient for bootstrapping further native compiler builds within MSYS2. The resulting binary is portable within MSYS2 and can be executed from anywhere inside an MSYS2 shell, although compilation requires `mingw-w64-ucrt-x86_64-cc`, probably `mingw-w64-ucrt-x86_64-pkgconf`, plus the respective development libraries listed in #15077. The DLLs bundled under `bin/` are needed to even start Crystal since they are dynamically linked at load time; they are not strictly needed if Crystal is always run only within MSYS2, but that is the job of an actual `PKGBUILD` file. --- .github/workflows/mingw-w64.yml | 91 +++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 .github/workflows/mingw-w64.yml diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml new file mode 100644 index 000000000000..2370ae133cdd --- /dev/null +++ b/.github/workflows/mingw-w64.yml @@ -0,0 +1,91 @@ +name: MinGW-w64 CI + +on: [push, pull_request] + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +jobs: + x86_64-mingw-w64-cross-compile: + runs-on: ubuntu-24.04 + steps: + - name: Download Crystal source + uses: actions/checkout@v4 + + - name: Install LLVM 18 + run: | + sudo apt remove 'llvm-*' 'libllvm*' + wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc + sudo apt-add-repository -y deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main + sudo apt install -y llvm-18-dev + + - name: Install Crystal + uses: crystal-lang/install-crystal@v1 + with: + crystal: "1.14.0" + + - name: Cross-compile Crystal + run: make && make -B target=x86_64-windows-gnu release=1 + + - name: Upload crystal.obj + uses: actions/upload-artifact@v4 + with: + name: x86_64-mingw-w64-crystal-obj + path: .build/crystal.obj + + - name: Upload standard library + uses: actions/upload-artifact@v4 + with: + name: x86_64-mingw-w64-crystal-stdlib + path: src + + x86_64-mingw-w64-link: + runs-on: windows-2022 + needs: [x86_64-mingw-w64-cross-compile] + steps: + - name: Setup MSYS2 + id: msys2 + uses: msys2/setup-msys2@ddf331adaebd714795f1042345e6ca57bd66cea8 # v2.24.1 + with: + msystem: UCRT64 + update: true + install: >- + mingw-w64-ucrt-x86_64-pkgconf + mingw-w64-ucrt-x86_64-cc + mingw-w64-ucrt-x86_64-gc + mingw-w64-ucrt-x86_64-pcre2 + mingw-w64-ucrt-x86_64-libiconv + mingw-w64-ucrt-x86_64-zlib + mingw-w64-ucrt-x86_64-llvm + + - name: Download crystal.obj + uses: actions/download-artifact@v4 + with: + name: x86_64-mingw-w64-crystal-obj + + - name: Download standard library + uses: actions/download-artifact@v4 + with: + name: x86_64-mingw-w64-crystal-stdlib + path: share/crystal/src + + - name: Link Crystal executable + shell: msys2 {0} + run: | + mkdir bin + cc crystal.obj -o bin/crystal.exe \ + $(pkg-config bdw-gc libpcre2-8 iconv zlib --libs) \ + $(llvm-config --libs --system-libs --ldflags) \ + -lDbgHelp -lole32 -lWS2_32 -Wl,--stack,0x800000 + ldd bin/crystal.exe | grep -iv /c/windows/system32 | sed 's/.* => //; s/ (.*//' | xargs -t -i cp '{}' bin/ + + - name: Upload Crystal + uses: actions/upload-artifact@v4 + with: + name: x86_64-mingw-w64-crystal + path: | + bin/ + share/ From b8475639fd087b00db5759f0a574ccf5fd67a18d Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 23 Oct 2024 23:26:26 +0200 Subject: [PATCH 1424/1551] Fix: LibC bindings and std specs on NetBSD 10 (#15115) The LibC bindings for NetBSD were a bit wrong and some std specs also didn't work as expected. The segfault handler is also broken on NetBSD (the process crashes with SIGILL after receiving SIGSEGV). With these fixes + pending specs I can run the std and compiler test suites in a NetBSD 10.0 VM. **Caveat**: the pkgsrc for LLVM enforces partial RELRO and linking the std specs from crystal fails. You must pass `--single-module` for the executable to be able to start without missing runtime symbols from libxml2. See #11046. --- spec/std/io/io_spec.cr | 2 +- spec/std/kernel_spec.cr | 62 +++++++++++++++------------ spec/std/socket/tcp_server_spec.cr | 4 +- spec/std/socket/tcp_socket_spec.cr | 6 +-- spec/std/socket/udp_socket_spec.cr | 3 ++ spec/std/string_spec.cr | 8 ++-- src/crystal/system/unix/dir.cr | 7 ++- src/lib_c/x86_64-netbsd/c/dirent.cr | 1 - src/lib_c/x86_64-netbsd/c/netdb.cr | 1 + src/lib_c/x86_64-netbsd/c/sys/time.cr | 2 +- 10 files changed, 55 insertions(+), 41 deletions(-) diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 3be5c07e1479..620a1d034d9f 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -736,7 +736,7 @@ describe IO do it "says invalid byte sequence" do io = SimpleIOMemory.new(Slice.new(1, 255_u8)) io.set_encoding("EUC-JP") - expect_raises ArgumentError, {% if flag?(:musl) || flag?(:freebsd) %}"Incomplete multibyte sequence"{% else %}"Invalid multibyte sequence"{% end %} do + expect_raises ArgumentError, {% if flag?(:musl) || flag?(:freebsd) || flag?(:netbsd) %}"Incomplete multibyte sequence"{% else %}"Invalid multibyte sequence"{% end %} do io.read_char end end diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index f41529af901a..7f3c39d9e9ec 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -254,38 +254,44 @@ describe "hardware exception" do error.should_not contain("Stack overflow") end - it "detects stack overflow on the main stack", tags: %w[slow] do - # This spec can take some time under FreeBSD where - # the default stack size is 0.5G. Setting a - # smaller stack size with `ulimit -s 8192` - # will address this. - status, _, error = compile_and_run_source <<-'CRYSTAL' - def foo - y = StaticArray(Int8, 512).new(0) + {% if flag?(:netbsd) %} + # FIXME: on netbsd the process crashes with SIGILL after receiving SIGSEGV + pending "detects stack overflow on the main stack" + pending "detects stack overflow on a fiber stack" + {% else %} + it "detects stack overflow on the main stack", tags: %w[slow] do + # This spec can take some time under FreeBSD where + # the default stack size is 0.5G. Setting a + # smaller stack size with `ulimit -s 8192` + # will address this. + status, _, error = compile_and_run_source <<-'CRYSTAL' + def foo + y = StaticArray(Int8, 512).new(0) + foo + end foo - end - foo - CRYSTAL + CRYSTAL - status.success?.should be_false - error.should contain("Stack overflow") - end + status.success?.should be_false + error.should contain("Stack overflow") + end - it "detects stack overflow on a fiber stack", tags: %w[slow] do - status, _, error = compile_and_run_source <<-'CRYSTAL' - def foo - y = StaticArray(Int8, 512).new(0) - foo - end + it "detects stack overflow on a fiber stack", tags: %w[slow] do + status, _, error = compile_and_run_source <<-'CRYSTAL' + def foo + y = StaticArray(Int8, 512).new(0) + foo + end - spawn do - foo - end + spawn do + foo + end - sleep 60.seconds - CRYSTAL + sleep 60.seconds + CRYSTAL - status.success?.should be_false - error.should contain("Stack overflow") - end + status.success?.should be_false + error.should contain("Stack overflow") + end + {% end %} end diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr index 0c6113a4a7ff..ee3c861956b8 100644 --- a/spec/std/socket/tcp_server_spec.cr +++ b/spec/std/socket/tcp_server_spec.cr @@ -96,7 +96,7 @@ describe TCPServer, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) %} + {% elsif flag?(:android) || flag?(:netbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error @@ -110,7 +110,7 @@ describe TCPServer, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) %} + {% elsif flag?(:android) || flag?(:netbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index f3d460f92401..5ec3467362e0 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -79,7 +79,7 @@ describe TCPSocket, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) %} + {% elsif flag?(:android) || flag?(:netbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error @@ -93,7 +93,7 @@ describe TCPSocket, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) %} + {% elsif flag?(:android) || flag?(:netbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error @@ -142,7 +142,7 @@ describe TCPSocket, tags: "network" do (client.tcp_nodelay = false).should be_false client.tcp_nodelay?.should be_false - {% unless flag?(:openbsd) %} + {% unless flag?(:openbsd) || flag?(:netbsd) %} (client.tcp_keepalive_idle = 42).should eq 42 client.tcp_keepalive_idle.should eq 42 (client.tcp_keepalive_interval = 42).should eq 42 diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr index 6e4b607b80ea..9b624110fad9 100644 --- a/spec/std/socket/udp_socket_spec.cr +++ b/spec/std/socket/udp_socket_spec.cr @@ -85,6 +85,9 @@ describe UDPSocket, tags: "network" do elsif {{ flag?(:freebsd) }} && family == Socket::Family::INET6 # FIXME: fails with "Error sending datagram to [ipv6]:port: Network is unreachable" pending "joins and transmits to multicast groups" + elsif {{ flag?(:netbsd) }} && family == Socket::Family::INET6 + # FIXME: fails with "setsockopt: EADDRNOTAVAIL" + pending "joins and transmits to multicast groups" else it "joins and transmits to multicast groups" do udp = UDPSocket.new(family) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 2ffe5bf3d1fa..6d7487ded0e2 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2830,7 +2830,7 @@ describe "String" do bytes.to_a.should eq([72, 0, 101, 0, 108, 0, 108, 0, 111, 0]) end - {% unless flag?(:musl) || flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) %} + {% unless flag?(:musl) || flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "flushes the shift state (#11992)" do "\u{00CA}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x66]) "\u{00CA}\u{0304}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x62]) @@ -2839,7 +2839,7 @@ describe "String" do # FreeBSD iconv encoder expects ISO/IEC 10646 compatibility code points, # see https://www.ccli.gov.hk/doc/e_hkscs_2008.pdf for details. - {% if flag?(:freebsd) || flag?(:dragonfly) %} + {% if flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "flushes the shift state (#11992)" do "\u{F329}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x66]) "\u{F325}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x62]) @@ -2883,7 +2883,7 @@ describe "String" do String.new(bytes, "UTF-16LE").should eq("Hello") end - {% unless flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) %} + {% unless flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "decodes with shift state" do String.new(Bytes[0x88, 0x66], "BIG5-HKSCS").should eq("\u{00CA}") String.new(Bytes[0x88, 0x62], "BIG5-HKSCS").should eq("\u{00CA}\u{0304}") @@ -2892,7 +2892,7 @@ describe "String" do # FreeBSD iconv decoder returns ISO/IEC 10646-1:2000 code points, # see https://www.ccli.gov.hk/doc/e_hkscs_2008.pdf for details. - {% if flag?(:freebsd) || flag?(:dragonfly) %} + {% if flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "decodes with shift state" do String.new(Bytes[0x88, 0x66], "BIG5-HKSCS").should eq("\u{00CA}") String.new(Bytes[0x88, 0x62], "BIG5-HKSCS").should eq("\u{F325}") diff --git a/src/crystal/system/unix/dir.cr b/src/crystal/system/unix/dir.cr index 5e66b33b65e7..72d1183dcc72 100644 --- a/src/crystal/system/unix/dir.cr +++ b/src/crystal/system/unix/dir.cr @@ -42,7 +42,12 @@ module Crystal::System::Dir end def self.info(dir, path) : ::File::Info - Crystal::System::FileDescriptor.system_info LibC.dirfd(dir) + fd = {% if flag?(:netbsd) %} + dir.value.dd_fd + {% else %} + LibC.dirfd(dir) + {% end %} + Crystal::System::FileDescriptor.system_info(fd) end def self.close(dir, path) : Nil diff --git a/src/lib_c/x86_64-netbsd/c/dirent.cr b/src/lib_c/x86_64-netbsd/c/dirent.cr index 71dabe7b08ce..e3b8492083f7 100644 --- a/src/lib_c/x86_64-netbsd/c/dirent.cr +++ b/src/lib_c/x86_64-netbsd/c/dirent.cr @@ -29,5 +29,4 @@ lib LibC fun opendir = __opendir30(x0 : Char*) : DIR* fun readdir = __readdir30(x0 : DIR*) : Dirent* fun rewinddir(x0 : DIR*) : Void - fun dirfd(dirp : DIR*) : Int end diff --git a/src/lib_c/x86_64-netbsd/c/netdb.cr b/src/lib_c/x86_64-netbsd/c/netdb.cr index 4443325cd487..c098ab2f5fc6 100644 --- a/src/lib_c/x86_64-netbsd/c/netdb.cr +++ b/src/lib_c/x86_64-netbsd/c/netdb.cr @@ -13,6 +13,7 @@ lib LibC EAI_FAIL = 4 EAI_FAMILY = 5 EAI_MEMORY = 6 + EAI_NODATA = 7 EAI_NONAME = 8 EAI_SERVICE = 9 EAI_SOCKTYPE = 10 diff --git a/src/lib_c/x86_64-netbsd/c/sys/time.cr b/src/lib_c/x86_64-netbsd/c/sys/time.cr index f276784708c0..3bb54d42c5cd 100644 --- a/src/lib_c/x86_64-netbsd/c/sys/time.cr +++ b/src/lib_c/x86_64-netbsd/c/sys/time.cr @@ -13,5 +13,5 @@ lib LibC fun gettimeofday = __gettimeofday50(x0 : Timeval*, x1 : Timezone*) : Int fun utimes = __utimes50(path : Char*, times : Timeval[2]) : Int - fun futimens = __futimens50(fd : Int, times : Timespec[2]) : Int + fun futimens(fd : Int, times : Timespec[2]) : Int end From 2e8715d67a568ad4d4ea3d703fd8a0bbf2fe5434 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 24 Oct 2024 05:27:02 +0800 Subject: [PATCH 1425/1551] Disable specs that break on MinGW-w64 (#15116) These specs do not have straightforward workarounds when run under MSYS2. --- spec/compiler/codegen/pointer_spec.cr | 53 ++++++++++++---------- spec/compiler/codegen/thread_local_spec.cr | 2 +- spec/std/kernel_spec.cr | 8 ++++ 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/spec/compiler/codegen/pointer_spec.cr b/spec/compiler/codegen/pointer_spec.cr index 1230d80cb5f6..da132cdee406 100644 --- a/spec/compiler/codegen/pointer_spec.cr +++ b/spec/compiler/codegen/pointer_spec.cr @@ -492,28 +492,33 @@ describe "Code gen: pointer" do )).to_b.should be_true end - it "takes pointerof lib external var" do - test_c( - %( - int external_var = 0; - ), - %( - lib LibFoo - $external_var : Int32 - end - - LibFoo.external_var = 1 - - ptr = pointerof(LibFoo.external_var) - x = ptr.value - - ptr.value = 10 - y = ptr.value - - ptr.value = 100 - z = LibFoo.external_var - - x + y + z - ), &.to_i.should eq(111)) - end + # FIXME: `$external_var` implies __declspec(dllimport), but we only have an + # object file, so MinGW-w64 fails linking (actually MSVC also emits an + # LNK4217 linker warning) + {% unless flag?(:win32) && flag?(:gnu) %} + it "takes pointerof lib external var" do + test_c( + %( + int external_var = 0; + ), + %( + lib LibFoo + $external_var : Int32 + end + + LibFoo.external_var = 1 + + ptr = pointerof(LibFoo.external_var) + x = ptr.value + + ptr.value = 10 + y = ptr.value + + ptr.value = 100 + z = LibFoo.external_var + + x + y + z + ), &.to_i.should eq(111)) + end + {% end %} end diff --git a/spec/compiler/codegen/thread_local_spec.cr b/spec/compiler/codegen/thread_local_spec.cr index 694cb430b8c1..386043f2c5fd 100644 --- a/spec/compiler/codegen/thread_local_spec.cr +++ b/spec/compiler/codegen/thread_local_spec.cr @@ -1,4 +1,4 @@ -{% skip_file if flag?(:openbsd) %} +{% skip_file if flag?(:openbsd) || (flag?(:win32) && flag?(:gnu)) %} require "../../spec_helper" diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index 7f3c39d9e9ec..f8e4ff1e8ae2 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -8,6 +8,14 @@ describe "PROGRAM_NAME" do pending! "Example is broken in Nix shell (#12332)" end + # MSYS2: gcc/ld doesn't support unicode paths + # https://github.com/msys2/MINGW-packages/issues/17812 + {% if flag?(:windows) %} + if ENV["MSYSTEM"]? + pending! "Example is broken in MSYS2 shell" + end + {% end %} + File.write(source_file, "File.basename(PROGRAM_NAME).inspect(STDOUT)") compile_file(source_file, bin_name: "×‽😂") do |executable_file| From 94386b640c4d70305bf12f7da9e2184694277587 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 24 Oct 2024 05:27:25 +0800 Subject: [PATCH 1426/1551] Treat `WinError::ERROR_DIRECTORY` as an error for non-existent files (#15114) --- src/crystal/system/win32/file.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 7b7b443ce310..b6f9cf2b7ccd 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -116,6 +116,7 @@ module Crystal::System::File WinError::ERROR_FILE_NOT_FOUND, WinError::ERROR_PATH_NOT_FOUND, WinError::ERROR_INVALID_NAME, + WinError::ERROR_DIRECTORY, } def self.check_not_found_error(message, path) From 454744a35e34fdd995c22200a32f67a2b4320f1c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 24 Oct 2024 18:30:42 +0800 Subject: [PATCH 1427/1551] Support call stacks for MinGW-w64 builds (#15117) Introduces new methods for extracting COFF debug information from programs in the PE format, integrating them into Crystal's existing DWARF parsing functionality. Resolves part of #6170. It is questionable whether reusing `src/exception/call_stack/elf.cr` for MinGW-w64 is appropriate, since nothing here is in the ELF format, but this PR tries to avoid moving existing code around, save for the old `Exception::CallStack.setup_crash_handler` as it remains the only common portion between MSVC and MinGW-w64. --- spec/std/exception/call_stack_spec.cr | 6 +- src/crystal/pe.cr | 110 +++++++++++++++++ src/crystal/system/win32/signal.cr | 44 +++++++ src/exception/call_stack.cr | 5 +- src/exception/call_stack/dwarf.cr | 4 + src/exception/call_stack/elf.cr | 86 +++++++------ src/exception/call_stack/libunwind.cr | 113 ++++++++++++++++-- src/exception/call_stack/stackwalk.cr | 61 +--------- .../x86_64-windows-msvc/c/libloaderapi.cr | 3 + src/lib_c/x86_64-windows-msvc/c/winnt.cr | 54 +++++++++ src/raise.cr | 2 +- 11 files changed, 379 insertions(+), 109 deletions(-) create mode 100644 src/crystal/pe.cr diff --git a/spec/std/exception/call_stack_spec.cr b/spec/std/exception/call_stack_spec.cr index c01fb0ff6b8a..7c6f5d746bdc 100644 --- a/spec/std/exception/call_stack_spec.cr +++ b/spec/std/exception/call_stack_spec.cr @@ -12,9 +12,9 @@ describe "Backtrace" do _, output, _ = compile_and_run_file(source_file) - # resolved file:line:column (no column for windows PDB because of poor - # support in general) - {% if flag?(:win32) %} + # resolved file:line:column (no column for MSVC PDB because of poor support + # by external tooling in general) + {% if flag?(:msvc) %} output.should match(/^#{Regex.escape(source_file)}:3 in 'callee1'/m) output.should match(/^#{Regex.escape(source_file)}:13 in 'callee3'/m) {% else %} diff --git a/src/crystal/pe.cr b/src/crystal/pe.cr new file mode 100644 index 000000000000..d1b19401ad19 --- /dev/null +++ b/src/crystal/pe.cr @@ -0,0 +1,110 @@ +module Crystal + # :nodoc: + # + # Portable Executable reader. + # + # Documentation: + # - + struct PE + class Error < Exception + end + + record SectionHeader, name : String, virtual_offset : UInt32, offset : UInt32, size : UInt32 + + record COFFSymbol, offset : UInt32, name : String + + # addresses in COFF debug info are relative to this image base; used by + # `Exception::CallStack.read_dwarf_sections` to calculate the real relocated + # addresses + getter original_image_base : UInt64 + + @section_headers : Slice(SectionHeader) + @string_table_base : UInt32 + + # mapping from zero-based section index to list of symbols sorted by + # offsets within that section + getter coff_symbols = Hash(Int32, Array(COFFSymbol)).new + + def self.open(path : String | ::Path, &) + File.open(path, "r") do |file| + yield new(file) + end + end + + def initialize(@io : IO::FileDescriptor) + dos_header = uninitialized LibC::IMAGE_DOS_HEADER + io.read_fully(pointerof(dos_header).to_slice(1).to_unsafe_bytes) + raise Error.new("Invalid DOS header") unless dos_header.e_magic == 0x5A4D # MZ + + io.seek(dos_header.e_lfanew) + nt_header = uninitialized LibC::IMAGE_NT_HEADERS + io.read_fully(pointerof(nt_header).to_slice(1).to_unsafe_bytes) + raise Error.new("Invalid PE header") unless nt_header.signature == 0x00004550 # PE\0\0 + + @original_image_base = nt_header.optionalHeader.imageBase + @string_table_base = nt_header.fileHeader.pointerToSymbolTable + nt_header.fileHeader.numberOfSymbols * sizeof(LibC::IMAGE_SYMBOL) + + section_count = nt_header.fileHeader.numberOfSections + nt_section_headers = Pointer(LibC::IMAGE_SECTION_HEADER).malloc(section_count).to_slice(section_count) + io.read_fully(nt_section_headers.to_unsafe_bytes) + + @section_headers = nt_section_headers.map do |nt_header| + if nt_header.name[0] === '/' + # section name is longer than 8 bytes; look up the COFF string table + name_buf = nt_header.name.to_slice + 1 + string_offset = String.new(name_buf.to_unsafe, name_buf.index(0) || name_buf.size).to_i + io.seek(@string_table_base + string_offset) + name = io.gets('\0', chomp: true).not_nil! + else + name = String.new(nt_header.name.to_unsafe, nt_header.name.index(0) || nt_header.name.size) + end + + SectionHeader.new(name: name, virtual_offset: nt_header.virtualAddress, offset: nt_header.pointerToRawData, size: nt_header.virtualSize) + end + + io.seek(nt_header.fileHeader.pointerToSymbolTable) + image_symbol_count = nt_header.fileHeader.numberOfSymbols + image_symbols = Pointer(LibC::IMAGE_SYMBOL).malloc(image_symbol_count).to_slice(image_symbol_count) + io.read_fully(image_symbols.to_unsafe_bytes) + + aux_count = 0 + image_symbols.each_with_index do |sym, i| + if aux_count == 0 + aux_count = sym.numberOfAuxSymbols.to_i + else + aux_count &-= 1 + end + + next unless aux_count == 0 + next unless sym.type.bits_set?(0x20) # COFF function + next unless sym.sectionNumber > 0 # one-based section index + next unless sym.storageClass.in?(LibC::IMAGE_SYM_CLASS_EXTERNAL, LibC::IMAGE_SYM_CLASS_STATIC) + + if sym.n.name.short == 0 + io.seek(@string_table_base + sym.n.name.long) + name = io.gets('\0', chomp: true).not_nil! + else + name = String.new(sym.n.shortName.to_slice).rstrip('\0') + end + + # `@coff_symbols` uses zero-based indices + section_coff_symbols = @coff_symbols.put_if_absent(sym.sectionNumber.to_i &- 1) { [] of COFFSymbol } + section_coff_symbols << COFFSymbol.new(sym.value, name) + end + + # add one sentinel symbol to ensure binary search on the offsets works + @coff_symbols.each_with_index do |(_, symbols), i| + symbols.sort_by!(&.offset) + symbols << COFFSymbol.new(@section_headers[i].size, "??") + end + end + + def read_section?(name : String, &) + if sh = @section_headers.find(&.name.== name) + @io.seek(sh.offset) do + yield sh, @io + end + end + end + end +end diff --git a/src/crystal/system/win32/signal.cr b/src/crystal/system/win32/signal.cr index d805ea4fd1ab..4cebe7cf9c6a 100644 --- a/src/crystal/system/win32/signal.cr +++ b/src/crystal/system/win32/signal.cr @@ -1,4 +1,5 @@ require "c/signal" +require "c/malloc" module Crystal::System::Signal def self.trap(signal, handler) : Nil @@ -16,4 +17,47 @@ module Crystal::System::Signal def self.ignore(signal) : Nil raise NotImplementedError.new("Crystal::System::Signal.ignore") end + + def self.setup_seh_handler + LibC.AddVectoredExceptionHandler(1, ->(exception_info) do + case exception_info.value.exceptionRecord.value.exceptionCode + when LibC::EXCEPTION_ACCESS_VIOLATION + addr = exception_info.value.exceptionRecord.value.exceptionInformation[1] + Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr) + {% if flag?(:gnu) %} + Exception::CallStack.print_backtrace + {% else %} + Exception::CallStack.print_backtrace(exception_info) + {% end %} + LibC._exit(1) + when LibC::EXCEPTION_STACK_OVERFLOW + LibC._resetstkoflw + Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n" + {% if flag?(:gnu) %} + Exception::CallStack.print_backtrace + {% else %} + Exception::CallStack.print_backtrace(exception_info) + {% end %} + LibC._exit(1) + else + LibC::EXCEPTION_CONTINUE_SEARCH + end + end) + + # ensure that even in the case of stack overflow there is enough reserved + # stack space for recovery (for other threads this is done in + # `Crystal::System::Thread.thread_proc`) + stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE + LibC.SetThreadStackGuarantee(pointerof(stack_size)) + + # this catches invalid argument checks inside the C runtime library + LibC._set_invalid_parameter_handler(->(expression, _function, _file, _line, _pReserved) do + message = expression ? String.from_utf16(expression)[0] : "(no message)" + Crystal::System.print_error "CRT invalid parameter handler invoked: %s\n", message + caller.each do |frame| + Crystal::System.print_error " from %s\n", frame + end + LibC._exit(1) + end) + end end diff --git a/src/exception/call_stack.cr b/src/exception/call_stack.cr index 44a281570c1c..506317d2580e 100644 --- a/src/exception/call_stack.cr +++ b/src/exception/call_stack.cr @@ -1,10 +1,7 @@ {% if flag?(:interpreted) %} require "./call_stack/interpreter" -{% elsif flag?(:win32) %} +{% elsif flag?(:win32) && !flag?(:gnu) %} require "./call_stack/stackwalk" - {% if flag?(:gnu) %} - require "./lib_unwind" - {% end %} {% elsif flag?(:wasm32) %} require "./call_stack/null" {% else %} diff --git a/src/exception/call_stack/dwarf.cr b/src/exception/call_stack/dwarf.cr index 96d99f03205a..253a72a38ebc 100644 --- a/src/exception/call_stack/dwarf.cr +++ b/src/exception/call_stack/dwarf.cr @@ -10,6 +10,10 @@ struct Exception::CallStack @@dwarf_line_numbers : Crystal::DWARF::LineNumbers? @@dwarf_function_names : Array(Tuple(LibC::SizeT, LibC::SizeT, String))? + {% if flag?(:win32) %} + @@coff_symbols : Hash(Int32, Array(Crystal::PE::COFFSymbol))? + {% end %} + # :nodoc: def self.load_debug_info : Nil return if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "0" diff --git a/src/exception/call_stack/elf.cr b/src/exception/call_stack/elf.cr index efa54f41329c..51d565528577 100644 --- a/src/exception/call_stack/elf.cr +++ b/src/exception/call_stack/elf.cr @@ -1,65 +1,83 @@ -require "crystal/elf" -{% unless flag?(:wasm32) %} - require "c/link" +{% if flag?(:win32) %} + require "crystal/pe" +{% else %} + require "crystal/elf" + {% unless flag?(:wasm32) %} + require "c/link" + {% end %} {% end %} struct Exception::CallStack - private struct DlPhdrData - getter program : String - property base_address : LibC::Elf_Addr = 0 + {% unless flag?(:win32) %} + private struct DlPhdrData + getter program : String + property base_address : LibC::Elf_Addr = 0 - def initialize(@program : String) + def initialize(@program : String) + end end - end + {% end %} protected def self.load_debug_info_impl : Nil program = Process.executable_path return unless program && File::Info.readable? program - data = DlPhdrData.new(program) - - phdr_callback = LibC::DlPhdrCallback.new do |info, size, data| - # `dl_iterate_phdr` does not always visit the current program first; on - # Android the first object is `/system/bin/linker64`, the second is the - # full program path (not the empty string), so we check both here - name_c_str = info.value.name - if name_c_str && (name_c_str.value == 0 || LibC.strcmp(name_c_str, data.as(DlPhdrData*).value.program) == 0) - # The first entry is the header for the current program. - # Note that we avoid allocating here and just store the base address - # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns. - # Calling self.read_dwarf_sections from this callback may lead to reallocations - # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084). - data.as(DlPhdrData*).value.base_address = info.value.addr - 1 - else - 0 + + {% if flag?(:win32) %} + if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out hmodule) != 0 + self.read_dwarf_sections(program, hmodule.address) end - end + {% else %} + data = DlPhdrData.new(program) - LibC.dl_iterate_phdr(phdr_callback, pointerof(data)) - self.read_dwarf_sections(data.program, data.base_address) + phdr_callback = LibC::DlPhdrCallback.new do |info, size, data| + # `dl_iterate_phdr` does not always visit the current program first; on + # Android the first object is `/system/bin/linker64`, the second is the + # full program path (not the empty string), so we check both here + name_c_str = info.value.name + if name_c_str && (name_c_str.value == 0 || LibC.strcmp(name_c_str, data.as(DlPhdrData*).value.program) == 0) + # The first entry is the header for the current program. + # Note that we avoid allocating here and just store the base address + # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns. + # Calling self.read_dwarf_sections from this callback may lead to reallocations + # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084). + data.as(DlPhdrData*).value.base_address = info.value.addr + 1 + else + 0 + end + end + + LibC.dl_iterate_phdr(phdr_callback, pointerof(data)) + self.read_dwarf_sections(data.program, data.base_address) + {% end %} end protected def self.read_dwarf_sections(program, base_address = 0) - Crystal::ELF.open(program) do |elf| - line_strings = elf.read_section?(".debug_line_str") do |sh, io| + {{ flag?(:win32) ? Crystal::PE : Crystal::ELF }}.open(program) do |image| + {% if flag?(:win32) %} + base_address -= image.original_image_base + @@coff_symbols = image.coff_symbols + {% end %} + + line_strings = image.read_section?(".debug_line_str") do |sh, io| Crystal::DWARF::Strings.new(io, sh.offset, sh.size) end - strings = elf.read_section?(".debug_str") do |sh, io| + strings = image.read_section?(".debug_str") do |sh, io| Crystal::DWARF::Strings.new(io, sh.offset, sh.size) end - elf.read_section?(".debug_line") do |sh, io| + image.read_section?(".debug_line") do |sh, io| @@dwarf_line_numbers = Crystal::DWARF::LineNumbers.new(io, sh.size, base_address, strings, line_strings) end - elf.read_section?(".debug_info") do |sh, io| + image.read_section?(".debug_info") do |sh, io| names = [] of {LibC::SizeT, LibC::SizeT, String} while (offset = io.pos - sh.offset) < sh.size info = Crystal::DWARF::Info.new(io, offset) - elf.read_section?(".debug_abbrev") do |sh, io| + image.read_section?(".debug_abbrev") do |sh, io| info.read_abbreviations(io) end diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 1542d52cc736..c0f75867aeba 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -1,9 +1,11 @@ -require "c/dlfcn" +{% unless flag?(:win32) %} + require "c/dlfcn" +{% end %} require "c/stdio" require "c/string" require "../lib_unwind" -{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) || flag?(:solaris) %} +{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) || flag?(:solaris) || flag?(:win32) %} require "./dwarf" {% else %} require "./null" @@ -33,7 +35,11 @@ struct Exception::CallStack {% end %} def self.setup_crash_handler - Crystal::System::Signal.setup_segfault_handler + {% if flag?(:win32) %} + Crystal::System::Signal.setup_seh_handler + {% else %} + Crystal::System::Signal.setup_segfault_handler + {% end %} end {% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %} @@ -167,9 +173,102 @@ struct Exception::CallStack end end - private def self.dladdr(ip, &) - if LibC.dladdr(ip, out info) != 0 - yield info.dli_fname, info.dli_sname, info.dli_saddr + {% if flag?(:win32) %} + def self.dladdr(ip, &) + if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | LibC::GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, ip.as(LibC::LPWSTR), out hmodule) != 0 + symbol, address = internal_symbol(hmodule, ip) || external_symbol(hmodule, ip) || return + + utf16_file = uninitialized LibC::WCHAR[LibC::MAX_PATH] + len = LibC.GetModuleFileNameW(hmodule, utf16_file, utf16_file.size) + if 0 < len < utf16_file.size + utf8_file = uninitialized UInt8[sizeof(UInt8[LibC::MAX_PATH][3])] + file = utf8_file.to_unsafe + appender = file.appender + String.each_utf16_char(utf16_file.to_slice[0, len + 1]) do |ch| + ch.each_byte { |b| appender << b } + end + else + file = Pointer(UInt8).null + end + + yield file, symbol, address + end end - end + + private def self.internal_symbol(hmodule, ip) + if coff_symbols = @@coff_symbols + if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out this_hmodule) != 0 && this_hmodule == hmodule + section_base, section_index = lookup_section(hmodule, ip) || return + offset = ip - section_base + section_coff_symbols = coff_symbols[section_index]? || return + next_sym = section_coff_symbols.bsearch_index { |sym| offset < sym.offset } || return + sym = section_coff_symbols[next_sym - 1]? || return + + {sym.name.to_unsafe, section_base + sym.offset} + end + end + end + + private def self.external_symbol(hmodule, ip) + if dir = data_directory(hmodule, LibC::IMAGE_DIRECTORY_ENTRY_EXPORT) + exports = dir.to_unsafe.as(LibC::IMAGE_EXPORT_DIRECTORY*).value + + found_address = Pointer(Void).null + found_index = -1 + + func_address_offsets = (hmodule + exports.addressOfFunctions).as(LibC::DWORD*).to_slice(exports.numberOfFunctions) + func_address_offsets.each_with_index do |offset, i| + address = hmodule + offset + if found_address < address <= ip + found_address, found_index = address, i + end + end + + return unless found_address + + func_name_ordinals = (hmodule + exports.addressOfNameOrdinals).as(LibC::WORD*).to_slice(exports.numberOfNames) + if ordinal_index = func_name_ordinals.index(&.== found_index) + symbol = (hmodule + (hmodule + exports.addressOfNames).as(LibC::DWORD*)[ordinal_index]).as(UInt8*) + {symbol, found_address} + end + end + end + + private def self.lookup_section(hmodule, ip) + dos_header = hmodule.as(LibC::IMAGE_DOS_HEADER*) + return unless dos_header.value.e_magic == 0x5A4D # MZ + + nt_header = (hmodule + dos_header.value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*) + return unless nt_header.value.signature == 0x00004550 # PE\0\0 + + section_headers = (nt_header + 1).as(LibC::IMAGE_SECTION_HEADER*).to_slice(nt_header.value.fileHeader.numberOfSections) + section_headers.each_with_index do |header, i| + base = hmodule + header.virtualAddress + if base <= ip < base + header.virtualSize + return base, i + end + end + end + + private def self.data_directory(hmodule, index) + dos_header = hmodule.as(LibC::IMAGE_DOS_HEADER*) + return unless dos_header.value.e_magic == 0x5A4D # MZ + + nt_header = (hmodule + dos_header.value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*) + return unless nt_header.value.signature == 0x00004550 # PE\0\0 + return unless nt_header.value.optionalHeader.magic == {{ flag?(:bits64) ? 0x20b : 0x10b }} + return unless index.in?(0...{16, nt_header.value.optionalHeader.numberOfRvaAndSizes}.min) + + directory = nt_header.value.optionalHeader.dataDirectory.to_unsafe[index] + if directory.virtualAddress != 0 + Bytes.new(hmodule.as(UInt8*) + directory.virtualAddress, directory.size, read_only: true) + end + end + {% else %} + private def self.dladdr(ip, &) + if LibC.dladdr(ip, out info) != 0 + yield info.dli_fname, info.dli_sname, info.dli_saddr + end + end + {% end %} end diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index 6ac59fa6db48..d7e3da8e35f1 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -1,5 +1,4 @@ require "c/dbghelp" -require "c/malloc" # :nodoc: struct Exception::CallStack @@ -33,38 +32,7 @@ struct Exception::CallStack end def self.setup_crash_handler - LibC.AddVectoredExceptionHandler(1, ->(exception_info) do - case exception_info.value.exceptionRecord.value.exceptionCode - when LibC::EXCEPTION_ACCESS_VIOLATION - addr = exception_info.value.exceptionRecord.value.exceptionInformation[1] - Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr) - print_backtrace(exception_info) - LibC._exit(1) - when LibC::EXCEPTION_STACK_OVERFLOW - LibC._resetstkoflw - Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n" - print_backtrace(exception_info) - LibC._exit(1) - else - LibC::EXCEPTION_CONTINUE_SEARCH - end - end) - - # ensure that even in the case of stack overflow there is enough reserved - # stack space for recovery (for other threads this is done in - # `Crystal::System::Thread.thread_proc`) - stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE - LibC.SetThreadStackGuarantee(pointerof(stack_size)) - - # this catches invalid argument checks inside the C runtime library - LibC._set_invalid_parameter_handler(->(expression, _function, _file, _line, _pReserved) do - message = expression ? String.from_utf16(expression)[0] : "(no message)" - Crystal::System.print_error "CRT invalid parameter handler invoked: %s\n", message - caller.each do |frame| - Crystal::System.print_error " from %s\n", frame - end - LibC._exit(1) - end) + Crystal::System::Signal.setup_seh_handler end {% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %} @@ -168,33 +136,6 @@ struct Exception::CallStack end end - # TODO: needed only if `__crystal_raise` fails, check if this actually works - {% if flag?(:gnu) %} - def self.print_backtrace : Nil - backtrace_fn = ->(context : LibUnwind::Context, data : Void*) do - last_frame = data.as(RepeatedFrame*) - - ip = {% if flag?(:arm) %} - Pointer(Void).new(__crystal_unwind_get_ip(context)) - {% else %} - Pointer(Void).new(LibUnwind.get_ip(context)) - {% end %} - - if last_frame.value.ip == ip - last_frame.value.incr - else - print_frame(last_frame.value) unless last_frame.value.ip.address == 0 - last_frame.value = RepeatedFrame.new ip - end - LibUnwind::ReasonCode::NO_REASON - end - - rf = RepeatedFrame.new(Pointer(Void).null) - LibUnwind.backtrace(backtrace_fn, pointerof(rf).as(Void*)) - print_frame(rf) - end - {% end %} - private def self.print_frame(repeated_frame) Crystal::System.print_error "[%p] ", repeated_frame.ip print_frame_location(repeated_frame) diff --git a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr index 37a95f3fa089..5612233553d9 100644 --- a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr @@ -9,6 +9,9 @@ lib LibC fun LoadLibraryExW(lpLibFileName : LPWSTR, hFile : HANDLE, dwFlags : DWORD) : HMODULE fun FreeLibrary(hLibModule : HMODULE) : BOOL + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002 + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004 + fun GetModuleHandleExW(dwFlags : DWORD, lpModuleName : LPWSTR, phModule : HMODULE*) : BOOL fun GetProcAddress(hModule : HMODULE, lpProcName : LPSTR) : FARPROC diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index 1db4b2def700..e9aecc01e033 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -392,11 +392,65 @@ lib LibC optionalHeader : IMAGE_OPTIONAL_HEADER64 end + IMAGE_DIRECTORY_ENTRY_EXPORT = 0 + IMAGE_DIRECTORY_ENTRY_IMPORT = 1 + IMAGE_DIRECTORY_ENTRY_IAT = 12 + + struct IMAGE_SECTION_HEADER + name : BYTE[8] + virtualSize : DWORD + virtualAddress : DWORD + sizeOfRawData : DWORD + pointerToRawData : DWORD + pointerToRelocations : DWORD + pointerToLinenumbers : DWORD + numberOfRelocations : WORD + numberOfLinenumbers : WORD + characteristics : DWORD + end + + struct IMAGE_EXPORT_DIRECTORY + characteristics : DWORD + timeDateStamp : DWORD + majorVersion : WORD + minorVersion : WORD + name : DWORD + base : DWORD + numberOfFunctions : DWORD + numberOfNames : DWORD + addressOfFunctions : DWORD + addressOfNames : DWORD + addressOfNameOrdinals : DWORD + end + struct IMAGE_IMPORT_BY_NAME hint : WORD name : CHAR[1] end + struct IMAGE_SYMBOL_n_name + short : DWORD + long : DWORD + end + + union IMAGE_SYMBOL_n + shortName : BYTE[8] + name : IMAGE_SYMBOL_n_name + end + + IMAGE_SYM_CLASS_EXTERNAL = 2 + IMAGE_SYM_CLASS_STATIC = 3 + + @[Packed] + struct IMAGE_SYMBOL + n : IMAGE_SYMBOL_n + value : DWORD + sectionNumber : Short + type : WORD + storageClass : BYTE + numberOfAuxSymbols : BYTE + end + union IMAGE_THUNK_DATA64_u1 forwarderString : ULongLong function : ULongLong diff --git a/src/raise.cr b/src/raise.cr index a8e06a3c3930..0c9563495a94 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -181,7 +181,7 @@ end 0u64 end {% else %} - {% mingw = flag?(:windows) && flag?(:gnu) %} + {% mingw = flag?(:win32) && flag?(:gnu) %} fun {{ mingw ? "__crystal_personality_imp".id : "__crystal_personality".id }}( version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*, ) : LibUnwind::ReasonCode From 24fc1a91ac5c9057a1de24090a40d7badf9f81c8 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 24 Oct 2024 18:30:54 +0800 Subject: [PATCH 1428/1551] Support OpenSSL on MSYS2 (#15111) --- src/openssl/lib_crypto.cr | 12 +++++++----- src/openssl/lib_ssl.cr | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr index 8d450b28ff17..fecc69ad44fc 100644 --- a/src/openssl/lib_crypto.cr +++ b/src/openssl/lib_crypto.cr @@ -1,6 +1,6 @@ {% begin %} lib LibCrypto - {% if flag?(:win32) %} + {% if flag?(:msvc) %} {% from_libressl = false %} {% ssl_version = nil %} {% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %} @@ -13,10 +13,12 @@ {% end %} {% ssl_version ||= "0.0.0" %} {% else %} - {% from_libressl = (`hash pkg-config 2> /dev/null || printf %s false` != "false") && - (`test -f $(pkg-config --silence-errors --variable=includedir libcrypto)/openssl/opensslv.h || printf %s false` != "false") && - (`printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libcrypto || true) -E -`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %} - {% ssl_version = `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libcrypto || printf %s 0.0.0`.split.last.gsub(/[^0-9.]/, "") %} + # these have to be wrapped in `sh -c` since for MinGW-w64 the compiler + # passes the command string to `LibC.CreateProcessW` + {% from_libressl = (`sh -c 'hash pkg-config 2> /dev/null || printf %s false'` != "false") && + (`sh -c 'test -f $(pkg-config --silence-errors --variable=includedir libcrypto)/openssl/opensslv.h || printf %s false'` != "false") && + (`sh -c 'printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libcrypto || true) -E -'`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %} + {% ssl_version = `sh -c 'hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libcrypto || printf %s 0.0.0'`.split.last.gsub(/[^0-9.]/, "") %} {% end %} {% if from_libressl %} diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr index 6adb3f172a3b..4e7e2def549c 100644 --- a/src/openssl/lib_ssl.cr +++ b/src/openssl/lib_ssl.cr @@ -6,7 +6,7 @@ require "./lib_crypto" {% begin %} lib LibSSL - {% if flag?(:win32) %} + {% if flag?(:msvc) %} {% from_libressl = false %} {% ssl_version = nil %} {% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %} @@ -19,10 +19,12 @@ require "./lib_crypto" {% end %} {% ssl_version ||= "0.0.0" %} {% else %} - {% from_libressl = (`hash pkg-config 2> /dev/null || printf %s false` != "false") && - (`test -f $(pkg-config --silence-errors --variable=includedir libssl)/openssl/opensslv.h || printf %s false` != "false") && - (`printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libssl || true) -E -`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %} - {% ssl_version = `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libssl || printf %s 0.0.0`.split.last.gsub(/[^0-9.]/, "") %} + # these have to be wrapped in `sh -c` since for MinGW-w64 the compiler + # passes the command string to `LibC.CreateProcessW` + {% from_libressl = (`sh -c 'hash pkg-config 2> /dev/null || printf %s false'` != "false") && + (`sh -c 'test -f $(pkg-config --silence-errors --variable=includedir libssl)/openssl/opensslv.h || printf %s false'` != "false") && + (`sh -c 'printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libssl || true) -E -'`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %} + {% ssl_version = `sh -c 'hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libssl || printf %s 0.0.0'`.split.last.gsub(/[^0-9.]/, "") %} {% end %} {% if from_libressl %} From 4016f39d2ed781031613ad027bb13a4529984bfa Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 25 Oct 2024 15:34:02 +0800 Subject: [PATCH 1429/1551] Use per-thread libxml2 global state on all platforms (#15121) libxml2's build files enable threads by default, so I'd be surprised if any platform still disables them (embedded systems don't count yet). --- src/xml.cr | 14 ++------------ src/xml/libxml2.cr | 10 ++-------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/xml.cr b/src/xml.cr index e0529be130f3..a9c9eab5d64e 100644 --- a/src/xml.cr +++ b/src/xml.cr @@ -107,12 +107,7 @@ module XML end protected def self.with_indent_tree_output(indent : Bool, &) - ptr = {% if flag?(:win32) %} - LibXML.__xmlIndentTreeOutput - {% else %} - pointerof(LibXML.xmlIndentTreeOutput) - {% end %} - + ptr = LibXML.__xmlIndentTreeOutput old, ptr.value = ptr.value, indent ? 1 : 0 begin yield @@ -122,12 +117,7 @@ module XML end protected def self.with_tree_indent_string(string : String, &) - ptr = {% if flag?(:win32) %} - LibXML.__xmlTreeIndentString - {% else %} - pointerof(LibXML.xmlTreeIndentString) - {% end %} - + ptr = LibXML.__xmlTreeIndentString old, ptr.value = ptr.value, string.to_unsafe begin yield diff --git a/src/xml/libxml2.cr b/src/xml/libxml2.cr index e1c2b8d12372..fbfb0702faef 100644 --- a/src/xml/libxml2.cr +++ b/src/xml/libxml2.cr @@ -13,14 +13,8 @@ lib LibXML fun xmlInitParser - # TODO: check if other platforms also support per-thread globals - {% if flag?(:win32) %} - fun __xmlIndentTreeOutput : Int* - fun __xmlTreeIndentString : UInt8** - {% else %} - $xmlIndentTreeOutput : Int - $xmlTreeIndentString : UInt8* - {% end %} + fun __xmlIndentTreeOutput : Int* + fun __xmlTreeIndentString : UInt8** alias Dtd = Void* alias Dict = Void* From 0236a68b93e9695d7b70ae61813c26ca9ed90f05 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 25 Oct 2024 15:34:20 +0800 Subject: [PATCH 1430/1551] Support "long format" DLL import libraries (#15119) `Crystal::System::LibraryArchive.imported_dlls` is used by the interpreter to obtain all dependent DLLs of a given import library. Currently, the method only supports libraries using the [short format](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#import-library-format), emitted by MSVC's linker. This PR implements the long format used by MinGW-w64 when passing the `-Wl,--out-implib` flag to `cc`. Specs will be added when `Crystal::Loader` supports MinGW-w64. In the mean time, if you have MSYS2, you could try this on the UCRT64 import libraries: ```crystal require "crystal/system/win32/library_archive" Crystal::System::LibraryArchive.imported_dlls("C:/msys64/ucrt64/lib/libpcre2-8.dll.a") # => Set{"libpcre2-8-0.dll"} Crystal::System::LibraryArchive.imported_dlls("C:/msys64/ucrt64/lib/libucrt.a") # => Set{"api-ms-win-crt-utility-l1-1-0.dll", "api-ms-win-crt-time-l1-1-0.dll", "api-ms-win-crt-string-l1-1-0.dll", "api-ms-win-crt-stdio-l1-1-0.dll", "api-ms-win-crt-runtime-l1-1-0.dll", "api-ms-win-crt-process-l1-1-0.dll", "api-ms-win-crt-private-l1-1-0.dll", "api-ms-win-crt-multibyte-l1-1-0.dll", "api-ms-win-crt-math-l1-1-0.dll", "api-ms-win-crt-locale-l1-1-0.dll", "api-ms-win-crt-heap-l1-1-0.dll", "api-ms-win-crt-filesystem-l1-1-0.dll", "api-ms-win-crt-environment-l1-1-0.dll", "api-ms-win-crt-convert-l1-1-0.dll", "api-ms-win-crt-conio-l1-1-0.dll"} ``` --- src/crystal/system/win32/library_archive.cr | 74 +++++++++++++++++---- src/lib_c/x86_64-windows-msvc/c/winnt.cr | 2 + 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/crystal/system/win32/library_archive.cr b/src/crystal/system/win32/library_archive.cr index 775677938bac..24c50f3405fa 100644 --- a/src/crystal/system/win32/library_archive.cr +++ b/src/crystal/system/win32/library_archive.cr @@ -17,6 +17,10 @@ module Crystal::System::LibraryArchive private struct COFFReader getter dlls = Set(String).new + # MSVC-style import libraries include the `__NULL_IMPORT_DESCRIPTOR` symbol, + # MinGW-style ones do not + getter? msvc = false + def initialize(@ar : ::File) end @@ -39,6 +43,7 @@ module Crystal::System::LibraryArchive if first first = false return unless filename == "/" + handle_first_member(io) elsif !filename.in?("/", "//") handle_standard_member(io) end @@ -62,26 +67,69 @@ module Crystal::System::LibraryArchive @ar.seek(new_pos) end + private def handle_first_member(io) + symbol_count = io.read_bytes(UInt32, IO::ByteFormat::BigEndian) + + # 4-byte offset per symbol + io.skip(symbol_count * 4) + + symbol_count.times do + symbol = io.gets('\0', chomp: true) + if symbol == "__NULL_IMPORT_DESCRIPTOR" + @msvc = true + break + end + end + end + private def handle_standard_member(io) - sig1 = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian) - return unless sig1 == 0x0000 # IMAGE_FILE_MACHINE_UNKNOWN + machine = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian) + section_count = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian) - sig2 = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian) - return unless sig2 == 0xFFFF + if machine == 0x0000 && section_count == 0xFFFF + # short import library + version = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian) + return unless version == 0 # 1 and 2 are used by object files (ANON_OBJECT_HEADER) - version = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian) - return unless version == 0 # 1 and 2 are used by object files (ANON_OBJECT_HEADER) + # machine(2) + time(4) + size(4) + ordinal/hint(2) + flags(2) + io.skip(14) - # machine(2) + time(4) + size(4) + ordinal/hint(2) + flags(2) - io.skip(14) + # TODO: is there a way to do this without constructing a temporary string, + # but with the optimizations present in `IO#gets`? + return unless io.gets('\0') # symbol name - # TODO: is there a way to do this without constructing a temporary string, - # but with the optimizations present in `IO#gets`? - return unless io.gets('\0') # symbol name + if dll_name = io.gets('\0', chomp: true) + @dlls << dll_name if valid_dll?(dll_name) + end + else + # long import library, code based on GNU binutils `dlltool -I`: + # https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=binutils/dlltool.c;hb=967dc35c78adb85ee1e2e596047d9dc69107a9db#l3231 + + # timeDateStamp(4) + pointerToSymbolTable(4) + numberOfSymbols(4) + sizeOfOptionalHeader(2) + characteristics(2) + io.skip(16) + + section_count.times do |i| + section_header = uninitialized LibC::IMAGE_SECTION_HEADER + return unless io.read_fully?(pointerof(section_header).to_slice(1).to_unsafe_bytes) + + name = String.new(section_header.name.to_unsafe, section_header.name.index(0) || section_header.name.size) + next unless name == (msvc? ? ".idata$6" : ".idata$7") + + if msvc? ? section_header.characteristics.bits_set?(LibC::IMAGE_SCN_CNT_INITIALIZED_DATA) : section_header.pointerToRelocations == 0 + bytes_read = sizeof(LibC::IMAGE_FILE_HEADER) + sizeof(LibC::IMAGE_SECTION_HEADER) * (i + 1) + io.skip(section_header.pointerToRawData - bytes_read) + if dll_name = io.gets('\0', chomp: true, limit: section_header.sizeOfRawData) + @dlls << dll_name if valid_dll?(dll_name) + end + end - if dll_name = io.gets('\0', chomp: true) - @dlls << dll_name + return + end end end + + private def valid_dll?(name) + name.size >= 5 && name[-4..].compare(".dll", case_insensitive: true) == 0 + end end end diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index e9aecc01e033..99c8f24ac9e1 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -396,6 +396,8 @@ lib LibC IMAGE_DIRECTORY_ENTRY_IMPORT = 1 IMAGE_DIRECTORY_ENTRY_IAT = 12 + IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 + struct IMAGE_SECTION_HEADER name : BYTE[8] virtualSize : DWORD From 8a96e33ae303bcc819cda90fe283e2643165ee64 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 25 Oct 2024 09:35:49 +0200 Subject: [PATCH 1431/1551] OpenBSD: fix integration and broken specs (#15118) Fixes compatibility with OpenBSD 7.4 that enforced indirect branch tracking by default but we're not compatible yet (see #13665), so we must disable it. With this patch I can run the std specs, except for the SSL specs because of openssl/libressl mess, as well as the compiler specs, except for the interpreter specs that regularly crash with SIGABRT (may help to debug issues in the interpreter). Note: the segfault handler is broken on OpenBSD and processes eventually crash with SIGILL after receiving SIGSEGV. --- bin/crystal | 2 +- spec/compiler/loader/unix_spec.cr | 6 +- spec/std/exception/call_stack_spec.cr | 17 +++-- spec/std/io/io_spec.cr | 8 +-- spec/std/kernel_spec.cr | 89 ++++++++++++++------------- spec/std/socket/addrinfo_spec.cr | 4 +- spec/std/socket/tcp_server_spec.cr | 6 +- spec/std/socket/tcp_socket_spec.cr | 6 +- spec/std/socket/udp_socket_spec.cr | 3 + src/compiler/crystal/compiler.cr | 13 ++++ src/lib_c/x86_64-openbsd/c/netdb.cr | 1 + 11 files changed, 93 insertions(+), 62 deletions(-) diff --git a/bin/crystal b/bin/crystal index e8abdff30ee8..a1fddf1c58b4 100755 --- a/bin/crystal +++ b/bin/crystal @@ -196,7 +196,7 @@ esac if [ -x "$CRYSTAL_DIR/${CRYSTAL_BIN}" ]; then __warning_msg "Using compiled compiler at ${CRYSTAL_DIR#"$PWD/"}/${CRYSTAL_BIN}" exec "$CRYSTAL_DIR/${CRYSTAL_BIN}" "$@" -elif !($PARENT_CRYSTAL_EXISTS); then +elif (! $PARENT_CRYSTAL_EXISTS); then __error_msg 'You need to have a crystal executable in your path! or set CRYSTAL env variable' exit 1 else diff --git a/spec/compiler/loader/unix_spec.cr b/spec/compiler/loader/unix_spec.cr index 42a63b88e860..6adcb040148b 100644 --- a/spec/compiler/loader/unix_spec.cr +++ b/spec/compiler/loader/unix_spec.cr @@ -40,7 +40,11 @@ describe Crystal::Loader do exc = expect_raises(Crystal::Loader::LoadError, /no such file|not found|cannot open/i) do Crystal::Loader.parse(["-l", "foo/bar.o"], search_paths: [] of String) end - exc.message.should contain File.join(Dir.current, "foo", "bar.o") + {% if flag?(:openbsd) %} + exc.message.should contain "foo/bar.o" + {% else %} + exc.message.should contain File.join(Dir.current, "foo", "bar.o") + {% end %} end end diff --git a/spec/std/exception/call_stack_spec.cr b/spec/std/exception/call_stack_spec.cr index 7c6f5d746bdc..6df0741d2a7b 100644 --- a/spec/std/exception/call_stack_spec.cr +++ b/spec/std/exception/call_stack_spec.cr @@ -55,14 +55,19 @@ describe "Backtrace" do error.to_s.should contain("IndexError") end - it "prints crash backtrace to stderr", tags: %w[slow] do - sample = datapath("crash_backtrace_sample") + {% if flag?(:openbsd) %} + # FIXME: the segfault handler doesn't work on OpenBSD + pending "prints crash backtrace to stderr" + {% else %} + it "prints crash backtrace to stderr", tags: %w[slow] do + sample = datapath("crash_backtrace_sample") - _, output, error = compile_and_run_file(sample) + _, output, error = compile_and_run_file(sample) - output.to_s.should be_empty - error.to_s.should contain("Invalid memory access") - end + output.to_s.should be_empty + error.to_s.should contain("Invalid memory access") + end + {% end %} # Do not test this on platforms that cannot remove the current working # directory of the process: diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 620a1d034d9f..1904940f4883 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -425,9 +425,9 @@ describe IO do str.read_fully?(slice).should be_nil end - # pipe(2) returns bidirectional file descriptors on FreeBSD and Solaris, + # pipe(2) returns bidirectional file descriptors on some platforms, # gate this test behind the platform flag. - {% unless flag?(:freebsd) || flag?(:solaris) %} + {% unless flag?(:freebsd) || flag?(:solaris) || flag?(:openbsd) %} it "raises if trying to read to an IO not opened for reading" do IO.pipe do |r, w| expect_raises(IO::Error, "File not open for reading") do @@ -574,9 +574,9 @@ describe IO do io.read_byte.should be_nil end - # pipe(2) returns bidirectional file descriptors on FreeBSD and Solaris, + # pipe(2) returns bidirectional file descriptors on some platforms, # gate this test behind the platform flag. - {% unless flag?(:freebsd) || flag?(:solaris) %} + {% unless flag?(:freebsd) || flag?(:solaris) || flag?(:openbsd) %} it "raises if trying to write to an IO not opened for writing" do IO.pipe do |r, w| # unless sync is used the flush on close triggers the exception again diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index f8e4ff1e8ae2..0a682af8381b 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -251,55 +251,60 @@ describe "at_exit" do end end -describe "hardware exception" do - it "reports invalid memory access", tags: %w[slow] do - status, _, error = compile_and_run_source <<-'CRYSTAL' - puts Pointer(Int64).null.value - CRYSTAL - - status.success?.should be_false - error.should contain("Invalid memory access") - error.should_not contain("Stack overflow") - end - - {% if flag?(:netbsd) %} - # FIXME: on netbsd the process crashes with SIGILL after receiving SIGSEGV - pending "detects stack overflow on the main stack" - pending "detects stack overflow on a fiber stack" - {% else %} - it "detects stack overflow on the main stack", tags: %w[slow] do - # This spec can take some time under FreeBSD where - # the default stack size is 0.5G. Setting a - # smaller stack size with `ulimit -s 8192` - # will address this. +{% if flag?(:openbsd) %} + # FIXME: the segfault handler doesn't work on OpenBSD + pending "hardware exception" +{% else %} + describe "hardware exception" do + it "reports invalid memory access", tags: %w[slow] do status, _, error = compile_and_run_source <<-'CRYSTAL' - def foo - y = StaticArray(Int8, 512).new(0) - foo - end - foo + puts Pointer(Int64).null.value CRYSTAL status.success?.should be_false - error.should contain("Stack overflow") + error.should contain("Invalid memory access") + error.should_not contain("Stack overflow") end - it "detects stack overflow on a fiber stack", tags: %w[slow] do - status, _, error = compile_and_run_source <<-'CRYSTAL' - def foo - y = StaticArray(Int8, 512).new(0) + {% if flag?(:netbsd) %} + # FIXME: on netbsd the process crashes with SIGILL after receiving SIGSEGV + pending "detects stack overflow on the main stack" + pending "detects stack overflow on a fiber stack" + {% else %} + it "detects stack overflow on the main stack", tags: %w[slow] do + # This spec can take some time under FreeBSD where + # the default stack size is 0.5G. Setting a + # smaller stack size with `ulimit -s 8192` + # will address this. + status, _, error = compile_and_run_source <<-'CRYSTAL' + def foo + y = StaticArray(Int8, 512).new(0) + foo + end foo - end + CRYSTAL - spawn do - foo - end + status.success?.should be_false + error.should contain("Stack overflow") + end - sleep 60.seconds - CRYSTAL + it "detects stack overflow on a fiber stack", tags: %w[slow] do + status, _, error = compile_and_run_source <<-'CRYSTAL' + def foo + y = StaticArray(Int8, 512).new(0) + foo + end - status.success?.should be_false - error.should contain("Stack overflow") - end - {% end %} -end + spawn do + foo + end + + sleep 60.seconds + CRYSTAL + + status.success?.should be_false + error.should contain("Stack overflow") + end + {% end %} + end +{% end %} diff --git a/spec/std/socket/addrinfo_spec.cr b/spec/std/socket/addrinfo_spec.cr index 109eb383562b..b1d6b459623d 100644 --- a/spec/std/socket/addrinfo_spec.cr +++ b/spec/std/socket/addrinfo_spec.cr @@ -24,8 +24,8 @@ describe Socket::Addrinfo, tags: "network" do end it "raises helpful message on getaddrinfo failure" do - expect_raises(Socket::Addrinfo::Error, "Hostname lookup for badhostname failed: ") do - Socket::Addrinfo.resolve("badhostname", 80, type: Socket::Type::DGRAM) + expect_raises(Socket::Addrinfo::Error, "Hostname lookup for badhostname.unknown failed: ") do + Socket::Addrinfo.resolve("badhostname.unknown", 80, type: Socket::Type::DGRAM) end end diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr index ee3c861956b8..a7d85b8edeff 100644 --- a/spec/std/socket/tcp_server_spec.cr +++ b/spec/std/socket/tcp_server_spec.cr @@ -43,7 +43,7 @@ describe TCPServer, tags: "network" do end error.os_error.should eq({% if flag?(:win32) %} WinError::WSATYPE_NOT_FOUND - {% elsif flag?(:linux) && !flag?(:android) %} + {% elsif (flag?(:linux) && !flag?(:android)) || flag?(:openbsd) %} Errno.new(LibC::EAI_SERVICE) {% else %} Errno.new(LibC::EAI_NONAME) @@ -96,7 +96,7 @@ describe TCPServer, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) || flag?(:netbsd) %} + {% elsif flag?(:android) || flag?(:netbsd) || flag?(:openbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error @@ -110,7 +110,7 @@ describe TCPServer, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) || flag?(:netbsd) %} + {% elsif flag?(:android) || flag?(:netbsd) || flag?(:openbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index 5ec3467362e0..0b3a381372bf 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -47,7 +47,7 @@ describe TCPSocket, tags: "network" do end error.os_error.should eq({% if flag?(:win32) %} WinError::WSATYPE_NOT_FOUND - {% elsif flag?(:linux) && !flag?(:android) %} + {% elsif (flag?(:linux) && !flag?(:android)) || flag?(:openbsd) %} Errno.new(LibC::EAI_SERVICE) {% else %} Errno.new(LibC::EAI_NONAME) @@ -79,7 +79,7 @@ describe TCPSocket, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) || flag?(:netbsd) %} + {% elsif flag?(:android) || flag?(:netbsd) || flag?(:openbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error @@ -93,7 +93,7 @@ describe TCPSocket, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) || flag?(:netbsd) %} + {% elsif flag?(:android) || flag?(:netbsd) || flag?(:openbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr index 9b624110fad9..dc66d8038036 100644 --- a/spec/std/socket/udp_socket_spec.cr +++ b/spec/std/socket/udp_socket_spec.cr @@ -88,6 +88,9 @@ describe UDPSocket, tags: "network" do elsif {{ flag?(:netbsd) }} && family == Socket::Family::INET6 # FIXME: fails with "setsockopt: EADDRNOTAVAIL" pending "joins and transmits to multicast groups" + elsif {{ flag?(:openbsd) }} + # FIXME: fails with "setsockopt: EINVAL (ipv4) or EADDRNOTAVAIL (ipv6)" + pending "joins and transmits to multicast groups" else it "joins and transmits to multicast groups" do udp = UDPSocket.new(family) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 6c7664bacc25..83509bf88392 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -500,6 +500,19 @@ module Crystal else link_flags = @link_flags || "" link_flags += " -rdynamic" + + if program.has_flag?("freebsd") || program.has_flag?("openbsd") + # pkgs are installed to usr/local/lib but it's not in LIBRARY_PATH by + # default; we declare it to ease linking on these platforms: + link_flags += " -L/usr/local/lib" + end + + if program.has_flag?("openbsd") + # OpenBSD requires Indirect Branch Tracking by default, but we're not + # compatible (yet), so we disable it for now: + link_flags += " -Wl,-znobtcfi" + end + {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags(@cross_compile)}), object_names} end end diff --git a/src/lib_c/x86_64-openbsd/c/netdb.cr b/src/lib_c/x86_64-openbsd/c/netdb.cr index be3c5f06ab2d..6dd1e6c8513f 100644 --- a/src/lib_c/x86_64-openbsd/c/netdb.cr +++ b/src/lib_c/x86_64-openbsd/c/netdb.cr @@ -13,6 +13,7 @@ lib LibC EAI_FAIL = -4 EAI_FAMILY = -6 EAI_MEMORY = -10 + EAI_NODATA = -5 EAI_NONAME = -2 EAI_SERVICE = -8 EAI_SOCKTYPE = -7 From 0987812fad23b4c28dc6101a3667c3285a238bf1 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 25 Oct 2024 23:50:13 +0800 Subject: [PATCH 1432/1551] Support `.exe` file extension in `Makefile` on MSYS2 (#15123) `.build/crystal` already adds the `.exe` file extension in `Makefile` when run on MSYS2. This PR does the same to the spec executables so that running `make std_spec` twice will only build the executable once. --- Makefile | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index b39c089bef99..37c42890c9f4 100644 --- a/Makefile +++ b/Makefile @@ -68,10 +68,10 @@ CXXFLAGS += $(if $(debug),-g -O0) # MSYS2 support (native Windows should use `Makefile.win` instead) ifeq ($(OS),Windows_NT) - CRYSTAL_BIN := crystal.exe + EXE := .exe WINDOWS := 1 else - CRYSTAL_BIN := crystal + EXE := WINDOWS := endif @@ -112,28 +112,28 @@ test: spec ## Run tests spec: std_spec primitives_spec compiler_spec .PHONY: std_spec -std_spec: $(O)/std_spec ## Run standard library specs - $(O)/std_spec $(SPEC_FLAGS) +std_spec: $(O)/std_spec$(EXE) ## Run standard library specs + $(O)/std_spec$(EXE) $(SPEC_FLAGS) .PHONY: compiler_spec -compiler_spec: $(O)/compiler_spec ## Run compiler specs - $(O)/compiler_spec $(SPEC_FLAGS) +compiler_spec: $(O)/compiler_spec$(EXE) ## Run compiler specs + $(O)/compiler_spec$(EXE) $(SPEC_FLAGS) .PHONY: primitives_spec -primitives_spec: $(O)/primitives_spec ## Run primitives specs - $(O)/primitives_spec $(SPEC_FLAGS) +primitives_spec: $(O)/primitives_spec$(EXE) ## Run primitives specs + $(O)/primitives_spec$(EXE) $(SPEC_FLAGS) .PHONY: interpreter_spec -interpreter_spec: $(O)/interpreter_spec ## Run interpreter specs - $(O)/interpreter_spec $(SPEC_FLAGS) +interpreter_spec: $(O)/interpreter_spec$(EXE) ## Run interpreter specs + $(O)/interpreter_spec$(EXE) $(SPEC_FLAGS) .PHONY: smoke_test smoke_test: ## Build specs as a smoke test -smoke_test: $(O)/std_spec $(O)/compiler_spec $(O)/$(CRYSTAL_BIN) +smoke_test: $(O)/std_spec$(EXE) $(O)/compiler_spec$(EXE) $(O)/$(CRYSTAL)$(EXE) .PHONY: all_spec -all_spec: $(O)/all_spec ## Run all specs (note: this builds a huge program; `test` recipe builds individual binaries and is recommended for reduced resource usage) - $(O)/all_spec $(SPEC_FLAGS) +all_spec: $(O)/all_spec$(EXE) ## Run all specs (note: this builds a huge program; `test` recipe builds individual binaries and is recommended for reduced resource usage) + $(O)/all_spec$(EXE) $(SPEC_FLAGS) .PHONY: samples samples: ## Build example programs @@ -146,7 +146,7 @@ docs: ## Generate standard library documentation cp -av doc/ docs/ .PHONY: crystal -crystal: $(O)/$(CRYSTAL_BIN) ## Build the compiler +crystal: $(O)/$(CRYSTAL)$(EXE) ## Build the compiler .PHONY: deps llvm_ext deps: $(DEPS) ## Build dependencies @@ -161,9 +161,9 @@ generate_data: ## Run generator scripts for Unicode, SSL config, ... $(MAKE) -B -f scripts/generate_data.mk .PHONY: install -install: $(O)/$(CRYSTAL_BIN) man/crystal.1.gz ## Install the compiler at DESTDIR +install: $(O)/$(CRYSTAL)$(EXE) man/crystal.1.gz ## Install the compiler at DESTDIR $(INSTALL) -d -m 0755 "$(BINDIR)/" - $(INSTALL) -m 0755 "$(O)/$(CRYSTAL_BIN)" "$(BINDIR)/$(CRYSTAL_BIN)" + $(INSTALL) -m 0755 "$(O)/$(CRYSTAL)$(EXE)" "$(BINDIR)/$(CRYSTAL)$(EXE)" $(INSTALL) -d -m 0755 $(DATADIR) cp -av src "$(DATADIR)/src" @@ -183,14 +183,14 @@ install: $(O)/$(CRYSTAL_BIN) man/crystal.1.gz ## Install the compiler at DESTDIR ifeq ($(WINDOWS),1) .PHONY: install_dlls -install_dlls: $(O)/$(CRYSTAL_BIN) ## Install the compiler's dependent DLLs at DESTDIR (Windows only) +install_dlls: $(O)/$(CRYSTAL)$(EXE) ## Install the compiler's dependent DLLs at DESTDIR (Windows only) $(INSTALL) -d -m 0755 "$(BINDIR)/" - @ldd $(O)/$(CRYSTAL_BIN) | grep -iv ' => /c/windows/system32' | sed 's/.* => //; s/ (.*//' | xargs -t -i $(INSTALL) -m 0755 '{}' "$(BINDIR)/" + @ldd $(O)/$(CRYSTAL)$(EXE) | grep -iv ' => /c/windows/system32' | sed 's/.* => //; s/ (.*//' | xargs -t -i $(INSTALL) -m 0755 '{}' "$(BINDIR)/" endif .PHONY: uninstall uninstall: ## Uninstall the compiler from DESTDIR - rm -f "$(BINDIR)/$(CRYSTAL_BIN)" + rm -f "$(BINDIR)/$(CRYSTAL)$(EXE)" rm -rf "$(DATADIR)/src" @@ -212,31 +212,31 @@ uninstall_docs: ## Uninstall docs from DESTDIR rm -rf "$(DATADIR)/docs" rm -rf "$(DATADIR)/examples" -$(O)/all_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES) +$(O)/all_spec$(EXE): $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(call check_llvm_config) @mkdir -p $(O) $(EXPORT_CC) $(EXPORTS) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/all_spec.cr -$(O)/std_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES) +$(O)/std_spec$(EXE): $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(call check_llvm_config) @mkdir -p $(O) $(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/std_spec.cr -$(O)/compiler_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES) +$(O)/compiler_spec$(EXE): $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(call check_llvm_config) @mkdir -p $(O) $(EXPORT_CC) $(EXPORTS) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler_spec.cr --release -$(O)/primitives_spec: $(O)/$(CRYSTAL_BIN) $(DEPS) $(SOURCES) $(SPEC_SOURCES) +$(O)/primitives_spec$(EXE): $(O)/$(CRYSTAL)$(EXE) $(DEPS) $(SOURCES) $(SPEC_SOURCES) @mkdir -p $(O) $(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/primitives_spec.cr -$(O)/interpreter_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES) +$(O)/interpreter_spec$(EXE): $(DEPS) $(SOURCES) $(SPEC_SOURCES) $(eval interpreter=1) @mkdir -p $(O) $(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler/interpreter_spec.cr -$(O)/$(CRYSTAL_BIN): $(DEPS) $(SOURCES) +$(O)/$(CRYSTAL)$(EXE): $(DEPS) $(SOURCES) $(call check_llvm_config) @mkdir -p $(O) @# NOTE: USE_PCRE1 is only used for testing compatibility with legacy environments that don't provide libpcre2. From adeda557febce9abdce0b2ae48dcac9e3a6953f0 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 25 Oct 2024 23:50:28 +0800 Subject: [PATCH 1433/1551] Allow skipping compiler tool specs that require Git (#15125) Under rare circumstances like a minimal MSYS2 setup (e.g. the CI in #15124), Git might not be available at all when running the compiler specs, but this is not a hard failure for `crystal init` and `crystal docs`. This PR marks the relevant specs as pending if Git cannot be found. --- .../crystal/tools/doc/project_info_spec.cr | 2 ++ spec/compiler/crystal/tools/init_spec.cr | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/spec/compiler/crystal/tools/doc/project_info_spec.cr b/spec/compiler/crystal/tools/doc/project_info_spec.cr index 61bf20c2da67..c92ee9d12f9d 100644 --- a/spec/compiler/crystal/tools/doc/project_info_spec.cr +++ b/spec/compiler/crystal/tools/doc/project_info_spec.cr @@ -5,6 +5,8 @@ private alias ProjectInfo = Crystal::Doc::ProjectInfo private def run_git(command) Process.run(%(git -c user.email="" -c user.name="spec" #{command}), shell: true) +rescue IO::Error + pending! "Git is not available" end private def assert_with_defaults(initial, expected, *, file = __FILE__, line = __LINE__) diff --git a/spec/compiler/crystal/tools/init_spec.cr b/spec/compiler/crystal/tools/init_spec.cr index 71bbd8de9d35..9149986a673c 100644 --- a/spec/compiler/crystal/tools/init_spec.cr +++ b/spec/compiler/crystal/tools/init_spec.cr @@ -41,9 +41,17 @@ private def run_init_project(skeleton_type, name, author, email, github_name, di ).run end +private def git_available? + Process.run(Crystal::Git.executable).success? +rescue IO::Error + false +end + module Crystal describe Init::InitProject do it "correctly uses git config" do + pending! "Git is not available" unless git_available? + within_temporary_directory do File.write(".gitconfig", <<-INI) [user] @@ -212,9 +220,11 @@ module Crystal ) end - with_file "example/.git/config" { } + if git_available? + with_file "example/.git/config" { } - with_file "other-example-directory/.git/config" { } + with_file "other-example-directory/.git/config" { } + end end end end From 0e1018fb2369afef0bf4c4aa9a3944cff10c39ec Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 25 Oct 2024 23:50:56 +0800 Subject: [PATCH 1434/1551] Add MinGW-w64 CI workflow for stdlib and compiler specs (#15124) The use of the build artifact is temporary; once Crystal is available from MSYS2's Pacman, we should be able to use that directly for the test job. (The build job needs to stay because we need that artifact to bootstrap the Pacman build first.) The MSYS Git is only needed for `spec/compiler/crystal/tools/init_spec.cr` and `spec/compiler/crystal/tools/doc/project_info_spec.cr`. Apparently some of the specs in those files fail if Git cannot be located at all. As a final touch, this PR also ensures build commands have their embedded newlines replaced with whitespace, just like for MSVC, otherwise tools like `ld.exe` might consider `\n-lLLVM-18\n` on the command line a single argument and fail. --- .github/workflows/mingw-w64.yml | 63 ++++++++++++++++++++++++++++++++ src/compiler/crystal/compiler.cr | 2 +- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml index 2370ae133cdd..b32aed414f77 100644 --- a/.github/workflows/mingw-w64.yml +++ b/.github/workflows/mingw-w64.yml @@ -8,6 +8,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} +env: + SPEC_SPLIT_DOTS: 160 + jobs: x86_64-mingw-w64-cross-compile: runs-on: ubuntu-24.04 @@ -89,3 +92,63 @@ jobs: path: | bin/ share/ + + x86_64-mingw-w64-test: + runs-on: windows-2022 + needs: [x86_64-mingw-w64-link] + steps: + - name: Setup MSYS2 + id: msys2 + uses: msys2/setup-msys2@ddf331adaebd714795f1042345e6ca57bd66cea8 # v2.24.1 + with: + msystem: UCRT64 + update: true + install: >- + git + make + mingw-w64-ucrt-x86_64-pkgconf + mingw-w64-ucrt-x86_64-cc + mingw-w64-ucrt-x86_64-gc + mingw-w64-ucrt-x86_64-pcre2 + mingw-w64-ucrt-x86_64-libiconv + mingw-w64-ucrt-x86_64-zlib + mingw-w64-ucrt-x86_64-llvm + mingw-w64-ucrt-x86_64-gmp + mingw-w64-ucrt-x86_64-libxml2 + mingw-w64-ucrt-x86_64-libyaml + mingw-w64-ucrt-x86_64-openssl + mingw-w64-ucrt-x86_64-libffi + + - name: Disable CRLF line ending substitution + run: | + git config --global core.autocrlf false + + - name: Download Crystal source + uses: actions/checkout@v4 + + - name: Download Crystal executable + uses: actions/download-artifact@v4 + with: + name: x86_64-mingw-w64-crystal + path: crystal + + - name: Run stdlib specs + shell: msys2 {0} + run: | + export PATH="$(pwd)/crystal/bin:$PATH" + export CRYSTAL_SPEC_COMPILER_BIN="$(pwd)/crystal/bin/crystal.exe" + make std_spec + + - name: Run compiler specs + shell: msys2 {0} + run: | + export PATH="$(pwd)/crystal/bin:$PATH" + export CRYSTAL_SPEC_COMPILER_BIN="$(pwd)/crystal/bin/crystal.exe" + make compiler_spec FLAGS=-Dwithout_ffi + + - name: Run primitives specs + shell: msys2 {0} + run: | + export PATH="$(pwd)/crystal/bin:$PATH" + export CRYSTAL_SPEC_COMPILER_BIN="$(pwd)/crystal/bin/crystal.exe" + make -o .build/crystal.exe primitives_spec # we know the compiler is fresh; do not rebuild it here diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 83509bf88392..ffd2b3e4a7d2 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -479,7 +479,7 @@ module Crystal link_flags += " -Wl,--stack,0x800000" lib_flags = program.lib_flags(@cross_compile) lib_flags = expand_lib_flags(lib_flags) if expand - cmd = %(#{DEFAULT_LINKER} #{Process.quote_windows(object_names)} -o #{Process.quote_windows(output_filename)} #{link_flags} #{lib_flags}) + cmd = %(#{DEFAULT_LINKER} #{Process.quote_windows(object_names)} -o #{Process.quote_windows(output_filename)} #{link_flags} #{lib_flags}).gsub('\n', ' ') if cmd.size > 32000 # The command line would be too big, pass the args through a file instead. From be1e1e54308c3986f59af993d290f70ab5516daf Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 28 Oct 2024 17:50:48 +0800 Subject: [PATCH 1435/1551] Add `cc`'s search paths to Unix dynamic library loader (#15127) --- spec/std/llvm/aarch64_spec.cr | 7 ------- spec/std/llvm/arm_abi_spec.cr | 7 ------- spec/std/llvm/avr_spec.cr | 7 ------- spec/std/llvm/llvm_spec.cr | 7 ------- spec/std/llvm/type_spec.cr | 7 ------- spec/std/llvm/x86_64_abi_spec.cr | 7 ------- spec/std/llvm/x86_abi_spec.cr | 7 ------- src/compiler/crystal/loader/unix.cr | 31 ++++++++++++++++++++++++++++- 8 files changed, 30 insertions(+), 50 deletions(-) diff --git a/spec/std/llvm/aarch64_spec.cr b/spec/std/llvm/aarch64_spec.cr index 6e2bac04dc47..41a308b480ec 100644 --- a/spec/std/llvm/aarch64_spec.cr +++ b/spec/std/llvm/aarch64_spec.cr @@ -1,11 +1,4 @@ require "spec" - -{% if flag?(:interpreted) && !flag?(:win32) %} - # TODO: figure out how to link against libstdc++ in interpreted code (#14398) - pending LLVM::ABI::AArch64 - {% skip_file %} -{% end %} - require "llvm" {% if LibLLVM::BUILT_TARGETS.includes?(:aarch64) %} diff --git a/spec/std/llvm/arm_abi_spec.cr b/spec/std/llvm/arm_abi_spec.cr index 8132ca0a38ce..98ae9b588a41 100644 --- a/spec/std/llvm/arm_abi_spec.cr +++ b/spec/std/llvm/arm_abi_spec.cr @@ -1,11 +1,4 @@ require "spec" - -{% if flag?(:interpreted) && !flag?(:win32) %} - # TODO: figure out how to link against libstdc++ in interpreted code (#14398) - pending LLVM::ABI::ARM - {% skip_file %} -{% end %} - require "llvm" {% if LibLLVM::BUILT_TARGETS.includes?(:arm) %} diff --git a/spec/std/llvm/avr_spec.cr b/spec/std/llvm/avr_spec.cr index 3c23c9bbed6e..a6e95d8937be 100644 --- a/spec/std/llvm/avr_spec.cr +++ b/spec/std/llvm/avr_spec.cr @@ -1,11 +1,4 @@ require "spec" - -{% if flag?(:interpreted) && !flag?(:win32) %} - # TODO: figure out how to link against libstdc++ in interpreted code (#14398) - pending LLVM::ABI::AVR - {% skip_file %} -{% end %} - require "llvm" {% if LibLLVM::BUILT_TARGETS.includes?(:avr) %} diff --git a/spec/std/llvm/llvm_spec.cr b/spec/std/llvm/llvm_spec.cr index 17ea96d5e261..e39398879e5d 100644 --- a/spec/std/llvm/llvm_spec.cr +++ b/spec/std/llvm/llvm_spec.cr @@ -1,11 +1,4 @@ require "spec" - -{% if flag?(:interpreted) && !flag?(:win32) %} - # TODO: figure out how to link against libstdc++ in interpreted code (#14398) - pending LLVM - {% skip_file %} -{% end %} - require "llvm" describe LLVM do diff --git a/spec/std/llvm/type_spec.cr b/spec/std/llvm/type_spec.cr index 8c6b99662ca2..94e34f226250 100644 --- a/spec/std/llvm/type_spec.cr +++ b/spec/std/llvm/type_spec.cr @@ -1,11 +1,4 @@ require "spec" - -{% if flag?(:interpreted) && !flag?(:win32) %} - # TODO: figure out how to link against libstdc++ in interpreted code (#14398) - pending LLVM::Type - {% skip_file %} -{% end %} - require "llvm" describe LLVM::Type do diff --git a/spec/std/llvm/x86_64_abi_spec.cr b/spec/std/llvm/x86_64_abi_spec.cr index 8b971a679c2a..0ba644cefa01 100644 --- a/spec/std/llvm/x86_64_abi_spec.cr +++ b/spec/std/llvm/x86_64_abi_spec.cr @@ -1,11 +1,4 @@ require "spec" - -{% if flag?(:interpreted) && !flag?(:win32) %} - # TODO: figure out how to link against libstdc++ in interpreted code (#14398) - pending LLVM::ABI::X86_64 - {% skip_file %} -{% end %} - require "llvm" {% if LibLLVM::BUILT_TARGETS.includes?(:x86) %} diff --git a/spec/std/llvm/x86_abi_spec.cr b/spec/std/llvm/x86_abi_spec.cr index b79ebc4d4d5c..27d387820298 100644 --- a/spec/std/llvm/x86_abi_spec.cr +++ b/spec/std/llvm/x86_abi_spec.cr @@ -1,13 +1,6 @@ {% skip_file if flag?(:win32) %} # 32-bit windows is not supported require "spec" - -{% if flag?(:interpreted) %} - # TODO: figure out how to link against libstdc++ in interpreted code (#14398) - pending LLVM::ABI::X86 - {% skip_file %} -{% end %} - require "llvm" {% if LibLLVM::BUILT_TARGETS.includes?(:x86) %} diff --git a/src/compiler/crystal/loader/unix.cr b/src/compiler/crystal/loader/unix.cr index dfab9736b038..962a3a47f22a 100644 --- a/src/compiler/crystal/loader/unix.cr +++ b/src/compiler/crystal/loader/unix.cr @@ -76,6 +76,15 @@ class Crystal::Loader parser.unknown_args do |args, after_dash| file_paths.concat args end + + # although flags starting with `-Wl,` appear in `args` above, this is + # still called by `OptionParser`, so we assume it is fine to ignore these + # flags + parser.invalid_option do |arg| + unless arg.starts_with?("-Wl,") + raise LoadError.new "Not a recognized linker flag: #{arg}" + end + end end search_paths = extra_search_paths + search_paths @@ -162,6 +171,10 @@ class Crystal::Loader read_ld_conf(default_search_paths) {% end %} + cc_each_library_path do |path| + default_search_paths << path + end + {% if flag?(:darwin) %} default_search_paths << "/usr/lib" default_search_paths << "/usr/local/lib" @@ -179,7 +192,7 @@ class Crystal::Loader default_search_paths << "/usr/lib" {% end %} - default_search_paths + default_search_paths.uniq! end def self.read_ld_conf(array = [] of String, path = "/etc/ld.so.conf") : Nil @@ -201,4 +214,20 @@ class Crystal::Loader end end end + + def self.cc_each_library_path(& : String ->) : Nil + search_dirs = begin + `#{Crystal::Compiler::DEFAULT_LINKER} -print-search-dirs` + rescue IO::Error + return + end + + search_dirs.each_line do |line| + if libraries = line.lchop?("libraries: =") + libraries.split(Process::PATH_DELIMITER) do |path| + yield File.expand_path(path) + end + end + end + end end From e60cb731f4db08c8502428511a5b9d68c8f64260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 28 Oct 2024 16:16:43 +0100 Subject: [PATCH 1436/1551] Fix remove trailing whitespace from CRYSTAL definition (#15131) #15123 broke the Makefile (and in turn CI: https://app.circleci.com/pipelines/github/crystal-lang/crystal/16310/workflows/f2194e36-a31e-4df1-87ab-64fa2ced45e2/jobs/86740) Since this commit, `"$(O)/$(CRYSTAL)$(EXE)"` in the `install` recpie resolves to the path `.build/crystal ` which doesn't exist. It looks like `$(EXE)` resolves to a single whitespace, but the error is actually in the definition of `CRYSTAL` which contains a trailing whitespace. This is only an issue in the `install` recipe because it's the only place where we put the path in quotes. So it would be simple to fix this by removing the quotes. The introduction of `$(EXE)` replaced `$(CRYSTAL_BIN)` with `$(CRYSTAL)$(EXE)`. But this is wrong. `CRYSTAL` describes the base compiler, not the output path. This patch partially reverts #15123 and reintroduces `$(CRYSTAL_BIN)`, but it's now based on `$(EXE)`. --- Makefile | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 37c42890c9f4..51ab60bb40ec 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ all: ## Run generators (Unicode, SSL config, ...) ## $ make -B generate_data -CRYSTAL ?= crystal ## which previous crystal compiler use +CRYSTAL ?= crystal## which previous crystal compiler use LLVM_CONFIG ?= ## llvm-config command path to use release ?= ## Compile in release mode @@ -74,6 +74,7 @@ else EXE := WINDOWS := endif +CRYSTAL_BIN := crystal$(EXE) DESTDIR ?= PREFIX ?= /usr/local @@ -129,7 +130,7 @@ interpreter_spec: $(O)/interpreter_spec$(EXE) ## Run interpreter specs .PHONY: smoke_test smoke_test: ## Build specs as a smoke test -smoke_test: $(O)/std_spec$(EXE) $(O)/compiler_spec$(EXE) $(O)/$(CRYSTAL)$(EXE) +smoke_test: $(O)/std_spec$(EXE) $(O)/compiler_spec$(EXE) $(O)/$(CRYSTAL_BIN) .PHONY: all_spec all_spec: $(O)/all_spec$(EXE) ## Run all specs (note: this builds a huge program; `test` recipe builds individual binaries and is recommended for reduced resource usage) @@ -146,7 +147,7 @@ docs: ## Generate standard library documentation cp -av doc/ docs/ .PHONY: crystal -crystal: $(O)/$(CRYSTAL)$(EXE) ## Build the compiler +crystal: $(O)/$(CRYSTAL_BIN) ## Build the compiler .PHONY: deps llvm_ext deps: $(DEPS) ## Build dependencies @@ -161,9 +162,9 @@ generate_data: ## Run generator scripts for Unicode, SSL config, ... $(MAKE) -B -f scripts/generate_data.mk .PHONY: install -install: $(O)/$(CRYSTAL)$(EXE) man/crystal.1.gz ## Install the compiler at DESTDIR +install: $(O)/$(CRYSTAL_BIN) man/crystal.1.gz ## Install the compiler at DESTDIR $(INSTALL) -d -m 0755 "$(BINDIR)/" - $(INSTALL) -m 0755 "$(O)/$(CRYSTAL)$(EXE)" "$(BINDIR)/$(CRYSTAL)$(EXE)" + $(INSTALL) -m 0755 "$(O)/$(CRYSTAL_BIN)" "$(BINDIR)/$(CRYSTAL_BIN)" $(INSTALL) -d -m 0755 $(DATADIR) cp -av src "$(DATADIR)/src" @@ -183,14 +184,14 @@ install: $(O)/$(CRYSTAL)$(EXE) man/crystal.1.gz ## Install the compiler at DESTD ifeq ($(WINDOWS),1) .PHONY: install_dlls -install_dlls: $(O)/$(CRYSTAL)$(EXE) ## Install the compiler's dependent DLLs at DESTDIR (Windows only) +install_dlls: $(O)/$(CRYSTAL_BIN) ## Install the compiler's dependent DLLs at DESTDIR (Windows only) $(INSTALL) -d -m 0755 "$(BINDIR)/" - @ldd $(O)/$(CRYSTAL)$(EXE) | grep -iv ' => /c/windows/system32' | sed 's/.* => //; s/ (.*//' | xargs -t -i $(INSTALL) -m 0755 '{}' "$(BINDIR)/" + @ldd $(O)/$(CRYSTAL_BIN) | grep -iv ' => /c/windows/system32' | sed 's/.* => //; s/ (.*//' | xargs -t -i $(INSTALL) -m 0755 '{}' "$(BINDIR)/" endif .PHONY: uninstall uninstall: ## Uninstall the compiler from DESTDIR - rm -f "$(BINDIR)/$(CRYSTAL)$(EXE)" + rm -f "$(BINDIR)/$(CRYSTAL_BIN)" rm -rf "$(DATADIR)/src" @@ -227,7 +228,7 @@ $(O)/compiler_spec$(EXE): $(DEPS) $(SOURCES) $(SPEC_SOURCES) @mkdir -p $(O) $(EXPORT_CC) $(EXPORTS) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler_spec.cr --release -$(O)/primitives_spec$(EXE): $(O)/$(CRYSTAL)$(EXE) $(DEPS) $(SOURCES) $(SPEC_SOURCES) +$(O)/primitives_spec$(EXE): $(O)/$(CRYSTAL_BIN) $(DEPS) $(SOURCES) $(SPEC_SOURCES) @mkdir -p $(O) $(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/primitives_spec.cr @@ -236,7 +237,7 @@ $(O)/interpreter_spec$(EXE): $(DEPS) $(SOURCES) $(SPEC_SOURCES) @mkdir -p $(O) $(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler/interpreter_spec.cr -$(O)/$(CRYSTAL)$(EXE): $(DEPS) $(SOURCES) +$(O)/$(CRYSTAL_BIN): $(DEPS) $(SOURCES) $(call check_llvm_config) @mkdir -p $(O) @# NOTE: USE_PCRE1 is only used for testing compatibility with legacy environments that don't provide libpcre2. From 6118fa2393120808c67f01831206bbbd28087ab7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 29 Oct 2024 19:40:10 +0800 Subject: [PATCH 1437/1551] Fix `Crystal::Loader.default_search_paths` spec for macOS (#15135) --- spec/compiler/loader/unix_spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/compiler/loader/unix_spec.cr b/spec/compiler/loader/unix_spec.cr index 6adcb040148b..e3309346803c 100644 --- a/spec/compiler/loader/unix_spec.cr +++ b/spec/compiler/loader/unix_spec.cr @@ -53,7 +53,7 @@ describe Crystal::Loader do with_env "LD_LIBRARY_PATH": "ld1::ld2", "DYLD_LIBRARY_PATH": nil do search_paths = Crystal::Loader.default_search_paths {% if flag?(:darwin) %} - search_paths.should eq ["/usr/lib", "/usr/local/lib"] + search_paths[-2..].should eq ["/usr/lib", "/usr/local/lib"] {% else %} search_paths[0, 2].should eq ["ld1", "ld2"] {% if flag?(:android) %} From 04ace0405c19785a45112b50e0753b445a599e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 30 Oct 2024 11:14:00 +0100 Subject: [PATCH 1438/1551] Make `Box` constructor and `object` getter nodoc (#15136) --- src/box.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/box.cr b/src/box.cr index 78799838e688..a5a6900b2ea1 100644 --- a/src/box.cr +++ b/src/box.cr @@ -5,9 +5,13 @@ # # For an example usage, see `Proc`'s explanation about sending Procs to C. class Box(T) + # :nodoc: + # # Returns the original object getter object : T + # :nodoc: + # # Creates a `Box` with the given object. # # This method isn't usually used directly. Instead, `Box.box` is used. From fd44c0816f85ea0e4e75419a0784984bad796603 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 30 Oct 2024 23:36:07 +0100 Subject: [PATCH 1439/1551] Add indirect branch tracking (#15122) Adds support for indirect branch tracking for X86[_64] (CET) and AArch64 targets through the following compile time flags (taken from gcc/clang/rust): - `-Dcf-protection=branch` (or `=return` or `=full`) for X86 - `-Dbranch-protection=bti` for AArch64 These flags are automatically set for OpenBSD, that enforces IBT or BTI on all user land applications. The patch also removes the `-Wl-znobtcfi` linker option since we don't need to disable it anymore. OpenBSD is the only OS I know to support _and_ enforce IBT or BTI in user land. Linux for example only supports it for kernel code (for the time being). I manually tested IBT in an OpenBSD VM on x86_64 with a supported CPU (Intel Raptor Lake). I can compile & recompile crystal as well as run `gmake std_spec` without running into IBT issues :+1: Notes: - I expected to have to add the ASM instructions to the fiber context switch ASM... but messing with the stack pointer isn't considered as a conditional jump apparently :shrug: - I'm using the genius idea from @straight-shoota that we can pass `-Dkey=value` then test for `flag?("key=value")` and it just worked :astonished: - I can't test BTI on AArch64: I have no hardware and there are no bindings for the `aarch64-unknown-openbsd` target; there are little reasons it wouldn't work though; - I added support for shadow stack (SHSTK) on X86 (`-Dcf-protection=return`). I'm not sure we really support it though, since fibers are messing with the stacks? --- src/compiler/crystal/codegen/codegen.cr | 32 +++++++++++++++++++------ src/compiler/crystal/codegen/debug.cr | 8 ++----- src/compiler/crystal/codegen/fun.cr | 3 +-- src/compiler/crystal/compiler.cr | 6 ----- src/compiler/crystal/semantic/flags.cr | 13 +++++++++- src/llvm/lib_llvm/core.cr | 7 +++++- src/llvm/module.cr | 4 ++++ 7 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index c4844df9a5e8..7e15b1bdc385 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -274,7 +274,7 @@ module Crystal @llvm_context : LLVM::Context = LLVM::Context.new) @abi = @program.target_machine.abi # LLVM::Context.register(@llvm_context, "main") - @llvm_mod = @llvm_context.new_module("main_module") + @llvm_mod = configure_module(@llvm_context.new_module("main_module")) @main_mod = @llvm_mod @main_llvm_context = @main_mod.context @llvm_typer = LLVMTyper.new(@program, @llvm_context) @@ -345,8 +345,6 @@ module Crystal @unused_fun_defs = [] of FunDef @proc_counts = Hash(String, Int32).new(0) - @llvm_mod.data_layout = self.data_layout - # We need to define __crystal_malloc and __crystal_realloc as soon as possible, # to avoid some memory being allocated with plain malloc. codegen_well_known_functions @node @@ -367,6 +365,30 @@ module Crystal getter llvm_context + def configure_module(llvm_mod) + llvm_mod.data_layout = @program.target_machine.data_layout + + # enable branch authentication instructions (BTI) + if @program.has_flag?("aarch64") + if @program.has_flag?("branch-protection=bti") + llvm_mod.add_flag(:override, "branch-target-enforcement", 1) + end + end + + # enable control flow enforcement protection (CET): IBT and/or SHSTK + if @program.has_flag?("x86_64") || @program.has_flag?("i386") + if @program.has_flag?("cf-protection=branch") || @program.has_flag?("cf-protection=full") + llvm_mod.add_flag(:override, "cf-protection-branch", 1) + end + + if @program.has_flag?("cf-protection=return") || @program.has_flag?("cf-protection=full") + llvm_mod.add_flag(:override, "cf-protection-return", 1) + end + end + + llvm_mod + end + def new_builder(llvm_context) wrap_builder(llvm_context.new_builder) end @@ -419,10 +441,6 @@ module Crystal global.initializer = llvm_element_type.const_array(llvm_elements) end - def data_layout - @program.target_machine.data_layout - end - class CodegenWellKnownFunctions < Visitor @codegen : CodeGenVisitor diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index dd4b6c361905..870506377f7a 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -42,17 +42,13 @@ module Crystal if @program.has_flag?("msvc") # Windows uses CodeView instead of DWARF - mod.add_flag( - LibLLVM::ModuleFlagBehavior::Warning, - "CodeView", - mod.context.int32.const_int(1) - ) + mod.add_flag(LibLLVM::ModuleFlagBehavior::Warning, "CodeView", 1) end mod.add_flag( LibLLVM::ModuleFlagBehavior::Warning, "Debug Info Version", - mod.context.int32.const_int(LLVM::DEBUG_METADATA_VERSION) + LLVM::DEBUG_METADATA_VERSION ) end diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 616b21b79d24..c56bde6e5c2a 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -626,8 +626,7 @@ class Crystal::CodeGenVisitor # LLVM::Context.register(llvm_context, type_name) llvm_typer = LLVMTyper.new(@program, llvm_context) - llvm_mod = llvm_context.new_module(type_name) - llvm_mod.data_layout = self.data_layout + llvm_mod = configure_module(llvm_context.new_module(type_name)) llvm_builder = new_builder(llvm_context) define_symbol_table llvm_mod, llvm_typer diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index ffd2b3e4a7d2..878a1ae4896a 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -507,12 +507,6 @@ module Crystal link_flags += " -L/usr/local/lib" end - if program.has_flag?("openbsd") - # OpenBSD requires Indirect Branch Tracking by default, but we're not - # compatible (yet), so we disable it for now: - link_flags += " -Wl,-znobtcfi" - end - {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags(@cross_compile)}), object_names} end end diff --git a/src/compiler/crystal/semantic/flags.cr b/src/compiler/crystal/semantic/flags.cr index d455f1fdb0c7..d4b0f265a3d1 100644 --- a/src/compiler/crystal/semantic/flags.cr +++ b/src/compiler/crystal/semantic/flags.cr @@ -49,7 +49,18 @@ class Crystal::Program flags.add "freebsd#{target.freebsd_version}" end flags.add "netbsd" if target.netbsd? - flags.add "openbsd" if target.openbsd? + + if target.openbsd? + flags.add "openbsd" + + case target.architecture + when "aarch64" + flags.add "branch-protection=bti" unless flags.any?(&.starts_with?("branch-protection=")) + when "x86_64", "i386" + flags.add "cf-protection=branch" unless flags.any?(&.starts_with?("cf-protection=")) + end + end + flags.add "dragonfly" if target.dragonfly? flags.add "solaris" if target.solaris? flags.add "android" if target.android? diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr index 1796bd00a0ee..7137501fdb31 100644 --- a/src/llvm/lib_llvm/core.cr +++ b/src/llvm/lib_llvm/core.cr @@ -5,7 +5,12 @@ lib LibLLVM # counterparts (e.g. `LLVMModuleFlagBehavior` v.s. `LLVM::Module::ModFlagBehavior`) enum ModuleFlagBehavior - Warning = 1 + Error = 0 + Warning = 1 + Require = 2 + Override = 3 + Append = 4 + AppendUnique = 5 end alias AttributeIndex = UInt diff --git a/src/llvm/module.cr b/src/llvm/module.cr index 32b025bffee7..0e73e983358a 100644 --- a/src/llvm/module.cr +++ b/src/llvm/module.cr @@ -45,6 +45,10 @@ class LLVM::Module GlobalCollection.new(self) end + def add_flag(module_flag : LibLLVM::ModuleFlagBehavior, key : String, val : Int32) + add_flag(module_flag, key, @context.int32.const_int(val)) + end + def add_flag(module_flag : LibLLVM::ModuleFlagBehavior, key : String, val : Value) LibLLVM.add_module_flag( self, From d353bb42b2a3c7dcb5d03d169634313a856d1445 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 31 Oct 2024 18:16:14 +0800 Subject: [PATCH 1440/1551] Support deferencing symlinks in `make install` (#15138) On platforms without complete symbolic link support (e.g. native MSYS2 environments), `make install deref_symlinks=1` will dereference the individual directories under `src/lib_c` and copy the contents, instead of copying the directories as symbolic links. --- Makefile | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 51ab60bb40ec..d30db53464f7 100644 --- a/Makefile +++ b/Makefile @@ -24,18 +24,19 @@ all: CRYSTAL ?= crystal## which previous crystal compiler use LLVM_CONFIG ?= ## llvm-config command path to use -release ?= ## Compile in release mode -stats ?= ## Enable statistics output -progress ?= ## Enable progress output -threads ?= ## Maximum number of threads to use -debug ?= ## Add symbolic debug info -verbose ?= ## Run specs in verbose mode -junit_output ?= ## Path to output junit results -static ?= ## Enable static linking -target ?= ## Cross-compilation target -interpreter ?= ## Enable interpreter feature -check ?= ## Enable only check when running format -order ?=random ## Enable order for spec execution (values: "default" | "random" | seed number) +release ?= ## Compile in release mode +stats ?= ## Enable statistics output +progress ?= ## Enable progress output +threads ?= ## Maximum number of threads to use +debug ?= ## Add symbolic debug info +verbose ?= ## Run specs in verbose mode +junit_output ?= ## Path to output junit results +static ?= ## Enable static linking +target ?= ## Cross-compilation target +interpreter ?= ## Enable interpreter feature +check ?= ## Enable only check when running format +order ?=random ## Enable order for spec execution (values: "default" | "random" | seed number) +deref_symlinks ?= ## Deference symbolic links for `make install` O := .build SOURCES := $(shell find src -name '*.cr') @@ -167,7 +168,7 @@ install: $(O)/$(CRYSTAL_BIN) man/crystal.1.gz ## Install the compiler at DESTDIR $(INSTALL) -m 0755 "$(O)/$(CRYSTAL_BIN)" "$(BINDIR)/$(CRYSTAL_BIN)" $(INSTALL) -d -m 0755 $(DATADIR) - cp -av src "$(DATADIR)/src" + cp $(if $(deref_symlinks),-rvL --preserve=all,-av) src "$(DATADIR)/src" rm -rf "$(DATADIR)/$(LLVM_EXT_OBJ)" # Don't install llvm_ext.o $(INSTALL) -d -m 0755 "$(MANDIR)/man1/" From 8635bce731a58b55a7a95a1a21ed892e81590b5d Mon Sep 17 00:00:00 2001 From: Barney <86712892+BigBoyBarney@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:17:43 +0100 Subject: [PATCH 1441/1551] Improve docs for `String#rindex!` (#15132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/string.cr | 70 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/src/string.cr b/src/string.cr index f0dbd1a1eae3..7507e3b7249e 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3473,8 +3473,8 @@ class String # ``` # "Hello, World".rindex('o') # => 8 # "Hello, World".rindex('Z') # => nil - # "Hello, World".rindex("o", 5) # => 4 - # "Hello, World".rindex("W", 2) # => nil + # "Hello, World".rindex('o', 5) # => 4 + # "Hello, World".rindex('W', 2) # => nil # ``` def rindex(search : Char, offset = size - 1) # If it's ASCII we can delegate to slice @@ -3519,7 +3519,16 @@ class String end end - # :ditto: + # Returns the index of the _last_ appearance of *search* in the string, + # If *offset* is present, it defines the position to _end_ the search + # (characters beyond this point are ignored). + # + # ``` + # "Hello, World".rindex("orld") # => 8 + # "Hello, World".rindex("snorlax") # => nil + # "Hello, World".rindex("o", 5) # => 4 + # "Hello, World".rindex("W", 2) # => nil + # ``` def rindex(search : String, offset = size - search.size) : Int32? offset += size if offset < 0 return if offset < 0 @@ -3572,7 +3581,16 @@ class String end end - # :ditto: + # Returns the index of the _last_ appearance of *search* in the string, + # If *offset* is present, it defines the position to _end_ the search + # (characters beyond this point are ignored). + # + # ``` + # "Hello, World".rindex(/world/i) # => 7 + # "Hello, World".rindex(/world/) # => nil + # "Hello, World".rindex(/o/, 5) # => 4 + # "Hello, World".rindex(/W/, 2) # => nil + # ``` def rindex(search : Regex, offset = size, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32? offset += size if offset < 0 return nil unless 0 <= offset <= size @@ -3586,21 +3604,49 @@ class String match_result.try &.begin end - # :ditto: - # + # Returns the index of the _last_ appearance of *search* in the string, + # If *offset* is present, it defines the position to _end_ the search + # (characters beyond this point are ignored). # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`. - def rindex!(search : Regex, offset = size, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32 - rindex(search, offset, options: options) || raise Enumerable::NotFoundError.new + # + # ``` + # "Hello, World".rindex!('o') # => 8 + # "Hello, World".rindex!('Z') # raises Enumerable::NotFoundError + # "Hello, World".rindex!('o', 5) # => 4 + # "Hello, World".rindex!('W', 2) # raises Enumerable::NotFoundError + # ``` + def rindex!(search : Char, offset = size - 1) : Int32 + rindex(search, offset) || raise Enumerable::NotFoundError.new end - # :ditto: + # Returns the index of the _last_ appearance of *search* in the string, + # If *offset* is present, it defines the position to _end_ the search + # (characters beyond this point are ignored). + # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`. + # + # ``` + # "Hello, World".rindex!("orld") # => 8 + # "Hello, World".rindex!("snorlax") # raises Enumerable::NotFoundError + # "Hello, World".rindex!("o", 5) # => 4 + # "Hello, World".rindex!("W", 2) # raises Enumerable::NotFoundError + # ``` def rindex!(search : String, offset = size - search.size) : Int32 rindex(search, offset) || raise Enumerable::NotFoundError.new end - # :ditto: - def rindex!(search : Char, offset = size - 1) : Int32 - rindex(search, offset) || raise Enumerable::NotFoundError.new + # Returns the index of the _last_ appearance of *search* in the string, + # If *offset* is present, it defines the position to _end_ the search + # (characters beyond this point are ignored). + # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`. + # + # ``` + # "Hello, World".rindex!(/world/i) # => 7 + # "Hello, World".rindex!(/world/) # raises Enumerable::NotFoundError + # "Hello, World".rindex!(/o/, 5) # => 4 + # "Hello, World".rindex!(/W/, 2) # raises Enumerable::NotFoundError + # ``` + def rindex!(search : Regex, offset = size, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32 + rindex(search, offset, options: options) || raise Enumerable::NotFoundError.new end # Searches separator or pattern (`Regex`) in the string, and returns From 4aac6f2ee3494a593d79eeea389c77037e025adc Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 31 Oct 2024 18:20:50 +0800 Subject: [PATCH 1442/1551] Protect constant initializers with mutex on Windows (#15134) `Crystal::System::FileDescriptor#@@reader_thread` is initialized before `Crystal::System::Fiber::RESERVED_STACK_SIZE` which creates a race condition. Regression from #14947 --- src/crystal/once.cr | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/crystal/once.cr b/src/crystal/once.cr index 1e6243669809..56eea2be693a 100644 --- a/src/crystal/once.cr +++ b/src/crystal/once.cr @@ -11,9 +11,6 @@ # :nodoc: class Crystal::OnceState @rec = [] of Bool* - {% if flag?(:preview_mt) %} - @mutex = Mutex.new(:reentrant) - {% end %} def once(flag : Bool*, initializer : Void*) unless flag.value @@ -29,7 +26,13 @@ class Crystal::OnceState end end - {% if flag?(:preview_mt) %} + # on Win32, `Crystal::System::FileDescriptor#@@reader_thread` spawns a new + # thread even without the `preview_mt` flag, and the thread can also reference + # Crystal constants, leading to race conditions, so we always enable the mutex + # TODO: can this be improved? + {% if flag?(:preview_mt) || flag?(:win32) %} + @mutex = Mutex.new(:reentrant) + def once(flag : Bool*, initializer : Void*) unless flag.value @mutex.synchronize do From 2eb1b5fb25b3240dda9f9710f8df2c69462f6e8f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 4 Nov 2024 06:22:09 +0800 Subject: [PATCH 1443/1551] Basic MinGW-w64-based interpreter support (#15140) Implements a MinGW-based loader for `x86_64-windows-gnu` and enables the interpreter. --- .github/workflows/mingw-w64.yml | 14 +- spec/compiler/ffi/ffi_spec.cr | 10 +- spec/compiler/interpreter/lib_spec.cr | 39 ++-- spec/compiler/loader/spec_helper.cr | 3 + src/compiler/crystal/interpreter/context.cr | 8 +- src/compiler/crystal/loader.cr | 4 +- src/compiler/crystal/loader/mingw.cr | 195 ++++++++++++++++++++ src/crystal/system/win32/wmain.cr | 2 +- 8 files changed, 246 insertions(+), 29 deletions(-) create mode 100644 src/compiler/crystal/loader/mingw.cr diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml index b32aed414f77..10841a325bf5 100644 --- a/.github/workflows/mingw-w64.yml +++ b/.github/workflows/mingw-w64.yml @@ -31,7 +31,7 @@ jobs: crystal: "1.14.0" - name: Cross-compile Crystal - run: make && make -B target=x86_64-windows-gnu release=1 + run: make && make -B target=x86_64-windows-gnu release=1 interpreter=1 - name: Upload crystal.obj uses: actions/upload-artifact@v4 @@ -63,6 +63,7 @@ jobs: mingw-w64-ucrt-x86_64-libiconv mingw-w64-ucrt-x86_64-zlib mingw-w64-ucrt-x86_64-llvm + mingw-w64-ucrt-x86_64-libffi - name: Download crystal.obj uses: actions/download-artifact@v4 @@ -80,7 +81,7 @@ jobs: run: | mkdir bin cc crystal.obj -o bin/crystal.exe \ - $(pkg-config bdw-gc libpcre2-8 iconv zlib --libs) \ + $(pkg-config bdw-gc libpcre2-8 iconv zlib libffi --libs) \ $(llvm-config --libs --system-libs --ldflags) \ -lDbgHelp -lole32 -lWS2_32 -Wl,--stack,0x800000 ldd bin/crystal.exe | grep -iv /c/windows/system32 | sed 's/.* => //; s/ (.*//' | xargs -t -i cp '{}' bin/ @@ -144,7 +145,14 @@ jobs: run: | export PATH="$(pwd)/crystal/bin:$PATH" export CRYSTAL_SPEC_COMPILER_BIN="$(pwd)/crystal/bin/crystal.exe" - make compiler_spec FLAGS=-Dwithout_ffi + make compiler_spec + + - name: Run interpreter specs + shell: msys2 {0} + run: | + export PATH="$(pwd)/crystal/bin:$PATH" + export CRYSTAL_SPEC_COMPILER_BIN="$(pwd)/crystal/bin/crystal.exe" + make interpreter_spec - name: Run primitives specs shell: msys2 {0} diff --git a/spec/compiler/ffi/ffi_spec.cr b/spec/compiler/ffi/ffi_spec.cr index ec644e45870d..a4718edb3501 100644 --- a/spec/compiler/ffi/ffi_spec.cr +++ b/spec/compiler/ffi/ffi_spec.cr @@ -27,7 +27,7 @@ private def dll_search_paths {% end %} end -{% if flag?(:unix) %} +{% if flag?(:unix) || (flag?(:win32) && flag?(:gnu)) %} class Crystal::Loader def self.new(search_paths : Array(String), *, dll_search_paths : Nil) new(search_paths) @@ -39,9 +39,17 @@ describe Crystal::FFI::CallInterface do before_all do FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH) build_c_dynlib(compiler_datapath("ffi", "sum.c")) + + {% if flag?(:win32) && flag?(:gnu) %} + ENV["PATH"] = "#{SPEC_CRYSTAL_LOADER_LIB_PATH}#{Process::PATH_DELIMITER}#{ENV["PATH"]}" + {% end %} end after_all do + {% if flag?(:win32) && flag?(:gnu) %} + ENV["PATH"] = ENV["PATH"].delete_at(0, ENV["PATH"].index!(Process::PATH_DELIMITER) + 1) + {% end %} + FileUtils.rm_rf(SPEC_CRYSTAL_LOADER_LIB_PATH) end diff --git a/spec/compiler/interpreter/lib_spec.cr b/spec/compiler/interpreter/lib_spec.cr index 2c1798645645..bbf6367ee6df 100644 --- a/spec/compiler/interpreter/lib_spec.cr +++ b/spec/compiler/interpreter/lib_spec.cr @@ -3,7 +3,7 @@ require "./spec_helper" require "../loader/spec_helper" private def ldflags - {% if flag?(:win32) %} + {% if flag?(:msvc) %} "/LIBPATH:#{SPEC_CRYSTAL_LOADER_LIB_PATH} sum.lib" {% else %} "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -lsum" @@ -11,7 +11,7 @@ private def ldflags end private def ldflags_with_backtick - {% if flag?(:win32) %} + {% if flag?(:msvc) %} "/LIBPATH:#{SPEC_CRYSTAL_LOADER_LIB_PATH} `powershell.exe -C Write-Host -NoNewline sum.lib`" {% else %} "-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -l`echo sum`" @@ -19,12 +19,24 @@ private def ldflags_with_backtick end describe Crystal::Repl::Interpreter do - context "variadic calls" do - before_all do - FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH) - build_c_dynlib(compiler_datapath("interpreter", "sum.c")) - end + before_all do + FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH) + build_c_dynlib(compiler_datapath("interpreter", "sum.c")) + + {% if flag?(:win32) %} + ENV["PATH"] = "#{SPEC_CRYSTAL_LOADER_LIB_PATH}#{Process::PATH_DELIMITER}#{ENV["PATH"]}" + {% end %} + end + + after_all do + {% if flag?(:win32) %} + ENV["PATH"] = ENV["PATH"].delete_at(0, ENV["PATH"].index!(Process::PATH_DELIMITER) + 1) + {% end %} + + FileUtils.rm_rf(SPEC_CRYSTAL_LOADER_LIB_PATH) + end + context "variadic calls" do it "promotes float" do interpret(<<-CRYSTAL).should eq 3.5 @[Link(ldflags: #{ldflags.inspect})] @@ -65,18 +77,9 @@ describe Crystal::Repl::Interpreter do LibSum.sum_int(2, E::ONE, F::FOUR) CRYSTAL end - - after_all do - FileUtils.rm_rf(SPEC_CRYSTAL_LOADER_LIB_PATH) - end end context "command expansion" do - before_all do - FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH) - build_c_dynlib(compiler_datapath("interpreter", "sum.c")) - end - it "expands ldflags" do interpret(<<-CRYSTAL).should eq 4 @[Link(ldflags: #{ldflags_with_backtick.inspect})] @@ -87,9 +90,5 @@ describe Crystal::Repl::Interpreter do LibSum.simple_sum_int(2, 2) CRYSTAL end - - after_all do - FileUtils.rm_rf(SPEC_CRYSTAL_LOADER_LIB_PATH) - end end end diff --git a/spec/compiler/loader/spec_helper.cr b/spec/compiler/loader/spec_helper.cr index 0db69dc19752..5b2a6454bfa1 100644 --- a/spec/compiler/loader/spec_helper.cr +++ b/spec/compiler/loader/spec_helper.cr @@ -8,6 +8,9 @@ def build_c_dynlib(c_filename, *, lib_name = nil, target_dir = SPEC_CRYSTAL_LOAD {% if flag?(:msvc) %} o_basename = o_filename.rchop(".lib") `#{ENV["CC"]? || "cl.exe"} /nologo /LD #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_basename}")} #{Process.quote("/Fe#{o_basename}")}` + {% elsif flag?(:win32) && flag?(:gnu) %} + o_basename = o_filename.rchop(".a") + `#{ENV["CC"]? || "cc"} -shared -fvisibility=hidden #{Process.quote(c_filename)} -o #{Process.quote(o_basename + ".dll")} #{Process.quote("-Wl,--out-implib,#{o_basename}.a")}` {% else %} `#{ENV["CC"]? || "cc"} -shared -fvisibility=hidden #{Process.quote(c_filename)} -o #{Process.quote(o_filename)}` {% end %} diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr index 50e36a3ff8b7..c2c1537e002d 100644 --- a/src/compiler/crystal/interpreter/context.cr +++ b/src/compiler/crystal/interpreter/context.cr @@ -393,14 +393,16 @@ class Crystal::Repl::Context getter(loader : Loader) { lib_flags = program.lib_flags # Execute and expand `subcommands`. - lib_flags = lib_flags.gsub(/`(.*?)`/) { `#{$1}` } + lib_flags = lib_flags.gsub(/`(.*?)`/) { `#{$1}`.chomp } args = Process.parse_arguments(lib_flags) # FIXME: Part 1: This is a workaround for initial integration of the interpreter: # The loader can't handle the static libgc.a usually shipped with crystal and loading as a shared library conflicts # with the compiler's own GC. - # (MSVC doesn't seem to have this issue) - args.delete("-lgc") + # (Windows doesn't seem to have this issue) + unless program.has_flag?("win32") && program.has_flag?("gnu") + args.delete("-lgc") + end # recreate the MSVC developer prompt environment, similar to how compiled # code does it in `Compiler#linker_command` diff --git a/src/compiler/crystal/loader.cr b/src/compiler/crystal/loader.cr index 5a147dad590f..84ff43d03d8e 100644 --- a/src/compiler/crystal/loader.cr +++ b/src/compiler/crystal/loader.cr @@ -1,4 +1,4 @@ -{% skip_file unless flag?(:unix) || flag?(:msvc) %} +{% skip_file unless flag?(:unix) || flag?(:win32) %} require "option_parser" # This loader component imitates the behaviour of `ld.so` for linking and loading @@ -105,4 +105,6 @@ end require "./loader/unix" {% elsif flag?(:msvc) %} require "./loader/msvc" +{% elsif flag?(:win32) && flag?(:gnu) %} + require "./loader/mingw" {% end %} diff --git a/src/compiler/crystal/loader/mingw.cr b/src/compiler/crystal/loader/mingw.cr new file mode 100644 index 000000000000..677f564cec16 --- /dev/null +++ b/src/compiler/crystal/loader/mingw.cr @@ -0,0 +1,195 @@ +{% skip_file unless flag?(:win32) && flag?(:gnu) %} + +require "crystal/system/win32/library_archive" + +# MinGW-based loader used on Windows. Assumes an MSYS2 shell. +# +# The core implementation is derived from the MSVC loader. Main deviations are: +# +# - `.parse` follows GNU `ld`'s style, rather than MSVC `link`'s; +# - `#library_filename` follows the usual naming of the MinGW linker: `.dll.a` +# for DLL import libraries, `.a` for other libraries; +# - `.default_search_paths` relies solely on `.cc_each_library_path`. +# +# TODO: The actual MinGW linker supports linking to DLLs directly, figure out +# how this is done. + +class Crystal::Loader + alias Handle = Void* + + def initialize(@search_paths : Array(String)) + end + + # Parses linker arguments in the style of `ld`. + # + # This is identical to the Unix loader. *dll_search_paths* has no effect. + def self.parse(args : Array(String), *, search_paths : Array(String) = default_search_paths, dll_search_paths : Array(String)? = nil) : self + libnames = [] of String + file_paths = [] of String + extra_search_paths = [] of String + + OptionParser.parse(args.dup) do |parser| + parser.on("-L DIRECTORY", "--library-path DIRECTORY", "Add DIRECTORY to library search path") do |directory| + extra_search_paths << directory + end + parser.on("-l LIBNAME", "--library LIBNAME", "Search for library LIBNAME") do |libname| + libnames << libname + end + parser.on("-static", "Do not link against shared libraries") do + raise LoadError.new "static libraries are not supported by Crystal's runtime loader" + end + parser.unknown_args do |args, after_dash| + file_paths.concat args + end + + parser.invalid_option do |arg| + unless arg.starts_with?("-Wl,") + raise LoadError.new "Not a recognized linker flag: #{arg}" + end + end + end + + search_paths = extra_search_paths + search_paths + + begin + loader = new(search_paths) + loader.load_all(libnames, file_paths) + loader + rescue exc : LoadError + exc.args = args + exc.search_paths = search_paths + raise exc + end + end + + def self.library_filename(libname : String) : String + "lib#{libname}.a" + end + + def find_symbol?(name : String) : Handle? + @handles.each do |handle| + address = LibC.GetProcAddress(handle, name.check_no_null_byte) + return address if address + end + end + + def load_file(path : String | ::Path) : Nil + load_file?(path) || raise LoadError.new "cannot load #{path}" + end + + def load_file?(path : String | ::Path) : Bool + if api_set?(path) + return load_dll?(path.to_s) + end + + return false unless File.file?(path) + + System::LibraryArchive.imported_dlls(path).all? do |dll| + load_dll?(dll) + end + end + + private def load_dll?(dll) + handle = open_library(dll) + return false unless handle + + @handles << handle + @loaded_libraries << (module_filename(handle) || dll) + true + end + + def load_library(libname : String) : Nil + load_library?(libname) || raise LoadError.new "cannot find #{Loader.library_filename(libname)}" + end + + def load_library?(libname : String) : Bool + if ::Path::SEPARATORS.any? { |separator| libname.includes?(separator) } + return load_file?(::Path[libname].expand) + end + + # attempt .dll.a before .a + # TODO: verify search order + @search_paths.each do |directory| + library_path = File.join(directory, Loader.library_filename(libname + ".dll")) + return true if load_file?(library_path) + + library_path = File.join(directory, Loader.library_filename(libname)) + return true if load_file?(library_path) + end + + false + end + + private def open_library(path : String) + LibC.LoadLibraryExW(System.to_wstr(path), nil, 0) + end + + def load_current_program_handle + if LibC.GetModuleHandleExW(0, nil, out hmodule) != 0 + @handles << hmodule + @loaded_libraries << (Process.executable_path || "current program handle") + end + end + + def close_all : Nil + @handles.each do |handle| + LibC.FreeLibrary(handle) + end + @handles.clear + end + + private def api_set?(dll) + dll.to_s.matches?(/^(?:api-|ext-)[a-zA-Z0-9-]*l\d+-\d+-\d+\.dll$/) + end + + private def module_filename(handle) + Crystal::System.retry_wstr_buffer do |buffer, small_buf| + len = LibC.GetModuleFileNameW(handle, buffer, buffer.size) + if 0 < len < buffer.size + break String.from_utf16(buffer[0, len]) + elsif small_buf && len == buffer.size + next 32767 # big enough. 32767 is the maximum total path length of UNC path. + else + break nil + end + end + end + + # Returns a list of directories used as the default search paths. + # + # Right now this depends on `cc` exclusively. + def self.default_search_paths : Array(String) + default_search_paths = [] of String + + cc_each_library_path do |path| + default_search_paths << path + end + + default_search_paths.uniq! + end + + # identical to the Unix loader + def self.cc_each_library_path(& : String ->) : Nil + search_dirs = begin + cc = + {% if Crystal.has_constant?("Compiler") %} + Crystal::Compiler::DEFAULT_LINKER + {% else %} + # this allows the loader to be required alone without the compiler + ENV["CC"]? || "cc" + {% end %} + + `#{cc} -print-search-dirs` + rescue IO::Error + return + end + + search_dirs.each_line do |line| + if libraries = line.lchop?("libraries: =") + libraries.split(Process::PATH_DELIMITER) do |path| + yield File.expand_path(path) + end + end + end + end +end diff --git a/src/crystal/system/win32/wmain.cr b/src/crystal/system/win32/wmain.cr index 3dd64f9c1b92..caad6748229f 100644 --- a/src/crystal/system/win32/wmain.cr +++ b/src/crystal/system/win32/wmain.cr @@ -7,7 +7,7 @@ require "c/stdlib" @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})] {% if flag?(:msvc) %} @[Link(ldflags: "/ENTRY:wmainCRTStartup")] - {% elsif flag?(:gnu) %} + {% elsif flag?(:gnu) && !flag?(:interpreted) %} @[Link(ldflags: "-municode")] {% end %} {% end %} From 563d6d2de572bfec3b631f433d9b8bf99e3992c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 4 Nov 2024 12:21:15 +0100 Subject: [PATCH 1444/1551] Fix: use `uninitialized LibC::SigsetT` (#15144) `LibC::SigsetT` is a `struct` on some platforms and an alias to `UInt32` on others. `.new` is only valid for the struct variant. `uninitialized` should work in either case. This code is only used with `-Dgc_none`, so a reproduction needs to include that flag and build for a target where `LibC::SigsetT = alias UInt32`, such as `aarch64-apple-darwin`. --- src/crystal/system/unix/pthread.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index bbdfcbc3d41c..73aa2a652ca1 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -208,7 +208,7 @@ module Crystal::System::Thread Thread.current_thread.@suspended.set(true) # block all signals but SIG_RESUME - mask = LibC::SigsetT.new + mask = uninitialized LibC::SigsetT LibC.sigfillset(pointerof(mask)) LibC.sigdelset(pointerof(mask), SIG_RESUME) From 07038e25c4c0977054b3d814b2c38fcc2af817a3 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 5 Nov 2024 17:44:06 +0800 Subject: [PATCH 1445/1551] Run interpreter specs on Windows CI (#15141) --- .github/workflows/win.yml | 3 +++ src/compiler/crystal/interpreter/context.cr | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 03aac8e2f0b1..aacf1a4aae4f 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -276,6 +276,9 @@ jobs: - name: Run compiler specs run: make -f Makefile.win compiler_spec + - name: Run interpreter specs + run: make -f Makefile.win interpreter_spec + - name: Run primitives specs run: make -f Makefile.win -o .build\crystal.exe primitives_spec # we know the compiler is fresh; do not rebuild it here diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr index c2c1537e002d..987781c4aefb 100644 --- a/src/compiler/crystal/interpreter/context.cr +++ b/src/compiler/crystal/interpreter/context.cr @@ -434,7 +434,7 @@ class Crystal::Repl::Context # used in `Crystal::Program#each_dll_path` private def dll_search_paths {% if flag?(:msvc) %} - paths = CrystalLibraryPath.paths + paths = CrystalLibraryPath.default_paths if executable_path = Process.executable_path paths << File.dirname(executable_path) From 806acff3dbd9202bc11bad796eb3856f1828fab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 5 Nov 2024 13:47:06 +0100 Subject: [PATCH 1446/1551] [CI] Remove pin for ancient nix version (#15150) --- .github/workflows/macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 8ae3ac28209e..77e9e0b3371c 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -29,9 +29,9 @@ jobs: - uses: cachix/install-nix-action@v27 with: - install_url: https://releases.nixos.org/nix/nix-2.9.2/install extra_nix_config: | experimental-features = nix-command + - uses: cachix/cachix-action@v15 with: name: crystal-ci From dc632a9ad60d45569c64c6a91bb62018ad7dfa65 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:47:37 +0100 Subject: [PATCH 1447/1551] Migrate renovate config (#15151) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/renovate.json | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 39932ec1f648..c6ff478b7c47 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,17 +1,27 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:base"], + "extends": [ + "config:recommended" + ], "separateMajorMinor": false, "packageRules": [ { - "matchDatasources": ["docker"], + "matchDatasources": [ + "docker" + ], "enabled": false }, { "groupName": "GH Actions", - "matchManagers": ["github-actions"], - "schedule": ["after 5am and before 8am on Wednesday"] + "matchManagers": [ + "github-actions" + ], + "schedule": [ + "after 5am and before 8am on Wednesday" + ] } ], - "labels": ["topic:infrastructure/ci"] + "labels": [ + "topic:infrastructure/ci" + ] } From 9f5533c572e8472d3af74bce2d96a00ac8d95504 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 5 Nov 2024 20:14:11 +0100 Subject: [PATCH 1448/1551] DragonFlyBSD: std specs fixes + pending (#15152) With these changes I can run all the std and compiler specs in DragonFlyBSD 6.4 VM (vagrant/libvirt). I disabled some TCP specs that usually hang for me. Starting the servers usually work, but trying to _connect_ (especially expecting errors) are left hanging. It might be a network or even a DNS issue inside the VM (or vagrant or libvirt configuration). I didn't investigate deeply. --- spec/std/io/io_spec.cr | 14 ++- spec/std/signal_spec.cr | 14 ++- spec/std/socket/tcp_socket_spec.cr | 193 ++++++++++++++++------------- spec/std/socket/udp_socket_spec.cr | 4 + 4 files changed, 132 insertions(+), 93 deletions(-) diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 1904940f4883..9fa7c867a290 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -427,7 +427,7 @@ describe IO do # pipe(2) returns bidirectional file descriptors on some platforms, # gate this test behind the platform flag. - {% unless flag?(:freebsd) || flag?(:solaris) || flag?(:openbsd) %} + {% unless flag?(:freebsd) || flag?(:solaris) || flag?(:openbsd) || flag?(:dragonfly) %} it "raises if trying to read to an IO not opened for reading" do IO.pipe do |r, w| expect_raises(IO::Error, "File not open for reading") do @@ -576,7 +576,7 @@ describe IO do # pipe(2) returns bidirectional file descriptors on some platforms, # gate this test behind the platform flag. - {% unless flag?(:freebsd) || flag?(:solaris) || flag?(:openbsd) %} + {% unless flag?(:freebsd) || flag?(:solaris) || flag?(:openbsd) || flag?(:dragonfly) %} it "raises if trying to write to an IO not opened for writing" do IO.pipe do |r, w| # unless sync is used the flush on close triggers the exception again @@ -736,9 +736,13 @@ describe IO do it "says invalid byte sequence" do io = SimpleIOMemory.new(Slice.new(1, 255_u8)) io.set_encoding("EUC-JP") - expect_raises ArgumentError, {% if flag?(:musl) || flag?(:freebsd) || flag?(:netbsd) %}"Incomplete multibyte sequence"{% else %}"Invalid multibyte sequence"{% end %} do - io.read_char - end + message = + {% if flag?(:musl) || flag?(:freebsd) || flag?(:netbsd) || flag?(:dragonfly) %} + "Incomplete multibyte sequence" + {% else %} + "Invalid multibyte sequence" + {% end %} + expect_raises(ArgumentError, message) { io.read_char } end it "skips invalid byte sequences" do diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr index e27373e3be21..ee5eff048c73 100644 --- a/spec/std/signal_spec.cr +++ b/spec/std/signal_spec.cr @@ -18,7 +18,19 @@ pending_interpreted describe: "Signal" do Signal::ABRT.should be_a(Signal) end - {% unless flag?(:win32) %} + {% if flag?(:dragonfly) %} + # FIXME: can't use SIGUSR1/SIGUSR2 because Boehm uses them + no + # SIRTMIN/SIGRTMAX support => figure which signals we could use + pending "runs a signal handler" + pending "ignores a signal" + pending "allows chaining of signals" + pending "CHLD.reset sets default Crystal child handler" + pending "CHLD.ignore sets default Crystal child handler" + pending "CHLD.trap is called after default Crystal child handler" + pending "CHLD.reset removes previously set trap" + {% end %} + + {% unless flag?(:win32) || flag?(:dragonfly) %} # can't use SIGUSR1/SIGUSR2 on FreeBSD because Boehm uses them to suspend/resume threads signal1 = {% if flag?(:freebsd) %} Signal.new(LibC::SIGRTMAX - 1) {% else %} Signal::USR1 {% end %} signal2 = {% if flag?(:freebsd) %} Signal.new(LibC::SIGRTMAX - 2) {% else %} Signal::USR2 {% end %} diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index 0b3a381372bf..b7a22882ec0f 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -33,13 +33,18 @@ describe TCPSocket, tags: "network" do end end - it "raises when connection is refused" do - port = unused_local_port - - expect_raises(Socket::ConnectError, "Error connecting to '#{address}:#{port}'") do - TCPSocket.new(address, port) + {% if flag?(:dragonfly) %} + # FIXME: this spec regularly hangs in a vagrant/libvirt VM + pending "raises when connection is refused" + {% else %} + it "raises when connection is refused" do + port = unused_local_port + + expect_raises(Socket::ConnectError, "Error connecting to '#{address}:#{port}'") do + TCPSocket.new(address, port) + end end - end + {% end %} it "raises when port is negative" do error = expect_raises(Socket::Addrinfo::Error) do @@ -54,11 +59,16 @@ describe TCPSocket, tags: "network" do {% end %}) end - it "raises when port is zero" do - expect_raises(Socket::ConnectError) do - TCPSocket.new(address, 0) + {% if flag?(:dragonfly) %} + # FIXME: this spec regularly hangs in a vagrant/libvirt VM + pending "raises when port is zero" + {% else %} + it "raises when port is zero" do + expect_raises(Socket::ConnectError) do + TCPSocket.new(address, 0) + end end - end + {% end %} end describe "address resolution" do @@ -112,105 +122,114 @@ describe TCPSocket, tags: "network" do end end - it "sync from server" do - port = unused_local_port + {% if flag?(:dragonfly) %} + # FIXME: these specs regularly hang in a vagrant/libvirt VM + pending "sync from server" + pending "settings" + pending "fails when connection is refused" + pending "sends and receives messages" + pending "sends and receives messages (fibers & channels)" + {% else %} + it "sync from server" do + port = unused_local_port - TCPServer.open("::", port) do |server| - TCPSocket.open("localhost", port) do |client| - sock = server.accept - sock.sync?.should eq(server.sync?) - end + TCPServer.open("::", port) do |server| + TCPSocket.open("localhost", port) do |client| + sock = server.accept + sock.sync?.should eq(server.sync?) + end - # test sync flag propagation after accept - server.sync = !server.sync? + # test sync flag propagation after accept + server.sync = !server.sync? - TCPSocket.open("localhost", port) do |client| - sock = server.accept - sock.sync?.should eq(server.sync?) + TCPSocket.open("localhost", port) do |client| + sock = server.accept + sock.sync?.should eq(server.sync?) + end end end - end - it "settings" do - port = unused_local_port + it "settings" do + port = unused_local_port - TCPServer.open("::", port) do |server| - TCPSocket.open("localhost", port) do |client| - # test protocol specific socket options - (client.tcp_nodelay = true).should be_true - client.tcp_nodelay?.should be_true - (client.tcp_nodelay = false).should be_false - client.tcp_nodelay?.should be_false - - {% unless flag?(:openbsd) || flag?(:netbsd) %} - (client.tcp_keepalive_idle = 42).should eq 42 - client.tcp_keepalive_idle.should eq 42 - (client.tcp_keepalive_interval = 42).should eq 42 - client.tcp_keepalive_interval.should eq 42 - (client.tcp_keepalive_count = 42).should eq 42 - client.tcp_keepalive_count.should eq 42 - {% end %} + TCPServer.open("::", port) do |server| + TCPSocket.open("localhost", port) do |client| + # test protocol specific socket options + (client.tcp_nodelay = true).should be_true + client.tcp_nodelay?.should be_true + (client.tcp_nodelay = false).should be_false + client.tcp_nodelay?.should be_false + + {% unless flag?(:openbsd) || flag?(:netbsd) %} + (client.tcp_keepalive_idle = 42).should eq 42 + client.tcp_keepalive_idle.should eq 42 + (client.tcp_keepalive_interval = 42).should eq 42 + client.tcp_keepalive_interval.should eq 42 + (client.tcp_keepalive_count = 42).should eq 42 + client.tcp_keepalive_count.should eq 42 + {% end %} + end end end - end - it "fails when connection is refused" do - port = TCPServer.open("localhost", 0) do |server| - server.local_address.port - end + it "fails when connection is refused" do + port = TCPServer.open("localhost", 0) do |server| + server.local_address.port + end - expect_raises(Socket::ConnectError, "Error connecting to 'localhost:#{port}'") do - TCPSocket.new("localhost", port) + expect_raises(Socket::ConnectError, "Error connecting to 'localhost:#{port}'") do + TCPSocket.new("localhost", port) + end end - end - it "sends and receives messages" do - port = unused_local_port + it "sends and receives messages" do + port = unused_local_port - TCPServer.open("::", port) do |server| - TCPSocket.open("localhost", port) do |client| - sock = server.accept + TCPServer.open("::", port) do |server| + TCPSocket.open("localhost", port) do |client| + sock = server.accept - client << "ping" - sock.gets(4).should eq("ping") - sock << "pong" - client.gets(4).should eq("pong") + client << "ping" + sock.gets(4).should eq("ping") + sock << "pong" + client.gets(4).should eq("pong") + end end end - end - it "sends and receives messages" do - port = unused_local_port + it "sends and receives messages (fibers & channels)" do + port = unused_local_port - channel = Channel(Exception?).new - spawn do - TCPServer.open("::", port) do |server| - channel.send nil - sock = server.accept - sock.read_timeout = 3.second - sock.write_timeout = 3.second - - sock.gets(4).should eq("ping") - sock << "pong" - channel.send nil + channel = Channel(Exception?).new + spawn do + TCPServer.open("::", port) do |server| + channel.send nil + sock = server.accept + sock.read_timeout = 3.second + sock.write_timeout = 3.second + + sock.gets(4).should eq("ping") + sock << "pong" + channel.send nil + end + rescue exc + channel.send exc end - rescue exc - channel.send exc - end - if exc = channel.receive - raise exc - end + if exc = channel.receive + raise exc + end - TCPSocket.open("localhost", port) do |client| - client.read_timeout = 3.second - client.write_timeout = 3.second - client << "ping" - client.gets(4).should eq("pong") - end + TCPSocket.open("localhost", port) do |client| + client.read_timeout = 3.second + client.write_timeout = 3.second + client << "ping" + client.gets(4).should eq("pong") + end - if exc = channel.receive - raise exc + if exc = channel.receive + raise exc + end end - end + {% end %} end diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr index dc66d8038036..6b349072294d 100644 --- a/spec/std/socket/udp_socket_spec.cr +++ b/spec/std/socket/udp_socket_spec.cr @@ -78,6 +78,10 @@ describe UDPSocket, tags: "network" do # Darwin also has a bug that prevents selecting the "default" interface. # https://lists.apple.com/archives/darwin-kernel/2014/Mar/msg00012.html pending "joins and transmits to multicast groups" + elsif {{ flag?(:dragonfly) }} && family == Socket::Family::INET6 + # TODO: figure out why updating `multicast_loopback` produces a + # `setsockopt 9: Can't assign requested address + pending "joins and transmits to multicast groups" elsif {{ flag?(:solaris) }} && family == Socket::Family::INET # TODO: figure out why updating `multicast_loopback` produces a # `setsockopt 18: Invalid argument` error From 0cac615ac9575ef368d9d6663d914d2772885b2d Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Tue, 5 Nov 2024 13:14:38 -0600 Subject: [PATCH 1449/1551] Add `Enumerable#find_value` (#14893) Co-authored-by: Sijawusz Pur Rahnama --- spec/std/enumerable_spec.cr | 25 +++++++++++++++++++++++++ src/enumerable.cr | 19 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 4ff17d672687..084fe80dcf96 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -557,6 +557,31 @@ describe "Enumerable" do end end + describe "find_value" do + it "finds and returns the first truthy block result" do + [1, 2, 3].find_value { |i| "1" if i == 1 }.should eq "1" + {1, 2, 3}.find_value { |i| "2" if i == 2 }.should eq "2" + (1..3).find_value { |i| "3" if i == 3 }.should eq "3" + + # Block returns `true && expression` vs the above `expression if true`. + # Same idea, but a different idiom. It serves as an allegory for the next + # test which checks `false` vs `nil`. + [1, 2, 3].find_value { |i| i == 1 && "1" }.should eq "1" + {1, 2, 3}.find_value { |i| i == 2 && "2" }.should eq "2" + (1..3).find_value { |i| i == 3 && "3" }.should eq "3" + end + + it "returns the default value if there are no truthy block results" do + {1, 2, 3}.find_value { |i| "4" if i == 4 }.should eq nil + {1, 2, 3}.find_value "nope" { |i| "4" if i == 4 }.should eq "nope" + ([] of Int32).find_value false { true }.should eq false + + # Same as above but returns `false` instead of `nil`. + {1, 2, 3}.find_value { |i| i == 4 && "4" }.should eq nil + {1, 2, 3}.find_value "nope" { |i| i == 4 && "4" }.should eq "nope" + end + end + describe "first" do it "calls block if empty" do (1...1).first { 10 }.should eq(10) diff --git a/src/enumerable.cr b/src/enumerable.cr index ff49de1ff308..5504c5d60064 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -558,6 +558,25 @@ module Enumerable(T) raise Enumerable::NotFoundError.new end + # Yields each value until the first truthy block result and returns that result. + # + # Accepts an optional parameter `if_none`, to set what gets returned if + # no element is found (defaults to `nil`). + # + # ``` + # [1, 2, 3, 4].find_value { |i| i > 2 } # => true + # [1, 2, 3, 4].find_value { |i| i > 8 } # => nil + # [1, 2, 3, 4].find_value(-1) { |i| i > 8 } # => -1 + # ``` + def find_value(if_none = nil, & : T ->) + each do |i| + if result = yield i + return result + end + end + if_none + end + # Returns the first element in the collection, # If the collection is empty, calls the block and returns its value. # From a92a6c245d31d6de36a7817faabdf70f5d22d8fb Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 6 Nov 2024 17:22:50 +0800 Subject: [PATCH 1450/1551] Improve `System::User` specs on Windows (#15156) * Ensures `USER_NAME` and `USER_ID` are correct if the current username contains whitespace characters. * Normalizes the username portion of the user ID in assertions as well, since it seems some versions of Windows would break the specs without it. We already do this to the domain portion. --- spec/std/system/user_spec.cr | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/spec/std/system/user_spec.cr b/spec/std/system/user_spec.cr index f0cb977d014d..32f2126d13c0 100644 --- a/spec/std/system/user_spec.cr +++ b/spec/std/system/user_spec.cr @@ -2,9 +2,9 @@ require "spec" require "system/user" {% if flag?(:win32) %} - {% name, id = `whoami /USER /FO TABLE /NH`.stringify.chomp.split(" ") %} - USER_NAME = {{ name }} - USER_ID = {{ id }} + {% parts = `whoami /USER /FO TABLE /NH`.stringify.chomp.split(" ") %} + USER_NAME = {{ parts[0..-2].join(" ") }} + USER_ID = {{ parts[-1] }} {% else %} USER_NAME = {{ `id -un`.stringify.chomp }} USER_ID = {{ `id -u`.stringify.chomp }} @@ -17,8 +17,7 @@ def normalized_username(username) # on Windows, domain names are case-insensitive, so we unify the letter case # from sources like `whoami`, `hostname`, or Win32 APIs {% if flag?(:win32) %} - domain, _, user = username.partition('\\') - "#{domain.upcase}\\#{user}" + username.upcase {% else %} username {% end %} From c32237051ae0abfadca97f86c4ec6d16ab0ef90f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 6 Nov 2024 17:23:24 +0800 Subject: [PATCH 1451/1551] Implement the ARM64 Windows context switch (#15155) This is essentially the existing AArch64 context switch, plus the Win32-specific Thread Information Block handling. It makes specs like `spec/std/channel_spec.cr` pass, and the compiler macro run also depends on it. --- .../{aarch64.cr => aarch64-generic.cr} | 2 +- src/fiber/context/aarch64-microsoft.cr | 149 ++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) rename src/fiber/context/{aarch64.cr => aarch64-generic.cr} (98%) create mode 100644 src/fiber/context/aarch64-microsoft.cr diff --git a/src/fiber/context/aarch64.cr b/src/fiber/context/aarch64-generic.cr similarity index 98% rename from src/fiber/context/aarch64.cr rename to src/fiber/context/aarch64-generic.cr index 4bd811200fc1..2839ee030ef5 100644 --- a/src/fiber/context/aarch64.cr +++ b/src/fiber/context/aarch64-generic.cr @@ -1,4 +1,4 @@ -{% skip_file unless flag?(:aarch64) %} +{% skip_file unless flag?(:aarch64) && !flag?(:win32) %} class Fiber # :nodoc: diff --git a/src/fiber/context/aarch64-microsoft.cr b/src/fiber/context/aarch64-microsoft.cr new file mode 100644 index 000000000000..b2fa76580418 --- /dev/null +++ b/src/fiber/context/aarch64-microsoft.cr @@ -0,0 +1,149 @@ +{% skip_file unless flag?(:aarch64) && flag?(:win32) %} + +class Fiber + # :nodoc: + def makecontext(stack_ptr, fiber_main) : Nil + # ARM64 Windows also follows the AAPCS64 for the most part, except extra + # bookkeeping information needs to be kept in the Thread Information Block, + # referenceable from the x18 register + + # 12 general-purpose registers + 8 FPU registers + 1 parameter + 3 qwords for NT_TIB + @context.stack_top = (stack_ptr - 24).as(Void*) + @context.resumable = 1 + + # actual stack top, not including guard pages and reserved pages + LibC.GetNativeSystemInfo(out system_info) + stack_top = @stack_bottom - system_info.dwPageSize + + stack_ptr[-4] = self.as(Void*) # x0 (r0): puts `self` as first argument for `fiber_main` + stack_ptr[-16] = fiber_main.pointer # x30 (lr): initial `resume` will `ret` to this address + + # The following three values are stored in the Thread Information Block (NT_TIB) + # and are used by Windows to track the current stack limits + stack_ptr[-3] = @stack # [x18, #0x1478]: Win32 DeallocationStack + stack_ptr[-2] = stack_top # [x18, #16]: Stack Limit + stack_ptr[-1] = @stack_bottom # [x18, #8]: Stack Base + end + + # :nodoc: + @[NoInline] + @[Naked] + def self.swapcontext(current_context, new_context) : Nil + # x0 , x1 + + # see also `./aarch64-generic.cr` + {% if compare_versions(Crystal::LLVM_VERSION, "9.0.0") >= 0 %} + asm(" + stp d15, d14, [sp, #-24*8]! + stp d13, d12, [sp, #2*8] + stp d11, d10, [sp, #4*8] + stp d9, d8, [sp, #6*8] + stp x30, x29, [sp, #8*8] // lr, fp + stp x28, x27, [sp, #10*8] + stp x26, x25, [sp, #12*8] + stp x24, x23, [sp, #14*8] + stp x22, x21, [sp, #16*8] + stp x20, x19, [sp, #18*8] + str x0, [sp, #20*8] // push 1st argument + + ldr x19, [x18, #0x1478] // Thread Information Block: Win32 DeallocationStack + str x19, [sp, #21*8] + ldr x19, [x18, #16] // Thread Information Block: Stack Limit + str x19, [sp, #22*8] + ldr x19, [x18, #8] // Thread Information Block: Stack Base + str x19, [sp, #23*8] + + mov x19, sp // current_context.stack_top = sp + str x19, [x0, #0] + mov x19, #1 // current_context.resumable = 1 + str x19, [x0, #8] + + mov x19, #0 // new_context.resumable = 0 + str x19, [x1, #8] + ldr x19, [x1, #0] // sp = new_context.stack_top (x19) + mov sp, x19 + + ldr x19, [sp, #23*8] + str x19, [x18, #8] + ldr x19, [sp, #22*8] + str x19, [x18, #16] + ldr x19, [sp, #21*8] + str x19, [x18, #0x1478] + + ldr x0, [sp, #20*8] // pop 1st argument (+ alignment) + ldp x20, x19, [sp, #18*8] + ldp x22, x21, [sp, #16*8] + ldp x24, x23, [sp, #14*8] + ldp x26, x25, [sp, #12*8] + ldp x28, x27, [sp, #10*8] + ldp x30, x29, [sp, #8*8] // lr, fp + ldp d9, d8, [sp, #6*8] + ldp d11, d10, [sp, #4*8] + ldp d13, d12, [sp, #2*8] + ldp d15, d14, [sp], #24*8 + + // avoid a stack corruption that will confuse the unwinder + mov x16, x30 // save lr + mov x30, #0 // reset lr + br x16 // jump to new pc value + ") + {% else %} + # On LLVM < 9.0 using the previous code emits some additional + # instructions that breaks the context switching. + asm(" + stp d15, d14, [sp, #-24*8]! + stp d13, d12, [sp, #2*8] + stp d11, d10, [sp, #4*8] + stp d9, d8, [sp, #6*8] + stp x30, x29, [sp, #8*8] // lr, fp + stp x28, x27, [sp, #10*8] + stp x26, x25, [sp, #12*8] + stp x24, x23, [sp, #14*8] + stp x22, x21, [sp, #16*8] + stp x20, x19, [sp, #18*8] + str x0, [sp, #20*8] // push 1st argument + + ldr x19, [x18, #0x1478] // Thread Information Block: Win32 DeallocationStack + str x19, [sp, #21*8] + ldr x19, [x18, #16] // Thread Information Block: Stack Limit + str x19, [sp, #22*8] + ldr x19, [x18, #8] // Thread Information Block: Stack Base + str x19, [sp, #23*8] + + mov x19, sp // current_context.stack_top = sp + str x19, [$0, #0] + mov x19, #1 // current_context.resumable = 1 + str x19, [$0, #8] + + mov x19, #0 // new_context.resumable = 0 + str x19, [$1, #8] + ldr x19, [$1, #0] // sp = new_context.stack_top (x19) + mov sp, x19 + + ldr x19, [sp, #23*8] + str x19, [x18, #8] + ldr x19, [sp, #22*8] + str x19, [x18, #16] + ldr x19, [sp, #21*8] + str x19, [x18, #0x1478] + + ldr x0, [sp, #20*8] // pop 1st argument (+ alignment) + ldp x20, x19, [sp, #18*8] + ldp x22, x21, [sp, #16*8] + ldp x24, x23, [sp, #14*8] + ldp x26, x25, [sp, #12*8] + ldp x28, x27, [sp, #10*8] + ldp x30, x29, [sp, #8*8] // lr, fp + ldp d9, d8, [sp, #6*8] + ldp d11, d10, [sp, #4*8] + ldp d13, d12, [sp, #2*8] + ldp d15, d14, [sp], #24*8 + + // avoid a stack corruption that will confuse the unwinder + mov x16, x30 // save lr + mov x30, #0 // reset lr + br x16 // jump to new pc value + " :: "r"(current_context), "r"(new_context)) + {% end %} + end +end From 650bb6d6517d6a26ac3e1a3b70b745472e739182 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 6 Nov 2024 17:23:55 +0800 Subject: [PATCH 1452/1551] Make `cmd.exe` drop `%PROCESSOR_ARCHITECTURE%` in `Process` specs (#15158) On ARM64 this appears to be set to `ARM64` automatically by `cmd.exe`, even when an empty environment block is passed to `LibC.CreateProcessW`. --- spec/std/process_spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 8347804cadc5..965ed1431cf4 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -32,7 +32,7 @@ end private def print_env_command {% if flag?(:win32) %} # cmd adds these by itself, clear them out before printing. - shell_command("set COMSPEC=& set PATHEXT=& set PROMPT=& set") + shell_command("set COMSPEC=& set PATHEXT=& set PROMPT=& set PROCESSOR_ARCHITECTURE=& set") {% else %} {"env", [] of String} {% end %} From b0e708a6dda65f892d5810891b7b43ae54bc9494 Mon Sep 17 00:00:00 2001 From: nanobowers Date: Wed, 6 Nov 2024 12:41:07 -0500 Subject: [PATCH 1453/1551] Make utilities posix compatible (#15139) Replace `cp -av` with `cp -R -P -p` because neither `-a` nor `-v` are POSIX compliant (Makefile) Replace `local` keyword in shell functions with subshell style `( )` functions. (bin/crystal) Fixed other Shellcheck warnings. (bin/crystal) --- Makefile | 8 ++++---- bin/crystal | 35 ++++++++++++++--------------------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index d30db53464f7..b77fe62df52b 100644 --- a/Makefile +++ b/Makefile @@ -145,7 +145,7 @@ samples: ## Build example programs docs: ## Generate standard library documentation $(call check_llvm_config) ./bin/crystal docs src/docs_main.cr $(DOCS_OPTIONS) --project-name=Crystal --project-version=$(CRYSTAL_VERSION) --source-refname=$(CRYSTAL_CONFIG_BUILD_COMMIT) - cp -av doc/ docs/ + cp -R -P -p doc/ docs/ .PHONY: crystal crystal: $(O)/$(CRYSTAL_BIN) ## Build the compiler @@ -168,7 +168,7 @@ install: $(O)/$(CRYSTAL_BIN) man/crystal.1.gz ## Install the compiler at DESTDIR $(INSTALL) -m 0755 "$(O)/$(CRYSTAL_BIN)" "$(BINDIR)/$(CRYSTAL_BIN)" $(INSTALL) -d -m 0755 $(DATADIR) - cp $(if $(deref_symlinks),-rvL --preserve=all,-av) src "$(DATADIR)/src" + cp -R -p $(if $(deref_symlinks),-L,-P) src "$(DATADIR)/src" rm -rf "$(DATADIR)/$(LLVM_EXT_OBJ)" # Don't install llvm_ext.o $(INSTALL) -d -m 0755 "$(MANDIR)/man1/" @@ -206,8 +206,8 @@ uninstall: ## Uninstall the compiler from DESTDIR install_docs: docs ## Install docs at DESTDIR $(INSTALL) -d -m 0755 $(DATADIR) - cp -av docs "$(DATADIR)/docs" - cp -av samples "$(DATADIR)/examples" + cp -R -P -p docs "$(DATADIR)/docs" + cp -R -P -p samples "$(DATADIR)/examples" .PHONY: uninstall_docs uninstall_docs: ## Uninstall docs from DESTDIR diff --git a/bin/crystal b/bin/crystal index a1fddf1c58b4..ad5e3357c985 100755 --- a/bin/crystal +++ b/bin/crystal @@ -32,18 +32,16 @@ resolve_symlinks() { _resolve_symlinks "$1" } -_resolve_symlinks() { +_resolve_symlinks() ( _assert_no_path_cycles "$@" || return - local dir_context path - if path=$(readlink -- "$1"); then dir_context=$(dirname -- "$1") _resolve_symlinks "$(_prepend_dir_context_if_necessary "$dir_context" "$path")" "$@" else printf '%s\n' "$1" fi -} +) _prepend_dir_context_if_necessary() { if [ "$1" = . ]; then @@ -60,9 +58,7 @@ _prepend_path_if_relative() { esac } -_assert_no_path_cycles() { - local target path - +_assert_no_path_cycles() ( target=$1 shift @@ -71,7 +67,7 @@ _assert_no_path_cycles() { return 1 fi done -} +) canonicalize_path() { if [ -d "$1" ]; then @@ -85,35 +81,32 @@ _canonicalize_dir_path() { { cd "$1" 2>/dev/null && pwd -P; } } -_canonicalize_file_path() { - local dir file +_canonicalize_file_path() ( dir=$(dirname -- "$1") file=$(basename -- "$1") { cd "$dir" 2>/dev/null >/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file"; } -} +) ############################################################################## # Based on http://stackoverflow.com/q/370047/641451 -remove_path_item() { - local path item - +remove_path_item() ( path="$1" printf "%s" "$path" | awk -v item="$2" -v RS=: -v ORS=: '$0 != item' | sed 's/:$//' -} +) ############################################################################## -__has_colors() { - local num_colors=$(tput colors 2>/dev/null) +__has_colors() ( + num_colors=$(tput colors 2>/dev/null) if [ -n "$num_colors" ] && [ "$num_colors" -gt 2 ]; then return 0 else return 1 fi -} +) __error_msg() { if __has_colors; then # bold red coloring @@ -148,7 +141,7 @@ export CRYSTAL_HAS_WRAPPER=true PARENT_CRYSTAL="$CRYSTAL" # check if the parent crystal command is a path that refers to this script -if [ -z "${PARENT_CRYSTAL##*/*}" -a "$(realpath "$PARENT_CRYSTAL")" = "$SCRIPT_PATH" ]; then +if [ -z "${PARENT_CRYSTAL##*/*}" ] && [ "$(realpath "$PARENT_CRYSTAL")" = "$SCRIPT_PATH" ]; then # ignore it and use `crystal` as parent compiler command PARENT_CRYSTAL="crystal" fi @@ -174,8 +167,8 @@ PARENT_CRYSTAL_EXISTS=$(test !$?) if ($PARENT_CRYSTAL_EXISTS); then if [ -z "$CRYSTAL_CONFIG_LIBRARY_PATH" ] || [ -z "$CRYSTAL_LIBRARY_PATH" ]; then CRYSTAL_INSTALLED_LIBRARY_PATH="$($PARENT_CRYSTAL env CRYSTAL_LIBRARY_PATH 2> /dev/null || echo "")" - export CRYSTAL_LIBRARY_PATH=${CRYSTAL_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} - export CRYSTAL_CONFIG_LIBRARY_PATH=${CRYSTAL_CONFIG_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH} + export CRYSTAL_LIBRARY_PATH="${CRYSTAL_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH}" + export CRYSTAL_CONFIG_LIBRARY_PATH="${CRYSTAL_CONFIG_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH}" fi fi From febdbce7bb544c8fe648f588d805bf5300debf6d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 7 Nov 2024 01:50:24 +0800 Subject: [PATCH 1454/1551] Do not link against `DbgHelp` for MinGW-w64 CI build (#15160) --- .github/workflows/mingw-w64.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml index 10841a325bf5..3f8172e9a575 100644 --- a/.github/workflows/mingw-w64.yml +++ b/.github/workflows/mingw-w64.yml @@ -83,7 +83,7 @@ jobs: cc crystal.obj -o bin/crystal.exe \ $(pkg-config bdw-gc libpcre2-8 iconv zlib libffi --libs) \ $(llvm-config --libs --system-libs --ldflags) \ - -lDbgHelp -lole32 -lWS2_32 -Wl,--stack,0x800000 + -lole32 -lWS2_32 -Wl,--stack,0x800000 ldd bin/crystal.exe | grep -iv /c/windows/system32 | sed 's/.* => //; s/ (.*//' | xargs -t -i cp '{}' bin/ - name: Upload Crystal From 84a293d498211e7aa9dc05deaa26724433127c29 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 7 Nov 2024 01:50:47 +0800 Subject: [PATCH 1455/1551] Pre-compute `String` size after `#chomp()` if possible (#15153) If a `String` ends with one of the newlines and already has its number of characters known, the returned `String` must have 1 or 2 fewer characters than `self`. This PR avoids the need to re-compute `@length` in the new string again if possible. This technique is also applicable to many more substring extraction methods in `String`. --- spec/std/string_spec.cr | 18 ++++++++++++++++++ src/string.cr | 14 +++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 6d7487ded0e2..0a57ee9034a9 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -773,6 +773,24 @@ describe "String" do it { "hello\n\n\n\n".chomp("").should eq("hello\n\n\n\n") } it { "hello\r\n".chomp("\n").should eq("hello") } + + it "pre-computes string size if possible" do + {"!hello!", "\u{1f602}hello\u{1f602}", "\xFEhello\xFF"}.each do |str| + {"", "\n", "\r", "\r\n"}.each do |newline| + x = str + newline + x.size_known?.should be_true + y = x.chomp + y.@length.should eq(7) + end + end + end + + it "does not pre-compute string size if not possible" do + x = String.build &.<< "abc\n" + x.size_known?.should be_false + y = x.chomp + y.size_known?.should be_false + end end describe "lchop" do diff --git a/src/string.cr b/src/string.cr index 7507e3b7249e..09272c80eb45 100644 --- a/src/string.cr +++ b/src/string.cr @@ -1661,12 +1661,12 @@ class String case to_unsafe[bytesize - 1] when '\n' if bytesize > 1 && to_unsafe[bytesize - 2] === '\r' - unsafe_byte_slice_string(0, bytesize - 2) + unsafe_byte_slice_string(0, bytesize - 2, @length > 0 ? @length - 2 : 0) else - unsafe_byte_slice_string(0, bytesize - 1) + unsafe_byte_slice_string(0, bytesize - 1, @length > 0 ? @length - 1 : 0) end when '\r' - unsafe_byte_slice_string(0, bytesize - 1) + unsafe_byte_slice_string(0, bytesize - 1, @length > 0 ? @length - 1 : 0) else self end @@ -5552,12 +5552,12 @@ class String Slice.new(to_unsafe + byte_offset, bytesize - byte_offset, read_only: true) end - protected def unsafe_byte_slice_string(byte_offset) - String.new(unsafe_byte_slice(byte_offset)) + protected def unsafe_byte_slice_string(byte_offset, *, size = 0) + String.new(to_unsafe + byte_offset, bytesize - byte_offset, size) end - protected def unsafe_byte_slice_string(byte_offset, count) - String.new(unsafe_byte_slice(byte_offset, count)) + protected def unsafe_byte_slice_string(byte_offset, count, size = 0) + String.new(to_unsafe + byte_offset, count, size) end protected def self.char_bytes_and_bytesize(char : Char) From e8a99d48b3e9db6d36554d0b4a793e86d36b0b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 7 Nov 2024 13:16:18 +0100 Subject: [PATCH 1456/1551] Update XCode 15.3.0 in circleci (#15164) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 574d390f3bc3..baf9b41a1be3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -285,7 +285,7 @@ jobs: dist_darwin: macos: - xcode: 13.4.1 + xcode: 15.3.0 shell: /bin/bash --login -eo pipefail steps: - restore_cache: From 3480f052361b75db46663f9bd3a89b6957c3f99a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 7 Nov 2024 22:25:51 +0800 Subject: [PATCH 1457/1551] Support MSYS2's CLANGARM64 environment on ARM64 Windows (#15159) Tested using a Windows VM on an Apple M2 host. The instructions we use in our MinGW-w64 CI workflow will just work by simply replacing `-ucrt-` in the MSYS2 package names with `-clang-aarch64-`. Requires #15155 on both the cross-compilation host and the target. --- src/lib_c/aarch64-windows-gnu | 1 + 1 file changed, 1 insertion(+) create mode 120000 src/lib_c/aarch64-windows-gnu diff --git a/src/lib_c/aarch64-windows-gnu b/src/lib_c/aarch64-windows-gnu new file mode 120000 index 000000000000..072348f65d09 --- /dev/null +++ b/src/lib_c/aarch64-windows-gnu @@ -0,0 +1 @@ +x86_64-windows-msvc \ No newline at end of file From fd1d0e30f9efc3c9ad4f30d1d4bf12796c67c89c Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Thu, 7 Nov 2024 15:30:23 +0100 Subject: [PATCH 1458/1551] Fix: setup signal handlers in interpreted code (#14766) Adds a couple interpreter primitives to handle signals properly between the interpreter (receives signals and forwards them to the interpreted code signal pipe) then the interpreted fiber will receive the signal (through the pipe) and handle it. The interpreted code is thus in control of what signals it wants the process to receive, while avoiding the issue where a signal handler would be interpreted code (i.e. recipe for disaster). Fixes the default handler for SIGCHLD into the interpreted code, so `Process.run` ~~now works as expected and~~ don't hang forever anymore. The interpreter will still randomly segfault because `fork` should probably be an interpreter primitive (see the comments below). --- spec/std/signal_spec.cr | 3 +- .../crystal/interpreter/instructions.cr | 9 +++ .../crystal/interpreter/interpreter.cr | 32 ++++++++++ .../crystal/interpreter/primitives.cr | 6 ++ src/crystal/interpreter.cr | 8 +++ src/crystal/system/unix/signal.cr | 64 +++++++++++++++---- src/kernel.cr | 4 ++ 7 files changed, 110 insertions(+), 16 deletions(-) diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr index ee5eff048c73..b90c45c389e7 100644 --- a/spec/std/signal_spec.cr +++ b/spec/std/signal_spec.cr @@ -3,8 +3,7 @@ require "./spec_helper" require "signal" -# interpreted code never receives signals (#12241) -pending_interpreted describe: "Signal" do +describe "Signal" do typeof(Signal::ABRT.reset) typeof(Signal::ABRT.ignore) typeof(Signal::ABRT.trap { 1 }) diff --git a/src/compiler/crystal/interpreter/instructions.cr b/src/compiler/crystal/interpreter/instructions.cr index 23428df03b90..f8f986f1b44a 100644 --- a/src/compiler/crystal/interpreter/instructions.cr +++ b/src/compiler/crystal/interpreter/instructions.cr @@ -1675,6 +1675,15 @@ require "./repl" code: fiber_resumable(context), }, + interpreter_signal_descriptor: { + pop_values: [fd : Int32], + code: signal_descriptor(fd), + }, + interpreter_signal: { + pop_values: [signum : Int32, handler : Int32], + code: signal(signum, handler), + }, + {% if flag?(:bits64) %} interpreter_intrinsics_memcpy: { pop_values: [dest : Pointer(Void), src : Pointer(Void), len : UInt64, is_volatile : Bool], diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index e26a6751c176..c084ff43e910 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -1174,6 +1174,38 @@ class Crystal::Repl::Interpreter fiber.@context.resumable end + private def signal_descriptor(fd : Int32) : Nil + {% if flag?(:unix) %} + # replace the interpreter's signal writer so that the interpreted code + # will receive signals from now on + writer = IO::FileDescriptor.new(fd) + writer.sync = true + Crystal::System::Signal.writer = writer + {% else %} + raise "BUG: interpreter doesn't support signals on this target" + {% end %} + end + + private def signal(signum : Int32, handler : Int32) : Nil + {% if flag?(:unix) %} + signal = ::Signal.new(signum) + case handler + when 0 + signal.reset + when 1 + signal.ignore + else + # register the signal for the OS so the process will receive them; + # registers a fake handler since the interpreter won't handle the signal: + # the interpreted code will receive it and will execute the interpreted + # handler + signal.trap { } + end + {% else %} + raise "BUG: interpreter doesn't support signals on this target" + {% end %} + end + private def pry(ip, instructions, stack_bottom, stack) offset = (ip - instructions.instructions.to_unsafe).to_i32 node = instructions.nodes[offset]? diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr index ca436947370e..619f678ad6bd 100644 --- a/src/compiler/crystal/interpreter/primitives.cr +++ b/src/compiler/crystal/interpreter/primitives.cr @@ -431,6 +431,12 @@ class Crystal::Repl::Compiler when "interpreter_fiber_resumable" accept_call_args(node) interpreter_fiber_resumable(node: node) + when "interpreter_signal_descriptor" + accept_call_args(node) + interpreter_signal_descriptor(node: node) + when "interpreter_signal" + accept_call_args(node) + interpreter_signal(node: node) when "interpreter_intrinsics_memcpy" accept_call_args(node) interpreter_intrinsics_memcpy(node: node) diff --git a/src/crystal/interpreter.cr b/src/crystal/interpreter.cr index d3b3589d50cb..45a2bce699d1 100644 --- a/src/crystal/interpreter.cr +++ b/src/crystal/interpreter.cr @@ -24,5 +24,13 @@ module Crystal @[Primitive(:interpreter_fiber_resumable)] def self.fiber_resumable(context) : LibC::Long end + + @[Primitive(:interpreter_signal_descriptor)] + def self.signal_descriptor(fd : Int32) : Nil + end + + @[Primitive(:interpreter_signal)] + def self.signal(signum : Int32, handler : Int32) : Nil + end end end diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr index 1d1e885fc71d..99fc30839a6d 100644 --- a/src/crystal/system/unix/signal.cr +++ b/src/crystal/system/unix/signal.cr @@ -22,17 +22,21 @@ module Crystal::System::Signal @@mutex.synchronize do unless @@handlers[signal]? @@sigset << signal - action = LibC::Sigaction.new - - # restart some interrupted syscalls (read, write, accept, ...) instead - # of returning EINTR: - action.sa_flags = LibC::SA_RESTART - - action.sa_sigaction = LibC::SigactionHandlerT.new do |value, _, _| - writer.write_bytes(value) unless writer.closed? - end - LibC.sigemptyset(pointerof(action.@sa_mask)) - LibC.sigaction(signal, pointerof(action), nil) + {% if flag?(:interpreted) %} + Crystal::Interpreter.signal(signal.value, 2) + {% else %} + action = LibC::Sigaction.new + + # restart some interrupted syscalls (read, write, accept, ...) instead + # of returning EINTR: + action.sa_flags = LibC::SA_RESTART + + action.sa_sigaction = LibC::SigactionHandlerT.new do |value, _, _| + writer.write_bytes(value) unless writer.closed? + end + LibC.sigemptyset(pointerof(action.@sa_mask)) + LibC.sigaction(signal, pointerof(action), nil) + {% end %} end @@handlers[signal] = handler end @@ -62,7 +66,16 @@ module Crystal::System::Signal else @@mutex.synchronize do @@handlers.delete(signal) - LibC.signal(signal, handler) + {% if flag?(:interpreted) %} + h = case handler + when LibC::SIG_DFL then 0 + when LibC::SIG_IGN then 1 + else 2 + end + Crystal::Interpreter.signal(signal.value, h) + {% else %} + LibC.signal(signal, handler) + {% end %} @@sigset.delete(signal) end end @@ -116,7 +129,13 @@ module Crystal::System::Signal # sub-process. def self.after_fork_before_exec ::Signal.each do |signal| - LibC.signal(signal, LibC::SIG_DFL) if @@sigset.includes?(signal) + next unless @@sigset.includes?(signal) + + {% if flag?(:interpreted) %} + Crystal::Interpreter.signal(signal.value, 0) + {% else %} + LibC.signal(signal, LibC::SIG_DFL) + {% end %} end ensure {% unless flag?(:preview_mt) %} @@ -132,6 +151,14 @@ module Crystal::System::Signal @@pipe[1] end + {% unless flag?(:interpreted) %} + # :nodoc: + def self.writer=(writer : IO::FileDescriptor) + @@pipe = {@@pipe[0], writer} + writer + end + {% end %} + private def self.fatal(message : String) STDERR.puts("FATAL: #{message}, exiting") STDERR.flush @@ -175,7 +202,16 @@ module Crystal::System::Signal return unless @@setup_default_handlers.test_and_set @@sigset.clear start_loop - ::Signal::PIPE.ignore + + {% if flag?(:interpreted) %} + # replace the interpreter's writer pipe with the interpreted, so signals + # will be received by the interpreter, but handled by the interpreted + # signal loop + Crystal::Interpreter.signal_descriptor(@@pipe[1].fd) + {% else %} + ::Signal::PIPE.ignore + {% end %} + ::Signal::CHLD.reset end diff --git a/src/kernel.cr b/src/kernel.cr index ac241161c16d..f32f98e67c54 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -629,3 +629,7 @@ end end end {% end %} + +{% if flag?(:interpreted) && flag?(:unix) %} + Crystal::System::Signal.setup_default_handlers +{% end %} From de2d02eea70c7d4f2af8a25118adeff90b5f09ff Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 7 Nov 2024 22:30:38 +0800 Subject: [PATCH 1459/1551] Replace handle atomically in `IO::FileDescriptor#close` on Windows (#15165) The spec introduced in #14698 fails on Windows very rarely (e.g. https://github.com/crystal-lang/crystal/actions/runs/11681506288/job/32532033082, https://github.com/crystal-lang/crystal/actions/runs/11642959689/job/32449149741). This PR seems to fix that; other platforms already do the same in `#system_close`, and double close remains an error. --- src/crystal/system/win32/file_descriptor.cr | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 1f277505302a..4265701cd8b2 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -195,7 +195,11 @@ module Crystal::System::FileDescriptor end def file_descriptor_close(&) - if LibC.CloseHandle(windows_handle) == 0 + # Clear the @volatile_fd before actually closing it in order to + # reduce the chance of reading an outdated handle value + handle = LibC::HANDLE.new(@volatile_fd.swap(LibC::INVALID_HANDLE_VALUE.address)) + + if LibC.CloseHandle(handle) == 0 yield end end From eabc15d0a008adf5829530afd920755cf08c0bca Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 8 Nov 2024 03:46:15 +0800 Subject: [PATCH 1460/1551] Close some dangling sockets in specs (#15163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These appear to be causing intermittent CI failures, especially on Windows (e.g. https://github.com/crystal-lang/crystal/actions/runs/11031590842/job/30639450164). Co-authored-by: Johannes Müller --- spec/std/socket/socket_spec.cr | 2 ++ spec/std/socket/udp_socket_spec.cr | 2 ++ 2 files changed, 4 insertions(+) diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index 65f7ed72a453..8bb7349318c6 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -87,6 +87,8 @@ describe Socket, tags: "network" do expect_raises(IO::TimeoutError) { server.accept } expect_raises(IO::TimeoutError) { server.accept? } + ensure + server.try &.close end it "sends messages" do diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr index 6b349072294d..a84a6adebc74 100644 --- a/spec/std/socket/udp_socket_spec.cr +++ b/spec/std/socket/udp_socket_spec.cr @@ -28,6 +28,8 @@ describe UDPSocket, tags: "network" do socket = UDPSocket.new(family) socket.bind(address, 0) socket.local_address.address.should eq address + ensure + socket.try &.close end it "sends and receives messages" do From d9cb48494b2a34900591b23324645b711e88b325 Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Thu, 7 Nov 2024 16:46:28 -0300 Subject: [PATCH 1461/1551] Add type restrictions to Levenshtein (#15168) It best documents what happens when no entry is between the tolerance level of distance --- src/levenshtein.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/levenshtein.cr b/src/levenshtein.cr index e890d59c90ef..01ad1bc40784 100644 --- a/src/levenshtein.cr +++ b/src/levenshtein.cr @@ -139,7 +139,7 @@ module Levenshtein # end # best_match # => "ello" # ``` - def self.find(name, tolerance = nil, &) + def self.find(name, tolerance = nil, &) : String? Finder.find(name, tolerance) do |sn| yield sn end @@ -154,7 +154,7 @@ module Levenshtein # Levenshtein.find("hello", ["hullo", "hel", "hall", "hell"], 2) # => "hullo" # Levenshtein.find("hello", ["hurlo", "hel", "hall"], 1) # => nil # ``` - def self.find(name, all_names, tolerance = nil) + def self.find(name, all_names, tolerance = nil) : String? Finder.find(name, all_names, tolerance) end end From 6caea6a0149e383514fc64bf730a4be0acee075b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 8 Nov 2024 19:32:28 +0800 Subject: [PATCH 1462/1551] Fix static linking when using MinGW-w64 (#15167) * The MinGW-w64 equivalent for MSVC's `libcmt.lib` or `msvcrt.lib` is provided by MinGW-w64's built-in spec files directly (see `cc -dumpspecs`), so we do not link against it. * There cannot be static libraries for the Win32 APIs in MinGW-w64, because that would be proprietary code; all static libraries on MSYS2 link against the C runtimes dynamically, i.e. they behave like `/MD` in MSVC or `CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL` in CMake. We therefore never link against `libucrt`. * Passing `-lucrt` explicitly may lead to a crash in the startup code when combined with `-static` and `-msvcrt`, depending on the relative link order. In MinGW-w64, [`-lmsvcrt` is already equivalent to `-lucrt` or `-lmsvcrt-os`](https://gcc.gnu.org/onlinedocs/gcc/Cygwin-and-MinGW-Options.html), depending on how it was built; in particular, `/ucrt64/bin/libmsvcrt.a` is a copy of `libucrt.a` in MSYS2, but `/mingw64/bin/libmsvcrt.a` is a copy of `libmsvcrt-os.a`. Thus we drop `-lucrt` entirely and rely on the MinGW-w64's build-time configuration to select the appropriate C runtime. * `-mcrtdll` can be used to override the C runtime, and it _should_ be possible to cross-build binaries between the MINGW64 and the UCRT64 environments using this flag. The interpreter now imitates this linker behavior. --- .github/workflows/mingw-w64.yml | 2 +- src/compiler/crystal/loader/mingw.cr | 15 +++++++++++++-- src/crystal/system/win32/wmain.cr | 12 +++++------- src/empty.cr | 2 +- src/lib_c.cr | 2 +- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml index 3f8172e9a575..8e5db39a5fa1 100644 --- a/.github/workflows/mingw-w64.yml +++ b/.github/workflows/mingw-w64.yml @@ -80,7 +80,7 @@ jobs: shell: msys2 {0} run: | mkdir bin - cc crystal.obj -o bin/crystal.exe \ + cc crystal.obj -o bin/crystal.exe -municode \ $(pkg-config bdw-gc libpcre2-8 iconv zlib libffi --libs) \ $(llvm-config --libs --system-libs --ldflags) \ -lole32 -lWS2_32 -Wl,--stack,0x800000 diff --git a/src/compiler/crystal/loader/mingw.cr b/src/compiler/crystal/loader/mingw.cr index 677f564cec16..2c557a893640 100644 --- a/src/compiler/crystal/loader/mingw.cr +++ b/src/compiler/crystal/loader/mingw.cr @@ -7,6 +7,8 @@ require "crystal/system/win32/library_archive" # The core implementation is derived from the MSVC loader. Main deviations are: # # - `.parse` follows GNU `ld`'s style, rather than MSVC `link`'s; +# - `.parse` automatically inserts a C runtime library if `-mcrtdll` isn't +# supplied; # - `#library_filename` follows the usual naming of the MinGW linker: `.dll.a` # for DLL import libraries, `.a` for other libraries; # - `.default_search_paths` relies solely on `.cc_each_library_path`. @@ -28,6 +30,11 @@ class Crystal::Loader file_paths = [] of String extra_search_paths = [] of String + # note that `msvcrt` is a default runtime chosen at MinGW-w64 build time, + # `ucrt` is always UCRT (even in a MINGW64 environment), and + # `msvcrt-os` is always MSVCRT (even in a UCRT64 environment) + crt_dll = "msvcrt" + OptionParser.parse(args.dup) do |parser| parser.on("-L DIRECTORY", "--library-path DIRECTORY", "Add DIRECTORY to library search path") do |directory| extra_search_paths << directory @@ -39,17 +46,21 @@ class Crystal::Loader raise LoadError.new "static libraries are not supported by Crystal's runtime loader" end parser.unknown_args do |args, after_dash| - file_paths.concat args + file_paths.concat args.reject(&.starts_with?("-mcrtdll=")) end parser.invalid_option do |arg| - unless arg.starts_with?("-Wl,") + if crt_dll_arg = arg.lchop?("-mcrtdll=") + # the GCC spec is `%{!mcrtdll=*:-lmsvcrt} %{mcrtdll=*:-l%*}` + crt_dll = crt_dll_arg + elsif !arg.starts_with?("-Wl,") raise LoadError.new "Not a recognized linker flag: #{arg}" end end end search_paths = extra_search_paths + search_paths + libnames << crt_dll begin loader = new(search_paths) diff --git a/src/crystal/system/win32/wmain.cr b/src/crystal/system/win32/wmain.cr index caad6748229f..2120bfc06bfc 100644 --- a/src/crystal/system/win32/wmain.cr +++ b/src/crystal/system/win32/wmain.cr @@ -2,14 +2,12 @@ require "c/stringapiset" require "c/winnls" require "c/stdlib" -{% begin %} - # we have both `main` and `wmain`, so we must choose an unambiguous entry point +# we have both `main` and `wmain`, so we must choose an unambiguous entry point +{% if flag?(:msvc) %} @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})] - {% if flag?(:msvc) %} - @[Link(ldflags: "/ENTRY:wmainCRTStartup")] - {% elsif flag?(:gnu) && !flag?(:interpreted) %} - @[Link(ldflags: "-municode")] - {% end %} + @[Link(ldflags: "/ENTRY:wmainCRTStartup")] +{% elsif flag?(:gnu) && !flag?(:interpreted) %} + @[Link(ldflags: "-municode")] {% end %} lib LibCrystalMain end diff --git a/src/empty.cr b/src/empty.cr index 204e30da48c0..cb79610a5be3 100644 --- a/src/empty.cr +++ b/src/empty.cr @@ -1,6 +1,6 @@ require "primitives" -{% if flag?(:win32) %} +{% if flag?(:msvc) %} @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})] # For `mainCRTStartup` {% end %} lib LibCrystalMain diff --git a/src/lib_c.cr b/src/lib_c.cr index 0bd8d2c2cc35..7bc94a34f53e 100644 --- a/src/lib_c.cr +++ b/src/lib_c.cr @@ -1,4 +1,4 @@ -{% if flag?(:win32) %} +{% if flag?(:msvc) %} @[Link({{ flag?(:static) ? "libucrt" : "ucrt" }})] {% end %} lib LibC From 675c68c1c9c681919d1eb876033f5722376e5614 Mon Sep 17 00:00:00 2001 From: Barney <86712892+BigBoyBarney@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:32:43 +0100 Subject: [PATCH 1463/1551] Add example for `Dir.glob` (#15171) --- src/dir/glob.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dir/glob.cr b/src/dir/glob.cr index cd45f0a03baf..2fc8d988c20a 100644 --- a/src/dir/glob.cr +++ b/src/dir/glob.cr @@ -37,6 +37,10 @@ class Dir # Returns an array of all files that match against any of *patterns*. # + # ``` + # Dir.glob "path/to/folder/*.txt" # Returns all files in the target folder that end in ".txt". + # Dir.glob "path/to/folder/**/*" # Returns all files in the target folder and its subfolders. + # ``` # The pattern syntax is similar to shell filename globbing, see `File.match?` for details. # # NOTE: Path separator in patterns needs to be always `/`. The returned file names use system-specific path separators. From 0620da4c5b9b609073e2300c9670124ec6657b6e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 10 Nov 2024 20:05:40 +0800 Subject: [PATCH 1464/1551] Use Win32 heap functions with `-Dgc_none` (#15173) These functions can (re)allocate and zero memory in a single call, including `HeapReAlloc` when the new memory size is larger than the original. --- src/gc/none.cr | 39 ++++++++++++++++++---- src/lib_c/x86_64-windows-msvc/c/heapapi.cr | 2 ++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/gc/none.cr b/src/gc/none.cr index ce84027e6e69..651027266e5b 100644 --- a/src/gc/none.cr +++ b/src/gc/none.cr @@ -1,5 +1,6 @@ {% if flag?(:win32) %} require "c/process" + require "c/heapapi" {% end %} require "crystal/tracing" @@ -11,21 +12,42 @@ module GC # :nodoc: def self.malloc(size : LibC::SizeT) : Void* Crystal.trace :gc, "malloc", size: size - # libc malloc is not guaranteed to return cleared memory, so we need to - # explicitly clear it. Ref: https://github.com/crystal-lang/crystal/issues/14678 - LibC.malloc(size).tap(&.clear) + + {% if flag?(:win32) %} + LibC.HeapAlloc(LibC.GetProcessHeap, LibC::HEAP_ZERO_MEMORY, size) + {% else %} + # libc malloc is not guaranteed to return cleared memory, so we need to + # explicitly clear it. Ref: https://github.com/crystal-lang/crystal/issues/14678 + LibC.malloc(size).tap(&.clear) + {% end %} end # :nodoc: def self.malloc_atomic(size : LibC::SizeT) : Void* Crystal.trace :gc, "malloc", size: size, atomic: 1 - LibC.malloc(size) + + {% if flag?(:win32) %} + LibC.HeapAlloc(LibC.GetProcessHeap, 0, size) + {% else %} + LibC.malloc(size) + {% end %} end # :nodoc: def self.realloc(pointer : Void*, size : LibC::SizeT) : Void* Crystal.trace :gc, "realloc", size: size - LibC.realloc(pointer, size) + + {% if flag?(:win32) %} + # realloc with a null pointer should behave like plain malloc, but Win32 + # doesn't do that + if pointer + LibC.HeapReAlloc(LibC.GetProcessHeap, LibC::HEAP_ZERO_MEMORY, pointer, size) + else + LibC.HeapAlloc(LibC.GetProcessHeap, LibC::HEAP_ZERO_MEMORY, size) + end + {% else %} + LibC.realloc(pointer, size) + {% end %} end def self.collect @@ -39,7 +61,12 @@ module GC def self.free(pointer : Void*) : Nil Crystal.trace :gc, "free" - LibC.free(pointer) + + {% if flag?(:win32) %} + LibC.HeapFree(LibC.GetProcessHeap, 0, pointer) + {% else %} + LibC.free(pointer) + {% end %} end def self.is_heap_ptr(pointer : Void*) : Bool diff --git a/src/lib_c/x86_64-windows-msvc/c/heapapi.cr b/src/lib_c/x86_64-windows-msvc/c/heapapi.cr index 1738cf774cac..8db5152585bc 100644 --- a/src/lib_c/x86_64-windows-msvc/c/heapapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/heapapi.cr @@ -1,6 +1,8 @@ require "c/winnt" lib LibC + HEAP_ZERO_MEMORY = 0x00000008 + fun GetProcessHeap : HANDLE fun HeapAlloc(hHeap : HANDLE, dwFlags : DWORD, dwBytes : SizeT) : Void* fun HeapReAlloc(hHeap : HANDLE, dwFlags : DWORD, lpMem : Void*, dwBytes : SizeT) : Void* From caf57c2656e6b18fce43e132848f418f3c672a10 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 10 Nov 2024 20:05:54 +0800 Subject: [PATCH 1465/1551] Optimize `String#rchop?()` (#15175) This is similar to #15153. * Uses `Char::Reader` to read backwards from the end of the string. This does not require decoding the whole string, unlike calculating `size - 1`. * If the current string does not end with an ASCII character and its size is unknown, `#single_byte_optimizable?` traverses the entire string. This is no longer unnecessary because the above handles everything already. * If the current string's size is known, the new string's size can be derived from it. --- src/string.cr | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/string.cr b/src/string.cr index 09272c80eb45..273472d34517 100644 --- a/src/string.cr +++ b/src/string.cr @@ -1798,11 +1798,7 @@ class String def rchop? : String? return if empty? - if to_unsafe[bytesize - 1] < 0x80 || single_byte_optimizable? - return unsafe_byte_slice_string(0, bytesize - 1) - end - - self[0, size - 1] + unsafe_byte_slice_string(0, Char::Reader.new(at_end: self).pos, @length > 0 ? @length - 1 : 0) end # Returns a new `String` with *suffix* removed from the end of the string if possible, else returns `nil`. From 489cdf1f902db59603bed39b8e1bf9af78d34093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 13 Nov 2024 15:57:22 +0100 Subject: [PATCH 1466/1551] Add compiler versions constraints for interpreter signal handler (#15178) This is a follow-up to #14766. The new primitives are only available in master This patch ensures forward compatibility for the latest stable compiler (Crystal 1.14) and below. --- spec/std/signal_spec.cr | 2 ++ src/crystal/interpreter.cr | 14 ++++++++------ src/crystal/system/unix/signal.cr | 8 ++++---- src/kernel.cr | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr index b90c45c389e7..8b264d6aa49a 100644 --- a/spec/std/signal_spec.cr +++ b/spec/std/signal_spec.cr @@ -3,6 +3,8 @@ require "./spec_helper" require "signal" +{% skip_file if flag?(:interpreted) && !Crystal::Interpreter.has_method?(:signal) %} + describe "Signal" do typeof(Signal::ABRT.reset) typeof(Signal::ABRT.ignore) diff --git a/src/crystal/interpreter.cr b/src/crystal/interpreter.cr index 45a2bce699d1..bad67420f5f3 100644 --- a/src/crystal/interpreter.cr +++ b/src/crystal/interpreter.cr @@ -25,12 +25,14 @@ module Crystal def self.fiber_resumable(context) : LibC::Long end - @[Primitive(:interpreter_signal_descriptor)] - def self.signal_descriptor(fd : Int32) : Nil - end + {% if compare_versions(Crystal::VERSION, "1.15.0-dev") >= 0 %} + @[Primitive(:interpreter_signal_descriptor)] + def self.signal_descriptor(fd : Int32) : Nil + end - @[Primitive(:interpreter_signal)] - def self.signal(signum : Int32, handler : Int32) : Nil - end + @[Primitive(:interpreter_signal)] + def self.signal(signum : Int32, handler : Int32) : Nil + end + {% end %} end end diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr index 99fc30839a6d..ab094d2f3094 100644 --- a/src/crystal/system/unix/signal.cr +++ b/src/crystal/system/unix/signal.cr @@ -22,7 +22,7 @@ module Crystal::System::Signal @@mutex.synchronize do unless @@handlers[signal]? @@sigset << signal - {% if flag?(:interpreted) %} + {% if flag?(:interpreted) && Crystal::Interpreter.has_method?(:signal) %} Crystal::Interpreter.signal(signal.value, 2) {% else %} action = LibC::Sigaction.new @@ -66,7 +66,7 @@ module Crystal::System::Signal else @@mutex.synchronize do @@handlers.delete(signal) - {% if flag?(:interpreted) %} + {% if flag?(:interpreted) && Crystal::Interpreter.has_method?(:signal) %} h = case handler when LibC::SIG_DFL then 0 when LibC::SIG_IGN then 1 @@ -131,7 +131,7 @@ module Crystal::System::Signal ::Signal.each do |signal| next unless @@sigset.includes?(signal) - {% if flag?(:interpreted) %} + {% if flag?(:interpreted) && Crystal::Interpreter.has_method?(:signal) %} Crystal::Interpreter.signal(signal.value, 0) {% else %} LibC.signal(signal, LibC::SIG_DFL) @@ -203,7 +203,7 @@ module Crystal::System::Signal @@sigset.clear start_loop - {% if flag?(:interpreted) %} + {% if flag?(:interpreted) && Interpreter.has_method?(:signal_descriptor) %} # replace the interpreter's writer pipe with the interpreted, so signals # will be received by the interpreter, but handled by the interpreted # signal loop diff --git a/src/kernel.cr b/src/kernel.cr index f32f98e67c54..1203d1c66a7e 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -630,6 +630,6 @@ end end {% end %} -{% if flag?(:interpreted) && flag?(:unix) %} +{% if flag?(:interpreted) && flag?(:unix) && Crystal::Interpreter.has_method?(:signal_descriptor) %} Crystal::System::Signal.setup_default_handlers {% end %} From 2725705473bdbc5a489de33e64badff0d4b42378 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Wed, 13 Nov 2024 09:58:36 -0500 Subject: [PATCH 1467/1551] Update specs to run with IPv6 support disabled (#15046) --- spec/std/http/server/server_spec.cr | 2 +- spec/std/openssl/ssl/server_spec.cr | 8 ++++---- spec/std/socket/spec_helper.cr | 4 ++-- spec/std/socket/tcp_server_spec.cr | 2 +- spec/std/socket/tcp_socket_spec.cr | 8 +++++--- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index 3c634d755abf..ce8e76f9a11e 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -18,7 +18,7 @@ private def unix_request(path) end private def unused_port - TCPServer.open(0) do |server| + TCPServer.open(Socket::IPAddress::UNSPECIFIED, 0) do |server| server.local_address.port end end diff --git a/spec/std/openssl/ssl/server_spec.cr b/spec/std/openssl/ssl/server_spec.cr index 8618ed780a50..d2cc41efe88b 100644 --- a/spec/std/openssl/ssl/server_spec.cr +++ b/spec/std/openssl/ssl/server_spec.cr @@ -11,7 +11,7 @@ require "../../../support/ssl" describe OpenSSL::SSL::Server do it "sync_close" do - TCPServer.open(0) do |tcp_server| + TCPServer.open(Socket::IPAddress::UNSPECIFIED, 0) do |tcp_server| context = OpenSSL::SSL::Context::Server.new ssl_server = OpenSSL::SSL::Server.new(tcp_server, context) @@ -22,7 +22,7 @@ describe OpenSSL::SSL::Server do end it "don't sync_close" do - TCPServer.open(0) do |tcp_server| + TCPServer.open(Socket::IPAddress::UNSPECIFIED, 0) do |tcp_server| context = OpenSSL::SSL::Context::Server.new ssl_server = OpenSSL::SSL::Server.new(tcp_server, context, sync_close: false) ssl_server.context.should eq context @@ -35,7 +35,7 @@ describe OpenSSL::SSL::Server do it ".new" do context = OpenSSL::SSL::Context::Server.new - TCPServer.open(0) do |tcp_server| + TCPServer.open(Socket::IPAddress::UNSPECIFIED, 0) do |tcp_server| ssl_server = OpenSSL::SSL::Server.new tcp_server, context, sync_close: false ssl_server.context.should eq context @@ -46,7 +46,7 @@ describe OpenSSL::SSL::Server do it ".open" do context = OpenSSL::SSL::Context::Server.new - TCPServer.open(0) do |tcp_server| + TCPServer.open(Socket::IPAddress::UNSPECIFIED, 0) do |tcp_server| ssl_server = nil OpenSSL::SSL::Server.open tcp_server, context do |server| server.wrapped.should eq tcp_server diff --git a/spec/std/socket/spec_helper.cr b/spec/std/socket/spec_helper.cr index 486e4a142ee7..276e2f4195f2 100644 --- a/spec/std/socket/spec_helper.cr +++ b/spec/std/socket/spec_helper.cr @@ -5,7 +5,7 @@ module SocketSpecHelper class_getter?(supports_ipv6 : Bool) do TCPServer.open("::1", 0) { return true } false - rescue Socket::BindError + rescue Socket::Error false end end @@ -33,7 +33,7 @@ def each_ip_family(&block : Socket::Family, String, String ->) end def unused_local_port - TCPServer.open("::", 0) do |server| + TCPServer.open(Socket::IPAddress::UNSPECIFIED, 0) do |server| server.local_address.port end end diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr index a7d85b8edeff..451cbbb33d61 100644 --- a/spec/std/socket/tcp_server_spec.cr +++ b/spec/std/socket/tcp_server_spec.cr @@ -120,7 +120,7 @@ describe TCPServer, tags: "network" do it "binds to all interfaces" do port = unused_local_port - TCPServer.open(port) do |server| + TCPServer.open(Socket::IPAddress::UNSPECIFIED, port) do |server| server.local_address.port.should eq port end end diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index b7a22882ec0f..b44b3a9729f6 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -112,6 +112,8 @@ describe TCPSocket, tags: "network" do end it "fails to connect IPv6 to IPv4 server" do + pending! "IPv6 is unavailable" unless SocketSpecHelper.supports_ipv6? + port = unused_local_port TCPServer.open("0.0.0.0", port) do |server| @@ -133,7 +135,7 @@ describe TCPSocket, tags: "network" do it "sync from server" do port = unused_local_port - TCPServer.open("::", port) do |server| + TCPServer.open(Socket::IPAddress::UNSPECIFIED, port) do |server| TCPSocket.open("localhost", port) do |client| sock = server.accept sock.sync?.should eq(server.sync?) @@ -152,7 +154,7 @@ describe TCPSocket, tags: "network" do it "settings" do port = unused_local_port - TCPServer.open("::", port) do |server| + TCPServer.open(Socket::IPAddress::UNSPECIFIED, port) do |server| TCPSocket.open("localhost", port) do |client| # test protocol specific socket options (client.tcp_nodelay = true).should be_true @@ -202,7 +204,7 @@ describe TCPSocket, tags: "network" do channel = Channel(Exception?).new spawn do - TCPServer.open("::", port) do |server| + TCPServer.open(Socket::IPAddress::UNSPECIFIED, port) do |server| channel.send nil sock = server.accept sock.read_timeout = 3.second From 9ca69addc7caad2256985200600a3241b640ae2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 14 Nov 2024 10:58:53 +0100 Subject: [PATCH 1468/1551] Make `Fiber.timeout` and `.cancel_timeout` nodoc (#15184) --- src/fiber.cr | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fiber.cr b/src/fiber.cr index 1086ebdd3669..2b596a16017c 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -246,12 +246,15 @@ class Fiber @timeout_event.try &.delete end + # :nodoc: + # # The current fiber will resume after a period of time. # The timeout can be cancelled with `cancel_timeout` def self.timeout(timeout : Time::Span, select_action : Channel::TimeoutAction) : Nil Fiber.current.timeout(timeout, select_action) end + # :nodoc: def self.cancel_timeout : Nil Fiber.current.cancel_timeout end From 5e0bbaaa29018b05dfa9c852bfc5805ea74982f1 Mon Sep 17 00:00:00 2001 From: Stephanie Wilde-Hobbs Date: Thu, 14 Nov 2024 14:00:10 +0100 Subject: [PATCH 1469/1551] Emit position dependent code for embedded targets (#15174) --- src/compiler/crystal/codegen/target.cr | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/compiler/crystal/codegen/target.cr b/src/compiler/crystal/codegen/target.cr index 223d64fe859b..cf11ed96fef4 100644 --- a/src/compiler/crystal/codegen/target.cr +++ b/src/compiler/crystal/codegen/target.cr @@ -192,6 +192,10 @@ class Crystal::Codegen::Target @architecture == "avr" end + def embedded? + environment_parts.any? { |part| part == "eabi" || part == "eabihf" } + end + def to_target_machine(cpu = "", features = "", optimization_mode = Compiler::OptimizationMode::O0, code_model = LLVM::CodeModel::Default) : LLVM::TargetMachine case @architecture @@ -228,8 +232,14 @@ class Crystal::Codegen::Target in .o0? then LLVM::CodeGenOptLevel::None end + if embedded? + reloc = LLVM::RelocMode::Static + else + reloc = LLVM::RelocMode::PIC + end + target = LLVM::Target.from_triple(self.to_s) - machine = target.create_target_machine(self.to_s, cpu: cpu, features: features, opt_level: opt_level, code_model: code_model).not_nil! + machine = target.create_target_machine(self.to_s, cpu: cpu, features: features, opt_level: opt_level, reloc: reloc, code_model: code_model).not_nil! # FIXME: We need to disable global isel until https://reviews.llvm.org/D80898 is released, # or we fixed generating values for 0 sized types. # When removing this, also remove it from the ABI specs and jit compiler. From f4606b74fb4d3d59958c00fef826e14d35d8fffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=C3=B6rjesson?= Date: Fri, 15 Nov 2024 11:10:48 +0100 Subject: [PATCH 1470/1551] Add optional `name` parameter forward to `WaitGroup#spawn` (#15189) --- src/wait_group.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wait_group.cr b/src/wait_group.cr index c1ebe67bf508..cf1ca8900e8f 100644 --- a/src/wait_group.cr +++ b/src/wait_group.cr @@ -73,9 +73,9 @@ class WaitGroup # wg.spawn { do_something } # wg.wait # ``` - def spawn(&block) : Fiber + def spawn(*, name : String? = nil, &block) : Fiber add - ::spawn do + ::spawn(name: name) do block.call ensure done From 32be3b6c55d66a547c975849c3d986c8a349f0bb Mon Sep 17 00:00:00 2001 From: Barney <86712892+BigBoyBarney@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:11:31 +0100 Subject: [PATCH 1471/1551] Update example code for `::spawn` with `WaitGroup` (#15191) Co-authored-by: Sijawusz Pur Rahnama --- src/concurrent.cr | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/concurrent.cr b/src/concurrent.cr index 0f8805857720..07ae945a84f6 100644 --- a/src/concurrent.cr +++ b/src/concurrent.cr @@ -33,20 +33,23 @@ end # Spawns a new fiber. # -# The newly created fiber doesn't run as soon as spawned. +# NOTE: The newly created fiber doesn't run as soon as spawned. # # Example: # ``` # # Write "1" every 1 second and "2" every 2 seconds for 6 seconds. # -# ch = Channel(Nil).new +# require "wait_group" +# +# wg = WaitGroup.new 2 # # spawn do # 6.times do # sleep 1.second # puts 1 # end -# ch.send(nil) +# ensure +# wg.done # end # # spawn do @@ -54,10 +57,11 @@ end # sleep 2.seconds # puts 2 # end -# ch.send(nil) +# ensure +# wg.done # end # -# 2.times { ch.receive } +# wg.wait # ``` def spawn(*, name : String? = nil, same_thread = false, &block) fiber = Fiber.new(name, &block) From 6cec6a904c3349fcb4ff10bc582f3f010aa1d166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 15 Nov 2024 11:12:31 +0100 Subject: [PATCH 1472/1551] Add docs for lib bindings with supported library versions (#14900) Co-authored-by: Quinton Miller --- src/big/lib_gmp.cr | 6 ++++++ src/compiler/crystal/ffi/lib_ffi.cr | 5 +++++ src/crystal/lib_iconv.cr | 5 +++++ src/crystal/system/unix/lib_event2.cr | 5 +++++ src/gc/boehm.cr | 5 +++++ src/lib_c.cr | 11 +++++++++++ src/lib_z/lib_z.cr | 5 +++++ src/llvm/lib_llvm.cr | 5 +++++ src/openssl/lib_crypto.cr | 6 ++++++ src/openssl/lib_ssl.cr | 6 ++++++ src/regex/lib_pcre.cr | 5 +++++ src/regex/lib_pcre2.cr | 5 +++++ src/xml/libxml2.cr | 5 +++++ src/yaml/lib_yaml.cr | 5 +++++ 14 files changed, 79 insertions(+) diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index 7368cb0e9fb6..c0e8ef8b2e37 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -1,3 +1,9 @@ +# Supported library versions: +# +# * libgmp +# * libmpir +# +# See https://crystal-lang.org/reference/man/required_libraries.html#big-numbers {% if flag?(:win32) && !flag?(:gnu) %} @[Link("mpir")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} diff --git a/src/compiler/crystal/ffi/lib_ffi.cr b/src/compiler/crystal/ffi/lib_ffi.cr index 2d08cf4e18dd..22929279c09e 100644 --- a/src/compiler/crystal/ffi/lib_ffi.cr +++ b/src/compiler/crystal/ffi/lib_ffi.cr @@ -1,3 +1,8 @@ +# Supported library versions: +# +# * libffi +# +# See https://crystal-lang.org/reference/man/required_libraries.html#compiler-dependencies module Crystal @[Link("ffi")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} diff --git a/src/crystal/lib_iconv.cr b/src/crystal/lib_iconv.cr index 07100ff9c1dc..dafcb7a75d53 100644 --- a/src/crystal/lib_iconv.cr +++ b/src/crystal/lib_iconv.cr @@ -4,6 +4,11 @@ require "c/stddef" {% raise "The `without_iconv` flag is preventing you to use the LibIconv module" %} {% end %} +# Supported library versions: +# +# * libiconv-gnu +# +# See https://crystal-lang.org/reference/man/required_libraries.html#internationalization-conversion @[Link("iconv")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} @[Link(dll: "iconv-2.dll")] diff --git a/src/crystal/system/unix/lib_event2.cr b/src/crystal/system/unix/lib_event2.cr index 2cd3e4635194..e8e44b0f7473 100644 --- a/src/crystal/system/unix/lib_event2.cr +++ b/src/crystal/system/unix/lib_event2.cr @@ -7,6 +7,11 @@ require "c/netdb" @[Link("rt")] {% end %} +# Supported library versions: +# +# * libevent2 +# +# See https://crystal-lang.org/reference/man/required_libraries.html#other-runtime-libraries {% if flag?(:openbsd) %} @[Link("event_core")] @[Link("event_extra")] diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 33d6466d792b..6037abe830e2 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -32,6 +32,11 @@ require "crystal/tracing" @[Link("gc", pkg_config: "bdw-gc")] {% end %} +# Supported library versions: +# +# * libgc (8.2.0+; earlier versions require a patch for MT support) +# +# See https://crystal-lang.org/reference/man/required_libraries.html#other-runtime-libraries {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} @[Link(dll: "gc.dll")] {% end %} diff --git a/src/lib_c.cr b/src/lib_c.cr index 7bc94a34f53e..c52ea52bfcbc 100644 --- a/src/lib_c.cr +++ b/src/lib_c.cr @@ -1,3 +1,14 @@ +# Supported library versions: +# +# * glibc (2.26+) +# * musl libc (1.2+) +# * system libraries of several BSDs +# * macOS system library (11+) +# * MSVCRT +# * WASI +# * bionic libc +# +# See https://crystal-lang.org/reference/man/required_libraries.html#system-library {% if flag?(:msvc) %} @[Link({{ flag?(:static) ? "libucrt" : "ucrt" }})] {% end %} diff --git a/src/lib_z/lib_z.cr b/src/lib_z/lib_z.cr index 1c88cb67bba8..47de2981e2f6 100644 --- a/src/lib_z/lib_z.cr +++ b/src/lib_z/lib_z.cr @@ -1,3 +1,8 @@ +# Supported library versions: +# +# * zlib +# +# See https://crystal-lang.org/reference/man/required_libraries.html#other-stdlib-libraries @[Link("z")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} @[Link(dll: "zlib1.dll")] diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index 8b6856631b55..1349d5bf6a91 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -40,6 +40,11 @@ end {% end %} +# Supported library versions: +# +# * LLVM (8-19; aarch64 requires 13+) +# +# See https://crystal-lang.org/reference/man/required_libraries.html#other-stdlib-libraries {% begin %} lib LibLLVM IS_180 = {{LibLLVM::VERSION.starts_with?("18.0")}} diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr index fecc69ad44fc..bc7130351059 100644 --- a/src/openssl/lib_crypto.cr +++ b/src/openssl/lib_crypto.cr @@ -1,3 +1,9 @@ +# Supported library versions: +# +# * openssl (1.1.0–3.3+) +# * libressl (2.0–3.8+) +# +# See https://crystal-lang.org/reference/man/required_libraries.html#tls {% begin %} lib LibCrypto {% if flag?(:msvc) %} diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr index 4e7e2def549c..5a7c7ec76cd0 100644 --- a/src/openssl/lib_ssl.cr +++ b/src/openssl/lib_ssl.cr @@ -4,6 +4,12 @@ require "./lib_crypto" {% raise "The `without_openssl` flag is preventing you to use the LibSSL module" %} {% end %} +# Supported library versions: +# +# * openssl (1.1.0–3.3+) +# * libressl (2.0–4.0+) +# +# See https://crystal-lang.org/reference/man/required_libraries.html#tls {% begin %} lib LibSSL {% if flag?(:msvc) %} diff --git a/src/regex/lib_pcre.cr b/src/regex/lib_pcre.cr index da3ac3beb764..8502e5531d3e 100644 --- a/src/regex/lib_pcre.cr +++ b/src/regex/lib_pcre.cr @@ -1,3 +1,8 @@ +# Supported library versions: +# +# * libpcre +# +# See https://crystal-lang.org/reference/man/required_libraries.html#regular-expression-engine @[Link("pcre", pkg_config: "libpcre")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} @[Link(dll: "pcre.dll")] diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index 651a1c95bef2..6f45a4465219 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -1,3 +1,8 @@ +# Supported library versions: +# +# * libpcre2 (recommended: 10.36+) +# +# See https://crystal-lang.org/reference/man/required_libraries.html#regular-expression-engine @[Link("pcre2-8", pkg_config: "libpcre2-8")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} @[Link(dll: "pcre2-8.dll")] diff --git a/src/xml/libxml2.cr b/src/xml/libxml2.cr index fbfb0702faef..05b255ba23dc 100644 --- a/src/xml/libxml2.cr +++ b/src/xml/libxml2.cr @@ -4,6 +4,11 @@ require "./parser_options" require "./html_parser_options" require "./save_options" +# Supported library versions: +# +# * libxml2 +# +# See https://crystal-lang.org/reference/man/required_libraries.html#other-stdlib-libraries @[Link("xml2", pkg_config: "libxml-2.0")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} @[Link(dll: "libxml2.dll")] diff --git a/src/yaml/lib_yaml.cr b/src/yaml/lib_yaml.cr index 0b4248afc793..d1527db63be2 100644 --- a/src/yaml/lib_yaml.cr +++ b/src/yaml/lib_yaml.cr @@ -1,5 +1,10 @@ require "./enums" +# Supported library versions: +# +# * libyaml +# +# See https://crystal-lang.org/reference/man/required_libraries.html#other-stdlib-libraries @[Link("yaml", pkg_config: "yaml-0.1")] {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %} @[Link(dll: "yaml.dll")] From 87ec603f2f6d1ec16cdf8c8d3a0253643b2f4c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 15 Nov 2024 23:39:16 +0100 Subject: [PATCH 1473/1551] Enable bindings for functions in LibreSSL (#15177) A lot of symbols that are available in LibreSSL are missing in the bindings when macro branches only checked for OpenSSL versions. I added the respective LibreSSL versions. --- .github/workflows/linux.yml | 1 + spec/std/openssl/pkcs5_spec.cr | 2 +- spec/std/openssl/ssl/context_spec.cr | 14 +++++++------- src/openssl/lib_crypto.cr | 13 +++++++------ src/openssl/lib_ssl.cr | 14 ++++++++------ 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 79c3f143d303..eb5874a2687a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -20,6 +20,7 @@ jobs: DOCKER_TEST_PREFIX: crystallang/crystal:${{ matrix.crystal_bootstrap_version }} runs-on: ubuntu-latest strategy: + fail-fast: false matrix: crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2, 1.13.3, 1.14.0] flags: [""] diff --git a/spec/std/openssl/pkcs5_spec.cr b/spec/std/openssl/pkcs5_spec.cr index 70fd351f0bbc..a8261d42c1f6 100644 --- a/spec/std/openssl/pkcs5_spec.cr +++ b/spec/std/openssl/pkcs5_spec.cr @@ -13,7 +13,7 @@ describe OpenSSL::PKCS5 do end end - {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.0") >= 0 %} + {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.0") >= 0 || LibSSL::LIBRESSL_VERSION != "0.0.0" %} {% if compare_versions(LibSSL::OPENSSL_VERSION, "3.0.0") < 0 %} [ {OpenSSL::Algorithm::MD4, 1, 16, "1857f69412150bca4542581d0f9e7fd1"}, diff --git a/spec/std/openssl/ssl/context_spec.cr b/spec/std/openssl/ssl/context_spec.cr index 74c79411c82a..c37055dcedec 100644 --- a/spec/std/openssl/ssl/context_spec.cr +++ b/spec/std/openssl/ssl/context_spec.cr @@ -32,7 +32,7 @@ describe OpenSSL::SSL::Context do (context.options & OpenSSL::SSL::Options::NO_SESSION_RESUMPTION_ON_RENEGOTIATION).should eq(OpenSSL::SSL::Options::NO_SESSION_RESUMPTION_ON_RENEGOTIATION) (context.options & OpenSSL::SSL::Options::SINGLE_ECDH_USE).should eq(OpenSSL::SSL::Options::SINGLE_ECDH_USE) (context.options & OpenSSL::SSL::Options::SINGLE_DH_USE).should eq(OpenSSL::SSL::Options::SINGLE_DH_USE) - {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 %} + {% if LibSSL::Options.has_constant?(:NO_RENEGOTIATION) %} (context.options & OpenSSL::SSL::Options::NO_RENEGOTIATION).should eq(OpenSSL::SSL::Options::NO_RENEGOTIATION) {% end %} @@ -128,12 +128,12 @@ describe OpenSSL::SSL::Context do context = OpenSSL::SSL::Context::Client.new level = context.security_level context.security_level = level + 1 - # SSL_CTX_get_security_level is not supported by libressl - {% if LibSSL::OPENSSL_VERSION != "0.0.0" %} + + if LibSSL.responds_to?(:ssl_ctx_set_security_level) context.security_level.should eq(level + 1) - {% else %} + else context.security_level.should eq 0 - {% end %} + end end it "adds temporary ecdh curve (P-256)" do @@ -194,12 +194,12 @@ describe OpenSSL::SSL::Context do context.verify_mode.should eq(OpenSSL::SSL::VerifyMode::PEER) end - {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.2") >= 0 %} + if LibSSL.responds_to?(:ssl_ctx_set_alpn_protos) it "alpn_protocol=" do context = OpenSSL::SSL::Context::Client.insecure context.alpn_protocol = "h2" end - {% end %} + end it "calls #finalize on insecure client context" do assert_finalizes("insecure_client_ctx") { OpenSSL::SSL::Context::Client.insecure } diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr index bc7130351059..aa7eef54c5ab 100644 --- a/src/openssl/lib_crypto.cr +++ b/src/openssl/lib_crypto.cr @@ -109,7 +109,7 @@ lib LibCrypto alias BioMethodDestroy = Bio* -> Int alias BioMethodCallbackCtrl = (Bio*, Int, Void*) -> Long - {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %} + {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LibCrypto::LIBRESSL_VERSION, "2.7.0") >= 0 %} type BioMethod = Void {% else %} struct BioMethod @@ -129,7 +129,7 @@ lib LibCrypto fun BIO_new(BioMethod*) : Bio* fun BIO_free(Bio*) : Int - {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %} + {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LibCrypto::LIBRESSL_VERSION, "2.7.0") >= 0 %} fun BIO_set_data(Bio*, Void*) fun BIO_get_data(Bio*) : Void* fun BIO_set_init(Bio*, Int) @@ -145,6 +145,7 @@ lib LibCrypto fun BIO_meth_set_destroy(BioMethod*, BioMethodDestroy) fun BIO_meth_set_callback_ctrl(BioMethod*, BioMethodCallbackCtrl) {% end %} + # LibreSSL does not define these symbols {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.1") >= 0 %} fun BIO_meth_set_read_ex(BioMethod*, BioMethodRead) fun BIO_meth_set_write_ex(BioMethod*, BioMethodWrite) @@ -229,7 +230,7 @@ lib LibCrypto fun evp_digestfinal_ex = EVP_DigestFinal_ex(ctx : EVP_MD_CTX, md : UInt8*, size : UInt32*) : Int32 - {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %} + {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LibCrypto::LIBRESSL_VERSION, "2.7.0") >= 0 %} fun evp_md_ctx_new = EVP_MD_CTX_new : EVP_MD_CTX fun evp_md_ctx_free = EVP_MD_CTX_free(ctx : EVP_MD_CTX) {% else %} @@ -306,7 +307,7 @@ lib LibCrypto fun md5 = MD5(data : UInt8*, length : LibC::SizeT, md : UInt8*) : UInt8* fun pkcs5_pbkdf2_hmac_sha1 = PKCS5_PBKDF2_HMAC_SHA1(pass : LibC::Char*, passlen : LibC::Int, salt : UInt8*, saltlen : LibC::Int, iter : LibC::Int, keylen : LibC::Int, out : UInt8*) : LibC::Int - {% if compare_versions(OPENSSL_VERSION, "1.0.0") >= 0 %} + {% if compare_versions(OPENSSL_VERSION, "1.0.0") >= 0 || LIBRESSL_VERSION != "0.0.0" %} fun pkcs5_pbkdf2_hmac = PKCS5_PBKDF2_HMAC(pass : LibC::Char*, passlen : LibC::Int, salt : UInt8*, saltlen : LibC::Int, iter : LibC::Int, digest : EVP_MD, keylen : LibC::Int, out : UInt8*) : LibC::Int {% end %} @@ -380,12 +381,12 @@ lib LibCrypto fun x509_store_add_cert = X509_STORE_add_cert(ctx : X509_STORE, x : X509) : Int - {% unless compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %} + {% unless compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LibCrypto::LIBRESSL_VERSION, "3.0.0") >= 0 %} fun err_load_crypto_strings = ERR_load_crypto_strings fun openssl_add_all_algorithms = OPENSSL_add_all_algorithms_noconf {% end %} - {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 %} + {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 || LIBRESSL_VERSION != "0.0.0" %} type X509VerifyParam = Void* @[Flags] diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr index 5a7c7ec76cd0..449f35dd0f72 100644 --- a/src/openssl/lib_ssl.cr +++ b/src/openssl/lib_ssl.cr @@ -145,7 +145,7 @@ lib LibSSL NETSCAPE_DEMO_CIPHER_CHANGE_BUG = 0x40000000 CRYPTOPRO_TLSEXT_BUG = 0x80000000 - {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %} + {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LIBRESSL_VERSION, "2.3.0") >= 0 %} MICROSOFT_SESS_ID_BUG = 0x00000000 NETSCAPE_CHALLENGE_BUG = 0x00000000 NETSCAPE_REUSE_CIPHER_CHANGE_BUG = 0x00000000 @@ -243,6 +243,7 @@ lib LibSSL fun ssl_get_peer_certificate = SSL_get_peer_certificate(handle : SSL) : LibCrypto::X509 {% end %} + # In LibreSSL these functions are implemented as macros {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %} fun ssl_ctx_get_options = SSL_CTX_get_options(ctx : SSLContext) : ULong fun ssl_ctx_set_options = SSL_CTX_set_options(ctx : SSLContext, larg : ULong) : ULong @@ -257,12 +258,13 @@ lib LibSSL fun ssl_ctx_set_cert_verify_callback = SSL_CTX_set_cert_verify_callback(ctx : SSLContext, callback : CertVerifyCallback, arg : Void*) # control TLS 1.3 session ticket generation + # LibreSSL does not seem to implement these functions {% if compare_versions(OPENSSL_VERSION, "1.1.1") >= 0 %} fun ssl_ctx_set_num_tickets = SSL_CTX_set_num_tickets(ctx : SSLContext, larg : LibC::SizeT) : Int fun ssl_set_num_tickets = SSL_set_num_tickets(ctx : SSL, larg : LibC::SizeT) : Int {% end %} - {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %} + {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LibSSL::LIBRESSL_VERSION, "2.3.0") >= 0 %} fun tls_method = TLS_method : SSLMethod {% else %} fun ssl_library_init = SSL_library_init @@ -270,7 +272,7 @@ lib LibSSL fun sslv23_method = SSLv23_method : SSLMethod {% end %} - {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 || compare_versions(LIBRESSL_VERSION, "2.5.0") >= 0 %} + {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 || compare_versions(LIBRESSL_VERSION, "2.1.0") >= 0 %} alias ALPNCallback = (SSL, Char**, Char*, Char*, Int, Void*) -> Int fun ssl_get0_alpn_selected = SSL_get0_alpn_selected(handle : SSL, data : Char**, len : LibC::UInt*) : Void @@ -278,7 +280,7 @@ lib LibSSL fun ssl_ctx_set_alpn_protos = SSL_CTX_set_alpn_protos(ctx : SSLContext, protos : Char*, protos_len : Int) : Int {% end %} - {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 %} + {% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 || compare_versions(LIBRESSL_VERSION, "2.7.0") >= 0 %} alias X509VerifyParam = LibCrypto::X509VerifyParam fun dtls_method = DTLS_method : SSLMethod @@ -288,7 +290,7 @@ lib LibSSL fun ssl_ctx_set1_param = SSL_CTX_set1_param(ctx : SSLContext, param : X509VerifyParam) : Int {% end %} - {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %} + {% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 || compare_versions(LIBRESSL_VERSION, "3.6.0") >= 0 %} fun ssl_ctx_set_security_level = SSL_CTX_set_security_level(ctx : SSLContext, level : Int) : Void fun ssl_ctx_get_security_level = SSL_CTX_get_security_level(ctx : SSLContext) : Int {% end %} @@ -299,7 +301,7 @@ lib LibSSL {% end %} end -{% unless compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 %} +{% if LibSSL.has_method?(:ssl_library_init) %} LibSSL.ssl_library_init LibSSL.ssl_load_error_strings LibCrypto.openssl_add_all_algorithms From 44f6233fc7ec06d5c213013c8cffd48b4b46b640 Mon Sep 17 00:00:00 2001 From: Anton Maminov Date: Sat, 16 Nov 2024 13:17:38 +0200 Subject: [PATCH 1474/1551] Add `Socket::Address.from` without `addrlen` (#15060) The `addlen` parameter is not necessary when creating an address from a `LibC::Sockaddr` pointer, as it can be obtained directly within the method. --- spec/std/socket/address_spec.cr | 3 +++ src/socket/address.cr | 47 +++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/spec/std/socket/address_spec.cr b/spec/std/socket/address_spec.cr index d2e4768db987..08508940bc7d 100644 --- a/spec/std/socket/address_spec.cr +++ b/spec/std/socket/address_spec.cr @@ -51,6 +51,7 @@ describe Socket::IPAddress do addr2.port.should eq(addr1.port) typeof(addr2.address).should eq(String) addr2.address.should eq(addr1.address) + addr2.should eq(Socket::IPAddress.from(addr1_c)) end it "transforms an IPv6 address into a C struct and back" do @@ -64,6 +65,7 @@ describe Socket::IPAddress do addr2.port.should eq(addr1.port) typeof(addr2.address).should eq(String) addr2.address.should eq(addr1.address) + addr2.should eq(Socket::IPAddress.from(addr1_c)) end it "won't resolve domains" do @@ -431,6 +433,7 @@ end addr2.family.should eq(addr1.family) addr2.path.should eq(addr1.path) addr2.to_s.should eq(path) + addr2 = Socket::UNIXAddress.from(addr1.to_unsafe) end it "raises when path is too long" do diff --git a/src/socket/address.cr b/src/socket/address.cr index 20fca43544e6..bac36088152f 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -21,6 +21,26 @@ class Socket end end + # :ditto: + def self.from(sockaddr : LibC::Sockaddr*) : Address + case family = Family.new(sockaddr.value.sa_family) + when Family::INET6 + sockaddr = sockaddr.as(LibC::SockaddrIn6*) + + IPAddress.new(sockaddr, sizeof(typeof(sockaddr))) + when Family::INET + sockaddr = sockaddr.as(LibC::SockaddrIn*) + + IPAddress.new(sockaddr, sizeof(typeof(sockaddr))) + when Family::UNIX + sockaddr = sockaddr.as(LibC::SockaddrUn*) + + UNIXAddress.new(sockaddr, sizeof(typeof(sockaddr))) + else + raise "Unsupported family type: #{family} (#{family.value})" + end + end + # Parses a `Socket::Address` from an URI. # # Supported formats: @@ -113,6 +133,22 @@ class Socket end end + # :ditto: + def self.from(sockaddr : LibC::Sockaddr*) : IPAddress + case family = Family.new(sockaddr.value.sa_family) + when Family::INET6 + sockaddr = sockaddr.as(LibC::SockaddrIn6*) + + new(sockaddr, sizeof(typeof(sockaddr))) + when Family::INET + sockaddr = sockaddr.as(LibC::SockaddrIn*) + + new(sockaddr, sizeof(typeof(sockaddr))) + else + raise "Unsupported family type: #{family} (#{family.value})" + end + end + # Parses a `Socket::IPAddress` from an URI. # # It expects the URI to include `://:` where `scheme` as @@ -750,6 +786,17 @@ class Socket {% end %} end + # :ditto: + def self.from(sockaddr : LibC::Sockaddr*) : UNIXAddress + {% if flag?(:wasm32) %} + raise NotImplementedError.new "Socket::UNIXAddress.from" + {% else %} + sockaddr = sockaddr.as(LibC::SockaddrUn*) + + new(sockaddr, sizeof(typeof(sockaddr))) + {% end %} + end + # Parses a `Socket::UNIXAddress` from an URI. # # It expects the URI to include `://` where `scheme` as well From 69d192a9a3aa3a00ae44bbdf9026a275642505ea Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 16 Nov 2024 19:18:02 +0800 Subject: [PATCH 1475/1551] Clarify behavior of `strict` for `String`-to-number conversions (#15199) In table form: | | `whitespace: false` | `whitespace: true` | |:-:|:-:|:-:| | **`strict: false`** | `1.2` `1.2 ` `1.2@@` | `1.2` `1.2 ` `1.2@@`
    ` 1.2` ` 1.2 ` ` 1.2@@` | | **`strict: true`** | `1.2` | `1.2` `1.2 `
    ` 1.2` ` 1.2 ` | The current wording suggests that the two arguments are mutually exclusive and cannot be both set to true, but the lower-right quadrant suggests otherwise. (Note that both `whitespace` and `strict` are also true by default.) This PR reflects the current status. --- src/string.cr | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/string.cr b/src/string.cr index 273472d34517..07d65f10dbd4 100644 --- a/src/string.cr +++ b/src/string.cr @@ -317,7 +317,9 @@ class String # * **whitespace**: if `true`, leading and trailing whitespaces are allowed # * **underscore**: if `true`, underscores in numbers are allowed # * **prefix**: if `true`, the prefixes `"0x"`, `"0o"` and `"0b"` override the base - # * **strict**: if `true`, extraneous characters past the end of the number are disallowed + # * **strict**: if `true`, extraneous characters past the end of the number + # are disallowed, unless **whitespace** is also `true` and all the trailing + # characters past the number are whitespaces # * **leading_zero_is_octal**: if `true`, then a number prefixed with `"0"` will be treated as an octal # # ``` @@ -692,7 +694,9 @@ class String # # Options: # * **whitespace**: if `true`, leading and trailing whitespaces are allowed - # * **strict**: if `true`, extraneous characters past the end of the number are disallowed + # * **strict**: if `true`, extraneous characters past the end of the number + # are disallowed, unless **whitespace** is also `true` and all the trailing + # characters past the number are whitespaces # # ``` # "123.45e1".to_f # => 1234.5 @@ -717,7 +721,9 @@ class String # # Options: # * **whitespace**: if `true`, leading and trailing whitespaces are allowed - # * **strict**: if `true`, extraneous characters past the end of the number are disallowed + # * **strict**: if `true`, extraneous characters past the end of the number + # are disallowed, unless **whitespace** is also `true` and all the trailing + # characters past the number are whitespaces # # ``` # "123.45e1".to_f? # => 1234.5 From ea86dd5b7c939e9b11c6fb9fd61c48cf3496cd40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 16 Nov 2024 12:18:28 +0100 Subject: [PATCH 1476/1551] Fix `EventLoop` docs for `Socket` `read`, `write` (#15194) --- src/crystal/system/event_loop/socket.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/event_loop/socket.cr b/src/crystal/system/event_loop/socket.cr index 6309aed391e0..8fa86e50affc 100644 --- a/src/crystal/system/event_loop/socket.cr +++ b/src/crystal/system/event_loop/socket.cr @@ -12,7 +12,7 @@ abstract class Crystal::EventLoop # Returns the number of bytes read (up to `slice.size`). # Returns 0 when the socket is closed and no data available. # - # Use `#send_to` for sending a message to a specific target address. + # Use `#receive_from` for capturing the source address of a message. abstract def read(socket : ::Socket, slice : Bytes) : Int32 # Writes at least one byte from *slice* to the socket. @@ -22,7 +22,7 @@ abstract class Crystal::EventLoop # # Returns the number of bytes written (up to `slice.size`). # - # Use `#receive_from` for capturing the source address of a message. + # Use `#send_to` for sending a message to a specific target address. abstract def write(socket : ::Socket, slice : Bytes) : Int32 # Accepts an incoming TCP connection on the socket. From 25faaadbfee79e3871971fd3f71c00ed28ec7680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 16 Nov 2024 12:19:02 +0100 Subject: [PATCH 1477/1551] Fixup libressl version range for `lib_crypto` [fixup #14900] (#15198) The version info was updated to 4.0 for `lib_ssl` but not `lib_crypto`. --- src/openssl/lib_crypto.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr index aa7eef54c5ab..8e24bbcbc78e 100644 --- a/src/openssl/lib_crypto.cr +++ b/src/openssl/lib_crypto.cr @@ -1,7 +1,7 @@ # Supported library versions: # # * openssl (1.1.0–3.3+) -# * libressl (2.0–3.8+) +# * libressl (2.0–4.0+) # # See https://crystal-lang.org/reference/man/required_libraries.html#tls {% begin %} From 4b38ff9d724c6f7901a0608b97c02043248a053e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 16 Nov 2024 19:19:24 +0800 Subject: [PATCH 1478/1551] Add note about locale-dependent system error messages (#15196) `WasiError#message` is implemented entirely in Crystal and locale-independent. --- src/errno.cr | 5 ++++- src/winerror.cr | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/errno.cr b/src/errno.cr index 9d608c80bc1b..c519a8ab9fdb 100644 --- a/src/errno.cr +++ b/src/errno.cr @@ -38,7 +38,10 @@ enum Errno {% end %} {% end %} - # Convert an Errno to an error message + # Returns the system error message associated with this errno. + # + # NOTE: The result may depend on the current system locale. Specs and + # comparisons should use `#value` instead of this method. def message : String unsafe_message { |slice| String.new(slice) } end diff --git a/src/winerror.cr b/src/winerror.cr index fbb2fb553873..844df5b07315 100644 --- a/src/winerror.cr +++ b/src/winerror.cr @@ -60,6 +60,9 @@ enum WinError : UInt32 # using the current default `LANGID`. # # On non-win32 platforms the result is always an empty string. + # + # NOTE: The result may depend on the current system locale. Specs and + # comparisons should use `#value` instead of this method. def message : String {% if flag?(:win32) %} unsafe_message { |slice| String.from_utf16(slice).strip } From 5e02cd4ec5fee91afe2baa89e20fa73e8e83e966 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 18 Nov 2024 22:04:04 +0800 Subject: [PATCH 1479/1551] Use MSYS2's upstream LLVM version on MinGW-w64 CI (#15197) MSYS2's Pacman has upgraded LLVM to 19.1.3-1 and dropped 18, breaking our CI, so we pick a newer LLVM version on the cross-compiling host to match the target for now (i.e. until #15091) This should fix CI failures on MSYS2. --- .github/workflows/mingw-w64.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml index 8e5db39a5fa1..aecf1f2ca549 100644 --- a/.github/workflows/mingw-w64.yml +++ b/.github/workflows/mingw-w64.yml @@ -18,12 +18,13 @@ jobs: - name: Download Crystal source uses: actions/checkout@v4 - - name: Install LLVM 18 + - name: Install LLVM run: | + _llvm_major=$(wget -qO- https://raw.githubusercontent.com/msys2/MINGW-packages/refs/heads/master/mingw-w64-llvm/PKGBUILD | grep '_version=' | sed -E 's/_version=([0-9]+).*/\1/') sudo apt remove 'llvm-*' 'libllvm*' wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc - sudo apt-add-repository -y deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main - sudo apt install -y llvm-18-dev + sudo apt-add-repository -y deb http://apt.llvm.org/noble/ llvm-toolchain-noble-${_llvm_major} main + sudo apt install -y llvm-${_llvm_major}-dev - name: Install Crystal uses: crystal-lang/install-crystal@v1 From cc30da2d3b8d5d3ee300a65d73268b0ded5638f3 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 19 Nov 2024 14:00:53 +0100 Subject: [PATCH 1480/1551] Refactor Lifetime Event Loop (#14996) Implement RFC 0009: https://github.com/crystal-lang/rfcs/blob/main/text/0009-lifetime-event_loop.md --- spec/std/crystal/evented/arena_spec.cr | 226 ++++++++ .../crystal/evented/poll_descriptor_spec.cr | 100 ++++ spec/std/crystal/evented/timers_spec.cr | 100 ++++ spec/std/crystal/evented/waiters_spec.cr | 168 ++++++ src/crystal/system/event_loop.cr | 21 +- src/crystal/system/unix/epoll.cr | 66 +++ src/crystal/system/unix/epoll/event_loop.cr | 142 +++++ src/crystal/system/unix/evented/arena.cr | 257 +++++++++ src/crystal/system/unix/evented/event.cr | 58 ++ src/crystal/system/unix/evented/event_loop.cr | 521 ++++++++++++++++++ .../system/unix/evented/fiber_event.cr | 33 ++ .../system/unix/evented/poll_descriptor.cr | 50 ++ src/crystal/system/unix/evented/timers.cr | 86 +++ src/crystal/system/unix/evented/waiters.cr | 62 +++ src/crystal/system/unix/eventfd.cr | 31 ++ src/crystal/system/unix/file_descriptor.cr | 5 +- src/crystal/system/unix/kqueue.cr | 89 +++ src/crystal/system/unix/kqueue/event_loop.cr | 245 ++++++++ src/crystal/system/unix/process.cr | 1 + src/crystal/system/unix/signal.cr | 5 +- src/crystal/system/unix/socket.cr | 8 +- src/crystal/system/unix/timerfd.cr | 33 ++ src/crystal/tracing.cr | 1 + src/io/evented.cr | 4 +- src/lib_c/aarch64-android/c/sys/epoll.cr | 32 ++ src/lib_c/aarch64-android/c/sys/eventfd.cr | 5 + src/lib_c/aarch64-android/c/sys/resource.cr | 11 + src/lib_c/aarch64-android/c/sys/timerfd.cr | 10 + src/lib_c/aarch64-android/c/time.cr | 5 + src/lib_c/aarch64-darwin/c/sys/event.cr | 31 ++ src/lib_c/aarch64-darwin/c/sys/resource.cr | 2 + src/lib_c/aarch64-linux-gnu/c/sys/epoll.cr | 32 ++ src/lib_c/aarch64-linux-gnu/c/sys/eventfd.cr | 5 + src/lib_c/aarch64-linux-gnu/c/sys/resource.cr | 11 + src/lib_c/aarch64-linux-gnu/c/sys/timerfd.cr | 10 + src/lib_c/aarch64-linux-gnu/c/time.cr | 5 + src/lib_c/aarch64-linux-musl/c/sys/epoll.cr | 32 ++ src/lib_c/aarch64-linux-musl/c/sys/eventfd.cr | 5 + .../aarch64-linux-musl/c/sys/resource.cr | 2 + src/lib_c/aarch64-linux-musl/c/sys/timerfd.cr | 10 + src/lib_c/aarch64-linux-musl/c/time.cr | 5 + src/lib_c/arm-linux-gnueabihf/c/sys/epoll.cr | 32 ++ .../arm-linux-gnueabihf/c/sys/eventfd.cr | 5 + .../arm-linux-gnueabihf/c/sys/resource.cr | 11 + .../arm-linux-gnueabihf/c/sys/timerfd.cr | 10 + src/lib_c/arm-linux-gnueabihf/c/time.cr | 5 + src/lib_c/i386-linux-gnu/c/sys/epoll.cr | 32 ++ src/lib_c/i386-linux-gnu/c/sys/eventfd.cr | 5 + src/lib_c/i386-linux-gnu/c/sys/resource.cr | 11 + src/lib_c/i386-linux-gnu/c/sys/timerfd.cr | 10 + src/lib_c/i386-linux-gnu/c/time.cr | 5 + src/lib_c/i386-linux-musl/c/sys/epoll.cr | 32 ++ src/lib_c/i386-linux-musl/c/sys/eventfd.cr | 5 + src/lib_c/i386-linux-musl/c/sys/resource.cr | 2 + src/lib_c/i386-linux-musl/c/sys/timerfd.cr | 10 + src/lib_c/i386-linux-musl/c/time.cr | 5 + src/lib_c/x86_64-darwin/c/sys/event.cr | 31 ++ src/lib_c/x86_64-darwin/c/sys/resource.cr | 2 + src/lib_c/x86_64-dragonfly/c/sys/event.cr | 30 + src/lib_c/x86_64-dragonfly/c/sys/resource.cr | 11 + src/lib_c/x86_64-freebsd/c/sys/event.cr | 32 ++ src/lib_c/x86_64-freebsd/c/sys/resource.cr | 11 + src/lib_c/x86_64-linux-gnu/c/sys/epoll.cr | 33 ++ src/lib_c/x86_64-linux-gnu/c/sys/eventfd.cr | 5 + src/lib_c/x86_64-linux-gnu/c/sys/resource.cr | 11 + src/lib_c/x86_64-linux-gnu/c/sys/timerfd.cr | 10 + src/lib_c/x86_64-linux-gnu/c/time.cr | 5 + src/lib_c/x86_64-linux-musl/c/sys/epoll.cr | 33 ++ src/lib_c/x86_64-linux-musl/c/sys/eventfd.cr | 5 + src/lib_c/x86_64-linux-musl/c/sys/resource.cr | 2 + src/lib_c/x86_64-linux-musl/c/sys/timerfd.cr | 10 + src/lib_c/x86_64-linux-musl/c/time.cr | 5 + src/lib_c/x86_64-netbsd/c/sys/event.cr | 32 ++ src/lib_c/x86_64-netbsd/c/sys/resource.cr | 11 + src/lib_c/x86_64-openbsd/c/sys/event.cr | 28 + src/lib_c/x86_64-openbsd/c/sys/resource.cr | 11 + src/lib_c/x86_64-solaris/c/sys/epoll.cr | 33 ++ src/lib_c/x86_64-solaris/c/sys/eventfd.cr | 5 + src/lib_c/x86_64-solaris/c/sys/resource.cr | 11 + src/lib_c/x86_64-solaris/c/sys/timerfd.cr | 10 + src/lib_c/x86_64-solaris/c/time.cr | 5 + 81 files changed, 3079 insertions(+), 8 deletions(-) create mode 100644 spec/std/crystal/evented/arena_spec.cr create mode 100644 spec/std/crystal/evented/poll_descriptor_spec.cr create mode 100644 spec/std/crystal/evented/timers_spec.cr create mode 100644 spec/std/crystal/evented/waiters_spec.cr create mode 100644 src/crystal/system/unix/epoll.cr create mode 100644 src/crystal/system/unix/epoll/event_loop.cr create mode 100644 src/crystal/system/unix/evented/arena.cr create mode 100644 src/crystal/system/unix/evented/event.cr create mode 100644 src/crystal/system/unix/evented/event_loop.cr create mode 100644 src/crystal/system/unix/evented/fiber_event.cr create mode 100644 src/crystal/system/unix/evented/poll_descriptor.cr create mode 100644 src/crystal/system/unix/evented/timers.cr create mode 100644 src/crystal/system/unix/evented/waiters.cr create mode 100644 src/crystal/system/unix/eventfd.cr create mode 100644 src/crystal/system/unix/kqueue.cr create mode 100644 src/crystal/system/unix/kqueue/event_loop.cr create mode 100644 src/crystal/system/unix/timerfd.cr create mode 100644 src/lib_c/aarch64-android/c/sys/epoll.cr create mode 100644 src/lib_c/aarch64-android/c/sys/eventfd.cr create mode 100644 src/lib_c/aarch64-android/c/sys/timerfd.cr create mode 100644 src/lib_c/aarch64-darwin/c/sys/event.cr create mode 100644 src/lib_c/aarch64-linux-gnu/c/sys/epoll.cr create mode 100644 src/lib_c/aarch64-linux-gnu/c/sys/eventfd.cr create mode 100644 src/lib_c/aarch64-linux-gnu/c/sys/timerfd.cr create mode 100644 src/lib_c/aarch64-linux-musl/c/sys/epoll.cr create mode 100644 src/lib_c/aarch64-linux-musl/c/sys/eventfd.cr create mode 100644 src/lib_c/aarch64-linux-musl/c/sys/timerfd.cr create mode 100644 src/lib_c/arm-linux-gnueabihf/c/sys/epoll.cr create mode 100644 src/lib_c/arm-linux-gnueabihf/c/sys/eventfd.cr create mode 100644 src/lib_c/arm-linux-gnueabihf/c/sys/timerfd.cr create mode 100644 src/lib_c/i386-linux-gnu/c/sys/epoll.cr create mode 100644 src/lib_c/i386-linux-gnu/c/sys/eventfd.cr create mode 100644 src/lib_c/i386-linux-gnu/c/sys/timerfd.cr create mode 100644 src/lib_c/i386-linux-musl/c/sys/epoll.cr create mode 100644 src/lib_c/i386-linux-musl/c/sys/eventfd.cr create mode 100644 src/lib_c/i386-linux-musl/c/sys/timerfd.cr create mode 100644 src/lib_c/x86_64-darwin/c/sys/event.cr create mode 100644 src/lib_c/x86_64-dragonfly/c/sys/event.cr create mode 100644 src/lib_c/x86_64-freebsd/c/sys/event.cr create mode 100644 src/lib_c/x86_64-linux-gnu/c/sys/epoll.cr create mode 100644 src/lib_c/x86_64-linux-gnu/c/sys/eventfd.cr create mode 100644 src/lib_c/x86_64-linux-gnu/c/sys/timerfd.cr create mode 100644 src/lib_c/x86_64-linux-musl/c/sys/epoll.cr create mode 100644 src/lib_c/x86_64-linux-musl/c/sys/eventfd.cr create mode 100644 src/lib_c/x86_64-linux-musl/c/sys/timerfd.cr create mode 100644 src/lib_c/x86_64-netbsd/c/sys/event.cr create mode 100644 src/lib_c/x86_64-openbsd/c/sys/event.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/epoll.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/eventfd.cr create mode 100644 src/lib_c/x86_64-solaris/c/sys/timerfd.cr diff --git a/spec/std/crystal/evented/arena_spec.cr b/spec/std/crystal/evented/arena_spec.cr new file mode 100644 index 000000000000..c25bb9ec1adc --- /dev/null +++ b/spec/std/crystal/evented/arena_spec.cr @@ -0,0 +1,226 @@ +{% skip_file unless Crystal.has_constant?(:Evented) %} + +require "spec" + +describe Crystal::Evented::Arena do + describe "#allocate_at?" do + it "yields block when not allocated" do + arena = Crystal::Evented::Arena(Int32).new(32) + pointer = nil + index = nil + called = 0 + + ret = arena.allocate_at?(0) do |ptr, idx| + pointer = ptr + index = idx + called += 1 + end + ret.should eq(index) + called.should eq(1) + + ret = arena.allocate_at?(0) { called += 1 } + ret.should be_nil + called.should eq(1) + + pointer.should_not be_nil + index.should_not be_nil + + arena.get(index.not_nil!) do |ptr| + ptr.should eq(pointer) + end + end + + it "allocates up to capacity" do + arena = Crystal::Evented::Arena(Int32).new(32) + indexes = [] of Crystal::Evented::Arena::Index + + indexes = 32.times.map do |i| + arena.allocate_at?(i) { |ptr, _| ptr.value = i } + end.to_a + + indexes.size.should eq(32) + + indexes.each do |index| + arena.get(index.not_nil!) do |pointer| + pointer.should eq(pointer) + pointer.value.should eq(index.not_nil!.index) + end + end + end + + it "checks bounds" do + arena = Crystal::Evented::Arena(Int32).new(32) + expect_raises(IndexError) { arena.allocate_at?(-1) { } } + expect_raises(IndexError) { arena.allocate_at?(33) { } } + end + end + + describe "#get" do + it "returns previously allocated object" do + arena = Crystal::Evented::Arena(Int32).new(32) + pointer = nil + + index = arena.allocate_at(30) do |ptr| + pointer = ptr + ptr.value = 654321 + end + called = 0 + + 2.times do + arena.get(index.not_nil!) do |ptr| + ptr.should eq(pointer) + ptr.value.should eq(654321) + called += 1 + end + end + called.should eq(2) + end + + it "can't access unallocated object" do + arena = Crystal::Evented::Arena(Int32).new(32) + + expect_raises(RuntimeError) do + arena.get(Crystal::Evented::Arena::Index.new(10, 0)) { } + end + end + + it "checks generation" do + arena = Crystal::Evented::Arena(Int32).new(32) + called = 0 + + index1 = arena.allocate_at(2) { called += 1 } + called.should eq(1) + + arena.free(index1) { } + expect_raises(RuntimeError) { arena.get(index1) { } } + + index2 = arena.allocate_at(2) { called += 1 } + called.should eq(2) + expect_raises(RuntimeError) { arena.get(index1) { } } + + arena.get(index2) { } + end + + it "checks out of bounds" do + arena = Crystal::Evented::Arena(Int32).new(32) + expect_raises(IndexError) { arena.get(Crystal::Evented::Arena::Index.new(-1, 0)) { } } + expect_raises(IndexError) { arena.get(Crystal::Evented::Arena::Index.new(33, 0)) { } } + end + end + + describe "#get?" do + it "returns previously allocated object" do + arena = Crystal::Evented::Arena(Int32).new(32) + pointer = nil + + index = arena.allocate_at(30) do |ptr| + pointer = ptr + ptr.value = 654321 + end + + called = 0 + 2.times do + ret = arena.get?(index) do |ptr| + ptr.should eq(pointer) + ptr.not_nil!.value.should eq(654321) + called += 1 + end + ret.should be_true + end + called.should eq(2) + end + + it "can't access unallocated index" do + arena = Crystal::Evented::Arena(Int32).new(32) + + called = 0 + ret = arena.get?(Crystal::Evented::Arena::Index.new(10, 0)) { called += 1 } + ret.should be_false + called.should eq(0) + end + + it "checks generation" do + arena = Crystal::Evented::Arena(Int32).new(32) + called = 0 + + old_index = arena.allocate_at(2) { } + arena.free(old_index) { } + + # not accessible after free: + ret = arena.get?(old_index) { called += 1 } + ret.should be_false + called.should eq(0) + + # can be reallocated: + new_index = arena.allocate_at(2) { } + + # still not accessible after reallocate: + ret = arena.get?(old_index) { called += 1 } + ret.should be_false + called.should eq(0) + + # accessible after reallocate (new index): + ret = arena.get?(new_index) { called += 1 } + ret.should be_true + called.should eq(1) + end + + it "checks out of bounds" do + arena = Crystal::Evented::Arena(Int32).new(32) + called = 0 + + arena.get?(Crystal::Evented::Arena::Index.new(-1, 0)) { called += 1 }.should be_false + arena.get?(Crystal::Evented::Arena::Index.new(33, 0)) { called += 1 }.should be_false + + called.should eq(0) + end + end + + describe "#free" do + it "deallocates the object" do + arena = Crystal::Evented::Arena(Int32).new(32) + + index1 = arena.allocate_at(3) { |ptr| ptr.value = 123 } + arena.free(index1) { } + + index2 = arena.allocate_at(3) { } + index2.should_not eq(index1) + + value = nil + arena.get(index2) { |ptr| value = ptr.value } + value.should eq(0) + end + + it "checks generation" do + arena = Crystal::Evented::Arena(Int32).new(32) + + called = 0 + old_index = arena.allocate_at(1) { } + + # can free: + arena.free(old_index) { called += 1 } + called.should eq(1) + + # can reallocate: + new_index = arena.allocate_at(1) { } + + # can't free with invalid index: + arena.free(old_index) { called += 1 } + called.should eq(1) + + # but new index can: + arena.free(new_index) { called += 1 } + called.should eq(2) + end + + it "checks out of bounds" do + arena = Crystal::Evented::Arena(Int32).new(32) + called = 0 + + arena.free(Crystal::Evented::Arena::Index.new(-1, 0)) { called += 1 } + arena.free(Crystal::Evented::Arena::Index.new(33, 0)) { called += 1 } + + called.should eq(0) + end + end +end diff --git a/spec/std/crystal/evented/poll_descriptor_spec.cr b/spec/std/crystal/evented/poll_descriptor_spec.cr new file mode 100644 index 000000000000..d50ecd1036b9 --- /dev/null +++ b/spec/std/crystal/evented/poll_descriptor_spec.cr @@ -0,0 +1,100 @@ +{% skip_file unless Crystal.has_constant?(:Evented) %} + +require "spec" + +class Crystal::Evented::FakeLoop < Crystal::Evented::EventLoop + getter operations = [] of {Symbol, Int32, Crystal::Evented::Arena::Index | Bool} + + private def system_run(blocking : Bool) : Nil + end + + private def interrupt : Nil + end + + protected def system_add(fd : Int32, index : Arena::Index) : Nil + operations << {:add, fd, index} + end + + protected def system_del(fd : Int32, closing = true) : Nil + operations << {:del, fd, closing} + end + + protected def system_del(fd : Int32, closing = true, &) : Nil + operations << {:del, fd, closing} + end + + private def system_set_timer(time : Time::Span?) : Nil + end +end + +describe Crystal::Evented::Waiters do + describe "#take_ownership" do + it "associates a poll descriptor to an evloop instance" do + fd = Int32::MAX + pd = Crystal::Evented::PollDescriptor.new + index = Crystal::Evented::Arena::Index.new(fd, 0) + evloop = Crystal::Evented::FakeLoop.new + + pd.take_ownership(evloop, fd, index) + pd.@event_loop.should be(evloop) + + evloop.operations.should eq([ + {:add, fd, index}, + ]) + end + + it "moves a poll descriptor to another evloop instance" do + fd = Int32::MAX + pd = Crystal::Evented::PollDescriptor.new + index = Crystal::Evented::Arena::Index.new(fd, 0) + + evloop1 = Crystal::Evented::FakeLoop.new + evloop2 = Crystal::Evented::FakeLoop.new + + pd.take_ownership(evloop1, fd, index) + pd.take_ownership(evloop2, fd, index) + + pd.@event_loop.should be(evloop2) + + evloop1.operations.should eq([ + {:add, fd, index}, + {:del, fd, false}, + ]) + evloop2.operations.should eq([ + {:add, fd, index}, + ]) + end + + it "can't move to the current evloop" do + fd = Int32::MAX + pd = Crystal::Evented::PollDescriptor.new + index = Crystal::Evented::Arena::Index.new(fd, 0) + + evloop = Crystal::Evented::FakeLoop.new + + pd.take_ownership(evloop, fd, index) + expect_raises(Exception) { pd.take_ownership(evloop, fd, index) } + end + + it "can't move with pending waiters" do + fd = Int32::MAX + pd = Crystal::Evented::PollDescriptor.new + index = Crystal::Evented::Arena::Index.new(fd, 0) + event = Crystal::Evented::Event.new(:io_read, Fiber.current) + + evloop1 = Crystal::Evented::FakeLoop.new + pd.take_ownership(evloop1, fd, index) + pd.@readers.add(pointerof(event)) + + evloop2 = Crystal::Evented::FakeLoop.new + expect_raises(RuntimeError) { pd.take_ownership(evloop2, fd, index) } + + pd.@event_loop.should be(evloop1) + + evloop1.operations.should eq([ + {:add, fd, index}, + ]) + evloop2.operations.should be_empty + end + end +end diff --git a/spec/std/crystal/evented/timers_spec.cr b/spec/std/crystal/evented/timers_spec.cr new file mode 100644 index 000000000000..d40917910d1d --- /dev/null +++ b/spec/std/crystal/evented/timers_spec.cr @@ -0,0 +1,100 @@ +{% skip_file unless Crystal.has_constant?(:Evented) %} + +require "spec" + +describe Crystal::Evented::Timers do + it "#empty?" do + timers = Crystal::Evented::Timers.new + timers.empty?.should be_true + + event = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 7.seconds) + timers.add(pointerof(event)) + timers.empty?.should be_false + end + + it "#next_ready?" do + # empty + timers = Crystal::Evented::Timers.new + timers.next_ready?.should be_nil + + # with events + event = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 5.seconds) + timers.add(pointerof(event)) + timers.next_ready?.should eq(event.wake_at?) + end + + it "#dequeue_ready" do + timers = Crystal::Evented::Timers.new + + event1 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds) + event2 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds) + event3 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.minute) + + # empty + called = 0 + timers.dequeue_ready { called += 1 } + called.should eq(0) + + # add events in non chronological order + timers = Crystal::Evented::Timers.new + timers.add(pointerof(event1)) + timers.add(pointerof(event3)) + timers.add(pointerof(event2)) + + events = [] of Crystal::Evented::Event* + timers.dequeue_ready { |event| events << event } + + events.should eq([ + pointerof(event1), + pointerof(event2), + ]) + timers.empty?.should be_false + end + + it "#add" do + timers = Crystal::Evented::Timers.new + + event0 = Crystal::Evented::Event.new(:sleep, Fiber.current) + event1 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds) + event2 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 2.minutes) + event3 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.minute) + + # add events in non chronological order + timers.add(pointerof(event1)).should be_true # added to the head (next ready) + timers.add(pointerof(event2)).should be_false + timers.add(pointerof(event3)).should be_false + + event0.wake_at = -1.minute + timers.add(pointerof(event0)).should be_true # added new head (next ready) + + events = [] of Crystal::Evented::Event* + timers.each { |event| events << event } + events.should eq([ + pointerof(event0), + pointerof(event1), + pointerof(event3), + pointerof(event2), + ]) + timers.empty?.should be_false + end + + it "#delete" do + event1 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds) + event2 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds) + event3 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.minute) + event4 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 4.minutes) + + # add events in non chronological order + timers = Crystal::Evented::Timers.new + timers.add(pointerof(event1)) + timers.add(pointerof(event3)) + timers.add(pointerof(event2)) + + timers.delete(pointerof(event1)).should eq({true, true}) # dequeued+removed head (next ready) + timers.delete(pointerof(event3)).should eq({true, false}) # dequeued + timers.delete(pointerof(event2)).should eq({true, true}) # dequeued+removed new head (next ready) + timers.empty?.should be_true + timers.delete(pointerof(event2)).should eq({false, false}) # not dequeued + timers.delete(pointerof(event4)).should eq({false, false}) # not dequeued + end +end diff --git a/spec/std/crystal/evented/waiters_spec.cr b/spec/std/crystal/evented/waiters_spec.cr new file mode 100644 index 000000000000..91e145f6f811 --- /dev/null +++ b/spec/std/crystal/evented/waiters_spec.cr @@ -0,0 +1,168 @@ +{% skip_file unless Crystal.has_constant?(:Evented) %} + +require "spec" + +describe Crystal::Evented::Waiters do + describe "#add" do + it "adds event to list" do + waiters = Crystal::Evented::Waiters.new + + event = Crystal::Evented::Event.new(:io_read, Fiber.current) + ret = waiters.add(pointerof(event)) + ret.should be_true + end + + it "doesn't add the event when the list is ready (race condition)" do + waiters = Crystal::Evented::Waiters.new + waiters.ready_one { true } + + event = Crystal::Evented::Event.new(:io_read, Fiber.current) + ret = waiters.add(pointerof(event)) + ret.should be_false + waiters.@ready.should be_false + end + + it "doesn't add the event when the list is always ready" do + waiters = Crystal::Evented::Waiters.new + waiters.ready_all { } + + event = Crystal::Evented::Event.new(:io_read, Fiber.current) + ret = waiters.add(pointerof(event)) + ret.should be_false + waiters.@always_ready.should be_true + end + end + + describe "#delete" do + it "removes the event from the list" do + waiters = Crystal::Evented::Waiters.new + event = Crystal::Evented::Event.new(:io_read, Fiber.current) + + waiters.add(pointerof(event)) + waiters.delete(pointerof(event)) + + called = false + waiters.ready_one { called = true } + called.should be_false + end + + it "does nothing when the event isn't in the list" do + waiters = Crystal::Evented::Waiters.new + event = Crystal::Evented::Event.new(:io_read, Fiber.current) + waiters.delete(pointerof(event)) + end + end + + describe "#ready_one" do + it "marks the list as ready when empty (race condition)" do + waiters = Crystal::Evented::Waiters.new + called = false + + waiters.ready_one { called = true } + + called.should be_false + waiters.@ready.should be_true + end + + it "dequeues events in FIFO order" do + waiters = Crystal::Evented::Waiters.new + event1 = Crystal::Evented::Event.new(:io_read, Fiber.current) + event2 = Crystal::Evented::Event.new(:io_read, Fiber.current) + event3 = Crystal::Evented::Event.new(:io_read, Fiber.current) + called = 0 + + waiters.add(pointerof(event1)) + waiters.add(pointerof(event2)) + waiters.add(pointerof(event3)) + + 3.times do + waiters.ready_one do |event| + case called += 1 + when 1 then event.should eq(pointerof(event1)) + when 2 then event.should eq(pointerof(event2)) + when 3 then event.should eq(pointerof(event3)) + end + true + end + end + called.should eq(3) + waiters.@ready.should be_false + + waiters.ready_one do + called += 1 + true + end + called.should eq(3) + waiters.@ready.should be_true + end + + it "dequeues events until the block returns true" do + waiters = Crystal::Evented::Waiters.new + event1 = Crystal::Evented::Event.new(:io_read, Fiber.current) + event2 = Crystal::Evented::Event.new(:io_read, Fiber.current) + event3 = Crystal::Evented::Event.new(:io_read, Fiber.current) + called = 0 + + waiters.add(pointerof(event1)) + waiters.add(pointerof(event2)) + waiters.add(pointerof(event3)) + + waiters.ready_one do |event| + (called += 1) == 2 + end + called.should eq(2) + waiters.@ready.should be_false + end + + it "dequeues events until empty and marks the list as ready" do + waiters = Crystal::Evented::Waiters.new + event1 = Crystal::Evented::Event.new(:io_read, Fiber.current) + event2 = Crystal::Evented::Event.new(:io_read, Fiber.current) + called = 0 + + waiters.add(pointerof(event1)) + waiters.add(pointerof(event2)) + + waiters.ready_one do |event| + called += 1 + false + end + called.should eq(2) + waiters.@ready.should be_true + end + end + + describe "#ready_all" do + it "marks the list as always ready" do + waiters = Crystal::Evented::Waiters.new + called = false + + waiters.ready_all { called = true } + + called.should be_false + waiters.@always_ready.should be_true + end + + it "dequeues all events" do + waiters = Crystal::Evented::Waiters.new + event1 = Crystal::Evented::Event.new(:io_read, Fiber.current) + event2 = Crystal::Evented::Event.new(:io_read, Fiber.current) + event3 = Crystal::Evented::Event.new(:io_read, Fiber.current) + called = 0 + + waiters.add(pointerof(event1)) + waiters.add(pointerof(event2)) + waiters.add(pointerof(event3)) + + waiters.ready_all do |event| + case called += 1 + when 1 then event.should eq(pointerof(event1)) + when 2 then event.should eq(pointerof(event2)) + when 3 then event.should eq(pointerof(event3)) + end + end + called.should eq(3) + waiters.@always_ready.should be_true + end + end +end diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index fe973ec8c99e..33ff4f9dac85 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -4,7 +4,16 @@ abstract class Crystal::EventLoop {% if flag?(:wasi) %} Crystal::Wasi::EventLoop.new {% elsif flag?(:unix) %} - Crystal::LibEvent::EventLoop.new + # TODO: enable more targets by default (need manual tests or fixes) + {% if flag?("evloop=libevent") %} + Crystal::LibEvent::EventLoop.new + {% elsif flag?("evloop=epoll") || flag?(:android) || flag?(:linux) %} + Crystal::Epoll::EventLoop.new + {% elsif flag?("evloop=kqueue") || flag?(:darwin) || flag?(:freebsd) %} + Crystal::Kqueue::EventLoop.new + {% else %} + Crystal::LibEvent::EventLoop.new + {% end %} {% elsif flag?(:win32) %} Crystal::IOCP::EventLoop.new {% else %} @@ -78,7 +87,15 @@ end {% if flag?(:wasi) %} require "./wasi/event_loop" {% elsif flag?(:unix) %} - require "./unix/event_loop_libevent" + {% if flag?("evloop=libevent") %} + require "./unix/event_loop_libevent" + {% elsif flag?("evloop=epoll") || flag?(:android) || flag?(:linux) %} + require "./unix/epoll/event_loop" + {% elsif flag?("evloop=kqueue") || flag?(:darwin) || flag?(:freebsd) %} + require "./unix/kqueue/event_loop" + {% else %} + require "./unix/event_loop_libevent" + {% end %} {% elsif flag?(:win32) %} require "./win32/event_loop_iocp" {% else %} diff --git a/src/crystal/system/unix/epoll.cr b/src/crystal/system/unix/epoll.cr new file mode 100644 index 000000000000..28a157ae3360 --- /dev/null +++ b/src/crystal/system/unix/epoll.cr @@ -0,0 +1,66 @@ +require "c/sys/epoll" + +struct Crystal::System::Epoll + def initialize + @epfd = LibC.epoll_create1(LibC::EPOLL_CLOEXEC) + raise RuntimeError.from_errno("epoll_create1") if @epfd == -1 + end + + def fd : Int32 + @epfd + end + + def add(fd : Int32, epoll_event : LibC::EpollEvent*) : Nil + if LibC.epoll_ctl(@epfd, LibC::EPOLL_CTL_ADD, fd, epoll_event) == -1 + raise RuntimeError.from_errno("epoll_ctl(EPOLL_CTL_ADD)") unless Errno.value == Errno::EPERM + end + end + + def add(fd : Int32, events : UInt32, u64 : UInt64) : Nil + epoll_event = uninitialized LibC::EpollEvent + epoll_event.events = events + epoll_event.data.u64 = u64 + add(fd, pointerof(epoll_event)) + end + + def modify(fd : Int32, epoll_event : LibC::EpollEvent*) : Nil + if LibC.epoll_ctl(@epfd, LibC::EPOLL_CTL_MOD, fd, epoll_event) == -1 + raise RuntimeError.from_errno("epoll_ctl(EPOLL_CTL_MOD)") + end + end + + def delete(fd : Int32) : Nil + delete(fd) do + raise RuntimeError.from_errno("epoll_ctl(EPOLL_CTL_DEL)") + end + end + + def delete(fd : Int32, &) : Nil + if LibC.epoll_ctl(@epfd, LibC::EPOLL_CTL_DEL, fd, nil) == -1 + yield + end + end + + # `timeout` is in milliseconds; -1 will wait indefinitely; 0 will never wait. + def wait(events : Slice(LibC::EpollEvent), timeout : Int32) : Slice(LibC::EpollEvent) + count = 0 + + loop do + count = LibC.epoll_wait(@epfd, events.to_unsafe, events.size, timeout) + break unless count == -1 + + if Errno.value == Errno::EINTR + # retry when waiting indefinitely, return otherwise + break unless timeout == -1 + else + raise RuntimeError.from_errno("epoll_wait") + end + end + + events[0, count.clamp(0..)] + end + + def close : Nil + LibC.close(@epfd) + end +end diff --git a/src/crystal/system/unix/epoll/event_loop.cr b/src/crystal/system/unix/epoll/event_loop.cr new file mode 100644 index 000000000000..dc2f2052dfa2 --- /dev/null +++ b/src/crystal/system/unix/epoll/event_loop.cr @@ -0,0 +1,142 @@ +require "../evented/event_loop" +require "../epoll" +require "../eventfd" +require "../timerfd" + +class Crystal::Epoll::EventLoop < Crystal::Evented::EventLoop + def initialize + # the epoll instance + @epoll = System::Epoll.new + + # notification to interrupt a run + @interrupted = Atomic::Flag.new + @eventfd = System::EventFD.new + @epoll.add(@eventfd.fd, LibC::EPOLLIN, u64: @eventfd.fd.to_u64!) + + # we use timerfd to go below the millisecond precision of epoll_wait; it + # also allows to avoid locking timers before every epoll_wait call + @timerfd = System::TimerFD.new + @epoll.add(@timerfd.fd, LibC::EPOLLIN, u64: @timerfd.fd.to_u64!) + end + + def after_fork_before_exec : Nil + super + + # O_CLOEXEC would close these automatically, but we don't want to mess with + # the parent process fds (it would mess the parent evloop) + @epoll.close + @eventfd.close + @timerfd.close + end + + {% unless flag?(:preview_mt) %} + def after_fork : Nil + super + + # close inherited fds + @epoll.close + @eventfd.close + @timerfd.close + + # create new fds + @epoll = System::Epoll.new + + @interrupted.clear + @eventfd = System::EventFD.new + @epoll.add(@eventfd.fd, LibC::EPOLLIN, u64: @eventfd.fd.to_u64!) + + @timerfd = System::TimerFD.new + @epoll.add(@timerfd.fd, LibC::EPOLLIN, u64: @timerfd.fd.to_u64!) + system_set_timer(@timers.next_ready?) + + # re-add all registered fds + Evented.arena.each { |fd, index| system_add(fd, index) } + end + {% end %} + + private def system_run(blocking : Bool) : Nil + Crystal.trace :evloop, "run", blocking: blocking ? 1 : 0 + + # wait for events (indefinitely when blocking) + buffer = uninitialized LibC::EpollEvent[128] + epoll_events = @epoll.wait(buffer.to_slice, timeout: blocking ? -1 : 0) + + timer_triggered = false + + # process events + epoll_events.size.times do |i| + epoll_event = epoll_events.to_unsafe + i + + case epoll_event.value.data.u64 + when @eventfd.fd + # TODO: panic if epoll_event.value.events != LibC::EPOLLIN (could be EPOLLERR or EPLLHUP) + Crystal.trace :evloop, "interrupted" + @eventfd.read + # OPTIMIZE: only reset interrupted before a blocking wait + @interrupted.clear + when @timerfd.fd + # TODO: panic if epoll_event.value.events != LibC::EPOLLIN (could be EPOLLERR or EPLLHUP) + Crystal.trace :evloop, "timer" + timer_triggered = true + else + process_io(epoll_event) + end + end + + process_timers(timer_triggered) + end + + private def process_io(epoll_event : LibC::EpollEvent*) : Nil + index = Evented::Arena::Index.new(epoll_event.value.data.u64) + events = epoll_event.value.events + + Crystal.trace :evloop, "event", fd: index.index, index: index.to_i64, events: events + + Evented.arena.get?(index) do |pd| + if (events & (LibC::EPOLLERR | LibC::EPOLLHUP)) != 0 + pd.value.@readers.ready_all { |event| unsafe_resume_io(event) } + pd.value.@writers.ready_all { |event| unsafe_resume_io(event) } + return + end + + if (events & LibC::EPOLLRDHUP) == LibC::EPOLLRDHUP + pd.value.@readers.ready_all { |event| unsafe_resume_io(event) } + elsif (events & LibC::EPOLLIN) == LibC::EPOLLIN + pd.value.@readers.ready_one { |event| unsafe_resume_io(event) } + end + + if (events & LibC::EPOLLOUT) == LibC::EPOLLOUT + pd.value.@writers.ready_one { |event| unsafe_resume_io(event) } + end + end + end + + def interrupt : Nil + # the atomic makes sure we only write once + @eventfd.write(1) if @interrupted.test_and_set + end + + protected def system_add(fd : Int32, index : Evented::Arena::Index) : Nil + Crystal.trace :evloop, "epoll_ctl", op: "add", fd: fd, index: index.to_i64 + events = LibC::EPOLLIN | LibC::EPOLLOUT | LibC::EPOLLRDHUP | LibC::EPOLLET + @epoll.add(fd, events, u64: index.to_u64) + end + + protected def system_del(fd : Int32, closing = true) : Nil + Crystal.trace :evloop, "epoll_ctl", op: "del", fd: fd + @epoll.delete(fd) + end + + protected def system_del(fd : Int32, closing = true, &) : Nil + Crystal.trace :evloop, "epoll_ctl", op: "del", fd: fd + @epoll.delete(fd) { yield } + end + + private def system_set_timer(time : Time::Span?) : Nil + if time + @timerfd.set(time) + else + @timerfd.cancel + end + end +end diff --git a/src/crystal/system/unix/evented/arena.cr b/src/crystal/system/unix/evented/arena.cr new file mode 100644 index 000000000000..818b80b83c41 --- /dev/null +++ b/src/crystal/system/unix/evented/arena.cr @@ -0,0 +1,257 @@ +# Generational Arena. +# +# Allocates a `Slice` of `T` through `mmap`. `T` is supposed to be a struct, so +# it can be embedded right into the memory region. +# +# The arena allocates objects `T` at a predefined index. The object iself is +# uninitialized (outside of having its memory initialized to zero). The object +# can be allocated and later retrieved using the generation index +# (Arena::Index) that contains both the actual index (Int32) and the generation +# number (UInt32). Deallocating the object increases the generation number, +# which allows the object to be reallocated later on. Trying to retrieve the +# allocation using the generation index will fail if the generation number +# changed (it's a new allocation). +# +# This arena isn't generic as it won't keep a list of free indexes. It assumes +# that something else will maintain the uniqueness of indexes and reuse indexes +# as much as possible instead of growing. +# +# For example this arena is used to hold `Crystal::Evented::PollDescriptor` +# allocations for all the fd in a program, where the fd is used as the index. +# They're unique to the process and the OS always reuses the lowest fd numbers +# before growing. +# +# Thread safety: the memory region is pre-allocated (up to capacity) using mmap +# (virtual allocation) and pointers are never invalidated. Individual +# allocation, deallocation and regular accesses are protected by a fine grained +# lock over each object: parallel accesses to the memory region are prohibited, +# and pointers are expected to not outlive the block that yielded them (don't +# capture them). +# +# Guarantees: `mmap` initializes the memory to zero, which means `T` objects are +# initialized to zero by default, then `#free` will also clear the memory, so +# the next allocation shall be initialized to zero, too. +# +# TODO: instead of the mmap that must preallocate a fixed chunk of virtual +# memory, we could allocate individual blocks of memory, then access the actual +# block at `index % size`. Pointers would still be valid (as long as the block +# isn't collected). We wouldn't have to worry about maximum capacity, we could +# still allocate blocks discontinuously & collect unused blocks during GC +# collections. +class Crystal::Evented::Arena(T) + INVALID_INDEX = Index.new(-1, 0) + + struct Index + def initialize(index : Int32, generation : UInt32) + @data = (index.to_i64! << 32) | generation.to_u64! + end + + def initialize(@data : Int64) + end + + def initialize(data : UInt64) + @data = data.to_i64! + end + + # Returns the generation number. + def generation : UInt32 + @data.to_u32! + end + + # Returns the actual index. + def index : Int32 + (@data >> 32).to_i32! + end + + def to_i64 : Int64 + @data + end + + def to_u64 : UInt64 + @data.to_u64! + end + + def valid? : Bool + @data >= 0 + end + end + + struct Entry(T) + @lock = SpinLock.new # protects parallel allocate/free calls + property? allocated = false + property generation = 0_u32 + @object = uninitialized T + + def pointer : Pointer(T) + pointerof(@object) + end + + def free : Nil + @generation &+= 1_u32 + @allocated = false + pointer.clear(1) + end + end + + @buffer : Slice(Entry(T)) + + {% unless flag?(:preview_mt) %} + # Remember the maximum allocated fd ever; + # + # This is specific to `EventLoop#after_fork` that needs to iterate the arena + # for registered fds in epoll/kqueue to re-add them to the new epoll/kqueue + # instances. Without this upper limit we'd iterate the whole arena which + # would lead the kernel to try and allocate the whole mmap in physical + # memory (instead of virtual memory) which would at best be a waste, and a + # worst fill the memory (e.g. unlimited open files). + @maximum = 0 + {% end %} + + def initialize(capacity : Int32) + pointer = self.class.mmap(LibC::SizeT.new(sizeof(Entry(T))) * capacity) + @buffer = Slice.new(pointer.as(Pointer(Entry(T))), capacity) + end + + protected def self.mmap(bytesize) + flags = LibC::MAP_PRIVATE | LibC::MAP_ANON + prot = LibC::PROT_READ | LibC::PROT_WRITE + + pointer = LibC.mmap(nil, bytesize, prot, flags, -1, 0) + System.panic("mmap", Errno.value) if pointer == LibC::MAP_FAILED + + {% if flag?(:linux) %} + LibC.madvise(pointer, bytesize, LibC::MADV_NOHUGEPAGE) + {% end %} + + pointer + end + + def finalize + LibC.munmap(@buffer.to_unsafe, @buffer.bytesize) + end + + # Allocates the object at *index* unless already allocated, then yields a + # pointer to the object at *index* and the current generation index to later + # retrieve and free the allocated object. Eventually returns the generation + # index. + # + # Does nothing if the object has already been allocated and returns `nil`. + # + # There are no generational checks. + # Raises if *index* is out of bounds. + def allocate_at?(index : Int32, & : (Pointer(T), Index) ->) : Index? + entry = at(index) + + entry.value.@lock.sync do + return if entry.value.allocated? + + {% unless flag?(:preview_mt) %} + @maximum = index if index > @maximum + {% end %} + entry.value.allocated = true + + gen_index = Index.new(index, entry.value.generation) + yield entry.value.pointer, gen_index + + gen_index + end + end + + # Same as `#allocate_at?` but raises when already allocated. + def allocate_at(index : Int32, & : (Pointer(T), Index) ->) : Index? + allocate_at?(index) { |ptr, idx| yield ptr, idx } || + raise RuntimeError.new("#{self.class.name}: already allocated index=#{index}") + end + + # Yields a pointer to the object previously allocated at *index*. + # + # Raises if the object isn't allocated. + # Raises if the generation has changed (i.e. the object has been freed then reallocated). + # Raises if *index* is negative. + def get(index : Index, &) : Nil + at(index) do |entry| + yield entry.value.pointer + end + end + + # Yields a pointer to the object previously allocated at *index* and returns + # true. + # Does nothing if the object isn't allocated or the generation has changed, + # and returns false. + # + # Raises if *index* is negative. + def get?(index : Index, &) : Bool + at?(index) do |entry| + yield entry.value.pointer + return true + end + false + end + + # Yields the object previously allocated at *index* then releases it. + # Does nothing if the object isn't allocated or the generation has changed. + # + # Raises if *index* is negative. + def free(index : Index, &) : Nil + at?(index) do |entry| + begin + yield entry.value.pointer + ensure + entry.value.free + end + end + end + + private def at(index : Index, &) : Nil + entry = at(index.index) + entry.value.@lock.lock + + unless entry.value.allocated? && entry.value.generation == index.generation + entry.value.@lock.unlock + raise RuntimeError.new("#{self.class.name}: invalid reference index=#{index.index}:#{index.generation} current=#{index.index}:#{entry.value.generation}") + end + + begin + yield entry + ensure + entry.value.@lock.unlock + end + end + + private def at?(index : Index, &) : Nil + return unless entry = at?(index.index) + + entry.value.@lock.sync do + return unless entry.value.allocated? + return unless entry.value.generation == index.generation + + yield entry + end + end + + private def at(index : Int32) : Pointer(Entry(T)) + (@buffer + index).to_unsafe + end + + private def at?(index : Int32) : Pointer(Entry(T))? + if 0 <= index < @buffer.size + @buffer.to_unsafe + index + end + end + + {% unless flag?(:preview_mt) %} + # Iterates all allocated objects, yields the actual index as well as the + # generation index. + def each(&) : Nil + pointer = @buffer.to_unsafe + + 0.upto(@maximum) do |index| + entry = pointer + index + + if entry.value.allocated? + yield index, Index.new(index, entry.value.generation) + end + end + end + {% end %} +end diff --git a/src/crystal/system/unix/evented/event.cr b/src/crystal/system/unix/evented/event.cr new file mode 100644 index 000000000000..b33130df53c2 --- /dev/null +++ b/src/crystal/system/unix/evented/event.cr @@ -0,0 +1,58 @@ +require "crystal/pointer_linked_list" + +# Information about the event that a `Fiber` is waiting on. +# +# The event can be waiting for `IO` with or without a timeout, or be a timed +# event such as sleep or a select timeout (without IO). +# +# The events can be found in different queues, for example `Timers` and/or +# `Waiters` depending on their type. +struct Crystal::Evented::Event + enum Type + IoRead + IoWrite + Sleep + SelectTimeout + end + + getter type : Type + + # The `Fiber` that is waiting on the event and that the `EventLoop` shall + # resume. + getter fiber : Fiber + + # Arena index to access the associated `PollDescriptor` when processing an IO + # event. Nil for timed events (sleep, select timeout). + getter! index : Arena::Index? + + # The absolute time, against the monotonic clock, at which a timed event shall + # trigger. Nil for IO events without a timeout. + getter! wake_at : Time::Span + + # True if an IO event has timed out (i.e. we're past `#wake_at`). + getter? timed_out : Bool = false + + # The event can be added to `Waiters` lists. + include PointerLinkedList::Node + + def initialize(@type : Type, @fiber, @index = nil, timeout : Time::Span? = nil) + if timeout + seconds, nanoseconds = System::Time.monotonic + now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds) + @wake_at = now + timeout + end + end + + # Mark the IO event as timed out. + def timed_out! : Bool + @timed_out = true + end + + # Manually set the absolute time (against the monotonic clock). This is meant + # for `FiberEvent` to set and cancel its inner sleep or select timeout; these + # objects are allocated once per `Fiber`. + # + # NOTE: musn't be changed after registering the event into `Timers`! + def wake_at=(@wake_at) + end +end diff --git a/src/crystal/system/unix/evented/event_loop.cr b/src/crystal/system/unix/evented/event_loop.cr new file mode 100644 index 000000000000..65b9e746b9b2 --- /dev/null +++ b/src/crystal/system/unix/evented/event_loop.cr @@ -0,0 +1,521 @@ +require "./*" +require "./arena" + +module Crystal::System::FileDescriptor + # user data (generation index for the arena) + property __evloop_data : Evented::Arena::Index = Evented::Arena::INVALID_INDEX +end + +module Crystal::System::Socket + # user data (generation index for the arena) + property __evloop_data : Evented::Arena::Index = Evented::Arena::INVALID_INDEX +end + +module Crystal::Evented + # The generational arena: + # + # 1. decorrelates the fd from the IO since the evloop only really cares about + # the fd state and to resume pending fibers (it could monitor a fd without + # an IO object); + # + # 2. permits to avoid pushing raw pointers to IO objects into kernel data + # structures that are unknown to the GC, and to safely check whether the + # allocation is still valid before trying to dereference the pointer. Since + # `PollDescriptor` also doesn't have pointers to the actual IO object, it + # won't prevent the GC from collecting lost IO objects (and spares us from + # using weak references). + # + # 3. to a lesser extent, it also allows to keep the `PollDescriptor` allocated + # together in the same region, and polluting the IO object itself with + # specific evloop data (except for the generation index). + # + # The implementation takes advantage of the fd being unique per process and + # that the operating system will always reuse the lowest fd (POSIX compliance) + # and will only grow when the process needs that many file descriptors, so the + # allocated memory region won't grow larger than necessary. This assumption + # allows the arena to skip maintaining a list of free indexes. Some systems + # may deviate from the POSIX default, but all systems seem to follow it, as it + # allows optimizations to the OS (it can reuse already allocated resources), + # and either the man page explicitly says so (Linux), or they don't (BSD) and + # they must follow the POSIX definition. + protected class_getter arena = Arena(PollDescriptor).new(max_fds) + + private def self.max_fds : Int32 + if LibC.getrlimit(LibC::RLIMIT_NOFILE, out rlimit) == -1 + raise RuntimeError.from_errno("getrlimit(RLIMIT_NOFILE)") + end + rlimit.rlim_cur.clamp(..Int32::MAX).to_i32! + end +end + +# Polling EventLoop. +# +# This is the abstract interface that implements `Crystal::EventLoop` for +# polling based UNIX targets, such as epoll (linux), kqueue (bsd), or poll +# (posix) syscalls. This class only implements the generic parts for the +# external world to interact with the loop. A specific implementation is +# required to handle the actual syscalls. See `Crystal::Epoll::EventLoop` and +# `Crystal::Kqueue::EventLoop`. +# +# The event loop registers the fd into the kernel data structures when an IO +# operation would block, then keeps it there until the fd is closed. +# +# NOTE: the fds must have `O_NONBLOCK` set. +# +# It is possible to have multiple event loop instances, but an fd can only be in +# one instance at a time. When trying to block from another loop, the fd will be +# removed from its associated loop and added to the current one (this is +# automatic). Trying to move a fd to another loop with pending waiters is +# unsupported and will raise an exception. See `PollDescriptor#remove`. +# +# A timed event such as sleep or select timeout follows the following logic: +# +# 1. create an `Event` (actually reuses it, see `FiberChannel`); +# 2. register the event in `@timers`; +# 3. supend the current fiber. +# +# The timer will eventually trigger and resume the fiber. +# When an IO operation on fd would block, the loop follows the following logic: +# +# 1. register the fd (once); +# 2. create an `Event`; +# 3. suspend the current fiber; +# +# When the IO operation is ready, the fiber will eventually be resumed (one +# fiber at a time). If it's an IO operation, the operation is tried again which +# may block again, until the operation succeeds or an error occured (e.g. +# closed, broken pipe). +# +# If the IO operation has a timeout, the event is also registered into `@timers` +# before suspending the fiber, then after resume it will raise +# `IO::TimeoutError` if the event timed out, and continue otherwise. +abstract class Crystal::Evented::EventLoop < Crystal::EventLoop + @lock = SpinLock.new # protects parallel accesses to @timers + @timers = Timers.new + + # reset the mutexes since another thread may have acquired the lock of one + # event loop, which would prevent closing file descriptors for example. + def after_fork_before_exec : Nil + @lock = SpinLock.new + end + + {% unless flag?(:preview_mt) %} + # no parallelism issues, but let's clean-up anyway + def after_fork : Nil + @lock = SpinLock.new + end + {% end %} + + # NOTE: thread unsafe + def run(blocking : Bool) : Bool + system_run(blocking) + true + end + + # fiber interface, see Crystal::EventLoop + + def create_resume_event(fiber : Fiber) : FiberEvent + FiberEvent.new(self, fiber, :sleep) + end + + def create_timeout_event(fiber : Fiber) : FiberEvent + FiberEvent.new(self, fiber, :select_timeout) + end + + # file descriptor interface, see Crystal::EventLoop::FileDescriptor + + def read(file_descriptor : System::FileDescriptor, slice : Bytes) : Int32 + size = evented_read(file_descriptor, slice, file_descriptor.@read_timeout) + + if size == -1 + if Errno.value == Errno::EBADF + raise IO::Error.new("File not open for reading", target: file_descriptor) + else + raise IO::Error.from_errno("read", target: file_descriptor) + end + else + size.to_i32 + end + end + + def write(file_descriptor : System::FileDescriptor, slice : Bytes) : Int32 + size = evented_write(file_descriptor, slice, file_descriptor.@write_timeout) + + if size == -1 + if Errno.value == Errno::EBADF + raise IO::Error.new("File not open for writing", target: file_descriptor) + else + raise IO::Error.from_errno("write", target: file_descriptor) + end + else + size.to_i32 + end + end + + def close(file_descriptor : System::FileDescriptor) : Nil + evented_close(file_descriptor) + end + + def remove(file_descriptor : System::FileDescriptor) : Nil + internal_remove(file_descriptor) + end + + # socket interface, see Crystal::EventLoop::Socket + + def read(socket : ::Socket, slice : Bytes) : Int32 + size = evented_read(socket, slice, socket.@read_timeout) + raise IO::Error.from_errno("read", target: socket) if size == -1 + size + end + + def write(socket : ::Socket, slice : Bytes) : Int32 + size = evented_write(socket, slice, socket.@write_timeout) + raise IO::Error.from_errno("write", target: socket) if size == -1 + size + end + + def accept(socket : ::Socket) : ::Socket::Handle? + loop do + client_fd = + {% if LibC.has_method?(:accept4) %} + LibC.accept4(socket.fd, nil, nil, LibC::SOCK_CLOEXEC) + {% else %} + # we may fail to set FD_CLOEXEC between `accept` and `fcntl` but we + # can't call `Crystal::System::Socket.lock_read` because the socket + # might be in blocking mode and accept would block until the socket + # receives a connection. + # + # we could lock when `socket.blocking?` is false, but another thread + # could change the socket back to blocking mode between the condition + # check and the `accept` call. + LibC.accept(socket.fd, nil, nil).tap do |fd| + System::Socket.fcntl(fd, LibC::F_SETFD, LibC::FD_CLOEXEC) unless fd == -1 + end + {% end %} + + return client_fd unless client_fd == -1 + return if socket.closed? + + if Errno.value == Errno::EAGAIN + wait_readable(socket, socket.@read_timeout) do + raise IO::TimeoutError.new("Accept timed out") + end + return if socket.closed? + else + raise ::Socket::Error.from_errno("accept") + end + end + end + + def connect(socket : ::Socket, address : ::Socket::Addrinfo | ::Socket::Address, timeout : Time::Span?) : IO::Error? + loop do + ret = LibC.connect(socket.fd, address, address.size) + return unless ret == -1 + + case Errno.value + when Errno::EISCONN + return + when Errno::EINPROGRESS, Errno::EALREADY + wait_writable(socket, timeout) do + return IO::TimeoutError.new("Connect timed out") + end + else + return ::Socket::ConnectError.from_errno("connect") + end + end + end + + def send_to(socket : ::Socket, slice : Bytes, address : ::Socket::Address) : Int32 + bytes_sent = LibC.sendto(socket.fd, slice.to_unsafe.as(Void*), slice.size, 0, address, address.size) + raise ::Socket::Error.from_errno("Error sending datagram to #{address}") if bytes_sent == -1 + bytes_sent.to_i32 + end + + def receive_from(socket : ::Socket, slice : Bytes) : {Int32, ::Socket::Address} + sockaddr = Pointer(LibC::SockaddrStorage).malloc.as(LibC::Sockaddr*) + + # initialize sockaddr with the initialized family of the socket + copy = sockaddr.value + copy.sa_family = socket.family + sockaddr.value = copy + addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrStorage)) + + loop do + size = LibC.recvfrom(socket.fd, slice, slice.size, 0, sockaddr, pointerof(addrlen)) + if size == -1 + if Errno.value == Errno::EAGAIN + wait_readable(socket, socket.@read_timeout) + check_open(socket) + else + raise IO::Error.from_errno("recvfrom", target: socket) + end + else + return {size.to_i32, ::Socket::Address.from(sockaddr, addrlen)} + end + end + end + + def close(socket : ::Socket) : Nil + evented_close(socket) + end + + def remove(socket : ::Socket) : Nil + internal_remove(socket) + end + + # internals: IO + + private def evented_read(io, slice : Bytes, timeout : Time::Span?) : Int32 + loop do + ret = LibC.read(io.fd, slice, slice.size) + if ret == -1 && Errno.value == Errno::EAGAIN + wait_readable(io, timeout) + check_open(io) + else + return ret.to_i + end + end + end + + private def evented_write(io, slice : Bytes, timeout : Time::Span?) : Int32 + loop do + ret = LibC.write(io.fd, slice, slice.size) + if ret == -1 && Errno.value == Errno::EAGAIN + wait_writable(io, timeout) + check_open(io) + else + return ret.to_i + end + end + end + + protected def evented_close(io) + return unless (index = io.__evloop_data).valid? + + Evented.arena.free(index) do |pd| + pd.value.@readers.ready_all do |event| + pd.value.@event_loop.try(&.unsafe_resume_io(event)) + end + + pd.value.@writers.ready_all do |event| + pd.value.@event_loop.try(&.unsafe_resume_io(event)) + end + + pd.value.remove(io.fd) + end + end + + private def internal_remove(io) + return unless (index = io.__evloop_data).valid? + + Evented.arena.free(index) do |pd| + pd.value.remove(io.fd) { } # ignore system error + end + end + + private def wait_readable(io, timeout = nil) : Nil + wait_readable(io, timeout) do + raise IO::TimeoutError.new("Read timed out") + end + end + + private def wait_writable(io, timeout = nil) : Nil + wait_writable(io, timeout) do + raise IO::TimeoutError.new("Write timed out") + end + end + + private def wait_readable(io, timeout = nil, &) : Nil + yield if wait(:io_read, io, timeout) do |pd, event| + # don't wait if the waiter has already been marked ready (see Waiters#add) + return unless pd.value.@readers.add(event) + end + end + + private def wait_writable(io, timeout = nil, &) : Nil + yield if wait(:io_write, io, timeout) do |pd, event| + # don't wait if the waiter has already been marked ready (see Waiters#add) + return unless pd.value.@writers.add(event) + end + end + + private def wait(type : Evented::Event::Type, io, timeout, &) + # prepare event (on the stack); we can't initialize it properly until we get + # the arena index below; we also can't use a nilable since `pointerof` would + # point to the union, not the event + event = uninitialized Evented::Event + + # add the event to the waiting list; in case we can't access or allocate the + # poll descriptor into the arena, we merely return to let the caller handle + # the situation (maybe the IO got closed?) + if (index = io.__evloop_data).valid? + event = Evented::Event.new(type, Fiber.current, index, timeout) + + return false unless Evented.arena.get?(index) do |pd| + yield pd, pointerof(event) + end + else + # OPTIMIZE: failing to allocate may be a simple conflict with 2 fibers + # starting to read or write on the same fd, we may want to detect any + # error situation instead of returning and retrying a syscall + return false unless Evented.arena.allocate_at?(io.fd) do |pd, index| + # register the fd with the event loop (once), it should usually merely add + # the fd to the current evloop but may "transfer" the ownership from + # another event loop: + io.__evloop_data = index + pd.value.take_ownership(self, io.fd, index) + + event = Evented::Event.new(type, Fiber.current, index, timeout) + yield pd, pointerof(event) + end + end + + if event.wake_at? + add_timer(pointerof(event)) + + Fiber.suspend + + # no need to delete the timer: either it triggered which means it was + # dequeued, or `#unsafe_resume_io` was called to resume the IO and the + # timer got deleted from the timers before the fiber got reenqueued. + return event.timed_out? + end + + Fiber.suspend + false + end + + private def check_open(io : IO) + raise IO::Error.new("Closed stream") if io.closed? + end + + # internals: timers + + protected def add_timer(event : Evented::Event*) + @lock.sync do + is_next_ready = @timers.add(event) + system_set_timer(event.value.wake_at) if is_next_ready + end + end + + protected def delete_timer(event : Evented::Event*) : Bool + @lock.sync do + dequeued, was_next_ready = @timers.delete(event) + # update system timer if we deleted the next timer + system_set_timer(@timers.next_ready?) if was_next_ready + dequeued + end + end + + # Helper to resume the fiber associated to an IO event and remove the event + # from timers if applicable. Returns true if the fiber has been enqueued. + # + # Thread unsafe: we must hold the poll descriptor waiter lock for the whole + # duration of the dequeue/resume_io otherwise we might conflict with timers + # trying to cancel an IO event. + protected def unsafe_resume_io(event : Evented::Event*) : Bool + # we only partially own the poll descriptor; thanks to the lock we know that + # another thread won't dequeue it, yet it may still be in the timers queue, + # which at worst may be waiting on the lock to be released, so event* can be + # dereferenced safely. + + if !event.value.wake_at? || delete_timer(event) + # no timeout or we canceled it: we fully own the event + Crystal::Scheduler.enqueue(event.value.fiber) + true + else + # failed to cancel the timeout so the timer owns the event (by rule) + false + end + end + + # Process ready timers. + # + # Shall be called after processing IO events. IO events with a timeout that + # have succeeded shall already have been removed from `@timers` otherwise the + # fiber could be resumed twice! + private def process_timers(timer_triggered : Bool) : Nil + # collect ready timers before processing them —this is safe— to avoids a + # deadlock situation when another thread tries to process a ready IO event + # (in poll descriptor waiters) with a timeout (same event* in timers) + buffer = uninitialized StaticArray(Pointer(Evented::Event), 128) + size = 0 + + @lock.sync do + @timers.dequeue_ready do |event| + buffer.to_unsafe[size] = event + break if (size &+= 1) == buffer.size + end + + if size > 0 || timer_triggered + system_set_timer(@timers.next_ready?) + end + end + + buffer.to_slice[0, size].each do |event| + process_timer(event) + end + end + + private def process_timer(event : Evented::Event*) + # we dequeued the event from timers, and by rule we own it, so event* can + # safely be dereferenced: + fiber = event.value.fiber + + case event.value.type + when .io_read? + # reached read timeout: cancel io event; by rule the timer always wins, + # even in case of conflict with #unsafe_resume_io we must resume the fiber + Evented.arena.get?(event.value.index) { |pd| pd.value.@readers.delete(event) } + event.value.timed_out! + when .io_write? + # reached write timeout: cancel io event; by rule the timer always wins, + # even in case of conflict with #unsafe_resume_io we must resume the fiber + Evented.arena.get?(event.value.index) { |pd| pd.value.@writers.delete(event) } + event.value.timed_out! + when .select_timeout? + # always dequeue the event but only enqueue the fiber if we win the + # atomic CAS + return unless select_action = fiber.timeout_select_action + fiber.timeout_select_action = nil + return unless select_action.time_expired? + fiber.@timeout_event.as(FiberEvent).clear + when .sleep? + # cleanup + fiber.@resume_event.as(FiberEvent).clear + else + raise RuntimeError.new("BUG: unexpected event in timers: #{event.value}%s\n") + end + + Crystal::Scheduler.enqueue(fiber) + end + + # internals: system + + # Process ready events and timers. + # + # The loop must always process ready events and timers before returning. When + # *blocking* is `true` the loop must wait for events to become ready (possibly + # indefinitely); when `false` the loop shall return immediately. + # + # The `PollDescriptor` of IO events can be retrieved using the *index* + # from the system event's user data. + private abstract def system_run(blocking : Bool) : Nil + + # Add *fd* to the polling system, setting *index* as user data. + protected abstract def system_add(fd : Int32, index : Index) : Nil + + # Remove *fd* from the polling system. Must raise a `RuntimeError` on error. + # + # If *closing* is true, then it preceeds a call to `close(2)`. Some + # implementations may take advantage of close doing the book keeping. + # + # If *closing* is false then the fd must be deleted from the polling system. + protected abstract def system_del(fd : Int32, closing = true) : Nil + + # Identical to `#system_del` but yields on error. + protected abstract def system_del(fd : Int32, closing = true, &) : Nil + + # Arm a timer to interrupt a run at *time*. Set to `nil` to disarm the timer. + private abstract def system_set_timer(time : Time::Span?) : Nil +end diff --git a/src/crystal/system/unix/evented/fiber_event.cr b/src/crystal/system/unix/evented/fiber_event.cr new file mode 100644 index 000000000000..074dd67e926f --- /dev/null +++ b/src/crystal/system/unix/evented/fiber_event.cr @@ -0,0 +1,33 @@ +class Crystal::Evented::FiberEvent + include Crystal::EventLoop::Event + + def initialize(@event_loop : EventLoop, fiber : Fiber, type : Evented::Event::Type) + @event = Evented::Event.new(type, fiber) + end + + # sleep or select timeout + def add(timeout : Time::Span) : Nil + seconds, nanoseconds = System::Time.monotonic + now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds) + @event.wake_at = now + timeout + @event_loop.add_timer(pointerof(@event)) + end + + # select timeout has been cancelled + def delete : Nil + return unless @event.wake_at? + + @event.wake_at = nil + @event_loop.delete_timer(pointerof(@event)) + end + + # fiber died + def free : Nil + delete + end + + # the timer triggered (already dequeued from eventloop) + def clear : Nil + @event.wake_at = nil + end +end diff --git a/src/crystal/system/unix/evented/poll_descriptor.cr b/src/crystal/system/unix/evented/poll_descriptor.cr new file mode 100644 index 000000000000..1ef318e454bb --- /dev/null +++ b/src/crystal/system/unix/evented/poll_descriptor.cr @@ -0,0 +1,50 @@ +require "./event_loop" + +# Information related to the evloop for a fd, such as the read and write queues +# (waiting `Event`), as well as which evloop instance currently owns the fd. +# +# Thread-unsafe: parallel mutations must be protected with a lock. +struct Crystal::Evented::PollDescriptor + @event_loop : Evented::EventLoop? + @readers = Waiters.new + @writers = Waiters.new + + # Makes *event_loop* the new owner of *fd*. + # Removes *fd* from the current event loop (if any). + def take_ownership(event_loop : EventLoop, fd : Int32, index : Arena::Index) : Nil + current = @event_loop + + if event_loop == current + raise "BUG: evloop already owns the poll-descriptor for fd=#{fd}" + end + + # ensure we can't have cross enqueues after we transfer the fd, so we + # can optimize (all enqueues are local) and we don't end up with a timer + # from evloop A to cancel an event from evloop B (currently unsafe) + if current && !empty? + raise RuntimeError.new("BUG: transfering fd=#{fd} to another evloop with pending reader/writer fibers") + end + + @event_loop = event_loop + event_loop.system_add(fd, index) + current.try(&.system_del(fd, closing: false)) + end + + # Removes *fd* from its owner event loop. Raises on errors. + def remove(fd : Int32) : Nil + current, @event_loop = @event_loop, nil + current.try(&.system_del(fd)) + end + + # Same as `#remove` but yields on errors. + def remove(fd : Int32, &) : Nil + current, @event_loop = @event_loop, nil + current.try(&.system_del(fd) { yield }) + end + + # Returns true when there is at least one reader or writer. Returns false + # otherwise. + def empty? : Bool + @readers.@list.empty? && @writers.@list.empty? + end +end diff --git a/src/crystal/system/unix/evented/timers.cr b/src/crystal/system/unix/evented/timers.cr new file mode 100644 index 000000000000..ace4fefcf09b --- /dev/null +++ b/src/crystal/system/unix/evented/timers.cr @@ -0,0 +1,86 @@ +# List of `Event` ordered by `Event#wake_at` ascending. Optimized for fast +# dequeue and determining when is the next timer event. +# +# Thread unsafe: parallel accesses much be protected. +# +# NOTE: this is a struct because it only wraps a const pointer to a deque +# allocated in the heap. +# +# OPTIMIZE: consider a skiplist for faster lookups (add/delete). +# +# OPTIMIZE: we could avoid memmove on add/delete by allocating a buffer, putting +# entries at whatever available index in the buffer, and linking entries in +# order (using indices so we can realloc the buffer); we'd have to keep a list +# of free indexes, too. It could be a good combo of unbounded linked list while +# retaining some memory locality. It should even be compatible with a skiplist +# (e.g. make entries a fixed height tower instead of prev/next node). +struct Crystal::Evented::Timers + def initialize + @list = Deque(Evented::Event*).new + end + + def empty? : Bool + @list.empty? + end + + # Returns the time at which the next timer is supposed to run. + def next_ready? : Time::Span? + @list.first?.try(&.value.wake_at) + end + + # Dequeues and yields each ready timer (their `#wake_at` is lower than + # `System::Time.monotonic`) from the oldest to the most recent (i.e. time + # ascending). + def dequeue_ready(& : Evented::Event* -> Nil) : Nil + return if @list.empty? + + seconds, nanoseconds = System::Time.monotonic + now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds) + n = 0 + + @list.each do |event| + break if event.value.wake_at > now + yield event + n += 1 + end + + # OPTIMIZE: consume the n entries at once + n.times { @list.shift } + end + + # Add a new timer into the list. Returns true if it is the next ready timer. + def add(event : Evented::Event*) : Bool + if @list.empty? + @list << event + true + elsif index = lookup(event.value.wake_at) + @list.insert(index, event) + index == 0 + else + @list.push(event) + false + end + end + + private def lookup(wake_at) + @list.each_with_index do |event, index| + return index if event.value.wake_at >= wake_at + end + end + + # Remove a timer from the list. Returns a tuple(dequeued, was_next_ready) of + # booleans. The first bool tells whether the event was dequeued, in which case + # the second one tells if it was the next ready event. + def delete(event : Evented::Event*) : {Bool, Bool} + if index = @list.index(event) + @list.delete_at(index) + {true, index.zero?} + else + {false, false} + end + end + + def each(&) : Nil + @list.each { |event| yield event } + end +end diff --git a/src/crystal/system/unix/evented/waiters.cr b/src/crystal/system/unix/evented/waiters.cr new file mode 100644 index 000000000000..2d052718bae9 --- /dev/null +++ b/src/crystal/system/unix/evented/waiters.cr @@ -0,0 +1,62 @@ +require "./event" + +# A FIFO queue of `Event` waiting on the same operation (either read or write) +# for a fd. See `PollDescriptor`. +# +# Race conditions on the state of the waiting list are handled through the ready +# always ready variables. +# +# Thread unsafe: parallel mutations must be protected with a lock. +struct Crystal::Evented::Waiters + @list = PointerLinkedList(Event).new + @ready = false + @always_ready = false + + # Adds an event to the waiting list. May return false immediately if another + # thread marked the list as ready in parallel, returns true otherwise. + def add(event : Pointer(Event)) : Bool + if @always_ready + # another thread closed the fd or we received a fd error or hup event: + # the fd will never block again + return false + end + + if @ready + # another thread readied the fd before the current thread got to add + # the event: don't block and resets @ready for the next loop + @ready = false + return false + end + + @list.push(event) + true + end + + def delete(event : Pointer(Event)) : Nil + @list.delete(event) if event.value.next + end + + # Removes one pending event or marks the list as ready when there are no + # pending events (we got notified of readiness before a thread enqueued). + def ready_one(& : Pointer(Event) -> Bool) : Nil + # loop until the block succesfully processes an event (it may have to + # dequeue the timeout from timers) + loop do + if event = @list.shift? + break if yield event + else + # no event queued but another thread may be waiting for the lock to + # add an event: set as ready to resolve the race condition + @ready = true + return + end + end + end + + # Dequeues all pending events and marks the list as always ready. This must be + # called when a fd is closed or an error or hup event occurred. + def ready_all(& : Pointer(Event) ->) : Nil + @list.consume_each { |event| yield event } + @always_ready = true + end +end diff --git a/src/crystal/system/unix/eventfd.cr b/src/crystal/system/unix/eventfd.cr new file mode 100644 index 000000000000..6180bf90bf23 --- /dev/null +++ b/src/crystal/system/unix/eventfd.cr @@ -0,0 +1,31 @@ +require "c/sys/eventfd" + +struct Crystal::System::EventFD + # NOTE: no need to concern ourselves with endianness: we interpret the bytes + # in the system order and eventfd can only be used locally (no cross system + # issues). + + getter fd : Int32 + + def initialize(value = 0) + @fd = LibC.eventfd(value, LibC::EFD_CLOEXEC) + raise RuntimeError.from_errno("eventfd") if @fd == -1 + end + + def read : UInt64 + buf = uninitialized UInt8[8] + bytes_read = LibC.read(@fd, buf.to_unsafe, buf.size) + raise RuntimeError.from_errno("eventfd_read") unless bytes_read == 8 + buf.unsafe_as(UInt64) + end + + def write(value : UInt64) : Nil + buf = value.unsafe_as(StaticArray(UInt8, 8)) + bytes_written = LibC.write(@fd, buf.to_unsafe, buf.size) + raise RuntimeError.from_errno("eventfd_write") unless bytes_written == 8 + end + + def close : Nil + LibC.close(@fd) + end +end diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 60515b701136..4aa1ec580d32 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -1,5 +1,4 @@ require "c/fcntl" -require "io/evented" require "termios" {% if flag?(:android) && LibC::ANDROID_API < 28 %} require "c/sys/ioctl" @@ -7,7 +6,9 @@ require "termios" # :nodoc: module Crystal::System::FileDescriptor - include IO::Evented + {% if IO.has_constant?(:Evented) %} + include IO::Evented + {% end %} # Platform-specific type to represent a file descriptor handle to the operating # system. diff --git a/src/crystal/system/unix/kqueue.cr b/src/crystal/system/unix/kqueue.cr new file mode 100644 index 000000000000..9f7cb1f414b9 --- /dev/null +++ b/src/crystal/system/unix/kqueue.cr @@ -0,0 +1,89 @@ +require "c/sys/event" + +struct Crystal::System::Kqueue + @kq : LibC::Int + + def initialize + @kq = + {% if LibC.has_method?(:kqueue1) %} + LibC.kqueue1(LibC::O_CLOEXEC) + {% else %} + LibC.kqueue + {% end %} + if @kq == -1 + function_name = {% if LibC.has_method?(:kqueue1) %} "kqueue1" {% else %} "kqueue" {% end %} + raise RuntimeError.from_errno(function_name) + end + end + + # Helper to register a single event. Returns immediately. + def kevent(ident, filter, flags, fflags = 0, data = 0, udata = nil, &) : Nil + kevent = uninitialized LibC::Kevent + Kqueue.set pointerof(kevent), ident, filter, flags, fflags, data, udata + ret = LibC.kevent(@kq, pointerof(kevent), 1, nil, 0, nil) + yield if ret == -1 + end + + # Helper to register a single event. Returns immediately. + def kevent(ident, filter, flags, fflags = 0, data = 0, udata = nil) : Nil + kevent(ident, filter, flags, fflags, data, udata) do + raise RuntimeError.from_errno("kevent") + end + end + + # Helper to register multiple *changes*. Returns immediately. + def kevent(changes : Slice(LibC::Kevent), &) : Nil + ret = LibC.kevent(@kq, changes.to_unsafe, changes.size, nil, 0, nil) + yield if ret == -1 + end + + # Waits for registered events to become active. Returns a subslice to + # *events*. + # + # Timeout is relative to now; blocks indefinitely if `nil`; returns + # immediately if zero. + def wait(events : Slice(LibC::Kevent), timeout : ::Time::Span? = nil) : Slice(LibC::Kevent) + if timeout + ts = uninitialized LibC::Timespec + ts.tv_sec = typeof(ts.tv_sec).new!(timeout.@seconds) + ts.tv_nsec = typeof(ts.tv_nsec).new!(timeout.@nanoseconds) + tsp = pointerof(ts) + else + tsp = Pointer(LibC::Timespec).null + end + + changes = Slice(LibC::Kevent).empty + count = 0 + + loop do + count = LibC.kevent(@kq, changes.to_unsafe, changes.size, events.to_unsafe, events.size, tsp) + break unless count == -1 + + if Errno.value == Errno::EINTR + # retry when waiting indefinitely, return otherwise + break if timeout + else + raise RuntimeError.from_errno("kevent") + end + end + + events[0, count.clamp(0..)] + end + + def close : Nil + LibC.close(@kq) + end + + @[AlwaysInline] + def self.set(kevent : LibC::Kevent*, ident, filter, flags, fflags = 0, data = 0, udata = nil) : Nil + kevent.value.ident = ident + kevent.value.filter = filter + kevent.value.flags = flags + kevent.value.fflags = fflags + kevent.value.data = data + kevent.value.udata = udata ? udata.as(Void*) : Pointer(Void).null + {% if LibC::Kevent.has_method?(:ext) %} + kevent.value.ext.fill(0) + {% end %} + end +end diff --git a/src/crystal/system/unix/kqueue/event_loop.cr b/src/crystal/system/unix/kqueue/event_loop.cr new file mode 100644 index 000000000000..6eb98a7dc948 --- /dev/null +++ b/src/crystal/system/unix/kqueue/event_loop.cr @@ -0,0 +1,245 @@ +require "../evented/event_loop" +require "../kqueue" + +class Crystal::Kqueue::EventLoop < Crystal::Evented::EventLoop + # the following are arbitrary numbers to identify specific events + INTERRUPT_IDENTIFIER = 9 + TIMER_IDENTIFIER = 10 + + {% unless LibC.has_constant?(:EVFILT_USER) %} + @pipe = uninitialized LibC::Int[2] + {% end %} + + def initialize + # the kqueue instance + @kqueue = System::Kqueue.new + + # notification to interrupt a run + @interrupted = Atomic::Flag.new + + {% if LibC.has_constant?(:EVFILT_USER) %} + @kqueue.kevent( + INTERRUPT_IDENTIFIER, + LibC::EVFILT_USER, + LibC::EV_ADD | LibC::EV_ENABLE | LibC::EV_CLEAR) + {% else %} + @pipe = System::FileDescriptor.system_pipe + @kqueue.kevent(@pipe[0], LibC::EVFILT_READ, LibC::EV_ADD) + {% end %} + end + + def after_fork_before_exec : Nil + super + + # O_CLOEXEC would close these automatically but we don't want to mess with + # the parent process fds (that would mess the parent evloop) + + # kqueue isn't inherited by fork on darwin/dragonfly, but we still close + @kqueue.close + + {% unless LibC.has_constant?(:EVFILT_USER) %} + @pipe.each { |fd| LibC.close(fd) } + {% end %} + end + + {% unless flag?(:preview_mt) %} + def after_fork : Nil + super + + # kqueue isn't inherited by fork on darwin/dragonfly, but we still close + @kqueue.close + @kqueue = System::Kqueue.new + + @interrupted.clear + + {% if LibC.has_constant?(:EVFILT_USER) %} + @kqueue.kevent( + INTERRUPT_IDENTIFIER, + LibC::EVFILT_USER, + LibC::EV_ADD | LibC::EV_ENABLE | LibC::EV_CLEAR) + {% else %} + @pipe.each { |fd| LibC.close(fd) } + @pipe = System::FileDescriptor.system_pipe + @kqueue.kevent(@pipe[0], LibC::EVFILT_READ, LibC::EV_ADD) + {% end %} + + system_set_timer(@timers.next_ready?) + + # re-add all registered fds + Evented.arena.each { |fd, index| system_add(fd, index) } + end + {% end %} + + private def system_run(blocking : Bool) : Nil + buffer = uninitialized LibC::Kevent[128] + + Crystal.trace :evloop, "run", blocking: blocking ? 1 : 0 + timeout = blocking ? nil : Time::Span.zero + kevents = @kqueue.wait(buffer.to_slice, timeout) + + timer_triggered = false + + # process events + kevents.size.times do |i| + kevent = kevents.to_unsafe + i + + if process_interrupt?(kevent) + # nothing special + elsif kevent.value.filter == LibC::EVFILT_TIMER + # nothing special + timer_triggered = true + else + process_io(kevent) + end + end + + process_timers(timer_triggered) + end + + private def process_interrupt?(kevent) + {% if LibC.has_constant?(:EVFILT_USER) %} + if kevent.value.filter == LibC::EVFILT_USER + @interrupted.clear if kevent.value.ident == INTERRUPT_IDENTIFIER + return true + end + {% else %} + if kevent.value.filter == LibC::EVFILT_READ && kevent.value.ident == @pipe[0] + ident = 0 + ret = LibC.read(@pipe[0], pointerof(ident), sizeof(Int32)) + raise RuntimeError.from_errno("read") if ret == -1 + @interrupted.clear if ident == INTERRUPT_IDENTIFIER + return true + end + {% end %} + false + end + + private def process_io(kevent : LibC::Kevent*) : Nil + index = + {% if flag?(:bits64) %} + Evented::Arena::Index.new(kevent.value.udata.address) + {% else %} + # assuming 32-bit target: rebuild the arena index + Evented::Arena::Index.new(kevent.value.ident.to_i32!, kevent.value.udata.address.to_u32!) + {% end %} + + Crystal.trace :evloop, "event", fd: kevent.value.ident, index: index.to_i64, + filter: kevent.value.filter, flags: kevent.value.flags, fflags: kevent.value.fflags + + Evented.arena.get?(index) do |pd| + if (kevent.value.fflags & LibC::EV_EOF) == LibC::EV_EOF + # apparently some systems may report EOF on write with EVFILT_READ instead + # of EVFILT_WRITE, so let's wake all waiters: + pd.value.@readers.ready_all { |event| unsafe_resume_io(event) } + pd.value.@writers.ready_all { |event| unsafe_resume_io(event) } + return + end + + case kevent.value.filter + when LibC::EVFILT_READ + if (kevent.value.fflags & LibC::EV_ERROR) == LibC::EV_ERROR + # OPTIMIZE: pass errno (kevent.data) through PollDescriptor + pd.value.@readers.ready_all { |event| unsafe_resume_io(event) } + else + pd.value.@readers.ready_one { |event| unsafe_resume_io(event) } + end + when LibC::EVFILT_WRITE + if (kevent.value.fflags & LibC::EV_ERROR) == LibC::EV_ERROR + # OPTIMIZE: pass errno (kevent.data) through PollDescriptor + pd.value.@writers.ready_all { |event| unsafe_resume_io(event) } + else + pd.value.@writers.ready_one { |event| unsafe_resume_io(event) } + end + end + end + end + + def interrupt : Nil + return unless @interrupted.test_and_set + + {% if LibC.has_constant?(:EVFILT_USER) %} + @kqueue.kevent(INTERRUPT_IDENTIFIER, LibC::EVFILT_USER, 0, LibC::NOTE_TRIGGER) + {% else %} + ident = INTERRUPT_IDENTIFIER + ret = LibC.write(@pipe[1], pointerof(ident), sizeof(Int32)) + raise RuntimeError.from_errno("write") if ret == -1 + {% end %} + end + + protected def system_add(fd : Int32, index : Evented::Arena::Index) : Nil + Crystal.trace :evloop, "kevent", op: "add", fd: fd, index: index.to_i64 + + # register both read and write events + kevents = uninitialized LibC::Kevent[2] + {LibC::EVFILT_READ, LibC::EVFILT_WRITE}.each_with_index do |filter, i| + kevent = kevents.to_unsafe + i + udata = + {% if flag?(:bits64) %} + Pointer(Void).new(index.to_u64) + {% else %} + # assuming 32-bit target: pass the generation as udata (ident is the fd/index) + Pointer(Void).new(index.generation) + {% end %} + System::Kqueue.set(kevent, fd, filter, LibC::EV_ADD | LibC::EV_CLEAR, udata: udata) + end + + @kqueue.kevent(kevents.to_slice) do + raise RuntimeError.from_errno("kevent") + end + end + + protected def system_del(fd : Int32, closing = true) : Nil + system_del(fd, closing) do + raise RuntimeError.from_errno("kevent") + end + end + + protected def system_del(fd : Int32, closing = true, &) : Nil + return if closing # nothing to do: close(2) will do the cleanup + + Crystal.trace :evloop, "kevent", op: "del", fd: fd + + # unregister both read and write events + kevents = uninitialized LibC::Kevent[2] + {LibC::EVFILT_READ, LibC::EVFILT_WRITE}.each_with_index do |filter, i| + kevent = kevents.to_unsafe + i + System::Kqueue.set(kevent, fd, filter, LibC::EV_DELETE) + end + + @kqueue.kevent(kevents.to_slice) do + raise RuntimeError.from_errno("kevent") + end + end + + private def system_set_timer(time : Time::Span?) : Nil + if time + flags = LibC::EV_ADD | LibC::EV_ONESHOT | LibC::EV_CLEAR + + seconds, nanoseconds = System::Time.monotonic + now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds) + t = time - now + + data = + {% if LibC.has_constant?(:NOTE_NSECONDS) %} + t.total_nanoseconds.to_i64!.clamp(0..) + {% else %} + # legacy BSD (and DragonFly) only have millisecond precision + t.positive? ? t.total_milliseconds.to_i64!.clamp(1..) : 0 + {% end %} + else + flags = LibC::EV_DELETE + data = 0_u64 + end + + fflags = + {% if LibC.has_constant?(:NOTE_NSECONDS) %} + LibC::NOTE_NSECONDS + {% else %} + 0 + {% end %} + + @kqueue.kevent(TIMER_IDENTIFIER, LibC::EVFILT_TIMER, flags, fflags, data) do + raise RuntimeError.from_errno("kevent") unless Errno.value == Errno::ENOENT + end + end +end diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 0eb58231900e..875d834bb266 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -352,6 +352,7 @@ struct Crystal::System::Process private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor) if src_io.closed? + Crystal::EventLoop.current.remove(dst_io) dst_io.file_descriptor_close else src_io = to_real_fd(src_io) diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr index ab094d2f3094..802cb418db15 100644 --- a/src/crystal/system/unix/signal.cr +++ b/src/crystal/system/unix/signal.cr @@ -110,7 +110,10 @@ module Crystal::System::Signal # Replaces the signal pipe so the child process won't share the file # descriptors of the parent process and send it received signals. def self.after_fork - @@pipe.each(&.file_descriptor_close) + @@pipe.each do |pipe_io| + Crystal::EventLoop.current.remove(pipe_io) + pipe_io.file_descriptor_close { } + end ensure @@pipe = IO.pipe(read_blocking: false, write_blocking: true) end diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 7c39e140849c..535f37f386c0 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -1,10 +1,11 @@ require "c/netdb" require "c/netinet/tcp" require "c/sys/socket" -require "io/evented" module Crystal::System::Socket - include IO::Evented + {% if IO.has_constant?(:Evented) %} + include IO::Evented + {% end %} alias Handle = Int32 @@ -24,6 +25,9 @@ module Crystal::System::Socket end private def initialize_handle(fd) + {% if Crystal.has_constant?(:Evented) %} + @__evloop_data = Crystal::Evented::Arena::INVALID_INDEX + {% end %} end # Tries to bind the socket to a local address. diff --git a/src/crystal/system/unix/timerfd.cr b/src/crystal/system/unix/timerfd.cr new file mode 100644 index 000000000000..34edbbec7482 --- /dev/null +++ b/src/crystal/system/unix/timerfd.cr @@ -0,0 +1,33 @@ +require "c/sys/timerfd" + +struct Crystal::System::TimerFD + getter fd : Int32 + + # Create a `timerfd` instance set to the monotonic clock. + def initialize + @fd = LibC.timerfd_create(LibC::CLOCK_MONOTONIC, LibC::TFD_CLOEXEC) + raise RuntimeError.from_errno("timerfd_settime") if @fd == -1 + end + + # Arm (start) the timer to run at *time* (absolute time). + def set(time : ::Time::Span) : Nil + itimerspec = uninitialized LibC::Itimerspec + itimerspec.it_interval.tv_sec = 0 + itimerspec.it_interval.tv_nsec = 0 + itimerspec.it_value.tv_sec = typeof(itimerspec.it_value.tv_sec).new!(time.@seconds) + itimerspec.it_value.tv_nsec = typeof(itimerspec.it_value.tv_nsec).new!(time.@nanoseconds) + ret = LibC.timerfd_settime(@fd, LibC::TFD_TIMER_ABSTIME, pointerof(itimerspec), nil) + raise RuntimeError.from_errno("timerfd_settime") if ret == -1 + end + + # Disarm (stop) the timer. + def cancel : Nil + itimerspec = LibC::Itimerspec.new + ret = LibC.timerfd_settime(@fd, LibC::TFD_TIMER_ABSTIME, pointerof(itimerspec), nil) + raise RuntimeError.from_errno("timerfd_settime") if ret == -1 + end + + def close + LibC.close(@fd) + end +end diff --git a/src/crystal/tracing.cr b/src/crystal/tracing.cr index a680bfea717f..684680b10b28 100644 --- a/src/crystal/tracing.cr +++ b/src/crystal/tracing.cr @@ -7,6 +7,7 @@ module Crystal enum Section GC Sched + Evloop def self.from_id(slice) : self {% begin %} diff --git a/src/io/evented.cr b/src/io/evented.cr index d2b3a66c336f..f59aa205c543 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -1,4 +1,6 @@ -{% skip_file if flag?(:win32) %} +require "crystal/system/event_loop" + +{% skip_file unless flag?(:wasi) || Crystal.has_constant?(:LibEvent) %} require "crystal/thread_local_value" diff --git a/src/lib_c/aarch64-android/c/sys/epoll.cr b/src/lib_c/aarch64-android/c/sys/epoll.cr new file mode 100644 index 000000000000..ba34b8414732 --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/epoll.cr @@ -0,0 +1,32 @@ +lib LibC + EPOLLIN = 0x001_u32 + EPOLLOUT = 0x004_u32 + EPOLLERR = 0x008_u32 + EPOLLHUP = 0x010_u32 + EPOLLRDHUP = 0x2000_u32 + + EPOLLEXCLUSIVE = 1_u32 << 28 + EPOLLET = 1_u32 << 31 + + EPOLL_CTL_ADD = 1 + EPOLL_CTL_DEL = 2 + EPOLL_CTL_MOD = 3 + + EPOLL_CLOEXEC = 0o2000000 + + union EpollDataT + ptr : Void* + fd : Int + u32 : UInt32 + u64 : UInt64 + end + + struct EpollEvent + events : UInt32 + data : EpollDataT + end + + fun epoll_create1(Int) : Int + fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int + fun epoll_wait(Int, EpollEvent*, Int, Int) : Int +end diff --git a/src/lib_c/aarch64-android/c/sys/eventfd.cr b/src/lib_c/aarch64-android/c/sys/eventfd.cr new file mode 100644 index 000000000000..12f24428a8f4 --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/eventfd.cr @@ -0,0 +1,5 @@ +lib LibC + EFD_CLOEXEC = 0o2000000 + + fun eventfd(count : UInt, flags : Int) : Int +end diff --git a/src/lib_c/aarch64-android/c/sys/resource.cr b/src/lib_c/aarch64-android/c/sys/resource.cr index c6bfe1cf2e7b..52fe82cd446a 100644 --- a/src/lib_c/aarch64-android/c/sys/resource.cr +++ b/src/lib_c/aarch64-android/c/sys/resource.cr @@ -22,4 +22,15 @@ lib LibC RUSAGE_CHILDREN = -1 fun getrusage(__who : Int, __usage : RUsage*) : Int + + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + RLIMIT_NOFILE = 7 + + fun getrlimit(resource : Int, rlim : Rlimit*) : Int end diff --git a/src/lib_c/aarch64-android/c/sys/timerfd.cr b/src/lib_c/aarch64-android/c/sys/timerfd.cr new file mode 100644 index 000000000000..0632646b0e14 --- /dev/null +++ b/src/lib_c/aarch64-android/c/sys/timerfd.cr @@ -0,0 +1,10 @@ +require "../time" + +lib LibC + TFD_NONBLOCK = 0o0004000 + TFD_CLOEXEC = 0o2000000 + TFD_TIMER_ABSTIME = 1 << 0 + + fun timerfd_create(ClockidT, Int) : Int + fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int +end diff --git a/src/lib_c/aarch64-android/c/time.cr b/src/lib_c/aarch64-android/c/time.cr index 8f8b81291f0d..5007584d3069 100644 --- a/src/lib_c/aarch64-android/c/time.cr +++ b/src/lib_c/aarch64-android/c/time.cr @@ -23,6 +23,11 @@ lib LibC tv_nsec : Long end + struct Itimerspec + it_interval : Timespec + it_value : Timespec + end + fun clock_gettime(__clock : ClockidT, __ts : Timespec*) : Int fun clock_settime(__clock : ClockidT, __ts : Timespec*) : Int fun gmtime_r(__t : TimeT*, __tm : Tm*) : Tm* diff --git a/src/lib_c/aarch64-darwin/c/sys/event.cr b/src/lib_c/aarch64-darwin/c/sys/event.cr new file mode 100644 index 000000000000..1fd68b6d1975 --- /dev/null +++ b/src/lib_c/aarch64-darwin/c/sys/event.cr @@ -0,0 +1,31 @@ +require "../time" + +lib LibC + EVFILT_READ = -1_i16 + EVFILT_WRITE = -2_i16 + EVFILT_TIMER = -7_i16 + EVFILT_USER = -10_i16 + + EV_ADD = 0x0001_u16 + EV_DELETE = 0x0002_u16 + EV_ENABLE = 0x0004_u16 + EV_ONESHOT = 0x0010_u16 + EV_CLEAR = 0x0020_u16 + EV_EOF = 0x8000_u16 + EV_ERROR = 0x4000_u16 + + NOTE_NSECONDS = 0x00000004_u32 + NOTE_TRIGGER = 0x01000000_u32 + + struct Kevent + ident : SizeT # UintptrT + filter : Int16 + flags : UInt16 + fflags : UInt32 + data : SSizeT # IntptrT + udata : Void* + end + + fun kqueue : Int + fun kevent(kq : Int, changelist : Kevent*, nchanges : Int, eventlist : Kevent*, nevents : Int, timeout : Timespec*) : Int +end diff --git a/src/lib_c/aarch64-darwin/c/sys/resource.cr b/src/lib_c/aarch64-darwin/c/sys/resource.cr index daa583ac5895..4759e8c9b3e3 100644 --- a/src/lib_c/aarch64-darwin/c/sys/resource.cr +++ b/src/lib_c/aarch64-darwin/c/sys/resource.cr @@ -6,6 +6,8 @@ lib LibC rlim_max : RlimT end + RLIMIT_NOFILE = 8 + fun getrlimit(Int, Rlimit*) : Int RLIMIT_STACK = 3 diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/epoll.cr b/src/lib_c/aarch64-linux-gnu/c/sys/epoll.cr new file mode 100644 index 000000000000..ba34b8414732 --- /dev/null +++ b/src/lib_c/aarch64-linux-gnu/c/sys/epoll.cr @@ -0,0 +1,32 @@ +lib LibC + EPOLLIN = 0x001_u32 + EPOLLOUT = 0x004_u32 + EPOLLERR = 0x008_u32 + EPOLLHUP = 0x010_u32 + EPOLLRDHUP = 0x2000_u32 + + EPOLLEXCLUSIVE = 1_u32 << 28 + EPOLLET = 1_u32 << 31 + + EPOLL_CTL_ADD = 1 + EPOLL_CTL_DEL = 2 + EPOLL_CTL_MOD = 3 + + EPOLL_CLOEXEC = 0o2000000 + + union EpollDataT + ptr : Void* + fd : Int + u32 : UInt32 + u64 : UInt64 + end + + struct EpollEvent + events : UInt32 + data : EpollDataT + end + + fun epoll_create1(Int) : Int + fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int + fun epoll_wait(Int, EpollEvent*, Int, Int) : Int +end diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/eventfd.cr b/src/lib_c/aarch64-linux-gnu/c/sys/eventfd.cr new file mode 100644 index 000000000000..12f24428a8f4 --- /dev/null +++ b/src/lib_c/aarch64-linux-gnu/c/sys/eventfd.cr @@ -0,0 +1,5 @@ +lib LibC + EFD_CLOEXEC = 0o2000000 + + fun eventfd(count : UInt, flags : Int) : Int +end diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr b/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr index a0900a4730c4..444c4ba692c8 100644 --- a/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr +++ b/src/lib_c/aarch64-linux-gnu/c/sys/resource.cr @@ -22,4 +22,15 @@ lib LibC RUSAGE_CHILDREN = -1 fun getrusage(who : Int, usage : RUsage*) : Int + + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + RLIMIT_NOFILE = 7 + + fun getrlimit(resource : Int, rlim : Rlimit*) : Int end diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/timerfd.cr b/src/lib_c/aarch64-linux-gnu/c/sys/timerfd.cr new file mode 100644 index 000000000000..0632646b0e14 --- /dev/null +++ b/src/lib_c/aarch64-linux-gnu/c/sys/timerfd.cr @@ -0,0 +1,10 @@ +require "../time" + +lib LibC + TFD_NONBLOCK = 0o0004000 + TFD_CLOEXEC = 0o2000000 + TFD_TIMER_ABSTIME = 1 << 0 + + fun timerfd_create(ClockidT, Int) : Int + fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int +end diff --git a/src/lib_c/aarch64-linux-gnu/c/time.cr b/src/lib_c/aarch64-linux-gnu/c/time.cr index 710d477e269b..d00579281b41 100644 --- a/src/lib_c/aarch64-linux-gnu/c/time.cr +++ b/src/lib_c/aarch64-linux-gnu/c/time.cr @@ -23,6 +23,11 @@ lib LibC tv_nsec : Long end + struct Itimerspec + it_interval : Timespec + it_value : Timespec + end + fun clock_gettime(clock_id : ClockidT, tp : Timespec*) : Int fun clock_settime(clock_id : ClockidT, tp : Timespec*) : Int fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm* diff --git a/src/lib_c/aarch64-linux-musl/c/sys/epoll.cr b/src/lib_c/aarch64-linux-musl/c/sys/epoll.cr new file mode 100644 index 000000000000..ba34b8414732 --- /dev/null +++ b/src/lib_c/aarch64-linux-musl/c/sys/epoll.cr @@ -0,0 +1,32 @@ +lib LibC + EPOLLIN = 0x001_u32 + EPOLLOUT = 0x004_u32 + EPOLLERR = 0x008_u32 + EPOLLHUP = 0x010_u32 + EPOLLRDHUP = 0x2000_u32 + + EPOLLEXCLUSIVE = 1_u32 << 28 + EPOLLET = 1_u32 << 31 + + EPOLL_CTL_ADD = 1 + EPOLL_CTL_DEL = 2 + EPOLL_CTL_MOD = 3 + + EPOLL_CLOEXEC = 0o2000000 + + union EpollDataT + ptr : Void* + fd : Int + u32 : UInt32 + u64 : UInt64 + end + + struct EpollEvent + events : UInt32 + data : EpollDataT + end + + fun epoll_create1(Int) : Int + fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int + fun epoll_wait(Int, EpollEvent*, Int, Int) : Int +end diff --git a/src/lib_c/aarch64-linux-musl/c/sys/eventfd.cr b/src/lib_c/aarch64-linux-musl/c/sys/eventfd.cr new file mode 100644 index 000000000000..12f24428a8f4 --- /dev/null +++ b/src/lib_c/aarch64-linux-musl/c/sys/eventfd.cr @@ -0,0 +1,5 @@ +lib LibC + EFD_CLOEXEC = 0o2000000 + + fun eventfd(count : UInt, flags : Int) : Int +end diff --git a/src/lib_c/aarch64-linux-musl/c/sys/resource.cr b/src/lib_c/aarch64-linux-musl/c/sys/resource.cr index daa583ac5895..656e43cb0379 100644 --- a/src/lib_c/aarch64-linux-musl/c/sys/resource.cr +++ b/src/lib_c/aarch64-linux-musl/c/sys/resource.cr @@ -6,6 +6,8 @@ lib LibC rlim_max : RlimT end + RLIMIT_NOFILE = 7 + fun getrlimit(Int, Rlimit*) : Int RLIMIT_STACK = 3 diff --git a/src/lib_c/aarch64-linux-musl/c/sys/timerfd.cr b/src/lib_c/aarch64-linux-musl/c/sys/timerfd.cr new file mode 100644 index 000000000000..0632646b0e14 --- /dev/null +++ b/src/lib_c/aarch64-linux-musl/c/sys/timerfd.cr @@ -0,0 +1,10 @@ +require "../time" + +lib LibC + TFD_NONBLOCK = 0o0004000 + TFD_CLOEXEC = 0o2000000 + TFD_TIMER_ABSTIME = 1 << 0 + + fun timerfd_create(ClockidT, Int) : Int + fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int +end diff --git a/src/lib_c/aarch64-linux-musl/c/time.cr b/src/lib_c/aarch64-linux-musl/c/time.cr index f687c8b35db4..4bf25a7f9efc 100644 --- a/src/lib_c/aarch64-linux-musl/c/time.cr +++ b/src/lib_c/aarch64-linux-musl/c/time.cr @@ -23,6 +23,11 @@ lib LibC tv_nsec : Long end + struct Itimerspec + it_interval : Timespec + it_value : Timespec + end + fun clock_gettime(x0 : ClockidT, x1 : Timespec*) : Int fun clock_settime(x0 : ClockidT, x1 : Timespec*) : Int fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/epoll.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/epoll.cr new file mode 100644 index 000000000000..ba34b8414732 --- /dev/null +++ b/src/lib_c/arm-linux-gnueabihf/c/sys/epoll.cr @@ -0,0 +1,32 @@ +lib LibC + EPOLLIN = 0x001_u32 + EPOLLOUT = 0x004_u32 + EPOLLERR = 0x008_u32 + EPOLLHUP = 0x010_u32 + EPOLLRDHUP = 0x2000_u32 + + EPOLLEXCLUSIVE = 1_u32 << 28 + EPOLLET = 1_u32 << 31 + + EPOLL_CTL_ADD = 1 + EPOLL_CTL_DEL = 2 + EPOLL_CTL_MOD = 3 + + EPOLL_CLOEXEC = 0o2000000 + + union EpollDataT + ptr : Void* + fd : Int + u32 : UInt32 + u64 : UInt64 + end + + struct EpollEvent + events : UInt32 + data : EpollDataT + end + + fun epoll_create1(Int) : Int + fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int + fun epoll_wait(Int, EpollEvent*, Int, Int) : Int +end diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/eventfd.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/eventfd.cr new file mode 100644 index 000000000000..12f24428a8f4 --- /dev/null +++ b/src/lib_c/arm-linux-gnueabihf/c/sys/eventfd.cr @@ -0,0 +1,5 @@ +lib LibC + EFD_CLOEXEC = 0o2000000 + + fun eventfd(count : UInt, flags : Int) : Int +end diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/resource.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/resource.cr index 7f550c37a622..1c2c2fb678f5 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/sys/resource.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/sys/resource.cr @@ -22,4 +22,15 @@ lib LibC RUSAGE_CHILDREN = -1 fun getrusage(who : Int, usage : RUsage*) : Int16 + + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + RLIMIT_NOFILE = 7 + + fun getrlimit(resource : Int, rlim : Rlimit*) : Int end diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/timerfd.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/timerfd.cr new file mode 100644 index 000000000000..0632646b0e14 --- /dev/null +++ b/src/lib_c/arm-linux-gnueabihf/c/sys/timerfd.cr @@ -0,0 +1,10 @@ +require "../time" + +lib LibC + TFD_NONBLOCK = 0o0004000 + TFD_CLOEXEC = 0o2000000 + TFD_TIMER_ABSTIME = 1 << 0 + + fun timerfd_create(ClockidT, Int) : Int + fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int +end diff --git a/src/lib_c/arm-linux-gnueabihf/c/time.cr b/src/lib_c/arm-linux-gnueabihf/c/time.cr index 710d477e269b..d00579281b41 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/time.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/time.cr @@ -23,6 +23,11 @@ lib LibC tv_nsec : Long end + struct Itimerspec + it_interval : Timespec + it_value : Timespec + end + fun clock_gettime(clock_id : ClockidT, tp : Timespec*) : Int fun clock_settime(clock_id : ClockidT, tp : Timespec*) : Int fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm* diff --git a/src/lib_c/i386-linux-gnu/c/sys/epoll.cr b/src/lib_c/i386-linux-gnu/c/sys/epoll.cr new file mode 100644 index 000000000000..ba34b8414732 --- /dev/null +++ b/src/lib_c/i386-linux-gnu/c/sys/epoll.cr @@ -0,0 +1,32 @@ +lib LibC + EPOLLIN = 0x001_u32 + EPOLLOUT = 0x004_u32 + EPOLLERR = 0x008_u32 + EPOLLHUP = 0x010_u32 + EPOLLRDHUP = 0x2000_u32 + + EPOLLEXCLUSIVE = 1_u32 << 28 + EPOLLET = 1_u32 << 31 + + EPOLL_CTL_ADD = 1 + EPOLL_CTL_DEL = 2 + EPOLL_CTL_MOD = 3 + + EPOLL_CLOEXEC = 0o2000000 + + union EpollDataT + ptr : Void* + fd : Int + u32 : UInt32 + u64 : UInt64 + end + + struct EpollEvent + events : UInt32 + data : EpollDataT + end + + fun epoll_create1(Int) : Int + fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int + fun epoll_wait(Int, EpollEvent*, Int, Int) : Int +end diff --git a/src/lib_c/i386-linux-gnu/c/sys/eventfd.cr b/src/lib_c/i386-linux-gnu/c/sys/eventfd.cr new file mode 100644 index 000000000000..12f24428a8f4 --- /dev/null +++ b/src/lib_c/i386-linux-gnu/c/sys/eventfd.cr @@ -0,0 +1,5 @@ +lib LibC + EFD_CLOEXEC = 0o2000000 + + fun eventfd(count : UInt, flags : Int) : Int +end diff --git a/src/lib_c/i386-linux-gnu/c/sys/resource.cr b/src/lib_c/i386-linux-gnu/c/sys/resource.cr index a0900a4730c4..444c4ba692c8 100644 --- a/src/lib_c/i386-linux-gnu/c/sys/resource.cr +++ b/src/lib_c/i386-linux-gnu/c/sys/resource.cr @@ -22,4 +22,15 @@ lib LibC RUSAGE_CHILDREN = -1 fun getrusage(who : Int, usage : RUsage*) : Int + + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + RLIMIT_NOFILE = 7 + + fun getrlimit(resource : Int, rlim : Rlimit*) : Int end diff --git a/src/lib_c/i386-linux-gnu/c/sys/timerfd.cr b/src/lib_c/i386-linux-gnu/c/sys/timerfd.cr new file mode 100644 index 000000000000..0632646b0e14 --- /dev/null +++ b/src/lib_c/i386-linux-gnu/c/sys/timerfd.cr @@ -0,0 +1,10 @@ +require "../time" + +lib LibC + TFD_NONBLOCK = 0o0004000 + TFD_CLOEXEC = 0o2000000 + TFD_TIMER_ABSTIME = 1 << 0 + + fun timerfd_create(ClockidT, Int) : Int + fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int +end diff --git a/src/lib_c/i386-linux-gnu/c/time.cr b/src/lib_c/i386-linux-gnu/c/time.cr index 710d477e269b..d00579281b41 100644 --- a/src/lib_c/i386-linux-gnu/c/time.cr +++ b/src/lib_c/i386-linux-gnu/c/time.cr @@ -23,6 +23,11 @@ lib LibC tv_nsec : Long end + struct Itimerspec + it_interval : Timespec + it_value : Timespec + end + fun clock_gettime(clock_id : ClockidT, tp : Timespec*) : Int fun clock_settime(clock_id : ClockidT, tp : Timespec*) : Int fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm* diff --git a/src/lib_c/i386-linux-musl/c/sys/epoll.cr b/src/lib_c/i386-linux-musl/c/sys/epoll.cr new file mode 100644 index 000000000000..ba34b8414732 --- /dev/null +++ b/src/lib_c/i386-linux-musl/c/sys/epoll.cr @@ -0,0 +1,32 @@ +lib LibC + EPOLLIN = 0x001_u32 + EPOLLOUT = 0x004_u32 + EPOLLERR = 0x008_u32 + EPOLLHUP = 0x010_u32 + EPOLLRDHUP = 0x2000_u32 + + EPOLLEXCLUSIVE = 1_u32 << 28 + EPOLLET = 1_u32 << 31 + + EPOLL_CTL_ADD = 1 + EPOLL_CTL_DEL = 2 + EPOLL_CTL_MOD = 3 + + EPOLL_CLOEXEC = 0o2000000 + + union EpollDataT + ptr : Void* + fd : Int + u32 : UInt32 + u64 : UInt64 + end + + struct EpollEvent + events : UInt32 + data : EpollDataT + end + + fun epoll_create1(Int) : Int + fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int + fun epoll_wait(Int, EpollEvent*, Int, Int) : Int +end diff --git a/src/lib_c/i386-linux-musl/c/sys/eventfd.cr b/src/lib_c/i386-linux-musl/c/sys/eventfd.cr new file mode 100644 index 000000000000..12f24428a8f4 --- /dev/null +++ b/src/lib_c/i386-linux-musl/c/sys/eventfd.cr @@ -0,0 +1,5 @@ +lib LibC + EFD_CLOEXEC = 0o2000000 + + fun eventfd(count : UInt, flags : Int) : Int +end diff --git a/src/lib_c/i386-linux-musl/c/sys/resource.cr b/src/lib_c/i386-linux-musl/c/sys/resource.cr index daa583ac5895..656e43cb0379 100644 --- a/src/lib_c/i386-linux-musl/c/sys/resource.cr +++ b/src/lib_c/i386-linux-musl/c/sys/resource.cr @@ -6,6 +6,8 @@ lib LibC rlim_max : RlimT end + RLIMIT_NOFILE = 7 + fun getrlimit(Int, Rlimit*) : Int RLIMIT_STACK = 3 diff --git a/src/lib_c/i386-linux-musl/c/sys/timerfd.cr b/src/lib_c/i386-linux-musl/c/sys/timerfd.cr new file mode 100644 index 000000000000..0632646b0e14 --- /dev/null +++ b/src/lib_c/i386-linux-musl/c/sys/timerfd.cr @@ -0,0 +1,10 @@ +require "../time" + +lib LibC + TFD_NONBLOCK = 0o0004000 + TFD_CLOEXEC = 0o2000000 + TFD_TIMER_ABSTIME = 1 << 0 + + fun timerfd_create(ClockidT, Int) : Int + fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int +end diff --git a/src/lib_c/i386-linux-musl/c/time.cr b/src/lib_c/i386-linux-musl/c/time.cr index f687c8b35db4..4bf25a7f9efc 100644 --- a/src/lib_c/i386-linux-musl/c/time.cr +++ b/src/lib_c/i386-linux-musl/c/time.cr @@ -23,6 +23,11 @@ lib LibC tv_nsec : Long end + struct Itimerspec + it_interval : Timespec + it_value : Timespec + end + fun clock_gettime(x0 : ClockidT, x1 : Timespec*) : Int fun clock_settime(x0 : ClockidT, x1 : Timespec*) : Int fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* diff --git a/src/lib_c/x86_64-darwin/c/sys/event.cr b/src/lib_c/x86_64-darwin/c/sys/event.cr new file mode 100644 index 000000000000..1fd68b6d1975 --- /dev/null +++ b/src/lib_c/x86_64-darwin/c/sys/event.cr @@ -0,0 +1,31 @@ +require "../time" + +lib LibC + EVFILT_READ = -1_i16 + EVFILT_WRITE = -2_i16 + EVFILT_TIMER = -7_i16 + EVFILT_USER = -10_i16 + + EV_ADD = 0x0001_u16 + EV_DELETE = 0x0002_u16 + EV_ENABLE = 0x0004_u16 + EV_ONESHOT = 0x0010_u16 + EV_CLEAR = 0x0020_u16 + EV_EOF = 0x8000_u16 + EV_ERROR = 0x4000_u16 + + NOTE_NSECONDS = 0x00000004_u32 + NOTE_TRIGGER = 0x01000000_u32 + + struct Kevent + ident : SizeT # UintptrT + filter : Int16 + flags : UInt16 + fflags : UInt32 + data : SSizeT # IntptrT + udata : Void* + end + + fun kqueue : Int + fun kevent(kq : Int, changelist : Kevent*, nchanges : Int, eventlist : Kevent*, nevents : Int, timeout : Timespec*) : Int +end diff --git a/src/lib_c/x86_64-darwin/c/sys/resource.cr b/src/lib_c/x86_64-darwin/c/sys/resource.cr index daa583ac5895..4759e8c9b3e3 100644 --- a/src/lib_c/x86_64-darwin/c/sys/resource.cr +++ b/src/lib_c/x86_64-darwin/c/sys/resource.cr @@ -6,6 +6,8 @@ lib LibC rlim_max : RlimT end + RLIMIT_NOFILE = 8 + fun getrlimit(Int, Rlimit*) : Int RLIMIT_STACK = 3 diff --git a/src/lib_c/x86_64-dragonfly/c/sys/event.cr b/src/lib_c/x86_64-dragonfly/c/sys/event.cr new file mode 100644 index 000000000000..aff6274b8fd1 --- /dev/null +++ b/src/lib_c/x86_64-dragonfly/c/sys/event.cr @@ -0,0 +1,30 @@ +require "../time" + +lib LibC + EVFILT_READ = -1_i16 + EVFILT_WRITE = -2_i16 + EVFILT_TIMER = -7_i16 + EVFILT_USER = -9_i16 + + EV_ADD = 0x0001_u16 + EV_DELETE = 0x0002_u16 + EV_ENABLE = 0x0004_u16 + EV_ONESHOT = 0x0010_u16 + EV_CLEAR = 0x0020_u16 + EV_EOF = 0x8000_u16 + EV_ERROR = 0x4000_u16 + + NOTE_TRIGGER = 0x01000000_u32 + + struct Kevent + ident : SizeT # UintptrT + filter : Short + flags : UShort + fflags : UInt + data : SSizeT # IntptrT + udata : Void* + end + + fun kqueue : Int + fun kevent(kq : Int, changelist : Kevent*, nchanges : Int, eventlist : Kevent*, nevents : Int, timeout : Timespec*) : Int +end diff --git a/src/lib_c/x86_64-dragonfly/c/sys/resource.cr b/src/lib_c/x86_64-dragonfly/c/sys/resource.cr index d52182f69bce..388b52651f21 100644 --- a/src/lib_c/x86_64-dragonfly/c/sys/resource.cr +++ b/src/lib_c/x86_64-dragonfly/c/sys/resource.cr @@ -22,4 +22,15 @@ lib LibC RUSAGE_CHILDREN = -1 fun getrusage(who : Int, usage : RUsage*) : Int + + alias RlimT = UInt64 + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + RLIMIT_NOFILE = 8 + + fun getrlimit(resource : Int, rlim : Rlimit*) : Int end diff --git a/src/lib_c/x86_64-freebsd/c/sys/event.cr b/src/lib_c/x86_64-freebsd/c/sys/event.cr new file mode 100644 index 000000000000..0abe0686aba0 --- /dev/null +++ b/src/lib_c/x86_64-freebsd/c/sys/event.cr @@ -0,0 +1,32 @@ +require "../time" + +lib LibC + EVFILT_READ = -1_i16 + EVFILT_WRITE = -2_i16 + EVFILT_TIMER = -7_i16 + EVFILT_USER = -11_i16 + + EV_ADD = 0x0001_u16 + EV_DELETE = 0x0002_u16 + EV_ENABLE = 0x0004_u16 + EV_ONESHOT = 0x0010_u16 + EV_CLEAR = 0x0020_u16 + EV_EOF = 0x8000_u16 + EV_ERROR = 0x4000_u16 + + NOTE_NSECONDS = 0x00000008_u32 + NOTE_TRIGGER = 0x01000000_u32 + + struct Kevent + ident : SizeT # UintptrT + filter : Short + flags : UShort + fflags : UInt + data : Int64 + udata : Void* + ext : UInt64[4] + end + + fun kqueue1(flags : Int) : Int + fun kevent(kq : Int, changelist : Kevent*, nchanges : Int, eventlist : Kevent*, nevents : Int, timeout : Timespec*) : Int +end diff --git a/src/lib_c/x86_64-freebsd/c/sys/resource.cr b/src/lib_c/x86_64-freebsd/c/sys/resource.cr index 7f550c37a622..6f078dda986d 100644 --- a/src/lib_c/x86_64-freebsd/c/sys/resource.cr +++ b/src/lib_c/x86_64-freebsd/c/sys/resource.cr @@ -22,4 +22,15 @@ lib LibC RUSAGE_CHILDREN = -1 fun getrusage(who : Int, usage : RUsage*) : Int16 + + alias RlimT = UInt64 + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + RLIMIT_NOFILE = 8 + + fun getrlimit(resource : Int, rlim : Rlimit*) : Int end diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/epoll.cr b/src/lib_c/x86_64-linux-gnu/c/sys/epoll.cr new file mode 100644 index 000000000000..4dc752f64652 --- /dev/null +++ b/src/lib_c/x86_64-linux-gnu/c/sys/epoll.cr @@ -0,0 +1,33 @@ +lib LibC + EPOLLIN = 0x001_u32 + EPOLLOUT = 0x004_u32 + EPOLLERR = 0x008_u32 + EPOLLHUP = 0x010_u32 + EPOLLRDHUP = 0x2000_u32 + + EPOLLEXCLUSIVE = 1_u32 << 28 + EPOLLET = 1_u32 << 31 + + EPOLL_CTL_ADD = 1 + EPOLL_CTL_DEL = 2 + EPOLL_CTL_MOD = 3 + + EPOLL_CLOEXEC = 0o2000000 + + union EpollDataT + ptr : Void* + fd : Int + u32 : UInt32 + u64 : UInt64 + end + + @[Packed] + struct EpollEvent + events : UInt32 + data : EpollDataT + end + + fun epoll_create1(Int) : Int + fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int + fun epoll_wait(Int, EpollEvent*, Int, Int) : Int +end diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/eventfd.cr b/src/lib_c/x86_64-linux-gnu/c/sys/eventfd.cr new file mode 100644 index 000000000000..12f24428a8f4 --- /dev/null +++ b/src/lib_c/x86_64-linux-gnu/c/sys/eventfd.cr @@ -0,0 +1,5 @@ +lib LibC + EFD_CLOEXEC = 0o2000000 + + fun eventfd(count : UInt, flags : Int) : Int +end diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr b/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr index a0900a4730c4..444c4ba692c8 100644 --- a/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr +++ b/src/lib_c/x86_64-linux-gnu/c/sys/resource.cr @@ -22,4 +22,15 @@ lib LibC RUSAGE_CHILDREN = -1 fun getrusage(who : Int, usage : RUsage*) : Int + + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + RLIMIT_NOFILE = 7 + + fun getrlimit(resource : Int, rlim : Rlimit*) : Int end diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/timerfd.cr b/src/lib_c/x86_64-linux-gnu/c/sys/timerfd.cr new file mode 100644 index 000000000000..0632646b0e14 --- /dev/null +++ b/src/lib_c/x86_64-linux-gnu/c/sys/timerfd.cr @@ -0,0 +1,10 @@ +require "../time" + +lib LibC + TFD_NONBLOCK = 0o0004000 + TFD_CLOEXEC = 0o2000000 + TFD_TIMER_ABSTIME = 1 << 0 + + fun timerfd_create(ClockidT, Int) : Int + fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int +end diff --git a/src/lib_c/x86_64-linux-gnu/c/time.cr b/src/lib_c/x86_64-linux-gnu/c/time.cr index 710d477e269b..d00579281b41 100644 --- a/src/lib_c/x86_64-linux-gnu/c/time.cr +++ b/src/lib_c/x86_64-linux-gnu/c/time.cr @@ -23,6 +23,11 @@ lib LibC tv_nsec : Long end + struct Itimerspec + it_interval : Timespec + it_value : Timespec + end + fun clock_gettime(clock_id : ClockidT, tp : Timespec*) : Int fun clock_settime(clock_id : ClockidT, tp : Timespec*) : Int fun gmtime_r(timer : TimeT*, tp : Tm*) : Tm* diff --git a/src/lib_c/x86_64-linux-musl/c/sys/epoll.cr b/src/lib_c/x86_64-linux-musl/c/sys/epoll.cr new file mode 100644 index 000000000000..4dc752f64652 --- /dev/null +++ b/src/lib_c/x86_64-linux-musl/c/sys/epoll.cr @@ -0,0 +1,33 @@ +lib LibC + EPOLLIN = 0x001_u32 + EPOLLOUT = 0x004_u32 + EPOLLERR = 0x008_u32 + EPOLLHUP = 0x010_u32 + EPOLLRDHUP = 0x2000_u32 + + EPOLLEXCLUSIVE = 1_u32 << 28 + EPOLLET = 1_u32 << 31 + + EPOLL_CTL_ADD = 1 + EPOLL_CTL_DEL = 2 + EPOLL_CTL_MOD = 3 + + EPOLL_CLOEXEC = 0o2000000 + + union EpollDataT + ptr : Void* + fd : Int + u32 : UInt32 + u64 : UInt64 + end + + @[Packed] + struct EpollEvent + events : UInt32 + data : EpollDataT + end + + fun epoll_create1(Int) : Int + fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int + fun epoll_wait(Int, EpollEvent*, Int, Int) : Int +end diff --git a/src/lib_c/x86_64-linux-musl/c/sys/eventfd.cr b/src/lib_c/x86_64-linux-musl/c/sys/eventfd.cr new file mode 100644 index 000000000000..12f24428a8f4 --- /dev/null +++ b/src/lib_c/x86_64-linux-musl/c/sys/eventfd.cr @@ -0,0 +1,5 @@ +lib LibC + EFD_CLOEXEC = 0o2000000 + + fun eventfd(count : UInt, flags : Int) : Int +end diff --git a/src/lib_c/x86_64-linux-musl/c/sys/resource.cr b/src/lib_c/x86_64-linux-musl/c/sys/resource.cr index daa583ac5895..656e43cb0379 100644 --- a/src/lib_c/x86_64-linux-musl/c/sys/resource.cr +++ b/src/lib_c/x86_64-linux-musl/c/sys/resource.cr @@ -6,6 +6,8 @@ lib LibC rlim_max : RlimT end + RLIMIT_NOFILE = 7 + fun getrlimit(Int, Rlimit*) : Int RLIMIT_STACK = 3 diff --git a/src/lib_c/x86_64-linux-musl/c/sys/timerfd.cr b/src/lib_c/x86_64-linux-musl/c/sys/timerfd.cr new file mode 100644 index 000000000000..0632646b0e14 --- /dev/null +++ b/src/lib_c/x86_64-linux-musl/c/sys/timerfd.cr @@ -0,0 +1,10 @@ +require "../time" + +lib LibC + TFD_NONBLOCK = 0o0004000 + TFD_CLOEXEC = 0o2000000 + TFD_TIMER_ABSTIME = 1 << 0 + + fun timerfd_create(ClockidT, Int) : Int + fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int +end diff --git a/src/lib_c/x86_64-linux-musl/c/time.cr b/src/lib_c/x86_64-linux-musl/c/time.cr index f687c8b35db4..4bf25a7f9efc 100644 --- a/src/lib_c/x86_64-linux-musl/c/time.cr +++ b/src/lib_c/x86_64-linux-musl/c/time.cr @@ -23,6 +23,11 @@ lib LibC tv_nsec : Long end + struct Itimerspec + it_interval : Timespec + it_value : Timespec + end + fun clock_gettime(x0 : ClockidT, x1 : Timespec*) : Int fun clock_settime(x0 : ClockidT, x1 : Timespec*) : Int fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* diff --git a/src/lib_c/x86_64-netbsd/c/sys/event.cr b/src/lib_c/x86_64-netbsd/c/sys/event.cr new file mode 100644 index 000000000000..91da3cea1a04 --- /dev/null +++ b/src/lib_c/x86_64-netbsd/c/sys/event.cr @@ -0,0 +1,32 @@ +require "../time" + +lib LibC + EVFILT_READ = 0_u32 + EVFILT_WRITE = 1_u32 + EVFILT_TIMER = 6_u32 + EVFILT_USER = 8_u32 + + EV_ADD = 0x0001_u32 + EV_DELETE = 0x0002_u32 + EV_ENABLE = 0x0004_u16 + EV_ONESHOT = 0x0010_u32 + EV_CLEAR = 0x0020_u32 + EV_EOF = 0x8000_u32 + EV_ERROR = 0x4000_u32 + + NOTE_NSECONDS = 0x00000003_u32 + NOTE_TRIGGER = 0x01000000_u32 + + struct Kevent + ident : SizeT # UintptrT + filter : UInt32 + flags : UInt32 + fflags : UInt32 + data : Int64 + udata : Void* + ext : UInt64[4] + end + + fun kqueue1(flags : Int) : Int + fun kevent = __kevent50(kq : Int, changelist : Kevent*, nchanges : SizeT, eventlist : Kevent*, nevents : SizeT, timeout : Timespec*) : Int +end diff --git a/src/lib_c/x86_64-netbsd/c/sys/resource.cr b/src/lib_c/x86_64-netbsd/c/sys/resource.cr index d52182f69bce..388b52651f21 100644 --- a/src/lib_c/x86_64-netbsd/c/sys/resource.cr +++ b/src/lib_c/x86_64-netbsd/c/sys/resource.cr @@ -22,4 +22,15 @@ lib LibC RUSAGE_CHILDREN = -1 fun getrusage(who : Int, usage : RUsage*) : Int + + alias RlimT = UInt64 + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + RLIMIT_NOFILE = 8 + + fun getrlimit(resource : Int, rlim : Rlimit*) : Int end diff --git a/src/lib_c/x86_64-openbsd/c/sys/event.cr b/src/lib_c/x86_64-openbsd/c/sys/event.cr new file mode 100644 index 000000000000..b95764cb7f54 --- /dev/null +++ b/src/lib_c/x86_64-openbsd/c/sys/event.cr @@ -0,0 +1,28 @@ +require "../time" + +lib LibC + EVFILT_READ = -1_i16 + EVFILT_WRITE = -2_i16 + EVFILT_TIMER = -7_i16 + + EV_ADD = 0x0001_u16 + EV_DELETE = 0x0002_u16 + EV_ONESHOT = 0x0010_u16 + EV_CLEAR = 0x0020_u16 + EV_EOF = 0x8000_u16 + EV_ERROR = 0x4000_u16 + + NOTE_NSECONDS = 0x00000003_u32 + + struct Kevent + ident : SizeT # UintptrT + filter : Short + flags : UShort + fflags : UInt + data : Int64 + udata : Void* + end + + fun kqueue1(flags : Int) : Int + fun kevent(kq : Int, changelist : Kevent*, nchanges : Int, eventlist : Kevent*, nevents : Int, timeout : Timespec*) : Int +end diff --git a/src/lib_c/x86_64-openbsd/c/sys/resource.cr b/src/lib_c/x86_64-openbsd/c/sys/resource.cr index 7f550c37a622..6f078dda986d 100644 --- a/src/lib_c/x86_64-openbsd/c/sys/resource.cr +++ b/src/lib_c/x86_64-openbsd/c/sys/resource.cr @@ -22,4 +22,15 @@ lib LibC RUSAGE_CHILDREN = -1 fun getrusage(who : Int, usage : RUsage*) : Int16 + + alias RlimT = UInt64 + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + RLIMIT_NOFILE = 8 + + fun getrlimit(resource : Int, rlim : Rlimit*) : Int end diff --git a/src/lib_c/x86_64-solaris/c/sys/epoll.cr b/src/lib_c/x86_64-solaris/c/sys/epoll.cr new file mode 100644 index 000000000000..4dc752f64652 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/epoll.cr @@ -0,0 +1,33 @@ +lib LibC + EPOLLIN = 0x001_u32 + EPOLLOUT = 0x004_u32 + EPOLLERR = 0x008_u32 + EPOLLHUP = 0x010_u32 + EPOLLRDHUP = 0x2000_u32 + + EPOLLEXCLUSIVE = 1_u32 << 28 + EPOLLET = 1_u32 << 31 + + EPOLL_CTL_ADD = 1 + EPOLL_CTL_DEL = 2 + EPOLL_CTL_MOD = 3 + + EPOLL_CLOEXEC = 0o2000000 + + union EpollDataT + ptr : Void* + fd : Int + u32 : UInt32 + u64 : UInt64 + end + + @[Packed] + struct EpollEvent + events : UInt32 + data : EpollDataT + end + + fun epoll_create1(Int) : Int + fun epoll_ctl(Int, Int, Int, EpollEvent*) : Int + fun epoll_wait(Int, EpollEvent*, Int, Int) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/sys/eventfd.cr b/src/lib_c/x86_64-solaris/c/sys/eventfd.cr new file mode 100644 index 000000000000..12f24428a8f4 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/eventfd.cr @@ -0,0 +1,5 @@ +lib LibC + EFD_CLOEXEC = 0o2000000 + + fun eventfd(count : UInt, flags : Int) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/sys/resource.cr b/src/lib_c/x86_64-solaris/c/sys/resource.cr index d52182f69bce..74f9b56f9971 100644 --- a/src/lib_c/x86_64-solaris/c/sys/resource.cr +++ b/src/lib_c/x86_64-solaris/c/sys/resource.cr @@ -22,4 +22,15 @@ lib LibC RUSAGE_CHILDREN = -1 fun getrusage(who : Int, usage : RUsage*) : Int + + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + RLIMIT_NOFILE = 5 + + fun getrlimit(resource : Int, rlim : Rlimit*) : Int end diff --git a/src/lib_c/x86_64-solaris/c/sys/timerfd.cr b/src/lib_c/x86_64-solaris/c/sys/timerfd.cr new file mode 100644 index 000000000000..0632646b0e14 --- /dev/null +++ b/src/lib_c/x86_64-solaris/c/sys/timerfd.cr @@ -0,0 +1,10 @@ +require "../time" + +lib LibC + TFD_NONBLOCK = 0o0004000 + TFD_CLOEXEC = 0o2000000 + TFD_TIMER_ABSTIME = 1 << 0 + + fun timerfd_create(ClockidT, Int) : Int + fun timerfd_settime(Int, Int, Itimerspec*, Itimerspec*) : Int +end diff --git a/src/lib_c/x86_64-solaris/c/time.cr b/src/lib_c/x86_64-solaris/c/time.cr index 531f8e373f4b..0aa8f3fce053 100644 --- a/src/lib_c/x86_64-solaris/c/time.cr +++ b/src/lib_c/x86_64-solaris/c/time.cr @@ -21,6 +21,11 @@ lib LibC tv_nsec : Long end + struct Itimerspec + it_interval : Timespec + it_value : Timespec + end + fun clock_gettime(x0 : ClockidT, x1 : Timespec*) : Int fun clock_settime(x0 : ClockidT, x1 : Timespec*) : Int fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* From 32b5d7403ea39fbbbbef66b35de20298766cc5f3 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 20 Nov 2024 21:41:04 +0800 Subject: [PATCH 1481/1551] Make MinGW-w64 build artifact a full installation (#15204) Until now, the MinGW-w64 build artifact included only the compiler itself, all dependent DLLs, and the standard library. This PR turns that into a full installation using `make install`, most notably including the license file. There are no changes to functionality. --- .github/workflows/mingw-w64.yml | 36 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml index aecf1f2ca549..050f8800b520 100644 --- a/.github/workflows/mingw-w64.yml +++ b/.github/workflows/mingw-w64.yml @@ -40,12 +40,6 @@ jobs: name: x86_64-mingw-w64-crystal-obj path: .build/crystal.obj - - name: Upload standard library - uses: actions/upload-artifact@v4 - with: - name: x86_64-mingw-w64-crystal-stdlib - path: src - x86_64-mingw-w64-link: runs-on: windows-2022 needs: [x86_64-mingw-w64-cross-compile] @@ -57,6 +51,7 @@ jobs: msystem: UCRT64 update: true install: >- + make mingw-w64-ucrt-x86_64-pkgconf mingw-w64-ucrt-x86_64-cc mingw-w64-ucrt-x86_64-gc @@ -66,34 +61,37 @@ jobs: mingw-w64-ucrt-x86_64-llvm mingw-w64-ucrt-x86_64-libffi + - name: Disable CRLF line ending substitution + run: | + git config --global core.autocrlf false + + - name: Download Crystal source + uses: actions/checkout@v4 + - name: Download crystal.obj uses: actions/download-artifact@v4 with: name: x86_64-mingw-w64-crystal-obj - - name: Download standard library - uses: actions/download-artifact@v4 - with: - name: x86_64-mingw-w64-crystal-stdlib - path: share/crystal/src - - name: Link Crystal executable shell: msys2 {0} run: | - mkdir bin - cc crystal.obj -o bin/crystal.exe -municode \ + mkdir .build + cc crystal.obj -o .build/crystal.exe -municode \ $(pkg-config bdw-gc libpcre2-8 iconv zlib libffi --libs) \ $(llvm-config --libs --system-libs --ldflags) \ -lole32 -lWS2_32 -Wl,--stack,0x800000 - ldd bin/crystal.exe | grep -iv /c/windows/system32 | sed 's/.* => //; s/ (.*//' | xargs -t -i cp '{}' bin/ - - name: Upload Crystal + - name: Package Crystal + shell: msys2 {0} + run: | + make install install_dlls deref_symlinks=1 PREFIX="$(pwd)/crystal" + + - name: Upload Crystal executable uses: actions/upload-artifact@v4 with: name: x86_64-mingw-w64-crystal - path: | - bin/ - share/ + path: crystal x86_64-mingw-w64-test: runs-on: windows-2022 From d57c9d84d40477ec10174ac2613f56f6ce755e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 20 Nov 2024 18:16:04 +0100 Subject: [PATCH 1482/1551] Update `shell.nix` to `nixpkgs-24.05` and LLVM 18 (#14651) --- .github/workflows/macos.yml | 3 +++ bin/ci | 3 ++- shell.nix | 8 ++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 77e9e0b3371c..66a35b1661dd 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -11,18 +11,21 @@ concurrency: env: SPEC_SPLIT_DOTS: 160 CI_NIX_SHELL: true + CRYSTAL_OPTS: -Dwithout_iconv jobs: darwin-test: runs-on: ${{ matrix.runs-on }} name: ${{ matrix.arch }} strategy: + fail-fast: false matrix: include: - runs-on: macos-13 arch: x86_64-darwin - runs-on: macos-14 arch: aarch64-darwin + steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index 03d8a20a19e4..adbf33a0c5c3 100755 --- a/bin/ci +++ b/bin/ci @@ -213,6 +213,7 @@ with_build_env() { -e CRYSTAL_CACHE_DIR="/tmp/crystal" \ -e SPEC_SPLIT_DOTS \ -e USE_PCRE1 \ + -e CRYSTAL_OPTS \ "$DOCKER_TEST_IMAGE" \ "$ARCH_CMD" /bin/sh -c "'$command'" @@ -222,7 +223,7 @@ with_build_env() { CRYSTAL_CACHE_DIR="/tmp/crystal" \ /bin/sh -c "'$command'" - on_nix_shell nix-shell --pure $CI_NIX_SHELL_ARGS --run "'TZ=$TZ $command'" + on_nix_shell nix-shell --pure $CI_NIX_SHELL_ARGS --keep CRYSTAL_OPTS --run "'TZ=$TZ $command'" on_github echo "::endgroup::" } diff --git a/shell.nix b/shell.nix index 6501b4a0c577..9aacbed2575b 100644 --- a/shell.nix +++ b/shell.nix @@ -19,13 +19,13 @@ # $ nix-shell --pure --arg musl true # -{llvm ? 11, musl ? false, system ? builtins.currentSystem}: +{llvm ? 18, musl ? false, system ? builtins.currentSystem}: let nixpkgs = import (builtins.fetchTarball { - name = "nixpkgs-23.05"; - url = "https://github.com/NixOS/nixpkgs/archive/23.05.tar.gz"; - sha256 = "10wn0l08j9lgqcw8177nh2ljrnxdrpri7bp0g7nvrsn9rkawvlbf"; + name = "nixpkgs-24.05"; + url = "https://github.com/NixOS/nixpkgs/archive/24.05.tar.gz"; + sha256 = "1lr1h35prqkd1mkmzriwlpvxcb34kmhc9dnr48gkm8hh089hifmx"; }) { inherit system; }; From 02df7ff56c496970c7f5ad226193d1d0de603298 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Thu, 21 Nov 2024 12:25:31 +0100 Subject: [PATCH 1483/1551] Refactor `Evented::Arena` to allocate in blocks [fixup #14996] (#15205) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the static `mmap` that must accommodate for as many file descriptors as allowed by ulimit/rlimit. Despite being virtual memory, not really allocated in practice, this led to out-of-memory errors in some situations. The arena now dynamically allocates individual blocks as needed (no more virtual memory). For simplicity reasons it will only ever grow, and won't shrink (we may think of a solution for this later). The original safety guarantees still hold: once an entry has been allocated in the arena, its pointer won't change. The event loop still limits the arena capacity to the hardware limit (ulimit: open files). **Side effect:** the arena don't need to remember the maximum fd/index anymore; that was only needed for `fork`; we can simply iterate the allocated blocks now. Co-authored-by: Johannes Müller --- spec/std/crystal/evented/arena_spec.cr | 56 +++++-- src/crystal/system/unix/epoll/event_loop.cr | 2 +- src/crystal/system/unix/evented/arena.cr | 152 ++++++++---------- src/crystal/system/unix/evented/event_loop.cr | 9 +- src/crystal/system/unix/kqueue/event_loop.cr | 2 +- 5 files changed, 122 insertions(+), 99 deletions(-) diff --git a/spec/std/crystal/evented/arena_spec.cr b/spec/std/crystal/evented/arena_spec.cr index c25bb9ec1adc..edf5fd90e11b 100644 --- a/spec/std/crystal/evented/arena_spec.cr +++ b/spec/std/crystal/evented/arena_spec.cr @@ -5,7 +5,7 @@ require "spec" describe Crystal::Evented::Arena do describe "#allocate_at?" do it "yields block when not allocated" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) pointer = nil index = nil called = 0 @@ -31,7 +31,7 @@ describe Crystal::Evented::Arena do end it "allocates up to capacity" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) indexes = [] of Crystal::Evented::Arena::Index indexes = 32.times.map do |i| @@ -49,7 +49,7 @@ describe Crystal::Evented::Arena do end it "checks bounds" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) expect_raises(IndexError) { arena.allocate_at?(-1) { } } expect_raises(IndexError) { arena.allocate_at?(33) { } } end @@ -57,7 +57,7 @@ describe Crystal::Evented::Arena do describe "#get" do it "returns previously allocated object" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) pointer = nil index = arena.allocate_at(30) do |ptr| @@ -77,7 +77,7 @@ describe Crystal::Evented::Arena do end it "can't access unallocated object" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) expect_raises(RuntimeError) do arena.get(Crystal::Evented::Arena::Index.new(10, 0)) { } @@ -85,7 +85,7 @@ describe Crystal::Evented::Arena do end it "checks generation" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) called = 0 index1 = arena.allocate_at(2) { called += 1 } @@ -102,7 +102,7 @@ describe Crystal::Evented::Arena do end it "checks out of bounds" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) expect_raises(IndexError) { arena.get(Crystal::Evented::Arena::Index.new(-1, 0)) { } } expect_raises(IndexError) { arena.get(Crystal::Evented::Arena::Index.new(33, 0)) { } } end @@ -110,7 +110,7 @@ describe Crystal::Evented::Arena do describe "#get?" do it "returns previously allocated object" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) pointer = nil index = arena.allocate_at(30) do |ptr| @@ -131,7 +131,7 @@ describe Crystal::Evented::Arena do end it "can't access unallocated index" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) called = 0 ret = arena.get?(Crystal::Evented::Arena::Index.new(10, 0)) { called += 1 } @@ -140,7 +140,7 @@ describe Crystal::Evented::Arena do end it "checks generation" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) called = 0 old_index = arena.allocate_at(2) { } @@ -166,7 +166,7 @@ describe Crystal::Evented::Arena do end it "checks out of bounds" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) called = 0 arena.get?(Crystal::Evented::Arena::Index.new(-1, 0)) { called += 1 }.should be_false @@ -178,7 +178,7 @@ describe Crystal::Evented::Arena do describe "#free" do it "deallocates the object" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) index1 = arena.allocate_at(3) { |ptr| ptr.value = 123 } arena.free(index1) { } @@ -192,7 +192,7 @@ describe Crystal::Evented::Arena do end it "checks generation" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) called = 0 old_index = arena.allocate_at(1) { } @@ -214,7 +214,7 @@ describe Crystal::Evented::Arena do end it "checks out of bounds" do - arena = Crystal::Evented::Arena(Int32).new(32) + arena = Crystal::Evented::Arena(Int32, 96).new(32) called = 0 arena.free(Crystal::Evented::Arena::Index.new(-1, 0)) { called += 1 } @@ -223,4 +223,32 @@ describe Crystal::Evented::Arena do called.should eq(0) end end + + it "#each_index" do + arena = Crystal::Evented::Arena(Int32, 96).new(32) + indices = [] of {Int32, Crystal::Evented::Arena::Index} + + arena.each_index { |i, index| indices << {i, index} } + indices.should be_empty + + index5 = arena.allocate_at(5) { } + + arena.each_index { |i, index| indices << {i, index} } + indices.should eq([{5, index5}]) + + index3 = arena.allocate_at(3) { } + index11 = arena.allocate_at(11) { } + index10 = arena.allocate_at(10) { } + index30 = arena.allocate_at(30) { } + + indices.clear + arena.each_index { |i, index| indices << {i, index} } + indices.should eq([ + {3, index3}, + {5, index5}, + {10, index10}, + {11, index11}, + {30, index30}, + ]) + end end diff --git a/src/crystal/system/unix/epoll/event_loop.cr b/src/crystal/system/unix/epoll/event_loop.cr index dc2f2052dfa2..f638a34b2ea2 100644 --- a/src/crystal/system/unix/epoll/event_loop.cr +++ b/src/crystal/system/unix/epoll/event_loop.cr @@ -50,7 +50,7 @@ class Crystal::Epoll::EventLoop < Crystal::Evented::EventLoop system_set_timer(@timers.next_ready?) # re-add all registered fds - Evented.arena.each { |fd, index| system_add(fd, index) } + Evented.arena.each_index { |fd, index| system_add(fd, index) } end {% end %} diff --git a/src/crystal/system/unix/evented/arena.cr b/src/crystal/system/unix/evented/arena.cr index 818b80b83c41..57e408183679 100644 --- a/src/crystal/system/unix/evented/arena.cr +++ b/src/crystal/system/unix/evented/arena.cr @@ -1,14 +1,11 @@ # Generational Arena. # -# Allocates a `Slice` of `T` through `mmap`. `T` is supposed to be a struct, so -# it can be embedded right into the memory region. -# # The arena allocates objects `T` at a predefined index. The object iself is # uninitialized (outside of having its memory initialized to zero). The object -# can be allocated and later retrieved using the generation index -# (Arena::Index) that contains both the actual index (Int32) and the generation -# number (UInt32). Deallocating the object increases the generation number, -# which allows the object to be reallocated later on. Trying to retrieve the +# can be allocated and later retrieved using the generation index (Arena::Index) +# that contains both the actual index (Int32) and the generation number +# (UInt32). Deallocating the object increases the generation number, which +# allows the object to be reallocated later on. Trying to retrieve the # allocation using the generation index will fail if the generation number # changed (it's a new allocation). # @@ -21,24 +18,15 @@ # They're unique to the process and the OS always reuses the lowest fd numbers # before growing. # -# Thread safety: the memory region is pre-allocated (up to capacity) using mmap -# (virtual allocation) and pointers are never invalidated. Individual -# allocation, deallocation and regular accesses are protected by a fine grained -# lock over each object: parallel accesses to the memory region are prohibited, -# and pointers are expected to not outlive the block that yielded them (don't -# capture them). +# Thread safety: the memory region is divided in blocks of size BLOCK_BYTESIZE +# allocated in the GC. Pointers are thus never invalidated. Mutating the blocks +# is protected by a mutual exclusion lock. Individual (de)allocations of objects +# are protected with a fine grained lock. # -# Guarantees: `mmap` initializes the memory to zero, which means `T` objects are +# Guarantees: blocks' memory is initialized to zero, which means `T` objects are # initialized to zero by default, then `#free` will also clear the memory, so # the next allocation shall be initialized to zero, too. -# -# TODO: instead of the mmap that must preallocate a fixed chunk of virtual -# memory, we could allocate individual blocks of memory, then access the actual -# block at `index % size`. Pointers would still be valid (as long as the block -# isn't collected). We wouldn't have to worry about maximum capacity, we could -# still allocate blocks discontinuously & collect unused blocks during GC -# collections. -class Crystal::Evented::Arena(T) +class Crystal::Evented::Arena(T, BLOCK_BYTESIZE) INVALID_INDEX = Index.new(-1, 0) struct Index @@ -93,41 +81,12 @@ class Crystal::Evented::Arena(T) end end - @buffer : Slice(Entry(T)) - - {% unless flag?(:preview_mt) %} - # Remember the maximum allocated fd ever; - # - # This is specific to `EventLoop#after_fork` that needs to iterate the arena - # for registered fds in epoll/kqueue to re-add them to the new epoll/kqueue - # instances. Without this upper limit we'd iterate the whole arena which - # would lead the kernel to try and allocate the whole mmap in physical - # memory (instead of virtual memory) which would at best be a waste, and a - # worst fill the memory (e.g. unlimited open files). - @maximum = 0 - {% end %} - - def initialize(capacity : Int32) - pointer = self.class.mmap(LibC::SizeT.new(sizeof(Entry(T))) * capacity) - @buffer = Slice.new(pointer.as(Pointer(Entry(T))), capacity) - end - - protected def self.mmap(bytesize) - flags = LibC::MAP_PRIVATE | LibC::MAP_ANON - prot = LibC::PROT_READ | LibC::PROT_WRITE - - pointer = LibC.mmap(nil, bytesize, prot, flags, -1, 0) - System.panic("mmap", Errno.value) if pointer == LibC::MAP_FAILED - - {% if flag?(:linux) %} - LibC.madvise(pointer, bytesize, LibC::MADV_NOHUGEPAGE) - {% end %} + @blocks : Slice(Pointer(Entry(T))) + @capacity : Int32 - pointer - end - - def finalize - LibC.munmap(@buffer.to_unsafe, @buffer.bytesize) + def initialize(@capacity : Int32) + @blocks = Slice(Pointer(Entry(T))).new(1) { allocate_block } + @mutex = Thread::Mutex.new end # Allocates the object at *index* unless already allocated, then yields a @@ -140,14 +99,11 @@ class Crystal::Evented::Arena(T) # There are no generational checks. # Raises if *index* is out of bounds. def allocate_at?(index : Int32, & : (Pointer(T), Index) ->) : Index? - entry = at(index) + entry = at(index, grow: true) entry.value.@lock.sync do return if entry.value.allocated? - {% unless flag?(:preview_mt) %} - @maximum = index if index > @maximum - {% end %} entry.value.allocated = true gen_index = Index.new(index, entry.value.generation) @@ -165,9 +121,8 @@ class Crystal::Evented::Arena(T) # Yields a pointer to the object previously allocated at *index*. # - # Raises if the object isn't allocated. - # Raises if the generation has changed (i.e. the object has been freed then reallocated). - # Raises if *index* is negative. + # Raises if the object isn't allocated, the generation has changed (i.e. the + # object has been freed then reallocated) or *index* is out of bounds. def get(index : Index, &) : Nil at(index) do |entry| yield entry.value.pointer @@ -176,10 +131,9 @@ class Crystal::Evented::Arena(T) # Yields a pointer to the object previously allocated at *index* and returns # true. - # Does nothing if the object isn't allocated or the generation has changed, - # and returns false. # - # Raises if *index* is negative. + # Does nothing if the object isn't allocated, the generation has changed or + # *index* is out of bounds. def get?(index : Index, &) : Bool at?(index) do |entry| yield entry.value.pointer @@ -189,9 +143,9 @@ class Crystal::Evented::Arena(T) end # Yields the object previously allocated at *index* then releases it. - # Does nothing if the object isn't allocated or the generation has changed. # - # Raises if *index* is negative. + # Does nothing if the object isn't allocated, the generation has changed or + # *index* is out of bounds. def free(index : Index, &) : Nil at?(index) do |entry| begin @@ -203,7 +157,7 @@ class Crystal::Evented::Arena(T) end private def at(index : Index, &) : Nil - entry = at(index.index) + entry = at(index.index, grow: false) entry.value.@lock.lock unless entry.value.allocated? && entry.value.generation == index.generation @@ -229,29 +183,65 @@ class Crystal::Evented::Arena(T) end end - private def at(index : Int32) : Pointer(Entry(T)) - (@buffer + index).to_unsafe + private def at(index : Int32, grow : Bool) : Pointer(Entry(T)) + raise IndexError.new unless 0 <= index < @capacity + + n, j = index.divmod(entries_per_block) + + if n >= @blocks.size + raise RuntimeError.new("#{self.class.name}: not allocated index=#{index}") unless grow + @mutex.synchronize { unsafe_grow(n) if n >= @blocks.size } + end + + @blocks.to_unsafe[n] + j end private def at?(index : Int32) : Pointer(Entry(T))? - if 0 <= index < @buffer.size - @buffer.to_unsafe + index + return unless 0 <= index < @capacity + + n, j = index.divmod(entries_per_block) + + if block = @blocks[n]? + block + j end end - {% unless flag?(:preview_mt) %} - # Iterates all allocated objects, yields the actual index as well as the - # generation index. - def each(&) : Nil - pointer = @buffer.to_unsafe + private def unsafe_grow(n) + # we manually dup instead of using realloc to avoid parallelism issues, for + # example fork or another thread trying to iterate after realloc but before + # we got the time to set @blocks or to allocate the new blocks + new_size = n + 1 + new_pointer = GC.malloc(new_size * sizeof(Pointer(Entry(T)))).as(Pointer(Pointer(Entry(T)))) + @blocks.to_unsafe.copy_to(new_pointer, @blocks.size) + @blocks.size.upto(n) { |j| new_pointer[j] = allocate_block } + + @blocks = Slice.new(new_pointer, new_size) + end + + private def allocate_block + GC.malloc(BLOCK_BYTESIZE).as(Pointer(Entry(T))) + end - 0.upto(@maximum) do |index| - entry = pointer + index + # Iterates all allocated objects, yields the actual index as well as the + # generation index. + def each_index(&) : Nil + index = 0 + + @blocks.each do |block| + entries_per_block.times do |j| + entry = block + j if entry.value.allocated? yield index, Index.new(index, entry.value.generation) end + + index += 1 end end - {% end %} + end + + private def entries_per_block + # can't be a constant: can't access a generic when assigning a constant + BLOCK_BYTESIZE // sizeof(Entry(T)) + end end diff --git a/src/crystal/system/unix/evented/event_loop.cr b/src/crystal/system/unix/evented/event_loop.cr index 65b9e746b9b2..e05025aeae98 100644 --- a/src/crystal/system/unix/evented/event_loop.cr +++ b/src/crystal/system/unix/evented/event_loop.cr @@ -38,13 +38,18 @@ module Crystal::Evented # allows optimizations to the OS (it can reuse already allocated resources), # and either the man page explicitly says so (Linux), or they don't (BSD) and # they must follow the POSIX definition. - protected class_getter arena = Arena(PollDescriptor).new(max_fds) + # + # The block size is set to 64KB because it's a multiple of: + # - 4KB (usual page size) + # - 1024 (common soft limit for open files) + # - sizeof(Arena::Entry(PollDescriptor)) + protected class_getter arena = Arena(PollDescriptor, 65536).new(max_fds) private def self.max_fds : Int32 if LibC.getrlimit(LibC::RLIMIT_NOFILE, out rlimit) == -1 raise RuntimeError.from_errno("getrlimit(RLIMIT_NOFILE)") end - rlimit.rlim_cur.clamp(..Int32::MAX).to_i32! + rlimit.rlim_max.clamp(..Int32::MAX).to_i32! end end diff --git a/src/crystal/system/unix/kqueue/event_loop.cr b/src/crystal/system/unix/kqueue/event_loop.cr index 6eb98a7dc948..bdf0a0bf6815 100644 --- a/src/crystal/system/unix/kqueue/event_loop.cr +++ b/src/crystal/system/unix/kqueue/event_loop.cr @@ -66,7 +66,7 @@ class Crystal::Kqueue::EventLoop < Crystal::Evented::EventLoop system_set_timer(@timers.next_ready?) # re-add all registered fds - Evented.arena.each { |fd, index| system_add(fd, index) } + Evented.arena.each_index { |fd, index| system_add(fd, index) } end {% end %} From 63428827911700572f44f666a5898e7cc0f7ff82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 22 Nov 2024 12:14:41 +0100 Subject: [PATCH 1484/1551] Remove the entire compiler code base from `external_command_spec` (#15208) The spec file only needs `support/tempfile`, not the entire `spec_helper`, which also include the entire compiler code base. --- spec/primitives/external_command_spec.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/primitives/external_command_spec.cr b/spec/primitives/external_command_spec.cr index 91687f7c2d21..9dceee0753bb 100644 --- a/spec/primitives/external_command_spec.cr +++ b/spec/primitives/external_command_spec.cr @@ -1,8 +1,8 @@ {% skip_file if flag?(:interpreted) %} -require "../spec_helper" +require "../support/tempfile" -describe Crystal::Command do +describe "Crystal::Command" do it "exec external commands", tags: %w[slow] do with_temp_executable "crystal-external" do |path| with_tempfile "crystal-external.cr" do |source_file| From edb8764639b034416affa0e707ea4b56da4ae331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 22 Nov 2024 12:15:13 +0100 Subject: [PATCH 1485/1551] Refactor `Enumerable#map` to delegate to `#map_with_index` (#15210) `#map` and `map_with_index` are identical except that the latter also yields an index counter. But this counter is entirely optional and can be omitted from the block. It's possible to call `#map_with_index` with exactly the same signature as `#map`. ```cr ["foo", "bar"].map_with_index do |e| e.upcase end # => ["FOO", "BAR"] ["foo", "bar"].map do |e| e.upcase end # => ["FOO", "BAR"] ``` The implementation of both methods is also pretty much identical, in `Enumerable` as well as in any including type. Of course, `map_with_index` has a counter and yields its value. But LLVM optimization happily removes it when unused. So I think it makes sense to implement `Enumerable#map` by delegating to `#map_with_index`. This embodies the close connection between these two methods. As a result, including types only need to override `#map_with_index` with a custom implementation and `#map` will follow suit. Including types *may* still override `#map` with a custom implementation, of course. --- src/enumerable.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/enumerable.cr b/src/enumerable.cr index 5504c5d60064..0993f38bbc4d 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -1033,9 +1033,9 @@ module Enumerable(T) # [1, 2, 3].map { |i| i * 10 } # => [10, 20, 30] # ``` def map(& : T -> U) : Array(U) forall U - ary = [] of U - each { |e| ary << yield e } - ary + map_with_index do |e| + yield e + end end # Like `map`, but the block gets passed both the element and its index. From c2d1a017368bde7cd84db910a69a36cfd7cbfbae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 22 Nov 2024 13:08:43 +0100 Subject: [PATCH 1486/1551] Revert "Update `shell.nix` to `nixpkgs-24.05` and LLVM 18" (#15212) This reverts commit d57c9d84d40477ec10174ac2613f56f6ce755e88. --- .github/workflows/macos.yml | 3 --- bin/ci | 3 +-- shell.nix | 8 ++++---- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 66a35b1661dd..77e9e0b3371c 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -11,21 +11,18 @@ concurrency: env: SPEC_SPLIT_DOTS: 160 CI_NIX_SHELL: true - CRYSTAL_OPTS: -Dwithout_iconv jobs: darwin-test: runs-on: ${{ matrix.runs-on }} name: ${{ matrix.arch }} strategy: - fail-fast: false matrix: include: - runs-on: macos-13 arch: x86_64-darwin - runs-on: macos-14 arch: aarch64-darwin - steps: - name: Download Crystal source uses: actions/checkout@v4 diff --git a/bin/ci b/bin/ci index adbf33a0c5c3..03d8a20a19e4 100755 --- a/bin/ci +++ b/bin/ci @@ -213,7 +213,6 @@ with_build_env() { -e CRYSTAL_CACHE_DIR="/tmp/crystal" \ -e SPEC_SPLIT_DOTS \ -e USE_PCRE1 \ - -e CRYSTAL_OPTS \ "$DOCKER_TEST_IMAGE" \ "$ARCH_CMD" /bin/sh -c "'$command'" @@ -223,7 +222,7 @@ with_build_env() { CRYSTAL_CACHE_DIR="/tmp/crystal" \ /bin/sh -c "'$command'" - on_nix_shell nix-shell --pure $CI_NIX_SHELL_ARGS --keep CRYSTAL_OPTS --run "'TZ=$TZ $command'" + on_nix_shell nix-shell --pure $CI_NIX_SHELL_ARGS --run "'TZ=$TZ $command'" on_github echo "::endgroup::" } diff --git a/shell.nix b/shell.nix index 9aacbed2575b..6501b4a0c577 100644 --- a/shell.nix +++ b/shell.nix @@ -19,13 +19,13 @@ # $ nix-shell --pure --arg musl true # -{llvm ? 18, musl ? false, system ? builtins.currentSystem}: +{llvm ? 11, musl ? false, system ? builtins.currentSystem}: let nixpkgs = import (builtins.fetchTarball { - name = "nixpkgs-24.05"; - url = "https://github.com/NixOS/nixpkgs/archive/24.05.tar.gz"; - sha256 = "1lr1h35prqkd1mkmzriwlpvxcb34kmhc9dnr48gkm8hh089hifmx"; + name = "nixpkgs-23.05"; + url = "https://github.com/NixOS/nixpkgs/archive/23.05.tar.gz"; + sha256 = "10wn0l08j9lgqcw8177nh2ljrnxdrpri7bp0g7nvrsn9rkawvlbf"; }) { inherit system; }; From fe90f2787933904f7601a31cf25dd7052294497b Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sat, 23 Nov 2024 13:16:19 +0100 Subject: [PATCH 1487/1551] EventLoop: yield fibers internally [fixup #14996] (#15215) Refactors the internals of the epoll/kqueue event loop to `yield` the fiber(s) to be resumed instead of blindly calling `Crystal::Scheduler.enqueue`, so the `#run` method becomes the one place responsible to enqueue the fibers. The current behavior doesn't change, the `#run` method still enqueues the fiber immediately, but it can now be changed in a single place. For example the [execution context shard](https://github.com/ysbaddaden/execution_context) monkey-patches an alternative `#run` method that collects and returns fibers to avoid parallel enqueues from an evloop run to interrupt the evloop run (:sob:). Note that the `#close` method still directly enqueues waiting fibers one by one, for now. --- .../crystal/evented/poll_descriptor_spec.cr | 2 +- src/crystal/system/unix/epoll/event_loop.cr | 20 +++++++------- src/crystal/system/unix/evented/event_loop.cr | 26 ++++++++++++------- src/crystal/system/unix/kqueue/event_loop.cr | 21 ++++++++------- 4 files changed, 38 insertions(+), 31 deletions(-) diff --git a/spec/std/crystal/evented/poll_descriptor_spec.cr b/spec/std/crystal/evented/poll_descriptor_spec.cr index d50ecd1036b9..a5719f7ff7a8 100644 --- a/spec/std/crystal/evented/poll_descriptor_spec.cr +++ b/spec/std/crystal/evented/poll_descriptor_spec.cr @@ -5,7 +5,7 @@ require "spec" class Crystal::Evented::FakeLoop < Crystal::Evented::EventLoop getter operations = [] of {Symbol, Int32, Crystal::Evented::Arena::Index | Bool} - private def system_run(blocking : Bool) : Nil + private def system_run(blocking : Bool, & : Fiber ->) : Nil end private def interrupt : Nil diff --git a/src/crystal/system/unix/epoll/event_loop.cr b/src/crystal/system/unix/epoll/event_loop.cr index f638a34b2ea2..4850e68739f2 100644 --- a/src/crystal/system/unix/epoll/event_loop.cr +++ b/src/crystal/system/unix/epoll/event_loop.cr @@ -54,7 +54,7 @@ class Crystal::Epoll::EventLoop < Crystal::Evented::EventLoop end {% end %} - private def system_run(blocking : Bool) : Nil + private def system_run(blocking : Bool, & : Fiber ->) : Nil Crystal.trace :evloop, "run", blocking: blocking ? 1 : 0 # wait for events (indefinitely when blocking) @@ -72,21 +72,21 @@ class Crystal::Epoll::EventLoop < Crystal::Evented::EventLoop # TODO: panic if epoll_event.value.events != LibC::EPOLLIN (could be EPOLLERR or EPLLHUP) Crystal.trace :evloop, "interrupted" @eventfd.read - # OPTIMIZE: only reset interrupted before a blocking wait @interrupted.clear when @timerfd.fd # TODO: panic if epoll_event.value.events != LibC::EPOLLIN (could be EPOLLERR or EPLLHUP) Crystal.trace :evloop, "timer" timer_triggered = true else - process_io(epoll_event) + process_io(epoll_event) { |fiber| yield fiber } end end - process_timers(timer_triggered) + # OPTIMIZE: only process timers when timer_triggered (?) + process_timers(timer_triggered) { |fiber| yield fiber } end - private def process_io(epoll_event : LibC::EpollEvent*) : Nil + private def process_io(epoll_event : LibC::EpollEvent*, &) : Nil index = Evented::Arena::Index.new(epoll_event.value.data.u64) events = epoll_event.value.events @@ -94,19 +94,19 @@ class Crystal::Epoll::EventLoop < Crystal::Evented::EventLoop Evented.arena.get?(index) do |pd| if (events & (LibC::EPOLLERR | LibC::EPOLLHUP)) != 0 - pd.value.@readers.ready_all { |event| unsafe_resume_io(event) } - pd.value.@writers.ready_all { |event| unsafe_resume_io(event) } + pd.value.@readers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } } + pd.value.@writers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } } return end if (events & LibC::EPOLLRDHUP) == LibC::EPOLLRDHUP - pd.value.@readers.ready_all { |event| unsafe_resume_io(event) } + pd.value.@readers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } } elsif (events & LibC::EPOLLIN) == LibC::EPOLLIN - pd.value.@readers.ready_one { |event| unsafe_resume_io(event) } + pd.value.@readers.ready_one { |event| unsafe_resume_io(event) { |fiber| yield fiber } } end if (events & LibC::EPOLLOUT) == LibC::EPOLLOUT - pd.value.@writers.ready_one { |event| unsafe_resume_io(event) } + pd.value.@writers.ready_one { |event| unsafe_resume_io(event) { |fiber| yield fiber } } end end end diff --git a/src/crystal/system/unix/evented/event_loop.cr b/src/crystal/system/unix/evented/event_loop.cr index e05025aeae98..e33fb3d2ea99 100644 --- a/src/crystal/system/unix/evented/event_loop.cr +++ b/src/crystal/system/unix/evented/event_loop.cr @@ -113,7 +113,9 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop # NOTE: thread unsafe def run(blocking : Bool) : Bool - system_run(blocking) + system_run(blocking) do |fiber| + Crystal::Scheduler.enqueue(fiber) + end true end @@ -299,11 +301,15 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop Evented.arena.free(index) do |pd| pd.value.@readers.ready_all do |event| - pd.value.@event_loop.try(&.unsafe_resume_io(event)) + pd.value.@event_loop.try(&.unsafe_resume_io(event) do |fiber| + Crystal::Scheduler.enqueue(fiber) + end) end pd.value.@writers.ready_all do |event| - pd.value.@event_loop.try(&.unsafe_resume_io(event)) + pd.value.@event_loop.try(&.unsafe_resume_io(event) do |fiber| + Crystal::Scheduler.enqueue(fiber) + end) end pd.value.remove(io.fd) @@ -418,7 +424,7 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop # Thread unsafe: we must hold the poll descriptor waiter lock for the whole # duration of the dequeue/resume_io otherwise we might conflict with timers # trying to cancel an IO event. - protected def unsafe_resume_io(event : Evented::Event*) : Bool + protected def unsafe_resume_io(event : Evented::Event*, &) : Bool # we only partially own the poll descriptor; thanks to the lock we know that # another thread won't dequeue it, yet it may still be in the timers queue, # which at worst may be waiting on the lock to be released, so event* can be @@ -426,7 +432,7 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop if !event.value.wake_at? || delete_timer(event) # no timeout or we canceled it: we fully own the event - Crystal::Scheduler.enqueue(event.value.fiber) + yield event.value.fiber true else # failed to cancel the timeout so the timer owns the event (by rule) @@ -439,7 +445,7 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop # Shall be called after processing IO events. IO events with a timeout that # have succeeded shall already have been removed from `@timers` otherwise the # fiber could be resumed twice! - private def process_timers(timer_triggered : Bool) : Nil + private def process_timers(timer_triggered : Bool, &) : Nil # collect ready timers before processing them —this is safe— to avoids a # deadlock situation when another thread tries to process a ready IO event # (in poll descriptor waiters) with a timeout (same event* in timers) @@ -458,11 +464,11 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop end buffer.to_slice[0, size].each do |event| - process_timer(event) + process_timer(event) { |fiber| yield fiber } end end - private def process_timer(event : Evented::Event*) + private def process_timer(event : Evented::Event*, &) # we dequeued the event from timers, and by rule we own it, so event* can # safely be dereferenced: fiber = event.value.fiber @@ -492,7 +498,7 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop raise RuntimeError.new("BUG: unexpected event in timers: #{event.value}%s\n") end - Crystal::Scheduler.enqueue(fiber) + yield fiber end # internals: system @@ -505,7 +511,7 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop # # The `PollDescriptor` of IO events can be retrieved using the *index* # from the system event's user data. - private abstract def system_run(blocking : Bool) : Nil + private abstract def system_run(blocking : Bool, & : Fiber ->) : Nil # Add *fd* to the polling system, setting *index* as user data. protected abstract def system_add(fd : Int32, index : Index) : Nil diff --git a/src/crystal/system/unix/kqueue/event_loop.cr b/src/crystal/system/unix/kqueue/event_loop.cr index bdf0a0bf6815..eb55fde0cf37 100644 --- a/src/crystal/system/unix/kqueue/event_loop.cr +++ b/src/crystal/system/unix/kqueue/event_loop.cr @@ -70,7 +70,7 @@ class Crystal::Kqueue::EventLoop < Crystal::Evented::EventLoop end {% end %} - private def system_run(blocking : Bool) : Nil + private def system_run(blocking : Bool, & : Fiber ->) : Nil buffer = uninitialized LibC::Kevent[128] Crystal.trace :evloop, "run", blocking: blocking ? 1 : 0 @@ -89,11 +89,12 @@ class Crystal::Kqueue::EventLoop < Crystal::Evented::EventLoop # nothing special timer_triggered = true else - process_io(kevent) + process_io(kevent) { |fiber| yield fiber } end end - process_timers(timer_triggered) + # OPTIMIZE: only process timers when timer_triggered (?) + process_timers(timer_triggered) { |fiber| yield fiber } end private def process_interrupt?(kevent) @@ -114,7 +115,7 @@ class Crystal::Kqueue::EventLoop < Crystal::Evented::EventLoop false end - private def process_io(kevent : LibC::Kevent*) : Nil + private def process_io(kevent : LibC::Kevent*, &) : Nil index = {% if flag?(:bits64) %} Evented::Arena::Index.new(kevent.value.udata.address) @@ -130,8 +131,8 @@ class Crystal::Kqueue::EventLoop < Crystal::Evented::EventLoop if (kevent.value.fflags & LibC::EV_EOF) == LibC::EV_EOF # apparently some systems may report EOF on write with EVFILT_READ instead # of EVFILT_WRITE, so let's wake all waiters: - pd.value.@readers.ready_all { |event| unsafe_resume_io(event) } - pd.value.@writers.ready_all { |event| unsafe_resume_io(event) } + pd.value.@readers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } } + pd.value.@writers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } } return end @@ -139,16 +140,16 @@ class Crystal::Kqueue::EventLoop < Crystal::Evented::EventLoop when LibC::EVFILT_READ if (kevent.value.fflags & LibC::EV_ERROR) == LibC::EV_ERROR # OPTIMIZE: pass errno (kevent.data) through PollDescriptor - pd.value.@readers.ready_all { |event| unsafe_resume_io(event) } + pd.value.@readers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } } else - pd.value.@readers.ready_one { |event| unsafe_resume_io(event) } + pd.value.@readers.ready_one { |event| unsafe_resume_io(event) { |fiber| yield fiber } } end when LibC::EVFILT_WRITE if (kevent.value.fflags & LibC::EV_ERROR) == LibC::EV_ERROR # OPTIMIZE: pass errno (kevent.data) through PollDescriptor - pd.value.@writers.ready_all { |event| unsafe_resume_io(event) } + pd.value.@writers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } } else - pd.value.@writers.ready_one { |event| unsafe_resume_io(event) } + pd.value.@writers.ready_one { |event| unsafe_resume_io(event) { |fiber| yield fiber } } end end end From 558ce7b46767bb475efa51e583f77e68885ee3a0 Mon Sep 17 00:00:00 2001 From: David Keller Date: Mon, 25 Nov 2024 15:08:51 +0100 Subject: [PATCH 1488/1551] Chore: Link i128 constants internally if possible (#15217) --- src/compiler/crystal/codegen/const.cr | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/compiler/crystal/codegen/const.cr b/src/compiler/crystal/codegen/const.cr index 8ace05ff76e8..ec306e97296d 100644 --- a/src/compiler/crystal/codegen/const.cr +++ b/src/compiler/crystal/codegen/const.cr @@ -46,12 +46,16 @@ class Crystal::CodeGenVisitor @main_mod.globals.add(@main_llvm_typer.llvm_type(const.value.type), global_name) type = const.value.type - # TODO: there's an LLVM bug that prevents us from having internal globals of type i128 or u128: + # TODO: LLVM < 9.0.0 has a bug that prevents us from having internal globals of type i128 or u128: # https://bugs.llvm.org/show_bug.cgi?id=42932 - # so we just use global. - if @single_module && !(type.is_a?(IntegerType) && (type.kind.i128? || type.kind.u128?)) + # so we just use global in that case. + {% if compare_versions(Crystal::LLVM_VERSION, "9.0.0") < 0 %} + if @single_module && !(type.is_a?(IntegerType) && (type.kind.i128? || type.kind.u128?)) + global.linkage = LLVM::Linkage::Internal + end + {% else %} global.linkage = LLVM::Linkage::Internal if @single_module - end + {% end %} global end From 5aa120467d431a97d6a979e887f3acd4f0fff6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 26 Nov 2024 15:21:56 +0100 Subject: [PATCH 1489/1551] Makefile: Allow custom extensions for exports and spec flags (#15099) This allows to add custom exports or spec flags from CLI arguments or `Makefile.local`. For example `make std_spec EXPORTS=-Dpreview_mt SPEC_FLAGS=--tag=~slow`. --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index b77fe62df52b..6328415d4ed7 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ SOURCES := $(shell find src -name '*.cr') SPEC_SOURCES := $(shell find spec -name '*.cr') override FLAGS += -D strict_multi_assign -D preview_overload_order $(if $(release),--release )$(if $(stats),--stats )$(if $(progress),--progress )$(if $(threads),--threads $(threads) )$(if $(debug),-d )$(if $(static),--static )$(if $(LDFLAGS),--link-flags="$(LDFLAGS)" )$(if $(target),--cross-compile --target $(target) )$(if $(interpreter),,-Dwithout_interpreter ) SPEC_WARNINGS_OFF := --exclude-warnings spec/std --exclude-warnings spec/compiler --exclude-warnings spec/primitives -SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) )$(if $(order),--order=$(order) ) +override SPEC_FLAGS += $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) )$(if $(order),--order=$(order) ) CRYSTAL_CONFIG_LIBRARY_PATH := '$$ORIGIN/../lib/crystal' CRYSTAL_CONFIG_BUILD_COMMIT ?= $(shell git rev-parse --short HEAD 2> /dev/null) CRYSTAL_CONFIG_PATH := '$$ORIGIN/../share/crystal/src' @@ -53,11 +53,11 @@ check_lld := command -v ld.lld >/dev/null && case "$$(uname -s)" in MINGW32*|MIN ifeq ($(shell $(check_lld)),1) EXPORT_CC ?= CC="$(CC) -fuse-ld=lld" endif -EXPORTS := \ +override EXPORTS += \ CRYSTAL_CONFIG_BUILD_COMMIT="$(CRYSTAL_CONFIG_BUILD_COMMIT)" \ CRYSTAL_CONFIG_PATH=$(CRYSTAL_CONFIG_PATH) \ SOURCE_DATE_EPOCH="$(SOURCE_DATE_EPOCH)" -EXPORTS_BUILD := \ +override EXPORTS_BUILD += \ $(EXPORT_CC) \ CRYSTAL_CONFIG_LIBRARY_PATH=$(CRYSTAL_CONFIG_LIBRARY_PATH) SHELL = sh From ec11b2d976b75ad02cb013f420117bfeb6e10c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 26 Nov 2024 15:22:35 +0100 Subject: [PATCH 1490/1551] Fix `SyntaxHighlighter` delimiter state (#15104) The syntax highlighter needs to reset the lexer's previous `delimiter_state` after exiting a nested state. --- spec/std/crystal/syntax_highlighter/html_spec.cr | 4 ++++ src/crystal/syntax_highlighter.cr | 3 +++ 2 files changed, 7 insertions(+) diff --git a/spec/std/crystal/syntax_highlighter/html_spec.cr b/spec/std/crystal/syntax_highlighter/html_spec.cr index 2d8b37a4f51e..cf43e50b92b9 100644 --- a/spec/std/crystal/syntax_highlighter/html_spec.cr +++ b/spec/std/crystal/syntax_highlighter/html_spec.cr @@ -162,4 +162,8 @@ describe Crystal::SyntaxHighlighter::HTML do it_highlights! "%w[foo" it_highlights! "%i[foo" end + + # fix for https://forum.crystal-lang.org/t/question-about-the-crystal-syntax-highlighter/7283 + it_highlights %q(/#{l[""]}/ + "\\n"), %(/\#{l[""]}/\n "\\\\n") end diff --git a/src/crystal/syntax_highlighter.cr b/src/crystal/syntax_highlighter.cr index 1d4abcb60c70..a7794e96a21c 100644 --- a/src/crystal/syntax_highlighter.cr +++ b/src/crystal/syntax_highlighter.cr @@ -84,6 +84,8 @@ abstract class Crystal::SyntaxHighlighter space_before = false while true + previous_delimiter_state = lexer.token.delimiter_state + token = lexer.next_token case token.type @@ -105,6 +107,7 @@ abstract class Crystal::SyntaxHighlighter highlight_token token, last_is_def else highlight_delimiter_state lexer, token + token.delimiter_state = previous_delimiter_state end when .string_array_start?, .symbol_array_start? highlight_string_array lexer, token From 6928ca70e5e31c1481a308f07818c296ca005a0f Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 26 Nov 2024 15:23:51 +0100 Subject: [PATCH 1491/1551] EventLoop: store Timers in min Pairing Heap [fixup #14996] (#15206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to [RFC #0012](https://github.com/crystal-lang/rfcs/pull/12). Replaces the `Deque` used in #14996 for a min [Pairing Heap] which is a kind of [Mergeable Heap] and is one of the best performing heap in practical tests when arbitrary deletions are required (think cancelling a timeout), otherwise a D-ary Heap (e.g. 4-heap) will usually perform better. See the [A Nearly-Tight Analysis of Multipass Pairing Heaps](https://epubs.siam.org/doi/epdf/10.1137/1.9781611973068.52) paper or the Wikipedia page for more details. The implementation itself is based on the [Pairing Heaps: Experiments and Analysis](https://dl.acm.org/doi/pdf/10.1145/214748.214759) paper, and merely implements a recursive twopass algorithm (the auxiliary twopass might perform even better). The `Crystal::PointerPairingList(T)` type is generic and relies on intrusive nodes (the links are into `T`) to avoid extra allocations for the nodes (same as `Crystal::PointerLinkedList(T)`). It also requires a `T#heap_compare` method, so we can use the same type for a min or max heap, or to build a more complex comparison. Note: I also tried a 4-heap, and while it performs very well and only needs a flat array, the arbitrary deletion (e.g. cancelling timeout) needs a linear scan and its performance quickly plummets, even at low occupancy, and becomes painfully slow at higher occupancy (tens of microseconds on _each_ delete, while the pairing heap does it in tens of nanoseconds). Follow up to #14996 [Mergeable Heap]: https://en.wikipedia.org/wiki/Mergeable_heap [Pairing Heap]: https://en.wikipedia.org/wiki/Pairing_heap [D-ary Heap]: https://en.wikipedia.org/wiki/D-ary_heap Co-authored-by: Linus Sellberg Co-authored-by: Johannes Müller --- spec/std/crystal/evented/timers_spec.cr | 28 ++-- spec/std/crystal/pointer_pairing_heap_spec.cr | 150 +++++++++++++++++ src/crystal/pointer_pairing_heap.cr | 158 ++++++++++++++++++ src/crystal/system/unix/evented/event.cr | 8 + src/crystal/system/unix/evented/timers.cr | 64 ++----- 5 files changed, 349 insertions(+), 59 deletions(-) create mode 100644 spec/std/crystal/pointer_pairing_heap_spec.cr create mode 100644 src/crystal/pointer_pairing_heap.cr diff --git a/spec/std/crystal/evented/timers_spec.cr b/spec/std/crystal/evented/timers_spec.cr index d40917910d1d..9dccbf4f56f2 100644 --- a/spec/std/crystal/evented/timers_spec.cr +++ b/spec/std/crystal/evented/timers_spec.cr @@ -10,6 +10,9 @@ describe Crystal::Evented::Timers do event = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 7.seconds) timers.add(pointerof(event)) timers.empty?.should be_false + + timers.delete(pointerof(event)) + timers.empty?.should be_true end it "#next_ready?" do @@ -18,9 +21,18 @@ describe Crystal::Evented::Timers do timers.next_ready?.should be_nil # with events - event = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 5.seconds) - timers.add(pointerof(event)) - timers.next_ready?.should eq(event.wake_at?) + event1s = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.second) + event3m = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 3.minutes) + event5m = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 5.minutes) + + timers.add(pointerof(event5m)) + timers.next_ready?.should eq(event5m.wake_at?) + + timers.add(pointerof(event1s)) + timers.next_ready?.should eq(event1s.wake_at?) + + timers.add(pointerof(event3m)) + timers.next_ready?.should eq(event1s.wake_at?) end it "#dequeue_ready" do @@ -66,16 +78,6 @@ describe Crystal::Evented::Timers do event0.wake_at = -1.minute timers.add(pointerof(event0)).should be_true # added new head (next ready) - - events = [] of Crystal::Evented::Event* - timers.each { |event| events << event } - events.should eq([ - pointerof(event0), - pointerof(event1), - pointerof(event3), - pointerof(event2), - ]) - timers.empty?.should be_false end it "#delete" do diff --git a/spec/std/crystal/pointer_pairing_heap_spec.cr b/spec/std/crystal/pointer_pairing_heap_spec.cr new file mode 100644 index 000000000000..7aca79b37f07 --- /dev/null +++ b/spec/std/crystal/pointer_pairing_heap_spec.cr @@ -0,0 +1,150 @@ +require "spec" +require "../../../src/crystal/pointer_pairing_heap" + +private struct Node + getter key : Int32 + + include Crystal::PointerPairingHeap::Node + + def initialize(@key : Int32) + end + + def heap_compare(other : Pointer(self)) : Bool + key < other.value.key + end + + def inspect(io : IO, indent = 0) : Nil + prv = @heap_previous + nxt = @heap_next + chd = @heap_child + + indent.times { io << ' ' } + io << "Node value=" << key + io << " prv=" << prv.try(&.value.key) + io << " nxt=" << nxt.try(&.value.key) + io << " chd=" << chd.try(&.value.key) + io.puts + + node = heap_child? + while node + node.value.inspect(io, indent + 2) + node = node.value.heap_next? + end + end +end + +describe Crystal::PointerPairingHeap do + it "#add" do + heap = Crystal::PointerPairingHeap(Node).new + node1 = Node.new(1) + node2 = Node.new(2) + node2b = Node.new(2) + node3 = Node.new(3) + + # can add distinct nodes + heap.add(pointerof(node3)) + heap.add(pointerof(node1)) + heap.add(pointerof(node2)) + + # can add duplicate key (different nodes) + heap.add(pointerof(node2b)) + + # can't add same node twice + expect_raises(ArgumentError) { heap.add(pointerof(node1)) } + + # can re-add removed nodes + heap.delete(pointerof(node3)) + heap.add(pointerof(node3)) + + heap.shift?.should eq(pointerof(node1)) + heap.add(pointerof(node1)) + end + + it "#shift?" do + heap = Crystal::PointerPairingHeap(Node).new + nodes = StaticArray(Node, 10).new { |i| Node.new(i) } + + # insert in random order + (0..9).to_a.shuffle.each do |i| + heap.add nodes.to_unsafe + i + end + + # removes in ascending order + 10.times do |i| + node = heap.shift? + node.should eq(nodes.to_unsafe + i) + end + end + + it "#delete" do + heap = Crystal::PointerPairingHeap(Node).new + nodes = StaticArray(Node, 10).new { |i| Node.new(i) } + + # insert in random order + (0..9).to_a.shuffle.each do |i| + heap.add nodes.to_unsafe + i + end + + # remove some values + heap.delete(nodes.to_unsafe + 3) + heap.delete(nodes.to_unsafe + 7) + heap.delete(nodes.to_unsafe + 1) + + # remove tail + heap.delete(nodes.to_unsafe + 9) + + # remove head + heap.delete(nodes.to_unsafe + 0) + + # repeatedly delete min + [2, 4, 5, 6, 8].each do |i| + heap.shift?.should eq(nodes.to_unsafe + i) + end + heap.shift?.should be_nil + end + + it "adds 1000 nodes then shifts them in order" do + heap = Crystal::PointerPairingHeap(Node).new + + nodes = StaticArray(Node, 1000).new { |i| Node.new(i) } + (0..999).to_a.shuffle.each { |i| heap.add(nodes.to_unsafe + i) } + + i = 0 + while node = heap.shift? + node.value.key.should eq(i) + i += 1 + end + i.should eq(1000) + + heap.shift?.should be_nil + end + + it "randomly shift while we add nodes" do + heap = Crystal::PointerPairingHeap(Node).new + + nodes = uninitialized StaticArray(Node, 1000) + (0..999).to_a.shuffle.each_with_index { |i, j| nodes[j] = Node.new(i) } + + i = 0 + removed = 0 + + # regularly calls delete-min while we insert + loop do + if rand(0..5) == 0 + removed += 1 if heap.shift? + else + heap.add(nodes.to_unsafe + i) + break if (i += 1) == 1000 + end + end + + # exhaust the heap + while heap.shift? + removed += 1 + end + + # we must have added and removed all nodes _once_ + i.should eq(1000) + removed.should eq(1000) + end +end diff --git a/src/crystal/pointer_pairing_heap.cr b/src/crystal/pointer_pairing_heap.cr new file mode 100644 index 000000000000..1b0d73d06bcf --- /dev/null +++ b/src/crystal/pointer_pairing_heap.cr @@ -0,0 +1,158 @@ +# :nodoc: +# +# Tree of `T` structs referenced as pointers. +# `T` must include `Crystal::PointerPairingHeap::Node`. +class Crystal::PointerPairingHeap(T) + module Node + macro included + property? heap_previous : Pointer(self)? + property? heap_next : Pointer(self)? + property? heap_child : Pointer(self)? + end + + # Compare self with other. For example: + # + # Use `<` to create a min heap. + # Use `>` to create a max heap. + abstract def heap_compare(other : Pointer(self)) : Bool + end + + @head : Pointer(T)? + + private def head=(head) + @head = head + head.value.heap_previous = nil if head + head + end + + def empty? + @head.nil? + end + + def first? : Pointer(T)? + @head + end + + def shift? : Pointer(T)? + if node = @head + self.head = merge_pairs(node.value.heap_child?) + node.value.heap_child = nil + node + end + end + + def add(node : Pointer(T)) : Nil + if node.value.heap_previous? || node.value.heap_next? || node.value.heap_child? + raise ArgumentError.new("The node is already in a Pairing Heap tree") + end + self.head = meld(@head, node) + end + + def delete(node : Pointer(T)) : Nil + if previous_node = node.value.heap_previous? + next_sibling = node.value.heap_next? + + if previous_node.value.heap_next? == node + previous_node.value.heap_next = next_sibling + else + previous_node.value.heap_child = next_sibling + end + + if next_sibling + next_sibling.value.heap_previous = previous_node + end + + subtree = merge_pairs(node.value.heap_child?) + clear(node) + self.head = meld(@head, subtree) + else + # removing head + self.head = merge_pairs(node.value.heap_child?) + node.value.heap_child = nil + end + end + + def clear : Nil + if node = @head + clear_recursive(node) + @head = nil + end + end + + private def clear_recursive(node) + child = node.value.heap_child? + while child + clear_recursive(child) + child = child.value.heap_next? + end + clear(node) + end + + private def meld(a : Pointer(T), b : Pointer(T)) : Pointer(T) + if a.value.heap_compare(b) + add_child(a, b) + else + add_child(b, a) + end + end + + private def meld(a : Pointer(T), b : Nil) : Pointer(T) + a + end + + private def meld(a : Nil, b : Pointer(T)) : Pointer(T) + b + end + + private def meld(a : Nil, b : Nil) : Nil + end + + private def add_child(parent : Pointer(T), node : Pointer(T)) : Pointer(T) + first_child = parent.value.heap_child? + parent.value.heap_child = node + + first_child.value.heap_previous = node if first_child + node.value.heap_previous = parent + node.value.heap_next = first_child + + parent + end + + private def merge_pairs(node : Pointer(T)?) : Pointer(T)? + return unless node + + # 1st pass: meld children into pairs (left to right) + tail = nil + + while a = node + if b = a.value.heap_next? + node = b.value.heap_next? + root = meld(a, b) + root.value.heap_previous = tail + tail = root + else + a.value.heap_previous = tail + tail = a + break + end + end + + # 2nd pass: meld the pairs back into a single tree (right to left) + root = nil + + while tail + node = tail.value.heap_previous? + root = meld(root, tail) + tail = node + end + + root.value.heap_next = nil if root + root + end + + private def clear(node) : Nil + node.value.heap_previous = nil + node.value.heap_next = nil + node.value.heap_child = nil + end +end diff --git a/src/crystal/system/unix/evented/event.cr b/src/crystal/system/unix/evented/event.cr index b33130df53c2..e6937cf4d044 100644 --- a/src/crystal/system/unix/evented/event.cr +++ b/src/crystal/system/unix/evented/event.cr @@ -1,4 +1,5 @@ require "crystal/pointer_linked_list" +require "crystal/pointer_pairing_heap" # Information about the event that a `Fiber` is waiting on. # @@ -35,6 +36,9 @@ struct Crystal::Evented::Event # The event can be added to `Waiters` lists. include PointerLinkedList::Node + # The event can be added to the `Timers` list. + include PointerPairingHeap::Node + def initialize(@type : Type, @fiber, @index = nil, timeout : Time::Span? = nil) if timeout seconds, nanoseconds = System::Time.monotonic @@ -55,4 +59,8 @@ struct Crystal::Evented::Event # NOTE: musn't be changed after registering the event into `Timers`! def wake_at=(@wake_at) end + + def heap_compare(other : Pointer(self)) : Bool + wake_at < other.value.wake_at + end end diff --git a/src/crystal/system/unix/evented/timers.cr b/src/crystal/system/unix/evented/timers.cr index ace4fefcf09b..7b6deac4f543 100644 --- a/src/crystal/system/unix/evented/timers.cr +++ b/src/crystal/system/unix/evented/timers.cr @@ -1,86 +1,58 @@ +require "crystal/pointer_pairing_heap" + # List of `Event` ordered by `Event#wake_at` ascending. Optimized for fast # dequeue and determining when is the next timer event. # -# Thread unsafe: parallel accesses much be protected. +# Thread unsafe: parallel accesses much be protected! # -# NOTE: this is a struct because it only wraps a const pointer to a deque +# NOTE: this is a struct because it only wraps a const pointer to an object # allocated in the heap. -# -# OPTIMIZE: consider a skiplist for faster lookups (add/delete). -# -# OPTIMIZE: we could avoid memmove on add/delete by allocating a buffer, putting -# entries at whatever available index in the buffer, and linking entries in -# order (using indices so we can realloc the buffer); we'd have to keep a list -# of free indexes, too. It could be a good combo of unbounded linked list while -# retaining some memory locality. It should even be compatible with a skiplist -# (e.g. make entries a fixed height tower instead of prev/next node). struct Crystal::Evented::Timers def initialize - @list = Deque(Evented::Event*).new + @heap = PointerPairingHeap(Evented::Event).new end def empty? : Bool - @list.empty? + @heap.empty? end - # Returns the time at which the next timer is supposed to run. + # Returns the time of the next ready timer (if any). def next_ready? : Time::Span? - @list.first?.try(&.value.wake_at) + @heap.first?.try(&.value.wake_at) end # Dequeues and yields each ready timer (their `#wake_at` is lower than # `System::Time.monotonic`) from the oldest to the most recent (i.e. time # ascending). def dequeue_ready(& : Evented::Event* -> Nil) : Nil - return if @list.empty? - seconds, nanoseconds = System::Time.monotonic now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds) - n = 0 - @list.each do |event| + while event = @heap.first? break if event.value.wake_at > now + @heap.shift? yield event - n += 1 end - - # OPTIMIZE: consume the n entries at once - n.times { @list.shift } end # Add a new timer into the list. Returns true if it is the next ready timer. def add(event : Evented::Event*) : Bool - if @list.empty? - @list << event - true - elsif index = lookup(event.value.wake_at) - @list.insert(index, event) - index == 0 - else - @list.push(event) - false - end - end - - private def lookup(wake_at) - @list.each_with_index do |event, index| - return index if event.value.wake_at >= wake_at - end + @heap.add(event) + @heap.first? == event end # Remove a timer from the list. Returns a tuple(dequeued, was_next_ready) of # booleans. The first bool tells whether the event was dequeued, in which case # the second one tells if it was the next ready event. def delete(event : Evented::Event*) : {Bool, Bool} - if index = @list.index(event) - @list.delete_at(index) - {true, index.zero?} + if @heap.first? == event + @heap.shift? + {true, true} + elsif event.value.heap_previous? + @heap.delete(event) + {true, false} else {false, false} end end - - def each(&) : Nil - @list.each { |event| yield event } - end end From b87d3e8a0ec7a8439e3e561ddde483e59cf21994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Wed, 27 Nov 2024 01:56:43 -0800 Subject: [PATCH 1492/1551] Disallow weird assignments (#14815) --- spec/compiler/parser/parser_spec.cr | 47 +++++++++++++++++++++++++++ spec/support/syntax.cr | 4 +-- src/compiler/crystal/syntax/ast.cr | 1 + src/compiler/crystal/syntax/parser.cr | 26 +++++++++++---- 4 files changed, 70 insertions(+), 8 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 09569b88f003..897e5bf7060c 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -204,6 +204,8 @@ module Crystal it_parses "a = 1", Assign.new("a".var, 1.int32) it_parses "a = b = 2", Assign.new("a".var, Assign.new("b".var, 2.int32)) + it_parses "a[] = 1", Call.new("a".call, "[]=", 1.int32) + it_parses "a.[] = 1", Call.new("a".call, "[]=", 1.int32) it_parses "a, b = 1, 2", MultiAssign.new(["a".var, "b".var] of ASTNode, [1.int32, 2.int32] of ASTNode) it_parses "a, b = 1", MultiAssign.new(["a".var, "b".var] of ASTNode, [1.int32] of ASTNode) @@ -276,6 +278,10 @@ module Crystal assert_syntax_error "a.b() += 1" assert_syntax_error "a.[]() += 1" + assert_syntax_error "a.[] 0 = 1" + assert_syntax_error "a.[] 0 += 1" + assert_syntax_error "a b: 0 = 1" + it_parses "def foo\n1\nend", Def.new("foo", body: 1.int32) it_parses "def downto(n)\n1\nend", Def.new("downto", ["n".arg], 1.int32) it_parses "def foo ; 1 ; end", Def.new("foo", body: 1.int32) @@ -524,11 +530,15 @@ module Crystal it_parses "foo &.+(2)", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "+", 2.int32))) it_parses "foo &.bar.baz", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "bar"), "baz"))) it_parses "foo(&.bar.baz)", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "bar"), "baz"))) + it_parses "foo &.block[]", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "block"), "[]"))) it_parses "foo &.block[0]", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "block"), "[]", 0.int32))) it_parses "foo &.block=(0)", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "block=", 0.int32))) it_parses "foo &.block = 0", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "block=", 0.int32))) + it_parses "foo &.block[] = 1", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "block"), "[]=", 1.int32))) it_parses "foo &.block[0] = 1", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "block"), "[]=", 0.int32, 1.int32))) + it_parses "foo &.[]", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "[]"))) it_parses "foo &.[0]", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "[]", 0.int32))) + it_parses "foo &.[] = 1", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "[]=", 1.int32))) it_parses "foo &.[0] = 1", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "[]=", 0.int32, 1.int32))) it_parses "foo(&.is_a?(T))", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], IsA.new(Var.new("__arg0"), "T".path))) it_parses "foo(&.!)", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Not.new(Var.new("__arg0")))) @@ -2226,6 +2236,31 @@ module Crystal assert_syntax_error "lib Foo%end", %(unexpected token: "%") + assert_syntax_error "foo.[]? = 1" + assert_syntax_error "foo.[]? += 1" + assert_syntax_error "foo[0]? = 1" + assert_syntax_error "foo[0]? += 1" + assert_syntax_error "foo.[0]? = 1" + assert_syntax_error "foo.[0]? += 1" + assert_syntax_error "foo &.[0]? = 1" + assert_syntax_error "foo &.[0]? += 1" + + assert_syntax_error "foo &.[]?=(1)" + assert_syntax_error "foo &.[]? = 1" + assert_syntax_error "foo &.[]? 0 =(1)" + assert_syntax_error "foo &.[]? 0 = 1" + assert_syntax_error "foo &.[]?(0)=(1)" + assert_syntax_error "foo &.[]?(0) = 1" + assert_syntax_error "foo &.[] 0 =(1)" + assert_syntax_error "foo &.[] 0 = 1" + assert_syntax_error "foo &.[](0)=(1)" + assert_syntax_error "foo &.[](0) = 1" + + assert_syntax_error "foo &.bar.[] 0 =(1)" + assert_syntax_error "foo &.bar.[] 0 = 1" + assert_syntax_error "foo &.bar.[](0)=(1)" + assert_syntax_error "foo &.bar.[](0) = 1" + describe "end locations" do assert_end_location "nil" assert_end_location "false" @@ -3021,5 +3056,17 @@ module Crystal node = Parser.parse(source).as(Annotation).path node_source(source, node).should eq("::Foo") end + + it "sets args_in_brackets to false for `a.b`" do + parser = Parser.new("a.b") + node = parser.parse.as(Call) + node.args_in_brackets?.should be_false + end + + it "sets args_in_brackets to true for `a[b]`" do + parser = Parser.new("a[b]") + node = parser.parse.as(Call) + node.args_in_brackets?.should be_true + end end end diff --git a/spec/support/syntax.cr b/spec/support/syntax.cr index e1fd8f43d951..a6fe6286d11b 100644 --- a/spec/support/syntax.cr +++ b/spec/support/syntax.cr @@ -133,8 +133,8 @@ class Crystal::ASTNode end end -def assert_syntax_error(str, message = nil, line = nil, column = nil, metafile = __FILE__, metaline = __LINE__, metaendline = __END_LINE__) - it "says syntax error on #{str.inspect}", metafile, metaline, metaendline do +def assert_syntax_error(str, message = nil, line = nil, column = nil, metafile = __FILE__, metaline = __LINE__, metaendline = __END_LINE__, *, focus : Bool = false) + it "says syntax error on #{str.inspect}", metafile, metaline, metaendline, focus: focus do begin parse str fail "Expected SyntaxException to be raised", metafile, metaline diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index f6d314371034..9ccd8dda1f69 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -653,6 +653,7 @@ module Crystal property visibility = Visibility::Public property? global : Bool property? expansion = false + property? args_in_brackets = false property? has_parentheses = false def initialize(@obj, @name, @args = [] of ASTNode, @block = nil, @block_arg = nil, @named_args = nil, @global : Bool = false) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 1f0b6160a363..da2a6b7a4f42 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -735,6 +735,9 @@ module Crystal case @token.type when .op_eq? + atomic = Call.new(atomic, name) + unexpected_token unless can_be_assigned?(atomic) + # Rewrite 'f.x = arg' as f.x=(arg) next_token @@ -760,15 +763,20 @@ module Crystal end_location = arg.end_location end - atomic = Call.new(atomic, "#{name}=", arg).at(location).at_end(end_location) + atomic.at(location).at_end(end_location) + atomic.name = "#{name}=" + atomic.args = [arg] of ASTNode atomic.name_location = name_location next when .assignment_operator? + call = Call.new(atomic, name) + unexpected_token unless can_be_assigned?(call) + op_name_location = @token.location method = @token.type.to_s.byte_slice(0, @token.type.to_s.size - 1) next_token_skip_space_or_newline value = parse_op_assign - call = Call.new(atomic, name).at(location) + call.at(location) call.name_location = name_location atomic = OpAssign.new(call, method, value).at(location) atomic.name_location = op_name_location @@ -848,7 +856,8 @@ module Crystal atomic = Call.new(atomic, method_name, (args || [] of ASTNode), block, block_arg, named_args).at(location) atomic.name_location = name_location atomic.end_location = end_location - atomic.name_size = 0 if atomic.is_a?(Call) + atomic.name_size = 0 + atomic.args_in_brackets = true atomic else break @@ -1622,7 +1631,7 @@ module Crystal elsif @token.type.op_lsquare? call = parse_atomic_method_suffix obj, location - if @token.type.op_eq? && call.is_a?(Call) + if @token.type.op_eq? && call.is_a?(Call) && can_be_assigned?(call) next_token_skip_space exp = parse_op_assign call.name = "#{call.name}=" @@ -1643,6 +1652,8 @@ module Crystal call = call.as(Call) if @token.type.op_eq? + unexpected_token unless can_be_assigned?(call) + next_token_skip_space if @token.type.op_lparen? next_token_skip_space @@ -1660,7 +1671,7 @@ module Crystal else call = parse_atomic_method_suffix call, location - if @token.type.op_eq? && call.is_a?(Call) && call.name == "[]" + if @token.type.op_eq? && call.is_a?(Call) && can_be_assigned?(call) next_token_skip_space exp = parse_op_assign call.name = "#{call.name}=" @@ -6187,7 +6198,10 @@ module Crystal when Var, InstanceVar, ClassVar, Path, Global, Underscore true when Call - !node.has_parentheses? && ((node.obj.nil? && node.args.empty? && node.block.nil?) || node.name == "[]") + return false if node.has_parentheses? + no_args = node.args.empty? && node.named_args.nil? && node.block.nil? + return true if Lexer.ident?(node.name) && no_args + node.name == "[]" && (node.args_in_brackets? || no_args) else false end From 62638f4bb652bb2506af508dcd24da48e724d5b0 Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Sun, 1 Dec 2024 09:29:17 -0600 Subject: [PATCH 1493/1551] Use safe var name in getter/setter/property macro (#15239) Previously, using a local variable called `value` or calling a method with that name without an explicit `self` receiver would collide with the one defined in the `getter`, `setter`, and `property` blocks. This commit uses a safe variable name to avoid that collision. --- src/object.cr | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/object.cr b/src/object.cr index 800736687788..4443eaec3916 100644 --- a/src/object.cr +++ b/src/object.cr @@ -457,18 +457,18 @@ class Object {{var_prefix}}\{{name.var.id}} : \{{name.type}}? def {{method_prefix}}\{{name.var.id}} : \{{name.type}} - if (value = {{var_prefix}}\{{name.var.id}}).nil? + if (%value = {{var_prefix}}\{{name.var.id}}).nil? {{var_prefix}}\{{name.var.id}} = \{{yield}} else - value + %value end end \{% else %} def {{method_prefix}}\{{name.id}} - if (value = {{var_prefix}}\{{name.id}}).nil? + if (%value = {{var_prefix}}\{{name.id}}).nil? {{var_prefix}}\{{name.id}} = \{{yield}} else - value + %value end end \{% end %} @@ -561,10 +561,10 @@ class Object end def {{method_prefix}}\{{name.var.id}} : \{{name.type}} - if (value = {{var_prefix}}\{{name.var.id}}).nil? + if (%value = {{var_prefix}}\{{name.var.id}}).nil? ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.var.id}} cannot be nil") else - value + %value end end \{% else %} @@ -573,10 +573,10 @@ class Object end def {{method_prefix}}\{{name.id}} - if (value = {{var_prefix}}\{{name.id}}).nil? + if (%value = {{var_prefix}}\{{name.id}}).nil? ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.id}} cannot be nil") else - value + %value end end \{% end %} @@ -688,18 +688,18 @@ class Object {{var_prefix}}\{{name.var.id}} : \{{name.type}}? def {{method_prefix}}\{{name.var.id}}? : \{{name.type}} - if (value = {{var_prefix}}\{{name.var.id}}).nil? + if (%value = {{var_prefix}}\{{name.var.id}}).nil? {{var_prefix}}\{{name.var.id}} = \{{yield}} else - value + %value end end \{% else %} def {{method_prefix}}\{{name.id}}? - if (value = {{var_prefix}}\{{name.id}}).nil? + if (%value = {{var_prefix}}\{{name.id}}).nil? {{var_prefix}}\{{name.id}} = \{{yield}} else - value + %value end end \{% end %} @@ -970,10 +970,10 @@ class Object {{var_prefix}}\{{name.var.id}} : \{{name.type}}? def {{method_prefix}}\{{name.var.id}} : \{{name.type}} - if (value = {{var_prefix}}\{{name.var.id}}).nil? + if (%value = {{var_prefix}}\{{name.var.id}}).nil? {{var_prefix}}\{{name.var.id}} = \{{yield}} else - value + %value end end @@ -981,10 +981,10 @@ class Object end \{% else %} def {{method_prefix}}\{{name.id}} - if (value = {{var_prefix}}\{{name.id}}).nil? + if (%value = {{var_prefix}}\{{name.id}}).nil? {{var_prefix}}\{{name.id}} = \{{yield}} else - value + %value end end @@ -1216,10 +1216,10 @@ class Object {{var_prefix}}\{{name.var.id}} : \{{name.type}}? def {{method_prefix}}\{{name.var.id}}? : \{{name.type}} - if (value = {{var_prefix}}\{{name.var.id}}).nil? + if (%value = {{var_prefix}}\{{name.var.id}}).nil? {{var_prefix}}\{{name.var.id}} = \{{yield}} else - value + %value end end @@ -1227,10 +1227,10 @@ class Object end \{% else %} def {{method_prefix}}\{{name.id}}? - if (value = {{var_prefix}}\{{name.id}}).nil? + if (%value = {{var_prefix}}\{{name.id}}).nil? {{var_prefix}}\{{name.id}} = \{{yield}} else - value + %value end end From 4cf574852bd968ea3ba2c88bac0dd37bf3e1325a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 1 Dec 2024 19:41:50 +0100 Subject: [PATCH 1494/1551] Fix proper error handling for early end in `HTTP_DATE` parser (#15232) --- spec/std/http/http_spec.cr | 62 ++++++++++++++++------------- src/time/format/custom/http_date.cr | 1 + 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/spec/std/http/http_spec.cr b/spec/std/http/http_spec.cr index 6159cdc9dc5e..84eb50197ad7 100644 --- a/spec/std/http/http_spec.cr +++ b/spec/std/http/http_spec.cr @@ -11,39 +11,45 @@ private def http_quote_string(string) end describe HTTP do - it "parses RFC 1123" do - time = Time.utc(1994, 11, 6, 8, 49, 37) - HTTP.parse_time("Sun, 06 Nov 1994 08:49:37 GMT").should eq(time) - end + describe ".parse_time" do + it "parses RFC 1123" do + time = Time.utc(1994, 11, 6, 8, 49, 37) + HTTP.parse_time("Sun, 06 Nov 1994 08:49:37 GMT").should eq(time) + end - it "parses RFC 1123 without day name" do - time = Time.utc(1994, 11, 6, 8, 49, 37) - HTTP.parse_time("06 Nov 1994 08:49:37 GMT").should eq(time) - end + it "parses RFC 1123 without day name" do + time = Time.utc(1994, 11, 6, 8, 49, 37) + HTTP.parse_time("06 Nov 1994 08:49:37 GMT").should eq(time) + end - it "parses RFC 1036" do - time = Time.utc(1994, 11, 6, 8, 49, 37) - HTTP.parse_time("Sunday, 06-Nov-94 08:49:37 GMT").should eq(time) - end + it "parses RFC 1036" do + time = Time.utc(1994, 11, 6, 8, 49, 37) + HTTP.parse_time("Sunday, 06-Nov-94 08:49:37 GMT").should eq(time) + end - it "parses ANSI C" do - time = Time.utc(1994, 11, 6, 8, 49, 37) - HTTP.parse_time("Sun Nov 6 08:49:37 1994").should eq(time) - time2 = Time.utc(1994, 11, 16, 8, 49, 37) - HTTP.parse_time("Sun Nov 16 08:49:37 1994").should eq(time2) - end + it "parses ANSI C" do + time = Time.utc(1994, 11, 6, 8, 49, 37) + HTTP.parse_time("Sun Nov 6 08:49:37 1994").should eq(time) + time2 = Time.utc(1994, 11, 16, 8, 49, 37) + HTTP.parse_time("Sun Nov 16 08:49:37 1994").should eq(time2) + end - it "parses and is UTC (#2744)" do - date = "Mon, 09 Sep 2011 23:36:00 GMT" - parsed_time = HTTP.parse_time(date).not_nil! - parsed_time.utc?.should be_true - end + it "parses and is UTC (#2744)" do + date = "Mon, 09 Sep 2011 23:36:00 GMT" + parsed_time = HTTP.parse_time(date).not_nil! + parsed_time.utc?.should be_true + end - it "parses and is local (#2744)" do - date = "Mon, 09 Sep 2011 23:36:00 -0300" - parsed_time = HTTP.parse_time(date).not_nil! - parsed_time.offset.should eq -3 * 3600 - parsed_time.to_utc.to_s.should eq("2011-09-10 02:36:00 UTC") + it "parses and is local (#2744)" do + date = "Mon, 09 Sep 2011 23:36:00 -0300" + parsed_time = HTTP.parse_time(date).not_nil! + parsed_time.offset.should eq -3 * 3600 + parsed_time.to_utc.to_s.should eq("2011-09-10 02:36:00 UTC") + end + + it "handles errors" do + HTTP.parse_time("Thu").should be_nil + end end describe "generates HTTP date" do diff --git a/src/time/format/custom/http_date.cr b/src/time/format/custom/http_date.cr index d9ca38b9d7e5..25847b21aa00 100644 --- a/src/time/format/custom/http_date.cr +++ b/src/time/format/custom/http_date.cr @@ -102,6 +102,7 @@ struct Time::Format ansi_c_format = current_char != ',' next_char unless ansi_c_format + raise "Invalid date format" unless current_char.ascii_whitespace? whitespace ansi_c_format From a6a0f9e815eee8e2abf3c6c11266ec4e34f6de27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 1 Dec 2024 19:42:06 +0100 Subject: [PATCH 1495/1551] Add location to `RegexLiteral` (#15235) --- src/compiler/crystal/syntax/parser.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index da2a6b7a4f42..569bbd4d9409 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -2117,7 +2117,7 @@ module Crystal raise "invalid regex: #{regex_error}", location end - result = RegexLiteral.new(result, options) + result = RegexLiteral.new(result, options).at(location) else # no special treatment end From 1b02592fbbcdd7c83dd8b1af2cadd74c05b030ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 1 Dec 2024 19:42:25 +0100 Subject: [PATCH 1496/1551] Add specs for signal exit (#15229) --- spec/std/process/status_spec.cr | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 470a0a1a34d9..bdfb2ee38d26 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -9,6 +9,16 @@ private def exit_status(status) {% end %} end +private def status_for(exit_reason : Process::ExitReason) + exit_code = case exit_reason + when .interrupted? + {% if flag?(:unix) %}Signal::INT.value{% else %}LibC::STATUS_CONTROL_C_EXIT{% end %} + else + raise NotImplementedError.new("status_for") + end + Process::Status.new(exit_code) +end + describe Process::Status do it "#exit_code" do Process::Status.new(exit_status(0)).exit_code.should eq 0 @@ -16,6 +26,8 @@ describe Process::Status do Process::Status.new(exit_status(127)).exit_code.should eq 127 Process::Status.new(exit_status(128)).exit_code.should eq 128 Process::Status.new(exit_status(255)).exit_code.should eq 255 + + status_for(:interrupted).exit_code.should eq({% if flag?(:unix) %}0{% else %}LibC::STATUS_CONTROL_C_EXIT.to_i32!{% end %}) end it "#success?" do @@ -24,6 +36,8 @@ describe Process::Status do Process::Status.new(exit_status(127)).success?.should be_false Process::Status.new(exit_status(128)).success?.should be_false Process::Status.new(exit_status(255)).success?.should be_false + + status_for(:interrupted).success?.should be_false end it "#normal_exit?" do @@ -32,6 +46,8 @@ describe Process::Status do Process::Status.new(exit_status(127)).normal_exit?.should be_true Process::Status.new(exit_status(128)).normal_exit?.should be_true Process::Status.new(exit_status(255)).normal_exit?.should be_true + + status_for(:interrupted).normal_exit?.should eq {{ flag?(:win32) }} end it "#signal_exit?" do @@ -40,6 +56,8 @@ describe Process::Status do Process::Status.new(exit_status(127)).signal_exit?.should be_false Process::Status.new(exit_status(128)).signal_exit?.should be_false Process::Status.new(exit_status(255)).signal_exit?.should be_false + + status_for(:interrupted).signal_exit?.should eq {{ !flag?(:win32) }} end it "equality" do From 8e339d19f2d3b885ca6b337cde42c7aa766890dd Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sun, 1 Dec 2024 22:34:21 +0100 Subject: [PATCH 1497/1551] Crystal::EventLoop namespace (#15226) The current event loops are scattered a bit everywhere, or buried inside `src/crystal/system/x` when they don't use the `Crystal::System` namespace. This patch groups all the implementations under `Crystal::EventLoop` in `src/crystal/event_loop`. Of course the actual system parts are kept in (or moved to) `Crystal::System`. - `Crystal::Evented::EventLoop` => `Crystal::EventLoop::Polling` (abstract) - `Crystal::Epoll::EventLoop` => `Crystal::EventLoop::Epoll` - `Crystal::Kqueue::EventLoop` => `Crystal::EventLoop::Kqueue` - `Crystal::LibEvent::EventLoop` => `Crystal::EventLoop::LibEvent` - `Crystal::IOCP::EventLoop` => `Crystal::EventLoop::IOCP` - `Crystal::IOCP` => `Crystal::System::IOCP` A new evloop, for example `io_uring`, would naturally be implemented as `Crystal::EventLoop::IoUring`. --- .../polling}/arena_spec.cr | 54 ++++---- .../polling}/poll_descriptor_spec.cr | 38 +++--- .../polling}/timers_spec.cr | 48 +++---- .../polling}/waiters_spec.cr | 58 ++++---- src/crystal/{system => }/event_loop.cr | 24 ++-- .../event_loop.cr => event_loop/epoll.cr} | 18 +-- .../event_loop/file_descriptor.cr | 0 .../event_loop_iocp.cr => event_loop/iocp.cr} | 30 ++--- .../event_loop.cr => event_loop/kqueue.cr} | 16 +-- .../libevent.cr} | 10 +- .../libevent/event.cr} | 4 +- .../libevent}/lib_event2.cr | 0 .../event_loop.cr => event_loop/polling.cr} | 124 +++++++++--------- .../evented => event_loop/polling}/arena.cr | 4 +- .../evented => event_loop/polling}/event.cr | 2 +- .../polling}/fiber_event.cr | 6 +- .../polling}/poll_descriptor.cr | 6 +- .../evented => event_loop/polling}/timers.cr | 10 +- .../evented => event_loop/polling}/waiters.cr | 4 +- src/crystal/{system => }/event_loop/socket.cr | 2 +- .../wasi/event_loop.cr => event_loop/wasi.cr} | 4 +- src/crystal/scheduler.cr | 2 +- src/crystal/system/socket.cr | 2 +- src/crystal/system/unix/socket.cr | 4 +- src/crystal/system/win32/addrinfo.cr | 6 +- src/crystal/system/win32/file_descriptor.cr | 8 +- src/crystal/system/win32/iocp.cr | 16 +-- src/io/evented.cr | 4 +- src/kernel.cr | 2 +- 29 files changed, 253 insertions(+), 253 deletions(-) rename spec/std/crystal/{evented => event_loop/polling}/arena_spec.cr (70%) rename spec/std/crystal/{evented => event_loop/polling}/poll_descriptor_spec.cr (60%) rename spec/std/crystal/{evented => event_loop/polling}/timers_spec.cr (51%) rename spec/std/crystal/{evented => event_loop/polling}/waiters_spec.cr (61%) rename src/crystal/{system => }/event_loop.cr (86%) rename src/crystal/{system/unix/epoll/event_loop.cr => event_loop/epoll.cr} (91%) rename src/crystal/{system => }/event_loop/file_descriptor.cr (100%) rename src/crystal/{system/win32/event_loop_iocp.cr => event_loop/iocp.cr} (89%) rename src/crystal/{system/unix/kqueue/event_loop.cr => event_loop/kqueue.cr} (94%) rename src/crystal/{system/unix/event_loop_libevent.cr => event_loop/libevent.cr} (95%) rename src/crystal/{system/unix/event_libevent.cr => event_loop/libevent/event.cr} (96%) rename src/crystal/{system/unix => event_loop/libevent}/lib_event2.cr (100%) rename src/crystal/{system/unix/evented/event_loop.cr => event_loop/polling.cr} (93%) rename src/crystal/{system/unix/evented => event_loop/polling}/arena.cr (97%) rename src/crystal/{system/unix/evented => event_loop/polling}/event.cr (97%) rename src/crystal/{system/unix/evented => event_loop/polling}/fiber_event.cr (87%) rename src/crystal/{system/unix/evented => event_loop/polling}/poll_descriptor.cr (94%) rename src/crystal/{system/unix/evented => event_loop/polling}/timers.cr (86%) rename src/crystal/{system/unix/evented => event_loop/polling}/waiters.cr (97%) rename src/crystal/{system => }/event_loop/socket.cr (96%) rename src/crystal/{system/wasi/event_loop.cr => event_loop/wasi.cr} (97%) diff --git a/spec/std/crystal/evented/arena_spec.cr b/spec/std/crystal/event_loop/polling/arena_spec.cr similarity index 70% rename from spec/std/crystal/evented/arena_spec.cr rename to spec/std/crystal/event_loop/polling/arena_spec.cr index edf5fd90e11b..66e83be3b192 100644 --- a/spec/std/crystal/evented/arena_spec.cr +++ b/spec/std/crystal/event_loop/polling/arena_spec.cr @@ -1,11 +1,11 @@ -{% skip_file unless Crystal.has_constant?(:Evented) %} +{% skip_file unless Crystal::EventLoop.has_constant?(:Polling) %} require "spec" -describe Crystal::Evented::Arena do +describe Crystal::EventLoop::Polling::Arena do describe "#allocate_at?" do it "yields block when not allocated" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) pointer = nil index = nil called = 0 @@ -31,8 +31,8 @@ describe Crystal::Evented::Arena do end it "allocates up to capacity" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) - indexes = [] of Crystal::Evented::Arena::Index + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) + indexes = [] of Crystal::EventLoop::Polling::Arena::Index indexes = 32.times.map do |i| arena.allocate_at?(i) { |ptr, _| ptr.value = i } @@ -49,7 +49,7 @@ describe Crystal::Evented::Arena do end it "checks bounds" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) expect_raises(IndexError) { arena.allocate_at?(-1) { } } expect_raises(IndexError) { arena.allocate_at?(33) { } } end @@ -57,7 +57,7 @@ describe Crystal::Evented::Arena do describe "#get" do it "returns previously allocated object" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) pointer = nil index = arena.allocate_at(30) do |ptr| @@ -77,15 +77,15 @@ describe Crystal::Evented::Arena do end it "can't access unallocated object" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) expect_raises(RuntimeError) do - arena.get(Crystal::Evented::Arena::Index.new(10, 0)) { } + arena.get(Crystal::EventLoop::Polling::Arena::Index.new(10, 0)) { } end end it "checks generation" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) called = 0 index1 = arena.allocate_at(2) { called += 1 } @@ -102,15 +102,15 @@ describe Crystal::Evented::Arena do end it "checks out of bounds" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) - expect_raises(IndexError) { arena.get(Crystal::Evented::Arena::Index.new(-1, 0)) { } } - expect_raises(IndexError) { arena.get(Crystal::Evented::Arena::Index.new(33, 0)) { } } + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) + expect_raises(IndexError) { arena.get(Crystal::EventLoop::Polling::Arena::Index.new(-1, 0)) { } } + expect_raises(IndexError) { arena.get(Crystal::EventLoop::Polling::Arena::Index.new(33, 0)) { } } end end describe "#get?" do it "returns previously allocated object" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) pointer = nil index = arena.allocate_at(30) do |ptr| @@ -131,16 +131,16 @@ describe Crystal::Evented::Arena do end it "can't access unallocated index" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) called = 0 - ret = arena.get?(Crystal::Evented::Arena::Index.new(10, 0)) { called += 1 } + ret = arena.get?(Crystal::EventLoop::Polling::Arena::Index.new(10, 0)) { called += 1 } ret.should be_false called.should eq(0) end it "checks generation" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) called = 0 old_index = arena.allocate_at(2) { } @@ -166,11 +166,11 @@ describe Crystal::Evented::Arena do end it "checks out of bounds" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) called = 0 - arena.get?(Crystal::Evented::Arena::Index.new(-1, 0)) { called += 1 }.should be_false - arena.get?(Crystal::Evented::Arena::Index.new(33, 0)) { called += 1 }.should be_false + arena.get?(Crystal::EventLoop::Polling::Arena::Index.new(-1, 0)) { called += 1 }.should be_false + arena.get?(Crystal::EventLoop::Polling::Arena::Index.new(33, 0)) { called += 1 }.should be_false called.should eq(0) end @@ -178,7 +178,7 @@ describe Crystal::Evented::Arena do describe "#free" do it "deallocates the object" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) index1 = arena.allocate_at(3) { |ptr| ptr.value = 123 } arena.free(index1) { } @@ -192,7 +192,7 @@ describe Crystal::Evented::Arena do end it "checks generation" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) called = 0 old_index = arena.allocate_at(1) { } @@ -214,19 +214,19 @@ describe Crystal::Evented::Arena do end it "checks out of bounds" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) called = 0 - arena.free(Crystal::Evented::Arena::Index.new(-1, 0)) { called += 1 } - arena.free(Crystal::Evented::Arena::Index.new(33, 0)) { called += 1 } + arena.free(Crystal::EventLoop::Polling::Arena::Index.new(-1, 0)) { called += 1 } + arena.free(Crystal::EventLoop::Polling::Arena::Index.new(33, 0)) { called += 1 } called.should eq(0) end end it "#each_index" do - arena = Crystal::Evented::Arena(Int32, 96).new(32) - indices = [] of {Int32, Crystal::Evented::Arena::Index} + arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32) + indices = [] of {Int32, Crystal::EventLoop::Polling::Arena::Index} arena.each_index { |i, index| indices << {i, index} } indices.should be_empty diff --git a/spec/std/crystal/evented/poll_descriptor_spec.cr b/spec/std/crystal/event_loop/polling/poll_descriptor_spec.cr similarity index 60% rename from spec/std/crystal/evented/poll_descriptor_spec.cr rename to spec/std/crystal/event_loop/polling/poll_descriptor_spec.cr index a5719f7ff7a8..04c090e7b83f 100644 --- a/spec/std/crystal/evented/poll_descriptor_spec.cr +++ b/spec/std/crystal/event_loop/polling/poll_descriptor_spec.cr @@ -1,9 +1,9 @@ -{% skip_file unless Crystal.has_constant?(:Evented) %} +{% skip_file unless Crystal::EventLoop.has_constant?(:Polling) %} require "spec" -class Crystal::Evented::FakeLoop < Crystal::Evented::EventLoop - getter operations = [] of {Symbol, Int32, Crystal::Evented::Arena::Index | Bool} +class Crystal::EventLoop::FakeLoop < Crystal::EventLoop::Polling + getter operations = [] of {Symbol, Int32, Arena::Index | Bool} private def system_run(blocking : Bool, & : Fiber ->) : Nil end @@ -27,13 +27,13 @@ class Crystal::Evented::FakeLoop < Crystal::Evented::EventLoop end end -describe Crystal::Evented::Waiters do +describe Crystal::EventLoop::Polling::Waiters do describe "#take_ownership" do it "associates a poll descriptor to an evloop instance" do fd = Int32::MAX - pd = Crystal::Evented::PollDescriptor.new - index = Crystal::Evented::Arena::Index.new(fd, 0) - evloop = Crystal::Evented::FakeLoop.new + pd = Crystal::EventLoop::Polling::PollDescriptor.new + index = Crystal::EventLoop::Polling::Arena::Index.new(fd, 0) + evloop = Crystal::EventLoop::Polling::FakeLoop.new pd.take_ownership(evloop, fd, index) pd.@event_loop.should be(evloop) @@ -45,11 +45,11 @@ describe Crystal::Evented::Waiters do it "moves a poll descriptor to another evloop instance" do fd = Int32::MAX - pd = Crystal::Evented::PollDescriptor.new - index = Crystal::Evented::Arena::Index.new(fd, 0) + pd = Crystal::EventLoop::Polling::PollDescriptor.new + index = Crystal::EventLoop::Polling::Arena::Index.new(fd, 0) - evloop1 = Crystal::Evented::FakeLoop.new - evloop2 = Crystal::Evented::FakeLoop.new + evloop1 = Crystal::EventLoop::Polling::FakeLoop.new + evloop2 = Crystal::EventLoop::Polling::FakeLoop.new pd.take_ownership(evloop1, fd, index) pd.take_ownership(evloop2, fd, index) @@ -67,10 +67,10 @@ describe Crystal::Evented::Waiters do it "can't move to the current evloop" do fd = Int32::MAX - pd = Crystal::Evented::PollDescriptor.new - index = Crystal::Evented::Arena::Index.new(fd, 0) + pd = Crystal::EventLoop::Polling::PollDescriptor.new + index = Crystal::EventLoop::Polling::Arena::Index.new(fd, 0) - evloop = Crystal::Evented::FakeLoop.new + evloop = Crystal::EventLoop::Polling::FakeLoop.new pd.take_ownership(evloop, fd, index) expect_raises(Exception) { pd.take_ownership(evloop, fd, index) } @@ -78,15 +78,15 @@ describe Crystal::Evented::Waiters do it "can't move with pending waiters" do fd = Int32::MAX - pd = Crystal::Evented::PollDescriptor.new - index = Crystal::Evented::Arena::Index.new(fd, 0) - event = Crystal::Evented::Event.new(:io_read, Fiber.current) + pd = Crystal::EventLoop::Polling::PollDescriptor.new + index = Crystal::EventLoop::Polling::Arena::Index.new(fd, 0) + event = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) - evloop1 = Crystal::Evented::FakeLoop.new + evloop1 = Crystal::EventLoop::Polling::FakeLoop.new pd.take_ownership(evloop1, fd, index) pd.@readers.add(pointerof(event)) - evloop2 = Crystal::Evented::FakeLoop.new + evloop2 = Crystal::EventLoop::Polling::FakeLoop.new expect_raises(RuntimeError) { pd.take_ownership(evloop2, fd, index) } pd.@event_loop.should be(evloop1) diff --git a/spec/std/crystal/evented/timers_spec.cr b/spec/std/crystal/event_loop/polling/timers_spec.cr similarity index 51% rename from spec/std/crystal/evented/timers_spec.cr rename to spec/std/crystal/event_loop/polling/timers_spec.cr index 9dccbf4f56f2..6f6b8a670b08 100644 --- a/spec/std/crystal/evented/timers_spec.cr +++ b/spec/std/crystal/event_loop/polling/timers_spec.cr @@ -1,13 +1,13 @@ -{% skip_file unless Crystal.has_constant?(:Evented) %} +{% skip_file unless Crystal::EventLoop.has_constant?(:Polling) %} require "spec" -describe Crystal::Evented::Timers do +describe Crystal::EventLoop::Polling::Timers do it "#empty?" do - timers = Crystal::Evented::Timers.new + timers = Crystal::EventLoop::Polling::Timers.new timers.empty?.should be_true - event = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 7.seconds) + event = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 7.seconds) timers.add(pointerof(event)) timers.empty?.should be_false @@ -17,13 +17,13 @@ describe Crystal::Evented::Timers do it "#next_ready?" do # empty - timers = Crystal::Evented::Timers.new + timers = Crystal::EventLoop::Polling::Timers.new timers.next_ready?.should be_nil # with events - event1s = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.second) - event3m = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 3.minutes) - event5m = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 5.minutes) + event1s = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.second) + event3m = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 3.minutes) + event5m = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 5.minutes) timers.add(pointerof(event5m)) timers.next_ready?.should eq(event5m.wake_at?) @@ -36,11 +36,11 @@ describe Crystal::Evented::Timers do end it "#dequeue_ready" do - timers = Crystal::Evented::Timers.new + timers = Crystal::EventLoop::Polling::Timers.new - event1 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds) - event2 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds) - event3 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.minute) + event1 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds) + event2 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds) + event3 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.minute) # empty called = 0 @@ -48,12 +48,12 @@ describe Crystal::Evented::Timers do called.should eq(0) # add events in non chronological order - timers = Crystal::Evented::Timers.new + timers = Crystal::EventLoop::Polling::Timers.new timers.add(pointerof(event1)) timers.add(pointerof(event3)) timers.add(pointerof(event2)) - events = [] of Crystal::Evented::Event* + events = [] of Crystal::EventLoop::Polling::Event* timers.dequeue_ready { |event| events << event } events.should eq([ @@ -64,12 +64,12 @@ describe Crystal::Evented::Timers do end it "#add" do - timers = Crystal::Evented::Timers.new + timers = Crystal::EventLoop::Polling::Timers.new - event0 = Crystal::Evented::Event.new(:sleep, Fiber.current) - event1 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds) - event2 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 2.minutes) - event3 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.minute) + event0 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current) + event1 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds) + event2 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 2.minutes) + event3 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.minute) # add events in non chronological order timers.add(pointerof(event1)).should be_true # added to the head (next ready) @@ -81,13 +81,13 @@ describe Crystal::Evented::Timers do end it "#delete" do - event1 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds) - event2 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds) - event3 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.minute) - event4 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 4.minutes) + event1 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds) + event2 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds) + event3 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.minute) + event4 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 4.minutes) # add events in non chronological order - timers = Crystal::Evented::Timers.new + timers = Crystal::EventLoop::Polling::Timers.new timers.add(pointerof(event1)) timers.add(pointerof(event3)) timers.add(pointerof(event2)) diff --git a/spec/std/crystal/evented/waiters_spec.cr b/spec/std/crystal/event_loop/polling/waiters_spec.cr similarity index 61% rename from spec/std/crystal/evented/waiters_spec.cr rename to spec/std/crystal/event_loop/polling/waiters_spec.cr index 91e145f6f811..7a72b591fba2 100644 --- a/spec/std/crystal/evented/waiters_spec.cr +++ b/spec/std/crystal/event_loop/polling/waiters_spec.cr @@ -1,32 +1,32 @@ -{% skip_file unless Crystal.has_constant?(:Evented) %} +{% skip_file unless Crystal::EventLoop.has_constant?(:Polling) %} require "spec" -describe Crystal::Evented::Waiters do +describe Crystal::EventLoop::Polling::Waiters do describe "#add" do it "adds event to list" do - waiters = Crystal::Evented::Waiters.new + waiters = Crystal::EventLoop::Polling::Waiters.new - event = Crystal::Evented::Event.new(:io_read, Fiber.current) + event = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) ret = waiters.add(pointerof(event)) ret.should be_true end it "doesn't add the event when the list is ready (race condition)" do - waiters = Crystal::Evented::Waiters.new + waiters = Crystal::EventLoop::Polling::Waiters.new waiters.ready_one { true } - event = Crystal::Evented::Event.new(:io_read, Fiber.current) + event = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) ret = waiters.add(pointerof(event)) ret.should be_false waiters.@ready.should be_false end it "doesn't add the event when the list is always ready" do - waiters = Crystal::Evented::Waiters.new + waiters = Crystal::EventLoop::Polling::Waiters.new waiters.ready_all { } - event = Crystal::Evented::Event.new(:io_read, Fiber.current) + event = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) ret = waiters.add(pointerof(event)) ret.should be_false waiters.@always_ready.should be_true @@ -35,8 +35,8 @@ describe Crystal::Evented::Waiters do describe "#delete" do it "removes the event from the list" do - waiters = Crystal::Evented::Waiters.new - event = Crystal::Evented::Event.new(:io_read, Fiber.current) + waiters = Crystal::EventLoop::Polling::Waiters.new + event = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) waiters.add(pointerof(event)) waiters.delete(pointerof(event)) @@ -47,15 +47,15 @@ describe Crystal::Evented::Waiters do end it "does nothing when the event isn't in the list" do - waiters = Crystal::Evented::Waiters.new - event = Crystal::Evented::Event.new(:io_read, Fiber.current) + waiters = Crystal::EventLoop::Polling::Waiters.new + event = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) waiters.delete(pointerof(event)) end end describe "#ready_one" do it "marks the list as ready when empty (race condition)" do - waiters = Crystal::Evented::Waiters.new + waiters = Crystal::EventLoop::Polling::Waiters.new called = false waiters.ready_one { called = true } @@ -65,10 +65,10 @@ describe Crystal::Evented::Waiters do end it "dequeues events in FIFO order" do - waiters = Crystal::Evented::Waiters.new - event1 = Crystal::Evented::Event.new(:io_read, Fiber.current) - event2 = Crystal::Evented::Event.new(:io_read, Fiber.current) - event3 = Crystal::Evented::Event.new(:io_read, Fiber.current) + waiters = Crystal::EventLoop::Polling::Waiters.new + event1 = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) + event2 = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) + event3 = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) called = 0 waiters.add(pointerof(event1)) @@ -97,10 +97,10 @@ describe Crystal::Evented::Waiters do end it "dequeues events until the block returns true" do - waiters = Crystal::Evented::Waiters.new - event1 = Crystal::Evented::Event.new(:io_read, Fiber.current) - event2 = Crystal::Evented::Event.new(:io_read, Fiber.current) - event3 = Crystal::Evented::Event.new(:io_read, Fiber.current) + waiters = Crystal::EventLoop::Polling::Waiters.new + event1 = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) + event2 = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) + event3 = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) called = 0 waiters.add(pointerof(event1)) @@ -115,9 +115,9 @@ describe Crystal::Evented::Waiters do end it "dequeues events until empty and marks the list as ready" do - waiters = Crystal::Evented::Waiters.new - event1 = Crystal::Evented::Event.new(:io_read, Fiber.current) - event2 = Crystal::Evented::Event.new(:io_read, Fiber.current) + waiters = Crystal::EventLoop::Polling::Waiters.new + event1 = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) + event2 = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) called = 0 waiters.add(pointerof(event1)) @@ -134,7 +134,7 @@ describe Crystal::Evented::Waiters do describe "#ready_all" do it "marks the list as always ready" do - waiters = Crystal::Evented::Waiters.new + waiters = Crystal::EventLoop::Polling::Waiters.new called = false waiters.ready_all { called = true } @@ -144,10 +144,10 @@ describe Crystal::Evented::Waiters do end it "dequeues all events" do - waiters = Crystal::Evented::Waiters.new - event1 = Crystal::Evented::Event.new(:io_read, Fiber.current) - event2 = Crystal::Evented::Event.new(:io_read, Fiber.current) - event3 = Crystal::Evented::Event.new(:io_read, Fiber.current) + waiters = Crystal::EventLoop::Polling::Waiters.new + event1 = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) + event2 = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) + event3 = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current) called = 0 waiters.add(pointerof(event1)) diff --git a/src/crystal/system/event_loop.cr b/src/crystal/event_loop.cr similarity index 86% rename from src/crystal/system/event_loop.cr rename to src/crystal/event_loop.cr index 33ff4f9dac85..45fc9e4f8558 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/event_loop.cr @@ -2,20 +2,20 @@ abstract class Crystal::EventLoop # Creates an event loop instance def self.create : self {% if flag?(:wasi) %} - Crystal::Wasi::EventLoop.new + Crystal::EventLoop::Wasi.new {% elsif flag?(:unix) %} # TODO: enable more targets by default (need manual tests or fixes) {% if flag?("evloop=libevent") %} - Crystal::LibEvent::EventLoop.new + Crystal::EventLoop::LibEvent.new {% elsif flag?("evloop=epoll") || flag?(:android) || flag?(:linux) %} - Crystal::Epoll::EventLoop.new + Crystal::EventLoop::Epoll.new {% elsif flag?("evloop=kqueue") || flag?(:darwin) || flag?(:freebsd) %} - Crystal::Kqueue::EventLoop.new + Crystal::EventLoop::Kqueue.new {% else %} - Crystal::LibEvent::EventLoop.new + Crystal::EventLoop::LibEvent.new {% end %} {% elsif flag?(:win32) %} - Crystal::IOCP::EventLoop.new + Crystal::EventLoop::IOCP.new {% else %} {% raise "Event loop not supported" %} {% end %} @@ -85,19 +85,19 @@ abstract class Crystal::EventLoop end {% if flag?(:wasi) %} - require "./wasi/event_loop" + require "./event_loop/wasi" {% elsif flag?(:unix) %} {% if flag?("evloop=libevent") %} - require "./unix/event_loop_libevent" + require "./event_loop/libevent" {% elsif flag?("evloop=epoll") || flag?(:android) || flag?(:linux) %} - require "./unix/epoll/event_loop" + require "./event_loop/epoll" {% elsif flag?("evloop=kqueue") || flag?(:darwin) || flag?(:freebsd) %} - require "./unix/kqueue/event_loop" + require "./event_loop/kqueue" {% else %} - require "./unix/event_loop_libevent" + require "./event_loop/libevent" {% end %} {% elsif flag?(:win32) %} - require "./win32/event_loop_iocp" + require "./event_loop/iocp" {% else %} {% raise "Event loop not supported" %} {% end %} diff --git a/src/crystal/system/unix/epoll/event_loop.cr b/src/crystal/event_loop/epoll.cr similarity index 91% rename from src/crystal/system/unix/epoll/event_loop.cr rename to src/crystal/event_loop/epoll.cr index 4850e68739f2..2d7d08ce7c94 100644 --- a/src/crystal/system/unix/epoll/event_loop.cr +++ b/src/crystal/event_loop/epoll.cr @@ -1,9 +1,9 @@ -require "../evented/event_loop" -require "../epoll" -require "../eventfd" -require "../timerfd" +require "./polling" +require "../system/unix/epoll" +require "../system/unix/eventfd" +require "../system/unix/timerfd" -class Crystal::Epoll::EventLoop < Crystal::Evented::EventLoop +class Crystal::EventLoop::Epoll < Crystal::EventLoop::Polling def initialize # the epoll instance @epoll = System::Epoll.new @@ -50,7 +50,7 @@ class Crystal::Epoll::EventLoop < Crystal::Evented::EventLoop system_set_timer(@timers.next_ready?) # re-add all registered fds - Evented.arena.each_index { |fd, index| system_add(fd, index) } + Polling.arena.each_index { |fd, index| system_add(fd, index) } end {% end %} @@ -87,12 +87,12 @@ class Crystal::Epoll::EventLoop < Crystal::Evented::EventLoop end private def process_io(epoll_event : LibC::EpollEvent*, &) : Nil - index = Evented::Arena::Index.new(epoll_event.value.data.u64) + index = Polling::Arena::Index.new(epoll_event.value.data.u64) events = epoll_event.value.events Crystal.trace :evloop, "event", fd: index.index, index: index.to_i64, events: events - Evented.arena.get?(index) do |pd| + Polling.arena.get?(index) do |pd| if (events & (LibC::EPOLLERR | LibC::EPOLLHUP)) != 0 pd.value.@readers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } } pd.value.@writers.ready_all { |event| unsafe_resume_io(event) { |fiber| yield fiber } } @@ -116,7 +116,7 @@ class Crystal::Epoll::EventLoop < Crystal::Evented::EventLoop @eventfd.write(1) if @interrupted.test_and_set end - protected def system_add(fd : Int32, index : Evented::Arena::Index) : Nil + protected def system_add(fd : Int32, index : Polling::Arena::Index) : Nil Crystal.trace :evloop, "epoll_ctl", op: "add", fd: fd, index: index.to_i64 events = LibC::EPOLLIN | LibC::EPOLLOUT | LibC::EPOLLRDHUP | LibC::EPOLLET @epoll.add(fd, events, u64: index.to_u64) diff --git a/src/crystal/system/event_loop/file_descriptor.cr b/src/crystal/event_loop/file_descriptor.cr similarity index 100% rename from src/crystal/system/event_loop/file_descriptor.cr rename to src/crystal/event_loop/file_descriptor.cr diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/event_loop/iocp.cr similarity index 89% rename from src/crystal/system/win32/event_loop_iocp.cr rename to src/crystal/event_loop/iocp.cr index 3089e36edfeb..ce3112fa9d1d 100644 --- a/src/crystal/system/win32/event_loop_iocp.cr +++ b/src/crystal/event_loop/iocp.cr @@ -1,11 +1,11 @@ require "c/ioapiset" require "crystal/system/print_error" -require "./iocp" +require "../system/win32/iocp" # :nodoc: -class Crystal::IOCP::EventLoop < Crystal::EventLoop +class Crystal::EventLoop::IOCP < Crystal::EventLoop # This is a list of resume and timeout events managed outside of IOCP. - @queue = Deque(Crystal::IOCP::Event).new + @queue = Deque(Event).new @lock = Crystal::SpinLock.new @interrupted = Atomic(Bool).new(false) @@ -63,7 +63,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop end wait_time = blocking ? (next_event.wake_at - now).total_milliseconds : 0 - timed_out = IOCP.wait_queued_completions(wait_time, alertable: blocking) do |fiber| + timed_out = System::IOCP.wait_queued_completions(wait_time, alertable: blocking) do |fiber| # This block may run multiple times. Every single fiber gets enqueued. fiber.enqueue end @@ -124,34 +124,34 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop LibC.QueueUserAPC(->(ptr : LibC::ULONG_PTR) { }, thread, LibC::ULONG_PTR.new(0)) end - def enqueue(event : Crystal::IOCP::Event) + def enqueue(event : Event) unless @queue.includes?(event) @queue << event end end - def dequeue(event : Crystal::IOCP::Event) + def dequeue(event : Event) @queue.delete(event) end # Create a new resume event for a fiber. def create_resume_event(fiber : Fiber) : Crystal::EventLoop::Event - Crystal::IOCP::Event.new(fiber) + Event.new(fiber) end def create_timeout_event(fiber) : Crystal::EventLoop::Event - Crystal::IOCP::Event.new(fiber, timeout: true) + Event.new(fiber, timeout: true) end def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 - IOCP.overlapped_operation(file_descriptor, "ReadFile", file_descriptor.read_timeout) do |overlapped| + System::IOCP.overlapped_operation(file_descriptor, "ReadFile", file_descriptor.read_timeout) do |overlapped| ret = LibC.ReadFile(file_descriptor.windows_handle, slice, slice.size, out byte_count, overlapped) {ret, byte_count} end.to_i32 end def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 - IOCP.overlapped_operation(file_descriptor, "WriteFile", file_descriptor.write_timeout, writing: true) do |overlapped| + System::IOCP.overlapped_operation(file_descriptor, "WriteFile", file_descriptor.write_timeout, writing: true) do |overlapped| ret = LibC.WriteFile(file_descriptor.windows_handle, slice, slice.size, out byte_count, overlapped) {ret, byte_count} end.to_i32 @@ -174,7 +174,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop def read(socket : ::Socket, slice : Bytes) : Int32 wsabuf = wsa_buffer(slice) - bytes_read = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSARecv", socket.read_timeout, connreset_is_error: false) do |overlapped| + bytes_read = System::IOCP.wsa_overlapped_operation(socket, socket.fd, "WSARecv", socket.read_timeout, connreset_is_error: false) do |overlapped| flags = 0_u32 ret = LibC.WSARecv(socket.fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), overlapped, nil) {ret, bytes_received} @@ -186,7 +186,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop def write(socket : ::Socket, slice : Bytes) : Int32 wsabuf = wsa_buffer(slice) - bytes = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSASend", socket.write_timeout) do |overlapped| + bytes = System::IOCP.wsa_overlapped_operation(socket, socket.fd, "WSASend", socket.write_timeout) do |overlapped| ret = LibC.WSASend(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, overlapped, nil) {ret, bytes_sent} end @@ -196,7 +196,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop def send_to(socket : ::Socket, slice : Bytes, address : ::Socket::Address) : Int32 wsabuf = wsa_buffer(slice) - bytes_written = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSASendTo", socket.write_timeout) do |overlapped| + bytes_written = System::IOCP.wsa_overlapped_operation(socket, socket.fd, "WSASendTo", socket.write_timeout) do |overlapped| ret = LibC.WSASendTo(socket.fd, pointerof(wsabuf), 1, out bytes_sent, 0, address, address.size, overlapped, nil) {ret, bytes_sent} end @@ -222,7 +222,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop wsabuf = wsa_buffer(slice) flags = 0_u32 - bytes_read = IOCP.wsa_overlapped_operation(socket, socket.fd, "WSARecvFrom", socket.read_timeout) do |overlapped| + bytes_read = System::IOCP.wsa_overlapped_operation(socket, socket.fd, "WSARecvFrom", socket.read_timeout) do |overlapped| ret = LibC.WSARecvFrom(socket.fd, pointerof(wsabuf), 1, out bytes_received, pointerof(flags), sockaddr, pointerof(addrlen), overlapped, nil) {ret, bytes_received} end @@ -279,7 +279,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop end end -class Crystal::IOCP::Event +class Crystal::EventLoop::IOCP::Event include Crystal::EventLoop::Event getter fiber diff --git a/src/crystal/system/unix/kqueue/event_loop.cr b/src/crystal/event_loop/kqueue.cr similarity index 94% rename from src/crystal/system/unix/kqueue/event_loop.cr rename to src/crystal/event_loop/kqueue.cr index eb55fde0cf37..52a7701ef2b1 100644 --- a/src/crystal/system/unix/kqueue/event_loop.cr +++ b/src/crystal/event_loop/kqueue.cr @@ -1,7 +1,7 @@ -require "../evented/event_loop" -require "../kqueue" +require "./polling" +require "../system/unix/kqueue" -class Crystal::Kqueue::EventLoop < Crystal::Evented::EventLoop +class Crystal::EventLoop::Kqueue < Crystal::EventLoop::Polling # the following are arbitrary numbers to identify specific events INTERRUPT_IDENTIFIER = 9 TIMER_IDENTIFIER = 10 @@ -66,7 +66,7 @@ class Crystal::Kqueue::EventLoop < Crystal::Evented::EventLoop system_set_timer(@timers.next_ready?) # re-add all registered fds - Evented.arena.each_index { |fd, index| system_add(fd, index) } + Polling.arena.each_index { |fd, index| system_add(fd, index) } end {% end %} @@ -118,16 +118,16 @@ class Crystal::Kqueue::EventLoop < Crystal::Evented::EventLoop private def process_io(kevent : LibC::Kevent*, &) : Nil index = {% if flag?(:bits64) %} - Evented::Arena::Index.new(kevent.value.udata.address) + Polling::Arena::Index.new(kevent.value.udata.address) {% else %} # assuming 32-bit target: rebuild the arena index - Evented::Arena::Index.new(kevent.value.ident.to_i32!, kevent.value.udata.address.to_u32!) + Polling::Arena::Index.new(kevent.value.ident.to_i32!, kevent.value.udata.address.to_u32!) {% end %} Crystal.trace :evloop, "event", fd: kevent.value.ident, index: index.to_i64, filter: kevent.value.filter, flags: kevent.value.flags, fflags: kevent.value.fflags - Evented.arena.get?(index) do |pd| + Polling.arena.get?(index) do |pd| if (kevent.value.fflags & LibC::EV_EOF) == LibC::EV_EOF # apparently some systems may report EOF on write with EVFILT_READ instead # of EVFILT_WRITE, so let's wake all waiters: @@ -167,7 +167,7 @@ class Crystal::Kqueue::EventLoop < Crystal::Evented::EventLoop {% end %} end - protected def system_add(fd : Int32, index : Evented::Arena::Index) : Nil + protected def system_add(fd : Int32, index : Polling::Arena::Index) : Nil Crystal.trace :evloop, "kevent", op: "add", fd: fd, index: index.to_i64 # register both read and write events diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/event_loop/libevent.cr similarity index 95% rename from src/crystal/system/unix/event_loop_libevent.cr rename to src/crystal/event_loop/libevent.cr index 4594f07ffe66..21ad97030336 100644 --- a/src/crystal/system/unix/event_loop_libevent.cr +++ b/src/crystal/event_loop/libevent.cr @@ -1,8 +1,8 @@ -require "./event_libevent" +require "./libevent/event" # :nodoc: -class Crystal::LibEvent::EventLoop < Crystal::EventLoop - private getter(event_base) { Crystal::LibEvent::Event::Base.new } +class Crystal::EventLoop::LibEvent < Crystal::EventLoop + private getter(event_base) { Crystal::EventLoop::LibEvent::Event::Base.new } def after_fork_before_exec : Nil end @@ -23,14 +23,14 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop end # Create a new resume event for a fiber. - def create_resume_event(fiber : Fiber) : Crystal::EventLoop::Event + def create_resume_event(fiber : Fiber) : Crystal::EventLoop::LibEvent::Event event_base.new_event(-1, LibEvent2::EventFlags::None, fiber) do |s, flags, data| data.as(Fiber).enqueue end end # Creates a timeout_event. - def create_timeout_event(fiber) : Crystal::EventLoop::Event + def create_timeout_event(fiber) : Crystal::EventLoop::LibEvent::Event event_base.new_event(-1, LibEvent2::EventFlags::None, fiber) do |s, flags, data| f = data.as(Fiber) if (select_action = f.timeout_select_action) diff --git a/src/crystal/system/unix/event_libevent.cr b/src/crystal/event_loop/libevent/event.cr similarity index 96% rename from src/crystal/system/unix/event_libevent.cr rename to src/crystal/event_loop/libevent/event.cr index 32578e5aba9a..d6b1a5dc0433 100644 --- a/src/crystal/system/unix/event_libevent.cr +++ b/src/crystal/event_loop/libevent/event.cr @@ -5,7 +5,7 @@ require "./lib_event2" {% end %} # :nodoc: -module Crystal::LibEvent +class Crystal::EventLoop::LibEvent < Crystal::EventLoop struct Event include Crystal::EventLoop::Event @@ -56,7 +56,7 @@ module Crystal::LibEvent def new_event(s : Int32, flags : LibEvent2::EventFlags, data, &callback : LibEvent2::Callback) event = LibEvent2.event_new(@base, s, flags, callback, data.as(Void*)) - Crystal::LibEvent::Event.new(event) + LibEvent::Event.new(event) end # NOTE: may return `true` even if no event has been triggered (e.g. diff --git a/src/crystal/system/unix/lib_event2.cr b/src/crystal/event_loop/libevent/lib_event2.cr similarity index 100% rename from src/crystal/system/unix/lib_event2.cr rename to src/crystal/event_loop/libevent/lib_event2.cr diff --git a/src/crystal/system/unix/evented/event_loop.cr b/src/crystal/event_loop/polling.cr similarity index 93% rename from src/crystal/system/unix/evented/event_loop.cr rename to src/crystal/event_loop/polling.cr index e33fb3d2ea99..0df0b134c7f4 100644 --- a/src/crystal/system/unix/evented/event_loop.cr +++ b/src/crystal/event_loop/polling.cr @@ -1,56 +1,16 @@ -require "./*" -require "./arena" +# forward declaration for the require below to not create a module +abstract class Crystal::EventLoop::Polling < Crystal::EventLoop; end + +require "./polling/*" module Crystal::System::FileDescriptor # user data (generation index for the arena) - property __evloop_data : Evented::Arena::Index = Evented::Arena::INVALID_INDEX + property __evloop_data : EventLoop::Polling::Arena::Index = EventLoop::Polling::Arena::INVALID_INDEX end module Crystal::System::Socket # user data (generation index for the arena) - property __evloop_data : Evented::Arena::Index = Evented::Arena::INVALID_INDEX -end - -module Crystal::Evented - # The generational arena: - # - # 1. decorrelates the fd from the IO since the evloop only really cares about - # the fd state and to resume pending fibers (it could monitor a fd without - # an IO object); - # - # 2. permits to avoid pushing raw pointers to IO objects into kernel data - # structures that are unknown to the GC, and to safely check whether the - # allocation is still valid before trying to dereference the pointer. Since - # `PollDescriptor` also doesn't have pointers to the actual IO object, it - # won't prevent the GC from collecting lost IO objects (and spares us from - # using weak references). - # - # 3. to a lesser extent, it also allows to keep the `PollDescriptor` allocated - # together in the same region, and polluting the IO object itself with - # specific evloop data (except for the generation index). - # - # The implementation takes advantage of the fd being unique per process and - # that the operating system will always reuse the lowest fd (POSIX compliance) - # and will only grow when the process needs that many file descriptors, so the - # allocated memory region won't grow larger than necessary. This assumption - # allows the arena to skip maintaining a list of free indexes. Some systems - # may deviate from the POSIX default, but all systems seem to follow it, as it - # allows optimizations to the OS (it can reuse already allocated resources), - # and either the man page explicitly says so (Linux), or they don't (BSD) and - # they must follow the POSIX definition. - # - # The block size is set to 64KB because it's a multiple of: - # - 4KB (usual page size) - # - 1024 (common soft limit for open files) - # - sizeof(Arena::Entry(PollDescriptor)) - protected class_getter arena = Arena(PollDescriptor, 65536).new(max_fds) - - private def self.max_fds : Int32 - if LibC.getrlimit(LibC::RLIMIT_NOFILE, out rlimit) == -1 - raise RuntimeError.from_errno("getrlimit(RLIMIT_NOFILE)") - end - rlimit.rlim_max.clamp(..Int32::MAX).to_i32! - end + property __evloop_data : EventLoop::Polling::Arena::Index = EventLoop::Polling::Arena::INVALID_INDEX end # Polling EventLoop. @@ -94,7 +54,47 @@ end # If the IO operation has a timeout, the event is also registered into `@timers` # before suspending the fiber, then after resume it will raise # `IO::TimeoutError` if the event timed out, and continue otherwise. -abstract class Crystal::Evented::EventLoop < Crystal::EventLoop +abstract class Crystal::EventLoop::Polling < Crystal::EventLoop + # The generational arena: + # + # 1. decorrelates the fd from the IO since the evloop only really cares about + # the fd state and to resume pending fibers (it could monitor a fd without + # an IO object); + # + # 2. permits to avoid pushing raw pointers to IO objects into kernel data + # structures that are unknown to the GC, and to safely check whether the + # allocation is still valid before trying to dereference the pointer. Since + # `PollDescriptor` also doesn't have pointers to the actual IO object, it + # won't prevent the GC from collecting lost IO objects (and spares us from + # using weak references). + # + # 3. to a lesser extent, it also allows to keep the `PollDescriptor` allocated + # together in the same region, and polluting the IO object itself with + # specific evloop data (except for the generation index). + # + # The implementation takes advantage of the fd being unique per process and + # that the operating system will always reuse the lowest fd (POSIX compliance) + # and will only grow when the process needs that many file descriptors, so the + # allocated memory region won't grow larger than necessary. This assumption + # allows the arena to skip maintaining a list of free indexes. Some systems + # may deviate from the POSIX default, but all systems seem to follow it, as it + # allows optimizations to the OS (it can reuse already allocated resources), + # and either the man page explicitly says so (Linux), or they don't (BSD) and + # they must follow the POSIX definition. + # + # The block size is set to 64KB because it's a multiple of: + # - 4KB (usual page size) + # - 1024 (common soft limit for open files) + # - sizeof(Arena::Entry(PollDescriptor)) + protected class_getter arena = Arena(PollDescriptor, 65536).new(max_fds) + + private def self.max_fds : Int32 + if LibC.getrlimit(LibC::RLIMIT_NOFILE, out rlimit) == -1 + raise RuntimeError.from_errno("getrlimit(RLIMIT_NOFILE)") + end + rlimit.rlim_max.clamp(..Int32::MAX).to_i32! + end + @lock = SpinLock.new # protects parallel accesses to @timers @timers = Timers.new @@ -299,7 +299,7 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop protected def evented_close(io) return unless (index = io.__evloop_data).valid? - Evented.arena.free(index) do |pd| + Polling.arena.free(index) do |pd| pd.value.@readers.ready_all do |event| pd.value.@event_loop.try(&.unsafe_resume_io(event) do |fiber| Crystal::Scheduler.enqueue(fiber) @@ -319,7 +319,7 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop private def internal_remove(io) return unless (index = io.__evloop_data).valid? - Evented.arena.free(index) do |pd| + Polling.arena.free(index) do |pd| pd.value.remove(io.fd) { } # ignore system error end end @@ -350,33 +350,33 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop end end - private def wait(type : Evented::Event::Type, io, timeout, &) + private def wait(type : Polling::Event::Type, io, timeout, &) # prepare event (on the stack); we can't initialize it properly until we get # the arena index below; we also can't use a nilable since `pointerof` would # point to the union, not the event - event = uninitialized Evented::Event + event = uninitialized Event # add the event to the waiting list; in case we can't access or allocate the # poll descriptor into the arena, we merely return to let the caller handle # the situation (maybe the IO got closed?) if (index = io.__evloop_data).valid? - event = Evented::Event.new(type, Fiber.current, index, timeout) + event = Event.new(type, Fiber.current, index, timeout) - return false unless Evented.arena.get?(index) do |pd| + return false unless Polling.arena.get?(index) do |pd| yield pd, pointerof(event) end else # OPTIMIZE: failing to allocate may be a simple conflict with 2 fibers # starting to read or write on the same fd, we may want to detect any # error situation instead of returning and retrying a syscall - return false unless Evented.arena.allocate_at?(io.fd) do |pd, index| + return false unless Polling.arena.allocate_at?(io.fd) do |pd, index| # register the fd with the event loop (once), it should usually merely add # the fd to the current evloop but may "transfer" the ownership from # another event loop: io.__evloop_data = index pd.value.take_ownership(self, io.fd, index) - event = Evented::Event.new(type, Fiber.current, index, timeout) + event = Event.new(type, Fiber.current, index, timeout) yield pd, pointerof(event) end end @@ -402,14 +402,14 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop # internals: timers - protected def add_timer(event : Evented::Event*) + protected def add_timer(event : Event*) @lock.sync do is_next_ready = @timers.add(event) system_set_timer(event.value.wake_at) if is_next_ready end end - protected def delete_timer(event : Evented::Event*) : Bool + protected def delete_timer(event : Event*) : Bool @lock.sync do dequeued, was_next_ready = @timers.delete(event) # update system timer if we deleted the next timer @@ -424,7 +424,7 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop # Thread unsafe: we must hold the poll descriptor waiter lock for the whole # duration of the dequeue/resume_io otherwise we might conflict with timers # trying to cancel an IO event. - protected def unsafe_resume_io(event : Evented::Event*, &) : Bool + protected def unsafe_resume_io(event : Event*, &) : Bool # we only partially own the poll descriptor; thanks to the lock we know that # another thread won't dequeue it, yet it may still be in the timers queue, # which at worst may be waiting on the lock to be released, so event* can be @@ -449,7 +449,7 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop # collect ready timers before processing them —this is safe— to avoids a # deadlock situation when another thread tries to process a ready IO event # (in poll descriptor waiters) with a timeout (same event* in timers) - buffer = uninitialized StaticArray(Pointer(Evented::Event), 128) + buffer = uninitialized StaticArray(Pointer(Event), 128) size = 0 @lock.sync do @@ -468,7 +468,7 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop end end - private def process_timer(event : Evented::Event*, &) + private def process_timer(event : Event*, &) # we dequeued the event from timers, and by rule we own it, so event* can # safely be dereferenced: fiber = event.value.fiber @@ -477,12 +477,12 @@ abstract class Crystal::Evented::EventLoop < Crystal::EventLoop when .io_read? # reached read timeout: cancel io event; by rule the timer always wins, # even in case of conflict with #unsafe_resume_io we must resume the fiber - Evented.arena.get?(event.value.index) { |pd| pd.value.@readers.delete(event) } + Polling.arena.get?(event.value.index) { |pd| pd.value.@readers.delete(event) } event.value.timed_out! when .io_write? # reached write timeout: cancel io event; by rule the timer always wins, # even in case of conflict with #unsafe_resume_io we must resume the fiber - Evented.arena.get?(event.value.index) { |pd| pd.value.@writers.delete(event) } + Polling.arena.get?(event.value.index) { |pd| pd.value.@writers.delete(event) } event.value.timed_out! when .select_timeout? # always dequeue the event but only enqueue the fiber if we win the diff --git a/src/crystal/system/unix/evented/arena.cr b/src/crystal/event_loop/polling/arena.cr similarity index 97% rename from src/crystal/system/unix/evented/arena.cr rename to src/crystal/event_loop/polling/arena.cr index 57e408183679..a7bcb181a66f 100644 --- a/src/crystal/system/unix/evented/arena.cr +++ b/src/crystal/event_loop/polling/arena.cr @@ -13,7 +13,7 @@ # that something else will maintain the uniqueness of indexes and reuse indexes # as much as possible instead of growing. # -# For example this arena is used to hold `Crystal::Evented::PollDescriptor` +# For example this arena is used to hold `Crystal::EventLoop::Polling::PollDescriptor` # allocations for all the fd in a program, where the fd is used as the index. # They're unique to the process and the OS always reuses the lowest fd numbers # before growing. @@ -26,7 +26,7 @@ # Guarantees: blocks' memory is initialized to zero, which means `T` objects are # initialized to zero by default, then `#free` will also clear the memory, so # the next allocation shall be initialized to zero, too. -class Crystal::Evented::Arena(T, BLOCK_BYTESIZE) +class Crystal::EventLoop::Polling::Arena(T, BLOCK_BYTESIZE) INVALID_INDEX = Index.new(-1, 0) struct Index diff --git a/src/crystal/system/unix/evented/event.cr b/src/crystal/event_loop/polling/event.cr similarity index 97% rename from src/crystal/system/unix/evented/event.cr rename to src/crystal/event_loop/polling/event.cr index e6937cf4d044..93caf843b049 100644 --- a/src/crystal/system/unix/evented/event.cr +++ b/src/crystal/event_loop/polling/event.cr @@ -8,7 +8,7 @@ require "crystal/pointer_pairing_heap" # # The events can be found in different queues, for example `Timers` and/or # `Waiters` depending on their type. -struct Crystal::Evented::Event +struct Crystal::EventLoop::Polling::Event enum Type IoRead IoWrite diff --git a/src/crystal/system/unix/evented/fiber_event.cr b/src/crystal/event_loop/polling/fiber_event.cr similarity index 87% rename from src/crystal/system/unix/evented/fiber_event.cr rename to src/crystal/event_loop/polling/fiber_event.cr index 074dd67e926f..e21cf2b90526 100644 --- a/src/crystal/system/unix/evented/fiber_event.cr +++ b/src/crystal/event_loop/polling/fiber_event.cr @@ -1,8 +1,8 @@ -class Crystal::Evented::FiberEvent +class Crystal::EventLoop::Polling::FiberEvent include Crystal::EventLoop::Event - def initialize(@event_loop : EventLoop, fiber : Fiber, type : Evented::Event::Type) - @event = Evented::Event.new(type, fiber) + def initialize(@event_loop : EventLoop, fiber : Fiber, type : Event::Type) + @event = Event.new(type, fiber) end # sleep or select timeout diff --git a/src/crystal/system/unix/evented/poll_descriptor.cr b/src/crystal/event_loop/polling/poll_descriptor.cr similarity index 94% rename from src/crystal/system/unix/evented/poll_descriptor.cr rename to src/crystal/event_loop/polling/poll_descriptor.cr index 1ef318e454bb..801d1b148d89 100644 --- a/src/crystal/system/unix/evented/poll_descriptor.cr +++ b/src/crystal/event_loop/polling/poll_descriptor.cr @@ -1,11 +1,9 @@ -require "./event_loop" - # Information related to the evloop for a fd, such as the read and write queues # (waiting `Event`), as well as which evloop instance currently owns the fd. # # Thread-unsafe: parallel mutations must be protected with a lock. -struct Crystal::Evented::PollDescriptor - @event_loop : Evented::EventLoop? +struct Crystal::EventLoop::Polling::PollDescriptor + @event_loop : Polling? @readers = Waiters.new @writers = Waiters.new diff --git a/src/crystal/system/unix/evented/timers.cr b/src/crystal/event_loop/polling/timers.cr similarity index 86% rename from src/crystal/system/unix/evented/timers.cr rename to src/crystal/event_loop/polling/timers.cr index 7b6deac4f543..b9191f008f46 100644 --- a/src/crystal/system/unix/evented/timers.cr +++ b/src/crystal/event_loop/polling/timers.cr @@ -7,9 +7,9 @@ require "crystal/pointer_pairing_heap" # # NOTE: this is a struct because it only wraps a const pointer to an object # allocated in the heap. -struct Crystal::Evented::Timers +struct Crystal::EventLoop::Polling::Timers def initialize - @heap = PointerPairingHeap(Evented::Event).new + @heap = PointerPairingHeap(Event).new end def empty? : Bool @@ -24,7 +24,7 @@ struct Crystal::Evented::Timers # Dequeues and yields each ready timer (their `#wake_at` is lower than # `System::Time.monotonic`) from the oldest to the most recent (i.e. time # ascending). - def dequeue_ready(& : Evented::Event* -> Nil) : Nil + def dequeue_ready(& : Event* -> Nil) : Nil seconds, nanoseconds = System::Time.monotonic now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds) @@ -36,7 +36,7 @@ struct Crystal::Evented::Timers end # Add a new timer into the list. Returns true if it is the next ready timer. - def add(event : Evented::Event*) : Bool + def add(event : Event*) : Bool @heap.add(event) @heap.first? == event end @@ -44,7 +44,7 @@ struct Crystal::Evented::Timers # Remove a timer from the list. Returns a tuple(dequeued, was_next_ready) of # booleans. The first bool tells whether the event was dequeued, in which case # the second one tells if it was the next ready event. - def delete(event : Evented::Event*) : {Bool, Bool} + def delete(event : Event*) : {Bool, Bool} if @heap.first? == event @heap.shift? {true, true} diff --git a/src/crystal/system/unix/evented/waiters.cr b/src/crystal/event_loop/polling/waiters.cr similarity index 97% rename from src/crystal/system/unix/evented/waiters.cr rename to src/crystal/event_loop/polling/waiters.cr index 2d052718bae9..85d10fd6f5ba 100644 --- a/src/crystal/system/unix/evented/waiters.cr +++ b/src/crystal/event_loop/polling/waiters.cr @@ -1,5 +1,3 @@ -require "./event" - # A FIFO queue of `Event` waiting on the same operation (either read or write) # for a fd. See `PollDescriptor`. # @@ -7,7 +5,7 @@ require "./event" # always ready variables. # # Thread unsafe: parallel mutations must be protected with a lock. -struct Crystal::Evented::Waiters +struct Crystal::EventLoop::Polling::Waiters @list = PointerLinkedList(Event).new @ready = false @always_ready = false diff --git a/src/crystal/system/event_loop/socket.cr b/src/crystal/event_loop/socket.cr similarity index 96% rename from src/crystal/system/event_loop/socket.cr rename to src/crystal/event_loop/socket.cr index 8fa86e50affc..03b556b3be96 100644 --- a/src/crystal/system/event_loop/socket.cr +++ b/src/crystal/event_loop/socket.cr @@ -1,4 +1,4 @@ -# This file is only required when sockets are used (`require "./event_loop/socket"` in `src/crystal/system/socket.cr`) +# This file is only required when sockets are used (`require "crystal/event_loop/socket"` in `src/crystal/system/socket.cr`) # # It fills `Crystal::EventLoop::Socket` with abstract defs. diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/event_loop/wasi.cr similarity index 97% rename from src/crystal/system/wasi/event_loop.cr rename to src/crystal/event_loop/wasi.cr index 3cce9ba8361c..a91c469f406c 100644 --- a/src/crystal/system/wasi/event_loop.cr +++ b/src/crystal/event_loop/wasi.cr @@ -1,5 +1,5 @@ # :nodoc: -class Crystal::Wasi::EventLoop < Crystal::EventLoop +class Crystal::EventLoop::Wasi < Crystal::EventLoop # Runs the event loop. def run(blocking : Bool) : Bool raise NotImplementedError.new("Crystal::Wasi::EventLoop.run") @@ -129,7 +129,7 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop end end -struct Crystal::Wasi::Event +struct Crystal::EventLoop::Wasi::Event include Crystal::EventLoop::Event def add(timeout : Time::Span) : Nil diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index bed98ef4d05b..9b64823f3905 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -1,4 +1,4 @@ -require "crystal/system/event_loop" +require "crystal/event_loop" require "crystal/system/print_error" require "./fiber_channel" require "fiber" diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr index 8d5e8c9afaf0..54648f17f7db 100644 --- a/src/crystal/system/socket.cr +++ b/src/crystal/system/socket.cr @@ -1,4 +1,4 @@ -require "./event_loop/socket" +require "../event_loop/socket" module Crystal::System::Socket # Creates a file descriptor / socket handle diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr index 535f37f386c0..2ca502aa28f8 100644 --- a/src/crystal/system/unix/socket.cr +++ b/src/crystal/system/unix/socket.cr @@ -25,8 +25,8 @@ module Crystal::System::Socket end private def initialize_handle(fd) - {% if Crystal.has_constant?(:Evented) %} - @__evloop_data = Crystal::Evented::Arena::INVALID_INDEX + {% if Crystal::EventLoop.has_constant?(:Polling) %} + @__evloop_data = Crystal::EventLoop::Polling::Arena::INVALID_INDEX {% end %} end diff --git a/src/crystal/system/win32/addrinfo.cr b/src/crystal/system/win32/addrinfo.cr index 91ebb1620a43..da5cb6ce20c3 100644 --- a/src/crystal/system/win32/addrinfo.cr +++ b/src/crystal/system/win32/addrinfo.cr @@ -43,9 +43,9 @@ module Crystal::System::Addrinfo end end - Crystal::IOCP::GetAddrInfoOverlappedOperation.run(Crystal::EventLoop.current.iocp) do |operation| + IOCP::GetAddrInfoOverlappedOperation.run(Crystal::EventLoop.current.iocp) do |operation| completion_routine = LibC::LPLOOKUPSERVICE_COMPLETION_ROUTINE.new do |dwError, dwBytes, lpOverlapped| - orig_operation = Crystal::IOCP::GetAddrInfoOverlappedOperation.unbox(lpOverlapped) + orig_operation = IOCP::GetAddrInfoOverlappedOperation.unbox(lpOverlapped) LibC.PostQueuedCompletionStatus(orig_operation.iocp, 0, 0, lpOverlapped) end @@ -60,7 +60,7 @@ module Crystal::System::Addrinfo else case error = WinError.new(result.to_u32!) when .wsa_io_pending? - # used in `Crystal::IOCP::OverlappedOperation#try_cancel_getaddrinfo` + # used in `IOCP::OverlappedOperation#try_cancel_getaddrinfo` operation.cancel_handle = cancel_handle else raise ::Socket::Addrinfo::Error.from_os_error("GetAddrInfoExW", error, domain: domain, type: type, protocol: protocol, service: service) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 4265701cd8b2..894fcfaf5cb1 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -490,7 +490,7 @@ private module ConsoleUtils handle: handle, slice: slice, iocp: Crystal::EventLoop.current.iocp, - completion_key: Crystal::IOCP::CompletionKey.new(:stdin_read, ::Fiber.current), + completion_key: Crystal::System::IOCP::CompletionKey.new(:stdin_read, ::Fiber.current), ) @@read_cv.signal end @@ -509,7 +509,11 @@ private module ConsoleUtils units_read.to_i32 end - record ReadRequest, handle : LibC::HANDLE, slice : Slice(UInt16), iocp : LibC::HANDLE, completion_key : Crystal::IOCP::CompletionKey + record ReadRequest, + handle : LibC::HANDLE, + slice : Slice(UInt16), + iocp : LibC::HANDLE, + completion_key : Crystal::System::IOCP::CompletionKey @@read_cv = ::Thread::ConditionVariable.new @@read_requests = Deque(ReadRequest).new diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr index 19c92c8f8725..fece9ada3a83 100644 --- a/src/crystal/system/win32/iocp.cr +++ b/src/crystal/system/win32/iocp.cr @@ -3,7 +3,7 @@ require "c/handleapi" require "crystal/system/thread_linked_list" # :nodoc: -module Crystal::IOCP +module Crystal::System::IOCP # :nodoc: class CompletionKey enum Tag @@ -11,10 +11,10 @@ module Crystal::IOCP StdinRead end - property fiber : Fiber? + property fiber : ::Fiber? getter tag : Tag - def initialize(@tag : Tag, @fiber : Fiber? = nil) + def initialize(@tag : Tag, @fiber : ::Fiber? = nil) end end @@ -88,7 +88,7 @@ module Crystal::IOCP private abstract def try_cancel : Bool @overlapped = LibC::OVERLAPPED.new - @fiber = Fiber.current + @fiber = ::Fiber.current @state : State = :started def self.run(*args, **opts, &) @@ -120,14 +120,14 @@ module Crystal::IOCP if timeout sleep timeout else - Fiber.suspend + ::Fiber.suspend end unless @state.done? if try_cancel # Wait for cancellation to complete. We must not free the operation # until it's completed. - Fiber.suspend + ::Fiber.suspend end end end @@ -225,7 +225,7 @@ module Crystal::IOCP error = WinError.new(result.to_u32!) yield error - raise Socket::Addrinfo::Error.from_os_error("GetAddrInfoExOverlappedResult", error) + raise ::Socket::Addrinfo::Error.from_os_error("GetAddrInfoExOverlappedResult", error) end @overlapped.union.pointer.as(LibC::ADDRINFOEXW**).value @@ -239,7 +239,7 @@ module Crystal::IOCP # Operation has already completed, do nothing return false else - raise Socket::Addrinfo::Error.from_os_error("GetAddrInfoExCancel", error) + raise ::Socket::Addrinfo::Error.from_os_error("GetAddrInfoExCancel", error) end end true diff --git a/src/io/evented.cr b/src/io/evented.cr index f59aa205c543..1f95d1870b0b 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -1,6 +1,6 @@ -require "crystal/system/event_loop" +require "crystal/event_loop" -{% skip_file unless flag?(:wasi) || Crystal.has_constant?(:LibEvent) %} +{% skip_file unless flag?(:wasi) || Crystal::EventLoop.has_constant?(:LibEvent) %} require "crystal/thread_local_value" diff --git a/src/kernel.cr b/src/kernel.cr index 1203d1c66a7e..2063acce95ae 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -620,7 +620,7 @@ end # This is a temporary workaround to ensure there is always something in the IOCP # event loop being awaited, since both the interrupt loop and the fiber stack # pool collector are disabled in interpreted code. Without this, asynchronous -# code that bypasses `Crystal::IOCP::OverlappedOperation` does not currently +# code that bypasses `Crystal::System::IOCP::OverlappedOperation` does not currently # work, see https://github.com/crystal-lang/crystal/pull/14949#issuecomment-2328314463 {% if flag?(:interpreted) && flag?(:win32) %} spawn(name: "Interpreter idle loop") do From c283eabf66c575c3d1a9e56576cfe4c1a91aa136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 2 Dec 2024 11:16:04 +0100 Subject: [PATCH 1498/1551] Optimize `String#==` taking character size into account (#15233) Co-authored-by: Oleh Prypin --- src/string.cr | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/string.cr b/src/string.cr index 07d65f10dbd4..4b52d08c7426 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3088,8 +3088,18 @@ class String # "abcdef".compare("ABCDEF", case_insensitive: true) == 0 # => true # ``` def ==(other : self) : Bool + # Quick pointer comparison if both strings are identical references return true if same?(other) - return false unless bytesize == other.bytesize + + # If the bytesize differs, they cannot be equal + return false if bytesize != other.bytesize + + # If the character size of both strings differs, they cannot be equal. + # We need to exclude the case that @length of either string might not have + # been calculated (indicated by `0`). + return false if @length != other.@length && @length != 0 && other.@length != 0 + + # All meta data matches up, so we need to compare byte-by-byte. to_unsafe.memcmp(other.to_unsafe, bytesize) == 0 end From 9a5cc2374ae8ed0d5ff8f002e1e09cde3a3f3ad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 2 Dec 2024 11:16:20 +0100 Subject: [PATCH 1499/1551] Optimize `Slice#<=>` and `#==` with reference check (#15234) --- src/slice.cr | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/slice.cr b/src/slice.cr index ace008e53e05..266d7bb31249 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -825,6 +825,9 @@ struct Slice(T) # Bytes[1, 2] <=> Bytes[1, 2] # => 0 # ``` def <=>(other : Slice(U)) forall U + # If both slices are identical references, we can skip the memory comparison. + return 0 if same?(other) + min_size = Math.min(size, other.size) {% if T == UInt8 && U == UInt8 %} cmp = to_unsafe.memcmp(other.to_unsafe, min_size) @@ -847,8 +850,13 @@ struct Slice(T) # Bytes[1, 2] == Bytes[1, 2, 3] # => false # ``` def ==(other : Slice(U)) : Bool forall U + # If both slices are of different sizes, they cannot be equal. return false if size != other.size + # If both slices are identical references, we can skip the memory comparison. + # Not using `same?` here because we have already compared sizes. + return true if to_unsafe == other.to_unsafe + {% if T == UInt8 && U == UInt8 %} to_unsafe.memcmp(other.to_unsafe, size) == 0 {% else %} From d6d41b71f793913b3fe92724a023fc0999c6d681 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:45:43 +0100 Subject: [PATCH 1500/1551] Update GH Actions (#15052) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/interpreter.yml | 8 ++++---- .github/workflows/macos.yml | 2 +- .github/workflows/mingw-w64.yml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml index 103dc766509b..9aa2d2ca24f4 100644 --- a/.github/workflows/interpreter.yml +++ b/.github/workflows/interpreter.yml @@ -13,7 +13,7 @@ env: jobs: test-interpreter_spec: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 container: image: crystallang/crystal:1.14.0-build name: "Test Interpreter" @@ -24,7 +24,7 @@ jobs: run: make interpreter_spec junit_output=.junit/interpreter_spec.xml build-interpreter: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 container: image: crystallang/crystal:1.14.0-build name: Build interpreter @@ -43,7 +43,7 @@ jobs: test-interpreter-std_spec: needs: build-interpreter - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 container: image: crystallang/crystal:1.14.0-build strategy: @@ -67,7 +67,7 @@ jobs: test-interpreter-primitives_spec: needs: build-interpreter - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 container: image: crystallang/crystal:1.14.0-build name: "Test primitives_spec with interpreter" diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 77e9e0b3371c..99f178fff6f5 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -27,7 +27,7 @@ jobs: - name: Download Crystal source uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v27 + - uses: cachix/install-nix-action@v30 with: extra_nix_config: | experimental-features = nix-command diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml index 050f8800b520..eacf6a34c006 100644 --- a/.github/workflows/mingw-w64.yml +++ b/.github/workflows/mingw-w64.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Setup MSYS2 id: msys2 - uses: msys2/setup-msys2@ddf331adaebd714795f1042345e6ca57bd66cea8 # v2.24.1 + uses: msys2/setup-msys2@c52d1fa9c7492275e60fe763540fb601f5f232a1 # v2.25.0 with: msystem: UCRT64 update: true @@ -99,7 +99,7 @@ jobs: steps: - name: Setup MSYS2 id: msys2 - uses: msys2/setup-msys2@ddf331adaebd714795f1042345e6ca57bd66cea8 # v2.24.1 + uses: msys2/setup-msys2@c52d1fa9c7492275e60fe763540fb601f5f232a1 # v2.25.0 with: msystem: UCRT64 update: true From aca8dd42bf01a0c82498c510c87ca18ef7229348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 3 Dec 2024 17:31:44 +0100 Subject: [PATCH 1501/1551] Add stringification for `HTTP::Cookie` (#15240) * Add `Cookie#to_set_cookie_header(IO)` * Add docs for `Cookie#to_cookie_header` * Add `Cookie#to_s` * Add `Cookie#inspect` --- spec/std/http/cookie_spec.cr | 37 ++++++++++++------ src/http/cookie.cr | 75 ++++++++++++++++++++++++++++++------ 2 files changed, 89 insertions(+), 23 deletions(-) diff --git a/spec/std/http/cookie_spec.cr b/spec/std/http/cookie_spec.cr index 1a29a3f56754..be4ad06a6b6b 100644 --- a/spec/std/http/cookie_spec.cr +++ b/spec/std/http/cookie_spec.cr @@ -1,6 +1,7 @@ require "spec" require "http/cookie" require "http/headers" +require "spec/helpers/string" private def parse_first_cookie(header) cookies = HTTP::Cookie::Parser.parse_cookies(header) @@ -145,24 +146,38 @@ module HTTP end describe "#to_set_cookie_header" do - it { HTTP::Cookie.new("x", "v$1").to_set_cookie_header.should eq "x=v$1" } + it { assert_prints HTTP::Cookie.new("x", "v$1").to_set_cookie_header, "x=v$1" } - it { HTTP::Cookie.new("x", "seven", domain: "127.0.0.1").to_set_cookie_header.should eq "x=seven; domain=127.0.0.1" } + it { assert_prints HTTP::Cookie.new("x", "seven", domain: "127.0.0.1").to_set_cookie_header, "x=seven; domain=127.0.0.1" } - it { HTTP::Cookie.new("x", "y", path: "/").to_set_cookie_header.should eq "x=y; path=/" } - it { HTTP::Cookie.new("x", "y", path: "/example").to_set_cookie_header.should eq "x=y; path=/example" } + it { assert_prints HTTP::Cookie.new("x", "y", path: "/").to_set_cookie_header, "x=y; path=/" } + it { assert_prints HTTP::Cookie.new("x", "y", path: "/example").to_set_cookie_header, "x=y; path=/example" } - it { HTTP::Cookie.new("x", "expiring", expires: Time.unix(1257894000)).to_set_cookie_header.should eq "x=expiring; expires=Tue, 10 Nov 2009 23:00:00 GMT" } - it { HTTP::Cookie.new("x", "expiring-1601", expires: Time.utc(1601, 1, 1, 1, 1, 1, nanosecond: 1)).to_set_cookie_header.should eq "x=expiring-1601; expires=Mon, 01 Jan 1601 01:01:01 GMT" } + it { assert_prints HTTP::Cookie.new("x", "expiring", expires: Time.unix(1257894000)).to_set_cookie_header, "x=expiring; expires=Tue, 10 Nov 2009 23:00:00 GMT" } + it { assert_prints HTTP::Cookie.new("x", "expiring-1601", expires: Time.utc(1601, 1, 1, 1, 1, 1, nanosecond: 1)).to_set_cookie_header, "x=expiring-1601; expires=Mon, 01 Jan 1601 01:01:01 GMT" } it "samesite" do - HTTP::Cookie.new("x", "samesite-default", samesite: nil).to_set_cookie_header.should eq "x=samesite-default" - HTTP::Cookie.new("x", "samesite-lax", samesite: :lax).to_set_cookie_header.should eq "x=samesite-lax; SameSite=Lax" - HTTP::Cookie.new("x", "samesite-strict", samesite: :strict).to_set_cookie_header.should eq "x=samesite-strict; SameSite=Strict" - HTTP::Cookie.new("x", "samesite-none", samesite: :none).to_set_cookie_header.should eq "x=samesite-none; SameSite=None" + assert_prints HTTP::Cookie.new("x", "samesite-default", samesite: nil).to_set_cookie_header, "x=samesite-default" + assert_prints HTTP::Cookie.new("x", "samesite-lax", samesite: :lax).to_set_cookie_header, "x=samesite-lax; SameSite=Lax" + assert_prints HTTP::Cookie.new("x", "samesite-strict", samesite: :strict).to_set_cookie_header, "x=samesite-strict; SameSite=Strict" + assert_prints HTTP::Cookie.new("x", "samesite-none", samesite: :none).to_set_cookie_header, "x=samesite-none; SameSite=None" end - it { HTTP::Cookie.new("empty-value", "").to_set_cookie_header.should eq "empty-value=" } + it { assert_prints HTTP::Cookie.new("empty-value", "").to_set_cookie_header, "empty-value=" } + end + + describe "#to_s" do + it "stringifies" do + HTTP::Cookie.new("foo", "bar").to_s.should eq "foo=bar" + HTTP::Cookie.new("x", "y", domain: "example.com", path: "/foo", expires: Time.unix(1257894000), samesite: :lax).to_s.should eq "x=y; domain=example.com; path=/foo; expires=Tue, 10 Nov 2009 23:00:00 GMT; SameSite=Lax" + end + end + + describe "#inspect" do + it "stringifies" do + HTTP::Cookie.new("foo", "bar").inspect.should eq %(HTTP::Cookie["foo=bar"]) + HTTP::Cookie.new("x", "y", domain: "example.com", path: "/foo", expires: Time.unix(1257894000), samesite: :lax).inspect.should eq %(HTTP::Cookie["x=y; domain=example.com; path=/foo; expires=Tue, 10 Nov 2009 23:00:00 GMT; SameSite=Lax"]) + end end describe "#valid? & #validate!" do diff --git a/src/http/cookie.cr b/src/http/cookie.cr index 8138249aa830..5eabf833ad60 100644 --- a/src/http/cookie.cr +++ b/src/http/cookie.cr @@ -104,31 +104,82 @@ module HTTP end end + # Returns an unambiguous string representation of this cookie. + # + # It uses the `Set-Cookie` serialization from `#to_set_cookie_header` which + # represents the full state of the cookie. + # + # ``` + # HTTP::Cookie.new("foo", "bar").inspect # => HTTP::Cookie["foo=bar"] + # HTTP::Cookie.new("foo", "bar", domain: "example.com").inspect # => HTTP::Cookie["foo=bar; domain=example.com"] + # ``` + def inspect(io : IO) : Nil + io << "HTTP::Cookie[" + to_s.inspect(io) + io << "]" + end + + # Returns a string representation of this cookie. + # + # It uses the `Set-Cookie` serialization from `#to_set_cookie_header` which + # represents the full state of the cookie. + # + # ``` + # HTTP::Cookie.new("foo", "bar").to_s # => "foo=bar" + # HTTP::Cookie.new("foo", "bar", domain: "example.com").to_s # => "foo=bar; domain=example.com" + # ``` + def to_s(io : IO) : Nil + to_set_cookie_header(io) + end + + # Returns a string representation of this cookie in the format used by the + # `Set-Cookie` header of an HTTP response. + # + # ``` + # HTTP::Cookie.new("foo", "bar").to_set_cookie_header # => "foo=bar" + # HTTP::Cookie.new("foo", "bar", domain: "example.com").to_set_cookie_header # => "foo=bar; domain=example.com" + # ``` def to_set_cookie_header : String + String.build do |header| + to_set_cookie_header(header) + end + end + + # :ditto: + def to_set_cookie_header(io : IO) : Nil path = @path expires = @expires max_age = @max_age domain = @domain samesite = @samesite - String.build do |header| - to_cookie_header(header) - header << "; domain=#{domain}" if domain - header << "; path=#{path}" if path - header << "; expires=#{HTTP.format_time(expires)}" if expires - header << "; max-age=#{max_age.to_i}" if max_age - header << "; Secure" if @secure - header << "; HttpOnly" if @http_only - header << "; SameSite=#{samesite}" if samesite - header << "; #{@extension}" if @extension - end - end + to_cookie_header(io) + io << "; domain=#{domain}" if domain + io << "; path=#{path}" if path + io << "; expires=#{HTTP.format_time(expires)}" if expires + io << "; max-age=#{max_age.to_i}" if max_age + io << "; Secure" if @secure + io << "; HttpOnly" if @http_only + io << "; SameSite=#{samesite}" if samesite + io << "; #{@extension}" if @extension + end + + # Returns a string representation of this cookie in the format used by the + # `Cookie` header of an HTTP request. + # This includes only the `#name` and `#value`. All other attributes are left + # out. + # + # ``` + # HTTP::Cookie.new("foo", "bar").to_cookie_header # => "foo=bar" + # HTTP::Cookie.new("foo", "bar", domain: "example.com").to_cookie_header # => "foo=bar + # ``` def to_cookie_header : String String.build(@name.bytesize + @value.bytesize + 1) do |io| to_cookie_header(io) end end + # :ditto: def to_cookie_header(io) : Nil io << @name io << '=' From 22eb886b27319df4d0f6133832691cc4c802f060 Mon Sep 17 00:00:00 2001 From: Connor Date: Tue, 3 Dec 2024 11:34:45 -0500 Subject: [PATCH 1502/1551] Add `unit_separator` to `Int#humanize` and `#humanize_bytes` (#15176) --- spec/std/humanize_spec.cr | 13 +++++++++++++ src/humanize.cr | 36 ++++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/spec/std/humanize_spec.cr b/spec/std/humanize_spec.cr index d24d2017cb28..e4230540804d 100644 --- a/spec/std/humanize_spec.cr +++ b/spec/std/humanize_spec.cr @@ -207,6 +207,16 @@ describe Number do it { assert_prints 1.0e+34.humanize, "10,000Q" } it { assert_prints 1.0e+35.humanize, "100,000Q" } + it { assert_prints 0.humanize(unit_separator: '_'), "0.0" } + it { assert_prints 0.123_456_78.humanize(5, unit_separator: '\u00A0'), "123.46\u00A0m" } + it { assert_prints 1.0e-14.humanize(unit_separator: ' '), "10.0 f" } + it { assert_prints 0.000_001.humanize(unit_separator: '\u2009'), "1.0\u2009µ" } + it { assert_prints 1_000_000_000_000.humanize(unit_separator: "__"), "1.0__T" } + it { assert_prints 0.000_000_001.humanize(unit_separator: "."), "1.0.n" } + it { assert_prints 1.0e+9.humanize(unit_separator: "\t"), "1.0\tG" } + it { assert_prints 123_456_789_012.humanize(unit_separator: 0), "1230G" } + it { assert_prints 123_456_789_012.humanize(unit_separator: nil), "123G" } + it { assert_prints Float32::INFINITY.humanize, "Infinity" } it { assert_prints (-Float32::INFINITY).humanize, "-Infinity" } it { assert_prints Float32::NAN.humanize, "NaN" } @@ -261,6 +271,7 @@ describe Number do it { assert_prints 1.0e+8.humanize(prefixes: CUSTOM_PREFIXES), "100d" } it { assert_prints 1.0e+9.humanize(prefixes: CUSTOM_PREFIXES), "1,000d" } it { assert_prints 1.0e+10.humanize(prefixes: CUSTOM_PREFIXES), "10,000d" } + it { assert_prints 1.0e+10.humanize(prefixes: CUSTOM_PREFIXES, unit_separator: '\u00A0'), "10,000\u00A0d" } end end end @@ -281,6 +292,7 @@ describe Int do it { assert_prints 1025.humanize_bytes(format: Int::BinaryPrefixFormat::JEDEC), "1.0KB" } it { assert_prints 1026.humanize_bytes(format: Int::BinaryPrefixFormat::JEDEC), "1.01KB" } it { assert_prints 2048.humanize_bytes(format: Int::BinaryPrefixFormat::JEDEC), "2.0KB" } + it { assert_prints 2048.humanize_bytes(format: Int::BinaryPrefixFormat::JEDEC, unit_separator: '\u202F'), "2.0\u202FKB" } it { assert_prints 1536.humanize_bytes(format: Int::BinaryPrefixFormat::JEDEC), "1.5KB" } it { assert_prints 524288.humanize_bytes(format: Int::BinaryPrefixFormat::JEDEC), "512KB" } @@ -289,6 +301,7 @@ describe Int do it { assert_prints 1099511627776.humanize_bytes(format: Int::BinaryPrefixFormat::JEDEC), "1.0TB" } it { assert_prints 1125899906842624.humanize_bytes(format: Int::BinaryPrefixFormat::JEDEC), "1.0PB" } it { assert_prints 1152921504606846976.humanize_bytes(format: Int::BinaryPrefixFormat::JEDEC), "1.0EB" } + it { assert_prints 1152921504606846976.humanize_bytes(format: Int::BinaryPrefixFormat::JEDEC, unit_separator: '\u2009'), "1.0\u2009EB" } it { assert_prints 1024.humanize_bytes(format: Int::BinaryPrefixFormat::IEC), "1.0kiB" } it { assert_prints 1073741824.humanize_bytes(format: Int::BinaryPrefixFormat::IEC), "1.0GiB" } diff --git a/src/humanize.cr b/src/humanize.cr index db9d84c64889..e3e4ed4428c7 100644 --- a/src/humanize.cr +++ b/src/humanize.cr @@ -151,18 +151,21 @@ struct Number # *separator* describes the decimal separator, *delimiter* the thousands # delimiter (see `#format`). # + # *unit_separator* is inserted between the value and the unit. + # Users are encouraged to use a non-breaking space ('\u00A0') to prevent output being split across lines. + # # See `Int#humanize_bytes` to format a file size. - def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, prefixes : Indexable = SI_PREFIXES) : Nil - humanize(io, precision, separator, delimiter, base: base, significant: significant) do |magnitude, _| + def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, prefixes : Indexable = SI_PREFIXES) : Nil + humanize(io, precision, separator, delimiter, base: base, significant: significant, unit_separator: unit_separator) do |magnitude, _| magnitude = Number.prefix_index(magnitude, prefixes: prefixes) {magnitude, Number.si_prefix(magnitude, prefixes)} end end # :ditto: - def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, prefixes = SI_PREFIXES) : String + def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, prefixes = SI_PREFIXES) : String String.build do |io| - humanize(io, precision, separator, delimiter, base: base, significant: significant, prefixes: prefixes) + humanize(io, precision, separator, delimiter, base: base, significant: significant, unit_separator: unit_separator, prefixes: prefixes) end end @@ -215,7 +218,7 @@ struct Number # ``` # # See `Int#humanize_bytes` to format a file size. - def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, &prefixes : (Int32, Float64) -> {Int32, _} | {Int32, _, Bool}) : Nil + def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, &prefixes : (Int32, Float64) -> {Int32, _} | {Int32, _, Bool}) : Nil if zero? || (responds_to?(:infinite?) && self.infinite?) || (responds_to?(:nan?) && self.nan?) digits = 0 else @@ -259,29 +262,30 @@ struct Number number.format(io, separator, delimiter, decimal_places: decimal_places, only_significant: significant) + io << unit_separator if unit io << unit end # :ditto: - def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, &) : String + def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, &) : String String.build do |io| - humanize(io, precision, separator, delimiter, base: base, significant: significant) do |magnitude, number| + humanize(io, precision, separator, delimiter, base: base, significant: significant, unit_separator: unit_separator) do |magnitude, number| yield magnitude, number end end end # :ditto: - def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, prefixes : Proc) : Nil - humanize(io, precision, separator, delimiter, base: base, significant: significant) do |magnitude, number| + def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, prefixes : Proc) : Nil + humanize(io, precision, separator, delimiter, base: base, significant: significant, unit_separator: unit_separator) do |magnitude, number| prefixes.call(magnitude, number) end end # :ditto: - def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, prefixes : Proc) : String + def humanize(precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, unit_separator = nil, prefixes : Proc) : String String.build do |io| - humanize(io, precision, separator, delimiter, base: base, significant: significant, prefixes: prefixes) + humanize(io, precision, separator, delimiter, base: base, significant: significant, unit_separator: unit_separator, prefixes: prefixes) end end end @@ -321,7 +325,7 @@ struct Int # ``` # # See `Number#humanize` for more details on the behaviour and arguments. - def humanize_bytes(io : IO, precision : Int = 3, separator = '.', *, significant : Bool = true, format : BinaryPrefixFormat = :IEC) : Nil + def humanize_bytes(io : IO, precision : Int = 3, separator = '.', *, significant : Bool = true, unit_separator = nil, format : BinaryPrefixFormat = :IEC) : Nil humanize(io, precision, separator, nil, base: 1024, significant: significant) do |magnitude| magnitude = Number.prefix_index(magnitude) @@ -330,9 +334,9 @@ struct Int unit = "B" else if format.iec? - unit = "#{prefix}iB" + unit = "#{unit_separator}#{prefix}iB" else - unit = "#{prefix.upcase}B" + unit = "#{unit_separator}#{prefix.upcase}B" end end {magnitude, unit, magnitude > 0} @@ -340,9 +344,9 @@ struct Int end # :ditto: - def humanize_bytes(precision : Int = 3, separator = '.', *, significant : Bool = true, format : BinaryPrefixFormat = :IEC) : String + def humanize_bytes(precision : Int = 3, separator = '.', *, significant : Bool = true, unit_separator = nil, format : BinaryPrefixFormat = :IEC) : String String.build do |io| - humanize_bytes(io, precision, separator, significant: significant, format: format) + humanize_bytes(io, precision, separator, significant: significant, unit_separator: unit_separator, format: format) end end end From 099f5723ec30ef720d71bd37389205440acb6b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 3 Dec 2024 22:22:39 +0100 Subject: [PATCH 1503/1551] Raise on abnormal exit in `Procss::Status#exit_code` (#15241) --- spec/std/process/status_spec.cr | 8 +++++++- src/process/status.cr | 12 +++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index bdfb2ee38d26..96802be31489 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -27,7 +27,13 @@ describe Process::Status do Process::Status.new(exit_status(128)).exit_code.should eq 128 Process::Status.new(exit_status(255)).exit_code.should eq 255 - status_for(:interrupted).exit_code.should eq({% if flag?(:unix) %}0{% else %}LibC::STATUS_CONTROL_C_EXIT.to_i32!{% end %}) + if {{ flag?(:unix) }} + expect_raises(RuntimeError, "Abnormal exit has no exit code") do + status_for(:interrupted).exit_code + end + else + status_for(:interrupted).exit_code.should eq({% if flag?(:unix) %}0{% else %}LibC::STATUS_CONTROL_C_EXIT.to_i32!{% end %}) + end end it "#success?" do diff --git a/src/process/status.cr b/src/process/status.cr index de29351ff12f..a3db8a7c4346 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -205,9 +205,19 @@ class Process::Status {% end %} end - # If `normal_exit?` is `true`, returns the exit code of the process. + # Returns the exit code of the process if it exited normally (`#normal_exit?`). + # + # Raises `RuntimeError` if the status describes an abnormal exit. + # + # ``` + # Process.run("true").exit_code # => 1 + # Process.run("exit 123", shell: true).exit_code # => 123 + # Process.new("sleep", ["10"]).tap(&.terminate).wait.exit_code # RuntimeError: Abnormal exit has no exit code + # ``` def exit_code : Int32 {% if flag?(:unix) %} + raise RuntimeError.new("Abnormal exit has no exit code") unless normal_exit? + # define __WEXITSTATUS(status) (((status) & 0xff00) >> 8) (@exit_status & 0xff00) >> 8 {% else %} From bf0a58607610f77b759dfd97c28428815bfb48d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 4 Dec 2024 13:56:12 +0100 Subject: [PATCH 1504/1551] Merge changelog entries for fixups with main PR (#15207) --- scripts/github-changelog.cr | 144 +++++++++++++++++++++++++----------- 1 file changed, 99 insertions(+), 45 deletions(-) diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr index 2f89bd923153..cc2f24a1f365 100755 --- a/scripts/github-changelog.cr +++ b/scripts/github-changelog.cr @@ -140,43 +140,10 @@ record PullRequest, @[JSON::Field(root: "nodes", converter: JSON::ArrayConverter(LabelNameConverter))] @labels : Array(String) - def to_s(io : IO) - if topic = self.sub_topic - io << "*(" << sub_topic << ")* " - end - if labels.includes?("security") - io << "**[security]** " - end - if labels.includes?("breaking-change") - io << "**[breaking]** " - end - if regression? - io << "**[regression]** " - end - if experimental? - io << "**[experimental]** " - end - if deprecated? - io << "**[deprecation]** " - end - io << title.sub(/^\[?(?:#{type}|#{sub_topic})(?::|\]:?) /i, "") << " (" - link_ref(io) - if author = self.author - io << ", thanks @" << author - end - io << ")" - end - def link_ref(io) io << "[#" << number << "]" end - def print_ref_label(io) - link_ref(io) - io << ": " << permalink - io.puts - end - def <=>(other : self) sort_tuple <=> other.sort_tuple end @@ -299,6 +266,11 @@ record PullRequest, else type || "" end end + + def fixup? + md = title.match(/\[fixup #(.\d+)/) || return + md[1]?.try(&.to_i) + end end def query_milestone(api_token, repository, number) @@ -340,7 +312,89 @@ end milestone = query_milestone(api_token, repository, milestone) -sections = milestone.pull_requests.group_by(&.section) +struct ChangelogEntry + getter pull_requests : Array(PullRequest) + + def initialize(pr : PullRequest) + @pull_requests = [pr] + end + + def pr + pull_requests[0] + end + + def to_s(io : IO) + if sub_topic = pr.sub_topic + io << "*(" << pr.sub_topic << ")* " + end + if pr.labels.includes?("security") + io << "**[security]** " + end + if pr.labels.includes?("breaking-change") + io << "**[breaking]** " + end + if pr.regression? + io << "**[regression]** " + end + if pr.experimental? + io << "**[experimental]** " + end + if pr.deprecated? + io << "**[deprecation]** " + end + io << pr.title.sub(/^\[?(?:#{pr.type}|#{pr.sub_topic})(?::|\]:?) /i, "") + + io << " (" + pull_requests.join(io, ", ") do |pr| + pr.link_ref(io) + end + + authors = collect_authors + if authors.present? + io << ", thanks " + authors.join(io, ", ") do |author| + io << "@" << author + end + end + io << ")" + end + + def collect_authors + authors = [] of String + pull_requests.each do |pr| + author = pr.author || next + authors << author unless authors.includes?(author) + end + authors + end + + def print_ref_labels(io) + pull_requests.each { |pr| print_ref_label(io, pr) } + end + + def print_ref_label(io, pr) + pr.link_ref(io) + io << ": " << pr.permalink + io.puts + end +end + +entries = milestone.pull_requests.compact_map do |pr| + ChangelogEntry.new(pr) unless pr.fixup? +end + +milestone.pull_requests.each do |pr| + parent_number = pr.fixup? || next + + parent_entry = entries.find { |entry| entry.pr.number == parent_number } + if parent_entry + parent_entry.pull_requests << pr + else + STDERR.puts "Unresolved fixup: ##{parent_number} for: #{pr.title} (##{pr.number})" + end +end + +sections = entries.group_by(&.pr.section) SECTION_TITLES = { "breaking" => "Breaking changes", @@ -367,37 +421,37 @@ puts puts "[#{milestone.title}]: https://github.com/#{repository}/releases/#{milestone.title}" puts -def print_items(prs) - prs.each do |pr| - puts "- #{pr}" +def print_entries(entries) + entries.each do |entry| + puts "- #{entry}" end puts - prs.each(&.print_ref_label(STDOUT)) + entries.each(&.print_ref_labels(STDOUT)) puts end SECTION_TITLES.each do |id, title| - prs = sections[id]? || next + entries = sections[id]? || next puts "### #{title}" puts if id == "infra" - prs.sort_by!(&.infra_sort_tuple) - print_items prs + entries.sort_by!(&.pr.infra_sort_tuple) + print_entries entries else - topics = prs.group_by(&.primary_topic) + topics = entries.group_by(&.pr.primary_topic) topic_titles = topics.keys.sort_by! { |k| TOPIC_ORDER.index(k) || Int32::MAX } topic_titles.each do |topic_title| - topic_prs = topics[topic_title]? || next + topic_entries = topics[topic_title]? || next puts "#### #{topic_title}" puts - topic_prs.sort! - print_items topic_prs + topic_entries.sort_by!(&.pr) + print_entries topic_entries end end end From 0580ff2a1825edd0e939d7d0b25d206305ebec2d Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 4 Dec 2024 13:56:33 +0100 Subject: [PATCH 1505/1551] Change `libevent` event loop to wait forever when blocking (#15243) --- src/crystal/event_loop/libevent.cr | 4 +++- src/crystal/event_loop/libevent/event.cr | 5 +---- src/crystal/event_loop/libevent/lib_event2.cr | 5 +++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/crystal/event_loop/libevent.cr b/src/crystal/event_loop/libevent.cr index 21ad97030336..7b45939bd537 100644 --- a/src/crystal/event_loop/libevent.cr +++ b/src/crystal/event_loop/libevent.cr @@ -15,7 +15,9 @@ class Crystal::EventLoop::LibEvent < Crystal::EventLoop {% end %} def run(blocking : Bool) : Bool - event_base.loop(once: true, nonblock: !blocking) + flags = LibEvent2::EventLoopFlags::Once + flags |= blocking ? LibEvent2::EventLoopFlags::NoExitOnEmpty : LibEvent2::EventLoopFlags::NonBlock + event_base.loop(flags) end def interrupt : Nil diff --git a/src/crystal/event_loop/libevent/event.cr b/src/crystal/event_loop/libevent/event.cr index d6b1a5dc0433..084ba30bb1d2 100644 --- a/src/crystal/event_loop/libevent/event.cr +++ b/src/crystal/event_loop/libevent/event.cr @@ -61,10 +61,7 @@ class Crystal::EventLoop::LibEvent < Crystal::EventLoop # NOTE: may return `true` even if no event has been triggered (e.g. # nonblocking), but `false` means that nothing was processed. - def loop(once : Bool, nonblock : Bool) : Bool - flags = LibEvent2::EventLoopFlags::None - flags |= LibEvent2::EventLoopFlags::Once if once - flags |= LibEvent2::EventLoopFlags::NonBlock if nonblock + def loop(flags : LibEvent2::EventLoopFlags) : Bool LibEvent2.event_base_loop(@base, flags) == 0 end diff --git a/src/crystal/event_loop/libevent/lib_event2.cr b/src/crystal/event_loop/libevent/lib_event2.cr index e8e44b0f7473..98280f407df3 100644 --- a/src/crystal/event_loop/libevent/lib_event2.cr +++ b/src/crystal/event_loop/libevent/lib_event2.cr @@ -31,8 +31,9 @@ lib LibEvent2 @[Flags] enum EventLoopFlags - Once = 0x01 - NonBlock = 0x02 + Once = 0x01 + NonBlock = 0x02 + NoExitOnEmpty = 0x04 end @[Flags] From 0e80a60f0826f6e5031edbd55ee3c3edd9891c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 4 Dec 2024 22:28:11 +0100 Subject: [PATCH 1506/1551] Add stringification for `HTTP::Cookies` (#15246) --- spec/std/http/cookie_spec.cr | 35 +++++++++++++++++++++++++++++++++++ src/http/cookie.cr | 27 +++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/spec/std/http/cookie_spec.cr b/spec/std/http/cookie_spec.cr index be4ad06a6b6b..96c5f4b879a6 100644 --- a/spec/std/http/cookie_spec.cr +++ b/spec/std/http/cookie_spec.cr @@ -751,4 +751,39 @@ module HTTP cookies.to_h.should_not eq(cookies_hash) end end + + describe "#to_s" do + it "stringifies" do + cookies = HTTP::Cookies{ + HTTP::Cookie.new("foo", "bar"), + HTTP::Cookie.new("x", "y", domain: "example.com", path: "/foo", expires: Time.unix(1257894000), samesite: :lax), + } + + cookies.to_s.should eq %(HTTP::Cookies{"foo=bar", "x=y; domain=example.com; path=/foo; expires=Tue, 10 Nov 2009 23:00:00 GMT; SameSite=Lax"}) + end + end + + describe "#inspect" do + it "stringifies" do + cookies = HTTP::Cookies{ + HTTP::Cookie.new("foo", "bar"), + HTTP::Cookie.new("x", "y", domain: "example.com", path: "/foo", expires: Time.unix(1257894000), samesite: :lax), + } + + cookies.inspect.should eq %(HTTP::Cookies{"foo=bar", "x=y; domain=example.com; path=/foo; expires=Tue, 10 Nov 2009 23:00:00 GMT; SameSite=Lax"}) + end + end + + describe "#pretty_print" do + it "stringifies" do + cookies = HTTP::Cookies{ + HTTP::Cookie.new("foo", "bar"), + HTTP::Cookie.new("x", "y", domain: "example.com", path: "/foo", expires: Time.unix(1257894000), samesite: :lax), + } + cookies.pretty_inspect.should eq <<-CRYSTAL + HTTP::Cookies{"foo=bar", + "x=y; domain=example.com; path=/foo; expires=Tue, 10 Nov 2009 23:00:00 GMT; SameSite=Lax"} + CRYSTAL + end + end end diff --git a/src/http/cookie.cr b/src/http/cookie.cr index 5eabf833ad60..56d8800848d7 100644 --- a/src/http/cookie.cr +++ b/src/http/cookie.cr @@ -539,5 +539,32 @@ module HTTP def to_h : Hash(String, Cookie) @cookies.dup end + + # Returns a string representation of this cookies list. + # + # It uses the `Set-Cookie` serialization from `Cookie#to_set_cookie_header` which + # represents the full state of the cookie. + # + # ``` + # HTTP::Cookies{ + # HTTP::Cookie.new("foo", "bar"), + # HTTP::Cookie.new("foo", "bar", domain: "example.com"), + # }.to_s # => "HTTP::Cookies{\"foo=bar\", \"foo=bar; domain=example.com\"}" + # ``` + def to_s(io : IO) + io << "HTTP::Cookies{" + join(io, ", ") { |cookie| cookie.to_set_cookie_header.inspect(io) } + io << "}" + end + + # :ditto: + def inspect(io : IO) + to_s(io) + end + + # :ditto: + def pretty_print(pp) : Nil + pp.list("HTTP::Cookies{", self, "}") { |elem| pp.text(elem.to_set_cookie_header.inspect) } + end end end From f8db68ec58910477e7e5e82bd65d16e2638008c9 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Fri, 6 Dec 2024 10:32:58 +0100 Subject: [PATCH 1507/1551] Refactor the IOCP event loop (timers, ...) (#15238) Upgrades the IOCP event loop for Windows to be on par with the Polling event loops (epoll, kqueue) on UNIX. After a few low hanging fruits (enqueue multiple fibers on each call, for example) the last commit completely rewrites the `#run` method: - store events in pairing heaps; - high resolution timers (`CreateWaitableTimer`); - block forever/never (no need for timeout); - cancelling timeouts (no more dead fibers); - thread safety (parallel timer de/enqueues) for [RFC #0002]; - interrupt run using completion key instead of an UserAPC for [RFC #0002] (untested). [RFC #0002]: https://github.com/crystal-lang/rfcs/pull/2 --- .github/workflows/mingw-w64.yml | 2 +- .../event_loop/{polling => }/timers_spec.cr | 63 ++-- src/crystal/event_loop/iocp.cr | 271 +++++++++--------- src/crystal/event_loop/iocp/fiber_event.cr | 34 +++ src/crystal/event_loop/iocp/timer.cr | 40 +++ src/crystal/event_loop/polling.cr | 3 +- .../event_loop/{polling => }/timers.cr | 18 +- src/crystal/system/win32/addrinfo.cr | 2 +- src/crystal/system/win32/file_descriptor.cr | 2 +- src/crystal/system/win32/iocp.cr | 159 ++++++++-- src/crystal/system/win32/process.cr | 2 +- src/crystal/system/win32/waitable_timer.cr | 38 +++ src/kernel.cr | 13 - src/lib_c/x86_64-windows-msvc/c/ntdef.cr | 16 ++ src/lib_c/x86_64-windows-msvc/c/ntdll.cr | 36 +++ src/lib_c/x86_64-windows-msvc/c/ntstatus.cr | 2 + src/lib_c/x86_64-windows-msvc/c/synchapi.cr | 7 + src/lib_c/x86_64-windows-msvc/c/winnt.cr | 5 + src/lib_c/x86_64-windows-msvc/c/winternl.cr | 4 + src/winerror.cr | 9 + 20 files changed, 526 insertions(+), 200 deletions(-) rename spec/std/crystal/event_loop/{polling => }/timers_spec.cr (51%) create mode 100644 src/crystal/event_loop/iocp/fiber_event.cr create mode 100644 src/crystal/event_loop/iocp/timer.cr rename src/crystal/event_loop/{polling => }/timers.cr (75%) create mode 100644 src/crystal/system/win32/waitable_timer.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/ntdef.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/ntdll.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/winternl.cr diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml index eacf6a34c006..a9bbec81e1ce 100644 --- a/.github/workflows/mingw-w64.yml +++ b/.github/workflows/mingw-w64.yml @@ -80,7 +80,7 @@ jobs: cc crystal.obj -o .build/crystal.exe -municode \ $(pkg-config bdw-gc libpcre2-8 iconv zlib libffi --libs) \ $(llvm-config --libs --system-libs --ldflags) \ - -lole32 -lWS2_32 -Wl,--stack,0x800000 + -lole32 -lWS2_32 -lntdll -Wl,--stack,0x800000 - name: Package Crystal shell: msys2 {0} diff --git a/spec/std/crystal/event_loop/polling/timers_spec.cr b/spec/std/crystal/event_loop/timers_spec.cr similarity index 51% rename from spec/std/crystal/event_loop/polling/timers_spec.cr rename to spec/std/crystal/event_loop/timers_spec.cr index 6f6b8a670b08..a474d0a5167c 100644 --- a/spec/std/crystal/event_loop/polling/timers_spec.cr +++ b/spec/std/crystal/event_loop/timers_spec.cr @@ -1,13 +1,26 @@ -{% skip_file unless Crystal::EventLoop.has_constant?(:Polling) %} - require "spec" +require "crystal/event_loop/timers" + +private struct Timer + include Crystal::PointerPairingHeap::Node + + property! wake_at : Time::Span + + def initialize(timeout : Time::Span? = nil) + @wake_at = Time.monotonic + timeout if timeout + end + + def heap_compare(other : Pointer(self)) : Bool + wake_at < other.value.wake_at + end +end -describe Crystal::EventLoop::Polling::Timers do +describe Crystal::EventLoop::Timers do it "#empty?" do - timers = Crystal::EventLoop::Polling::Timers.new + timers = Crystal::EventLoop::Timers(Timer).new timers.empty?.should be_true - event = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 7.seconds) + event = Timer.new(7.seconds) timers.add(pointerof(event)) timers.empty?.should be_false @@ -17,13 +30,13 @@ describe Crystal::EventLoop::Polling::Timers do it "#next_ready?" do # empty - timers = Crystal::EventLoop::Polling::Timers.new + timers = Crystal::EventLoop::Timers(Timer).new timers.next_ready?.should be_nil # with events - event1s = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.second) - event3m = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 3.minutes) - event5m = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 5.minutes) + event1s = Timer.new(1.second) + event3m = Timer.new(3.minutes) + event5m = Timer.new(5.minutes) timers.add(pointerof(event5m)) timers.next_ready?.should eq(event5m.wake_at?) @@ -36,11 +49,11 @@ describe Crystal::EventLoop::Polling::Timers do end it "#dequeue_ready" do - timers = Crystal::EventLoop::Polling::Timers.new + timers = Crystal::EventLoop::Timers(Timer).new - event1 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds) - event2 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds) - event3 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.minute) + event1 = Timer.new(0.seconds) + event2 = Timer.new(0.seconds) + event3 = Timer.new(1.minute) # empty called = 0 @@ -48,12 +61,12 @@ describe Crystal::EventLoop::Polling::Timers do called.should eq(0) # add events in non chronological order - timers = Crystal::EventLoop::Polling::Timers.new + timers = Crystal::EventLoop::Timers(Timer).new timers.add(pointerof(event1)) timers.add(pointerof(event3)) timers.add(pointerof(event2)) - events = [] of Crystal::EventLoop::Polling::Event* + events = [] of Timer* timers.dequeue_ready { |event| events << event } events.should eq([ @@ -64,12 +77,12 @@ describe Crystal::EventLoop::Polling::Timers do end it "#add" do - timers = Crystal::EventLoop::Polling::Timers.new + timers = Crystal::EventLoop::Timers(Timer).new - event0 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current) - event1 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds) - event2 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 2.minutes) - event3 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.minute) + event0 = Timer.new + event1 = Timer.new(0.seconds) + event2 = Timer.new(2.minutes) + event3 = Timer.new(1.minute) # add events in non chronological order timers.add(pointerof(event1)).should be_true # added to the head (next ready) @@ -81,13 +94,13 @@ describe Crystal::EventLoop::Polling::Timers do end it "#delete" do - event1 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds) - event2 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds) - event3 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.minute) - event4 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 4.minutes) + event1 = Timer.new(0.seconds) + event2 = Timer.new(0.seconds) + event3 = Timer.new(1.minute) + event4 = Timer.new(4.minutes) # add events in non chronological order - timers = Crystal::EventLoop::Polling::Timers.new + timers = Crystal::EventLoop::Timers(Timer).new timers.add(pointerof(event1)) timers.add(pointerof(event3)) timers.add(pointerof(event2)) diff --git a/src/crystal/event_loop/iocp.cr b/src/crystal/event_loop/iocp.cr index ce3112fa9d1d..5628e99121b1 100644 --- a/src/crystal/event_loop/iocp.cr +++ b/src/crystal/event_loop/iocp.cr @@ -1,146 +1,186 @@ -require "c/ioapiset" -require "crystal/system/print_error" +# forward declaration for the require below to not create a module +class Crystal::EventLoop::IOCP < Crystal::EventLoop +end + +require "c/ntdll" require "../system/win32/iocp" +require "../system/win32/waitable_timer" +require "./timers" +require "./iocp/*" # :nodoc: class Crystal::EventLoop::IOCP < Crystal::EventLoop - # This is a list of resume and timeout events managed outside of IOCP. - @queue = Deque(Event).new - - @lock = Crystal::SpinLock.new - @interrupted = Atomic(Bool).new(false) - @blocked_thread = Atomic(Thread?).new(nil) + @waitable_timer : System::WaitableTimer? + @timer_packet : LibC::HANDLE? + @timer_key : System::IOCP::CompletionKey? + + def initialize + @mutex = Thread::Mutex.new + @timers = Timers(Timer).new + + # the completion port + @iocp = System::IOCP.new + + # custom completion to interrupt a blocking run + @interrupted = Atomic(Bool).new(false) + @interrupt_key = System::IOCP::CompletionKey.new(:interrupt) + + # On Windows 10+ we leverage a high resolution timer with completion packet + # to notify a completion port; on legacy Windows we fallback to the low + # resolution timeout (~15.6ms) + if System::IOCP.wait_completion_packet_methods? + @waitable_timer = System::WaitableTimer.new + @timer_packet = @iocp.create_wait_completion_packet + @timer_key = System::IOCP::CompletionKey.new(:timer) + end + end - # Returns the base IO Completion Port - getter iocp : LibC::HANDLE do - create_completion_port(LibC::INVALID_HANDLE_VALUE, nil) + # Returns the base IO Completion Port. + def iocp_handle : LibC::HANDLE + @iocp.handle end - def create_completion_port(handle : LibC::HANDLE, parent : LibC::HANDLE? = iocp) - iocp = LibC.CreateIoCompletionPort(handle, parent, nil, 0) - if iocp.null? - raise IO::Error.from_winerror("CreateIoCompletionPort") - end - if parent - # all overlapped operations may finish synchronously, in which case we do - # not reschedule the running fiber; the following call tells Win32 not to - # queue an I/O completion packet to the associated IOCP as well, as this - # would be done by default - if LibC.SetFileCompletionNotificationModes(handle, LibC::FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) == 0 - raise IO::Error.from_winerror("SetFileCompletionNotificationModes") - end + def create_completion_port(handle : LibC::HANDLE) : LibC::HANDLE + iocp = LibC.CreateIoCompletionPort(handle, @iocp.handle, nil, 0) + raise IO::Error.from_winerror("CreateIoCompletionPort") if iocp.null? + + # all overlapped operations may finish synchronously, in which case we do + # not reschedule the running fiber; the following call tells Win32 not to + # queue an I/O completion packet to the associated IOCP as well, as this + # would be done by default + if LibC.SetFileCompletionNotificationModes(handle, LibC::FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) == 0 + raise IO::Error.from_winerror("SetFileCompletionNotificationModes") end + iocp end + def run(blocking : Bool) : Bool + enqueued = false + + run_impl(blocking) do |fiber| + fiber.enqueue + enqueued = true + end + + enqueued + end + # Runs the event loop and enqueues the fiber for the next upcoming event or # completion. - def run(blocking : Bool) : Bool - # Pull the next upcoming event from the event queue. This determines the - # timeout for waiting on the completion port. - # OPTIMIZE: Implement @queue as a priority queue in order to avoid this - # explicit search for the lowest value and dequeue more efficient. - next_event = @queue.min_by?(&.wake_at) - - # no registered events: nothing to wait for - return false unless next_event - - now = Time.monotonic - - if next_event.wake_at > now - # There is no event ready to wake. We wait for completions until the next - # event wake time, unless nonblocking or already interrupted (timeout - # immediately). - if blocking - @lock.sync do - if @interrupted.get(:acquire) - blocking = false - else - # memorize the blocked thread (so we can alert it) - @blocked_thread.set(Thread.current, :release) - end - end + private def run_impl(blocking : Bool, &) : Nil + Crystal.trace :evloop, "run", blocking: blocking ? 1 : 0 + + if @waitable_timer + timeout = blocking ? LibC::INFINITE : 0_i64 + elsif blocking + if time = @mutex.synchronize { @timers.next_ready? } + # convert absolute time of next timer to relative time, expressed in + # milliseconds, rounded up + seconds, nanoseconds = System::Time.monotonic + relative = time - Time::Span.new(seconds: seconds, nanoseconds: nanoseconds) + timeout = (relative.to_i * 1000 + (relative.nanoseconds + 999_999) // 1_000_000).clamp(0_i64..) + else + timeout = LibC::INFINITE end + else + timeout = 0_i64 + end - wait_time = blocking ? (next_event.wake_at - now).total_milliseconds : 0 - timed_out = System::IOCP.wait_queued_completions(wait_time, alertable: blocking) do |fiber| - # This block may run multiple times. Every single fiber gets enqueued. - fiber.enqueue + # the array must be at least as large as `overlapped_entries` in + # `System::IOCP#wait_queued_completions` + events = uninitialized FiberEvent[64] + size = 0 + + @iocp.wait_queued_completions(timeout) do |fiber| + if (event = fiber.@resume_event) && event.wake_at? + events[size] = event + size += 1 end + yield fiber + end - @blocked_thread.set(nil, :release) - @interrupted.set(false, :release) + @mutex.synchronize do + # cancel the timeout of completed operations + events.to_slice[0...size].each do |event| + @timers.delete(pointerof(event.@timer)) + event.clear + end - # The wait for completion enqueued events. - return true unless timed_out + # run expired timers + @timers.dequeue_ready do |timer| + process_timer(timer) { |fiber| yield fiber } + end - # Wait for completion timed out but it may have been interrupted or we ask - # for immediate timeout (nonblocking), so we check for the next event - # readiness again: - return false if next_event.wake_at > Time.monotonic + # update timer + rearm_waitable_timer(@timers.next_ready?, interruptible: false) end - # next_event gets activated because its wake time is passed, either from the - # start or because completion wait has timed out. - - dequeue next_event - - fiber = next_event.fiber + @interrupted.set(false, :release) + end - # If the waiting fiber was already shut down in the mean time, we can just - # abandon here. There's no need to go for the next event because the scheduler - # will just try again. - # OPTIMIZE: It might still be worth considering to start over from the top - # or call recursively, in order to ensure at least one fiber get enqueued. - # This would avoid the scheduler needing to looking at runnable again just - # to notice it's still empty. The lock involved there should typically be - # uncontested though, so it's probably not a big deal. - return false if fiber.dead? + private def process_timer(timer : Pointer(Timer), &) + fiber = timer.value.fiber - # A timeout event needs special handling because it does not necessarily - # means to resume the fiber directly, in case a different select branch - # was already activated. - if next_event.timeout? && (select_action = fiber.timeout_select_action) + case timer.value.type + in .sleep? + timer.value.timed_out! + fiber.@resume_event.as(FiberEvent).clear + in .select_timeout? + return unless select_action = fiber.timeout_select_action fiber.timeout_select_action = nil - select_action.time_expired(fiber) - else - fiber.enqueue + return unless select_action.time_expired? + fiber.@timeout_event.as(FiberEvent).clear end - # We enqueued a fiber. - true + yield fiber end def interrupt : Nil - thread = nil - - @lock.sync do - @interrupted.set(true) - thread = @blocked_thread.swap(nil, :acquire) + unless @interrupted.get(:acquire) + @iocp.post_queued_completion_status(@interrupt_key) end - return unless thread + end - # alert the thread to interrupt GetQueuedCompletionStatusEx - LibC.QueueUserAPC(->(ptr : LibC::ULONG_PTR) { }, thread, LibC::ULONG_PTR.new(0)) + protected def add_timer(timer : Pointer(Timer)) : Nil + @mutex.synchronize do + is_next_ready = @timers.add(timer) + rearm_waitable_timer(timer.value.wake_at, interruptible: true) if is_next_ready + end end - def enqueue(event : Event) - unless @queue.includes?(event) - @queue << event + protected def delete_timer(timer : Pointer(Timer)) : Nil + @mutex.synchronize do + _, was_next_ready = @timers.delete(timer) + rearm_waitable_timer(@timers.next_ready?, interruptible: false) if was_next_ready end end - def dequeue(event : Event) - @queue.delete(event) + protected def rearm_waitable_timer(time : Time::Span?, interruptible : Bool) : Nil + if waitable_timer = @waitable_timer + status = @iocp.cancel_wait_completion_packet(@timer_packet.not_nil!, true) + if time + waitable_timer.set(time) + if status == LibC::STATUS_PENDING + interrupt + else + # STATUS_CANCELLED, STATUS_SUCCESS + @iocp.associate_wait_completion_packet(@timer_packet.not_nil!, waitable_timer.handle, @timer_key.not_nil!) + end + else + waitable_timer.cancel + end + elsif interruptible + interrupt + end end - # Create a new resume event for a fiber. - def create_resume_event(fiber : Fiber) : Crystal::EventLoop::Event - Event.new(fiber) + def create_resume_event(fiber : Fiber) : EventLoop::Event + FiberEvent.new(:sleep, fiber) end - def create_timeout_event(fiber) : Crystal::EventLoop::Event - Event.new(fiber, timeout: true) + def create_timeout_event(fiber : Fiber) : EventLoop::Event + FiberEvent.new(:select_timeout, fiber) end def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32 @@ -278,28 +318,3 @@ class Crystal::EventLoop::IOCP < Crystal::EventLoop def remove(socket : ::Socket) : Nil end end - -class Crystal::EventLoop::IOCP::Event - include Crystal::EventLoop::Event - - getter fiber - getter wake_at - getter? timeout - - def initialize(@fiber : Fiber, @wake_at = Time.monotonic, *, @timeout = false) - end - - # Frees the event - def free : Nil - Crystal::EventLoop.current.dequeue(self) - end - - def delete - free - end - - def add(timeout : Time::Span) : Nil - @wake_at = Time.monotonic + timeout - Crystal::EventLoop.current.enqueue(self) - end -end diff --git a/src/crystal/event_loop/iocp/fiber_event.cr b/src/crystal/event_loop/iocp/fiber_event.cr new file mode 100644 index 000000000000..481648016210 --- /dev/null +++ b/src/crystal/event_loop/iocp/fiber_event.cr @@ -0,0 +1,34 @@ +class Crystal::EventLoop::IOCP::FiberEvent + include Crystal::EventLoop::Event + + delegate type, wake_at, wake_at?, fiber, timed_out?, to: @timer + + def initialize(type : Timer::Type, fiber : Fiber) + @timer = Timer.new(type, fiber) + end + + # io timeout, sleep, or select timeout + def add(timeout : Time::Span) : Nil + seconds, nanoseconds = System::Time.monotonic + now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds) + @timer.wake_at = now + timeout + EventLoop.current.add_timer(pointerof(@timer)) + end + + # select timeout has been cancelled + def delete : Nil + return unless @timer.wake_at? + EventLoop.current.delete_timer(pointerof(@timer)) + clear + end + + # fiber died + def free : Nil + delete + end + + # the timer triggered (already dequeued from eventloop) + def clear : Nil + @timer.wake_at = nil + end +end diff --git a/src/crystal/event_loop/iocp/timer.cr b/src/crystal/event_loop/iocp/timer.cr new file mode 100644 index 000000000000..b7284d53e130 --- /dev/null +++ b/src/crystal/event_loop/iocp/timer.cr @@ -0,0 +1,40 @@ +# NOTE: this struct is only needed to be able to re-use `PointerPairingHeap` +# because EventLoop::Polling uses pointers. If `EventLoop::Polling::Event` was a +# reference, then `PairingHeap` wouldn't need pointers, and this struct could be +# merged into `Event`. +struct Crystal::EventLoop::IOCP::Timer + enum Type + Sleep + SelectTimeout + end + + getter type : Type + + # The `Fiber` that is waiting on the event and that the `EventLoop` shall + # resume. + getter fiber : Fiber + + # The absolute time, against the monotonic clock, at which a timed event shall + # trigger. Nil for IO events without a timeout. + getter! wake_at : Time::Span + + # True if an IO event has timed out (i.e. we're past `#wake_at`). + getter? timed_out : Bool = false + + # The event can be added to the `Timers` list. + include PointerPairingHeap::Node + + def initialize(@type : Type, @fiber) + end + + def wake_at=(@wake_at) + end + + def timed_out! : Bool + @timed_out = true + end + + def heap_compare(other : Pointer(self)) : Bool + wake_at < other.value.wake_at + end +end diff --git a/src/crystal/event_loop/polling.cr b/src/crystal/event_loop/polling.cr index 0df0b134c7f4..774cc7060715 100644 --- a/src/crystal/event_loop/polling.cr +++ b/src/crystal/event_loop/polling.cr @@ -2,6 +2,7 @@ abstract class Crystal::EventLoop::Polling < Crystal::EventLoop; end require "./polling/*" +require "./timers" module Crystal::System::FileDescriptor # user data (generation index for the arena) @@ -96,7 +97,7 @@ abstract class Crystal::EventLoop::Polling < Crystal::EventLoop end @lock = SpinLock.new # protects parallel accesses to @timers - @timers = Timers.new + @timers = Timers(Event).new # reset the mutexes since another thread may have acquired the lock of one # event loop, which would prevent closing file descriptors for example. diff --git a/src/crystal/event_loop/polling/timers.cr b/src/crystal/event_loop/timers.cr similarity index 75% rename from src/crystal/event_loop/polling/timers.cr rename to src/crystal/event_loop/timers.cr index b9191f008f46..0ea686efad82 100644 --- a/src/crystal/event_loop/polling/timers.cr +++ b/src/crystal/event_loop/timers.cr @@ -1,15 +1,17 @@ require "crystal/pointer_pairing_heap" -# List of `Event` ordered by `Event#wake_at` ascending. Optimized for fast -# dequeue and determining when is the next timer event. +# List of `Pointer(T)` to `T` structs. # -# Thread unsafe: parallel accesses much be protected! +# Internally wraps a `PointerPairingHeap(T)` and thus requires that `T` +# implements `PointerPairingHeap::Node`. +# +# Thread unsafe: parallel accesses must be protected! # # NOTE: this is a struct because it only wraps a const pointer to an object # allocated in the heap. -struct Crystal::EventLoop::Polling::Timers +struct Crystal::EventLoop::Timers(T) def initialize - @heap = PointerPairingHeap(Event).new + @heap = PointerPairingHeap(T).new end def empty? : Bool @@ -24,7 +26,7 @@ struct Crystal::EventLoop::Polling::Timers # Dequeues and yields each ready timer (their `#wake_at` is lower than # `System::Time.monotonic`) from the oldest to the most recent (i.e. time # ascending). - def dequeue_ready(& : Event* -> Nil) : Nil + def dequeue_ready(& : Pointer(T) -> Nil) : Nil seconds, nanoseconds = System::Time.monotonic now = Time::Span.new(seconds: seconds, nanoseconds: nanoseconds) @@ -36,7 +38,7 @@ struct Crystal::EventLoop::Polling::Timers end # Add a new timer into the list. Returns true if it is the next ready timer. - def add(event : Event*) : Bool + def add(event : Pointer(T)) : Bool @heap.add(event) @heap.first? == event end @@ -44,7 +46,7 @@ struct Crystal::EventLoop::Polling::Timers # Remove a timer from the list. Returns a tuple(dequeued, was_next_ready) of # booleans. The first bool tells whether the event was dequeued, in which case # the second one tells if it was the next ready event. - def delete(event : Event*) : {Bool, Bool} + def delete(event : Pointer(T)) : {Bool, Bool} if @heap.first? == event @heap.shift? {true, true} diff --git a/src/crystal/system/win32/addrinfo.cr b/src/crystal/system/win32/addrinfo.cr index da5cb6ce20c3..24cff9c9aec3 100644 --- a/src/crystal/system/win32/addrinfo.cr +++ b/src/crystal/system/win32/addrinfo.cr @@ -43,7 +43,7 @@ module Crystal::System::Addrinfo end end - IOCP::GetAddrInfoOverlappedOperation.run(Crystal::EventLoop.current.iocp) do |operation| + IOCP::GetAddrInfoOverlappedOperation.run(Crystal::EventLoop.current.iocp_handle) do |operation| completion_routine = LibC::LPLOOKUPSERVICE_COMPLETION_ROUTINE.new do |dwError, dwBytes, lpOverlapped| orig_operation = IOCP::GetAddrInfoOverlappedOperation.unbox(lpOverlapped) LibC.PostQueuedCompletionStatus(orig_operation.iocp, 0, 0, lpOverlapped) diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 894fcfaf5cb1..4a99d82e9134 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -489,7 +489,7 @@ private module ConsoleUtils @@read_requests << ReadRequest.new( handle: handle, slice: slice, - iocp: Crystal::EventLoop.current.iocp, + iocp: Crystal::EventLoop.current.iocp_handle, completion_key: Crystal::System::IOCP::CompletionKey.new(:stdin_read, ::Fiber.current), ) @@read_cv.signal diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr index fece9ada3a83..70048d24cf8c 100644 --- a/src/crystal/system/win32/iocp.cr +++ b/src/crystal/system/win32/iocp.cr @@ -1,14 +1,63 @@ {% skip_file unless flag?(:win32) %} require "c/handleapi" +require "c/ioapiset" +require "c/ntdll" require "crystal/system/thread_linked_list" # :nodoc: -module Crystal::System::IOCP +struct Crystal::System::IOCP + @@wait_completion_packet_methods : Bool? = nil + + {% if flag?(:interpreted) %} + # We can't load the symbols from interpreted code since it would create + # interpreted Proc. We thus merely check for the existence of the symbols, + # then let the interpreter load the symbols, which will create interpreter + # Proc (not interpreted) that can be called. + class_getter?(wait_completion_packet_methods : Bool) do + detect_wait_completion_packet_methods + end + + private def self.detect_wait_completion_packet_methods : Bool + if handle = LibC.LoadLibraryExW(Crystal::System.to_wstr("ntdll.dll"), nil, 0) + !LibC.GetProcAddress(handle, "NtCreateWaitCompletionPacket").null? + else + false + end + end + {% else %} + @@_NtCreateWaitCompletionPacket = uninitialized LibNTDLL::NtCreateWaitCompletionPacketProc + @@_NtAssociateWaitCompletionPacket = uninitialized LibNTDLL::NtAssociateWaitCompletionPacketProc + @@_NtCancelWaitCompletionPacket = uninitialized LibNTDLL::NtCancelWaitCompletionPacketProc + + class_getter?(wait_completion_packet_methods : Bool) do + load_wait_completion_packet_methods + end + + private def self.load_wait_completion_packet_methods : Bool + handle = LibC.LoadLibraryExW(Crystal::System.to_wstr("ntdll.dll"), nil, 0) + return false if handle.null? + + pointer = LibC.GetProcAddress(handle, "NtCreateWaitCompletionPacket") + return false if pointer.null? + @@_NtCreateWaitCompletionPacket = LibNTDLL::NtCreateWaitCompletionPacketProc.new(pointer, Pointer(Void).null) + + pointer = LibC.GetProcAddress(handle, "NtAssociateWaitCompletionPacket") + @@_NtAssociateWaitCompletionPacket = LibNTDLL::NtAssociateWaitCompletionPacketProc.new(pointer, Pointer(Void).null) + + pointer = LibC.GetProcAddress(handle, "NtCancelWaitCompletionPacket") + @@_NtCancelWaitCompletionPacket = LibNTDLL::NtCancelWaitCompletionPacketProc.new(pointer, Pointer(Void).null) + + true + end + {% end %} + # :nodoc: class CompletionKey enum Tag ProcessRun StdinRead + Interrupt + Timer end property fiber : ::Fiber? @@ -16,17 +65,35 @@ module Crystal::System::IOCP def initialize(@tag : Tag, @fiber : ::Fiber? = nil) end + + def valid?(number_of_bytes_transferred) + case tag + in .process_run? + number_of_bytes_transferred.in?(LibC::JOB_OBJECT_MSG_EXIT_PROCESS, LibC::JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS) + in .stdin_read?, .interrupt?, .timer? + true + end + end + end + + getter handle : LibC::HANDLE + + def initialize + @handle = LibC.CreateIoCompletionPort(LibC::INVALID_HANDLE_VALUE, nil, nil, 0) + raise IO::Error.from_winerror("CreateIoCompletionPort") if @handle.null? end - def self.wait_queued_completions(timeout, alertable = false, &) - overlapped_entries = uninitialized LibC::OVERLAPPED_ENTRY[1] + def wait_queued_completions(timeout, alertable = false, &) + overlapped_entries = uninitialized LibC::OVERLAPPED_ENTRY[64] if timeout > UInt64::MAX timeout = LibC::INFINITE else timeout = timeout.to_u64 end - result = LibC.GetQueuedCompletionStatusEx(Crystal::EventLoop.current.iocp, overlapped_entries, overlapped_entries.size, out removed, timeout, alertable) + + result = LibC.GetQueuedCompletionStatusEx(@handle, overlapped_entries, overlapped_entries.size, out removed, timeout, alertable) + if result == 0 error = WinError.value if timeout && error.wait_timeout? @@ -42,17 +109,21 @@ module Crystal::System::IOCP raise IO::Error.new("GetQueuedCompletionStatusEx returned 0") end + # TODO: wouldn't the processing fit better in `EventLoop::IOCP#run`? removed.times do |i| entry = overlapped_entries[i] - # at the moment only `::Process#wait` uses a non-nil completion key; all - # I/O operations, including socket ones, do not set this field + # See `CompletionKey` for the operations that use a non-nil completion + # key. All IO operations (include File, Socket) do not set this field. case completion_key = Pointer(Void).new(entry.lpCompletionKey).as(CompletionKey?) in Nil operation = OverlappedOperation.unbox(entry.lpOverlapped) + Crystal.trace :evloop, "operation", op: operation.class.name, fiber: operation.@fiber operation.schedule { |fiber| yield fiber } in CompletionKey - if completion_key_valid?(completion_key, entry.dwNumberOfBytesTransferred) + Crystal.trace :evloop, "completion", tag: completion_key.tag.to_s, bytes: entry.dwNumberOfBytesTransferred, fiber: completion_key.fiber + + if completion_key.valid?(entry.dwNumberOfBytesTransferred) # if `Process` exits before a call to `#wait`, this fiber will be # reset already if fiber = completion_key.fiber @@ -69,12 +140,52 @@ module Crystal::System::IOCP false end - private def self.completion_key_valid?(completion_key, number_of_bytes_transferred) - case completion_key.tag - in .process_run? - number_of_bytes_transferred.in?(LibC::JOB_OBJECT_MSG_EXIT_PROCESS, LibC::JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS) - in .stdin_read? - true + def post_queued_completion_status(completion_key : CompletionKey, number_of_bytes_transferred = 0) + result = LibC.PostQueuedCompletionStatus(@handle, number_of_bytes_transferred, completion_key.as(Void*).address, nil) + raise RuntimeError.from_winerror("PostQueuedCompletionStatus") if result == 0 + end + + def create_wait_completion_packet : LibC::HANDLE + packet_handle = LibC::HANDLE.null + object_attributes = Pointer(LibC::OBJECT_ATTRIBUTES).null + status = + {% if flag?(:interpreted) %} + LibNTDLL.NtCreateWaitCompletionPacket(pointerof(packet_handle), LibNTDLL::GENERIC_ALL, object_attributes) + {% else %} + @@_NtCreateWaitCompletionPacket.call(pointerof(packet_handle), LibNTDLL::GENERIC_ALL, object_attributes) + {% end %} + raise RuntimeError.from_os_error("NtCreateWaitCompletionPacket", WinError.from_ntstatus(status)) unless status == 0 + packet_handle + end + + def associate_wait_completion_packet(wait_handle : LibC::HANDLE, target_handle : LibC::HANDLE, completion_key : CompletionKey) : Bool + signaled = 0_u8 + status = + {% if flag?(:interpreted) %} + LibNTDLL.NtAssociateWaitCompletionPacket(wait_handle, @handle, + target_handle, completion_key.as(Void*), nil, 0, nil, pointerof(signaled)) + {% else %} + @@_NtAssociateWaitCompletionPacket.call(wait_handle, @handle, + target_handle, completion_key.as(Void*), Pointer(Void).null, + LibNTDLL::NTSTATUS.new!(0), Pointer(LibC::ULONG).null, + pointerof(signaled)) + {% end %} + raise RuntimeError.from_os_error("NtAssociateWaitCompletionPacket", WinError.from_ntstatus(status)) unless status == 0 + signaled == 1 + end + + def cancel_wait_completion_packet(wait_handle : LibC::HANDLE, remove_signaled : Bool) : LibNTDLL::NTSTATUS + status = + {% if flag?(:interpreted) %} + LibNTDLL.NtCancelWaitCompletionPacket(wait_handle, remove_signaled ? 1 : 0) + {% else %} + @@_NtCancelWaitCompletionPacket.call(wait_handle, remove_signaled ? 1_u8 : 0_u8) + {% end %} + case status + when LibC::STATUS_CANCELLED, LibC::STATUS_SUCCESS, LibC::STATUS_PENDING + status + else + raise RuntimeError.from_os_error("NtCancelWaitCompletionPacket", WinError.from_ntstatus(status)) end end @@ -112,23 +223,29 @@ module Crystal::System::IOCP end private def done! - @fiber.cancel_timeout @state = :done end private def wait_for_completion(timeout) if timeout - sleep timeout - else + event = ::Fiber.current.resume_event + event.add(timeout) + ::Fiber.suspend - end - unless @state.done? - if try_cancel - # Wait for cancellation to complete. We must not free the operation - # until it's completed. + if event.timed_out? + # By the time the fiber was resumed, the operation may have completed + # concurrently. + return if @state.done? + return unless try_cancel + + # We cancelled the operation or failed to cancel it (e.g. race + # condition), we must suspend the fiber again until the completion + # port is notified of the actual result. ::Fiber.suspend end + else + ::Fiber.suspend end end end diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 7031654d2299..5eb02d826c3b 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -37,7 +37,7 @@ struct Crystal::System::Process LibC::JOBOBJECTINFOCLASS::AssociateCompletionPortInformation, LibC::JOBOBJECT_ASSOCIATE_COMPLETION_PORT.new( completionKey: @completion_key.as(Void*), - completionPort: Crystal::EventLoop.current.iocp, + completionPort: Crystal::EventLoop.current.iocp_handle, ), ) diff --git a/src/crystal/system/win32/waitable_timer.cr b/src/crystal/system/win32/waitable_timer.cr new file mode 100644 index 000000000000..68ec821d6922 --- /dev/null +++ b/src/crystal/system/win32/waitable_timer.cr @@ -0,0 +1,38 @@ +require "c/ntdll" +require "c/synchapi" +require "c/winternl" + +class Crystal::System::WaitableTimer + getter handle : LibC::HANDLE + + def initialize + flags = LibC::CREATE_WAITABLE_TIMER_HIGH_RESOLUTION + desired_access = LibC::SYNCHRONIZE | LibC::TIMER_QUERY_STATE | LibC::TIMER_MODIFY_STATE + @handle = LibC.CreateWaitableTimerExW(nil, nil, flags, desired_access) + raise RuntimeError.from_winerror("CreateWaitableTimerExW") if @handle.null? + end + + def set(time : ::Time::Span) : Nil + # convert absolute time to relative time, expressed in 100ns interval, + # rounded up + seconds, nanoseconds = System::Time.monotonic + relative = time - ::Time::Span.new(seconds: seconds, nanoseconds: nanoseconds) + ticks = (relative.to_i * 10_000_000 + (relative.nanoseconds + 99) // 100).clamp(0_i64..) + + # negative duration means relative time (positive would mean absolute + # realtime clock) + duration = -ticks + + ret = LibC.SetWaitableTimer(@handle, pointerof(duration), 0, nil, nil, 0) + raise RuntimeError.from_winerror("SetWaitableTimer") if ret == 0 + end + + def cancel : Nil + ret = LibC.CancelWaitableTimer(@handle) + raise RuntimeError.from_winerror("CancelWaitableTimer") if ret == 0 + end + + def close : Nil + LibC.CloseHandle(@handle) + end +end diff --git a/src/kernel.cr b/src/kernel.cr index 2063acce95ae..34763b994839 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -617,19 +617,6 @@ end {% end %} {% end %} -# This is a temporary workaround to ensure there is always something in the IOCP -# event loop being awaited, since both the interrupt loop and the fiber stack -# pool collector are disabled in interpreted code. Without this, asynchronous -# code that bypasses `Crystal::System::IOCP::OverlappedOperation` does not currently -# work, see https://github.com/crystal-lang/crystal/pull/14949#issuecomment-2328314463 -{% if flag?(:interpreted) && flag?(:win32) %} - spawn(name: "Interpreter idle loop") do - while true - sleep 1.day - end - end -{% end %} - {% if flag?(:interpreted) && flag?(:unix) && Crystal::Interpreter.has_method?(:signal_descriptor) %} Crystal::System::Signal.setup_default_handlers {% end %} diff --git a/src/lib_c/x86_64-windows-msvc/c/ntdef.cr b/src/lib_c/x86_64-windows-msvc/c/ntdef.cr new file mode 100644 index 000000000000..a9a07a07b27e --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/ntdef.cr @@ -0,0 +1,16 @@ +lib LibC + struct UNICODE_STRING + length : USHORT + maximumLength : USHORT + buffer : LPWSTR + end + + struct OBJECT_ATTRIBUTES + length : ULONG + rootDirectory : HANDLE + objectName : UNICODE_STRING* + attributes : ULONG + securityDescriptor : Void* + securityQualityOfService : Void* + end +end diff --git a/src/lib_c/x86_64-windows-msvc/c/ntdll.cr b/src/lib_c/x86_64-windows-msvc/c/ntdll.cr new file mode 100644 index 000000000000..8d2653b8bb31 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/ntdll.cr @@ -0,0 +1,36 @@ +require "c/ntdef" +require "c/winnt" + +@[Link("ntdll")] +lib LibNTDLL + alias NTSTATUS = LibC::ULONG + alias ACCESS_MASK = LibC::DWORD + + GENERIC_ALL = 0x10000000_u32 + + alias NtCreateWaitCompletionPacketProc = Proc(LibC::HANDLE*, ACCESS_MASK, LibC::OBJECT_ATTRIBUTES*, NTSTATUS) + alias NtAssociateWaitCompletionPacketProc = Proc(LibC::HANDLE, LibC::HANDLE, LibC::HANDLE, Void*, Void*, NTSTATUS, LibC::ULONG*, LibC::BOOLEAN*, NTSTATUS) + alias NtCancelWaitCompletionPacketProc = Proc(LibC::HANDLE, LibC::BOOLEAN, NTSTATUS) + + fun NtCreateWaitCompletionPacket( + waitCompletionPacketHandle : LibC::HANDLE*, + desiredAccess : ACCESS_MASK, + objectAttributes : LibC::OBJECT_ATTRIBUTES*, + ) : NTSTATUS + + fun NtAssociateWaitCompletionPacket( + waitCompletionPacketHandle : LibC::HANDLE, + ioCompletionHandle : LibC::HANDLE, + targetObjectHandle : LibC::HANDLE, + keyContext : Void*, + apcContext : Void*, + ioStatus : NTSTATUS, + ioStatusInformation : LibC::ULONG*, + alreadySignaled : LibC::BOOLEAN*, + ) : NTSTATUS + + fun NtCancelWaitCompletionPacket( + waitCompletionPacketHandle : LibC::HANDLE, + removeSignaledPacket : LibC::BOOLEAN, + ) : NTSTATUS +end diff --git a/src/lib_c/x86_64-windows-msvc/c/ntstatus.cr b/src/lib_c/x86_64-windows-msvc/c/ntstatus.cr index 2a013036adb4..0596c641bcc3 100644 --- a/src/lib_c/x86_64-windows-msvc/c/ntstatus.cr +++ b/src/lib_c/x86_64-windows-msvc/c/ntstatus.cr @@ -1,6 +1,7 @@ require "lib_c" lib LibC + STATUS_SUCCESS = 0x00000000_u32 STATUS_FATAL_APP_EXIT = 0x40000015_u32 STATUS_DATATYPE_MISALIGNMENT = 0x80000002_u32 STATUS_BREAKPOINT = 0x80000003_u32 @@ -13,5 +14,6 @@ lib LibC STATUS_FLOAT_UNDERFLOW = 0xC0000093_u32 STATUS_PRIVILEGED_INSTRUCTION = 0xC0000096_u32 STATUS_STACK_OVERFLOW = 0xC00000FD_u32 + STATUS_CANCELLED = 0xC0000120_u32 STATUS_CONTROL_C_EXIT = 0xC000013A_u32 end diff --git a/src/lib_c/x86_64-windows-msvc/c/synchapi.cr b/src/lib_c/x86_64-windows-msvc/c/synchapi.cr index e101b7f6284b..e85f0af1eb8f 100644 --- a/src/lib_c/x86_64-windows-msvc/c/synchapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/synchapi.cr @@ -32,4 +32,11 @@ lib LibC fun Sleep(dwMilliseconds : DWORD) fun WaitForSingleObject(hHandle : HANDLE, dwMilliseconds : DWORD) : DWORD + + alias PTIMERAPCROUTINE = (Void*, DWORD, DWORD) -> + CREATE_WAITABLE_TIMER_HIGH_RESOLUTION = 0x00000002_u32 + + fun CreateWaitableTimerExW(lpTimerAttributes : SECURITY_ATTRIBUTES*, lpTimerName : LPWSTR, dwFlags : DWORD, dwDesiredAccess : DWORD) : HANDLE + fun SetWaitableTimer(hTimer : HANDLE, lpDueTime : LARGE_INTEGER*, lPeriod : LONG, pfnCompletionRoutine : PTIMERAPCROUTINE*, lpArgToCompletionRoutine : Void*, fResume : BOOL) : BOOL + fun CancelWaitableTimer(hTimer : HANDLE) : BOOL end diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index 99c8f24ac9e1..1bee1cb173ab 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -3,6 +3,8 @@ require "c/int_safe" lib LibC alias BOOLEAN = BYTE alias LONG = Int32 + alias ULONG = UInt32 + alias USHORT = UInt16 alias LARGE_INTEGER = Int64 alias CHAR = UChar @@ -469,4 +471,7 @@ lib LibC alias IMAGE_NT_HEADERS = IMAGE_NT_HEADERS64 alias IMAGE_THUNK_DATA = IMAGE_THUNK_DATA64 IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG64 + + TIMER_QUERY_STATE = 0x0001 + TIMER_MODIFY_STATE = 0x0002 end diff --git a/src/lib_c/x86_64-windows-msvc/c/winternl.cr b/src/lib_c/x86_64-windows-msvc/c/winternl.cr new file mode 100644 index 000000000000..7046370a1035 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/winternl.cr @@ -0,0 +1,4 @@ +@[Link("ntdll")] +lib LibNTDLL + fun RtlNtStatusToDosError(status : LibC::ULONG) : LibC::ULONG +end diff --git a/src/winerror.cr b/src/winerror.cr index 844df5b07315..ae4eceb1f18e 100644 --- a/src/winerror.cr +++ b/src/winerror.cr @@ -2,6 +2,7 @@ require "c/winbase" require "c/errhandlingapi" require "c/winsock2" + require "c/winternl" {% end %} # `WinError` represents Windows' [System Error Codes](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes#system-error-codes-1). @@ -54,6 +55,14 @@ enum WinError : UInt32 {% end %} end + def self.from_ntstatus(status) : self + {% if flag?(:win32) %} + WinError.new(LibNTDLL.RtlNtStatusToDosError(status)) + {% else %} + raise NotImplementedError.new("WinError.from_ntstatus") + {% end %} + end + # Returns the system error message associated with this error code. # # The message is retrieved via [`FormatMessageW`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagew) From 655b8a58cb95f41eb7eff17e9b58f79d2839f199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 7 Dec 2024 19:55:40 +0100 Subject: [PATCH 1508/1551] Add `Process::Status#exit_code?` (#15247) This provides an alternative to #exit_code which raises in this case since #15241. --- spec/std/process/status_spec.cr | 10 ++++++++++ src/process/status.cr | 15 ++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 96802be31489..5f0c3b597376 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -36,6 +36,16 @@ describe Process::Status do end end + it "#exit_code?" do + Process::Status.new(exit_status(0)).exit_code?.should eq 0 + Process::Status.new(exit_status(1)).exit_code?.should eq 1 + Process::Status.new(exit_status(127)).exit_code?.should eq 127 + Process::Status.new(exit_status(128)).exit_code?.should eq 128 + Process::Status.new(exit_status(255)).exit_code?.should eq 255 + + status_for(:interrupted).exit_code?.should eq({% if flag?(:unix) %}nil{% else %}LibC::STATUS_CONTROL_C_EXIT.to_i32!{% 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 a3db8a7c4346..738128519f36 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -215,8 +215,21 @@ class Process::Status # Process.new("sleep", ["10"]).tap(&.terminate).wait.exit_code # RuntimeError: Abnormal exit has no exit code # ``` def exit_code : Int32 + exit_code? || raise RuntimeError.new("Abnormal exit has no exit code") + end + + # Returns the exit code of the process if it exited normally. + # + # Returns `nil` if the status describes an abnormal exit. + # + # ``` + # Process.run("true").exit_code? # => 1 + # Process.run("exit 123", shell: true).exit_code? # => 123 + # Process.new("sleep", ["10"]).tap(&.terminate).wait.exit_code? # => nil + # ``` + def exit_code? : Int32? {% if flag?(:unix) %} - raise RuntimeError.new("Abnormal exit has no exit code") unless normal_exit? + return unless normal_exit? # define __WEXITSTATUS(status) (((status) & 0xff00) >> 8) (@exit_status & 0xff00) >> 8 From 972e1841ef6d1e4bb8d57c3b2833d8e2706b12e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 7 Dec 2024 19:55:58 +0100 Subject: [PATCH 1509/1551] Add specs for invalid special characters in `Cookie` (#15244) These specs describe the status quo of special character handling in HTTP::Cookie per #15218. --------- Co-authored-by: Julien Portalier --- spec/std/http/cookie_spec.cr | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/spec/std/http/cookie_spec.cr b/spec/std/http/cookie_spec.cr index 96c5f4b879a6..55183c48cbe5 100644 --- a/spec/std/http/cookie_spec.cr +++ b/spec/std/http/cookie_spec.cr @@ -15,6 +15,9 @@ private def parse_set_cookie(header) cookie.not_nil! end +# invalid printable ascii characters, non-printable ascii characters and control characters +private INVALID_COOKIE_VALUES = ("\x00".."\x08").to_a + ("\x0A".."\x1F").to_a + ["\r", "\t", "\n", %(" "), %("), ",", ";", "\\", "\x7f", "\xFF", "🍪"] + module HTTP describe Cookie do it "#==" do @@ -45,6 +48,12 @@ module HTTP expect_raises IO::Error, "Invalid cookie value" do HTTP::Cookie.new("x", %(foo\rbar)) end + + INVALID_COOKIE_VALUES.each do |char| + expect_raises IO::Error, "Invalid cookie value" do + HTTP::Cookie.new("x", char) + end + end end describe "with a security prefix" do @@ -132,14 +141,10 @@ module HTTP describe "#value=" do it "raises on invalid value" do cookie = HTTP::Cookie.new("x", "") - invalid_values = { - '"', ',', ';', '\\', # invalid printable ascii characters - '\r', '\t', '\n', # non-printable ascii characters - }.map { |c| "foo#{c}bar" } - invalid_values.each do |invalid_value| + INVALID_COOKIE_VALUES.each do |v| expect_raises IO::Error, "Invalid cookie value" do - cookie.value = invalid_value + cookie.value = "foo#{v}bar" end end end @@ -562,6 +567,16 @@ module HTTP cookies = Cookies.from_client_headers Headers{"Cookie" => "a=b", "Set-Cookie" => "x=y"} cookies.to_h.should eq({"a" => Cookie.new("a", "b")}) end + + it "chops value at the first invalid byte" do + HTTP::Cookies.from_client_headers( + HTTP::Headers{"Cookie" => "ginger=snap; cookie=hm🍪delicious; snicker=doodle"} + ).to_h.should eq({ + "ginger" => HTTP::Cookie.new("ginger", "snap"), + "cookie" => HTTP::Cookie.new("cookie", "hm"), + "snicker" => HTTP::Cookie.new("snicker", "doodle"), + }) + end end describe ".from_server_headers" do @@ -573,6 +588,15 @@ module HTTP cookies = Cookies.from_server_headers Headers{"Set-Cookie" => "a=b", "Cookie" => "x=y"} cookies.to_h.should eq({"a" => Cookie.new("a", "b")}) end + + it "drops cookies with invalid byte in value" do + HTTP::Cookies.from_server_headers( + HTTP::Headers{"Set-Cookie" => ["ginger=snap", "cookie=hm🍪delicious", "snicker=doodle"]} + ).to_h.should eq({ + "ginger" => HTTP::Cookie.new("ginger", "snap"), + "snicker" => HTTP::Cookie.new("snicker", "doodle"), + }) + end end it "allows adding cookies and retrieving" do From 6df6b3d8a304c4f33909f2ade737adfe6b8171cd Mon Sep 17 00:00:00 2001 From: Zeljko Predjeskovic <44953551+Zeljko-Predjeskovic@users.noreply.github.com> Date: Sat, 7 Dec 2024 19:56:18 +0100 Subject: [PATCH 1510/1551] Add `String#byte_index(Regex)` (#15248) --- spec/std/string_spec.cr | 21 +++++++++++++++++++++ src/string.cr | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 0a57ee9034a9..2bbc63f7e18e 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -1367,6 +1367,27 @@ describe "String" do "foo foo".byte_index("oo", 2).should eq(5) "こんにちは世界".byte_index("ちは").should eq(9) end + + it "gets byte index of regex" do + str = "0123x" + pattern = /x/ + + str.byte_index(pattern).should eq(4) + str.byte_index(pattern, offset: 4).should eq(4) + str.byte_index(pattern, offset: 5).should be_nil + str.byte_index(pattern, offset: -1).should eq(4) + str.byte_index(/y/).should be_nil + + str = "012abc678" + pattern = /[abc]/ + + str.byte_index(pattern).should eq(3) + str.byte_index(pattern, offset: 2).should eq(3) + str.byte_index(pattern, offset: 5).should eq(5) + str.byte_index(pattern, offset: -4).should eq(5) + str.byte_index(pattern, offset: -1).should be_nil + str.byte_index(/y/).should be_nil + end end describe "includes?" do diff --git a/src/string.cr b/src/string.cr index 4b52d08c7426..d47e87638976 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3886,6 +3886,27 @@ class String nil end + # Returns the byte index of the regex *pattern* in the string, or `nil` if the pattern does not find a match. + # If *offset* is present, it defines the position to start the search. + # + # Negative *offset* can be used to start the search from the end of the string. + # + # ``` + # "hello world".byte_index(/o/) # => 4 + # "hello world".byte_index(/o/, offset: 4) # => 4 + # "hello world".byte_index(/o/, offset: 5) # => 7 + # "hello world".byte_index(/o/, offset: -1) # => nil + # "hello world".byte_index(/y/) # => nil + # ``` + def byte_index(pattern : Regex, offset = 0, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32? + offset += bytesize if offset < 0 + return if offset < 0 + + if match = pattern.match_at_byte_index(self, offset, options: options) + match.byte_begin + end + end + # Returns the byte index of a char index, or `nil` if out of bounds. # # It is valid to pass `#size` to *index*, and in this case the answer From 4a98b919d0ea1dc3973b933a9aff94139e07e1e3 Mon Sep 17 00:00:00 2001 From: Barney <86712892+BigBoyBarney@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:08:42 +0100 Subject: [PATCH 1511/1551] Update link to good first issues (#15250) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 640c980909ee..3f9d8a47e21d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,8 +21,8 @@ The best place to start an open discussion about potential changes is the [Cryst ### What's needed right now -You can find a list of tasks that we consider suitable for a first time contribution at -the [newcomer label](https://github.com/crystal-lang/crystal/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%3Anewcomer). +You can find a list of tasks that we consider suitable for a first time contribution with +the [good first issue label](https://github.com/crystal-lang/crystal/contribute). As you feel more confident, you can keep an eye out for open issues with the following labels: * [`community:to-research`](https://github.com/crystal-lang/crystal/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Acommunity%3Ato-research): Help needed on **researching and investigating** the issue at hand; could be from going through an RFC to figure out how something _should_ be working, to go through details on a C-library we'd like to bind. From 6abe0ba00b4aeee8766441c18d83fb388c8b01bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 10 Dec 2024 11:09:01 +0100 Subject: [PATCH 1512/1551] Refactor use of `Process::Status#exit_code` to `#exit_code?` (#15254) Use the new non-raising variant `#exit_code?` (introduced in #15247) where possible over `#exit_code` which raises since https://github.com/crystal-lang/crystal/pull/15241. The code can also be simplified in some cases, removing unnecessary calls to `Status#normal_exit?`. --- src/compiler/crystal/command.cr | 4 ++-- src/compiler/crystal/compiler.cr | 19 ++++++++++--------- src/compiler/crystal/macros/methods.cr | 2 +- src/process/status.cr | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 571c965352e0..3ce4fcd71550 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -301,8 +301,8 @@ class Crystal::Command puts "Execute: #{elapsed_time}" end - if status.exit_reason.normal? && !error_on_exit - exit status.exit_code + if (exit_code = status.exit_code?) && !error_on_exit + exit exit_code end if message = exit_message(status) diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 878a1ae4896a..cebd5d222a5c 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -876,16 +876,17 @@ module Crystal status = $? unless status.success? - if status.normal_exit? - case status.exit_code - when 126 - linker_not_found File::AccessDeniedError, linker_name - when 127 - linker_not_found File::NotFoundError, linker_name - end + exit_code = status.exit_code? + case exit_code + when 126 + linker_not_found File::AccessDeniedError, linker_name + when 127 + linker_not_found File::NotFoundError, linker_name + when nil + # abnormal exit + exit_code = 1 end - code = status.normal_exit? ? status.exit_code : 1 - error "execution of command failed with exit status #{status}: #{command}", exit_code: code + error "execution of command failed with exit status #{status}: #{command}", exit_code: exit_code end end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 3a81015f0ffd..ab7b353fec45 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -325,7 +325,7 @@ module Crystal command = "#{Process.quote(original_filename)} #{Process.quote(run_args)}" message = IO::Memory.new - message << "Error executing run (exit code: #{result.status.exit_code}): #{command}\n" + message << "Error executing run (exit code: #{result.status}): #{command}\n" if result.stdout.empty? && result.stderr.empty? message << "\nGot no output." diff --git a/src/process/status.cr b/src/process/status.cr index 738128519f36..3ce5c54b8e18 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -240,7 +240,7 @@ class Process::Status # Returns `true` if the process exited normally with an exit code of `0`. def success? : Bool - normal_exit? && exit_code == 0 + exit_code? == 0 end private def signal_code From cc2bc1cf578d5662bf0a3859d8cfa4e4c18fa533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 10 Dec 2024 11:09:46 +0100 Subject: [PATCH 1513/1551] Adjust definition of `ExitReason::Aborted` (#15256) Clarify the documentation to distingish aborted process from an abnormal exit which includes other exit reasons as well. Ref https://github.com/crystal-lang/crystal/issues/15231#issuecomment-2512353630 --- src/process/status.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/process/status.cr b/src/process/status.cr index 3ce5c54b8e18..6bf99cda3e5e 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -14,7 +14,7 @@ enum Process::ExitReason # reserved for normal exits. Normal - # The process terminated abnormally. + # The process terminated due to an abort request. # # * On Unix-like systems, this corresponds to `Signal::ABRT`, `Signal::KILL`, # and `Signal::QUIT`. From d1201b319ef2697501fa36a1f6a63d1a4d4b48d1 Mon Sep 17 00:00:00 2001 From: Lachlan Dowding Date: Wed, 11 Dec 2024 07:17:58 +1000 Subject: [PATCH 1514/1551] Fix `Log` to emit with `exception` even if block outputs `nil` (#15253) Previous change #12000 skips logging when the given block outputs `nil`, however it inadvertently also suppresses logging exceptions. Logging is now skipped only if both the given exception and the block result are `nil`. --- spec/std/log/log_spec.cr | 17 ++++++++++++++++- src/log/log.cr | 17 ++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/spec/std/log/log_spec.cr b/spec/std/log/log_spec.cr index 6482509f1704..02838fe5e0a7 100644 --- a/spec/std/log/log_spec.cr +++ b/spec/std/log/log_spec.cr @@ -264,7 +264,7 @@ describe Log do entry.exception.should be_nil end - it "does not emit anything when a nil is emitted" do + it "does not emit when block returns nil" do backend = Log::MemoryBackend.new log = Log.new("a", backend, :notice) @@ -272,5 +272,20 @@ describe Log do backend.entries.should be_empty end + + it "does emit when block returns nil but exception is provided" do + backend = Log::MemoryBackend.new + log = Log.new("a", backend, :notice) + ex = Exception.new "the attached exception" + + log.notice(exception: ex) { nil } + + entry = backend.entries.first + entry.source.should eq("a") + entry.severity.should eq(s(:notice)) + entry.message.should eq("") + entry.data.should eq(Log::Metadata.empty) + entry.exception.should eq(ex) + end end end diff --git a/src/log/log.cr b/src/log/log.cr index 3480cfecf33b..0ae62d3deddd 100644 --- a/src/log/log.cr +++ b/src/log/log.cr @@ -65,13 +65,16 @@ class Log dsl = Emitter.new(@source, severity, exception) result = yield dsl - case result - when Entry - backend.dispatch result - when Nil - # emit nothing - else - backend.dispatch dsl.emit(result.to_s) + unless result.nil? && exception.nil? + entry = + case result + when Entry + result + else + dsl.emit(result.to_s) + end + + backend.dispatch entry end end {% end %} From 5d6757c5943505c6ff22262e217b1e71c1e71147 Mon Sep 17 00:00:00 2001 From: nanobowers Date: Tue, 10 Dec 2024 16:19:29 -0500 Subject: [PATCH 1515/1551] Allow constants to start with non-ascii uppercase and titlecase (#15148) --- spec/compiler/lexer/lexer_spec.cr | 2 ++ src/compiler/crystal/syntax/lexer.cr | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/compiler/lexer/lexer_spec.cr b/spec/compiler/lexer/lexer_spec.cr index 6813c1fe8df3..cae4959ed636 100644 --- a/spec/compiler/lexer/lexer_spec.cr +++ b/spec/compiler/lexer/lexer_spec.cr @@ -276,6 +276,8 @@ describe "Lexer" do it_lexes "&+@foo", :OP_AMP_PLUS it_lexes "&-@foo", :OP_AMP_MINUS it_lexes_const "Foo" + it_lexes_const "ÁrvíztűrőTükörfúrógép" + it_lexes_const "DžLjNjDzᾈᾉᾊ" it_lexes_instance_var "@foo" it_lexes_class_var "@@foo" it_lexes_globals ["$foo", "$FOO", "$_foo", "$foo123"] diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index dbca2448585d..660bcf2f6848 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -1048,7 +1048,7 @@ module Crystal scan_ident(start) else - if current_char.ascii_uppercase? + if current_char.uppercase? || current_char.titlecase? while ident_part?(next_char) # Nothing to do end From 746075e7122330c87313d12628e411da81115aa9 Mon Sep 17 00:00:00 2001 From: nanobowers Date: Tue, 10 Dec 2024 16:20:51 -0500 Subject: [PATCH 1516/1551] Change `sprintf "%c"` to support only `Char` and `Int::Primitive` (#15142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sijawusz Pur Rahnama Co-authored-by: Johannes Müller --- spec/std/sprintf_spec.cr | 24 ++++++++++++++++++++++++ src/string/formatter.cr | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/spec/std/sprintf_spec.cr b/spec/std/sprintf_spec.cr index a91ce8030915..674a32c2ab30 100644 --- a/spec/std/sprintf_spec.cr +++ b/spec/std/sprintf_spec.cr @@ -1176,6 +1176,30 @@ describe "::sprintf" do pending "floats" end + context "chars" do + it "works" do + assert_sprintf "%c", 'a', "a" + assert_sprintf "%3c", 'R', " R" + assert_sprintf "%-3c", 'L', "L " + assert_sprintf "%c", '▞', "▞" + assert_sprintf "%c", 65, "A" + assert_sprintf "%c", 66_i8, "B" + assert_sprintf "%c", 67_i16, "C" + assert_sprintf "%c", 68_i32, "D" + assert_sprintf "%c", 69_i64, "E" + assert_sprintf "%c", 97_u8, "a" + assert_sprintf "%c", 98_u16, "b" + assert_sprintf "%c", 99_u32, "c" + assert_sprintf "%c", 100_u64, "d" + assert_sprintf "%c", 0x259E, "▞" + end + + it "raises if not a Char or Int" do + expect_raises(ArgumentError, "Expected a char or integer") { sprintf("%c", "this") } + expect_raises(ArgumentError, "Expected a char or integer") { sprintf("%c", 17.34) } + end + end + context "strings" do it "works" do assert_sprintf "%s", 'a', "a" diff --git a/src/string/formatter.cr b/src/string/formatter.cr index 60da55a2601f..347d65bcb340 100644 --- a/src/string/formatter.cr +++ b/src/string/formatter.cr @@ -248,6 +248,12 @@ struct String::Formatter(A) end def char(flags, arg) : Nil + if arg.is_a?(Int::Primitive) + arg = arg.chr + end + unless arg.is_a?(Char) + raise ArgumentError.new("Expected a char or integer, not #{arg.inspect}") + end pad 1, flags if flags.left_padding? @io << arg pad 1, flags if flags.right_padding? From 512a8b77c1d5708c2329b651a023444439822cde Mon Sep 17 00:00:00 2001 From: David Keller Date: Tue, 10 Dec 2024 22:21:15 +0100 Subject: [PATCH 1517/1551] Mark `__crystal_personality` as nodoc [fixup #15070] (#15219) --- src/raise.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/raise.cr b/src/raise.cr index 0c9563495a94..1ba0243def28 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -182,6 +182,7 @@ end end {% else %} {% mingw = flag?(:win32) && flag?(:gnu) %} + # :nodoc: fun {{ mingw ? "__crystal_personality_imp".id : "__crystal_personality".id }}( version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*, ) : LibUnwind::ReasonCode @@ -206,12 +207,14 @@ end alias DISPATCHER_CONTEXT = Void end + # :nodoc: lib LibUnwind alias PersonalityFn = Int32, Action, UInt64, Exception*, Void* -> ReasonCode fun _GCC_specific_handler(ms_exc : LibC::EXCEPTION_RECORD64*, this_frame : Void*, ms_orig_context : LibC::CONTEXT*, ms_disp : LibC::DISPATCHER_CONTEXT*, gcc_per : PersonalityFn) : LibC::EXCEPTION_DISPOSITION end + # :nodoc: fun __crystal_personality(ms_exc : LibC::EXCEPTION_RECORD64*, this_frame : Void*, ms_orig_context : LibC::CONTEXT*, ms_disp : LibC::DISPATCHER_CONTEXT*) : LibC::EXCEPTION_DISPOSITION LibUnwind._GCC_specific_handler(ms_exc, this_frame, ms_orig_context, ms_disp, ->__crystal_personality_imp) end @@ -293,6 +296,7 @@ fun __crystal_raise_overflow : NoReturn end {% if flag?(:interpreted) %} + # :nodoc: def __crystal_raise_cast_failed(obj, type_name : String, location : String) raise TypeCastError.new("Cast from #{obj.class} to #{type_name} failed, at #{location}") end From ed7dce32f406dde08005f96d9eaf0f54d92ef541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 11 Dec 2024 10:07:06 +0100 Subject: [PATCH 1518/1551] Redefine `Process::Status#normal_exit?` on Windows (#15255) Aligns the definition of `Process::Status#normal_exit?` with `Process::ExitReason::Normal` as discusssed in #15231. --- spec/std/process/status_spec.cr | 12 ++++-------- src/process/status.cr | 18 +++++++++--------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 5f0c3b597376..ce066e0d7968 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -27,12 +27,8 @@ describe Process::Status do Process::Status.new(exit_status(128)).exit_code.should eq 128 Process::Status.new(exit_status(255)).exit_code.should eq 255 - if {{ flag?(:unix) }} - expect_raises(RuntimeError, "Abnormal exit has no exit code") do - status_for(:interrupted).exit_code - end - else - status_for(:interrupted).exit_code.should eq({% if flag?(:unix) %}0{% else %}LibC::STATUS_CONTROL_C_EXIT.to_i32!{% end %}) + expect_raises(RuntimeError, "Abnormal exit has no exit code") do + status_for(:interrupted).exit_code end end @@ -43,7 +39,7 @@ describe Process::Status do Process::Status.new(exit_status(128)).exit_code?.should eq 128 Process::Status.new(exit_status(255)).exit_code?.should eq 255 - status_for(:interrupted).exit_code?.should eq({% if flag?(:unix) %}nil{% else %}LibC::STATUS_CONTROL_C_EXIT.to_i32!{% end %}) + status_for(:interrupted).exit_code?.should be_nil end it "#success?" do @@ -63,7 +59,7 @@ describe Process::Status do Process::Status.new(exit_status(128)).normal_exit?.should be_true Process::Status.new(exit_status(255)).normal_exit?.should be_true - status_for(:interrupted).normal_exit?.should eq {{ flag?(:win32) }} + status_for(:interrupted).normal_exit?.should be_false end it "#signal_exit?" do diff --git a/src/process/status.cr b/src/process/status.cr index 6bf99cda3e5e..694b35d0fd52 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -135,7 +135,8 @@ class Process::Status @exit_status & 0xC0000000_u32 == 0 ? ExitReason::Normal : ExitReason::Unknown end {% elsif flag?(:unix) && !flag?(:wasm32) %} - if normal_exit? + # define __WIFEXITED(status) (__WTERMSIG(status) == 0) + if signal_code == 0 ExitReason::Normal elsif signal_exit? case Signal.from_value?(signal_code) @@ -181,13 +182,12 @@ class Process::Status end # Returns `true` if the process terminated normally. + # + # Equivalent to `ExitReason::Normal` + # + # * `#exit_reason` provides more insights into other exit reasons. def normal_exit? : Bool - {% if flag?(:unix) %} - # define __WIFEXITED(status) (__WTERMSIG(status) == 0) - signal_code == 0 - {% else %} - true - {% end %} + exit_reason.normal? end # If `signal_exit?` is `true`, returns the *Signal* the process @@ -228,9 +228,9 @@ class Process::Status # Process.new("sleep", ["10"]).tap(&.terminate).wait.exit_code? # => nil # ``` def exit_code? : Int32? - {% if flag?(:unix) %} - return unless normal_exit? + return unless normal_exit? + {% if flag?(:unix) %} # define __WEXITSTATUS(status) (((status) & 0xff00) >> 8) (@exit_status & 0xff00) >> 8 {% else %} From ee699813cfd563cb88450b03515f6c2f09751c1d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:16:16 +0100 Subject: [PATCH 1519/1551] Update msys2/setup-msys2 action to v2.26.0 (#15265) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/mingw-w64.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml index a9bbec81e1ce..f06efdd80161 100644 --- a/.github/workflows/mingw-w64.yml +++ b/.github/workflows/mingw-w64.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Setup MSYS2 id: msys2 - uses: msys2/setup-msys2@c52d1fa9c7492275e60fe763540fb601f5f232a1 # v2.25.0 + uses: msys2/setup-msys2@d44ca8e88d8b43d56cf5670f91747359d5537f97 # v2.26.0 with: msystem: UCRT64 update: true @@ -99,7 +99,7 @@ jobs: steps: - name: Setup MSYS2 id: msys2 - uses: msys2/setup-msys2@c52d1fa9c7492275e60fe763540fb601f5f232a1 # v2.25.0 + uses: msys2/setup-msys2@d44ca8e88d8b43d56cf5670f91747359d5537f97 # v2.26.0 with: msystem: UCRT64 update: true From 5915e3b32f973cdcabbd1b679f60b59621266628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 13 Dec 2024 10:58:47 +0100 Subject: [PATCH 1520/1551] Add `Process::Status#abnormal_exit?` (#15266) Convenience methods for the inverse of `ExitReason#normal`? and `Status#normal_exit?` --- spec/std/process/status_spec.cr | 10 ++++++++++ src/process/status.cr | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index ce066e0d7968..ea360a9a9dae 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -62,6 +62,16 @@ describe Process::Status do status_for(:interrupted).normal_exit?.should be_false end + it "#abnormal_exit?" do + Process::Status.new(exit_status(0)).abnormal_exit?.should be_false + Process::Status.new(exit_status(1)).abnormal_exit?.should be_false + Process::Status.new(exit_status(127)).abnormal_exit?.should be_false + Process::Status.new(exit_status(128)).abnormal_exit?.should be_false + Process::Status.new(exit_status(255)).abnormal_exit?.should be_false + + status_for(:interrupted).abnormal_exit?.should be_true + end + it "#signal_exit?" do Process::Status.new(exit_status(0)).signal_exit?.should be_false Process::Status.new(exit_status(1)).signal_exit?.should be_false diff --git a/src/process/status.cr b/src/process/status.cr index 694b35d0fd52..15913ce2fd5e 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -91,6 +91,13 @@ enum Process::ExitReason # * On Unix-like systems, this corresponds to `Signal::TERM`. # * On Windows, this corresponds to the `CTRL_LOGOFF_EVENT` and `CTRL_SHUTDOWN_EVENT` messages. SessionEnded + + # Returns `true` if the process exited abnormally. + # + # This includes all values except `Normal`. + def abnormal? + !normal? + end end # The status of a terminated process. Returned by `Process#wait`. @@ -186,10 +193,21 @@ class Process::Status # Equivalent to `ExitReason::Normal` # # * `#exit_reason` provides more insights into other exit reasons. + # * `#abnormal_exit?` returns the inverse. def normal_exit? : Bool exit_reason.normal? end + # Returns `true` if the process terminated abnormally. + # + # Equivalent to `ExitReason#abnormal?` + # + # * `#exit_reason` provides more insights into the specific exit reason. + # * `#normal_exit?` returns the inverse. + def abnormal_exit? : Bool + exit_reason.abnormal? + end + # If `signal_exit?` is `true`, returns the *Signal* the process # received and didn't handle. Will raise if `signal_exit?` is `false`. # From 8f12767bc07be8b9742f3561ae51b30cbb362132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 14 Dec 2024 14:04:37 +0100 Subject: [PATCH 1521/1551] Fix stringification of abnormal `Process::Status` on Windows [fixup #15255] (#15267) The change in #15255 broke `Process::Status#to_s` and `#inspect` on Windows due to the redefinition of `#normal_exit?`. Unfortunately we were missing specs for this, so it did go unnoticed. This patch adds specs and restablishes the previous behaviour with the small improvement of treating the `exit_status` value as `UInt32` instead of `Int32`, which results in positive numbers. ```cr # Crystal 1.14.0 Process::Status.new(LibC::STATUS_CONTROL_C_EXIT)).inspect # => "Process::Status[-1073741510]" # master Process::Status.new(LibC::STATUS_CONTROL_C_EXIT)).inspect # NotImplementedError: Process::Status#exit_signal # this patch Process::Status.new(LibC::STATUS_CONTROL_C_EXIT)).inspect # => "Process::Status[3221225786]" ``` There's still room for improvement, for example map the values to names and/or use base 16 numerals as is custom for error statuses on Windows), but I'll leave that for a follow-up. --- spec/std/process/status_spec.cr | 16 +++++++++++++ src/process/status.cr | 42 +++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index ea360a9a9dae..86529b2cefd4 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -236,6 +236,14 @@ describe Process::Status do assert_prints Process::Status.new(exit_status(255)).to_s, "255" end + it "on abnormal exit" do + {% if flag?(:win32) %} + assert_prints status_for(:interrupted).to_s, "3221225786" + {% else %} + assert_prints status_for(:interrupted).to_s, "INT" + {% end %} + end + {% if flag?(:unix) && !flag?(:wasi) %} it "with exit signal" do assert_prints Process::Status.new(Signal::HUP.value).to_s, "HUP" @@ -254,6 +262,14 @@ describe Process::Status do assert_prints Process::Status.new(exit_status(255)).inspect, "Process::Status[255]" end + it "on abnormal exit" do + {% if flag?(:win32) %} + assert_prints status_for(:interrupted).inspect, "Process::Status[3221225786]" + {% else %} + assert_prints status_for(:interrupted).inspect, "Process::Status[Signal::INT]" + {% end %} + end + {% if flag?(:unix) && !flag?(:wasi) %} it "with exit signal" do assert_prints Process::Status.new(Signal::HUP.value).inspect, "Process::Status[Signal::HUP]" diff --git a/src/process/status.cr b/src/process/status.cr index 15913ce2fd5e..5fd70e5ad203 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -275,11 +275,15 @@ class Process::Status # `Process::Status[Signal::HUP]`. def inspect(io : IO) : Nil io << "Process::Status[" - if normal_exit? - exit_code.inspect(io) - else - exit_signal.inspect(io) - end + {% if flag?(:win32) %} + @exit_status.to_s(io) + {% else %} + if normal_exit? + exit_code.inspect(io) + else + exit_signal.inspect(io) + end + {% end %} io << "]" end @@ -288,11 +292,15 @@ class Process::Status # A normal exit status prints the numerical value (`0`, `1` etc). # A signal exit status prints the name of the `Signal` member (`HUP`, `INT`, etc.). def to_s(io : IO) : Nil - if normal_exit? - io << exit_code - else - io << exit_signal - end + {% if flag?(:win32) %} + @exit_status.to_s(io) + {% else %} + if normal_exit? + io << exit_code + else + io << exit_signal + end + {% end %} end # Returns a textual representation of the process status. @@ -300,10 +308,14 @@ class Process::Status # A normal exit status prints the numerical value (`0`, `1` etc). # A signal exit status prints the name of the `Signal` member (`HUP`, `INT`, etc.). def to_s : String - if normal_exit? - exit_code.to_s - else - exit_signal.to_s - end + {% if flag?(:win32) %} + @exit_status.to_s + {% else %} + if normal_exit? + exit_code.to_s + else + exit_signal.to_s + end + {% end %} end end From c878d22cc797c2a27009e479201d5c1bb9f013d0 Mon Sep 17 00:00:00 2001 From: Lachlan Dowding Date: Sat, 14 Dec 2024 23:05:00 +1000 Subject: [PATCH 1522/1551] Add `Log` overloads for logging exceptions without giving a block (#15257) Per the discussion on #15221, this change provides non-yielding overloads to the `Log.{{severity}}` methods for logging exceptions without giving an associated message producing block. --- spec/std/log/log_spec.cr | 20 ++++++++++++++++++++ src/log/log.cr | 9 +++++++++ src/log/main.cr | 5 +++++ 3 files changed, 34 insertions(+) diff --git a/spec/std/log/log_spec.cr b/spec/std/log/log_spec.cr index 02838fe5e0a7..a9cf90dfe08b 100644 --- a/spec/std/log/log_spec.cr +++ b/spec/std/log/log_spec.cr @@ -106,6 +106,26 @@ describe Log do backend.entries.all? { |e| e.exception == ex }.should be_true end + it "can log exceptions without specifying a block" do + backend = Log::MemoryBackend.new + log = Log.new("a", backend, :warn) + ex = Exception.new + + log.trace(exception: ex) + log.debug(exception: ex) + log.info(exception: ex) + log.notice(exception: ex) + log.warn(exception: ex) + log.error(exception: ex) + log.fatal(exception: ex) + + backend.entries.map { |e| {e.source, e.severity, e.message, e.data, e.exception} }.should eq([ + {"a", s(:warn), "", Log::Metadata.empty, ex}, + {"a", s(:error), "", Log::Metadata.empty, ex}, + {"a", s(:fatal), "", Log::Metadata.empty, ex}, + ]) + end + it "contains the current context" do Log.context.set a: 1 diff --git a/src/log/log.cr b/src/log/log.cr index 0ae62d3deddd..f7c8cf4f1cf9 100644 --- a/src/log/log.cr +++ b/src/log/log.cr @@ -41,6 +41,15 @@ class Log {% for method in %w(trace debug info notice warn error fatal) %} {% severity = method.id.camelcase %} + # Logs the given *exception* if the logger's current severity is lower than + # or equal to `Severity::{{severity}}`. + def {{method.id}}(*, exception : Exception) : Nil + severity = Severity::{{severity}} + if level <= severity && (backend = @backend) + backend.dispatch Emitter.new(@source, severity, exception).emit("") + end + end + # Logs a message if the logger's current severity is lower than or equal to # `Severity::{{ severity }}`. # diff --git a/src/log/main.cr b/src/log/main.cr index 3ff86e169ba4..91d0b03d0817 100644 --- a/src/log/main.cr +++ b/src/log/main.cr @@ -36,6 +36,11 @@ class Log private Top = Log.for("") {% for method in %i(trace debug info notice warn error fatal) %} + # See `Log#{{method.id}}`. + def self.{{method.id}}(*, exception : Exception) : Nil + Top.{{method.id}}(exception: exception) + end + # See `Log#{{method.id}}`. def self.{{method.id}}(*, exception : Exception? = nil) Top.{{method.id}}(exception: exception) do |dsl| From 9b377c3735ea7905131439e8c6edc5be8451118e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 15 Dec 2024 20:22:30 +0100 Subject: [PATCH 1523/1551] Use empty prelude for compiler tools specs (#15272) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply some tiny refactors to specs that rely on features from the stdlib prelude. These seem to be not particularly relevant for the test scenario because we're testing compiler features, not stdlib implementations. These changes allow using the compiler with the empty prelude which results in significantly less compilation effort. The spec runtime for these tests reduces from 3:06 minutes to 13.81 seconds (over 90% reduction! 🎉🚀) --- spec/compiler/crystal/tools/context_spec.cr | 36 +++++++++---------- spec/compiler/crystal/tools/expand_spec.cr | 1 + .../crystal/tools/implementations_spec.cr | 8 ++--- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/spec/compiler/crystal/tools/context_spec.cr b/spec/compiler/crystal/tools/context_spec.cr index 84c711e8af53..87c2ad5902db 100644 --- a/spec/compiler/crystal/tools/context_spec.cr +++ b/spec/compiler/crystal/tools/context_spec.cr @@ -3,6 +3,7 @@ require "../../../spec_helper" private def processed_context_visitor(code, cursor_location) compiler = Compiler.new compiler.no_codegen = true + compiler.prelude = "empty" result = compiler.compile(Compiler::Source.new(".", code), "fake-no-build") visitor = ContextVisitor.new(cursor_location) @@ -118,15 +119,20 @@ describe "context" do it "includes last call" do assert_context_includes %( class Foo - property lorem + def lorem + @lorem + end def initialize(@lorem : Int64) end end + def foo(f) + end + f = Foo.new(1i64) - puts f.lo‸rem + foo f.lo‸rem 1 ), "f.lorem", ["Int64"] end @@ -141,9 +147,13 @@ describe "context" do it "does includes regex special variables" do assert_context_keys %( + def match + $~ = "match" + end + def foo - s = "string" - s =~ /s/ + s = "foo" + match ‸ 0 end @@ -185,11 +195,7 @@ describe "context" do it "can handle union types" do assert_context_includes %( - a = if rand() > 0 - 1i64 - else - "foo" - end + a = 1_i64.as(Int64 | String) ‸ 0 ), "a", ["(Int64 | String)"] @@ -197,11 +203,7 @@ describe "context" do it "can display text output" do run_context_tool(%( - a = if rand() > 0 - 1i64 - else - "foo" - end + a = 1_i64.as(Int64 | String) ‸ 0 )) do |result| @@ -218,11 +220,7 @@ describe "context" do it "can display json output" do run_context_tool(%( - a = if rand() > 0 - 1i64 - else - "foo" - end + a = 1_i64.as(Int64 | String) ‸ 0 )) do |result| diff --git a/spec/compiler/crystal/tools/expand_spec.cr b/spec/compiler/crystal/tools/expand_spec.cr index 40a122587afd..e8f9b770f3ec 100644 --- a/spec/compiler/crystal/tools/expand_spec.cr +++ b/spec/compiler/crystal/tools/expand_spec.cr @@ -5,6 +5,7 @@ private def processed_expand_visitor(code, cursor_location) compiler.no_codegen = true compiler.no_cleanup = true compiler.wants_doc = true + compiler.prelude = "empty" result = compiler.compile(Compiler::Source.new(".", code), "fake-no-build") visitor = ExpandVisitor.new(cursor_location) diff --git a/spec/compiler/crystal/tools/implementations_spec.cr b/spec/compiler/crystal/tools/implementations_spec.cr index 7d35659de2bb..3f275c671101 100644 --- a/spec/compiler/crystal/tools/implementations_spec.cr +++ b/spec/compiler/crystal/tools/implementations_spec.cr @@ -3,6 +3,7 @@ require "../../../spec_helper" private def processed_implementation_visitor(code, cursor_location) compiler = Compiler.new compiler.no_codegen = true + compiler.prelude = "empty" result = compiler.compile(Compiler::Source.new(".", code), "fake-no-build") visitor = ImplementationsVisitor.new(cursor_location) @@ -52,7 +53,7 @@ describe "implementations" do 1 end - puts f‸oo + f‸oo ) end @@ -117,7 +118,6 @@ describe "implementations" do end while f‸oo - puts 2 end ) end @@ -129,7 +129,6 @@ describe "implementations" do end if f‸oo - puts 2 end ) end @@ -140,7 +139,7 @@ describe "implementations" do 1 end - puts 2 if f‸oo + 2 if f‸oo ) end @@ -151,7 +150,6 @@ describe "implementations" do end begin - puts 2 rescue f‸oo end From 9183bb00c474355018f5f4457334fe52046b8124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 15 Dec 2024 20:22:55 +0100 Subject: [PATCH 1524/1551] Fix `tool implementations` to handle gracefully a def with missing location (#15273) Without this change, `crystal tool implementations` would crash when a target def has no location. The example uses an enum type's `.new` method which is defined by the compiler without a location. This patch fixes the nil error. The result may still not be terribly useful, but it's better than crashing the program. --- .../compiler/crystal/tools/implementations_spec.cr | 14 ++++++++++++++ src/compiler/crystal/tools/implementations.cr | 6 ++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/spec/compiler/crystal/tools/implementations_spec.cr b/spec/compiler/crystal/tools/implementations_spec.cr index 3f275c671101..fb6399cf3663 100644 --- a/spec/compiler/crystal/tools/implementations_spec.cr +++ b/spec/compiler/crystal/tools/implementations_spec.cr @@ -476,4 +476,18 @@ describe "implementations" do F‸oo ) end + + it "find implementation on def with no location" do + _, result = processed_implementation_visitor <<-CRYSTAL, Location.new(".", 5, 5) + enum Foo + FOO + end + + Foo.new(42) + CRYSTAL + + result.implementations.not_nil!.map do |e| + Location.new(e.filename, e.line, e.column).to_s + end.should eq [":0:0"] + end end diff --git a/src/compiler/crystal/tools/implementations.cr b/src/compiler/crystal/tools/implementations.cr index e2dbee001346..e4a6d210d922 100644 --- a/src/compiler/crystal/tools/implementations.cr +++ b/src/compiler/crystal/tools/implementations.cr @@ -53,7 +53,9 @@ module Crystal @line = macro_location.line_number + loc.line_number @column = loc.column_number else - raise "not implemented" + @line = loc.line_number + @column = loc.column_number + @filename = "" end end @@ -111,7 +113,7 @@ module Crystal if target_defs = node.target_defs target_defs.each do |target_def| - @locations << target_def.location.not_nil! + @locations << (target_def.location || Location.new(nil, 0, 0)) end end false From 46459d62dcd4d66c8e4bedc45840df38d2a5095b Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Sun, 15 Dec 2024 20:23:12 +0100 Subject: [PATCH 1525/1551] Fix: register GC callbacks inside GC.init (#15278) Running the std specs would immediately hang with the `master` branch of the GC, waiting on `GC.lock_write`: the GC is trying to collect while Crystal is initializing constants and class variables (`__crystal_main`) and it would lock the rwlock for write twice, never unlocking it, before the start callback was registered, but not the before collect callback (oops). This patch: 1. Registers the GC callbacks right in the GC.init method; this makes sure all callbacks are registered before we start running any Crystal code. 2. Initializes the rwlock inside the GC.init method; this avoids a lazy initializer and makes sure the memory is properly initialized ASAP. --- src/gc/boehm.cr | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 6037abe830e2..327b3d50409f 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -175,7 +175,7 @@ end module GC {% if flag?(:preview_mt) %} - @@lock = Crystal::RWLock.new + @@lock = uninitialized Crystal::RWLock {% end %} # :nodoc: @@ -205,10 +205,33 @@ module GC {% end %} LibGC.init + {% if flag?(:preview_mt) %} + @@lock = Crystal::RWLock.new + {% end %} + LibGC.set_start_callback -> do GC.lock_write end + # pushes the stack of pending fibers when the GC wants to collect memory: + {% unless flag?(:interpreted) %} + GC.before_collect do + Fiber.unsafe_each do |fiber| + fiber.push_gc_roots unless fiber.running? + end + + {% if flag?(:preview_mt) %} + Thread.unsafe_each do |thread| + if fiber = thread.current_fiber? + GC.set_stackbottom(thread.gc_thread_handler, fiber.@stack_bottom) + end + end + {% end %} + + GC.unlock_write + end + {% end %} + {% if flag?(:tracing) %} if ::Crystal::Tracing.enabled?(:gc) set_on_heap_resize_proc @@ -462,25 +485,6 @@ module GC end end - # pushes the stack of pending fibers when the GC wants to collect memory: - {% unless flag?(:interpreted) %} - GC.before_collect do - Fiber.unsafe_each do |fiber| - fiber.push_gc_roots unless fiber.running? - end - - {% if flag?(:preview_mt) %} - Thread.unsafe_each do |thread| - if fiber = thread.current_fiber? - GC.set_stackbottom(thread.gc_thread_handler, fiber.@stack_bottom) - end - end - {% end %} - - GC.unlock_write - end - {% end %} - # :nodoc: def self.stop_world : Nil LibGC.stop_world_external From 6437ab9a576df47e1289f49b1e882034f02767a8 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 16 Dec 2024 11:47:08 +0100 Subject: [PATCH 1526/1551] Drop `Crystal::FiberChannel` (#15245) Its main purpose was to keep the eventloop from exiting when empty, which isn't needed anymore (evloop#run can now wait forever). Since we need to grab the lock to send the fiber, we can just push it to the runnables deque instead of passing it through a pipe; which didn't work properly on Windows anyway. The Windows IOCP eventloop will need a refactor to check on completions even where there are no queued (timers). --- .../polling/poll_descriptor_spec.cr | 2 +- src/crystal/fiber_channel.cr | 23 --------------- src/crystal/scheduler.cr | 28 +++++++------------ 3 files changed, 11 insertions(+), 42 deletions(-) delete mode 100644 src/crystal/fiber_channel.cr diff --git a/spec/std/crystal/event_loop/polling/poll_descriptor_spec.cr b/spec/std/crystal/event_loop/polling/poll_descriptor_spec.cr index 04c090e7b83f..6227ad57028e 100644 --- a/spec/std/crystal/event_loop/polling/poll_descriptor_spec.cr +++ b/spec/std/crystal/event_loop/polling/poll_descriptor_spec.cr @@ -8,7 +8,7 @@ class Crystal::EventLoop::FakeLoop < Crystal::EventLoop::Polling private def system_run(blocking : Bool, & : Fiber ->) : Nil end - private def interrupt : Nil + def interrupt : Nil end protected def system_add(fd : Int32, index : Arena::Index) : Nil diff --git a/src/crystal/fiber_channel.cr b/src/crystal/fiber_channel.cr deleted file mode 100644 index dbe0cc6187b9..000000000000 --- a/src/crystal/fiber_channel.cr +++ /dev/null @@ -1,23 +0,0 @@ -# :nodoc: -# -# This struct wraps around a IO pipe to send and receive fibers between -# worker threads. The receiving thread will hang on listening for new fibers -# or fibers that become runnable by the execution of other threads, at the same -# time it waits for other IO events or timers within the event loop -struct Crystal::FiberChannel - @worker_in : IO::FileDescriptor - @worker_out : IO::FileDescriptor - - def initialize - @worker_out, @worker_in = IO.pipe - end - - def send(fiber : Fiber) - @worker_in.write_bytes(fiber.object_id) - end - - def receive - oid = @worker_out.read_bytes(UInt64) - Pointer(Fiber).new(oid).as(Fiber) - end -end diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index 9b64823f3905..ad0f2a55672e 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -1,6 +1,5 @@ require "crystal/event_loop" require "crystal/system/print_error" -require "./fiber_channel" require "fiber" require "fiber/stack_pool" require "crystal/system/thread" @@ -97,10 +96,6 @@ class Crystal::Scheduler {% end %} end - {% if flag?(:preview_mt) %} - private getter(fiber_channel : Crystal::FiberChannel) { Crystal::FiberChannel.new } - {% end %} - @main : Fiber @lock = Crystal::SpinLock.new @sleeping = false @@ -180,6 +175,7 @@ class Crystal::Scheduler end {% if flag?(:preview_mt) %} + private getter! worker_fiber : Fiber @rr_target = 0 protected def find_target_thread @@ -192,38 +188,34 @@ class Crystal::Scheduler end def run_loop + @worker_fiber = Fiber.current + spawn_stack_pool_collector - fiber_channel = self.fiber_channel loop do @lock.lock if runnable = @runnables.shift? - @runnables << Fiber.current + @runnables << worker_fiber @lock.unlock resume(runnable) else @sleeping = true @lock.unlock - Crystal.trace :sched, "mt:sleeping" - fiber = Crystal.trace(:sched, "mt:slept") { fiber_channel.receive } - - @lock.lock - @sleeping = false - @runnables << Fiber.current - @lock.unlock - resume(fiber) + Crystal.trace(:sched, "mt:slept") { ::Fiber.suspend } end end end def send_fiber(fiber : Fiber) @lock.lock + @runnables << fiber + if @sleeping - fiber_channel.send(fiber) - else - @runnables << fiber + @sleeping = false + @runnables << worker_fiber + @event_loop.interrupt end @lock.unlock end From 2f3e07d282382050823ad10c674c8e9f212d2254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 16 Dec 2024 11:47:37 +0100 Subject: [PATCH 1527/1551] Make `Enum` an abstract struct (#15274) --- src/enum.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/enum.cr b/src/enum.cr index 8b6ca9eebbae..0b2214ff34ad 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -100,7 +100,7 @@ # # Color::Red.value # : UInt8 # ``` -struct Enum +abstract struct Enum include Comparable(self) # Returns *value*. From 1df4b23cff81a8818ba4dcea4b0f2629679de77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 17 Dec 2024 11:40:18 +0100 Subject: [PATCH 1528/1551] Fix `Process::Status` for unknown signals (#15280) Using `Signal.from_value` limits the result to named members of the enum `Signal`. That means it only accepts signals that are explicitly defined in the Crystal bindings. While we generally strive for completeness, there's no guarantee that the operating system might indicate a signal that is not in the bindings. And in fact there are no mappings for real-time signals (`SIGRTMIN`..`SIGRTMAX`), for example (not sure if they're particularly relevant as exit signals, but it shows incompleteness). This mechanism should be fault tolerant and be able to represent _any_ signal value, regardless of whether it's mapped in the Crystal bindings or not. If it's missing we cannot associate a name, but we know it's a signal and can express that in the type `Signal` (enums can take any value, not just those of named members). --- spec/std/process/status_spec.cr | 8 ++++++++ src/enum.cr | 3 ++- src/process/status.cr | 12 +++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 86529b2cefd4..63136a2ddac7 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -105,6 +105,9 @@ describe Process::Status do Process::Status.new(Signal::INT.value).exit_signal.should eq Signal::INT last_signal = Signal.values[-1] Process::Status.new(last_signal.value).exit_signal.should eq last_signal + + unknown_signal = Signal.new(126) + Process::Status.new(unknown_signal.value).exit_signal.should eq unknown_signal end it "#normal_exit? with signal code" do @@ -249,6 +252,8 @@ describe Process::Status do assert_prints Process::Status.new(Signal::HUP.value).to_s, "HUP" last_signal = Signal.values[-1] assert_prints Process::Status.new(last_signal.value).to_s, last_signal.to_s + + assert_prints Process::Status.new(Signal.new(126).value).to_s, "Signal[126]" end {% end %} end @@ -275,6 +280,9 @@ describe Process::Status do assert_prints Process::Status.new(Signal::HUP.value).inspect, "Process::Status[Signal::HUP]" last_signal = Signal.values[-1] assert_prints Process::Status.new(last_signal.value).inspect, "Process::Status[#{last_signal.inspect}]" + + unknown_signal = Signal.new(126) + assert_prints Process::Status.new(unknown_signal.value).inspect, "Process::Status[Signal[126]]" end {% end %} end diff --git a/src/enum.cr b/src/enum.cr index 0b2214ff34ad..058c17f6ee1c 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -225,7 +225,8 @@ abstract struct Enum end end - private def member_name + # :nodoc: + def member_name : String? # Can't use `case` here because case with duplicate values do # not compile, but enums can have duplicates (such as `enum Foo; FOO = 1; BAR = 1; end`). {% for member in @type.constants %} diff --git a/src/process/status.cr b/src/process/status.cr index 5fd70e5ad203..28e6049238dc 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -217,7 +217,7 @@ class Process::Status # which also works on Windows. def exit_signal : Signal {% if flag?(:unix) && !flag?(:wasm32) %} - Signal.from_value(signal_code) + Signal.new(signal_code) {% else %} raise NotImplementedError.new("Process::Status#exit_signal") {% end %} @@ -298,7 +298,12 @@ class Process::Status if normal_exit? io << exit_code else - io << exit_signal + signal = exit_signal + if name = signal.member_name + io << name + else + signal.inspect(io) + end end {% end %} end @@ -314,7 +319,8 @@ class Process::Status if normal_exit? exit_code.to_s else - exit_signal.to_s + signal = exit_signal + signal.member_name || signal.inspect end {% end %} end From a576c9d48d230f6d6c7a816f4f022ed587ff5f8e Mon Sep 17 00:00:00 2001 From: Stephanie Wilde-Hobbs Date: Tue, 17 Dec 2024 19:52:23 +0100 Subject: [PATCH 1529/1551] Fix first doc comment inside macro yield (#15050) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following example eats the doc comment: ```cr macro foo {{yield}} end foo do # doc comment def test end end ``` This is because the first line of comment is generated on the same line as the `begin` which is inserted when using `{{yield}}`, like so: ```cr begin # doc comment def test end end ``` Using a newline instead of whitespace after `begin` in macro yield fixes this. --------- Co-authored-by: Johannes Müller Co-authored-by: Julien Portalier --- spec/compiler/semantic/macro_spec.cr | 15 +++++++++++++++ src/compiler/crystal/macros/interpreter.cr | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/macro_spec.cr b/spec/compiler/semantic/macro_spec.cr index c66ee3d902f5..028ef9d39187 100644 --- a/spec/compiler/semantic/macro_spec.cr +++ b/spec/compiler/semantic/macro_spec.cr @@ -613,6 +613,21 @@ describe "Semantic: macro" do CRYSTAL end + it "begins with {{ yield }} (#15050)" do + result = top_level_semantic <<-CRYSTAL, wants_doc: true + macro foo + {{yield}} + end + + foo do + # doc comment + def test + end + end + CRYSTAL + result.program.defs.try(&.["test"][0].def.doc).should eq "doc comment" + end + it "can return class type in macro def" do assert_type(<<-CRYSTAL) { types["Int32"].metaclass } class Foo diff --git a/src/compiler/crystal/macros/interpreter.cr b/src/compiler/crystal/macros/interpreter.cr index 8db46bd118cf..978c57470a14 100644 --- a/src/compiler/crystal/macros/interpreter.cr +++ b/src/compiler/crystal/macros/interpreter.cr @@ -104,7 +104,7 @@ module Crystal if (loc = @last.location) && loc.filename.is_a?(String) || is_yield macro_expansion_pragmas = @macro_expansion_pragmas ||= {} of Int32 => Array(Lexer::LocPragma) (macro_expansion_pragmas[@str.pos.to_i32] ||= [] of Lexer::LocPragma) << Lexer::LocPushPragma.new - @str << "begin " if is_yield + @str << "begin\n" if is_yield @last.to_s(@str, macro_expansion_pragmas: macro_expansion_pragmas, emit_doc: true) @str << " end" if is_yield (macro_expansion_pragmas[@str.pos.to_i32] ||= [] of Lexer::LocPragma) << Lexer::LocPopPragma.new From 25086b9f22ff7deafff5b073f3a561c212f468a6 Mon Sep 17 00:00:00 2001 From: Margret Riegert Date: Tue, 17 Dec 2024 13:58:43 -0500 Subject: [PATCH 1530/1551] Add `ECR::Lexer::SyntaxException` with location info (#15222) --- src/ecr/lexer.cr | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ecr/lexer.cr b/src/ecr/lexer.cr index e32de726040f..81fedac17087 100644 --- a/src/ecr/lexer.cr +++ b/src/ecr/lexer.cr @@ -25,6 +25,15 @@ class ECR::Lexer end end + class SyntaxException < Exception + getter line_number : Int32 + getter column_number : Int32 + + def initialize(message, @line_number, @column_number) + super(message) + end + end + def initialize(string) @reader = Char::Reader.new(string) @token = Token.new @@ -198,4 +207,8 @@ class ECR::Lexer private def string_range(start_pos, end_pos) @reader.string.byte_slice(start_pos, end_pos - start_pos) end + + private def raise(message : String) + raise SyntaxException.new(message, @line_number, @column_number) + end end From 4f008895c23307b7a8553123d59acaad9112806e Mon Sep 17 00:00:00 2001 From: Abdullah Alhusaini <44743015+a-alhusaini@users.noreply.github.com> Date: Tue, 17 Dec 2024 21:59:27 +0300 Subject: [PATCH 1531/1551] Add `HTTP::Cookie#expire` (#14819) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/http/cookie_spec.cr | 9 +++++++++ src/http/cookie.cr | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/spec/std/http/cookie_spec.cr b/spec/std/http/cookie_spec.cr index 55183c48cbe5..7bc13080f60e 100644 --- a/spec/std/http/cookie_spec.cr +++ b/spec/std/http/cookie_spec.cr @@ -80,6 +80,15 @@ module HTTP end end + it "#expire" do + cookie = HTTP::Cookie.new("hello", "world") + cookie.expire + + cookie.value.empty?.should be_true + cookie.expired?.should be_true + cookie.max_age.should eq(Time::Span.zero) + end + describe "#name=" do it "raises on invalid name" do cookie = HTTP::Cookie.new("x", "") diff --git a/src/http/cookie.cr b/src/http/cookie.cr index 56d8800848d7..8a9a29855318 100644 --- a/src/http/cookie.cr +++ b/src/http/cookie.cr @@ -243,6 +243,24 @@ module HTTP end end + # Expires the cookie. + # + # Causes the cookie to be destroyed. Sets the value to the empty string and + # expires its lifetime. + # + # ``` + # cookie = HTTP::Cookie.new("hello", "world") + # cookie.expire + # + # cookie.value # => "" + # cookie.expired? # => true + # ``` + def expire + self.value = "" + self.expires = Time::UNIX_EPOCH + self.max_age = Time::Span.zero + end + # :nodoc: module Parser module Regex From 762986321b9a81a187efd79be0da6b3eba2e7a8b Mon Sep 17 00:00:00 2001 From: Devonte Date: Wed, 18 Dec 2024 10:20:07 +0000 Subject: [PATCH 1532/1551] Add `Colorize::Object#ansi_escape` (#15113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- spec/std/colorize_spec.cr | 20 ++++++++++++++++++++ src/colorize.cr | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/spec/std/colorize_spec.cr b/spec/std/colorize_spec.cr index c318cfaa8dbc..3a4170667ec9 100644 --- a/spec/std/colorize_spec.cr +++ b/spec/std/colorize_spec.cr @@ -97,6 +97,26 @@ describe "colorize" do colorize("hello").overline.to_s.should eq("\e[53mhello\e[0m") end + it "prints colorize ANSI escape codes" do + Colorize.with.bold.ansi_escape.should eq("\e[1m") + Colorize.with.bright.ansi_escape.should eq("\e[1m") + Colorize.with.dim.ansi_escape.should eq("\e[2m") + Colorize.with.italic.ansi_escape.should eq("\e[3m") + Colorize.with.underline.ansi_escape.should eq("\e[4m") + Colorize.with.blink.ansi_escape.should eq("\e[5m") + Colorize.with.blink_fast.ansi_escape.should eq("\e[6m") + Colorize.with.reverse.ansi_escape.should eq("\e[7m") + Colorize.with.hidden.ansi_escape.should eq("\e[8m") + Colorize.with.strikethrough.ansi_escape.should eq("\e[9m") + Colorize.with.double_underline.ansi_escape.should eq("\e[21m") + Colorize.with.overline.ansi_escape.should eq("\e[53m") + end + + it "only prints colorize ANSI escape codes" do + colorize("hello").red.bold.ansi_escape.should eq("\e[31;1m") + colorize("hello").bold.dim.underline.blink.reverse.hidden.ansi_escape.should eq("\e[1;2;4;5;7;8m") + end + it "colorizes mode combination" do colorize("hello").bold.dim.underline.blink.reverse.hidden.to_s.should eq("\e[1;2;4;5;7;8mhello\e[0m") end diff --git a/src/colorize.cr b/src/colorize.cr index 83fd82c3935e..20d6879f7cb3 100644 --- a/src/colorize.cr +++ b/src/colorize.cr @@ -460,6 +460,26 @@ struct Colorize::Object(T) end end + # Prints the ANSI escape codes for an object. Note that this has no effect on a `Colorize::Object` with content, + # only the escape codes. + # + # ``` + # require "colorize" + # + # Colorize.with.red.ansi_escape # => "\e[31m" + # "hello world".green.bold.ansi_escape # => "\e[32;1m" + # ``` + def ansi_escape : String + String.build do |io| + ansi_escape io + end + end + + # Same as `ansi_escape` but writes to a given *io*. + def ansi_escape(io : IO) : Nil + self.class.ansi_escape(io, to_named_tuple) + end + private def to_named_tuple { fore: @fore, @@ -474,6 +494,12 @@ struct Colorize::Object(T) mode: Mode::None, } + protected def self.ansi_escape(io : IO, color : {fore: Color, back: Color, mode: Mode}) : Nil + last_color = @@last_color + append_start(io, color) + @@last_color = last_color + end + protected def self.surround(io, color, &) last_color = @@last_color must_append_end = append_start(io, color) From 7326cefc2e8e1086a581afc3837638e42f26c11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 18 Dec 2024 11:20:48 +0100 Subject: [PATCH 1533/1551] Improve `Process::Status#to_s` for abnormal exits on Windows (#15283) Abnormal exit statuses on Windows are indicated by specific status values which correlate to `LibC` constants. This patch changes `Status#to_s` (and `#inspect`) to return the name of the constant if available. This improves usability because it's easier to interpret the status. --- spec/std/process/status_spec.cr | 4 +-- src/process/status.cr | 47 +++++++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 63136a2ddac7..31c86be4aae9 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -241,7 +241,7 @@ describe Process::Status do it "on abnormal exit" do {% if flag?(:win32) %} - assert_prints status_for(:interrupted).to_s, "3221225786" + assert_prints status_for(:interrupted).to_s, "STATUS_CONTROL_C_EXIT" {% else %} assert_prints status_for(:interrupted).to_s, "INT" {% end %} @@ -269,7 +269,7 @@ describe Process::Status do it "on abnormal exit" do {% if flag?(:win32) %} - assert_prints status_for(:interrupted).inspect, "Process::Status[3221225786]" + assert_prints status_for(:interrupted).inspect, "Process::Status[LibC::STATUS_CONTROL_C_EXIT]" {% else %} assert_prints status_for(:interrupted).inspect, "Process::Status[Signal::INT]" {% end %} diff --git a/src/process/status.cr b/src/process/status.cr index 28e6049238dc..6598008b4e6f 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -270,13 +270,18 @@ class Process::Status # Prints a textual representation of the process status to *io*. # - # The result is equivalent to `#to_s`, but prefixed by the type name and - # delimited by square brackets: `Process::Status[0]`, `Process::Status[1]`, - # `Process::Status[Signal::HUP]`. + # The result is similar to `#to_s`, but prefixed by the type name, + # delimited by square brackets, and constants use full paths: + # `Process::Status[0]`, `Process::Status[1]`, `Process::Status[Signal::HUP]`, + # `Process::Status[LibC::STATUS_CONTROL_C_EXIT]`. def inspect(io : IO) : Nil io << "Process::Status[" {% if flag?(:win32) %} - @exit_status.to_s(io) + if name = name_for_win32_exit_status + io << "LibC::" << name + else + @exit_status.to_s(io) + end {% else %} if normal_exit? exit_code.inspect(io) @@ -287,13 +292,38 @@ class Process::Status io << "]" end + private def name_for_win32_exit_status + case @exit_status + # Ignoring LibC::STATUS_SUCCESS here because we prefer its numerical representation `0` + when LibC::STATUS_FATAL_APP_EXIT then "STATUS_FATAL_APP_EXIT" + when LibC::STATUS_DATATYPE_MISALIGNMENT then "STATUS_DATATYPE_MISALIGNMENT" + when LibC::STATUS_BREAKPOINT then "STATUS_BREAKPOINT" + when LibC::STATUS_ACCESS_VIOLATION then "STATUS_ACCESS_VIOLATION" + when LibC::STATUS_ILLEGAL_INSTRUCTION then "STATUS_ILLEGAL_INSTRUCTION" + when LibC::STATUS_FLOAT_DIVIDE_BY_ZERO then "STATUS_FLOAT_DIVIDE_BY_ZERO" + when LibC::STATUS_FLOAT_INEXACT_RESULT then "STATUS_FLOAT_INEXACT_RESULT" + when LibC::STATUS_FLOAT_INVALID_OPERATION then "STATUS_FLOAT_INVALID_OPERATION" + when LibC::STATUS_FLOAT_OVERFLOW then "STATUS_FLOAT_OVERFLOW" + when LibC::STATUS_FLOAT_UNDERFLOW then "STATUS_FLOAT_UNDERFLOW" + when LibC::STATUS_PRIVILEGED_INSTRUCTION then "STATUS_PRIVILEGED_INSTRUCTION" + when LibC::STATUS_STACK_OVERFLOW then "STATUS_STACK_OVERFLOW" + when LibC::STATUS_CANCELLED then "STATUS_CANCELLED" + when LibC::STATUS_CONTROL_C_EXIT then "STATUS_CONTROL_C_EXIT" + end + end + # Prints a textual representation of the process status to *io*. # - # A normal exit status prints the numerical value (`0`, `1` etc). + # A normal exit status prints the numerical value (`0`, `1` etc) or a named + # status (e.g. `STATUS_CONTROL_C_EXIT` on Windows). # A signal exit status prints the name of the `Signal` member (`HUP`, `INT`, etc.). def to_s(io : IO) : Nil {% if flag?(:win32) %} - @exit_status.to_s(io) + if name = name_for_win32_exit_status + io << name + else + @exit_status.to_s(io) + end {% else %} if normal_exit? io << exit_code @@ -310,11 +340,12 @@ class Process::Status # Returns a textual representation of the process status. # - # A normal exit status prints the numerical value (`0`, `1` etc). + # A normal exit status prints the numerical value (`0`, `1` etc) or a named + # status (e.g. `STATUS_CONTROL_C_EXIT` on Windows). # A signal exit status prints the name of the `Signal` member (`HUP`, `INT`, etc.). def to_s : String {% if flag?(:win32) %} - @exit_status.to_s + name_for_win32_exit_status || @exit_status.to_s {% else %} if normal_exit? exit_code.to_s From 5545bcae7cc966d849ff1423b2d1005c0d00dc8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 18 Dec 2024 17:46:06 +0100 Subject: [PATCH 1534/1551] Add `Process::Status#exit_signal?` (#15284) --- spec/std/process/status_spec.cr | 17 +++++++++++++++++ src/compiler/crystal/command.cr | 3 +-- src/process/status.cr | 34 ++++++++++++++++++++++----------- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 31c86be4aae9..124a2b58add4 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -99,6 +99,13 @@ describe Process::Status do err1.hash.should eq(err2.hash) end + it "#exit_signal?" do + Process::Status.new(exit_status(0)).exit_signal?.should be_nil + Process::Status.new(exit_status(1)).exit_signal?.should be_nil + + status_for(:interrupted).exit_signal?.should eq({% if flag?(:unix) %}Signal::INT{% else %}nil{% end %}) + end + {% if flag?(:unix) && !flag?(:wasi) %} it "#exit_signal" do Process::Status.new(Signal::HUP.value).exit_signal.should eq Signal::HUP @@ -110,6 +117,16 @@ describe Process::Status do Process::Status.new(unknown_signal.value).exit_signal.should eq unknown_signal end + it "#exit_signal?" do + Process::Status.new(Signal::HUP.value).exit_signal?.should eq Signal::HUP + Process::Status.new(Signal::INT.value).exit_signal?.should eq Signal::INT + last_signal = Signal.values[-1] + Process::Status.new(last_signal.value).exit_signal?.should eq last_signal + + unknown_signal = Signal.new(126) + Process::Status.new(unknown_signal.value).exit_signal?.should eq unknown_signal + end + it "#normal_exit? with signal code" do Process::Status.new(0x00).normal_exit?.should be_true Process::Status.new(0x01).normal_exit?.should be_false diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 3ce4fcd71550..cc6f39657f64 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -316,8 +316,7 @@ class Crystal::Command private def exit_message(status) case status.exit_reason when .aborted?, .session_ended?, .terminal_disconnected? - if status.signal_exit? - signal = status.exit_signal + if signal = status.exit_signal? if signal.kill? "Program was killed" else diff --git a/src/process/status.cr b/src/process/status.cr index 6598008b4e6f..0b7a3c827fb8 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -223,6 +223,20 @@ class Process::Status {% end %} end + # Returns the exit `Signal` or `nil` if there is none. + # + # On Windows returns always `nil`. + # + # * `#exit_reason` is a portable alternative. + def exit_signal? : Signal? + {% if flag?(:unix) && !flag?(:wasm32) %} + code = signal_code + unless code.zero? + Signal.new(code) + end + {% end %} + end + # Returns the exit code of the process if it exited normally (`#normal_exit?`). # # Raises `RuntimeError` if the status describes an abnormal exit. @@ -283,10 +297,10 @@ class Process::Status @exit_status.to_s(io) end {% else %} - if normal_exit? - exit_code.inspect(io) + if signal = exit_signal? + signal.inspect(io) else - exit_signal.inspect(io) + exit_code.inspect(io) end {% end %} io << "]" @@ -325,15 +339,14 @@ class Process::Status @exit_status.to_s(io) end {% else %} - if normal_exit? - io << exit_code - else - signal = exit_signal + if signal = exit_signal? if name = signal.member_name io << name else signal.inspect(io) end + else + io << exit_code end {% end %} end @@ -347,11 +360,10 @@ class Process::Status {% if flag?(:win32) %} name_for_win32_exit_status || @exit_status.to_s {% else %} - if normal_exit? - exit_code.to_s - else - signal = exit_signal + if signal = exit_signal? signal.member_name || signal.inspect + else + exit_code.to_s end {% end %} end From 22fb31b543a1e313765c480666144a30361e67ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 20 Dec 2024 13:03:07 +0100 Subject: [PATCH 1535/1551] Change `Process::Status#to_s` to hex format on Windows (#15285) Status numbers on Windows are usually represented in hexadecimal notation. This applies that for all large exit status values (above `UInt16::MAX`, inspired from https://cs.opensource.google/go/go/+/refs/tags/go1.23.4:src/os/exec_posix.go;l=117-121). This again improves usability because it's easier to interpret unknown status values and compare them with listings sharing the typical number format. In #15283 we already changed `Status#to_s` to print the name of known values. But not all status codes are named. --- spec/std/process/status_spec.cr | 16 ++++++++++++++++ src/process/status.cr | 16 +++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 124a2b58add4..5561b106613b 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -273,6 +273,14 @@ describe Process::Status do assert_prints Process::Status.new(Signal.new(126).value).to_s, "Signal[126]" end {% end %} + + {% if flag?(:win32) %} + it "hex format" do + assert_prints Process::Status.new(UInt16::MAX).to_s, "0x0000FFFF" + assert_prints Process::Status.new(0x01234567).to_s, "0x01234567" + assert_prints Process::Status.new(UInt32::MAX).to_s, "0xFFFFFFFF" + end + {% end %} end describe "#inspect" do @@ -302,5 +310,13 @@ describe Process::Status do assert_prints Process::Status.new(unknown_signal.value).inspect, "Process::Status[Signal[126]]" end {% end %} + + {% if flag?(:win32) %} + it "hex format" do + assert_prints Process::Status.new(UInt16::MAX).inspect, "Process::Status[0x0000FFFF]" + assert_prints Process::Status.new(0x01234567).inspect, "Process::Status[0x01234567]" + assert_prints Process::Status.new(UInt32::MAX).inspect, "Process::Status[0xFFFFFFFF]" + end + {% end %} end end diff --git a/src/process/status.cr b/src/process/status.cr index 0b7a3c827fb8..2bbcb175c033 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -294,7 +294,7 @@ class Process::Status if name = name_for_win32_exit_status io << "LibC::" << name else - @exit_status.to_s(io) + stringify_exit_status_windows(io) end {% else %} if signal = exit_signal? @@ -336,7 +336,7 @@ class Process::Status if name = name_for_win32_exit_status io << name else - @exit_status.to_s(io) + stringify_exit_status_windows(io) end {% else %} if signal = exit_signal? @@ -358,7 +358,7 @@ class Process::Status # A signal exit status prints the name of the `Signal` member (`HUP`, `INT`, etc.). def to_s : String {% if flag?(:win32) %} - name_for_win32_exit_status || @exit_status.to_s + name_for_win32_exit_status || String.build { |io| stringify_exit_status_windows(io) } {% else %} if signal = exit_signal? signal.member_name || signal.inspect @@ -367,4 +367,14 @@ class Process::Status end {% end %} end + + private def stringify_exit_status_windows(io) + # On Windows large status codes are typically expressed in hexadecimal + if @exit_status >= UInt16::MAX + io << "0x" + @exit_status.to_s(base: 16, upcase: true).rjust(io, 8, '0') + else + @exit_status.to_s(io) + end + end end From 7e480697653359ba5474444adfd3f2e9d5925b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 20 Dec 2024 13:03:24 +0100 Subject: [PATCH 1536/1551] Redefine `Process::Status#signal_exit?` (#15289) Aligns `Status#signal_exit?` with `#exit_signal?` from #15284. This includes the change that `Signal::STOP` (`0x7e`) is considered a signal by `#signal_exit?`, in concordance with `#exit_signal?`. See discussion in https://github.com/crystal-lang/crystal/issues/15231#issuecomment-2501712968 --- spec/std/process/status_spec.cr | 2 +- src/process/status.cr | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 5561b106613b..b56f261c4b5c 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -138,7 +138,7 @@ describe Process::Status do Process::Status.new(0x00).signal_exit?.should be_false Process::Status.new(0x01).signal_exit?.should be_true Process::Status.new(0x7e).signal_exit?.should be_true - Process::Status.new(0x7f).signal_exit?.should be_false + Process::Status.new(0x7f).signal_exit?.should be_true end {% end %} diff --git a/src/process/status.cr b/src/process/status.cr index 2bbcb175c033..74ab92b329d6 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -180,12 +180,14 @@ class Process::Status end # Returns `true` if the process was terminated by a signal. + # + # NOTE: In contrast to `WIFSIGNALED` in glibc, the status code `0x7E` (`SIGSTOP`) + # is considered a signal. + # + # * `#abnormal_exit?` is a more portable alternative. + # * `#exit_signal?` provides more information about the signal. def signal_exit? : Bool - {% if flag?(:unix) %} - 0x01 <= (@exit_status & 0x7F) <= 0x7E - {% else %} - false - {% end %} + !!exit_signal? end # Returns `true` if the process terminated normally. From f0185ee612e313e23f6d033555481421652b2401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 20 Dec 2024 13:03:35 +0100 Subject: [PATCH 1537/1551] Refactor simplify `Process::Status#exit_reason` on Unix (#15288) --- src/process/status.cr | 49 +++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/src/process/status.cr b/src/process/status.cr index 74ab92b329d6..db759bd1c178 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -142,37 +142,30 @@ class Process::Status @exit_status & 0xC0000000_u32 == 0 ? ExitReason::Normal : ExitReason::Unknown end {% elsif flag?(:unix) && !flag?(:wasm32) %} - # define __WIFEXITED(status) (__WTERMSIG(status) == 0) - if signal_code == 0 + case exit_signal? + when Nil ExitReason::Normal - elsif signal_exit? - case Signal.from_value?(signal_code) - when Nil - ExitReason::Signal - when .abrt?, .kill?, .quit? - ExitReason::Aborted - when .hup? - ExitReason::TerminalDisconnected - when .term? - ExitReason::SessionEnded - when .int? - ExitReason::Interrupted - when .trap? - ExitReason::Breakpoint - when .segv? - ExitReason::AccessViolation - when .bus? - ExitReason::BadMemoryAccess - when .ill? - ExitReason::BadInstruction - when .fpe? - ExitReason::FloatException - else - ExitReason::Signal - end + when .abrt?, .kill?, .quit? + ExitReason::Aborted + when .hup? + ExitReason::TerminalDisconnected + when .term? + ExitReason::SessionEnded + when .int? + ExitReason::Interrupted + when .trap? + ExitReason::Breakpoint + when .segv? + ExitReason::AccessViolation + when .bus? + ExitReason::BadMemoryAccess + when .ill? + ExitReason::BadInstruction + when .fpe? + ExitReason::FloatException else # TODO: stop / continue - ExitReason::Unknown + ExitReason::Signal end {% else %} raise NotImplementedError.new("Process::Status#exit_reason") From 119e0db2513a5161194c64aa192587a501272de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 20 Dec 2024 22:24:01 +0100 Subject: [PATCH 1538/1551] Update shards 0.19.0 (#15290) --- .github/workflows/win_build_portable.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index a81b9e8083ed..585b9e67bd6a 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -127,7 +127,7 @@ jobs: uses: actions/checkout@v4 with: repository: crystal-lang/shards - ref: v0.18.0 + ref: v0.19.0 path: shards - name: Build shards release From 69345ba5311dc671a26d1fe97839c245bfb77465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 20 Dec 2024 22:24:14 +0100 Subject: [PATCH 1539/1551] Update distribution-scripts (#15291) Updates `distribution-scripts` dependency to https://github.com/crystal-lang/distribution-scripts/commit/588099d9e9de7ecf5925365796d30f832870e18c This includes the following changes: * crystal-lang/distribution-scripts#339 * crystal-lang/distribution-scripts#335 * crystal-lang/distribution-scripts#334 * crystal-lang/distribution-scripts#269 * crystal-lang/distribution-scripts#332 * crystal-lang/distribution-scripts#329 * crystal-lang/distribution-scripts#327 * crystal-lang/distribution-scripts#324 * crystal-lang/distribution-scripts#323 * crystal-lang/distribution-scripts#325 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index baf9b41a1be3..1f69f8d18edc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ parameters: distribution-scripts-version: description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/" type: string - default: "da59efb2dfd70dcd7272eaecceffb636ef547427" + default: "588099d9e9de7ecf5925365796d30f832870e18c" previous_crystal_base_url: description: "Prefix for URLs to Crystal bootstrap compiler" type: string From b6b190f83309c21c536ac16d6e85c8d280d01a61 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Fri, 20 Dec 2024 16:24:39 -0500 Subject: [PATCH 1540/1551] Crystal `Not` operators do not need parens (#15292) --- spec/compiler/parser/to_s_spec.cr | 1 + src/compiler/crystal/syntax/to_s.cr | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index d7d33db11e09..86464e197267 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -150,6 +150,7 @@ describe "ASTNode#to_s" do expect_to_s "1e10_f64", "1e10" expect_to_s "!a" expect_to_s "!(1 < 2)" + expect_to_s "!a.b && true" expect_to_s "(1 + 2)..3" expect_to_s "macro foo\n{{ @type }}\nend" expect_to_s "macro foo\n\\{{ @type }}\nend" diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 271f003824b1..4ce9ca7efc43 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -487,7 +487,7 @@ module Crystal end when Var, NilLiteral, BoolLiteral, CharLiteral, NumberLiteral, StringLiteral, StringInterpolation, Path, Generic, InstanceVar, ClassVar, Global, - ImplicitObj, TupleLiteral, NamedTupleLiteral, IsA + ImplicitObj, TupleLiteral, NamedTupleLiteral, IsA, Not false when ArrayLiteral !!obj.of From c5455ce0d39b1efe15f7e6d60917eb5434ed2a70 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 21 Dec 2024 19:20:09 +0800 Subject: [PATCH 1541/1551] Implement `fast_float` for `String#to_f` (#15195) This is a source port of https://github.com/fastfloat/fast_float, which is both locale-independent and platform-independent, meaning the special float values will work on MSYS2's MINGW64 environment too, as we are not calling `LibC.strtod` anymore. Additionally, non-ASCII whitespace characters are now stripped, just like `#to_i`. **The current implementation doesn't accept hexfloats.** This implementation brings a roughly 3x speedup, without any additional allocations. --- spec/manual/string_to_f32_spec.cr | 27 + spec/manual/string_to_f_supplemental_spec.cr | 103 +++ spec/std/string_spec.cr | 4 + spec/support/number.cr | 32 + src/float/fast_float.cr | 75 ++ src/float/fast_float/ascii_number.cr | 270 +++++++ src/float/fast_float/bigint.cr | 577 +++++++++++++++ src/float/fast_float/decimal_to_binary.cr | 177 +++++ src/float/fast_float/digit_comparison.cr | 399 +++++++++++ src/float/fast_float/fast_table.cr | 695 +++++++++++++++++++ src/float/fast_float/float_common.cr | 294 ++++++++ src/float/fast_float/parse_number.cr | 197 ++++++ src/lib_c/x86_64-windows-msvc/c/stdlib.cr | 4 +- src/string.cr | 67 +- 14 files changed, 2859 insertions(+), 62 deletions(-) create mode 100644 spec/manual/string_to_f32_spec.cr create mode 100644 spec/manual/string_to_f_supplemental_spec.cr create mode 100644 src/float/fast_float.cr create mode 100644 src/float/fast_float/ascii_number.cr create mode 100644 src/float/fast_float/bigint.cr create mode 100644 src/float/fast_float/decimal_to_binary.cr create mode 100644 src/float/fast_float/digit_comparison.cr create mode 100644 src/float/fast_float/fast_table.cr create mode 100644 src/float/fast_float/float_common.cr create mode 100644 src/float/fast_float/parse_number.cr diff --git a/spec/manual/string_to_f32_spec.cr b/spec/manual/string_to_f32_spec.cr new file mode 100644 index 000000000000..6d0940b1190c --- /dev/null +++ b/spec/manual/string_to_f32_spec.cr @@ -0,0 +1,27 @@ +require "spec" + +# Exhaustively checks that for all 4294967296 possible `Float32` values, +# `to_s.to_f32` returns the original number. Splits the floats into 4096 bins +# for better progress tracking. Also useful as a sort of benchmark. +# +# This was originally added when `String#to_f` moved from `LibC.strtod` to +# `fast_float`, but is applicable to any other implementation as well. +describe "x.to_s.to_f32 == x" do + (0_u32..0xFFF_u32).each do |i| + it "%03x00000..%03xfffff" % {i, i} do + 0x100000.times do |j| + bits = i << 20 | j + float = bits.unsafe_as(Float32) + str = float.to_s + val = str.to_f32?.should_not be_nil + + if float.nan? + val.nan?.should be_true + else + val.should eq(float) + Math.copysign(1, val).should eq(Math.copysign(1, float)) + end + end + end + end +end diff --git a/spec/manual/string_to_f_supplemental_spec.cr b/spec/manual/string_to_f_supplemental_spec.cr new file mode 100644 index 000000000000..1b016e22c86a --- /dev/null +++ b/spec/manual/string_to_f_supplemental_spec.cr @@ -0,0 +1,103 @@ +# Runs the fast_float supplemental test suite: +# https://github.com/fastfloat/supplemental_test_files +# +# Supplemental data files for testing floating parsing (credit: Nigel Tao for +# the data) +# +# LICENSE file (Apache 2): https://github.com/nigeltao/parse-number-fxx-test-data/blob/main/LICENSE +# +# Due to the sheer volume of the test cases (5.2+ million test cases across +# 270+ MB of text) these specs are not vendored into the Crystal repository. + +require "spec" +require "http/client" +require "../support/number" +require "wait_group" + +# these specs permit underflow and overflow to return 0 and infinity +# respectively (when `ret.rc == Errno::ERANGE`), so we have to use +# `Float::FastFloat` directly +def fast_float_to_f32(str) + value = uninitialized Float32 + start = str.to_unsafe + finish = start + str.bytesize + options = Float::FastFloat::ParseOptionsT(typeof(str.to_unsafe.value)).new(format: :general) + + ret = Float::FastFloat::BinaryFormat_Float32.new.from_chars_advanced(start, finish, pointerof(value), options) + {Errno::NONE, Errno::ERANGE}.should contain(ret.ec) + value +end + +def fast_float_to_f64(str) + value = uninitialized Float64 + start = str.to_unsafe + finish = start + str.bytesize + options = Float::FastFloat::ParseOptionsT(typeof(str.to_unsafe.value)).new(format: :general) + + ret = Float::FastFloat::BinaryFormat_Float64.new.from_chars_advanced(start, finish, pointerof(value), options) + {Errno::NONE, Errno::ERANGE}.should contain(ret.ec) + value +end + +RAW_BASE_URL = "https://raw.githubusercontent.com/fastfloat/supplemental_test_files/7cc512a7c60361ebe1baf54991d7905efdc62aa0/data/" # @1.0.0 + +TEST_SUITES = %w( + freetype-2-7.txt + google-double-conversion.txt + google-wuffs.txt + ibm-fpgen.txt + lemire-fast-double-parser.txt + lemire-fast-float.txt + more-test-cases.txt + remyoudompheng-fptest-0.txt + remyoudompheng-fptest-1.txt + remyoudompheng-fptest-2.txt + remyoudompheng-fptest-3.txt + tencent-rapidjson.txt + ulfjack-ryu.txt +) + +test_suite_cache = {} of String => Array({UInt32, UInt64, String}) +puts "Fetching #{TEST_SUITES.size} test suites" +WaitGroup.wait do |wg| + TEST_SUITES.each do |suite| + wg.spawn do + url = RAW_BASE_URL + suite + + cache = HTTP::Client.get(url) do |res| + res.body_io.each_line.map do |line| + args = line.split(' ') + raise "BUG: should have 4 args" unless args.size == 4 + + # f16_bits = args[0].to_u16(16) + f32_bits = args[1].to_u32(16) + f64_bits = args[2].to_u64(16) + str = args[3] + + {f32_bits, f64_bits, str} + end.to_a + end + + puts "#{cache.size} test cases cached from #{url}" + test_suite_cache[suite] = cache + end + end +end +puts "There are a total of #{test_suite_cache.sum(&.last.size)} test cases" + +describe String do + describe "#to_f" do + test_suite_cache.each do |suite, cache| + describe suite do + each_hardware_rounding_mode do |mode, mode_name| + it mode_name do + cache.each do |f32_bits, f64_bits, str| + fast_float_to_f32(str).unsafe_as(UInt32).should eq(f32_bits) + fast_float_to_f64(str).unsafe_as(UInt64).should eq(f64_bits) + end + end + end + end + end + end +end diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 2bbc63f7e18e..72e05adab458 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -482,6 +482,7 @@ describe "String" do it { "1Y2P0IJ32E8E7".to_i64(36).should eq(9223372036854775807) } end + # more specs are available in `spec/manual/string_to_f_supplemental_spec.cr` it "does to_f" do expect_raises(ArgumentError) { "".to_f } "".to_f?.should be_nil @@ -503,6 +504,7 @@ describe "String" do " 1234.56 ".to_f?(whitespace: false).should be_nil expect_raises(ArgumentError) { " 1234.56foo".to_f } " 1234.56foo".to_f?.should be_nil + "\u{A0}\u{2028}\u{2029}1234.56\u{A0}\u{2028}\u{2029}".to_f.should eq(1234.56_f64) "123.45 x".to_f64(strict: false).should eq(123.45_f64) expect_raises(ArgumentError) { "x1.2".to_f64 } "x1.2".to_f64?.should be_nil @@ -547,6 +549,7 @@ describe "String" do " 1234.56 ".to_f32?(whitespace: false).should be_nil expect_raises(ArgumentError) { " 1234.56foo".to_f32 } " 1234.56foo".to_f32?.should be_nil + "\u{A0}\u{2028}\u{2029}1234.56\u{A0}\u{2028}\u{2029}".to_f32.should eq(1234.56_f32) "123.45 x".to_f32(strict: false).should eq(123.45_f32) expect_raises(ArgumentError) { "x1.2".to_f32 } "x1.2".to_f32?.should be_nil @@ -590,6 +593,7 @@ describe "String" do " 1234.56 ".to_f64?(whitespace: false).should be_nil expect_raises(ArgumentError) { " 1234.56foo".to_f64 } " 1234.56foo".to_f64?.should be_nil + "\u{A0}\u{2028}\u{2029}1234.56\u{A0}\u{2028}\u{2029}".to_f64.should eq(1234.56_f64) "123.45 x".to_f64(strict: false).should eq(123.45_f64) expect_raises(ArgumentError) { "x1.2".to_f64 } "x1.2".to_f64?.should be_nil diff --git a/spec/support/number.cr b/spec/support/number.cr index 4ec22f9dcf87..404d2bd32438 100644 --- a/spec/support/number.cr +++ b/spec/support/number.cr @@ -94,3 +94,35 @@ macro hexfloat(str) ::Float64.parse_hexfloat({{ str }}) {% end %} end + +# See also: https://github.com/crystal-lang/crystal/issues/15192 +lib LibC + {% if flag?(:win32) %} + FE_TONEAREST = 0x00000000 + FE_DOWNWARD = 0x00000100 + FE_UPWARD = 0x00000200 + FE_TOWARDZERO = 0x00000300 + {% else %} + FE_TONEAREST = 0x00000000 + FE_DOWNWARD = 0x00000400 + FE_UPWARD = 0x00000800 + FE_TOWARDZERO = 0x00000C00 + {% end %} + + fun fegetround : Int + fun fesetround(round : Int) : Int +end + +def with_hardware_rounding_mode(mode, &) + old_mode = LibC.fegetround + LibC.fesetround(mode) + yield ensure LibC.fesetround(old_mode) +end + +def each_hardware_rounding_mode(&) + {% for mode in %w(FE_TONEAREST FE_DOWNWARD FE_UPWARD FE_TOWARDZERO) %} + with_hardware_rounding_mode(LibC::{{ mode.id }}) do + yield LibC::{{ mode.id }}, {{ mode }} + end + {% end %} +end diff --git a/src/float/fast_float.cr b/src/float/fast_float.cr new file mode 100644 index 000000000000..010476db4bca --- /dev/null +++ b/src/float/fast_float.cr @@ -0,0 +1,75 @@ +struct Float + # :nodoc: + # Source port of the floating-point part of fast_float for C++: + # https://github.com/fastfloat/fast_float + # + # fast_float implements the C++17 `std::from_chars`, which accepts a subset of + # the C `strtod` / `strtof`'s string format: + # + # - a leading plus sign is disallowed, but both fast_float and this port + # accept it; + # - the exponent may be required or disallowed, depending on the format + # argument (this port always allows both); + # - hexfloats are not enabled by default, and fast_float doesn't implement it; + # (https://github.com/fastfloat/fast_float/issues/124) + # - hexfloats cannot start with `0x` or `0X`. + # + # The following is their license: + # + # Licensed under either of Apache License, Version 2.0 or MIT license or + # BOOST license. + # + # Unless you explicitly state otherwise, any contribution intentionally + # submitted for inclusion in this repository by you, as defined in the + # Apache-2.0 license, shall be triple licensed as above, without any + # additional terms or conditions. + # + # Main differences from the original fast_float: + # + # - Only `UC == UInt8` is implemented and tested, not the other wide chars; + # - No explicit SIMD (the original mainly uses this for wide char strings). + # + # The following compile-time configuration is assumed: + # + # - #define FASTFLOAT_ALLOWS_LEADING_PLUS + # - #define FLT_EVAL_METHOD 0 + module FastFloat + # Current revision: https://github.com/fastfloat/fast_float/tree/v6.1.6 + + def self.to_f64?(str : String, whitespace : Bool, strict : Bool) : Float64? + value = uninitialized Float64 + start = str.to_unsafe + finish = start + str.bytesize + options = ParseOptionsT(typeof(str.to_unsafe.value)).new(format: :general) + + if whitespace + start += str.calc_excess_left + finish -= str.calc_excess_right + end + + ret = BinaryFormat_Float64.new.from_chars_advanced(start, finish, pointerof(value), options) + if ret.ec == Errno::NONE && (!strict || ret.ptr == finish) + value + end + end + + def self.to_f32?(str : String, whitespace : Bool, strict : Bool) : Float32? + value = uninitialized Float32 + start = str.to_unsafe + finish = start + str.bytesize + options = ParseOptionsT(typeof(str.to_unsafe.value)).new(format: :general) + + if whitespace + start += str.calc_excess_left + finish -= str.calc_excess_right + end + + ret = BinaryFormat_Float32.new.from_chars_advanced(start, finish, pointerof(value), options) + if ret.ec == Errno::NONE && (!strict || ret.ptr == finish) + value + end + end + end +end + +require "./fast_float/parse_number" diff --git a/src/float/fast_float/ascii_number.cr b/src/float/fast_float/ascii_number.cr new file mode 100644 index 000000000000..1c4b43ea4b7d --- /dev/null +++ b/src/float/fast_float/ascii_number.cr @@ -0,0 +1,270 @@ +require "./float_common" + +module Float::FastFloat + # Next function can be micro-optimized, but compilers are entirely able to + # optimize it well. + def self.is_integer?(c : UC) : Bool forall UC + !(c > '9'.ord || c < '0'.ord) + end + + # Read 8 UC into a u64. Truncates UC if not char. + def self.read8_to_u64(chars : UC*) : UInt64 forall UC + val = uninitialized UInt64 + chars.as(UInt8*).copy_to(pointerof(val).as(UInt8*), sizeof(UInt64)) + {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::BigEndian %} + val.byte_swap + {% else %} + val + {% end %} + end + + # credit @aqrit + def self.parse_eight_digits_unrolled(val : UInt64) : UInt32 + mask = 0x000000FF000000FF_u64 + mul1 = 0x000F424000000064_u64 # 100 + (1000000ULL << 32) + mul2 = 0x0000271000000001_u64 # 1 + (10000ULL << 32) + val &-= 0x3030303030303030 + val = (val &* 10) &+ val.unsafe_shr(8) # val = (val * 2561) >> 8 + val = (((val & mask) &* mul1) &+ ((val.unsafe_shr(16) & mask) &* mul2)).unsafe_shr(32) + val.to_u32! + end + + # Call this if chars are definitely 8 digits. + def self.parse_eight_digits_unrolled(chars : UC*) : UInt32 forall UC + parse_eight_digits_unrolled(read8_to_u64(chars)) + end + + # credit @aqrit + def self.is_made_of_eight_digits_fast?(val : UInt64) : Bool + ((val &+ 0x4646464646464646_u64) | (val &- 0x3030303030303030_u64)) & 0x8080808080808080_u64 == 0 + end + + # NOTE(crystal): returns {p, i} + def self.loop_parse_if_eight_digits(p : UInt8*, pend : UInt8*, i : UInt64) : {UInt8*, UInt64} + # optimizes better than parse_if_eight_digits_unrolled() for UC = char. + while pend - p >= 8 && is_made_of_eight_digits_fast?(read8_to_u64(p)) + i = i &* 100000000 &+ parse_eight_digits_unrolled(read8_to_u64(p)) # in rare cases, this will overflow, but that's ok + p += 8 + end + {p, i} + end + + enum ParseError + NoError + + # [JSON-only] The minus sign must be followed by an integer. + MissingIntegerAfterSign + + # A sign must be followed by an integer or dot. + MissingIntegerOrDotAfterSign + + # [JSON-only] The integer part must not have leading zeros. + LeadingZerosInIntegerPart + + # [JSON-only] The integer part must have at least one digit. + NoDigitsInIntegerPart + + # [JSON-only] If there is a decimal point, there must be digits in the + # fractional part. + NoDigitsInFractionalPart + + # The mantissa must have at least one digit. + NoDigitsInMantissa + + # Scientific notation requires an exponential part. + MissingExponentialPart + end + + struct ParsedNumberStringT(UC) + property exponent : Int64 = 0 + property mantissa : UInt64 = 0 + property lastmatch : UC* = Pointer(UC).null + property negative : Bool = false + property valid : Bool = false + property too_many_digits : Bool = false + # contains the range of the significant digits + property integer : Slice(UC) = Slice(UC).empty # non-nullable + property fraction : Slice(UC) = Slice(UC).empty # nullable + property error : ParseError = :no_error + end + + alias ByteSpan = ::Bytes + alias ParsedNumberString = ParsedNumberStringT(UInt8) + + def self.report_parse_error(p : UC*, error : ParseError) : ParsedNumberStringT(UC) forall UC + answer = ParsedNumberStringT(UC).new + answer.valid = false + answer.lastmatch = p + answer.error = error + answer + end + + # Assuming that you use no more than 19 digits, this will parse an ASCII + # string. + def self.parse_number_string(p : UC*, pend : UC*, options : ParseOptionsT(UC)) : ParsedNumberStringT(UC) forall UC + fmt = options.format + decimal_point = options.decimal_point + + answer = ParsedNumberStringT(UInt8).new + answer.valid = false + answer.too_many_digits = false + answer.negative = p.value === '-' + + if p.value === '-' || (!fmt.json_fmt? && p.value === '+') + p += 1 + if p == pend + return report_parse_error(p, :missing_integer_or_dot_after_sign) + end + if fmt.json_fmt? + if !is_integer?(p.value) # a sign must be followed by an integer + return report_parse_error(p, :missing_integer_after_sign) + end + else + if !is_integer?(p.value) && p.value != decimal_point # a sign must be followed by an integer or the dot + return report_parse_error(p, :missing_integer_or_dot_after_sign) + end + end + end + start_digits = p + + i = 0_u64 # an unsigned int avoids signed overflows (which are bad) + + while p != pend && is_integer?(p.value) + # a multiplication by 10 is cheaper than an arbitrary integer multiplication + i = i &* 10 &+ (p.value &- '0'.ord).to_u64! # might overflow, we will handle the overflow later + p += 1 + end + end_of_integer_part = p + digit_count = (end_of_integer_part - start_digits).to_i32! + answer.integer = Slice.new(start_digits, digit_count) + if fmt.json_fmt? + # at least 1 digit in integer part, without leading zeros + if digit_count == 0 + return report_parse_error(p, :no_digits_in_integer_part) + end + if start_digits[0] === '0' && digit_count > 1 + return report_parse_error(p, :leading_zeros_in_integer_part) + end + end + + exponent = 0_i64 + has_decimal_point = p != pend && p.value == decimal_point + if has_decimal_point + p += 1 + before = p + # can occur at most twice without overflowing, but let it occur more, since + # for integers with many digits, digit parsing is the primary bottleneck. + p, i = loop_parse_if_eight_digits(p, pend, i) + + while p != pend && is_integer?(p.value) + digit = (p.value &- '0'.ord).to_u8! + p += 1 + i = i &* 10 &+ digit # in rare cases, this will overflow, but that's ok + end + exponent = before - p + answer.fraction = Slice.new(before, (p - before).to_i32!) + digit_count &-= exponent + end + if fmt.json_fmt? + # at least 1 digit in fractional part + if has_decimal_point && exponent == 0 + return report_parse_error(p, :no_digits_in_fractional_part) + end + elsif digit_count == 0 # we must have encountered at least one integer! + return report_parse_error(p, :no_digits_in_mantissa) + end + exp_number = 0_i64 # explicit exponential part + if (fmt.scientific? && p != pend && p.value.unsafe_chr.in?('e', 'E')) || + (fmt.fortran_fmt? && p != pend && p.value.unsafe_chr.in?('+', '-', 'd', 'D')) + location_of_e = p + if p.value.unsafe_chr.in?('e', 'E', 'd', 'D') + p += 1 + end + neg_exp = false + if p != pend && p.value === '-' + neg_exp = true + p += 1 + elsif p != pend && p.value === '+' # '+' on exponent is allowed by C++17 20.19.3.(7.1) + p += 1 + end + if p == pend || !is_integer?(p.value) + if !fmt.fixed? + # The exponential part is invalid for scientific notation, so it must + # be a trailing token for fixed notation. However, fixed notation is + # disabled, so report a scientific notation error. + return report_parse_error(p, :missing_exponential_part) + end + # Otherwise, we will be ignoring the 'e'. + p = location_of_e + else + while p != pend && is_integer?(p.value) + digit = (p.value &- '0'.ord).to_u8! + if exp_number < 0x10000000 + exp_number = exp_number &* 10 &+ digit + end + p += 1 + end + if neg_exp + exp_number = 0_i64 &- exp_number + end + exponent &+= exp_number + end + else + # If it scientific and not fixed, we have to bail out. + if fmt.scientific? && !fmt.fixed? + return report_parse_error(p, :missing_exponential_part) + end + end + answer.lastmatch = p + answer.valid = true + + # If we frequently had to deal with long strings of digits, + # we could extend our code by using a 128-bit integer instead + # of a 64-bit integer. However, this is uncommon. + # + # We can deal with up to 19 digits. + if digit_count > 19 # this is uncommon + # It is possible that the integer had an overflow. + # We have to handle the case where we have 0.0000somenumber. + # We need to be mindful of the case where we only have zeroes... + # E.g., 0.000000000...000. + start = start_digits + while start != pend && (start.value === '0' || start.value == decimal_point) + if start.value === '0' + digit_count &-= 1 + end + start += 1 + end + + if digit_count > 19 + answer.too_many_digits = true + # Let us start again, this time, avoiding overflows. + # We don't need to check if is_integer, since we use the + # pre-tokenized spans from above. + i = 0_u64 + p = answer.integer.to_unsafe + int_end = p + answer.integer.size + minimal_nineteen_digit_integer = 1000000000000000000_u64 + while i < minimal_nineteen_digit_integer && p != int_end + i = i &* 10 &+ (p.value &- '0'.ord).to_u64! + p += 1 + end + if i >= minimal_nineteen_digit_integer # We have a big integers + exponent = (end_of_integer_part - p) &+ exp_number + else # We have a value with a fractional component. + p = answer.fraction.to_unsafe + frac_end = p + answer.fraction.size + while i < minimal_nineteen_digit_integer && p != frac_end + i = i &* 10 &+ (p.value &- '0'.ord).to_u64! + p += 1 + end + exponent = (answer.fraction.to_unsafe - p) &+ exp_number + end + # We have now corrected both exponent and i, to a truncated value + end + end + answer.exponent = exponent + answer.mantissa = i + answer + end +end diff --git a/src/float/fast_float/bigint.cr b/src/float/fast_float/bigint.cr new file mode 100644 index 000000000000..14b0bb2d0549 --- /dev/null +++ b/src/float/fast_float/bigint.cr @@ -0,0 +1,577 @@ +require "./float_common" + +module Float::FastFloat + # the limb width: we want efficient multiplication of double the bits in + # limb, or for 64-bit limbs, at least 64-bit multiplication where we can + # extract the high and low parts efficiently. this is every 64-bit + # architecture except for sparc, which emulates 128-bit multiplication. + # we might have platforms where `CHAR_BIT` is not 8, so let's avoid + # doing `8 * sizeof(limb)`. + {% if flag?(:bits64) %} + alias Limb = UInt64 + LIMB_BITS = 64 + {% else %} + alias Limb = UInt32 + LIMB_BITS = 32 + {% end %} + + alias LimbSpan = Slice(Limb) + + # number of bits in a bigint. this needs to be at least the number + # of bits required to store the largest bigint, which is + # `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or + # ~3600 bits, so we round to 4000. + BIGINT_BITS = 4000 + {% begin %} + BIGINT_LIMBS = {{ BIGINT_BITS // LIMB_BITS }} + {% end %} + + # vector-like type that is allocated on the stack. the entire + # buffer is pre-allocated, and only the length changes. + # NOTE(crystal): Deviates a lot from the original implementation to reuse + # `Indexable` as much as possible. Contrast with `Crystal::SmallDeque` and + # `Crystal::Tracing::BufferIO` + struct Stackvec(Size) + include Indexable::Mutable(Limb) + + @data = uninitialized Limb[Size] + + # we never need more than 150 limbs + @length = 0_u16 + + def unsafe_fetch(index : Int) : Limb + @data.to_unsafe[index] + end + + def unsafe_put(index : Int, value : Limb) : Limb + @data.to_unsafe[index] = value + end + + def size : Int32 + @length.to_i32! + end + + def to_unsafe : Limb* + @data.to_unsafe + end + + def to_slice : LimbSpan + LimbSpan.new(@data.to_unsafe, @length) + end + + def initialize + end + + # create stack vector from existing limb span. + def initialize(s : LimbSpan) + try_extend(s) + end + + # index from the end of the container + def rindex(index : Int) : Limb + rindex = @length &- index &- 1 + @data.to_unsafe[rindex] + end + + # set the length, without bounds checking. + def size=(@length : UInt16) : UInt16 + length + end + + def capacity : Int32 + Size.to_i32! + end + + # append item to vector, without bounds checking. + def push_unchecked(value : Limb) : Nil + @data.to_unsafe[@length] = value + @length &+= 1 + end + + # append item to vector, returning if item was added + def try_push(value : Limb) : Bool + if size < capacity + push_unchecked(value) + true + else + false + end + end + + # add items to the vector, from a span, without bounds checking + def extend_unchecked(s : LimbSpan) : Nil + ptr = @data.to_unsafe + @length + s.to_unsafe.copy_to(ptr, s.size) + @length &+= s.size + end + + # try to add items to the vector, returning if items were added + def try_extend(s : LimbSpan) : Bool + if size &+ s.size <= capacity + extend_unchecked(s) + true + else + false + end + end + + # resize the vector, without bounds checking + # if the new size is longer than the vector, assign value to each + # appended item. + def resize_unchecked(new_len : UInt16, value : Limb) : Nil + if new_len > @length + count = new_len &- @length + first = @data.to_unsafe + @length + count.times { |i| first[i] = value } + @length = new_len + else + @length = new_len + end + end + + # try to resize the vector, returning if the vector was resized. + def try_resize(new_len : UInt16, value : Limb) : Bool + if new_len > capacity + false + else + resize_unchecked(new_len, value) + true + end + end + + # check if any limbs are non-zero after the given index. + # this needs to be done in reverse order, since the index + # is relative to the most significant limbs. + def nonzero?(index : Int) : Bool + while index < size + if rindex(index) != 0 + return true + end + index &+= 1 + end + false + end + + # normalize the big integer, so most-significant zero limbs are removed. + def normalize : Nil + while @length > 0 && rindex(0) == 0 + @length &-= 1 + end + end + end + + # NOTE(crystal): returns also *truncated* by value (ditto below) + def self.empty_hi64 : {UInt64, Bool} + truncated = false + {0_u64, truncated} + end + + def self.uint64_hi64(r0 : UInt64) : {UInt64, Bool} + truncated = false + shl = r0.leading_zeros_count + {r0.unsafe_shl(shl), truncated} + end + + def self.uint64_hi64(r0 : UInt64, r1 : UInt64) : {UInt64, Bool} + shl = r0.leading_zeros_count + if shl == 0 + truncated = r1 != 0 + {r0, truncated} + else + shr = 64 &- shl + truncated = r1.unsafe_shl(shl) != 0 + {r0.unsafe_shl(shl) | r1.unsafe_shr(shr), truncated} + end + end + + def self.uint32_hi64(r0 : UInt32) : {UInt64, Bool} + uint64_hi64(r0.to_u64!) + end + + def self.uint32_hi64(r0 : UInt32, r1 : UInt32) : {UInt64, Bool} + x0 = r0.to_u64! + x1 = r1.to_u64! + uint64_hi64(x0.unsafe_shl(32) | x1) + end + + def self.uint32_hi64(r0 : UInt32, r1 : UInt32, r2 : UInt32) : {UInt64, Bool} + x0 = r0.to_u64! + x1 = r1.to_u64! + x2 = r2.to_u64! + uint64_hi64(x0, x1.unsafe_shl(32) | x2) + end + + # add two small integers, checking for overflow. + # we want an efficient operation. + # NOTE(crystal): returns also *overflow* by value + def self.scalar_add(x : Limb, y : Limb) : {Limb, Bool} + z = x &+ y + overflow = z < x + {z, overflow} + end + + # multiply two small integers, getting both the high and low bits. + # NOTE(crystal): passes *carry* in and out by value + def self.scalar_mul(x : Limb, y : Limb, carry : Limb) : {Limb, Limb} + {% if Limb == UInt64 %} + z = x.to_u128! &* y.to_u128! &+ carry + carry = z.unsafe_shr(LIMB_BITS).to_u64! + {z.to_u64!, carry} + {% else %} + z = x.to_u64! &* y.to_u64! &+ carry + carry = z.unsafe_shr(LIMB_BITS).to_u32! + {z.to_u32!, carry} + {% end %} + end + + # add scalar value to bigint starting from offset. + # used in grade school multiplication + def self.small_add_from(vec : Stackvec(Size)*, y : Limb, start : Int) : Bool forall Size + index = start + carry = y + + while carry != 0 && index < vec.value.size + x, overflow = scalar_add(vec.value.unsafe_fetch(index), carry) + vec.value.unsafe_put(index, x) + carry = Limb.new!(overflow ? 1 : 0) + index &+= 1 + end + if carry != 0 + fastfloat_try vec.value.try_push(carry) + end + true + end + + # add scalar value to bigint. + def self.small_add(vec : Stackvec(Size)*, y : Limb) : Bool forall Size + small_add_from(vec, y, 0) + end + + # multiply bigint by scalar value. + def self.small_mul(vec : Stackvec(Size)*, y : Limb) : Bool forall Size + carry = Limb.zero + i = 0 + while i < vec.value.size + xi = vec.value.unsafe_fetch(i) + z, carry = scalar_mul(xi, y, carry) + vec.value.unsafe_put(i, z) + i &+= 1 + end + if carry != 0 + fastfloat_try vec.value.try_push(carry) + end + true + end + + # add bigint to bigint starting from index. + # used in grade school multiplication + def self.large_add_from(x : Stackvec(Size)*, y : LimbSpan, start : Int) : Bool forall Size + # the effective x buffer is from `xstart..x.len()`, so exit early + # if we can't get that current range. + if x.value.size < start || y.size > x.value.size &- start + fastfloat_try x.value.try_resize((y.size &+ start).to_u16!, 0) + end + + carry = false + index = 0 + while index < y.size + xi = x.value.unsafe_fetch(index &+ start) + yi = y.unsafe_fetch(index) + c2 = false + xi, c1 = scalar_add(xi, yi) + if carry + xi, c2 = scalar_add(xi, 1) + end + x.value.unsafe_put(index &+ start, xi) + carry = c1 || c2 + index &+= 1 + end + + # handle overflow + if carry + fastfloat_try small_add_from(x, 1, y.size &+ start) + end + true + end + + # add bigint to bigint. + def self.large_add_from(x : Stackvec(Size)*, y : LimbSpan) : Bool forall Size + large_add_from(x, y, 0) + end + + # grade-school multiplication algorithm + def self.long_mul(x : Stackvec(Size)*, y : LimbSpan) : Bool forall Size + xs = x.value.to_slice + z = Stackvec(Size).new(xs) + zs = z.to_slice + + if y.size != 0 + y0 = y.unsafe_fetch(0) + fastfloat_try small_mul(x, y0) + (1...y.size).each do |index| + yi = y.unsafe_fetch(index) + zi = Stackvec(Size).new + if yi != 0 + # re-use the same buffer throughout + zi.size = 0 + fastfloat_try zi.try_extend(zs) + fastfloat_try small_mul(pointerof(zi), yi) + zis = zi.to_slice + fastfloat_try large_add_from(x, zis, index) + end + end + end + + x.value.normalize + true + end + + # grade-school multiplication algorithm + def self.large_mul(x : Stackvec(Size)*, y : LimbSpan) : Bool forall Size + if y.size == 1 + fastfloat_try small_mul(x, y.unsafe_fetch(0)) + else + fastfloat_try long_mul(x, y) + end + true + end + + module Pow5Tables + LARGE_STEP = 135_u32 + + SMALL_POWER_OF_5 = [ + 1_u64, + 5_u64, + 25_u64, + 125_u64, + 625_u64, + 3125_u64, + 15625_u64, + 78125_u64, + 390625_u64, + 1953125_u64, + 9765625_u64, + 48828125_u64, + 244140625_u64, + 1220703125_u64, + 6103515625_u64, + 30517578125_u64, + 152587890625_u64, + 762939453125_u64, + 3814697265625_u64, + 19073486328125_u64, + 95367431640625_u64, + 476837158203125_u64, + 2384185791015625_u64, + 11920928955078125_u64, + 59604644775390625_u64, + 298023223876953125_u64, + 1490116119384765625_u64, + 7450580596923828125_u64, + ] + + {% if Limb == UInt64 %} + LARGE_POWER_OF_5 = Slice[ + 1414648277510068013_u64, 9180637584431281687_u64, 4539964771860779200_u64, + 10482974169319127550_u64, 198276706040285095_u64, + ] + {% else %} + LARGE_POWER_OF_5 = Slice[ + 4279965485_u32, 329373468_u32, 4020270615_u32, 2137533757_u32, 4287402176_u32, + 1057042919_u32, 1071430142_u32, 2440757623_u32, 381945767_u32, 46164893_u32, + ] + {% end %} + end + + # big integer type. implements a small subset of big integer + # arithmetic, using simple algorithms since asymptotically + # faster algorithms are slower for a small number of limbs. + # all operations assume the big-integer is normalized. + # NOTE(crystal): contrast with ::BigInt + struct Bigint + # storage of the limbs, in little-endian order. + @vec = Stackvec(BIGINT_LIMBS).new + + def initialize + end + + def initialize(value : UInt64) + {% if Limb == UInt64 %} + @vec.push_unchecked(value) + {% else %} + @vec.push_unchecked(value.to_u32!) + @vec.push_unchecked(value.unsafe_shr(32).to_u32!) + {% end %} + @vec.normalize + end + + # get the high 64 bits from the vector, and if bits were truncated. + # this is to get the significant digits for the float. + # NOTE(crystal): returns also *truncated* by value + def hi64 : {UInt64, Bool} + {% if Limb == UInt64 %} + if @vec.empty? + FastFloat.empty_hi64 + elsif @vec.size == 1 + FastFloat.uint64_hi64(@vec.rindex(0)) + else + result, truncated = FastFloat.uint64_hi64(@vec.rindex(0), @vec.rindex(1)) + truncated ||= @vec.nonzero?(2) + {result, truncated} + end + {% else %} + if @vec.empty? + FastFloat.empty_hi64 + elsif @vec.size == 1 + FastFloat.uint32_hi64(@vec.rindex(0)) + elsif @vec.size == 2 + FastFloat.uint32_hi64(@vec.rindex(0), @vec.rindex(1)) + else + result, truncated = FastFloat.uint32_hi64(@vec.rindex(0), @vec.rindex(1), @vec.rindex(2)) + truncated ||= @vec.nonzero?(3) + {result, truncated} + end + {% end %} + end + + # compare two big integers, returning the large value. + # assumes both are normalized. if the return value is + # negative, other is larger, if the return value is + # positive, this is larger, otherwise they are equal. + # the limbs are stored in little-endian order, so we + # must compare the limbs in ever order. + def compare(other : Bigint*) : Int32 + if @vec.size > other.value.@vec.size + 1 + elsif @vec.size < other.value.@vec.size + -1 + else + index = @vec.size + while index > 0 + xi = @vec.unsafe_fetch(index &- 1) + yi = other.value.@vec.unsafe_fetch(index &- 1) + if xi > yi + return 1 + elsif xi < yi + return -1 + end + index &-= 1 + end + 0 + end + end + + # shift left each limb n bits, carrying over to the new limb + # returns true if we were able to shift all the digits. + def shl_bits(n : Int) : Bool + # Internally, for each item, we shift left by n, and add the previous + # right shifted limb-bits. + # For example, we transform (for u8) shifted left 2, to: + # b10100100 b01000010 + # b10 b10010001 b00001000 + shl = n + shr = LIMB_BITS &- n + prev = Limb.zero + index = 0 + while index < @vec.size + xi = @vec.unsafe_fetch(index) + @vec.unsafe_put(index, xi.unsafe_shl(shl) | prev.unsafe_shr(shr)) + prev = xi + index &+= 1 + end + + carry = prev.unsafe_shr(shr) + if carry != 0 + return @vec.try_push(carry) + end + true + end + + # move the limbs left by `n` limbs. + def shl_limbs(n : Int) : Bool + if n &+ @vec.size > @vec.capacity + false + elsif !@vec.empty? + # move limbs + dst = @vec.to_unsafe + n + src = @vec.to_unsafe + src.move_to(dst, @vec.size) + # fill in empty limbs + first = @vec.to_unsafe + n.times { |i| first[i] = 0 } + @vec.size = (@vec.size &+ n).to_u16! + true + else + true + end + end + + # move the limbs left by `n` bits. + def shl(n : Int) : Bool + rem = n.unsafe_mod(LIMB_BITS) + div = n.unsafe_div(LIMB_BITS) + if rem != 0 + FastFloat.fastfloat_try shl_bits(rem) + end + if div != 0 + FastFloat.fastfloat_try shl_limbs(div) + end + true + end + + # get the number of leading zeros in the bigint. + def ctlz : Int32 + if @vec.empty? + 0 + else + @vec.rindex(0).leading_zeros_count.to_i32! + end + end + + # get the number of bits in the bigint. + def bit_length : Int32 + lz = ctlz + (LIMB_BITS &* @vec.size &- lz).to_i32! + end + + def mul(y : Limb) : Bool + FastFloat.small_mul(pointerof(@vec), y) + end + + def add(y : Limb) : Bool + FastFloat.small_add(pointerof(@vec), y) + end + + # multiply as if by 2 raised to a power. + def pow2(exp : UInt32) : Bool + shl(exp) + end + + # multiply as if by 5 raised to a power. + def pow5(exp : UInt32) : Bool + # multiply by a power of 5 + large = Pow5Tables::LARGE_POWER_OF_5 + while exp >= Pow5Tables::LARGE_STEP + FastFloat.fastfloat_try FastFloat.large_mul(pointerof(@vec), large) + exp &-= Pow5Tables::LARGE_STEP + end + small_step = {{ Limb == UInt64 ? 27_u32 : 13_u32 }} + max_native = {{ Limb == UInt64 ? 7450580596923828125_u64 : 1220703125_u32 }} + while exp >= small_step + FastFloat.fastfloat_try FastFloat.small_mul(pointerof(@vec), max_native) + exp &-= small_step + end + if exp != 0 + FastFloat.fastfloat_try FastFloat.small_mul(pointerof(@vec), Limb.new!(Pow5Tables::SMALL_POWER_OF_5.unsafe_fetch(exp))) + end + + true + end + + # multiply as if by 10 raised to a power. + def pow10(exp : UInt32) : Bool + FastFloat.fastfloat_try pow5(exp) + pow2(exp) + end + end +end diff --git a/src/float/fast_float/decimal_to_binary.cr b/src/float/fast_float/decimal_to_binary.cr new file mode 100644 index 000000000000..eea77c44c6be --- /dev/null +++ b/src/float/fast_float/decimal_to_binary.cr @@ -0,0 +1,177 @@ +require "./float_common" +require "./fast_table" + +module Float::FastFloat + # This will compute or rather approximate w * 5**q and return a pair of 64-bit + # words approximating the result, with the "high" part corresponding to the + # most significant bits and the low part corresponding to the least significant + # bits. + def self.compute_product_approximation(q : Int64, w : UInt64, bit_precision : Int) : Value128 + power_of_five_128 = Powers::POWER_OF_FIVE_128.to_unsafe + + index = 2 &* (q &- Powers::SMALLEST_POWER_OF_FIVE) + # For small values of q, e.g., q in [0,27], the answer is always exact + # because The line value128 firstproduct = full_multiplication(w, + # power_of_five_128[index]); gives the exact answer. + firstproduct = w.to_u128! &* power_of_five_128[index] + + precision_mask = bit_precision < 64 ? 0xFFFFFFFFFFFFFFFF_u64.unsafe_shr(bit_precision) : 0xFFFFFFFFFFFFFFFF_u64 + if firstproduct.unsafe_shr(64).bits_set?(precision_mask) # could further guard with (lower + w < lower) + # regarding the second product, we only need secondproduct.high, but our + # expectation is that the compiler will optimize this extra work away if + # needed. + secondproduct = w.to_u128! &* power_of_five_128[index &+ 1] + firstproduct &+= secondproduct.unsafe_shr(64) + end + Value128.new(firstproduct) + end + + module Detail + # For q in (0,350), we have that + # f = (((152170 + 65536) * q ) >> 16); + # is equal to + # floor(p) + q + # where + # p = log(5**q)/log(2) = q * log(5)/log(2) + # + # For negative values of q in (-400,0), we have that + # f = (((152170 + 65536) * q ) >> 16); + # is equal to + # -ceil(p) + q + # where + # p = log(5**-q)/log(2) = -q * log(5)/log(2) + def self.power(q : Int32) : Int32 + ((152170 &+ 65536) &* q).unsafe_shr(16) &+ 63 + end + end + + module BinaryFormat(T, EquivUint) + # create an adjusted mantissa, biased by the invalid power2 + # for significant digits already multiplied by 10 ** q. + def compute_error_scaled(q : Int64, w : UInt64, lz : Int) : AdjustedMantissa + hilz = w.unsafe_shr(63).to_i32! ^ 1 + bias = mantissa_explicit_bits &- minimum_exponent + + AdjustedMantissa.new( + mantissa: w.unsafe_shl(hilz), + power2: Detail.power(q.to_i32!) &+ bias &- hilz &- lz &- 62 &+ INVALID_AM_BIAS, + ) + end + + # w * 10 ** q, without rounding the representation up. + # the power2 in the exponent will be adjusted by invalid_am_bias. + def compute_error(q : Int64, w : UInt64) : AdjustedMantissa + lz = w.leading_zeros_count.to_i32! + w = w.unsafe_shl(lz) + product = FastFloat.compute_product_approximation(q, w, mantissa_explicit_bits &+ 3) + compute_error_scaled(q, product.high, lz) + end + + # w * 10 ** q + # The returned value should be a valid ieee64 number that simply need to be + # packed. However, in some very rare cases, the computation will fail. In such + # cases, we return an adjusted_mantissa with a negative power of 2: the caller + # should recompute in such cases. + def compute_float(q : Int64, w : UInt64) : AdjustedMantissa + if w == 0 || q < smallest_power_of_ten + # result should be zero + return AdjustedMantissa.new( + power2: 0, + mantissa: 0, + ) + end + if q > largest_power_of_ten + # we want to get infinity: + return AdjustedMantissa.new( + power2: infinite_power, + mantissa: 0, + ) + end + # At this point in time q is in [powers::smallest_power_of_five, + # powers::largest_power_of_five]. + + # We want the most significant bit of i to be 1. Shift if needed. + lz = w.leading_zeros_count + w = w.unsafe_shl(lz) + + # The required precision is binary::mantissa_explicit_bits() + 3 because + # 1. We need the implicit bit + # 2. We need an extra bit for rounding purposes + # 3. We might lose a bit due to the "upperbit" routine (result too small, + # requiring a shift) + + product = FastFloat.compute_product_approximation(q, w, mantissa_explicit_bits &+ 3) + # The computed 'product' is always sufficient. + # Mathematical proof: + # Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to + # appear) See script/mushtak_lemire.py + + # The "compute_product_approximation" function can be slightly slower than a + # branchless approach: value128 product = compute_product(q, w); but in + # practice, we can win big with the compute_product_approximation if its + # additional branch is easily predicted. Which is best is data specific. + upperbit = product.high.unsafe_shr(63).to_i32! + shift = upperbit &+ 64 &- mantissa_explicit_bits &- 3 + + mantissa = product.high.unsafe_shr(shift) + + power2 = (Detail.power(q.to_i32!) &+ upperbit &- lz &- minimum_exponent).to_i32! + if power2 <= 0 # we have a subnormal? + # Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if 1 &- power2 >= 64 # if we have more than 64 bits below the minimum exponent, you have a zero for sure. + # result should be zero + return AdjustedMantissa.new( + power2: 0, + mantissa: 0, + ) + end + # next line is safe because -answer.power2 + 1 < 64 + mantissa = mantissa.unsafe_shr(1 &- power2) + # Thankfully, we can't have both "round-to-even" and subnormals because + # "round-to-even" only occurs for powers close to 0. + mantissa &+= mantissa & 1 + mantissa = mantissa.unsafe_shr(1) + # There is a weird scenario where we don't have a subnormal but just. + # Suppose we start with 2.2250738585072013e-308, we end up + # with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + # whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + # up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + # subnormal, but we can only know this after rounding. + # So we only declare a subnormal if we are smaller than the threshold. + power2 = mantissa < 1_u64.unsafe_shl(mantissa_explicit_bits) ? 0 : 1 + return AdjustedMantissa.new(power2: power2, mantissa: mantissa) + end + + # usually, we round *up*, but if we fall right in between and and we have an + # even basis, we need to round down + # We are only concerned with the cases where 5**q fits in single 64-bit word. + if product.low <= 1 && q >= min_exponent_round_to_even && q <= max_exponent_round_to_even && mantissa & 3 == 1 + # we may fall between two floats! + # To be in-between two floats we need that in doing + # answer.mantissa = product.high >> (upperbit + 64 - + # binary::mantissa_explicit_bits() - 3); + # ... we dropped out only zeroes. But if this happened, then we can go + # back!!! + if mantissa.unsafe_shl(shift) == product.high + mantissa &= ~1_u64 # flip it so that we do not round up + end + end + + mantissa &+= mantissa & 1 # round up + mantissa = mantissa.unsafe_shr(1) + if mantissa >= 2_u64.unsafe_shl(mantissa_explicit_bits) + mantissa = 1_u64.unsafe_shl(mantissa_explicit_bits) + power2 &+= 1 # undo previous addition + end + + mantissa &= ~(1_u64.unsafe_shl(mantissa_explicit_bits)) + if power2 >= infinite_power # infinity + return AdjustedMantissa.new( + power2: infinite_power, + mantissa: 0, + ) + end + AdjustedMantissa.new(power2: power2, mantissa: mantissa) + end + end +end diff --git a/src/float/fast_float/digit_comparison.cr b/src/float/fast_float/digit_comparison.cr new file mode 100644 index 000000000000..2da4c455bac4 --- /dev/null +++ b/src/float/fast_float/digit_comparison.cr @@ -0,0 +1,399 @@ +require "./float_common" +require "./bigint" +require "./ascii_number" + +module Float::FastFloat + # 1e0 to 1e19 + POWERS_OF_TEN_UINT64 = [ + 1_u64, + 10_u64, + 100_u64, + 1000_u64, + 10000_u64, + 100000_u64, + 1000000_u64, + 10000000_u64, + 100000000_u64, + 1000000000_u64, + 10000000000_u64, + 100000000000_u64, + 1000000000000_u64, + 10000000000000_u64, + 100000000000000_u64, + 1000000000000000_u64, + 10000000000000000_u64, + 100000000000000000_u64, + 1000000000000000000_u64, + 10000000000000000000_u64, + ] + + # calculate the exponent, in scientific notation, of the number. + # this algorithm is not even close to optimized, but it has no practical + # effect on performance: in order to have a faster algorithm, we'd need + # to slow down performance for faster algorithms, and this is still fast. + def self.scientific_exponent(num : ParsedNumberStringT(UC)) : Int32 forall UC + mantissa = num.mantissa + exponent = num.exponent.to_i32! + while mantissa >= 10000 + mantissa = mantissa.unsafe_div(10000) + exponent &+= 4 + end + while mantissa >= 100 + mantissa = mantissa.unsafe_div(100) + exponent &+= 2 + end + while mantissa >= 10 + mantissa = mantissa.unsafe_div(10) + exponent &+= 1 + end + exponent + end + + module BinaryFormat(T, EquivUint) + # this converts a native floating-point number to an extended-precision float. + def to_extended(value : T) : AdjustedMantissa + exponent_mask = self.exponent_mask + mantissa_mask = self.mantissa_mask + hidden_bit_mask = self.hidden_bit_mask + + bias = mantissa_explicit_bits &- minimum_exponent + bits = value.unsafe_as(EquivUint) + if bits & exponent_mask == 0 + # denormal + power2 = 1 &- bias + mantissa = bits & mantissa_mask + else + # normal + power2 = (bits & exponent_mask).unsafe_shr(mantissa_explicit_bits).to_i32! + power2 &-= bias + mantissa = (bits & mantissa_mask) | hidden_bit_mask + end + + AdjustedMantissa.new(power2: power2, mantissa: mantissa.to_u64!) + end + + # get the extended precision value of the halfway point between b and b+u. + # we are given a native float that represents b, so we need to adjust it + # halfway between b and b+u. + def to_extended_halfway(value : T) : AdjustedMantissa + am = to_extended(value) + am.mantissa = am.mantissa.unsafe_shl(1) + am.mantissa &+= 1 + am.power2 &-= 1 + am + end + + # round an extended-precision float to the nearest machine float. + # NOTE(crystal): passes *am* in and out by value + def round(am : AdjustedMantissa, & : AdjustedMantissa, Int32 -> AdjustedMantissa) : AdjustedMantissa + mantissa_shift = 64 &- mantissa_explicit_bits &- 1 + if 0 &- am.power2 >= mantissa_shift + # have a denormal float + shift = 1 &- am.power2 + am = yield am, {shift, 64}.min + # check for round-up: if rounding-nearest carried us to the hidden bit. + am.power2 = am.mantissa < 1_u64.unsafe_shl(mantissa_explicit_bits) ? 0 : 1 + return am + end + + # have a normal float, use the default shift. + am = yield am, mantissa_shift + + # check for carry + if am.mantissa >= 2_u64.unsafe_shl(mantissa_explicit_bits) + am.mantissa = 1_u64.unsafe_shl(mantissa_explicit_bits) + am.power2 &+= 1 + end + + # check for infinite: we could have carried to an infinite power + am.mantissa &= ~(1_u64.unsafe_shl(mantissa_explicit_bits)) + if am.power2 >= infinite_power + am.power2 = infinite_power + am.mantissa = 0 + end + + am + end + + # NOTE(crystal): passes *am* in and out by value + def round_nearest_tie_even(am : AdjustedMantissa, shift : Int32, & : Bool, Bool, Bool -> Bool) : AdjustedMantissa + mask = shift == 64 ? UInt64::MAX : 1_u64.unsafe_shl(shift) &- 1 + halfway = shift == 0 ? 0_u64 : 1_u64.unsafe_shl(shift &- 1) + truncated_bits = am.mantissa & mask + is_above = truncated_bits > halfway + is_halfway = truncated_bits == halfway + + # shift digits into position + if shift == 64 + am.mantissa = 0 + else + am.mantissa = am.mantissa.unsafe_shr(shift) + end + am.power2 &+= shift + + is_odd = am.mantissa.bits_set?(1) + am.mantissa &+= (yield is_odd, is_halfway, is_above) ? 1 : 0 + am + end + + # NOTE(crystal): passes *am* in and out by value + def round_down(am : AdjustedMantissa, shift : Int32) : AdjustedMantissa + if shift == 64 + am.mantissa = 0 + else + am.mantissa = am.mantissa.unsafe_shr(shift) + end + am.power2 &+= shift + am + end + + # NOTE(crystal): returns the new *first* by value + def skip_zeros(first : UC*, last : UC*) : UC* forall UC + int_cmp_len = FastFloat.int_cmp_len(UC) + int_cmp_zeros = FastFloat.int_cmp_zeros(UC) + + val = uninitialized UInt64 + while last - first >= int_cmp_len + first.copy_to(pointerof(val).as(UC*), int_cmp_len) + if val != int_cmp_zeros + break + end + first += int_cmp_len + end + while first != last + unless first.value === '0' + break + end + first += 1 + end + first + end + + # determine if any non-zero digits were truncated. + # all characters must be valid digits. + def is_truncated?(first : UC*, last : UC*) : Bool forall UC + int_cmp_len = FastFloat.int_cmp_len(UC) + int_cmp_zeros = FastFloat.int_cmp_zeros(UC) + + # do 8-bit optimizations, can just compare to 8 literal 0s. + + val = uninitialized UInt64 + while last - first >= int_cmp_len + first.copy_to(pointerof(val).as(UC*), int_cmp_len) + if val != int_cmp_zeros + return true + end + first += int_cmp_len + end + while first != last + unless first.value === '0' + return true + end + first += 1 + end + false + end + + def is_truncated?(s : Slice(UC)) : Bool forall UC + is_truncated?(s.to_unsafe, s.to_unsafe + s.size) + end + + macro parse_eight_digits(p, value, counter, count) + {{ value }} = {{ value }} &* 100000000 &+ FastFloat.parse_eight_digits_unrolled({{ p }}) + {{ p }} += 8 + {{ counter }} &+= 8 + {{ count }} &+= 8 + end + + macro parse_one_digit(p, value, counter, count) + {{ value }} = {{ value }} &* 10 &+ {{ p }}.value &- '0'.ord + {{ p }} += 1 + {{ counter }} &+= 1 + {{ count }} &+= 1 + end + + macro add_native(big, power, value) + {{ big }}.value.mul({{ power }}) + {{ big }}.value.add({{ value }}) + end + + macro round_up_bigint(big, count) + # need to round-up the digits, but need to avoid rounding + # ....9999 to ...10000, which could cause a false halfway point. + add_native({{ big }}, 10, 1) + {{ count }} &+= 1 + end + + # parse the significant digits into a big integer + # NOTE(crystal): returns the new *digits* by value + def parse_mantissa(result : Bigint*, num : ParsedNumberStringT(UC), max_digits : Int) : Int forall UC + # try to minimize the number of big integer and scalar multiplication. + # therefore, try to parse 8 digits at a time, and multiply by the largest + # scalar value (9 or 19 digits) for each step. + counter = 0 + digits = 0 + value = Limb.zero + step = {{ Limb == UInt64 ? 19 : 9 }} + + # process all integer digits. + p = num.integer.to_unsafe + pend = p + num.integer.size + p = skip_zeros(p, pend) + # process all digits, in increments of step per loop + while p != pend + while pend - p >= 8 && step &- counter >= 8 && max_digits &- digits >= 8 + parse_eight_digits(p, value, counter, digits) + end + while counter < step && p != pend && digits < max_digits + parse_one_digit(p, value, counter, digits) + end + if digits == max_digits + # add the temporary value, then check if we've truncated any digits + add_native(result, Limb.new!(POWERS_OF_TEN_UINT64.unsafe_fetch(counter)), value) + truncated = is_truncated?(p, pend) + unless num.fraction.empty? + truncated ||= is_truncated?(num.fraction) + end + if truncated + round_up_bigint(result, digits) + end + return digits + else + add_native(result, Limb.new!(POWERS_OF_TEN_UINT64.unsafe_fetch(counter)), value) + counter = 0 + value = Limb.zero + end + end + + # add our fraction digits, if they're available. + unless num.fraction.empty? + p = num.fraction.to_unsafe + pend = p + num.fraction.size + if digits == 0 + p = skip_zeros(p, pend) + end + # process all digits, in increments of step per loop + while p != pend + while pend - p >= 8 && step &- counter >= 8 && max_digits &- digits >= 8 + parse_eight_digits(p, value, counter, digits) + end + while counter < step && p != pend && digits < max_digits + parse_one_digit(p, value, counter, digits) + end + if digits == max_digits + # add the temporary value, then check if we've truncated any digits + add_native(result, Limb.new!(POWERS_OF_TEN_UINT64.unsafe_fetch(counter)), value) + truncated = is_truncated?(p, pend) + if truncated + round_up_bigint(result, digits) + end + return digits + else + add_native(result, Limb.new!(POWERS_OF_TEN_UINT64.unsafe_fetch(counter)), value) + counter = 0 + value = Limb.zero + end + end + end + + if counter != 0 + add_native(result, Limb.new!(POWERS_OF_TEN_UINT64.unsafe_fetch(counter)), value) + end + + digits + end + + def positive_digit_comp(bigmant : Bigint*, exponent : Int32) : AdjustedMantissa + bigmant.value.pow10(exponent.to_u32!) + mantissa, truncated = bigmant.value.hi64 + bias = mantissa_explicit_bits &- minimum_exponent + power2 = bigmant.value.bit_length &- 64 &+ bias + answer = AdjustedMantissa.new(power2: power2, mantissa: mantissa) + + answer = round(answer) do |a, shift| + round_nearest_tie_even(a, shift) do |is_odd, is_halfway, is_above| + is_above || (is_halfway && truncated) || (is_odd && is_halfway) + end + end + + answer + end + + # the scaling here is quite simple: we have, for the real digits `m * 10^e`, + # and for the theoretical digits `n * 2^f`. Since `e` is always negative, + # to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. + # we then need to scale by `2^(f- e)`, and then the two significant digits + # are of the same magnitude. + def negative_digit_comp(bigmant : Bigint*, am : AdjustedMantissa, exponent : Int32) : AdjustedMantissa + real_digits = bigmant + real_exp = exponent + + # get the value of `b`, rounded down, and get a bigint representation of b+h + am_b = round(am) do |a, shift| + round_down(a, shift) + end + b = to_float(false, am_b) + theor = to_extended_halfway(b) + theor_digits = Bigint.new(theor.mantissa) + theor_exp = theor.power2 + + # scale real digits and theor digits to be same power. + pow2_exp = theor_exp &- real_exp + pow5_exp = 0_u32 &- real_exp + if pow5_exp != 0 + theor_digits.pow5(pow5_exp) + end + if pow2_exp > 0 + theor_digits.pow2(pow2_exp.to_u32!) + elsif pow2_exp < 0 + real_digits.value.pow2(0_u32 &- pow2_exp) + end + + # compare digits, and use it to director rounding + ord = real_digits.value.compare(pointerof(theor_digits)) + answer = round(am) do |a, shift| + round_nearest_tie_even(a, shift) do |is_odd, _, _| + if ord > 0 + true + elsif ord < 0 + false + else + is_odd + end + end + end + + answer + end + + # parse the significant digits as a big integer to unambiguously round the + # the significant digits. here, we are trying to determine how to round + # an extended float representation close to `b+h`, halfway between `b` + # (the float rounded-down) and `b+u`, the next positive float. this + # algorithm is always correct, and uses one of two approaches. when + # the exponent is positive relative to the significant digits (such as + # 1234), we create a big-integer representation, get the high 64-bits, + # determine if any lower bits are truncated, and use that to direct + # rounding. in case of a negative exponent relative to the significant + # digits (such as 1.2345), we create a theoretical representation of + # `b` as a big-integer type, scaled to the same binary exponent as + # the actual digits. we then compare the big integer representations + # of both, and use that to direct rounding. + def digit_comp(num : ParsedNumberStringT(UC), am : AdjustedMantissa) : AdjustedMantissa forall UC + # remove the invalid exponent bias + am.power2 &-= INVALID_AM_BIAS + + sci_exp = FastFloat.scientific_exponent(num) + max_digits = self.max_digits + bigmant = Bigint.new + digits = parse_mantissa(pointerof(bigmant), num, max_digits) + # can't underflow, since digits is at most max_digits. + exponent = sci_exp &+ 1 &- digits + if exponent >= 0 + positive_digit_comp(pointerof(bigmant), exponent) + else + negative_digit_comp(pointerof(bigmant), am, exponent) + end + end + end +end diff --git a/src/float/fast_float/fast_table.cr b/src/float/fast_float/fast_table.cr new file mode 100644 index 000000000000..a2c2b2e9d1c9 --- /dev/null +++ b/src/float/fast_float/fast_table.cr @@ -0,0 +1,695 @@ +module Float::FastFloat + # When mapping numbers from decimal to binary, + # we go from w * 10^q to m * 2^p but we have + # 10^q = 5^q * 2^q, so effectively + # we are trying to match + # w * 2^q * 5^q to m * 2^p. Thus the powers of two + # are not a concern since they can be represented + # exactly using the binary notation, only the powers of five + # affect the binary significand. + + # The smallest non-zero float (binary64) is 2^-1074. + # We take as input numbers of the form w x 10^q where w < 2^64. + # We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + # However, we have that + # (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. + # Thus it is possible for a number of the form w * 10^-342 where + # w is a 64-bit value to be a non-zero floating-point number. + # + # Any number of form w * 10^309 where w>= 1 is going to be + # infinite in binary64 so we never need to worry about powers + # of 5 greater than 308. + module Powers + SMALLEST_POWER_OF_FIVE = -342 + LARGEST_POWER_OF_FIVE = 308 + NUMBER_OF_ENTRIES = {{ 2 * (LARGEST_POWER_OF_FIVE - SMALLEST_POWER_OF_FIVE + 1) }} + + # TODO: this is needed to avoid generating lots of allocas + # in LLVM, which makes LLVM really slow. The compiler should + # try to avoid/reuse temporary allocas. + # Explanation: https://github.com/crystal-lang/crystal/issues/4516#issuecomment-306226171 + private def self.put(array, value) : Nil + array << value + end + + # Powers of five from 5^-342 all the way to 5^308 rounded toward one. + # NOTE(crystal): this is very similar to + # `Float::Printer::Dragonbox::ImplInfo_Float64::CACHE`, except the endpoints + # are different and the rounding is in a different direction + POWER_OF_FIVE_128 = begin + array = Array(UInt64).new(NUMBER_OF_ENTRIES) + put(array, 0xeef453d6923bd65a_u64); put(array, 0x113faa2906a13b3f_u64) + put(array, 0x9558b4661b6565f8_u64); put(array, 0x4ac7ca59a424c507_u64) + put(array, 0xbaaee17fa23ebf76_u64); put(array, 0x5d79bcf00d2df649_u64) + put(array, 0xe95a99df8ace6f53_u64); put(array, 0xf4d82c2c107973dc_u64) + put(array, 0x91d8a02bb6c10594_u64); put(array, 0x79071b9b8a4be869_u64) + put(array, 0xb64ec836a47146f9_u64); put(array, 0x9748e2826cdee284_u64) + put(array, 0xe3e27a444d8d98b7_u64); put(array, 0xfd1b1b2308169b25_u64) + put(array, 0x8e6d8c6ab0787f72_u64); put(array, 0xfe30f0f5e50e20f7_u64) + put(array, 0xb208ef855c969f4f_u64); put(array, 0xbdbd2d335e51a935_u64) + put(array, 0xde8b2b66b3bc4723_u64); put(array, 0xad2c788035e61382_u64) + put(array, 0x8b16fb203055ac76_u64); put(array, 0x4c3bcb5021afcc31_u64) + put(array, 0xaddcb9e83c6b1793_u64); put(array, 0xdf4abe242a1bbf3d_u64) + put(array, 0xd953e8624b85dd78_u64); put(array, 0xd71d6dad34a2af0d_u64) + put(array, 0x87d4713d6f33aa6b_u64); put(array, 0x8672648c40e5ad68_u64) + put(array, 0xa9c98d8ccb009506_u64); put(array, 0x680efdaf511f18c2_u64) + put(array, 0xd43bf0effdc0ba48_u64); put(array, 0x212bd1b2566def2_u64) + put(array, 0x84a57695fe98746d_u64); put(array, 0x14bb630f7604b57_u64) + put(array, 0xa5ced43b7e3e9188_u64); put(array, 0x419ea3bd35385e2d_u64) + put(array, 0xcf42894a5dce35ea_u64); put(array, 0x52064cac828675b9_u64) + put(array, 0x818995ce7aa0e1b2_u64); put(array, 0x7343efebd1940993_u64) + put(array, 0xa1ebfb4219491a1f_u64); put(array, 0x1014ebe6c5f90bf8_u64) + put(array, 0xca66fa129f9b60a6_u64); put(array, 0xd41a26e077774ef6_u64) + put(array, 0xfd00b897478238d0_u64); put(array, 0x8920b098955522b4_u64) + put(array, 0x9e20735e8cb16382_u64); put(array, 0x55b46e5f5d5535b0_u64) + put(array, 0xc5a890362fddbc62_u64); put(array, 0xeb2189f734aa831d_u64) + put(array, 0xf712b443bbd52b7b_u64); put(array, 0xa5e9ec7501d523e4_u64) + put(array, 0x9a6bb0aa55653b2d_u64); put(array, 0x47b233c92125366e_u64) + put(array, 0xc1069cd4eabe89f8_u64); put(array, 0x999ec0bb696e840a_u64) + put(array, 0xf148440a256e2c76_u64); put(array, 0xc00670ea43ca250d_u64) + put(array, 0x96cd2a865764dbca_u64); put(array, 0x380406926a5e5728_u64) + put(array, 0xbc807527ed3e12bc_u64); put(array, 0xc605083704f5ecf2_u64) + put(array, 0xeba09271e88d976b_u64); put(array, 0xf7864a44c633682e_u64) + put(array, 0x93445b8731587ea3_u64); put(array, 0x7ab3ee6afbe0211d_u64) + put(array, 0xb8157268fdae9e4c_u64); put(array, 0x5960ea05bad82964_u64) + put(array, 0xe61acf033d1a45df_u64); put(array, 0x6fb92487298e33bd_u64) + put(array, 0x8fd0c16206306bab_u64); put(array, 0xa5d3b6d479f8e056_u64) + put(array, 0xb3c4f1ba87bc8696_u64); put(array, 0x8f48a4899877186c_u64) + put(array, 0xe0b62e2929aba83c_u64); put(array, 0x331acdabfe94de87_u64) + put(array, 0x8c71dcd9ba0b4925_u64); put(array, 0x9ff0c08b7f1d0b14_u64) + put(array, 0xaf8e5410288e1b6f_u64); put(array, 0x7ecf0ae5ee44dd9_u64) + put(array, 0xdb71e91432b1a24a_u64); put(array, 0xc9e82cd9f69d6150_u64) + put(array, 0x892731ac9faf056e_u64); put(array, 0xbe311c083a225cd2_u64) + put(array, 0xab70fe17c79ac6ca_u64); put(array, 0x6dbd630a48aaf406_u64) + put(array, 0xd64d3d9db981787d_u64); put(array, 0x92cbbccdad5b108_u64) + put(array, 0x85f0468293f0eb4e_u64); put(array, 0x25bbf56008c58ea5_u64) + put(array, 0xa76c582338ed2621_u64); put(array, 0xaf2af2b80af6f24e_u64) + put(array, 0xd1476e2c07286faa_u64); put(array, 0x1af5af660db4aee1_u64) + put(array, 0x82cca4db847945ca_u64); put(array, 0x50d98d9fc890ed4d_u64) + put(array, 0xa37fce126597973c_u64); put(array, 0xe50ff107bab528a0_u64) + put(array, 0xcc5fc196fefd7d0c_u64); put(array, 0x1e53ed49a96272c8_u64) + put(array, 0xff77b1fcbebcdc4f_u64); put(array, 0x25e8e89c13bb0f7a_u64) + put(array, 0x9faacf3df73609b1_u64); put(array, 0x77b191618c54e9ac_u64) + put(array, 0xc795830d75038c1d_u64); put(array, 0xd59df5b9ef6a2417_u64) + put(array, 0xf97ae3d0d2446f25_u64); put(array, 0x4b0573286b44ad1d_u64) + put(array, 0x9becce62836ac577_u64); put(array, 0x4ee367f9430aec32_u64) + put(array, 0xc2e801fb244576d5_u64); put(array, 0x229c41f793cda73f_u64) + put(array, 0xf3a20279ed56d48a_u64); put(array, 0x6b43527578c1110f_u64) + put(array, 0x9845418c345644d6_u64); put(array, 0x830a13896b78aaa9_u64) + put(array, 0xbe5691ef416bd60c_u64); put(array, 0x23cc986bc656d553_u64) + put(array, 0xedec366b11c6cb8f_u64); put(array, 0x2cbfbe86b7ec8aa8_u64) + put(array, 0x94b3a202eb1c3f39_u64); put(array, 0x7bf7d71432f3d6a9_u64) + put(array, 0xb9e08a83a5e34f07_u64); put(array, 0xdaf5ccd93fb0cc53_u64) + put(array, 0xe858ad248f5c22c9_u64); put(array, 0xd1b3400f8f9cff68_u64) + put(array, 0x91376c36d99995be_u64); put(array, 0x23100809b9c21fa1_u64) + put(array, 0xb58547448ffffb2d_u64); put(array, 0xabd40a0c2832a78a_u64) + put(array, 0xe2e69915b3fff9f9_u64); put(array, 0x16c90c8f323f516c_u64) + put(array, 0x8dd01fad907ffc3b_u64); put(array, 0xae3da7d97f6792e3_u64) + put(array, 0xb1442798f49ffb4a_u64); put(array, 0x99cd11cfdf41779c_u64) + put(array, 0xdd95317f31c7fa1d_u64); put(array, 0x40405643d711d583_u64) + put(array, 0x8a7d3eef7f1cfc52_u64); put(array, 0x482835ea666b2572_u64) + put(array, 0xad1c8eab5ee43b66_u64); put(array, 0xda3243650005eecf_u64) + put(array, 0xd863b256369d4a40_u64); put(array, 0x90bed43e40076a82_u64) + put(array, 0x873e4f75e2224e68_u64); put(array, 0x5a7744a6e804a291_u64) + put(array, 0xa90de3535aaae202_u64); put(array, 0x711515d0a205cb36_u64) + put(array, 0xd3515c2831559a83_u64); put(array, 0xd5a5b44ca873e03_u64) + put(array, 0x8412d9991ed58091_u64); put(array, 0xe858790afe9486c2_u64) + put(array, 0xa5178fff668ae0b6_u64); put(array, 0x626e974dbe39a872_u64) + put(array, 0xce5d73ff402d98e3_u64); put(array, 0xfb0a3d212dc8128f_u64) + put(array, 0x80fa687f881c7f8e_u64); put(array, 0x7ce66634bc9d0b99_u64) + put(array, 0xa139029f6a239f72_u64); put(array, 0x1c1fffc1ebc44e80_u64) + put(array, 0xc987434744ac874e_u64); put(array, 0xa327ffb266b56220_u64) + put(array, 0xfbe9141915d7a922_u64); put(array, 0x4bf1ff9f0062baa8_u64) + put(array, 0x9d71ac8fada6c9b5_u64); put(array, 0x6f773fc3603db4a9_u64) + put(array, 0xc4ce17b399107c22_u64); put(array, 0xcb550fb4384d21d3_u64) + put(array, 0xf6019da07f549b2b_u64); put(array, 0x7e2a53a146606a48_u64) + put(array, 0x99c102844f94e0fb_u64); put(array, 0x2eda7444cbfc426d_u64) + put(array, 0xc0314325637a1939_u64); put(array, 0xfa911155fefb5308_u64) + put(array, 0xf03d93eebc589f88_u64); put(array, 0x793555ab7eba27ca_u64) + put(array, 0x96267c7535b763b5_u64); put(array, 0x4bc1558b2f3458de_u64) + put(array, 0xbbb01b9283253ca2_u64); put(array, 0x9eb1aaedfb016f16_u64) + put(array, 0xea9c227723ee8bcb_u64); put(array, 0x465e15a979c1cadc_u64) + put(array, 0x92a1958a7675175f_u64); put(array, 0xbfacd89ec191ec9_u64) + put(array, 0xb749faed14125d36_u64); put(array, 0xcef980ec671f667b_u64) + put(array, 0xe51c79a85916f484_u64); put(array, 0x82b7e12780e7401a_u64) + put(array, 0x8f31cc0937ae58d2_u64); put(array, 0xd1b2ecb8b0908810_u64) + put(array, 0xb2fe3f0b8599ef07_u64); put(array, 0x861fa7e6dcb4aa15_u64) + put(array, 0xdfbdcece67006ac9_u64); put(array, 0x67a791e093e1d49a_u64) + put(array, 0x8bd6a141006042bd_u64); put(array, 0xe0c8bb2c5c6d24e0_u64) + put(array, 0xaecc49914078536d_u64); put(array, 0x58fae9f773886e18_u64) + put(array, 0xda7f5bf590966848_u64); put(array, 0xaf39a475506a899e_u64) + put(array, 0x888f99797a5e012d_u64); put(array, 0x6d8406c952429603_u64) + put(array, 0xaab37fd7d8f58178_u64); put(array, 0xc8e5087ba6d33b83_u64) + put(array, 0xd5605fcdcf32e1d6_u64); put(array, 0xfb1e4a9a90880a64_u64) + put(array, 0x855c3be0a17fcd26_u64); put(array, 0x5cf2eea09a55067f_u64) + put(array, 0xa6b34ad8c9dfc06f_u64); put(array, 0xf42faa48c0ea481e_u64) + put(array, 0xd0601d8efc57b08b_u64); put(array, 0xf13b94daf124da26_u64) + put(array, 0x823c12795db6ce57_u64); put(array, 0x76c53d08d6b70858_u64) + put(array, 0xa2cb1717b52481ed_u64); put(array, 0x54768c4b0c64ca6e_u64) + put(array, 0xcb7ddcdda26da268_u64); put(array, 0xa9942f5dcf7dfd09_u64) + put(array, 0xfe5d54150b090b02_u64); put(array, 0xd3f93b35435d7c4c_u64) + put(array, 0x9efa548d26e5a6e1_u64); put(array, 0xc47bc5014a1a6daf_u64) + put(array, 0xc6b8e9b0709f109a_u64); put(array, 0x359ab6419ca1091b_u64) + put(array, 0xf867241c8cc6d4c0_u64); put(array, 0xc30163d203c94b62_u64) + put(array, 0x9b407691d7fc44f8_u64); put(array, 0x79e0de63425dcf1d_u64) + put(array, 0xc21094364dfb5636_u64); put(array, 0x985915fc12f542e4_u64) + put(array, 0xf294b943e17a2bc4_u64); put(array, 0x3e6f5b7b17b2939d_u64) + put(array, 0x979cf3ca6cec5b5a_u64); put(array, 0xa705992ceecf9c42_u64) + put(array, 0xbd8430bd08277231_u64); put(array, 0x50c6ff782a838353_u64) + put(array, 0xece53cec4a314ebd_u64); put(array, 0xa4f8bf5635246428_u64) + put(array, 0x940f4613ae5ed136_u64); put(array, 0x871b7795e136be99_u64) + put(array, 0xb913179899f68584_u64); put(array, 0x28e2557b59846e3f_u64) + put(array, 0xe757dd7ec07426e5_u64); put(array, 0x331aeada2fe589cf_u64) + put(array, 0x9096ea6f3848984f_u64); put(array, 0x3ff0d2c85def7621_u64) + put(array, 0xb4bca50b065abe63_u64); put(array, 0xfed077a756b53a9_u64) + put(array, 0xe1ebce4dc7f16dfb_u64); put(array, 0xd3e8495912c62894_u64) + put(array, 0x8d3360f09cf6e4bd_u64); put(array, 0x64712dd7abbbd95c_u64) + put(array, 0xb080392cc4349dec_u64); put(array, 0xbd8d794d96aacfb3_u64) + put(array, 0xdca04777f541c567_u64); put(array, 0xecf0d7a0fc5583a0_u64) + put(array, 0x89e42caaf9491b60_u64); put(array, 0xf41686c49db57244_u64) + put(array, 0xac5d37d5b79b6239_u64); put(array, 0x311c2875c522ced5_u64) + put(array, 0xd77485cb25823ac7_u64); put(array, 0x7d633293366b828b_u64) + put(array, 0x86a8d39ef77164bc_u64); put(array, 0xae5dff9c02033197_u64) + put(array, 0xa8530886b54dbdeb_u64); put(array, 0xd9f57f830283fdfc_u64) + put(array, 0xd267caa862a12d66_u64); put(array, 0xd072df63c324fd7b_u64) + put(array, 0x8380dea93da4bc60_u64); put(array, 0x4247cb9e59f71e6d_u64) + put(array, 0xa46116538d0deb78_u64); put(array, 0x52d9be85f074e608_u64) + put(array, 0xcd795be870516656_u64); put(array, 0x67902e276c921f8b_u64) + put(array, 0x806bd9714632dff6_u64); put(array, 0xba1cd8a3db53b6_u64) + put(array, 0xa086cfcd97bf97f3_u64); put(array, 0x80e8a40eccd228a4_u64) + put(array, 0xc8a883c0fdaf7df0_u64); put(array, 0x6122cd128006b2cd_u64) + put(array, 0xfad2a4b13d1b5d6c_u64); put(array, 0x796b805720085f81_u64) + put(array, 0x9cc3a6eec6311a63_u64); put(array, 0xcbe3303674053bb0_u64) + put(array, 0xc3f490aa77bd60fc_u64); put(array, 0xbedbfc4411068a9c_u64) + put(array, 0xf4f1b4d515acb93b_u64); put(array, 0xee92fb5515482d44_u64) + put(array, 0x991711052d8bf3c5_u64); put(array, 0x751bdd152d4d1c4a_u64) + put(array, 0xbf5cd54678eef0b6_u64); put(array, 0xd262d45a78a0635d_u64) + put(array, 0xef340a98172aace4_u64); put(array, 0x86fb897116c87c34_u64) + put(array, 0x9580869f0e7aac0e_u64); put(array, 0xd45d35e6ae3d4da0_u64) + put(array, 0xbae0a846d2195712_u64); put(array, 0x8974836059cca109_u64) + put(array, 0xe998d258869facd7_u64); put(array, 0x2bd1a438703fc94b_u64) + put(array, 0x91ff83775423cc06_u64); put(array, 0x7b6306a34627ddcf_u64) + put(array, 0xb67f6455292cbf08_u64); put(array, 0x1a3bc84c17b1d542_u64) + put(array, 0xe41f3d6a7377eeca_u64); put(array, 0x20caba5f1d9e4a93_u64) + put(array, 0x8e938662882af53e_u64); put(array, 0x547eb47b7282ee9c_u64) + put(array, 0xb23867fb2a35b28d_u64); put(array, 0xe99e619a4f23aa43_u64) + put(array, 0xdec681f9f4c31f31_u64); put(array, 0x6405fa00e2ec94d4_u64) + put(array, 0x8b3c113c38f9f37e_u64); put(array, 0xde83bc408dd3dd04_u64) + put(array, 0xae0b158b4738705e_u64); put(array, 0x9624ab50b148d445_u64) + put(array, 0xd98ddaee19068c76_u64); put(array, 0x3badd624dd9b0957_u64) + put(array, 0x87f8a8d4cfa417c9_u64); put(array, 0xe54ca5d70a80e5d6_u64) + put(array, 0xa9f6d30a038d1dbc_u64); put(array, 0x5e9fcf4ccd211f4c_u64) + put(array, 0xd47487cc8470652b_u64); put(array, 0x7647c3200069671f_u64) + put(array, 0x84c8d4dfd2c63f3b_u64); put(array, 0x29ecd9f40041e073_u64) + put(array, 0xa5fb0a17c777cf09_u64); put(array, 0xf468107100525890_u64) + put(array, 0xcf79cc9db955c2cc_u64); put(array, 0x7182148d4066eeb4_u64) + put(array, 0x81ac1fe293d599bf_u64); put(array, 0xc6f14cd848405530_u64) + put(array, 0xa21727db38cb002f_u64); put(array, 0xb8ada00e5a506a7c_u64) + put(array, 0xca9cf1d206fdc03b_u64); put(array, 0xa6d90811f0e4851c_u64) + put(array, 0xfd442e4688bd304a_u64); put(array, 0x908f4a166d1da663_u64) + put(array, 0x9e4a9cec15763e2e_u64); put(array, 0x9a598e4e043287fe_u64) + put(array, 0xc5dd44271ad3cdba_u64); put(array, 0x40eff1e1853f29fd_u64) + put(array, 0xf7549530e188c128_u64); put(array, 0xd12bee59e68ef47c_u64) + put(array, 0x9a94dd3e8cf578b9_u64); put(array, 0x82bb74f8301958ce_u64) + put(array, 0xc13a148e3032d6e7_u64); put(array, 0xe36a52363c1faf01_u64) + put(array, 0xf18899b1bc3f8ca1_u64); put(array, 0xdc44e6c3cb279ac1_u64) + put(array, 0x96f5600f15a7b7e5_u64); put(array, 0x29ab103a5ef8c0b9_u64) + put(array, 0xbcb2b812db11a5de_u64); put(array, 0x7415d448f6b6f0e7_u64) + put(array, 0xebdf661791d60f56_u64); put(array, 0x111b495b3464ad21_u64) + put(array, 0x936b9fcebb25c995_u64); put(array, 0xcab10dd900beec34_u64) + put(array, 0xb84687c269ef3bfb_u64); put(array, 0x3d5d514f40eea742_u64) + put(array, 0xe65829b3046b0afa_u64); put(array, 0xcb4a5a3112a5112_u64) + put(array, 0x8ff71a0fe2c2e6dc_u64); put(array, 0x47f0e785eaba72ab_u64) + put(array, 0xb3f4e093db73a093_u64); put(array, 0x59ed216765690f56_u64) + put(array, 0xe0f218b8d25088b8_u64); put(array, 0x306869c13ec3532c_u64) + put(array, 0x8c974f7383725573_u64); put(array, 0x1e414218c73a13fb_u64) + put(array, 0xafbd2350644eeacf_u64); put(array, 0xe5d1929ef90898fa_u64) + put(array, 0xdbac6c247d62a583_u64); put(array, 0xdf45f746b74abf39_u64) + put(array, 0x894bc396ce5da772_u64); put(array, 0x6b8bba8c328eb783_u64) + put(array, 0xab9eb47c81f5114f_u64); put(array, 0x66ea92f3f326564_u64) + put(array, 0xd686619ba27255a2_u64); put(array, 0xc80a537b0efefebd_u64) + put(array, 0x8613fd0145877585_u64); put(array, 0xbd06742ce95f5f36_u64) + put(array, 0xa798fc4196e952e7_u64); put(array, 0x2c48113823b73704_u64) + put(array, 0xd17f3b51fca3a7a0_u64); put(array, 0xf75a15862ca504c5_u64) + put(array, 0x82ef85133de648c4_u64); put(array, 0x9a984d73dbe722fb_u64) + put(array, 0xa3ab66580d5fdaf5_u64); put(array, 0xc13e60d0d2e0ebba_u64) + put(array, 0xcc963fee10b7d1b3_u64); put(array, 0x318df905079926a8_u64) + put(array, 0xffbbcfe994e5c61f_u64); put(array, 0xfdf17746497f7052_u64) + put(array, 0x9fd561f1fd0f9bd3_u64); put(array, 0xfeb6ea8bedefa633_u64) + put(array, 0xc7caba6e7c5382c8_u64); put(array, 0xfe64a52ee96b8fc0_u64) + put(array, 0xf9bd690a1b68637b_u64); put(array, 0x3dfdce7aa3c673b0_u64) + put(array, 0x9c1661a651213e2d_u64); put(array, 0x6bea10ca65c084e_u64) + put(array, 0xc31bfa0fe5698db8_u64); put(array, 0x486e494fcff30a62_u64) + put(array, 0xf3e2f893dec3f126_u64); put(array, 0x5a89dba3c3efccfa_u64) + put(array, 0x986ddb5c6b3a76b7_u64); put(array, 0xf89629465a75e01c_u64) + put(array, 0xbe89523386091465_u64); put(array, 0xf6bbb397f1135823_u64) + put(array, 0xee2ba6c0678b597f_u64); put(array, 0x746aa07ded582e2c_u64) + put(array, 0x94db483840b717ef_u64); put(array, 0xa8c2a44eb4571cdc_u64) + put(array, 0xba121a4650e4ddeb_u64); put(array, 0x92f34d62616ce413_u64) + put(array, 0xe896a0d7e51e1566_u64); put(array, 0x77b020baf9c81d17_u64) + put(array, 0x915e2486ef32cd60_u64); put(array, 0xace1474dc1d122e_u64) + put(array, 0xb5b5ada8aaff80b8_u64); put(array, 0xd819992132456ba_u64) + put(array, 0xe3231912d5bf60e6_u64); put(array, 0x10e1fff697ed6c69_u64) + put(array, 0x8df5efabc5979c8f_u64); put(array, 0xca8d3ffa1ef463c1_u64) + put(array, 0xb1736b96b6fd83b3_u64); put(array, 0xbd308ff8a6b17cb2_u64) + put(array, 0xddd0467c64bce4a0_u64); put(array, 0xac7cb3f6d05ddbde_u64) + put(array, 0x8aa22c0dbef60ee4_u64); put(array, 0x6bcdf07a423aa96b_u64) + put(array, 0xad4ab7112eb3929d_u64); put(array, 0x86c16c98d2c953c6_u64) + put(array, 0xd89d64d57a607744_u64); put(array, 0xe871c7bf077ba8b7_u64) + put(array, 0x87625f056c7c4a8b_u64); put(array, 0x11471cd764ad4972_u64) + put(array, 0xa93af6c6c79b5d2d_u64); put(array, 0xd598e40d3dd89bcf_u64) + put(array, 0xd389b47879823479_u64); put(array, 0x4aff1d108d4ec2c3_u64) + put(array, 0x843610cb4bf160cb_u64); put(array, 0xcedf722a585139ba_u64) + put(array, 0xa54394fe1eedb8fe_u64); put(array, 0xc2974eb4ee658828_u64) + put(array, 0xce947a3da6a9273e_u64); put(array, 0x733d226229feea32_u64) + put(array, 0x811ccc668829b887_u64); put(array, 0x806357d5a3f525f_u64) + put(array, 0xa163ff802a3426a8_u64); put(array, 0xca07c2dcb0cf26f7_u64) + put(array, 0xc9bcff6034c13052_u64); put(array, 0xfc89b393dd02f0b5_u64) + put(array, 0xfc2c3f3841f17c67_u64); put(array, 0xbbac2078d443ace2_u64) + put(array, 0x9d9ba7832936edc0_u64); put(array, 0xd54b944b84aa4c0d_u64) + put(array, 0xc5029163f384a931_u64); put(array, 0xa9e795e65d4df11_u64) + put(array, 0xf64335bcf065d37d_u64); put(array, 0x4d4617b5ff4a16d5_u64) + put(array, 0x99ea0196163fa42e_u64); put(array, 0x504bced1bf8e4e45_u64) + put(array, 0xc06481fb9bcf8d39_u64); put(array, 0xe45ec2862f71e1d6_u64) + put(array, 0xf07da27a82c37088_u64); put(array, 0x5d767327bb4e5a4c_u64) + put(array, 0x964e858c91ba2655_u64); put(array, 0x3a6a07f8d510f86f_u64) + put(array, 0xbbe226efb628afea_u64); put(array, 0x890489f70a55368b_u64) + put(array, 0xeadab0aba3b2dbe5_u64); put(array, 0x2b45ac74ccea842e_u64) + put(array, 0x92c8ae6b464fc96f_u64); put(array, 0x3b0b8bc90012929d_u64) + put(array, 0xb77ada0617e3bbcb_u64); put(array, 0x9ce6ebb40173744_u64) + put(array, 0xe55990879ddcaabd_u64); put(array, 0xcc420a6a101d0515_u64) + put(array, 0x8f57fa54c2a9eab6_u64); put(array, 0x9fa946824a12232d_u64) + put(array, 0xb32df8e9f3546564_u64); put(array, 0x47939822dc96abf9_u64) + put(array, 0xdff9772470297ebd_u64); put(array, 0x59787e2b93bc56f7_u64) + put(array, 0x8bfbea76c619ef36_u64); put(array, 0x57eb4edb3c55b65a_u64) + put(array, 0xaefae51477a06b03_u64); put(array, 0xede622920b6b23f1_u64) + put(array, 0xdab99e59958885c4_u64); put(array, 0xe95fab368e45eced_u64) + put(array, 0x88b402f7fd75539b_u64); put(array, 0x11dbcb0218ebb414_u64) + put(array, 0xaae103b5fcd2a881_u64); put(array, 0xd652bdc29f26a119_u64) + put(array, 0xd59944a37c0752a2_u64); put(array, 0x4be76d3346f0495f_u64) + put(array, 0x857fcae62d8493a5_u64); put(array, 0x6f70a4400c562ddb_u64) + put(array, 0xa6dfbd9fb8e5b88e_u64); put(array, 0xcb4ccd500f6bb952_u64) + put(array, 0xd097ad07a71f26b2_u64); put(array, 0x7e2000a41346a7a7_u64) + put(array, 0x825ecc24c873782f_u64); put(array, 0x8ed400668c0c28c8_u64) + put(array, 0xa2f67f2dfa90563b_u64); put(array, 0x728900802f0f32fa_u64) + put(array, 0xcbb41ef979346bca_u64); put(array, 0x4f2b40a03ad2ffb9_u64) + put(array, 0xfea126b7d78186bc_u64); put(array, 0xe2f610c84987bfa8_u64) + put(array, 0x9f24b832e6b0f436_u64); put(array, 0xdd9ca7d2df4d7c9_u64) + put(array, 0xc6ede63fa05d3143_u64); put(array, 0x91503d1c79720dbb_u64) + put(array, 0xf8a95fcf88747d94_u64); put(array, 0x75a44c6397ce912a_u64) + put(array, 0x9b69dbe1b548ce7c_u64); put(array, 0xc986afbe3ee11aba_u64) + put(array, 0xc24452da229b021b_u64); put(array, 0xfbe85badce996168_u64) + put(array, 0xf2d56790ab41c2a2_u64); put(array, 0xfae27299423fb9c3_u64) + put(array, 0x97c560ba6b0919a5_u64); put(array, 0xdccd879fc967d41a_u64) + put(array, 0xbdb6b8e905cb600f_u64); put(array, 0x5400e987bbc1c920_u64) + put(array, 0xed246723473e3813_u64); put(array, 0x290123e9aab23b68_u64) + put(array, 0x9436c0760c86e30b_u64); put(array, 0xf9a0b6720aaf6521_u64) + put(array, 0xb94470938fa89bce_u64); put(array, 0xf808e40e8d5b3e69_u64) + put(array, 0xe7958cb87392c2c2_u64); put(array, 0xb60b1d1230b20e04_u64) + put(array, 0x90bd77f3483bb9b9_u64); put(array, 0xb1c6f22b5e6f48c2_u64) + put(array, 0xb4ecd5f01a4aa828_u64); put(array, 0x1e38aeb6360b1af3_u64) + put(array, 0xe2280b6c20dd5232_u64); put(array, 0x25c6da63c38de1b0_u64) + put(array, 0x8d590723948a535f_u64); put(array, 0x579c487e5a38ad0e_u64) + put(array, 0xb0af48ec79ace837_u64); put(array, 0x2d835a9df0c6d851_u64) + put(array, 0xdcdb1b2798182244_u64); put(array, 0xf8e431456cf88e65_u64) + put(array, 0x8a08f0f8bf0f156b_u64); put(array, 0x1b8e9ecb641b58ff_u64) + put(array, 0xac8b2d36eed2dac5_u64); put(array, 0xe272467e3d222f3f_u64) + put(array, 0xd7adf884aa879177_u64); put(array, 0x5b0ed81dcc6abb0f_u64) + put(array, 0x86ccbb52ea94baea_u64); put(array, 0x98e947129fc2b4e9_u64) + put(array, 0xa87fea27a539e9a5_u64); put(array, 0x3f2398d747b36224_u64) + put(array, 0xd29fe4b18e88640e_u64); put(array, 0x8eec7f0d19a03aad_u64) + put(array, 0x83a3eeeef9153e89_u64); put(array, 0x1953cf68300424ac_u64) + put(array, 0xa48ceaaab75a8e2b_u64); put(array, 0x5fa8c3423c052dd7_u64) + put(array, 0xcdb02555653131b6_u64); put(array, 0x3792f412cb06794d_u64) + put(array, 0x808e17555f3ebf11_u64); put(array, 0xe2bbd88bbee40bd0_u64) + put(array, 0xa0b19d2ab70e6ed6_u64); put(array, 0x5b6aceaeae9d0ec4_u64) + put(array, 0xc8de047564d20a8b_u64); put(array, 0xf245825a5a445275_u64) + put(array, 0xfb158592be068d2e_u64); put(array, 0xeed6e2f0f0d56712_u64) + put(array, 0x9ced737bb6c4183d_u64); put(array, 0x55464dd69685606b_u64) + put(array, 0xc428d05aa4751e4c_u64); put(array, 0xaa97e14c3c26b886_u64) + put(array, 0xf53304714d9265df_u64); put(array, 0xd53dd99f4b3066a8_u64) + put(array, 0x993fe2c6d07b7fab_u64); put(array, 0xe546a8038efe4029_u64) + put(array, 0xbf8fdb78849a5f96_u64); put(array, 0xde98520472bdd033_u64) + put(array, 0xef73d256a5c0f77c_u64); put(array, 0x963e66858f6d4440_u64) + put(array, 0x95a8637627989aad_u64); put(array, 0xdde7001379a44aa8_u64) + put(array, 0xbb127c53b17ec159_u64); put(array, 0x5560c018580d5d52_u64) + put(array, 0xe9d71b689dde71af_u64); put(array, 0xaab8f01e6e10b4a6_u64) + put(array, 0x9226712162ab070d_u64); put(array, 0xcab3961304ca70e8_u64) + put(array, 0xb6b00d69bb55c8d1_u64); put(array, 0x3d607b97c5fd0d22_u64) + put(array, 0xe45c10c42a2b3b05_u64); put(array, 0x8cb89a7db77c506a_u64) + put(array, 0x8eb98a7a9a5b04e3_u64); put(array, 0x77f3608e92adb242_u64) + put(array, 0xb267ed1940f1c61c_u64); put(array, 0x55f038b237591ed3_u64) + put(array, 0xdf01e85f912e37a3_u64); put(array, 0x6b6c46dec52f6688_u64) + put(array, 0x8b61313bbabce2c6_u64); put(array, 0x2323ac4b3b3da015_u64) + put(array, 0xae397d8aa96c1b77_u64); put(array, 0xabec975e0a0d081a_u64) + put(array, 0xd9c7dced53c72255_u64); put(array, 0x96e7bd358c904a21_u64) + put(array, 0x881cea14545c7575_u64); put(array, 0x7e50d64177da2e54_u64) + put(array, 0xaa242499697392d2_u64); put(array, 0xdde50bd1d5d0b9e9_u64) + put(array, 0xd4ad2dbfc3d07787_u64); put(array, 0x955e4ec64b44e864_u64) + put(array, 0x84ec3c97da624ab4_u64); put(array, 0xbd5af13bef0b113e_u64) + put(array, 0xa6274bbdd0fadd61_u64); put(array, 0xecb1ad8aeacdd58e_u64) + put(array, 0xcfb11ead453994ba_u64); put(array, 0x67de18eda5814af2_u64) + put(array, 0x81ceb32c4b43fcf4_u64); put(array, 0x80eacf948770ced7_u64) + put(array, 0xa2425ff75e14fc31_u64); put(array, 0xa1258379a94d028d_u64) + put(array, 0xcad2f7f5359a3b3e_u64); put(array, 0x96ee45813a04330_u64) + put(array, 0xfd87b5f28300ca0d_u64); put(array, 0x8bca9d6e188853fc_u64) + put(array, 0x9e74d1b791e07e48_u64); put(array, 0x775ea264cf55347e_u64) + put(array, 0xc612062576589dda_u64); put(array, 0x95364afe032a819e_u64) + put(array, 0xf79687aed3eec551_u64); put(array, 0x3a83ddbd83f52205_u64) + put(array, 0x9abe14cd44753b52_u64); put(array, 0xc4926a9672793543_u64) + put(array, 0xc16d9a0095928a27_u64); put(array, 0x75b7053c0f178294_u64) + put(array, 0xf1c90080baf72cb1_u64); put(array, 0x5324c68b12dd6339_u64) + put(array, 0x971da05074da7bee_u64); put(array, 0xd3f6fc16ebca5e04_u64) + put(array, 0xbce5086492111aea_u64); put(array, 0x88f4bb1ca6bcf585_u64) + put(array, 0xec1e4a7db69561a5_u64); put(array, 0x2b31e9e3d06c32e6_u64) + put(array, 0x9392ee8e921d5d07_u64); put(array, 0x3aff322e62439fd0_u64) + put(array, 0xb877aa3236a4b449_u64); put(array, 0x9befeb9fad487c3_u64) + put(array, 0xe69594bec44de15b_u64); put(array, 0x4c2ebe687989a9b4_u64) + put(array, 0x901d7cf73ab0acd9_u64); put(array, 0xf9d37014bf60a11_u64) + put(array, 0xb424dc35095cd80f_u64); put(array, 0x538484c19ef38c95_u64) + put(array, 0xe12e13424bb40e13_u64); put(array, 0x2865a5f206b06fba_u64) + put(array, 0x8cbccc096f5088cb_u64); put(array, 0xf93f87b7442e45d4_u64) + put(array, 0xafebff0bcb24aafe_u64); put(array, 0xf78f69a51539d749_u64) + put(array, 0xdbe6fecebdedd5be_u64); put(array, 0xb573440e5a884d1c_u64) + put(array, 0x89705f4136b4a597_u64); put(array, 0x31680a88f8953031_u64) + put(array, 0xabcc77118461cefc_u64); put(array, 0xfdc20d2b36ba7c3e_u64) + put(array, 0xd6bf94d5e57a42bc_u64); put(array, 0x3d32907604691b4d_u64) + put(array, 0x8637bd05af6c69b5_u64); put(array, 0xa63f9a49c2c1b110_u64) + put(array, 0xa7c5ac471b478423_u64); put(array, 0xfcf80dc33721d54_u64) + put(array, 0xd1b71758e219652b_u64); put(array, 0xd3c36113404ea4a9_u64) + put(array, 0x83126e978d4fdf3b_u64); put(array, 0x645a1cac083126ea_u64) + put(array, 0xa3d70a3d70a3d70a_u64); put(array, 0x3d70a3d70a3d70a4_u64) + put(array, 0xcccccccccccccccc_u64); put(array, 0xcccccccccccccccd_u64) + put(array, 0x8000000000000000_u64); put(array, 0x0_u64) + put(array, 0xa000000000000000_u64); put(array, 0x0_u64) + put(array, 0xc800000000000000_u64); put(array, 0x0_u64) + put(array, 0xfa00000000000000_u64); put(array, 0x0_u64) + put(array, 0x9c40000000000000_u64); put(array, 0x0_u64) + put(array, 0xc350000000000000_u64); put(array, 0x0_u64) + put(array, 0xf424000000000000_u64); put(array, 0x0_u64) + put(array, 0x9896800000000000_u64); put(array, 0x0_u64) + put(array, 0xbebc200000000000_u64); put(array, 0x0_u64) + put(array, 0xee6b280000000000_u64); put(array, 0x0_u64) + put(array, 0x9502f90000000000_u64); put(array, 0x0_u64) + put(array, 0xba43b74000000000_u64); put(array, 0x0_u64) + put(array, 0xe8d4a51000000000_u64); put(array, 0x0_u64) + put(array, 0x9184e72a00000000_u64); put(array, 0x0_u64) + put(array, 0xb5e620f480000000_u64); put(array, 0x0_u64) + put(array, 0xe35fa931a0000000_u64); put(array, 0x0_u64) + put(array, 0x8e1bc9bf04000000_u64); put(array, 0x0_u64) + put(array, 0xb1a2bc2ec5000000_u64); put(array, 0x0_u64) + put(array, 0xde0b6b3a76400000_u64); put(array, 0x0_u64) + put(array, 0x8ac7230489e80000_u64); put(array, 0x0_u64) + put(array, 0xad78ebc5ac620000_u64); put(array, 0x0_u64) + put(array, 0xd8d726b7177a8000_u64); put(array, 0x0_u64) + put(array, 0x878678326eac9000_u64); put(array, 0x0_u64) + put(array, 0xa968163f0a57b400_u64); put(array, 0x0_u64) + put(array, 0xd3c21bcecceda100_u64); put(array, 0x0_u64) + put(array, 0x84595161401484a0_u64); put(array, 0x0_u64) + put(array, 0xa56fa5b99019a5c8_u64); put(array, 0x0_u64) + put(array, 0xcecb8f27f4200f3a_u64); put(array, 0x0_u64) + put(array, 0x813f3978f8940984_u64); put(array, 0x4000000000000000_u64) + put(array, 0xa18f07d736b90be5_u64); put(array, 0x5000000000000000_u64) + put(array, 0xc9f2c9cd04674ede_u64); put(array, 0xa400000000000000_u64) + put(array, 0xfc6f7c4045812296_u64); put(array, 0x4d00000000000000_u64) + put(array, 0x9dc5ada82b70b59d_u64); put(array, 0xf020000000000000_u64) + put(array, 0xc5371912364ce305_u64); put(array, 0x6c28000000000000_u64) + put(array, 0xf684df56c3e01bc6_u64); put(array, 0xc732000000000000_u64) + put(array, 0x9a130b963a6c115c_u64); put(array, 0x3c7f400000000000_u64) + put(array, 0xc097ce7bc90715b3_u64); put(array, 0x4b9f100000000000_u64) + put(array, 0xf0bdc21abb48db20_u64); put(array, 0x1e86d40000000000_u64) + put(array, 0x96769950b50d88f4_u64); put(array, 0x1314448000000000_u64) + put(array, 0xbc143fa4e250eb31_u64); put(array, 0x17d955a000000000_u64) + put(array, 0xeb194f8e1ae525fd_u64); put(array, 0x5dcfab0800000000_u64) + put(array, 0x92efd1b8d0cf37be_u64); put(array, 0x5aa1cae500000000_u64) + put(array, 0xb7abc627050305ad_u64); put(array, 0xf14a3d9e40000000_u64) + put(array, 0xe596b7b0c643c719_u64); put(array, 0x6d9ccd05d0000000_u64) + put(array, 0x8f7e32ce7bea5c6f_u64); put(array, 0xe4820023a2000000_u64) + put(array, 0xb35dbf821ae4f38b_u64); put(array, 0xdda2802c8a800000_u64) + put(array, 0xe0352f62a19e306e_u64); put(array, 0xd50b2037ad200000_u64) + put(array, 0x8c213d9da502de45_u64); put(array, 0x4526f422cc340000_u64) + put(array, 0xaf298d050e4395d6_u64); put(array, 0x9670b12b7f410000_u64) + put(array, 0xdaf3f04651d47b4c_u64); put(array, 0x3c0cdd765f114000_u64) + put(array, 0x88d8762bf324cd0f_u64); put(array, 0xa5880a69fb6ac800_u64) + put(array, 0xab0e93b6efee0053_u64); put(array, 0x8eea0d047a457a00_u64) + put(array, 0xd5d238a4abe98068_u64); put(array, 0x72a4904598d6d880_u64) + put(array, 0x85a36366eb71f041_u64); put(array, 0x47a6da2b7f864750_u64) + put(array, 0xa70c3c40a64e6c51_u64); put(array, 0x999090b65f67d924_u64) + put(array, 0xd0cf4b50cfe20765_u64); put(array, 0xfff4b4e3f741cf6d_u64) + put(array, 0x82818f1281ed449f_u64); put(array, 0xbff8f10e7a8921a4_u64) + put(array, 0xa321f2d7226895c7_u64); put(array, 0xaff72d52192b6a0d_u64) + put(array, 0xcbea6f8ceb02bb39_u64); put(array, 0x9bf4f8a69f764490_u64) + put(array, 0xfee50b7025c36a08_u64); put(array, 0x2f236d04753d5b4_u64) + put(array, 0x9f4f2726179a2245_u64); put(array, 0x1d762422c946590_u64) + put(array, 0xc722f0ef9d80aad6_u64); put(array, 0x424d3ad2b7b97ef5_u64) + put(array, 0xf8ebad2b84e0d58b_u64); put(array, 0xd2e0898765a7deb2_u64) + put(array, 0x9b934c3b330c8577_u64); put(array, 0x63cc55f49f88eb2f_u64) + put(array, 0xc2781f49ffcfa6d5_u64); put(array, 0x3cbf6b71c76b25fb_u64) + put(array, 0xf316271c7fc3908a_u64); put(array, 0x8bef464e3945ef7a_u64) + put(array, 0x97edd871cfda3a56_u64); put(array, 0x97758bf0e3cbb5ac_u64) + put(array, 0xbde94e8e43d0c8ec_u64); put(array, 0x3d52eeed1cbea317_u64) + put(array, 0xed63a231d4c4fb27_u64); put(array, 0x4ca7aaa863ee4bdd_u64) + put(array, 0x945e455f24fb1cf8_u64); put(array, 0x8fe8caa93e74ef6a_u64) + put(array, 0xb975d6b6ee39e436_u64); put(array, 0xb3e2fd538e122b44_u64) + put(array, 0xe7d34c64a9c85d44_u64); put(array, 0x60dbbca87196b616_u64) + put(array, 0x90e40fbeea1d3a4a_u64); put(array, 0xbc8955e946fe31cd_u64) + put(array, 0xb51d13aea4a488dd_u64); put(array, 0x6babab6398bdbe41_u64) + put(array, 0xe264589a4dcdab14_u64); put(array, 0xc696963c7eed2dd1_u64) + put(array, 0x8d7eb76070a08aec_u64); put(array, 0xfc1e1de5cf543ca2_u64) + put(array, 0xb0de65388cc8ada8_u64); put(array, 0x3b25a55f43294bcb_u64) + put(array, 0xdd15fe86affad912_u64); put(array, 0x49ef0eb713f39ebe_u64) + put(array, 0x8a2dbf142dfcc7ab_u64); put(array, 0x6e3569326c784337_u64) + put(array, 0xacb92ed9397bf996_u64); put(array, 0x49c2c37f07965404_u64) + put(array, 0xd7e77a8f87daf7fb_u64); put(array, 0xdc33745ec97be906_u64) + put(array, 0x86f0ac99b4e8dafd_u64); put(array, 0x69a028bb3ded71a3_u64) + put(array, 0xa8acd7c0222311bc_u64); put(array, 0xc40832ea0d68ce0c_u64) + put(array, 0xd2d80db02aabd62b_u64); put(array, 0xf50a3fa490c30190_u64) + put(array, 0x83c7088e1aab65db_u64); put(array, 0x792667c6da79e0fa_u64) + put(array, 0xa4b8cab1a1563f52_u64); put(array, 0x577001b891185938_u64) + put(array, 0xcde6fd5e09abcf26_u64); put(array, 0xed4c0226b55e6f86_u64) + put(array, 0x80b05e5ac60b6178_u64); put(array, 0x544f8158315b05b4_u64) + put(array, 0xa0dc75f1778e39d6_u64); put(array, 0x696361ae3db1c721_u64) + put(array, 0xc913936dd571c84c_u64); put(array, 0x3bc3a19cd1e38e9_u64) + put(array, 0xfb5878494ace3a5f_u64); put(array, 0x4ab48a04065c723_u64) + put(array, 0x9d174b2dcec0e47b_u64); put(array, 0x62eb0d64283f9c76_u64) + put(array, 0xc45d1df942711d9a_u64); put(array, 0x3ba5d0bd324f8394_u64) + put(array, 0xf5746577930d6500_u64); put(array, 0xca8f44ec7ee36479_u64) + put(array, 0x9968bf6abbe85f20_u64); put(array, 0x7e998b13cf4e1ecb_u64) + put(array, 0xbfc2ef456ae276e8_u64); put(array, 0x9e3fedd8c321a67e_u64) + put(array, 0xefb3ab16c59b14a2_u64); put(array, 0xc5cfe94ef3ea101e_u64) + put(array, 0x95d04aee3b80ece5_u64); put(array, 0xbba1f1d158724a12_u64) + put(array, 0xbb445da9ca61281f_u64); put(array, 0x2a8a6e45ae8edc97_u64) + put(array, 0xea1575143cf97226_u64); put(array, 0xf52d09d71a3293bd_u64) + put(array, 0x924d692ca61be758_u64); put(array, 0x593c2626705f9c56_u64) + put(array, 0xb6e0c377cfa2e12e_u64); put(array, 0x6f8b2fb00c77836c_u64) + put(array, 0xe498f455c38b997a_u64); put(array, 0xb6dfb9c0f956447_u64) + put(array, 0x8edf98b59a373fec_u64); put(array, 0x4724bd4189bd5eac_u64) + put(array, 0xb2977ee300c50fe7_u64); put(array, 0x58edec91ec2cb657_u64) + put(array, 0xdf3d5e9bc0f653e1_u64); put(array, 0x2f2967b66737e3ed_u64) + put(array, 0x8b865b215899f46c_u64); put(array, 0xbd79e0d20082ee74_u64) + put(array, 0xae67f1e9aec07187_u64); put(array, 0xecd8590680a3aa11_u64) + put(array, 0xda01ee641a708de9_u64); put(array, 0xe80e6f4820cc9495_u64) + put(array, 0x884134fe908658b2_u64); put(array, 0x3109058d147fdcdd_u64) + put(array, 0xaa51823e34a7eede_u64); put(array, 0xbd4b46f0599fd415_u64) + put(array, 0xd4e5e2cdc1d1ea96_u64); put(array, 0x6c9e18ac7007c91a_u64) + put(array, 0x850fadc09923329e_u64); put(array, 0x3e2cf6bc604ddb0_u64) + put(array, 0xa6539930bf6bff45_u64); put(array, 0x84db8346b786151c_u64) + put(array, 0xcfe87f7cef46ff16_u64); put(array, 0xe612641865679a63_u64) + put(array, 0x81f14fae158c5f6e_u64); put(array, 0x4fcb7e8f3f60c07e_u64) + put(array, 0xa26da3999aef7749_u64); put(array, 0xe3be5e330f38f09d_u64) + put(array, 0xcb090c8001ab551c_u64); put(array, 0x5cadf5bfd3072cc5_u64) + put(array, 0xfdcb4fa002162a63_u64); put(array, 0x73d9732fc7c8f7f6_u64) + put(array, 0x9e9f11c4014dda7e_u64); put(array, 0x2867e7fddcdd9afa_u64) + put(array, 0xc646d63501a1511d_u64); put(array, 0xb281e1fd541501b8_u64) + put(array, 0xf7d88bc24209a565_u64); put(array, 0x1f225a7ca91a4226_u64) + put(array, 0x9ae757596946075f_u64); put(array, 0x3375788de9b06958_u64) + put(array, 0xc1a12d2fc3978937_u64); put(array, 0x52d6b1641c83ae_u64) + put(array, 0xf209787bb47d6b84_u64); put(array, 0xc0678c5dbd23a49a_u64) + put(array, 0x9745eb4d50ce6332_u64); put(array, 0xf840b7ba963646e0_u64) + put(array, 0xbd176620a501fbff_u64); put(array, 0xb650e5a93bc3d898_u64) + put(array, 0xec5d3fa8ce427aff_u64); put(array, 0xa3e51f138ab4cebe_u64) + put(array, 0x93ba47c980e98cdf_u64); put(array, 0xc66f336c36b10137_u64) + put(array, 0xb8a8d9bbe123f017_u64); put(array, 0xb80b0047445d4184_u64) + put(array, 0xe6d3102ad96cec1d_u64); put(array, 0xa60dc059157491e5_u64) + put(array, 0x9043ea1ac7e41392_u64); put(array, 0x87c89837ad68db2f_u64) + put(array, 0xb454e4a179dd1877_u64); put(array, 0x29babe4598c311fb_u64) + put(array, 0xe16a1dc9d8545e94_u64); put(array, 0xf4296dd6fef3d67a_u64) + put(array, 0x8ce2529e2734bb1d_u64); put(array, 0x1899e4a65f58660c_u64) + put(array, 0xb01ae745b101e9e4_u64); put(array, 0x5ec05dcff72e7f8f_u64) + put(array, 0xdc21a1171d42645d_u64); put(array, 0x76707543f4fa1f73_u64) + put(array, 0x899504ae72497eba_u64); put(array, 0x6a06494a791c53a8_u64) + put(array, 0xabfa45da0edbde69_u64); put(array, 0x487db9d17636892_u64) + put(array, 0xd6f8d7509292d603_u64); put(array, 0x45a9d2845d3c42b6_u64) + put(array, 0x865b86925b9bc5c2_u64); put(array, 0xb8a2392ba45a9b2_u64) + put(array, 0xa7f26836f282b732_u64); put(array, 0x8e6cac7768d7141e_u64) + put(array, 0xd1ef0244af2364ff_u64); put(array, 0x3207d795430cd926_u64) + put(array, 0x8335616aed761f1f_u64); put(array, 0x7f44e6bd49e807b8_u64) + put(array, 0xa402b9c5a8d3a6e7_u64); put(array, 0x5f16206c9c6209a6_u64) + put(array, 0xcd036837130890a1_u64); put(array, 0x36dba887c37a8c0f_u64) + put(array, 0x802221226be55a64_u64); put(array, 0xc2494954da2c9789_u64) + put(array, 0xa02aa96b06deb0fd_u64); put(array, 0xf2db9baa10b7bd6c_u64) + put(array, 0xc83553c5c8965d3d_u64); put(array, 0x6f92829494e5acc7_u64) + put(array, 0xfa42a8b73abbf48c_u64); put(array, 0xcb772339ba1f17f9_u64) + put(array, 0x9c69a97284b578d7_u64); put(array, 0xff2a760414536efb_u64) + put(array, 0xc38413cf25e2d70d_u64); put(array, 0xfef5138519684aba_u64) + put(array, 0xf46518c2ef5b8cd1_u64); put(array, 0x7eb258665fc25d69_u64) + put(array, 0x98bf2f79d5993802_u64); put(array, 0xef2f773ffbd97a61_u64) + put(array, 0xbeeefb584aff8603_u64); put(array, 0xaafb550ffacfd8fa_u64) + put(array, 0xeeaaba2e5dbf6784_u64); put(array, 0x95ba2a53f983cf38_u64) + put(array, 0x952ab45cfa97a0b2_u64); put(array, 0xdd945a747bf26183_u64) + put(array, 0xba756174393d88df_u64); put(array, 0x94f971119aeef9e4_u64) + put(array, 0xe912b9d1478ceb17_u64); put(array, 0x7a37cd5601aab85d_u64) + put(array, 0x91abb422ccb812ee_u64); put(array, 0xac62e055c10ab33a_u64) + put(array, 0xb616a12b7fe617aa_u64); put(array, 0x577b986b314d6009_u64) + put(array, 0xe39c49765fdf9d94_u64); put(array, 0xed5a7e85fda0b80b_u64) + put(array, 0x8e41ade9fbebc27d_u64); put(array, 0x14588f13be847307_u64) + put(array, 0xb1d219647ae6b31c_u64); put(array, 0x596eb2d8ae258fc8_u64) + put(array, 0xde469fbd99a05fe3_u64); put(array, 0x6fca5f8ed9aef3bb_u64) + put(array, 0x8aec23d680043bee_u64); put(array, 0x25de7bb9480d5854_u64) + put(array, 0xada72ccc20054ae9_u64); put(array, 0xaf561aa79a10ae6a_u64) + put(array, 0xd910f7ff28069da4_u64); put(array, 0x1b2ba1518094da04_u64) + put(array, 0x87aa9aff79042286_u64); put(array, 0x90fb44d2f05d0842_u64) + put(array, 0xa99541bf57452b28_u64); put(array, 0x353a1607ac744a53_u64) + put(array, 0xd3fa922f2d1675f2_u64); put(array, 0x42889b8997915ce8_u64) + put(array, 0x847c9b5d7c2e09b7_u64); put(array, 0x69956135febada11_u64) + put(array, 0xa59bc234db398c25_u64); put(array, 0x43fab9837e699095_u64) + put(array, 0xcf02b2c21207ef2e_u64); put(array, 0x94f967e45e03f4bb_u64) + put(array, 0x8161afb94b44f57d_u64); put(array, 0x1d1be0eebac278f5_u64) + put(array, 0xa1ba1ba79e1632dc_u64); put(array, 0x6462d92a69731732_u64) + put(array, 0xca28a291859bbf93_u64); put(array, 0x7d7b8f7503cfdcfe_u64) + put(array, 0xfcb2cb35e702af78_u64); put(array, 0x5cda735244c3d43e_u64) + put(array, 0x9defbf01b061adab_u64); put(array, 0x3a0888136afa64a7_u64) + put(array, 0xc56baec21c7a1916_u64); put(array, 0x88aaa1845b8fdd0_u64) + put(array, 0xf6c69a72a3989f5b_u64); put(array, 0x8aad549e57273d45_u64) + put(array, 0x9a3c2087a63f6399_u64); put(array, 0x36ac54e2f678864b_u64) + put(array, 0xc0cb28a98fcf3c7f_u64); put(array, 0x84576a1bb416a7dd_u64) + put(array, 0xf0fdf2d3f3c30b9f_u64); put(array, 0x656d44a2a11c51d5_u64) + put(array, 0x969eb7c47859e743_u64); put(array, 0x9f644ae5a4b1b325_u64) + put(array, 0xbc4665b596706114_u64); put(array, 0x873d5d9f0dde1fee_u64) + put(array, 0xeb57ff22fc0c7959_u64); put(array, 0xa90cb506d155a7ea_u64) + put(array, 0x9316ff75dd87cbd8_u64); put(array, 0x9a7f12442d588f2_u64) + put(array, 0xb7dcbf5354e9bece_u64); put(array, 0xc11ed6d538aeb2f_u64) + put(array, 0xe5d3ef282a242e81_u64); put(array, 0x8f1668c8a86da5fa_u64) + put(array, 0x8fa475791a569d10_u64); put(array, 0xf96e017d694487bc_u64) + put(array, 0xb38d92d760ec4455_u64); put(array, 0x37c981dcc395a9ac_u64) + put(array, 0xe070f78d3927556a_u64); put(array, 0x85bbe253f47b1417_u64) + put(array, 0x8c469ab843b89562_u64); put(array, 0x93956d7478ccec8e_u64) + put(array, 0xaf58416654a6babb_u64); put(array, 0x387ac8d1970027b2_u64) + put(array, 0xdb2e51bfe9d0696a_u64); put(array, 0x6997b05fcc0319e_u64) + put(array, 0x88fcf317f22241e2_u64); put(array, 0x441fece3bdf81f03_u64) + put(array, 0xab3c2fddeeaad25a_u64); put(array, 0xd527e81cad7626c3_u64) + put(array, 0xd60b3bd56a5586f1_u64); put(array, 0x8a71e223d8d3b074_u64) + put(array, 0x85c7056562757456_u64); put(array, 0xf6872d5667844e49_u64) + put(array, 0xa738c6bebb12d16c_u64); put(array, 0xb428f8ac016561db_u64) + put(array, 0xd106f86e69d785c7_u64); put(array, 0xe13336d701beba52_u64) + put(array, 0x82a45b450226b39c_u64); put(array, 0xecc0024661173473_u64) + put(array, 0xa34d721642b06084_u64); put(array, 0x27f002d7f95d0190_u64) + put(array, 0xcc20ce9bd35c78a5_u64); put(array, 0x31ec038df7b441f4_u64) + put(array, 0xff290242c83396ce_u64); put(array, 0x7e67047175a15271_u64) + put(array, 0x9f79a169bd203e41_u64); put(array, 0xf0062c6e984d386_u64) + put(array, 0xc75809c42c684dd1_u64); put(array, 0x52c07b78a3e60868_u64) + put(array, 0xf92e0c3537826145_u64); put(array, 0xa7709a56ccdf8a82_u64) + put(array, 0x9bbcc7a142b17ccb_u64); put(array, 0x88a66076400bb691_u64) + put(array, 0xc2abf989935ddbfe_u64); put(array, 0x6acff893d00ea435_u64) + put(array, 0xf356f7ebf83552fe_u64); put(array, 0x583f6b8c4124d43_u64) + put(array, 0x98165af37b2153de_u64); put(array, 0xc3727a337a8b704a_u64) + put(array, 0xbe1bf1b059e9a8d6_u64); put(array, 0x744f18c0592e4c5c_u64) + put(array, 0xeda2ee1c7064130c_u64); put(array, 0x1162def06f79df73_u64) + put(array, 0x9485d4d1c63e8be7_u64); put(array, 0x8addcb5645ac2ba8_u64) + put(array, 0xb9a74a0637ce2ee1_u64); put(array, 0x6d953e2bd7173692_u64) + put(array, 0xe8111c87c5c1ba99_u64); put(array, 0xc8fa8db6ccdd0437_u64) + put(array, 0x910ab1d4db9914a0_u64); put(array, 0x1d9c9892400a22a2_u64) + put(array, 0xb54d5e4a127f59c8_u64); put(array, 0x2503beb6d00cab4b_u64) + put(array, 0xe2a0b5dc971f303a_u64); put(array, 0x2e44ae64840fd61d_u64) + put(array, 0x8da471a9de737e24_u64); put(array, 0x5ceaecfed289e5d2_u64) + put(array, 0xb10d8e1456105dad_u64); put(array, 0x7425a83e872c5f47_u64) + put(array, 0xdd50f1996b947518_u64); put(array, 0xd12f124e28f77719_u64) + put(array, 0x8a5296ffe33cc92f_u64); put(array, 0x82bd6b70d99aaa6f_u64) + put(array, 0xace73cbfdc0bfb7b_u64); put(array, 0x636cc64d1001550b_u64) + put(array, 0xd8210befd30efa5a_u64); put(array, 0x3c47f7e05401aa4e_u64) + put(array, 0x8714a775e3e95c78_u64); put(array, 0x65acfaec34810a71_u64) + put(array, 0xa8d9d1535ce3b396_u64); put(array, 0x7f1839a741a14d0d_u64) + put(array, 0xd31045a8341ca07c_u64); put(array, 0x1ede48111209a050_u64) + put(array, 0x83ea2b892091e44d_u64); put(array, 0x934aed0aab460432_u64) + put(array, 0xa4e4b66b68b65d60_u64); put(array, 0xf81da84d5617853f_u64) + put(array, 0xce1de40642e3f4b9_u64); put(array, 0x36251260ab9d668e_u64) + put(array, 0x80d2ae83e9ce78f3_u64); put(array, 0xc1d72b7c6b426019_u64) + put(array, 0xa1075a24e4421730_u64); put(array, 0xb24cf65b8612f81f_u64) + put(array, 0xc94930ae1d529cfc_u64); put(array, 0xdee033f26797b627_u64) + put(array, 0xfb9b7cd9a4a7443c_u64); put(array, 0x169840ef017da3b1_u64) + put(array, 0x9d412e0806e88aa5_u64); put(array, 0x8e1f289560ee864e_u64) + put(array, 0xc491798a08a2ad4e_u64); put(array, 0xf1a6f2bab92a27e2_u64) + put(array, 0xf5b5d7ec8acb58a2_u64); put(array, 0xae10af696774b1db_u64) + put(array, 0x9991a6f3d6bf1765_u64); put(array, 0xacca6da1e0a8ef29_u64) + put(array, 0xbff610b0cc6edd3f_u64); put(array, 0x17fd090a58d32af3_u64) + put(array, 0xeff394dcff8a948e_u64); put(array, 0xddfc4b4cef07f5b0_u64) + put(array, 0x95f83d0a1fb69cd9_u64); put(array, 0x4abdaf101564f98e_u64) + put(array, 0xbb764c4ca7a4440f_u64); put(array, 0x9d6d1ad41abe37f1_u64) + put(array, 0xea53df5fd18d5513_u64); put(array, 0x84c86189216dc5ed_u64) + put(array, 0x92746b9be2f8552c_u64); put(array, 0x32fd3cf5b4e49bb4_u64) + put(array, 0xb7118682dbb66a77_u64); put(array, 0x3fbc8c33221dc2a1_u64) + put(array, 0xe4d5e82392a40515_u64); put(array, 0xfabaf3feaa5334a_u64) + put(array, 0x8f05b1163ba6832d_u64); put(array, 0x29cb4d87f2a7400e_u64) + put(array, 0xb2c71d5bca9023f8_u64); put(array, 0x743e20e9ef511012_u64) + put(array, 0xdf78e4b2bd342cf6_u64); put(array, 0x914da9246b255416_u64) + put(array, 0x8bab8eefb6409c1a_u64); put(array, 0x1ad089b6c2f7548e_u64) + put(array, 0xae9672aba3d0c320_u64); put(array, 0xa184ac2473b529b1_u64) + put(array, 0xda3c0f568cc4f3e8_u64); put(array, 0xc9e5d72d90a2741e_u64) + put(array, 0x8865899617fb1871_u64); put(array, 0x7e2fa67c7a658892_u64) + put(array, 0xaa7eebfb9df9de8d_u64); put(array, 0xddbb901b98feeab7_u64) + put(array, 0xd51ea6fa85785631_u64); put(array, 0x552a74227f3ea565_u64) + put(array, 0x8533285c936b35de_u64); put(array, 0xd53a88958f87275f_u64) + put(array, 0xa67ff273b8460356_u64); put(array, 0x8a892abaf368f137_u64) + put(array, 0xd01fef10a657842c_u64); put(array, 0x2d2b7569b0432d85_u64) + put(array, 0x8213f56a67f6b29b_u64); put(array, 0x9c3b29620e29fc73_u64) + put(array, 0xa298f2c501f45f42_u64); put(array, 0x8349f3ba91b47b8f_u64) + put(array, 0xcb3f2f7642717713_u64); put(array, 0x241c70a936219a73_u64) + put(array, 0xfe0efb53d30dd4d7_u64); put(array, 0xed238cd383aa0110_u64) + put(array, 0x9ec95d1463e8a506_u64); put(array, 0xf4363804324a40aa_u64) + put(array, 0xc67bb4597ce2ce48_u64); put(array, 0xb143c6053edcd0d5_u64) + put(array, 0xf81aa16fdc1b81da_u64); put(array, 0xdd94b7868e94050a_u64) + put(array, 0x9b10a4e5e9913128_u64); put(array, 0xca7cf2b4191c8326_u64) + put(array, 0xc1d4ce1f63f57d72_u64); put(array, 0xfd1c2f611f63a3f0_u64) + put(array, 0xf24a01a73cf2dccf_u64); put(array, 0xbc633b39673c8cec_u64) + put(array, 0x976e41088617ca01_u64); put(array, 0xd5be0503e085d813_u64) + put(array, 0xbd49d14aa79dbc82_u64); put(array, 0x4b2d8644d8a74e18_u64) + put(array, 0xec9c459d51852ba2_u64); put(array, 0xddf8e7d60ed1219e_u64) + put(array, 0x93e1ab8252f33b45_u64); put(array, 0xcabb90e5c942b503_u64) + put(array, 0xb8da1662e7b00a17_u64); put(array, 0x3d6a751f3b936243_u64) + put(array, 0xe7109bfba19c0c9d_u64); put(array, 0xcc512670a783ad4_u64) + put(array, 0x906a617d450187e2_u64); put(array, 0x27fb2b80668b24c5_u64) + put(array, 0xb484f9dc9641e9da_u64); put(array, 0xb1f9f660802dedf6_u64) + put(array, 0xe1a63853bbd26451_u64); put(array, 0x5e7873f8a0396973_u64) + put(array, 0x8d07e33455637eb2_u64); put(array, 0xdb0b487b6423e1e8_u64) + put(array, 0xb049dc016abc5e5f_u64); put(array, 0x91ce1a9a3d2cda62_u64) + put(array, 0xdc5c5301c56b75f7_u64); put(array, 0x7641a140cc7810fb_u64) + put(array, 0x89b9b3e11b6329ba_u64); put(array, 0xa9e904c87fcb0a9d_u64) + put(array, 0xac2820d9623bf429_u64); put(array, 0x546345fa9fbdcd44_u64) + put(array, 0xd732290fbacaf133_u64); put(array, 0xa97c177947ad4095_u64) + put(array, 0x867f59a9d4bed6c0_u64); put(array, 0x49ed8eabcccc485d_u64) + put(array, 0xa81f301449ee8c70_u64); put(array, 0x5c68f256bfff5a74_u64) + put(array, 0xd226fc195c6a2f8c_u64); put(array, 0x73832eec6fff3111_u64) + put(array, 0x83585d8fd9c25db7_u64); put(array, 0xc831fd53c5ff7eab_u64) + put(array, 0xa42e74f3d032f525_u64); put(array, 0xba3e7ca8b77f5e55_u64) + put(array, 0xcd3a1230c43fb26f_u64); put(array, 0x28ce1bd2e55f35eb_u64) + put(array, 0x80444b5e7aa7cf85_u64); put(array, 0x7980d163cf5b81b3_u64) + put(array, 0xa0555e361951c366_u64); put(array, 0xd7e105bcc332621f_u64) + put(array, 0xc86ab5c39fa63440_u64); put(array, 0x8dd9472bf3fefaa7_u64) + put(array, 0xfa856334878fc150_u64); put(array, 0xb14f98f6f0feb951_u64) + put(array, 0x9c935e00d4b9d8d2_u64); put(array, 0x6ed1bf9a569f33d3_u64) + put(array, 0xc3b8358109e84f07_u64); put(array, 0xa862f80ec4700c8_u64) + put(array, 0xf4a642e14c6262c8_u64); put(array, 0xcd27bb612758c0fa_u64) + put(array, 0x98e7e9cccfbd7dbd_u64); put(array, 0x8038d51cb897789c_u64) + put(array, 0xbf21e44003acdd2c_u64); put(array, 0xe0470a63e6bd56c3_u64) + put(array, 0xeeea5d5004981478_u64); put(array, 0x1858ccfce06cac74_u64) + put(array, 0x95527a5202df0ccb_u64); put(array, 0xf37801e0c43ebc8_u64) + put(array, 0xbaa718e68396cffd_u64); put(array, 0xd30560258f54e6ba_u64) + put(array, 0xe950df20247c83fd_u64); put(array, 0x47c6b82ef32a2069_u64) + put(array, 0x91d28b7416cdd27e_u64); put(array, 0x4cdc331d57fa5441_u64) + put(array, 0xb6472e511c81471d_u64); put(array, 0xe0133fe4adf8e952_u64) + put(array, 0xe3d8f9e563a198e5_u64); put(array, 0x58180fddd97723a6_u64) + put(array, 0x8e679c2f5e44ff8f_u64); put(array, 0x570f09eaa7ea7648_u64) + array + end + end +end diff --git a/src/float/fast_float/float_common.cr b/src/float/fast_float/float_common.cr new file mode 100644 index 000000000000..a66dc99f82f7 --- /dev/null +++ b/src/float/fast_float/float_common.cr @@ -0,0 +1,294 @@ +module Float::FastFloat + @[Flags] + enum CharsFormat + Scientific = 1 << 0 + Fixed = 1 << 2 + Hex = 1 << 3 + NoInfnan = 1 << 4 + JsonFmt = 1 << 5 + FortranFmt = 1 << 6 + + # RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 + Json = JsonFmt | Fixed | Scientific | NoInfnan + + # Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. + JsonOrInfnan = JsonFmt | Fixed | Scientific + + Fortran = FortranFmt | Fixed | Scientific + General = Fixed | Scientific + end + + # NOTE(crystal): uses `Errno` to represent C++'s `std::errc` + record FromCharsResultT(UC), ptr : UC*, ec : Errno + + alias FromCharsResult = FromCharsResultT(UInt8) + + record ParseOptionsT(UC), format : CharsFormat = :general, decimal_point : UC = 0x2E # '.'.ord + + alias ParseOptions = ParseOptionsT(UInt8) + + # rust style `try!()` macro, or `?` operator + macro fastfloat_try(x) + unless {{ x }} + return false + end + end + + # Compares two ASCII strings in a case insensitive manner. + def self.fastfloat_strncasecmp(input1 : UC*, input2 : UC*, length : Int) : Bool forall UC + running_diff = 0_u8 + length.times do |i| + running_diff |= input1[i].to_u8! ^ input2[i].to_u8! + end + running_diff.in?(0_u8, 32_u8) + end + + record Value128, low : UInt64, high : UInt64 do + def self.new(x : UInt128) : self + new(low: x.to_u64!, high: x.unsafe_shr(64).to_u64!) + end + end + + struct AdjustedMantissa + property mantissa : UInt64 + property power2 : Int32 + + def initialize(@mantissa : UInt64 = 0, @power2 : Int32 = 0) + end + end + + INVALID_AM_BIAS = -0x8000 + + CONSTANT_55555 = 3125_u64 + + module BinaryFormat(T, EquivUint) + end + + struct BinaryFormat_Float64 + include BinaryFormat(Float64, UInt64) + + POWERS_OF_TEN = [ + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, + ] + + # Largest integer value v so that (5**index * v) <= 1<<53. + # 0x20000000000000 == 1 << 53 + MAX_MANTISSA = [ + 0x20000000000000_u64, + 0x20000000000000_u64.unsafe_div(5), + 0x20000000000000_u64.unsafe_div(5 * 5), + 0x20000000000000_u64.unsafe_div(5 * 5 * 5), + 0x20000000000000_u64.unsafe_div(5 * 5 * 5 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5 * 5 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * 5 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * 5 * 5 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5 * 5 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5 * 5), + 0x20000000000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * CONSTANT_55555 * 5 * 5 * 5 * 5), + ] + + def min_exponent_fast_path : Int32 + -22 + end + + def mantissa_explicit_bits : Int32 + 52 + end + + def max_exponent_round_to_even : Int32 + 23 + end + + def min_exponent_round_to_even : Int32 + -4 + end + + def minimum_exponent : Int32 + -1023 + end + + def infinite_power : Int32 + 0x7FF + end + + def sign_index : Int32 + 63 + end + + def max_exponent_fast_path : Int32 + 22 + end + + def max_mantissa_fast_path : UInt64 + 0x20000000000000_u64 + end + + def max_mantissa_fast_path(power : Int64) : UInt64 + # caller is responsible to ensure that + # power >= 0 && power <= 22 + MAX_MANTISSA.unsafe_fetch(power) + end + + def exact_power_of_ten(power : Int64) : Float64 + POWERS_OF_TEN.unsafe_fetch(power) + end + + def largest_power_of_ten : Int32 + 308 + end + + def smallest_power_of_ten : Int32 + -342 + end + + def max_digits : Int32 + 769 + end + + def exponent_mask : EquivUint + 0x7FF0000000000000_u64 + end + + def mantissa_mask : EquivUint + 0x000FFFFFFFFFFFFF_u64 + end + + def hidden_bit_mask : EquivUint + 0x0010000000000000_u64 + end + end + + struct BinaryFormat_Float32 + include BinaryFormat(Float32, UInt32) + + POWERS_OF_TEN = [ + 1e0f32, 1e1f32, 1e2f32, 1e3f32, 1e4f32, 1e5f32, 1e6f32, 1e7f32, 1e8f32, 1e9f32, 1e10f32, + ] + + # Largest integer value v so that (5**index * v) <= 1<<24. + # 0x1000000 == 1<<24 + MAX_MANTISSA = [ + 0x1000000_u64, + 0x1000000_u64.unsafe_div(5), + 0x1000000_u64.unsafe_div(5 * 5), + 0x1000000_u64.unsafe_div(5 * 5 * 5), + 0x1000000_u64.unsafe_div(5 * 5 * 5 * 5), + 0x1000000_u64.unsafe_div(CONSTANT_55555), + 0x1000000_u64.unsafe_div(CONSTANT_55555 * 5), + 0x1000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5), + 0x1000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5 * 5), + 0x1000000_u64.unsafe_div(CONSTANT_55555 * 5 * 5 * 5 * 5), + 0x1000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555), + 0x1000000_u64.unsafe_div(CONSTANT_55555 * CONSTANT_55555 * 5), + ] + + def min_exponent_fast_path : Int32 + -10 + end + + def mantissa_explicit_bits : Int32 + 23 + end + + def max_exponent_round_to_even : Int32 + 10 + end + + def min_exponent_round_to_even : Int32 + -17 + end + + def minimum_exponent : Int32 + -127 + end + + def infinite_power : Int32 + 0xFF + end + + def sign_index : Int32 + 31 + end + + def max_exponent_fast_path : Int32 + 10 + end + + def max_mantissa_fast_path : UInt64 + 0x1000000_u64 + end + + def max_mantissa_fast_path(power : Int64) : UInt64 + # caller is responsible to ensure that + # power >= 0 && power <= 10 + MAX_MANTISSA.unsafe_fetch(power) + end + + def exact_power_of_ten(power : Int64) : Float32 + POWERS_OF_TEN.unsafe_fetch(power) + end + + def largest_power_of_ten : Int32 + 38 + end + + def smallest_power_of_ten : Int32 + -64 + end + + def max_digits : Int32 + 114 + end + + def exponent_mask : EquivUint + 0x7F800000_u32 + end + + def mantissa_mask : EquivUint + 0x007FFFFF_u32 + end + + def hidden_bit_mask : EquivUint + 0x00800000_u32 + end + end + + module BinaryFormat(T, EquivUint) + # NOTE(crystal): returns the new *value* by value + def to_float(negative : Bool, am : AdjustedMantissa) : T + word = EquivUint.new!(am.mantissa) + word |= EquivUint.new!(am.power2).unsafe_shl(mantissa_explicit_bits) + word |= EquivUint.new!(negative ? 1 : 0).unsafe_shl(sign_index) + word.unsafe_as(T) + end + end + + def self.int_cmp_zeros(uc : UC.class) : UInt64 forall UC + case sizeof(UC) + when 1 + 0x3030303030303030_u64 + when 2 + 0x0030003000300030_u64 + else + 0x0000003000000030_u64 + end + end + + def self.int_cmp_len(uc : UC.class) : Int32 forall UC + sizeof(UInt64).unsafe_div(sizeof(UC)) + end +end diff --git a/src/float/fast_float/parse_number.cr b/src/float/fast_float/parse_number.cr new file mode 100644 index 000000000000..3c1ac4c1cb24 --- /dev/null +++ b/src/float/fast_float/parse_number.cr @@ -0,0 +1,197 @@ +require "./ascii_number" +require "./decimal_to_binary" +require "./digit_comparison" +require "./float_common" + +module Float::FastFloat + module Detail + def self.parse_infnan(first : UC*, last : UC*, value : T*) : FromCharsResultT(UC) forall T, UC + ptr = first + ec = Errno::NONE # be optimistic + minus_sign = false + if first.value === '-' # assume first < last, so dereference without checks + minus_sign = true + first += 1 + elsif first.value === '+' + first += 1 + end + + if last - first >= 3 + if FastFloat.fastfloat_strncasecmp(first, "nan".to_unsafe, 3) + first += 3 + ptr = first + value.value = minus_sign ? -T::NAN : T::NAN + # Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, + # C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if first != last && first.value === '(' + ptr2 = first + 1 + while ptr2 != last + case ptr2.value.unsafe_chr + when ')' + ptr = ptr2 + 1 # valid nan(n-char-seq-opt) + break + when 'a'..'z', 'A'..'Z', '0'..'9', '_' + # Do nothing + else + break # forbidden char, not nan(n-char-seq-opt) + end + ptr2 += 1 + end + end + return FromCharsResultT(UC).new(ptr, ec) + end + end + if FastFloat.fastfloat_strncasecmp(first, "inf".to_unsafe, 3) + if last - first >= 8 && FastFloat.fastfloat_strncasecmp(first + 3, "inity".to_unsafe, 5) + ptr = first + 8 + else + ptr = first + 3 + end + value.value = minus_sign ? -T::INFINITY : T::INFINITY + return FromCharsResultT(UC).new(ptr, ec) + end + + ec = Errno::EINVAL + FromCharsResultT(UC).new(ptr, ec) + end + + # See + # A fast function to check your floating-point rounding mode + # https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ + # + # This function is meant to be equivalent to : + # prior: #include + # return fegetround() == FE_TONEAREST; + # However, it is expected to be much faster than the fegetround() + # function call. + # + # NOTE(crystal): uses a pointer instead of a volatile variable to prevent + # LLVM optimization + @@fmin : Float32* = Pointer(Float32).malloc(1, Float32::MIN_POSITIVE) + + # Returns true if the floating-pointing rounding mode is to 'nearest'. + # It is the default on most system. This function is meant to be inexpensive. + # Credit : @mwalcott3 + def self.rounds_to_nearest? : Bool + fmin = @@fmin.value # we copy it so that it gets loaded at most once. + + # Explanation: + # Only when fegetround() == FE_TONEAREST do we have that + # fmin + 1.0f == 1.0f - fmin. + # + # FE_UPWARD: + # fmin + 1.0f > 1 + # 1.0f - fmin == 1 + # + # FE_DOWNWARD or FE_TOWARDZERO: + # fmin + 1.0f == 1 + # 1.0f - fmin < 1 + # + # Note: This may fail to be accurate if fast-math has been + # enabled, as rounding conventions may not apply. + fmin + 1.0_f32 == 1.0_f32 - fmin + end + end + + module BinaryFormat(T, EquivUint) + def from_chars_advanced(pns : ParsedNumberStringT(UC), value : T*) : FromCharsResultT(UC) forall UC + {% raise "only some floating-point types are supported" unless T == Float32 || T == Float64 %} + + # TODO(crystal): support UInt16 and UInt32 + {% raise "only UInt8 is supported" unless UC == UInt8 %} + + ec = Errno::NONE # be optimistic + ptr = pns.lastmatch + # The implementation of the Clinger's fast path is convoluted because + # we want round-to-nearest in all cases, irrespective of the rounding mode + # selected on the thread. + # We proceed optimistically, assuming that detail::rounds_to_nearest() + # returns true. + if (min_exponent_fast_path <= pns.exponent <= max_exponent_fast_path) && !pns.too_many_digits + # Unfortunately, the conventional Clinger's fast path is only possible + # when the system rounds to the nearest float. + # + # We expect the next branch to almost always be selected. + # We could check it first (before the previous branch), but + # there might be performance advantages at having the check + # be last. + if Detail.rounds_to_nearest? + # We have that fegetround() == FE_TONEAREST. + # Next is Clinger's fast path. + if pns.mantissa <= max_mantissa_fast_path + if pns.mantissa == 0 + value.value = pns.negative ? T.new(-0.0) : T.new(0.0) + return FromCharsResultT(UC).new(ptr, ec) + end + value.value = T.new(pns.mantissa) + if pns.exponent < 0 + value.value /= exact_power_of_ten(0_i64 &- pns.exponent) + else + value.value *= exact_power_of_ten(pns.exponent) + end + if pns.negative + value.value = -value.value + end + return FromCharsResultT(UC).new(ptr, ec) + end + else + # We do not have that fegetround() == FE_TONEAREST. + # Next is a modified Clinger's fast path, inspired by Jakub Jelínek's + # proposal + if pns.exponent >= 0 && pns.mantissa <= max_mantissa_fast_path(pns.exponent) + # Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD + if pns.mantissa == 0 + value.value = pns.negative ? T.new(-0.0) : T.new(0.0) + return FromCharsResultT(UC).new(ptr, ec) + end + value.value = T.new(pns.mantissa) * exact_power_of_ten(pns.exponent) + if pns.negative + value.value = -value.value + end + return FromCharsResultT(UC).new(ptr, ec) + end + end + end + am = compute_float(pns.exponent, pns.mantissa) + if pns.too_many_digits && am.power2 >= 0 + if am != compute_float(pns.exponent, pns.mantissa &+ 1) + am = compute_error(pns.exponent, pns.mantissa) + end + end + # If we called compute_float>(pns.exponent, pns.mantissa) + # and we have an invalid power (am.power2 < 0), then we need to go the long + # way around again. This is very uncommon. + if am.power2 < 0 + am = digit_comp(pns, am) + end + value.value = to_float(pns.negative, am) + # Test for over/underflow. + if (pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == infinite_power + ec = Errno::ERANGE + end + FromCharsResultT(UC).new(ptr, ec) + end + + def from_chars_advanced(first : UC*, last : UC*, value : T*, options : ParseOptionsT(UC)) : FromCharsResultT(UC) forall UC + {% raise "only some floating-point types are supported" unless T == Float32 || T == Float64 %} + + # TODO(crystal): support UInt16 and UInt32 + {% raise "only UInt8 is supported" unless UC == UInt8 %} + + if first == last + return FromCharsResultT(UC).new(first, Errno::EINVAL) + end + pns = FastFloat.parse_number_string(first, last, options) + if !pns.valid + if options.format.no_infnan? + return FromCharsResultT(UC).new(first, Errno::EINVAL) + else + return Detail.parse_infnan(first, last, value) + end + end + + # call overload that takes parsed_number_string_t directly. + from_chars_advanced(pns, value) + end + end +end diff --git a/src/lib_c/x86_64-windows-msvc/c/stdlib.cr b/src/lib_c/x86_64-windows-msvc/c/stdlib.cr index 63c38003fd6a..140e49a229a7 100644 --- a/src/lib_c/x86_64-windows-msvc/c/stdlib.cr +++ b/src/lib_c/x86_64-windows-msvc/c/stdlib.cr @@ -11,13 +11,13 @@ lib LibC fun free(ptr : Void*) : Void fun malloc(size : SizeT) : Void* fun realloc(ptr : Void*, size : SizeT) : Void* - fun strtof(nptr : Char*, endptr : Char**) : Float - fun strtod(nptr : Char*, endptr : Char**) : Double alias InvalidParameterHandler = WCHAR*, WCHAR*, WCHAR*, UInt, UIntPtrT -> fun _set_invalid_parameter_handler(pNew : InvalidParameterHandler) : InvalidParameterHandler # unused + fun strtof(nptr : Char*, endptr : Char**) : Float + fun strtod(nptr : Char*, endptr : Char**) : Double fun atof(nptr : Char*) : Double fun div(numer : Int, denom : Int) : DivT fun putenv(string : Char*) : Int diff --git a/src/string.cr b/src/string.cr index d47e87638976..9bc9d0c22701 100644 --- a/src/string.cr +++ b/src/string.cr @@ -1,9 +1,9 @@ -require "c/stdlib" require "c/string" require "crystal/small_deque" {% unless flag?(:without_iconv) %} require "crystal/iconv" {% end %} +require "float/fast_float" # A `String` represents an immutable sequence of UTF-8 characters. # @@ -738,10 +738,7 @@ class String # :ditto: def to_f64?(whitespace : Bool = true, strict : Bool = true) : Float64? - to_f_impl(whitespace: whitespace, strict: strict) do - v = LibC.strtod self, out endptr - {v, endptr} - end + Float::FastFloat.to_f64?(self, whitespace, strict) end # Same as `#to_f` but returns a Float32. @@ -751,59 +748,7 @@ class String # Same as `#to_f?` but returns a Float32. def to_f32?(whitespace : Bool = true, strict : Bool = true) : Float32? - to_f_impl(whitespace: whitespace, strict: strict) do - v = LibC.strtof self, out endptr - {v, endptr} - end - end - - private def to_f_impl(whitespace : Bool = true, strict : Bool = true, &) - return unless first_char = self[0]? - return unless whitespace || '0' <= first_char <= '9' || first_char.in?('-', '+', 'i', 'I', 'n', 'N') - - v, endptr = yield - - unless v.finite? - startptr = to_unsafe - if whitespace - while startptr.value.unsafe_chr.ascii_whitespace? - startptr += 1 - end - end - if startptr.value.unsafe_chr.in?('+', '-') - startptr += 1 - end - - if v.nan? - return unless startptr.value.unsafe_chr.in?('n', 'N') - else - return unless startptr.value.unsafe_chr.in?('i', 'I') - end - end - - string_end = to_unsafe + bytesize - - # blank string - return if endptr == to_unsafe - - if strict - if whitespace - while endptr < string_end && endptr.value.unsafe_chr.ascii_whitespace? - endptr += 1 - end - end - # reached the end of the string - v if endptr == string_end - else - ptr = to_unsafe - if whitespace - while ptr < string_end && ptr.value.unsafe_chr.ascii_whitespace? - ptr += 1 - end - end - # consumed some bytes - v if endptr > ptr - end + Float::FastFloat.to_f32?(self, whitespace, strict) end # Returns the `Char` at the given *index*. @@ -2166,7 +2111,8 @@ class String remove_excess_left(excess_left) end - private def calc_excess_right + # :nodoc: + def calc_excess_right if single_byte_optimizable? i = bytesize - 1 while i >= 0 && to_unsafe[i].unsafe_chr.ascii_whitespace? @@ -2204,7 +2150,8 @@ class String bytesize - byte_index end - private def calc_excess_left + # :nodoc: + def calc_excess_left if single_byte_optimizable? excess_left = 0 # All strings end with '\0', and it's not a whitespace 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 1542/1551] 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 1543/1551] 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 1544/1551] 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 1545/1551] 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 1546/1551] 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 1547/1551] 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 1548/1551] 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 1549/1551] 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 1550/1551] 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 From 11cf206c6357eabf647584314b3bb09d13996cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 2 Jan 2025 16:21:48 +0100 Subject: [PATCH 1551/1551] Bump NOTICE copyright year (#15318) --- NOTICE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NOTICE.md b/NOTICE.md index bb0120eb54a6..87bc5a34a7f7 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1,6 +1,6 @@ # Crystal Programming Language -Copyright 2012-2024 Manas Technology Solutions. +Copyright 2012-2025 Manas Technology Solutions. This product includes software developed at Manas Technology Solutions ().